From 2056e5d487300c26ce53b59605f9d41c0f9ebfe3 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle Date: Fri, 6 May 2022 10:15:15 +0200 Subject: [PATCH 001/117] Run code cleanup --- .../Actions/ActionAssignDomain.cs | 14 +- src/Umbraco.Core/Actions/ActionBrowse.cs | 67 +- src/Umbraco.Core/Actions/ActionCollection.cs | 84 +- .../Actions/ActionCollectionBuilder.cs | 43 +- src/Umbraco.Core/Actions/ActionCopy.cs | 41 +- .../ActionCreateBlueprintFromContent.cs | 37 +- src/Umbraco.Core/Actions/ActionDelete.cs | 49 +- src/Umbraco.Core/Actions/ActionMove.cs | 41 +- src/Umbraco.Core/Actions/ActionNew.cs | 49 +- src/Umbraco.Core/Actions/ActionNotify.cs | 37 +- src/Umbraco.Core/Actions/ActionProtect.cs | 41 +- src/Umbraco.Core/Actions/ActionPublish.cs | 41 +- src/Umbraco.Core/Actions/ActionRestore.cs | 41 +- src/Umbraco.Core/Actions/ActionRights.cs | 41 +- src/Umbraco.Core/Actions/ActionRollback.cs | 41 +- src/Umbraco.Core/Actions/ActionSort.cs | 41 +- src/Umbraco.Core/Actions/ActionToPublish.cs | 41 +- src/Umbraco.Core/Actions/ActionUnpublish.cs | 42 +- src/Umbraco.Core/Actions/ActionUpdate.cs | 41 +- src/Umbraco.Core/Actions/IAction.cs | 75 +- src/Umbraco.Core/Attempt.cs | 200 +- src/Umbraco.Core/AttemptOfTResult.cs | 216 +- src/Umbraco.Core/AttemptOfTResultTStatus.cs | 227 +- src/Umbraco.Core/Cache/AppCacheExtensions.cs | 100 +- src/Umbraco.Core/Cache/AppCaches.cs | 158 +- .../Cache/AppPolicedCacheDictionary.cs | 137 +- .../Cache/ApplicationCacheRefresher.cs | 59 +- src/Umbraco.Core/Cache/CacheKeys.cs | 39 +- src/Umbraco.Core/Cache/CacheRefresherBase.cs | 208 +- .../Cache/CacheRefresherCollection.cs | 18 +- .../Cache/CacheRefresherCollectionBuilder.cs | 10 +- .../CacheRefresherNotificationFactory.cs | 34 +- .../Cache/ContentCacheRefresher.cs | 256 +- .../Cache/ContentTypeCacheRefresher.cs | 194 +- .../Cache/DataTypeCacheRefresher.cs | 163 +- src/Umbraco.Core/Cache/DeepCloneAppCache.cs | 246 +- src/Umbraco.Core/Cache/DictionaryAppCache.cs | 154 +- .../Cache/DictionaryCacheRefresher.cs | 48 +- src/Umbraco.Core/Cache/DistributedCache.cs | 298 +- .../Cache/DomainCacheRefresher.cs | 102 +- .../Cache/FastDictionaryAppCache.cs | 258 +- .../Cache/FastDictionaryAppCacheBase.cs | 446 +- src/Umbraco.Core/Cache/IAppCache.cs | 160 +- src/Umbraco.Core/Cache/IAppPolicyCache.cs | 73 +- src/Umbraco.Core/Cache/ICacheRefresher.cs | 56 +- .../ICacheRefresherNotificationFactory.cs | 20 +- src/Umbraco.Core/Cache/IJsonCacheRefresher.cs | 19 +- .../Cache/IPayloadCacheRefresher.cs | 19 +- .../Cache/IRepositoryCachePolicy.cs | 129 +- src/Umbraco.Core/Cache/IRequestCache.cs | 19 +- src/Umbraco.Core/Cache/IValueEditorCache.cs | 14 +- src/Umbraco.Core/Cache/IsolatedCaches.cs | 64 +- .../Cache/JsonCacheRefresherBase.cs | 93 +- .../Cache/LanguageCacheRefresher.cs | 233 +- src/Umbraco.Core/Cache/MacroCacheRefresher.cs | 143 +- src/Umbraco.Core/Cache/MediaCacheRefresher.cs | 166 +- .../Cache/MemberCacheRefresher.cs | 124 +- .../Cache/MemberGroupCacheRefresher.cs | 98 +- src/Umbraco.Core/Cache/NoAppCache.cs | 132 +- .../Cache/NoCacheRepositoryCachePolicy.cs | 75 +- src/Umbraco.Core/Cache/ObjectCacheAppCache.cs | 572 +- .../Cache/PayloadCacheRefresherBase.cs | 67 +- .../Cache/PublicAccessCacheRefresher.cs | 68 +- .../Cache/RelationTypeCacheRefresher.cs | 67 +- .../Cache/RepositoryCachePolicyOptions.cs | 81 +- src/Umbraco.Core/Cache/SafeLazy.cs | 94 +- .../Cache/TemplateCacheRefresher.cs | 88 +- src/Umbraco.Core/Cache/UserCacheRefresher.cs | 73 +- .../Cache/UserGroupCacheRefresher.cs | 94 +- src/Umbraco.Core/Cache/ValueEditorCache.cs | 73 +- .../Cache/ValueEditorCacheRefresher.cs | 83 +- .../CodeAnnotations/FriendlyNameAttribute.cs | 47 +- .../UmbracoObjectTypeAttribute.cs | 34 +- .../UmbracoUdiTypeAttribute.cs | 16 +- .../Collections/CompositeIntStringKey.cs | 61 +- .../Collections/CompositeNStringNStringKey.cs | 57 +- .../Collections/CompositeStringStringKey.cs | 57 +- .../Collections/CompositeTypeTypeKey.cs | 82 +- .../Collections/ConcurrentHashSet.cs | 386 +- .../Collections/DeepCloneableList.cs | 229 +- .../EventClearingObservableCollection.cs | 63 +- .../Collections/ListCloneBehavior.cs | 31 +- .../Collections/ObservableDictionary.cs | 368 +- .../Collections/OrderedHashSet.cs | 61 +- src/Umbraco.Core/Collections/StackQueue.cs | 63 +- src/Umbraco.Core/Collections/TopoGraph.cs | 208 +- src/Umbraco.Core/Collections/TypeList.cs | 45 +- .../Composing/BuilderCollectionBase.cs | 48 +- .../Composing/CollectionBuilderBase.cs | 258 +- .../Composing/ComponentCollection.cs | 82 +- .../Composing/ComponentCollectionBuilder.cs | 52 +- .../Composing/ComponentComposer.cs | 30 +- .../Composing/ComposeAfterAttribute.cs | 103 +- .../Composing/ComposeBeforeAttribute.cs | 66 +- src/Umbraco.Core/Composing/ComposerGraph.cs | 568 +- .../Composing/CompositionExtensions.cs | 68 +- .../DefaultUmbracoAssemblyProvider.cs | 91 +- .../Composing/DisableAttribute.cs | 69 +- .../Composing/DisableComposerAttribute.cs | 42 +- src/Umbraco.Core/Composing/EnableAttribute.cs | 60 +- .../Composing/EnableComposerAttribute.cs | 51 +- .../FindAssembliesWithReferencesTo.cs | 100 +- .../Composing/HideFromTypeFinderAttribute.cs | 14 +- .../Composing/IAssemblyProvider.cs | 18 +- .../Composing/IBuilderCollection.cs | 19 +- .../Composing/ICollectionBuilder.cs | 48 +- src/Umbraco.Core/Composing/IComponent.cs | 43 +- src/Umbraco.Core/Composing/IComposer.cs | 17 +- src/Umbraco.Core/Composing/IDiscoverable.cs | 6 +- src/Umbraco.Core/Composing/IRuntimeHash.cs | 23 +- src/Umbraco.Core/Composing/ITypeFinder.cs | 88 +- src/Umbraco.Core/Composing/IUserComposer.cs | 14 +- .../Composing/LazyCollectionBuilderBase.cs | 216 +- .../Composing/LazyReadOnlyCollection.cs | 62 +- src/Umbraco.Core/Composing/LazyResolve.cs | 15 +- .../Composing/OrderedCollectionBuilderBase.cs | 658 +- .../Composing/ReferenceResolver.cs | 293 +- src/Umbraco.Core/Composing/RuntimeHash.cs | 127 +- .../Composing/RuntimeHashPaths.cs | 60 +- .../Composing/SetCollectionBuilderBase.cs | 325 +- .../Composing/TypeCollectionBuilderBase.cs | 98 +- src/Umbraco.Core/Composing/TypeFinder.cs | 797 +- .../Composing/TypeFinderConfig.cs | 42 +- .../Composing/TypeFinderExtensions.cs | 80 +- src/Umbraco.Core/Composing/TypeHelper.cs | 640 +- src/Umbraco.Core/Composing/TypeLoader.cs | 767 +- .../Composing/VaryingRuntimeHash.cs | 22 +- src/Umbraco.Core/Composing/WeightAttribute.cs | 32 +- .../WeightedCollectionBuilderBase.cs | 238 +- .../Configuration/ConfigConnectionString.cs | 1 + .../ConfigureConnectionStrings.cs | 7 +- .../ContentSettingsExtensions.cs | 50 +- .../Configuration/EntryAssemblyMetadata.cs | 29 +- .../HealthCheckSettingsExtensions.cs | 37 +- .../Configuration/GlobalSettingsExtensions.cs | 97 +- .../Configuration/Grid/GridConfig.cs | 17 +- .../Configuration/Grid/GridEditorsConfig.cs | 119 +- .../Configuration/Grid/IGridConfig.cs | 11 +- .../Configuration/Grid/IGridEditorConfig.cs | 21 +- .../Configuration/Grid/IGridEditorsConfig.cs | 9 +- .../Configuration/IConfigManipulator.cs | 17 +- .../Configuration/ICronTabParser.cs | 35 +- .../Configuration/IEntryAssemblyMetadata.cs | 25 +- .../IMemberPasswordConfiguration.cs | 13 +- .../Configuration/IPasswordConfiguration.cs | 82 +- .../Configuration/ITypeFinderSettings.cs | 9 +- .../IUmbracoConfigurationSection.cs | 14 +- .../Configuration/IUmbracoVersion.cs | 75 +- .../IUserPasswordConfiguration.cs | 13 +- .../Configuration/LocalTempStorage.cs | 13 +- .../MemberPasswordConfiguration.cs | 17 +- .../Models/ActiveDirectorySettings.cs | 19 +- .../Attributes/UmbracoOptionsAttribute.cs | 18 +- .../Configuration/Models/BasicAuthSettings.cs | 29 +- .../Configuration/Models/CharItem.cs | 23 +- .../Configuration/Models/ConnectionStrings.cs | 10 +- .../Models/ContentDashboardSettings.cs | 53 +- .../Configuration/Models/ContentErrorPage.cs | 88 +- .../Models/ContentImagingSettings.cs | 54 +- .../Models/ContentNotificationSettings.cs | 31 +- .../Configuration/Models/ContentSettings.cs | 209 +- .../ContentVersionCleanupPolicySettings.cs | 48 +- .../Configuration/Models/CoreDebugSettings.cs | 37 +- .../Models/DatabaseServerMessengerSettings.cs | 62 +- .../Models/DatabaseServerRegistrarSettings.cs | 36 +- .../Models/DisabledHealthCheckSettings.cs | 33 +- .../Models/ExceptionFilterSettings.cs | 25 +- .../Configuration/Models/GlobalSettings.cs | 443 +- .../HealthChecksNotificationMethodSettings.cs | 57 +- .../HealthChecksNotificationSettings.cs | 72 +- .../Models/HealthChecksSettings.cs | 29 +- .../Configuration/Models/HelpPageSettings.cs | 17 +- .../Configuration/Models/HostingSettings.cs | 56 +- .../Models/ImagingAutoFillUploadField.cs | 59 +- .../Models/ImagingCacheSettings.cs | 81 +- .../Models/ImagingResizeSettings.cs | 35 +- .../Configuration/Models/ImagingSettings.cs | 27 +- .../Models/IndexCreatorSettings.cs | 20 +- .../Models/InstallDefaultDataSettings.cs | 117 +- .../Configuration/Models/KeepAliveSettings.cs | 37 +- .../Models/LegacyPasswordMigrationSettings.cs | 39 +- .../Configuration/Models/LoggingSettings.cs | 26 +- .../Models/LuceneDirectoryFactory.cs | 33 +- .../MemberPasswordConfigurationSettings.cs | 83 +- .../Models/ModelsBuilderSettings.cs | 124 +- .../Models/NuCacheSerializerType.cs | 17 +- .../Configuration/Models/NuCacheSettings.cs | 68 +- .../Models/PackageMigrationSettings.cs | 63 +- .../Models/RequestHandlerSettings.cs | 131 +- .../Models/RichTextEditorSettings.cs | 229 +- .../Models/RuntimeMinificationCacheBuster.cs | 13 +- .../Models/RuntimeMinificationSettings.cs | 46 +- .../Configuration/Models/RuntimeSettings.cs | 27 +- .../Configuration/Models/SecuritySettings.cs | 132 +- .../Configuration/Models/SmtpSettings.cs | 166 +- .../Configuration/Models/TourSettings.cs | 25 +- .../Models/TypeFinderSettings.cs | 33 +- .../Models/UmbracoPluginSettings.cs | 39 +- .../Models/UnattendedSettings.cs | 101 +- .../UserPasswordConfigurationSettings.cs | 83 +- .../Validation/ConfigurationValidatorBase.cs | 113 +- .../Validation/ContentSettingsValidator.cs | 51 +- .../Validation/GlobalSettingsValidator.cs | 70 +- .../HealthChecksSettingsValidator.cs | 61 +- .../RequestHandlerSettingsValidator.cs | 37 +- .../Validation/UnattendedSettingsValidator.cs | 56 +- .../Models/Validation/ValidatableEntryBase.cs | 22 +- .../Models/WebRoutingSettings.cs | 132 +- .../ModelsBuilderConfigExtensions.cs | 79 +- src/Umbraco.Core/Configuration/ModelsMode.cs | 67 +- .../Configuration/ModelsModeExtensions.cs | 39 +- .../Configuration/PasswordConfiguration.cs | 45 +- .../CharacterReplacementEqualityComparer.cs | 53 +- .../Configuration/UmbracoSettings/IChar.cs | 11 +- .../IImagingAutoFillUploadField.cs | 23 +- .../IPasswordConfigurationSection.cs | 23 +- .../UmbracoSettings/ITypeFinderConfig.cs | 9 +- .../Configuration/UmbracoVersion.cs | 108 +- .../UserPasswordConfiguration.cs | 17 +- src/Umbraco.Core/Constants-Applications.cs | 251 +- src/Umbraco.Core/Constants-CharArrays.cs | 266 +- src/Umbraco.Core/Constants-Composing.cs | 30 +- src/Umbraco.Core/Constants-Configuration.cs | 135 +- src/Umbraco.Core/Constants-Conventions.cs | 676 +- src/Umbraco.Core/Constants-DataTypes.cs | 744 +- src/Umbraco.Core/Constants-DeploySelector.cs | 25 +- src/Umbraco.Core/Constants-HealthChecks.cs | 82 +- src/Umbraco.Core/Constants-HttpClients.cs | 21 +- .../Constants-HttpContextItemsKeys.cs | 21 +- src/Umbraco.Core/Constants-Icons.cs | 277 +- src/Umbraco.Core/Constants-Indexes.cs | 15 +- src/Umbraco.Core/Constants-ModelsBuilder.cs | 19 +- src/Umbraco.Core/Constants-ObjectTypes.cs | 135 +- .../Constants-PackageRepository.cs | 21 +- src/Umbraco.Core/Constants-PropertyEditors.cs | 426 +- .../Constants-PropertyTypeGroups.cs | 67 +- src/Umbraco.Core/Constants-Security.cs | 148 +- src/Umbraco.Core/Constants-Sql.cs | 27 +- src/Umbraco.Core/Constants-SqlTemplates.cs | 84 +- src/Umbraco.Core/Constants-System.cs | 104 +- .../Constants-SystemDirectories.cs | 92 +- src/Umbraco.Core/Constants-Telemetry.cs | 54 +- src/Umbraco.Core/Constants-UdiEntityType.cs | 105 +- src/Umbraco.Core/Constants-Web.cs | 114 +- .../ContentAppFactoryCollection.cs | 85 +- .../ContentAppFactoryCollectionBuilder.cs | 52 +- .../ContentEditorContentAppFactory.cs | 84 +- .../ContentInfoContentAppFactory.cs | 84 +- .../ContentTypeDesignContentAppFactory.cs | 40 +- .../ContentTypeListViewContentAppFactory.cs | 40 +- ...ContentTypePermissionsContentAppFactory.cs | 40 +- .../ContentTypeTemplatesContentAppFactory.cs | 40 +- .../DictionaryContentAppFactory.cs | 40 +- .../ContentApps/ListViewContentAppFactory.cs | 219 +- .../MemberEditorContentAppFactory.cs | 44 +- src/Umbraco.Core/ConventionsHelper.cs | 34 +- .../CustomBooleanTypeConverter.cs | 53 +- src/Umbraco.Core/Dashboards/AccessRule.cs | 22 +- src/Umbraco.Core/Dashboards/AccessRuleType.cs | 41 +- .../Dashboards/AnalyticsDashboard.cs | 15 +- .../Dashboards/ContentDashboard.cs | 18 +- .../Dashboards/DashboardCollection.cs | 11 +- .../Dashboards/DashboardCollectionBuilder.cs | 59 +- src/Umbraco.Core/Dashboards/DashboardSlim.cs | 13 +- .../Dashboards/ExamineDashboard.cs | 22 +- src/Umbraco.Core/Dashboards/FormsDashboard.cs | 20 +- .../Dashboards/HealthCheckDashboard.cs | 22 +- src/Umbraco.Core/Dashboards/IAccessRule.cs | 25 +- src/Umbraco.Core/Dashboards/IDashboard.cs | 65 +- src/Umbraco.Core/Dashboards/IDashboardSlim.cs | 29 +- src/Umbraco.Core/Dashboards/MediaDashboard.cs | 20 +- .../Dashboards/MembersDashboard.cs | 20 +- .../Dashboards/ModelsBuilderDashboard.cs | 18 +- .../Dashboards/ProfilerDashboard.cs | 20 +- .../Dashboards/PublishedStatusDashboard.cs | 22 +- .../Dashboards/RedirectUrlDashboard.cs | 20 +- .../Dashboards/SettingsDashboards.cs | 20 +- .../DefaultEventMessagesFactory.cs | 38 +- src/Umbraco.Core/DelegateEqualityComparer.cs | 96 +- .../IScopedServiceProvider.cs | 25 +- .../DependencyInjection/IUmbracoBuilder.cs | 50 +- .../ServiceCollectionExtensions.cs | 201 +- .../ServiceProviderExtensions.cs | 88 +- .../StaticServiceProvider.cs | 38 +- .../UmbracoBuilder.CollectionBuilders.cs | 188 +- .../UmbracoBuilder.Collections.cs | 483 +- .../UmbracoBuilder.Composers.cs | 29 +- .../UmbracoBuilder.Configuration.cs | 181 +- .../UmbracoBuilder.Events.cs | 112 +- .../DependencyInjection/UmbracoBuilder.cs | 467 +- .../UniqueServiceDescriptor.cs | 89 +- src/Umbraco.Core/Deploy/ArtifactBase.cs | 83 +- src/Umbraco.Core/Deploy/ArtifactDependency.cs | 66 +- .../Deploy/ArtifactDependencyCollection.cs | 81 +- .../Deploy/ArtifactDependencyMode.cs | 25 +- .../Deploy/ArtifactDeployState.cs | 78 +- .../ArtifactDeployStateOfTArtifactTEntity.cs | 64 +- src/Umbraco.Core/Deploy/ArtifactSignature.cs | 24 +- src/Umbraco.Core/Deploy/Difference.cs | 42 +- src/Umbraco.Core/Deploy/Direction.cs | 11 +- src/Umbraco.Core/Deploy/IArtifact.cs | 17 +- src/Umbraco.Core/Deploy/IArtifactSignature.cs | 73 +- .../Deploy/IDataTypeConfigurationConnector.cs | 52 +- src/Umbraco.Core/Deploy/IDeployContext.cs | 70 +- src/Umbraco.Core/Deploy/IFileSource.cs | 150 +- src/Umbraco.Core/Deploy/IFileType.cs | 36 +- .../Deploy/IFileTypeCollection.cs | 11 +- src/Umbraco.Core/Deploy/IImageSourceParser.cs | 39 +- src/Umbraco.Core/Deploy/ILocalLinkParser.cs | 42 +- src/Umbraco.Core/Deploy/IMacroParser.cs | 51 +- src/Umbraco.Core/Deploy/IServiceConnector.cs | 143 +- .../IUniqueIdentifyingServiceConnector.cs | 41 +- src/Umbraco.Core/Deploy/IValueConnector.cs | 61 +- src/Umbraco.Core/Diagnostics/IMarchal.cs | 21 +- src/Umbraco.Core/Diagnostics/MiniDump.cs | 238 +- src/Umbraco.Core/Diagnostics/NoopMarchal.cs | 9 +- .../Dictionary/ICultureDictionary.cs | 44 +- .../Dictionary/ICultureDictionaryFactory.cs | 9 +- .../Dictionary/UmbracoCultureDictionary.cs | 213 +- .../UmbracoCultureDictionaryFactory.cs | 40 +- src/Umbraco.Core/Direction.cs | 11 +- src/Umbraco.Core/DisposableObjectSlim.cs | 78 +- .../DistributedLocking/DistributedLockType.cs | 2 +- .../Exceptions/DistributedLockingException.cs | 8 +- .../DistributedLockingTimeoutException.cs | 4 +- .../DistributedReadLockTimeoutException.cs | 4 +- .../DistributedWriteLockTimeoutException.cs | 4 +- .../DistributedLocking/IDistributedLock.cs | 8 +- .../IDistributedLockingMechanism.cs | 46 +- .../IDistributedLockingMechanismFactory.cs | 2 +- .../Editors/BackOfficePreviewModel.cs | 28 +- .../Editors/EditorValidatorCollection.cs | 11 +- .../EditorValidatorCollectionBuilder.cs | 10 +- .../Editors/EditorValidatorOfT.cs | 25 +- src/Umbraco.Core/Editors/IEditorValidator.cs | 50 +- .../Editors/UserEditorAuthorizationHelper.cs | 256 +- .../Snippets/MultinodeTree-picker.cshtml | 2 - src/Umbraco.Core/Enum.cs | 152 +- src/Umbraco.Core/EnvironmentHelper.cs | 19 +- .../CancellableEnumerableObjectEventArgs.cs | 100 +- .../Events/CancellableEventArgs.cs | 229 +- .../Events/CancellableObjectEventArgs.cs | 69 +- ...ancellableObjectEventArgsOfTEventObject.cs | 128 +- .../Events/ContentCacheEventArgs.cs | 7 +- src/Umbraco.Core/Events/CopyEventArgs.cs | 136 +- src/Umbraco.Core/Events/DeleteEventArgs.cs | 305 +- .../Events/EventAggregator.Notifications.cs | 317 +- src/Umbraco.Core/Events/EventAggregator.cs | 175 +- src/Umbraco.Core/Events/EventDefinition.cs | 105 +- .../Events/EventDefinitionBase.cs | 110 +- .../Events/EventDefinitionFilter.cs | 35 +- src/Umbraco.Core/Events/EventExtensions.cs | 73 +- src/Umbraco.Core/Events/EventMessage.cs | 40 +- src/Umbraco.Core/Events/EventMessageType.cs | 23 +- src/Umbraco.Core/Events/EventMessages.cs | 32 +- src/Umbraco.Core/Events/EventNameExtractor.cs | 278 +- .../Events/EventNameExtractorError.cs | 11 +- .../Events/EventNameExtractorResult.cs | 21 +- .../Events/ExportedMemberEventArgs.cs | 22 +- .../Events/IDeletingMediaFilesEventArgs.cs | 9 +- src/Umbraco.Core/Events/IEventAggregator.cs | 77 +- src/Umbraco.Core/Events/IEventDefinition.cs | 15 +- src/Umbraco.Core/Events/IEventDispatcher.cs | 174 +- .../Events/IEventMessagesAccessor.cs | 9 +- .../Events/IEventMessagesFactory.cs | 17 +- .../Events/INotificationAsyncHandler.cs | 29 +- .../Events/INotificationHandler.cs | 23 +- .../Events/IScopedNotificationPublisher.cs | 68 +- .../Events/MacroErrorEventArgs.cs | 67 +- src/Umbraco.Core/Events/MoveEventArgs.cs | 233 +- src/Umbraco.Core/Events/MoveEventInfo.cs | 79 +- src/Umbraco.Core/Events/NewEventArgs.cs | 195 +- .../Events/PassThroughEventDispatcher.cs | 83 +- src/Umbraco.Core/Events/PublishEventArgs.cs | 209 +- .../Events/QueuingEventDispatcher.cs | 53 +- .../Events/QueuingEventDispatcherBase.cs | 582 +- .../Events/RecycleBinEventArgs.cs | 111 +- .../Events/RefreshContentEventArgs.cs | 8 +- .../Events/RelateOnCopyNotificationHandler.cs | 70 +- src/Umbraco.Core/Events/RolesEventArgs.cs | 19 +- src/Umbraco.Core/Events/RollbackEventArgs.cs | 28 +- src/Umbraco.Core/Events/SaveEventArgs.cs | 203 +- .../Events/ScopedNotificationPublisher.cs | 170 +- src/Umbraco.Core/Events/SendEmailEventArgs.cs | 17 +- .../Events/SendToPublishEventArgs.cs | 28 +- .../Events/SupersedeEventAttribute.cs | 27 +- .../Events/TransientEventMessagesFactory.cs | 23 +- src/Umbraco.Core/Events/TypedEventHandler.cs | 9 +- src/Umbraco.Core/Events/UserGroupWithUsers.cs | 23 +- .../Events/UserNotificationsHandler.cs | 367 +- .../Exceptions/AuthorizationException.cs | 81 +- .../Exceptions/BootFailedException.cs | 144 +- .../Exceptions/ConfigurationException.cs | 70 +- .../Exceptions/DataOperationException.cs | 175 +- .../Exceptions/InvalidCompositionException.cs | 320 +- src/Umbraco.Core/Exceptions/PanicException.cs | 82 +- .../Exceptions/UnattendedInstallException.cs | 76 +- src/Umbraco.Core/ExpressionHelper.cs | 669 +- .../Extensions/AssemblyExtensions.cs | 162 +- .../Extensions/ClaimsIdentityExtensions.cs | 605 +- .../Extensions/ConnectionStringExtensions.cs | 44 +- .../Extensions/ContentExtensions.cs | 651 +- .../Extensions/ContentVariationExtensions.cs | 669 +- .../Extensions/CoreCacheHelperExtensions.cs | 27 +- .../Extensions/DataTableExtensions.cs | 176 +- .../Extensions/DateTimeExtensions.cs | 69 +- .../Extensions/DecimalExtensions.cs | 35 +- .../Extensions/DelegateExtensions.cs | 69 +- .../Extensions/DictionaryExtensions.cs | 508 +- src/Umbraco.Core/Extensions/EnumExtensions.cs | 67 +- .../Extensions/EnumerableExtensions.cs | 546 +- .../Extensions/ExpressionExtensions.cs | 30 +- .../Extensions/HostEnvironmentExtensions.cs | 74 +- src/Umbraco.Core/Extensions/IfExtensions.cs | 90 +- src/Umbraco.Core/Extensions/IntExtensions.cs | 47 +- .../Extensions/KeyValuePairExtensions.cs | 23 +- .../Extensions/MediaTypeExtensions.cs | 15 +- .../NameValueCollectionExtensions.cs | 46 +- .../Extensions/ObjectExtensions.cs | 1295 +-- .../PasswordConfigurationExtensions.cs | 53 +- .../Extensions/PublishedContentExtensions.cs | 2518 +++--- .../Extensions/PublishedElementExtensions.cs | 371 +- .../PublishedModelFactoryExtensions.cs | 64 +- .../Extensions/PublishedPropertyExtension.cs | 111 +- .../PublishedSnapshotAccessorExtensions.cs | 19 +- .../RequestHandlerSettingsExtension.cs | 133 +- .../Extensions/RuntimeStateExtensions.cs | 52 +- .../Extensions/SemVersionExtensions.cs | 23 +- .../Extensions/StringExtensions.cs | 2463 +++--- .../Extensions/ThreadExtensions.cs | 88 +- src/Umbraco.Core/Extensions/TypeExtensions.cs | 752 +- .../Extensions/TypeLoaderExtensions.cs | 38 +- .../Extensions/UdiGetterExtensions.cs | 637 +- .../Extensions/UmbracoBuilderExtensions.cs | 142 +- .../UmbracoContextAccessorExtensions.cs | 23 +- .../Extensions/UmbracoContextExtensions.cs | 16 +- src/Umbraco.Core/Extensions/UriExtensions.cs | 304 +- .../Extensions/VersionExtensions.cs | 119 +- .../Extensions/WaitHandleExtensions.cs | 68 +- src/Umbraco.Core/Extensions/XmlExtensions.cs | 600 +- src/Umbraco.Core/Features/DisabledFeatures.cs | 45 +- src/Umbraco.Core/Features/EnabledFeatures.cs | 23 +- src/Umbraco.Core/Features/IUmbracoFeature.cs | 14 +- src/Umbraco.Core/Features/UmbracoFeatures.cs | 55 +- src/Umbraco.Core/GuidUdi.cs | 94 +- src/Umbraco.Core/GuidUtils.cs | 208 +- .../Handlers/AuditNotificationsHandler.cs | 386 +- .../Handlers/PublicAccessHandler.cs | 42 +- src/Umbraco.Core/HashCodeCombiner.cs | 131 +- src/Umbraco.Core/HashCodeHelper.cs | 146 +- src/Umbraco.Core/HashGenerator.cs | 207 +- .../HealthChecks/AcceptableConfiguration.cs | 11 +- .../Checks/AbstractSettingsCheck.cs | 166 +- .../Checks/Configuration/MacroErrorsCheck.cs | 124 +- .../Configuration/NotificationEmailCheck.cs | 95 +- .../Checks/Data/DatabaseIntegrityCheck.cs | 203 +- .../LiveEnvironment/CompilationDebugCheck.cs | 90 +- .../FolderAndFilePermissionsCheck.cs | 145 +- .../Checks/ProvidedValueValidation.cs | 13 +- .../Checks/Security/BaseHttpHeaderCheck.cs | 225 +- .../Checks/Security/ClickJackingCheck.cs | 36 +- .../Checks/Security/ExcessiveHeadersCheck.cs | 141 +- .../HealthChecks/Checks/Security/HstsCheck.cs | 49 +- .../Checks/Security/HttpsCheck.cs | 286 +- .../Checks/Security/NoSniffCheck.cs | 35 +- .../Security/UmbracoApplicationUrlCheck.cs | 100 +- .../Checks/Security/XssProtectionCheck.cs | 50 +- .../HealthChecks/Checks/Services/SmtpCheck.cs | 149 +- .../ConfigurationServiceResult.cs | 11 +- src/Umbraco.Core/HealthChecks/HealthCheck.cs | 91 +- .../HealthChecks/HealthCheckAction.cs | 147 +- .../HealthChecks/HealthCheckAttribute.cs | 33 +- .../HealthChecks/HealthCheckCollection.cs | 11 +- .../HealthChecks/HealthCheckGroup.cs | 18 +- .../HealthCheckNotificationMethodAttribute.cs | 22 +- ...HealthCheckNotificationMethodCollection.cs | 12 +- ...heckNotificationMethodCollectionBuilder.cs | 11 +- .../HealthCheckNotificationVerbosity.cs | 12 +- .../HealthChecks/HealthCheckResults.cs | 257 +- .../HealthChecks/HealthCheckStatus.cs | 103 +- .../HeathCheckCollectionBuilder.cs | 17 +- .../EmailNotificationMethod.cs | 127 +- .../IHealthCheckNotificationMethod.cs | 14 +- .../IMarkdownToHtmlConverter.cs | 9 +- .../NotificationMethodBase.cs | 67 +- .../HealthChecks/StatusResultType.cs | 15 +- .../HealthChecks/ValueComparisonType.cs | 11 +- src/Umbraco.Core/HexEncoder.cs | 131 +- .../Hosting/IApplicationShutdownRegistry.cs | 11 +- .../Hosting/IHostingEnvironment.cs | 181 +- .../Hosting/IUmbracoApplicationLifetime.cs | 23 +- .../NoopApplicationShutdownRegistry.cs | 11 +- src/Umbraco.Core/HybridAccessorBase.cs | 120 +- .../HybridEventMessagesAccessor.cs | 21 +- src/Umbraco.Core/IBackOfficeInfo.cs | 16 +- src/Umbraco.Core/ICompletable.cs | 9 +- src/Umbraco.Core/IO/CleanFolderResult.cs | 54 +- .../IO/CleanFolderResultStatus.cs | 13 +- .../IO/DefaultViewContentProvider.cs | 101 +- src/Umbraco.Core/IO/FileSystemExtensions.cs | 155 +- src/Umbraco.Core/IO/FileSystems.cs | 572 +- .../IO/IDefaultViewContentProvider.cs | 11 +- src/Umbraco.Core/IO/IFileProviderFactory.cs | 23 +- src/Umbraco.Core/IO/IFileSystem.cs | 347 +- src/Umbraco.Core/IO/IIOHelper.cs | 115 +- src/Umbraco.Core/IO/IMediaPathScheme.cs | 50 +- src/Umbraco.Core/IO/IOHelper.cs | 350 +- src/Umbraco.Core/IO/IOHelperExtensions.cs | 77 +- src/Umbraco.Core/IO/IOHelperLinux.cs | 46 +- src/Umbraco.Core/IO/IOHelperOSX.cs | 46 +- src/Umbraco.Core/IO/IOHelperWindows.cs | 95 +- src/Umbraco.Core/IO/IViewHelper.cs | 17 +- src/Umbraco.Core/IO/MediaFileManager.cs | 401 +- .../CombinedGuidsMediaPathScheme.cs | 42 +- .../TwoGuidsMediaPathScheme.cs | 35 +- .../MediaPathSchemes/UniqueMediaPathScheme.cs | 62 +- src/Umbraco.Core/IO/PhysicalFileSystem.cs | 767 +- src/Umbraco.Core/IO/ShadowFileSystem.cs | 639 +- src/Umbraco.Core/IO/ShadowFileSystems.cs | 44 +- src/Umbraco.Core/IO/ShadowWrapper.cs | 323 +- src/Umbraco.Core/IO/ViewHelper.cs | 180 +- src/Umbraco.Core/IRegisteredObject.cs | 9 +- .../Install/FilePermissionTest.cs | 15 +- .../Install/IFilePermissionHelper.cs | 20 +- src/Umbraco.Core/Install/InstallException.cs | 192 +- .../Install/InstallStatusTracker.cs | 210 +- .../InstallSteps/FilePermissionsStep.cs | 77 +- .../InstallSteps/TelemetryIdentifierStep.cs | 73 +- .../Install/InstallSteps/UpgradeStep.cs | 84 +- .../Install/Models/DatabaseModel.cs | 36 +- .../Install/Models/InstallInstructions.cs | 19 +- .../Models/InstallProgressResultModel.cs | 59 +- .../Install/Models/InstallSetup.cs | 34 +- .../Install/Models/InstallSetupResult.cs | 69 +- .../Install/Models/InstallSetupStep.cs | 124 +- .../Models/InstallSetupStepAttribute.cs | 71 +- .../Install/Models/InstallTrackingItem.cs | 47 +- .../Install/Models/InstallationType.cs | 13 +- src/Umbraco.Core/Install/Models/Package.cs | 23 +- src/Umbraco.Core/Install/Models/UserModel.cs | 22 +- src/Umbraco.Core/InstallLog.cs | 56 +- src/Umbraco.Core/LambdaExpressionCacheKey.cs | 132 +- src/Umbraco.Core/Logging/DisposableTimer.cs | 292 +- .../Logging/ILoggingConfiguration.cs | 16 +- src/Umbraco.Core/Logging/IMessageTemplates.cs | 15 +- src/Umbraco.Core/Logging/IProfiler.cs | 48 +- src/Umbraco.Core/Logging/IProfilerHtml.cs | 21 +- src/Umbraco.Core/Logging/IProfilingLogger.cs | 64 +- .../Logging/LogHttpRequestExtension.cs | 34 +- src/Umbraco.Core/Logging/LogLevel.cs | 25 +- src/Umbraco.Core/Logging/LogProfiler.cs | 84 +- .../Logging/LoggingConfiguration.cs | 15 +- .../Logging/LoggingTaskExtension.cs | 81 +- src/Umbraco.Core/Logging/NoopProfiler.cs | 29 +- .../Logging/ProfilerExtensions.cs | 71 +- src/Umbraco.Core/Logging/ProfilingLogger.cs | 149 +- src/Umbraco.Core/Macros/IMacroRenderer.cs | 20 +- src/Umbraco.Core/Macros/MacroContent.cs | 25 +- .../Macros/MacroErrorBehaviour.cs | 47 +- src/Umbraco.Core/Macros/MacroModel.cs | 88 +- src/Umbraco.Core/Macros/MacroPropertyModel.cs | 40 +- src/Umbraco.Core/Mail/IEmailSender.cs | 20 +- src/Umbraco.Core/Mail/ISmsSender.cs | 17 +- .../Mail/NotImplementedEmailSender.cs | 25 +- .../Mail/NotImplementedSmsSender.cs | 19 +- src/Umbraco.Core/Manifest/BundleOptions.cs | 43 +- .../Manifest/CompositePackageManifest.cs | 102 +- src/Umbraco.Core/Manifest/IManifestFilter.cs | 25 +- src/Umbraco.Core/Manifest/IManifestParser.cs | 37 +- src/Umbraco.Core/Manifest/IPackageManifest.cs | 105 +- src/Umbraco.Core/Manifest/ManifestAssets.cs | 20 +- .../Manifest/ManifestContentAppDefinition.cs | 124 +- .../Manifest/ManifestContentAppFactory.cs | 301 +- .../Manifest/ManifestDashboard.cs | 29 +- .../Manifest/ManifestFilterCollection.cs | 31 +- .../ManifestFilterCollectionBuilder.cs | 14 +- src/Umbraco.Core/Manifest/ManifestSection.cs | 15 +- src/Umbraco.Core/Manifest/PackageManifest.cs | 196 +- src/Umbraco.Core/Mapping/IMapDefinition.cs | 17 +- src/Umbraco.Core/Mapping/IUmbracoMapper.cs | 305 +- .../Mapping/MapDefinitionCollection.cs | 11 +- .../Mapping/MapDefinitionCollectionBuilder.cs | 12 +- src/Umbraco.Core/Mapping/MapperContext.cs | 217 +- .../Media/EmbedProviders/DailyMotion.cs | 42 +- .../Media/EmbedProviders/EmbedProviderBase.cs | 14 +- .../EmbedProvidersCollection.cs | 11 +- .../EmbedProvidersCollectionBuilder.cs | 10 +- .../Media/EmbedProviders/Flickr.cs | 44 +- .../Media/EmbedProviders/GettyImages.cs | 38 +- .../Media/EmbedProviders/Giphy.cs | 40 +- src/Umbraco.Core/Media/EmbedProviders/Hulu.cs | 33 +- .../Media/EmbedProviders/Issuu.cs | 42 +- .../Media/EmbedProviders/Kickstarter.cs | 33 +- .../Media/EmbedProviders/LottieFiles.cs | 75 +- .../EmbedProviders/OEmbedProviderBase.cs | 112 +- .../Media/EmbedProviders/OEmbedResponse.cs | 79 +- .../Media/EmbedProviders/Slideshare.cs | 34 +- .../Media/EmbedProviders/SoundCloud.cs | 34 +- src/Umbraco.Core/Media/EmbedProviders/Ted.cs | 34 +- .../Media/EmbedProviders/Twitter.cs | 33 +- .../Media/EmbedProviders/Vimeo.cs | 34 +- .../Media/EmbedProviders/Youtube.cs | 42 +- src/Umbraco.Core/Media/Exif/BitConverterEx.cs | 649 +- .../Media/Exif/ExifBitConverter.cs | 608 +- src/Umbraco.Core/Media/Exif/ExifEnums.cs | 587 +- src/Umbraco.Core/Media/Exif/ExifExceptions.cs | 84 +- .../Media/Exif/ExifExtendedProperty.cs | 671 +- .../Media/Exif/ExifFileTypeDescriptor.cs | 181 +- .../Media/Exif/ExifInterOperability.cs | 102 +- src/Umbraco.Core/Media/Exif/ExifProperty.cs | 943 ++- .../Media/Exif/ExifPropertyCollection.cs | 760 +- .../Media/Exif/ExifPropertyFactory.cs | 691 +- src/Umbraco.Core/Media/Exif/ExifTag.cs | 591 +- src/Umbraco.Core/Media/Exif/ExifTagFactory.cs | 103 +- src/Umbraco.Core/Media/Exif/IFD.cs | 31 +- src/Umbraco.Core/Media/Exif/ImageFile.cs | 235 +- .../Media/Exif/ImageFileDirectory.cs | 153 +- .../Media/Exif/ImageFileDirectoryEntry.cs | 210 +- .../Media/Exif/ImageFileFormat.cs | 44 +- src/Umbraco.Core/Media/Exif/JFIFEnums.cs | 72 +- .../Media/Exif/JFIFExtendedProperty.cs | 111 +- src/Umbraco.Core/Media/Exif/JFIFThumbnail.cs | 105 +- src/Umbraco.Core/Media/Exif/JPEGExceptions.cs | 376 +- src/Umbraco.Core/Media/Exif/JPEGFile.cs | 1656 ++-- src/Umbraco.Core/Media/Exif/JPEGMarker.cs | 176 +- src/Umbraco.Core/Media/Exif/JPEGSection.cs | 118 +- src/Umbraco.Core/Media/Exif/MathEx.cs | 2295 +++--- src/Umbraco.Core/Media/Exif/SvgFile.cs | 39 +- src/Umbraco.Core/Media/Exif/TIFFFile.cs | 286 +- src/Umbraco.Core/Media/Exif/TIFFHeader.cs | 149 +- src/Umbraco.Core/Media/Exif/TIFFStrip.cs | 37 +- src/Umbraco.Core/Media/Exif/Utility.cs | 37 +- src/Umbraco.Core/Media/IEmbedProvider.cs | 35 +- .../Media/IImageDimensionExtractor.cs | 10 +- src/Umbraco.Core/Media/IImageUrlGenerator.cs | 40 +- .../Media/ImageUrlGeneratorExtensions.cs | 42 +- src/Umbraco.Core/Media/OEmbedResult.cs | 13 +- src/Umbraco.Core/Media/OEmbedStatus.cs | 13 +- .../Media/TypeDetector/JpegDetector.cs | 13 +- .../TypeDetector/RasterizedTypeDetector.cs | 25 +- .../Media/TypeDetector/SvgDetector.cs | 32 +- .../Media/TypeDetector/TIFFDetector.cs | 32 +- .../Media/UploadAutoFillProperties.cs | 289 +- src/Umbraco.Core/Models/AnchorsModel.cs | 9 +- src/Umbraco.Core/Models/AuditEntry.cs | 128 +- src/Umbraco.Core/Models/AuditItem.cs | 54 +- src/Umbraco.Core/Models/AuditType.cs | 249 +- src/Umbraco.Core/Models/BackOfficeTour.cs | 54 +- src/Umbraco.Core/Models/BackOfficeTourFile.cs | 50 +- src/Umbraco.Core/Models/BackOfficeTourStep.cs | 61 +- .../Models/Blocks/BlockListItem.cs | 218 +- .../Models/Blocks/BlockListModel.cs | 104 +- .../Blocks/ContentAndSettingsReference.cs | 44 +- .../Models/Blocks/IBlockReference.cs | 61 +- src/Umbraco.Core/Models/CacheInstruction.cs | 75 +- .../Models/ChangingPasswordModel.cs | 41 +- src/Umbraco.Core/Models/Consent.cs | 129 +- src/Umbraco.Core/Models/ConsentExtensions.cs | 25 +- src/Umbraco.Core/Models/ConsentState.cs | 57 +- src/Umbraco.Core/Models/Content.cs | 843 +- src/Umbraco.Core/Models/ContentBase.cs | 892 +- .../Models/ContentBaseExtensions.cs | 58 +- .../Models/ContentCultureInfos.cs | 168 +- .../Models/ContentCultureInfosCollection.cs | 94 +- .../Models/ContentDataIntegrityReport.cs | 80 +- .../Models/ContentDataIntegrityReportEntry.cs | 16 +- .../ContentDataIntegrityReportOptions.cs | 19 +- .../AssignedContentPermissions.cs | 30 +- .../AssignedUserGroupPermissions.cs | 58 +- .../Models/ContentEditing/AuditLog.cs | 39 +- .../ContentEditing/BackOfficeNotification.cs | 35 +- .../Models/ContentEditing/CodeFileDisplay.cs | 110 +- .../Models/ContentEditing/ContentApp.cs | 128 +- .../Models/ContentEditing/ContentAppBadge.cs | 54 +- .../ContentEditing/ContentAppBadgeType.cs | 33 +- .../Models/ContentEditing/ContentBaseSave.cs | 107 +- .../ContentDomainsAndCulture.cs | 18 +- .../Models/ContentEditing/ContentItemBasic.cs | 145 +- .../ContentEditing/ContentItemDisplay.cs | 410 +- .../ContentEditing/ContentItemDisplayBase.cs | 77 +- .../Models/ContentEditing/ContentItemSave.cs | 94 +- .../ContentEditing/ContentPropertyBasic.cs | 111 +- .../ContentPropertyCollectionDto.cs | 27 +- .../ContentEditing/ContentPropertyDisplay.cs | 56 +- .../ContentEditing/ContentPropertyDto.cs | 29 +- .../ContentEditing/ContentRedirectUrl.cs | 30 +- .../ContentEditing/ContentSaveAction.cs | 107 +- .../ContentEditing/ContentSavedState.cs | 41 +- .../Models/ContentEditing/ContentSortOrder.cs | 43 +- .../Models/ContentEditing/ContentTypeBasic.cs | 171 +- .../ContentTypeCompositionDisplay.cs | 111 +- .../Models/ContentEditing/ContentTypeSave.cs | 183 +- .../ContentEditing/ContentTypesByAliases.cs | 35 +- .../ContentEditing/ContentTypesByKeys.cs | 36 +- .../ContentEditing/ContentVariantSave.cs | 111 +- .../ContentEditing/ContentVariationDisplay.cs | 117 +- .../CreatedDocumentTypeCollectionResult.cs | 21 +- .../Models/ContentEditing/DataTypeBasic.cs | 35 +- .../DataTypeConfigurationFieldDisplay.cs | 64 +- .../DataTypeConfigurationFieldSave.cs | 31 +- .../Models/ContentEditing/DataTypeDisplay.cs | 50 +- .../ContentEditing/DataTypeReferences.cs | 46 +- .../Models/ContentEditing/DataTypeSave.cs | 74 +- .../ContentEditing/DictionaryDisplay.cs | 71 +- .../DictionaryOverviewDisplay.cs | 65 +- .../DictionaryOverviewTranslationDisplay.cs | 31 +- .../Models/ContentEditing/DictionarySave.cs | 56 +- .../DictionaryTranslationDisplay.cs | 23 +- .../DictionaryTranslationSave.cs | 41 +- .../ContentEditing/DocumentTypeDisplay.cs | 41 +- .../Models/ContentEditing/DocumentTypeSave.cs | 59 +- .../Models/ContentEditing/DomainDisplay.cs | 29 +- .../Models/ContentEditing/DomainSave.cs | 21 +- .../Models/ContentEditing/EditorNavigation.cs | 30 +- .../Models/ContentEditing/EntityBasic.cs | 105 +- .../ContentEditing/EntitySearchResults.cs | 28 +- .../GetAvailableCompositionsFilter.cs | 42 +- .../Models/ContentEditing/HistoryCleanup.cs | 51 +- .../ContentEditing/HistoryCleanupViewModel.cs | 22 +- .../ContentEditing/IContentAppFactory.cs | 36 +- .../ContentEditing/IContentProperties.cs | 12 +- .../Models/ContentEditing/IContentSave.cs | 36 +- .../Models/ContentEditing/IErrorModel.cs | 25 +- .../ContentEditing/IHaveUploadedFiles.cs | 12 +- .../ContentEditing/INotificationModel.cs | 20 +- .../Models/ContentEditing/ITabbedContent.cs | 12 +- .../Models/ContentEditing/Language.cs | 33 +- .../Models/ContentEditing/LinkDisplay.cs | 33 +- .../ListViewAwareContentItemDisplayBase.cs | 41 +- .../Models/ContentEditing/MacroDisplay.cs | 104 +- .../Models/ContentEditing/MacroParameter.cs | 64 +- .../ContentEditing/MacroParameterDisplay.cs | 51 +- .../Models/ContentEditing/MediaItemDisplay.cs | 32 +- .../Models/ContentEditing/MediaItemSave.cs | 15 +- .../Models/ContentEditing/MediaTypeDisplay.cs | 13 +- .../Models/ContentEditing/MediaTypeSave.cs | 15 +- .../Models/ContentEditing/MemberBasic.cs | 32 +- .../Models/ContentEditing/MemberDisplay.cs | 62 +- .../ContentEditing/MemberGroupDisplay.cs | 27 +- .../Models/ContentEditing/MemberGroupSave.cs | 9 +- .../ContentEditing/MemberListDisplay.cs | 21 +- .../ContentEditing/MemberPropertyTypeBasic.cs | 25 +- .../MemberPropertyTypeDisplay.cs | 19 +- .../Models/ContentEditing/MemberSave.cs | 60 +- .../ContentEditing/MemberTypeDisplay.cs | 9 +- .../Models/ContentEditing/MemberTypeSave.cs | 13 +- .../ContentEditing/MessagesExtensions.cs | 97 +- .../ContentEditing/ModelWithNotifications.cs | 49 +- .../Models/ContentEditing/MoveOrCopy.cs | 60 +- .../ContentEditing/NotificationStyle.cs | 53 +- .../Models/ContentEditing/NotifySetting.cs | 25 +- .../Models/ContentEditing/ObjectType.cs | 18 +- .../Models/ContentEditing/Permission.cs | 61 +- .../Models/ContentEditing/PostedFiles.cs | 34 +- .../Models/ContentEditing/PostedFolder.cs | 21 +- .../ContentEditing/PropertyEditorBasic.cs | 24 +- .../ContentEditing/PropertyGroupBasic.cs | 107 +- .../PropertyGroupBasicExtensions.cs | 11 +- .../ContentEditing/PropertyGroupDisplay.cs | 60 +- .../ContentEditing/PropertyTypeBasic.cs | 128 +- .../ContentEditing/PropertyTypeDisplay.cs | 77 +- .../ContentEditing/PropertyTypeValidation.cs | 28 +- .../Models/ContentEditing/PublicAccess.cs | 21 +- .../RedirectUrlSearchResults.cs | 24 +- .../Models/ContentEditing/RelationDisplay.cs | 84 +- .../ContentEditing/RelationTypeDisplay.cs | 116 +- .../Models/ContentEditing/RelationTypeSave.cs | 52 +- .../ContentEditing/RichTextEditorCommand.cs | 30 +- .../RichTextEditorConfiguration.cs | 27 +- .../ContentEditing/RichTextEditorPlugin.cs | 12 +- .../Models/ContentEditing/RollbackVersion.cs | 25 +- .../Models/ContentEditing/SearchResult.cs | 24 +- .../ContentEditing/SearchResultEntity.cs | 23 +- .../Models/ContentEditing/SearchResults.cs | 25 +- .../Models/ContentEditing/Section.cs | 33 +- .../ContentEditing/SimpleNotificationModel.cs | 43 +- .../Models/ContentEditing/SnippetDisplay.cs | 17 +- .../Models/ContentEditing/StyleSheet.cs | 15 +- .../Models/ContentEditing/StylesheetRule.cs | 18 +- src/Umbraco.Core/Models/ContentEditing/Tab.cs | 48 +- .../ContentEditing/TabbedContentItem.cs | 53 +- .../Models/ContentEditing/TemplateDisplay.cs | 57 +- .../Models/ContentEditing/TreeSearchResult.cs | 49 +- .../ContentEditing/UmbracoEntityTypes.cs | 189 +- .../Models/ContentEditing/UnpublishContent.cs | 21 +- .../Models/ContentEditing/UrlAndAnchors.cs | 24 +- .../Models/ContentEditing/UserBasic.cs | 98 +- .../Models/ContentEditing/UserDetail.cs | 110 +- .../Models/ContentEditing/UserDisplay.cs | 123 +- .../Models/ContentEditing/UserGroupBasic.cs | 71 +- .../Models/ContentEditing/UserGroupDisplay.cs | 44 +- .../UserGroupPermissionsSave.cs | 53 +- .../Models/ContentEditing/UserGroupSave.cs | 106 +- .../Models/ContentEditing/UserInvite.cs | 55 +- .../Models/ContentEditing/UserProfile.cs | 35 +- .../Models/ContentEditing/UserSave.cs | 77 +- src/Umbraco.Core/Models/ContentModel.cs | 27 +- .../Models/ContentModelOfTContent.cs | 27 +- .../Models/ContentRepositoryExtensions.cs | 573 +- src/Umbraco.Core/Models/ContentSchedule.cs | 119 +- .../Models/ContentScheduleAction.cs | 25 +- .../Models/ContentScheduleCollection.cs | 372 +- src/Umbraco.Core/Models/ContentStatus.cs | 67 +- .../Models/ContentTagsExtensions.cs | 82 +- src/Umbraco.Core/Models/ContentType.cs | 272 +- .../ContentTypeAvailableCompositionsResult.cs | 25 +- ...ContentTypeAvailableCompositionsResults.cs | 37 +- src/Umbraco.Core/Models/ContentTypeBase.cs | 865 +- .../Models/ContentTypeBaseExtensions.cs | 109 +- .../Models/ContentTypeCompositionBase.cs | 509 +- .../Models/ContentTypeImportModel.cs | 24 +- src/Umbraco.Core/Models/ContentTypeSort.cs | 113 +- src/Umbraco.Core/Models/ContentVariation.cs | 57 +- .../ContentVersionCleanupPolicySettings.cs | 17 +- src/Umbraco.Core/Models/ContentVersionMeta.cs | 71 +- src/Umbraco.Core/Models/CultureImpact.cs | 463 +- src/Umbraco.Core/Models/DataType.cs | 342 +- src/Umbraco.Core/Models/DataTypeExtensions.cs | 153 +- src/Umbraco.Core/Models/DeepCloneHelper.cs | 321 +- src/Umbraco.Core/Models/DictionaryItem.cs | 129 +- .../Models/DictionaryItemExtensions.cs | 48 +- .../Models/DictionaryTranslation.cs | 167 +- .../Models/DoNotCloneAttribute.cs | 33 +- .../Models/Editors/ContentPropertyData.cs | 69 +- .../Models/Editors/ContentPropertyFile.cs | 67 +- .../Models/Editors/UmbracoEntityReference.cs | 93 +- src/Umbraco.Core/Models/Email/EmailMessage.cs | 108 +- .../Models/Email/EmailMessageAttachment.cs | 19 +- .../Models/Email/NotificationEmailAddress.cs | 25 +- .../Models/Email/NotificationEmailModel.cs | 75 +- .../Models/Entities/BeingDirty.cs | 55 +- .../Models/Entities/BeingDirtyBase.cs | 288 +- .../Models/Entities/ContentEntitySlim.cs | 25 +- .../Models/Entities/DocumentEntitySlim.cs | 70 +- .../Models/Entities/EntityBase.cs | 224 +- .../Models/Entities/EntityExtensions.cs | 70 +- .../Models/Entities/EntitySlim.cs | 243 +- .../Models/Entities/ICanBeDirty.cs | 74 +- .../Models/Entities/IContentEntitySlim.cs | 33 +- .../Models/Entities/IDocumentEntitySlim.cs | 59 +- src/Umbraco.Core/Models/Entities/IEntity.cs | 71 +- .../Models/Entities/IEntitySlim.cs | 33 +- .../Models/Entities/IHaveAdditionalData.cs | 61 +- .../Models/Entities/IMediaEntitySlim.cs | 18 +- .../Models/Entities/IMemberEntitySlim.cs | 8 +- .../Models/Entities/IRememberBeingDirty.cs | 62 +- .../Models/Entities/ITreeEntity.cs | 107 +- .../Models/Entities/IUmbracoEntity.cs | 23 +- .../Models/Entities/IValueObject.cs | 16 +- .../Models/Entities/MediaEntitySlim.cs | 15 +- .../Models/Entities/MemberEntitySlim.cs | 7 +- .../Models/Entities/TreeEntityBase.cs | 183 +- .../Models/Entities/TreeEntityPath.cs | 25 +- src/Umbraco.Core/Models/EntityContainer.cs | 142 +- src/Umbraco.Core/Models/File.cs | 245 +- src/Umbraco.Core/Models/Folder.cs | 14 +- .../Models/HaveAdditionalDataExtensions.cs | 26 +- src/Umbraco.Core/Models/IAuditEntry.cs | 116 +- src/Umbraco.Core/Models/IAuditItem.cs | 49 +- src/Umbraco.Core/Models/IConsent.cs | 96 +- src/Umbraco.Core/Models/IContent.cs | 255 +- src/Umbraco.Core/Models/IContentBase.cs | 258 +- src/Umbraco.Core/Models/IContentModel.cs | 51 +- src/Umbraco.Core/Models/IContentType.cs | 113 +- src/Umbraco.Core/Models/IContentTypeBase.cs | 347 +- .../Models/IContentTypeComposition.cs | 113 +- src/Umbraco.Core/Models/IDataType.cs | 59 +- src/Umbraco.Core/Models/IDataValueEditor.cs | 129 +- src/Umbraco.Core/Models/IDeepCloneable.cs | 15 +- src/Umbraco.Core/Models/IDictionaryItem.cs | 41 +- .../Models/IDictionaryTranslation.cs | 29 +- src/Umbraco.Core/Models/IDomain.cs | 23 +- src/Umbraco.Core/Models/IFile.cs | 80 +- src/Umbraco.Core/Models/IKeyValue.cs | 11 +- src/Umbraco.Core/Models/ILanguage.cs | 93 +- src/Umbraco.Core/Models/ILogViewerQuery.cs | 11 +- src/Umbraco.Core/Models/IMacro.cs | 99 +- src/Umbraco.Core/Models/IMacroProperty.cs | 62 +- src/Umbraco.Core/Models/IMedia.cs | 6 +- src/Umbraco.Core/Models/IMediaType.cs | 22 +- src/Umbraco.Core/Models/IMediaUrlGenerator.cs | 27 +- src/Umbraco.Core/Models/IMember.cs | 119 +- src/Umbraco.Core/Models/IMemberGroup.cs | 25 +- src/Umbraco.Core/Models/IMemberType.cs | 81 +- src/Umbraco.Core/Models/IMigrationEntry.cs | 11 +- src/Umbraco.Core/Models/IPartialView.cs | 9 +- src/Umbraco.Core/Models/IProperty.cs | 70 +- .../Models/IPropertyCollection.cs | 68 +- src/Umbraco.Core/Models/IPropertyType.cs | 172 +- src/Umbraco.Core/Models/IPropertyValue.cs | 59 +- .../Models/IReadOnlyContentBase.cs | 111 +- src/Umbraco.Core/Models/IRedirectUrl.cs | 65 +- src/Umbraco.Core/Models/IRelation.cs | 78 +- src/Umbraco.Core/Models/IRelationType.cs | 80 +- src/Umbraco.Core/Models/IScript.cs | 8 +- .../Models/IServerRegistration.cs | 50 +- src/Umbraco.Core/Models/ISimpleContentType.cs | 119 +- src/Umbraco.Core/Models/IStylesheet.cs | 44 +- .../Models/IStylesheetProperty.cs | 13 +- src/Umbraco.Core/Models/ITag.cs | 49 +- src/Umbraco.Core/Models/ITemplate.cs | 51 +- src/Umbraco.Core/Models/ITwoFactorLogin.cs | 16 +- src/Umbraco.Core/Models/IconModel.cs | 11 +- src/Umbraco.Core/Models/ImageCropAnchor.cs | 25 +- src/Umbraco.Core/Models/ImageCropMode.cs | 62 +- .../Models/ImageUrlGenerationOptions.cs | 182 +- src/Umbraco.Core/Models/KeyValue.cs | 50 +- src/Umbraco.Core/Models/Language.cs | 134 +- src/Umbraco.Core/Models/Link.cs | 17 +- src/Umbraco.Core/Models/LinkType.cs | 13 +- src/Umbraco.Core/Models/LogViewerQuery.cs | 50 +- src/Umbraco.Core/Models/Macro.cs | 462 +- src/Umbraco.Core/Models/MacroProperty.cs | 270 +- .../Models/MacroPropertyCollection.cs | 97 +- .../Models/Mapping/AuditMapDefinition.cs | 33 +- .../Models/Mapping/CodeFileMapDefinition.cs | 162 +- .../Models/Mapping/CommonMapper.cs | 91 +- .../Mapping/ContentPropertyBasicMapper.cs | 129 +- .../Mapping/ContentPropertyDisplayMapper.cs | 108 +- .../Mapping/ContentPropertyDtoMapper.cs | 43 +- .../Mapping/ContentPropertyMapDefinition.cs | 99 +- .../Models/Mapping/ContentSavedStateMapper.cs | 113 +- .../Mapping/ContentTypeMapDefinition.cs | 1520 ++-- .../Models/Mapping/ContentVariantMapper.cs | 266 +- .../Models/Mapping/DataTypeMapDefinition.cs | 355 +- .../Models/Mapping/DictionaryMapDefinition.cs | 186 +- .../Models/Mapping/LanguageMapDefinition.cs | 91 +- .../Models/Mapping/MacroMapDefinition.cs | 142 +- .../Models/Mapping/MapperContextExtensions.cs | 99 +- .../Models/Mapping/MemberMapDefinition.cs | 43 +- .../Mapping/MemberTabsAndPropertiesMapper.cs | 485 +- .../Models/Mapping/PropertyTypeGroupMapper.cs | 455 +- .../Mapping/RedirectUrlMapDefinition.cs | 41 +- .../Models/Mapping/RelationMapDefinition.cs | 141 +- .../Models/Mapping/SectionMapDefinition.cs | 65 +- .../Models/Mapping/TabsAndPropertiesMapper.cs | 246 +- .../Models/Mapping/TagMapDefinition.cs | 27 +- .../Models/Mapping/TemplateMapDefinition.cs | 75 +- .../Models/Mapping/UserMapDefinition.cs | 804 +- src/Umbraco.Core/Models/Media.cs | 139 +- src/Umbraco.Core/Models/MediaExtensions.cs | 42 +- src/Umbraco.Core/Models/MediaType.cs | 92 +- src/Umbraco.Core/Models/Member.cs | 913 ++- src/Umbraco.Core/Models/MemberGroup.cs | 78 +- .../Models/MemberPropertyModel.cs | 50 +- src/Umbraco.Core/Models/MemberType.cs | 276 +- .../Models/MemberTypePropertyProfileAccess.cs | 29 +- .../Models/Membership/ContentPermissionSet.cs | 77 +- .../Models/Membership/EntityPermission.cs | 103 +- .../Membership/EntityPermissionCollection.cs | 86 +- .../Models/Membership/EntityPermissionSet.cs | 78 +- .../Models/Membership/IMembershipUser.cs | 86 +- .../Models/Membership/IProfile.cs | 17 +- .../Models/Membership/IReadOnlyUserGroup.cs | 46 +- src/Umbraco.Core/Models/Membership/IUser.cs | 82 +- .../Models/Membership/IUserGroup.cs | 63 +- .../Models/Membership/MemberCountType.cs | 19 +- .../Models/Membership/MemberExportModel.cs | 28 +- .../Models/Membership/MemberExportProperty.cs | 19 +- .../Membership/PersistedPasswordSettings.cs | 32 +- .../Models/Membership/ReadOnlyUserGroup.cs | 106 +- src/Umbraco.Core/Models/Membership/User.cs | 692 +- .../Models/Membership/UserGroup.cs | 228 +- .../Models/Membership/UserGroupExtensions.cs | 41 +- .../Models/Membership/UserProfile.cs | 60 +- .../Models/Membership/UserState.cs | 25 +- src/Umbraco.Core/Models/MigrationEntry.cs | 52 +- src/Umbraco.Core/Models/Notification.cs | 27 +- .../Models/NotificationEmailBodyParams.cs | 51 +- .../Models/NotificationEmailSubjectParams.cs | 24 +- src/Umbraco.Core/Models/ObjectTypes.cs | 236 +- .../Models/Packaging/CompiledPackage.cs | 48 +- .../Packaging/CompiledPackageContentBase.cs | 29 +- .../Models/Packaging/InstallWarnings.cs | 17 +- src/Umbraco.Core/Models/PagedResult.cs | 79 +- src/Umbraco.Core/Models/PagedResultOfT.cs | 28 +- src/Umbraco.Core/Models/PartialView.cs | 35 +- .../Models/PartialViewMacroModel.cs | 48 +- .../Models/PartialViewMacroModelExtensions.cs | 58 +- src/Umbraco.Core/Models/PartialViewType.cs | 13 +- .../Models/PasswordChangedModel.cs | 25 +- src/Umbraco.Core/Models/Property.cs | 972 ++- src/Umbraco.Core/Models/PropertyCollection.cs | 324 +- src/Umbraco.Core/Models/PropertyGroup.cs | 245 +- .../Models/PropertyGroupCollection.cs | 227 +- .../Models/PropertyGroupExtensions.cs | 131 +- src/Umbraco.Core/Models/PropertyGroupType.cs | 26 +- .../Models/PropertyTagsExtensions.cs | 423 +- src/Umbraco.Core/Models/PropertyType.cs | 504 +- .../Models/PropertyTypeCollection.cs | 280 +- src/Umbraco.Core/Models/PublicAccessEntry.cs | 230 +- src/Umbraco.Core/Models/PublicAccessRule.cs | 58 +- .../Models/PublishedContent/Fallback.cs | 108 +- .../HttpContextVariationContextAccessor.cs | 33 +- .../HybridVariationContextAccessor.cs | 30 +- .../IAutoPublishedModelFactory.cs | 34 +- .../PublishedContent/IPublishedContent.cs | 294 +- .../PublishedContent/IPublishedContentType.cs | 106 +- .../IPublishedContentTypeFactory.cs | 98 +- .../PublishedContent/IPublishedElement.cs | 92 +- .../PublishedContent/IPublishedMemberCache.cs | 46 +- .../IPublishedModelFactory.cs | 83 +- .../PublishedContent/IPublishedProperty.cs | 119 +- .../IPublishedPropertyType.cs | 188 +- .../IPublishedValueFallback.cs | 277 +- .../IVariationContextAccessor.cs | 17 +- .../PublishedContent/IndexedArrayItem.cs | 813 +- .../Models/PublishedContent/ModelType.cs | 663 +- .../NoopPublishedModelFactory.cs | 27 +- .../NoopPublishedValueFallback.cs | 97 +- .../PublishedContent/PublishedContentBase.cs | 139 +- .../PublishedContentExtensionsForModels.cs | 54 +- .../PublishedContent/PublishedContentModel.cs | 30 +- .../PublishedContent/PublishedContentType.cs | 326 +- .../PublishedContentTypeConverter.cs | 36 +- .../PublishedContentTypeFactory.cs | 190 +- .../PublishedContentWrapped.cs | 185 +- .../PublishedContent/PublishedCultureInfos.cs | 88 +- .../PublishedContent/PublishedDataType.cs | 101 +- .../PublishedContent/PublishedElementModel.cs | 32 +- .../PublishedElementWrapped.cs | 64 +- .../PublishedContent/PublishedItemType.cs | 51 +- .../PublishedModelAttribute.cs | 47 +- .../PublishedContent/PublishedModelFactory.cs | 243 +- .../PublishedContent/PublishedPropertyBase.cs | 106 +- .../PublishedContent/PublishedPropertyType.cs | 427 +- .../PublishedContent/PublishedSearchResult.cs | 21 +- .../PublishedValueFallback.cs | 484 +- .../PublishedContent/RawValueProperty.cs | 94 +- .../ThreadCultureVariationContextAccessor.cs | 33 +- .../Models/PublishedContent/UrlMode.cs | 41 +- .../PublishedContent/VariationContext.cs | 51 +- .../VariationContextAccessorExtensions.cs | 55 +- src/Umbraco.Core/Models/PublishedState.cs | 111 +- src/Umbraco.Core/Models/Range.cs | 240 +- .../Models/ReadOnlyContentBaseAdapter.cs | 43 +- src/Umbraco.Core/Models/ReadOnlyRelation.cs | 53 +- src/Umbraco.Core/Models/RedirectUrl.cs | 100 +- src/Umbraco.Core/Models/Relation.cs | 172 +- src/Umbraco.Core/Models/RelationItem.cs | 50 +- src/Umbraco.Core/Models/RelationType.cs | 183 +- .../Models/RelationTypeExtensions.cs | 19 +- .../Models/RequestPasswordResetModel.cs | 16 +- src/Umbraco.Core/Models/Script.cs | 46 +- src/Umbraco.Core/Models/SendCodeViewModel.cs | 48 +- src/Umbraco.Core/Models/ServerRegistration.cs | 210 +- src/Umbraco.Core/Models/SetPasswordModel.cs | 27 +- src/Umbraco.Core/Models/SimpleContentType.cs | 144 +- .../Models/SimpleValidationModel.cs | 19 +- src/Umbraco.Core/Models/Stylesheet.cs | 264 +- src/Umbraco.Core/Models/StylesheetProperty.cs | 81 +- src/Umbraco.Core/Models/Tag.cs | 101 +- src/Umbraco.Core/Models/TagModel.cs | 23 +- .../Models/TaggableObjectTypes.cs | 21 +- src/Umbraco.Core/Models/TaggedEntity.cs | 45 +- src/Umbraco.Core/Models/TaggedProperty.cs | 49 +- src/Umbraco.Core/Models/TagsStorageType.cs | 31 +- src/Umbraco.Core/Models/TelemetryLevel.cs | 15 +- src/Umbraco.Core/Models/TelemetryResource.cs | 12 +- src/Umbraco.Core/Models/Template.cs | 136 +- src/Umbraco.Core/Models/TemplateNode.cs | 51 +- src/Umbraco.Core/Models/TemplateOnDisk.cs | 79 +- .../Models/TemplateQuery/ContentTypeModel.cs | 11 +- .../Models/TemplateQuery/Operator.cs | 23 +- .../Models/TemplateQuery/OperatorFactory.cs | 50 +- .../Models/TemplateQuery/OperatorTerm.cs | 35 +- .../Models/TemplateQuery/PropertyModel.cs | 13 +- .../Models/TemplateQuery/QueryCondition.cs | 13 +- .../TemplateQuery/QueryConditionExtensions.cs | 125 +- .../Models/TemplateQuery/QueryModel.cs | 17 +- .../Models/TemplateQuery/QueryResultModel.cs | 19 +- .../Models/TemplateQuery/SortExpression.cs | 11 +- .../Models/TemplateQuery/SourceModel.cs | 11 +- .../TemplateQuery/TemplateQueryResult.cs | 11 +- .../Models/Trees/ActionMenuItem.cs | 78 +- .../Models/Trees/CreateChildEntity.cs | 38 +- src/Umbraco.Core/Models/Trees/ExportMember.cs | 20 +- src/Umbraco.Core/Models/Trees/MenuItem.cs | 385 +- src/Umbraco.Core/Models/Trees/RefreshNode.cs | 39 +- src/Umbraco.Core/Models/TwoFactorLogin.cs | 18 +- src/Umbraco.Core/Models/UmbracoDomain.cs | 89 +- src/Umbraco.Core/Models/UmbracoObjectTypes.cs | 337 +- .../Models/UmbracoUserExtensions.cs | 112 +- src/Umbraco.Core/Models/UnLinkLoginModel.cs | 19 +- .../Models/UpgradeCheckResponse.cs | 32 +- src/Umbraco.Core/Models/UsageInformation.cs | 23 +- src/Umbraco.Core/Models/UserData.cs | 24 +- src/Umbraco.Core/Models/UserExtensions.cs | 481 +- src/Umbraco.Core/Models/UserTourStatus.cs | 87 +- .../Models/UserTwoFactorProviderModel.cs | 23 +- .../Models/ValidatePasswordResetCodeModel.cs | 26 +- .../RequiredForPersistenceAttribute.cs | 45 +- src/Umbraco.Core/Models/ValueStorageType.cs | 69 +- src/Umbraco.Core/MonitorLock.cs | 43 +- src/Umbraco.Core/NamedUdiRange.cs | 54 +- src/Umbraco.Core/Net/IIpResolver.cs | 9 +- src/Umbraco.Core/Net/ISessionIdResolver.cs | 9 +- src/Umbraco.Core/Net/IUserAgentProvider.cs | 9 +- src/Umbraco.Core/Net/NullSessionIdResolver.cs | 9 +- .../ApplicationCacheRefresherNotification.cs | 10 +- .../AssignedMemberRolesNotification.cs | 10 +- ...ssignedUserGroupPermissionsNotification.cs | 15 +- .../CacheRefresherNotification.cs | 26 +- .../CancelableEnumerableObjectNotification.cs | 18 +- .../Notifications/CancelableNotification.cs | 23 +- .../CancelableObjectNotification.cs | 23 +- .../ContentCacheRefresherNotification.cs | 10 +- .../ContentCopiedNotification.cs | 12 +- .../ContentCopyingNotification.cs | 11 +- .../ContentDeletedBlueprintNotification.cs | 21 +- .../ContentDeletedNotification.cs | 9 +- .../ContentDeletedVersionsNotification.cs | 12 +- .../ContentDeletingNotification.cs | 16 +- .../ContentDeletingVersionsNotification.cs | 12 +- .../ContentEmptiedRecycleBinNotification.cs | 11 +- .../ContentEmptyingRecycleBinNotification.cs | 11 +- .../Notifications/ContentMovedNotification.cs | 17 +- .../ContentMovedToRecycleBinNotification.cs | 18 +- .../ContentMovingNotification.cs | 17 +- .../ContentMovingToRecycleBinNotification.cs | 18 +- .../ContentNotificationExtensions.cs | 109 +- .../ContentPublishedNotification.cs | 20 +- .../ContentPublishingNotification.cs | 20 +- .../ContentRefreshNotification.cs | 15 +- .../ContentRolledBackNotification.cs | 9 +- .../ContentRollingBackNotification.cs | 9 +- .../ContentSavedBlueprintNotification.cs | 13 +- .../Notifications/ContentSavedNotification.cs | 16 +- .../ContentSavingNotification.cs | 16 +- .../ContentSendingToPublishNotification.cs | 13 +- .../ContentSentToPublishNotification.cs | 13 +- .../ContentSortedNotification.cs | 10 +- .../ContentSortingNotification.cs | 10 +- .../ContentTreeChangeNotification.cs | 38 +- .../ContentTypeCacheRefresherNotification.cs | 10 +- .../ContentTypeChangeNotification.cs | 23 +- .../ContentTypeChangedNotification.cs | 18 +- .../ContentTypeDeletedNotification.cs | 17 +- .../ContentTypeDeletingNotification.cs | 17 +- .../ContentTypeMovedNotification.cs | 18 +- .../ContentTypeMovingNotification.cs | 18 +- .../ContentTypeRefreshNotification.cs | 19 +- .../ContentTypeRefreshedNotification.cs | 23 +- .../ContentTypeSavedNotification.cs | 17 +- .../ContentTypeSavingNotification.cs | 17 +- .../ContentUnpublishedNotification.cs | 20 +- .../ContentUnpublishingNotification.cs | 21 +- .../Notifications/CopiedNotification.cs | 26 +- .../Notifications/CopyingNotification.cs | 21 +- .../Notifications/CreatedNotification.cs | 13 +- .../Notifications/CreatingNotification.cs | 13 +- .../CreatingRequestNotification.cs | 25 +- .../DataTypeCacheRefresherNotification.cs | 10 +- .../DataTypeDeletedNotification.cs | 9 +- .../DataTypeDeletingNotification.cs | 9 +- .../DataTypeMovedNotification.cs | 9 +- .../DataTypeMovingNotification.cs | 9 +- .../DataTypeSavedNotification.cs | 16 +- .../DataTypeSavingNotification.cs | 16 +- .../Notifications/DeletedNotification.cs | 20 +- .../DeletedVersionsNotification.cs | 13 +- .../DeletedVersionsNotificationBase.cs | 33 +- .../Notifications/DeletingNotification.cs | 20 +- .../DeletingVersionsNotification.cs | 18 +- .../DictionaryCacheRefresherNotification.cs | 10 +- .../DictionaryItemDeletedNotification.cs | 9 +- .../DictionaryItemDeletingNotification.cs | 17 +- .../DictionaryItemSavedNotification.cs | 17 +- .../DictionaryItemSavingNotification.cs | 17 +- .../DomainCacheRefresherNotification.cs | 10 +- .../DomainDeletedNotification.cs | 9 +- .../DomainDeletingNotification.cs | 16 +- .../Notifications/DomainSavedNotification.cs | 16 +- .../Notifications/DomainSavingNotification.cs | 16 +- .../EmptiedRecycleBinNotification.cs | 20 +- .../EmptyingRecycleBinNotification.cs | 22 +- .../EntityContainerDeletedNotification.cs | 9 +- .../EntityContainerDeletingNotification.cs | 9 +- .../EntityContainerRenamedNotification.cs | 9 +- .../EntityContainerRenamingNotification.cs | 9 +- .../EntityContainerSavedNotification.cs | 9 +- .../EntityContainerSavingNotification.cs | 9 +- .../EntityRefreshNotification.cs | 13 +- .../EnumerableObjectNotification.cs | 16 +- .../ExportedMemberNotification.cs | 19 +- .../Notifications/ICancelableNotification.cs | 9 +- .../Notifications/INotification.cs | 13 +- .../Notifications/IStatefulNotification.cs | 9 +- ...IUmbracoApplicationLifetimeNotification.cs | 25 +- .../ImportedPackageNotification.cs | 16 +- .../ImportingPackageNotification.cs | 16 +- .../LanguageCacheRefresherNotification.cs | 10 +- .../LanguageDeletedNotification.cs | 9 +- .../LanguageDeletingNotification.cs | 16 +- .../LanguageSavedNotification.cs | 16 +- .../LanguageSavingNotification.cs | 16 +- .../MacroCacheRefresherNotification.cs | 10 +- .../Notifications/MacroDeletedNotification.cs | 9 +- .../MacroDeletingNotification.cs | 16 +- .../Notifications/MacroSavedNotification.cs | 16 +- .../Notifications/MacroSavingNotification.cs | 16 +- .../MediaCacheRefresherNotification.cs | 10 +- .../Notifications/MediaDeletedNotification.cs | 9 +- .../MediaDeletedVersionsNotification.cs | 12 +- .../MediaDeletingNotification.cs | 16 +- .../MediaDeletingVersionsNotification.cs | 12 +- .../MediaEmptiedRecycleBinNotification.cs | 11 +- .../MediaEmptyingRecycleBinNotification.cs | 11 +- .../Notifications/MediaMovedNotification.cs | 17 +- .../MediaMovedToRecycleBinNotification.cs | 18 +- .../Notifications/MediaMovingNotification.cs | 17 +- .../MediaMovingToRecycleBinNotification.cs | 18 +- .../Notifications/MediaRefreshNotification.cs | 14 +- .../Notifications/MediaSavedNotification.cs | 16 +- .../Notifications/MediaSavingNotification.cs | 16 +- .../MediaTreeChangeNotification.cs | 36 +- .../MediaTypeChangedNotification.cs | 18 +- .../MediaTypeDeletedNotification.cs | 16 +- .../MediaTypeDeletingNotification.cs | 17 +- .../MediaTypeMovedNotification.cs | 17 +- .../MediaTypeMovingNotification.cs | 18 +- .../MediaTypeRefreshedNotification.cs | 23 +- .../MediaTypeSavedNotification.cs | 16 +- .../MediaTypeSavingNotification.cs | 16 +- .../MemberCacheRefresherNotification.cs | 10 +- .../MemberDeletedNotification.cs | 9 +- .../MemberDeletingNotification.cs | 16 +- .../MemberGroupCacheRefresherNotification.cs | 10 +- .../MemberGroupDeletedNotification.cs | 9 +- .../MemberGroupDeletingNotification.cs | 17 +- .../MemberGroupSavedNotification.cs | 17 +- .../MemberGroupSavingNotification.cs | 19 +- .../MemberRefreshNotification.cs | 14 +- .../Notifications/MemberRolesNotification.cs | 19 +- .../Notifications/MemberSavedNotification.cs | 16 +- .../Notifications/MemberSavingNotification.cs | 16 +- .../MemberTwoFactorRequestedNotification.cs | 14 +- .../MemberTypeChangedNotification.cs | 18 +- .../MemberTypeDeletedNotification.cs | 17 +- .../MemberTypeDeletingNotification.cs | 17 +- .../MemberTypeMovedNotification.cs | 18 +- .../MemberTypeMovingNotification.cs | 18 +- .../MemberTypeRefreshedNotification.cs | 23 +- .../MemberTypeSavedNotification.cs | 16 +- .../MemberTypeSavingNotification.cs | 17 +- .../ModelBindingErrorNotification.cs | 52 +- .../Notifications/MovedNotification.cs | 20 +- .../MovedToRecycleBinNotification.cs | 22 +- .../Notifications/MovingNotification.cs | 20 +- .../MovingToRecycleBinNotification.cs | 22 +- .../Notifications/NotificationExtensions.cs | 22 +- .../Notifications/ObjectNotification.cs | 19 +- .../PartialViewCreatedNotification.cs | 9 +- .../PartialViewCreatingNotification.cs | 9 +- .../PartialViewDeletedNotification.cs | 9 +- .../PartialViewDeletingNotification.cs | 17 +- .../PartialViewSavedNotification.cs | 17 +- .../PartialViewSavingNotification.cs | 17 +- .../PublicAccessCacheRefresherNotification.cs | 10 +- .../PublicAccessEntryDeletedNotification.cs | 10 +- .../PublicAccessEntryDeletingNotification.cs | 18 +- .../PublicAccessEntrySavedNotification.cs | 17 +- .../PublicAccessEntrySavingNotification.cs | 18 +- .../RelationDeletedNotification.cs | 16 +- .../RelationDeletingNotification.cs | 16 +- .../RelationSavedNotification.cs | 16 +- .../RelationSavingNotification.cs | 16 +- .../RelationTypeCacheRefresherNotification.cs | 10 +- .../RelationTypeDeletedNotification.cs | 9 +- .../RelationTypeDeletingNotification.cs | 17 +- .../RelationTypeSavedNotification.cs | 17 +- .../RelationTypeSavingNotification.cs | 17 +- .../RemovedMemberRolesNotification.cs | 10 +- .../Notifications/RenamedNotification.cs | 20 +- .../Notifications/RenamingNotification.cs | 20 +- .../Notifications/RolledBackNotification.cs | 13 +- .../Notifications/RollingBackNotification.cs | 13 +- .../RoutingRequestNotification.cs | 25 +- .../RuntimeUnattendedInstallNotification.cs | 21 +- .../RuntimeUnattendedUpgradeNotification.cs | 40 +- .../Notifications/SavedNotification.cs | 20 +- .../Notifications/SavingNotification.cs | 20 +- .../ScopedEntityRemoveNotification.cs | 18 +- .../ScriptDeletedNotification.cs | 9 +- .../ScriptDeletingNotification.cs | 16 +- .../Notifications/ScriptSavedNotification.cs | 16 +- .../Notifications/ScriptSavingNotification.cs | 16 +- .../Notifications/SendEmailNotification.cs | 41 +- .../SendingAllowedChildrenNotification.cs | 20 +- .../SendingContentNotification.cs | 19 +- .../SendingDashboardsNotification.cs | 20 +- .../Notifications/SendingMediaNotification.cs | 19 +- .../SendingMemberNotification.cs | 19 +- .../Notifications/SendingUserNotification.cs | 19 +- .../ServerVariablesParsingNotification.cs | 26 +- .../Notifications/SortedNotification.cs | 14 +- .../Notifications/SortingNotification.cs | 14 +- .../Notifications/StatefulNotification.cs | 26 +- .../StylesheetDeletedNotification.cs | 9 +- .../StylesheetDeletingNotification.cs | 17 +- .../StylesheetSavedNotification.cs | 16 +- .../StylesheetSavingNotification.cs | 17 +- .../TemplateCacheRefresherNotification.cs | 10 +- .../TemplateDeletedNotification.cs | 9 +- .../TemplateDeletingNotification.cs | 16 +- .../TemplateSavedNotification.cs | 78 +- .../TemplateSavingNotification.cs | 104 +- .../Notifications/TreeChangeNotification.cs | 20 +- ...icationComponentsInstallingNotification.cs | 37 +- ...oApplicationMainDomAcquiredNotification.cs | 29 +- .../UmbracoApplicationStartedNotification.cs | 25 +- .../UmbracoApplicationStartingNotification.cs | 68 +- .../UmbracoApplicationStoppedNotification.cs | 25 +- .../UmbracoApplicationStoppingNotification.cs | 41 +- .../UmbracoRequestBeginNotification.cs | 25 +- .../UmbracoRequestEndNotification.cs | 25 +- .../UnattendedInstallNotification.cs | 13 +- .../UserCacheRefresherNotification.cs | 10 +- .../Notifications/UserDeletedNotification.cs | 9 +- .../Notifications/UserDeletingNotification.cs | 16 +- .../UserForgotPasswordChangedNotification.cs | 10 +- ...UserForgotPasswordRequestedNotification.cs | 10 +- .../UserGroupCacheRefresherNotification.cs | 10 +- .../UserGroupDeletedNotification.cs | 9 +- .../UserGroupDeletingNotification.cs | 17 +- .../UserGroupSavedNotification.cs | 16 +- .../UserGroupSavingNotification.cs | 16 +- .../UserGroupWithUsersSavedNotification.cs | 18 +- .../UserGroupWithUsersSavingNotification.cs | 18 +- .../Notifications/UserLockedNotification.cs | 10 +- .../UserLoginFailedNotification.cs | 10 +- ...erLoginRequiresVerificationNotification.cs | 10 +- .../UserLoginSuccessNotification.cs | 10 +- .../UserLogoutSuccessNotification.cs | 14 +- .../Notifications/UserNotification.cs | 51 +- .../UserPasswordChangedNotification.cs | 10 +- .../UserPasswordResetNotification.cs | 10 +- .../UserResetAccessFailedCountNotification.cs | 10 +- .../Notifications/UserSavedNotification.cs | 16 +- .../Notifications/UserSavingNotification.cs | 16 +- .../UserTwoFactorRequestedNotification.cs | 14 +- .../Notifications/UserUnlockedNotification.cs | 10 +- .../Packaging/CompiledPackageXmlParser.cs | 123 +- .../Packaging/ConflictingPackageData.cs | 100 +- .../Packaging/ICreatedPackagesRepository.cs | 19 +- .../Packaging/IPackageDefinitionRepository.cs | 31 +- .../Packaging/IPackageInstallation.cs | 47 +- .../Packaging/InstallationSummary.cs | 128 +- .../Packaging/InstalledPackage.cs | 48 +- .../InstalledPackageMigrationPlans.cs | 40 +- .../Packaging/PackageDefinition.cs | 91 +- .../Packaging/PackageDefinitionXmlParser.cs | 160 +- .../Packaging/PackageMigrationResource.cs | 177 +- .../Packaging/PackagesRepository.cs | 1257 +-- .../Persistence/Constants-DatabaseSchema.cs | 166 +- .../Persistence/Constants-Locks.cs | 107 +- .../Persistence/IQueryRepository.cs | 28 +- .../Persistence/IReadRepository.cs | 33 +- .../Persistence/IReadWriteQueryRepository.cs | 13 +- src/Umbraco.Core/Persistence/IRepository.cs | 12 +- .../Persistence/IWriteRepository.cs | 27 +- .../Persistence/Querying/IQuery.cs | 63 +- .../Querying/StringPropertyMatchType.cs | 26 +- .../Querying/ValuePropertyMatchType.cs | 23 +- .../Repositories/IAuditEntryRepository.cs | 30 +- .../Repositories/IAuditRepository.cs | 64 +- .../ICacheInstructionRepository.cs | 85 +- .../Repositories/IConsentRepository.cs | 17 +- .../Repositories/IContentRepository.cs | 123 +- .../IContentTypeCommonRepository.cs | 35 +- .../Repositories/IContentTypeRepository.cs | 53 +- .../IContentTypeRepositoryBase.cs | 71 +- .../IDataTypeContainerRepository.cs | 6 +- .../Repositories/IDataTypeRepository.cs | 25 +- .../Repositories/IDictionaryRepository.cs | 19 +- .../IDocumentBlueprintRepository.cs | 6 +- .../Repositories/IDocumentRepository.cs | 157 +- .../IDocumentTypeContainerRepository.cs | 6 +- .../IDocumentVersionRepository.cs | 57 +- .../Repositories/IDomainRepository.cs | 18 +- .../IEntityContainerRepository.cs | 15 +- .../Repositories/IEntityRepository.cs | 102 +- .../Repositories/IExternalLoginRepository.cs | 43 +- .../IExternalLoginWithKeyRepository.cs | 37 +- .../Repositories/IFileRepository.cs | 13 +- .../IFileWithFoldersRepository.cs | 11 +- .../Repositories/IIdKeyMapRepository.cs | 3 +- .../Repositories/IInstallationRepository.cs | 9 +- .../Repositories/IKeyValueRepository.cs | 20 +- .../Repositories/ILanguageRepository.cs | 65 +- .../Repositories/ILogViewerQueryRepository.cs | 9 +- .../Repositories/IMacroRepository.cs | 14 +- .../Repositories/IMacroWithAliasRepository.cs | 15 +- .../Repositories/IMediaRepository.cs | 14 +- .../IMediaTypeContainerRepository.cs | 6 +- .../Repositories/IMediaTypeRepository.cs | 6 +- .../Repositories/IMemberGroupRepository.cs | 89 +- .../Repositories/IMemberRepository.cs | 105 +- .../IMemberTypeContainerRepository.cs | 6 +- .../Repositories/IMemberTypeRepository.cs | 6 +- .../Repositories/INodeCountRepository.cs | 4 +- .../Repositories/INotificationsRepository.cs | 30 +- .../IPartialViewMacroRepository.cs | 14 +- .../Repositories/IPartialViewRepository.cs | 8 +- .../Repositories/IPublicAccessRepository.cs | 9 +- .../Repositories/IRedirectUrlRepository.cs | 147 +- .../Repositories/IRelationRepository.cs | 55 +- .../Repositories/IRelationTypeRepository.cs | 10 +- .../Repositories/IScriptRepository.cs | 9 +- .../IServerRegistrationRepository.cs | 14 +- .../Repositories/IStylesheetRepository.cs | 8 +- .../Repositories/ITagRepository.cs | 192 +- .../Repositories/ITemplateRepository.cs | 16 +- .../ITrackedReferencesRepository.cs | 82 +- .../Repositories/ITwoFactorLoginRepository.cs | 17 +- .../Repositories/IUpgradeCheckRepository.cs | 12 +- .../Repositories/IUserGroupRepository.cs | 117 +- .../Repositories/IUserRepository.cs | 187 +- .../Repositories/InstallationRepository.cs | 43 +- .../Repositories/RepositoryCacheKeys.cs | 46 +- .../Repositories/UpgradeCheckRepository.cs | 81 +- .../Persistence/SqlExpressionExtensions.cs | 68 +- .../Persistence/SqlExtensionsStatics.cs | 74 +- .../Persistence/TextColumnType.cs | 11 +- .../PropertyEditors/BlockListConfiguration.cs | 96 +- .../ColorPickerConfiguration.cs | 19 +- .../PropertyEditors/ConfigurationEditor.cs | 227 +- .../ConfigurationEditorOfTConfiguration.cs | 249 +- .../PropertyEditors/ConfigurationField.cs | 193 +- .../ConfigurationFieldAttribute.cs | 234 +- .../ContentPickerConfiguration.cs | 23 +- .../ContentPickerConfigurationEditor.cs | 46 +- .../ContentPickerPropertyEditor.cs | 106 +- .../PropertyEditors/DataEditor.cs | 362 +- .../PropertyEditors/DataEditorAttribute.cs | 281 +- .../PropertyEditors/DataEditorCollection.cs | 11 +- .../DataEditorCollectionBuilder.cs | 11 +- .../PropertyEditors/DataValueEditor.cs | 684 +- .../PropertyEditors/DataValueEditorFactory.cs | 21 +- .../DataValueReferenceFactoryCollection.cs | 78 +- ...aValueReferenceFactoryCollectionBuilder.cs | 10 +- .../PropertyEditors/DateTimeConfiguration.cs | 32 +- .../DateTimeConfigurationEditor.cs | 43 +- .../PropertyEditors/DateValueEditor.cs | 50 +- .../DecimalConfigurationEditor.cs | 55 +- .../PropertyEditors/DecimalPropertyEditor.cs | 59 +- .../DefaultPropertyIndexValueFactory.cs | 28 +- .../DefaultPropertyValueConverterAttribute.cs | 49 +- .../DropDownFlexibleConfiguration.cs | 12 +- .../PropertyEditors/EditorType.cs | 35 +- .../EmailAddressConfiguration.cs | 20 +- .../EmailAddressConfigurationEditor.cs | 29 +- .../EyeDropperColorPickerConfiguration.cs | 22 +- ...yeDropperColorPickerConfigurationEditor.cs | 64 +- .../EyeDropperColorPickerPropertyEditor.cs | 75 +- .../FileExtensionConfigItem.cs | 15 +- .../FileUploadConfiguration.cs | 17 +- .../FileUploadConfigurationEditor.cs | 31 +- .../PropertyEditors/GridEditor.cs | 95 +- .../PropertyEditors/IConfigurationEditor.cs | 130 +- .../PropertyEditors/IConfigureValueType.cs | 23 +- .../PropertyEditors/IDataEditor.cs | 114 +- .../IDataValueEditorFactory.cs | 14 +- .../PropertyEditors/IDataValueReference.cs | 24 +- .../IDataValueReferenceFactory.cs | 28 +- .../PropertyEditors/IFileExtensionConfig.cs | 15 +- .../IFileExtensionConfigItem.cs | 11 +- .../IIgnoreUserStartNodesConfig.cs | 15 +- .../IManifestValueValidator.cs | 19 +- .../IPropertyCacheCompression.cs | 27 +- .../IPropertyCacheCompressionOptions.cs | 23 +- .../IPropertyIndexValueFactory.cs | 41 +- .../IPropertyValueConverter.cs | 223 +- .../PropertyEditors/IValueFormatValidator.cs | 36 +- .../IValueRequiredValidator.cs | 32 +- .../PropertyEditors/IValueValidator.cs | 37 +- .../IntegerConfigurationEditor.cs | 55 +- .../PropertyEditors/IntegerPropertyEditor.cs | 57 +- .../PropertyEditors/LabelConfiguration.cs | 17 +- .../LabelConfigurationEditor.cs | 59 +- .../PropertyEditors/LabelPropertyEditor.cs | 96 +- .../PropertyEditors/ListViewConfiguration.cs | 196 +- .../ListViewConfigurationEditor.cs | 29 +- .../ManifestValueValidatorCollection.cs | 39 +- ...ManifestValueValidatorCollectionBuilder.cs | 10 +- .../PropertyEditors/MarkdownConfiguration.cs | 27 +- .../MarkdownConfigurationEditor.cs | 16 +- .../PropertyEditors/MarkdownPropertyEditor.cs | 76 +- .../MediaPicker3Configuration.cs | 79 +- .../MediaPicker3ConfigurationEditor.cs | 50 +- .../MediaPickerConfiguration.cs | 39 +- .../MediaPickerConfigurationEditor.cs | 62 +- .../MediaUrlGeneratorCollection.cs | 38 +- .../MediaUrlGeneratorCollectionBuilder.cs | 10 +- .../MemberGroupPickerPropertyEditor.cs | 30 +- .../MemberPickerConfiguration.cs | 13 +- .../MemberPickerPropertyEditor.cs | 34 +- .../PropertyEditors/MissingPropertyEditor.cs | 49 +- .../MultiNodePickerConfiguration.cs | 42 +- .../MultiNodePickerConfigurationEditor.cs | 78 +- .../MultiNodePickerConfigurationTreeSource.cs | 24 +- .../MultiUrlPickerConfiguration.cs | 36 +- .../MultiUrlPickerConfigurationEditor.cs | 25 +- .../MultipleTextStringConfiguration.cs | 19 +- .../NestedContentConfiguration.cs | 57 +- .../NestedContentConfigurationEditor.cs | 29 +- .../NoopPropertyCacheCompressionOptions.cs | 17 +- .../ParameterEditorCollection.cs | 31 +- .../ContentTypeParameterEditor.cs | 41 +- .../MultipleContentPickerParameterEditor.cs | 59 +- .../MultipleContentTypeParameterEditor.cs | 33 +- .../MultipleMediaPickerParameterEditor.cs | 59 +- ...ultiplePickerParamateterValueEditorBase.cs | 71 +- .../MultiplePropertyGroupParameterEditor.cs | 37 +- .../MultiplePropertyTypeParameterEditor.cs | 37 +- .../PropertyGroupParameterEditor.cs | 37 +- .../PropertyTypeParameterEditor.cs | 37 +- .../PropertyCacheCompression.cs | 77 +- .../PropertyEditors/PropertyCacheLevel.cs | 63 +- .../PropertyEditorCollection.cs | 40 +- .../PropertyEditorTagsExtensions.cs | 29 +- .../PropertyValueConverterBase.cs | 98 +- .../PropertyValueConverterCollection.cs | 57 +- ...PropertyValueConverterCollectionBuilder.cs | 10 +- .../PropertyEditors/PropertyValueLevel.cs | 33 +- .../PropertyEditors/RichTextConfiguration.cs | 42 +- .../RichTextConfigurationEditor.cs | 29 +- .../PropertyEditors/SliderConfiguration.cs | 37 +- .../SliderConfigurationEditor.cs | 29 +- .../PropertyEditors/TagConfiguration.cs | 30 +- .../PropertyEditors/TagConfigurationEditor.cs | 79 +- .../TagsPropertyEditorAttribute.cs | 105 +- .../PropertyEditors/TextAreaConfiguration.cs | 23 +- .../TextAreaConfigurationEditor.cs | 29 +- .../PropertyEditors/TextOnlyValueEditor.cs | 84 +- .../TextStringValueConverter.cs | 86 +- .../PropertyEditors/TextboxConfiguration.cs | 18 +- .../TextboxConfigurationEditor.cs | 29 +- .../PropertyEditors/TrueFalseConfiguration.cs | 32 +- .../TrueFalseConfigurationEditor.cs | 29 +- ...dContentPropertyCacheCompressionOptions.cs | 37 +- .../UserPickerConfiguration.cs | 14 +- .../UserPickerPropertyEditor.cs | 34 +- ...omplexEditorElementTypeValidationResult.cs | 65 +- ...mplexEditorPropertyTypeValidationResult.cs | 52 +- .../ComplexEditorValidationResult.cs | 39 +- .../Validators/DateTimeValidator.cs | 48 +- .../Validators/DecimalValidator.cs | 36 +- .../Validators/DelimitedValueValidator.cs | 78 +- .../Validators/EmailValidator.cs | 38 +- .../Validators/IntegerValidator.cs | 34 +- .../Validators/RegexValidator.cs | 129 +- .../Validators/RequiredValidator.cs | 74 +- .../CheckboxListValueConverter.cs | 48 +- .../ContentPickerValueConverter.cs | 138 +- .../DatePickerValueConverter.cs | 78 +- .../ValueConverters/DecimalValueConverter.cs | 76 +- .../EmailAddressValueConverter.cs | 30 +- .../EyeDropperValueConverter.cs | 27 +- .../ValueConverters/IntegerValueConverter.cs | 30 +- .../ValueConverters/LabelValueConverter.cs | 189 +- .../MediaPickerValueConverter.cs | 134 +- .../MemberGroupPickerValueConverter.cs | 30 +- .../MemberPickerValueConverter.cs | 139 +- .../MultiNodeTreePickerValueConverter.cs | 247 +- .../MultipleTextStringValueConverter.cs | 127 +- .../MustBeStringValueConverter.cs | 60 +- .../RadioButtonListValueConverter.cs | 39 +- .../SimpleTinyMceValueConverter.cs | 61 +- .../ValueConverters/SliderValueConverter.cs | 117 +- .../ValueConverters/TagsValueConverter.cs | 114 +- .../UploadPropertyConverter.cs | 36 +- .../ValueConverters/YesNoValueConverter.cs | 88 +- .../PropertyEditors/ValueListConfiguration.cs | 32 +- .../PropertyEditors/ValueTypes.cs | 205 +- .../PropertyEditors/VoidEditor.cs | 73 +- .../PublishedCache/DefaultCultureAccessor.cs | 46 +- .../PublishedCache/IDefaultCultureAccessor.cs | 23 +- .../PublishedCache/IDomainCache.cs | 55 +- .../PublishedCache/IPublishedCache.cs | 477 +- .../PublishedCache/IPublishedContentCache.cs | 123 +- .../PublishedCache/IPublishedMediaCache.cs | 6 +- .../PublishedCache/IPublishedSnapshot.cs | 118 +- .../IPublishedSnapshotAccessor.cs | 16 +- .../IPublishedSnapshotService.cs | 182 +- .../IPublishedSnapshotStatus.cs | 25 +- src/Umbraco.Core/PublishedCache/ITagQuery.cs | 109 +- .../Internal/InternalPublishedContent.cs | 130 +- .../Internal/InternalPublishedContentCache.cs | 79 +- .../Internal/InternalPublishedProperty.cs | 32 +- .../Internal/InternalPublishedSnapshot.cs | 42 +- .../InternalPublishedSnapshotService.cs | 83 +- .../PublishedCache/PublishedCacheBase.cs | 127 +- .../PublishedCache/PublishedElement.cs | 146 +- .../PublishedElementPropertyBase.cs | 324 +- ...UmbracoContextPublishedSnapshotAccessor.cs | 60 +- src/Umbraco.Core/ReflectionUtilities.cs | 1806 +++-- src/Umbraco.Core/Routing/AliasUrlProvider.cs | 233 +- .../Routing/ContentFinderByIdPath.cs | 160 +- .../Routing/ContentFinderByPageIdQuery.cs | 66 +- .../Routing/ContentFinderByRedirectUrl.cs | 147 +- .../Routing/ContentFinderByUrl.cs | 144 +- .../Routing/ContentFinderByUrlAlias.cs | 229 +- .../Routing/ContentFinderByUrlAndTemplate.cs | 175 +- .../Routing/ContentFinderCollection.cs | 11 +- .../Routing/ContentFinderCollectionBuilder.cs | 10 +- .../Routing/DefaultMediaUrlProvider.cs | 114 +- .../Routing/DefaultUrlProvider.cs | 370 +- src/Umbraco.Core/Routing/Domain.cs | 103 +- src/Umbraco.Core/Routing/DomainAndUri.cs | 68 +- src/Umbraco.Core/Routing/DomainUtilities.cs | 657 +- src/Umbraco.Core/Routing/IContentFinder.cs | 26 +- .../Routing/IContentLastChanceFinder.cs | 16 +- src/Umbraco.Core/Routing/IMediaUrlProvider.cs | 52 +- src/Umbraco.Core/Routing/IPublishedRequest.cs | 175 +- .../Routing/IPublishedRequestBuilder.cs | 320 +- src/Umbraco.Core/Routing/IPublishedRouter.cs | 86 +- .../Routing/IPublishedUrlProvider.cs | 192 +- src/Umbraco.Core/Routing/ISiteDomainMapper.cs | 82 +- src/Umbraco.Core/Routing/IUrlProvider.cs | 69 +- .../Routing/MediaUrlProviderCollection.cs | 11 +- .../MediaUrlProviderCollectionBuilder.cs | 10 +- src/Umbraco.Core/Routing/PublishedRequest.cs | 101 +- .../Routing/PublishedRequestBuilder.cs | 316 +- .../Routing/PublishedRequestExtensions.cs | 173 +- .../Routing/PublishedRequestOld.cs | 709 +- src/Umbraco.Core/Routing/PublishedRouter.cs | 1302 +-- src/Umbraco.Core/Routing/RouteDirection.cs | 25 +- .../Routing/RouteRequestOptions.cs | 37 +- src/Umbraco.Core/Routing/SiteDomainMapper.cs | 637 +- .../Routing/UmbracoRequestPaths.cs | 215 +- .../Routing/UmbracoRouteResult.cs | 31 +- src/Umbraco.Core/Routing/UriUtility.cs | 311 +- src/Umbraco.Core/Routing/UrlInfo.cs | 163 +- src/Umbraco.Core/Routing/UrlProvider.cs | 420 +- .../Routing/UrlProviderCollection.cs | 11 +- .../Routing/UrlProviderCollectionBuilder.cs | 10 +- .../Routing/UrlProviderExtensions.cs | 422 +- src/Umbraco.Core/Routing/WebPath.cs | 72 +- .../Runtime/EssentialDirectoryCreator.cs | 45 +- src/Umbraco.Core/Runtime/IMainDom.cs | 58 +- .../Runtime/IMainDomKeyGenerator.cs | 17 +- src/Umbraco.Core/Runtime/IMainDomLock.cs | 42 +- .../Runtime/IUmbracoBootPermissionChecker.cs | 9 +- src/Umbraco.Core/Runtime/MainDom.cs | 438 +- .../Runtime/MainDomSemaphoreLock.cs | 150 +- src/Umbraco.Core/RuntimeLevel.cs | 61 +- src/Umbraco.Core/RuntimeLevelReason.cs | 123 +- src/Umbraco.Core/Scoping/ICoreScope.cs | 19 +- .../Scoping/ICoreScopeProvider.cs | 81 +- .../Scoping/IInstanceIdentifiable.cs | 20 +- src/Umbraco.Core/Scoping/IScopeContext.cs | 89 +- .../Scoping/RepositoryCacheMode.cs | 57 +- src/Umbraco.Core/Sections/ContentSection.cs | 21 +- src/Umbraco.Core/Sections/FormsSection.cs | 17 +- src/Umbraco.Core/Sections/ISection.cs | 25 +- src/Umbraco.Core/Sections/MediaSection.cs | 17 +- src/Umbraco.Core/Sections/MembersSection.cs | 21 +- src/Umbraco.Core/Sections/PackagesSection.cs | 21 +- .../Sections/SectionCollection.cs | 11 +- .../Sections/SectionCollectionBuilder.cs | 29 +- src/Umbraco.Core/Sections/SettingsSection.cs | 21 +- .../Sections/TranslationSection.cs | 21 +- src/Umbraco.Core/Sections/UsersSection.cs | 21 +- .../Security/AuthenticationExtensions.cs | 37 +- .../BackOfficeExternalLoginProviderErrors.cs | 28 +- .../Security/BackOfficeIdentityOptions.cs | 13 +- .../BackOfficeUserPasswordCheckerResult.cs | 19 +- .../Security/ClaimsPrincipalExtensions.cs | 135 +- .../Security/ContentPermissions.cs | 504 +- src/Umbraco.Core/Security/ExternalLogin.cs | 38 +- .../Security/ExternalLoginToken.cs | 37 +- .../Security/IBackofficeSecurity.cs | 73 +- .../Security/IBackofficeSecurityAccessor.cs | 9 +- src/Umbraco.Core/Security/IExternalLogin.cs | 33 +- .../Security/IExternalLoginToken.cs | 33 +- src/Umbraco.Core/Security/IHtmlSanitizer.cs | 19 +- .../Security/IIdentityUserLogin.cs | 43 +- .../Security/IIdentityUserToken.cs | 41 +- src/Umbraco.Core/Security/IPasswordHasher.cs | 19 +- .../Security/IPublicAccessChecker.cs | 9 +- .../Security/ITwoFactorProvider.cs | 25 +- .../Security/IdentityAuditEventArgs.cs | 142 +- .../Security/IdentityUserLogin.cs | 65 +- .../Security/IdentityUserToken.cs | 67 +- .../Security/LegacyPasswordSecurity.cs | 379 +- src/Umbraco.Core/Security/MediaPermissions.cs | 111 +- .../Security/NoopHtmlSanitizer.cs | 12 +- .../Security/PasswordGenerator.cs | 292 +- .../Security/PublicAccessStatus.cs | 17 +- .../Security/UpdateMemberProfileResult.cs | 28 +- .../Security/UpdateMemberProfileStatus.cs | 11 +- src/Umbraco.Core/Semver/Semver.cs | 357 +- .../IConfigurationEditorJsonSerializer.cs | 8 +- .../Serialization/IJsonSerializer.cs | 13 +- src/Umbraco.Core/Services/AuditService.cs | 479 +- src/Umbraco.Core/Services/BasicAuthService.cs | 54 +- .../Services/Changes/ContentTypeChange.cs | 26 +- .../Changes/ContentTypeChangeExtensions.cs | 34 +- .../Changes/ContentTypeChangeTypes.cs | 43 +- .../Services/Changes/DomainChangeTypes.cs | 15 +- .../Services/Changes/TreeChange.cs | 43 +- .../Services/Changes/TreeChangeExtensions.cs | 37 +- .../Services/Changes/TreeChangeTypes.cs | 33 +- src/Umbraco.Core/Services/ConsentService.cs | 141 +- src/Umbraco.Core/Services/ContentService.cs | 5644 +++++++------ .../Services/ContentServiceExtensions.cs | 150 +- .../ContentTypeBaseServiceProvider.cs | 62 +- .../Services/ContentTypeService.cs | 201 +- .../Services/ContentTypeServiceBase.cs | 11 +- ...peServiceBaseOfTRepositoryTItemTService.cs | 1637 ++-- .../Services/ContentTypeServiceExtensions.cs | 324 +- .../Services/ContentVersionService.cs | 296 +- src/Umbraco.Core/Services/DashboardService.cs | 226 +- src/Umbraco.Core/Services/DataTypeService.cs | 914 ++- .../Services/DateTypeServiceExtensions.cs | 24 +- .../DefaultContentVersionCleanupPolicy.cs | 129 +- src/Umbraco.Core/Services/DomainService.cs | 131 +- src/Umbraco.Core/Services/EntityService.cs | 695 +- .../Services/EntityXmlSerializer.cs | 1128 +-- .../Services/ExternalLoginService.cs | 162 +- src/Umbraco.Core/Services/FileService.cs | 1500 ++-- src/Umbraco.Core/Services/IAuditService.cs | 156 +- .../Services/IBasicAuthService.cs | 11 +- .../Services/ICacheInstructionService.cs | 83 +- .../Services/IConflictingRouteService.cs | 9 +- src/Umbraco.Core/Services/IConsentService.cs | 80 +- src/Umbraco.Core/Services/IContentService.cs | 1098 +-- .../Services/IContentServiceBase.cs | 33 +- .../IContentTypeBaseServiceProvider.cs | 43 +- .../Services/IContentTypeService.cs | 53 +- .../Services/IContentTypeServiceBase.cs | 179 +- .../Services/IContentVersionCleanupPolicy.cs | 19 +- .../Services/IContentVersionService.cs | 36 +- .../Services/IDashboardService.cs | 39 +- src/Umbraco.Core/Services/IDataTypeService.cs | 159 +- src/Umbraco.Core/Services/IDomainService.cs | 24 +- .../Services/IEditorConfigurationParser.cs | 6 +- src/Umbraco.Core/Services/IEntityService.cs | 495 +- .../Services/IEntityXmlSerializer.cs | 137 +- .../Services/IExamineIndexCountService.cs | 9 +- .../Services/IExternalLoginService.cs | 107 +- .../Services/IExternalLoginWithKeyService.cs | 87 +- src/Umbraco.Core/Services/IFileService.cs | 608 +- src/Umbraco.Core/Services/IIconService.cs | 32 +- src/Umbraco.Core/Services/IIdKeyMap.cs | 22 +- .../Services/IInstallationService.cs | 9 +- src/Umbraco.Core/Services/IKeyValueService.cs | 72 +- .../Services/ILocalizationService.cs | 341 +- .../Services/ILocalizedTextService.cs | 104 +- src/Umbraco.Core/Services/IMacroService.cs | 98 +- .../Services/IMacroWithAliasService.cs | 23 +- src/Umbraco.Core/Services/IMediaService.cs | 731 +- .../Services/IMediaTypeService.cs | 12 +- .../Services/IMemberGroupService.cs | 23 +- src/Umbraco.Core/Services/IMemberService.cs | 459 +- .../Services/IMemberTypeService.cs | 15 +- .../Services/IMembershipMemberService.cs | 347 +- .../Services/IMembershipRoleService.cs | 52 +- .../Services/IMembershipUserService.cs | 35 +- .../Services/IMetricsConsentService.cs | 11 +- .../Services/INodeCountService.cs | 11 +- .../Services/INotificationService.cs | 151 +- .../Services/IPackagingService.cs | 111 +- .../Services/IPropertyValidationService.cs | 60 +- .../Services/IPublicAccessService.cs | 121 +- .../Services/IRedirectUrlService.cs | 156 +- src/Umbraco.Core/Services/IRelationService.cs | 703 +- src/Umbraco.Core/Services/IRuntime.cs | 27 +- src/Umbraco.Core/Services/IRuntimeState.cs | 93 +- src/Umbraco.Core/Services/ISectionService.cs | 42 +- .../Services/IServerRegistrationService.cs | 95 +- src/Umbraco.Core/Services/IService.cs | 14 +- src/Umbraco.Core/Services/ITagService.cs | 190 +- .../Services/ITrackedReferencesService.cs | 75 +- src/Umbraco.Core/Services/ITreeService.cs | 46 +- .../Services/ITwoFactorLoginService.cs | 103 +- src/Umbraco.Core/Services/IUpgradeService.cs | 12 +- .../Services/IUsageInformationService.cs | 12 +- src/Umbraco.Core/Services/IUserDataService.cs | 12 +- src/Umbraco.Core/Services/IUserService.cs | 467 +- src/Umbraco.Core/Services/IdKeyMap.cs | 464 +- .../Services/InstallationService.cs | 22 +- src/Umbraco.Core/Services/KeyValueService.cs | 126 +- .../Services/LocalizationService.cs | 773 +- .../Services/LocalizedTextService.cs | 733 +- .../LocalizedTextServiceExtensions.cs | 165 +- .../LocalizedTextServiceFileSources.cs | 475 +- ...lizedTextServiceSupplementaryFileSource.cs | 22 +- src/Umbraco.Core/Services/MacroService.cs | 252 +- src/Umbraco.Core/Services/MediaService.cs | 2131 ++--- .../Services/MediaServiceExtensions.cs | 58 +- src/Umbraco.Core/Services/MediaTypeService.cs | 102 +- .../Services/MemberGroupService.cs | 141 +- src/Umbraco.Core/Services/MemberService.cs | 2095 ++--- .../Services/MemberTypeService.cs | 158 +- .../Services/MetricsConsentService.cs | 77 +- .../Services/MoveOperationStatusType.cs | 48 +- src/Umbraco.Core/Services/NodeCountService.cs | 42 +- .../Services/NotificationService.cs | 929 ++- src/Umbraco.Core/Services/OperationResult.cs | 414 +- .../Services/OperationResultType.cs | 81 +- src/Umbraco.Core/Services/Ordering.cs | 147 +- .../Services/ProcessInstructionsResult.cs | 37 +- .../Services/PropertyValidationService.cs | 327 +- .../Services/PublicAccessService.cs | 367 +- .../Services/PublicAccessServiceExtensions.cs | 168 +- src/Umbraco.Core/Services/PublishResult.cs | 53 +- .../Services/PublishResultType.cs | 296 +- .../Services/RedirectUrlService.cs | 158 +- src/Umbraco.Core/Services/RelationService.cs | 882 +- .../Services/RepositoryService.cs | 33 +- src/Umbraco.Core/Services/SectionService.cs | 59 +- .../Services/ServerRegistrationService.cs | 270 +- src/Umbraco.Core/Services/ServiceContext.cs | 490 +- src/Umbraco.Core/Services/TagService.cs | 229 +- .../Services/TrackedReferencesService.cs | 107 +- src/Umbraco.Core/Services/TreeService.cs | 64 +- .../Services/TwoFactorLoginService.cs | 312 +- src/Umbraco.Core/Services/UpgradeService.cs | 22 +- src/Umbraco.Core/Services/UserDataService.cs | 69 +- src/Umbraco.Core/Services/UserService.cs | 1944 +++-- .../Services/UserServiceExtensions.cs | 146 +- src/Umbraco.Core/Settable.cs | 147 +- src/Umbraco.Core/SimpleMainDom.cs | 112 +- src/Umbraco.Core/StaticApplicationLogging.cs | 19 +- src/Umbraco.Core/StringUdi.cs | 92 +- src/Umbraco.Core/Strings/CleanStringType.cs | 235 +- .../Strings/Css/StylesheetHelper.cs | 90 +- .../Strings/Css/StylesheetRule.cs | 59 +- .../Strings/DefaultShortStringHelper.cs | 1058 +-- .../Strings/DefaultShortStringHelperConfig.cs | 357 +- .../Strings/DefaultUrlSegmentProvider.cs | 58 +- src/Umbraco.Core/Strings/Diff.cs | 838 +- src/Umbraco.Core/Strings/HtmlEncodedString.cs | 44 +- .../Strings/IHtmlEncodedString.cs | 19 +- .../Strings/IShortStringHelper.cs | 215 +- .../Strings/IUrlSegmentProvider.cs | 39 +- src/Umbraco.Core/Strings/PathUtility.cs | 37 +- .../Strings/UrlSegmentProviderCollection.cs | 11 +- .../UrlSegmentProviderCollectionBuilder.cs | 10 +- .../Strings/Utf8ToAsciiConverter.cs | 7188 +++++++++-------- .../Sync/ElectedServerRoleAccessor.cs | 45 +- src/Umbraco.Core/Sync/IServerAddress.cs | 19 +- src/Umbraco.Core/Sync/IServerMessenger.cs | 134 +- src/Umbraco.Core/Sync/IServerRoleAccessor.cs | 17 +- .../Sync/ISyncBootStateAccessor.cs | 19 +- src/Umbraco.Core/Sync/MessageType.cs | 27 +- .../Sync/NonRuntimeLevelBootStateAccessor.cs | 15 +- src/Umbraco.Core/Sync/RefreshInstruction.cs | 387 +- src/Umbraco.Core/Sync/RefreshMethodType.cs | 71 +- src/Umbraco.Core/Sync/ServerRole.cs | 41 +- .../Sync/SingleServerRoleAccessor.cs | 28 +- src/Umbraco.Core/Sync/SyncBootState.cs | 31 +- src/Umbraco.Core/SystemLock.cs | 288 +- .../Telemetry/ISiteIdentifierService.cs | 44 +- .../Telemetry/ITelemetryService.cs | 17 +- .../Telemetry/Models/PackageTelemetry.cs | 37 +- .../Telemetry/Models/TelemetryReportData.cs | 52 +- .../Telemetry/SiteIdentifierService.cs | 114 +- .../Telemetry/TelemetryService.cs | 128 +- .../Templates/HtmlImageSourceParser.cs | 144 +- .../Templates/HtmlLocalLinkParser.cs | 179 +- src/Umbraco.Core/Templates/HtmlUrlParser.cs | 105 +- .../Templates/ITemplateRenderer.cs | 16 +- .../Templates/IUmbracoComponentRenderer.cs | 89 +- .../Templates/UmbracoComponentRenderer.cs | 156 +- src/Umbraco.Core/Tour/BackOfficeTourFilter.cs | 112 +- src/Umbraco.Core/Tour/TourFilterCollection.cs | 17 +- .../Tour/TourFilterCollectionBuilder.cs | 104 +- src/Umbraco.Core/Trees/ActionUrlMethod.cs | 17 +- src/Umbraco.Core/Trees/CoreTreeAttribute.cs | 20 +- .../Trees/IMenuItemCollectionFactory.cs | 20 +- src/Umbraco.Core/Trees/ISearchableTree.cs | 41 +- src/Umbraco.Core/Trees/ITree.cs | 71 +- src/Umbraco.Core/Trees/MenuItemCollection.cs | 61 +- .../Trees/MenuItemCollectionFactory.cs | 20 +- src/Umbraco.Core/Trees/MenuItemList.cs | 91 +- .../Trees/SearchableApplicationTree.cs | 36 +- .../Trees/SearchableTreeAttribute.cs | 100 +- .../Trees/SearchableTreeCollection.cs | 68 +- .../Trees/SearchableTreeCollectionBuilder.cs | 14 +- src/Umbraco.Core/Trees/Tree.cs | 99 +- src/Umbraco.Core/Trees/TreeCollection.cs | 18 +- src/Umbraco.Core/Trees/TreeNode.cs | 214 +- src/Umbraco.Core/Trees/TreeNodeCollection.cs | 26 +- src/Umbraco.Core/Trees/TreeNodeExtensions.cs | 113 +- src/Umbraco.Core/Trees/TreeUse.cs | 35 +- src/Umbraco.Core/Udi.cs | 252 +- src/Umbraco.Core/UdiDefinitionAttribute.cs | 27 +- src/Umbraco.Core/UdiEntityTypeHelper.cs | 178 +- src/Umbraco.Core/UdiParser.cs | 371 +- .../UdiParserServiceConnectors.cs | 131 +- src/Umbraco.Core/UdiRange.cs | 151 +- src/Umbraco.Core/UdiType.cs | 19 +- src/Umbraco.Core/UdiTypeConverter.cs | 48 +- .../UmbracoApiControllerTypeCollection.cs | 11 +- src/Umbraco.Core/UmbracoContextReference.cs | 85 +- src/Umbraco.Core/UnknownTypeUdi.cs | 21 +- src/Umbraco.Core/UpgradeResult.cs | 23 +- src/Umbraco.Core/UriUtilityCore.cs | 76 +- .../Web/CookieManagerExtensions.cs | 15 +- .../Web/HybridUmbracoContextAccessor.cs | 54 +- src/Umbraco.Core/Web/ICookieManager.cs | 17 +- src/Umbraco.Core/Web/IRequestAccessor.cs | 31 +- src/Umbraco.Core/Web/ISessionManager.cs | 13 +- src/Umbraco.Core/Web/IUmbracoContext.cs | 117 +- .../Web/IUmbracoContextAccessor.cs | 24 +- .../Web/IUmbracoContextFactory.cs | 54 +- .../Web/Mvc/PluginControllerMetadata.cs | 29 +- src/Umbraco.Core/WebAssets/AssetFile.cs | 28 +- src/Umbraco.Core/WebAssets/AssetType.cs | 11 +- src/Umbraco.Core/WebAssets/BundlingOptions.cs | 77 +- src/Umbraco.Core/WebAssets/CssFile.cs | 21 +- .../CustomBackOfficeAssetsCollection.cs | 11 +- ...CustomBackOfficeAssetsCollectionBuilder.cs | 10 +- src/Umbraco.Core/WebAssets/IAssetFile.cs | 11 +- .../WebAssets/IRuntimeMinifier.cs | 180 +- src/Umbraco.Core/WebAssets/JavascriptFile.cs | 21 +- src/Umbraco.Core/Xml/DynamicContext.cs | 501 +- .../Xml/UmbracoXPathPathSyntaxParser.cs | 186 +- .../Xml/XPath/INavigableContent.cs | 99 +- .../Xml/XPath/INavigableContentType.cs | 27 +- .../Xml/XPath/INavigableFieldType.cs | 33 +- .../Xml/XPath/INavigableSource.cs | 47 +- src/Umbraco.Core/Xml/XPath/MacroNavigator.cs | 1768 ++-- .../Xml/XPath/NavigableNavigator.cs | 1892 ++--- .../Xml/XPath/RenamedRootNavigator.cs | 141 +- .../Xml/XPathNavigatorExtensions.cs | 99 +- src/Umbraco.Core/Xml/XPathVariable.cs | 45 +- src/Umbraco.Core/Xml/XmlHelper.cs | 790 +- src/Umbraco.Core/Xml/XmlNamespaces.cs | 61 +- src/Umbraco.Core/Xml/XmlNodeListFactory.cs | 233 +- 1933 files changed, 113046 insertions(+), 108997 deletions(-) diff --git a/src/Umbraco.Core/Actions/ActionAssignDomain.cs b/src/Umbraco.Core/Actions/ActionAssignDomain.cs index 0638f605af39..b0a6542c6fd9 100644 --- a/src/Umbraco.Core/Actions/ActionAssignDomain.cs +++ b/src/Umbraco.Core/Actions/ActionAssignDomain.cs @@ -9,25 +9,25 @@ namespace Umbraco.Cms.Core.Actions; public class ActionAssignDomain : IAction { /// - /// The unique action letter + /// The unique action letter /// public const char ActionLetter = 'I'; - /// + /// public char Letter => ActionLetter; - /// + /// public string Alias => "assignDomain"; - /// + /// public string Category => Constants.Conventions.PermissionCategories.AdministrationCategory; - /// + /// public string Icon => "home"; - /// + /// public bool ShowInNotifier => false; - /// + /// public bool CanBePermissionAssigned => true; } diff --git a/src/Umbraco.Core/Actions/ActionBrowse.cs b/src/Umbraco.Core/Actions/ActionBrowse.cs index 5be16a01c912..1686875eb370 100644 --- a/src/Umbraco.Core/Actions/ActionBrowse.cs +++ b/src/Umbraco.Core/Actions/ActionBrowse.cs @@ -1,40 +1,41 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Actions +namespace Umbraco.Cms.Core.Actions; + +/// +/// This action is used as a security constraint that grants a user the ability to view nodes in a tree +/// that has permissions applied to it. +/// +/// +/// This action should not be invoked. It is used as the minimum required permission to view nodes in the content tree. +/// By +/// granting a user this permission, the user is able to see the node in the tree but not edit the document. This may +/// be used by other trees +/// that support permissions in the future. +/// +public class ActionBrowse : IAction { /// - /// This action is used as a security constraint that grants a user the ability to view nodes in a tree - /// that has permissions applied to it. + /// The unique action letter /// - /// - /// This action should not be invoked. It is used as the minimum required permission to view nodes in the content tree. By - /// granting a user this permission, the user is able to see the node in the tree but not edit the document. This may be used by other trees - /// that support permissions in the future. - /// - public class ActionBrowse : IAction - { - /// - /// The unique action letter - /// - public const char ActionLetter = 'F'; - - /// - public char Letter => ActionLetter; - - /// - public bool ShowInNotifier => false; - - /// - public bool CanBePermissionAssigned => true; - - /// - public string Icon => string.Empty; - - /// - public string Alias => "browse"; - - /// - public string Category => Constants.Conventions.PermissionCategories.ContentCategory; - } + public const char ActionLetter = 'F'; + + /// + public char Letter => ActionLetter; + + /// + public bool ShowInNotifier => false; + + /// + public bool CanBePermissionAssigned => true; + + /// + public string Icon => string.Empty; + + /// + public string Alias => "browse"; + + /// + public string Category => Constants.Conventions.PermissionCategories.ContentCategory; } diff --git a/src/Umbraco.Core/Actions/ActionCollection.cs b/src/Umbraco.Core/Actions/ActionCollection.cs index 1e396952a20a..b204075b8869 100644 --- a/src/Umbraco.Core/Actions/ActionCollection.cs +++ b/src/Umbraco.Core/Actions/ActionCollection.cs @@ -1,60 +1,56 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Actions +namespace Umbraco.Cms.Core.Actions; + +/// +/// The collection of actions +/// +public class ActionCollection : BuilderCollectionBase { /// - /// The collection of actions + /// Initializes a new instance of the class. /// - public class ActionCollection : BuilderCollectionBase + public ActionCollection(Func> items) + : base(items) { - /// - /// Initializes a new instance of the class. - /// - public ActionCollection(Func> items) - : base(items) - { - } + } - /// - /// Gets the action of the specified type. - /// - /// The specified type to get - /// The action - public T? GetAction() - where T : IAction => this.OfType().FirstOrDefault(); + /// + /// Gets the action of the specified type. + /// + /// The specified type to get + /// The action + public T? GetAction() + where T : IAction => this.OfType().FirstOrDefault(); - /// - /// Gets the actions by the specified letters - /// - public IEnumerable GetByLetters(IEnumerable letters) - { - IAction[] actions = this.ToArray(); // no worry: internally, it's already an array - return letters - .Where(x => x.Length == 1) - .Select(x => actions.FirstOrDefault(y => y.Letter == x[0])) - .WhereNotNull() - .ToList(); - } + /// + /// Gets the actions by the specified letters + /// + public IEnumerable GetByLetters(IEnumerable letters) + { + IAction[] actions = this.ToArray(); // no worry: internally, it's already an array + return letters + .Where(x => x.Length == 1) + .Select(x => actions.FirstOrDefault(y => y.Letter == x[0])) + .WhereNotNull() + .ToList(); + } - /// - /// Gets the actions from an EntityPermission - /// - public IReadOnlyList FromEntityPermission(EntityPermission entityPermission) - { - IAction[] actions = this.ToArray(); // no worry: internally, it's already an array - return entityPermission.AssignedPermissions - .Where(x => x.Length == 1) - .SelectMany(x => actions.Where(y => y.Letter == x[0])) - .WhereNotNull() - .ToList(); - } + /// + /// Gets the actions from an EntityPermission + /// + public IReadOnlyList FromEntityPermission(EntityPermission entityPermission) + { + IAction[] actions = this.ToArray(); // no worry: internally, it's already an array + return entityPermission.AssignedPermissions + .Where(x => x.Length == 1) + .SelectMany(x => actions.Where(y => y.Letter == x[0])) + .WhereNotNull() + .ToList(); } } diff --git a/src/Umbraco.Core/Actions/ActionCollectionBuilder.cs b/src/Umbraco.Core/Actions/ActionCollectionBuilder.cs index 58e70e4a2aa2..3c7cbd6a1e25 100644 --- a/src/Umbraco.Core/Actions/ActionCollectionBuilder.cs +++ b/src/Umbraco.Core/Actions/ActionCollectionBuilder.cs @@ -1,35 +1,32 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Actions +namespace Umbraco.Cms.Core.Actions; + +/// +/// The action collection builder +/// +public class ActionCollectionBuilder : LazyCollectionBuilderBase { - /// - /// The action collection builder - /// - public class ActionCollectionBuilder : LazyCollectionBuilderBase + /// + protected override ActionCollectionBuilder This => this; + + /// + protected override IEnumerable CreateItems(IServiceProvider factory) { - /// - protected override ActionCollectionBuilder This => this; + var items = base.CreateItems(factory).ToList(); - /// - protected override IEnumerable CreateItems(IServiceProvider factory) + // Validate the items, no actions should exist that do not either expose notifications or permissions + var invalidItems = items.Where(x => !x.CanBePermissionAssigned && !x.ShowInNotifier).ToList(); + if (invalidItems.Count == 0) { - var items = base.CreateItems(factory).ToList(); - - // Validate the items, no actions should exist that do not either expose notifications or permissions - var invalidItems = items.Where(x => !x.CanBePermissionAssigned && !x.ShowInNotifier).ToList(); - if (invalidItems.Count == 0) - { - return items; - } - - var invalidActions = string.Join(", ", invalidItems.Select(x => "'" + x.Alias + "'")); - throw new InvalidOperationException($"Invalid actions {invalidActions}'. All {typeof(IAction)} implementations must be true for either {nameof(IAction.CanBePermissionAssigned)} or {nameof(IAction.ShowInNotifier)}."); + return items; } + + var invalidActions = string.Join(", ", invalidItems.Select(x => "'" + x.Alias + "'")); + throw new InvalidOperationException( + $"Invalid actions {invalidActions}'. All {typeof(IAction)} implementations must be true for either {nameof(IAction.CanBePermissionAssigned)} or {nameof(IAction.ShowInNotifier)}."); } } diff --git a/src/Umbraco.Core/Actions/ActionCopy.cs b/src/Umbraco.Core/Actions/ActionCopy.cs index 83a855d1ff0a..880d4847c243 100644 --- a/src/Umbraco.Core/Actions/ActionCopy.cs +++ b/src/Umbraco.Core/Actions/ActionCopy.cs @@ -1,34 +1,33 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Actions +namespace Umbraco.Cms.Core.Actions; + +/// +/// This action is invoked when copying a document, media, member +/// +public class ActionCopy : IAction { /// - /// This action is invoked when copying a document, media, member + /// The unique action letter /// - public class ActionCopy : IAction - { - /// - /// The unique action letter - /// - public const char ActionLetter = 'O'; + public const char ActionLetter = 'O'; - /// - public char Letter => ActionLetter; + /// + public char Letter => ActionLetter; - /// - public string Alias => "copy"; + /// + public string Alias => "copy"; - /// - public string Category => Constants.Conventions.PermissionCategories.StructureCategory; + /// + public string Category => Constants.Conventions.PermissionCategories.StructureCategory; - /// - public string Icon => "documents"; + /// + public string Icon => "documents"; - /// - public bool ShowInNotifier => true; + /// + public bool ShowInNotifier => true; - /// - public bool CanBePermissionAssigned => true; - } + /// + public bool CanBePermissionAssigned => true; } diff --git a/src/Umbraco.Core/Actions/ActionCreateBlueprintFromContent.cs b/src/Umbraco.Core/Actions/ActionCreateBlueprintFromContent.cs index 806868af4023..99716ab61726 100644 --- a/src/Umbraco.Core/Actions/ActionCreateBlueprintFromContent.cs +++ b/src/Umbraco.Core/Actions/ActionCreateBlueprintFromContent.cs @@ -1,29 +1,28 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Actions +namespace Umbraco.Cms.Core.Actions; + +/// +/// This action is invoked when creating a blueprint from a content +/// +public class ActionCreateBlueprintFromContent : IAction { - /// - /// This action is invoked when creating a blueprint from a content - /// - public class ActionCreateBlueprintFromContent : IAction - { - /// - public char Letter => 'ï'; + /// + public char Letter => 'ï'; - /// - public bool ShowInNotifier => false; + /// + public bool ShowInNotifier => false; - /// - public bool CanBePermissionAssigned => true; + /// + public bool CanBePermissionAssigned => true; - /// - public string Icon => "blueprint"; + /// + public string Icon => "blueprint"; - /// - public string Alias => "createblueprint"; + /// + public string Alias => "createblueprint"; - /// - public string Category => Constants.Conventions.PermissionCategories.ContentCategory; - } + /// + public string Category => Constants.Conventions.PermissionCategories.ContentCategory; } diff --git a/src/Umbraco.Core/Actions/ActionDelete.cs b/src/Umbraco.Core/Actions/ActionDelete.cs index b31a8b9c4596..f64ecd32bfeb 100644 --- a/src/Umbraco.Core/Actions/ActionDelete.cs +++ b/src/Umbraco.Core/Actions/ActionDelete.cs @@ -1,39 +1,38 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Actions +namespace Umbraco.Cms.Core.Actions; + +/// +/// This action is invoked when a document, media, member is deleted +/// +public class ActionDelete : IAction { /// - /// This action is invoked when a document, media, member is deleted + /// The unique action alias /// - public class ActionDelete : IAction - { - /// - /// The unique action alias - /// - private const string ActionAlias = "delete"; + private const string ActionAlias = "delete"; - /// - /// The unique action letter - /// - public const char ActionLetter = 'D'; + /// + /// The unique action letter + /// + public const char ActionLetter = 'D'; - /// - public char Letter => ActionLetter; + /// + public char Letter => ActionLetter; - /// - public string Alias => ActionAlias; + /// + public string Alias => ActionAlias; - /// - public string Category => Constants.Conventions.PermissionCategories.ContentCategory; + /// + public string Category => Constants.Conventions.PermissionCategories.ContentCategory; - /// - public string Icon => "delete"; + /// + public string Icon => "delete"; - /// - public bool ShowInNotifier => true; + /// + public bool ShowInNotifier => true; - /// - public bool CanBePermissionAssigned => true; - } + /// + public bool CanBePermissionAssigned => true; } diff --git a/src/Umbraco.Core/Actions/ActionMove.cs b/src/Umbraco.Core/Actions/ActionMove.cs index 0f8b4b830550..76a99b479154 100644 --- a/src/Umbraco.Core/Actions/ActionMove.cs +++ b/src/Umbraco.Core/Actions/ActionMove.cs @@ -1,34 +1,33 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Actions +namespace Umbraco.Cms.Core.Actions; + +/// +/// This action is invoked upon creation of a document, media, member +/// +public class ActionMove : IAction { /// - /// This action is invoked upon creation of a document, media, member + /// The unique action letter /// - public class ActionMove : IAction - { - /// - /// The unique action letter - /// - public const char ActionLetter = 'M'; + public const char ActionLetter = 'M'; - /// - public char Letter => ActionLetter; + /// + public char Letter => ActionLetter; - /// - public string Alias => "move"; + /// + public string Alias => "move"; - /// - public string Category => Constants.Conventions.PermissionCategories.StructureCategory; + /// + public string Category => Constants.Conventions.PermissionCategories.StructureCategory; - /// - public string Icon => "enter"; + /// + public string Icon => "enter"; - /// - public bool ShowInNotifier => true; + /// + public bool ShowInNotifier => true; - /// - public bool CanBePermissionAssigned => true; - } + /// + public bool CanBePermissionAssigned => true; } diff --git a/src/Umbraco.Core/Actions/ActionNew.cs b/src/Umbraco.Core/Actions/ActionNew.cs index 25e85cd377c1..05f8e4c4c4f7 100644 --- a/src/Umbraco.Core/Actions/ActionNew.cs +++ b/src/Umbraco.Core/Actions/ActionNew.cs @@ -1,39 +1,38 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Actions +namespace Umbraco.Cms.Core.Actions; + +/// +/// This action is invoked upon creation of a document +/// +public class ActionNew : IAction { /// - /// This action is invoked upon creation of a document + /// The unique action alias /// - public class ActionNew : IAction - { - /// - /// The unique action alias - /// - public const string ActionAlias = "create"; + public const string ActionAlias = "create"; - /// - /// The unique action letter - /// - public const char ActionLetter = 'C'; + /// + /// The unique action letter + /// + public const char ActionLetter = 'C'; - /// - public char Letter => ActionLetter; + /// + public char Letter => ActionLetter; - /// - public string Alias => ActionAlias; + /// + public string Alias => ActionAlias; - /// - public string Icon => "add"; + /// + public string Icon => "add"; - /// - public bool ShowInNotifier => true; + /// + public bool ShowInNotifier => true; - /// - public bool CanBePermissionAssigned => true; + /// + public bool CanBePermissionAssigned => true; - /// - public string Category => Constants.Conventions.PermissionCategories.ContentCategory; - } + /// + public string Category => Constants.Conventions.PermissionCategories.ContentCategory; } diff --git a/src/Umbraco.Core/Actions/ActionNotify.cs b/src/Umbraco.Core/Actions/ActionNotify.cs index 3f1e855cff5f..2f13d9ce365e 100644 --- a/src/Umbraco.Core/Actions/ActionNotify.cs +++ b/src/Umbraco.Core/Actions/ActionNotify.cs @@ -1,29 +1,28 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Actions +namespace Umbraco.Cms.Core.Actions; + +/// +/// This action is invoked upon modifying the notification of a content +/// +public class ActionNotify : IAction { - /// - /// This action is invoked upon modifying the notification of a content - /// - public class ActionNotify : IAction - { - /// - public char Letter => 'N'; + /// + public char Letter => 'N'; - /// - public bool ShowInNotifier => false; + /// + public bool ShowInNotifier => false; - /// - public bool CanBePermissionAssigned => true; + /// + public bool CanBePermissionAssigned => true; - /// - public string Icon => "megaphone"; + /// + public string Icon => "megaphone"; - /// - public string Alias => "notify"; + /// + public string Alias => "notify"; - /// - public string Category => Constants.Conventions.PermissionCategories.ContentCategory; - } + /// + public string Category => Constants.Conventions.PermissionCategories.ContentCategory; } diff --git a/src/Umbraco.Core/Actions/ActionProtect.cs b/src/Umbraco.Core/Actions/ActionProtect.cs index 10684a69e2ed..c99fa7b5917c 100644 --- a/src/Umbraco.Core/Actions/ActionProtect.cs +++ b/src/Umbraco.Core/Actions/ActionProtect.cs @@ -1,34 +1,33 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Actions +namespace Umbraco.Cms.Core.Actions; + +/// +/// This action is invoked when a document is protected or unprotected +/// +public class ActionProtect : IAction { /// - /// This action is invoked when a document is protected or unprotected + /// The unique action letter /// - public class ActionProtect : IAction - { - /// - /// The unique action letter - /// - public const char ActionLetter = 'P'; + public const char ActionLetter = 'P'; - /// - public char Letter => ActionLetter; + /// + public char Letter => ActionLetter; - /// - public string Alias => "protect"; + /// + public string Alias => "protect"; - /// - public string Category => Constants.Conventions.PermissionCategories.AdministrationCategory; + /// + public string Category => Constants.Conventions.PermissionCategories.AdministrationCategory; - /// - public string Icon => "lock"; + /// + public string Icon => "lock"; - /// - public bool ShowInNotifier => true; + /// + public bool ShowInNotifier => true; - /// - public bool CanBePermissionAssigned => true; - } + /// + public bool CanBePermissionAssigned => true; } diff --git a/src/Umbraco.Core/Actions/ActionPublish.cs b/src/Umbraco.Core/Actions/ActionPublish.cs index 02f77d686235..39eff9f1716b 100644 --- a/src/Umbraco.Core/Actions/ActionPublish.cs +++ b/src/Umbraco.Core/Actions/ActionPublish.cs @@ -1,34 +1,33 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Actions +namespace Umbraco.Cms.Core.Actions; + +/// +/// This action is invoked when a document is being published +/// +public class ActionPublish : IAction { /// - /// This action is invoked when a document is being published + /// The unique action letter /// - public class ActionPublish : IAction - { - /// - /// The unique action letter - /// - public const char ActionLetter = 'U'; + public const char ActionLetter = 'U'; - /// - public char Letter => ActionLetter; + /// + public char Letter => ActionLetter; - /// - public string Alias => "publish"; + /// + public string Alias => "publish"; - /// - public string Category => Constants.Conventions.PermissionCategories.ContentCategory; + /// + public string Category => Constants.Conventions.PermissionCategories.ContentCategory; - /// - public string Icon => string.Empty; + /// + public string Icon => string.Empty; - /// - public bool ShowInNotifier => true; + /// + public bool ShowInNotifier => true; - /// - public bool CanBePermissionAssigned => true; - } + /// + public bool CanBePermissionAssigned => true; } diff --git a/src/Umbraco.Core/Actions/ActionRestore.cs b/src/Umbraco.Core/Actions/ActionRestore.cs index 164c93e2d5da..8c5aa8428ead 100644 --- a/src/Umbraco.Core/Actions/ActionRestore.cs +++ b/src/Umbraco.Core/Actions/ActionRestore.cs @@ -1,34 +1,33 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Actions +namespace Umbraco.Cms.Core.Actions; + +/// +/// This action is invoked when the content/media item is to be restored from the recycle bin +/// +public class ActionRestore : IAction { /// - /// This action is invoked when the content/media item is to be restored from the recycle bin + /// The unique action alias /// - public class ActionRestore : IAction - { - /// - /// The unique action alias - /// - public const string ActionAlias = "restore"; + public const string ActionAlias = "restore"; - /// - public char Letter => 'V'; + /// + public char Letter => 'V'; - /// - public string Alias => ActionAlias; + /// + public string Alias => ActionAlias; - /// - public string? Category => null; + /// + public string? Category => null; - /// - public string Icon => "undo"; + /// + public string Icon => "undo"; - /// - public bool ShowInNotifier => true; + /// + public bool ShowInNotifier => true; - /// - public bool CanBePermissionAssigned => false; - } + /// + public bool CanBePermissionAssigned => false; } diff --git a/src/Umbraco.Core/Actions/ActionRights.cs b/src/Umbraco.Core/Actions/ActionRights.cs index fff7cc86525d..bcf353b59603 100644 --- a/src/Umbraco.Core/Actions/ActionRights.cs +++ b/src/Umbraco.Core/Actions/ActionRights.cs @@ -1,34 +1,33 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Actions +namespace Umbraco.Cms.Core.Actions; + +/// +/// This action is invoked when rights are changed on a document +/// +public class ActionRights : IAction { /// - /// This action is invoked when rights are changed on a document + /// The unique action letter /// - public class ActionRights : IAction - { - /// - /// The unique action letter - /// - public const char ActionLetter = 'R'; + public const char ActionLetter = 'R'; - /// - public char Letter => ActionLetter; + /// + public char Letter => ActionLetter; - /// - public string Alias => "rights"; + /// + public string Alias => "rights"; - /// - public string Category => Constants.Conventions.PermissionCategories.ContentCategory; + /// + public string Category => Constants.Conventions.PermissionCategories.ContentCategory; - /// - public string Icon => "vcard"; + /// + public string Icon => "vcard"; - /// - public bool ShowInNotifier => true; + /// + public bool ShowInNotifier => true; - /// - public bool CanBePermissionAssigned => true; - } + /// + public bool CanBePermissionAssigned => true; } diff --git a/src/Umbraco.Core/Actions/ActionRollback.cs b/src/Umbraco.Core/Actions/ActionRollback.cs index 565a8469c52d..2d0dadf7a602 100644 --- a/src/Umbraco.Core/Actions/ActionRollback.cs +++ b/src/Umbraco.Core/Actions/ActionRollback.cs @@ -1,34 +1,33 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Actions +namespace Umbraco.Cms.Core.Actions; + +/// +/// This action is invoked when copying a document is being rolled back +/// +public class ActionRollback : IAction { /// - /// This action is invoked when copying a document is being rolled back + /// The unique action letter /// - public class ActionRollback : IAction - { - /// - /// The unique action letter - /// - public const char ActionLetter = 'K'; + public const char ActionLetter = 'K'; - /// - public char Letter => ActionLetter; + /// + public char Letter => ActionLetter; - /// - public string Alias => "rollback"; + /// + public string Alias => "rollback"; - /// - public string Category => Constants.Conventions.PermissionCategories.AdministrationCategory; + /// + public string Category => Constants.Conventions.PermissionCategories.AdministrationCategory; - /// - public string Icon => "undo"; + /// + public string Icon => "undo"; - /// - public bool ShowInNotifier => true; + /// + public bool ShowInNotifier => true; - /// - public bool CanBePermissionAssigned => true; - } + /// + public bool CanBePermissionAssigned => true; } diff --git a/src/Umbraco.Core/Actions/ActionSort.cs b/src/Umbraco.Core/Actions/ActionSort.cs index 1f87bfcc3c5e..35420501435d 100644 --- a/src/Umbraco.Core/Actions/ActionSort.cs +++ b/src/Umbraco.Core/Actions/ActionSort.cs @@ -1,34 +1,33 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Actions +namespace Umbraco.Cms.Core.Actions; + +/// +/// This action is invoked when children to a document, media, member is being sorted +/// +public class ActionSort : IAction { /// - /// This action is invoked when children to a document, media, member is being sorted + /// The unique action letter /// - public class ActionSort : IAction - { - /// - /// The unique action letter - /// - public const char ActionLetter = 'S'; + public const char ActionLetter = 'S'; - /// - public char Letter => ActionLetter; + /// + public char Letter => ActionLetter; - /// - public string Alias => "sort"; + /// + public string Alias => "sort"; - /// - public string Category => Constants.Conventions.PermissionCategories.StructureCategory; + /// + public string Category => Constants.Conventions.PermissionCategories.StructureCategory; - /// - public string Icon => "navigation-vertical"; + /// + public string Icon => "navigation-vertical"; - /// - public bool ShowInNotifier => true; + /// + public bool ShowInNotifier => true; - /// - public bool CanBePermissionAssigned => true; - } + /// + public bool CanBePermissionAssigned => true; } diff --git a/src/Umbraco.Core/Actions/ActionToPublish.cs b/src/Umbraco.Core/Actions/ActionToPublish.cs index 654b71661d6f..86ba32997a5f 100644 --- a/src/Umbraco.Core/Actions/ActionToPublish.cs +++ b/src/Umbraco.Core/Actions/ActionToPublish.cs @@ -1,34 +1,33 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Actions +namespace Umbraco.Cms.Core.Actions; + +/// +/// This action is invoked when children to a document is being sent to published (by an editor without publishrights) +/// +public class ActionToPublish : IAction { /// - /// This action is invoked when children to a document is being sent to published (by an editor without publishrights) + /// The unique action letter /// - public class ActionToPublish : IAction - { - /// - /// The unique action letter - /// - public const char ActionLetter = 'H'; + public const char ActionLetter = 'H'; - /// - public char Letter => ActionLetter; + /// + public char Letter => ActionLetter; - /// - public string Alias => "sendtopublish"; + /// + public string Alias => "sendtopublish"; - /// - public string Category => Constants.Conventions.PermissionCategories.ContentCategory; + /// + public string Category => Constants.Conventions.PermissionCategories.ContentCategory; - /// - public string Icon => "outbox"; + /// + public string Icon => "outbox"; - /// - public bool ShowInNotifier => true; + /// + public bool ShowInNotifier => true; - /// - public bool CanBePermissionAssigned => true; - } + /// + public bool CanBePermissionAssigned => true; } diff --git a/src/Umbraco.Core/Actions/ActionUnpublish.cs b/src/Umbraco.Core/Actions/ActionUnpublish.cs index 6e9ec8506b97..1388a98bffe6 100644 --- a/src/Umbraco.Core/Actions/ActionUnpublish.cs +++ b/src/Umbraco.Core/Actions/ActionUnpublish.cs @@ -1,35 +1,33 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Actions +namespace Umbraco.Cms.Core.Actions; + +/// +/// This action is invoked when a document is being unpublished +/// +public class ActionUnpublish : IAction { /// - /// This action is invoked when a document is being unpublished + /// The unique action letter /// - public class ActionUnpublish : IAction - { - /// - /// The unique action letter - /// - public const char ActionLetter = 'Z'; - - /// - public char Letter => ActionLetter; + public const char ActionLetter = 'Z'; - /// - public string Alias => "unpublish"; + /// + public char Letter => ActionLetter; - /// - public string Category => Constants.Conventions.PermissionCategories.ContentCategory; + /// + public string Alias => "unpublish"; - /// - public string Icon => "circle-dotted"; + /// + public string Category => Constants.Conventions.PermissionCategories.ContentCategory; - /// - public bool ShowInNotifier => false; + /// + public string Icon => "circle-dotted"; - /// - public bool CanBePermissionAssigned => true; - } + /// + public bool ShowInNotifier => false; + /// + public bool CanBePermissionAssigned => true; } diff --git a/src/Umbraco.Core/Actions/ActionUpdate.cs b/src/Umbraco.Core/Actions/ActionUpdate.cs index 3f8092c1fc04..b6684fb0d5f9 100644 --- a/src/Umbraco.Core/Actions/ActionUpdate.cs +++ b/src/Umbraco.Core/Actions/ActionUpdate.cs @@ -1,34 +1,33 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Actions +namespace Umbraco.Cms.Core.Actions; + +/// +/// This action is invoked when copying a document or media +/// +public class ActionUpdate : IAction { /// - /// This action is invoked when copying a document or media + /// The unique action letter /// - public class ActionUpdate : IAction - { - /// - /// The unique action letter - /// - public const char ActionLetter = 'A'; + public const char ActionLetter = 'A'; - /// - public char Letter => ActionLetter; + /// + public char Letter => ActionLetter; - /// - public string Alias => "update"; + /// + public string Alias => "update"; - /// - public string Category => Constants.Conventions.PermissionCategories.ContentCategory; + /// + public string Category => Constants.Conventions.PermissionCategories.ContentCategory; - /// - public string Icon => "save"; + /// + public string Icon => "save"; - /// - public bool ShowInNotifier => true; + /// + public bool ShowInNotifier => true; - /// - public bool CanBePermissionAssigned => true; - } + /// + public bool CanBePermissionAssigned => true; } diff --git a/src/Umbraco.Core/Actions/IAction.cs b/src/Umbraco.Core/Actions/IAction.cs index 2d9876afc647..995f68603b58 100644 --- a/src/Umbraco.Core/Actions/IAction.cs +++ b/src/Umbraco.Core/Actions/IAction.cs @@ -3,47 +3,46 @@ using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Actions +namespace Umbraco.Cms.Core.Actions; + +/// +/// Defines a back office action that can be permission assigned or subscribed to for notifications +/// +/// +/// If an IAction returns false for both ShowInNotifier and CanBePermissionAssigned then the IAction should not exist +/// +public interface IAction : IDiscoverable { /// - /// Defines a back office action that can be permission assigned or subscribed to for notifications + /// Gets the letter used to assign a permission (must be unique) + /// + char Letter { get; } + + /// + /// Gets a value indicating whether whether to allow subscribing to notifications for this action + /// + bool ShowInNotifier { get; } + + /// + /// Gets a value indicating whether whether to allow assigning permissions based on this action + /// + bool CanBePermissionAssigned { get; } + + /// + /// Gets the icon to display for this action + /// + string Icon { get; } + + /// + /// Gets the alias for this action (must be unique) + /// + string Alias { get; } + + /// + /// Gets the category used for this action /// /// - /// If an IAction returns false for both ShowInNotifier and CanBePermissionAssigned then the IAction should not exist + /// Used in the UI when assigning permissions /// - public interface IAction : IDiscoverable - { - /// - /// Gets the letter used to assign a permission (must be unique) - /// - char Letter { get; } - - /// - /// Gets a value indicating whether whether to allow subscribing to notifications for this action - /// - bool ShowInNotifier { get; } - - /// - /// Gets a value indicating whether whether to allow assigning permissions based on this action - /// - bool CanBePermissionAssigned { get; } - - /// - /// Gets the icon to display for this action - /// - string Icon { get; } - - /// - /// Gets the alias for this action (must be unique) - /// - string Alias { get; } - - /// - /// Gets the category used for this action - /// - /// - /// Used in the UI when assigning permissions - /// - string? Category { get; } - } + string? Category { get; } } diff --git a/src/Umbraco.Core/Attempt.cs b/src/Umbraco.Core/Attempt.cs index 71eabd2f0dc3..87231a27b870 100644 --- a/src/Umbraco.Core/Attempt.cs +++ b/src/Umbraco.Core/Attempt.cs @@ -1,126 +1,102 @@ -using System; +namespace Umbraco.Cms.Core; -namespace Umbraco.Cms.Core +/// +/// Provides ways to create attempts. +/// +public static class Attempt { + // note: + // cannot rely on overloads only to differentiate between with/without status + // in some cases it will always be ambiguous, so be explicit w/ 'WithStatus' methods + /// - /// Provides ways to create attempts. + /// Creates a successful attempt with a result. /// - public static class Attempt - { - // note: - // cannot rely on overloads only to differentiate between with/without status - // in some cases it will always be ambiguous, so be explicit w/ 'WithStatus' methods - - /// - /// Creates a successful attempt with a result. - /// - /// The type of the attempted operation result. - /// The result of the attempt. - /// The successful attempt. - public static Attempt Succeed(TResult? result) - { - return Attempt.Succeed(result); - } + /// The type of the attempted operation result. + /// The result of the attempt. + /// The successful attempt. + public static Attempt Succeed(TResult? result) => Attempt.Succeed(result); - /// - /// Creates a successful attempt with a result and a status. - /// - /// The type of the attempted operation result. - /// The type of the attempted operation status. - /// The status of the attempt. - /// The result of the attempt. - /// The successful attempt. - public static Attempt SucceedWithStatus(TStatus status, TResult result) - { - return Attempt.Succeed(status, result); - } + /// + /// Creates a successful attempt with a result and a status. + /// + /// The type of the attempted operation result. + /// The type of the attempted operation status. + /// The status of the attempt. + /// The result of the attempt. + /// The successful attempt. + public static Attempt SucceedWithStatus(TStatus status, TResult result) => + Attempt.Succeed(status, result); - /// - /// Creates a failed attempt. - /// - /// The type of the attempted operation result. - /// The failed attempt. - public static Attempt Fail() - { - return Attempt.Fail(); - } + /// + /// Creates a failed attempt. + /// + /// The type of the attempted operation result. + /// The failed attempt. + public static Attempt Fail() => Attempt.Fail(); - /// - /// Creates a failed attempt with a result. - /// - /// The type of the attempted operation result. - /// The result of the attempt. - /// The failed attempt. - public static Attempt Fail(TResult result) - { - return Attempt.Fail(result); - } + /// + /// Creates a failed attempt with a result. + /// + /// The type of the attempted operation result. + /// The result of the attempt. + /// The failed attempt. + public static Attempt Fail(TResult result) => Attempt.Fail(result); - /// - /// Creates a failed attempt with a result and a status. - /// - /// The type of the attempted operation result. - /// The type of the attempted operation status. - /// The status of the attempt. - /// The result of the attempt. - /// The failed attempt. - public static Attempt FailWithStatus(TStatus status, TResult result) - { - return Attempt.Fail(status, result); - } + /// + /// Creates a failed attempt with a result and a status. + /// + /// The type of the attempted operation result. + /// The type of the attempted operation status. + /// The status of the attempt. + /// The result of the attempt. + /// The failed attempt. + public static Attempt FailWithStatus(TStatus status, TResult result) => + Attempt.Fail(status, result); - /// - /// Creates a failed attempt with a result and an exception. - /// - /// The type of the attempted operation result. - /// The result of the attempt. - /// The exception causing the failure of the attempt. - /// The failed attempt. - public static Attempt Fail(TResult result, Exception exception) - { - return Attempt.Fail(result, exception); - } + /// + /// Creates a failed attempt with a result and an exception. + /// + /// The type of the attempted operation result. + /// The result of the attempt. + /// The exception causing the failure of the attempt. + /// The failed attempt. + public static Attempt Fail(TResult result, Exception exception) => + Attempt.Fail(result, exception); - /// - /// Creates a failed attempt with a result, an exception and a status. - /// - /// The type of the attempted operation result. - /// The type of the attempted operation status. - /// The status of the attempt. - /// The result of the attempt. - /// The exception causing the failure of the attempt. - /// The failed attempt. - public static Attempt FailWithStatus(TStatus status, TResult result, Exception exception) - { - return Attempt.Fail(status, result, exception); - } + /// + /// Creates a failed attempt with a result, an exception and a status. + /// + /// The type of the attempted operation result. + /// The type of the attempted operation status. + /// The status of the attempt. + /// The result of the attempt. + /// The exception causing the failure of the attempt. + /// The failed attempt. + public static Attempt FailWithStatus(TStatus status, TResult result, + Exception exception) => Attempt.Fail(status, result, exception); - /// - /// Creates a successful or a failed attempt, with a result. - /// - /// The type of the attempted operation result. - /// A value indicating whether the attempt is successful. - /// The result of the attempt. - /// The attempt. - public static Attempt If(bool condition, TResult result) - { - return Attempt.If(condition, result); - } + /// + /// Creates a successful or a failed attempt, with a result. + /// + /// The type of the attempted operation result. + /// A value indicating whether the attempt is successful. + /// The result of the attempt. + /// The attempt. + public static Attempt If(bool condition, TResult result) => + Attempt.If(condition, result); - /// - /// Creates a successful or a failed attempt, with a result. - /// - /// The type of the attempted operation result. - /// The type of the attempted operation status. - /// A value indicating whether the attempt is successful. - /// The status of the successful attempt. - /// The status of the failed attempt. - /// The result of the attempt. - /// The attempt. - public static Attempt IfWithStatus(bool condition, TStatus succStatus, TStatus failStatus, TResult result) - { - return Attempt.If(condition, succStatus, failStatus, result); - } - } + /// + /// Creates a successful or a failed attempt, with a result. + /// + /// The type of the attempted operation result. + /// The type of the attempted operation status. + /// A value indicating whether the attempt is successful. + /// The status of the successful attempt. + /// The status of the failed attempt. + /// The result of the attempt. + /// The attempt. + public static Attempt IfWithStatus(bool condition, TStatus succStatus, + TStatus failStatus, TResult result) => Attempt.If(condition, succStatus, failStatus, result); } diff --git a/src/Umbraco.Core/AttemptOfTResult.cs b/src/Umbraco.Core/AttemptOfTResult.cs index 5cf85964ccc1..de5ad7bb38e9 100644 --- a/src/Umbraco.Core/AttemptOfTResult.cs +++ b/src/Umbraco.Core/AttemptOfTResult.cs @@ -1,141 +1,111 @@ -using System; - -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Represents the result of an operation attempt. +/// +/// The type of the attempted operation result. +[Serializable] +public struct Attempt { + // private - use Succeed() or Fail() methods to create attempts + private Attempt(bool success, TResult? result, Exception? exception) + { + Success = success; + Result = result; + Exception = exception; + } + + /// + /// Gets a value indicating whether this was successful. + /// + public bool Success { get; } + /// - /// Represents the result of an operation attempt. + /// Gets the exception associated with an unsuccessful attempt. /// - /// The type of the attempted operation result. - [Serializable] - public struct Attempt + public Exception? Exception { get; } + + /// + /// Gets the attempt result. + /// + public TResult? Result { get; } + + /// + /// Gets the attempt result, if successful, else a default value. + /// + public TResult ResultOr(TResult value) { - // private - use Succeed() or Fail() methods to create attempts - private Attempt(bool success, TResult? result, Exception? exception) + if (Success && Result is not null) { - Success = success; - Result = result; - Exception = exception; + return Result; } - /// - /// Gets a value indicating whether this was successful. - /// - public bool Success { get; } - - /// - /// Gets the exception associated with an unsuccessful attempt. - /// - public Exception? Exception { get; } - - /// - /// Gets the attempt result. - /// - public TResult? Result { get; } - - /// - /// Gets the attempt result, if successful, else a default value. - /// - public TResult ResultOr(TResult value) - { - if (Success && Result is not null) - { - return Result; - } - - return value; - } + return value; + } - // optimize, use a singleton failed attempt - private static readonly Attempt Failed = new Attempt(false, default(TResult), null); + // optimize, use a singleton failed attempt + private static readonly Attempt Failed = new(false, default, null); - /// - /// Creates a successful attempt. - /// - /// The successful attempt. - public static Attempt Succeed() - { - return new Attempt(true, default(TResult), null); - } + /// + /// Creates a successful attempt. + /// + /// The successful attempt. + public static Attempt Succeed() => new(true, default, null); - /// - /// Creates a successful attempt with a result. - /// - /// The result of the attempt. - /// The successful attempt. - public static Attempt Succeed(TResult? result) - { - return new Attempt(true, result, null); - } + /// + /// Creates a successful attempt with a result. + /// + /// The result of the attempt. + /// The successful attempt. + public static Attempt Succeed(TResult? result) => new(true, result, null); - /// - /// Creates a failed attempt. - /// - /// The failed attempt. - public static Attempt Fail() - { - return Failed; - } + /// + /// Creates a failed attempt. + /// + /// The failed attempt. + public static Attempt Fail() => Failed; - /// - /// Creates a failed attempt with an exception. - /// - /// The exception causing the failure of the attempt. - /// The failed attempt. - public static Attempt Fail(Exception? exception) - { - return new Attempt(false, default(TResult), exception); - } + /// + /// Creates a failed attempt with an exception. + /// + /// The exception causing the failure of the attempt. + /// The failed attempt. + public static Attempt Fail(Exception? exception) => new(false, default, exception); - /// - /// Creates a failed attempt with a result. - /// - /// The result of the attempt. - /// The failed attempt. - public static Attempt Fail(TResult result) - { - return new Attempt(false, result, null); - } + /// + /// Creates a failed attempt with a result. + /// + /// The result of the attempt. + /// The failed attempt. + public static Attempt Fail(TResult result) => new(false, result, null); - /// - /// Creates a failed attempt with a result and an exception. - /// - /// The result of the attempt. - /// The exception causing the failure of the attempt. - /// The failed attempt. - public static Attempt Fail(TResult result, Exception exception) - { - return new Attempt(false, result, exception); - } + /// + /// Creates a failed attempt with a result and an exception. + /// + /// The result of the attempt. + /// The exception causing the failure of the attempt. + /// The failed attempt. + public static Attempt Fail(TResult result, Exception exception) => new(false, result, exception); - /// - /// Creates a successful or a failed attempt. - /// - /// A value indicating whether the attempt is successful. - /// The attempt. - public static Attempt If(bool condition) - { - return condition ? new Attempt(true, default(TResult), null) : Failed; - } + /// + /// Creates a successful or a failed attempt. + /// + /// A value indicating whether the attempt is successful. + /// The attempt. + public static Attempt If(bool condition) => condition ? new Attempt(true, default, null) : Failed; - /// - /// Creates a successful or a failed attempt, with a result. - /// - /// A value indicating whether the attempt is successful. - /// The result of the attempt. - /// The attempt. - public static Attempt If(bool condition, TResult? result) - { - return new Attempt(condition, result, null); - } + /// + /// Creates a successful or a failed attempt, with a result. + /// + /// A value indicating whether the attempt is successful. + /// The result of the attempt. + /// The attempt. + public static Attempt If(bool condition, TResult? result) => new(condition, result, null); - /// - /// Implicitly operator to check if the attempt was successful without having to access the 'success' property - /// - /// - /// - public static implicit operator bool(Attempt a) - { - return a.Success; - } - } + /// + /// Implicitly operator to check if the attempt was successful without having to access the 'success' property + /// + /// + /// + public static implicit operator bool(Attempt a) => a.Success; } diff --git a/src/Umbraco.Core/AttemptOfTResultTStatus.cs b/src/Umbraco.Core/AttemptOfTResultTStatus.cs index 65a3e483344b..7a12c9d9958a 100644 --- a/src/Umbraco.Core/AttemptOfTResultTStatus.cs +++ b/src/Umbraco.Core/AttemptOfTResultTStatus.cs @@ -1,142 +1,121 @@ -using System; +namespace Umbraco.Cms.Core; -namespace Umbraco.Cms.Core +/// +/// Represents the result of an operation attempt. +/// +/// The type of the attempted operation result. +/// The type of the attempted operation status. +[Serializable] +public struct Attempt { /// - /// Represents the result of an operation attempt. + /// Gets a value indicating whether this was successful. /// - /// The type of the attempted operation result. - /// The type of the attempted operation status. - [Serializable] - public struct Attempt - { - /// - /// Gets a value indicating whether this was successful. - /// - public bool Success { get; } + public bool Success { get; } - /// - /// Gets the exception associated with an unsuccessful attempt. - /// - public Exception? Exception { get; } + /// + /// Gets the exception associated with an unsuccessful attempt. + /// + public Exception? Exception { get; } - /// - /// Gets the attempt result. - /// - public TResult Result { get; } + /// + /// Gets the attempt result. + /// + public TResult Result { get; } - /// - /// Gets the attempt status. - /// - public TStatus Status { get; } + /// + /// Gets the attempt status. + /// + public TStatus Status { get; } - // private - use Succeed() or Fail() methods to create attempts - private Attempt(bool success, TResult result, TStatus status, Exception? exception) - { - Success = success; - Result = result; - Status = status; - Exception = exception; - } + // private - use Succeed() or Fail() methods to create attempts + private Attempt(bool success, TResult result, TStatus status, Exception? exception) + { + Success = success; + Result = result; + Status = status; + Exception = exception; + } - /// - /// Creates a successful attempt. - /// - /// The status of the attempt. - /// The successful attempt. - public static Attempt Succeed(TStatus status) - { - return new Attempt(true, default(TResult), status, null); - } + /// + /// Creates a successful attempt. + /// + /// The status of the attempt. + /// The successful attempt. + public static Attempt Succeed(TStatus status) => + new Attempt(true, default, status, null); - /// - /// Creates a successful attempt with a result. - /// - /// The status of the attempt. - /// The result of the attempt. - /// The successful attempt. - public static Attempt Succeed(TStatus status, TResult result) - { - return new Attempt(true, result, status, null); - } + /// + /// Creates a successful attempt with a result. + /// + /// The status of the attempt. + /// The result of the attempt. + /// The successful attempt. + public static Attempt Succeed(TStatus status, TResult result) => + new Attempt(true, result, status, null); - /// - /// Creates a failed attempt. - /// - /// The status of the attempt. - /// The failed attempt. - public static Attempt Fail(TStatus status) - { - return new Attempt(false, default(TResult), status, null); - } + /// + /// Creates a failed attempt. + /// + /// The status of the attempt. + /// The failed attempt. + public static Attempt Fail(TStatus status) => + new Attempt(false, default, status, null); - /// - /// Creates a failed attempt with an exception. - /// - /// The status of the attempt. - /// The exception causing the failure of the attempt. - /// The failed attempt. - public static Attempt Fail(TStatus status, Exception exception) - { - return new Attempt(false, default(TResult), status, exception); - } + /// + /// Creates a failed attempt with an exception. + /// + /// The status of the attempt. + /// The exception causing the failure of the attempt. + /// The failed attempt. + public static Attempt Fail(TStatus status, Exception exception) => + new Attempt(false, default, status, exception); - /// - /// Creates a failed attempt with a result. - /// - /// The status of the attempt. - /// The result of the attempt. - /// The failed attempt. - public static Attempt Fail(TStatus status, TResult result) - { - return new Attempt(false, result, status, null); - } + /// + /// Creates a failed attempt with a result. + /// + /// The status of the attempt. + /// The result of the attempt. + /// The failed attempt. + public static Attempt Fail(TStatus status, TResult result) => + new Attempt(false, result, status, null); - /// - /// Creates a failed attempt with a result and an exception. - /// - /// The status of the attempt. - /// The result of the attempt. - /// The exception causing the failure of the attempt. - /// The failed attempt. - public static Attempt Fail(TStatus status, TResult result, Exception exception) - { - return new Attempt(false, result, status, exception); - } + /// + /// Creates a failed attempt with a result and an exception. + /// + /// The status of the attempt. + /// The result of the attempt. + /// The exception causing the failure of the attempt. + /// The failed attempt. + public static Attempt Fail(TStatus status, TResult result, Exception exception) => + new Attempt(false, result, status, exception); - /// - /// Creates a successful or a failed attempt. - /// - /// A value indicating whether the attempt is successful. - /// The status of the successful attempt. - /// The status of the failed attempt. - /// The attempt. - public static Attempt If(bool condition, TStatus succStatus, TStatus failStatus) - { - return new Attempt(condition, default(TResult), condition ? succStatus : failStatus, null); - } + /// + /// Creates a successful or a failed attempt. + /// + /// A value indicating whether the attempt is successful. + /// The status of the successful attempt. + /// The status of the failed attempt. + /// The attempt. + public static Attempt If(bool condition, TStatus succStatus, TStatus failStatus) => + new Attempt(condition, default, condition ? succStatus : failStatus, null); - /// - /// Creates a successful or a failed attempt, with a result. - /// - /// A value indicating whether the attempt is successful. - /// The status of the successful attempt. - /// The status of the failed attempt. - /// The result of the attempt. - /// The attempt. - public static Attempt If(bool condition, TStatus succStatus, TStatus failStatus, TResult result) - { - return new Attempt(condition, result, condition ? succStatus : failStatus, null); - } + /// + /// Creates a successful or a failed attempt, with a result. + /// + /// A value indicating whether the attempt is successful. + /// The status of the successful attempt. + /// The status of the failed attempt. + /// The result of the attempt. + /// The attempt. + public static Attempt + If(bool condition, TStatus succStatus, TStatus failStatus, TResult result) => + new Attempt(condition, result, condition ? succStatus : failStatus, null); - /// - /// Implicitly operator to check if the attempt was successful without having to access the 'success' property - /// - /// - /// - public static implicit operator bool(Attempt a) - { - return a.Success; - } - } + /// + /// Implicitly operator to check if the attempt was successful without having to access the 'success' property + /// + /// + /// + public static implicit operator bool(Attempt a) => a.Success; } diff --git a/src/Umbraco.Core/Cache/AppCacheExtensions.cs b/src/Umbraco.Core/Cache/AppCacheExtensions.cs index f5e92cc1166e..52441a635baa 100644 --- a/src/Umbraco.Core/Cache/AppCacheExtensions.cs +++ b/src/Umbraco.Core/Cache/AppCacheExtensions.cs @@ -1,66 +1,62 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Cache; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Extensions for strongly typed access +/// +public static class AppCacheExtensions { - /// - /// Extensions for strongly typed access - /// - public static class AppCacheExtensions + public static T? GetCacheItem(this IAppPolicyCache provider, + string cacheKey, + Func getCacheItem, + TimeSpan? timeout, + bool isSliding = false, + string[]? dependentFiles = null) { - public static T? GetCacheItem(this IAppPolicyCache provider, - string cacheKey, - Func getCacheItem, - TimeSpan? timeout, - bool isSliding = false, - string[]? dependentFiles = null) - { - var result = provider.Get(cacheKey, () => getCacheItem(), timeout, isSliding, dependentFiles); - return result == null ? default(T) : result.TryConvertTo().Result; - } + var result = provider.Get(cacheKey, () => getCacheItem(), timeout, isSliding, dependentFiles); + return result == null ? default : result.TryConvertTo().Result; + } - public static void InsertCacheItem(this IAppPolicyCache provider, - string cacheKey, - Func getCacheItem, - TimeSpan? timeout = null, - bool isSliding = false, - string[]? dependentFiles = null) - { - provider.Insert(cacheKey, () => getCacheItem(), timeout, isSliding, dependentFiles); - } + public static void InsertCacheItem(this IAppPolicyCache provider, + string cacheKey, + Func getCacheItem, + TimeSpan? timeout = null, + bool isSliding = false, + string[]? dependentFiles = null) => + provider.Insert(cacheKey, () => getCacheItem(), timeout, isSliding, dependentFiles); - public static IEnumerable GetCacheItemsByKeySearch(this IAppCache provider, string keyStartsWith) - { - var result = provider.SearchByKey(keyStartsWith); - return result.Select(x => x.TryConvertTo().Result); - } + public static IEnumerable GetCacheItemsByKeySearch(this IAppCache provider, string keyStartsWith) + { + IEnumerable result = provider.SearchByKey(keyStartsWith); + return result.Select(x => x.TryConvertTo().Result); + } - public static IEnumerable GetCacheItemsByKeyExpression(this IAppCache provider, string regexString) - { - var result = provider.SearchByRegex(regexString); - return result.Select(x => x.TryConvertTo().Result); - } + public static IEnumerable GetCacheItemsByKeyExpression(this IAppCache provider, string regexString) + { + IEnumerable result = provider.SearchByRegex(regexString); + return result.Select(x => x.TryConvertTo().Result); + } - public static T? GetCacheItem(this IAppCache provider, string cacheKey) + public static T? GetCacheItem(this IAppCache provider, string cacheKey) + { + var result = provider.Get(cacheKey); + if (result == null) { - var result = provider.Get(cacheKey); - if (result == null) - { - return default(T); - } - return result.TryConvertTo().Result; + return default; } - public static T? GetCacheItem(this IAppCache provider, string cacheKey, Func getCacheItem) + return result.TryConvertTo().Result; + } + + public static T? GetCacheItem(this IAppCache provider, string cacheKey, Func getCacheItem) + { + var result = provider.Get(cacheKey, () => getCacheItem()); + if (result == null) { - var result = provider.Get(cacheKey, () => getCacheItem()); - if (result == null) - { - return default(T); - } - return result.TryConvertTo().Result; + return default; } + + return result.TryConvertTo().Result; } } diff --git a/src/Umbraco.Core/Cache/AppCaches.cs b/src/Umbraco.Core/Cache/AppCaches.cs index a04ece0d04af..d53f92650a3e 100644 --- a/src/Umbraco.Core/Cache/AppCaches.cs +++ b/src/Umbraco.Core/Cache/AppCaches.cs @@ -1,100 +1,98 @@ -using System; -using Umbraco.Extensions; +using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +/// +/// Represents the application caches. +/// +public class AppCaches : IDisposable { + private bool _disposedValue; + /// - /// Represents the application caches. + /// Initializes a new instance of the with cache providers. /// - public class AppCaches : IDisposable + public AppCaches( + IAppPolicyCache runtimeCache, + IRequestCache requestCache, + IsolatedCaches isolatedCaches) { - private bool _disposedValue; + RuntimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache)); + RequestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache)); + IsolatedCaches = isolatedCaches ?? throw new ArgumentNullException(nameof(isolatedCaches)); + } - /// - /// Initializes a new instance of the with cache providers. - /// - public AppCaches( - IAppPolicyCache runtimeCache, - IRequestCache requestCache, - IsolatedCaches isolatedCaches) - { - RuntimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache)); - RequestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache)); - IsolatedCaches = isolatedCaches ?? throw new ArgumentNullException(nameof(isolatedCaches)); - } + /// + /// Gets the special disabled instance. + /// + /// + /// When used by repositories, all cache policies apply, but the underlying caches do not cache anything. + /// Used by tests. + /// + public static AppCaches Disabled { get; } = new(NoAppCache.Instance, NoAppCache.Instance, + new IsolatedCaches(_ => NoAppCache.Instance)); - /// - /// Gets the special disabled instance. - /// - /// - /// When used by repositories, all cache policies apply, but the underlying caches do not cache anything. - /// Used by tests. - /// - public static AppCaches Disabled { get; } = new AppCaches(NoAppCache.Instance, NoAppCache.Instance, new IsolatedCaches(_ => NoAppCache.Instance)); + /// + /// Gets the special no-cache instance. + /// + /// + /// When used by repositories, all cache policies are bypassed. + /// Used by repositories that do no cache. + /// + public static AppCaches NoCache { get; } = new(NoAppCache.Instance, NoAppCache.Instance, + new IsolatedCaches(_ => NoAppCache.Instance)); - /// - /// Gets the special no-cache instance. - /// - /// - /// When used by repositories, all cache policies are bypassed. - /// Used by repositories that do no cache. - /// - public static AppCaches NoCache { get; } = new AppCaches(NoAppCache.Instance, NoAppCache.Instance, new IsolatedCaches(_ => NoAppCache.Instance)); + /// + /// Gets the per-request cache. + /// + /// + /// The per-request caches works on top of the current HttpContext items. + /// Outside a web environment, the behavior of that cache is unspecified. + /// + public IRequestCache RequestCache { get; } - /// - /// Gets the per-request cache. - /// - /// - /// The per-request caches works on top of the current HttpContext items. - /// Outside a web environment, the behavior of that cache is unspecified. - /// - public IRequestCache RequestCache { get; } + /// + /// Gets the runtime cache. + /// + /// + /// The runtime cache is the main application cache. + /// + public IAppPolicyCache RuntimeCache { get; } - /// - /// Gets the runtime cache. - /// - /// - /// The runtime cache is the main application cache. - /// - public IAppPolicyCache RuntimeCache { get; } + /// + /// Gets the isolated caches. + /// + /// + /// + /// Isolated caches are used by e.g. repositories, to ensure that each cached entity + /// type has its own cache, so that lookups are fast and the repository does not need to + /// search through all keys on a global scale. + /// + /// + public IsolatedCaches IsolatedCaches { get; } - /// - /// Gets the isolated caches. - /// - /// - /// Isolated caches are used by e.g. repositories, to ensure that each cached entity - /// type has its own cache, so that lookups are fast and the repository does not need to - /// search through all keys on a global scale. - /// - public IsolatedCaches IsolatedCaches { get; } + public void Dispose() => + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(true); - public static AppCaches Create(IRequestCache requestCache) - { - return new AppCaches( - new DeepCloneAppCache(new ObjectCacheAppCache()), - requestCache, - new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); - } + public static AppCaches Create(IRequestCache requestCache) => + new( + new DeepCloneAppCache(new ObjectCacheAppCache()), + requestCache, + new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); - protected virtual void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) { - if (!_disposedValue) + if (disposing) { - if (disposing) - { - RuntimeCache.DisposeIfDisposable(); - RequestCache.DisposeIfDisposable(); - IsolatedCaches.Dispose(); - } - - _disposedValue = true; + RuntimeCache.DisposeIfDisposable(); + RequestCache.DisposeIfDisposable(); + IsolatedCaches.Dispose(); } - } - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); + _disposedValue = true; } } } diff --git a/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs b/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs index 53e45bbb2e0d..edfc8e7b28b2 100644 --- a/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs +++ b/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs @@ -1,99 +1,92 @@ -using System; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +/// +/// Provides a base class for implementing a dictionary of . +/// +/// The type of the dictionary key. +public abstract class AppPolicedCacheDictionary : IDisposable + where TKey : notnull { /// - /// Provides a base class for implementing a dictionary of . + /// Gets the internal cache factory, for tests only! /// - /// The type of the dictionary key. - public abstract class AppPolicedCacheDictionary : IDisposable - where TKey : notnull - { - private readonly ConcurrentDictionary _caches = new ConcurrentDictionary(); + private readonly Func _cacheFactory; - /// - /// Initializes a new instance of the class. - /// - /// - protected AppPolicedCacheDictionary(Func cacheFactory) - { - _cacheFactory = cacheFactory; - } + private readonly ConcurrentDictionary _caches = new(); + private bool _disposedValue; - /// - /// Gets the internal cache factory, for tests only! - /// - private readonly Func _cacheFactory; - private bool _disposedValue; + /// + /// Initializes a new instance of the class. + /// + /// + protected AppPolicedCacheDictionary(Func cacheFactory) => _cacheFactory = cacheFactory; - /// - /// Gets or creates a cache. - /// - public IAppPolicyCache GetOrCreate(TKey key) - => _caches.GetOrAdd(key, k => _cacheFactory(k)); + public void Dispose() => + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(true); - /// - /// Tries to get a cache. - /// - protected Attempt Get(TKey key) - => _caches.TryGetValue(key, out var cache) ? Attempt.Succeed(cache) : Attempt.Fail(); + /// + /// Gets or creates a cache. + /// + public IAppPolicyCache GetOrCreate(TKey key) + => _caches.GetOrAdd(key, k => _cacheFactory(k)); - /// - /// Removes a cache. - /// - public void Remove(TKey key) - { - _caches.TryRemove(key, out _); - } + /// + /// Tries to get a cache. + /// + protected Attempt Get(TKey key) + => _caches.TryGetValue(key, out IAppPolicyCache cache) + ? Attempt.Succeed(cache) + : Attempt.Fail(); - /// - /// Removes all caches. - /// - public void RemoveAll() - { - _caches.Clear(); - } + /// + /// Removes a cache. + /// + public void Remove(TKey key) => _caches.TryRemove(key, out _); - /// - /// Clears a cache. - /// - protected void ClearCache(TKey key) + /// + /// Removes all caches. + /// + public void RemoveAll() => _caches.Clear(); + + /// + /// Clears a cache. + /// + protected void ClearCache(TKey key) + { + if (_caches.TryGetValue(key, out IAppPolicyCache cache)) { - if (_caches.TryGetValue(key, out var cache)) - cache.Clear(); + cache.Clear(); } + } - /// - /// Clears all caches. - /// - public void ClearAllCaches() + /// + /// Clears all caches. + /// + public void ClearAllCaches() + { + foreach (IAppPolicyCache cache in _caches.Values) { - foreach (var cache in _caches.Values) - cache.Clear(); + cache.Clear(); } + } - protected virtual void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) { - if (!_disposedValue) + if (disposing) { - if (disposing) + foreach (IAppPolicyCache value in _caches.Values) { - foreach(var value in _caches.Values) - { - value.DisposeIfDisposable(); - } + value.DisposeIfDisposable(); } - - _disposedValue = true; } - } - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); + _disposedValue = true; } } } diff --git a/src/Umbraco.Core/Cache/ApplicationCacheRefresher.cs b/src/Umbraco.Core/Cache/ApplicationCacheRefresher.cs index 582915fb2e21..230e254d80b9 100644 --- a/src/Umbraco.Core/Cache/ApplicationCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/ApplicationCacheRefresher.cs @@ -1,46 +1,45 @@ -using System; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +public sealed class ApplicationCacheRefresher : CacheRefresherBase { - public sealed class ApplicationCacheRefresher : CacheRefresherBase + public ApplicationCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, eventAggregator, factory) { - public ApplicationCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) - : base(appCaches, eventAggregator, factory) - { - } - - #region Define + } - public static readonly Guid UniqueId = Guid.Parse("B15F34A1-BC1D-4F8B-8369-3222728AB4C8"); + #region Define - public override Guid RefresherUniqueId => UniqueId; + public static readonly Guid UniqueId = Guid.Parse("B15F34A1-BC1D-4F8B-8369-3222728AB4C8"); - public override string Name => "Application Cache Refresher"; + public override Guid RefresherUniqueId => UniqueId; - #endregion + public override string Name => "Application Cache Refresher"; - #region Refresher + #endregion - public override void RefreshAll() - { - AppCaches.RuntimeCache.Clear(CacheKeys.ApplicationsCacheKey); - base.RefreshAll(); - } + #region Refresher - public override void Refresh(int id) - { - Remove(id); - base.Refresh(id); - } + public override void RefreshAll() + { + AppCaches.RuntimeCache.Clear(CacheKeys.ApplicationsCacheKey); + base.RefreshAll(); + } - public override void Remove(int id) - { - AppCaches.RuntimeCache.Clear(CacheKeys.ApplicationsCacheKey); - base.Remove(id); - } + public override void Refresh(int id) + { + Remove(id); + base.Refresh(id); + } - #endregion + public override void Remove(int id) + { + AppCaches.RuntimeCache.Clear(CacheKeys.ApplicationsCacheKey); + base.Remove(id); } + + #endregion } diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs index acabe0fcc4d5..a73c81eaa863 100644 --- a/src/Umbraco.Core/Cache/CacheKeys.cs +++ b/src/Umbraco.Core/Cache/CacheKeys.cs @@ -1,26 +1,25 @@ -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +/// +/// Constants storing cache keys used in caching +/// +public static class CacheKeys { - /// - /// Constants storing cache keys used in caching - /// - public static class CacheKeys - { - public const string ApplicationsCacheKey = "ApplicationCache"; // used by SectionService + public const string ApplicationsCacheKey = "ApplicationCache"; // used by SectionService + + // TODO: this one can probably be removed + public const string TemplateFrontEndCacheKey = "template"; - // TODO: this one can probably be removed - public const string TemplateFrontEndCacheKey = "template"; + public const string MacroContentCacheKey = "macroContent_"; // used in MacroRenderers + public const string MacroFromAliasCacheKey = "macroFromAlias_"; - public const string MacroContentCacheKey = "macroContent_"; // used in MacroRenderers - public const string MacroFromAliasCacheKey = "macroFromAlias_"; + public const string UserGroupGetByAliasCacheKeyPrefix = "UserGroupRepository_GetByAlias_"; - public const string UserGroupGetByAliasCacheKeyPrefix = "UserGroupRepository_GetByAlias_"; + public const string UserAllContentStartNodesPrefix = "AllContentStartNodes"; + public const string UserAllMediaStartNodesPrefix = "AllMediaStartNodes"; + public const string UserMediaStartNodePathsPrefix = "MediaStartNodePaths"; + public const string UserContentStartNodePathsPrefix = "ContentStartNodePaths"; - public const string UserAllContentStartNodesPrefix = "AllContentStartNodes"; - public const string UserAllMediaStartNodesPrefix = "AllMediaStartNodes"; - public const string UserMediaStartNodePathsPrefix = "MediaStartNodePaths"; - public const string UserContentStartNodePathsPrefix = "ContentStartNodePaths"; - - public const string ContentRecycleBinCacheKey = "recycleBin_content"; - public const string MediaRecycleBinCacheKey = "recycleBin_media"; - } + public const string ContentRecycleBinCacheKey = "recycleBin_content"; + public const string MediaRecycleBinCacheKey = "recycleBin_media"; } diff --git a/src/Umbraco.Core/Cache/CacheRefresherBase.cs b/src/Umbraco.Core/Cache/CacheRefresherBase.cs index 7b962065c561..e93f7cba6cd8 100644 --- a/src/Umbraco.Core/Cache/CacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/CacheRefresherBase.cs @@ -1,122 +1,108 @@ -using System; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +/// +/// A base class for cache refreshers that handles events. +/// +/// The actual cache refresher type. +/// The actual cache refresher type is used for strongly typed events. +public abstract class CacheRefresherBase : ICacheRefresher + where TNotification : CacheRefresherNotification { /// - /// A base class for cache refreshers that handles events. + /// Initializes a new instance of the . /// - /// The actual cache refresher type. - /// The actual cache refresher type is used for strongly typed events. - public abstract class CacheRefresherBase : ICacheRefresher - where TNotification : CacheRefresherNotification + /// A cache helper. + protected CacheRefresherBase(AppCaches appCaches, IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) { - /// - /// Initializes a new instance of the . - /// - /// A cache helper. - protected CacheRefresherBase(AppCaches appCaches, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) - { - AppCaches = appCaches; - EventAggregator = eventAggregator; - NotificationFactory = factory; - } - - #region Define - - /// - /// Gets the unique identifier of the refresher. - /// - public abstract Guid RefresherUniqueId { get; } - - /// - /// Gets the name of the refresher. - /// - public abstract string Name { get; } - - /// - /// Gets the for - /// - protected ICacheRefresherNotificationFactory NotificationFactory { get; } - - #endregion - - #region Refresher - - /// - /// Refreshes all entities. - /// - public virtual void RefreshAll() - { - // NOTE: We pass in string.Empty here because if we pass in NULL this causes problems with - // the underlying ActivatorUtilities.CreateInstance which doesn't seem to support passing in - // null to an 'object' parameter and we end up with "A suitable constructor for type 'ZYZ' could not be located." - // In this case, all cache refreshers should be checking for the type first before checking for a msg value - // so this shouldn't cause any issues. - OnCacheUpdated(NotificationFactory.Create(string.Empty, MessageType.RefreshAll)); - } - - /// - /// Refreshes an entity. - /// - /// The entity's identifier. - public virtual void Refresh(int id) - { - OnCacheUpdated(NotificationFactory.Create(id, MessageType.RefreshById)); - } - - /// - /// Refreshes an entity. - /// - /// The entity's identifier. - public virtual void Refresh(Guid id) - { - OnCacheUpdated(NotificationFactory.Create(id, MessageType.RefreshById)); - } - - /// - /// Removes an entity. - /// - /// The entity's identifier. - public virtual void Remove(int id) - { - OnCacheUpdated(NotificationFactory.Create(id, MessageType.RemoveById)); - } - - #endregion - - #region Protected - - /// - /// Gets the cache helper. - /// - protected AppCaches AppCaches { get; } - - protected IEventAggregator EventAggregator { get; } - - /// - /// Clears the cache for all repository entities of a specified type. - /// - /// The type of the entities. - protected void ClearAllIsolatedCacheByEntityType() - where TEntity : class, IEntity - { - AppCaches.IsolatedCaches.ClearCache(); - } - - /// - /// Raises the CacheUpdated event. - /// - /// The event sender. - /// The event arguments. - protected void OnCacheUpdated(CacheRefresherNotification notification) - { - EventAggregator.Publish(notification); - } - - #endregion + AppCaches = appCaches; + EventAggregator = eventAggregator; + NotificationFactory = factory; } + + #region Define + + /// + /// Gets the unique identifier of the refresher. + /// + public abstract Guid RefresherUniqueId { get; } + + /// + /// Gets the name of the refresher. + /// + public abstract string Name { get; } + + /// + /// Gets the for + /// + protected ICacheRefresherNotificationFactory NotificationFactory { get; } + + #endregion + + #region Refresher + + /// + /// Refreshes all entities. + /// + public virtual void RefreshAll() => + // NOTE: We pass in string.Empty here because if we pass in NULL this causes problems with + // the underlying ActivatorUtilities.CreateInstance which doesn't seem to support passing in + // null to an 'object' parameter and we end up with "A suitable constructor for type 'ZYZ' could not be located." + // In this case, all cache refreshers should be checking for the type first before checking for a msg value + // so this shouldn't cause any issues. + OnCacheUpdated(NotificationFactory.Create(string.Empty, MessageType.RefreshAll)); + + /// + /// Refreshes an entity. + /// + /// The entity's identifier. + public virtual void Refresh(int id) => + OnCacheUpdated(NotificationFactory.Create(id, MessageType.RefreshById)); + + /// + /// Refreshes an entity. + /// + /// The entity's identifier. + public virtual void Refresh(Guid id) => + OnCacheUpdated(NotificationFactory.Create(id, MessageType.RefreshById)); + + /// + /// Removes an entity. + /// + /// The entity's identifier. + public virtual void Remove(int id) => + OnCacheUpdated(NotificationFactory.Create(id, MessageType.RemoveById)); + + #endregion + + #region Protected + + /// + /// Gets the cache helper. + /// + protected AppCaches AppCaches { get; } + + protected IEventAggregator EventAggregator { get; } + + /// + /// Clears the cache for all repository entities of a specified type. + /// + /// The type of the entities. + protected void ClearAllIsolatedCacheByEntityType() + where TEntity : class, IEntity => + AppCaches.IsolatedCaches.ClearCache(); + + /// + /// Raises the CacheUpdated event. + /// + /// The event sender. + /// The event arguments. + protected void OnCacheUpdated(CacheRefresherNotification notification) => EventAggregator.Publish(notification); + + #endregion } diff --git a/src/Umbraco.Core/Cache/CacheRefresherCollection.cs b/src/Umbraco.Core/Cache/CacheRefresherCollection.cs index b9dc7f598444..2c22339f18ca 100644 --- a/src/Umbraco.Core/Cache/CacheRefresherCollection.cs +++ b/src/Umbraco.Core/Cache/CacheRefresherCollection.cs @@ -1,17 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +public class CacheRefresherCollection : BuilderCollectionBase { - public class CacheRefresherCollection : BuilderCollectionBase + public CacheRefresherCollection(Func> items) : base(items) { - public CacheRefresherCollection(Func> items) : base(items) - { - } - - public ICacheRefresher? this[Guid id] - => this.FirstOrDefault(x => x.RefresherUniqueId == id); } + + public ICacheRefresher? this[Guid id] + => this.FirstOrDefault(x => x.RefresherUniqueId == id); } diff --git a/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs b/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs index 34a274a17799..0ab9542ea833 100644 --- a/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs +++ b/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs @@ -1,9 +1,9 @@ using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +public class CacheRefresherCollectionBuilder : LazyCollectionBuilderBase { - public class CacheRefresherCollectionBuilder : LazyCollectionBuilderBase - { - protected override CacheRefresherCollectionBuilder This => this; - } + protected override CacheRefresherCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/Cache/CacheRefresherNotificationFactory.cs b/src/Umbraco.Core/Cache/CacheRefresherNotificationFactory.cs index bd41ee9d9b7b..40bab16b12dd 100644 --- a/src/Umbraco.Core/Cache/CacheRefresherNotificationFactory.cs +++ b/src/Umbraco.Core/Cache/CacheRefresherNotificationFactory.cs @@ -1,24 +1,24 @@ -using System; using Umbraco.Cms.Core.Notifications; -using Umbraco.Extensions; using Umbraco.Cms.Core.Sync; +using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +/// +/// A that uses ActivatorUtilities to create the +/// instances +/// +public sealed class CacheRefresherNotificationFactory : ICacheRefresherNotificationFactory { - /// - /// A that uses ActivatorUtilities to create the instances - /// - public sealed class CacheRefresherNotificationFactory : ICacheRefresherNotificationFactory - { - private readonly IServiceProvider _serviceProvider; + private readonly IServiceProvider _serviceProvider; - public CacheRefresherNotificationFactory(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; + public CacheRefresherNotificationFactory(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; - /// - /// Create a using ActivatorUtilities - /// - /// The to create - public TNotification Create(object msgObject, MessageType type) where TNotification : CacheRefresherNotification - => _serviceProvider.CreateInstance(new object[] { msgObject, type }); - } + /// + /// Create a using ActivatorUtilities + /// + /// The to create + public TNotification Create(object msgObject, MessageType type) + where TNotification : CacheRefresherNotification + => _serviceProvider.CreateInstance(msgObject, type); } diff --git a/src/Umbraco.Core/Cache/ContentCacheRefresher.cs b/src/Umbraco.Core/Cache/ContentCacheRefresher.cs index ff55a201f588..5cef496ca9b9 100644 --- a/src/Umbraco.Core/Cache/ContentCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/ContentCacheRefresher.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; @@ -11,168 +8,171 @@ using Umbraco.Cms.Core.Services.Changes; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +public sealed class ContentCacheRefresher : PayloadCacheRefresherBase { - public sealed class ContentCacheRefresher : PayloadCacheRefresherBase + private readonly IDomainService _domainService; + private readonly IIdKeyMap _idKeyMap; + private readonly IPublishedSnapshotService _publishedSnapshotService; + + public ContentCacheRefresher( + AppCaches appCaches, + IJsonSerializer serializer, + IPublishedSnapshotService publishedSnapshotService, + IIdKeyMap idKeyMap, + IDomainService domainService, + IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, serializer, eventAggregator, factory) { - private readonly IPublishedSnapshotService _publishedSnapshotService; - private readonly IIdKeyMap _idKeyMap; - private readonly IDomainService _domainService; - - public ContentCacheRefresher( - AppCaches appCaches, - IJsonSerializer serializer, - IPublishedSnapshotService publishedSnapshotService, - IIdKeyMap idKeyMap, - IDomainService domainService, - IEventAggregator eventAggregator, - ICacheRefresherNotificationFactory factory) - : base(appCaches, serializer, eventAggregator, factory) - { - _publishedSnapshotService = publishedSnapshotService; - _idKeyMap = idKeyMap; - _domainService = domainService; - } + _publishedSnapshotService = publishedSnapshotService; + _idKeyMap = idKeyMap; + _domainService = domainService; + } + + #region Indirect + + public static void RefreshContentTypes(AppCaches appCaches) + { + // we could try to have a mechanism to notify the PublishedCachesService + // and figure out whether published items were modified or not... keep it + // simple for now, just clear the whole thing + + appCaches.ClearPartialViewCache(); + + appCaches.IsolatedCaches.ClearCache(); + appCaches.IsolatedCaches.ClearCache(); + } - #region Define + #endregion - public static readonly Guid UniqueId = Guid.Parse("900A4FBE-DF3C-41E6-BB77-BE896CD158EA"); + #region Define - public override Guid RefresherUniqueId => UniqueId; + public static readonly Guid UniqueId = Guid.Parse("900A4FBE-DF3C-41E6-BB77-BE896CD158EA"); - public override string Name => "ContentCacheRefresher"; + public override Guid RefresherUniqueId => UniqueId; - #endregion + public override string Name => "ContentCacheRefresher"; - #region Refresher + #endregion - public override void Refresh(JsonPayload[] payloads) + #region Refresher + + public override void Refresh(JsonPayload[] payloads) + { + AppCaches.RuntimeCache.ClearOfType(); + AppCaches.RuntimeCache.ClearByKey(CacheKeys.ContentRecycleBinCacheKey); + + var idsRemoved = new HashSet(); + IAppPolicyCache isolatedCache = AppCaches.IsolatedCaches.GetOrCreate(); + + foreach (JsonPayload payload in payloads.Where(x => x.Id != default)) { - AppCaches.RuntimeCache.ClearOfType(); - AppCaches.RuntimeCache.ClearByKey(CacheKeys.ContentRecycleBinCacheKey); + //By INT Id + isolatedCache.Clear(RepositoryCacheKeys.GetKey(payload.Id)); + //By GUID Key + isolatedCache.Clear(RepositoryCacheKeys.GetKey(payload.Key)); - var idsRemoved = new HashSet(); - var isolatedCache = AppCaches.IsolatedCaches.GetOrCreate(); + _idKeyMap.ClearCache(payload.Id); - foreach (var payload in payloads.Where(x => x.Id != default)) + // remove those that are in the branch + if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.RefreshBranch | TreeChangeTypes.Remove)) { - //By INT Id - isolatedCache.Clear(RepositoryCacheKeys.GetKey(payload.Id)); - //By GUID Key - isolatedCache.Clear(RepositoryCacheKeys.GetKey(payload.Key)); - - _idKeyMap.ClearCache(payload.Id); - - // remove those that are in the branch - if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.RefreshBranch | TreeChangeTypes.Remove)) - { - var pathid = "," + payload.Id + ","; - isolatedCache.ClearOfType((k, v) => v.Path?.Contains(pathid) ?? false); - } - - //if the item is being completely removed, we need to refresh the domains cache if any domain was assigned to the content - if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.Remove)) - { - idsRemoved.Add(payload.Id); - } + var pathid = "," + payload.Id + ","; + isolatedCache.ClearOfType((k, v) => v.Path?.Contains(pathid) ?? false); } - if (idsRemoved.Count > 0) + //if the item is being completely removed, we need to refresh the domains cache if any domain was assigned to the content + if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.Remove)) { - var assignedDomains = _domainService.GetAll(true)?.Where(x => x.RootContentId.HasValue && idsRemoved.Contains(x.RootContentId.Value)).ToList(); - - if (assignedDomains?.Count > 0) - { - // TODO: this is duplicating the logic in DomainCacheRefresher BUT we cannot inject that into this because it it not registered explicitly in the container, - // and we cannot inject the CacheRefresherCollection since that would be a circular reference, so what is the best way to call directly in to the - // DomainCacheRefresher? - - ClearAllIsolatedCacheByEntityType(); - // note: must do what's above FIRST else the repositories still have the old cached - // content and when the PublishedCachesService is notified of changes it does not see - // the new content... - // notify - _publishedSnapshotService.Notify(assignedDomains.Select(x => new DomainCacheRefresher.JsonPayload(x.Id, DomainChangeTypes.Remove)).ToArray()); - } + idsRemoved.Add(payload.Id); } + } - // note: must do what's above FIRST else the repositories still have the old cached - // content and when the PublishedCachesService is notified of changes it does not see - // the new content... - - // TODO: what about this? - // should rename it, and then, this is only for Deploy, and then, ??? - //if (Suspendable.PageCacheRefresher.CanUpdateDocumentCache) - // ... - - NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, payloads); + if (idsRemoved.Count > 0) + { + var assignedDomains = _domainService.GetAll(true) + ?.Where(x => x.RootContentId.HasValue && idsRemoved.Contains(x.RootContentId.Value)).ToList(); - base.Refresh(payloads); + if (assignedDomains?.Count > 0) + { + // TODO: this is duplicating the logic in DomainCacheRefresher BUT we cannot inject that into this because it it not registered explicitly in the container, + // and we cannot inject the CacheRefresherCollection since that would be a circular reference, so what is the best way to call directly in to the + // DomainCacheRefresher? + + ClearAllIsolatedCacheByEntityType(); + // note: must do what's above FIRST else the repositories still have the old cached + // content and when the PublishedCachesService is notified of changes it does not see + // the new content... + // notify + _publishedSnapshotService.Notify(assignedDomains + .Select(x => new DomainCacheRefresher.JsonPayload(x.Id, DomainChangeTypes.Remove)).ToArray()); + } } - // these events should never trigger - // everything should be PAYLOAD/JSON + // note: must do what's above FIRST else the repositories still have the old cached + // content and when the PublishedCachesService is notified of changes it does not see + // the new content... - public override void RefreshAll() => throw new NotSupportedException(); + // TODO: what about this? + // should rename it, and then, this is only for Deploy, and then, ??? + //if (Suspendable.PageCacheRefresher.CanUpdateDocumentCache) + // ... - public override void Refresh(int id) => throw new NotSupportedException(); + NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, payloads); - public override void Refresh(Guid id) => throw new NotSupportedException(); + base.Refresh(payloads); + } - public override void Remove(int id) => throw new NotSupportedException(); + // these events should never trigger + // everything should be PAYLOAD/JSON - #endregion + public override void RefreshAll() => throw new NotSupportedException(); - #region Json + public override void Refresh(int id) => throw new NotSupportedException(); - /// - /// Refreshes the publish snapshot service and if there are published changes ensures that partial view caches are refreshed too - /// - /// - /// - /// - internal static void NotifyPublishedSnapshotService(IPublishedSnapshotService service, AppCaches appCaches, JsonPayload[] payloads) - { - service.Notify(payloads, out _, out var publishedChanged); + public override void Refresh(Guid id) => throw new NotSupportedException(); - if (payloads.Any(x => x.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) || publishedChanged) - { - // when a public version changes - appCaches.ClearPartialViewCache(); - } - } + public override void Remove(int id) => throw new NotSupportedException(); - public class JsonPayload - { - public JsonPayload(int id, Guid? key, TreeChangeTypes changeTypes) - { - Id = id; - Key = key; - ChangeTypes = changeTypes; - } + #endregion - public int Id { get; } - public Guid? Key { get; } - public TreeChangeTypes ChangeTypes { get; } - } + #region Json - #endregion - - #region Indirect + /// + /// Refreshes the publish snapshot service and if there are published changes ensures that partial view caches are + /// refreshed too + /// + /// + /// + /// + internal static void NotifyPublishedSnapshotService(IPublishedSnapshotService service, AppCaches appCaches, + JsonPayload[] payloads) + { + service.Notify(payloads, out _, out var publishedChanged); - public static void RefreshContentTypes(AppCaches appCaches) + if (payloads.Any(x => x.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) || publishedChanged) { - // we could try to have a mechanism to notify the PublishedCachesService - // and figure out whether published items were modified or not... keep it - // simple for now, just clear the whole thing - + // when a public version changes appCaches.ClearPartialViewCache(); - - appCaches.IsolatedCaches.ClearCache(); - appCaches.IsolatedCaches.ClearCache(); } + } - #endregion + public class JsonPayload + { + public JsonPayload(int id, Guid? key, TreeChangeTypes changeTypes) + { + Id = id; + Key = key; + ChangeTypes = changeTypes; + } + public int Id { get; } + public Guid? Key { get; } + public TreeChangeTypes ChangeTypes { get; } } + + #endregion } diff --git a/src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs index 9a709e9a9f6e..c957f4878072 100644 --- a/src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; @@ -11,136 +9,130 @@ using Umbraco.Cms.Core.Services.Changes; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +public sealed class ContentTypeCacheRefresher : PayloadCacheRefresherBase { - public sealed class ContentTypeCacheRefresher : PayloadCacheRefresherBase + private readonly IContentTypeCommonRepository _contentTypeCommonRepository; + private readonly IIdKeyMap _idKeyMap; + private readonly IPublishedModelFactory _publishedModelFactory; + private readonly IPublishedSnapshotService _publishedSnapshotService; + + public ContentTypeCacheRefresher( + AppCaches appCaches, + IJsonSerializer serializer, + IPublishedSnapshotService publishedSnapshotService, + IPublishedModelFactory publishedModelFactory, + IIdKeyMap idKeyMap, + IContentTypeCommonRepository contentTypeCommonRepository, + IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, serializer, eventAggregator, factory) + { + _publishedSnapshotService = publishedSnapshotService; + _publishedModelFactory = publishedModelFactory; + _idKeyMap = idKeyMap; + _contentTypeCommonRepository = contentTypeCommonRepository; + } + + #region Json + + public class JsonPayload { - private readonly IPublishedSnapshotService _publishedSnapshotService; - private readonly IPublishedModelFactory _publishedModelFactory; - private readonly IContentTypeCommonRepository _contentTypeCommonRepository; - private readonly IIdKeyMap _idKeyMap; - - public ContentTypeCacheRefresher( - AppCaches appCaches, - IJsonSerializer serializer, - IPublishedSnapshotService publishedSnapshotService, - IPublishedModelFactory publishedModelFactory, - IIdKeyMap idKeyMap, - IContentTypeCommonRepository contentTypeCommonRepository, - IEventAggregator eventAggregator, - ICacheRefresherNotificationFactory factory) - : base(appCaches, serializer, eventAggregator, factory) + public JsonPayload(string itemType, int id, ContentTypeChangeTypes changeTypes) { - _publishedSnapshotService = publishedSnapshotService; - _publishedModelFactory = publishedModelFactory; - _idKeyMap = idKeyMap; - _contentTypeCommonRepository = contentTypeCommonRepository; + ItemType = itemType; + Id = id; + ChangeTypes = changeTypes; } - #region Define + public string ItemType { get; } + + public int Id { get; } - public static readonly Guid UniqueId = Guid.Parse("6902E22C-9C10-483C-91F3-66B7CAE9E2F5"); + public ContentTypeChangeTypes ChangeTypes { get; } + } + + #endregion + + #region Define - public override Guid RefresherUniqueId => UniqueId; + public static readonly Guid UniqueId = Guid.Parse("6902E22C-9C10-483C-91F3-66B7CAE9E2F5"); - public override string Name => "Content Type Cache Refresher"; + public override Guid RefresherUniqueId => UniqueId; - #endregion + public override string Name => "Content Type Cache Refresher"; - #region Refresher + #endregion + + #region Refresher + + public override void Refresh(JsonPayload[] payloads) + { + // TODO: refactor + // we should NOT directly clear caches here, but instead ask whatever class + // is managing the cache to please clear that cache properly - public override void Refresh(JsonPayload[] payloads) + _contentTypeCommonRepository.ClearCache(); // always + + if (payloads.Any(x => x.ItemType == typeof(IContentType).Name)) { - // TODO: refactor - // we should NOT directly clear caches here, but instead ask whatever class - // is managing the cache to please clear that cache properly - - _contentTypeCommonRepository.ClearCache(); // always - - if (payloads.Any(x => x.ItemType == typeof(IContentType).Name)) - { - ClearAllIsolatedCacheByEntityType(); - ClearAllIsolatedCacheByEntityType(); - } - - if (payloads.Any(x => x.ItemType == typeof(IMediaType).Name)) - { - ClearAllIsolatedCacheByEntityType(); - ClearAllIsolatedCacheByEntityType(); - } - - if (payloads.Any(x => x.ItemType == typeof(IMemberType).Name)) - { - ClearAllIsolatedCacheByEntityType(); - ClearAllIsolatedCacheByEntityType(); - } - - foreach (var id in payloads.Select(x => x.Id)) - { - _idKeyMap.ClearCache(id); - } - - if (payloads.Any(x => x.ItemType == typeof(IContentType).Name)) - // don't try to be clever - refresh all - ContentCacheRefresher.RefreshContentTypes(AppCaches); - - if (payloads.Any(x => x.ItemType == typeof(IMediaType).Name)) - // don't try to be clever - refresh all - MediaCacheRefresher.RefreshMediaTypes(AppCaches); - - if (payloads.Any(x => x.ItemType == typeof(IMemberType).Name)) - // don't try to be clever - refresh all - MemberCacheRefresher.RefreshMemberTypes(AppCaches); - - // refresh the models and cache - _publishedModelFactory.WithSafeLiveFactoryReset(() => - _publishedSnapshotService.Notify(payloads)); - - // now we can trigger the event - base.Refresh(payloads); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); } + if (payloads.Any(x => x.ItemType == typeof(IMediaType).Name)) + { + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + } - public override void RefreshAll() + if (payloads.Any(x => x.ItemType == typeof(IMemberType).Name)) { - throw new NotSupportedException(); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); } - public override void Refresh(int id) + foreach (var id in payloads.Select(x => x.Id)) { - throw new NotSupportedException(); + _idKeyMap.ClearCache(id); } - public override void Refresh(Guid id) + if (payloads.Any(x => x.ItemType == typeof(IContentType).Name)) + // don't try to be clever - refresh all { - throw new NotSupportedException(); + ContentCacheRefresher.RefreshContentTypes(AppCaches); } - public override void Remove(int id) + if (payloads.Any(x => x.ItemType == typeof(IMediaType).Name)) + // don't try to be clever - refresh all { - throw new NotSupportedException(); + MediaCacheRefresher.RefreshMediaTypes(AppCaches); } - #endregion + if (payloads.Any(x => x.ItemType == typeof(IMemberType).Name)) + // don't try to be clever - refresh all + { + MemberCacheRefresher.RefreshMemberTypes(AppCaches); + } - #region Json + // refresh the models and cache + _publishedModelFactory.WithSafeLiveFactoryReset(() => + _publishedSnapshotService.Notify(payloads)); - public class JsonPayload - { - public JsonPayload(string itemType, int id, ContentTypeChangeTypes changeTypes) - { - ItemType = itemType; - Id = id; - ChangeTypes = changeTypes; - } + // now we can trigger the event + base.Refresh(payloads); + } - public string ItemType { get; } - public int Id { get; } + public override void RefreshAll() => throw new NotSupportedException(); - public ContentTypeChangeTypes ChangeTypes { get; } - } + public override void Refresh(int id) => throw new NotSupportedException(); - #endregion - } + public override void Refresh(Guid id) => throw new NotSupportedException(); + + public override void Remove(int id) => throw new NotSupportedException(); + + #endregion } diff --git a/src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs b/src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs index 44d730be8389..0770e3ac4e1e 100644 --- a/src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs @@ -1,4 +1,3 @@ -using System; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; @@ -10,121 +9,109 @@ using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +public sealed class DataTypeCacheRefresher : PayloadCacheRefresherBase { - public sealed class DataTypeCacheRefresher : PayloadCacheRefresherBase + private readonly IIdKeyMap _idKeyMap; + private readonly IPublishedModelFactory _publishedModelFactory; + private readonly IPublishedSnapshotService _publishedSnapshotService; + + public DataTypeCacheRefresher( + AppCaches appCaches, + IJsonSerializer serializer, + IPublishedSnapshotService publishedSnapshotService, + IPublishedModelFactory publishedModelFactory, + IIdKeyMap idKeyMap, + IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, serializer, eventAggregator, factory) { - private readonly IPublishedSnapshotService _publishedSnapshotService; - private readonly IPublishedModelFactory _publishedModelFactory; - private readonly IIdKeyMap _idKeyMap; - - public DataTypeCacheRefresher( - AppCaches appCaches, - IJsonSerializer serializer, - IPublishedSnapshotService publishedSnapshotService, - IPublishedModelFactory publishedModelFactory, - IIdKeyMap idKeyMap, - IEventAggregator eventAggregator, - ICacheRefresherNotificationFactory factory) - : base(appCaches, serializer, eventAggregator, factory) - { - _publishedSnapshotService = publishedSnapshotService; - _publishedModelFactory = publishedModelFactory; - _idKeyMap = idKeyMap; - } - - #region Define + _publishedSnapshotService = publishedSnapshotService; + _publishedModelFactory = publishedModelFactory; + _idKeyMap = idKeyMap; + } - public static readonly Guid UniqueId = Guid.Parse("35B16C25-A17E-45D7-BC8F-EDAB1DCC28D2"); + #region Json - public override Guid RefresherUniqueId => UniqueId; + public class JsonPayload + { + public JsonPayload(int id, Guid key, bool removed) + { + Id = id; + Key = key; + Removed = removed; + } - public override string Name => "Data Type Cache Refresher"; + public int Id { get; } - #endregion + public Guid Key { get; } - #region Refresher + public bool Removed { get; } + } - public override void Refresh(JsonPayload[] payloads) - { - //we need to clear the ContentType runtime cache since that is what caches the - // db data type to store the value against and anytime a datatype changes, this also might change - // we basically need to clear all sorts of runtime caches here because so many things depend upon a data type + #endregion - ClearAllIsolatedCacheByEntityType(); - ClearAllIsolatedCacheByEntityType(); - ClearAllIsolatedCacheByEntityType(); - ClearAllIsolatedCacheByEntityType(); - ClearAllIsolatedCacheByEntityType(); - ClearAllIsolatedCacheByEntityType(); + #region Define - var dataTypeCache = AppCaches.IsolatedCaches.Get(); + public static readonly Guid UniqueId = Guid.Parse("35B16C25-A17E-45D7-BC8F-EDAB1DCC28D2"); - foreach (var payload in payloads) - { - _idKeyMap.ClearCache(payload.Id); + public override Guid RefresherUniqueId => UniqueId; - if (dataTypeCache.Success) - { - dataTypeCache.Result?.Clear(RepositoryCacheKeys.GetKey(payload.Id)); - } - } + public override string Name => "Data Type Cache Refresher"; - // TODO: not sure I like these? - TagsValueConverter.ClearCaches(); - SliderValueConverter.ClearCaches(); + #endregion - // refresh the models and cache + #region Refresher - _publishedModelFactory.WithSafeLiveFactoryReset(() => - _publishedSnapshotService.Notify(payloads)); + public override void Refresh(JsonPayload[] payloads) + { + //we need to clear the ContentType runtime cache since that is what caches the + // db data type to store the value against and anytime a datatype changes, this also might change + // we basically need to clear all sorts of runtime caches here because so many things depend upon a data type - base.Refresh(payloads); - } + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); - // these events should never trigger - // everything should be PAYLOAD/JSON + Attempt dataTypeCache = AppCaches.IsolatedCaches.Get(); - public override void RefreshAll() + foreach (JsonPayload payload in payloads) { - throw new NotSupportedException(); - } + _idKeyMap.ClearCache(payload.Id); - public override void Refresh(int id) - { - throw new NotSupportedException(); + if (dataTypeCache.Success) + { + dataTypeCache.Result?.Clear(RepositoryCacheKeys.GetKey(payload.Id)); + } } - public override void Refresh(Guid id) - { - throw new NotSupportedException(); - } + // TODO: not sure I like these? + TagsValueConverter.ClearCaches(); + SliderValueConverter.ClearCaches(); - public override void Remove(int id) - { - throw new NotSupportedException(); - } + // refresh the models and cache - #endregion + _publishedModelFactory.WithSafeLiveFactoryReset(() => + _publishedSnapshotService.Notify(payloads)); - #region Json + base.Refresh(payloads); + } - public class JsonPayload - { - public JsonPayload(int id, Guid key, bool removed) - { - Id = id; - Key = key; - Removed = removed; - } + // these events should never trigger + // everything should be PAYLOAD/JSON - public int Id { get; } + public override void RefreshAll() => throw new NotSupportedException(); - public Guid Key { get; } + public override void Refresh(int id) => throw new NotSupportedException(); - public bool Removed { get; } - } + public override void Refresh(Guid id) => throw new NotSupportedException(); - #endregion - } + public override void Remove(int id) => throw new NotSupportedException(); + + #endregion } diff --git a/src/Umbraco.Core/Cache/DeepCloneAppCache.cs b/src/Umbraco.Core/Cache/DeepCloneAppCache.cs index 60a0d8d7b36d..701b3ef29ce7 100644 --- a/src/Umbraco.Core/Cache/DeepCloneAppCache.cs +++ b/src/Umbraco.Core/Cache/DeepCloneAppCache.cs @@ -1,178 +1,152 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +/// +/// Implements by wrapping an inner other +/// instance, and ensuring that all inserts and returns are deep cloned copies of the cache item, +/// when the item is deep-cloneable. +/// +public class DeepCloneAppCache : IAppPolicyCache, IDisposable { + private bool _disposedValue; + /// - /// Implements by wrapping an inner other - /// instance, and ensuring that all inserts and returns are deep cloned copies of the cache item, - /// when the item is deep-cloneable. + /// Initializes a new instance of the class. /// - public class DeepCloneAppCache : IAppPolicyCache, IDisposable + public DeepCloneAppCache(IAppPolicyCache innerCache) { - private bool _disposedValue; + Type type = typeof(DeepCloneAppCache); - /// - /// Initializes a new instance of the class. - /// - public DeepCloneAppCache(IAppPolicyCache innerCache) + if (innerCache.GetType() == type) { - var type = typeof (DeepCloneAppCache); + throw new InvalidOperationException($"A {type} cannot wrap another instance of itself."); + } - if (innerCache.GetType() == type) - throw new InvalidOperationException($"A {type} cannot wrap another instance of itself."); + InnerCache = innerCache; + } - InnerCache = innerCache; - } + /// + /// Gets the inner cache. + /// + private IAppPolicyCache InnerCache { get; } - /// - /// Gets the inner cache. - /// - private IAppPolicyCache InnerCache { get; } + /// + public object? Get(string key) + { + var item = InnerCache.Get(key); + return CheckCloneableAndTracksChanges(item); + } - /// - public object? Get(string key) - { - var item = InnerCache.Get(key); - return CheckCloneableAndTracksChanges(item); - } + /// + public object? Get(string key, Func factory) + { + var cached = InnerCache.Get(key, () => + { + Lazy result = SafeLazy.GetSafeLazy(factory); + var value = result + .Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache + // do not store null values (backward compat), clone / reset to go into the cache + return value == null ? null : CheckCloneableAndTracksChanges(value); + }); + return CheckCloneableAndTracksChanges(cached); + } - /// - public object? Get(string key, Func factory) - { - var cached = InnerCache.Get(key, () => - { - var result = SafeLazy.GetSafeLazy(factory); - var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache - // do not store null values (backward compat), clone / reset to go into the cache - return value == null ? null : CheckCloneableAndTracksChanges(value); - }); - return CheckCloneableAndTracksChanges(cached); - } + /// + public IEnumerable SearchByKey(string keyStartsWith) => + InnerCache.SearchByKey(keyStartsWith) + .Select(CheckCloneableAndTracksChanges); - /// - public IEnumerable SearchByKey(string keyStartsWith) - { - return InnerCache.SearchByKey(keyStartsWith) - .Select(CheckCloneableAndTracksChanges); - } + /// + public IEnumerable SearchByRegex(string regex) => + InnerCache.SearchByRegex(regex) + .Select(CheckCloneableAndTracksChanges); - /// - public IEnumerable SearchByRegex(string regex) + /// + public object? Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, + string[]? dependentFiles = null) + { + var cached = InnerCache.Get(key, () => { - return InnerCache.SearchByRegex(regex) - .Select(CheckCloneableAndTracksChanges); - } + Lazy result = SafeLazy.GetSafeLazy(factory); + var value = result + .Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache + // do not store null values (backward compat), clone / reset to go into the cache + return value == null ? null : CheckCloneableAndTracksChanges(value); - /// - public object? Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, string[]? dependentFiles = null) + // clone / reset to go into the cache + }, timeout, isSliding, dependentFiles); + + // clone / reset to go into the cache + return CheckCloneableAndTracksChanges(cached); + } + + /// + public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, + string[]? dependentFiles = null) => + InnerCache.Insert(key, () => { - var cached = InnerCache.Get(key, () => - { - var result = SafeLazy.GetSafeLazy(factory); - var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache - // do not store null values (backward compat), clone / reset to go into the cache - return value == null ? null : CheckCloneableAndTracksChanges(value); + Lazy result = SafeLazy.GetSafeLazy(factory); + var value = result + .Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache + // do not store null values (backward compat), clone / reset to go into the cache + return value == null ? null : CheckCloneableAndTracksChanges(value); + }, timeout, isSliding, dependentFiles); - // clone / reset to go into the cache - }, timeout, isSliding, dependentFiles); + /// + public void Clear() => InnerCache.Clear(); - // clone / reset to go into the cache - return CheckCloneableAndTracksChanges(cached); - } + /// + public void Clear(string key) => InnerCache.Clear(key); - /// - public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, string[]? dependentFiles = null) - { - InnerCache.Insert(key, () => - { - var result = SafeLazy.GetSafeLazy(factory); - var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache - // do not store null values (backward compat), clone / reset to go into the cache - return value == null ? null : CheckCloneableAndTracksChanges(value); - }, timeout, isSliding, dependentFiles); - } + /// + public void ClearOfType(Type type) => InnerCache.ClearOfType(type); - /// - public void Clear() - { - InnerCache.Clear(); - } + /// + public void ClearOfType() => InnerCache.ClearOfType(); - /// - public void Clear(string key) - { - InnerCache.Clear(key); - } + /// + public void ClearOfType(Func predicate) => InnerCache.ClearOfType(predicate); - /// - public void ClearOfType(Type type) - { - InnerCache.ClearOfType(type); - } + /// + public void ClearByKey(string keyStartsWith) => InnerCache.ClearByKey(keyStartsWith); - /// - public void ClearOfType() - { - InnerCache.ClearOfType(); - } + /// + public void ClearByRegex(string regex) => InnerCache.ClearByRegex(regex); - /// - public void ClearOfType(Func predicate) - { - InnerCache.ClearOfType(predicate); - } + public void Dispose() => + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(true); - /// - public void ClearByKey(string keyStartsWith) + private static object? CheckCloneableAndTracksChanges(object? input) + { + if (input is IDeepCloneable cloneable) { - InnerCache.ClearByKey(keyStartsWith); + input = cloneable.DeepClone(); } - /// - public void ClearByRegex(string regex) + // reset dirty initial properties + if (input is IRememberBeingDirty tracksChanges) { - InnerCache.ClearByRegex(regex); + tracksChanges.ResetDirtyProperties(false); + input = tracksChanges; } - private static object? CheckCloneableAndTracksChanges(object? input) - { - if (input is IDeepCloneable cloneable) - { - input = cloneable.DeepClone(); - } - - // reset dirty initial properties - if (input is IRememberBeingDirty tracksChanges) - { - tracksChanges.ResetDirtyProperties(false); - input = tracksChanges; - } - - return input; - } + return input; + } - protected virtual void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) { - if (!_disposedValue) + if (disposing) { - if (disposing) - { - InnerCache.DisposeIfDisposable(); - } - - _disposedValue = true; + InnerCache.DisposeIfDisposable(); } - } - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); + _disposedValue = true; } } } diff --git a/src/Umbraco.Core/Cache/DictionaryAppCache.cs b/src/Umbraco.Core/Cache/DictionaryAppCache.cs index 296050a36109..5f7740b8416c 100644 --- a/src/Umbraco.Core/Cache/DictionaryAppCache.cs +++ b/src/Umbraco.Core/Cache/DictionaryAppCache.cs @@ -1,111 +1,103 @@ -using System; -using System.Collections; +using System.Collections; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Text.RegularExpressions; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +/// +/// Implements on top of a concurrent dictionary. +/// +public class DictionaryAppCache : IRequestCache { /// - /// Implements on top of a concurrent dictionary. + /// Gets the internal items dictionary, for tests only! /// - public class DictionaryAppCache : IRequestCache - { - /// - /// Gets the internal items dictionary, for tests only! - /// - private readonly ConcurrentDictionary _items = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _items = new(); - public int Count => _items.Count; + public int Count => _items.Count; - /// - public bool IsAvailable => true; + /// + public bool IsAvailable => true; - /// - public virtual object? Get(string key) - { - return _items.TryGetValue(key, out var value) ? value : null; - } + /// + public virtual object? Get(string key) => _items.TryGetValue(key, out var value) ? value : null; - /// - public virtual object? Get(string key, Func factory) - { - return _items.GetOrAdd(key, _ => factory()); - } + /// + public virtual object? Get(string key, Func factory) => _items.GetOrAdd(key, _ => factory()); - public bool Set(string key, object? value) => _items.TryAdd(key, value); + public bool Set(string key, object? value) => _items.TryAdd(key, value); - public bool Remove(string key) => _items.TryRemove(key, out _); + public bool Remove(string key) => _items.TryRemove(key, out _); - /// - public virtual IEnumerable SearchByKey(string keyStartsWith) + /// + public virtual IEnumerable SearchByKey(string keyStartsWith) + { + var items = new List(); + foreach (var (key, value) in _items) { - var items = new List(); - foreach (var (key, value) in _items) - if (key.InvariantStartsWith(keyStartsWith)) - items.Add(value); - return items; + if (key.InvariantStartsWith(keyStartsWith)) + { + items.Add(value); + } } - /// - public IEnumerable SearchByRegex(string regex) - { - var compiled = new Regex(regex, RegexOptions.Compiled); - var items = new List(); - foreach (var (key, value) in _items) - if (compiled.IsMatch(key)) - items.Add(value); - return items; - } + return items; + } - /// - public virtual void Clear() + /// + public IEnumerable SearchByRegex(string regex) + { + var compiled = new Regex(regex, RegexOptions.Compiled); + var items = new List(); + foreach (var (key, value) in _items) { - _items.Clear(); + if (compiled.IsMatch(key)) + { + items.Add(value); + } } - /// - public virtual void Clear(string key) - { - _items.TryRemove(key, out _); - } + return items; + } - /// - public virtual void ClearOfType(Type type) - { - _items.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == type); - } + /// + public virtual void Clear() => _items.Clear(); - /// - public virtual void ClearOfType() - { - var typeOfT = typeof(T); - ClearOfType(typeOfT); - } + /// + public virtual void Clear(string key) => _items.TryRemove(key, out _); - /// - public virtual void ClearOfType(Func predicate) - { - var typeOfT = typeof(T); - _items.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == typeOfT && predicate(kvp.Key, (T)kvp.Value)); - } + /// + public virtual void ClearOfType(Type type) => + _items.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == type); - /// - public virtual void ClearByKey(string keyStartsWith) - { - _items.RemoveAll(kvp => kvp.Key.InvariantStartsWith(keyStartsWith)); - } + /// + public virtual void ClearOfType() + { + Type typeOfT = typeof(T); + ClearOfType(typeOfT); + } - /// - public virtual void ClearByRegex(string regex) - { - var compiled = new Regex(regex, RegexOptions.Compiled); - _items.RemoveAll(kvp => compiled.IsMatch(kvp.Key)); - } + /// + public virtual void ClearOfType(Func predicate) + { + Type typeOfT = typeof(T); + _items.RemoveAll(kvp => + kvp.Value != null && kvp.Value.GetType() == typeOfT && predicate(kvp.Key, (T)kvp.Value)); + } - public IEnumerator> GetEnumerator() => _items.GetEnumerator(); + /// + public virtual void ClearByKey(string keyStartsWith) => + _items.RemoveAll(kvp => kvp.Key.InvariantStartsWith(keyStartsWith)); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + /// + public virtual void ClearByRegex(string regex) + { + var compiled = new Regex(regex, RegexOptions.Compiled); + _items.RemoveAll(kvp => compiled.IsMatch(kvp.Key)); } + + public IEnumerator> GetEnumerator() => _items.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/src/Umbraco.Core/Cache/DictionaryCacheRefresher.cs b/src/Umbraco.Core/Cache/DictionaryCacheRefresher.cs index dbe84b114e4e..dec535448a02 100644 --- a/src/Umbraco.Core/Cache/DictionaryCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/DictionaryCacheRefresher.cs @@ -1,40 +1,40 @@ -using System; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +public sealed class DictionaryCacheRefresher : CacheRefresherBase { - public sealed class DictionaryCacheRefresher : CacheRefresherBase + public DictionaryCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, eventAggregator, factory) { - public DictionaryCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) - : base(appCaches, eventAggregator , factory) - { } - - #region Define + } - public static readonly Guid UniqueId = Guid.Parse("D1D7E227-F817-4816-BFE9-6C39B6152884"); + #region Define - public override Guid RefresherUniqueId => UniqueId; + public static readonly Guid UniqueId = Guid.Parse("D1D7E227-F817-4816-BFE9-6C39B6152884"); - public override string Name => "Dictionary Cache Refresher"; + public override Guid RefresherUniqueId => UniqueId; - #endregion + public override string Name => "Dictionary Cache Refresher"; - #region Refresher + #endregion - public override void Refresh(int id) - { - ClearAllIsolatedCacheByEntityType(); - base.Refresh(id); - } + #region Refresher - public override void Remove(int id) - { - ClearAllIsolatedCacheByEntityType(); - base.Remove(id); - } + public override void Refresh(int id) + { + ClearAllIsolatedCacheByEntityType(); + base.Refresh(id); + } - #endregion + public override void Remove(int id) + { + ClearAllIsolatedCacheByEntityType(); + base.Remove(id); } + + #endregion } diff --git a/src/Umbraco.Core/Cache/DistributedCache.cs b/src/Umbraco.Core/Cache/DistributedCache.cs index 95c17b946db3..712ab72344b3 100644 --- a/src/Umbraco.Core/Cache/DistributedCache.cs +++ b/src/Umbraco.Core/Cache/DistributedCache.cs @@ -1,176 +1,192 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +/// +/// Represents the entry point into Umbraco's distributed cache infrastructure. +/// +/// +/// +/// The distributed cache infrastructure ensures that distributed caches are +/// invalidated properly in load balancing environments. +/// +/// +/// Distribute caches include static (in-memory) cache, runtime cache, front-end content cache, Examine/Lucene +/// indexes +/// +/// +public sealed class DistributedCache { - /// - /// Represents the entry point into Umbraco's distributed cache infrastructure. - /// - /// - /// - /// The distributed cache infrastructure ensures that distributed caches are - /// invalidated properly in load balancing environments. - /// - /// - /// Distribute caches include static (in-memory) cache, runtime cache, front-end content cache, Examine/Lucene indexes - /// - /// - public sealed class DistributedCache + private readonly CacheRefresherCollection _cacheRefreshers; + private readonly IServerMessenger _serverMessenger; + + public DistributedCache(IServerMessenger serverMessenger, CacheRefresherCollection cacheRefreshers) { - private readonly IServerMessenger _serverMessenger; - private readonly CacheRefresherCollection _cacheRefreshers; + _serverMessenger = serverMessenger; + _cacheRefreshers = cacheRefreshers; + } - public DistributedCache(IServerMessenger serverMessenger, CacheRefresherCollection cacheRefreshers) + // helper method to get an ICacheRefresher by its unique identifier + private ICacheRefresher GetRefresherById(Guid refresherGuid) + { + ICacheRefresher? refresher = _cacheRefreshers[refresherGuid]; + if (refresher == null) { - _serverMessenger = serverMessenger; - _cacheRefreshers = cacheRefreshers; + throw new InvalidOperationException($"No cache refresher found with id {refresherGuid}"); } - #region Core notification methods - - /// - /// Notifies the distributed cache of specified item invalidation, for a specified . - /// - /// The type of the invalidated items. - /// The unique identifier of the ICacheRefresher. - /// A function returning the unique identifier of items. - /// The invalidated items. - /// - /// This method is much better for performance because it does not need to re-lookup object instances. - /// - public void Refresh(Guid refresherGuid, Func getNumericId, params T[] instances) - { - if (refresherGuid == Guid.Empty || instances.Length == 0 || getNumericId == null) return; + return refresher; + } - _serverMessenger.QueueRefresh( - GetRefresherById(refresherGuid), - getNumericId, - instances); - } + #region Core notification methods - /// - /// Notifies the distributed cache of a specified item invalidation, for a specified . - /// - /// The unique identifier of the ICacheRefresher. - /// The unique identifier of the invalidated item. - public void Refresh(Guid refresherGuid, int id) + /// + /// Notifies the distributed cache of specified item invalidation, for a specified . + /// + /// The type of the invalidated items. + /// The unique identifier of the ICacheRefresher. + /// A function returning the unique identifier of items. + /// The invalidated items. + /// + /// This method is much better for performance because it does not need to re-lookup object instances. + /// + public void Refresh(Guid refresherGuid, Func getNumericId, params T[] instances) + { + if (refresherGuid == Guid.Empty || instances.Length == 0 || getNumericId == null) { - if (refresherGuid == Guid.Empty || id == default(int)) return; - - _serverMessenger.QueueRefresh( - GetRefresherById(refresherGuid), - id); + return; } - /// - /// Notifies the distributed cache of a specified item invalidation, for a specified . - /// - /// The unique identifier of the ICacheRefresher. - /// The unique identifier of the invalidated item. - public void Refresh(Guid refresherGuid, Guid id) - { - if (refresherGuid == Guid.Empty || id == Guid.Empty) return; + _serverMessenger.QueueRefresh( + GetRefresherById(refresherGuid), + getNumericId, + instances); + } - _serverMessenger.QueueRefresh( - GetRefresherById(refresherGuid), - id); + /// + /// Notifies the distributed cache of a specified item invalidation, for a specified . + /// + /// The unique identifier of the ICacheRefresher. + /// The unique identifier of the invalidated item. + public void Refresh(Guid refresherGuid, int id) + { + if (refresherGuid == Guid.Empty || id == default) + { + return; } - // payload should be an object, or array of objects, NOT a - // Linq enumerable of some sort (IEnumerable, query...) - public void RefreshByPayload(Guid refresherGuid, TPayload[] payload) - { - if (refresherGuid == Guid.Empty || payload == null) return; + _serverMessenger.QueueRefresh( + GetRefresherById(refresherGuid), + id); + } - _serverMessenger.QueueRefresh( - GetRefresherById(refresherGuid), - payload); + /// + /// Notifies the distributed cache of a specified item invalidation, for a specified . + /// + /// The unique identifier of the ICacheRefresher. + /// The unique identifier of the invalidated item. + public void Refresh(Guid refresherGuid, Guid id) + { + if (refresherGuid == Guid.Empty || id == Guid.Empty) + { + return; } - // so deal with IEnumerable - public void RefreshByPayload(Guid refresherGuid, IEnumerable payloads) - where TPayload : class - { - if (refresherGuid == Guid.Empty || payloads == null) return; + _serverMessenger.QueueRefresh( + GetRefresherById(refresherGuid), + id); + } - _serverMessenger.QueueRefresh( - GetRefresherById(refresherGuid), - payloads.ToArray()); + // payload should be an object, or array of objects, NOT a + // Linq enumerable of some sort (IEnumerable, query...) + public void RefreshByPayload(Guid refresherGuid, TPayload[] payload) + { + if (refresherGuid == Guid.Empty || payload == null) + { + return; } - ///// - ///// Notifies the distributed cache, for a specified . - ///// - ///// The unique identifier of the ICacheRefresher. - ///// The notification content. - //internal void Notify(Guid refresherId, object payload) - //{ - // if (refresherId == Guid.Empty || payload == null) return; - - // _serverMessenger.Notify( - // Current.ServerRegistrar.Registrations, - // GetRefresherById(refresherId), - // json); - //} - - /// - /// Notifies the distributed cache of a global invalidation for a specified . - /// - /// The unique identifier of the ICacheRefresher. - public void RefreshAll(Guid refresherGuid) - { - if (refresherGuid == Guid.Empty) return; + _serverMessenger.QueueRefresh( + GetRefresherById(refresherGuid), + payload); + } - _serverMessenger.QueueRefreshAll( - GetRefresherById(refresherGuid)); + // so deal with IEnumerable + public void RefreshByPayload(Guid refresherGuid, IEnumerable payloads) + where TPayload : class + { + if (refresherGuid == Guid.Empty || payloads == null) + { + return; } - /// - /// Notifies the distributed cache of a specified item removal, for a specified . - /// - /// The unique identifier of the ICacheRefresher. - /// The unique identifier of the removed item. - public void Remove(Guid refresherGuid, int id) - { - if (refresherGuid == Guid.Empty || id == default(int)) return; + _serverMessenger.QueueRefresh( + GetRefresherById(refresherGuid), + payloads.ToArray()); + } - _serverMessenger.QueueRemove( - GetRefresherById(refresherGuid), - id); - } + ///// + ///// Notifies the distributed cache, for a specified . + ///// + ///// The unique identifier of the ICacheRefresher. + ///// The notification content. + //internal void Notify(Guid refresherId, object payload) + //{ + // if (refresherId == Guid.Empty || payload == null) return; + + // _serverMessenger.Notify( + // Current.ServerRegistrar.Registrations, + // GetRefresherById(refresherId), + // json); + //} - /// - /// Notifies the distributed cache of specified item removal, for a specified . - /// - /// The type of the removed items. - /// The unique identifier of the ICacheRefresher. - /// A function returning the unique identifier of items. - /// The removed items. - /// - /// This method is much better for performance because it does not need to re-lookup object instances. - /// - public void Remove(Guid refresherGuid, Func getNumericId, params T[] instances) + /// + /// Notifies the distributed cache of a global invalidation for a specified . + /// + /// The unique identifier of the ICacheRefresher. + public void RefreshAll(Guid refresherGuid) + { + if (refresherGuid == Guid.Empty) { - _serverMessenger.QueueRemove( - GetRefresherById(refresherGuid), - getNumericId, - instances); + return; } - #endregion + _serverMessenger.QueueRefreshAll( + GetRefresherById(refresherGuid)); + } - // helper method to get an ICacheRefresher by its unique identifier - private ICacheRefresher GetRefresherById(Guid refresherGuid) + /// + /// Notifies the distributed cache of a specified item removal, for a specified . + /// + /// The unique identifier of the ICacheRefresher. + /// The unique identifier of the removed item. + public void Remove(Guid refresherGuid, int id) + { + if (refresherGuid == Guid.Empty || id == default) { - ICacheRefresher? refresher = _cacheRefreshers[refresherGuid]; - if (refresher == null) - { - throw new InvalidOperationException($"No cache refresher found with id {refresherGuid}"); - } - - return refresher; + return; } + + _serverMessenger.QueueRemove( + GetRefresherById(refresherGuid), + id); } + + /// + /// Notifies the distributed cache of specified item removal, for a specified . + /// + /// The type of the removed items. + /// The unique identifier of the ICacheRefresher. + /// A function returning the unique identifier of items. + /// The removed items. + /// + /// This method is much better for performance because it does not need to re-lookup object instances. + /// + public void Remove(Guid refresherGuid, Func getNumericId, params T[] instances) => + _serverMessenger.QueueRemove( + GetRefresherById(refresherGuid), + getNumericId, + instances); + + #endregion } diff --git a/src/Umbraco.Core/Cache/DomainCacheRefresher.cs b/src/Umbraco.Core/Cache/DomainCacheRefresher.cs index 28e62c854d6b..e18cdeb16335 100644 --- a/src/Umbraco.Core/Cache/DomainCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/DomainCacheRefresher.cs @@ -1,4 +1,3 @@ -using System; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; @@ -6,78 +5,75 @@ using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services.Changes; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +public sealed class + DomainCacheRefresher : PayloadCacheRefresherBase { - public sealed class DomainCacheRefresher : PayloadCacheRefresherBase + private readonly IPublishedSnapshotService _publishedSnapshotService; + + public DomainCacheRefresher( + AppCaches appCaches, + IJsonSerializer serializer, + IPublishedSnapshotService publishedSnapshotService, + IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, serializer, eventAggregator, factory) => + _publishedSnapshotService = publishedSnapshotService; + + #region Json + + public class JsonPayload { - private readonly IPublishedSnapshotService _publishedSnapshotService; - - public DomainCacheRefresher( - AppCaches appCaches, - IJsonSerializer serializer, - IPublishedSnapshotService publishedSnapshotService, - IEventAggregator eventAggregator, - ICacheRefresherNotificationFactory factory) - : base(appCaches, serializer, eventAggregator, factory) + public JsonPayload(int id, DomainChangeTypes changeType) { - _publishedSnapshotService = publishedSnapshotService; + Id = id; + ChangeType = changeType; } - #region Define + public int Id { get; } - public static readonly Guid UniqueId = Guid.Parse("11290A79-4B57-4C99-AD72-7748A3CF38AF"); - - public override Guid RefresherUniqueId => UniqueId; + public DomainChangeTypes ChangeType { get; } + } - public override string Name => "Domain Cache Refresher"; + #endregion - #endregion + #region Define - #region Refresher + public static readonly Guid UniqueId = Guid.Parse("11290A79-4B57-4C99-AD72-7748A3CF38AF"); - public override void Refresh(JsonPayload[] payloads) - { - ClearAllIsolatedCacheByEntityType(); + public override Guid RefresherUniqueId => UniqueId; - // note: must do what's above FIRST else the repositories still have the old cached - // content and when the PublishedCachesService is notified of changes it does not see - // the new content... + public override string Name => "Domain Cache Refresher"; - // notify - _publishedSnapshotService.Notify(payloads); - // then trigger event - base.Refresh(payloads); - } + #endregion - // these events should never trigger - // everything should be PAYLOAD/JSON + #region Refresher - public override void RefreshAll() => throw new NotSupportedException(); + public override void Refresh(JsonPayload[] payloads) + { + ClearAllIsolatedCacheByEntityType(); - public override void Refresh(int id) => throw new NotSupportedException(); + // note: must do what's above FIRST else the repositories still have the old cached + // content and when the PublishedCachesService is notified of changes it does not see + // the new content... - public override void Refresh(Guid id) => throw new NotSupportedException(); + // notify + _publishedSnapshotService.Notify(payloads); + // then trigger event + base.Refresh(payloads); + } - public override void Remove(int id) => throw new NotSupportedException(); + // these events should never trigger + // everything should be PAYLOAD/JSON - #endregion + public override void RefreshAll() => throw new NotSupportedException(); - #region Json + public override void Refresh(int id) => throw new NotSupportedException(); - public class JsonPayload - { - public JsonPayload(int id, DomainChangeTypes changeType) - { - Id = id; - ChangeType = changeType; - } + public override void Refresh(Guid id) => throw new NotSupportedException(); - public int Id { get; } + public override void Remove(int id) => throw new NotSupportedException(); - public DomainChangeTypes ChangeType { get; } - } - - #endregion - - } + #endregion } diff --git a/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs b/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs index 6c3b8855d2a2..a19410ee1a92 100644 --- a/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs +++ b/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs @@ -1,166 +1,172 @@ -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; using System.Text.RegularExpressions; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +/// +/// Implements a fast on top of a concurrent dictionary. +/// +public class FastDictionaryAppCache : IAppCache { /// - /// Implements a fast on top of a concurrent dictionary. + /// Gets the internal items dictionary, for tests only! /// - public class FastDictionaryAppCache : IAppCache - { + private readonly ConcurrentDictionary> _items = new(); - /// - /// Gets the internal items dictionary, for tests only! - /// - private readonly ConcurrentDictionary> _items = new ConcurrentDictionary>(); + public IEnumerable Keys => _items.Keys; - public IEnumerable Keys => _items.Keys; + public int Count => _items.Count; - public int Count => _items.Count; + /// + public object? Get(string cacheKey) + { + _items.TryGetValue(cacheKey, out Lazy result); // else null + return result == null ? null : SafeLazy.GetSafeLazyValue(result!); // return exceptions as null + } - /// - public object? Get(string cacheKey) + /// + public object? Get(string cacheKey, Func getCacheItem) + { + Lazy result = _items.GetOrAdd(cacheKey, k => SafeLazy.GetSafeLazy(getCacheItem)); + + var value = result.Value; // will not throw (safe lazy) + if (!(value is SafeLazy.ExceptionHolder eh)) { - _items.TryGetValue(cacheKey, out var result); // else null - return result == null ? null : SafeLazy.GetSafeLazyValue(result!); // return exceptions as null + return value; } - /// - public object? Get(string cacheKey, Func getCacheItem) - { - var result = _items.GetOrAdd(cacheKey, k => SafeLazy.GetSafeLazy(getCacheItem)); + // and... it's in the cache anyway - so contrary to other cache providers, + // which would trick with GetSafeLazyValue, we need to remove by ourselves, + // in order NOT to cache exceptions - var value = result.Value; // will not throw (safe lazy) - if (!(value is SafeLazy.ExceptionHolder eh)) - return value; + _items.TryRemove(cacheKey, out result); + eh.Exception.Throw(); // throw once! + return null; // never reached + } - // and... it's in the cache anyway - so contrary to other cache providers, - // which would trick with GetSafeLazyValue, we need to remove by ourselves, - // in order NOT to cache exceptions + /// + public IEnumerable SearchByKey(string keyStartsWith) => + _items + .Where(kvp => kvp.Key.InvariantStartsWith(keyStartsWith)) + .Select(kvp => SafeLazy.GetSafeLazyValue(kvp.Value)) + .Where(x => x != null); - _items.TryRemove(cacheKey, out result); - eh.Exception.Throw(); // throw once! - return null; // never reached - } + /// + public IEnumerable SearchByRegex(string regex) + { + var compiled = new Regex(regex, RegexOptions.Compiled); + return _items + .Where(kvp => compiled.IsMatch(kvp.Key)) + .Select(kvp => SafeLazy.GetSafeLazyValue(kvp.Value)) + .Where(x => x != null); + } - /// - public IEnumerable SearchByKey(string keyStartsWith) - { - return _items - .Where(kvp => kvp.Key.InvariantStartsWith(keyStartsWith)) - .Select(kvp => SafeLazy.GetSafeLazyValue(kvp.Value)) - .Where(x => x != null); - } + /// + public void Clear() => _items.Clear(); - /// - public IEnumerable SearchByRegex(string regex) - { - var compiled = new Regex(regex, RegexOptions.Compiled); - return _items - .Where(kvp => compiled.IsMatch(kvp.Key)) - .Select(kvp => SafeLazy.GetSafeLazyValue(kvp.Value)) - .Where(x => x != null); - } + /// + public void Clear(string key) => _items.TryRemove(key, out _); - /// - public void Clear() + /// + public void ClearOfType(Type type) + { + if (type == null) { - _items.Clear(); + return; } - /// - public void Clear(string key) - { - _items.TryRemove(key, out _); - } + var isInterface = type.IsInterface; - /// - public void ClearOfType(Type type) + foreach (KeyValuePair> kvp in _items + .Where(x => + { + // entry.Value is Lazy and not null, its value may be null + // remove null values as well, does not hurt + // get non-created as NonCreatedValue & exceptions as null + var value = SafeLazy.GetSafeLazyValue(x.Value, true); + + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return value == null || (isInterface ? type.IsInstanceOfType(value) : value.GetType() == type); + })) { - if (type == null) return; - var isInterface = type.IsInterface; - - foreach (var kvp in _items - .Where(x => - { - // entry.Value is Lazy and not null, its value may be null - // remove null values as well, does not hurt - // get non-created as NonCreatedValue & exceptions as null - var value = SafeLazy.GetSafeLazyValue(x.Value, true); - - // if T is an interface remove anything that implements that interface - // otherwise remove exact types (not inherited types) - return value == null || (isInterface ? (type.IsInstanceOfType(value)) : (value.GetType() == type)); - })) - _items.TryRemove(kvp.Key, out _); + _items.TryRemove(kvp.Key, out _); } + } - /// - public void ClearOfType() + /// + public void ClearOfType() + { + Type typeOfT = typeof(T); + var isInterface = typeOfT.IsInterface; + + foreach (KeyValuePair> kvp in _items + .Where(x => + { + // entry.Value is Lazy and not null, its value may be null + // remove null values as well, does not hurt + // compare on exact type, don't use "is" + // get non-created as NonCreatedValue & exceptions as null + var value = SafeLazy.GetSafeLazyValue(x.Value, true); + + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return value == null || (isInterface ? value is T : value.GetType() == typeOfT); + })) { - var typeOfT = typeof(T); - var isInterface = typeOfT.IsInterface; - - foreach (var kvp in _items - .Where(x => - { - // entry.Value is Lazy and not null, its value may be null - // remove null values as well, does not hurt - // compare on exact type, don't use "is" - // get non-created as NonCreatedValue & exceptions as null - var value = SafeLazy.GetSafeLazyValue(x.Value, true); - - // if T is an interface remove anything that implements that interface - // otherwise remove exact types (not inherited types) - return value == null || (isInterface ? (value is T) : (value.GetType() == typeOfT)); - })) - _items.TryRemove(kvp.Key, out _); + _items.TryRemove(kvp.Key, out _); } + } - /// - public void ClearOfType(Func predicate) + /// + public void ClearOfType(Func predicate) + { + Type typeOfT = typeof(T); + var isInterface = typeOfT.IsInterface; + + foreach (KeyValuePair> kvp in _items + .Where(x => + { + // entry.Value is Lazy and not null, its value may be null + // remove null values as well, does not hurt + // compare on exact type, don't use "is" + // get non-created as NonCreatedValue & exceptions as null + var value = SafeLazy.GetSafeLazyValue(x.Value, true); + if (value == null) + { + return true; + } + + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return (isInterface ? value is T : value.GetType() == typeOfT) + // run predicate on the 'public key' part only, ie without prefix + && predicate(x.Key, (T)value); + })) { - var typeOfT = typeof(T); - var isInterface = typeOfT.IsInterface; - - foreach (var kvp in _items - .Where(x => - { - // entry.Value is Lazy and not null, its value may be null - // remove null values as well, does not hurt - // compare on exact type, don't use "is" - // get non-created as NonCreatedValue & exceptions as null - var value = SafeLazy.GetSafeLazyValue(x.Value, true); - if (value == null) return true; - - // if T is an interface remove anything that implements that interface - // otherwise remove exact types (not inherited types) - return (isInterface ? (value is T) : (value.GetType() == typeOfT)) - // run predicate on the 'public key' part only, ie without prefix - && predicate(x.Key, (T)value); - })) - _items.TryRemove(kvp.Key, out _); + _items.TryRemove(kvp.Key, out _); } + } - /// - public void ClearByKey(string keyStartsWith) + /// + public void ClearByKey(string keyStartsWith) + { + foreach (KeyValuePair> ikvp in _items + .Where(kvp => kvp.Key.InvariantStartsWith(keyStartsWith))) { - foreach (var ikvp in _items - .Where(kvp => kvp.Key.InvariantStartsWith(keyStartsWith))) - _items.TryRemove(ikvp.Key, out _); + _items.TryRemove(ikvp.Key, out _); } + } - /// - public void ClearByRegex(string regex) + /// + public void ClearByRegex(string regex) + { + var compiled = new Regex(regex, RegexOptions.Compiled); + foreach (KeyValuePair> ikvp in _items + .Where(kvp => compiled.IsMatch(kvp.Key))) { - var compiled = new Regex(regex, RegexOptions.Compiled); - foreach (var ikvp in _items - .Where(kvp => compiled.IsMatch(kvp.Key))) - _items.TryRemove(ikvp.Key, out _); + _items.TryRemove(ikvp.Key, out _); } } } diff --git a/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs b/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs index e0bbd5739794..3db980d12a73 100644 --- a/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs +++ b/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs @@ -1,281 +1,285 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Text.RegularExpressions; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +/// +/// Provides a base class to fast, dictionary-based implementations. +/// +public abstract class FastDictionaryAppCacheBase : IAppCache { - /// - /// Provides a base class to fast, dictionary-based implementations. - /// - public abstract class FastDictionaryAppCacheBase : IAppCache - { - // prefix cache keys so we know which one are ours - protected const string CacheItemPrefix = "umbrtmche"; + // prefix cache keys so we know which one are ours + protected const string CacheItemPrefix = "umbrtmche"; - #region IAppCache + #region IAppCache - /// - public virtual object? Get(string key) + /// + public virtual object? Get(string key) + { + key = GetCacheKey(key); + Lazy? result; + try { - key = GetCacheKey(key); - Lazy? result; - try - { - EnterReadLock(); - result = GetEntry(key) as Lazy; // null if key not found - } - finally - { - ExitReadLock(); - } - return result == null ? null : SafeLazy.GetSafeLazyValue(result); // return exceptions as null + EnterReadLock(); + result = GetEntry(key) as Lazy; // null if key not found } + finally + { + ExitReadLock(); + } + + return result == null ? null : SafeLazy.GetSafeLazyValue(result); // return exceptions as null + } - /// - public abstract object? Get(string key, Func factory); + /// + public abstract object? Get(string key, Func factory); - /// - public virtual IEnumerable SearchByKey(string keyStartsWith) + /// + public virtual IEnumerable SearchByKey(string keyStartsWith) + { + var plen = CacheItemPrefix.Length + 1; + IEnumerable> entries; + try { - var plen = CacheItemPrefix.Length + 1; - IEnumerable> entries; - try - { - EnterReadLock(); - entries = GetDictionaryEntries() - .Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith)) - .ToArray(); // evaluate while locked - } - finally - { - ExitReadLock(); - } + EnterReadLock(); + entries = GetDictionaryEntries() + .Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith)) + .ToArray(); // evaluate while locked + } + finally + { + ExitReadLock(); + } - return entries - .Select(x => SafeLazy.GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null - .Where(x => x != null)!; // backward compat, don't store null values in the cache + return entries + .Select(x => SafeLazy.GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null + .Where(x => x != null)!; // backward compat, don't store null values in the cache + } + + /// + public virtual IEnumerable SearchByRegex(string regex) + { + const string prefix = CacheItemPrefix + "-"; + var compiled = new Regex(regex, RegexOptions.Compiled); + var plen = prefix.Length; + IEnumerable> entries; + try + { + EnterReadLock(); + entries = GetDictionaryEntries() + .Where(x => compiled.IsMatch(((string)x.Key).Substring(plen))) + .ToArray(); // evaluate while locked + } + finally + { + ExitReadLock(); } - /// - public virtual IEnumerable SearchByRegex(string regex) + return entries + .Select(x => SafeLazy.GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null + .Where(x => x != null); // backward compatible, don't store null values in the cache + } + + /// + public virtual void Clear() + { + try { - const string prefix = CacheItemPrefix + "-"; - var compiled = new Regex(regex, RegexOptions.Compiled); - var plen = prefix.Length; - IEnumerable> entries; - try - { - EnterReadLock(); - entries = GetDictionaryEntries() - .Where(x => compiled.IsMatch(((string)x.Key).Substring(plen))) - .ToArray(); // evaluate while locked - } - finally + EnterWriteLock(); + foreach (KeyValuePair entry in GetDictionaryEntries().ToArray()) { - ExitReadLock(); + RemoveEntry((string)entry.Key); } - return entries - .Select(x => SafeLazy.GetSafeLazyValue( (Lazy)x.Value)) // return exceptions as null - .Where(x => x != null); // backward compatible, don't store null values in the cache } + finally + { + ExitWriteLock(); + } + } - /// - public virtual void Clear() + /// + public virtual void Clear(string key) + { + var cacheKey = GetCacheKey(key); + try { - try - { - EnterWriteLock(); - foreach (var entry in GetDictionaryEntries().ToArray()) - { - RemoveEntry((string) entry.Key); - } - } - finally - { - ExitWriteLock(); - } + EnterWriteLock(); + RemoveEntry(cacheKey); + } + finally + { + ExitWriteLock(); } + } - /// - public virtual void Clear(string key) + /// + public virtual void ClearOfType(Type type) + { + if (type == null) { - var cacheKey = GetCacheKey(key); - try - { - EnterWriteLock(); - RemoveEntry(cacheKey); - } - finally - { - ExitWriteLock(); - } + return; } - /// - public virtual void ClearOfType(Type type) + var isInterface = type.IsInterface; + try { - if (type == null) return; - var isInterface = type.IsInterface; - try - { - EnterWriteLock(); - foreach (var entry in GetDictionaryEntries() - .Where(x => - { - // entry.Value is Lazy and not null, its value may be null - // remove null values as well, does not hurt - // get non-created as NonCreatedValue & exceptions as null - var value = SafeLazy.GetSafeLazyValue((Lazy) x.Value, true); + EnterWriteLock(); + foreach (KeyValuePair entry in GetDictionaryEntries() + .Where(x => + { + // entry.Value is Lazy and not null, its value may be null + // remove null values as well, does not hurt + // get non-created as NonCreatedValue & exceptions as null + var value = SafeLazy.GetSafeLazyValue((Lazy)x.Value, true); - // if T is an interface remove anything that implements that interface - // otherwise remove exact types (not inherited types) - return value == null || (isInterface ? (type.IsInstanceOfType(value)) : (value.GetType() == type)); - }) - .ToArray()) - { - RemoveEntry((string) entry.Key); - } - } - finally + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return value == null || + (isInterface ? type.IsInstanceOfType(value) : value.GetType() == type); + }) + .ToArray()) { - ExitWriteLock(); + RemoveEntry((string)entry.Key); } } + finally + { + ExitWriteLock(); + } + } - /// - public virtual void ClearOfType() + /// + public virtual void ClearOfType() + { + Type typeOfT = typeof(T); + var isInterface = typeOfT.IsInterface; + try { - var typeOfT = typeof(T); - var isInterface = typeOfT.IsInterface; - try - { - EnterWriteLock(); - foreach (var entry in GetDictionaryEntries() - .Where(x => - { - // entry.Value is Lazy and not null, its value may be null - // remove null values as well, does not hurt - // compare on exact type, don't use "is" - // get non-created as NonCreatedValue & exceptions as null - var value = SafeLazy.GetSafeLazyValue((Lazy) x.Value, true); + EnterWriteLock(); + foreach (KeyValuePair entry in GetDictionaryEntries() + .Where(x => + { + // entry.Value is Lazy and not null, its value may be null + // remove null values as well, does not hurt + // compare on exact type, don't use "is" + // get non-created as NonCreatedValue & exceptions as null + var value = SafeLazy.GetSafeLazyValue((Lazy)x.Value, true); - // if T is an interface remove anything that implements that interface - // otherwise remove exact types (not inherited types) - return value == null || (isInterface ? (value is T) : (value.GetType() == typeOfT)); - }) - .ToArray()) - { - RemoveEntry((string) entry.Key); - } - } - finally + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return value == null || (isInterface ? value is T : value.GetType() == typeOfT); + }) + .ToArray()) { - ExitWriteLock(); + RemoveEntry((string)entry.Key); } } + finally + { + ExitWriteLock(); + } + } - /// - public virtual void ClearOfType(Func predicate) + /// + public virtual void ClearOfType(Func predicate) + { + Type typeOfT = typeof(T); + var isInterface = typeOfT.IsInterface; + var plen = CacheItemPrefix.Length + 1; + try { - var typeOfT = typeof(T); - var isInterface = typeOfT.IsInterface; - var plen = CacheItemPrefix.Length + 1; - try - { - EnterWriteLock(); - foreach (var entry in GetDictionaryEntries() - .Where(x => - { - // entry.Value is Lazy and not null, its value may be null - // remove null values as well, does not hurt - // compare on exact type, don't use "is" - // get non-created as NonCreatedValue & exceptions as null - var value = SafeLazy.GetSafeLazyValue((Lazy) x.Value, true); - if (value == null) return true; + EnterWriteLock(); + foreach (KeyValuePair entry in GetDictionaryEntries() + .Where(x => + { + // entry.Value is Lazy and not null, its value may be null + // remove null values as well, does not hurt + // compare on exact type, don't use "is" + // get non-created as NonCreatedValue & exceptions as null + var value = SafeLazy.GetSafeLazyValue((Lazy)x.Value, true); + if (value == null) + { + return true; + } - // if T is an interface remove anything that implements that interface - // otherwise remove exact types (not inherited types) - return (isInterface ? (value is T) : (value.GetType() == typeOfT)) - // run predicate on the 'public key' part only, ie without prefix - && predicate(((string) x.Key).Substring(plen), (T) value); - })) - { - RemoveEntry((string) entry.Key); - } - } - finally + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return (isInterface ? value is T : value.GetType() == typeOfT) + // run predicate on the 'public key' part only, ie without prefix + && predicate(((string)x.Key).Substring(plen), (T)value); + })) { - ExitWriteLock(); + RemoveEntry((string)entry.Key); } } + finally + { + ExitWriteLock(); + } + } - /// - public virtual void ClearByKey(string keyStartsWith) + /// + public virtual void ClearByKey(string keyStartsWith) + { + var plen = CacheItemPrefix.Length + 1; + try { - var plen = CacheItemPrefix.Length + 1; - try + EnterWriteLock(); + foreach (KeyValuePair entry in GetDictionaryEntries() + .Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith)) + .ToArray()) { - EnterWriteLock(); - foreach (var entry in GetDictionaryEntries() - .Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith)) - .ToArray()) - { - RemoveEntry((string) entry.Key); - } - } - finally - { - ExitWriteLock(); + RemoveEntry((string)entry.Key); } } + finally + { + ExitWriteLock(); + } + } - /// - public virtual void ClearByRegex(string regex) + /// + public virtual void ClearByRegex(string regex) + { + var compiled = new Regex(regex, RegexOptions.Compiled); + var plen = CacheItemPrefix.Length + 1; + try { - var compiled = new Regex(regex, RegexOptions.Compiled); - var plen = CacheItemPrefix.Length + 1; - try - { - EnterWriteLock(); - foreach (var entry in GetDictionaryEntries() - .Where(x => compiled.IsMatch(((string)x.Key).Substring(plen))) - .ToArray()) - { - RemoveEntry((string) entry.Key); - } - } - finally + EnterWriteLock(); + foreach (KeyValuePair entry in GetDictionaryEntries() + .Where(x => compiled.IsMatch(((string)x.Key).Substring(plen))) + .ToArray()) { - ExitWriteLock(); + RemoveEntry((string)entry.Key); } } + finally + { + ExitWriteLock(); + } + } - #endregion - - #region Dictionary - - // manipulate the underlying cache entries - // these *must* be called from within the appropriate locks - // and use the full prefixed cache keys - protected abstract IEnumerable> GetDictionaryEntries(); - protected abstract void RemoveEntry(string key); - protected abstract object? GetEntry(string key); + #endregion - // read-write lock the underlying cache - //protected abstract IDisposable ReadLock { get; } - //protected abstract IDisposable WriteLock { get; } + #region Dictionary - protected abstract void EnterReadLock(); - protected abstract void ExitReadLock(); - protected abstract void EnterWriteLock(); - protected abstract void ExitWriteLock(); + // manipulate the underlying cache entries + // these *must* be called from within the appropriate locks + // and use the full prefixed cache keys + protected abstract IEnumerable> GetDictionaryEntries(); + protected abstract void RemoveEntry(string key); + protected abstract object? GetEntry(string key); - protected string GetCacheKey(string key) => $"{CacheItemPrefix}-{key}"; + // read-write lock the underlying cache + //protected abstract IDisposable ReadLock { get; } + //protected abstract IDisposable WriteLock { get; } + protected abstract void EnterReadLock(); + protected abstract void ExitReadLock(); + protected abstract void EnterWriteLock(); + protected abstract void ExitWriteLock(); + protected string GetCacheKey(string key) => $"{CacheItemPrefix}-{key}"; - #endregion - } + #endregion } diff --git a/src/Umbraco.Core/Cache/IAppCache.cs b/src/Umbraco.Core/Cache/IAppCache.cs index 81cfc2e114a6..c223015c7a7f 100644 --- a/src/Umbraco.Core/Cache/IAppCache.cs +++ b/src/Umbraco.Core/Cache/IAppCache.cs @@ -1,94 +1,96 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Cache; -namespace Umbraco.Cms.Core.Cache +/// +/// Defines an application cache. +/// +public interface IAppCache { /// - /// Defines an application cache. + /// Gets an item identified by its key. /// - public interface IAppCache - { - /// - /// Gets an item identified by its key. - /// - /// The key of the item. - /// The item, or null if the item was not found. - object? Get(string key); + /// The key of the item. + /// The item, or null if the item was not found. + object? Get(string key); - /// - /// Gets or creates an item identified by its key. - /// - /// The key of the item. - /// A factory function that can create the item. - /// The item. - object? Get(string key, Func factory); + /// + /// Gets or creates an item identified by its key. + /// + /// The key of the item. + /// A factory function that can create the item. + /// The item. + object? Get(string key, Func factory); - /// - /// Gets items with a key starting with the specified value. - /// - /// The StartsWith value to use in the search. - /// Items matching the search. - IEnumerable SearchByKey(string keyStartsWith); + /// + /// Gets items with a key starting with the specified value. + /// + /// The StartsWith value to use in the search. + /// Items matching the search. + IEnumerable SearchByKey(string keyStartsWith); - /// - /// Gets items with a key matching a regular expression. - /// - /// The regular expression. - /// Items matching the search. - IEnumerable SearchByRegex(string regex); + /// + /// Gets items with a key matching a regular expression. + /// + /// The regular expression. + /// Items matching the search. + IEnumerable SearchByRegex(string regex); - /// - /// Removes all items from the cache. - /// - void Clear(); + /// + /// Removes all items from the cache. + /// + void Clear(); - /// - /// Removes an item identified by its key from the cache. - /// - /// The key of the item. - void Clear(string key); + /// + /// Removes an item identified by its key from the cache. + /// + /// The key of the item. + void Clear(string key); - /// - /// Removes items of a specified type from the cache. - /// - /// The type to remove. - /// - /// If the type is an interface, then all items of a type implementing that interface are - /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from - /// the specified type are not removed). - /// Performs a case-sensitive search. - /// - void ClearOfType(Type type); + /// + /// Removes items of a specified type from the cache. + /// + /// The type to remove. + /// + /// + /// If the type is an interface, then all items of a type implementing that interface are + /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from + /// the specified type are not removed). + /// + /// Performs a case-sensitive search. + /// + void ClearOfType(Type type); - /// - /// Removes items of a specified type from the cache. - /// - /// The type of the items to remove. - /// If the type is an interface, then all items of a type implementing that interface are - /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from - /// the specified type are not removed). - void ClearOfType(); + /// + /// Removes items of a specified type from the cache. + /// + /// The type of the items to remove. + /// + /// If the type is an interface, then all items of a type implementing that interface are + /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from + /// the specified type are not removed). + /// + void ClearOfType(); - /// - /// Removes items of a specified type from the cache. - /// - /// The type of the items to remove. - /// The predicate to satisfy. - /// If the type is an interface, then all items of a type implementing that interface are - /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from - /// the specified type are not removed). - void ClearOfType(Func predicate); + /// + /// Removes items of a specified type from the cache. + /// + /// The type of the items to remove. + /// The predicate to satisfy. + /// + /// If the type is an interface, then all items of a type implementing that interface are + /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from + /// the specified type are not removed). + /// + void ClearOfType(Func predicate); - /// - /// Clears items with a key starting with the specified value. - /// - /// The StartsWith value to use in the search. - void ClearByKey(string keyStartsWith); + /// + /// Clears items with a key starting with the specified value. + /// + /// The StartsWith value to use in the search. + void ClearByKey(string keyStartsWith); - /// - /// Clears items with a key matching a regular expression. - /// - /// The regular expression. - void ClearByRegex(string regex); - } + /// + /// Clears items with a key matching a regular expression. + /// + /// The regular expression. + void ClearByRegex(string regex); } diff --git a/src/Umbraco.Core/Cache/IAppPolicyCache.cs b/src/Umbraco.Core/Cache/IAppPolicyCache.cs index ec59bf390b28..930685563489 100644 --- a/src/Umbraco.Core/Cache/IAppPolicyCache.cs +++ b/src/Umbraco.Core/Cache/IAppPolicyCache.cs @@ -1,43 +1,42 @@ -using System; +namespace Umbraco.Cms.Core.Cache; -namespace Umbraco.Cms.Core.Cache +/// +/// Defines an application cache that support cache policies. +/// +/// +/// A cache policy can be used to cache with timeouts, +/// or depending on files, and with a remove callback, etc. +/// +public interface IAppPolicyCache : IAppCache { /// - /// Defines an application cache that support cache policies. + /// Gets an item identified by its key. /// - /// A cache policy can be used to cache with timeouts, - /// or depending on files, and with a remove callback, etc. - public interface IAppPolicyCache : IAppCache - { - /// - /// Gets an item identified by its key. - /// - /// The key of the item. - /// A factory function that can create the item. - /// An optional cache timeout. - /// An optional value indicating whether the cache timeout is sliding (default is false). - /// Files the cache entry depends on. - /// The item. - object? Get( - string key, - Func factory, - TimeSpan? timeout, - bool isSliding = false, - string[]? dependentFiles = null); + /// The key of the item. + /// A factory function that can create the item. + /// An optional cache timeout. + /// An optional value indicating whether the cache timeout is sliding (default is false). + /// Files the cache entry depends on. + /// The item. + object? Get( + string key, + Func factory, + TimeSpan? timeout, + bool isSliding = false, + string[]? dependentFiles = null); - /// - /// Inserts an item. - /// - /// The key of the item. - /// A factory function that can create the item. - /// An optional cache timeout. - /// An optional value indicating whether the cache timeout is sliding (default is false). - /// Files the cache entry depends on. - void Insert( - string key, - Func factory, - TimeSpan? timeout = null, - bool isSliding = false, - string[]? dependentFiles = null); - } + /// + /// Inserts an item. + /// + /// The key of the item. + /// A factory function that can create the item. + /// An optional cache timeout. + /// An optional value indicating whether the cache timeout is sliding (default is false). + /// Files the cache entry depends on. + void Insert( + string key, + Func factory, + TimeSpan? timeout = null, + bool isSliding = false, + string[]? dependentFiles = null); } diff --git a/src/Umbraco.Core/Cache/ICacheRefresher.cs b/src/Umbraco.Core/Cache/ICacheRefresher.cs index 97a3bf08eb0b..ccd5de268144 100644 --- a/src/Umbraco.Core/Cache/ICacheRefresher.cs +++ b/src/Umbraco.Core/Cache/ICacheRefresher.cs @@ -1,33 +1,31 @@ -using System; -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +/// +/// The IcacheRefresher Interface is used for load balancing. +/// +public interface ICacheRefresher : IDiscoverable { - /// - /// The IcacheRefresher Interface is used for load balancing. - /// - /// - public interface ICacheRefresher : IDiscoverable - { - Guid RefresherUniqueId { get; } - string Name { get; } - void RefreshAll(); - void Refresh(int id); - void Remove(int id); - void Refresh(Guid id); - } + Guid RefresherUniqueId { get; } + string Name { get; } + void RefreshAll(); + void Refresh(int id); + void Remove(int id); + void Refresh(Guid id); +} - /// - /// Strongly type cache refresher that is able to refresh cache of real instances of objects as well as IDs - /// - /// - /// - /// This is much better for performance when we're not running in a load balanced environment so we can refresh the cache - /// against a already resolved object instead of looking the object back up by id. - /// - public interface ICacheRefresher : ICacheRefresher - { - void Refresh(T instance); - void Remove(T instance); - } +/// +/// Strongly type cache refresher that is able to refresh cache of real instances of objects as well as IDs +/// +/// +/// +/// This is much better for performance when we're not running in a load balanced environment so we can refresh the +/// cache +/// against a already resolved object instead of looking the object back up by id. +/// +public interface ICacheRefresher : ICacheRefresher +{ + void Refresh(T instance); + void Remove(T instance); } diff --git a/src/Umbraco.Core/Cache/ICacheRefresherNotificationFactory.cs b/src/Umbraco.Core/Cache/ICacheRefresherNotificationFactory.cs index 04b91e43d8b6..15a92ae24649 100644 --- a/src/Umbraco.Core/Cache/ICacheRefresherNotificationFactory.cs +++ b/src/Umbraco.Core/Cache/ICacheRefresherNotificationFactory.cs @@ -1,17 +1,17 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +/// +/// Factory for creating cache refresher notification instances +/// +public interface ICacheRefresherNotificationFactory { /// - /// Factory for creating cache refresher notification instances + /// Creates a /// - public interface ICacheRefresherNotificationFactory - { - /// - /// Creates a - /// - /// The to create - TNotification Create(object msgObject, MessageType type) where TNotification : CacheRefresherNotification; - } + /// The to create + TNotification Create(object msgObject, MessageType type) + where TNotification : CacheRefresherNotification; } diff --git a/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs b/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs index 619fc1eb56ce..120414cfd5bf 100644 --- a/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs @@ -1,14 +1,13 @@ -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +/// +/// A cache refresher that supports refreshing or removing cache based on a custom Json payload +/// +public interface IJsonCacheRefresher : ICacheRefresher { /// - /// A cache refresher that supports refreshing or removing cache based on a custom Json payload + /// Refreshes, clears, etc... any cache based on the information provided in the json /// - public interface IJsonCacheRefresher : ICacheRefresher - { - /// - /// Refreshes, clears, etc... any cache based on the information provided in the json - /// - /// - void Refresh(string json); - } + /// + void Refresh(string json); } diff --git a/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs b/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs index 21dfdd840d0f..150ce18a10bf 100644 --- a/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs @@ -1,14 +1,13 @@ -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +/// +/// A cache refresher that supports refreshing cache based on a custom payload +/// +public interface IPayloadCacheRefresher : IJsonCacheRefresher { /// - /// A cache refresher that supports refreshing cache based on a custom payload + /// Refreshes, clears, etc... any cache based on the information provided in the payload /// - public interface IPayloadCacheRefresher : IJsonCacheRefresher - { - /// - /// Refreshes, clears, etc... any cache based on the information provided in the payload - /// - /// - void Refresh(TPayload[] payloads); - } + /// + void Refresh(TPayload[] payloads); } diff --git a/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs index af44f2c08540..e4efb3316e00 100644 --- a/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs @@ -1,76 +1,73 @@ -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +public interface IRepositoryCachePolicy + where TEntity : class, IEntity { - public interface IRepositoryCachePolicy - where TEntity : class, IEntity - { - /// - /// Gets an entity from the cache, else from the repository. - /// - /// The identifier. - /// The repository PerformGet method. - /// The repository PerformGetAll method. - /// The entity with the specified identifier, if it exits, else null. - /// First considers the cache then the repository. - TEntity? Get(TId? id, Func performGet, Func?> performGetAll); + /// + /// Gets an entity from the cache, else from the repository. + /// + /// The identifier. + /// The repository PerformGet method. + /// The repository PerformGetAll method. + /// The entity with the specified identifier, if it exits, else null. + /// First considers the cache then the repository. + TEntity? Get(TId? id, Func performGet, Func?> performGetAll); - /// - /// Gets an entity from the cache. - /// - /// The identifier. - /// The entity with the specified identifier, if it is in the cache already, else null. - /// Does not consider the repository at all. - TEntity? GetCached(TId id); + /// + /// Gets an entity from the cache. + /// + /// The identifier. + /// The entity with the specified identifier, if it is in the cache already, else null. + /// Does not consider the repository at all. + TEntity? GetCached(TId id); - /// - /// Gets a value indicating whether an entity with a specified identifier exists. - /// - /// The identifier. - /// The repository PerformExists method. - /// The repository PerformGetAll method. - /// A value indicating whether an entity with the specified identifier exists. - /// First considers the cache then the repository. - bool Exists(TId id, Func performExists, Func?> performGetAll); + /// + /// Gets a value indicating whether an entity with a specified identifier exists. + /// + /// The identifier. + /// The repository PerformExists method. + /// The repository PerformGetAll method. + /// A value indicating whether an entity with the specified identifier exists. + /// First considers the cache then the repository. + bool Exists(TId id, Func performExists, Func?> performGetAll); - /// - /// Creates an entity. - /// - /// The entity. - /// The repository PersistNewItem method. - /// Creates the entity in the repository, and updates the cache accordingly. - void Create(TEntity entity, Action persistNew); + /// + /// Creates an entity. + /// + /// The entity. + /// The repository PersistNewItem method. + /// Creates the entity in the repository, and updates the cache accordingly. + void Create(TEntity entity, Action persistNew); - /// - /// Updates an entity. - /// - /// The entity. - /// The repository PersistUpdatedItem method. - /// Updates the entity in the repository, and updates the cache accordingly. - void Update(TEntity entity, Action persistUpdated); + /// + /// Updates an entity. + /// + /// The entity. + /// The repository PersistUpdatedItem method. + /// Updates the entity in the repository, and updates the cache accordingly. + void Update(TEntity entity, Action persistUpdated); - /// - /// Removes an entity. - /// - /// The entity. - /// The repository PersistDeletedItem method. - /// Removes the entity from the repository and clears the cache. - void Delete(TEntity entity, Action persistDeleted); + /// + /// Removes an entity. + /// + /// The entity. + /// The repository PersistDeletedItem method. + /// Removes the entity from the repository and clears the cache. + void Delete(TEntity entity, Action persistDeleted); - /// - /// Gets entities. - /// - /// The identifiers. - /// The repository PerformGetAll method. - /// If is empty, all entities, else the entities with the specified identifiers. - /// Get all the entities. Either from the cache or the repository depending on the implementation. - TEntity[] GetAll(TId[]? ids, Func> performGetAll); + /// + /// Gets entities. + /// + /// The identifiers. + /// The repository PerformGetAll method. + /// If is empty, all entities, else the entities with the specified identifiers. + /// Get all the entities. Either from the cache or the repository depending on the implementation. + TEntity[] GetAll(TId[]? ids, Func> performGetAll); - /// - /// Clears the entire cache. - /// - void ClearAll(); - } + /// + /// Clears the entire cache. + /// + void ClearAll(); } diff --git a/src/Umbraco.Core/Cache/IRequestCache.cs b/src/Umbraco.Core/Cache/IRequestCache.cs index 02f37e6ea999..50b55abd93fa 100644 --- a/src/Umbraco.Core/Cache/IRequestCache.cs +++ b/src/Umbraco.Core/Cache/IRequestCache.cs @@ -1,15 +1,12 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Cache; -namespace Umbraco.Cms.Core.Cache +public interface IRequestCache : IAppCache, IEnumerable> { - public interface IRequestCache : IAppCache, IEnumerable> - { - bool Set(string key, object? value); - bool Remove(string key); + /// + /// Returns true if the request cache is available otherwise false + /// + bool IsAvailable { get; } - /// - /// Returns true if the request cache is available otherwise false - /// - bool IsAvailable { get; } - } + bool Set(string key, object? value); + bool Remove(string key); } diff --git a/src/Umbraco.Core/Cache/IValueEditorCache.cs b/src/Umbraco.Core/Cache/IValueEditorCache.cs index f283d730b511..349631023ffb 100644 --- a/src/Umbraco.Core/Cache/IValueEditorCache.cs +++ b/src/Umbraco.Core/Cache/IValueEditorCache.cs @@ -1,12 +1,10 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +public interface IValueEditorCache { - public interface IValueEditorCache - { - public IDataValueEditor GetValueEditor(IDataEditor dataEditor, IDataType dataType); - public void ClearCache(IEnumerable dataTypeIds); - } + public IDataValueEditor GetValueEditor(IDataEditor dataEditor, IDataType dataType); + public void ClearCache(IEnumerable dataTypeIds); } diff --git a/src/Umbraco.Core/Cache/IsolatedCaches.cs b/src/Umbraco.Core/Cache/IsolatedCaches.cs index 7c273c913611..670c2e15c243 100644 --- a/src/Umbraco.Core/Cache/IsolatedCaches.cs +++ b/src/Umbraco.Core/Cache/IsolatedCaches.cs @@ -1,41 +1,41 @@ -using System; +namespace Umbraco.Cms.Core.Cache; -namespace Umbraco.Cms.Core.Cache +/// +/// Represents a dictionary of for types. +/// +/// +/// +/// Isolated caches are used by e.g. repositories, to ensure that each cached entity +/// type has its own cache, so that lookups are fast and the repository does not need to +/// search through all keys on a global scale. +/// +/// +public class IsolatedCaches : AppPolicedCacheDictionary { /// - /// Represents a dictionary of for types. + /// Initializes a new instance of the class. /// - /// - /// Isolated caches are used by e.g. repositories, to ensure that each cached entity - /// type has its own cache, so that lookups are fast and the repository does not need to - /// search through all keys on a global scale. - /// - public class IsolatedCaches : AppPolicedCacheDictionary + /// + public IsolatedCaches(Func cacheFactory) + : base(cacheFactory) { - /// - /// Initializes a new instance of the class. - /// - /// - public IsolatedCaches(Func cacheFactory) - : base(cacheFactory) - { } + } - /// - /// Gets a cache. - /// - public IAppPolicyCache GetOrCreate() - => GetOrCreate(typeof(T)); + /// + /// Gets a cache. + /// + public IAppPolicyCache GetOrCreate() + => GetOrCreate(typeof(T)); - /// - /// Tries to get a cache. - /// - public Attempt Get() - => Get(typeof(T)); + /// + /// Tries to get a cache. + /// + public Attempt Get() + => Get(typeof(T)); - /// - /// Clears a cache. - /// - public void ClearCache() - => ClearCache(typeof(T)); - } + /// + /// Clears a cache. + /// + public void ClearCache() + => ClearCache(typeof(T)); } diff --git a/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs index a6b705ae5d43..7bef322b416f 100644 --- a/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs @@ -3,58 +3,49 @@ using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +/// +/// A base class for "json" cache refreshers. +/// +/// The actual cache refresher type. +/// The actual cache refresher type is used for strongly typed events. +public abstract class JsonCacheRefresherBase : CacheRefresherBase, + IJsonCacheRefresher + where TNotification : CacheRefresherNotification { /// - /// A base class for "json" cache refreshers. + /// Initializes a new instance of the . + /// + /// A cache helper. + protected JsonCacheRefresherBase( + AppCaches appCaches, + IJsonSerializer jsonSerializer, + IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, eventAggregator, factory) => + JsonSerializer = jsonSerializer; + + protected IJsonSerializer JsonSerializer { get; } + + /// + /// Refreshes as specified by a json payload. + /// + /// The json payload. + public virtual void Refresh(string json) => + OnCacheUpdated(NotificationFactory.Create(json, MessageType.RefreshByJson)); + + #region Json + + /// + /// Deserializes a json payload into an object payload. /// - /// The actual cache refresher type. - /// The actual cache refresher type is used for strongly typed events. - public abstract class JsonCacheRefresherBase : CacheRefresherBase, IJsonCacheRefresher - where TNotification : CacheRefresherNotification - { - protected IJsonSerializer JsonSerializer { get; } - - /// - /// Initializes a new instance of the . - /// - /// A cache helper. - protected JsonCacheRefresherBase( - AppCaches appCaches, - IJsonSerializer jsonSerializer, - IEventAggregator eventAggregator, - ICacheRefresherNotificationFactory factory) - : base(appCaches, eventAggregator, factory) - { - JsonSerializer = jsonSerializer; - } - - /// - /// Refreshes as specified by a json payload. - /// - /// The json payload. - public virtual void Refresh(string json) - { - OnCacheUpdated(NotificationFactory.Create(json, MessageType.RefreshByJson)); - } - - #region Json - /// - /// Deserializes a json payload into an object payload. - /// - /// The json payload. - /// The deserialized object payload. - public TJsonPayload[]? Deserialize(string json) - { - return JsonSerializer.Deserialize(json); - } - - - public string Serialize(params TJsonPayload[] jsonPayloads) - { - return JsonSerializer.Serialize(jsonPayloads); - } - #endregion - - } + /// The json payload. + /// The deserialized object payload. + public TJsonPayload[]? Deserialize(string json) => JsonSerializer.Deserialize(json); + + + public string Serialize(params TJsonPayload[] jsonPayloads) => JsonSerializer.Serialize(jsonPayloads); + + #endregion } diff --git a/src/Umbraco.Core/Cache/LanguageCacheRefresher.cs b/src/Umbraco.Core/Cache/LanguageCacheRefresher.cs index 414c51c186ad..0fbe66c14316 100644 --- a/src/Umbraco.Core/Cache/LanguageCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/LanguageCacheRefresher.cs @@ -1,4 +1,3 @@ -using System; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; @@ -7,146 +6,152 @@ using Umbraco.Cms.Core.Services.Changes; using static Umbraco.Cms.Core.Cache.LanguageCacheRefresher.JsonPayload; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +public sealed class LanguageCacheRefresher : PayloadCacheRefresherBase { - public sealed class LanguageCacheRefresher : PayloadCacheRefresherBase + public LanguageCacheRefresher( + AppCaches appCaches, + IJsonSerializer serializer, + IPublishedSnapshotService publishedSnapshotService, + IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, serializer, eventAggregator, factory) => + _publishedSnapshotService = publishedSnapshotService; + + /// + /// Clears all domain caches + /// + private void RefreshDomains() { - public LanguageCacheRefresher( - AppCaches appCaches, - IJsonSerializer serializer, - IPublishedSnapshotService publishedSnapshotService, - IEventAggregator eventAggregator, - ICacheRefresherNotificationFactory factory) - : base(appCaches, serializer, eventAggregator, factory) - { - _publishedSnapshotService = publishedSnapshotService; - } - - #region Define - - public static readonly Guid UniqueId = Guid.Parse("3E0F95D8-0BE5-44B8-8394-2B8750B62654"); - private readonly IPublishedSnapshotService _publishedSnapshotService; + ClearAllIsolatedCacheByEntityType(); - public override Guid RefresherUniqueId => UniqueId; + // note: must do what's above FIRST else the repositories still have the old cached + // content and when the PublishedCachesService is notified of changes it does not see + // the new content... - public override string Name => "Language Cache Refresher"; - - #endregion - - #region Refresher - - public override void Refresh(JsonPayload[] payloads) + DomainCacheRefresher.JsonPayload[] payloads = new[] { - if (payloads.Length == 0) return; - - var clearDictionary = false; - var clearContent = false; - - //clear all no matter what type of payload - ClearAllIsolatedCacheByEntityType(); - - foreach (var payload in payloads) - { - switch (payload.ChangeType) - { - case LanguageChangeType.Update: - clearDictionary = true; - break; - case LanguageChangeType.Remove: - case LanguageChangeType.ChangeCulture: - clearDictionary = true; - clearContent = true; - break; - } - } + new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll) + }; + _publishedSnapshotService.Notify(payloads); + } - if (clearDictionary) - { - ClearAllIsolatedCacheByEntityType(); - } + #region Json - //if this flag is set, we will tell the published snapshot service to refresh ALL content and evict ALL IContent items - if (clearContent) - { - //clear all domain caches - RefreshDomains(); - ContentCacheRefresher.RefreshContentTypes(AppCaches); // we need to evict all IContent items - //now refresh all nucache - var clearContentPayload = new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }; - ContentCacheRefresher.NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, clearContentPayload); - } + public class JsonPayload + { + public enum LanguageChangeType + { + /// + /// A new languages has been added + /// + Add = 0, + + /// + /// A language has been deleted + /// + Remove = 1, + + /// + /// A language has been updated - but it's culture remains the same + /// + Update = 2, + + /// + /// A language has been updated - it's culture has changed + /// + ChangeCulture = 3 + } - // then trigger event - base.Refresh(payloads); + public JsonPayload(int id, string isoCode, LanguageChangeType changeType) + { + Id = id; + IsoCode = isoCode; + ChangeType = changeType; } - // these events should never trigger - // everything should be PAYLOAD/JSON + public int Id { get; } + public string IsoCode { get; } + public LanguageChangeType ChangeType { get; } + } - public override void RefreshAll() => throw new NotSupportedException(); + #endregion - public override void Refresh(int id) => throw new NotSupportedException(); + #region Define - public override void Refresh(Guid id) => throw new NotSupportedException(); + public static readonly Guid UniqueId = Guid.Parse("3E0F95D8-0BE5-44B8-8394-2B8750B62654"); + private readonly IPublishedSnapshotService _publishedSnapshotService; - public override void Remove(int id) => throw new NotSupportedException(); + public override Guid RefresherUniqueId => UniqueId; - #endregion + public override string Name => "Language Cache Refresher"; - /// - /// Clears all domain caches - /// - private void RefreshDomains() - { - ClearAllIsolatedCacheByEntityType(); + #endregion - // note: must do what's above FIRST else the repositories still have the old cached - // content and when the PublishedCachesService is notified of changes it does not see - // the new content... + #region Refresher - var payloads = new[] { new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll) }; - _publishedSnapshotService.Notify(payloads); + public override void Refresh(JsonPayload[] payloads) + { + if (payloads.Length == 0) + { + return; } - #region Json + var clearDictionary = false; + var clearContent = false; + + //clear all no matter what type of payload + ClearAllIsolatedCacheByEntityType(); - public class JsonPayload + foreach (JsonPayload payload in payloads) { - public JsonPayload(int id, string isoCode, LanguageChangeType changeType) + switch (payload.ChangeType) { - Id = id; - IsoCode = isoCode; - ChangeType = changeType; + case LanguageChangeType.Update: + clearDictionary = true; + break; + case LanguageChangeType.Remove: + case LanguageChangeType.ChangeCulture: + clearDictionary = true; + clearContent = true; + break; } + } - public int Id { get; } - public string IsoCode { get; } - public LanguageChangeType ChangeType { get; } + if (clearDictionary) + { + ClearAllIsolatedCacheByEntityType(); + } - public enum LanguageChangeType - { - /// - /// A new languages has been added - /// - Add = 0, - - /// - /// A language has been deleted - /// - Remove = 1, - - /// - /// A language has been updated - but it's culture remains the same - /// - Update = 2, - - /// - /// A language has been updated - it's culture has changed - /// - ChangeCulture = 3 - } + //if this flag is set, we will tell the published snapshot service to refresh ALL content and evict ALL IContent items + if (clearContent) + { + //clear all domain caches + RefreshDomains(); + ContentCacheRefresher.RefreshContentTypes(AppCaches); // we need to evict all IContent items + //now refresh all nucache + ContentCacheRefresher.JsonPayload[] clearContentPayload = + new[] {new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll)}; + ContentCacheRefresher.NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, + clearContentPayload); } - #endregion + // then trigger event + base.Refresh(payloads); } + + // these events should never trigger + // everything should be PAYLOAD/JSON + + public override void RefreshAll() => throw new NotSupportedException(); + + public override void Refresh(int id) => throw new NotSupportedException(); + + public override void Refresh(Guid id) => throw new NotSupportedException(); + + public override void Remove(int id) => throw new NotSupportedException(); + + #endregion } diff --git a/src/Umbraco.Core/Cache/MacroCacheRefresher.cs b/src/Umbraco.Core/Cache/MacroCacheRefresher.cs index 8f49ce134ce5..c76624034b8f 100644 --- a/src/Umbraco.Core/Cache/MacroCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MacroCacheRefresher.cs @@ -1,112 +1,109 @@ -using System; -using System.Linq; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +public sealed class + MacroCacheRefresher : PayloadCacheRefresherBase { - public sealed class MacroCacheRefresher : PayloadCacheRefresherBase + public MacroCacheRefresher( + AppCaches appCaches, + IJsonSerializer jsonSerializer, + IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, jsonSerializer, eventAggregator, factory) { - public MacroCacheRefresher( - AppCaches appCaches, - IJsonSerializer jsonSerializer, - IEventAggregator eventAggregator, - ICacheRefresherNotificationFactory factory) - : base(appCaches, jsonSerializer, eventAggregator, factory) - { + } + + #region Json + public class JsonPayload + { + public JsonPayload(int id, string alias) + { + Id = id; + Alias = alias; } - #region Define + public int Id { get; } - public static readonly Guid UniqueId = Guid.Parse("7B1E683C-5F34-43dd-803D-9699EA1E98CA"); + public string Alias { get; } + } - public override Guid RefresherUniqueId => UniqueId; + #endregion - public override string Name => "Macro Cache Refresher"; + #region Define - #endregion + public static readonly Guid UniqueId = Guid.Parse("7B1E683C-5F34-43dd-803D-9699EA1E98CA"); - #region Refresher + public override Guid RefresherUniqueId => UniqueId; - public override void RefreshAll() - { - foreach (var prefix in GetAllMacroCacheKeys()) - AppCaches.RuntimeCache.ClearByKey(prefix); + public override string Name => "Macro Cache Refresher"; - ClearAllIsolatedCacheByEntityType(); + #endregion - base.RefreshAll(); - } + #region Refresher - public override void Refresh(string json) + public override void RefreshAll() + { + foreach (var prefix in GetAllMacroCacheKeys()) { - var payloads = Deserialize(json); - - if (payloads is not null) - { - Refresh(payloads); - } + AppCaches.RuntimeCache.ClearByKey(prefix); } - public override void Refresh(JsonPayload[] payloads) - { - foreach (var payload in payloads) - { - foreach (var alias in GetCacheKeysForAlias(payload.Alias)) - { - AppCaches.RuntimeCache.ClearByKey(alias); - } - - Attempt macroRepoCache = AppCaches.IsolatedCaches.Get(); - if (macroRepoCache) - { - macroRepoCache.Result?.Clear(RepositoryCacheKeys.GetKey(payload.Id)); - macroRepoCache.Result?.Clear(RepositoryCacheKeys.GetKey(payload.Alias)); // Repository caching of macro definition by alias - } - } + ClearAllIsolatedCacheByEntityType(); - base.Refresh(payloads); - } + base.RefreshAll(); + } - #endregion + public override void Refresh(string json) + { + JsonPayload[] payloads = Deserialize(json); - #region Json + if (payloads is not null) + { + Refresh(payloads); + } + } - public class JsonPayload + public override void Refresh(JsonPayload[] payloads) + { + foreach (JsonPayload payload in payloads) { - public JsonPayload(int id, string alias) + foreach (var alias in GetCacheKeysForAlias(payload.Alias)) { - Id = id; - Alias = alias; + AppCaches.RuntimeCache.ClearByKey(alias); } - public int Id { get; } - - public string Alias { get; } + Attempt macroRepoCache = AppCaches.IsolatedCaches.Get(); + if (macroRepoCache) + { + macroRepoCache.Result?.Clear(RepositoryCacheKeys.GetKey(payload.Id)); + macroRepoCache.Result?.Clear( + RepositoryCacheKeys + .GetKey(payload.Alias)); // Repository caching of macro definition by alias + } } - #endregion + base.Refresh(payloads); + } - #region Helpers + #endregion - internal static string[] GetAllMacroCacheKeys() - { - return new[] - { - CacheKeys.MacroContentCacheKey, // macro render cache - CacheKeys.MacroFromAliasCacheKey, // lookup macro by alias - }; - } + #region Helpers - internal static string[] GetCacheKeysForAlias(string alias) + internal static string[] GetAllMacroCacheKeys() => + new[] { - return GetAllMacroCacheKeys().Select(x => x + alias).ToArray(); - } + CacheKeys.MacroContentCacheKey, // macro render cache + CacheKeys.MacroFromAliasCacheKey // lookup macro by alias + }; - #endregion - } + internal static string[] GetCacheKeysForAlias(string alias) => + GetAllMacroCacheKeys().Select(x => x + alias).ToArray(); + + #endregion } diff --git a/src/Umbraco.Core/Cache/MediaCacheRefresher.cs b/src/Umbraco.Core/Cache/MediaCacheRefresher.cs index 2efd23d71f4b..8aba64ceb2d9 100644 --- a/src/Umbraco.Core/Cache/MediaCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MediaCacheRefresher.cs @@ -1,4 +1,3 @@ -using System; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; @@ -9,120 +8,115 @@ using Umbraco.Cms.Core.Services.Changes; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +public sealed class + MediaCacheRefresher : PayloadCacheRefresherBase { - public sealed class MediaCacheRefresher : PayloadCacheRefresherBase - { - private readonly IPublishedSnapshotService _publishedSnapshotService; - private readonly IIdKeyMap _idKeyMap; + private readonly IIdKeyMap _idKeyMap; + private readonly IPublishedSnapshotService _publishedSnapshotService; - public MediaCacheRefresher(AppCaches appCaches, IJsonSerializer serializer, IPublishedSnapshotService publishedSnapshotService, IIdKeyMap idKeyMap, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) - : base(appCaches, serializer, eventAggregator, factory) - { - _publishedSnapshotService = publishedSnapshotService; - _idKeyMap = idKeyMap; - } + public MediaCacheRefresher(AppCaches appCaches, IJsonSerializer serializer, + IPublishedSnapshotService publishedSnapshotService, IIdKeyMap idKeyMap, IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, serializer, eventAggregator, factory) + { + _publishedSnapshotService = publishedSnapshotService; + _idKeyMap = idKeyMap; + } - #region Define + #region Indirect - public static readonly Guid UniqueId = Guid.Parse("B29286DD-2D40-4DDB-B325-681226589FEC"); + public static void RefreshMediaTypes(AppCaches appCaches) => appCaches.IsolatedCaches.ClearCache(); - public override Guid RefresherUniqueId => UniqueId; + #endregion - public override string Name => "Media Cache Refresher"; + #region Json - #endregion + public class JsonPayload + { + public JsonPayload(int id, Guid? key, TreeChangeTypes changeTypes) + { + Id = id; + Key = key; + ChangeTypes = changeTypes; + } - #region Refresher + public int Id { get; } + public Guid? Key { get; } + public TreeChangeTypes ChangeTypes { get; } + } - public override void Refresh(JsonPayload[] payloads) - { - if (payloads == null) return; + #endregion - _publishedSnapshotService.Notify(payloads, out var anythingChanged); + #region Define - if (anythingChanged) - { - AppCaches.ClearPartialViewCache(); - AppCaches.RuntimeCache.ClearByKey(CacheKeys.MediaRecycleBinCacheKey); + public static readonly Guid UniqueId = Guid.Parse("B29286DD-2D40-4DDB-B325-681226589FEC"); - var mediaCache = AppCaches.IsolatedCaches.Get(); + public override Guid RefresherUniqueId => UniqueId; - foreach (var payload in payloads) - { - if (payload.ChangeTypes == TreeChangeTypes.Remove) - _idKeyMap.ClearCache(payload.Id); - - if (!mediaCache.Success) continue; - - // repository cache - // it *was* done for each pathId but really that does not make sense - // only need to do it for the current media - mediaCache.Result?.Clear(RepositoryCacheKeys.GetKey(payload.Id)); - mediaCache.Result?.Clear(RepositoryCacheKeys.GetKey(payload.Key)); - - // remove those that are in the branch - if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.RefreshBranch | TreeChangeTypes.Remove)) - { - var pathid = "," + payload.Id + ","; - mediaCache.Result?.ClearOfType((_, v) => v.Path?.Contains(pathid) ?? false); - } - } - } + public override string Name => "Media Cache Refresher"; - base.Refresh(payloads); - } + #endregion - // these events should never trigger - // everything should be JSON + #region Refresher - public override void RefreshAll() + public override void Refresh(JsonPayload[] payloads) + { + if (payloads == null) { - throw new NotSupportedException(); + return; } - public override void Refresh(int id) - { - throw new NotSupportedException(); - } + _publishedSnapshotService.Notify(payloads, out var anythingChanged); - public override void Refresh(Guid id) + if (anythingChanged) { - throw new NotSupportedException(); - } + AppCaches.ClearPartialViewCache(); + AppCaches.RuntimeCache.ClearByKey(CacheKeys.MediaRecycleBinCacheKey); - public override void Remove(int id) - { - throw new NotSupportedException(); - } + Attempt mediaCache = AppCaches.IsolatedCaches.Get(); - #endregion + foreach (JsonPayload payload in payloads) + { + if (payload.ChangeTypes == TreeChangeTypes.Remove) + { + _idKeyMap.ClearCache(payload.Id); + } - #region Json + if (!mediaCache.Success) + { + continue; + } - public class JsonPayload - { - public JsonPayload(int id, Guid? key, TreeChangeTypes changeTypes) - { - Id = id; - Key = key; - ChangeTypes = changeTypes; - } + // repository cache + // it *was* done for each pathId but really that does not make sense + // only need to do it for the current media + mediaCache.Result?.Clear(RepositoryCacheKeys.GetKey(payload.Id)); + mediaCache.Result?.Clear(RepositoryCacheKeys.GetKey(payload.Key)); - public int Id { get; } - public Guid? Key { get; } - public TreeChangeTypes ChangeTypes { get; } + // remove those that are in the branch + if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.RefreshBranch | TreeChangeTypes.Remove)) + { + var pathid = "," + payload.Id + ","; + mediaCache.Result?.ClearOfType((_, v) => v.Path?.Contains(pathid) ?? false); + } + } } - #endregion + base.Refresh(payloads); + } - #region Indirect + // these events should never trigger + // everything should be JSON - public static void RefreshMediaTypes(AppCaches appCaches) - { - appCaches.IsolatedCaches.ClearCache(); - } + public override void RefreshAll() => throw new NotSupportedException(); - #endregion - } + public override void Refresh(int id) => throw new NotSupportedException(); + + public override void Refresh(Guid id) => throw new NotSupportedException(); + + public override void Remove(int id) => throw new NotSupportedException(); + + #endregion } diff --git a/src/Umbraco.Core/Cache/MemberCacheRefresher.cs b/src/Umbraco.Core/Cache/MemberCacheRefresher.cs index 9869f226b9ed..1673889b6a7a 100644 --- a/src/Umbraco.Core/Cache/MemberCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MemberCacheRefresher.cs @@ -1,6 +1,5 @@ //using Newtonsoft.Json; -using System; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; @@ -9,89 +8,84 @@ using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +public sealed class + MemberCacheRefresher : PayloadCacheRefresherBase { - public sealed class MemberCacheRefresher : PayloadCacheRefresherBase - { - private readonly IIdKeyMap _idKeyMap; + private readonly IIdKeyMap _idKeyMap; - public MemberCacheRefresher(AppCaches appCaches, IJsonSerializer serializer, IIdKeyMap idKeyMap, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) - : base(appCaches, serializer, eventAggregator, factory) - { - _idKeyMap = idKeyMap; - } + public MemberCacheRefresher(AppCaches appCaches, IJsonSerializer serializer, IIdKeyMap idKeyMap, + IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) + : base(appCaches, serializer, eventAggregator, factory) => + _idKeyMap = idKeyMap; - public class JsonPayload - { - //[JsonConstructor] - public JsonPayload(int id, string? username, bool removed) - { - Id = id; - Username = username; - Removed = removed; - } - - public int Id { get; } - public string? Username { get; } - public bool Removed { get; } - } + #region Indirect - #region Define + public static void RefreshMemberTypes(AppCaches appCaches) => appCaches.IsolatedCaches.ClearCache(); - public static readonly Guid UniqueId = Guid.Parse("E285DF34-ACDC-4226-AE32-C0CB5CF388DA"); + #endregion - public override Guid RefresherUniqueId => UniqueId; + public class JsonPayload + { + //[JsonConstructor] + public JsonPayload(int id, string? username, bool removed) + { + Id = id; + Username = username; + Removed = removed; + } - public override string Name => "Member Cache Refresher"; + public int Id { get; } + public string? Username { get; } + public bool Removed { get; } + } - #endregion + #region Define - #region Refresher + public static readonly Guid UniqueId = Guid.Parse("E285DF34-ACDC-4226-AE32-C0CB5CF388DA"); - public override void Refresh(JsonPayload[] payloads) - { - ClearCache(payloads); - base.Refresh(payloads); - } + public override Guid RefresherUniqueId => UniqueId; - public override void Refresh(int id) - { - ClearCache(new JsonPayload(id, null, false)); - base.Refresh(id); - } + public override string Name => "Member Cache Refresher"; - public override void Remove(int id) - { - ClearCache(new JsonPayload(id, null, false)); - base.Remove(id); - } + #endregion - private void ClearCache(params JsonPayload[] payloads) - { - AppCaches.ClearPartialViewCache(); - var memberCache = AppCaches.IsolatedCaches.Get(); + #region Refresher - foreach (var p in payloads) - { - _idKeyMap.ClearCache(p.Id); - if (memberCache.Success) - { - memberCache.Result?.Clear(RepositoryCacheKeys.GetKey(p.Id)); - memberCache.Result?.Clear(RepositoryCacheKeys.GetKey(p.Username)); - } - } + public override void Refresh(JsonPayload[] payloads) + { + ClearCache(payloads); + base.Refresh(payloads); + } - } + public override void Refresh(int id) + { + ClearCache(new JsonPayload(id, null, false)); + base.Refresh(id); + } - #endregion + public override void Remove(int id) + { + ClearCache(new JsonPayload(id, null, false)); + base.Remove(id); + } - #region Indirect + private void ClearCache(params JsonPayload[] payloads) + { + AppCaches.ClearPartialViewCache(); + Attempt memberCache = AppCaches.IsolatedCaches.Get(); - public static void RefreshMemberTypes(AppCaches appCaches) + foreach (JsonPayload p in payloads) { - appCaches.IsolatedCaches.ClearCache(); + _idKeyMap.ClearCache(p.Id); + if (memberCache.Success) + { + memberCache.Result?.Clear(RepositoryCacheKeys.GetKey(p.Id)); + memberCache.Result?.Clear(RepositoryCacheKeys.GetKey(p.Username)); + } } - - #endregion } + + #endregion } diff --git a/src/Umbraco.Core/Cache/MemberGroupCacheRefresher.cs b/src/Umbraco.Core/Cache/MemberGroupCacheRefresher.cs index 0866f7b39aff..af551aa070af 100644 --- a/src/Umbraco.Core/Cache/MemberGroupCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MemberGroupCacheRefresher.cs @@ -1,74 +1,70 @@ -using System; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +public sealed class MemberGroupCacheRefresher : PayloadCacheRefresherBase { - public sealed class MemberGroupCacheRefresher : PayloadCacheRefresherBase + public MemberGroupCacheRefresher(AppCaches appCaches, IJsonSerializer jsonSerializer, + IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) + : base(appCaches, jsonSerializer, eventAggregator, factory) { - public MemberGroupCacheRefresher(AppCaches appCaches, IJsonSerializer jsonSerializer, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) - : base(appCaches, jsonSerializer, eventAggregator, factory) - { - - } + } - #region Define + #region Json - public static readonly Guid UniqueId = Guid.Parse("187F236B-BD21-4C85-8A7C-29FBA3D6C00C"); + public class JsonPayload + { + public JsonPayload(int id, string name) + { + Id = id; + Name = name; + } - public override Guid RefresherUniqueId => UniqueId; + public string Name { get; } + public int Id { get; } + } - public override string Name => "Member Group Cache Refresher"; + #endregion - #endregion + #region Define - #region Refresher + public static readonly Guid UniqueId = Guid.Parse("187F236B-BD21-4C85-8A7C-29FBA3D6C00C"); - public override void Refresh(string json) - { - ClearCache(); - base.Refresh(json); - } + public override Guid RefresherUniqueId => UniqueId; - public override void Refresh(int id) - { - ClearCache(); - base.Refresh(id); - } + public override string Name => "Member Group Cache Refresher"; - public override void Remove(int id) - { - ClearCache(); - base.Remove(id); - } + #endregion - private void ClearCache() - { - // Since we cache by group name, it could be problematic when renaming to - // previously existing names - see http://issues.umbraco.org/issue/U4-10846. - // To work around this, just clear all the cache items - AppCaches.IsolatedCaches.ClearCache(); - } + #region Refresher - #endregion + public override void Refresh(string json) + { + ClearCache(); + base.Refresh(json); + } - #region Json + public override void Refresh(int id) + { + ClearCache(); + base.Refresh(id); + } - public class JsonPayload - { - public JsonPayload(int id, string name) - { - Id = id; - Name = name; - } - - public string Name { get; } - public int Id { get; } - } + public override void Remove(int id) + { + ClearCache(); + base.Remove(id); + } + private void ClearCache() => + // Since we cache by group name, it could be problematic when renaming to + // previously existing names - see http://issues.umbraco.org/issue/U4-10846. + // To work around this, just clear all the cache items + AppCaches.IsolatedCaches.ClearCache(); - #endregion - } + #endregion } diff --git a/src/Umbraco.Core/Cache/NoAppCache.cs b/src/Umbraco.Core/Cache/NoAppCache.cs index ef22a51ab091..1796b50a38f2 100644 --- a/src/Umbraco.Core/Cache/NoAppCache.cs +++ b/src/Umbraco.Core/Cache/NoAppCache.cs @@ -1,93 +1,85 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; +using System.Collections; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +/// +/// Implements and do not cache. +/// +public class NoAppCache : IAppPolicyCache, IRequestCache { + protected NoAppCache() { } + /// - /// Implements and do not cache. + /// Gets the singleton instance. /// - public class NoAppCache : IAppPolicyCache, IRequestCache - { - protected NoAppCache() { } + public static NoAppCache Instance { get; } = new(); - /// - /// Gets the singleton instance. - /// - public static NoAppCache Instance { get; } = new NoAppCache(); + /// + public virtual object? Get(string cacheKey) => null; - /// - public bool IsAvailable => false; + /// + public virtual object? Get(string cacheKey, Func factory) => factory(); - /// - public virtual object? Get(string cacheKey) - { - return null; - } + /// + public virtual IEnumerable SearchByKey(string keyStartsWith) => Enumerable.Empty(); - /// - public virtual object? Get(string cacheKey, Func factory) - { - return factory(); - } + /// + public IEnumerable SearchByRegex(string regex) => Enumerable.Empty(); - public bool Set(string key, object? value) => false; + /// + public object? Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, + string[]? dependentFiles = null) => factory(); - public bool Remove(string key) => false; - - /// - public virtual IEnumerable SearchByKey(string keyStartsWith) - { - return Enumerable.Empty(); - } + /// + public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, + string[]? dependentFiles = null) + { + } - /// - public IEnumerable SearchByRegex(string regex) - { - return Enumerable.Empty(); - } + /// + public virtual void Clear() + { + } - /// - public object? Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, string[]? dependentFiles = null) - { - return factory(); - } + /// + public virtual void Clear(string key) + { + } - /// - public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, string[]? dependentFiles = null) - { } + /// + public virtual void ClearOfType(Type type) + { + } - /// - public virtual void Clear() - { } + /// + public virtual void ClearOfType() + { + } - /// - public virtual void Clear(string key) - { } + /// + public virtual void ClearOfType(Func predicate) + { + } - /// - public virtual void ClearOfType(Type type) - { } + /// + public virtual void ClearByKey(string keyStartsWith) + { + } - /// - public virtual void ClearOfType() - { } + /// + public virtual void ClearByRegex(string regex) + { + } - /// - public virtual void ClearOfType(Func predicate) - { } + /// + public bool IsAvailable => false; - /// - public virtual void ClearByKey(string keyStartsWith) - { } + public bool Set(string key, object? value) => false; - /// - public virtual void ClearByRegex(string regex) - { } + public bool Remove(string key) => false; - public IEnumerator> GetEnumerator() => new Dictionary().GetEnumerator(); + public IEnumerator> GetEnumerator() => + new Dictionary().GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs index b99975e0e481..430bee607e10 100644 --- a/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs @@ -1,53 +1,32 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +public class NoCacheRepositoryCachePolicy : IRepositoryCachePolicy + where TEntity : class, IEntity { - public class NoCacheRepositoryCachePolicy : IRepositoryCachePolicy - where TEntity : class, IEntity + private NoCacheRepositoryCachePolicy() { } + + public static NoCacheRepositoryCachePolicy Instance { get; } = new(); + + public TEntity? Get(TId? id, Func performGet, Func?> performGetAll) => + performGet(id); + + public TEntity? GetCached(TId id) => null; + + public bool Exists(TId id, Func performExists, Func?> performGetAll) => + performExists(id); + + public void Create(TEntity entity, Action persistNew) => persistNew(entity); + + public void Update(TEntity entity, Action persistUpdated) => persistUpdated(entity); + + public void Delete(TEntity entity, Action persistDeleted) => persistDeleted(entity); + + public TEntity[] GetAll(TId[]? ids, Func?> performGetAll) => + performGetAll(ids)?.ToArray() ?? Array.Empty(); + + public void ClearAll() { - private NoCacheRepositoryCachePolicy() { } - - public static NoCacheRepositoryCachePolicy Instance { get; } = new NoCacheRepositoryCachePolicy(); - - public TEntity? Get(TId? id, Func performGet, Func?> performGetAll) - { - return performGet(id); - } - - public TEntity? GetCached(TId id) - { - return null; - } - - public bool Exists(TId id, Func performExists, Func?> performGetAll) - { - return performExists(id); - } - - public void Create(TEntity entity, Action persistNew) - { - persistNew(entity); - } - - public void Update(TEntity entity, Action persistUpdated) - { - persistUpdated(entity); - } - - public void Delete(TEntity entity, Action persistDeleted) - { - persistDeleted(entity); - } - - public TEntity[] GetAll(TId[]? ids, Func?> performGetAll) - { - return performGetAll(ids)?.ToArray() ?? Array.Empty(); - } - - public void ClearAll() - { } } } diff --git a/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs b/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs index 4ec91c4933ea..b6cb3798e1bd 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs @@ -1,367 +1,415 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.Caching; using System.Text.RegularExpressions; -using System.Threading; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +/// +/// Implements on top of a . +/// +public class ObjectCacheAppCache : IAppPolicyCache, IDisposable { + private readonly ReaderWriterLockSlim _locker = new(LockRecursionPolicy.SupportsRecursion); + private bool _disposedValue; + /// - /// Implements on top of a . + /// Initializes a new instance of the . /// - public class ObjectCacheAppCache : IAppPolicyCache, IDisposable - { - private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); - private bool _disposedValue; + public ObjectCacheAppCache() => + // the MemoryCache is created with name "in-memory". That name is + // used to retrieve configuration options. It does not identify the memory cache, i.e. + // each instance of this class has its own, independent, memory cache. + MemoryCache = new MemoryCache("in-memory"); - /// - /// Initializes a new instance of the . - /// - public ObjectCacheAppCache() + /// + /// Gets the internal memory cache, for tests only! + /// + public ObjectCache MemoryCache { get; private set; } + + /// + public object? Get(string key) + { + Lazy? result; + try { - // the MemoryCache is created with name "in-memory". That name is - // used to retrieve configuration options. It does not identify the memory cache, i.e. - // each instance of this class has its own, independent, memory cache. - MemoryCache = new MemoryCache("in-memory"); + _locker.EnterReadLock(); + result = MemoryCache.Get(key) as Lazy; // null if key not found } - - /// - /// Gets the internal memory cache, for tests only! - /// - public ObjectCache MemoryCache { get; private set; } - - /// - public object? Get(string key) + finally { - Lazy? result; - try + if (_locker.IsReadLockHeld) { - _locker.EnterReadLock(); - result = MemoryCache.Get(key) as Lazy; // null if key not found + _locker.ExitReadLock(); } - finally - { - if (_locker.IsReadLockHeld) - _locker.ExitReadLock(); - } - return result == null ? null : SafeLazy.GetSafeLazyValue(result); // return exceptions as null } - /// - public object? Get(string key, Func factory) + return result == null ? null : SafeLazy.GetSafeLazyValue(result); // return exceptions as null + } + + /// + public object? Get(string key, Func factory) => Get(key, factory, null); + + /// + public IEnumerable SearchByKey(string keyStartsWith) + { + KeyValuePair[] entries; + try { - return Get(key, factory, null); + _locker.EnterReadLock(); + entries = MemoryCache + .Where(x => x.Key.InvariantStartsWith(keyStartsWith)) + .ToArray(); // evaluate while locked } - - /// - public IEnumerable SearchByKey(string keyStartsWith) + finally { - KeyValuePair[] entries; - try + if (_locker.IsReadLockHeld) { - _locker.EnterReadLock(); - entries = MemoryCache - .Where(x => x.Key.InvariantStartsWith(keyStartsWith)) - .ToArray(); // evaluate while locked + _locker.ExitReadLock(); } - finally - { - if (_locker.IsReadLockHeld) - _locker.ExitReadLock(); - } - return entries - .Select(x => SafeLazy.GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null - .Where(x => x != null) // backward compat, don't store null values in the cache - .ToList()!; } - /// - public IEnumerable SearchByRegex(string regex) - { - var compiled = new Regex(regex, RegexOptions.Compiled); + return entries + .Select(x => SafeLazy.GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null + .Where(x => x != null) // backward compat, don't store null values in the cache + .ToList()!; + } - KeyValuePair[] entries; - try - { - _locker.EnterReadLock(); - entries = MemoryCache - .Where(x => compiled.IsMatch(x.Key)) - .ToArray(); // evaluate while locked - } - finally + /// + public IEnumerable SearchByRegex(string regex) + { + var compiled = new Regex(regex, RegexOptions.Compiled); + + KeyValuePair[] entries; + try + { + _locker.EnterReadLock(); + entries = MemoryCache + .Where(x => compiled.IsMatch(x.Key)) + .ToArray(); // evaluate while locked + } + finally + { + if (_locker.IsReadLockHeld) { - if (_locker.IsReadLockHeld) - _locker.ExitReadLock(); + _locker.ExitReadLock(); } - return entries - .Select(x => SafeLazy.GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null - .Where(x => x != null) // backward compat, don't store null values in the cache - .ToList()!; } - /// - public object? Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, string[]? dependentFiles = null) - { - // see notes in HttpRuntimeAppCache + return entries + .Select(x => SafeLazy.GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null + .Where(x => x != null) // backward compat, don't store null values in the cache + .ToList()!; + } - Lazy? result; + /// + public object? Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, + string[]? dependentFiles = null) + { + // see notes in HttpRuntimeAppCache + + Lazy? result; + + try + { + _locker.EnterUpgradeableReadLock(); - try + result = MemoryCache.Get(key) as Lazy; + if (result == null || + SafeLazy.GetSafeLazyValue(result, true) == + null) // get non-created as NonCreatedValue & exceptions as null { - _locker.EnterUpgradeableReadLock(); + result = SafeLazy.GetSafeLazy(factory); + CacheItemPolicy policy = GetPolicy(timeout, isSliding, dependentFiles); - result = MemoryCache.Get(key) as Lazy; - if (result == null || SafeLazy.GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null + try { - result = SafeLazy.GetSafeLazy(factory); - var policy = GetPolicy(timeout, isSliding, dependentFiles); - - try - { - _locker.EnterWriteLock(); - //NOTE: This does an add or update - MemoryCache.Set(key, result, policy); - } - finally + _locker.EnterWriteLock(); + //NOTE: This does an add or update + MemoryCache.Set(key, result, policy); + } + finally + { + if (_locker.IsWriteLockHeld) { - if (_locker.IsWriteLockHeld) - _locker.ExitWriteLock(); + _locker.ExitWriteLock(); } } } - finally + } + finally + { + if (_locker.IsUpgradeableReadLockHeld) { - if (_locker.IsUpgradeableReadLockHeld) - _locker.ExitUpgradeableReadLock(); + _locker.ExitUpgradeableReadLock(); } + } - //return result.Value; + //return result.Value; - var value = result.Value; // will not throw (safe lazy) - if (value is SafeLazy.ExceptionHolder eh) eh.Exception.Throw(); // throw once! - return value; + var value = result.Value; // will not throw (safe lazy) + if (value is SafeLazy.ExceptionHolder eh) + { + eh.Exception.Throw(); // throw once! } - /// - public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, string[]? dependentFiles = null) - { - // NOTE - here also we must insert a Lazy but we can evaluate it right now - // and make sure we don't store a null value. + return value; + } - var result = SafeLazy.GetSafeLazy(factory); - var value = result.Value; // force evaluation now - if (value == null) return; // do not store null values (backward compat) + /// + public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, + string[]? dependentFiles = null) + { + // NOTE - here also we must insert a Lazy but we can evaluate it right now + // and make sure we don't store a null value. - var policy = GetPolicy(timeout, isSliding, dependentFiles); - //NOTE: This does an add or update - MemoryCache.Set(key, result, policy); + Lazy result = SafeLazy.GetSafeLazy(factory); + var value = result.Value; // force evaluation now + if (value == null) + { + return; // do not store null values (backward compat) } - /// - public virtual void Clear() + CacheItemPolicy policy = GetPolicy(timeout, isSliding, dependentFiles); + //NOTE: This does an add or update + MemoryCache.Set(key, result, policy); + } + + /// + public virtual void Clear() + { + try { - try - { - _locker.EnterWriteLock(); - MemoryCache.DisposeIfDisposable(); - MemoryCache = new MemoryCache("in-memory"); - } - finally + _locker.EnterWriteLock(); + MemoryCache.DisposeIfDisposable(); + MemoryCache = new MemoryCache("in-memory"); + } + finally + { + if (_locker.IsWriteLockHeld) { - if (_locker.IsWriteLockHeld) - _locker.ExitWriteLock(); + _locker.ExitWriteLock(); } } + } - /// - public virtual void Clear(string key) + /// + public virtual void Clear(string key) + { + try { - try + _locker.EnterWriteLock(); + if (MemoryCache[key] == null) { - _locker.EnterWriteLock(); - if (MemoryCache[key] == null) return; - MemoryCache.Remove(key); + return; } - finally + + MemoryCache.Remove(key); + } + finally + { + if (_locker.IsWriteLockHeld) { - if (_locker.IsWriteLockHeld) - _locker.ExitWriteLock(); + _locker.ExitWriteLock(); } } + } - /// - public virtual void ClearOfType(Type type) + /// + public virtual void ClearOfType(Type type) + { + if (type == null) { - if (type == null) return; - var isInterface = type.IsInterface; - try + return; + } + + var isInterface = type.IsInterface; + try + { + _locker.EnterWriteLock(); + foreach (var key in MemoryCache + .Where(x => + { + // x.Value is Lazy and not null, its value may be null + // remove null values as well, does not hurt + // get non-created as NonCreatedValue & exceptions as null + var value = SafeLazy.GetSafeLazyValue((Lazy)x.Value, true); + + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return value == null || + (isInterface ? type.IsInstanceOfType(value) : value.GetType() == type); + }) + .Select(x => x.Key) + .ToArray()) // ToArray required to remove { - _locker.EnterWriteLock(); - foreach (var key in MemoryCache - .Where(x => - { - // x.Value is Lazy and not null, its value may be null - // remove null values as well, does not hurt - // get non-created as NonCreatedValue & exceptions as null - var value = SafeLazy.GetSafeLazyValue((Lazy)x.Value, true); - - // if T is an interface remove anything that implements that interface - // otherwise remove exact types (not inherited types) - return value == null || (isInterface ? (type.IsInstanceOfType(value)) : (value.GetType() == type)); - }) - .Select(x => x.Key) - .ToArray()) // ToArray required to remove - MemoryCache.Remove(key); + MemoryCache.Remove(key); } - finally + } + finally + { + if (_locker.IsWriteLockHeld) { - if (_locker.IsWriteLockHeld) - _locker.ExitWriteLock(); + _locker.ExitWriteLock(); } } + } - /// - public virtual void ClearOfType() + /// + public virtual void ClearOfType() + { + try { - try + _locker.EnterWriteLock(); + Type typeOfT = typeof(T); + var isInterface = typeOfT.IsInterface; + foreach (var key in MemoryCache + .Where(x => + { + // x.Value is Lazy and not null, its value may be null + // remove null values as well, does not hurt + // get non-created as NonCreatedValue & exceptions as null + var value = SafeLazy.GetSafeLazyValue((Lazy)x.Value, true); + + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return value == null || (isInterface ? value is T : value.GetType() == typeOfT); + }) + .Select(x => x.Key) + .ToArray()) // ToArray required to remove { - _locker.EnterWriteLock(); - var typeOfT = typeof(T); - var isInterface = typeOfT.IsInterface; - foreach (var key in MemoryCache - .Where(x => - { - // x.Value is Lazy and not null, its value may be null - // remove null values as well, does not hurt - // get non-created as NonCreatedValue & exceptions as null - var value = SafeLazy.GetSafeLazyValue((Lazy)x.Value, true); - - // if T is an interface remove anything that implements that interface - // otherwise remove exact types (not inherited types) - return value == null || (isInterface ? (value is T) : (value.GetType() == typeOfT)); - - }) - .Select(x => x.Key) - .ToArray()) // ToArray required to remove - MemoryCache.Remove(key); + MemoryCache.Remove(key); } - finally + } + finally + { + if (_locker.IsWriteLockHeld) { - if (_locker.IsWriteLockHeld) - _locker.ExitWriteLock(); + _locker.ExitWriteLock(); } } + } - /// - public virtual void ClearOfType(Func predicate) + /// + public virtual void ClearOfType(Func predicate) + { + try { - try + _locker.EnterWriteLock(); + Type typeOfT = typeof(T); + var isInterface = typeOfT.IsInterface; + foreach (var key in MemoryCache + .Where(x => + { + // x.Value is Lazy and not null, its value may be null + // remove null values as well, does not hurt + // get non-created as NonCreatedValue & exceptions as null + var value = SafeLazy.GetSafeLazyValue((Lazy)x.Value, true); + if (value == null) + { + return true; + } + + // if T is an interface remove anything that implements that interface + // otherwise remove exact types (not inherited types) + return (isInterface ? value is T : value.GetType() == typeOfT) + && predicate(x.Key, (T)value); + }) + .Select(x => x.Key) + .ToArray()) // ToArray required to remove { - _locker.EnterWriteLock(); - var typeOfT = typeof(T); - var isInterface = typeOfT.IsInterface; - foreach (var key in MemoryCache - .Where(x => - { - // x.Value is Lazy and not null, its value may be null - // remove null values as well, does not hurt - // get non-created as NonCreatedValue & exceptions as null - var value = SafeLazy.GetSafeLazyValue((Lazy)x.Value, true); - if (value == null) return true; - - // if T is an interface remove anything that implements that interface - // otherwise remove exact types (not inherited types) - return (isInterface ? (value is T) : (value.GetType() == typeOfT)) - && predicate(x.Key, (T)value); - }) - .Select(x => x.Key) - .ToArray()) // ToArray required to remove - MemoryCache.Remove(key); + MemoryCache.Remove(key); } - finally + } + finally + { + if (_locker.IsWriteLockHeld) { - if (_locker.IsWriteLockHeld) - _locker.ExitWriteLock(); + _locker.ExitWriteLock(); } } + } - /// - public virtual void ClearByKey(string keyStartsWith) + /// + public virtual void ClearByKey(string keyStartsWith) + { + try { - try + _locker.EnterWriteLock(); + foreach (var key in MemoryCache + .Where(x => x.Key.InvariantStartsWith(keyStartsWith)) + .Select(x => x.Key) + .ToArray()) // ToArray required to remove { - _locker.EnterWriteLock(); - foreach (var key in MemoryCache - .Where(x => x.Key.InvariantStartsWith(keyStartsWith)) - .Select(x => x.Key) - .ToArray()) // ToArray required to remove - MemoryCache.Remove(key); + MemoryCache.Remove(key); } - finally + } + finally + { + if (_locker.IsWriteLockHeld) { - if (_locker.IsWriteLockHeld) - _locker.ExitWriteLock(); + _locker.ExitWriteLock(); } } + } - /// - public virtual void ClearByRegex(string regex) - { - var compiled = new Regex(regex, RegexOptions.Compiled); + /// + public virtual void ClearByRegex(string regex) + { + var compiled = new Regex(regex, RegexOptions.Compiled); - try + try + { + _locker.EnterWriteLock(); + foreach (var key in MemoryCache + .Where(x => compiled.IsMatch(x.Key)) + .Select(x => x.Key) + .ToArray()) // ToArray required to remove { - _locker.EnterWriteLock(); - foreach (var key in MemoryCache - .Where(x => compiled.IsMatch(x.Key)) - .Select(x => x.Key) - .ToArray()) // ToArray required to remove - MemoryCache.Remove(key); + MemoryCache.Remove(key); } - finally + } + finally + { + if (_locker.IsWriteLockHeld) { - if (_locker.IsWriteLockHeld) - _locker.ExitWriteLock(); + _locker.ExitWriteLock(); } } + } - private static CacheItemPolicy GetPolicy(TimeSpan? timeout = null, bool isSliding = false, string[]? dependentFiles = null) - { - var absolute = isSliding ? ObjectCache.InfiniteAbsoluteExpiration : (timeout == null ? ObjectCache.InfiniteAbsoluteExpiration : DateTime.Now.Add(timeout.Value)); - var sliding = isSliding == false ? ObjectCache.NoSlidingExpiration : (timeout ?? ObjectCache.NoSlidingExpiration); + public void Dispose() => + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(true); - var policy = new CacheItemPolicy - { - AbsoluteExpiration = absolute, - SlidingExpiration = sliding - }; + private static CacheItemPolicy GetPolicy(TimeSpan? timeout = null, bool isSliding = false, + string[]? dependentFiles = null) + { + DateTimeOffset absolute = isSliding ? ObjectCache.InfiniteAbsoluteExpiration : + timeout == null ? ObjectCache.InfiniteAbsoluteExpiration : DateTime.Now.Add(timeout.Value); + TimeSpan sliding = isSliding == false + ? ObjectCache.NoSlidingExpiration + : timeout ?? ObjectCache.NoSlidingExpiration; - if (dependentFiles != null && dependentFiles.Any()) - { - policy.ChangeMonitors.Add(new HostFileChangeMonitor(dependentFiles.ToList())); - } + var policy = new CacheItemPolicy {AbsoluteExpiration = absolute, SlidingExpiration = sliding}; - return policy; + if (dependentFiles != null && dependentFiles.Any()) + { + policy.ChangeMonitors.Add(new HostFileChangeMonitor(dependentFiles.ToList())); } - protected virtual void Dispose(bool disposing) + return policy; + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) { - if (!_disposedValue) + if (disposing) { - if (disposing) - { - _locker.Dispose(); - } - _disposedValue = true; + _locker.Dispose(); } - } - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); + _disposedValue = true; } } } diff --git a/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs b/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs index 2dc3ddcf1bb7..6bb735fa459e 100644 --- a/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs @@ -3,49 +3,48 @@ using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +/// +/// A base class for "payload" class refreshers. +/// +/// The actual cache refresher type. +/// The payload type. +/// The actual cache refresher type is used for strongly typed events. +public abstract class + PayloadCacheRefresherBase : JsonCacheRefresherBase, + IPayloadCacheRefresher + where TNotification : CacheRefresherNotification { /// - /// A base class for "payload" class refreshers. + /// Initializes a new instance of the . /// - /// The actual cache refresher type. - /// The payload type. - /// The actual cache refresher type is used for strongly typed events. - public abstract class PayloadCacheRefresherBase : JsonCacheRefresherBase, IPayloadCacheRefresher - where TNotification : CacheRefresherNotification + /// A cache helper. + /// + protected PayloadCacheRefresherBase(AppCaches appCaches, IJsonSerializer serializer, + IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) + : base(appCaches, serializer, eventAggregator, factory) { - - /// - /// Initializes a new instance of the . - /// - /// A cache helper. - /// - protected PayloadCacheRefresherBase(AppCaches appCaches, IJsonSerializer serializer, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) - : base(appCaches, serializer, eventAggregator, factory) - { - } + } - #region Refresher + #region Refresher - public override void Refresh(string json) + public override void Refresh(string json) + { + TPayload[] payload = Deserialize(json); + if (payload is not null) { - var payload = Deserialize(json); - if (payload is not null) - { - Refresh(payload); - } + Refresh(payload); } + } - /// - /// Refreshes as specified by a payload. - /// - /// The payload. - public virtual void Refresh(TPayload[] payloads) - { - OnCacheUpdated(NotificationFactory.Create(payloads, MessageType.RefreshByPayload)); - } + /// + /// Refreshes as specified by a payload. + /// + /// The payload. + public virtual void Refresh(TPayload[] payloads) => + OnCacheUpdated(NotificationFactory.Create(payloads, MessageType.RefreshByPayload)); - #endregion - } + #endregion } diff --git a/src/Umbraco.Core/Cache/PublicAccessCacheRefresher.cs b/src/Umbraco.Core/Cache/PublicAccessCacheRefresher.cs index 5c9eb20b4c53..ac51c7c84030 100644 --- a/src/Umbraco.Core/Cache/PublicAccessCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/PublicAccessCacheRefresher.cs @@ -1,52 +1,52 @@ -using System; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +public sealed class PublicAccessCacheRefresher : CacheRefresherBase { - public sealed class PublicAccessCacheRefresher : CacheRefresherBase + public PublicAccessCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, eventAggregator, factory) { - public PublicAccessCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) - : base(appCaches, eventAggregator, factory) - { } - - #region Define + } - public static readonly Guid UniqueId = Guid.Parse("1DB08769-B104-4F8B-850E-169CAC1DF2EC"); + #region Define - public override Guid RefresherUniqueId => UniqueId; + public static readonly Guid UniqueId = Guid.Parse("1DB08769-B104-4F8B-850E-169CAC1DF2EC"); - public override string Name => "Public Access Cache Refresher"; + public override Guid RefresherUniqueId => UniqueId; - #endregion + public override string Name => "Public Access Cache Refresher"; - #region Refresher + #endregion - public override void Refresh(Guid id) - { - ClearAllIsolatedCacheByEntityType(); - base.Refresh(id); - } + #region Refresher - public override void Refresh(int id) - { - ClearAllIsolatedCacheByEntityType(); - base.Refresh(id); - } + public override void Refresh(Guid id) + { + ClearAllIsolatedCacheByEntityType(); + base.Refresh(id); + } - public override void RefreshAll() - { - ClearAllIsolatedCacheByEntityType(); - base.RefreshAll(); - } + public override void Refresh(int id) + { + ClearAllIsolatedCacheByEntityType(); + base.Refresh(id); + } - public override void Remove(int id) - { - ClearAllIsolatedCacheByEntityType(); - base.Remove(id); - } + public override void RefreshAll() + { + ClearAllIsolatedCacheByEntityType(); + base.RefreshAll(); + } - #endregion + public override void Remove(int id) + { + ClearAllIsolatedCacheByEntityType(); + base.Remove(id); } + + #endregion } diff --git a/src/Umbraco.Core/Cache/RelationTypeCacheRefresher.cs b/src/Umbraco.Core/Cache/RelationTypeCacheRefresher.cs index 9f1c45374e65..89669e76bc8a 100644 --- a/src/Umbraco.Core/Cache/RelationTypeCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/RelationTypeCacheRefresher.cs @@ -1,55 +1,60 @@ -using System; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Repositories; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +public sealed class RelationTypeCacheRefresher : CacheRefresherBase { - public sealed class RelationTypeCacheRefresher : CacheRefresherBase + public RelationTypeCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, eventAggregator, factory) { - public RelationTypeCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) - : base(appCaches, eventAggregator, factory) - { } + } - #region Define + #region Define - public static readonly Guid UniqueId = Guid.Parse("D8375ABA-4FB3-4F86-B505-92FBA1B6F7C9"); + public static readonly Guid UniqueId = Guid.Parse("D8375ABA-4FB3-4F86-B505-92FBA1B6F7C9"); - public override Guid RefresherUniqueId => UniqueId; + public override Guid RefresherUniqueId => UniqueId; - public override string Name => "Relation Type Cache Refresher"; + public override string Name => "Relation Type Cache Refresher"; - #endregion + #endregion - #region Refresher + #region Refresher - public override void RefreshAll() - { - ClearAllIsolatedCacheByEntityType(); - base.RefreshAll(); - } + public override void RefreshAll() + { + ClearAllIsolatedCacheByEntityType(); + base.RefreshAll(); + } - public override void Refresh(int id) + public override void Refresh(int id) + { + Attempt cache = AppCaches.IsolatedCaches.Get(); + if (cache.Success) { - var cache = AppCaches.IsolatedCaches.Get(); - if (cache.Success) cache.Result?.Clear(RepositoryCacheKeys.GetKey(id)); - base.Refresh(id); + cache.Result?.Clear(RepositoryCacheKeys.GetKey(id)); } - public override void Refresh(Guid id) - { - throw new NotSupportedException(); - //base.Refresh(id); - } + base.Refresh(id); + } - public override void Remove(int id) + public override void Refresh(Guid id) => throw new NotSupportedException(); + + //base.Refresh(id); + public override void Remove(int id) + { + Attempt cache = AppCaches.IsolatedCaches.Get(); + if (cache.Success) { - var cache = AppCaches.IsolatedCaches.Get(); - if (cache.Success) cache.Result?.Clear(RepositoryCacheKeys.GetKey(id)); - base.Remove(id); + cache.Result?.Clear(RepositoryCacheKeys.GetKey(id)); } - #endregion + base.Remove(id); } + + #endregion } diff --git a/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs b/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs index c719ce72e5f4..ee02ab105ea4 100644 --- a/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs +++ b/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs @@ -1,51 +1,50 @@ -using System; +namespace Umbraco.Cms.Core.Cache; -namespace Umbraco.Cms.Core.Cache +/// +/// Specifies how a repository cache policy should cache entities. +/// +public class RepositoryCachePolicyOptions { /// - /// Specifies how a repository cache policy should cache entities. + /// Ctor - sets GetAllCacheValidateCount = true /// - public class RepositoryCachePolicyOptions + public RepositoryCachePolicyOptions(Func performCount) { - /// - /// Ctor - sets GetAllCacheValidateCount = true - /// - public RepositoryCachePolicyOptions(Func performCount) - { - PerformCount = performCount; - GetAllCacheValidateCount = true; - GetAllCacheAllowZeroCount = false; - } + PerformCount = performCount; + GetAllCacheValidateCount = true; + GetAllCacheAllowZeroCount = false; + } - /// - /// Ctor - sets GetAllCacheValidateCount = false - /// - public RepositoryCachePolicyOptions() - { - PerformCount = null; - GetAllCacheValidateCount = false; - GetAllCacheAllowZeroCount = false; - } + /// + /// Ctor - sets GetAllCacheValidateCount = false + /// + public RepositoryCachePolicyOptions() + { + PerformCount = null; + GetAllCacheValidateCount = false; + GetAllCacheAllowZeroCount = false; + } - /// - /// Callback required to get count for GetAllCacheValidateCount - /// - public Func? PerformCount { get; set; } + /// + /// Callback required to get count for GetAllCacheValidateCount + /// + public Func? PerformCount { get; set; } - /// - /// True/false as to validate the total item count when all items are returned from cache, the default is true but this - /// means that a db lookup will occur - though that lookup will probably be significantly less expensive than the normal - /// GetAll method. - /// - /// - /// setting this to return false will improve performance of GetAll cache with no params but should only be used - /// for specific circumstances - /// - public bool GetAllCacheValidateCount { get; set; } + /// + /// True/false as to validate the total item count when all items are returned from cache, the default is true but this + /// means that a db lookup will occur - though that lookup will probably be significantly less expensive than the + /// normal + /// GetAll method. + /// + /// + /// setting this to return false will improve performance of GetAll cache with no params but should only be used + /// for specific circumstances + /// + public bool GetAllCacheValidateCount { get; set; } - /// - /// True if the GetAll method will cache that there are zero results so that the db is not hit when there are no results found - /// - public bool GetAllCacheAllowZeroCount { get; set; } - } + /// + /// True if the GetAll method will cache that there are zero results so that the db is not hit when there are no + /// results found + /// + public bool GetAllCacheAllowZeroCount { get; set; } } diff --git a/src/Umbraco.Core/Cache/SafeLazy.cs b/src/Umbraco.Core/Cache/SafeLazy.cs index 387e5c0271f3..07c70f0be3c6 100644 --- a/src/Umbraco.Core/Cache/SafeLazy.cs +++ b/src/Umbraco.Core/Cache/SafeLazy.cs @@ -1,63 +1,63 @@ -using System; -using System.Runtime.ExceptionServices; +using System.Runtime.ExceptionServices; -namespace Umbraco.Cms.Core.Cache -{ - public static class SafeLazy - { - // an object that represent a value that has not been created yet - internal static readonly object ValueNotCreated = new object(); +namespace Umbraco.Cms.Core.Cache; - public static Lazy GetSafeLazy(Func getCacheItem) - { - // try to generate the value and if it fails, - // wrap in an ExceptionHolder - would be much simpler - // to just use lazy.IsValueFaulted alas that field is - // internal - return new Lazy(() => - { - try - { - return getCacheItem(); - } - catch (Exception e) - { - return new ExceptionHolder(ExceptionDispatchInfo.Capture(e)); - } - }); - } +public static class SafeLazy +{ + // an object that represent a value that has not been created yet + internal static readonly object ValueNotCreated = new(); - public static object? GetSafeLazyValue(Lazy? lazy, bool onlyIfValueIsCreated = false) + public static Lazy GetSafeLazy(Func getCacheItem) => + // try to generate the value and if it fails, + // wrap in an ExceptionHolder - would be much simpler + // to just use lazy.IsValueFaulted alas that field is + // internal + new Lazy(() => { - // if onlyIfValueIsCreated, do not trigger value creation - // must return something, though, to differentiate from null values - if (onlyIfValueIsCreated && lazy?.IsValueCreated == false) return ValueNotCreated; - - // if execution has thrown then lazy.IsValueCreated is false - // and lazy.IsValueFaulted is true (but internal) so we use our - // own exception holder (see Lazy source code) to return null - if (lazy?.Value is ExceptionHolder) return null; - - // we have a value and execution has not thrown so returning - // here does not throw - unless we're re-entering, take care of it try { - return lazy?.Value; + return getCacheItem(); } - catch (InvalidOperationException e) + catch (Exception e) { - throw new InvalidOperationException("The method that computes a value for the cache has tried to read that value from the cache.", e); + return new ExceptionHolder(ExceptionDispatchInfo.Capture(e)); } + }); + + public static object? GetSafeLazyValue(Lazy? lazy, bool onlyIfValueIsCreated = false) + { + // if onlyIfValueIsCreated, do not trigger value creation + // must return something, though, to differentiate from null values + if (onlyIfValueIsCreated && lazy?.IsValueCreated == false) + { + return ValueNotCreated; } - public class ExceptionHolder + // if execution has thrown then lazy.IsValueCreated is false + // and lazy.IsValueFaulted is true (but internal) so we use our + // own exception holder (see Lazy source code) to return null + if (lazy?.Value is ExceptionHolder) { - public ExceptionHolder(ExceptionDispatchInfo e) - { - Exception = e; - } + return null; + } - public ExceptionDispatchInfo Exception { get; } + // we have a value and execution has not thrown so returning + // here does not throw - unless we're re-entering, take care of it + try + { + return lazy?.Value; + } + catch (InvalidOperationException e) + { + throw new InvalidOperationException( + "The method that computes a value for the cache has tried to read that value from the cache.", e); } } + + public class ExceptionHolder + { + public ExceptionHolder(ExceptionDispatchInfo e) => Exception = e; + + public ExceptionDispatchInfo Exception { get; } + } } diff --git a/src/Umbraco.Core/Cache/TemplateCacheRefresher.cs b/src/Umbraco.Core/Cache/TemplateCacheRefresher.cs index 0bc2c6c5ef1c..025b3e25fd0f 100644 --- a/src/Umbraco.Core/Cache/TemplateCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/TemplateCacheRefresher.cs @@ -1,66 +1,66 @@ -using System; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.Cache -{ - public sealed class TemplateCacheRefresher : CacheRefresherBase - { - private readonly IIdKeyMap _idKeyMap; - private readonly IContentTypeCommonRepository _contentTypeCommonRepository; +namespace Umbraco.Cms.Core.Cache; - public TemplateCacheRefresher(AppCaches appCaches, IIdKeyMap idKeyMap, IContentTypeCommonRepository contentTypeCommonRepository, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) - : base(appCaches, eventAggregator, factory) - { - _idKeyMap = idKeyMap; - _contentTypeCommonRepository = contentTypeCommonRepository; - } +public sealed class TemplateCacheRefresher : CacheRefresherBase +{ + private readonly IContentTypeCommonRepository _contentTypeCommonRepository; + private readonly IIdKeyMap _idKeyMap; - #region Define + public TemplateCacheRefresher(AppCaches appCaches, IIdKeyMap idKeyMap, + IContentTypeCommonRepository contentTypeCommonRepository, IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, eventAggregator, factory) + { + _idKeyMap = idKeyMap; + _contentTypeCommonRepository = contentTypeCommonRepository; + } - public static readonly Guid UniqueId = Guid.Parse("DD12B6A0-14B9-46e8-8800-C154F74047C8"); + #region Define - public override Guid RefresherUniqueId => UniqueId; + public static readonly Guid UniqueId = Guid.Parse("DD12B6A0-14B9-46e8-8800-C154F74047C8"); - public override string Name => "Template Cache Refresher"; + public override Guid RefresherUniqueId => UniqueId; - #endregion + public override string Name => "Template Cache Refresher"; - #region Refresher + #endregion - public override void Refresh(int id) - { - RemoveFromCache(id); - base.Refresh(id); - } + #region Refresher - public override void Remove(int id) - { - RemoveFromCache(id); + public override void Refresh(int id) + { + RemoveFromCache(id); + base.Refresh(id); + } - //During removal we need to clear the runtime cache for templates, content and content type instances!!! - // all three of these types are referenced by templates, and the cache needs to be cleared on every server, - // otherwise things like looking up content type's after a template is removed is still going to show that - // it has an associated template. - ClearAllIsolatedCacheByEntityType(); - ClearAllIsolatedCacheByEntityType(); - _contentTypeCommonRepository.ClearCache(); + public override void Remove(int id) + { + RemoveFromCache(id); - base.Remove(id); - } + //During removal we need to clear the runtime cache for templates, content and content type instances!!! + // all three of these types are referenced by templates, and the cache needs to be cleared on every server, + // otherwise things like looking up content type's after a template is removed is still going to show that + // it has an associated template. + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + _contentTypeCommonRepository.ClearCache(); - private void RemoveFromCache(int id) - { - _idKeyMap.ClearCache(id); - AppCaches.RuntimeCache.Clear($"{CacheKeys.TemplateFrontEndCacheKey}{id}"); + base.Remove(id); + } - //need to clear the runtime cache for templates - ClearAllIsolatedCacheByEntityType(); - } + private void RemoveFromCache(int id) + { + _idKeyMap.ClearCache(id); + AppCaches.RuntimeCache.Clear($"{CacheKeys.TemplateFrontEndCacheKey}{id}"); - #endregion + //need to clear the runtime cache for templates + ClearAllIsolatedCacheByEntityType(); } + + #endregion } diff --git a/src/Umbraco.Core/Cache/UserCacheRefresher.cs b/src/Umbraco.Core/Cache/UserCacheRefresher.cs index 10c4865ba828..d07c9633cd8c 100644 --- a/src/Umbraco.Core/Cache/UserCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/UserCacheRefresher.cs @@ -1,56 +1,57 @@ -using System; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Repositories; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +public sealed class UserCacheRefresher : CacheRefresherBase { - public sealed class UserCacheRefresher : CacheRefresherBase + public UserCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, eventAggregator, factory) { - public UserCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) - : base(appCaches, eventAggregator, factory) - { } + } - #region Define + #region Define - public static readonly Guid UniqueId = Guid.Parse("E057AF6D-2EE6-41F4-8045-3694010F0AA6"); + public static readonly Guid UniqueId = Guid.Parse("E057AF6D-2EE6-41F4-8045-3694010F0AA6"); - public override Guid RefresherUniqueId => UniqueId; + public override Guid RefresherUniqueId => UniqueId; - public override string Name => "User Cache Refresher"; + public override string Name => "User Cache Refresher"; - #endregion + #endregion - #region Refresher + #region Refresher - public override void RefreshAll() - { - ClearAllIsolatedCacheByEntityType(); - base.RefreshAll(); - } + public override void RefreshAll() + { + ClearAllIsolatedCacheByEntityType(); + base.RefreshAll(); + } - public override void Refresh(int id) - { - Remove(id); - base.Refresh(id); - } + public override void Refresh(int id) + { + Remove(id); + base.Refresh(id); + } - public override void Remove(int id) + public override void Remove(int id) + { + Attempt userCache = AppCaches.IsolatedCaches.Get(); + if (userCache.Success) { - var userCache = AppCaches.IsolatedCaches.Get(); - if (userCache.Success) - { - userCache.Result?.Clear(RepositoryCacheKeys.GetKey(id)); - userCache.Result?.ClearByKey(CacheKeys.UserContentStartNodePathsPrefix + id); - userCache.Result?.ClearByKey(CacheKeys.UserMediaStartNodePathsPrefix + id); - userCache.Result?.ClearByKey(CacheKeys.UserAllContentStartNodesPrefix + id); - userCache.Result?.ClearByKey(CacheKeys.UserAllMediaStartNodesPrefix + id); - } - - - base.Remove(id); + userCache.Result?.Clear(RepositoryCacheKeys.GetKey(id)); + userCache.Result?.ClearByKey(CacheKeys.UserContentStartNodePathsPrefix + id); + userCache.Result?.ClearByKey(CacheKeys.UserMediaStartNodePathsPrefix + id); + userCache.Result?.ClearByKey(CacheKeys.UserAllContentStartNodesPrefix + id); + userCache.Result?.ClearByKey(CacheKeys.UserAllMediaStartNodesPrefix + id); } - #endregion + + + base.Remove(id); } + + #endregion } diff --git a/src/Umbraco.Core/Cache/UserGroupCacheRefresher.cs b/src/Umbraco.Core/Cache/UserGroupCacheRefresher.cs index a889146794ad..9b01266b984b 100644 --- a/src/Umbraco.Core/Cache/UserGroupCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/UserGroupCacheRefresher.cs @@ -1,71 +1,71 @@ -using System; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Repositories; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +/// +/// Handles User group cache invalidation/refreshing +/// +/// +/// This also needs to clear the user cache since IReadOnlyUserGroup's are attached to IUser objects +/// +public sealed class UserGroupCacheRefresher : CacheRefresherBase { - /// - /// Handles User group cache invalidation/refreshing - /// - /// - /// This also needs to clear the user cache since IReadOnlyUserGroup's are attached to IUser objects - /// - public sealed class UserGroupCacheRefresher : CacheRefresherBase + public UserGroupCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory) + : base(appCaches, eventAggregator, factory) { - public UserGroupCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) - : base(appCaches, eventAggregator, factory) - { } + } - #region Define + #region Define - public static readonly Guid UniqueId = Guid.Parse("45178038-B232-4FE8-AA1A-F2B949C44762"); + public static readonly Guid UniqueId = Guid.Parse("45178038-B232-4FE8-AA1A-F2B949C44762"); - public override Guid RefresherUniqueId => UniqueId; + public override Guid RefresherUniqueId => UniqueId; - public override string Name => "User Group Cache Refresher"; + public override string Name => "User Group Cache Refresher"; - #endregion + #endregion - #region Refresher + #region Refresher - public override void RefreshAll() + public override void RefreshAll() + { + ClearAllIsolatedCacheByEntityType(); + Attempt userGroupCache = AppCaches.IsolatedCaches.Get(); + if (userGroupCache.Success) { - ClearAllIsolatedCacheByEntityType(); - var userGroupCache = AppCaches.IsolatedCaches.Get(); - if (userGroupCache.Success) - { - userGroupCache.Result?.ClearByKey(CacheKeys.UserGroupGetByAliasCacheKeyPrefix); - } - - //We'll need to clear all user cache too - ClearAllIsolatedCacheByEntityType(); - - base.RefreshAll(); + userGroupCache.Result?.ClearByKey(CacheKeys.UserGroupGetByAliasCacheKeyPrefix); } - public override void Refresh(int id) - { - Remove(id); - base.Refresh(id); - } + //We'll need to clear all user cache too + ClearAllIsolatedCacheByEntityType(); - public override void Remove(int id) - { - var userGroupCache = AppCaches.IsolatedCaches.Get(); - if (userGroupCache.Success) - { - userGroupCache.Result?.Clear(RepositoryCacheKeys.GetKey(id)); - userGroupCache.Result?.ClearByKey(CacheKeys.UserGroupGetByAliasCacheKeyPrefix); - } + base.RefreshAll(); + } - //we don't know what user's belong to this group without doing a look up so we'll need to just clear them all - ClearAllIsolatedCacheByEntityType(); + public override void Refresh(int id) + { + Remove(id); + base.Refresh(id); + } - base.Remove(id); + public override void Remove(int id) + { + Attempt userGroupCache = AppCaches.IsolatedCaches.Get(); + if (userGroupCache.Success) + { + userGroupCache.Result?.Clear(RepositoryCacheKeys.GetKey(id)); + userGroupCache.Result?.ClearByKey(CacheKeys.UserGroupGetByAliasCacheKeyPrefix); } - #endregion + //we don't know what user's belong to this group without doing a look up so we'll need to just clear them all + ClearAllIsolatedCacheByEntityType(); + + base.Remove(id); } + + #endregion } diff --git a/src/Umbraco.Core/Cache/ValueEditorCache.cs b/src/Umbraco.Core/Cache/ValueEditorCache.cs index 7d5f20efb45a..a5b6034b4edd 100644 --- a/src/Umbraco.Core/Cache/ValueEditorCache.cs +++ b/src/Umbraco.Core/Cache/ValueEditorCache.cs @@ -1,60 +1,57 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +public class ValueEditorCache : IValueEditorCache { - public class ValueEditorCache : IValueEditorCache - { - private readonly Dictionary> _valueEditorCache; - private readonly object _dictionaryLocker; + private readonly object _dictionaryLocker; + private readonly Dictionary> _valueEditorCache; - public ValueEditorCache() - { - _valueEditorCache = new Dictionary>(); - _dictionaryLocker = new object(); - } + public ValueEditorCache() + { + _valueEditorCache = new Dictionary>(); + _dictionaryLocker = new object(); + } - public IDataValueEditor GetValueEditor(IDataEditor editor, IDataType dataType) + public IDataValueEditor GetValueEditor(IDataEditor editor, IDataType dataType) + { + // Lock just in case multiple threads uses the cache at the same time. + lock (_dictionaryLocker) { - // Lock just in case multiple threads uses the cache at the same time. - lock (_dictionaryLocker) + // We try and get the dictionary based on the IDataEditor alias, + // this is here just in case a data type can have more than one value data editor. + // If this is not the case this could be simplified quite a bit, by just using the inner dictionary only. + IDataValueEditor? valueEditor; + if (_valueEditorCache.TryGetValue(editor.Alias, out Dictionary? dataEditorCache)) { - // We try and get the dictionary based on the IDataEditor alias, - // this is here just in case a data type can have more than one value data editor. - // If this is not the case this could be simplified quite a bit, by just using the inner dictionary only. - IDataValueEditor? valueEditor; - if (_valueEditorCache.TryGetValue(editor.Alias, out Dictionary? dataEditorCache)) + if (dataEditorCache.TryGetValue(dataType.Id, out valueEditor)) { - if (dataEditorCache.TryGetValue(dataType.Id, out valueEditor)) - { - return valueEditor; - } - - valueEditor = editor.GetValueEditor(dataType.Configuration); - dataEditorCache[dataType.Id] = valueEditor; return valueEditor; } valueEditor = editor.GetValueEditor(dataType.Configuration); - _valueEditorCache[editor.Alias] = new Dictionary { [dataType.Id] = valueEditor }; + dataEditorCache[dataType.Id] = valueEditor; return valueEditor; } + + valueEditor = editor.GetValueEditor(dataType.Configuration); + _valueEditorCache[editor.Alias] = new Dictionary {[dataType.Id] = valueEditor}; + return valueEditor; } + } - public void ClearCache(IEnumerable dataTypeIds) + public void ClearCache(IEnumerable dataTypeIds) + { + lock (_dictionaryLocker) { - lock (_dictionaryLocker) + // If a datatype is saved or deleted we have to clear any value editors based on their ID from the cache, + // since it could mean that their configuration has changed. + foreach (var id in dataTypeIds) { - // If a datatype is saved or deleted we have to clear any value editors based on their ID from the cache, - // since it could mean that their configuration has changed. - foreach (var id in dataTypeIds) + foreach (Dictionary editors in _valueEditorCache.Values) { - foreach (Dictionary editors in _valueEditorCache.Values) - { - editors.Remove(id); - } + editors.Remove(id); } } } diff --git a/src/Umbraco.Core/Cache/ValueEditorCacheRefresher.cs b/src/Umbraco.Core/Cache/ValueEditorCacheRefresher.cs index c815ca7a7167..0bcb3796a51e 100644 --- a/src/Umbraco.Core/Cache/ValueEditorCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/ValueEditorCacheRefresher.cs @@ -1,57 +1,40 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Cache +namespace Umbraco.Cms.Core.Cache; + +public sealed class ValueEditorCacheRefresher : PayloadCacheRefresherBase { - public sealed class ValueEditorCacheRefresher : PayloadCacheRefresherBase + public static readonly Guid UniqueId = Guid.Parse("D28A1DBB-2308-4918-9A92-2F8689B6CBFE"); + private readonly IValueEditorCache _valueEditorCache; + + public ValueEditorCacheRefresher( + AppCaches appCaches, + IJsonSerializer serializer, + IEventAggregator eventAggregator, + ICacheRefresherNotificationFactory factory, + IValueEditorCache valueEditorCache) : base(appCaches, serializer, eventAggregator, factory) => + _valueEditorCache = valueEditorCache; + + public override Guid RefresherUniqueId => UniqueId; + public override string Name => "ValueEditorCacheRefresher"; + + public override void Refresh(DataTypeCacheRefresher.JsonPayload[] payloads) { - private readonly IValueEditorCache _valueEditorCache; - - public ValueEditorCacheRefresher( - AppCaches appCaches, - IJsonSerializer serializer, - IEventAggregator eventAggregator, - ICacheRefresherNotificationFactory factory, - IValueEditorCache valueEditorCache) : base(appCaches, serializer, eventAggregator, factory) - { - _valueEditorCache = valueEditorCache; - } - - public static readonly Guid UniqueId = Guid.Parse("D28A1DBB-2308-4918-9A92-2F8689B6CBFE"); - public override Guid RefresherUniqueId => UniqueId; - public override string Name => "ValueEditorCacheRefresher"; - - public override void Refresh(DataTypeCacheRefresher.JsonPayload[] payloads) - { - IEnumerable ids = payloads.Select(x => x.Id); - _valueEditorCache.ClearCache(ids); - } - - // these events should never trigger - // everything should be PAYLOAD/JSON - - public override void RefreshAll() - { - throw new NotSupportedException(); - } - - public override void Refresh(int id) - { - throw new NotSupportedException(); - } - - public override void Refresh(Guid id) - { - throw new NotSupportedException(); - } - - public override void Remove(int id) - { - throw new NotSupportedException(); - } + IEnumerable ids = payloads.Select(x => x.Id); + _valueEditorCache.ClearCache(ids); } + + // these events should never trigger + // everything should be PAYLOAD/JSON + + public override void RefreshAll() => throw new NotSupportedException(); + + public override void Refresh(int id) => throw new NotSupportedException(); + + public override void Refresh(Guid id) => throw new NotSupportedException(); + + public override void Remove(int id) => throw new NotSupportedException(); } diff --git a/src/Umbraco.Core/CodeAnnotations/FriendlyNameAttribute.cs b/src/Umbraco.Core/CodeAnnotations/FriendlyNameAttribute.cs index f6ee1217425d..1c1cc793e4f2 100644 --- a/src/Umbraco.Core/CodeAnnotations/FriendlyNameAttribute.cs +++ b/src/Umbraco.Core/CodeAnnotations/FriendlyNameAttribute.cs @@ -1,35 +1,26 @@ -using System; +namespace Umbraco.Cms.Core.CodeAnnotations; -namespace Umbraco.Cms.Core.CodeAnnotations +/// +/// Attribute to add a Friendly Name string with an UmbracoObjectType enum value +/// +[AttributeUsage(AttributeTargets.All, Inherited = false)] +public class FriendlyNameAttribute : Attribute { /// - /// Attribute to add a Friendly Name string with an UmbracoObjectType enum value + /// friendly name value /// - [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] - public class FriendlyNameAttribute : Attribute - { - /// - /// friendly name value - /// - private readonly string _friendlyName; + private readonly string _friendlyName; - /// - /// Initializes a new instance of the FriendlyNameAttribute class - /// Sets the friendly name value - /// - /// attribute value - public FriendlyNameAttribute(string friendlyName) - { - this._friendlyName = friendlyName; - } + /// + /// Initializes a new instance of the FriendlyNameAttribute class + /// Sets the friendly name value + /// + /// attribute value + public FriendlyNameAttribute(string friendlyName) => _friendlyName = friendlyName; - /// - /// Gets the friendly name - /// - /// string of friendly name - public override string ToString() - { - return this._friendlyName; - } - } + /// + /// Gets the friendly name + /// + /// string of friendly name + public override string ToString() => _friendlyName; } diff --git a/src/Umbraco.Core/CodeAnnotations/UmbracoObjectTypeAttribute.cs b/src/Umbraco.Core/CodeAnnotations/UmbracoObjectTypeAttribute.cs index 6c4e2b9d0499..750757cea5ca 100644 --- a/src/Umbraco.Core/CodeAnnotations/UmbracoObjectTypeAttribute.cs +++ b/src/Umbraco.Core/CodeAnnotations/UmbracoObjectTypeAttribute.cs @@ -1,26 +1,20 @@ -using System; +namespace Umbraco.Cms.Core.CodeAnnotations; -namespace Umbraco.Cms.Core.CodeAnnotations +/// +/// Attribute to associate a GUID string and Type with an UmbracoObjectType Enum value +/// +[AttributeUsage(AttributeTargets.Field)] +public class UmbracoObjectTypeAttribute : Attribute { - /// - /// Attribute to associate a GUID string and Type with an UmbracoObjectType Enum value - /// - [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] - public class UmbracoObjectTypeAttribute : Attribute - { - public UmbracoObjectTypeAttribute(string objectId) - { - ObjectId = new Guid(objectId); - } + public UmbracoObjectTypeAttribute(string objectId) => ObjectId = new Guid(objectId); - public UmbracoObjectTypeAttribute(string objectId, Type modelType) - { - ObjectId = new Guid(objectId); - ModelType = modelType; - } + public UmbracoObjectTypeAttribute(string objectId, Type modelType) + { + ObjectId = new Guid(objectId); + ModelType = modelType; + } - public Guid ObjectId { get; private set; } + public Guid ObjectId { get; } - public Type? ModelType { get; private set; } - } + public Type? ModelType { get; } } diff --git a/src/Umbraco.Core/CodeAnnotations/UmbracoUdiTypeAttribute.cs b/src/Umbraco.Core/CodeAnnotations/UmbracoUdiTypeAttribute.cs index 5f889daa5c9b..6c40fe8f35d1 100644 --- a/src/Umbraco.Core/CodeAnnotations/UmbracoUdiTypeAttribute.cs +++ b/src/Umbraco.Core/CodeAnnotations/UmbracoUdiTypeAttribute.cs @@ -1,15 +1,9 @@ -using System; +namespace Umbraco.Cms.Core.CodeAnnotations; -namespace Umbraco.Cms.Core.CodeAnnotations +[AttributeUsage(AttributeTargets.Field)] +public class UmbracoUdiTypeAttribute : Attribute { - [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] - public class UmbracoUdiTypeAttribute : Attribute - { - public string UdiType { get; private set; } + public UmbracoUdiTypeAttribute(string udiType) => UdiType = udiType; - public UmbracoUdiTypeAttribute(string udiType) - { - UdiType = udiType; - } - } + public string UdiType { get; } } diff --git a/src/Umbraco.Core/Collections/CompositeIntStringKey.cs b/src/Umbraco.Core/Collections/CompositeIntStringKey.cs index a9bd71c6cca2..08efb4d70bb4 100644 --- a/src/Umbraco.Core/Collections/CompositeIntStringKey.cs +++ b/src/Umbraco.Core/Collections/CompositeIntStringKey.cs @@ -1,43 +1,44 @@ -using System; +namespace Umbraco.Cms.Core.Collections; -namespace Umbraco.Cms.Core.Collections +/// +/// Represents a composite key of (int, string) for fast dictionaries. +/// +/// +/// The integer part of the key must be greater than, or equal to, zero. +/// The string part of the key is case-insensitive. +/// Null is a valid value for both parts. +/// +public struct CompositeIntStringKey : IEquatable { + private readonly int _key1; + private readonly string _key2; + /// - /// Represents a composite key of (int, string) for fast dictionaries. + /// Initializes a new instance of the struct. /// - /// - /// The integer part of the key must be greater than, or equal to, zero. - /// The string part of the key is case-insensitive. - /// Null is a valid value for both parts. - /// - public struct CompositeIntStringKey : IEquatable + public CompositeIntStringKey(int? key1, string key2) { - private readonly int _key1; - private readonly string _key2; - - /// - /// Initializes a new instance of the struct. - /// - public CompositeIntStringKey(int? key1, string key2) + if (key1 < 0) { - if (key1 < 0) throw new ArgumentOutOfRangeException(nameof(key1)); - _key1 = key1 ?? -1; - _key2 = key2?.ToLowerInvariant() ?? "NULL"; + throw new ArgumentOutOfRangeException(nameof(key1)); } - public bool Equals(CompositeIntStringKey other) - => _key2 == other._key2 && _key1 == other._key1; + _key1 = key1 ?? -1; + _key2 = key2?.ToLowerInvariant() ?? "NULL"; + } - public override bool Equals(object? obj) - => obj is CompositeIntStringKey other && _key2 == other._key2 && _key1 == other._key1; + public bool Equals(CompositeIntStringKey other) + => _key2 == other._key2 && _key1 == other._key1; - public override int GetHashCode() - => _key2.GetHashCode() * 31 + _key1; + public override bool Equals(object? obj) + => obj is CompositeIntStringKey other && _key2 == other._key2 && _key1 == other._key1; - public static bool operator ==(CompositeIntStringKey key1, CompositeIntStringKey key2) - => key1._key2 == key2._key2 && key1._key1 == key2._key1; + public override int GetHashCode() + => (_key2.GetHashCode() * 31) + _key1; - public static bool operator !=(CompositeIntStringKey key1, CompositeIntStringKey key2) - => key1._key2 != key2._key2 || key1._key1 != key2._key1; - } + public static bool operator ==(CompositeIntStringKey key1, CompositeIntStringKey key2) + => key1._key2 == key2._key2 && key1._key1 == key2._key1; + + public static bool operator !=(CompositeIntStringKey key1, CompositeIntStringKey key2) + => key1._key2 != key2._key2 || key1._key1 != key2._key1; } diff --git a/src/Umbraco.Core/Collections/CompositeNStringNStringKey.cs b/src/Umbraco.Core/Collections/CompositeNStringNStringKey.cs index 2886de92f178..70d17207f880 100644 --- a/src/Umbraco.Core/Collections/CompositeNStringNStringKey.cs +++ b/src/Umbraco.Core/Collections/CompositeNStringNStringKey.cs @@ -1,41 +1,38 @@ -using System; +namespace Umbraco.Cms.Core.Collections; -namespace Umbraco.Cms.Core.Collections +/// +/// Represents a composite key of (string, string) for fast dictionaries. +/// +/// +/// The string parts of the key are case-insensitive. +/// Null is a valid value for both parts. +/// +public struct CompositeNStringNStringKey : IEquatable { + private readonly string _key1; + private readonly string _key2; + /// - /// Represents a composite key of (string, string) for fast dictionaries. + /// Initializes a new instance of the struct. /// - /// - /// The string parts of the key are case-insensitive. - /// Null is a valid value for both parts. - /// - public struct CompositeNStringNStringKey : IEquatable + public CompositeNStringNStringKey(string? key1, string? key2) { - private readonly string _key1; - private readonly string _key2; - - /// - /// Initializes a new instance of the struct. - /// - public CompositeNStringNStringKey(string? key1, string? key2) - { - _key1 = key1?.ToLowerInvariant() ?? "NULL"; - _key2 = key2?.ToLowerInvariant() ?? "NULL"; - } + _key1 = key1?.ToLowerInvariant() ?? "NULL"; + _key2 = key2?.ToLowerInvariant() ?? "NULL"; + } - public bool Equals(CompositeNStringNStringKey other) - => _key2 == other._key2 && _key1 == other._key1; + public bool Equals(CompositeNStringNStringKey other) + => _key2 == other._key2 && _key1 == other._key1; - public override bool Equals(object? obj) - => obj is CompositeNStringNStringKey other && _key2 == other._key2 && _key1 == other._key1; + public override bool Equals(object? obj) + => obj is CompositeNStringNStringKey other && _key2 == other._key2 && _key1 == other._key1; - public override int GetHashCode() - => _key2.GetHashCode() * 31 + _key1.GetHashCode(); + public override int GetHashCode() + => (_key2.GetHashCode() * 31) + _key1.GetHashCode(); - public static bool operator ==(CompositeNStringNStringKey key1, CompositeNStringNStringKey key2) - => key1._key2 == key2._key2 && key1._key1 == key2._key1; + public static bool operator ==(CompositeNStringNStringKey key1, CompositeNStringNStringKey key2) + => key1._key2 == key2._key2 && key1._key1 == key2._key1; - public static bool operator !=(CompositeNStringNStringKey key1, CompositeNStringNStringKey key2) - => key1._key2 != key2._key2 || key1._key1 != key2._key1; - } + public static bool operator !=(CompositeNStringNStringKey key1, CompositeNStringNStringKey key2) + => key1._key2 != key2._key2 || key1._key1 != key2._key1; } diff --git a/src/Umbraco.Core/Collections/CompositeStringStringKey.cs b/src/Umbraco.Core/Collections/CompositeStringStringKey.cs index 01f94bf149e9..333192ac8751 100644 --- a/src/Umbraco.Core/Collections/CompositeStringStringKey.cs +++ b/src/Umbraco.Core/Collections/CompositeStringStringKey.cs @@ -1,41 +1,38 @@ -using System; +namespace Umbraco.Cms.Core.Collections; -namespace Umbraco.Cms.Core.Collections +/// +/// Represents a composite key of (string, string) for fast dictionaries. +/// +/// +/// The string parts of the key are case-insensitive. +/// Null is NOT a valid value for neither parts. +/// +public struct CompositeStringStringKey : IEquatable { + private readonly string _key1; + private readonly string _key2; + /// - /// Represents a composite key of (string, string) for fast dictionaries. + /// Initializes a new instance of the struct. /// - /// - /// The string parts of the key are case-insensitive. - /// Null is NOT a valid value for neither parts. - /// - public struct CompositeStringStringKey : IEquatable + public CompositeStringStringKey(string? key1, string? key2) { - private readonly string _key1; - private readonly string _key2; - - /// - /// Initializes a new instance of the struct. - /// - public CompositeStringStringKey(string? key1, string? key2) - { - _key1 = key1?.ToLowerInvariant() ?? throw new ArgumentNullException(nameof(key1)); - _key2 = key2?.ToLowerInvariant() ?? throw new ArgumentNullException(nameof(key2)); - } + _key1 = key1?.ToLowerInvariant() ?? throw new ArgumentNullException(nameof(key1)); + _key2 = key2?.ToLowerInvariant() ?? throw new ArgumentNullException(nameof(key2)); + } - public bool Equals(CompositeStringStringKey other) - => _key2 == other._key2 && _key1 == other._key1; + public bool Equals(CompositeStringStringKey other) + => _key2 == other._key2 && _key1 == other._key1; - public override bool Equals(object? obj) - => obj is CompositeStringStringKey other && _key2 == other._key2 && _key1 == other._key1; + public override bool Equals(object? obj) + => obj is CompositeStringStringKey other && _key2 == other._key2 && _key1 == other._key1; - public override int GetHashCode() - => _key2.GetHashCode() * 31 + _key1.GetHashCode(); + public override int GetHashCode() + => (_key2.GetHashCode() * 31) + _key1.GetHashCode(); - public static bool operator ==(CompositeStringStringKey key1, CompositeStringStringKey key2) - => key1._key2 == key2._key2 && key1._key1 == key2._key1; + public static bool operator ==(CompositeStringStringKey key1, CompositeStringStringKey key2) + => key1._key2 == key2._key2 && key1._key1 == key2._key1; - public static bool operator !=(CompositeStringStringKey key1, CompositeStringStringKey key2) - => key1._key2 != key2._key2 || key1._key1 != key2._key1; - } + public static bool operator !=(CompositeStringStringKey key1, CompositeStringStringKey key2) + => key1._key2 != key2._key2 || key1._key1 != key2._key1; } diff --git a/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs b/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs index ea737e0522a4..820f1bb191a7 100644 --- a/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs +++ b/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs @@ -1,62 +1,52 @@ -using System; +namespace Umbraco.Cms.Core.Collections; -namespace Umbraco.Cms.Core.Collections +/// +/// Represents a composite key of (Type, Type) for fast dictionaries. +/// +public struct CompositeTypeTypeKey : IEquatable { /// - /// Represents a composite key of (Type, Type) for fast dictionaries. + /// Initializes a new instance of the struct. /// - public struct CompositeTypeTypeKey : IEquatable + public CompositeTypeTypeKey(Type type1, Type type2) + : this() { - /// - /// Initializes a new instance of the struct. - /// - public CompositeTypeTypeKey(Type type1, Type type2) - : this() - { - Type1 = type1; - Type2 = type2; - } + Type1 = type1; + Type2 = type2; + } - /// - /// Gets the first type. - /// - public Type Type1 { get; } + /// + /// Gets the first type. + /// + public Type Type1 { get; } - /// - /// Gets the second type. - /// - public Type Type2 { get; } + /// + /// Gets the second type. + /// + public Type Type2 { get; } - /// - public bool Equals(CompositeTypeTypeKey other) - { - return Type1 == other.Type1 && Type2 == other.Type2; - } + /// + public bool Equals(CompositeTypeTypeKey other) => Type1 == other.Type1 && Type2 == other.Type2; - /// - public override bool Equals(object? obj) - { - var other = obj is CompositeTypeTypeKey key ? key : default; - return Type1 == other.Type1 && Type2 == other.Type2; - } + /// + public override bool Equals(object? obj) + { + CompositeTypeTypeKey other = obj is CompositeTypeTypeKey key ? key : default; + return Type1 == other.Type1 && Type2 == other.Type2; + } - public static bool operator ==(CompositeTypeTypeKey key1, CompositeTypeTypeKey key2) - { - return key1.Type1 == key2.Type1 && key1.Type2 == key2.Type2; - } + public static bool operator ==(CompositeTypeTypeKey key1, CompositeTypeTypeKey key2) => + key1.Type1 == key2.Type1 && key1.Type2 == key2.Type2; - public static bool operator !=(CompositeTypeTypeKey key1, CompositeTypeTypeKey key2) - { - return key1.Type1 != key2.Type1 || key1.Type2 != key2.Type2; - } + public static bool operator !=(CompositeTypeTypeKey key1, CompositeTypeTypeKey key2) => + key1.Type1 != key2.Type1 || key1.Type2 != key2.Type2; - /// - public override int GetHashCode() + /// + public override int GetHashCode() + { + unchecked { - unchecked - { - return (Type1.GetHashCode() * 397) ^ Type2.GetHashCode(); - } + return (Type1.GetHashCode() * 397) ^ Type2.GetHashCode(); } } } diff --git a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs index f9c10e560762..8a53f826cb0a 100644 --- a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs +++ b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs @@ -1,216 +1,278 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Threading; +using System.Collections; -namespace Umbraco.Cms.Core.Collections +namespace Umbraco.Cms.Core.Collections; + +/// +/// A thread-safe representation of a . +/// Enumerating this collection is thread-safe and will only operate on a clone that is generated before returning the +/// enumerator. +/// +/// +[Serializable] +public class ConcurrentHashSet : ICollection { + private readonly HashSet _innerSet = new(); + private readonly ReaderWriterLockSlim _instanceLocker = new(LockRecursionPolicy.NoRecursion); + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// A that can be used to iterate through the collection. + /// + /// 1 + public IEnumerator GetEnumerator() => GetThreadSafeClone().GetEnumerator(); + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + /// 2 + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + /// - /// A thread-safe representation of a . - /// Enumerating this collection is thread-safe and will only operate on a clone that is generated before returning the enumerator. + /// Removes the first occurrence of a specific object from the + /// . /// - /// - [Serializable] - public class ConcurrentHashSet : ICollection + /// + /// true if was successfully removed from the + /// ; otherwise, false. This method also returns false if + /// is not found in the original . + /// + /// The object to remove from the . + /// + /// The is + /// read-only. + /// + public bool Remove(T item) { - private readonly HashSet _innerSet = new HashSet(); - private readonly ReaderWriterLockSlim _instanceLocker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// A that can be used to iterate through the collection. - /// - /// 1 - public IEnumerator GetEnumerator() + try { - return GetThreadSafeClone().GetEnumerator(); + _instanceLocker.EnterWriteLock(); + return _innerSet.Remove(item); } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - /// 2 - IEnumerator IEnumerable.GetEnumerator() + finally { - return GetEnumerator(); + if (_instanceLocker.IsWriteLockHeld) + { + _instanceLocker.ExitWriteLock(); + } } + } - /// - /// Removes the first occurrence of a specific object from the . - /// - /// - /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . - /// - /// The object to remove from the .The is read-only. - public bool Remove(T item) + + /// + /// Gets the number of elements contained in the . + /// + /// + /// The number of elements contained in the . + /// + /// 2 + public int Count + { + get { try { - _instanceLocker.EnterWriteLock(); - return _innerSet.Remove(item); + _instanceLocker.EnterReadLock(); + return _innerSet.Count; } finally { - if (_instanceLocker.IsWriteLockHeld) - _instanceLocker.ExitWriteLock(); + if (_instanceLocker.IsReadLockHeld) + { + _instanceLocker.ExitReadLock(); + } } } + } + /// + /// Gets a value indicating whether the is read-only. + /// + /// + /// true if the is read-only; otherwise, false. + /// + public bool IsReadOnly => false; - /// - /// Gets the number of elements contained in the . - /// - /// - /// The number of elements contained in the . - /// - /// 2 - public int Count + /// + /// Adds an item to the . + /// + /// The object to add to the . + /// + /// The is + /// read-only. + /// + public void Add(T item) + { + try { - get + _instanceLocker.EnterWriteLock(); + _innerSet.Add(item); + } + finally + { + if (_instanceLocker.IsWriteLockHeld) { - try - { - _instanceLocker.EnterReadLock(); - return _innerSet.Count; - } - finally - { - if (_instanceLocker.IsReadLockHeld) - _instanceLocker.ExitReadLock(); - } - + _instanceLocker.ExitWriteLock(); } } + } - /// - /// Gets a value indicating whether the is read-only. - /// - /// - /// true if the is read-only; otherwise, false. - /// - public bool IsReadOnly => false; - - /// - /// Adds an item to the . - /// - /// The object to add to the .The is read-only. - public void Add(T item) + /// + /// Removes all items from the . + /// + /// + /// The is + /// read-only. + /// + public void Clear() + { + try { - try - { - _instanceLocker.EnterWriteLock(); - _innerSet.Add(item); - } - finally + _instanceLocker.EnterWriteLock(); + _innerSet.Clear(); + } + finally + { + if (_instanceLocker.IsWriteLockHeld) { - if (_instanceLocker.IsWriteLockHeld) - _instanceLocker.ExitWriteLock(); + _instanceLocker.ExitWriteLock(); } } + } - /// - /// Attempts to add an item to the collection - /// - /// - /// - public bool TryAdd(T item) + /// + /// Determines whether the contains a specific value. + /// + /// + /// true if is found in the ; + /// otherwise, false. + /// + /// The object to locate in the . + public bool Contains(T item) + { + try { - if (Contains(item)) return false; - try - { - _instanceLocker.EnterWriteLock(); - - //double check - if (_innerSet.Contains(item)) return false; - _innerSet.Add(item); - return true; - } - finally + _instanceLocker.EnterReadLock(); + return _innerSet.Contains(item); + } + finally + { + if (_instanceLocker.IsReadLockHeld) { - if (_instanceLocker.IsWriteLockHeld) - _instanceLocker.ExitWriteLock(); + _instanceLocker.ExitReadLock(); } } + } + + /// + /// Copies the elements of the to an + /// , starting at a specified index. + /// + /// + /// The one-dimensional that is the destination of the elements copied + /// from the . The array must have + /// zero-based indexing. + /// + /// The zero-based index in at which copying begins. + /// + /// is a null reference (Nothing in Visual + /// Basic). + /// + /// is less than zero. + /// + /// is equal to or greater than the length of the + /// -or- The number of elements in the source + /// is greater than the available space from + /// to the end of the destination . + /// + public void CopyTo(T[] array, int index) + { + HashSet clone = GetThreadSafeClone(); + clone.CopyTo(array, index); + } - /// - /// Removes all items from the . - /// - /// The is read-only. - public void Clear() + /// + /// Attempts to add an item to the collection + /// + /// + /// + public bool TryAdd(T item) + { + if (Contains(item)) { - try - { - _instanceLocker.EnterWriteLock(); - _innerSet.Clear(); - } - finally - { - if (_instanceLocker.IsWriteLockHeld) - _instanceLocker.ExitWriteLock(); - } + return false; } - /// - /// Determines whether the contains a specific value. - /// - /// - /// true if is found in the ; otherwise, false. - /// - /// The object to locate in the . - public bool Contains(T item) + try { - try + _instanceLocker.EnterWriteLock(); + + //double check + if (_innerSet.Contains(item)) { - _instanceLocker.EnterReadLock(); - return _innerSet.Contains(item); + return false; } - finally + + _innerSet.Add(item); + return true; + } + finally + { + if (_instanceLocker.IsWriteLockHeld) { - if (_instanceLocker.IsReadLockHeld) - _instanceLocker.ExitReadLock(); + _instanceLocker.ExitWriteLock(); } } + } - /// - /// Copies the elements of the to an , starting at a specified index. - /// - /// The one-dimensional that is the destination of the elements copied from the . The array must have zero-based indexing.The zero-based index in at which copying begins. is a null reference (Nothing in Visual Basic). is less than zero. is equal to or greater than the length of the -or- The number of elements in the source is greater than the available space from to the end of the destination . - public void CopyTo(T[] array, int index) + private HashSet GetThreadSafeClone() + { + HashSet? clone = null; + try { - var clone = GetThreadSafeClone(); - clone.CopyTo(array, index); + _instanceLocker.EnterReadLock(); + clone = new HashSet(_innerSet, _innerSet.Comparer); } - - private HashSet GetThreadSafeClone() + finally { - HashSet? clone = null; - try - { - _instanceLocker.EnterReadLock(); - clone = new HashSet(_innerSet, _innerSet.Comparer); - } - finally + if (_instanceLocker.IsReadLockHeld) { - if (_instanceLocker.IsReadLockHeld) - _instanceLocker.ExitReadLock(); + _instanceLocker.ExitReadLock(); } - return clone; } - /// - /// Copies the elements of the to an , starting at a particular index. - /// - /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. The zero-based index in at which copying begins. is null. is less than zero. is multidimensional.-or- The number of elements in the source is greater than the available space from to the end of the destination . The type of the source cannot be cast automatically to the type of the destination . 2 - public void CopyTo(Array array, int index) - { - var clone = GetThreadSafeClone(); - Array.Copy(clone.ToArray(), 0, array, index, clone.Count); - } + return clone; + } + + /// + /// Copies the elements of the to an , + /// starting at a particular index. + /// + /// + /// The one-dimensional that is the destination of the elements copied + /// from . The must have zero-based + /// indexing. + /// + /// The zero-based index in at which copying begins. + /// is null. + /// is less than zero. + /// + /// is multidimensional.-or- The number of elements + /// in the source is greater than the available space from + /// to the end of the destination . + /// + /// + /// The type of the source + /// cannot be cast automatically to the type of the destination . + /// + /// 2 + public void CopyTo(Array array, int index) + { + HashSet clone = GetThreadSafeClone(); + Array.Copy(clone.ToArray(), 0, array, index, clone.Count); } } diff --git a/src/Umbraco.Core/Collections/DeepCloneableList.cs b/src/Umbraco.Core/Collections/DeepCloneableList.cs index db7677153cb9..cff34be3b864 100644 --- a/src/Umbraco.Core/Collections/DeepCloneableList.cs +++ b/src/Umbraco.Core/Collections/DeepCloneableList.cs @@ -1,159 +1,136 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; +using System.ComponentModel; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Collections +namespace Umbraco.Cms.Core.Collections; + +/// +/// A List that can be deep cloned with deep cloned elements and can reset the collection's items dirty flags +/// +/// +public class DeepCloneableList : List, IDeepCloneable, IRememberBeingDirty { - /// - /// A List that can be deep cloned with deep cloned elements and can reset the collection's items dirty flags - /// - /// - public class DeepCloneableList : List, IDeepCloneable, IRememberBeingDirty - { - private readonly ListCloneBehavior _listCloneBehavior; + private readonly ListCloneBehavior _listCloneBehavior; - public DeepCloneableList(ListCloneBehavior listCloneBehavior) - { - _listCloneBehavior = listCloneBehavior; - } + public DeepCloneableList(ListCloneBehavior listCloneBehavior) => _listCloneBehavior = listCloneBehavior; - public DeepCloneableList(IEnumerable collection, ListCloneBehavior listCloneBehavior) : base(collection) - { - _listCloneBehavior = listCloneBehavior; - } + public DeepCloneableList(IEnumerable collection, ListCloneBehavior listCloneBehavior) : base(collection) => + _listCloneBehavior = listCloneBehavior; - /// - /// Default behavior is CloneOnce - /// - /// - public DeepCloneableList(IEnumerable collection) - : this(collection, ListCloneBehavior.CloneOnce) - { - } + /// + /// Default behavior is CloneOnce + /// + /// + public DeepCloneableList(IEnumerable collection) + : this(collection, ListCloneBehavior.CloneOnce) + { + } - /// - /// Creates a new list and adds each element as a deep cloned element if it is of type IDeepCloneable - /// - /// - public object DeepClone() + /// + /// Creates a new list and adds each element as a deep cloned element if it is of type IDeepCloneable + /// + /// + public object DeepClone() + { + switch (_listCloneBehavior) { - switch (_listCloneBehavior) - { - case ListCloneBehavior.CloneOnce: - //we are cloning once, so create a new list in none mode - // and deep clone all items into it - var newList = new DeepCloneableList(ListCloneBehavior.None); - foreach (var item in this) + case ListCloneBehavior.CloneOnce: + //we are cloning once, so create a new list in none mode + // and deep clone all items into it + var newList = new DeepCloneableList(ListCloneBehavior.None); + foreach (T item in this) + { + if (item is IDeepCloneable dc) { - if (item is IDeepCloneable dc) - { - newList.Add((T)dc.DeepClone()); - } - else - { - newList.Add(item); - } + newList.Add((T)dc.DeepClone()); } - return newList; - case ListCloneBehavior.None: - //we are in none mode, so just return a new list with the same items - return new DeepCloneableList(this, ListCloneBehavior.None); - case ListCloneBehavior.Always: - //always clone to new list - var newList2 = new DeepCloneableList(ListCloneBehavior.Always); - foreach (var item in this) + else { - if (item is IDeepCloneable dc) - { - newList2.Add((T)dc.DeepClone()); - } - else - { - newList2.Add(item); - } + newList.Add(item); } - return newList2; - default: - throw new ArgumentOutOfRangeException(); - } - } + } + + return newList; + case ListCloneBehavior.None: + //we are in none mode, so just return a new list with the same items + return new DeepCloneableList(this, ListCloneBehavior.None); + case ListCloneBehavior.Always: + //always clone to new list + var newList2 = new DeepCloneableList(ListCloneBehavior.Always); + foreach (T item in this) + { + if (item is IDeepCloneable dc) + { + newList2.Add((T)dc.DeepClone()); + } + else + { + newList2.Add(item); + } + } - #region IRememberBeingDirty - public bool IsDirty() - { - return this.OfType().Any(x => x.IsDirty()); + return newList2; + default: + throw new ArgumentOutOfRangeException(); } + } - public bool WasDirty() - { - return this.OfType().Any(x => x.WasDirty()); - } + #region IRememberBeingDirty - /// - /// Always return false, the list has no properties that can be dirty. - public bool IsPropertyDirty(string propName) - { - return false; - } + public bool IsDirty() => this.OfType().Any(x => x.IsDirty()); - /// - /// Always return false, the list has no properties that can be dirty. - public bool WasPropertyDirty(string propertyName) - { - return false; - } + public bool WasDirty() => this.OfType().Any(x => x.WasDirty()); - /// - /// Always return an empty enumerable, the list has no properties that can be dirty. - public IEnumerable GetDirtyProperties() - { - return Enumerable.Empty(); - } + /// + /// Always return false, the list has no properties that can be dirty. + public bool IsPropertyDirty(string propName) => false; - public void ResetDirtyProperties() - { - foreach (var dc in this.OfType()) - { - dc.ResetDirtyProperties(); - } - } + /// + /// Always return false, the list has no properties that can be dirty. + public bool WasPropertyDirty(string propertyName) => false; - public void DisableChangeTracking() - { - // noop - } + /// + /// Always return an empty enumerable, the list has no properties that can be dirty. + public IEnumerable GetDirtyProperties() => Enumerable.Empty(); - public void EnableChangeTracking() + public void ResetDirtyProperties() + { + foreach (IRememberBeingDirty dc in this.OfType()) { - // noop + dc.ResetDirtyProperties(); } + } - public void ResetWereDirtyProperties() - { - foreach (var dc in this.OfType()) - { - dc.ResetWereDirtyProperties(); - } - } + public void DisableChangeTracking() + { + // noop + } - public void ResetDirtyProperties(bool rememberDirty) + public void EnableChangeTracking() + { + // noop + } + + public void ResetWereDirtyProperties() + { + foreach (IRememberBeingDirty dc in this.OfType()) { - foreach (var dc in this.OfType()) - { - dc.ResetDirtyProperties(rememberDirty); - } + dc.ResetWereDirtyProperties(); } + } - /// Always return an empty enumerable, the list has no properties that can be dirty. - public IEnumerable GetWereDirtyProperties() + public void ResetDirtyProperties(bool rememberDirty) + { + foreach (IRememberBeingDirty dc in this.OfType()) { - return Enumerable.Empty(); + dc.ResetDirtyProperties(rememberDirty); } - - public event PropertyChangedEventHandler? PropertyChanged; // noop - #endregion } + + /// Always return an empty enumerable, the list has no properties that can be dirty. + public IEnumerable GetWereDirtyProperties() => Enumerable.Empty(); + + public event PropertyChangedEventHandler? PropertyChanged; // noop + + #endregion } diff --git a/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs b/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs index f4702f01247d..26e50675fe10 100644 --- a/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs +++ b/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs @@ -1,41 +1,40 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; using System.Collections.Specialized; -namespace Umbraco.Cms.Core.Collections +namespace Umbraco.Cms.Core.Collections; + +/// +/// Allows clearing all event handlers +/// +/// +public class EventClearingObservableCollection : ObservableCollection, INotifyCollectionChanged { - /// - /// Allows clearing all event handlers - /// - /// - public class EventClearingObservableCollection : ObservableCollection, INotifyCollectionChanged - { - public EventClearingObservableCollection() - { - } + // need to explicitly implement with event accessor syntax in order to override in order to to clear + // c# events are weird, they do not behave the same way as other c# things that are 'virtual', + // a good article is here: https://medium.com/@unicorn_dev/virtual-events-in-c-something-went-wrong-c6f6f5fbe252 + // and https://stackoverflow.com/questions/2268065/c-sharp-language-design-explicit-interface-implementation-of-an-event + private NotifyCollectionChangedEventHandler? _changed; - public EventClearingObservableCollection(List list) : base(list) - { - } + public EventClearingObservableCollection() + { + } - public EventClearingObservableCollection(IEnumerable collection) : base(collection) - { - } + public EventClearingObservableCollection(List list) : base(list) + { + } - // need to explicitly implement with event accessor syntax in order to override in order to to clear - // c# events are weird, they do not behave the same way as other c# things that are 'virtual', - // a good article is here: https://medium.com/@unicorn_dev/virtual-events-in-c-something-went-wrong-c6f6f5fbe252 - // and https://stackoverflow.com/questions/2268065/c-sharp-language-design-explicit-interface-implementation-of-an-event - private NotifyCollectionChangedEventHandler? _changed; - event NotifyCollectionChangedEventHandler? INotifyCollectionChanged.CollectionChanged - { - add { _changed += value; } - remove { _changed -= value; } - } + public EventClearingObservableCollection(IEnumerable collection) : base(collection) + { + } - /// - /// Clears all event handlers for the event - /// - public void ClearCollectionChangedEvents() => _changed = null; + event NotifyCollectionChangedEventHandler? INotifyCollectionChanged.CollectionChanged + { + add => _changed += value; + remove => _changed -= value; } + + /// + /// Clears all event handlers for the event + /// + public void ClearCollectionChangedEvents() => _changed = null; } diff --git a/src/Umbraco.Core/Collections/ListCloneBehavior.cs b/src/Umbraco.Core/Collections/ListCloneBehavior.cs index 148141f78331..1588d56773eb 100644 --- a/src/Umbraco.Core/Collections/ListCloneBehavior.cs +++ b/src/Umbraco.Core/Collections/ListCloneBehavior.cs @@ -1,20 +1,19 @@ -namespace Umbraco.Cms.Core.Collections +namespace Umbraco.Cms.Core.Collections; + +public enum ListCloneBehavior { - public enum ListCloneBehavior - { - /// - /// When set, DeepClone will clone the items one time and the result list behavior will be None - /// - CloneOnce, + /// + /// When set, DeepClone will clone the items one time and the result list behavior will be None + /// + CloneOnce, - /// - /// When set, DeepClone will not clone any items - /// - None, + /// + /// When set, DeepClone will not clone any items + /// + None, - /// - /// When set, DeepClone will always clone all items - /// - Always - } + /// + /// When set, DeepClone will always clone all items + /// + Always } diff --git a/src/Umbraco.Core/Collections/ObservableDictionary.cs b/src/Umbraco.Core/Collections/ObservableDictionary.cs index 1ea6a827c4b7..186e16bff736 100644 --- a/src/Umbraco.Core/Collections/ObservableDictionary.cs +++ b/src/Umbraco.Core/Collections/ObservableDictionary.cs @@ -1,257 +1,251 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; using System.Collections.Specialized; -using System.Linq; -namespace Umbraco.Cms.Core.Collections +namespace Umbraco.Cms.Core.Collections; + +/// +/// An ObservableDictionary +/// +/// +/// Assumes that the key will not change and is unique for each element in the collection. +/// Collection is not thread-safe, so calls should be made single-threaded. +/// +/// The type of elements contained in the BindableCollection +/// The type of the indexing key +public class ObservableDictionary : ObservableCollection, IReadOnlyDictionary, + IDictionary, INotifyCollectionChanged + where TKey : notnull { + // need to explicitly implement with event accessor syntax in order to override in order to to clear + // c# events are weird, they do not behave the same way as other c# things that are 'virtual', + // a good article is here: https://medium.com/@unicorn_dev/virtual-events-in-c-something-went-wrong-c6f6f5fbe252 + // and https://stackoverflow.com/questions/2268065/c-sharp-language-design-explicit-interface-implementation-of-an-event + private NotifyCollectionChangedEventHandler? _changed; /// - /// An ObservableDictionary + /// Create new ObservableDictionary /// - /// - /// Assumes that the key will not change and is unique for each element in the collection. - /// Collection is not thread-safe, so calls should be made single-threaded. - /// - /// The type of elements contained in the BindableCollection - /// The type of the indexing key - public class ObservableDictionary : ObservableCollection, IReadOnlyDictionary, IDictionary, INotifyCollectionChanged - where TKey : notnull + /// Selector function to create key from value + /// The equality comparer to use when comparing keys, or null to use the default comparer. + public ObservableDictionary(Func keySelector, IEqualityComparer? equalityComparer = null) { - protected Dictionary Indecies { get; } - protected Func KeySelector { get; } - - /// - /// Create new ObservableDictionary - /// - /// Selector function to create key from value - /// The equality comparer to use when comparing keys, or null to use the default comparer. - public ObservableDictionary(Func keySelector, IEqualityComparer? equalityComparer = null) + KeySelector = keySelector ?? throw new ArgumentException(nameof(keySelector)); + Indecies = new Dictionary(equalityComparer); + } + + protected Dictionary Indecies { get; } + protected Func KeySelector { get; } + + public bool Remove(TKey key) + { + if (!Indecies.ContainsKey(key)) { - KeySelector = keySelector ?? throw new ArgumentException(nameof(keySelector)); - Indecies = new Dictionary(equalityComparer); + return false; } - #region Protected Methods + RemoveAt(Indecies[key]); + return true; + } - protected override void InsertItem(int index, TValue item) - { - var key = KeySelector(item); - if (Indecies.ContainsKey(key)) - throw new ArgumentException($"An element with the same key '{key}' already exists in the dictionary.", nameof(item)); + event NotifyCollectionChangedEventHandler? INotifyCollectionChanged.CollectionChanged + { + add => _changed += value; + remove => _changed -= value; + } + + public bool ContainsKey(TKey key) => Indecies.ContainsKey(key); - if (index != Count) + /// + /// Gets or sets the element with the specified key. If setting a new value, new value must have same key. + /// + /// Key of element to replace + /// + public TValue this[TKey key] + { + get => this[Indecies[key]]; + set + { + //confirm key matches + if (!KeySelector(value)!.Equals(key)) { - foreach (var k in Indecies.Keys.Where(k => Indecies[k] >= index).ToList()) - { - Indecies[k]++; - } + throw new InvalidOperationException("Key of new value does not match."); } - base.InsertItem(index, item); - Indecies[key] = index; + if (!Indecies.ContainsKey(key)) + { + Add(value); + } + else + { + this[Indecies[key]] = value; + } } + } + + /// + /// Clears all event handlers + /// + public void ClearCollectionChangedEvents() => _changed = null; - protected override void ClearItems() + /// + /// Replaces element at given key with new value. New value must have same key. + /// + /// Key of element to replace + /// New value + /// + /// False if key not found + public bool Replace(TKey key, TValue value) + { + if (!Indecies.ContainsKey(key)) { - base.ClearItems(); - Indecies.Clear(); + return false; } - protected override void RemoveItem(int index) + //confirm key matches + if (!KeySelector(value)!.Equals(key)) { - var item = this[index]; - var key = KeySelector(item); - - base.RemoveItem(index); - - Indecies.Remove(key); - - foreach (var k in Indecies.Keys.Where(k => Indecies[k] > index).ToList()) - { - Indecies[k]--; - } + throw new InvalidOperationException("Key of new value does not match."); } - #endregion + this[Indecies[key]] = value; + return true; + } - // need to explicitly implement with event accessor syntax in order to override in order to to clear - // c# events are weird, they do not behave the same way as other c# things that are 'virtual', - // a good article is here: https://medium.com/@unicorn_dev/virtual-events-in-c-something-went-wrong-c6f6f5fbe252 - // and https://stackoverflow.com/questions/2268065/c-sharp-language-design-explicit-interface-implementation-of-an-event - private NotifyCollectionChangedEventHandler? _changed; - event NotifyCollectionChangedEventHandler? INotifyCollectionChanged.CollectionChanged + public void ReplaceAll(IEnumerable values) + { + if (values == null) { - add { _changed += value; } - remove { _changed -= value; } + throw new ArgumentNullException(nameof(values)); } - /// - /// Clears all event handlers - /// - public void ClearCollectionChangedEvents() => _changed = null; + Clear(); - public bool ContainsKey(TKey key) + foreach (TValue value in values) { - return Indecies.ContainsKey(key); + Add(value); } + } - /// - /// Gets or sets the element with the specified key. If setting a new value, new value must have same key. - /// - /// Key of element to replace - /// - public TValue this[TKey key] + /// + /// Allows us to change the key of an item + /// + /// + /// + public void ChangeKey(TKey currentKey, TKey newKey) + { + if (!Indecies.ContainsKey(currentKey)) { - - get => this[Indecies[key]]; - set - { - //confirm key matches - if (!KeySelector(value)!.Equals(key)) - throw new InvalidOperationException("Key of new value does not match."); - - if (!Indecies.ContainsKey(key)) - { - Add(value); - } - else - { - this[Indecies[key]] = value; - } - } + throw new InvalidOperationException($"No item with the key '{currentKey}' was found in the dictionary."); } - /// - /// Replaces element at given key with new value. New value must have same key. - /// - /// Key of element to replace - /// New value - /// - /// - /// False if key not found - public bool Replace(TKey key, TValue value) + if (ContainsKey(newKey)) { - if (!Indecies.ContainsKey(key)) return false; + throw new ArgumentException($"An element with the same key '{newKey}' already exists in the dictionary.", + nameof(newKey)); + } - //confirm key matches - if (!KeySelector(value)!.Equals(key)) - throw new InvalidOperationException("Key of new value does not match."); + var currentIndex = Indecies[currentKey]; - this[Indecies[key]] = value; - return true; + Indecies.Remove(currentKey); + Indecies.Add(newKey, currentIndex); + } - } + #region Protected Methods - public void ReplaceAll(IEnumerable values) + protected override void InsertItem(int index, TValue item) + { + TKey key = KeySelector(item); + if (Indecies.ContainsKey(key)) { - if (values == null) throw new ArgumentNullException(nameof(values)); - - Clear(); + throw new ArgumentException($"An element with the same key '{key}' already exists in the dictionary.", + nameof(item)); + } - foreach (var value in values) + if (index != Count) + { + foreach (TKey k in Indecies.Keys.Where(k => Indecies[k] >= index).ToList()) { - Add(value); + Indecies[k]++; } } - public bool Remove(TKey key) - { - if (!Indecies.ContainsKey(key)) return false; - - RemoveAt(Indecies[key]); - return true; + base.InsertItem(index, item); + Indecies[key] = index; + } - } + protected override void ClearItems() + { + base.ClearItems(); + Indecies.Clear(); + } - /// - /// Allows us to change the key of an item - /// - /// - /// - public void ChangeKey(TKey currentKey, TKey newKey) - { - if (!Indecies.ContainsKey(currentKey)) - { - throw new InvalidOperationException($"No item with the key '{currentKey}' was found in the dictionary."); - } + protected override void RemoveItem(int index) + { + TValue item = this[index]; + TKey key = KeySelector(item); - if (ContainsKey(newKey)) - { - throw new ArgumentException($"An element with the same key '{newKey}' already exists in the dictionary.", nameof(newKey)); - } + base.RemoveItem(index); - var currentIndex = Indecies[currentKey]; + Indecies.Remove(key); - Indecies.Remove(currentKey); - Indecies.Add(newKey, currentIndex); + foreach (TKey k in Indecies.Keys.Where(k => Indecies[k] > index).ToList()) + { + Indecies[k]--; } + } - #region IDictionary and IReadOnlyDictionary implementation + #endregion - public bool TryGetValue(TKey key, out TValue val) + #region IDictionary and IReadOnlyDictionary implementation + + public bool TryGetValue(TKey key, out TValue val) + { + if (Indecies.TryGetValue(key, out var index)) { - if (Indecies.TryGetValue(key, out var index)) - { - val = this[index]; - return true; - } - val = default!; - return false; + val = this[index]; + return true; } - /// - /// Returns all keys - /// - public IEnumerable Keys => Indecies.Keys; + val = default!; + return false; + } - /// - /// Returns all values - /// - public IEnumerable Values => base.Items; + /// + /// Returns all keys + /// + public IEnumerable Keys => Indecies.Keys; - ICollection IDictionary.Keys => Indecies.Keys; + /// + /// Returns all values + /// + public IEnumerable Values => Items; - //this will never be used - ICollection IDictionary.Values => Values.ToList(); + ICollection IDictionary.Keys => Indecies.Keys; - bool ICollection>.IsReadOnly => false; + //this will never be used + ICollection IDictionary.Values => Values.ToList(); - IEnumerator> IEnumerable>.GetEnumerator() - { - foreach (var i in Values) - { - var key = KeySelector(i); - yield return new KeyValuePair(key, i); - } - } + bool ICollection>.IsReadOnly => false; - void IDictionary.Add(TKey key, TValue value) + IEnumerator> IEnumerable>.GetEnumerator() + { + foreach (TValue i in Values) { - Add(value); + TKey key = KeySelector(i); + yield return new KeyValuePair(key, i); } + } - void ICollection>.Add(KeyValuePair item) - { - Add(item.Value); - } + void IDictionary.Add(TKey key, TValue value) => Add(value); - bool ICollection>.Contains(KeyValuePair item) - { - return ContainsKey(item.Key); - } + void ICollection>.Add(KeyValuePair item) => Add(item.Value); - void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) - { - throw new NotImplementedException(); - } + bool ICollection>.Contains(KeyValuePair item) => ContainsKey(item.Key); - bool ICollection>.Remove(KeyValuePair item) - { - return Remove(item.Key); - } + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) => + throw new NotImplementedException(); - #endregion - } + bool ICollection>.Remove(KeyValuePair item) => Remove(item.Key); + + #endregion } diff --git a/src/Umbraco.Core/Collections/OrderedHashSet.cs b/src/Umbraco.Core/Collections/OrderedHashSet.cs index e5a34083be8a..367ae7546ae7 100644 --- a/src/Umbraco.Core/Collections/OrderedHashSet.cs +++ b/src/Umbraco.Core/Collections/OrderedHashSet.cs @@ -1,50 +1,45 @@ using System.Collections.ObjectModel; -namespace Umbraco.Cms.Core.Collections +namespace Umbraco.Cms.Core.Collections; + +/// +/// A custom collection similar to HashSet{T} which only contains unique items, however this collection keeps items in +/// order +/// and is customizable to keep the newest or oldest equatable item +/// +/// +public class OrderedHashSet : KeyedCollection where T : notnull { - /// - /// A custom collection similar to HashSet{T} which only contains unique items, however this collection keeps items in order - /// and is customizable to keep the newest or oldest equatable item - /// - /// - public class OrderedHashSet : KeyedCollection where T : notnull - { - private readonly bool _keepOldest; + private readonly bool _keepOldest; - public OrderedHashSet(bool keepOldest = true) + public OrderedHashSet(bool keepOldest = true) => _keepOldest = keepOldest; + + protected override void InsertItem(int index, T item) + { + if (Dictionary == null) { - _keepOldest = keepOldest; + base.InsertItem(index, item); } - - protected override void InsertItem(int index, T item) + else { - if (Dictionary == null) + var exists = Dictionary.ContainsKey(item); + + //if we want to keep the newest, then we need to remove the old item and add the new one + if (exists == false) { base.InsertItem(index, item); } - else + else if (_keepOldest == false) { - var exists = Dictionary.ContainsKey(item); - - //if we want to keep the newest, then we need to remove the old item and add the new one - if (exists == false) + if (Remove(item)) { - base.InsertItem(index, item); + index--; } - else if(_keepOldest == false) - { - if (Remove(item)) - { - index--; - } - base.InsertItem(index, item); - } - } - } - protected override T GetKeyForItem(T item) - { - return item; + base.InsertItem(index, item); + } } } + + protected override T GetKeyForItem(T item) => item; } diff --git a/src/Umbraco.Core/Collections/StackQueue.cs b/src/Umbraco.Core/Collections/StackQueue.cs index 242766771d3b..7d4d8a56dbd5 100644 --- a/src/Umbraco.Core/Collections/StackQueue.cs +++ b/src/Umbraco.Core/Collections/StackQueue.cs @@ -1,45 +1,46 @@ -namespace Umbraco.Cms.Core.Collections +namespace Umbraco.Cms.Core.Collections; + +/// +/// Collection that can be both a queue and a stack. +/// +/// +public class StackQueue { - /// - /// Collection that can be both a queue and a stack. - /// - /// - public class StackQueue - { - private readonly LinkedList _linkedList = new (); + private readonly LinkedList _linkedList = new(); - public int Count => _linkedList.Count; + public int Count => _linkedList.Count; - public void Clear() => _linkedList.Clear(); + public void Clear() => _linkedList.Clear(); - public void Push(T? obj) => _linkedList.AddFirst(obj); + public void Push(T? obj) => _linkedList.AddFirst(obj); - public void Enqueue(T? obj) => _linkedList.AddFirst(obj); + public void Enqueue(T? obj) => _linkedList.AddFirst(obj); - public T Pop() + public T Pop() + { + var obj = default(T); + if (_linkedList.First is not null) { - T? obj = default(T); - if (_linkedList.First is not null) - { - obj = _linkedList.First.Value; - } - _linkedList.RemoveFirst(); - return obj!; + obj = _linkedList.First.Value; } - public T Dequeue() + _linkedList.RemoveFirst(); + return obj!; + } + + public T Dequeue() + { + var obj = default(T); + if (_linkedList.Last is not null) { - T? obj = default(T); - if (_linkedList.Last is not null) - { - obj = _linkedList.Last.Value; - } - _linkedList.RemoveLast(); - return obj!; + obj = _linkedList.Last.Value; } - public T? PeekStack() => _linkedList.First is not null ? _linkedList.First.Value : default; - - public T? PeekQueue() => _linkedList.Last is not null ? _linkedList.Last.Value : default; + _linkedList.RemoveLast(); + return obj!; } + + public T? PeekStack() => _linkedList.First is not null ? _linkedList.First.Value : default; + + public T? PeekQueue() => _linkedList.Last is not null ? _linkedList.Last.Value : default; } diff --git a/src/Umbraco.Core/Collections/TopoGraph.cs b/src/Umbraco.Core/Collections/TopoGraph.cs index 11fd155684eb..999cfef6ed55 100644 --- a/src/Umbraco.Core/Collections/TopoGraph.cs +++ b/src/Umbraco.Core/Collections/TopoGraph.cs @@ -1,133 +1,143 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Collections; -namespace Umbraco.Cms.Core.Collections +public class TopoGraph { - public class TopoGraph - { - internal const string CycleDependencyError = "Cyclic dependency."; - internal const string MissingDependencyError = "Missing dependency."; + internal const string CycleDependencyError = "Cyclic dependency."; + internal const string MissingDependencyError = "Missing dependency."; - public class Node - { - public Node(TKey key, TItem item, IEnumerable dependencies) - { - Key = key; - Item = item; - Dependencies = dependencies; - } + public static Node CreateNode(TKey key, TItem item, IEnumerable dependencies) => + new(key, item, dependencies); - public TKey Key { get; } - public TItem Item { get; } - public IEnumerable Dependencies { get; } + public class Node + { + public Node(TKey key, TItem item, IEnumerable dependencies) + { + Key = key; + Item = item; + Dependencies = dependencies; } - public static Node CreateNode(TKey key, TItem item, IEnumerable dependencies) - => new Node(key, item, dependencies); + public TKey Key { get; } + public TItem Item { get; } + public IEnumerable Dependencies { get; } } +} + +/// +/// Represents a generic DAG that can be topologically sorted. +/// +/// The type of the keys. +/// The type of the items. +public class TopoGraph : TopoGraph + where TKey : notnull +{ + private readonly Func?> _getDependencies; + private readonly Func _getKey; + private readonly Dictionary _items = new(); /// - /// Represents a generic DAG that can be topologically sorted. + /// Initializes a new instance of the class. /// - /// The type of the keys. - /// The type of the items. - public class TopoGraph : TopoGraph - where TKey : notnull + /// A method that returns the key of an item. + /// A method that returns the dependency keys of an item. + public TopoGraph(Func getKey, Func?> getDependencies) { - private readonly Func _getKey; - private readonly Func?> _getDependencies; - private readonly Dictionary _items = new Dictionary(); - - /// - /// Initializes a new instance of the class. - /// - /// A method that returns the key of an item. - /// A method that returns the dependency keys of an item. - public TopoGraph(Func getKey, Func?> getDependencies) - { - _getKey = getKey; - _getDependencies = getDependencies; - } + _getKey = getKey; + _getDependencies = getDependencies; + } - /// - /// Adds an item to the graph. - /// - /// The item. - public void AddItem(TItem item) - { - var key = _getKey(item); - _items[key] = item; - } + /// + /// Adds an item to the graph. + /// + /// The item. + public void AddItem(TItem item) + { + TKey key = _getKey(item); + _items[key] = item; + } - /// - /// Adds items to the graph. - /// - /// The items. - public void AddItems(IEnumerable items) + /// + /// Adds items to the graph. + /// + /// The items. + public void AddItems(IEnumerable items) + { + foreach (TItem item in items) { - foreach (var item in items) - AddItem(item); + AddItem(item); } + } - /// - /// Gets the sorted items. - /// - /// A value indicating whether to throw on cycles, or just ignore the branch. - /// A value indicating whether to throw on missing dependency, or just ignore the dependency. - /// A value indicating whether to reverse the order. - /// The (topologically) sorted items. - public IEnumerable GetSortedItems(bool throwOnCycle = true, bool throwOnMissing = true, bool reverse = false) - { - var sorted = new TItem[_items.Count]; - var visited = new HashSet(); - var index = reverse ? _items.Count - 1 : 0; - var incr = reverse ? -1 : +1; - - foreach (var item in _items.Values) - Visit(item, visited, sorted, ref index, incr, throwOnCycle, throwOnMissing); - - return sorted; - } + /// + /// Gets the sorted items. + /// + /// A value indicating whether to throw on cycles, or just ignore the branch. + /// A value indicating whether to throw on missing dependency, or just ignore the dependency. + /// A value indicating whether to reverse the order. + /// The (topologically) sorted items. + public IEnumerable GetSortedItems(bool throwOnCycle = true, bool throwOnMissing = true, bool reverse = false) + { + var sorted = new TItem[_items.Count]; + var visited = new HashSet(); + var index = reverse ? _items.Count - 1 : 0; + var incr = reverse ? -1 : +1; - private static bool Contains(TItem[] items, TItem item, int start, int count) + foreach (TItem item in _items.Values) { - return Array.IndexOf(items, item, start, count) >= 0; + Visit(item, visited, sorted, ref index, incr, throwOnCycle, throwOnMissing); } - private void Visit(TItem item, ISet visited, TItem[] sorted, ref int index, int incr, bool throwOnCycle, bool throwOnMissing) + return sorted; + } + + private static bool Contains(TItem[] items, TItem item, int start, int count) => + Array.IndexOf(items, item, start, count) >= 0; + + private void Visit(TItem item, ISet visited, TItem[] sorted, ref int index, int incr, bool throwOnCycle, + bool throwOnMissing) + { + if (visited.Contains(item)) { - if (visited.Contains(item)) + // visited but not sorted yet = cycle + var start = incr > 0 ? 0 : index; + var count = incr > 0 ? index : sorted.Length - index; + if (throwOnCycle && Contains(sorted, item, start, count) == false) { - // visited but not sorted yet = cycle - var start = incr > 0 ? 0 : index; - var count = incr > 0 ? index : sorted.Length - index; - if (throwOnCycle && Contains(sorted, item, start, count) == false) - throw new Exception(CycleDependencyError +": " + item); - return; + throw new Exception(CycleDependencyError + ": " + item); } - visited.Add(item); + return; + } - var keys = _getDependencies(item); - var dependencies = keys == null ? null : FindDependencies(keys, throwOnMissing); + visited.Add(item); - if (dependencies != null) - foreach (var dep in dependencies) - Visit(dep, visited, sorted, ref index, incr, throwOnCycle, throwOnMissing); + IEnumerable keys = _getDependencies(item); + IEnumerable dependencies = keys == null ? null : FindDependencies(keys, throwOnMissing); - sorted[index] = item; - index += incr; + if (dependencies != null) + { + foreach (TItem dep in dependencies) + { + Visit(dep, visited, sorted, ref index, incr, throwOnCycle, throwOnMissing); + } } - private IEnumerable FindDependencies(IEnumerable keys, bool throwOnMissing) + sorted[index] = item; + index += incr; + } + + private IEnumerable FindDependencies(IEnumerable keys, bool throwOnMissing) + { + foreach (TKey key in keys) { - foreach (var key in keys) + TItem? value; + if (_items.TryGetValue(key, out value)) + { + yield return value; + } + else if (throwOnMissing) { - TItem? value; - if (_items.TryGetValue(key, out value)) - yield return value; - else if (throwOnMissing) - throw new Exception($"{MissingDependencyError} Error in type {typeof(TItem).Name}, with key {key}"); + throw new Exception($"{MissingDependencyError} Error in type {typeof(TItem).Name}, with key {key}"); } } } diff --git a/src/Umbraco.Core/Collections/TypeList.cs b/src/Umbraco.Core/Collections/TypeList.cs index 96565a843c9f..ab51dd56b2f0 100644 --- a/src/Umbraco.Core/Collections/TypeList.cs +++ b/src/Umbraco.Core/Collections/TypeList.cs @@ -1,33 +1,24 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Collections; -namespace Umbraco.Cms.Core.Collections +/// +/// Represents a list of types. +/// +/// Types in the list are, or derive from, or implement, the base type. +/// The base type. +public class TypeList { + private readonly List _list = new(); + /// - /// Represents a list of types. + /// Adds a type to the list. /// - /// Types in the list are, or derive from, or implement, the base type. - /// The base type. - public class TypeList - { - private readonly List _list = new List(); - - /// - /// Adds a type to the list. - /// - /// The type to add. - public void Add() - where T : TBase - { - _list.Add(typeof(T)); - } + /// The type to add. + public void Add() + where T : TBase => + _list.Add(typeof(T)); - /// - /// Determines whether a type is in the list. - /// - public bool Contains(Type type) - { - return _list.Contains(type); - } - } + /// + /// Determines whether a type is in the list. + /// + public bool Contains(Type type) => _list.Contains(type); } diff --git a/src/Umbraco.Core/Composing/BuilderCollectionBase.cs b/src/Umbraco.Core/Composing/BuilderCollectionBase.cs index 1af9511fb77a..ffacd89cffe0 100644 --- a/src/Umbraco.Core/Composing/BuilderCollectionBase.cs +++ b/src/Umbraco.Core/Composing/BuilderCollectionBase.cs @@ -1,34 +1,32 @@ -using System; using System.Collections; -using System.Collections.Generic; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// Provides a base class for builder collections. +/// +/// The type of the items. +public abstract class BuilderCollectionBase : IBuilderCollection { + private readonly LazyReadOnlyCollection _items; - /// - /// Provides a base class for builder collections. + /// Initializes a new instance of the + /// + /// with items. /// - /// The type of the items. - public abstract class BuilderCollectionBase : IBuilderCollection - { - private readonly LazyReadOnlyCollection _items; + /// The items. + public BuilderCollectionBase(Func> items) => _items = new LazyReadOnlyCollection(items); - /// Initializes a new instance of the with items. - /// - /// The items. - public BuilderCollectionBase(Func> items) => _items = new LazyReadOnlyCollection(items); + /// + public int Count => _items.Count; - /// - public int Count => _items.Count; - - /// - /// Gets an enumerator. - /// - public IEnumerator GetEnumerator() => ((IEnumerable)_items).GetEnumerator(); + /// + /// Gets an enumerator. + /// + public IEnumerator GetEnumerator() => _items.GetEnumerator(); - /// - /// Gets an enumerator. - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } + /// + /// Gets an enumerator. + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs index 8b5913ab1d32..32f3d33052ab 100644 --- a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs @@ -1,160 +1,174 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Microsoft.Extensions.DependencyInjection; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// Provides a base class for collection builders. +/// +/// The type of the builder. +/// The type of the collection. +/// The type of the items. +public abstract class CollectionBuilderBase : ICollectionBuilder + where TBuilder : CollectionBuilderBase + where TCollection : class, IBuilderCollection { + private readonly object _locker = new(); + private readonly List _types = new(); + private Type[]? _registeredTypes; + /// - /// Provides a base class for collection builders. + /// Gets the collection lifetime. /// - /// The type of the builder. - /// The type of the collection. - /// The type of the items. - public abstract class CollectionBuilderBase : ICollectionBuilder - where TBuilder : CollectionBuilderBase - where TCollection : class, IBuilderCollection + protected virtual ServiceLifetime CollectionLifetime => ServiceLifetime.Singleton; + + /// + public virtual void RegisterWith(IServiceCollection services) { - private readonly List _types = new List(); - private readonly object _locker = new object(); - private Type[]? _registeredTypes; + if (_registeredTypes != null) + { + throw new InvalidOperationException("This builder has already been registered."); + } - /// - /// Gets the internal list of types as an IEnumerable (immutable). - /// - public IEnumerable GetTypes() => _types; + // register the collection + services.Add(new ServiceDescriptor(typeof(TCollection), CreateCollection, CollectionLifetime)); - /// - public virtual void RegisterWith(IServiceCollection services) - { - if (_registeredTypes != null) - throw new InvalidOperationException("This builder has already been registered."); + // register the types + RegisterTypes(services); + } - // register the collection - services.Add(new ServiceDescriptor(typeof(TCollection), CreateCollection, CollectionLifetime)); + /// + /// Creates a collection. + /// + /// A collection. + /// Creates a new collection each time it is invoked. + public virtual TCollection CreateCollection(IServiceProvider factory) + => factory.CreateInstance(CreateItemsFactory(factory)); - // register the types - RegisterTypes(services); - } + /// + /// Gets the internal list of types as an IEnumerable (immutable). + /// + public IEnumerable GetTypes() => _types; - /// - /// Gets the collection lifetime. - /// - protected virtual ServiceLifetime CollectionLifetime => ServiceLifetime.Singleton; - - /// - /// Configures the internal list of types. - /// - /// The action to execute. - /// Throws if the types have already been registered. - protected void Configure(Action> action) + /// + /// Configures the internal list of types. + /// + /// The action to execute. + /// Throws if the types have already been registered. + protected void Configure(Action> action) + { + lock (_locker) { - lock (_locker) + if (_registeredTypes != null) { - if (_registeredTypes != null) - throw new InvalidOperationException("Cannot configure a collection builder after it has been registered."); - action(_types); + throw new InvalidOperationException( + "Cannot configure a collection builder after it has been registered."); } - } - /// - /// Gets the types. - /// - /// The internal list of types. - /// The list of types to register. - /// Used by implementations to add types to the internal list, sort the list, etc. - protected virtual IEnumerable GetRegisteringTypes(IEnumerable types) - { - return types; + action(_types); } + } - private void RegisterTypes(IServiceCollection services) + /// + /// Gets the types. + /// + /// The internal list of types. + /// The list of types to register. + /// Used by implementations to add types to the internal list, sort the list, etc. + protected virtual IEnumerable GetRegisteringTypes(IEnumerable types) => types; + + private void RegisterTypes(IServiceCollection services) + { + lock (_locker) { - lock (_locker) + if (_registeredTypes != null) { - if (_registeredTypes != null) - return; - - var types = GetRegisteringTypes(_types).ToArray(); + return; + } - // ensure they are safe - foreach (var type in types) - EnsureType(type, "register"); + Type[] types = GetRegisteringTypes(_types).ToArray(); - // register them - ensuring that each item is registered with the same lifetime as the collection. - // NOTE: Previously each one was not registered with the same lifetime which would mean that if there - // was a dependency on an individual item, it would resolve a brand new transient instance which isn't what - // we would expect to happen. The same item should be resolved from the container as the collection. - foreach (var type in types) - services.Add(new ServiceDescriptor(type, type, CollectionLifetime)); + // ensure they are safe + foreach (Type type in types) + { + EnsureType(type, "register"); + } - _registeredTypes = types; + // register them - ensuring that each item is registered with the same lifetime as the collection. + // NOTE: Previously each one was not registered with the same lifetime which would mean that if there + // was a dependency on an individual item, it would resolve a brand new transient instance which isn't what + // we would expect to happen. The same item should be resolved from the container as the collection. + foreach (Type type in types) + { + services.Add(new ServiceDescriptor(type, type, CollectionLifetime)); } + + _registeredTypes = types; } + } - /// - /// Creates the collection items. - /// - /// The collection items. - protected virtual IEnumerable CreateItems(IServiceProvider factory) + /// + /// Creates the collection items. + /// + /// The collection items. + protected virtual IEnumerable CreateItems(IServiceProvider factory) + { + if (_registeredTypes == null) { - if (_registeredTypes == null) - throw new InvalidOperationException("Cannot create items before the collection builder has been registered."); - - return _registeredTypes // respect order - .Select(x => CreateItem(factory, x)) - .ToArray(); // safe + throw new InvalidOperationException( + "Cannot create items before the collection builder has been registered."); } - /// - /// Creates a collection item. - /// - protected virtual TItem CreateItem(IServiceProvider factory, Type itemType) - => (TItem)factory.GetRequiredService(itemType); + return _registeredTypes // respect order + .Select(x => CreateItem(factory, x)) + .ToArray(); // safe + } - /// - /// Creates a collection. - /// - /// A collection. - /// Creates a new collection each time it is invoked. - public virtual TCollection CreateCollection(IServiceProvider factory) - => factory.CreateInstance(CreateItemsFactory(factory)); + /// + /// Creates a collection item. + /// + protected virtual TItem CreateItem(IServiceProvider factory, Type itemType) + => (TItem)factory.GetRequiredService(itemType); - // used to resolve a Func> parameter - private Func> CreateItemsFactory(IServiceProvider factory) => () => CreateItems(factory); + // used to resolve a Func> parameter + private Func> CreateItemsFactory(IServiceProvider factory) => () => CreateItems(factory); - protected Type EnsureType(Type type, string action) + protected Type EnsureType(Type type, string action) + { + if (typeof(TItem).IsAssignableFrom(type) == false) { - if (typeof(TItem).IsAssignableFrom(type) == false) - throw new InvalidOperationException($"Cannot {action} type {type.FullName} as it does not inherit from/implement {typeof(TItem).FullName}."); - return type; + throw new InvalidOperationException( + $"Cannot {action} type {type.FullName} as it does not inherit from/implement {typeof(TItem).FullName}."); } - /// - /// Gets a value indicating whether the collection contains a type. - /// - /// The type to look for. - /// A value indicating whether the collection contains the type. - /// Some builder implementations may use this to expose a public Has{T}() method, - /// when it makes sense. Probably does not make sense for lazy builders, for example. - public virtual bool Has() - where T : TItem - { - return _types.Contains(typeof(T)); - } + return type; + } - /// - /// Gets a value indicating whether the collection contains a type. - /// - /// The type to look for. - /// A value indicating whether the collection contains the type. - /// Some builder implementations may use this to expose a public Has{T}() method, - /// when it makes sense. Probably does not make sense for lazy builders, for example. - public virtual bool Has(Type type) - { - EnsureType(type, "find"); - return _types.Contains(type); - } + /// + /// Gets a value indicating whether the collection contains a type. + /// + /// The type to look for. + /// A value indicating whether the collection contains the type. + /// + /// Some builder implementations may use this to expose a public Has{T}() method, + /// when it makes sense. Probably does not make sense for lazy builders, for example. + /// + public virtual bool Has() + where T : TItem => + _types.Contains(typeof(T)); + + /// + /// Gets a value indicating whether the collection contains a type. + /// + /// The type to look for. + /// A value indicating whether the collection contains the type. + /// + /// Some builder implementations may use this to expose a public Has{T}() method, + /// when it makes sense. Probably does not make sense for lazy builders, for example. + /// + public virtual bool Has(Type type) + { + EnsureType(type, "find"); + return _types.Contains(type); } } diff --git a/src/Umbraco.Core/Composing/ComponentCollection.cs b/src/Umbraco.Core/Composing/ComponentCollection.cs index c39dd503e0bc..1b73e88a4d1a 100644 --- a/src/Umbraco.Core/Composing/ComponentCollection.cs +++ b/src/Umbraco.Core/Composing/ComponentCollection.cs @@ -1,62 +1,64 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Logging; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// Represents the collection of implementations. +/// +public class ComponentCollection : BuilderCollectionBase { - /// - /// Represents the collection of implementations. - /// - public class ComponentCollection : BuilderCollectionBase - { - private const int LogThresholdMilliseconds = 100; + private const int LogThresholdMilliseconds = 100; + private readonly ILogger _logger; - private readonly IProfilingLogger _profilingLogger; - private readonly ILogger _logger; + private readonly IProfilingLogger _profilingLogger; - public ComponentCollection(Func> items, IProfilingLogger profilingLogger, ILogger logger) - : base(items) - { - _profilingLogger = profilingLogger; - _logger = logger; - } + public ComponentCollection(Func> items, IProfilingLogger profilingLogger, + ILogger logger) + : base(items) + { + _profilingLogger = profilingLogger; + _logger = logger; + } - public void Initialize() + public void Initialize() + { + using (_profilingLogger.DebugDuration( + $"Initializing. (log components when >{LogThresholdMilliseconds}ms)", "Initialized.")) { - using (_profilingLogger.DebugDuration($"Initializing. (log components when >{LogThresholdMilliseconds}ms)", "Initialized.")) + foreach (IComponent component in this) { - foreach (var component in this) + Type componentType = component.GetType(); + using (_profilingLogger.DebugDuration($"Initializing {componentType.FullName}.", + $"Initialized {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) { - var componentType = component.GetType(); - using (_profilingLogger.DebugDuration($"Initializing {componentType.FullName}.", $"Initialized {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) - { - component.Initialize(); - } + component.Initialize(); } } } + } - public void Terminate() + public void Terminate() + { + using (_profilingLogger.DebugDuration( + $"Terminating. (log components when >{LogThresholdMilliseconds}ms)", "Terminated.")) { - using (_profilingLogger.DebugDuration($"Terminating. (log components when >{LogThresholdMilliseconds}ms)", "Terminated.")) + foreach (IComponent component in this.Reverse()) // terminate components in reverse order { - foreach (var component in this.Reverse()) // terminate components in reverse order + Type componentType = component.GetType(); + using (_profilingLogger.DebugDuration($"Terminating {componentType.FullName}.", + $"Terminated {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) { - var componentType = component.GetType(); - using (_profilingLogger.DebugDuration($"Terminating {componentType.FullName}.", $"Terminated {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) + try + { + component.Terminate(); + component.DisposeIfDisposable(); + } + catch (Exception ex) { - try - { - component.Terminate(); - component.DisposeIfDisposable(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error while terminating component {ComponentType}.", componentType.FullName); - } + _logger.LogError(ex, "Error while terminating component {ComponentType}.", + componentType.FullName); } } } diff --git a/src/Umbraco.Core/Composing/ComponentCollectionBuilder.cs b/src/Umbraco.Core/Composing/ComponentCollectionBuilder.cs index 1e36c4e8e988..c179312f51f9 100644 --- a/src/Umbraco.Core/Composing/ComponentCollectionBuilder.cs +++ b/src/Umbraco.Core/Composing/ComponentCollectionBuilder.cs @@ -1,40 +1,38 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Logging; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// Builds a . +/// +public class + ComponentCollectionBuilder : OrderedCollectionBuilderBase { - /// - /// Builds a . - /// - public class ComponentCollectionBuilder : OrderedCollectionBuilderBase - { - private const int LogThresholdMilliseconds = 100; + private const int LogThresholdMilliseconds = 100; - public ComponentCollectionBuilder() - { } + protected override ComponentCollectionBuilder This => this; - protected override ComponentCollectionBuilder This => this; + protected override IEnumerable CreateItems(IServiceProvider factory) + { + IProfilingLogger logger = factory.GetRequiredService(); - protected override IEnumerable CreateItems(IServiceProvider factory) + using (logger.DebugDuration( + $"Creating components. (log when >{LogThresholdMilliseconds}ms)", "Created.")) { - var logger = factory.GetRequiredService(); - - using (logger.DebugDuration($"Creating components. (log when >{LogThresholdMilliseconds}ms)", "Created.")) - { - return base.CreateItems(factory); - } + return base.CreateItems(factory); } + } - protected override IComponent CreateItem(IServiceProvider factory, Type itemType) - { - var logger = factory.GetRequiredService(); + protected override IComponent CreateItem(IServiceProvider factory, Type itemType) + { + IProfilingLogger logger = factory.GetRequiredService(); - using (logger.DebugDuration($"Creating {itemType.FullName}.", $"Created {itemType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) - { - return base.CreateItem(factory, itemType); - } + using (logger.DebugDuration($"Creating {itemType.FullName}.", + $"Created {itemType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) + { + return base.CreateItem(factory, itemType); } } } diff --git a/src/Umbraco.Core/Composing/ComponentComposer.cs b/src/Umbraco.Core/Composing/ComponentComposer.cs index 54b895ce491a..a0bc86ccef96 100644 --- a/src/Umbraco.Core/Composing/ComponentComposer.cs +++ b/src/Umbraco.Core/Composing/ComponentComposer.cs @@ -1,22 +1,18 @@ using Umbraco.Cms.Core.DependencyInjection; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// Provides a base class for composers which compose a component. +/// +/// The type of the component +public abstract class ComponentComposer : IComposer + where TComponent : IComponent { - /// - /// Provides a base class for composers which compose a component. - /// - /// The type of the component - public abstract class ComponentComposer : IComposer - where TComponent : IComponent - { - /// - public virtual void Compose(IUmbracoBuilder builder) - { - builder.Components()?.Append(); - } + /// + public virtual void Compose(IUmbracoBuilder builder) => builder.Components()?.Append(); - // note: thanks to this class, a component that does not compose anything can be - // registered with one line: - // public class MyComponentComposer : ComponentComposer { } - } + // note: thanks to this class, a component that does not compose anything can be + // registered with one line: + // public class MyComponentComposer : ComponentComposer { } } diff --git a/src/Umbraco.Core/Composing/ComposeAfterAttribute.cs b/src/Umbraco.Core/Composing/ComposeAfterAttribute.cs index c12ddbcd3e98..4b7979193411 100644 --- a/src/Umbraco.Core/Composing/ComposeAfterAttribute.cs +++ b/src/Umbraco.Core/Composing/ComposeAfterAttribute.cs @@ -1,59 +1,66 @@ -using System; +namespace Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Composing +/// +/// Indicates that a composer requires another composer. +/// +/// +/// +/// This attribute is *not* inherited. This means that a composer class inheriting from +/// another composer class does *not* inherit its requirements. However, the runtime checks +/// the *interfaces* of every composer for their requirements, so requirements declared on +/// interfaces are inherited by every composer class implementing the interface. +/// +/// +/// When targeting a class, indicates a dependency on the composer which must be enabled, +/// unless the requirement has explicitly been declared as weak (and then, only if the composer +/// is enabled). +/// +/// +/// When targeting an interface, indicates a dependency on enabled composers implementing +/// the interface. It could be no composer at all, unless the requirement has explicitly been +/// declared as strong (and at least one composer must be enabled). +/// +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)] +public sealed class ComposeAfterAttribute : Attribute { /// - /// Indicates that a composer requires another composer. + /// Initializes a new instance of the class. /// - /// - /// This attribute is *not* inherited. This means that a composer class inheriting from - /// another composer class does *not* inherit its requirements. However, the runtime checks - /// the *interfaces* of every composer for their requirements, so requirements declared on - /// interfaces are inherited by every composer class implementing the interface. - /// When targeting a class, indicates a dependency on the composer which must be enabled, - /// unless the requirement has explicitly been declared as weak (and then, only if the composer - /// is enabled). - /// When targeting an interface, indicates a dependency on enabled composers implementing - /// the interface. It could be no composer at all, unless the requirement has explicitly been - /// declared as strong (and at least one composer must be enabled). - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)] - public sealed class ComposeAfterAttribute : Attribute + /// The type of the required composer. + public ComposeAfterAttribute(Type requiredType) { - /// - /// Initializes a new instance of the class. - /// - /// The type of the required composer. - public ComposeAfterAttribute(Type requiredType) + if (typeof(IComposer).IsAssignableFrom(requiredType) == false) { - if (typeof(IComposer).IsAssignableFrom(requiredType) == false) - throw new ArgumentException($"Type {requiredType.FullName} is invalid here because it does not implement {typeof(IComposer).FullName}."); - RequiredType = requiredType; + throw new ArgumentException( + $"Type {requiredType.FullName} is invalid here because it does not implement {typeof(IComposer).FullName}."); } - /// - /// Initializes a new instance of the class. - /// - /// The type of the required composer. - /// A value indicating whether the requirement is weak. - public ComposeAfterAttribute(Type requiredType, bool weak) - : this(requiredType) - { - Weak = weak; - } + RequiredType = requiredType; + } - /// - /// Gets the required type. - /// - public Type RequiredType { get; } + /// + /// Initializes a new instance of the class. + /// + /// The type of the required composer. + /// A value indicating whether the requirement is weak. + public ComposeAfterAttribute(Type requiredType, bool weak) + : this(requiredType) => + Weak = weak; - /// - /// Gets a value indicating whether the requirement is weak. - /// - /// Returns true if the requirement is weak (requires the other composer if it - /// is enabled), false if the requirement is strong (requires the other composer to be - /// enabled), and null if unspecified, in which case it is strong for classes and weak for - /// interfaces. - public bool? Weak { get; } - } + /// + /// Gets the required type. + /// + public Type RequiredType { get; } + + /// + /// Gets a value indicating whether the requirement is weak. + /// + /// + /// Returns true if the requirement is weak (requires the other composer if it + /// is enabled), false if the requirement is strong (requires the other composer to be + /// enabled), and null if unspecified, in which case it is strong for classes and weak for + /// interfaces. + /// + public bool? Weak { get; } } diff --git a/src/Umbraco.Core/Composing/ComposeBeforeAttribute.cs b/src/Umbraco.Core/Composing/ComposeBeforeAttribute.cs index 382772de8d58..1f060558dd66 100644 --- a/src/Umbraco.Core/Composing/ComposeBeforeAttribute.cs +++ b/src/Umbraco.Core/Composing/ComposeBeforeAttribute.cs @@ -1,40 +1,46 @@ -using System; +namespace Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Composing +/// +/// Indicates that a component is required by another composer. +/// +/// +/// +/// This attribute is *not* inherited. This means that a composer class inheriting from +/// another composer class does *not* inherit its requirements. However, the runtime checks +/// the *interfaces* of every composer for their requirements, so requirements declared on +/// interfaces are inherited by every composer class implementing the interface. +/// +/// +/// When targeting a class, indicates a dependency on the composer which must be enabled, +/// unless the requirement has explicitly been declared as weak (and then, only if the composer +/// is enabled). +/// +/// +/// When targeting an interface, indicates a dependency on enabled composers implementing +/// the interface. It could be no composer at all, unless the requirement has explicitly been +/// declared as strong (and at least one composer must be enabled). +/// +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)] +public sealed class ComposeBeforeAttribute : Attribute { /// - /// Indicates that a component is required by another composer. + /// Initializes a new instance of the class. /// - /// - /// This attribute is *not* inherited. This means that a composer class inheriting from - /// another composer class does *not* inherit its requirements. However, the runtime checks - /// the *interfaces* of every composer for their requirements, so requirements declared on - /// interfaces are inherited by every composer class implementing the interface. - /// When targeting a class, indicates a dependency on the composer which must be enabled, - /// unless the requirement has explicitly been declared as weak (and then, only if the composer - /// is enabled). - /// When targeting an interface, indicates a dependency on enabled composers implementing - /// the interface. It could be no composer at all, unless the requirement has explicitly been - /// declared as strong (and at least one composer must be enabled). - /// - - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)] - public sealed class ComposeBeforeAttribute : Attribute + /// The type of the required composer. + public ComposeBeforeAttribute(Type requiringType) { - /// - /// Initializes a new instance of the class. - /// - /// The type of the required composer. - public ComposeBeforeAttribute(Type requiringType) + if (typeof(IComposer).IsAssignableFrom(requiringType) == false) { - if (typeof(IComposer).IsAssignableFrom(requiringType) == false) - throw new ArgumentException($"Type {requiringType.FullName} is invalid here because it does not implement {typeof(IComposer).FullName}."); - RequiringType = requiringType; + throw new ArgumentException( + $"Type {requiringType.FullName} is invalid here because it does not implement {typeof(IComposer).FullName}."); } - /// - /// Gets the required type. - /// - public Type RequiringType { get; } + RequiringType = requiringType; } + + /// + /// Gets the required type. + /// + public Type RequiringType { get; } } diff --git a/src/Umbraco.Core/Composing/ComposerGraph.cs b/src/Umbraco.Core/Composing/ComposerGraph.cs index 510d59b3749c..701150bf6500 100644 --- a/src/Umbraco.Core/Composing/ComposerGraph.cs +++ b/src/Umbraco.Core/Composing/ComposerGraph.cs @@ -1,351 +1,419 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; using System.Text; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Collections; using Umbraco.Cms.Core.DependencyInjection; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; +// note: this class is NOT thread-safe in any way + +/// +/// Handles the composers. +/// +internal class ComposerGraph { - // note: this class is NOT thread-safe in any way + private readonly IUmbracoBuilder _builder; + private readonly IEnumerable _composerTypes; + private readonly IEnumerable _enableDisableAttributes; + private readonly ILogger _logger; /// - /// Handles the composers. + /// Initializes a new instance of the class. /// - internal class ComposerGraph + /// The composition. + /// The types. + /// + /// The and/or + /// attributes. + /// + /// The logger. + /// + /// composition + /// or + /// composerTypes + /// or + /// enableDisableAttributes + /// or + /// logger + /// + public ComposerGraph(IUmbracoBuilder builder, IEnumerable composerTypes, + IEnumerable enableDisableAttributes, ILogger logger) { - private readonly IUmbracoBuilder _builder; - private readonly ILogger _logger; - private readonly IEnumerable _composerTypes; - private readonly IEnumerable _enableDisableAttributes; - - /// - /// Initializes a new instance of the class. - /// - /// The composition. - /// The types. - /// The and/or attributes. - /// The logger. - /// composition - /// or - /// composerTypes - /// or - /// enableDisableAttributes - /// or - /// logger - public ComposerGraph(IUmbracoBuilder builder, IEnumerable composerTypes, IEnumerable enableDisableAttributes, ILogger logger) - { - _builder = builder ?? throw new ArgumentNullException(nameof(builder)); - _composerTypes = composerTypes ?? throw new ArgumentNullException(nameof(composerTypes)); - _enableDisableAttributes = enableDisableAttributes ?? throw new ArgumentNullException(nameof(enableDisableAttributes)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } + _builder = builder ?? throw new ArgumentNullException(nameof(builder)); + _composerTypes = composerTypes ?? throw new ArgumentNullException(nameof(composerTypes)); + _enableDisableAttributes = + enableDisableAttributes ?? throw new ArgumentNullException(nameof(enableDisableAttributes)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + /// Instantiates and composes the composers. + /// + public void Compose() + { + // make sure it is there + _builder.WithCollectionBuilder(); + + IEnumerable orderedComposerTypes = PrepareComposerTypes(); - private class EnableInfo + foreach (IComposer composer in InstantiateComposers(orderedComposerTypes)) { - public bool Enabled { get; set; } - public int Weight { get; set; } = -1; + composer.Compose(_builder); } + } + + internal IEnumerable PrepareComposerTypes() + { + Dictionary> requirements = GetRequirements(); + + // only for debugging, this is verbose + //_logger.Debug(GetComposersReport(requirements)); + + IEnumerable sortedComposerTypes = SortComposers(requirements); - /// - /// Instantiates and composes the composers. - /// - public void Compose() + // bit verbose but should help for troubleshooting + //var text = "Ordered Composers: " + Environment.NewLine + string.Join(Environment.NewLine, sortedComposerTypes) + Environment.NewLine; + _logger.LogDebug("Ordered Composers: {SortedComposerTypes}", sortedComposerTypes); + + return sortedComposerTypes; + } + + internal Dictionary?> GetRequirements(bool throwOnMissing = true) + { + // create a list, remove those that cannot be enabled due to runtime level + var composerTypeList = _composerTypes.ToList(); + + // enable or disable composers + EnableDisableComposers(_enableDisableAttributes, composerTypeList); + + void GatherInterfaces(Type type, Func getTypeInAttribute, HashSet iset, + List set2) + where TAttribute : Attribute { - // make sure it is there - _builder.WithCollectionBuilder(); + foreach (TAttribute attribute in type.GetCustomAttributes()) + { + Type typeInAttribute = getTypeInAttribute(attribute); + if (typeInAttribute != null && // if the attribute references a type ... + typeInAttribute.IsInterface && // ... which is an interface ... + typeof(IComposer).IsAssignableFrom(typeInAttribute) && // ... which implements IComposer ... + !iset.Contains(typeInAttribute)) // ... which is not already in the list + { + // add it to the new list + iset.Add(typeInAttribute); + set2.Add(typeInAttribute); - IEnumerable orderedComposerTypes = PrepareComposerTypes(); + // add all its interfaces implementing IComposer + foreach (Type i in typeInAttribute.GetInterfaces() + .Where(x => typeof(IComposer).IsAssignableFrom(x))) + { + iset.Add(i); + set2.Add(i); + } + } + } + } - foreach (IComposer composer in InstantiateComposers(orderedComposerTypes)) + // gather interfaces too + var interfaces = new HashSet(composerTypeList.SelectMany(x => + x.GetInterfaces().Where(y => typeof(IComposer).IsAssignableFrom(y)))); + composerTypeList.AddRange(interfaces); + List list1 = composerTypeList; + while (list1.Count > 0) + { + var list2 = new List(); + foreach (Type t in list1) { - composer.Compose(_builder); + GatherInterfaces(t, a => a.RequiredType, interfaces, list2); + GatherInterfaces(t, a => a.RequiringType, interfaces, list2); } + + composerTypeList.AddRange(list2); + list1 = list2; } - internal IEnumerable PrepareComposerTypes() + // sort the composers according to their dependencies + var requirements = new Dictionary?>(); + foreach (Type type in composerTypeList) { - var requirements = GetRequirements(); + requirements[type] = null; + } - // only for debugging, this is verbose - //_logger.Debug(GetComposersReport(requirements)); + foreach (Type type in composerTypeList) + { + GatherRequirementsFromAfterAttribute(type, composerTypeList, requirements, throwOnMissing); + GatherRequirementsFromBeforeAttribute(type, composerTypeList, requirements); + } + + return requirements; + } - var sortedComposerTypes = SortComposers(requirements); + internal IEnumerable SortComposers(Dictionary?> requirements) + { + // sort composers + var graph = new TopoGraph?>>(kvp => kvp.Key, kvp => kvp.Value); + graph.AddItems(requirements); + List sortedComposerTypes; + try + { + sortedComposerTypes = graph.GetSortedItems().Select(x => x.Key).Where(x => !x.IsInterface).ToList(); + } + catch (Exception e) + { + // in case of an error, force-dump everything to log + _logger.LogInformation("Composer Report:\r\n{ComposerReport}", GetComposersReport(requirements)); + _logger.LogError(e, "Failed to sort composers."); + throw; + } - // bit verbose but should help for troubleshooting - //var text = "Ordered Composers: " + Environment.NewLine + string.Join(Environment.NewLine, sortedComposerTypes) + Environment.NewLine; - _logger.LogDebug("Ordered Composers: {SortedComposerTypes}", sortedComposerTypes); + return sortedComposerTypes; + } - return sortedComposerTypes; + internal static string GetComposersReport(Dictionary?> requirements) + { + var text = new StringBuilder(); + text.AppendLine("Composers & Dependencies:"); + text.AppendLine(" < compose before"); + text.AppendLine(" > compose after"); + text.AppendLine(" : implements"); + text.AppendLine(" = depends"); + text.AppendLine(); + + bool HasReq(IEnumerable types, Type type) + { + return types.Any(x => type.IsAssignableFrom(x) && !x.IsInterface); } - internal Dictionary?> GetRequirements(bool throwOnMissing = true) + foreach (KeyValuePair> kvp in requirements) { - // create a list, remove those that cannot be enabled due to runtime level - var composerTypeList = _composerTypes.ToList(); + Type type = kvp.Key; - // enable or disable composers - EnableDisableComposers(_enableDisableAttributes, composerTypeList); + text.AppendLine(type.FullName); + foreach (ComposeAfterAttribute attribute in type.GetCustomAttributes()) + { + var weak = !(attribute.RequiredType.IsInterface ? attribute.Weak == false : attribute.Weak != true); + text.AppendLine(" > " + attribute.RequiredType + + (weak ? " (weak" : " (strong") + + (HasReq(requirements.Keys, attribute.RequiredType) ? ", found" : ", missing") + ")"); + } - void GatherInterfaces(Type type, Func getTypeInAttribute, HashSet iset, List set2) - where TAttribute : Attribute + foreach (ComposeBeforeAttribute attribute in type.GetCustomAttributes()) { - foreach (var attribute in type.GetCustomAttributes()) - { - var typeInAttribute = getTypeInAttribute(attribute); - if (typeInAttribute != null && // if the attribute references a type ... - typeInAttribute.IsInterface && // ... which is an interface ... - typeof(IComposer).IsAssignableFrom(typeInAttribute) && // ... which implements IComposer ... - !iset.Contains(typeInAttribute)) // ... which is not already in the list - { - // add it to the new list - iset.Add(typeInAttribute); - set2.Add(typeInAttribute); - - // add all its interfaces implementing IComposer - foreach (var i in typeInAttribute.GetInterfaces().Where(x => typeof(IComposer).IsAssignableFrom(x))) - { - iset.Add(i); - set2.Add(i); - } - } - } + text.AppendLine(" < " + attribute.RequiringType); } - // gather interfaces too - var interfaces = new HashSet(composerTypeList.SelectMany(x => x.GetInterfaces().Where(y => typeof(IComposer).IsAssignableFrom(y)))); - composerTypeList.AddRange(interfaces); - var list1 = composerTypeList; - while (list1.Count > 0) + foreach (Type i in type.GetInterfaces()) { - var list2 = new List(); - foreach (var t in list1) - { - GatherInterfaces(t, a => a.RequiredType, interfaces, list2); - GatherInterfaces(t, a => a.RequiringType, interfaces, list2); - } - composerTypeList.AddRange(list2); - list1 = list2; + text.AppendLine(" : " + i.FullName); } - // sort the composers according to their dependencies - var requirements = new Dictionary?>(); - foreach (var type in composerTypeList) - requirements[type] = null; - foreach (var type in composerTypeList) + if (kvp.Value != null) { - GatherRequirementsFromAfterAttribute(type, composerTypeList, requirements, throwOnMissing); - GatherRequirementsFromBeforeAttribute(type, composerTypeList, requirements); + foreach (Type t in kvp.Value) + { + text.AppendLine(" = " + t); + } } - return requirements; + text.AppendLine(); } - internal IEnumerable SortComposers(Dictionary?> requirements) + text.AppendLine("/"); + text.AppendLine(); + return text.ToString(); + } + + private static void EnableDisableComposers(IEnumerable enableDisableAttributes, ICollection types) + { + var enabled = new Dictionary(); + + // process the enable/disable attributes + // these two attributes are *not* inherited and apply to *classes* only (not interfaces). + // remote declarations (when a composer enables/disables *another* composer) + // have priority over local declarations (when a composer disables itself) so that + // ppl can enable composers that, by default, are disabled. + // what happens in case of conflicting remote declarations is unspecified. more + // precisely, the last declaration to be processed wins, but the order of the + // declarations depends on the type finder and is unspecified. + + void UpdateEnableInfo(Type composerType, int weight2, Dictionary enabled2, bool value) { - // sort composers - var graph = new TopoGraph?>>(kvp => kvp.Key, kvp => kvp.Value); - graph.AddItems(requirements); - List sortedComposerTypes; - try + if (enabled.TryGetValue(composerType, out EnableInfo enableInfo) == false) { - sortedComposerTypes = graph.GetSortedItems().Select(x => x.Key).Where(x => !x.IsInterface).ToList(); + enableInfo = enabled2[composerType] = new EnableInfo(); } - catch (Exception e) + + if (enableInfo.Weight > weight2) { - // in case of an error, force-dump everything to log - _logger.LogInformation("Composer Report:\r\n{ComposerReport}", GetComposersReport(requirements)); - _logger.LogError(e, "Failed to sort composers."); - throw; + return; } - return sortedComposerTypes; + enableInfo.Enabled = value; + enableInfo.Weight = weight2; } - internal static string GetComposersReport(Dictionary?> requirements) + foreach (EnableComposerAttribute attr in enableDisableAttributes.OfType()) { - var text = new StringBuilder(); - text.AppendLine("Composers & Dependencies:"); - text.AppendLine(" < compose before"); - text.AppendLine(" > compose after"); - text.AppendLine(" : implements"); - text.AppendLine(" = depends"); - text.AppendLine(); - - bool HasReq(IEnumerable types, Type type) - => types.Any(x => type.IsAssignableFrom(x) && !x.IsInterface); - - foreach (var kvp in requirements) - { - var type = kvp.Key; + Type type = attr.EnabledType; + UpdateEnableInfo(type, 2, enabled, true); + } - text.AppendLine(type.FullName); - foreach (var attribute in type.GetCustomAttributes()) - { - var weak = !(attribute.RequiredType.IsInterface ? attribute.Weak == false : attribute.Weak != true); - text.AppendLine(" > " + attribute.RequiredType + - (weak ? " (weak" : " (strong") + (HasReq(requirements.Keys, attribute.RequiredType) ? ", found" : ", missing") + ")"); - } - foreach (var attribute in type.GetCustomAttributes()) - text.AppendLine(" < " + attribute.RequiringType); - foreach (var i in type.GetInterfaces()) - text.AppendLine(" : " + i.FullName); - if (kvp.Value != null) - foreach (var t in kvp.Value) - text.AppendLine(" = " + t); - text.AppendLine(); - } - text.AppendLine("/"); - text.AppendLine(); - return text.ToString(); + foreach (DisableComposerAttribute attr in enableDisableAttributes.OfType()) + { + Type type = attr.DisabledType; + UpdateEnableInfo(type, 2, enabled, false); } - private static void EnableDisableComposers(IEnumerable enableDisableAttributes, ICollection types) + foreach (Type composerType in types) { - var enabled = new Dictionary(); - - // process the enable/disable attributes - // these two attributes are *not* inherited and apply to *classes* only (not interfaces). - // remote declarations (when a composer enables/disables *another* composer) - // have priority over local declarations (when a composer disables itself) so that - // ppl can enable composers that, by default, are disabled. - // what happens in case of conflicting remote declarations is unspecified. more - // precisely, the last declaration to be processed wins, but the order of the - // declarations depends on the type finder and is unspecified. - - void UpdateEnableInfo(Type composerType, int weight2, Dictionary enabled2, bool value) + foreach (EnableAttribute attr in composerType.GetCustomAttributes()) { - if (enabled.TryGetValue(composerType, out var enableInfo) == false) enableInfo = enabled2[composerType] = new EnableInfo(); - if (enableInfo.Weight > weight2) return; - - enableInfo.Enabled = value; - enableInfo.Weight = weight2; + Type type = attr.EnabledType ?? composerType; + var weight = type == composerType ? 1 : 3; + UpdateEnableInfo(type, weight, enabled, true); } - foreach (var attr in enableDisableAttributes.OfType()) + foreach (DisableAttribute attr in composerType.GetCustomAttributes()) { - var type = attr.EnabledType; - UpdateEnableInfo(type, 2, enabled, true); + Type type = attr.DisabledType ?? composerType; + var weight = type == composerType ? 1 : 3; + UpdateEnableInfo(type, weight, enabled, false); } + } - foreach (var attr in enableDisableAttributes.OfType()) + // remove composers that end up being disabled + foreach (KeyValuePair kvp in enabled.Where(x => x.Value.Enabled == false)) + { + types.Remove(kvp.Key); + } + } + + private static void GatherRequirementsFromAfterAttribute(Type type, ICollection types, + IDictionary?> requirements, bool throwOnMissing = true) + { + // get 'require' attributes + // these attributes are *not* inherited because we want to "custom-inherit" for interfaces only + IEnumerable afterAttributes = type + .GetInterfaces().SelectMany(x => x.GetCustomAttributes()) // those marking interfaces + .Concat(type.GetCustomAttributes()); // those marking the composer + + // what happens in case of conflicting attributes (different strong/weak for same type) is not specified. + foreach (ComposeAfterAttribute attr in afterAttributes) + { + if (attr.RequiredType == type) { - var type = attr.DisabledType; - UpdateEnableInfo(type, 2, enabled, false); + continue; // ignore self-requirements (+ exclude in implems, below) } - foreach (var composerType in types) + // requiring an interface = require any enabled composer implementing that interface + // unless strong, and then require at least one enabled composer implementing that interface + if (attr.RequiredType.IsInterface) { - foreach (var attr in composerType.GetCustomAttributes()) + var implems = types.Where(x => x != type && attr.RequiredType.IsAssignableFrom(x) && !x.IsInterface) + .ToList(); + if (implems.Count > 0) { - var type = attr.EnabledType ?? composerType; - var weight = type == composerType ? 1 : 3; - UpdateEnableInfo(type, weight, enabled, true); - } + if (requirements[type] == null) + { + requirements[type] = new List(); + } - foreach (var attr in composerType.GetCustomAttributes()) + requirements[type]!.AddRange(implems); + } + else if (attr.Weak == false && throwOnMissing) // if explicitly set to !weak, is strong, else is weak { - var type = attr.DisabledType ?? composerType; - var weight = type == composerType ? 1 : 3; - UpdateEnableInfo(type, weight, enabled, false); + throw new Exception( + $"Broken composer dependency: {type.FullName} -> {attr.RequiredType.FullName}."); } } - - // remove composers that end up being disabled - foreach (var kvp in enabled.Where(x => x.Value.Enabled == false)) - types.Remove(kvp.Key); - } - - private static void GatherRequirementsFromAfterAttribute(Type type, ICollection types, IDictionary?> requirements, bool throwOnMissing = true) - { - // get 'require' attributes - // these attributes are *not* inherited because we want to "custom-inherit" for interfaces only - var afterAttributes = type - .GetInterfaces().SelectMany(x => x.GetCustomAttributes()) // those marking interfaces - .Concat(type.GetCustomAttributes()); // those marking the composer - - // what happens in case of conflicting attributes (different strong/weak for same type) is not specified. - foreach (var attr in afterAttributes) + // requiring a class = require that the composer is enabled + // unless weak, and then requires it if it is enabled + else { - if (attr.RequiredType == type) continue; // ignore self-requirements (+ exclude in implems, below) - - // requiring an interface = require any enabled composer implementing that interface - // unless strong, and then require at least one enabled composer implementing that interface - if (attr.RequiredType.IsInterface) + if (types.Contains(attr.RequiredType)) { - var implems = types.Where(x => x != type && attr.RequiredType.IsAssignableFrom(x) && !x.IsInterface).ToList(); - if (implems.Count > 0) + if (requirements[type] == null) { - if (requirements[type] == null) requirements[type] = new List(); - requirements[type]!.AddRange(implems); + requirements[type] = new List(); } - else if (attr.Weak == false && throwOnMissing) // if explicitly set to !weak, is strong, else is weak - throw new Exception($"Broken composer dependency: {type.FullName} -> {attr.RequiredType.FullName}."); + + requirements[type]!.Add(attr.RequiredType); } - // requiring a class = require that the composer is enabled - // unless weak, and then requires it if it is enabled - else + else if (attr.Weak != true && throwOnMissing) // if not explicitly set to weak, is strong { - if (types.Contains(attr.RequiredType)) - { - if (requirements[type] == null) requirements[type] = new List(); - requirements[type]!.Add(attr.RequiredType); - } - else if (attr.Weak != true && throwOnMissing) // if not explicitly set to weak, is strong - throw new Exception($"Broken composer dependency: {type.FullName} -> {attr.RequiredType.FullName}."); + throw new Exception( + $"Broken composer dependency: {type.FullName} -> {attr.RequiredType.FullName}."); } } } + } - private static void GatherRequirementsFromBeforeAttribute(Type type, ICollection types, IDictionary?> requirements) + private static void GatherRequirementsFromBeforeAttribute(Type type, ICollection types, + IDictionary?> requirements) + { + // get 'required' attributes + // these attributes are *not* inherited because we want to "custom-inherit" for interfaces only + IEnumerable beforeAttributes = type + .GetInterfaces() + .SelectMany(x => x.GetCustomAttributes()) // those marking interfaces + .Concat(type.GetCustomAttributes()); // those marking the composer + + foreach (ComposeBeforeAttribute attr in beforeAttributes) { - // get 'required' attributes - // these attributes are *not* inherited because we want to "custom-inherit" for interfaces only - var beforeAttributes = type - .GetInterfaces().SelectMany(x => x.GetCustomAttributes()) // those marking interfaces - .Concat(type.GetCustomAttributes()); // those marking the composer - - foreach (var attr in beforeAttributes) + if (attr.RequiringType == type) { - if (attr.RequiringType == type) continue; // ignore self-requirements (+ exclude in implems, below) + continue; // ignore self-requirements (+ exclude in implems, below) + } - // required by an interface = by any enabled composer implementing this that interface - if (attr.RequiringType.IsInterface) + // required by an interface = by any enabled composer implementing this that interface + if (attr.RequiringType.IsInterface) + { + var implems = types.Where(x => x != type && attr.RequiringType.IsAssignableFrom(x) && !x.IsInterface) + .ToList(); + foreach (Type implem in implems) { - var implems = types.Where(x => x != type && attr.RequiringType.IsAssignableFrom(x) && !x.IsInterface).ToList(); - foreach (var implem in implems) + if (requirements[implem] == null) { - if (requirements[implem] == null) requirements[implem] = new List(); - requirements[implem]!.Add(type); + requirements[implem] = new List(); } + + requirements[implem]!.Add(type); } - // required by a class - else + } + // required by a class + else + { + if (types.Contains(attr.RequiringType)) { - if (types.Contains(attr.RequiringType)) + if (requirements[attr.RequiringType] == null) { - if (requirements[attr.RequiringType] == null) requirements[attr.RequiringType] = new List(); - requirements[attr.RequiringType]!.Add(type); + requirements[attr.RequiringType] = new List(); } + + requirements[attr.RequiringType]!.Add(type); } } } + } - private static IEnumerable InstantiateComposers(IEnumerable types) + private static IEnumerable InstantiateComposers(IEnumerable types) + { + foreach (Type type in types) { - foreach (Type type in types) - { - ConstructorInfo? ctor = type.GetConstructor(Array.Empty()); + ConstructorInfo? ctor = type.GetConstructor(Array.Empty()); - if (ctor == null) - { - throw new InvalidOperationException($"Composer {type.FullName} does not have a parameter-less constructor."); - } - - yield return (IComposer) ctor.Invoke(Array.Empty()); + if (ctor == null) + { + throw new InvalidOperationException( + $"Composer {type.FullName} does not have a parameter-less constructor."); } + + yield return (IComposer)ctor.Invoke(Array.Empty()); } } + + private class EnableInfo + { + public bool Enabled { get; set; } + public int Weight { get; set; } = -1; + } } diff --git a/src/Umbraco.Core/Composing/CompositionExtensions.cs b/src/Umbraco.Core/Composing/CompositionExtensions.cs index d087af77d845..7c93eaf99454 100644 --- a/src/Umbraco.Core/Composing/CompositionExtensions.cs +++ b/src/Umbraco.Core/Composing/CompositionExtensions.cs @@ -1,43 +1,43 @@ -using System; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.PublishedCache; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class CompositionExtensions { - public static class CompositionExtensions + /// + /// Sets the published snapshot service. + /// + /// The builder. + /// A function creating a published snapshot service. + public static IUmbracoBuilder SetPublishedSnapshotService(this IUmbracoBuilder builder, + Func factory) { - /// - /// Sets the published snapshot service. - /// - /// The builder. - /// A function creating a published snapshot service. - public static IUmbracoBuilder SetPublishedSnapshotService(this IUmbracoBuilder builder, Func factory) - { - builder.Services.AddUnique(factory); - return builder; - } + builder.Services.AddUnique(factory); + return builder; + } - /// - /// Sets the published snapshot service. - /// - /// The type of the published snapshot service. - /// The builder. - public static IUmbracoBuilder SetPublishedSnapshotService(this IUmbracoBuilder builder) - where T : class, IPublishedSnapshotService - { - builder.Services.AddUnique(); - return builder; - } + /// + /// Sets the published snapshot service. + /// + /// The type of the published snapshot service. + /// The builder. + public static IUmbracoBuilder SetPublishedSnapshotService(this IUmbracoBuilder builder) + where T : class, IPublishedSnapshotService + { + builder.Services.AddUnique(); + return builder; + } - /// - /// Sets the published snapshot service. - /// - /// The builder. - /// A published snapshot service. - public static IUmbracoBuilder SetPublishedSnapshotService(this IUmbracoBuilder builder, IPublishedSnapshotService service) - { - builder.Services.AddUnique(service); - return builder; - } + /// + /// Sets the published snapshot service. + /// + /// The builder. + /// A published snapshot service. + public static IUmbracoBuilder SetPublishedSnapshotService(this IUmbracoBuilder builder, + IPublishedSnapshotService service) + { + builder.Services.AddUnique(service); + return builder; } } diff --git a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs index 5cc38f31a7b1..5188c095a6c2 100644 --- a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs +++ b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs @@ -1,61 +1,60 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; using Microsoft.Extensions.Logging; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// Returns a list of scannable assemblies based on an entry point assembly and it's references +/// +/// +/// This will recursively search through the entry point's assemblies and Umbraco's core assemblies and their +/// references +/// to create a list of scannable assemblies based on whether they themselves or their transitive dependencies +/// reference Umbraco core assemblies. +/// +public class DefaultUmbracoAssemblyProvider : IAssemblyProvider { - /// - /// Returns a list of scannable assemblies based on an entry point assembly and it's references - /// - /// - /// This will recursively search through the entry point's assemblies and Umbraco's core assemblies and their references - /// to create a list of scannable assemblies based on whether they themselves or their transitive dependencies reference Umbraco core assemblies. - /// - public class DefaultUmbracoAssemblyProvider : IAssemblyProvider + private readonly IEnumerable? _additionalTargetAssemblies; + private readonly Assembly _entryPointAssembly; + private readonly ILoggerFactory _loggerFactory; + private List? _discovered; + + public DefaultUmbracoAssemblyProvider( + Assembly? entryPointAssembly, + ILoggerFactory loggerFactory, + IEnumerable? additionalTargetAssemblies = null) { - private readonly Assembly _entryPointAssembly; - private readonly ILoggerFactory _loggerFactory; - private readonly IEnumerable? _additionalTargetAssemblies; - private List? _discovered; - - public DefaultUmbracoAssemblyProvider( - Assembly? entryPointAssembly, - ILoggerFactory loggerFactory, - IEnumerable? additionalTargetAssemblies = null) - { - _entryPointAssembly = entryPointAssembly ?? throw new ArgumentNullException(nameof(entryPointAssembly)); - _loggerFactory = loggerFactory; - _additionalTargetAssemblies = additionalTargetAssemblies; - } + _entryPointAssembly = entryPointAssembly ?? throw new ArgumentNullException(nameof(entryPointAssembly)); + _loggerFactory = loggerFactory; + _additionalTargetAssemblies = additionalTargetAssemblies; + } - // TODO: It would be worth investigating a netcore3 version of this which would use - // var allAssemblies = System.Runtime.Loader.AssemblyLoadContext.All.SelectMany(x => x.Assemblies); - // that will still only resolve Assemblies that are already loaded but it would also make it possible to - // query dynamically generated assemblies once they are added. It would also provide the ability to probe - // assembly locations that are not in the same place as the entry point assemblies. + // TODO: It would be worth investigating a netcore3 version of this which would use + // var allAssemblies = System.Runtime.Loader.AssemblyLoadContext.All.SelectMany(x => x.Assemblies); + // that will still only resolve Assemblies that are already loaded but it would also make it possible to + // query dynamically generated assemblies once they are added. It would also provide the ability to probe + // assembly locations that are not in the same place as the entry point assemblies. - public IEnumerable Assemblies + public IEnumerable Assemblies + { + get { - get + if (_discovered != null) { - if (_discovered != null) - { - return _discovered; - } + return _discovered; + } - IEnumerable additionalTargetAssemblies = Constants.Composing.UmbracoCoreAssemblyNames; - if (_additionalTargetAssemblies != null) - { - additionalTargetAssemblies = additionalTargetAssemblies.Concat(_additionalTargetAssemblies); - } + IEnumerable additionalTargetAssemblies = Constants.Composing.UmbracoCoreAssemblyNames; + if (_additionalTargetAssemblies != null) + { + additionalTargetAssemblies = additionalTargetAssemblies.Concat(_additionalTargetAssemblies); + } - var finder = new FindAssembliesWithReferencesTo(new[] { _entryPointAssembly }, additionalTargetAssemblies.ToArray(), true, _loggerFactory); - _discovered = finder.Find().ToList(); + var finder = new FindAssembliesWithReferencesTo(new[] {_entryPointAssembly}, + additionalTargetAssemblies.ToArray(), true, _loggerFactory); + _discovered = finder.Find().ToList(); - return _discovered; - } + return _discovered; } } } diff --git a/src/Umbraco.Core/Composing/DisableAttribute.cs b/src/Umbraco.Core/Composing/DisableAttribute.cs index 23d825ee1c98..9823542f685c 100644 --- a/src/Umbraco.Core/Composing/DisableAttribute.cs +++ b/src/Umbraco.Core/Composing/DisableAttribute.cs @@ -1,43 +1,44 @@ -using System; -using System.Reflection; +using System.Reflection; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// Indicates that a composer should be disabled. +/// +/// +/// +/// If a type is specified, disables the composer of that type, else disables the composer marked with the +/// attribute. +/// +/// This attribute is *not* inherited. +/// This attribute applies to classes only, it is not possible to enable/disable interfaces. +/// +/// Assembly-level has greater priority than +/// +/// attribute when it is marking the composer itself, but lower priority that when it is referencing another +/// composer. +/// +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] +public class DisableAttribute : Attribute { /// - /// Indicates that a composer should be disabled. + /// Initializes a new instance of the class. /// - /// - /// If a type is specified, disables the composer of that type, else disables the composer marked with the attribute. - /// This attribute is *not* inherited. - /// This attribute applies to classes only, it is not possible to enable/disable interfaces. - /// Assembly-level has greater priority than - /// attribute when it is marking the composer itself, but lower priority that when it is referencing another composer. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - public class DisableAttribute : Attribute + public DisableAttribute() { - /// - /// Initializes a new instance of the class. - /// - public DisableAttribute() - { } + } - public DisableAttribute(string fullTypeName, string assemblyName) - { - DisabledType = Assembly.Load(assemblyName)?.GetType(fullTypeName); - } + public DisableAttribute(string fullTypeName, string assemblyName) => + DisabledType = Assembly.Load(assemblyName)?.GetType(fullTypeName); - /// - /// Initializes a new instance of the class. - /// - public DisableAttribute(Type disabledType) - { - DisabledType = disabledType; - } + /// + /// Initializes a new instance of the class. + /// + public DisableAttribute(Type disabledType) => DisabledType = disabledType; - /// - /// Gets the disabled type, or null if it is the composer marked with the attribute. - /// - public Type? DisabledType { get; } - } + /// + /// Gets the disabled type, or null if it is the composer marked with the attribute. + /// + public Type? DisabledType { get; } } diff --git a/src/Umbraco.Core/Composing/DisableComposerAttribute.cs b/src/Umbraco.Core/Composing/DisableComposerAttribute.cs index 59b36178cfdc..7ab79612ee38 100644 --- a/src/Umbraco.Core/Composing/DisableComposerAttribute.cs +++ b/src/Umbraco.Core/Composing/DisableComposerAttribute.cs @@ -1,28 +1,26 @@ -using System; +namespace Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Composing +/// +/// Indicates that a composer should be disabled. +/// +/// +/// +/// Assembly-level has greater priority than +/// +/// attribute when it is marking the composer itself, but lower priority that when it is referencing another +/// composer. +/// +/// +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +public class DisableComposerAttribute : Attribute { /// - /// Indicates that a composer should be disabled. + /// Initializes a new instance of the class. /// - /// - /// Assembly-level has greater priority than - /// attribute when it is marking the composer itself, but lower priority that when it is referencing another composer. - /// - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)] - public class DisableComposerAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - public DisableComposerAttribute(Type disabledType) - { - DisabledType = disabledType; - } + public DisableComposerAttribute(Type disabledType) => DisabledType = disabledType; - /// - /// Gets the disabled type, or null if it is the composer marked with the attribute. - /// - public Type DisabledType { get; } - } + /// + /// Gets the disabled type, or null if it is the composer marked with the attribute. + /// + public Type DisabledType { get; } } diff --git a/src/Umbraco.Core/Composing/EnableAttribute.cs b/src/Umbraco.Core/Composing/EnableAttribute.cs index 90fb1a9cc6e9..35b02f44c777 100644 --- a/src/Umbraco.Core/Composing/EnableAttribute.cs +++ b/src/Umbraco.Core/Composing/EnableAttribute.cs @@ -1,37 +1,39 @@ -using System; +namespace Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Composing +/// +/// Indicates that a composer should be enabled. +/// +/// +/// +/// If a type is specified, enables the composer of that type, else enables the composer marked with the +/// attribute. +/// +/// This attribute is *not* inherited. +/// This attribute applies to classes only, it is not possible to enable/disable interfaces. +/// +/// Assembly-level has greater priority than +/// +/// attribute when it is marking the composer itself, but lower priority that when it is referencing another +/// composer. +/// +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] +public class EnableAttribute : Attribute { /// - /// Indicates that a composer should be enabled. + /// Initializes a new instance of the class. /// - /// - /// If a type is specified, enables the composer of that type, else enables the composer marked with the attribute. - /// This attribute is *not* inherited. - /// This attribute applies to classes only, it is not possible to enable/disable interfaces. - /// Assembly-level has greater priority than - /// attribute when it is marking the composer itself, but lower priority that when it is referencing another composer. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - public class EnableAttribute : Attribute + public EnableAttribute() { - /// - /// Initializes a new instance of the class. - /// - public EnableAttribute() - { } + } - /// - /// Initializes a new instance of the class. - /// - public EnableAttribute(Type enabledType) - { - EnabledType = enabledType; - } + /// + /// Initializes a new instance of the class. + /// + public EnableAttribute(Type enabledType) => EnabledType = enabledType; - /// - /// Gets the enabled type, or null if it is the composer marked with the attribute. - /// - public Type? EnabledType { get; } - } + /// + /// Gets the enabled type, or null if it is the composer marked with the attribute. + /// + public Type? EnabledType { get; } } diff --git a/src/Umbraco.Core/Composing/EnableComposerAttribute.cs b/src/Umbraco.Core/Composing/EnableComposerAttribute.cs index 048a19a80f49..3ad71fd3f277 100644 --- a/src/Umbraco.Core/Composing/EnableComposerAttribute.cs +++ b/src/Umbraco.Core/Composing/EnableComposerAttribute.cs @@ -1,31 +1,32 @@ -using System; +namespace Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Composing +/// +/// Indicates that a composer should be enabled. +/// +/// +/// +/// If a type is specified, enables the composer of that type, else enables the composer marked with the +/// attribute. +/// +/// This attribute is *not* inherited. +/// This attribute applies to classes only, it is not possible to enable/disable interfaces. +/// +/// Assembly-level has greater priority than +/// +/// attribute when it is marking the composer itself, but lower priority that when it is referencing another +/// composer. +/// +/// +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +public class EnableComposerAttribute : Attribute { /// - /// Indicates that a composer should be enabled. + /// Initializes a new instance of the class. /// - /// - /// If a type is specified, enables the composer of that type, else enables the composer marked with the attribute. - /// This attribute is *not* inherited. - /// This attribute applies to classes only, it is not possible to enable/disable interfaces. - /// Assembly-level has greater priority than - /// attribute when it is marking the composer itself, but lower priority that when it is referencing another composer. - /// - [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)] - public class EnableComposerAttribute : Attribute - { - /// - /// Initializes a new instance of the class. - /// - public EnableComposerAttribute(Type enabledType) - { - EnabledType = enabledType; - } + public EnableComposerAttribute(Type enabledType) => EnabledType = enabledType; - /// - /// Gets the enabled type, or null if it is the composer marked with the attribute. - /// - public Type EnabledType { get; } - } + /// + /// Gets the enabled type, or null if it is the composer marked with the attribute. + /// + public Type EnabledType { get; } } diff --git a/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs b/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs index 78cdb80f58fc..6c502c11fc22 100644 --- a/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs +++ b/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs @@ -1,69 +1,71 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Reflection; using Microsoft.Extensions.Logging; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// Finds Assemblies from the entry point assemblies, it's dependencies and it's transitive dependencies that reference +/// that targetAssemblyNames +/// +/// +/// borrowed and modified from here +/// https://github.com/dotnet/aspnetcore-tooling/blob/master/src/Razor/src/Microsoft.NET.Sdk.Razor/FindAssembliesWithReferencesTo.cs +/// +internal class FindAssembliesWithReferencesTo { + private readonly bool _includeTargets; + private readonly ILogger _logger; + private readonly ILoggerFactory _loggerFactory; + private readonly Assembly[] _referenceAssemblies; + private readonly string[] _targetAssemblies; + /// - /// Finds Assemblies from the entry point assemblies, it's dependencies and it's transitive dependencies that reference that targetAssemblyNames + /// Constructor /// - /// - /// borrowed and modified from here https://github.com/dotnet/aspnetcore-tooling/blob/master/src/Razor/src/Microsoft.NET.Sdk.Razor/FindAssembliesWithReferencesTo.cs - /// - internal class FindAssembliesWithReferencesTo + /// Entry point assemblies + /// + /// Used to check if the entry point or it's transitive assemblies reference these + /// assembly names + /// + /// If true will also use the target assembly names as entry point assemblies + /// Logger factory for when scanning goes wrong + public FindAssembliesWithReferencesTo(Assembly[] referenceAssemblies, string[] targetAssemblyNames, + bool includeTargets, ILoggerFactory loggerFactory) { - private readonly Assembly[] _referenceAssemblies; - private readonly string[] _targetAssemblies; - private readonly bool _includeTargets; - private readonly ILoggerFactory _loggerFactory; - private readonly ILogger _logger; + _referenceAssemblies = referenceAssemblies; + _targetAssemblies = targetAssemblyNames; + _includeTargets = includeTargets; + _loggerFactory = loggerFactory; + _logger = _loggerFactory.CreateLogger(); + } - /// - /// Constructor - /// - /// Entry point assemblies - /// Used to check if the entry point or it's transitive assemblies reference these assembly names - /// If true will also use the target assembly names as entry point assemblies - /// Logger factory for when scanning goes wrong - public FindAssembliesWithReferencesTo(Assembly[] referenceAssemblies, string[] targetAssemblyNames, bool includeTargets, ILoggerFactory loggerFactory) + public IEnumerable Find() + { + var referenceItems = new List(); + foreach (Assembly assembly in _referenceAssemblies) { - _referenceAssemblies = referenceAssemblies; - _targetAssemblies = targetAssemblyNames; - _includeTargets = includeTargets; - _loggerFactory = loggerFactory; - _logger = _loggerFactory.CreateLogger(); + referenceItems.Add(assembly); } - public IEnumerable Find() + if (_includeTargets) { - var referenceItems = new List(); - foreach (var assembly in _referenceAssemblies) - { - referenceItems.Add(assembly); - } - - if (_includeTargets) + foreach (var target in _targetAssemblies) { - foreach(var target in _targetAssemblies) + try { - try - { - referenceItems.Add(Assembly.Load(target)); - } - catch (FileNotFoundException ex) - { - // occurs if we cannot load this ... for example in a test project where we aren't currently referencing Umbraco.Web, etc... - _logger.LogDebug(ex, "Could not load assembly " + target); - } + referenceItems.Add(Assembly.Load(target)); + } + catch (FileNotFoundException ex) + { + // occurs if we cannot load this ... for example in a test project where we aren't currently referencing Umbraco.Web, etc... + _logger.LogDebug(ex, "Could not load assembly " + target); } } - - var provider = new ReferenceResolver(_targetAssemblies, referenceItems, _loggerFactory.CreateLogger()); - var assemblyNames = provider.ResolveAssemblies(); - return assemblyNames.ToList(); } + var provider = new ReferenceResolver(_targetAssemblies, referenceItems, + _loggerFactory.CreateLogger()); + IEnumerable assemblyNames = provider.ResolveAssemblies(); + return assemblyNames.ToList(); } } diff --git a/src/Umbraco.Core/Composing/HideFromTypeFinderAttribute.cs b/src/Umbraco.Core/Composing/HideFromTypeFinderAttribute.cs index b985a7949417..e4ac2eaaa122 100644 --- a/src/Umbraco.Core/Composing/HideFromTypeFinderAttribute.cs +++ b/src/Umbraco.Core/Composing/HideFromTypeFinderAttribute.cs @@ -1,11 +1,9 @@ -using System; +namespace Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Composing +/// +/// Notifies the TypeFinder that it should ignore the class marked with this attribute. +/// +[AttributeUsage(AttributeTargets.Class)] +public sealed class HideFromTypeFinderAttribute : Attribute { - /// - /// Notifies the TypeFinder that it should ignore the class marked with this attribute. - /// - [AttributeUsage(AttributeTargets.Class)] - public sealed class HideFromTypeFinderAttribute : Attribute - { } } diff --git a/src/Umbraco.Core/Composing/IAssemblyProvider.cs b/src/Umbraco.Core/Composing/IAssemblyProvider.cs index fdc942ae2438..dc6c6c33b792 100644 --- a/src/Umbraco.Core/Composing/IAssemblyProvider.cs +++ b/src/Umbraco.Core/Composing/IAssemblyProvider.cs @@ -1,13 +1,11 @@ -using System.Collections.Generic; -using System.Reflection; +using System.Reflection; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// Provides a list of assemblies that can be scanned +/// +public interface IAssemblyProvider { - /// - /// Provides a list of assemblies that can be scanned - /// - public interface IAssemblyProvider - { - IEnumerable Assemblies { get; } - } + IEnumerable Assemblies { get; } } diff --git a/src/Umbraco.Core/Composing/IBuilderCollection.cs b/src/Umbraco.Core/Composing/IBuilderCollection.cs index 5e78cf0c2f69..2a0e6d0742b0 100644 --- a/src/Umbraco.Core/Composing/IBuilderCollection.cs +++ b/src/Umbraco.Core/Composing/IBuilderCollection.cs @@ -1,16 +1,13 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Composing +/// +/// Represents a builder collection, ie an immutable enumeration of items. +/// +/// The type of the items. +public interface IBuilderCollection : IEnumerable { /// - /// Represents a builder collection, ie an immutable enumeration of items. + /// Gets the number of items in the collection. /// - /// The type of the items. - public interface IBuilderCollection : IEnumerable - { - /// - /// Gets the number of items in the collection. - /// - int Count { get; } - } + int Count { get; } } diff --git a/src/Umbraco.Core/Composing/ICollectionBuilder.cs b/src/Umbraco.Core/Composing/ICollectionBuilder.cs index ea09558cad87..aa448c87b502 100644 --- a/src/Umbraco.Core/Composing/ICollectionBuilder.cs +++ b/src/Umbraco.Core/Composing/ICollectionBuilder.cs @@ -1,33 +1,31 @@ -using System; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// Represents a collection builder. +/// +public interface ICollectionBuilder { /// - /// Represents a collection builder. + /// Registers the builder so it can build the collection, by + /// registering the collection and the types. /// - public interface ICollectionBuilder - { - /// - /// Registers the builder so it can build the collection, by - /// registering the collection and the types. - /// - void RegisterWith(IServiceCollection services); - } + void RegisterWith(IServiceCollection services); +} +/// +/// Represents a collection builder. +/// +/// The type of the collection. +/// The type of the items. +public interface ICollectionBuilder : ICollectionBuilder + where TCollection : IBuilderCollection +{ /// - /// Represents a collection builder. + /// Creates a collection. /// - /// The type of the collection. - /// The type of the items. - public interface ICollectionBuilder : ICollectionBuilder - where TCollection : IBuilderCollection - { - /// - /// Creates a collection. - /// - /// A collection. - /// Creates a new collection each time it is invoked. - TCollection CreateCollection(IServiceProvider factory); - } + /// A collection. + /// Creates a new collection each time it is invoked. + TCollection CreateCollection(IServiceProvider factory); } diff --git a/src/Umbraco.Core/Composing/IComponent.cs b/src/Umbraco.Core/Composing/IComponent.cs index 8e9cf815e87c..c5079e6e26c2 100644 --- a/src/Umbraco.Core/Composing/IComponent.cs +++ b/src/Umbraco.Core/Composing/IComponent.cs @@ -1,25 +1,28 @@ -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// Represents a component. +/// +/// +/// Components are created by DI and therefore must have a public constructor. +/// +/// All components are terminated in reverse order when Umbraco terminates, and +/// disposable components are disposed. +/// +/// +/// The Dispose method may be invoked more than once, and components +/// should ensure they support this. +/// +/// +public interface IComponent { /// - /// Represents a component. + /// Initializes the component. /// - /// - /// Components are created by DI and therefore must have a public constructor. - /// All components are terminated in reverse order when Umbraco terminates, and - /// disposable components are disposed. - /// The Dispose method may be invoked more than once, and components - /// should ensure they support this. - /// - public interface IComponent - { - /// - /// Initializes the component. - /// - void Initialize(); + void Initialize(); - /// - /// Terminates the component. - /// - void Terminate(); - } + /// + /// Terminates the component. + /// + void Terminate(); } diff --git a/src/Umbraco.Core/Composing/IComposer.cs b/src/Umbraco.Core/Composing/IComposer.cs index 6f1978ee3e10..3d9bf2f73319 100644 --- a/src/Umbraco.Core/Composing/IComposer.cs +++ b/src/Umbraco.Core/Composing/IComposer.cs @@ -1,15 +1,14 @@ using Umbraco.Cms.Core.DependencyInjection; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// Represents a composer. +/// +public interface IComposer : IDiscoverable { /// - /// Represents a composer. + /// Compose. /// - public interface IComposer : IDiscoverable - { - /// - /// Compose. - /// - void Compose(IUmbracoBuilder builder); - } + void Compose(IUmbracoBuilder builder); } diff --git a/src/Umbraco.Core/Composing/IDiscoverable.cs b/src/Umbraco.Core/Composing/IDiscoverable.cs index 153fde36b632..f0e6077d8be4 100644 --- a/src/Umbraco.Core/Composing/IDiscoverable.cs +++ b/src/Umbraco.Core/Composing/IDiscoverable.cs @@ -1,5 +1,5 @@ -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +public interface IDiscoverable { - public interface IDiscoverable - { } } diff --git a/src/Umbraco.Core/Composing/IRuntimeHash.cs b/src/Umbraco.Core/Composing/IRuntimeHash.cs index b19b22a7e914..5cdc0193235f 100644 --- a/src/Umbraco.Core/Composing/IRuntimeHash.cs +++ b/src/Umbraco.Core/Composing/IRuntimeHash.cs @@ -1,14 +1,13 @@ -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// Used to create a hash value of the current runtime +/// +/// +/// This is used to detect if the runtime itself has changed, like a DLL has changed or another dynamically compiled +/// part of the application has changed. This is used to detect if we need to re-type scan. +/// +public interface IRuntimeHash { - /// - /// Used to create a hash value of the current runtime - /// - /// - /// This is used to detect if the runtime itself has changed, like a DLL has changed or another dynamically compiled - /// part of the application has changed. This is used to detect if we need to re-type scan. - /// - public interface IRuntimeHash - { - string GetHashValue(); - } + string GetHashValue(); } diff --git a/src/Umbraco.Core/Composing/ITypeFinder.cs b/src/Umbraco.Core/Composing/ITypeFinder.cs index 7d59b688693f..79c189d5aa68 100644 --- a/src/Umbraco.Core/Composing/ITypeFinder.cs +++ b/src/Umbraco.Core/Composing/ITypeFinder.cs @@ -1,55 +1,53 @@ -using System; -using System.Collections.Generic; using System.Reflection; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// Used to find objects by implemented types, names and/or attributes +/// +public interface ITypeFinder { /// - /// Used to find objects by implemented types, names and/or attributes + /// Return a list of found local Assemblies that Umbraco should scan for type finding /// - public interface ITypeFinder - { - Type? GetTypeByName(string name); + /// + IEnumerable AssembliesToScan { get; } - /// - /// Return a list of found local Assemblies that Umbraco should scan for type finding - /// - /// - IEnumerable AssembliesToScan { get; } + Type? GetTypeByName(string name); - /// - /// Finds any classes derived from the assignTypeFrom Type that contain the attribute TAttribute - /// - /// - /// - /// - /// - /// - IEnumerable FindClassesOfTypeWithAttribute( - Type assignTypeFrom, - Type attributeType, - IEnumerable? assemblies = null, - bool onlyConcreteClasses = true); + /// + /// Finds any classes derived from the assignTypeFrom Type that contain the attribute TAttribute + /// + /// + /// + /// + /// + /// + IEnumerable FindClassesOfTypeWithAttribute( + Type assignTypeFrom, + Type attributeType, + IEnumerable? assemblies = null, + bool onlyConcreteClasses = true); - /// - /// Returns all types found of in the assemblies specified of type T - /// - /// - /// - /// - /// - IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable? assemblies = null, bool onlyConcreteClasses = true); + /// + /// Returns all types found of in the assemblies specified of type T + /// + /// + /// + /// + /// + IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable? assemblies = null, + bool onlyConcreteClasses = true); - /// - /// Finds any classes with the attribute. - /// - /// The attribute type - /// The assemblies. - /// if set to true only concrete classes. - /// - IEnumerable FindClassesWithAttribute( - Type attributeType, - IEnumerable? assemblies, - bool onlyConcreteClasses); - } + /// + /// Finds any classes with the attribute. + /// + /// The attribute type + /// The assemblies. + /// if set to true only concrete classes. + /// + IEnumerable FindClassesWithAttribute( + Type attributeType, + IEnumerable? assemblies, + bool onlyConcreteClasses); } diff --git a/src/Umbraco.Core/Composing/IUserComposer.cs b/src/Umbraco.Core/Composing/IUserComposer.cs index fe5af3a98544..a3e45054f820 100644 --- a/src/Umbraco.Core/Composing/IUserComposer.cs +++ b/src/Umbraco.Core/Composing/IUserComposer.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// Represents a user . +/// +[Obsolete("This interface is obsolete. Use IComposer instead.")] +public interface IUserComposer : IComposer { - /// - /// Represents a user . - /// - [System.Obsolete("This interface is obsolete. Use IComposer instead.")] - public interface IUserComposer : IComposer - { } } diff --git a/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs index baae385af489..49ada40dfa42 100644 --- a/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs @@ -1,129 +1,131 @@ -using System; -using System.Collections.Generic; -using System.Linq; +namespace Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Composing +/// +/// Implements a lazy collection builder. +/// +/// The type of the builder. +/// The type of the collection. +/// The type of the items. +/// +/// This type of collection builder is typically used when type scanning is required (i.e. plugins). +/// +public abstract class + LazyCollectionBuilderBase : CollectionBuilderBase + where TBuilder : LazyCollectionBuilderBase + where TCollection : class, IBuilderCollection { + private readonly List _excluded = new(); + private readonly List>> _producers = new(); + + protected abstract TBuilder This { get; } + /// - /// Implements a lazy collection builder. + /// Clears all types in the collection. /// - /// The type of the builder. - /// The type of the collection. - /// The type of the items. - /// - /// This type of collection builder is typically used when type scanning is required (i.e. plugins). - /// - public abstract class LazyCollectionBuilderBase : CollectionBuilderBase - where TBuilder : LazyCollectionBuilderBase - where TCollection : class, IBuilderCollection + /// The builder. + public TBuilder Clear() { - private readonly List>> _producers = new List>>(); - private readonly List _excluded = new List(); - - protected abstract TBuilder This { get; } - - /// - /// Clears all types in the collection. - /// - /// The builder. - public TBuilder Clear() + Configure(types => { - Configure(types => - { - types.Clear(); - _producers.Clear(); - _excluded.Clear(); - }); - return This; - } + types.Clear(); + _producers.Clear(); + _excluded.Clear(); + }); + return This; + } - /// - /// Adds a type to the collection. - /// - /// The type to add. - /// The builder. - public TBuilder Add() - where T : TItem + /// + /// Adds a type to the collection. + /// + /// The type to add. + /// The builder. + public TBuilder Add() + where T : TItem + { + Configure(types => { - Configure(types => + Type type = typeof(T); + if (types.Contains(type) == false) { - var type = typeof(T); - if (types.Contains(type) == false) - types.Add(type); - }); - return This; - } + types.Add(type); + } + }); + return This; + } - /// - /// Adds a type to the collection. - /// - /// The type to add. - /// The builder. - public TBuilder Add(Type type) + /// + /// Adds a type to the collection. + /// + /// The type to add. + /// The builder. + public TBuilder Add(Type type) + { + Configure(types => { - Configure(types => + EnsureType(type, "register"); + if (types.Contains(type) == false) { - EnsureType(type, "register"); - if (types.Contains(type) == false) - types.Add(type); - }); - return This; - } + types.Add(type); + } + }); + return This; + } - /// - /// Adds a types producer to the collection. - /// - /// The types producer. - /// The builder. - public TBuilder Add(Func> producer) + /// + /// Adds a types producer to the collection. + /// + /// The types producer. + /// The builder. + public TBuilder Add(Func> producer) + { + Configure(types => { - Configure(types => - { - _producers.Add(producer); - }); - return This; - } + _producers.Add(producer); + }); + return This; + } - /// - /// Excludes a type from the collection. - /// - /// The type to exclude. - /// The builder. - public TBuilder Exclude() - where T : TItem + /// + /// Excludes a type from the collection. + /// + /// The type to exclude. + /// The builder. + public TBuilder Exclude() + where T : TItem + { + Configure(types => { - Configure(types => + Type type = typeof(T); + if (_excluded.Contains(type) == false) { - var type = typeof(T); - if (_excluded.Contains(type) == false) - _excluded.Add(type); - }); - return This; - } + _excluded.Add(type); + } + }); + return This; + } - /// - /// Excludes a type from the collection. - /// - /// The type to exclude. - /// The builder. - public TBuilder Exclude(Type type) + /// + /// Excludes a type from the collection. + /// + /// The type to exclude. + /// The builder. + public TBuilder Exclude(Type type) + { + Configure(types => { - Configure(types => + EnsureType(type, "exclude"); + if (_excluded.Contains(type) == false) { - EnsureType(type, "exclude"); - if (_excluded.Contains(type) == false) - _excluded.Add(type); - }); - return This; - } - - protected override IEnumerable GetRegisteringTypes(IEnumerable types) - { - return types - .Union(_producers.SelectMany(x => x())) - .Distinct() - .Select(x => EnsureType(x, "register")) - .Except(_excluded); - } + _excluded.Add(type); + } + }); + return This; } + + protected override IEnumerable GetRegisteringTypes(IEnumerable types) => + types + .Union(_producers.SelectMany(x => x())) + .Distinct() + .Select(x => EnsureType(x, "register")) + .Except(_excluded); } diff --git a/src/Umbraco.Core/Composing/LazyReadOnlyCollection.cs b/src/Umbraco.Core/Composing/LazyReadOnlyCollection.cs index 67116524acb9..eb6f2ed055af 100644 --- a/src/Umbraco.Core/Composing/LazyReadOnlyCollection.cs +++ b/src/Umbraco.Core/Composing/LazyReadOnlyCollection.cs @@ -1,48 +1,46 @@ -using System; using System.Collections; -using System.Collections.Generic; -using System.Linq; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +public sealed class LazyReadOnlyCollection : IReadOnlyCollection { - public sealed class LazyReadOnlyCollection : IReadOnlyCollection - { - private readonly Lazy> _lazyCollection; - private int? _count; + private readonly Lazy> _lazyCollection; + private int? _count; - public LazyReadOnlyCollection(Lazy> lazyCollection) => _lazyCollection = lazyCollection; + public LazyReadOnlyCollection(Lazy> lazyCollection) => _lazyCollection = lazyCollection; - public LazyReadOnlyCollection(Func> lazyCollection) => _lazyCollection = new Lazy>(lazyCollection); + public LazyReadOnlyCollection(Func> lazyCollection) => + _lazyCollection = new Lazy>(lazyCollection); - public IEnumerable Value => EnsureCollection(); + public IEnumerable Value => EnsureCollection(); - private IEnumerable EnsureCollection() + public int Count + { + get { - if (_lazyCollection == null) - { - _count = 0; - return Enumerable.Empty(); - } - - IEnumerable val = _lazyCollection.Value; - if (_count == null) - { - _count = val.Count(); - } - return val; + EnsureCollection(); + return _count.GetValueOrDefault(); } + } + + public IEnumerator GetEnumerator() => Value.GetEnumerator(); - public int Count + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + private IEnumerable EnsureCollection() + { + if (_lazyCollection == null) { - get - { - EnsureCollection(); - return _count.GetValueOrDefault(); - } + _count = 0; + return Enumerable.Empty(); } - public IEnumerator GetEnumerator() => Value.GetEnumerator(); + IEnumerable val = _lazyCollection.Value; + if (_count == null) + { + _count = val.Count(); + } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + return val; } } diff --git a/src/Umbraco.Core/Composing/LazyResolve.cs b/src/Umbraco.Core/Composing/LazyResolve.cs index afa22f74b661..1c243187c05a 100644 --- a/src/Umbraco.Core/Composing/LazyResolve.cs +++ b/src/Umbraco.Core/Composing/LazyResolve.cs @@ -1,13 +1,12 @@ -using System; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +public class LazyResolve : Lazy + where T : class { - public class LazyResolve : Lazy - where T : class + public LazyResolve(IServiceProvider serviceProvider) + : base(serviceProvider.GetRequiredService) { - public LazyResolve(IServiceProvider serviceProvider) - : base(serviceProvider.GetRequiredService) - { } } } diff --git a/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs index 939561f557b7..7842f93da6e8 100644 --- a/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs @@ -1,331 +1,419 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// Implements an ordered collection builder. +/// +/// The type of the builder. +/// The type of the collection. +/// The type of the items. +public abstract class + OrderedCollectionBuilderBase : CollectionBuilderBase + where TBuilder : OrderedCollectionBuilderBase + where TCollection : class, IBuilderCollection { + protected abstract TBuilder This { get; } + /// - /// Implements an ordered collection builder. + /// Clears all types in the collection. /// - /// The type of the builder. - /// The type of the collection. - /// The type of the items. - public abstract class OrderedCollectionBuilderBase : CollectionBuilderBase - where TBuilder : OrderedCollectionBuilderBase - where TCollection : class, IBuilderCollection + /// The builder. + public TBuilder Clear() { - protected abstract TBuilder This { get; } + Configure(types => types.Clear()); + return This; + } - /// - /// Clears all types in the collection. - /// - /// The builder. - public TBuilder Clear() - { - Configure(types => types.Clear()); - return This; - } - - /// - /// Appends a type to the collection. - /// - /// The type to append. - /// The builder. - public TBuilder Append() - where T : TItem + /// + /// Appends a type to the collection. + /// + /// The type to append. + /// The builder. + public TBuilder Append() + where T : TItem + { + Configure(types => { - Configure(types => + Type type = typeof(T); + if (types.Contains(type)) { - var type = typeof (T); - if (types.Contains(type)) types.Remove(type); - types.Add(type); - }); - return This; - } - - /// - /// Appends a type to the collection. - /// - /// The type to append. - /// The builder. - public TBuilder Append(Type type) + types.Remove(type); + } + + types.Add(type); + }); + return This; + } + + /// + /// Appends a type to the collection. + /// + /// The type to append. + /// The builder. + public TBuilder Append(Type type) + { + Configure(types => { - Configure(types => + EnsureType(type, "register"); + if (types.Contains(type)) { - EnsureType(type, "register"); - if (types.Contains(type)) types.Remove(type); - types.Add(type); - }); - return This; - } - - /// - /// Appends types to the collections. - /// - /// The types to append. - /// The builder. - public TBuilder Append(IEnumerable types) + types.Remove(type); + } + + types.Add(type); + }); + return This; + } + + /// + /// Appends types to the collections. + /// + /// The types to append. + /// The builder. + public TBuilder Append(IEnumerable types) + { + Configure(list => { - Configure(list => + foreach (Type type in types) { - foreach (var type in types) + // would be detected by CollectionBuilderBase when registering, anyways, but let's fail fast + EnsureType(type, "register"); + if (list.Contains(type)) { - // would be detected by CollectionBuilderBase when registering, anyways, but let's fail fast - EnsureType(type, "register"); - if (list.Contains(type)) list.Remove(type); - list.Add(type); + list.Remove(type); } - }); - return This; - } - - /// - /// Inserts a type into the collection. - /// - /// The type to insert. - /// The optional index. - /// The builder. - /// Throws if the index is out of range. - public TBuilder Insert(int index = 0) - where T : TItem + + list.Add(type); + } + }); + return This; + } + + /// + /// Inserts a type into the collection. + /// + /// The type to insert. + /// The optional index. + /// The builder. + /// Throws if the index is out of range. + public TBuilder Insert(int index = 0) + where T : TItem + { + Configure(types => { - Configure(types => + Type type = typeof(T); + if (types.Contains(type)) { - var type = typeof (T); - if (types.Contains(type)) types.Remove(type); - types.Insert(index, type); - }); - return This; - } - - /// - /// Inserts a type into the collection. - /// - /// The type to insert. - /// The builder. - /// Throws if the index is out of range. - public TBuilder Insert(Type type) + types.Remove(type); + } + + types.Insert(index, type); + }); + return This; + } + + /// + /// Inserts a type into the collection. + /// + /// The type to insert. + /// The builder. + /// Throws if the index is out of range. + public TBuilder Insert(Type type) => Insert(0, type); + + /// + /// Inserts a type into the collection. + /// + /// The index. + /// The type to insert. + /// The builder. + /// Throws if the index is out of range. + public TBuilder Insert(int index, Type type) + { + Configure(types => { - return Insert(0, type); - } - - /// - /// Inserts a type into the collection. - /// - /// The index. - /// The type to insert. - /// The builder. - /// Throws if the index is out of range. - public TBuilder Insert(int index, Type type) + EnsureType(type, "register"); + if (types.Contains(type)) + { + types.Remove(type); + } + + types.Insert(index, type); + }); + return This; + } + + /// + /// Inserts a type before another type. + /// + /// The other type. + /// The type to insert. + /// The builder. + /// Throws if both types are identical, or if the other type does not already belong to the collection. + public TBuilder InsertBefore() + where TBefore : TItem + where T : TItem + { + Configure(types => { - Configure(types => + Type typeBefore = typeof(TBefore); + Type type = typeof(T); + if (typeBefore == type) { - EnsureType(type, "register"); - if (types.Contains(type)) types.Remove(type); - types.Insert(index, type); - }); - return This; - } - - /// - /// Inserts a type before another type. - /// - /// The other type. - /// The type to insert. - /// The builder. - /// Throws if both types are identical, or if the other type does not already belong to the collection. - public TBuilder InsertBefore() - where TBefore : TItem - where T : TItem + throw new InvalidOperationException(); + } + + var index = types.IndexOf(typeBefore); + if (index < 0) + { + throw new InvalidOperationException(); + } + + if (types.Contains(type)) + { + types.Remove(type); + } + + index = types.IndexOf(typeBefore); // in case removing type changed index + types.Insert(index, type); + }); + return This; + } + + /// + /// Inserts a type before another type. + /// + /// The other type. + /// The type to insert. + /// The builder. + /// Throws if both types are identical, or if the other type does not already belong to the collection. + public TBuilder InsertBefore(Type typeBefore, Type type) + { + Configure(types => { - Configure(types => + EnsureType(typeBefore, "find"); + EnsureType(type, "register"); + + if (typeBefore == type) + { + throw new InvalidOperationException(); + } + + var index = types.IndexOf(typeBefore); + if (index < 0) { - var typeBefore = typeof(TBefore); - var type = typeof(T); - if (typeBefore == type) throw new InvalidOperationException(); + throw new InvalidOperationException(); + } - var index = types.IndexOf(typeBefore); - if (index < 0) throw new InvalidOperationException(); + if (types.Contains(type)) + { + types.Remove(type); + } - if (types.Contains(type)) types.Remove(type); - index = types.IndexOf(typeBefore); // in case removing type changed index - types.Insert(index, type); - }); - return This; - } - - /// - /// Inserts a type before another type. - /// - /// The other type. - /// The type to insert. - /// The builder. - /// Throws if both types are identical, or if the other type does not already belong to the collection. - public TBuilder InsertBefore(Type typeBefore, Type type) + index = types.IndexOf(typeBefore); // in case removing type changed index + types.Insert(index, type); + }); + return This; + } + + /// + /// Inserts a type after another type. + /// + /// The other type. + /// The type to append. + /// The builder. + /// Throws if both types are identical, or if the other type does not already belong to the collection. + public TBuilder InsertAfter() + where TAfter : TItem + where T : TItem + { + Configure(types => { - Configure(types => + Type typeAfter = typeof(TAfter); + Type type = typeof(T); + if (typeAfter == type) { - EnsureType(typeBefore, "find"); - EnsureType(type, "register"); + throw new InvalidOperationException(); + } + + var index = types.IndexOf(typeAfter); + if (index < 0) + { + throw new InvalidOperationException(); + } - if (typeBefore == type) throw new InvalidOperationException(); + if (types.Contains(type)) + { + types.Remove(type); + } - var index = types.IndexOf(typeBefore); - if (index < 0) throw new InvalidOperationException(); + index = types.IndexOf(typeAfter); // in case removing type changed index + index += 1; // insert here - if (types.Contains(type)) types.Remove(type); - index = types.IndexOf(typeBefore); // in case removing type changed index + if (index == types.Count) + { + types.Add(type); + } + else + { types.Insert(index, type); - }); - return This; - } - - /// - /// Inserts a type after another type. - /// - /// The other type. - /// The type to append. - /// The builder. - /// Throws if both types are identical, or if the other type does not already belong to the collection. - public TBuilder InsertAfter() - where TAfter : TItem - where T : TItem + } + }); + return This; + } + + /// + /// Inserts a type after another type. + /// + /// The other type. + /// The type to insert. + /// The builder. + /// Throws if both types are identical, or if the other type does not already belong to the collection. + public TBuilder InsertAfter(Type typeAfter, Type type) + { + Configure(types => { - Configure(types => + EnsureType(typeAfter, "find"); + EnsureType(type, "register"); + + if (typeAfter == type) { - var typeAfter = typeof(TAfter); - var type = typeof(T); - if (typeAfter == type) throw new InvalidOperationException(); - - var index = types.IndexOf(typeAfter); - if (index < 0) throw new InvalidOperationException(); - - if (types.Contains(type)) types.Remove(type); - index = types.IndexOf(typeAfter); // in case removing type changed index - index += 1; // insert here - - if (index == types.Count) - types.Add(type); - else - types.Insert(index, type); - }); - return This; - } - - /// - /// Inserts a type after another type. - /// - /// The other type. - /// The type to insert. - /// The builder. - /// Throws if both types are identical, or if the other type does not already belong to the collection. - public TBuilder InsertAfter(Type typeAfter, Type type) - { - Configure(types => + throw new InvalidOperationException(); + } + + var index = types.IndexOf(typeAfter); + if (index < 0) { - EnsureType(typeAfter, "find"); - EnsureType(type, "register"); + throw new InvalidOperationException(); + } + + if (types.Contains(type)) + { + types.Remove(type); + } + + index = types.IndexOf(typeAfter); // in case removing type changed index + index += 1; // insert here + + if (index == types.Count) + { + types.Add(type); + } + else + { + types.Insert(index, type); + } + }); + return This; + } - if (typeAfter == type) throw new InvalidOperationException(); - - var index = types.IndexOf(typeAfter); - if (index < 0) throw new InvalidOperationException(); - - if (types.Contains(type)) types.Remove(type); - index = types.IndexOf(typeAfter); // in case removing type changed index - index += 1; // insert here - - if (index == types.Count) - types.Add(type); - else - types.Insert(index, type); - }); - return This; - } - - /// - /// Removes a type from the collection. - /// - /// The type to remove. - /// The builder. - public TBuilder Remove() - where T : TItem + /// + /// Removes a type from the collection. + /// + /// The type to remove. + /// The builder. + public TBuilder Remove() + where T : TItem + { + Configure(types => { - Configure(types => + Type type = typeof(T); + if (types.Contains(type)) { - var type = typeof (T); - if (types.Contains(type)) types.Remove(type); - }); - return This; - } - - /// - /// Removes a type from the collection. - /// - /// The type to remove. - /// The builder. - public TBuilder Remove(Type type) + types.Remove(type); + } + }); + return This; + } + + /// + /// Removes a type from the collection. + /// + /// The type to remove. + /// The builder. + public TBuilder Remove(Type type) + { + Configure(types => { - Configure(types => + EnsureType(type, "remove"); + if (types.Contains(type)) { - EnsureType(type, "remove"); - if (types.Contains(type)) types.Remove(type); - }); - return This; - } - - /// - /// Replaces a type in the collection. - /// - /// The type to replace. - /// The type to insert. - /// The builder. - /// Throws if the type to replace does not already belong to the collection. - public TBuilder Replace() - where TReplaced : TItem - where T : TItem + types.Remove(type); + } + }); + return This; + } + + /// + /// Replaces a type in the collection. + /// + /// The type to replace. + /// The type to insert. + /// The builder. + /// Throws if the type to replace does not already belong to the collection. + public TBuilder Replace() + where TReplaced : TItem + where T : TItem + { + Configure(types => { - Configure(types => + Type typeReplaced = typeof(TReplaced); + Type type = typeof(T); + if (typeReplaced == type) { - var typeReplaced = typeof(TReplaced); - var type = typeof(T); - if (typeReplaced == type) return; + return; + } - var index = types.IndexOf(typeReplaced); - if (index < 0) throw new InvalidOperationException(); + var index = types.IndexOf(typeReplaced); + if (index < 0) + { + throw new InvalidOperationException(); + } - if (types.Contains(type)) types.Remove(type); - index = types.IndexOf(typeReplaced); // in case removing type changed index - types.Insert(index, type); - types.Remove(typeReplaced); - }); - return This; - } - - /// - /// Replaces a type in the collection. - /// - /// The type to replace. - /// The type to insert. - /// The builder. - /// Throws if the type to replace does not already belong to the collection. - public TBuilder Replace(Type typeReplaced, Type type) - { - Configure(types => + if (types.Contains(type)) { - EnsureType(typeReplaced, "find"); - EnsureType(type, "register"); + types.Remove(type); + } + + index = types.IndexOf(typeReplaced); // in case removing type changed index + types.Insert(index, type); + types.Remove(typeReplaced); + }); + return This; + } - if (typeReplaced == type) return; + /// + /// Replaces a type in the collection. + /// + /// The type to replace. + /// The type to insert. + /// The builder. + /// Throws if the type to replace does not already belong to the collection. + public TBuilder Replace(Type typeReplaced, Type type) + { + Configure(types => + { + EnsureType(typeReplaced, "find"); + EnsureType(type, "register"); - var index = types.IndexOf(typeReplaced); - if (index < 0) throw new InvalidOperationException(); + if (typeReplaced == type) + { + return; + } - if (types.Contains(type)) types.Remove(type); - index = types.IndexOf(typeReplaced); // in case removing type changed index - types.Insert(index, type); - types.Remove(typeReplaced); - }); - return This; - } + var index = types.IndexOf(typeReplaced); + if (index < 0) + { + throw new InvalidOperationException(); + } + + if (types.Contains(type)) + { + types.Remove(type); + } + + index = types.IndexOf(typeReplaced); // in case removing type changed index + types.Insert(index, type); + types.Remove(typeReplaced); + }); + return This; } } diff --git a/src/Umbraco.Core/Composing/ReferenceResolver.cs b/src/Umbraco.Core/Composing/ReferenceResolver.cs index 5b7c5ffde9a5..b8676082cc82 100644 --- a/src/Umbraco.Core/Composing/ReferenceResolver.cs +++ b/src/Umbraco.Core/Composing/ReferenceResolver.cs @@ -1,195 +1,202 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; +using System.Diagnostics; using System.Reflection; using System.Security; using Microsoft.Extensions.Logging; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// Resolves assemblies that reference one of the specified "targetAssemblies" either directly or transitively. +/// +/// +/// Borrowed and modified from +/// https://github.com/dotnet/aspnetcore-tooling/blob/master/src/Razor/src/Microsoft.NET.Sdk.Razor/ReferenceResolver.cs +/// +internal class ReferenceResolver { + private readonly IReadOnlyList _assemblies; + private readonly Dictionary _classifications; + private readonly ILogger _logger; + private readonly List _lookup = new(); + private readonly HashSet _umbracoAssemblies; + + public ReferenceResolver(IReadOnlyList targetAssemblies, IReadOnlyList entryPointAssemblies, + ILogger logger) + { + _umbracoAssemblies = new HashSet(targetAssemblies, StringComparer.Ordinal); + _assemblies = entryPointAssemblies; + _logger = logger; + _classifications = new Dictionary(); + + foreach (Assembly item in entryPointAssemblies) + { + _lookup.Add(item); + } + } + /// - /// Resolves assemblies that reference one of the specified "targetAssemblies" either directly or transitively. + /// Returns a list of assemblies that directly reference or transitively reference the targetAssemblies /// + /// /// - /// Borrowed and modified from https://github.com/dotnet/aspnetcore-tooling/blob/master/src/Razor/src/Microsoft.NET.Sdk.Razor/ReferenceResolver.cs + /// This includes all assemblies in the same location as the entry point assemblies /// - internal class ReferenceResolver + public IEnumerable ResolveAssemblies() { - private readonly HashSet _umbracoAssemblies; - private readonly IReadOnlyList _assemblies; - private readonly Dictionary _classifications; - private readonly List _lookup = new List(); - private readonly ILogger _logger; - public ReferenceResolver(IReadOnlyList targetAssemblies, IReadOnlyList entryPointAssemblies, ILogger logger) - { - _umbracoAssemblies = new HashSet(targetAssemblies, StringComparer.Ordinal); - _assemblies = entryPointAssemblies; - _logger = logger; - _classifications = new Dictionary(); - - foreach (var item in entryPointAssemblies) - { - _lookup.Add(item); - } - } + var applicationParts = new List(); - /// - /// Returns a list of assemblies that directly reference or transitively reference the targetAssemblies - /// - /// - /// - /// This includes all assemblies in the same location as the entry point assemblies - /// - public IEnumerable ResolveAssemblies() - { - var applicationParts = new List(); + var assemblies = new HashSet(_assemblies); - var assemblies = new HashSet(_assemblies); + // Get the unique directories of the assemblies + var assemblyLocations = GetAssemblyFolders(assemblies).ToList(); - // Get the unique directories of the assemblies - var assemblyLocations = GetAssemblyFolders(assemblies).ToList(); - - // Load in each assembly in the directory of the entry assembly to be included in the search - // for Umbraco dependencies/transitive dependencies - foreach(var dir in assemblyLocations) + // Load in each assembly in the directory of the entry assembly to be included in the search + // for Umbraco dependencies/transitive dependencies + foreach (var dir in assemblyLocations) + { + foreach (var dll in Directory.EnumerateFiles(dir ?? string.Empty, "*.dll")) { - foreach(var dll in Directory.EnumerateFiles(dir ?? string.Empty, "*.dll")) + AssemblyName? assemblyName = null; + try { - AssemblyName? assemblyName = null; - try - { - assemblyName = AssemblyName.GetAssemblyName(dll); - } - catch (BadImageFormatException e) - { - _logger.LogDebug(e, "Could not load {dll} for type scanning, skipping", dll); - } - catch (SecurityException e) - { - _logger.LogError(e, "Could not access {dll} for type scanning due to a security problem", dll); - } - catch (Exception e) + assemblyName = AssemblyName.GetAssemblyName(dll); + } + catch (BadImageFormatException e) + { + _logger.LogDebug(e, "Could not load {dll} for type scanning, skipping", dll); + } + catch (SecurityException e) + { + _logger.LogError(e, "Could not access {dll} for type scanning due to a security problem", dll); + } + catch (Exception e) + { + _logger.LogInformation(e, "Error: could not load {dll} for type scanning", dll); + } + + if (assemblyName != null) + { + // don't include if this is excluded + if (TypeFinder.KnownAssemblyExclusionFilter.Any(f => + assemblyName.FullName.StartsWith(f, StringComparison.InvariantCultureIgnoreCase))) { - _logger.LogInformation(e, "Error: could not load {dll} for type scanning", dll); + continue; } - if (assemblyName != null) + // don't include this item if it's Umbraco Core + if (Constants.Composing.UmbracoCoreAssemblyNames.Any(x => + assemblyName.FullName.StartsWith(x) || (assemblyName.Name?.EndsWith(".Views") ?? false))) { - // don't include if this is excluded - if (TypeFinder.KnownAssemblyExclusionFilter.Any(f => - assemblyName.FullName.StartsWith(f, StringComparison.InvariantCultureIgnoreCase))) - continue; - - // don't include this item if it's Umbraco Core - if (Constants.Composing.UmbracoCoreAssemblyNames.Any(x=>assemblyName.FullName.StartsWith(x) || (assemblyName.Name?.EndsWith(".Views") ?? false))) - continue; - - var assembly = Assembly.Load(assemblyName); - assemblies.Add(assembly); + continue; } + + var assembly = Assembly.Load(assemblyName); + assemblies.Add(assembly); } } + } - foreach (var item in assemblies) + foreach (Assembly item in assemblies) + { + Classification classification = Resolve(item); + if (classification == Classification.ReferencesUmbraco || classification == Classification.IsUmbraco) { - var classification = Resolve(item); - if (classification == Classification.ReferencesUmbraco || classification == Classification.IsUmbraco) - { - applicationParts.Add(item); - } + applicationParts.Add(item); } - - return applicationParts; } + return applicationParts; + } + + + private IEnumerable GetAssemblyFolders(IEnumerable assemblies) => + assemblies.Select(x => Path.GetDirectoryName(GetAssemblyLocation(x))).Distinct(); - private IEnumerable GetAssemblyFolders(IEnumerable assemblies) + // borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Core/src/ApplicationParts/RelatedAssemblyAttribute.cs + private string GetAssemblyLocation(Assembly assembly) + { + if (Uri.TryCreate(assembly.CodeBase, UriKind.Absolute, out Uri result) && + result.IsFile && string.IsNullOrWhiteSpace(result.Fragment)) { - return assemblies.Select(x => Path.GetDirectoryName(GetAssemblyLocation(x))).Distinct(); + return result.LocalPath; } - // borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Core/src/ApplicationParts/RelatedAssemblyAttribute.cs - private string GetAssemblyLocation(Assembly assembly) - { - if (Uri.TryCreate(assembly.CodeBase, UriKind.Absolute, out var result) && - result.IsFile && string.IsNullOrWhiteSpace(result.Fragment)) - { - return result.LocalPath; - } + return assembly.Location; + } - return assembly.Location; + private Classification Resolve(Assembly assembly) + { + if (_classifications.TryGetValue(assembly, out Classification classification)) + { + return classification; } - private Classification Resolve(Assembly assembly) + // Initialize the dictionary with a value to short-circuit recursive references. + classification = Classification.Unknown; + _classifications[assembly] = classification; + + if (TypeFinder.KnownAssemblyExclusionFilter.Any(f => + assembly.FullName?.StartsWith(f, StringComparison.InvariantCultureIgnoreCase) ?? false)) + { + // if its part of the filter it doesn't reference umbraco + classification = Classification.DoesNotReferenceUmbraco; + } + else if (_umbracoAssemblies.Contains(assembly.GetName().Name!)) + { + classification = Classification.IsUmbraco; + } + else { - if (_classifications.TryGetValue(assembly, out var classification)) + classification = Classification.DoesNotReferenceUmbraco; + foreach (Assembly reference in GetReferences(assembly)) { - return classification; - } - - // Initialize the dictionary with a value to short-circuit recursive references. - classification = Classification.Unknown; - _classifications[assembly] = classification; + // recurse + Classification referenceClassification = Resolve(reference); - if (TypeFinder.KnownAssemblyExclusionFilter.Any(f => assembly.FullName?.StartsWith(f, StringComparison.InvariantCultureIgnoreCase) ?? false)) - { - // if its part of the filter it doesn't reference umbraco - classification = Classification.DoesNotReferenceUmbraco; - } - else if (_umbracoAssemblies.Contains(assembly.GetName().Name!)) - { - classification = Classification.IsUmbraco; - } - else - { - classification = Classification.DoesNotReferenceUmbraco; - foreach (var reference in GetReferences(assembly)) + if (referenceClassification == Classification.IsUmbraco || + referenceClassification == Classification.ReferencesUmbraco) { - // recurse - var referenceClassification = Resolve(reference); - - if (referenceClassification == Classification.IsUmbraco || referenceClassification == Classification.ReferencesUmbraco) - { - classification = Classification.ReferencesUmbraco; - break; - } + classification = Classification.ReferencesUmbraco; + break; } } - - Debug.Assert(classification != Classification.Unknown); - _classifications[assembly] = classification; - return classification; } - protected virtual IEnumerable GetReferences(Assembly assembly) + Debug.Assert(classification != Classification.Unknown); + _classifications[assembly] = classification; + return classification; + } + + protected virtual IEnumerable GetReferences(Assembly assembly) + { + foreach (AssemblyName referenceName in assembly.GetReferencedAssemblies()) { - foreach (var referenceName in assembly.GetReferencedAssemblies()) + // don't include if this is excluded + if (TypeFinder.KnownAssemblyExclusionFilter.Any(f => + referenceName.FullName.StartsWith(f, StringComparison.InvariantCultureIgnoreCase))) { - // don't include if this is excluded - if (TypeFinder.KnownAssemblyExclusionFilter.Any(f => referenceName.FullName.StartsWith(f, StringComparison.InvariantCultureIgnoreCase))) - continue; + continue; + } - var reference = Assembly.Load(referenceName); + var reference = Assembly.Load(referenceName); - if (!_lookup.Contains(reference)) - { - // A dependency references an item that isn't referenced by this project. - // We'll add this reference so that we can calculate the classification. + if (!_lookup.Contains(reference)) + { + // A dependency references an item that isn't referenced by this project. + // We'll add this reference so that we can calculate the classification. - _lookup.Add(reference); - } - yield return reference; + _lookup.Add(reference); } - } - protected enum Classification - { - Unknown, - DoesNotReferenceUmbraco, - ReferencesUmbraco, - IsUmbraco, + yield return reference; } } + + protected enum Classification + { + Unknown, + DoesNotReferenceUmbraco, + ReferencesUmbraco, + IsUmbraco + } } diff --git a/src/Umbraco.Core/Composing/RuntimeHash.cs b/src/Umbraco.Core/Composing/RuntimeHash.cs index 5e0523f09dad..38f0f10f6bec 100644 --- a/src/Umbraco.Core/Composing/RuntimeHash.cs +++ b/src/Umbraco.Core/Composing/RuntimeHash.cs @@ -1,93 +1,90 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Security.Cryptography; using Umbraco.Cms.Core.Logging; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// Determines the runtime hash based on file system paths to scan +/// +public class RuntimeHash : IRuntimeHash { - /// - /// Determines the runtime hash based on file system paths to scan - /// - public class RuntimeHash : IRuntimeHash - { - private readonly IProfilingLogger _logger; - private readonly RuntimeHashPaths _paths; - private string? _calculated; + private readonly IProfilingLogger _logger; + private readonly RuntimeHashPaths _paths; + private string? _calculated; - public RuntimeHash(IProfilingLogger logger, RuntimeHashPaths paths) - { - _logger = logger; - _paths = paths; - } + public RuntimeHash(IProfilingLogger logger, RuntimeHashPaths paths) + { + _logger = logger; + _paths = paths; + } - public string GetHashValue() + public string GetHashValue() + { + if (_calculated != null) { - if (_calculated != null) - { - return _calculated; - } + return _calculated; + } - IEnumerable<(FileSystemInfo, bool)> allPaths = _paths.GetFolders() - .Select(x => ((FileSystemInfo)x, false)) - .Concat(_paths.GetFiles().Select(x => ((FileSystemInfo)x.Key, x.Value))); + IEnumerable<(FileSystemInfo, bool)> allPaths = _paths.GetFolders() + .Select(x => ((FileSystemInfo)x, false)) + .Concat(_paths.GetFiles().Select(x => ((FileSystemInfo)x.Key, x.Value))); - _calculated = GetFileHash(allPaths); + _calculated = GetFileHash(allPaths); - return _calculated; - } + return _calculated; + } - /// - /// Returns a unique hash for a combination of FileInfo objects. - /// - /// A collection of files. - /// The hash. - /// Each file is a tuple containing the FileInfo object and a boolean which indicates whether to hash the - /// file properties (false) or the file contents (true). - private string GetFileHash(IEnumerable<(FileSystemInfo fileOrFolder, bool scanFileContent)> filesAndFolders) + /// + /// Returns a unique hash for a combination of FileInfo objects. + /// + /// A collection of files. + /// The hash. + /// + /// Each file is a tuple containing the FileInfo object and a boolean which indicates whether to hash the + /// file properties (false) or the file contents (true). + /// + private string GetFileHash(IEnumerable<(FileSystemInfo fileOrFolder, bool scanFileContent)> filesAndFolders) + { + using (_logger.DebugDuration("Determining hash of code files on disk", "Hash determined")) { - using (_logger.DebugDuration("Determining hash of code files on disk", "Hash determined")) - { - // get the distinct file infos to hash - var uniqInfos = new HashSet(); - var uniqContent = new HashSet(); + // get the distinct file infos to hash + var uniqInfos = new HashSet(); + var uniqContent = new HashSet(); - using var generator = new HashGenerator(); + using var generator = new HashGenerator(); - foreach ((FileSystemInfo fileOrFolder, bool scanFileContent) in filesAndFolders) + foreach ((FileSystemInfo fileOrFolder, var scanFileContent) in filesAndFolders) + { + if (scanFileContent) { - if (scanFileContent) + // add each unique file's contents to the hash + // normalize the content for cr/lf and case-sensitivity + if (uniqContent.Add(fileOrFolder.FullName)) { - // add each unique file's contents to the hash - // normalize the content for cr/lf and case-sensitivity - if (uniqContent.Add(fileOrFolder.FullName)) + if (File.Exists(fileOrFolder.FullName) == false) { - if (File.Exists(fileOrFolder.FullName) == false) - { - continue; - } + continue; + } - using (FileStream fileStream = File.OpenRead(fileOrFolder.FullName)) - { - var hash = fileStream.GetStreamHash(); - generator.AddCaseInsensitiveString(hash); - } + using (FileStream fileStream = File.OpenRead(fileOrFolder.FullName)) + { + var hash = fileStream.GetStreamHash(); + generator.AddCaseInsensitiveString(hash); } } - else + } + else + { + // add each unique folder/file to the hash + if (uniqInfos.Add(fileOrFolder.FullName)) { - // add each unique folder/file to the hash - if (uniqInfos.Add(fileOrFolder.FullName)) - { - generator.AddFileSystemItem(fileOrFolder); - } + generator.AddFileSystemItem(fileOrFolder); } } - return generator.GenerateHash(); } - } + return generator.GenerateHash(); + } } } diff --git a/src/Umbraco.Core/Composing/RuntimeHashPaths.cs b/src/Umbraco.Core/Composing/RuntimeHashPaths.cs index eac2f83bcd1b..e94494bf36f0 100644 --- a/src/Umbraco.Core/Composing/RuntimeHashPaths.cs +++ b/src/Umbraco.Core/Composing/RuntimeHashPaths.cs @@ -1,44 +1,42 @@ -using System.Collections.Generic; -using System.IO; using System.Reflection; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// Paths used to determine the +/// +public sealed class RuntimeHashPaths { + private readonly Dictionary _files = new(); + private readonly List _paths = new(); + + public RuntimeHashPaths AddFolder(DirectoryInfo pathInfo) + { + _paths.Add(pathInfo); + return this; + } + /// - /// Paths used to determine the + /// Creates a runtime hash based on the assembly provider /// - public sealed class RuntimeHashPaths + /// + /// + public RuntimeHashPaths AddAssemblies(IAssemblyProvider assemblyProvider) { - private readonly List _paths = new List(); - private readonly Dictionary _files = new Dictionary(); - - public RuntimeHashPaths AddFolder(DirectoryInfo pathInfo) + foreach (Assembly assembly in assemblyProvider.Assemblies) { - _paths.Add(pathInfo); - return this; - } - - /// - /// Creates a runtime hash based on the assembly provider - /// - /// - /// - public RuntimeHashPaths AddAssemblies(IAssemblyProvider assemblyProvider) - { - foreach (Assembly assembly in assemblyProvider.Assemblies) + // TODO: We need to test this on a published website + if (!assembly.IsDynamic && assembly.Location != null) { - // TODO: We need to test this on a published website - if (!assembly.IsDynamic && assembly.Location != null) - { - AddFile(new FileInfo(assembly.Location)); - } + AddFile(new FileInfo(assembly.Location)); } - return this; } - public void AddFile(FileInfo fileInfo, bool scanFileContent = false) => _files.Add(fileInfo, scanFileContent); - - public IEnumerable GetFolders() => _paths; - public IReadOnlyDictionary GetFiles() => _files; + return this; } + + public void AddFile(FileInfo fileInfo, bool scanFileContent = false) => _files.Add(fileInfo, scanFileContent); + + public IEnumerable GetFolders() => _paths; + public IReadOnlyDictionary GetFiles() => _files; } diff --git a/src/Umbraco.Core/Composing/SetCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/SetCollectionBuilderBase.cs index 358aab75dd31..4d64529ce007 100644 --- a/src/Umbraco.Core/Composing/SetCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/SetCollectionBuilderBase.cs @@ -1,171 +1,208 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Composing +/// +/// Implements an un-ordered collection builder. +/// +/// The type of the builder. +/// The type of the collection. +/// The type of the items. +/// +/// +/// A set collection builder is the most basic collection builder, +/// where items are not ordered. +/// +/// +public abstract class + SetCollectionBuilderBase : CollectionBuilderBase + where TBuilder : SetCollectionBuilderBase + where TCollection : class, IBuilderCollection { + protected abstract TBuilder This { get; } + /// - /// Implements an un-ordered collection builder. + /// Clears all types in the collection. /// - /// The type of the builder. - /// The type of the collection. - /// The type of the items. - /// - /// A set collection builder is the most basic collection builder, - /// where items are not ordered. - /// - public abstract class SetCollectionBuilderBase : CollectionBuilderBase - where TBuilder : SetCollectionBuilderBase - where TCollection : class, IBuilderCollection + /// The builder. + public TBuilder Clear() { - protected abstract TBuilder This { get; } + Configure(types => types.Clear()); + return This; + } - /// - /// Clears all types in the collection. - /// - /// The builder. - public TBuilder Clear() - { - Configure(types => types.Clear()); - return This; - } - - /// - /// Adds a type to the collection. - /// - /// The type to append. - /// The builder. - public TBuilder Add() - where T : TItem + /// + /// Adds a type to the collection. + /// + /// The type to append. + /// The builder. + public TBuilder Add() + where T : TItem + { + Configure(types => { - Configure(types => + Type type = typeof(T); + if (types.Contains(type)) { - var type = typeof(T); - if (types.Contains(type)) types.Remove(type); - types.Add(type); - }); - return This; - } - - /// - /// Adds a type to the collection. - /// - /// The type to append. - /// The builder. - public TBuilder Add(Type type) + types.Remove(type); + } + + types.Add(type); + }); + return This; + } + + /// + /// Adds a type to the collection. + /// + /// The type to append. + /// The builder. + public TBuilder Add(Type type) + { + Configure(types => { - Configure(types => + EnsureType(type, "register"); + if (types.Contains(type)) { - EnsureType(type, "register"); - if (types.Contains(type)) types.Remove(type); - types.Add(type); - }); - return This; - } - - /// - /// Adds types to the collections. - /// - /// The types to append. - /// The builder. - public TBuilder Add(IEnumerable types) + types.Remove(type); + } + + types.Add(type); + }); + return This; + } + + /// + /// Adds types to the collections. + /// + /// The types to append. + /// The builder. + public TBuilder Add(IEnumerable types) + { + Configure(list => { - Configure(list => + foreach (Type type in types) { - foreach (var type in types) + // would be detected by CollectionBuilderBase when registering, anyways, but let's fail fast + EnsureType(type, "register"); + if (list.Contains(type)) { - // would be detected by CollectionBuilderBase when registering, anyways, but let's fail fast - EnsureType(type, "register"); - if (list.Contains(type)) list.Remove(type); - list.Add(type); + list.Remove(type); } - }); - return This; - } - - /// - /// Removes a type from the collection. - /// - /// The type to remove. - /// The builder. - public TBuilder Remove() - where T : TItem + + list.Add(type); + } + }); + return This; + } + + /// + /// Removes a type from the collection. + /// + /// The type to remove. + /// The builder. + public TBuilder Remove() + where T : TItem + { + Configure(types => { - Configure(types => + Type type = typeof(T); + if (types.Contains(type)) { - var type = typeof(T); - if (types.Contains(type)) types.Remove(type); - }); - return This; - } - - /// - /// Removes a type from the collection. - /// - /// The type to remove. - /// The builder. - public TBuilder Remove(Type type) + types.Remove(type); + } + }); + return This; + } + + /// + /// Removes a type from the collection. + /// + /// The type to remove. + /// The builder. + public TBuilder Remove(Type type) + { + Configure(types => { - Configure(types => + EnsureType(type, "remove"); + if (types.Contains(type)) { - EnsureType(type, "remove"); - if (types.Contains(type)) types.Remove(type); - }); - return This; - } - - /// - /// Replaces a type in the collection. - /// - /// The type to replace. - /// The type to insert. - /// The builder. - /// Throws if the type to replace does not already belong to the collection. - public TBuilder Replace() - where TReplaced : TItem - where T : TItem + types.Remove(type); + } + }); + return This; + } + + /// + /// Replaces a type in the collection. + /// + /// The type to replace. + /// The type to insert. + /// The builder. + /// Throws if the type to replace does not already belong to the collection. + public TBuilder Replace() + where TReplaced : TItem + where T : TItem + { + Configure(types => { - Configure(types => + Type typeReplaced = typeof(TReplaced); + Type type = typeof(T); + if (typeReplaced == type) + { + return; + } + + var index = types.IndexOf(typeReplaced); + if (index < 0) { - var typeReplaced = typeof(TReplaced); - var type = typeof(T); - if (typeReplaced == type) return; - - var index = types.IndexOf(typeReplaced); - if (index < 0) throw new InvalidOperationException(); - - if (types.Contains(type)) types.Remove(type); - index = types.IndexOf(typeReplaced); // in case removing type changed index - types.Insert(index, type); - types.Remove(typeReplaced); - }); - return This; - } - - /// - /// Replaces a type in the collection. - /// - /// The type to replace. - /// The type to insert. - /// The builder. - /// Throws if the type to replace does not already belong to the collection. - public TBuilder Replace(Type typeReplaced, Type type) + throw new InvalidOperationException(); + } + + if (types.Contains(type)) + { + types.Remove(type); + } + + index = types.IndexOf(typeReplaced); // in case removing type changed index + types.Insert(index, type); + types.Remove(typeReplaced); + }); + return This; + } + + /// + /// Replaces a type in the collection. + /// + /// The type to replace. + /// The type to insert. + /// The builder. + /// Throws if the type to replace does not already belong to the collection. + public TBuilder Replace(Type typeReplaced, Type type) + { + Configure(types => { - Configure(types => + EnsureType(typeReplaced, "find"); + EnsureType(type, "register"); + + if (typeReplaced == type) { - EnsureType(typeReplaced, "find"); - EnsureType(type, "register"); + return; + } - if (typeReplaced == type) return; + var index = types.IndexOf(typeReplaced); + if (index < 0) + { + throw new InvalidOperationException(); + } - var index = types.IndexOf(typeReplaced); - if (index < 0) throw new InvalidOperationException(); + if (types.Contains(type)) + { + types.Remove(type); + } - if (types.Contains(type)) types.Remove(type); - index = types.IndexOf(typeReplaced); // in case removing type changed index - types.Insert(index, type); - types.Remove(typeReplaced); - }); - return This; - } + index = types.IndexOf(typeReplaced); // in case removing type changed index + types.Insert(index, type); + types.Remove(typeReplaced); + }); + return This; } } diff --git a/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs index 40ce3d8a4628..a5931e860159 100644 --- a/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs @@ -1,69 +1,71 @@ -using System; -using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// Provides a base class for collections of types. +/// +public abstract class + TypeCollectionBuilderBase : ICollectionBuilder + where TBuilder : TypeCollectionBuilderBase + where TCollection : class, IBuilderCollection { - /// - /// Provides a base class for collections of types. - /// - public abstract class TypeCollectionBuilderBase : ICollectionBuilder - where TBuilder : TypeCollectionBuilderBase - where TCollection : class, IBuilderCollection - { - private readonly HashSet _types = new HashSet(); + private readonly HashSet _types = new(); - protected abstract TBuilder This { get; } + protected abstract TBuilder This { get; } - private static Type Validate(Type type, string action) - { - if (!typeof(TConstraint).IsAssignableFrom(type)) - throw new InvalidOperationException($"Cannot {action} type {type.FullName} as it does not inherit from/implement {typeof(TConstraint).FullName}."); - return type; - } + public TCollection CreateCollection(IServiceProvider factory) + => factory.CreateInstance(CreateItemsFactory()); - public TBuilder Add(Type type) - { - _types.Add(Validate(type, "add")); - return This; - } + public void RegisterWith(IServiceCollection services) + => services.Add(new ServiceDescriptor(typeof(TCollection), CreateCollection, ServiceLifetime.Singleton)); - public TBuilder Add() + private static Type Validate(Type type, string action) + { + if (!typeof(TConstraint).IsAssignableFrom(type)) { - Add(typeof(T)); - return This; + throw new InvalidOperationException( + $"Cannot {action} type {type.FullName} as it does not inherit from/implement {typeof(TConstraint).FullName}."); } - public TBuilder Add(IEnumerable types) - { - foreach (var type in types) - { - Add(type); - } + return type; + } - return This; - } + public TBuilder Add(Type type) + { + _types.Add(Validate(type, "add")); + return This; + } - public TBuilder Remove(Type type) - { - _types.Remove(Validate(type, "remove")); - return This; - } + public TBuilder Add() + { + Add(typeof(T)); + return This; + } - public TBuilder Remove() + public TBuilder Add(IEnumerable types) + { + foreach (Type type in types) { - Remove(typeof(T)); - return This; + Add(type); } - public TCollection CreateCollection(IServiceProvider factory) - => factory.CreateInstance(CreateItemsFactory()); + return This; + } - public void RegisterWith(IServiceCollection services) - => services.Add(new ServiceDescriptor(typeof(TCollection), CreateCollection, ServiceLifetime.Singleton)); + public TBuilder Remove(Type type) + { + _types.Remove(Validate(type, "remove")); + return This; + } - // used to resolve a Func> parameter - private Func> CreateItemsFactory() => () => _types; + public TBuilder Remove() + { + Remove(typeof(T)); + return This; } + + // used to resolve a Func> parameter + private Func> CreateItemsFactory() => () => _types; } diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index dfeac6a73126..238f609822b7 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -1,7 +1,4 @@ -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; using System.Reflection; using System.Security; using System.Text; @@ -9,495 +6,501 @@ using Umbraco.Cms.Core.Configuration.UmbracoSettings; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Composing -{ +namespace Umbraco.Cms.Core.Composing; - /// - public class TypeFinder : ITypeFinder +/// +public class TypeFinder : ITypeFinder +{ + private static readonly ConcurrentDictionary s_typeNamesCache = new(); + + // TODO: Kill this + + /// + /// this is our assembly filter to filter out known types that def don't contain types we'd like to find or plugins + /// + /// + /// NOTE the comma vs period... comma delimits the name in an Assembly FullName property so if it ends with comma then + /// its an exact name match + /// NOTE this means that "foo." will NOT exclude "foo.dll" but only "foo.*.dll" + /// + internal static readonly string[] KnownAssemblyExclusionFilter = { - private readonly ILogger _logger; - private readonly IAssemblyProvider _assemblyProvider; - private volatile HashSet? _localFilteredAssemblyCache; - private readonly object _localFilteredAssemblyCacheLocker = new object(); - private readonly List _notifiedLoadExceptionAssemblies = new List(); - private static readonly ConcurrentDictionary s_typeNamesCache = new ConcurrentDictionary(); - - private readonly ITypeFinderConfig? _typeFinderConfig; - // used for benchmark tests - internal bool QueryWithReferencingAssemblies { get; set; } = true; - - public TypeFinder(ILogger logger, IAssemblyProvider assemblyProvider, ITypeFinderConfig? typeFinderConfig = null) - { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _assemblyProvider = assemblyProvider; - _typeFinderConfig = typeFinderConfig; - } + "mscorlib,", "netstandard,", "System,", "Antlr3.", "AutoMapper,", "AutoMapper.", "Autofac,", // DI + "Autofac.", "AzureDirectory,", "Castle.", // DI, tests + "ClientDependency.", "CookComputing.", "CSharpTest.", // BTree for NuCache + "DataAnnotationsExtensions,", "DataAnnotationsExtensions.", "Dynamic,", "Examine,", "Examine.", + "HtmlAgilityPack,", "HtmlAgilityPack.", "HtmlDiff,", "ICSharpCode.", "Iesi.Collections,", // used by NHibernate + "JetBrains.Annotations,", "LightInject.", // DI + "LightInject,", "Lucene.", "Markdown,", "Microsoft.", "MiniProfiler,", "Moq,", "MySql.", "NHibernate,", + "NHibernate.", "Newtonsoft.", "NPoco,", "NuGet.", "RouteDebugger,", "Semver.", "Serilog.", "Serilog,", + "ServiceStack.", "SqlCE4Umbraco,", "Superpower,", // used by Serilog + "System.", "TidyNet,", "TidyNet.", "WebDriver,", "itextsharp,", "mscorlib,", "NUnit,", "NUnit.", "NUnit3.", + "Selenium.", "ImageProcessor", "MiniProfiler.", "Owin,", "SQLite", + "ReSharperTestRunner32" // used by resharper testrunner + }; + + private readonly IAssemblyProvider _assemblyProvider; + private readonly object _localFilteredAssemblyCacheLocker = new(); + private readonly ILogger _logger; + private readonly List _notifiedLoadExceptionAssemblies = new(); + + private readonly ITypeFinderConfig? _typeFinderConfig; + + private string[]? _assembliesAcceptingLoadExceptions; + private volatile HashSet? _localFilteredAssemblyCache; + + public TypeFinder(ILogger logger, IAssemblyProvider assemblyProvider, + ITypeFinderConfig? typeFinderConfig = null) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _assemblyProvider = assemblyProvider; + _typeFinderConfig = typeFinderConfig; + } - private string[]? _assembliesAcceptingLoadExceptions = null; + // used for benchmark tests + internal bool QueryWithReferencingAssemblies { get; set; } = true; - private string[] AssembliesAcceptingLoadExceptions + private string[] AssembliesAcceptingLoadExceptions + { + get { - get + if (_assembliesAcceptingLoadExceptions is not null) { - if (_assembliesAcceptingLoadExceptions is not null) - { - return _assembliesAcceptingLoadExceptions; - } - - _assembliesAcceptingLoadExceptions = - _typeFinderConfig?.AssembliesAcceptingLoadExceptions.Where(x => !x.IsNullOrWhiteSpace()).ToArray() ?? - Array.Empty(); - return _assembliesAcceptingLoadExceptions; } - } + _assembliesAcceptingLoadExceptions = + _typeFinderConfig?.AssembliesAcceptingLoadExceptions.Where(x => !x.IsNullOrWhiteSpace()).ToArray() ?? + Array.Empty(); - private bool AcceptsLoadExceptions(Assembly a) - { - if (AssembliesAcceptingLoadExceptions.Length == 0) - return false; - if (AssembliesAcceptingLoadExceptions.Length == 1 && AssembliesAcceptingLoadExceptions[0] == "*") - return true; - var name = a.GetName().Name; // simple name of the assembly - return AssembliesAcceptingLoadExceptions.Any(pattern => - { - if (pattern.Length > name?.Length) - return false; // pattern longer than name - if (pattern.Length == name?.Length) - return pattern.InvariantEquals(name); // same length, must be identical - if (pattern[pattern.Length] != '.') - return false; // pattern is shorter than name, must end with dot - return name?.StartsWith(pattern) ?? false; // and name must start with pattern - }); + return _assembliesAcceptingLoadExceptions; } + } - - private IEnumerable GetAllAssemblies() => _assemblyProvider.Assemblies; - - /// - public IEnumerable AssembliesToScan + /// + public IEnumerable AssembliesToScan + { + get { - get + lock (_localFilteredAssemblyCacheLocker) { - lock (_localFilteredAssemblyCacheLocker) + if (_localFilteredAssemblyCache != null) { - if (_localFilteredAssemblyCache != null) - return _localFilteredAssemblyCache; - - var assemblies = GetFilteredAssemblies(null, KnownAssemblyExclusionFilter); - _localFilteredAssemblyCache = new HashSet(assemblies); return _localFilteredAssemblyCache; } + + IEnumerable assemblies = GetFilteredAssemblies(null, KnownAssemblyExclusionFilter); + _localFilteredAssemblyCache = new HashSet(assemblies); + return _localFilteredAssemblyCache; } } + } + + /// + /// Finds any classes derived from the assignTypeFrom Type that contain the attribute TAttribute + /// + /// + /// + /// + /// + /// + public IEnumerable FindClassesOfTypeWithAttribute( + Type assignTypeFrom, + Type attributeType, + IEnumerable? assemblies = null, + bool onlyConcreteClasses = true) + { + IEnumerable assemblyList = assemblies ?? AssembliesToScan; + + return GetClassesWithBaseType(assignTypeFrom, assemblyList, onlyConcreteClasses, + //the additional filter will ensure that any found types also have the attribute applied. + t => t.GetCustomAttributes(attributeType, false).Any()); + } + + /// + /// Returns all types found of in the assemblies specified of type T + /// + /// + /// + /// + /// + public IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable? assemblies = null, + bool onlyConcreteClasses = true) + { + IEnumerable assemblyList = assemblies ?? AssembliesToScan; + + return GetClassesWithBaseType(assignTypeFrom, assemblyList, onlyConcreteClasses); + } + + /// + /// Finds any classes with the attribute. + /// + /// The attribute type + /// The assemblies. + /// if set to true only concrete classes. + /// + public IEnumerable FindClassesWithAttribute( + Type attributeType, + IEnumerable? assemblies = null, + bool onlyConcreteClasses = true) + { + IEnumerable assemblyList = assemblies ?? AssembliesToScan; + + return GetClassesWithAttribute(attributeType, assemblyList, onlyConcreteClasses); + } - /// - /// Return a distinct list of found local Assemblies and excluding the ones passed in and excluding the exclusion list filter - /// - /// - /// - /// - private IEnumerable GetFilteredAssemblies( - IEnumerable? excludeFromResults = null, - string[]? exclusionFilter = null) + /// + /// Returns a Type for the string type name + /// + /// + /// + public virtual Type? GetTypeByName(string name) + { + //NOTE: This will not find types in dynamic assemblies unless those assemblies are already loaded + //into the appdomain. + + + // This is exactly what the BuildManager does, if the type is an assembly qualified type + // name it will find it. + if (TypeNameContainsAssembly(name)) { - if (excludeFromResults == null) - excludeFromResults = new HashSet(); - if (exclusionFilter == null) - exclusionFilter = new string[] { }; - - return GetAllAssemblies() - .Where(x => excludeFromResults.Contains(x) == false - && x.GlobalAssemblyCache == false - && exclusionFilter.Any(f => x.FullName?.StartsWith(f) ?? false) == false); + return Type.GetType(name); } - // TODO: Kill this - - /// - /// this is our assembly filter to filter out known types that def don't contain types we'd like to find or plugins - /// - /// - /// NOTE the comma vs period... comma delimits the name in an Assembly FullName property so if it ends with comma then its an exact name match - /// NOTE this means that "foo." will NOT exclude "foo.dll" but only "foo.*.dll" - /// - internal static readonly string[] KnownAssemblyExclusionFilter = { - "mscorlib,", - "netstandard,", - "System,", - "Antlr3.", - "AutoMapper,", - "AutoMapper.", - "Autofac,", // DI - "Autofac.", - "AzureDirectory,", - "Castle.", // DI, tests - "ClientDependency.", - "CookComputing.", - "CSharpTest.", // BTree for NuCache - "DataAnnotationsExtensions,", - "DataAnnotationsExtensions.", - "Dynamic,", - "Examine,", - "Examine.", - "HtmlAgilityPack,", - "HtmlAgilityPack.", - "HtmlDiff,", - "ICSharpCode.", - "Iesi.Collections,", // used by NHibernate - "JetBrains.Annotations,", - "LightInject.", // DI - "LightInject,", - "Lucene.", - "Markdown,", - "Microsoft.", - "MiniProfiler,", - "Moq,", - "MySql.", - "NHibernate,", - "NHibernate.", - "Newtonsoft.", - "NPoco,", - "NuGet.", - "RouteDebugger,", - "Semver.", - "Serilog.", - "Serilog,", - "ServiceStack.", - "SqlCE4Umbraco,", - "Superpower,", // used by Serilog - "System.", - "TidyNet,", - "TidyNet.", - "WebDriver,", - "itextsharp,", - "mscorlib,", - "NUnit,", - "NUnit.", - "NUnit3.", - "Selenium.", - "ImageProcessor", - "MiniProfiler.", - "Owin,", - "SQLite", - "ReSharperTestRunner32" // used by resharper testrunner - }; - - /// - /// Finds any classes derived from the assignTypeFrom Type that contain the attribute TAttribute - /// - /// - /// - /// - /// - /// - public IEnumerable FindClassesOfTypeWithAttribute( - Type assignTypeFrom, - Type attributeType, - IEnumerable? assemblies = null, - bool onlyConcreteClasses = true) - { - var assemblyList = assemblies ?? AssembliesToScan; + // It didn't parse, so try loading from each already loaded assembly and cache it + return s_typeNamesCache.GetOrAdd(name, s => + AppDomain.CurrentDomain.GetAssemblies() + .Select(x => x.GetType(s)) + .FirstOrDefault(x => x != null)); + } - return GetClassesWithBaseType(assignTypeFrom, assemblyList, onlyConcreteClasses, - //the additional filter will ensure that any found types also have the attribute applied. - t => t.GetCustomAttributes(attributeType, false).Any()); - } - /// - /// Returns all types found of in the assemblies specified of type T - /// - /// - /// - /// - /// - public IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable? assemblies = null, bool onlyConcreteClasses = true) + private bool AcceptsLoadExceptions(Assembly a) + { + if (AssembliesAcceptingLoadExceptions.Length == 0) { - var assemblyList = assemblies ?? AssembliesToScan; - - return GetClassesWithBaseType(assignTypeFrom, assemblyList, onlyConcreteClasses); + return false; } - /// - /// Finds any classes with the attribute. - /// - /// The attribute type - /// The assemblies. - /// if set to true only concrete classes. - /// - public IEnumerable FindClassesWithAttribute( - Type attributeType, - IEnumerable? assemblies = null, - bool onlyConcreteClasses = true) + if (AssembliesAcceptingLoadExceptions.Length == 1 && AssembliesAcceptingLoadExceptions[0] == "*") { - var assemblyList = assemblies ?? AssembliesToScan; - - return GetClassesWithAttribute(attributeType, assemblyList, onlyConcreteClasses); + return true; } - /// - /// Returns a Type for the string type name - /// - /// - /// - public virtual Type? GetTypeByName(string name) + var name = a.GetName().Name; // simple name of the assembly + return AssembliesAcceptingLoadExceptions.Any(pattern => { + if (pattern.Length > name?.Length) + { + return false; // pattern longer than name + } - //NOTE: This will not find types in dynamic assemblies unless those assemblies are already loaded - //into the appdomain. - + if (pattern.Length == name?.Length) + { + return pattern.InvariantEquals(name); // same length, must be identical + } - // This is exactly what the BuildManager does, if the type is an assembly qualified type - // name it will find it. - if (TypeNameContainsAssembly(name)) + if (pattern[pattern.Length] != '.') { - return Type.GetType(name); + return false; // pattern is shorter than name, must end with dot } - // It didn't parse, so try loading from each already loaded assembly and cache it - return s_typeNamesCache.GetOrAdd(name, s => - AppDomain.CurrentDomain.GetAssemblies() - .Select(x => x.GetType(s)) - .FirstOrDefault(x => x != null)); + return name?.StartsWith(pattern) ?? false; // and name must start with pattern + }); + } + + + private IEnumerable GetAllAssemblies() => _assemblyProvider.Assemblies; + + /// + /// Return a distinct list of found local Assemblies and excluding the ones passed in and excluding the exclusion list + /// filter + /// + /// + /// + /// + private IEnumerable GetFilteredAssemblies( + IEnumerable? excludeFromResults = null, + string[]? exclusionFilter = null) + { + if (excludeFromResults == null) + { + excludeFromResults = new HashSet(); } - #region Private methods + if (exclusionFilter == null) + { + exclusionFilter = new string[] { }; + } + + return GetAllAssemblies() + .Where(x => excludeFromResults.Contains(x) == false + && x.GlobalAssemblyCache == false + && exclusionFilter.Any(f => x.FullName?.StartsWith(f) ?? false) == false); + } + + #region Private methods + + // borrowed from aspnet System.Web.UI.Util + private static bool TypeNameContainsAssembly(string typeName) => CommaIndexInTypeName(typeName) > 0; - // borrowed from aspnet System.Web.UI.Util - private static bool TypeNameContainsAssembly(string typeName) + // borrowed from aspnet System.Web.UI.Util + private static int CommaIndexInTypeName(string typeName) + { + var num1 = typeName.LastIndexOf(','); + if (num1 < 0) { - return CommaIndexInTypeName(typeName) > 0; + return -1; } - // borrowed from aspnet System.Web.UI.Util - private static int CommaIndexInTypeName(string typeName) + var num2 = typeName.LastIndexOf(']'); + if (num2 > num1) { - var num1 = typeName.LastIndexOf(','); - if (num1 < 0) - return -1; - var num2 = typeName.LastIndexOf(']'); - if (num2 > num1) - return -1; - return typeName.IndexOf(',', num2 + 1); + return -1; } - private IEnumerable GetClassesWithAttribute( - Type attributeType, - IEnumerable assemblies, - bool onlyConcreteClasses) + return typeName.IndexOf(',', num2 + 1); + } + + private IEnumerable GetClassesWithAttribute( + Type attributeType, + IEnumerable assemblies, + bool onlyConcreteClasses) + { + if (typeof(Attribute).IsAssignableFrom(attributeType) == false) { - if (typeof(Attribute).IsAssignableFrom(attributeType) == false) - throw new ArgumentException("Type " + attributeType + " is not an Attribute type."); + throw new ArgumentException("Type " + attributeType + " is not an Attribute type."); + } - var candidateAssemblies = new HashSet(assemblies); - var attributeAssemblyIsCandidate = candidateAssemblies.Contains(attributeType.Assembly); - candidateAssemblies.Remove(attributeType.Assembly); - var types = new List(); + var candidateAssemblies = new HashSet(assemblies); + var attributeAssemblyIsCandidate = candidateAssemblies.Contains(attributeType.Assembly); + candidateAssemblies.Remove(attributeType.Assembly); + var types = new List(); - var stack = new Stack(); - stack.Push(attributeType.Assembly); + var stack = new Stack(); + stack.Push(attributeType.Assembly); - if (!QueryWithReferencingAssemblies) + if (!QueryWithReferencingAssemblies) + { + foreach (Assembly a in candidateAssemblies) { - foreach (var a in candidateAssemblies) - stack.Push(a); + stack.Push(a); } + } - while (stack.Count > 0) - { - var assembly = stack.Pop(); + while (stack.Count > 0) + { + Assembly assembly = stack.Pop(); - IReadOnlyList? assemblyTypes = null; - if (assembly != attributeType.Assembly || attributeAssemblyIsCandidate) + IReadOnlyList? assemblyTypes = null; + if (assembly != attributeType.Assembly || attributeAssemblyIsCandidate) + { + // get all assembly types that can be assigned to baseType + try { - // get all assembly types that can be assigned to baseType - try - { - assemblyTypes = GetTypesWithFormattedException(assembly) - .ToList(); // in try block - } - catch (TypeLoadException ex) - { - _logger.LogError(ex, "Could not query types on {Assembly} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", assembly); - continue; - } - - types.AddRange(assemblyTypes.Where(x => - x.IsClass // only classes - && (x.IsAbstract == false || x.IsSealed == false) // ie non-static, static is abstract and sealed - && x.IsNestedPrivate == false // exclude nested private - && (onlyConcreteClasses == false || x.IsAbstract == false) // exclude abstract - && x.GetCustomAttribute() == null // exclude hidden - && x.GetCustomAttributes(attributeType, false).Any())); // marked with the attribute + assemblyTypes = GetTypesWithFormattedException(assembly) + .ToList(); // in try block } - - if (assembly != attributeType.Assembly && assemblyTypes?.Where(attributeType.IsAssignableFrom).Any() == false) + catch (TypeLoadException ex) + { + _logger.LogError(ex, + "Could not query types on {Assembly} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", + assembly); continue; + } - if (QueryWithReferencingAssemblies) + types.AddRange(assemblyTypes.Where(x => + x.IsClass // only classes + && (x.IsAbstract == false || x.IsSealed == false) // ie non-static, static is abstract and sealed + && x.IsNestedPrivate == false // exclude nested private + && (onlyConcreteClasses == false || x.IsAbstract == false) // exclude abstract + && x.GetCustomAttribute() == null // exclude hidden + && x.GetCustomAttributes(attributeType, false).Any())); // marked with the attribute + } + + if (assembly != attributeType.Assembly && + assemblyTypes?.Where(attributeType.IsAssignableFrom).Any() == false) + { + continue; + } + + if (QueryWithReferencingAssemblies) + { + foreach (Assembly referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies)) { - foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies)) - { - candidateAssemblies.Remove(referencing); - stack.Push(referencing); - } + candidateAssemblies.Remove(referencing); + stack.Push(referencing); } } - - return types; } - /// - /// Finds types that are assignable from the assignTypeFrom parameter and will scan for these types in the assembly - /// list passed in, however we will only scan assemblies that have a reference to the assignTypeFrom Type or any type - /// deriving from the base type. - /// - /// - /// - /// - /// An additional filter to apply for what types will actually be included in the return value - /// - private IEnumerable GetClassesWithBaseType( - Type baseType, - IEnumerable assemblies, - bool onlyConcreteClasses, - Func? additionalFilter = null) - { - var candidateAssemblies = new HashSet(assemblies); - var baseTypeAssemblyIsCandidate = candidateAssemblies.Contains(baseType.Assembly); - candidateAssemblies.Remove(baseType.Assembly); - var types = new List(); + return types; + } + + /// + /// Finds types that are assignable from the assignTypeFrom parameter and will scan for these types in the assembly + /// list passed in, however we will only scan assemblies that have a reference to the assignTypeFrom Type or any type + /// deriving from the base type. + /// + /// + /// + /// + /// + /// An additional filter to apply for what types will actually be included in the return + /// value + /// + /// + private IEnumerable GetClassesWithBaseType( + Type baseType, + IEnumerable assemblies, + bool onlyConcreteClasses, + Func? additionalFilter = null) + { + var candidateAssemblies = new HashSet(assemblies); + var baseTypeAssemblyIsCandidate = candidateAssemblies.Contains(baseType.Assembly); + candidateAssemblies.Remove(baseType.Assembly); + var types = new List(); - var stack = new Stack(); - stack.Push(baseType.Assembly); + var stack = new Stack(); + stack.Push(baseType.Assembly); - if (!QueryWithReferencingAssemblies) + if (!QueryWithReferencingAssemblies) + { + foreach (Assembly a in candidateAssemblies) { - foreach (var a in candidateAssemblies) - stack.Push(a); + stack.Push(a); } + } - while (stack.Count > 0) - { - var assembly = stack.Pop(); + while (stack.Count > 0) + { + Assembly assembly = stack.Pop(); - // get all assembly types that can be assigned to baseType - IReadOnlyList? assemblyTypes = null; - if (assembly != baseType.Assembly || baseTypeAssemblyIsCandidate) + // get all assembly types that can be assigned to baseType + IReadOnlyList? assemblyTypes = null; + if (assembly != baseType.Assembly || baseTypeAssemblyIsCandidate) + { + try { - try - { - assemblyTypes = GetTypesWithFormattedException(assembly) - .Where(baseType.IsAssignableFrom) - .ToList(); // in try block - } - catch (TypeLoadException ex) - { - _logger.LogError(ex, "Could not query types on {Assembly} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", assembly); - continue; - } - - types.AddRange(assemblyTypes.Where(x => - x.IsClass // only classes - && (x.IsAbstract == false || x.IsSealed == false) // ie non-static, static is abstract and sealed - && x.IsNestedPrivate == false // exclude nested private - && (onlyConcreteClasses == false || x.IsAbstract == false) // exclude abstract - && x.GetCustomAttribute(false) == null // exclude hidden - && (additionalFilter == null || additionalFilter(x)))); // filter + assemblyTypes = GetTypesWithFormattedException(assembly) + .Where(baseType.IsAssignableFrom) + .ToList(); // in try block } - - if (assembly != baseType.Assembly && (assemblyTypes?.All(x => x.IsSealed) ?? false)) + catch (TypeLoadException ex) + { + _logger.LogError(ex, + "Could not query types on {Assembly} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", + assembly); continue; + } + + types.AddRange(assemblyTypes.Where(x => + x.IsClass // only classes + && (x.IsAbstract == false || x.IsSealed == false) // ie non-static, static is abstract and sealed + && x.IsNestedPrivate == false // exclude nested private + && (onlyConcreteClasses == false || x.IsAbstract == false) // exclude abstract + && x.GetCustomAttribute(false) == null // exclude hidden + && (additionalFilter == null || additionalFilter(x)))); // filter + } + + if (assembly != baseType.Assembly && (assemblyTypes?.All(x => x.IsSealed) ?? false)) + { + continue; + } - if (QueryWithReferencingAssemblies) + if (QueryWithReferencingAssemblies) + { + foreach (Assembly referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies)) { - foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies)) - { - candidateAssemblies.Remove(referencing); - stack.Push(referencing); - } + candidateAssemblies.Remove(referencing); + stack.Push(referencing); } } - - return types; } - private IEnumerable GetTypesWithFormattedException(Assembly a) + return types; + } + + private IEnumerable GetTypesWithFormattedException(Assembly a) + { + //if the assembly is dynamic, do not try to scan it + if (a.IsDynamic) { - //if the assembly is dynamic, do not try to scan it - if (a.IsDynamic) - return Enumerable.Empty(); + return Enumerable.Empty(); + } - var getAll = a.GetCustomAttribute() == null; + var getAll = a.GetCustomAttribute() == null; - try - { - //we need to detect if an assembly is partially trusted, if so we cannot go interrogating all of it's types - //only its exported types, otherwise we'll get exceptions. - return getAll ? a.GetTypes() : a.GetExportedTypes(); - } - catch (TypeLoadException ex) // GetExportedTypes *can* throw TypeLoadException! - { - var sb = new StringBuilder(); - AppendCouldNotLoad(sb, a, getAll); - AppendLoaderException(sb, ex); + try + { + //we need to detect if an assembly is partially trusted, if so we cannot go interrogating all of it's types + //only its exported types, otherwise we'll get exceptions. + return getAll ? a.GetTypes() : a.GetExportedTypes(); + } + catch (TypeLoadException ex) // GetExportedTypes *can* throw TypeLoadException! + { + var sb = new StringBuilder(); + AppendCouldNotLoad(sb, a, getAll); + AppendLoaderException(sb, ex); - // rethrow as ReflectionTypeLoadException (for consistency) with new message - throw new ReflectionTypeLoadException(new Type[0], new Exception[] { ex }, sb.ToString()); - } - catch (ReflectionTypeLoadException rex) // GetTypes throws ReflectionTypeLoadException + // rethrow as ReflectionTypeLoadException (for consistency) with new message + throw new ReflectionTypeLoadException(new Type[0], new Exception[] {ex}, sb.ToString()); + } + catch (ReflectionTypeLoadException rex) // GetTypes throws ReflectionTypeLoadException + { + var sb = new StringBuilder(); + AppendCouldNotLoad(sb, a, getAll); + foreach (Exception loaderException in rex.LoaderExceptions.WhereNotNull()) { - var sb = new StringBuilder(); - AppendCouldNotLoad(sb, a, getAll); - foreach (var loaderException in rex.LoaderExceptions.WhereNotNull()) - AppendLoaderException(sb, loaderException); + AppendLoaderException(sb, loaderException); + } - var ex = new ReflectionTypeLoadException(rex.Types, rex.LoaderExceptions, sb.ToString()); + var ex = new ReflectionTypeLoadException(rex.Types, rex.LoaderExceptions, sb.ToString()); - // rethrow with new message, unless accepted - if (AcceptsLoadExceptions(a) == false) - throw ex; + // rethrow with new message, unless accepted + if (AcceptsLoadExceptions(a) == false) + { + throw ex; + } - // log a warning, and return what we can - lock (_notifiedLoadExceptionAssemblies) + // log a warning, and return what we can + lock (_notifiedLoadExceptionAssemblies) + { + if (a.FullName is not null && _notifiedLoadExceptionAssemblies.Contains(a.FullName) == false) { - if (a.FullName is not null && _notifiedLoadExceptionAssemblies.Contains(a.FullName) == false) - { - _notifiedLoadExceptionAssemblies.Add(a.FullName); - _logger.LogWarning(ex, "Could not load all types from {TypeName}.", a.GetName().Name); - } + _notifiedLoadExceptionAssemblies.Add(a.FullName); + _logger.LogWarning(ex, "Could not load all types from {TypeName}.", a.GetName().Name); } - return rex.Types.WhereNotNull().ToArray(); } - } - private static void AppendCouldNotLoad(StringBuilder sb, Assembly a, bool getAll) - { - sb.Append("Could not load "); - sb.Append(getAll ? "all" : "exported"); - sb.Append(" types from \""); - sb.Append(a.FullName); - sb.AppendLine("\" due to LoaderExceptions, skipping:"); + return rex.Types.WhereNotNull().ToArray(); } + } - private static void AppendLoaderException(StringBuilder sb, Exception loaderException) - { - sb.Append(". "); - sb.Append(loaderException.GetType().FullName); + private static void AppendCouldNotLoad(StringBuilder sb, Assembly a, bool getAll) + { + sb.Append("Could not load "); + sb.Append(getAll ? "all" : "exported"); + sb.Append(" types from \""); + sb.Append(a.FullName); + sb.AppendLine("\" due to LoaderExceptions, skipping:"); + } - if (loaderException is TypeLoadException tloadex) - { - sb.Append(" on "); - sb.Append(tloadex.TypeName); - } + private static void AppendLoaderException(StringBuilder sb, Exception loaderException) + { + sb.Append(". "); + sb.Append(loaderException.GetType().FullName); - sb.Append(": "); - sb.Append(loaderException.Message); - sb.AppendLine(); + if (loaderException is TypeLoadException tloadex) + { + sb.Append(" on "); + sb.Append(tloadex.TypeName); } - #endregion - + sb.Append(": "); + sb.Append(loaderException.Message); + sb.AppendLine(); } + + #endregion } diff --git a/src/Umbraco.Core/Composing/TypeFinderConfig.cs b/src/Umbraco.Core/Composing/TypeFinderConfig.cs index 4b5271039fb0..2fd9283500a6 100644 --- a/src/Umbraco.Core/Composing/TypeFinderConfig.cs +++ b/src/Umbraco.Core/Composing/TypeFinderConfig.cs @@ -1,36 +1,32 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Configuration.UmbracoSettings; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// TypeFinder config via appSettings +/// +public class TypeFinderConfig : ITypeFinderConfig { - /// - /// TypeFinder config via appSettings - /// - public class TypeFinderConfig : ITypeFinderConfig - { - private readonly TypeFinderSettings _settings; - private IEnumerable? _assembliesAcceptingLoadExceptions; + private readonly TypeFinderSettings _settings; + private IEnumerable? _assembliesAcceptingLoadExceptions; - public TypeFinderConfig(IOptions settings) => _settings = settings.Value; + public TypeFinderConfig(IOptions settings) => _settings = settings.Value; - public IEnumerable AssembliesAcceptingLoadExceptions + public IEnumerable AssembliesAcceptingLoadExceptions + { + get { - get + if (_assembliesAcceptingLoadExceptions != null) { - if (_assembliesAcceptingLoadExceptions != null) - { - return _assembliesAcceptingLoadExceptions; - } - - var s = _settings.AssembliesAcceptingLoadExceptions; - return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s) - ? Array.Empty() - : s.Split(',').Select(x => x.Trim()).ToArray(); + return _assembliesAcceptingLoadExceptions; } + + var s = _settings.AssembliesAcceptingLoadExceptions; + return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s) + ? Array.Empty() + : s.Split(',').Select(x => x.Trim()).ToArray(); } } } diff --git a/src/Umbraco.Core/Composing/TypeFinderExtensions.cs b/src/Umbraco.Core/Composing/TypeFinderExtensions.cs index adb920b64a0e..8524be300f3a 100644 --- a/src/Umbraco.Core/Composing/TypeFinderExtensions.cs +++ b/src/Umbraco.Core/Composing/TypeFinderExtensions.cs @@ -1,46 +1,46 @@ -using System; -using System.Collections.Generic; -using System.Reflection; +using System.Reflection; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class TypeFinderExtensions { - public static class TypeFinderExtensions - { - /// - /// Finds any classes derived from the type T that contain the attribute TAttribute - /// - /// - /// - /// - /// - /// - /// - public static IEnumerable FindClassesOfTypeWithAttribute(this ITypeFinder typeFinder, IEnumerable? assemblies = null, bool onlyConcreteClasses = true) - where TAttribute : Attribute - => typeFinder.FindClassesOfTypeWithAttribute(typeof(T), typeof(TAttribute), assemblies, onlyConcreteClasses); + /// + /// Finds any classes derived from the type T that contain the attribute TAttribute + /// + /// + /// + /// + /// + /// + /// + public static IEnumerable FindClassesOfTypeWithAttribute(this ITypeFinder typeFinder, + IEnumerable? assemblies = null, bool onlyConcreteClasses = true) + where TAttribute : Attribute + => typeFinder.FindClassesOfTypeWithAttribute(typeof(T), typeof(TAttribute), assemblies, onlyConcreteClasses); - /// - /// Returns all types found of in the assemblies specified of type T - /// - /// - /// - /// - /// - /// - public static IEnumerable FindClassesOfType(this ITypeFinder typeFinder, IEnumerable? assemblies = null, bool onlyConcreteClasses = true) - => typeFinder.FindClassesOfType(typeof(T), assemblies, onlyConcreteClasses); + /// + /// Returns all types found of in the assemblies specified of type T + /// + /// + /// + /// + /// + /// + public static IEnumerable FindClassesOfType(this ITypeFinder typeFinder, + IEnumerable? assemblies = null, bool onlyConcreteClasses = true) + => typeFinder.FindClassesOfType(typeof(T), assemblies, onlyConcreteClasses); - /// - /// Finds the classes with attribute. - /// - /// - /// - /// The assemblies. - /// if set to true only concrete classes. - /// - public static IEnumerable FindClassesWithAttribute(this ITypeFinder typeFinder, IEnumerable? assemblies = null, bool onlyConcreteClasses = true) - where T : Attribute - => typeFinder.FindClassesWithAttribute(typeof(T), assemblies, onlyConcreteClasses); - } + /// + /// Finds the classes with attribute. + /// + /// + /// + /// The assemblies. + /// if set to true only concrete classes. + /// + public static IEnumerable FindClassesWithAttribute(this ITypeFinder typeFinder, + IEnumerable? assemblies = null, bool onlyConcreteClasses = true) + where T : Attribute + => typeFinder.FindClassesWithAttribute(typeof(T), assemblies, onlyConcreteClasses); } diff --git a/src/Umbraco.Core/Composing/TypeHelper.cs b/src/Umbraco.Core/Composing/TypeHelper.cs index 08893732a8fe..5f604296b234 100644 --- a/src/Umbraco.Core/Composing/TypeHelper.cs +++ b/src/Umbraco.Core/Composing/TypeHelper.cs @@ -1,395 +1,417 @@ -using System; -using System.Collections; +using System.Collections; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; using System.Reflection; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// A utility class for type checking, this provides internal caching so that calls to these methods will be faster +/// than doing a manual type check in c# +/// +public static class TypeHelper { + private static readonly ConcurrentDictionary, PropertyInfo[]> GetPropertiesCache + = new(); + + private static readonly ConcurrentDictionary GetFieldsCache = new(); + + private static readonly Assembly[] EmptyAssemblies = new Assembly[0]; + /// - /// A utility class for type checking, this provides internal caching so that calls to these methods will be faster - /// than doing a manual type check in c# + /// Based on a type we'll check if it is IEnumerable{T} (or similar) and if so we'll return a List{T}, this will also + /// deal with array types and return List{T} for those too. + /// If it cannot be done, null is returned. /// - public static class TypeHelper + public static IList? CreateGenericEnumerableFromObject(object? obj) { - private static readonly ConcurrentDictionary, PropertyInfo[]> GetPropertiesCache - = new ConcurrentDictionary, PropertyInfo[]>(); - private static readonly ConcurrentDictionary GetFieldsCache - = new ConcurrentDictionary(); - - private static readonly Assembly[] EmptyAssemblies = new Assembly[0]; - + if (obj is null) + { + return null; + } + Type type = obj.GetType(); - /// - /// Based on a type we'll check if it is IEnumerable{T} (or similar) and if so we'll return a List{T}, this will also deal with array types and return List{T} for those too. - /// If it cannot be done, null is returned. - /// - public static IList? CreateGenericEnumerableFromObject(object? obj) + if (type.IsGenericType) { - if (obj is null) + Type genericTypeDef = type.GetGenericTypeDefinition(); + + if (genericTypeDef == typeof(IEnumerable<>) + || genericTypeDef == typeof(ICollection<>) + || genericTypeDef == typeof(Collection<>) + || genericTypeDef == typeof(IList<>) + || genericTypeDef == typeof(List<>) + //this will occur when Linq is used and we get the odd WhereIterator or DistinctIterators since those are special iterator types + || obj is IEnumerable) { - return null; + //if it is a IEnumerable<>, IList or ICollection<> we'll use a List<> + Type genericType = typeof(List<>).MakeGenericType(type.GetGenericArguments()); + //pass in obj to fill the list + return (IList?)Activator.CreateInstance(genericType, obj); } + } - var type = obj.GetType(); - - if (type.IsGenericType) + if (type.IsArray) + { + //if its an array, we'll use a List<> + Type typeArguments = type.GetElementType(); + if (typeArguments is not null) { - var genericTypeDef = type.GetGenericTypeDefinition(); - - if (genericTypeDef == typeof(IEnumerable<>) - || genericTypeDef == typeof(ICollection<>) - || genericTypeDef == typeof(Collection<>) - || genericTypeDef == typeof(IList<>) - || genericTypeDef == typeof(List<>) - //this will occur when Linq is used and we get the odd WhereIterator or DistinctIterators since those are special iterator types - || obj is IEnumerable) - { - //if it is a IEnumerable<>, IList or ICollection<> we'll use a List<> - var genericType = typeof(List<>).MakeGenericType(type.GetGenericArguments()); - //pass in obj to fill the list - return (IList?)Activator.CreateInstance(genericType, obj); - } + Type genericType = typeof(List<>).MakeGenericType(typeArguments); + //pass in obj to fill the list + return (IList?)Activator.CreateInstance(genericType, obj); } + } - if (type.IsArray) - { - //if its an array, we'll use a List<> - var typeArguments = type.GetElementType(); - if (typeArguments is not null) - { - Type genericType = typeof(List<>).MakeGenericType(typeArguments); - //pass in obj to fill the list - return (IList?)Activator.CreateInstance(genericType, obj); - } - } + return null; + } - return null; - } + /// + /// Checks if the method is actually overriding a base method + /// + /// + /// + public static bool IsOverride(MethodInfo m) => m.GetBaseDefinition().DeclaringType != m.DeclaringType; - /// - /// Checks if the method is actually overriding a base method - /// - /// - /// - public static bool IsOverride(MethodInfo m) + /// + /// Find all assembly references that are referencing the assignTypeFrom Type's assembly found in the assemblyList + /// + /// The referenced assembly. + /// A list of assemblies. + /// + /// + /// If the assembly of the assignTypeFrom Type is in the App_Code assembly, then we return nothing since things cannot + /// reference that assembly, same with the global.asax assembly. + /// + public static IReadOnlyList GetReferencingAssemblies(Assembly assembly, IEnumerable assemblies) + { + if (assembly.IsDynamic || assembly.IsAppCodeAssembly() || assembly.IsGlobalAsaxAssembly()) { - return m.GetBaseDefinition().DeclaringType != m.DeclaringType; + return EmptyAssemblies; } - /// - /// Find all assembly references that are referencing the assignTypeFrom Type's assembly found in the assemblyList - /// - /// The referenced assembly. - /// A list of assemblies. - /// - /// - /// If the assembly of the assignTypeFrom Type is in the App_Code assembly, then we return nothing since things cannot - /// reference that assembly, same with the global.asax assembly. - /// - public static IReadOnlyList GetReferencingAssemblies(Assembly assembly, IEnumerable assemblies) - { - if (assembly.IsDynamic || assembly.IsAppCodeAssembly() || assembly.IsGlobalAsaxAssembly()) - return EmptyAssemblies; - - // find all assembly references that are referencing the current type's assembly since we - // should only be scanning those assemblies because any other assembly will definitely not - // contain sub type's of the one we're currently looking for - var name = assembly.GetName().Name; - return assemblies.Where(x => x == assembly || name is not null ? HasReference(x, name!) : false).ToList(); - } + // find all assembly references that are referencing the current type's assembly since we + // should only be scanning those assemblies because any other assembly will definitely not + // contain sub type's of the one we're currently looking for + var name = assembly.GetName().Name; + return assemblies.Where(x => x == assembly || name is not null ? HasReference(x, name!) : false).ToList(); + } - /// - /// Determines if an assembly references another assembly. - /// - /// - /// - /// - public static bool HasReference(Assembly assembly, string name) + /// + /// Determines if an assembly references another assembly. + /// + /// + /// + /// + public static bool HasReference(Assembly assembly, string name) + { + // ReSharper disable once LoopCanBeConvertedToQuery - no! + foreach (AssemblyName a in assembly.GetReferencedAssemblies()) { - // ReSharper disable once LoopCanBeConvertedToQuery - no! - foreach (var a in assembly.GetReferencedAssemblies()) + if (string.Equals(a.Name, name, StringComparison.Ordinal)) { - if (string.Equals(a.Name, name, StringComparison.Ordinal)) return true; + return true; } - return false; } - /// - /// Returns true if the type is a class and is not static - /// - /// - /// - public static bool IsNonStaticClass(Type t) + return false; + } + + /// + /// Returns true if the type is a class and is not static + /// + /// + /// + public static bool IsNonStaticClass(Type t) => t.IsClass && IsStaticClass(t) == false; + + /// + /// Returns true if the type is a static class + /// + /// + /// + /// + /// In IL a static class is abstract and sealed + /// see: http://stackoverflow.com/questions/1175888/determine-if-a-type-is-static + /// + public static bool IsStaticClass(Type type) => type.IsAbstract && type.IsSealed; + + /// + /// Finds a lowest base class amongst a collection of types + /// + /// + /// + /// + /// The term 'lowest' refers to the most base class of the type collection. + /// If a base type is not found amongst the type collection then an invalid attempt is returned. + /// + public static Attempt GetLowestBaseType(params Type[] types) + { + if (types.Length == 0) { - return t.IsClass && IsStaticClass(t) == false; + return Attempt.Fail(); } - /// - /// Returns true if the type is a static class - /// - /// - /// - /// - /// In IL a static class is abstract and sealed - /// see: http://stackoverflow.com/questions/1175888/determine-if-a-type-is-static - /// - public static bool IsStaticClass(Type type) + if (types.Length == 1) { - return type.IsAbstract && type.IsSealed; + return Attempt.Succeed(types[0]); } - /// - /// Finds a lowest base class amongst a collection of types - /// - /// - /// - /// - /// The term 'lowest' refers to the most base class of the type collection. - /// If a base type is not found amongst the type collection then an invalid attempt is returned. - /// - public static Attempt GetLowestBaseType(params Type[] types) + foreach (Type curr in types) { - if (types.Length == 0) - return Attempt.Fail(); + IEnumerable others = types.Except(new[] {curr}); - if (types.Length == 1) - return Attempt.Succeed(types[0]); + //is the current type a common denominator for all others ? + var isBase = others.All(curr.IsAssignableFrom); - foreach (var curr in types) + //if this type is the base for all others + if (isBase) { - var others = types.Except(new[] {curr}); + return Attempt.Succeed(curr); + } + } - //is the current type a common denominator for all others ? - var isBase = others.All(curr.IsAssignableFrom); + return Attempt.Fail(); + } - //if this type is the base for all others - if (isBase) - { - return Attempt.Succeed(curr); - } - } + /// + /// Determines whether the type is assignable from the specified implementation, + /// and caches the result across the application using a . + /// + /// The type of the contract. + /// The implementation. + /// + /// true if [is type assignable from] [the specified contract]; otherwise, false. + /// + public static bool IsTypeAssignableFrom(Type contract, Type? implementation) => + contract.IsAssignableFrom(implementation); - return Attempt.Fail(); - } + /// + /// Determines whether the type is assignable from the specified implementation + /// , + /// and caches the result across the application using a . + /// + /// The type of the contract. + /// The implementation. + public static bool IsTypeAssignableFrom(Type implementation) => + IsTypeAssignableFrom(typeof(TContract), implementation); - /// - /// Determines whether the type is assignable from the specified implementation, - /// and caches the result across the application using a . - /// - /// The type of the contract. - /// The implementation. - /// - /// true if [is type assignable from] [the specified contract]; otherwise, false. - /// - public static bool IsTypeAssignableFrom(Type contract, Type? implementation) + /// + /// Determines whether the object instance is assignable from the specified + /// implementation , + /// and caches the result across the application using a . + /// + /// The type of the contract. + /// The implementation. + public static bool IsTypeAssignableFrom(object implementation) + { + if (implementation == null) { - return contract.IsAssignableFrom(implementation); + throw new ArgumentNullException(nameof(implementation)); } - /// - /// Determines whether the type is assignable from the specified implementation , - /// and caches the result across the application using a . - /// - /// The type of the contract. - /// The implementation. - public static bool IsTypeAssignableFrom(Type implementation) - { - return IsTypeAssignableFrom(typeof(TContract), implementation); - } + return IsTypeAssignableFrom(implementation.GetType()); + } - /// - /// Determines whether the object instance is assignable from the specified implementation , - /// and caches the result across the application using a . - /// - /// The type of the contract. - /// The implementation. - public static bool IsTypeAssignableFrom(object implementation) - { - if (implementation == null) throw new ArgumentNullException(nameof(implementation)); - return IsTypeAssignableFrom(implementation.GetType()); - } + /// + /// A method to determine whether represents a value type. + /// + /// The implementation. + public static bool IsValueType(Type implementation) => implementation.IsValueType || implementation.IsPrimitive; - /// - /// A method to determine whether represents a value type. - /// - /// The implementation. - public static bool IsValueType(Type implementation) - { - return implementation.IsValueType || implementation.IsPrimitive; - } + /// + /// A method to determine whether is an implied value type ( + /// , or a string). + /// + /// The implementation. + public static bool IsImplicitValueType(Type implementation) => + IsValueType(implementation) || implementation.IsEnum || implementation == typeof(string); - /// - /// A method to determine whether is an implied value type (, or a string). - /// - /// The implementation. - public static bool IsImplicitValueType(Type implementation) - { - return IsValueType(implementation) || implementation.IsEnum || implementation == typeof (string); - } + /// + /// Returns (and caches) a PropertyInfo from a type + /// + /// + /// + /// + /// + /// + /// + /// + public static PropertyInfo? GetProperty(Type type, string name, + bool mustRead = true, + bool mustWrite = true, + bool includeIndexed = false, + bool caseSensitive = true) => + CachedDiscoverableProperties(type, mustRead, mustWrite, includeIndexed) + .FirstOrDefault(x => caseSensitive ? x.Name == name : x.Name.InvariantEquals(name)); + + /// + /// Gets (and caches) discoverable in the current for a given + /// . + /// + /// The source. + /// + public static FieldInfo[] CachedDiscoverableFields(Type type) => + GetFieldsCache.GetOrAdd( + type, + x => type + .GetFields(BindingFlags.Public | BindingFlags.Instance) + .Where(y => y.IsInitOnly == false) + .ToArray()); - /// - /// Returns (and caches) a PropertyInfo from a type - /// - /// - /// - /// - /// - /// - /// - /// - public static PropertyInfo? GetProperty(Type type, string name, - bool mustRead = true, - bool mustWrite = true, - bool includeIndexed = false, - bool caseSensitive = true) + /// + /// Gets (and caches) discoverable in the current for a given + /// . + /// + /// The source. + /// true if the properties discovered are readable + /// true if the properties discovered are writable + /// true if the properties discovered are indexable + /// + public static PropertyInfo[] CachedDiscoverableProperties(Type type, bool mustRead = true, bool mustWrite = true, + bool includeIndexed = false) => + GetPropertiesCache.GetOrAdd( + new Tuple(type, mustRead, mustWrite, includeIndexed), + x => type + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(y => (mustRead == false || y.CanRead) + && (mustWrite == false || y.CanWrite) + && (includeIndexed || y.GetIndexParameters().Any() == false)) + .ToArray()); + + #region Match Type + + // TODO: Need to determine if these methods should replace/combine/merge etc with IsTypeAssignableFrom, IsAssignableFromGeneric + + // readings: + // http://stackoverflow.com/questions/2033912/c-sharp-variance-problem-assigning-listderived-as-listbase + // http://stackoverflow.com/questions/2208043/generic-variance-in-c-sharp-4-0 + // http://stackoverflow.com/questions/8401738/c-sharp-casting-generics-covariance-and-contravariance + // http://stackoverflow.com/questions/1827425/how-to-check-programatically-if-a-type-is-a-struct-or-a-class + // http://stackoverflow.com/questions/74616/how-to-detect-if-type-is-another-generic-type/1075059#1075059 + + private static bool MatchGeneric(Type implementation, Type contract, IDictionary bindings) + { + // trying to match eg List with List + // or List>> with List>> + // classes are NOT invariant so List does not match List + + if (implementation.IsGenericType == false) { - return CachedDiscoverableProperties(type, mustRead, mustWrite, includeIndexed) - .FirstOrDefault(x => caseSensitive ? (x.Name == name) : x.Name.InvariantEquals(name)); + return false; } - /// - /// Gets (and caches) discoverable in the current for a given . - /// - /// The source. - /// - public static FieldInfo[] CachedDiscoverableFields(Type type) + // must have the same generic type definition + Type implDef = implementation.GetGenericTypeDefinition(); + Type contDef = contract.GetGenericTypeDefinition(); + if (implDef != contDef) { - return GetFieldsCache.GetOrAdd( - type, - x => type - .GetFields(BindingFlags.Public | BindingFlags.Instance) - .Where(y => y.IsInitOnly == false) - .ToArray()); + return false; } - /// - /// Gets (and caches) discoverable in the current for a given . - /// - /// The source. - /// true if the properties discovered are readable - /// true if the properties discovered are writable - /// true if the properties discovered are indexable - /// - public static PropertyInfo[] CachedDiscoverableProperties(Type type, bool mustRead = true, bool mustWrite = true, bool includeIndexed = false) + // must have the same number of generic arguments + Type[] implArgs = implementation.GetGenericArguments(); + Type[] contArgs = contract.GetGenericArguments(); + if (implArgs.Length != contArgs.Length) { - return GetPropertiesCache.GetOrAdd( - new Tuple(type, mustRead, mustWrite, includeIndexed), - x => type - .GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(y => (mustRead == false || y.CanRead) - && (mustWrite == false || y.CanWrite) - && (includeIndexed || y.GetIndexParameters().Any() == false)) - .ToArray()); + return false; } - #region Match Type - - // TODO: Need to determine if these methods should replace/combine/merge etc with IsTypeAssignableFrom, IsAssignableFromGeneric - - // readings: - // http://stackoverflow.com/questions/2033912/c-sharp-variance-problem-assigning-listderived-as-listbase - // http://stackoverflow.com/questions/2208043/generic-variance-in-c-sharp-4-0 - // http://stackoverflow.com/questions/8401738/c-sharp-casting-generics-covariance-and-contravariance - // http://stackoverflow.com/questions/1827425/how-to-check-programatically-if-a-type-is-a-struct-or-a-class - // http://stackoverflow.com/questions/74616/how-to-detect-if-type-is-another-generic-type/1075059#1075059 - - private static bool MatchGeneric(Type implementation, Type contract, IDictionary bindings) + // generic arguments must match + // in insta we should have actual types (eg int, string...) + // in typea we can have generic parameters (eg ) + for (var i = 0; i < implArgs.Length; i++) { - // trying to match eg List with List - // or List>> with List>> - // classes are NOT invariant so List does not match List - - if (implementation.IsGenericType == false) return false; - - // must have the same generic type definition - var implDef = implementation.GetGenericTypeDefinition(); - var contDef = contract.GetGenericTypeDefinition(); - if (implDef != contDef) return false; - - // must have the same number of generic arguments - var implArgs = implementation.GetGenericArguments(); - var contArgs = contract.GetGenericArguments(); - if (implArgs.Length != contArgs.Length) return false; - - // generic arguments must match - // in insta we should have actual types (eg int, string...) - // in typea we can have generic parameters (eg ) - for (var i = 0; i < implArgs.Length; i++) + const bool variance = false; // classes are NOT invariant + if (MatchType(implArgs[i], contArgs[i], bindings, variance) == false) { - const bool variance = false; // classes are NOT invariant - if (MatchType(implArgs[i], contArgs[i], bindings, variance) == false) - return false; + return false; } - - return true; } - public static bool MatchType(Type implementation, Type contract) - { - return MatchType(implementation, contract, new Dictionary()); - } + return true; + } - public static bool MatchType(Type implementation, Type contract, IDictionary bindings, bool variance = true) + public static bool MatchType(Type implementation, Type contract) => + MatchType(implementation, contract, new Dictionary()); + + public static bool MatchType(Type implementation, Type contract, IDictionary bindings, + bool variance = true) + { + if (contract.IsGenericType) { - if (contract.IsGenericType) - { - // eg type is List or List - // if we have variance then List can match IList - // if we don't have variance it can't - must have exact type + // eg type is List or List + // if we have variance then List can match IList + // if we don't have variance it can't - must have exact type - // try to match implementation against contract - if (MatchGeneric(implementation, contract, bindings)) return true; + // try to match implementation against contract + if (MatchGeneric(implementation, contract, bindings)) + { + return true; + } - // if no variance, fail - if (variance == false) return false; + // if no variance, fail + if (variance == false) + { + return false; + } - // try to match an ancestor of implementation against contract - var t = implementation.BaseType; - while (t != null) + // try to match an ancestor of implementation against contract + Type t = implementation.BaseType; + while (t != null) + { + if (MatchGeneric(t, contract, bindings)) { - if (MatchGeneric(t, contract, bindings)) return true; - t = t.BaseType; + return true; } - // try to match an interface of implementation against contract - return implementation.GetInterfaces().Any(i => MatchGeneric(i, contract, bindings)); + t = t.BaseType; } - if (contract.IsGenericParameter) - { - // eg + // try to match an interface of implementation against contract + return implementation.GetInterfaces().Any(i => MatchGeneric(i, contract, bindings)); + } - if (bindings.ContainsKey(contract.Name)) - { - // already bound: ensure it's compatible - return bindings[contract.Name] == implementation; - } + if (contract.IsGenericParameter) + { + // eg - // not already bound: bind - bindings[contract.Name] = implementation; - return true; + if (bindings.ContainsKey(contract.Name)) + { + // already bound: ensure it's compatible + return bindings[contract.Name] == implementation; } - // not a generic type, not a generic parameter - // so normal class or interface - // about primitive types, value types, etc: - // http://stackoverflow.com/questions/1827425/how-to-check-programatically-if-a-type-is-a-struct-or-a-class - // if it's a primitive type... it needs to be == + // not already bound: bind + bindings[contract.Name] = implementation; + return true; + } - if (implementation == contract) return true; - if (contract.IsClass && implementation.IsClass && implementation.IsSubclassOf(contract)) return true; - if (contract.IsInterface && implementation.GetInterfaces().Contains(contract)) return true; + // not a generic type, not a generic parameter + // so normal class or interface + // about primitive types, value types, etc: + // http://stackoverflow.com/questions/1827425/how-to-check-programatically-if-a-type-is-a-struct-or-a-class + // if it's a primitive type... it needs to be == - return false; + if (implementation == contract) + { + return true; } - #endregion + if (contract.IsClass && implementation.IsClass && implementation.IsSubclassOf(contract)) + { + return true; + } + + if (contract.IsInterface && implementation.GetInterfaces().Contains(contract)) + { + return true; + } + + return false; } + + #endregion } diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index 6f4d81fc34bd..5e94674c382e 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Reflection; using System.Runtime.Serialization; using Microsoft.Extensions.Logging; @@ -10,458 +6,493 @@ using Umbraco.Cms.Core.Logging; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Composing +namespace Umbraco.Cms.Core.Composing; + +/// +/// Provides methods to find and instantiate types. +/// +/// +/// +/// This class should be used to get all types, the class should never be used +/// directly. +/// +/// In most cases this class is not used directly but through extension methods that retrieve specific types. +/// +public sealed class TypeLoader { + private readonly object _locko = new(); + private readonly ILogger _logger; + + private readonly Dictionary _types = new(); + + private IEnumerable? _assemblies; + + /// + /// Initializes a new instance of the class. + /// + [Obsolete("Please use an alternative constructor.")] + public TypeLoader( + ITypeFinder typeFinder, + IRuntimeHash runtimeHash, + IAppPolicyCache runtimeCache, + DirectoryInfo localTempPath, + ILogger logger, + IProfiler profiler, + IEnumerable? assembliesToScan = null) + : this(typeFinder, logger, assembliesToScan) + { + } + /// - /// Provides methods to find and instantiate types. + /// Initializes a new instance of the class. + /// + [Obsolete("Please use an alternative constructor.")] + public TypeLoader( + ITypeFinder typeFinder, + IRuntimeHash runtimeHash, + IAppPolicyCache runtimeCache, + DirectoryInfo localTempPath, + ILogger logger, + IProfiler profiler, + bool detectChanges, + IEnumerable? assembliesToScan = null) + : this(typeFinder, logger, assembliesToScan) + { + } + + public TypeLoader( + ITypeFinder typeFinder, + ILogger logger, + IEnumerable? assembliesToScan = null) + { + TypeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _assemblies = assembliesToScan; + } + + /// + /// Returns the underlying + /// + // ReSharper disable once MemberCanBePrivate.Global + public ITypeFinder TypeFinder { get; } + + /// + /// Gets or sets the set of assemblies to scan. /// /// - /// This class should be used to get all types, the class should never be used directly. - /// In most cases this class is not used directly but through extension methods that retrieve specific types. + /// + /// If not explicitly set, defaults to all assemblies except those that are know to not have any of the + /// types we might scan. Because we only scan for application types, this means we can safely exclude GAC + /// assemblies + /// for example. + /// + /// This is for unit tests. /// - public sealed class TypeLoader - { - private readonly ILogger _logger; + // internal for tests + [Obsolete("This will be removed in a future version.")] + public IEnumerable AssembliesToScan => _assemblies ??= TypeFinder.AssembliesToScan; - private readonly Dictionary _types = new (); - private readonly object _locko = new (); + /// + /// Gets the type lists. + /// + /// For unit tests. + // internal for tests + [Obsolete("This will be removed in a future version.")] + public IEnumerable TypeLists => _types.Values; - private IEnumerable? _assemblies; + /// + /// Sets a type list. + /// + /// For unit tests. + // internal for tests + [Obsolete("This will be removed in a future version.")] + public void AddTypeList(TypeList typeList) + { + Type tobject = typeof(object); // CompositeTypeTypeKey does not support null values + _types[new CompositeTypeTypeKey(typeList.BaseType ?? tobject, typeList.AttributeType ?? tobject)] = typeList; + } - /// - /// Initializes a new instance of the class. - /// - [Obsolete("Please use an alternative constructor.")] - public TypeLoader( - ITypeFinder typeFinder, - IRuntimeHash runtimeHash, - IAppPolicyCache runtimeCache, - DirectoryInfo localTempPath, - ILogger logger, - IProfiler profiler, - IEnumerable? assembliesToScan = null) - : this(typeFinder, logger, assembliesToScan) - { - } + #region Get Assembly Attributes - /// - /// Initializes a new instance of the class. - /// - [Obsolete("Please use an alternative constructor.")] - public TypeLoader( - ITypeFinder typeFinder, - IRuntimeHash runtimeHash, - IAppPolicyCache runtimeCache, - DirectoryInfo localTempPath, - ILogger logger, - IProfiler profiler, - bool detectChanges, - IEnumerable? assembliesToScan = null) - : this(typeFinder, logger, assembliesToScan) + /// + /// Gets the assembly attributes of the specified . + /// + /// The attribute types. + /// + /// The assembly attributes of the specified types. + /// + /// attributeTypes + public IEnumerable GetAssemblyAttributes(params Type[] attributeTypes) + { + if (attributeTypes == null) { + throw new ArgumentNullException(nameof(attributeTypes)); } - public TypeLoader( - ITypeFinder typeFinder, - ILogger logger, - IEnumerable? assembliesToScan = null) - { - TypeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _assemblies = assembliesToScan; - } + return AssembliesToScan.SelectMany(a => attributeTypes.SelectMany(at => a.GetCustomAttributes(at))).ToList(); + } - /// - /// Returns the underlying - /// - // ReSharper disable once MemberCanBePrivate.Global - public ITypeFinder TypeFinder { get; } + #endregion - /// - /// Gets or sets the set of assemblies to scan. - /// - /// - /// If not explicitly set, defaults to all assemblies except those that are know to not have any of the - /// types we might scan. Because we only scan for application types, this means we can safely exclude GAC assemblies - /// for example. - /// This is for unit tests. - /// - // internal for tests - [Obsolete("This will be removed in a future version.")] - public IEnumerable AssembliesToScan => _assemblies ??= TypeFinder.AssembliesToScan; + #region Cache - /// - /// Gets the type lists. - /// - /// For unit tests. - // internal for tests - [Obsolete("This will be removed in a future version.")] - public IEnumerable TypeLists => _types.Values; + // internal for tests + [Obsolete("This will be removed in a future version.")] + public Attempt> TryGetCached(Type baseType, Type attributeType) => + Attempt>.Fail(); - /// - /// Sets a type list. - /// - /// For unit tests. - // internal for tests - [Obsolete("This will be removed in a future version.")] - public void AddTypeList(TypeList typeList) - { - var tobject = typeof(object); // CompositeTypeTypeKey does not support null values - _types[new CompositeTypeTypeKey(typeList.BaseType ?? tobject, typeList.AttributeType ?? tobject)] = typeList; - } + // internal for tests + [Obsolete("This will be removed in a future version.")] + public Dictionary<(string, string), IEnumerable>? ReadCache() => null; - #region Cache + // internal for tests + [Obsolete("This will be removed in a future version.")] + public string? GetTypesListFilePath() => null; - // internal for tests - [Obsolete("This will be removed in a future version.")] - public Attempt> TryGetCached(Type baseType, Type attributeType) - { - return Attempt>.Fail(); - } + // internal for tests + [Obsolete("This will be removed in a future version.")] + public void WriteCache() + { + } - // internal for tests - [Obsolete("This will be removed in a future version.")] - public Dictionary<(string, string), IEnumerable>? ReadCache() => null; + /// + /// Clears cache. + /// + /// Generally only used for resetting cache, for example during the install process. + [Obsolete("This will be removed in a future version.")] + public void ClearTypesCache() + { + } - // internal for tests - [Obsolete("This will be removed in a future version.")] - public string? GetTypesListFilePath() => null; + #endregion - // internal for tests - [Obsolete("This will be removed in a future version.")] - public void WriteCache() - { - } + #region Get Types - /// - /// Clears cache. - /// - /// Generally only used for resetting cache, for example during the install process. - [Obsolete("This will be removed in a future version.")] - public void ClearTypesCache() + /// + /// Gets class types inheriting from or implementing the specified type + /// + /// The type to inherit from or implement. + /// Indicates whether to use cache for type resolution. + /// A set of assemblies for type resolution. + /// All class types inheriting from or implementing the specified type. + /// Caching is disabled when using specific assemblies. + public IEnumerable GetTypes(bool cache = true, IEnumerable? specificAssemblies = null) + { + if (_logger == null) { + throw new InvalidOperationException("Cannot get types from a test/blank type loader."); } - #endregion - - #region Get Assembly Attributes + // do not cache anything from specific assemblies + cache &= specificAssemblies == null; - /// - /// Gets the assembly attributes of the specified . - /// - /// The attribute types. - /// - /// The assembly attributes of the specified types. - /// - /// attributeTypes - public IEnumerable GetAssemblyAttributes(params Type[] attributeTypes) + // if not IDiscoverable, directly get types + if (!typeof(IDiscoverable).IsAssignableFrom(typeof(T))) { - if (attributeTypes == null) - throw new ArgumentNullException(nameof(attributeTypes)); + // warn + _logger.LogDebug( + "Running a full, " + (cache ? "" : "non-") + + "cached, scan for non-discoverable type {TypeName} (slow).", typeof(T).FullName); - return AssembliesToScan.SelectMany(a => attributeTypes.SelectMany(at => a.GetCustomAttributes(at))).ToList(); + return GetTypesInternal( + typeof(T), null, + () => TypeFinder.FindClassesOfType(specificAssemblies ?? AssembliesToScan), + "scanning assemblies", + cache); } - #endregion - - #region Get Types + // get IDiscoverable and always cache + IEnumerable discovered = GetTypesInternal( + typeof(IDiscoverable), null, + () => TypeFinder.FindClassesOfType(AssembliesToScan), + "scanning assemblies", + true); - /// - /// Gets class types inheriting from or implementing the specified type - /// - /// The type to inherit from or implement. - /// Indicates whether to use cache for type resolution. - /// A set of assemblies for type resolution. - /// All class types inheriting from or implementing the specified type. - /// Caching is disabled when using specific assemblies. - public IEnumerable GetTypes(bool cache = true, IEnumerable? specificAssemblies = null) + // warn + if (!cache) { - if (_logger == null) - { - throw new InvalidOperationException("Cannot get types from a test/blank type loader."); - } + _logger.LogDebug("Running a non-cached, filter for discoverable type {TypeName} (slowish).", + typeof(T).FullName); + } - // do not cache anything from specific assemblies - cache &= specificAssemblies == null; + // filter the cached discovered types (and maybe cache the result) + return GetTypesInternal( + typeof(T), null, + () => discovered + .Where(x => typeof(T).IsAssignableFrom(x)), + "filtering IDiscoverable", + cache); + } - // if not IDiscoverable, directly get types - if (!typeof(IDiscoverable).IsAssignableFrom(typeof(T))) - { - // warn - _logger.LogDebug("Running a full, " + (cache ? "" : "non-") + "cached, scan for non-discoverable type {TypeName} (slow).", typeof(T).FullName); - - return GetTypesInternal( - typeof(T), null, - () => TypeFinder.FindClassesOfType(specificAssemblies ?? AssembliesToScan), - "scanning assemblies", - cache); - } + /// + /// Gets class types inheriting from or implementing the specified type and marked with the specified attribute. + /// + /// The type to inherit from or implement. + /// The type of the attribute. + /// Indicates whether to use cache for type resolution. + /// A set of assemblies for type resolution. + /// All class types inheriting from or implementing the specified type and marked with the specified attribute. + /// Caching is disabled when using specific assemblies. + public IEnumerable GetTypesWithAttribute(bool cache = true, + IEnumerable? specificAssemblies = null) + where TAttribute : Attribute + { + if (_logger == null) + { + throw new InvalidOperationException("Cannot get types from a test/blank type loader."); + } - // get IDiscoverable and always cache - var discovered = GetTypesInternal( - typeof(IDiscoverable), null, - () => TypeFinder.FindClassesOfType(AssembliesToScan), - "scanning assemblies", - true); + // do not cache anything from specific assemblies + cache &= specificAssemblies == null; - // warn - if (!cache) - { - _logger.LogDebug("Running a non-cached, filter for discoverable type {TypeName} (slowish).", typeof(T).FullName); - } + // if not IDiscoverable, directly get types + if (!typeof(IDiscoverable).IsAssignableFrom(typeof(T))) + { + _logger.LogDebug( + "Running a full, " + (cache ? "" : "non-") + + "cached, scan for non-discoverable type {TypeName} / attribute {AttributeName} (slow).", + typeof(T).FullName, typeof(TAttribute).FullName); - // filter the cached discovered types (and maybe cache the result) return GetTypesInternal( - typeof(T), null, - () => discovered - .Where(x => typeof(T).IsAssignableFrom(x)), - "filtering IDiscoverable", + typeof(T), typeof(TAttribute), + () => TypeFinder.FindClassesOfTypeWithAttribute(specificAssemblies ?? AssembliesToScan), + "scanning assemblies", cache); } - /// - /// Gets class types inheriting from or implementing the specified type and marked with the specified attribute. - /// - /// The type to inherit from or implement. - /// The type of the attribute. - /// Indicates whether to use cache for type resolution. - /// A set of assemblies for type resolution. - /// All class types inheriting from or implementing the specified type and marked with the specified attribute. - /// Caching is disabled when using specific assemblies. - public IEnumerable GetTypesWithAttribute(bool cache = true, IEnumerable? specificAssemblies = null) - where TAttribute : Attribute + // get IDiscoverable and always cache + IEnumerable discovered = GetTypesInternal( + typeof(IDiscoverable), null, + () => TypeFinder.FindClassesOfType(AssembliesToScan), + "scanning assemblies", + true); + + // warn + if (!cache) { - if (_logger == null) - { - throw new InvalidOperationException("Cannot get types from a test/blank type loader."); - } + _logger.LogDebug( + "Running a non-cached, filter for discoverable type {TypeName} / attribute {AttributeName} (slowish).", + typeof(T).FullName, typeof(TAttribute).FullName); + } - // do not cache anything from specific assemblies - cache &= specificAssemblies == null; + // filter the cached discovered types (and maybe cache the result) + return GetTypesInternal( + typeof(T), typeof(TAttribute), + () => discovered + .Where(x => typeof(T).IsAssignableFrom(x)) + .Where(x => x.GetCustomAttributes(false).Any()), + "filtering IDiscoverable", + cache); + } - // if not IDiscoverable, directly get types - if (!typeof(IDiscoverable).IsAssignableFrom(typeof(T))) - { - _logger.LogDebug("Running a full, " + (cache ? "" : "non-") + "cached, scan for non-discoverable type {TypeName} / attribute {AttributeName} (slow).", typeof(T).FullName, typeof(TAttribute).FullName); + /// + /// Gets class types marked with the specified attribute. + /// + /// The type of the attribute. + /// Indicates whether to use cache for type resolution. + /// A set of assemblies for type resolution. + /// All class types marked with the specified attribute. + /// Caching is disabled when using specific assemblies. + public IEnumerable GetAttributedTypes(bool cache = true, + IEnumerable? specificAssemblies = null) + where TAttribute : Attribute + { + if (_logger == null) + { + throw new InvalidOperationException("Cannot get types from a test/blank type loader."); + } - return GetTypesInternal( - typeof(T), typeof(TAttribute), - () => TypeFinder.FindClassesOfTypeWithAttribute(specificAssemblies ?? AssembliesToScan), - "scanning assemblies", - cache); - } + // do not cache anything from specific assemblies + cache &= specificAssemblies == null; - // get IDiscoverable and always cache - var discovered = GetTypesInternal( - typeof(IDiscoverable), null, - () => TypeFinder.FindClassesOfType(AssembliesToScan), - "scanning assemblies", - true); + if (!cache) + { + _logger.LogDebug("Running a full, non-cached, scan for types / attribute {AttributeName} (slow).", + typeof(TAttribute).FullName); + } - // warn - if (!cache) - { - _logger.LogDebug("Running a non-cached, filter for discoverable type {TypeName} / attribute {AttributeName} (slowish).", typeof(T).FullName, typeof(TAttribute).FullName); - } + return GetTypesInternal( + typeof(object), typeof(TAttribute), + () => TypeFinder.FindClassesWithAttribute(specificAssemblies ?? AssembliesToScan), + "scanning assemblies", + cache); + } - // filter the cached discovered types (and maybe cache the result) - return GetTypesInternal( - typeof(T), typeof(TAttribute), - () => discovered - .Where(x => typeof(T).IsAssignableFrom(x)) - .Where(x => x.GetCustomAttributes(false).Any()), - "filtering IDiscoverable", - cache); - } + private IEnumerable GetTypesInternal( + Type baseType, + Type? attributeType, + Func> finder, + string action, + bool cache) + { + // using an upgradeable lock makes little sense here as only one thread can enter the upgradeable + // lock at a time, and we don't have non-upgradeable readers, and quite probably the type + // loader is mostly not going to be used in any kind of massively multi-threaded scenario - so, + // a plain lock is enough - /// - /// Gets class types marked with the specified attribute. - /// - /// The type of the attribute. - /// Indicates whether to use cache for type resolution. - /// A set of assemblies for type resolution. - /// All class types marked with the specified attribute. - /// Caching is disabled when using specific assemblies. - public IEnumerable GetAttributedTypes(bool cache = true, IEnumerable? specificAssemblies = null) - where TAttribute : Attribute + lock (_locko) { - if (_logger == null) - { - throw new InvalidOperationException("Cannot get types from a test/blank type loader."); - } + return GetTypesInternalLocked(baseType, attributeType, finder, action, cache); + } + } - // do not cache anything from specific assemblies - cache &= specificAssemblies == null; + private static string GetName(Type? baseType, Type? attributeType) + { + var s = attributeType == null ? string.Empty : "[" + attributeType + "]"; + s += baseType; + return s; + } - if (!cache) - { - _logger.LogDebug("Running a full, non-cached, scan for types / attribute {AttributeName} (slow).", typeof(TAttribute).FullName); - } + private IEnumerable GetTypesInternalLocked( + Type? baseType, + Type? attributeType, + Func> finder, + string action, + bool cache) + { + // check if the TypeList already exists, if so return it, if not we'll create it + Type tobject = typeof(object); // CompositeTypeTypeKey does not support null values + var listKey = new CompositeTypeTypeKey(baseType ?? tobject, attributeType ?? tobject); + TypeList? typeList = null; - return GetTypesInternal( - typeof(object), typeof(TAttribute), - () => TypeFinder.FindClassesWithAttribute(specificAssemblies ?? AssembliesToScan), - "scanning assemblies", - cache); + if (cache) + { + _types.TryGetValue(listKey, out typeList); // else null } - private IEnumerable GetTypesInternal( - Type baseType, - Type? attributeType, - Func> finder, - string action, - bool cache) + // if caching and found, return + if (typeList != null) { - // using an upgradeable lock makes little sense here as only one thread can enter the upgradeable - // lock at a time, and we don't have non-upgradeable readers, and quite probably the type - // loader is mostly not going to be used in any kind of massively multi-threaded scenario - so, - // a plain lock is enough - - lock (_locko) - { - return GetTypesInternalLocked(baseType, attributeType, finder, action, cache); - } + // need to put some logging here to try to figure out why this is happening: http://issues.umbraco.org/issue/U4-3505 + _logger.LogDebug("Getting {TypeName}: found a cached type list.", GetName(baseType, attributeType)); + return typeList.Types; } - private static string GetName(Type? baseType, Type? attributeType) + // else proceed, + typeList = new TypeList(baseType, attributeType); + + // either we had to scan, or we could not get the types from the cache file - scan now + _logger.LogDebug("Getting {TypeName}: " + action + ".", GetName(baseType, attributeType)); + + foreach (Type t in finder()) { - var s = attributeType == null ? string.Empty : ("[" + attributeType + "]"); - s += baseType; - return s; + typeList.Add(t); } - private IEnumerable GetTypesInternalLocked( - Type? baseType, - Type? attributeType, - Func> finder, - string action, - bool cache) + // if we are to cache the results, do so + if (cache) { - // check if the TypeList already exists, if so return it, if not we'll create it - var tobject = typeof(object); // CompositeTypeTypeKey does not support null values - var listKey = new CompositeTypeTypeKey(baseType ?? tobject, attributeType ?? tobject); - TypeList? typeList = null; - - if (cache) + var added = _types.ContainsKey(listKey) == false; + if (added) { - _types.TryGetValue(listKey, out typeList); // else null + _types[listKey] = typeList; } - // if caching and found, return - if (typeList != null) - { - // need to put some logging here to try to figure out why this is happening: http://issues.umbraco.org/issue/U4-3505 - _logger.LogDebug("Getting {TypeName}: found a cached type list.", GetName(baseType, attributeType)); - return typeList.Types; - } + _logger.LogDebug("Got {TypeName}, caching ({CacheType}).", GetName(baseType, attributeType), + added.ToString().ToLowerInvariant()); + } + else + { + _logger.LogDebug("Got {TypeName}.", GetName(baseType, attributeType)); + } - // else proceed, - typeList = new TypeList(baseType, attributeType); + return typeList.Types; + } - // either we had to scan, or we could not get the types from the cache file - scan now - _logger.LogDebug("Getting {TypeName}: " + action + ".", GetName(baseType, attributeType)); + #endregion - foreach (var t in finder()) - { - typeList.Add(t); - } - - // if we are to cache the results, do so - if (cache) - { - var added = _types.ContainsKey(listKey) == false; - if (added) - { - _types[listKey] = typeList; - } + #region Nested classes and stuff - _logger.LogDebug("Got {TypeName}, caching ({CacheType}).", GetName(baseType, attributeType), added.ToString().ToLowerInvariant()); - } - else - { - _logger.LogDebug("Got {TypeName}.", GetName(baseType, attributeType)); - } + /// + /// Represents a list of types obtained by looking for types inheriting/implementing a + /// specified type, and/or marked with a specified attribute type. + /// + public sealed class TypeList + { + private readonly HashSet _types = new(); - return typeList.Types; + public TypeList(Type? baseType, Type? attributeType) + { + BaseType = baseType; + AttributeType = attributeType; } - #endregion + public Type? BaseType { get; } + public Type? AttributeType { get; } - #region Nested classes and stuff + /// + /// Gets the types. + /// + public IEnumerable Types => _types; /// - /// Represents a list of types obtained by looking for types inheriting/implementing a - /// specified type, and/or marked with a specified attribute type. + /// Adds a type. /// - public sealed class TypeList + public void Add(Type type) { - private readonly HashSet _types = new HashSet(); - - public TypeList(Type? baseType, Type? attributeType) + if (BaseType?.IsAssignableFrom(type) == false) { - BaseType = baseType; - AttributeType = attributeType; + throw new ArgumentException("Base type " + BaseType + " is not assignable from type " + type + ".", + nameof(type)); } - public Type? BaseType { get; } - public Type? AttributeType { get; } + _types.Add(type); + } + } - /// - /// Adds a type. - /// - public void Add(Type type) - { - if (BaseType?.IsAssignableFrom(type) == false) - throw new ArgumentException("Base type " + BaseType + " is not assignable from type " + type + ".", nameof(type)); - _types.Add(type); - } + /// + /// Represents the error that occurs when a type was not found in the cache type list with the specified + /// TypeResolutionKind. + /// + /// + [Serializable] + internal class CachedTypeNotFoundInFileException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public CachedTypeNotFoundInFileException() + { + } - /// - /// Gets the types. - /// - public IEnumerable Types => _types; + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public CachedTypeNotFoundInFileException(string message) + : base(message) + { } /// - /// Represents the error that occurs when a type was not found in the cache type list with the specified TypeResolutionKind. + /// Initializes a new instance of the class. /// - /// - [Serializable] - internal class CachedTypeNotFoundInFileException : Exception + /// The error message that explains the reason for the exception. + /// + /// The exception that is the cause of the current exception, or a null reference ( + /// in Visual Basic) if no inner exception is specified. + /// + public CachedTypeNotFoundInFileException(string message, Exception innerException) + : base(message, innerException) { - /// - /// Initializes a new instance of the class. - /// - public CachedTypeNotFoundInFileException() - { } - - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public CachedTypeNotFoundInFileException(string message) - : base(message) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. - public CachedTypeNotFoundInFileException(string message, Exception innerException) - : base(message, innerException) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected CachedTypeNotFoundInFileException(SerializationInfo info, StreamingContext context) - : base(info, context) - { } } - #endregion + /// + /// Initializes a new instance of the class. + /// + /// + /// The that holds the serialized object + /// data about the exception being thrown. + /// + /// + /// The that contains contextual + /// information about the source or destination. + /// + protected CachedTypeNotFoundInFileException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } + + #endregion } diff --git a/src/Umbraco.Core/Composing/VaryingRuntimeHash.cs b/src/Umbraco.Core/Composing/VaryingRuntimeHash.cs index eec2adc63776..61145d76313a 100644 --- a/src/Umbraco.Core/Composing/VaryingRuntimeHash.cs +++ b/src/Umbraco.Core/Composing/VaryingRuntimeHash.cs @@ -1,19 +1,13 @@ -using System; +namespace Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Composing +/// +/// A runtime hash this is always different on each app startup +/// +public sealed class VaryingRuntimeHash : IRuntimeHash { - /// - /// A runtime hash this is always different on each app startup - /// - public sealed class VaryingRuntimeHash : IRuntimeHash - { - private readonly string _hash; + private readonly string _hash; - public VaryingRuntimeHash() - { - _hash = DateTime.Now.Ticks.ToString(); - } + public VaryingRuntimeHash() => _hash = DateTime.Now.Ticks.ToString(); - public string GetHashValue() => _hash; - } + public string GetHashValue() => _hash; } diff --git a/src/Umbraco.Core/Composing/WeightAttribute.cs b/src/Umbraco.Core/Composing/WeightAttribute.cs index 1225abca0c75..cb5c9c122c2c 100644 --- a/src/Umbraco.Core/Composing/WeightAttribute.cs +++ b/src/Umbraco.Core/Composing/WeightAttribute.cs @@ -1,25 +1,19 @@ -using System; +namespace Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Composing +/// +/// Specifies the weight of pretty much anything. +/// +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +public class WeightAttribute : Attribute { /// - /// Specifies the weight of pretty much anything. + /// Initializes a new instance of the class with a weight. /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public class WeightAttribute : Attribute - { - /// - /// Initializes a new instance of the class with a weight. - /// - /// - public WeightAttribute(int weight) - { - Weight = weight; - } + /// + public WeightAttribute(int weight) => Weight = weight; - /// - /// Gets the weight value. - /// - public int Weight { get; } - } + /// + /// Gets the weight value. + /// + public int Weight { get; } } diff --git a/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs index 1eafcce9e01c..3af7902a3656 100644 --- a/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs @@ -1,141 +1,155 @@ -using System; -using System.Collections.Generic; -using System.Linq; +namespace Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Composing +/// +/// Implements a weighted collection builder. +/// +/// The type of the builder. +/// The type of the collection. +/// The type of the items. +public abstract class + WeightedCollectionBuilderBase : CollectionBuilderBase + where TBuilder : WeightedCollectionBuilderBase + where TCollection : class, IBuilderCollection { + private readonly Dictionary _customWeights = new(); + protected abstract TBuilder This { get; } + + public virtual int DefaultWeight { get; set; } = 100; + /// - /// Implements a weighted collection builder. + /// Clears all types in the collection. /// - /// The type of the builder. - /// The type of the collection. - /// The type of the items. - public abstract class WeightedCollectionBuilderBase : CollectionBuilderBase - where TBuilder : WeightedCollectionBuilderBase - where TCollection : class, IBuilderCollection + /// The builder. + public TBuilder Clear() { - protected abstract TBuilder This { get; } - - private readonly Dictionary _customWeights = new Dictionary(); - - /// - /// Clears all types in the collection. - /// - /// The builder. - public TBuilder Clear() - { - Configure(types => types.Clear()); - return This; - } + Configure(types => types.Clear()); + return This; + } - /// - /// Adds a type to the collection. - /// - /// The type to add. - /// The builder. - public TBuilder Add() - where T : TItem + /// + /// Adds a type to the collection. + /// + /// The type to add. + /// The builder. + public TBuilder Add() + where T : TItem + { + Configure(types => { - Configure(types => + Type type = typeof(T); + if (types.Contains(type) == false) { - var type = typeof(T); - if (types.Contains(type) == false) types.Add(type); - }); - return This; - } + types.Add(type); + } + }); + return This; + } - /// - /// Adds a type to the collection. - /// - /// The type to add. - /// The builder. - public TBuilder Add(Type type) + /// + /// Adds a type to the collection. + /// + /// The type to add. + /// The builder. + public TBuilder Add(Type type) + { + Configure(types => { - Configure(types => + EnsureType(type, "register"); + if (types.Contains(type) == false) { - EnsureType(type, "register"); - if (types.Contains(type) == false) types.Add(type); - }); - return This; - } + types.Add(type); + } + }); + return This; + } - /// - /// Adds types to the collection. - /// - /// The types to add. - /// The builder. - public TBuilder Add(IEnumerable types) + /// + /// Adds types to the collection. + /// + /// The types to add. + /// The builder. + public TBuilder Add(IEnumerable types) + { + Configure(list => { - Configure(list => + foreach (Type type in types) { - foreach (var type in types) + // would be detected by CollectionBuilderBase when registering, anyways, but let's fail fast + EnsureType(type, "register"); + if (list.Contains(type) == false) { - // would be detected by CollectionBuilderBase when registering, anyways, but let's fail fast - EnsureType(type, "register"); - if (list.Contains(type) == false) list.Add(type); + list.Add(type); } - }); - return This; - } + } + }); + return This; + } - /// - /// Removes a type from the collection. - /// - /// The type to remove. - /// The builder. - public TBuilder Remove() - where T : TItem + /// + /// Removes a type from the collection. + /// + /// The type to remove. + /// The builder. + public TBuilder Remove() + where T : TItem + { + Configure(types => { - Configure(types => + Type type = typeof(T); + if (types.Contains(type)) { - var type = typeof(T); - if (types.Contains(type)) types.Remove(type); - }); - return This; - } + types.Remove(type); + } + }); + return This; + } - /// - /// Removes a type from the collection. - /// - /// The type to remove. - /// The builder. - public TBuilder Remove(Type type) + /// + /// Removes a type from the collection. + /// + /// The type to remove. + /// The builder. + public TBuilder Remove(Type type) + { + Configure(types => { - Configure(types => + EnsureType(type, "remove"); + if (types.Contains(type)) { - EnsureType(type, "remove"); - if (types.Contains(type)) types.Remove(type); - }); - return This; - } - - /// - /// Changes the default weight of an item - /// - /// The type of item - /// The new weight - /// - public TBuilder SetWeight(int weight) where T : TItem - { - _customWeights[typeof(T)] = weight; - return This; - } + types.Remove(type); + } + }); + return This; + } - protected override IEnumerable GetRegisteringTypes(IEnumerable types) - { - var list = types.ToList(); - list.Sort((t1, t2) => GetWeight(t1).CompareTo(GetWeight(t2))); - return list; - } + /// + /// Changes the default weight of an item + /// + /// The type of item + /// The new weight + /// + public TBuilder SetWeight(int weight) where T : TItem + { + _customWeights[typeof(T)] = weight; + return This; + } - public virtual int DefaultWeight { get; set; } = 100; + protected override IEnumerable GetRegisteringTypes(IEnumerable types) + { + var list = types.ToList(); + list.Sort((t1, t2) => GetWeight(t1).CompareTo(GetWeight(t2))); + return list; + } - protected virtual int GetWeight(Type type) + protected virtual int GetWeight(Type type) + { + if (_customWeights.ContainsKey(type)) { - if (_customWeights.ContainsKey(type)) - return _customWeights[type]; - var attr = type.GetCustomAttributes(typeof(WeightAttribute), false).OfType().SingleOrDefault(); - return attr?.Weight ?? DefaultWeight; + return _customWeights[type]; } + + WeightAttribute attr = type.GetCustomAttributes(typeof(WeightAttribute), false).OfType() + .SingleOrDefault(); + return attr?.Weight ?? DefaultWeight; } } diff --git a/src/Umbraco.Core/Configuration/ConfigConnectionString.cs b/src/Umbraco.Core/Configuration/ConfigConnectionString.cs index e69de29bb2d1..8b137891791f 100644 --- a/src/Umbraco.Core/Configuration/ConfigConnectionString.cs +++ b/src/Umbraco.Core/Configuration/ConfigConnectionString.cs @@ -0,0 +1 @@ + diff --git a/src/Umbraco.Core/Configuration/ConfigureConnectionStrings.cs b/src/Umbraco.Core/Configuration/ConfigureConnectionStrings.cs index 174a65ac1e9b..60a287c508bd 100644 --- a/src/Umbraco.Core/Configuration/ConfigureConnectionStrings.cs +++ b/src/Umbraco.Core/Configuration/ConfigureConnectionStrings.cs @@ -6,14 +6,14 @@ namespace Umbraco.Cms.Core.Configuration; /// -/// Configures ConnectionStrings. +/// Configures ConnectionStrings. /// public class ConfigureConnectionStrings : IConfigureNamedOptions { private readonly IConfiguration _configuration; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public ConfigureConnectionStrings(IConfiguration configuration) => _configuration = configuration; @@ -35,6 +35,7 @@ public void Configure(string name, ConnectionStrings options) options.Name = name; options.ConnectionString = _configuration.GetConnectionString(name); - options.ProviderName = _configuration.GetConnectionString($"{name}{ConnectionStrings.ProviderNamePostfix}") ?? ConnectionStrings.DefaultProviderName; + options.ProviderName = _configuration.GetConnectionString($"{name}{ConnectionStrings.ProviderNamePostfix}") ?? + ConnectionStrings.DefaultProviderName; } } diff --git a/src/Umbraco.Core/Configuration/ContentSettingsExtensions.cs b/src/Umbraco.Core/Configuration/ContentSettingsExtensions.cs index c8f5c4198844..7f89677000ed 100644 --- a/src/Umbraco.Core/Configuration/ContentSettingsExtensions.cs +++ b/src/Umbraco.Core/Configuration/ContentSettingsExtensions.cs @@ -1,32 +1,28 @@ -using System.Linq; -using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Configuration.Models; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class ContentSettingsExtensions { - public static class ContentSettingsExtensions - { - /// - /// Determines if file extension is allowed for upload based on (optional) white list and black list - /// held in settings. - /// Allow upload if extension is whitelisted OR if there is no whitelist and extension is NOT blacklisted. - /// - public static bool IsFileAllowedForUpload(this ContentSettings contentSettings, string extension) - { - return contentSettings.AllowedUploadFiles.Any(x => x.InvariantEquals(extension)) || - (contentSettings.AllowedUploadFiles.Any() == false && - contentSettings.DisallowedUploadFiles.Any(x => x.InvariantEquals(extension)) == false); - } + /// + /// Determines if file extension is allowed for upload based on (optional) white list and black list + /// held in settings. + /// Allow upload if extension is whitelisted OR if there is no whitelist and extension is NOT blacklisted. + /// + public static bool IsFileAllowedForUpload(this ContentSettings contentSettings, string extension) => + contentSettings.AllowedUploadFiles.Any(x => x.InvariantEquals(extension)) || + (contentSettings.AllowedUploadFiles.Any() == false && + contentSettings.DisallowedUploadFiles.Any(x => x.InvariantEquals(extension)) == false); - /// - /// Gets the auto-fill configuration for a specified property alias. - /// - /// - /// The property type alias. - /// The auto-fill configuration for the specified property alias, or null. - public static ImagingAutoFillUploadField? GetConfig(this ContentSettings contentSettings, string propertyTypeAlias) - { - var autoFillConfigs = contentSettings.Imaging.AutoFillImageProperties; - return autoFillConfigs?.FirstOrDefault(x => x.Alias == propertyTypeAlias); - } + /// + /// Gets the auto-fill configuration for a specified property alias. + /// + /// + /// The property type alias. + /// The auto-fill configuration for the specified property alias, or null. + public static ImagingAutoFillUploadField? GetConfig(this ContentSettings contentSettings, string propertyTypeAlias) + { + ImagingAutoFillUploadField[] autoFillConfigs = contentSettings.Imaging.AutoFillImageProperties; + return autoFillConfigs?.FirstOrDefault(x => x.Alias == propertyTypeAlias); } } diff --git a/src/Umbraco.Core/Configuration/EntryAssemblyMetadata.cs b/src/Umbraco.Core/Configuration/EntryAssemblyMetadata.cs index b6b9f067b9a6..096eac6fe013 100644 --- a/src/Umbraco.Core/Configuration/EntryAssemblyMetadata.cs +++ b/src/Umbraco.Core/Configuration/EntryAssemblyMetadata.cs @@ -1,24 +1,23 @@ using System.Reflection; -namespace Umbraco.Cms.Core.Configuration +namespace Umbraco.Cms.Core.Configuration; + +internal class EntryAssemblyMetadata : IEntryAssemblyMetadata { - internal class EntryAssemblyMetadata : IEntryAssemblyMetadata + public EntryAssemblyMetadata() { - public EntryAssemblyMetadata() - { - var entryAssembly = Assembly.GetEntryAssembly(); + var entryAssembly = Assembly.GetEntryAssembly(); - Name = entryAssembly - ?.GetName() - ?.Name ?? string.Empty; + Name = entryAssembly + ?.GetName() + ?.Name ?? string.Empty; - InformationalVersion = entryAssembly - ?.GetCustomAttribute() - ?.InformationalVersion ?? string.Empty; - } + InformationalVersion = entryAssembly + ?.GetCustomAttribute() + ?.InformationalVersion ?? string.Empty; + } - public string Name { get; } + public string Name { get; } - public string InformationalVersion { get; } - } + public string InformationalVersion { get; } } diff --git a/src/Umbraco.Core/Configuration/Extensions/HealthCheckSettingsExtensions.cs b/src/Umbraco.Core/Configuration/Extensions/HealthCheckSettingsExtensions.cs index 765525298187..aaf326dc6696 100644 --- a/src/Umbraco.Core/Configuration/Extensions/HealthCheckSettingsExtensions.cs +++ b/src/Umbraco.Core/Configuration/Extensions/HealthCheckSettingsExtensions.cs @@ -1,28 +1,25 @@ -using System; -using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class HealthCheckSettingsExtensions { - public static class HealthCheckSettingsExtensions + public static TimeSpan GetNotificationDelay(this HealthChecksSettings settings, ICronTabParser cronTabParser, + DateTime now, TimeSpan defaultDelay) { - public static TimeSpan GetNotificationDelay(this HealthChecksSettings settings, ICronTabParser cronTabParser, DateTime now, TimeSpan defaultDelay) + // If first run time not set, start with just small delay after application start. + var firstRunTime = settings.Notification.FirstRunTime; + if (string.IsNullOrEmpty(firstRunTime)) { - // If first run time not set, start with just small delay after application start. - var firstRunTime = settings.Notification.FirstRunTime; - if (string.IsNullOrEmpty(firstRunTime)) - { - return defaultDelay; - } - else - { - // Otherwise start at scheduled time according to cron expression, unless within the default delay period. - var firstRunOccurance = cronTabParser.GetNextOccurrence(firstRunTime, now); - var delay = firstRunOccurance - now; - return delay < defaultDelay - ? defaultDelay - : delay; - } + return defaultDelay; } + + // Otherwise start at scheduled time according to cron expression, unless within the default delay period. + DateTime firstRunOccurance = cronTabParser.GetNextOccurrence(firstRunTime, now); + TimeSpan delay = firstRunOccurance - now; + return delay < defaultDelay + ? defaultDelay + : delay; } } diff --git a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs index 2b22b0f28bb3..7f12ad43fdb3 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs @@ -1,60 +1,73 @@ -using System; -using Umbraco.Cms.Core; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class GlobalSettingsExtensions { - public static class GlobalSettingsExtensions + private static string? _mvcArea; + private static string? _backOfficePath; + + /// + /// Returns the absolute path for the Umbraco back office + /// + /// + /// + /// + public static string GetBackOfficePath(this GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) { - private static string? _mvcArea; - private static string? _backOfficePath; - - /// - /// Returns the absolute path for the Umbraco back office - /// - /// - /// - /// - public static string GetBackOfficePath(this GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) + if (_backOfficePath != null) { - if (_backOfficePath != null) return _backOfficePath; - _backOfficePath = hostingEnvironment.ToAbsolute(globalSettings.UmbracoPath); return _backOfficePath; } - /// - /// This returns the string of the MVC Area route. - /// - /// - /// This will return the MVC area that we will route all custom routes through like surface controllers, etc... - /// We will use the 'Path' (default ~/umbraco) to create it but since it cannot contain '/' and people may specify a path of ~/asdf/asdf/admin - /// we will convert the '/' to '-' and use that as the path. its a bit lame but will work. - /// - /// We also make sure that the virtual directory (SystemDirectories.Root) is stripped off first, otherwise we'd end up with something - /// like "MyVirtualDirectory-Umbraco" instead of just "Umbraco". - /// - public static string GetUmbracoMvcArea(this GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) - { - if (_mvcArea != null) return _mvcArea; - - _mvcArea = globalSettings.GetUmbracoMvcAreaNoCache(hostingEnvironment); + _backOfficePath = hostingEnvironment.ToAbsolute(globalSettings.UmbracoPath); + return _backOfficePath; + } + /// + /// This returns the string of the MVC Area route. + /// + /// + /// This will return the MVC area that we will route all custom routes through like surface controllers, etc... + /// We will use the 'Path' (default ~/umbraco) to create it but since it cannot contain '/' and people may specify a + /// path of ~/asdf/asdf/admin + /// we will convert the '/' to '-' and use that as the path. its a bit lame but will work. + /// We also make sure that the virtual directory (SystemDirectories.Root) is stripped off first, otherwise we'd end up + /// with something + /// like "MyVirtualDirectory-Umbraco" instead of just "Umbraco". + /// + public static string GetUmbracoMvcArea(this GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) + { + if (_mvcArea != null) + { return _mvcArea; } - internal static string GetUmbracoMvcAreaNoCache(this GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) - { - var path = string.IsNullOrEmpty(globalSettings.UmbracoPath) - ? string.Empty - : hostingEnvironment.ToAbsolute(globalSettings.UmbracoPath); + _mvcArea = globalSettings.GetUmbracoMvcAreaNoCache(hostingEnvironment); - if (path.IsNullOrWhiteSpace()) - throw new InvalidOperationException("Cannot create an MVC Area path without the umbracoPath specified"); + return _mvcArea; + } + + internal static string GetUmbracoMvcAreaNoCache(this GlobalSettings globalSettings, + IHostingEnvironment hostingEnvironment) + { + var path = string.IsNullOrEmpty(globalSettings.UmbracoPath) + ? string.Empty + : hostingEnvironment.ToAbsolute(globalSettings.UmbracoPath); + + if (path.IsNullOrWhiteSpace()) + { + throw new InvalidOperationException("Cannot create an MVC Area path without the umbracoPath specified"); + } - if (path.StartsWith(hostingEnvironment.ApplicationVirtualPath)) // beware of TrimStart, see U4-2518 - path = path.Substring(hostingEnvironment.ApplicationVirtualPath.Length); - return path.TrimStart(Constants.CharArrays.Tilde).TrimStart(Constants.CharArrays.ForwardSlash).Replace('/', '-').Trim().ToLower(); + if (path.StartsWith(hostingEnvironment.ApplicationVirtualPath)) // beware of TrimStart, see U4-2518 + { + path = path.Substring(hostingEnvironment.ApplicationVirtualPath.Length); } + + return path.TrimStart(Constants.CharArrays.Tilde).TrimStart(Constants.CharArrays.ForwardSlash).Replace('/', '-') + .Trim().ToLower(); } } diff --git a/src/Umbraco.Core/Configuration/Grid/GridConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridConfig.cs index 27d6820399aa..12cefa01342e 100644 --- a/src/Umbraco.Core/Configuration/Grid/GridConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/GridConfig.cs @@ -4,15 +4,14 @@ using Umbraco.Cms.Core.Manifest; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Configuration.Grid +namespace Umbraco.Cms.Core.Configuration.Grid; + +public class GridConfig : IGridConfig { - public class GridConfig : IGridConfig - { - public GridConfig(AppCaches appCaches, IManifestParser manifestParser, IJsonSerializer jsonSerializer, IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory) - { - EditorsConfig = new GridEditorsConfig(appCaches, hostingEnvironment, manifestParser, jsonSerializer, loggerFactory.CreateLogger()); - } + public GridConfig(AppCaches appCaches, IManifestParser manifestParser, IJsonSerializer jsonSerializer, + IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory) => EditorsConfig = + new GridEditorsConfig(appCaches, hostingEnvironment, manifestParser, jsonSerializer, + loggerFactory.CreateLogger()); - public IGridEditorsConfig EditorsConfig { get; } - } + public IGridEditorsConfig EditorsConfig { get; } } diff --git a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs index db5d669ce93a..de8c1e8de103 100644 --- a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; +using System.Reflection; using System.Text; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Cache; @@ -10,78 +8,85 @@ using Umbraco.Cms.Core.Serialization; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Configuration.Grid +namespace Umbraco.Cms.Core.Configuration.Grid; + +internal class GridEditorsConfig : IGridEditorsConfig { - internal class GridEditorsConfig : IGridEditorsConfig - { - private readonly AppCaches _appCaches; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly IManifestParser _manifestParser; + private readonly AppCaches _appCaches; + private readonly IHostingEnvironment _hostingEnvironment; - private readonly IJsonSerializer _jsonSerializer; - private readonly ILogger _logger; + private readonly IJsonSerializer _jsonSerializer; + private readonly ILogger _logger; + private readonly IManifestParser _manifestParser; - public GridEditorsConfig(AppCaches appCaches, IHostingEnvironment hostingEnvironment, IManifestParser manifestParser,IJsonSerializer jsonSerializer, ILogger logger) - { - _appCaches = appCaches; - _hostingEnvironment = hostingEnvironment; - _manifestParser = manifestParser; - _jsonSerializer = jsonSerializer; - _logger = logger; - } + public GridEditorsConfig(AppCaches appCaches, IHostingEnvironment hostingEnvironment, + IManifestParser manifestParser, IJsonSerializer jsonSerializer, ILogger logger) + { + _appCaches = appCaches; + _hostingEnvironment = hostingEnvironment; + _manifestParser = manifestParser; + _jsonSerializer = jsonSerializer; + _logger = logger; + } - public IEnumerable Editors + public IEnumerable Editors + { + get { - get + List GetResult() { - List GetResult() + var configFolder = + new DirectoryInfo(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Config)); + var editors = new List(); + var gridConfig = Path.Combine(configFolder.FullName, "grid.editors.config.js"); + if (File.Exists(gridConfig)) { - var configFolder = new DirectoryInfo(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Config)); - var editors = new List(); - var gridConfig = Path.Combine(configFolder.FullName, "grid.editors.config.js"); - if (File.Exists(gridConfig)) - { - var sourceString = File.ReadAllText(gridConfig); + var sourceString = File.ReadAllText(gridConfig); - try - { - editors.AddRange(_jsonSerializer.Deserialize>(sourceString)!); - } - catch (Exception ex) - { - _logger.LogError(ex, "Could not parse the contents of grid.editors.config.js into a JSON array '{Json}", sourceString); - } + try + { + editors.AddRange(_jsonSerializer.Deserialize>(sourceString)!); } - else// Read default from embedded file + catch (Exception ex) { - var assembly = GetType().Assembly; - var resourceStream = assembly.GetManifestResourceStream( - "Umbraco.Cms.Core.EmbeddedResources.Grid.grid.editors.config.js"); - - if (resourceStream is not null) - { - using var reader = new StreamReader(resourceStream, Encoding.UTF8); - var sourceString = reader.ReadToEnd(); - editors.AddRange(_jsonSerializer.Deserialize>(sourceString)!); - } + _logger.LogError(ex, + "Could not parse the contents of grid.editors.config.js into a JSON array '{Json}", + sourceString); } + } + else // Read default from embedded file + { + Assembly assembly = GetType().Assembly; + Stream resourceStream = assembly.GetManifestResourceStream( + "Umbraco.Cms.Core.EmbeddedResources.Grid.grid.editors.config.js"); - // add manifest editors, skip duplicates - foreach (var gridEditor in _manifestParser.CombinedManifest.GridEditors) + if (resourceStream is not null) { - if (editors.Contains(gridEditor) == false) editors.Add(gridEditor); + using var reader = new StreamReader(resourceStream, Encoding.UTF8); + var sourceString = reader.ReadToEnd(); + editors.AddRange(_jsonSerializer.Deserialize>(sourceString)!); } - - return editors; } - //cache the result if debugging is disabled - var result = _hostingEnvironment.IsDebugMode - ? GetResult() - : _appCaches.RuntimeCache.GetCacheItem>(typeof(GridEditorsConfig) + ".Editors",GetResult, TimeSpan.FromMinutes(10)); + // add manifest editors, skip duplicates + foreach (GridEditor gridEditor in _manifestParser.CombinedManifest.GridEditors) + { + if (editors.Contains(gridEditor) == false) + { + editors.Add(gridEditor); + } + } - return result!; + return editors; } + + //cache the result if debugging is disabled + List result = _hostingEnvironment.IsDebugMode + ? GetResult() + : _appCaches.RuntimeCache.GetCacheItem(typeof(GridEditorsConfig) + ".Editors", GetResult, + TimeSpan.FromMinutes(10)); + + return result!; } } } diff --git a/src/Umbraco.Core/Configuration/Grid/IGridConfig.cs b/src/Umbraco.Core/Configuration/Grid/IGridConfig.cs index d009eddd25d3..cbbddcacf9f7 100644 --- a/src/Umbraco.Core/Configuration/Grid/IGridConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/IGridConfig.cs @@ -1,9 +1,6 @@ -namespace Umbraco.Cms.Core.Configuration.Grid -{ - public interface IGridConfig - { - - IGridEditorsConfig EditorsConfig { get; } +namespace Umbraco.Cms.Core.Configuration.Grid; - } +public interface IGridConfig +{ + IGridEditorsConfig EditorsConfig { get; } } diff --git a/src/Umbraco.Core/Configuration/Grid/IGridEditorConfig.cs b/src/Umbraco.Core/Configuration/Grid/IGridEditorConfig.cs index bfd3f17cbfeb..6b411a459087 100644 --- a/src/Umbraco.Core/Configuration/Grid/IGridEditorConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/IGridEditorConfig.cs @@ -1,15 +1,12 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Configuration.Grid; -namespace Umbraco.Cms.Core.Configuration.Grid +public interface IGridEditorConfig { - public interface IGridEditorConfig - { - string? Name { get; } - string? NameTemplate { get; } - string Alias { get; } - string? View { get; } - string? Render { get; } - string? Icon { get; } - IDictionary Config { get; } - } + string? Name { get; } + string? NameTemplate { get; } + string Alias { get; } + string? View { get; } + string? Render { get; } + string? Icon { get; } + IDictionary Config { get; } } diff --git a/src/Umbraco.Core/Configuration/Grid/IGridEditorsConfig.cs b/src/Umbraco.Core/Configuration/Grid/IGridEditorsConfig.cs index a49ae41d6ce3..f8855dd76588 100644 --- a/src/Umbraco.Core/Configuration/Grid/IGridEditorsConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/IGridEditorsConfig.cs @@ -1,9 +1,6 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Configuration.Grid; -namespace Umbraco.Cms.Core.Configuration.Grid +public interface IGridEditorsConfig { - public interface IGridEditorsConfig - { - IEnumerable Editors { get; } - } + IEnumerable Editors { get; } } diff --git a/src/Umbraco.Core/Configuration/IConfigManipulator.cs b/src/Umbraco.Core/Configuration/IConfigManipulator.cs index c99f90e5c91b..24ce06960f0b 100644 --- a/src/Umbraco.Core/Configuration/IConfigManipulator.cs +++ b/src/Umbraco.Core/Configuration/IConfigManipulator.cs @@ -1,11 +1,10 @@ -namespace Umbraco.Cms.Core.Configuration +namespace Umbraco.Cms.Core.Configuration; + +public interface IConfigManipulator { - public interface IConfigManipulator - { - void RemoveConnectionString(); - void SaveConnectionString(string connectionString, string? providerName); - void SaveConfigValue(string itemPath, object value); - void SaveDisableRedirectUrlTracking(bool disable); - void SetGlobalId(string id); - } + void RemoveConnectionString(); + void SaveConnectionString(string connectionString, string? providerName); + void SaveConfigValue(string itemPath, object value); + void SaveDisableRedirectUrlTracking(bool disable); + void SetGlobalId(string id); } diff --git a/src/Umbraco.Core/Configuration/ICronTabParser.cs b/src/Umbraco.Core/Configuration/ICronTabParser.cs index 565d9fa47b48..72b3ab0c07a1 100644 --- a/src/Umbraco.Core/Configuration/ICronTabParser.cs +++ b/src/Umbraco.Core/Configuration/ICronTabParser.cs @@ -1,28 +1,25 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; +namespace Umbraco.Cms.Core.Configuration; -namespace Umbraco.Cms.Core.Configuration +/// +/// Defines the contract for that allows the parsing of chrontab expressions. +/// +public interface ICronTabParser { /// - /// Defines the contract for that allows the parsing of chrontab expressions. + /// Returns a value indicating whether a given chrontab expression is valid. /// - public interface ICronTabParser - { - /// - /// Returns a value indicating whether a given chrontab expression is valid. - /// - /// The chrontab expression to parse. - /// The result. - bool IsValidCronTab(string cronTab); + /// The chrontab expression to parse. + /// The result. + bool IsValidCronTab(string cronTab); - /// - /// Returns the next occurence for the given chrontab expression from the given time. - /// - /// The chrontab expression to parse. - /// The date and time to start from. - /// The representing the next occurence. - DateTime GetNextOccurrence(string cronTab, DateTime time); - } + /// + /// Returns the next occurence for the given chrontab expression from the given time. + /// + /// The chrontab expression to parse. + /// The date and time to start from. + /// The representing the next occurence. + DateTime GetNextOccurrence(string cronTab, DateTime time); } diff --git a/src/Umbraco.Core/Configuration/IEntryAssemblyMetadata.cs b/src/Umbraco.Core/Configuration/IEntryAssemblyMetadata.cs index 09ea5058df35..857b62bb2611 100644 --- a/src/Umbraco.Core/Configuration/IEntryAssemblyMetadata.cs +++ b/src/Umbraco.Core/Configuration/IEntryAssemblyMetadata.cs @@ -1,18 +1,17 @@ -namespace Umbraco.Cms.Core.Configuration +namespace Umbraco.Cms.Core.Configuration; + +/// +/// Provides metadata about the entry assembly. +/// +public interface IEntryAssemblyMetadata { /// - /// Provides metadata about the entry assembly. + /// Gets the Name of entry assembly. /// - public interface IEntryAssemblyMetadata - { - /// - /// Gets the Name of entry assembly. - /// - public string Name { get; } + public string Name { get; } - /// - /// Gets the InformationalVersion string for entry assembly. - /// - public string InformationalVersion { get; } - } + /// + /// Gets the InformationalVersion string for entry assembly. + /// + public string InformationalVersion { get; } } diff --git a/src/Umbraco.Core/Configuration/IMemberPasswordConfiguration.cs b/src/Umbraco.Core/Configuration/IMemberPasswordConfiguration.cs index 7bd8ab9ef2a8..2cedb3d569ef 100644 --- a/src/Umbraco.Core/Configuration/IMemberPasswordConfiguration.cs +++ b/src/Umbraco.Core/Configuration/IMemberPasswordConfiguration.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.Configuration +namespace Umbraco.Cms.Core.Configuration; + +/// +/// The password configuration for members +/// +public interface IMemberPasswordConfiguration : IPasswordConfiguration { - /// - /// The password configuration for members - /// - public interface IMemberPasswordConfiguration : IPasswordConfiguration - { - } } diff --git a/src/Umbraco.Core/Configuration/IPasswordConfiguration.cs b/src/Umbraco.Core/Configuration/IPasswordConfiguration.cs index acfe81ece9fa..e0e934f550dc 100644 --- a/src/Umbraco.Core/Configuration/IPasswordConfiguration.cs +++ b/src/Umbraco.Core/Configuration/IPasswordConfiguration.cs @@ -1,50 +1,48 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Configuration +namespace Umbraco.Cms.Core.Configuration; + +/// +/// Password configuration +/// +public interface IPasswordConfiguration { + /// + /// Gets a value for the minimum required length for the password. + /// + int RequiredLength { get; } + + /// + /// Gets a value indicating whether at least one non-letter or digit is required for the password. + /// + bool RequireNonLetterOrDigit { get; } + + /// + /// Gets a value indicating whether at least one digit is required for the password. + /// + bool RequireDigit { get; } + + /// + /// Gets a value indicating whether at least one lower-case character is required for the password. + /// + bool RequireLowercase { get; } + + /// + /// Gets a value indicating whether at least one upper-case character is required for the password. + /// + bool RequireUppercase { get; } + + /// + /// Gets a value for the password hash algorithm type. + /// + string HashAlgorithmType { get; } /// - /// Password configuration + /// Gets a value for the maximum failed access attempts before lockout. /// - public interface IPasswordConfiguration - { - /// - /// Gets a value for the minimum required length for the password. - /// - int RequiredLength { get; } - - /// - /// Gets a value indicating whether at least one non-letter or digit is required for the password. - /// - bool RequireNonLetterOrDigit { get; } - - /// - /// Gets a value indicating whether at least one digit is required for the password. - /// - bool RequireDigit { get; } - - /// - /// Gets a value indicating whether at least one lower-case character is required for the password. - /// - bool RequireLowercase { get; } - - /// - /// Gets a value indicating whether at least one upper-case character is required for the password. - /// - bool RequireUppercase { get; } - - /// - /// Gets a value for the password hash algorithm type. - /// - string HashAlgorithmType { get; } - - /// - /// Gets a value for the maximum failed access attempts before lockout. - /// - /// - /// TODO: This doesn't really belong here - /// - int MaxFailedAccessAttemptsBeforeLockout { get; } - } + /// + /// TODO: This doesn't really belong here + /// + int MaxFailedAccessAttemptsBeforeLockout { get; } } diff --git a/src/Umbraco.Core/Configuration/ITypeFinderSettings.cs b/src/Umbraco.Core/Configuration/ITypeFinderSettings.cs index 9dc5805423af..c6ff218debec 100644 --- a/src/Umbraco.Core/Configuration/ITypeFinderSettings.cs +++ b/src/Umbraco.Core/Configuration/ITypeFinderSettings.cs @@ -1,7 +1,6 @@ -namespace Umbraco.Cms.Core.Configuration +namespace Umbraco.Cms.Core.Configuration; + +public interface ITypeFinderSettings { - public interface ITypeFinderSettings - { - string AssembliesAcceptingLoadExceptions { get; } - } + string AssembliesAcceptingLoadExceptions { get; } } diff --git a/src/Umbraco.Core/Configuration/IUmbracoConfigurationSection.cs b/src/Umbraco.Core/Configuration/IUmbracoConfigurationSection.cs index 4a1e65f13f6f..32f3acefd943 100644 --- a/src/Umbraco.Core/Configuration/IUmbracoConfigurationSection.cs +++ b/src/Umbraco.Core/Configuration/IUmbracoConfigurationSection.cs @@ -1,10 +1,8 @@ -namespace Umbraco.Cms.Core.Configuration -{ - /// - /// Represents an Umbraco configuration section which can be used to pass to UmbracoConfiguration.For{T} - /// - public interface IUmbracoConfigurationSection - { +namespace Umbraco.Cms.Core.Configuration; - } +/// +/// Represents an Umbraco configuration section which can be used to pass to UmbracoConfiguration.For{T} +/// +public interface IUmbracoConfigurationSection +{ } diff --git a/src/Umbraco.Core/Configuration/IUmbracoVersion.cs b/src/Umbraco.Core/Configuration/IUmbracoVersion.cs index 2758d9dabfb7..3672f28dae29 100644 --- a/src/Umbraco.Core/Configuration/IUmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/IUmbracoVersion.cs @@ -1,46 +1,45 @@ -using System; using Umbraco.Cms.Core.Semver; -namespace Umbraco.Cms.Core.Configuration -{ - public interface IUmbracoVersion - { - /// - /// Gets the non-semantic version of the Umbraco code. - /// - Version Version { get; } +namespace Umbraco.Cms.Core.Configuration; - /// - /// Gets the semantic version comments of the Umbraco code. - /// - string Comment { get; } +public interface IUmbracoVersion +{ + /// + /// Gets the non-semantic version of the Umbraco code. + /// + Version Version { get; } - /// - /// Gets the assembly version of the Umbraco code. - /// - /// - /// The assembly version is the value of the . - /// Is the one that the CLR checks for compatibility. Therefore, it changes only on - /// hard-breaking changes (for instance, on new major versions). - /// - Version? AssemblyVersion { get; } + /// + /// Gets the semantic version comments of the Umbraco code. + /// + string Comment { get; } - /// - /// Gets the assembly file version of the Umbraco code. - /// - /// - /// The assembly version is the value of the . - /// - Version? AssemblyFileVersion { get; } + /// + /// Gets the assembly version of the Umbraco code. + /// + /// + /// The assembly version is the value of the . + /// + /// Is the one that the CLR checks for compatibility. Therefore, it changes only on + /// hard-breaking changes (for instance, on new major versions). + /// + /// + Version? AssemblyVersion { get; } - /// - /// Gets the semantic version of the Umbraco code. - /// - /// - /// The semantic version is the value of the . - /// It is the full version of Umbraco, including comments. - /// - SemVersion SemanticVersion { get; } + /// + /// Gets the assembly file version of the Umbraco code. + /// + /// + /// The assembly version is the value of the . + /// + Version? AssemblyFileVersion { get; } - } + /// + /// Gets the semantic version of the Umbraco code. + /// + /// + /// The semantic version is the value of the . + /// It is the full version of Umbraco, including comments. + /// + SemVersion SemanticVersion { get; } } diff --git a/src/Umbraco.Core/Configuration/IUserPasswordConfiguration.cs b/src/Umbraco.Core/Configuration/IUserPasswordConfiguration.cs index db27103a67a5..476d81a66406 100644 --- a/src/Umbraco.Core/Configuration/IUserPasswordConfiguration.cs +++ b/src/Umbraco.Core/Configuration/IUserPasswordConfiguration.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.Configuration +namespace Umbraco.Cms.Core.Configuration; + +/// +/// The password configuration for back office users +/// +public interface IUserPasswordConfiguration : IPasswordConfiguration { - /// - /// The password configuration for back office users - /// - public interface IUserPasswordConfiguration : IPasswordConfiguration - { - } } diff --git a/src/Umbraco.Core/Configuration/LocalTempStorage.cs b/src/Umbraco.Core/Configuration/LocalTempStorage.cs index 696ec7900e9e..8f9deba96e86 100644 --- a/src/Umbraco.Core/Configuration/LocalTempStorage.cs +++ b/src/Umbraco.Core/Configuration/LocalTempStorage.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.Configuration +namespace Umbraco.Cms.Core.Configuration; + +public enum LocalTempStorage { - public enum LocalTempStorage - { - Unknown = 0, - Default, - EnvironmentTemp - } + Unknown = 0, + Default, + EnvironmentTemp } diff --git a/src/Umbraco.Core/Configuration/MemberPasswordConfiguration.cs b/src/Umbraco.Core/Configuration/MemberPasswordConfiguration.cs index c7ce20454f1c..b800dfc66df4 100644 --- a/src/Umbraco.Core/Configuration/MemberPasswordConfiguration.cs +++ b/src/Umbraco.Core/Configuration/MemberPasswordConfiguration.cs @@ -1,13 +1,12 @@ -namespace Umbraco.Cms.Core.Configuration +namespace Umbraco.Cms.Core.Configuration; + +/// +/// The password configuration for members +/// +public class MemberPasswordConfiguration : PasswordConfiguration, IMemberPasswordConfiguration { - /// - /// The password configuration for members - /// - public class MemberPasswordConfiguration : PasswordConfiguration, IMemberPasswordConfiguration + public MemberPasswordConfiguration(IMemberPasswordConfiguration configSettings) + : base(configSettings) { - public MemberPasswordConfiguration(IMemberPasswordConfiguration configSettings) - : base(configSettings) - { - } } } diff --git a/src/Umbraco.Core/Configuration/Models/ActiveDirectorySettings.cs b/src/Umbraco.Core/Configuration/Models/ActiveDirectorySettings.cs index 5e4f371c7342..a2eabf73e530 100644 --- a/src/Umbraco.Core/Configuration/Models/ActiveDirectorySettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ActiveDirectorySettings.cs @@ -1,17 +1,16 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for active directory settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigActiveDirectory)] +public class ActiveDirectorySettings { /// - /// Typed configuration options for active directory settings. + /// Gets or sets a value for the Active Directory domain. /// - [UmbracoOptions(Constants.Configuration.ConfigActiveDirectory)] - public class ActiveDirectorySettings - { - /// - /// Gets or sets a value for the Active Directory domain. - /// - public string? Domain { get; set; } - } + public string? Domain { get; set; } } diff --git a/src/Umbraco.Core/Configuration/Models/Attributes/UmbracoOptionsAttribute.cs b/src/Umbraco.Core/Configuration/Models/Attributes/UmbracoOptionsAttribute.cs index 211b6b3d8311..f470cfed2a22 100644 --- a/src/Umbraco.Core/Configuration/Models/Attributes/UmbracoOptionsAttribute.cs +++ b/src/Umbraco.Core/Configuration/Models/Attributes/UmbracoOptionsAttribute.cs @@ -1,16 +1,10 @@ -using System; +namespace Umbraco.Cms.Core.Configuration.Models; -namespace Umbraco.Cms.Core.Configuration.Models +[AttributeUsage(AttributeTargets.Class)] +public class UmbracoOptionsAttribute : Attribute { - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class UmbracoOptionsAttribute : Attribute - { - public string ConfigurationKey { get; } - public bool BindNonPublicProperties { get; set; } + public UmbracoOptionsAttribute(string configurationKey) => ConfigurationKey = configurationKey; - public UmbracoOptionsAttribute(string configurationKey) - { - ConfigurationKey = configurationKey; - } - } + public string ConfigurationKey { get; } + public bool BindNonPublicProperties { get; set; } } diff --git a/src/Umbraco.Core/Configuration/Models/BasicAuthSettings.cs b/src/Umbraco.Core/Configuration/Models/BasicAuthSettings.cs index 054619d84353..591bbcef2900 100644 --- a/src/Umbraco.Core/Configuration/Models/BasicAuthSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/BasicAuthSettings.cs @@ -1,27 +1,24 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using System.ComponentModel; -using System.Net; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for basic authentication settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigBasicAuth)] +public class BasicAuthSettings { + private const bool StaticEnabled = false; + /// - /// Typed configuration options for basic authentication settings. + /// Gets or sets a value indicating whether to keep the user logged in. /// - [UmbracoOptions(Constants.Configuration.ConfigBasicAuth)] - public class BasicAuthSettings - { - private const bool StaticEnabled = false; - - /// - /// Gets or sets a value indicating whether to keep the user logged in. - /// - [DefaultValue(StaticEnabled)] - public bool Enabled { get; set; } = StaticEnabled; + [DefaultValue(StaticEnabled)] + public bool Enabled { get; set; } = StaticEnabled; - public string[] AllowedIPs { get; set; } = Array.Empty(); - } + public string[] AllowedIPs { get; set; } = Array.Empty(); } diff --git a/src/Umbraco.Core/Configuration/Models/CharItem.cs b/src/Umbraco.Core/Configuration/Models/CharItem.cs index a74b0c0a8ba6..625033a82acb 100644 --- a/src/Umbraco.Core/Configuration/Models/CharItem.cs +++ b/src/Umbraco.Core/Configuration/Models/CharItem.cs @@ -1,17 +1,16 @@ using Umbraco.Cms.Core.Configuration.UmbracoSettings; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +public class CharItem : IChar { - public class CharItem : IChar - { - /// - /// The character to replace - /// - public string Char { get; set; } = null!; + /// + /// The character to replace + /// + public string Char { get; set; } = null!; - /// - /// The replacement character - /// - public string Replacement { get; set; } = null!; - } + /// + /// The replacement character + /// + public string Replacement { get; set; } = null!; } diff --git a/src/Umbraco.Core/Configuration/Models/ConnectionStrings.cs b/src/Umbraco.Core/Configuration/Models/ConnectionStrings.cs index 56fda7bfebf6..1ef7808975b3 100644 --- a/src/Umbraco.Core/Configuration/Models/ConnectionStrings.cs +++ b/src/Umbraco.Core/Configuration/Models/ConnectionStrings.cs @@ -5,23 +5,23 @@ namespace Umbraco.Cms.Core.Configuration.Models; [UmbracoOptions("ConnectionStrings")] public class ConnectionStrings { - private string? _connectionString; - /// - /// The default provider name when not present in configuration. + /// The default provider name when not present in configuration. /// public const string DefaultProviderName = "Microsoft.Data.SqlClient"; /// - /// The DataDirectory placeholder. + /// The DataDirectory placeholder. /// public const string DataDirectoryPlaceholder = "|DataDirectory|"; /// - /// The postfix used to identify a connection strings provider setting. + /// The postfix used to identify a connection strings provider setting. /// public const string ProviderNamePostfix = "_ProviderName"; + private string? _connectionString; + public string? Name { get; set; } public string? ConnectionString diff --git a/src/Umbraco.Core/Configuration/Models/ContentDashboardSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentDashboardSettings.cs index 19d636ed347e..74376a3ed2c4 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentDashboardSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentDashboardSettings.cs @@ -1,36 +1,35 @@ using System.ComponentModel; using Umbraco.Cms.Core.Configuration.Models; -namespace Umbraco.Cms.Core.Configuration +namespace Umbraco.Cms.Core.Configuration; + +/// +/// Typed configuration options for content dashboard settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigContentDashboard)] +public class ContentDashboardSettings { + private const string DefaultContentDashboardPath = "cms"; + /// - /// Typed configuration options for content dashboard settings. + /// Gets a value indicating whether the content dashboard should be available to all users. /// - [UmbracoOptions(Constants.Configuration.ConfigContentDashboard)] - public class ContentDashboardSettings - { - private const string DefaultContentDashboardPath = "cms"; + /// + /// true if the dashboard is visible for all user groups; otherwise, false + /// and the default access rules for that dashboard will be in use. + /// + public bool AllowContentDashboardAccessToAllUsers { get; set; } = true; - /// - /// Gets a value indicating whether the content dashboard should be available to all users. - /// - /// - /// true if the dashboard is visible for all user groups; otherwise, false - /// and the default access rules for that dashboard will be in use. - /// - public bool AllowContentDashboardAccessToAllUsers { get; set; } = true; - - /// - /// Gets the path to use when constructing the URL for retrieving data for the content dashboard. - /// - /// The URL path. - [DefaultValue(DefaultContentDashboardPath)] - public string ContentDashboardPath { get; set; } = DefaultContentDashboardPath; + /// + /// Gets the path to use when constructing the URL for retrieving data for the content dashboard. + /// + /// The URL path. + [DefaultValue(DefaultContentDashboardPath)] + public string ContentDashboardPath { get; set; } = DefaultContentDashboardPath; - /// - /// Gets the allowed addresses to retrieve data for the content dashboard. - /// - /// The URLs. - public string[]? ContentDashboardUrlAllowlist { get; set; } - } + /// + /// Gets the allowed addresses to retrieve data for the content dashboard. + /// + /// The URLs. + public string[]? ContentDashboardUrlAllowlist { get; set; } } diff --git a/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs b/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs index 6a6d3a8e61ec..415240e0176a 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs @@ -1,55 +1,53 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using System.ComponentModel.DataAnnotations; using Umbraco.Cms.Core.Configuration.Models.Validation; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration for a content error page. +/// +public class ContentErrorPage : ValidatableEntryBase { /// - /// Typed configuration for a content error page. + /// Gets or sets a value for the content Id. + /// + public int ContentId { get; set; } + + /// + /// Gets or sets a value for the content key. + /// + public Guid ContentKey { get; set; } + + /// + /// Gets or sets a value for the content XPath. + /// + public string? ContentXPath { get; set; } + + /// + /// Gets a value indicating whether the field is populated. /// - public class ContentErrorPage : ValidatableEntryBase - { - /// - /// Gets or sets a value for the content Id. - /// - public int ContentId { get; set; } - - /// - /// Gets or sets a value for the content key. - /// - public Guid ContentKey { get; set; } - - /// - /// Gets or sets a value for the content XPath. - /// - public string? ContentXPath { get; set; } - - /// - /// Gets a value indicating whether the field is populated. - /// - public bool HasContentId => ContentId != 0; - - /// - /// Gets a value indicating whether the field is populated. - /// - public bool HasContentKey => ContentKey != Guid.Empty; - - /// - /// Gets a value indicating whether the field is populated. - /// - public bool HasContentXPath => !string.IsNullOrEmpty(ContentXPath); - - /// - /// Gets or sets a value for the content culture. - /// - [Required] - public string Culture { get; set; } = null!; - - internal override bool IsValid() => - base.IsValid() && - ((HasContentId ? 1 : 0) + (HasContentKey ? 1 : 0) + (HasContentXPath ? 1 : 0) == 1); - } + public bool HasContentId => ContentId != 0; + + /// + /// Gets a value indicating whether the field is populated. + /// + public bool HasContentKey => ContentKey != Guid.Empty; + + /// + /// Gets a value indicating whether the field is populated. + /// + public bool HasContentXPath => !string.IsNullOrEmpty(ContentXPath); + + /// + /// Gets or sets a value for the content culture. + /// + [Required] + public string Culture { get; set; } = null!; + + internal override bool IsValid() => + base.IsValid() && + (HasContentId ? 1 : 0) + (HasContentKey ? 1 : 0) + (HasContentXPath ? 1 : 0) == 1; } diff --git a/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs index 2e109fe31009..bf77c3645d8f 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs @@ -1,39 +1,37 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using System.ComponentModel; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for content imaging settings. +/// +public class ContentImagingSettings { - /// - /// Typed configuration options for content imaging settings. - /// - public class ContentImagingSettings + internal const string StaticImageFileTypes = "jpeg,jpg,gif,bmp,png,tiff,tif,webp"; + + private static readonly ImagingAutoFillUploadField[] s_defaultImagingAutoFillUploadField = { - private static readonly ImagingAutoFillUploadField[] s_defaultImagingAutoFillUploadField = + new() { - new ImagingAutoFillUploadField - { - Alias = Constants.Conventions.Media.File, - WidthFieldAlias = Constants.Conventions.Media.Width, - HeightFieldAlias = Constants.Conventions.Media.Height, - ExtensionFieldAlias = Constants.Conventions.Media.Extension, - LengthFieldAlias = Constants.Conventions.Media.Bytes, - } - }; - - internal const string StaticImageFileTypes = "jpeg,jpg,gif,bmp,png,tiff,tif,webp"; + Alias = Constants.Conventions.Media.File, + WidthFieldAlias = Constants.Conventions.Media.Width, + HeightFieldAlias = Constants.Conventions.Media.Height, + ExtensionFieldAlias = Constants.Conventions.Media.Extension, + LengthFieldAlias = Constants.Conventions.Media.Bytes + } + }; - /// - /// Gets or sets a value for the collection of accepted image file extensions. - /// - [DefaultValue(StaticImageFileTypes)] - public string[] ImageFileTypes { get; set; } = StaticImageFileTypes.Split(','); + /// + /// Gets or sets a value for the collection of accepted image file extensions. + /// + [DefaultValue(StaticImageFileTypes)] + public string[] ImageFileTypes { get; set; } = StaticImageFileTypes.Split(','); - /// - /// Gets or sets a value for the imaging autofill following media file upload fields. - /// - public ImagingAutoFillUploadField[] AutoFillImageProperties { get; set; } = s_defaultImagingAutoFillUploadField; - } + /// + /// Gets or sets a value for the imaging autofill following media file upload fields. + /// + public ImagingAutoFillUploadField[] AutoFillImageProperties { get; set; } = s_defaultImagingAutoFillUploadField; } diff --git a/src/Umbraco.Core/Configuration/Models/ContentNotificationSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentNotificationSettings.cs index c23eac75b2a5..ce5c3aebf319 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentNotificationSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentNotificationSettings.cs @@ -3,24 +3,23 @@ using System.ComponentModel; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for content notification settings. +/// +public class ContentNotificationSettings { + internal const bool StaticDisableHtmlEmail = false; + /// - /// Typed configuration options for content notification settings. + /// Gets or sets a value for the email address for notifications. /// - public class ContentNotificationSettings - { - internal const bool StaticDisableHtmlEmail = false; - - /// - /// Gets or sets a value for the email address for notifications. - /// - public string? Email { get; set; } + public string? Email { get; set; } - /// - /// Gets or sets a value indicating whether HTML email notifications should be disabled. - /// - [DefaultValue(StaticDisableHtmlEmail)] - public bool DisableHtmlEmail { get; set; } = StaticDisableHtmlEmail; - } + /// + /// Gets or sets a value indicating whether HTML email notifications should be disabled. + /// + [DefaultValue(StaticDisableHtmlEmail)] + public bool DisableHtmlEmail { get; set; } = StaticDisableHtmlEmail; } diff --git a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs index e6e5c7006fe7..f0532a720381 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs @@ -1,23 +1,21 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using System.ComponentModel; using Umbraco.Cms.Core.Macros; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for content settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigContent)] +public class ContentSettings { - /// - /// Typed configuration options for content settings. - /// - [UmbracoOptions(Constants.Configuration.ConfigContent)] - public class ContentSettings - { + internal const bool StaticResolveUrlsFromTextString = false; - internal const bool StaticResolveUrlsFromTextString = false; - internal const string StaticDefaultPreviewBadge = - @" + internal const string StaticDefaultPreviewBadge = + @"
Preview mode @@ -151,98 +149,97 @@ @keyframes umbraco-preview-badge--effect {{ "; - internal const string StaticMacroErrors = "Inline"; - internal const string StaticDisallowedUploadFiles = "ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,xamlx"; - internal const bool StaticShowDeprecatedPropertyEditors = false; - internal const string StaticLoginBackgroundImage = "assets/img/login.jpg"; - internal const string StaticLoginLogoImage = "assets/img/application/umbraco_logo_white.svg"; - internal const bool StaticHideBackOfficeLogo = false; - internal const bool StaticDisableDeleteWhenReferenced = false; - internal const bool StaticDisableUnpublishWhenReferenced = false; - - /// - /// Gets or sets a value for the content notification settings. - /// - public ContentNotificationSettings Notifications { get; set; } = new ContentNotificationSettings(); - - /// - /// Gets or sets a value for the content imaging settings. - /// - public ContentImagingSettings Imaging { get; set; } = new ContentImagingSettings(); - - /// - /// Gets or sets a value indicating whether URLs should be resolved from text strings. - /// - [DefaultValue(StaticResolveUrlsFromTextString)] - public bool ResolveUrlsFromTextString { get; set; } = StaticResolveUrlsFromTextString; - - /// - /// Gets or sets a value for the collection of error pages. - /// - public ContentErrorPage[] Error404Collection { get; set; } = Array.Empty(); - - /// - /// Gets or sets a value for the preview badge mark-up. - /// - [DefaultValue(StaticDefaultPreviewBadge)] - public string PreviewBadge { get; set; } = StaticDefaultPreviewBadge; - - /// - /// Gets or sets a value for the macro error behaviour. - /// - [DefaultValue(StaticMacroErrors)] - public MacroErrorBehaviour MacroErrors { get; set; } = Enum.Parse(StaticMacroErrors); - - /// - /// Gets or sets a value for the collection of file extensions that are disallowed for upload. - /// - [DefaultValue(StaticDisallowedUploadFiles)] - public IEnumerable DisallowedUploadFiles { get; set; } = StaticDisallowedUploadFiles.Split(','); - - /// - /// Gets or sets a value for the collection of file extensions that are allowed for upload. - /// - public IEnumerable AllowedUploadFiles { get; set; } = Array.Empty(); - - /// - /// Gets or sets a value indicating whether deprecated property editors should be shown. - /// - [DefaultValue(StaticShowDeprecatedPropertyEditors)] - public bool ShowDeprecatedPropertyEditors { get; set; } = StaticShowDeprecatedPropertyEditors; - - /// - /// Gets or sets a value for the path to the login screen background image. - /// - [DefaultValue(StaticLoginBackgroundImage)] - public string LoginBackgroundImage { get; set; } = StaticLoginBackgroundImage; - - /// - /// Gets or sets a value for the path to the login screen logo image. - /// - [DefaultValue(StaticLoginLogoImage)] - public string LoginLogoImage { get; set; } = StaticLoginLogoImage; - - /// - /// Gets or sets a value indicating whether to hide the backoffice umbraco logo or not. - /// - [DefaultValue(StaticHideBackOfficeLogo)] - public bool HideBackOfficeLogo { get; set; } = StaticHideBackOfficeLogo; - - /// - /// Gets or sets a value indicating whether to disable the deletion of items referenced by other items. - /// - [DefaultValue(StaticDisableDeleteWhenReferenced)] - public bool DisableDeleteWhenReferenced { get; set; } = StaticDisableDeleteWhenReferenced; - - /// - /// Gets or sets a value indicating whether to disable the unpublishing of items referenced by other items. - /// - [DefaultValue(StaticDisableUnpublishWhenReferenced)] - public bool DisableUnpublishWhenReferenced { get; set; } = StaticDisableUnpublishWhenReferenced; - - /// - /// Get or sets the model representing the global content version cleanup policy - /// - public ContentVersionCleanupPolicySettings ContentVersionCleanupPolicy { get; set; } = new ContentVersionCleanupPolicySettings(); - } + internal const string StaticMacroErrors = "Inline"; + internal const string StaticDisallowedUploadFiles = "ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,xamlx"; + internal const bool StaticShowDeprecatedPropertyEditors = false; + internal const string StaticLoginBackgroundImage = "assets/img/login.jpg"; + internal const string StaticLoginLogoImage = "assets/img/application/umbraco_logo_white.svg"; + internal const bool StaticHideBackOfficeLogo = false; + internal const bool StaticDisableDeleteWhenReferenced = false; + internal const bool StaticDisableUnpublishWhenReferenced = false; + + /// + /// Gets or sets a value for the content notification settings. + /// + public ContentNotificationSettings Notifications { get; set; } = new(); + + /// + /// Gets or sets a value for the content imaging settings. + /// + public ContentImagingSettings Imaging { get; set; } = new(); + + /// + /// Gets or sets a value indicating whether URLs should be resolved from text strings. + /// + [DefaultValue(StaticResolveUrlsFromTextString)] + public bool ResolveUrlsFromTextString { get; set; } = StaticResolveUrlsFromTextString; + + /// + /// Gets or sets a value for the collection of error pages. + /// + public ContentErrorPage[] Error404Collection { get; set; } = Array.Empty(); + + /// + /// Gets or sets a value for the preview badge mark-up. + /// + [DefaultValue(StaticDefaultPreviewBadge)] + public string PreviewBadge { get; set; } = StaticDefaultPreviewBadge; + + /// + /// Gets or sets a value for the macro error behaviour. + /// + [DefaultValue(StaticMacroErrors)] + public MacroErrorBehaviour MacroErrors { get; set; } = Enum.Parse(StaticMacroErrors); + + /// + /// Gets or sets a value for the collection of file extensions that are disallowed for upload. + /// + [DefaultValue(StaticDisallowedUploadFiles)] + public IEnumerable DisallowedUploadFiles { get; set; } = StaticDisallowedUploadFiles.Split(','); + + /// + /// Gets or sets a value for the collection of file extensions that are allowed for upload. + /// + public IEnumerable AllowedUploadFiles { get; set; } = Array.Empty(); + + /// + /// Gets or sets a value indicating whether deprecated property editors should be shown. + /// + [DefaultValue(StaticShowDeprecatedPropertyEditors)] + public bool ShowDeprecatedPropertyEditors { get; set; } = StaticShowDeprecatedPropertyEditors; + + /// + /// Gets or sets a value for the path to the login screen background image. + /// + [DefaultValue(StaticLoginBackgroundImage)] + public string LoginBackgroundImage { get; set; } = StaticLoginBackgroundImage; + + /// + /// Gets or sets a value for the path to the login screen logo image. + /// + [DefaultValue(StaticLoginLogoImage)] + public string LoginLogoImage { get; set; } = StaticLoginLogoImage; + + /// + /// Gets or sets a value indicating whether to hide the backoffice umbraco logo or not. + /// + [DefaultValue(StaticHideBackOfficeLogo)] + public bool HideBackOfficeLogo { get; set; } = StaticHideBackOfficeLogo; + + /// + /// Gets or sets a value indicating whether to disable the deletion of items referenced by other items. + /// + [DefaultValue(StaticDisableDeleteWhenReferenced)] + public bool DisableDeleteWhenReferenced { get; set; } = StaticDisableDeleteWhenReferenced; + + /// + /// Gets or sets a value indicating whether to disable the unpublishing of items referenced by other items. + /// + [DefaultValue(StaticDisableUnpublishWhenReferenced)] + public bool DisableUnpublishWhenReferenced { get; set; } = StaticDisableUnpublishWhenReferenced; + + /// + /// Get or sets the model representing the global content version cleanup policy + /// + public ContentVersionCleanupPolicySettings ContentVersionCleanupPolicy { get; set; } = new(); } diff --git a/src/Umbraco.Core/Configuration/Models/ContentVersionCleanupPolicySettings.cs b/src/Umbraco.Core/Configuration/Models/ContentVersionCleanupPolicySettings.cs index bd460058eb3b..ed721382a963 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentVersionCleanupPolicySettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentVersionCleanupPolicySettings.cs @@ -1,33 +1,31 @@ using System.ComponentModel; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Model representing the global content version cleanup policy +/// +public class ContentVersionCleanupPolicySettings { + private const bool StaticEnableCleanup = false; + private const int StaticKeepAllVersionsNewerThanDays = 7; + private const int StaticKeepLatestVersionPerDayForDays = 90; + /// - /// Model representing the global content version cleanup policy + /// Gets or sets a value indicating whether or not the cleanup job should be executed. /// - public class ContentVersionCleanupPolicySettings - { - private const bool StaticEnableCleanup = false; - private const int StaticKeepAllVersionsNewerThanDays = 7; - private const int StaticKeepLatestVersionPerDayForDays = 90; - - /// - /// Gets or sets a value indicating whether or not the cleanup job should be executed. - /// - [DefaultValue(StaticEnableCleanup)] - public bool EnableCleanup { get; set; } = StaticEnableCleanup; - - /// - /// Gets or sets the number of days where all historical content versions are kept. - /// - [DefaultValue(StaticKeepAllVersionsNewerThanDays)] - public int KeepAllVersionsNewerThanDays { get; set; } = StaticKeepAllVersionsNewerThanDays; + [DefaultValue(StaticEnableCleanup)] + public bool EnableCleanup { get; set; } = StaticEnableCleanup; - /// - /// Gets or sets the number of days where the latest historical content version for that day are kept. - /// - [DefaultValue(StaticKeepLatestVersionPerDayForDays)] - public int KeepLatestVersionPerDayForDays { get; set; } = StaticKeepLatestVersionPerDayForDays; + /// + /// Gets or sets the number of days where all historical content versions are kept. + /// + [DefaultValue(StaticKeepAllVersionsNewerThanDays)] + public int KeepAllVersionsNewerThanDays { get; set; } = StaticKeepAllVersionsNewerThanDays; - } + /// + /// Gets or sets the number of days where the latest historical content version for that day are kept. + /// + [DefaultValue(StaticKeepLatestVersionPerDayForDays)] + public int KeepLatestVersionPerDayForDays { get; set; } = StaticKeepLatestVersionPerDayForDays; } diff --git a/src/Umbraco.Core/Configuration/Models/CoreDebugSettings.cs b/src/Umbraco.Core/Configuration/Models/CoreDebugSettings.cs index 58810a34625d..052d37c5fe3a 100644 --- a/src/Umbraco.Core/Configuration/Models/CoreDebugSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/CoreDebugSettings.cs @@ -3,27 +3,26 @@ using System.ComponentModel; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for core debug settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigCoreDebug)] +public class CoreDebugSettings { + internal const bool StaticLogIncompletedScopes = false; + internal const bool StaticDumpOnTimeoutThreadAbort = false; + /// - /// Typed configuration options for core debug settings. + /// Gets or sets a value indicating whether incompleted scopes should be logged. /// - [UmbracoOptions(Constants.Configuration.ConfigCoreDebug)] - public class CoreDebugSettings - { - internal const bool StaticLogIncompletedScopes = false; - internal const bool StaticDumpOnTimeoutThreadAbort = false; - - /// - /// Gets or sets a value indicating whether incompleted scopes should be logged. - /// - [DefaultValue(StaticLogIncompletedScopes)] - public bool LogIncompletedScopes { get; set; } = StaticLogIncompletedScopes; + [DefaultValue(StaticLogIncompletedScopes)] + public bool LogIncompletedScopes { get; set; } = StaticLogIncompletedScopes; - /// - /// Gets or sets a value indicating whether memory dumps on thread abort should be taken. - /// - [DefaultValue(StaticDumpOnTimeoutThreadAbort)] - public bool DumpOnTimeoutThreadAbort { get; set; } = StaticDumpOnTimeoutThreadAbort; - } + /// + /// Gets or sets a value indicating whether memory dumps on thread abort should be taken. + /// + [DefaultValue(StaticDumpOnTimeoutThreadAbort)] + public bool DumpOnTimeoutThreadAbort { get; set; } = StaticDumpOnTimeoutThreadAbort; } diff --git a/src/Umbraco.Core/Configuration/Models/DatabaseServerMessengerSettings.cs b/src/Umbraco.Core/Configuration/Models/DatabaseServerMessengerSettings.cs index f1320a199faf..a083b164a8d2 100644 --- a/src/Umbraco.Core/Configuration/Models/DatabaseServerMessengerSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/DatabaseServerMessengerSettings.cs @@ -1,43 +1,43 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using System.ComponentModel; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for database server messaging settings. +/// +public class DatabaseServerMessengerSettings { + internal const int StaticMaxProcessingInstructionCount = 1000; + internal const string StaticTimeToRetainInstructions = "2.00:00:00"; // TimeSpan.FromDays(2); + internal const string StaticTimeBetweenSyncOperations = "00:00:05"; // TimeSpan.FromSeconds(5); + internal const string StaticTimeBetweenPruneOperations = "00:01:00"; // TimeSpan.FromMinutes(1); + /// - /// Typed configuration options for database server messaging settings. + /// Gets or sets a value for the maximum number of instructions that can be processed at startup; otherwise the server + /// cold-boots (rebuilds its caches). /// - public class DatabaseServerMessengerSettings - { - internal const int StaticMaxProcessingInstructionCount = 1000; - internal const string StaticTimeToRetainInstructions = "2.00:00:00"; // TimeSpan.FromDays(2); - internal const string StaticTimeBetweenSyncOperations = "00:00:05"; // TimeSpan.FromSeconds(5); - internal const string StaticTimeBetweenPruneOperations = "00:01:00"; // TimeSpan.FromMinutes(1); - - /// - /// Gets or sets a value for the maximum number of instructions that can be processed at startup; otherwise the server cold-boots (rebuilds its caches). - /// - [DefaultValue(StaticMaxProcessingInstructionCount)] - public int MaxProcessingInstructionCount { get; set; } = StaticMaxProcessingInstructionCount; + [DefaultValue(StaticMaxProcessingInstructionCount)] + public int MaxProcessingInstructionCount { get; set; } = StaticMaxProcessingInstructionCount; - /// - /// Gets or sets a value for the time to keep instructions in the database; records older than this number will be pruned. - /// - [DefaultValue(StaticTimeToRetainInstructions)] - public TimeSpan TimeToRetainInstructions { get; set; } = TimeSpan.Parse(StaticTimeToRetainInstructions); + /// + /// Gets or sets a value for the time to keep instructions in the database; records older than this number will be + /// pruned. + /// + [DefaultValue(StaticTimeToRetainInstructions)] + public TimeSpan TimeToRetainInstructions { get; set; } = TimeSpan.Parse(StaticTimeToRetainInstructions); - /// - /// Gets or sets a value for the time to wait between each sync operations. - /// - [DefaultValue(StaticTimeBetweenSyncOperations)] - public TimeSpan TimeBetweenSyncOperations { get; set; } = TimeSpan.Parse(StaticTimeBetweenSyncOperations); + /// + /// Gets or sets a value for the time to wait between each sync operations. + /// + [DefaultValue(StaticTimeBetweenSyncOperations)] + public TimeSpan TimeBetweenSyncOperations { get; set; } = TimeSpan.Parse(StaticTimeBetweenSyncOperations); - /// - /// Gets or sets a value for the time to wait between each prune operations. - /// - [DefaultValue(StaticTimeBetweenPruneOperations)] - public TimeSpan TimeBetweenPruneOperations { get; set; } = TimeSpan.Parse(StaticTimeBetweenPruneOperations); - } + /// + /// Gets or sets a value for the time to wait between each prune operations. + /// + [DefaultValue(StaticTimeBetweenPruneOperations)] + public TimeSpan TimeBetweenPruneOperations { get; set; } = TimeSpan.Parse(StaticTimeBetweenPruneOperations); } diff --git a/src/Umbraco.Core/Configuration/Models/DatabaseServerRegistrarSettings.cs b/src/Umbraco.Core/Configuration/Models/DatabaseServerRegistrarSettings.cs index 91d293f272ce..80aefeae6eec 100644 --- a/src/Umbraco.Core/Configuration/Models/DatabaseServerRegistrarSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/DatabaseServerRegistrarSettings.cs @@ -1,29 +1,27 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using System.ComponentModel; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for database server registrar settings. +/// +public class DatabaseServerRegistrarSettings { + internal const string StaticWaitTimeBetweenCalls = "00:01:00"; + internal const string StaticStaleServerTimeout = "00:02:00"; + /// - /// Typed configuration options for database server registrar settings. + /// Gets or sets a value for the amount of time to wait between calls to the database on the background thread. /// - public class DatabaseServerRegistrarSettings - { - internal const string StaticWaitTimeBetweenCalls = "00:01:00"; - internal const string StaticStaleServerTimeout = "00:02:00"; - - /// - /// Gets or sets a value for the amount of time to wait between calls to the database on the background thread. - /// - [DefaultValue(StaticWaitTimeBetweenCalls)] - public TimeSpan WaitTimeBetweenCalls { get; set; } = TimeSpan.Parse(StaticWaitTimeBetweenCalls); + [DefaultValue(StaticWaitTimeBetweenCalls)] + public TimeSpan WaitTimeBetweenCalls { get; set; } = TimeSpan.Parse(StaticWaitTimeBetweenCalls); - /// - /// Gets or sets a value for the time span to wait before considering a server stale, after it has last been accessed. - /// - [DefaultValue(StaticStaleServerTimeout)] - public TimeSpan StaleServerTimeout { get; set; } = TimeSpan.Parse(StaticStaleServerTimeout); - } + /// + /// Gets or sets a value for the time span to wait before considering a server stale, after it has last been accessed. + /// + [DefaultValue(StaticStaleServerTimeout)] + public TimeSpan StaleServerTimeout { get; set; } = TimeSpan.Parse(StaticStaleServerTimeout); } diff --git a/src/Umbraco.Core/Configuration/Models/DisabledHealthCheckSettings.cs b/src/Umbraco.Core/Configuration/Models/DisabledHealthCheckSettings.cs index a24ec5b92324..f055aca7abc5 100644 --- a/src/Umbraco.Core/Configuration/Models/DisabledHealthCheckSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/DisabledHealthCheckSettings.cs @@ -1,28 +1,25 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; +namespace Umbraco.Cms.Core.Configuration.Models; -namespace Umbraco.Cms.Core.Configuration.Models +/// +/// Typed configuration options for disabled healthcheck settings. +/// +public class DisabledHealthCheckSettings { /// - /// Typed configuration options for disabled healthcheck settings. + /// Gets or sets a value for the healthcheck Id to disable. /// - public class DisabledHealthCheckSettings - { - /// - /// Gets or sets a value for the healthcheck Id to disable. - /// - public Guid Id { get; set; } + public Guid Id { get; set; } - /// - /// Gets or sets a value for the date the healthcheck was disabled. - /// - public DateTime DisabledOn { get; set; } + /// + /// Gets or sets a value for the date the healthcheck was disabled. + /// + public DateTime DisabledOn { get; set; } - /// - /// Gets or sets a value for Id of the user that disabled the healthcheck. - /// - public int DisabledBy { get; set; } - } + /// + /// Gets or sets a value for Id of the user that disabled the healthcheck. + /// + public int DisabledBy { get; set; } } diff --git a/src/Umbraco.Core/Configuration/Models/ExceptionFilterSettings.cs b/src/Umbraco.Core/Configuration/Models/ExceptionFilterSettings.cs index 0d48453071ed..ebf99c03ddc0 100644 --- a/src/Umbraco.Core/Configuration/Models/ExceptionFilterSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ExceptionFilterSettings.cs @@ -3,20 +3,19 @@ using System.ComponentModel; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for exception filter settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigExceptionFilter)] +public class ExceptionFilterSettings { + internal const bool StaticDisabled = false; + /// - /// Typed configuration options for exception filter settings. + /// Gets or sets a value indicating whether the exception filter is disabled. /// - [UmbracoOptions(Constants.Configuration.ConfigExceptionFilter)] - public class ExceptionFilterSettings - { - internal const bool StaticDisabled = false; - - /// - /// Gets or sets a value indicating whether the exception filter is disabled. - /// - [DefaultValue(StaticDisabled)] - public bool Disabled { get; set; } = StaticDisabled; - } + [DefaultValue(StaticDisabled)] + public bool Disabled { get; set; } = StaticDisabled; } diff --git a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs index 8d00d5819847..5351da317c02 100644 --- a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs @@ -1,227 +1,232 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using System.ComponentModel; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for global settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigGlobal)] +public class GlobalSettings { + internal const string + StaticReservedPaths = + "~/app_plugins/,~/install/,~/mini-profiler-resources/,~/umbraco/,"; // must end with a comma! + + internal const string StaticReservedUrls = "~/.well-known,"; // must end with a comma! + internal const string StaticTimeOut = "00:20:00"; + internal const string StaticDefaultUILanguage = "en-US"; + internal const bool StaticHideTopLevelNodeFromPath = true; + internal const bool StaticUseHttps = false; + internal const int StaticVersionCheckPeriod = 7; + internal const string StaticUmbracoPath = Constants.System.DefaultUmbracoPath; + internal const string StaticIconsPath = "umbraco/assets/icons"; + internal const string StaticUmbracoCssPath = "~/css"; + internal const string StaticUmbracoScriptsPath = "~/scripts"; + internal const string StaticUmbracoMediaPath = "~/media"; + internal const bool StaticInstallMissingDatabase = false; + internal const bool StaticDisableElectionForSingleServer = false; + internal const string StaticNoNodesViewPath = "~/umbraco/UmbracoWebsite/NoNodes.cshtml"; + internal const string StaticDistributedLockingReadLockDefaultTimeout = "00:01:00"; + internal const string StaticDistributedLockingWriteLockDefaultTimeout = "00:00:05"; + internal const bool StaticSanitizeTinyMce = false; + internal const int StaticMainDomReleaseSignalPollingInterval = 2000; + + /// + /// Gets or sets a value for the reserved URLs (must end with a comma). + /// + [DefaultValue(StaticReservedUrls)] + public string ReservedUrls { get; set; } = StaticReservedUrls; + + /// + /// Gets or sets a value for the reserved paths (must end with a comma). + /// + [DefaultValue(StaticReservedPaths)] + public string ReservedPaths { get; set; } = StaticReservedPaths; + + /// + /// Gets or sets a value for the back-office login timeout. + /// + [DefaultValue(StaticTimeOut)] + public TimeSpan TimeOut { get; set; } = TimeSpan.Parse(StaticTimeOut); + + /// + /// Gets or sets a value for the default UI language. + /// + [DefaultValue(StaticDefaultUILanguage)] + public string DefaultUILanguage { get; set; } = StaticDefaultUILanguage; + + /// + /// Gets or sets a value indicating whether to hide the top level node from the path. + /// + [DefaultValue(StaticHideTopLevelNodeFromPath)] + public bool HideTopLevelNodeFromPath { get; set; } = StaticHideTopLevelNodeFromPath; + + /// + /// Gets or sets a value indicating whether HTTPS should be used. + /// + [DefaultValue(StaticUseHttps)] + public bool UseHttps { get; set; } = StaticUseHttps; + + /// + /// Gets or sets a value for the version check period in days. + /// + [DefaultValue(StaticVersionCheckPeriod)] + public int VersionCheckPeriod { get; set; } = StaticVersionCheckPeriod; + + /// + /// Gets or sets a value for the Umbraco back-office path. + /// + [DefaultValue(StaticUmbracoPath)] + public string UmbracoPath { get; set; } = StaticUmbracoPath; + + /// + /// Gets or sets a value for the Umbraco icons path. + /// + /// + /// TODO: Umbraco cannot be hard coded here that is what UmbracoPath is for + /// so this should not be a normal get set it has to have dynamic ability to return the correct + /// path given UmbracoPath if this hasn't been explicitly set. + /// + [DefaultValue(StaticIconsPath)] + public string IconsPath { get; set; } = StaticIconsPath; + + /// + /// Gets or sets a value for the Umbraco CSS path. + /// + [DefaultValue(StaticUmbracoCssPath)] + public string UmbracoCssPath { get; set; } = StaticUmbracoCssPath; + + /// + /// Gets or sets a value for the Umbraco scripts path. + /// + [DefaultValue(StaticUmbracoScriptsPath)] + public string UmbracoScriptsPath { get; set; } = StaticUmbracoScriptsPath; + + /// + /// Gets or sets a value for the Umbraco media request path. + /// + [DefaultValue(StaticUmbracoMediaPath)] + public string UmbracoMediaPath { get; set; } = StaticUmbracoMediaPath; + + /// + /// Gets or sets a value for the physical Umbraco media root path (falls back to when + /// empty). + /// + /// + /// If the value is a virtual path, it's resolved relative to the webroot. + /// + public string UmbracoMediaPhysicalRootPath { get; set; } = null!; + + /// + /// Gets or sets a value indicating whether to install the database when it is missing. + /// + [DefaultValue(StaticInstallMissingDatabase)] + public bool InstallMissingDatabase { get; set; } = StaticInstallMissingDatabase; + + /// + /// Gets or sets a value indicating whether to disable the election for a single server. + /// + [DefaultValue(StaticDisableElectionForSingleServer)] + public bool DisableElectionForSingleServer { get; set; } = StaticDisableElectionForSingleServer; + + /// + /// Gets or sets a value for the database factory server version. + /// + public string DatabaseFactoryServerVersion { get; set; } = string.Empty; + + /// + /// Gets or sets a value for the main dom lock. + /// + public string MainDomLock { get; set; } = string.Empty; + + /// + /// Gets or sets a value to discriminate MainDom boundaries. + /// + /// Generally the default should suffice but useful for advanced scenarios e.g. azure deployment slot based zero + /// downtime deployments. + /// + /// + public string MainDomKeyDiscriminator { get; set; } = string.Empty; + + /// + /// Gets or sets the duration (in milliseconds) for which the MainDomLock release signal polling task should sleep. + /// + /// + /// Doesn't apply to MainDomSemaphoreLock. + /// + /// The default value is 2000ms. + /// + /// + [DefaultValue(StaticMainDomReleaseSignalPollingInterval)] + public int MainDomReleaseSignalPollingInterval { get; set; } = StaticMainDomReleaseSignalPollingInterval; + + /// + /// Gets or sets the telemetry ID. + /// + public string Id { get; set; } = string.Empty; + + /// + /// Gets or sets a value for the path to the no content view. + /// + [DefaultValue(StaticNoNodesViewPath)] + public string NoNodesViewPath { get; set; } = StaticNoNodesViewPath; + + /// + /// Gets or sets a value for the database server registrar settings. + /// + public DatabaseServerRegistrarSettings DatabaseServerRegistrar { get; set; } = new(); + /// - /// Typed configuration options for global settings. - /// - [UmbracoOptions(Constants.Configuration.ConfigGlobal)] - public class GlobalSettings - { - internal const string StaticReservedPaths = "~/app_plugins/,~/install/,~/mini-profiler-resources/,~/umbraco/,"; // must end with a comma! - internal const string StaticReservedUrls = "~/.well-known,"; // must end with a comma! - internal const string StaticTimeOut = "00:20:00"; - internal const string StaticDefaultUILanguage = "en-US"; - internal const bool StaticHideTopLevelNodeFromPath = true; - internal const bool StaticUseHttps = false; - internal const int StaticVersionCheckPeriod = 7; - internal const string StaticUmbracoPath = Constants.System.DefaultUmbracoPath; - internal const string StaticIconsPath = "umbraco/assets/icons"; - internal const string StaticUmbracoCssPath = "~/css"; - internal const string StaticUmbracoScriptsPath = "~/scripts"; - internal const string StaticUmbracoMediaPath = "~/media"; - internal const bool StaticInstallMissingDatabase = false; - internal const bool StaticDisableElectionForSingleServer = false; - internal const string StaticNoNodesViewPath = "~/umbraco/UmbracoWebsite/NoNodes.cshtml"; - internal const string StaticDistributedLockingReadLockDefaultTimeout = "00:01:00"; - internal const string StaticDistributedLockingWriteLockDefaultTimeout = "00:00:05"; - internal const bool StaticSanitizeTinyMce = false; - internal const int StaticMainDomReleaseSignalPollingInterval = 2000; - - /// - /// Gets or sets a value for the reserved URLs (must end with a comma). - /// - [DefaultValue(StaticReservedUrls)] - public string ReservedUrls { get; set; } = StaticReservedUrls; - - /// - /// Gets or sets a value for the reserved paths (must end with a comma). - /// - [DefaultValue(StaticReservedPaths)] - public string ReservedPaths { get; set; } = StaticReservedPaths; - - /// - /// Gets or sets a value for the back-office login timeout. - /// - [DefaultValue(StaticTimeOut)] - public TimeSpan TimeOut { get; set; } = TimeSpan.Parse(StaticTimeOut); - - /// - /// Gets or sets a value for the default UI language. - /// - [DefaultValue(StaticDefaultUILanguage)] - public string DefaultUILanguage { get; set; } = StaticDefaultUILanguage; - - /// - /// Gets or sets a value indicating whether to hide the top level node from the path. - /// - [DefaultValue(StaticHideTopLevelNodeFromPath)] - public bool HideTopLevelNodeFromPath { get; set; } = StaticHideTopLevelNodeFromPath; - - /// - /// Gets or sets a value indicating whether HTTPS should be used. - /// - [DefaultValue(StaticUseHttps)] - public bool UseHttps { get; set; } = StaticUseHttps; - - /// - /// Gets or sets a value for the version check period in days. - /// - [DefaultValue(StaticVersionCheckPeriod)] - public int VersionCheckPeriod { get; set; } = StaticVersionCheckPeriod; - - /// - /// Gets or sets a value for the Umbraco back-office path. - /// - [DefaultValue(StaticUmbracoPath)] - public string UmbracoPath { get; set; } = StaticUmbracoPath; - - /// - /// Gets or sets a value for the Umbraco icons path. - /// - /// - /// TODO: Umbraco cannot be hard coded here that is what UmbracoPath is for - /// so this should not be a normal get set it has to have dynamic ability to return the correct - /// path given UmbracoPath if this hasn't been explicitly set. - /// - [DefaultValue(StaticIconsPath)] - public string IconsPath { get; set; } = StaticIconsPath; - - /// - /// Gets or sets a value for the Umbraco CSS path. - /// - [DefaultValue(StaticUmbracoCssPath)] - public string UmbracoCssPath { get; set; } = StaticUmbracoCssPath; - - /// - /// Gets or sets a value for the Umbraco scripts path. - /// - [DefaultValue(StaticUmbracoScriptsPath)] - public string UmbracoScriptsPath { get; set; } = StaticUmbracoScriptsPath; - - /// - /// Gets or sets a value for the Umbraco media request path. - /// - [DefaultValue(StaticUmbracoMediaPath)] - public string UmbracoMediaPath { get; set; } = StaticUmbracoMediaPath; - - /// - /// Gets or sets a value for the physical Umbraco media root path (falls back to when empty). - /// - /// - /// If the value is a virtual path, it's resolved relative to the webroot. - /// - public string UmbracoMediaPhysicalRootPath { get; set; } = null!; - - /// - /// Gets or sets a value indicating whether to install the database when it is missing. - /// - [DefaultValue(StaticInstallMissingDatabase)] - public bool InstallMissingDatabase { get; set; } = StaticInstallMissingDatabase; - - /// - /// Gets or sets a value indicating whether to disable the election for a single server. - /// - [DefaultValue(StaticDisableElectionForSingleServer)] - public bool DisableElectionForSingleServer { get; set; } = StaticDisableElectionForSingleServer; - - /// - /// Gets or sets a value for the database factory server version. - /// - public string DatabaseFactoryServerVersion { get; set; } = string.Empty; - - /// - /// Gets or sets a value for the main dom lock. - /// - public string MainDomLock { get; set; } = string.Empty; - - /// - /// Gets or sets a value to discriminate MainDom boundaries. - /// - /// Generally the default should suffice but useful for advanced scenarios e.g. azure deployment slot based zero downtime deployments. - /// - /// - public string MainDomKeyDiscriminator { get; set; } = string.Empty; - - /// - /// Gets or sets the duration (in milliseconds) for which the MainDomLock release signal polling task should sleep. - /// - /// - /// Doesn't apply to MainDomSemaphoreLock. - /// - /// The default value is 2000ms. - /// - /// - [DefaultValue(StaticMainDomReleaseSignalPollingInterval)] - public int MainDomReleaseSignalPollingInterval { get; set; } = StaticMainDomReleaseSignalPollingInterval; - - /// - /// Gets or sets the telemetry ID. - /// - public string Id { get; set; } = string.Empty; - - /// - /// Gets or sets a value for the path to the no content view. - /// - [DefaultValue(StaticNoNodesViewPath)] - public string NoNodesViewPath { get; set; } = StaticNoNodesViewPath; - - /// - /// Gets or sets a value for the database server registrar settings. - /// - public DatabaseServerRegistrarSettings DatabaseServerRegistrar { get; set; } = new DatabaseServerRegistrarSettings(); - - /// - /// Gets or sets a value for the database server messenger settings. - /// - public DatabaseServerMessengerSettings DatabaseServerMessenger { get; set; } = new DatabaseServerMessengerSettings(); - - /// - /// Gets or sets a value for the SMTP settings. - /// - public SmtpSettings? Smtp { get; set; } - - /// - /// Gets a value indicating whether SMTP is configured. - /// - public bool IsSmtpServerConfigured => !string.IsNullOrWhiteSpace(Smtp?.Host); - - /// - /// Gets a value indicating whether there is a physical pickup directory configured. - /// - public bool IsPickupDirectoryLocationConfigured => !string.IsNullOrWhiteSpace(Smtp?.PickupDirectoryLocation); - - /// - /// Gets or sets a value indicating whether TinyMCE scripting sanitization should be applied. - /// - [DefaultValue(StaticSanitizeTinyMce)] - public bool SanitizeTinyMce { get; set; } = StaticSanitizeTinyMce; - - /// - /// Gets or sets a value representing the maximum time to wait whilst attempting to obtain a distributed read lock. - /// - /// - /// The default value is 60 seconds. - /// - [DefaultValue(StaticDistributedLockingReadLockDefaultTimeout)] - public TimeSpan DistributedLockingReadLockDefaultTimeout { get; set; } = TimeSpan.Parse(StaticDistributedLockingReadLockDefaultTimeout); - - /// - /// Gets or sets a value representing the maximum time to wait whilst attempting to obtain a distributed write lock. - /// - /// - /// The default value is 5 seconds. - /// - [DefaultValue(StaticDistributedLockingWriteLockDefaultTimeout)] - public TimeSpan DistributedLockingWriteLockDefaultTimeout { get; set; } = TimeSpan.Parse(StaticDistributedLockingWriteLockDefaultTimeout); - - /// - /// Gets or sets a value representing the DistributedLockingMechanism to use. - /// - public string DistributedLockingMechanism { get; set; } = string.Empty; - } + /// Gets or sets a value for the database server messenger settings. + /// + public DatabaseServerMessengerSettings DatabaseServerMessenger { get; set; } = new(); + + /// + /// Gets or sets a value for the SMTP settings. + /// + public SmtpSettings? Smtp { get; set; } + + /// + /// Gets a value indicating whether SMTP is configured. + /// + public bool IsSmtpServerConfigured => !string.IsNullOrWhiteSpace(Smtp?.Host); + + /// + /// Gets a value indicating whether there is a physical pickup directory configured. + /// + public bool IsPickupDirectoryLocationConfigured => !string.IsNullOrWhiteSpace(Smtp?.PickupDirectoryLocation); + + /// + /// Gets or sets a value indicating whether TinyMCE scripting sanitization should be applied. + /// + [DefaultValue(StaticSanitizeTinyMce)] + public bool SanitizeTinyMce { get; set; } = StaticSanitizeTinyMce; + + /// + /// Gets or sets a value representing the maximum time to wait whilst attempting to obtain a distributed read lock. + /// + /// + /// The default value is 60 seconds. + /// + [DefaultValue(StaticDistributedLockingReadLockDefaultTimeout)] + public TimeSpan DistributedLockingReadLockDefaultTimeout { get; set; } = + TimeSpan.Parse(StaticDistributedLockingReadLockDefaultTimeout); + + /// + /// Gets or sets a value representing the maximum time to wait whilst attempting to obtain a distributed write lock. + /// + /// + /// The default value is 5 seconds. + /// + [DefaultValue(StaticDistributedLockingWriteLockDefaultTimeout)] + public TimeSpan DistributedLockingWriteLockDefaultTimeout { get; set; } = + TimeSpan.Parse(StaticDistributedLockingWriteLockDefaultTimeout); + + /// + /// Gets or sets a value representing the DistributedLockingMechanism to use. + /// + public string DistributedLockingMechanism { get; set; } = string.Empty; } diff --git a/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationMethodSettings.cs b/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationMethodSettings.cs index 2fc621a4820d..c973f590250e 100644 --- a/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationMethodSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationMethodSettings.cs @@ -1,42 +1,41 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using System.ComponentModel; using Umbraco.Cms.Core.HealthChecks; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for healthcheck notification method settings. +/// +public class HealthChecksNotificationMethodSettings { + internal const bool StaticEnabled = false; + internal const string StaticVerbosity = "Summary"; // Enum + internal const bool StaticFailureOnly = false; + /// - /// Typed configuration options for healthcheck notification method settings. + /// Gets or sets a value indicating whether the health check notification method is enabled. /// - public class HealthChecksNotificationMethodSettings - { - internal const bool StaticEnabled = false; - internal const string StaticVerbosity = "Summary"; // Enum - internal const bool StaticFailureOnly = false; - - /// - /// Gets or sets a value indicating whether the health check notification method is enabled. - /// - [DefaultValue(StaticEnabled)] - public bool Enabled { get; set; } = StaticEnabled; + [DefaultValue(StaticEnabled)] + public bool Enabled { get; set; } = StaticEnabled; - /// - /// Gets or sets a value for the health check notifications reporting verbosity. - /// - [DefaultValue(StaticVerbosity)] - public HealthCheckNotificationVerbosity Verbosity { get; set; } = Enum.Parse(StaticVerbosity); + /// + /// Gets or sets a value for the health check notifications reporting verbosity. + /// + [DefaultValue(StaticVerbosity)] + public HealthCheckNotificationVerbosity Verbosity { get; set; } = + Enum.Parse(StaticVerbosity); - /// - /// Gets or sets a value indicating whether the health check notifications should occur on failures only. - /// - [DefaultValue(StaticFailureOnly)] - public bool FailureOnly { get; set; } = StaticFailureOnly; + /// + /// Gets or sets a value indicating whether the health check notifications should occur on failures only. + /// + [DefaultValue(StaticFailureOnly)] + public bool FailureOnly { get; set; } = StaticFailureOnly; - /// - /// Gets or sets a value providing provider specific settings for the health check notification method. - /// - public IDictionary Settings { get; set; } = new Dictionary(); - } + /// + /// Gets or sets a value providing provider specific settings for the health check notification method. + /// + public IDictionary Settings { get; set; } = new Dictionary(); } diff --git a/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationSettings.cs b/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationSettings.cs index 6e082da19f5c..7ae1378bd4e4 100644 --- a/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationSettings.cs @@ -1,46 +1,44 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Linq; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for healthcheck notification settings. +/// +public class HealthChecksNotificationSettings { + internal const bool StaticEnabled = false; + internal const string StaticPeriod = "1.00:00:00"; //TimeSpan.FromHours(24); + + /// + /// Gets or sets a value indicating whether health check notifications are enabled. + /// + [DefaultValue(StaticEnabled)] + public bool Enabled { get; set; } = StaticEnabled; + + /// + /// Gets or sets a value for the first run time of a healthcheck notification in crontab format. + /// + public string FirstRunTime { get; set; } = string.Empty; + + /// + /// Gets or sets a value for the period of the healthcheck notification. + /// + [DefaultValue(StaticPeriod)] + public TimeSpan Period { get; set; } = TimeSpan.Parse(StaticPeriod); + + /// + /// Gets or sets a value for the collection of health check notification methods. + /// + public IDictionary NotificationMethods { get; set; } = + new Dictionary(); + /// - /// Typed configuration options for healthcheck notification settings. + /// Gets or sets a value for the collection of health checks that are disabled for notifications. /// - public class HealthChecksNotificationSettings - { - internal const bool StaticEnabled = false; - internal const string StaticPeriod = "1.00:00:00"; //TimeSpan.FromHours(24); - - /// - /// Gets or sets a value indicating whether health check notifications are enabled. - /// - [DefaultValue(StaticEnabled)] - public bool Enabled { get; set; } = StaticEnabled; - - /// - /// Gets or sets a value for the first run time of a healthcheck notification in crontab format. - /// - public string FirstRunTime { get; set; } = string.Empty; - - /// - /// Gets or sets a value for the period of the healthcheck notification. - /// - [DefaultValue(StaticPeriod)] - public TimeSpan Period { get; set; } = TimeSpan.Parse(StaticPeriod); - - /// - /// Gets or sets a value for the collection of health check notification methods. - /// - public IDictionary NotificationMethods { get; set; } = new Dictionary(); - - /// - /// Gets or sets a value for the collection of health checks that are disabled for notifications. - /// - public IEnumerable DisabledChecks { get; set; } = Enumerable.Empty(); - } + public IEnumerable DisabledChecks { get; set; } = + Enumerable.Empty(); } diff --git a/src/Umbraco.Core/Configuration/Models/HealthChecksSettings.cs b/src/Umbraco.Core/Configuration/Models/HealthChecksSettings.cs index 0d232b9a9bb7..6ae79e974307 100644 --- a/src/Umbraco.Core/Configuration/Models/HealthChecksSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/HealthChecksSettings.cs @@ -1,25 +1,22 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; -using System.Linq; +namespace Umbraco.Cms.Core.Configuration.Models; -namespace Umbraco.Cms.Core.Configuration.Models +/// +/// Typed configuration options for healthchecks settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigHealthChecks)] +public class HealthChecksSettings { /// - /// Typed configuration options for healthchecks settings. + /// Gets or sets a value for the collection of healthchecks that are disabled. /// - [UmbracoOptions(Constants.Configuration.ConfigHealthChecks)] - public class HealthChecksSettings - { - /// - /// Gets or sets a value for the collection of healthchecks that are disabled. - /// - public IEnumerable DisabledChecks { get; set; } = Enumerable.Empty(); + public IEnumerable DisabledChecks { get; set; } = + Enumerable.Empty(); - /// - /// Gets or sets a value for the healthcheck notification settings. - /// - public HealthChecksNotificationSettings Notification { get; set; } = new HealthChecksNotificationSettings(); - } + /// + /// Gets or sets a value for the healthcheck notification settings. + /// + public HealthChecksNotificationSettings Notification { get; set; } = new(); } diff --git a/src/Umbraco.Core/Configuration/Models/HelpPageSettings.cs b/src/Umbraco.Core/Configuration/Models/HelpPageSettings.cs index b608b5c1554e..01d028f883ac 100644 --- a/src/Umbraco.Core/Configuration/Models/HelpPageSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/HelpPageSettings.cs @@ -1,11 +1,10 @@ -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +[UmbracoOptions(Constants.Configuration.ConfigHelpPage)] +public class HelpPageSettings { - [UmbracoOptions(Constants.Configuration.ConfigHelpPage)] - public class HelpPageSettings - { - /// - /// Gets or sets the allowed addresses to retrieve data for the content dashboard. - /// - public string[]? HelpPageUrlAllowList { get; set; } - } + /// + /// Gets or sets the allowed addresses to retrieve data for the content dashboard. + /// + public string[]? HelpPageUrlAllowList { get; set; } } diff --git a/src/Umbraco.Core/Configuration/Models/HostingSettings.cs b/src/Umbraco.Core/Configuration/Models/HostingSettings.cs index 8f5f47a566fd..2329c73d6600 100644 --- a/src/Umbraco.Core/Configuration/Models/HostingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/HostingSettings.cs @@ -3,38 +3,38 @@ using System.ComponentModel; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for hosting settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigHosting)] +public class HostingSettings { + internal const string StaticLocalTempStorageLocation = "Default"; + internal const bool StaticDebug = false; + /// - /// Typed configuration options for hosting settings. + /// Gets or sets a value for the application virtual path. /// - [UmbracoOptions(Constants.Configuration.ConfigHosting)] - public class HostingSettings - { - internal const string StaticLocalTempStorageLocation = "Default"; - internal const bool StaticDebug = false; - - /// - /// Gets or sets a value for the application virtual path. - /// - public string? ApplicationVirtualPath { get; set; } + public string? ApplicationVirtualPath { get; set; } - /// - /// Gets or sets a value for the location of temporary files. - /// - [DefaultValue(StaticLocalTempStorageLocation)] - public LocalTempStorage LocalTempStorageLocation { get; set; } = Enum.Parse(StaticLocalTempStorageLocation); + /// + /// Gets or sets a value for the location of temporary files. + /// + [DefaultValue(StaticLocalTempStorageLocation)] + public LocalTempStorage LocalTempStorageLocation { get; set; } = + Enum.Parse(StaticLocalTempStorageLocation); - /// - /// Gets or sets a value indicating whether umbraco is running in [debug mode]. - /// - /// true if [debug mode]; otherwise, false. - [DefaultValue(StaticDebug)] - public bool Debug { get; set; } = StaticDebug; + /// + /// Gets or sets a value indicating whether umbraco is running in [debug mode]. + /// + /// true if [debug mode]; otherwise, false. + [DefaultValue(StaticDebug)] + public bool Debug { get; set; } = StaticDebug; - /// - /// Gets or sets a value specifying the name of the site. - /// - public string? SiteName { get; set; } - } + /// + /// Gets or sets a value specifying the name of the site. + /// + public string? SiteName { get; set; } } diff --git a/src/Umbraco.Core/Configuration/Models/ImagingAutoFillUploadField.cs b/src/Umbraco.Core/Configuration/Models/ImagingAutoFillUploadField.cs index 8a0a1658b22d..3bcf91b0be17 100644 --- a/src/Umbraco.Core/Configuration/Models/ImagingAutoFillUploadField.cs +++ b/src/Umbraco.Core/Configuration/Models/ImagingAutoFillUploadField.cs @@ -4,41 +4,40 @@ using System.ComponentModel.DataAnnotations; using Umbraco.Cms.Core.Configuration.Models.Validation; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for image autofill upload settings. +/// +public class ImagingAutoFillUploadField : ValidatableEntryBase { /// - /// Typed configuration options for image autofill upload settings. + /// Gets or sets a value for the alias of the image upload property. /// - public class ImagingAutoFillUploadField : ValidatableEntryBase - { - /// - /// Gets or sets a value for the alias of the image upload property. - /// - [Required] - public string Alias { get; set; } = null!; + [Required] + public string Alias { get; set; } = null!; - /// - /// Gets or sets a value for the width field alias of the image upload property. - /// - [Required] - public string WidthFieldAlias { get; set; } = null!; + /// + /// Gets or sets a value for the width field alias of the image upload property. + /// + [Required] + public string WidthFieldAlias { get; set; } = null!; - /// - /// Gets or sets a value for the height field alias of the image upload property. - /// - [Required] - public string HeightFieldAlias { get; set; } = null!; + /// + /// Gets or sets a value for the height field alias of the image upload property. + /// + [Required] + public string HeightFieldAlias { get; set; } = null!; - /// - /// Gets or sets a value for the length field alias of the image upload property. - /// - [Required] - public string LengthFieldAlias { get; set; } = null!; + /// + /// Gets or sets a value for the length field alias of the image upload property. + /// + [Required] + public string LengthFieldAlias { get; set; } = null!; - /// - /// Gets or sets a value for the extension field alias of the image upload property. - /// - [Required] - public string ExtensionFieldAlias { get; set; } = null!; - } + /// + /// Gets or sets a value for the extension field alias of the image upload property. + /// + [Required] + public string ExtensionFieldAlias { get; set; } = null!; } diff --git a/src/Umbraco.Core/Configuration/Models/ImagingCacheSettings.cs b/src/Umbraco.Core/Configuration/Models/ImagingCacheSettings.cs index b3bdddc211d3..a433c5d3009d 100644 --- a/src/Umbraco.Core/Configuration/Models/ImagingCacheSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ImagingCacheSettings.cs @@ -1,51 +1,48 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using System.ComponentModel; -using System.IO; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for image cache settings. +/// +public class ImagingCacheSettings { + internal const string StaticBrowserMaxAge = "7.00:00:00"; + internal const string StaticCacheMaxAge = "365.00:00:00"; + internal const int StaticCacheHashLength = 12; + internal const int StaticCacheFolderDepth = 8; + internal const string StaticCacheFolder = Constants.SystemDirectories.TempData + "/MediaCache"; + + /// + /// Gets or sets a value for the browser image cache maximum age. + /// + [DefaultValue(StaticBrowserMaxAge)] + public TimeSpan BrowserMaxAge { get; set; } = TimeSpan.Parse(StaticBrowserMaxAge); + + /// + /// Gets or sets a value for the image cache maximum age. + /// + [DefaultValue(StaticCacheMaxAge)] + public TimeSpan CacheMaxAge { get; set; } = TimeSpan.Parse(StaticCacheMaxAge); + + /// + /// Gets or sets a value for the image cache hash length. + /// + [DefaultValue(StaticCacheHashLength)] + public uint CacheHashLength { get; set; } = StaticCacheHashLength; + + /// + /// Gets or sets a value for the image cache folder depth. + /// + [DefaultValue(StaticCacheFolderDepth)] + public uint CacheFolderDepth { get; set; } = StaticCacheFolderDepth; + /// - /// Typed configuration options for image cache settings. + /// Gets or sets a value for the image cache folder. /// - public class ImagingCacheSettings - { - internal const string StaticBrowserMaxAge = "7.00:00:00"; - internal const string StaticCacheMaxAge = "365.00:00:00"; - internal const int StaticCacheHashLength = 12; - internal const int StaticCacheFolderDepth = 8; - internal const string StaticCacheFolder = Constants.SystemDirectories.TempData + "/MediaCache"; - - /// - /// Gets or sets a value for the browser image cache maximum age. - /// - [DefaultValue(StaticBrowserMaxAge)] - public TimeSpan BrowserMaxAge { get; set; } = TimeSpan.Parse(StaticBrowserMaxAge); - - /// - /// Gets or sets a value for the image cache maximum age. - /// - [DefaultValue(StaticCacheMaxAge)] - public TimeSpan CacheMaxAge { get; set; } = TimeSpan.Parse(StaticCacheMaxAge); - - /// - /// Gets or sets a value for the image cache hash length. - /// - [DefaultValue(StaticCacheHashLength)] - public uint CacheHashLength { get; set; } = StaticCacheHashLength; - - /// - /// Gets or sets a value for the image cache folder depth. - /// - [DefaultValue(StaticCacheFolderDepth)] - public uint CacheFolderDepth { get; set; } = StaticCacheFolderDepth; - - /// - /// Gets or sets a value for the image cache folder. - /// - [DefaultValue(StaticCacheFolder)] - public string CacheFolder { get; set; } = StaticCacheFolder; - } + [DefaultValue(StaticCacheFolder)] + public string CacheFolder { get; set; } = StaticCacheFolder; } diff --git a/src/Umbraco.Core/Configuration/Models/ImagingResizeSettings.cs b/src/Umbraco.Core/Configuration/Models/ImagingResizeSettings.cs index ff02fdc5229e..dc4585bf9c7d 100644 --- a/src/Umbraco.Core/Configuration/Models/ImagingResizeSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ImagingResizeSettings.cs @@ -3,26 +3,25 @@ using System.ComponentModel; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for image resize settings. +/// +public class ImagingResizeSettings { + internal const int StaticMaxWidth = 5000; + internal const int StaticMaxHeight = 5000; + /// - /// Typed configuration options for image resize settings. + /// Gets or sets a value for the maximim resize width. /// - public class ImagingResizeSettings - { - internal const int StaticMaxWidth = 5000; - internal const int StaticMaxHeight = 5000; - - /// - /// Gets or sets a value for the maximim resize width. - /// - [DefaultValue(StaticMaxWidth)] - public int MaxWidth { get; set; } = StaticMaxWidth; + [DefaultValue(StaticMaxWidth)] + public int MaxWidth { get; set; } = StaticMaxWidth; - /// - /// Gets or sets a value for the maximim resize height. - /// - [DefaultValue(StaticMaxHeight)] - public int MaxHeight { get; set; } = StaticMaxHeight; - } + /// + /// Gets or sets a value for the maximim resize height. + /// + [DefaultValue(StaticMaxHeight)] + public int MaxHeight { get; set; } = StaticMaxHeight; } diff --git a/src/Umbraco.Core/Configuration/Models/ImagingSettings.cs b/src/Umbraco.Core/Configuration/Models/ImagingSettings.cs index fde303343c9c..8232746ead3e 100644 --- a/src/Umbraco.Core/Configuration/Models/ImagingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ImagingSettings.cs @@ -1,22 +1,21 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for imaging settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigImaging)] +public class ImagingSettings { /// - /// Typed configuration options for imaging settings. + /// Gets or sets a value for imaging cache settings. /// - [UmbracoOptions(Constants.Configuration.ConfigImaging)] - public class ImagingSettings - { - /// - /// Gets or sets a value for imaging cache settings. - /// - public ImagingCacheSettings Cache { get; set; } = new ImagingCacheSettings(); + public ImagingCacheSettings Cache { get; set; } = new(); - /// - /// Gets or sets a value for imaging resize settings. - /// - public ImagingResizeSettings Resize { get; set; } = new ImagingResizeSettings(); - } + /// + /// Gets or sets a value for imaging resize settings. + /// + public ImagingResizeSettings Resize { get; set; } = new(); } diff --git a/src/Umbraco.Core/Configuration/Models/IndexCreatorSettings.cs b/src/Umbraco.Core/Configuration/Models/IndexCreatorSettings.cs index c140463b4a27..8c18495d5576 100644 --- a/src/Umbraco.Core/Configuration/Models/IndexCreatorSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/IndexCreatorSettings.cs @@ -1,20 +1,16 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; +namespace Umbraco.Cms.Core.Configuration.Models; -namespace Umbraco.Cms.Core.Configuration.Models +/// +/// Typed configuration options for index creator settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigExamine)] +public class IndexCreatorSettings { /// - /// Typed configuration options for index creator settings. + /// Gets or sets a value for lucene directory factory type. /// - [UmbracoOptions(Constants.Configuration.ConfigExamine)] - public class IndexCreatorSettings - { - /// - /// Gets or sets a value for lucene directory factory type. - /// - public LuceneDirectoryFactory LuceneDirectoryFactory { get; set; } - - } + public LuceneDirectoryFactory LuceneDirectoryFactory { get; set; } } diff --git a/src/Umbraco.Core/Configuration/Models/InstallDefaultDataSettings.cs b/src/Umbraco.Core/Configuration/Models/InstallDefaultDataSettings.cs index 377e893bbf52..f0cfee550b9d 100644 --- a/src/Umbraco.Core/Configuration/Models/InstallDefaultDataSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/InstallDefaultDataSettings.cs @@ -1,73 +1,74 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Configuration.Models; -namespace Umbraco.Cms.Core.Configuration.Models +/// +/// An enumeration of options available for control over installation of default Umbraco data. +/// +public enum InstallDefaultDataOption { /// - /// An enumeration of options available for control over installation of default Umbraco data. + /// Do not install any items of this type (other than Umbraco defined essential ones). /// - public enum InstallDefaultDataOption - { - /// - /// Do not install any items of this type (other than Umbraco defined essential ones). - /// - None, + None, - /// - /// Only install the default data specified in the - /// - Values, + /// + /// Only install the default data specified in the + /// + Values, - /// - /// Install all default data, except that specified in the - /// - ExceptValues, + /// + /// Install all default data, except that specified in the + /// + ExceptValues, - /// - /// Install all default data. - /// - All - } + /// + /// Install all default data. + /// + All +} +/// +/// Typed configuration options for installation of default data. +/// +public class InstallDefaultDataSettings +{ /// - /// Typed configuration options for installation of default data. + /// Gets or sets a value indicating whether to create default data on installation. /// - public class InstallDefaultDataSettings - { - /// - /// Gets or sets a value indicating whether to create default data on installation. - /// - public InstallDefaultDataOption InstallData { get; set; } = InstallDefaultDataOption.All; + public InstallDefaultDataOption InstallData { get; set; } = InstallDefaultDataOption.All; - /// - /// Gets or sets a value indicating which default data (languages, data types, etc.) should be created when is - /// set to or . - /// - /// - /// - /// For languages, the values provided should be the ISO codes for the languages to be included or excluded, e.g. "en-US". - /// If removing the single default language, ensure that a different one is created via some other means (such - /// as a restore from Umbraco Deploy schema data). - /// - /// - /// For data types, the values provided should be the Guid values used by Umbraco for the data type, listed at: - /// - /// Some data types - such as the string label - cannot be excluded from install as they are required for core Umbraco - /// functionality. - /// Otherwise take care not to remove data types required for default Umbraco media and member types, unless you also - /// choose to exclude them. - /// - /// - /// For media types, the values provided should be the Guid values used by Umbraco for the media type, listed at: - /// https://github.com/umbraco/Umbraco-CMS/blob/v9/dev/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs. - /// - /// - /// For member types, the values provided should be the Guid values used by Umbraco for the member type, listed at: - /// https://github.com/umbraco/Umbraco-CMS/blob/v9/dev/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs. - /// - /// - public IList Values { get; set; } = new List(); - } + /// + /// Gets or sets a value indicating which default data (languages, data types, etc.) should be created when + /// is + /// set to or . + /// + /// + /// + /// For languages, the values provided should be the ISO codes for the languages to be included or excluded, e.g. + /// "en-US". + /// If removing the single default language, ensure that a different one is created via some other means (such + /// as a restore from Umbraco Deploy schema data). + /// + /// + /// For data types, the values provided should be the Guid values used by Umbraco for the data type, listed at: + /// + /// Some data types - such as the string label - cannot be excluded from install as they are required for core + /// Umbraco + /// functionality. + /// Otherwise take care not to remove data types required for default Umbraco media and member types, unless you + /// also + /// choose to exclude them. + /// + /// + /// For media types, the values provided should be the Guid values used by Umbraco for the media type, listed at: + /// https://github.com/umbraco/Umbraco-CMS/blob/v9/dev/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs. + /// + /// + /// For member types, the values provided should be the Guid values used by Umbraco for the member type, listed at: + /// https://github.com/umbraco/Umbraco-CMS/blob/v9/dev/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs. + /// + /// + public IList Values { get; set; } = new List(); } diff --git a/src/Umbraco.Core/Configuration/Models/KeepAliveSettings.cs b/src/Umbraco.Core/Configuration/Models/KeepAliveSettings.cs index 297e1dff87e0..64cd61ad2632 100644 --- a/src/Umbraco.Core/Configuration/Models/KeepAliveSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/KeepAliveSettings.cs @@ -3,27 +3,26 @@ using System.ComponentModel; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for keep alive settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigKeepAlive)] +public class KeepAliveSettings { + internal const bool StaticDisableKeepAliveTask = false; + internal const string StaticKeepAlivePingUrl = "~/api/keepalive/ping"; + /// - /// Typed configuration options for keep alive settings. + /// Gets or sets a value indicating whether the keep alive task is disabled. /// - [UmbracoOptions(Constants.Configuration.ConfigKeepAlive)] - public class KeepAliveSettings - { - internal const bool StaticDisableKeepAliveTask = false; - internal const string StaticKeepAlivePingUrl = "~/api/keepalive/ping"; - - /// - /// Gets or sets a value indicating whether the keep alive task is disabled. - /// - [DefaultValue(StaticDisableKeepAliveTask)] - public bool DisableKeepAliveTask { get; set; } = StaticDisableKeepAliveTask; + [DefaultValue(StaticDisableKeepAliveTask)] + public bool DisableKeepAliveTask { get; set; } = StaticDisableKeepAliveTask; - /// - /// Gets or sets a value for the keep alive ping URL. - /// - [DefaultValue(StaticKeepAlivePingUrl)] - public string KeepAlivePingUrl { get; set; } = StaticKeepAlivePingUrl; - } + /// + /// Gets or sets a value for the keep alive ping URL. + /// + [DefaultValue(StaticKeepAlivePingUrl)] + public string KeepAlivePingUrl { get; set; } = StaticKeepAlivePingUrl; } diff --git a/src/Umbraco.Core/Configuration/Models/LegacyPasswordMigrationSettings.cs b/src/Umbraco.Core/Configuration/Models/LegacyPasswordMigrationSettings.cs index c3909ed619b2..b44d70a46aca 100644 --- a/src/Umbraco.Core/Configuration/Models/LegacyPasswordMigrationSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/LegacyPasswordMigrationSettings.cs @@ -3,28 +3,27 @@ using System.ComponentModel; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for legacy machine key settings used for migration of members from a v8 solution. +/// +[UmbracoOptions(Constants.Configuration.ConfigLegacyPasswordMigration)] +public class LegacyPasswordMigrationSettings { + private const string StaticDecryptionKey = ""; + /// - /// Typed configuration options for legacy machine key settings used for migration of members from a v8 solution. + /// Gets the decryption algorithm. /// - [UmbracoOptions(Constants.Configuration.ConfigLegacyPasswordMigration)] - public class LegacyPasswordMigrationSettings - { - private const string StaticDecryptionKey = ""; - - /// - /// Gets the decryption algorithm. - /// - /// - /// Currently only AES is supported. This should include all machine keys generated by Umbraco. - /// - public string MachineKeyDecryption => "AES"; + /// + /// Currently only AES is supported. This should include all machine keys generated by Umbraco. + /// + public string MachineKeyDecryption => "AES"; - /// - /// Gets or sets the decryption hex-formatted string key found in legacy web.config machineKey configuration-element. - /// - [DefaultValue(StaticDecryptionKey)] - public string MachineKeyDecryptionKey { get; set; } = StaticDecryptionKey; - } + /// + /// Gets or sets the decryption hex-formatted string key found in legacy web.config machineKey configuration-element. + /// + [DefaultValue(StaticDecryptionKey)] + public string MachineKeyDecryptionKey { get; set; } = StaticDecryptionKey; } diff --git a/src/Umbraco.Core/Configuration/Models/LoggingSettings.cs b/src/Umbraco.Core/Configuration/Models/LoggingSettings.cs index 2075921c3f9f..37b671926c55 100644 --- a/src/Umbraco.Core/Configuration/Models/LoggingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/LoggingSettings.cs @@ -1,23 +1,21 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using System.ComponentModel; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for logging settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigLogging)] +public class LoggingSettings { + internal const string StaticMaxLogAge = "1.00:00:00"; // TimeSpan.FromHours(24); + /// - /// Typed configuration options for logging settings. + /// Gets or sets a value for the maximum age of a log file. /// - [UmbracoOptions(Constants.Configuration.ConfigLogging)] - public class LoggingSettings - { - internal const string StaticMaxLogAge = "1.00:00:00"; // TimeSpan.FromHours(24); - - /// - /// Gets or sets a value for the maximum age of a log file. - /// - [DefaultValue(StaticMaxLogAge)] - public TimeSpan MaxLogAge { get; set; } = TimeSpan.Parse(StaticMaxLogAge); - } + [DefaultValue(StaticMaxLogAge)] + public TimeSpan MaxLogAge { get; set; } = TimeSpan.Parse(StaticMaxLogAge); } diff --git a/src/Umbraco.Core/Configuration/Models/LuceneDirectoryFactory.cs b/src/Umbraco.Core/Configuration/Models/LuceneDirectoryFactory.cs index 5f06a850f16a..901fa1af1e00 100644 --- a/src/Umbraco.Core/Configuration/Models/LuceneDirectoryFactory.cs +++ b/src/Umbraco.Core/Configuration/Models/LuceneDirectoryFactory.cs @@ -1,24 +1,23 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +public enum LuceneDirectoryFactory { - public enum LuceneDirectoryFactory - { - /// - /// The index will operate from the default location: Umbraco/Data/Temp/ExamineIndexes - /// - Default, + /// + /// The index will operate from the default location: Umbraco/Data/Temp/ExamineIndexes + /// + Default, - /// - /// The index will operate on a local index created in the processes %temp% location and - /// will replicate back to main storage in Umbraco/Data/Temp/ExamineIndexes - /// - SyncedTempFileSystemDirectoryFactory, + /// + /// The index will operate on a local index created in the processes %temp% location and + /// will replicate back to main storage in Umbraco/Data/Temp/ExamineIndexes + /// + SyncedTempFileSystemDirectoryFactory, - /// - /// The index will operate only in the processes %temp% directory location - /// - TempFileSystemDirectoryFactory - } + /// + /// The index will operate only in the processes %temp% directory location + /// + TempFileSystemDirectoryFactory } diff --git a/src/Umbraco.Core/Configuration/Models/MemberPasswordConfigurationSettings.cs b/src/Umbraco.Core/Configuration/Models/MemberPasswordConfigurationSettings.cs index fa4f0725f7e0..1e884a150f28 100644 --- a/src/Umbraco.Core/Configuration/Models/MemberPasswordConfigurationSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/MemberPasswordConfigurationSettings.cs @@ -3,47 +3,46 @@ using System.ComponentModel; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for member password settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigMemberPassword)] +public class MemberPasswordConfigurationSettings : IPasswordConfiguration { - /// - /// Typed configuration options for member password settings. - /// - [UmbracoOptions(Constants.Configuration.ConfigMemberPassword)] - public class MemberPasswordConfigurationSettings : IPasswordConfiguration - { - internal const int StaticRequiredLength = 10; - internal const bool StaticRequireNonLetterOrDigit = false; - internal const bool StaticRequireDigit = false; - internal const bool StaticRequireLowercase = false; - internal const bool StaticRequireUppercase = false; - internal const int StaticMaxFailedAccessAttemptsBeforeLockout = 5; - - /// - [DefaultValue(StaticRequiredLength)] - public int RequiredLength { get; set; } = StaticRequiredLength; - - /// - [DefaultValue(StaticRequireNonLetterOrDigit)] - public bool RequireNonLetterOrDigit { get; set; } = StaticRequireNonLetterOrDigit; - - /// - [DefaultValue(StaticRequireDigit)] - public bool RequireDigit { get; set; } = StaticRequireDigit; - - /// - [DefaultValue(StaticRequireLowercase)] - public bool RequireLowercase { get; set; } = StaticRequireLowercase; - - /// - [DefaultValue(StaticRequireUppercase)] - public bool RequireUppercase { get; set; } = StaticRequireUppercase; - - /// - [DefaultValue(Constants.Security.AspNetCoreV3PasswordHashAlgorithmName)] - public string HashAlgorithmType { get; set; } = Constants.Security.AspNetCoreV3PasswordHashAlgorithmName; - - /// - [DefaultValue(StaticMaxFailedAccessAttemptsBeforeLockout)] - public int MaxFailedAccessAttemptsBeforeLockout { get; set; } = StaticMaxFailedAccessAttemptsBeforeLockout; - } + internal const int StaticRequiredLength = 10; + internal const bool StaticRequireNonLetterOrDigit = false; + internal const bool StaticRequireDigit = false; + internal const bool StaticRequireLowercase = false; + internal const bool StaticRequireUppercase = false; + internal const int StaticMaxFailedAccessAttemptsBeforeLockout = 5; + + /// + [DefaultValue(StaticRequiredLength)] + public int RequiredLength { get; set; } = StaticRequiredLength; + + /// + [DefaultValue(StaticRequireNonLetterOrDigit)] + public bool RequireNonLetterOrDigit { get; set; } = StaticRequireNonLetterOrDigit; + + /// + [DefaultValue(StaticRequireDigit)] + public bool RequireDigit { get; set; } = StaticRequireDigit; + + /// + [DefaultValue(StaticRequireLowercase)] + public bool RequireLowercase { get; set; } = StaticRequireLowercase; + + /// + [DefaultValue(StaticRequireUppercase)] + public bool RequireUppercase { get; set; } = StaticRequireUppercase; + + /// + [DefaultValue(Constants.Security.AspNetCoreV3PasswordHashAlgorithmName)] + public string HashAlgorithmType { get; set; } = Constants.Security.AspNetCoreV3PasswordHashAlgorithmName; + + /// + [DefaultValue(StaticMaxFailedAccessAttemptsBeforeLockout)] + public int MaxFailedAccessAttemptsBeforeLockout { get; set; } = StaticMaxFailedAccessAttemptsBeforeLockout; } diff --git a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs index 73d046de32cb..6a78d7af3724 100644 --- a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs @@ -2,83 +2,81 @@ // See LICENSE for more details. using System.ComponentModel; -using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for models builder settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigModelsBuilder, BindNonPublicProperties = true)] +public class ModelsBuilderSettings { + internal const string StaticModelsMode = "InMemoryAuto"; + internal const string StaticModelsDirectory = "~/umbraco/models"; + internal const bool StaticAcceptUnsafeModelsDirectory = false; + internal const int StaticDebugLevel = 0; + private bool _flagOutOfDateModels = true; + /// - /// Typed configuration options for models builder settings. + /// Gets or sets a value for the models mode. /// - [UmbracoOptions(Constants.Configuration.ConfigModelsBuilder, BindNonPublicProperties = true)] - public class ModelsBuilderSettings - { - private bool _flagOutOfDateModels = true; - internal const string StaticModelsMode = "InMemoryAuto"; - internal const string StaticModelsDirectory = "~/umbraco/models"; - internal const bool StaticAcceptUnsafeModelsDirectory = false; - internal const int StaticDebugLevel = 0; + [DefaultValue(StaticModelsMode)] + public ModelsMode ModelsMode { get; set; } = Enum.Parse(StaticModelsMode); - /// - /// Gets or sets a value for the models mode. - /// - [DefaultValue(StaticModelsMode)] - public ModelsMode ModelsMode { get; set; } = Enum.Parse(StaticModelsMode); + /// + /// Gets or sets a value for models namespace. + /// + /// That value could be overriden by other (attribute in user's code...). Return default if no value was supplied. + [DefaultValue(Constants.ModelsBuilder.DefaultModelsNamespace)] + public string ModelsNamespace { get; set; } = Constants.ModelsBuilder.DefaultModelsNamespace; - /// - /// Gets or sets a value for models namespace. - /// - /// That value could be overriden by other (attribute in user's code...). Return default if no value was supplied. - [DefaultValue(Constants.ModelsBuilder.DefaultModelsNamespace)] - public string ModelsNamespace { get; set; } = Constants.ModelsBuilder.DefaultModelsNamespace; + /// + /// Gets or sets a value indicating whether we should flag out-of-date models. + /// + /// + /// Models become out-of-date when data types or content types are updated. When this + /// setting is activated the ~/umbraco/models/PureLive/ood.txt file is then created. When models are + /// generated through the dashboard, the files is cleared. Default value is false. + /// + public bool FlagOutOfDateModels + { + get => _flagOutOfDateModels; - /// - /// Gets or sets a value indicating whether we should flag out-of-date models. - /// - /// - /// Models become out-of-date when data types or content types are updated. When this - /// setting is activated the ~/umbraco/models/PureLive/ood.txt file is then created. When models are - /// generated through the dashboard, the files is cleared. Default value is false. - /// - public bool FlagOutOfDateModels + set { - get => _flagOutOfDateModels; - - set + if (!ModelsMode.IsAuto()) { - if (!ModelsMode.IsAuto()) - { - _flagOutOfDateModels = false; - return; - } - - _flagOutOfDateModels = value; + _flagOutOfDateModels = false; + return; } + + _flagOutOfDateModels = value; } + } - /// - /// Gets or sets a value for the models directory. - /// - /// Default is ~/umbraco/models but that can be changed. - [DefaultValue(StaticModelsDirectory)] - public string ModelsDirectory { get; set; } = StaticModelsDirectory; + /// + /// Gets or sets a value for the models directory. + /// + /// Default is ~/umbraco/models but that can be changed. + [DefaultValue(StaticModelsDirectory)] + public string ModelsDirectory { get; set; } = StaticModelsDirectory; - /// - /// Gets or sets a value indicating whether to accept an unsafe value for ModelsDirectory. - /// - /// - /// An unsafe value is an absolute path, or a relative path pointing outside - /// of the website root. - /// - [DefaultValue(StaticAcceptUnsafeModelsDirectory)] - public bool AcceptUnsafeModelsDirectory { get; set; } = StaticAcceptUnsafeModelsDirectory; + /// + /// Gets or sets a value indicating whether to accept an unsafe value for ModelsDirectory. + /// + /// + /// An unsafe value is an absolute path, or a relative path pointing outside + /// of the website root. + /// + [DefaultValue(StaticAcceptUnsafeModelsDirectory)] + public bool AcceptUnsafeModelsDirectory { get; set; } = StaticAcceptUnsafeModelsDirectory; - /// - /// Gets or sets a value indicating the debug log level. - /// - /// 0 means minimal (safe on live site), anything else means more and more details (maybe not safe). - [DefaultValue(StaticDebugLevel)] - public int DebugLevel { get; set; } = StaticDebugLevel; - } + /// + /// Gets or sets a value indicating the debug log level. + /// + /// 0 means minimal (safe on live site), anything else means more and more details (maybe not safe). + [DefaultValue(StaticDebugLevel)] + public int DebugLevel { get; set; } = StaticDebugLevel; } diff --git a/src/Umbraco.Core/Configuration/Models/NuCacheSerializerType.cs b/src/Umbraco.Core/Configuration/Models/NuCacheSerializerType.cs index 8f889b10c3d4..5fff8047d39e 100644 --- a/src/Umbraco.Core/Configuration/Models/NuCacheSerializerType.cs +++ b/src/Umbraco.Core/Configuration/Models/NuCacheSerializerType.cs @@ -1,14 +1,13 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// The serializer type that nucache uses to persist documents in the database. +/// +public enum NuCacheSerializerType { - /// - /// The serializer type that nucache uses to persist documents in the database. - /// - public enum NuCacheSerializerType - { - MessagePack = 1, // Default - JSON = 2 - } + MessagePack = 1, // Default + JSON = 2 } diff --git a/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs b/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs index ee41fc32d395..b88dbb5d0dce 100644 --- a/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs @@ -3,41 +3,41 @@ using System.ComponentModel; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for NuCache settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigNuCache)] +public class NuCacheSettings { + internal const string StaticNuCacheSerializerType = "MessagePack"; + internal const int StaticSqlPageSize = 1000; + internal const int StaticKitBatchSize = 1; + + /// + /// Gets or sets a value defining the BTree block size. + /// + public int? BTreeBlockSize { get; set; } + + /// + /// The serializer type that nucache uses to persist documents in the database. + /// + [DefaultValue(StaticNuCacheSerializerType)] + public NuCacheSerializerType NuCacheSerializerType { get; set; } = + Enum.Parse(StaticNuCacheSerializerType); + + /// + /// The paging size to use for nucache SQL queries. + /// + [DefaultValue(StaticSqlPageSize)] + public int SqlPageSize { get; set; } = StaticSqlPageSize; + /// - /// Typed configuration options for NuCache settings. + /// The size to use for nucache Kit batches. Higher value means more content loaded into memory at a time. /// - [UmbracoOptions(Constants.Configuration.ConfigNuCache)] - public class NuCacheSettings - { - internal const string StaticNuCacheSerializerType = "MessagePack"; - internal const int StaticSqlPageSize = 1000; - internal const int StaticKitBatchSize = 1; - - /// - /// Gets or sets a value defining the BTree block size. - /// - public int? BTreeBlockSize { get; set; } - - /// - /// The serializer type that nucache uses to persist documents in the database. - /// - [DefaultValue(StaticNuCacheSerializerType)] - public NuCacheSerializerType NuCacheSerializerType { get; set; } = Enum.Parse(StaticNuCacheSerializerType); - - /// - /// The paging size to use for nucache SQL queries. - /// - [DefaultValue(StaticSqlPageSize)] - public int SqlPageSize { get; set; } = StaticSqlPageSize; - - /// - /// The size to use for nucache Kit batches. Higher value means more content loaded into memory at a time. - /// - [DefaultValue(StaticKitBatchSize)] - public int KitBatchSize { get; set; } = StaticKitBatchSize; - - public bool UnPublishedContentCompression { get; set; } = false; - } + [DefaultValue(StaticKitBatchSize)] + public int KitBatchSize { get; set; } = StaticKitBatchSize; + + public bool UnPublishedContentCompression { get; set; } = false; } diff --git a/src/Umbraco.Core/Configuration/Models/PackageMigrationSettings.cs b/src/Umbraco.Core/Configuration/Models/PackageMigrationSettings.cs index 27968fdcd2ad..ee48d5a64258 100644 --- a/src/Umbraco.Core/Configuration/Models/PackageMigrationSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/PackageMigrationSettings.cs @@ -3,38 +3,41 @@ using System.ComponentModel; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for package migration settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigPackageMigration)] +public class PackageMigrationSettings { + private const bool StaticRunSchemaAndContentMigrations = true; + private const bool StaticAllowComponentOverrideOfRunSchemaAndContentMigrations = true; + /// - /// Typed configuration options for package migration settings. + /// Gets or sets a value indicating whether package migration steps that install schema and content should run. /// - [UmbracoOptions(Constants.Configuration.ConfigPackageMigration)] - public class PackageMigrationSettings - { - private const bool StaticRunSchemaAndContentMigrations = true; - private const bool StaticAllowComponentOverrideOfRunSchemaAndContentMigrations = true; - - /// - /// Gets or sets a value indicating whether package migration steps that install schema and content should run. - /// - /// - /// By default this is true and schema and content defined in a package migration are installed. - /// Using configuration, administrators can optionally switch this off in certain environments. - /// Deployment tools such as Umbraco Deploy can also configure this option to run or not run these migration - /// steps as is appropriate for normal use of the tool. - /// - [DefaultValue(StaticRunSchemaAndContentMigrations)] - public bool RunSchemaAndContentMigrations { get; set; } = StaticRunSchemaAndContentMigrations; + /// + /// By default this is true and schema and content defined in a package migration are installed. + /// Using configuration, administrators can optionally switch this off in certain environments. + /// Deployment tools such as Umbraco Deploy can also configure this option to run or not run these migration + /// steps as is appropriate for normal use of the tool. + /// + [DefaultValue(StaticRunSchemaAndContentMigrations)] + public bool RunSchemaAndContentMigrations { get; set; } = StaticRunSchemaAndContentMigrations; - /// - /// Gets or sets a value indicating whether components can override the configured value for . - /// - /// - /// By default this is true and components can override the configured setting for . - /// If an administrator wants explicit control over which environments migration steps installing schema and content can run, - /// they can set this to false. Components should respect this and not override the configuration. - /// - [DefaultValue(StaticAllowComponentOverrideOfRunSchemaAndContentMigrations)] - public bool AllowComponentOverrideOfRunSchemaAndContentMigrations { get; set; } = StaticAllowComponentOverrideOfRunSchemaAndContentMigrations; - } + /// + /// Gets or sets a value indicating whether components can override the configured value for + /// . + /// + /// + /// By default this is true and components can override the configured setting for + /// . + /// If an administrator wants explicit control over which environments migration steps installing schema and content + /// can run, + /// they can set this to false. Components should respect this and not override the configuration. + /// + [DefaultValue(StaticAllowComponentOverrideOfRunSchemaAndContentMigrations)] + public bool AllowComponentOverrideOfRunSchemaAndContentMigrations { get; set; } = + StaticAllowComponentOverrideOfRunSchemaAndContentMigrations; } diff --git a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs index 45a9bc98ed43..f8438005f701 100644 --- a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs @@ -1,90 +1,75 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using System.ComponentModel; -using System.ComponentModel.DataAnnotations; using Umbraco.Cms.Core.Configuration.UmbracoSettings; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for request handler settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigRequestHandler)] +public class RequestHandlerSettings { - /// - /// Typed configuration options for request handler settings. - /// - [UmbracoOptions(Constants.Configuration.ConfigRequestHandler)] - public class RequestHandlerSettings - { - internal const bool StaticAddTrailingSlash = true; - internal const string StaticConvertUrlsToAscii = "try"; - internal const bool StaticEnableDefaultCharReplacements = true; + internal const bool StaticAddTrailingSlash = true; + internal const string StaticConvertUrlsToAscii = "try"; + internal const bool StaticEnableDefaultCharReplacements = true; - internal static readonly Umbraco.Cms.Core.Configuration.Models.CharItem[] DefaultCharCollection = - { - new () { Char = " ", Replacement = "-" }, - new () { Char = "\"", Replacement = string.Empty }, - new () { Char = "'", Replacement = string.Empty }, - new () { Char = "%", Replacement = string.Empty }, - new () { Char = ".", Replacement = string.Empty }, - new () { Char = ";", Replacement = string.Empty }, - new () { Char = "/", Replacement = string.Empty }, - new () { Char = "\\", Replacement = string.Empty }, - new () { Char = ":", Replacement = string.Empty }, - new () { Char = "#", Replacement = string.Empty }, - new () { Char = "+", Replacement = "plus" }, - new () { Char = "*", Replacement = "star" }, - new () { Char = "&", Replacement = string.Empty }, - new () { Char = "?", Replacement = string.Empty }, - new () { Char = "æ", Replacement = "ae" }, - new () { Char = "ä", Replacement = "ae" }, - new () { Char = "ø", Replacement = "oe" }, - new () { Char = "ö", Replacement = "oe" }, - new () { Char = "å", Replacement = "aa" }, - new () { Char = "ü", Replacement = "ue" }, - new () { Char = "ß", Replacement = "ss" }, - new () { Char = "|", Replacement = "-" }, - new () { Char = "<", Replacement = string.Empty }, - new () { Char = ">", Replacement = string.Empty } - }; + internal static readonly CharItem[] DefaultCharCollection = + { + new() {Char = " ", Replacement = "-"}, new() {Char = "\"", Replacement = string.Empty}, + new() {Char = "'", Replacement = string.Empty}, new() {Char = "%", Replacement = string.Empty}, + new() {Char = ".", Replacement = string.Empty}, new() {Char = ";", Replacement = string.Empty}, + new() {Char = "/", Replacement = string.Empty}, new() {Char = "\\", Replacement = string.Empty}, + new() {Char = ":", Replacement = string.Empty}, new() {Char = "#", Replacement = string.Empty}, + new() {Char = "+", Replacement = "plus"}, new() {Char = "*", Replacement = "star"}, + new() {Char = "&", Replacement = string.Empty}, new() {Char = "?", Replacement = string.Empty}, + new() {Char = "æ", Replacement = "ae"}, new() {Char = "ä", Replacement = "ae"}, + new() {Char = "ø", Replacement = "oe"}, new() {Char = "ö", Replacement = "oe"}, + new() {Char = "å", Replacement = "aa"}, new() {Char = "ü", Replacement = "ue"}, + new() {Char = "ß", Replacement = "ss"}, new() {Char = "|", Replacement = "-"}, + new() {Char = "<", Replacement = string.Empty}, new() {Char = ">", Replacement = string.Empty} + }; - /// - /// Gets or sets a value indicating whether to add a trailing slash to URLs. - /// - [DefaultValue(StaticAddTrailingSlash)] - public bool AddTrailingSlash { get; set; } = StaticAddTrailingSlash; + /// + /// Gets or sets a value indicating whether to add a trailing slash to URLs. + /// + [DefaultValue(StaticAddTrailingSlash)] + public bool AddTrailingSlash { get; set; } = StaticAddTrailingSlash; - /// - /// Gets or sets a value indicating whether to convert URLs to ASCII (valid values: "true", "try" or "false"). - /// - [DefaultValue(StaticConvertUrlsToAscii)] - public string ConvertUrlsToAscii { get; set; } = StaticConvertUrlsToAscii; + /// + /// Gets or sets a value indicating whether to convert URLs to ASCII (valid values: "true", "try" or "false"). + /// + [DefaultValue(StaticConvertUrlsToAscii)] + public string ConvertUrlsToAscii { get; set; } = StaticConvertUrlsToAscii; - /// - /// Gets a value indicating whether URLs should be converted to ASCII. - /// - public bool ShouldConvertUrlsToAscii => ConvertUrlsToAscii.InvariantEquals("true"); + /// + /// Gets a value indicating whether URLs should be converted to ASCII. + /// + public bool ShouldConvertUrlsToAscii => ConvertUrlsToAscii.InvariantEquals("true"); - /// - /// Gets a value indicating whether URLs should be tried to be converted to ASCII. - /// - public bool ShouldTryConvertUrlsToAscii => ConvertUrlsToAscii.InvariantEquals("try"); + /// + /// Gets a value indicating whether URLs should be tried to be converted to ASCII. + /// + public bool ShouldTryConvertUrlsToAscii => ConvertUrlsToAscii.InvariantEquals("try"); - /// - /// Disable all default character replacements - /// - [DefaultValue(StaticEnableDefaultCharReplacements)] - public bool EnableDefaultCharReplacements { get; set; } = StaticEnableDefaultCharReplacements; + /// + /// Disable all default character replacements + /// + [DefaultValue(StaticEnableDefaultCharReplacements)] + public bool EnableDefaultCharReplacements { get; set; } = StaticEnableDefaultCharReplacements; - /// - /// Add additional character replacements, or override defaults - /// - [Obsolete("Use the GetCharReplacements extension method in the Umbraco.Extensions namespace instead. Scheduled for removal in V11")] - public IEnumerable CharCollection { get; set; } = DefaultCharCollection; + /// + /// Add additional character replacements, or override defaults + /// + [Obsolete( + "Use the GetCharReplacements extension method in the Umbraco.Extensions namespace instead. Scheduled for removal in V11")] + public IEnumerable CharCollection { get; set; } = DefaultCharCollection; - /// - /// Add additional character replacements, or override defaults - /// - public IEnumerable? UserDefinedCharCollection { get; set; } - } + /// + /// Add additional character replacements, or override defaults + /// + public IEnumerable? UserDefinedCharCollection { get; set; } } diff --git a/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs b/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs index cd82376c57d0..3fe9821918ba 100644 --- a/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs @@ -1,111 +1,154 @@ -using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using Umbraco.Cms.Core.Models.ContentEditing; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +[UmbracoOptions(Constants.Configuration.ConfigRichTextEditor)] +public class RichTextEditorSettings { - [UmbracoOptions(Constants.Configuration.ConfigRichTextEditor)] - public class RichTextEditorSettings - { - internal const string StaticValidElements = "+a[id|style|rel|data-id|data-udi|rev|charset|hreflang|dir|lang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],-strong/-b[class|style],-em/-i[class|style],-strike[class|style],-u[class|style],#p[id|style|dir|class|align],-ol[class|reversed|start|style|type],-ul[class|style],-li[class|style],br[class],img[id|dir|lang|longdesc|usemap|style|class|src|onmouseover|onmouseout|border|alt=|title|hspace|vspace|width|height|align|umbracoorgwidth|umbracoorgheight|onresize|onresizestart|onresizeend|rel|data-id],-sub[style|class],-sup[style|class],-blockquote[dir|style|class],-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|style|dir|id|lang|bgcolor|background|bordercolor],-tr[id|lang|dir|class|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor],tbody[id|class],thead[id|class],tfoot[id|class],#td[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor|scope],-th[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|scope],caption[id|lang|dir|class|style],-div[id|dir|class|align|style],-span[class|align|style],-pre[class|align|style],address[class|align|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align|style],hr[class|style],small[class|style],dd[id|class|title|style|dir|lang],dl[id|class|title|style|dir|lang],dt[id|class|title|style|dir|lang],object[class|id|width|height|codebase|*],param[name|value|_value|class],embed[type|width|height|src|class|*],map[name|class],area[shape|coords|href|alt|target|class],bdo[class],button[class],iframe[*],figure,figcaption"; - internal const string StaticInvalidElements = "font"; + internal const string StaticValidElements = + "+a[id|style|rel|data-id|data-udi|rev|charset|hreflang|dir|lang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],-strong/-b[class|style],-em/-i[class|style],-strike[class|style],-u[class|style],#p[id|style|dir|class|align],-ol[class|reversed|start|style|type],-ul[class|style],-li[class|style],br[class],img[id|dir|lang|longdesc|usemap|style|class|src|onmouseover|onmouseout|border|alt=|title|hspace|vspace|width|height|align|umbracoorgwidth|umbracoorgheight|onresize|onresizestart|onresizeend|rel|data-id],-sub[style|class],-sup[style|class],-blockquote[dir|style|class],-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|style|dir|id|lang|bgcolor|background|bordercolor],-tr[id|lang|dir|class|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor],tbody[id|class],thead[id|class],tfoot[id|class],#td[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor|scope],-th[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|scope],caption[id|lang|dir|class|style],-div[id|dir|class|align|style],-span[class|align|style],-pre[class|align|style],address[class|align|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align|style],hr[class|style],small[class|style],dd[id|class|title|style|dir|lang],dl[id|class|title|style|dir|lang],dt[id|class|title|style|dir|lang],object[class|id|width|height|codebase|*],param[name|value|_value|class],embed[type|width|height|src|class|*],map[name|class],area[shape|coords|href|alt|target|class],bdo[class],button[class],iframe[*],figure,figcaption"; - private static readonly string[] s_default_plugins = new[] - { - "paste", - "anchor", - "charmap", - "table", - "lists", - "advlist", - "hr", - "autolink", - "directionality", - "tabfocus", - "searchreplace" - }; - private static readonly RichTextEditorCommand[] s_default_commands = new [] - { - new RichTextEditorCommand(){Alias = "ace" , Name = "Source code editor" , Mode = RichTextEditorCommandMode.Insert}, - new RichTextEditorCommand(){Alias = "removeformat" , Name = "Remove format" , Mode = RichTextEditorCommandMode.Selection}, - new RichTextEditorCommand(){Alias = "undo" , Name = "Undo" , Mode = RichTextEditorCommandMode.Insert}, - new RichTextEditorCommand(){Alias = "redo" , Name = "Redo" , Mode = RichTextEditorCommandMode.Insert}, - new RichTextEditorCommand(){Alias = "cut" , Name = "Cut" , Mode = RichTextEditorCommandMode.Selection}, - new RichTextEditorCommand(){Alias = "copy" , Name = "Copy" , Mode = RichTextEditorCommandMode.Selection}, - new RichTextEditorCommand(){Alias = "paste" , Name = "Paste" , Mode = RichTextEditorCommandMode.All}, - new RichTextEditorCommand(){Alias = "styleselect" , Name = "Style select" , Mode = RichTextEditorCommandMode.All}, - new RichTextEditorCommand(){Alias = "bold" , Name = "Bold" , Mode = RichTextEditorCommandMode.Selection}, - new RichTextEditorCommand(){Alias = "italic" , Name = "Italic" , Mode = RichTextEditorCommandMode.Selection}, - new RichTextEditorCommand(){Alias = "underline" , Name = "Underline" , Mode = RichTextEditorCommandMode.Selection}, - new RichTextEditorCommand(){Alias = "strikethrough" , Name = "Strikethrough" , Mode = RichTextEditorCommandMode.Selection}, - new RichTextEditorCommand(){Alias = "alignleft" , Name = "Justify left" , Mode = RichTextEditorCommandMode.Selection}, - new RichTextEditorCommand(){Alias = "aligncenter" , Name = "Justify center" , Mode = RichTextEditorCommandMode.Selection}, - new RichTextEditorCommand(){Alias = "alignright" , Name = "Justify right" , Mode = RichTextEditorCommandMode.Selection}, - new RichTextEditorCommand(){Alias = "alignjustify" , Name = "Justify full" , Mode = RichTextEditorCommandMode.Selection}, - new RichTextEditorCommand(){Alias = "bullist" , Name = "Bullet list" , Mode = RichTextEditorCommandMode.All}, - new RichTextEditorCommand(){Alias = "numlist" , Name = "Numbered list" , Mode = RichTextEditorCommandMode.All}, - new RichTextEditorCommand(){Alias = "outdent" , Name = "Decrease indent" , Mode = RichTextEditorCommandMode.All}, - new RichTextEditorCommand(){Alias = "indent" , Name = "Increase indent" , Mode = RichTextEditorCommandMode.All}, - new RichTextEditorCommand(){Alias = "link" , Name = "Insert/edit link" , Mode = RichTextEditorCommandMode.All}, - new RichTextEditorCommand(){Alias = "unlink" , Name = "Remove link" , Mode = RichTextEditorCommandMode.Selection}, - new RichTextEditorCommand(){Alias = "anchor" , Name = "Anchor" , Mode = RichTextEditorCommandMode.Selection}, - new RichTextEditorCommand(){Alias = "umbmediapicker" , Name = "Image" , Mode = RichTextEditorCommandMode.Insert}, - new RichTextEditorCommand(){Alias = "umbmacro" , Name = "Macro" , Mode = RichTextEditorCommandMode.All}, - new RichTextEditorCommand(){Alias = "table" , Name = "Table" , Mode = RichTextEditorCommandMode.Insert}, - new RichTextEditorCommand(){Alias = "umbembeddialog" , Name = "Embed" , Mode = RichTextEditorCommandMode.Insert}, - new RichTextEditorCommand(){Alias = "hr" , Name = "Horizontal rule" , Mode = RichTextEditorCommandMode.Insert}, - new RichTextEditorCommand(){Alias = "subscript" , Name = "Subscript" , Mode = RichTextEditorCommandMode.Selection}, - new RichTextEditorCommand(){Alias = "superscript" , Name = "Superscript" , Mode = RichTextEditorCommandMode.Selection}, - new RichTextEditorCommand(){Alias = "charmap" , Name = "Character map" , Mode = RichTextEditorCommandMode.Insert}, - new RichTextEditorCommand(){Alias = "rtl" , Name = "Right to left" , Mode = RichTextEditorCommandMode.Selection}, - new RichTextEditorCommand(){Alias = "ltr" , Name = "Left to right" , Mode = RichTextEditorCommandMode.Selection}, - }; + internal const string StaticInvalidElements = "font"; + + private static readonly string[] s_default_plugins = + { + "paste", "anchor", "charmap", "table", "lists", "advlist", "hr", "autolink", "directionality", "tabfocus", + "searchreplace" + }; - private static readonly IDictionary s_default_custom_config = new Dictionary() + private static readonly RichTextEditorCommand[] s_default_commands = + { + new RichTextEditorCommand + { + Alias = "ace", Name = "Source code editor", Mode = RichTextEditorCommandMode.Insert + }, + new RichTextEditorCommand + { + Alias = "removeformat", Name = "Remove format", Mode = RichTextEditorCommandMode.Selection + }, + new RichTextEditorCommand {Alias = "undo", Name = "Undo", Mode = RichTextEditorCommandMode.Insert}, + new RichTextEditorCommand {Alias = "redo", Name = "Redo", Mode = RichTextEditorCommandMode.Insert}, + new RichTextEditorCommand {Alias = "cut", Name = "Cut", Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand {Alias = "copy", Name = "Copy", Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand {Alias = "paste", Name = "Paste", Mode = RichTextEditorCommandMode.All}, + new RichTextEditorCommand + { + Alias = "styleselect", Name = "Style select", Mode = RichTextEditorCommandMode.All + }, + new RichTextEditorCommand {Alias = "bold", Name = "Bold", Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand {Alias = "italic", Name = "Italic", Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand + { + Alias = "underline", Name = "Underline", Mode = RichTextEditorCommandMode.Selection + }, + new RichTextEditorCommand + { + Alias = "strikethrough", Name = "Strikethrough", Mode = RichTextEditorCommandMode.Selection + }, + new RichTextEditorCommand + { + Alias = "alignleft", Name = "Justify left", Mode = RichTextEditorCommandMode.Selection + }, + new RichTextEditorCommand + { + Alias = "aligncenter", Name = "Justify center", Mode = RichTextEditorCommandMode.Selection + }, + new RichTextEditorCommand + { + Alias = "alignright", Name = "Justify right", Mode = RichTextEditorCommandMode.Selection + }, + new RichTextEditorCommand + { + Alias = "alignjustify", Name = "Justify full", Mode = RichTextEditorCommandMode.Selection + }, + new RichTextEditorCommand {Alias = "bullist", Name = "Bullet list", Mode = RichTextEditorCommandMode.All}, + new RichTextEditorCommand {Alias = "numlist", Name = "Numbered list", Mode = RichTextEditorCommandMode.All}, + new RichTextEditorCommand { - ["entity_encoding"] = "raw" - }; + Alias = "outdent", Name = "Decrease indent", Mode = RichTextEditorCommandMode.All + }, + new RichTextEditorCommand + { + Alias = "indent", Name = "Increase indent", Mode = RichTextEditorCommandMode.All + }, + new RichTextEditorCommand {Alias = "link", Name = "Insert/edit link", Mode = RichTextEditorCommandMode.All}, + new RichTextEditorCommand + { + Alias = "unlink", Name = "Remove link", Mode = RichTextEditorCommandMode.Selection + }, + new RichTextEditorCommand {Alias = "anchor", Name = "Anchor", Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand + { + Alias = "umbmediapicker", Name = "Image", Mode = RichTextEditorCommandMode.Insert + }, + new RichTextEditorCommand {Alias = "umbmacro", Name = "Macro", Mode = RichTextEditorCommandMode.All}, + new RichTextEditorCommand {Alias = "table", Name = "Table", Mode = RichTextEditorCommandMode.Insert}, + new RichTextEditorCommand + { + Alias = "umbembeddialog", Name = "Embed", Mode = RichTextEditorCommandMode.Insert + }, + new RichTextEditorCommand {Alias = "hr", Name = "Horizontal rule", Mode = RichTextEditorCommandMode.Insert}, + new RichTextEditorCommand + { + Alias = "subscript", Name = "Subscript", Mode = RichTextEditorCommandMode.Selection + }, + new RichTextEditorCommand + { + Alias = "superscript", Name = "Superscript", Mode = RichTextEditorCommandMode.Selection + }, + new RichTextEditorCommand + { + Alias = "charmap", Name = "Character map", Mode = RichTextEditorCommandMode.Insert + }, + new RichTextEditorCommand + { + Alias = "rtl", Name = "Right to left", Mode = RichTextEditorCommandMode.Selection + }, + new RichTextEditorCommand + { + Alias = "ltr", Name = "Left to right", Mode = RichTextEditorCommandMode.Selection + } + }; - /// - /// HTML RichText Editor TinyMCE Commands - /// - /// WB-TODO Custom Array of objects - public RichTextEditorCommand[] Commands { get; set; } = s_default_commands; + private static readonly IDictionary s_default_custom_config = + new Dictionary {["entity_encoding"] = "raw"}; - /// - /// HTML RichText Editor TinyMCE Plugins - /// - public string[] Plugins { get; set; } = s_default_plugins; + /// + /// HTML RichText Editor TinyMCE Commands + /// + /// WB-TODO Custom Array of objects + public RichTextEditorCommand[] Commands { get; set; } = s_default_commands; - /// - /// HTML RichText Editor TinyMCE Custom Config - /// - /// WB-TODO Custom Dictionary - public IDictionary CustomConfig { get; set; } = s_default_custom_config; + /// + /// HTML RichText Editor TinyMCE Plugins + /// + public string[] Plugins { get; set; } = s_default_plugins; - /// - /// - /// - [DefaultValue(StaticValidElements)] - public string ValidElements { get; set; } = StaticValidElements; + /// + /// HTML RichText Editor TinyMCE Custom Config + /// + /// WB-TODO Custom Dictionary + public IDictionary CustomConfig { get; set; } = s_default_custom_config; - /// - /// Invalid HTML elements for RichText Editor - /// - [DefaultValue(StaticInvalidElements)] - public string InvalidElements { get; set; } = StaticInvalidElements; + /// + /// + [DefaultValue(StaticValidElements)] + public string ValidElements { get; set; } = StaticValidElements; - public class RichTextEditorCommand - { - [Required] - public string Alias { get; set; } = null!; + /// + /// Invalid HTML elements for RichText Editor + /// + [DefaultValue(StaticInvalidElements)] + public string InvalidElements { get; set; } = StaticInvalidElements; - [Required] - public string Name { get; set; } = null!; + public class RichTextEditorCommand + { + [Required] public string Alias { get; set; } = null!; - [Required] - public RichTextEditorCommandMode Mode { get; set; } - } + [Required] public string Name { get; set; } = null!; + + [Required] public RichTextEditorCommandMode Mode { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/RuntimeMinificationCacheBuster.cs b/src/Umbraco.Core/Configuration/Models/RuntimeMinificationCacheBuster.cs index db1e1526e583..f3491543b2a6 100644 --- a/src/Umbraco.Core/Configuration/Models/RuntimeMinificationCacheBuster.cs +++ b/src/Umbraco.Core/Configuration/Models/RuntimeMinificationCacheBuster.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +public enum RuntimeMinificationCacheBuster { - public enum RuntimeMinificationCacheBuster - { - Version, - AppDomain, - Timestamp - } + Version, + AppDomain, + Timestamp } diff --git a/src/Umbraco.Core/Configuration/Models/RuntimeMinificationSettings.cs b/src/Umbraco.Core/Configuration/Models/RuntimeMinificationSettings.cs index 643e83bcac13..09c55c784b9d 100644 --- a/src/Umbraco.Core/Configuration/Models/RuntimeMinificationSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RuntimeMinificationSettings.cs @@ -1,30 +1,30 @@ using System.ComponentModel; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +[UmbracoOptions(Constants.Configuration.ConfigRuntimeMinification)] +public class RuntimeMinificationSettings { - [UmbracoOptions(Constants.Configuration.ConfigRuntimeMinification)] - public class RuntimeMinificationSettings - { - internal const bool StaticUseInMemoryCache = false; - internal const string StaticCacheBuster = "Version"; - internal const string? StaticVersion = null; + internal const bool StaticUseInMemoryCache = false; + internal const string StaticCacheBuster = "Version"; + internal const string? StaticVersion = null; - /// - /// Use in memory cache - /// - [DefaultValue(StaticUseInMemoryCache)] - public bool UseInMemoryCache { get; set; } = StaticUseInMemoryCache; + /// + /// Use in memory cache + /// + [DefaultValue(StaticUseInMemoryCache)] + public bool UseInMemoryCache { get; set; } = StaticUseInMemoryCache; - /// - /// The cache buster type to use - /// - [DefaultValue(StaticCacheBuster)] - public RuntimeMinificationCacheBuster CacheBuster { get; set; } = Enum.Parse(StaticCacheBuster); + /// + /// The cache buster type to use + /// + [DefaultValue(StaticCacheBuster)] + public RuntimeMinificationCacheBuster CacheBuster { get; set; } = + Enum.Parse(StaticCacheBuster); - /// - /// The unique version string used if CacheBuster is 'Version'. - /// - [DefaultValue(StaticVersion)] - public string? Version { get; set; } = StaticVersion; - } + /// + /// The unique version string used if CacheBuster is 'Version'. + /// + [DefaultValue(StaticVersion)] + public string? Version { get; set; } = StaticVersion; } diff --git a/src/Umbraco.Core/Configuration/Models/RuntimeSettings.cs b/src/Umbraco.Core/Configuration/Models/RuntimeSettings.cs index ef67d4010264..ac4e51a1c26a 100644 --- a/src/Umbraco.Core/Configuration/Models/RuntimeSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RuntimeSettings.cs @@ -1,22 +1,21 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for runtime settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigRuntime)] +public class RuntimeSettings { /// - /// Typed configuration options for runtime settings. + /// Gets or sets a value for the maximum query string length. /// - [UmbracoOptions(Constants.Configuration.ConfigRuntime)] - public class RuntimeSettings - { - /// - /// Gets or sets a value for the maximum query string length. - /// - public int? MaxQueryStringLength { get; set; } + public int? MaxQueryStringLength { get; set; } - /// - /// Gets or sets a value for the maximum request length in kb. - /// - public int? MaxRequestLength { get; set; } - } + /// + /// Gets or sets a value for the maximum request length in kb. + /// + public int? MaxRequestLength { get; set; } } diff --git a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs index 5ec94381b4c2..241b796d16fe 100644 --- a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs +++ b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs @@ -3,81 +3,85 @@ using System.ComponentModel; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for security settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigSecurity)] +public class SecuritySettings { + internal const bool StaticMemberBypassTwoFactorForExternalLogins = true; + internal const bool StaticUserBypassTwoFactorForExternalLogins = true; + internal const bool StaticKeepUserLoggedIn = false; + internal const bool StaticHideDisabledUsersInBackOffice = false; + internal const bool StaticAllowPasswordReset = true; + internal const string StaticAuthCookieName = "UMB_UCONTEXT"; + + internal const string StaticAllowedUserNameCharacters = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+\\"; + /// - /// Typed configuration options for security settings. + /// Gets or sets a value indicating whether to keep the user logged in. /// - [UmbracoOptions(Constants.Configuration.ConfigSecurity)] - public class SecuritySettings - { - internal const bool StaticMemberBypassTwoFactorForExternalLogins = true; - internal const bool StaticUserBypassTwoFactorForExternalLogins = true; - internal const bool StaticKeepUserLoggedIn = false; - internal const bool StaticHideDisabledUsersInBackOffice = false; - internal const bool StaticAllowPasswordReset = true; - internal const string StaticAuthCookieName = "UMB_UCONTEXT"; - internal const string StaticAllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+\\"; + [DefaultValue(StaticKeepUserLoggedIn)] + public bool KeepUserLoggedIn { get; set; } = StaticKeepUserLoggedIn; - /// - /// Gets or sets a value indicating whether to keep the user logged in. - /// - [DefaultValue(StaticKeepUserLoggedIn)] - public bool KeepUserLoggedIn { get; set; } = StaticKeepUserLoggedIn; + /// + /// Gets or sets a value indicating whether to hide disabled users in the back-office. + /// + [DefaultValue(StaticHideDisabledUsersInBackOffice)] + public bool HideDisabledUsersInBackOffice { get; set; } = StaticHideDisabledUsersInBackOffice; - /// - /// Gets or sets a value indicating whether to hide disabled users in the back-office. - /// - [DefaultValue(StaticHideDisabledUsersInBackOffice)] - public bool HideDisabledUsersInBackOffice { get; set; } = StaticHideDisabledUsersInBackOffice; + /// + /// Gets or sets a value indicating whether to allow user password reset. + /// + [DefaultValue(StaticAllowPasswordReset)] + public bool AllowPasswordReset { get; set; } = StaticAllowPasswordReset; - /// - /// Gets or sets a value indicating whether to allow user password reset. - /// - [DefaultValue(StaticAllowPasswordReset)] - public bool AllowPasswordReset { get; set; } = StaticAllowPasswordReset; + /// + /// Gets or sets a value for the authorization cookie name. + /// + [DefaultValue(StaticAuthCookieName)] + public string AuthCookieName { get; set; } = StaticAuthCookieName; - /// - /// Gets or sets a value for the authorization cookie name. - /// - [DefaultValue(StaticAuthCookieName)] - public string AuthCookieName { get; set; } = StaticAuthCookieName; + /// + /// Gets or sets a value for the authorization cookie domain. + /// + public string? AuthCookieDomain { get; set; } - /// - /// Gets or sets a value for the authorization cookie domain. - /// - public string? AuthCookieDomain { get; set; } + /// + /// Gets or sets a value indicating whether the user's email address is to be considered as their username. + /// + public bool UsernameIsEmail { get; set; } = true; - /// - /// Gets or sets a value indicating whether the user's email address is to be considered as their username. - /// - public bool UsernameIsEmail { get; set; } = true; + /// + /// Gets or sets the set of allowed characters for a username + /// + [DefaultValue(StaticAllowedUserNameCharacters)] + public string AllowedUserNameCharacters { get; set; } = StaticAllowedUserNameCharacters; - /// - /// Gets or sets the set of allowed characters for a username - /// - [DefaultValue(StaticAllowedUserNameCharacters)] - public string AllowedUserNameCharacters { get; set; } = StaticAllowedUserNameCharacters; + /// + /// Gets or sets a value for the user password settings. + /// + public UserPasswordConfigurationSettings? UserPassword { get; set; } - /// - /// Gets or sets a value for the user password settings. - /// - public UserPasswordConfigurationSettings? UserPassword { get; set; } + /// + /// Gets or sets a value for the member password settings. + /// + public MemberPasswordConfigurationSettings? MemberPassword { get; set; } - /// - /// Gets or sets a value for the member password settings. - /// - public MemberPasswordConfigurationSettings? MemberPassword { get; set; } - /// - /// Gets or sets a value indicating whether to bypass the two factor requirement in Umbraco when using external login for members. Thereby rely on the External login and potential 2FA at that provider. - /// - [DefaultValue(StaticMemberBypassTwoFactorForExternalLogins)] - public bool MemberBypassTwoFactorForExternalLogins { get; set; } = StaticMemberBypassTwoFactorForExternalLogins; + /// + /// Gets or sets a value indicating whether to bypass the two factor requirement in Umbraco when using external login + /// for members. Thereby rely on the External login and potential 2FA at that provider. + /// + [DefaultValue(StaticMemberBypassTwoFactorForExternalLogins)] + public bool MemberBypassTwoFactorForExternalLogins { get; set; } = StaticMemberBypassTwoFactorForExternalLogins; - /// - /// Gets or sets a value indicating whether to bypass the two factor requirement in Umbraco when using external login for users. Thereby rely on the External login and potential 2FA at that provider. - /// - [DefaultValue(StaticUserBypassTwoFactorForExternalLogins)] - public bool UserBypassTwoFactorForExternalLogins { get; set; } = StaticUserBypassTwoFactorForExternalLogins; - } + /// + /// Gets or sets a value indicating whether to bypass the two factor requirement in Umbraco when using external login + /// for users. Thereby rely on the External login and potential 2FA at that provider. + /// + [DefaultValue(StaticUserBypassTwoFactorForExternalLogins)] + public bool UserBypassTwoFactorForExternalLogins { get; set; } = StaticUserBypassTwoFactorForExternalLogins; } diff --git a/src/Umbraco.Core/Configuration/Models/SmtpSettings.cs b/src/Umbraco.Core/Configuration/Models/SmtpSettings.cs index 54b9ad6c84fe..055c318cc5e9 100644 --- a/src/Umbraco.Core/Configuration/Models/SmtpSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/SmtpSettings.cs @@ -6,91 +6,95 @@ using System.Net.Mail; using Umbraco.Cms.Core.Configuration.Models.Validation; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Matches MailKit.Security.SecureSocketOptions and defined locally to avoid having to take +/// a dependency on this external library into Umbraco.Core. +/// +/// +public enum SecureSocketOptions { /// - /// Matches MailKit.Security.SecureSocketOptions and defined locally to avoid having to take - /// a dependency on this external library into Umbraco.Core. + /// No SSL or TLS encryption should be used. + /// + None = 0, + + /// + /// Allow the IMailService to decide which SSL or TLS options to use (default). If the server does not support SSL or + /// TLS, then the connection will continue without any encryption. + /// + Auto = 1, + + /// + /// The connection should use SSL or TLS encryption immediately. + /// + SslOnConnect = 2, + + /// + /// Elevates the connection to use TLS encryption immediately after reading the greeting and capabilities of the + /// server. If the server does not support the STARTTLS extension, then the connection will fail and a + /// NotSupportedException will be thrown. + /// + StartTls = 3, + + /// + /// Elevates the connection to use TLS encryption immediately after reading the greeting and capabilities of the + /// server, but only if the server supports the STARTTLS extension. + /// + StartTlsWhenAvailable = 4 +} + +/// +/// Typed configuration options for SMTP settings. +/// +public class SmtpSettings : ValidatableEntryBase +{ + internal const string StaticSecureSocketOptions = "Auto"; + internal const string StaticDeliveryMethod = "Network"; + + /// + /// Gets or sets a value for the SMTP from address to use for messages. + /// + [Required] + [EmailAddress] + public string From { get; set; } = null!; + + /// + /// Gets or sets a value for the SMTP host. + /// + public string? Host { get; set; } + + /// + /// Gets or sets a value for the SMTP port. + /// + public int Port { get; set; } + + /// + /// Gets or sets a value for the secure socket options. + /// + [DefaultValue(StaticSecureSocketOptions)] + public SecureSocketOptions SecureSocketOptions { get; set; } = + Enum.Parse(StaticSecureSocketOptions); + + /// + /// Gets or sets a value for the SMTP pick-up directory. + /// + public string? PickupDirectoryLocation { get; set; } + + /// + /// Gets or sets a value for the SMTP delivery method. + /// + [DefaultValue(StaticDeliveryMethod)] + public SmtpDeliveryMethod DeliveryMethod { get; set; } = Enum.Parse(StaticDeliveryMethod); + + /// + /// Gets or sets a value for the SMTP user name. /// - /// - public enum SecureSocketOptions - { - /// - /// No SSL or TLS encryption should be used. - /// - None = 0, - - /// - /// Allow the IMailService to decide which SSL or TLS options to use (default). If the server does not support SSL or TLS, then the connection will continue without any encryption. - /// - Auto = 1, - - /// - /// The connection should use SSL or TLS encryption immediately. - /// - SslOnConnect = 2, - - /// - /// Elevates the connection to use TLS encryption immediately after reading the greeting and capabilities of the server. If the server does not support the STARTTLS extension, then the connection will fail and a NotSupportedException will be thrown. - /// - StartTls = 3, - - /// - /// Elevates the connection to use TLS encryption immediately after reading the greeting and capabilities of the server, but only if the server supports the STARTTLS extension. - /// - StartTlsWhenAvailable = 4 - } + public string? Username { get; set; } /// - /// Typed configuration options for SMTP settings. + /// Gets or sets a value for the SMTP password. /// - public class SmtpSettings : ValidatableEntryBase - { - internal const string StaticSecureSocketOptions = "Auto"; - internal const string StaticDeliveryMethod = "Network"; - - /// - /// Gets or sets a value for the SMTP from address to use for messages. - /// - [Required] - [EmailAddress] - public string From { get; set; } = null!; - - /// - /// Gets or sets a value for the SMTP host. - /// - public string? Host { get; set; } - - /// - /// Gets or sets a value for the SMTP port. - /// - public int Port { get; set; } - - /// - /// Gets or sets a value for the secure socket options. - /// - [DefaultValue(StaticSecureSocketOptions)] - public SecureSocketOptions SecureSocketOptions { get; set; } = Enum.Parse(StaticSecureSocketOptions); - - /// - /// Gets or sets a value for the SMTP pick-up directory. - /// - public string? PickupDirectoryLocation { get; set; } - - /// - /// Gets or sets a value for the SMTP delivery method. - /// - [DefaultValue(StaticDeliveryMethod)] - public SmtpDeliveryMethod DeliveryMethod { get; set; } = Enum.Parse(StaticDeliveryMethod); - - /// - /// Gets or sets a value for the SMTP user name. - /// - public string? Username { get; set; } - - /// - /// Gets or sets a value for the SMTP password. - /// - public string? Password { get; set; } - } + public string? Password { get; set; } } diff --git a/src/Umbraco.Core/Configuration/Models/TourSettings.cs b/src/Umbraco.Core/Configuration/Models/TourSettings.cs index cdc54dfe1fbe..aaf2063c64c1 100644 --- a/src/Umbraco.Core/Configuration/Models/TourSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/TourSettings.cs @@ -3,20 +3,19 @@ using System.ComponentModel; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for tour settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigTours)] +public class TourSettings { + internal const bool StaticEnableTours = true; + /// - /// Typed configuration options for tour settings. + /// Gets or sets a value indicating whether back-office tours are enabled. /// - [UmbracoOptions(Constants.Configuration.ConfigTours)] - public class TourSettings - { - internal const bool StaticEnableTours = true; - - /// - /// Gets or sets a value indicating whether back-office tours are enabled. - /// - [DefaultValue(StaticEnableTours)] - public bool EnableTours { get; set; } = StaticEnableTours; - } + [DefaultValue(StaticEnableTours)] + public bool EnableTours { get; set; } = StaticEnableTours; } diff --git a/src/Umbraco.Core/Configuration/Models/TypeFinderSettings.cs b/src/Umbraco.Core/Configuration/Models/TypeFinderSettings.cs index 30ef3718f401..f281bbc31a36 100644 --- a/src/Umbraco.Core/Configuration/Models/TypeFinderSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/TypeFinderSettings.cs @@ -1,28 +1,25 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for type finder settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigTypeFinder)] +public class TypeFinderSettings { /// - /// Typed configuration options for type finder settings. + /// Gets or sets a value for the assemblies that accept load exceptions during type finder operations. /// - [UmbracoOptions(Constants.Configuration.ConfigTypeFinder)] - public class TypeFinderSettings - { - /// - /// Gets or sets a value for the assemblies that accept load exceptions during type finder operations. - /// - [Required] - public string AssembliesAcceptingLoadExceptions { get; set; } = null!; + [Required] + public string AssembliesAcceptingLoadExceptions { get; set; } = null!; - /// - /// By default the entry assemblies for scanning plugin types is the Umbraco DLLs. If you require - /// scanning for plugins based on different root referenced assemblies you can add the assembly name to this list. - /// - public IEnumerable? AdditionalEntryAssemblies { get; set; } - } + /// + /// By default the entry assemblies for scanning plugin types is the Umbraco DLLs. If you require + /// scanning for plugins based on different root referenced assemblies you can add the assembly name to this list. + /// + public IEnumerable? AdditionalEntryAssemblies { get; set; } } diff --git a/src/Umbraco.Core/Configuration/Models/UmbracoPluginSettings.cs b/src/Umbraco.Core/Configuration/Models/UmbracoPluginSettings.cs index d016e3547bfe..4beef98d9b49 100644 --- a/src/Umbraco.Core/Configuration/Models/UmbracoPluginSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/UmbracoPluginSettings.cs @@ -1,30 +1,27 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Configuration.Models; -namespace Umbraco.Cms.Core.Configuration.Models +/// +/// Typed configuration options for the plugins. +/// +[UmbracoOptions(Constants.Configuration.ConfigPlugins)] +public class UmbracoPluginSettings { /// - /// Typed configuration options for the plugins. + /// Gets or sets the allowed file extensions (including the period ".") that should be accessible from the browser. /// - [UmbracoOptions(Constants.Configuration.ConfigPlugins)] - public class UmbracoPluginSettings + /// WB-TODO + public ISet BrowsableFileExtensions { get; set; } = new HashSet(new[] { - /// - /// Gets or sets the allowed file extensions (including the period ".") that should be accessible from the browser. - /// - /// WB-TODO - public ISet BrowsableFileExtensions { get; set; } = new HashSet(new[] - { - ".html", // markup - ".css", // styles - ".js", // scripts - ".jpg", ".jpeg", ".gif", ".png", ".svg", // images - ".eot", ".ttf", ".woff", // fonts - ".xml", ".json", ".config", // configurations - ".lic", // license - ".map" // js map files - }); - } + ".html", // markup + ".css", // styles + ".js", // scripts + ".jpg", ".jpeg", ".gif", ".png", ".svg", // images + ".eot", ".ttf", ".woff", // fonts + ".xml", ".json", ".config", // configurations + ".lic", // license + ".map" // js map files + }); } diff --git a/src/Umbraco.Core/Configuration/Models/UnattendedSettings.cs b/src/Umbraco.Core/Configuration/Models/UnattendedSettings.cs index 08a4af566745..577fb9a2d9f7 100644 --- a/src/Umbraco.Core/Configuration/Models/UnattendedSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/UnattendedSettings.cs @@ -4,57 +4,58 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for unattended settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigUnattended)] +public class UnattendedSettings { + private const bool StaticInstallUnattended = false; + private const bool StaticUpgradeUnattended = false; + + /// + /// Gets or sets a value indicating whether unattended installs are enabled. + /// + /// + /// + /// By default, when a database connection string is configured and it is possible to connect to + /// the database, but the database is empty, the runtime enters the Install level. + /// If this option is set to true an unattended install will be performed and the runtime enters + /// the Run level. + /// + /// + [DefaultValue(StaticInstallUnattended)] + public bool InstallUnattended { get; set; } = StaticInstallUnattended; + + /// + /// Gets or sets a value indicating whether unattended upgrades are enabled. + /// + [DefaultValue(StaticUpgradeUnattended)] + public bool UpgradeUnattended { get; set; } = StaticUpgradeUnattended; + + /// + /// Gets or sets a value indicating whether unattended package migrations are enabled. + /// + /// + /// This is true by default. + /// + public bool PackageMigrationsUnattended { get; set; } = true; + + /// + /// Gets or sets a value to use for creating a user with a name for Unattended Installs + /// + public string? UnattendedUserName { get; set; } = null; + + /// + /// Gets or sets a value to use for creating a user with an email for Unattended Installs + /// + [EmailAddress] + public string? UnattendedUserEmail { get; set; } = null; + /// - /// Typed configuration options for unattended settings. + /// Gets or sets a value to use for creating a user with a password for Unattended Installs /// - [UmbracoOptions(Constants.Configuration.ConfigUnattended)] - public class UnattendedSettings - { - private const bool StaticInstallUnattended = false; - private const bool StaticUpgradeUnattended = false; - - /// - /// Gets or sets a value indicating whether unattended installs are enabled. - /// - /// - /// By default, when a database connection string is configured and it is possible to connect to - /// the database, but the database is empty, the runtime enters the Install level. - /// If this option is set to true an unattended install will be performed and the runtime enters - /// the Run level. - /// - [DefaultValue(StaticInstallUnattended)] - public bool InstallUnattended { get; set; } = StaticInstallUnattended; - - /// - /// Gets or sets a value indicating whether unattended upgrades are enabled. - /// - [DefaultValue(StaticUpgradeUnattended)] - public bool UpgradeUnattended { get; set; } = StaticUpgradeUnattended; - - /// - /// Gets or sets a value indicating whether unattended package migrations are enabled. - /// - /// - /// This is true by default. - /// - public bool PackageMigrationsUnattended { get; set; } = true; - - /// - /// Gets or sets a value to use for creating a user with a name for Unattended Installs - /// - public string? UnattendedUserName { get; set; } = null; - - /// - /// Gets or sets a value to use for creating a user with an email for Unattended Installs - /// - [EmailAddress] - public string? UnattendedUserEmail { get; set; } = null; - - /// - /// Gets or sets a value to use for creating a user with a password for Unattended Installs - /// - public string? UnattendedUserPassword { get; set; } = null; - } + public string? UnattendedUserPassword { get; set; } = null; } diff --git a/src/Umbraco.Core/Configuration/Models/UserPasswordConfigurationSettings.cs b/src/Umbraco.Core/Configuration/Models/UserPasswordConfigurationSettings.cs index b53e98f712aa..156f90419c48 100644 --- a/src/Umbraco.Core/Configuration/Models/UserPasswordConfigurationSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/UserPasswordConfigurationSettings.cs @@ -3,47 +3,46 @@ using System.ComponentModel; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for user password settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigUserPassword)] +public class UserPasswordConfigurationSettings : IPasswordConfiguration { - /// - /// Typed configuration options for user password settings. - /// - [UmbracoOptions(Constants.Configuration.ConfigUserPassword)] - public class UserPasswordConfigurationSettings : IPasswordConfiguration - { - internal const int StaticRequiredLength = 10; - internal const bool StaticRequireNonLetterOrDigit = false; - internal const bool StaticRequireDigit = false; - internal const bool StaticRequireLowercase = false; - internal const bool StaticRequireUppercase = false; - internal const int StaticMaxFailedAccessAttemptsBeforeLockout = 5; - - /// - [DefaultValue(StaticRequiredLength)] - public int RequiredLength { get; set; } = StaticRequiredLength; - - /// - [DefaultValue(StaticRequireNonLetterOrDigit)] - public bool RequireNonLetterOrDigit { get; set; } = StaticRequireNonLetterOrDigit; - - /// - [DefaultValue(StaticRequireDigit)] - public bool RequireDigit { get; set; } = StaticRequireDigit; - - /// - [DefaultValue(StaticRequireLowercase)] - public bool RequireLowercase { get; set; } = StaticRequireLowercase; - - /// - [DefaultValue(StaticRequireUppercase)] - public bool RequireUppercase { get; set; } = StaticRequireUppercase; - - /// - [DefaultValue(Constants.Security.AspNetCoreV3PasswordHashAlgorithmName)] - public string HashAlgorithmType { get; set; } = Constants.Security.AspNetCoreV3PasswordHashAlgorithmName; - - /// - [DefaultValue(StaticMaxFailedAccessAttemptsBeforeLockout)] - public int MaxFailedAccessAttemptsBeforeLockout { get; set; } = StaticMaxFailedAccessAttemptsBeforeLockout; - } + internal const int StaticRequiredLength = 10; + internal const bool StaticRequireNonLetterOrDigit = false; + internal const bool StaticRequireDigit = false; + internal const bool StaticRequireLowercase = false; + internal const bool StaticRequireUppercase = false; + internal const int StaticMaxFailedAccessAttemptsBeforeLockout = 5; + + /// + [DefaultValue(StaticRequiredLength)] + public int RequiredLength { get; set; } = StaticRequiredLength; + + /// + [DefaultValue(StaticRequireNonLetterOrDigit)] + public bool RequireNonLetterOrDigit { get; set; } = StaticRequireNonLetterOrDigit; + + /// + [DefaultValue(StaticRequireDigit)] + public bool RequireDigit { get; set; } = StaticRequireDigit; + + /// + [DefaultValue(StaticRequireLowercase)] + public bool RequireLowercase { get; set; } = StaticRequireLowercase; + + /// + [DefaultValue(StaticRequireUppercase)] + public bool RequireUppercase { get; set; } = StaticRequireUppercase; + + /// + [DefaultValue(Constants.Security.AspNetCoreV3PasswordHashAlgorithmName)] + public string HashAlgorithmType { get; set; } = Constants.Security.AspNetCoreV3PasswordHashAlgorithmName; + + /// + [DefaultValue(StaticMaxFailedAccessAttemptsBeforeLockout)] + public int MaxFailedAccessAttemptsBeforeLockout { get; set; } = StaticMaxFailedAccessAttemptsBeforeLockout; } diff --git a/src/Umbraco.Core/Configuration/Models/Validation/ConfigurationValidatorBase.cs b/src/Umbraco.Core/Configuration/Models/Validation/ConfigurationValidatorBase.cs index ca5d4d11e551..d293ee4b09ad 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/ConfigurationValidatorBase.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/ConfigurationValidatorBase.cs @@ -1,75 +1,76 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; -using System.Linq; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Configuration.Models.Validation +namespace Umbraco.Cms.Core.Configuration.Models.Validation; + +/// +/// Base class for configuration validators. +/// +public abstract class ConfigurationValidatorBase { /// - /// Base class for configuration validators. + /// Validates that a string is one of a set of valid values. /// - public abstract class ConfigurationValidatorBase + /// Configuration path from where the setting is found. + /// The value to check. + /// The set of valid values. + /// A message to output if the value does not match. + /// True if valid, false if not. + public bool ValidateStringIsOneOfValidValues(string configPath, string value, IEnumerable validValues, + out string message) { - /// - /// Validates that a string is one of a set of valid values. - /// - /// Configuration path from where the setting is found. - /// The value to check. - /// The set of valid values. - /// A message to output if the value does not match. - /// True if valid, false if not. - public bool ValidateStringIsOneOfValidValues(string configPath, string value, IEnumerable validValues, out string message) + if (!validValues.InvariantContains(value)) { - if (!validValues.InvariantContains(value)) - { - message = $"Configuration entry {configPath} contains an invalid value '{value}', it should be one of the following: '{string.Join(", ", validValues)}'."; - return false; - } - - message = string.Empty; - return true; + message = + $"Configuration entry {configPath} contains an invalid value '{value}', it should be one of the following: '{string.Join(", ", validValues)}'."; + return false; } - /// - /// Validates that a collection of objects are all valid based on their data annotations. - /// - /// Configuration path from where the setting is found. - /// The values to check. - /// Description of validation appended to message if validation fails. - /// A message to output if the value does not match. - /// True if valid, false if not. - public bool ValidateCollection(string configPath, IEnumerable values, string validationDescription, out string message) - { - if (values.Any(x => !x.IsValid())) - { - message = $"Configuration entry {configPath} contains one or more invalid values. {validationDescription}."; - return false; - } + message = string.Empty; + return true; + } - message = string.Empty; - return true; + /// + /// Validates that a collection of objects are all valid based on their data annotations. + /// + /// Configuration path from where the setting is found. + /// The values to check. + /// Description of validation appended to message if validation fails. + /// A message to output if the value does not match. + /// True if valid, false if not. + public bool ValidateCollection(string configPath, IEnumerable values, + string validationDescription, out string message) + { + if (values.Any(x => !x.IsValid())) + { + message = $"Configuration entry {configPath} contains one or more invalid values. {validationDescription}."; + return false; } - /// - /// Validates a configuration entry is valid if provided. - /// - /// Configuration path from where the setting is found. - /// The value to check. - /// Description of validation appended to message if validation fails. - /// A message to output if the value does not match. - /// True if valid, false if not. - public bool ValidateOptionalEntry(string configPath, ValidatableEntryBase? value, string validationDescription, out string message) - { - if (value != null && !value.IsValid()) - { - message = $"Configuration entry {configPath} contains one or more invalid values. {validationDescription}."; - return false; - } + message = string.Empty; + return true; + } - message = string.Empty; - return true; + /// + /// Validates a configuration entry is valid if provided. + /// + /// Configuration path from where the setting is found. + /// The value to check. + /// Description of validation appended to message if validation fails. + /// A message to output if the value does not match. + /// True if valid, false if not. + public bool ValidateOptionalEntry(string configPath, ValidatableEntryBase? value, string validationDescription, + out string message) + { + if (value != null && !value.IsValid()) + { + message = $"Configuration entry {configPath} contains one or more invalid values. {validationDescription}."; + return false; } + + message = string.Empty; + return true; } } diff --git a/src/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidator.cs b/src/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidator.cs index d21d6277bf4e..60527bf7b8c0 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidator.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidator.cs @@ -1,36 +1,41 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Microsoft.Extensions.Options; -namespace Umbraco.Cms.Core.Configuration.Models.Validation +namespace Umbraco.Cms.Core.Configuration.Models.Validation; + +/// +/// Validator for configuration representated as . +/// +public class ContentSettingsValidator : ConfigurationValidatorBase, IValidateOptions { - /// - /// Validator for configuration representated as . - /// - public class ContentSettingsValidator : ConfigurationValidatorBase, IValidateOptions + /// + public ValidateOptionsResult Validate(string name, ContentSettings options) { - /// - public ValidateOptionsResult Validate(string name, ContentSettings options) + if (!ValidateError404Collection(options.Error404Collection, out var message)) { - if (!ValidateError404Collection(options.Error404Collection, out string message)) - { - return ValidateOptionsResult.Fail(message); - } - - if (!ValidateAutoFillImageProperties(options.Imaging.AutoFillImageProperties, out message)) - { - return ValidateOptionsResult.Fail(message); - } - - return ValidateOptionsResult.Success; + return ValidateOptionsResult.Fail(message); } - private bool ValidateError404Collection(IEnumerable values, out string message) => - ValidateCollection($"{Constants.Configuration.ConfigContent}:{nameof(ContentSettings.Error404Collection)}", values, "Culture and one and only one of ContentId, ContentKey and ContentXPath must be specified for each entry", out message); + if (!ValidateAutoFillImageProperties(options.Imaging.AutoFillImageProperties, out message)) + { + return ValidateOptionsResult.Fail(message); + } - private bool ValidateAutoFillImageProperties(IEnumerable values, out string message) => - ValidateCollection($"{Constants.Configuration.ConfigContent}:{nameof(ContentSettings.Imaging)}:{nameof(ContentSettings.Imaging.AutoFillImageProperties)}", values, "Alias, WidthFieldAlias, HeightFieldAlias, LengthFieldAlias and ExtensionFieldAlias must be specified for each entry", out message); + return ValidateOptionsResult.Success; } + + private bool ValidateError404Collection(IEnumerable values, out string message) => + ValidateCollection($"{Constants.Configuration.ConfigContent}:{nameof(ContentSettings.Error404Collection)}", + values, + "Culture and one and only one of ContentId, ContentKey and ContentXPath must be specified for each entry", + out message); + + private bool ValidateAutoFillImageProperties(IEnumerable values, out string message) => + ValidateCollection( + $"{Constants.Configuration.ConfigContent}:{nameof(ContentSettings.Imaging)}:{nameof(ContentSettings.Imaging.AutoFillImageProperties)}", + values, + "Alias, WidthFieldAlias, HeightFieldAlias, LengthFieldAlias and ExtensionFieldAlias must be specified for each entry", + out message); } diff --git a/src/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidator.cs b/src/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidator.cs index 31d0779626e3..ce31b56ec4ec 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidator.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidator.cs @@ -1,48 +1,50 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Microsoft.Extensions.Options; -namespace Umbraco.Cms.Core.Configuration.Models.Validation +namespace Umbraco.Cms.Core.Configuration.Models.Validation; + +/// +/// Validator for configuration representated as . +/// +public class GlobalSettingsValidator + : ConfigurationValidatorBase, IValidateOptions { - /// - /// Validator for configuration representated as . - /// - public class GlobalSettingsValidator - : ConfigurationValidatorBase, IValidateOptions + /// + public ValidateOptionsResult Validate(string name, GlobalSettings options) { - /// - public ValidateOptionsResult Validate(string name, GlobalSettings options) + if (!ValidateSmtpSetting(options.Smtp, out var message)) { - if (!ValidateSmtpSetting(options.Smtp, out var message)) - { - return ValidateOptionsResult.Fail(message); - } - - if (!ValidateSqlWriteLockTimeOutSetting(options.DistributedLockingWriteLockDefaultTimeout, out var message2)) - { - return ValidateOptionsResult.Fail(message2); - } + return ValidateOptionsResult.Fail(message); + } - return ValidateOptionsResult.Success; + if (!ValidateSqlWriteLockTimeOutSetting(options.DistributedLockingWriteLockDefaultTimeout, out var message2)) + { + return ValidateOptionsResult.Fail(message2); } - private bool ValidateSmtpSetting(SmtpSettings? value, out string message) => - ValidateOptionalEntry($"{Constants.Configuration.ConfigGlobal}:{nameof(GlobalSettings.Smtp)}", value, "A valid From email address is required", out message); - - private bool ValidateSqlWriteLockTimeOutSetting(TimeSpan configuredTimeOut, out string message) { - // Only apply this setting if it's not excessively high or low - const int minimumTimeOut = 100; - const int maximumTimeOut = 20000; - if (configuredTimeOut.TotalMilliseconds < minimumTimeOut || configuredTimeOut.TotalMilliseconds > maximumTimeOut) // between 0.1 and 20 seconds - { - message = $"The `{Constants.Configuration.ConfigGlobal}:{nameof(GlobalSettings.DistributedLockingWriteLockDefaultTimeout)}` setting is not between the minimum of {minimumTimeOut} ms and maximum of {maximumTimeOut} ms"; - return false; - } - - message = string.Empty; - return true; + return ValidateOptionsResult.Success; + } + + private bool ValidateSmtpSetting(SmtpSettings? value, out string message) => + ValidateOptionalEntry($"{Constants.Configuration.ConfigGlobal}:{nameof(GlobalSettings.Smtp)}", value, + "A valid From email address is required", out message); + + private bool ValidateSqlWriteLockTimeOutSetting(TimeSpan configuredTimeOut, out string message) + { + // Only apply this setting if it's not excessively high or low + const int minimumTimeOut = 100; + const int maximumTimeOut = 20000; + if (configuredTimeOut.TotalMilliseconds < minimumTimeOut || + configuredTimeOut.TotalMilliseconds > maximumTimeOut) // between 0.1 and 20 seconds + { + message = + $"The `{Constants.Configuration.ConfigGlobal}:{nameof(GlobalSettings.DistributedLockingWriteLockDefaultTimeout)}` setting is not between the minimum of {minimumTimeOut} ms and maximum of {maximumTimeOut} ms"; + return false; } + + message = string.Empty; + return true; } } diff --git a/src/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidator.cs b/src/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidator.cs index a8b63f39a0c6..d7e7250551ae 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidator.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidator.cs @@ -3,45 +3,46 @@ using Microsoft.Extensions.Options; -namespace Umbraco.Cms.Core.Configuration.Models.Validation +namespace Umbraco.Cms.Core.Configuration.Models.Validation; + +/// +/// Validator for configuration representated as . +/// +public class HealthChecksSettingsValidator : ConfigurationValidatorBase, IValidateOptions { + private readonly ICronTabParser _cronTabParser; + /// - /// Validator for configuration representated as . + /// Initializes a new instance of the class. /// - public class HealthChecksSettingsValidator : ConfigurationValidatorBase, IValidateOptions - { - private readonly ICronTabParser _cronTabParser; + /// Helper for parsing crontab expressions. + public HealthChecksSettingsValidator(ICronTabParser cronTabParser) => _cronTabParser = cronTabParser; - /// - /// Initializes a new instance of the class. - /// - /// Helper for parsing crontab expressions. - public HealthChecksSettingsValidator(ICronTabParser cronTabParser) => _cronTabParser = cronTabParser; - - /// - public ValidateOptionsResult Validate(string name, HealthChecksSettings options) + /// + public ValidateOptionsResult Validate(string name, HealthChecksSettings options) + { + if (!ValidateNotificationFirstRunTime(options.Notification.FirstRunTime, out var message)) { - if (!ValidateNotificationFirstRunTime(options.Notification.FirstRunTime, out var message)) - { - return ValidateOptionsResult.Fail(message); - } - - return ValidateOptionsResult.Success; + return ValidateOptionsResult.Fail(message); } - private bool ValidateNotificationFirstRunTime(string value, out string message) => - ValidateOptionalCronTab($"{Constants.Configuration.ConfigHealthChecks}:{nameof(HealthChecksSettings.Notification)}:{nameof(HealthChecksSettings.Notification.FirstRunTime)}", value, out message); + return ValidateOptionsResult.Success; + } + + private bool ValidateNotificationFirstRunTime(string value, out string message) => + ValidateOptionalCronTab( + $"{Constants.Configuration.ConfigHealthChecks}:{nameof(HealthChecksSettings.Notification)}:{nameof(HealthChecksSettings.Notification.FirstRunTime)}", + value, out message); - private bool ValidateOptionalCronTab(string configPath, string value, out string message) + private bool ValidateOptionalCronTab(string configPath, string value, out string message) + { + if (!string.IsNullOrEmpty(value) && !_cronTabParser.IsValidCronTab(value)) { - if (!string.IsNullOrEmpty(value) && !_cronTabParser.IsValidCronTab(value)) - { - message = $"Configuration entry {configPath} contains an invalid cron expression."; - return false; - } - - message = string.Empty; - return true; + message = $"Configuration entry {configPath} contains an invalid cron expression."; + return false; } + + message = string.Empty; + return true; } } diff --git a/src/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidator.cs b/src/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidator.cs index 6260341c181f..fedb32ad3ec9 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidator.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidator.cs @@ -3,28 +3,29 @@ using Microsoft.Extensions.Options; -namespace Umbraco.Cms.Core.Configuration.Models.Validation +namespace Umbraco.Cms.Core.Configuration.Models.Validation; + +/// +/// Validator for configuration representated as . +/// +public class RequestHandlerSettingsValidator : ConfigurationValidatorBase, IValidateOptions { - /// - /// Validator for configuration representated as . - /// - public class RequestHandlerSettingsValidator : ConfigurationValidatorBase, IValidateOptions + /// + public ValidateOptionsResult Validate(string name, RequestHandlerSettings options) { - /// - public ValidateOptionsResult Validate(string name, RequestHandlerSettings options) + if (!ValidateConvertUrlsToAscii(options.ConvertUrlsToAscii, out var message)) { - if (!ValidateConvertUrlsToAscii(options.ConvertUrlsToAscii, out var message)) - { - return ValidateOptionsResult.Fail(message); - } - - return ValidateOptionsResult.Success; + return ValidateOptionsResult.Fail(message); } - private bool ValidateConvertUrlsToAscii(string value, out string message) - { - var validValues = new[] { "try", "true", "false" }; - return ValidateStringIsOneOfValidValues($"{Constants.Configuration.ConfigRequestHandler}:{nameof(RequestHandlerSettings.ConvertUrlsToAscii)}", value, validValues, out message); - } + return ValidateOptionsResult.Success; + } + + private bool ValidateConvertUrlsToAscii(string value, out string message) + { + var validValues = new[] {"try", "true", "false"}; + return ValidateStringIsOneOfValidValues( + $"{Constants.Configuration.ConfigRequestHandler}:{nameof(RequestHandlerSettings.ConvertUrlsToAscii)}", + value, validValues, out message); } } diff --git a/src/Umbraco.Core/Configuration/Models/Validation/UnattendedSettingsValidator.cs b/src/Umbraco.Core/Configuration/Models/Validation/UnattendedSettingsValidator.cs index 3c073ac1008a..f96a88b13fe9 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/UnattendedSettingsValidator.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/UnattendedSettingsValidator.cs @@ -3,42 +3,42 @@ using Microsoft.Extensions.Options; -namespace Umbraco.Cms.Core.Configuration.Models.Validation +namespace Umbraco.Cms.Core.Configuration.Models.Validation; + +/// +/// Validator for configuration representated as . +/// +public class UnattendedSettingsValidator + : IValidateOptions { - /// - /// Validator for configuration representated as . - /// - public class UnattendedSettingsValidator - : IValidateOptions + /// + public ValidateOptionsResult Validate(string name, UnattendedSettings options) { - /// - public ValidateOptionsResult Validate(string name, UnattendedSettings options) + if (options.InstallUnattended) { - if (options.InstallUnattended) + var setValues = 0; + if (!string.IsNullOrEmpty(options.UnattendedUserName)) { - int setValues = 0; - if (!string.IsNullOrEmpty(options.UnattendedUserName)) - { - setValues++; - } - - if (!string.IsNullOrEmpty(options.UnattendedUserEmail)) - { - setValues++; - } + setValues++; + } - if (!string.IsNullOrEmpty(options.UnattendedUserPassword)) - { - setValues++; - } + if (!string.IsNullOrEmpty(options.UnattendedUserEmail)) + { + setValues++; + } - if (0 < setValues && setValues < 3) - { - return ValidateOptionsResult.Fail($"Configuration entry {Constants.Configuration.ConfigUnattended} contains invalid values.\nIf any of the {nameof(options.UnattendedUserName)}, {nameof(options.UnattendedUserEmail)}, {nameof(options.UnattendedUserPassword)} are set, all of them are required."); - } + if (!string.IsNullOrEmpty(options.UnattendedUserPassword)) + { + setValues++; } - return ValidateOptionsResult.Success; + if (0 < setValues && setValues < 3) + { + return ValidateOptionsResult.Fail( + $"Configuration entry {Constants.Configuration.ConfigUnattended} contains invalid values.\nIf any of the {nameof(options.UnattendedUserName)}, {nameof(options.UnattendedUserEmail)}, {nameof(options.UnattendedUserPassword)} are set, all of them are required."); + } } + + return ValidateOptionsResult.Success; } } diff --git a/src/Umbraco.Core/Configuration/Models/Validation/ValidatableEntryBase.cs b/src/Umbraco.Core/Configuration/Models/Validation/ValidatableEntryBase.cs index 970146a27eeb..ff858943ac49 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/ValidatableEntryBase.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/ValidatableEntryBase.cs @@ -1,21 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -namespace Umbraco.Cms.Core.Configuration.Models.Validation +namespace Umbraco.Cms.Core.Configuration.Models.Validation; + +/// +/// Provides a base class for configuration models that can be validated based on data annotations. +/// +public abstract class ValidatableEntryBase { - /// - /// Provides a base class for configuration models that can be validated based on data annotations. - /// - public abstract class ValidatableEntryBase + internal virtual bool IsValid() { - internal virtual bool IsValid() - { - var ctx = new ValidationContext(this); - var results = new List(); - return Validator.TryValidateObject(this, ctx, results, true); - } + var ctx = new ValidationContext(this); + var results = new List(); + return Validator.TryValidateObject(this, ctx, results, true); } } diff --git a/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs b/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs index deb7c64a9f3d..c4dff7a54281 100644 --- a/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/WebRoutingSettings.cs @@ -4,81 +4,81 @@ using System.ComponentModel; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for web routing settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigWebRouting)] +public class WebRoutingSettings { + internal const bool StaticTryMatchingEndpointsForAllPages = false; + internal const bool StaticTrySkipIisCustomErrors = false; + internal const bool StaticInternalRedirectPreservesTemplate = false; + internal const bool StaticDisableAlternativeTemplates = false; + internal const bool StaticValidateAlternativeTemplates = false; + internal const bool StaticDisableFindContentByIdPath = false; + internal const bool StaticDisableRedirectUrlTracking = false; + internal const string StaticUrlProviderMode = "Auto"; + /// - /// Typed configuration options for web routing settings. + /// Gets or sets a value indicating whether to check if any routed endpoints match a front-end request before + /// the Umbraco dynamic router tries to map the request to an Umbraco content item. /// - [UmbracoOptions(Constants.Configuration.ConfigWebRouting)] - public class WebRoutingSettings - { - - internal const bool StaticTryMatchingEndpointsForAllPages = false; - internal const bool StaticTrySkipIisCustomErrors = false; - internal const bool StaticInternalRedirectPreservesTemplate = false; - internal const bool StaticDisableAlternativeTemplates = false; - internal const bool StaticValidateAlternativeTemplates = false; - internal const bool StaticDisableFindContentByIdPath = false; - internal const bool StaticDisableRedirectUrlTracking = false; - internal const string StaticUrlProviderMode = "Auto"; + /// + /// This should not be necessary if the Umbraco catch-all/dynamic route is registered last like it's supposed to be. In + /// that case + /// ASP.NET Core will automatically handle this in all cases. This is more of a backward compatible option since this + /// is what v7/v8 used + /// to do. + /// + [DefaultValue(StaticTryMatchingEndpointsForAllPages)] + public bool TryMatchingEndpointsForAllPages { get; set; } = StaticTryMatchingEndpointsForAllPages; - /// - /// Gets or sets a value indicating whether to check if any routed endpoints match a front-end request before - /// the Umbraco dynamic router tries to map the request to an Umbraco content item. - /// - /// - /// This should not be necessary if the Umbraco catch-all/dynamic route is registered last like it's supposed to be. In that case - /// ASP.NET Core will automatically handle this in all cases. This is more of a backward compatible option since this is what v7/v8 used - /// to do. - /// - [DefaultValue(StaticTryMatchingEndpointsForAllPages)] - public bool TryMatchingEndpointsForAllPages { get; set; } = StaticTryMatchingEndpointsForAllPages; - - /// - /// Gets or sets a value indicating whether IIS custom errors should be skipped. - /// - [DefaultValue(StaticTrySkipIisCustomErrors)] - public bool TrySkipIisCustomErrors { get; set; } = StaticTrySkipIisCustomErrors; + /// + /// Gets or sets a value indicating whether IIS custom errors should be skipped. + /// + [DefaultValue(StaticTrySkipIisCustomErrors)] + public bool TrySkipIisCustomErrors { get; set; } = StaticTrySkipIisCustomErrors; - /// - /// Gets or sets a value indicating whether an internal redirect should preserve the template. - /// - [DefaultValue(StaticInternalRedirectPreservesTemplate)] - public bool InternalRedirectPreservesTemplate { get; set; } = StaticInternalRedirectPreservesTemplate; + /// + /// Gets or sets a value indicating whether an internal redirect should preserve the template. + /// + [DefaultValue(StaticInternalRedirectPreservesTemplate)] + public bool InternalRedirectPreservesTemplate { get; set; } = StaticInternalRedirectPreservesTemplate; - /// - /// Gets or sets a value indicating whether the use of alternative templates are disabled. - /// - [DefaultValue(StaticDisableAlternativeTemplates)] - public bool DisableAlternativeTemplates { get; set; } = StaticDisableAlternativeTemplates; + /// + /// Gets or sets a value indicating whether the use of alternative templates are disabled. + /// + [DefaultValue(StaticDisableAlternativeTemplates)] + public bool DisableAlternativeTemplates { get; set; } = StaticDisableAlternativeTemplates; - /// - /// Gets or sets a value indicating whether the use of alternative templates should be validated. - /// - [DefaultValue(StaticValidateAlternativeTemplates)] - public bool ValidateAlternativeTemplates { get; set; } = StaticValidateAlternativeTemplates; + /// + /// Gets or sets a value indicating whether the use of alternative templates should be validated. + /// + [DefaultValue(StaticValidateAlternativeTemplates)] + public bool ValidateAlternativeTemplates { get; set; } = StaticValidateAlternativeTemplates; - /// - /// Gets or sets a value indicating whether find content ID by path is disabled. - /// - [DefaultValue(StaticDisableFindContentByIdPath)] - public bool DisableFindContentByIdPath { get; set; } = StaticDisableFindContentByIdPath; + /// + /// Gets or sets a value indicating whether find content ID by path is disabled. + /// + [DefaultValue(StaticDisableFindContentByIdPath)] + public bool DisableFindContentByIdPath { get; set; } = StaticDisableFindContentByIdPath; - /// - /// Gets or sets a value indicating whether redirect URL tracking is disabled. - /// - [DefaultValue(StaticDisableRedirectUrlTracking)] - public bool DisableRedirectUrlTracking { get; set; } = StaticDisableRedirectUrlTracking; + /// + /// Gets or sets a value indicating whether redirect URL tracking is disabled. + /// + [DefaultValue(StaticDisableRedirectUrlTracking)] + public bool DisableRedirectUrlTracking { get; set; } = StaticDisableRedirectUrlTracking; - /// - /// Gets or sets a value for the URL provider mode (). - /// - [DefaultValue(StaticUrlProviderMode)] - public UrlMode UrlProviderMode { get; set; } = Enum.Parse(StaticUrlProviderMode); + /// + /// Gets or sets a value for the URL provider mode (). + /// + [DefaultValue(StaticUrlProviderMode)] + public UrlMode UrlProviderMode { get; set; } = Enum.Parse(StaticUrlProviderMode); - /// - /// Gets or sets a value for the Umbraco application URL. - /// - public string UmbracoApplicationUrl { get; set; } = null!; - } + /// + /// Gets or sets a value for the Umbraco application URL. + /// + public string UmbracoApplicationUrl { get; set; } = null!; } diff --git a/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs b/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs index 1b1ebc6af5f5..fe10b46746ba 100644 --- a/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs +++ b/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs @@ -1,57 +1,62 @@ -using System.IO; -using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Exceptions; using Umbraco.Cms.Core.Hosting; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class ModelsBuilderConfigExtensions { - public static class ModelsBuilderConfigExtensions - { - private static string? _modelsDirectoryAbsolute = null; + private static string? _modelsDirectoryAbsolute; - public static string ModelsDirectoryAbsolute(this ModelsBuilderSettings modelsBuilderConfig, IHostingEnvironment hostingEnvironment) + public static string ModelsDirectoryAbsolute(this ModelsBuilderSettings modelsBuilderConfig, + IHostingEnvironment hostingEnvironment) + { + if (_modelsDirectoryAbsolute is null) { - if (_modelsDirectoryAbsolute is null) - { - var modelsDirectory = modelsBuilderConfig.ModelsDirectory; - var root = hostingEnvironment.MapPathContentRoot("~/"); + var modelsDirectory = modelsBuilderConfig.ModelsDirectory; + var root = hostingEnvironment.MapPathContentRoot("~/"); - _modelsDirectoryAbsolute = GetModelsDirectory(root, modelsDirectory, - modelsBuilderConfig.AcceptUnsafeModelsDirectory); - } - - return _modelsDirectoryAbsolute; + _modelsDirectoryAbsolute = GetModelsDirectory(root, modelsDirectory, + modelsBuilderConfig.AcceptUnsafeModelsDirectory); } - // internal for tests - internal static string GetModelsDirectory(string root, string config, bool acceptUnsafe) - { - // making sure it is safe, ie under the website root, - // unless AcceptUnsafeModelsDirectory and then everything is OK. + return _modelsDirectoryAbsolute; + } - if (!Path.IsPathRooted(root)) - throw new ConfigurationException($"Root is not rooted \"{root}\"."); + // internal for tests + internal static string GetModelsDirectory(string root, string config, bool acceptUnsafe) + { + // making sure it is safe, ie under the website root, + // unless AcceptUnsafeModelsDirectory and then everything is OK. - if (config.StartsWith("~/")) - { - var dir = Path.Combine(root, config.TrimStart("~/")); + if (!Path.IsPathRooted(root)) + { + throw new ConfigurationException($"Root is not rooted \"{root}\"."); + } - // sanitize - GetFullPath will take care of any relative - // segments in path, eg '../../foo.tmp' - it may throw a SecurityException - // if the combined path reaches illegal parts of the filesystem - dir = Path.GetFullPath(dir); - root = Path.GetFullPath(root); + if (config.StartsWith("~/")) + { + var dir = Path.Combine(root, config.TrimStart("~/")); - if (!dir.StartsWith(root) && !acceptUnsafe) - throw new ConfigurationException($"Invalid models directory \"{config}\"."); + // sanitize - GetFullPath will take care of any relative + // segments in path, eg '../../foo.tmp' - it may throw a SecurityException + // if the combined path reaches illegal parts of the filesystem + dir = Path.GetFullPath(dir); + root = Path.GetFullPath(root); - return dir; + if (!dir.StartsWith(root) && !acceptUnsafe) + { + throw new ConfigurationException($"Invalid models directory \"{config}\"."); } - if (acceptUnsafe) - return Path.GetFullPath(config); + return dir; + } - throw new ConfigurationException($"Invalid models directory \"{config}\"."); + if (acceptUnsafe) + { + return Path.GetFullPath(config); } + + throw new ConfigurationException($"Invalid models directory \"{config}\"."); } } diff --git a/src/Umbraco.Core/Configuration/ModelsMode.cs b/src/Umbraco.Core/Configuration/ModelsMode.cs index 064e035892fa..778b60db1582 100644 --- a/src/Umbraco.Core/Configuration/ModelsMode.cs +++ b/src/Umbraco.Core/Configuration/ModelsMode.cs @@ -1,39 +1,42 @@ -namespace Umbraco.Cms.Core.Configuration +namespace Umbraco.Cms.Core.Configuration; + +/// +/// Defines the models generation modes. +/// +public enum ModelsMode { /// - /// Defines the models generation modes. + /// Do not generate strongly typed models. /// - public enum ModelsMode - { - /// - /// Do not generate strongly typed models. - /// - /// - /// This means that only IPublishedContent instances will be used. - /// - Nothing = 0, + /// + /// This means that only IPublishedContent instances will be used. + /// + Nothing = 0, - /// - /// Generate models in memory. - /// When: a content type change occurs. - /// - /// The app does not restart. Models are available in views exclusively. - InMemoryAuto, + /// + /// Generate models in memory. + /// When: a content type change occurs. + /// + /// The app does not restart. Models are available in views exclusively. + InMemoryAuto, - /// - /// Generate models as *.cs files. - /// When: generation is triggered. - /// - /// Generation can be triggered from the dashboard. The app does not restart. - /// Models are not compiled and thus are not available to the project. - SourceCodeManual, + /// + /// Generate models as *.cs files. + /// When: generation is triggered. + /// + /// + /// Generation can be triggered from the dashboard. The app does not restart. + /// Models are not compiled and thus are not available to the project. + /// + SourceCodeManual, - /// - /// Generate models as *.cs files. - /// When: a content type change occurs, or generation is triggered. - /// - /// Generation can be triggered from the dashboard. The app does not restart. - /// Models are not compiled and thus are not available to the project. - SourceCodeAuto - } + /// + /// Generate models as *.cs files. + /// When: a content type change occurs, or generation is triggered. + /// + /// + /// Generation can be triggered from the dashboard. The app does not restart. + /// Models are not compiled and thus are not available to the project. + /// + SourceCodeAuto } diff --git a/src/Umbraco.Core/Configuration/ModelsModeExtensions.cs b/src/Umbraco.Core/Configuration/ModelsModeExtensions.cs index f27d54b55d67..52256a29f071 100644 --- a/src/Umbraco.Core/Configuration/ModelsModeExtensions.cs +++ b/src/Umbraco.Core/Configuration/ModelsModeExtensions.cs @@ -1,28 +1,27 @@ using Umbraco.Cms.Core.Configuration; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Provides extensions for the enumeration. +/// +public static class ModelsModeExtensions { /// - /// Provides extensions for the enumeration. + /// Gets a value indicating whether the mode is *Auto. /// - public static class ModelsModeExtensions - { - /// - /// Gets a value indicating whether the mode is *Auto. - /// - public static bool IsAuto(this ModelsMode modelsMode) - => modelsMode == ModelsMode.InMemoryAuto || modelsMode == ModelsMode.SourceCodeAuto; + public static bool IsAuto(this ModelsMode modelsMode) + => modelsMode == ModelsMode.InMemoryAuto || modelsMode == ModelsMode.SourceCodeAuto; - /// - /// Gets a value indicating whether the mode is *Auto but not InMemory. - /// - public static bool IsAutoNotInMemory(this ModelsMode modelsMode) - => modelsMode == ModelsMode.SourceCodeAuto; + /// + /// Gets a value indicating whether the mode is *Auto but not InMemory. + /// + public static bool IsAutoNotInMemory(this ModelsMode modelsMode) + => modelsMode == ModelsMode.SourceCodeAuto; - /// - /// Gets a value indicating whether the mode supports explicit manual generation. - /// - public static bool SupportsExplicitGeneration(this ModelsMode modelsMode) - => modelsMode == ModelsMode.SourceCodeManual || modelsMode == ModelsMode.SourceCodeAuto; - } + /// + /// Gets a value indicating whether the mode supports explicit manual generation. + /// + public static bool SupportsExplicitGeneration(this ModelsMode modelsMode) + => modelsMode == ModelsMode.SourceCodeManual || modelsMode == ModelsMode.SourceCodeAuto; } diff --git a/src/Umbraco.Core/Configuration/PasswordConfiguration.cs b/src/Umbraco.Core/Configuration/PasswordConfiguration.cs index 506821df6d91..2b7684223aab 100644 --- a/src/Umbraco.Core/Configuration/PasswordConfiguration.cs +++ b/src/Umbraco.Core/Configuration/PasswordConfiguration.cs @@ -1,37 +1,34 @@ -using System; +namespace Umbraco.Cms.Core.Configuration; -namespace Umbraco.Cms.Core.Configuration +public abstract class PasswordConfiguration : IPasswordConfiguration { - public abstract class PasswordConfiguration : IPasswordConfiguration + protected PasswordConfiguration(IPasswordConfiguration configSettings) { - protected PasswordConfiguration(IPasswordConfiguration configSettings) + if (configSettings == null) { - if (configSettings == null) - { - throw new ArgumentNullException(nameof(configSettings)); - } - - RequiredLength = configSettings.RequiredLength; - RequireNonLetterOrDigit = configSettings.RequireNonLetterOrDigit; - RequireDigit = configSettings.RequireDigit; - RequireLowercase = configSettings.RequireLowercase; - RequireUppercase = configSettings.RequireUppercase; - HashAlgorithmType = configSettings.HashAlgorithmType; - MaxFailedAccessAttemptsBeforeLockout = configSettings.MaxFailedAccessAttemptsBeforeLockout; + throw new ArgumentNullException(nameof(configSettings)); } - public int RequiredLength { get; } + RequiredLength = configSettings.RequiredLength; + RequireNonLetterOrDigit = configSettings.RequireNonLetterOrDigit; + RequireDigit = configSettings.RequireDigit; + RequireLowercase = configSettings.RequireLowercase; + RequireUppercase = configSettings.RequireUppercase; + HashAlgorithmType = configSettings.HashAlgorithmType; + MaxFailedAccessAttemptsBeforeLockout = configSettings.MaxFailedAccessAttemptsBeforeLockout; + } - public bool RequireNonLetterOrDigit { get; } + public int RequiredLength { get; } - public bool RequireDigit { get; } + public bool RequireNonLetterOrDigit { get; } - public bool RequireLowercase { get; } + public bool RequireDigit { get; } - public bool RequireUppercase { get; } + public bool RequireLowercase { get; } - public string HashAlgorithmType { get; } + public bool RequireUppercase { get; } - public int MaxFailedAccessAttemptsBeforeLockout { get; } - } + public string HashAlgorithmType { get; } + + public int MaxFailedAccessAttemptsBeforeLockout { get; } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacementEqualityComparer.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacementEqualityComparer.cs index b8049fe650ae..8740b81cb5b9 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacementEqualityComparer.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/CharacterReplacementEqualityComparer.cs @@ -1,41 +1,38 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Configuration.Models; +namespace Umbraco.Cms.Core.Configuration.UmbracoSettings; -namespace Umbraco.Cms.Core.Configuration.UmbracoSettings +public class CharacterReplacementEqualityComparer : IEqualityComparer { - public class CharacterReplacementEqualityComparer : IEqualityComparer + public bool Equals(IChar? x, IChar? y) { - public bool Equals(IChar? x, IChar? y) + if (ReferenceEquals(x, y)) { - if (ReferenceEquals(x, y)) - { - return true; - } - - if (x is null) - { - return false; - } + return true; + } - if (y is null) - { - return false; - } + if (x is null) + { + return false; + } - if (x.GetType() != y.GetType()) - { - return false; - } + if (y is null) + { + return false; + } - return x.Char == y.Char && x.Replacement == y.Replacement; + if (x.GetType() != y.GetType()) + { + return false; } - public int GetHashCode(IChar obj) + return x.Char == y.Char && x.Replacement == y.Replacement; + } + + public int GetHashCode(IChar obj) + { + unchecked { - unchecked - { - return ((obj.Char != null ? obj.Char.GetHashCode() : 0) * 397) ^ (obj.Replacement != null ? obj.Replacement.GetHashCode() : 0); - } + return ((obj.Char != null ? obj.Char.GetHashCode() : 0) * 397) ^ + (obj.Replacement != null ? obj.Replacement.GetHashCode() : 0); } } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IChar.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IChar.cs index 61e840245cf9..a2ba30b776bc 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IChar.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IChar.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.Configuration.UmbracoSettings +namespace Umbraco.Cms.Core.Configuration.UmbracoSettings; + +public interface IChar { - public interface IChar - { - string Char { get; } + string Char { get; } - string Replacement { get; } - } + string Replacement { get; } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IImagingAutoFillUploadField.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IImagingAutoFillUploadField.cs index c7d91a6d0af7..2484675aee66 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IImagingAutoFillUploadField.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IImagingAutoFillUploadField.cs @@ -1,18 +1,17 @@ -namespace Umbraco.Cms.Core.Configuration.UmbracoSettings +namespace Umbraco.Cms.Core.Configuration.UmbracoSettings; + +public interface IImagingAutoFillUploadField { - public interface IImagingAutoFillUploadField - { - /// - /// Allow setting internally so we can create a default - /// - string Alias { get; } + /// + /// Allow setting internally so we can create a default + /// + string Alias { get; } - string WidthFieldAlias { get; } + string WidthFieldAlias { get; } - string HeightFieldAlias { get; } + string HeightFieldAlias { get; } - string LengthFieldAlias { get; } + string LengthFieldAlias { get; } - string ExtensionFieldAlias { get; } - } + string ExtensionFieldAlias { get; } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IPasswordConfigurationSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IPasswordConfigurationSection.cs index d79d8940c3fe..356858db070d 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IPasswordConfigurationSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IPasswordConfigurationSection.cs @@ -1,21 +1,20 @@ -namespace Umbraco.Cms.Core.Configuration.UmbracoSettings +namespace Umbraco.Cms.Core.Configuration.UmbracoSettings; + +public interface IPasswordConfigurationSection : IUmbracoConfigurationSection { - public interface IPasswordConfigurationSection : IUmbracoConfigurationSection - { - int RequiredLength { get; } + int RequiredLength { get; } - bool RequireNonLetterOrDigit { get; } + bool RequireNonLetterOrDigit { get; } - bool RequireDigit { get; } + bool RequireDigit { get; } - bool RequireLowercase { get; } + bool RequireLowercase { get; } - bool RequireUppercase { get; } + bool RequireUppercase { get; } - bool UseLegacyEncoding { get; } + bool UseLegacyEncoding { get; } - string HashAlgorithmType { get; } + string HashAlgorithmType { get; } - int MaxFailedAccessAttemptsBeforeLockout { get; } - } + int MaxFailedAccessAttemptsBeforeLockout { get; } } diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ITypeFinderConfig.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ITypeFinderConfig.cs index 903f21f21ac2..6eeff38495ff 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ITypeFinderConfig.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ITypeFinderConfig.cs @@ -1,9 +1,6 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Configuration.UmbracoSettings; -namespace Umbraco.Cms.Core.Configuration.UmbracoSettings +public interface ITypeFinderConfig { - public interface ITypeFinderConfig - { - IEnumerable AssembliesAcceptingLoadExceptions { get; } - } + IEnumerable AssembliesAcceptingLoadExceptions { get; } } diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 4b4ad87801fd..9664d7cb7330 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -1,68 +1,72 @@ -using System; using System.Reflection; using Umbraco.Cms.Core.Semver; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Configuration +namespace Umbraco.Cms.Core.Configuration; + +/// +/// Represents the version of the executing code. +/// +public class UmbracoVersion : IUmbracoVersion { - /// - /// Represents the version of the executing code. - /// - public class UmbracoVersion : IUmbracoVersion + public UmbracoVersion() { - public UmbracoVersion() - { - var umbracoCoreAssembly = typeof(SemVersion).Assembly; + Assembly umbracoCoreAssembly = typeof(SemVersion).Assembly; - // gets the value indicated by the AssemblyVersion attribute - AssemblyVersion = umbracoCoreAssembly.GetName().Version; + // gets the value indicated by the AssemblyVersion attribute + AssemblyVersion = umbracoCoreAssembly.GetName().Version; - // gets the value indicated by the AssemblyFileVersion attribute - AssemblyFileVersion = System.Version.Parse(umbracoCoreAssembly.GetCustomAttribute()?.Version ?? string.Empty); + // gets the value indicated by the AssemblyFileVersion attribute + AssemblyFileVersion = + Version.Parse(umbracoCoreAssembly.GetCustomAttribute()?.Version ?? + string.Empty); - // gets the value indicated by the AssemblyInformationalVersion attribute - // this is the true semantic version of the Umbraco Cms - SemanticVersion = SemVersion.Parse(umbracoCoreAssembly.GetCustomAttribute()?.InformationalVersion ?? string.Empty); + // gets the value indicated by the AssemblyInformationalVersion attribute + // this is the true semantic version of the Umbraco Cms + SemanticVersion = + SemVersion.Parse(umbracoCoreAssembly.GetCustomAttribute() + ?.InformationalVersion ?? string.Empty); - // gets the non-semantic version - Version = SemanticVersion.GetVersion(3); - } + // gets the non-semantic version + Version = SemanticVersion.GetVersion(3); + } - /// - /// Gets the non-semantic version of the Umbraco code. - /// - public Version Version { get; } + /// + /// Gets the non-semantic version of the Umbraco code. + /// + public Version Version { get; } - /// - /// Gets the semantic version comments of the Umbraco code. - /// - public string Comment => SemanticVersion.Prerelease; + /// + /// Gets the semantic version comments of the Umbraco code. + /// + public string Comment => SemanticVersion.Prerelease; - /// - /// Gets the assembly version of the Umbraco code. - /// - /// - /// The assembly version is the value of the . - /// Is the one that the CLR checks for compatibility. Therefore, it changes only on - /// hard-breaking changes (for instance, on new major versions). - /// - public Version? AssemblyVersion { get; } + /// + /// Gets the assembly version of the Umbraco code. + /// + /// + /// The assembly version is the value of the . + /// + /// Is the one that the CLR checks for compatibility. Therefore, it changes only on + /// hard-breaking changes (for instance, on new major versions). + /// + /// + public Version? AssemblyVersion { get; } - /// - /// Gets the assembly file version of the Umbraco code. - /// - /// - /// The assembly version is the value of the . - /// - public Version? AssemblyFileVersion { get; } + /// + /// Gets the assembly file version of the Umbraco code. + /// + /// + /// The assembly version is the value of the . + /// + public Version? AssemblyFileVersion { get; } - /// - /// Gets the semantic version of the Umbraco code. - /// - /// - /// The semantic version is the value of the . - /// It is the full version of Umbraco, including comments. - /// - public SemVersion SemanticVersion { get; } - } + /// + /// Gets the semantic version of the Umbraco code. + /// + /// + /// The semantic version is the value of the . + /// It is the full version of Umbraco, including comments. + /// + public SemVersion SemanticVersion { get; } } diff --git a/src/Umbraco.Core/Configuration/UserPasswordConfiguration.cs b/src/Umbraco.Core/Configuration/UserPasswordConfiguration.cs index 6c30fbba713f..fb132ebf0d7b 100644 --- a/src/Umbraco.Core/Configuration/UserPasswordConfiguration.cs +++ b/src/Umbraco.Core/Configuration/UserPasswordConfiguration.cs @@ -1,13 +1,12 @@ -namespace Umbraco.Cms.Core.Configuration +namespace Umbraco.Cms.Core.Configuration; + +/// +/// The password configuration for back office users +/// +public class UserPasswordConfiguration : PasswordConfiguration, IUserPasswordConfiguration { - /// - /// The password configuration for back office users - /// - public class UserPasswordConfiguration : PasswordConfiguration, IUserPasswordConfiguration + public UserPasswordConfiguration(IUserPasswordConfiguration configSettings) + : base(configSettings) { - public UserPasswordConfiguration(IUserPasswordConfiguration configSettings) - : base(configSettings) - { - } } } diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index da945731af26..a53ca068b494 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -1,162 +1,161 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public static partial class Constants { - public static partial class Constants + /// + /// Defines the alias identifiers for Umbraco's core application sections. + /// + public static class Applications { /// - /// Defines the alias identifiers for Umbraco's core application sections. + /// Application alias for the content section. /// - public static class Applications - { - /// - /// Application alias for the content section. - /// - public const string Content = "content"; - - /// - /// Application alias for the packages section. - /// - public const string Packages = "packages"; - - /// - /// Application alias for the media section. - /// - public const string Media = "media"; - - /// - /// Application alias for the members section. - /// - public const string Members = "member"; - - /// - /// Application alias for the settings section. - /// - public const string Settings = "settings"; - - /// - /// Application alias for the translation section. - /// - public const string Translation = "translation"; - - /// - /// Application alias for the users section. - /// - public const string Users = "users"; - - /// - /// Application alias for the forms section. - /// - public const string Forms = "forms"; - } + public const string Content = "content"; /// - /// Defines the alias identifiers for Umbraco's core trees. + /// Application alias for the packages section. /// - public static class Trees - { - /// - /// alias for the content tree. - /// - public const string Content = "content"; + public const string Packages = "packages"; - /// - /// alias for the content blueprint tree. - /// - public const string ContentBlueprints = "contentBlueprints"; + /// + /// Application alias for the media section. + /// + public const string Media = "media"; - /// - /// alias for the member tree. - /// - public const string Members = "member"; + /// + /// Application alias for the members section. + /// + public const string Members = "member"; - /// - /// alias for the media tree. - /// - public const string Media = "media"; + /// + /// Application alias for the settings section. + /// + public const string Settings = "settings"; + + /// + /// Application alias for the translation section. + /// + public const string Translation = "translation"; + + /// + /// Application alias for the users section. + /// + public const string Users = "users"; + + /// + /// Application alias for the forms section. + /// + public const string Forms = "forms"; + } + + /// + /// Defines the alias identifiers for Umbraco's core trees. + /// + public static class Trees + { + /// + /// alias for the content tree. + /// + public const string Content = "content"; + + /// + /// alias for the content blueprint tree. + /// + public const string ContentBlueprints = "contentBlueprints"; + + /// + /// alias for the member tree. + /// + public const string Members = "member"; - /// - /// alias for the macro tree. - /// - public const string Macros = "macros"; + /// + /// alias for the media tree. + /// + public const string Media = "media"; - /// - /// alias for the datatype tree. - /// - public const string DataTypes = "dataTypes"; + /// + /// alias for the macro tree. + /// + public const string Macros = "macros"; - /// - /// alias for the packages tree - /// - public const string Packages = "packages"; + /// + /// alias for the datatype tree. + /// + public const string DataTypes = "dataTypes"; - /// - /// alias for the dictionary tree. - /// - public const string Dictionary = "dictionary"; + /// + /// alias for the packages tree + /// + public const string Packages = "packages"; - public const string Stylesheets = "stylesheets"; + /// + /// alias for the dictionary tree. + /// + public const string Dictionary = "dictionary"; - /// - /// alias for the document type tree. - /// - public const string DocumentTypes = "documentTypes"; + public const string Stylesheets = "stylesheets"; - /// - /// alias for the media type tree. - /// - public const string MediaTypes = "mediaTypes"; + /// + /// alias for the document type tree. + /// + public const string DocumentTypes = "documentTypes"; - /// - /// alias for the member type tree. - /// - public const string MemberTypes = "memberTypes"; + /// + /// alias for the media type tree. + /// + public const string MediaTypes = "mediaTypes"; - /// - /// alias for the member group tree. - /// - public const string MemberGroups = "memberGroups"; + /// + /// alias for the member type tree. + /// + public const string MemberTypes = "memberTypes"; - /// - /// alias for the template tree. - /// - public const string Templates = "templates"; + /// + /// alias for the member group tree. + /// + public const string MemberGroups = "memberGroups"; - public const string RelationTypes = "relationTypes"; + /// + /// alias for the template tree. + /// + public const string Templates = "templates"; - public const string Languages = "languages"; + public const string RelationTypes = "relationTypes"; - /// - /// alias for the user types tree. - /// - public const string UserTypes = "userTypes"; + public const string Languages = "languages"; - /// - /// alias for the user permissions tree. - /// - public const string UserPermissions = "userPermissions"; + /// + /// alias for the user types tree. + /// + public const string UserTypes = "userTypes"; - /// - /// alias for the users tree. - /// - public const string Users = "users"; + /// + /// alias for the user permissions tree. + /// + public const string UserPermissions = "userPermissions"; - public const string Scripts = "scripts"; + /// + /// alias for the users tree. + /// + public const string Users = "users"; - public const string PartialViews = "partialViews"; + public const string Scripts = "scripts"; - public const string PartialViewMacros = "partialViewMacros"; + public const string PartialViews = "partialViews"; - public const string LogViewer = "logViewer"; + public const string PartialViewMacros = "partialViewMacros"; - public static class Groups - { - public const string Settings = "settingsGroup"; + public const string LogViewer = "logViewer"; - public const string Templating = "templatingGroup"; + public static class Groups + { + public const string Settings = "settingsGroup"; - public const string ThirdParty = "thirdPartyGroup"; - } + public const string Templating = "templatingGroup"; - // TODO: Fill in the rest! + public const string ThirdParty = "thirdPartyGroup"; } + + // TODO: Fill in the rest! } } diff --git a/src/Umbraco.Core/Constants-CharArrays.cs b/src/Umbraco.Core/Constants-CharArrays.cs index 4be5ecba0443..43a64cfc91b7 100644 --- a/src/Umbraco.Core/Constants-CharArrays.cs +++ b/src/Umbraco.Core/Constants-CharArrays.cs @@ -1,138 +1,138 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public static partial class Constants { - public static partial class Constants + /// + /// Char Arrays to avoid allocations + /// + public static class CharArrays { /// - /// Char Arrays to avoid allocations - /// - public static class CharArrays - { - /// - /// Char array containing only / - /// - public static readonly char[] ForwardSlash = new char[] { '/' }; - - /// - /// Char array containing only \ - /// - public static readonly char[] Backslash = new char[] { '\\' }; - - /// - /// Char array containing only ' - /// - public static readonly char[] SingleQuote = new char[] { '\'' }; - - /// - /// Char array containing only " - /// - public static readonly char[] DoubleQuote = new char[] { '\"' }; - - - /// - /// Char array containing ' " - /// - public static readonly char[] DoubleQuoteSingleQuote = new char[] { '\"', '\'' }; - - /// - /// Char array containing only _ - /// - public static readonly char[] Underscore = new char[] { '_' }; - - /// - /// Char array containing \n \r - /// - public static readonly char[] LineFeedCarriageReturn = new char[] { '\n', '\r' }; - - - /// - /// Char array containing \n - /// - public static readonly char[] LineFeed = new char[] { '\n' }; - - /// - /// Char array containing only , - /// - public static readonly char[] Comma = new char[] { ',' }; - - /// - /// Char array containing only & - /// - public static readonly char[] Ampersand = new char[] { '&' }; - - /// - /// Char array containing only \0 - /// - public static readonly char[] NullTerminator = new char[] { '\0' }; - - /// - /// Char array containing only . - /// - public static readonly char[] Period = new char[] { '.' }; - - /// - /// Char array containing only ~ - /// - public static readonly char[] Tilde = new char[] { '~' }; - /// - /// Char array containing ~ / - /// - public static readonly char[] TildeForwardSlash = new char[] { '~', '/' }; - - - /// - /// Char array containing ~ / \ - /// - public static readonly char[] TildeForwardSlashBackSlash = new char[] { '~', '/', '\\' }; - - /// - /// Char array containing only ? - /// - public static readonly char[] QuestionMark = new char[] { '?' }; - - /// - /// Char array containing ? & - /// - public static readonly char[] QuestionMarkAmpersand = new char[] { '?', '&' }; - - /// - /// Char array containing XML 1.1 whitespace chars - /// - public static readonly char[] XmlWhitespaceChars = new char[] { ' ', '\t', '\r', '\n' }; - - /// - /// Char array containing only the Space char - /// - public static readonly char[] Space = new char[] { ' ' }; - - /// - /// Char array containing only ; - /// - public static readonly char[] Semicolon = new char[] { ';' }; - - /// - /// Char array containing a comma and a space - /// - public static readonly char[] CommaSpace = new char[] { ',', ' ' }; - - /// - /// Char array containing _ - - /// - public static readonly char[] UnderscoreDash = new char[] { '_', '-' }; - - /// - /// Char array containing = - /// - public static readonly char[] EqualsChar = new char[] { '=' }; - - /// - /// Char array containing > - /// - public static readonly char[] GreaterThan = new char[] { '>' }; - - /// - /// Char array containing | - /// - public static readonly char[] VerticalTab = new char[] { '|' }; - } + /// Char array containing only / + /// + public static readonly char[] ForwardSlash = {'/'}; + + /// + /// Char array containing only \ + /// + public static readonly char[] Backslash = {'\\'}; + + /// + /// Char array containing only ' + /// + public static readonly char[] SingleQuote = {'\''}; + + /// + /// Char array containing only " + /// + public static readonly char[] DoubleQuote = {'\"'}; + + + /// + /// Char array containing ' " + /// + public static readonly char[] DoubleQuoteSingleQuote = {'\"', '\''}; + + /// + /// Char array containing only _ + /// + public static readonly char[] Underscore = {'_'}; + + /// + /// Char array containing \n \r + /// + public static readonly char[] LineFeedCarriageReturn = {'\n', '\r'}; + + + /// + /// Char array containing \n + /// + public static readonly char[] LineFeed = {'\n'}; + + /// + /// Char array containing only , + /// + public static readonly char[] Comma = {','}; + + /// + /// Char array containing only & + /// + public static readonly char[] Ampersand = {'&'}; + + /// + /// Char array containing only \0 + /// + public static readonly char[] NullTerminator = {'\0'}; + + /// + /// Char array containing only . + /// + public static readonly char[] Period = {'.'}; + + /// + /// Char array containing only ~ + /// + public static readonly char[] Tilde = {'~'}; + + /// + /// Char array containing ~ / + /// + public static readonly char[] TildeForwardSlash = {'~', '/'}; + + + /// + /// Char array containing ~ / \ + /// + public static readonly char[] TildeForwardSlashBackSlash = {'~', '/', '\\'}; + + /// + /// Char array containing only ? + /// + public static readonly char[] QuestionMark = {'?'}; + + /// + /// Char array containing ? & + /// + public static readonly char[] QuestionMarkAmpersand = {'?', '&'}; + + /// + /// Char array containing XML 1.1 whitespace chars + /// + public static readonly char[] XmlWhitespaceChars = {' ', '\t', '\r', '\n'}; + + /// + /// Char array containing only the Space char + /// + public static readonly char[] Space = {' '}; + + /// + /// Char array containing only ; + /// + public static readonly char[] Semicolon = {';'}; + + /// + /// Char array containing a comma and a space + /// + public static readonly char[] CommaSpace = {',', ' '}; + + /// + /// Char array containing _ - + /// + public static readonly char[] UnderscoreDash = {'_', '-'}; + + /// + /// Char array containing = + /// + public static readonly char[] EqualsChar = {'='}; + + /// + /// Char array containing > + /// + public static readonly char[] GreaterThan = {'>'}; + + /// + /// Char array containing | + /// + public static readonly char[] VerticalTab = {'|'}; } } diff --git a/src/Umbraco.Core/Constants-Composing.cs b/src/Umbraco.Core/Constants-Composing.cs index 747a74b8d8ba..ff95a3d04b7c 100644 --- a/src/Umbraco.Core/Constants-Composing.cs +++ b/src/Umbraco.Core/Constants-Composing.cs @@ -1,25 +1,19 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Defines constants. +/// +public static partial class Constants { /// - /// Defines constants. + /// Defines constants for composition. /// - public static partial class Constants + public static class Composing { - /// - /// Defines constants for composition. - /// - public static class Composing + public static readonly string[] UmbracoCoreAssemblyNames = { - public static readonly string[] UmbracoCoreAssemblyNames = new[] - { - "Umbraco.Core", - "Umbraco.Infrastructure", - "Umbraco.PublishedCache.NuCache", - "Umbraco.Examine.Lucene", - "Umbraco.Web.Common", - "Umbraco.Web.BackOffice", - "Umbraco.Web.Website", - }; - } + "Umbraco.Core", "Umbraco.Infrastructure", "Umbraco.PublishedCache.NuCache", "Umbraco.Examine.Lucene", + "Umbraco.Web.Common", "Umbraco.Web.BackOffice", "Umbraco.Web.Website" + }; } } diff --git a/src/Umbraco.Core/Constants-Configuration.cs b/src/Umbraco.Core/Constants-Configuration.cs index b9375c2619e2..17f8a1bd9c30 100644 --- a/src/Umbraco.Core/Constants-Configuration.cs +++ b/src/Umbraco.Core/Constants-Configuration.cs @@ -1,76 +1,79 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public static partial class Constants { - public static partial class Constants + public static class Configuration { - public static class Configuration - { - /// - /// Case insensitive prefix for all configurations - /// - /// - /// ":" is used as marker for nested objects in json. E.g. "Umbraco:CMS:" = {"Umbraco":{"CMS":{....}} - /// - public const string ConfigPrefix = "Umbraco:CMS:"; - public const string ConfigContentPrefix = ConfigPrefix + "Content:"; - public const string ConfigContentNotificationsPrefix = ConfigContentPrefix + "Notifications:"; - public const string ConfigCorePrefix = ConfigPrefix + "Core:"; - public const string ConfigCustomErrorsPrefix = ConfigPrefix + "CustomErrors:"; - public const string ConfigGlobalPrefix = ConfigPrefix + "Global:"; - public const string ConfigGlobalId = ConfigGlobalPrefix + "Id"; - public const string ConfigGlobalDistributedLockingMechanism = ConfigGlobalPrefix + "DistributedLockingMechanism"; - public const string ConfigHostingPrefix = ConfigPrefix + "Hosting:"; - public const string ConfigModelsBuilderPrefix = ConfigPrefix + "ModelsBuilder:"; - public const string ConfigSecurityPrefix = ConfigPrefix + "Security:"; - public const string ConfigContentNotificationsEmail = ConfigContentNotificationsPrefix + "Email"; - public const string ConfigContentMacroErrors = ConfigContentPrefix + "MacroErrors"; - public const string ConfigGlobalUseHttps = ConfigGlobalPrefix + "UseHttps"; - public const string ConfigHostingDebug = ConfigHostingPrefix + "Debug"; - public const string ConfigCustomErrorsMode = ConfigCustomErrorsPrefix + "Mode"; - public const string ConfigActiveDirectory = ConfigPrefix + "ActiveDirectory"; - public const string ConfigLegacyPasswordMigration = ConfigPrefix + "LegacyPasswordMigration"; - public const string ConfigContent = ConfigPrefix + "Content"; - public const string ConfigCoreDebug = ConfigCorePrefix + "Debug"; - public const string ConfigExceptionFilter = ConfigPrefix + "ExceptionFilter"; - public const string ConfigGlobal = ConfigPrefix + "Global"; - public const string ConfigUnattended = ConfigPrefix + "Unattended"; - public const string ConfigHealthChecks = ConfigPrefix + "HealthChecks"; - public const string ConfigHosting = ConfigPrefix + "Hosting"; - public const string ConfigImaging = ConfigPrefix + "Imaging"; - public const string ConfigExamine = ConfigPrefix + "Examine"; - public const string ConfigKeepAlive = ConfigPrefix + "KeepAlive"; - public const string ConfigLogging = ConfigPrefix + "Logging"; - public const string ConfigMemberPassword = ConfigPrefix + "Security:MemberPassword"; - public const string ConfigModelsBuilder = ConfigPrefix + "ModelsBuilder"; - public const string ConfigNuCache = ConfigPrefix + "NuCache"; - public const string ConfigPlugins = ConfigPrefix + "Plugins"; - public const string ConfigRequestHandler = ConfigPrefix + "RequestHandler"; - public const string ConfigRuntime = ConfigPrefix + "Runtime"; - public const string ConfigRuntimeMinification = ConfigPrefix + "RuntimeMinification"; - public const string ConfigRuntimeMinificationVersion = ConfigRuntimeMinification + ":Version"; - public const string ConfigSecurity = ConfigPrefix + "Security"; - public const string ConfigBasicAuth = ConfigPrefix + "BasicAuth"; - public const string ConfigTours = ConfigPrefix + "Tours"; - public const string ConfigTypeFinder = ConfigPrefix + "TypeFinder"; - public const string ConfigWebRouting = ConfigPrefix + "WebRouting"; - public const string ConfigUserPassword = ConfigPrefix + "Security:UserPassword"; - public const string ConfigRichTextEditor = ConfigPrefix + "RichTextEditor"; - public const string ConfigPackageMigration = ConfigPrefix + "PackageMigration"; - public const string ConfigContentDashboard = ConfigPrefix + "ContentDashboard"; - public const string ConfigHelpPage = ConfigPrefix + "HelpPage"; - public const string ConfigInstallDefaultData = ConfigPrefix + "InstallDefaultData"; + /// + /// Case insensitive prefix for all configurations + /// + /// + /// ":" is used as marker for nested objects in json. E.g. "Umbraco:CMS:" = {"Umbraco":{"CMS":{....}} + /// + public const string ConfigPrefix = "Umbraco:CMS:"; + + public const string ConfigContentPrefix = ConfigPrefix + "Content:"; + public const string ConfigContentNotificationsPrefix = ConfigContentPrefix + "Notifications:"; + public const string ConfigCorePrefix = ConfigPrefix + "Core:"; + public const string ConfigCustomErrorsPrefix = ConfigPrefix + "CustomErrors:"; + public const string ConfigGlobalPrefix = ConfigPrefix + "Global:"; + public const string ConfigGlobalId = ConfigGlobalPrefix + "Id"; - public static class NamedOptions + public const string ConfigGlobalDistributedLockingMechanism = + ConfigGlobalPrefix + "DistributedLockingMechanism"; + + public const string ConfigHostingPrefix = ConfigPrefix + "Hosting:"; + public const string ConfigModelsBuilderPrefix = ConfigPrefix + "ModelsBuilder:"; + public const string ConfigSecurityPrefix = ConfigPrefix + "Security:"; + public const string ConfigContentNotificationsEmail = ConfigContentNotificationsPrefix + "Email"; + public const string ConfigContentMacroErrors = ConfigContentPrefix + "MacroErrors"; + public const string ConfigGlobalUseHttps = ConfigGlobalPrefix + "UseHttps"; + public const string ConfigHostingDebug = ConfigHostingPrefix + "Debug"; + public const string ConfigCustomErrorsMode = ConfigCustomErrorsPrefix + "Mode"; + public const string ConfigActiveDirectory = ConfigPrefix + "ActiveDirectory"; + public const string ConfigLegacyPasswordMigration = ConfigPrefix + "LegacyPasswordMigration"; + public const string ConfigContent = ConfigPrefix + "Content"; + public const string ConfigCoreDebug = ConfigCorePrefix + "Debug"; + public const string ConfigExceptionFilter = ConfigPrefix + "ExceptionFilter"; + public const string ConfigGlobal = ConfigPrefix + "Global"; + public const string ConfigUnattended = ConfigPrefix + "Unattended"; + public const string ConfigHealthChecks = ConfigPrefix + "HealthChecks"; + public const string ConfigHosting = ConfigPrefix + "Hosting"; + public const string ConfigImaging = ConfigPrefix + "Imaging"; + public const string ConfigExamine = ConfigPrefix + "Examine"; + public const string ConfigKeepAlive = ConfigPrefix + "KeepAlive"; + public const string ConfigLogging = ConfigPrefix + "Logging"; + public const string ConfigMemberPassword = ConfigPrefix + "Security:MemberPassword"; + public const string ConfigModelsBuilder = ConfigPrefix + "ModelsBuilder"; + public const string ConfigNuCache = ConfigPrefix + "NuCache"; + public const string ConfigPlugins = ConfigPrefix + "Plugins"; + public const string ConfigRequestHandler = ConfigPrefix + "RequestHandler"; + public const string ConfigRuntime = ConfigPrefix + "Runtime"; + public const string ConfigRuntimeMinification = ConfigPrefix + "RuntimeMinification"; + public const string ConfigRuntimeMinificationVersion = ConfigRuntimeMinification + ":Version"; + public const string ConfigSecurity = ConfigPrefix + "Security"; + public const string ConfigBasicAuth = ConfigPrefix + "BasicAuth"; + public const string ConfigTours = ConfigPrefix + "Tours"; + public const string ConfigTypeFinder = ConfigPrefix + "TypeFinder"; + public const string ConfigWebRouting = ConfigPrefix + "WebRouting"; + public const string ConfigUserPassword = ConfigPrefix + "Security:UserPassword"; + public const string ConfigRichTextEditor = ConfigPrefix + "RichTextEditor"; + public const string ConfigPackageMigration = ConfigPrefix + "PackageMigration"; + public const string ConfigContentDashboard = ConfigPrefix + "ContentDashboard"; + public const string ConfigHelpPage = ConfigPrefix + "HelpPage"; + public const string ConfigInstallDefaultData = ConfigPrefix + "InstallDefaultData"; + + public static class NamedOptions + { + public static class InstallDefaultData { - public static class InstallDefaultData - { - public const string Languages = "Languages"; + public const string Languages = "Languages"; - public const string DataTypes = "DataTypes"; + public const string DataTypes = "DataTypes"; - public const string MediaTypes = "MediaTypes"; + public const string MediaTypes = "MediaTypes"; - public const string MemberTypes = "MemberTypes"; - } + public const string MemberTypes = "MemberTypes"; } } } diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index cb34901e6c98..216ccfc778d4 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -1,343 +1,353 @@ -using System; +namespace Umbraco.Cms.Core; -namespace Umbraco.Cms.Core +public static partial class Constants { - public static partial class Constants + /// + /// Defines the identifiers for property-type alias conventions that are used within the Umbraco core. + /// + public static class Conventions { + public static class Migrations + { + public const string UmbracoUpgradePlanName = "Umbraco.Core"; + public const string KeyValuePrefix = "Umbraco.Core.Upgrader.State+"; + public const string UmbracoUpgradePlanKey = KeyValuePrefix + UmbracoUpgradePlanName; + } + + public static class PermissionCategories + { + public const string ContentCategory = "content"; + public const string AdministrationCategory = "administration"; + public const string StructureCategory = "structure"; + public const string OtherCategory = "other"; + } + + public static class PublicAccess + { + public const string MemberUsernameRuleType = "MemberUsername"; + public const string MemberRoleRuleType = "MemberRole"; + } + + + public static class DataTypes + { + public const string ListViewPrefix = "List View - "; + } + + /// + /// Constants for Umbraco Content property aliases. + /// + public static class Content + { + /// + /// Property alias for the Content's Url (internal) redirect. + /// + public const string InternalRedirectId = "umbracoInternalRedirectId"; + + /// + /// Property alias for the Content's navigational hide, (not actually used in core code). + /// + public const string NaviHide = "umbracoNaviHide"; + + /// + /// Property alias for the Content's Url redirect. + /// + public const string Redirect = "umbracoRedirect"; + + /// + /// Property alias for the Content's Url alias. + /// + public const string UrlAlias = "umbracoUrlAlias"; + + /// + /// Property alias for the Content's Url name. + /// + public const string UrlName = "umbracoUrlName"; + } + + /// + /// Constants for Umbraco Media property aliases. + /// + public static class Media + { + /// + /// Property alias for the Media's file name. + /// + public const string File = "umbracoFile"; + + /// + /// Property alias for the Media's width. + /// + public const string Width = "umbracoWidth"; + + /// + /// Property alias for the Media's height. + /// + public const string Height = "umbracoHeight"; + + /// + /// Property alias for the Media's file size (in bytes). + /// + public const string Bytes = "umbracoBytes"; + + /// + /// Property alias for the Media's file extension. + /// + public const string Extension = "umbracoExtension"; + + /// + /// The default height/width of an image file if the size can't be determined from the metadata + /// + public const int DefaultSize = 200; + } + /// - /// Defines the identifiers for property-type alias conventions that are used within the Umbraco core. + /// Defines the alias identifiers for Umbraco media types. /// - public static class Conventions + public static class MediaTypes { - public static class Migrations - { - public const string UmbracoUpgradePlanName = "Umbraco.Core"; - public const string KeyValuePrefix = "Umbraco.Core.Upgrader.State+"; - public const string UmbracoUpgradePlanKey = KeyValuePrefix + UmbracoUpgradePlanName; - } - - public static class PermissionCategories - { - public const string ContentCategory = "content"; - public const string AdministrationCategory = "administration"; - public const string StructureCategory = "structure"; - public const string OtherCategory = "other"; - } - - public static class PublicAccess - { - public const string MemberUsernameRuleType = "MemberUsername"; - public const string MemberRoleRuleType = "MemberRole"; - } - - - public static class DataTypes - { - public const string ListViewPrefix = "List View - "; - } - - /// - /// Constants for Umbraco Content property aliases. - /// - public static class Content - { - /// - /// Property alias for the Content's Url (internal) redirect. - /// - public const string InternalRedirectId = "umbracoInternalRedirectId"; - - /// - /// Property alias for the Content's navigational hide, (not actually used in core code). - /// - public const string NaviHide = "umbracoNaviHide"; - - /// - /// Property alias for the Content's Url redirect. - /// - public const string Redirect = "umbracoRedirect"; - - /// - /// Property alias for the Content's Url alias. - /// - public const string UrlAlias = "umbracoUrlAlias"; - - /// - /// Property alias for the Content's Url name. - /// - public const string UrlName = "umbracoUrlName"; - } - - /// - /// Constants for Umbraco Media property aliases. - /// - public static class Media - { - /// - /// Property alias for the Media's file name. - /// - public const string File = "umbracoFile"; - - /// - /// Property alias for the Media's width. - /// - public const string Width = "umbracoWidth"; - - /// - /// Property alias for the Media's height. - /// - public const string Height = "umbracoHeight"; - - /// - /// Property alias for the Media's file size (in bytes). - /// - public const string Bytes = "umbracoBytes"; - - /// - /// Property alias for the Media's file extension. - /// - public const string Extension = "umbracoExtension"; - - /// - /// The default height/width of an image file if the size can't be determined from the metadata - /// - public const int DefaultSize = 200; - } - - /// - /// Defines the alias identifiers for Umbraco media types. - /// - public static class MediaTypes - { - /// - /// MediaType alias for a file. - /// - public const string File = "File"; - - /// - /// MediaType alias for a folder. - /// - public const string Folder = "Folder"; - - /// - /// MediaType alias for an image. - /// - public const string Image = "Image"; - - /// - /// MediaType name for a video. - /// - public const string Video = "Video"; - - /// - /// MediaType name for an audio. - /// - public const string Audio = "Audio"; - - /// - /// MediaType name for an article. - /// - public const string Article = "Article"; - - /// - /// MediaType name for vector graphics. - /// - public const string VectorGraphics = "VectorGraphics"; - - /// - /// MediaType alias for a video. - /// - public const string VideoAlias = "umbracoMediaVideo"; - - /// - /// MediaType alias for an audio. - /// - public const string AudioAlias = "umbracoMediaAudio"; - - /// - /// MediaType alias for an article. - /// - public const string ArticleAlias = "umbracoMediaArticle"; - - /// - /// MediaType alias for vector graphics. - /// - public const string VectorGraphicsAlias = "umbracoMediaVectorGraphics"; - - /// - /// MediaType alias indicating allowing auto-selection. - /// - public const string AutoSelect = "umbracoAutoSelect"; - } - - /// - /// Constants for Umbraco Member property aliases. - /// - public static class Member - { - /// - /// if a role starts with __umbracoRole we won't show it as it's an internal role used for public access - /// - public static readonly string InternalRolePrefix = "__umbracoRole"; - - /// - /// Property alias for the Comments on a Member - /// - public const string Comments = "umbracoMemberComments"; - - public const string CommentsLabel = "Comments"; - - /// - /// Property alias for the Approved boolean of a Member - /// - [Obsolete("IsApproved is no longer property data, access the property directly on IMember instead, scheduled for removal in V11")] - public const string IsApproved = "umbracoMemberApproved"; - [Obsolete("Use the stateApproved translation in the user area instead, scheduled for removal in V11")] - public const string IsApprovedLabel = "Is Approved"; - - /// - /// Property alias for the Locked out boolean of a Member - /// - [Obsolete("IsLockedOut is no longer property data, access the property directly on IMember instead, scheduled for removal in V11")] - public const string IsLockedOut = "umbracoMemberLockedOut"; - [Obsolete("Use the stateLockedOut translation in the user area instead, scheduled for removal in V11")] - public const string IsLockedOutLabel = "Is Locked Out"; - - /// - /// Property alias for the last date the Member logged in - /// - [Obsolete("LastLoginDate is no longer property data, access the property directly on IMember instead, scheduled for removal in V11")] - public const string LastLoginDate = "umbracoMemberLastLogin"; - [Obsolete("Use the lastLogin translation in the user area instead, scheduled for removal in V11")] - public const string LastLoginDateLabel = "Last Login Date"; - - /// - /// Property alias for the last date a Member changed its password - /// - [Obsolete("LastPasswordChangeDate is no longer property data, access the property directly on IMember instead, scheduled for removal in V11")] - public const string LastPasswordChangeDate = "umbracoMemberLastPasswordChangeDate"; - [Obsolete("Use the lastPasswordChangeDate translation in the user area instead, scheduled for removal in V11")] - public const string LastPasswordChangeDateLabel = "Last Password Change Date"; - - /// - /// Property alias for the last date a Member was locked out - /// - [Obsolete("LastLockoutDate is no longer property data, access the property directly on IMember instead, scheduled for removal in V11")] - public const string LastLockoutDate = "umbracoMemberLastLockoutDate"; - [Obsolete("Use the lastLockoutDate translation in the user area instead, scheduled for removal in V11")] - public const string LastLockoutDateLabel = "Last Lockout Date"; - - /// - /// Property alias for the number of failed login attempts - /// - [Obsolete("FailedPasswordAttempts is no longer property data, access the property directly on IMember instead, scheduled for removal in V11")] - public const string FailedPasswordAttempts = "umbracoMemberFailedPasswordAttempts"; - [Obsolete("Use the failedPasswordAttempts translation in the user area instead, scheduled for removal in V11")] - public const string FailedPasswordAttemptsLabel = "Failed Password Attempts"; - - /// - /// The standard properties group alias for membership properties. - /// - public const string StandardPropertiesGroupAlias = "membership"; - - /// - /// The standard properties group name for membership properties. - /// - public const string StandardPropertiesGroupName = "Membership"; - } - - /// - /// Defines the alias identifiers for Umbraco member types. - /// - public static class MemberTypes - { - /// - /// MemberType alias for default member type. - /// - public const string DefaultAlias = "Member"; - - public const string SystemDefaultProtectType = "_umbracoSystemDefaultProtectType"; - - public const string AllMembersListId = "all-members"; - } - - /// - /// Constants for Umbraco URLs/Querystrings. - /// - public static class Url - { - /// - /// Querystring parameter name used for Umbraco's alternative template functionality. - /// - public const string AltTemplate = "altTemplate"; - } - - /// - /// Defines the alias identifiers for built-in Umbraco relation types. - /// - public static class RelationTypes - { - /// - /// Name for default relation type "Related Media". - /// - public const string RelatedMediaName = "Related Media"; - - /// - /// Alias for default relation type "Related Media" - /// - public const string RelatedMediaAlias = "umbMedia"; - - /// - /// Name for default relation type "Related Document". - /// - public const string RelatedDocumentName = "Related Document"; - - /// - /// Alias for default relation type "Related Document" - /// - public const string RelatedDocumentAlias = "umbDocument"; - - /// - /// Name for default relation type "Relate Document On Copy". - /// - public const string RelateDocumentOnCopyName = "Relate Document On Copy"; - - /// - /// Alias for default relation type "Relate Document On Copy". - /// - public const string RelateDocumentOnCopyAlias = "relateDocumentOnCopy"; - - /// - /// Name for default relation type "Relate Parent Document On Delete". - /// - public const string RelateParentDocumentOnDeleteName = "Relate Parent Document On Delete"; - - /// - /// Alias for default relation type "Relate Parent Document On Delete". - /// - public const string RelateParentDocumentOnDeleteAlias = "relateParentDocumentOnDelete"; - - /// - /// Name for default relation type "Relate Parent Media Folder On Delete". - /// - public const string RelateParentMediaFolderOnDeleteName = "Relate Parent Media Folder On Delete"; - - /// - /// Alias for default relation type "Relate Parent Media Folder On Delete". - /// - public const string RelateParentMediaFolderOnDeleteAlias = "relateParentMediaFolderOnDelete"; - - /// - /// Returns the types of relations that are automatically tracked - /// - /// - /// Developers should not manually use these relation types since they will all be cleared whenever an entity - /// (content, media or member) is saved since they are auto-populated based on property values. - /// - public static string[] AutomaticRelationTypes { get; } = new[] { RelatedMediaAlias, RelatedDocumentAlias }; - - //TODO: return a list of built in types so we can use that to prevent deletion in the uI - } + /// + /// MediaType alias for a file. + /// + public const string File = "File"; + + /// + /// MediaType alias for a folder. + /// + public const string Folder = "Folder"; + + /// + /// MediaType alias for an image. + /// + public const string Image = "Image"; + + /// + /// MediaType name for a video. + /// + public const string Video = "Video"; + + /// + /// MediaType name for an audio. + /// + public const string Audio = "Audio"; + + /// + /// MediaType name for an article. + /// + public const string Article = "Article"; + + /// + /// MediaType name for vector graphics. + /// + public const string VectorGraphics = "VectorGraphics"; + + /// + /// MediaType alias for a video. + /// + public const string VideoAlias = "umbracoMediaVideo"; + + /// + /// MediaType alias for an audio. + /// + public const string AudioAlias = "umbracoMediaAudio"; + + /// + /// MediaType alias for an article. + /// + public const string ArticleAlias = "umbracoMediaArticle"; + + /// + /// MediaType alias for vector graphics. + /// + public const string VectorGraphicsAlias = "umbracoMediaVectorGraphics"; + + /// + /// MediaType alias indicating allowing auto-selection. + /// + public const string AutoSelect = "umbracoAutoSelect"; + } + + /// + /// Constants for Umbraco Member property aliases. + /// + public static class Member + { + /// + /// Property alias for the Comments on a Member + /// + public const string Comments = "umbracoMemberComments"; + + public const string CommentsLabel = "Comments"; + + /// + /// Property alias for the Approved boolean of a Member + /// + [Obsolete( + "IsApproved is no longer property data, access the property directly on IMember instead, scheduled for removal in V11")] + public const string IsApproved = "umbracoMemberApproved"; + + [Obsolete("Use the stateApproved translation in the user area instead, scheduled for removal in V11")] + public const string IsApprovedLabel = "Is Approved"; + + /// + /// Property alias for the Locked out boolean of a Member + /// + [Obsolete( + "IsLockedOut is no longer property data, access the property directly on IMember instead, scheduled for removal in V11")] + public const string IsLockedOut = "umbracoMemberLockedOut"; + + [Obsolete("Use the stateLockedOut translation in the user area instead, scheduled for removal in V11")] + public const string IsLockedOutLabel = "Is Locked Out"; + + /// + /// Property alias for the last date the Member logged in + /// + [Obsolete( + "LastLoginDate is no longer property data, access the property directly on IMember instead, scheduled for removal in V11")] + public const string LastLoginDate = "umbracoMemberLastLogin"; + + [Obsolete("Use the lastLogin translation in the user area instead, scheduled for removal in V11")] + public const string LastLoginDateLabel = "Last Login Date"; + + /// + /// Property alias for the last date a Member changed its password + /// + [Obsolete( + "LastPasswordChangeDate is no longer property data, access the property directly on IMember instead, scheduled for removal in V11")] + public const string LastPasswordChangeDate = "umbracoMemberLastPasswordChangeDate"; + + [Obsolete( + "Use the lastPasswordChangeDate translation in the user area instead, scheduled for removal in V11")] + public const string LastPasswordChangeDateLabel = "Last Password Change Date"; + + /// + /// Property alias for the last date a Member was locked out + /// + [Obsolete( + "LastLockoutDate is no longer property data, access the property directly on IMember instead, scheduled for removal in V11")] + public const string LastLockoutDate = "umbracoMemberLastLockoutDate"; + + [Obsolete("Use the lastLockoutDate translation in the user area instead, scheduled for removal in V11")] + public const string LastLockoutDateLabel = "Last Lockout Date"; + + /// + /// Property alias for the number of failed login attempts + /// + [Obsolete( + "FailedPasswordAttempts is no longer property data, access the property directly on IMember instead, scheduled for removal in V11")] + public const string FailedPasswordAttempts = "umbracoMemberFailedPasswordAttempts"; + + [Obsolete( + "Use the failedPasswordAttempts translation in the user area instead, scheduled for removal in V11")] + public const string FailedPasswordAttemptsLabel = "Failed Password Attempts"; + + /// + /// The standard properties group alias for membership properties. + /// + public const string StandardPropertiesGroupAlias = "membership"; + + /// + /// The standard properties group name for membership properties. + /// + public const string StandardPropertiesGroupName = "Membership"; + + /// + /// if a role starts with __umbracoRole we won't show it as it's an internal role used for public access + /// + public static readonly string InternalRolePrefix = "__umbracoRole"; + } + + /// + /// Defines the alias identifiers for Umbraco member types. + /// + public static class MemberTypes + { + /// + /// MemberType alias for default member type. + /// + public const string DefaultAlias = "Member"; + + public const string SystemDefaultProtectType = "_umbracoSystemDefaultProtectType"; + + public const string AllMembersListId = "all-members"; + } + + /// + /// Constants for Umbraco URLs/Querystrings. + /// + public static class Url + { + /// + /// Querystring parameter name used for Umbraco's alternative template functionality. + /// + public const string AltTemplate = "altTemplate"; + } + + /// + /// Defines the alias identifiers for built-in Umbraco relation types. + /// + public static class RelationTypes + { + /// + /// Name for default relation type "Related Media". + /// + public const string RelatedMediaName = "Related Media"; + + /// + /// Alias for default relation type "Related Media" + /// + public const string RelatedMediaAlias = "umbMedia"; + + /// + /// Name for default relation type "Related Document". + /// + public const string RelatedDocumentName = "Related Document"; + + /// + /// Alias for default relation type "Related Document" + /// + public const string RelatedDocumentAlias = "umbDocument"; + + /// + /// Name for default relation type "Relate Document On Copy". + /// + public const string RelateDocumentOnCopyName = "Relate Document On Copy"; + + /// + /// Alias for default relation type "Relate Document On Copy". + /// + public const string RelateDocumentOnCopyAlias = "relateDocumentOnCopy"; + + /// + /// Name for default relation type "Relate Parent Document On Delete". + /// + public const string RelateParentDocumentOnDeleteName = "Relate Parent Document On Delete"; + + /// + /// Alias for default relation type "Relate Parent Document On Delete". + /// + public const string RelateParentDocumentOnDeleteAlias = "relateParentDocumentOnDelete"; + + /// + /// Name for default relation type "Relate Parent Media Folder On Delete". + /// + public const string RelateParentMediaFolderOnDeleteName = "Relate Parent Media Folder On Delete"; + + /// + /// Alias for default relation type "Relate Parent Media Folder On Delete". + /// + public const string RelateParentMediaFolderOnDeleteAlias = "relateParentMediaFolderOnDelete"; + + /// + /// Returns the types of relations that are automatically tracked + /// + /// + /// Developers should not manually use these relation types since they will all be cleared whenever an entity + /// (content, media or member) is saved since they are auto-populated based on property values. + /// + public static string[] AutomaticRelationTypes { get; } = {RelatedMediaAlias, RelatedDocumentAlias}; + //TODO: return a list of built in types so we can use that to prevent deletion in the uI } } } diff --git a/src/Umbraco.Core/Constants-DataTypes.cs b/src/Umbraco.Core/Constants-DataTypes.cs index ba8827cd26a9..b1c8e240cbdd 100644 --- a/src/Umbraco.Core/Constants-DataTypes.cs +++ b/src/Umbraco.Core/Constants-DataTypes.cs @@ -1,461 +1,455 @@ -using System; +namespace Umbraco.Cms.Core; -namespace Umbraco.Cms.Core +public static partial class Constants { - public static partial class Constants + public static class DataTypes { - public static class DataTypes + //NOTE: unfortunately due to backwards compat we can't move/rename these, with the addition of the GUID + //constants, it would make more sense to have these suffixed with "ID" or in a Subclass called "INT", for + //now all we can do is make a subclass called Guids to put the GUID IDs. + + public const int LabelString = System.DefaultLabelDataTypeId; + public const int LabelInt = -91; + public const int LabelBigint = -93; + public const int LabelDateTime = -94; + public const int LabelTime = -98; + public const int LabelDecimal = -99; + + public const int Textarea = -89; + public const int Textbox = -88; + public const int RichtextEditor = -87; + public const int Boolean = -49; + public const int DateTime = -36; + public const int DropDownSingle = -39; + public const int DropDownMultiple = -42; + public const int Upload = -90; + public const int UploadVideo = -100; + public const int UploadAudio = -101; + public const int UploadArticle = -102; + public const int UploadVectorGraphics = -103; + + public const int DefaultContentListView = -95; + public const int DefaultMediaListView = -96; + public const int DefaultMembersListView = -97; + + public const int ImageCropper = 1043; + public const int Tags = 1041; + + public static class ReservedPreValueKeys { - //NOTE: unfortunately due to backwards compat we can't move/rename these, with the addition of the GUID - //constants, it would make more sense to have these suffixed with "ID" or in a Subclass called "INT", for - //now all we can do is make a subclass called Guids to put the GUID IDs. - - public const int LabelString = System.DefaultLabelDataTypeId; - public const int LabelInt = -91; - public const int LabelBigint = -93; - public const int LabelDateTime = -94; - public const int LabelTime = -98; - public const int LabelDecimal = -99; - - public const int Textarea = -89; - public const int Textbox = -88; - public const int RichtextEditor = -87; - public const int Boolean = -49; - public const int DateTime = -36; - public const int DropDownSingle = -39; - public const int DropDownMultiple = -42; - public const int Upload = -90; - public const int UploadVideo = -100; - public const int UploadAudio = -101; - public const int UploadArticle = -102; - public const int UploadVectorGraphics = -103; - - public const int DefaultContentListView = -95; - public const int DefaultMediaListView = -96; - public const int DefaultMembersListView = -97; - - public const int ImageCropper = 1043; - public const int Tags = 1041; + public const string IgnoreUserStartNodes = "ignoreUserStartNodes"; + } - public static class ReservedPreValueKeys - { - public const string IgnoreUserStartNodes = "ignoreUserStartNodes"; - } - - /// - /// Defines the identifiers for Umbraco data types as constants for easy centralized access/management. + /// + /// Defines the identifiers for Umbraco data types as constants for easy centralized access/management. + /// + public static class Guids + { + /// + /// Guid for Content Picker as string /// - public static class Guids - { + public const string ContentPicker = "FD1E0DA5-5606-4862-B679-5D0CF3A52A59"; - /// - /// Guid for Content Picker as string - /// - public const string ContentPicker = "FD1E0DA5-5606-4862-B679-5D0CF3A52A59"; - /// - /// Guid for Content Picker - /// - public static readonly Guid ContentPickerGuid = new Guid(ContentPicker); + /// + /// Guid for Member Picker as string + /// + public const string MemberPicker = "1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"; - /// - /// Guid for Member Picker as string - /// - public const string MemberPicker = "1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"; + /// + /// Guid for Media Picker as string + /// + public const string MediaPicker = "135D60E0-64D9-49ED-AB08-893C9BA44AE5"; - /// - /// Guid for Member Picker - /// - public static readonly Guid MemberPickerGuid = new Guid(MemberPicker); + /// + /// Guid for Multiple Media Picker as string + /// + public const string MultipleMediaPicker = "9DBBCBBB-2327-434A-B355-AF1B84E5010A"; - /// - /// Guid for Media Picker as string - /// - public const string MediaPicker = "135D60E0-64D9-49ED-AB08-893C9BA44AE5"; - /// - /// Guid for Media Picker - /// - public static readonly Guid MediaPickerGuid = new Guid(MediaPicker); + /// + /// Guid for Media Picker v3 as string + /// + public const string MediaPicker3 = "4309A3EA-0D78-4329-A06C-C80B036AF19A"; + /// + /// Guid for Media Picker v3 multiple as string + /// + public const string MediaPicker3Multiple = "1B661F40-2242-4B44-B9CB-3990EE2B13C0"; - /// - /// Guid for Multiple Media Picker as string - /// - public const string MultipleMediaPicker = "9DBBCBBB-2327-434A-B355-AF1B84E5010A"; - /// - /// Guid for Multiple Media Picker - /// - public static readonly Guid MultipleMediaPickerGuid = new Guid(MultipleMediaPicker); + /// + /// Guid for Media Picker v3 single-image as string + /// + public const string MediaPicker3SingleImage = "AD9F0CF2-BDA2-45D5-9EA1-A63CFC873FD3"; - /// - /// Guid for Media Picker v3 as string - /// - public const string MediaPicker3 = "4309A3EA-0D78-4329-A06C-C80B036AF19A"; + /// + /// Guid for Media Picker v3 multi-image as string + /// + public const string MediaPicker3MultipleImages = "0E63D883-B62B-4799-88C3-157F82E83ECC"; - /// - /// Guid for Media Picker v3 - /// - public static readonly Guid MediaPicker3Guid = new Guid(MediaPicker3); - /// - /// Guid for Media Picker v3 multiple as string - /// - public const string MediaPicker3Multiple = "1B661F40-2242-4B44-B9CB-3990EE2B13C0"; + /// + /// Guid for Related Links as string + /// + public const string RelatedLinks = "B4E3535A-1753-47E2-8568-602CF8CFEE6F"; - /// - /// Guid for Media Picker v3 multiple - /// - public static readonly Guid MediaPicker3MultipleGuid = new Guid(MediaPicker3Multiple); + /// + /// Guid for Member as string + /// + public const string Member = "d59be02f-1df9-4228-aa1e-01917d806cda"; - /// - /// Guid for Media Picker v3 single-image as string - /// - public const string MediaPicker3SingleImage = "AD9F0CF2-BDA2-45D5-9EA1-A63CFC873FD3"; - /// - /// Guid for Media Picker v3 single-image - /// - public static readonly Guid MediaPicker3SingleImageGuid = new Guid(MediaPicker3SingleImage); + /// + /// Guid for Image Cropper as string + /// + public const string ImageCropper = "1df9f033-e6d4-451f-b8d2-e0cbc50a836f"; - /// - /// Guid for Media Picker v3 multi-image as string - /// - public const string MediaPicker3MultipleImages = "0E63D883-B62B-4799-88C3-157F82E83ECC"; + /// + /// Guid for Tags as string + /// + public const string Tags = "b6b73142-b9c1-4bf8-a16d-e1c23320b549"; - /// - /// Guid for Media Picker v3 multi-image - /// - public static readonly Guid MediaPicker3MultipleImagesGuid = new Guid(MediaPicker3MultipleImages); + /// + /// Guid for List View - Content as string + /// + public const string ListViewContent = "C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"; - /// - /// Guid for Related Links as string - /// - public const string RelatedLinks = "B4E3535A-1753-47E2-8568-602CF8CFEE6F"; - /// - /// Guid for Related Links - /// - public static readonly Guid RelatedLinksGuid = new Guid(RelatedLinks); + /// + /// Guid for List View - Media as string + /// + public const string ListViewMedia = "3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"; - /// - /// Guid for Member as string - /// - public const string Member = "d59be02f-1df9-4228-aa1e-01917d806cda"; + /// + /// Guid for List View - Members as string + /// + public const string ListViewMembers = "AA2C52A0-CE87-4E65-A47C-7DF09358585D"; - /// - /// Guid for Member - /// - public static readonly Guid MemberGuid = new Guid(Member); + /// + /// Guid for Date Picker with time as string + /// + public const string DatePickerWithTime = "e4d66c0f-b935-4200-81f0-025f7256b89a"; - /// - /// Guid for Image Cropper as string - /// - public const string ImageCropper = "1df9f033-e6d4-451f-b8d2-e0cbc50a836f"; + /// + /// Guid for Approved Color as string + /// + public const string ApprovedColor = "0225af17-b302-49cb-9176-b9f35cab9c17"; - /// - /// Guid for Image Cropper - /// - public static readonly Guid ImageCropperGuid = new Guid(ImageCropper); + /// + /// Guid for Dropdown multiple as string + /// + public const string DropdownMultiple = "f38f0ac7-1d27-439c-9f3f-089cd8825a53"; - /// - /// Guid for Tags as string - /// - public const string Tags = "b6b73142-b9c1-4bf8-a16d-e1c23320b549"; - /// - /// Guid for Tags - /// - public static readonly Guid TagsGuid = new Guid(Tags); + /// + /// Guid for Radiobox as string + /// + public const string Radiobox = "bb5f57c9-ce2b-4bb9-b697-4caca783a805"; - /// - /// Guid for List View - Content as string - /// - public const string ListViewContent = "C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"; + /// + /// Guid for Date Picker as string + /// + public const string DatePicker = "5046194e-4237-453c-a547-15db3a07c4e1"; - /// - /// Guid for List View - Content - /// - public static readonly Guid ListViewContentGuid = new Guid(ListViewContent); + /// + /// Guid for Dropdown as string + /// + public const string Dropdown = "0b6a45e7-44ba-430d-9da5-4e46060b9e03"; - /// - /// Guid for List View - Media as string - /// - public const string ListViewMedia = "3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"; - /// - /// Guid for List View - Media - /// - public static readonly Guid ListViewMediaGuid = new Guid(ListViewMedia); + /// + /// Guid for Checkbox list as string + /// + public const string CheckboxList = "fbaf13a8-4036-41f2-93a3-974f678c312a"; - /// - /// Guid for List View - Members as string - /// - public const string ListViewMembers = "AA2C52A0-CE87-4E65-A47C-7DF09358585D"; + /// + /// Guid for Checkbox as string + /// + public const string Checkbox = "92897bc6-a5f3-4ffe-ae27-f2e7e33dda49"; - /// - /// Guid for List View - Members - /// - public static readonly Guid ListViewMembersGuid = new Guid(ListViewMembers); - /// - /// Guid for Date Picker with time as string - /// - public const string DatePickerWithTime = "e4d66c0f-b935-4200-81f0-025f7256b89a"; + /// + /// Guid for Numeric as string + /// + public const string Numeric = "2e6d3631-066e-44b8-aec4-96f09099b2b5"; - /// - /// Guid for Date Picker with time - /// - public static readonly Guid DatePickerWithTimeGuid = new Guid(DatePickerWithTime); + /// + /// Guid for Richtext editor as string + /// + public const string RichtextEditor = "ca90c950-0aff-4e72-b976-a30b1ac57dad"; - /// - /// Guid for Approved Color as string - /// - public const string ApprovedColor = "0225af17-b302-49cb-9176-b9f35cab9c17"; - /// - /// Guid for Approved Color - /// - public static readonly Guid ApprovedColorGuid = new Guid(ApprovedColor); + /// + /// Guid for Textstring as string + /// + public const string Textstring = "0cc0eba1-9960-42c9-bf9b-60e150b429ae"; - /// - /// Guid for Dropdown multiple as string - /// - public const string DropdownMultiple = "f38f0ac7-1d27-439c-9f3f-089cd8825a53"; + /// + /// Guid for Textarea as string + /// + public const string Textarea = "c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"; - /// - /// Guid for Dropdown multiple - /// - public static readonly Guid DropdownMultipleGuid = new Guid(DropdownMultiple); + /// + /// Guid for Upload as string + /// + public const string Upload = "84c6b441-31df-4ffe-b67e-67d5bc3ae65a"; - /// - /// Guid for Radiobox as string - /// - public const string Radiobox = "bb5f57c9-ce2b-4bb9-b697-4caca783a805"; + /// + /// Guid for UploadVideo as string + /// + public const string UploadVideo = "70575fe7-9812-4396-bbe1-c81a76db71b5"; - /// - /// Guid for Radiobox - /// - public static readonly Guid RadioboxGuid = new Guid(Radiobox); + /// + /// Guid for UploadAudio as string + /// + public const string UploadAudio = "8f430dd6-4e96-447e-9dc0-cb552c8cd1f3"; + /// + /// Guid for UploadArticle as string + /// + public const string UploadArticle = "bc1e266c-dac4-4164-bf08-8a1ec6a7143d"; - /// - /// Guid for Date Picker as string - /// - public const string DatePicker = "5046194e-4237-453c-a547-15db3a07c4e1"; + /// + /// Guid for UploadVectorGraphics as string + /// + public const string UploadVectorGraphics = "215cb418-2153-4429-9aef-8c0f0041191b"; - /// - /// Guid for Date Picker - /// - public static readonly Guid DatePickerGuid = new Guid(DatePicker); + /// + /// Guid for Label as string + /// + public const string LabelString = "f0bc4bfb-b499-40d6-ba86-058885a5178c"; - /// - /// Guid for Dropdown as string - /// - public const string Dropdown = "0b6a45e7-44ba-430d-9da5-4e46060b9e03"; + /// + /// Guid for Label as int + /// + public const string LabelInt = "8e7f995c-bd81-4627-9932-c40e568ec788"; - /// - /// Guid for Dropdown - /// - public static readonly Guid DropdownGuid = new Guid(Dropdown); + /// + /// Guid for Label as big int + /// + public const string LabelBigInt = "930861bf-e262-4ead-a704-f99453565708"; + /// + /// Guid for Label as date time + /// + public const string LabelDateTime = "0e9794eb-f9b5-4f20-a788-93acd233a7e4"; - /// - /// Guid for Checkbox list as string - /// - public const string CheckboxList = "fbaf13a8-4036-41f2-93a3-974f678c312a"; + /// + /// Guid for Label as time + /// + public const string LabelTime = "a97cec69-9b71-4c30-8b12-ec398860d7e8"; - /// - /// Guid for Checkbox list - /// - public static readonly Guid CheckboxListGuid = new Guid(CheckboxList); + /// + /// Guid for Label as decimal + /// + public const string LabelDecimal = "8f1ef1e1-9de4-40d3-a072-6673f631ca64"; + /// + /// Guid for Content Picker + /// + public static readonly Guid ContentPickerGuid = new(ContentPicker); - /// - /// Guid for Checkbox as string - /// - public const string Checkbox = "92897bc6-a5f3-4ffe-ae27-f2e7e33dda49"; + /// + /// Guid for Member Picker + /// + public static readonly Guid MemberPickerGuid = new(MemberPicker); - /// - /// Guid for Checkbox - /// - public static readonly Guid CheckboxGuid = new Guid(Checkbox); + /// + /// Guid for Media Picker + /// + public static readonly Guid MediaPickerGuid = new(MediaPicker); + /// + /// Guid for Multiple Media Picker + /// + public static readonly Guid MultipleMediaPickerGuid = new(MultipleMediaPicker); - /// - /// Guid for Numeric as string - /// - public const string Numeric = "2e6d3631-066e-44b8-aec4-96f09099b2b5"; + /// + /// Guid for Media Picker v3 + /// + public static readonly Guid MediaPicker3Guid = new(MediaPicker3); - /// - /// Guid for Dropdown - /// - public static readonly Guid NumericGuid = new Guid(Numeric); + /// + /// Guid for Media Picker v3 multiple + /// + public static readonly Guid MediaPicker3MultipleGuid = new(MediaPicker3Multiple); + /// + /// Guid for Media Picker v3 single-image + /// + public static readonly Guid MediaPicker3SingleImageGuid = new(MediaPicker3SingleImage); + + /// + /// Guid for Media Picker v3 multi-image + /// + public static readonly Guid MediaPicker3MultipleImagesGuid = new(MediaPicker3MultipleImages); + + /// + /// Guid for Related Links + /// + public static readonly Guid RelatedLinksGuid = new(RelatedLinks); + + /// + /// Guid for Member + /// + public static readonly Guid MemberGuid = new(Member); + + /// + /// Guid for Image Cropper + /// + public static readonly Guid ImageCropperGuid = new(ImageCropper); + + /// + /// Guid for Tags + /// + public static readonly Guid TagsGuid = new(Tags); + + /// + /// Guid for List View - Content + /// + public static readonly Guid ListViewContentGuid = new(ListViewContent); + + /// + /// Guid for List View - Media + /// + public static readonly Guid ListViewMediaGuid = new(ListViewMedia); + + /// + /// Guid for List View - Members + /// + public static readonly Guid ListViewMembersGuid = new(ListViewMembers); + + /// + /// Guid for Date Picker with time + /// + public static readonly Guid DatePickerWithTimeGuid = new(DatePickerWithTime); + + /// + /// Guid for Approved Color + /// + public static readonly Guid ApprovedColorGuid = new(ApprovedColor); + + /// + /// Guid for Dropdown multiple + /// + public static readonly Guid DropdownMultipleGuid = new(DropdownMultiple); + + /// + /// Guid for Radiobox + /// + public static readonly Guid RadioboxGuid = new(Radiobox); + + /// + /// Guid for Date Picker + /// + public static readonly Guid DatePickerGuid = new(DatePicker); + + /// + /// Guid for Dropdown + /// + public static readonly Guid DropdownGuid = new(Dropdown); - /// - /// Guid for Richtext editor as string - /// - public const string RichtextEditor = "ca90c950-0aff-4e72-b976-a30b1ac57dad"; + /// + /// Guid for Checkbox list + /// + public static readonly Guid CheckboxListGuid = new(CheckboxList); + + /// + /// Guid for Checkbox + /// + public static readonly Guid CheckboxGuid = new(Checkbox); + + /// + /// Guid for Dropdown + /// + public static readonly Guid NumericGuid = new(Numeric); + + /// + /// Guid for Richtext editor + /// + public static readonly Guid RichtextEditorGuid = new(RichtextEditor); + + /// + /// Guid for Textstring + /// + public static readonly Guid TextstringGuid = new(Textstring); + + /// + /// Guid for Dropdown + /// + public static readonly Guid TextareaGuid = new(Textarea); + + /// + /// Guid for Upload + /// + public static readonly Guid UploadGuid = new(Upload); + + /// + /// Guid for UploadVideo + /// + public static readonly Guid UploadVideoGuid = new(UploadVideo); + + /// + /// Guid for UploadAudio + /// + public static readonly Guid UploadAudioGuid = new(UploadAudio); - /// - /// Guid for Richtext editor - /// - public static readonly Guid RichtextEditorGuid = new Guid(RichtextEditor); + /// + /// Guid for UploadArticle + /// + public static readonly Guid UploadArticleGuid = new(UploadArticle); + + /// + /// Guid for UploadVectorGraphics + /// + public static readonly Guid UploadVectorGraphicsGuid = new(UploadVectorGraphics); + + /// + /// Guid for Label string + /// + public static readonly Guid LabelStringGuid = new(LabelString); + /// + /// Guid for Label int + /// + public static readonly Guid LabelIntGuid = new(LabelInt); - /// - /// Guid for Textstring as string - /// - public const string Textstring = "0cc0eba1-9960-42c9-bf9b-60e150b429ae"; + /// + /// Guid for Label big int + /// + public static readonly Guid LabelBigIntGuid = new(LabelBigInt); - /// - /// Guid for Textstring - /// - public static readonly Guid TextstringGuid = new Guid(Textstring); + /// + /// Guid for Label date time + /// + public static readonly Guid LabelDateTimeGuid = new(LabelDateTime); + /// + /// Guid for Label time + /// + public static readonly Guid LabelTimeGuid = new(LabelTime); - /// - /// Guid for Textarea as string - /// - public const string Textarea = "c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"; - - /// - /// Guid for Dropdown - /// - public static readonly Guid TextareaGuid = new Guid(Textarea); - - - /// - /// Guid for Upload as string - /// - public const string Upload = "84c6b441-31df-4ffe-b67e-67d5bc3ae65a"; - - /// - /// Guid for Upload - /// - public static readonly Guid UploadGuid = new Guid(Upload); - - /// - /// Guid for UploadVideo as string - /// - public const string UploadVideo = "70575fe7-9812-4396-bbe1-c81a76db71b5"; - - /// - /// Guid for UploadVideo - /// - public static readonly Guid UploadVideoGuid = new Guid(UploadVideo); - - /// - /// Guid for UploadAudio as string - /// - public const string UploadAudio = "8f430dd6-4e96-447e-9dc0-cb552c8cd1f3"; - - /// - /// Guid for UploadAudio - /// - public static readonly Guid UploadAudioGuid = new Guid(UploadAudio); - - /// - /// Guid for UploadArticle as string - /// - public const string UploadArticle = "bc1e266c-dac4-4164-bf08-8a1ec6a7143d"; - - /// - /// Guid for UploadArticle - /// - public static readonly Guid UploadArticleGuid = new Guid(UploadArticle); - - /// - /// Guid for UploadVectorGraphics as string - /// - public const string UploadVectorGraphics = "215cb418-2153-4429-9aef-8c0f0041191b"; - - /// - /// Guid for UploadVectorGraphics - /// - public static readonly Guid UploadVectorGraphicsGuid = new Guid(UploadVectorGraphics); - - - /// - /// Guid for Label as string - /// - public const string LabelString = "f0bc4bfb-b499-40d6-ba86-058885a5178c"; - - /// - /// Guid for Label string - /// - public static readonly Guid LabelStringGuid = new Guid(LabelString); - - /// - /// Guid for Label as int - /// - public const string LabelInt = "8e7f995c-bd81-4627-9932-c40e568ec788"; - - /// - /// Guid for Label int - /// - public static readonly Guid LabelIntGuid = new Guid(LabelInt); - - /// - /// Guid for Label as big int - /// - public const string LabelBigInt = "930861bf-e262-4ead-a704-f99453565708"; - - /// - /// Guid for Label big int - /// - public static readonly Guid LabelBigIntGuid = new Guid(LabelBigInt); - - /// - /// Guid for Label as date time - /// - public const string LabelDateTime = "0e9794eb-f9b5-4f20-a788-93acd233a7e4"; - - /// - /// Guid for Label date time - /// - public static readonly Guid LabelDateTimeGuid = new Guid(LabelDateTime); - - /// - /// Guid for Label as time - /// - public const string LabelTime = "a97cec69-9b71-4c30-8b12-ec398860d7e8"; - - /// - /// Guid for Label time - /// - public static readonly Guid LabelTimeGuid = new Guid(LabelTime); - - /// - /// Guid for Label as decimal - /// - public const string LabelDecimal = "8f1ef1e1-9de4-40d3-a072-6673f631ca64"; - - /// - /// Guid for Label decimal - /// - public static readonly Guid LabelDecimalGuid = new Guid(LabelDecimal); - - - } + /// + /// Guid for Label decimal + /// + public static readonly Guid LabelDecimalGuid = new(LabelDecimal); } } } diff --git a/src/Umbraco.Core/Constants-DeploySelector.cs b/src/Umbraco.Core/Constants-DeploySelector.cs index 30daacf42b66..cb6bcc99f839 100644 --- a/src/Umbraco.Core/Constants-DeploySelector.cs +++ b/src/Umbraco.Core/Constants-DeploySelector.cs @@ -1,17 +1,16 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public static partial class Constants { - public static partial class Constants + /// + /// Contains the valid selector values. + /// + public static class DeploySelector { - /// - /// Contains the valid selector values. - /// - public static class DeploySelector - { - public const string This = "this"; - public const string ThisAndChildren = "this-and-children"; - public const string ThisAndDescendants = "this-and-descendants"; - public const string ChildrenOfThis = "children"; - public const string DescendantsOfThis = "descendants"; - } + public const string This = "this"; + public const string ThisAndChildren = "this-and-children"; + public const string ThisAndDescendants = "this-and-descendants"; + public const string ChildrenOfThis = "children"; + public const string DescendantsOfThis = "descendants"; } } diff --git a/src/Umbraco.Core/Constants-HealthChecks.cs b/src/Umbraco.Core/Constants-HealthChecks.cs index 5a8ea401cbdf..2980a5945719 100644 --- a/src/Umbraco.Core/Constants-HealthChecks.cs +++ b/src/Umbraco.Core/Constants-HealthChecks.cs @@ -1,56 +1,58 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Defines constants. +/// +public static partial class Constants { /// - /// Defines constants. + /// Defines constants for ModelsBuilder. /// - public static partial class Constants + public static class HealthChecks { - /// - /// Defines constants for ModelsBuilder. - /// - public static class HealthChecks + public static class DocumentationLinks { + public const string SmtpCheck = "https://umbra.co/healthchecks-smtp"; - public static class DocumentationLinks + public static class LiveEnvironment { - public const string SmtpCheck = "https://umbra.co/healthchecks-smtp"; + public const string CompilationDebugCheck = "https://umbra.co/healthchecks-compilation-debug"; + } - public static class LiveEnvironment - { + public static class Configuration + { + public const string MacroErrorsCheck = "https://umbra.co/healthchecks-macro-errors"; - public const string CompilationDebugCheck = "https://umbra.co/healthchecks-compilation-debug"; - } + public const string TrySkipIisCustomErrorsCheck = + "https://umbra.co/healthchecks-skip-iis-custom-errors"; - public static class Configuration - { - public const string MacroErrorsCheck = "https://umbra.co/healthchecks-macro-errors"; - public const string TrySkipIisCustomErrorsCheck = "https://umbra.co/healthchecks-skip-iis-custom-errors"; - public const string NotificationEmailCheck = "https://umbra.co/healthchecks-notification-email"; - } + public const string NotificationEmailCheck = "https://umbra.co/healthchecks-notification-email"; + } - public static class FolderAndFilePermissionsCheck - { - public const string FileWriting = "https://umbra.co/healthchecks-file-writing"; - public const string FolderCreation = "https://umbra.co/healthchecks-folder-creation"; - public const string FileWritingForPackages = "https://umbra.co/healthchecks-file-writing-for-packages"; - public const string MediaFolderCreation = "https://umbra.co/healthchecks-media-folder-creation"; - } + public static class FolderAndFilePermissionsCheck + { + public const string FileWriting = "https://umbra.co/healthchecks-file-writing"; + public const string FolderCreation = "https://umbra.co/healthchecks-folder-creation"; + public const string FileWritingForPackages = "https://umbra.co/healthchecks-file-writing-for-packages"; + public const string MediaFolderCreation = "https://umbra.co/healthchecks-media-folder-creation"; + } + + public static class Security + { + public const string UmbracoApplicationUrlCheck = + "https://umbra.co/healthchecks-umbraco-application-url"; + + public const string ClickJackingCheck = "https://umbra.co/healthchecks-click-jacking"; + public const string HstsCheck = "https://umbra.co/healthchecks-hsts"; + public const string NoSniffCheck = "https://umbra.co/healthchecks-no-sniff"; + public const string XssProtectionCheck = "https://umbra.co/healthchecks-xss-protection"; + public const string ExcessiveHeadersCheck = "https://umbra.co/healthchecks-excessive-headers"; - public static class Security + public static class HttpsCheck { - public const string UmbracoApplicationUrlCheck = "https://umbra.co/healthchecks-umbraco-application-url"; - public const string ClickJackingCheck = "https://umbra.co/healthchecks-click-jacking"; - public const string HstsCheck = "https://umbra.co/healthchecks-hsts"; - public const string NoSniffCheck = "https://umbra.co/healthchecks-no-sniff"; - public const string XssProtectionCheck = "https://umbra.co/healthchecks-xss-protection"; - public const string ExcessiveHeadersCheck = "https://umbra.co/healthchecks-excessive-headers"; - - public static class HttpsCheck - { - public const string CheckIfCurrentSchemeIsHttps = "https://umbra.co/healthchecks-https-request"; - public const string CheckHttpsConfigurationSetting = "https://umbra.co/healthchecks-https-config"; - public const string CheckForValidCertificate = "https://umbra.co/healthchecks-valid-certificate"; - } + public const string CheckIfCurrentSchemeIsHttps = "https://umbra.co/healthchecks-https-request"; + public const string CheckHttpsConfigurationSetting = "https://umbra.co/healthchecks-https-config"; + public const string CheckForValidCertificate = "https://umbra.co/healthchecks-valid-certificate"; } } } diff --git a/src/Umbraco.Core/Constants-HttpClients.cs b/src/Umbraco.Core/Constants-HttpClients.cs index 474ec49a5085..677f4420856d 100644 --- a/src/Umbraco.Core/Constants-HttpClients.cs +++ b/src/Umbraco.Core/Constants-HttpClients.cs @@ -1,19 +1,18 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Defines constants. +/// +public static partial class Constants { /// - /// Defines constants. + /// Defines constants for named http clients. /// - public static partial class Constants + public static class HttpClients { /// - /// Defines constants for named http clients. + /// Name for http client which ignores certificate errors. /// - public static class HttpClients - { - /// - /// Name for http client which ignores certificate errors. - /// - public const string IgnoreCertificateErrors = "Umbraco:HttpClients:IgnoreCertificateErrors"; - } + public const string IgnoreCertificateErrors = "Umbraco:HttpClients:IgnoreCertificateErrors"; } } diff --git a/src/Umbraco.Core/Constants-HttpContextItemsKeys.cs b/src/Umbraco.Core/Constants-HttpContextItemsKeys.cs index 7be1fbd1406c..a89bfc2553fc 100644 --- a/src/Umbraco.Core/Constants-HttpContextItemsKeys.cs +++ b/src/Umbraco.Core/Constants-HttpContextItemsKeys.cs @@ -1,19 +1,18 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public static partial class Constants { - public static partial class Constants + public static class HttpContext { - public static class HttpContext + /// + /// Defines keys for items stored in HttpContext.Items + /// + public static class Items { /// - /// Defines keys for items stored in HttpContext.Items + /// Key for current requests body deserialized as JObject. /// - public static class Items - { - /// - /// Key for current requests body deserialized as JObject. - /// - public const string RequestBodyAsJObject = "RequestBodyAsJObject"; - } + public const string RequestBodyAsJObject = "RequestBodyAsJObject"; } } } diff --git a/src/Umbraco.Core/Constants-Icons.cs b/src/Umbraco.Core/Constants-Icons.cs index 39980f116a80..40ab52aaa5ab 100644 --- a/src/Umbraco.Core/Constants-Icons.cs +++ b/src/Umbraco.Core/Constants-Icons.cs @@ -1,143 +1,142 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public static partial class Constants { - public static partial class Constants + public static class Icons { - public static class Icons - { - /// - /// System default icon - /// - public const string DefaultIcon = Content; - - /// - /// System blueprint icon - /// - public const string Blueprint = "icon-blueprint"; - - /// - /// System content icon - /// - public const string Content = "icon-document"; - - /// - /// System content type icon - /// - public const string ContentType = "icon-item-arrangement"; - - /// - /// System data type icon - /// - public const string DataType = "icon-autofill"; - - /// - /// System dictionary icon - /// - public const string Dictionary = "icon-book-alt"; - - /// - /// System generic folder icon - /// - public const string Folder = "icon-folder"; - - /// - /// System language icon - /// - public const string Language = "icon-globe"; - - /// - /// System logviewer icon - /// - public const string LogViewer = "icon-box-alt"; - - /// - /// System list view icon - /// - public const string ListView = "icon-thumbnail-list"; - - /// - /// System macro icon - /// - public const string Macro = "icon-settings-alt"; - - /// - /// System media file icon - /// - public const string MediaFile = "icon-document"; - - /// - /// System media video icon - /// - public const string MediaVideo = "icon-video"; - - /// - /// System media audio icon - /// - public const string MediaAudio = "icon-sound-waves"; - - /// - /// System media article icon - /// - public const string MediaArticle = "icon-article"; - - /// - /// System media vector icon - /// - public const string MediaVectorGraphics = "icon-picture"; - - /// - /// System media folder icon - /// - public const string MediaFolder = "icon-folder"; - - /// - /// System media image icon - /// - public const string MediaImage = "icon-picture"; - - /// - /// System media type icon - /// - public const string MediaType = "icon-thumbnails"; - - /// - /// System member icon - /// - public const string Member = "icon-user"; - - /// - /// System member group icon - /// - public const string MemberGroup = "icon-users-alt"; - - /// - /// System member type icon - /// - public const string MemberType = "icon-users"; - - /// - /// System packages icon - /// - public const string Packages = "icon-box"; - - /// - /// System property editor icon - /// - public const string PropertyEditor = "icon-autofill"; - - /// - /// System member icon - /// - public const string Template = "icon-layout"; - - /// - /// System user icon - /// - public const string User = "icon-user"; - - /// - /// System user group icon - /// - public const string UserGroup = "icon-users"; - } + /// + /// System default icon + /// + public const string DefaultIcon = Content; + + /// + /// System blueprint icon + /// + public const string Blueprint = "icon-blueprint"; + + /// + /// System content icon + /// + public const string Content = "icon-document"; + + /// + /// System content type icon + /// + public const string ContentType = "icon-item-arrangement"; + + /// + /// System data type icon + /// + public const string DataType = "icon-autofill"; + + /// + /// System dictionary icon + /// + public const string Dictionary = "icon-book-alt"; + + /// + /// System generic folder icon + /// + public const string Folder = "icon-folder"; + + /// + /// System language icon + /// + public const string Language = "icon-globe"; + + /// + /// System logviewer icon + /// + public const string LogViewer = "icon-box-alt"; + + /// + /// System list view icon + /// + public const string ListView = "icon-thumbnail-list"; + + /// + /// System macro icon + /// + public const string Macro = "icon-settings-alt"; + + /// + /// System media file icon + /// + public const string MediaFile = "icon-document"; + + /// + /// System media video icon + /// + public const string MediaVideo = "icon-video"; + + /// + /// System media audio icon + /// + public const string MediaAudio = "icon-sound-waves"; + + /// + /// System media article icon + /// + public const string MediaArticle = "icon-article"; + + /// + /// System media vector icon + /// + public const string MediaVectorGraphics = "icon-picture"; + + /// + /// System media folder icon + /// + public const string MediaFolder = "icon-folder"; + + /// + /// System media image icon + /// + public const string MediaImage = "icon-picture"; + + /// + /// System media type icon + /// + public const string MediaType = "icon-thumbnails"; + + /// + /// System member icon + /// + public const string Member = "icon-user"; + + /// + /// System member group icon + /// + public const string MemberGroup = "icon-users-alt"; + + /// + /// System member type icon + /// + public const string MemberType = "icon-users"; + + /// + /// System packages icon + /// + public const string Packages = "icon-box"; + + /// + /// System property editor icon + /// + public const string PropertyEditor = "icon-autofill"; + + /// + /// System member icon + /// + public const string Template = "icon-layout"; + + /// + /// System user icon + /// + public const string User = "icon-user"; + + /// + /// System user group icon + /// + public const string UserGroup = "icon-users"; } } diff --git a/src/Umbraco.Core/Constants-Indexes.cs b/src/Umbraco.Core/Constants-Indexes.cs index fcf2e7ed1475..9c5d9ca48e67 100644 --- a/src/Umbraco.Core/Constants-Indexes.cs +++ b/src/Umbraco.Core/Constants-Indexes.cs @@ -1,12 +1,11 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public static partial class Constants { - public static partial class Constants + public static class UmbracoIndexes { - public static class UmbracoIndexes - { - public const string InternalIndexName = "InternalIndex"; - public const string ExternalIndexName = "ExternalIndex"; - public const string MembersIndexName = "MembersIndex"; - } + public const string InternalIndexName = "InternalIndex"; + public const string ExternalIndexName = "ExternalIndex"; + public const string MembersIndexName = "MembersIndex"; } } diff --git a/src/Umbraco.Core/Constants-ModelsBuilder.cs b/src/Umbraco.Core/Constants-ModelsBuilder.cs index 289c0355a8ab..19e237754c81 100644 --- a/src/Umbraco.Core/Constants-ModelsBuilder.cs +++ b/src/Umbraco.Core/Constants-ModelsBuilder.cs @@ -1,16 +1,15 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Defines constants. +/// +public static partial class Constants { /// - /// Defines constants. + /// Defines constants for ModelsBuilder. /// - public static partial class Constants + public static class ModelsBuilder { - /// - /// Defines constants for ModelsBuilder. - /// - public static class ModelsBuilder - { - public const string DefaultModelsNamespace = "Umbraco.Cms.Web.Common.PublishedModels"; - } + public const string DefaultModelsNamespace = "Umbraco.Cms.Web.Common.PublishedModels"; } } diff --git a/src/Umbraco.Core/Constants-ObjectTypes.cs b/src/Umbraco.Core/Constants-ObjectTypes.cs index 0a9847b8488b..e1f18b601e55 100644 --- a/src/Umbraco.Core/Constants-ObjectTypes.cs +++ b/src/Umbraco.Core/Constants-ObjectTypes.cs @@ -1,125 +1,124 @@ -using System; +namespace Umbraco.Cms.Core; -namespace Umbraco.Cms.Core +public static partial class Constants { - public static partial class Constants + /// + /// Defines the Umbraco object type unique identifiers. + /// + public static class ObjectTypes { - /// - /// Defines the Umbraco object type unique identifiers. - /// - public static class ObjectTypes - { - /// - /// Defines the Umbraco object type unique identifiers as string. - /// - /// Should be used only when it's not possible to use the corresponding - /// readonly Guid value, e.g. in attributes (where only consts can be used). - public static class Strings - { - // ReSharper disable MemberHidesStaticFromOuterClass + public static readonly Guid SystemRoot = new(Strings.SystemRoot); - public const string DataTypeContainer = "521231E3-8B37-469C-9F9D-51AFC91FEB7B"; + public static readonly Guid ContentRecycleBin = new(Strings.ContentRecycleBin); - public const string DocumentTypeContainer = "2F7A2769-6B0B-4468-90DD-AF42D64F7F16"; + public static readonly Guid MediaRecycleBin = new(Strings.MediaRecycleBin); - public const string MediaTypeContainer = "42AEF799-B288-4744-9B10-BE144B73CDC4"; + public static readonly Guid DataTypeContainer = new(Strings.DataTypeContainer); - public const string ContentItem = "10E2B09F-C28B-476D-B77A-AA686435E44A"; + public static readonly Guid DocumentTypeContainer = new(Strings.DocumentTypeContainer); - public const string ContentItemType = "7A333C54-6F43-40A4-86A2-18688DC7E532"; + public static readonly Guid MediaTypeContainer = new(Strings.MediaTypeContainer); - public const string ContentRecycleBin = "01BB7FF2-24DC-4C0C-95A2-C24EF72BBAC8"; + public static readonly Guid DataType = new(Strings.DataType); - public const string DataType = "30A2A501-1978-4DDB-A57B-F7EFED43BA3C"; + public static readonly Guid Document = new(Strings.Document); - public const string Document = "C66BA18E-EAF3-4CFF-8A22-41B16D66A972"; + public static readonly Guid DocumentBlueprint = new(Strings.DocumentBlueprint); - public const string DocumentBlueprint = "6EBEF410-03AA-48CF-A792-E1C1CB087ACA"; + public static readonly Guid DocumentType = new(Strings.DocumentType); - public const string DocumentType = "A2CB7800-F571-4787-9638-BC48539A0EFB"; + public static readonly Guid Media = new(Strings.Media); - public const string Media = "B796F64C-1F99-4FFB-B886-4BF4BC011A9C"; + public static readonly Guid MediaType = new(Strings.MediaType); - public const string MediaRecycleBin = "CF3D8E34-1C1C-41e9-AE56-878B57B32113"; + public static readonly Guid Member = new(Strings.Member); - public const string MediaType = "4EA4382B-2F5A-4C2B-9587-AE9B3CF3602E"; + public static readonly Guid MemberGroup = new(Strings.MemberGroup); - public const string Member = "39EB0F98-B348-42A1-8662-E7EB18487560"; + public static readonly Guid MemberType = new(Strings.MemberType); - public const string MemberGroup = "366E63B9-880F-4E13-A61C-98069B029728"; + public static readonly Guid TemplateType = new(Strings.Template); - public const string MemberType = "9B5416FB-E72F-45A9-A07B-5A9A2709CE43"; + public static readonly Guid LockObject = new(Strings.LockObject); - public const string SystemRoot = "EA7D8624-4CFE-4578-A871-24AA946BF34D"; + public static readonly Guid RelationType = new(Strings.RelationType); - public const string Template = "6FBDE604-4178-42CE-A10B-8A2600A2F07D"; + public static readonly Guid FormsForm = new(Strings.FormsForm); - public const string LockObject = "87A9F1FF-B1E4-4A25-BABB-465A4A47EC41"; + public static readonly Guid FormsPreValue = new(Strings.FormsPreValue); - public const string RelationType = "B1988FAD-8675-4F47-915A-B3A602BC5D8D"; + public static readonly Guid FormsDataSource = new(Strings.FormsDataSource); - public const string FormsForm = "F5A9F787-6593-46F0-B8FF-BFD9BCA9F6BB"; + public static readonly Guid Language = new(Strings.Language); - public const string FormsPreValue = "42D7BF9B-A362-4FEE-B45A-674D5C064B70"; + public static readonly Guid IdReservation = new(Strings.IdReservation); - public const string FormsDataSource = "CFED6CE4-9359-443E-9977-9956FEB1D867"; + public static readonly Guid Template = new(Strings.Template); - public const string Language = "6B05D05B-EC78-49BE-A4E4-79E274F07A77"; + public static readonly Guid ContentItem = new(Strings.ContentItem); - public const string IdReservation = "92849B1E-3904-4713-9356-F646F87C25F4"; + /// + /// Defines the Umbraco object type unique identifiers as string. + /// + /// + /// Should be used only when it's not possible to use the corresponding + /// readonly Guid value, e.g. in attributes (where only consts can be used). + /// + public static class Strings + { + // ReSharper disable MemberHidesStaticFromOuterClass - // ReSharper restore MemberHidesStaticFromOuterClass - } + public const string DataTypeContainer = "521231E3-8B37-469C-9F9D-51AFC91FEB7B"; - public static readonly Guid SystemRoot = new Guid(Strings.SystemRoot); + public const string DocumentTypeContainer = "2F7A2769-6B0B-4468-90DD-AF42D64F7F16"; - public static readonly Guid ContentRecycleBin = new Guid(Strings.ContentRecycleBin); + public const string MediaTypeContainer = "42AEF799-B288-4744-9B10-BE144B73CDC4"; - public static readonly Guid MediaRecycleBin = new Guid(Strings.MediaRecycleBin); + public const string ContentItem = "10E2B09F-C28B-476D-B77A-AA686435E44A"; - public static readonly Guid DataTypeContainer = new Guid(Strings.DataTypeContainer); + public const string ContentItemType = "7A333C54-6F43-40A4-86A2-18688DC7E532"; - public static readonly Guid DocumentTypeContainer = new Guid(Strings.DocumentTypeContainer); + public const string ContentRecycleBin = "01BB7FF2-24DC-4C0C-95A2-C24EF72BBAC8"; - public static readonly Guid MediaTypeContainer = new Guid(Strings.MediaTypeContainer); + public const string DataType = "30A2A501-1978-4DDB-A57B-F7EFED43BA3C"; - public static readonly Guid DataType = new Guid(Strings.DataType); + public const string Document = "C66BA18E-EAF3-4CFF-8A22-41B16D66A972"; - public static readonly Guid Document = new Guid(Strings.Document); + public const string DocumentBlueprint = "6EBEF410-03AA-48CF-A792-E1C1CB087ACA"; - public static readonly Guid DocumentBlueprint = new Guid(Strings.DocumentBlueprint); + public const string DocumentType = "A2CB7800-F571-4787-9638-BC48539A0EFB"; - public static readonly Guid DocumentType = new Guid(Strings.DocumentType); + public const string Media = "B796F64C-1F99-4FFB-B886-4BF4BC011A9C"; - public static readonly Guid Media = new Guid(Strings.Media); + public const string MediaRecycleBin = "CF3D8E34-1C1C-41e9-AE56-878B57B32113"; - public static readonly Guid MediaType = new Guid(Strings.MediaType); + public const string MediaType = "4EA4382B-2F5A-4C2B-9587-AE9B3CF3602E"; - public static readonly Guid Member = new Guid(Strings.Member); + public const string Member = "39EB0F98-B348-42A1-8662-E7EB18487560"; - public static readonly Guid MemberGroup = new Guid(Strings.MemberGroup); + public const string MemberGroup = "366E63B9-880F-4E13-A61C-98069B029728"; - public static readonly Guid MemberType = new Guid(Strings.MemberType); + public const string MemberType = "9B5416FB-E72F-45A9-A07B-5A9A2709CE43"; - public static readonly Guid TemplateType = new Guid(Strings.Template); + public const string SystemRoot = "EA7D8624-4CFE-4578-A871-24AA946BF34D"; - public static readonly Guid LockObject = new Guid(Strings.LockObject); + public const string Template = "6FBDE604-4178-42CE-A10B-8A2600A2F07D"; - public static readonly Guid RelationType = new Guid(Strings.RelationType); + public const string LockObject = "87A9F1FF-B1E4-4A25-BABB-465A4A47EC41"; - public static readonly Guid FormsForm = new Guid(Strings.FormsForm); + public const string RelationType = "B1988FAD-8675-4F47-915A-B3A602BC5D8D"; - public static readonly Guid FormsPreValue = new Guid(Strings.FormsPreValue); + public const string FormsForm = "F5A9F787-6593-46F0-B8FF-BFD9BCA9F6BB"; - public static readonly Guid FormsDataSource = new Guid(Strings.FormsDataSource); + public const string FormsPreValue = "42D7BF9B-A362-4FEE-B45A-674D5C064B70"; - public static readonly Guid Language = new Guid(Strings.Language); + public const string FormsDataSource = "CFED6CE4-9359-443E-9977-9956FEB1D867"; - public static readonly Guid IdReservation = new Guid(Strings.IdReservation); + public const string Language = "6B05D05B-EC78-49BE-A4E4-79E274F07A77"; - public static readonly Guid Template = new Guid(Strings.Template); + public const string IdReservation = "92849B1E-3904-4713-9356-F646F87C25F4"; - public static readonly Guid ContentItem = new Guid(Strings.ContentItem); + // ReSharper restore MemberHidesStaticFromOuterClass } } } diff --git a/src/Umbraco.Core/Constants-PackageRepository.cs b/src/Umbraco.Core/Constants-PackageRepository.cs index 96ef39b7c136..51b31ec81c51 100644 --- a/src/Umbraco.Core/Constants-PackageRepository.cs +++ b/src/Umbraco.Core/Constants-PackageRepository.cs @@ -1,15 +1,14 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public static partial class Constants { - public static partial class Constants + /// + /// Defines the constants used for the Umbraco package repository + /// + public static class PackageRepository { - /// - /// Defines the constants used for the Umbraco package repository - /// - public static class PackageRepository - { - public const string RestApiBaseUrl = "https://our.umbraco.com/webapi/packages/v1"; - public const string DefaultRepositoryName = "Umbraco package Repository"; - public const string DefaultRepositoryId = "65194810-1f85-11dd-bd0b-0800200c9a66"; - } + public const string RestApiBaseUrl = "https://our.umbraco.com/webapi/packages/v1"; + public const string DefaultRepositoryName = "Umbraco package Repository"; + public const string DefaultRepositoryId = "65194810-1f85-11dd-bd0b-0800200c9a66"; } } diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index b34351d902f9..2bb53b32994b 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -1,241 +1,239 @@ using Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public static partial class Constants { - public static partial class Constants + /// + /// Defines property editors constants. + /// + public static class PropertyEditors { /// - /// Defines property editors constants. + /// Used to prefix generic properties that are internal content properties /// - public static class PropertyEditors + public const string InternalGenericPropertiesPrefix = "_umb_"; + + public static class Legacy + { + public static class Aliases + { + public const string Textbox = "Umbraco.Textbox"; + public const string Date = "Umbraco.Date"; + public const string ContentPicker2 = "Umbraco.ContentPicker2"; + public const string MediaPicker2 = "Umbraco.MediaPicker2"; + public const string MemberPicker2 = "Umbraco.MemberPicker2"; + public const string MultiNodeTreePicker2 = "Umbraco.MultiNodeTreePicker2"; + public const string TextboxMultiple = "Umbraco.TextboxMultiple"; + public const string RelatedLinks2 = "Umbraco.RelatedLinks2"; + public const string RelatedLinks = "Umbraco.RelatedLinks"; + } + } + + /// + /// Defines Umbraco built-in property editor aliases. + /// + public static class Aliases { /// - /// Used to prefix generic properties that are internal content properties + /// Block List. /// - public const string InternalGenericPropertiesPrefix = "_umb_"; + public const string BlockList = "Umbraco.BlockList"; - public static class Legacy - { - public static class Aliases - { - public const string Textbox = "Umbraco.Textbox"; - public const string Date = "Umbraco.Date"; - public const string ContentPicker2 = "Umbraco.ContentPicker2"; - public const string MediaPicker2 = "Umbraco.MediaPicker2"; - public const string MemberPicker2 = "Umbraco.MemberPicker2"; - public const string MultiNodeTreePicker2 = "Umbraco.MultiNodeTreePicker2"; - public const string TextboxMultiple = "Umbraco.TextboxMultiple"; - public const string RelatedLinks2 = "Umbraco.RelatedLinks2"; - public const string RelatedLinks = "Umbraco.RelatedLinks"; - - } - } + /// + /// CheckBox List. + /// + public const string CheckBoxList = "Umbraco.CheckBoxList"; /// - /// Defines Umbraco built-in property editor aliases. + /// Color Picker. /// - public static class Aliases - { - /// - /// Block List. - /// - public const string BlockList = "Umbraco.BlockList"; - - /// - /// CheckBox List. - /// - public const string CheckBoxList = "Umbraco.CheckBoxList"; - - /// - /// Color Picker. - /// - public const string ColorPicker = "Umbraco.ColorPicker"; - - /// - /// Eye Dropper Color Picker. - /// - public const string ColorPickerEyeDropper = "Umbraco.ColorPicker.EyeDropper"; - - /// - /// Content Picker. - /// - public const string ContentPicker = "Umbraco.ContentPicker"; - - /// - /// DateTime. - /// - public const string DateTime = "Umbraco.DateTime"; - - /// - /// DropDown List. - /// - public const string DropDownListFlexible = "Umbraco.DropDown.Flexible"; - - /// - /// Grid. - /// - public const string Grid = "Umbraco.Grid"; - - /// - /// Image Cropper. - /// - public const string ImageCropper = "Umbraco.ImageCropper"; - - /// - /// Integer. - /// - public const string Integer = "Umbraco.Integer"; - - /// - /// Decimal. - /// - public const string Decimal = "Umbraco.Decimal"; - - /// - /// ListView. - /// - public const string ListView = "Umbraco.ListView"; - - /// - /// Media Picker. - /// - public const string MediaPicker = "Umbraco.MediaPicker"; - - /// - /// Media Picker v.3. - /// - public const string MediaPicker3 = "Umbraco.MediaPicker3"; - - /// - /// Multiple Media Picker. - /// - public const string MultipleMediaPicker = "Umbraco.MultipleMediaPicker"; - - /// - /// Member Picker. - /// - public const string MemberPicker = "Umbraco.MemberPicker"; - - /// - /// Member Group Picker. - /// - public const string MemberGroupPicker = "Umbraco.MemberGroupPicker"; - - /// - /// MultiNode Tree Picker. - /// - public const string MultiNodeTreePicker = "Umbraco.MultiNodeTreePicker"; - - /// - /// Multiple TextString. - /// - public const string MultipleTextstring = "Umbraco.MultipleTextstring"; - - /// - /// Label. - /// - public const string Label = "Umbraco.Label"; - - /// - /// Picker Relations. - /// - public const string PickerRelations = "Umbraco.PickerRelations"; - - /// - /// RadioButton list. - /// - public const string RadioButtonList = "Umbraco.RadioButtonList"; - - /// - /// Slider. - /// - public const string Slider = "Umbraco.Slider"; - - /// - /// Tags. - /// - public const string Tags = "Umbraco.Tags"; - - /// - /// Textbox. - /// - public const string TextBox = "Umbraco.TextBox"; - - /// - /// Textbox Multiple. - /// - public const string TextArea = "Umbraco.TextArea"; - - /// - /// TinyMCE - /// - public const string TinyMce = "Umbraco.TinyMCE"; - - /// - /// Boolean. - /// - public const string Boolean = "Umbraco.TrueFalse"; - - /// - /// Markdown Editor. - /// - public const string MarkdownEditor = "Umbraco.MarkdownEditor"; - - /// - /// User Picker. - /// - public const string UserPicker = "Umbraco.UserPicker"; - - /// - /// Upload Field. - /// - public const string UploadField = "Umbraco.UploadField"; - - /// - /// Email Address. - /// - public const string EmailAddress = "Umbraco.EmailAddress"; - - /// - /// Nested Content. - /// - public const string NestedContent = "Umbraco.NestedContent"; - - /// - /// Alias for the multi URL picker editor. - /// - public const string MultiUrlPicker = "Umbraco.MultiUrlPicker"; - } + public const string ColorPicker = "Umbraco.ColorPicker"; /// - /// Defines Umbraco build-in datatype configuration keys. + /// Eye Dropper Color Picker. /// - public static class ConfigurationKeys - { - /// - /// The value type of property data (i.e., string, integer, etc) - /// - /// Must be a valid value. - public const string DataValueType = "umbracoDataValueType"; - } + public const string ColorPickerEyeDropper = "Umbraco.ColorPicker.EyeDropper"; /// - /// Defines Umbraco's built-in property editor groups. + /// Content Picker. /// - public static class Groups - { - public const string Common = "Common"; + public const string ContentPicker = "Umbraco.ContentPicker"; - public const string Lists = "Lists"; + /// + /// DateTime. + /// + public const string DateTime = "Umbraco.DateTime"; - public const string Media = "Media"; + /// + /// DropDown List. + /// + public const string DropDownListFlexible = "Umbraco.DropDown.Flexible"; - public const string People = "People"; + /// + /// Grid. + /// + public const string Grid = "Umbraco.Grid"; - public const string Pickers = "Pickers"; + /// + /// Image Cropper. + /// + public const string ImageCropper = "Umbraco.ImageCropper"; - public const string RichContent = "Rich Content"; - } + /// + /// Integer. + /// + public const string Integer = "Umbraco.Integer"; + + /// + /// Decimal. + /// + public const string Decimal = "Umbraco.Decimal"; + + /// + /// ListView. + /// + public const string ListView = "Umbraco.ListView"; + + /// + /// Media Picker. + /// + public const string MediaPicker = "Umbraco.MediaPicker"; + + /// + /// Media Picker v.3. + /// + public const string MediaPicker3 = "Umbraco.MediaPicker3"; + + /// + /// Multiple Media Picker. + /// + public const string MultipleMediaPicker = "Umbraco.MultipleMediaPicker"; + + /// + /// Member Picker. + /// + public const string MemberPicker = "Umbraco.MemberPicker"; + + /// + /// Member Group Picker. + /// + public const string MemberGroupPicker = "Umbraco.MemberGroupPicker"; + + /// + /// MultiNode Tree Picker. + /// + public const string MultiNodeTreePicker = "Umbraco.MultiNodeTreePicker"; + + /// + /// Multiple TextString. + /// + public const string MultipleTextstring = "Umbraco.MultipleTextstring"; + + /// + /// Label. + /// + public const string Label = "Umbraco.Label"; + + /// + /// Picker Relations. + /// + public const string PickerRelations = "Umbraco.PickerRelations"; + + /// + /// RadioButton list. + /// + public const string RadioButtonList = "Umbraco.RadioButtonList"; + + /// + /// Slider. + /// + public const string Slider = "Umbraco.Slider"; + + /// + /// Tags. + /// + public const string Tags = "Umbraco.Tags"; + + /// + /// Textbox. + /// + public const string TextBox = "Umbraco.TextBox"; + + /// + /// Textbox Multiple. + /// + public const string TextArea = "Umbraco.TextArea"; + + /// + /// TinyMCE + /// + public const string TinyMce = "Umbraco.TinyMCE"; + + /// + /// Boolean. + /// + public const string Boolean = "Umbraco.TrueFalse"; + + /// + /// Markdown Editor. + /// + public const string MarkdownEditor = "Umbraco.MarkdownEditor"; + + /// + /// User Picker. + /// + public const string UserPicker = "Umbraco.UserPicker"; + + /// + /// Upload Field. + /// + public const string UploadField = "Umbraco.UploadField"; + + /// + /// Email Address. + /// + public const string EmailAddress = "Umbraco.EmailAddress"; + + /// + /// Nested Content. + /// + public const string NestedContent = "Umbraco.NestedContent"; + + /// + /// Alias for the multi URL picker editor. + /// + public const string MultiUrlPicker = "Umbraco.MultiUrlPicker"; + } + + /// + /// Defines Umbraco build-in datatype configuration keys. + /// + public static class ConfigurationKeys + { + /// + /// The value type of property data (i.e., string, integer, etc) + /// + /// Must be a valid value. + public const string DataValueType = "umbracoDataValueType"; + } + + /// + /// Defines Umbraco's built-in property editor groups. + /// + public static class Groups + { + public const string Common = "Common"; + + public const string Lists = "Lists"; + + public const string Media = "Media"; + + public const string People = "People"; + + public const string Pickers = "Pickers"; + + public const string RichContent = "Rich Content"; } } } diff --git a/src/Umbraco.Core/Constants-PropertyTypeGroups.cs b/src/Umbraco.Core/Constants-PropertyTypeGroups.cs index 46b41ea23368..f30bafe6fe57 100644 --- a/src/Umbraco.Core/Constants-PropertyTypeGroups.cs +++ b/src/Umbraco.Core/Constants-PropertyTypeGroups.cs @@ -1,46 +1,45 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public static partial class Constants { - public static partial class Constants + /// + /// Defines the identifiers for property-type groups conventions that are used within the Umbraco core. + /// + public static class PropertyTypeGroups { /// - /// Defines the identifiers for property-type groups conventions that are used within the Umbraco core. + /// Guid for an Image PropertyTypeGroup object. /// - public static class PropertyTypeGroups - { - /// - /// Guid for an Image PropertyTypeGroup object. - /// - public const string Image = "79ED4D07-254A-42CF-8FA9-EBE1C116A596"; + public const string Image = "79ED4D07-254A-42CF-8FA9-EBE1C116A596"; - /// - /// Guid for a File PropertyTypeGroup object. - /// - public const string File = "50899F9C-023A-4466-B623-ABA9049885FE"; + /// + /// Guid for a File PropertyTypeGroup object. + /// + public const string File = "50899F9C-023A-4466-B623-ABA9049885FE"; - /// - /// Guid for a Video PropertyTypeGroup object. - /// - public const string Video = "2F0A61B6-CF92-4FF4-B437-751AB35EB254"; + /// + /// Guid for a Video PropertyTypeGroup object. + /// + public const string Video = "2F0A61B6-CF92-4FF4-B437-751AB35EB254"; - /// - /// Guid for an Audio PropertyTypeGroup object. - /// - public const string Audio = "335FB495-0A87-4E82-B902-30EB367B767C"; + /// + /// Guid for an Audio PropertyTypeGroup object. + /// + public const string Audio = "335FB495-0A87-4E82-B902-30EB367B767C"; - /// - /// Guid for an Article PropertyTypeGroup object. - /// - public const string Article = "9AF3BD65-F687-4453-9518-5F180D1898EC"; + /// + /// Guid for an Article PropertyTypeGroup object. + /// + public const string Article = "9AF3BD65-F687-4453-9518-5F180D1898EC"; - /// - /// Guid for a VectorGraphics PropertyTypeGroup object. - /// - public const string VectorGraphics = "F199B4D7-9E84-439F-8531-F87D9AF37711"; + /// + /// Guid for a VectorGraphics PropertyTypeGroup object. + /// + public const string VectorGraphics = "F199B4D7-9E84-439F-8531-F87D9AF37711"; - /// - /// Guid for a Membership PropertyTypeGroup object. - /// - public const string Membership = "0756729D-D665-46E3-B84A-37ACEAA614F8"; - } + /// + /// Guid for a Membership PropertyTypeGroup object. + /// + public const string Membership = "0756729D-D665-46E3-B84A-37ACEAA614F8"; } } diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index 68601a78b0ab..226778aeee6a 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -1,73 +1,83 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public static partial class Constants { - public static partial class Constants + public static class Security { - public static class Security - { - /// - /// Gets the identifier of the 'super' user. - /// - public const int SuperUserId = -1; - - public const string SuperUserIdAsString = "-1"; - - /// - /// The id for the 'unknown' user. - /// - /// - /// This is a user row that exists in the DB only for referential integrity but the user is never returned from any of the services - /// - public const int UnknownUserId = 0; - - /// - /// The name of the 'unknown' user. - /// - public const string UnknownUserName = "SYSTEM"; - - public const string AdminGroupAlias = "admin"; - public const string EditorGroupAlias = "editor"; - public const string SensitiveDataGroupAlias = "sensitiveData"; - public const string TranslatorGroupAlias = "translator"; - public const string WriterGroupAlias = "writer"; - - public const string BackOfficeAuthenticationType = "UmbracoBackOffice"; - public const string BackOfficeExternalAuthenticationType = "UmbracoExternalCookie"; - public const string BackOfficeExternalCookieName = "UMB_EXTLOGIN"; - public const string BackOfficeTokenAuthenticationType = "UmbracoBackOfficeToken"; - public const string BackOfficeTwoFactorAuthenticationType = "UmbracoTwoFactorCookie"; - public const string BackOfficeTwoFactorRememberMeAuthenticationType = "UmbracoTwoFactorRememberMeCookie"; - - public const string EmptyPasswordPrefix = "___UIDEMPTYPWORD__"; - - public const string DefaultMemberTypeAlias = "Member"; - - - /// - /// The prefix used for external identity providers for their authentication type - /// - /// - /// By default we don't want to interfere with front-end external providers and their default setup, for back office the - /// providers need to be setup differently and each auth type for the back office will be prefixed with this value - /// - public const string BackOfficeExternalAuthenticationTypePrefix = "Umbraco."; - public const string MemberExternalAuthenticationTypePrefix = "UmbracoMembers."; - - public const string StartContentNodeIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/startcontentnode"; - public const string StartMediaNodeIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/startmedianode"; - public const string AllowedApplicationsClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/allowedapp"; - public const string SessionIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/sessionid"; - public const string TicketExpiresClaimType = "http://umbraco.org/2020/06/identity/claims/backoffice/ticketexpires"; - - /// - /// The claim type for the ASP.NET Identity security stamp - /// - public const string SecurityStampClaimType = "AspNet.Identity.SecurityStamp"; - - public const string AspNetCoreV3PasswordHashAlgorithmName = "PBKDF2.ASPNETCORE.V3"; - public const string AspNetCoreV2PasswordHashAlgorithmName = "PBKDF2.ASPNETCORE.V2"; - public const string AspNetUmbraco8PasswordHashAlgorithmName = "HMACSHA256"; - public const string AspNetUmbraco4PasswordHashAlgorithmName = "HMACSHA1"; - public const string UnknownPasswordConfigJson = "{\"hashAlgorithm\":\"Unknown\"}"; - } + /// + /// Gets the identifier of the 'super' user. + /// + public const int SuperUserId = -1; + + public const string SuperUserIdAsString = "-1"; + + /// + /// The id for the 'unknown' user. + /// + /// + /// This is a user row that exists in the DB only for referential integrity but the user is never returned from any of + /// the services + /// + public const int UnknownUserId = 0; + + /// + /// The name of the 'unknown' user. + /// + public const string UnknownUserName = "SYSTEM"; + + public const string AdminGroupAlias = "admin"; + public const string EditorGroupAlias = "editor"; + public const string SensitiveDataGroupAlias = "sensitiveData"; + public const string TranslatorGroupAlias = "translator"; + public const string WriterGroupAlias = "writer"; + + public const string BackOfficeAuthenticationType = "UmbracoBackOffice"; + public const string BackOfficeExternalAuthenticationType = "UmbracoExternalCookie"; + public const string BackOfficeExternalCookieName = "UMB_EXTLOGIN"; + public const string BackOfficeTokenAuthenticationType = "UmbracoBackOfficeToken"; + public const string BackOfficeTwoFactorAuthenticationType = "UmbracoTwoFactorCookie"; + public const string BackOfficeTwoFactorRememberMeAuthenticationType = "UmbracoTwoFactorRememberMeCookie"; + + public const string EmptyPasswordPrefix = "___UIDEMPTYPWORD__"; + + public const string DefaultMemberTypeAlias = "Member"; + + + /// + /// The prefix used for external identity providers for their authentication type + /// + /// + /// By default we don't want to interfere with front-end external providers and their default setup, for back office + /// the + /// providers need to be setup differently and each auth type for the back office will be prefixed with this value + /// + public const string BackOfficeExternalAuthenticationTypePrefix = "Umbraco."; + + public const string MemberExternalAuthenticationTypePrefix = "UmbracoMembers."; + + public const string StartContentNodeIdClaimType = + "http://umbraco.org/2015/02/identity/claims/backoffice/startcontentnode"; + + public const string StartMediaNodeIdClaimType = + "http://umbraco.org/2015/02/identity/claims/backoffice/startmedianode"; + + public const string AllowedApplicationsClaimType = + "http://umbraco.org/2015/02/identity/claims/backoffice/allowedapp"; + + public const string SessionIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/sessionid"; + + public const string TicketExpiresClaimType = + "http://umbraco.org/2020/06/identity/claims/backoffice/ticketexpires"; + + /// + /// The claim type for the ASP.NET Identity security stamp + /// + public const string SecurityStampClaimType = "AspNet.Identity.SecurityStamp"; + + public const string AspNetCoreV3PasswordHashAlgorithmName = "PBKDF2.ASPNETCORE.V3"; + public const string AspNetCoreV2PasswordHashAlgorithmName = "PBKDF2.ASPNETCORE.V2"; + public const string AspNetUmbraco8PasswordHashAlgorithmName = "HMACSHA256"; + public const string AspNetUmbraco4PasswordHashAlgorithmName = "HMACSHA1"; + public const string UnknownPasswordConfigJson = "{\"hashAlgorithm\":\"Unknown\"}"; } } diff --git a/src/Umbraco.Core/Constants-Sql.cs b/src/Umbraco.Core/Constants-Sql.cs index b57861c92ac8..500584152444 100644 --- a/src/Umbraco.Core/Constants-Sql.cs +++ b/src/Umbraco.Core/Constants-Sql.cs @@ -1,18 +1,17 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public static partial class Constants { - public static partial class Constants + public static class Sql { - public static class Sql - { - /// - /// The maximum amount of parameters that can be used in a query. - /// - /// - /// The actual limit is 2100 - /// (https://docs.microsoft.com/en-us/sql/sql-server/maximum-capacity-specifications-for-sql-server), - /// but we want to ensure there's room for additional parameters if this value is used to create groups/batches. - /// - public const int MaxParameterCount = 2000; - } + /// + /// The maximum amount of parameters that can be used in a query. + /// + /// + /// The actual limit is 2100 + /// (https://docs.microsoft.com/en-us/sql/sql-server/maximum-capacity-specifications-for-sql-server), + /// but we want to ensure there's room for additional parameters if this value is used to create groups/batches. + /// + public const int MaxParameterCount = 2000; } } diff --git a/src/Umbraco.Core/Constants-SqlTemplates.cs b/src/Umbraco.Core/Constants-SqlTemplates.cs index a2fe501ab339..2ffdbab19c4d 100644 --- a/src/Umbraco.Core/Constants-SqlTemplates.cs +++ b/src/Umbraco.Core/Constants-SqlTemplates.cs @@ -1,43 +1,53 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public static partial class Constants { - public static partial class Constants + public static class SqlTemplates { - public static class SqlTemplates + public static class VersionableRepository + { + public const string GetVersionIds = "Umbraco.Core.VersionableRepository.GetVersionIds"; + public const string GetVersion = "Umbraco.Core.VersionableRepository.GetVersion"; + public const string GetVersions = "Umbraco.Core.VersionableRepository.GetVersions"; + public const string EnsureUniqueNodeName = "Umbraco.Core.VersionableRepository.EnsureUniqueNodeName"; + public const string GetSortOrder = "Umbraco.Core.VersionableRepository.GetSortOrder"; + public const string GetParentNode = "Umbraco.Core.VersionableRepository.GetParentNode"; + public const string GetReservedId = "Umbraco.Core.VersionableRepository.GetReservedId"; + } + + public static class RelationRepository + { + public const string DeleteByParentAll = "Umbraco.Core.RelationRepository.DeleteByParent"; + public const string DeleteByParentIn = "Umbraco.Core.RelationRepository.DeleteByParentIn"; + } + + public static class DataTypeRepository + { + public const string EnsureUniqueNodeName = "Umbraco.Core.DataTypeDefinitionRepository.EnsureUniqueNodeName"; + } + + public static class NuCacheDatabaseDataSource { - public static class VersionableRepository - { - public const string GetVersionIds = "Umbraco.Core.VersionableRepository.GetVersionIds"; - public const string GetVersion = "Umbraco.Core.VersionableRepository.GetVersion"; - public const string GetVersions = "Umbraco.Core.VersionableRepository.GetVersions"; - public const string EnsureUniqueNodeName = "Umbraco.Core.VersionableRepository.EnsureUniqueNodeName"; - public const string GetSortOrder = "Umbraco.Core.VersionableRepository.GetSortOrder"; - public const string GetParentNode = "Umbraco.Core.VersionableRepository.GetParentNode"; - public const string GetReservedId = "Umbraco.Core.VersionableRepository.GetReservedId"; - } - public static class RelationRepository - { - public const string DeleteByParentAll = "Umbraco.Core.RelationRepository.DeleteByParent"; - public const string DeleteByParentIn = "Umbraco.Core.RelationRepository.DeleteByParentIn"; - } - - public static class DataTypeRepository - { - public const string EnsureUniqueNodeName = "Umbraco.Core.DataTypeDefinitionRepository.EnsureUniqueNodeName"; - } - - public static class NuCacheDatabaseDataSource - { - public const string WhereNodeId = "Umbraco.Web.PublishedCache.NuCache.DataSource.WhereNodeId"; - public const string WhereNodeIdX = "Umbraco.Web.PublishedCache.NuCache.DataSource.WhereNodeIdX"; - public const string SourcesSelectUmbracoNodeJoin = "Umbraco.Web.PublishedCache.NuCache.DataSource.SourcesSelectUmbracoNodeJoin"; - public const string ContentSourcesSelect = "Umbraco.Web.PublishedCache.NuCache.DataSource.ContentSourcesSelect"; - public const string ContentSourcesCount = "Umbraco.Web.PublishedCache.NuCache.DataSource.ContentSourcesCount"; - public const string MediaSourcesSelect = "Umbraco.Web.PublishedCache.NuCache.DataSource.MediaSourcesSelect"; - public const string MediaSourcesCount = "Umbraco.Web.PublishedCache.NuCache.DataSource.MediaSourcesCount"; - public const string ObjectTypeNotTrashedFilter = "Umbraco.Web.PublishedCache.NuCache.DataSource.ObjectTypeNotTrashedFilter"; - public const string OrderByLevelIdSortOrder = "Umbraco.Web.PublishedCache.NuCache.DataSource.OrderByLevelIdSortOrder"; - - } + public const string WhereNodeId = "Umbraco.Web.PublishedCache.NuCache.DataSource.WhereNodeId"; + public const string WhereNodeIdX = "Umbraco.Web.PublishedCache.NuCache.DataSource.WhereNodeIdX"; + + public const string SourcesSelectUmbracoNodeJoin = + "Umbraco.Web.PublishedCache.NuCache.DataSource.SourcesSelectUmbracoNodeJoin"; + + public const string ContentSourcesSelect = + "Umbraco.Web.PublishedCache.NuCache.DataSource.ContentSourcesSelect"; + + public const string ContentSourcesCount = + "Umbraco.Web.PublishedCache.NuCache.DataSource.ContentSourcesCount"; + + public const string MediaSourcesSelect = "Umbraco.Web.PublishedCache.NuCache.DataSource.MediaSourcesSelect"; + public const string MediaSourcesCount = "Umbraco.Web.PublishedCache.NuCache.DataSource.MediaSourcesCount"; + + public const string ObjectTypeNotTrashedFilter = + "Umbraco.Web.PublishedCache.NuCache.DataSource.ObjectTypeNotTrashedFilter"; + + public const string OrderByLevelIdSortOrder = + "Umbraco.Web.PublishedCache.NuCache.DataSource.OrderByLevelIdSortOrder"; } } } diff --git a/src/Umbraco.Core/Constants-System.cs b/src/Umbraco.Core/Constants-System.cs index 8b19781d396b..db5c485e7b77 100644 --- a/src/Umbraco.Core/Constants-System.cs +++ b/src/Umbraco.Core/Constants-System.cs @@ -1,68 +1,68 @@ - namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public static partial class Constants { - public static partial class Constants + /// + /// Defines the identifiers for Umbraco system nodes. + /// + public static class System { /// - /// Defines the identifiers for Umbraco system nodes. + /// The integer identifier for global system root node. /// - public static class System - { - /// - /// The integer identifier for global system root node. - /// - public const int Root = -1; + public const int Root = -1; - /// - /// The string identifier for global system root node. - /// - /// Use this instead of re-creating the string everywhere. - public const string RootString = "-1"; + /// + /// The string identifier for global system root node. + /// + /// Use this instead of re-creating the string everywhere. + public const string RootString = "-1"; - /// - /// The integer identifier for content's recycle bin. - /// - public const int RecycleBinContent = -20; + /// + /// The integer identifier for content's recycle bin. + /// + public const int RecycleBinContent = -20; - /// - /// The string identifier for content's recycle bin. - /// - /// Use this instead of re-creating the string everywhere. - public const string RecycleBinContentString = "-20"; + /// + /// The string identifier for content's recycle bin. + /// + /// Use this instead of re-creating the string everywhere. + public const string RecycleBinContentString = "-20"; - /// - /// The string path prefix of the content's recycle bin. - /// - /// - /// Everything that is in the content recycle bin, has a path that starts with the prefix. - /// Use this instead of re-creating the string everywhere. - /// - public const string RecycleBinContentPathPrefix = "-1,-20,"; + /// + /// The string path prefix of the content's recycle bin. + /// + /// + /// Everything that is in the content recycle bin, has a path that starts with the prefix. + /// Use this instead of re-creating the string everywhere. + /// + public const string RecycleBinContentPathPrefix = "-1,-20,"; - /// - /// The integer identifier for media's recycle bin. - /// - public const int RecycleBinMedia = -21; + /// + /// The integer identifier for media's recycle bin. + /// + public const int RecycleBinMedia = -21; - /// - /// The string identifier for media's recycle bin. - /// - /// Use this instead of re-creating the string everywhere. - public const string RecycleBinMediaString = "-21"; + /// + /// The string identifier for media's recycle bin. + /// + /// Use this instead of re-creating the string everywhere. + public const string RecycleBinMediaString = "-21"; - /// - /// The string path prefix of the media's recycle bin. - /// - /// - /// Everything that is in the media recycle bin, has a path that starts with the prefix. - /// Use this instead of re-creating the string everywhere. - /// - public const string RecycleBinMediaPathPrefix = "-1,-21,"; + /// + /// The string path prefix of the media's recycle bin. + /// + /// + /// Everything that is in the media recycle bin, has a path that starts with the prefix. + /// Use this instead of re-creating the string everywhere. + /// + public const string RecycleBinMediaPathPrefix = "-1,-21,"; - public const int DefaultLabelDataTypeId = -92; + public const int DefaultLabelDataTypeId = -92; - public const string UmbracoDefaultDatabaseName = "Umbraco"; + public const string UmbracoDefaultDatabaseName = "Umbraco"; - public const string UmbracoConnectionName = "umbracoDbDSN";public const string DefaultUmbracoPath = "~/umbraco"; - } + public const string UmbracoConnectionName = "umbracoDbDSN"; + public const string DefaultUmbracoPath = "~/umbraco"; } } diff --git a/src/Umbraco.Core/Constants-SystemDirectories.cs b/src/Umbraco.Core/Constants-SystemDirectories.cs index f70dd199fc02..c941affd6abf 100644 --- a/src/Umbraco.Core/Constants-SystemDirectories.cs +++ b/src/Umbraco.Core/Constants-SystemDirectories.cs @@ -1,71 +1,67 @@ -using System; +namespace Umbraco.Cms.Core; -namespace Umbraco.Cms.Core +public static partial class Constants { - public static partial class Constants + public static class SystemDirectories { - public static class SystemDirectories - { - /// - /// The aspnet bin folder - /// - public const string Bin = "~/bin"; + /// + /// The aspnet bin folder + /// + public const string Bin = "~/bin"; - // TODO: Shouldn't this exist underneath /Umbraco in the content root? - public const string Config = "~/config"; + // TODO: Shouldn't this exist underneath /Umbraco in the content root? + public const string Config = "~/config"; - /// - /// The Umbraco folder that exists at the content root. - /// - /// - /// This is not the same as the Umbraco web folder which is configurable for serving front-end files. - /// - public const string Umbraco = "~/umbraco"; + /// + /// The Umbraco folder that exists at the content root. + /// + /// + /// This is not the same as the Umbraco web folder which is configurable for serving front-end files. + /// + public const string Umbraco = "~/umbraco"; - /// - /// The Umbraco data folder in the content root. - /// - public const string Data = Umbraco + "/Data"; + /// + /// The Umbraco data folder in the content root. + /// + public const string Data = Umbraco + "/Data"; - /// - /// The Umbraco licenses folder in the content root. - /// - public const string Licenses = Umbraco + "/Licenses"; + /// + /// The Umbraco licenses folder in the content root. + /// + public const string Licenses = Umbraco + "/Licenses"; - /// - /// The Umbraco temp data folder in the content root. - /// - public const string TempData = Data + "/TEMP"; + /// + /// The Umbraco temp data folder in the content root. + /// + public const string TempData = Data + "/TEMP"; - public const string TempFileUploads = TempData + "/FileUploads"; + public const string TempFileUploads = TempData + "/FileUploads"; - public const string TempImageUploads = TempFileUploads + "/rte"; + public const string TempImageUploads = TempFileUploads + "/rte"; - public const string Install = "~/install"; + public const string Install = "~/install"; - public const string AppPlugins = "/App_Plugins"; + public const string AppPlugins = "/App_Plugins"; - [Obsolete("Use PluginIcons instead")] - public static string AppPluginIcons => "/Backoffice/Icons"; + public const string PluginIcons = "/backoffice/icons"; - public const string PluginIcons = "/backoffice/icons"; + public const string MvcViews = "~/Views"; - public const string MvcViews = "~/Views"; + public const string PartialViews = MvcViews + "/Partials/"; - public const string PartialViews = MvcViews + "/Partials/"; + public const string MacroPartials = MvcViews + "/MacroPartials/"; - public const string MacroPartials = MvcViews + "/MacroPartials/"; + public const string Packages = Data + "/packages"; - public const string Packages = Data + "/packages"; + public const string CreatedPackages = Data + "/CreatedPackages"; - public const string CreatedPackages = Data + "/CreatedPackages"; + public const string Preview = Data + "/preview"; - public const string Preview = Data + "/preview"; + /// + /// The default folder where Umbraco log files are stored + /// + public const string LogFiles = Umbraco + "/Logs"; - /// - /// The default folder where Umbraco log files are stored - /// - public const string LogFiles = Umbraco + "/Logs"; - } + [Obsolete("Use PluginIcons instead")] public static string AppPluginIcons => "/Backoffice/Icons"; } } diff --git a/src/Umbraco.Core/Constants-Telemetry.cs b/src/Umbraco.Core/Constants-Telemetry.cs index 6fc474d9aeb3..493a2ffbe33e 100644 --- a/src/Umbraco.Core/Constants-Telemetry.cs +++ b/src/Umbraco.Core/Constants-Telemetry.cs @@ -1,32 +1,30 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public static partial class Constants { - public static partial class Constants + public static class Telemetry { - public static class Telemetry - { - - public static string RootCount = "RootCount"; - public static string DomainCount = "DomainCount"; - public static string ExamineIndexCount = "ExamineIndexCount"; - public static string LanguageCount = "LanguageCount"; - public static string MacroCount = "MacroCount"; - public static string MediaCount = "MediaCount"; - public static string MemberCount = "MemberCount"; - public static string TemplateCount = "TemplateCount"; - public static string ContentCount = "ContentCount"; - public static string DocumentTypeCount = "DocumentTypeCount"; - public static string Properties = "Properties"; - public static string UserCount = "UserCount"; - public static string UserGroupCount = "UserGroupCount"; - public static string ServerOs = "ServerOs"; - public static string ServerFramework = "ServerFramework"; - public static string OsLanguage = "OsLanguage"; - public static string WebServer = "WebServer"; - public static string ModelsBuilderMode = "ModelBuilderMode"; - public static string CustomUmbracoPath = "CustomUmbracoPath"; - public static string AspEnvironment = "AspEnvironment"; - public static string IsDebug = "IsDebug"; - public static string DatabaseProvider = "DatabaseProvider"; - } + public static string RootCount = "RootCount"; + public static string DomainCount = "DomainCount"; + public static string ExamineIndexCount = "ExamineIndexCount"; + public static string LanguageCount = "LanguageCount"; + public static string MacroCount = "MacroCount"; + public static string MediaCount = "MediaCount"; + public static string MemberCount = "MemberCount"; + public static string TemplateCount = "TemplateCount"; + public static string ContentCount = "ContentCount"; + public static string DocumentTypeCount = "DocumentTypeCount"; + public static string Properties = "Properties"; + public static string UserCount = "UserCount"; + public static string UserGroupCount = "UserGroupCount"; + public static string ServerOs = "ServerOs"; + public static string ServerFramework = "ServerFramework"; + public static string OsLanguage = "OsLanguage"; + public static string WebServer = "WebServer"; + public static string ModelsBuilderMode = "ModelBuilderMode"; + public static string CustomUmbracoPath = "CustomUmbracoPath"; + public static string AspEnvironment = "AspEnvironment"; + public static string IsDebug = "IsDebug"; + public static string DatabaseProvider = "DatabaseProvider"; } } diff --git a/src/Umbraco.Core/Constants-UdiEntityType.cs b/src/Umbraco.Core/Constants-UdiEntityType.cs index 01e9ca213d22..390d33b31a1e 100644 --- a/src/Umbraco.Core/Constants-UdiEntityType.cs +++ b/src/Umbraco.Core/Constants-UdiEntityType.cs @@ -1,74 +1,71 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public static partial class Constants { - public static partial class Constants + /// + /// Defines well-known entity types. + /// + /// + /// Well-known entity types are those that Deploy already knows about, + /// but entity types are strings and so can be extended beyond what is defined here. + /// + public static class UdiEntityType { - /// - /// Defines well-known entity types. - /// - /// Well-known entity types are those that Deploy already knows about, - /// but entity types are strings and so can be extended beyond what is defined here. - public static class UdiEntityType - { - // note: const fields in this class MUST be consistent with what GetTypes returns - // this is validated by UdiTests.ValidateUdiEntityType - // also, this is used exclusively in Udi static ctor, only once, so there is no - // need to keep it around in a field nor to make it readonly - - - public const string Unknown = "unknown"; - - // guid entity types - - public const string AnyGuid = "any-guid"; // that one is for tests + // note: const fields in this class MUST be consistent with what GetTypes returns + // this is validated by UdiTests.ValidateUdiEntityType + // also, this is used exclusively in Udi static ctor, only once, so there is no + // need to keep it around in a field nor to make it readonly - public const string Element = "element"; - public const string Document = "document"; - public const string DocumentBlueprint = "document-blueprint"; + public const string Unknown = "unknown"; - public const string Media = "media"; - public const string Member = "member"; + // guid entity types - public const string DictionaryItem = "dictionary-item"; - public const string Macro = "macro"; - public const string Template = "template"; + public const string AnyGuid = "any-guid"; // that one is for tests - public const string DocumentType = "document-type"; - public const string DocumentTypeContainer = "document-type-container"; + public const string Element = "element"; + public const string Document = "document"; - // TODO: What is this? This alias is only used for the blue print tree to render the blueprint's document type, it's not a real udi type - public const string DocumentTypeBluePrints = "document-type-blueprints"; - public const string MediaType = "media-type"; - public const string MediaTypeContainer = "media-type-container"; - public const string DataType = "data-type"; - public const string DataTypeContainer = "data-type-container"; - public const string MemberType = "member-type"; - public const string MemberGroup = "member-group"; + public const string DocumentBlueprint = "document-blueprint"; - public const string RelationType = "relation-type"; + public const string Media = "media"; + public const string Member = "member"; - // forms + public const string DictionaryItem = "dictionary-item"; + public const string Macro = "macro"; + public const string Template = "template"; - public const string FormsForm = "forms-form"; - public const string FormsPreValue = "forms-prevalue"; - public const string FormsDataSource = "forms-datasource"; + public const string DocumentType = "document-type"; + public const string DocumentTypeContainer = "document-type-container"; - // string entity types + // TODO: What is this? This alias is only used for the blue print tree to render the blueprint's document type, it's not a real udi type + public const string DocumentTypeBluePrints = "document-type-blueprints"; + public const string MediaType = "media-type"; + public const string MediaTypeContainer = "media-type-container"; + public const string DataType = "data-type"; + public const string DataTypeContainer = "data-type-container"; + public const string MemberType = "member-type"; + public const string MemberGroup = "member-group"; - public const string AnyString = "any-string"; // that one is for tests + public const string RelationType = "relation-type"; - public const string Language = "language"; - public const string MacroScript = "macroscript"; - public const string MediaFile = "media-file"; - public const string TemplateFile = "template-file"; - public const string Script = "script"; - public const string Stylesheet = "stylesheet"; - public const string PartialView = "partial-view"; - public const string PartialViewMacro = "partial-view-macro"; + // forms + public const string FormsForm = "forms-form"; + public const string FormsPreValue = "forms-prevalue"; + public const string FormsDataSource = "forms-datasource"; + // string entity types + public const string AnyString = "any-string"; // that one is for tests - } + public const string Language = "language"; + public const string MacroScript = "macroscript"; + public const string MediaFile = "media-file"; + public const string TemplateFile = "template-file"; + public const string Script = "script"; + public const string Stylesheet = "stylesheet"; + public const string PartialView = "partial-view"; + public const string PartialViewMacro = "partial-view-macro"; } } diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs index f6a8c00970ab..bfbe4e56d52e 100644 --- a/src/Umbraco.Core/Constants-Web.cs +++ b/src/Umbraco.Core/Constants-Web.cs @@ -1,73 +1,75 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public static partial class Constants { - public static partial class Constants + /// + /// Defines the identifiers for Umbraco system nodes. + /// + public static class Web { /// - /// Defines the identifiers for Umbraco system nodes. + /// The preview cookie name /// - public static class Web - { - /// - /// The preview cookie name - /// - public const string PreviewCookieName = "UMB_PREVIEW"; + public const string PreviewCookieName = "UMB_PREVIEW"; - /// - /// Client-side cookie that determines whether the user has accepted to be in Preview Mode when visiting the website. - /// - public const string AcceptPreviewCookieName = "UMB-WEBSITE-PREVIEW-ACCEPT"; + /// + /// Client-side cookie that determines whether the user has accepted to be in Preview Mode when visiting the website. + /// + public const string AcceptPreviewCookieName = "UMB-WEBSITE-PREVIEW-ACCEPT"; - public const string InstallerCookieName = "umb_installId"; + public const string InstallerCookieName = "umb_installId"; - /// - /// The cookie name that is used to store the validation value - /// - public const string CsrfValidationCookieName = "UMB-XSRF-V"; + /// + /// The cookie name that is used to store the validation value + /// + public const string CsrfValidationCookieName = "UMB-XSRF-V"; - /// - /// The cookie name that is set for angular to use to pass in to the header value for "X-UMB-XSRF-TOKEN" - /// - public const string AngularCookieName = "UMB-XSRF-TOKEN"; + /// + /// The cookie name that is set for angular to use to pass in to the header value for "X-UMB-XSRF-TOKEN" + /// + public const string AngularCookieName = "UMB-XSRF-TOKEN"; - /// - /// The header name that angular uses to pass in the token to validate the cookie - /// - public const string AngularHeadername = "X-UMB-XSRF-TOKEN"; + /// + /// The header name that angular uses to pass in the token to validate the cookie + /// + public const string AngularHeadername = "X-UMB-XSRF-TOKEN"; - /// - /// The route name of the page shown when Umbraco has no published content. - /// - public const string NoContentRouteName = "umbraco-no-content"; + /// + /// The route name of the page shown when Umbraco has no published content. + /// + public const string NoContentRouteName = "umbraco-no-content"; + + /// + /// The default authentication type used for remembering that 2FA is not needed on next login + /// + public const string TwoFactorRememberBrowserCookie = "TwoFactorRememberBrowser"; + + public static class Mvc + { + public const string InstallArea = "UmbracoInstall"; - /// - /// The default authentication type used for remembering that 2FA is not needed on next login - /// - public const string TwoFactorRememberBrowserCookie = "TwoFactorRememberBrowser"; + public const string + BackOfficePathSegment = "BackOffice"; // The path segment prefix for all back office controllers - public static class Mvc - { - public const string InstallArea = "UmbracoInstall"; - public const string BackOfficePathSegment = "BackOffice"; // The path segment prefix for all back office controllers - public const string BackOfficeArea = "UmbracoBackOffice"; // Used for area routes of non-api controllers - public const string BackOfficeApiArea = "UmbracoApi"; // Same name as v8 so all routing remains the same - public const string BackOfficeTreeArea = "UmbracoTrees"; // Same name as v8 so all routing remains the same - } + public const string BackOfficeArea = "UmbracoBackOffice"; // Used for area routes of non-api controllers + public const string BackOfficeApiArea = "UmbracoApi"; // Same name as v8 so all routing remains the same + public const string BackOfficeTreeArea = "UmbracoTrees"; // Same name as v8 so all routing remains the same + } - public static class Routing - { - public const string ControllerToken = "controller"; - public const string ActionToken = "action"; - public const string AreaToken = "area"; - } + public static class Routing + { + public const string ControllerToken = "controller"; + public const string ActionToken = "action"; + public const string AreaToken = "area"; + } - public static class EmailTypes - { - public const string HealthCheck = "HealthCheck"; - public const string Notification = "Notification"; - public const string PasswordReset = "PasswordReset"; - public const string TwoFactorAuth = "2FA"; - public const string UserInvite = "UserInvite"; - } + public static class EmailTypes + { + public const string HealthCheck = "HealthCheck"; + public const string Notification = "Notification"; + public const string PasswordReset = "PasswordReset"; + public const string TwoFactorAuth = "2FA"; + public const string UserInvite = "UserInvite"; } } } diff --git a/src/Umbraco.Core/ContentApps/ContentAppFactoryCollection.cs b/src/Umbraco.Core/ContentApps/ContentAppFactoryCollection.cs index e4a5eedf1868..fe89b40813c1 100644 --- a/src/Umbraco.Core/ContentApps/ContentAppFactoryCollection.cs +++ b/src/Umbraco.Core/ContentApps/ContentAppFactoryCollection.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Models.ContentEditing; @@ -8,59 +5,61 @@ using Umbraco.Cms.Core.Security; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.ContentApps -{ - public class ContentAppFactoryCollection : BuilderCollectionBase - { - private readonly ILogger _logger; - private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; +namespace Umbraco.Cms.Core.ContentApps; - public ContentAppFactoryCollection(Func> items, ILogger logger, IBackOfficeSecurityAccessor backOfficeSecurityAccessor) - : base(items) - { - _logger = logger; - _backOfficeSecurityAccessor = backOfficeSecurityAccessor; - } +public class ContentAppFactoryCollection : BuilderCollectionBase +{ + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + private readonly ILogger _logger; - private IEnumerable GetCurrentUserGroups() - { - var currentUser = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser; - return currentUser == null - ? Enumerable.Empty() - : currentUser.Groups; + public ContentAppFactoryCollection(Func> items, + ILogger logger, IBackOfficeSecurityAccessor backOfficeSecurityAccessor) + : base(items) + { + _logger = logger; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + } - } + private IEnumerable GetCurrentUserGroups() + { + IUser currentUser = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser; + return currentUser == null + ? Enumerable.Empty() + : currentUser.Groups; + } - public IEnumerable GetContentAppsFor(object o, IEnumerable? userGroups = null) - { - var roles = GetCurrentUserGroups(); + public IEnumerable GetContentAppsFor(object o, IEnumerable? userGroups = null) + { + IEnumerable roles = GetCurrentUserGroups(); - var apps = this.Select(x => x.GetContentAppFor(o, roles)).WhereNotNull().OrderBy(x => x.Weight).ToList(); + var apps = this.Select(x => x.GetContentAppFor(o, roles)).WhereNotNull().OrderBy(x => x.Weight).ToList(); - var aliases = new HashSet(); - List? dups = null; + var aliases = new HashSet(); + List? dups = null; - foreach (var app in apps) + foreach (ContentApp app in apps) + { + if (app.Alias is not null) { - if (app.Alias is not null) + if (aliases.Contains(app.Alias)) { - - if (aliases.Contains(app.Alias)) - (dups ?? (dups = new List())).Add(app.Alias); - else - aliases.Add(app.Alias); + (dups ?? (dups = new List())).Add(app.Alias); + } + else + { + aliases.Add(app.Alias); } } + } - if (dups != null) - { - // dying is not user-friendly, so let's write to log instead, and wish people read logs... - - //throw new InvalidOperationException($"Duplicate content app aliases found: {string.Join(",", dups)}"); - _logger.LogWarning("Duplicate content app aliases found: {DuplicateAliases}", string.Join(",", dups)); - } + if (dups != null) + { + // dying is not user-friendly, so let's write to log instead, and wish people read logs... - return apps; + //throw new InvalidOperationException($"Duplicate content app aliases found: {string.Join(",", dups)}"); + _logger.LogWarning("Duplicate content app aliases found: {DuplicateAliases}", string.Join(",", dups)); } + + return apps; } } diff --git a/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs b/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs index a80c79a3ef55..4bfa45dbc74e 100644 --- a/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs +++ b/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Composing; @@ -9,31 +6,34 @@ using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Security; -namespace Umbraco.Cms.Core.ContentApps +namespace Umbraco.Cms.Core.ContentApps; + +public class ContentAppFactoryCollectionBuilder : OrderedCollectionBuilderBase { - public class ContentAppFactoryCollectionBuilder : OrderedCollectionBuilderBase - { - protected override ContentAppFactoryCollectionBuilder This => this; + protected override ContentAppFactoryCollectionBuilder This => this; - // need to inject dependencies in the collection, so override creation - public override ContentAppFactoryCollection CreateCollection(IServiceProvider factory) - { - // get the logger factory just-in-time - see note below for manifest parser - var loggerFactory = factory.GetRequiredService(); - var backOfficeSecurityAccessor = factory.GetRequiredService(); - return new ContentAppFactoryCollection( - () => CreateItems(factory), - loggerFactory.CreateLogger(), backOfficeSecurityAccessor); - } + // need to inject dependencies in the collection, so override creation + public override ContentAppFactoryCollection CreateCollection(IServiceProvider factory) + { + // get the logger factory just-in-time - see note below for manifest parser + ILoggerFactory loggerFactory = factory.GetRequiredService(); + IBackOfficeSecurityAccessor backOfficeSecurityAccessor = + factory.GetRequiredService(); + return new ContentAppFactoryCollection( + () => CreateItems(factory), + loggerFactory.CreateLogger(), backOfficeSecurityAccessor); + } - protected override IEnumerable CreateItems(IServiceProvider factory) - { - // get the manifest parser just-in-time - injecting it in the ctor would mean that - // simply getting the builder in order to configure the collection, would require - // its dependencies too, and that can create cycles or other oddities - var manifestParser = factory.GetRequiredService(); - var ioHelper = factory.GetRequiredService(); - return base.CreateItems(factory).Concat(manifestParser.CombinedManifest.ContentApps.Select(x => new ManifestContentAppFactory(x, ioHelper))); - } + protected override IEnumerable CreateItems(IServiceProvider factory) + { + // get the manifest parser just-in-time - injecting it in the ctor would mean that + // simply getting the builder in order to configure the collection, would require + // its dependencies too, and that can create cycles or other oddities + IManifestParser manifestParser = factory.GetRequiredService(); + IIOHelper ioHelper = factory.GetRequiredService(); + return base.CreateItems(factory) + .Concat(manifestParser.CombinedManifest.ContentApps.Select(x => + new ManifestContentAppFactory(x, ioHelper))); } } diff --git a/src/Umbraco.Core/ContentApps/ContentEditorContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ContentEditorContentAppFactory.cs index 948c563ea996..be6e384c5c59 100644 --- a/src/Umbraco.Core/ContentApps/ContentEditorContentAppFactory.cs +++ b/src/Umbraco.Core/ContentApps/ContentEditorContentAppFactory.cs @@ -1,56 +1,54 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.ContentApps +namespace Umbraco.Cms.Core.ContentApps; + +public class ContentEditorContentAppFactory : IContentAppFactory { - public class ContentEditorContentAppFactory : IContentAppFactory - { - // see note on ContentApp - internal const int Weight = -100; + // see note on ContentApp + internal const int Weight = -100; - private ContentApp? _contentApp; - private ContentApp? _mediaApp; - private ContentApp? _memberApp; + private ContentApp? _contentApp; + private ContentApp? _mediaApp; + private ContentApp? _memberApp; - public ContentApp? GetContentAppFor(object o, IEnumerable userGroups) + public ContentApp? GetContentAppFor(object o, IEnumerable userGroups) + { + switch (o) { - switch (o) - { - case IContent content when content.Properties.Count > 0: - return _contentApp ?? (_contentApp = new ContentApp - { - Alias = "umbContent", - Name = "Content", - Icon = Constants.Icons.Content, - View = "views/content/apps/content/content.html", - Weight = Weight - }); + case IContent content when content.Properties.Count > 0: + return _contentApp ?? (_contentApp = new ContentApp + { + Alias = "umbContent", + Name = "Content", + Icon = Constants.Icons.Content, + View = "views/content/apps/content/content.html", + Weight = Weight + }); - case IMedia media when !media.ContentType.IsContainer || media.Properties.Count > 0: - return _mediaApp ?? (_mediaApp = new ContentApp - { - Alias = "umbContent", - Name = "Content", - Icon = Constants.Icons.Content, - View = "views/media/apps/content/content.html", - Weight = Weight - }); + case IMedia media when !media.ContentType.IsContainer || media.Properties.Count > 0: + return _mediaApp ?? (_mediaApp = new ContentApp + { + Alias = "umbContent", + Name = "Content", + Icon = Constants.Icons.Content, + View = "views/media/apps/content/content.html", + Weight = Weight + }); - case IMember _: - return _memberApp ?? (_memberApp = new ContentApp - { - Alias = "umbContent", - Name = "Content", - Icon = Constants.Icons.Content, - View = "views/member/apps/content/content.html", - Weight = Weight - }); + case IMember _: + return _memberApp ?? (_memberApp = new ContentApp + { + Alias = "umbContent", + Name = "Content", + Icon = Constants.Icons.Content, + View = "views/member/apps/content/content.html", + Weight = Weight + }); - default: - return null; - } + default: + return null; } } } diff --git a/src/Umbraco.Core/ContentApps/ContentInfoContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ContentInfoContentAppFactory.cs index 3e068750c4b5..13a3b38021bd 100644 --- a/src/Umbraco.Core/ContentApps/ContentInfoContentAppFactory.cs +++ b/src/Umbraco.Core/ContentApps/ContentInfoContentAppFactory.cs @@ -1,55 +1,53 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.ContentApps +namespace Umbraco.Cms.Core.ContentApps; + +public class ContentInfoContentAppFactory : IContentAppFactory { - public class ContentInfoContentAppFactory : IContentAppFactory - { - // see note on ContentApp - private const int Weight = +100; + // see note on ContentApp + private const int Weight = +100; - private ContentApp? _contentApp; - private ContentApp? _mediaApp; - private ContentApp? _memberApp; + private ContentApp? _contentApp; + private ContentApp? _mediaApp; + private ContentApp? _memberApp; - public ContentApp? GetContentAppFor(object o, IEnumerable userGroups) + public ContentApp? GetContentAppFor(object o, IEnumerable userGroups) + { + switch (o) { - switch (o) - { - case IContent _: - return _contentApp ??= new ContentApp - { - Alias = "umbInfo", - Name = "Info", - Icon = "icon-info", - View = "views/content/apps/info/info.html", - Weight = Weight - }; + case IContent _: + return _contentApp ??= new ContentApp + { + Alias = "umbInfo", + Name = "Info", + Icon = "icon-info", + View = "views/content/apps/info/info.html", + Weight = Weight + }; - case IMedia _: - return _mediaApp ??= new ContentApp - { - Alias = "umbInfo", - Name = "Info", - Icon = "icon-info", - View = "views/media/apps/info/info.html", - Weight = Weight - }; - case IMember _: - return _memberApp ??= new ContentApp - { - Alias = "umbInfo", - Name = "Info", - Icon = "icon-info", - View = "views/member/apps/info/info.html", - Weight = Weight - }; + case IMedia _: + return _mediaApp ??= new ContentApp + { + Alias = "umbInfo", + Name = "Info", + Icon = "icon-info", + View = "views/media/apps/info/info.html", + Weight = Weight + }; + case IMember _: + return _memberApp ??= new ContentApp + { + Alias = "umbInfo", + Name = "Info", + Icon = "icon-info", + View = "views/member/apps/info/info.html", + Weight = Weight + }; - default: - return null; - } + default: + return null; } } } diff --git a/src/Umbraco.Core/ContentApps/ContentTypeDesignContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ContentTypeDesignContentAppFactory.cs index 0fe482e7d4e5..3011fbb7dfe6 100644 --- a/src/Umbraco.Core/ContentApps/ContentTypeDesignContentAppFactory.cs +++ b/src/Umbraco.Core/ContentApps/ContentTypeDesignContentAppFactory.cs @@ -1,32 +1,30 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.ContentApps +namespace Umbraco.Cms.Core.ContentApps; + +public class ContentTypeDesignContentAppFactory : IContentAppFactory { - public class ContentTypeDesignContentAppFactory : IContentAppFactory - { - private const int Weight = -200; + private const int Weight = -200; - private ContentApp? _contentTypeApp; + private ContentApp? _contentTypeApp; - public ContentApp? GetContentAppFor(object source, IEnumerable userGroups) + public ContentApp? GetContentAppFor(object source, IEnumerable userGroups) + { + switch (source) { - switch (source) - { - case IContentType _: - return _contentTypeApp ??= new ContentApp() - { - Alias = "design", - Name = "Design", - Icon = "icon-document-dashed-line", - View = "views/documentTypes/views/design/design.html", - Weight = Weight - }; - default: - return null; - } + case IContentType _: + return _contentTypeApp ??= new ContentApp + { + Alias = "design", + Name = "Design", + Icon = "icon-document-dashed-line", + View = "views/documentTypes/views/design/design.html", + Weight = Weight + }; + default: + return null; } } } diff --git a/src/Umbraco.Core/ContentApps/ContentTypeListViewContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ContentTypeListViewContentAppFactory.cs index 6ddf98e132fb..b1a2a8e5c9cd 100644 --- a/src/Umbraco.Core/ContentApps/ContentTypeListViewContentAppFactory.cs +++ b/src/Umbraco.Core/ContentApps/ContentTypeListViewContentAppFactory.cs @@ -1,32 +1,30 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.ContentApps +namespace Umbraco.Cms.Core.ContentApps; + +public class ContentTypeListViewContentAppFactory : IContentAppFactory { - public class ContentTypeListViewContentAppFactory : IContentAppFactory - { - private const int Weight = -180; + private const int Weight = -180; - private ContentApp? _contentTypeApp; + private ContentApp? _contentTypeApp; - public ContentApp? GetContentAppFor(object source, IEnumerable userGroups) + public ContentApp? GetContentAppFor(object source, IEnumerable userGroups) + { + switch (source) { - switch (source) - { - case IContentType _: - return _contentTypeApp ??= new ContentApp() - { - Alias = "listView", - Name = "List view", - Icon = "icon-list", - View = "views/documentTypes/views/listview/listview.html", - Weight = Weight - }; - default: - return null; - } + case IContentType _: + return _contentTypeApp ??= new ContentApp + { + Alias = "listView", + Name = "List view", + Icon = "icon-list", + View = "views/documentTypes/views/listview/listview.html", + Weight = Weight + }; + default: + return null; } } } diff --git a/src/Umbraco.Core/ContentApps/ContentTypePermissionsContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ContentTypePermissionsContentAppFactory.cs index 98b82d24e77a..b2436388bff4 100644 --- a/src/Umbraco.Core/ContentApps/ContentTypePermissionsContentAppFactory.cs +++ b/src/Umbraco.Core/ContentApps/ContentTypePermissionsContentAppFactory.cs @@ -1,32 +1,30 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.ContentApps +namespace Umbraco.Cms.Core.ContentApps; + +public class ContentTypePermissionsContentAppFactory : IContentAppFactory { - public class ContentTypePermissionsContentAppFactory : IContentAppFactory - { - private const int Weight = -160; + private const int Weight = -160; - private ContentApp? _contentTypeApp; + private ContentApp? _contentTypeApp; - public ContentApp? GetContentAppFor(object source, IEnumerable userGroups) + public ContentApp? GetContentAppFor(object source, IEnumerable userGroups) + { + switch (source) { - switch (source) - { - case IContentType _: - return _contentTypeApp ??= new ContentApp() - { - Alias = "permissions", - Name = "Permissions", - Icon = "icon-keychain", - View = "views/documentTypes/views/permissions/permissions.html", - Weight = Weight - }; - default: - return null; - } + case IContentType _: + return _contentTypeApp ??= new ContentApp + { + Alias = "permissions", + Name = "Permissions", + Icon = "icon-keychain", + View = "views/documentTypes/views/permissions/permissions.html", + Weight = Weight + }; + default: + return null; } } } diff --git a/src/Umbraco.Core/ContentApps/ContentTypeTemplatesContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ContentTypeTemplatesContentAppFactory.cs index 74e57d76c92b..7a28fba5b64a 100644 --- a/src/Umbraco.Core/ContentApps/ContentTypeTemplatesContentAppFactory.cs +++ b/src/Umbraco.Core/ContentApps/ContentTypeTemplatesContentAppFactory.cs @@ -1,32 +1,30 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.ContentApps +namespace Umbraco.Cms.Core.ContentApps; + +public class ContentTypeTemplatesContentAppFactory : IContentAppFactory { - public class ContentTypeTemplatesContentAppFactory : IContentAppFactory - { - private const int Weight = -140; + private const int Weight = -140; - private ContentApp? _contentTypeApp; + private ContentApp? _contentTypeApp; - public ContentApp? GetContentAppFor(object source, IEnumerable userGroups) + public ContentApp? GetContentAppFor(object source, IEnumerable userGroups) + { + switch (source) { - switch (source) - { - case IContentType _: - return _contentTypeApp ??= new ContentApp() - { - Alias = "templates", - Name = "Templates", - Icon = "icon-layout", - View = "views/documentTypes/views/templates/templates.html", - Weight = Weight - }; - default: - return null; - } + case IContentType _: + return _contentTypeApp ??= new ContentApp + { + Alias = "templates", + Name = "Templates", + Icon = "icon-layout", + View = "views/documentTypes/views/templates/templates.html", + Weight = Weight + }; + default: + return null; } } } diff --git a/src/Umbraco.Core/ContentApps/DictionaryContentAppFactory.cs b/src/Umbraco.Core/ContentApps/DictionaryContentAppFactory.cs index ae8a957df7e1..fc2226036b10 100644 --- a/src/Umbraco.Core/ContentApps/DictionaryContentAppFactory.cs +++ b/src/Umbraco.Core/ContentApps/DictionaryContentAppFactory.cs @@ -1,32 +1,30 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.ContentApps +namespace Umbraco.Cms.Core.ContentApps; + +internal class DictionaryContentAppFactory : IContentAppFactory { - internal class DictionaryContentAppFactory : IContentAppFactory - { - private const int Weight = -100; + private const int Weight = -100; - private ContentApp? _dictionaryApp; + private ContentApp? _dictionaryApp; - public ContentApp? GetContentAppFor(object source, IEnumerable userGroups) + public ContentApp? GetContentAppFor(object source, IEnumerable userGroups) + { + switch (source) { - switch (source) - { - case IDictionaryItem _: - return _dictionaryApp ??= new ContentApp - { - Alias = "dictionaryContent", - Name = "Content", - Icon = "icon-document", - View = "views/dictionary/views/content/content.html", - Weight = Weight - }; - default: - return null; - } + case IDictionaryItem _: + return _dictionaryApp ??= new ContentApp + { + Alias = "dictionaryContent", + Name = "Content", + Icon = "icon-document", + View = "views/dictionary/views/content/content.html", + Weight = Weight + }; + default: + return null; } } } diff --git a/src/Umbraco.Core/ContentApps/ListViewContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ListViewContentAppFactory.cs index d33c50499f53..dc76f391bf61 100644 --- a/src/Umbraco.Core/ContentApps/ListViewContentAppFactory.cs +++ b/src/Umbraco.Core/ContentApps/ListViewContentAppFactory.cs @@ -1,135 +1,158 @@ -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.ContentApps +namespace Umbraco.Cms.Core.ContentApps; + +public class ListViewContentAppFactory : IContentAppFactory { - public class ListViewContentAppFactory : IContentAppFactory + // see note on ContentApp + private const int Weight = -666; + + private readonly IDataTypeService _dataTypeService; + private readonly PropertyEditorCollection _propertyEditors; + + public ListViewContentAppFactory(IDataTypeService dataTypeService, PropertyEditorCollection propertyEditors) { - // see note on ContentApp - private const int Weight = -666; + _dataTypeService = dataTypeService; + _propertyEditors = propertyEditors; + } - private readonly IDataTypeService _dataTypeService; - private readonly PropertyEditorCollection _propertyEditors; + public ContentApp? GetContentAppFor(object o, IEnumerable userGroups) + { + string contentTypeAlias, entityType; + int dtdId; - public ListViewContentAppFactory(IDataTypeService dataTypeService, PropertyEditorCollection propertyEditors) + switch (o) { - _dataTypeService = dataTypeService; - _propertyEditors = propertyEditors; + case IContent content when !content.ContentType.IsContainer: + return null; + case IContent content: + contentTypeAlias = content.ContentType.Alias; + entityType = "content"; + dtdId = Constants.DataTypes.DefaultContentListView; + break; + case IMedia media when !media.ContentType.IsContainer && + media.ContentType.Alias != Constants.Conventions.MediaTypes.Folder: + return null; + case IMedia media: + contentTypeAlias = media.ContentType.Alias; + entityType = "media"; + dtdId = Constants.DataTypes.DefaultMediaListView; + break; + default: + return null; } - public ContentApp? GetContentAppFor(object o, IEnumerable userGroups) + return CreateContentApp(_dataTypeService, _propertyEditors, entityType, contentTypeAlias, dtdId); + } + + public static ContentApp CreateContentApp(IDataTypeService dataTypeService, + PropertyEditorCollection propertyEditors, + string entityType, string contentTypeAlias, + int defaultListViewDataType) + { + if (dataTypeService == null) { - string contentTypeAlias, entityType; - int dtdId; + throw new ArgumentNullException(nameof(dataTypeService)); + } - switch (o) - { - case IContent content when !content.ContentType.IsContainer: - return null; - case IContent content: - contentTypeAlias = content.ContentType.Alias; - entityType = "content"; - dtdId = Constants.DataTypes.DefaultContentListView; - break; - case IMedia media when !media.ContentType.IsContainer && media.ContentType.Alias != Constants.Conventions.MediaTypes.Folder: - return null; - case IMedia media: - contentTypeAlias = media.ContentType.Alias; - entityType = "media"; - dtdId = Constants.DataTypes.DefaultMediaListView; - break; - default: - return null; - } + if (propertyEditors == null) + { + throw new ArgumentNullException(nameof(propertyEditors)); + } - return CreateContentApp(_dataTypeService, _propertyEditors, entityType, contentTypeAlias, dtdId); + if (string.IsNullOrWhiteSpace(entityType)) + { + throw new ArgumentException("message", nameof(entityType)); } - public static ContentApp CreateContentApp(IDataTypeService dataTypeService, - PropertyEditorCollection propertyEditors, - string entityType, string contentTypeAlias, - int defaultListViewDataType) + if (string.IsNullOrWhiteSpace(contentTypeAlias)) { - if (dataTypeService == null) throw new ArgumentNullException(nameof(dataTypeService)); - if (propertyEditors == null) throw new ArgumentNullException(nameof(propertyEditors)); - if (string.IsNullOrWhiteSpace(entityType)) throw new ArgumentException("message", nameof(entityType)); - if (string.IsNullOrWhiteSpace(contentTypeAlias)) throw new ArgumentException("message", nameof(contentTypeAlias)); - if (defaultListViewDataType == default) throw new ArgumentException("defaultListViewDataType", nameof(defaultListViewDataType)); + throw new ArgumentException("message", nameof(contentTypeAlias)); + } - var contentApp = new ContentApp - { - Alias = "umbListView", - Name = "Child items", - Icon = "icon-list", - View = "views/content/apps/listview/listview.html", - Weight = Weight - }; + if (defaultListViewDataType == default) + { + throw new ArgumentException("defaultListViewDataType", nameof(defaultListViewDataType)); + } + + var contentApp = new ContentApp + { + Alias = "umbListView", + Name = "Child items", + Icon = "icon-list", + View = "views/content/apps/listview/listview.html", + Weight = Weight + }; - var customDtdName = Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias; + var customDtdName = Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias; - //first try to get the custom one if there is one - var dt = dataTypeService.GetDataType(customDtdName) - ?? dataTypeService.GetDataType(defaultListViewDataType); + //first try to get the custom one if there is one + IDataType dt = dataTypeService.GetDataType(customDtdName) + ?? dataTypeService.GetDataType(defaultListViewDataType); - if (dt == null) - { - throw new InvalidOperationException("No list view data type was found for this document type, ensure that the default list view data types exists and/or that your custom list view data type exists"); - } + if (dt == null) + { + throw new InvalidOperationException( + "No list view data type was found for this document type, ensure that the default list view data types exists and/or that your custom list view data type exists"); + } - var editor = propertyEditors[dt.EditorAlias]; - if (editor == null) - { - throw new NullReferenceException("The property editor with alias " + dt.EditorAlias + " does not exist"); - } + IDataEditor editor = propertyEditors[dt.EditorAlias]; + if (editor == null) + { + throw new NullReferenceException("The property editor with alias " + dt.EditorAlias + " does not exist"); + } - var listViewConfig = editor.GetConfigurationEditor().ToConfigurationEditor(dt.Configuration); - //add the entity type to the config - listViewConfig["entityType"] = entityType; + IDictionary listViewConfig = + editor.GetConfigurationEditor().ToConfigurationEditor(dt.Configuration); + //add the entity type to the config + listViewConfig["entityType"] = entityType; - //Override Tab Label if tabName is provided - if (listViewConfig.ContainsKey("tabName")) + //Override Tab Label if tabName is provided + if (listViewConfig.ContainsKey("tabName")) + { + var configTabName = listViewConfig["tabName"]; + if (configTabName != null && string.IsNullOrWhiteSpace(configTabName.ToString()) == false) { - var configTabName = listViewConfig["tabName"]; - if (configTabName != null && String.IsNullOrWhiteSpace(configTabName.ToString()) == false) - contentApp.Name = configTabName.ToString(); + contentApp.Name = configTabName.ToString(); } + } - //Override Icon if icon is provided - if (listViewConfig.ContainsKey("icon")) + //Override Icon if icon is provided + if (listViewConfig.ContainsKey("icon")) + { + var configIcon = listViewConfig["icon"]; + if (configIcon != null && string.IsNullOrWhiteSpace(configIcon.ToString()) == false) { - var configIcon = listViewConfig["icon"]; - if (configIcon != null && String.IsNullOrWhiteSpace(configIcon.ToString()) == false) - contentApp.Icon = configIcon.ToString(); + contentApp.Icon = configIcon.ToString(); } + } - // if the list view is configured to show umbContent first, update the list view content app weight accordingly - if(listViewConfig.ContainsKey("showContentFirst") && - listViewConfig["showContentFirst"]?.ToString().TryConvertTo().Result == true) + // if the list view is configured to show umbContent first, update the list view content app weight accordingly + if (listViewConfig.ContainsKey("showContentFirst") && + listViewConfig["showContentFirst"]?.ToString().TryConvertTo().Result == true) + { + contentApp.Weight = ContentEditorContentAppFactory.Weight + 1; + } + + //This is the view model used for the list view app + contentApp.ViewModel = new List + { + new() { - contentApp.Weight = ContentEditorContentAppFactory.Weight + 1; + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}containerView", + Label = "", + Value = null, + View = editor.GetValueEditor().View, + HideLabel = true, + Config = listViewConfig } + }; - //This is the view model used for the list view app - contentApp.ViewModel = new List - { - new ContentPropertyDisplay - { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}containerView", - Label = "", - Value = null, - View = editor.GetValueEditor().View, - HideLabel = true, - Config = listViewConfig - } - }; - - return contentApp; - } + return contentApp; } } diff --git a/src/Umbraco.Core/ContentApps/MemberEditorContentAppFactory.cs b/src/Umbraco.Core/ContentApps/MemberEditorContentAppFactory.cs index ae5e783bbcad..e3b4148fa158 100644 --- a/src/Umbraco.Core/ContentApps/MemberEditorContentAppFactory.cs +++ b/src/Umbraco.Core/ContentApps/MemberEditorContentAppFactory.cs @@ -1,34 +1,32 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.ContentApps +namespace Umbraco.Cms.Core.ContentApps; + +internal class MemberEditorContentAppFactory : IContentAppFactory { - internal class MemberEditorContentAppFactory : IContentAppFactory - { - // see note on ContentApp - internal const int Weight = +50; + // see note on ContentApp + internal const int Weight = +50; - private ContentApp? _memberApp; + private ContentApp? _memberApp; - public ContentApp? GetContentAppFor(object source, IEnumerable userGroups) + public ContentApp? GetContentAppFor(object source, IEnumerable userGroups) + { + switch (source) { - switch (source) - { - case IMember _: - return _memberApp ??= new ContentApp - { - Alias = "umbMembership", - Name = "Member", - Icon = "icon-user", - View = "views/member/apps/membership/membership.html", - Weight = Weight - }; + case IMember _: + return _memberApp ??= new ContentApp + { + Alias = "umbMembership", + Name = "Member", + Icon = "icon-user", + View = "views/member/apps/membership/membership.html", + Weight = Weight + }; - default: - return null; - } + default: + return null; } } } diff --git a/src/Umbraco.Core/ConventionsHelper.cs b/src/Umbraco.Core/ConventionsHelper.cs index 2f9203ef92a7..c388d7dff005 100644 --- a/src/Umbraco.Core/ConventionsHelper.cs +++ b/src/Umbraco.Core/ConventionsHelper.cs @@ -1,26 +1,20 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Strings; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public static class ConventionsHelper { - public static class ConventionsHelper - { - public static Dictionary GetStandardPropertyTypeStubs(IShortStringHelper shortStringHelper) => - new Dictionary + public static Dictionary GetStandardPropertyTypeStubs(IShortStringHelper shortStringHelper) => + new() + { { - { - Constants.Conventions.Member.Comments, - new PropertyType( - shortStringHelper, - Constants.PropertyEditors.Aliases.TextArea, - ValueStorageType.Ntext, - true, - Constants.Conventions.Member.Comments) - { - Name = Constants.Conventions.Member.CommentsLabel, - } - }, - }; - } + Constants.Conventions.Member.Comments, new PropertyType( + shortStringHelper, + Constants.PropertyEditors.Aliases.TextArea, + ValueStorageType.Ntext, + true, + Constants.Conventions.Member.Comments) {Name = Constants.Conventions.Member.CommentsLabel} + } + }; } diff --git a/src/Umbraco.Core/CustomBooleanTypeConverter.cs b/src/Umbraco.Core/CustomBooleanTypeConverter.cs index 253f070b4061..c8f896933211 100644 --- a/src/Umbraco.Core/CustomBooleanTypeConverter.cs +++ b/src/Umbraco.Core/CustomBooleanTypeConverter.cs @@ -1,34 +1,49 @@ -using System; using System.ComponentModel; +using System.Globalization; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Allows for converting string representations of 0 and 1 to boolean +/// +public class CustomBooleanTypeConverter : BooleanConverter { - /// - /// Allows for converting string representations of 0 and 1 to boolean - /// - public class CustomBooleanTypeConverter : BooleanConverter + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) + { + if (sourceType == typeof(string)) + { + return true; + } + + return base.CanConvertFrom(context, sourceType); + } + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) { - public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) + if (value is string) { - if (sourceType == typeof(string)) + var str = (string)value; + if (str == null || str.Length == 0 || str == "0") + { + return false; + } + + if (str == "1") { return true; } - return base.CanConvertFrom(context, sourceType); - } - public override object? ConvertFrom(ITypeDescriptorContext? context, System.Globalization.CultureInfo? culture, object value) - { - if (value is string) + if (str.Equals("Yes", StringComparison.OrdinalIgnoreCase)) { - var str = (string)value; - if (str == null || str.Length == 0 || str == "0") return false; - if (str == "1") return true; - if (str.Equals("Yes", StringComparison.OrdinalIgnoreCase)) return true; - if (str.Equals("No", StringComparison.OrdinalIgnoreCase)) return false; + return true; } - return base.ConvertFrom(context, culture, value); + if (str.Equals("No", StringComparison.OrdinalIgnoreCase)) + { + return false; + } } + + return base.ConvertFrom(context, culture, value); } } diff --git a/src/Umbraco.Core/Dashboards/AccessRule.cs b/src/Umbraco.Core/Dashboards/AccessRule.cs index 070659518e32..a728c9561cbc 100644 --- a/src/Umbraco.Core/Dashboards/AccessRule.cs +++ b/src/Umbraco.Core/Dashboards/AccessRule.cs @@ -1,13 +1,13 @@ -namespace Umbraco.Cms.Core.Dashboards +namespace Umbraco.Cms.Core.Dashboards; + +/// +/// Implements . +/// +public class AccessRule : IAccessRule { - /// - /// Implements . - /// - public class AccessRule : IAccessRule - { - /// - public AccessRuleType Type { get; set; } = AccessRuleType.Unknown; - /// - public string? Value { get; set; } - } + /// + public AccessRuleType Type { get; set; } = AccessRuleType.Unknown; + + /// + public string? Value { get; set; } } diff --git a/src/Umbraco.Core/Dashboards/AccessRuleType.cs b/src/Umbraco.Core/Dashboards/AccessRuleType.cs index 103d944de854..479d7f6be63c 100644 --- a/src/Umbraco.Core/Dashboards/AccessRuleType.cs +++ b/src/Umbraco.Core/Dashboards/AccessRuleType.cs @@ -1,28 +1,27 @@ -namespace Umbraco.Cms.Core.Dashboards +namespace Umbraco.Cms.Core.Dashboards; + +/// +/// Defines dashboard access rules type. +/// +public enum AccessRuleType { /// - /// Defines dashboard access rules type. + /// Unknown (default value). /// - public enum AccessRuleType - { - /// - /// Unknown (default value). - /// - Unknown = 0, + Unknown = 0, - /// - /// Grant access to the dashboard if user belongs to the specified user group. - /// - Grant, + /// + /// Grant access to the dashboard if user belongs to the specified user group. + /// + Grant, - /// - /// Deny access to the dashboard if user belongs to the specified user group. - /// - Deny, + /// + /// Deny access to the dashboard if user belongs to the specified user group. + /// + Deny, - /// - /// Grant access to the dashboard if user has access to the specified section. - /// - GrantBySection - } + /// + /// Grant access to the dashboard if user has access to the specified section. + /// + GrantBySection } diff --git a/src/Umbraco.Core/Dashboards/AnalyticsDashboard.cs b/src/Umbraco.Core/Dashboards/AnalyticsDashboard.cs index 1be6e045d014..a409d128bed8 100644 --- a/src/Umbraco.Core/Dashboards/AnalyticsDashboard.cs +++ b/src/Umbraco.Core/Dashboards/AnalyticsDashboard.cs @@ -1,15 +1,12 @@ -using System; +namespace Umbraco.Cms.Core.Dashboards; -namespace Umbraco.Cms.Core.Dashboards +public class AnalyticsDashboard : IDashboard { - public class AnalyticsDashboard : IDashboard - { - public string Alias => "settingsAnalytics"; + public string Alias => "settingsAnalytics"; - public string[] Sections => new [] { "settings" }; + public string[] Sections => new[] {"settings"}; - public string View => "views/dashboard/settings/analytics.html"; + public string View => "views/dashboard/settings/analytics.html"; - public IAccessRule[] AccessRules => Array.Empty(); - } + public IAccessRule[] AccessRules => Array.Empty(); } diff --git a/src/Umbraco.Core/Dashboards/ContentDashboard.cs b/src/Umbraco.Core/Dashboards/ContentDashboard.cs index 135fe4304d79..6664bb924fb5 100644 --- a/src/Umbraco.Core/Dashboards/ContentDashboard.cs +++ b/src/Umbraco.Core/Dashboards/ContentDashboard.cs @@ -1,17 +1,15 @@ -using System; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Dashboards +namespace Umbraco.Cms.Core.Dashboards; + +[Weight(10)] +public class ContentDashboard : IDashboard { - [Weight(10)] - public class ContentDashboard : IDashboard - { - public string Alias => "contentIntro"; + public string Alias => "contentIntro"; - public string[] Sections => new[] { "content" }; + public string[] Sections => new[] {"content"}; - public string View => "views/dashboard/default/startupdashboardintro.html"; + public string View => "views/dashboard/default/startupdashboardintro.html"; - public IAccessRule[] AccessRules { get; } = Array.Empty(); - } + public IAccessRule[] AccessRules { get; } = Array.Empty(); } diff --git a/src/Umbraco.Core/Dashboards/DashboardCollection.cs b/src/Umbraco.Core/Dashboards/DashboardCollection.cs index e5c8378139f2..68b8b683c7c9 100644 --- a/src/Umbraco.Core/Dashboards/DashboardCollection.cs +++ b/src/Umbraco.Core/Dashboards/DashboardCollection.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Dashboards +namespace Umbraco.Cms.Core.Dashboards; + +public class DashboardCollection : BuilderCollectionBase { - public class DashboardCollection : BuilderCollectionBase + public DashboardCollection(Func> items) : base(items) { - public DashboardCollection(Func> items) : base(items) - { - } } } diff --git a/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs b/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs index 348e81e38379..bd5e52102788 100644 --- a/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs +++ b/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs @@ -1,46 +1,43 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Manifest; -using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Dashboards +namespace Umbraco.Cms.Core.Dashboards; + +public class + DashboardCollectionBuilder : WeightedCollectionBuilderBase { - public class DashboardCollectionBuilder : WeightedCollectionBuilderBase + protected override DashboardCollectionBuilder This => this; + + protected override IEnumerable CreateItems(IServiceProvider factory) { - protected override DashboardCollectionBuilder This => this; + // get the manifest parser just-in-time - injecting it in the ctor would mean that + // simply getting the builder in order to configure the collection, would require + // its dependencies too, and that can create cycles or other oddities + IManifestParser manifestParser = factory.GetRequiredService(); - protected override IEnumerable CreateItems(IServiceProvider factory) - { - // get the manifest parser just-in-time - injecting it in the ctor would mean that - // simply getting the builder in order to configure the collection, would require - // its dependencies too, and that can create cycles or other oddities - var manifestParser = factory.GetRequiredService(); + IEnumerable dashboardSections = + Merge(base.CreateItems(factory), manifestParser.CombinedManifest.Dashboards); - var dashboardSections = Merge(base.CreateItems(factory), manifestParser.CombinedManifest.Dashboards); + return dashboardSections; + } - return dashboardSections; - } + private IEnumerable Merge(IEnumerable dashboardsFromCode, + IReadOnlyList dashboardFromManifest) => + dashboardsFromCode.Concat(dashboardFromManifest) + .Where(x => !string.IsNullOrEmpty(x.Alias)) + .OrderBy(GetWeight); - private IEnumerable Merge(IEnumerable dashboardsFromCode, IReadOnlyList dashboardFromManifest) + private int GetWeight(IDashboard dashboard) + { + switch (dashboard) { - return dashboardsFromCode.Concat(dashboardFromManifest) - .Where(x => !string.IsNullOrEmpty(x.Alias)) - .OrderBy(GetWeight); - } + case ManifestDashboard manifestDashboardDefinition: + return manifestDashboardDefinition.Weight; - private int GetWeight(IDashboard dashboard) - { - switch (dashboard) - { - case ManifestDashboard manifestDashboardDefinition: - return manifestDashboardDefinition.Weight; - - default: - return GetWeight(dashboard.GetType()); - } + default: + return GetWeight(dashboard.GetType()); } } } diff --git a/src/Umbraco.Core/Dashboards/DashboardSlim.cs b/src/Umbraco.Core/Dashboards/DashboardSlim.cs index 9ff2b51bafa6..010382b5dc98 100644 --- a/src/Umbraco.Core/Dashboards/DashboardSlim.cs +++ b/src/Umbraco.Core/Dashboards/DashboardSlim.cs @@ -1,12 +1,11 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Dashboards +namespace Umbraco.Cms.Core.Dashboards; + +[DataContract(IsReference = true)] +public class DashboardSlim : IDashboardSlim { - [DataContract(IsReference = true)] - public class DashboardSlim : IDashboardSlim - { - public string? Alias { get; set; } + public string? Alias { get; set; } - public string? View { get; set; } - } + public string? View { get; set; } } diff --git a/src/Umbraco.Core/Dashboards/ExamineDashboard.cs b/src/Umbraco.Core/Dashboards/ExamineDashboard.cs index 5411f1d3cea1..52828a12132d 100644 --- a/src/Umbraco.Core/Dashboards/ExamineDashboard.cs +++ b/src/Umbraco.Core/Dashboards/ExamineDashboard.cs @@ -1,19 +1,15 @@ -using System; -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Dashboards -{ - [Weight(20)] - public class ExamineDashboard : IDashboard - { - public string Alias => "settingsExamine"; - - public string[] Sections => new [] { "settings" }; +namespace Umbraco.Cms.Core.Dashboards; - public string View => "views/dashboard/settings/examinemanagement.html"; +[Weight(20)] +public class ExamineDashboard : IDashboard +{ + public string Alias => "settingsExamine"; - public IAccessRule[] AccessRules => Array.Empty(); - } + public string[] Sections => new[] {"settings"}; + public string View => "views/dashboard/settings/examinemanagement.html"; + public IAccessRule[] AccessRules => Array.Empty(); } diff --git a/src/Umbraco.Core/Dashboards/FormsDashboard.cs b/src/Umbraco.Core/Dashboards/FormsDashboard.cs index c56ad7c51a0e..ba1888d62d22 100644 --- a/src/Umbraco.Core/Dashboards/FormsDashboard.cs +++ b/src/Umbraco.Core/Dashboards/FormsDashboard.cs @@ -1,17 +1,15 @@ -using System; -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Dashboards +namespace Umbraco.Cms.Core.Dashboards; + +[Weight(10)] +public class FormsDashboard : IDashboard { - [Weight(10)] - public class FormsDashboard : IDashboard - { - public string Alias => "formsInstall"; + public string Alias => "formsInstall"; - public string[] Sections => new [] { Constants.Applications.Forms }; + public string[] Sections => new[] {Constants.Applications.Forms}; - public string View => "views/dashboard/forms/formsdashboardintro.html"; + public string View => "views/dashboard/forms/formsdashboardintro.html"; - public IAccessRule[] AccessRules => Array.Empty(); - } + public IAccessRule[] AccessRules => Array.Empty(); } diff --git a/src/Umbraco.Core/Dashboards/HealthCheckDashboard.cs b/src/Umbraco.Core/Dashboards/HealthCheckDashboard.cs index 24b4efaf6dbb..658a9a89a0af 100644 --- a/src/Umbraco.Core/Dashboards/HealthCheckDashboard.cs +++ b/src/Umbraco.Core/Dashboards/HealthCheckDashboard.cs @@ -1,19 +1,15 @@ -using System; -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Dashboards -{ - [Weight(50)] - public class HealthCheckDashboard : IDashboard - { - public string Alias => "settingsHealthCheck"; - - public string[] Sections => new [] { "settings" }; +namespace Umbraco.Cms.Core.Dashboards; - public string View => "views/dashboard/settings/healthcheck.html"; +[Weight(50)] +public class HealthCheckDashboard : IDashboard +{ + public string Alias => "settingsHealthCheck"; - public IAccessRule[] AccessRules => Array.Empty(); - } + public string[] Sections => new[] {"settings"}; + public string View => "views/dashboard/settings/healthcheck.html"; + public IAccessRule[] AccessRules => Array.Empty(); } diff --git a/src/Umbraco.Core/Dashboards/IAccessRule.cs b/src/Umbraco.Core/Dashboards/IAccessRule.cs index 9f8c1209104d..b77ffbcf762d 100644 --- a/src/Umbraco.Core/Dashboards/IAccessRule.cs +++ b/src/Umbraco.Core/Dashboards/IAccessRule.cs @@ -1,18 +1,17 @@ -namespace Umbraco.Cms.Core.Dashboards +namespace Umbraco.Cms.Core.Dashboards; + +/// +/// Represents an access rule. +/// +public interface IAccessRule { /// - /// Represents an access rule. + /// Gets or sets the rule type. /// - public interface IAccessRule - { - /// - /// Gets or sets the rule type. - /// - AccessRuleType Type { get; set; } + AccessRuleType Type { get; set; } - /// - /// Gets or sets the value for the rule. - /// - string? Value { get; set; } - } + /// + /// Gets or sets the value for the rule. + /// + string? Value { get; set; } } diff --git a/src/Umbraco.Core/Dashboards/IDashboard.cs b/src/Umbraco.Core/Dashboards/IDashboard.cs index 41a60cb518e7..74ceb78a6949 100644 --- a/src/Umbraco.Core/Dashboards/IDashboard.cs +++ b/src/Umbraco.Core/Dashboards/IDashboard.cs @@ -1,37 +1,44 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Dashboards +namespace Umbraco.Cms.Core.Dashboards; + +/// +/// Represents a dashboard. +/// +public interface IDashboard : IDashboardSlim { /// - /// Represents a dashboard. + /// Gets the aliases of sections/applications where this dashboard appears. /// - public interface IDashboard : IDashboardSlim - { - /// - /// Gets the aliases of sections/applications where this dashboard appears. - /// - /// - /// This field is *not* needed by the UI and therefore we want to exclude - /// it from serialization, but it is deserialized as part of the manifest, - /// therefore we cannot plainly ignore it. - /// So, it has to remain a data member, plus we use our special - /// JsonDontSerialize attribute (see attribute for more details). - /// - [DataMember(Name = "sections")] - string[] Sections { get; } + /// + /// + /// This field is *not* needed by the UI and therefore we want to exclude + /// it from serialization, but it is deserialized as part of the manifest, + /// therefore we cannot plainly ignore it. + /// + /// + /// So, it has to remain a data member, plus we use our special + /// JsonDontSerialize attribute (see attribute for more details). + /// + /// + [DataMember(Name = "sections")] + string[] Sections { get; } - /// - /// Gets the access rule determining the visibility of the dashboard. - /// - /// - /// This field is *not* needed by the UI and therefore we want to exclude - /// it from serialization, but it is deserialized as part of the manifest, - /// therefore we cannot plainly ignore it. - /// So, it has to remain a data member, plus we use our special - /// JsonDontSerialize attribute (see attribute for more details). - /// - [DataMember(Name = "access")] - IAccessRule[] AccessRules { get; } - } + /// + /// Gets the access rule determining the visibility of the dashboard. + /// + /// + /// + /// This field is *not* needed by the UI and therefore we want to exclude + /// it from serialization, but it is deserialized as part of the manifest, + /// therefore we cannot plainly ignore it. + /// + /// + /// So, it has to remain a data member, plus we use our special + /// JsonDontSerialize attribute (see attribute for more details). + /// + /// + [DataMember(Name = "access")] + IAccessRule[] AccessRules { get; } } diff --git a/src/Umbraco.Core/Dashboards/IDashboardSlim.cs b/src/Umbraco.Core/Dashboards/IDashboardSlim.cs index 4859f5dc8437..d1c479e1a447 100644 --- a/src/Umbraco.Core/Dashboards/IDashboardSlim.cs +++ b/src/Umbraco.Core/Dashboards/IDashboardSlim.cs @@ -1,22 +1,21 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Dashboards +namespace Umbraco.Cms.Core.Dashboards; + +/// +/// Represents a dashboard with only minimal data. +/// +public interface IDashboardSlim { /// - /// Represents a dashboard with only minimal data. + /// Gets the alias of the dashboard. /// - public interface IDashboardSlim - { - /// - /// Gets the alias of the dashboard. - /// - [DataMember(Name = "alias")] - string? Alias { get; } + [DataMember(Name = "alias")] + string? Alias { get; } - /// - /// Gets the view used to render the dashboard. - /// - [DataMember(Name = "view")] - string? View { get; } - } + /// + /// Gets the view used to render the dashboard. + /// + [DataMember(Name = "view")] + string? View { get; } } diff --git a/src/Umbraco.Core/Dashboards/MediaDashboard.cs b/src/Umbraco.Core/Dashboards/MediaDashboard.cs index acbad0bc2a9e..c8d6d35d560b 100644 --- a/src/Umbraco.Core/Dashboards/MediaDashboard.cs +++ b/src/Umbraco.Core/Dashboards/MediaDashboard.cs @@ -1,17 +1,15 @@ -using System; -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Dashboards +namespace Umbraco.Cms.Core.Dashboards; + +[Weight(10)] +public class MediaDashboard : IDashboard { - [Weight(10)] - public class MediaDashboard : IDashboard - { - public string Alias => "mediaFolderBrowser"; + public string Alias => "mediaFolderBrowser"; - public string[] Sections => new [] { "media" }; + public string[] Sections => new[] {"media"}; - public string View => "views/dashboard/media/mediafolderbrowser.html"; + public string View => "views/dashboard/media/mediafolderbrowser.html"; - public IAccessRule[] AccessRules => Array.Empty(); - } + public IAccessRule[] AccessRules => Array.Empty(); } diff --git a/src/Umbraco.Core/Dashboards/MembersDashboard.cs b/src/Umbraco.Core/Dashboards/MembersDashboard.cs index 3023d63b8a87..4317be78a0af 100644 --- a/src/Umbraco.Core/Dashboards/MembersDashboard.cs +++ b/src/Umbraco.Core/Dashboards/MembersDashboard.cs @@ -1,17 +1,15 @@ -using System; -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Dashboards +namespace Umbraco.Cms.Core.Dashboards; + +[Weight(10)] +public class MembersDashboard : IDashboard { - [Weight(10)] - public class MembersDashboard : IDashboard - { - public string Alias => "memberIntro"; + public string Alias => "memberIntro"; - public string[] Sections => new [] { "member" }; + public string[] Sections => new[] {"member"}; - public string View => "views/dashboard/members/membersdashboardvideos.html"; + public string View => "views/dashboard/members/membersdashboardvideos.html"; - public IAccessRule[] AccessRules => Array.Empty(); - } + public IAccessRule[] AccessRules => Array.Empty(); } diff --git a/src/Umbraco.Core/Dashboards/ModelsBuilderDashboard.cs b/src/Umbraco.Core/Dashboards/ModelsBuilderDashboard.cs index 9ba5c9dd0c20..e37833c6b93f 100644 --- a/src/Umbraco.Core/Dashboards/ModelsBuilderDashboard.cs +++ b/src/Umbraco.Core/Dashboards/ModelsBuilderDashboard.cs @@ -1,17 +1,15 @@ -using System; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Dashboards +namespace Umbraco.Cms.Core.Dashboards; + +[Weight(40)] +public class ModelsBuilderDashboard : IDashboard { - [Weight(40)] - public class ModelsBuilderDashboard : IDashboard - { - public string Alias => "settingsModelsBuilder"; + public string Alias => "settingsModelsBuilder"; - public string[] Sections => new [] { "settings" }; + public string[] Sections => new[] {"settings"}; - public string View => "views/dashboard/settings/modelsbuildermanagement.html"; + public string View => "views/dashboard/settings/modelsbuildermanagement.html"; - public IAccessRule[] AccessRules => Array.Empty(); - } + public IAccessRule[] AccessRules => Array.Empty(); } diff --git a/src/Umbraco.Core/Dashboards/ProfilerDashboard.cs b/src/Umbraco.Core/Dashboards/ProfilerDashboard.cs index 7a3829209f4d..a2a6c97e7de7 100644 --- a/src/Umbraco.Core/Dashboards/ProfilerDashboard.cs +++ b/src/Umbraco.Core/Dashboards/ProfilerDashboard.cs @@ -1,17 +1,15 @@ -using System; -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Dashboards +namespace Umbraco.Cms.Core.Dashboards; + +[Weight(60)] +public class ProfilerDashboard : IDashboard { - [Weight(60)] - public class ProfilerDashboard : IDashboard - { - public string Alias => "settingsProfiler"; + public string Alias => "settingsProfiler"; - public string[] Sections => new [] { "settings" }; + public string[] Sections => new[] {"settings"}; - public string View => "views/dashboard/settings/profiler.html"; + public string View => "views/dashboard/settings/profiler.html"; - public IAccessRule[] AccessRules => Array.Empty(); - } + public IAccessRule[] AccessRules => Array.Empty(); } diff --git a/src/Umbraco.Core/Dashboards/PublishedStatusDashboard.cs b/src/Umbraco.Core/Dashboards/PublishedStatusDashboard.cs index 5cae4594f7b6..3fcf4c3e5b79 100644 --- a/src/Umbraco.Core/Dashboards/PublishedStatusDashboard.cs +++ b/src/Umbraco.Core/Dashboards/PublishedStatusDashboard.cs @@ -1,19 +1,15 @@ -using System; -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Dashboards -{ - [Weight(30)] - public class PublishedStatusDashboard : IDashboard - { - public string Alias => "settingsPublishedStatus"; - - public string[] Sections => new [] { "settings" }; +namespace Umbraco.Cms.Core.Dashboards; - public string View => "views/dashboard/settings/publishedstatus.html"; +[Weight(30)] +public class PublishedStatusDashboard : IDashboard +{ + public string Alias => "settingsPublishedStatus"; - public IAccessRule[] AccessRules => Array.Empty(); - } + public string[] Sections => new[] {"settings"}; + public string View => "views/dashboard/settings/publishedstatus.html"; + public IAccessRule[] AccessRules => Array.Empty(); } diff --git a/src/Umbraco.Core/Dashboards/RedirectUrlDashboard.cs b/src/Umbraco.Core/Dashboards/RedirectUrlDashboard.cs index 15eb8836973f..ad88b060d009 100644 --- a/src/Umbraco.Core/Dashboards/RedirectUrlDashboard.cs +++ b/src/Umbraco.Core/Dashboards/RedirectUrlDashboard.cs @@ -1,17 +1,15 @@ -using System; -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Dashboards +namespace Umbraco.Cms.Core.Dashboards; + +[Weight(20)] +public class RedirectUrlDashboard : IDashboard { - [Weight(20)] - public class RedirectUrlDashboard : IDashboard - { - public string Alias => "contentRedirectManager"; + public string Alias => "contentRedirectManager"; - public string[] Sections => new [] { "content" }; + public string[] Sections => new[] {"content"}; - public string View => "views/dashboard/content/redirecturls.html"; + public string View => "views/dashboard/content/redirecturls.html"; - public IAccessRule[] AccessRules => Array.Empty(); - } + public IAccessRule[] AccessRules => Array.Empty(); } diff --git a/src/Umbraco.Core/Dashboards/SettingsDashboards.cs b/src/Umbraco.Core/Dashboards/SettingsDashboards.cs index e5f37fd5a316..04929b041826 100644 --- a/src/Umbraco.Core/Dashboards/SettingsDashboards.cs +++ b/src/Umbraco.Core/Dashboards/SettingsDashboards.cs @@ -1,17 +1,15 @@ -using System; -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Dashboards +namespace Umbraco.Cms.Core.Dashboards; + +[Weight(10)] +public class SettingsDashboard : IDashboard { - [Weight(10)] - public class SettingsDashboard : IDashboard - { - public string Alias => "settingsWelcome"; + public string Alias => "settingsWelcome"; - public string[] Sections => new [] { "settings" }; + public string[] Sections => new[] {"settings"}; - public string View => "views/dashboard/settings/settingsdashboardintro.html"; + public string View => "views/dashboard/settings/settingsdashboardintro.html"; - public IAccessRule[] AccessRules => Array.Empty(); - } + public IAccessRule[] AccessRules => Array.Empty(); } diff --git a/src/Umbraco.Core/DefaultEventMessagesFactory.cs b/src/Umbraco.Core/DefaultEventMessagesFactory.cs index 544299b03a16..7ede3f6a7543 100644 --- a/src/Umbraco.Core/DefaultEventMessagesFactory.cs +++ b/src/Umbraco.Core/DefaultEventMessagesFactory.cs @@ -1,29 +1,31 @@ -using System; -using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public class DefaultEventMessagesFactory : IEventMessagesFactory { - public class DefaultEventMessagesFactory : IEventMessagesFactory - { - private readonly IEventMessagesAccessor _eventMessagesAccessor; + private readonly IEventMessagesAccessor _eventMessagesAccessor; - public DefaultEventMessagesFactory(IEventMessagesAccessor eventMessagesAccessor) + public DefaultEventMessagesFactory(IEventMessagesAccessor eventMessagesAccessor) + { + if (eventMessagesAccessor == null) { - if (eventMessagesAccessor == null) throw new ArgumentNullException(nameof(eventMessagesAccessor)); - _eventMessagesAccessor = eventMessagesAccessor; + throw new ArgumentNullException(nameof(eventMessagesAccessor)); } - public EventMessages Get() - { - var eventMessages = _eventMessagesAccessor.EventMessages; - if (eventMessages == null) - _eventMessagesAccessor.EventMessages = eventMessages = new EventMessages(); - return eventMessages; - } + _eventMessagesAccessor = eventMessagesAccessor; + } - public EventMessages? GetOrDefault() + public EventMessages Get() + { + EventMessages eventMessages = _eventMessagesAccessor.EventMessages; + if (eventMessages == null) { - return _eventMessagesAccessor.EventMessages; + _eventMessagesAccessor.EventMessages = eventMessages = new EventMessages(); } + + return eventMessages; } + + public EventMessages? GetOrDefault() => _eventMessagesAccessor.EventMessages; } diff --git a/src/Umbraco.Core/DelegateEqualityComparer.cs b/src/Umbraco.Core/DelegateEqualityComparer.cs index 64d715c838a5..7ad004a2e685 100644 --- a/src/Umbraco.Core/DelegateEqualityComparer.cs +++ b/src/Umbraco.Core/DelegateEqualityComparer.cs @@ -1,60 +1,54 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core; -namespace Umbraco.Cms.Core +/// +/// A custom equality comparer that excepts a delegate to do the comparison operation +/// +/// +public class DelegateEqualityComparer : IEqualityComparer { - /// - /// A custom equality comparer that excepts a delegate to do the comparison operation - /// - /// - public class DelegateEqualityComparer : IEqualityComparer - { - private readonly Func _equals; - private readonly Func _getHashcode; + private readonly Func _equals; + private readonly Func _getHashcode; - #region Implementation of IEqualityComparer + #region Implementation of IEqualityComparer - public DelegateEqualityComparer(Func equals, Func getHashcode) - { - _getHashcode = getHashcode; - _equals = equals; - } + public DelegateEqualityComparer(Func equals, Func getHashcode) + { + _getHashcode = getHashcode; + _equals = equals; + } - public static DelegateEqualityComparer CompareMember(Func memberExpression) where TMember : IEquatable - { - return new DelegateEqualityComparer( - (x, y) => memberExpression.Invoke(x).Equals((TMember)memberExpression.Invoke(y)), - x => - { - var invoked = memberExpression.Invoke(x); - return !ReferenceEquals(invoked, default(TMember)) ? invoked.GetHashCode() : 0; - }); - } + public static DelegateEqualityComparer CompareMember(Func memberExpression) + where TMember : IEquatable => + new DelegateEqualityComparer( + (x, y) => memberExpression.Invoke(x).Equals(memberExpression.Invoke(y)), + x => + { + TMember invoked = memberExpression.Invoke(x); + return !ReferenceEquals(invoked, default(TMember)) ? invoked.GetHashCode() : 0; + }); - /// - /// Determines whether the specified objects are equal. - /// - /// - /// true if the specified objects are equal; otherwise, false. - /// - /// The first object of type to compare.The second object of type to compare. - public bool Equals(T? x, T? y) - { - return _equals.Invoke(x, y); - } + /// + /// Determines whether the specified objects are equal. + /// + /// + /// true if the specified objects are equal; otherwise, false. + /// + /// The first object of type to compare. + /// The second object of type to compare. + public bool Equals(T? x, T? y) => _equals.Invoke(x, y); - /// - /// Returns a hash code for the specified object. - /// - /// - /// A hash code for the specified object. - /// - /// The for which a hash code is to be returned.The type of is a reference type and is null. - public int GetHashCode(T obj) - { - return _getHashcode.Invoke(obj); - } + /// + /// Returns a hash code for the specified object. + /// + /// + /// A hash code for the specified object. + /// + /// The for which a hash code is to be returned. + /// + /// The type of is a reference type and + /// is null. + /// + public int GetHashCode(T obj) => _getHashcode.Invoke(obj); - #endregion - } + #endregion } diff --git a/src/Umbraco.Core/DependencyInjection/IScopedServiceProvider.cs b/src/Umbraco.Core/DependencyInjection/IScopedServiceProvider.cs index d1fabe26dbbc..939315cd86e4 100644 --- a/src/Umbraco.Core/DependencyInjection/IScopedServiceProvider.cs +++ b/src/Umbraco.Core/DependencyInjection/IScopedServiceProvider.cs @@ -1,19 +1,16 @@ -using System; +namespace Umbraco.Cms.Core.DependencyInjection; -namespace Umbraco.Cms.Core.DependencyInjection +/// +/// Provides access to a request scoped service provider when available for cases where +/// IHttpContextAccessor is not available. e.g. No reference to AspNetCore.Http in core. +/// +public interface IScopedServiceProvider { /// - /// Provides access to a request scoped service provider when available for cases where - /// IHttpContextAccessor is not available. e.g. No reference to AspNetCore.Http in core. + /// Gets a request scoped service provider when available. /// - public interface IScopedServiceProvider - { - /// - /// Gets a request scoped service provider when available. - /// - /// - /// Can be null. - /// - IServiceProvider? ServiceProvider { get; } - } + /// + /// Can be null. + /// + IServiceProvider? ServiceProvider { get; } } diff --git a/src/Umbraco.Core/DependencyInjection/IUmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/IUmbracoBuilder.cs index 521f55dbef38..dc040a766442 100644 --- a/src/Umbraco.Core/DependencyInjection/IUmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/IUmbracoBuilder.cs @@ -1,4 +1,3 @@ -using System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -7,33 +6,32 @@ using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Logging; -namespace Umbraco.Cms.Core.DependencyInjection +namespace Umbraco.Cms.Core.DependencyInjection; + +public interface IUmbracoBuilder { - public interface IUmbracoBuilder - { - IServiceCollection Services { get; } - IConfiguration Config { get; } - TypeLoader TypeLoader { get; } + IServiceCollection Services { get; } + IConfiguration Config { get; } + TypeLoader TypeLoader { get; } - /// - /// A Logger factory created specifically for the . This is NOT the same - /// instance that will be resolved from DI. Use only if required during configuration. - /// - ILoggerFactory BuilderLoggerFactory { get; } + /// + /// A Logger factory created specifically for the . This is NOT the same + /// instance that will be resolved from DI. Use only if required during configuration. + /// + ILoggerFactory BuilderLoggerFactory { get; } - /// - /// A hosting environment created specifically for the . This is NOT the same - /// instance that will be resolved from DI. Use only if required during configuration. - /// - /// - /// This may be null. - /// - [Obsolete("This property will be removed in a future version, please find an alternative approach.")] - IHostingEnvironment? BuilderHostingEnvironment { get; } + /// + /// A hosting environment created specifically for the . This is NOT the same + /// instance that will be resolved from DI. Use only if required during configuration. + /// + /// + /// This may be null. + /// + [Obsolete("This property will be removed in a future version, please find an alternative approach.")] + IHostingEnvironment? BuilderHostingEnvironment { get; } - IProfiler Profiler { get; } - AppCaches AppCaches { get; } - TBuilder? WithCollectionBuilder() where TBuilder : ICollectionBuilder; - void Build(); - } + IProfiler Profiler { get; } + AppCaches AppCaches { get; } + TBuilder? WithCollectionBuilder() where TBuilder : ICollectionBuilder; + void Build(); } diff --git a/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs b/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs index 6c806ce0db93..28d9c02d6bbb 100644 --- a/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Umbraco.Core/DependencyInjection/ServiceCollectionExtensions.cs @@ -1,113 +1,116 @@ -using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class ServiceCollectionExtensions { - public static class ServiceCollectionExtensions - { - /// - /// Adds a service of type with an implementation type of to the specified . - /// - /// - /// Removes all previous registrations for the type . - /// - public static void AddUnique( - this IServiceCollection services) - where TService : class - where TImplementing : class, TService - { - AddUnique(services, ServiceLifetime.Singleton); - } + /// + /// Adds a service of type with an implementation type of + /// to the specified . + /// + /// + /// Removes all previous registrations for the type . + /// + public static void AddUnique( + this IServiceCollection services) + where TService : class + where TImplementing : class, TService => + AddUnique(services, ServiceLifetime.Singleton); - /// - /// Adds a service of type with an implementation type of to the specified . - /// - /// - /// Removes all previous registrations for the type . - /// - public static void AddUnique( - this IServiceCollection services, - ServiceLifetime lifetime) - where TService : class - where TImplementing : class, TService - { - services.RemoveAll(); - services.Add(ServiceDescriptor.Describe(typeof(TService), typeof(TImplementing), lifetime)); - } + /// + /// Adds a service of type with an implementation type of + /// to the specified . + /// + /// + /// Removes all previous registrations for the type . + /// + public static void AddUnique( + this IServiceCollection services, + ServiceLifetime lifetime) + where TService : class + where TImplementing : class, TService + { + services.RemoveAll(); + services.Add(ServiceDescriptor.Describe(typeof(TService), typeof(TImplementing), lifetime)); + } - /// - /// Adds services of types & with a shared implementation type of to the specified . - /// - /// - /// Removes all previous registrations for the types & . - /// - public static void AddMultipleUnique( - this IServiceCollection services, - ServiceLifetime lifetime = ServiceLifetime.Singleton) - where TService1 : class - where TService2 : class - where TImplementing : class, TService1, TService2 - { - services.AddUnique(lifetime); - services.AddUnique(factory => (TImplementing)factory.GetRequiredService(), lifetime); - } + /// + /// Adds services of types & with a shared + /// implementation type of to the specified . + /// + /// + /// Removes all previous registrations for the types & + /// . + /// + public static void AddMultipleUnique( + this IServiceCollection services, + ServiceLifetime lifetime = ServiceLifetime.Singleton) + where TService1 : class + where TService2 : class + where TImplementing : class, TService1, TService2 + { + services.AddUnique(lifetime); + services.AddUnique(factory => (TImplementing)factory.GetRequiredService(), lifetime); + } - // TODO(V11): Remove this function. - [Obsolete("This method is functionally equivalent to AddSingleton() please use that instead.")] - public static void AddUnique(this IServiceCollection services) - where TImplementing : class - { - services.RemoveAll(); - services.AddSingleton(); - } + // TODO(V11): Remove this function. + [Obsolete("This method is functionally equivalent to AddSingleton() please use that instead.")] + public static void AddUnique(this IServiceCollection services) + where TImplementing : class + { + services.RemoveAll(); + services.AddSingleton(); + } - /// - /// Adds a service of type with an implementation factory method to the specified . - /// - /// - /// Removes all previous registrations for the type . - /// - public static void AddUnique( - this IServiceCollection services, - Func factory, - ServiceLifetime lifetime = ServiceLifetime.Singleton) - where TService : class - { - services.RemoveAll(); - services.Add(ServiceDescriptor.Describe(typeof(TService), factory, lifetime)); - } + /// + /// Adds a service of type with an implementation factory method to the specified + /// . + /// + /// + /// Removes all previous registrations for the type . + /// + public static void AddUnique( + this IServiceCollection services, + Func factory, + ServiceLifetime lifetime = ServiceLifetime.Singleton) + where TService : class + { + services.RemoveAll(); + services.Add(ServiceDescriptor.Describe(typeof(TService), factory, lifetime)); + } - /// - /// Adds a singleton service of the type specified by to the specified . - /// - /// - /// Removes all previous registrations for the type specified by . - /// - public static void AddUnique(this IServiceCollection services, Type serviceType, object instance) - { - services.RemoveAll(serviceType); - services.AddSingleton(serviceType, instance); - } + /// + /// Adds a singleton service of the type specified by to the specified + /// . + /// + /// + /// Removes all previous registrations for the type specified by . + /// + public static void AddUnique(this IServiceCollection services, Type serviceType, object instance) + { + services.RemoveAll(serviceType); + services.AddSingleton(serviceType, instance); + } - /// - /// Adds a singleton service of type to the specified . - /// - /// - /// Removes all previous registrations for the type type . - /// - public static void AddUnique(this IServiceCollection services, TService instance) - where TService : class - { - services.RemoveAll(); - services.AddSingleton(instance); - } + /// + /// Adds a singleton service of type to the specified + /// . + /// + /// + /// Removes all previous registrations for the type type . + /// + public static void AddUnique(this IServiceCollection services, TService instance) + where TService : class + { + services.RemoveAll(); + services.AddSingleton(instance); + } - internal static IServiceCollection AddLazySupport(this IServiceCollection services) - { - services.Replace(ServiceDescriptor.Transient(typeof(Lazy<>), typeof(LazyResolve<>))); - return services; - } + internal static IServiceCollection AddLazySupport(this IServiceCollection services) + { + services.Replace(ServiceDescriptor.Transient(typeof(Lazy<>), typeof(LazyResolve<>))); + return services; } } diff --git a/src/Umbraco.Core/DependencyInjection/ServiceProviderExtensions.cs b/src/Umbraco.Core/DependencyInjection/ServiceProviderExtensions.cs index 9bcc0cf7f81a..9c2202e2aaa6 100644 --- a/src/Umbraco.Core/DependencyInjection/ServiceProviderExtensions.cs +++ b/src/Umbraco.Core/DependencyInjection/ServiceProviderExtensions.cs @@ -1,57 +1,55 @@ -using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Linq; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Provides extension methods to the class. +/// +public static class ServiceProviderExtensions { /// - /// Provides extension methods to the class. + /// Creates an instance with arguments. /// - public static class ServiceProviderExtensions - { - /// - /// Creates an instance with arguments. - /// - /// The type of the instance. - /// The factory. - /// Arguments. - /// An instance of the specified type. - /// - /// Throws an exception if the factory failed to get an instance of the specified type. - /// The arguments are used as dependencies by the factory. - /// - public static T CreateInstance(this IServiceProvider serviceProvider, params object[] args) - where T : class - => (T)serviceProvider.CreateInstance(typeof(T), args); + /// The type of the instance. + /// The factory. + /// Arguments. + /// An instance of the specified type. + /// + /// Throws an exception if the factory failed to get an instance of the specified type. + /// The arguments are used as dependencies by the factory. + /// + public static T CreateInstance(this IServiceProvider serviceProvider, params object[] args) + where T : class + => (T)serviceProvider.CreateInstance(typeof(T), args); - /// - /// Creates an instance of a service, with arguments. - /// - /// The - /// The type of the instance. - /// Named arguments. - /// An instance of the specified type. - /// - /// The instance type does not need to be registered into the factory. - /// The arguments are used as dependencies by the factory. Other dependencies - /// are retrieved from the factory. - /// - public static object CreateInstance(this IServiceProvider serviceProvider, Type type, params object[] args) - => ActivatorUtilities.CreateInstance(serviceProvider, type, args); + /// + /// Creates an instance of a service, with arguments. + /// + /// The + /// The type of the instance. + /// Named arguments. + /// An instance of the specified type. + /// + /// The instance type does not need to be registered into the factory. + /// + /// The arguments are used as dependencies by the factory. Other dependencies + /// are retrieved from the factory. + /// + /// + public static object CreateInstance(this IServiceProvider serviceProvider, Type type, params object[] args) + => ActivatorUtilities.CreateInstance(serviceProvider, type, args); - [EditorBrowsable(EditorBrowsableState.Never)] - public static PublishedModelFactory CreateDefaultPublishedModelFactory(this IServiceProvider factory) - { - TypeLoader typeLoader = factory.GetRequiredService(); - IPublishedValueFallback publishedValueFallback = factory.GetRequiredService(); - IEnumerable types = typeLoader - .GetTypes() // element models - .Concat(typeLoader.GetTypes()); // content models - return new PublishedModelFactory(types, publishedValueFallback); - } + [EditorBrowsable(EditorBrowsableState.Never)] + public static PublishedModelFactory CreateDefaultPublishedModelFactory(this IServiceProvider factory) + { + TypeLoader typeLoader = factory.GetRequiredService(); + IPublishedValueFallback publishedValueFallback = factory.GetRequiredService(); + IEnumerable types = typeLoader + .GetTypes() // element models + .Concat(typeLoader.GetTypes()); // content models + return new PublishedModelFactory(types, publishedValueFallback); } } diff --git a/src/Umbraco.Core/DependencyInjection/StaticServiceProvider.cs b/src/Umbraco.Core/DependencyInjection/StaticServiceProvider.cs index fdc4e3f6224b..7489f94fd29c 100644 --- a/src/Umbraco.Core/DependencyInjection/StaticServiceProvider.cs +++ b/src/Umbraco.Core/DependencyInjection/StaticServiceProvider.cs @@ -1,25 +1,25 @@ -using System; -using System.ComponentModel; +using System.ComponentModel; -namespace Umbraco.Cms.Web.Common.DependencyInjection +namespace Umbraco.Cms.Web.Common.DependencyInjection; + +/// +/// Service locator for internal (umbraco cms) only purposes. Should only be used if no other ways exist. +/// +/// +/// It is created with only two goals in mind +/// 1) Continue to have the same extension methods on IPublishedContent and IPublishedElement as in V8. To make +/// migration easier. +/// 2) To have a tool to avoid breaking changes in minor and patch versions. All methods using this should in theory be +/// obsolete. +/// Keep in mind, every time this is used, the code becomes basically untestable. +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public static class StaticServiceProvider { /// - /// Service locator for internal (umbraco cms) only purposes. Should only be used if no other ways exist. + /// The service locator. /// - /// - /// It is created with only two goals in mind - /// 1) Continue to have the same extension methods on IPublishedContent and IPublishedElement as in V8. To make migration easier. - /// 2) To have a tool to avoid breaking changes in minor and patch versions. All methods using this should in theory be obsolete. - /// - /// Keep in mind, every time this is used, the code becomes basically untestable. - /// [EditorBrowsable(EditorBrowsableState.Never)] - public static class StaticServiceProvider - { - /// - /// The service locator. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public static IServiceProvider Instance { get; set; } = null!; // This is set doing startup and will always exists after that - } + public static IServiceProvider Instance { get; set; } = + null!; // This is set doing startup and will always exists after that } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.CollectionBuilders.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.CollectionBuilders.cs index cb4097437538..59f3cfcb9b8b 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.CollectionBuilders.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.CollectionBuilders.cs @@ -1,4 +1,3 @@ -using System; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Dashboards; using Umbraco.Cms.Core.Media; @@ -6,111 +5,110 @@ using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Sections; -namespace Umbraco.Cms.Core.DependencyInjection +namespace Umbraco.Cms.Core.DependencyInjection; + +/// +/// Contains extensions methods for used for registering content apps. +/// +public static partial class UmbracoBuilderExtensions { /// - /// Contains extensions methods for used for registering content apps. + /// Register a component. /// - public static partial class UmbracoBuilderExtensions + /// The type of the component. + /// The builder. + public static IUmbracoBuilder AddComponent(this IUmbracoBuilder builder) + where T : class, IComponent { - /// - /// Register a component. - /// - /// The type of the component. - /// The builder. - public static IUmbracoBuilder AddComponent(this IUmbracoBuilder builder) - where T : class, IComponent - { - builder.Components()?.Append(); - return builder; - } + builder.Components()?.Append(); + return builder; + } - /// - /// Register a content app. - /// - /// The type of the content app. - /// The builder. - public static IUmbracoBuilder AddContentApp(this IUmbracoBuilder builder) - where T : class, IContentAppFactory - { - builder.ContentApps()?.Append(); - return builder; - } + /// + /// Register a content app. + /// + /// The type of the content app. + /// The builder. + public static IUmbracoBuilder AddContentApp(this IUmbracoBuilder builder) + where T : class, IContentAppFactory + { + builder.ContentApps()?.Append(); + return builder; + } - /// - /// Register a content finder. - /// - /// The type of the content finder. - /// The builder. - public static IUmbracoBuilder AddContentFinder(this IUmbracoBuilder builder) - where T : class, IContentFinder - { - builder.ContentFinders()?.Append(); - return builder; - } + /// + /// Register a content finder. + /// + /// The type of the content finder. + /// The builder. + public static IUmbracoBuilder AddContentFinder(this IUmbracoBuilder builder) + where T : class, IContentFinder + { + builder.ContentFinders()?.Append(); + return builder; + } - /// - /// Register a dashboard. - /// - /// The type of the dashboard. - /// The builder. - public static IUmbracoBuilder AddDashboard(this IUmbracoBuilder builder) - where T : class, IDashboard - { - builder.Dashboards()?.Add(); - return builder; - } + /// + /// Register a dashboard. + /// + /// The type of the dashboard. + /// The builder. + public static IUmbracoBuilder AddDashboard(this IUmbracoBuilder builder) + where T : class, IDashboard + { + builder.Dashboards()?.Add(); + return builder; + } - /// - /// Register a media url provider. - /// - /// The type of the media url provider. - /// The builder. - public static IUmbracoBuilder AddMediaUrlProvider(this IUmbracoBuilder builder) - where T : class, IMediaUrlProvider - { - builder.MediaUrlProviders()?.Append(); - return builder; - } + /// + /// Register a media url provider. + /// + /// The type of the media url provider. + /// The builder. + public static IUmbracoBuilder AddMediaUrlProvider(this IUmbracoBuilder builder) + where T : class, IMediaUrlProvider + { + builder.MediaUrlProviders()?.Append(); + return builder; + } - /// - /// Register a embed provider. - /// - /// The type of the embed provider. - /// The builder. - public static IUmbracoBuilder AddEmbedProvider(this IUmbracoBuilder builder) - where T : class, IEmbedProvider - { - builder.EmbedProviders()?.Append(); - return builder; - } + /// + /// Register a embed provider. + /// + /// The type of the embed provider. + /// The builder. + public static IUmbracoBuilder AddEmbedProvider(this IUmbracoBuilder builder) + where T : class, IEmbedProvider + { + builder.EmbedProviders()?.Append(); + return builder; + } - [Obsolete("Use AddEmbedProvider instead. This will be removed in Umbraco 10")] - public static IUmbracoBuilder AddOEmbedProvider(this IUmbracoBuilder builder) - where T : class, IEmbedProvider => AddEmbedProvider(builder); + [Obsolete("Use AddEmbedProvider instead. This will be removed in Umbraco 10")] + public static IUmbracoBuilder AddOEmbedProvider(this IUmbracoBuilder builder) + where T : class, IEmbedProvider => AddEmbedProvider(builder); - /// - /// Register a section. - /// - /// The type of the section. - /// The builder. - public static IUmbracoBuilder AddSection(this IUmbracoBuilder builder) - where T : class, ISection - { - builder.Sections()?.Append(); - return builder; - } + /// + /// Register a section. + /// + /// The type of the section. + /// The builder. + public static IUmbracoBuilder AddSection(this IUmbracoBuilder builder) + where T : class, ISection + { + builder.Sections()?.Append(); + return builder; + } - /// - /// Register a url provider. - /// - /// The type of the url provider. - /// The Builder. - public static IUmbracoBuilder AddUrlProvider(this IUmbracoBuilder builder) - where T : class, IUrlProvider - { - builder.UrlProviders()?.Append(); - return builder; - } + /// + /// Register a url provider. + /// + /// The type of the url provider. + /// The Builder. + public static IUmbracoBuilder AddUrlProvider(this IUmbracoBuilder builder) + where T : class, IUrlProvider + { + builder.UrlProviders()?.Append(); + return builder; } } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs index b1913037a3e8..4a051e98bc29 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs @@ -1,4 +1,3 @@ -using System; using Umbraco.Cms.Core.Actions; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Composing; @@ -10,7 +9,6 @@ using Umbraco.Cms.Core.Manifest; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Media.EmbedProviders; -using Umbraco.Cms.Core.Packaging; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.PropertyEditors.Validators; using Umbraco.Cms.Core.Routing; @@ -21,275 +19,276 @@ using Umbraco.Cms.Core.WebAssets; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.DependencyInjection +namespace Umbraco.Cms.Core.DependencyInjection; + +/// +/// Extension methods for +/// +public static partial class UmbracoBuilderExtensions { /// - /// Extension methods for + /// Adds all core collection builders /// - public static partial class UmbracoBuilderExtensions + internal static void AddAllCoreCollectionBuilders(this IUmbracoBuilder builder) { - /// - /// Adds all core collection builders - /// - internal static void AddAllCoreCollectionBuilders(this IUmbracoBuilder builder) - { - builder.CacheRefreshers()?.Add(() => builder.TypeLoader.GetCacheRefreshers()); - builder.DataEditors()?.Add(() => builder.TypeLoader.GetDataEditors()); - builder.Actions()?.Add(() => builder .TypeLoader.GetActions()); + builder.CacheRefreshers()?.Add(() => builder.TypeLoader.GetCacheRefreshers()); + builder.DataEditors()?.Add(() => builder.TypeLoader.GetDataEditors()); + builder.Actions()?.Add(() => builder.TypeLoader.GetActions()); - // register known content apps - builder.ContentApps()? - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append(); + // register known content apps + builder.ContentApps()? + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); - // all built-in finders in the correct order, - // devs can then modify this list on application startup - builder.ContentFinders()? - .Append() - .Append() - .Append() - /*.Append() // disabled, this is an odd finder */ - .Append() - .Append(); - builder.EditorValidators()?.Add(() => builder.TypeLoader.GetTypes()); - builder.HealthChecks()?.Add(() => builder.TypeLoader.GetTypes()); - builder.HealthCheckNotificationMethods()?.Add(() => builder.TypeLoader.GetTypes()); - builder.TourFilters(); - builder.UrlProviders()? - .Append() - .Append(); - builder.MediaUrlProviders()? - .Append(); - // register back office sections in the order we want them rendered - builder.Sections()? - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append(); - builder.Components(); - // register core CMS dashboards and 3rd party types - will be ordered by weight attribute & merged with package.manifest dashboards - builder.Dashboards()? - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add() - .Add(builder.TypeLoader.GetTypes()); - builder.DataValueReferenceFactories(); - builder.PropertyValueConverters()?.Append(builder.TypeLoader.GetTypes()); - builder.UrlSegmentProviders()?.Append(); - builder.ManifestValueValidators()? - .Add() - .Add() - .Add() - .Add() - .Add() - .Add(); - builder.ManifestFilters(); - builder.MediaUrlGenerators(); - // register OEmbed providers - no type scanning - all explicit opt-in of adding types, IEmbedProvider is not IDiscoverable - builder.EmbedProviders()? - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append() - .Append(); - builder.SearchableTrees()?.Add(() => builder.TypeLoader.GetTypes()); - builder.BackOfficeAssets(); - } + // all built-in finders in the correct order, + // devs can then modify this list on application startup + builder.ContentFinders()? + .Append() + .Append() + .Append() + /*.Append() // disabled, this is an odd finder */ + .Append() + .Append(); + builder.EditorValidators()?.Add(() => builder.TypeLoader.GetTypes()); + builder.HealthChecks()?.Add(() => builder.TypeLoader.GetTypes()); + builder.HealthCheckNotificationMethods() + ?.Add(() => builder.TypeLoader.GetTypes()); + builder.TourFilters(); + builder.UrlProviders()? + .Append() + .Append(); + builder.MediaUrlProviders()? + .Append(); + // register back office sections in the order we want them rendered + builder.Sections()? + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); + builder.Components(); + // register core CMS dashboards and 3rd party types - will be ordered by weight attribute & merged with package.manifest dashboards + builder.Dashboards()? + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add() + .Add(builder.TypeLoader.GetTypes()); + builder.DataValueReferenceFactories(); + builder.PropertyValueConverters()?.Append(builder.TypeLoader.GetTypes()); + builder.UrlSegmentProviders()?.Append(); + builder.ManifestValueValidators()? + .Add() + .Add() + .Add() + .Add() + .Add() + .Add(); + builder.ManifestFilters(); + builder.MediaUrlGenerators(); + // register OEmbed providers - no type scanning - all explicit opt-in of adding types, IEmbedProvider is not IDiscoverable + builder.EmbedProviders()? + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append() + .Append(); + builder.SearchableTrees()?.Add(() => builder.TypeLoader.GetTypes()); + builder.BackOfficeAssets(); + } - /// - /// Gets the actions collection builder. - /// - /// The builder. - public static ActionCollectionBuilder? Actions(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the actions collection builder. + /// + /// The builder. + public static ActionCollectionBuilder? Actions(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the content apps collection builder. - /// - /// The builder. - public static ContentAppFactoryCollectionBuilder? ContentApps(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the content apps collection builder. + /// + /// The builder. + public static ContentAppFactoryCollectionBuilder? ContentApps(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the content finders collection builder. - /// - /// The builder. - public static ContentFinderCollectionBuilder? ContentFinders(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the content finders collection builder. + /// + /// The builder. + public static ContentFinderCollectionBuilder? ContentFinders(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the editor validators collection builder. - /// - /// The builder. - public static EditorValidatorCollectionBuilder? EditorValidators(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the editor validators collection builder. + /// + /// The builder. + public static EditorValidatorCollectionBuilder? EditorValidators(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the health checks collection builder. - /// - /// The builder. - public static HealthCheckCollectionBuilder? HealthChecks(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the health checks collection builder. + /// + /// The builder. + public static HealthCheckCollectionBuilder? HealthChecks(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - public static HealthCheckNotificationMethodCollectionBuilder? HealthCheckNotificationMethods(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + public static HealthCheckNotificationMethodCollectionBuilder? HealthCheckNotificationMethods( + this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the TourFilters collection builder. - /// - public static TourFilterCollectionBuilder? TourFilters(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the TourFilters collection builder. + /// + public static TourFilterCollectionBuilder? TourFilters(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the URL providers collection builder. - /// - /// The builder. - public static UrlProviderCollectionBuilder? UrlProviders(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the URL providers collection builder. + /// + /// The builder. + public static UrlProviderCollectionBuilder? UrlProviders(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the media url providers collection builder. - /// - /// The builder. - public static MediaUrlProviderCollectionBuilder? MediaUrlProviders(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the media url providers collection builder. + /// + /// The builder. + public static MediaUrlProviderCollectionBuilder? MediaUrlProviders(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the backoffice sections/applications collection builder. - /// - /// The builder. - public static SectionCollectionBuilder? Sections(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the backoffice sections/applications collection builder. + /// + /// The builder. + public static SectionCollectionBuilder? Sections(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the components collection builder. - /// - public static ComponentCollectionBuilder? Components(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the components collection builder. + /// + public static ComponentCollectionBuilder? Components(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the backoffice dashboards collection builder. - /// - /// The builder. - public static DashboardCollectionBuilder? Dashboards(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the backoffice dashboards collection builder. + /// + /// The builder. + public static DashboardCollectionBuilder? Dashboards(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the cache refreshers collection builder. - /// - /// The builder. - public static CacheRefresherCollectionBuilder? CacheRefreshers(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the cache refreshers collection builder. + /// + /// The builder. + public static CacheRefresherCollectionBuilder? CacheRefreshers(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the map definitions collection builder. - /// - /// The builder. - public static MapDefinitionCollectionBuilder? MapDefinitions(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the map definitions collection builder. + /// + /// The builder. + public static MapDefinitionCollectionBuilder? MapDefinitions(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the data editor collection builder. - /// - /// The builder. - public static DataEditorCollectionBuilder? DataEditors(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the data editor collection builder. + /// + /// The builder. + public static DataEditorCollectionBuilder? DataEditors(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the data value reference factory collection builder. - /// - /// The builder. - public static DataValueReferenceFactoryCollectionBuilder? DataValueReferenceFactories(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the data value reference factory collection builder. + /// + /// The builder. + public static DataValueReferenceFactoryCollectionBuilder? DataValueReferenceFactories(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the property value converters collection builder. - /// - /// The builder. - public static PropertyValueConverterCollectionBuilder? PropertyValueConverters(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the property value converters collection builder. + /// + /// The builder. + public static PropertyValueConverterCollectionBuilder? PropertyValueConverters(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the url segment providers collection builder. - /// - /// The builder. - public static UrlSegmentProviderCollectionBuilder? UrlSegmentProviders(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the url segment providers collection builder. + /// + /// The builder. + public static UrlSegmentProviderCollectionBuilder? UrlSegmentProviders(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the validators collection builder. - /// - /// The builder. - internal static ManifestValueValidatorCollectionBuilder? ManifestValueValidators(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the validators collection builder. + /// + /// The builder. + internal static ManifestValueValidatorCollectionBuilder? ManifestValueValidators(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the manifest filter collection builder. - /// - /// The builder. - public static ManifestFilterCollectionBuilder? ManifestFilters(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the manifest filter collection builder. + /// + /// The builder. + public static ManifestFilterCollectionBuilder? ManifestFilters(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the content finders collection builder. - /// - /// The builder. - public static MediaUrlGeneratorCollectionBuilder? MediaUrlGenerators(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the content finders collection builder. + /// + /// The builder. + public static MediaUrlGeneratorCollectionBuilder? MediaUrlGenerators(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the backoffice OEmbed Providers collection builder. - /// - /// The builder. - [Obsolete("Use EmbedProviders() instead")] - public static EmbedProvidersCollectionBuilder? OEmbedProviders(this IUmbracoBuilder builder) - => EmbedProviders(builder); + /// + /// Gets the backoffice OEmbed Providers collection builder. + /// + /// The builder. + [Obsolete("Use EmbedProviders() instead")] + public static EmbedProvidersCollectionBuilder? OEmbedProviders(this IUmbracoBuilder builder) + => EmbedProviders(builder); - /// - /// Gets the backoffice Embed Providers collection builder. - /// - /// The builder. - public static EmbedProvidersCollectionBuilder? EmbedProviders(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the backoffice Embed Providers collection builder. + /// + /// The builder. + public static EmbedProvidersCollectionBuilder? EmbedProviders(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the back office searchable tree collection builder - /// - public static SearchableTreeCollectionBuilder? SearchableTrees(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the back office searchable tree collection builder + /// + public static SearchableTreeCollectionBuilder? SearchableTrees(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the back office custom assets collection builder - /// - public static CustomBackOfficeAssetsCollectionBuilder? BackOfficeAssets(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - } + /// + /// Gets the back office custom assets collection builder + /// + public static CustomBackOfficeAssetsCollectionBuilder? BackOfficeAssets(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Composers.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Composers.cs index 81a1bbac326f..558c18027608 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Composers.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Composers.cs @@ -1,26 +1,25 @@ -using System; -using System.Collections.Generic; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.DependencyInjection +namespace Umbraco.Cms.Core.DependencyInjection; + +/// +/// Extension methods for +/// +public static partial class UmbracoBuilderExtensions { /// - /// Extension methods for + /// Adds Umbraco composers for plugins /// - public static partial class UmbracoBuilderExtensions + public static IUmbracoBuilder AddComposers(this IUmbracoBuilder builder) { - /// - /// Adds Umbraco composers for plugins - /// - public static IUmbracoBuilder AddComposers(this IUmbracoBuilder builder) - { - IEnumerable composerTypes = builder.TypeLoader.GetTypes(); - IEnumerable enableDisable = builder.TypeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute)); + IEnumerable composerTypes = builder.TypeLoader.GetTypes(); + IEnumerable enableDisable = + builder.TypeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute)); - new ComposerGraph(builder, composerTypes, enableDisable, builder.BuilderLoggerFactory.CreateLogger()).Compose(); + new ComposerGraph(builder, composerTypes, enableDisable, + builder.BuilderLoggerFactory.CreateLogger()).Compose(); - return builder; - } + return builder; } } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs index a8a374fef237..26023c2443db 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; using System.Reflection; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration; @@ -9,105 +6,109 @@ using Umbraco.Cms.Core.Configuration.Models.Validation; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.DependencyInjection +namespace Umbraco.Cms.Core.DependencyInjection; + +/// +/// Extension methods for +/// +public static partial class UmbracoBuilderExtensions { - /// - /// Extension methods for - /// - public static partial class UmbracoBuilderExtensions + private static IUmbracoBuilder AddUmbracoOptions(this IUmbracoBuilder builder, + Action>? configure = null) + where TOptions : class { - - private static IUmbracoBuilder AddUmbracoOptions(this IUmbracoBuilder builder, Action>? configure = null) - where TOptions : class + UmbracoOptionsAttribute umbracoOptionsAttribute = + typeof(TOptions).GetCustomAttribute(); + if (umbracoOptionsAttribute is null) { - var umbracoOptionsAttribute = typeof(TOptions).GetCustomAttribute(); - if (umbracoOptionsAttribute is null) - { - throw new ArgumentException($"{typeof(TOptions)} do not have the UmbracoOptionsAttribute."); - } + throw new ArgumentException($"{typeof(TOptions)} do not have the UmbracoOptionsAttribute."); + } - var optionsBuilder = builder.Services.AddOptions() - .Bind( - builder.Config.GetSection(umbracoOptionsAttribute.ConfigurationKey), - o => o.BindNonPublicProperties = umbracoOptionsAttribute.BindNonPublicProperties - ) - .ValidateDataAnnotations(); + OptionsBuilder optionsBuilder = builder.Services.AddOptions() + .Bind( + builder.Config.GetSection(umbracoOptionsAttribute.ConfigurationKey), + o => o.BindNonPublicProperties = umbracoOptionsAttribute.BindNonPublicProperties + ) + .ValidateDataAnnotations(); - configure?.Invoke(optionsBuilder); + configure?.Invoke(optionsBuilder); - return builder; - } + return builder; + } - /// - /// Add Umbraco configuration services and options - /// - public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder) - { - // Register configuration validators. - builder.Services.AddSingleton, ContentSettingsValidator>(); - builder.Services.AddSingleton, GlobalSettingsValidator>(); - builder.Services.AddSingleton, HealthChecksSettingsValidator>(); - builder.Services.AddSingleton, RequestHandlerSettingsValidator>(); - builder.Services.AddSingleton, UnattendedSettingsValidator>(); + /// + /// Add Umbraco configuration services and options + /// + public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder) + { + // Register configuration validators. + builder.Services.AddSingleton, ContentSettingsValidator>(); + builder.Services.AddSingleton, GlobalSettingsValidator>(); + builder.Services.AddSingleton, HealthChecksSettingsValidator>(); + builder.Services.AddSingleton, RequestHandlerSettingsValidator>(); + builder.Services.AddSingleton, UnattendedSettingsValidator>(); - // Register configuration sections. - builder - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions(optionsBuilder => optionsBuilder.PostConfigure(options => + // Register configuration sections. + builder + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions(optionsBuilder => optionsBuilder.PostConfigure(options => + { + if (string.IsNullOrEmpty(options.UmbracoMediaPhysicalRootPath)) { - if (string.IsNullOrEmpty(options.UmbracoMediaPhysicalRootPath)) - { - options.UmbracoMediaPhysicalRootPath = options.UmbracoMediaPath; - } - })) - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions() - .AddUmbracoOptions(); + options.UmbracoMediaPhysicalRootPath = options.UmbracoMediaPath; + } + })) + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions() + .AddUmbracoOptions(); - builder.Services.AddSingleton, ConfigureConnectionStrings>(); + builder.Services.AddSingleton, ConfigureConnectionStrings>(); - builder.Services.Configure( - Constants.Configuration.NamedOptions.InstallDefaultData.Languages, - builder.Config.GetSection($"{Constants.Configuration.ConfigInstallDefaultData}:{Constants.Configuration.NamedOptions.InstallDefaultData.Languages}")); - builder.Services.Configure( - Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, - builder.Config.GetSection($"{Constants.Configuration.ConfigInstallDefaultData}:{Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes}")); - builder.Services.Configure( - Constants.Configuration.NamedOptions.InstallDefaultData.MediaTypes, - builder.Config.GetSection($"{Constants.Configuration.ConfigInstallDefaultData}:{Constants.Configuration.NamedOptions.InstallDefaultData.MediaTypes}")); - builder.Services.Configure( - Constants.Configuration.NamedOptions.InstallDefaultData.MemberTypes, - builder.Config.GetSection($"{Constants.Configuration.ConfigInstallDefaultData}:{Constants.Configuration.NamedOptions.InstallDefaultData.MemberTypes}")); + builder.Services.Configure( + Constants.Configuration.NamedOptions.InstallDefaultData.Languages, + builder.Config.GetSection( + $"{Constants.Configuration.ConfigInstallDefaultData}:{Constants.Configuration.NamedOptions.InstallDefaultData.Languages}")); + builder.Services.Configure( + Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes, + builder.Config.GetSection( + $"{Constants.Configuration.ConfigInstallDefaultData}:{Constants.Configuration.NamedOptions.InstallDefaultData.DataTypes}")); + builder.Services.Configure( + Constants.Configuration.NamedOptions.InstallDefaultData.MediaTypes, + builder.Config.GetSection( + $"{Constants.Configuration.ConfigInstallDefaultData}:{Constants.Configuration.NamedOptions.InstallDefaultData.MediaTypes}")); + builder.Services.Configure( + Constants.Configuration.NamedOptions.InstallDefaultData.MemberTypes, + builder.Config.GetSection( + $"{Constants.Configuration.ConfigInstallDefaultData}:{Constants.Configuration.NamedOptions.InstallDefaultData.MemberTypes}")); - builder.Services.Configure(options => options.MergeReplacements(builder.Config)); + builder.Services.Configure(options => options.MergeReplacements(builder.Config)); - return builder; - } + return builder; } } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs index 441bc836da8b..3ec78c40251f 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs @@ -5,72 +5,76 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.DependencyInjection +namespace Umbraco.Cms.Core.DependencyInjection; + +/// +/// Contains extensions methods for used for registering event handlers. +/// +public static partial class UmbracoBuilderExtensions { + /// + /// Registers a notification handler against the Umbraco service collection. + /// + /// The type of notification. + /// The type of notificiation handler. + /// The Umbraco builder. + /// The . + public static IUmbracoBuilder AddNotificationHandler( + this IUmbracoBuilder builder) + where TNotificationHandler : INotificationHandler + where TNotification : INotification + { + builder.Services.AddNotificationHandler(); + return builder; + } /// - /// Contains extensions methods for used for registering event handlers. + /// Registers a notification async handler against the Umbraco service collection. /// - public static partial class UmbracoBuilderExtensions + /// The type of notification. + /// The type of notification async handler. + /// The Umbraco builder. + /// The . + public static IUmbracoBuilder AddNotificationAsyncHandler( + this IUmbracoBuilder builder) + where TNotificationAsyncHandler : INotificationAsyncHandler + where TNotification : INotification { - /// - /// Registers a notification handler against the Umbraco service collection. - /// - /// The type of notification. - /// The type of notificiation handler. - /// The Umbraco builder. - /// The . - public static IUmbracoBuilder AddNotificationHandler(this IUmbracoBuilder builder) - where TNotificationHandler : INotificationHandler - where TNotification : INotification - { - builder.Services.AddNotificationHandler(); - return builder; - } + builder.Services.AddNotificationAsyncHandler(); + return builder; + } - /// - /// Registers a notification async handler against the Umbraco service collection. - /// - /// The type of notification. - /// The type of notification async handler. - /// The Umbraco builder. - /// The . - public static IUmbracoBuilder AddNotificationAsyncHandler(this IUmbracoBuilder builder) - where TNotificationAsyncHandler : INotificationAsyncHandler - where TNotification : INotification - { - builder.Services.AddNotificationAsyncHandler(); - return builder; - } + internal static IServiceCollection AddNotificationHandler( + this IServiceCollection services) + where TNotificationHandler : INotificationHandler + where TNotification : INotification + { + // Register the handler as transient. This ensures that anything can be injected into it. + var descriptor = new UniqueServiceDescriptor(typeof(INotificationHandler), + typeof(TNotificationHandler), ServiceLifetime.Transient); - internal static IServiceCollection AddNotificationHandler(this IServiceCollection services) - where TNotificationHandler : INotificationHandler - where TNotification : INotification + if (!services.Contains(descriptor)) { - // Register the handler as transient. This ensures that anything can be injected into it. - var descriptor = new UniqueServiceDescriptor(typeof(INotificationHandler), typeof(TNotificationHandler), ServiceLifetime.Transient); - - if (!services.Contains(descriptor)) - { - services.Add(descriptor); - } - - return services; + services.Add(descriptor); } - internal static IServiceCollection AddNotificationAsyncHandler(this IServiceCollection services) - where TNotificationAsyncHandler : INotificationAsyncHandler - where TNotification : INotification - { - // Register the handler as transient. This ensures that anything can be injected into it. - var descriptor = new ServiceDescriptor(typeof(INotificationAsyncHandler), typeof(TNotificationAsyncHandler), ServiceLifetime.Transient); + return services; + } - if (!services.Contains(descriptor)) - { - services.Add(descriptor); - } + internal static IServiceCollection AddNotificationAsyncHandler( + this IServiceCollection services) + where TNotificationAsyncHandler : INotificationAsyncHandler + where TNotification : INotification + { + // Register the handler as transient. This ensures that anything can be injected into it. + var descriptor = new ServiceDescriptor(typeof(INotificationAsyncHandler), + typeof(TNotificationAsyncHandler), ServiceLifetime.Transient); - return services; + if (!services.Contains(descriptor)) + { + services.Add(descriptor); } + + return services; } } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 4a05cb0268f0..523c85555ba2 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -1,8 +1,6 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using System.Runtime.InteropServices; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -25,7 +23,6 @@ using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Logging; using Umbraco.Cms.Core.Mail; -using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Packaging; @@ -44,288 +41,290 @@ using Umbraco.Cms.Core.Web; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.DependencyInjection +namespace Umbraco.Cms.Core.DependencyInjection; + +public class UmbracoBuilder : IUmbracoBuilder { - public class UmbracoBuilder : IUmbracoBuilder + private readonly Dictionary _builders = new(); + + /// + /// Initializes a new instance of the class primarily for testing. + /// + public UmbracoBuilder(IServiceCollection services, IConfiguration config, TypeLoader typeLoader) + : this(services, config, typeLoader, NullLoggerFactory.Instance, new NoopProfiler(), AppCaches.Disabled, null) { - private readonly Dictionary _builders = new Dictionary(); + } - public IServiceCollection Services { get; } + /// + /// Initializes a new instance of the class. + /// + public UmbracoBuilder( + IServiceCollection services, + IConfiguration config, + TypeLoader typeLoader, + ILoggerFactory loggerFactory, + IProfiler profiler, + AppCaches appCaches, + IHostingEnvironment? hostingEnvironment) + { + Services = services; + Config = config; + BuilderLoggerFactory = loggerFactory; + BuilderHostingEnvironment = hostingEnvironment; + Profiler = profiler; + AppCaches = appCaches; + TypeLoader = typeLoader; + + AddCoreServices(); + } - public IConfiguration Config { get; } + public IServiceCollection Services { get; } - public TypeLoader TypeLoader { get; } + public IConfiguration Config { get; } - /// - public ILoggerFactory BuilderLoggerFactory { get; } + public TypeLoader TypeLoader { get; } - /// - public IHostingEnvironment? BuilderHostingEnvironment { get; } + /// + public ILoggerFactory BuilderLoggerFactory { get; } - public IProfiler Profiler { get; } + /// + public IHostingEnvironment? BuilderHostingEnvironment { get; } - public AppCaches AppCaches { get; } + public IProfiler Profiler { get; } - /// - /// Initializes a new instance of the class primarily for testing. - /// - public UmbracoBuilder(IServiceCollection services, IConfiguration config, TypeLoader typeLoader) - : this(services, config, typeLoader, NullLoggerFactory.Instance, new NoopProfiler(), AppCaches.Disabled, null) - { } + public AppCaches AppCaches { get; } - /// - /// Initializes a new instance of the class. - /// - public UmbracoBuilder( - IServiceCollection services, - IConfiguration config, - TypeLoader typeLoader, - ILoggerFactory loggerFactory, - IProfiler profiler, - AppCaches appCaches, - IHostingEnvironment? hostingEnvironment) - { - Services = services; - Config = config; - BuilderLoggerFactory = loggerFactory; - BuilderHostingEnvironment = hostingEnvironment; - Profiler = profiler; - AppCaches = appCaches; - TypeLoader = typeLoader; - - AddCoreServices(); - } + /// + /// Gets a collection builder (and registers the collection). + /// + /// The type of the collection builder. + /// The collection builder. + public TBuilder? WithCollectionBuilder() + where TBuilder : ICollectionBuilder + { + Type typeOfBuilder = typeof(TBuilder); - /// - /// Gets a collection builder (and registers the collection). - /// - /// The type of the collection builder. - /// The collection builder. - public TBuilder? WithCollectionBuilder() - where TBuilder : ICollectionBuilder + if (_builders.TryGetValue(typeOfBuilder, out ICollectionBuilder? o)) { - Type typeOfBuilder = typeof(TBuilder); + return (TBuilder?)o; + } - if (_builders.TryGetValue(typeOfBuilder, out ICollectionBuilder? o)) - { - return (TBuilder?)o; - } + TBuilder? builder; - TBuilder? builder; + if (typeof(TBuilder).GetConstructor(Type.EmptyTypes) != null) + { + builder = Activator.CreateInstance(); + } + else if (typeof(TBuilder).GetConstructor(new[] {typeof(IUmbracoBuilder)}) != null) + { + // Handle those collection builders which need a reference to umbraco builder i.e. DistributedLockingCollectionBuilder. + builder = (TBuilder?)Activator.CreateInstance(typeof(TBuilder), this); + } + else + { + throw new InvalidOperationException( + "A CollectionBuilder must have either a parameterless constructor or a constructor whose only parameter is of type IUmbracoBuilder"); + } - if (typeof(TBuilder).GetConstructor(Type.EmptyTypes) != null) - { - builder = Activator.CreateInstance(); - } - else if (typeof(TBuilder).GetConstructor(new[] { typeof(IUmbracoBuilder) }) != null) - { - // Handle those collection builders which need a reference to umbraco builder i.e. DistributedLockingCollectionBuilder. - builder = (TBuilder?)Activator.CreateInstance(typeof(TBuilder), this); - } - else - { - throw new InvalidOperationException("A CollectionBuilder must have either a parameterless constructor or a constructor whose only parameter is of type IUmbracoBuilder"); - } + _builders[typeOfBuilder] = builder; + return builder; + } - _builders[typeOfBuilder] = builder; - return builder; + public void Build() + { + foreach (ICollectionBuilder? builder in _builders.Values) + { + builder?.RegisterWith(Services); } - public void Build() - { - foreach (ICollectionBuilder? builder in _builders.Values) - { - builder?.RegisterWith(Services); - } + _builders.Clear(); + } - _builders.Clear(); - } + private void AddCoreServices() + { + Services.AddSingleton(AppCaches); + Services.AddSingleton(Profiler); - private void AddCoreServices() - { - Services.AddSingleton(AppCaches); - Services.AddSingleton(Profiler); + // Register as singleton to allow injection everywhere. + Services.AddSingleton(p => p.GetService!); + Services.AddSingleton(); - // Register as singleton to allow injection everywhere. - Services.AddSingleton(p => p.GetService!); - Services.AddSingleton(); + Services.AddLazySupport(); - Services.AddLazySupport(); + // Adds no-op registrations as many core services require these dependencies but these + // dependencies cannot be fulfilled in the Core project + Services.AddUnique(); + Services.AddUnique(); - // Adds no-op registrations as many core services require these dependencies but these - // dependencies cannot be fulfilled in the Core project - Services.AddUnique(); - Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); + Services.AddUnique(factory => + { + IHostingEnvironment hostingEnvironment = factory.GetRequiredService(); - Services.AddUnique(factory => + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - IHostingEnvironment hostingEnvironment = factory.GetRequiredService(); - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return new IOHelperLinux(hostingEnvironment); - } + return new IOHelperLinux(hostingEnvironment); + } - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return new IOHelperOSX(hostingEnvironment); - } + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return new IOHelperOSX(hostingEnvironment); + } - return new IOHelperWindows(hostingEnvironment); - }); + return new IOHelperWindows(hostingEnvironment); + }); - Services.AddUnique(factory => factory.GetRequiredService().RuntimeCache); - Services.AddUnique(factory => factory.GetRequiredService().RequestCache); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); + Services.AddUnique(factory => factory.GetRequiredService().RuntimeCache); + Services.AddUnique(factory => factory.GetRequiredService().RequestCache); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); - this.AddAllCoreCollectionBuilders(); - this.AddNotificationHandler(); + this.AddAllCoreCollectionBuilders(); + this.AddNotificationHandler(); - Services.AddSingleton(); + Services.AddSingleton(); - Services.AddSingleton(); + Services.AddSingleton(); - // by default, register a noop factory - Services.AddUnique(); + // by default, register a noop factory + Services.AddUnique(); - Services.AddUnique(); - Services.AddSingleton(f => f.GetRequiredService().CreateDictionary()); + Services.AddUnique(); + Services.AddSingleton(f => f.GetRequiredService().CreateDictionary()); - Services.AddSingleton(); + Services.AddSingleton(); - Services.AddUnique(); - Services.AddSingleton(); + Services.AddUnique(); + Services.AddSingleton(); - // will be injected in controllers when needed to invoke rest endpoints on Our - Services.AddUnique(); - Services.AddUnique(); + // will be injected in controllers when needed to invoke rest endpoints on Our + Services.AddUnique(); + Services.AddUnique(); - // Grid config is not a real config file as we know them - Services.AddUnique(); + // Grid config is not a real config file as we know them + Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); - Services.AddSingleton(); - Services.AddSingleton(); - Services.AddSingleton(); + Services.AddSingleton(); + Services.AddSingleton(); + Services.AddSingleton(); - // register properties fallback - Services.AddUnique(); + // register properties fallback + Services.AddUnique(); - Services.AddSingleton(); + Services.AddSingleton(); - // register published router - Services.AddUnique(); + // register published router + Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); - Services.AddUnique(); + Services.AddUnique(); - // register distributed cache - Services.AddUnique(f => new DistributedCache(f.GetRequiredService(), f.GetRequiredService())); - Services.AddUnique(); + // register distributed cache + Services.AddUnique(f => new DistributedCache(f.GetRequiredService(), + f.GetRequiredService())); + Services.AddUnique(); - // register the http context and umbraco context accessors - // we *should* use the HttpContextUmbracoContextAccessor, however there are cases when - // we have no http context, eg when booting Umbraco or in background threads, so instead - // let's use an hybrid accessor that can fall back to a ThreadStatic context. - Services.AddUnique(); + // register the http context and umbraco context accessors + // we *should* use the HttpContextUmbracoContextAccessor, however there are cases when + // we have no http context, eg when booting Umbraco or in background threads, so instead + // let's use an hybrid accessor that can fall back to a ThreadStatic context. + Services.AddUnique(); - Services.AddSingleton(); - Services.AddSingleton(); - Services.AddSingleton(); - Services.AddSingleton(); + Services.AddSingleton(); + Services.AddSingleton(); + Services.AddSingleton(); + Services.AddSingleton(); - Services.AddSingleton(); - Services.AddSingleton(); + Services.AddSingleton(); + Services.AddSingleton(); - // register a server registrar, by default it's the db registrar - Services.AddUnique(f => - { - GlobalSettings globalSettings = f.GetRequiredService>().Value; - var singleServer = globalSettings.DisableElectionForSingleServer; - return singleServer - ? new SingleServerRoleAccessor() - : new ElectedServerRoleAccessor(f.GetRequiredService()); - }); - - // For Umbraco to work it must have the default IPublishedModelFactory - // which may be replaced by models builder but the default is required to make plain old IPublishedContent - // instances. - Services.AddSingleton(factory => factory.CreateDefaultPublishedModelFactory()); - - Services - .AddNotificationHandler() - .AddNotificationHandler(); - - Services.AddSingleton(); - - // register a basic/noop published snapshot service to be replaced - Services.AddSingleton(); - - // Register ValueEditorCache used for validation - Services.AddSingleton(); - - // Register telemetry service used to gather data about installed packages - Services.AddUnique(); - Services.AddUnique(); - - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(); - Services.AddUnique(factory => new ExternalLoginService( - factory.GetRequiredService(), - factory.GetRequiredService(), - factory.GetRequiredService(), - factory.GetRequiredService() - )); - Services.AddUnique(factory => factory.GetRequiredService()); - Services.AddUnique(factory => factory.GetRequiredService()); - Services.AddUnique(factory => new LocalizedTextService( - factory.GetRequiredService>(), - factory.GetRequiredService>())); - - Services.AddUnique(); - - Services.AddSingleton(); - Services.AddSingleton(); - - // Register a noop IHtmlSanitizer to be replaced - Services.AddUnique(); - } + // register a server registrar, by default it's the db registrar + Services.AddUnique(f => + { + GlobalSettings globalSettings = f.GetRequiredService>().Value; + var singleServer = globalSettings.DisableElectionForSingleServer; + return singleServer + ? new SingleServerRoleAccessor() + : new ElectedServerRoleAccessor(f.GetRequiredService()); + }); + + // For Umbraco to work it must have the default IPublishedModelFactory + // which may be replaced by models builder but the default is required to make plain old IPublishedContent + // instances. + Services.AddSingleton(factory => factory.CreateDefaultPublishedModelFactory()); + + Services + .AddNotificationHandler() + .AddNotificationHandler(); + + Services.AddSingleton(); + + // register a basic/noop published snapshot service to be replaced + Services.AddSingleton(); + + // Register ValueEditorCache used for validation + Services.AddSingleton(); + + // Register telemetry service used to gather data about installed packages + Services.AddUnique(); + Services.AddUnique(); + + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(factory => new ExternalLoginService( + factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService(), + factory.GetRequiredService() + )); + Services.AddUnique(factory => factory.GetRequiredService()); + Services.AddUnique(factory => factory.GetRequiredService()); + Services.AddUnique(factory => new LocalizedTextService( + factory.GetRequiredService>(), + factory.GetRequiredService>())); + + Services.AddUnique(); + + Services.AddSingleton(); + Services.AddSingleton(); + + // Register a noop IHtmlSanitizer to be replaced + Services.AddUnique(); } } diff --git a/src/Umbraco.Core/DependencyInjection/UniqueServiceDescriptor.cs b/src/Umbraco.Core/DependencyInjection/UniqueServiceDescriptor.cs index 538f3f1dda30..0d9f442f4f01 100644 --- a/src/Umbraco.Core/DependencyInjection/UniqueServiceDescriptor.cs +++ b/src/Umbraco.Core/DependencyInjection/UniqueServiceDescriptor.cs @@ -1,58 +1,65 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; -namespace Umbraco.Cms.Core.DependencyInjection +namespace Umbraco.Cms.Core.DependencyInjection; + +/// +/// A custom that supports unique checking +/// +/// +/// This is required because the default implementation doesn't implement Equals or GetHashCode. +/// see: https://github.com/dotnet/runtime/issues/47262 +/// +public sealed class UniqueServiceDescriptor : ServiceDescriptor, IEquatable { /// - /// A custom that supports unique checking + /// Initializes a new instance of the class. /// - /// - /// This is required because the default implementation doesn't implement Equals or GetHashCode. - /// see: https://github.com/dotnet/runtime/issues/47262 - /// - public sealed class UniqueServiceDescriptor : ServiceDescriptor, IEquatable + public UniqueServiceDescriptor(Type serviceType, Type implementationType, ServiceLifetime lifetime) + : base(serviceType, implementationType, lifetime) + { + } + + /// + public bool Equals(UniqueServiceDescriptor? other) => other != null && Lifetime == other.Lifetime && + EqualityComparer.Default.Equals(ServiceType, + other.ServiceType) && + EqualityComparer.Default.Equals(ImplementationType, + other.ImplementationType) && + EqualityComparer.Default.Equals( + ImplementationInstance, other.ImplementationInstance) && + EqualityComparer?>.Default + .Equals(ImplementationFactory, + other.ImplementationFactory); + + /// + public override bool Equals(object? obj) => Equals(obj as UniqueServiceDescriptor); + + /// + public override int GetHashCode() { - /// - /// Initializes a new instance of the class. - /// - public UniqueServiceDescriptor(Type serviceType, Type implementationType, ServiceLifetime lifetime) - : base(serviceType, implementationType, lifetime) + var hashCode = 493849952; + hashCode = (hashCode * -1521134295) + Lifetime.GetHashCode(); + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(ServiceType); + + if (ImplementationType is not null) { + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(ImplementationType); } - /// - public override bool Equals(object? obj) => Equals(obj as UniqueServiceDescriptor); - - /// - public bool Equals(UniqueServiceDescriptor? other) => other != null && Lifetime == other.Lifetime && EqualityComparer.Default.Equals(ServiceType, other.ServiceType) && EqualityComparer.Default.Equals(ImplementationType, other.ImplementationType) && EqualityComparer.Default.Equals(ImplementationInstance, other.ImplementationInstance) && EqualityComparer?>.Default.Equals(ImplementationFactory, other.ImplementationFactory); + if (ImplementationInstance is not null) + { + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(ImplementationInstance); + } - /// - public override int GetHashCode() + if (ImplementationFactory is not null) { - int hashCode = 493849952; - hashCode = (hashCode * -1521134295) + Lifetime.GetHashCode(); - hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(ServiceType); - - if (ImplementationType is not null) - { - hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(ImplementationType); - } - - if (ImplementationInstance is not null) - { - hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(ImplementationInstance); - } - - if (ImplementationFactory is not null) - { - hashCode = (hashCode * -1521134295) + EqualityComparer?>.Default.GetHashCode(ImplementationFactory); - } - - return hashCode; + hashCode = (hashCode * -1521134295) + + EqualityComparer?>.Default.GetHashCode(ImplementationFactory); } + + return hashCode; } } diff --git a/src/Umbraco.Core/Deploy/ArtifactBase.cs b/src/Umbraco.Core/Deploy/ArtifactBase.cs index ff5287d0b860..a2e9e49dfde4 100644 --- a/src/Umbraco.Core/Deploy/ArtifactBase.cs +++ b/src/Umbraco.Core/Deploy/ArtifactBase.cs @@ -1,58 +1,55 @@ -using System; -using System.Collections.Generic; -using System.Linq; +namespace Umbraco.Cms.Core.Deploy; -namespace Umbraco.Cms.Core.Deploy +/// +/// Provides a base class to all artifacts. +/// +public abstract class ArtifactBase : IArtifact + where TUdi : Udi { - /// - /// Provides a base class to all artifacts. - /// - public abstract class ArtifactBase : IArtifact - where TUdi : Udi - { - protected ArtifactBase(TUdi udi, IEnumerable? dependencies = null) - { - Udi = udi ?? throw new ArgumentNullException("udi"); - Name = Udi.ToString(); + private readonly Lazy _checksum; - Dependencies = dependencies ?? Enumerable.Empty(); - _checksum = new Lazy(GetChecksum); - } + private IEnumerable? _dependencies; - private readonly Lazy _checksum; - - private IEnumerable? _dependencies; - - protected abstract string GetChecksum(); + protected ArtifactBase(TUdi udi, IEnumerable? dependencies = null) + { + Udi = udi ?? throw new ArgumentNullException("udi"); + Name = Udi.ToString(); - #region Abstract implementation of IArtifactSignature + Dependencies = dependencies ?? Enumerable.Empty(); + _checksum = new Lazy(GetChecksum); + } - Udi IArtifactSignature.Udi => Udi; + public string Name { get; set; } - public TUdi Udi { get; set; } + public string? Alias { get; set; } - public string Checksum => _checksum.Value; + protected abstract string GetChecksum(); - /// - /// Prevents the property from being serialized. - /// - /// - /// Note that we can't use here as that works only on fields, not properties. And we want to avoid using [JsonIgnore] - /// as that would require an external dependency in Umbraco.Cms.Core. - /// So using this method of excluding properties from serialized data, documented here: https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm - /// - public bool ShouldSerializeChecksum() => false; + #region Abstract implementation of IArtifactSignature - public IEnumerable? Dependencies - { - get => _dependencies; - set => _dependencies = value?.OrderBy(x => x.Udi); - } + Udi IArtifactSignature.Udi => Udi; - #endregion + public TUdi Udi { get; set; } - public string Name { get; set; } + public string Checksum => _checksum.Value; - public string? Alias { get; set; } + /// + /// Prevents the property from being serialized. + /// + /// + /// Note that we can't use here as that works only on fields, not properties. + /// And we want to avoid using [JsonIgnore] + /// as that would require an external dependency in Umbraco.Cms.Core. + /// So using this method of excluding properties from serialized data, documented here: + /// https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm + /// + public bool ShouldSerializeChecksum() => false; + + public IEnumerable? Dependencies + { + get => _dependencies; + set => _dependencies = value?.OrderBy(x => x.Udi); } + + #endregion } diff --git a/src/Umbraco.Core/Deploy/ArtifactDependency.cs b/src/Umbraco.Core/Deploy/ArtifactDependency.cs index 618400e39547..92e27c87bdeb 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDependency.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDependency.cs @@ -1,40 +1,42 @@ -namespace Umbraco.Cms.Core.Deploy +namespace Umbraco.Cms.Core.Deploy; + +/// +/// Represents an artifact dependency. +/// +/// +/// Dependencies have an order property which indicates whether it must be respected when ordering artifacts. +/// +/// Dependencies have a mode which can be Match or Exist depending on whether the checksum should +/// match. +/// +/// +public class ArtifactDependency { /// - /// Represents an artifact dependency. + /// Initializes a new instance of the ArtifactDependency class with an entity identifier and a mode. /// - /// - /// Dependencies have an order property which indicates whether it must be respected when ordering artifacts. - /// Dependencies have a mode which can be Match or Exist depending on whether the checksum should match. - /// - public class ArtifactDependency + /// The entity identifier of the artifact that is a dependency. + /// A value indicating whether the dependency is ordering. + /// The dependency mode. + public ArtifactDependency(Udi udi, bool ordering, ArtifactDependencyMode mode) { - /// - /// Initializes a new instance of the ArtifactDependency class with an entity identifier and a mode. - /// - /// The entity identifier of the artifact that is a dependency. - /// A value indicating whether the dependency is ordering. - /// The dependency mode. - public ArtifactDependency(Udi udi, bool ordering, ArtifactDependencyMode mode) - { - Udi = udi; - Ordering = ordering; - Mode = mode; - } + Udi = udi; + Ordering = ordering; + Mode = mode; + } - /// - /// Gets the entity id of the artifact that is a dependency. - /// - public Udi Udi { get; private set; } + /// + /// Gets the entity id of the artifact that is a dependency. + /// + public Udi Udi { get; } - /// - /// Gets a value indicating whether the dependency is ordering. - /// - public bool Ordering { get; private set; } + /// + /// Gets a value indicating whether the dependency is ordering. + /// + public bool Ordering { get; } - /// - /// Gets the dependency mode. - /// - public ArtifactDependencyMode Mode { get; private set; } - } + /// + /// Gets the dependency mode. + /// + public ArtifactDependencyMode Mode { get; } } diff --git a/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs b/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs index a5fff5380097..c879695b48f0 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs @@ -1,69 +1,44 @@ -using System; -using System.Collections; -using System.Collections.Generic; +using System.Collections; -namespace Umbraco.Cms.Core.Deploy +namespace Umbraco.Cms.Core.Deploy; + +/// +/// Represents a collection of distinct . +/// +/// The collection cannot contain duplicates and modes are properly managed. +public class ArtifactDependencyCollection : ICollection { - /// - /// Represents a collection of distinct . - /// - /// The collection cannot contain duplicates and modes are properly managed. - public class ArtifactDependencyCollection : ICollection - { - private readonly Dictionary _dependencies - = new Dictionary(); + private readonly Dictionary _dependencies = new(); - public IEnumerator GetEnumerator() - { - return _dependencies.Values.GetEnumerator(); - } + public IEnumerator GetEnumerator() => _dependencies.Values.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public void Add(ArtifactDependency item) + public void Add(ArtifactDependency item) + { + if (_dependencies.ContainsKey(item.Udi)) { - if (_dependencies.ContainsKey(item.Udi)) + ArtifactDependency exist = _dependencies[item.Udi]; + if (item.Mode == ArtifactDependencyMode.Exist || item.Mode == exist.Mode) { - var exist = _dependencies[item.Udi]; - if (item.Mode == ArtifactDependencyMode.Exist || item.Mode == exist.Mode) - return; + return; } - - _dependencies[item.Udi] = item; } - public void Clear() - { - _dependencies.Clear(); - } + _dependencies[item.Udi] = item; + } - public bool Contains(ArtifactDependency item) - { - return _dependencies.ContainsKey(item.Udi) && - (_dependencies[item.Udi].Mode == item.Mode || _dependencies[item.Udi].Mode == ArtifactDependencyMode.Match); - } + public void Clear() => _dependencies.Clear(); - public void CopyTo(ArtifactDependency[] array, int arrayIndex) - { - _dependencies.Values.CopyTo(array, arrayIndex); - } + public bool Contains(ArtifactDependency item) => + _dependencies.ContainsKey(item.Udi) && + (_dependencies[item.Udi].Mode == item.Mode || _dependencies[item.Udi].Mode == ArtifactDependencyMode.Match); - public bool Remove(ArtifactDependency item) - { - throw new NotSupportedException(); - } + public void CopyTo(ArtifactDependency[] array, int arrayIndex) => _dependencies.Values.CopyTo(array, arrayIndex); - public int Count - { - get { return _dependencies.Count; } - } + public bool Remove(ArtifactDependency item) => throw new NotSupportedException(); - public bool IsReadOnly - { - get { return false; } - } - } + public int Count => _dependencies.Count; + + public bool IsReadOnly => false; } diff --git a/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs b/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs index 7a2d108a1361..be3c27c565e5 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs @@ -1,18 +1,17 @@ -namespace Umbraco.Cms.Core.Deploy +namespace Umbraco.Cms.Core.Deploy; + +/// +/// Indicates the mode of the dependency. +/// +public enum ArtifactDependencyMode { /// - /// Indicates the mode of the dependency. + /// The dependency must match exactly. /// - public enum ArtifactDependencyMode - { - /// - /// The dependency must match exactly. - /// - Match, + Match, - /// - /// The dependency must exist. - /// - Exist - } + /// + /// The dependency must exist. + /// + Exist } diff --git a/src/Umbraco.Core/Deploy/ArtifactDeployState.cs b/src/Umbraco.Core/Deploy/ArtifactDeployState.cs index d451c1941d54..e9274f38af2e 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDeployState.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDeployState.cs @@ -1,47 +1,47 @@ -namespace Umbraco.Cms.Core.Deploy +namespace Umbraco.Cms.Core.Deploy; + +/// +/// Represent the state of an artifact being deployed. +/// +public abstract class ArtifactDeployState { /// - /// Represent the state of an artifact being deployed. + /// Gets the artifact. /// - public abstract class ArtifactDeployState - { - /// - /// Creates a new instance of the class from an artifact and an entity. - /// - /// The type of the artifact. - /// The type of the entity. - /// The artifact. - /// The entity. - /// The service connector deploying the artifact. - /// The next pass number. - /// A deploying artifact. - public static ArtifactDeployState Create(TArtifact art, TEntity entity, IServiceConnector connector, int nextPass) - where TArtifact : IArtifact - { - return new ArtifactDeployState(art, entity, connector, nextPass); - } + public IArtifact Artifact => GetArtifactAsIArtifact(); - /// - /// Gets the artifact. - /// - public IArtifact Artifact => GetArtifactAsIArtifact(); + /// + /// Gets or sets the service connector in charge of deploying the artifact. + /// + public IServiceConnector? Connector { get; set; } - /// - /// Gets the artifact as an . - /// - /// The artifact, as an . - /// This is because classes that inherit from this class cannot override the Artifact property - /// with a property that specializes the return type, and so they need to 'new' the property. - protected abstract IArtifact GetArtifactAsIArtifact(); + /// + /// Gets or sets the next pass number. + /// + public int NextPass { get; set; } - /// - /// Gets or sets the service connector in charge of deploying the artifact. - /// - public IServiceConnector? Connector { get; set; } + /// + /// Creates a new instance of the class from an artifact and an entity. + /// + /// The type of the artifact. + /// The type of the entity. + /// The artifact. + /// The entity. + /// The service connector deploying the artifact. + /// The next pass number. + /// A deploying artifact. + public static ArtifactDeployState Create(TArtifact art, TEntity entity, + IServiceConnector connector, int nextPass) + where TArtifact : IArtifact => + new ArtifactDeployState(art, entity, connector, nextPass); - /// - /// Gets or sets the next pass number. - /// - public int NextPass { get; set; } - } + /// + /// Gets the artifact as an . + /// + /// The artifact, as an . + /// + /// This is because classes that inherit from this class cannot override the Artifact property + /// with a property that specializes the return type, and so they need to 'new' the property. + /// + protected abstract IArtifact GetArtifactAsIArtifact(); } diff --git a/src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs b/src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs index afe619f3bdea..090ccc588a33 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs @@ -1,42 +1,38 @@ -namespace Umbraco.Cms.Core.Deploy +namespace Umbraco.Cms.Core.Deploy; + +/// +/// Represent the state of an artifact being deployed. +/// +/// The type of the artifact. +/// The type of the entity. +public class ArtifactDeployState : ArtifactDeployState + where TArtifact : IArtifact { /// - /// Represent the state of an artifact being deployed. + /// Initializes a new instance of the class. /// - /// The type of the artifact. - /// The type of the entity. - public class ArtifactDeployState : ArtifactDeployState - where TArtifact : IArtifact + /// The artifact. + /// The entity. + /// The service connector deploying the artifact. + /// The next pass number. + public ArtifactDeployState(TArtifact art, TEntity entity, IServiceConnector connector, int nextPass) { - /// - /// Initializes a new instance of the class. - /// - /// The artifact. - /// The entity. - /// The service connector deploying the artifact. - /// The next pass number. - public ArtifactDeployState(TArtifact art, TEntity entity, IServiceConnector connector, int nextPass) - { - Artifact = art; - Entity = entity; - Connector = connector; - NextPass = nextPass; - } + Artifact = art; + Entity = entity; + Connector = connector; + NextPass = nextPass; + } - /// - /// Gets or sets the artifact. - /// - public new TArtifact Artifact { get; set; } + /// + /// Gets or sets the artifact. + /// + public new TArtifact Artifact { get; set; } - /// - /// Gets or sets the entity. - /// - public TEntity Entity { get; set; } + /// + /// Gets or sets the entity. + /// + public TEntity Entity { get; set; } - /// - protected sealed override IArtifact GetArtifactAsIArtifact() - { - return Artifact; - } - } + /// + protected sealed override IArtifact GetArtifactAsIArtifact() => Artifact; } diff --git a/src/Umbraco.Core/Deploy/ArtifactSignature.cs b/src/Umbraco.Core/Deploy/ArtifactSignature.cs index 629d65593c20..89813866c99d 100644 --- a/src/Umbraco.Core/Deploy/ArtifactSignature.cs +++ b/src/Umbraco.Core/Deploy/ArtifactSignature.cs @@ -1,21 +1,17 @@ -using System.Collections.Generic; -using System.Linq; +namespace Umbraco.Cms.Core.Deploy; -namespace Umbraco.Cms.Core.Deploy +public sealed class ArtifactSignature : IArtifactSignature { - public sealed class ArtifactSignature : IArtifactSignature + public ArtifactSignature(Udi udi, string checksum, IEnumerable? dependencies = null) { - public ArtifactSignature(Udi udi, string checksum, IEnumerable? dependencies = null) - { - Udi = udi; - Checksum = checksum; - Dependencies = dependencies ?? Enumerable.Empty(); - } + Udi = udi; + Checksum = checksum; + Dependencies = dependencies ?? Enumerable.Empty(); + } - public Udi Udi { get; private set; } + public Udi Udi { get; } - public string Checksum { get; private set; } + public string Checksum { get; } - public IEnumerable Dependencies { get; private set; } - } + public IEnumerable Dependencies { get; } } diff --git a/src/Umbraco.Core/Deploy/Difference.cs b/src/Umbraco.Core/Deploy/Difference.cs index be0c086c0b8f..dc049d99bce4 100644 --- a/src/Umbraco.Core/Deploy/Difference.cs +++ b/src/Umbraco.Core/Deploy/Difference.cs @@ -1,28 +1,36 @@ -namespace Umbraco.Cms.Core.Deploy +namespace Umbraco.Cms.Core.Deploy; + +public class Difference { - public class Difference + public Difference(string title, string? text = null, string? category = null) + { + Title = title; + Text = text; + Category = category; + } + + public string Title { get; set; } + public string? Text { get; set; } + public string? Category { get; set; } + + public override string ToString() { - public Difference(string title, string? text = null, string? category = null) + var s = Title; + if (!string.IsNullOrWhiteSpace(Category)) { - Title = title; - Text = text; - Category = category; + s += string.Format("[{0}]", Category); } - public string Title { get; set; } - public string? Text { get; set; } - public string? Category { get; set; } - - public override string ToString() + if (!string.IsNullOrWhiteSpace(Text)) { - var s = Title; - if (!string.IsNullOrWhiteSpace(Category)) s += string.Format("[{0}]", Category); - if (!string.IsNullOrWhiteSpace(Text)) + if (s.Length > 0) { - if (s.Length > 0) s += ":"; - s += Text; + s += ":"; } - return s; + + s += Text; } + + return s; } } diff --git a/src/Umbraco.Core/Deploy/Direction.cs b/src/Umbraco.Core/Deploy/Direction.cs index 7a6ee5ae0977..5f1e5b7a048a 100644 --- a/src/Umbraco.Core/Deploy/Direction.cs +++ b/src/Umbraco.Core/Deploy/Direction.cs @@ -1,8 +1,7 @@ -namespace Umbraco.Cms.Core.Deploy +namespace Umbraco.Cms.Core.Deploy; + +public enum Direction { - public enum Direction - { - ToArtifact, - FromArtifact - } + ToArtifact, + FromArtifact } diff --git a/src/Umbraco.Core/Deploy/IArtifact.cs b/src/Umbraco.Core/Deploy/IArtifact.cs index 5eb9c079f382..a3500c12c63c 100644 --- a/src/Umbraco.Core/Deploy/IArtifact.cs +++ b/src/Umbraco.Core/Deploy/IArtifact.cs @@ -1,11 +1,10 @@ -namespace Umbraco.Cms.Core.Deploy +namespace Umbraco.Cms.Core.Deploy; + +/// +/// Represents an artifact ie an object that can be transfered between environments. +/// +public interface IArtifact : IArtifactSignature { - /// - /// Represents an artifact ie an object that can be transfered between environments. - /// - public interface IArtifact : IArtifactSignature - { - string Name { get; } - string? Alias { get; } - } + string Name { get; } + string? Alias { get; } } diff --git a/src/Umbraco.Core/Deploy/IArtifactSignature.cs b/src/Umbraco.Core/Deploy/IArtifactSignature.cs index edd4d1ad42c8..14e6e6ca6d3a 100644 --- a/src/Umbraco.Core/Deploy/IArtifactSignature.cs +++ b/src/Umbraco.Core/Deploy/IArtifactSignature.cs @@ -1,41 +1,46 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Deploy; -namespace Umbraco.Cms.Core.Deploy +/// +/// Represents the signature of an artifact. +/// +public interface IArtifactSignature { /// - /// Represents the signature of an artifact. + /// Gets the entity unique identifier of this artifact. /// - public interface IArtifactSignature - { - /// - /// Gets the entity unique identifier of this artifact. - /// - /// - /// The project identifier is independent from the state of the artifact, its data - /// values, dependencies, anything. It never changes and fully identifies the artifact. - /// What an entity uses as a unique identifier will influence what we can transfer - /// between environments. Eg content type "Foo" on one environment is not necessarily the - /// same as "Foo" on another environment, if guids are used as unique identifiers. What is - /// used should be documented for each entity, along with the consequences of the choice. - /// - Udi Udi { get; } + /// + /// + /// The project identifier is independent from the state of the artifact, its data + /// values, dependencies, anything. It never changes and fully identifies the artifact. + /// + /// + /// What an entity uses as a unique identifier will influence what we can transfer + /// between environments. Eg content type "Foo" on one environment is not necessarily the + /// same as "Foo" on another environment, if guids are used as unique identifiers. What is + /// used should be documented for each entity, along with the consequences of the choice. + /// + /// + Udi Udi { get; } - /// - /// Gets the checksum of this artifact. - /// - /// - /// The checksum depends on the artifact's properties, and on the identifiers of all its dependencies, - /// but not on their checksums. So the checksum changes when any of the artifact's properties changes, - /// or when the list of dependencies changes. But not if one of these dependencies change. - /// It is assumed that checksum collisions cannot happen ie that no two different artifact's - /// states will ever produce the same checksum, so that if two artifacts have the same checksum then - /// they are identical. - /// - string Checksum { get; } + /// + /// Gets the checksum of this artifact. + /// + /// + /// + /// The checksum depends on the artifact's properties, and on the identifiers of all its dependencies, + /// but not on their checksums. So the checksum changes when any of the artifact's properties changes, + /// or when the list of dependencies changes. But not if one of these dependencies change. + /// + /// + /// It is assumed that checksum collisions cannot happen ie that no two different artifact's + /// states will ever produce the same checksum, so that if two artifacts have the same checksum then + /// they are identical. + /// + /// + string Checksum { get; } - /// - /// Gets the dependencies of this artifact. - /// - IEnumerable? Dependencies { get; } - } + /// + /// Gets the dependencies of this artifact. + /// + IEnumerable? Dependencies { get; } } diff --git a/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs b/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs index 548a6d80f19d..fdc1471358a0 100644 --- a/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs +++ b/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs @@ -1,34 +1,36 @@ -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Deploy +namespace Umbraco.Cms.Core.Deploy; + +/// +/// Defines methods that can convert data type configuration to / from an environment-agnostic string. +/// +/// +/// Configuration may contain values such as content identifiers, that would be local +/// to one environment, and need to be converted in order to be deployed. +/// +[SuppressMessage("ReSharper", "UnusedMember.Global", + Justification = + "This is actual only used by Deploy, but we don't want third parties to have references on deploy, that's why this interface is part of core.")] +public interface IDataTypeConfigurationConnector { /// - /// Defines methods that can convert data type configuration to / from an environment-agnostic string. + /// Gets the property editor aliases that the value converter supports by default. /// - /// Configuration may contain values such as content identifiers, that would be local - /// to one environment, and need to be converted in order to be deployed. - [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "This is actual only used by Deploy, but we don't want third parties to have references on deploy, that's why this interface is part of core.")] - public interface IDataTypeConfigurationConnector - { - /// - /// Gets the property editor aliases that the value converter supports by default. - /// - IEnumerable PropertyEditorAliases { get; } + IEnumerable PropertyEditorAliases { get; } - /// - /// Gets the artifact datatype configuration corresponding to the actual datatype configuration. - /// - /// The datatype. - /// The dependencies. - string ToArtifact(IDataType dataType, ICollection dependencies); + /// + /// Gets the artifact datatype configuration corresponding to the actual datatype configuration. + /// + /// The datatype. + /// The dependencies. + string ToArtifact(IDataType dataType, ICollection dependencies); - /// - /// Gets the actual datatype configuration corresponding to the artifact configuration. - /// - /// The datatype. - /// The artifact configuration. - object FromArtifact(IDataType dataType, string configuration); - } + /// + /// Gets the actual datatype configuration corresponding to the artifact configuration. + /// + /// The datatype. + /// The artifact configuration. + object FromArtifact(IDataType dataType, string configuration); } diff --git a/src/Umbraco.Core/Deploy/IDeployContext.cs b/src/Umbraco.Core/Deploy/IDeployContext.cs index c6e2da997b0f..eb4443485175 100644 --- a/src/Umbraco.Core/Deploy/IDeployContext.cs +++ b/src/Umbraco.Core/Deploy/IDeployContext.cs @@ -1,47 +1,43 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Deploy; -namespace Umbraco.Cms.Core.Deploy +/// +/// Represents a deployment context. +/// +public interface IDeployContext { /// - /// Represents a deployment context. + /// Gets the unique identifier of the deployment. /// - public interface IDeployContext - { - /// - /// Gets the unique identifier of the deployment. - /// - Guid SessionId { get; } + Guid SessionId { get; } - /// - /// Gets the file source. - /// - /// The file source is used to obtain files from the source environment. - IFileSource FileSource { get; } + /// + /// Gets the file source. + /// + /// The file source is used to obtain files from the source environment. + IFileSource FileSource { get; } - /// - /// Gets the next number in a numerical sequence. - /// - /// The next sequence number. - /// Can be used to uniquely number things during a deployment. - int NextSeq(); + /// + /// Gets items. + /// + IDictionary Items { get; } - /// - /// Gets items. - /// - IDictionary Items { get; } + /// + /// Gets the next number in a numerical sequence. + /// + /// The next sequence number. + /// Can be used to uniquely number things during a deployment. + int NextSeq(); - /// - /// Gets item. - /// - /// The type of the item. - /// The key of the item. - /// The item with the specified key and type, if any, else null. - T? Item(string key) where T : class; + /// + /// Gets item. + /// + /// The type of the item. + /// The key of the item. + /// The item with the specified key and type, if any, else null. + T? Item(string key) where T : class; - ///// - ///// Gets the global deployment cancellation token. - ///// - //CancellationToken CancellationToken { get; } - } + ///// + ///// Gets the global deployment cancellation token. + ///// + //CancellationToken CancellationToken { get; } } diff --git a/src/Umbraco.Core/Deploy/IFileSource.cs b/src/Umbraco.Core/Deploy/IFileSource.cs index 6e582803a291..5b9d1999910a 100644 --- a/src/Umbraco.Core/Deploy/IFileSource.cs +++ b/src/Umbraco.Core/Deploy/IFileSource.cs @@ -1,91 +1,85 @@ -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; +namespace Umbraco.Cms.Core.Deploy; -namespace Umbraco.Cms.Core.Deploy +/// +/// Represents a file source, ie a mean for a target environment involved in a +/// deployment to obtain the content of files being deployed. +/// +public interface IFileSource { /// - /// Represents a file source, ie a mean for a target environment involved in a - /// deployment to obtain the content of files being deployed. + /// Gets the content of a file as a stream. /// - public interface IFileSource - { - /// - /// Gets the content of a file as a stream. - /// - /// A file entity identifier. - /// A stream with read access to the file content. - /// - /// Returns null if no content could be read. - /// The caller should ensure that the stream is properly closed/disposed. - /// - Stream GetFileStream(StringUdi udi); + /// A file entity identifier. + /// A stream with read access to the file content. + /// + /// Returns null if no content could be read. + /// The caller should ensure that the stream is properly closed/disposed. + /// + Stream GetFileStream(StringUdi udi); - /// - /// Gets the content of a file as a stream. - /// - /// A file entity identifier. - /// A cancellation token. - /// A stream with read access to the file content. - /// - /// Returns null if no content could be read. - /// The caller should ensure that the stream is properly closed/disposed. - /// - Task GetFileStreamAsync(StringUdi udi, CancellationToken token); + /// + /// Gets the content of a file as a stream. + /// + /// A file entity identifier. + /// A cancellation token. + /// A stream with read access to the file content. + /// + /// Returns null if no content could be read. + /// The caller should ensure that the stream is properly closed/disposed. + /// + Task GetFileStreamAsync(StringUdi udi, CancellationToken token); - /// - /// Gets the content of a file as a string. - /// - /// A file entity identifier. - /// A string containing the file content. - /// Returns null if no content could be read. - string GetFileContent(StringUdi udi); + /// + /// Gets the content of a file as a string. + /// + /// A file entity identifier. + /// A string containing the file content. + /// Returns null if no content could be read. + string GetFileContent(StringUdi udi); - /// - /// Gets the content of a file as a string. - /// - /// A file entity identifier. - /// A cancellation token. - /// A string containing the file content. - /// Returns null if no content could be read. - Task GetFileContentAsync(StringUdi udi, CancellationToken token); + /// + /// Gets the content of a file as a string. + /// + /// A file entity identifier. + /// A cancellation token. + /// A string containing the file content. + /// Returns null if no content could be read. + Task GetFileContentAsync(StringUdi udi, CancellationToken token); - /// - /// Gets the length of a file. - /// - /// A file entity identifier. - /// The length of the file, or -1 if the file does not exist. - long GetFileLength(StringUdi udi); + /// + /// Gets the length of a file. + /// + /// A file entity identifier. + /// The length of the file, or -1 if the file does not exist. + long GetFileLength(StringUdi udi); - /// - /// Gets the length of a file. - /// - /// A file entity identifier. - /// A cancellation token. - /// The length of the file, or -1 if the file does not exist. - Task GetFileLengthAsync(StringUdi udi, CancellationToken token); + /// + /// Gets the length of a file. + /// + /// A file entity identifier. + /// A cancellation token. + /// The length of the file, or -1 if the file does not exist. + Task GetFileLengthAsync(StringUdi udi, CancellationToken token); - /// - /// Gets files and store them using a file store. - /// - /// The udis of the files to get. - /// A collection of file types which can store the files. - void GetFiles(IEnumerable udis, IFileTypeCollection fileTypes); + /// + /// Gets files and store them using a file store. + /// + /// The udis of the files to get. + /// A collection of file types which can store the files. + void GetFiles(IEnumerable udis, IFileTypeCollection fileTypes); - /// - /// Gets files and store them using a file store. - /// - /// The udis of the files to get. - /// A collection of file types which can store the files. - /// A cancellation token. - Task GetFilesAsync(IEnumerable udis, IFileTypeCollection fileTypes, CancellationToken token); + /// + /// Gets files and store them using a file store. + /// + /// The udis of the files to get. + /// A collection of file types which can store the files. + /// A cancellation token. + Task GetFilesAsync(IEnumerable udis, IFileTypeCollection fileTypes, CancellationToken token); - ///// - ///// Gets the content of a file as a bytes array. - ///// - ///// A file entity identifier. - ///// A byte array containing the file content. - //byte[] GetFileBytes(StringUdi Udi); - } + ///// + ///// Gets the content of a file as a bytes array. + ///// + ///// A file entity identifier. + ///// A byte array containing the file content. + //byte[] GetFileBytes(StringUdi Udi); } diff --git a/src/Umbraco.Core/Deploy/IFileType.cs b/src/Umbraco.Core/Deploy/IFileType.cs index ef6c44e1e650..3b16afb75031 100644 --- a/src/Umbraco.Core/Deploy/IFileType.cs +++ b/src/Umbraco.Core/Deploy/IFileType.cs @@ -1,32 +1,26 @@ -using System.IO; -using System.Threading; -using System.Threading.Tasks; +namespace Umbraco.Cms.Core.Deploy; -namespace Umbraco.Cms.Core.Deploy +public interface IFileType { - public interface IFileType - { - Stream GetStream(StringUdi udi); + bool CanSetPhysical { get; } + Stream GetStream(StringUdi udi); - Task GetStreamAsync(StringUdi udi, CancellationToken token); + Task GetStreamAsync(StringUdi udi, CancellationToken token); - Stream GetChecksumStream(StringUdi udi); + Stream GetChecksumStream(StringUdi udi); - long GetLength(StringUdi udi); + long GetLength(StringUdi udi); - void SetStream(StringUdi udi, Stream stream); + void SetStream(StringUdi udi, Stream stream); - Task SetStreamAsync(StringUdi udi, Stream stream, CancellationToken token); + Task SetStreamAsync(StringUdi udi, Stream stream, CancellationToken token); - bool CanSetPhysical { get; } + void Set(StringUdi udi, string physicalPath, bool copy = false); - void Set(StringUdi udi, string physicalPath, bool copy = false); + // this is not pretty as *everywhere* in Deploy we take care of ignoring + // the physical path and always rely on Core's virtual IFileSystem but + // Cloud wants to add some of these files to Git and needs the path... + string GetPhysicalPath(StringUdi udi); - // this is not pretty as *everywhere* in Deploy we take care of ignoring - // the physical path and always rely on Core's virtual IFileSystem but - // Cloud wants to add some of these files to Git and needs the path... - string GetPhysicalPath(StringUdi udi); - - string GetVirtualPath(StringUdi udi); - } + string GetVirtualPath(StringUdi udi); } diff --git a/src/Umbraco.Core/Deploy/IFileTypeCollection.cs b/src/Umbraco.Core/Deploy/IFileTypeCollection.cs index d19d2ad64a99..3bede6bd0502 100644 --- a/src/Umbraco.Core/Deploy/IFileTypeCollection.cs +++ b/src/Umbraco.Core/Deploy/IFileTypeCollection.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.Deploy +namespace Umbraco.Cms.Core.Deploy; + +public interface IFileTypeCollection { - public interface IFileTypeCollection - { - IFileType this[string entityType] { get; } + IFileType this[string entityType] { get; } - bool Contains(string entityType); - } + bool Contains(string entityType); } diff --git a/src/Umbraco.Core/Deploy/IImageSourceParser.cs b/src/Umbraco.Core/Deploy/IImageSourceParser.cs index 084ba1b11867..ea3ad6d56a2c 100644 --- a/src/Umbraco.Core/Deploy/IImageSourceParser.cs +++ b/src/Umbraco.Core/Deploy/IImageSourceParser.cs @@ -1,25 +1,24 @@ -namespace Umbraco.Cms.Core.Deploy +namespace Umbraco.Cms.Core.Deploy; + +/// +/// Provides methods to parse image tag sources in property values. +/// +public interface IImageSourceParser { /// - /// Provides methods to parse image tag sources in property values. + /// Parses an Umbraco property value and produces an artifact property value. /// - public interface IImageSourceParser - { - /// - /// Parses an Umbraco property value and produces an artifact property value. - /// - /// The property value. - /// A list of dependencies. - /// The parsed value. - /// Turns src="/media/..." into src="umb://media/..." and adds the corresponding udi to the dependencies. - string? ToArtifact(string? value, ICollection dependencies); + /// The property value. + /// A list of dependencies. + /// The parsed value. + /// Turns src="/media/..." into src="umb://media/..." and adds the corresponding udi to the dependencies. + string? ToArtifact(string? value, ICollection dependencies); - /// - /// Parses an artifact property value and produces an Umbraco property value. - /// - /// The artifact property value. - /// The parsed value. - /// Turns umb://media/... into /media/.... - string? FromArtifact(string? value); - } + /// + /// Parses an artifact property value and produces an Umbraco property value. + /// + /// The artifact property value. + /// The parsed value. + /// Turns umb://media/... into /media/.... + string? FromArtifact(string? value); } diff --git a/src/Umbraco.Core/Deploy/ILocalLinkParser.cs b/src/Umbraco.Core/Deploy/ILocalLinkParser.cs index 5883f7321762..6083602c15d5 100644 --- a/src/Umbraco.Core/Deploy/ILocalLinkParser.cs +++ b/src/Umbraco.Core/Deploy/ILocalLinkParser.cs @@ -1,25 +1,27 @@ -namespace Umbraco.Cms.Core.Deploy +namespace Umbraco.Cms.Core.Deploy; + +/// +/// Provides methods to parse local link tags in property values. +/// +public interface ILocalLinkParser { /// - /// Provides methods to parse local link tags in property values. + /// Parses an Umbraco property value and produces an artifact property value. /// - public interface ILocalLinkParser - { - /// - /// Parses an Umbraco property value and produces an artifact property value. - /// - /// The property value. - /// A list of dependencies. - /// The parsed value. - /// Turns {{localLink:1234}} into {{localLink:umb://{type}/{id}}} and adds the corresponding udi to the dependencies. - string ToArtifact(string value, ICollection dependencies); + /// The property value. + /// A list of dependencies. + /// The parsed value. + /// + /// Turns {{localLink:1234}} into {{localLink:umb://{type}/{id}}} and adds the corresponding udi to the + /// dependencies. + /// + string ToArtifact(string value, ICollection dependencies); - /// - /// Parses an artifact property value and produces an Umbraco property value. - /// - /// The artifact property value. - /// The parsed value. - /// Turns {{localLink:umb://{type}/{id}}} into {{localLink:1234}}. - string FromArtifact(string value); - } + /// + /// Parses an artifact property value and produces an Umbraco property value. + /// + /// The artifact property value. + /// The parsed value. + /// Turns {{localLink:umb://{type}/{id}}} into {{localLink:1234}}. + string FromArtifact(string value); } diff --git a/src/Umbraco.Core/Deploy/IMacroParser.cs b/src/Umbraco.Core/Deploy/IMacroParser.cs index 81b014c1cca6..0aaaa09c2cee 100644 --- a/src/Umbraco.Core/Deploy/IMacroParser.cs +++ b/src/Umbraco.Core/Deploy/IMacroParser.cs @@ -1,32 +1,29 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Deploy; -namespace Umbraco.Cms.Core.Deploy +public interface IMacroParser { - public interface IMacroParser - { - /// - /// Parses an Umbraco property value and produces an artifact property value. - /// - /// Property value. - /// A list of dependencies. - /// Parsed value. - string? ToArtifact(string? value, ICollection dependencies); + /// + /// Parses an Umbraco property value and produces an artifact property value. + /// + /// Property value. + /// A list of dependencies. + /// Parsed value. + string? ToArtifact(string? value, ICollection dependencies); - /// - /// Parses an artifact property value and produces an Umbraco property value. - /// - /// Artifact property value. - /// Parsed value. - string? FromArtifact(string? value); + /// + /// Parses an artifact property value and produces an Umbraco property value. + /// + /// Artifact property value. + /// Parsed value. + string? FromArtifact(string? value); - /// - /// Tries to replace the value of the attribute/parameter with a value containing a converted identifier. - /// - /// Value to attempt to convert - /// Alias of the editor used for the parameter - /// Collection to add dependencies to when performing ToArtifact - /// Indicates which action is being performed (to or from artifact) - /// Value with converted identifiers - string ReplaceAttributeValue(string value, string editorAlias, ICollection dependencies, Direction direction); - } + /// + /// Tries to replace the value of the attribute/parameter with a value containing a converted identifier. + /// + /// Value to attempt to convert + /// Alias of the editor used for the parameter + /// Collection to add dependencies to when performing ToArtifact + /// Indicates which action is being performed (to or from artifact) + /// Value with converted identifiers + string ReplaceAttributeValue(string value, string editorAlias, ICollection dependencies, Direction direction); } diff --git a/src/Umbraco.Core/Deploy/IServiceConnector.cs b/src/Umbraco.Core/Deploy/IServiceConnector.cs index 3f789e2e38e4..90b1f570e9f3 100644 --- a/src/Umbraco.Core/Deploy/IServiceConnector.cs +++ b/src/Umbraco.Core/Deploy/IServiceConnector.cs @@ -1,84 +1,83 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Deploy +namespace Umbraco.Cms.Core.Deploy; + +/// +/// Connects to an Umbraco service. +/// +public interface IServiceConnector : IDiscoverable { /// - /// Connects to an Umbraco service. + /// Gets an artifact. /// - public interface IServiceConnector : IDiscoverable - { - /// - /// Gets an artifact. - /// - /// The entity identifier of the artifact. - /// The corresponding artifact, or null. - IArtifact? GetArtifact(Udi udi); - - /// - /// Gets an artifact. - /// - /// The entity. - /// The corresponding artifact. - IArtifact GetArtifact(object entity); + /// The entity identifier of the artifact. + /// The corresponding artifact, or null. + IArtifact? GetArtifact(Udi udi); - /// - /// Initializes processing for an artifact. - /// - /// The artifact. - /// The deploy context. - /// The mapped artifact. - ArtifactDeployState ProcessInit(IArtifact art, IDeployContext context); + /// + /// Gets an artifact. + /// + /// The entity. + /// The corresponding artifact. + IArtifact GetArtifact(object entity); - /// - /// Processes an artifact. - /// - /// The mapped artifact. - /// The deploy context. - /// The processing pass number. - void Process(ArtifactDeployState dart, IDeployContext context, int pass); + /// + /// Initializes processing for an artifact. + /// + /// The artifact. + /// The deploy context. + /// The mapped artifact. + ArtifactDeployState ProcessInit(IArtifact art, IDeployContext context); - /// - /// Explodes a range into udis. - /// - /// The range. - /// The list of udis where to add the new udis. - /// Also, it's cool to have a method named Explode. Kaboom! - void Explode(UdiRange range, List udis); + /// + /// Processes an artifact. + /// + /// The mapped artifact. + /// The deploy context. + /// The processing pass number. + void Process(ArtifactDeployState dart, IDeployContext context, int pass); - /// - /// Gets a named range for a specified udi and selector. - /// - /// The udi. - /// The selector. - /// The named range for the specified udi and selector. - NamedUdiRange GetRange(Udi udi, string selector); + /// + /// Explodes a range into udis. + /// + /// The range. + /// The list of udis where to add the new udis. + /// Also, it's cool to have a method named Explode. Kaboom! + void Explode(UdiRange range, List udis); - /// - /// Gets a named range for specified entity type, identifier and selector. - /// - /// The entity type. - /// The identifier. - /// The selector. - /// The named range for the specified entity type, identifier and selector. - /// - /// This is temporary. At least we thought it would be, in sept. 2016. What day is it now? - /// At the moment our UI has a hard time returning proper udis, mainly because Core's tree do - /// not manage guids but only ints... so we have to provide a way to support it. The string id here - /// can be either a real string (for string udis) or an "integer as a string", using the value "-1" to - /// indicate the "root" i.e. an open udi. - /// - NamedUdiRange GetRange(string entityType, string sid, string selector); + /// + /// Gets a named range for a specified udi and selector. + /// + /// The udi. + /// The selector. + /// The named range for the specified udi and selector. + NamedUdiRange GetRange(Udi udi, string selector); - /// - /// Compares two artifacts. - /// - /// The first artifact. - /// The second artifact. - /// A collection of differences to append to, if not null. - /// A boolean value indicating whether the artifacts are identical. - /// ServiceConnectorBase{TArtifact} provides a very basic default implementation. - bool Compare(IArtifact? art1, IArtifact? art2, ICollection? differences = null); - } + /// + /// Gets a named range for specified entity type, identifier and selector. + /// + /// The entity type. + /// The identifier. + /// The selector. + /// The named range for the specified entity type, identifier and selector. + /// + /// This is temporary. At least we thought it would be, in sept. 2016. What day is it now? + /// + /// At the moment our UI has a hard time returning proper udis, mainly because Core's tree do + /// not manage guids but only ints... so we have to provide a way to support it. The string id here + /// can be either a real string (for string udis) or an "integer as a string", using the value "-1" to + /// indicate the "root" i.e. an open udi. + /// + /// + NamedUdiRange GetRange(string entityType, string sid, string selector); + /// + /// Compares two artifacts. + /// + /// The first artifact. + /// The second artifact. + /// A collection of differences to append to, if not null. + /// A boolean value indicating whether the artifacts are identical. + /// ServiceConnectorBase{TArtifact} provides a very basic default implementation. + bool Compare(IArtifact? art1, IArtifact? art2, ICollection? differences = null); } diff --git a/src/Umbraco.Core/Deploy/IUniqueIdentifyingServiceConnector.cs b/src/Umbraco.Core/Deploy/IUniqueIdentifyingServiceConnector.cs index 66364a08f36f..e7844642be96 100644 --- a/src/Umbraco.Core/Deploy/IUniqueIdentifyingServiceConnector.cs +++ b/src/Umbraco.Core/Deploy/IUniqueIdentifyingServiceConnector.cs @@ -1,25 +1,24 @@ -namespace Umbraco.Cms.Core.Deploy +namespace Umbraco.Cms.Core.Deploy; + +/// +/// Provides a method to retrieve an artifact's unique identifier. +/// +/// +/// Artifacts are uniquely identified by their , however they represent +/// elements in Umbraco that may be uniquely identified by another value. For example, +/// a content type is uniquely identified by its alias. If someone creates a new content +/// type, and tries to deploy it to a remote environment where a content type with the +/// same alias already exists, both content types end up having different +/// but the same alias. By default, Deploy would fail and throw when trying to save the +/// new content type (duplicate alias). However, if the connector also implements this +/// interface, the situation can be detected beforehand and reported in a nicer way. +/// +public interface IUniqueIdentifyingServiceConnector { /// - /// Provides a method to retrieve an artifact's unique identifier. + /// Gets the unique identifier of the specified artifact. /// - /// - /// Artifacts are uniquely identified by their , however they represent - /// elements in Umbraco that may be uniquely identified by another value. For example, - /// a content type is uniquely identified by its alias. If someone creates a new content - /// type, and tries to deploy it to a remote environment where a content type with the - /// same alias already exists, both content types end up having different - /// but the same alias. By default, Deploy would fail and throw when trying to save the - /// new content type (duplicate alias). However, if the connector also implements this - /// interface, the situation can be detected beforehand and reported in a nicer way. - /// - public interface IUniqueIdentifyingServiceConnector - { - /// - /// Gets the unique identifier of the specified artifact. - /// - /// The artifact. - /// The unique identifier. - string GetUniqueIdentifier(IArtifact artifact); - } + /// The artifact. + /// The unique identifier. + string GetUniqueIdentifier(IArtifact artifact); } diff --git a/src/Umbraco.Core/Deploy/IValueConnector.cs b/src/Umbraco.Core/Deploy/IValueConnector.cs index 90896f9da499..b10d9bab0489 100644 --- a/src/Umbraco.Core/Deploy/IValueConnector.cs +++ b/src/Umbraco.Core/Deploy/IValueConnector.cs @@ -1,37 +1,38 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Deploy +namespace Umbraco.Cms.Core.Deploy; + +/// +/// Defines methods that can convert a property value to / from an environment-agnostic string. +/// +/// +/// Property values may contain values such as content identifiers, that would be local +/// to one environment, and need to be converted in order to be deployed. Connectors also deal +/// with serializing to / from string. +/// +public interface IValueConnector { /// - /// Defines methods that can convert a property value to / from an environment-agnostic string. + /// Gets the property editor aliases that the value converter supports by default. /// - /// Property values may contain values such as content identifiers, that would be local - /// to one environment, and need to be converted in order to be deployed. Connectors also deal - /// with serializing to / from string. - public interface IValueConnector - { - /// - /// Gets the property editor aliases that the value converter supports by default. - /// - IEnumerable PropertyEditorAliases { get; } + IEnumerable PropertyEditorAliases { get; } - /// - /// Gets the deploy property value corresponding to a content property value, and gather dependencies. - /// - /// The content property value. - /// The value property type - /// The content dependencies. - /// The deploy property value. - string? ToArtifact(object? value, IPropertyType propertyType, ICollection dependencies); + /// + /// Gets the deploy property value corresponding to a content property value, and gather dependencies. + /// + /// The content property value. + /// The value property type + /// The content dependencies. + /// The deploy property value. + string? ToArtifact(object? value, IPropertyType propertyType, ICollection dependencies); - /// - /// Gets the content property value corresponding to a deploy property value. - /// - /// The deploy property value. - /// The value property type< - /// The current content property value. - /// The content property value. - object? FromArtifact(string? value, IPropertyType propertyType, object? currentValue); - } + /// + /// Gets the content property value corresponding to a deploy property value. + /// + /// The deploy property value. + /// + /// The value property type< + /// The current content property value. + /// The content property value. + object? FromArtifact(string? value, IPropertyType propertyType, object? currentValue); } diff --git a/src/Umbraco.Core/Diagnostics/IMarchal.cs b/src/Umbraco.Core/Diagnostics/IMarchal.cs index 988eaca78c63..304ff22c5a68 100644 --- a/src/Umbraco.Core/Diagnostics/IMarchal.cs +++ b/src/Umbraco.Core/Diagnostics/IMarchal.cs @@ -1,16 +1,15 @@ -using System; +namespace Umbraco.Cms.Core.Diagnostics; -namespace Umbraco.Cms.Core.Diagnostics +/// +/// Provides a collection of methods for allocating unmanaged memory, copying unmanaged memory blocks, and converting +/// managed to unmanaged types, as well as other miscellaneous methods used when interacting with unmanaged code. +/// +public interface IMarchal { /// - /// Provides a collection of methods for allocating unmanaged memory, copying unmanaged memory blocks, and converting managed to unmanaged types, as well as other miscellaneous methods used when interacting with unmanaged code. + /// Retrieves a computer-independent description of an exception, and information about the state that existed for the + /// thread when the exception occurred. /// - public interface IMarchal - { - /// - /// Retrieves a computer-independent description of an exception, and information about the state that existed for the thread when the exception occurred. - /// - /// A pointer to an EXCEPTION_POINTERS structure. - IntPtr GetExceptionPointers(); - } + /// A pointer to an EXCEPTION_POINTERS structure. + IntPtr GetExceptionPointers(); } diff --git a/src/Umbraco.Core/Diagnostics/MiniDump.cs b/src/Umbraco.Core/Diagnostics/MiniDump.cs index 25f6e530e115..e07db99b853e 100644 --- a/src/Umbraco.Core/Diagnostics/MiniDump.cs +++ b/src/Umbraco.Core/Diagnostics/MiniDump.cs @@ -1,145 +1,149 @@ -using System; using System.Diagnostics; -using System.IO; using System.Runtime.InteropServices; using Umbraco.Cms.Core.Hosting; -namespace Umbraco.Cms.Core.Diagnostics -{ - // taken from https://blogs.msdn.microsoft.com/dondu/2010/10/24/writing-minidumps-in-c/ - // and https://blogs.msdn.microsoft.com/dondu/2010/10/31/writing-minidumps-from-exceptions-in-c/ - // which itself got it from http://blog.kalmbach-software.de/2008/12/13/writing-minidumps-in-c/ +namespace Umbraco.Cms.Core.Diagnostics; +// taken from https://blogs.msdn.microsoft.com/dondu/2010/10/24/writing-minidumps-in-c/ +// and https://blogs.msdn.microsoft.com/dondu/2010/10/31/writing-minidumps-from-exceptions-in-c/ +// which itself got it from http://blog.kalmbach-software.de/2008/12/13/writing-minidumps-in-c/ - public static class MiniDump +public static class MiniDump +{ + [Flags] + public enum Option : uint { - private static readonly object LockO = new object(); + // From dbghelp.h: + Normal = 0x00000000, + WithDataSegs = 0x00000001, + WithFullMemory = 0x00000002, + WithHandleData = 0x00000004, + FilterMemory = 0x00000008, + ScanMemory = 0x00000010, + WithUnloadedModules = 0x00000020, + WithIndirectlyReferencedMemory = 0x00000040, + FilterModulePaths = 0x00000080, + WithProcessThreadData = 0x00000100, + WithPrivateReadWriteMemory = 0x00000200, + WithoutOptionalData = 0x00000400, + WithFullMemoryInfo = 0x00000800, + WithThreadInfo = 0x00001000, + WithCodeSegs = 0x00002000, + WithoutAuxiliaryState = 0x00004000, + WithFullAuxiliaryState = 0x00008000, + WithPrivateWriteCopyMemory = 0x00010000, + IgnoreInaccessibleMemory = 0x00020000, + ValidTypeFlags = 0x0003ffff + } - [Flags] - public enum Option : uint + private static readonly object LockO = new(); + + //BOOL + //WINAPI + //MiniDumpWriteDump( + // __in HANDLE hProcess, + // __in DWORD ProcessId, + // __in HANDLE hFile, + // __in MINIDUMP_TYPE DumpType, + // __in_opt PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + // __in_opt PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + // __in_opt PMINIDUMP_CALLBACK_INFORMATION CallbackParam + // ); + + // Overload requiring MiniDumpExceptionInformation + [DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, + CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + private static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, + ref MiniDumpExceptionInformation expParam, IntPtr userStreamParam, IntPtr callbackParam); + + // Overload supporting MiniDumpExceptionInformation == NULL + [DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, + CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + private static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, + IntPtr expParam, IntPtr userStreamParam, IntPtr callbackParam); + + [DllImport("kernel32.dll", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)] + private static extern uint GetCurrentThreadId(); + + private static bool Write(IMarchal marchal, SafeHandle fileHandle, Option options, bool withException = false) + { + using (var currentProcess = Process.GetCurrentProcess()) { - // From dbghelp.h: - Normal = 0x00000000, - WithDataSegs = 0x00000001, - WithFullMemory = 0x00000002, - WithHandleData = 0x00000004, - FilterMemory = 0x00000008, - ScanMemory = 0x00000010, - WithUnloadedModules = 0x00000020, - WithIndirectlyReferencedMemory = 0x00000040, - FilterModulePaths = 0x00000080, - WithProcessThreadData = 0x00000100, - WithPrivateReadWriteMemory = 0x00000200, - WithoutOptionalData = 0x00000400, - WithFullMemoryInfo = 0x00000800, - WithThreadInfo = 0x00001000, - WithCodeSegs = 0x00002000, - WithoutAuxiliaryState = 0x00004000, - WithFullAuxiliaryState = 0x00008000, - WithPrivateWriteCopyMemory = 0x00010000, - IgnoreInaccessibleMemory = 0x00020000, - ValidTypeFlags = 0x0003ffff, - } + IntPtr currentProcessHandle = currentProcess.Handle; + var currentProcessId = (uint)currentProcess.Id; - //typedef struct _MINIDUMP_EXCEPTION_INFORMATION { - // DWORD ThreadId; - // PEXCEPTION_POINTERS ExceptionPointers; - // BOOL ClientPointers; - //} MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION; - [StructLayout(LayoutKind.Sequential, Pack = 4)] // Pack=4 is important! So it works also for x64! - public struct MiniDumpExceptionInformation - { - public uint ThreadId; - public IntPtr ExceptionPointers; - [MarshalAs(UnmanagedType.Bool)] - public bool ClientPointers; - } + MiniDumpExceptionInformation exp; - //BOOL - //WINAPI - //MiniDumpWriteDump( - // __in HANDLE hProcess, - // __in DWORD ProcessId, - // __in HANDLE hFile, - // __in MINIDUMP_TYPE DumpType, - // __in_opt PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, - // __in_opt PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, - // __in_opt PMINIDUMP_CALLBACK_INFORMATION CallbackParam - // ); - - // Overload requiring MiniDumpExceptionInformation - [DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] - private static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, ref MiniDumpExceptionInformation expParam, IntPtr userStreamParam, IntPtr callbackParam); - - // Overload supporting MiniDumpExceptionInformation == NULL - [DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] - private static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, IntPtr expParam, IntPtr userStreamParam, IntPtr callbackParam); - - [DllImport("kernel32.dll", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)] - private static extern uint GetCurrentThreadId(); - - private static bool Write(IMarchal marchal, SafeHandle fileHandle, Option options, bool withException = false) - { - using (var currentProcess = Process.GetCurrentProcess()) + exp.ThreadId = GetCurrentThreadId(); + exp.ClientPointers = false; + exp.ExceptionPointers = IntPtr.Zero; + + if (withException) { - var currentProcessHandle = currentProcess.Handle; - var currentProcessId = (uint)currentProcess.Id; + exp.ExceptionPointers = marchal.GetExceptionPointers(); + } - MiniDumpExceptionInformation exp; + var bRet = exp.ExceptionPointers == IntPtr.Zero + ? MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint)options, IntPtr.Zero, + IntPtr.Zero, IntPtr.Zero) + : MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint)options, ref exp, + IntPtr.Zero, IntPtr.Zero); - exp.ThreadId = GetCurrentThreadId(); - exp.ClientPointers = false; - exp.ExceptionPointers = IntPtr.Zero; + return bRet; + } + } - if (withException) - { - exp.ExceptionPointers = marchal.GetExceptionPointers(); - } + public static bool Dump(IMarchal marchal, IHostingEnvironment hostingEnvironment, + Option options = Option.WithFullMemory, bool withException = false) + { + lock (LockO) + { + // work around "stack trace is not available while minidump debugging", + // by making sure a local var (that we can inspect) contains the stack trace. + // getting the call stack before it is unwound would require a special exception + // filter everywhere in our code = not! + var stacktrace = withException ? Environment.StackTrace : string.Empty; - var bRet = exp.ExceptionPointers == IntPtr.Zero - ? MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint)options, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero) - : MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint)options, ref exp, IntPtr.Zero, IntPtr.Zero); + var directory = hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data + "/MiniDump"); - return bRet; + if (Directory.Exists(directory) == false) + { + Directory.CreateDirectory(directory); } - } - public static bool Dump(IMarchal marchal, IHostingEnvironment hostingEnvironment, Option options = Option.WithFullMemory, bool withException = false) - { - lock (LockO) + var filename = Path.Combine(directory, + $"{DateTime.UtcNow:yyyyMMddTHHmmss}.{Guid.NewGuid().ToString("N").Substring(0, 4)}.dmp"); + using (var stream = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite, FileShare.Write)) { - // work around "stack trace is not available while minidump debugging", - // by making sure a local var (that we can inspect) contains the stack trace. - // getting the call stack before it is unwound would require a special exception - // filter everywhere in our code = not! - var stacktrace = withException ? Environment.StackTrace : string.Empty; - - var directory = hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data + "/MiniDump"); - - if (Directory.Exists(directory) == false) - { - Directory.CreateDirectory(directory); - } - - var filename = Path.Combine(directory, $"{DateTime.UtcNow:yyyyMMddTHHmmss}.{Guid.NewGuid().ToString("N").Substring(0, 4)}.dmp"); - using (var stream = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite, FileShare.Write)) - { - return Write(marchal, stream.SafeFileHandle, options, withException); - } + return Write(marchal, stream.SafeFileHandle, options, withException); } } + } - public static bool OkToDump(IHostingEnvironment hostingEnvironment) + public static bool OkToDump(IHostingEnvironment hostingEnvironment) + { + lock (LockO) { - lock (LockO) + var directory = hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data + "/MiniDump"); + if (Directory.Exists(directory) == false) { - var directory = hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data + "/MiniDump"); - if (Directory.Exists(directory) == false) - { - return true; - } - var count = Directory.GetFiles(directory, "*.dmp").Length; - return count < 8; + return true; } + + var count = Directory.GetFiles(directory, "*.dmp").Length; + return count < 8; } } + + //typedef struct _MINIDUMP_EXCEPTION_INFORMATION { + // DWORD ThreadId; + // PEXCEPTION_POINTERS ExceptionPointers; + // BOOL ClientPointers; + //} MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION; + [StructLayout(LayoutKind.Sequential, Pack = 4)] // Pack=4 is important! So it works also for x64! + public struct MiniDumpExceptionInformation + { + public uint ThreadId; + public IntPtr ExceptionPointers; + [MarshalAs(UnmanagedType.Bool)] public bool ClientPointers; + } } diff --git a/src/Umbraco.Core/Diagnostics/NoopMarchal.cs b/src/Umbraco.Core/Diagnostics/NoopMarchal.cs index 273a4fb32c11..d1e775dbaac1 100644 --- a/src/Umbraco.Core/Diagnostics/NoopMarchal.cs +++ b/src/Umbraco.Core/Diagnostics/NoopMarchal.cs @@ -1,9 +1,6 @@ -using System; +namespace Umbraco.Cms.Core.Diagnostics; -namespace Umbraco.Cms.Core.Diagnostics +internal class NoopMarchal : IMarchal { - internal class NoopMarchal : IMarchal - { - public IntPtr GetExceptionPointers() => IntPtr.Zero; - } + public IntPtr GetExceptionPointers() => IntPtr.Zero; } diff --git a/src/Umbraco.Core/Dictionary/ICultureDictionary.cs b/src/Umbraco.Core/Dictionary/ICultureDictionary.cs index e8e3c620505e..3b9a6b7b6fc2 100644 --- a/src/Umbraco.Core/Dictionary/ICultureDictionary.cs +++ b/src/Umbraco.Core/Dictionary/ICultureDictionary.cs @@ -1,30 +1,28 @@ -using System.Collections.Generic; -using System.Globalization; +using System.Globalization; -namespace Umbraco.Cms.Core.Dictionary +namespace Umbraco.Cms.Core.Dictionary; + +/// +/// Represents a dictionary based on a specific culture +/// +public interface ICultureDictionary { /// - /// Represents a dictionary based on a specific culture + /// Returns the dictionary value based on the key supplied /// - public interface ICultureDictionary - { - /// - /// Returns the dictionary value based on the key supplied - /// - /// - /// - string? this[string key] { get; } + /// + /// + string? this[string key] { get; } - /// - /// Returns the current culture - /// - CultureInfo Culture { get; } + /// + /// Returns the current culture + /// + CultureInfo Culture { get; } - /// - /// Returns the child dictionary entries for a given key - /// - /// - /// - IDictionary GetChildren(string key); - } + /// + /// Returns the child dictionary entries for a given key + /// + /// + /// + IDictionary GetChildren(string key); } diff --git a/src/Umbraco.Core/Dictionary/ICultureDictionaryFactory.cs b/src/Umbraco.Core/Dictionary/ICultureDictionaryFactory.cs index 40fbb1bad821..f01b6efbc347 100644 --- a/src/Umbraco.Core/Dictionary/ICultureDictionaryFactory.cs +++ b/src/Umbraco.Core/Dictionary/ICultureDictionaryFactory.cs @@ -1,7 +1,6 @@ -namespace Umbraco.Cms.Core.Dictionary +namespace Umbraco.Cms.Core.Dictionary; + +public interface ICultureDictionaryFactory { - public interface ICultureDictionaryFactory - { - ICultureDictionary CreateDictionary(); - } + ICultureDictionary CreateDictionary(); } diff --git a/src/Umbraco.Core/Dictionary/UmbracoCultureDictionary.cs b/src/Umbraco.Core/Dictionary/UmbracoCultureDictionary.cs index 44cc15033f96..37738a5d4ebc 100644 --- a/src/Umbraco.Core/Dictionary/UmbracoCultureDictionary.cs +++ b/src/Umbraco.Core/Dictionary/UmbracoCultureDictionary.cs @@ -1,142 +1,141 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; +using System.Globalization; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Dictionary +namespace Umbraco.Cms.Core.Dictionary; + +/// +/// A culture dictionary that uses the Umbraco ILocalizationService +/// +/// +/// TODO: The ICultureDictionary needs to represent the 'fast' way to do dictionary item retrieval - for front-end and +/// back office. +/// The ILocalizationService is the service used for interacting with this data from the database which isn't all that +/// fast +/// (even though there is caching involved, if there's lots of dictionary items the caching is not great) +/// +internal class DefaultCultureDictionary : ICultureDictionary { + private readonly ILocalizationService _localizationService; + private readonly IAppCache _requestCache; + private readonly CultureInfo? _specificCulture; + /// - /// A culture dictionary that uses the Umbraco ILocalizationService + /// Default constructor which will use the current thread's culture /// - /// - /// TODO: The ICultureDictionary needs to represent the 'fast' way to do dictionary item retrieval - for front-end and back office. - /// The ILocalizationService is the service used for interacting with this data from the database which isn't all that fast - /// (even though there is caching involved, if there's lots of dictionary items the caching is not great) - /// - internal class DefaultCultureDictionary : ICultureDictionary + /// + /// + public DefaultCultureDictionary(ILocalizationService localizationService, IAppCache requestCache) { - private readonly ILocalizationService _localizationService; - private readonly IAppCache _requestCache; - private readonly CultureInfo? _specificCulture; - - /// - /// Default constructor which will use the current thread's culture - /// - /// - /// - public DefaultCultureDictionary(ILocalizationService localizationService, IAppCache requestCache) - { - _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); - _requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache)); - } + _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); + _requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache)); + } - /// - /// Constructor for testing to specify a static culture - /// - /// - /// - /// - public DefaultCultureDictionary(CultureInfo specificCulture, ILocalizationService localizationService, IAppCache requestCache) - { - _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); - _requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache)); - _specificCulture = specificCulture ?? throw new ArgumentNullException(nameof(specificCulture)); - } + /// + /// Constructor for testing to specify a static culture + /// + /// + /// + /// + public DefaultCultureDictionary(CultureInfo specificCulture, ILocalizationService localizationService, + IAppCache requestCache) + { + _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); + _requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache)); + _specificCulture = specificCulture ?? throw new ArgumentNullException(nameof(specificCulture)); + } - /// - /// Returns the dictionary value based on the key supplied - /// - /// - /// - public string? this[string key] - { - get + private ILanguage? Language => + //ensure it's stored/retrieved from request cache + //NOTE: This is no longer necessary since these are cached at the runtime level, but we can leave it here for now. + _requestCache.GetCacheItem(typeof(DefaultCultureDictionary).Name + "Culture" + Culture.Name, + () => { - var found = _localizationService.GetDictionaryItemByKey(key); - if (found == null) + // find a language that matches the current culture or any of its parent cultures + CultureInfo culture = Culture; + while (culture != CultureInfo.InvariantCulture) { - return string.Empty; - } + ILanguage language = _localizationService.GetLanguageByIsoCode(culture.Name); + if (language != null) + { + return language; + } - var byLang = found.Translations?.FirstOrDefault(x => x.Language?.Equals(Language) ?? false); - if (byLang == null) - { - return string.Empty; + culture = culture.Parent; } - return byLang.Value; - } - } + return null; + }); - /// - /// Returns the current culture - /// - public CultureInfo Culture => _specificCulture ?? System.Threading.Thread.CurrentThread.CurrentUICulture; - - /// - /// Returns the child dictionary entries for a given key - /// - /// - /// - /// - /// NOTE: The result of this is not cached anywhere - the underlying repository does not cache - /// the child lookups because that is done by a query lookup. This method isn't used in our codebase - /// so I don't think this is a performance issue but if devs are using this it could be optimized here. - /// - public IDictionary GetChildren(string key) + /// + /// Returns the dictionary value based on the key supplied + /// + /// + /// + public string? this[string key] + { + get { - var result = new Dictionary(); - - var found = _localizationService.GetDictionaryItemByKey(key); + IDictionaryItem found = _localizationService.GetDictionaryItemByKey(key); if (found == null) { - return result; + return string.Empty; } - var children = _localizationService.GetDictionaryItemChildren(found.Key); - if (children == null) + IDictionaryTranslation byLang = + found.Translations?.FirstOrDefault(x => x.Language?.Equals(Language) ?? false); + if (byLang == null) { - return result; + return string.Empty; } - foreach (var dictionaryItem in children) - { - var byLang = dictionaryItem.Translations?.FirstOrDefault((x => x.Language?.Equals(Language) ?? false)); - if (byLang != null && dictionaryItem.ItemKey is not null && byLang.Value is not null) - { - result.Add(dictionaryItem.ItemKey, byLang.Value); - } - } + return byLang.Value; + } + } + /// + /// Returns the current culture + /// + public CultureInfo Culture => _specificCulture ?? Thread.CurrentThread.CurrentUICulture; + + /// + /// Returns the child dictionary entries for a given key + /// + /// + /// + /// + /// NOTE: The result of this is not cached anywhere - the underlying repository does not cache + /// the child lookups because that is done by a query lookup. This method isn't used in our codebase + /// so I don't think this is a performance issue but if devs are using this it could be optimized here. + /// + public IDictionary GetChildren(string key) + { + var result = new Dictionary(); + + IDictionaryItem found = _localizationService.GetDictionaryItemByKey(key); + if (found == null) + { return result; } - private ILanguage? Language + IEnumerable children = _localizationService.GetDictionaryItemChildren(found.Key); + if (children == null) { - get + return result; + } + + foreach (IDictionaryItem dictionaryItem in children) + { + IDictionaryTranslation byLang = + dictionaryItem.Translations?.FirstOrDefault(x => x.Language?.Equals(Language) ?? false); + if (byLang != null && dictionaryItem.ItemKey is not null && byLang.Value is not null) { - //ensure it's stored/retrieved from request cache - //NOTE: This is no longer necessary since these are cached at the runtime level, but we can leave it here for now. - return _requestCache.GetCacheItem(typeof (DefaultCultureDictionary).Name + "Culture" + Culture.Name, - () => { - // find a language that matches the current culture or any of its parent cultures - var culture = Culture; - while(culture != CultureInfo.InvariantCulture) - { - var language = _localizationService.GetLanguageByIsoCode(culture.Name); - if(language != null) - { - return language; - } - culture = culture.Parent; - } - return null; - }); + result.Add(dictionaryItem.ItemKey, byLang.Value); } } + + return result; } } diff --git a/src/Umbraco.Core/Dictionary/UmbracoCultureDictionaryFactory.cs b/src/Umbraco.Core/Dictionary/UmbracoCultureDictionaryFactory.cs index 8713e338ea47..afa66f4238cc 100644 --- a/src/Umbraco.Core/Dictionary/UmbracoCultureDictionaryFactory.cs +++ b/src/Umbraco.Core/Dictionary/UmbracoCultureDictionaryFactory.cs @@ -1,28 +1,26 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.Dictionary -{ - /// - /// A culture dictionary factory used to create an Umbraco.Core.Dictionary.ICultureDictionary. - /// - /// - /// In the future this will allow use to potentially store dictionary items elsewhere and allows for maximum flexibility. - /// - public class DefaultCultureDictionaryFactory : ICultureDictionaryFactory - { - private readonly ILocalizationService _localizationService; - private readonly AppCaches _appCaches; +namespace Umbraco.Cms.Core.Dictionary; - public DefaultCultureDictionaryFactory(ILocalizationService localizationService, AppCaches appCaches) - { - _localizationService = localizationService; - _appCaches = appCaches; - } +/// +/// A culture dictionary factory used to create an Umbraco.Core.Dictionary.ICultureDictionary. +/// +/// +/// In the future this will allow use to potentially store dictionary items elsewhere and allows for maximum +/// flexibility. +/// +public class DefaultCultureDictionaryFactory : ICultureDictionaryFactory +{ + private readonly AppCaches _appCaches; + private readonly ILocalizationService _localizationService; - public ICultureDictionary CreateDictionary() - { - return new DefaultCultureDictionary(_localizationService, _appCaches.RequestCache); - } + public DefaultCultureDictionaryFactory(ILocalizationService localizationService, AppCaches appCaches) + { + _localizationService = localizationService; + _appCaches = appCaches; } + + public ICultureDictionary CreateDictionary() => + new DefaultCultureDictionary(_localizationService, _appCaches.RequestCache); } diff --git a/src/Umbraco.Core/Direction.cs b/src/Umbraco.Core/Direction.cs index 152a3663fd80..941d96d9bc93 100644 --- a/src/Umbraco.Core/Direction.cs +++ b/src/Umbraco.Core/Direction.cs @@ -1,8 +1,7 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public enum Direction { - public enum Direction - { - Ascending = 0, - Descending = 1 - } + Ascending = 0, + Descending = 1 } diff --git a/src/Umbraco.Core/DisposableObjectSlim.cs b/src/Umbraco.Core/DisposableObjectSlim.cs index 4304098324ad..6cc7f38d91e0 100644 --- a/src/Umbraco.Core/DisposableObjectSlim.cs +++ b/src/Umbraco.Core/DisposableObjectSlim.cs @@ -1,56 +1,50 @@ -using System; +namespace Umbraco.Cms.Core; -namespace Umbraco.Cms.Core +/// +/// Abstract implementation of managed IDisposable. +/// +/// +/// This is for objects that do NOT have unmanaged resources. +/// Can also be used as a pattern for when inheriting is not possible. +/// See also: https://msdn.microsoft.com/en-us/library/b1yfkh5e%28v=vs.110%29.aspx +/// See also: https://lostechies.com/chrispatterson/2012/11/29/idisposable-done-right/ +/// Note: if an object's ctor throws, it will never be disposed, and so if that ctor +/// has allocated disposable objects, it should take care of disposing them. +/// +public abstract class DisposableObjectSlim : IDisposable { /// - /// Abstract implementation of managed IDisposable. + /// Gets a value indicating whether this instance is disposed. /// /// - /// This is for objects that do NOT have unmanaged resources. - /// - /// Can also be used as a pattern for when inheriting is not possible. - /// - /// See also: https://msdn.microsoft.com/en-us/library/b1yfkh5e%28v=vs.110%29.aspx - /// See also: https://lostechies.com/chrispatterson/2012/11/29/idisposable-done-right/ - /// - /// Note: if an object's ctor throws, it will never be disposed, and so if that ctor - /// has allocated disposable objects, it should take care of disposing them. + /// for internal tests only (not thread safe) /// - public abstract class DisposableObjectSlim : IDisposable - { - /// - /// Gets a value indicating whether this instance is disposed. - /// - /// - /// for internal tests only (not thread safe) - /// - public bool Disposed { get; private set; } + public bool Disposed { get; private set; } - /// - /// Disposes managed resources - /// - protected abstract void DisposeResources(); + /// +#pragma warning disable CA1063 // Implement IDisposable Correctly + public void Dispose() => Dispose(true); // We do not use GC.SuppressFinalize because this has no finalizer +#pragma warning restore CA1063 // Implement IDisposable Correctly - /// - /// Disposes managed resources - /// - /// True if disposing via Dispose method and not a finalizer. Always true for this class. - protected virtual void Dispose(bool disposing) + /// + /// Disposes managed resources + /// + protected abstract void DisposeResources(); + + /// + /// Disposes managed resources + /// + /// True if disposing via Dispose method and not a finalizer. Always true for this class. + protected virtual void Dispose(bool disposing) + { + if (!Disposed) { - if (!Disposed) + if (disposing) { - if (disposing) - { - DisposeResources(); - } - - Disposed = true; + DisposeResources(); } - } - /// -#pragma warning disable CA1063 // Implement IDisposable Correctly - public void Dispose() => Dispose(disposing: true); // We do not use GC.SuppressFinalize because this has no finalizer -#pragma warning restore CA1063 // Implement IDisposable Correctly + Disposed = true; + } } } diff --git a/src/Umbraco.Core/DistributedLocking/DistributedLockType.cs b/src/Umbraco.Core/DistributedLocking/DistributedLockType.cs index 01acd02c1067..9b6030702d87 100644 --- a/src/Umbraco.Core/DistributedLocking/DistributedLockType.cs +++ b/src/Umbraco.Core/DistributedLocking/DistributedLockType.cs @@ -1,7 +1,7 @@ namespace Umbraco.Cms.Core.DistributedLocking; /// -/// Represents the type of distributed lock. +/// Represents the type of distributed lock. /// public enum DistributedLockType { diff --git a/src/Umbraco.Core/DistributedLocking/Exceptions/DistributedLockingException.cs b/src/Umbraco.Core/DistributedLocking/Exceptions/DistributedLockingException.cs index 2f27929a6c17..570af005b590 100644 --- a/src/Umbraco.Core/DistributedLocking/Exceptions/DistributedLockingException.cs +++ b/src/Umbraco.Core/DistributedLocking/Exceptions/DistributedLockingException.cs @@ -1,14 +1,12 @@ -using System; - namespace Umbraco.Cms.Core.DistributedLocking.Exceptions; /// -/// Base class for all DistributedLockingExceptions. +/// Base class for all DistributedLockingExceptions. /// public class DistributedLockingException : ApplicationException { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public DistributedLockingException(string message) : base(message) @@ -16,7 +14,7 @@ public DistributedLockingException(string message) } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// // ReSharper disable once UnusedMember.Global public DistributedLockingException(string message, Exception innerException) diff --git a/src/Umbraco.Core/DistributedLocking/Exceptions/DistributedLockingTimeoutException.cs b/src/Umbraco.Core/DistributedLocking/Exceptions/DistributedLockingTimeoutException.cs index 9d6502379080..064a04680316 100644 --- a/src/Umbraco.Core/DistributedLocking/Exceptions/DistributedLockingTimeoutException.cs +++ b/src/Umbraco.Core/DistributedLocking/Exceptions/DistributedLockingTimeoutException.cs @@ -1,12 +1,12 @@ namespace Umbraco.Cms.Core.DistributedLocking.Exceptions; /// -/// Base class for all DistributedLocking timeout related exceptions. +/// Base class for all DistributedLocking timeout related exceptions. /// public abstract class DistributedLockingTimeoutException : DistributedLockingException { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// protected DistributedLockingTimeoutException(int lockId, bool isWrite) : base($"Failed to acquire {(isWrite ? "write" : "read")} lock for id: {lockId}.") diff --git a/src/Umbraco.Core/DistributedLocking/Exceptions/DistributedReadLockTimeoutException.cs b/src/Umbraco.Core/DistributedLocking/Exceptions/DistributedReadLockTimeoutException.cs index 4d37238c0df9..8e21004cecfd 100644 --- a/src/Umbraco.Core/DistributedLocking/Exceptions/DistributedReadLockTimeoutException.cs +++ b/src/Umbraco.Core/DistributedLocking/Exceptions/DistributedReadLockTimeoutException.cs @@ -1,12 +1,12 @@ namespace Umbraco.Cms.Core.DistributedLocking.Exceptions; /// -/// Exception thrown when a read lock could not be obtained in a timely manner. +/// Exception thrown when a read lock could not be obtained in a timely manner. /// public class DistributedReadLockTimeoutException : DistributedLockingTimeoutException { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public DistributedReadLockTimeoutException(int lockId) : base(lockId, false) diff --git a/src/Umbraco.Core/DistributedLocking/Exceptions/DistributedWriteLockTimeoutException.cs b/src/Umbraco.Core/DistributedLocking/Exceptions/DistributedWriteLockTimeoutException.cs index abf84470e05f..068684f31039 100644 --- a/src/Umbraco.Core/DistributedLocking/Exceptions/DistributedWriteLockTimeoutException.cs +++ b/src/Umbraco.Core/DistributedLocking/Exceptions/DistributedWriteLockTimeoutException.cs @@ -1,12 +1,12 @@ namespace Umbraco.Cms.Core.DistributedLocking.Exceptions; /// -/// Exception thrown when a write lock could not be obtained in a timely manner. +/// Exception thrown when a write lock could not be obtained in a timely manner. /// public class DistributedWriteLockTimeoutException : DistributedLockingTimeoutException { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public DistributedWriteLockTimeoutException(int lockId) : base(lockId, true) diff --git a/src/Umbraco.Core/DistributedLocking/IDistributedLock.cs b/src/Umbraco.Core/DistributedLocking/IDistributedLock.cs index 202bb594bcf1..261bd802e3b4 100644 --- a/src/Umbraco.Core/DistributedLocking/IDistributedLock.cs +++ b/src/Umbraco.Core/DistributedLocking/IDistributedLock.cs @@ -1,19 +1,17 @@ -using System; - namespace Umbraco.Cms.Core.DistributedLocking; /// -/// Interface representing a DistributedLock. +/// Interface representing a DistributedLock. /// public interface IDistributedLock : IDisposable { /// - /// Gets the LockId. + /// Gets the LockId. /// int LockId { get; } /// - /// Gets the DistributedLockType. + /// Gets the DistributedLockType. /// DistributedLockType LockType { get; } } diff --git a/src/Umbraco.Core/DistributedLocking/IDistributedLockingMechanism.cs b/src/Umbraco.Core/DistributedLocking/IDistributedLockingMechanism.cs index 5df8a236509a..57252364d322 100644 --- a/src/Umbraco.Core/DistributedLocking/IDistributedLockingMechanism.cs +++ b/src/Umbraco.Core/DistributedLocking/IDistributedLockingMechanism.cs @@ -1,50 +1,52 @@ -using System; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DistributedLocking.Exceptions; namespace Umbraco.Cms.Core.DistributedLocking; /// -/// Represents a class responsible for managing distributed locks. +/// Represents a class responsible for managing distributed locks. /// /// -/// In general the rules for distributed locks are as follows. -/// -/// -/// Cannot obtain a write lock if a read lock exists for same lock id (except during an upgrade from reader -> writer) -/// -/// -/// Cannot obtain a write lock if a write lock exists for same lock id. -/// -/// -/// Cannot obtain a read lock if a write lock exists for same lock id. -/// -/// -/// Can obtain a read lock if a read lock exists for same lock id. -/// -/// +/// In general the rules for distributed locks are as follows. +/// +/// +/// Cannot obtain a write lock if a read lock exists for same lock id (except during an upgrade from +/// reader -> writer) +/// +/// +/// Cannot obtain a write lock if a write lock exists for same lock id. +/// +/// +/// Cannot obtain a read lock if a write lock exists for same lock id. +/// +/// +/// Can obtain a read lock if a read lock exists for same lock id. +/// +/// /// public interface IDistributedLockingMechanism { /// - /// Gets a value indicating whether this distributed locking mechanism can be used. + /// Gets a value indicating whether this distributed locking mechanism can be used. /// bool Enabled { get; } /// - /// Obtains a distributed read lock. + /// Obtains a distributed read lock. /// /// - /// When timeout is null, implementations should use . + /// When timeout is null, implementations should use + /// . /// /// Failed to obtain distributed read lock in time. IDistributedLock ReadLock(int lockId, TimeSpan? obtainLockTimeout = null); /// - /// Obtains a distributed read lock. + /// Obtains a distributed read lock. /// /// - /// When timeout is null, implementations should use . + /// When timeout is null, implementations should use + /// . /// /// Failed to obtain distributed write lock in time. IDistributedLock WriteLock(int lockId, TimeSpan? obtainLockTimeout = null); diff --git a/src/Umbraco.Core/DistributedLocking/IDistributedLockingMechanismFactory.cs b/src/Umbraco.Core/DistributedLocking/IDistributedLockingMechanismFactory.cs index 1bd1cfe206de..ecc1c99cfa01 100644 --- a/src/Umbraco.Core/DistributedLocking/IDistributedLockingMechanismFactory.cs +++ b/src/Umbraco.Core/DistributedLocking/IDistributedLockingMechanismFactory.cs @@ -1,7 +1,7 @@ namespace Umbraco.Cms.Core.DistributedLocking; /// -/// Picks an appropriate IDistributedLockingMechanism when multiple are registered +/// Picks an appropriate IDistributedLockingMechanism when multiple are registered /// public interface IDistributedLockingMechanismFactory { diff --git a/src/Umbraco.Core/Editors/BackOfficePreviewModel.cs b/src/Umbraco.Core/Editors/BackOfficePreviewModel.cs index d8bd73aca933..8a66e890a28b 100644 --- a/src/Umbraco.Core/Editors/BackOfficePreviewModel.cs +++ b/src/Umbraco.Core/Editors/BackOfficePreviewModel.cs @@ -1,21 +1,19 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Features; +using Umbraco.Cms.Core.Features; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Editors -{ - public class BackOfficePreviewModel - { - private readonly UmbracoFeatures _features; +namespace Umbraco.Cms.Core.Editors; - public BackOfficePreviewModel(UmbracoFeatures features, IEnumerable languages) - { - _features = features; - Languages = languages; - } +public class BackOfficePreviewModel +{ + private readonly UmbracoFeatures _features; - public IEnumerable Languages { get; } - public bool DisableDevicePreview => _features.Disabled.DisableDevicePreview; - public string? PreviewExtendedHeaderView => _features.Enabled.PreviewExtendedView; + public BackOfficePreviewModel(UmbracoFeatures features, IEnumerable languages) + { + _features = features; + Languages = languages; } + + public IEnumerable Languages { get; } + public bool DisableDevicePreview => _features.Disabled.DisableDevicePreview; + public string? PreviewExtendedHeaderView => _features.Enabled.PreviewExtendedView; } diff --git a/src/Umbraco.Core/Editors/EditorValidatorCollection.cs b/src/Umbraco.Core/Editors/EditorValidatorCollection.cs index 91bc3e191b42..f63526cc8b31 100644 --- a/src/Umbraco.Core/Editors/EditorValidatorCollection.cs +++ b/src/Umbraco.Core/Editors/EditorValidatorCollection.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Editors +namespace Umbraco.Cms.Core.Editors; + +public class EditorValidatorCollection : BuilderCollectionBase { - public class EditorValidatorCollection : BuilderCollectionBase + public EditorValidatorCollection(Func> items) : base(items) { - public EditorValidatorCollection(Func> items) : base(items) - { - } } } diff --git a/src/Umbraco.Core/Editors/EditorValidatorCollectionBuilder.cs b/src/Umbraco.Core/Editors/EditorValidatorCollectionBuilder.cs index 223778b79d3a..a128453bf62c 100644 --- a/src/Umbraco.Core/Editors/EditorValidatorCollectionBuilder.cs +++ b/src/Umbraco.Core/Editors/EditorValidatorCollectionBuilder.cs @@ -1,9 +1,9 @@ using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Editors +namespace Umbraco.Cms.Core.Editors; + +public class EditorValidatorCollectionBuilder : LazyCollectionBuilderBase { - public class EditorValidatorCollectionBuilder : LazyCollectionBuilderBase - { - protected override EditorValidatorCollectionBuilder This => this; - } + protected override EditorValidatorCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/Editors/EditorValidatorOfT.cs b/src/Umbraco.Core/Editors/EditorValidatorOfT.cs index a70509237a73..6b698075d15a 100644 --- a/src/Umbraco.Core/Editors/EditorValidatorOfT.cs +++ b/src/Umbraco.Core/Editors/EditorValidatorOfT.cs @@ -1,19 +1,16 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; -namespace Umbraco.Cms.Core.Editors +namespace Umbraco.Cms.Core.Editors; + +/// +/// Provides a base class for implementations. +/// +/// The validated object type. +public abstract class EditorValidator : IEditorValidator { - /// - /// Provides a base class for implementations. - /// - /// The validated object type. - public abstract class EditorValidator : IEditorValidator - { - public Type ModelType => typeof (T); + public Type ModelType => typeof(T); - public IEnumerable Validate(object model) => Validate((T) model); + public IEnumerable Validate(object model) => Validate((T)model); - protected abstract IEnumerable Validate(T model); - } + protected abstract IEnumerable Validate(T model); } diff --git a/src/Umbraco.Core/Editors/IEditorValidator.cs b/src/Umbraco.Core/Editors/IEditorValidator.cs index 17bb195e4b95..6ddb4ef14035 100644 --- a/src/Umbraco.Core/Editors/IEditorValidator.cs +++ b/src/Umbraco.Core/Editors/IEditorValidator.cs @@ -1,34 +1,30 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Editors -{ - // note - about IEditorValidator - // - // interface: IEditorValidator - // base class: EditorValidator - // static validation: EditorValidator.Validate() - // composition: via EditorValidationCollection and builder - // initialized with all IEditorValidator instances - // - // validation is used exclusively in ContentTypeControllerBase - // currently the only implementations are for Models Builder. +namespace Umbraco.Cms.Core.Editors; +// note - about IEditorValidator +// +// interface: IEditorValidator +// base class: EditorValidator +// static validation: EditorValidator.Validate() +// composition: via EditorValidationCollection and builder +// initialized with all IEditorValidator instances +// +// validation is used exclusively in ContentTypeControllerBase +// currently the only implementations are for Models Builder. +/// +/// Provides a general object validator. +/// +public interface IEditorValidator : IDiscoverable +{ /// - /// Provides a general object validator. + /// Gets the object type validated by this validator. /// - public interface IEditorValidator : IDiscoverable - { - /// - /// Gets the object type validated by this validator. - /// - Type ModelType { get; } + Type ModelType { get; } - /// - /// Validates an object. - /// - IEnumerable Validate(object model); - } + /// + /// Validates an object. + /// + IEnumerable Validate(object model); } diff --git a/src/Umbraco.Core/Editors/UserEditorAuthorizationHelper.cs b/src/Umbraco.Core/Editors/UserEditorAuthorizationHelper.cs index 23fc59da24bc..07cf89872ba8 100644 --- a/src/Umbraco.Core/Editors/UserEditorAuthorizationHelper.cs +++ b/src/Umbraco.Core/Editors/UserEditorAuthorizationHelper.cs @@ -1,8 +1,6 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; @@ -10,161 +8,193 @@ using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Editors +namespace Umbraco.Cms.Core.Editors; + +public class UserEditorAuthorizationHelper { - public class UserEditorAuthorizationHelper - { - private readonly IContentService _contentService; - private readonly IMediaService _mediaService; - private readonly IEntityService _entityService; - private readonly AppCaches _appCaches; + private readonly AppCaches _appCaches; + private readonly IContentService _contentService; + private readonly IEntityService _entityService; + private readonly IMediaService _mediaService; - public UserEditorAuthorizationHelper(IContentService contentService, IMediaService mediaService, IEntityService entityService, AppCaches appCaches) - { - _contentService = contentService; - _mediaService = mediaService; - _entityService = entityService; - _appCaches = appCaches; - } + public UserEditorAuthorizationHelper(IContentService contentService, IMediaService mediaService, + IEntityService entityService, AppCaches appCaches) + { + _contentService = contentService; + _mediaService = mediaService; + _entityService = entityService; + _appCaches = appCaches; + } - /// - /// Checks if the current user has access to save the user data - /// - /// The current user trying to save user data - /// The user instance being saved (can be null if it's a new user) - /// The start content ids of the user being saved (can be null or empty) - /// The start media ids of the user being saved (can be null or empty) - /// The user aliases of the user being saved (can be null or empty) - /// - public Attempt IsAuthorized(IUser? currentUser, - IUser? savingUser, - IEnumerable? startContentIds, IEnumerable? startMediaIds, - IEnumerable? userGroupAliases) - { - var currentIsAdmin = currentUser?.IsAdmin() ?? false; + /// + /// Checks if the current user has access to save the user data + /// + /// The current user trying to save user data + /// The user instance being saved (can be null if it's a new user) + /// The start content ids of the user being saved (can be null or empty) + /// The start media ids of the user being saved (can be null or empty) + /// The user aliases of the user being saved (can be null or empty) + /// + public Attempt IsAuthorized(IUser? currentUser, + IUser? savingUser, + IEnumerable? startContentIds, IEnumerable? startMediaIds, + IEnumerable? userGroupAliases) + { + var currentIsAdmin = currentUser?.IsAdmin() ?? false; - // a) A non-admin cannot save an admin + // a) A non-admin cannot save an admin - if (savingUser != null) + if (savingUser != null) + { + if (savingUser.IsAdmin() && currentIsAdmin == false) { - if (savingUser.IsAdmin() && currentIsAdmin == false) - return Attempt.Fail("The current user is not an administrator so cannot save another administrator"); + return Attempt.Fail("The current user is not an administrator so cannot save another administrator"); } + } - // b) If a start node is changing, a user cannot set a start node on another user that they don't have access to, this even goes for admins - - //only validate any start nodes that have changed. - //a user can remove any start nodes and add start nodes that they have access to - //but they cannot add a start node that they do not have access to - - var changedStartContentIds = savingUser == null - ? startContentIds - : startContentIds == null || savingUser.StartContentIds is null - ? null - : startContentIds.Except(savingUser.StartContentIds).ToArray(); - var changedStartMediaIds = savingUser == null - ? startMediaIds - : startMediaIds == null || savingUser.StartMediaIds is null - ? null - : startMediaIds.Except(savingUser.StartMediaIds).ToArray(); - var pathResult = currentUser is null ? Attempt.Fail() : AuthorizePath(currentUser, changedStartContentIds, changedStartMediaIds); - if (pathResult == false) - return pathResult; + // b) If a start node is changing, a user cannot set a start node on another user that they don't have access to, this even goes for admins + + //only validate any start nodes that have changed. + //a user can remove any start nodes and add start nodes that they have access to + //but they cannot add a start node that they do not have access to + + IEnumerable changedStartContentIds = savingUser == null + ? startContentIds + : startContentIds == null || savingUser.StartContentIds is null + ? null + : startContentIds.Except(savingUser.StartContentIds).ToArray(); + IEnumerable changedStartMediaIds = savingUser == null + ? startMediaIds + : startMediaIds == null || savingUser.StartMediaIds is null + ? null + : startMediaIds.Except(savingUser.StartMediaIds).ToArray(); + Attempt pathResult = currentUser is null + ? Attempt.Fail() + : AuthorizePath(currentUser, changedStartContentIds, changedStartMediaIds); + if (pathResult == false) + { + return pathResult; + } - // c) an admin can manage any group or section access + // c) an admin can manage any group or section access - if (currentIsAdmin) - return Attempt.Succeed(); + if (currentIsAdmin) + { + return Attempt.Succeed(); + } - if (userGroupAliases != null) - { - var savingGroupAliases = userGroupAliases.ToArray(); - var existingGroupAliases = savingUser == null + if (userGroupAliases != null) + { + var savingGroupAliases = userGroupAliases.ToArray(); + var existingGroupAliases = savingUser == null ? new string[0] : savingUser.Groups.Select(x => x.Alias).ToArray(); - var addedGroupAliases = savingGroupAliases.Except(existingGroupAliases); + IEnumerable addedGroupAliases = savingGroupAliases.Except(existingGroupAliases); - // As we know the current user is not admin, it is only allowed to use groups that the user do have themselves. - var savingGroupAliasesNotAllowed = addedGroupAliases.Except(currentUser?.Groups.Select(x=> x.Alias) ?? Enumerable.Empty()).ToArray(); - if (savingGroupAliasesNotAllowed.Any()) - { - return Attempt.Fail("Cannot assign the group(s) '" + string.Join(", ", savingGroupAliasesNotAllowed) + "', the current user is not part of them or admin"); - } + // As we know the current user is not admin, it is only allowed to use groups that the user do have themselves. + var savingGroupAliasesNotAllowed = addedGroupAliases + .Except(currentUser?.Groups.Select(x => x.Alias) ?? Enumerable.Empty()).ToArray(); + if (savingGroupAliasesNotAllowed.Any()) + { + return Attempt.Fail("Cannot assign the group(s) '" + string.Join(", ", savingGroupAliasesNotAllowed) + + "', the current user is not part of them or admin"); + } - //only validate any groups that have changed. - //a non-admin user can remove groups and add groups that they have access to - //but they cannot add a group that they do not have access to or that grants them - //path or section access that they don't have access to. + //only validate any groups that have changed. + //a non-admin user can remove groups and add groups that they have access to + //but they cannot add a group that they do not have access to or that grants them + //path or section access that they don't have access to. - var newGroups = savingUser == null - ? savingGroupAliases - : savingGroupAliases.Except(savingUser.Groups.Select(x => x.Alias)).ToArray(); + var newGroups = savingUser == null + ? savingGroupAliases + : savingGroupAliases.Except(savingUser.Groups.Select(x => x.Alias)).ToArray(); - var userGroupsChanged = savingUser != null && newGroups.Length > 0; + var userGroupsChanged = savingUser != null && newGroups.Length > 0; - if (userGroupsChanged) + if (userGroupsChanged) + { + // d) A user cannot assign a group to another user that they do not belong to + var currentUserGroups = currentUser?.Groups.Select(x => x.Alias).ToArray(); + foreach (var group in newGroups) { - // d) A user cannot assign a group to another user that they do not belong to - var currentUserGroups = currentUser?.Groups.Select(x => x.Alias).ToArray(); - foreach (var group in newGroups) + if (currentUserGroups?.Contains(group) == false) { - if (currentUserGroups?.Contains(group) == false) - { - return Attempt.Fail("Cannot assign the group " + group + ", the current user is not a member"); - } + return Attempt.Fail("Cannot assign the group " + group + ", the current user is not a member"); } } } - - return Attempt.Succeed(); } - private Attempt AuthorizePath(IUser currentUser, IEnumerable? startContentIds, IEnumerable? startMediaIds) + return Attempt.Succeed(); + } + + private Attempt AuthorizePath(IUser currentUser, IEnumerable? startContentIds, + IEnumerable? startMediaIds) + { + if (startContentIds != null) { - if (startContentIds != null) + foreach (var contentId in startContentIds) { - foreach (var contentId in startContentIds) + if (contentId == Constants.System.Root) + { + var hasAccess = ContentPermissions.HasPathAccess("-1", + currentUser.CalculateContentStartNodeIds(_entityService, _appCaches), + Constants.System.RecycleBinContent); + if (hasAccess == false) + { + return Attempt.Fail("The current user does not have access to the content root"); + } + } + else { - if (contentId == Constants.System.Root) + IContent content = _contentService.GetById(contentId); + if (content == null) { - var hasAccess = ContentPermissions.HasPathAccess("-1", currentUser.CalculateContentStartNodeIds(_entityService, _appCaches), Constants.System.RecycleBinContent); - if (hasAccess == false) - return Attempt.Fail("The current user does not have access to the content root"); + continue; } - else + + var hasAccess = currentUser.HasPathAccess(content, _entityService, _appCaches); + if (hasAccess == false) { - var content = _contentService.GetById(contentId); - if (content == null) continue; - var hasAccess = currentUser.HasPathAccess(content, _entityService, _appCaches); - if (hasAccess == false) - return Attempt.Fail("The current user does not have access to the content path " + content.Path); + return Attempt.Fail("The current user does not have access to the content path " + + content.Path); } } } + } - if (startMediaIds != null) + if (startMediaIds != null) + { + foreach (var mediaId in startMediaIds) { - foreach (var mediaId in startMediaIds) + if (mediaId == Constants.System.Root) + { + var hasAccess = ContentPermissions.HasPathAccess("-1", + currentUser.CalculateMediaStartNodeIds(_entityService, _appCaches), + Constants.System.RecycleBinMedia); + if (hasAccess == false) + { + return Attempt.Fail("The current user does not have access to the media root"); + } + } + else { - if (mediaId == Constants.System.Root) + IMedia media = _mediaService.GetById(mediaId); + if (media == null) { - var hasAccess = ContentPermissions.HasPathAccess("-1", currentUser.CalculateMediaStartNodeIds(_entityService, _appCaches), Constants.System.RecycleBinMedia); - if (hasAccess == false) - return Attempt.Fail("The current user does not have access to the media root"); + continue; } - else + + var hasAccess = currentUser.HasPathAccess(media, _entityService, _appCaches); + if (hasAccess == false) { - var media = _mediaService.GetById(mediaId); - if (media == null) continue; - var hasAccess = currentUser.HasPathAccess(media, _entityService, _appCaches); - if (hasAccess == false) - return Attempt.Fail("The current user does not have access to the media path " + media.Path); + return Attempt.Fail("The current user does not have access to the media path " + media.Path); } } } - - return Attempt.Succeed(); } + + return Attempt.Succeed(); } } diff --git a/src/Umbraco.Core/EmbeddedResources/Snippets/MultinodeTree-picker.cshtml b/src/Umbraco.Core/EmbeddedResources/Snippets/MultinodeTree-picker.cshtml index e89f9e7076d8..6edf84dbc72c 100644 --- a/src/Umbraco.Core/EmbeddedResources/Snippets/MultinodeTree-picker.cshtml +++ b/src/Umbraco.Core/EmbeddedResources/Snippets/MultinodeTree-picker.cshtml @@ -1,6 +1,4 @@ @using Umbraco.Cms.Core.Models.PublishedContent -@using Umbraco.Cms.Core.Routing -@using Umbraco.Extensions @inherits Umbraco.Cms.Web.Common.Macros.PartialViewMacroPage @inject IPublishedValueFallback PublishedValueFallback @inject IPublishedUrlProvider PublishedUrlProvider diff --git a/src/Umbraco.Core/Enum.cs b/src/Umbraco.Core/Enum.cs index 9ca1111a3044..cbc54d602bea 100644 --- a/src/Umbraco.Core/Enum.cs +++ b/src/Umbraco.Core/Enum.cs @@ -1,110 +1,102 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; - -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Provides utility methods for handling enumerations. +/// +/// +/// Taken from http://damieng.com/blog/2010/10/17/enums-better-syntax-improved-performance-and-tryparse-in-net-3-5 +/// +public static class Enum + where T : struct { - /// - /// Provides utility methods for handling enumerations. - /// - /// - /// Taken from http://damieng.com/blog/2010/10/17/enums-better-syntax-improved-performance-and-tryparse-in-net-3-5 - /// - public static class Enum - where T : struct + private static readonly List Values; + private static readonly Dictionary InsensitiveNameToValue; + private static readonly Dictionary SensitiveNameToValue; + private static readonly Dictionary IntToValue; + private static readonly Dictionary ValueToName; + + static Enum() { - private static readonly List Values; - private static readonly Dictionary InsensitiveNameToValue; - private static readonly Dictionary SensitiveNameToValue; - private static readonly Dictionary IntToValue; - private static readonly Dictionary ValueToName; + Values = Enum.GetValues(typeof(T)).Cast().ToList(); - static Enum() - { - Values = Enum.GetValues(typeof(T)).Cast().ToList(); - - IntToValue = new Dictionary(); - ValueToName = new Dictionary(); - SensitiveNameToValue = new Dictionary(); - InsensitiveNameToValue = new Dictionary(); - - foreach (var value in Values) - { - var name = value.ToString(); - - IntToValue[Convert.ToInt32(value)] = value; - ValueToName[value] = name!; - SensitiveNameToValue[name!] = value; - InsensitiveNameToValue[name!.ToLowerInvariant()] = value; - } - } + IntToValue = new Dictionary(); + ValueToName = new Dictionary(); + SensitiveNameToValue = new Dictionary(); + InsensitiveNameToValue = new Dictionary(); - public static bool IsDefined(T value) + foreach (T value in Values) { - return ValueToName.Keys.Contains(value); - } + var name = value.ToString(); - public static bool IsDefined(string value) - { - return SensitiveNameToValue.Keys.Contains(value); + IntToValue[Convert.ToInt32(value)] = value; + ValueToName[value] = name!; + SensitiveNameToValue[name!] = value; + InsensitiveNameToValue[name!.ToLowerInvariant()] = value; } + } - public static bool IsDefined(int value) - { - return IntToValue.Keys.Contains(value); - } + public static bool IsDefined(T value) => ValueToName.Keys.Contains(value); - public static IEnumerable GetValues() - { - return Values; - } + public static bool IsDefined(string value) => SensitiveNameToValue.Keys.Contains(value); - public static string[] GetNames() - { - return ValueToName.Values.ToArray(); - } + public static bool IsDefined(int value) => IntToValue.Keys.Contains(value); - public static string? GetName(T value) - { - return ValueToName.TryGetValue(value, out var name) ? name : null; - } + public static IEnumerable GetValues() => Values; - public static T Parse(string value, bool ignoreCase = false) - { - var names = ignoreCase ? InsensitiveNameToValue : SensitiveNameToValue; - if (ignoreCase) value = value.ToLowerInvariant(); + public static string[] GetNames() => ValueToName.Values.ToArray(); - if (names.TryGetValue(value, out var parsed)) - return parsed; + public static string? GetName(T value) => ValueToName.TryGetValue(value, out var name) ? name : null; - throw new ArgumentException($"Value \"{value}\"is not a valid {typeof(T).Name} enumeration value.", nameof(value)); + public static T Parse(string value, bool ignoreCase = false) + { + Dictionary names = ignoreCase ? InsensitiveNameToValue : SensitiveNameToValue; + if (ignoreCase) + { + value = value.ToLowerInvariant(); } - public static bool TryParse(string value, out T returnValue, bool ignoreCase = false) + if (names.TryGetValue(value, out T parsed)) { - var names = ignoreCase ? InsensitiveNameToValue : SensitiveNameToValue; - if (ignoreCase) value = value.ToLowerInvariant(); - return names.TryGetValue(value, out returnValue); + return parsed; } - public static T? ParseOrNull(string value) + throw new ArgumentException($"Value \"{value}\"is not a valid {typeof(T).Name} enumeration value.", + nameof(value)); + } + + public static bool TryParse(string value, out T returnValue, bool ignoreCase = false) + { + Dictionary names = ignoreCase ? InsensitiveNameToValue : SensitiveNameToValue; + if (ignoreCase) { - if (string.IsNullOrWhiteSpace(value)) - return null; + value = value.ToLowerInvariant(); + } - if (InsensitiveNameToValue.TryGetValue(value.ToLowerInvariant(), out var parsed)) - return parsed; + return names.TryGetValue(value, out returnValue); + } + public static T? ParseOrNull(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { return null; } - public static T? CastOrNull(int value) + if (InsensitiveNameToValue.TryGetValue(value.ToLowerInvariant(), out T parsed)) { - if (IntToValue.TryGetValue(value, out var foundValue)) - return foundValue; + return parsed; + } - return null; + return null; + } + + public static T? CastOrNull(int value) + { + if (IntToValue.TryGetValue(value, out T foundValue)) + { + return foundValue; } + + return null; } } diff --git a/src/Umbraco.Core/EnvironmentHelper.cs b/src/Umbraco.Core/EnvironmentHelper.cs index 097ffc962975..04b3bc91ff9e 100644 --- a/src/Umbraco.Core/EnvironmentHelper.cs +++ b/src/Umbraco.Core/EnvironmentHelper.cs @@ -1,17 +1,14 @@ -using System; using Umbraco.Extensions; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Currently just used to get the machine name for use with file names +/// +internal class EnvironmentHelper { /// - /// Currently just used to get the machine name for use with file names + /// Returns the machine name that is safe to use in file paths. /// - internal class EnvironmentHelper - { - /// - /// Returns the machine name that is safe to use in file paths. - /// - public static string FileSafeMachineName => Environment.MachineName.ReplaceNonAlphanumericChars('-'); - - } + public static string FileSafeMachineName => Environment.MachineName.ReplaceNonAlphanumericChars('-'); } diff --git a/src/Umbraco.Core/Events/CancellableEnumerableObjectEventArgs.cs b/src/Umbraco.Core/Events/CancellableEnumerableObjectEventArgs.cs index c9958a5fc9a7..5989444714dd 100644 --- a/src/Umbraco.Core/Events/CancellableEnumerableObjectEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableEnumerableObjectEventArgs.cs @@ -1,59 +1,81 @@ -using System; -using System.Collections.Generic; -using System.Linq; +namespace Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Events +/// +/// Represents event data, for events that support cancellation, and expose impacted objects. +/// +/// The type of the exposed, impacted objects. +public class CancellableEnumerableObjectEventArgs : CancellableObjectEventArgs>, + IEquatable> { - /// - /// Represents event data, for events that support cancellation, and expose impacted objects. - /// - /// The type of the exposed, impacted objects. - public class CancellableEnumerableObjectEventArgs : CancellableObjectEventArgs>, IEquatable> + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel, + EventMessages messages, IDictionary additionalData) + : base(eventObject, canCancel, messages, additionalData) { - public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) - : base(eventObject, canCancel, messages, additionalData) - { } + } + + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel, + EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { + } - public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) - : base(eventObject, canCancel, eventMessages) - { } + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, EventMessages eventMessages) + : base(eventObject, eventMessages) + { + } - public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, EventMessages eventMessages) - : base(eventObject, eventMessages) - { } + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel) + : base(eventObject, canCancel) + { + } - public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel) - : base(eventObject, canCancel) - { } + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject) + : base(eventObject) + { + } - public CancellableEnumerableObjectEventArgs(IEnumerable eventObject) - : base(eventObject) - { } + public bool Equals(CancellableEnumerableObjectEventArgs? other) + { + if (other is null || other.EventObject is null) + { + return false; + } - public bool Equals(CancellableEnumerableObjectEventArgs? other) + if (ReferenceEquals(this, other)) { - if (other is null || other.EventObject is null) return false; - if (ReferenceEquals(this, other)) return true; + return true; + } + + return EventObject?.SequenceEqual(other.EventObject) ?? false; + } - return EventObject?.SequenceEqual(other.EventObject) ?? false; + public override bool Equals(object? obj) + { + if (obj is null) + { + return false; } - public override bool Equals(object? obj) + if (ReferenceEquals(this, obj)) { - if (obj is null) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((CancellableEnumerableObjectEventArgs)obj); + return true; } - public override int GetHashCode() + if (obj.GetType() != GetType()) { - if (EventObject is not null) - { - return HashCodeHelper.GetHashCode(EventObject); - } + return false; + } + + return Equals((CancellableEnumerableObjectEventArgs)obj); + } - return base.GetHashCode(); + public override int GetHashCode() + { + if (EventObject is not null) + { + return HashCodeHelper.GetHashCode(EventObject); } + + return base.GetHashCode(); } } diff --git a/src/Umbraco.Core/Events/CancellableEventArgs.cs b/src/Umbraco.Core/Events/CancellableEventArgs.cs index a991f6532b44..e3f3418ae713 100644 --- a/src/Umbraco.Core/Events/CancellableEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableEventArgs.cs @@ -1,141 +1,162 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +/// +/// Represents event data for events that support cancellation. +/// +public class CancellableEventArgs : EventArgs, IEquatable { - /// - /// Represents event data for events that support cancellation. - /// - public class CancellableEventArgs : EventArgs, IEquatable - { - private bool _cancel; - private IDictionary? _eventState; + private static readonly ReadOnlyDictionary EmptyAdditionalData = + new(new Dictionary()); - private static readonly ReadOnlyDictionary EmptyAdditionalData = new ReadOnlyDictionary(new Dictionary()); + private bool _cancel; + private IDictionary? _eventState; - public CancellableEventArgs(bool canCancel, EventMessages messages, IDictionary additionalData) - { - CanCancel = canCancel; - Messages = messages; - AdditionalData = new ReadOnlyDictionary(additionalData); - } + public CancellableEventArgs(bool canCancel, EventMessages messages, IDictionary additionalData) + { + CanCancel = canCancel; + Messages = messages; + AdditionalData = new ReadOnlyDictionary(additionalData); + } - public CancellableEventArgs(bool canCancel, EventMessages eventMessages) + public CancellableEventArgs(bool canCancel, EventMessages eventMessages) + { + if (eventMessages == null) { - if (eventMessages == null) throw new ArgumentNullException("eventMessages"); - CanCancel = canCancel; - Messages = eventMessages; - AdditionalData = EmptyAdditionalData; + throw new ArgumentNullException("eventMessages"); } - public CancellableEventArgs(bool canCancel) - { - CanCancel = canCancel; - //create a standalone messages - Messages = new EventMessages(); - AdditionalData = EmptyAdditionalData; - } + CanCancel = canCancel; + Messages = eventMessages; + AdditionalData = EmptyAdditionalData; + } + + public CancellableEventArgs(bool canCancel) + { + CanCancel = canCancel; + //create a standalone messages + Messages = new EventMessages(); + AdditionalData = EmptyAdditionalData; + } - public CancellableEventArgs(EventMessages eventMessages) - : this(true, eventMessages) - { } + public CancellableEventArgs(EventMessages eventMessages) + : this(true, eventMessages) + { + } - public CancellableEventArgs() - : this(true) - { } + public CancellableEventArgs() + : this(true) + { + } - /// - /// Flag to determine if this instance will support being cancellable - /// - public bool CanCancel { get; set; } + /// + /// Flag to determine if this instance will support being cancellable + /// + public bool CanCancel { get; set; } - /// - /// If this instance supports cancellation, this gets/sets the cancel value - /// - public bool Cancel + /// + /// If this instance supports cancellation, this gets/sets the cancel value + /// + public bool Cancel + { + get { - get + if (CanCancel == false) { - if (CanCancel == false) - { - throw new InvalidOperationException("This event argument class does not support canceling."); - } - return _cancel; + throw new InvalidOperationException("This event argument class does not support canceling."); } - set + + return _cancel; + } + set + { + if (CanCancel == false) { - if (CanCancel == false) - { - throw new InvalidOperationException("This event argument class does not support canceling."); - } - _cancel = value; + throw new InvalidOperationException("This event argument class does not support canceling."); } - } - /// - /// if this instance supports cancellation, this will set Cancel to true with an affiliated cancellation message - /// - /// - public void CancelOperation(EventMessage cancelationMessage) - { - Cancel = true; - cancelationMessage.IsDefaultEventMessage = true; - Messages.Add(cancelationMessage); + _cancel = value; } + } - /// - /// Returns the EventMessages object which is used to add messages to the message collection for this event - /// - public EventMessages Messages { get; } - - /// - /// In some cases raised evens might need to contain additional arbitrary readonly data which can be read by event subscribers - /// - /// - /// This allows for a bit of flexibility in our event raising - it's not pretty but we need to maintain backwards compatibility - /// so we cannot change the strongly typed nature for some events. - /// - public ReadOnlyDictionary AdditionalData { get; set; } - - /// - /// This can be used by event subscribers to store state in the event args so they easily deal with custom state data between a starting ("ing") - /// event and an ending ("ed") event - /// - public IDictionary EventState - { - get => _eventState ?? (_eventState = new Dictionary()); - set => _eventState = value; - } + /// + /// Returns the EventMessages object which is used to add messages to the message collection for this event + /// + public EventMessages Messages { get; } + + /// + /// In some cases raised evens might need to contain additional arbitrary readonly data which can be read by event + /// subscribers + /// + /// + /// This allows for a bit of flexibility in our event raising - it's not pretty but we need to maintain backwards + /// compatibility + /// so we cannot change the strongly typed nature for some events. + /// + public ReadOnlyDictionary AdditionalData { get; set; } + + /// + /// This can be used by event subscribers to store state in the event args so they easily deal with custom state data + /// between a starting ("ing") + /// event and an ending ("ed") event + /// + public IDictionary EventState + { + get => _eventState ?? (_eventState = new Dictionary()); + set => _eventState = value; + } - public bool Equals(CancellableEventArgs? other) + public bool Equals(CancellableEventArgs? other) + { + if (ReferenceEquals(null, other)) { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(AdditionalData, other.AdditionalData); + return false; } - public override bool Equals(object? obj) + if (ReferenceEquals(this, other)) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != GetType()) return false; - return Equals((CancellableEventArgs) obj); + return true; } - public override int GetHashCode() + return Equals(AdditionalData, other.AdditionalData); + } + + /// + /// if this instance supports cancellation, this will set Cancel to true with an affiliated cancellation message + /// + /// + public void CancelOperation(EventMessage cancelationMessage) + { + Cancel = true; + cancelationMessage.IsDefaultEventMessage = true; + Messages.Add(cancelationMessage); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) { - return AdditionalData != null ? AdditionalData.GetHashCode() : 0; + return false; } - public static bool operator ==(CancellableEventArgs? left, CancellableEventArgs? right) + if (ReferenceEquals(this, obj)) { - return Equals(left, right); + return true; } - public static bool operator !=(CancellableEventArgs left, CancellableEventArgs right) + if (obj.GetType() != GetType()) { - return Equals(left, right) == false; + return false; } + + return Equals((CancellableEventArgs)obj); } + + public override int GetHashCode() => AdditionalData != null ? AdditionalData.GetHashCode() : 0; + + public static bool operator ==(CancellableEventArgs? left, CancellableEventArgs? right) => Equals(left, right); + + public static bool operator !=(CancellableEventArgs left, CancellableEventArgs right) => + Equals(left, right) == false; } diff --git a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs index 2697b773c22f..df0702cd7d19 100644 --- a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs @@ -1,46 +1,39 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Events +/// +/// Provides a base class for classes representing event data, for events that support cancellation, and expose an +/// impacted object. +/// +public abstract class CancellableObjectEventArgs : CancellableEventArgs { - /// - /// Provides a base class for classes representing event data, for events that support cancellation, and expose an impacted object. - /// - public abstract class CancellableObjectEventArgs : CancellableEventArgs - { - protected CancellableObjectEventArgs(object? eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) - : base(canCancel, messages, additionalData) - { - EventObject = eventObject; - } + protected CancellableObjectEventArgs(object? eventObject, bool canCancel, EventMessages messages, + IDictionary additionalData) + : base(canCancel, messages, additionalData) => + EventObject = eventObject; - protected CancellableObjectEventArgs(object? eventObject, bool canCancel, EventMessages eventMessages) - : base(canCancel, eventMessages) - { - EventObject = eventObject; - } + protected CancellableObjectEventArgs(object? eventObject, bool canCancel, EventMessages eventMessages) + : base(canCancel, eventMessages) => + EventObject = eventObject; - protected CancellableObjectEventArgs(object? eventObject, EventMessages eventMessages) - : this(eventObject, true, eventMessages) - { - } - - protected CancellableObjectEventArgs(object? eventObject, bool canCancel) - : base(canCancel) - { - EventObject = eventObject; - } + protected CancellableObjectEventArgs(object? eventObject, EventMessages eventMessages) + : this(eventObject, true, eventMessages) + { + } - protected CancellableObjectEventArgs(object? eventObject) - : this(eventObject, true) - { - } + protected CancellableObjectEventArgs(object? eventObject, bool canCancel) + : base(canCancel) => + EventObject = eventObject; - /// - /// Gets or sets the impacted object. - /// - /// - /// This is protected so that inheritors can expose it with their own name - /// - public object? EventObject { get; set; } + protected CancellableObjectEventArgs(object? eventObject) + : this(eventObject, true) + { } + + /// + /// Gets or sets the impacted object. + /// + /// + /// This is protected so that inheritors can expose it with their own name + /// + public object? EventObject { get; set; } } diff --git a/src/Umbraco.Core/Events/CancellableObjectEventArgsOfTEventObject.cs b/src/Umbraco.Core/Events/CancellableObjectEventArgsOfTEventObject.cs index 939fd8e11be0..20175b101302 100644 --- a/src/Umbraco.Core/Events/CancellableObjectEventArgsOfTEventObject.cs +++ b/src/Umbraco.Core/Events/CancellableObjectEventArgsOfTEventObject.cs @@ -1,87 +1,101 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Events +/// +/// Represent event data, for events that support cancellation, and expose an impacted object. +/// +/// The type of the exposed, impacted object. +public class CancellableObjectEventArgs : CancellableObjectEventArgs, + IEquatable> { + public CancellableObjectEventArgs(TEventObject? eventObject, bool canCancel, EventMessages messages, + IDictionary additionalData) + : base(eventObject, canCancel, messages, additionalData) + { + } + + public CancellableObjectEventArgs(TEventObject? eventObject, bool canCancel, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { + } + + public CancellableObjectEventArgs(TEventObject? eventObject, EventMessages eventMessages) + : base(eventObject, eventMessages) + { + } + + public CancellableObjectEventArgs(TEventObject? eventObject, bool canCancel) + : base(eventObject, canCancel) + { + } + + public CancellableObjectEventArgs(TEventObject? eventObject) + : base(eventObject) + { + } + /// - /// Represent event data, for events that support cancellation, and expose an impacted object. + /// Gets or sets the impacted object. /// - /// The type of the exposed, impacted object. - public class CancellableObjectEventArgs : CancellableObjectEventArgs, IEquatable> + /// + /// This is protected so that inheritors can expose it with their own name + /// + protected new TEventObject? EventObject { - public CancellableObjectEventArgs(TEventObject? eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) - : base(eventObject, canCancel, messages, additionalData) - { - } + get => (TEventObject?)base.EventObject; + set => base.EventObject = value; + } - public CancellableObjectEventArgs(TEventObject? eventObject, bool canCancel, EventMessages eventMessages) - : base(eventObject, canCancel, eventMessages) + public bool Equals(CancellableObjectEventArgs? other) + { + if (other is null) { + return false; } - public CancellableObjectEventArgs(TEventObject? eventObject, EventMessages eventMessages) - : base(eventObject, eventMessages) + if (ReferenceEquals(this, other)) { + return true; } - public CancellableObjectEventArgs(TEventObject? eventObject, bool canCancel) - : base(eventObject, canCancel) - { - } + return base.Equals(other) && EqualityComparer.Default.Equals(EventObject, other.EventObject); + } - public CancellableObjectEventArgs(TEventObject? eventObject) - : base(eventObject) + public override bool Equals(object? obj) + { + if (obj is null) { + return false; } - /// - /// Gets or sets the impacted object. - /// - /// - /// This is protected so that inheritors can expose it with their own name - /// - protected new TEventObject? EventObject + if (ReferenceEquals(this, obj)) { - get => (TEventObject?) base.EventObject; - set => base.EventObject = value; + return true; } - public bool Equals(CancellableObjectEventArgs? other) + if (obj.GetType() != GetType()) { - if (other is null) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && EqualityComparer.Default.Equals(EventObject, other.EventObject); + return false; } - public override bool Equals(object? obj) - { - if (obj is null) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((CancellableObjectEventArgs)obj); - } + return Equals((CancellableObjectEventArgs)obj); + } - public override int GetHashCode() + public override int GetHashCode() + { + unchecked { - unchecked + if (EventObject is not null) { - if (EventObject is not null) - { - return (base.GetHashCode() * 397) ^ EqualityComparer.Default.GetHashCode(EventObject); - } - - return base.GetHashCode() * 397; + return (base.GetHashCode() * 397) ^ EqualityComparer.Default.GetHashCode(EventObject); } - } - - public static bool operator ==(CancellableObjectEventArgs left, CancellableObjectEventArgs right) - { - return Equals(left, right); - } - public static bool operator !=(CancellableObjectEventArgs left, CancellableObjectEventArgs right) - { - return !Equals(left, right); + return base.GetHashCode() * 397; } } + + public static bool operator ==(CancellableObjectEventArgs left, + CancellableObjectEventArgs right) => Equals(left, right); + + public static bool operator !=(CancellableObjectEventArgs left, + CancellableObjectEventArgs right) => !Equals(left, right); } diff --git a/src/Umbraco.Core/Events/ContentCacheEventArgs.cs b/src/Umbraco.Core/Events/ContentCacheEventArgs.cs index 78f714f75439..5509ddd694be 100644 --- a/src/Umbraco.Core/Events/ContentCacheEventArgs.cs +++ b/src/Umbraco.Core/Events/ContentCacheEventArgs.cs @@ -1,4 +1,7 @@ -namespace Umbraco.Cms.Core.Events +using System.ComponentModel; + +namespace Umbraco.Cms.Core.Events; + +public class ContentCacheEventArgs : CancelEventArgs { - public class ContentCacheEventArgs : System.ComponentModel.CancelEventArgs { } } diff --git a/src/Umbraco.Core/Events/CopyEventArgs.cs b/src/Umbraco.Core/Events/CopyEventArgs.cs index 6a4969710a76..c5f480357046 100644 --- a/src/Umbraco.Core/Events/CopyEventArgs.cs +++ b/src/Umbraco.Core/Events/CopyEventArgs.cs @@ -1,91 +1,99 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Events +public class CopyEventArgs : CancellableObjectEventArgs, IEquatable> { - public class CopyEventArgs : CancellableObjectEventArgs, IEquatable> + public CopyEventArgs(TEntity original, TEntity copy, bool canCancel, int parentId) + : base(original, canCancel) { - public CopyEventArgs(TEntity original, TEntity copy, bool canCancel, int parentId) - : base(original, canCancel) - { - Copy = copy; - ParentId = parentId; - } + Copy = copy; + ParentId = parentId; + } + + public CopyEventArgs(TEntity eventObject, TEntity copy, int parentId) + : base(eventObject) + { + Copy = copy; + ParentId = parentId; + } + + public CopyEventArgs(TEntity eventObject, TEntity copy, bool canCancel, int parentId, bool relateToOriginal) + : base(eventObject, canCancel) + { + Copy = copy; + ParentId = parentId; + RelateToOriginal = relateToOriginal; + } + + /// + /// The copied entity + /// + public TEntity Copy { get; set; } + + /// + /// The original entity + /// + public TEntity? Original => EventObject; - public CopyEventArgs(TEntity eventObject, TEntity copy, int parentId) - : base(eventObject) + /// + /// Gets or Sets the Id of the objects new parent. + /// + public int ParentId { get; } + + public bool RelateToOriginal { get; set; } + + public bool Equals(CopyEventArgs? other) + { + if (ReferenceEquals(null, other)) { - Copy = copy; - ParentId = parentId; + return false; } - public CopyEventArgs(TEntity eventObject, TEntity copy, bool canCancel, int parentId, bool relateToOriginal) - : base(eventObject, canCancel) + if (ReferenceEquals(this, other)) { - Copy = copy; - ParentId = parentId; - RelateToOriginal = relateToOriginal; + return true; } - /// - /// The copied entity - /// - public TEntity Copy { get; set; } + return base.Equals(other) && EqualityComparer.Default.Equals(Copy, other.Copy) && + ParentId == other.ParentId && RelateToOriginal == other.RelateToOriginal; + } - /// - /// The original entity - /// - public TEntity? Original + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) { - get { return EventObject; } + return false; } - /// - /// Gets or Sets the Id of the objects new parent. - /// - public int ParentId { get; private set; } - - public bool RelateToOriginal { get; set; } - - public bool Equals(CopyEventArgs? other) + if (ReferenceEquals(this, obj)) { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && EqualityComparer.Default.Equals(Copy, other.Copy) && ParentId == other.ParentId && RelateToOriginal == other.RelateToOriginal; + return true; } - public override bool Equals(object? obj) + if (obj.GetType() != GetType()) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((CopyEventArgs) obj); + return false; } - public override int GetHashCode() + return Equals((CopyEventArgs)obj); + } + + public override int GetHashCode() + { + unchecked { - unchecked + var hashCode = base.GetHashCode(); + if (Copy is not null) { - int hashCode = base.GetHashCode(); - if (Copy is not null) - { - hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(Copy); - } - - hashCode = (hashCode * 397) ^ ParentId; - hashCode = (hashCode * 397) ^ RelateToOriginal.GetHashCode(); - return hashCode; + hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(Copy); } - } - public static bool operator ==(CopyEventArgs left, CopyEventArgs right) - { - return Equals(left, right); - } - - public static bool operator !=(CopyEventArgs left, CopyEventArgs right) - { - return !Equals(left, right); + hashCode = (hashCode * 397) ^ ParentId; + hashCode = (hashCode * 397) ^ RelateToOriginal.GetHashCode(); + return hashCode; } } + + public static bool operator ==(CopyEventArgs left, CopyEventArgs right) => Equals(left, right); + + public static bool operator !=(CopyEventArgs left, CopyEventArgs right) => !Equals(left, right); } diff --git a/src/Umbraco.Core/Events/DeleteEventArgs.cs b/src/Umbraco.Core/Events/DeleteEventArgs.cs index 1696e07ec66f..a820227c6049 100644 --- a/src/Umbraco.Core/Events/DeleteEventArgs.cs +++ b/src/Umbraco.Core/Events/DeleteEventArgs.cs @@ -1,202 +1,205 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +[SupersedeEvent(typeof(SaveEventArgs<>))] +[SupersedeEvent(typeof(PublishEventArgs<>))] +[SupersedeEvent(typeof(MoveEventArgs<>))] +[SupersedeEvent(typeof(CopyEventArgs<>))] +public class DeleteEventArgs : CancellableEnumerableObjectEventArgs, + IEquatable>, IDeletingMediaFilesEventArgs { - [SupersedeEvent(typeof(SaveEventArgs<>))] - [SupersedeEvent(typeof(PublishEventArgs<>))] - [SupersedeEvent(typeof(MoveEventArgs<>))] - [SupersedeEvent(typeof(CopyEventArgs<>))] - public class DeleteEventArgs : CancellableEnumerableObjectEventArgs, IEquatable>, IDeletingMediaFilesEventArgs + /// + /// Constructor accepting multiple entities that are used in the delete operation + /// + /// + /// + /// + public DeleteEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) : + base(eventObject, canCancel, eventMessages) => MediaFilesToDelete = new List(); + + /// + /// Constructor accepting multiple entities that are used in the delete operation + /// + /// + /// + public DeleteEventArgs(IEnumerable eventObject, EventMessages eventMessages) : base(eventObject, + eventMessages) => MediaFilesToDelete = new List(); + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + public DeleteEventArgs(TEntity eventObject, EventMessages eventMessages) + : base(new List {eventObject}, eventMessages) => + MediaFilesToDelete = new List(); + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + /// + public DeleteEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) + : base(new List {eventObject}, canCancel, eventMessages) => + MediaFilesToDelete = new List(); + + /// + /// Constructor accepting multiple entities that are used in the delete operation + /// + /// + /// + public DeleteEventArgs(IEnumerable eventObject, bool canCancel) : base(eventObject, canCancel) => + MediaFilesToDelete = new List(); + + /// + /// Constructor accepting multiple entities that are used in the delete operation + /// + /// + public DeleteEventArgs(IEnumerable eventObject) : base(eventObject) => + MediaFilesToDelete = new List(); + + /// + /// Constructor accepting a single entity instance + /// + /// + public DeleteEventArgs(TEntity eventObject) + : base(new List {eventObject}) => + MediaFilesToDelete = new List(); + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + public DeleteEventArgs(TEntity eventObject, bool canCancel) + : base(new List {eventObject}, canCancel) => + MediaFilesToDelete = new List(); + + /// + /// Returns all entities that were deleted during the operation + /// + public IEnumerable DeletedEntities { - /// - /// Constructor accepting multiple entities that are used in the delete operation - /// - /// - /// - /// - public DeleteEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) : base(eventObject, canCancel, eventMessages) - { - MediaFilesToDelete = new List(); - } + get => EventObject ?? Enumerable.Empty(); + set => EventObject = value; + } - /// - /// Constructor accepting multiple entities that are used in the delete operation - /// - /// - /// - public DeleteEventArgs(IEnumerable eventObject, EventMessages eventMessages) : base(eventObject, eventMessages) - { - MediaFilesToDelete = new List(); - } + /// + /// A list of media files that can be added to during a deleted operation for which Umbraco will ensure are removed + /// + public List MediaFilesToDelete { get; } - /// - /// Constructor accepting a single entity instance - /// - /// - /// - public DeleteEventArgs(TEntity eventObject, EventMessages eventMessages) - : base(new List { eventObject }, eventMessages) + public bool Equals(DeleteEventArgs? other) + { + if (ReferenceEquals(null, other)) { - MediaFilesToDelete = new List(); + return false; } - /// - /// Constructor accepting a single entity instance - /// - /// - /// - /// - public DeleteEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) - : base(new List { eventObject }, canCancel, eventMessages) + if (ReferenceEquals(this, other)) { - MediaFilesToDelete = new List(); + return true; } - /// - /// Constructor accepting multiple entities that are used in the delete operation - /// - /// - /// - public DeleteEventArgs(IEnumerable eventObject, bool canCancel) : base(eventObject, canCancel) - { - MediaFilesToDelete = new List(); - } + return base.Equals(other) && MediaFilesToDelete.SequenceEqual(other.MediaFilesToDelete); + } - /// - /// Constructor accepting multiple entities that are used in the delete operation - /// - /// - public DeleteEventArgs(IEnumerable eventObject) : base(eventObject) + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) { - MediaFilesToDelete = new List(); + return false; } - /// - /// Constructor accepting a single entity instance - /// - /// - public DeleteEventArgs(TEntity eventObject) - : base(new List { eventObject }) + if (ReferenceEquals(this, obj)) { - MediaFilesToDelete = new List(); + return true; } - /// - /// Constructor accepting a single entity instance - /// - /// - /// - public DeleteEventArgs(TEntity eventObject, bool canCancel) - : base(new List { eventObject }, canCancel) + if (obj.GetType() != GetType()) { - MediaFilesToDelete = new List(); + return false; } - /// - /// Returns all entities that were deleted during the operation - /// - public IEnumerable DeletedEntities + return Equals((DeleteEventArgs)obj); + } + + public override int GetHashCode() + { + unchecked { - get => EventObject ?? Enumerable.Empty(); - set => EventObject = value; + return (base.GetHashCode() * 397) ^ MediaFilesToDelete.GetHashCode(); } + } - /// - /// A list of media files that can be added to during a deleted operation for which Umbraco will ensure are removed - /// - public List MediaFilesToDelete { get; private set; } + public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) => + Equals(left, right); - public bool Equals(DeleteEventArgs? other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && MediaFilesToDelete.SequenceEqual(other.MediaFilesToDelete); - } + public static bool operator !=(DeleteEventArgs left, DeleteEventArgs right) => + !Equals(left, right); +} - public override bool Equals(object? obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((DeleteEventArgs) obj); - } +public class DeleteEventArgs : CancellableEventArgs, IEquatable +{ + public DeleteEventArgs(int id, bool canCancel, EventMessages eventMessages) + : base(canCancel, eventMessages) => + Id = id; - public override int GetHashCode() - { - unchecked - { - return (base.GetHashCode() * 397) ^ MediaFilesToDelete.GetHashCode(); - } - } + public DeleteEventArgs(int id, bool canCancel) + : base(canCancel) => + Id = id; - public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) - { - return Equals(left, right); - } + public DeleteEventArgs(int id) => Id = id; - public static bool operator !=(DeleteEventArgs left, DeleteEventArgs right) - { - return !Equals(left, right); - } - } + /// + /// Gets the Id of the object being deleted. + /// + public int Id { get; } - public class DeleteEventArgs : CancellableEventArgs, IEquatable + public bool Equals(DeleteEventArgs? other) { - public DeleteEventArgs(int id, bool canCancel, EventMessages eventMessages) - : base(canCancel, eventMessages) + if (ReferenceEquals(null, other)) { - Id = id; + return false; } - public DeleteEventArgs(int id, bool canCancel) - : base(canCancel) + if (ReferenceEquals(this, other)) { - Id = id; + return true; } - public DeleteEventArgs(int id) - { - Id = id; - } - - /// - /// Gets the Id of the object being deleted. - /// - public int Id { get; private set; } + return base.Equals(other) && Id == other.Id; + } - public bool Equals(DeleteEventArgs? other) + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && Id == other.Id; + return false; } - public override bool Equals(object? obj) + if (ReferenceEquals(this, obj)) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((DeleteEventArgs) obj); + return true; } - public override int GetHashCode() + if (obj.GetType() != GetType()) { - unchecked - { - return (base.GetHashCode() * 397) ^ Id; - } + return false; } - public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) - { - return Equals(left, right); - } + return Equals((DeleteEventArgs)obj); + } - public static bool operator !=(DeleteEventArgs left, DeleteEventArgs right) + public override int GetHashCode() + { + unchecked { - return !Equals(left, right); + return (base.GetHashCode() * 397) ^ Id; } } + + public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) => Equals(left, right); + + public static bool operator !=(DeleteEventArgs left, DeleteEventArgs right) => !Equals(left, right); } diff --git a/src/Umbraco.Core/Events/EventAggregator.Notifications.cs b/src/Umbraco.Core/Events/EventAggregator.Notifications.cs index e27c155ec4a1..d0a7d9957001 100644 --- a/src/Umbraco.Core/Events/EventAggregator.Notifications.cs +++ b/src/Umbraco.Core/Events/EventAggregator.Notifications.cs @@ -1,183 +1,188 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; -using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Events -{ - /// - /// Contains types and methods that allow publishing general notifications. - /// - public partial class EventAggregator : IEventAggregator - { - private static readonly ConcurrentDictionary s_notificationAsyncHandlers - = new ConcurrentDictionary(); - - private static readonly ConcurrentDictionary s_notificationHandlers - = new ConcurrentDictionary(); +namespace Umbraco.Cms.Core.Events; - private Task PublishNotificationAsync(INotification notification, CancellationToken cancellationToken = default) - { - Type notificationType = notification.GetType(); - NotificationAsyncHandlerWrapper asyncHandler = s_notificationAsyncHandlers.GetOrAdd( - notificationType, - t => - { - var value = Activator.CreateInstance( - typeof(NotificationAsyncHandlerWrapperImpl<>).MakeGenericType(notificationType)); - return value is not null - ? (NotificationAsyncHandlerWrapper)value - : throw new InvalidCastException("Activator could not create instance of NotificationHandler"); - }); - - return asyncHandler.HandleAsync(notification, cancellationToken, _serviceFactory, PublishCoreAsync); - } +/// +/// Contains types and methods that allow publishing general notifications. +/// +public partial class EventAggregator : IEventAggregator +{ + private static readonly ConcurrentDictionary s_notificationAsyncHandlers + = new(); - private void PublishNotification(INotification notification) - { - Type notificationType = notification.GetType(); - NotificationHandlerWrapper? asyncHandler = s_notificationHandlers.GetOrAdd( - notificationType, - t => - { - var value = Activator.CreateInstance( - typeof(NotificationHandlerWrapperImpl<>).MakeGenericType(notificationType)); - return value is not null ? (NotificationHandlerWrapper)value : throw new InvalidCastException("Activator could not create instance of NotificationHandler"); - }); - - asyncHandler?.Handle(notification, _serviceFactory, PublishCore); - } + private static readonly ConcurrentDictionary s_notificationHandlers = new(); - private async Task PublishCoreAsync( - IEnumerable> allHandlers, - INotification notification, - CancellationToken cancellationToken) - { - foreach (Func handler in allHandlers) - { - await handler(notification, cancellationToken).ConfigureAwait(false); - } - } - - private void PublishCore( - IEnumerable> allHandlers, - INotification notification) - { - foreach (Action handler in allHandlers) + private Task PublishNotificationAsync(INotification notification, CancellationToken cancellationToken = default) + { + Type notificationType = notification.GetType(); + NotificationAsyncHandlerWrapper asyncHandler = s_notificationAsyncHandlers.GetOrAdd( + notificationType, + t => { - handler(notification); - } - } + var value = Activator.CreateInstance( + typeof(NotificationAsyncHandlerWrapperImpl<>).MakeGenericType(notificationType)); + return value is not null + ? (NotificationAsyncHandlerWrapper)value + : throw new InvalidCastException("Activator could not create instance of NotificationHandler"); + }); + + return asyncHandler.HandleAsync(notification, cancellationToken, _serviceFactory, PublishCoreAsync); } - internal abstract class NotificationHandlerWrapper + private void PublishNotification(INotification notification) { - public abstract void Handle( - INotification notification, - ServiceFactory serviceFactory, - Action>, INotification> publish); + Type notificationType = notification.GetType(); + NotificationHandlerWrapper? asyncHandler = s_notificationHandlers.GetOrAdd( + notificationType, + t => + { + var value = Activator.CreateInstance( + typeof(NotificationHandlerWrapperImpl<>).MakeGenericType(notificationType)); + return value is not null + ? (NotificationHandlerWrapper)value + : throw new InvalidCastException("Activator could not create instance of NotificationHandler"); + }); + + asyncHandler?.Handle(notification, _serviceFactory, PublishCore); } - internal abstract class NotificationAsyncHandlerWrapper + private async Task PublishCoreAsync( + IEnumerable> allHandlers, + INotification notification, + CancellationToken cancellationToken) { - public abstract Task HandleAsync( - INotification notification, - CancellationToken cancellationToken, - ServiceFactory serviceFactory, - Func>, INotification, CancellationToken, Task> publish); + foreach (Func handler in allHandlers) + { + await handler(notification, cancellationToken).ConfigureAwait(false); + } } - internal class NotificationAsyncHandlerWrapperImpl : NotificationAsyncHandlerWrapper - where TNotification : INotification + private void PublishCore( + IEnumerable> allHandlers, + INotification notification) { - /// - /// - /// Background - During v9 build we wanted an in-process message bus to facilitate removal of the old static event handlers.
- /// Instead of taking a dependency on MediatR we (the community) implemented our own using MediatR as inspiration. - ///
- /// - /// - /// Some things worth knowing about MediatR. - /// - /// All handlers are by default registered with transient lifetime, but can easily depend on services with state. - /// Both the Mediatr instance and its handler resolver are registered transient and as such it is always possible to depend on scoped services in a handler. - /// - /// - /// - /// - /// Our EventAggregator started out registered with a transient lifetime but later (before initial release) the registration was changed to singleton, presumably - /// because there are a lot of singleton services in Umbraco which like to publish notifications and it's a pain to use scoped services from a singleton. - ///
- /// The problem with a singleton EventAggregator is it forces handlers to create a service scope and service locate any scoped services - /// they wish to make use of e.g. a unit of work (think entity framework DBContext). - ///
- /// - /// - /// Moving forwards it probably makes more sense to register EventAggregator transient but doing so now would mean an awful lot of service location to avoid breaking changes. - ///
- /// For now we can do the next best thing which is to create a scope for each published notification, thus enabling the transient handlers to take a dependency on a scoped service. - ///
- /// - /// - /// Did discuss using HttpContextAccessor/IScopedServiceProvider to enable sharing of scopes when publisher has http context, - /// but decided against because it's inconsistent with what happens in background threads and will just cause confusion. - /// - ///
- public override Task HandleAsync( - INotification notification, - CancellationToken cancellationToken, - ServiceFactory serviceFactory, - Func>, INotification, CancellationToken, Task> publish) + foreach (Action handler in allHandlers) { - // Create a new service scope from which to resolve handlers and ensure it's disposed when it goes out of scope. - // TODO: go back to using ServiceFactory to resolve - IServiceScopeFactory scopeFactory = serviceFactory.GetInstance(); - using IServiceScope scope = scopeFactory.CreateScope(); - IServiceProvider container = scope.ServiceProvider; - - IEnumerable> handlers = container - .GetServices>() - .Select(x => new Func( - (theNotification, theToken) => - x.HandleAsync((TNotification)theNotification, theToken))); - - return publish(handlers, notification, cancellationToken); + handler(notification); } } +} + +internal abstract class NotificationHandlerWrapper +{ + public abstract void Handle( + INotification notification, + ServiceFactory serviceFactory, + Action>, INotification> publish); +} + +internal abstract class NotificationAsyncHandlerWrapper +{ + public abstract Task HandleAsync( + INotification notification, + CancellationToken cancellationToken, + ServiceFactory serviceFactory, + Func>, INotification, CancellationToken, Task> + publish); +} + +internal class NotificationAsyncHandlerWrapperImpl : NotificationAsyncHandlerWrapper + where TNotification : INotification +{ + /// + /// + /// Background - During v9 build we wanted an in-process message bus to facilitate removal of the old static event + /// handlers.
+ /// Instead of taking a dependency on MediatR we (the community) implemented our own using MediatR as inspiration. + ///
+ /// + /// Some things worth knowing about MediatR. + /// + /// + /// All handlers are by default registered with transient lifetime, but can easily depend on services + /// with state. + /// + /// + /// Both the Mediatr instance and its handler resolver are registered transient and as such it is always + /// possible to depend on scoped services in a handler. + /// + /// + /// + /// + /// Our EventAggregator started out registered with a transient lifetime but later (before initial release) the + /// registration was changed to singleton, presumably + /// because there are a lot of singleton services in Umbraco which like to publish notifications and it's a pain to + /// use scoped services from a singleton. + ///
+ /// The problem with a singleton EventAggregator is it forces handlers to create a service scope and service locate + /// any scoped services + /// they wish to make use of e.g. a unit of work (think entity framework DBContext). + ///
+ /// + /// Moving forwards it probably makes more sense to register EventAggregator transient but doing so now would mean + /// an awful lot of service location to avoid breaking changes. + ///
+ /// For now we can do the next best thing which is to create a scope for each published notification, thus enabling + /// the transient handlers to take a dependency on a scoped service. + ///
+ /// + /// Did discuss using HttpContextAccessor/IScopedServiceProvider to enable sharing of scopes when publisher has + /// http context, + /// but decided against because it's inconsistent with what happens in background threads and will just cause + /// confusion. + /// + ///
+ public override Task HandleAsync( + INotification notification, + CancellationToken cancellationToken, + ServiceFactory serviceFactory, + Func>, INotification, CancellationToken, Task> publish) + { + // Create a new service scope from which to resolve handlers and ensure it's disposed when it goes out of scope. + // TODO: go back to using ServiceFactory to resolve + IServiceScopeFactory scopeFactory = serviceFactory.GetInstance(); + using IServiceScope scope = scopeFactory.CreateScope(); + IServiceProvider container = scope.ServiceProvider; + + IEnumerable> handlers = container + .GetServices>() + .Select(x => new Func( + (theNotification, theToken) => + x.HandleAsync((TNotification)theNotification, theToken))); + + return publish(handlers, notification, cancellationToken); + } +} - internal class NotificationHandlerWrapperImpl : NotificationHandlerWrapper - where TNotification : INotification +internal class NotificationHandlerWrapperImpl : NotificationHandlerWrapper + where TNotification : INotification +{ + /// + /// See remarks on for explanation on + /// what's going on with the IServiceProvider stuff here. + /// + public override void Handle( + INotification notification, + ServiceFactory serviceFactory, + Action>, INotification> publish) { - /// - /// See remarks on for explanation on - /// what's going on with the IServiceProvider stuff here. - /// - public override void Handle( - INotification notification, - ServiceFactory serviceFactory, - Action>, INotification> publish) - { - // Create a new service scope from which to resolve handlers and ensure it's disposed when it goes out of scope. - // TODO: go back to using ServiceFactory to resolve - IServiceScopeFactory scopeFactory = serviceFactory.GetInstance(); - using IServiceScope scope = scopeFactory.CreateScope(); - IServiceProvider container = scope.ServiceProvider; - - IEnumerable> handlers = container - .GetServices>() - .Select(x => new Action( - (theNotification) => - x.Handle((TNotification)theNotification))); - - publish(handlers, notification); - } + // Create a new service scope from which to resolve handlers and ensure it's disposed when it goes out of scope. + // TODO: go back to using ServiceFactory to resolve + IServiceScopeFactory scopeFactory = serviceFactory.GetInstance(); + using IServiceScope scope = scopeFactory.CreateScope(); + IServiceProvider container = scope.ServiceProvider; + + IEnumerable> handlers = container + .GetServices>() + .Select(x => new Action( + theNotification => + x.Handle((TNotification)theNotification))); + + publish(handlers, notification); } } diff --git a/src/Umbraco.Core/Events/EventAggregator.cs b/src/Umbraco.Core/Events/EventAggregator.cs index 5bf54b516a50..82c9b49dfe01 100644 --- a/src/Umbraco.Core/Events/EventAggregator.cs +++ b/src/Umbraco.Core/Events/EventAggregator.cs @@ -1,117 +1,112 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; using Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +/// +/// A factory method used to resolve all services. +/// For multiple instances, it will resolve against . +/// +/// Type of service to resolve. +/// An instance of type . +public delegate object ServiceFactory(Type serviceType); + +/// +public partial class EventAggregator : IEventAggregator { + private readonly ServiceFactory _serviceFactory; + /// - /// A factory method used to resolve all services. - /// For multiple instances, it will resolve against . + /// Initializes a new instance of the class. /// - /// Type of service to resolve. - /// An instance of type . - public delegate object ServiceFactory(Type serviceType); + /// The service instance factory. + public EventAggregator(ServiceFactory serviceFactory) + => _serviceFactory = serviceFactory; + + /// + public Task PublishAsync(TNotification notification, CancellationToken cancellationToken = default) + where TNotification : INotification + { + // TODO: Introduce codegen efficient Guard classes to reduce noise. + if (notification == null) + { + throw new ArgumentNullException(nameof(notification)); + } + + PublishNotification(notification); + return PublishNotificationAsync(notification, cancellationToken); + } - /// - public partial class EventAggregator : IEventAggregator + /// + public void Publish(TNotification notification) + where TNotification : INotification { - private readonly ServiceFactory _serviceFactory; - - /// - /// Initializes a new instance of the class. - /// - /// The service instance factory. - public EventAggregator(ServiceFactory serviceFactory) - => _serviceFactory = serviceFactory; - - /// - public Task PublishAsync(TNotification notification, CancellationToken cancellationToken = default) - where TNotification : INotification + // TODO: Introduce codegen efficient Guard classes to reduce noise. + if (notification == null) { - // TODO: Introduce codegen efficient Guard classes to reduce noise. - if (notification == null) - { - throw new ArgumentNullException(nameof(notification)); - } - - PublishNotification(notification); - return PublishNotificationAsync(notification, cancellationToken); + throw new ArgumentNullException(nameof(notification)); } - /// - public void Publish(TNotification notification) - where TNotification : INotification + PublishNotification(notification); + Task task = PublishNotificationAsync(notification); + if (task is not null) { - // TODO: Introduce codegen efficient Guard classes to reduce noise. - if (notification == null) - { - throw new ArgumentNullException(nameof(notification)); - } - - PublishNotification(notification); - var task = PublishNotificationAsync(notification); - if (task is not null) - { - Task.WaitAll(task); - } + Task.WaitAll(task); } + } - public bool PublishCancelable(TCancelableNotification notification) - where TCancelableNotification : ICancelableNotification + public bool PublishCancelable(TCancelableNotification notification) + where TCancelableNotification : ICancelableNotification + { + if (notification == null) { - if (notification == null) - { - throw new ArgumentNullException(nameof(notification)); - } + throw new ArgumentNullException(nameof(notification)); + } - Publish(notification); - return notification.Cancel; + Publish(notification); + return notification.Cancel; + } + + public async Task PublishCancelableAsync(TCancelableNotification notification) + where TCancelableNotification : ICancelableNotification + { + if (notification is null) + { + throw new ArgumentNullException(nameof(notification)); } - public async Task PublishCancelableAsync(TCancelableNotification notification) - where TCancelableNotification : ICancelableNotification + Task? task = PublishAsync(notification); + if (task is not null) { - if (notification is null) - { - throw new ArgumentNullException(nameof(notification)); - } - - Task? task = PublishAsync(notification); - if (task is not null) - { - await task; - } - - return notification.Cancel; + await task; } + + return notification.Cancel; } +} +/// +/// Extensions for . +/// +public static class ServiceFactoryExtensions +{ /// - /// Extensions for . + /// Gets an instance of . /// - public static class ServiceFactoryExtensions - { - /// - /// Gets an instance of . - /// - /// The type to return. - /// The service factory. - /// The new instance. - public static T GetInstance(this ServiceFactory factory) - => (T)factory(typeof(T)); - - /// - /// Gets a collection of instances of . - /// - /// The collection item type to return. - /// The service factory. - /// The new instance collection. - public static IEnumerable GetInstances(this ServiceFactory factory) - => (IEnumerable)factory(typeof(IEnumerable)); - } + /// The type to return. + /// The service factory. + /// The new instance. + public static T GetInstance(this ServiceFactory factory) + => (T)factory(typeof(T)); + + /// + /// Gets a collection of instances of . + /// + /// The collection item type to return. + /// The service factory. + /// The new instance collection. + public static IEnumerable GetInstances(this ServiceFactory factory) + => (IEnumerable)factory(typeof(IEnumerable)); } diff --git a/src/Umbraco.Core/Events/EventDefinition.cs b/src/Umbraco.Core/Events/EventDefinition.cs index aa6f2899cd63..ec39fc31673e 100644 --- a/src/Umbraco.Core/Events/EventDefinition.cs +++ b/src/Umbraco.Core/Events/EventDefinition.cs @@ -1,73 +1,72 @@ -using System; +namespace Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Events +public class EventDefinition : EventDefinitionBase { - public class EventDefinition : EventDefinitionBase - { - private readonly EventHandler _trackedEvent; - private readonly object _sender; - private readonly EventArgs _args; + private readonly EventArgs _args; + private readonly object _sender; + private readonly EventHandler _trackedEvent; - public EventDefinition(EventHandler trackedEvent, object sender, EventArgs args, string? eventName = null) - : base(sender, args, eventName) - { - _trackedEvent = trackedEvent; - _sender = sender; - _args = args; - } + public EventDefinition(EventHandler trackedEvent, object sender, EventArgs args, string? eventName = null) + : base(sender, args, eventName) + { + _trackedEvent = trackedEvent; + _sender = sender; + _args = args; + } - public override void RaiseEvent() + public override void RaiseEvent() + { + if (_trackedEvent != null) { - if (_trackedEvent != null) - { - _trackedEvent(_sender, _args); - } + _trackedEvent(_sender, _args); } } +} - public class EventDefinition : EventDefinitionBase - { - private readonly EventHandler _trackedEvent; - private readonly object _sender; - private readonly TEventArgs _args; +public class EventDefinition : EventDefinitionBase +{ + private readonly TEventArgs _args; + private readonly object _sender; + private readonly EventHandler _trackedEvent; - public EventDefinition(EventHandler trackedEvent, object sender, TEventArgs args, string? eventName = null) - : base(sender, args, eventName) - { - _trackedEvent = trackedEvent; - _sender = sender; - _args = args; - } + public EventDefinition(EventHandler trackedEvent, object sender, TEventArgs args, + string? eventName = null) + : base(sender, args, eventName) + { + _trackedEvent = trackedEvent; + _sender = sender; + _args = args; + } - public override void RaiseEvent() + public override void RaiseEvent() + { + if (_trackedEvent != null) { - if (_trackedEvent != null) - { - _trackedEvent(_sender, _args); - } + _trackedEvent(_sender, _args); } } +} - public class EventDefinition : EventDefinitionBase - { - private readonly TypedEventHandler _trackedEvent; - private readonly TSender _sender; - private readonly TEventArgs _args; +public class EventDefinition : EventDefinitionBase +{ + private readonly TEventArgs _args; + private readonly TSender _sender; + private readonly TypedEventHandler _trackedEvent; - public EventDefinition(TypedEventHandler trackedEvent, TSender sender, TEventArgs args, string? eventName = null) - : base(sender, args, eventName) - { - _trackedEvent = trackedEvent; - _sender = sender; - _args = args; - } + public EventDefinition(TypedEventHandler trackedEvent, TSender sender, TEventArgs args, + string? eventName = null) + : base(sender, args, eventName) + { + _trackedEvent = trackedEvent; + _sender = sender; + _args = args; + } - public override void RaiseEvent() + public override void RaiseEvent() + { + if (_trackedEvent != null) { - if (_trackedEvent != null) - { - _trackedEvent(_sender, _args); - } + _trackedEvent(_sender, _args); } } } diff --git a/src/Umbraco.Core/Events/EventDefinitionBase.cs b/src/Umbraco.Core/Events/EventDefinitionBase.cs index 422392423496..fd6466294639 100644 --- a/src/Umbraco.Core/Events/EventDefinitionBase.cs +++ b/src/Umbraco.Core/Events/EventDefinitionBase.cs @@ -1,73 +1,91 @@ -using System; -using System.Reflection; +using System.Reflection; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +public abstract class EventDefinitionBase : IEventDefinition, IEquatable { - public abstract class EventDefinitionBase : IEventDefinition, IEquatable + protected EventDefinitionBase(object? sender, object? args, string? eventName = null) { - protected EventDefinitionBase(object? sender, object? args, string? eventName = null) + Sender = sender ?? throw new ArgumentNullException(nameof(sender)); + Args = args ?? throw new ArgumentNullException(nameof(args)); + EventName = eventName; + + if (EventName.IsNullOrWhiteSpace()) { - Sender = sender ?? throw new ArgumentNullException(nameof(sender)); - Args = args ?? throw new ArgumentNullException(nameof(args)); - EventName = eventName; + // don't match "Ing" suffixed names + Attempt findResult = + EventNameExtractor.FindEvent(sender, args, EventNameExtractor.MatchIngNames); - if (EventName.IsNullOrWhiteSpace()) + if (findResult.Success == false) { - // don't match "Ing" suffixed names - var findResult = EventNameExtractor.FindEvent(sender, args, exclude:EventNameExtractor.MatchIngNames); - - if (findResult.Success == false) - throw new AmbiguousMatchException("Could not automatically find the event name, the event name will need to be explicitly registered for this event definition. " - + $"Sender: {sender.GetType()} Args: {args.GetType()}" - + " Error: " + findResult.Result?.Error); - EventName = findResult.Result?.Name; + throw new AmbiguousMatchException( + "Could not automatically find the event name, the event name will need to be explicitly registered for this event definition. " + + $"Sender: {sender.GetType()} Args: {args.GetType()}" + + " Error: " + findResult.Result?.Error); } - } - public object Sender { get; } - public object Args { get; } - public string? EventName { get; } + EventName = findResult.Result?.Name; + } + } - public abstract void RaiseEvent(); + public bool Equals(EventDefinitionBase? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } - public bool Equals(EventDefinitionBase? other) + if (ReferenceEquals(this, other)) { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Args.Equals(other.Args) && string.Equals(EventName, other.EventName) && Sender.Equals(other.Sender); + return true; } - public override bool Equals(object? obj) + return Args.Equals(other.Args) && string.Equals(EventName, other.EventName) && Sender.Equals(other.Sender); + } + + public object Sender { get; } + public object Args { get; } + public string? EventName { get; } + + public abstract void RaiseEvent(); + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((EventDefinitionBase) obj); + return false; } - public override int GetHashCode() + if (ReferenceEquals(this, obj)) { - unchecked - { - var hashCode = Args.GetHashCode(); - if (EventName is not null) - { - hashCode = (hashCode * 397) ^ EventName.GetHashCode(); - } - hashCode = (hashCode * 397) ^ Sender.GetHashCode(); - return hashCode; - } + return true; } - public static bool operator ==(EventDefinitionBase left, EventDefinitionBase right) + if (obj.GetType() != GetType()) { - return Equals(left, right); + return false; } - public static bool operator !=(EventDefinitionBase left, EventDefinitionBase right) + return Equals((EventDefinitionBase)obj); + } + + public override int GetHashCode() + { + unchecked { - return Equals(left, right) == false; + var hashCode = Args.GetHashCode(); + if (EventName is not null) + { + hashCode = (hashCode * 397) ^ EventName.GetHashCode(); + } + + hashCode = (hashCode * 397) ^ Sender.GetHashCode(); + return hashCode; } } + + public static bool operator ==(EventDefinitionBase left, EventDefinitionBase right) => Equals(left, right); + + public static bool operator !=(EventDefinitionBase left, EventDefinitionBase right) => Equals(left, right) == false; } diff --git a/src/Umbraco.Core/Events/EventDefinitionFilter.cs b/src/Umbraco.Core/Events/EventDefinitionFilter.cs index 47b0f9a44ea6..7c007164ab45 100644 --- a/src/Umbraco.Core/Events/EventDefinitionFilter.cs +++ b/src/Umbraco.Core/Events/EventDefinitionFilter.cs @@ -1,24 +1,23 @@ -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +/// +/// The filter used in the GetEvents method which determines +/// how the result list is filtered +/// +public enum EventDefinitionFilter { /// - /// The filter used in the GetEvents method which determines - /// how the result list is filtered + /// Returns all events tracked /// - public enum EventDefinitionFilter - { - /// - /// Returns all events tracked - /// - All, + All, - /// - /// Deduplicates events and only returns the first duplicate instance tracked - /// - FirstIn, + /// + /// Deduplicates events and only returns the first duplicate instance tracked + /// + FirstIn, - /// - /// Deduplicates events and only returns the last duplicate instance tracked - /// - LastIn - } + /// + /// Deduplicates events and only returns the last duplicate instance tracked + /// + LastIn } diff --git a/src/Umbraco.Core/Events/EventExtensions.cs b/src/Umbraco.Core/Events/EventExtensions.cs index 4d98cbbcca95..b1a3af404517 100644 --- a/src/Umbraco.Core/Events/EventExtensions.cs +++ b/src/Umbraco.Core/Events/EventExtensions.cs @@ -1,46 +1,53 @@ -using System; +namespace Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Events +/// +/// Extension methods for cancellable event operations +/// +public static class EventExtensions { + // keep these two for backward compatibility reasons but understand that + // they are *not* part of any scope / event dispatcher / anything... + /// - /// Extension methods for cancellable event operations + /// Raises a cancelable event and returns a value indicating whether the event should be cancelled. /// - public static class EventExtensions + /// The type of the event source. + /// The type of the event data. + /// The event handler. + /// The event source. + /// The event data. + /// A value indicating whether the cancelable event should be cancelled + /// A cancelable event is raised by a component when it is about to perform an action that can be canceled. + public static bool IsRaisedEventCancelled(this TypedEventHandler eventHandler, + TArgs args, TSender sender) + where TArgs : CancellableEventArgs { - // keep these two for backward compatibility reasons but understand that - // they are *not* part of any scope / event dispatcher / anything... - - /// - /// Raises a cancelable event and returns a value indicating whether the event should be cancelled. - /// - /// The type of the event source. - /// The type of the event data. - /// The event handler. - /// The event source. - /// The event data. - /// A value indicating whether the cancelable event should be cancelled - /// A cancelable event is raised by a component when it is about to perform an action that can be canceled. - public static bool IsRaisedEventCancelled(this TypedEventHandler eventHandler, TArgs args, TSender sender) - where TArgs : CancellableEventArgs + if (eventHandler == null) { - if (eventHandler == null) return args.Cancel; - eventHandler(sender, args); return args.Cancel; } - /// - /// Raises an event. - /// - /// The type of the event source. - /// The type of the event data. - /// The event handler. - /// The event source. - /// The event data. - public static void RaiseEvent(this TypedEventHandler eventHandler, TArgs args, TSender sender) - where TArgs : EventArgs + eventHandler(sender, args); + return args.Cancel; + } + + /// + /// Raises an event. + /// + /// The type of the event source. + /// The type of the event data. + /// The event handler. + /// The event source. + /// The event data. + public static void RaiseEvent(this TypedEventHandler eventHandler, TArgs args, + TSender sender) + where TArgs : EventArgs + { + if (eventHandler == null) { - if (eventHandler == null) return; - eventHandler(sender, args); + return; } + + eventHandler(sender, args); } } diff --git a/src/Umbraco.Core/Events/EventMessage.cs b/src/Umbraco.Core/Events/EventMessage.cs index eef0985c23bd..02fb1ee370a3 100644 --- a/src/Umbraco.Core/Events/EventMessage.cs +++ b/src/Umbraco.Core/Events/EventMessage.cs @@ -1,27 +1,27 @@ -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +/// +/// An event message +/// +public sealed class EventMessage { /// - /// An event message + /// Initializes a new instance of the class. /// - public sealed class EventMessage + public EventMessage(string category, string message, EventMessageType messageType = EventMessageType.Default) { - /// - /// Initializes a new instance of the class. - /// - public EventMessage(string category, string message, EventMessageType messageType = EventMessageType.Default) - { - Category = category; - Message = message; - MessageType = messageType; - } + Category = category; + Message = message; + MessageType = messageType; + } - public string Category { get; private set; } - public string Message { get; private set; } - public EventMessageType MessageType { get; private set; } + public string Category { get; } + public string Message { get; } + public EventMessageType MessageType { get; } - /// - /// This is used to track if this message should be used as a default message so that Umbraco doesn't also append it's own default messages - /// - public bool IsDefaultEventMessage { get; set; } - } + /// + /// This is used to track if this message should be used as a default message so that Umbraco doesn't also append it's + /// own default messages + /// + public bool IsDefaultEventMessage { get; set; } } diff --git a/src/Umbraco.Core/Events/EventMessageType.cs b/src/Umbraco.Core/Events/EventMessageType.cs index afbed0d590d9..a4b64d8d1787 100644 --- a/src/Umbraco.Core/Events/EventMessageType.cs +++ b/src/Umbraco.Core/Events/EventMessageType.cs @@ -1,14 +1,13 @@ -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +/// +/// The type of event message +/// +public enum EventMessageType { - /// - /// The type of event message - /// - public enum EventMessageType - { - Default = 0, - Info = 1, - Error = 2, - Success = 3, - Warning = 4 - } + Default = 0, + Info = 1, + Error = 2, + Success = 3, + Warning = 4 } diff --git a/src/Umbraco.Core/Events/EventMessages.cs b/src/Umbraco.Core/Events/EventMessages.cs index 23b40118c768..7f3fa4cd8b8c 100644 --- a/src/Umbraco.Core/Events/EventMessages.cs +++ b/src/Umbraco.Core/Events/EventMessages.cs @@ -1,29 +1,17 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Events +/// +/// Event messages collection +/// +public sealed class EventMessages : DisposableObjectSlim { - /// - /// Event messages collection - /// - public sealed class EventMessages : DisposableObjectSlim - { - private readonly List _msgs = new List(); + private readonly List _msgs = new(); - public void Add(EventMessage msg) - { - _msgs.Add(msg); - } + public int Count => _msgs.Count; - public int Count => _msgs.Count; + public void Add(EventMessage msg) => _msgs.Add(msg); - public IEnumerable GetAll() - { - return _msgs; - } + public IEnumerable GetAll() => _msgs; - protected override void DisposeResources() - { - _msgs.Clear(); - } - } + protected override void DisposeResources() => _msgs.Clear(); } diff --git a/src/Umbraco.Core/Events/EventNameExtractor.cs b/src/Umbraco.Core/Events/EventNameExtractor.cs index c74d2e293e55..b6393d6a9fa2 100644 --- a/src/Umbraco.Core/Events/EventNameExtractor.cs +++ b/src/Umbraco.Core/Events/EventNameExtractor.cs @@ -1,168 +1,182 @@ -using System; -using System.Collections.Concurrent; -using System.Linq; +using System.Collections.Concurrent; using System.Reflection; using System.Text.RegularExpressions; -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +/// +/// There is actually no way to discover an event name in c# at the time of raising the event. It is possible +/// to get the event name from the handler that is being executed based on the event being raised, however that is not +/// what we want in this case. We need to find the event name before it is being raised - you would think that it's +/// possible +/// with reflection or anything but that is not the case, the delegate that defines an event has no info attached to +/// it, it +/// is literally just an event. +/// So what this does is take the sender and event args objects, looks up all public/static events on the sender that +/// have +/// a generic event handler with generic arguments (but only) one, then we match the type of event arguments with the +/// ones +/// being passed in. As it turns out, in our services this will work for the majority of our events! In some cases it +/// may not +/// work and we'll have to supply a string but hopefully this saves a bit of magic strings. +/// We can also write tests to validate these are all working correctly for all services. +/// +public class EventNameExtractor { /// - /// There is actually no way to discover an event name in c# at the time of raising the event. It is possible - /// to get the event name from the handler that is being executed based on the event being raised, however that is not - /// what we want in this case. We need to find the event name before it is being raised - you would think that it's possible - /// with reflection or anything but that is not the case, the delegate that defines an event has no info attached to it, it - /// is literally just an event. - /// - /// So what this does is take the sender and event args objects, looks up all public/static events on the sender that have - /// a generic event handler with generic arguments (but only) one, then we match the type of event arguments with the ones - /// being passed in. As it turns out, in our services this will work for the majority of our events! In some cases it may not - /// work and we'll have to supply a string but hopefully this saves a bit of magic strings. - /// - /// We can also write tests to validate these are all working correctly for all services. + /// Used to cache all candidate events for a given type so we don't re-look them up /// - public class EventNameExtractor + private static readonly ConcurrentDictionary CandidateEvents = new(); + + /// + /// Used to cache all matched event names by (sender type + arg type) so we don't re-look them up + /// + private static readonly ConcurrentDictionary, string[]> MatchedEventNames = new(); + + /// + /// Finds the event name on the sender that matches the args type + /// + /// + /// + /// + /// A filter to exclude matched event names, this filter should return true to exclude the event name from being + /// matched + /// + /// + /// null if not found or an ambiguous match + /// + public static Attempt FindEvent(Type senderType, Type argsType, + Func exclude) { + var events = FindEvents(senderType, argsType, exclude); - /// - /// Finds the event name on the sender that matches the args type - /// - /// - /// - /// - /// A filter to exclude matched event names, this filter should return true to exclude the event name from being matched - /// - /// - /// null if not found or an ambiguous match - /// - public static Attempt FindEvent(Type senderType, Type argsType, Func exclude) + switch (events.Length) { - var events = FindEvents(senderType, argsType, exclude); + case 0: + return Attempt.Fail(new EventNameExtractorResult(EventNameExtractorError.NoneFound)); - switch (events.Length) - { - case 0: - return Attempt.Fail(new EventNameExtractorResult(EventNameExtractorError.NoneFound)); - - case 1: - return Attempt.Succeed(new EventNameExtractorResult(events[0])); + case 1: + return Attempt.Succeed(new EventNameExtractorResult(events[0])); - default: - //there's more than one left so it's ambiguous! - return Attempt.Fail(new EventNameExtractorResult(EventNameExtractorError.Ambiguous)); - } + default: + //there's more than one left so it's ambiguous! + return Attempt.Fail(new EventNameExtractorResult(EventNameExtractorError.Ambiguous)); } + } - public static string[] FindEvents(Type senderType, Type argsType, Func exclude) + public static string[] FindEvents(Type senderType, Type argsType, Func exclude) + { + var found = MatchedEventNames.GetOrAdd(new Tuple(senderType, argsType), tuple => { - var found = MatchedEventNames.GetOrAdd(new Tuple(senderType, argsType), tuple => + EventInfoArgs[] events = CandidateEvents.GetOrAdd(senderType, t => { - var events = CandidateEvents.GetOrAdd(senderType, t => - { - return t.GetEvents(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy) - //we can only look for events handlers with generic types because that is the only - // way that we can try to find a matching event based on the arg type passed in - .Where(x => x.EventHandlerType?.IsGenericType ?? false) - .Select(x => new EventInfoArgs(x, x.EventHandlerType!.GetGenericArguments())) - //we are only looking for event handlers that have more than one generic argument - .Where(x => + return t.GetEvents(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.FlattenHierarchy) + //we can only look for events handlers with generic types because that is the only + // way that we can try to find a matching event based on the arg type passed in + .Where(x => x.EventHandlerType?.IsGenericType ?? false) + .Select(x => new EventInfoArgs(x, x.EventHandlerType!.GetGenericArguments())) + //we are only looking for event handlers that have more than one generic argument + .Where(x => + { + if (x.GenericArgs.Length == 1) { - if (x.GenericArgs.Length == 1) return true; + return true; + } - //special case for our own TypedEventHandler - if (x.EventInfo.EventHandlerType?.GetGenericTypeDefinition() == typeof(TypedEventHandler<,>) && x.GenericArgs.Length == 2) - { - return true; - } + //special case for our own TypedEventHandler + if (x.EventInfo.EventHandlerType?.GetGenericTypeDefinition() == typeof(TypedEventHandler<,>) && + x.GenericArgs.Length == 2) + { + return true; + } - return false; - }) - .ToArray(); - }); + return false; + }) + .ToArray(); + }); - return events.Where(x => + return events.Where(x => + { + if (x.GenericArgs.Length == 1 && x.GenericArgs[0] == tuple.Item2) { - if (x.GenericArgs.Length == 1 && x.GenericArgs[0] == tuple.Item2) - return true; + return true; + } - //special case for our own TypedEventHandler - if (x.EventInfo.EventHandlerType?.GetGenericTypeDefinition() == typeof(TypedEventHandler<,>) - && x.GenericArgs.Length == 2 - && x.GenericArgs[1] == tuple.Item2) - { - return true; - } + //special case for our own TypedEventHandler + if (x.EventInfo.EventHandlerType?.GetGenericTypeDefinition() == typeof(TypedEventHandler<,>) + && x.GenericArgs.Length == 2 + && x.GenericArgs[1] == tuple.Item2) + { + return true; + } - return false; - }).Select(x => x.EventInfo.Name).ToArray(); - }); + return false; + }).Select(x => x.EventInfo.Name).ToArray(); + }); - return found.Where(x => exclude(x) == false).ToArray(); - } + return found.Where(x => exclude(x) == false).ToArray(); + } - /// - /// Finds the event name on the sender that matches the args type - /// - /// - /// - /// - /// A filter to exclude matched event names, this filter should return true to exclude the event name from being matched - /// - /// - /// null if not found or an ambiguous match - /// - public static Attempt FindEvent(object sender, object args, Func exclude) - { - return FindEvent(sender.GetType(), args.GetType(), exclude); - } + /// + /// Finds the event name on the sender that matches the args type + /// + /// + /// + /// + /// A filter to exclude matched event names, this filter should return true to exclude the event name from being + /// matched + /// + /// + /// null if not found or an ambiguous match + /// + public static Attempt + FindEvent(object sender, object args, Func exclude) => + FindEvent(sender.GetType(), args.GetType(), exclude); - /// - /// Return true if the event is named with an ING name such as "Saving" or "RollingBack" - /// - /// - /// - public static bool MatchIngNames(string eventName) + /// + /// Return true if the event is named with an ING name such as "Saving" or "RollingBack" + /// + /// + /// + public static bool MatchIngNames(string eventName) + { + var splitter = new Regex(@"(? - /// Return true if the event is not named with an ING name such as "Saving" or "RollingBack" - ///
- /// - /// - public static bool MatchNonIngNames(string eventName) + return words[0].EndsWith("ing"); + } + + /// + /// Return true if the event is not named with an ING name such as "Saving" or "RollingBack" + /// + /// + /// + public static bool MatchNonIngNames(string eventName) + { + var splitter = new Regex(@"(? - /// Used to cache all candidate events for a given type so we don't re-look them up - /// - private static readonly ConcurrentDictionary CandidateEvents = new ConcurrentDictionary(); - - /// - /// Used to cache all matched event names by (sender type + arg type) so we don't re-look them up - /// - private static readonly ConcurrentDictionary, string[]> MatchedEventNames = new ConcurrentDictionary, string[]>(); + public EventInfo EventInfo { get; } + public Type[] GenericArgs { get; } } } diff --git a/src/Umbraco.Core/Events/EventNameExtractorError.cs b/src/Umbraco.Core/Events/EventNameExtractorError.cs index 8a506f907114..3e9029c98a99 100644 --- a/src/Umbraco.Core/Events/EventNameExtractorError.cs +++ b/src/Umbraco.Core/Events/EventNameExtractorError.cs @@ -1,8 +1,7 @@ -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +public enum EventNameExtractorError { - public enum EventNameExtractorError - { - NoneFound, - Ambiguous - } + NoneFound, + Ambiguous } diff --git a/src/Umbraco.Core/Events/EventNameExtractorResult.cs b/src/Umbraco.Core/Events/EventNameExtractorResult.cs index 2f5498c33f28..5e7107a39311 100644 --- a/src/Umbraco.Core/Events/EventNameExtractorResult.cs +++ b/src/Umbraco.Core/Events/EventNameExtractorResult.cs @@ -1,18 +1,11 @@ -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +public class EventNameExtractorResult { - public class EventNameExtractorResult - { - public EventNameExtractorError? Error { get; private set; } - public string? Name { get; private set; } + public EventNameExtractorResult(string? name) => Name = name; - public EventNameExtractorResult(string? name) - { - Name = name; - } + public EventNameExtractorResult(EventNameExtractorError? error) => Error = error; - public EventNameExtractorResult(EventNameExtractorError? error) - { - Error = error; - } - } + public EventNameExtractorError? Error { get; } + public string? Name { get; } } diff --git a/src/Umbraco.Core/Events/ExportedMemberEventArgs.cs b/src/Umbraco.Core/Events/ExportedMemberEventArgs.cs index 2026f41ff300..6d208ffee6f2 100644 --- a/src/Umbraco.Core/Events/ExportedMemberEventArgs.cs +++ b/src/Umbraco.Core/Events/ExportedMemberEventArgs.cs @@ -1,18 +1,16 @@ -using System; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +public class ExportedMemberEventArgs : EventArgs { - public class ExportedMemberEventArgs : EventArgs + public ExportedMemberEventArgs(IMember member, MemberExportModel exported) { - public IMember Member { get; } - public MemberExportModel Exported { get; } - - public ExportedMemberEventArgs(IMember member, MemberExportModel exported) - { - Member = member; - Exported = exported; - } + Member = member; + Exported = exported; } + + public IMember Member { get; } + public MemberExportModel Exported { get; } } diff --git a/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs b/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs index 9a6a4357e0d6..2be700f27fa1 100644 --- a/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs +++ b/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs @@ -1,9 +1,6 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Events +public interface IDeletingMediaFilesEventArgs { - public interface IDeletingMediaFilesEventArgs - { - List MediaFilesToDelete { get; } - } + List MediaFilesToDelete { get; } } diff --git a/src/Umbraco.Core/Events/IEventAggregator.cs b/src/Umbraco.Core/Events/IEventAggregator.cs index c654bb6c8619..379f532be22b 100644 --- a/src/Umbraco.Core/Events/IEventAggregator.cs +++ b/src/Umbraco.Core/Events/IEventAggregator.cs @@ -1,52 +1,49 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Threading; -using System.Threading.Tasks; using Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +/// +/// Defines an object that channels events from multiple objects into a single object +/// to simplify registration for clients. +/// +public interface IEventAggregator { /// - /// Defines an object that channels events from multiple objects into a single object - /// to simplify registration for clients. + /// Asynchronously send a notification to multiple handlers of both sync and async /// - public interface IEventAggregator - { - /// - /// Asynchronously send a notification to multiple handlers of both sync and async - /// - /// The type of notification being handled. - /// The notification object. - /// An optional cancellation token. - /// A task that represents the publish operation. - Task PublishAsync(TNotification notification, CancellationToken cancellationToken = default) - where TNotification : INotification; + /// The type of notification being handled. + /// The notification object. + /// An optional cancellation token. + /// A task that represents the publish operation. + Task PublishAsync(TNotification notification, CancellationToken cancellationToken = default) + where TNotification : INotification; - /// - /// Synchronously send a notification to multiple handlers of both sync and async - /// - /// The type of notification being handled. - /// The notification object. - void Publish(TNotification notification) - where TNotification : INotification; + /// + /// Synchronously send a notification to multiple handlers of both sync and async + /// + /// The type of notification being handled. + /// The notification object. + void Publish(TNotification notification) + where TNotification : INotification; - /// - /// Publishes a cancelable notification to the notification subscribers - /// - /// The type of notification being handled. - /// - /// True if the notification was cancelled by a subscriber, false otherwise - bool PublishCancelable(TCancelableNotification notification) - where TCancelableNotification : ICancelableNotification; + /// + /// Publishes a cancelable notification to the notification subscribers + /// + /// The type of notification being handled. + /// + /// True if the notification was cancelled by a subscriber, false otherwise + bool PublishCancelable(TCancelableNotification notification) + where TCancelableNotification : ICancelableNotification; - /// - /// Publishes a cancelable notification async to the notification subscribers - /// - /// The type of notification being handled. - /// - /// True if the notification was cancelled by a subscriber, false otherwise - Task PublishCancelableAsync(TCancelableNotification notification) - where TCancelableNotification : ICancelableNotification; - } + /// + /// Publishes a cancelable notification async to the notification subscribers + /// + /// The type of notification being handled. + /// + /// True if the notification was cancelled by a subscriber, false otherwise + Task PublishCancelableAsync(TCancelableNotification notification) + where TCancelableNotification : ICancelableNotification; } diff --git a/src/Umbraco.Core/Events/IEventDefinition.cs b/src/Umbraco.Core/Events/IEventDefinition.cs index e3918113e1dc..15e8117b107a 100644 --- a/src/Umbraco.Core/Events/IEventDefinition.cs +++ b/src/Umbraco.Core/Events/IEventDefinition.cs @@ -1,11 +1,10 @@ -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +public interface IEventDefinition { - public interface IEventDefinition - { - object Sender { get; } - object Args { get; } - string? EventName { get; } + object Sender { get; } + object Args { get; } + string? EventName { get; } - void RaiseEvent(); - } + void RaiseEvent(); } diff --git a/src/Umbraco.Core/Events/IEventDispatcher.cs b/src/Umbraco.Core/Events/IEventDispatcher.cs index bef94b6d4a47..3c9bb0a7b502 100644 --- a/src/Umbraco.Core/Events/IEventDispatcher.cs +++ b/src/Umbraco.Core/Events/IEventDispatcher.cs @@ -1,98 +1,100 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Events +/// +/// Dispatches events from within a scope. +/// +/// +/// +/// The name of the event is auto-magically discovered by matching the sender type, args type, and +/// eventHandler type. If the match is not unique, then the name parameter must be used to specify the +/// name in an explicit way. +/// +/// +/// What happens when an event is dispatched depends on the scope settings. It can be anything from +/// "trigger immediately" to "just ignore". Refer to the scope documentation for more details. +/// +/// +public interface IEventDispatcher { + // not sure about the Dispatch & DispatchCancelable signatures at all for now + // nor about the event name thing, etc - but let's keep it like this + /// - /// Dispatches events from within a scope. + /// Dispatches a cancelable event. /// - /// - /// The name of the event is auto-magically discovered by matching the sender type, args type, and - /// eventHandler type. If the match is not unique, then the name parameter must be used to specify the - /// name in an explicit way. - /// What happens when an event is dispatched depends on the scope settings. It can be anything from - /// "trigger immediately" to "just ignore". Refer to the scope documentation for more details. - /// - public interface IEventDispatcher - { - // not sure about the Dispatch & DispatchCancelable signatures at all for now - // nor about the event name thing, etc - but let's keep it like this - - /// - /// Dispatches a cancelable event. - /// - /// The event handler. - /// The object that raised the event. - /// The event data. - /// The optional name of the event. - /// A value indicating whether the cancelable event was cancelled. - /// See general remarks on the interface. - bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string? name = null); + /// The event handler. + /// The object that raised the event. + /// The event data. + /// The optional name of the event. + /// A value indicating whether the cancelable event was cancelled. + /// See general remarks on the interface. + bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string? name = null); - /// - /// Dispatches a cancelable event. - /// - /// The event handler. - /// The object that raised the event. - /// The event data. - /// The optional name of the event. - /// A value indicating whether the cancelable event was cancelled. - /// See general remarks on the interface. - bool DispatchCancelable(EventHandler eventHandler, object sender, TArgs args, string? name = null) - where TArgs : CancellableEventArgs; + /// + /// Dispatches a cancelable event. + /// + /// The event handler. + /// The object that raised the event. + /// The event data. + /// The optional name of the event. + /// A value indicating whether the cancelable event was cancelled. + /// See general remarks on the interface. + bool DispatchCancelable(EventHandler eventHandler, object sender, TArgs args, string? name = null) + where TArgs : CancellableEventArgs; - /// - /// Dispatches a cancelable event. - /// - /// The event handler. - /// The object that raised the event. - /// The event data. - /// The optional name of the event. - /// A value indicating whether the cancelable event was cancelled. - /// See general remarks on the interface. - bool DispatchCancelable(TypedEventHandler eventHandler, TSender sender, TArgs args, string? name = null) - where TArgs : CancellableEventArgs; + /// + /// Dispatches a cancelable event. + /// + /// The event handler. + /// The object that raised the event. + /// The event data. + /// The optional name of the event. + /// A value indicating whether the cancelable event was cancelled. + /// See general remarks on the interface. + bool DispatchCancelable(TypedEventHandler eventHandler, TSender sender, TArgs args, + string? name = null) + where TArgs : CancellableEventArgs; - /// - /// Dispatches an event. - /// - /// The event handler. - /// The object that raised the event. - /// The event data. - /// The optional name of the event. - /// See general remarks on the interface. - void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string? name = null); + /// + /// Dispatches an event. + /// + /// The event handler. + /// The object that raised the event. + /// The event data. + /// The optional name of the event. + /// See general remarks on the interface. + void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string? name = null); - /// - /// Dispatches an event. - /// - /// The event handler. - /// The object that raised the event. - /// The event data. - /// The optional name of the event. - /// See general remarks on the interface. - void Dispatch(EventHandler eventHandler, object sender, TArgs args, string? name = null); + /// + /// Dispatches an event. + /// + /// The event handler. + /// The object that raised the event. + /// The event data. + /// The optional name of the event. + /// See general remarks on the interface. + void Dispatch(EventHandler eventHandler, object sender, TArgs args, string? name = null); - /// - /// Dispatches an event. - /// - /// The event handler. - /// The object that raised the event. - /// The event data. - /// The optional name of the event. - /// See general remarks on the interface. - void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, string? name = null); + /// + /// Dispatches an event. + /// + /// The event handler. + /// The object that raised the event. + /// The event data. + /// The optional name of the event. + /// See general remarks on the interface. + void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, + string? name = null); - /// - /// Notifies the dispatcher that the scope is exiting. - /// - /// A value indicating whether the scope completed. - void ScopeExit(bool completed); + /// + /// Notifies the dispatcher that the scope is exiting. + /// + /// A value indicating whether the scope completed. + void ScopeExit(bool completed); - /// - /// Gets the collected events. - /// - /// The collected events. - IEnumerable GetEvents(EventDefinitionFilter filter); - } + /// + /// Gets the collected events. + /// + /// The collected events. + IEnumerable GetEvents(EventDefinitionFilter filter); } diff --git a/src/Umbraco.Core/Events/IEventMessagesAccessor.cs b/src/Umbraco.Core/Events/IEventMessagesAccessor.cs index cffff705da7e..0592643d17ac 100644 --- a/src/Umbraco.Core/Events/IEventMessagesAccessor.cs +++ b/src/Umbraco.Core/Events/IEventMessagesAccessor.cs @@ -1,7 +1,6 @@ -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +public interface IEventMessagesAccessor { - public interface IEventMessagesAccessor - { - EventMessages? EventMessages { get; set; } - } + EventMessages? EventMessages { get; set; } } diff --git a/src/Umbraco.Core/Events/IEventMessagesFactory.cs b/src/Umbraco.Core/Events/IEventMessagesFactory.cs index 6abf6e8d4116..df69a7f14208 100644 --- a/src/Umbraco.Core/Events/IEventMessagesFactory.cs +++ b/src/Umbraco.Core/Events/IEventMessagesFactory.cs @@ -1,12 +1,11 @@ -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +/// +/// Event messages factory +/// +public interface IEventMessagesFactory { - /// - /// Event messages factory - /// - public interface IEventMessagesFactory - { - EventMessages Get(); + EventMessages Get(); - EventMessages? GetOrDefault(); - } + EventMessages? GetOrDefault(); } diff --git a/src/Umbraco.Core/Events/INotificationAsyncHandler.cs b/src/Umbraco.Core/Events/INotificationAsyncHandler.cs index cdcc21542f2a..be9371949e46 100644 --- a/src/Umbraco.Core/Events/INotificationAsyncHandler.cs +++ b/src/Umbraco.Core/Events/INotificationAsyncHandler.cs @@ -1,25 +1,22 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Threading; -using System.Threading.Tasks; using Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +/// +/// Defines a handler for a async notification. +/// +/// The type of notification being handled. +public interface INotificationAsyncHandler + where TNotification : INotification { /// - /// Defines a handler for a async notification. + /// Handles a notification /// - /// The type of notification being handled. - public interface INotificationAsyncHandler - where TNotification : INotification - { - /// - /// Handles a notification - /// - /// The notification - /// The cancellation token. - /// A representing the asynchronous operation. - Task HandleAsync(TNotification notification, CancellationToken cancellationToken); - } + /// The notification + /// The cancellation token. + /// A representing the asynchronous operation. + Task HandleAsync(TNotification notification, CancellationToken cancellationToken); } diff --git a/src/Umbraco.Core/Events/INotificationHandler.cs b/src/Umbraco.Core/Events/INotificationHandler.cs index 548bec39b891..2111009faabb 100644 --- a/src/Umbraco.Core/Events/INotificationHandler.cs +++ b/src/Umbraco.Core/Events/INotificationHandler.cs @@ -3,19 +3,18 @@ using Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +/// +/// Defines a handler for a notification. +/// +/// The type of notification being handled. +public interface INotificationHandler + where TNotification : INotification { /// - /// Defines a handler for a notification. + /// Handles a notification /// - /// The type of notification being handled. - public interface INotificationHandler - where TNotification : INotification - { - /// - /// Handles a notification - /// - /// The notification - void Handle(TNotification notification); - } + /// The notification + void Handle(TNotification notification); } diff --git a/src/Umbraco.Core/Events/IScopedNotificationPublisher.cs b/src/Umbraco.Core/Events/IScopedNotificationPublisher.cs index 58fdafc341ed..89962bbb9c9a 100644 --- a/src/Umbraco.Core/Events/IScopedNotificationPublisher.cs +++ b/src/Umbraco.Core/Events/IScopedNotificationPublisher.cs @@ -1,45 +1,45 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Threading.Tasks; using Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +public interface IScopedNotificationPublisher { - public interface IScopedNotificationPublisher - { - /// - /// Suppresses all notifications from being added/created until the result object is disposed. - /// - /// - IDisposable Suppress(); + /// + /// Suppresses all notifications from being added/created until the result object is disposed. + /// + /// + IDisposable Suppress(); - /// - /// Publishes a cancelable notification to the notification subscribers - /// - /// - /// True if the notification was cancelled by a subscriber, false otherwise - bool PublishCancelable(ICancelableNotification notification); + /// + /// Publishes a cancelable notification to the notification subscribers + /// + /// + /// True if the notification was cancelled by a subscriber, false otherwise + bool PublishCancelable(ICancelableNotification notification); - /// - /// Publishes a cancelable notification to the notification subscribers - /// - /// - /// True if the notification was cancelled by a subscriber, false otherwise - Task PublishCancelableAsync(ICancelableNotification notification); + /// + /// Publishes a cancelable notification to the notification subscribers + /// + /// + /// True if the notification was cancelled by a subscriber, false otherwise + Task PublishCancelableAsync(ICancelableNotification notification); - /// - /// Publishes a notification to the notification subscribers - /// - /// - /// The notification is published upon successful completion of the current scope, i.e. when things have been saved/published/deleted etc. - void Publish(INotification notification); + /// + /// Publishes a notification to the notification subscribers + /// + /// + /// + /// The notification is published upon successful completion of the current scope, i.e. when things have been + /// saved/published/deleted etc. + /// + void Publish(INotification notification); - /// - /// Invokes publishing of all pending notifications within the current scope - /// - /// - void ScopeExit(bool completed); - } + /// + /// Invokes publishing of all pending notifications within the current scope + /// + /// + void ScopeExit(bool completed); } diff --git a/src/Umbraco.Core/Events/MacroErrorEventArgs.cs b/src/Umbraco.Core/Events/MacroErrorEventArgs.cs index 8d0e8dbfe1f9..fb51b9e63cbf 100644 --- a/src/Umbraco.Core/Events/MacroErrorEventArgs.cs +++ b/src/Umbraco.Core/Events/MacroErrorEventArgs.cs @@ -1,42 +1,41 @@ -using System; -using Umbraco.Cms.Core.Macros; +using Umbraco.Cms.Core.Macros; -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +// Provides information on the macro that caused an error +public class MacroErrorEventArgs : EventArgs { - // Provides information on the macro that caused an error - public class MacroErrorEventArgs : EventArgs - { - /// - /// Name of the faulting macro. - /// - public string? Name { get; set; } + /// + /// Name of the faulting macro. + /// + public string? Name { get; set; } - /// - /// Alias of the faulting macro. - /// - public string? Alias { get; set; } + /// + /// Alias of the faulting macro. + /// + public string? Alias { get; set; } - /// - /// Filename, file path, fully qualified class name, or other key used by the macro engine to do it's processing of the faulting macro. - /// - public string? MacroSource { get; set; } + /// + /// Filename, file path, fully qualified class name, or other key used by the macro engine to do it's processing of the + /// faulting macro. + /// + public string? MacroSource { get; set; } - /// - /// Exception raised. - /// - public Exception? Exception { get; set; } + /// + /// Exception raised. + /// + public Exception? Exception { get; set; } - /// - /// Gets or sets the desired behaviour when a matching macro causes an error. See - /// for definitions. By setting this in your event - /// you can override the default behaviour defined in UmbracoSettings.config. - /// - /// Macro error behaviour enum. - public MacroErrorBehaviour Behaviour { get; set; } + /// + /// Gets or sets the desired behaviour when a matching macro causes an error. See + /// for definitions. By setting this in your event + /// you can override the default behaviour defined in UmbracoSettings.config. + /// + /// Macro error behaviour enum. + public MacroErrorBehaviour Behaviour { get; set; } - /// - /// The HTML code to display when Behavior is Content. - /// - public string? Html { get; set; } - } + /// + /// The HTML code to display when Behavior is Content. + /// + public string? Html { get; set; } } diff --git a/src/Umbraco.Core/Events/MoveEventArgs.cs b/src/Umbraco.Core/Events/MoveEventArgs.cs index 2f6505635333..194a76900b30 100644 --- a/src/Umbraco.Core/Events/MoveEventArgs.cs +++ b/src/Umbraco.Core/Events/MoveEventArgs.cs @@ -1,151 +1,160 @@ -using System; -using System.Collections.Generic; -using System.Linq; +namespace Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Events +public class MoveEventArgs : CancellableObjectEventArgs, IEquatable> { - public class MoveEventArgs : CancellableObjectEventArgs, IEquatable> + private IEnumerable>? _moveInfoCollection; + + /// + /// Constructor accepting a collection of MoveEventInfo objects + /// + /// + /// + /// + /// A collection of MoveEventInfo objects that exposes all entities that have been moved during a single move operation + /// + public MoveEventArgs(bool canCancel, EventMessages eventMessages, params MoveEventInfo[] moveInfo) + : base(default, canCancel, eventMessages) { - private IEnumerable>? _moveInfoCollection; - - /// - /// Constructor accepting a collection of MoveEventInfo objects - /// - /// - /// - /// - /// A collection of MoveEventInfo objects that exposes all entities that have been moved during a single move operation - /// - public MoveEventArgs(bool canCancel, EventMessages eventMessages, params MoveEventInfo[] moveInfo) - : base(default, canCancel, eventMessages) + if (moveInfo.FirstOrDefault() is null) { - if (moveInfo.FirstOrDefault() is null) - { - throw new ArgumentException("moveInfo argument must contain at least one item"); - } - - MoveInfoCollection = moveInfo; - //assign the legacy props - EventObject = moveInfo.First().Entity; + throw new ArgumentException("moveInfo argument must contain at least one item"); } - /// - /// Constructor accepting a collection of MoveEventInfo objects - /// - /// - /// - /// A collection of MoveEventInfo objects that exposes all entities that have been moved during a single move operation - /// - public MoveEventArgs(EventMessages eventMessages, params MoveEventInfo[] moveInfo) - : base(default, eventMessages) - { - if (moveInfo.FirstOrDefault() is null) - { - throw new ArgumentException("moveInfo argument must contain at least one item"); - } + MoveInfoCollection = moveInfo; + //assign the legacy props + EventObject = moveInfo.First().Entity; + } - MoveInfoCollection = moveInfo; - //assign the legacy props - EventObject = moveInfo.First().Entity; + /// + /// Constructor accepting a collection of MoveEventInfo objects + /// + /// + /// + /// A collection of MoveEventInfo objects that exposes all entities that have been moved during a single move operation + /// + public MoveEventArgs(EventMessages eventMessages, params MoveEventInfo[] moveInfo) + : base(default, eventMessages) + { + if (moveInfo.FirstOrDefault() is null) + { + throw new ArgumentException("moveInfo argument must contain at least one item"); } - /// - /// Constructor accepting a collection of MoveEventInfo objects - /// - /// - /// - /// A collection of MoveEventInfo objects that exposes all entities that have been moved during a single move operation - /// - public MoveEventArgs(bool canCancel, params MoveEventInfo[] moveInfo) - : base(default, canCancel) - { - if (moveInfo.FirstOrDefault() is null) - { - throw new ArgumentException("moveInfo argument must contain at least one item"); - } + MoveInfoCollection = moveInfo; + //assign the legacy props + EventObject = moveInfo.First().Entity; + } - MoveInfoCollection = moveInfo; - //assign the legacy props - EventObject = moveInfo.First().Entity; + /// + /// Constructor accepting a collection of MoveEventInfo objects + /// + /// + /// + /// A collection of MoveEventInfo objects that exposes all entities that have been moved during a single move operation + /// + public MoveEventArgs(bool canCancel, params MoveEventInfo[] moveInfo) + : base(default, canCancel) + { + if (moveInfo.FirstOrDefault() is null) + { + throw new ArgumentException("moveInfo argument must contain at least one item"); } - /// - /// Constructor accepting a collection of MoveEventInfo objects - /// - /// - /// A collection of MoveEventInfo objects that exposes all entities that have been moved during a single move operation - /// - public MoveEventArgs(params MoveEventInfo[] moveInfo) - : base(default) - { - if (moveInfo.FirstOrDefault() is null) - { - throw new ArgumentException("moveInfo argument must contain at least one item"); - } + MoveInfoCollection = moveInfo; + //assign the legacy props + EventObject = moveInfo.First().Entity; + } - MoveInfoCollection = moveInfo; - //assign the legacy props - EventObject = moveInfo.First().Entity; + /// + /// Constructor accepting a collection of MoveEventInfo objects + /// + /// + /// A collection of MoveEventInfo objects that exposes all entities that have been moved during a single move operation + /// + public MoveEventArgs(params MoveEventInfo[] moveInfo) + : base(default) + { + if (moveInfo.FirstOrDefault() is null) + { + throw new ArgumentException("moveInfo argument must contain at least one item"); } + MoveInfoCollection = moveInfo; + //assign the legacy props + EventObject = moveInfo.First().Entity; + } + - /// - /// Gets all MoveEventInfo objects used to create the object - /// - public IEnumerable>? MoveInfoCollection + /// + /// Gets all MoveEventInfo objects used to create the object + /// + public IEnumerable>? MoveInfoCollection + { + get => _moveInfoCollection; + set { - get { return _moveInfoCollection; } - set + MoveEventInfo first = value?.FirstOrDefault(); + if (first is null) { - var first = value?.FirstOrDefault(); - if (first is null) - { - throw new InvalidOperationException("MoveInfoCollection must have at least one item"); - } + throw new InvalidOperationException("MoveInfoCollection must have at least one item"); + } - _moveInfoCollection = value; + _moveInfoCollection = value; - //assign the legacy props - EventObject = first.Entity; - } + //assign the legacy props + EventObject = first.Entity; } + } - public bool Equals(MoveEventArgs? other) + public bool Equals(MoveEventArgs? other) + { + if (other is null) { - if (other is null) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && (MoveInfoCollection?.Equals(other.MoveInfoCollection) ?? false); + return false; } - public override bool Equals(object? obj) + if (ReferenceEquals(this, other)) { - if (obj is null) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((MoveEventArgs) obj); + return true; } - public override int GetHashCode() + return base.Equals(other) && (MoveInfoCollection?.Equals(other.MoveInfoCollection) ?? false); + } + + public override bool Equals(object? obj) + { + if (obj is null) { - unchecked - { - if (MoveInfoCollection is not null) - { - return (base.GetHashCode() * 397) ^ MoveInfoCollection.GetHashCode(); - } + return false; + } - return base.GetHashCode() * 397; - } + if (ReferenceEquals(this, obj)) + { + return true; } - public static bool operator ==(MoveEventArgs left, MoveEventArgs right) + if (obj.GetType() != GetType()) { - return Equals(left, right); + return false; } - public static bool operator !=(MoveEventArgs left, MoveEventArgs right) + return Equals((MoveEventArgs)obj); + } + + public override int GetHashCode() + { + unchecked { - return !Equals(left, right); + if (MoveInfoCollection is not null) + { + return (base.GetHashCode() * 397) ^ MoveInfoCollection.GetHashCode(); + } + + return base.GetHashCode() * 397; } } + + public static bool operator ==(MoveEventArgs left, MoveEventArgs right) => Equals(left, right); + + public static bool operator !=(MoveEventArgs left, MoveEventArgs right) => !Equals(left, right); } diff --git a/src/Umbraco.Core/Events/MoveEventInfo.cs b/src/Umbraco.Core/Events/MoveEventInfo.cs index 126a3fd23003..0c0d557ff993 100644 --- a/src/Umbraco.Core/Events/MoveEventInfo.cs +++ b/src/Umbraco.Core/Events/MoveEventInfo.cs @@ -1,55 +1,68 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Events +public class MoveEventInfo : IEquatable> { - public class MoveEventInfo : IEquatable> + public MoveEventInfo(TEntity entity, string originalPath, int newParentId) { - public MoveEventInfo(TEntity entity, string originalPath, int newParentId) + Entity = entity; + OriginalPath = originalPath; + NewParentId = newParentId; + } + + public TEntity Entity { get; set; } + public string OriginalPath { get; set; } + public int NewParentId { get; set; } + + public bool Equals(MoveEventInfo? other) + { + if (ReferenceEquals(null, other)) { - Entity = entity; - OriginalPath = originalPath; - NewParentId = newParentId; + return false; } - public TEntity Entity { get; set; } - public string OriginalPath { get; set; } - public int NewParentId { get; set; } - - public bool Equals(MoveEventInfo? other) + if (ReferenceEquals(this, other)) { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return EqualityComparer.Default.Equals(Entity, other.Entity) && NewParentId == other.NewParentId && string.Equals(OriginalPath, other.OriginalPath); + return true; } - public override bool Equals(object? obj) + return EqualityComparer.Default.Equals(Entity, other.Entity) && NewParentId == other.NewParentId && + string.Equals(OriginalPath, other.OriginalPath); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((MoveEventInfo) obj); + return false; } - public override int GetHashCode() + if (ReferenceEquals(this, obj)) { - unchecked - { - var hashCode = Entity is not null ? EqualityComparer.Default.GetHashCode(Entity) : base.GetHashCode(); - hashCode = (hashCode * 397) ^ NewParentId; - hashCode = (hashCode * 397) ^ OriginalPath.GetHashCode(); - return hashCode; - } + return true; } - public static bool operator ==(MoveEventInfo left, MoveEventInfo right) + if (obj.GetType() != GetType()) { - return Equals(left, right); + return false; } - public static bool operator !=(MoveEventInfo left, MoveEventInfo right) + return Equals((MoveEventInfo)obj); + } + + public override int GetHashCode() + { + unchecked { - return !Equals(left, right); + var hashCode = Entity is not null + ? EqualityComparer.Default.GetHashCode(Entity) + : base.GetHashCode(); + hashCode = (hashCode * 397) ^ NewParentId; + hashCode = (hashCode * 397) ^ OriginalPath.GetHashCode(); + return hashCode; } } + + public static bool operator ==(MoveEventInfo left, MoveEventInfo right) => Equals(left, right); + + public static bool operator !=(MoveEventInfo left, MoveEventInfo right) => !Equals(left, right); } diff --git a/src/Umbraco.Core/Events/NewEventArgs.cs b/src/Umbraco.Core/Events/NewEventArgs.cs index d3e8436d0ee1..64a129c53af1 100644 --- a/src/Umbraco.Core/Events/NewEventArgs.cs +++ b/src/Umbraco.Core/Events/NewEventArgs.cs @@ -1,130 +1,135 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Events +public class NewEventArgs : CancellableObjectEventArgs, IEquatable> { - public class NewEventArgs : CancellableObjectEventArgs, IEquatable> + public NewEventArgs(TEntity eventObject, bool canCancel, string alias, int parentId, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) { + Alias = alias; + ParentId = parentId; + } + public NewEventArgs(TEntity eventObject, bool canCancel, string alias, TEntity? parent, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { + Alias = alias; + Parent = parent; + } - public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, int parentId, EventMessages eventMessages) - : base(eventObject, canCancel, eventMessages) - { - Alias = alias; - ParentId = parentId; - } + public NewEventArgs(TEntity eventObject, string alias, int parentId, EventMessages eventMessages) + : base(eventObject, eventMessages) + { + Alias = alias; + ParentId = parentId; + } - public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, TEntity? parent, EventMessages eventMessages) - : base(eventObject, canCancel, eventMessages) - { - Alias = alias; - Parent = parent; - } + public NewEventArgs(TEntity eventObject, string alias, TEntity? parent, EventMessages eventMessages) + : base(eventObject, eventMessages) + { + Alias = alias; + Parent = parent; + } - public NewEventArgs(TEntity eventObject, string @alias, int parentId, EventMessages eventMessages) - : base(eventObject, eventMessages) - { - Alias = alias; - ParentId = parentId; - } - public NewEventArgs(TEntity eventObject, string @alias, TEntity? parent, EventMessages eventMessages) - : base(eventObject, eventMessages) - { - Alias = alias; - Parent = parent; - } + public NewEventArgs(TEntity eventObject, bool canCancel, string alias, int parentId) : base(eventObject, canCancel) + { + Alias = alias; + ParentId = parentId; + } + public NewEventArgs(TEntity eventObject, bool canCancel, string alias, TEntity? parent) + : base(eventObject, canCancel) + { + Alias = alias; + Parent = parent; + } + public NewEventArgs(TEntity eventObject, string alias, int parentId) : base(eventObject) + { + Alias = alias; + ParentId = parentId; + } - public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, int parentId) : base(eventObject, canCancel) - { - Alias = alias; - ParentId = parentId; - } + public NewEventArgs(TEntity eventObject, string alias, TEntity? parent) + : base(eventObject) + { + Alias = alias; + Parent = parent; + } - public NewEventArgs(TEntity eventObject, bool canCancel, string @alias, TEntity? parent) - : base(eventObject, canCancel) - { - Alias = alias; - Parent = parent; - } + /// + /// The entity being created + /// + public TEntity? Entity => EventObject; - public NewEventArgs(TEntity eventObject, string @alias, int parentId) : base(eventObject) - { - Alias = alias; - ParentId = parentId; - } + /// + /// Gets or Sets the Alias. + /// + public string Alias { get; } + + /// + /// Gets or Sets the Id of the parent. + /// + public int ParentId { get; } - public NewEventArgs(TEntity eventObject, string @alias, TEntity? parent) - : base(eventObject) + /// + /// Gets or Sets the parent IContent object. + /// + public TEntity? Parent { get; } + + public bool Equals(NewEventArgs? other) + { + if (ReferenceEquals(null, other)) { - Alias = alias; - Parent = parent; + return false; } - /// - /// The entity being created - /// - public TEntity? Entity + if (ReferenceEquals(this, other)) { - get { return EventObject; } + return true; } - /// - /// Gets or Sets the Alias. - /// - public string Alias { get; private set; } - - /// - /// Gets or Sets the Id of the parent. - /// - public int ParentId { get; private set; } - - /// - /// Gets or Sets the parent IContent object. - /// - public TEntity? Parent { get; private set; } + return base.Equals(other) && string.Equals(Alias, other.Alias) && + EqualityComparer.Default.Equals(Parent, other.Parent) && ParentId == other.ParentId; + } - public bool Equals(NewEventArgs? other) + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && string.Equals(Alias, other.Alias) && EqualityComparer.Default.Equals(Parent, other.Parent) && ParentId == other.ParentId; + return false; } - public override bool Equals(object? obj) + if (ReferenceEquals(this, obj)) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((NewEventArgs?) obj); + return true; } - public override int GetHashCode() + if (obj.GetType() != GetType()) { - unchecked - { - int hashCode = base.GetHashCode(); - hashCode = (hashCode * 397) ^ Alias.GetHashCode(); - if (Parent is not null) - { - hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(Parent); - } - - hashCode = (hashCode * 397) ^ ParentId; - return hashCode; - } + return false; } - public static bool operator ==(NewEventArgs left, NewEventArgs right) - { - return Equals(left, right); - } + return Equals((NewEventArgs?)obj); + } - public static bool operator !=(NewEventArgs left, NewEventArgs right) + public override int GetHashCode() + { + unchecked { - return !Equals(left, right); + var hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ Alias.GetHashCode(); + if (Parent is not null) + { + hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(Parent); + } + + hashCode = (hashCode * 397) ^ ParentId; + return hashCode; } } + + public static bool operator ==(NewEventArgs left, NewEventArgs right) => Equals(left, right); + + public static bool operator !=(NewEventArgs left, NewEventArgs right) => !Equals(left, right); } diff --git a/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs b/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs index a36368ea54b1..2c7a52f5b3a2 100644 --- a/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs +++ b/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs @@ -1,60 +1,65 @@ -using System; -using System.Collections.Generic; -using System.Linq; +namespace Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Events +/// +/// An IEventDispatcher that immediately raise all events. +/// +/// +/// This means that events will be raised during the scope transaction, +/// whatever happens, and the transaction could roll back in the end. +/// +internal class PassThroughEventDispatcher : IEventDispatcher { - /// - /// An IEventDispatcher that immediately raise all events. - /// - /// This means that events will be raised during the scope transaction, - /// whatever happens, and the transaction could roll back in the end. - internal class PassThroughEventDispatcher : IEventDispatcher + public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, + string? eventName = null) { - public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string? eventName = null) + if (eventHandler == null) { - if (eventHandler == null) return args.Cancel; - eventHandler(sender, args); return args.Cancel; } - public bool DispatchCancelable(EventHandler eventHandler, object sender, TArgs args, string? eventName = null) - where TArgs : CancellableEventArgs + eventHandler(sender, args); + return args.Cancel; + } + + public bool DispatchCancelable(EventHandler eventHandler, object sender, TArgs args, + string? eventName = null) + where TArgs : CancellableEventArgs + { + if (eventHandler == null) { - if (eventHandler == null) return args.Cancel; - eventHandler(sender, args); return args.Cancel; } - public bool DispatchCancelable(TypedEventHandler eventHandler, TSender sender, TArgs args, string? eventName = null) - where TArgs : CancellableEventArgs + eventHandler(sender, args); + return args.Cancel; + } + + public bool DispatchCancelable(TypedEventHandler eventHandler, TSender sender, + TArgs args, string? eventName = null) + where TArgs : CancellableEventArgs + { + if (eventHandler == null) { - if (eventHandler == null) return args.Cancel; - eventHandler(sender, args); return args.Cancel; } - public void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string? eventName = null) - { - eventHandler?.Invoke(sender, args); - } + eventHandler(sender, args); + return args.Cancel; + } - public void Dispatch(EventHandler eventHandler, object sender, TArgs args, string? eventName = null) - { - eventHandler?.Invoke(sender, args); - } + public void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string? eventName = null) => + eventHandler?.Invoke(sender, args); - public void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, string? eventName = null) - { - eventHandler?.Invoke(sender, args); - } + public void Dispatch(EventHandler eventHandler, object sender, TArgs args, + string? eventName = null) => eventHandler?.Invoke(sender, args); - public IEnumerable GetEvents(EventDefinitionFilter filter) - { - return Enumerable.Empty(); - } + public void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, + string? eventName = null) => eventHandler?.Invoke(sender, args); - public void ScopeExit(bool completed) - { } + public IEnumerable GetEvents(EventDefinitionFilter filter) => + Enumerable.Empty(); + + public void ScopeExit(bool completed) + { } } diff --git a/src/Umbraco.Core/Events/PublishEventArgs.cs b/src/Umbraco.Core/Events/PublishEventArgs.cs index 80b6dcd8c7d5..2e8996013c84 100644 --- a/src/Umbraco.Core/Events/PublishEventArgs.cs +++ b/src/Umbraco.Core/Events/PublishEventArgs.cs @@ -1,128 +1,141 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Events +public class PublishEventArgs : CancellableEnumerableObjectEventArgs, + IEquatable> { - public class PublishEventArgs : CancellableEnumerableObjectEventArgs, IEquatable> + /// + /// Constructor accepting multiple entities that are used in the publish operation + /// + /// + /// + /// + public PublishEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) { - /// - /// Constructor accepting multiple entities that are used in the publish operation - /// - /// - /// - /// - public PublishEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) - : base(eventObject, canCancel, eventMessages) - { - } + } - /// - /// Constructor accepting multiple entities that are used in the publish operation - /// - /// - /// - public PublishEventArgs(IEnumerable eventObject, EventMessages eventMessages) - : base(eventObject, eventMessages) - { - } + /// + /// Constructor accepting multiple entities that are used in the publish operation + /// + /// + /// + public PublishEventArgs(IEnumerable eventObject, EventMessages eventMessages) + : base(eventObject, eventMessages) + { + } - /// - /// Constructor accepting a single entity instance - /// - /// - /// - public PublishEventArgs(TEntity eventObject, EventMessages eventMessages) - : base(new List { eventObject }, eventMessages) - { - } + /// + /// Constructor accepting a single entity instance + /// + /// + /// + public PublishEventArgs(TEntity eventObject, EventMessages eventMessages) + : base(new List {eventObject}, eventMessages) + { + } - /// - /// Constructor accepting a single entity instance - /// - /// - /// - /// - public PublishEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) - : base(new List { eventObject }, canCancel, eventMessages) - { - } + /// + /// Constructor accepting a single entity instance + /// + /// + /// + /// + public PublishEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) + : base(new List {eventObject}, canCancel, eventMessages) + { + } - /// - /// Constructor accepting multiple entities that are used in the publish operation - /// - /// - /// - /// - public PublishEventArgs(IEnumerable eventObject, bool canCancel, bool isAllPublished) - : base(eventObject, canCancel) - { - } + /// + /// Constructor accepting multiple entities that are used in the publish operation + /// + /// + /// + /// + public PublishEventArgs(IEnumerable eventObject, bool canCancel, bool isAllPublished) + : base(eventObject, canCancel) + { + } - /// - /// Constructor accepting multiple entities that are used in the publish operation - /// - /// - public PublishEventArgs(IEnumerable eventObject) - : base(eventObject) - { - } + /// + /// Constructor accepting multiple entities that are used in the publish operation + /// + /// + public PublishEventArgs(IEnumerable eventObject) + : base(eventObject) + { + } + + /// + /// Constructor accepting a single entity instance + /// + /// + public PublishEventArgs(TEntity eventObject) + : base(new List {eventObject}) + { + } + + /// + /// Constructor accepting a single entity instance + /// + /// + /// + /// + public PublishEventArgs(TEntity eventObject, bool canCancel, bool isAllPublished) + : base(new List {eventObject}, canCancel) + { + } + + /// + /// Returns all entities that were published during the operation + /// + public IEnumerable? PublishedEntities => EventObject; - /// - /// Constructor accepting a single entity instance - /// - /// - public PublishEventArgs(TEntity eventObject) - : base(new List { eventObject }) + public bool Equals(PublishEventArgs? other) + { + if (ReferenceEquals(null, other)) { + return false; } - /// - /// Constructor accepting a single entity instance - /// - /// - /// - /// - public PublishEventArgs(TEntity eventObject, bool canCancel, bool isAllPublished) - : base(new List { eventObject }, canCancel) + if (ReferenceEquals(this, other)) { + return true; } - /// - /// Returns all entities that were published during the operation - /// - public IEnumerable? PublishedEntities => EventObject; + return base.Equals(other); + } - public bool Equals(PublishEventArgs? other) + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other); + return false; } - public override bool Equals(object? obj) + if (ReferenceEquals(this, obj)) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((PublishEventArgs) obj); + return true; } - public override int GetHashCode() + if (obj.GetType() != GetType()) { - unchecked - { - return (base.GetHashCode() * 397); - } + return false; } - public static bool operator ==(PublishEventArgs left, PublishEventArgs right) - { - return Equals(left, right); - } + return Equals((PublishEventArgs)obj); + } - public static bool operator !=(PublishEventArgs left, PublishEventArgs right) + public override int GetHashCode() + { + unchecked { - return !Equals(left, right); + return base.GetHashCode() * 397; } } + + public static bool operator ==(PublishEventArgs left, PublishEventArgs right) => + Equals(left, right); + + public static bool operator !=(PublishEventArgs left, PublishEventArgs right) => + !Equals(left, right); } diff --git a/src/Umbraco.Core/Events/QueuingEventDispatcher.cs b/src/Umbraco.Core/Events/QueuingEventDispatcher.cs index e79cd67cd8f7..ea24f409076a 100644 --- a/src/Umbraco.Core/Events/QueuingEventDispatcher.cs +++ b/src/Umbraco.Core/Events/QueuingEventDispatcher.cs @@ -3,41 +3,38 @@ using Umbraco.Cms.Core.IO; -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +/// +/// An IEventDispatcher that queues events, and raise them when the scope +/// exits and has been completed. +/// +public class QueuingEventDispatcher : QueuingEventDispatcherBase { - /// - /// An IEventDispatcher that queues events, and raise them when the scope - /// exits and has been completed. - /// - public class QueuingEventDispatcher : QueuingEventDispatcherBase + private readonly MediaFileManager _mediaFileManager; + + public QueuingEventDispatcher(MediaFileManager mediaFileManager) + : base(true) => + _mediaFileManager = mediaFileManager; + + protected override void ScopeExitCompleted() { - private readonly MediaFileManager _mediaFileManager; - public QueuingEventDispatcher(MediaFileManager mediaFileManager) - : base(true) - { - _mediaFileManager = mediaFileManager; - } + // processing only the last instance of each event... + // this is probably far from perfect, because if eg a content is saved in a list + // and then as a single content, the two events will probably not be de-duplicated, + // but it's better than nothing - protected override void ScopeExitCompleted() + foreach (IEventDefinition e in GetEvents(EventDefinitionFilter.LastIn)) { - // processing only the last instance of each event... - // this is probably far from perfect, because if eg a content is saved in a list - // and then as a single content, the two events will probably not be de-duplicated, - // but it's better than nothing + e.RaiseEvent(); - foreach (var e in GetEvents(EventDefinitionFilter.LastIn)) + // separating concerns means that this should probably not be here, + // but then where should it be (without making things too complicated)? + var delete = e.Args as IDeletingMediaFilesEventArgs; + if (delete != null && delete.MediaFilesToDelete.Count > 0) { - e.RaiseEvent(); - - // separating concerns means that this should probably not be here, - // but then where should it be (without making things too complicated)? - var delete = e.Args as IDeletingMediaFilesEventArgs; - if (delete != null && delete.MediaFilesToDelete.Count > 0) - _mediaFileManager.DeleteMediaFiles(delete.MediaFilesToDelete); + _mediaFileManager.DeleteMediaFiles(delete.MediaFilesToDelete); } } - - - } } diff --git a/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs b/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs index 71b7647b4f9b..18e9e760f90e 100644 --- a/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs +++ b/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs @@ -1,344 +1,424 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections; using Umbraco.Cms.Core.Collections; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +/// +/// An IEventDispatcher that queues events. +/// +/// +/// Can raise, or ignore, cancelable events, depending on option. +/// +/// Implementations must override ScopeExitCompleted to define what +/// to do with the events when the scope exits and has been completed. +/// +/// If the scope exits without being completed, events are ignored. +/// +public abstract class QueuingEventDispatcherBase : IEventDispatcher { - /// - /// An IEventDispatcher that queues events. - /// - /// - /// Can raise, or ignore, cancelable events, depending on option. - /// Implementations must override ScopeExitCompleted to define what - /// to do with the events when the scope exits and has been completed. - /// If the scope exits without being completed, events are ignored. - /// - public abstract class QueuingEventDispatcherBase : IEventDispatcher + private readonly bool _raiseCancelable; + + //events will be enlisted in the order they are raised + private List? _events; + + protected QueuingEventDispatcherBase(bool raiseCancelable) => _raiseCancelable = raiseCancelable; + + private List Events => _events ?? (_events = new List()); + + public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, + string? eventName = null) { - //events will be enlisted in the order they are raised - private List? _events; - private readonly bool _raiseCancelable; + if (eventHandler == null) + { + return args.Cancel; + } - protected QueuingEventDispatcherBase(bool raiseCancelable) + if (_raiseCancelable == false) { - _raiseCancelable = raiseCancelable; + return args.Cancel; } - private List Events => _events ?? (_events = new List()); + eventHandler(sender, args); + return args.Cancel; + } - public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string? eventName = null) + public bool DispatchCancelable(EventHandler eventHandler, object sender, TArgs args, + string? eventName = null) + where TArgs : CancellableEventArgs + { + if (eventHandler == null) { - if (eventHandler == null) return args.Cancel; - if (_raiseCancelable == false) return args.Cancel; - eventHandler(sender, args); return args.Cancel; } - public bool DispatchCancelable(EventHandler eventHandler, object sender, TArgs args, string? eventName = null) - where TArgs : CancellableEventArgs + if (_raiseCancelable == false) { - if (eventHandler == null) return args.Cancel; - if (_raiseCancelable == false) return args.Cancel; - eventHandler(sender, args); return args.Cancel; } - public bool DispatchCancelable(TypedEventHandler eventHandler, TSender sender, TArgs args, string? eventName = null) - where TArgs : CancellableEventArgs + eventHandler(sender, args); + return args.Cancel; + } + + public bool DispatchCancelable(TypedEventHandler eventHandler, TSender sender, + TArgs args, string? eventName = null) + where TArgs : CancellableEventArgs + { + if (eventHandler == null) { - if (eventHandler == null) return args.Cancel; - if (_raiseCancelable == false) return args.Cancel; - eventHandler(sender, args); return args.Cancel; } - public void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string? eventName = null) + if (_raiseCancelable == false) { - if (eventHandler == null) return; - Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); + return args.Cancel; } - public void Dispatch(EventHandler eventHandler, object sender, TArgs args, string? eventName = null) + eventHandler(sender, args); + return args.Cancel; + } + + public void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string? eventName = null) + { + if (eventHandler == null) { - if (eventHandler == null) return; - Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); + return; } - public void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, string? eventName = null) + Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); + } + + public void Dispatch(EventHandler eventHandler, object sender, TArgs args, string? eventName = null) + { + if (eventHandler == null) { - if (eventHandler == null) return; - Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); + return; } - public IEnumerable GetEvents(EventDefinitionFilter filter) + Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); + } + + public void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, + string? eventName = null) + { + if (eventHandler == null) { - if (_events == null) - return Enumerable.Empty(); + return; + } - IReadOnlyList events; - switch (filter) - { - case EventDefinitionFilter.All: - events = _events; - break; - case EventDefinitionFilter.FirstIn: - var l1 = new OrderedHashSet(); - foreach (var e in _events) - l1.Add(e); - events = l1; - break; - case EventDefinitionFilter.LastIn: - var l2 = new OrderedHashSet(keepOldest: false); - foreach (var e in _events) - l2.Add(e); - events = l2; - break; - default: - throw new ArgumentOutOfRangeException("filter", filter, null); - } + Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); + } - return FilterSupersededAndUpdateToLatestEntity(events); + public IEnumerable GetEvents(EventDefinitionFilter filter) + { + if (_events == null) + { + return Enumerable.Empty(); } - private class EventDefinitionInfos + IReadOnlyList events; + switch (filter) { - public IEventDefinition? EventDefinition { get; set; } - public Type[]? SupersedeTypes { get; set; } + case EventDefinitionFilter.All: + events = _events; + break; + case EventDefinitionFilter.FirstIn: + var l1 = new OrderedHashSet(); + foreach (IEventDefinition e in _events) + { + l1.Add(e); + } + + events = l1; + break; + case EventDefinitionFilter.LastIn: + var l2 = new OrderedHashSet(false); + foreach (IEventDefinition e in _events) + { + l2.Add(e); + } + + events = l2; + break; + default: + throw new ArgumentOutOfRangeException("filter", filter, null); } - // this is way too convoluted, the supersede attribute is used only on DeleteEventargs to specify - // that it supersedes save, publish, move and copy - BUT - publish event args is also used for - // unpublishing and should NOT be superseded - so really it should not be managed at event args - // level but at event level - // - // what we want is: - // if an entity is deleted, then all Saved, Moved, Copied, Published events prior to this should - // not trigger for the entity - and even though, does it make any sense? making a copy of an entity - // should ... trigger? + return FilterSupersededAndUpdateToLatestEntity(events); + } + + public void ScopeExit(bool completed) + { + if (_events == null) + { + return; + } + + if (completed) + { + ScopeExitCompleted(); + } + + _events.Clear(); + } + + // this is way too convoluted, the supersede attribute is used only on DeleteEventargs to specify + // that it supersedes save, publish, move and copy - BUT - publish event args is also used for + // unpublishing and should NOT be superseded - so really it should not be managed at event args + // level but at event level + // + // what we want is: + // if an entity is deleted, then all Saved, Moved, Copied, Published events prior to this should + // not trigger for the entity - and even though, does it make any sense? making a copy of an entity + // should ... trigger? + // + // not going to refactor it all - we probably want to *always* trigger event but tell people that + // due to scopes, they should not expected eg a saved entity to still be around - however, now, + // going to write a ugly condition to deal with U4-10764 + + // iterates over the events (latest first) and filter out any events or entities in event args that are included + // in more recent events that Supersede previous ones. For example, If an Entity has been Saved and then Deleted, we don't want + // to raise the Saved event (well actually we just don't want to include it in the args for that saved event) + internal static IEnumerable FilterSupersededAndUpdateToLatestEntity( + IReadOnlyList events) + { + // keeps the 'latest' entity and associated event data + var entities = new List>(); + + // collects the event definitions + // collects the arguments in result, that require their entities to be updated + var result = new List(); + var resultArgs = new List(); + + // eagerly fetch superseded arg types for each arg type + var argTypeSuperceeding = events.Select(x => x.Args.GetType()) + .Distinct() + .ToDictionary(x => x, + x => x.GetCustomAttributes(false).Select(y => y.SupersededEventArgsType) + .ToArray()); + + // iterate over all events and filter // - // not going to refactor it all - we probably want to *always* trigger event but tell people that - // due to scopes, they should not expected eg a saved entity to still be around - however, now, - // going to write a ugly condition to deal with U4-10764 - - // iterates over the events (latest first) and filter out any events or entities in event args that are included - // in more recent events that Supersede previous ones. For example, If an Entity has been Saved and then Deleted, we don't want - // to raise the Saved event (well actually we just don't want to include it in the args for that saved event) - internal static IEnumerable FilterSupersededAndUpdateToLatestEntity(IReadOnlyList events) + // process the list in reverse, because events are added in the order they are raised and we want to keep + // the latest (most recent) entities and filter out what is not relevant anymore (too old), eg if an entity + // is Deleted after being Saved, we want to filter out the Saved event + for (var index = events.Count - 1; index >= 0; index--) { - // keeps the 'latest' entity and associated event data - var entities = new List>(); - - // collects the event definitions - // collects the arguments in result, that require their entities to be updated - var result = new List(); - var resultArgs = new List(); - - // eagerly fetch superseded arg types for each arg type - var argTypeSuperceeding = events.Select(x => x.Args.GetType()) - .Distinct() - .ToDictionary(x => x, x => x.GetCustomAttributes(false).Select(y => y.SupersededEventArgsType).ToArray()); - - // iterate over all events and filter - // - // process the list in reverse, because events are added in the order they are raised and we want to keep - // the latest (most recent) entities and filter out what is not relevant anymore (too old), eg if an entity - // is Deleted after being Saved, we want to filter out the Saved event - for (var index = events.Count - 1; index >= 0; index--) + IEventDefinition def = events[index]; + + var infos = new EventDefinitionInfos { - var def = events[index]; + EventDefinition = def, SupersedeTypes = argTypeSuperceeding[def.Args.GetType()] + }; - var infos = new EventDefinitionInfos + var args = def.Args as CancellableObjectEventArgs; + if (args == null) + { + // not a cancellable event arg, include event definition in result + result.Add(def); + } + else + { + // event object can either be a single object or an enumerable of objects + // try to get as an enumerable, get null if it's not + IList eventObjects = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject); + if (eventObjects == null) { - EventDefinition = def, - SupersedeTypes = argTypeSuperceeding[def.Args.GetType()] - }; + // single object, cast as an IEntity + // if cannot cast, cannot filter, nothing - just include event definition in result + var eventEntity = args.EventObject as IEntity; + if (eventEntity == null) + { + result.Add(def); + continue; + } - var args = def.Args as CancellableObjectEventArgs; - if (args == null) - { - // not a cancellable event arg, include event definition in result - result.Add(def); + // look for this entity in superseding event args + // found = must be removed (ie not added), else track + if (IsSuperceeded(eventEntity, infos, entities) == false) + { + // track + entities.Add(Tuple.Create(eventEntity, infos)); + + // track result arguments + // include event definition in result + resultArgs.Add(args); + result.Add(def); + } } else { - // event object can either be a single object or an enumerable of objects - // try to get as an enumerable, get null if it's not - var eventObjects = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject); - if (eventObjects == null) + // enumerable of objects + var toRemove = new List(); + foreach (var eventObject in eventObjects) { - // single object, cast as an IEntity - // if cannot cast, cannot filter, nothing - just include event definition in result - var eventEntity = args.EventObject as IEntity; + // extract the event object, cast as an IEntity + // if cannot cast, cannot filter, nothing to do - just leave it in the list & continue + var eventEntity = eventObject as IEntity; if (eventEntity == null) { - result.Add(def); continue; } // look for this entity in superseding event args - // found = must be removed (ie not added), else track - if (IsSuperceeded(eventEntity, infos, entities) == false) + // found = must be removed, else track + if (IsSuperceeded(eventEntity, infos, entities)) { - // track - entities.Add(Tuple.Create(eventEntity, infos)); - - // track result arguments - // include event definition in result - resultArgs.Add(args); - result.Add(def); + toRemove.Add(eventEntity); } - } - else - { - // enumerable of objects - var toRemove = new List(); - foreach (var eventObject in eventObjects) + else { - // extract the event object, cast as an IEntity - // if cannot cast, cannot filter, nothing to do - just leave it in the list & continue - var eventEntity = eventObject as IEntity; - if (eventEntity == null) - continue; - - // look for this entity in superseding event args - // found = must be removed, else track - if (IsSuperceeded(eventEntity, infos, entities)) - toRemove.Add(eventEntity); - else - entities.Add(Tuple.Create(eventEntity, infos)); + entities.Add(Tuple.Create(eventEntity, infos)); } + } - // remove superseded entities - foreach (var entity in toRemove) - eventObjects.Remove(entity); + // remove superseded entities + foreach (IEntity entity in toRemove) + { + eventObjects.Remove(entity); + } - // if there are still entities in the list, keep the event definition - if (eventObjects.Count > 0) + // if there are still entities in the list, keep the event definition + if (eventObjects.Count > 0) + { + if (toRemove.Count > 0) { - if (toRemove.Count > 0) - { - // re-assign if changed - args.EventObject = eventObjects; - } - - // track result arguments - // include event definition in result - resultArgs.Add(args); - result.Add(def); + // re-assign if changed + args.EventObject = eventObjects; } + + // track result arguments + // include event definition in result + resultArgs.Add(args); + result.Add(def); } } } + } - // go over all args in result, and update them with the latest instanceof each entity - UpdateToLatestEntities(entities, resultArgs); + // go over all args in result, and update them with the latest instanceof each entity + UpdateToLatestEntities(entities, resultArgs); - // reverse, since we processed the list in reverse - result.Reverse(); + // reverse, since we processed the list in reverse + result.Reverse(); - return result; - } + return result; + } - // edits event args to use the latest instance of each entity - private static void UpdateToLatestEntities(IEnumerable> entities, IEnumerable args) + // edits event args to use the latest instance of each entity + private static void UpdateToLatestEntities(IEnumerable> entities, + IEnumerable args) + { + // get the latest entities + // ordered hash set + keepOldest will keep the latest inserted entity (in case of duplicates) + var latestEntities = new OrderedHashSet(true); + foreach (Tuple entity in entities.OrderByDescending(entity => + entity.Item1.UpdateDate)) { - // get the latest entities - // ordered hash set + keepOldest will keep the latest inserted entity (in case of duplicates) - var latestEntities = new OrderedHashSet(keepOldest: true); - foreach (var entity in entities.OrderByDescending(entity => entity.Item1.UpdateDate)) - latestEntities.Add(entity.Item1); + latestEntities.Add(entity.Item1); + } - foreach (var arg in args) + foreach (CancellableObjectEventArgs arg in args) + { + // event object can either be a single object or an enumerable of objects + // try to get as an enumerable, get null if it's not + IList eventObjects = TypeHelper.CreateGenericEnumerableFromObject(arg.EventObject); + if (eventObjects == null) { - // event object can either be a single object or an enumerable of objects - // try to get as an enumerable, get null if it's not - var eventObjects = TypeHelper.CreateGenericEnumerableFromObject(arg.EventObject); - if (eventObjects == null) + // single object + // look for a more recent entity for that object, and replace if any + // works by "equalling" entities ie the more recent one "equals" this one (though different object) + IEntity foundEntity = latestEntities.FirstOrDefault(x => Equals(x, arg.EventObject)); + if (foundEntity != null) { - // single object - // look for a more recent entity for that object, and replace if any - // works by "equalling" entities ie the more recent one "equals" this one (though different object) - var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, arg.EventObject)); - if (foundEntity != null) - arg.EventObject = foundEntity; + arg.EventObject = foundEntity; } - else + } + else + { + // enumerable of objects + // same as above but for each object + var updated = false; + for (var i = 0; i < eventObjects.Count; i++) { - // enumerable of objects - // same as above but for each object - var updated = false; - for (var i = 0; i < eventObjects.Count; i++) + IEntity foundEntity = latestEntities.FirstOrDefault(x => Equals(x, eventObjects[i])); + if (foundEntity == null) { - var foundEntity = latestEntities.FirstOrDefault(x => Equals(x, eventObjects[i])); - if (foundEntity == null) continue; - eventObjects[i] = foundEntity; - updated = true; + continue; } - if (updated) - arg.EventObject = eventObjects; + eventObjects[i] = foundEntity; + updated = true; + } + + if (updated) + { + arg.EventObject = eventObjects; } } } + } + + // determines if a given entity, appearing in a given event definition, should be filtered out, + // considering the entities that have already been visited - an entity is filtered out if it + // appears in another even definition, which supersedes this event definition. + private static bool IsSuperceeded(IEntity entity, EventDefinitionInfos infos, + List> entities) + { + //var argType = meta.EventArgsType; + Type argType = infos.EventDefinition?.Args.GetType(); + + // look for other instances of the same entity, coming from an event args that supersedes other event args, + // ie is marked with the attribute, and is not this event args (cannot supersede itself) + Tuple[] superceeding = entities + .Where(x => x.Item2.SupersedeTypes?.Length > 0 // has the attribute + && x.Item2.EventDefinition?.Args.GetType() != argType // is not the same + && Equals(x.Item1, entity)) // same entity + .ToArray(); + + // first time we see this entity = not filtered + if (superceeding.Length == 0) + { + return false; + } - // determines if a given entity, appearing in a given event definition, should be filtered out, - // considering the entities that have already been visited - an entity is filtered out if it - // appears in another even definition, which supersedes this event definition. - private static bool IsSuperceeded(IEntity entity, EventDefinitionInfos infos, List> entities) + // delete event args does NOT supersedes 'unpublished' event + if ((argType?.IsGenericType ?? false) && argType.GetGenericTypeDefinition() == typeof(PublishEventArgs<>) && + infos.EventDefinition?.EventName == "Unpublished") { - //var argType = meta.EventArgsType; - var argType = infos.EventDefinition?.Args.GetType(); - - // look for other instances of the same entity, coming from an event args that supersedes other event args, - // ie is marked with the attribute, and is not this event args (cannot supersede itself) - var superceeding = entities - .Where(x => x.Item2.SupersedeTypes?.Length > 0 // has the attribute - && x.Item2.EventDefinition?.Args.GetType() != argType // is not the same - && Equals(x.Item1, entity)) // same entity - .ToArray(); - - // first time we see this entity = not filtered - if (superceeding.Length == 0) - return false; - - // delete event args does NOT supersedes 'unpublished' event - if ((argType?.IsGenericType ?? false) && argType.GetGenericTypeDefinition() == typeof(PublishEventArgs<>) && infos.EventDefinition?.EventName == "Unpublished") - return false; - - // found occurrences, need to determine if this event args is superseded - if (argType?.IsGenericType ?? false) - { - // generic, must compare type arguments - var supercededBy = superceeding.FirstOrDefault(x => - x.Item2.SupersedeTypes?.Any(y => - // superseding a generic type which has the same generic type definition - // (but ... no matter the generic type parameters? could be different?) - y.IsGenericTypeDefinition && y == argType.GetGenericTypeDefinition() - // or superceeding a non-generic type which is ... (but... how is this ever possible? argType *is* generic? - || y.IsGenericTypeDefinition == false && y == argType) ?? false); - return supercededBy != null; - } - else - { - // non-generic, can compare types 1:1 - var supercededBy = superceeding.FirstOrDefault(x => - x.Item2.SupersedeTypes?.Any(y => y == argType) ?? false); - return supercededBy != null; - } + return false; } - public void ScopeExit(bool completed) + // found occurrences, need to determine if this event args is superseded + if (argType?.IsGenericType ?? false) + { + // generic, must compare type arguments + Tuple supercededBy = superceeding.FirstOrDefault(x => + x.Item2.SupersedeTypes?.Any(y => + // superseding a generic type which has the same generic type definition + // (but ... no matter the generic type parameters? could be different?) + (y.IsGenericTypeDefinition && y == argType.GetGenericTypeDefinition()) + // or superceeding a non-generic type which is ... (but... how is this ever possible? argType *is* generic? + || (y.IsGenericTypeDefinition == false && y == argType)) ?? false); + return supercededBy != null; + } + else { - if (_events == null) return; - if (completed) - ScopeExitCompleted(); - _events.Clear(); + // non-generic, can compare types 1:1 + Tuple supercededBy = superceeding.FirstOrDefault(x => + x.Item2.SupersedeTypes?.Any(y => y == argType) ?? false); + return supercededBy != null; } + } + + protected abstract void ScopeExitCompleted(); - protected abstract void ScopeExitCompleted(); + private class EventDefinitionInfos + { + public IEventDefinition? EventDefinition { get; set; } + public Type[]? SupersedeTypes { get; set; } } } diff --git a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs index ee0d43a07a6b..39543e82bd9c 100644 --- a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs +++ b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs @@ -1,77 +1,84 @@ -using System; +namespace Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Events +public class RecycleBinEventArgs : CancellableEventArgs, IEquatable { - public class RecycleBinEventArgs : CancellableEventArgs, IEquatable - { - public RecycleBinEventArgs(Guid nodeObjectType, EventMessages eventMessages) - : base(true, eventMessages) - { - NodeObjectType = nodeObjectType; - } + public RecycleBinEventArgs(Guid nodeObjectType, EventMessages eventMessages) + : base(true, eventMessages) => + NodeObjectType = nodeObjectType; - public RecycleBinEventArgs(Guid nodeObjectType) - : base(true) - { - NodeObjectType = nodeObjectType; + public RecycleBinEventArgs(Guid nodeObjectType) + : base(true) => + NodeObjectType = nodeObjectType; - } + /// + /// Gets the Id of the node object type of the items + /// being deleted from the Recycle Bin. + /// + public Guid NodeObjectType { get; } - /// - /// Gets the Id of the node object type of the items - /// being deleted from the Recycle Bin. - /// - public Guid NodeObjectType { get; } + /// + /// Boolean indicating whether the Recycle Bin was emptied successfully + /// + public bool RecycleBinEmptiedSuccessfully { get; set; } - /// - /// Boolean indicating whether the Recycle Bin was emptied successfully - /// - public bool RecycleBinEmptiedSuccessfully { get; set; } + /// + /// Boolean indicating whether this event was fired for the Content's Recycle Bin. + /// + public bool IsContentRecycleBin => NodeObjectType == Constants.ObjectTypes.Document; - /// - /// Boolean indicating whether this event was fired for the Content's Recycle Bin. - /// - public bool IsContentRecycleBin => NodeObjectType == Constants.ObjectTypes.Document; + /// + /// Boolean indicating whether this event was fired for the Media's Recycle Bin. + /// + public bool IsMediaRecycleBin => NodeObjectType == Constants.ObjectTypes.Media; - /// - /// Boolean indicating whether this event was fired for the Media's Recycle Bin. - /// - public bool IsMediaRecycleBin => NodeObjectType == Constants.ObjectTypes.Media; + public bool Equals(RecycleBinEventArgs? other) + { + if (ReferenceEquals(null, other)) + { + return false; + } - public bool Equals(RecycleBinEventArgs? other) + if (ReferenceEquals(this, other)) { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return base.Equals(other) && NodeObjectType.Equals(other.NodeObjectType) && RecycleBinEmptiedSuccessfully == other.RecycleBinEmptiedSuccessfully; + return true; } - public override bool Equals(object? obj) + return base.Equals(other) && NodeObjectType.Equals(other.NodeObjectType) && + RecycleBinEmptiedSuccessfully == other.RecycleBinEmptiedSuccessfully; + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((RecycleBinEventArgs) obj); + return false; } - public override int GetHashCode() + if (ReferenceEquals(this, obj)) { - unchecked - { - int hashCode = base.GetHashCode(); - hashCode = (hashCode * 397) ^ NodeObjectType.GetHashCode(); - hashCode = (hashCode * 397) ^ RecycleBinEmptiedSuccessfully.GetHashCode(); - return hashCode; - } + return true; } - public static bool operator ==(RecycleBinEventArgs left, RecycleBinEventArgs right) + if (obj.GetType() != GetType()) { - return Equals(left, right); + return false; } - public static bool operator !=(RecycleBinEventArgs left, RecycleBinEventArgs right) + return Equals((RecycleBinEventArgs)obj); + } + + public override int GetHashCode() + { + unchecked { - return !Equals(left, right); + var hashCode = base.GetHashCode(); + hashCode = (hashCode * 397) ^ NodeObjectType.GetHashCode(); + hashCode = (hashCode * 397) ^ RecycleBinEmptiedSuccessfully.GetHashCode(); + return hashCode; } } + + public static bool operator ==(RecycleBinEventArgs left, RecycleBinEventArgs right) => Equals(left, right); + + public static bool operator !=(RecycleBinEventArgs left, RecycleBinEventArgs right) => !Equals(left, right); } diff --git a/src/Umbraco.Core/Events/RefreshContentEventArgs.cs b/src/Umbraco.Core/Events/RefreshContentEventArgs.cs index c41043a03996..366a4c29bc2d 100644 --- a/src/Umbraco.Core/Events/RefreshContentEventArgs.cs +++ b/src/Umbraco.Core/Events/RefreshContentEventArgs.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events -{ - //public class RefreshContentEventArgs : System.ComponentModel.CancelEventArgs { } -} +namespace Umbraco.Cms.Core.Events; + + +//public class RefreshContentEventArgs : System.ComponentModel.CancelEventArgs { } diff --git a/src/Umbraco.Core/Events/RelateOnCopyNotificationHandler.cs b/src/Umbraco.Core/Events/RelateOnCopyNotificationHandler.cs index f37d8723a7fc..b1319687b9ee 100644 --- a/src/Umbraco.Core/Events/RelateOnCopyNotificationHandler.cs +++ b/src/Umbraco.Core/Events/RelateOnCopyNotificationHandler.cs @@ -5,48 +5,48 @@ using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +public class RelateOnCopyNotificationHandler : INotificationHandler { - public class RelateOnCopyNotificationHandler : INotificationHandler + private readonly IAuditService _auditService; + private readonly IRelationService _relationService; + + public RelateOnCopyNotificationHandler(IRelationService relationService, IAuditService auditService) { - private readonly IRelationService _relationService; - private readonly IAuditService _auditService; + _relationService = relationService; + _auditService = auditService; + } - public RelateOnCopyNotificationHandler(IRelationService relationService, IAuditService auditService) + public void Handle(ContentCopiedNotification notification) + { + if (notification.RelateToOriginal == false) { - _relationService = relationService; - _auditService = auditService; + return; } - public void Handle(ContentCopiedNotification notification) + IRelationType relationType = + _relationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias); + + if (relationType == null) { - if (notification.RelateToOriginal == false) - { - return; - } - - var relationType = _relationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias); - - if (relationType == null) - { - relationType = new RelationType(Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, - Constants.Conventions.RelationTypes.RelateDocumentOnCopyName, - true, - Constants.ObjectTypes.Document, - Constants.ObjectTypes.Document, - false); - - _relationService.Save(relationType); - } - - var relation = new Relation(notification.Original.Id, notification.Copy.Id, relationType); - _relationService.Save(relation); - - _auditService.Add( - AuditType.Copy, - notification.Copy.WriterId, - notification.Copy.Id, ObjectTypes.GetName(UmbracoObjectTypes.Document) ?? string.Empty, - $"Copied content with Id: '{notification.Copy.Id}' related to original content with Id: '{notification.Original.Id}'"); + relationType = new RelationType(Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, + Constants.Conventions.RelationTypes.RelateDocumentOnCopyName, + true, + Constants.ObjectTypes.Document, + Constants.ObjectTypes.Document, + false); + + _relationService.Save(relationType); } + + var relation = new Relation(notification.Original.Id, notification.Copy.Id, relationType); + _relationService.Save(relation); + + _auditService.Add( + AuditType.Copy, + notification.Copy.WriterId, + notification.Copy.Id, UmbracoObjectTypes.Document.GetName() ?? string.Empty, + $"Copied content with Id: '{notification.Copy.Id}' related to original content with Id: '{notification.Original.Id}'"); } } diff --git a/src/Umbraco.Core/Events/RolesEventArgs.cs b/src/Umbraco.Core/Events/RolesEventArgs.cs index a4fb6c3d18f5..d71dcef9fbd7 100644 --- a/src/Umbraco.Core/Events/RolesEventArgs.cs +++ b/src/Umbraco.Core/Events/RolesEventArgs.cs @@ -1,16 +1,13 @@ -using System; +namespace Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Events +public class RolesEventArgs : EventArgs { - public class RolesEventArgs : EventArgs + public RolesEventArgs(int[] memberIds, string[] roles) { - public RolesEventArgs(int[] memberIds, string[] roles) - { - MemberIds = memberIds; - Roles = roles; - } - - public int[] MemberIds { get; set; } - public string[] Roles { get; set; } + MemberIds = memberIds; + Roles = roles; } + + public int[] MemberIds { get; set; } + public string[] Roles { get; set; } } diff --git a/src/Umbraco.Core/Events/RollbackEventArgs.cs b/src/Umbraco.Core/Events/RollbackEventArgs.cs index 96b67ba769fb..4debddd60964 100644 --- a/src/Umbraco.Core/Events/RollbackEventArgs.cs +++ b/src/Umbraco.Core/Events/RollbackEventArgs.cs @@ -1,21 +1,17 @@ -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +public class RollbackEventArgs : CancellableObjectEventArgs { - public class RollbackEventArgs : CancellableObjectEventArgs + public RollbackEventArgs(TEntity eventObject, bool canCancel) : base(eventObject, canCancel) { - public RollbackEventArgs(TEntity eventObject, bool canCancel) : base(eventObject, canCancel) - { - } - - public RollbackEventArgs(TEntity eventObject) : base(eventObject) - { - } + } - /// - /// The entity being rolledback - /// - public TEntity? Entity - { - get { return EventObject; } - } + public RollbackEventArgs(TEntity eventObject) : base(eventObject) + { } + + /// + /// The entity being rolledback + /// + public TEntity? Entity => EventObject; } diff --git a/src/Umbraco.Core/Events/SaveEventArgs.cs b/src/Umbraco.Core/Events/SaveEventArgs.cs index 3424962a54b9..2678df1f9dd4 100644 --- a/src/Umbraco.Core/Events/SaveEventArgs.cs +++ b/src/Umbraco.Core/Events/SaveEventArgs.cs @@ -1,117 +1,116 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Events +public class SaveEventArgs : CancellableEnumerableObjectEventArgs { - public class SaveEventArgs : CancellableEnumerableObjectEventArgs + /// + /// Constructor accepting multiple entities that are used in the saving operation + /// + /// + /// + /// + /// + public SaveEventArgs(IEnumerable eventObject, bool canCancel, EventMessages messages, + IDictionary additionalData) + : base(eventObject, canCancel, messages, additionalData) { - /// - /// Constructor accepting multiple entities that are used in the saving operation - /// - /// - /// - /// - /// - public SaveEventArgs(IEnumerable eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) - : base(eventObject, canCancel, messages, additionalData) - { - } - - /// - /// Constructor accepting multiple entities that are used in the saving operation - /// - /// - /// - /// - public SaveEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) - : base(eventObject, canCancel, eventMessages) - { - } + } - /// - /// Constructor accepting multiple entities that are used in the saving operation - /// - /// - /// - public SaveEventArgs(IEnumerable eventObject, EventMessages eventMessages) - : base(eventObject, eventMessages) - { - } + /// + /// Constructor accepting multiple entities that are used in the saving operation + /// + /// + /// + /// + public SaveEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) + { + } - /// - /// Constructor accepting a single entity instance - /// - /// - /// - /// - /// - public SaveEventArgs(TEntity eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) - : base(new List { eventObject }, canCancel, messages, additionalData) - { - } + /// + /// Constructor accepting multiple entities that are used in the saving operation + /// + /// + /// + public SaveEventArgs(IEnumerable eventObject, EventMessages eventMessages) + : base(eventObject, eventMessages) + { + } - /// - /// Constructor accepting a single entity instance - /// - /// - /// - public SaveEventArgs(TEntity eventObject, EventMessages eventMessages) - : base(new List { eventObject }, eventMessages) - { - } + /// + /// Constructor accepting a single entity instance + /// + /// + /// + /// + /// + public SaveEventArgs(TEntity eventObject, bool canCancel, EventMessages messages, + IDictionary additionalData) + : base(new List {eventObject}, canCancel, messages, additionalData) + { + } - /// - /// Constructor accepting a single entity instance - /// - /// - /// - /// - public SaveEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) - : base(new List { eventObject }, canCancel, eventMessages) - { - } + /// + /// Constructor accepting a single entity instance + /// + /// + /// + public SaveEventArgs(TEntity eventObject, EventMessages eventMessages) + : base(new List {eventObject}, eventMessages) + { + } + /// + /// Constructor accepting a single entity instance + /// + /// + /// + /// + public SaveEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) + : base(new List {eventObject}, canCancel, eventMessages) + { + } - /// - /// Constructor accepting multiple entities that are used in the saving operation - /// - /// - /// - public SaveEventArgs(IEnumerable eventObject, bool canCancel) - : base(eventObject, canCancel) - { - } - /// - /// Constructor accepting multiple entities that are used in the saving operation - /// - /// - public SaveEventArgs(IEnumerable eventObject) - : base(eventObject) - { - } + /// + /// Constructor accepting multiple entities that are used in the saving operation + /// + /// + /// + public SaveEventArgs(IEnumerable eventObject, bool canCancel) + : base(eventObject, canCancel) + { + } - /// - /// Constructor accepting a single entity instance - /// - /// - public SaveEventArgs(TEntity eventObject) - : base(new List { eventObject }) - { - } + /// + /// Constructor accepting multiple entities that are used in the saving operation + /// + /// + public SaveEventArgs(IEnumerable eventObject) + : base(eventObject) + { + } - /// - /// Constructor accepting a single entity instance - /// - /// - /// - public SaveEventArgs(TEntity eventObject, bool canCancel) - : base(new List { eventObject }, canCancel) - { - } + /// + /// Constructor accepting a single entity instance + /// + /// + public SaveEventArgs(TEntity eventObject) + : base(new List {eventObject}) + { + } - /// - /// Returns all entities that were saved during the operation - /// - public IEnumerable? SavedEntities => EventObject; + /// + /// Constructor accepting a single entity instance + /// + /// + /// + public SaveEventArgs(TEntity eventObject, bool canCancel) + : base(new List {eventObject}, canCancel) + { } + + /// + /// Returns all entities that were saved during the operation + /// + public IEnumerable? SavedEntities => EventObject; } diff --git a/src/Umbraco.Core/Events/ScopedNotificationPublisher.cs b/src/Umbraco.Core/Events/ScopedNotificationPublisher.cs index cdd8707a79f9..6681d321b728 100644 --- a/src/Umbraco.Core/Events/ScopedNotificationPublisher.cs +++ b/src/Umbraco.Core/Events/ScopedNotificationPublisher.cs @@ -1,135 +1,133 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Threading.Tasks; using Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +public class ScopedNotificationPublisher : IScopedNotificationPublisher { - public class ScopedNotificationPublisher : IScopedNotificationPublisher + private readonly IEventAggregator _eventAggregator; + private readonly object _locker = new(); + private readonly List _notificationOnScopeCompleted; + private bool _isSuppressed; + + public ScopedNotificationPublisher(IEventAggregator eventAggregator) { - private readonly IEventAggregator _eventAggregator; - private readonly List _notificationOnScopeCompleted; - private readonly object _locker = new object(); - private bool _isSuppressed = false; + _eventAggregator = eventAggregator; + _notificationOnScopeCompleted = new List(); + } - public ScopedNotificationPublisher(IEventAggregator eventAggregator) + public bool PublishCancelable(ICancelableNotification notification) + { + if (notification == null) { - _eventAggregator = eventAggregator; - _notificationOnScopeCompleted = new List(); + throw new ArgumentNullException(nameof(notification)); } - public bool PublishCancelable(ICancelableNotification notification) + if (_isSuppressed) { - if (notification == null) - { - throw new ArgumentNullException(nameof(notification)); - } + return false; + } - if (_isSuppressed) - { - return false; - } + _eventAggregator.Publish(notification); + return notification.Cancel; + } - _eventAggregator.Publish(notification); - return notification.Cancel; + public async Task PublishCancelableAsync(ICancelableNotification notification) + { + if (notification == null) + { + throw new ArgumentNullException(nameof(notification)); } - public async Task PublishCancelableAsync(ICancelableNotification notification) + if (_isSuppressed) { - if (notification == null) - { - throw new ArgumentNullException(nameof(notification)); - } + return false; + } - if (_isSuppressed) - { - return false; - } + Task task = _eventAggregator.PublishAsync(notification); + if (task is not null) + { + await task; + } - var task = _eventAggregator.PublishAsync(notification); - if (task is not null) - { - await task; - } + return notification.Cancel; + } - return notification.Cancel; + public void Publish(INotification notification) + { + if (notification == null) + { + throw new ArgumentNullException(nameof(notification)); } - public void Publish(INotification notification) + if (_isSuppressed) { - if (notification == null) - { - throw new ArgumentNullException(nameof(notification)); - } - - if (_isSuppressed) - { - return; - } - - _notificationOnScopeCompleted.Add(notification); + return; } - public void ScopeExit(bool completed) + _notificationOnScopeCompleted.Add(notification); + } + + public void ScopeExit(bool completed) + { + try { - try + if (completed) { - if (completed) + foreach (INotification notification in _notificationOnScopeCompleted) { - foreach (INotification notification in _notificationOnScopeCompleted) - { - _eventAggregator.Publish(notification); - } + _eventAggregator.Publish(notification); } } - finally - { - _notificationOnScopeCompleted.Clear(); - } } + finally + { + _notificationOnScopeCompleted.Clear(); + } + } - public IDisposable Suppress() + public IDisposable Suppress() + { + lock (_locker) { - lock(_locker) + if (_isSuppressed) { - if (_isSuppressed) - { - throw new InvalidOperationException("Notifications are already suppressed"); - } - return new Suppressor(this); + throw new InvalidOperationException("Notifications are already suppressed"); } + + return new Suppressor(this); } + } - private class Suppressor : IDisposable + private class Suppressor : IDisposable + { + private readonly ScopedNotificationPublisher _scopedNotificationPublisher; + private bool _disposedValue; + + public Suppressor(ScopedNotificationPublisher scopedNotificationPublisher) { - private bool _disposedValue; - private readonly ScopedNotificationPublisher _scopedNotificationPublisher; + _scopedNotificationPublisher = scopedNotificationPublisher; + _scopedNotificationPublisher._isSuppressed = true; + } - public Suppressor(ScopedNotificationPublisher scopedNotificationPublisher) - { - _scopedNotificationPublisher = scopedNotificationPublisher; - _scopedNotificationPublisher._isSuppressed = true; - } + public void Dispose() => Dispose(true); - protected virtual void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) { - if (!_disposedValue) + if (disposing) { - if (disposing) + lock (_scopedNotificationPublisher._locker) { - lock (_scopedNotificationPublisher._locker) - { - _scopedNotificationPublisher._isSuppressed = false; - } + _scopedNotificationPublisher._isSuppressed = false; } - _disposedValue = true; } + + _disposedValue = true; } - public void Dispose() => Dispose(disposing: true); } } } diff --git a/src/Umbraco.Core/Events/SendEmailEventArgs.cs b/src/Umbraco.Core/Events/SendEmailEventArgs.cs index c1e626c6c127..094ffc847aa9 100644 --- a/src/Umbraco.Core/Events/SendEmailEventArgs.cs +++ b/src/Umbraco.Core/Events/SendEmailEventArgs.cs @@ -1,15 +1,10 @@ -using System; -using Umbraco.Cms.Core.Models.Email; +using Umbraco.Cms.Core.Models.Email; -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +public class SendEmailEventArgs : EventArgs { - public class SendEmailEventArgs : EventArgs - { - public EmailMessage Message { get; } + public SendEmailEventArgs(EmailMessage message) => Message = message; - public SendEmailEventArgs(EmailMessage message) - { - Message = message; - } - } + public EmailMessage Message { get; } } diff --git a/src/Umbraco.Core/Events/SendToPublishEventArgs.cs b/src/Umbraco.Core/Events/SendToPublishEventArgs.cs index 9b4e07814941..815972b8e5c5 100644 --- a/src/Umbraco.Core/Events/SendToPublishEventArgs.cs +++ b/src/Umbraco.Core/Events/SendToPublishEventArgs.cs @@ -1,21 +1,17 @@ -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +public class SendToPublishEventArgs : CancellableObjectEventArgs { - public class SendToPublishEventArgs : CancellableObjectEventArgs + public SendToPublishEventArgs(TEntity eventObject, bool canCancel) : base(eventObject, canCancel) { - public SendToPublishEventArgs(TEntity eventObject, bool canCancel) : base(eventObject, canCancel) - { - } - - public SendToPublishEventArgs(TEntity eventObject) : base(eventObject) - { - } + } - /// - /// The entity being sent to publish - /// - public TEntity? Entity - { - get { return EventObject; } - } + public SendToPublishEventArgs(TEntity eventObject) : base(eventObject) + { } + + /// + /// The entity being sent to publish + /// + public TEntity? Entity => EventObject; } diff --git a/src/Umbraco.Core/Events/SupersedeEventAttribute.cs b/src/Umbraco.Core/Events/SupersedeEventAttribute.cs index d733f0706a88..89cf4f20dd10 100644 --- a/src/Umbraco.Core/Events/SupersedeEventAttribute.cs +++ b/src/Umbraco.Core/Events/SupersedeEventAttribute.cs @@ -1,20 +1,15 @@ -using System; +namespace Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Events +/// +/// This is used to know if the event arg attributed should supersede another event arg type when +/// tracking events for the same entity. If one event args supersedes another then the event args that have been +/// superseded +/// will mean that the event will not be dispatched or the args will be filtered to exclude the entity. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] +public class SupersedeEventAttribute : Attribute { - /// - /// This is used to know if the event arg attributed should supersede another event arg type when - /// tracking events for the same entity. If one event args supersedes another then the event args that have been superseded - /// will mean that the event will not be dispatched or the args will be filtered to exclude the entity. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - public class SupersedeEventAttribute : Attribute - { - public Type SupersededEventArgsType { get; private set; } + public SupersedeEventAttribute(Type supersededEventArgsType) => SupersededEventArgsType = supersededEventArgsType; - public SupersedeEventAttribute(Type supersededEventArgsType) - { - SupersededEventArgsType = supersededEventArgsType; - } - } + public Type SupersededEventArgsType { get; } } diff --git a/src/Umbraco.Core/Events/TransientEventMessagesFactory.cs b/src/Umbraco.Core/Events/TransientEventMessagesFactory.cs index 2c8dde89a235..383be4a6eb23 100644 --- a/src/Umbraco.Core/Events/TransientEventMessagesFactory.cs +++ b/src/Umbraco.Core/Events/TransientEventMessagesFactory.cs @@ -1,18 +1,11 @@ -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +/// +/// A simple/default transient messages factory +/// +public class TransientEventMessagesFactory : IEventMessagesFactory { - /// - /// A simple/default transient messages factory - /// - public class TransientEventMessagesFactory : IEventMessagesFactory - { - public EventMessages Get() - { - return new EventMessages(); - } + public EventMessages Get() => new EventMessages(); - public EventMessages? GetOrDefault() - { - return null; - } - } + public EventMessages? GetOrDefault() => null; } diff --git a/src/Umbraco.Core/Events/TypedEventHandler.cs b/src/Umbraco.Core/Events/TypedEventHandler.cs index 11301448e0d0..d65dc9e9216e 100644 --- a/src/Umbraco.Core/Events/TypedEventHandler.cs +++ b/src/Umbraco.Core/Events/TypedEventHandler.cs @@ -1,7 +1,4 @@ -using System; +namespace Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Events -{ - [Serializable] - public delegate void TypedEventHandler(TSender sender, TEventArgs e); -} +[Serializable] +public delegate void TypedEventHandler(TSender sender, TEventArgs e); diff --git a/src/Umbraco.Core/Events/UserGroupWithUsers.cs b/src/Umbraco.Core/Events/UserGroupWithUsers.cs index 17946a781f4f..b5f15124876e 100644 --- a/src/Umbraco.Core/Events/UserGroupWithUsers.cs +++ b/src/Umbraco.Core/Events/UserGroupWithUsers.cs @@ -1,18 +1,17 @@ using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +public class UserGroupWithUsers { - public class UserGroupWithUsers + public UserGroupWithUsers(IUserGroup userGroup, IUser[] addedUsers, IUser[] removedUsers) { - public UserGroupWithUsers(IUserGroup userGroup, IUser[] addedUsers, IUser[] removedUsers) - { - UserGroup = userGroup; - AddedUsers = addedUsers; - RemovedUsers = removedUsers; - } - - public IUserGroup UserGroup { get; } - public IUser[] AddedUsers { get; } - public IUser[] RemovedUsers { get; } + UserGroup = userGroup; + AddedUsers = addedUsers; + RemovedUsers = removedUsers; } + + public IUserGroup UserGroup { get; } + public IUser[] AddedUsers { get; } + public IUser[] RemovedUsers { get; } } diff --git a/src/Umbraco.Core/Events/UserNotificationsHandler.cs b/src/Umbraco.Core/Events/UserNotificationsHandler.cs index 96425e644f30..151bdacc8228 100644 --- a/src/Umbraco.Core/Events/UserNotificationsHandler.cs +++ b/src/Umbraco.Core/Events/UserNotificationsHandler.cs @@ -1,10 +1,7 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Actions; @@ -18,221 +15,245 @@ using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Events +namespace Umbraco.Cms.Core.Events; + +public sealed class UserNotificationsHandler : + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler { - public sealed class UserNotificationsHandler : - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler + private readonly ActionCollection _actions; + private readonly IContentService _contentService; + private readonly Notifier _notifier; + + public UserNotificationsHandler(Notifier notifier, ActionCollection actions, IContentService contentService) { - private readonly Notifier _notifier; - private readonly ActionCollection _actions; - private readonly IContentService _contentService; + _notifier = notifier; + _actions = actions; + _contentService = contentService; + } - public UserNotificationsHandler(Notifier notifier, ActionCollection actions, IContentService contentService) + public void Handle(AssignedUserGroupPermissionsNotification notification) + { + IContent[] entities = _contentService.GetByIds(notification.EntityPermissions.Select(e => e.EntityId)) + ?.ToArray(); + if (entities?.Any() == false) { - _notifier = notifier; - _actions = actions; - _contentService = contentService; + return; } - public void Handle(ContentSavedNotification notification) + _notifier.Notify(_actions.GetAction(), entities!); + } + + public void Handle(ContentCopiedNotification notification) => + _notifier.Notify(_actions.GetAction(), notification.Original); + + public void Handle(ContentMovedNotification notification) + { + // notify about the move for all moved items + _notifier.Notify(_actions.GetAction(), + notification.MoveInfoCollection.Select(m => m.Entity).ToArray()); + + // for any items being moved from the recycle bin (restored), explicitly notify about that too + IContent[] restoredEntities = notification.MoveInfoCollection + .Where(m => m.OriginalPath.Contains(Constants.System.RecycleBinContentString)) + .Select(m => m.Entity) + .ToArray(); + if (restoredEntities.Any()) { - var newEntities = new List(); - var updatedEntities = new List(); + _notifier.Notify(_actions.GetAction(), restoredEntities); + } + } + + public void Handle(ContentMovedToRecycleBinNotification notification) => _notifier.Notify( + _actions.GetAction(), notification.MoveInfoCollection.Select(m => m.Entity).ToArray()); + + public void Handle(ContentPublishedNotification notification) => + _notifier.Notify(_actions.GetAction(), notification.PublishedEntities.ToArray()); + + public void Handle(ContentRolledBackNotification notification) => + _notifier.Notify(_actions.GetAction(), notification.Entity); + + public void Handle(ContentSavedNotification notification) + { + var newEntities = new List(); + var updatedEntities = new List(); - //need to determine if this is updating or if it is new - foreach (var entity in notification.SavedEntities) + //need to determine if this is updating or if it is new + foreach (IContent entity in notification.SavedEntities) + { + var dirty = (IRememberBeingDirty)entity; + if (dirty.WasPropertyDirty("Id")) { - var dirty = (IRememberBeingDirty)entity; - if (dirty.WasPropertyDirty("Id")) - { - //it's new - newEntities.Add(entity); - } - else - { - //it's updating - updatedEntities.Add(entity); - } + //it's new + newEntities.Add(entity); + } + else + { + //it's updating + updatedEntities.Add(entity); } - _notifier.Notify(_actions.GetAction(), newEntities.ToArray()); - _notifier.Notify(_actions.GetAction(), updatedEntities.ToArray()); } - public void Handle(ContentSortedNotification notification) - { - var parentId = notification.SortedEntities.Select(x => x.ParentId).Distinct().ToList(); - if (parentId.Count != 1) - return; // this shouldn't happen, for sorting all entities will have the same parent id - - // in this case there's nothing to report since if the root is sorted we can't report on a fake entity. - // this is how it was in v7, we can't report on root changes because you can't subscribe to root changes. - if (parentId[0] <= 0) - return; + _notifier.Notify(_actions.GetAction(), newEntities.ToArray()); + _notifier.Notify(_actions.GetAction(), updatedEntities.ToArray()); + } - var parent = _contentService.GetById(parentId[0]); - if (parent == null) - return; // this shouldn't happen + public void Handle(ContentSentToPublishNotification notification) => + _notifier.Notify(_actions.GetAction(), notification.Entity); - _notifier.Notify(_actions.GetAction(), new[] { parent }); + public void Handle(ContentSortedNotification notification) + { + var parentId = notification.SortedEntities.Select(x => x.ParentId).Distinct().ToList(); + if (parentId.Count != 1) + { + return; // this shouldn't happen, for sorting all entities will have the same parent id } - public void Handle(ContentPublishedNotification notification) => _notifier.Notify(_actions.GetAction(), notification.PublishedEntities.ToArray()); + // in this case there's nothing to report since if the root is sorted we can't report on a fake entity. + // this is how it was in v7, we can't report on root changes because you can't subscribe to root changes. + if (parentId[0] <= 0) + { + return; + } - public void Handle(ContentMovedNotification notification) + IContent parent = _contentService.GetById(parentId[0]); + if (parent == null) { - // notify about the move for all moved items - _notifier.Notify(_actions.GetAction(), notification.MoveInfoCollection.Select(m => m.Entity).ToArray()); - - // for any items being moved from the recycle bin (restored), explicitly notify about that too - var restoredEntities = notification.MoveInfoCollection - .Where(m => m.OriginalPath.Contains(Constants.System.RecycleBinContentString)) - .Select(m => m.Entity) - .ToArray(); - if (restoredEntities.Any()) - { - _notifier.Notify(_actions.GetAction(), restoredEntities); - } + return; // this shouldn't happen } - public void Handle(ContentMovedToRecycleBinNotification notification) => _notifier.Notify(_actions.GetAction(), notification.MoveInfoCollection.Select(m => m.Entity).ToArray()); + _notifier.Notify(_actions.GetAction(), parent); + } - public void Handle(ContentCopiedNotification notification) => _notifier.Notify(_actions.GetAction(), notification.Original); + public void Handle(ContentUnpublishedNotification notification) => + _notifier.Notify(_actions.GetAction(), notification.UnpublishedEntities.ToArray()); - public void Handle(ContentRolledBackNotification notification) => _notifier.Notify(_actions.GetAction(), notification.Entity); + public void Handle(PublicAccessEntrySavedNotification notification) + { + IContent[] entities = _contentService.GetByIds(notification.SavedEntities.Select(e => e.ProtectedNodeId)) + ?.ToArray(); + if (entities?.Any() == false) + { + return; + } - public void Handle(ContentSentToPublishNotification notification) => _notifier.Notify(_actions.GetAction(), notification.Entity); + _notifier.Notify(_actions.GetAction(), entities!); + } - public void Handle(ContentUnpublishedNotification notification) => _notifier.Notify(_actions.GetAction(), notification.UnpublishedEntities.ToArray()); + /// + /// This class is used to send the notifications + /// + public sealed class Notifier + { + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly ILogger _logger; + private readonly INotificationService _notificationService; + private readonly ILocalizedTextService _textService; + private readonly IUserService _userService; + private GlobalSettings _globalSettings; /// - /// This class is used to send the notifications + /// Initializes a new instance of the class. /// - public sealed class Notifier + public Notifier( + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IHostingEnvironment hostingEnvironment, + INotificationService notificationService, + IUserService userService, + ILocalizedTextService textService, + IOptionsMonitor globalSettings, + ILogger logger) { - private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly INotificationService _notificationService; - private readonly IUserService _userService; - private readonly ILocalizedTextService _textService; - private GlobalSettings _globalSettings; - private readonly ILogger _logger; - - /// - /// Initializes a new instance of the class. - /// - public Notifier( - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IHostingEnvironment hostingEnvironment, - INotificationService notificationService, - IUserService userService, - ILocalizedTextService textService, - IOptionsMonitor globalSettings, - ILogger logger) - { - _backOfficeSecurityAccessor = backOfficeSecurityAccessor; - _hostingEnvironment = hostingEnvironment; - _notificationService = notificationService; - _userService = userService; - _textService = textService; - _globalSettings = globalSettings.CurrentValue; - _logger = logger; - - globalSettings.OnChange(x => _globalSettings = x); - } + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + _hostingEnvironment = hostingEnvironment; + _notificationService = notificationService; + _userService = userService; + _textService = textService; + _globalSettings = globalSettings.CurrentValue; + _logger = logger; - public void Notify(IAction? action, params IContent[] entities) - { - var user = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser; - - //if there is no current user, then use the admin - if (user == null) - { - _logger.LogDebug("There is no current Umbraco user logged in, the notifications will be sent from the administrator"); - user = _userService.GetUserById(Constants.Security.SuperUserId); - if (user == null) - { - _logger.LogWarning("Notifications can not be sent, no admin user with id {SuperUserId} could be resolved", Constants.Security.SuperUserId); - return; - } - } + globalSettings.OnChange(x => _globalSettings = x); + } - SendNotification(user, entities, action, _hostingEnvironment.ApplicationMainUrl); - } + public void Notify(IAction? action, params IContent[] entities) + { + IUser user = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser; - private void SendNotification(IUser sender, IEnumerable entities, IAction? action, Uri? siteUri) + //if there is no current user, then use the admin + if (user == null) { - if (sender == null) - throw new ArgumentNullException(nameof(sender)); - if (siteUri == null) + _logger.LogDebug( + "There is no current Umbraco user logged in, the notifications will be sent from the administrator"); + user = _userService.GetUserById(Constants.Security.SuperUserId); + if (user == null) { - _logger.LogWarning("Notifications can not be sent, no site URL is set (might be during boot process?)"); + _logger.LogWarning( + "Notifications can not be sent, no admin user with id {SuperUserId} could be resolved", + Constants.Security.SuperUserId); return; } - - //group by the content type variation since the emails will be different - foreach (var contentVariantGroup in entities.GroupBy(x => x.ContentType.Variations)) - { - _notificationService.SendNotifications( - sender, - contentVariantGroup, - action?.Letter.ToString(CultureInfo.InvariantCulture), - _textService.Localize("actions", action?.Alias), - siteUri, - ((IUser user, NotificationEmailSubjectParams subject) x) - => _textService.Localize( - "notifications", "mailSubject", - x.user.GetUserCulture(_textService, _globalSettings), - new[] { x.subject.SiteUrl, x.subject.Action, x.subject.ItemName }), - ((IUser user, NotificationEmailBodyParams body, bool isHtml) x) - => _textService.Localize( - "notifications", x.isHtml ? "mailBodyHtml" : "mailBody", - x.user.GetUserCulture(_textService, _globalSettings), - new[] - { - x.body.RecipientName, - x.body.Action, - x.body.ItemName, - x.body.EditedUser, - x.body.SiteUrl, - x.body.ItemId, - //format the summary depending on if it's variant or not - contentVariantGroup.Key == ContentVariation.Culture - ? (x.isHtml ? _textService.Localize("notifications", "mailBodyVariantHtmlSummary", new[]{ x.body.Summary }) : _textService.Localize("notifications","mailBodyVariantSummary", new []{ x.body.Summary })) - : x.body.Summary, - x.body.ItemUrl - })); - } } + + SendNotification(user, entities, action, _hostingEnvironment.ApplicationMainUrl); } - public void Handle(AssignedUserGroupPermissionsNotification notification) + private void SendNotification(IUser sender, IEnumerable entities, IAction? action, Uri? siteUri) { - var entities = _contentService.GetByIds(notification.EntityPermissions.Select(e => e.EntityId))?.ToArray(); - if (entities?.Any() == false) + if (sender == null) { - return; + throw new ArgumentNullException(nameof(sender)); } - _notifier.Notify(_actions.GetAction(), entities!); - } - public void Handle(PublicAccessEntrySavedNotification notification) - { - var entities = _contentService.GetByIds(notification.SavedEntities.Select(e => e.ProtectedNodeId))?.ToArray(); - if (entities?.Any() == false) + if (siteUri == null) { + _logger.LogWarning("Notifications can not be sent, no site URL is set (might be during boot process?)"); return; } - _notifier.Notify(_actions.GetAction(), entities!); + + //group by the content type variation since the emails will be different + foreach (IGrouping contentVariantGroup in entities.GroupBy(x => + x.ContentType.Variations)) + { + _notificationService.SendNotifications( + sender, + contentVariantGroup, + action?.Letter.ToString(CultureInfo.InvariantCulture), + _textService.Localize("actions", action?.Alias), + siteUri, + x + => _textService.Localize( + "notifications", "mailSubject", + x.user.GetUserCulture(_textService, _globalSettings), + new[] {x.subject.SiteUrl, x.subject.Action, x.subject.ItemName}), + x + => _textService.Localize( + "notifications", x.isHtml ? "mailBodyHtml" : "mailBody", + x.user.GetUserCulture(_textService, _globalSettings), + new[] + { + x.body.RecipientName, x.body.Action, x.body.ItemName, x.body.EditedUser, x.body.SiteUrl, + x.body.ItemId, + //format the summary depending on if it's variant or not + contentVariantGroup.Key == ContentVariation.Culture + ? x.isHtml + ? _textService.Localize("notifications", "mailBodyVariantHtmlSummary", + new[] {x.body.Summary}) + : _textService.Localize("notifications", "mailBodyVariantSummary", + new[] {x.body.Summary}) + : x.body.Summary, + x.body.ItemUrl + })); + } } } } diff --git a/src/Umbraco.Core/Exceptions/AuthorizationException.cs b/src/Umbraco.Core/Exceptions/AuthorizationException.cs index fa2399fc5c77..ba4e40d1901d 100644 --- a/src/Umbraco.Core/Exceptions/AuthorizationException.cs +++ b/src/Umbraco.Core/Exceptions/AuthorizationException.cs @@ -1,45 +1,56 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Exceptions +namespace Umbraco.Cms.Core.Exceptions; + +/// +/// The exception that is thrown when authorization failed. +/// +/// +[Serializable] +public class AuthorizationException : Exception { /// - /// The exception that is thrown when authorization failed. + /// Initializes a new instance of the class. /// - /// - [Serializable] - public class AuthorizationException : Exception + public AuthorizationException() { - /// - /// Initializes a new instance of the class. - /// - public AuthorizationException() - { } + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public AuthorizationException(string message) - : base(message) - { } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public AuthorizationException(string message) + : base(message) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. - public AuthorizationException(string message, Exception innerException) - : base(message, innerException) - { } + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// + /// The exception that is the cause of the current exception, or a null reference ( + /// in Visual Basic) if no inner exception is specified. + /// + public AuthorizationException(string message, Exception innerException) + : base(message, innerException) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected AuthorizationException(SerializationInfo info, StreamingContext context) - : base(info, context) - { } + /// + /// Initializes a new instance of the class. + /// + /// + /// The that holds the serialized object + /// data about the exception being thrown. + /// + /// + /// The that contains contextual + /// information about the source or destination. + /// + protected AuthorizationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } } diff --git a/src/Umbraco.Core/Exceptions/BootFailedException.cs b/src/Umbraco.Core/Exceptions/BootFailedException.cs index eeac07869d1c..55ec0970c0c3 100644 --- a/src/Umbraco.Core/Exceptions/BootFailedException.cs +++ b/src/Umbraco.Core/Exceptions/BootFailedException.cs @@ -1,83 +1,97 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using System.Text; -namespace Umbraco.Cms.Core.Exceptions +namespace Umbraco.Cms.Core.Exceptions; + +/// +/// An exception that is thrown if the Umbraco application cannot boot. +/// +/// +[Serializable] +public class BootFailedException : Exception { /// - /// An exception that is thrown if the Umbraco application cannot boot. + /// Defines the default boot failed exception message. /// - /// - [Serializable] - public class BootFailedException : Exception - { - /// - /// Defines the default boot failed exception message. - /// - public const string DefaultMessage = "Boot failed: Umbraco cannot run. See Umbraco's log file for more details."; + public const string DefaultMessage = "Boot failed: Umbraco cannot run. See Umbraco's log file for more details."; - /// - /// Initializes a new instance of the class. - /// - public BootFailedException() - { } + /// + /// Initializes a new instance of the class. + /// + public BootFailedException() + { + } - /// - /// Initializes a new instance of the class with a specified error message. - /// - /// The message that describes the error. - public BootFailedException(string message) - : base(message) - { } + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public BootFailedException(string message) + : base(message) + { + } - /// - /// Initializes a new instance of the class with a specified error message - /// and a reference to the inner exception which is the cause of this exception. - /// - /// The message that describes the error. - /// The inner exception, or null. - public BootFailedException(string message, Exception innerException) - : base(message, innerException) - { } + /// + /// Initializes a new instance of the class with a specified error message + /// and a reference to the inner exception which is the cause of this exception. + /// + /// The message that describes the error. + /// The inner exception, or null. + public BootFailedException(string message, Exception innerException) + : base(message, innerException) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected BootFailedException(SerializationInfo info, StreamingContext context) - : base(info, context) - { } + /// + /// Initializes a new instance of the class. + /// + /// + /// The that holds the serialized object + /// data about the exception being thrown. + /// + /// + /// The that contains contextual + /// information about the source or destination. + /// + protected BootFailedException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } - /// - /// Rethrows a captured . - /// - /// The boot failed exception. - /// - /// - /// - /// The exception can be null, in which case a default message is used. - /// - public static void Rethrow(BootFailedException? bootFailedException) + /// + /// Rethrows a captured . + /// + /// The boot failed exception. + /// + /// + /// + /// The exception can be null, in which case a default message is used. + /// + public static void Rethrow(BootFailedException? bootFailedException) + { + if (bootFailedException == null) { - if (bootFailedException == null) - throw new BootFailedException(DefaultMessage); + throw new BootFailedException(DefaultMessage); + } - // see https://stackoverflow.com/questions/57383 - // would that be the correct way to do it? - //ExceptionDispatchInfo.Capture(bootFailedException).Throw(); + // see https://stackoverflow.com/questions/57383 + // would that be the correct way to do it? + //ExceptionDispatchInfo.Capture(bootFailedException).Throw(); - Exception? e = bootFailedException; - var m = new StringBuilder(); - m.Append(DefaultMessage); - while (e != null) + Exception? e = bootFailedException; + var m = new StringBuilder(); + m.Append(DefaultMessage); + while (e != null) + { + m.Append($"\n\n-> {e.GetType().FullName}: {e.Message}"); + if (string.IsNullOrWhiteSpace(e.StackTrace) == false) { - m.Append($"\n\n-> {e.GetType().FullName}: {e.Message}"); - if (string.IsNullOrWhiteSpace(e.StackTrace) == false) - m.Append($"\n{e.StackTrace}"); - e = e.InnerException; + m.Append($"\n{e.StackTrace}"); } - throw new BootFailedException(m.ToString()); + + e = e.InnerException; } + + throw new BootFailedException(m.ToString()); } } diff --git a/src/Umbraco.Core/Exceptions/ConfigurationException.cs b/src/Umbraco.Core/Exceptions/ConfigurationException.cs index fe711a98231d..4f227b9d94f2 100644 --- a/src/Umbraco.Core/Exceptions/ConfigurationException.cs +++ b/src/Umbraco.Core/Exceptions/ConfigurationException.cs @@ -1,41 +1,47 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Exceptions +namespace Umbraco.Cms.Core.Exceptions; + +/// +/// An exception that is thrown if the configuration is wrong. +/// +/// +[Serializable] +public class ConfigurationException : Exception { /// - /// An exception that is thrown if the configuration is wrong. + /// Initializes a new instance of the class with a specified error message. /// - /// - [Serializable] - public class ConfigurationException : Exception + /// The message that describes the error. + public ConfigurationException(string message) + : base(message) { - /// - /// Initializes a new instance of the class with a specified error message. - /// - /// The message that describes the error. - public ConfigurationException(string message) - : base(message) - { } - - /// - /// Initializes a new instance of the class with a specified error message - /// and a reference to the inner exception which is the cause of this exception. - /// - /// The message that describes the error. - /// The inner exception, or null. - public ConfigurationException(string message, Exception innerException) - : base(message, innerException) - { } + } - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected ConfigurationException(SerializationInfo info, StreamingContext context) - : base(info, context) - { } + /// + /// Initializes a new instance of the class with a specified error message + /// and a reference to the inner exception which is the cause of this exception. + /// + /// The message that describes the error. + /// The inner exception, or null. + public ConfigurationException(string message, Exception innerException) + : base(message, innerException) + { + } + /// + /// Initializes a new instance of the class. + /// + /// + /// The that holds the serialized object + /// data about the exception being thrown. + /// + /// + /// The that contains contextual + /// information about the source or destination. + /// + protected ConfigurationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } } diff --git a/src/Umbraco.Core/Exceptions/DataOperationException.cs b/src/Umbraco.Core/Exceptions/DataOperationException.cs index 0b56cfb72cd0..85c7559fd67a 100644 --- a/src/Umbraco.Core/Exceptions/DataOperationException.cs +++ b/src/Umbraco.Core/Exceptions/DataOperationException.cs @@ -1,98 +1,111 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Exceptions +namespace Umbraco.Cms.Core.Exceptions; + +/// +/// +/// +/// +[Serializable] +public class DataOperationException : Exception + where T : Enum { /// - /// + /// Initializes a new instance of the class. /// - /// - /// - [Serializable] - public class DataOperationException : Exception - where T : Enum + public DataOperationException() { - /// - /// Gets the operation. - /// - /// - /// The operation. - /// - /// - /// This object should be serializable to prevent a to be thrown. - /// - public T? Operation { get; private set; } + } - /// - /// Initializes a new instance of the class. - /// - public DataOperationException() - { } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public DataOperationException(string message) + : base(message) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public DataOperationException(string message) - : base(message) - { } + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// + /// The exception that is the cause of the current exception, or a null reference ( + /// in Visual Basic) if no inner exception is specified. + /// + public DataOperationException(string message, Exception innerException) + : base(message, innerException) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. - public DataOperationException(string message, Exception innerException) - : base(message, innerException) - { } + /// + /// Initializes a new instance of the class. + /// + /// The operation. + public DataOperationException(T operation) + : this(operation, "Data operation exception: " + operation) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The operation. - public DataOperationException(T operation) - : this(operation, "Data operation exception: " + operation) - { } + /// + /// Initializes a new instance of the class. + /// + /// The operation. + /// The message. + public DataOperationException(T operation, string message) + : base(message) => + Operation = operation; - /// - /// Initializes a new instance of the class. - /// - /// The operation. - /// The message. - public DataOperationException(T operation, string message) - : base(message) - { - Operation = operation; - } + /// + /// Initializes a new instance of the class. + /// + /// + /// The that holds the serialized object + /// data about the exception being thrown. + /// + /// + /// The that contains contextual + /// information about the source or destination. + /// + /// info + protected DataOperationException(SerializationInfo info, StreamingContext context) + : base(info, context) => + Operation = (T)Enum.Parse(typeof(T), info.GetString(nameof(Operation)) ?? string.Empty); - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - /// info - protected DataOperationException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - Operation = (T)Enum.Parse(typeof(T), info.GetString(nameof(Operation)) ?? string.Empty); - } + /// + /// Gets the operation. + /// + /// + /// The operation. + /// + /// + /// This object should be serializable to prevent a to be thrown. + /// + public T? Operation { get; private set; } - /// - /// When overridden in a derived class, sets the with information about the exception. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - /// info - public override void GetObjectData(SerializationInfo info, StreamingContext context) + /// + /// When overridden in a derived class, sets the with + /// information about the exception. + /// + /// + /// The that holds the serialized object + /// data about the exception being thrown. + /// + /// + /// The that contains contextual + /// information about the source or destination. + /// + /// info + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } + throw new ArgumentNullException(nameof(info)); + } - info.AddValue(nameof(Operation), Operation is not null ? Enum.GetName(typeof(T), Operation) : string.Empty); + info.AddValue(nameof(Operation), Operation is not null ? Enum.GetName(typeof(T), Operation) : string.Empty); - base.GetObjectData(info, context); - } + base.GetObjectData(info, context); } } diff --git a/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs b/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs index ba8c2b61069c..4593fabc7cf6 100644 --- a/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs +++ b/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs @@ -1,166 +1,194 @@ -using System; -using System.Runtime.Serialization; -using Umbraco.Extensions; +using System.Runtime.Serialization; using System.Text; +using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Exceptions +namespace Umbraco.Cms.Core.Exceptions; + +/// +/// The exception that is thrown when a composition is invalid. +/// +/// +[Serializable] +public class InvalidCompositionException : Exception { /// - /// The exception that is thrown when a composition is invalid. + /// Initializes a new instance of the class. + /// + public InvalidCompositionException() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The content type alias. + /// The property type aliases. + public InvalidCompositionException(string contentTypeAlias, string[] propertyTypeAliases) + : this(contentTypeAlias, null, propertyTypeAliases) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The content type alias. + /// The added composition alias. + /// The property type aliases. + public InvalidCompositionException(string contentTypeAlias, string? addedCompositionAlias, + string[] propertyTypeAliases) + : this(contentTypeAlias, addedCompositionAlias, propertyTypeAliases, new string[0]) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The content type alias. + /// The added composition alias. + /// The property type aliases. + /// The property group aliases. + public InvalidCompositionException(string contentTypeAlias, string? addedCompositionAlias, + string[] propertyTypeAliases, string[] propertyGroupAliases) + : this(FormatMessage(contentTypeAlias, addedCompositionAlias, propertyTypeAliases, propertyGroupAliases)) + { + ContentTypeAlias = contentTypeAlias; + AddedCompositionAlias = addedCompositionAlias; + PropertyTypeAliases = propertyTypeAliases; + PropertyGroupAliases = propertyGroupAliases; + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public InvalidCompositionException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// + /// The exception that is the cause of the current exception, or a null reference ( + /// in Visual Basic) if no inner exception is specified. + /// + public InvalidCompositionException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class. /// - /// - [Serializable] - public class InvalidCompositionException : Exception + /// + /// The that holds the serialized object + /// data about the exception being thrown. + /// + /// + /// The that contains contextual + /// information about the source or destination. + /// + protected InvalidCompositionException(SerializationInfo info, StreamingContext context) + : base(info, context) { - /// - /// Gets the content type alias. - /// - /// - /// The content type alias. - /// - public string? ContentTypeAlias { get; } - - /// - /// Gets the added composition alias. - /// - /// - /// The added composition alias. - /// - public string? AddedCompositionAlias { get; } - - /// - /// Gets the property type aliases. - /// - /// - /// The property type aliases. - /// - public string[]? PropertyTypeAliases { get; } - - /// - /// Gets the property group aliases. - /// - /// - /// The property group aliases. - /// - public string[]? PropertyGroupAliases { get; } - - /// - /// Initializes a new instance of the class. - /// - public InvalidCompositionException() - { } - - /// - /// Initializes a new instance of the class. - /// - /// The content type alias. - /// The property type aliases. - public InvalidCompositionException(string contentTypeAlias, string[] propertyTypeAliases) - : this(contentTypeAlias, null, propertyTypeAliases) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The content type alias. - /// The added composition alias. - /// The property type aliases. - public InvalidCompositionException(string contentTypeAlias, string? addedCompositionAlias, string[] propertyTypeAliases) - : this(contentTypeAlias, addedCompositionAlias, propertyTypeAliases, new string[0]) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The content type alias. - /// The added composition alias. - /// The property type aliases. - /// The property group aliases. - public InvalidCompositionException(string contentTypeAlias, string? addedCompositionAlias, string[] propertyTypeAliases, string[] propertyGroupAliases) - : this(FormatMessage(contentTypeAlias, addedCompositionAlias, propertyTypeAliases, propertyGroupAliases)) + ContentTypeAlias = info.GetString(nameof(ContentTypeAlias)); + AddedCompositionAlias = info.GetString(nameof(AddedCompositionAlias)); + PropertyTypeAliases = (string[]?)info.GetValue(nameof(PropertyTypeAliases), typeof(string[])); + PropertyGroupAliases = (string[]?)info.GetValue(nameof(PropertyGroupAliases), typeof(string[])); + } + + /// + /// Gets the content type alias. + /// + /// + /// The content type alias. + /// + public string? ContentTypeAlias { get; } + + /// + /// Gets the added composition alias. + /// + /// + /// The added composition alias. + /// + public string? AddedCompositionAlias { get; } + + /// + /// Gets the property type aliases. + /// + /// + /// The property type aliases. + /// + public string[]? PropertyTypeAliases { get; } + + /// + /// Gets the property group aliases. + /// + /// + /// The property group aliases. + /// + public string[]? PropertyGroupAliases { get; } + + private static string FormatMessage(string contentTypeAlias, string? addedCompositionAlias, + string[] propertyTypeAliases, string[] propertyGroupAliases) + { + var sb = new StringBuilder(); + + if (addedCompositionAlias.IsNullOrWhiteSpace()) { - ContentTypeAlias = contentTypeAlias; - AddedCompositionAlias = addedCompositionAlias; - PropertyTypeAliases = propertyTypeAliases; - PropertyGroupAliases = propertyGroupAliases; + sb.AppendFormat("Content type with alias '{0}' has an invalid composition.", contentTypeAlias); } - - private static string FormatMessage(string contentTypeAlias, string? addedCompositionAlias, string[] propertyTypeAliases, string[] propertyGroupAliases) + else { - var sb = new StringBuilder(); - - if (addedCompositionAlias.IsNullOrWhiteSpace()) - { - sb.AppendFormat("Content type with alias '{0}' has an invalid composition.", contentTypeAlias); - } - else - { - sb.AppendFormat("Content type with alias '{0}' was added as a composition to content type with alias '{1}', but there was a conflict.", addedCompositionAlias, contentTypeAlias); - } - - if (propertyTypeAliases.Length > 0) - { - sb.AppendFormat(" Property types must have a unique alias across all compositions, these aliases are duplicate: {0}.", string.Join(", ", propertyTypeAliases)); - } - - if (propertyGroupAliases.Length > 0) - { - sb.AppendFormat(" Property groups with the same alias must also have the same type across all compositions, these aliases have different types: {0}.", string.Join(", ", propertyGroupAliases)); - } - - return sb.ToString(); + sb.AppendFormat( + "Content type with alias '{0}' was added as a composition to content type with alias '{1}', but there was a conflict.", + addedCompositionAlias, contentTypeAlias); } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public InvalidCompositionException(string message) - : base(message) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. - public InvalidCompositionException(string message, Exception innerException) - : base(message, innerException) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected InvalidCompositionException(SerializationInfo info, StreamingContext context) - : base(info, context) + if (propertyTypeAliases.Length > 0) { - ContentTypeAlias = info.GetString(nameof(ContentTypeAlias)); - AddedCompositionAlias = info.GetString(nameof(AddedCompositionAlias)); - PropertyTypeAliases = (string[]?)info.GetValue(nameof(PropertyTypeAliases), typeof(string[])); - PropertyGroupAliases = (string[] ?)info.GetValue(nameof(PropertyGroupAliases), typeof(string[])); + sb.AppendFormat( + " Property types must have a unique alias across all compositions, these aliases are duplicate: {0}.", + string.Join(", ", propertyTypeAliases)); } - /// - /// When overridden in a derived class, sets the with information about the exception. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - /// info - public override void GetObjectData(SerializationInfo info, StreamingContext context) + if (propertyGroupAliases.Length > 0) { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } + sb.AppendFormat( + " Property groups with the same alias must also have the same type across all compositions, these aliases have different types: {0}.", + string.Join(", ", propertyGroupAliases)); + } - info.AddValue(nameof(ContentTypeAlias), ContentTypeAlias); - info.AddValue(nameof(AddedCompositionAlias), AddedCompositionAlias); - info.AddValue(nameof(PropertyTypeAliases), PropertyTypeAliases); - info.AddValue(nameof(PropertyGroupAliases), PropertyGroupAliases); + return sb.ToString(); + } - base.GetObjectData(info, context); + /// + /// When overridden in a derived class, sets the with + /// information about the exception. + /// + /// + /// The that holds the serialized object + /// data about the exception being thrown. + /// + /// + /// The that contains contextual + /// information about the source or destination. + /// + /// info + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + throw new ArgumentNullException(nameof(info)); } + + info.AddValue(nameof(ContentTypeAlias), ContentTypeAlias); + info.AddValue(nameof(AddedCompositionAlias), AddedCompositionAlias); + info.AddValue(nameof(PropertyTypeAliases), PropertyTypeAliases); + info.AddValue(nameof(PropertyGroupAliases), PropertyGroupAliases); + + base.GetObjectData(info, context); } } diff --git a/src/Umbraco.Core/Exceptions/PanicException.cs b/src/Umbraco.Core/Exceptions/PanicException.cs index 9ba1311e84ec..cec361d4f166 100644 --- a/src/Umbraco.Core/Exceptions/PanicException.cs +++ b/src/Umbraco.Core/Exceptions/PanicException.cs @@ -1,45 +1,57 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Exceptions +namespace Umbraco.Cms.Core.Exceptions; + +/// +/// Represents an internal exception that in theory should never been thrown, it is only thrown in circumstances that +/// should never happen. +/// +/// +[Serializable] +public class PanicException : Exception { /// - /// Represents an internal exception that in theory should never been thrown, it is only thrown in circumstances that should never happen. + /// Initializes a new instance of the class. /// - /// - [Serializable] - public class PanicException : Exception + public PanicException() { - /// - /// Initializes a new instance of the class. - /// - public PanicException() - { } + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public PanicException(string message) - : base(message) - { } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public PanicException(string message) + : base(message) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. - public PanicException(string message, Exception innerException) - : base(message, innerException) - { } + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// + /// The exception that is the cause of the current exception, or a null reference ( + /// in Visual Basic) if no inner exception is specified. + /// + public PanicException(string message, Exception innerException) + : base(message, innerException) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected PanicException(SerializationInfo info, StreamingContext context) - : base(info, context) - { } + /// + /// Initializes a new instance of the class. + /// + /// + /// The that holds the serialized object + /// data about the exception being thrown. + /// + /// + /// The that contains contextual + /// information about the source or destination. + /// + protected PanicException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } } diff --git a/src/Umbraco.Core/Exceptions/UnattendedInstallException.cs b/src/Umbraco.Core/Exceptions/UnattendedInstallException.cs index 2a2b97b23d3e..b4c998f151f0 100644 --- a/src/Umbraco.Core/Exceptions/UnattendedInstallException.cs +++ b/src/Umbraco.Core/Exceptions/UnattendedInstallException.cs @@ -1,46 +1,50 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Exceptions +namespace Umbraco.Cms.Core.Exceptions; + +/// +/// An exception that is thrown if an unattended installation occurs. +/// +[Serializable] +public class UnattendedInstallException : Exception { /// - /// An exception that is thrown if an unattended installation occurs. + /// Initializes a new instance of the class. /// - [Serializable] - public class UnattendedInstallException : Exception + public UnattendedInstallException() { - /// - /// Initializes a new instance of the class. - /// - public UnattendedInstallException() - { - } + } - /// - /// Initializes a new instance of the class with a specified error message. - /// - /// The message that describes the error. - public UnattendedInstallException(string message) : base(message) - { - } + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public UnattendedInstallException(string message) : base(message) + { + } - /// - /// Initializes a new instance of the class with a specified error message - /// and a reference to the inner exception which is the cause of this exception. - /// - /// The message that describes the error. - /// The inner exception, or null. - public UnattendedInstallException(string message, Exception innerException) : base(message, innerException) - { - } + /// + /// Initializes a new instance of the class with a specified error message + /// and a reference to the inner exception which is the cause of this exception. + /// + /// The message that describes the error. + /// The inner exception, or null. + public UnattendedInstallException(string message, Exception innerException) : base(message, innerException) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected UnattendedInstallException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + /// + /// Initializes a new instance of the class. + /// + /// + /// The that holds the serialized object + /// data about the exception being thrown. + /// + /// + /// The that contains contextual + /// information about the source or destination. + /// + protected UnattendedInstallException(SerializationInfo info, StreamingContext context) : base(info, context) + { } } diff --git a/src/Umbraco.Core/ExpressionHelper.cs b/src/Umbraco.Core/ExpressionHelper.cs index 1895364d1722..35af2e7a4a31 100644 --- a/src/Umbraco.Core/ExpressionHelper.cs +++ b/src/Umbraco.Core/ExpressionHelper.cs @@ -1,372 +1,447 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Concurrent; using System.Linq.Expressions; using System.Reflection; using Umbraco.Cms.Core.Persistence; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// A set of helper methods for dealing with expressions +/// +/// +public static class ExpressionHelper { + private static readonly ConcurrentDictionary PropertyInfoCache = new(); + /// - /// A set of helper methods for dealing with expressions + /// Gets a object from an expression. /// + /// The type of the source. + /// The type of the property. + /// The source. + /// The property lambda. + /// /// - public static class ExpressionHelper - { - private static readonly ConcurrentDictionary PropertyInfoCache = new ConcurrentDictionary(); - - /// - /// Gets a object from an expression. - /// - /// The type of the source. - /// The type of the property. - /// The source. - /// The property lambda. - /// - /// - public static PropertyInfo GetPropertyInfo(this TSource source, Expression> propertyLambda) - { - return GetPropertyInfo(propertyLambda); - } + public static PropertyInfo GetPropertyInfo(this TSource source, + Expression> propertyLambda) => GetPropertyInfo(propertyLambda); - /// - /// Gets a object from an expression. - /// - /// The type of the source. - /// The type of the property. - /// The property lambda. - /// - /// - public static PropertyInfo GetPropertyInfo(Expression> propertyLambda) - { - return PropertyInfoCache.GetOrAdd( - new LambdaExpressionCacheKey(propertyLambda), - x => - { - var type = typeof(TSource); + /// + /// Gets a object from an expression. + /// + /// The type of the source. + /// The type of the property. + /// The property lambda. + /// + /// + public static PropertyInfo + GetPropertyInfo(Expression> propertyLambda) => + PropertyInfoCache.GetOrAdd( + new LambdaExpressionCacheKey(propertyLambda), + x => + { + Type type = typeof(TSource); - var member = propertyLambda.Body as MemberExpression; - if (member == null) + var member = propertyLambda.Body as MemberExpression; + if (member == null) + { + if (propertyLambda.Body.GetType().Name == "UnaryExpression") + { + // The expression might be for some boxing, e.g. representing a value type like HiveId as an object + // in which case the expression will be Convert(x.MyProperty) + var unary = propertyLambda.Body as UnaryExpression; + if (unary != null) { - if (propertyLambda.Body.GetType().Name == "UnaryExpression") + var boxedMember = unary.Operand as MemberExpression; + if (boxedMember == null) { - // The expression might be for some boxing, e.g. representing a value type like HiveId as an object - // in which case the expression will be Convert(x.MyProperty) - var unary = propertyLambda.Body as UnaryExpression; - if (unary != null) - { - var boxedMember = unary.Operand as MemberExpression; - if (boxedMember == null) - throw new ArgumentException("The type of property could not be inferred, try specifying the type parameters explicitly. This can happen if you have tried to access PropertyInfo where the property's return type is a value type, but the expression is trying to convert it to an object"); - else member = boxedMember; - } + throw new ArgumentException( + "The type of property could not be inferred, try specifying the type parameters explicitly. This can happen if you have tried to access PropertyInfo where the property's return type is a value type, but the expression is trying to convert it to an object"); } - else throw new ArgumentException(string.Format("Expression '{0}' refers to a method, not a property.", propertyLambda)); + + member = boxedMember; } + } + else + { + throw new ArgumentException( + string.Format("Expression '{0}' refers to a method, not a property.", propertyLambda)); + } + } - var propInfo = member!.Member as PropertyInfo; - if (propInfo == null) - throw new ArgumentException(string.Format( - "Expression '{0}' refers to a field, not a property.", - propertyLambda)); + var propInfo = member!.Member as PropertyInfo; + if (propInfo == null) + { + throw new ArgumentException(string.Format( + "Expression '{0}' refers to a field, not a property.", + propertyLambda)); + } + + if (type != propInfo.ReflectedType && + !type.IsSubclassOf(propInfo.ReflectedType!)) + { + throw new ArgumentException(string.Format( + "Expression '{0}' refers to a property that is not from type {1}.", + propertyLambda, + type)); + } - if (type != propInfo.ReflectedType && - !type.IsSubclassOf(propInfo.ReflectedType!)) - throw new ArgumentException(string.Format( - "Expression '{0}' refers to a property that is not from type {1}.", - propertyLambda, - type)); + return propInfo; + }); - return propInfo; - }); + public static (MemberInfo, string?) FindProperty(LambdaExpression lambda) + { + void Throw() + { + throw new ArgumentException( + $"Expression '{lambda}' must resolve to top-level member and not any child object's properties. Use a custom resolver on the child type or the AfterMap option instead.", + nameof(lambda)); } - public static (MemberInfo, string?) FindProperty(LambdaExpression lambda) + Expression expr = lambda; + var loop = true; + string? alias = null; + while (loop) { - void Throw() + switch (expr.NodeType) { - throw new ArgumentException($"Expression '{lambda}' must resolve to top-level member and not any child object's properties. Use a custom resolver on the child type or the AfterMap option instead.", nameof(lambda)); - } + case ExpressionType.Convert: + expr = ((UnaryExpression)expr).Operand; + break; + case ExpressionType.Lambda: + expr = ((LambdaExpression)expr).Body; + break; + case ExpressionType.Call: + var callExpr = (MethodCallExpression)expr; + MethodInfo method = callExpr.Method; + if (method.DeclaringType != typeof(SqlExtensionsStatics) || method.Name != "Alias" || + !(callExpr.Arguments[1] is ConstantExpression aliasExpr)) + { + Throw(); + } - Expression expr = lambda; - var loop = true; - string? alias = null; - while (loop) - { - switch (expr.NodeType) - { - case ExpressionType.Convert: - expr = ((UnaryExpression) expr).Operand; - break; - case ExpressionType.Lambda: - expr = ((LambdaExpression) expr).Body; - break; - case ExpressionType.Call: - var callExpr = (MethodCallExpression) expr; - var method = callExpr.Method; - if (method.DeclaringType != typeof(SqlExtensionsStatics) || method.Name != "Alias" || !(callExpr.Arguments[1] is ConstantExpression aliasExpr)) - Throw(); - expr = callExpr.Arguments[0]; - alias = aliasExpr.Value?.ToString(); - break; - case ExpressionType.MemberAccess: - var memberExpr = (MemberExpression) expr; - if (memberExpr.Expression?.NodeType != ExpressionType.Parameter && memberExpr.Expression?.NodeType != ExpressionType.Convert) - Throw(); - return (memberExpr.Member, alias); - default: - loop = false; - break; - } + expr = callExpr.Arguments[0]; + alias = aliasExpr.Value?.ToString(); + break; + case ExpressionType.MemberAccess: + var memberExpr = (MemberExpression)expr; + if (memberExpr.Expression?.NodeType != ExpressionType.Parameter && + memberExpr.Expression?.NodeType != ExpressionType.Convert) + { + Throw(); + } + + return (memberExpr.Member, alias); + default: + loop = false; + break; } + } + + throw new Exception("Configuration for members is only supported for top-level individual members on a type."); + } - throw new Exception("Configuration for members is only supported for top-level individual members on a type."); + public static IDictionary? GetMethodParams(Expression> fromExpression) + { + if (fromExpression == null) + { + return null; } - public static IDictionary? GetMethodParams(Expression> fromExpression) + var body = fromExpression.Body as MethodCallExpression; + if (body == null) { - if (fromExpression == null) return null; - var body = fromExpression.Body as MethodCallExpression; - if (body == null) - return new Dictionary(); - - var rVal = new Dictionary(); - var parameters = body.Method.GetParameters().Select(x => x.Name).Where(x => x is not null).ToArray(); - var i = 0; - foreach (var argument in body.Arguments) - { - var lambda = Expression.Lambda(argument, fromExpression.Parameters); - var d = lambda.Compile(); - var value = d.DynamicInvoke(new object[1]); - rVal.Add(parameters[i]!, value); - i++; - } - return rVal; + return new Dictionary(); } - /// - /// Gets a from an provided it refers to a method call. - /// - /// - /// From expression. - /// The or null if is null or cannot be converted to . - /// - public static MethodInfo? GetMethodInfo(Expression> fromExpression) + var rVal = new Dictionary(); + var parameters = body.Method.GetParameters().Select(x => x.Name).Where(x => x is not null).ToArray(); + var i = 0; + foreach (Expression argument in body.Arguments) { - if (fromExpression == null) return null; - var body = fromExpression.Body as MethodCallExpression; - return body != null ? body.Method : null; + LambdaExpression lambda = Expression.Lambda(argument, fromExpression.Parameters); + Delegate d = lambda.Compile(); + var value = d.DynamicInvoke(new object[1]); + rVal.Add(parameters[i]!, value); + i++; } - /// - /// Gets the method info. - /// - /// The return type of the method. - /// From expression. - /// - public static MethodInfo? GetMethodInfo(Expression> fromExpression) + return rVal; + } + + /// + /// Gets a from an provided it refers to a method call. + /// + /// + /// From expression. + /// + /// The or null if is null or cannot be converted to + /// . + /// + /// + public static MethodInfo? GetMethodInfo(Expression> fromExpression) + { + if (fromExpression == null) { - if (fromExpression == null) return null; - var body = fromExpression.Body as MethodCallExpression; - return body != null ? body.Method : null; + return null; } - /// - /// Gets the method info. - /// - /// The type of the 1. - /// The type of the 2. - /// From expression. - /// - public static MethodInfo? GetMethodInfo(Expression> fromExpression) + var body = fromExpression.Body as MethodCallExpression; + return body != null ? body.Method : null; + } + + /// + /// Gets the method info. + /// + /// The return type of the method. + /// From expression. + /// + public static MethodInfo? GetMethodInfo(Expression> fromExpression) + { + if (fromExpression == null) { - if (fromExpression == null) return null; + return null; + } - MethodCallExpression? me; - switch (fromExpression.Body.NodeType) - { - case ExpressionType.Convert: - case ExpressionType.ConvertChecked: - var ue = fromExpression.Body as UnaryExpression; - me = ((ue != null) ? ue.Operand : null) as MethodCallExpression; - break; - default: - me = fromExpression.Body as MethodCallExpression; - break; - } + var body = fromExpression.Body as MethodCallExpression; + return body != null ? body.Method : null; + } - return me != null ? me.Method : null; + /// + /// Gets the method info. + /// + /// The type of the 1. + /// The type of the 2. + /// From expression. + /// + public static MethodInfo? GetMethodInfo(Expression> fromExpression) + { + if (fromExpression == null) + { + return null; } - /// - /// Gets a from an provided it refers to a method call. - /// - /// The expression. - /// The or null if cannot be converted to . - /// - public static MethodInfo? GetMethod(Expression expression) + MethodCallExpression? me; + switch (fromExpression.Body.NodeType) { - if (expression == null) return null; - return IsMethod(expression) ? (((MethodCallExpression)expression).Method) : null; + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + var ue = fromExpression.Body as UnaryExpression; + me = (ue != null ? ue.Operand : null) as MethodCallExpression; + break; + default: + me = fromExpression.Body as MethodCallExpression; + break; } - /// - /// Gets a from an provided it refers to member access. - /// - /// - /// The type of the return. - /// From expression. - /// The or null if cannot be converted to . - /// - public static MemberInfo? GetMemberInfo(Expression> fromExpression) + return me != null ? me.Method : null; + } + + /// + /// Gets a from an provided it refers to a method call. + /// + /// The expression. + /// + /// The or null if cannot be converted to + /// . + /// + /// + public static MethodInfo? GetMethod(Expression expression) + { + if (expression == null) { - if (fromExpression == null) return null; + return null; + } - MemberExpression? me; - switch (fromExpression.Body.NodeType) - { - case ExpressionType.Convert: - case ExpressionType.ConvertChecked: - var ue = fromExpression.Body as UnaryExpression; - me = ((ue != null) ? ue.Operand : null) as MemberExpression; - break; - default: - me = fromExpression.Body as MemberExpression; - break; - } + return IsMethod(expression) ? ((MethodCallExpression)expression).Method : null; + } - return me != null ? me.Member : null; + /// + /// Gets a from an provided it refers to member + /// access. + /// + /// + /// The type of the return. + /// From expression. + /// + /// The or null if cannot be converted to + /// . + /// + /// + public static MemberInfo? GetMemberInfo(Expression> fromExpression) + { + if (fromExpression == null) + { + return null; } - /// - /// Determines whether the MethodInfo is the same based on signature, not based on the equality operator or HashCode. - /// - /// The left. - /// The right. - /// - /// true if [is method signature equal to] [the specified left]; otherwise, false. - /// - /// - /// This is useful for comparing Expression methods that may contain different generic types - /// - public static bool IsMethodSignatureEqualTo(this MethodInfo left, MethodInfo right) + MemberExpression? me; + switch (fromExpression.Body.NodeType) + { + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + var ue = fromExpression.Body as UnaryExpression; + me = (ue != null ? ue.Operand : null) as MemberExpression; + break; + default: + me = fromExpression.Body as MemberExpression; + break; + } + + return me != null ? me.Member : null; + } + + /// + /// Determines whether the MethodInfo is the same based on signature, not based on the equality operator or HashCode. + /// + /// The left. + /// The right. + /// + /// true if [is method signature equal to] [the specified left]; otherwise, false. + /// + /// + /// This is useful for comparing Expression methods that may contain different generic types + /// + public static bool IsMethodSignatureEqualTo(this MethodInfo left, MethodInfo right) + { + if (left.Equals(right)) { - if (left.Equals(right)) - return true; - if (left.DeclaringType != right.DeclaringType) - return false; - if (left.Name != right.Name) - return false; - var leftParams = left.GetParameters(); - var rightParams = right.GetParameters(); - if (leftParams.Length != rightParams.Length) - return false; - for (int i = 0; i < leftParams.Length; i++) - { - //if they are delegate parameters, then assume they match as they could be anything - if (typeof(Delegate).IsAssignableFrom(leftParams[i].ParameterType) && typeof(Delegate).IsAssignableFrom(rightParams[i].ParameterType)) - continue; - //if they are not delegates, then compare the types - if (leftParams[i].ParameterType != rightParams[i].ParameterType) - return false; - } - if (left.ReturnType != right.ReturnType) - return false; return true; } - /// - /// Gets a from an provided it refers to member access. - /// - /// The expression. - /// - /// - public static MemberInfo? GetMember(Expression expression) + if (left.DeclaringType != right.DeclaringType) { - if (expression == null) return null; - return IsMember(expression) ? (((MemberExpression)expression).Member) : null; + return false; } - /// - /// Gets a from a - /// - /// From method group. - /// - /// - public static MethodInfo GetStaticMethodInfo(Delegate fromMethodGroup) + if (left.Name != right.Name) { - if (fromMethodGroup == null) throw new ArgumentNullException("fromMethodGroup"); + return false; + } + ParameterInfo[] leftParams = left.GetParameters(); + ParameterInfo[] rightParams = right.GetParameters(); + if (leftParams.Length != rightParams.Length) + { + return false; + } - return fromMethodGroup.Method; + for (var i = 0; i < leftParams.Length; i++) + { + //if they are delegate parameters, then assume they match as they could be anything + if (typeof(Delegate).IsAssignableFrom(leftParams[i].ParameterType) && + typeof(Delegate).IsAssignableFrom(rightParams[i].ParameterType)) + { + continue; + } + + //if they are not delegates, then compare the types + if (leftParams[i].ParameterType != rightParams[i].ParameterType) + { + return false; + } } - ///// - ///// Formats an unhandled item for representing the expression as a string. - ///// - ///// - ///// The unhandled item. - ///// - ///// - //public static string FormatUnhandledItem(T unhandledItem) where T : class - //{ - // if (unhandledItem == null) throw new ArgumentNullException("unhandledItem"); - - - // var itemAsExpression = unhandledItem as Expression; - // return itemAsExpression != null - // ? FormattingExpressionTreeVisitor.Format(itemAsExpression) - // : unhandledItem.ToString(); - //} - - /// - /// Determines whether the specified expression is a method. - /// - /// The expression. - /// true if the specified expression is method; otherwise, false. - /// - public static bool IsMethod(Expression expression) + if (left.ReturnType != right.ReturnType) { - return expression is MethodCallExpression; + return false; } + return true; + } - /// - /// Determines whether the specified expression is a member. - /// - /// The expression. - /// true if the specified expression is member; otherwise, false. - /// - public static bool IsMember(Expression expression) + /// + /// Gets a from an provided it refers to member access. + /// + /// The expression. + /// + /// + public static MemberInfo? GetMember(Expression expression) + { + if (expression == null) { - return expression is MemberExpression; + return null; } - /// - /// Determines whether the specified expression is a constant. - /// - /// The expression. - /// true if the specified expression is constant; otherwise, false. - /// - public static bool IsConstant(Expression expression) + return IsMember(expression) ? ((MemberExpression)expression).Member : null; + } + + /// + /// Gets a from a + /// + /// From method group. + /// + /// + public static MethodInfo GetStaticMethodInfo(Delegate fromMethodGroup) + { + if (fromMethodGroup == null) { - return expression is ConstantExpression; + throw new ArgumentNullException("fromMethodGroup"); } - /// - /// Gets the first value from the supplied arguments of an expression, for those arguments that can be cast to . - /// - /// The arguments. - /// - /// - public static object? GetFirstValueFromArguments(IEnumerable arguments) + + return fromMethodGroup.Method; + } + + ///// + ///// Formats an unhandled item for representing the expression as a string. + ///// + ///// + ///// The unhandled item. + ///// + ///// + //public static string FormatUnhandledItem(T unhandledItem) where T : class + //{ + // if (unhandledItem == null) throw new ArgumentNullException("unhandledItem"); + + + // var itemAsExpression = unhandledItem as Expression; + // return itemAsExpression != null + // ? FormattingExpressionTreeVisitor.Format(itemAsExpression) + // : unhandledItem.ToString(); + //} + + /// + /// Determines whether the specified expression is a method. + /// + /// The expression. + /// true if the specified expression is method; otherwise, false. + /// + public static bool IsMethod(Expression expression) => expression is MethodCallExpression; + + + /// + /// Determines whether the specified expression is a member. + /// + /// The expression. + /// true if the specified expression is member; otherwise, false. + /// + public static bool IsMember(Expression expression) => expression is MemberExpression; + + /// + /// Determines whether the specified expression is a constant. + /// + /// The expression. + /// true if the specified expression is constant; otherwise, false. + /// + public static bool IsConstant(Expression expression) => expression is ConstantExpression; + + /// + /// Gets the first value from the supplied arguments of an expression, for those arguments that can be cast to + /// . + /// + /// The arguments. + /// + /// + public static object? GetFirstValueFromArguments(IEnumerable arguments) + { + if (arguments == null) { - if (arguments == null) return false; - return - arguments.Where(x => x is ConstantExpression).Cast - ().Select(x => x.Value).DefaultIfEmpty(null).FirstOrDefault(); + return false; } + + return + arguments.Where(x => x is ConstantExpression).Cast + ().Select(x => x.Value).DefaultIfEmpty(null).FirstOrDefault(); } } diff --git a/src/Umbraco.Core/Extensions/AssemblyExtensions.cs b/src/Umbraco.Core/Extensions/AssemblyExtensions.cs index aea0f847abb6..87c0c1478c0e 100644 --- a/src/Umbraco.Core/Extensions/AssemblyExtensions.cs +++ b/src/Umbraco.Core/Extensions/AssemblyExtensions.cs @@ -1,106 +1,106 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.IO; using System.Reflection; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class AssemblyExtensions { - public static class AssemblyExtensions - { - private static string _rootDir = ""; + private static string _rootDir = ""; - /// - /// Utility method that returns the path to the root of the application, by getting the path to where the assembly where this - /// method is included is present, then traversing until it's past the /bin directory. Ie. this makes it work - /// even if the assembly is in a /bin/debug or /bin/release folder - /// - /// - public static string GetRootDirectorySafe(this Assembly executingAssembly) + /// + /// Utility method that returns the path to the root of the application, by getting the path to where the assembly + /// where this + /// method is included is present, then traversing until it's past the /bin directory. Ie. this makes it work + /// even if the assembly is in a /bin/debug or /bin/release folder + /// + /// + public static string GetRootDirectorySafe(this Assembly executingAssembly) + { + if (string.IsNullOrEmpty(_rootDir) == false) { - if (string.IsNullOrEmpty(_rootDir) == false) - { - return _rootDir; - } - var codeBase = executingAssembly.Location; - var uri = new Uri(codeBase); - var path = uri.LocalPath; - var baseDirectory = Path.GetDirectoryName(path); - if (string.IsNullOrEmpty(baseDirectory)) - throw new Exception("No root directory could be resolved. Please ensure that your Umbraco solution is correctly configured."); - - _rootDir = baseDirectory.Contains("bin") - ? baseDirectory.Substring(0, baseDirectory.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase) - 1) - : baseDirectory; - return _rootDir; } - /// - /// Returns the file used to load the assembly - /// - /// - /// - public static FileInfo GetAssemblyFile(this Assembly assembly) + var codeBase = executingAssembly.Location; + var uri = new Uri(codeBase); + var path = uri.LocalPath; + var baseDirectory = Path.GetDirectoryName(path); + if (string.IsNullOrEmpty(baseDirectory)) { - var codeBase = assembly.Location; - var uri = new Uri(codeBase); - var path = uri.LocalPath; - return new FileInfo(path); + throw new Exception( + "No root directory could be resolved. Please ensure that your Umbraco solution is correctly configured."); } - /// - /// Returns true if the assembly is the App_Code assembly - /// - /// - /// - public static bool IsAppCodeAssembly(this Assembly assembly) + _rootDir = baseDirectory.Contains("bin") + ? baseDirectory.Substring(0, baseDirectory.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase) - 1) + : baseDirectory; + + return _rootDir; + } + + /// + /// Returns the file used to load the assembly + /// + /// + /// + public static FileInfo GetAssemblyFile(this Assembly assembly) + { + var codeBase = assembly.Location; + var uri = new Uri(codeBase); + var path = uri.LocalPath; + return new FileInfo(path); + } + + /// + /// Returns true if the assembly is the App_Code assembly + /// + /// + /// + public static bool IsAppCodeAssembly(this Assembly assembly) + { + if (assembly.FullName!.StartsWith("App_Code")) { - if (assembly.FullName!.StartsWith("App_Code")) + try + { + Assembly.Load("App_Code"); + return true; + } + catch (FileNotFoundException) { - try - { - Assembly.Load("App_Code"); - return true; - } - catch (FileNotFoundException) - { - //this will occur if it cannot load the assembly - return false; - } + //this will occur if it cannot load the assembly + return false; } - return false; } - /// - /// Returns true if the assembly is the compiled global asax. - /// - /// - /// - public static bool IsGlobalAsaxAssembly(this Assembly assembly) - { - //only way I can figure out how to test is by the name - return assembly.FullName!.StartsWith("App_global.asax"); - } + return false; + } - /// - /// Returns the file used to load the assembly - /// - /// - /// - public static FileInfo? GetAssemblyFile(this AssemblyName assemblyName) - { - var codeBase = assemblyName.CodeBase; - if (!string.IsNullOrEmpty(codeBase)) - { - var uri = new Uri(codeBase); - var path = uri.LocalPath; - return new FileInfo(path); - } + /// + /// Returns true if the assembly is the compiled global asax. + /// + /// + /// + public static bool IsGlobalAsaxAssembly(this Assembly assembly) => + //only way I can figure out how to test is by the name + assembly.FullName!.StartsWith("App_global.asax"); - return null; + /// + /// Returns the file used to load the assembly + /// + /// + /// + public static FileInfo? GetAssemblyFile(this AssemblyName assemblyName) + { + var codeBase = assemblyName.CodeBase; + if (!string.IsNullOrEmpty(codeBase)) + { + var uri = new Uri(codeBase); + var path = uri.LocalPath; + return new FileInfo(path); } + return null; } } diff --git a/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs b/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs index e3d6f7f4fd33..93694124711b 100644 --- a/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs +++ b/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs @@ -1,378 +1,397 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.Linq; using System.Security.Claims; using System.Security.Principal; using Umbraco.Cms.Core; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class ClaimsIdentityExtensions { - public static class ClaimsIdentityExtensions + /// + /// Returns the required claim types for a back office identity + /// + /// + /// This does not include the role claim type or allowed apps type since that is a collection and in theory could be + /// empty + /// + public static IEnumerable RequiredBackOfficeClaimTypes => new[] + { + ClaimTypes.NameIdentifier, //id + ClaimTypes.Name, //username + ClaimTypes.GivenName, + // Constants.Security.StartContentNodeIdClaimType, These seem to be able to be null... + // Constants.Security.StartMediaNodeIdClaimType, + ClaimTypes.Locality, Constants.Security.SecurityStampClaimType + }; + + public static T? GetUserId(this IIdentity identity) + { + var strId = identity.GetUserId(); + Attempt converted = strId.TryConvertTo(); + return converted.Result ?? default; + } + + /// + /// Returns the user id from the of either the claim type + /// or "sub" + /// + /// + /// + /// The string value of the user id if found otherwise null + /// + public static string? GetUserId(this IIdentity identity) { - public static T? GetUserId(this IIdentity identity) + if (identity == null) { - var strId = identity.GetUserId(); - var converted = strId.TryConvertTo(); - return converted.Result ?? default; + throw new ArgumentNullException(nameof(identity)); } - /// - /// Returns the user id from the of either the claim type or "sub" - /// - /// - /// - /// The string value of the user id if found otherwise null - /// - public static string? GetUserId(this IIdentity identity) + string? userId = null; + if (identity is ClaimsIdentity claimsIdentity) { - if (identity == null) throw new ArgumentNullException(nameof(identity)); + userId = claimsIdentity.FindFirstValue(ClaimTypes.NameIdentifier) + ?? claimsIdentity.FindFirstValue("sub"); + } - string? userId = null; - if (identity is ClaimsIdentity claimsIdentity) - { - userId = claimsIdentity.FindFirstValue(ClaimTypes.NameIdentifier) - ?? claimsIdentity.FindFirstValue("sub"); - } + return userId; + } - return userId; + /// + /// Returns the user name from the of either the claim type or + /// "preferred_username" + /// + /// + /// + /// The string value of the user name if found otherwise null + /// + public static string? GetUserName(this IIdentity identity) + { + if (identity == null) + { + throw new ArgumentNullException(nameof(identity)); } - /// - /// Returns the user name from the of either the claim type or "preferred_username" - /// - /// - /// - /// The string value of the user name if found otherwise null - /// - public static string? GetUserName(this IIdentity identity) + string? username = null; + if (identity is ClaimsIdentity claimsIdentity) { - if (identity == null) throw new ArgumentNullException(nameof(identity)); + username = claimsIdentity.FindFirstValue(ClaimTypes.Name) + ?? claimsIdentity.FindFirstValue("preferred_username"); + } - string? username = null; - if (identity is ClaimsIdentity claimsIdentity) - { - username = claimsIdentity.FindFirstValue(ClaimTypes.Name) - ?? claimsIdentity.FindFirstValue("preferred_username"); - } + return username; + } - return username; + public static string? GetEmail(this IIdentity identity) + { + if (identity == null) + { + throw new ArgumentNullException(nameof(identity)); } - public static string? GetEmail(this IIdentity identity) + string? email = null; + if (identity is ClaimsIdentity claimsIdentity) { - if (identity == null) throw new ArgumentNullException(nameof(identity)); + email = claimsIdentity.FindFirstValue(ClaimTypes.Email); + } - string? email = null; - if (identity is ClaimsIdentity claimsIdentity) - { - email = claimsIdentity.FindFirstValue(ClaimTypes.Email); - } + return email; + } - return email; + /// + /// Returns the first claim value found in the for the given claimType + /// + /// + /// + /// + /// The string value of the claim if found otherwise null + /// + public static string? FindFirstValue(this ClaimsIdentity identity, string claimType) + { + if (identity == null) + { + throw new ArgumentNullException(nameof(identity)); } - /// - /// Returns the first claim value found in the for the given claimType - /// - /// - /// - /// - /// The string value of the claim if found otherwise null - /// - public static string? FindFirstValue(this ClaimsIdentity identity, string claimType) - { - if (identity == null) throw new ArgumentNullException(nameof(identity)); + return identity.FindFirst(claimType)?.Value; + } - return identity.FindFirst(claimType)?.Value; + /// + /// Verify that a ClaimsIdentity has all the required claim types + /// + /// + /// Verified identity wrapped in a ClaimsIdentity with BackOfficeAuthentication type + /// True if ClaimsIdentity + public static bool VerifyBackOfficeIdentity(this ClaimsIdentity identity, + [MaybeNullWhen(false)] out ClaimsIdentity verifiedIdentity) + { + if (identity is null) + { + verifiedIdentity = null; + return false; } - /// - /// Returns the required claim types for a back office identity - /// - /// - /// This does not include the role claim type or allowed apps type since that is a collection and in theory could be empty - /// - public static IEnumerable RequiredBackOfficeClaimTypes => new[] - { - ClaimTypes.NameIdentifier, //id - ClaimTypes.Name, //username - ClaimTypes.GivenName, - // Constants.Security.StartContentNodeIdClaimType, These seem to be able to be null... - // Constants.Security.StartMediaNodeIdClaimType, - ClaimTypes.Locality, - Constants.Security.SecurityStampClaimType - }; - - /// - /// Verify that a ClaimsIdentity has all the required claim types - /// - /// - /// Verified identity wrapped in a ClaimsIdentity with BackOfficeAuthentication type - /// True if ClaimsIdentity - public static bool VerifyBackOfficeIdentity(this ClaimsIdentity identity, [MaybeNullWhen(false)] out ClaimsIdentity verifiedIdentity) + // Validate that all required claims exist + foreach (var claimType in RequiredBackOfficeClaimTypes) { - if (identity is null) + if (identity.HasClaim(x => x.Type == claimType) == false || + identity.HasClaim(x => x.Type == claimType && x.Value.IsNullOrWhiteSpace())) { verifiedIdentity = null; return false; } + } - // Validate that all required claims exist - foreach (var claimType in RequiredBackOfficeClaimTypes) - { - if (identity.HasClaim(x => x.Type == claimType) == false || - identity.HasClaim(x => x.Type == claimType && x.Value.IsNullOrWhiteSpace())) - { - verifiedIdentity = null; - return false; - } - } + verifiedIdentity = identity.AuthenticationType == Constants.Security.BackOfficeAuthenticationType + ? identity + : new ClaimsIdentity(identity.Claims, Constants.Security.BackOfficeAuthenticationType); + return true; + } - verifiedIdentity = identity.AuthenticationType == Constants.Security.BackOfficeAuthenticationType ? identity : new ClaimsIdentity(identity.Claims, Constants.Security.BackOfficeAuthenticationType); - return true; + /// + /// Add the required claims to be a BackOffice ClaimsIdentity + /// + /// this + /// The users Id + /// Username + /// Real name + /// Start content nodes + /// Start media nodes + /// The locality of the user + /// Security stamp + /// Allowed apps + /// Roles + public static void AddRequiredClaims(this ClaimsIdentity identity, string userId, string username, + string realName, IEnumerable? startContentNodes, IEnumerable? startMediaNodes, string culture, + string securityStamp, IEnumerable allowedApps, IEnumerable roles) + { + //This is the id that 'identity' uses to check for the user id + if (identity.HasClaim(x => x.Type == ClaimTypes.NameIdentifier) == false) + { + identity.AddClaim(new Claim( + ClaimTypes.NameIdentifier, + userId, + ClaimValueTypes.String, + Constants.Security.BackOfficeAuthenticationType, + Constants.Security.BackOfficeAuthenticationType, + identity)); } - /// - /// Add the required claims to be a BackOffice ClaimsIdentity - /// - /// this - /// The users Id - /// Username - /// Real name - /// Start content nodes - /// Start media nodes - /// The locality of the user - /// Security stamp - /// Allowed apps - /// Roles - public static void AddRequiredClaims(this ClaimsIdentity identity, string userId, string username, - string realName, IEnumerable? startContentNodes, IEnumerable? startMediaNodes, string culture, - string securityStamp, IEnumerable allowedApps, IEnumerable roles) + if (identity.HasClaim(x => x.Type == ClaimTypes.Name) == false) { - //This is the id that 'identity' uses to check for the user id - if (identity.HasClaim(x => x.Type == ClaimTypes.NameIdentifier) == false) - { - identity.AddClaim(new Claim( - ClaimTypes.NameIdentifier, - userId, - ClaimValueTypes.String, - Constants.Security.BackOfficeAuthenticationType, - Constants.Security.BackOfficeAuthenticationType, - identity)); - } + identity.AddClaim(new Claim( + ClaimTypes.Name, + username, + ClaimValueTypes.String, + Constants.Security.BackOfficeAuthenticationType, + Constants.Security.BackOfficeAuthenticationType, + identity)); + } + + if (identity.HasClaim(x => x.Type == ClaimTypes.GivenName) == false) + { + identity.AddClaim(new Claim( + ClaimTypes.GivenName, + realName, + ClaimValueTypes.String, + Constants.Security.BackOfficeAuthenticationType, + Constants.Security.BackOfficeAuthenticationType, + identity)); + } - if (identity.HasClaim(x => x.Type == ClaimTypes.Name) == false) + if (identity.HasClaim(x => x.Type == Constants.Security.StartContentNodeIdClaimType) == false && + startContentNodes != null) + { + foreach (var startContentNode in startContentNodes) { identity.AddClaim(new Claim( - ClaimTypes.Name, - username, - ClaimValueTypes.String, + Constants.Security.StartContentNodeIdClaimType, + startContentNode.ToInvariantString(), + ClaimValueTypes.Integer32, Constants.Security.BackOfficeAuthenticationType, Constants.Security.BackOfficeAuthenticationType, identity)); } + } - if (identity.HasClaim(x => x.Type == ClaimTypes.GivenName) == false) + if (identity.HasClaim(x => x.Type == Constants.Security.StartMediaNodeIdClaimType) == false && + startMediaNodes != null) + { + foreach (var startMediaNode in startMediaNodes) { identity.AddClaim(new Claim( - ClaimTypes.GivenName, - realName, - ClaimValueTypes.String, + Constants.Security.StartMediaNodeIdClaimType, + startMediaNode.ToInvariantString(), + ClaimValueTypes.Integer32, Constants.Security.BackOfficeAuthenticationType, Constants.Security.BackOfficeAuthenticationType, identity)); } + } - if (identity.HasClaim(x => x.Type == Constants.Security.StartContentNodeIdClaimType) == false && - startContentNodes != null) - { - foreach (var startContentNode in startContentNodes) - { - identity.AddClaim(new Claim( - Constants.Security.StartContentNodeIdClaimType, - startContentNode.ToInvariantString(), - ClaimValueTypes.Integer32, - Constants.Security.BackOfficeAuthenticationType, - Constants.Security.BackOfficeAuthenticationType, - identity)); - } - } + if (identity.HasClaim(x => x.Type == ClaimTypes.Locality) == false) + { + identity.AddClaim(new Claim( + ClaimTypes.Locality, + culture, + ClaimValueTypes.String, + Constants.Security.BackOfficeAuthenticationType, + Constants.Security.BackOfficeAuthenticationType, + identity)); + } - if (identity.HasClaim(x => x.Type == Constants.Security.StartMediaNodeIdClaimType) == false && - startMediaNodes != null) - { - foreach (var startMediaNode in startMediaNodes) - { - identity.AddClaim(new Claim( - Constants.Security.StartMediaNodeIdClaimType, - startMediaNode.ToInvariantString(), - ClaimValueTypes.Integer32, - Constants.Security.BackOfficeAuthenticationType, - Constants.Security.BackOfficeAuthenticationType, - identity)); - } - } + // The security stamp claim is also required + if (identity.HasClaim(x => x.Type == Constants.Security.SecurityStampClaimType) == false) + { + identity.AddClaim(new Claim( + Constants.Security.SecurityStampClaimType, + securityStamp, + ClaimValueTypes.String, + Constants.Security.BackOfficeAuthenticationType, + Constants.Security.BackOfficeAuthenticationType, + identity)); + } - if (identity.HasClaim(x => x.Type == ClaimTypes.Locality) == false) + // Add each app as a separate claim + if (identity.HasClaim(x => x.Type == Constants.Security.AllowedApplicationsClaimType) == false && + allowedApps != null) + { + foreach (var application in allowedApps) { identity.AddClaim(new Claim( - ClaimTypes.Locality, - culture, + Constants.Security.AllowedApplicationsClaimType, + application, ClaimValueTypes.String, Constants.Security.BackOfficeAuthenticationType, Constants.Security.BackOfficeAuthenticationType, identity)); } + } - // The security stamp claim is also required - if (identity.HasClaim(x => x.Type == Constants.Security.SecurityStampClaimType) == false) + // Claims are added by the ClaimsIdentityFactory because our UserStore supports roles, however this identity might + // not be made with that factory if it was created with a different ticket so perform the check + if (identity.HasClaim(x => x.Type == ClaimsIdentity.DefaultRoleClaimType) == false && roles != null) + { + // Manually add them + foreach (var roleName in roles) { identity.AddClaim(new Claim( - Constants.Security.SecurityStampClaimType, - securityStamp, + identity.RoleClaimType, + roleName, ClaimValueTypes.String, Constants.Security.BackOfficeAuthenticationType, Constants.Security.BackOfficeAuthenticationType, identity)); } - - // Add each app as a separate claim - if (identity.HasClaim(x => x.Type == Constants.Security.AllowedApplicationsClaimType) == false && - allowedApps != null) - { - foreach (var application in allowedApps) - { - identity.AddClaim(new Claim( - Constants.Security.AllowedApplicationsClaimType, - application, - ClaimValueTypes.String, - Constants.Security.BackOfficeAuthenticationType, - Constants.Security.BackOfficeAuthenticationType, - identity)); - } - } - - // Claims are added by the ClaimsIdentityFactory because our UserStore supports roles, however this identity might - // not be made with that factory if it was created with a different ticket so perform the check - if (identity.HasClaim(x => x.Type == ClaimsIdentity.DefaultRoleClaimType) == false && roles != null) - { - // Manually add them - foreach (var roleName in roles) - { - identity.AddClaim(new Claim( - identity.RoleClaimType, - roleName, - ClaimValueTypes.String, - Constants.Security.BackOfficeAuthenticationType, - Constants.Security.BackOfficeAuthenticationType, - identity)); - } - } } + } - /// - /// Get the start content nodes from a ClaimsIdentity - /// - /// - /// Array of start content nodes - public static int[] GetStartContentNodes(this ClaimsIdentity identity) => - identity.FindAll(x => x.Type == Constants.Security.StartContentNodeIdClaimType) - .Select(node => int.TryParse(node.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var i) ? i : default) - .Where(x => x != default).ToArray(); - - /// - /// Get the start media nodes from a ClaimsIdentity - /// - /// - /// Array of start media nodes - public static int[] GetStartMediaNodes(this ClaimsIdentity identity) => - identity.FindAll(x => x.Type == Constants.Security.StartMediaNodeIdClaimType) - .Select(node => int.TryParse(node.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var i) ? i : default) - .Where(x => x != default).ToArray(); - - /// - /// Get the allowed applications from a ClaimsIdentity - /// - /// - /// - public static string[] GetAllowedApplications(this ClaimsIdentity identity) => identity - .FindAll(x => x.Type == Constants.Security.AllowedApplicationsClaimType).Select(app => app.Value).ToArray(); - - /// - /// Get the user ID from a ClaimsIdentity - /// - /// - /// User ID as integer - public static int? GetId(this ClaimsIdentity identity) + /// + /// Get the start content nodes from a ClaimsIdentity + /// + /// + /// Array of start content nodes + public static int[] GetStartContentNodes(this ClaimsIdentity identity) => + identity.FindAll(x => x.Type == Constants.Security.StartContentNodeIdClaimType) + .Select(node => int.TryParse(node.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var i) + ? i + : default) + .Where(x => x != default).ToArray(); + + /// + /// Get the start media nodes from a ClaimsIdentity + /// + /// + /// Array of start media nodes + public static int[] GetStartMediaNodes(this ClaimsIdentity identity) => + identity.FindAll(x => x.Type == Constants.Security.StartMediaNodeIdClaimType) + .Select(node => int.TryParse(node.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var i) + ? i + : default) + .Where(x => x != default).ToArray(); + + /// + /// Get the allowed applications from a ClaimsIdentity + /// + /// + /// + public static string[] GetAllowedApplications(this ClaimsIdentity identity) => identity + .FindAll(x => x.Type == Constants.Security.AllowedApplicationsClaimType).Select(app => app.Value).ToArray(); + + /// + /// Get the user ID from a ClaimsIdentity + /// + /// + /// User ID as integer + public static int? GetId(this ClaimsIdentity identity) + { + var firstValue = identity.FindFirstValue(ClaimTypes.NameIdentifier); + if (firstValue is not null) { - var firstValue = identity.FindFirstValue(ClaimTypes.NameIdentifier); - if (firstValue is not null) - { - return int.Parse(firstValue, CultureInfo.InvariantCulture); - } - - return null; + return int.Parse(firstValue, CultureInfo.InvariantCulture); } - /// - /// Get the real name belonging to the user from a ClaimsIdentity - /// - /// - /// Real name of the user - public static string? GetRealName(this ClaimsIdentity identity) => identity.FindFirstValue(ClaimTypes.GivenName); - - /// - /// Get the username of the user from a ClaimsIdentity - /// - /// - /// Username of the user - public static string? GetUsername(this ClaimsIdentity identity) => identity.FindFirstValue(ClaimTypes.Name); - - /// - /// Get the culture string from a ClaimsIdentity - /// - /// - /// Culture string - public static string? GetCultureString(this ClaimsIdentity identity) => identity.FindFirstValue(ClaimTypes.Locality); - - /// - /// Get the security stamp from a ClaimsIdentity - /// - /// - /// Security stamp - public static string? GetSecurityStamp(this ClaimsIdentity identity) => identity.FindFirstValue(Constants.Security.SecurityStampClaimType); - - /// - /// Get the roles assigned to a user from a ClaimsIdentity - /// - /// - /// Array of roles - public static string[] GetRoles(this ClaimsIdentity identity) => identity - .FindAll(x => x.Type == ClaimsIdentity.DefaultRoleClaimType).Select(role => role.Value).ToArray(); - - - /// - /// Adds or updates and existing claim. - /// - public static void AddOrUpdateClaim(this ClaimsIdentity identity, Claim? claim) + return null; + } + + /// + /// Get the real name belonging to the user from a ClaimsIdentity + /// + /// + /// Real name of the user + public static string? GetRealName(this ClaimsIdentity identity) => identity.FindFirstValue(ClaimTypes.GivenName); + + /// + /// Get the username of the user from a ClaimsIdentity + /// + /// + /// Username of the user + public static string? GetUsername(this ClaimsIdentity identity) => identity.FindFirstValue(ClaimTypes.Name); + + /// + /// Get the culture string from a ClaimsIdentity + /// + /// + /// Culture string + public static string? GetCultureString(this ClaimsIdentity identity) => + identity.FindFirstValue(ClaimTypes.Locality); + + /// + /// Get the security stamp from a ClaimsIdentity + /// + /// + /// Security stamp + public static string? GetSecurityStamp(this ClaimsIdentity identity) => + identity.FindFirstValue(Constants.Security.SecurityStampClaimType); + + /// + /// Get the roles assigned to a user from a ClaimsIdentity + /// + /// + /// Array of roles + public static string[] GetRoles(this ClaimsIdentity identity) => identity + .FindAll(x => x.Type == ClaimsIdentity.DefaultRoleClaimType).Select(role => role.Value).ToArray(); + + + /// + /// Adds or updates and existing claim. + /// + public static void AddOrUpdateClaim(this ClaimsIdentity identity, Claim? claim) + { + if (identity == null) { - if (identity == null) - { - throw new ArgumentNullException(nameof(identity)); - } + throw new ArgumentNullException(nameof(identity)); + } - if (claim is not null) - { - Claim? existingClaim = identity.Claims.FirstOrDefault(x => x.Type == claim.Type); - identity.TryRemoveClaim(existingClaim); + if (claim is not null) + { + Claim? existingClaim = identity.Claims.FirstOrDefault(x => x.Type == claim.Type); + identity.TryRemoveClaim(existingClaim); - identity.AddClaim(claim); - } + identity.AddClaim(claim); } } } diff --git a/src/Umbraco.Core/Extensions/ConnectionStringExtensions.cs b/src/Umbraco.Core/Extensions/ConnectionStringExtensions.cs index 6af569e514ca..8d739a97e4f9 100644 --- a/src/Umbraco.Core/Extensions/ConnectionStringExtensions.cs +++ b/src/Umbraco.Core/Extensions/ConnectionStringExtensions.cs @@ -1,35 +1,33 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Microsoft.Extensions.Configuration; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class ConnectionStringExtensions { - public static class ConnectionStringExtensions - { - public static bool IsConnectionStringConfigured(this ConnectionStrings connectionString) - => connectionString != null && - !string.IsNullOrWhiteSpace(connectionString.ConnectionString) && - !string.IsNullOrWhiteSpace(connectionString.ProviderName); + public static bool IsConnectionStringConfigured(this ConnectionStrings connectionString) + => connectionString != null && + !string.IsNullOrWhiteSpace(connectionString.ConnectionString) && + !string.IsNullOrWhiteSpace(connectionString.ProviderName); - /// - /// Gets a connection string from configuration with placeholders replaced. - /// - public static string? GetUmbracoConnectionString( - this IConfiguration configuration, - string connectionStringName = Constants.System.UmbracoConnectionName) => - configuration.GetConnectionString(connectionStringName).ReplaceDataDirectoryPlaceholder(); + /// + /// Gets a connection string from configuration with placeholders replaced. + /// + public static string? GetUmbracoConnectionString( + this IConfiguration configuration, + string connectionStringName = Constants.System.UmbracoConnectionName) => + configuration.GetConnectionString(connectionStringName).ReplaceDataDirectoryPlaceholder(); - /// - /// Replaces instances of the |DataDirectory| placeholder in a string with the value of AppDomain DataDirectory. - /// - public static string? ReplaceDataDirectoryPlaceholder(this string input) - { - var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString(); - return input?.Replace(ConnectionStrings.DataDirectoryPlaceholder, dataDirectory); - } + /// + /// Replaces instances of the |DataDirectory| placeholder in a string with the value of AppDomain DataDirectory. + /// + public static string? ReplaceDataDirectoryPlaceholder(this string input) + { + var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString(); + return input?.Replace(ConnectionStrings.DataDirectoryPlaceholder, dataDirectory); } } diff --git a/src/Umbraco.Core/Extensions/ContentExtensions.cs b/src/Umbraco.Core/Extensions/ContentExtensions.cs index 0bd1e36d9eb0..747fcfc74192 100644 --- a/src/Umbraco.Core/Extensions/ContentExtensions.cs +++ b/src/Umbraco.Core/Extensions/ContentExtensions.cs @@ -1,11 +1,7 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using System.Globalization; -using System.IO; -using System.Linq; using System.Xml.Linq; using Umbraco.Cms.Core; using Umbraco.Cms.Core.IO; @@ -15,395 +11,404 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class ContentExtensions { - public static class ContentExtensions + /// + /// Returns the path to a media item stored in a property if the property editor is + /// + /// + /// + /// + /// + /// + /// + /// True if the file path can be resolved and the property is + public static bool TryGetMediaPath( + this IContentBase content, + string propertyTypeAlias, + MediaUrlGeneratorCollection mediaUrlGenerators, + out string? mediaFilePath, + string? culture = null, + string? segment = null) { - /// - /// Returns the path to a media item stored in a property if the property editor is - /// - /// - /// - /// - /// - /// - /// - /// True if the file path can be resolved and the property is - public static bool TryGetMediaPath( - this IContentBase content, - string propertyTypeAlias, - MediaUrlGeneratorCollection mediaUrlGenerators, - out string? mediaFilePath, - string? culture = null, - string? segment = null) + if (!content.Properties.TryGetValue(propertyTypeAlias, out IProperty? property)) { - if (!content.Properties.TryGetValue(propertyTypeAlias, out IProperty? property)) - { - mediaFilePath = null; - return false; - } + mediaFilePath = null; + return false; + } - if (!mediaUrlGenerators.TryGetMediaPath( + if (!mediaUrlGenerators.TryGetMediaPath( property?.PropertyType?.PropertyEditorAlias, property?.GetValue(culture, segment), out mediaFilePath)) - { - return false; - } - - return true; - } - - public static bool IsAnyUserPropertyDirty(this IContentBase entity) { - return entity.Properties.Any(x => x.IsDirty()); + return false; } - public static bool WasAnyUserPropertyDirty(this IContentBase entity) - { - return entity.Properties.Any(x => x.WasDirty()); - } + return true; + } + public static bool IsAnyUserPropertyDirty(this IContentBase entity) => entity.Properties.Any(x => x.IsDirty()); - public static bool IsMoving(this IContentBase entity) - { - // Check if this entity is being moved as a descendant as part of a bulk moving operations. - // When this occurs, only Path + Level + UpdateDate are being changed. In this case we can bypass a lot of the below - // operations which will make this whole operation go much faster. When moving we don't need to create - // new versions, etc... because we cannot roll this operation back anyways. - var isMoving = entity.IsPropertyDirty(nameof(entity.Path)) - && entity.IsPropertyDirty(nameof(entity.Level)) - && entity.IsPropertyDirty(nameof(entity.UpdateDate)); - - return isMoving; - } + public static bool WasAnyUserPropertyDirty(this IContentBase entity) => entity.Properties.Any(x => x.WasDirty()); + + + public static bool IsMoving(this IContentBase entity) + { + // Check if this entity is being moved as a descendant as part of a bulk moving operations. + // When this occurs, only Path + Level + UpdateDate are being changed. In this case we can bypass a lot of the below + // operations which will make this whole operation go much faster. When moving we don't need to create + // new versions, etc... because we cannot roll this operation back anyways. + var isMoving = entity.IsPropertyDirty(nameof(entity.Path)) + && entity.IsPropertyDirty(nameof(entity.Level)) + && entity.IsPropertyDirty(nameof(entity.UpdateDate)); + + return isMoving; + } - /// - /// Removes characters that are not valid XML characters from all entity properties - /// of type string. See: http://stackoverflow.com/a/961504/5018 - /// - /// - /// - /// If this is not done then the xml cache can get corrupt and it will throw YSODs upon reading it. - /// - /// - public static void SanitizeEntityPropertiesForXmlStorage(this IContentBase entity) + /// + /// Removes characters that are not valid XML characters from all entity properties + /// of type string. See: http://stackoverflow.com/a/961504/5018 + /// + /// + /// + /// If this is not done then the xml cache can get corrupt and it will throw YSODs upon reading it. + /// + /// + public static void SanitizeEntityPropertiesForXmlStorage(this IContentBase entity) + { + entity.Name = entity.Name?.ToValidXmlString(); + foreach (IProperty property in entity.Properties) { - entity.Name = entity.Name?.ToValidXmlString(); - foreach (var property in entity.Properties) + foreach (IPropertyValue propertyValue in property.Values) { - foreach (var propertyValue in property.Values) + if (propertyValue.EditedValue is string editString) { - if (propertyValue.EditedValue is string editString) - propertyValue.EditedValue = editString.ToValidXmlString(); - if (propertyValue.PublishedValue is string publishedString) - propertyValue.PublishedValue = publishedString.ToValidXmlString(); + propertyValue.EditedValue = editString.ToValidXmlString(); + } + + if (propertyValue.PublishedValue is string publishedString) + { + propertyValue.PublishedValue = publishedString.ToValidXmlString(); } } } + } - /// - /// Checks if the IContentBase has children - /// - /// - /// - /// - /// - /// This is a bit of a hack because we need to type check! - /// - internal static bool? HasChildren(IContentBase content, ServiceContext services) + /// + /// Checks if the IContentBase has children + /// + /// + /// + /// + /// + /// This is a bit of a hack because we need to type check! + /// + internal static bool? HasChildren(IContentBase content, ServiceContext services) + { + if (content is IContent) { - if (content is IContent) - { - return services.ContentService?.HasChildren(content.Id); - } - if (content is IMedia) - { - return services.MediaService?.HasChildren(content.Id); - } - return false; + return services.ContentService?.HasChildren(content.Id); } - - /// - /// Returns all properties based on the editorAlias - /// - /// - /// - /// - public static IEnumerable GetPropertiesByEditor(this IContentBase content, string editorAlias) - => content.Properties.Where(x => x.PropertyType?.PropertyEditorAlias == editorAlias); - - - #region IContent - - /// - /// Gets the current status of the Content - /// - public static ContentStatus GetStatus(this IContent content, ContentScheduleCollection contentSchedule, string? culture = null) + if (content is IMedia) { - if (content.Trashed) - return ContentStatus.Trashed; - - if (!content.ContentType.VariesByCulture()) - culture = string.Empty; - else if (culture.IsNullOrWhiteSpace()) - throw new ArgumentNullException($"{nameof(culture)} cannot be null or empty"); - - var expires = contentSchedule.GetSchedule(culture!, ContentScheduleAction.Expire); - if (expires != null && expires.Any(x => x.Date > DateTime.MinValue && DateTime.Now > x.Date)) - return ContentStatus.Expired; - - var release = contentSchedule.GetSchedule(culture!, ContentScheduleAction.Release); - if (release != null && release.Any(x => x.Date > DateTime.MinValue && x.Date > DateTime.Now)) - return ContentStatus.AwaitingRelease; - - if (content.Published) - return ContentStatus.Published; - - return ContentStatus.Unpublished; + return services.MediaService?.HasChildren(content.Id); } - /// - /// Gets a collection containing the ids of all ancestors. - /// - /// to retrieve ancestors for - /// An Enumerable list of integer ids - public static IEnumerable? GetAncestorIds(this IContent content) => - content.Path?.Split(Constants.CharArrays.Comma) - .Where(x => x != Constants.System.RootString && x != content.Id.ToString(CultureInfo.InvariantCulture)).Select(s => - int.Parse(s, CultureInfo.InvariantCulture)); - - #endregion + return false; + } - /// - /// Gets the for the Creator of this content item. - /// - public static IProfile? GetCreatorProfile(this IContentBase content, IUserService userService) - { - return userService.GetProfileById(content.CreatorId); - } - /// - /// Gets the for the Writer of this content. - /// - public static IProfile? GetWriterProfile(this IContent content, IUserService userService) + /// + /// Returns all properties based on the editorAlias + /// + /// + /// + /// + public static IEnumerable GetPropertiesByEditor(this IContentBase content, string editorAlias) + => content.Properties.Where(x => x.PropertyType?.PropertyEditorAlias == editorAlias); + + + /// + /// Gets the for the Creator of this content item. + /// + public static IProfile? GetCreatorProfile(this IContentBase content, IUserService userService) => + userService.GetProfileById(content.CreatorId); + + /// + /// Gets the for the Writer of this content. + /// + public static IProfile? GetWriterProfile(this IContent content, IUserService userService) => + userService.GetProfileById(content.WriterId); + + /// + /// Gets the for the Writer of this content. + /// + public static IProfile? GetWriterProfile(this IMedia content, IUserService userService) => + userService.GetProfileById(content.WriterId); + + + #region User/Profile methods + + /// + /// Gets the for the Creator of this media item. + /// + public static IProfile? GetCreatorProfile(this IMedia media, IUserService userService) => + userService.GetProfileById(media.CreatorId); + + #endregion + + + /// + /// Returns properties that do not belong to a group + /// + /// + /// + public static IEnumerable GetNonGroupedProperties(this IContentBase content) => + content.Properties + .Where(x => x.PropertyType?.PropertyGroupId == null) + .OrderBy(x => x.PropertyType?.SortOrder); + + /// + /// Returns the Property object for the given property group + /// + /// + /// + /// + public static IEnumerable + GetPropertiesForGroup(this IContentBase content, PropertyGroup propertyGroup) => + //get the properties for the current tab + content.Properties + .Where(property => propertyGroup.PropertyTypes is not null && propertyGroup.PropertyTypes + .Select(propertyType => propertyType.Id) + .Contains(property.PropertyTypeId)); + + + #region Dirty + + public static IEnumerable GetDirtyUserProperties(this IContentBase entity) => + entity.Properties.Where(x => x.IsDirty()).Select(x => x.Alias); + + #endregion + + + /// + /// Creates the full xml representation for the object and all of it's descendants + /// + /// to generate xml for + /// + /// Xml representation of the passed in + public static XElement ToDeepXml(this IContent content, IEntityXmlSerializer serializer) => + serializer.Serialize(content, false, true); + + /// + /// Creates the xml representation for the object + /// + /// to generate xml for + /// + /// Xml representation of the passed in + public static XElement ToXml(this IContent content, IEntityXmlSerializer serializer) => + serializer.Serialize(content, false); + + + /// + /// Creates the xml representation for the object + /// + /// to generate xml for + /// + /// Xml representation of the passed in + public static XElement ToXml(this IMedia media, IEntityXmlSerializer serializer) => serializer.Serialize(media); + + /// + /// Creates the xml representation for the object + /// + /// to generate xml for + /// + /// Xml representation of the passed in + public static XElement ToXml(this IMember member, IEntityXmlSerializer serializer) => serializer.Serialize(member); + + + #region IContent + + /// + /// Gets the current status of the Content + /// + public static ContentStatus GetStatus(this IContent content, ContentScheduleCollection contentSchedule, + string? culture = null) + { + if (content.Trashed) { - return userService.GetProfileById(content.WriterId); + return ContentStatus.Trashed; } - /// - /// Gets the for the Writer of this content. - /// - public static IProfile? GetWriterProfile(this IMedia content, IUserService userService) + if (!content.ContentType.VariesByCulture()) { - return userService.GetProfileById(content.WriterId); + culture = string.Empty; } - - - #region User/Profile methods - - /// - /// Gets the for the Creator of this media item. - /// - public static IProfile? GetCreatorProfile(this IMedia media, IUserService userService) + else if (culture.IsNullOrWhiteSpace()) { - return userService.GetProfileById(media.CreatorId); + throw new ArgumentNullException($"{nameof(culture)} cannot be null or empty"); } - - #endregion - - - /// - /// Returns properties that do not belong to a group - /// - /// - /// - public static IEnumerable GetNonGroupedProperties(this IContentBase content) + IEnumerable expires = contentSchedule.GetSchedule(culture!, ContentScheduleAction.Expire); + if (expires != null && expires.Any(x => x.Date > DateTime.MinValue && DateTime.Now > x.Date)) { - return content.Properties - .Where(x => x.PropertyType?.PropertyGroupId == null) - .OrderBy(x => x.PropertyType?.SortOrder); + return ContentStatus.Expired; } - /// - /// Returns the Property object for the given property group - /// - /// - /// - /// - public static IEnumerable GetPropertiesForGroup(this IContentBase content, PropertyGroup propertyGroup) + IEnumerable release = contentSchedule.GetSchedule(culture!, ContentScheduleAction.Release); + if (release != null && release.Any(x => x.Date > DateTime.MinValue && x.Date > DateTime.Now)) { - //get the properties for the current tab - return content.Properties - .Where(property => propertyGroup.PropertyTypes is not null && propertyGroup.PropertyTypes - .Select(propertyType => propertyType.Id) - .Contains(property.PropertyTypeId)); + return ContentStatus.AwaitingRelease; } - - #region SetValue for setting file contents - - /// - /// Sets the posted file value of a property. - /// - public static void SetValue( - this IContentBase content, - MediaFileManager mediaFileManager, - MediaUrlGeneratorCollection mediaUrlGenerators, - IShortStringHelper shortStringHelper, - IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, - string propertyTypeAlias, - string filename, - Stream filestream, - string? culture = null, - string? segment = null) + if (content.Published) { - if (filename == null || filestream == null) - return; - - filename = shortStringHelper.CleanStringForSafeFileName(filename); - if (string.IsNullOrWhiteSpace(filename)) - return; - filename = filename.ToLower(); - - SetUploadFile(content, mediaFileManager, mediaUrlGenerators, contentTypeBaseServiceProvider, propertyTypeAlias, filename, filestream, culture, segment); + return ContentStatus.Published; } - private static void SetUploadFile( - this IContentBase content, - MediaFileManager mediaFileManager, - MediaUrlGeneratorCollection mediaUrlGenerators, - IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, - string propertyTypeAlias, - string filename, - Stream filestream, - string? culture = null, - string? segment = null) - { - var property = GetProperty(content, contentTypeBaseServiceProvider, propertyTypeAlias); - - // Fixes https://github.com/umbraco/Umbraco-CMS/issues/3937 - Assigning a new file to an - // existing IMedia with extension SetValue causes exception 'Illegal characters in path' - string? oldpath = null; - - if (content.TryGetMediaPath(property.Alias, mediaUrlGenerators, out string? mediaFilePath, culture, segment)) - { - oldpath = mediaFileManager.FileSystem.GetRelativePath(mediaFilePath!); - } - - var filepath = mediaFileManager.StoreFile(content, property.PropertyType, filename, filestream, oldpath); - - // NOTE: Here we are just setting the value to a string which means that any file based editor - // will need to handle the raw string value and save it to it's correct (i.e. JSON) - // format. I'm unsure how this works today with image cropper but it does (maybe events?) - property.SetValue(mediaFileManager.FileSystem.GetUrl(filepath), culture, segment); - } + return ContentStatus.Unpublished; + } - // gets or creates a property for a content item. - private static IProperty GetProperty(IContentBase content, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, string propertyTypeAlias) + /// + /// Gets a collection containing the ids of all ancestors. + /// + /// to retrieve ancestors for + /// An Enumerable list of integer ids + public static IEnumerable? GetAncestorIds(this IContent content) => + content.Path?.Split(Constants.CharArrays.Comma) + .Where(x => x != Constants.System.RootString && x != content.Id.ToString(CultureInfo.InvariantCulture)) + .Select(s => + int.Parse(s, CultureInfo.InvariantCulture)); + + #endregion + + + #region SetValue for setting file contents + + /// + /// Sets the posted file value of a property. + /// + public static void SetValue( + this IContentBase content, + MediaFileManager mediaFileManager, + MediaUrlGeneratorCollection mediaUrlGenerators, + IShortStringHelper shortStringHelper, + IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, + string propertyTypeAlias, + string filename, + Stream filestream, + string? culture = null, + string? segment = null) + { + if (filename == null || filestream == null) { - var property = content.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); - if (property != null) - return property; - - var contentType = contentTypeBaseServiceProvider.GetContentTypeOf(content); - var propertyType = contentType?.CompositionPropertyTypes - .FirstOrDefault(x => x.Alias?.InvariantEquals(propertyTypeAlias) ?? false); - if (propertyType == null) - throw new Exception("No property type exists with alias " + propertyTypeAlias + "."); - - property = new Property(propertyType); - content.Properties.Add(property); - return property; + return; } - /// - /// Stores a file. - /// - /// A content item. - /// The property alias. - /// The name of the file. - /// A stream containing the file data. - /// The original file path, if any. - /// The path to the file, relative to the media filesystem. - /// - /// Does NOT set the property value, so one should probably store the file and then do - /// something alike: property.Value = MediaHelper.FileSystem.GetUrl(filepath). - /// The original file path is used, in the old media file path scheme, to try and reuse - /// the "folder number" that was assigned to the previous file referenced by the property, - /// if any. - /// - public static string StoreFile(this IContentBase content, MediaFileManager mediaFileManager, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, string propertyTypeAlias, string filename, Stream filestream, string filepath) + filename = shortStringHelper.CleanStringForSafeFileName(filename); + if (string.IsNullOrWhiteSpace(filename)) { - var contentType = contentTypeBaseServiceProvider.GetContentTypeOf(content); - var propertyType = contentType? - .CompositionPropertyTypes.FirstOrDefault(x => x.Alias?.InvariantEquals(propertyTypeAlias) ?? false); - if (propertyType == null) - throw new ArgumentException("Invalid property type alias " + propertyTypeAlias + "."); - return mediaFileManager.StoreFile(content, propertyType, filename, filestream, filepath); + return; } - #endregion + filename = filename.ToLower(); + SetUploadFile(content, mediaFileManager, mediaUrlGenerators, contentTypeBaseServiceProvider, propertyTypeAlias, + filename, filestream, culture, segment); + } - #region Dirty + private static void SetUploadFile( + this IContentBase content, + MediaFileManager mediaFileManager, + MediaUrlGeneratorCollection mediaUrlGenerators, + IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, + string propertyTypeAlias, + string filename, + Stream filestream, + string? culture = null, + string? segment = null) + { + IProperty property = GetProperty(content, contentTypeBaseServiceProvider, propertyTypeAlias); - public static IEnumerable GetDirtyUserProperties(this IContentBase entity) + // Fixes https://github.com/umbraco/Umbraco-CMS/issues/3937 - Assigning a new file to an + // existing IMedia with extension SetValue causes exception 'Illegal characters in path' + string? oldpath = null; + + if (content.TryGetMediaPath(property.Alias, mediaUrlGenerators, out var mediaFilePath, culture, segment)) { - return entity.Properties.Where(x => x.IsDirty()).Select(x => x.Alias); + oldpath = mediaFileManager.FileSystem.GetRelativePath(mediaFilePath!); } + var filepath = mediaFileManager.StoreFile(content, property.PropertyType, filename, filestream, oldpath); + // NOTE: Here we are just setting the value to a string which means that any file based editor + // will need to handle the raw string value and save it to it's correct (i.e. JSON) + // format. I'm unsure how this works today with image cropper but it does (maybe events?) + property.SetValue(mediaFileManager.FileSystem.GetUrl(filepath), culture, segment); + } - #endregion - - - /// - /// Creates the full xml representation for the object and all of it's descendants - /// - /// to generate xml for - /// - /// Xml representation of the passed in - public static XElement ToDeepXml(this IContent content, IEntityXmlSerializer serializer) + // gets or creates a property for a content item. + private static IProperty GetProperty(IContentBase content, + IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, string propertyTypeAlias) + { + IProperty property = content.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); + if (property != null) { - return serializer.Serialize(content, false, true); + return property; } - /// - /// Creates the xml representation for the object - /// - /// to generate xml for - /// - /// Xml representation of the passed in - public static XElement ToXml(this IContent content, IEntityXmlSerializer serializer) + IContentTypeComposition contentType = contentTypeBaseServiceProvider.GetContentTypeOf(content); + IPropertyType propertyType = contentType?.CompositionPropertyTypes + .FirstOrDefault(x => x.Alias?.InvariantEquals(propertyTypeAlias) ?? false); + if (propertyType == null) { - return serializer.Serialize(content, false, false); + throw new Exception("No property type exists with alias " + propertyTypeAlias + "."); } + property = new Property(propertyType); + content.Properties.Add(property); + return property; + } - /// - /// Creates the xml representation for the object - /// - /// to generate xml for - /// - /// Xml representation of the passed in - public static XElement ToXml(this IMedia media, IEntityXmlSerializer serializer) + /// + /// Stores a file. + /// + /// A content item. + /// The property alias. + /// The name of the file. + /// A stream containing the file data. + /// The original file path, if any. + /// The path to the file, relative to the media filesystem. + /// + /// + /// Does NOT set the property value, so one should probably store the file and then do + /// something alike: property.Value = MediaHelper.FileSystem.GetUrl(filepath). + /// + /// + /// The original file path is used, in the old media file path scheme, to try and reuse + /// the "folder number" that was assigned to the previous file referenced by the property, + /// if any. + /// + /// + public static string StoreFile(this IContentBase content, MediaFileManager mediaFileManager, + IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, string propertyTypeAlias, string filename, + Stream filestream, string filepath) + { + IContentTypeComposition contentType = contentTypeBaseServiceProvider.GetContentTypeOf(content); + IPropertyType propertyType = contentType? + .CompositionPropertyTypes.FirstOrDefault(x => x.Alias?.InvariantEquals(propertyTypeAlias) ?? false); + if (propertyType == null) { - return serializer.Serialize(media); + throw new ArgumentException("Invalid property type alias " + propertyTypeAlias + "."); } - /// - /// Creates the xml representation for the object - /// - /// to generate xml for - /// - /// Xml representation of the passed in - public static XElement ToXml(this IMember member, IEntityXmlSerializer serializer) - { - return serializer.Serialize(member); - } + return mediaFileManager.StoreFile(content, propertyType, filename, filestream, filepath); } + + #endregion } diff --git a/src/Umbraco.Core/Extensions/ContentVariationExtensions.cs b/src/Umbraco.Core/Extensions/ContentVariationExtensions.cs index 4469683acb1b..1b4d897029bd 100644 --- a/src/Umbraco.Core/Extensions/ContentVariationExtensions.cs +++ b/src/Umbraco.Core/Extensions/ContentVariationExtensions.cs @@ -1,346 +1,383 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Provides extension methods for content variations. +/// +public static class ContentVariationExtensions { /// - /// Provides extension methods for content variations. + /// Determines whether the content type is invariant. + /// + /// The content type. + /// + /// A value indicating whether the content type is invariant. + /// + public static bool VariesByNothing(this ISimpleContentType contentType) => contentType.Variations.VariesByNothing(); + + /// + /// Determines whether the content type is invariant. + /// + /// The content type. + /// + /// A value indicating whether the content type is invariant. + /// + public static bool VariesByNothing(this IContentTypeBase contentType) => contentType.Variations.VariesByNothing(); + + /// + /// Determines whether the content type is invariant. + /// + /// The content type. + /// + /// A value indicating whether the content type is invariant. + /// + public static bool VariesByNothing(this IPublishedContentType contentType) => + contentType.Variations.VariesByNothing(); + + /// + /// Determines whether the property type is invariant. + /// + /// The property type. + /// + /// A value indicating whether the property type is invariant. + /// + public static bool VariesByNothing(this IPropertyType propertyType) => propertyType.Variations.VariesByNothing(); + + /// + /// Determines whether the property type is invariant. + /// + /// The property type. + /// + /// A value indicating whether the property type is invariant. + /// + public static bool VariesByNothing(this IPublishedPropertyType propertyType) => + propertyType.Variations.VariesByNothing(); + + /// + /// Determines whether a variation is invariant. + /// + /// The variation. + /// + /// A value indicating whether the variation is invariant. + /// + public static bool VariesByNothing(this ContentVariation variation) => variation == ContentVariation.Nothing; + + /// + /// Determines whether the content type varies by culture. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture. + /// + public static bool VariesByCulture(this ISimpleContentType contentType) => contentType.Variations.VariesByCulture(); + + /// + /// Determines whether the content type varies by culture. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture. + /// + public static bool VariesByCulture(this IContentTypeBase contentType) => contentType.Variations.VariesByCulture(); + + /// + /// Determines whether the content type varies by culture. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture. + /// + public static bool VariesByCulture(this IPublishedContentType contentType) => + contentType.Variations.VariesByCulture(); + + /// + /// Determines whether the property type varies by culture. + /// + /// The property type. + /// + /// A value indicating whether the property type varies by culture. + /// + public static bool VariesByCulture(this IPropertyType propertyType) => propertyType.Variations.VariesByCulture(); + + /// + /// Determines whether the property type varies by culture. + /// + /// The property type. + /// + /// A value indicating whether the property type varies by culture. + /// + public static bool VariesByCulture(this IPublishedPropertyType propertyType) => + propertyType.Variations.VariesByCulture(); + + /// + /// Determines whether a variation varies by culture. + /// + /// The variation. + /// + /// A value indicating whether the variation varies by culture. + /// + public static bool VariesByCulture(this ContentVariation variation) => (variation & ContentVariation.Culture) > 0; + + /// + /// Determines whether the content type varies by segment. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by segment. + /// + public static bool VariesBySegment(this ISimpleContentType contentType) => contentType.Variations.VariesBySegment(); + + /// + /// Determines whether the content type varies by segment. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by segment. + /// + public static bool VariesBySegment(this IContentTypeBase contentType) => contentType.Variations.VariesBySegment(); + + /// + /// Determines whether the content type varies by segment. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by segment. + /// + public static bool VariesBySegment(this IPublishedContentType contentType) => + contentType.Variations.VariesBySegment(); + + /// + /// Determines whether the property type varies by segment. + /// + /// The property type. + /// + /// A value indicating whether the property type varies by segment. + /// + public static bool VariesBySegment(this IPropertyType propertyType) => propertyType.Variations.VariesBySegment(); + + /// + /// Determines whether the property type varies by segment. + /// + /// The property type. + /// + /// A value indicating whether the property type varies by segment. + /// + public static bool VariesBySegment(this IPublishedPropertyType propertyType) => + propertyType.Variations.VariesBySegment(); + + /// + /// Determines whether a variation varies by segment. + /// + /// The variation. + /// + /// A value indicating whether the variation varies by segment. + /// + public static bool VariesBySegment(this ContentVariation variation) => (variation & ContentVariation.Segment) > 0; + + /// + /// Determines whether the content type varies by culture and segment. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this ISimpleContentType contentType) => + contentType.Variations.VariesByCultureAndSegment(); + + /// + /// Determines whether the content type varies by culture and segment. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this IContentTypeBase contentType) => + contentType.Variations.VariesByCultureAndSegment(); + + /// + /// Determines whether the content type varies by culture and segment. + /// + /// The content type. + /// + /// A value indicating whether the content type varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this IPublishedContentType contentType) => + contentType.Variations.VariesByCultureAndSegment(); + + /// + /// Determines whether the property type varies by culture and segment. + /// + /// The property type. + /// + /// A value indicating whether the property type varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this IPropertyType propertyType) => + propertyType.Variations.VariesByCultureAndSegment(); + + /// + /// Determines whether the property type varies by culture and segment. /// - public static class ContentVariationExtensions + /// The property type. + /// + /// A value indicating whether the property type varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this IPublishedPropertyType propertyType) => + propertyType.Variations.VariesByCultureAndSegment(); + + /// + /// Determines whether a variation varies by culture and segment. + /// + /// The variation. + /// + /// A value indicating whether the variation varies by culture and segment. + /// + public static bool VariesByCultureAndSegment(this ContentVariation variation) => + (variation & ContentVariation.CultureAndSegment) == ContentVariation.CultureAndSegment; + + /// + /// Sets or removes the content type variation depending on the specified value. + /// + /// The content type. + /// The variation to set or remove. + /// If set to true sets the variation; otherwise, removes the variation. + /// + /// This method does not support setting the variation to nothing. + /// + public static void SetVariesBy(this IContentTypeBase contentType, ContentVariation variation, bool value = true) => + contentType.Variations = contentType.Variations.SetFlag(variation, value); + + /// + /// Sets or removes the property type variation depending on the specified value. + /// + /// The property type. + /// The variation to set or remove. + /// If set to true sets the variation; otherwise, removes the variation. + /// + /// This method does not support setting the variation to nothing. + /// + public static void SetVariesBy(this IPropertyType propertyType, ContentVariation variation, bool value = true) => + propertyType.Variations = propertyType.Variations.SetFlag(variation, value); + + /// + /// Returns the variations with the variation set or removed depending on the specified value. + /// + /// The existing variations. + /// The variation to set or remove. + /// If set to true sets the variation; otherwise, removes the variation. + /// + /// The variations with the variation set or removed. + /// + /// + /// This method does not support setting the variation to nothing. + /// + public static ContentVariation SetFlag(this ContentVariation variations, ContentVariation variation, + bool value = true) => + value + ? variations | variation // Set flag using bitwise logical OR + : variations & + ~variation; // Remove flag using bitwise logical AND with bitwise complement (reversing the bit) + + /// + /// Validates that a combination of culture and segment is valid for the variation. + /// + /// The variation. + /// The culture. + /// The segment. + /// A value indicating whether to perform exact validation. + /// A value indicating whether to support wildcards. + /// + /// A value indicating whether to throw a when the + /// combination is invalid. + /// + /// + /// true if the combination is valid; otherwise false. + /// + /// + /// Occurs when the combination is invalid, and + /// is true. + /// + /// + /// + /// When validation is exact, the combination must match the variation exactly. For instance, if the variation is + /// Culture, then + /// a culture is required. When validation is not strict, the combination must be equivalent, or more restrictive: + /// if the variation is + /// Culture, an invariant combination is ok. + /// + /// + /// Basically, exact is for one content type, or one property type, and !exact is for "all property types" of one + /// content type. + /// + /// Both and can be "*" to indicate "all of them". + /// + public static bool ValidateVariation(this ContentVariation variation, string? culture, string? segment, bool exact, + bool wildcards, bool throwIfInvalid) { - /// - /// Determines whether the content type is invariant. - /// - /// The content type. - /// - /// A value indicating whether the content type is invariant. - /// - public static bool VariesByNothing(this ISimpleContentType contentType) => contentType.Variations.VariesByNothing(); - - /// - /// Determines whether the content type is invariant. - /// - /// The content type. - /// - /// A value indicating whether the content type is invariant. - /// - public static bool VariesByNothing(this IContentTypeBase contentType) => contentType.Variations.VariesByNothing(); - - /// - /// Determines whether the content type is invariant. - /// - /// The content type. - /// - /// A value indicating whether the content type is invariant. - /// - public static bool VariesByNothing(this IPublishedContentType contentType) => contentType.Variations.VariesByNothing(); - - /// - /// Determines whether the property type is invariant. - /// - /// The property type. - /// - /// A value indicating whether the property type is invariant. - /// - public static bool VariesByNothing(this IPropertyType propertyType) => propertyType.Variations.VariesByNothing(); - - /// - /// Determines whether the property type is invariant. - /// - /// The property type. - /// - /// A value indicating whether the property type is invariant. - /// - public static bool VariesByNothing(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByNothing(); - - /// - /// Determines whether a variation is invariant. - /// - /// The variation. - /// - /// A value indicating whether the variation is invariant. - /// - public static bool VariesByNothing(this ContentVariation variation) => variation == ContentVariation.Nothing; - - /// - /// Determines whether the content type varies by culture. - /// - /// The content type. - /// - /// A value indicating whether the content type varies by culture. - /// - public static bool VariesByCulture(this ISimpleContentType contentType) => contentType.Variations.VariesByCulture(); - - /// - /// Determines whether the content type varies by culture. - /// - /// The content type. - /// - /// A value indicating whether the content type varies by culture. - /// - public static bool VariesByCulture(this IContentTypeBase contentType) => contentType.Variations.VariesByCulture(); - - /// - /// Determines whether the content type varies by culture. - /// - /// The content type. - /// - /// A value indicating whether the content type varies by culture. - /// - public static bool VariesByCulture(this IPublishedContentType contentType) => contentType.Variations.VariesByCulture(); - - /// - /// Determines whether the property type varies by culture. - /// - /// The property type. - /// - /// A value indicating whether the property type varies by culture. - /// - public static bool VariesByCulture(this IPropertyType propertyType) => propertyType.Variations.VariesByCulture(); - - /// - /// Determines whether the property type varies by culture. - /// - /// The property type. - /// - /// A value indicating whether the property type varies by culture. - /// - public static bool VariesByCulture(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCulture(); - - /// - /// Determines whether a variation varies by culture. - /// - /// The variation. - /// - /// A value indicating whether the variation varies by culture. - /// - public static bool VariesByCulture(this ContentVariation variation) => (variation & ContentVariation.Culture) > 0; - - /// - /// Determines whether the content type varies by segment. - /// - /// The content type. - /// - /// A value indicating whether the content type varies by segment. - /// - public static bool VariesBySegment(this ISimpleContentType contentType) => contentType.Variations.VariesBySegment(); - - /// - /// Determines whether the content type varies by segment. - /// - /// The content type. - /// - /// A value indicating whether the content type varies by segment. - /// - public static bool VariesBySegment(this IContentTypeBase contentType) => contentType.Variations.VariesBySegment(); - - /// - /// Determines whether the content type varies by segment. - /// - /// The content type. - /// - /// A value indicating whether the content type varies by segment. - /// - public static bool VariesBySegment(this IPublishedContentType contentType) => contentType.Variations.VariesBySegment(); - - /// - /// Determines whether the property type varies by segment. - /// - /// The property type. - /// - /// A value indicating whether the property type varies by segment. - /// - public static bool VariesBySegment(this IPropertyType propertyType) => propertyType.Variations.VariesBySegment(); - - /// - /// Determines whether the property type varies by segment. - /// - /// The property type. - /// - /// A value indicating whether the property type varies by segment. - /// - public static bool VariesBySegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesBySegment(); - - /// - /// Determines whether a variation varies by segment. - /// - /// The variation. - /// - /// A value indicating whether the variation varies by segment. - /// - public static bool VariesBySegment(this ContentVariation variation) => (variation & ContentVariation.Segment) > 0; - - /// - /// Determines whether the content type varies by culture and segment. - /// - /// The content type. - /// - /// A value indicating whether the content type varies by culture and segment. - /// - public static bool VariesByCultureAndSegment(this ISimpleContentType contentType) => contentType.Variations.VariesByCultureAndSegment(); - - /// - /// Determines whether the content type varies by culture and segment. - /// - /// The content type. - /// - /// A value indicating whether the content type varies by culture and segment. - /// - public static bool VariesByCultureAndSegment(this IContentTypeBase contentType) => contentType.Variations.VariesByCultureAndSegment(); - - /// - /// Determines whether the content type varies by culture and segment. - /// - /// The content type. - /// - /// A value indicating whether the content type varies by culture and segment. - /// - public static bool VariesByCultureAndSegment(this IPublishedContentType contentType) => contentType.Variations.VariesByCultureAndSegment(); - - /// - /// Determines whether the property type varies by culture and segment. - /// - /// The property type. - /// - /// A value indicating whether the property type varies by culture and segment. - /// - public static bool VariesByCultureAndSegment(this IPropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment(); - - /// - /// Determines whether the property type varies by culture and segment. - /// - /// The property type. - /// - /// A value indicating whether the property type varies by culture and segment. - /// - public static bool VariesByCultureAndSegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment(); - - /// - /// Determines whether a variation varies by culture and segment. - /// - /// The variation. - /// - /// A value indicating whether the variation varies by culture and segment. - /// - public static bool VariesByCultureAndSegment(this ContentVariation variation) => (variation & ContentVariation.CultureAndSegment) == ContentVariation.CultureAndSegment; - - /// - /// Sets or removes the content type variation depending on the specified value. - /// - /// The content type. - /// The variation to set or remove. - /// If set to true sets the variation; otherwise, removes the variation. - /// - /// This method does not support setting the variation to nothing. - /// - public static void SetVariesBy(this IContentTypeBase contentType, ContentVariation variation, bool value = true) => contentType.Variations = contentType.Variations.SetFlag(variation, value); - - /// - /// Sets or removes the property type variation depending on the specified value. - /// - /// The property type. - /// The variation to set or remove. - /// If set to true sets the variation; otherwise, removes the variation. - /// - /// This method does not support setting the variation to nothing. - /// - public static void SetVariesBy(this IPropertyType propertyType, ContentVariation variation, bool value = true) => propertyType.Variations = propertyType.Variations.SetFlag(variation, value); - - /// - /// Returns the variations with the variation set or removed depending on the specified value. - /// - /// The existing variations. - /// The variation to set or remove. - /// If set to true sets the variation; otherwise, removes the variation. - /// - /// The variations with the variation set or removed. - /// - /// - /// This method does not support setting the variation to nothing. - /// - public static ContentVariation SetFlag(this ContentVariation variations, ContentVariation variation, bool value = true) - { - return value - ? variations | variation // Set flag using bitwise logical OR - : variations & ~variation; // Remove flag using bitwise logical AND with bitwise complement (reversing the bit) - } + culture = culture?.NullOrWhiteSpaceAsNull(); + segment = segment?.NullOrWhiteSpaceAsNull(); - /// - /// Validates that a combination of culture and segment is valid for the variation. - /// - /// The variation. - /// The culture. - /// The segment. - /// A value indicating whether to perform exact validation. - /// A value indicating whether to support wildcards. - /// A value indicating whether to throw a when the combination is invalid. - /// - /// true if the combination is valid; otherwise false. - /// - /// Occurs when the combination is invalid, and is true. - /// - /// When validation is exact, the combination must match the variation exactly. For instance, if the variation is Culture, then - /// a culture is required. When validation is not strict, the combination must be equivalent, or more restrictive: if the variation is - /// Culture, an invariant combination is ok. - /// Basically, exact is for one content type, or one property type, and !exact is for "all property types" of one content type. - /// Both and can be "*" to indicate "all of them". - /// - public static bool ValidateVariation(this ContentVariation variation, string? culture, string? segment, bool exact, bool wildcards, bool throwIfInvalid) + // if wildcards are disabled, do not allow "*" + if (!wildcards && (culture == "*" || segment == "*")) { - culture = culture?.NullOrWhiteSpaceAsNull(); - segment = segment?.NullOrWhiteSpaceAsNull(); - - // if wildcards are disabled, do not allow "*" - if (!wildcards && (culture == "*" || segment == "*")) + if (throwIfInvalid) { - if (throwIfInvalid) - throw new NotSupportedException($"Variation wildcards are not supported."); - return false; + throw new NotSupportedException("Variation wildcards are not supported."); } - if (variation.VariesByCulture()) + return false; + } + + if (variation.VariesByCulture()) + { + // varies by culture + // in exact mode, the culture cannot be null + if (exact && culture == null) { - // varies by culture - // in exact mode, the culture cannot be null - if (exact && culture == null) + if (throwIfInvalid) { - if (throwIfInvalid) - throw new NotSupportedException($"Culture may not be null because culture variation is enabled."); - - return false; + throw new NotSupportedException("Culture may not be null because culture variation is enabled."); } + + return false; } - else + } + else + { + // does not vary by culture + // the culture cannot have a value + // unless wildcards and it's "*" + if (culture != null && !(wildcards && culture == "*")) { - // does not vary by culture - // the culture cannot have a value - // unless wildcards and it's "*" - if (culture != null && !(wildcards && culture == "*")) + if (throwIfInvalid) { - if (throwIfInvalid) - throw new NotSupportedException($"Culture \"{culture}\" is invalid because culture variation is disabled."); - - return false; + throw new NotSupportedException( + $"Culture \"{culture}\" is invalid because culture variation is disabled."); } + + return false; } + } - // if it does not vary by segment - // the segment cannot have a value - // segment may always be null, even when the ContentVariation.Segment flag is set for this variation, - // therefore the exact parameter is not used in segment validation. - if (!variation.VariesBySegment() && segment != null && !(wildcards && segment == "*")) + // if it does not vary by segment + // the segment cannot have a value + // segment may always be null, even when the ContentVariation.Segment flag is set for this variation, + // therefore the exact parameter is not used in segment validation. + if (!variation.VariesBySegment() && segment != null && !(wildcards && segment == "*")) + { + if (throwIfInvalid) { - if (throwIfInvalid) - throw new NotSupportedException($"Segment \"{segment}\" is invalid because segment variation is disabled."); - - return false; + throw new NotSupportedException( + $"Segment \"{segment}\" is invalid because segment variation is disabled."); } - return true; + return false; } + + return true; } } diff --git a/src/Umbraco.Core/Extensions/CoreCacheHelperExtensions.cs b/src/Umbraco.Core/Extensions/CoreCacheHelperExtensions.cs index 8dfec45c7ecc..2bf5470c7f09 100644 --- a/src/Umbraco.Core/Extensions/CoreCacheHelperExtensions.cs +++ b/src/Umbraco.Core/Extensions/CoreCacheHelperExtensions.cs @@ -3,22 +3,19 @@ using Umbraco.Cms.Core.Cache; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Extension methods for the cache helper +/// +public static class CoreCacheHelperExtensions { + public const string PartialViewCacheKey = "Umbraco.Web.PartialViewCacheKey"; + /// - /// Extension methods for the cache helper + /// Clears the cache for partial views /// - public static class CoreCacheHelperExtensions - { - public const string PartialViewCacheKey = "Umbraco.Web.PartialViewCacheKey"; - - /// - /// Clears the cache for partial views - /// - /// - public static void ClearPartialViewCache(this AppCaches appCaches) - { - appCaches.RuntimeCache.ClearByKey(PartialViewCacheKey); - } - } + /// + public static void ClearPartialViewCache(this AppCaches appCaches) => + appCaches.RuntimeCache.ClearByKey(PartialViewCacheKey); } diff --git a/src/Umbraco.Core/Extensions/DataTableExtensions.cs b/src/Umbraco.Core/Extensions/DataTableExtensions.cs index 4594709407c5..6cf9e0892a49 100644 --- a/src/Umbraco.Core/Extensions/DataTableExtensions.cs +++ b/src/Umbraco.Core/Extensions/DataTableExtensions.cs @@ -1,114 +1,112 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using System.Data; -using System.Linq; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Static and extension methods for the DataTable object +/// +public static class DataTableExtensions { /// - /// Static and extension methods for the DataTable object + /// Creates a DataTable with the specified alias and columns and uses a callback to populate the headers. /// - public static class DataTableExtensions + /// + /// + /// + /// + /// + /// This has been migrated from the Node class and uses proper locking now. It is now used by the Node class and the + /// DynamicPublishedContent extensions for legacy reasons. + /// + public static DataTable GenerateDataTable( + string tableAlias, + Func>> getHeaders, + Func>, IEnumerable>>>> + rowData) { - /// - /// Creates a DataTable with the specified alias and columns and uses a callback to populate the headers. - /// - /// - /// - /// - /// - /// - /// This has been migrated from the Node class and uses proper locking now. It is now used by the Node class and the - /// DynamicPublishedContent extensions for legacy reasons. - /// - public static DataTable GenerateDataTable( - string tableAlias, - Func>> getHeaders, - Func>, IEnumerable>>>> rowData) - { - var dt = new DataTable(tableAlias); - - //get all row data - var tableData = rowData().ToArray(); + var dt = new DataTable(tableAlias); - //get all headers - var propertyHeaders = GetPropertyHeaders(tableAlias, getHeaders); - foreach(var h in propertyHeaders) - { - dt.Columns.Add(new DataColumn(h.Value)); - } + //get all row data + Tuple>, IEnumerable>>[] tableData = + rowData().ToArray(); - //add row data - foreach(var r in tableData) - { - dt.PopulateRow( - propertyHeaders, - r.Item1, - r.Item2); - } - - return dt; - } - - /// - /// Helper method to return this ugly object - /// - /// - /// - /// This is for legacy code, I didn't want to go creating custom classes for these - /// - public static List>, IEnumerable>>> CreateTableData() + //get all headers + IDictionary propertyHeaders = GetPropertyHeaders(tableAlias, getHeaders); + foreach (KeyValuePair h in propertyHeaders) { - return new List>, IEnumerable>>>(); + dt.Columns.Add(new DataColumn(h.Value)); } - /// - /// Helper method to deal with these ugly objects - /// - /// - /// - /// - /// - /// This is for legacy code, I didn't want to go creating custom classes for these - /// - public static void AddRowData( - List>, IEnumerable>>> rowData, - IEnumerable> standardVals, - IEnumerable> userVals) + //add row data + foreach (Tuple>, IEnumerable>> r in + tableData) { - rowData.Add(new System.Tuple>, IEnumerable>>( - standardVals, - userVals - )); + dt.PopulateRow( + propertyHeaders, + r.Item1, + r.Item2); } - private static IDictionary GetPropertyHeaders(string alias, Func>> getHeaders) + return dt; + } + + /// + /// Helper method to return this ugly object + /// + /// + /// + /// This is for legacy code, I didn't want to go creating custom classes for these + /// + public static List>, IEnumerable>>> + CreateTableData() => + new List>, IEnumerable>>>(); + + /// + /// Helper method to deal with these ugly objects + /// + /// + /// + /// + /// + /// This is for legacy code, I didn't want to go creating custom classes for these + /// + public static void AddRowData( + List>, IEnumerable>>> rowData, + IEnumerable> standardVals, + IEnumerable> userVals) => + rowData.Add(new Tuple>, IEnumerable>>( + standardVals, + userVals + )); + + private static IDictionary GetPropertyHeaders(string alias, + Func>> getHeaders) + { + IEnumerable> headers = getHeaders(alias); + var def = headers.ToDictionary(pt => pt.Key, pt => pt.Value); + return def; + } + + private static void PopulateRow( + this DataTable dt, + IDictionary aliasesToNames, + IEnumerable> standardVals, + IEnumerable> userPropertyVals) + { + DataRow dr = dt.NewRow(); + foreach (KeyValuePair r in standardVals) { - var headers = getHeaders(alias); - var def = headers.ToDictionary(pt => pt.Key, pt => pt.Value); - return def; + dr[r.Key] = r.Value; } - private static void PopulateRow( - this DataTable dt, - IDictionary aliasesToNames, - IEnumerable> standardVals, - IEnumerable> userPropertyVals) + foreach (KeyValuePair p in userPropertyVals.Where(p => p.Value != null)) { - var dr = dt.NewRow(); - foreach (var r in standardVals) - { - dr[r.Key] = r.Value; - } - foreach (var p in userPropertyVals.Where(p => p.Value != null)) - { - dr[aliasesToNames[p.Key]] = p.Value; - } - dt.Rows.Add(dr); + dr[aliasesToNames[p.Key]] = p.Value; } + dt.Rows.Add(dr); } } diff --git a/src/Umbraco.Core/Extensions/DateTimeExtensions.cs b/src/Umbraco.Core/Extensions/DateTimeExtensions.cs index e500cf86b039..864e74539e67 100644 --- a/src/Umbraco.Core/Extensions/DateTimeExtensions.cs +++ b/src/Umbraco.Core/Extensions/DateTimeExtensions.cs @@ -1,46 +1,57 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using System.Globalization; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class DateTimeExtensions { - public static class DateTimeExtensions + public enum DateTruncate + { + Year, + Month, + Day, + Hour, + Minute, + Second + } + + /// + /// Returns the DateTime as an ISO formatted string that is globally expectable + /// + /// + /// + public static string ToIsoString(this DateTime dt) => + dt.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture); + + public static DateTime TruncateTo(this DateTime dt, DateTruncate truncateTo) { - /// - /// Returns the DateTime as an ISO formatted string that is globally expectable - /// - /// - /// - public static string ToIsoString(this DateTime dt) + if (truncateTo == DateTruncate.Year) + { + return new DateTime(dt.Year, 1, 1); + } + + if (truncateTo == DateTruncate.Month) + { + return new DateTime(dt.Year, dt.Month, 1); + } + + if (truncateTo == DateTruncate.Day) { - return dt.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture); + return new DateTime(dt.Year, dt.Month, dt.Day); } - public static DateTime TruncateTo(this DateTime dt, DateTruncate truncateTo) + if (truncateTo == DateTruncate.Hour) { - if (truncateTo == DateTruncate.Year) - return new DateTime(dt.Year, 1, 1); - if (truncateTo == DateTruncate.Month) - return new DateTime(dt.Year, dt.Month, 1); - if (truncateTo == DateTruncate.Day) - return new DateTime(dt.Year, dt.Month, dt.Day); - if (truncateTo == DateTruncate.Hour) - return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, 0, 0); - if (truncateTo == DateTruncate.Minute) - return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0); - return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second); + return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, 0, 0); } - public enum DateTruncate + if (truncateTo == DateTruncate.Minute) { - Year, - Month, - Day, - Hour, - Minute, - Second + return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0); } + + return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second); } } diff --git a/src/Umbraco.Core/Extensions/DecimalExtensions.cs b/src/Umbraco.Core/Extensions/DecimalExtensions.cs index fa6280584168..9f0bc95de3bf 100644 --- a/src/Umbraco.Core/Extensions/DecimalExtensions.cs +++ b/src/Umbraco.Core/Extensions/DecimalExtensions.cs @@ -1,26 +1,25 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Provides extension methods for System.Decimal. +/// +/// +/// See System.Decimal on MSDN and also +/// http://stackoverflow.com/questions/4298719/parse-decimal-and-filter-extra-0-on-the-right/4298787#4298787. +/// +public static class DecimalExtensions { /// - /// Provides extension methods for System.Decimal. + /// Gets the normalized value. /// - /// See System.Decimal on MSDN and also - /// http://stackoverflow.com/questions/4298719/parse-decimal-and-filter-extra-0-on-the-right/4298787#4298787. + /// The value to normalize. + /// The normalized value. + /// + /// Normalizing changes the scaling factor and removes trailing zeros, + /// so 1.2500m comes out as 1.25m. /// - public static class DecimalExtensions - { - /// - /// Gets the normalized value. - /// - /// The value to normalize. - /// The normalized value. - /// Normalizing changes the scaling factor and removes trailing zeros, - /// so 1.2500m comes out as 1.25m. - public static decimal Normalize(this decimal value) - { - return value / 1.000000000000000000000000000000000m; - } - } + public static decimal Normalize(this decimal value) => value / 1.000000000000000000000000000000000m; } diff --git a/src/Umbraco.Core/Extensions/DelegateExtensions.cs b/src/Umbraco.Core/Extensions/DelegateExtensions.cs index 4cbcdd5d6a94..e623d495b60d 100644 --- a/src/Umbraco.Core/Extensions/DelegateExtensions.cs +++ b/src/Umbraco.Core/Extensions/DelegateExtensions.cs @@ -1,48 +1,51 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using System.Diagnostics; -using System.Threading; using Umbraco.Cms.Core; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class DelegateExtensions { - public static class DelegateExtensions + public static Attempt RetryUntilSuccessOrTimeout(this Func> task, TimeSpan timeout, + TimeSpan pause) { - public static Attempt RetryUntilSuccessOrTimeout(this Func> task, TimeSpan timeout, TimeSpan pause) + if (pause.TotalMilliseconds < 0) { - if (pause.TotalMilliseconds < 0) - { - throw new ArgumentException("pause must be >= 0 milliseconds"); - } - var stopwatch = Stopwatch.StartNew(); - do - { - var result = task(); - if (result.Success) { return result; } - Thread.Sleep((int)pause.TotalMilliseconds); - } - while (stopwatch.Elapsed < timeout); - return Attempt.Fail(); + throw new ArgumentException("pause must be >= 0 milliseconds"); } - public static Attempt RetryUntilSuccessOrMaxAttempts(this Func> task, int totalAttempts, TimeSpan pause) + var stopwatch = Stopwatch.StartNew(); + do { - if (pause.TotalMilliseconds < 0) - { - throw new ArgumentException("pause must be >= 0 milliseconds"); - } - int attempts = 0; - do - { - attempts++; - var result = task(attempts); - if (result.Success) { return result; } - Thread.Sleep((int)pause.TotalMilliseconds); - } - while (attempts < totalAttempts); - return Attempt.Fail(); + Attempt result = task(); + if (result.Success) { return result; } + + Thread.Sleep((int)pause.TotalMilliseconds); + } while (stopwatch.Elapsed < timeout); + + return Attempt.Fail(); + } + + public static Attempt RetryUntilSuccessOrMaxAttempts(this Func> task, int totalAttempts, + TimeSpan pause) + { + if (pause.TotalMilliseconds < 0) + { + throw new ArgumentException("pause must be >= 0 milliseconds"); } + + var attempts = 0; + do + { + attempts++; + Attempt result = task(attempts); + if (result.Success) { return result; } + + Thread.Sleep((int)pause.TotalMilliseconds); + } while (attempts < totalAttempts); + + return Attempt.Fail(); } } diff --git a/src/Umbraco.Core/Extensions/DictionaryExtensions.cs b/src/Umbraco.Core/Extensions/DictionaryExtensions.cs index 906f12282e8e..dc6f7ac7db09 100644 --- a/src/Umbraco.Core/Extensions/DictionaryExtensions.cs +++ b/src/Umbraco.Core/Extensions/DictionaryExtensions.cs @@ -1,306 +1,324 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using System.Collections; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Collections.Specialized; -using System.Linq; using System.Net; using System.Text; -using System.Threading.Tasks; using Umbraco.Cms.Core; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Extension methods for Dictionary & ConcurrentDictionary +/// +public static class DictionaryExtensions { - /// - /// Extension methods for Dictionary & ConcurrentDictionary - /// - public static class DictionaryExtensions + /// + /// Method to Get a value by the key. If the key doesn't exist it will create a new TVal object for the key and return + /// it. + /// + /// + /// + /// + /// + /// + public static TVal GetOrCreate(this IDictionary dict, TKey key) + where TVal : class, new() { - - /// - /// Method to Get a value by the key. If the key doesn't exist it will create a new TVal object for the key and return it. - /// - /// - /// - /// - /// - /// - public static TVal GetOrCreate(this IDictionary dict, TKey key) - where TVal : class, new() + if (dict.ContainsKey(key) == false) { - if (dict.ContainsKey(key) == false) - { - dict.Add(key, new TVal()); - } - return dict[key]; + dict.Add(key, new TVal()); } - /// - /// Updates an item with the specified key with the specified value - /// - /// - /// - /// - /// - /// - /// - /// - /// Taken from: http://stackoverflow.com/questions/12240219/is-there-a-way-to-use-concurrentdictionary-tryupdate-with-a-lambda-expression - /// - /// If there is an item in the dictionary with the key, it will keep trying to update it until it can - /// - public static bool TryUpdate(this ConcurrentDictionary dict, TKey key, Func updateFactory) - where TKey : notnull + return dict[key]; + } + + /// + /// Updates an item with the specified key with the specified value + /// + /// + /// + /// + /// + /// + /// + /// + /// Taken from: + /// http://stackoverflow.com/questions/12240219/is-there-a-way-to-use-concurrentdictionary-tryupdate-with-a-lambda-expression + /// If there is an item in the dictionary with the key, it will keep trying to update it until it can + /// + public static bool TryUpdate(this ConcurrentDictionary dict, TKey key, + Func updateFactory) + where TKey : notnull + { + TValue? curValue; + while (dict.TryGetValue(key, out curValue)) { - TValue? curValue; - while (dict.TryGetValue(key, out curValue)) + if (dict.TryUpdate(key, updateFactory(curValue), curValue)) { - if (dict.TryUpdate(key, updateFactory(curValue), curValue)) - return true; - //if we're looping either the key was removed by another thread, or another thread - //changed the value, so we start again. + return true; } - return false; + //if we're looping either the key was removed by another thread, or another thread + //changed the value, so we start again. } - /// - /// Updates an item with the specified key with the specified value - /// - /// - /// - /// - /// - /// - /// - /// - /// Taken from: http://stackoverflow.com/questions/12240219/is-there-a-way-to-use-concurrentdictionary-tryupdate-with-a-lambda-expression - /// - /// WARNING: If the value changes after we've retrieved it, then the item will not be updated - /// - public static bool TryUpdateOptimisitic(this ConcurrentDictionary dict, TKey key, Func updateFactory) - where TKey : notnull + return false; + } + + /// + /// Updates an item with the specified key with the specified value + /// + /// + /// + /// + /// + /// + /// + /// + /// Taken from: + /// http://stackoverflow.com/questions/12240219/is-there-a-way-to-use-concurrentdictionary-tryupdate-with-a-lambda-expression + /// WARNING: If the value changes after we've retrieved it, then the item will not be updated + /// + public static bool TryUpdateOptimisitic(this ConcurrentDictionary dict, TKey key, + Func updateFactory) + where TKey : notnull + { + TValue? curValue; + if (!dict.TryGetValue(key, out curValue)) { - TValue? curValue; - if (!dict.TryGetValue(key, out curValue)) - return false; - dict.TryUpdate(key, updateFactory(curValue), curValue); - return true;//note we return true whether we succeed or not, see explanation below. + return false; } - /// - /// Converts a dictionary to another type by only using direct casting - /// - /// - /// - /// - /// - public static IDictionary ConvertTo(this IDictionary d) - where TKeyOut : notnull + dict.TryUpdate(key, updateFactory(curValue), curValue); + return true; //note we return true whether we succeed or not, see explanation below. + } + + /// + /// Converts a dictionary to another type by only using direct casting + /// + /// + /// + /// + /// + public static IDictionary ConvertTo(this IDictionary d) + where TKeyOut : notnull + { + var result = new Dictionary(); + foreach (DictionaryEntry v in d) { - var result = new Dictionary(); - foreach (DictionaryEntry v in d) - { - result.Add((TKeyOut)v.Key, (TValOut)v.Value!); - } - return result; + result.Add((TKeyOut)v.Key, (TValOut)v.Value!); } - /// - /// Converts a dictionary to another type using the specified converters - /// - /// - /// - /// - /// - /// - /// - public static IDictionary ConvertTo(this IDictionary d, Func keyConverter, Func valConverter) - where TKeyOut : notnull + return result; + } + + /// + /// Converts a dictionary to another type using the specified converters + /// + /// + /// + /// + /// + /// + /// + public static IDictionary ConvertTo(this IDictionary d, + Func keyConverter, Func valConverter) + where TKeyOut : notnull + { + var result = new Dictionary(); + foreach (DictionaryEntry v in d) { - var result = new Dictionary(); - foreach (DictionaryEntry v in d) - { - result.Add(keyConverter(v.Key), valConverter(v.Value!)); - } - return result; + result.Add(keyConverter(v.Key), valConverter(v.Value!)); } - /// - /// Converts a dictionary to a NameValueCollection - /// - /// - /// - public static NameValueCollection ToNameValueCollection(this IDictionary d) + return result; + } + + /// + /// Converts a dictionary to a NameValueCollection + /// + /// + /// + public static NameValueCollection ToNameValueCollection(this IDictionary d) + { + var n = new NameValueCollection(); + foreach (KeyValuePair i in d) { - var n = new NameValueCollection(); - foreach (var i in d) - { - n.Add(i.Key, i.Value); - } - return n; + n.Add(i.Key, i.Value); } + return n; + } + - /// - /// Merges all key/values from the sources dictionaries into the destination dictionary - /// - /// - /// - /// - /// The source dictionary to merge other dictionaries into - /// - /// By default all values will be retained in the destination if the same keys exist in the sources but - /// this can changed if overwrite = true, then any key/value found in any of the sources will overwritten in the destination. Note that - /// it will just use the last found key/value if this is true. - /// - /// The other dictionaries to merge values from - public static void MergeLeft(this T destination, IEnumerable> sources, bool overwrite = false) - where T : IDictionary + /// + /// Merges all key/values from the sources dictionaries into the destination dictionary + /// + /// + /// + /// + /// The source dictionary to merge other dictionaries into + /// + /// By default all values will be retained in the destination if the same keys exist in the sources but + /// this can changed if overwrite = true, then any key/value found in any of the sources will overwritten in the + /// destination. Note that + /// it will just use the last found key/value if this is true. + /// + /// The other dictionaries to merge values from + public static void MergeLeft(this T destination, IEnumerable> sources, + bool overwrite = false) + where T : IDictionary + { + foreach (KeyValuePair p in sources.SelectMany(src => src) + .Where(p => overwrite || destination.ContainsKey(p.Key) == false)) { - foreach (var p in sources.SelectMany(src => src).Where(p => overwrite || destination.ContainsKey(p.Key) == false)) - { - destination[p.Key] = p.Value; - } + destination[p.Key] = p.Value; } + } + + /// + /// Merges all key/values from the sources dictionaries into the destination dictionary + /// + /// + /// + /// + /// The source dictionary to merge other dictionaries into + /// + /// By default all values will be retained in the destination if the same keys exist in the sources but + /// this can changed if overwrite = true, then any key/value found in any of the sources will overwritten in the + /// destination. Note that + /// it will just use the last found key/value if this is true. + /// + /// The other dictionary to merge values from + public static void MergeLeft(this T destination, IDictionary source, bool overwrite = false) + where T : IDictionary => + destination.MergeLeft(new[] {source}, overwrite); - /// - /// Merges all key/values from the sources dictionaries into the destination dictionary - /// - /// - /// - /// - /// The source dictionary to merge other dictionaries into - /// - /// By default all values will be retained in the destination if the same keys exist in the sources but - /// this can changed if overwrite = true, then any key/value found in any of the sources will overwritten in the destination. Note that - /// it will just use the last found key/value if this is true. - /// - /// The other dictionary to merge values from - public static void MergeLeft(this T destination, IDictionary source, bool overwrite = false) - where T : IDictionary + /// + /// Returns the value of the key value based on the key, if the key is not found, a null value is returned + /// + /// The type of the key. + /// The type of the val. + /// The d. + /// The key. + /// The default value. + /// + public static TVal? GetValue(this IDictionary d, TKey key, TVal? defaultValue = default) + { + if (d.ContainsKey(key)) { - destination.MergeLeft(new[] {source}, overwrite); + return d[key]; } - /// - /// Returns the value of the key value based on the key, if the key is not found, a null value is returned - /// - /// The type of the key. - /// The type of the val. - /// The d. - /// The key. - /// The default value. - /// - public static TVal? GetValue(this IDictionary d, TKey key, TVal? defaultValue = default(TVal)) + return defaultValue; + } + + /// + /// Returns the value of the key value based on the key as it's string value, if the key is not found, then an empty + /// string is returned + /// + /// + /// + /// + public static string? GetValueAsString(this IDictionary d, TKey key) + => d.ContainsKey(key) ? d[key]!.ToString() : string.Empty; + + /// + /// Returns the value of the key value based on the key as it's string value, if the key is not found or is an empty + /// string, then the provided default value is returned + /// + /// + /// + /// + /// + public static string? GetValueAsString(this IDictionary d, TKey key, string defaultValue) + { + if (d.ContainsKey(key)) { - if (d.ContainsKey(key)) + var value = d[key]!.ToString(); + if (value != string.Empty) { - return d[key]; + return value; } - return defaultValue; } - /// - /// Returns the value of the key value based on the key as it's string value, if the key is not found, then an empty string is returned - /// - /// - /// - /// - public static string? GetValueAsString(this IDictionary d, TKey key) - => d.ContainsKey(key) ? d[key]!.ToString() : string.Empty; - - /// - /// Returns the value of the key value based on the key as it's string value, if the key is not found or is an empty string, then the provided default value is returned - /// - /// - /// - /// - /// - public static string? GetValueAsString(this IDictionary d, TKey key, string defaultValue) - { - if (d.ContainsKey(key)) - { - var value = d[key]!.ToString(); - if (value != string.Empty) - return value; - } + return defaultValue; + } - return defaultValue; - } + /// contains key ignore case. + /// The dictionary. + /// The key. + /// Value Type + /// The contains key ignore case. + public static bool ContainsKeyIgnoreCase(this IDictionary dictionary, string key) => + dictionary.Keys.InvariantContains(key); - /// contains key ignore case. - /// The dictionary. - /// The key. - /// Value Type - /// The contains key ignore case. - public static bool ContainsKeyIgnoreCase(this IDictionary dictionary, string key) + /// + /// Converts a dictionary object to a query string representation such as: + /// firstname=shannon&lastname=deminick + /// + /// + /// + public static string ToQueryString(this IDictionary d) + { + if (!d.Any()) { - return dictionary.Keys.InvariantContains(key); + return ""; } - /// - /// Converts a dictionary object to a query string representation such as: - /// firstname=shannon&lastname=deminick - /// - /// - /// - public static string ToQueryString(this IDictionary d) + var builder = new StringBuilder(); + foreach (KeyValuePair i in d) { - if (!d.Any()) return ""; - - var builder = new StringBuilder(); - foreach (var i in d) - { - builder.Append(String.Format("{0}={1}&", WebUtility.UrlEncode(i.Key), i.Value == null ? string.Empty : WebUtility.UrlEncode(i.Value.ToString()))); - } - return builder.ToString().TrimEnd(Constants.CharArrays.Ampersand); + builder.Append(string.Format("{0}={1}&", WebUtility.UrlEncode(i.Key), + i.Value == null ? string.Empty : WebUtility.UrlEncode(i.Value.ToString()))); } - /// The get entry ignore case. - /// The dictionary. - /// The key. - /// The type - /// The entry - public static TValue? GetValueIgnoreCase(this IDictionary dictionary, string key) - => dictionary!.GetValueIgnoreCase(key, default(TValue)); - - /// The get entry ignore case. - /// The dictionary. - /// The key. - /// The default value. - /// The type - /// The entry - public static TValue GetValueIgnoreCase(this IDictionary dictionary, string? key, TValue - defaultValue) - { - key = dictionary.Keys.FirstOrDefault(i => i.InvariantEquals(key)); + return builder.ToString().TrimEnd(Constants.CharArrays.Ampersand); + } - return key.IsNullOrWhiteSpace() == false - ? dictionary[key!] - : defaultValue; - } + /// The get entry ignore case. + /// The dictionary. + /// The key. + /// The type + /// The entry + public static TValue? GetValueIgnoreCase(this IDictionary dictionary, string key) + => dictionary!.GetValueIgnoreCase(key, default); - public static async Task> ToDictionaryAsync( - this IEnumerable enumerable, - Func syncKeySelector, - Func> asyncValueSelector) - where TKey : notnull - { - Dictionary dictionary = new Dictionary(); + /// The get entry ignore case. + /// The dictionary. + /// The key. + /// The default value. + /// The type + /// The entry + public static TValue GetValueIgnoreCase(this IDictionary dictionary, string? key, TValue + defaultValue) + { + key = dictionary.Keys.FirstOrDefault(i => i.InvariantEquals(key)); - foreach (var item in enumerable) - { - var key = syncKeySelector(item); + return key.IsNullOrWhiteSpace() == false + ? dictionary[key!] + : defaultValue; + } + + public static async Task> ToDictionaryAsync( + this IEnumerable enumerable, + Func syncKeySelector, + Func> asyncValueSelector) + where TKey : notnull + { + var dictionary = new Dictionary(); - var value = await asyncValueSelector(item); + foreach (TInput item in enumerable) + { + TKey key = syncKeySelector(item); - dictionary.Add(key,value); - } + TValue value = await asyncValueSelector(item); - return dictionary; + dictionary.Add(key, value); } + + return dictionary; } } diff --git a/src/Umbraco.Core/Extensions/EnumExtensions.cs b/src/Umbraco.Core/Extensions/EnumExtensions.cs index e13467ef3251..5b762bd2e802 100644 --- a/src/Umbraco.Core/Extensions/EnumExtensions.cs +++ b/src/Umbraco.Core/Extensions/EnumExtensions.cs @@ -1,47 +1,42 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; +namespace Umbraco.Extensions; -namespace Umbraco.Extensions +/// +/// Provides extension methods to . +/// +public static class EnumExtensions { /// - /// Provides extension methods to . + /// Determines whether all the flags/bits are set within the enum value. /// - public static class EnumExtensions - { - /// - /// Determines whether all the flags/bits are set within the enum value. - /// - /// The enum type. - /// The enum value. - /// The flags. - /// - /// true if all the flags/bits are set within the enum value; otherwise, false. - /// - [Obsolete("Use Enum.HasFlag() or bitwise operations (if performance is important) instead.")] - public static bool HasFlagAll(this T value, T flags) - where T : Enum - { - return value.HasFlag(flags); - } + /// The enum type. + /// The enum value. + /// The flags. + /// + /// true if all the flags/bits are set within the enum value; otherwise, false. + /// + [Obsolete("Use Enum.HasFlag() or bitwise operations (if performance is important) instead.")] + public static bool HasFlagAll(this T value, T flags) + where T : Enum => + value.HasFlag(flags); - /// - /// Determines whether any of the flags/bits are set within the enum value. - /// - /// The enum type. - /// The value. - /// The flags. - /// - /// true if any of the flags/bits are set within the enum value; otherwise, false. - /// - public static bool HasFlagAny(this T value, T flags) - where T : Enum - { - var v = Convert.ToUInt64(value); - var f = Convert.ToUInt64(flags); + /// + /// Determines whether any of the flags/bits are set within the enum value. + /// + /// The enum type. + /// The value. + /// The flags. + /// + /// true if any of the flags/bits are set within the enum value; otherwise, false. + /// + public static bool HasFlagAny(this T value, T flags) + where T : Enum + { + var v = Convert.ToUInt64(value); + var f = Convert.ToUInt64(flags); - return (v & f) > 0; - } + return (v & f) > 0; } } diff --git a/src/Umbraco.Core/Extensions/EnumerableExtensions.cs b/src/Umbraco.Core/Extensions/EnumerableExtensions.cs index 28f4844f00a0..8d8944c44ef2 100644 --- a/src/Umbraco.Core/Extensions/EnumerableExtensions.cs +++ b/src/Umbraco.Core/Extensions/EnumerableExtensions.cs @@ -1,351 +1,391 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Extensions for enumerable sources +/// +public static class EnumerableExtensions { - /// - /// Extensions for enumerable sources - /// - public static class EnumerableExtensions - { - public static bool IsCollectionEmpty(this IReadOnlyCollection? list) => list == null || list.Count == 0; + public static bool IsCollectionEmpty(this IReadOnlyCollection? list) => list == null || list.Count == 0; - internal static bool HasDuplicates(this IEnumerable items, bool includeNull) + internal static bool HasDuplicates(this IEnumerable items, bool includeNull) + { + var hs = new HashSet(); + foreach (T item in items) { - var hs = new HashSet(); - foreach (var item in items) + if ((item != null || includeNull) && !hs.Add(item)) { - if ((item != null || includeNull) && !hs.Add(item)) - return true; + return true; } - return false; } + return false; + } + - /// - /// Wraps this object instance into an IEnumerable{T} consisting of a single item. - /// - /// Type of the object. - /// The instance that will be wrapped. - /// An IEnumerable{T} consisting of a single item. - public static IEnumerable Yield(this T item) + /// + /// Wraps this object instance into an IEnumerable{T} consisting of a single item. + /// + /// Type of the object. + /// The instance that will be wrapped. + /// An IEnumerable{T} consisting of a single item. + public static IEnumerable Yield(this T item) + { + // see EnumeratorBenchmarks - this is faster, and allocates less, than returning an array + yield return item; + } + + public static IEnumerable> InGroupsOf(this IEnumerable? source, int groupSize) + { + if (source == null) { - // see EnumeratorBenchmarks - this is faster, and allocates less, than returning an array - yield return item; + throw new ArgumentNullException("source"); } - public static IEnumerable> InGroupsOf(this IEnumerable? source, int groupSize) + if (groupSize <= 0) { - if (source == null) - throw new ArgumentNullException("source"); - if (groupSize <= 0) - throw new ArgumentException("Must be greater than zero.", "groupSize"); + throw new ArgumentException("Must be greater than zero.", "groupSize"); + } - // following code derived from MoreLinq and does not allocate bazillions of tuples + // following code derived from MoreLinq and does not allocate bazillions of tuples - T[]? temp = null; - var count = 0; + T[]? temp = null; + var count = 0; - foreach (var item in source) + foreach (T item in source) + { + if (temp == null) { - if (temp == null) temp = new T[groupSize]; - temp[count++] = item; - if (count != groupSize) continue; - yield return temp/*.Select(x => x)*/; - temp = null; - count = 0; + temp = new T[groupSize]; } - if (temp != null && count > 0) - yield return temp.Take(count); + temp[count++] = item; + if (count != groupSize) + { + continue; + } + + yield return temp /*.Select(x => x)*/; + temp = null; + count = 0; } - public static IEnumerable SelectByGroups(this IEnumerable source, Func, IEnumerable> selector, int groupSize) + if (temp != null && count > 0) { - // don't want to use a SelectMany(x => x) here - isn't this better? - // ReSharper disable once LoopCanBeConvertedToQuery - foreach (var resultGroup in source.InGroupsOf(groupSize).Select(selector)) - foreach (var result in resultGroup) - yield return result; + yield return temp.Take(count); } + } - /// - /// Returns a sequence of length whose elements are the result of invoking . - /// - /// - /// The factory. - /// The count. - /// - public static IEnumerable Range(Func factory, int count) + public static IEnumerable SelectByGroups(this IEnumerable source, + Func, IEnumerable> selector, int groupSize) + { + // don't want to use a SelectMany(x => x) here - isn't this better? + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (IEnumerable resultGroup in source.InGroupsOf(groupSize).Select(selector)) + foreach (TResult result in resultGroup) { - for (int i = 1; i <= count; i++) - { - yield return factory.Invoke(i - 1); - } + yield return result; + } + } + + /// + /// Returns a sequence of length whose elements are the result of invoking + /// . + /// + /// + /// The factory. + /// The count. + /// + public static IEnumerable Range(Func factory, int count) + { + for (var i = 1; i <= count; i++) + { + yield return factory.Invoke(i - 1); } + } - /// The if not null. - /// The items. - /// The action. - /// The type - public static void IfNotNull(this IEnumerable items, Action action) where TItem : class + /// The if not null. + /// The items. + /// The action. + /// The type + public static void IfNotNull(this IEnumerable items, Action action) where TItem : class + { + if (items != null) { - if (items != null) + foreach (TItem item in items) { - foreach (TItem item in items) - { - item.IfNotNull(action); - } + item.IfNotNull(action); } } + } - /// - /// Returns true if all items in the other collection exist in this collection - /// - /// - /// - /// - /// - public static bool ContainsAll(this IEnumerable source, IEnumerable other) + /// + /// Returns true if all items in the other collection exist in this collection + /// + /// + /// + /// + /// + public static bool ContainsAll(this IEnumerable source, IEnumerable other) + { + if (source == null) { - if (source == null) throw new ArgumentNullException("source"); - if (other == null) throw new ArgumentNullException("other"); - - return other.Except(source).Any() == false; + throw new ArgumentNullException("source"); } - /// - /// Returns true if the source contains any of the items in the other list - /// - /// - /// - /// - /// - public static bool ContainsAny(this IEnumerable source, IEnumerable other) + if (other == null) { - return other.Any(source.Contains); + throw new ArgumentNullException("other"); } - /// - /// Removes all matching items from an . - /// - /// - /// The list. - /// The predicate. - /// - public static void RemoveAll(this IList list, Func predicate) + return other.Except(source).Any() == false; + } + + /// + /// Returns true if the source contains any of the items in the other list + /// + /// + /// + /// + /// + public static bool ContainsAny(this IEnumerable source, IEnumerable other) => + other.Any(source.Contains); + + /// + /// Removes all matching items from an . + /// + /// + /// The list. + /// The predicate. + /// + public static void RemoveAll(this IList list, Func predicate) + { + for (var i = 0; i < list.Count; i++) { - for (var i = 0; i < list.Count; i++) + if (predicate(list[i])) { - if (predicate(list[i])) - { - list.RemoveAt(i--); - } + list.RemoveAt(i--); } } + } - /// - /// Removes all matching items from an . - /// - /// - /// The list. - /// The predicate. - /// - public static void RemoveAll(this ICollection list, Func predicate) + /// + /// Removes all matching items from an . + /// + /// + /// The list. + /// The predicate. + /// + public static void RemoveAll(this ICollection list, Func predicate) + { + T[] matches = list.Where(predicate).ToArray(); + foreach (T match in matches) { - var matches = list.Where(predicate).ToArray(); - foreach (var match in matches) - { - list.Remove(match); - } + list.Remove(match); } + } - public static IEnumerable SelectRecursive( - this IEnumerable source, - Func> recursiveSelector, int maxRecusionDepth = 100) - { - var stack = new Stack>(); - stack.Push(source.GetEnumerator()); + public static IEnumerable SelectRecursive( + this IEnumerable source, + Func> recursiveSelector, int maxRecusionDepth = 100) + { + var stack = new Stack>(); + stack.Push(source.GetEnumerator()); - try + try + { + while (stack.Count > 0) { - while (stack.Count > 0) + if (stack.Count > maxRecusionDepth) { - if (stack.Count > maxRecusionDepth) - throw new InvalidOperationException("Maximum recursion depth reached of " + maxRecusionDepth); + throw new InvalidOperationException("Maximum recursion depth reached of " + maxRecusionDepth); + } - if (stack.Peek().MoveNext()) - { - var current = stack.Peek().Current; + if (stack.Peek().MoveNext()) + { + TSource current = stack.Peek().Current; - yield return current; + yield return current; - stack.Push(recursiveSelector(current).GetEnumerator()); - } - else - { - stack.Pop().Dispose(); - } + stack.Push(recursiveSelector(current).GetEnumerator()); } - } - finally - { - while (stack.Count > 0) + else { stack.Pop().Dispose(); } } } - - /// - /// Filters a sequence of values to ignore those which are null. - /// - /// - /// The coll. - /// - /// - public static IEnumerable WhereNotNull(this IEnumerable coll) where T : class + finally { - return coll.Where(x => x != null)!; + while (stack.Count > 0) + { + stack.Pop().Dispose(); + } } + } - public static IEnumerable ForAllThatAre(this IEnumerable sequence, Action projection) - where TActual : class - { - return sequence.Select( - x => + /// + /// Filters a sequence of values to ignore those which are null. + /// + /// + /// The coll. + /// + /// + public static IEnumerable WhereNotNull(this IEnumerable coll) where T : class => + coll.Where(x => x != null)!; + + public static IEnumerable ForAllThatAre(this IEnumerable sequence, + Action projection) + where TActual : class => + sequence.Select( + x => + { + if (x is TActual casted) { - if (x is TActual casted) - { - projection.Invoke(casted); - } - return x; - }); + projection.Invoke(casted); + } + + return x; + }); + + /// + /// Finds the index of the first item matching an expression in an enumerable. + /// + /// The type of the enumerated objects. + /// The enumerable to search. + /// The expression to test the items against. + /// The index of the first matching item, or -1. + public static int FindIndex(this IEnumerable items, Func predicate) => + FindIndex(items, 0, predicate); + + /// + /// Finds the index of the first item matching an expression in an enumerable. + /// + /// The type of the enumerated objects. + /// The enumerable to search. + /// The index to start at. + /// The expression to test the items against. + /// The index of the first matching item, or -1. + public static int FindIndex(this IEnumerable items, int startIndex, Func predicate) + { + if (items == null) + { + throw new ArgumentNullException("items"); } - /// - /// Finds the index of the first item matching an expression in an enumerable. - /// - /// The type of the enumerated objects. - /// The enumerable to search. - /// The expression to test the items against. - /// The index of the first matching item, or -1. - public static int FindIndex(this IEnumerable items, Func predicate) + if (predicate == null) { - return FindIndex(items, 0, predicate); + throw new ArgumentNullException("predicate"); } - /// - /// Finds the index of the first item matching an expression in an enumerable. - /// - /// The type of the enumerated objects. - /// The enumerable to search. - /// The index to start at. - /// The expression to test the items against. - /// The index of the first matching item, or -1. - public static int FindIndex(this IEnumerable items, int startIndex, Func predicate) + if (startIndex < 0) { - if (items == null) throw new ArgumentNullException("items"); - if (predicate == null) throw new ArgumentNullException("predicate"); - if (startIndex < 0) throw new ArgumentOutOfRangeException("startIndex"); + throw new ArgumentOutOfRangeException("startIndex"); + } - var index = startIndex; - if (index > 0) - items = items.Skip(index); + var index = startIndex; + if (index > 0) + { + items = items.Skip(index); + } - foreach (var item in items) + foreach (T item in items) + { + if (predicate(item)) { - if (predicate(item)) return index; - index++; + return index; } - return -1; + index++; } - ///Finds the index of the first occurrence of an item in an enumerable. - ///The enumerable to search. - ///The item to find. - ///The index of the first matching item, or -1 if the item was not found. - public static int IndexOf(this IEnumerable items, T item) + return -1; + } + + /// Finds the index of the first occurrence of an item in an enumerable. + /// The enumerable to search. + /// The item to find. + /// The index of the first matching item, or -1 if the item was not found. + public static int IndexOf(this IEnumerable items, T item) => + items.FindIndex(i => EqualityComparer.Default.Equals(item, i)); + + /// + /// Determines if 2 lists have equal elements within them regardless of how they are sorted + /// + /// + /// + /// + /// + /// + /// The logic for this is taken from: + /// http://stackoverflow.com/questions/4576723/test-whether-two-ienumerablet-have-the-same-values-with-the-same-frequencies + /// There's a few answers, this one seems the best for it's simplicity and based on the comment of Eamon + /// + public static bool UnsortedSequenceEqual(this IEnumerable? source, IEnumerable? other) + { + if (source == null && other == null) { - return items.FindIndex(i => EqualityComparer.Default.Equals(item, i)); + return true; } - /// - /// Determines if 2 lists have equal elements within them regardless of how they are sorted - /// - /// - /// - /// - /// - /// - /// The logic for this is taken from: - /// http://stackoverflow.com/questions/4576723/test-whether-two-ienumerablet-have-the-same-values-with-the-same-frequencies - /// - /// There's a few answers, this one seems the best for it's simplicity and based on the comment of Eamon - /// - public static bool UnsortedSequenceEqual(this IEnumerable? source, IEnumerable? other) + if (source == null || other == null) { - if (source == null && other == null) return true; - if (source == null || other == null) return false; + return false; + } - var list1Groups = source.ToLookup(i => i); - var list2Groups = other.ToLookup(i => i); - return list1Groups.Count == list2Groups.Count + ILookup list1Groups = source.ToLookup(i => i); + ILookup list2Groups = other.ToLookup(i => i); + return list1Groups.Count == list2Groups.Count && list1Groups.All(g => g.Count() == list2Groups[g.Key].Count()); - } + } - /// - /// Transforms an enumerable. - /// - /// - /// - /// - /// - public static IEnumerable Transform(this IEnumerable source, Func, IEnumerable> transform) - { - return transform(source); - } + /// + /// Transforms an enumerable. + /// + /// + /// + /// + /// + public static IEnumerable Transform(this IEnumerable source, + Func, IEnumerable> transform) => transform(source); - /// - /// Gets a null IEnumerable as an empty IEnumerable. - /// - /// - /// - /// - public static IEnumerable EmptyNull(this IEnumerable? items) - { - return items ?? Enumerable.Empty(); - } + /// + /// Gets a null IEnumerable as an empty IEnumerable. + /// + /// + /// + /// + public static IEnumerable EmptyNull(this IEnumerable? items) => items ?? Enumerable.Empty(); - // the .OfType() filter is nice when there's only one type - // this is to support filtering with multiple types - public static IEnumerable OfTypes(this IEnumerable contents, params Type[] types) - { - return contents.Where(x => types.Contains(x?.GetType())); - } + // the .OfType() filter is nice when there's only one type + // this is to support filtering with multiple types + public static IEnumerable OfTypes(this IEnumerable contents, params Type[] types) => + contents.Where(x => types.Contains(x?.GetType())); - public static IEnumerable SkipLast(this IEnumerable source) + public static IEnumerable SkipLast(this IEnumerable source) + { + using (IEnumerator e = source.GetEnumerator()) { - using (var e = source.GetEnumerator()) + if (e.MoveNext() == false) { - if (e.MoveNext() == false) yield break; - - for (var value = e.Current; e.MoveNext(); value = e.Current) - yield return value; + yield break; } - } - public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector, Direction sortOrder) - { - return sortOrder == Direction.Ascending ? source.OrderBy(keySelector) : source.OrderByDescending(keySelector); + for (T value = e.Current; e.MoveNext(); value = e.Current) + { + yield return value; + } } } + + public static IOrderedEnumerable OrderBy(this IEnumerable source, + Func keySelector, Direction sortOrder) => sortOrder == Direction.Ascending + ? source.OrderBy(keySelector) + : source.OrderByDescending(keySelector); } diff --git a/src/Umbraco.Core/Extensions/ExpressionExtensions.cs b/src/Umbraco.Core/Extensions/ExpressionExtensions.cs index d76f39a8de97..9d152ad5832c 100644 --- a/src/Umbraco.Core/Extensions/ExpressionExtensions.cs +++ b/src/Umbraco.Core/Extensions/ExpressionExtensions.cs @@ -1,27 +1,25 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using System.Linq.Expressions; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +internal static class ExpressionExtensions { - internal static class ExpressionExtensions - { - public static Expression> True() { return f => true; } + public static Expression> True() => f => true; - public static Expression> False() { return f => false; } + public static Expression> False() => f => false; - public static Expression> Or(this Expression> left, Expression> right) - { - var invokedExpr = Expression.Invoke(right, left.Parameters); - return Expression.Lambda>(Expression.OrElse(left.Body, invokedExpr), left.Parameters); - } + public static Expression> Or(this Expression> left, Expression> right) + { + InvocationExpression invokedExpr = Expression.Invoke(right, left.Parameters); + return Expression.Lambda>(Expression.OrElse(left.Body, invokedExpr), left.Parameters); + } - public static Expression> And(this Expression> left, Expression> right) - { - var invokedExpr = Expression.Invoke(right, left.Parameters); - return Expression.Lambda> (Expression.AndAlso(left.Body, invokedExpr), left.Parameters); - } + public static Expression> And(this Expression> left, Expression> right) + { + InvocationExpression invokedExpr = Expression.Invoke(right, left.Parameters); + return Expression.Lambda>(Expression.AndAlso(left.Body, invokedExpr), left.Parameters); } } diff --git a/src/Umbraco.Core/Extensions/HostEnvironmentExtensions.cs b/src/Umbraco.Core/Extensions/HostEnvironmentExtensions.cs index 944c9360b4a1..4176eaf987ef 100644 --- a/src/Umbraco.Core/Extensions/HostEnvironmentExtensions.cs +++ b/src/Umbraco.Core/Extensions/HostEnvironmentExtensions.cs @@ -1,53 +1,51 @@ -using System; -using System.IO; using Microsoft.Extensions.Hosting; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Extensions +namespace Umbraco.Cms.Core.Extensions; + +/// +/// Contains extension methods for the interface. +/// +public static class HostEnvironmentExtensions { + private static string? s_temporaryApplicationId; + /// - /// Contains extension methods for the interface. + /// Maps a virtual path to a physical path to the application's content root. /// - public static class HostEnvironmentExtensions + /// + /// Generally the content root is the parent directory of the web root directory. + /// + public static string MapPathContentRoot(this IHostEnvironment hostEnvironment, string path) { - private static string? s_temporaryApplicationId; - - /// - /// Maps a virtual path to a physical path to the application's content root. - /// - /// - /// Generally the content root is the parent directory of the web root directory. - /// - public static string MapPathContentRoot(this IHostEnvironment hostEnvironment, string path) - { - var root = hostEnvironment.ContentRootPath; + var root = hostEnvironment.ContentRootPath; - var newPath = path.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar); + var newPath = path.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar); - // TODO: This is a temporary error because we switched from IOHelper.MapPath to HostingEnvironment.MapPathXXX - // IOHelper would check if the path passed in started with the root, and not prepend the root again if it did, - // however if you are requesting a path be mapped, it should always assume the path is relative to the root, not - // absolute in the file system. This error will help us find and fix improper uses, and should be removed once - // all those uses have been found and fixed - if (newPath.StartsWith(root)) - { - throw new ArgumentException("The path appears to already be fully qualified. Please remove the call to MapPathContentRoot"); - } - - return Path.Combine(root, newPath.TrimStart(Constants.CharArrays.TildeForwardSlashBackSlash)); + // TODO: This is a temporary error because we switched from IOHelper.MapPath to HostingEnvironment.MapPathXXX + // IOHelper would check if the path passed in started with the root, and not prepend the root again if it did, + // however if you are requesting a path be mapped, it should always assume the path is relative to the root, not + // absolute in the file system. This error will help us find and fix improper uses, and should be removed once + // all those uses have been found and fixed + if (newPath.StartsWith(root)) + { + throw new ArgumentException( + "The path appears to already be fully qualified. Please remove the call to MapPathContentRoot"); } - /// - /// Gets a temporary application id for use before the ioc container is built. - /// - public static string GetTemporaryApplicationId(this IHostEnvironment hostEnvironment) - { - if (s_temporaryApplicationId != null) - { - return s_temporaryApplicationId; - } + return Path.Combine(root, newPath.TrimStart(Constants.CharArrays.TildeForwardSlashBackSlash)); + } - return s_temporaryApplicationId = hostEnvironment.ContentRootPath.GenerateHash(); + /// + /// Gets a temporary application id for use before the ioc container is built. + /// + public static string GetTemporaryApplicationId(this IHostEnvironment hostEnvironment) + { + if (s_temporaryApplicationId != null) + { + return s_temporaryApplicationId; } + + return s_temporaryApplicationId = hostEnvironment.ContentRootPath.GenerateHash(); } } diff --git a/src/Umbraco.Core/Extensions/IfExtensions.cs b/src/Umbraco.Core/Extensions/IfExtensions.cs index b4ef60ea57cd..d65157654808 100644 --- a/src/Umbraco.Core/Extensions/IfExtensions.cs +++ b/src/Umbraco.Core/Extensions/IfExtensions.cs @@ -1,60 +1,58 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; +namespace Umbraco.Extensions; -namespace Umbraco.Extensions +/// +/// Extension methods for 'If' checking like checking If something is null or not null +/// +public static class IfExtensions { - /// - /// Extension methods for 'If' checking like checking If something is null or not null - /// - public static class IfExtensions + /// The if not null. + /// The item. + /// The action. + /// The type + public static void IfNotNull(this TItem item, Action action) where TItem : class { - /// The if not null. - /// The item. - /// The action. - /// The type - public static void IfNotNull(this TItem item, Action action) where TItem : class + if (item != null) { - if (item != null) - { - action(item); - } + action(item); } + } - /// The if true. - /// The predicate. - /// The action. - public static void IfTrue(this bool predicate, Action action) + /// The if true. + /// The predicate. + /// The action. + public static void IfTrue(this bool predicate, Action action) + { + if (predicate) { - if (predicate) - { - action(); - } + action(); } + } - /// - /// Checks if the item is not null, and if so returns an action on that item, or a default value - /// - /// the result type - /// The type - /// The item. - /// The action. - /// The default value. - /// - public static TResult? IfNotNull(this TItem? item, Func action, TResult? defaultValue = default(TResult)) - where TItem : class - => item != null ? action(item) : defaultValue; + /// + /// Checks if the item is not null, and if so returns an action on that item, or a default value + /// + /// the result type + /// The type + /// The item. + /// The action. + /// The default value. + /// + public static TResult? IfNotNull(this TItem? item, Func action, + TResult? defaultValue = default) + where TItem : class + => item != null ? action(item) : defaultValue; - /// - /// Checks if the value is null, if it is it returns the value specified, otherwise returns the non-null value - /// - /// - /// - /// - /// - public static TItem IfNull(this TItem? item, Func action) - where TItem : class - => item ?? action(item!); - } + /// + /// Checks if the value is null, if it is it returns the value specified, otherwise returns the non-null value + /// + /// + /// + /// + /// + public static TItem IfNull(this TItem? item, Func action) + where TItem : class + => item ?? action(item!); } diff --git a/src/Umbraco.Core/Extensions/IntExtensions.cs b/src/Umbraco.Core/Extensions/IntExtensions.cs index 4f79baa3f505..5265587a140e 100644 --- a/src/Umbraco.Core/Extensions/IntExtensions.cs +++ b/src/Umbraco.Core/Extensions/IntExtensions.cs @@ -1,35 +1,34 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; +namespace Umbraco.Extensions; -namespace Umbraco.Extensions +public static class IntExtensions { - public static class IntExtensions + /// + /// Does something 'x' amount of times + /// + /// + /// + public static void Times(this int n, Action action) { - /// - /// Does something 'x' amount of times - /// - /// - /// - public static void Times(this int n, Action action) + for (var i = 0; i < n; i++) { - for (int i = 0; i < n; i++) - { - action(i); - } + action(i); } + } - /// - /// Creates a Guid based on an integer value - /// - /// value to convert - /// - public static Guid ToGuid(this int value) - { - byte[] bytes = new byte[16]; - BitConverter.GetBytes(value).CopyTo(bytes, 0); - return new Guid(bytes); - } + /// + /// Creates a Guid based on an integer value + /// + /// value to convert + /// + /// + /// + public static Guid ToGuid(this int value) + { + var bytes = new byte[16]; + BitConverter.GetBytes(value).CopyTo(bytes, 0); + return new Guid(bytes); } } diff --git a/src/Umbraco.Core/Extensions/KeyValuePairExtensions.cs b/src/Umbraco.Core/Extensions/KeyValuePairExtensions.cs index 73927f7a4123..5a612595fead 100644 --- a/src/Umbraco.Core/Extensions/KeyValuePairExtensions.cs +++ b/src/Umbraco.Core/Extensions/KeyValuePairExtensions.cs @@ -1,23 +1,20 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; +namespace Umbraco.Extensions; -namespace Umbraco.Extensions +/// +/// Provides extension methods for the struct. +/// +public static class KeyValuePairExtensions { /// - /// Provides extension methods for the struct. + /// Implements key/value pair deconstruction. /// - public static class KeyValuePairExtensions + /// Allows for foreach ((var k, var v) in ...). + public static void Deconstruct(this KeyValuePair kvp, out TKey key, out TValue value) { - /// - /// Implements key/value pair deconstruction. - /// - /// Allows for foreach ((var k, var v) in ...). - public static void Deconstruct(this KeyValuePair kvp, out TKey key, out TValue value) - { - key = kvp.Key; - value = kvp.Value; - } + key = kvp.Key; + value = kvp.Value; } } diff --git a/src/Umbraco.Core/Extensions/MediaTypeExtensions.cs b/src/Umbraco.Core/Extensions/MediaTypeExtensions.cs index 2c4627196462..d29df4f4993b 100644 --- a/src/Umbraco.Core/Extensions/MediaTypeExtensions.cs +++ b/src/Umbraco.Core/Extensions/MediaTypeExtensions.cs @@ -4,13 +4,12 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class MediaTypeExtensions { - public static class MediaTypeExtensions - { - public static bool IsSystemMediaType(this IMediaType mediaType) => - mediaType.Alias == Constants.Conventions.MediaTypes.File - || mediaType.Alias == Constants.Conventions.MediaTypes.Folder - || mediaType.Alias == Constants.Conventions.MediaTypes.Image; - } + public static bool IsSystemMediaType(this IMediaType mediaType) => + mediaType.Alias == Constants.Conventions.MediaTypes.File + || mediaType.Alias == Constants.Conventions.MediaTypes.Folder + || mediaType.Alias == Constants.Conventions.MediaTypes.Image; } diff --git a/src/Umbraco.Core/Extensions/NameValueCollectionExtensions.cs b/src/Umbraco.Core/Extensions/NameValueCollectionExtensions.cs index a07abfbd9693..155c68cc4863 100644 --- a/src/Umbraco.Core/Extensions/NameValueCollectionExtensions.cs +++ b/src/Umbraco.Core/Extensions/NameValueCollectionExtensions.cs @@ -1,43 +1,39 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using System.Collections.Specialized; -using System.Linq; +using Umbraco.Cms.Core; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class NameValueCollectionExtensions { - public static class NameValueCollectionExtensions + public static IEnumerable> AsEnumerable(this NameValueCollection nvc) { - public static IEnumerable> AsEnumerable(this NameValueCollection nvc) + foreach (var key in nvc.AllKeys) { - foreach (string? key in nvc.AllKeys) - { - yield return new KeyValuePair(key, nvc[key]); - } + yield return new KeyValuePair(key, nvc[key]); } + } - public static bool ContainsKey(this NameValueCollection collection, string key) + public static bool ContainsKey(this NameValueCollection collection, string key) => + collection.Keys.Cast().Any(k => (string)k == key); + + public static T? GetValue(this NameValueCollection collection, string key, T defaultIfNotFound) + { + if (collection.ContainsKey(key) == false) { - return collection.Keys.Cast().Any(k => (string) k == key); + return defaultIfNotFound; } - public static T? GetValue(this NameValueCollection collection, string key, T defaultIfNotFound) + var val = collection[key]; + if (val == null) { - if (collection.ContainsKey(key) == false) - { - return defaultIfNotFound; - } - - var val = collection[key]; - if (val == null) - { - return defaultIfNotFound; - } + return defaultIfNotFound; + } - var result = val.TryConvertTo(); + Attempt result = val.TryConvertTo(); - return result.Success ? result.Result : defaultIfNotFound; - } + return result.Success ? result.Result : defaultIfNotFound; } } diff --git a/src/Umbraco.Core/Extensions/ObjectExtensions.cs b/src/Umbraco.Core/Extensions/ObjectExtensions.cs index 1ba7e0fc4d6c..aa3c38fc9ab0 100644 --- a/src/Umbraco.Core/Extensions/ObjectExtensions.cs +++ b/src/Umbraco.Core/Extensions/ObjectExtensions.cs @@ -1,13 +1,9 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using System.Collections; using System.Collections.Concurrent; -using System.Collections.Generic; using System.ComponentModel; -using System.Globalization; -using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; @@ -15,767 +11,858 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Collections; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Provides object extension methods. +/// +public static class ObjectExtensions { - /// - /// Provides object extension methods. - /// - public static class ObjectExtensions - { - private static readonly ConcurrentDictionary NullableGenericCache = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary InputTypeConverterCache = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary DestinationTypeConverterCache = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary AssignableTypeCache = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary BoolConvertCache = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary NullableGenericCache = new(); + private static readonly ConcurrentDictionary InputTypeConverterCache = new(); + + private static readonly ConcurrentDictionary DestinationTypeConverterCache = + new(); + + private static readonly ConcurrentDictionary AssignableTypeCache = new(); + private static readonly ConcurrentDictionary BoolConvertCache = new(); - private static readonly char[] NumberDecimalSeparatorsToNormalize = { '.', ',' }; - private static readonly CustomBooleanTypeConverter CustomBooleanTypeConverter = new CustomBooleanTypeConverter(); + private static readonly char[] NumberDecimalSeparatorsToNormalize = {'.', ','}; + private static readonly CustomBooleanTypeConverter CustomBooleanTypeConverter = new(); - //private static readonly ConcurrentDictionary> ObjectFactoryCache = new ConcurrentDictionary>(); + //private static readonly ConcurrentDictionary> ObjectFactoryCache = new ConcurrentDictionary>(); - /// - /// - /// - /// - /// - /// - public static IEnumerable AsEnumerableOfOne(this T input) + /// + /// + /// + /// + /// + public static IEnumerable AsEnumerableOfOne(this T input) => Enumerable.Repeat(input, 1); + + /// + /// + /// + public static void DisposeIfDisposable(this object input) + { + if (input is IDisposable disposable) { - return Enumerable.Repeat(input, 1); + disposable.Dispose(); } + } - /// - /// - /// - /// - public static void DisposeIfDisposable(this object input) + /// + /// Provides a shortcut way of safely casting an input when you cannot guarantee the is + /// an instance type (i.e., when the C# AS keyword is not applicable). + /// + /// + /// The input. + /// + public static T? SafeCast(this object input) + { + if (ReferenceEquals(null, input) || ReferenceEquals(default(T), input)) { - if (input is IDisposable disposable) - disposable.Dispose(); + return default; } - /// - /// Provides a shortcut way of safely casting an input when you cannot guarantee the is - /// an instance type (i.e., when the C# AS keyword is not applicable). - /// - /// - /// The input. - /// - public static T? SafeCast(this object input) + if (input is T variable) { - if (ReferenceEquals(null, input) || ReferenceEquals(default(T), input)) return default; - if (input is T variable) return variable; - return default; + return variable; } - /// - /// Attempts to convert the input object to the output type. - /// - /// This code is an optimized version of the original Umbraco method - /// The type to convert to - /// The input. - /// The - public static Attempt TryConvertTo(this object? input) + return default; + } + + /// + /// Attempts to convert the input object to the output type. + /// + /// This code is an optimized version of the original Umbraco method + /// The type to convert to + /// The input. + /// The + public static Attempt TryConvertTo(this object? input) + { + Attempt result = TryConvertTo(input, typeof(T)); + + if (result.Success) { - Attempt result = TryConvertTo(input, typeof(T)); + return Attempt.Succeed((T?)result.Result); + } - if (result.Success) + if (input == null) + { + if (typeof(T).IsValueType) { - return Attempt.Succeed((T?)result.Result); + // fail, cannot convert null to a value type + return Attempt.Fail(); } + // sure, null can be any object + return Attempt.Succeed((T)input!); + } + + // just try to cast + try + { + return Attempt.Succeed((T)input); + } + catch (Exception e) + { + return Attempt.Fail(e); + } + } + + /// + /// Attempts to convert the input object to the output type. + /// + /// This code is an optimized version of the original Umbraco method + /// The input. + /// The type to convert to + /// The + public static Attempt TryConvertTo(this object? input, Type target) + { + if (target == null) + { + return Attempt.Fail(); + } + + try + { if (input == null) { - if (typeof(T).IsValueType) - { - // fail, cannot convert null to a value type - return Attempt.Fail(); - } - else + // Nullable is ok + if (target.IsGenericType && GetCachedGenericNullableType(target) != null) { - // sure, null can be any object - return Attempt.Succeed((T)input!); + return Attempt.Succeed(null); } - } - // just try to cast - try - { - return Attempt.Succeed((T)input); + // Reference types are ok + return Attempt.If(target.IsValueType == false, null); } - catch (Exception e) + + Type inputType = input.GetType(); + + // Easy + if (target == typeof(object) || inputType == target) { - return Attempt.Fail(e); + return Attempt.Succeed(input); } - } - /// - /// Attempts to convert the input object to the output type. - /// - /// This code is an optimized version of the original Umbraco method - /// The input. - /// The type to convert to - /// The - public static Attempt TryConvertTo(this object? input, Type target) - { - if (target == null) + // Check for string so that overloaders of ToString() can take advantage of the conversion. + if (target == typeof(string)) { - return Attempt.Fail(); + return Attempt.Succeed(input.ToString()); } - try + // If we've got a nullable of something, we try to convert directly to that thing. + // We cache the destination type and underlying nullable types + // Any other generic types need to fall through + if (target.IsGenericType) { - if (input == null) - { - // Nullable is ok - if (target.IsGenericType && GetCachedGenericNullableType(target) != null) - { - return Attempt.Succeed(null); - } - - // Reference types are ok - return Attempt.If(target.IsValueType == false, null); - } - - var inputType = input.GetType(); - - // Easy - if (target == typeof(object) || inputType == target) - { - return Attempt.Succeed(input); - } - - // Check for string so that overloaders of ToString() can take advantage of the conversion. - if (target == typeof(string)) - { - return Attempt.Succeed(input.ToString()); - } - - // If we've got a nullable of something, we try to convert directly to that thing. - // We cache the destination type and underlying nullable types - // Any other generic types need to fall through - if (target.IsGenericType) + Type underlying = GetCachedGenericNullableType(target); + if (underlying != null) { - var underlying = GetCachedGenericNullableType(target); - if (underlying != null) + // Special case for empty strings for bools/dates which should return null if an empty string. + if (input is string inputString) { - // Special case for empty strings for bools/dates which should return null if an empty string. - if (input is string inputString) - { - // TODO: Why the check against only bool/date when a string is null/empty? In what scenario can we convert to another type when the string is null or empty other than just being null? - if (string.IsNullOrEmpty(inputString) && (underlying == typeof(DateTime) || underlying == typeof(bool))) - { - return Attempt.Succeed(null); - } - } - - // Recursively call into this method with the inner (not-nullable) type and handle the outcome - var inner = input.TryConvertTo(underlying); - - // And if successful, fall on through to rewrap in a nullable; if failed, pass on the exception - if (inner.Success) + // TODO: Why the check against only bool/date when a string is null/empty? In what scenario can we convert to another type when the string is null or empty other than just being null? + if (string.IsNullOrEmpty(inputString) && + (underlying == typeof(DateTime) || underlying == typeof(bool))) { - input = inner.Result; // Now fall on through... - } - else - { - return Attempt.Fail(inner.Exception); + return Attempt.Succeed(null); } } - } - else - { - // target is not a generic type - if (input is string inputString) + // Recursively call into this method with the inner (not-nullable) type and handle the outcome + Attempt inner = input.TryConvertTo(underlying); + + // And if successful, fall on through to rewrap in a nullable; if failed, pass on the exception + if (inner.Success) { - // Try convert from string, returns an Attempt if the string could be - // processed (either succeeded or failed), else null if we need to try - // other methods - var result = TryConvertToFromString(inputString, target); - if (result.HasValue) - { - return result.Value; - } + input = inner.Result; // Now fall on through... } - - // TODO: Do a check for destination type being IEnumerable and source type implementing IEnumerable with - // the same 'T', then we'd have to find the extension method for the type AsEnumerable() and execute it. - if (GetCachedCanAssign(input, inputType, target)) + else { - return Attempt.Succeed(Convert.ChangeType(input, target)); + return Attempt.Fail(inner.Exception); } } + } + else + { + // target is not a generic type - if (target == typeof(bool)) + if (input is string inputString) { - if (GetCachedCanConvertToBoolean(inputType)) + // Try convert from string, returns an Attempt if the string could be + // processed (either succeeded or failed), else null if we need to try + // other methods + Attempt? result = TryConvertToFromString(inputString, target); + if (result.HasValue) { - return Attempt.Succeed(CustomBooleanTypeConverter.ConvertFrom(input!)); + return result.Value; } } - var inputConverter = GetCachedSourceTypeConverter(inputType, target); - if (inputConverter != null) + // TODO: Do a check for destination type being IEnumerable and source type implementing IEnumerable with + // the same 'T', then we'd have to find the extension method for the type AsEnumerable() and execute it. + if (GetCachedCanAssign(input, inputType, target)) { - return Attempt.Succeed(inputConverter.ConvertTo(input, target)); + return Attempt.Succeed(Convert.ChangeType(input, target)); } + } - var outputConverter = GetCachedTargetTypeConverter(inputType, target); - if (outputConverter != null) + if (target == typeof(bool)) + { + if (GetCachedCanConvertToBoolean(inputType)) { - return Attempt.Succeed(outputConverter.ConvertFrom(input!)); + return Attempt.Succeed(CustomBooleanTypeConverter.ConvertFrom(input!)); } + } - if (target.IsGenericType && GetCachedGenericNullableType(target) != null) - { - // cannot Convert.ChangeType as that does not work with nullable - // input has already been converted to the underlying type - just - // return input, there's an implicit conversion from T to T? anyways - return Attempt.Succeed(input); - } + TypeConverter inputConverter = GetCachedSourceTypeConverter(inputType, target); + if (inputConverter != null) + { + return Attempt.Succeed(inputConverter.ConvertTo(input, target)); + } - // Re-check convertibles since we altered the input through recursion - if (input is IConvertible convertible2) - { - return Attempt.Succeed(Convert.ChangeType(convertible2, target)); - } + TypeConverter outputConverter = GetCachedTargetTypeConverter(inputType, target); + if (outputConverter != null) + { + return Attempt.Succeed(outputConverter.ConvertFrom(input!)); } - catch (Exception e) + + if (target.IsGenericType && GetCachedGenericNullableType(target) != null) { - return Attempt.Fail(e); + // cannot Convert.ChangeType as that does not work with nullable + // input has already been converted to the underlying type - just + // return input, there's an implicit conversion from T to T? anyways + return Attempt.Succeed(input); } - return Attempt.Fail(); + // Re-check convertibles since we altered the input through recursion + if (input is IConvertible convertible2) + { + return Attempt.Succeed(Convert.ChangeType(convertible2, target)); + } + } + catch (Exception e) + { + return Attempt.Fail(e); } - /// - /// Attempts to convert the input string to the output type. - /// - /// This code is an optimized version of the original Umbraco method - /// The input. - /// The type to convert to - /// The - private static Attempt? TryConvertToFromString(this string input, Type target) + return Attempt.Fail(); + } + + /// + /// Attempts to convert the input string to the output type. + /// + /// This code is an optimized version of the original Umbraco method + /// The input. + /// The type to convert to + /// The + private static Attempt? TryConvertToFromString(this string input, Type target) + { + // Easy + if (target == typeof(string)) { - // Easy - if (target == typeof(string)) + return Attempt.Succeed(input); + } + + // Null, empty, whitespaces + if (string.IsNullOrWhiteSpace(input)) + { + if (target == typeof(bool)) { - return Attempt.Succeed(input); + // null/empty = bool false + return Attempt.Succeed(false); } - // Null, empty, whitespaces - if (string.IsNullOrWhiteSpace(input)) + if (target == typeof(DateTime)) { - if (target == typeof(bool)) + // null/empty = min DateTime value + return Attempt.Succeed(DateTime.MinValue); + } + + // Cannot decide here, + // Any of the types below will fail parsing and will return a failed attempt + // but anything else will not be processed and will return null + // so even though the string is null/empty we have to proceed. + } + + // Look for type conversions in the expected order of frequency of use. + // + // By using a mixture of ordered if statements and switches we can optimize both for + // fast conditional checking for most frequently used types and the branching + // that does not depend on previous values available to switch statements. + if (target.IsPrimitive) + { + if (target == typeof(int)) + { + if (int.TryParse(input, out var value)) { - // null/empty = bool false - return Attempt.Succeed(false); + return Attempt.Succeed(value); } - if (target == typeof(DateTime)) + // Because decimal 100.01m will happily convert to integer 100, it + // makes sense that string "100.01" *also* converts to integer 100. + var input2 = NormalizeNumberDecimalSeparator(input); + return Attempt.If(decimal.TryParse(input2, out var value2), Convert.ToInt32(value2)); + } + + if (target == typeof(long)) + { + if (long.TryParse(input, out var value)) { - // null/empty = min DateTime value - return Attempt.Succeed(DateTime.MinValue); + return Attempt.Succeed(value); } - // Cannot decide here, - // Any of the types below will fail parsing and will return a failed attempt - // but anything else will not be processed and will return null - // so even though the string is null/empty we have to proceed. + // Same as int + var input2 = NormalizeNumberDecimalSeparator(input); + return Attempt.If(decimal.TryParse(input2, out var value2), Convert.ToInt64(value2)); } - // Look for type conversions in the expected order of frequency of use. - // - // By using a mixture of ordered if statements and switches we can optimize both for - // fast conditional checking for most frequently used types and the branching - // that does not depend on previous values available to switch statements. - if (target.IsPrimitive) + // TODO: Should we do the decimal trick for short, byte, unsigned? + + if (target == typeof(bool)) { - if (target == typeof(int)) + if (bool.TryParse(input, out var value)) { - if (int.TryParse(input, out var value)) - { - return Attempt.Succeed(value); - } - - // Because decimal 100.01m will happily convert to integer 100, it - // makes sense that string "100.01" *also* converts to integer 100. - var input2 = NormalizeNumberDecimalSeparator(input); - return Attempt.If(decimal.TryParse(input2, out var value2), Convert.ToInt32(value2)); + return Attempt.Succeed(value); } - if (target == typeof(long)) - { - if (long.TryParse(input, out var value)) - { - return Attempt.Succeed(value); - } + // Don't declare failure so the CustomBooleanTypeConverter can try + return null; + } - // Same as int + // Calling this method directly is faster than any attempt to cache it. + switch (Type.GetTypeCode(target)) + { + case TypeCode.Int16: + return Attempt.If(short.TryParse(input, out var value), value); + + case TypeCode.Double: var input2 = NormalizeNumberDecimalSeparator(input); - return Attempt.If(decimal.TryParse(input2, out var value2), Convert.ToInt64(value2)); - } + return Attempt.If(double.TryParse(input2, out var valueD), valueD); - // TODO: Should we do the decimal trick for short, byte, unsigned? + case TypeCode.Single: + var input3 = NormalizeNumberDecimalSeparator(input); + return Attempt.If(float.TryParse(input3, out var valueF), valueF); - if (target == typeof(bool)) - { - if (bool.TryParse(input, out var value)) - { - return Attempt.Succeed(value); - } + case TypeCode.Char: + return Attempt.If(char.TryParse(input, out var valueC), valueC); - // Don't declare failure so the CustomBooleanTypeConverter can try - return null; - } + case TypeCode.Byte: + return Attempt.If(byte.TryParse(input, out var valueB), valueB); + + case TypeCode.SByte: + return Attempt.If(sbyte.TryParse(input, out var valueSb), valueSb); + + case TypeCode.UInt32: + return Attempt.If(uint.TryParse(input, out var valueU), valueU); + + case TypeCode.UInt16: + return Attempt.If(ushort.TryParse(input, out var valueUs), valueUs); - // Calling this method directly is faster than any attempt to cache it. - switch (Type.GetTypeCode(target)) + case TypeCode.UInt64: + return Attempt.If(ulong.TryParse(input, out var valueUl), valueUl); + } + } + else if (target == typeof(Guid)) + { + return Attempt.If(Guid.TryParse(input, out Guid value), value); + } + else if (target == typeof(DateTime)) + { + if (DateTime.TryParse(input, out DateTime value)) + { + switch (value.Kind) { - case TypeCode.Int16: - return Attempt.If(short.TryParse(input, out var value), value); + case DateTimeKind.Unspecified: + case DateTimeKind.Utc: + return Attempt.Succeed(value); - case TypeCode.Double: - var input2 = NormalizeNumberDecimalSeparator(input); - return Attempt.If(double.TryParse(input2, out var valueD), valueD); + case DateTimeKind.Local: + return Attempt.Succeed(value.ToUniversalTime()); - case TypeCode.Single: - var input3 = NormalizeNumberDecimalSeparator(input); - return Attempt.If(float.TryParse(input3, out var valueF), valueF); + default: + throw new ArgumentOutOfRangeException(); + } + } - case TypeCode.Char: - return Attempt.If(char.TryParse(input, out var valueC), valueC); + return Attempt.Fail(); + } + else if (target == typeof(DateTimeOffset)) + { + return Attempt.If(DateTimeOffset.TryParse(input, out DateTimeOffset value), value); + } + else if (target == typeof(TimeSpan)) + { + return Attempt.If(TimeSpan.TryParse(input, out TimeSpan value), value); + } + else if (target == typeof(decimal)) + { + var input2 = NormalizeNumberDecimalSeparator(input); + return Attempt.If(decimal.TryParse(input2, out var value), value); + } + else if (input != null && target == typeof(Version)) + { + return Attempt.If(Version.TryParse(input, out Version value), value); + } - case TypeCode.Byte: - return Attempt.If(byte.TryParse(input, out var valueB), valueB); + // E_NOTIMPL IPAddress, BigInteger + return null; // we can't decide... + } - case TypeCode.SByte: - return Attempt.If(sbyte.TryParse(input, out var valueSb), valueSb); + internal static void CheckThrowObjectDisposed(this IDisposable disposable, bool isDisposed, string objectname) + { + // TODO: Localize this exception + if (isDisposed) + { + throw new ObjectDisposedException(objectname); + } + } - case TypeCode.UInt32: - return Attempt.If(uint.TryParse(input, out var valueU), valueU); + //public enum PropertyNamesCaseType + //{ + // CamelCase, + // CaseInsensitive + //} + + ///// + ///// Convert an object to a JSON string with camelCase formatting + ///// + ///// + ///// + //public static string ToJsonString(this object obj) + //{ + // return obj.ToJsonString(PropertyNamesCaseType.CamelCase); + //} + + ///// + ///// Convert an object to a JSON string with the specified formatting + ///// + ///// The obj. + ///// Type of the property names case. + ///// + //public static string ToJsonString(this object obj, PropertyNamesCaseType propertyNamesCaseType) + //{ + // var type = obj.GetType(); + // var dateTimeStyle = "yyyy-MM-dd HH:mm:ss"; + + // if (type.IsPrimitive || typeof(string).IsAssignableFrom(type)) + // { + // return obj.ToString(); + // } + + // if (typeof(DateTime).IsAssignableFrom(type) || typeof(DateTimeOffset).IsAssignableFrom(type)) + // { + // return Convert.ToDateTime(obj).ToString(dateTimeStyle); + // } + + // var serializer = new JsonSerializer(); + + // switch (propertyNamesCaseType) + // { + // case PropertyNamesCaseType.CamelCase: + // serializer.ContractResolver = new CamelCasePropertyNamesContractResolver(); + // break; + // } + + // var dateTimeConverter = new IsoDateTimeConverter + // { + // DateTimeStyles = System.Globalization.DateTimeStyles.None, + // DateTimeFormat = dateTimeStyle + // }; + + // if (typeof(IDictionary).IsAssignableFrom(type)) + // { + // return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter); + // } + + // if (type.IsArray || (typeof(IEnumerable).IsAssignableFrom(type))) + // { + // return JArray.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter); + // } + + // return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter); + //} - case TypeCode.UInt16: - return Attempt.If(ushort.TryParse(input, out var valueUs), valueUs); + /// + /// Converts an object into a dictionary + /// + /// + /// + /// + /// + /// + /// + public static IDictionary? ToDictionary(this T o, + params Expression>[] ignoreProperties) => o?.ToDictionary(ignoreProperties + .Select(e => o.GetPropertyInfo(e)).Select(propInfo => propInfo.Name).ToArray()); - case TypeCode.UInt64: - return Attempt.If(ulong.TryParse(input, out var valueUl), valueUl); - } - } - else if (target == typeof(Guid)) - { - return Attempt.If(Guid.TryParse(input, out var value), value); - } - else if (target == typeof(DateTime)) + /// + /// Turns object into dictionary + /// + /// + /// Properties to ignore + /// + public static IDictionary ToDictionary(this object o, params string[] ignoreProperties) + { + if (o != null) + { + PropertyDescriptorCollection props = TypeDescriptor.GetProperties(o); + var d = new Dictionary(); + foreach (PropertyDescriptor prop in props.Cast() + .Where(x => ignoreProperties.Contains(x.Name) == false)) { - if (DateTime.TryParse(input, out var value)) + var val = prop.GetValue(o); + if (val != null) { - switch (value.Kind) - { - case DateTimeKind.Unspecified: - case DateTimeKind.Utc: - return Attempt.Succeed(value); + d.Add(prop.Name, (TVal)val); + } + } - case DateTimeKind.Local: - return Attempt.Succeed(value.ToUniversalTime()); + return d; + } - default: - throw new ArgumentOutOfRangeException(); - } - } + return new Dictionary(); + } - return Attempt.Fail(); - } - else if (target == typeof(DateTimeOffset)) + + internal static string? ToDebugString(this object? obj, int levels = 0) + { + if (obj == null) + { + return "{null}"; + } + + try + { + if (obj is string) { - return Attempt.If(DateTimeOffset.TryParse(input, out var value), value); + return "\"{0}\"".InvariantFormat(obj); } - else if (target == typeof(TimeSpan)) + + if (obj is int || obj is short || obj is long || obj is float || obj is double || obj is bool || + obj is int? || obj is short? || obj is long? || obj is float? || obj is double? || obj is bool?) { - return Attempt.If(TimeSpan.TryParse(input, out var value), value); + return "{0}".InvariantFormat(obj); } - else if (target == typeof(decimal)) + + if (obj is Enum) { - var input2 = NormalizeNumberDecimalSeparator(input); - return Attempt.If(decimal.TryParse(input2, out var value), value); + return "[{0}]".InvariantFormat(obj); } - else if (input != null && target == typeof(Version)) + + if (obj is IEnumerable enumerable) { - return Attempt.If(Version.TryParse(input, out var value), value); + var items = (from object enumItem in enumerable + let value = GetEnumPropertyDebugString(enumItem, levels) + where value != null + select value).Take(10).ToList(); + + return items.Any() + ? "{{ {0} }}".InvariantFormat(string.Join(", ", items)) + : null; } - // E_NOTIMPL IPAddress, BigInteger - return null; // we can't decide... - } - internal static void CheckThrowObjectDisposed(this IDisposable disposable, bool isDisposed, string objectname) - { - // TODO: Localize this exception - if (isDisposed) - throw new ObjectDisposedException(objectname); - } - - //public enum PropertyNamesCaseType - //{ - // CamelCase, - // CaseInsensitive - //} - - ///// - ///// Convert an object to a JSON string with camelCase formatting - ///// - ///// - ///// - //public static string ToJsonString(this object obj) - //{ - // return obj.ToJsonString(PropertyNamesCaseType.CamelCase); - //} - - ///// - ///// Convert an object to a JSON string with the specified formatting - ///// - ///// The obj. - ///// Type of the property names case. - ///// - //public static string ToJsonString(this object obj, PropertyNamesCaseType propertyNamesCaseType) - //{ - // var type = obj.GetType(); - // var dateTimeStyle = "yyyy-MM-dd HH:mm:ss"; - - // if (type.IsPrimitive || typeof(string).IsAssignableFrom(type)) - // { - // return obj.ToString(); - // } - - // if (typeof(DateTime).IsAssignableFrom(type) || typeof(DateTimeOffset).IsAssignableFrom(type)) - // { - // return Convert.ToDateTime(obj).ToString(dateTimeStyle); - // } - - // var serializer = new JsonSerializer(); - - // switch (propertyNamesCaseType) - // { - // case PropertyNamesCaseType.CamelCase: - // serializer.ContractResolver = new CamelCasePropertyNamesContractResolver(); - // break; - // } - - // var dateTimeConverter = new IsoDateTimeConverter - // { - // DateTimeStyles = System.Globalization.DateTimeStyles.None, - // DateTimeFormat = dateTimeStyle - // }; - - // if (typeof(IDictionary).IsAssignableFrom(type)) - // { - // return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter); - // } - - // if (type.IsArray || (typeof(IEnumerable).IsAssignableFrom(type))) - // { - // return JArray.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter); - // } - - // return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter); - //} - - /// - /// Converts an object into a dictionary - /// - /// - /// - /// - /// - /// - /// - public static IDictionary? ToDictionary(this T o, params Expression>[] ignoreProperties) - { - return o?.ToDictionary(ignoreProperties.Select(e => o.GetPropertyInfo(e)).Select(propInfo => propInfo.Name).ToArray()); - } - - /// - /// Turns object into dictionary - /// - /// - /// Properties to ignore - /// - public static IDictionary ToDictionary(this object o, params string[] ignoreProperties) - { - if (o != null) + PropertyInfo[] props = obj.GetType().GetProperties(); + if (props.Length == 2 && props[0].Name == "Key" && props[1].Name == "Value" && levels > -2) { - var props = TypeDescriptor.GetProperties(o); - var d = new Dictionary(); - foreach (var prop in props.Cast().Where(x => ignoreProperties.Contains(x.Name) == false)) + try { - var val = prop.GetValue(o); - if (val != null) - { - d.Add(prop.Name, (TVal)val); - } + var key = props[0].GetValue(obj, null) as string; + var value = props[1].GetValue(obj, null).ToDebugString(levels - 1); + return "{0}={1}".InvariantFormat(key, value); + } + catch (Exception) + { + return "[KeyValuePropertyException]"; } - return d; } - return new Dictionary(); + + if (levels > -1) + { + var items = + (from propertyInfo in props + let value = GetPropertyDebugString(propertyInfo, obj, levels) + where value != null + select "{0}={1}".InvariantFormat(propertyInfo.Name, value)).ToArray(); + + return items.Any() + ? "[{0}]:{{ {1} }}".InvariantFormat(obj.GetType().Name, string.Join(", ", items)) + : null; + } + } + catch (Exception ex) + { + return "[Exception:{0}]".InvariantFormat(ex.Message); } + return null; + } + /// + /// Attempts to serialize the value to an XmlString using ToXmlString + /// + /// + /// + /// + internal static Attempt TryConvertToXmlString(this object value, Type type) + { + try + { + var output = value.ToXmlString(type); + return Attempt.Succeed(output); + } + catch (NotSupportedException ex) + { + return Attempt.Fail(ex); + } + } - internal static string? ToDebugString(this object? obj, int levels = 0) + /// + /// Returns an XmlSerialized safe string representation for the value + /// + /// + /// The Type can only be a primitive type or Guid and byte[] otherwise an exception is thrown + /// + public static string ToXmlString(this object value, Type type) + { + if (value == null) { - if (obj == null) return "{null}"; - try - { - if (obj is string) - { - return "\"{0}\"".InvariantFormat(obj); - } - if (obj is int || obj is short || obj is long || obj is float || obj is double || obj is bool || obj is int? || obj is short? || obj is long? || obj is float? || obj is double? || obj is bool?) - { - return "{0}".InvariantFormat(obj); - } - if (obj is Enum) - { - return "[{0}]".InvariantFormat(obj); - } - if (obj is IEnumerable enumerable) - { - var items = (from object enumItem in enumerable let value = GetEnumPropertyDebugString(enumItem, levels) where value != null select value).Take(10).ToList(); + return string.Empty; + } - return items.Any() - ? "{{ {0} }}".InvariantFormat(string.Join(", ", items)) - : null; - } + if (type == typeof(string)) + { + return value.ToString().IsNullOrWhiteSpace() ? "" : value.ToString()!; + } - var props = obj.GetType().GetProperties(); - if ((props.Length == 2) && props[0].Name == "Key" && props[1].Name == "Value" && levels > -2) - { - try - { - var key = props[0].GetValue(obj, null) as string; - var value = props[1].GetValue(obj, null).ToDebugString(levels - 1); - return "{0}={1}".InvariantFormat(key, value); - } - catch (Exception) - { - return "[KeyValuePropertyException]"; - } - } - if (levels > -1) - { - var items = - (from propertyInfo in props - let value = GetPropertyDebugString(propertyInfo, obj, levels) - where value != null - select "{0}={1}".InvariantFormat(propertyInfo.Name, value)).ToArray(); - - return items.Any() - ? "[{0}]:{{ {1} }}".InvariantFormat(obj.GetType().Name, String.Join(", ", items)) - : null; - } - } - catch (Exception ex) - { - return "[Exception:{0}]".InvariantFormat(ex.Message); - } - return null; + if (type == typeof(bool)) + { + return XmlConvert.ToString((bool)value); } - /// - /// Attempts to serialize the value to an XmlString using ToXmlString - /// - /// - /// - /// - internal static Attempt TryConvertToXmlString(this object value, Type type) + if (type == typeof(byte)) { - try - { - var output = value.ToXmlString(type); - return Attempt.Succeed(output); - } - catch (NotSupportedException ex) - { - return Attempt.Fail(ex); - } + return XmlConvert.ToString((byte)value); } - /// - /// Returns an XmlSerialized safe string representation for the value - /// - /// - /// The Type can only be a primitive type or Guid and byte[] otherwise an exception is thrown - /// - public static string ToXmlString(this object value, Type type) - { - if (value == null) return string.Empty; - if (type == typeof(string)) return (value.ToString().IsNullOrWhiteSpace() ? "" : value.ToString()!); - if (type == typeof(bool)) return XmlConvert.ToString((bool)value); - if (type == typeof(byte)) return XmlConvert.ToString((byte)value); - if (type == typeof(char)) return XmlConvert.ToString((char)value); - if (type == typeof(DateTime)) return XmlConvert.ToString((DateTime)value, XmlDateTimeSerializationMode.Unspecified); - if (type == typeof(DateTimeOffset)) return XmlConvert.ToString((DateTimeOffset)value); - if (type == typeof(decimal)) return XmlConvert.ToString((decimal)value); - if (type == typeof(double)) return XmlConvert.ToString((double)value); - if (type == typeof(float)) return XmlConvert.ToString((float)value); - if (type == typeof(Guid)) return XmlConvert.ToString((Guid)value); - if (type == typeof(int)) return XmlConvert.ToString((int)value); - if (type == typeof(long)) return XmlConvert.ToString((long)value); - if (type == typeof(sbyte)) return XmlConvert.ToString((sbyte)value); - if (type == typeof(short)) return XmlConvert.ToString((short)value); - if (type == typeof(TimeSpan)) return XmlConvert.ToString((TimeSpan)value); - if (type == typeof(uint)) return XmlConvert.ToString((uint)value); - if (type == typeof(ulong)) return XmlConvert.ToString((ulong)value); - if (type == typeof(ushort)) return XmlConvert.ToString((ushort)value); - - throw new NotSupportedException("Cannot convert type " + type.FullName + " to a string using ToXmlString as it is not supported by XmlConvert"); - } - - /// - /// Returns an XmlSerialized safe string representation for the value and type - /// - /// - /// - /// - public static string ToXmlString(this object value) - { - return value.ToXmlString(typeof (T)); - } - - private static string? GetEnumPropertyDebugString(object enumItem, int levels) - { - try - { - return enumItem.ToDebugString(levels - 1); - } - catch (Exception) - { - return "[GetEnumPartException]"; - } + if (type == typeof(char)) + { + return XmlConvert.ToString((char)value); } - private static string? GetPropertyDebugString(PropertyInfo propertyInfo, object obj, int levels) + if (type == typeof(DateTime)) { - try - { - return propertyInfo.GetValue(obj, null).ToDebugString(levels - 1); - } - catch (Exception) - { - return "[GetPropertyValueException]"; - } + return XmlConvert.ToString((DateTime)value, XmlDateTimeSerializationMode.Unspecified); } - public static Guid AsGuid(this object value) + if (type == typeof(DateTimeOffset)) { - return value is Guid guid ? guid : Guid.Empty; + return XmlConvert.ToString((DateTimeOffset)value); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string NormalizeNumberDecimalSeparator(string s) + if (type == typeof(decimal)) { - var normalized = System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator[0]; - return s.ReplaceMany(NumberDecimalSeparatorsToNormalize, normalized); + return XmlConvert.ToString((decimal)value); } - // gets a converter for source, that can convert to target, or null if none exists - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static TypeConverter? GetCachedSourceTypeConverter(Type source, Type target) + if (type == typeof(double)) { - var key = new CompositeTypeTypeKey(source, target); + return XmlConvert.ToString((double)value); + } - if (InputTypeConverterCache.TryGetValue(key, out var typeConverter)) - { - return typeConverter; - } + if (type == typeof(float)) + { + return XmlConvert.ToString((float)value); + } - var converter = TypeDescriptor.GetConverter(source); - if (converter.CanConvertTo(target)) - { - return InputTypeConverterCache[key] = converter; - } + if (type == typeof(Guid)) + { + return XmlConvert.ToString((Guid)value); + } - InputTypeConverterCache[key] = null; - return null; + if (type == typeof(int)) + { + return XmlConvert.ToString((int)value); } - // gets a converter for target, that can convert from source, or null if none exists - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static TypeConverter? GetCachedTargetTypeConverter(Type source, Type target) + if (type == typeof(long)) { - var key = new CompositeTypeTypeKey(source, target); + return XmlConvert.ToString((long)value); + } - if (DestinationTypeConverterCache.TryGetValue(key, out var typeConverter)) - { - return typeConverter; - } + if (type == typeof(sbyte)) + { + return XmlConvert.ToString((sbyte)value); + } - var converter = TypeDescriptor.GetConverter(target); - if (converter.CanConvertFrom(source)) - { - return DestinationTypeConverterCache[key] = converter; - } + if (type == typeof(short)) + { + return XmlConvert.ToString((short)value); + } - DestinationTypeConverterCache[key] = null; - return null; + if (type == typeof(TimeSpan)) + { + return XmlConvert.ToString((TimeSpan)value); } - // gets the underlying type of a nullable type, or null if the type is not nullable - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Type? GetCachedGenericNullableType(Type type) + if (type == typeof(uint)) { - if (NullableGenericCache.TryGetValue(type, out var underlyingType)) - { - return underlyingType; - } + return XmlConvert.ToString((uint)value); + } - if (type.GetGenericTypeDefinition() == typeof(Nullable<>)) - { - Type? underlying = Nullable.GetUnderlyingType(type); - return NullableGenericCache[type] = underlying; - } + if (type == typeof(ulong)) + { + return XmlConvert.ToString((ulong)value); + } - NullableGenericCache[type] = null; - return null; + if (type == typeof(ushort)) + { + return XmlConvert.ToString((ushort)value); } - // gets an IConvertible from source to target type, or null if none exists - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool GetCachedCanAssign(object input, Type source, Type target) + throw new NotSupportedException("Cannot convert type " + type.FullName + + " to a string using ToXmlString as it is not supported by XmlConvert"); + } + + /// + /// Returns an XmlSerialized safe string representation for the value and type + /// + /// + /// + /// + public static string ToXmlString(this object value) => value.ToXmlString(typeof(T)); + + private static string? GetEnumPropertyDebugString(object enumItem, int levels) + { + try { - var key = new CompositeTypeTypeKey(source, target); - if (AssignableTypeCache.TryGetValue(key, out var canConvert)) - { - return canConvert; - } + return enumItem.ToDebugString(levels - 1); + } + catch (Exception) + { + return "[GetEnumPartException]"; + } + } - // "object is" is faster than "Type.IsAssignableFrom. - // We can use it to very quickly determine whether true/false - if (input is IConvertible && target.IsAssignableFrom(source)) - { - return AssignableTypeCache[key] = true; - } + private static string? GetPropertyDebugString(PropertyInfo propertyInfo, object obj, int levels) + { + try + { + return propertyInfo.GetValue(obj, null).ToDebugString(levels - 1); + } + catch (Exception) + { + return "[GetPropertyValueException]"; + } + } + + public static Guid AsGuid(this object value) => value is Guid guid ? guid : Guid.Empty; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string NormalizeNumberDecimalSeparator(string s) + { + var normalized = Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator[0]; + return s.ReplaceMany(NumberDecimalSeparatorsToNormalize, normalized); + } + + // gets a converter for source, that can convert to target, or null if none exists + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static TypeConverter? GetCachedSourceTypeConverter(Type source, Type target) + { + var key = new CompositeTypeTypeKey(source, target); - return AssignableTypeCache[key] = false; + if (InputTypeConverterCache.TryGetValue(key, out TypeConverter typeConverter)) + { + return typeConverter; } - // determines whether a type can be converted to boolean - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool GetCachedCanConvertToBoolean(Type type) + TypeConverter converter = TypeDescriptor.GetConverter(source); + if (converter.CanConvertTo(target)) { - if (BoolConvertCache.TryGetValue(type, out var result)) - { - return result; - } + return InputTypeConverterCache[key] = converter; + } - if (CustomBooleanTypeConverter.CanConvertFrom(type)) - { - return BoolConvertCache[type] = true; - } + InputTypeConverterCache[key] = null; + return null; + } - return BoolConvertCache[type] = false; + // gets a converter for target, that can convert from source, or null if none exists + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static TypeConverter? GetCachedTargetTypeConverter(Type source, Type target) + { + var key = new CompositeTypeTypeKey(source, target); + + if (DestinationTypeConverterCache.TryGetValue(key, out TypeConverter typeConverter)) + { + return typeConverter; + } + + TypeConverter converter = TypeDescriptor.GetConverter(target); + if (converter.CanConvertFrom(source)) + { + return DestinationTypeConverterCache[key] = converter; } + DestinationTypeConverterCache[key] = null; + return null; + } + + // gets the underlying type of a nullable type, or null if the type is not nullable + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Type? GetCachedGenericNullableType(Type type) + { + if (NullableGenericCache.TryGetValue(type, out Type underlyingType)) + { + return underlyingType; + } + + if (type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + Type? underlying = Nullable.GetUnderlyingType(type); + return NullableGenericCache[type] = underlying; + } + + NullableGenericCache[type] = null; + return null; + } + + // gets an IConvertible from source to target type, or null if none exists + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool GetCachedCanAssign(object input, Type source, Type target) + { + var key = new CompositeTypeTypeKey(source, target); + if (AssignableTypeCache.TryGetValue(key, out var canConvert)) + { + return canConvert; + } + + // "object is" is faster than "Type.IsAssignableFrom. + // We can use it to very quickly determine whether true/false + if (input is IConvertible && target.IsAssignableFrom(source)) + { + return AssignableTypeCache[key] = true; + } + + return AssignableTypeCache[key] = false; + } + + // determines whether a type can be converted to boolean + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool GetCachedCanConvertToBoolean(Type type) + { + if (BoolConvertCache.TryGetValue(type, out var result)) + { + return result; + } + + if (CustomBooleanTypeConverter.CanConvertFrom(type)) + { + return BoolConvertCache[type] = true; + } + return BoolConvertCache[type] = false; } } diff --git a/src/Umbraco.Core/Extensions/PasswordConfigurationExtensions.cs b/src/Umbraco.Core/Extensions/PasswordConfigurationExtensions.cs index 0c15da6bdb9f..6a74970c93b8 100644 --- a/src/Umbraco.Core/Extensions/PasswordConfigurationExtensions.cs +++ b/src/Umbraco.Core/Extensions/PasswordConfigurationExtensions.cs @@ -1,41 +1,34 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Configuration; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class PasswordConfigurationExtensions { - public static class PasswordConfigurationExtensions - { - /// - /// Returns the configuration of the membership provider used to configure change password editors - /// - /// - /// - /// - public static IDictionary GetConfiguration( - this IPasswordConfiguration passwordConfiguration, - bool allowManuallyChangingPassword = false) + /// + /// Returns the configuration of the membership provider used to configure change password editors + /// + /// + /// + /// + public static IDictionary GetConfiguration( + this IPasswordConfiguration passwordConfiguration, + bool allowManuallyChangingPassword = false) => + new Dictionary { - return new Dictionary - { - {"minPasswordLength", passwordConfiguration.RequiredLength}, - - // TODO: This doesn't make a ton of sense with asp.net identity and also there's a bunch of other settings - // that we can consider with IPasswordConfiguration, but these are currently still based on how membership providers worked. - {"minNonAlphaNumericChars", passwordConfiguration.GetMinNonAlphaNumericChars()}, + {"minPasswordLength", passwordConfiguration.RequiredLength}, - // A flag to indicate if the current password box should be shown or not, only a user that has access to change other user/member passwords - // doesn't have to specify the current password for the user/member. A user changing their own password must specify their current password. - {"allowManuallyChangingPassword", allowManuallyChangingPassword}, - }; - } + // TODO: This doesn't make a ton of sense with asp.net identity and also there's a bunch of other settings + // that we can consider with IPasswordConfiguration, but these are currently still based on how membership providers worked. + {"minNonAlphaNumericChars", passwordConfiguration.GetMinNonAlphaNumericChars()}, - public static int GetMinNonAlphaNumericChars(this IPasswordConfiguration passwordConfiguration) - { - return passwordConfiguration.RequireNonLetterOrDigit ? 2 : 0; - } + // A flag to indicate if the current password box should be shown or not, only a user that has access to change other user/member passwords + // doesn't have to specify the current password for the user/member. A user changing their own password must specify their current password. + {"allowManuallyChangingPassword", allowManuallyChangingPassword} + }; - } + public static int GetMinNonAlphaNumericChars(this IPasswordConfiguration passwordConfiguration) => + passwordConfiguration.RequireNonLetterOrDigit ? 2 : 0; } diff --git a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs index 48769bda2c67..b2bc5f723d28 100644 --- a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs +++ b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs @@ -1,1377 +1,1483 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using System.Data; -using System.Linq; -using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Services; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class PublishedContentExtensions { - public static class PublishedContentExtensions + #region Name + + /// + /// Gets the name of the content item. + /// + /// The content item. + /// + /// + /// The specific culture to get the name for. If null is used the current culture is used (Default is + /// null). + /// + public static string? Name(this IPublishedContent content, IVariationContextAccessor? variationContextAccessor, + string? culture = null) { - #region Name - - /// - /// Gets the name of the content item. - /// - /// The content item. - /// - /// The specific culture to get the name for. If null is used the current culture is used (Default is null). - public static string? Name(this IPublishedContent content, IVariationContextAccessor? variationContextAccessor, string? culture = null) + if (content == null) { - if (content == null) - { - throw new ArgumentNullException(nameof(content)); - } - - // invariant has invariant value (whatever the requested culture) - if (!content.ContentType.VariesByCulture()) - return content.Cultures.TryGetValue(string.Empty, out var invariantInfos) ? invariantInfos.Name : null; - - // handle context culture for variant - if (culture == null) - culture = variationContextAccessor?.VariationContext?.Culture ?? string.Empty; - - // get - return culture != string.Empty && content.Cultures.TryGetValue(culture, out var infos) ? infos.Name : null; + throw new ArgumentNullException(nameof(content)); } - #endregion - - #region Url segment - - /// - /// Gets the URL segment of the content item. - /// - /// The content item. - /// - /// The specific culture to get the URL segment for. If null is used the current culture is used (Default is null). - public static string? UrlSegment(this IPublishedContent content, IVariationContextAccessor? variationContextAccessor, string? culture = null) + // invariant has invariant value (whatever the requested culture) + if (!content.ContentType.VariesByCulture()) { - if (content == null) - { - throw new ArgumentNullException(nameof(content)); - } - - // invariant has invariant value (whatever the requested culture) - if (!content.ContentType.VariesByCulture()) - return content.Cultures.TryGetValue("", out var invariantInfos) ? invariantInfos.UrlSegment : null; - - // handle context culture for variant - if (culture == null) - culture = variationContextAccessor?.VariationContext?.Culture ?? ""; - - // get - return culture != "" && content.Cultures.TryGetValue(culture, out var infos) ? infos.UrlSegment : null; + return content.Cultures.TryGetValue(string.Empty, out PublishedCultureInfo invariantInfos) + ? invariantInfos.Name + : null; } - #endregion - - #region Culture - - /// - /// Determines whether the content has a culture. - /// - /// Culture is case-insensitive. - public static bool HasCulture(this IPublishedContent content, string? culture) + // handle context culture for variant + if (culture == null) { - if (content == null) - { - throw new ArgumentNullException(nameof(content)); - } - - return content.Cultures.ContainsKey(culture ?? string.Empty); + culture = variationContextAccessor?.VariationContext?.Culture ?? string.Empty; } - /// - /// Determines whether the content is invariant, or has a culture. - /// - /// Culture is case-insensitive. - public static bool IsInvariantOrHasCulture(this IPublishedContent content, string culture) - => !content.ContentType.VariesByCulture() || content.Cultures.ContainsKey(culture ?? ""); - - /// - /// Filters a sequence of to return invariant items, and items that are published for the specified culture. - /// - /// The content items. - /// - /// The specific culture to filter for. If null is used the current culture is used. (Default is null). - internal static IEnumerable WhereIsInvariantOrHasCulture(this IEnumerable contents, IVariationContextAccessor variationContextAccessor, string? culture = null) - where T : class, IPublishedContent - { - if (contents == null) throw new ArgumentNullException(nameof(contents)); - - culture = culture ?? variationContextAccessor.VariationContext?.Culture ?? ""; + // get + return culture != string.Empty && content.Cultures.TryGetValue(culture, out PublishedCultureInfo infos) + ? infos.Name + : null; + } - // either does not vary by culture, or has the specified culture - return contents.Where(x => !x.ContentType.VariesByCulture() || HasCulture(x, culture)); + #endregion + + #region Url segment + + /// + /// Gets the URL segment of the content item. + /// + /// The content item. + /// + /// + /// The specific culture to get the URL segment for. If null is used the current culture is used + /// (Default is null). + /// + public static string? UrlSegment(this IPublishedContent content, + IVariationContextAccessor? variationContextAccessor, string? culture = null) + { + if (content == null) + { + throw new ArgumentNullException(nameof(content)); } - /// - /// Gets the culture date of the content item. - /// - /// The content item. - /// - /// The specific culture to get the name for. If null is used the current culture is used (Default is null). - public static DateTime CultureDate(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) + // invariant has invariant value (whatever the requested culture) + if (!content.ContentType.VariesByCulture()) { - // invariant has invariant value (whatever the requested culture) - if (!content.ContentType.VariesByCulture()) - return content.UpdateDate; - - // handle context culture for variant - if (culture == null) - culture = variationContextAccessor?.VariationContext?.Culture ?? ""; - - // get - return culture != "" && content.Cultures.TryGetValue(culture, out var infos) ? infos.Date : DateTime.MinValue; + return content.Cultures.TryGetValue("", out PublishedCultureInfo invariantInfos) + ? invariantInfos.UrlSegment + : null; } - #endregion - - #region IsComposedOf - - /// - /// Gets a value indicating whether the content is of a content type composed of the given alias - /// - /// The content. - /// The content type alias. - /// A value indicating whether the content is of a content type composed of a content type identified by the alias. - public static bool IsComposedOf(this IPublishedContent content, string alias) + // handle context culture for variant + if (culture == null) { - return content.ContentType.CompositionAliases.InvariantContains(alias); + culture = variationContextAccessor?.VariationContext?.Culture ?? ""; } - #endregion - - #region Template - - /// - /// Returns the current template Alias - /// - /// Empty string if none is set. - public static string GetTemplateAlias(this IPublishedContent content, IFileService fileService) - { - if (content.TemplateId.HasValue == false) - { - return string.Empty; - } + // get + return culture != "" && content.Cultures.TryGetValue(culture, out PublishedCultureInfo infos) + ? infos.UrlSegment + : null; + } - var template = fileService.GetTemplate(content.TemplateId.Value); - return template?.Alias ?? string.Empty; - } + #endregion - public static bool IsAllowedTemplate(this IPublishedContent content, IContentTypeService contentTypeService, - WebRoutingSettings webRoutingSettings, int templateId) - { - return content.IsAllowedTemplate(contentTypeService, - webRoutingSettings.DisableAlternativeTemplates, - webRoutingSettings.ValidateAlternativeTemplates, templateId); - } + #region IsComposedOf - public static bool IsAllowedTemplate(this IPublishedContent content, IContentTypeService contentTypeService, bool disableAlternativeTemplates, bool validateAlternativeTemplates, int templateId) - { - if (disableAlternativeTemplates) - return content.TemplateId == templateId; + /// + /// Gets a value indicating whether the content is of a content type composed of the given alias + /// + /// The content. + /// The content type alias. + /// + /// A value indicating whether the content is of a content type composed of a content type identified by the + /// alias. + /// + public static bool IsComposedOf(this IPublishedContent content, string alias) => + content.ContentType.CompositionAliases.InvariantContains(alias); - if (content.TemplateId == templateId || !validateAlternativeTemplates) - return true; + #endregion - var publishedContentContentType = contentTypeService.Get(content.ContentType.Id); - if (publishedContentContentType == null) - throw new NullReferenceException("No content type returned for published content (contentType='" + content.ContentType.Id + "')"); + #region Axes: parent - return publishedContentContentType.IsAllowedTemplate(templateId); + // Parent is native - } - public static bool IsAllowedTemplate(this IPublishedContent content, IFileService fileService, IContentTypeService contentTypeService, bool disableAlternativeTemplates, bool validateAlternativeTemplates, string templateAlias) + /// + /// Gets the parent of the content, of a given content type. + /// + /// The content type. + /// The content. + /// The parent of content, of the given content type, else null. + public static T? Parent(this IPublishedContent content) + where T : class, IPublishedContent + { + if (content == null) { - var template = fileService.GetTemplate(templateAlias); - return template != null && content.IsAllowedTemplate(contentTypeService, disableAlternativeTemplates, validateAlternativeTemplates, template.Id); + throw new ArgumentNullException(nameof(content)); } - #endregion - - #region HasValue, Value, Value - - /// - /// Gets a value indicating whether the content has a value for a property identified by its alias. - /// - /// The content. - /// The published value fallback implementation. - /// The property alias. - /// The variation language. - /// The variation segment. - /// Optional fallback strategy. - /// A value indicating whether the content has a value for the property identified by the alias. - /// Returns true if HasValue is true, or a fallback strategy can provide a value. - public static bool HasValue(this IPublishedContent content, IPublishedValueFallback publishedValueFallback, string alias, string? culture = null, string? segment = null, Fallback fallback = default) - { - var property = content.GetProperty(alias); - - // if we have a property, and it has a value, return that value - if (property != null && property.HasValue(culture, segment)) - return true; - - // else let fallback try to get a value - return publishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, null, out _, out _); - } + return content.Parent as T; + } - /// - /// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value. - /// - /// The content. - /// The published value fallback implementation. - /// The property alias. - /// The variation language. - /// The variation segment. - /// Optional fallback strategy. - /// The default value. - /// The value of the content's property identified by the alias, if it exists, otherwise a default value. - public static object? Value(this IPublishedContent content, IPublishedValueFallback publishedValueFallback, string alias, string? culture = null, string? segment = null, Fallback fallback = default, object? defaultValue = default) + #endregion + + #region Url + + /// + /// Gets the url of the content item. + /// + /// + /// + /// If the content item is a document, then this method returns the url of the + /// document. If it is a media, then this methods return the media url for the + /// 'umbracoFile' property. Use the MediaUrl() method to get the media url for other + /// properties. + /// + /// + /// The value of this property is contextual. It depends on the 'current' request uri, + /// if any. In addition, when the content type is multi-lingual, this is the url for the + /// specified culture. Otherwise, it is the invariant url. + /// + /// + public static string Url(this IPublishedContent content, IPublishedUrlProvider publishedUrlProvider, + string? culture = null, UrlMode mode = UrlMode.Default) + { + if (publishedUrlProvider == null) { - var property = content.GetProperty(alias); - - // if we have a property, and it has a value, return that value - if (property != null && property.HasValue(culture, segment)) - return property.GetValue(culture, segment); - - // else let fallback try to get a value - if (publishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value, out property)) - return value; - - // else... if we have a property, at least let the converter return its own - // vision of 'no value' (could be an empty enumerable) - return property?.GetValue(culture, segment); + throw new InvalidOperationException( + "Cannot resolve a Url when Current.UmbracoContext.UrlProvider is null."); } - /// - /// Gets the value of a content's property identified by its alias, converted to a specified type. - /// - /// The target property type. - /// The content. - /// The published value fallback implementation. - /// The property alias. - /// The variation language. - /// The variation segment. - /// Optional fallback strategy. - /// The default value. - /// The value of the content's property identified by the alias, converted to the specified type. - public static T? Value(this IPublishedContent content, IPublishedValueFallback publishedValueFallback, string alias, string? culture = null, string? segment = null, Fallback fallback = default, T? defaultValue = default) + switch (content.ContentType.ItemType) { - var property = content.GetProperty(alias); + case PublishedItemType.Content: + return publishedUrlProvider.GetUrl(content, mode, culture); - // if we have a property, and it has a value, return that value - if (property != null && property.HasValue(culture, segment)) - return property.Value(publishedValueFallback, culture, segment); + case PublishedItemType.Media: + return publishedUrlProvider.GetMediaUrl(content, mode, culture); - // else let fallback try to get a value - if (publishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value, out property)) - return value; - - // else... if we have a property, at least let the converter return its own - // vision of 'no value' (could be an empty enumerable) - otherwise, default - return property == null ? default : property.Value(publishedValueFallback, culture, segment); + default: + throw new NotSupportedException(); } + } - #endregion + #endregion - #region IsSomething: misc. + #region Culture - /// - /// Determines whether the specified content is a specified content type. - /// - /// The content to determine content type of. - /// The alias of the content type to test against. - /// True if the content is of the specified content type; otherwise false. - public static bool IsDocumentType(this IPublishedContent content, string docTypeAlias) + /// + /// Determines whether the content has a culture. + /// + /// Culture is case-insensitive. + public static bool HasCulture(this IPublishedContent content, string? culture) + { + if (content == null) { - return content.ContentType.Alias.InvariantEquals(docTypeAlias); + throw new ArgumentNullException(nameof(content)); } - /// - /// Determines whether the specified content is a specified content type or it's derived types. - /// - /// The content to determine content type of. - /// The alias of the content type to test against. - /// When true, recurses up the content type tree to check inheritance; when false just calls IsDocumentType(this IPublishedContent content, string docTypeAlias). - /// True if the content is of the specified content type or a derived content type; otherwise false. - public static bool IsDocumentType(this IPublishedContent content, string docTypeAlias, bool recursive) - { - if (content.IsDocumentType(docTypeAlias)) - return true; + return content.Cultures.ContainsKey(culture ?? string.Empty); + } - return recursive && content.IsComposedOf(docTypeAlias); + /// + /// Determines whether the content is invariant, or has a culture. + /// + /// Culture is case-insensitive. + public static bool IsInvariantOrHasCulture(this IPublishedContent content, string culture) + => !content.ContentType.VariesByCulture() || content.Cultures.ContainsKey(culture ?? ""); + + /// + /// Filters a sequence of to return invariant items, and items that are published for + /// the specified culture. + /// + /// The content items. + /// + /// + /// The specific culture to filter for. If null is used the current culture is used. (Default is + /// null). + /// + internal static IEnumerable WhereIsInvariantOrHasCulture(this IEnumerable contents, + IVariationContextAccessor variationContextAccessor, string? culture = null) + where T : class, IPublishedContent + { + if (contents == null) + { + throw new ArgumentNullException(nameof(contents)); } - #endregion + culture = culture ?? variationContextAccessor.VariationContext?.Culture ?? ""; - #region IsSomething: equality + // either does not vary by culture, or has the specified culture + return contents.Where(x => !x.ContentType.VariesByCulture() || HasCulture(x, culture)); + } - public static bool IsEqual(this IPublishedContent content, IPublishedContent other) + /// + /// Gets the culture date of the content item. + /// + /// The content item. + /// + /// + /// The specific culture to get the name for. If null is used the current culture is used (Default is + /// null). + /// + public static DateTime CultureDate(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, string? culture = null) + { + // invariant has invariant value (whatever the requested culture) + if (!content.ContentType.VariesByCulture()) { - return content.Id == other.Id; + return content.UpdateDate; } - public static bool IsNotEqual(this IPublishedContent content, IPublishedContent other) + // handle context culture for variant + if (culture == null) { - return content.IsEqual(other) == false; + culture = variationContextAccessor?.VariationContext?.Culture ?? ""; } - #endregion - - #region IsSomething: ancestors and descendants - - public static bool IsDescendant(this IPublishedContent content, IPublishedContent other) - { - return other.Level < content.Level && content.Path.InvariantStartsWith(other.Path.EnsureEndsWith(',')); - } + // get + return culture != "" && content.Cultures.TryGetValue(culture, out PublishedCultureInfo infos) + ? infos.Date + : DateTime.MinValue; + } - public static bool IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other) - { - return content.Path.InvariantEquals(other.Path) || content.IsDescendant(other); - } + #endregion - public static bool IsAncestor(this IPublishedContent content, IPublishedContent other) - { - return content.Level < other.Level && other.Path.InvariantStartsWith(content.Path.EnsureEndsWith(',')); - } + #region Template - public static bool IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other) + /// + /// Returns the current template Alias + /// + /// Empty string if none is set. + public static string GetTemplateAlias(this IPublishedContent content, IFileService fileService) + { + if (content.TemplateId.HasValue == false) { - return other.Path.InvariantEquals(content.Path) || content.IsAncestor(other); + return string.Empty; } - #endregion - - #region Axes: ancestors, ancestors-or-self - - // as per XPath 1.0 specs �2.2, - // - the ancestor axis contains the ancestors of the context node; the ancestors of the context node consist - // of the parent of context node and the parent's parent and so on; thus, the ancestor axis will always - // include the root node, unless the context node is the root node. - // - the ancestor-or-self axis contains the context node and the ancestors of the context node; thus, - // the ancestor axis will always include the root node. - // - // as per XPath 2.0 specs �3.2.1.1, - // - the ancestor axis is defined as the transitive closure of the parent axis; it contains the ancestors - // of the context node (the parent, the parent of the parent, and so on) - The ancestor axis includes the - // root node of the tree in which the context node is found, unless the context node is the root node. - // - the ancestor-or-self axis contains the context node and the ancestors of the context node; thus, - // the ancestor-or-self axis will always include the root node. - // - // the ancestor and ancestor-or-self axis are reverse axes ie they contain the context node or nodes that - // are before the context node in document order. - // - // document order is defined by �2.4.1 as: - // - the root node is the first node. - // - every node occurs before all of its children and descendants. - // - the relative order of siblings is the order in which they occur in the children property of their parent node. - // - children and descendants occur before following siblings. - - /// - /// Gets the ancestors of the content. - /// - /// The content. - /// The ancestors of the content, in down-top order. - /// Does not consider the content itself. - public static IEnumerable Ancestors(this IPublishedContent content) - { - return content.AncestorsOrSelf(false, null); - } + ITemplate template = fileService.GetTemplate(content.TemplateId.Value); + return template?.Alias ?? string.Empty; + } - /// - /// Gets the ancestors of the content, at a level lesser or equal to a specified level. - /// - /// The content. - /// The level. - /// The ancestors of the content, at a level lesser or equal to the specified level, in down-top order. - /// Does not consider the content itself. Only content that are "high enough" in the tree are returned. - public static IEnumerable Ancestors(this IPublishedContent content, int maxLevel) - { - return content.AncestorsOrSelf(false, n => n.Level <= maxLevel); - } + public static bool IsAllowedTemplate(this IPublishedContent content, IContentTypeService contentTypeService, + WebRoutingSettings webRoutingSettings, int templateId) => + content.IsAllowedTemplate(contentTypeService, + webRoutingSettings.DisableAlternativeTemplates, + webRoutingSettings.ValidateAlternativeTemplates, templateId); - /// - /// Gets the ancestors of the content, of a specified content type. - /// - /// The content. - /// The content type. - /// The ancestors of the content, of the specified content type, in down-top order. - /// Does not consider the content itself. Returns all ancestors, of the specified content type. - public static IEnumerable Ancestors(this IPublishedContent content, string contentTypeAlias) + public static bool IsAllowedTemplate(this IPublishedContent content, IContentTypeService contentTypeService, + bool disableAlternativeTemplates, bool validateAlternativeTemplates, int templateId) + { + if (disableAlternativeTemplates) { - return content.AncestorsOrSelf(false, n => n.ContentType.Alias.InvariantEquals(contentTypeAlias)); + return content.TemplateId == templateId; } - /// - /// Gets the ancestors of the content, of a specified content type. - /// - /// The content type. - /// The content. - /// The ancestors of the content, of the specified content type, in down-top order. - /// Does not consider the content itself. Returns all ancestors, of the specified content type. - public static IEnumerable Ancestors(this IPublishedContent content) - where T : class, IPublishedContent + if (content.TemplateId == templateId || !validateAlternativeTemplates) { - return content.Ancestors().OfType(); + return true; } - /// - /// Gets the ancestors of the content, at a level lesser or equal to a specified level, and of a specified content type. - /// - /// The content type. - /// The content. - /// The level. - /// The ancestors of the content, at a level lesser or equal to the specified level, and of the specified - /// content type, in down-top order. - /// Does not consider the content itself. Only content that are "high enough" in the trees, and of the - /// specified content type, are returned. - public static IEnumerable Ancestors(this IPublishedContent content, int maxLevel) - where T : class, IPublishedContent + IContentType publishedContentContentType = contentTypeService.Get(content.ContentType.Id); + if (publishedContentContentType == null) { - return content.Ancestors(maxLevel).OfType(); + throw new NullReferenceException("No content type returned for published content (contentType='" + + content.ContentType.Id + "')"); } - /// - /// Gets the content and its ancestors. - /// - /// The content. - /// The content and its ancestors, in down-top order. - public static IEnumerable AncestorsOrSelf(this IPublishedContent content) - { - return content.AncestorsOrSelf(true, null); - } + return publishedContentContentType.IsAllowedTemplate(templateId); + } - /// - /// Gets the content and its ancestors, at a level lesser or equal to a specified level. - /// - /// The content. - /// The level. - /// The content and its ancestors, at a level lesser or equal to the specified level, - /// in down-top order. - /// Only content that are "high enough" in the tree are returned. So it may or may not begin - /// with the content itself, depending on its level. - public static IEnumerable AncestorsOrSelf(this IPublishedContent content, int maxLevel) - { - return content.AncestorsOrSelf(true, n => n.Level <= maxLevel); - } + public static bool IsAllowedTemplate(this IPublishedContent content, IFileService fileService, + IContentTypeService contentTypeService, bool disableAlternativeTemplates, bool validateAlternativeTemplates, + string templateAlias) + { + ITemplate template = fileService.GetTemplate(templateAlias); + return template != null && content.IsAllowedTemplate(contentTypeService, disableAlternativeTemplates, + validateAlternativeTemplates, template.Id); + } - /// - /// Gets the content and its ancestors, of a specified content type. - /// - /// The content. - /// The content type. - /// The content and its ancestors, of the specified content type, in down-top order. - /// May or may not begin with the content itself, depending on its content type. - public static IEnumerable AncestorsOrSelf(this IPublishedContent content, string contentTypeAlias) - { - return content.AncestorsOrSelf(true, n => n.ContentType.Alias.InvariantEquals(contentTypeAlias)); - } + #endregion + + #region HasValue, Value, Value + + /// + /// Gets a value indicating whether the content has a value for a property identified by its alias. + /// + /// The content. + /// The published value fallback implementation. + /// The property alias. + /// The variation language. + /// The variation segment. + /// Optional fallback strategy. + /// A value indicating whether the content has a value for the property identified by the alias. + /// Returns true if HasValue is true, or a fallback strategy can provide a value. + public static bool HasValue(this IPublishedContent content, IPublishedValueFallback publishedValueFallback, + string alias, string? culture = null, string? segment = null, Fallback fallback = default) + { + IPublishedProperty property = content.GetProperty(alias); - /// - /// Gets the content and its ancestors, of a specified content type. - /// - /// The content type. - /// The content. - /// The content and its ancestors, of the specified content type, in down-top order. - /// May or may not begin with the content itself, depending on its content type. - public static IEnumerable AncestorsOrSelf(this IPublishedContent content) - where T : class, IPublishedContent + // if we have a property, and it has a value, return that value + if (property != null && property.HasValue(culture, segment)) { - return content.AncestorsOrSelf().OfType(); + return true; } - /// - /// Gets the content and its ancestor, at a lever lesser or equal to a specified level, and of a specified content type. - /// - /// The content type. - /// The content. - /// The level. - /// The content and its ancestors, at a level lesser or equal to the specified level, and of the specified - /// content type, in down-top order. - /// May or may not begin with the content itself, depending on its level and content type. - public static IEnumerable AncestorsOrSelf(this IPublishedContent content, int maxLevel) - where T : class, IPublishedContent - { - return content.AncestorsOrSelf(maxLevel).OfType(); - } + // else let fallback try to get a value + return publishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, null, out _, out _); + } - /// - /// Gets the ancestor of the content, ie its parent. - /// - /// The content. - /// The ancestor of the content. - /// This method is here for consistency purposes but does not make much sense. - public static IPublishedContent? Ancestor(this IPublishedContent content) - { - return content.Parent; - } + /// + /// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value. + /// + /// The content. + /// The published value fallback implementation. + /// The property alias. + /// The variation language. + /// The variation segment. + /// Optional fallback strategy. + /// The default value. + /// The value of the content's property identified by the alias, if it exists, otherwise a default value. + public static object? Value(this IPublishedContent content, IPublishedValueFallback publishedValueFallback, + string alias, string? culture = null, string? segment = null, Fallback fallback = default, + object? defaultValue = default) + { + IPublishedProperty property = content.GetProperty(alias); - /// - /// Gets the nearest ancestor of the content, at a lever lesser or equal to a specified level. - /// - /// The content. - /// The level. - /// The nearest (in down-top order) ancestor of the content, at a level lesser or equal to the specified level. - /// Does not consider the content itself. May return null. - public static IPublishedContent? Ancestor(this IPublishedContent content, int maxLevel) + // if we have a property, and it has a value, return that value + if (property != null && property.HasValue(culture, segment)) { - return content.EnumerateAncestors(false).FirstOrDefault(x => x.Level <= maxLevel); + return property.GetValue(culture, segment); } - /// - /// Gets the nearest ancestor of the content, of a specified content type. - /// - /// The content. - /// The content type alias. - /// The nearest (in down-top order) ancestor of the content, of the specified content type. - /// Does not consider the content itself. May return null. - public static IPublishedContent? Ancestor(this IPublishedContent content, string contentTypeAlias) + // else let fallback try to get a value + if (publishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value, + out property)) { - return content.EnumerateAncestors(false).FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias)); + return value; } - /// - /// Gets the nearest ancestor of the content, of a specified content type. - /// - /// The content type. - /// The content. - /// The nearest (in down-top order) ancestor of the content, of the specified content type. - /// Does not consider the content itself. May return null. - public static T? Ancestor(this IPublishedContent content) - where T : class, IPublishedContent - { - return content.Ancestors().FirstOrDefault(); - } + // else... if we have a property, at least let the converter return its own + // vision of 'no value' (could be an empty enumerable) + return property?.GetValue(culture, segment); + } - /// - /// Gets the nearest ancestor of the content, at the specified level and of the specified content type. - /// - /// The content type. - /// The content. - /// The level. - /// The ancestor of the content, at the specified level and of the specified content type. - /// Does not consider the content itself. If the ancestor at the specified level is - /// not of the specified type, returns null. - public static T? Ancestor(this IPublishedContent content, int maxLevel) - where T : class, IPublishedContent - { - return content.Ancestors(maxLevel).FirstOrDefault(); - } + /// + /// Gets the value of a content's property identified by its alias, converted to a specified type. + /// + /// The target property type. + /// The content. + /// The published value fallback implementation. + /// The property alias. + /// The variation language. + /// The variation segment. + /// Optional fallback strategy. + /// The default value. + /// The value of the content's property identified by the alias, converted to the specified type. + public static T? Value(this IPublishedContent content, IPublishedValueFallback publishedValueFallback, + string alias, string? culture = null, string? segment = null, Fallback fallback = default, + T? defaultValue = default) + { + IPublishedProperty property = content.GetProperty(alias); - /// - /// Gets the content or its nearest ancestor. - /// - /// The content. - /// The content. - /// This method is here for consistency purposes but does not make much sense. - public static IPublishedContent AncestorOrSelf(this IPublishedContent content) + // if we have a property, and it has a value, return that value + if (property != null && property.HasValue(culture, segment)) { - return content; + return property.Value(publishedValueFallback, culture, segment); } - /// - /// Gets the content or its nearest ancestor, at a lever lesser or equal to a specified level. - /// - /// The content. - /// The level. - /// The content or its nearest (in down-top order) ancestor, at a level lesser or equal to the specified level. - /// May or may not return the content itself depending on its level. May return null. - public static IPublishedContent? AncestorOrSelf(this IPublishedContent content, int maxLevel) + // else let fallback try to get a value + if (publishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out T value, + out property)) { - return content.EnumerateAncestors(true).FirstOrDefault(x => x.Level <= maxLevel); + return value; } - /// - /// Gets the content or its nearest ancestor, of a specified content type. - /// - /// The content. - /// The content type. - /// The content or its nearest (in down-top order) ancestor, of the specified content type. - /// May or may not return the content itself depending on its content type. May return null. - public static IPublishedContent? AncestorOrSelf(this IPublishedContent content, string contentTypeAlias) - { - return content.EnumerateAncestors(true).FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias)); - } + // else... if we have a property, at least let the converter return its own + // vision of 'no value' (could be an empty enumerable) - otherwise, default + return property == null ? default : property.Value(publishedValueFallback, culture, segment); + } - /// - /// Gets the content or its nearest ancestor, of a specified content type. - /// - /// The content type. - /// The content. - /// The content or its nearest (in down-top order) ancestor, of the specified content type. - /// May or may not return the content itself depending on its content type. May return null. - public static T? AncestorOrSelf(this IPublishedContent content) - where T : class, IPublishedContent + #endregion + + #region IsSomething: misc. + + /// + /// Determines whether the specified content is a specified content type. + /// + /// The content to determine content type of. + /// The alias of the content type to test against. + /// True if the content is of the specified content type; otherwise false. + public static bool IsDocumentType(this IPublishedContent content, string docTypeAlias) => + content.ContentType.Alias.InvariantEquals(docTypeAlias); + + /// + /// Determines whether the specified content is a specified content type or it's derived types. + /// + /// The content to determine content type of. + /// The alias of the content type to test against. + /// + /// When true, recurses up the content type tree to check inheritance; when false just calls + /// IsDocumentType(this IPublishedContent content, string docTypeAlias). + /// + /// True if the content is of the specified content type or a derived content type; otherwise false. + public static bool IsDocumentType(this IPublishedContent content, string docTypeAlias, bool recursive) + { + if (content.IsDocumentType(docTypeAlias)) { - return content.AncestorsOrSelf().FirstOrDefault(); + return true; } - /// - /// Gets the content or its nearest ancestor, at a lever lesser or equal to a specified level, and of a specified content type. - /// - /// The content type. - /// The content. - /// The level. - /// - public static T? AncestorOrSelf(this IPublishedContent content, int maxLevel) - where T : class, IPublishedContent - { - return content.AncestorsOrSelf(maxLevel).FirstOrDefault(); - } + return recursive && content.IsComposedOf(docTypeAlias); + } - public static IEnumerable AncestorsOrSelf(this IPublishedContent content, bool orSelf, Func? func) - { - var ancestorsOrSelf = content.EnumerateAncestors(orSelf); - return func == null ? ancestorsOrSelf : ancestorsOrSelf.Where(func); - } + #endregion + + #region IsSomething: equality + + public static bool IsEqual(this IPublishedContent content, IPublishedContent other) => content.Id == other.Id; + + public static bool IsNotEqual(this IPublishedContent content, IPublishedContent other) => + content.IsEqual(other) == false; + + #endregion + + #region IsSomething: ancestors and descendants + + public static bool IsDescendant(this IPublishedContent content, IPublishedContent other) => + other.Level < content.Level && content.Path.InvariantStartsWith(other.Path.EnsureEndsWith(',')); + + public static bool IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other) => + content.Path.InvariantEquals(other.Path) || content.IsDescendant(other); + + public static bool IsAncestor(this IPublishedContent content, IPublishedContent other) => + content.Level < other.Level && other.Path.InvariantStartsWith(content.Path.EnsureEndsWith(',')); + + public static bool IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other) => + other.Path.InvariantEquals(content.Path) || content.IsAncestor(other); + + #endregion + + #region Axes: ancestors, ancestors-or-self + + // as per XPath 1.0 specs �2.2, + // - the ancestor axis contains the ancestors of the context node; the ancestors of the context node consist + // of the parent of context node and the parent's parent and so on; thus, the ancestor axis will always + // include the root node, unless the context node is the root node. + // - the ancestor-or-self axis contains the context node and the ancestors of the context node; thus, + // the ancestor axis will always include the root node. + // + // as per XPath 2.0 specs �3.2.1.1, + // - the ancestor axis is defined as the transitive closure of the parent axis; it contains the ancestors + // of the context node (the parent, the parent of the parent, and so on) - The ancestor axis includes the + // root node of the tree in which the context node is found, unless the context node is the root node. + // - the ancestor-or-self axis contains the context node and the ancestors of the context node; thus, + // the ancestor-or-self axis will always include the root node. + // + // the ancestor and ancestor-or-self axis are reverse axes ie they contain the context node or nodes that + // are before the context node in document order. + // + // document order is defined by �2.4.1 as: + // - the root node is the first node. + // - every node occurs before all of its children and descendants. + // - the relative order of siblings is the order in which they occur in the children property of their parent node. + // - children and descendants occur before following siblings. + + /// + /// Gets the ancestors of the content. + /// + /// The content. + /// The ancestors of the content, in down-top order. + /// Does not consider the content itself. + public static IEnumerable Ancestors(this IPublishedContent content) => + content.AncestorsOrSelf(false, null); + + /// + /// Gets the ancestors of the content, at a level lesser or equal to a specified level. + /// + /// The content. + /// The level. + /// The ancestors of the content, at a level lesser or equal to the specified level, in down-top order. + /// Does not consider the content itself. Only content that are "high enough" in the tree are returned. + public static IEnumerable Ancestors(this IPublishedContent content, int maxLevel) => + content.AncestorsOrSelf(false, n => n.Level <= maxLevel); + + /// + /// Gets the ancestors of the content, of a specified content type. + /// + /// The content. + /// The content type. + /// The ancestors of the content, of the specified content type, in down-top order. + /// Does not consider the content itself. Returns all ancestors, of the specified content type. + public static IEnumerable Ancestors(this IPublishedContent content, string contentTypeAlias) => + content.AncestorsOrSelf(false, n => n.ContentType.Alias.InvariantEquals(contentTypeAlias)); + + /// + /// Gets the ancestors of the content, of a specified content type. + /// + /// The content type. + /// The content. + /// The ancestors of the content, of the specified content type, in down-top order. + /// Does not consider the content itself. Returns all ancestors, of the specified content type. + public static IEnumerable Ancestors(this IPublishedContent content) + where T : class, IPublishedContent => + content.Ancestors().OfType(); + + /// + /// Gets the ancestors of the content, at a level lesser or equal to a specified level, and of a specified content + /// type. + /// + /// The content type. + /// The content. + /// The level. + /// + /// The ancestors of the content, at a level lesser or equal to the specified level, and of the specified + /// content type, in down-top order. + /// + /// + /// Does not consider the content itself. Only content that are "high enough" in the trees, and of the + /// specified content type, are returned. + /// + public static IEnumerable Ancestors(this IPublishedContent content, int maxLevel) + where T : class, IPublishedContent => + content.Ancestors(maxLevel).OfType(); + + /// + /// Gets the content and its ancestors. + /// + /// The content. + /// The content and its ancestors, in down-top order. + public static IEnumerable AncestorsOrSelf(this IPublishedContent content) => + content.AncestorsOrSelf(true, null); + + /// + /// Gets the content and its ancestors, at a level lesser or equal to a specified level. + /// + /// The content. + /// The level. + /// + /// The content and its ancestors, at a level lesser or equal to the specified level, + /// in down-top order. + /// + /// + /// Only content that are "high enough" in the tree are returned. So it may or may not begin + /// with the content itself, depending on its level. + /// + public static IEnumerable AncestorsOrSelf(this IPublishedContent content, int maxLevel) => + content.AncestorsOrSelf(true, n => n.Level <= maxLevel); + + /// + /// Gets the content and its ancestors, of a specified content type. + /// + /// The content. + /// The content type. + /// The content and its ancestors, of the specified content type, in down-top order. + /// May or may not begin with the content itself, depending on its content type. + public static IEnumerable + AncestorsOrSelf(this IPublishedContent content, string contentTypeAlias) => + content.AncestorsOrSelf(true, n => n.ContentType.Alias.InvariantEquals(contentTypeAlias)); + + /// + /// Gets the content and its ancestors, of a specified content type. + /// + /// The content type. + /// The content. + /// The content and its ancestors, of the specified content type, in down-top order. + /// May or may not begin with the content itself, depending on its content type. + public static IEnumerable AncestorsOrSelf(this IPublishedContent content) + where T : class, IPublishedContent => + content.AncestorsOrSelf().OfType(); + + /// + /// Gets the content and its ancestor, at a lever lesser or equal to a specified level, and of a specified content + /// type. + /// + /// The content type. + /// The content. + /// The level. + /// + /// The content and its ancestors, at a level lesser or equal to the specified level, and of the specified + /// content type, in down-top order. + /// + /// May or may not begin with the content itself, depending on its level and content type. + public static IEnumerable AncestorsOrSelf(this IPublishedContent content, int maxLevel) + where T : class, IPublishedContent => + content.AncestorsOrSelf(maxLevel).OfType(); + + /// + /// Gets the ancestor of the content, ie its parent. + /// + /// The content. + /// The ancestor of the content. + /// This method is here for consistency purposes but does not make much sense. + public static IPublishedContent? Ancestor(this IPublishedContent content) => content.Parent; + + /// + /// Gets the nearest ancestor of the content, at a lever lesser or equal to a specified level. + /// + /// The content. + /// The level. + /// The nearest (in down-top order) ancestor of the content, at a level lesser or equal to the specified level. + /// Does not consider the content itself. May return null. + public static IPublishedContent? Ancestor(this IPublishedContent content, int maxLevel) => + content.EnumerateAncestors(false).FirstOrDefault(x => x.Level <= maxLevel); + + /// + /// Gets the nearest ancestor of the content, of a specified content type. + /// + /// The content. + /// The content type alias. + /// The nearest (in down-top order) ancestor of the content, of the specified content type. + /// Does not consider the content itself. May return null. + public static IPublishedContent? Ancestor(this IPublishedContent content, string contentTypeAlias) => content + .EnumerateAncestors(false).FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias)); + + /// + /// Gets the nearest ancestor of the content, of a specified content type. + /// + /// The content type. + /// The content. + /// The nearest (in down-top order) ancestor of the content, of the specified content type. + /// Does not consider the content itself. May return null. + public static T? Ancestor(this IPublishedContent content) + where T : class, IPublishedContent => + content.Ancestors().FirstOrDefault(); + + /// + /// Gets the nearest ancestor of the content, at the specified level and of the specified content type. + /// + /// The content type. + /// The content. + /// The level. + /// The ancestor of the content, at the specified level and of the specified content type. + /// + /// Does not consider the content itself. If the ancestor at the specified level is + /// not of the specified type, returns null. + /// + public static T? Ancestor(this IPublishedContent content, int maxLevel) + where T : class, IPublishedContent => + content.Ancestors(maxLevel).FirstOrDefault(); + + /// + /// Gets the content or its nearest ancestor. + /// + /// The content. + /// The content. + /// This method is here for consistency purposes but does not make much sense. + public static IPublishedContent AncestorOrSelf(this IPublishedContent content) => content; + + /// + /// Gets the content or its nearest ancestor, at a lever lesser or equal to a specified level. + /// + /// The content. + /// The level. + /// The content or its nearest (in down-top order) ancestor, at a level lesser or equal to the specified level. + /// May or may not return the content itself depending on its level. May return null. + public static IPublishedContent? AncestorOrSelf(this IPublishedContent content, int maxLevel) => + content.EnumerateAncestors(true).FirstOrDefault(x => x.Level <= maxLevel); + + /// + /// Gets the content or its nearest ancestor, of a specified content type. + /// + /// The content. + /// The content type. + /// The content or its nearest (in down-top order) ancestor, of the specified content type. + /// May or may not return the content itself depending on its content type. May return null. + public static IPublishedContent? AncestorOrSelf(this IPublishedContent content, string contentTypeAlias) => content + .EnumerateAncestors(true).FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias)); + + /// + /// Gets the content or its nearest ancestor, of a specified content type. + /// + /// The content type. + /// The content. + /// The content or its nearest (in down-top order) ancestor, of the specified content type. + /// May or may not return the content itself depending on its content type. May return null. + public static T? AncestorOrSelf(this IPublishedContent content) + where T : class, IPublishedContent => + content.AncestorsOrSelf().FirstOrDefault(); + + /// + /// Gets the content or its nearest ancestor, at a lever lesser or equal to a specified level, and of a specified + /// content type. + /// + /// The content type. + /// The content. + /// The level. + /// + public static T? AncestorOrSelf(this IPublishedContent content, int maxLevel) + where T : class, IPublishedContent => + content.AncestorsOrSelf(maxLevel).FirstOrDefault(); + + public static IEnumerable AncestorsOrSelf(this IPublishedContent content, bool orSelf, + Func? func) + { + IEnumerable ancestorsOrSelf = content.EnumerateAncestors(orSelf); + return func == null ? ancestorsOrSelf : ancestorsOrSelf.Where(func); + } - /// - /// Enumerates ancestors of the content, bottom-up. - /// - /// The content. - /// Indicates whether the content should be included. - /// Enumerates bottom-up ie walking up the tree (parent, grand-parent, etc). - internal static IEnumerable EnumerateAncestors(this IPublishedContent? content, bool orSelf) + /// + /// Enumerates ancestors of the content, bottom-up. + /// + /// The content. + /// Indicates whether the content should be included. + /// Enumerates bottom-up ie walking up the tree (parent, grand-parent, etc). + internal static IEnumerable EnumerateAncestors(this IPublishedContent? content, bool orSelf) + { + if (content == null) { - if (content == null) throw new ArgumentNullException(nameof(content)); - if (orSelf) yield return content; - while ((content = content.Parent) != null) - yield return content; + throw new ArgumentNullException(nameof(content)); } - #endregion - - #region Axes: breadcrumbs - - /// - /// Gets the breadcrumbs (ancestors and self, top to bottom) for the specified . - /// - /// The content. - /// Indicates whether the specified content should be included. - /// - /// The breadcrumbs (ancestors and self, top to bottom) for the specified . - /// - public static IEnumerable Breadcrumbs(this IPublishedContent content, bool andSelf = true) + if (orSelf) { - return content.AncestorsOrSelf(andSelf, null).Reverse(); + yield return content; } - /// - /// Gets the breadcrumbs (ancestors and self, top to bottom) for the specified at a level higher or equal to . - /// - /// The content. - /// The minimum level. - /// Indicates whether the specified content should be included. - /// - /// The breadcrumbs (ancestors and self, top to bottom) for the specified at a level higher or equal to . - /// - public static IEnumerable Breadcrumbs(this IPublishedContent content, int minLevel, bool andSelf = true) + while ((content = content.Parent) != null) { - return content.AncestorsOrSelf(andSelf, n => n.Level >= minLevel).Reverse(); + yield return content; } + } - /// - /// Gets the breadcrumbs (ancestors and self, top to bottom) for the specified at a level higher or equal to the specified root content type . - /// - /// The root content type. - /// The content. - /// Indicates whether the specified content should be included. - /// - /// The breadcrumbs (ancestors and self, top to bottom) for the specified at a level higher or equal to the specified root content type . - /// - public static IEnumerable Breadcrumbs(this IPublishedContent content, bool andSelf = true) - where T : class, IPublishedContent + #endregion + + #region Axes: breadcrumbs + + /// + /// Gets the breadcrumbs (ancestors and self, top to bottom) for the specified . + /// + /// The content. + /// Indicates whether the specified content should be included. + /// + /// The breadcrumbs (ancestors and self, top to bottom) for the specified . + /// + public static IEnumerable Breadcrumbs(this IPublishedContent content, bool andSelf = true) => + content.AncestorsOrSelf(andSelf, null).Reverse(); + + /// + /// Gets the breadcrumbs (ancestors and self, top to bottom) for the specified at a level + /// higher or equal to . + /// + /// The content. + /// The minimum level. + /// Indicates whether the specified content should be included. + /// + /// The breadcrumbs (ancestors and self, top to bottom) for the specified at a level higher + /// or equal to . + /// + public static IEnumerable Breadcrumbs(this IPublishedContent content, int minLevel, + bool andSelf = true) => content.AncestorsOrSelf(andSelf, n => n.Level >= minLevel).Reverse(); + + /// + /// Gets the breadcrumbs (ancestors and self, top to bottom) for the specified at a level + /// higher or equal to the specified root content type . + /// + /// The root content type. + /// The content. + /// Indicates whether the specified content should be included. + /// + /// The breadcrumbs (ancestors and self, top to bottom) for the specified at a level higher + /// or equal to the specified root content type . + /// + public static IEnumerable Breadcrumbs(this IPublishedContent content, bool andSelf = true) + where T : class, IPublishedContent + { + static IEnumerable TakeUntil(IEnumerable source, + Func predicate) { - static IEnumerable TakeUntil(IEnumerable source, Func predicate) + foreach (IPublishedContent item in source) { - foreach (var item in source) + yield return item; + if (predicate(item)) { - yield return item; - if (predicate(item)) - { - yield break; - } + yield break; } } - - return TakeUntil(content.AncestorsOrSelf(andSelf, null), n => n is T).Reverse(); - } - - #endregion - - #region Axes: descendants, descendants-or-self - - /// - /// Returns all DescendantsOrSelf of all content referenced - /// - /// - /// Variation context accessor. - /// - /// The specific culture to filter for. If null is used the current culture is used. (Default is null) - /// - /// - /// This can be useful in order to return all nodes in an entire site by a type when combined with TypedContentAtRoot - /// - public static IEnumerable DescendantsOrSelfOfType(this IEnumerable parentNodes, IVariationContextAccessor variationContextAccessor, string docTypeAlias, string? culture = null) - { - return parentNodes.SelectMany(x => x.DescendantsOrSelfOfType(variationContextAccessor, docTypeAlias, culture)); - } - - /// - /// Returns all DescendantsOrSelf of all content referenced - /// - /// - /// Variation context accessor. - /// The specific culture to filter for. If null is used the current culture is used. (Default is null) - /// - /// - /// This can be useful in order to return all nodes in an entire site by a type when combined with TypedContentAtRoot - /// - public static IEnumerable DescendantsOrSelf(this IEnumerable parentNodes, IVariationContextAccessor variationContextAccessor, string? culture = null) - where T : class, IPublishedContent - { - return parentNodes.SelectMany(x => x.DescendantsOrSelf(variationContextAccessor, culture)); - } - - - // as per XPath 1.0 specs �2.2, - // - the descendant axis contains the descendants of the context node; a descendant is a child or a child of a child and so on; thus - // the descendant axis never contains attribute or namespace nodes. - // - the descendant-or-self axis contains the context node and the descendants of the context node. - // - // as per XPath 2.0 specs �3.2.1.1, - // - the descendant axis is defined as the transitive closure of the child axis; it contains the descendants of the context node (the - // children, the children of the children, and so on). - // - the descendant-or-self axis contains the context node and the descendants of the context node. - // - // the descendant and descendant-or-self axis are forward axes ie they contain the context node or nodes that are after the context - // node in document order. - // - // document order is defined by �2.4.1 as: - // - the root node is the first node. - // - every node occurs before all of its children and descendants. - // - the relative order of siblings is the order in which they occur in the children property of their parent node. - // - children and descendants occur before following siblings. - - public static IEnumerable Descendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) - { - return content.DescendantsOrSelf(variationContextAccessor, false, null, culture); - } - - public static IEnumerable Descendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) - { - return content.DescendantsOrSelf(variationContextAccessor, false, p => p.Level >= level, culture); - } - - public static IEnumerable DescendantsOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) - { - return content.DescendantsOrSelf(variationContextAccessor, false, p => p.ContentType.Alias.InvariantEquals(contentTypeAlias), culture); - } - - public static IEnumerable Descendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) - where T : class, IPublishedContent - { - return content.Descendants(variationContextAccessor, culture).OfType(); - } - - public static IEnumerable Descendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) - where T : class, IPublishedContent - { - return content.Descendants(variationContextAccessor, level, culture).OfType(); - } - - public static IEnumerable DescendantsOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) - { - return content.DescendantsOrSelf(variationContextAccessor, true, null, culture); - } - - public static IEnumerable DescendantsOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) - { - return content.DescendantsOrSelf(variationContextAccessor, true, p => p.Level >= level, culture); } - public static IEnumerable DescendantsOrSelfOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string ?culture = null) - { - return content.DescendantsOrSelf(variationContextAccessor, true, p => p.ContentType.Alias.InvariantEquals(contentTypeAlias), culture); - } - - public static IEnumerable DescendantsOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) - where T : class, IPublishedContent - { - return content.DescendantsOrSelf(variationContextAccessor, culture).OfType(); - } - - public static IEnumerable DescendantsOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) - where T : class, IPublishedContent - { - return content.DescendantsOrSelf(variationContextAccessor, level, culture).OfType(); - } - - public static IPublishedContent? Descendant(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) - { - return content.Children(variationContextAccessor, culture)?.FirstOrDefault(); - } - - public static IPublishedContent? Descendant(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) - { - return content.EnumerateDescendants(variationContextAccessor, false, culture).FirstOrDefault(x => x.Level == level); - } - - public static IPublishedContent? DescendantOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) - { - return content.EnumerateDescendants(variationContextAccessor, false, culture).FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias)); - } - - public static T? Descendant(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) - where T : class, IPublishedContent - { - return content.EnumerateDescendants(variationContextAccessor, false, culture).FirstOrDefault(x => x is T) as T; - } - - public static T? Descendant(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) - where T : class, IPublishedContent - { - return content.Descendant(variationContextAccessor, level, culture) as T; - } - - public static IPublishedContent DescendantOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) - { - return content; - } - - public static IPublishedContent? DescendantOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) - { - return content.EnumerateDescendants(variationContextAccessor, true, culture).FirstOrDefault(x => x.Level == level); - } - - public static IPublishedContent? DescendantOrSelfOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) - { - return content.EnumerateDescendants(variationContextAccessor, true, culture).FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias)); - } - - public static T? DescendantOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) - where T : class, IPublishedContent - { - return content.EnumerateDescendants(variationContextAccessor, true, culture).FirstOrDefault(x => x is T) as T; - } + return TakeUntil(content.AncestorsOrSelf(andSelf, null), n => n is T).Reverse(); + } - public static T? DescendantOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) - where T : class, IPublishedContent + #endregion + + #region Axes: descendants, descendants-or-self + + /// + /// Returns all DescendantsOrSelf of all content referenced + /// + /// + /// Variation context accessor. + /// + /// + /// The specific culture to filter for. If null is used the current culture is used. (Default is + /// null) + /// + /// + /// + /// This can be useful in order to return all nodes in an entire site by a type when combined with TypedContentAtRoot + /// + public static IEnumerable DescendantsOrSelfOfType( + this IEnumerable parentNodes, IVariationContextAccessor variationContextAccessor, + string docTypeAlias, string? culture = null) => parentNodes.SelectMany(x => + x.DescendantsOrSelfOfType(variationContextAccessor, docTypeAlias, culture)); + + /// + /// Returns all DescendantsOrSelf of all content referenced + /// + /// + /// Variation context accessor. + /// + /// The specific culture to filter for. If null is used the current culture is used. (Default is + /// null) + /// + /// + /// + /// This can be useful in order to return all nodes in an entire site by a type when combined with TypedContentAtRoot + /// + public static IEnumerable DescendantsOrSelf(this IEnumerable parentNodes, + IVariationContextAccessor variationContextAccessor, string? culture = null) + where T : class, IPublishedContent => + parentNodes.SelectMany(x => x.DescendantsOrSelf(variationContextAccessor, culture)); + + + // as per XPath 1.0 specs �2.2, + // - the descendant axis contains the descendants of the context node; a descendant is a child or a child of a child and so on; thus + // the descendant axis never contains attribute or namespace nodes. + // - the descendant-or-self axis contains the context node and the descendants of the context node. + // + // as per XPath 2.0 specs �3.2.1.1, + // - the descendant axis is defined as the transitive closure of the child axis; it contains the descendants of the context node (the + // children, the children of the children, and so on). + // - the descendant-or-self axis contains the context node and the descendants of the context node. + // + // the descendant and descendant-or-self axis are forward axes ie they contain the context node or nodes that are after the context + // node in document order. + // + // document order is defined by �2.4.1 as: + // - the root node is the first node. + // - every node occurs before all of its children and descendants. + // - the relative order of siblings is the order in which they occur in the children property of their parent node. + // - children and descendants occur before following siblings. + + public static IEnumerable Descendants(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, string? culture = null) => + content.DescendantsOrSelf(variationContextAccessor, false, null, culture); + + public static IEnumerable Descendants(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, int level, string? culture = null) => + content.DescendantsOrSelf(variationContextAccessor, false, p => p.Level >= level, culture); + + public static IEnumerable DescendantsOfType(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) => + content.DescendantsOrSelf(variationContextAccessor, false, + p => p.ContentType.Alias.InvariantEquals(contentTypeAlias), culture); + + public static IEnumerable Descendants(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, string? culture = null) + where T : class, IPublishedContent => + content.Descendants(variationContextAccessor, culture).OfType(); + + public static IEnumerable Descendants(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, int level, string? culture = null) + where T : class, IPublishedContent => + content.Descendants(variationContextAccessor, level, culture).OfType(); + + public static IEnumerable DescendantsOrSelf(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, string? culture = null) => + content.DescendantsOrSelf(variationContextAccessor, true, null, culture); + + public static IEnumerable DescendantsOrSelf(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, int level, string? culture = null) => + content.DescendantsOrSelf(variationContextAccessor, true, p => p.Level >= level, culture); + + public static IEnumerable DescendantsOrSelfOfType(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) => + content.DescendantsOrSelf(variationContextAccessor, true, + p => p.ContentType.Alias.InvariantEquals(contentTypeAlias), culture); + + public static IEnumerable DescendantsOrSelf(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, string? culture = null) + where T : class, IPublishedContent => + content.DescendantsOrSelf(variationContextAccessor, culture).OfType(); + + public static IEnumerable DescendantsOrSelf(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, int level, string? culture = null) + where T : class, IPublishedContent => + content.DescendantsOrSelf(variationContextAccessor, level, culture).OfType(); + + public static IPublishedContent? Descendant(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, string? culture = null) => + content.Children(variationContextAccessor, culture)?.FirstOrDefault(); + + public static IPublishedContent? Descendant(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, int level, string? culture = null) => content + .EnumerateDescendants(variationContextAccessor, false, culture).FirstOrDefault(x => x.Level == level); + + public static IPublishedContent? DescendantOfType(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) => content + .EnumerateDescendants(variationContextAccessor, false, culture) + .FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias)); + + public static T? Descendant(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, + string? culture = null) + where T : class, IPublishedContent => + content.EnumerateDescendants(variationContextAccessor, false, culture).FirstOrDefault(x => x is T) as T; + + public static T? Descendant(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, + int level, string? culture = null) + where T : class, IPublishedContent => + content.Descendant(variationContextAccessor, level, culture) as T; + + public static IPublishedContent DescendantOrSelf(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, string? culture = null) => content; + + public static IPublishedContent? DescendantOrSelf(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, int level, string? culture = null) => content + .EnumerateDescendants(variationContextAccessor, true, culture).FirstOrDefault(x => x.Level == level); + + public static IPublishedContent? DescendantOrSelfOfType(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) => content + .EnumerateDescendants(variationContextAccessor, true, culture) + .FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias)); + + public static T? DescendantOrSelf(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, string? culture = null) + where T : class, IPublishedContent => + content.EnumerateDescendants(variationContextAccessor, true, culture).FirstOrDefault(x => x is T) as T; + + public static T? DescendantOrSelf(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, int level, string? culture = null) + where T : class, IPublishedContent => + content.DescendantOrSelf(variationContextAccessor, level, culture) as T; + + internal static IEnumerable DescendantsOrSelf(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, bool orSelf, Func? func, + string? culture = null) => content.EnumerateDescendants(variationContextAccessor, orSelf, culture) + .Where(x => func == null || func(x)); + + internal static IEnumerable EnumerateDescendants(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, bool orSelf, string? culture = null) + { + if (content == null) { - return content.DescendantOrSelf(variationContextAccessor, level, culture) as T; + throw new ArgumentNullException(nameof(content)); } - internal static IEnumerable DescendantsOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, bool orSelf, Func? func, string? culture = null) + if (orSelf) { - return content.EnumerateDescendants(variationContextAccessor, orSelf, culture).Where(x => func == null || func(x)); + yield return content; } - internal static IEnumerable EnumerateDescendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, bool orSelf, string? culture = null) + IEnumerable children = content.Children(variationContextAccessor, culture); + if (children is not null) { - if (content == null) throw new ArgumentNullException(nameof(content)); - if (orSelf) yield return content; - - var children = content.Children(variationContextAccessor, culture); - if (children is not null) + foreach (IPublishedContent desc in children.SelectMany(x => + x.EnumerateDescendants(variationContextAccessor, culture))) { - foreach (var desc in children.SelectMany(x => x.EnumerateDescendants(variationContextAccessor, culture))) - yield return desc; + yield return desc; } } + } - internal static IEnumerable EnumerateDescendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) + internal static IEnumerable EnumerateDescendants(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, string? culture = null) + { + yield return content; + IEnumerable children = content.Children(variationContextAccessor, culture); + if (children is not null) { - yield return content; - var children = content.Children(variationContextAccessor, culture); - if (children is not null) + foreach (IPublishedContent desc in children.SelectMany(x => + x.EnumerateDescendants(variationContextAccessor, culture))) { - foreach (var desc in children.SelectMany(x => x.EnumerateDescendants(variationContextAccessor, culture))) - yield return desc; + yield return desc; } } + } - #endregion - - #region Axes: children - - /// - /// Gets the children of the content item. - /// - /// The content item. - /// - /// - /// The specific culture to get the URL children for. Default is null which will use the current culture in - /// - /// - /// Gets children that are available for the specified culture. - /// Children are sorted by their sortOrder. - /// - /// For culture, - /// if null is used the current culture is used. - /// If an empty string is used only invariant children are returned. - /// If "*" is used all children are returned. - /// - /// - /// If a variant culture is specified or there is a current culture in the then the Children returned - /// will include both the variant children matching the culture AND the invariant children because the invariant children flow with the current culture. - /// However, if an empty string is specified only invariant children are returned. - /// - /// - public static IEnumerable? Children(this IPublishedContent content, IVariationContextAccessor? variationContextAccessor, string? culture = null) - { - // handle context culture for variant - if (culture == null) - culture = variationContextAccessor?.VariationContext?.Culture ?? ""; - - var children = content.ChildrenForAllCultures; - return culture == "*" - ? children - : children?.Where(x => x.IsInvariantOrHasCulture(culture)); - } - - /// - /// Gets the children of the content, filtered by a predicate. - /// - /// The content. - /// Published snapshot instance - /// The predicate. - /// The specific culture to filter for. If null is used the current culture is used. (Default is null) - /// The children of the content, filtered by the predicate. - /// - /// Children are sorted by their sortOrder. - /// - public static IEnumerable? Children(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, Func predicate, string? culture = null) - { - return content.Children(variationContextAccessor, culture)?.Where(predicate); - } - - /// - /// Gets the children of the content, of any of the specified types. - /// - /// The content. - /// Published snapshot instance - /// The specific culture to filter for. If null is used the current culture is used. (Default is null) - /// The content type alias. - /// The children of the content, of any of the specified types. - public static IEnumerable? ChildrenOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? contentTypeAlias, string? culture = null) - { - return content.Children(variationContextAccessor, x => x.ContentType.Alias.InvariantEquals(contentTypeAlias), culture); - } - - /// - /// Gets the children of the content, of a given content type. - /// - /// The content type. - /// The content. - /// Published snapshot instance - /// The specific culture to filter for. If null is used the current culture is used. (Default is null) - /// The children of content, of the given content type. - /// - /// Children are sorted by their sortOrder. - /// - public static IEnumerable? Children(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) - where T : class, IPublishedContent - { - return content.Children(variationContextAccessor, culture)?.OfType(); - } - - public static IPublishedContent? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) - { - return content.Children(variationContextAccessor, culture)?.FirstOrDefault(); - } - - /// - /// Gets the first child of the content, of a given content type. - /// - public static IPublishedContent? FirstChildOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) - { - return content.ChildrenOfType(variationContextAccessor, contentTypeAlias, culture)?.FirstOrDefault(); - } - - public static IPublishedContent? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, Func predicate, string? culture = null) - { - return content.Children(variationContextAccessor, predicate, culture)?.FirstOrDefault(); - } - - public static IPublishedContent? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, Guid uniqueId, string? culture = null) - { - return content.Children(variationContextAccessor, x => x.Key == uniqueId, culture)?.FirstOrDefault(); - } - - public static T? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) - where T : class, IPublishedContent - { - return content.Children(variationContextAccessor, culture)?.FirstOrDefault(); - } - - public static T? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, Func predicate, string? culture = null) - where T : class, IPublishedContent - { - return content.Children(variationContextAccessor, culture)?.FirstOrDefault(predicate); - } - - #endregion - - #region Axes: parent - - // Parent is native - - /// - /// Gets the parent of the content, of a given content type. - /// - /// The content type. - /// The content. - /// The parent of content, of the given content type, else null. - public static T? Parent(this IPublishedContent content) - where T : class, IPublishedContent - { - if (content == null) throw new ArgumentNullException(nameof(content)); - return content.Parent as T; - } - - #endregion - - #region Axes: siblings - - /// - /// Gets the siblings of the content. - /// - /// The content. - /// Published snapshot instance - /// Variation context accessor. - /// The specific culture to filter for. If null is used the current culture is used. (Default is null) - /// The siblings of the content. - /// - /// Note that in V7 this method also return the content node self. - /// - public static IEnumerable? Siblings(this IPublishedContent content, IPublishedSnapshot? publishedSnapshot, IVariationContextAccessor variationContextAccessor, string? culture = null) - { - return SiblingsAndSelf(content, publishedSnapshot, variationContextAccessor, culture)?.Where(x => x.Id != content.Id); - } - - /// - /// Gets the siblings of the content, of a given content type. - /// - /// The content. - /// Published snapshot instance - /// Variation context accessor. - /// The specific culture to filter for. If null is used the current culture is used. (Default is null) - /// The content type alias. - /// The siblings of the content, of the given content type. - /// - /// Note that in V7 this method also return the content node self. - /// - public static IEnumerable? SiblingsOfType(this IPublishedContent content, IPublishedSnapshot? publishedSnapshot, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) - { - return SiblingsAndSelfOfType(content, publishedSnapshot, variationContextAccessor, contentTypeAlias, culture)?.Where(x => x.Id != content.Id); - } - - /// - /// Gets the siblings of the content, of a given content type. - /// - /// The content type. - /// The content. - /// Published snapshot instance - /// Variation context accessor. - /// The specific culture to filter for. If null is used the current culture is used. (Default is null) - /// The siblings of the content, of the given content type. - /// - /// Note that in V7 this method also return the content node self. - /// - public static IEnumerable? Siblings(this IPublishedContent content, IPublishedSnapshot? publishedSnapshot, IVariationContextAccessor variationContextAccessor, string? culture = null) - where T : class, IPublishedContent - { - return SiblingsAndSelf(content, publishedSnapshot, variationContextAccessor, culture)?.Where(x => x.Id != content.Id); - } - - /// - /// Gets the siblings of the content including the node itself to indicate the position. - /// - /// The content. - /// Published snapshot instance - /// Variation context accessor. - /// The specific culture to filter for. If null is used the current culture is used. (Default is null) - /// The siblings of the content including the node itself. - public static IEnumerable? SiblingsAndSelf(this IPublishedContent content, IPublishedSnapshot? publishedSnapshot, IVariationContextAccessor variationContextAccessor, string? culture = null) - { - return content.Parent != null - ? content.Parent.Children(variationContextAccessor, culture) - : publishedSnapshot?.Content?.GetAtRoot(culture).WhereIsInvariantOrHasCulture(variationContextAccessor, culture); - } - - /// - /// Gets the siblings of the content including the node itself to indicate the position, of a given content type. - /// - /// The content. - /// Published snapshot instance - /// Variation context accessor. - /// The specific culture to filter for. If null is used the current culture is used. (Default is null) - /// The content type alias. - /// The siblings of the content including the node itself, of the given content type. - public static IEnumerable? SiblingsAndSelfOfType(this IPublishedContent content, IPublishedSnapshot? publishedSnapshot, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) - { - return content.Parent != null - ? content.Parent.ChildrenOfType(variationContextAccessor, contentTypeAlias, culture) - : publishedSnapshot?.Content?.GetAtRoot(culture).OfTypes(contentTypeAlias).WhereIsInvariantOrHasCulture(variationContextAccessor, culture); - } - - /// - /// Gets the siblings of the content including the node itself to indicate the position, of a given content type. - /// - /// The content type. - /// The content. - /// Published snapshot instance - /// Variation context accessor. - /// The specific culture to filter for. If null is used the current culture is used. (Default is null) - /// The siblings of the content including the node itself, of the given content type. - public static IEnumerable? SiblingsAndSelf(this IPublishedContent content, IPublishedSnapshot? publishedSnapshot, IVariationContextAccessor variationContextAccessor, string? culture = null) - where T : class, IPublishedContent - { - return content.Parent != null - ? content.Parent.Children(variationContextAccessor, culture) - : publishedSnapshot?.Content?.GetAtRoot(culture).OfType().WhereIsInvariantOrHasCulture(variationContextAccessor, culture); - } - - #endregion - - #region Axes: custom - - /// - /// Gets the root content (ancestor or self at level 1) for the specified . - /// - /// The content. - /// - /// The root content (ancestor or self at level 1) for the specified . - /// - /// - /// This is the same as calling with maxLevel set to 1. - /// - public static IPublishedContent? Root(this IPublishedContent content) - { - return content.AncestorOrSelf(1); - } - - /// - /// Gets the root content (ancestor or self at level 1) for the specified if it's of the specified content type . - /// - /// The content type. - /// The content. - /// - /// The root content (ancestor or self at level 1) for the specified of content type . - /// - /// - /// This is the same as calling with maxLevel set to 1. - /// - public static T? Root(this IPublishedContent content) - where T : class, IPublishedContent + #endregion + + #region Axes: children + + /// + /// Gets the children of the content item. + /// + /// The content item. + /// + /// + /// The specific culture to get the URL children for. Default is null which will use the current culture in + /// + /// + /// + /// Gets children that are available for the specified culture. + /// Children are sorted by their sortOrder. + /// + /// For culture, + /// if null is used the current culture is used. + /// If an empty string is used only invariant children are returned. + /// If "*" is used all children are returned. + /// + /// + /// If a variant culture is specified or there is a current culture in the then the + /// Children returned + /// will include both the variant children matching the culture AND the invariant children because the invariant + /// children flow with the current culture. + /// However, if an empty string is specified only invariant children are returned. + /// + /// + public static IEnumerable? Children(this IPublishedContent content, + IVariationContextAccessor? variationContextAccessor, string? culture = null) + { + // handle context culture for variant + if (culture == null) { - return content.AncestorOrSelf(1); + culture = variationContextAccessor?.VariationContext?.Culture ?? ""; } - #endregion - - #region Writer and creator - - public static string? GetCreatorName(this IPublishedContent content, IUserService userService) - { - var user = userService.GetProfileById(content.CreatorId); - return user?.Name; - } + IEnumerable children = content.ChildrenForAllCultures; + return culture == "*" + ? children + : children?.Where(x => x.IsInvariantOrHasCulture(culture)); + } - public static string? GetWriterName(this IPublishedContent content, IUserService userService) - { - var user = userService.GetProfileById(content.WriterId); - return user?.Name; - } + /// + /// Gets the children of the content, filtered by a predicate. + /// + /// The content. + /// Published snapshot instance + /// The predicate. + /// + /// The specific culture to filter for. If null is used the current culture is used. (Default is + /// null) + /// + /// The children of the content, filtered by the predicate. + /// + /// Children are sorted by their sortOrder. + /// + public static IEnumerable? Children(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, Func predicate, + string? culture = null) => content.Children(variationContextAccessor, culture)?.Where(predicate); + + /// + /// Gets the children of the content, of any of the specified types. + /// + /// The content. + /// Published snapshot instance + /// + /// The specific culture to filter for. If null is used the current culture is used. (Default is + /// null) + /// + /// The content type alias. + /// The children of the content, of any of the specified types. + public static IEnumerable? ChildrenOfType(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, string? contentTypeAlias, string? culture = null) => + content.Children(variationContextAccessor, x => x.ContentType.Alias.InvariantEquals(contentTypeAlias), culture); + + /// + /// Gets the children of the content, of a given content type. + /// + /// The content type. + /// The content. + /// Published snapshot instance + /// + /// The specific culture to filter for. If null is used the current culture is used. (Default is + /// null) + /// + /// The children of content, of the given content type. + /// + /// Children are sorted by their sortOrder. + /// + public static IEnumerable? Children(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, string? culture = null) + where T : class, IPublishedContent => + content.Children(variationContextAccessor, culture)?.OfType(); + + public static IPublishedContent? FirstChild(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, string? culture = null) => + content.Children(variationContextAccessor, culture)?.FirstOrDefault(); + + /// + /// Gets the first child of the content, of a given content type. + /// + public static IPublishedContent? FirstChildOfType(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) => + content.ChildrenOfType(variationContextAccessor, contentTypeAlias, culture)?.FirstOrDefault(); + + public static IPublishedContent? FirstChild(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, Func predicate, + string? culture = null) => content.Children(variationContextAccessor, predicate, culture)?.FirstOrDefault(); + + public static IPublishedContent? FirstChild(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, Guid uniqueId, string? culture = null) => content + .Children(variationContextAccessor, x => x.Key == uniqueId, culture)?.FirstOrDefault(); + + public static T? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, + string? culture = null) + where T : class, IPublishedContent => + content.Children(variationContextAccessor, culture)?.FirstOrDefault(); + + public static T? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, + Func predicate, string? culture = null) + where T : class, IPublishedContent => + content.Children(variationContextAccessor, culture)?.FirstOrDefault(predicate); + + #endregion + + #region Axes: siblings + + /// + /// Gets the siblings of the content. + /// + /// The content. + /// Published snapshot instance + /// Variation context accessor. + /// + /// The specific culture to filter for. If null is used the current culture is used. (Default is + /// null) + /// + /// The siblings of the content. + /// + /// Note that in V7 this method also return the content node self. + /// + public static IEnumerable? Siblings(this IPublishedContent content, + IPublishedSnapshot? publishedSnapshot, IVariationContextAccessor variationContextAccessor, + string? culture = null) => SiblingsAndSelf(content, publishedSnapshot, variationContextAccessor, culture) + ?.Where(x => x.Id != content.Id); + + /// + /// Gets the siblings of the content, of a given content type. + /// + /// The content. + /// Published snapshot instance + /// Variation context accessor. + /// + /// The specific culture to filter for. If null is used the current culture is used. (Default is + /// null) + /// + /// The content type alias. + /// The siblings of the content, of the given content type. + /// + /// Note that in V7 this method also return the content node self. + /// + public static IEnumerable? SiblingsOfType(this IPublishedContent content, + IPublishedSnapshot? publishedSnapshot, IVariationContextAccessor variationContextAccessor, + string contentTypeAlias, string? culture = null) => + SiblingsAndSelfOfType(content, publishedSnapshot, variationContextAccessor, contentTypeAlias, culture) + ?.Where(x => x.Id != content.Id); + + /// + /// Gets the siblings of the content, of a given content type. + /// + /// The content type. + /// The content. + /// Published snapshot instance + /// Variation context accessor. + /// + /// The specific culture to filter for. If null is used the current culture is used. (Default is + /// null) + /// + /// The siblings of the content, of the given content type. + /// + /// Note that in V7 this method also return the content node self. + /// + public static IEnumerable? Siblings(this IPublishedContent content, IPublishedSnapshot? publishedSnapshot, + IVariationContextAccessor variationContextAccessor, string? culture = null) + where T : class, IPublishedContent => + SiblingsAndSelf(content, publishedSnapshot, variationContextAccessor, culture) + ?.Where(x => x.Id != content.Id); + + /// + /// Gets the siblings of the content including the node itself to indicate the position. + /// + /// The content. + /// Published snapshot instance + /// Variation context accessor. + /// + /// The specific culture to filter for. If null is used the current culture is used. (Default is + /// null) + /// + /// The siblings of the content including the node itself. + public static IEnumerable? SiblingsAndSelf(this IPublishedContent content, + IPublishedSnapshot? publishedSnapshot, IVariationContextAccessor variationContextAccessor, + string? culture = null) => + content.Parent != null + ? content.Parent.Children(variationContextAccessor, culture) + : publishedSnapshot?.Content?.GetAtRoot(culture) + .WhereIsInvariantOrHasCulture(variationContextAccessor, culture); + + /// + /// Gets the siblings of the content including the node itself to indicate the position, of a given content type. + /// + /// The content. + /// Published snapshot instance + /// Variation context accessor. + /// + /// The specific culture to filter for. If null is used the current culture is used. (Default is + /// null) + /// + /// The content type alias. + /// The siblings of the content including the node itself, of the given content type. + public static IEnumerable? SiblingsAndSelfOfType(this IPublishedContent content, + IPublishedSnapshot? publishedSnapshot, IVariationContextAccessor variationContextAccessor, + string contentTypeAlias, string? culture = null) => + content.Parent != null + ? content.Parent.ChildrenOfType(variationContextAccessor, contentTypeAlias, culture) + : publishedSnapshot?.Content?.GetAtRoot(culture).OfTypes(contentTypeAlias) + .WhereIsInvariantOrHasCulture(variationContextAccessor, culture); + + /// + /// Gets the siblings of the content including the node itself to indicate the position, of a given content type. + /// + /// The content type. + /// The content. + /// Published snapshot instance + /// Variation context accessor. + /// + /// The specific culture to filter for. If null is used the current culture is used. (Default is + /// null) + /// + /// The siblings of the content including the node itself, of the given content type. + public static IEnumerable? SiblingsAndSelf(this IPublishedContent content, + IPublishedSnapshot? publishedSnapshot, IVariationContextAccessor variationContextAccessor, + string? culture = null) + where T : class, IPublishedContent => + content.Parent != null + ? content.Parent.Children(variationContextAccessor, culture) + : publishedSnapshot?.Content?.GetAtRoot(culture).OfType() + .WhereIsInvariantOrHasCulture(variationContextAccessor, culture); + + #endregion + + #region Axes: custom + + /// + /// Gets the root content (ancestor or self at level 1) for the specified . + /// + /// The content. + /// + /// The root content (ancestor or self at level 1) for the specified . + /// + /// + /// This is the same as calling + /// with maxLevel + /// set to 1. + /// + public static IPublishedContent? Root(this IPublishedContent content) => content.AncestorOrSelf(1); + + /// + /// Gets the root content (ancestor or self at level 1) for the specified if it's of the + /// specified content type . + /// + /// The content type. + /// The content. + /// + /// The root content (ancestor or self at level 1) for the specified of content type + /// . + /// + /// + /// This is the same as calling + /// with + /// maxLevel set to 1. + /// + public static T? Root(this IPublishedContent content) + where T : class, IPublishedContent => + content.AncestorOrSelf(1); + + #endregion + + #region Writer and creator + + public static string? GetCreatorName(this IPublishedContent content, IUserService userService) + { + IProfile user = userService.GetProfileById(content.CreatorId); + return user?.Name; + } - #endregion - - #region Url - - /// - /// Gets the url of the content item. - /// - /// - /// If the content item is a document, then this method returns the url of the - /// document. If it is a media, then this methods return the media url for the - /// 'umbracoFile' property. Use the MediaUrl() method to get the media url for other - /// properties. - /// The value of this property is contextual. It depends on the 'current' request uri, - /// if any. In addition, when the content type is multi-lingual, this is the url for the - /// specified culture. Otherwise, it is the invariant url. - /// - public static string Url(this IPublishedContent content, IPublishedUrlProvider publishedUrlProvider, string? culture = null, UrlMode mode = UrlMode.Default) - { - if (publishedUrlProvider == null) - throw new InvalidOperationException("Cannot resolve a Url when Current.UmbracoContext.UrlProvider is null."); + public static string? GetWriterName(this IPublishedContent content, IUserService userService) + { + IProfile user = userService.GetProfileById(content.WriterId); + return user?.Name; + } - switch (content.ContentType.ItemType) + #endregion + + #region Axes: children + + /// + /// Gets the children of the content in a DataTable. + /// + /// The content. + /// Variation context accessor. + /// The content type service. + /// The media type service. + /// The member type service. + /// The published url provider. + /// An optional content type alias. + /// + /// The specific culture to filter for. If null is used the current culture is used. (Default is + /// null) + /// + /// The children of the content. + public static DataTable ChildrenAsTable(this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, IContentTypeService contentTypeService, + IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, + IPublishedUrlProvider publishedUrlProvider, string contentTypeAliasFilter = "", string? culture = null) + => GenerateDataTable(content, variationContextAccessor, contentTypeService, mediaTypeService, memberTypeService, + publishedUrlProvider, contentTypeAliasFilter, culture); + + /// + /// Gets the children of the content in a DataTable. + /// + /// The content. + /// Variation context accessor. + /// The content type service. + /// The media type service. + /// The member type service. + /// The published url provider. + /// An optional content type alias. + /// + /// The specific culture to filter for. If null is used the current culture is used. (Default is + /// null) + /// + /// The children of the content. + private static DataTable GenerateDataTable(IPublishedContent content, + IVariationContextAccessor variationContextAccessor, IContentTypeService contentTypeService, + IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, + IPublishedUrlProvider publishedUrlProvider, string contentTypeAliasFilter = "", string? culture = null) + { + IPublishedContent firstNode = contentTypeAliasFilter.IsNullOrWhiteSpace() + ? content.Children(variationContextAccessor, culture)?.Any() ?? false + ? content.Children(variationContextAccessor, culture)?.ElementAt(0) + : null + : content.Children(variationContextAccessor, culture) + ?.FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAliasFilter)); + if (firstNode == null) + { + return new DataTable(); //no children found + } + + //use new utility class to create table so that we don't have to maintain code in many places, just one + DataTable dt = DataTableExtensions.GenerateDataTable( + //pass in the alias of the first child node since this is the node type we're rendering headers for + firstNode.ContentType.Alias, + //pass in the callback to extract the Dictionary of all defined aliases to their names + alias => GetPropertyAliasesAndNames(contentTypeService, mediaTypeService, memberTypeService, alias), + //pass in a callback to populate the datatable, yup its a bit ugly but it's already legacy and we just want to maintain code in one place. + () => { - case PublishedItemType.Content: - return publishedUrlProvider.GetUrl(content, mode, culture); - - case PublishedItemType.Media: - return publishedUrlProvider.GetMediaUrl(content, mode, culture, Constants.Conventions.Media.File); - - default: - throw new NotSupportedException(); - } - } - - #endregion - - #region Axes: children - - /// - /// Gets the children of the content in a DataTable. - /// - /// The content. - /// Variation context accessor. - /// The content type service. - /// The media type service. - /// The member type service. - /// The published url provider. - /// An optional content type alias. - /// The specific culture to filter for. If null is used the current culture is used. (Default is null) - /// The children of the content. - public static DataTable ChildrenAsTable(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, IContentTypeService contentTypeService, - IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, - IPublishedUrlProvider publishedUrlProvider, string contentTypeAliasFilter = "", string? culture = null) - => GenerateDataTable(content, variationContextAccessor, contentTypeService, mediaTypeService, memberTypeService, publishedUrlProvider, contentTypeAliasFilter, culture); - - /// - /// Gets the children of the content in a DataTable. - /// - /// The content. - /// Variation context accessor. - /// The content type service. - /// The media type service. - /// The member type service. - /// The published url provider. - /// An optional content type alias. - /// The specific culture to filter for. If null is used the current culture is used. (Default is null) - /// The children of the content. - private static DataTable GenerateDataTable(IPublishedContent content, - IVariationContextAccessor variationContextAccessor, IContentTypeService contentTypeService, - IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, - IPublishedUrlProvider publishedUrlProvider, string contentTypeAliasFilter = "", string? culture = null) - { - var firstNode = contentTypeAliasFilter.IsNullOrWhiteSpace() - ? content.Children(variationContextAccessor, culture)?.Any() ?? false - ? content.Children(variationContextAccessor, culture)?.ElementAt(0) - : null - : content.Children(variationContextAccessor, culture)?.FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAliasFilter)); - if (firstNode == null) - return new DataTable(); //no children found - - //use new utility class to create table so that we don't have to maintain code in many places, just one - var dt = DataTableExtensions.GenerateDataTable( - //pass in the alias of the first child node since this is the node type we're rendering headers for - firstNode.ContentType.Alias, - //pass in the callback to extract the Dictionary of all defined aliases to their names - alias => GetPropertyAliasesAndNames(contentTypeService, mediaTypeService, memberTypeService, alias), - //pass in a callback to populate the datatable, yup its a bit ugly but it's already legacy and we just want to maintain code in one place. - () => + //create all row data + List>, IEnumerable>>> + tableData = DataTableExtensions.CreateTableData(); + IOrderedEnumerable children = + content.Children(variationContextAccessor)?.OrderBy(x => x.SortOrder); + if (children is not null) { - //create all row data - var tableData = DataTableExtensions.CreateTableData(); - var children = content.Children(variationContextAccessor)?.OrderBy(x => x.SortOrder); - if (children is not null) + //loop through each child and create row data for it + foreach (IPublishedContent n in children) { - //loop through each child and create row data for it - foreach (var n in children) + if (contentTypeAliasFilter.IsNullOrWhiteSpace() == false) { - if (contentTypeAliasFilter.IsNullOrWhiteSpace() == false) + if (n.ContentType.Alias.InvariantEquals(contentTypeAliasFilter) == false) { - if (n.ContentType.Alias.InvariantEquals(contentTypeAliasFilter) == false) - continue; //skip this one, it doesn't match the filter + continue; //skip this one, it doesn't match the filter } + } - var standardVals = new Dictionary - { - { "Id", n.Id }, - { "NodeName", n.Name(variationContextAccessor) }, - { "NodeTypeAlias", n.ContentType.Alias }, - { "CreateDate", n.CreateDate }, - { "UpdateDate", n.UpdateDate }, - { "CreatorId", n.CreatorId}, - { "WriterId", n.WriterId }, - { "Url", n.Url(publishedUrlProvider) } - }; - - var userVals = new Dictionary(); - var properties = n.Properties?.Where(p => p.GetSourceValue() is not null) ?? Array.Empty(); - foreach (var p in properties) - { - // probably want the "object value" of the property here... - userVals[p.Alias] = p.GetValue(); - } - //add the row data - DataTableExtensions.AddRowData(tableData, standardVals, userVals); + var standardVals = new Dictionary + { + {"Id", n.Id}, + {"NodeName", n.Name(variationContextAccessor)}, + {"NodeTypeAlias", n.ContentType.Alias}, + {"CreateDate", n.CreateDate}, + {"UpdateDate", n.UpdateDate}, + {"CreatorId", n.CreatorId}, + {"WriterId", n.WriterId}, + {"Url", n.Url(publishedUrlProvider)} + }; + + var userVals = new Dictionary(); + IEnumerable properties = + n.Properties?.Where(p => p.GetSourceValue() is not null) ?? + Array.Empty(); + foreach (IPublishedProperty p in properties) + { + // probably want the "object value" of the property here... + userVals[p.Alias] = p.GetValue(); } + + //add the row data + DataTableExtensions.AddRowData(tableData, standardVals, userVals); } + } - return tableData; - }); - return dt; - } + return tableData; + }); + return dt; + } - #endregion + #endregion - #region PropertyAliasesAndNames + #region PropertyAliasesAndNames - private static Func>? _getPropertyAliasesAndNames; + private static Func>? + _getPropertyAliasesAndNames; - /// - /// This is used only for unit tests to set the delegate to look up aliases/names dictionary of a content type - /// - internal static Func> GetPropertyAliasesAndNames - { - get => _getPropertyAliasesAndNames ?? GetAliasesAndNames; - set => _getPropertyAliasesAndNames = value; - } + /// + /// This is used only for unit tests to set the delegate to look up aliases/names dictionary of a content type + /// + internal static Func> + GetPropertyAliasesAndNames + { + get => _getPropertyAliasesAndNames ?? GetAliasesAndNames; + set => _getPropertyAliasesAndNames = value; + } - private static Dictionary GetAliasesAndNames(IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, string alias) - { - var type = contentTypeService.Get(alias) - ?? mediaTypeService.Get(alias) - ?? (IContentTypeBase?)memberTypeService.Get(alias); - var fields = GetAliasesAndNames(type); + private static Dictionary GetAliasesAndNames(IContentTypeService contentTypeService, + IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, string alias) + { + IContentTypeBase type = contentTypeService.Get(alias) + ?? mediaTypeService.Get(alias) + ?? (IContentTypeBase?)memberTypeService.Get(alias); + Dictionary fields = GetAliasesAndNames(type); - // ensure the standard fields are there - var stdFields = new Dictionary - { - {"Id", "Id"}, - {"NodeName", "NodeName"}, - {"NodeTypeAlias", "NodeTypeAlias"}, - {"CreateDate", "CreateDate"}, - {"UpdateDate", "UpdateDate"}, - {"CreatorId", "CreatorId"}, - {"WriterId", "WriterId"}, - {"Url", "Url"} - }; - - foreach (var field in stdFields.Where(x => fields.ContainsKey(x.Key) == false)) - { - fields[field.Key] = field.Value; - } + // ensure the standard fields are there + var stdFields = new Dictionary + { + {"Id", "Id"}, + {"NodeName", "NodeName"}, + {"NodeTypeAlias", "NodeTypeAlias"}, + {"CreateDate", "CreateDate"}, + {"UpdateDate", "UpdateDate"}, + {"CreatorId", "CreatorId"}, + {"WriterId", "WriterId"}, + {"Url", "Url"} + }; - return fields; + foreach (KeyValuePair field in stdFields.Where(x => fields.ContainsKey(x.Key) == false)) + { + fields[field.Key] = field.Value; } - private static Dictionary GetAliasesAndNames(IContentTypeBase? contentType) => contentType?.PropertyTypes.ToDictionary(x => x.Alias, x => x.Name) ?? new Dictionary(); - - #endregion + return fields; } + + private static Dictionary GetAliasesAndNames(IContentTypeBase? contentType) => + contentType?.PropertyTypes.ToDictionary(x => x.Alias, x => x.Name) ?? new Dictionary(); + + #endregion } diff --git a/src/Umbraco.Core/Extensions/PublishedElementExtensions.cs b/src/Umbraco.Core/Extensions/PublishedElementExtensions.cs index 17133cddaac3..bea09a492dd6 100644 --- a/src/Umbraco.Core/Extensions/PublishedElementExtensions.cs +++ b/src/Umbraco.Core/Extensions/PublishedElementExtensions.cs @@ -1,210 +1,253 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Routing; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Provides extension methods for IPublishedElement. +/// +public static class PublishedElementExtensions { - /// - /// Provides extension methods for IPublishedElement. - /// - public static class PublishedElementExtensions - { - #region OfTypes + #region OfTypes - // the .OfType() filter is nice when there's only one type - // this is to support filtering with multiple types - public static IEnumerable OfTypes(this IEnumerable contents, params string[] types) - where T : IPublishedElement + // the .OfType() filter is nice when there's only one type + // this is to support filtering with multiple types + public static IEnumerable OfTypes(this IEnumerable contents, params string[] types) + where T : IPublishedElement + { + if (types == null || types.Length == 0) { - if (types == null || types.Length == 0) return Enumerable.Empty(); - - return contents.Where(x => types.InvariantContains(x.ContentType.Alias)); + return Enumerable.Empty(); } - #endregion + return contents.Where(x => types.InvariantContains(x.ContentType.Alias)); + } - #region IsComposedOf + #endregion - /// - /// Gets a value indicating whether the content is of a content type composed of the given alias - /// - /// The content. - /// The content type alias. - /// A value indicating whether the content is of a content type composed of a content type identified by the alias. - public static bool IsComposedOf(this IPublishedElement content, string alias) - { - return content.ContentType.CompositionAliases.InvariantContains(alias); - } + #region IsComposedOf - #endregion + /// + /// Gets a value indicating whether the content is of a content type composed of the given alias + /// + /// The content. + /// The content type alias. + /// + /// A value indicating whether the content is of a content type composed of a content type identified by the + /// alias. + /// + public static bool IsComposedOf(this IPublishedElement content, string alias) => + content.ContentType.CompositionAliases.InvariantContains(alias); - #region HasProperty + #endregion - /// - /// Gets a value indicating whether the content has a property identified by its alias. - /// - /// The content. - /// The property alias. - /// A value indicating whether the content has the property identified by the alias. - /// The content may have a property, and that property may not have a value. - public static bool HasProperty(this IPublishedElement content, string alias) - { - return content.ContentType.GetPropertyType(alias) != null; - } + #region HasProperty - #endregion + /// + /// Gets a value indicating whether the content has a property identified by its alias. + /// + /// The content. + /// The property alias. + /// A value indicating whether the content has the property identified by the alias. + /// The content may have a property, and that property may not have a value. + public static bool HasProperty(this IPublishedElement content, string alias) => + content.ContentType.GetPropertyType(alias) != null; - #region HasValue + #endregion - /// - /// Gets a value indicating whether the content has a value for a property identified by its alias. - /// - /// Returns true if GetProperty(alias) is not null and GetProperty(alias).HasValue is true. - public static bool HasValue(this IPublishedElement content, string alias, string? culture = null, string? segment = null) - { - var prop = content.GetProperty(alias); - return prop != null && prop.HasValue(culture, segment); - } + #region HasValue - #endregion - - #region Value - - /// - /// Gets the value of a content's property identified by its alias. - /// - /// The content. - /// The published value fallback implementation. - /// The property alias. - /// The variation language. - /// The variation segment. - /// Optional fallback strategy. - /// The default value. - /// The value of the content's property identified by the alias, if it exists, otherwise a default value. - /// - /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. - /// If no property with the specified alias exists, or if the property has no value, returns . - /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. - /// The alias is case-insensitive. - /// - public static object? Value(this IPublishedElement content, IPublishedValueFallback publishedValueFallback, string alias, string? culture = null, string? segment = null, Fallback fallback = default, object? defaultValue = default) - { - var property = content.GetProperty(alias); + /// + /// Gets a value indicating whether the content has a value for a property identified by its alias. + /// + /// + /// Returns true if GetProperty(alias) is not null and GetProperty(alias).HasValue is + /// true. + /// + public static bool HasValue(this IPublishedElement content, string alias, string? culture = null, + string? segment = null) + { + IPublishedProperty prop = content.GetProperty(alias); + return prop != null && prop.HasValue(culture, segment); + } - // if we have a property, and it has a value, return that value - if (property != null && property.HasValue(culture, segment)) - return property.GetValue(culture, segment); + #endregion - // else let fallback try to get a value - if (publishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value)) - return value; + #region Value + + /// + /// Gets the value of a content's property identified by its alias. + /// + /// The content. + /// The published value fallback implementation. + /// The property alias. + /// The variation language. + /// The variation segment. + /// Optional fallback strategy. + /// The default value. + /// The value of the content's property identified by the alias, if it exists, otherwise a default value. + /// + /// + /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering + /// content. + /// + /// + /// If no property with the specified alias exists, or if the property has no value, returns + /// . + /// + /// + /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the + /// converter. + /// + /// The alias is case-insensitive. + /// + public static object? Value(this IPublishedElement content, IPublishedValueFallback publishedValueFallback, + string alias, string? culture = null, string? segment = null, Fallback fallback = default, + object? defaultValue = default) + { + IPublishedProperty property = content.GetProperty(alias); - // else... if we have a property, at least let the converter return its own - // vision of 'no value' (could be an empty enumerable) - otherwise, default - return property?.GetValue(culture, segment); + // if we have a property, and it has a value, return that value + if (property != null && property.HasValue(culture, segment)) + { + return property.GetValue(culture, segment); } - #endregion - - #region Value - - /// - /// Gets the value of a content's property identified by its alias, converted to a specified type. - /// - /// The target property type. - /// The content. - /// The published value fallback implementation. - /// The property alias. - /// The variation language. - /// The variation segment. - /// Optional fallback strategy. - /// The default value. - /// The value of the content's property identified by the alias, converted to the specified type. - /// - /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering content. - /// If no property with the specified alias exists, or if the property has no value, or if it could not be converted, returns default(T). - /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter. - /// The alias is case-insensitive. - /// - public static T? Value(this IPublishedElement content, IPublishedValueFallback publishedValueFallback, string alias, string? culture = null, string? segment = null, Fallback fallback = default, T? defaultValue = default) + // else let fallback try to get a value + if (publishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value)) { - var property = content.GetProperty(alias); + return value; + } - // if we have a property, and it has a value, return that value - if (property != null && property.HasValue(culture, segment)) - return property.Value(publishedValueFallback, culture, segment); + // else... if we have a property, at least let the converter return its own + // vision of 'no value' (could be an empty enumerable) - otherwise, default + return property?.GetValue(culture, segment); + } - // else let fallback try to get a value - if (publishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value)) - return value; + #endregion - // else... if we have a property, at least let the converter return its own - // vision of 'no value' (could be an empty enumerable) - otherwise, default - return property == null ? default : property.Value(publishedValueFallback, culture, segment); - } + #region Value - #endregion + /// + /// Gets the value of a content's property identified by its alias, converted to a specified type. + /// + /// The target property type. + /// The content. + /// The published value fallback implementation. + /// The property alias. + /// The variation language. + /// The variation segment. + /// Optional fallback strategy. + /// The default value. + /// The value of the content's property identified by the alias, converted to the specified type. + /// + /// + /// The value comes from IPublishedProperty field Value ie it is suitable for use when rendering + /// content. + /// + /// + /// If no property with the specified alias exists, or if the property has no value, or if it could not be + /// converted, returns default(T). + /// + /// + /// If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the + /// converter. + /// + /// The alias is case-insensitive. + /// + public static T? Value(this IPublishedElement content, IPublishedValueFallback publishedValueFallback, + string alias, string? culture = null, string? segment = null, Fallback fallback = default, + T? defaultValue = default) + { + IPublishedProperty property = content.GetProperty(alias); - #region ToIndexedArray + // if we have a property, and it has a value, return that value + if (property != null && property.HasValue(culture, segment)) + { + return property.Value(publishedValueFallback, culture, segment); + } - public static IndexedArrayItem[] ToIndexedArray(this IEnumerable source) - where TContent : class, IPublishedElement + // else let fallback try to get a value + if (publishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out T value)) { - var set = source.Select((content, index) => new IndexedArrayItem(content, index)).ToArray(); - foreach (var setItem in set) setItem.TotalCount = set.Length; - return set; + return value; } - #endregion + // else... if we have a property, at least let the converter return its own + // vision of 'no value' (could be an empty enumerable) - otherwise, default + return property == null ? default : property.Value(publishedValueFallback, culture, segment); + } + + #endregion - #region IsSomething + #region ToIndexedArray - /// - /// Gets a value indicating whether the content is visible. - /// - /// The content. - /// The published value fallback implementation. - /// A value indicating whether the content is visible. - /// A content is not visible if it has an umbracoNaviHide property with a value of "1". Otherwise, - /// the content is visible. - public static bool IsVisible(this IPublishedElement content, IPublishedValueFallback publishedValueFallback) + public static IndexedArrayItem[] ToIndexedArray(this IEnumerable source) + where TContent : class, IPublishedElement + { + IndexedArrayItem[] set = + source.Select((content, index) => new IndexedArrayItem(content, index)).ToArray(); + foreach (IndexedArrayItem setItem in set) { - // rely on the property converter - will return default bool value, ie false, if property - // is not defined, or has no value, else will return its value. - return content.Value(publishedValueFallback, Constants.Conventions.Content.NaviHide) == false; + setItem.TotalCount = set.Length; } - #endregion - - #region MediaUrl - - /// - /// Gets the url for a media. - /// - /// The content item. - /// The published url provider. - /// The culture (use current culture by default). - /// The url mode (use site configuration by default). - /// The alias of the property (use 'umbracoFile' by default). - /// The url for the media. - /// - /// The value of this property is contextual. It depends on the 'current' request uri, - /// if any. In addition, when the content type is multi-lingual, this is the url for the - /// specified culture. Otherwise, it is the invariant url. - /// - public static string MediaUrl(this IPublishedContent content, IPublishedUrlProvider publishedUrlProvider, string? culture = null, UrlMode mode = UrlMode.Default, string propertyAlias = Constants.Conventions.Media.File) - { - if (publishedUrlProvider == null) throw new ArgumentNullException(nameof(publishedUrlProvider)); + return set; + } + + #endregion + + #region IsSomething + + /// + /// Gets a value indicating whether the content is visible. + /// + /// The content. + /// The published value fallback implementation. + /// A value indicating whether the content is visible. + /// + /// A content is not visible if it has an umbracoNaviHide property with a value of "1". Otherwise, + /// the content is visible. + /// + public static bool IsVisible(this IPublishedElement content, IPublishedValueFallback publishedValueFallback) => + // rely on the property converter - will return default bool value, ie false, if property + // is not defined, or has no value, else will return its value. + content.Value(publishedValueFallback, Constants.Conventions.Content.NaviHide) == false; + + #endregion + + #region MediaUrl - return publishedUrlProvider.GetMediaUrl(content, mode, culture, propertyAlias); + /// + /// Gets the url for a media. + /// + /// The content item. + /// The published url provider. + /// The culture (use current culture by default). + /// The url mode (use site configuration by default). + /// The alias of the property (use 'umbracoFile' by default). + /// The url for the media. + /// + /// + /// The value of this property is contextual. It depends on the 'current' request uri, + /// if any. In addition, when the content type is multi-lingual, this is the url for the + /// specified culture. Otherwise, it is the invariant url. + /// + /// + public static string MediaUrl(this IPublishedContent content, IPublishedUrlProvider publishedUrlProvider, + string? culture = null, UrlMode mode = UrlMode.Default, string propertyAlias = Constants.Conventions.Media.File) + { + if (publishedUrlProvider == null) + { + throw new ArgumentNullException(nameof(publishedUrlProvider)); } - #endregion + return publishedUrlProvider.GetMediaUrl(content, mode, culture, propertyAlias); } + + #endregion } diff --git a/src/Umbraco.Core/Extensions/PublishedModelFactoryExtensions.cs b/src/Umbraco.Core/Extensions/PublishedModelFactoryExtensions.cs index b4ffc401305d..0d84e3268ea6 100644 --- a/src/Umbraco.Core/Extensions/PublishedModelFactoryExtensions.cs +++ b/src/Umbraco.Core/Extensions/PublishedModelFactoryExtensions.cs @@ -1,52 +1,52 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Provides extension methods for . +/// +public static class PublishedModelFactoryExtensions { /// - /// Provides extension methods for . + /// Returns true if the current is an implementation of + /// and is enabled /// - public static class PublishedModelFactoryExtensions + public static bool IsLiveFactoryEnabled(this IPublishedModelFactory factory) { - /// - /// Returns true if the current is an implementation of and is enabled - /// - public static bool IsLiveFactoryEnabled(this IPublishedModelFactory factory) + if (factory is IAutoPublishedModelFactory liveFactory) { - if (factory is IAutoPublishedModelFactory liveFactory) - { - return liveFactory.Enabled; - } - - // if it's not ILivePublishedModelFactory we know we're not using a live factory - return false; + return liveFactory.Enabled; } - /// - /// Sets a flag to reset the ModelsBuilder models if the is - /// - /// - /// This does not recompile the InMemory models, only sets a flag to tell models builder to recompile when they are requested. - /// - internal static void WithSafeLiveFactoryReset(this IPublishedModelFactory factory, Action action) + // if it's not ILivePublishedModelFactory we know we're not using a live factory + return false; + } + + /// + /// Sets a flag to reset the ModelsBuilder models if the is + /// + /// + /// + /// This does not recompile the InMemory models, only sets a flag to tell models builder to recompile when they are + /// requested. + /// + internal static void WithSafeLiveFactoryReset(this IPublishedModelFactory factory, Action action) + { + if (factory is IAutoPublishedModelFactory liveFactory) { - if (factory is IAutoPublishedModelFactory liveFactory) + lock (liveFactory.SyncRoot) { - lock (liveFactory.SyncRoot) - { - liveFactory.Reset(); + liveFactory.Reset(); - action(); - } - } - else - { action(); } } - + else + { + action(); + } } } diff --git a/src/Umbraco.Core/Extensions/PublishedPropertyExtension.cs b/src/Umbraco.Core/Extensions/PublishedPropertyExtension.cs index 3ff5c7771954..f935437de2d5 100644 --- a/src/Umbraco.Core/Extensions/PublishedPropertyExtension.cs +++ b/src/Umbraco.Core/Extensions/PublishedPropertyExtension.cs @@ -1,77 +1,82 @@ // Copyright (c) Umbraco. // See LICENSE for more details. + +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Provides extension methods for IPublishedProperty. +/// +public static class PublishedPropertyExtension { - /// - /// Provides extension methods for IPublishedProperty. - /// - public static class PublishedPropertyExtension - { - #region Value + #region Value - public static object? Value(this IPublishedProperty property, IPublishedValueFallback publishedValueFallback, string? culture = null, string? segment = null, Fallback fallback = default, object? defaultValue = default) + public static object? Value(this IPublishedProperty property, IPublishedValueFallback publishedValueFallback, + string? culture = null, string? segment = null, Fallback fallback = default, object? defaultValue = default) + { + if (property.HasValue(culture, segment)) { - if (property.HasValue(culture, segment)) - return property.GetValue(culture, segment); - - return publishedValueFallback.TryGetValue(property, culture, segment, fallback, defaultValue, out var value) - ? value - : property.GetValue(culture, segment); // give converter a chance to return it's own vision of "no value" + return property.GetValue(culture, segment); } - #endregion + return publishedValueFallback.TryGetValue(property, culture, segment, fallback, defaultValue, out var value) + ? value + : property.GetValue(culture, segment); // give converter a chance to return it's own vision of "no value" + } + + #endregion - #region Value + #region Value - public static T? Value(this IPublishedProperty property, IPublishedValueFallback publishedValueFallback, string? culture = null, string? segment = null, Fallback fallback = default, T? defaultValue = default) + public static T? Value(this IPublishedProperty property, IPublishedValueFallback publishedValueFallback, + string? culture = null, string? segment = null, Fallback fallback = default, T? defaultValue = default) + { + if (property.HasValue(culture, segment)) { - if (property.HasValue(culture, segment)) + // we have a value + // try to cast or convert it + var value = property.GetValue(culture, segment); + if (value is T valueAsT) { - // we have a value - // try to cast or convert it - var value = property.GetValue(culture, segment); - if (value is T valueAsT) - { - return valueAsT; - } - - var valueConverted = value.TryConvertTo(); - if (valueConverted.Success) - { - return valueConverted.Result; - } - - // cannot cast nor convert the value, nothing we can return but 'default' - // note: we don't want to fallback in that case - would make little sense - return default; + return valueAsT; } - // we don't have a value, try fallback - if (publishedValueFallback.TryGetValue(property, culture, segment, fallback, defaultValue, out var fallbackValue)) + Attempt valueConverted = value.TryConvertTo(); + if (valueConverted.Success) { - return fallbackValue; + return valueConverted.Result; } - // we don't have a value - neither direct nor fallback - // give a chance to the converter to return something (eg empty enumerable) - var noValue = property.GetValue(culture, segment); - if (noValue is T noValueAsT) - { - return noValueAsT; - } + // cannot cast nor convert the value, nothing we can return but 'default' + // note: we don't want to fallback in that case - would make little sense + return default; + } - var noValueConverted = noValue.TryConvertTo(); - if (noValueConverted.Success) - { - return noValueConverted.Result; - } + // we don't have a value, try fallback + if (publishedValueFallback.TryGetValue(property, culture, segment, fallback, defaultValue, out T fallbackValue)) + { + return fallbackValue; + } - // cannot cast noValue nor convert it, nothing we can return but 'default' - return default; + // we don't have a value - neither direct nor fallback + // give a chance to the converter to return something (eg empty enumerable) + var noValue = property.GetValue(culture, segment); + if (noValue is T noValueAsT) + { + return noValueAsT; + } + + Attempt noValueConverted = noValue.TryConvertTo(); + if (noValueConverted.Success) + { + return noValueConverted.Result; } - #endregion + // cannot cast noValue nor convert it, nothing we can return but 'default' + return default; } + + #endregion } diff --git a/src/Umbraco.Core/Extensions/PublishedSnapshotAccessorExtensions.cs b/src/Umbraco.Core/Extensions/PublishedSnapshotAccessorExtensions.cs index 9fd7da4640d6..9e080cc58ea7 100644 --- a/src/Umbraco.Core/Extensions/PublishedSnapshotAccessorExtensions.cs +++ b/src/Umbraco.Core/Extensions/PublishedSnapshotAccessorExtensions.cs @@ -1,18 +1,17 @@ -using System; using Umbraco.Cms.Core.PublishedCache; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class PublishedSnapshotAccessorExtensions { - public static class PublishedSnapshotAccessorExtensions + public static IPublishedSnapshot GetRequiredPublishedSnapshot( + this IPublishedSnapshotAccessor publishedSnapshotAccessor) { - public static IPublishedSnapshot GetRequiredPublishedSnapshot(this IPublishedSnapshotAccessor publishedSnapshotAccessor) + if (publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot publishedSnapshot)) { - if (publishedSnapshotAccessor.TryGetPublishedSnapshot(out var publishedSnapshot)) - { - return publishedSnapshot!; - } - - throw new InvalidOperationException("Wasn't possible to a get a valid Snapshot"); + return publishedSnapshot!; } + + throw new InvalidOperationException("Wasn't possible to a get a valid Snapshot"); } } diff --git a/src/Umbraco.Core/Extensions/RequestHandlerSettingsExtension.cs b/src/Umbraco.Core/Extensions/RequestHandlerSettingsExtension.cs index e9e6618f8cd0..82d1320aaa80 100644 --- a/src/Umbraco.Core/Extensions/RequestHandlerSettingsExtension.cs +++ b/src/Umbraco.Core/Extensions/RequestHandlerSettingsExtension.cs @@ -1,96 +1,97 @@ -using System.Collections.Generic; -using System.Linq; using Microsoft.Extensions.Configuration; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Configuration.UmbracoSettings; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Get concatenated user and default character replacements +/// taking into account +/// +public static class RequestHandlerSettingsExtension { /// - /// Get concatenated user and default character replacements - /// taking into account + /// Get concatenated user and default character replacements + /// taking into account /// - public static class RequestHandlerSettingsExtension + public static IEnumerable GetCharReplacements(this RequestHandlerSettings requestHandlerSettings) { - /// - /// Get concatenated user and default character replacements - /// taking into account - /// - public static IEnumerable GetCharReplacements(this RequestHandlerSettings requestHandlerSettings) + if (requestHandlerSettings.EnableDefaultCharReplacements is false) { - if (requestHandlerSettings.EnableDefaultCharReplacements is false) - { - return requestHandlerSettings.UserDefinedCharCollection ?? Enumerable.Empty(); - } - - if (requestHandlerSettings.UserDefinedCharCollection == null || requestHandlerSettings.UserDefinedCharCollection.Any() is false) - { - return RequestHandlerSettings.DefaultCharCollection; - } - - return MergeUnique(requestHandlerSettings.UserDefinedCharCollection, RequestHandlerSettings.DefaultCharCollection); + return requestHandlerSettings.UserDefinedCharCollection ?? Enumerable.Empty(); } - /// - /// Merges CharCollection and UserDefinedCharCollection, prioritizing UserDefinedCharCollection - /// - internal static void MergeReplacements(this RequestHandlerSettings requestHandlerSettings, IConfiguration configuration) + if (requestHandlerSettings.UserDefinedCharCollection == null || + requestHandlerSettings.UserDefinedCharCollection.Any() is false) { - string sectionKey = $"{Constants.Configuration.ConfigRequestHandler}:"; + return RequestHandlerSettings.DefaultCharCollection; + } - IEnumerable charCollection = GetReplacements( - configuration, - $"{sectionKey}{nameof(RequestHandlerSettings.CharCollection)}"); + return MergeUnique(requestHandlerSettings.UserDefinedCharCollection, + RequestHandlerSettings.DefaultCharCollection); + } - IEnumerable userDefinedCharCollection = GetReplacements( - configuration, - $"{sectionKey}{nameof(requestHandlerSettings.UserDefinedCharCollection)}"); + /// + /// Merges CharCollection and UserDefinedCharCollection, prioritizing UserDefinedCharCollection + /// + internal static void MergeReplacements(this RequestHandlerSettings requestHandlerSettings, + IConfiguration configuration) + { + var sectionKey = $"{Constants.Configuration.ConfigRequestHandler}:"; - IEnumerable mergedCollection = MergeUnique(userDefinedCharCollection, charCollection); + IEnumerable charCollection = GetReplacements( + configuration, + $"{sectionKey}{nameof(RequestHandlerSettings.CharCollection)}"); - requestHandlerSettings.UserDefinedCharCollection = mergedCollection; - } + IEnumerable userDefinedCharCollection = GetReplacements( + configuration, + $"{sectionKey}{nameof(requestHandlerSettings.UserDefinedCharCollection)}"); - private static IEnumerable GetReplacements(IConfiguration configuration, string key) - { - var replacements = new List(); - IEnumerable config = configuration.GetSection(key).GetChildren(); + IEnumerable mergedCollection = MergeUnique(userDefinedCharCollection, charCollection); - foreach (IConfigurationSection section in config) - { - var @char = section.GetValue(nameof(CharItem.Char)); - var replacement = section.GetValue(nameof(CharItem.Replacement)); - replacements.Add(new CharItem { Char = @char, Replacement = replacement }); - } + requestHandlerSettings.UserDefinedCharCollection = mergedCollection; + } - return replacements; - } + private static IEnumerable GetReplacements(IConfiguration configuration, string key) + { + var replacements = new List(); + IEnumerable config = configuration.GetSection(key).GetChildren(); - /// - /// Merges two IEnumerable of CharItem without any duplicates, items in priorityReplacements will override those in alternativeReplacements - /// - private static IEnumerable MergeUnique( - IEnumerable priorityReplacements, - IEnumerable alternativeReplacements) + foreach (IConfigurationSection section in config) { - var priorityReplacementsList = priorityReplacements.ToList(); - var alternativeReplacementsList = alternativeReplacements.ToList(); + var @char = section.GetValue(nameof(CharItem.Char)); + var replacement = section.GetValue(nameof(CharItem.Replacement)); + replacements.Add(new CharItem {Char = @char, Replacement = replacement}); + } + + return replacements; + } - foreach (CharItem alternativeReplacement in alternativeReplacementsList) + /// + /// Merges two IEnumerable of CharItem without any duplicates, items in priorityReplacements will override those in + /// alternativeReplacements + /// + private static IEnumerable MergeUnique( + IEnumerable priorityReplacements, + IEnumerable alternativeReplacements) + { + var priorityReplacementsList = priorityReplacements.ToList(); + var alternativeReplacementsList = alternativeReplacements.ToList(); + + foreach (CharItem alternativeReplacement in alternativeReplacementsList) + { + foreach (CharItem priorityReplacement in priorityReplacementsList) { - foreach (CharItem priorityReplacement in priorityReplacementsList) + if (priorityReplacement.Char == alternativeReplacement.Char) { - if (priorityReplacement.Char == alternativeReplacement.Char) - { - alternativeReplacement.Replacement = priorityReplacement.Replacement; - } + alternativeReplacement.Replacement = priorityReplacement.Replacement; } } - - return priorityReplacementsList.Union( - alternativeReplacementsList, - new CharacterReplacementEqualityComparer()); } + + return priorityReplacementsList.Union( + alternativeReplacementsList, + new CharacterReplacementEqualityComparer()); } } diff --git a/src/Umbraco.Core/Extensions/RuntimeStateExtensions.cs b/src/Umbraco.Core/Extensions/RuntimeStateExtensions.cs index 72930b89f835..9c64ae944f82 100644 --- a/src/Umbraco.Core/Extensions/RuntimeStateExtensions.cs +++ b/src/Umbraco.Core/Extensions/RuntimeStateExtensions.cs @@ -1,33 +1,33 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Services; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class RuntimeStateExtensions { - public static class RuntimeStateExtensions - { - /// - /// Returns true if the installer is enabled based on the current runtime state - /// - /// - /// - public static bool EnableInstaller(this IRuntimeState state) - => state.Level == RuntimeLevel.Install || state.Level == RuntimeLevel.Upgrade; - // TODO: If we want to enable the installer for package migrations, but IMO i think we should do migrations in the back office - // if they are not unattended. - //=> state.Level == RuntimeLevel.Install || state.Level == RuntimeLevel.Upgrade || state.Level == RuntimeLevel.PackageMigrations; + /// + /// Returns true if the installer is enabled based on the current runtime state + /// + /// + /// + public static bool EnableInstaller(this IRuntimeState state) + => state.Level == RuntimeLevel.Install || state.Level == RuntimeLevel.Upgrade; + // TODO: If we want to enable the installer for package migrations, but IMO i think we should do migrations in the back office + // if they are not unattended. + //=> state.Level == RuntimeLevel.Install || state.Level == RuntimeLevel.Upgrade || state.Level == RuntimeLevel.PackageMigrations; - /// - /// Returns true if Umbraco is greater than - /// - public static bool UmbracoCanBoot(this IRuntimeState state) => state.Level > RuntimeLevel.BootFailed; + /// + /// Returns true if Umbraco is greater than + /// + public static bool UmbracoCanBoot(this IRuntimeState state) => state.Level > RuntimeLevel.BootFailed; - /// - /// Returns true if the runtime state indicates that unattended boot logic should execute - /// - /// - /// - public static bool RunUnattendedBootLogic(this IRuntimeState state) - => (state.Reason == RuntimeLevelReason.UpgradeMigrations || state.Reason == RuntimeLevelReason.UpgradePackageMigrations) - && state.Level == RuntimeLevel.Run; - } + /// + /// Returns true if the runtime state indicates that unattended boot logic should execute + /// + /// + /// + public static bool RunUnattendedBootLogic(this IRuntimeState state) + => (state.Reason == RuntimeLevelReason.UpgradeMigrations || + state.Reason == RuntimeLevelReason.UpgradePackageMigrations) + && state.Level == RuntimeLevel.Run; } diff --git a/src/Umbraco.Core/Extensions/SemVersionExtensions.cs b/src/Umbraco.Core/Extensions/SemVersionExtensions.cs index e8b2a2534bce..11ab97b374d2 100644 --- a/src/Umbraco.Core/Extensions/SemVersionExtensions.cs +++ b/src/Umbraco.Core/Extensions/SemVersionExtensions.cs @@ -3,20 +3,17 @@ using Umbraco.Cms.Core.Semver; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class SemVersionExtensions { - public static class SemVersionExtensions - { - public static string ToSemanticString(this SemVersion semVersion) - { - return semVersion.ToString().Replace("--", "-").Replace("-+", "+"); - } + public static string ToSemanticString(this SemVersion semVersion) => + semVersion.ToString().Replace("--", "-").Replace("-+", "+"); - public static string ToSemanticStringWithoutBuild(this SemVersion semVersion) - { - var version = semVersion.ToSemanticString(); - var indexOfBuild = version.IndexOf('+'); - return indexOfBuild >= 0 ? version.Substring(0, indexOfBuild) : version; - } + public static string ToSemanticStringWithoutBuild(this SemVersion semVersion) + { + var version = semVersion.ToSemanticString(); + var indexOfBuild = version.IndexOf('+'); + return indexOfBuild >= 0 ? version.Substring(0, indexOfBuild) : version; } } diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index c41bc290ff16..646bd1cf6a5a 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -1,1465 +1,1576 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.IO; -using System.Linq; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Strings; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// String extension methods +/// +public static class StringExtensions { - /// - /// String extension methods - /// - public static class StringExtensions + private const char DefaultEscapedStringEscapeChar = '\\'; + private static readonly char[] ToCSharpHexDigitLower = "0123456789abcdef".ToCharArray(); + private static readonly char[] ToCSharpEscapeChars; + + internal static readonly Lazy Whitespace = new(() => new Regex(@"\s+", RegexOptions.Compiled)); + internal static readonly string[] JsonEmpties = {"[]", "{}"}; + private static readonly char[] CleanForXssChars = "*?(){}[];:%<>/\\|&'\"".ToCharArray(); + + // From: http://stackoverflow.com/a/961504/5018 + // filters control characters but allows only properly-formed surrogate sequences + private static readonly Lazy InvalidXmlChars = new(() => + new Regex( + @"(? + /// The namespace for URLs (from RFC 4122, Appendix C). + /// See RFC 4122 + /// + internal static readonly Guid UrlNamespace = new("6ba7b811-9dad-11d1-80b4-00c04fd430c8"); + + static StringExtensions() { - private const char DefaultEscapedStringEscapeChar = '\\'; - private static readonly char[] ToCSharpHexDigitLower = "0123456789abcdef".ToCharArray(); - private static readonly char[] ToCSharpEscapeChars; - - static StringExtensions() + var escapes = new[] {"\aa", "\bb", "\ff", "\nn", "\rr", "\tt", "\vv", "\"\"", "\\\\", "??", "\00"}; + ToCSharpEscapeChars = new char[escapes.Max(e => e[0]) + 1]; + foreach (var escape in escapes) { - var escapes = new[] { "\aa", "\bb", "\ff", "\nn", "\rr", "\tt", "\vv", "\"\"", "\\\\", "??", "\00" }; - ToCSharpEscapeChars = new char[escapes.Max(e => e[0]) + 1]; - foreach (var escape in escapes) - ToCSharpEscapeChars[escape[0]] = escape[1]; + ToCSharpEscapeChars[escape[0]] = escape[1]; } + } + + /// + /// Convert a path to node ids in the order from right to left (deepest to shallowest) + /// + /// + /// + public static int[] GetIdsFromPathReversed(this string path) + { + var nodeIds = path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) + .Select(x => + int.TryParse(x, NumberStyles.Integer, CultureInfo.InvariantCulture, out var output) + ? Attempt.Succeed(output) + : Attempt.Fail()) + .Where(x => x.Success) + .Select(x => x.Result) + .Reverse() + .ToArray(); + return nodeIds; + } + + /// + /// Removes new lines and tabs + /// + /// + /// + public static string StripWhitespace(this string txt) => Regex.Replace(txt, @"\s", string.Empty); - /// - /// Convert a path to node ids in the order from right to left (deepest to shallowest) - /// - /// - /// - public static int[] GetIdsFromPathReversed(this string path) + public static string StripFileExtension(this string fileName) + { + //filenames cannot contain line breaks + if (fileName.Contains(Environment.NewLine) || fileName.Contains("\r") || fileName.Contains("\n")) { - var nodeIds = path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) - .Select(x => int.TryParse(x, NumberStyles.Integer, CultureInfo.InvariantCulture, out var output) ? Attempt.Succeed(output) : Attempt.Fail()) - .Where(x => x.Success) - .Select(x=>x.Result) - .Reverse() - .ToArray(); - return nodeIds; + return fileName; } - /// - /// Removes new lines and tabs - /// - /// - /// - public static string StripWhitespace(this string txt) + var lastIndex = fileName.LastIndexOf('.'); + if (lastIndex > 0) { - return Regex.Replace(txt, @"\s", string.Empty); + var ext = fileName.Substring(lastIndex); + //file extensions cannot contain whitespace + if (ext.Contains(" ")) + { + return fileName; + } + + return string.Format("{0}", fileName.Substring(0, fileName.IndexOf(ext, StringComparison.Ordinal))); } - public static string StripFileExtension(this string fileName) - { - //filenames cannot contain line breaks - if (fileName.Contains(Environment.NewLine) || fileName.Contains("\r") || fileName.Contains("\n")) return fileName; + return fileName; + } - var lastIndex = fileName.LastIndexOf('.'); - if (lastIndex > 0) - { - var ext = fileName.Substring(lastIndex); - //file extensions cannot contain whitespace - if (ext.Contains(" ")) return fileName; + /// + /// Determines the extension of the path or URL + /// + /// + /// Extension of the file + public static string GetFileExtension(this string file) + { + //Find any characters between the last . and the start of a query string or the end of the string + const string pattern = @"(?\.[^\.\?]+)(\?.*|$)"; + Match match = Regex.Match(file, pattern); + return match.Success + ? match.Groups["extension"].Value + : string.Empty; + } - return string.Format("{0}", fileName.Substring(0, fileName.IndexOf(ext, StringComparison.Ordinal))); - } + /// + /// This tries to detect a json string, this is not a fail safe way but it is quicker than doing + /// a try/catch when deserializing when it is not json. + /// + /// + /// + public static bool DetectIsJson(this string input) + { + if (input.IsNullOrWhiteSpace()) + { + return false; + } - return fileName; + input = input.Trim(); + return (input.StartsWith("{") && input.EndsWith("}")) + || (input.StartsWith("[") && input.EndsWith("]")); + } + public static bool DetectIsEmptyJson(this string input) => + JsonEmpties.Contains(Whitespace.Value.Replace(input, string.Empty)); + public static string ReplaceNonAlphanumericChars(this string input, string replacement) + { + //any character that is not alphanumeric, convert to a hyphen + var mName = input; + foreach (var c in mName.ToCharArray().Where(c => !char.IsLetterOrDigit(c))) + { + mName = mName.Replace(c.ToString(CultureInfo.InvariantCulture), replacement); } - /// - /// Determines the extension of the path or URL - /// - /// - /// Extension of the file - public static string GetFileExtension(this string file) + return mName; + } + + public static string ReplaceNonAlphanumericChars(this string input, char replacement) + { + var inputArray = input.ToCharArray(); + var outputArray = new char[input.Length]; + for (var i = 0; i < inputArray.Length; i++) { - //Find any characters between the last . and the start of a query string or the end of the string - const string pattern = @"(?\.[^\.\?]+)(\?.*|$)"; - var match = Regex.Match(file, pattern); - return match.Success - ? match.Groups["extension"].Value - : string.Empty; + outputArray[i] = char.IsLetterOrDigit(inputArray[i]) ? inputArray[i] : replacement; } - /// - /// This tries to detect a json string, this is not a fail safe way but it is quicker than doing - /// a try/catch when deserializing when it is not json. - /// - /// - /// - public static bool DetectIsJson(this string input) + return new string(outputArray); + } + + /// + /// Cleans string to aid in preventing xss attacks. + /// + /// + /// + /// + public static string CleanForXss(this string input, params char[] ignoreFromClean) + { + //remove any HTML + input = input.StripHtml(); + //strip out any potential chars involved with XSS + return input.ExceptChars(new HashSet(CleanForXssChars.Except(ignoreFromClean))); + } + + public static string ExceptChars(this string str, HashSet toExclude) + { + var sb = new StringBuilder(str.Length); + foreach (var c in str.Where(c => toExclude.Contains(c) == false)) { - if (input.IsNullOrWhiteSpace()) return false; - input = input.Trim(); - return (input.StartsWith("{") && input.EndsWith("}")) - || (input.StartsWith("[") && input.EndsWith("]")); + sb.Append(c); } - internal static readonly Lazy Whitespace = new Lazy(() => new Regex(@"\s+", RegexOptions.Compiled)); - internal static readonly string[] JsonEmpties = { "[]", "{}" }; - public static bool DetectIsEmptyJson(this string input) + return sb.ToString(); + } + + /// + /// Returns a stream from a string + /// + /// + /// + internal static Stream GenerateStreamFromString(this string s) + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.Write(s); + writer.Flush(); + stream.Position = 0; + return stream; + } + + /// + /// This will append the query string to the URL + /// + /// + /// + /// + /// + /// This methods ensures that the resulting URL is structured correctly, that there's only one '?' and that things are + /// delimited properly with '&' + /// + public static string AppendQueryStringToUrl(this string url, params string[] queryStrings) + { + //remove any prefixed '&' or '?' + for (var i = 0; i < queryStrings.Length; i++) { - return JsonEmpties.Contains(Whitespace.Value.Replace(input, string.Empty)); + queryStrings[i] = queryStrings[i].TrimStart(Constants.CharArrays.QuestionMarkAmpersand) + .TrimEnd(Constants.CharArrays.Ampersand); } - public static string ReplaceNonAlphanumericChars(this string input, string replacement) + var nonEmpty = queryStrings.Where(x => !x.IsNullOrWhiteSpace()).ToArray(); + + if (url.Contains("?")) { - //any character that is not alphanumeric, convert to a hyphen - var mName = input; - foreach (var c in mName.ToCharArray().Where(c => !char.IsLetterOrDigit(c))) - { - mName = mName.Replace(c.ToString(CultureInfo.InvariantCulture), replacement); - } - return mName; + return url + string.Join("&", nonEmpty).EnsureStartsWith('&'); } - public static string ReplaceNonAlphanumericChars(this string input, char replacement) + return url + string.Join("&", nonEmpty).EnsureStartsWith('?'); + } + + + //this is from SqlMetal and just makes it a bit of fun to allow pluralization + public static string MakePluralName(this string name) + { + if (name.EndsWith("x", StringComparison.OrdinalIgnoreCase) || + name.EndsWith("ch", StringComparison.OrdinalIgnoreCase) || + name.EndsWith("s", StringComparison.OrdinalIgnoreCase) || + name.EndsWith("sh", StringComparison.OrdinalIgnoreCase)) { - var inputArray = input.ToCharArray(); - var outputArray = new char[input.Length]; - for (var i = 0; i < inputArray.Length; i++) - outputArray[i] = char.IsLetterOrDigit(inputArray[i]) ? inputArray[i] : replacement; - return new string(outputArray); + name = name + "es"; + return name; } - private static readonly char[] CleanForXssChars = "*?(){}[];:%<>/\\|&'\"".ToCharArray(); - /// - /// Cleans string to aid in preventing xss attacks. - /// - /// - /// - /// - public static string CleanForXss(this string input, params char[] ignoreFromClean) + if (name.EndsWith("y", StringComparison.OrdinalIgnoreCase) && name.Length > 1 && + !IsVowel(name[name.Length - 2])) { - //remove any HTML - input = input.StripHtml(); - //strip out any potential chars involved with XSS - return input.ExceptChars(new HashSet(CleanForXssChars.Except(ignoreFromClean))); + name = name.Remove(name.Length - 1, 1); + name = name + "ies"; + return name; } - public static string ExceptChars(this string str, HashSet toExclude) + if (!name.EndsWith("s", StringComparison.OrdinalIgnoreCase)) { - var sb = new StringBuilder(str.Length); - foreach (var c in str.Where(c => toExclude.Contains(c) == false)) - { - sb.Append(c); - } - return sb.ToString(); - } - - /// - /// Returns a stream from a string - /// - /// - /// - internal static Stream GenerateStreamFromString(this string s) - { - var stream = new MemoryStream(); - var writer = new StreamWriter(stream); - writer.Write(s); - writer.Flush(); - stream.Position = 0; - return stream; - } - - /// - /// This will append the query string to the URL - /// - /// - /// - /// - /// - /// This methods ensures that the resulting URL is structured correctly, that there's only one '?' and that things are - /// delimited properly with '&' - /// - public static string AppendQueryStringToUrl(this string url, params string[] queryStrings) - { - //remove any prefixed '&' or '?' - for (var i = 0; i < queryStrings.Length; i++) - { - queryStrings[i] = queryStrings[i].TrimStart(Constants.CharArrays.QuestionMarkAmpersand).TrimEnd(Constants.CharArrays.Ampersand); - } + name = name + "s"; + } - var nonEmpty = queryStrings.Where(x => !x.IsNullOrWhiteSpace()).ToArray(); + return name; + } - if (url.Contains("?")) - { - return url + string.Join("&", nonEmpty).EnsureStartsWith('&'); - } - return url + string.Join("&", nonEmpty).EnsureStartsWith('?'); + public static bool IsVowel(this char c) + { + switch (c) + { + case 'O': + case 'U': + case 'Y': + case 'A': + case 'E': + case 'I': + case 'o': + case 'u': + case 'y': + case 'a': + case 'e': + case 'i': + return true; } + return false; + } - //this is from SqlMetal and just makes it a bit of fun to allow pluralization - public static string MakePluralName(this string name) + /// + /// Trims the specified value from a string; accepts a string input whereas the in-built implementation only accepts + /// char or char[]. + /// + /// The value. + /// For removing. + /// + public static string Trim(this string value, string forRemoving) + { + if (string.IsNullOrEmpty(value)) { - if ((name.EndsWith("x", StringComparison.OrdinalIgnoreCase) || name.EndsWith("ch", StringComparison.OrdinalIgnoreCase)) || (name.EndsWith("s", StringComparison.OrdinalIgnoreCase) || name.EndsWith("sh", StringComparison.OrdinalIgnoreCase))) - { - name = name + "es"; - return name; - } - if ((name.EndsWith("y", StringComparison.OrdinalIgnoreCase) && (name.Length > 1)) && !IsVowel(name[name.Length - 2])) - { - name = name.Remove(name.Length - 1, 1); - name = name + "ies"; - return name; - } - if (!name.EndsWith("s", StringComparison.OrdinalIgnoreCase)) - { - name = name + "s"; - } - return name; + return value; } - public static bool IsVowel(this char c) + return value.TrimEnd(forRemoving).TrimStart(forRemoving); + } + + public static string EncodeJsString(this string s) + { + var sb = new StringBuilder(); + foreach (var c in s) { switch (c) { - case 'O': - case 'U': - case 'Y': - case 'A': - case 'E': - case 'I': - case 'o': - case 'u': - case 'y': - case 'a': - case 'e': - case 'i': - return true; + case '\"': + sb.Append("\\\""); + break; + case '\\': + sb.Append("\\\\"); + break; + case '\b': + sb.Append("\\b"); + break; + case '\f': + sb.Append("\\f"); + break; + case '\n': + sb.Append("\\n"); + break; + case '\r': + sb.Append("\\r"); + break; + case '\t': + sb.Append("\\t"); + break; + default: + int i = c; + if (i < 32 || i > 127) + { + sb.AppendFormat("\\u{0:X04}", i); + } + else + { + sb.Append(c); + } + + break; } - return false; } - /// - /// Trims the specified value from a string; accepts a string input whereas the in-built implementation only accepts char or char[]. - /// - /// The value. - /// For removing. - /// - public static string Trim(this string value, string forRemoving) + return sb.ToString(); + } + + public static string TrimEnd(this string value, string forRemoving) + { + if (string.IsNullOrEmpty(value)) { - if (string.IsNullOrEmpty(value)) return value; - return value.TrimEnd(forRemoving).TrimStart(forRemoving); + return value; } - public static string EncodeJsString(this string s) + if (string.IsNullOrEmpty(forRemoving)) { - var sb = new StringBuilder(); - foreach (var c in s) - { - switch (c) - { - case '\"': - sb.Append("\\\""); - break; - case '\\': - sb.Append("\\\\"); - break; - case '\b': - sb.Append("\\b"); - break; - case '\f': - sb.Append("\\f"); - break; - case '\n': - sb.Append("\\n"); - break; - case '\r': - sb.Append("\\r"); - break; - case '\t': - sb.Append("\\t"); - break; - default: - int i = (int)c; - if (i < 32 || i > 127) - { - sb.AppendFormat("\\u{0:X04}", i); - } - else - { - sb.Append(c); - } - break; - } - } - return sb.ToString(); + return value; } - public static string TrimEnd(this string value, string forRemoving) + while (value.EndsWith(forRemoving, StringComparison.InvariantCultureIgnoreCase)) { - if (string.IsNullOrEmpty(value)) return value; - if (string.IsNullOrEmpty(forRemoving)) return value; + value = value.Remove(value.LastIndexOf(forRemoving, StringComparison.InvariantCultureIgnoreCase)); + } - while (value.EndsWith(forRemoving, StringComparison.InvariantCultureIgnoreCase)) - { - value = value.Remove(value.LastIndexOf(forRemoving, StringComparison.InvariantCultureIgnoreCase)); - } + return value; + } + + public static string TrimStart(this string value, string forRemoving) + { + if (string.IsNullOrEmpty(value)) + { return value; } - public static string TrimStart(this string value, string forRemoving) + if (string.IsNullOrEmpty(forRemoving)) { - if (string.IsNullOrEmpty(value)) return value; - if (string.IsNullOrEmpty(forRemoving)) return value; - - while (value.StartsWith(forRemoving, StringComparison.InvariantCultureIgnoreCase)) - { - value = value.Substring(forRemoving.Length); - } return value; } - public static string EnsureStartsWith(this string input, string toStartWith) + while (value.StartsWith(forRemoving, StringComparison.InvariantCultureIgnoreCase)) { - if (input.StartsWith(toStartWith)) return input; - return toStartWith + input.TrimStart(toStartWith); + value = value.Substring(forRemoving.Length); } - public static string EnsureStartsWith(this string input, char value) + return value; + } + + public static string EnsureStartsWith(this string input, string toStartWith) + { + if (input.StartsWith(toStartWith)) { - return input.StartsWith(value.ToString(CultureInfo.InvariantCulture)) ? input : value + input; + return input; } - public static string EnsureEndsWith(this string input, char value) + return toStartWith + input.TrimStart(toStartWith); + } + + public static string EnsureStartsWith(this string input, char value) => + input.StartsWith(value.ToString(CultureInfo.InvariantCulture)) ? input : value + input; + + public static string EnsureEndsWith(this string input, char value) => + input.EndsWith(value.ToString(CultureInfo.InvariantCulture)) ? input : input + value; + + public static string EnsureEndsWith(this string input, string toEndWith) => + input.EndsWith(toEndWith.ToString(CultureInfo.InvariantCulture)) ? input : input + toEndWith; + + public static bool IsLowerCase(this char ch) => ch.ToString(CultureInfo.InvariantCulture) == + ch.ToString(CultureInfo.InvariantCulture).ToLowerInvariant(); + + public static bool IsUpperCase(this char ch) => ch.ToString(CultureInfo.InvariantCulture) == + ch.ToString(CultureInfo.InvariantCulture).ToUpperInvariant(); + + /// + /// Indicates whether a specified string is null, empty, or + /// consists only of white-space characters. + /// + /// The value to check. + /// + /// Returns if the value is null, + /// empty, or consists only of white-space characters, otherwise + /// returns . + /// + public static bool IsNullOrWhiteSpace(this string? value) => string.IsNullOrWhiteSpace(value); + + public static string? IfNullOrWhiteSpace(this string? str, string? defaultValue) => + str.IsNullOrWhiteSpace() ? defaultValue : str; + + /// The to delimited list. + /// The list. + /// The delimiter. + /// the list + [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification = "By design")] + public static IList ToDelimitedList(this string list, string delimiter = ",") + { + var delimiters = new[] {delimiter}; + return !list.IsNullOrWhiteSpace() + ? list.Split(delimiters, StringSplitOptions.RemoveEmptyEntries) + .Select(i => i.Trim()) + .ToList() + : new List(); + } + + /// enum try parse. + /// The str type. + /// The ignore case. + /// The result. + /// The type + /// The enum try parse. + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "By Design")] + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "By Design")] + public static bool EnumTryParse(this string strType, bool ignoreCase, out T? result) + { + try + { + result = (T)Enum.Parse(typeof(T), strType, ignoreCase); + return true; + } + catch { - return input.EndsWith(value.ToString(CultureInfo.InvariantCulture)) ? input : input + value; + result = default; + return false; } + } + + /// + /// Parse string to Enum + /// + /// The enum type + /// The string to parse + /// The ignore case + /// The parsed enum + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "By Design")] + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "By Design")] + public static T EnumParse(this string strType, bool ignoreCase) => (T)Enum.Parse(typeof(T), strType, ignoreCase); + + /// + /// Strips all HTML from a string. + /// + /// The text. + /// Returns the string without any HTML tags. + public static string StripHtml(this string text) + { + const string pattern = @"<(.|\n)*?>"; + return Regex.Replace(text, pattern, string.Empty, RegexOptions.Compiled); + } - public static string EnsureEndsWith(this string input, string toEndWith) + /// + /// Encodes as GUID. + /// + /// The input. + /// + public static Guid EncodeAsGuid(this string input) + { + if (string.IsNullOrWhiteSpace(input)) { - return input.EndsWith(toEndWith.ToString(CultureInfo.InvariantCulture)) ? input : input + toEndWith; + throw new ArgumentNullException("input"); } - public static bool IsLowerCase(this char ch) + var convertToHex = input.ConvertToHex(); + var hexLength = convertToHex.Length < 32 ? convertToHex.Length : 32; + var hex = convertToHex.Substring(0, hexLength).PadLeft(32, '0'); + Guid output = Guid.Empty; + return Guid.TryParse(hex, out output) ? output : Guid.Empty; + } + + /// + /// Converts to hex. + /// + /// The input. + /// + public static string ConvertToHex(this string input) + { + if (string.IsNullOrEmpty(input)) { - return ch.ToString(CultureInfo.InvariantCulture) == ch.ToString(CultureInfo.InvariantCulture).ToLowerInvariant(); + return string.Empty; } - public static bool IsUpperCase(this char ch) + var sb = new StringBuilder(input.Length); + foreach (var c in input) { - return ch.ToString(CultureInfo.InvariantCulture) == ch.ToString(CultureInfo.InvariantCulture).ToUpperInvariant(); + sb.AppendFormat("{0:x2}", Convert.ToUInt32(c)); } - /// Indicates whether a specified string is null, empty, or - /// consists only of white-space characters. - /// The value to check. - /// Returns if the value is null, - /// empty, or consists only of white-space characters, otherwise - /// returns . - public static bool IsNullOrWhiteSpace(this string? value) => string.IsNullOrWhiteSpace(value); + return sb.ToString(); + } - public static string? IfNullOrWhiteSpace(this string? str, string? defaultValue) + public static string DecodeFromHex(this string hexValue) + { + var strValue = ""; + while (hexValue.Length > 0) { - return str.IsNullOrWhiteSpace() ? defaultValue : str; + strValue += Convert.ToChar(Convert.ToUInt32(hexValue.Substring(0, 2), 16)).ToString(); + hexValue = hexValue.Substring(2, hexValue.Length - 2); } - /// The to delimited list. - /// The list. - /// The delimiter. - /// the list - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification = "By design")] - public static IList ToDelimitedList(this string list, string delimiter = ",") + return strValue; + } + + /// + /// Encodes a string to a safe URL base64 string + /// + /// + /// + public static string ToUrlBase64(this string input) + { + if (input == null) { - var delimiters = new[] { delimiter }; - return !list.IsNullOrWhiteSpace() - ? list.Split(delimiters, StringSplitOptions.RemoveEmptyEntries) - .Select(i => i.Trim()) - .ToList() - : new List(); + throw new ArgumentNullException(nameof(input)); } - /// enum try parse. - /// The str type. - /// The ignore case. - /// The result. - /// The type - /// The enum try parse. - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "By Design")] - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "By Design")] - public static bool EnumTryParse(this string strType, bool ignoreCase, out T? result) + if (string.IsNullOrEmpty(input)) { - try - { - result = (T)Enum.Parse(typeof(T), strType, ignoreCase); - return true; - } - catch - { - result = default(T); - return false; - } + return string.Empty; } - /// - /// Parse string to Enum - /// - /// The enum type - /// The string to parse - /// The ignore case - /// The parsed enum - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "By Design")] - [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "By Design")] - public static T EnumParse(this string strType, bool ignoreCase) + //return Convert.ToBase64String(bytes).Replace(".", "-").Replace("/", "_").Replace("=", ","); + var bytes = Encoding.UTF8.GetBytes(input); + return UrlTokenEncode(bytes); + } + + /// + /// Decodes a URL safe base64 string back + /// + /// + /// + public static string? FromUrlBase64(this string input) + { + if (input == null) { - return (T)Enum.Parse(typeof(T), strType, ignoreCase); + throw new ArgumentNullException(nameof(input)); } - /// - /// Strips all HTML from a string. - /// - /// The text. - /// Returns the string without any HTML tags. - public static string StripHtml(this string text) + //if (input.IsInvalidBase64()) return null; + + try { - const string pattern = @"<(.|\n)*?>"; - return Regex.Replace(text, pattern, string.Empty, RegexOptions.Compiled); + //var decodedBytes = Convert.FromBase64String(input.Replace("-", ".").Replace("_", "/").Replace(",", "=")); + var decodedBytes = UrlTokenDecode(input); + return decodedBytes != null ? Encoding.UTF8.GetString(decodedBytes) : null; } - - /// - /// Encodes as GUID. - /// - /// The input. - /// - public static Guid EncodeAsGuid(this string input) + catch (FormatException) { - if (string.IsNullOrWhiteSpace(input)) throw new ArgumentNullException("input"); - - var convertToHex = input.ConvertToHex(); - var hexLength = convertToHex.Length < 32 ? convertToHex.Length : 32; - var hex = convertToHex.Substring(0, hexLength).PadLeft(32, '0'); - var output = Guid.Empty; - return Guid.TryParse(hex, out output) ? output : Guid.Empty; + return null; } + } - /// - /// Converts to hex. - /// - /// The input. - /// - public static string ConvertToHex(this string input) + /// + /// formats the string with invariant culture + /// + /// The format. + /// The args. + /// + public static string InvariantFormat(this string? format, params object?[] args) => + string.Format(CultureInfo.InvariantCulture, format ?? string.Empty, args); + + /// + /// Converts an integer to an invariant formatted string + /// + /// + /// + public static string ToInvariantString(this int str) => str.ToString(CultureInfo.InvariantCulture); + + public static string ToInvariantString(this long str) => str.ToString(CultureInfo.InvariantCulture); + + /// + /// Compares 2 strings with invariant culture and case ignored + /// + /// The compare. + /// The compare to. + /// + public static bool InvariantEquals(this string? compare, string? compareTo) => + string.Equals(compare, compareTo, StringComparison.InvariantCultureIgnoreCase); + + public static bool InvariantStartsWith(this string compare, string compareTo) => + compare.StartsWith(compareTo, StringComparison.InvariantCultureIgnoreCase); + + public static bool InvariantEndsWith(this string compare, string compareTo) => + compare.EndsWith(compareTo, StringComparison.InvariantCultureIgnoreCase); + + public static bool InvariantContains(this string compare, string compareTo) => + compare.IndexOf(compareTo, StringComparison.OrdinalIgnoreCase) >= 0; + + public static bool InvariantContains(this IEnumerable compare, string compareTo) => + compare.Contains(compareTo, StringComparer.InvariantCultureIgnoreCase); + + public static int InvariantIndexOf(this string s, string value) => + s.IndexOf(value, StringComparison.OrdinalIgnoreCase); + + public static int InvariantLastIndexOf(this string s, string value) => + s.LastIndexOf(value, StringComparison.OrdinalIgnoreCase); + + + /// + /// Tries to parse a string into the supplied type by finding and using the Type's "Parse" method + /// + /// + /// + /// + public static T? ParseInto(this string val) => (T?)val.ParseInto(typeof(T)); + + /// + /// Tries to parse a string into the supplied type by finding and using the Type's "Parse" method + /// + /// + /// + /// + public static object? ParseInto(this string val, Type type) + { + if (string.IsNullOrEmpty(val) == false) { - if (string.IsNullOrEmpty(input)) return string.Empty; - - var sb = new StringBuilder(input.Length); - foreach (var c in input) - { - sb.AppendFormat("{0:x2}", Convert.ToUInt32(c)); - } - return sb.ToString(); + TypeConverter tc = TypeDescriptor.GetConverter(type); + return tc.ConvertFrom(val); } - public static string DecodeFromHex(this string hexValue) + return val; + } + + /// + /// Generates a hash of a string based on the FIPS compliance setting. + /// + /// Refers to itself + /// The hashed string + public static string GenerateHash(this string str) => str.ToSHA1(); + + /// + /// Generate a hash of a string based on the specified hash algorithm. + /// + /// The hash algorithm implementation to use. + /// The to hash. + /// + /// The hashed string. + /// + public static string GenerateHash(this string str) + where T : HashAlgorithm => str.GenerateHash(typeof(T).FullName); + + /// + /// Converts the string to SHA1 + /// + /// refers to itself + /// The SHA1 hashed string + public static string ToSHA1(this string stringToConvert) => stringToConvert.GenerateHash("SHA1"); + + /// + /// Generate a hash of a string based on the hashType passed in + /// + /// Refers to itself + /// + /// String with the hash type. See remarks section of the CryptoConfig Class in MSDN docs for a + /// list of possible values. + /// + /// The hashed string + private static string GenerateHash(this string str, string? hashType) + { + HashAlgorithm? hasher = null; + //create an instance of the correct hashing provider based on the type passed in + if (hashType is not null) { - var strValue = ""; - while (hexValue.Length > 0) - { - strValue += Convert.ToChar(Convert.ToUInt32(hexValue.Substring(0, 2), 16)).ToString(); - hexValue = hexValue.Substring(2, hexValue.Length - 2); - } - return strValue; + hasher = HashAlgorithm.Create(hashType); } - /// - /// Encodes a string to a safe URL base64 string - /// - /// - /// - public static string ToUrlBase64(this string input) + if (hasher == null) { - if (input == null) throw new ArgumentNullException(nameof(input)); - - if (string.IsNullOrEmpty(input)) - return string.Empty; - - //return Convert.ToBase64String(bytes).Replace(".", "-").Replace("/", "_").Replace("=", ","); - var bytes = Encoding.UTF8.GetBytes(input); - return UrlTokenEncode(bytes); + throw new InvalidOperationException("No hashing type found by name " + hashType); } - /// - /// Decodes a URL safe base64 string back - /// - /// - /// - public static string? FromUrlBase64(this string input) + using (hasher) { - if (input == null) throw new ArgumentNullException(nameof(input)); + //convert our string into byte array + var byteArray = Encoding.UTF8.GetBytes(str); - //if (input.IsInvalidBase64()) return null; + //get the hashed values created by our selected provider + var hashedByteArray = hasher.ComputeHash(byteArray); - try - { - //var decodedBytes = Convert.FromBase64String(input.Replace("-", ".").Replace("_", "/").Replace(",", "=")); - var decodedBytes = UrlTokenDecode(input); - return decodedBytes != null ? Encoding.UTF8.GetString(decodedBytes) : null; - } - catch (FormatException) + //create a StringBuilder object + var stringBuilder = new StringBuilder(); + + //loop to each byte + foreach (var b in hashedByteArray) { - return null; + //append it to our StringBuilder + stringBuilder.Append(b.ToString("x2")); } - } - /// - /// formats the string with invariant culture - /// - /// The format. - /// The args. - /// - public static string InvariantFormat(this string? format, params object?[] args) - { - return string.Format(CultureInfo.InvariantCulture, format ?? string.Empty, args); + //return the hashed value + return stringBuilder.ToString(); } + } - /// - /// Converts an integer to an invariant formatted string - /// - /// - /// - public static string ToInvariantString(this int str) + /// + /// Decodes a string that was encoded with UrlTokenEncode + /// + /// + /// + public static byte[] UrlTokenDecode(this string input) + { + if (input == null) { - return str.ToString(CultureInfo.InvariantCulture); + throw new ArgumentNullException(nameof(input)); } - public static string ToInvariantString(this long str) + if (input.Length == 0) { - return str.ToString(CultureInfo.InvariantCulture); + return Array.Empty(); } - /// - /// Compares 2 strings with invariant culture and case ignored - /// - /// The compare. - /// The compare to. - /// - public static bool InvariantEquals(this string? compare, string? compareTo) + // calc array size - must be groups of 4 + var arrayLength = input.Length; + var remain = arrayLength % 4; + if (remain != 0) { - return String.Equals(compare, compareTo, StringComparison.InvariantCultureIgnoreCase); + arrayLength += 4 - remain; } - public static bool InvariantStartsWith(this string compare, string compareTo) + var inArray = new char[arrayLength]; + for (var i = 0; i < input.Length; i++) { - return compare.StartsWith(compareTo, StringComparison.InvariantCultureIgnoreCase); - } + var ch = input[i]; + switch (ch) + { + case '-': // restore '-' as '+' + inArray[i] = '+'; + break; - public static bool InvariantEndsWith(this string compare, string compareTo) - { - return compare.EndsWith(compareTo, StringComparison.InvariantCultureIgnoreCase); - } + case '_': // restore '_' as '/' + inArray[i] = '/'; + break; - public static bool InvariantContains(this string compare, string compareTo) - { - return compare.IndexOf(compareTo, StringComparison.OrdinalIgnoreCase) >= 0; + default: // keep char unchanged + inArray[i] = ch; + break; + } } - public static bool InvariantContains(this IEnumerable compare, string compareTo) + // pad with '=' + for (var j = input.Length; j < inArray.Length; j++) { - return compare.Contains(compareTo, StringComparer.InvariantCultureIgnoreCase); + inArray[j] = '='; } - public static int InvariantIndexOf(this string s, string value) + return Convert.FromBase64CharArray(inArray, 0, inArray.Length); + } + + /// + /// Encodes a string so that it is 'safe' for URLs, files, etc.. + /// + /// + /// + public static string UrlTokenEncode(this byte[] input) + { + if (input == null) { - return s.IndexOf(value, StringComparison.OrdinalIgnoreCase); + throw new ArgumentNullException(nameof(input)); } - public static int InvariantLastIndexOf(this string s, string value) + if (input.Length == 0) { - return s.LastIndexOf(value, StringComparison.OrdinalIgnoreCase); + return string.Empty; } + // base-64 digits are A-Z, a-z, 0-9, + and / + // the = char is used for trailing padding + + var str = Convert.ToBase64String(input); - /// - /// Tries to parse a string into the supplied type by finding and using the Type's "Parse" method - /// - /// - /// - /// - public static T? ParseInto(this string val) + var pos = str.IndexOf('='); + if (pos < 0) { - return (T?)val.ParseInto(typeof(T)); + pos = str.Length; } - /// - /// Tries to parse a string into the supplied type by finding and using the Type's "Parse" method - /// - /// - /// - /// - public static object? ParseInto(this string val, Type type) + // replace chars that would cause problems in URLs + var chArray = new char[pos]; + for (var i = 0; i < pos; i++) { - if (string.IsNullOrEmpty(val) == false) - { - TypeConverter tc = TypeDescriptor.GetConverter(type); - return tc.ConvertFrom(val); - } - return val; - } - - /// - /// Generates a hash of a string based on the FIPS compliance setting. - /// - /// Refers to itself - /// The hashed string - public static string GenerateHash(this string str) => str.ToSHA1(); - - /// - /// Generate a hash of a string based on the specified hash algorithm. - /// - /// The hash algorithm implementation to use. - /// The to hash. - /// - /// The hashed string. - /// - public static string GenerateHash(this string str) - where T : HashAlgorithm => str.GenerateHash(typeof(T).FullName); - - /// - /// Converts the string to SHA1 - /// - /// refers to itself - /// The SHA1 hashed string - public static string ToSHA1(this string stringToConvert) => stringToConvert.GenerateHash("SHA1"); - - /// Generate a hash of a string based on the hashType passed in - /// - /// Refers to itself - /// String with the hash type. See remarks section of the CryptoConfig Class in MSDN docs for a list of possible values. - /// The hashed string - private static string GenerateHash(this string str, string? hashType) - { - HashAlgorithm? hasher = null; - //create an instance of the correct hashing provider based on the type passed in - if (hashType is not null) + var ch = str[i]; + switch (ch) { - hasher = HashAlgorithm.Create(hashType); - } - - if (hasher == null) throw new InvalidOperationException("No hashing type found by name " + hashType); - using (hasher) - { - //convert our string into byte array - var byteArray = Encoding.UTF8.GetBytes(str); - - //get the hashed values created by our selected provider - var hashedByteArray = hasher.ComputeHash(byteArray); - - //create a StringBuilder object - var stringBuilder = new StringBuilder(); + case '+': // replace '+' with '-' + chArray[i] = '-'; + break; - //loop to each byte - foreach (var b in hashedByteArray) - { - //append it to our StringBuilder - stringBuilder.Append(b.ToString("x2")); - } + case '/': // replace '/' with '_' + chArray[i] = '_'; + break; - //return the hashed value - return stringBuilder.ToString(); + default: // keep char unchanged + chArray[i] = ch; + break; } } - /// - /// Decodes a string that was encoded with UrlTokenEncode - /// - /// - /// - public static byte[] UrlTokenDecode(this string input) - { - if (input == null) - throw new ArgumentNullException(nameof(input)); - - if (input.Length == 0) - return Array.Empty(); - - // calc array size - must be groups of 4 - var arrayLength = input.Length; - var remain = arrayLength % 4; - if (remain != 0) arrayLength += 4 - remain; - - var inArray = new char[arrayLength]; - for (var i = 0; i < input.Length; i++) - { - var ch = input[i]; - switch (ch) - { - case '-': // restore '-' as '+' - inArray[i] = '+'; - break; - - case '_': // restore '_' as '/' - inArray[i] = '/'; - break; - - default: // keep char unchanged - inArray[i] = ch; - break; - } - } + return new string(chArray); + } - // pad with '=' - for (var j = input.Length; j < inArray.Length; j++) - inArray[j] = '='; + /// + /// Ensures that the folder path ends with a DirectorySeparatorChar + /// + /// + /// + public static string NormaliseDirectoryPath(this string currentFolder) + { + currentFolder = currentFolder + .IfNull(x => string.Empty) + .TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; + return currentFolder; + } - return Convert.FromBase64CharArray(inArray, 0, inArray.Length); - } + /// + /// Truncates the specified text string. + /// + /// The text. + /// Length of the max. + /// The suffix. + /// + public static string Truncate(this string text, int maxLength, string suffix = "...") + { + // replaces the truncated string to a ... + var truncatedString = text; - /// - /// Encodes a string so that it is 'safe' for URLs, files, etc.. - /// - /// - /// - public static string UrlTokenEncode(this byte[] input) + if (maxLength <= 0) { - if (input == null) - throw new ArgumentNullException(nameof(input)); - - if (input.Length == 0) - return string.Empty; - - // base-64 digits are A-Z, a-z, 0-9, + and / - // the = char is used for trailing padding - - var str = Convert.ToBase64String(input); - - var pos = str.IndexOf('='); - if (pos < 0) pos = str.Length; - - // replace chars that would cause problems in URLs - var chArray = new char[pos]; - for (var i = 0; i < pos; i++) - { - var ch = str[i]; - switch (ch) - { - case '+': // replace '+' with '-' - chArray[i] = '-'; - break; - - case '/': // replace '/' with '_' - chArray[i] = '_'; - break; - - default: // keep char unchanged - chArray[i] = ch; - break; - } - } - - return new string(chArray); + return truncatedString; } - /// - /// Ensures that the folder path ends with a DirectorySeparatorChar - /// - /// - /// - public static string NormaliseDirectoryPath(this string currentFolder) - { - currentFolder = currentFolder - .IfNull(x => String.Empty) - .TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; - return currentFolder; - } + var strLength = maxLength - suffix.Length; - /// - /// Truncates the specified text string. - /// - /// The text. - /// Length of the max. - /// The suffix. - /// - public static string Truncate(this string text, int maxLength, string suffix = "...") + if (strLength <= 0) { - // replaces the truncated string to a ... - var truncatedString = text; - - if (maxLength <= 0) return truncatedString; - var strLength = maxLength - suffix.Length; - - if (strLength <= 0) return truncatedString; - - if (text == null || text.Length <= maxLength) return truncatedString; - - truncatedString = text.Substring(0, strLength); - truncatedString = truncatedString.TrimEnd(); - truncatedString += suffix; - return truncatedString; } - /// - /// Strips carrage returns and line feeds from the specified text. - /// - /// The input. - /// - public static string StripNewLines(this string input) + if (text == null || text.Length <= maxLength) { - return input.Replace("\r", "").Replace("\n", ""); + return truncatedString; } - /// - /// Converts to single line by replacing line breaks with spaces. - /// - public static string ToSingleLine(this string text) + truncatedString = text.Substring(0, strLength); + truncatedString = truncatedString.TrimEnd(); + truncatedString += suffix; + + return truncatedString; + } + + /// + /// Strips carrage returns and line feeds from the specified text. + /// + /// The input. + /// + public static string StripNewLines(this string input) => input.Replace("\r", "").Replace("\n", ""); + + /// + /// Converts to single line by replacing line breaks with spaces. + /// + public static string ToSingleLine(this string text) + { + if (string.IsNullOrEmpty(text)) { - if (string.IsNullOrEmpty(text)) return text; - text = text.Replace("\r\n", " "); // remove CRLF - text = text.Replace("\r", " "); // remove CR - text = text.Replace("\n", " "); // remove LF return text; } - public static string OrIfNullOrWhiteSpace(this string input, string alternative) - { - return !string.IsNullOrWhiteSpace(input) - ? input - : alternative; - } + text = text.Replace("\r\n", " "); // remove CRLF + text = text.Replace("\r", " "); // remove CR + text = text.Replace("\n", " "); // remove LF + return text; + } - /// - /// Returns a copy of the string with the first character converted to uppercase. - /// - /// The string. - /// The converted string. - public static string ToFirstUpper(this string input) + public static string OrIfNullOrWhiteSpace(this string input, string alternative) => + !string.IsNullOrWhiteSpace(input) + ? input + : alternative; + + /// + /// Returns a copy of the string with the first character converted to uppercase. + /// + /// The string. + /// The converted string. + public static string ToFirstUpper(this string input) => + string.IsNullOrWhiteSpace(input) + ? input + : input.Substring(0, 1).ToUpper() + input.Substring(1); + + /// + /// Returns a copy of the string with the first character converted to lowercase. + /// + /// The string. + /// The converted string. + public static string ToFirstLower(this string input) => + string.IsNullOrWhiteSpace(input) + ? input + : input.Substring(0, 1).ToLower() + input.Substring(1); + + /// + /// Returns a copy of the string with the first character converted to uppercase using the casing rules of the + /// specified culture. + /// + /// The string. + /// The culture. + /// The converted string. + public static string ToFirstUpper(this string input, CultureInfo culture) => + string.IsNullOrWhiteSpace(input) + ? input + : input.Substring(0, 1).ToUpper(culture) + input.Substring(1); + + /// + /// Returns a copy of the string with the first character converted to lowercase using the casing rules of the + /// specified culture. + /// + /// The string. + /// The culture. + /// The converted string. + public static string ToFirstLower(this string input, CultureInfo culture) => + string.IsNullOrWhiteSpace(input) + ? input + : input.Substring(0, 1).ToLower(culture) + input.Substring(1); + + /// + /// Returns a copy of the string with the first character converted to uppercase using the casing rules of the + /// invariant culture. + /// + /// The string. + /// The converted string. + public static string ToFirstUpperInvariant(this string input) => + string.IsNullOrWhiteSpace(input) + ? input + : input.Substring(0, 1).ToUpperInvariant() + input.Substring(1); + + /// + /// Returns a copy of the string with the first character converted to lowercase using the casing rules of the + /// invariant culture. + /// + /// The string. + /// The converted string. + public static string ToFirstLowerInvariant(this string input) => + string.IsNullOrWhiteSpace(input) + ? input + : input.Substring(0, 1).ToLowerInvariant() + input.Substring(1); + + /// + /// Returns a new string in which all occurrences of specified strings are replaced by other specified strings. + /// + /// The string to filter. + /// The replacements definition. + /// The filtered string. + public static string ReplaceMany(this string text, IDictionary replacements) + { + if (text == null) { - return string.IsNullOrWhiteSpace(input) - ? input - : input.Substring(0, 1).ToUpper() + input.Substring(1); + throw new ArgumentNullException(nameof(text)); } - /// - /// Returns a copy of the string with the first character converted to lowercase. - /// - /// The string. - /// The converted string. - public static string ToFirstLower(this string input) + if (replacements == null) { - return string.IsNullOrWhiteSpace(input) - ? input - : input.Substring(0, 1).ToLower() + input.Substring(1); + throw new ArgumentNullException(nameof(replacements)); } - /// - /// Returns a copy of the string with the first character converted to uppercase using the casing rules of the specified culture. - /// - /// The string. - /// The culture. - /// The converted string. - public static string ToFirstUpper(this string input, CultureInfo culture) + + foreach (KeyValuePair item in replacements) { - return string.IsNullOrWhiteSpace(input) - ? input - : input.Substring(0, 1).ToUpper(culture) + input.Substring(1); + text = text.Replace(item.Key, item.Value); } - /// - /// Returns a copy of the string with the first character converted to lowercase using the casing rules of the specified culture. - /// - /// The string. - /// The culture. - /// The converted string. - public static string ToFirstLower(this string input, CultureInfo culture) + return text; + } + + /// + /// Returns a new string in which all occurrences of specified characters are replaced by a specified character. + /// + /// The string to filter. + /// The characters to replace. + /// The replacement character. + /// The filtered string. + public static string ReplaceMany(this string text, char[] chars, char replacement) + { + if (text == null) { - return string.IsNullOrWhiteSpace(input) - ? input - : input.Substring(0, 1).ToLower(culture) + input.Substring(1); + throw new ArgumentNullException(nameof(text)); } - /// - /// Returns a copy of the string with the first character converted to uppercase using the casing rules of the invariant culture. - /// - /// The string. - /// The converted string. - public static string ToFirstUpperInvariant(this string input) + if (chars == null) { - return string.IsNullOrWhiteSpace(input) - ? input - : input.Substring(0, 1).ToUpperInvariant() + input.Substring(1); + throw new ArgumentNullException(nameof(chars)); } - /// - /// Returns a copy of the string with the first character converted to lowercase using the casing rules of the invariant culture. - /// - /// The string. - /// The converted string. - public static string ToFirstLowerInvariant(this string input) + + for (var i = 0; i < chars.Length; i++) { - return string.IsNullOrWhiteSpace(input) - ? input - : input.Substring(0, 1).ToLowerInvariant() + input.Substring(1); + text = text.Replace(chars[i], replacement); } - /// - /// Returns a new string in which all occurrences of specified strings are replaced by other specified strings. - /// - /// The string to filter. - /// The replacements definition. - /// The filtered string. - public static string ReplaceMany(this string text, IDictionary replacements) - { - if (text == null) throw new ArgumentNullException(nameof(text)); - if (replacements == null) throw new ArgumentNullException(nameof(replacements)); + return text; + } + /// + /// Returns a new string in which only the first occurrence of a specified string is replaced by a specified + /// replacement string. + /// + /// The string to filter. + /// The string to replace. + /// The replacement string. + /// The filtered string. + public static string ReplaceFirst(this string text, string search, string replace) + { + if (text == null) + { + throw new ArgumentNullException(nameof(text)); + } - foreach (KeyValuePair item in replacements) - text = text.Replace(item.Key, item.Value); + var pos = text.IndexOf(search, StringComparison.InvariantCulture); + if (pos < 0) + { return text; } - /// - /// Returns a new string in which all occurrences of specified characters are replaced by a specified character. - /// - /// The string to filter. - /// The characters to replace. - /// The replacement character. - /// The filtered string. - public static string ReplaceMany(this string text, char[] chars, char replacement) - { - if (text == null) throw new ArgumentNullException(nameof(text)); - if (chars == null) throw new ArgumentNullException(nameof(chars)); - + return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); + } - for (int i = 0; i < chars.Length; i++) - text = text.Replace(chars[i], replacement); - return text; - } + /// + /// An extension method that returns a new string in which all occurrences of a + /// specified string in the current instance are replaced with another specified string. + /// StringComparison specifies the type of search to use for the specified string. + /// + /// Current instance of the string + /// Specified string to replace + /// Specified string to inject + /// String Comparison object to specify search type + /// Updated string + public static string Replace(this string source, string oldString, string newString, + StringComparison stringComparison) + { + // This initialization ensures the first check starts at index zero of the source. On successive checks for + // a match, the source is skipped to immediately after the last replaced occurrence for efficiency + // and to avoid infinite loops when oldString and newString compare equal. + var index = -1 * newString.Length; - /// - /// Returns a new string in which only the first occurrence of a specified string is replaced by a specified replacement string. - /// - /// The string to filter. - /// The string to replace. - /// The replacement string. - /// The filtered string. - public static string ReplaceFirst(this string text, string search, string replace) + // Determine if there are any matches left in source, starting from just after the result of replacing the last match. + while ((index = source.IndexOf(oldString, index + newString.Length, stringComparison)) >= 0) { - if (text == null) throw new ArgumentNullException(nameof(text)); + // Remove the old text. + source = source.Remove(index, oldString.Length); - var pos = text.IndexOf(search, StringComparison.InvariantCulture); + // Add the replacement text. + source = source.Insert(index, newString); + } - if (pos < 0) - return text; + return source; + } - return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); + /// + /// Converts a literal string into a C# expression. + /// + /// Current instance of the string. + /// The string in a C# format. + public static string ToCSharpString(this string s) + { + if (s == null) + { + return ""; } + // http://stackoverflow.com/questions/323640/can-i-convert-a-c-sharp-string-value-to-an-escaped-string-literal - - /// - /// An extension method that returns a new string in which all occurrences of a - /// specified string in the current instance are replaced with another specified string. - /// StringComparison specifies the type of search to use for the specified string. - /// - /// Current instance of the string - /// Specified string to replace - /// Specified string to inject - /// String Comparison object to specify search type - /// Updated string - public static string Replace(this string source, string oldString, string newString, StringComparison stringComparison) + var sb = new StringBuilder(s.Length + 2); + for (var rp = 0; rp < s.Length; rp++) { - // This initialization ensures the first check starts at index zero of the source. On successive checks for - // a match, the source is skipped to immediately after the last replaced occurrence for efficiency - // and to avoid infinite loops when oldString and newString compare equal. - int index = -1 * newString.Length; - - // Determine if there are any matches left in source, starting from just after the result of replacing the last match. - while ((index = source.IndexOf(oldString, index + newString.Length, stringComparison)) >= 0) + var c = s[rp]; + if (c < ToCSharpEscapeChars.Length && '\0' != ToCSharpEscapeChars[c]) { - // Remove the old text. - source = source.Remove(index, oldString.Length); - - // Add the replacement text. - source = source.Insert(index, newString); + sb.Append('\\').Append(ToCSharpEscapeChars[c]); } - - return source; - } - - /// - /// Converts a literal string into a C# expression. - /// - /// Current instance of the string. - /// The string in a C# format. - public static string ToCSharpString(this string s) - { - if (s == null) return ""; - - // http://stackoverflow.com/questions/323640/can-i-convert-a-c-sharp-string-value-to-an-escaped-string-literal - - var sb = new StringBuilder(s.Length + 2); - for (var rp = 0; rp < s.Length; rp++) + else if ('~' >= c && c >= ' ') { - var c = s[rp]; - if (c < ToCSharpEscapeChars.Length && '\0' != ToCSharpEscapeChars[c]) - sb.Append('\\').Append(ToCSharpEscapeChars[c]); - else if ('~' >= c && c >= ' ') - sb.Append(c); - else - sb.Append(@"\x") - .Append(ToCSharpHexDigitLower[c >> 12 & 0x0F]) - .Append(ToCSharpHexDigitLower[c >> 8 & 0x0F]) - .Append(ToCSharpHexDigitLower[c >> 4 & 0x0F]) - .Append(ToCSharpHexDigitLower[c & 0x0F]); + sb.Append(c); } - - return sb.ToString(); - - // requires full trust - /* - using (var writer = new StringWriter()) - using (var provider = CodeDomProvider.CreateProvider("CSharp")) + else { - provider.GenerateCodeFromExpression(new CodePrimitiveExpression(s), writer, null); - return writer.ToString().Replace(string.Format("\" +{0}\t\"", Environment.NewLine), ""); + sb.Append(@"\x") + .Append(ToCSharpHexDigitLower[(c >> 12) & 0x0F]) + .Append(ToCSharpHexDigitLower[(c >> 8) & 0x0F]) + .Append(ToCSharpHexDigitLower[(c >> 4) & 0x0F]) + .Append(ToCSharpHexDigitLower[c & 0x0F]); } - */ } - public static string EscapeRegexSpecialCharacters(this string text) - { - var regexSpecialCharacters = new Dictionary - { - {".", @"\."}, - {"(", @"\("}, - {")", @"\)"}, - {"]", @"\]"}, - {"[", @"\["}, - {"{", @"\{"}, - {"}", @"\}"}, - {"?", @"\?"}, - {"!", @"\!"}, - {"$", @"\$"}, - {"^", @"\^"}, - {"+", @"\+"}, - {"*", @"\*"}, - {"|", @"\|"}, - {"<", @"\<"}, - {">", @"\>"} - }; - return ReplaceMany(text, regexSpecialCharacters); - } - - /// - /// Checks whether a string "haystack" contains within it any of the strings in the "needles" collection and returns true if it does or false if it doesn't - /// - /// The string to check - /// The collection of strings to check are contained within the first string - /// The type of comparison to perform - defaults to - /// True if any of the needles are contained with haystack; otherwise returns false - /// Added fix to ensure the comparison is used - see http://issues.umbraco.org/issue/U4-11313 - public static bool ContainsAny(this string haystack, IEnumerable needles, StringComparison comparison = StringComparison.CurrentCulture) - { - if (haystack == null) - throw new ArgumentNullException("haystack"); - - if (string.IsNullOrEmpty(haystack) || needles == null || !needles.Any()) - { - return false; - } + return sb.ToString(); - return needles.Any(value => haystack.IndexOf(value, comparison) >= 0); + // requires full trust + /* + using (var writer = new StringWriter()) + using (var provider = CodeDomProvider.CreateProvider("CSharp")) + { + provider.GenerateCodeFromExpression(new CodePrimitiveExpression(s), writer, null); + return writer.ToString().Replace(string.Format("\" +{0}\t\"", Environment.NewLine), ""); } + */ + } - public static bool CsvContains(this string csv, string value) + public static string EscapeRegexSpecialCharacters(this string text) + { + var regexSpecialCharacters = new Dictionary + { + {".", @"\."}, + {"(", @"\("}, + {")", @"\)"}, + {"]", @"\]"}, + {"[", @"\["}, + {"{", @"\{"}, + {"}", @"\}"}, + {"?", @"\?"}, + {"!", @"\!"}, + {"$", @"\$"}, + {"^", @"\^"}, + {"+", @"\+"}, + {"*", @"\*"}, + {"|", @"\|"}, + {"<", @"\<"}, + {">", @"\>"} + }; + return ReplaceMany(text, regexSpecialCharacters); + } + + /// + /// Checks whether a string "haystack" contains within it any of the strings in the "needles" collection and returns + /// true if it does or false if it doesn't + /// + /// The string to check + /// The collection of strings to check are contained within the first string + /// + /// The type of comparison to perform - defaults to + /// + /// True if any of the needles are contained with haystack; otherwise returns false + /// Added fix to ensure the comparison is used - see http://issues.umbraco.org/issue/U4-11313 + public static bool ContainsAny(this string haystack, IEnumerable needles, + StringComparison comparison = StringComparison.CurrentCulture) + { + if (haystack == null) { - if (string.IsNullOrEmpty(csv)) - { - return false; - } - var idCheckList = csv.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); - return idCheckList.Contains(value); + throw new ArgumentNullException("haystack"); } - /// - /// Converts a file name to a friendly name for a content item - /// - /// - /// - public static string ToFriendlyName(this string fileName) + if (string.IsNullOrEmpty(haystack) || needles == null || !needles.Any()) { - // strip the file extension - fileName = fileName.StripFileExtension(); + return false; + } - // underscores and dashes to spaces - fileName = fileName.ReplaceMany(Constants.CharArrays.UnderscoreDash, ' '); + return needles.Any(value => haystack.IndexOf(value, comparison) >= 0); + } - // any other conversions ? + public static bool CsvContains(this string csv, string value) + { + if (string.IsNullOrEmpty(csv)) + { + return false; + } - // Pascalcase (to be done last) - fileName = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(fileName); + var idCheckList = csv.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); + return idCheckList.Contains(value); + } - // Replace multiple consecutive spaces with a single space - fileName = string.Join(" ", fileName.Split(Constants.CharArrays.Space, StringSplitOptions.RemoveEmptyEntries)); + /// + /// Converts a file name to a friendly name for a content item + /// + /// + /// + public static string ToFriendlyName(this string fileName) + { + // strip the file extension + fileName = fileName.StripFileExtension(); - return fileName; - } + // underscores and dashes to spaces + fileName = fileName.ReplaceMany(Constants.CharArrays.UnderscoreDash, ' '); - // From: http://stackoverflow.com/a/961504/5018 - // filters control characters but allows only properly-formed surrogate sequences - private static readonly Lazy InvalidXmlChars = new Lazy(() => - new Regex( - @"(? - /// An extension method that returns a new string in which all occurrences of an - /// unicode characters that are invalid in XML files are replaced with an empty string. - /// - /// Current instance of the string - /// Updated string - /// - /// - /// removes any unusual unicode characters that can't be encoded into XML - /// - public static string ToValidXmlString(this string text) - { - return string.IsNullOrEmpty(text) ? text : InvalidXmlChars.Value.Replace(text, ""); - } - - /// - /// Converts a string to a Guid - WARNING, depending on the string, this may not be unique - /// - /// - /// - public static Guid ToGuid(this string text) - { - return CreateGuidFromHash(UrlNamespace, - text, - CryptoConfig.AllowOnlyFipsAlgorithms - ? 5 // SHA1 - : 3); // MD5 - } - - /// - /// The namespace for URLs (from RFC 4122, Appendix C). - /// - /// See RFC 4122 - /// - internal static readonly Guid UrlNamespace = new Guid("6ba7b811-9dad-11d1-80b4-00c04fd430c8"); - - /// - /// Creates a name-based UUID using the algorithm from RFC 4122 §4.3. - /// - /// See GuidUtility.cs for original implementation. - /// - /// The ID of the namespace. - /// The name (within that namespace). - /// The version number of the UUID to create; this value must be either - /// 3 (for MD5 hashing) or 5 (for SHA-1 hashing). - /// A UUID derived from the namespace and name. - /// See Generating a deterministic GUID. - internal static Guid CreateGuidFromHash(Guid namespaceId, string name, int version) - { - if (name == null) - throw new ArgumentNullException("name"); - if (version != 3 && version != 5) - throw new ArgumentOutOfRangeException("version", "version must be either 3 or 5."); - - // convert the name to a sequence of octets (as defined by the standard or conventions of its namespace) (step 3) - // ASSUME: UTF-8 encoding is always appropriate - byte[] nameBytes = Encoding.UTF8.GetBytes(name); - - // convert the namespace UUID to network order (step 3) - byte[] namespaceBytes = namespaceId.ToByteArray(); - SwapByteOrder(namespaceBytes); - - // comput the hash of the name space ID concatenated with the name (step 4) - byte[] hash; - using (HashAlgorithm algorithm = version == 3 ? (HashAlgorithm)MD5.Create() : SHA1.Create()) - { - algorithm.TransformBlock(namespaceBytes, 0, namespaceBytes.Length, null, 0); - algorithm.TransformFinalBlock(nameBytes, 0, nameBytes.Length); - hash = algorithm.Hash!; - } + // any other conversions ? - // most bytes from the hash are copied straight to the bytes of the new GUID (steps 5-7, 9, 11-12) - byte[] newGuid = new byte[16]; - Array.Copy(hash, 0, newGuid, 0, 16); + // Pascalcase (to be done last) + fileName = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(fileName); - // set the four most significant bits (bits 12 through 15) of the time_hi_and_version field to the appropriate 4-bit version number from Section 4.1.3 (step 8) - newGuid[6] = (byte)((newGuid[6] & 0x0F) | (version << 4)); + // Replace multiple consecutive spaces with a single space + fileName = string.Join(" ", fileName.Split(Constants.CharArrays.Space, StringSplitOptions.RemoveEmptyEntries)); - // set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively (step 10) - newGuid[8] = (byte)((newGuid[8] & 0x3F) | 0x80); + return fileName; + } - // convert the resulting UUID to local byte order (step 13) - SwapByteOrder(newGuid); - return new Guid(newGuid); - } - // Converts a GUID (expressed as a byte array) to/from network order (MSB-first). - internal static void SwapByteOrder(byte[] guid) + /// + /// An extension method that returns a new string in which all occurrences of an + /// unicode characters that are invalid in XML files are replaced with an empty string. + /// + /// Current instance of the string + /// Updated string + /// + /// removes any unusual unicode characters that can't be encoded into XML + /// + public static string ToValidXmlString(this string text) => + string.IsNullOrEmpty(text) ? text : InvalidXmlChars.Value.Replace(text, ""); + + /// + /// Converts a string to a Guid - WARNING, depending on the string, this may not be unique + /// + /// + /// + public static Guid ToGuid(this string text) => + CreateGuidFromHash(UrlNamespace, + text, + CryptoConfig.AllowOnlyFipsAlgorithms + ? 5 // SHA1 + : 3); // MD5 + + /// + /// Creates a name-based UUID using the algorithm from RFC 4122 §4.3. + /// See + /// GuidUtility.cs + /// for original implementation. + /// + /// The ID of the namespace. + /// The name (within that namespace). + /// + /// The version number of the UUID to create; this value must be either + /// 3 (for MD5 hashing) or 5 (for SHA-1 hashing). + /// + /// A UUID derived from the namespace and name. + /// + /// See + /// Generating a deterministic GUID + /// . + /// + internal static Guid CreateGuidFromHash(Guid namespaceId, string name, int version) + { + if (name == null) { - SwapBytes(guid, 0, 3); - SwapBytes(guid, 1, 2); - SwapBytes(guid, 4, 5); - SwapBytes(guid, 6, 7); + throw new ArgumentNullException("name"); } - private static void SwapBytes(byte[] guid, int left, int right) + if (version != 3 && version != 5) { - byte temp = guid[left]; - guid[left] = guid[right]; - guid[right] = temp; + throw new ArgumentOutOfRangeException("version", "version must be either 3 or 5."); } - /// - /// Turns an null-or-whitespace string into a null string. - /// - public static string? NullOrWhiteSpaceAsNull(this string text) - => string.IsNullOrWhiteSpace(text) ? null : text; + // convert the name to a sequence of octets (as defined by the standard or conventions of its namespace) (step 3) + // ASSUME: UTF-8 encoding is always appropriate + var nameBytes = Encoding.UTF8.GetBytes(name); + // convert the namespace UUID to network order (step 3) + var namespaceBytes = namespaceId.ToByteArray(); + SwapByteOrder(namespaceBytes); - /// - /// Checks if a given path is a full path including drive letter - /// - /// - /// - // From: http://stackoverflow.com/a/35046453/5018 - public static bool IsFullPath(this string path) + // comput the hash of the name space ID concatenated with the name (step 4) + byte[] hash; + using (HashAlgorithm algorithm = version == 3 ? MD5.Create() : SHA1.Create()) { - return string.IsNullOrWhiteSpace(path) == false - && path.IndexOfAny(Path.GetInvalidPathChars().ToArray()) == -1 - && Path.IsPathRooted(path) - && Path.GetPathRoot(path)?.Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) == false; + algorithm.TransformBlock(namespaceBytes, 0, namespaceBytes.Length, null, 0); + algorithm.TransformFinalBlock(nameBytes, 0, nameBytes.Length); + hash = algorithm.Hash!; } - // FORMAT STRINGS + // most bytes from the hash are copied straight to the bytes of the new GUID (steps 5-7, 9, 11-12) + var newGuid = new byte[16]; + Array.Copy(hash, 0, newGuid, 0, 16); - /// - /// Cleans a string to produce a string that can safely be used in an alias. - /// - /// The text to filter. - /// The short string helper. - /// The safe alias. - public static string ToSafeAlias(this string alias, IShortStringHelper? shortStringHelper) - { - return shortStringHelper?.CleanStringForSafeAlias(alias) ?? string.Empty; - } - - /// - /// Cleans a string to produce a string that can safely be used in an alias. - /// - /// The text to filter. - /// A value indicating that we want to camel-case the alias. - /// The short string helper. - /// The safe alias. - public static string ToSafeAlias(this string alias, IShortStringHelper shortStringHelper, bool camel) - { - var a = shortStringHelper.CleanStringForSafeAlias(alias); - if (string.IsNullOrWhiteSpace(a) || camel == false) return a; - return char.ToLowerInvariant(a[0]) + a.Substring(1); - } - - /// - /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an alias. - /// - /// The text to filter. - /// The culture. - /// The short string helper. - /// The safe alias. - public static string ToSafeAlias(this string alias, IShortStringHelper shortStringHelper, string culture) - { - return shortStringHelper.CleanStringForSafeAlias(alias, culture); - } + // set the four most significant bits (bits 12 through 15) of the time_hi_and_version field to the appropriate 4-bit version number from Section 4.1.3 (step 8) + newGuid[6] = (byte)((newGuid[6] & 0x0F) | (version << 4)); + // set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively (step 10) + newGuid[8] = (byte)((newGuid[8] & 0x3F) | 0x80); - // the new methods to get a url segment + // convert the resulting UUID to local byte order (step 13) + SwapByteOrder(newGuid); + return new Guid(newGuid); + } - /// - /// Cleans a string to produce a string that can safely be used in an url segment. - /// - /// The text to filter. - /// The short string helper. - /// The safe url segment. - public static string ToUrlSegment(this string text, IShortStringHelper shortStringHelper) - { - if (text == null) throw new ArgumentNullException(nameof(text)); - if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(text)); + // Converts a GUID (expressed as a byte array) to/from network order (MSB-first). + internal static void SwapByteOrder(byte[] guid) + { + SwapBytes(guid, 0, 3); + SwapBytes(guid, 1, 2); + SwapBytes(guid, 4, 5); + SwapBytes(guid, 6, 7); + } - return shortStringHelper.CleanStringForUrlSegment(text); - } + private static void SwapBytes(byte[] guid, int left, int right) + { + var temp = guid[left]; + guid[left] = guid[right]; + guid[right] = temp; + } - /// - /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an url segment. - /// - /// The text to filter. - /// The short string helper. - /// The culture. - /// The safe url segment. - public static string ToUrlSegment(this string text, IShortStringHelper shortStringHelper, string? culture) + /// + /// Turns an null-or-whitespace string into a null string. + /// + public static string? NullOrWhiteSpaceAsNull(this string text) + => string.IsNullOrWhiteSpace(text) ? null : text; + + + /// + /// Checks if a given path is a full path including drive letter + /// + /// + /// + // From: http://stackoverflow.com/a/35046453/5018 + public static bool IsFullPath(this string path) => + string.IsNullOrWhiteSpace(path) == false + && path.IndexOfAny(Path.GetInvalidPathChars().ToArray()) == -1 + && Path.IsPathRooted(path) + && Path.GetPathRoot(path)?.Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) == false; + + // FORMAT STRINGS + + /// + /// Cleans a string to produce a string that can safely be used in an alias. + /// + /// The text to filter. + /// The short string helper. + /// The safe alias. + public static string ToSafeAlias(this string alias, IShortStringHelper? shortStringHelper) => + shortStringHelper?.CleanStringForSafeAlias(alias) ?? string.Empty; + + /// + /// Cleans a string to produce a string that can safely be used in an alias. + /// + /// The text to filter. + /// A value indicating that we want to camel-case the alias. + /// The short string helper. + /// The safe alias. + public static string ToSafeAlias(this string alias, IShortStringHelper shortStringHelper, bool camel) + { + var a = shortStringHelper.CleanStringForSafeAlias(alias); + if (string.IsNullOrWhiteSpace(a) || camel == false) { - if (text == null) throw new ArgumentNullException(nameof(text)); - if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(text)); - - return shortStringHelper.CleanStringForUrlSegment(text, culture); + return a; } + return char.ToLowerInvariant(a[0]) + a.Substring(1); + } - /// - /// Cleans a string. - /// - /// The text to clean. - /// The short string helper. - /// A flag indicating the target casing and encoding of the string. By default, - /// strings are cleaned up to camelCase and Ascii. - /// The clean string. - /// The string is cleaned in the context of the ICurrent.ShortStringHelper default culture. - public static string ToCleanString(this string text, IShortStringHelper shortStringHelper, CleanStringType stringType) + /// + /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an alias. + /// + /// The text to filter. + /// The culture. + /// The short string helper. + /// The safe alias. + public static string ToSafeAlias(this string alias, IShortStringHelper shortStringHelper, string culture) => + shortStringHelper.CleanStringForSafeAlias(alias, culture); + + + // the new methods to get a url segment + + /// + /// Cleans a string to produce a string that can safely be used in an url segment. + /// + /// The text to filter. + /// The short string helper. + /// The safe url segment. + public static string ToUrlSegment(this string text, IShortStringHelper shortStringHelper) + { + if (text == null) { - return shortStringHelper.CleanString(text, stringType); + throw new ArgumentNullException(nameof(text)); } - /// - /// Cleans a string, using a specified separator. - /// - /// The text to clean. - /// The short string helper. - /// A flag indicating the target casing and encoding of the string. By default, - /// strings are cleaned up to camelCase and Ascii. - /// The separator. - /// The clean string. - /// The string is cleaned in the context of the ICurrent.ShortStringHelper default culture. - public static string ToCleanString(this string text, IShortStringHelper shortStringHelper, CleanStringType stringType, char separator) + if (string.IsNullOrWhiteSpace(text)) { - return shortStringHelper.CleanString(text, stringType, separator); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(text)); } - /// - /// Cleans a string in the context of a specified culture. - /// - /// The text to clean. - /// The short string helper. - /// A flag indicating the target casing and encoding of the string. By default, - /// strings are cleaned up to camelCase and Ascii. - /// The culture. - /// The clean string. - public static string ToCleanString(this string text, IShortStringHelper shortStringHelper, CleanStringType stringType, string culture) + return shortStringHelper.CleanStringForUrlSegment(text); + } + + /// + /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an url + /// segment. + /// + /// The text to filter. + /// The short string helper. + /// The culture. + /// The safe url segment. + public static string ToUrlSegment(this string text, IShortStringHelper shortStringHelper, string? culture) + { + if (text == null) { - return shortStringHelper.CleanString(text, stringType, culture); + throw new ArgumentNullException(nameof(text)); } - /// - /// Cleans a string in the context of a specified culture, using a specified separator. - /// - /// The text to clean. - /// The short string helper. - /// A flag indicating the target casing and encoding of the string. By default, - /// strings are cleaned up to camelCase and Ascii. - /// The separator. - /// The culture. - /// The clean string. - public static string ToCleanString(this string text, IShortStringHelper shortStringHelper, CleanStringType stringType, char separator, string culture) + if (string.IsNullOrWhiteSpace(text)) { - return shortStringHelper.CleanString(text, stringType, separator, culture); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(text)); } - // note: LegacyCurrent.ShortStringHelper will produce 100% backward-compatible output for SplitPascalCasing. - // other helpers may not. DefaultCurrent.ShortStringHelper produces better, but non-compatible, results. - - /// - /// Splits a Pascal cased string into a phrase separated by spaces. - /// - /// The text to split. - /// - /// The split text. - public static string SplitPascalCasing(this string phrase, IShortStringHelper shortStringHelper) - { - return shortStringHelper.SplitPascalCasing(phrase, ' '); - } + return shortStringHelper.CleanStringForUrlSegment(text, culture); + } - //NOTE: Not sure what this actually does but is used a few places, need to figure it out and then move to StringExtensions and obsolete. - // it basically is yet another version of SplitPascalCasing - // plugging string extensions here to be 99% compatible - // the only diff. is with numbers, Number6Is was "Number6 Is", and the new string helper does it too, - // but the legacy one does "Number6Is"... assuming it is not a big deal. - internal static string SpaceCamelCasing(this string phrase, IShortStringHelper shortStringHelper) - { - return phrase.Length < 2 ? phrase : phrase.SplitPascalCasing(shortStringHelper).ToFirstUpperInvariant(); - } - /// - /// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a filename, - /// both internally (on disk) and externally (as a url). - /// - /// The text to filter. - /// - /// The safe filename. - public static string ToSafeFileName(this string text, IShortStringHelper shortStringHelper) + /// + /// Cleans a string. + /// + /// The text to clean. + /// The short string helper. + /// + /// A flag indicating the target casing and encoding of the string. By default, + /// strings are cleaned up to camelCase and Ascii. + /// + /// The clean string. + /// The string is cleaned in the context of the ICurrent.ShortStringHelper default culture. + public static string ToCleanString(this string text, IShortStringHelper shortStringHelper, + CleanStringType stringType) => shortStringHelper.CleanString(text, stringType); + + /// + /// Cleans a string, using a specified separator. + /// + /// The text to clean. + /// The short string helper. + /// + /// A flag indicating the target casing and encoding of the string. By default, + /// strings are cleaned up to camelCase and Ascii. + /// + /// The separator. + /// The clean string. + /// The string is cleaned in the context of the ICurrent.ShortStringHelper default culture. + public static string ToCleanString(this string text, IShortStringHelper shortStringHelper, + CleanStringType stringType, char separator) => shortStringHelper.CleanString(text, stringType, separator); + + /// + /// Cleans a string in the context of a specified culture. + /// + /// The text to clean. + /// The short string helper. + /// + /// A flag indicating the target casing and encoding of the string. By default, + /// strings are cleaned up to camelCase and Ascii. + /// + /// The culture. + /// The clean string. + public static string ToCleanString(this string text, IShortStringHelper shortStringHelper, + CleanStringType stringType, string culture) => shortStringHelper.CleanString(text, stringType, culture); + + /// + /// Cleans a string in the context of a specified culture, using a specified separator. + /// + /// The text to clean. + /// The short string helper. + /// + /// A flag indicating the target casing and encoding of the string. By default, + /// strings are cleaned up to camelCase and Ascii. + /// + /// The separator. + /// The culture. + /// The clean string. + public static string ToCleanString(this string text, IShortStringHelper shortStringHelper, + CleanStringType stringType, char separator, string culture) => + shortStringHelper.CleanString(text, stringType, separator, culture); + + // note: LegacyCurrent.ShortStringHelper will produce 100% backward-compatible output for SplitPascalCasing. + // other helpers may not. DefaultCurrent.ShortStringHelper produces better, but non-compatible, results. + + /// + /// Splits a Pascal cased string into a phrase separated by spaces. + /// + /// The text to split. + /// + /// The split text. + public static string SplitPascalCasing(this string phrase, IShortStringHelper shortStringHelper) => + shortStringHelper.SplitPascalCasing(phrase, ' '); + + //NOTE: Not sure what this actually does but is used a few places, need to figure it out and then move to StringExtensions and obsolete. + // it basically is yet another version of SplitPascalCasing + // plugging string extensions here to be 99% compatible + // the only diff. is with numbers, Number6Is was "Number6 Is", and the new string helper does it too, + // but the legacy one does "Number6Is"... assuming it is not a big deal. + internal static string SpaceCamelCasing(this string phrase, IShortStringHelper shortStringHelper) => + phrase.Length < 2 ? phrase : phrase.SplitPascalCasing(shortStringHelper).ToFirstUpperInvariant(); + + /// + /// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a + /// filename, + /// both internally (on disk) and externally (as a url). + /// + /// The text to filter. + /// + /// The safe filename. + public static string ToSafeFileName(this string text, IShortStringHelper shortStringHelper) => + shortStringHelper.CleanStringForSafeFileName(text); + + /// + /// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a + /// filename, + /// both internally (on disk) and externally (as a url). + /// + /// The text to filter. + /// + /// The culture. + /// The safe filename. + public static string ToSafeFileName(this string text, IShortStringHelper shortStringHelper, string culture) => + shortStringHelper.CleanStringForSafeFileName(text, culture); + + /// + /// Splits a string with an escape character that allows for the split character to exist in a string + /// + /// The string to split + /// The character to split on + /// The character which can be used to escape the character to split on + /// The string split into substrings delimited by the split character + public static IEnumerable EscapedSplit(this string value, char splitChar, + char escapeChar = DefaultEscapedStringEscapeChar) + { + if (value == null) { - return shortStringHelper.CleanStringForSafeFileName(text); + yield break; } - /// - /// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a filename, - /// both internally (on disk) and externally (as a url). - /// - /// The text to filter. - /// - /// The culture. - /// The safe filename. - public static string ToSafeFileName(this string text, IShortStringHelper shortStringHelper, string culture) - { - return shortStringHelper.CleanStringForSafeFileName(text, culture); - } - - /// - /// Splits a string with an escape character that allows for the split character to exist in a string - /// - /// The string to split - /// The character to split on - /// The character which can be used to escape the character to split on - /// The string split into substrings delimited by the split character - public static IEnumerable EscapedSplit(this string value, char splitChar, char escapeChar = DefaultEscapedStringEscapeChar) - { - if (value == null) yield break; - - var sb = new StringBuilder(value.Length); - var escaped = false; + var sb = new StringBuilder(value.Length); + var escaped = false; - foreach (var chr in value.ToCharArray()) + foreach (var chr in value.ToCharArray()) + { + if (escaped) { - if (escaped) - { - escaped = false; - sb.Append(chr); - } - else if (chr == splitChar) - { - yield return sb.ToString(); - sb.Clear(); - } - else if (chr == escapeChar) - { - escaped = true; - } - else - { - sb.Append(chr); - } + escaped = false; + sb.Append(chr); + } + else if (chr == splitChar) + { + yield return sb.ToString(); + sb.Clear(); + } + else if (chr == escapeChar) + { + escaped = true; + } + else + { + sb.Append(chr); } - - yield return sb.ToString(); } + + yield return sb.ToString(); } } diff --git a/src/Umbraco.Core/Extensions/ThreadExtensions.cs b/src/Umbraco.Core/Extensions/ThreadExtensions.cs index 1c585a2de892..536587b9ba11 100644 --- a/src/Umbraco.Core/Extensions/ThreadExtensions.cs +++ b/src/Umbraco.Core/Extensions/ThreadExtensions.cs @@ -2,53 +2,55 @@ // See LICENSE for more details. using System.Globalization; -using System.Threading; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class ThreadExtensions { - public static class ThreadExtensions + public static void SanitizeThreadCulture(this Thread thread) { - public static void SanitizeThreadCulture(this Thread thread) + // get the current culture + CultureInfo currentCulture = CultureInfo.CurrentCulture; + + // at the top of any culture should be the invariant culture - find it + // doing an .Equals comparison ensure that we *will* find it and not loop + // endlessly + CultureInfo invariantCulture = currentCulture; + while (invariantCulture.Equals(CultureInfo.InvariantCulture) == false) + { + invariantCulture = invariantCulture.Parent; + } + + // now that invariant culture should be the same object as CultureInfo.InvariantCulture + // yet for some reasons, sometimes it is not - and this breaks anything that loops on + // culture.Parent until a reference equality to CultureInfo.InvariantCulture. See, for + // example, the following code in PerformanceCounterLib.IsCustomCategory: + // + // CultureInfo culture = CultureInfo.CurrentCulture; + // while (culture != CultureInfo.InvariantCulture) + // { + // library = GetPerformanceCounterLib(machine, culture); + // if (library.IsCustomCategory(category)) + // return true; + // culture = culture.Parent; + // } + // + // The reference comparisons never succeeds, hence the loop never ends, and the + // application hangs. + // + // granted, that comparison should probably be a .Equals comparison, but who knows + // how many times the framework assumes that it can do a reference comparison? So, + // better fix the cultures. + + if (ReferenceEquals(invariantCulture, CultureInfo.InvariantCulture)) { - // get the current culture - var currentCulture = CultureInfo.CurrentCulture; - - // at the top of any culture should be the invariant culture - find it - // doing an .Equals comparison ensure that we *will* find it and not loop - // endlessly - var invariantCulture = currentCulture; - while (invariantCulture.Equals(CultureInfo.InvariantCulture) == false) - invariantCulture = invariantCulture.Parent; - - // now that invariant culture should be the same object as CultureInfo.InvariantCulture - // yet for some reasons, sometimes it is not - and this breaks anything that loops on - // culture.Parent until a reference equality to CultureInfo.InvariantCulture. See, for - // example, the following code in PerformanceCounterLib.IsCustomCategory: - // - // CultureInfo culture = CultureInfo.CurrentCulture; - // while (culture != CultureInfo.InvariantCulture) - // { - // library = GetPerformanceCounterLib(machine, culture); - // if (library.IsCustomCategory(category)) - // return true; - // culture = culture.Parent; - // } - // - // The reference comparisons never succeeds, hence the loop never ends, and the - // application hangs. - // - // granted, that comparison should probably be a .Equals comparison, but who knows - // how many times the framework assumes that it can do a reference comparison? So, - // better fix the cultures. - - if (ReferenceEquals(invariantCulture, CultureInfo.InvariantCulture)) - return; - - // if we do not have equality, fix cultures by replacing them with a culture with - // the same name, but obtained here and now, with a proper invariant top culture - - thread.CurrentCulture = CultureInfo.GetCultureInfo(thread.CurrentCulture.Name); - thread.CurrentUICulture = CultureInfo.GetCultureInfo(thread.CurrentUICulture.Name); + return; } + + // if we do not have equality, fix cultures by replacing them with a culture with + // the same name, but obtained here and now, with a proper invariant top culture + + thread.CurrentCulture = CultureInfo.GetCultureInfo(thread.CurrentCulture.Name); + thread.CurrentUICulture = CultureInfo.GetCultureInfo(thread.CurrentUICulture.Name); } } diff --git a/src/Umbraco.Core/Extensions/TypeExtensions.cs b/src/Umbraco.Core/Extensions/TypeExtensions.cs index bb43c2b5d94f..391e69bdb7de 100644 --- a/src/Umbraco.Core/Extensions/TypeExtensions.cs +++ b/src/Umbraco.Core/Extensions/TypeExtensions.cs @@ -1,492 +1,520 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using System.Collections; -using System.Collections.Generic; -using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Strings; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class TypeExtensions { - public static class TypeExtensions + public static object? GetDefaultValue(this Type t) => + t.IsValueType + ? Activator.CreateInstance(t) + : null; + + internal static MethodInfo? GetGenericMethod(this Type type, string name, params Type[] parameterTypes) { - public static object? GetDefaultValue(this Type t) - { - return t.IsValueType - ? Activator.CreateInstance(t) - : null; - } + IEnumerable methods = type.GetMethods().Where(method => method.Name == name); - internal static MethodInfo? GetGenericMethod(this Type type, string name, params Type[] parameterTypes) + foreach (MethodInfo method in methods) { - var methods = type.GetMethods().Where(method => method.Name == name); - - foreach (var method in methods) + if (method.HasParameters(parameterTypes)) { - if (method.HasParameters(parameterTypes)) - return method; + return method; } - - return null; } - /// - /// Checks if the type is an anonymous type - /// - /// - /// - /// - /// reference: http://jclaes.blogspot.com/2011/05/checking-for-anonymous-types.html - /// - public static bool IsAnonymousType(this Type type) + return null; + } + + /// + /// Checks if the type is an anonymous type + /// + /// + /// + /// + /// reference: http://jclaes.blogspot.com/2011/05/checking-for-anonymous-types.html + /// + public static bool IsAnonymousType(this Type type) + { + if (type == null) { - if (type == null) throw new ArgumentNullException("type"); + throw new ArgumentNullException("type"); + } - return Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false) - && type.IsGenericType && type.Name.Contains("AnonymousType") - && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$")) - && (type.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic; - } + return Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false) + && type.IsGenericType && type.Name.Contains("AnonymousType") + && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$")) + && (type.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic; + } + /// + /// Determines whether the specified type is enumerable. + /// + /// The type. + /// + internal static bool HasParameters(this MethodInfo method, params Type[] parameterTypes) + { + Type[] methodParameters = method.GetParameters().Select(parameter => parameter.ParameterType).ToArray(); - /// - /// Determines whether the specified type is enumerable. - /// - /// The type. - /// - internal static bool HasParameters(this MethodInfo method, params Type[] parameterTypes) + if (methodParameters.Length != parameterTypes.Length) { - var methodParameters = method.GetParameters().Select(parameter => parameter.ParameterType).ToArray(); + return false; + } - if (methodParameters.Length != parameterTypes.Length) + for (var i = 0; i < methodParameters.Length; i++) + { + if (methodParameters[i].ToString() != parameterTypes[i].ToString()) + { return false; + } + } - for (int i = 0; i < methodParameters.Length; i++) - if (methodParameters[i].ToString() != parameterTypes[i].ToString()) - return false; + return true; + } - return true; + public static IEnumerable GetBaseTypes(this Type? type, bool andSelf) + { + if (andSelf) + { + yield return type; } - public static IEnumerable GetBaseTypes(this Type? type, bool andSelf) + while ((type = type?.BaseType) != null) { - if (andSelf) - yield return type; - - while ((type = type?.BaseType) != null) - yield return type; + yield return type; } + } - public static IEnumerable AllMethods(this Type target) - { - //var allTypes = target.AllInterfaces().ToList(); - var allTypes = target.GetInterfaces().ToList(); // GetInterfaces is ok here - allTypes.Add(target); + public static IEnumerable AllMethods(this Type target) + { + //var allTypes = target.AllInterfaces().ToList(); + var allTypes = target.GetInterfaces().ToList(); // GetInterfaces is ok here + allTypes.Add(target); - return allTypes.SelectMany(t => t.GetMethods()); - } + return allTypes.SelectMany(t => t.GetMethods()); + } - /// - /// true if the specified type is enumerable; otherwise, false. - /// - public static bool IsEnumerable(this Type type) + /// + /// true if the specified type is enumerable; otherwise, false. + /// + public static bool IsEnumerable(this Type type) + { + if (type.IsGenericType) { - if (type.IsGenericType) + if (type.GetGenericTypeDefinition().GetInterfaces().Contains(typeof(IEnumerable))) { - if (type.GetGenericTypeDefinition().GetInterfaces().Contains(typeof(IEnumerable))) - return true; + return true; } - else + } + else + { + if (type.GetInterfaces().Contains(typeof(IEnumerable))) { - if (type.GetInterfaces().Contains(typeof(IEnumerable))) - return true; + return true; } - return false; } - /// - /// Determines whether [is of generic type] [the specified type]. - /// - /// The type. - /// Type of the generic. - /// - /// true if [is of generic type] [the specified type]; otherwise, false. - /// - public static bool IsOfGenericType(this Type type, Type genericType) + return false; + } + + /// + /// Determines whether [is of generic type] [the specified type]. + /// + /// The type. + /// Type of the generic. + /// + /// true if [is of generic type] [the specified type]; otherwise, false. + /// + public static bool IsOfGenericType(this Type type, Type genericType) + { + Type[]? args; + return type.TryGetGenericArguments(genericType, out args); + } + + /// + /// Will find the generic type of the 'type' parameter passed in that is equal to the 'genericType' parameter passed in + /// + /// + /// + /// + /// + public static bool TryGetGenericArguments(this Type type, Type genericType, out Type[]? genericArgType) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + if (genericType == null) { - Type[]? args; - return type.TryGetGenericArguments(genericType, out args); + throw new ArgumentNullException("genericType"); } - /// - /// Will find the generic type of the 'type' parameter passed in that is equal to the 'genericType' parameter passed in - /// - /// - /// - /// - /// - public static bool TryGetGenericArguments(this Type type, Type genericType, out Type[]? genericArgType) + if (genericType.IsGenericType == false) { - if (type == null) - { - throw new ArgumentNullException("type"); - } - if (genericType == null) - { - throw new ArgumentNullException("genericType"); - } - if (genericType.IsGenericType == false) - { - throw new ArgumentException("genericType must be a generic type"); - } + throw new ArgumentException("genericType must be a generic type"); + } - Func checkGenericType = (@int, t) => + Func checkGenericType = (@int, t) => + { + if (@int.IsGenericType) { - if (@int.IsGenericType) + Type def = @int.GetGenericTypeDefinition(); + if (def == t) { - var def = @int.GetGenericTypeDefinition(); - if (def == t) - { - return @int.GetGenericArguments(); - } + return @int.GetGenericArguments(); } - return null; - }; + } - //first, check if the type passed in is already the generic type - genericArgType = checkGenericType(type, genericType); - if (genericArgType != null) - return true; + return null; + }; + + //first, check if the type passed in is already the generic type + genericArgType = checkGenericType(type, genericType); + if (genericArgType != null) + { + return true; + } - //if we're looking for interfaces, enumerate them: - if (genericType.IsInterface) + //if we're looking for interfaces, enumerate them: + if (genericType.IsInterface) + { + foreach (Type @interface in type.GetInterfaces()) { - foreach (Type @interface in type.GetInterfaces()) + genericArgType = checkGenericType(@interface, genericType); + if (genericArgType != null) { - genericArgType = checkGenericType(@interface, genericType); - if (genericArgType != null) - return true; + return true; } } - else + } + else + { + //loop back into the base types as long as they are generic + while (type.BaseType != null && type.BaseType != typeof(object)) { - //loop back into the base types as long as they are generic - while (type.BaseType != null && type.BaseType != typeof(object)) + genericArgType = checkGenericType(type.BaseType, genericType); + if (genericArgType != null) { - genericArgType = checkGenericType(type.BaseType, genericType); - if (genericArgType != null) - return true; - type = type.BaseType; + return true; } + type = type.BaseType; } - - return false; - } - /// - /// Gets all properties in a flat hierarchy - /// - /// Includes both Public and Non-Public properties - /// - /// - public static PropertyInfo[] GetAllProperties(this Type type) + return false; + } + + /// + /// Gets all properties in a flat hierarchy + /// + /// Includes both Public and Non-Public properties + /// + /// + public static PropertyInfo[] GetAllProperties(this Type type) + { + if (type.IsInterface) { - if (type.IsInterface) - { - var propertyInfos = new List(); + var propertyInfos = new List(); - var considered = new List(); - var queue = new Queue(); - considered.Add(type); - queue.Enqueue(type); - while (queue.Count > 0) + var considered = new List(); + var queue = new Queue(); + considered.Add(type); + queue.Enqueue(type); + while (queue.Count > 0) + { + Type subType = queue.Dequeue(); + foreach (Type subInterface in subType.GetInterfaces()) { - var subType = queue.Dequeue(); - foreach (var subInterface in subType.GetInterfaces()) + if (considered.Contains(subInterface)) { - if (considered.Contains(subInterface)) continue; - - considered.Add(subInterface); - queue.Enqueue(subInterface); + continue; } - var typeProperties = subType.GetProperties( - BindingFlags.FlattenHierarchy - | BindingFlags.Public - | BindingFlags.NonPublic - | BindingFlags.Instance); + considered.Add(subInterface); + queue.Enqueue(subInterface); + } - var newPropertyInfos = typeProperties - .Where(x => !propertyInfos.Contains(x)); + PropertyInfo[] typeProperties = subType.GetProperties( + BindingFlags.FlattenHierarchy + | BindingFlags.Public + | BindingFlags.NonPublic + | BindingFlags.Instance); - propertyInfos.InsertRange(0, newPropertyInfos); - } + IEnumerable newPropertyInfos = typeProperties + .Where(x => !propertyInfos.Contains(x)); - return propertyInfos.ToArray(); + propertyInfos.InsertRange(0, newPropertyInfos); } - return type.GetProperties(BindingFlags.FlattenHierarchy - | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + return propertyInfos.ToArray(); } - /// - /// Returns all public properties including inherited properties even for interfaces - /// - /// - /// - /// - /// taken from http://stackoverflow.com/questions/358835/getproperties-to-return-all-properties-for-an-interface-inheritance-hierarchy - /// - public static PropertyInfo[] GetPublicProperties(this Type type) + return type.GetProperties(BindingFlags.FlattenHierarchy + | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + } + + /// + /// Returns all public properties including inherited properties even for interfaces + /// + /// + /// + /// + /// taken from + /// http://stackoverflow.com/questions/358835/getproperties-to-return-all-properties-for-an-interface-inheritance-hierarchy + /// + public static PropertyInfo[] GetPublicProperties(this Type type) + { + if (type.IsInterface) { - if (type.IsInterface) - { - var propertyInfos = new List(); + var propertyInfos = new List(); - var considered = new List(); - var queue = new Queue(); - considered.Add(type); - queue.Enqueue(type); - while (queue.Count > 0) + var considered = new List(); + var queue = new Queue(); + considered.Add(type); + queue.Enqueue(type); + while (queue.Count > 0) + { + Type subType = queue.Dequeue(); + foreach (Type subInterface in subType.GetInterfaces()) { - var subType = queue.Dequeue(); - foreach (var subInterface in subType.GetInterfaces()) + if (considered.Contains(subInterface)) { - if (considered.Contains(subInterface)) continue; - - considered.Add(subInterface); - queue.Enqueue(subInterface); + continue; } - var typeProperties = subType.GetProperties( - BindingFlags.FlattenHierarchy - | BindingFlags.Public - | BindingFlags.Instance); + considered.Add(subInterface); + queue.Enqueue(subInterface); + } - var newPropertyInfos = typeProperties - .Where(x => !propertyInfos.Contains(x)); + PropertyInfo[] typeProperties = subType.GetProperties( + BindingFlags.FlattenHierarchy + | BindingFlags.Public + | BindingFlags.Instance); - propertyInfos.InsertRange(0, newPropertyInfos); - } + IEnumerable newPropertyInfos = typeProperties + .Where(x => !propertyInfos.Contains(x)); - return propertyInfos.ToArray(); + propertyInfos.InsertRange(0, newPropertyInfos); } - return type.GetProperties(BindingFlags.FlattenHierarchy - | BindingFlags.Public | BindingFlags.Instance); + return propertyInfos.ToArray(); } - /// - /// Determines whether the specified actual type is type. - /// - /// - /// The actual type. - /// - /// true if the specified actual type is type; otherwise, false. - /// - public static bool IsType(this Type actualType) - { - return TypeHelper.IsTypeAssignableFrom(actualType); - } + return type.GetProperties(BindingFlags.FlattenHierarchy + | BindingFlags.Public | BindingFlags.Instance); + } - public static bool Inherits(this Type type) - { - return typeof(TBase).IsAssignableFrom(type); - } + /// + /// Determines whether the specified actual type is type. + /// + /// + /// The actual type. + /// + /// true if the specified actual type is type; otherwise, false. + /// + public static bool IsType(this Type actualType) => TypeHelper.IsTypeAssignableFrom(actualType); - public static bool Inherits(this Type type, Type tbase) - { - return tbase.IsAssignableFrom(type); - } + public static bool Inherits(this Type type) => typeof(TBase).IsAssignableFrom(type); - public static bool Implements(this Type type) - { - return typeof(TInterface).IsAssignableFrom(type); - } + public static bool Inherits(this Type type, Type tbase) => tbase.IsAssignableFrom(type); - public static TAttribute? FirstAttribute(this Type type) - { - return type.FirstAttribute(true); - } + public static bool Implements(this Type type) => typeof(TInterface).IsAssignableFrom(type); - public static TAttribute? FirstAttribute(this Type type, bool inherit) - { - var attrs = type.GetCustomAttributes(typeof(TAttribute), inherit); - return (TAttribute?)(attrs.Length > 0 ? attrs[0] : null); - } + public static TAttribute? FirstAttribute(this Type type) => type.FirstAttribute(true); - public static TAttribute? FirstAttribute(this PropertyInfo propertyInfo) - { - return propertyInfo.FirstAttribute(true); - } + public static TAttribute? FirstAttribute(this Type type, bool inherit) + { + var attrs = type.GetCustomAttributes(typeof(TAttribute), inherit); + return (TAttribute?)(attrs.Length > 0 ? attrs[0] : null); + } - public static TAttribute? FirstAttribute(this PropertyInfo propertyInfo, bool inherit) - { - var attrs = propertyInfo.GetCustomAttributes(typeof(TAttribute), inherit); - return (TAttribute?)(attrs.Length > 0 ? attrs[0] : null); - } + public static TAttribute? FirstAttribute(this PropertyInfo propertyInfo) => + propertyInfo.FirstAttribute(true); - public static IEnumerable? MultipleAttribute(this PropertyInfo propertyInfo) - { - return propertyInfo.MultipleAttribute(true); - } + public static TAttribute? FirstAttribute(this PropertyInfo propertyInfo, bool inherit) + { + var attrs = propertyInfo.GetCustomAttributes(typeof(TAttribute), inherit); + return (TAttribute?)(attrs.Length > 0 ? attrs[0] : null); + } - public static IEnumerable? MultipleAttribute(this PropertyInfo propertyInfo, bool inherit) - { - var attrs = propertyInfo.GetCustomAttributes(typeof(TAttribute), inherit); - return (attrs.Length > 0 ? attrs.ToList().ConvertAll(input => (TAttribute)input) : null); - } + public static IEnumerable? MultipleAttribute(this PropertyInfo propertyInfo) => + propertyInfo.MultipleAttribute(true); - /// - /// Returns the full type name with the assembly but without all of the assembly specific version information. - /// - /// - /// - /// - /// This method is like an 'in between' of Type.FullName and Type.AssemblyQualifiedName which returns the type and the assembly separated - /// by a comma. - /// - /// - /// The output of this class would be: - /// - /// Umbraco.Core.TypeExtensions, Umbraco.Core - /// - public static string GetFullNameWithAssembly(this Type type) - { - var assemblyName = type.Assembly.GetName(); + public static IEnumerable? MultipleAttribute(this PropertyInfo propertyInfo, bool inherit) + { + var attrs = propertyInfo.GetCustomAttributes(typeof(TAttribute), inherit); + return attrs.Length > 0 ? attrs.ToList().ConvertAll(input => (TAttribute)input) : null; + } - return string.Concat(type.FullName, ", ", - assemblyName.FullName.StartsWith("App_Code.") ? "App_Code" : assemblyName.Name); - } + /// + /// Returns the full type name with the assembly but without all of the assembly specific version information. + /// + /// + /// + /// + /// This method is like an 'in between' of Type.FullName and Type.AssemblyQualifiedName which returns the type and the + /// assembly separated + /// by a comma. + /// + /// + /// The output of this class would be: + /// Umbraco.Core.TypeExtensions, Umbraco.Core + /// + public static string GetFullNameWithAssembly(this Type type) + { + AssemblyName assemblyName = type.Assembly.GetName(); - /// - /// Determines whether an instance of a specified type can be assigned to the current type instance. - /// - /// The current type. - /// The type to compare with the current type. - /// A value indicating whether an instance of the specified type can be assigned to the current type instance. - /// This extended version supports the current type being a generic type definition, and will - /// consider that eg List{int} is "assignable to" IList{}. - public static bool IsAssignableFromGtd(this Type type, Type c) - { - // type *can* be a generic type definition - // c is a real type, cannot be a generic type definition + return string.Concat(type.FullName, ", ", + assemblyName.FullName.StartsWith("App_Code.") ? "App_Code" : assemblyName.Name); + } + + /// + /// Determines whether an instance of a specified type can be assigned to the current type instance. + /// + /// The current type. + /// The type to compare with the current type. + /// A value indicating whether an instance of the specified type can be assigned to the current type instance. + /// + /// This extended version supports the current type being a generic type definition, and will + /// consider that eg List{int} is "assignable to" IList{}. + /// + public static bool IsAssignableFromGtd(this Type type, Type c) + { + // type *can* be a generic type definition + // c is a real type, cannot be a generic type definition - if (type.IsGenericTypeDefinition == false) - return type.IsAssignableFrom(c); + if (type.IsGenericTypeDefinition == false) + { + return type.IsAssignableFrom(c); + } - if (c.IsInterface == false) + if (c.IsInterface == false) + { + Type t = c; + while (t != typeof(object)) { - var t = c; - while (t != typeof(object)) + if (t is not null && t.IsGenericType && t.GetGenericTypeDefinition() == type) { - if (t is not null && t.IsGenericType && t.GetGenericTypeDefinition() == type) return true; - t = t?.BaseType; + return true; } - } - return c.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == type); + t = t?.BaseType; + } } - /// - /// If the given is an array or some other collection - /// comprised of 0 or more instances of a "subtype", get that type - /// - /// the source type - /// - public static Type? GetEnumeratedType(this Type type) - { - if (typeof(IEnumerable).IsAssignableFrom(type) == false) - return null; - - // provided by Array - var elType = type.GetElementType(); - if (null != elType) return elType; - - // otherwise provided by collection - var elTypes = type.GetGenericArguments(); - if (elTypes.Length > 0) return elTypes[0]; + return c.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == type); + } - // otherwise is not an 'enumerated' type + /// + /// If the given is an array or some other collection + /// comprised of 0 or more instances of a "subtype", get that type + /// + /// the source type + /// + public static Type? GetEnumeratedType(this Type type) + { + if (typeof(IEnumerable).IsAssignableFrom(type) == false) + { return null; } - public static T? GetCustomAttribute(this Type type, bool inherit) - where T : Attribute + // provided by Array + Type elType = type.GetElementType(); + if (null != elType) { - return type.GetCustomAttributes(inherit).SingleOrDefault(); + return elType; } - public static IEnumerable GetCustomAttributes(this Type type, bool inherited) - where T : Attribute + // otherwise provided by collection + Type[] elTypes = type.GetGenericArguments(); + if (elTypes.Length > 0) { - if (type == null) return Enumerable.Empty(); - return type.GetCustomAttributes(typeof(T), inherited).OfType(); + return elTypes[0]; } - public static bool HasCustomAttribute(this Type type, bool inherit) - where T : Attribute + // otherwise is not an 'enumerated' type + return null; + } + + public static T? GetCustomAttribute(this Type type, bool inherit) + where T : Attribute => + type.GetCustomAttributes(inherit).SingleOrDefault(); + + public static IEnumerable GetCustomAttributes(this Type type, bool inherited) + where T : Attribute + { + if (type == null) { - return type.GetCustomAttribute(inherit) != null; + return Enumerable.Empty(); } - /// - /// Tries to return a value based on a property name for an object but ignores case sensitivity - /// - /// - /// - /// - /// - /// - /// - /// Currently this will only work for ProperCase and camelCase properties, see the TODO below to enable complete case insensitivity - /// - internal static Attempt GetMemberIgnoreCase(this Type type, IShortStringHelper shortStringHelper, object target, string memberName) - { - Func> getMember = - memberAlias => - { - try - { - return Attempt.Succeed( - type.InvokeMember(memberAlias, - System.Reflection.BindingFlags.GetProperty | - System.Reflection.BindingFlags.Instance | - System.Reflection.BindingFlags.Public, - null, - target, - null)); - } - catch (MissingMethodException ex) - { - return Attempt.Fail(ex); - } - }; + return type.GetCustomAttributes(typeof(T), inherited).OfType(); + } - //try with the current casing - var attempt = getMember(memberName); - if (attempt.Success == false) + public static bool HasCustomAttribute(this Type type, bool inherit) + where T : Attribute => + type.GetCustomAttribute(inherit) != null; + + /// + /// Tries to return a value based on a property name for an object but ignores case sensitivity + /// + /// + /// + /// + /// + /// + /// + /// Currently this will only work for ProperCase and camelCase properties, see the TODO below to enable complete case + /// insensitivity + /// + internal static Attempt GetMemberIgnoreCase(this Type type, IShortStringHelper shortStringHelper, + object target, string memberName) + { + Func> getMember = + memberAlias => { - //if we cannot get with the current alias, try changing it's case - attempt = memberName[0].IsUpperCase() - ? getMember(memberName.ToCleanString(shortStringHelper, CleanStringType.Ascii | CleanStringType.ConvertCase | CleanStringType.CamelCase)) - : getMember(memberName.ToCleanString(shortStringHelper, CleanStringType.Ascii | CleanStringType.ConvertCase | CleanStringType.PascalCase)); - - // TODO: If this still fails then we should get a list of properties from the object and then compare - doing the above without listing - // all properties will surely be faster than using reflection to get ALL properties first and then query against them. - } + try + { + return Attempt.Succeed( + type.InvokeMember(memberAlias, + BindingFlags.GetProperty | + BindingFlags.Instance | + BindingFlags.Public, + null, + target, + null)); + } + catch (MissingMethodException ex) + { + return Attempt.Fail(ex); + } + }; - return attempt; + //try with the current casing + Attempt attempt = getMember(memberName); + if (attempt.Success == false) + { + //if we cannot get with the current alias, try changing it's case + attempt = memberName[0].IsUpperCase() + ? getMember(memberName.ToCleanString(shortStringHelper, + CleanStringType.Ascii | CleanStringType.ConvertCase | CleanStringType.CamelCase)) + : getMember(memberName.ToCleanString(shortStringHelper, + CleanStringType.Ascii | CleanStringType.ConvertCase | CleanStringType.PascalCase)); + + // TODO: If this still fails then we should get a list of properties from the object and then compare - doing the above without listing + // all properties will surely be faster than using reflection to get ALL properties first and then query against them. } + return attempt; } } diff --git a/src/Umbraco.Core/Extensions/TypeLoaderExtensions.cs b/src/Umbraco.Core/Extensions/TypeLoaderExtensions.cs index 8928d221c558..ca87d6bb7702 100644 --- a/src/Umbraco.Core/Extensions/TypeLoaderExtensions.cs +++ b/src/Umbraco.Core/Extensions/TypeLoaderExtensions.cs @@ -1,33 +1,29 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Actions; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.Packaging; using Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class TypeLoaderExtensions { - public static class TypeLoaderExtensions - { - /// - /// Gets all types implementing . - /// - public static IEnumerable GetDataEditors(this TypeLoader mgr) => mgr.GetTypes(); + /// + /// Gets all types implementing . + /// + public static IEnumerable GetDataEditors(this TypeLoader mgr) => mgr.GetTypes(); - /// - /// Gets all types implementing ICacheRefresher. - /// - public static IEnumerable GetCacheRefreshers(this TypeLoader mgr) => mgr.GetTypes(); + /// + /// Gets all types implementing ICacheRefresher. + /// + public static IEnumerable GetCacheRefreshers(this TypeLoader mgr) => mgr.GetTypes(); - /// - /// Gets all types implementing - /// - /// - /// - public static IEnumerable GetActions(this TypeLoader mgr) => mgr.GetTypes(); - } + /// + /// Gets all types implementing + /// + /// + /// + public static IEnumerable GetActions(this TypeLoader mgr) => mgr.GetTypes(); } diff --git a/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs b/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs index 70dd11ff3364..d663e48d22cf 100644 --- a/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs +++ b/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs @@ -1,325 +1,498 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Provides extension methods that return udis for Umbraco entities. +/// +public static class UdiGetterExtensions { /// - /// Provides extension methods that return udis for Umbraco entities. + /// Gets the entity identifier of the entity. /// - public static class UdiGetterExtensions + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this ITemplate entity) { - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// The entity identifier of the entity. - public static GuidUdi GetUdi(this ITemplate entity) + if (entity == null) { - if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.UdiEntityType.Template, entity.Key).EnsureClosed(); - } + throw new ArgumentNullException("entity"); + } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// The entity identifier of the entity. - public static GuidUdi GetUdi(this IContentType entity) - { - if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.UdiEntityType.DocumentType, entity.Key).EnsureClosed(); - } + return new GuidUdi(Constants.UdiEntityType.Template, entity.Key).EnsureClosed(); + } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// The entity identifier of the entity. - public static GuidUdi GetUdi(this IMediaType entity) + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IContentType entity) + { + if (entity == null) { - if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.UdiEntityType.MediaType, entity.Key).EnsureClosed(); - } + throw new ArgumentNullException("entity"); + } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// The entity identifier of the entity. - public static GuidUdi GetUdi(this IMemberType entity) + return new GuidUdi(Constants.UdiEntityType.DocumentType, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IMediaType entity) + { + if (entity == null) { - if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.UdiEntityType.MemberType, entity.Key).EnsureClosed(); + throw new ArgumentNullException("entity"); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// The entity identifier of the entity. - public static GuidUdi GetUdi(this IMemberGroup entity) + return new GuidUdi(Constants.UdiEntityType.MediaType, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IMemberType entity) + { + if (entity == null) { - if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.UdiEntityType.MemberGroup, entity.Key).EnsureClosed(); + throw new ArgumentNullException("entity"); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// The entity identifier of the entity. - public static GuidUdi GetUdi(this IContentTypeComposition entity) - { - if (entity == null) throw new ArgumentNullException("entity"); + return new GuidUdi(Constants.UdiEntityType.MemberType, entity.Key).EnsureClosed(); + } - string type; - if (entity is IContentType) type = Constants.UdiEntityType.DocumentType; - else if (entity is IMediaType) type = Constants.UdiEntityType.MediaType; - else if (entity is IMemberType) type = Constants.UdiEntityType.MemberType; - else throw new NotSupportedException(string.Format("Composition type {0} is not supported.", entity.GetType().FullName)); - return new GuidUdi(type, entity.Key).EnsureClosed(); + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IMemberGroup entity) + { + if (entity == null) + { + throw new ArgumentNullException("entity"); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// The entity identifier of the entity. - public static GuidUdi GetUdi(this IDataType entity) + return new GuidUdi(Constants.UdiEntityType.MemberGroup, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IContentTypeComposition entity) + { + if (entity == null) { - if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.UdiEntityType.DataType, entity.Key).EnsureClosed(); + throw new ArgumentNullException("entity"); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// The entity identifier of the entity. - public static GuidUdi GetUdi(this EntityContainer entity) + string type; + if (entity is IContentType) + { + type = Constants.UdiEntityType.DocumentType; + } + else if (entity is IMediaType) { - if (entity == null) throw new ArgumentNullException("entity"); + type = Constants.UdiEntityType.MediaType; + } + else if (entity is IMemberType) + { + type = Constants.UdiEntityType.MemberType; + } + else + { + throw new NotSupportedException(string.Format("Composition type {0} is not supported.", + entity.GetType().FullName)); + } - string entityType; - if (entity.ContainedObjectType == Constants.ObjectTypes.DataType) - entityType = Constants.UdiEntityType.DataTypeContainer; - else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentType) - entityType = Constants.UdiEntityType.DocumentTypeContainer; - else if (entity.ContainedObjectType == Constants.ObjectTypes.MediaType) - entityType = Constants.UdiEntityType.MediaTypeContainer; - else - throw new NotSupportedException(string.Format("Contained object type {0} is not supported.", entity.ContainedObjectType)); - return new GuidUdi(entityType, entity.Key).EnsureClosed(); + return new GuidUdi(type, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IDataType entity) + { + if (entity == null) + { + throw new ArgumentNullException("entity"); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// The entity identifier of the entity. - public static GuidUdi GetUdi(this IMedia entity) + return new GuidUdi(Constants.UdiEntityType.DataType, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this EntityContainer entity) + { + if (entity == null) { - if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.UdiEntityType.Media, entity.Key).EnsureClosed(); + throw new ArgumentNullException("entity"); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// The entity identifier of the entity. - public static GuidUdi GetUdi(this IContent entity) + string entityType; + if (entity.ContainedObjectType == Constants.ObjectTypes.DataType) { - if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(entity.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, entity.Key).EnsureClosed(); + entityType = Constants.UdiEntityType.DataTypeContainer; } + else if (entity.ContainedObjectType == Constants.ObjectTypes.DocumentType) + { + entityType = Constants.UdiEntityType.DocumentTypeContainer; + } + else if (entity.ContainedObjectType == Constants.ObjectTypes.MediaType) + { + entityType = Constants.UdiEntityType.MediaTypeContainer; + } + else + { + throw new NotSupportedException(string.Format("Contained object type {0} is not supported.", + entity.ContainedObjectType)); + } + + return new GuidUdi(entityType, entity.Key).EnsureClosed(); + } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// The entity identifier of the entity. - public static GuidUdi GetUdi(this IMember entity) + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IMedia entity) + { + if (entity == null) { - if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.UdiEntityType.Member, entity.Key).EnsureClosed(); + throw new ArgumentNullException("entity"); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// The entity identifier of the entity. - public static StringUdi GetUdi(this Stylesheet entity) + return new GuidUdi(Constants.UdiEntityType.Media, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IContent entity) + { + if (entity == null) { - if (entity == null) throw new ArgumentNullException("entity"); - return new StringUdi(Constants.UdiEntityType.Stylesheet, entity.Path.TrimStart(Constants.CharArrays.ForwardSlash)).EnsureClosed(); + throw new ArgumentNullException("entity"); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// The entity identifier of the entity. - public static StringUdi GetUdi(this Script entity) + return new GuidUdi( + entity.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, + entity.Key) + .EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IMember entity) + { + if (entity == null) { - if (entity == null) throw new ArgumentNullException("entity"); - return new StringUdi(Constants.UdiEntityType.Script, entity.Path.TrimStart(Constants.CharArrays.ForwardSlash)).EnsureClosed(); + throw new ArgumentNullException("entity"); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// The entity identifier of the entity. - public static GuidUdi GetUdi(this IDictionaryItem entity) + return new GuidUdi(Constants.UdiEntityType.Member, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static StringUdi GetUdi(this Stylesheet entity) + { + if (entity == null) { - if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.UdiEntityType.DictionaryItem, entity.Key).EnsureClosed(); + throw new ArgumentNullException("entity"); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// The entity identifier of the entity. - public static GuidUdi GetUdi(this IMacro entity) + return new StringUdi(Constants.UdiEntityType.Stylesheet, + entity.Path.TrimStart(Constants.CharArrays.ForwardSlash)).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static StringUdi GetUdi(this Script entity) + { + if (entity == null) { - if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.UdiEntityType.Macro, entity.Key).EnsureClosed(); + throw new ArgumentNullException("entity"); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// The entity identifier of the entity. - public static StringUdi GetUdi(this IPartialView entity) + return new StringUdi(Constants.UdiEntityType.Script, entity.Path.TrimStart(Constants.CharArrays.ForwardSlash)) + .EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IDictionaryItem entity) + { + if (entity == null) { - if (entity == null) throw new ArgumentNullException("entity"); + throw new ArgumentNullException("entity"); + } - // we should throw on Unknown but for the time being, assume it means PartialView - var entityType = entity.ViewType == PartialViewType.PartialViewMacro - ? Constants.UdiEntityType.PartialViewMacro - : Constants.UdiEntityType.PartialView; + return new GuidUdi(Constants.UdiEntityType.DictionaryItem, entity.Key).EnsureClosed(); + } - return new StringUdi(entityType, entity.Path.TrimStart(Constants.CharArrays.ForwardSlash)).EnsureClosed(); + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IMacro entity) + { + if (entity == null) + { + throw new ArgumentNullException("entity"); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// The entity identifier of the entity. - public static GuidUdi GetUdi(this IContentBase entity) + return new GuidUdi(Constants.UdiEntityType.Macro, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static StringUdi GetUdi(this IPartialView entity) + { + if (entity == null) { - if (entity == null) throw new ArgumentNullException("entity"); + throw new ArgumentNullException("entity"); + } + + // we should throw on Unknown but for the time being, assume it means PartialView + var entityType = entity.ViewType == PartialViewType.PartialViewMacro + ? Constants.UdiEntityType.PartialViewMacro + : Constants.UdiEntityType.PartialView; + + return new StringUdi(entityType, entity.Path.TrimStart(Constants.CharArrays.ForwardSlash)).EnsureClosed(); + } - string type; - if (entity is IContent) type = Constants.UdiEntityType.Document; - else if (entity is IMedia) type = Constants.UdiEntityType.Media; - else if (entity is IMember) type = Constants.UdiEntityType.Member; - else throw new NotSupportedException(string.Format("ContentBase type {0} is not supported.", entity.GetType().FullName)); - return new GuidUdi(type, entity.Key).EnsureClosed(); + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IContentBase entity) + { + if (entity == null) + { + throw new ArgumentNullException("entity"); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// The entity identifier of the entity. - public static GuidUdi GetUdi(this IRelationType entity) + string type; + if (entity is IContent) { - if (entity == null) throw new ArgumentNullException("entity"); - return new GuidUdi(Constants.UdiEntityType.RelationType, entity.Key).EnsureClosed(); + type = Constants.UdiEntityType.Document; + } + else if (entity is IMedia) + { + type = Constants.UdiEntityType.Media; + } + else if (entity is IMember) + { + type = Constants.UdiEntityType.Member; + } + else + { + throw new NotSupportedException(string.Format("ContentBase type {0} is not supported.", + entity.GetType().FullName)); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// The entity identifier of the entity. - public static StringUdi GetUdi(this ILanguage entity) + return new GuidUdi(type, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static GuidUdi GetUdi(this IRelationType entity) + { + if (entity == null) { - if (entity == null) throw new ArgumentNullException("entity"); - return new StringUdi(Constants.UdiEntityType.Language, entity.IsoCode).EnsureClosed(); + throw new ArgumentNullException("entity"); } - /// - /// Gets the entity identifier of the entity. - /// - /// The entity. - /// The entity identifier of the entity. - public static Udi GetUdi(this IEntity entity) + return new GuidUdi(Constants.UdiEntityType.RelationType, entity.Key).EnsureClosed(); + } + + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static StringUdi GetUdi(this ILanguage entity) + { + if (entity == null) { - if (entity == null) throw new ArgumentNullException("entity"); + throw new ArgumentNullException("entity"); + } + + return new StringUdi(Constants.UdiEntityType.Language, entity.IsoCode).EnsureClosed(); + } - // entity could eg be anything implementing IThing - // so we have to go through casts here + /// + /// Gets the entity identifier of the entity. + /// + /// The entity. + /// The entity identifier of the entity. + public static Udi GetUdi(this IEntity entity) + { + if (entity == null) + { + throw new ArgumentNullException("entity"); + } - var template = entity as ITemplate; - if (template != null) return template.GetUdi(); + // entity could eg be anything implementing IThing + // so we have to go through casts here - var contentType = entity as IContentType; - if (contentType != null) return contentType.GetUdi(); + var template = entity as ITemplate; + if (template != null) + { + return template.GetUdi(); + } - var mediaType = entity as IMediaType; - if (mediaType != null) return mediaType.GetUdi(); + var contentType = entity as IContentType; + if (contentType != null) + { + return contentType.GetUdi(); + } - var memberType = entity as IMemberType; - if (memberType != null) return memberType.GetUdi(); + var mediaType = entity as IMediaType; + if (mediaType != null) + { + return mediaType.GetUdi(); + } - var memberGroup = entity as IMemberGroup; - if (memberGroup != null) return memberGroup.GetUdi(); + var memberType = entity as IMemberType; + if (memberType != null) + { + return memberType.GetUdi(); + } - var contentTypeComposition = entity as IContentTypeComposition; - if (contentTypeComposition != null) return contentTypeComposition.GetUdi(); + var memberGroup = entity as IMemberGroup; + if (memberGroup != null) + { + return memberGroup.GetUdi(); + } - var dataTypeComposition = entity as IDataType; - if (dataTypeComposition != null) return dataTypeComposition.GetUdi(); + var contentTypeComposition = entity as IContentTypeComposition; + if (contentTypeComposition != null) + { + return contentTypeComposition.GetUdi(); + } - var container = entity as EntityContainer; - if (container != null) return container.GetUdi(); + var dataTypeComposition = entity as IDataType; + if (dataTypeComposition != null) + { + return dataTypeComposition.GetUdi(); + } - var media = entity as IMedia; - if (media != null) return media.GetUdi(); + var container = entity as EntityContainer; + if (container != null) + { + return container.GetUdi(); + } - var content = entity as IContent; - if (content != null) return content.GetUdi(); + var media = entity as IMedia; + if (media != null) + { + return media.GetUdi(); + } - var member = entity as IMember; - if (member != null) return member.GetUdi(); + var content = entity as IContent; + if (content != null) + { + return content.GetUdi(); + } - var stylesheet = entity as Stylesheet; - if (stylesheet != null) return stylesheet.GetUdi(); + var member = entity as IMember; + if (member != null) + { + return member.GetUdi(); + } - var script = entity as Script; - if (script != null) return script.GetUdi(); + var stylesheet = entity as Stylesheet; + if (stylesheet != null) + { + return stylesheet.GetUdi(); + } - var dictionaryItem = entity as IDictionaryItem; - if (dictionaryItem != null) return dictionaryItem.GetUdi(); + var script = entity as Script; + if (script != null) + { + return script.GetUdi(); + } - var macro = entity as IMacro; - if (macro != null) return macro.GetUdi(); + var dictionaryItem = entity as IDictionaryItem; + if (dictionaryItem != null) + { + return dictionaryItem.GetUdi(); + } - var partialView = entity as IPartialView; - if (partialView != null) return partialView.GetUdi(); + var macro = entity as IMacro; + if (macro != null) + { + return macro.GetUdi(); + } - var contentBase = entity as IContentBase; - if (contentBase != null) return contentBase.GetUdi(); + var partialView = entity as IPartialView; + if (partialView != null) + { + return partialView.GetUdi(); + } - var relationType = entity as IRelationType; - if (relationType != null) return relationType.GetUdi(); + var contentBase = entity as IContentBase; + if (contentBase != null) + { + return contentBase.GetUdi(); + } - var language = entity as ILanguage; - if (language != null) return language.GetUdi(); + var relationType = entity as IRelationType; + if (relationType != null) + { + return relationType.GetUdi(); + } - throw new NotSupportedException(string.Format("Entity type {0} is not supported.", entity.GetType().FullName)); + var language = entity as ILanguage; + if (language != null) + { + return language.GetUdi(); } + + throw new NotSupportedException(string.Format("Entity type {0} is not supported.", entity.GetType().FullName)); } } diff --git a/src/Umbraco.Core/Extensions/UmbracoBuilderExtensions.cs b/src/Umbraco.Core/Extensions/UmbracoBuilderExtensions.cs index 5b4e3a92d9ce..21249883fa9e 100644 --- a/src/Umbraco.Core/Extensions/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Core/Extensions/UmbracoBuilderExtensions.cs @@ -1,104 +1,104 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Extensions +namespace Umbraco.Cms.Core.Extensions; + +public static class UmbracoBuilderExtensions { - public static class UmbracoBuilderExtensions + /// + /// Registers all within an assembly + /// + /// + /// + /// + /// Type contained within the targeted assembly + /// + public static IUmbracoBuilder AddNotificationsFromAssembly(this IUmbracoBuilder self) { - /// - /// Registers all within an assembly - /// - /// - /// Type contained within the targeted assembly - /// - public static IUmbracoBuilder AddNotificationsFromAssembly(this IUmbracoBuilder self) - { - AddNotificationHandlers(self); - AddAsyncNotificationHandlers(self); + AddNotificationHandlers(self); + AddAsyncNotificationHandlers(self); - return self; - } + return self; + } - private static void AddNotificationHandlers(IUmbracoBuilder self) + private static void AddNotificationHandlers(IUmbracoBuilder self) + { + List notificationHandlers = GetNotificationHandlers(); + foreach (Type notificationHandler in notificationHandlers) { - var notificationHandlers = GetNotificationHandlers(); - foreach (var notificationHandler in notificationHandlers) + List handlerImplementations = GetNotificationHandlerImplementations(notificationHandler); + foreach (Type implementation in handlerImplementations) { - var handlerImplementations = GetNotificationHandlerImplementations(notificationHandler); - foreach (var implementation in handlerImplementations) - { - RegisterNotificationHandler(self, implementation, notificationHandler); - } + RegisterNotificationHandler(self, implementation, notificationHandler); } } + } - private static List GetNotificationHandlers() => - typeof(T).Assembly.GetTypes() - .Where(x => x.IsAssignableToGenericType(typeof(INotificationHandler<>))) - .ToList(); + private static List GetNotificationHandlers() => + typeof(T).Assembly.GetTypes() + .Where(x => x.IsAssignableToGenericType(typeof(INotificationHandler<>))) + .ToList(); - private static List GetNotificationHandlerImplementations(Type handlerType) => - handlerType - .GetInterfaces() - .Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(INotificationHandler<>)) - .ToList(); + private static List GetNotificationHandlerImplementations(Type handlerType) => + handlerType + .GetInterfaces() + .Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(INotificationHandler<>)) + .ToList(); - private static void AddAsyncNotificationHandlers(IUmbracoBuilder self) + private static void AddAsyncNotificationHandlers(IUmbracoBuilder self) + { + List notificationHandlers = GetAsyncNotificationHandlers(); + foreach (Type notificationHandler in notificationHandlers) { - var notificationHandlers = GetAsyncNotificationHandlers(); - foreach (var notificationHandler in notificationHandlers) + List handlerImplementations = GetAsyncNotificationHandlerImplementations(notificationHandler); + foreach (Type handler in handlerImplementations) { - var handlerImplementations = GetAsyncNotificationHandlerImplementations(notificationHandler); - foreach (var handler in handlerImplementations) - { - RegisterNotificationHandler(self, handler, notificationHandler); - } + RegisterNotificationHandler(self, handler, notificationHandler); } } + } - private static List GetAsyncNotificationHandlers() => - typeof(T).Assembly.GetTypes() - .Where(x => x.IsAssignableToGenericType(typeof(INotificationAsyncHandler<>))) - .ToList(); + private static List GetAsyncNotificationHandlers() => + typeof(T).Assembly.GetTypes() + .Where(x => x.IsAssignableToGenericType(typeof(INotificationAsyncHandler<>))) + .ToList(); - private static List GetAsyncNotificationHandlerImplementations(Type handlerType) => - handlerType - .GetInterfaces() - .Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(INotificationAsyncHandler<>)) - .ToList(); + private static List GetAsyncNotificationHandlerImplementations(Type handlerType) => + handlerType + .GetInterfaces() + .Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(INotificationAsyncHandler<>)) + .ToList(); - private static void RegisterNotificationHandler(IUmbracoBuilder self, Type notificationHandlerType, Type implementingHandlerType) + private static void RegisterNotificationHandler(IUmbracoBuilder self, Type notificationHandlerType, + Type implementingHandlerType) + { + var descriptor = + new UniqueServiceDescriptor(notificationHandlerType, implementingHandlerType, ServiceLifetime.Transient); + if (!self.Services.Contains(descriptor)) { - var descriptor = new UniqueServiceDescriptor(notificationHandlerType, implementingHandlerType, ServiceLifetime.Transient); - if (!self.Services.Contains(descriptor)) - { - self.Services.Add(descriptor); - } + self.Services.Add(descriptor); } + } - private static bool IsAssignableToGenericType(this Type givenType, Type genericType) - { - var interfaceTypes = givenType.GetInterfaces(); - - foreach (var it in interfaceTypes) - { - if (it.IsGenericType && it.GetGenericTypeDefinition() == genericType) - { - return true; - } - } + private static bool IsAssignableToGenericType(this Type givenType, Type genericType) + { + Type[] interfaceTypes = givenType.GetInterfaces(); - if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType) + foreach (Type it in interfaceTypes) + { + if (it.IsGenericType && it.GetGenericTypeDefinition() == genericType) { return true; } + } - var baseType = givenType.BaseType; - return baseType != null && IsAssignableToGenericType(baseType, genericType); + if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType) + { + return true; } + + Type baseType = givenType.BaseType; + return baseType != null && IsAssignableToGenericType(baseType, genericType); } } diff --git a/src/Umbraco.Core/Extensions/UmbracoContextAccessorExtensions.cs b/src/Umbraco.Core/Extensions/UmbracoContextAccessorExtensions.cs index 794c206db8dd..735e4611276c 100644 --- a/src/Umbraco.Core/Extensions/UmbracoContextAccessorExtensions.cs +++ b/src/Umbraco.Core/Extensions/UmbracoContextAccessorExtensions.cs @@ -1,21 +1,24 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Umbraco.Cms.Core.Web; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class UmbracoContextAccessorExtensions { - public static class UmbracoContextAccessorExtensions + public static IUmbracoContext GetRequiredUmbracoContext(this IUmbracoContextAccessor umbracoContextAccessor) { - public static IUmbracoContext GetRequiredUmbracoContext(this IUmbracoContextAccessor umbracoContextAccessor) + if (umbracoContextAccessor == null) { - if (umbracoContextAccessor == null) throw new ArgumentNullException(nameof(umbracoContextAccessor)); - if(!umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) - { - throw new InvalidOperationException("Wasn't able to get an UmbracoContext"); - } - return umbracoContext!; + throw new ArgumentNullException(nameof(umbracoContextAccessor)); } + + if (!umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext umbracoContext)) + { + throw new InvalidOperationException("Wasn't able to get an UmbracoContext"); + } + + return umbracoContext!; } } diff --git a/src/Umbraco.Core/Extensions/UmbracoContextExtensions.cs b/src/Umbraco.Core/Extensions/UmbracoContextExtensions.cs index 7d0e31f285a5..e5ec62530d66 100644 --- a/src/Umbraco.Core/Extensions/UmbracoContextExtensions.cs +++ b/src/Umbraco.Core/Extensions/UmbracoContextExtensions.cs @@ -3,13 +3,13 @@ using Umbraco.Cms.Core.Web; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class UmbracoContextExtensions { - public static class UmbracoContextExtensions - { - /// - /// Boolean value indicating whether the current request is a front-end umbraco request - /// - public static bool IsFrontEndUmbracoRequest(this IUmbracoContext umbracoContext) => umbracoContext.PublishedRequest != null; - } + /// + /// Boolean value indicating whether the current request is a front-end umbraco request + /// + public static bool IsFrontEndUmbracoRequest(this IUmbracoContext umbracoContext) => + umbracoContext.PublishedRequest != null; } diff --git a/src/Umbraco.Core/Extensions/UriExtensions.cs b/src/Umbraco.Core/Extensions/UriExtensions.cs index 52adbc6b67da..4839c7411dd4 100644 --- a/src/Umbraco.Core/Extensions/UriExtensions.cs +++ b/src/Umbraco.Core/Extensions/UriExtensions.cs @@ -1,192 +1,206 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; +using System.Net; +using System.Web; using Umbraco.Cms.Core; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Provides extension methods to . +/// +public static class UriExtensions { /// - /// Provides extension methods to . + /// Rewrites the path of uri. /// - public static class UriExtensions + /// The uri. + /// The new path, which must begin with a slash. + /// The rewritten uri. + /// Everything else remains unchanged, except for the fragment which is removed. + public static Uri Rewrite(this Uri uri, string path) { - /// - /// Rewrites the path of uri. - /// - /// The uri. - /// The new path, which must begin with a slash. - /// The rewritten uri. - /// Everything else remains unchanged, except for the fragment which is removed. - public static Uri Rewrite(this Uri uri, string path) + if (path.StartsWith("/") == false) { - if (path.StartsWith("/") == false) - throw new ArgumentException("Path must start with a slash.", "path"); - - return uri.IsAbsoluteUri - ? new Uri(uri.GetLeftPart(UriPartial.Authority) + path + uri.Query) - : new Uri(path + uri.GetSafeQuery(), UriKind.Relative); + throw new ArgumentException("Path must start with a slash.", "path"); } - /// - /// Rewrites the path and query of a uri. - /// - /// The uri. - /// The new path, which must begin with a slash. - /// The new query, which must be empty or begin with a question mark. - /// The rewritten uri. - /// Everything else remains unchanged, except for the fragment which is removed. - public static Uri Rewrite(this Uri uri, string path, string query) + return uri.IsAbsoluteUri + ? new Uri(uri.GetLeftPart(UriPartial.Authority) + path + uri.Query) + : new Uri(path + uri.GetSafeQuery(), UriKind.Relative); + } + + /// + /// Rewrites the path and query of a uri. + /// + /// The uri. + /// The new path, which must begin with a slash. + /// The new query, which must be empty or begin with a question mark. + /// The rewritten uri. + /// Everything else remains unchanged, except for the fragment which is removed. + public static Uri Rewrite(this Uri uri, string path, string query) + { + if (path.StartsWith("/") == false) { - if (path.StartsWith("/") == false) - throw new ArgumentException("Path must start with a slash.", "path"); - if (query.Length > 0 && query.StartsWith("?") == false) - throw new ArgumentException("Query must start with a question mark.", "query"); - if (query == "?") - query = ""; - - return uri.IsAbsoluteUri - ? new Uri(uri.GetLeftPart(UriPartial.Authority) + path + query) - : new Uri(path + query, UriKind.Relative); + throw new ArgumentException("Path must start with a slash.", "path"); } - /// - /// Gets the absolute path of the uri, even if the uri is relative. - /// - /// The uri. - /// The absolute path of the uri. - /// Default uri.AbsolutePath does not support relative uris. - public static string GetSafeAbsolutePath(this Uri uri) + if (query.Length > 0 && query.StartsWith("?") == false) { - if (uri.IsAbsoluteUri) - { - return uri.AbsolutePath; - } - - // cannot get .AbsolutePath on relative uri (InvalidOperation) - var s = uri.OriginalString; - - // TODO: Shouldn't this just use Uri.GetLeftPart? - var posq = s.IndexOf("?", StringComparison.Ordinal); - var posf = s.IndexOf("#", StringComparison.Ordinal); - var pos = posq > 0 ? posq : (posf > 0 ? posf : 0); - var path = pos > 0 ? s.Substring(0, pos) : s; - return path; + throw new ArgumentException("Query must start with a question mark.", "query"); } - /// - /// Gets the decoded, absolute path of the uri. - /// - /// The uri. - /// The absolute path of the uri. - /// Only for absolute uris. - public static string GetAbsolutePathDecoded(this Uri uri) + if (query == "?") { - return System.Web.HttpUtility.UrlDecode(uri.AbsolutePath); + query = ""; } - /// - /// Gets the decoded, absolute path of the uri, even if the uri is relative. - /// - /// The uri. - /// The absolute path of the uri. - /// Default uri.AbsolutePath does not support relative uris. - public static string GetSafeAbsolutePathDecoded(this Uri uri) + return uri.IsAbsoluteUri + ? new Uri(uri.GetLeftPart(UriPartial.Authority) + path + query) + : new Uri(path + query, UriKind.Relative); + } + + /// + /// Gets the absolute path of the uri, even if the uri is relative. + /// + /// The uri. + /// The absolute path of the uri. + /// Default uri.AbsolutePath does not support relative uris. + public static string GetSafeAbsolutePath(this Uri uri) + { + if (uri.IsAbsoluteUri) { - return System.Net.WebUtility.UrlDecode(uri.GetSafeAbsolutePath()); + return uri.AbsolutePath; } - /// - /// Rewrites the path of the uri so it ends with a slash. - /// - /// The uri. - /// The rewritten uri. - /// Everything else remains unchanged. - public static Uri EndPathWithSlash(this Uri uri) + // cannot get .AbsolutePath on relative uri (InvalidOperation) + var s = uri.OriginalString; + + // TODO: Shouldn't this just use Uri.GetLeftPart? + var posq = s.IndexOf("?", StringComparison.Ordinal); + var posf = s.IndexOf("#", StringComparison.Ordinal); + var pos = posq > 0 ? posq : posf > 0 ? posf : 0; + var path = pos > 0 ? s.Substring(0, pos) : s; + return path; + } + + /// + /// Gets the decoded, absolute path of the uri. + /// + /// The uri. + /// The absolute path of the uri. + /// Only for absolute uris. + public static string GetAbsolutePathDecoded(this Uri uri) => HttpUtility.UrlDecode(uri.AbsolutePath); + + /// + /// Gets the decoded, absolute path of the uri, even if the uri is relative. + /// + /// The uri. + /// The absolute path of the uri. + /// Default uri.AbsolutePath does not support relative uris. + public static string GetSafeAbsolutePathDecoded(this Uri uri) => WebUtility.UrlDecode(uri.GetSafeAbsolutePath()); + + /// + /// Rewrites the path of the uri so it ends with a slash. + /// + /// The uri. + /// The rewritten uri. + /// Everything else remains unchanged. + public static Uri EndPathWithSlash(this Uri uri) + { + var path = uri.GetSafeAbsolutePath(); + if (uri.IsAbsoluteUri) { - var path = uri.GetSafeAbsolutePath(); - if (uri.IsAbsoluteUri) + if (path != "/" && path.EndsWith("/") == false) { - if (path != "/" && path.EndsWith("/") == false) - uri = new Uri(uri.GetLeftPart(UriPartial.Authority) + path + "/" + uri.Query); - return uri; + uri = new Uri(uri.GetLeftPart(UriPartial.Authority) + path + "/" + uri.Query); } - if (path != "/" && path.EndsWith("/") == false) - uri = new Uri(path + "/" + uri.Query, UriKind.Relative); - return uri; } - /// - /// Rewrites the path of the uri so it does not end with a slash. - /// - /// The uri. - /// The rewritten uri. - /// Everything else remains unchanged. - public static Uri TrimPathEndSlash(this Uri uri) + if (path != "/" && path.EndsWith("/") == false) { - var path = uri.GetSafeAbsolutePath(); - if (uri.IsAbsoluteUri) + uri = new Uri(path + "/" + uri.Query, UriKind.Relative); + } + + return uri; + } + + /// + /// Rewrites the path of the uri so it does not end with a slash. + /// + /// The uri. + /// The rewritten uri. + /// Everything else remains unchanged. + public static Uri TrimPathEndSlash(this Uri uri) + { + var path = uri.GetSafeAbsolutePath(); + if (uri.IsAbsoluteUri) + { + if (path != "/") { - if (path != "/") - uri = new Uri(uri.GetLeftPart(UriPartial.Authority) + path.TrimEnd(Constants.CharArrays.ForwardSlash) + uri.Query); + uri = new Uri(uri.GetLeftPart(UriPartial.Authority) + path.TrimEnd(Constants.CharArrays.ForwardSlash) + + uri.Query); } - else + } + else + { + if (path != "/") { - if (path != "/") - uri = new Uri(path.TrimEnd(Constants.CharArrays.ForwardSlash) + uri.Query, UriKind.Relative); + uri = new Uri(path.TrimEnd(Constants.CharArrays.ForwardSlash) + uri.Query, UriKind.Relative); } - return uri; } - /// - /// Transforms a relative uri into an absolute uri. - /// - /// The relative uri. - /// The base absolute uri. - /// The absolute uri. - public static Uri MakeAbsolute(this Uri uri, Uri baseUri) - { - if (uri.IsAbsoluteUri) - throw new ArgumentException("Uri is already absolute.", "uri"); - - return new Uri(baseUri.GetLeftPart(UriPartial.Authority) + uri.GetSafeAbsolutePath() + uri.GetSafeQuery()); - } + return uri; + } - static string? GetSafeQuery(this Uri uri) + /// + /// Transforms a relative uri into an absolute uri. + /// + /// The relative uri. + /// The base absolute uri. + /// The absolute uri. + public static Uri MakeAbsolute(this Uri uri, Uri baseUri) + { + if (uri.IsAbsoluteUri) { - if (uri.IsAbsoluteUri) - return uri.Query; - - // cannot get .Query on relative uri (InvalidOperation) - var s = uri.OriginalString; - var posq = s.IndexOf("?", StringComparison.Ordinal); - var posf = s.IndexOf("#", StringComparison.Ordinal); - var query = posq < 0 ? null : (posf < 0 ? s.Substring(posq) : s.Substring(posq, posf - posq)); - - return query; + throw new ArgumentException("Uri is already absolute.", "uri"); } - /// - /// Removes the port from the uri. - /// - /// The uri. - /// The same uri, without its port. - public static Uri WithoutPort(this Uri uri) - { - return new Uri(uri.GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped)); - } + return new Uri(baseUri.GetLeftPart(UriPartial.Authority) + uri.GetSafeAbsolutePath() + uri.GetSafeQuery()); + } - /// - /// Replaces the host of a uri. - /// - /// The uri. - /// A replacement host. - /// The same uri, with its host replaced. - public static Uri ReplaceHost(this Uri uri, string host) + private static string? GetSafeQuery(this Uri uri) + { + if (uri.IsAbsoluteUri) { - return new UriBuilder(uri) { Host = host }.Uri; + return uri.Query; } + + // cannot get .Query on relative uri (InvalidOperation) + var s = uri.OriginalString; + var posq = s.IndexOf("?", StringComparison.Ordinal); + var posf = s.IndexOf("#", StringComparison.Ordinal); + var query = posq < 0 ? null : posf < 0 ? s.Substring(posq) : s.Substring(posq, posf - posq); + + return query; } + + /// + /// Removes the port from the uri. + /// + /// The uri. + /// The same uri, without its port. + public static Uri WithoutPort(this Uri uri) => + new Uri(uri.GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped)); + + /// + /// Replaces the host of a uri. + /// + /// The uri. + /// A replacement host. + /// The same uri, with its host replaced. + public static Uri ReplaceHost(this Uri uri, string host) => new UriBuilder(uri) {Host = host}.Uri; } diff --git a/src/Umbraco.Core/Extensions/VersionExtensions.cs b/src/Umbraco.Core/Extensions/VersionExtensions.cs index 24326ef32740..97cabca5ea5f 100644 --- a/src/Umbraco.Core/Extensions/VersionExtensions.cs +++ b/src/Umbraco.Core/Extensions/VersionExtensions.cs @@ -1,87 +1,92 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using System.Globalization; using Umbraco.Cms.Core.Semver; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class VersionExtensions { - public static class VersionExtensions + public static Version GetVersion(this SemVersion semVersion, int maxParts = 4) { - public static Version GetVersion(this SemVersion semVersion, int maxParts = 4) + var build = 0; + int.TryParse(semVersion.Build, NumberStyles.Integer, CultureInfo.InvariantCulture, out build); + + if (maxParts >= 4) { - int build = 0; - int.TryParse(semVersion.Build, NumberStyles.Integer, CultureInfo.InvariantCulture, out build); + return new Version(semVersion.Major, semVersion.Minor, semVersion.Patch, build); + } - if (maxParts >= 4) - { - return new Version(semVersion.Major, semVersion.Minor, semVersion.Patch, build); - } - if (maxParts == 3) + if (maxParts == 3) + { + return new Version(semVersion.Major, semVersion.Minor, semVersion.Patch); + } + + return new Version(semVersion.Major, semVersion.Minor); + } + + public static Version SubtractRevision(this Version version) + { + var parts = new List(new[] {version.Major, version.Minor, version.Build, version.Revision}); + + //remove all prefixed zero parts + while (parts[0] <= 0) + { + parts.RemoveAt(0); + if (parts.Count == 0) { - return new Version(semVersion.Major, semVersion.Minor, semVersion.Patch); + break; } - - return new Version(semVersion.Major, semVersion.Minor); } - public static Version SubtractRevision(this Version version) + for (var index = 0; index < parts.Count; index++) { - var parts = new List(new[] {version.Major, version.Minor, version.Build, version.Revision}); - - //remove all prefixed zero parts - while (parts[0] <= 0) + var part = parts[index]; + if (part <= 0) { - parts.RemoveAt(0); - if (parts.Count == 0) break; + parts.RemoveAt(index); + index++; } - - for (int index = 0; index < parts.Count; index++) + else { - var part = parts[index]; - if (part <= 0) - { - parts.RemoveAt(index); - index++; - } - else - { - //break when there isn't a zero part - break; - } + //break when there isn't a zero part + break; } + } - if (parts.Count == 0) throw new InvalidOperationException("Cannot subtract a revision from a zero version"); - - var lastNonZero = parts.FindLastIndex(i => i > 0); - - //subtract 1 from the last non-zero - parts[lastNonZero] = parts[lastNonZero] - 1; + if (parts.Count == 0) + { + throw new InvalidOperationException("Cannot subtract a revision from a zero version"); + } - //the last non zero is actually the revision so we can just return - if (lastNonZero == (parts.Count -1)) - { - return FromList(parts); - } + var lastNonZero = parts.FindLastIndex(i => i > 0); - //the last non zero isn't the revision so the remaining zero's need to be replaced with int.max - for (var i = lastNonZero + 1; i < parts.Count; i++) - { - parts[i] = int.MaxValue; - } + //subtract 1 from the last non-zero + parts[lastNonZero] = parts[lastNonZero] - 1; + //the last non zero is actually the revision so we can just return + if (lastNonZero == parts.Count - 1) + { return FromList(parts); } - private static Version FromList(IList parts) + //the last non zero isn't the revision so the remaining zero's need to be replaced with int.max + for (var i = lastNonZero + 1; i < parts.Count; i++) { - while (parts.Count < 4) - { - parts.Insert(0, 0); - } - return new Version(parts[0], parts[1], parts[2], parts[3]); + parts[i] = int.MaxValue; + } + + return FromList(parts); + } + + private static Version FromList(IList parts) + { + while (parts.Count < 4) + { + parts.Insert(0, 0); } + + return new Version(parts[0], parts[1], parts[2], parts[3]); } } diff --git a/src/Umbraco.Core/Extensions/WaitHandleExtensions.cs b/src/Umbraco.Core/Extensions/WaitHandleExtensions.cs index 5cb763949766..994997f37579 100644 --- a/src/Umbraco.Core/Extensions/WaitHandleExtensions.cs +++ b/src/Umbraco.Core/Extensions/WaitHandleExtensions.cs @@ -1,48 +1,44 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Threading; -using System.Threading.Tasks; +namespace Umbraco.Extensions; -namespace Umbraco.Extensions +public static class WaitHandleExtensions { - public static class WaitHandleExtensions - { - // http://stackoverflow.com/questions/25382583/waiting-on-a-named-semaphore-with-waitone100-vs-waitone0-task-delay100 - // http://blog.nerdbank.net/2011/07/c-await-for-waithandle.html - // F# has a AwaitWaitHandle method that accepts a time out... and seems pretty complex... - // version below should be OK + // http://stackoverflow.com/questions/25382583/waiting-on-a-named-semaphore-with-waitone100-vs-waitone0-task-delay100 + // http://blog.nerdbank.net/2011/07/c-await-for-waithandle.html + // F# has a AwaitWaitHandle method that accepts a time out... and seems pretty complex... + // version below should be OK - public static Task WaitOneAsync(this WaitHandle handle, int millisecondsTimeout = Timeout.Infinite) + public static Task WaitOneAsync(this WaitHandle handle, int millisecondsTimeout = Timeout.Infinite) + { + var tcs = new TaskCompletionSource(); + var callbackHandleInitLock = new object(); + lock (callbackHandleInitLock) { - var tcs = new TaskCompletionSource(); - var callbackHandleInitLock = new object(); - lock (callbackHandleInitLock) - { - RegisteredWaitHandle? callbackHandle = null; - // ReSharper disable once RedundantAssignment - callbackHandle = ThreadPool.RegisterWaitForSingleObject( - handle, - (state, timedOut) => - { - //TODO: We aren't checking if this is timed out + RegisteredWaitHandle? callbackHandle = null; + // ReSharper disable once RedundantAssignment + callbackHandle = ThreadPool.RegisterWaitForSingleObject( + handle, + (state, timedOut) => + { + //TODO: We aren't checking if this is timed out - tcs.SetResult(null); + tcs.SetResult(null); - // we take a lock here to make sure the outer method has completed setting the local variable callbackHandle. - lock (callbackHandleInitLock) - { - // ReSharper disable once PossibleNullReferenceException - // ReSharper disable once AccessToModifiedClosure - callbackHandle?.Unregister(null); - } - }, - /*state:*/ null, - /*millisecondsTimeOutInterval:*/ millisecondsTimeout, - /*executeOnlyOnce:*/ true); - } - - return tcs.Task; + // we take a lock here to make sure the outer method has completed setting the local variable callbackHandle. + lock (callbackHandleInitLock) + { + // ReSharper disable once PossibleNullReferenceException + // ReSharper disable once AccessToModifiedClosure + callbackHandle?.Unregister(null); + } + }, + /*state:*/ null, + /*millisecondsTimeOutInterval:*/ millisecondsTimeout, + /*executeOnlyOnce:*/ true); } + + return tcs.Task; } } diff --git a/src/Umbraco.Core/Extensions/XmlExtensions.cs b/src/Umbraco.Core/Extensions/XmlExtensions.cs index 141f4a0c1958..91d032b678d8 100644 --- a/src/Umbraco.Core/Extensions/XmlExtensions.cs +++ b/src/Umbraco.Core/Extensions/XmlExtensions.cs @@ -1,9 +1,6 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; -using System.Linq; using System.Text; using System.Xml; using System.Xml.Linq; @@ -11,337 +8,406 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Xml; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Extension methods for xml objects +/// +public static class XmlExtensions { + public static bool HasAttribute(this XmlAttributeCollection attributes, string attributeName) => + attributes.Cast().Any(x => x.Name == attributeName); + + /// + /// Selects a list of XmlNode matching an XPath expression. + /// + /// A source XmlNode. + /// An XPath expression. + /// A set of XPathVariables. + /// The list of XmlNode matching the XPath expression. + /// + /// + /// If + /// + /// is null, or is empty, or contains only one single + /// value which itself is null, then variables are ignored. + /// + /// The XPath expression should reference variables as $var. + /// + public static XmlNodeList? SelectNodes(this XmlNode source, string expression, + IEnumerable? variables) + { + XPathVariable[] av = variables == null ? null : variables.ToArray(); + return SelectNodes(source, expression, av); + } + + /// + /// Selects a list of XmlNode matching an XPath expression. + /// + /// A source XmlNode. + /// An XPath expression. + /// A set of XPathVariables. + /// The list of XmlNode matching the XPath expression. + /// + /// + /// If + /// + /// is null, or is empty, or contains only one single + /// value which itself is null, then variables are ignored. + /// + /// The XPath expression should reference variables as $var. + /// + public static XmlNodeList? SelectNodes(this XmlNode source, XPathExpression expression, + IEnumerable? variables) + { + XPathVariable[] av = variables == null ? null : variables.ToArray(); + return SelectNodes(source, expression, av); + } + /// - /// Extension methods for xml objects + /// Selects a list of XmlNode matching an XPath expression. /// - public static class XmlExtensions + /// A source XmlNode. + /// An XPath expression. + /// A set of XPathVariables. + /// The list of XmlNode matching the XPath expression. + /// + /// + /// If + /// + /// is null, or is empty, or contains only one single + /// value which itself is null, then variables are ignored. + /// + /// The XPath expression should reference variables as $var. + /// + public static XmlNodeList? SelectNodes(this XmlNode source, string? expression, params XPathVariable[]? variables) { - public static bool HasAttribute(this XmlAttributeCollection attributes, string attributeName) + if (variables == null || variables.Length == 0 || variables[0] == null) { - return attributes.Cast().Any(x => x.Name == attributeName); + return source.SelectNodes(expression ?? ""); } - /// - /// Selects a list of XmlNode matching an XPath expression. - /// - /// A source XmlNode. - /// An XPath expression. - /// A set of XPathVariables. - /// The list of XmlNode matching the XPath expression. - /// - /// If is null, or is empty, or contains only one single - /// value which itself is null, then variables are ignored. - /// The XPath expression should reference variables as $var. - /// - public static XmlNodeList? SelectNodes(this XmlNode source, string expression, IEnumerable? variables) + XPathNodeIterator iterator = source.CreateNavigator()?.Select(expression ?? "", variables); + return XmlNodeListFactory.CreateNodeList(iterator); + } + + /// + /// Selects a list of XmlNode matching an XPath expression. + /// + /// A source XmlNode. + /// An XPath expression. + /// A set of XPathVariables. + /// The list of XmlNode matching the XPath expression. + /// + /// + /// If + /// + /// is null, or is empty, or contains only one single + /// value which itself is null, then variables are ignored. + /// + /// The XPath expression should reference variables as $var. + /// + public static XmlNodeList SelectNodes(this XmlNode source, XPathExpression expression, + params XPathVariable[]? variables) + { + if (variables == null || variables.Length == 0 || variables[0] == null) { - var av = variables == null ? null : variables.ToArray(); - return SelectNodes(source, expression, av); + return source.SelectNodes(expression); } - /// - /// Selects a list of XmlNode matching an XPath expression. - /// - /// A source XmlNode. - /// An XPath expression. - /// A set of XPathVariables. - /// The list of XmlNode matching the XPath expression. - /// - /// If is null, or is empty, or contains only one single - /// value which itself is null, then variables are ignored. - /// The XPath expression should reference variables as $var. - /// - public static XmlNodeList? SelectNodes(this XmlNode source, XPathExpression expression, IEnumerable? variables) + XPathNodeIterator iterator = source.CreateNavigator()?.Select(expression, variables); + return XmlNodeListFactory.CreateNodeList(iterator); + } + + /// + /// Selects the first XmlNode that matches an XPath expression. + /// + /// A source XmlNode. + /// An XPath expression. + /// A set of XPathVariables. + /// The first XmlNode that matches the XPath expression. + /// + /// + /// If + /// + /// is null, or is empty, or contains only one single + /// value which itself is null, then variables are ignored. + /// + /// The XPath expression should reference variables as $var. + /// + public static XmlNode? SelectSingleNode(this XmlNode source, string expression, + IEnumerable? variables) + { + XPathVariable[] av = variables == null ? null : variables.ToArray(); + return SelectSingleNode(source, expression, av); + } + + /// + /// Selects the first XmlNode that matches an XPath expression. + /// + /// A source XmlNode. + /// An XPath expression. + /// A set of XPathVariables. + /// The first XmlNode that matches the XPath expression. + /// + /// + /// If + /// + /// is null, or is empty, or contains only one single + /// value which itself is null, then variables are ignored. + /// + /// The XPath expression should reference variables as $var. + /// + public static XmlNode? SelectSingleNode(this XmlNode source, XPathExpression expression, + IEnumerable? variables) + { + XPathVariable[] av = variables == null ? null : variables.ToArray(); + return SelectSingleNode(source, expression, av); + } + + /// + /// Selects the first XmlNode that matches an XPath expression. + /// + /// A source XmlNode. + /// An XPath expression. + /// A set of XPathVariables. + /// The first XmlNode that matches the XPath expression. + /// + /// + /// If + /// + /// is null, or is empty, or contains only one single + /// value which itself is null, then variables are ignored. + /// + /// The XPath expression should reference variables as $var. + /// + public static XmlNode? SelectSingleNode(this XmlNode source, string expression, params XPathVariable[]? variables) + { + if (variables == null || variables.Length == 0 || variables[0] == null) { - var av = variables == null ? null : variables.ToArray(); - return SelectNodes(source, expression, av); + return source.SelectSingleNode(expression); } - /// - /// Selects a list of XmlNode matching an XPath expression. - /// - /// A source XmlNode. - /// An XPath expression. - /// A set of XPathVariables. - /// The list of XmlNode matching the XPath expression. - /// - /// If is null, or is empty, or contains only one single - /// value which itself is null, then variables are ignored. - /// The XPath expression should reference variables as $var. - /// - public static XmlNodeList? SelectNodes(this XmlNode source, string? expression, params XPathVariable[]? variables) - { - if (variables == null || variables.Length == 0 || variables[0] == null) - return source.SelectNodes(expression ?? ""); + return SelectNodes(source, expression, variables)?.Cast().FirstOrDefault(); + } - var iterator = source.CreateNavigator()?.Select(expression ?? "", variables); - return XmlNodeListFactory.CreateNodeList(iterator); + /// + /// Selects the first XmlNode that matches an XPath expression. + /// + /// A source XmlNode. + /// An XPath expression. + /// A set of XPathVariables. + /// The first XmlNode that matches the XPath expression. + /// + /// + /// If + /// + /// is null, or is empty, or contains only one single + /// value which itself is null, then variables are ignored. + /// + /// The XPath expression should reference variables as $var. + /// + public static XmlNode? SelectSingleNode(this XmlNode source, XPathExpression expression, + params XPathVariable[]? variables) + { + if (variables == null || variables.Length == 0 || variables[0] == null) + { + return source.SelectSingleNode(expression); } - /// - /// Selects a list of XmlNode matching an XPath expression. - /// - /// A source XmlNode. - /// An XPath expression. - /// A set of XPathVariables. - /// The list of XmlNode matching the XPath expression. - /// - /// If is null, or is empty, or contains only one single - /// value which itself is null, then variables are ignored. - /// The XPath expression should reference variables as $var. - /// - public static XmlNodeList SelectNodes(this XmlNode source, XPathExpression expression, params XPathVariable[]? variables) - { - if (variables == null || variables.Length == 0 || variables[0] == null) - return source.SelectNodes(expression); + return SelectNodes(source, expression, variables).Cast().FirstOrDefault(); + } - var iterator = source.CreateNavigator()?.Select(expression, variables); - return XmlNodeListFactory.CreateNodeList(iterator); + /// + /// Converts from an XDocument to an XmlDocument + /// + /// + /// + public static XmlDocument ToXmlDocument(this XDocument xDocument) + { + var xmlDocument = new XmlDocument(); + using (XmlReader xmlReader = xDocument.CreateReader()) + { + xmlDocument.Load(xmlReader); } - /// - /// Selects the first XmlNode that matches an XPath expression. - /// - /// A source XmlNode. - /// An XPath expression. - /// A set of XPathVariables. - /// The first XmlNode that matches the XPath expression. - /// - /// If is null, or is empty, or contains only one single - /// value which itself is null, then variables are ignored. - /// The XPath expression should reference variables as $var. - /// - public static XmlNode? SelectSingleNode(this XmlNode source, string expression, IEnumerable? variables) + return xmlDocument; + } + + /// + /// Converts from an XmlDocument to an XDocument + /// + /// + /// + public static XDocument ToXDocument(this XmlDocument xmlDocument) + { + using (var nodeReader = new XmlNodeReader(xmlDocument)) { - var av = variables == null ? null : variables.ToArray(); - return SelectSingleNode(source, expression, av); + nodeReader.MoveToContent(); + return XDocument.Load(nodeReader); } + } - /// - /// Selects the first XmlNode that matches an XPath expression. - /// - /// A source XmlNode. - /// An XPath expression. - /// A set of XPathVariables. - /// The first XmlNode that matches the XPath expression. - /// - /// If is null, or is empty, or contains only one single - /// value which itself is null, then variables are ignored. - /// The XPath expression should reference variables as $var. - /// - public static XmlNode? SelectSingleNode(this XmlNode source, XPathExpression expression, IEnumerable? variables) + ///// + ///// Converts from an XElement to an XmlElement + ///// + ///// + ///// + public static XmlNode? ToXmlElement(this XContainer xElement) + { + var xmlDocument = new XmlDocument(); + using (XmlReader xmlReader = xElement.CreateReader()) { - var av = variables == null ? null : variables.ToArray(); - return SelectSingleNode(source, expression, av); + xmlDocument.Load(xmlReader); } - /// - /// Selects the first XmlNode that matches an XPath expression. - /// - /// A source XmlNode. - /// An XPath expression. - /// A set of XPathVariables. - /// The first XmlNode that matches the XPath expression. - /// - /// If is null, or is empty, or contains only one single - /// value which itself is null, then variables are ignored. - /// The XPath expression should reference variables as $var. - /// - public static XmlNode? SelectSingleNode(this XmlNode source, string expression, params XPathVariable[]? variables) - { - if (variables == null || variables.Length == 0 || variables[0] == null) - return source.SelectSingleNode(expression); + return xmlDocument.DocumentElement; + } - return SelectNodes(source, expression, variables)?.Cast().FirstOrDefault(); + /// + /// Converts from an XmlElement to an XElement + /// + /// + /// + public static XElement ToXElement(this XmlNode xmlElement) + { + using (var nodeReader = new XmlNodeReader(xmlElement)) + { + nodeReader.MoveToContent(); + return XElement.Load(nodeReader); } + } - /// - /// Selects the first XmlNode that matches an XPath expression. - /// - /// A source XmlNode. - /// An XPath expression. - /// A set of XPathVariables. - /// The first XmlNode that matches the XPath expression. - /// - /// If is null, or is empty, or contains only one single - /// value which itself is null, then variables are ignored. - /// The XPath expression should reference variables as $var. - /// - public static XmlNode? SelectSingleNode(this XmlNode source, XPathExpression expression, params XPathVariable[]? variables) + public static T? RequiredAttributeValue(this XElement xml, string attributeName) + { + if (xml == null) { - if (variables == null || variables.Length == 0 || variables[0] == null) - return source.SelectSingleNode(expression); - - return SelectNodes(source, expression, variables).Cast().FirstOrDefault(); + throw new ArgumentNullException(nameof(xml)); } - /// - /// Converts from an XDocument to an XmlDocument - /// - /// - /// - public static XmlDocument ToXmlDocument(this XDocument xDocument) + if (xml.HasAttributes == false) { - var xmlDocument = new XmlDocument(); - using (var xmlReader = xDocument.CreateReader()) - { - xmlDocument.Load(xmlReader); - } - return xmlDocument; + throw new InvalidOperationException($"{attributeName} not found in xml"); } - /// - /// Converts from an XmlDocument to an XDocument - /// - /// - /// - public static XDocument ToXDocument(this XmlDocument xmlDocument) + XAttribute? attribute = xml.Attribute(attributeName); + if (attribute is null) { - using (var nodeReader = new XmlNodeReader(xmlDocument)) - { - nodeReader.MoveToContent(); - return XDocument.Load(nodeReader); - } + throw new InvalidOperationException($"{attributeName} not found in xml"); } - ///// - ///// Converts from an XElement to an XmlElement - ///// - ///// - ///// - public static XmlNode? ToXmlElement(this XContainer xElement) + Attempt result = attribute.Value.TryConvertTo(); + if (result.Success) { - var xmlDocument = new XmlDocument(); - using (var xmlReader = xElement.CreateReader()) - { - xmlDocument.Load(xmlReader); - } - return xmlDocument.DocumentElement; + return result.Result; } - /// - /// Converts from an XmlElement to an XElement - /// - /// - /// - public static XElement ToXElement(this XmlNode xmlElement) + throw new InvalidOperationException($"{attribute.Value} attribute value cannot be converted to {typeof(T)}"); + } + + public static T? AttributeValue(this XElement xml, string attributeName) + { + if (xml == null) { - using (var nodeReader = new XmlNodeReader(xmlElement)) - { - nodeReader.MoveToContent(); - return XElement.Load(nodeReader); - } + throw new ArgumentNullException("xml"); } - public static T? RequiredAttributeValue(this XElement xml, string attributeName) + if (xml.HasAttributes == false) { - if (xml == null) - { - throw new ArgumentNullException(nameof(xml)); - } - - if (xml.HasAttributes == false) - { - throw new InvalidOperationException($"{attributeName} not found in xml"); - } - - XAttribute? attribute = xml.Attribute(attributeName); - if (attribute is null) - { - throw new InvalidOperationException($"{attributeName} not found in xml"); - } - - Attempt result = attribute.Value.TryConvertTo(); - if (result.Success) - { - return result.Result; - } - - throw new InvalidOperationException($"{attribute.Value} attribute value cannot be converted to {typeof(T)}"); + return default; } - public static T? AttributeValue(this XElement xml, string attributeName) + if (xml.Attribute(attributeName) == null) { - if (xml == null) throw new ArgumentNullException("xml"); - if (xml.HasAttributes == false) return default(T); + return default; + } - if (xml.Attribute(attributeName) == null) - return default(T); + var val = xml.Attribute(attributeName)?.Value; + Attempt result = val.TryConvertTo(); + if (result.Success) + { + return result.Result; + } - var val = xml.Attribute(attributeName)?.Value; - var result = val.TryConvertTo(); - if (result.Success) - return result.Result; + return default; + } - return default(T); + public static T? AttributeValue(this XmlNode xml, string attributeName) + { + if (xml == null) + { + throw new ArgumentNullException("xml"); } - public static T? AttributeValue(this XmlNode xml, string attributeName) + if (xml.Attributes == null) { - if (xml == null) throw new ArgumentNullException("xml"); - if (xml.Attributes == null) return default(T); - - if (xml.Attributes[attributeName] == null) - return default(T); - - var val = xml.Attributes[attributeName]?.Value; - var result = val.TryConvertTo(); - if (result.Success) - return result.Result; + return default; + } - return default(T); + if (xml.Attributes[attributeName] == null) + { + return default; } - public static XElement? GetXElement(this XmlNode node) + var val = xml.Attributes[attributeName]?.Value; + Attempt result = val.TryConvertTo(); + if (result.Success) { - XDocument xDoc = new XDocument(); - using (XmlWriter xmlWriter = xDoc.CreateWriter()) - node.WriteTo(xmlWriter); - return xDoc.Root; + return result.Result; } - public static XmlNode? GetXmlNode(this XContainer element) + return default; + } + + public static XElement? GetXElement(this XmlNode node) + { + var xDoc = new XDocument(); + using (XmlWriter xmlWriter = xDoc.CreateWriter()) { - using (var xmlReader = element.CreateReader()) - { - var xmlDoc = new XmlDocument(); - xmlDoc.Load(xmlReader); - return xmlDoc.DocumentElement; - } + node.WriteTo(xmlWriter); } - public static XmlNode? GetXmlNode(this XContainer element, XmlDocument xmlDoc) + return xDoc.Root; + } + + public static XmlNode? GetXmlNode(this XContainer element) + { + using (XmlReader xmlReader = element.CreateReader()) { - var node = element.GetXmlNode(); - if (node is not null) - { - return xmlDoc.ImportNode(node, true); - } + var xmlDoc = new XmlDocument(); + xmlDoc.Load(xmlReader); + return xmlDoc.DocumentElement; + } + } - return null; + public static XmlNode? GetXmlNode(this XContainer element, XmlDocument xmlDoc) + { + XmlNode node = element.GetXmlNode(); + if (node is not null) + { + return xmlDoc.ImportNode(node, true); } - // this exists because - // new XElement("root", "a\nb").Value is "a\nb" but - // .ToString(SaveOptions.*) is "a\r\nb" and cannot figure out how to get rid of "\r" - // and when saving data we want nothing to change - // this method will produce a string that respects the \r and \n in the data value - public static string ToDataString(this XElement xml) + return null; + } + + // this exists because + // new XElement("root", "a\nb").Value is "a\nb" but + // .ToString(SaveOptions.*) is "a\r\nb" and cannot figure out how to get rid of "\r" + // and when saving data we want nothing to change + // this method will produce a string that respects the \r and \n in the data value + public static string ToDataString(this XElement xml) + { + var settings = new XmlWriterSettings { - var settings = new XmlWriterSettings - { - OmitXmlDeclaration = true, - NewLineHandling = NewLineHandling.None, - Indent = false - }; - var output = new StringBuilder(); - using (var writer = XmlWriter.Create(output, settings)) - { - xml.WriteTo(writer); - } - return output.ToString(); + OmitXmlDeclaration = true, NewLineHandling = NewLineHandling.None, Indent = false + }; + var output = new StringBuilder(); + using (var writer = XmlWriter.Create(output, settings)) + { + xml.WriteTo(writer); } + + return output.ToString(); } } diff --git a/src/Umbraco.Core/Features/DisabledFeatures.cs b/src/Umbraco.Core/Features/DisabledFeatures.cs index e572818baf1b..e7f9eeb83d46 100644 --- a/src/Umbraco.Core/Features/DisabledFeatures.cs +++ b/src/Umbraco.Core/Features/DisabledFeatures.cs @@ -1,34 +1,29 @@ using Umbraco.Cms.Core.Collections; -namespace Umbraco.Cms.Core.Features +namespace Umbraco.Cms.Core.Features; + +/// +/// Represents disabled features. +/// +public class DisabledFeatures { /// - /// Represents disabled features. + /// Initializes a new instance of the class. /// - public class DisabledFeatures - { - /// - /// Initializes a new instance of the class. - /// - public DisabledFeatures() - { - Controllers = new TypeList(); - } - - /// - /// Gets the disabled controllers. - /// - public TypeList Controllers { get; } + public DisabledFeatures() => Controllers = new TypeList(); - /// - /// Disables the device preview feature of previewing. - /// - public bool DisableDevicePreview { get; set; } + /// + /// Gets the disabled controllers. + /// + public TypeList Controllers { get; } - /// - /// If true, all references to templates will be removed in the back office and routing - /// - public bool DisableTemplates { get; set; } + /// + /// Disables the device preview feature of previewing. + /// + public bool DisableDevicePreview { get; set; } - } + /// + /// If true, all references to templates will be removed in the back office and routing + /// + public bool DisableTemplates { get; set; } } diff --git a/src/Umbraco.Core/Features/EnabledFeatures.cs b/src/Umbraco.Core/Features/EnabledFeatures.cs index 5fb7a581dc93..aee19e2f14b0 100644 --- a/src/Umbraco.Core/Features/EnabledFeatures.cs +++ b/src/Umbraco.Core/Features/EnabledFeatures.cs @@ -1,16 +1,15 @@ -namespace Umbraco.Cms.Core.Features +namespace Umbraco.Cms.Core.Features; + +/// +/// Represents enabled features. +/// +public class EnabledFeatures { /// - /// Represents enabled features. + /// This allows us to inject a razor view into the Umbraco preview view to extend it /// - public class EnabledFeatures - { - /// - /// This allows us to inject a razor view into the Umbraco preview view to extend it - /// - /// - /// This is set to a virtual path of a razor view file - /// - public string? PreviewExtendedView { get; set; } - } + /// + /// This is set to a virtual path of a razor view file + /// + public string? PreviewExtendedView { get; set; } } diff --git a/src/Umbraco.Core/Features/IUmbracoFeature.cs b/src/Umbraco.Core/Features/IUmbracoFeature.cs index efb5337a0058..8beaeef32148 100644 --- a/src/Umbraco.Core/Features/IUmbracoFeature.cs +++ b/src/Umbraco.Core/Features/IUmbracoFeature.cs @@ -1,10 +1,8 @@ -namespace Umbraco.Cms.Core.Features -{ - /// - /// This is a marker interface to allow controllers to be disabled if also marked with FeatureAuthorizeAttribute. - /// - public interface IUmbracoFeature - { +namespace Umbraco.Cms.Core.Features; - } +/// +/// This is a marker interface to allow controllers to be disabled if also marked with FeatureAuthorizeAttribute. +/// +public interface IUmbracoFeature +{ } diff --git a/src/Umbraco.Core/Features/UmbracoFeatures.cs b/src/Umbraco.Core/Features/UmbracoFeatures.cs index 5b6bfd7bfb9c..0f971d8ba17a 100644 --- a/src/Umbraco.Core/Features/UmbracoFeatures.cs +++ b/src/Umbraco.Core/Features/UmbracoFeatures.cs @@ -1,40 +1,39 @@ -using System; +namespace Umbraco.Cms.Core.Features; -namespace Umbraco.Cms.Core.Features +/// +/// Represents the Umbraco features. +/// +public class UmbracoFeatures { /// - /// Represents the Umbraco features. + /// Initializes a new instance of the class. /// - public class UmbracoFeatures + public UmbracoFeatures() { - /// - /// Initializes a new instance of the class. - /// - public UmbracoFeatures() - { - Disabled = new DisabledFeatures(); - Enabled = new EnabledFeatures(); - } + Disabled = new DisabledFeatures(); + Enabled = new EnabledFeatures(); + } - /// - /// Gets the disabled features. - /// - public DisabledFeatures Disabled { get; } + /// + /// Gets the disabled features. + /// + public DisabledFeatures Disabled { get; } - /// - /// Gets the enabled features. - /// - public EnabledFeatures Enabled { get; } + /// + /// Gets the enabled features. + /// + public EnabledFeatures Enabled { get; } - /// - /// Determines whether a controller is enabled. - /// - public bool IsControllerEnabled(Type? feature) + /// + /// Determines whether a controller is enabled. + /// + public bool IsControllerEnabled(Type? feature) + { + if (typeof(IUmbracoFeature).IsAssignableFrom(feature)) { - if (typeof(IUmbracoFeature).IsAssignableFrom(feature)) - return Disabled.Controllers.Contains(feature) == false; - - throw new NotSupportedException("Not a supported feature type."); + return Disabled.Controllers.Contains(feature) == false; } + + throw new NotSupportedException("Not a supported feature type."); } } diff --git a/src/Umbraco.Core/GuidUdi.cs b/src/Umbraco.Core/GuidUdi.cs index 53c495ba87a5..318753b2eaa9 100644 --- a/src/Umbraco.Core/GuidUdi.cs +++ b/src/Umbraco.Core/GuidUdi.cs @@ -1,66 +1,62 @@ -using System; -using System.ComponentModel; +using System.ComponentModel; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Represents a guid-based entity identifier. +/// +[TypeConverter(typeof(UdiTypeConverter))] +public class GuidUdi : Udi { /// - /// Represents a guid-based entity identifier. + /// Initializes a new instance of the GuidUdi class with an entity type and a guid. /// - [TypeConverter(typeof(UdiTypeConverter))] - public class GuidUdi : Udi - { - /// - /// The guid part of the identifier. - /// - public Guid Guid { get; private set; } + /// The entity type part of the udi. + /// The guid part of the udi. + public GuidUdi(string entityType, Guid guid) + : base(entityType, "umb://" + entityType + "/" + guid.ToString("N")) => + Guid = guid; - /// - /// Initializes a new instance of the GuidUdi class with an entity type and a guid. - /// - /// The entity type part of the udi. - /// The guid part of the udi. - public GuidUdi(string entityType, Guid guid) - : base(entityType, "umb://" + entityType + "/" + guid.ToString("N")) + /// + /// Initializes a new instance of the GuidUdi class with an uri value. + /// + /// The uri value of the udi. + public GuidUdi(Uri uriValue) + : base(uriValue) + { + Guid guid; + if (Guid.TryParse(uriValue.AbsolutePath.TrimStart(Constants.CharArrays.ForwardSlash), out guid) == false) { - Guid = guid; + throw new FormatException("URI \"" + uriValue + "\" is not a GUID entity ID."); } - /// - /// Initializes a new instance of the GuidUdi class with an uri value. - /// - /// The uri value of the udi. - public GuidUdi(Uri uriValue) - : base(uriValue) - { - Guid guid; - if (Guid.TryParse(uriValue.AbsolutePath.TrimStart(Constants.CharArrays.ForwardSlash), out guid) == false) - throw new FormatException("URI \"" + uriValue + "\" is not a GUID entity ID."); + Guid = guid; + } - Guid = guid; - } + /// + /// The guid part of the identifier. + /// + public Guid Guid { get; } - public override bool Equals(object? obj) - { - var other = obj as GuidUdi; - if (other is null) return false; - return EntityType == other.EntityType && Guid == other.Guid; - } + /// + public override bool IsRoot => Guid == Guid.Empty; - public override int GetHashCode() + public override bool Equals(object? obj) + { + var other = obj as GuidUdi; + if (other is null) { - return base.GetHashCode(); + return false; } - /// - public override bool IsRoot - { - get { return Guid == Guid.Empty; } - } + return EntityType == other.EntityType && Guid == other.Guid; + } - public GuidUdi EnsureClosed() - { - EnsureNotRoot(); - return this; - } + public override int GetHashCode() => base.GetHashCode(); + + public GuidUdi EnsureClosed() + { + EnsureNotRoot(); + return this; } } diff --git a/src/Umbraco.Core/GuidUtils.cs b/src/Umbraco.Core/GuidUtils.cs index e6ccd6b27f1f..a4285d2e4940 100644 --- a/src/Umbraco.Core/GuidUtils.cs +++ b/src/Umbraco.Core/GuidUtils.cs @@ -1,112 +1,150 @@ -using System; -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Utility methods for the struct. +/// +public static class GuidUtils { + private static readonly char[] Base32Table = + { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', + 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5' + }; + /// - /// Utility methods for the struct. + /// Combines two guid instances utilizing an exclusive disjunction. + /// The resultant guid is not guaranteed to be unique since the number of unique bits is halved. /// - public static class GuidUtils + /// The first guid. + /// The seconds guid. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Guid Combine(Guid a, Guid b) { - /// - /// Combines two guid instances utilizing an exclusive disjunction. - /// The resultant guid is not guaranteed to be unique since the number of unique bits is halved. - /// - /// The first guid. - /// The seconds guid. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Guid Combine(Guid a, Guid b) - { - var ad = new DecomposedGuid(a); - var bd = new DecomposedGuid(b); + var ad = new DecomposedGuid(a); + var bd = new DecomposedGuid(b); - ad.Hi ^= bd.Hi; - ad.Lo ^= bd.Lo; + ad.Hi ^= bd.Hi; + ad.Lo ^= bd.Lo; - return ad.Value; - } + return ad.Value; + } - /// - /// A decomposed guid. Allows access to the high and low bits without unsafe code. - /// - [StructLayout(LayoutKind.Explicit)] - private struct DecomposedGuid + /// + /// Converts a Guid into a base-32 string. + /// + /// A Guid. + /// The string length. + /// A base-32 encoded string. + /// + /// + /// A base-32 string representation of a Guid is the shortest, efficient, representation + /// that is case insensitive (base-64 is case sensitive). + /// + /// Length must be 1-26, anything else becomes 26. + /// + public static string ToBase32String(Guid guid, int length = 26) + { + if (length <= 0 || length > 26) { - [FieldOffset(00)] public Guid Value; - [FieldOffset(00)] public long Hi; - [FieldOffset(08)] public long Lo; - - public DecomposedGuid(Guid value) : this() => this.Value = value; + length = 26; } - private static readonly char[] Base32Table = - { - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', - 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5' - }; - - /// - /// Converts a Guid into a base-32 string. - /// - /// A Guid. - /// The string length. - /// A base-32 encoded string. - /// - /// A base-32 string representation of a Guid is the shortest, efficient, representation - /// that is case insensitive (base-64 is case sensitive). - /// Length must be 1-26, anything else becomes 26. - /// - public static string ToBase32String(Guid guid, int length = 26) + var bytes = guid.ToByteArray(); // a Guid is 128 bits ie 16 bytes + + // this could be optimized by making it unsafe, + // and fixing the table + bytes + chars (see Convert.ToBase64CharArray) + + // each block of 5 bytes = 5*8 = 40 bits + // becomes 40 bits = 8*5 = 8 byte-32 chars + // a Guid is 3 blocks + 8 bits + + // so it turns into a 3*8+2 = 26 chars string + var chars = new char[length]; + + var i = 0; + var j = 0; + + while (i < 15) { + if (j == length) + { + break; + } - if (length <= 0 || length > 26) - length = 26; + chars[j++] = Base32Table[(bytes[i] & 0b1111_1000) >> 3]; + if (j == length) + { + break; + } - var bytes = guid.ToByteArray(); // a Guid is 128 bits ie 16 bytes + chars[j++] = Base32Table[((bytes[i] & 0b0000_0111) << 2) | ((bytes[i + 1] & 0b1100_0000) >> 6)]; + if (j == length) + { + break; + } - // this could be optimized by making it unsafe, - // and fixing the table + bytes + chars (see Convert.ToBase64CharArray) + chars[j++] = Base32Table[(bytes[i + 1] & 0b0011_1110) >> 1]; + if (j == length) + { + break; + } - // each block of 5 bytes = 5*8 = 40 bits - // becomes 40 bits = 8*5 = 8 byte-32 chars - // a Guid is 3 blocks + 8 bits + chars[j++] = Base32Table[(bytes[i + 1] & 0b0000_0001) | ((bytes[i + 2] & 0b1111_0000) >> 4)]; + if (j == length) + { + break; + } - // so it turns into a 3*8+2 = 26 chars string - var chars = new char[length]; + chars[j++] = Base32Table[((bytes[i + 2] & 0b0000_1111) << 1) | ((bytes[i + 3] & 0b1000_0000) >> 7)]; + if (j == length) + { + break; + } - var i = 0; - var j = 0; + chars[j++] = Base32Table[(bytes[i + 3] & 0b0111_1100) >> 2]; + if (j == length) + { + break; + } - while (i < 15) + chars[j++] = Base32Table[((bytes[i + 3] & 0b0000_0011) << 3) | ((bytes[i + 4] & 0b1110_0000) >> 5)]; + if (j == length) { - if (j == length) break; - chars[j++] = Base32Table[(bytes[i] & 0b1111_1000) >> 3]; - if (j == length) break; - chars[j++] = Base32Table[((bytes[i] & 0b0000_0111) << 2) | ((bytes[i + 1] & 0b1100_0000) >> 6)]; - if (j == length) break; - chars[j++] = Base32Table[(bytes[i + 1] & 0b0011_1110) >> 1]; - if (j == length) break; - chars[j++] = Base32Table[(bytes[i + 1] & 0b0000_0001) | ((bytes[i + 2] & 0b1111_0000) >> 4)]; - if (j == length) break; - chars[j++] = Base32Table[((bytes[i + 2] & 0b0000_1111) << 1) | ((bytes[i + 3] & 0b1000_0000) >> 7)]; - if (j == length) break; - chars[j++] = Base32Table[(bytes[i + 3] & 0b0111_1100) >> 2]; - if (j == length) break; - chars[j++] = Base32Table[((bytes[i + 3] & 0b0000_0011) << 3) | ((bytes[i + 4] & 0b1110_0000) >> 5)]; - if (j == length) break; - chars[j++] = Base32Table[bytes[i + 4] & 0b0001_1111]; - - i += 5; + break; } - if (j < length) - chars[j++] = Base32Table[(bytes[i] & 0b1111_1000) >> 3]; - if (j < length) - chars[j] = Base32Table[(bytes[i] & 0b0000_0111) << 2]; + chars[j++] = Base32Table[bytes[i + 4] & 0b0001_1111]; - return new string(chars); + i += 5; + } + + if (j < length) + { + chars[j++] = Base32Table[(bytes[i] & 0b1111_1000) >> 3]; } + + if (j < length) + { + chars[j] = Base32Table[(bytes[i] & 0b0000_0111) << 2]; + } + + return new string(chars); + } + + /// + /// A decomposed guid. Allows access to the high and low bits without unsafe code. + /// + [StructLayout(LayoutKind.Explicit)] + private struct DecomposedGuid + { + [FieldOffset(00)] public readonly Guid Value; + [FieldOffset(00)] public long Hi; + [FieldOffset(08)] public long Lo; + + public DecomposedGuid(Guid value) : this() => Value = value; } } diff --git a/src/Umbraco.Core/Handlers/AuditNotificationsHandler.cs b/src/Umbraco.Core/Handlers/AuditNotificationsHandler.cs index f15edfa1be86..2c5cef193625 100644 --- a/src/Umbraco.Core/Handlers/AuditNotificationsHandler.cs +++ b/src/Umbraco.Core/Handlers/AuditNotificationsHandler.cs @@ -1,10 +1,9 @@ -using System; -using System.Linq; using System.Text; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Net; using Umbraco.Cms.Core.Notifications; @@ -12,228 +11,255 @@ using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Handlers +namespace Umbraco.Cms.Core.Handlers; + +public sealed class AuditNotificationsHandler : + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler, + INotificationHandler { - public sealed class AuditNotificationsHandler : - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler, - INotificationHandler + private readonly IAuditService _auditService; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + private readonly IEntityService _entityService; + private readonly GlobalSettings _globalSettings; + private readonly IIpResolver _ipResolver; + private readonly IMemberService _memberService; + private readonly IUserService _userService; + + public AuditNotificationsHandler( + IAuditService auditService, + IUserService userService, + IEntityService entityService, + IIpResolver ipResolver, + IOptionsMonitor globalSettings, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IMemberService memberService) + { + _auditService = auditService; + _userService = userService; + _entityService = entityService; + _ipResolver = ipResolver; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + _memberService = memberService; + _globalSettings = globalSettings.CurrentValue; + } + + private IUser CurrentPerformingUser { - private readonly IAuditService _auditService; - private readonly IUserService _userService; - private readonly IEntityService _entityService; - private readonly IIpResolver _ipResolver; - private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - private readonly GlobalSettings _globalSettings; - private readonly IMemberService _memberService; - - public AuditNotificationsHandler( - IAuditService auditService, - IUserService userService, - IEntityService entityService, - IIpResolver ipResolver, - IOptionsMonitor globalSettings, - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IMemberService memberService) + get { - _auditService = auditService; - _userService = userService; - _entityService = entityService; - _ipResolver = ipResolver; - _backOfficeSecurityAccessor = backOfficeSecurityAccessor; - _memberService = memberService; - _globalSettings = globalSettings.CurrentValue; + IUser identity = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; + IUser user = identity == null ? null : _userService.GetUserById(Convert.ToInt32(identity.Id)); + return user ?? UnknownUser(_globalSettings); } + } - private IUser CurrentPerformingUser + private string PerformingIp => _ipResolver.GetCurrentRequestIpAddress(); + + public void Handle(AssignedMemberRolesNotification notification) + { + IUser performingUser = CurrentPerformingUser; + var roles = string.Join(", ", notification.Roles); + var members = _memberService.GetAllMembers(notification.MemberIds).ToDictionary(x => x.Id, x => x); + foreach (var id in notification.MemberIds) { - get - { - var identity = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; - var user = identity == null ? null : _userService.GetUserById(Convert.ToInt32(identity.Id)); - return user ?? UnknownUser(_globalSettings); - } + members.TryGetValue(id, out IMember member); + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + PerformingIp, + DateTime.UtcNow, + -1, $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}", + "umbraco/member/roles/assigned", $"roles modified, assigned {roles}"); } + } - public static IUser UnknownUser(GlobalSettings globalSettings) => new User(globalSettings) { Id = Constants.Security.UnknownUserId, Name = Constants.Security.UnknownUserName, Email = "" }; + public void Handle(AssignedUserGroupPermissionsNotification notification) + { + IUser performingUser = CurrentPerformingUser; + IEnumerable perms = notification.EntityPermissions; + foreach (EntityPermission perm in perms) + { + IUserGroup group = _userService.GetUserGroupById(perm.UserGroupId); + var assigned = string.Join(", ", perm.AssignedPermissions ?? Array.Empty()); + IEntitySlim entity = _entityService.Get(perm.EntityId); - private string PerformingIp => _ipResolver.GetCurrentRequestIpAddress(); + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + PerformingIp, + DateTime.UtcNow, + -1, $"User Group {group?.Id} \"{group?.Name}\" ({group?.Alias})", + "umbraco/user-group/permissions-change", + $"assigning {(string.IsNullOrWhiteSpace(assigned) ? "(nothing)" : assigned)} on id:{perm.EntityId} \"{entity?.Name}\""); + } + } - private string FormatEmail(IMember? member) => member == null ? string.Empty : member.Email.IsNullOrWhiteSpace() ? "" : $"<{member.Email}>"; + public void Handle(ExportedMemberNotification notification) + { + IUser performingUser = CurrentPerformingUser; + IMember member = notification.Member; - private string FormatEmail(IUser user) => user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>"; + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + PerformingIp, + DateTime.UtcNow, + -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", + "umbraco/member/exported", "exported member data"); + } - public void Handle(MemberSavedNotification notification) + public void Handle(MemberDeletedNotification notification) + { + IUser performingUser = CurrentPerformingUser; + IEnumerable members = notification.DeletedEntities; + foreach (IMember member in members) { - var performingUser = CurrentPerformingUser; - var members = notification.SavedEntities; - foreach (var member in members) - { - var dp = string.Join(", ", ((Member)member).GetWereDirtyProperties()); - - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, - DateTime.UtcNow, - -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", - "umbraco/member/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}"); - } + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + PerformingIp, + DateTime.UtcNow, + -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", + "umbraco/member/delete", $"delete member id:{member.Id} \"{member.Name}\" {FormatEmail(member)}"); } + } - public void Handle(MemberDeletedNotification notification) + public void Handle(MemberSavedNotification notification) + { + IUser performingUser = CurrentPerformingUser; + IEnumerable members = notification.SavedEntities; + foreach (IMember member in members) { - var performingUser = CurrentPerformingUser; - var members = notification.DeletedEntities; - foreach (var member in members) - { - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, - DateTime.UtcNow, - -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", - "umbraco/member/delete", $"delete member id:{member.Id} \"{member.Name}\" {FormatEmail(member)}"); - } - } + var dp = string.Join(", ", ((Member)member).GetWereDirtyProperties()); - public void Handle(AssignedMemberRolesNotification notification) - { - var performingUser = CurrentPerformingUser; - var roles = string.Join(", ", notification.Roles); - var members = _memberService.GetAllMembers(notification.MemberIds).ToDictionary(x => x.Id, x => x); - foreach (var id in notification.MemberIds) - { - members.TryGetValue(id, out var member); - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, - DateTime.UtcNow, - -1, $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}", - "umbraco/member/roles/assigned", $"roles modified, assigned {roles}"); - } + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + PerformingIp, + DateTime.UtcNow, + -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", + "umbraco/member/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}"); } + } - public void Handle(RemovedMemberRolesNotification notification) + public void Handle(RemovedMemberRolesNotification notification) + { + IUser performingUser = CurrentPerformingUser; + var roles = string.Join(", ", notification.Roles); + var members = _memberService.GetAllMembers(notification.MemberIds).ToDictionary(x => x.Id, x => x); + foreach (var id in notification.MemberIds) { - var performingUser = CurrentPerformingUser; - var roles = string.Join(", ", notification.Roles); - var members = _memberService.GetAllMembers(notification.MemberIds).ToDictionary(x => x.Id, x => x); - foreach (var id in notification.MemberIds) - { - members.TryGetValue(id, out var member); - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, - DateTime.UtcNow, - -1, $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}", - "umbraco/member/roles/removed", $"roles modified, removed {roles}"); - } + members.TryGetValue(id, out IMember member); + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + PerformingIp, + DateTime.UtcNow, + -1, $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}", + "umbraco/member/roles/removed", $"roles modified, removed {roles}"); } + } - public void Handle(ExportedMemberNotification notification) + public void Handle(UserDeletedNotification notification) + { + IUser performingUser = CurrentPerformingUser; + IEnumerable affectedUsers = notification.DeletedEntities; + foreach (IUser affectedUser in affectedUsers) { - var performingUser = CurrentPerformingUser; - var member = notification.Member; - - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + PerformingIp, DateTime.UtcNow, - -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", - "umbraco/member/exported", "exported member data"); + affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", + "umbraco/user/delete", "delete user"); } + } - public void Handle(UserSavedNotification notification) + public void Handle(UserGroupWithUsersSavedNotification notification) + { + IUser performingUser = CurrentPerformingUser; + foreach (UserGroupWithUsers groupWithUser in notification.SavedEntities) { - var performingUser = CurrentPerformingUser; - var affectedUsers = notification.SavedEntities; - foreach (var affectedUser in affectedUsers) - { - var groups = affectedUser.WasPropertyDirty("Groups") - ? string.Join(", ", affectedUser.Groups.Select(x => x.Alias)) - : null; + IUserGroup group = groupWithUser.UserGroup; - var dp = string.Join(", ", ((User)affectedUser).GetWereDirtyProperties()); + var dp = string.Join(", ", ((UserGroup)group).GetWereDirtyProperties()); + var sections = ((UserGroup)group).WasPropertyDirty("AllowedSections") + ? string.Join(", ", group.AllowedSections) + : null; + var perms = ((UserGroup)group).WasPropertyDirty("Permissions") && group.Permissions is not null + ? string.Join(", ", group.Permissions) + : null; - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, - DateTime.UtcNow, - affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", - "umbraco/user/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}{(groups == null ? "" : "; groups assigned: " + groups)}"); + var sb = new StringBuilder(); + sb.Append($"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)};"); + if (sections != null) + { + sb.Append($", assigned sections: {sections}"); } - } - - public void Handle(UserDeletedNotification notification) - { - var performingUser = CurrentPerformingUser; - var affectedUsers = notification.DeletedEntities; - foreach (var affectedUser in affectedUsers) - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, - DateTime.UtcNow, - affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", - "umbraco/user/delete", "delete user"); - } - public void Handle(UserGroupWithUsersSavedNotification notification) - { - var performingUser = CurrentPerformingUser; - foreach (var groupWithUser in notification.SavedEntities) + if (perms != null) { - var group = groupWithUser.UserGroup; - - var dp = string.Join(", ", ((UserGroup)group).GetWereDirtyProperties()); - var sections = ((UserGroup)group).WasPropertyDirty("AllowedSections") - ? string.Join(", ", group.AllowedSections) - : null; - var perms = ((UserGroup)group).WasPropertyDirty("Permissions") && group.Permissions is not null - ? string.Join(", ", group.Permissions) - : null; - - var sb = new StringBuilder(); - sb.Append($"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)};"); if (sections != null) - sb.Append($", assigned sections: {sections}"); - if (perms != null) { - if (sections != null) - sb.Append(", "); - sb.Append($"default perms: {perms}"); + sb.Append(", "); } - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, - DateTime.UtcNow, - -1, $"User Group {group.Id} \"{group.Name}\" ({group.Alias})", - "umbraco/user-group/save", $"{sb}"); + sb.Append($"default perms: {perms}"); + } - // now audit the users that have changed + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + PerformingIp, + DateTime.UtcNow, + -1, $"User Group {group.Id} \"{group.Name}\" ({group.Alias})", + "umbraco/user-group/save", $"{sb}"); - foreach (var user in groupWithUser.RemovedUsers) - { - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, - DateTime.UtcNow, - user.Id, $"User \"{user.Name}\" {FormatEmail(user)}", - "umbraco/user-group/save", $"Removed user \"{user.Name}\" {FormatEmail(user)} from group {group.Id} \"{group.Name}\" ({group.Alias})"); - } + // now audit the users that have changed - foreach (var user in groupWithUser.AddedUsers) - { - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, - DateTime.UtcNow, - user.Id, $"User \"{user.Name}\" {FormatEmail(user)}", - "umbraco/user-group/save", $"Added user \"{user.Name}\" {FormatEmail(user)} to group {group.Id} \"{group.Name}\" ({group.Alias})"); - } + foreach (IUser user in groupWithUser.RemovedUsers) + { + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + PerformingIp, + DateTime.UtcNow, + user.Id, $"User \"{user.Name}\" {FormatEmail(user)}", + "umbraco/user-group/save", + $"Removed user \"{user.Name}\" {FormatEmail(user)} from group {group.Id} \"{group.Name}\" ({group.Alias})"); } - } - public void Handle(AssignedUserGroupPermissionsNotification notification) - { - var performingUser = CurrentPerformingUser; - var perms = notification.EntityPermissions; - foreach (EntityPermission perm in perms) + foreach (IUser user in groupWithUser.AddedUsers) { - var group = _userService.GetUserGroupById(perm.UserGroupId); - var assigned = string.Join(", ", perm.AssignedPermissions ?? Array.Empty()); - var entity = _entityService.Get(perm.EntityId); - - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + PerformingIp, DateTime.UtcNow, - -1, $"User Group {group?.Id} \"{group?.Name}\" ({group?.Alias})", - "umbraco/user-group/permissions-change", $"assigning {(string.IsNullOrWhiteSpace(assigned) ? "(nothing)" : assigned)} on id:{perm.EntityId} \"{entity?.Name}\""); + user.Id, $"User \"{user.Name}\" {FormatEmail(user)}", + "umbraco/user-group/save", + $"Added user \"{user.Name}\" {FormatEmail(user)} to group {group.Id} \"{group.Name}\" ({group.Alias})"); } } } + + public void Handle(UserSavedNotification notification) + { + IUser performingUser = CurrentPerformingUser; + IEnumerable affectedUsers = notification.SavedEntities; + foreach (IUser affectedUser in affectedUsers) + { + var groups = affectedUser.WasPropertyDirty("Groups") + ? string.Join(", ", affectedUser.Groups.Select(x => x.Alias)) + : null; + + var dp = string.Join(", ", ((User)affectedUser).GetWereDirtyProperties()); + + _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + PerformingIp, + DateTime.UtcNow, + affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", + "umbraco/user/save", + $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}{(groups == null ? "" : "; groups assigned: " + groups)}"); + } + } + + public static IUser UnknownUser(GlobalSettings globalSettings) => new User(globalSettings) + { + Id = Constants.Security.UnknownUserId, Name = Constants.Security.UnknownUserName, Email = "" + }; + + private string FormatEmail(IMember? member) => + member == null ? string.Empty : member.Email.IsNullOrWhiteSpace() ? "" : $"<{member.Email}>"; + + private string FormatEmail(IUser user) => + user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>"; } diff --git a/src/Umbraco.Core/Handlers/PublicAccessHandler.cs b/src/Umbraco.Core/Handlers/PublicAccessHandler.cs index 466e09e3f10d..412fd6f737a2 100644 --- a/src/Umbraco.Core/Handlers/PublicAccessHandler.cs +++ b/src/Umbraco.Core/Handlers/PublicAccessHandler.cs @@ -1,38 +1,36 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Handlers +namespace Umbraco.Cms.Core.Handlers; + +public sealed class PublicAccessHandler : + INotificationHandler, + INotificationHandler { - public sealed class PublicAccessHandler : - INotificationHandler, - INotificationHandler - { - private readonly IPublicAccessService _publicAccessService; + private readonly IPublicAccessService _publicAccessService; - public PublicAccessHandler(IPublicAccessService publicAccessService) => - _publicAccessService = publicAccessService ?? throw new ArgumentNullException(nameof(publicAccessService)); + public PublicAccessHandler(IPublicAccessService publicAccessService) => + _publicAccessService = publicAccessService ?? throw new ArgumentNullException(nameof(publicAccessService)); - public void Handle(MemberGroupSavedNotification notification) => Handle(notification.SavedEntities); + public void Handle(MemberGroupDeletedNotification notification) => Handle(notification.DeletedEntities); - public void Handle(MemberGroupDeletedNotification notification) => Handle(notification.DeletedEntities); + public void Handle(MemberGroupSavedNotification notification) => Handle(notification.SavedEntities); - private void Handle(IEnumerable affectedEntities) + private void Handle(IEnumerable affectedEntities) + { + foreach (IMemberGroup grp in affectedEntities) { - foreach (var grp in affectedEntities) + //check if the name has changed + if ((grp.AdditionalData?.ContainsKey("previousName") ?? false) + && grp.AdditionalData["previousName"] != null + && grp.AdditionalData["previousName"]?.ToString().IsNullOrWhiteSpace() == false + && grp.AdditionalData["previousName"]?.ToString() != grp.Name) { - //check if the name has changed - if ((grp.AdditionalData?.ContainsKey("previousName") ?? false) - && grp.AdditionalData["previousName"] != null - && grp.AdditionalData["previousName"]?.ToString().IsNullOrWhiteSpace() == false - && grp.AdditionalData["previousName"]?.ToString() != grp.Name) - { - _publicAccessService.RenameMemberGroupRoleRules(grp.AdditionalData["previousName"]?.ToString(), grp.Name); - } + _publicAccessService.RenameMemberGroupRoleRules(grp.AdditionalData["previousName"]?.ToString(), + grp.Name); } } } diff --git a/src/Umbraco.Core/HashCodeCombiner.cs b/src/Umbraco.Core/HashCodeCombiner.cs index d8c1ac2a0702..a92f0fde36f6 100644 --- a/src/Umbraco.Core/HashCodeCombiner.cs +++ b/src/Umbraco.Core/HashCodeCombiner.cs @@ -1,100 +1,85 @@ -using System; -using System.Globalization; -using System.IO; +using System.Globalization; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Used to create a .NET HashCode from multiple objects. +/// +/// +/// .Net has a class the same as this: System.Web.Util.HashCodeCombiner and of course it works for all sorts of things +/// which we've not included here as we just need a quick easy class for this in order to create a unique +/// hash of directories/files to see if they have changed. +/// NOTE: It's probably best to not relying on the hashing result across AppDomains! If you need a constant/reliable +/// hash value +/// between AppDomains use SHA1. This is perfect for hashing things in a very fast way for a single AppDomain. +/// +public class HashCodeCombiner { - /// - /// Used to create a .NET HashCode from multiple objects. - /// - /// - /// .Net has a class the same as this: System.Web.Util.HashCodeCombiner and of course it works for all sorts of things - /// which we've not included here as we just need a quick easy class for this in order to create a unique - /// hash of directories/files to see if they have changed. - /// - /// NOTE: It's probably best to not relying on the hashing result across AppDomains! If you need a constant/reliable hash value - /// between AppDomains use SHA1. This is perfect for hashing things in a very fast way for a single AppDomain. - /// - public class HashCodeCombiner - { - private long _combinedHash = 5381L; + private long _combinedHash = 5381L; - public void AddInt(int i) - { - _combinedHash = ((_combinedHash << 5) + _combinedHash) ^ i; - } + public void AddInt(int i) => _combinedHash = ((_combinedHash << 5) + _combinedHash) ^ i; + + public void AddObject(object o) => AddInt(o.GetHashCode()); - public void AddObject(object o) + public void AddDateTime(DateTime d) => AddInt(d.GetHashCode()); + + public void AddString(string s) + { + if (s != null) { - AddInt(o.GetHashCode()); + AddInt(StringComparer.InvariantCulture.GetHashCode(s)); } + } - public void AddDateTime(DateTime d) + public void AddCaseInsensitiveString(string s) + { + if (s != null) { - AddInt(d.GetHashCode()); + AddInt(StringComparer.InvariantCultureIgnoreCase.GetHashCode(s)); } + } - public void AddString(string s) + public void AddFileSystemItem(FileSystemInfo f) + { + //if it doesn't exist, don't proceed. + if (!f.Exists) { - if (s != null) - AddInt((StringComparer.InvariantCulture).GetHashCode(s)); + return; } - public void AddCaseInsensitiveString(string s) + AddCaseInsensitiveString(f.FullName); + AddDateTime(f.CreationTimeUtc); + AddDateTime(f.LastWriteTimeUtc); + + //check if it is a file or folder + var fileInfo = f as FileInfo; + if (fileInfo != null) { - if (s != null) - AddInt((StringComparer.InvariantCultureIgnoreCase).GetHashCode(s)); + AddInt(fileInfo.Length.GetHashCode()); } - public void AddFileSystemItem(FileSystemInfo f) + var dirInfo = f as DirectoryInfo; + if (dirInfo != null) { - //if it doesn't exist, don't proceed. - if (!f.Exists) - return; - - AddCaseInsensitiveString(f.FullName); - AddDateTime(f.CreationTimeUtc); - AddDateTime(f.LastWriteTimeUtc); - - //check if it is a file or folder - var fileInfo = f as FileInfo; - if (fileInfo != null) + foreach (FileInfo d in dirInfo.GetFiles()) { - AddInt(fileInfo.Length.GetHashCode()); + AddFile(d); } - var dirInfo = f as DirectoryInfo; - if (dirInfo != null) + foreach (DirectoryInfo s in dirInfo.GetDirectories()) { - foreach (var d in dirInfo.GetFiles()) - { - AddFile(d); - } - foreach (var s in dirInfo.GetDirectories()) - { - AddFolder(s); - } + AddFolder(s); } } + } - public void AddFile(FileInfo f) - { - AddFileSystemItem(f); - } + public void AddFile(FileInfo f) => AddFileSystemItem(f); - public void AddFolder(DirectoryInfo d) - { - AddFileSystemItem(d); - } + public void AddFolder(DirectoryInfo d) => AddFileSystemItem(d); - /// - /// Returns the hex code of the combined hash code - /// - /// - public string GetCombinedHashCode() - { - return _combinedHash.ToString("x", CultureInfo.InvariantCulture); - } - - } + /// + /// Returns the hex code of the combined hash code + /// + /// + public string GetCombinedHashCode() => _combinedHash.ToString("x", CultureInfo.InvariantCulture); } diff --git a/src/Umbraco.Core/HashCodeHelper.cs b/src/Umbraco.Core/HashCodeHelper.cs index 6d98ec57b85b..369686de6418 100644 --- a/src/Umbraco.Core/HashCodeHelper.cs +++ b/src/Umbraco.Core/HashCodeHelper.cs @@ -1,104 +1,116 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core; -namespace Umbraco.Cms.Core +/// +/// Borrowed from http://stackoverflow.com/a/2575444/694494 +/// +public static class HashCodeHelper { - /// - /// Borrowed from http://stackoverflow.com/a/2575444/694494 - /// - public static class HashCodeHelper + public static int GetHashCode(T1 arg1, T2 arg2) { - public static int GetHashCode(T1 arg1, T2 arg2) + unchecked { - unchecked - { - return 31 * arg1!.GetHashCode() + arg2!.GetHashCode(); - } + return (31 * arg1!.GetHashCode()) + arg2!.GetHashCode(); } + } - public static int GetHashCode(T1 arg1, T2 arg2, T3 arg3) + public static int GetHashCode(T1 arg1, T2 arg2, T3 arg3) + { + unchecked { - unchecked - { - int hash = arg1!.GetHashCode(); - hash = 31 * hash + arg2!.GetHashCode(); - return 31 * hash + arg3!.GetHashCode(); - } + var hash = arg1!.GetHashCode(); + hash = (31 * hash) + arg2!.GetHashCode(); + return (31 * hash) + arg3!.GetHashCode(); } + } - public static int GetHashCode(T1 arg1, T2 arg2, T3 arg3, - T4 arg4) + public static int GetHashCode(T1 arg1, T2 arg2, T3 arg3, + T4 arg4) + { + unchecked { - unchecked - { - int hash = arg1!.GetHashCode(); - hash = 31 * hash + arg2!.GetHashCode(); - hash = 31 * hash + arg3!.GetHashCode(); - return 31 * hash + arg4!.GetHashCode(); - } + var hash = arg1!.GetHashCode(); + hash = (31 * hash) + arg2!.GetHashCode(); + hash = (31 * hash) + arg3!.GetHashCode(); + return (31 * hash) + arg4!.GetHashCode(); } + } - public static int GetHashCode(T[] list) + public static int GetHashCode(T[] list) + { + unchecked { - unchecked + var hash = 0; + foreach (T item in list) { - int hash = 0; - foreach (var item in list) + if (item == null) { - if (item == null) continue; - hash = 31 * hash + item.GetHashCode(); + continue; } - return hash; + + hash = (31 * hash) + item.GetHashCode(); } + + return hash; } + } - public static int GetHashCode(IEnumerable list) + public static int GetHashCode(IEnumerable list) + { + unchecked { - unchecked + var hash = 0; + foreach (T item in list) { - int hash = 0; - foreach (var item in list) + if (item == null) { - if (item == null) continue; - hash = 31 * hash + item.GetHashCode(); + continue; } - return hash; + + hash = (31 * hash) + item.GetHashCode(); } + + return hash; } + } - /// - /// Gets a hashcode for a collection for that the order of items - /// does not matter. - /// So {1, 2, 3} and {3, 2, 1} will get same hash code. - /// - public static int GetHashCodeForOrderNoMatterCollection( - IEnumerable list) + /// + /// Gets a hashcode for a collection for that the order of items + /// does not matter. + /// So {1, 2, 3} and {3, 2, 1} will get same hash code. + /// + public static int GetHashCodeForOrderNoMatterCollection( + IEnumerable list) + { + unchecked { - unchecked + var hash = 0; + var count = 0; + foreach (T item in list) { - int hash = 0; - int count = 0; - foreach (var item in list) + if (item == null) { - if (item == null) continue; - hash += item.GetHashCode(); - count++; + continue; } - return 31 * hash + count.GetHashCode(); + + hash += item.GetHashCode(); + count++; } + + return (31 * hash) + count.GetHashCode(); } + } - /// - /// Alternative way to get a hashcode is to use a fluent - /// interface like this:
- /// return 0.CombineHashCode(field1).CombineHashCode(field2). - /// CombineHashCode(field3); - ///
- public static int CombineHashCode(this int hashCode, T arg) + /// + /// Alternative way to get a hashcode is to use a fluent + /// interface like this:
+ /// return 0.CombineHashCode(field1).CombineHashCode(field2). + /// CombineHashCode(field3); + ///
+ public static int CombineHashCode(this int hashCode, T arg) + { + unchecked { - unchecked - { - return 31 * hashCode + arg!.GetHashCode(); - } + return (31 * hashCode) + arg!.GetHashCode(); } } } diff --git a/src/Umbraco.Core/HashGenerator.cs b/src/Umbraco.Core/HashGenerator.cs index 944e0bdf4997..2f66c1340904 100644 --- a/src/Umbraco.Core/HashGenerator.cs +++ b/src/Umbraco.Core/HashGenerator.cs @@ -1,151 +1,138 @@ -using System; -using System.IO; using System.Security.Cryptography; using System.Text; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Used to generate a string hash using crypto libraries over multiple objects +/// +/// +/// This should be used to generate a reliable hash that survives AppDomain restarts. +/// This will use the crypto libs to generate the hash and will try to ensure that +/// strings, etc... are not re-allocated so it's not consuming much memory. +/// +public class HashGenerator : DisposableObjectSlim { - /// - /// Used to generate a string hash using crypto libraries over multiple objects - /// - /// - /// This should be used to generate a reliable hash that survives AppDomain restarts. - /// This will use the crypto libs to generate the hash and will try to ensure that - /// strings, etc... are not re-allocated so it's not consuming much memory. - /// - public class HashGenerator : DisposableObjectSlim - { - public HashGenerator() - { - _writer = new StreamWriter(_ms, Encoding.Unicode, 1024, leaveOpen: true); - } + private readonly MemoryStream _ms = new(); + private StreamWriter _writer; - private readonly MemoryStream _ms = new MemoryStream(); - private StreamWriter _writer; + public HashGenerator() => _writer = new StreamWriter(_ms, Encoding.Unicode, 1024, true); - public void AddInt(int i) - { - _writer.Write(i); - } + public void AddInt(int i) => _writer.Write(i); - public void AddLong(long i) - { - _writer.Write(i); - } + public void AddLong(long i) => _writer.Write(i); - public void AddObject(object o) + public void AddObject(object o) => _writer.Write(o); + + public void AddDateTime(DateTime d) => _writer.Write(d.Ticks); + + public void AddString(string s) + { + if (s != null) { - _writer.Write(o); + _writer.Write(s); } + } - public void AddDateTime(DateTime d) + public void AddCaseInsensitiveString(string s) + { + //I've tried to no allocate a new string with this which can be done if we use the CompareInfo.GetSortKey method which will create a new + //byte array that we can use to write to the output, however this also allocates new objects so i really don't think the performance + //would be much different. In any case, I'll leave this here for reference. We could write the bytes out based on the sort key, + //this is how we could deal with case insensitivity without allocating another string + //for reference see: https://stackoverflow.com/a/10452967/694494 + //we could go a step further and s.Normalize() but we're not really dealing with crazy unicode with this class so far. + + if (s != null) { - _writer.Write(d.Ticks); + _writer.Write(s.ToUpperInvariant()); } + } - public void AddString(string s) + public void AddFileSystemItem(FileSystemInfo f) + { + //if it doesn't exist, don't proceed. + if (f.Exists == false) { - if (s != null) - _writer.Write(s); + return; } - public void AddCaseInsensitiveString(string s) + AddCaseInsensitiveString(f.FullName); + AddDateTime(f.CreationTimeUtc); + AddDateTime(f.LastWriteTimeUtc); + + //check if it is a file or folder + if (f is FileInfo fileInfo) { - //I've tried to no allocate a new string with this which can be done if we use the CompareInfo.GetSortKey method which will create a new - //byte array that we can use to write to the output, however this also allocates new objects so i really don't think the performance - //would be much different. In any case, I'll leave this here for reference. We could write the bytes out based on the sort key, - //this is how we could deal with case insensitivity without allocating another string - //for reference see: https://stackoverflow.com/a/10452967/694494 - //we could go a step further and s.Normalize() but we're not really dealing with crazy unicode with this class so far. - - if (s != null) - _writer.Write(s.ToUpperInvariant()); + AddLong(fileInfo.Length); } - public void AddFileSystemItem(FileSystemInfo f) + if (f is DirectoryInfo dirInfo) { - //if it doesn't exist, don't proceed. - if (f.Exists == false) - return; - - AddCaseInsensitiveString(f.FullName); - AddDateTime(f.CreationTimeUtc); - AddDateTime(f.LastWriteTimeUtc); - - //check if it is a file or folder - if (f is FileInfo fileInfo) + foreach (FileInfo d in dirInfo.GetFiles()) { - AddLong(fileInfo.Length); + AddFile(d); } - if (f is DirectoryInfo dirInfo) + foreach (DirectoryInfo s in dirInfo.GetDirectories()) { - foreach (var d in dirInfo.GetFiles()) - { - AddFile(d); - } - foreach (var s in dirInfo.GetDirectories()) - { - AddFolder(s); - } + AddFolder(s); } } + } - public void AddFile(FileInfo f) - { - AddFileSystemItem(f); - } + public void AddFile(FileInfo f) => AddFileSystemItem(f); + + public void AddFolder(DirectoryInfo d) => AddFileSystemItem(d); + + /// + /// Returns the generated hash output of all added objects + /// + /// + public string GenerateHash() + { + //flush,close,dispose the writer,then create a new one since it's possible to keep adding after GenerateHash is called. - public void AddFolder(DirectoryInfo d) + _writer.Flush(); + _writer.Close(); + _writer.Dispose(); + _writer = new StreamWriter(_ms, Encoding.UTF8, 1024, true); + + var hashType = CryptoConfig.AllowOnlyFipsAlgorithms ? "SHA1" : "MD5"; + + //create an instance of the correct hashing provider based on the type passed in + var hasher = HashAlgorithm.Create(hashType); + if (hasher == null) { - AddFileSystemItem(d); + throw new InvalidOperationException("No hashing type found by name " + hashType); } - /// - /// Returns the generated hash output of all added objects - /// - /// - public string GenerateHash() + using (hasher) { - //flush,close,dispose the writer,then create a new one since it's possible to keep adding after GenerateHash is called. + var buffer = _ms.GetBuffer(); + //get the hashed values created by our selected provider + var hashedByteArray = hasher.ComputeHash(buffer); - _writer.Flush(); - _writer.Close(); - _writer.Dispose(); - _writer = new StreamWriter(_ms, Encoding.UTF8, 1024, leaveOpen: true); + //create a StringBuilder object + var stringBuilder = new StringBuilder(); - var hashType = CryptoConfig.AllowOnlyFipsAlgorithms ? "SHA1" : "MD5"; - - //create an instance of the correct hashing provider based on the type passed in - var hasher = HashAlgorithm.Create(hashType); - if (hasher == null) throw new InvalidOperationException("No hashing type found by name " + hashType); - using (hasher) + //loop to each byte + foreach (var b in hashedByteArray) { - var buffer = _ms.GetBuffer(); - //get the hashed values created by our selected provider - var hashedByteArray = hasher.ComputeHash(buffer); - - //create a StringBuilder object - var stringBuilder = new StringBuilder(); - - //loop to each byte - foreach (var b in hashedByteArray) - { - //append it to our StringBuilder - stringBuilder.Append(b.ToString("x2")); - } - - //return the hashed value - return stringBuilder.ToString(); + //append it to our StringBuilder + stringBuilder.Append(b.ToString("x2")); } - } - protected override void DisposeResources() - { - _writer.Close(); - _writer.Dispose(); - _ms.Close(); - _ms.Dispose(); + //return the hashed value + return stringBuilder.ToString(); } } + + protected override void DisposeResources() + { + _writer.Close(); + _writer.Dispose(); + _ms.Close(); + _ms.Dispose(); + } } diff --git a/src/Umbraco.Core/HealthChecks/AcceptableConfiguration.cs b/src/Umbraco.Core/HealthChecks/AcceptableConfiguration.cs index 93cdea7c0b1f..1768a3917eeb 100644 --- a/src/Umbraco.Core/HealthChecks/AcceptableConfiguration.cs +++ b/src/Umbraco.Core/HealthChecks/AcceptableConfiguration.cs @@ -1,8 +1,7 @@ -namespace Umbraco.Cms.Core.HealthChecks +namespace Umbraco.Cms.Core.HealthChecks; + +public class AcceptableConfiguration { - public class AcceptableConfiguration - { - public string? Value { get; set; } - public bool IsRecommended { get; set; } - } + public string? Value { get; set; } + public bool IsRecommended { get; set; } } diff --git a/src/Umbraco.Core/HealthChecks/Checks/AbstractSettingsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/AbstractSettingsCheck.cs index 7123255b0d9c..8898b1e30fc5 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/AbstractSettingsCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/AbstractSettingsCheck.cs @@ -1,101 +1,95 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.HealthChecks.Checks +namespace Umbraco.Cms.Core.HealthChecks.Checks; + +/// +/// Provides a base class for health checks of configuration values. +/// +public abstract class AbstractSettingsCheck : HealthCheck { /// - /// Provides a base class for health checks of configuration values. + /// Initializes a new instance of the class. + /// + protected AbstractSettingsCheck(ILocalizedTextService textService) => LocalizedTextService = textService; + + /// + /// Gets the localized text service. + /// + protected ILocalizedTextService LocalizedTextService { get; } + + /// + /// Gets key within the JSON to check, in the colon-delimited format + /// https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1 + /// + public abstract string ItemPath { get; } + + /// + /// Gets a link to an external resource with more information. + /// + public abstract string ReadMoreLink { get; } + + /// + /// Gets the values to compare against. + /// + public abstract IEnumerable Values { get; } + + /// + /// Gets the current value of the config setting + /// + public abstract string CurrentValue { get; } + + /// + /// Gets the comparison type for checking the value. /// - public abstract class AbstractSettingsCheck : HealthCheck + public abstract ValueComparisonType ValueComparisonType { get; } + + /// + /// Gets the message for when the check has succeeded. + /// + public virtual string CheckSuccessMessage => LocalizedTextService.Localize("healthcheck", "checkSuccessMessage", + new[] {CurrentValue, Values.First(v => v.IsRecommended).Value, ItemPath}); + + /// + /// Gets the message for when the check has failed. + /// + public virtual string CheckErrorMessage => + ValueComparisonType == ValueComparisonType.ShouldEqual + ? LocalizedTextService.Localize( + "healthcheck", "checkErrorMessageDifferentExpectedValue", + new[] {CurrentValue, Values.First(v => v.IsRecommended).Value, ItemPath}) + : LocalizedTextService.Localize( + "healthcheck", "checkErrorMessageUnexpectedValue", + new[] {CurrentValue, Values.First(v => v.IsRecommended).Value, ItemPath}); + + /// + public override Task> GetStatus() { - /// - /// Initializes a new instance of the class. - /// - protected AbstractSettingsCheck(ILocalizedTextService textService) => LocalizedTextService = textService; - - /// - /// Gets the localized text service. - /// - protected ILocalizedTextService LocalizedTextService { get; } - - /// - /// Gets key within the JSON to check, in the colon-delimited format - /// https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1 - /// - public abstract string ItemPath { get; } - - /// - /// Gets a link to an external resource with more information. - /// - public abstract string ReadMoreLink { get; } - - /// - /// Gets the values to compare against. - /// - public abstract IEnumerable Values { get; } - - /// - /// Gets the current value of the config setting - /// - public abstract string CurrentValue { get; } - - /// - /// Gets the comparison type for checking the value. - /// - public abstract ValueComparisonType ValueComparisonType { get; } - - /// - /// Gets the message for when the check has succeeded. - /// - public virtual string CheckSuccessMessage => LocalizedTextService.Localize("healthcheck", "checkSuccessMessage", new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, ItemPath }); - - /// - /// Gets the message for when the check has failed. - /// - public virtual string CheckErrorMessage => - ValueComparisonType == ValueComparisonType.ShouldEqual - ? LocalizedTextService.Localize( - "healthcheck", "checkErrorMessageDifferentExpectedValue", - new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, ItemPath }) - : LocalizedTextService.Localize( - "healthcheck", "checkErrorMessageUnexpectedValue", - new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, ItemPath }); - - /// - public override Task> GetStatus() + // update the successMessage with the CurrentValue + var successMessage = string.Format(CheckSuccessMessage, ItemPath, Values, CurrentValue); + var valueFound = Values.Any(value => + string.Equals(CurrentValue, value.Value, StringComparison.InvariantCultureIgnoreCase)); + + if ((ValueComparisonType == ValueComparisonType.ShouldEqual && valueFound) + || (ValueComparisonType == ValueComparisonType.ShouldNotEqual && valueFound == false)) { - // update the successMessage with the CurrentValue - var successMessage = string.Format(CheckSuccessMessage, ItemPath, Values, CurrentValue); - bool valueFound = Values.Any(value => string.Equals(CurrentValue, value.Value, StringComparison.InvariantCultureIgnoreCase)); - - if ((ValueComparisonType == ValueComparisonType.ShouldEqual && valueFound) - || (ValueComparisonType == ValueComparisonType.ShouldNotEqual && valueFound == false)) - { - return Task.FromResult(new HealthCheckStatus(successMessage) - { - ResultType = StatusResultType.Success, - }.Yield()); - } - - string resultMessage = string.Format(CheckErrorMessage, ItemPath, Values, CurrentValue); - var healthCheckStatus = new HealthCheckStatus(resultMessage) - { - ResultType = StatusResultType.Error, - ReadMoreLink = ReadMoreLink - }; - - return Task.FromResult(healthCheckStatus.Yield()); + return Task.FromResult( + new HealthCheckStatus(successMessage) {ResultType = StatusResultType.Success}.Yield()); } - /// - public override HealthCheckStatus ExecuteAction(HealthCheckAction action) - => throw new NotSupportedException("Configuration cannot be automatically fixed."); + var resultMessage = string.Format(CheckErrorMessage, ItemPath, Values, CurrentValue); + var healthCheckStatus = new HealthCheckStatus(resultMessage) + { + ResultType = StatusResultType.Error, ReadMoreLink = ReadMoreLink + }; + + return Task.FromResult(healthCheckStatus.Yield()); } + + /// + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) + => throw new NotSupportedException("Configuration cannot be automatically fixed."); } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Configuration/MacroErrorsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Configuration/MacroErrorsCheck.cs index 2ded5a0659bb..19a0e196afc8 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Configuration/MacroErrorsCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Configuration/MacroErrorsCheck.cs @@ -1,92 +1,82 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; -using System.Linq; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Macros; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.HealthChecks.Checks.Configuration +namespace Umbraco.Cms.Core.HealthChecks.Checks.Configuration; + +/// +/// Health check for the recommended production configuration for Macro Errors. +/// +[HealthCheck( + "D0F7599E-9B2A-4D9E-9883-81C7EDC5616F", + "Macro errors", + Description = + "Checks to make sure macro errors are not set to throw a YSOD (yellow screen of death), which would prevent certain or all pages from loading completely.", + Group = "Configuration")] +public class MacroErrorsCheck : AbstractSettingsCheck { + private readonly IOptionsMonitor _contentSettings; + private readonly ILocalizedTextService _textService; + /// - /// Health check for the recommended production configuration for Macro Errors. + /// Initializes a new instance of the class. /// - [HealthCheck( - "D0F7599E-9B2A-4D9E-9883-81C7EDC5616F", - "Macro errors", - Description = "Checks to make sure macro errors are not set to throw a YSOD (yellow screen of death), which would prevent certain or all pages from loading completely.", - Group = "Configuration")] - public class MacroErrorsCheck : AbstractSettingsCheck + public MacroErrorsCheck( + ILocalizedTextService textService, + IOptionsMonitor contentSettings) + : base(textService) { - private readonly ILocalizedTextService _textService; - private readonly IOptionsMonitor _contentSettings; - - /// - /// Initializes a new instance of the class. - /// - public MacroErrorsCheck( - ILocalizedTextService textService, - IOptionsMonitor contentSettings) - : base(textService) - { - _textService = textService; - _contentSettings = contentSettings; - } + _textService = textService; + _contentSettings = contentSettings; + } - /// - public override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.Configuration.MacroErrorsCheck; + /// + public override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.Configuration.MacroErrorsCheck; - /// - public override ValueComparisonType ValueComparisonType => ValueComparisonType.ShouldEqual; + /// + public override ValueComparisonType ValueComparisonType => ValueComparisonType.ShouldEqual; - /// - public override string ItemPath => Constants.Configuration.ConfigContentMacroErrors; + /// + public override string ItemPath => Constants.Configuration.ConfigContentMacroErrors; - /// - /// Gets the values to compare against. - /// - public override IEnumerable Values + /// + /// Gets the values to compare against. + /// + public override IEnumerable Values + { + get { - get + var values = new List { - var values = new List - { - new AcceptableConfiguration - { - IsRecommended = true, - Value = MacroErrorBehaviour.Inline.ToString() - }, - new AcceptableConfiguration - { - IsRecommended = false, - Value = MacroErrorBehaviour.Silent.ToString() - } - }; + new() {IsRecommended = true, Value = MacroErrorBehaviour.Inline.ToString()}, + new() {IsRecommended = false, Value = MacroErrorBehaviour.Silent.ToString()} + }; - return values; - } + return values; } + } - /// - public override string CurrentValue => _contentSettings.CurrentValue.MacroErrors.ToString(); + /// + public override string CurrentValue => _contentSettings.CurrentValue.MacroErrors.ToString(); - /// - /// Gets the message for when the check has succeeded. - /// - public override string CheckSuccessMessage => - _textService.Localize( - "healthcheck","macroErrorModeCheckSuccessMessage", - new[] { CurrentValue, Values.First(v => v.IsRecommended).Value }); + /// + /// Gets the message for when the check has succeeded. + /// + public override string CheckSuccessMessage => + _textService.Localize( + "healthcheck", "macroErrorModeCheckSuccessMessage", + new[] {CurrentValue, Values.First(v => v.IsRecommended).Value}); - /// - /// Gets the message for when the check has failed. - /// - public override string CheckErrorMessage => - _textService.Localize( - "healthcheck","macroErrorModeCheckErrorMessage", - new[] { CurrentValue, Values.First(v => v.IsRecommended).Value }); - } + /// + /// Gets the message for when the check has failed. + /// + public override string CheckErrorMessage => + _textService.Localize( + "healthcheck", "macroErrorModeCheckErrorMessage", + new[] {CurrentValue, Values.First(v => v.IsRecommended).Value}); } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Configuration/NotificationEmailCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Configuration/NotificationEmailCheck.cs index 9cb56392056a..c52a55b18e8b 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Configuration/NotificationEmailCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Configuration/NotificationEmailCheck.cs @@ -1,62 +1,61 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.HealthChecks.Checks.Configuration +namespace Umbraco.Cms.Core.HealthChecks.Checks.Configuration; + +/// +/// Health check for the recommended production configuration for Notification Email. +/// +[HealthCheck( + "3E2F7B14-4B41-452B-9A30-E67FBC8E1206", + "Notification Email Settings", + Description = + "If notifications are used, the 'from' email address should be specified and changed from the default value.", + Group = "Configuration")] +public class NotificationEmailCheck : AbstractSettingsCheck { + private const string DefaultFromEmail = "your@email.here"; + private readonly IOptionsMonitor _contentSettings; /// - /// Health check for the recommended production configuration for Notification Email. + /// Initializes a new instance of the class. /// - [HealthCheck( - "3E2F7B14-4B41-452B-9A30-E67FBC8E1206", - "Notification Email Settings", - Description = "If notifications are used, the 'from' email address should be specified and changed from the default value.", - Group = "Configuration")] - public class NotificationEmailCheck : AbstractSettingsCheck + public NotificationEmailCheck( + ILocalizedTextService textService, + IOptionsMonitor contentSettings) + : base(textService) => + _contentSettings = contentSettings; + + /// + public override string ItemPath => Constants.Configuration.ConfigContentNotificationsEmail; + + /// + public override ValueComparisonType ValueComparisonType => ValueComparisonType.ShouldNotEqual; + + /// + public override IEnumerable Values => new List { - private readonly IOptionsMonitor _contentSettings; - private const string DefaultFromEmail = "your@email.here"; - - /// - /// Initializes a new instance of the class. - /// - public NotificationEmailCheck( - ILocalizedTextService textService, - IOptionsMonitor contentSettings) - : base(textService) => - _contentSettings = contentSettings; - - /// - public override string ItemPath => Constants.Configuration.ConfigContentNotificationsEmail; - - /// - public override ValueComparisonType ValueComparisonType => ValueComparisonType.ShouldNotEqual; - - /// - public override IEnumerable Values => new List - { - new AcceptableConfiguration { IsRecommended = false, Value = DefaultFromEmail }, - new AcceptableConfiguration { IsRecommended = false, Value = string.Empty } - }; - - /// - public override string CurrentValue => _contentSettings.CurrentValue.Notifications.Email ?? string.Empty; - - /// - public override string CheckSuccessMessage => - LocalizedTextService.Localize("healthcheck","notificationEmailsCheckSuccessMessage", - new[] { CurrentValue ?? "<null>" }); - - /// - public override string CheckErrorMessage => LocalizedTextService.Localize("healthcheck","notificationEmailsCheckErrorMessage", new[] { DefaultFromEmail }); - - /// - public override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.Configuration.NotificationEmailCheck; - } + new() {IsRecommended = false, Value = DefaultFromEmail}, new() {IsRecommended = false, Value = string.Empty} + }; + + /// + public override string CurrentValue => _contentSettings.CurrentValue.Notifications.Email ?? string.Empty; + + /// + public override string CheckSuccessMessage => + LocalizedTextService.Localize("healthcheck", "notificationEmailsCheckSuccessMessage", + new[] {CurrentValue ?? "<null>"}); + + /// + public override string CheckErrorMessage => LocalizedTextService.Localize("healthcheck", + "notificationEmailsCheckErrorMessage", new[] {DefaultFromEmail}); + + /// + public override string ReadMoreLink => + Constants.HealthChecks.DocumentationLinks.Configuration.NotificationEmailCheck; } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Data/DatabaseIntegrityCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Data/DatabaseIntegrityCheck.cs index dda7fb2e6e57..786a90ecf676 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Data/DatabaseIntegrityCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Data/DatabaseIntegrityCheck.cs @@ -1,138 +1,127 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading.Tasks; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.HealthChecks.Checks.Data +namespace Umbraco.Cms.Core.HealthChecks.Checks.Data; + +/// +/// Health check for the integrity of the data in the database. +/// +[HealthCheck( + "73DD0C1C-E0CA-4C31-9564-1DCA509788AF", + "Database data integrity check", + Description = "Checks for various data integrity issues in the Umbraco database.", + Group = "Data Integrity")] +public class DatabaseIntegrityCheck : HealthCheck { + private const string SSsFixMediaPaths = "fixMediaPaths"; + private const string SFixContentPaths = "fixContentPaths"; + private const string SFixMediaPathsTitle = "Fix media paths"; + private const string SFixContentPathsTitle = "Fix content paths"; + private readonly IContentService _contentService; + private readonly IMediaService _mediaService; + /// - /// Health check for the integrity of the data in the database. + /// Initializes a new instance of the class. /// - [HealthCheck( - "73DD0C1C-E0CA-4C31-9564-1DCA509788AF", - "Database data integrity check", - Description = "Checks for various data integrity issues in the Umbraco database.", - Group = "Data Integrity")] - public class DatabaseIntegrityCheck : HealthCheck + public DatabaseIntegrityCheck( + IContentService contentService, + IMediaService mediaService) { - private readonly IContentService _contentService; - private readonly IMediaService _mediaService; - private const string SSsFixMediaPaths = "fixMediaPaths"; - private const string SFixContentPaths = "fixContentPaths"; - private const string SFixMediaPathsTitle = "Fix media paths"; - private const string SFixContentPathsTitle = "Fix content paths"; - - /// - /// Initializes a new instance of the class. - /// - public DatabaseIntegrityCheck( - IContentService contentService, - IMediaService mediaService) + _contentService = contentService; + _mediaService = mediaService; + } + + /// + /// Get the status for this health check + /// + public override Task> GetStatus() => + Task.FromResult((IEnumerable)new[] {CheckDocuments(false), CheckMedia(false)}); + + private HealthCheckStatus CheckMedia(bool fix) => + CheckPaths( + SSsFixMediaPaths, + SFixMediaPathsTitle, + Constants.UdiEntityType.Media, + fix, + () => _mediaService.CheckDataIntegrity(new ContentDataIntegrityReportOptions {FixIssues = fix})); + + private HealthCheckStatus CheckDocuments(bool fix) => + CheckPaths( + SFixContentPaths, + SFixContentPathsTitle, + Constants.UdiEntityType.Document, + fix, + () => _contentService.CheckDataIntegrity(new ContentDataIntegrityReportOptions {FixIssues = fix})); + + private HealthCheckStatus CheckPaths(string actionAlias, string actionName, string entityType, bool detailedReport, + Func doCheck) + { + ContentDataIntegrityReport report = doCheck(); + + var actions = new List(); + if (!report.Ok) { - _contentService = contentService; - _mediaService = mediaService; + actions.Add(new HealthCheckAction(actionAlias, Id) {Name = actionName}); } - /// - /// Get the status for this health check - /// - public override Task> GetStatus() => - Task.FromResult((IEnumerable)new[] - { - CheckDocuments(false), - CheckMedia(false) - }); - - private HealthCheckStatus CheckMedia(bool fix) => - CheckPaths( - SSsFixMediaPaths, - SFixMediaPathsTitle, - Constants.UdiEntityType.Media, - fix, - () => _mediaService.CheckDataIntegrity(new ContentDataIntegrityReportOptions { FixIssues = fix })); - - private HealthCheckStatus CheckDocuments(bool fix) => - CheckPaths( - SFixContentPaths, - SFixContentPathsTitle, - Constants.UdiEntityType.Document, - fix, - () => _contentService.CheckDataIntegrity(new ContentDataIntegrityReportOptions { FixIssues = fix })); - - private HealthCheckStatus CheckPaths(string actionAlias, string actionName, string entityType, bool detailedReport, Func doCheck) + return new HealthCheckStatus(GetReport(report, entityType, detailedReport)) { - ContentDataIntegrityReport report = doCheck(); - - var actions = new List(); - if (!report.Ok) - { - actions.Add(new HealthCheckAction(actionAlias, Id) - { - Name = actionName - }); - } + ResultType = report.Ok ? StatusResultType.Success : StatusResultType.Error, Actions = actions + }; + } - return new HealthCheckStatus(GetReport(report, entityType, detailedReport)) - { - ResultType = report.Ok ? StatusResultType.Success : StatusResultType.Error, - Actions = actions - }; - } + private static string GetReport(ContentDataIntegrityReport report, string entityType, bool detailed) + { + var sb = new StringBuilder(); - private static string GetReport(ContentDataIntegrityReport report, string entityType, bool detailed) + if (report.Ok) { - var sb = new StringBuilder(); - - if (report.Ok) - { - sb.AppendLine($"

All {entityType} paths are valid

"); + sb.AppendLine($"

All {entityType} paths are valid

"); - if (!detailed) - { - return sb.ToString(); - } - } - else + if (!detailed) { - sb.AppendLine($"

{report.DetectedIssues.Count} invalid {entityType} paths detected.

"); + return sb.ToString(); } + } + else + { + sb.AppendLine($"

{report.DetectedIssues.Count} invalid {entityType} paths detected.

"); + } - if (detailed && report.DetectedIssues.Count > 0) + if (detailed && report.DetectedIssues.Count > 0) + { + sb.AppendLine("
    "); + foreach (IGrouping> + issueGroup in report.DetectedIssues.GroupBy(x => x.Value.IssueType)) { - sb.AppendLine("
      "); - foreach (IGrouping> issueGroup in report.DetectedIssues.GroupBy(x => x.Value.IssueType)) - { - var countByGroup = issueGroup.Count(); - var fixedByGroup = issueGroup.Count(x => x.Value.Fixed); - sb.AppendLine("
    • "); - sb.AppendLine($"{countByGroup} issues of type {issueGroup.Key} ... {fixedByGroup} fixed"); - sb.AppendLine("
    • "); - } - - sb.AppendLine("
    "); + var countByGroup = issueGroup.Count(); + var fixedByGroup = issueGroup.Count(x => x.Value.Fixed); + sb.AppendLine("
  • "); + sb.AppendLine($"{countByGroup} issues of type {issueGroup.Key} ... {fixedByGroup} fixed"); + sb.AppendLine("
  • "); } - return sb.ToString(); + sb.AppendLine("
"); } - /// - public override HealthCheckStatus ExecuteAction(HealthCheckAction action) + return sb.ToString(); + } + + /// + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) + { + switch (action.Alias) { - switch (action.Alias) - { - case SFixContentPaths: - return CheckDocuments(true); - case SSsFixMediaPaths: - return CheckMedia(true); - default: - throw new InvalidOperationException("Action not supported"); - } + case SFixContentPaths: + return CheckDocuments(true); + case SSsFixMediaPaths: + return CheckMedia(true); + default: + throw new InvalidOperationException("Action not supported"); } } } diff --git a/src/Umbraco.Core/HealthChecks/Checks/LiveEnvironment/CompilationDebugCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/LiveEnvironment/CompilationDebugCheck.cs index d28c3ca8f54b..f849e6e6daed 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/LiveEnvironment/CompilationDebugCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/LiveEnvironment/CompilationDebugCheck.cs @@ -1,59 +1,57 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.HealthChecks.Checks.LiveEnvironment +namespace Umbraco.Cms.Core.HealthChecks.Checks.LiveEnvironment; + +/// +/// Health check for the configuration of debug-flag. +/// +[HealthCheck( + "61214FF3-FC57-4B31-B5CF-1D095C977D6D", + "Debug Compilation Mode", + Description = + "Leaving debug compilation mode enabled can severely slow down a website and take up more memory on the server.", + Group = "Live Environment")] +public class CompilationDebugCheck : AbstractSettingsCheck { + private readonly IOptionsMonitor _hostingSettings; + /// - /// Health check for the configuration of debug-flag. + /// Initializes a new instance of the class. /// - [HealthCheck( - "61214FF3-FC57-4B31-B5CF-1D095C977D6D", - "Debug Compilation Mode", - Description = "Leaving debug compilation mode enabled can severely slow down a website and take up more memory on the server.", - Group = "Live Environment")] - public class CompilationDebugCheck : AbstractSettingsCheck + public CompilationDebugCheck(ILocalizedTextService textService, IOptionsMonitor hostingSettings) + : base(textService) => + _hostingSettings = hostingSettings; + + /// + public override string ItemPath => Constants.Configuration.ConfigHostingDebug; + + /// + public override string ReadMoreLink => + Constants.HealthChecks.DocumentationLinks.LiveEnvironment.CompilationDebugCheck; + + /// + public override ValueComparisonType ValueComparisonType => ValueComparisonType.ShouldEqual; + + /// + public override IEnumerable Values => new List { - private readonly IOptionsMonitor _hostingSettings; - - /// - /// Initializes a new instance of the class. - /// - public CompilationDebugCheck(ILocalizedTextService textService, IOptionsMonitor hostingSettings) - : base(textService) => - _hostingSettings = hostingSettings; - - /// - public override string ItemPath => Constants.Configuration.ConfigHostingDebug; - - /// - public override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.LiveEnvironment.CompilationDebugCheck; - - /// - public override ValueComparisonType ValueComparisonType => ValueComparisonType.ShouldEqual; - - /// - public override IEnumerable Values => new List - { - new AcceptableConfiguration - { - IsRecommended = true, - Value = bool.FalseString.ToLower() - } - }; - - /// - public override string CurrentValue => _hostingSettings.CurrentValue.Debug.ToString(); - - /// - public override string CheckSuccessMessage => LocalizedTextService.Localize("healthcheck","compilationDebugCheckSuccessMessage"); - - /// - public override string CheckErrorMessage => LocalizedTextService.Localize("healthcheck","compilationDebugCheckErrorMessage"); - } + new() {IsRecommended = true, Value = bool.FalseString.ToLower()} + }; + + /// + public override string CurrentValue => _hostingSettings.CurrentValue.Debug.ToString(); + + /// + public override string CheckSuccessMessage => + LocalizedTextService.Localize("healthcheck", "compilationDebugCheckSuccessMessage"); + + /// + public override string CheckErrorMessage => + LocalizedTextService.Localize("healthcheck", "compilationDebugCheckErrorMessage"); } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Permissions/FolderAndFilePermissionsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Permissions/FolderAndFilePermissionsCheck.cs index d10dc8fedd6d..c517db8c4db5 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Permissions/FolderAndFilePermissionsCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Permissions/FolderAndFilePermissionsCheck.cs @@ -1,102 +1,99 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading.Tasks; using Umbraco.Cms.Core.Install; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.HealthChecks.Checks.Permissions +namespace Umbraco.Cms.Core.HealthChecks.Checks.Permissions; + +/// +/// Health check for the folder and file permissions. +/// +[HealthCheck( + "53DBA282-4A79-4B67-B958-B29EC40FCC23", + "Folder & File Permissions", + Description = "Checks that the web server folder and file permissions are set correctly for Umbraco to run.", + Group = "Permissions")] +public class FolderAndFilePermissionsCheck : HealthCheck { + private readonly IFilePermissionHelper _filePermissionHelper; + private readonly ILocalizedTextService _textService; + /// - /// Health check for the folder and file permissions. + /// Initializes a new instance of the class. /// - [HealthCheck( - "53DBA282-4A79-4B67-B958-B29EC40FCC23", - "Folder & File Permissions", - Description = "Checks that the web server folder and file permissions are set correctly for Umbraco to run.", - Group = "Permissions")] - public class FolderAndFilePermissionsCheck : HealthCheck + public FolderAndFilePermissionsCheck( + ILocalizedTextService textService, + IFilePermissionHelper filePermissionHelper) { - private readonly ILocalizedTextService _textService; - private readonly IFilePermissionHelper _filePermissionHelper; + _textService = textService; + _filePermissionHelper = filePermissionHelper; + } - /// - /// Initializes a new instance of the class. - /// - public FolderAndFilePermissionsCheck( - ILocalizedTextService textService, - IFilePermissionHelper filePermissionHelper) - { - _textService = textService; - _filePermissionHelper = filePermissionHelper; - } + /// + /// Get the status for this health check + /// + public override Task> GetStatus() + { + _filePermissionHelper.RunFilePermissionTestSuite( + out Dictionary> errors); - /// - /// Get the status for this health check - /// - public override Task> GetStatus() + return Task.FromResult(errors.Select(x => new HealthCheckStatus(GetMessage(x)) { - _filePermissionHelper.RunFilePermissionTestSuite(out Dictionary> errors); + ResultType = x.Value.Any() ? StatusResultType.Error : StatusResultType.Success, + ReadMoreLink = GetReadMoreLink(x), + Description = GetErrorDescription(x) + })); + } - return Task.FromResult(errors.Select(x => new HealthCheckStatus(GetMessage(x)) - { - ResultType = x.Value.Any() ? StatusResultType.Error : StatusResultType.Success, - ReadMoreLink = GetReadMoreLink(x), - Description = GetErrorDescription(x) - })); + private string? GetErrorDescription(KeyValuePair> status) + { + if (!status.Value.Any()) + { + return null; } - private string? GetErrorDescription(KeyValuePair> status) + var sb = new StringBuilder("The following failed:"); + + sb.AppendLine("
    "); + foreach (var error in status.Value) { - if (!status.Value.Any()) - { - return null; - } + sb.Append("
  • " + error + "
  • "); + } - var sb = new StringBuilder("The following failed:"); + sb.AppendLine("
"); + return sb.ToString(); + } - sb.AppendLine("
    "); - foreach (var error in status.Value) - { - sb.Append("
  • " + error + "
  • "); - } + private string GetMessage(KeyValuePair> status) + => _textService.Localize("permissions", status.Key); - sb.AppendLine("
"); - return sb.ToString(); + private string? GetReadMoreLink(KeyValuePair> status) + { + if (!status.Value.Any()) + { + return null; } - private string GetMessage(KeyValuePair> status) - => _textService.Localize("permissions", status.Key); - - private string? GetReadMoreLink(KeyValuePair> status) + switch (status.Key) { - if (!status.Value.Any()) - { - return null; - } - - switch (status.Key) - { - case FilePermissionTest.FileWriting: - return Constants.HealthChecks.DocumentationLinks.FolderAndFilePermissionsCheck.FileWriting; - case FilePermissionTest.FolderCreation: - return Constants.HealthChecks.DocumentationLinks.FolderAndFilePermissionsCheck.FolderCreation; - case FilePermissionTest.FileWritingForPackages: - return Constants.HealthChecks.DocumentationLinks.FolderAndFilePermissionsCheck.FileWritingForPackages; - case FilePermissionTest.MediaFolderCreation: - return Constants.HealthChecks.DocumentationLinks.FolderAndFilePermissionsCheck.MediaFolderCreation; - default: return null; - } + case FilePermissionTest.FileWriting: + return Constants.HealthChecks.DocumentationLinks.FolderAndFilePermissionsCheck.FileWriting; + case FilePermissionTest.FolderCreation: + return Constants.HealthChecks.DocumentationLinks.FolderAndFilePermissionsCheck.FolderCreation; + case FilePermissionTest.FileWritingForPackages: + return Constants.HealthChecks.DocumentationLinks.FolderAndFilePermissionsCheck.FileWritingForPackages; + case FilePermissionTest.MediaFolderCreation: + return Constants.HealthChecks.DocumentationLinks.FolderAndFilePermissionsCheck.MediaFolderCreation; + default: return null; } - - /// - /// Executes the action and returns it's status - /// - public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => throw new InvalidOperationException("FolderAndFilePermissionsCheck has no executable actions"); } + + /// + /// Executes the action and returns it's status + /// + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => + throw new InvalidOperationException("FolderAndFilePermissionsCheck has no executable actions"); } diff --git a/src/Umbraco.Core/HealthChecks/Checks/ProvidedValueValidation.cs b/src/Umbraco.Core/HealthChecks/Checks/ProvidedValueValidation.cs index d99f05d7388b..2b12d5ebb847 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/ProvidedValueValidation.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/ProvidedValueValidation.cs @@ -1,12 +1,11 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.HealthChecks.Checks +namespace Umbraco.Cms.Core.HealthChecks.Checks; + +public enum ProvidedValueValidation { - public enum ProvidedValueValidation - { - None = 1, - Email = 2, - Regex = 3 - } + None = 1, + Email = 2, + Regex = 3 } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs index daeea79f026c..70736a7f4325 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs @@ -1,150 +1,143 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; using System.Text.RegularExpressions; -using System.Threading.Tasks; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.HealthChecks.Checks.Security +namespace Umbraco.Cms.Core.HealthChecks.Checks.Security; + +/// +/// Provides a base class for health checks of http header values. +/// +public abstract class BaseHttpHeaderCheck : HealthCheck { + private static HttpClient? s_httpClient; + private readonly string _header; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly string _localizedTextPrefix; + private readonly bool _metaTagOptionAvailable; + + [Obsolete("Use ctor without value.")] + protected BaseHttpHeaderCheck( + IHostingEnvironment hostingEnvironment, + ILocalizedTextService textService, + string header, + string value, + string localizedTextPrefix, + bool metaTagOptionAvailable) : this(hostingEnvironment, textService, header, localizedTextPrefix, + metaTagOptionAvailable) + { + } + /// - /// Provides a base class for health checks of http header values. + /// Initializes a new instance of the class. /// - public abstract class BaseHttpHeaderCheck : HealthCheck + protected BaseHttpHeaderCheck( + IHostingEnvironment hostingEnvironment, + ILocalizedTextService textService, + string header, + string localizedTextPrefix, + bool metaTagOptionAvailable) { - private readonly IHostingEnvironment _hostingEnvironment; - private readonly ILocalizedTextService _textService; - private readonly string _header; - private readonly string _localizedTextPrefix; - private readonly bool _metaTagOptionAvailable; - private static HttpClient? s_httpClient; - - [Obsolete("Use ctor without value.")] - protected BaseHttpHeaderCheck( - IHostingEnvironment hostingEnvironment, - ILocalizedTextService textService, - string header, - string value, - string localizedTextPrefix, - bool metaTagOptionAvailable) :this(hostingEnvironment, textService, header, localizedTextPrefix, metaTagOptionAvailable) - { + LocalizedTextService = textService ?? throw new ArgumentNullException(nameof(textService)); + _hostingEnvironment = hostingEnvironment; + _header = header; + _localizedTextPrefix = localizedTextPrefix; + _metaTagOptionAvailable = metaTagOptionAvailable; + } - } + [Obsolete("Save ILocalizedTextService in a field on the super class instead of using this")] + protected ILocalizedTextService LocalizedTextService { get; } - [Obsolete("Save ILocalizedTextService in a field on the super class instead of using this")] - protected ILocalizedTextService LocalizedTextService => _textService; - /// - /// Initializes a new instance of the class. - /// - protected BaseHttpHeaderCheck( - IHostingEnvironment hostingEnvironment, - ILocalizedTextService textService, - string header, - string localizedTextPrefix, - bool metaTagOptionAvailable) - { - _textService = textService ?? throw new ArgumentNullException(nameof(textService)); - _hostingEnvironment = hostingEnvironment; - _header = header; - _localizedTextPrefix = localizedTextPrefix; - _metaTagOptionAvailable = metaTagOptionAvailable; - } + private static HttpClient HttpClient => s_httpClient ??= new HttpClient(); - private static HttpClient HttpClient => s_httpClient ??= new HttpClient(); - - /// - /// Gets a link to an external read more page. - /// - protected abstract string ReadMoreLink { get; } - - /// - /// Get the status for this health check - /// - public override async Task> GetStatus() => - await Task.WhenAll(CheckForHeader()); - - /// - /// Executes the action and returns it's status - /// - public override HealthCheckStatus ExecuteAction(HealthCheckAction action) - => throw new InvalidOperationException("HTTP Header action requested is either not executable or does not exist"); - - /// - /// The actual health check method. - /// - protected async Task CheckForHeader() - { - string message; - var success = false; + /// + /// Gets a link to an external read more page. + /// + protected abstract string ReadMoreLink { get; } - // Access the site home page and check for the click-jack protection header or meta tag - var url = _hostingEnvironment.ApplicationMainUrl?.GetLeftPart(UriPartial.Authority); + /// + /// Get the status for this health check + /// + public override async Task> GetStatus() => + await Task.WhenAll(CheckForHeader()); - try - { - using HttpResponseMessage response = await HttpClient.GetAsync(url); + /// + /// Executes the action and returns it's status + /// + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) + => throw new InvalidOperationException( + "HTTP Header action requested is either not executable or does not exist"); - // Check first for header - success = HasMatchingHeader(response.Headers.Select(x => x.Key)); + /// + /// The actual health check method. + /// + protected async Task CheckForHeader() + { + string message; + var success = false; - // If not found, and available, check for meta-tag - if (success == false && _metaTagOptionAvailable) - { - success = await DoMetaTagsContainKeyForHeader(response); - } + // Access the site home page and check for the click-jack protection header or meta tag + var url = _hostingEnvironment.ApplicationMainUrl?.GetLeftPart(UriPartial.Authority); - message = success - ? _textService.Localize($"healthcheck", $"{_localizedTextPrefix}CheckHeaderFound") - : _textService.Localize($"healthcheck", $"{_localizedTextPrefix}CheckHeaderNotFound"); - } - catch (Exception ex) + try + { + using HttpResponseMessage response = await HttpClient.GetAsync(url); + + // Check first for header + success = HasMatchingHeader(response.Headers.Select(x => x.Key)); + + // If not found, and available, check for meta-tag + if (success == false && _metaTagOptionAvailable) { - message = _textService.Localize("healthcheck","healthCheckInvalidUrl", new[] { url?.ToString(), ex.Message }); + success = await DoMetaTagsContainKeyForHeader(response); } - return - new HealthCheckStatus(message) - { - ResultType = success ? StatusResultType.Success : StatusResultType.Error, - ReadMoreLink = success ? null : ReadMoreLink - }; + message = success + ? LocalizedTextService.Localize("healthcheck", $"{_localizedTextPrefix}CheckHeaderFound") + : LocalizedTextService.Localize("healthcheck", $"{_localizedTextPrefix}CheckHeaderNotFound"); } + catch (Exception ex) + { + message = LocalizedTextService.Localize("healthcheck", "healthCheckInvalidUrl", new[] {url, ex.Message}); + } + + return + new HealthCheckStatus(message) + { + ResultType = success ? StatusResultType.Success : StatusResultType.Error, + ReadMoreLink = success ? null : ReadMoreLink + }; + } - private bool HasMatchingHeader(IEnumerable headerKeys) - => headerKeys.Contains(_header, StringComparer.InvariantCultureIgnoreCase); + private bool HasMatchingHeader(IEnumerable headerKeys) + => headerKeys.Contains(_header, StringComparer.InvariantCultureIgnoreCase); - private async Task DoMetaTagsContainKeyForHeader(HttpResponseMessage response) + private async Task DoMetaTagsContainKeyForHeader(HttpResponseMessage response) + { + using (Stream stream = await response.Content.ReadAsStreamAsync()) { - using (Stream stream = await response.Content.ReadAsStreamAsync()) + if (stream == null) + { + return false; + } + + using (var reader = new StreamReader(stream)) { - if (stream == null) - { - return false; - } - - using (var reader = new StreamReader(stream)) - { - var html = reader.ReadToEnd(); - Dictionary metaTags = ParseMetaTags(html); - return HasMatchingHeader(metaTags.Keys); - } + var html = reader.ReadToEnd(); + Dictionary metaTags = ParseMetaTags(html); + return HasMatchingHeader(metaTags.Keys); } } + } - private static Dictionary ParseMetaTags(string html) - { - var regex = new Regex(" ParseMetaTags(string html) + { + var regex = new Regex("() - .ToDictionary(m => m.Groups[1].Value, m => m.Groups[2].Value); - } + return regex.Matches(html) + .ToDictionary(m => m.Groups[1].Value, m => m.Groups[2].Value); } } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs index 8586989f32a8..04abe70fd79d 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs @@ -4,27 +4,27 @@ using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.HealthChecks.Checks.Security +namespace Umbraco.Cms.Core.HealthChecks.Checks.Security; + +/// +/// Health check for the recommended production setup regarding the X-Frame-Options header. +/// +[HealthCheck( + "ED0D7E40-971E-4BE8-AB6D-8CC5D0A6A5B0", + "Click-Jacking Protection", + Description = + "Checks if your site is allowed to be IFRAMEd by another site and thus would be susceptible to click-jacking.", + Group = "Security")] +public class ClickJackingCheck : BaseHttpHeaderCheck { /// - /// Health check for the recommended production setup regarding the X-Frame-Options header. + /// Initializes a new instance of the class. /// - [HealthCheck( - "ED0D7E40-971E-4BE8-AB6D-8CC5D0A6A5B0", - "Click-Jacking Protection", - Description = "Checks if your site is allowed to be IFRAMEd by another site and thus would be susceptible to click-jacking.", - Group = "Security")] - public class ClickJackingCheck : BaseHttpHeaderCheck + public ClickJackingCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService) + : base(hostingEnvironment, textService, "X-Frame-Options", "clickJacking", true) { - /// - /// Initializes a new instance of the class. - /// - public ClickJackingCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService) - : base(hostingEnvironment, textService, "X-Frame-Options", "clickJacking", true) - { - } - - /// - protected override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.Security.ClickJackingCheck; } + + /// + protected override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.Security.ClickJackingCheck; } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs index 99729286c56c..1f26a88c59c5 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs @@ -1,94 +1,95 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.HealthChecks.Checks.Security +namespace Umbraco.Cms.Core.HealthChecks.Checks.Security; + +/// +/// Health check for the recommended production setup regarding unnecessary headers. +/// +[HealthCheck( + "92ABBAA2-0586-4089-8AE2-9A843439D577", + "Excessive Headers", + Description = + "Checks to see if your site is revealing information in its headers that gives away unnecessary details about the technology used to build and host it.", + Group = "Security")] +public class ExcessiveHeadersCheck : HealthCheck { + private static HttpClient? s_httpClient; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly ILocalizedTextService _textService; + /// - /// Health check for the recommended production setup regarding unnecessary headers. + /// Initializes a new instance of the class. /// - [HealthCheck( - "92ABBAA2-0586-4089-8AE2-9A843439D577", - "Excessive Headers", - Description = "Checks to see if your site is revealing information in its headers that gives away unnecessary details about the technology used to build and host it.", - Group = "Security")] - public class ExcessiveHeadersCheck : HealthCheck + public ExcessiveHeadersCheck(ILocalizedTextService textService, IHostingEnvironment hostingEnvironment) { - private readonly ILocalizedTextService _textService; - private readonly IHostingEnvironment _hostingEnvironment; - private static HttpClient? s_httpClient; + _textService = textService; + _hostingEnvironment = hostingEnvironment; + } - /// - /// Initializes a new instance of the class. - /// - public ExcessiveHeadersCheck(ILocalizedTextService textService, IHostingEnvironment hostingEnvironment) - { - _textService = textService; - _hostingEnvironment = hostingEnvironment; - } + private static HttpClient HttpClient => s_httpClient ??= new HttpClient(); - private static HttpClient HttpClient => s_httpClient ??= new HttpClient(); + /// + /// Get the status for this health check + /// + public override async Task> GetStatus() => + await Task.WhenAll(CheckForHeaders()); - /// - /// Get the status for this health check - /// - public override async Task> GetStatus() => - await Task.WhenAll(CheckForHeaders()); + /// + /// Executes the action and returns it's status + /// + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) + => throw new InvalidOperationException("ExcessiveHeadersCheck has no executable actions"); - /// - /// Executes the action and returns it's status - /// - public override HealthCheckStatus ExecuteAction(HealthCheckAction action) - => throw new InvalidOperationException("ExcessiveHeadersCheck has no executable actions"); + private async Task CheckForHeaders() + { + string message; + var success = false; + var url = _hostingEnvironment.ApplicationMainUrl?.GetLeftPart(UriPartial.Authority); - private async Task CheckForHeaders() + // Access the site home page and check for the headers + var request = new HttpRequestMessage(HttpMethod.Head, url); + try { - string message; - var success = false; - var url = _hostingEnvironment.ApplicationMainUrl?.GetLeftPart(UriPartial.Authority); + using HttpResponseMessage response = await HttpClient.SendAsync(request); - // Access the site home page and check for the headers - var request = new HttpRequestMessage(HttpMethod.Head, url); - try - { - using HttpResponseMessage response = await HttpClient.SendAsync(request); - - IEnumerable allHeaders = response.Headers.Select(x => x.Key); - var headersToCheckFor = new List {"Server", "X-Powered-By", "X-AspNet-Version", "X-AspNetMvc-Version" }; + IEnumerable allHeaders = response.Headers.Select(x => x.Key); + var headersToCheckFor = + new List {"Server", "X-Powered-By", "X-AspNet-Version", "X-AspNetMvc-Version"}; - // Ignore if server header is present and it's set to cloudflare - if (allHeaders.InvariantContains("Server") && response.Headers.TryGetValues("Server", out var serverHeaders) && (serverHeaders.FirstOrDefault()?.InvariantEquals("cloudflare") ?? false)) - { - headersToCheckFor.Remove("Server"); - } - - var headersFound = allHeaders - .Intersect(headersToCheckFor) - .ToArray(); - success = headersFound.Any() == false; - message = success - ? _textService.Localize("healthcheck","excessiveHeadersNotFound") - : _textService.Localize("healthcheck","excessiveHeadersFound", new[] { string.Join(", ", headersFound) }); - } - catch (Exception ex) + // Ignore if server header is present and it's set to cloudflare + if (allHeaders.InvariantContains("Server") && + response.Headers.TryGetValues("Server", out IEnumerable serverHeaders) && + (serverHeaders.FirstOrDefault()?.InvariantEquals("cloudflare") ?? false)) { - message = _textService.Localize("healthcheck","healthCheckInvalidUrl", new[] { url?.ToString(), ex.Message }); + headersToCheckFor.Remove("Server"); } - return - new HealthCheckStatus(message) - { - ResultType = success ? StatusResultType.Success : StatusResultType.Warning, - ReadMoreLink = success ? null : Constants.HealthChecks.DocumentationLinks.Security.ExcessiveHeadersCheck, - }; + var headersFound = allHeaders + .Intersect(headersToCheckFor) + .ToArray(); + success = headersFound.Any() == false; + message = success + ? _textService.Localize("healthcheck", "excessiveHeadersNotFound") + : _textService.Localize("healthcheck", "excessiveHeadersFound", + new[] {string.Join(", ", headersFound)}); } - } + catch (Exception ex) + { + message = _textService.Localize("healthcheck", "healthCheckInvalidUrl", new[] {url, ex.Message}); + } + + return + new HealthCheckStatus(message) + { + ResultType = success ? StatusResultType.Success : StatusResultType.Warning, + ReadMoreLink = success + ? null + : Constants.HealthChecks.DocumentationLinks.Security.ExcessiveHeadersCheck + }; + } } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/HstsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/HstsCheck.cs index 7902f4e3f873..229999472e1a 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/HstsCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/HstsCheck.cs @@ -4,34 +4,33 @@ using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.HealthChecks.Checks.Security +namespace Umbraco.Cms.Core.HealthChecks.Checks.Security; + +/// +/// Health check for the recommended production setup regarding the Strict-Transport-Security header. +/// +[HealthCheck( + "E2048C48-21C5-4BE1-A80B-8062162DF124", + "Cookie hijacking and protocol downgrade attacks Protection (Strict-Transport-Security Header (HSTS))", + Description = "Checks if your site, when running with HTTPS, contains the Strict-Transport-Security Header (HSTS).", + Group = "Security")] +public class HstsCheck : BaseHttpHeaderCheck { /// - /// Health check for the recommended production setup regarding the Strict-Transport-Security header. + /// Initializes a new instance of the class. /// - [HealthCheck( - "E2048C48-21C5-4BE1-A80B-8062162DF124", - "Cookie hijacking and protocol downgrade attacks Protection (Strict-Transport-Security Header (HSTS))", - Description = "Checks if your site, when running with HTTPS, contains the Strict-Transport-Security Header (HSTS).", - Group = "Security")] - public class HstsCheck : BaseHttpHeaderCheck + /// + /// The check is mostly based on the instructions in the OWASP CheatSheet + /// (https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/HTTP_Strict_Transport_Security_Cheat_Sheet.md) + /// and the blog post of Troy Hunt (https://www.troyhunt.com/understanding-http-strict-transport/) + /// If you want do to it perfectly, you have to submit it https://hstspreload.org/, + /// but then you should include subdomains and I wouldn't suggest to do that for Umbraco-sites. + /// + public HstsCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService) + : base(hostingEnvironment, textService, "Strict-Transport-Security", "hSTS", true) { - /// - /// Initializes a new instance of the class. - /// - /// - /// The check is mostly based on the instructions in the OWASP CheatSheet - /// (https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/HTTP_Strict_Transport_Security_Cheat_Sheet.md) - /// and the blog post of Troy Hunt (https://www.troyhunt.com/understanding-http-strict-transport/) - /// If you want do to it perfectly, you have to submit it https://hstspreload.org/, - /// but then you should include subdomains and I wouldn't suggest to do that for Umbraco-sites. - /// - public HstsCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService) - : base(hostingEnvironment, textService, "Strict-Transport-Security", "hSTS", true) - { - } - - /// - protected override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.Security.HstsCheck; } + + /// + protected override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.Security.HstsCheck; } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs index 0b58ca4b4060..7f1cdb85db1a 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs @@ -1,193 +1,199 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using System.Net; -using System.Net.Http; using System.Net.Security; using System.Security.Cryptography.X509Certificates; -using System.Threading.Tasks; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.HealthChecks.Checks.Security +namespace Umbraco.Cms.Core.HealthChecks.Checks.Security; + +/// +/// Health checks for the recommended production setup regarding HTTPS. +/// +[HealthCheck( + "EB66BB3B-1BCD-4314-9531-9DA2C1D6D9A7", + "HTTPS Configuration", + Description = + "Checks if your site is configured to work over HTTPS and if the Umbraco related configuration for that is correct.", + Group = "Security")] +public class HttpsCheck : HealthCheck { - /// - /// Health checks for the recommended production setup regarding HTTPS. - /// - [HealthCheck( - "EB66BB3B-1BCD-4314-9531-9DA2C1D6D9A7", - "HTTPS Configuration", - Description = "Checks if your site is configured to work over HTTPS and if the Umbraco related configuration for that is correct.", - Group = "Security")] - public class HttpsCheck : HealthCheck - { - private const int NumberOfDaysForExpiryWarning = 14; - private const string HttpPropertyKeyCertificateDaysToExpiry = "CertificateDaysToExpiry"; + private const int NumberOfDaysForExpiryWarning = 14; + private const string HttpPropertyKeyCertificateDaysToExpiry = "CertificateDaysToExpiry"; - private readonly ILocalizedTextService _textService; - private readonly IOptionsMonitor _globalSettings; - private readonly IHostingEnvironment _hostingEnvironment; + private static HttpClient? s_httpClient; + private readonly IOptionsMonitor _globalSettings; + private readonly IHostingEnvironment _hostingEnvironment; - private static HttpClient? s_httpClient; + private readonly ILocalizedTextService _textService; - private static HttpClient HttpClient => s_httpClient ??= new HttpClient(new HttpClientHandler() - { - ServerCertificateCustomValidationCallback = ServerCertificateCustomValidation - }); + /// + /// Initializes a new instance of the class. + /// + /// The text service. + /// The global settings. + /// The hosting environment. + public HttpsCheck( + ILocalizedTextService textService, + IOptionsMonitor globalSettings, + IHostingEnvironment hostingEnvironment) + { + _textService = textService; + _globalSettings = globalSettings; + _hostingEnvironment = hostingEnvironment; + } - /// - /// Initializes a new instance of the class. - /// - /// The text service. - /// The global settings. - /// The hosting environment. - public HttpsCheck( - ILocalizedTextService textService, - IOptionsMonitor globalSettings, - IHostingEnvironment hostingEnvironment) + private static HttpClient HttpClient => s_httpClient ??= new HttpClient(new HttpClientHandler + { + ServerCertificateCustomValidationCallback = ServerCertificateCustomValidation + }); + + /// + public override async Task> GetStatus() => + await Task.WhenAll( + CheckIfCurrentSchemeIsHttps(), + CheckHttpsConfigurationSetting(), + CheckForValidCertificate()); + + /// + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) + => throw new InvalidOperationException( + "HttpsCheck action requested is either not executable or does not exist"); + + private static bool ServerCertificateCustomValidation(HttpRequestMessage requestMessage, + X509Certificate2? certificate, X509Chain? chain, SslPolicyErrors sslErrors) + { + if (certificate is not null) { - _textService = textService; - _globalSettings = globalSettings; - _hostingEnvironment = hostingEnvironment; + requestMessage.Properties[HttpPropertyKeyCertificateDaysToExpiry] = + (int)Math.Floor((certificate.NotAfter - DateTime.Now).TotalDays); } - /// - public override async Task> GetStatus() => - await Task.WhenAll( - CheckIfCurrentSchemeIsHttps(), - CheckHttpsConfigurationSetting(), - CheckForValidCertificate()); + return sslErrors == SslPolicyErrors.None; + } - /// - public override HealthCheckStatus ExecuteAction(HealthCheckAction action) - => throw new InvalidOperationException("HttpsCheck action requested is either not executable or does not exist"); + private async Task CheckForValidCertificate() + { + string message; + StatusResultType result; - private static bool ServerCertificateCustomValidation(HttpRequestMessage requestMessage, X509Certificate2? certificate, X509Chain? chain, SslPolicyErrors sslErrors) - { - if (certificate is not null) - { - requestMessage.Properties[HttpPropertyKeyCertificateDaysToExpiry] = (int)Math.Floor((certificate.NotAfter - DateTime.Now).TotalDays); - } + // Attempt to access the site over HTTPS to see if it HTTPS is supported and a valid certificate has been configured + var urlBuilder = new UriBuilder(_hostingEnvironment.ApplicationMainUrl) {Scheme = Uri.UriSchemeHttps}; + Uri url = urlBuilder.Uri; - return sslErrors == SslPolicyErrors.None; - } + var request = new HttpRequestMessage(HttpMethod.Head, url); - private async Task CheckForValidCertificate() + try { - string message; - StatusResultType result; - - // Attempt to access the site over HTTPS to see if it HTTPS is supported and a valid certificate has been configured - var urlBuilder = new UriBuilder(_hostingEnvironment.ApplicationMainUrl) - { - Scheme = Uri.UriSchemeHttps - }; - var url = urlBuilder.Uri; - - var request = new HttpRequestMessage(HttpMethod.Head, url); + using HttpResponseMessage response = await HttpClient.SendAsync(request); - try + if (response.StatusCode == HttpStatusCode.OK) { - using HttpResponseMessage response = await HttpClient.SendAsync(request); - - if (response.StatusCode == HttpStatusCode.OK) + // Got a valid response, check now if the certificate is expiring within the specified amount of days + int? daysToExpiry = 0; + if (request.Properties.TryGetValue(HttpPropertyKeyCertificateDaysToExpiry, + out var certificateDaysToExpiry)) { - // Got a valid response, check now if the certificate is expiring within the specified amount of days - int? daysToExpiry = 0; - if (request.Properties.TryGetValue(HttpPropertyKeyCertificateDaysToExpiry, out var certificateDaysToExpiry)) - { - daysToExpiry = (int?)certificateDaysToExpiry; - } - - if (daysToExpiry <= 0) - { - result = StatusResultType.Error; - message = _textService.Localize("healthcheck","httpsCheckExpiredCertificate"); - } - else if (daysToExpiry < NumberOfDaysForExpiryWarning) - { - result = StatusResultType.Warning; - message = _textService.Localize("healthcheck","httpsCheckExpiringCertificate", new[] { daysToExpiry.ToString() }); - } - else - { - result = StatusResultType.Success; - message = _textService.Localize("healthcheck","httpsCheckValidCertificate"); - } + daysToExpiry = (int?)certificateDaysToExpiry; } - else + + if (daysToExpiry <= 0) { result = StatusResultType.Error; - message = _textService.Localize("healthcheck","healthCheckInvalidUrl", new[] { url.AbsoluteUri, response.ReasonPhrase }); + message = _textService.Localize("healthcheck", "httpsCheckExpiredCertificate"); } - } - catch (Exception ex) - { - if (ex is WebException exception) + else if (daysToExpiry < NumberOfDaysForExpiryWarning) { - message = exception.Status == WebExceptionStatus.TrustFailure - ? _textService.Localize("healthcheck", "httpsCheckInvalidCertificate", new[] { exception.Message }) - : _textService.Localize("healthcheck", "healthCheckInvalidUrl", new[] { url.AbsoluteUri, exception.Message }); + result = StatusResultType.Warning; + message = _textService.Localize("healthcheck", "httpsCheckExpiringCertificate", + new[] {daysToExpiry.ToString()}); } else { - message = _textService.Localize("healthcheck", "healthCheckInvalidUrl", new[] { url.AbsoluteUri, ex.Message }); + result = StatusResultType.Success; + message = _textService.Localize("healthcheck", "httpsCheckValidCertificate"); } - - result = StatusResultType.Error; } - - return new HealthCheckStatus(message) - { - ResultType = result, - ReadMoreLink = result == StatusResultType.Success - ? null - : Constants.HealthChecks.DocumentationLinks.Security.HttpsCheck.CheckIfCurrentSchemeIsHttps - }; - } - - private Task CheckIfCurrentSchemeIsHttps() - { - Uri uri = _hostingEnvironment.ApplicationMainUrl; - var success = uri.Scheme == Uri.UriSchemeHttps; - - return Task.FromResult(new HealthCheckStatus(_textService.Localize("healthcheck","httpsCheckIsCurrentSchemeHttps", new[] { success ? string.Empty : "not" })) + else { - ResultType = success ? StatusResultType.Success : StatusResultType.Error, - ReadMoreLink = success ? null : Constants.HealthChecks.DocumentationLinks.Security.HttpsCheck.CheckIfCurrentSchemeIsHttps - }); + result = StatusResultType.Error; + message = _textService.Localize("healthcheck", "healthCheckInvalidUrl", + new[] {url.AbsoluteUri, response.ReasonPhrase}); + } } - - private Task CheckHttpsConfigurationSetting() + catch (Exception ex) { - bool httpsSettingEnabled = _globalSettings.CurrentValue.UseHttps; - Uri uri = _hostingEnvironment.ApplicationMainUrl; - - string resultMessage; - StatusResultType resultType; - if (uri.Scheme != Uri.UriSchemeHttps) + if (ex is WebException exception) { - resultMessage = _textService.Localize("healthcheck","httpsCheckConfigurationRectifyNotPossible"); - resultType = StatusResultType.Info; + message = exception.Status == WebExceptionStatus.TrustFailure + ? _textService.Localize("healthcheck", "httpsCheckInvalidCertificate", new[] {exception.Message}) + : _textService.Localize("healthcheck", "healthCheckInvalidUrl", + new[] {url.AbsoluteUri, exception.Message}); } else { - resultMessage = _textService.Localize("healthcheck","httpsCheckConfigurationCheckResult", new[] { httpsSettingEnabled.ToString(), httpsSettingEnabled ? string.Empty : "not" }); - resultType = httpsSettingEnabled ? StatusResultType.Success : StatusResultType.Error; + message = _textService.Localize("healthcheck", "healthCheckInvalidUrl", + new[] {url.AbsoluteUri, ex.Message}); } - return Task.FromResult(new HealthCheckStatus(resultMessage) + result = StatusResultType.Error; + } + + return new HealthCheckStatus(message) + { + ResultType = result, + ReadMoreLink = result == StatusResultType.Success + ? null + : Constants.HealthChecks.DocumentationLinks.Security.HttpsCheck.CheckIfCurrentSchemeIsHttps + }; + } + + private Task CheckIfCurrentSchemeIsHttps() + { + Uri uri = _hostingEnvironment.ApplicationMainUrl; + var success = uri.Scheme == Uri.UriSchemeHttps; + + return Task.FromResult( + new HealthCheckStatus(_textService.Localize("healthcheck", "httpsCheckIsCurrentSchemeHttps", + new[] {success ? string.Empty : "not"})) { - ResultType = resultType, - ReadMoreLink = resultType == StatusResultType.Success + ResultType = success ? StatusResultType.Success : StatusResultType.Error, + ReadMoreLink = success ? null - : Constants.HealthChecks.DocumentationLinks.Security.HttpsCheck.CheckHttpsConfigurationSetting + : Constants.HealthChecks.DocumentationLinks.Security.HttpsCheck.CheckIfCurrentSchemeIsHttps }); + } + + private Task CheckHttpsConfigurationSetting() + { + var httpsSettingEnabled = _globalSettings.CurrentValue.UseHttps; + Uri uri = _hostingEnvironment.ApplicationMainUrl; + + string resultMessage; + StatusResultType resultType; + if (uri.Scheme != Uri.UriSchemeHttps) + { + resultMessage = _textService.Localize("healthcheck", "httpsCheckConfigurationRectifyNotPossible"); + resultType = StatusResultType.Info; + } + else + { + resultMessage = _textService.Localize("healthcheck", "httpsCheckConfigurationCheckResult", + new[] {httpsSettingEnabled.ToString(), httpsSettingEnabled ? string.Empty : "not"}); + resultType = httpsSettingEnabled ? StatusResultType.Success : StatusResultType.Error; } + + return Task.FromResult(new HealthCheckStatus(resultMessage) + { + ResultType = resultType, + ReadMoreLink = resultType == StatusResultType.Success + ? null + : Constants.HealthChecks.DocumentationLinks.Security.HttpsCheck.CheckHttpsConfigurationSetting + }); } } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/NoSniffCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/NoSniffCheck.cs index 78ee2c0e124f..b36201d5aa7d 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/NoSniffCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/NoSniffCheck.cs @@ -4,27 +4,26 @@ using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.HealthChecks.Checks.Security +namespace Umbraco.Cms.Core.HealthChecks.Checks.Security; + +/// +/// Health check for the recommended production setup regarding the X-Content-Type-Options header. +/// +[HealthCheck( + "1CF27DB3-EFC0-41D7-A1BB-EA912064E071", + "Content/MIME Sniffing Protection", + Description = "Checks that your site contains a header used to protect against MIME sniffing vulnerabilities.", + Group = "Security")] +public class NoSniffCheck : BaseHttpHeaderCheck { /// - /// Health check for the recommended production setup regarding the X-Content-Type-Options header. + /// Initializes a new instance of the class. /// - [HealthCheck( - "1CF27DB3-EFC0-41D7-A1BB-EA912064E071", - "Content/MIME Sniffing Protection", - Description = "Checks that your site contains a header used to protect against MIME sniffing vulnerabilities.", - Group = "Security")] - public class NoSniffCheck : BaseHttpHeaderCheck + public NoSniffCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService) + : base(hostingEnvironment, textService, "X-Content-Type-Options", "noSniff", false) { - /// - /// Initializes a new instance of the class. - /// - public NoSniffCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService) - : base(hostingEnvironment, textService, "X-Content-Type-Options", "noSniff", false) - { - } - - /// - protected override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.Security.NoSniffCheck; } + + /// + protected override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.Security.NoSniffCheck; } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/UmbracoApplicationUrlCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/UmbracoApplicationUrlCheck.cs index 44b10ba0e3c2..bd90e57647a5 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/UmbracoApplicationUrlCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/UmbracoApplicationUrlCheck.cs @@ -1,68 +1,68 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; -using System.Threading.Tasks; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.HealthChecks.Checks.Security -{ - [HealthCheck( - "6708CA45-E96E-40B8-A40A-0607C1CA7F28", - "Application URL Configuration", - Description = "Checks if the Umbraco application URL is configured for your site.", - Group = "Security")] - public class UmbracoApplicationUrlCheck : HealthCheck - { - private readonly ILocalizedTextService _textService; - private readonly IOptionsMonitor _webRoutingSettings; +namespace Umbraco.Cms.Core.HealthChecks.Checks.Security; - public UmbracoApplicationUrlCheck(ILocalizedTextService textService, IOptionsMonitor webRoutingSettings) - { - _textService = textService; - _webRoutingSettings = webRoutingSettings; - } +[HealthCheck( + "6708CA45-E96E-40B8-A40A-0607C1CA7F28", + "Application URL Configuration", + Description = "Checks if the Umbraco application URL is configured for your site.", + Group = "Security")] +public class UmbracoApplicationUrlCheck : HealthCheck +{ + private readonly ILocalizedTextService _textService; + private readonly IOptionsMonitor _webRoutingSettings; - /// - /// Executes the action and returns its status - /// - public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => throw new InvalidOperationException("UmbracoApplicationUrlCheck has no executable actions"); + public UmbracoApplicationUrlCheck(ILocalizedTextService textService, + IOptionsMonitor webRoutingSettings) + { + _textService = textService; + _webRoutingSettings = webRoutingSettings; + } - /// - /// Get the status for this health check - /// - public override Task> GetStatus() => - Task.FromResult(CheckUmbracoApplicationUrl().Yield()); + /// + /// Executes the action and returns its status + /// + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => + throw new InvalidOperationException("UmbracoApplicationUrlCheck has no executable actions"); - private HealthCheckStatus CheckUmbracoApplicationUrl() - { - var url = _webRoutingSettings.CurrentValue.UmbracoApplicationUrl; + /// + /// Get the status for this health check + /// + public override Task> GetStatus() => + Task.FromResult(CheckUmbracoApplicationUrl().Yield()); - string resultMessage; - StatusResultType resultType; - var success = false; + private HealthCheckStatus CheckUmbracoApplicationUrl() + { + var url = _webRoutingSettings.CurrentValue.UmbracoApplicationUrl; - if (url.IsNullOrWhiteSpace()) - { - resultMessage = _textService.Localize("healthcheck", "umbracoApplicationUrlCheckResultFalse"); - resultType = StatusResultType.Warning; - } - else - { - resultMessage = _textService.Localize("healthcheck", "umbracoApplicationUrlCheckResultTrue", new[] { url }); - resultType = StatusResultType.Success; - success = true; - } + string resultMessage; + StatusResultType resultType; + var success = false; - return new HealthCheckStatus(resultMessage) - { - ResultType = resultType, - ReadMoreLink = success ? null : Constants.HealthChecks.DocumentationLinks.Security.UmbracoApplicationUrlCheck - }; + if (url.IsNullOrWhiteSpace()) + { + resultMessage = _textService.Localize("healthcheck", "umbracoApplicationUrlCheckResultFalse"); + resultType = StatusResultType.Warning; } + else + { + resultMessage = _textService.Localize("healthcheck", "umbracoApplicationUrlCheckResultTrue", new[] {url}); + resultType = StatusResultType.Success; + success = true; + } + + return new HealthCheckStatus(resultMessage) + { + ResultType = resultType, + ReadMoreLink = success + ? null + : Constants.HealthChecks.DocumentationLinks.Security.UmbracoApplicationUrlCheck + }; } } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/XssProtectionCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/XssProtectionCheck.cs index 570ca8002d74..6dd1841d415d 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/XssProtectionCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/XssProtectionCheck.cs @@ -4,34 +4,34 @@ using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.HealthChecks.Checks.Security +namespace Umbraco.Cms.Core.HealthChecks.Checks.Security; + +/// +/// Health check for the recommended production setup regarding the X-XSS-Protection header. +/// +[HealthCheck( + "F4D2B02E-28C5-4999-8463-05759FA15C3A", + "Cross-site scripting Protection (X-XSS-Protection header)", + Description = + "This header enables the Cross-site scripting (XSS) filter in your browser. It checks for the presence of the X-XSS-Protection-header.", + Group = "Security")] +public class XssProtectionCheck : BaseHttpHeaderCheck { /// - /// Health check for the recommended production setup regarding the X-XSS-Protection header. + /// Initializes a new instance of the class. /// - [HealthCheck( - "F4D2B02E-28C5-4999-8463-05759FA15C3A", - "Cross-site scripting Protection (X-XSS-Protection header)", - Description = "This header enables the Cross-site scripting (XSS) filter in your browser. It checks for the presence of the X-XSS-Protection-header.", - Group = "Security")] - public class XssProtectionCheck : BaseHttpHeaderCheck + /// + /// The check is mostly based on the instructions in the OWASP CheatSheet + /// (https://www.owasp.org/index.php/HTTP_Strict_Transport_Security_Cheat_Sheet) + /// and the blog post of Troy Hunt (https://www.troyhunt.com/understanding-http-strict-transport/) + /// If you want do to it perfectly, you have to submit it https://hstspreload.appspot.com/, + /// but then you should include subdomains and I wouldn't suggest to do that for Umbraco-sites. + /// + public XssProtectionCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService) + : base(hostingEnvironment, textService, "X-XSS-Protection", "xssProtection", true) { - /// - /// Initializes a new instance of the class. - /// - /// - /// The check is mostly based on the instructions in the OWASP CheatSheet - /// (https://www.owasp.org/index.php/HTTP_Strict_Transport_Security_Cheat_Sheet) - /// and the blog post of Troy Hunt (https://www.troyhunt.com/understanding-http-strict-transport/) - /// If you want do to it perfectly, you have to submit it https://hstspreload.appspot.com/, - /// but then you should include subdomains and I wouldn't suggest to do that for Umbraco-sites. - /// - public XssProtectionCheck(IHostingEnvironment hostingEnvironment, ILocalizedTextService textService) - : base(hostingEnvironment, textService, "X-XSS-Protection", "xssProtection", true) - { - } - - /// - protected override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.Security.XssProtectionCheck; } + + /// + protected override string ReadMoreLink => Constants.HealthChecks.DocumentationLinks.Security.XssProtectionCheck; } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Services/SmtpCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Services/SmtpCheck.cs index 302a5829f6ea..17d1f7bc833e 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Services/SmtpCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Services/SmtpCheck.cs @@ -1,112 +1,107 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; -using System.IO; using System.Net.Sockets; -using System.Threading.Tasks; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.HealthChecks.Checks.Services +namespace Umbraco.Cms.Core.HealthChecks.Checks.Services; + +/// +/// Health check for the recommended setup regarding SMTP. +/// +[HealthCheck( + "1B5D221B-CE99-4193-97CB-5F3261EC73DF", + "SMTP Settings", + Description = "Checks that valid settings for sending emails are in place.", + Group = "Services")] +public class SmtpCheck : HealthCheck { + private readonly IOptionsMonitor _globalSettings; + private readonly ILocalizedTextService _textService; + /// - /// Health check for the recommended setup regarding SMTP. + /// Initializes a new instance of the class. /// - [HealthCheck( - "1B5D221B-CE99-4193-97CB-5F3261EC73DF", - "SMTP Settings", - Description = "Checks that valid settings for sending emails are in place.", - Group = "Services")] - public class SmtpCheck : HealthCheck + public SmtpCheck(ILocalizedTextService textService, IOptionsMonitor globalSettings) { - private readonly ILocalizedTextService _textService; - private readonly IOptionsMonitor _globalSettings; - - /// - /// Initializes a new instance of the class. - /// - public SmtpCheck(ILocalizedTextService textService, IOptionsMonitor globalSettings) - { - _textService = textService; - _globalSettings = globalSettings; - } + _textService = textService; + _globalSettings = globalSettings; + } - /// - /// Get the status for this health check - /// - public override Task> GetStatus() => - Task.FromResult(CheckSmtpSettings().Yield()); + /// + /// Get the status for this health check + /// + public override Task> GetStatus() => + Task.FromResult(CheckSmtpSettings().Yield()); - /// - /// Executes the action and returns it's status - /// - public override HealthCheckStatus ExecuteAction(HealthCheckAction action) - => throw new InvalidOperationException("SmtpCheck has no executable actions"); + /// + /// Executes the action and returns it's status + /// + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) + => throw new InvalidOperationException("SmtpCheck has no executable actions"); - private HealthCheckStatus CheckSmtpSettings() - { - var success = false; + private HealthCheckStatus CheckSmtpSettings() + { + var success = false; - SmtpSettings? smtpSettings = _globalSettings.CurrentValue.Smtp; + SmtpSettings? smtpSettings = _globalSettings.CurrentValue.Smtp; - string message; - if (smtpSettings == null) + string message; + if (smtpSettings == null) + { + message = _textService.Localize("healthcheck", "smtpMailSettingsNotFound"); + } + else + { + if (string.IsNullOrEmpty(smtpSettings.Host)) { - message = _textService.Localize("healthcheck", "smtpMailSettingsNotFound"); + message = _textService.Localize("healthcheck", "smtpMailSettingsHostNotConfigured"); } else { - if (string.IsNullOrEmpty(smtpSettings.Host)) - { - message = _textService.Localize("healthcheck", "smtpMailSettingsHostNotConfigured"); - } - else - { - success = CanMakeSmtpConnection(smtpSettings.Host, smtpSettings.Port); - message = success - ? _textService.Localize("healthcheck", "smtpMailSettingsConnectionSuccess") - : _textService.Localize( - "healthcheck", "smtpMailSettingsConnectionFail", - new[] { smtpSettings.Host, smtpSettings.Port.ToString() }); - } + success = CanMakeSmtpConnection(smtpSettings.Host, smtpSettings.Port); + message = success + ? _textService.Localize("healthcheck", "smtpMailSettingsConnectionSuccess") + : _textService.Localize( + "healthcheck", "smtpMailSettingsConnectionFail", + new[] {smtpSettings.Host, smtpSettings.Port.ToString()}); } - - return - new HealthCheckStatus(message) - { - ResultType = success ? StatusResultType.Success : StatusResultType.Error, - ReadMoreLink = success ? null : Constants.HealthChecks.DocumentationLinks.SmtpCheck - }; } - private static bool CanMakeSmtpConnection(string host, int port) + return + new HealthCheckStatus(message) + { + ResultType = success ? StatusResultType.Success : StatusResultType.Error, + ReadMoreLink = success ? null : Constants.HealthChecks.DocumentationLinks.SmtpCheck + }; + } + + private static bool CanMakeSmtpConnection(string host, int port) + { + try { - try + using (var client = new TcpClient()) { - using (var client = new TcpClient()) + client.Connect(host, port); + using (NetworkStream stream = client.GetStream()) { - client.Connect(host, port); - using (NetworkStream stream = client.GetStream()) + using (var writer = new StreamWriter(stream)) + using (var reader = new StreamReader(stream)) { - using (var writer = new StreamWriter(stream)) - using (var reader = new StreamReader(stream)) - { - writer.WriteLine("EHLO " + host); - writer.Flush(); - reader.ReadLine(); - return true; - } + writer.WriteLine("EHLO " + host); + writer.Flush(); + reader.ReadLine(); + return true; } } } - catch - { - return false; - } + } + catch + { + return false; } } } diff --git a/src/Umbraco.Core/HealthChecks/ConfigurationServiceResult.cs b/src/Umbraco.Core/HealthChecks/ConfigurationServiceResult.cs index a5d3ae82dac2..e2c9df2eeb63 100644 --- a/src/Umbraco.Core/HealthChecks/ConfigurationServiceResult.cs +++ b/src/Umbraco.Core/HealthChecks/ConfigurationServiceResult.cs @@ -1,8 +1,7 @@ -namespace Umbraco.Cms.Core.HealthChecks +namespace Umbraco.Cms.Core.HealthChecks; + +public class ConfigurationServiceResult { - public class ConfigurationServiceResult - { - public bool Success { get; set; } - public string? Result { get; set; } - } + public bool Success { get; set; } + public string? Result { get; set; } } diff --git a/src/Umbraco.Core/HealthChecks/HealthCheck.cs b/src/Umbraco.Core/HealthChecks/HealthCheck.cs index 59d6f912fad7..ad1382825777 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheck.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheck.cs @@ -1,60 +1,53 @@ -using System; -using System.Collections.Generic; using System.Runtime.Serialization; -using System.Threading.Tasks; using Umbraco.Cms.Core.Composing; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.HealthChecks +namespace Umbraco.Cms.Core.HealthChecks; + +/// +/// Provides a base class for health checks, filling in the healthcheck metadata on construction +/// +[DataContract(Name = "healthCheck", Namespace = "")] +public abstract class HealthCheck : IDiscoverable { - /// - /// Provides a base class for health checks, filling in the healthcheck metadata on construction - /// - [DataContract(Name = "healthCheck", Namespace = "")] - public abstract class HealthCheck : IDiscoverable + protected HealthCheck() { - protected HealthCheck() + Type thisType = GetType(); + HealthCheckAttribute meta = thisType.GetCustomAttribute(false); + if (meta == null) { - var thisType = GetType(); - var meta = thisType.GetCustomAttribute(false); - if (meta == null) - { - throw new InvalidOperationException($"The health check {thisType} requires a {typeof(HealthCheckAttribute)}"); - } - - Name = meta.Name; - Description = meta.Description; - Group = meta.Group; - Id = meta.Id; + throw new InvalidOperationException( + $"The health check {thisType} requires a {typeof(HealthCheckAttribute)}"); } - [DataMember(Name = "id")] - public Guid Id { get; private set; } - - [DataMember(Name = "name")] - public string Name { get; private set; } - - [DataMember(Name = "description")] - public string? Description { get; private set; } - - [DataMember(Name = "group")] - public string? Group { get; private set; } - - /// - /// Get the status for this health check - /// - /// - /// - /// If there are possible actions to take to rectify this check, this method must be overridden by a sub class - /// in order to explicitly provide those actions. - /// - public abstract Task> GetStatus(); - - /// - /// Executes the action and returns it's status - /// - /// - /// - public abstract HealthCheckStatus ExecuteAction(HealthCheckAction action); + Name = meta.Name; + Description = meta.Description; + Group = meta.Group; + Id = meta.Id; } + + [DataMember(Name = "id")] public Guid Id { get; private set; } + + [DataMember(Name = "name")] public string Name { get; private set; } + + [DataMember(Name = "description")] public string? Description { get; private set; } + + [DataMember(Name = "group")] public string? Group { get; private set; } + + /// + /// Get the status for this health check + /// + /// + /// + /// If there are possible actions to take to rectify this check, this method must be overridden by a sub class + /// in order to explicitly provide those actions. + /// + public abstract Task> GetStatus(); + + /// + /// Executes the action and returns it's status + /// + /// + /// + public abstract HealthCheckStatus ExecuteAction(HealthCheckAction action); } diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckAction.cs b/src/Umbraco.Core/HealthChecks/HealthCheckAction.cs index 06bc05f44afe..5fa189d42e33 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckAction.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckAction.cs @@ -1,89 +1,86 @@ -using System; -using System.Collections.Generic; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.HealthChecks +namespace Umbraco.Cms.Core.HealthChecks; + +[DataContract(Name = "healthCheckAction", Namespace = "")] +public class HealthCheckAction { - [DataContract(Name = "healthCheckAction", Namespace = "")] - public class HealthCheckAction - { - /// - /// Empty ctor used for serialization - /// - public HealthCheckAction() { } + /// + /// The name of the action - this is used to name the fix button + /// + [DataMember(Name = "name")] private string? _name; - /// - /// Default ctor - /// - /// - /// - public HealthCheckAction(string alias, Guid healthCheckId) - { - Alias = alias; - HealthCheckId = healthCheckId; - } + /// + /// Empty ctor used for serialization + /// + public HealthCheckAction() { } + + /// + /// Default ctor + /// + /// + /// + public HealthCheckAction(string alias, Guid healthCheckId) + { + Alias = alias; + HealthCheckId = healthCheckId; + } - /// - /// The alias of the action - this is used by the Health Check instance to execute the action - /// - [DataMember(Name = "alias")] - public string? Alias { get; set; } + /// + /// The alias of the action - this is used by the Health Check instance to execute the action + /// + [DataMember(Name = "alias")] + public string? Alias { get; set; } - /// - /// The Id of the Health Check instance - /// - /// - /// This is used to find the Health Check instance to execute this action - /// - [DataMember(Name = "healthCheckId")] - public Guid? HealthCheckId { get; set; } + /// + /// The Id of the Health Check instance + /// + /// + /// This is used to find the Health Check instance to execute this action + /// + [DataMember(Name = "healthCheckId")] + public Guid? HealthCheckId { get; set; } - /// - /// This could be used if the status has a custom view that specifies some parameters to be sent to the server - /// when an action needs to be executed - /// - [DataMember(Name = "actionParameters")] - public Dictionary? ActionParameters { get; set; } + /// + /// This could be used if the status has a custom view that specifies some parameters to be sent to the server + /// when an action needs to be executed + /// + [DataMember(Name = "actionParameters")] + public Dictionary? ActionParameters { get; set; } - /// - /// The name of the action - this is used to name the fix button - /// - [DataMember(Name = "name")] - private string? _name; - public string? Name - { - get { return _name; } - set { _name = value; } - } + public string? Name + { + get => _name; + set => _name = value; + } - /// - /// The description of the action - this is used to give a description before executing the action - /// - [DataMember(Name = "description")] - public string? Description { get; set; } + /// + /// The description of the action - this is used to give a description before executing the action + /// + [DataMember(Name = "description")] + public string? Description { get; set; } - /// - /// Indicates if a value is required to rectify the issue - /// - [DataMember(Name = "valueRequired")] - public bool ValueRequired { get; set; } + /// + /// Indicates if a value is required to rectify the issue + /// + [DataMember(Name = "valueRequired")] + public bool ValueRequired { get; set; } - /// - /// Indicates if a value required, how it is validated - /// - [DataMember(Name = "providedValueValidation")] - public string? ProvidedValueValidation { get; set; } + /// + /// Indicates if a value required, how it is validated + /// + [DataMember(Name = "providedValueValidation")] + public string? ProvidedValueValidation { get; set; } - /// - /// Indicates if a value required, and is validated by a regex, what the regex to use is - /// - [DataMember(Name = "providedValueValidationRegex")] - public string? ProvidedValueValidationRegex { get; set; } + /// + /// Indicates if a value required, and is validated by a regex, what the regex to use is + /// + [DataMember(Name = "providedValueValidationRegex")] + public string? ProvidedValueValidationRegex { get; set; } - /// - /// Provides a value to rectify the issue - /// - [DataMember(Name = "providedValue")] - public string? ProvidedValue { get; set; } - } + /// + /// Provides a value to rectify the issue + /// + [DataMember(Name = "providedValue")] + public string? ProvidedValue { get; set; } } diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckAttribute.cs b/src/Umbraco.Core/HealthChecks/HealthCheckAttribute.cs index 0fa66479714e..a78be543ecfb 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckAttribute.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckAttribute.cs @@ -1,26 +1,23 @@ -using System; +namespace Umbraco.Cms.Core.HealthChecks; -namespace Umbraco.Cms.Core.HealthChecks +/// +/// Metadata attribute for Health checks +/// +[AttributeUsage(AttributeTargets.Class)] +public sealed class HealthCheckAttribute : Attribute { - /// - /// Metadata attribute for Health checks - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public sealed class HealthCheckAttribute : Attribute + public HealthCheckAttribute(string id, string name) { - public HealthCheckAttribute(string id, string name) - { - Id = new Guid(id); - Name = name; - } + Id = new Guid(id); + Name = name; + } - public string Name { get; private set; } - public string? Description { get; set; } + public string Name { get; } + public string? Description { get; set; } - public string? Group { get; set; } + public string? Group { get; set; } - public Guid Id { get; private set; } + public Guid Id { get; } - // TODO: Do we need more metadata? - } + // TODO: Do we need more metadata? } diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckCollection.cs b/src/Umbraco.Core/HealthChecks/HealthCheckCollection.cs index bcbee9036bc0..7f3874ed764c 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckCollection.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckCollection.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.HealthChecks +namespace Umbraco.Cms.Core.HealthChecks; + +public class HealthCheckCollection : BuilderCollectionBase { - public class HealthCheckCollection : BuilderCollectionBase + public HealthCheckCollection(Func> items) : base(items) { - public HealthCheckCollection(Func> items) : base(items) - { - } } } diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckGroup.cs b/src/Umbraco.Core/HealthChecks/HealthCheckGroup.cs index aee97647d98c..c0cc713eca5a 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckGroup.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckGroup.cs @@ -1,15 +1,11 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.HealthChecks +namespace Umbraco.Cms.Core.HealthChecks; + +[DataContract(Name = "healthCheckGroup", Namespace = "")] +public class HealthCheckGroup { - [DataContract(Name = "healthCheckGroup", Namespace = "")] - public class HealthCheckGroup - { - [DataMember(Name = "name")] - public string? Name { get; set; } + [DataMember(Name = "name")] public string? Name { get; set; } - [DataMember(Name = "checks")] - public List? Checks { get; set; } - } + [DataMember(Name = "checks")] public List? Checks { get; set; } } diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodAttribute.cs b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodAttribute.cs index 6dd6df4b8bf9..3e5cef461a8e 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodAttribute.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodAttribute.cs @@ -1,18 +1,12 @@ -using System; +namespace Umbraco.Cms.Core.HealthChecks; -namespace Umbraco.Cms.Core.HealthChecks +/// +/// Metadata attribute for health check notification methods +/// +[AttributeUsage(AttributeTargets.Class)] +public sealed class HealthCheckNotificationMethodAttribute : Attribute { - /// - /// Metadata attribute for health check notification methods - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public sealed class HealthCheckNotificationMethodAttribute : Attribute - { - public HealthCheckNotificationMethodAttribute(string alias) - { - Alias = alias; - } + public HealthCheckNotificationMethodAttribute(string alias) => Alias = alias; - public string Alias { get; } - } + public string Alias { get; } } diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollection.cs b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollection.cs index af964857d88e..c31a652a7b98 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollection.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollection.cs @@ -1,14 +1,12 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.HealthChecks.NotificationMethods; -namespace Umbraco.Cms.Core.HealthChecks +namespace Umbraco.Cms.Core.HealthChecks; + +public class HealthCheckNotificationMethodCollection : BuilderCollectionBase { - public class HealthCheckNotificationMethodCollection : BuilderCollectionBase + public HealthCheckNotificationMethodCollection(Func> items) : + base(items) { - public HealthCheckNotificationMethodCollection(Func> items) : base(items) - { - } } } diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollectionBuilder.cs b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollectionBuilder.cs index 48f2629e2a1e..3b7cf6699f95 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollectionBuilder.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollectionBuilder.cs @@ -1,10 +1,11 @@ using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.HealthChecks.NotificationMethods; -namespace Umbraco.Cms.Core.HealthChecks +namespace Umbraco.Cms.Core.HealthChecks; + +public class HealthCheckNotificationMethodCollectionBuilder : LazyCollectionBuilderBase< + HealthCheckNotificationMethodCollectionBuilder, HealthCheckNotificationMethodCollection, + IHealthCheckNotificationMethod> { - public class HealthCheckNotificationMethodCollectionBuilder : LazyCollectionBuilderBase - { - protected override HealthCheckNotificationMethodCollectionBuilder This => this; - } + protected override HealthCheckNotificationMethodCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckNotificationVerbosity.cs b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationVerbosity.cs index cba8ab5c0f03..86914d91d717 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckNotificationVerbosity.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationVerbosity.cs @@ -1,9 +1,7 @@ -namespace Umbraco.Cms.Core.HealthChecks -{ - public enum HealthCheckNotificationVerbosity - { +namespace Umbraco.Cms.Core.HealthChecks; - Summary, - Detailed - } +public enum HealthCheckNotificationVerbosity +{ + Summary, + Detailed } diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs b/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs index bde90627c775..85e736cea943 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs @@ -1,166 +1,169 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Text; using Microsoft.Extensions.Logging; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.HealthChecks +namespace Umbraco.Cms.Core.HealthChecks; + +public class HealthCheckResults { - public class HealthCheckResults + public readonly bool AllChecksSuccessful; + + private HealthCheckResults(Dictionary> results, bool allChecksSuccessful) { - private readonly Dictionary> _results; - public readonly bool AllChecksSuccessful; + ResultsAsDictionary = results; + AllChecksSuccessful = allChecksSuccessful; + } - private static ILogger Logger => StaticApplicationLogging.Logger; // TODO: inject + private static ILogger Logger => StaticApplicationLogging.Logger; // TODO: inject - private HealthCheckResults(Dictionary> results, bool allChecksSuccessful) - { - _results = results; - AllChecksSuccessful = allChecksSuccessful; - } - public static async Task Create(IEnumerable checks) - { - var results = await checks.ToDictionaryAsync( - t => t.Name, - async t => { - try - { - return await t.GetStatus(); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error running scheduled health check: {HealthCheckName}", t.Name); - var message = $"Health check failed with exception: {ex.Message}. See logs for details."; - return new List - { - new HealthCheckStatus(message) - { - ResultType = StatusResultType.Error - } - }; - } - }); - - // find out if all checks pass or not - var allChecksSuccessful = true; - foreach (var result in results) + internal Dictionary> ResultsAsDictionary { get; } + + public static async Task Create(IEnumerable checks) + { + Dictionary> results = await checks.ToDictionaryAsync( + t => t.Name, + async t => { - var checkIsSuccess = result.Value.All(x => x.ResultType == StatusResultType.Success || x.ResultType == StatusResultType.Info || x.ResultType == StatusResultType.Warning); - if (checkIsSuccess == false) + try { - allChecksSuccessful = false; - break; + return await t.GetStatus(); } - } + catch (Exception ex) + { + Logger.LogError(ex, "Error running scheduled health check: {HealthCheckName}", t.Name); + var message = $"Health check failed with exception: {ex.Message}. See logs for details."; + return new List {new(message) {ResultType = StatusResultType.Error}}; + } + }); - return new HealthCheckResults(results, allChecksSuccessful); + // find out if all checks pass or not + var allChecksSuccessful = true; + foreach (KeyValuePair> result in results) + { + var checkIsSuccess = result.Value.All(x => + x.ResultType == StatusResultType.Success || x.ResultType == StatusResultType.Info || + x.ResultType == StatusResultType.Warning); + if (checkIsSuccess == false) + { + allChecksSuccessful = false; + break; + } } - public void LogResults() + return new HealthCheckResults(results, allChecksSuccessful); + } + + public void LogResults() + { + Logger.LogInformation("Scheduled health check results:"); + foreach (KeyValuePair> result in ResultsAsDictionary) { - Logger.LogInformation("Scheduled health check results:"); - foreach (var result in _results) + var checkName = result.Key; + IEnumerable checkResults = result.Value; + var checkIsSuccess = result.Value.All(x => x.ResultType == StatusResultType.Success); + if (checkIsSuccess) { - var checkName = result.Key; - var checkResults = result.Value; - var checkIsSuccess = result.Value.All(x => x.ResultType == StatusResultType.Success); - if (checkIsSuccess) - { - Logger.LogInformation("Checks for '{HealthCheckName}' all completed successfully.", checkName); - } - else - { - Logger.LogWarning("Checks for '{HealthCheckName}' completed with errors.", checkName); - } + Logger.LogInformation("Checks for '{HealthCheckName}' all completed successfully.", checkName); + } + else + { + Logger.LogWarning("Checks for '{HealthCheckName}' completed with errors.", checkName); + } - foreach (var checkResult in checkResults) - { - Logger.LogInformation("Result for {HealthCheckName}: {HealthCheckResult}, Message: '{HealthCheckMessage}'", checkName, checkResult.ResultType, checkResult.Message); - } + foreach (HealthCheckStatus checkResult in checkResults) + { + Logger.LogInformation( + "Result for {HealthCheckName}: {HealthCheckResult}, Message: '{HealthCheckMessage}'", checkName, + checkResult.ResultType, checkResult.Message); } } + } + + public string ResultsAsMarkDown(HealthCheckNotificationVerbosity verbosity) + { + var newItem = "- "; + + var sb = new StringBuilder(); - public string ResultsAsMarkDown(HealthCheckNotificationVerbosity verbosity) + foreach (KeyValuePair> result in ResultsAsDictionary) { - var newItem = "- "; + var checkName = result.Key; + IEnumerable checkResults = result.Value; + var checkIsSuccess = result.Value.All(x => x.ResultType == StatusResultType.Success); - var sb = new StringBuilder(); + // add a new line if not the first check + if (result.Equals(ResultsAsDictionary.First()) == false) + { + sb.Append(Environment.NewLine); + } - foreach (var result in _results) + if (checkIsSuccess) { - var checkName = result.Key; - var checkResults = result.Value; - var checkIsSuccess = result.Value.All(x => x.ResultType == StatusResultType.Success); + sb.AppendFormat("{0}Checks for '{1}' all completed successfully.{2}", newItem, checkName, + Environment.NewLine); + } + else + { + sb.AppendFormat("{0}Checks for '{1}' completed with errors.{2}", newItem, checkName, + Environment.NewLine); + } - // add a new line if not the first check - if (result.Equals(_results.First()) == false) - { - sb.Append(Environment.NewLine); - } + foreach (HealthCheckStatus checkResult in checkResults) + { + sb.AppendFormat("\t{0}Result: '{1}'", newItem, checkResult.ResultType); - if (checkIsSuccess) - { - sb.AppendFormat("{0}Checks for '{1}' all completed successfully.{2}", newItem, checkName, Environment.NewLine); - } - else + // With summary logging, only record details of warnings or errors + if (checkResult.ResultType != StatusResultType.Success || + verbosity == HealthCheckNotificationVerbosity.Detailed) { - sb.AppendFormat("{0}Checks for '{1}' completed with errors.{2}", newItem, checkName, Environment.NewLine); + sb.AppendFormat(", Message: '{0}'", SimpleHtmlToMarkDown(checkResult.Message)); } - foreach (var checkResult in checkResults) - { - sb.AppendFormat("\t{0}Result: '{1}'", newItem, checkResult.ResultType); - - // With summary logging, only record details of warnings or errors - if (checkResult.ResultType != StatusResultType.Success || verbosity == HealthCheckNotificationVerbosity.Detailed) - { - sb.AppendFormat(", Message: '{0}'", SimpleHtmlToMarkDown(checkResult.Message)); - } - - sb.AppendLine(Environment.NewLine); - } + sb.AppendLine(Environment.NewLine); } - - return sb.ToString(); } + return sb.ToString(); + } - internal Dictionary> ResultsAsDictionary => _results; + private string SimpleHtmlToMarkDown(string html) => + html.Replace("", "**") + .Replace("", "**") + .Replace("", "*") + .Replace("", "*"); - private string SimpleHtmlToMarkDown(string html) + public Dictionary>? GetResultsForStatus(StatusResultType resultType) + { + switch (resultType) { - return html.Replace("", "**") - .Replace("", "**") - .Replace("", "*") - .Replace("", "*"); + case StatusResultType.Success: + // a check is considered a success status if all checks are successful or info + IEnumerable>> successResults = + ResultsAsDictionary.Where(x => + x.Value.Any(y => y.ResultType == StatusResultType.Success) && x.Value.All(y => + y.ResultType == StatusResultType.Success || y.ResultType == StatusResultType.Info)); + return successResults.ToDictionary(x => x.Key, x => x.Value); + case StatusResultType.Warning: + // a check is considered warn status if one check is warn and all others are success or info + IEnumerable>> warnResults = + ResultsAsDictionary.Where(x => + x.Value.Any(y => y.ResultType == StatusResultType.Warning) && x.Value.All(y => + y.ResultType == StatusResultType.Warning || y.ResultType == StatusResultType.Success || + y.ResultType == StatusResultType.Info)); + return warnResults.ToDictionary(x => x.Key, x => x.Value); + case StatusResultType.Error: + // a check is considered error status if any check is error + IEnumerable>> errorResults = + ResultsAsDictionary.Where(x => x.Value.Any(y => y.ResultType == StatusResultType.Error)); + return errorResults.ToDictionary(x => x.Key, x => x.Value); + case StatusResultType.Info: + // a check is considered info status if all checks are info + IEnumerable>> infoResults = + ResultsAsDictionary.Where(x => x.Value.All(y => y.ResultType == StatusResultType.Info)); + return infoResults.ToDictionary(x => x.Key, x => x.Value); } - public Dictionary>? GetResultsForStatus(StatusResultType resultType) - { - switch (resultType) - { - case StatusResultType.Success: - // a check is considered a success status if all checks are successful or info - var successResults = _results.Where(x => x.Value.Any(y => y.ResultType == StatusResultType.Success) && x.Value.All(y => y.ResultType == StatusResultType.Success || y.ResultType == StatusResultType.Info)); - return successResults.ToDictionary(x => x.Key, x => x.Value); - case StatusResultType.Warning: - // a check is considered warn status if one check is warn and all others are success or info - var warnResults = _results.Where(x => x.Value.Any(y => y.ResultType == StatusResultType.Warning) && x.Value.All(y => y.ResultType == StatusResultType.Warning || y.ResultType == StatusResultType.Success || y.ResultType == StatusResultType.Info)); - return warnResults.ToDictionary(x => x.Key, x => x.Value); - case StatusResultType.Error: - // a check is considered error status if any check is error - var errorResults = _results.Where(x => x.Value.Any(y => y.ResultType == StatusResultType.Error)); - return errorResults.ToDictionary(x => x.Key, x => x.Value); - case StatusResultType.Info: - // a check is considered info status if all checks are info - var infoResults = _results.Where(x => x.Value.All(y => y.ResultType == StatusResultType.Info)); - return infoResults.ToDictionary(x => x.Key, x => x.Value); - } - - return null; - } + return null; } } diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckStatus.cs b/src/Umbraco.Core/HealthChecks/HealthCheckStatus.cs index 49428fe8996c..45141e3d1798 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckStatus.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckStatus.cs @@ -1,58 +1,55 @@ -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.HealthChecks +namespace Umbraco.Cms.Core.HealthChecks; + +/// +/// The status returned for a health check when it performs it check +/// TODO: This model will be used in the WebApi result so needs attributes for JSON usage +/// +[DataContract(Name = "healthCheckStatus", Namespace = "")] +public class HealthCheckStatus { - /// - /// The status returned for a health check when it performs it check - /// TODO: This model will be used in the WebApi result so needs attributes for JSON usage - /// - [DataContract(Name = "healthCheckStatus", Namespace = "")] - public class HealthCheckStatus + public HealthCheckStatus(string message) { - public HealthCheckStatus(string message) - { - Message = message; - Actions = Enumerable.Empty(); - } - - /// - /// The status message - /// - [DataMember(Name = "message")] - public string Message { get; private set; } - - /// - /// The status description if one is necessary - /// - [DataMember(Name = "description")] - public string? Description { get; set; } - - /// - /// This is optional but would allow a developer to specify a path to an angular HTML view - /// in order to either show more advanced information and/or to provide input for the admin - /// to configure how an action is executed - /// - [DataMember(Name = "view")] - public string? View { get; set; } - - /// - /// The status type - /// - [DataMember(Name = "resultType")] - public StatusResultType ResultType { get; set; } - - /// - /// The potential actions to take (in any) - /// - [DataMember(Name = "actions")] - public IEnumerable Actions { get; set; } - - /// - /// This is optional but would allow a developer to specify a link that is shown as a "read more" button. - /// - [DataMember(Name = "readMoreLink")] - public string? ReadMoreLink { get; set; } + Message = message; + Actions = Enumerable.Empty(); } + + /// + /// The status message + /// + [DataMember(Name = "message")] + public string Message { get; private set; } + + /// + /// The status description if one is necessary + /// + [DataMember(Name = "description")] + public string? Description { get; set; } + + /// + /// This is optional but would allow a developer to specify a path to an angular HTML view + /// in order to either show more advanced information and/or to provide input for the admin + /// to configure how an action is executed + /// + [DataMember(Name = "view")] + public string? View { get; set; } + + /// + /// The status type + /// + [DataMember(Name = "resultType")] + public StatusResultType ResultType { get; set; } + + /// + /// The potential actions to take (in any) + /// + [DataMember(Name = "actions")] + public IEnumerable Actions { get; set; } + + /// + /// This is optional but would allow a developer to specify a link that is shown as a "read more" button. + /// + [DataMember(Name = "readMoreLink")] + public string? ReadMoreLink { get; set; } } diff --git a/src/Umbraco.Core/HealthChecks/HeathCheckCollectionBuilder.cs b/src/Umbraco.Core/HealthChecks/HeathCheckCollectionBuilder.cs index 495fc42cf17b..2a80af720d12 100644 --- a/src/Umbraco.Core/HealthChecks/HeathCheckCollectionBuilder.cs +++ b/src/Umbraco.Core/HealthChecks/HeathCheckCollectionBuilder.cs @@ -1,14 +1,15 @@ using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.HealthChecks +namespace Umbraco.Cms.Core.HealthChecks; + +public class + HealthCheckCollectionBuilder : LazyCollectionBuilderBase { - public class HealthCheckCollectionBuilder : LazyCollectionBuilderBase - { - protected override HealthCheckCollectionBuilder This => this; + protected override HealthCheckCollectionBuilder This => this; - // note: in v7 they were per-request, not sure why? - // the collection is injected into the controller & there's only 1 controller per request anyways - protected override ServiceLifetime CollectionLifetime => ServiceLifetime.Transient; // transient! - } + // note: in v7 they were per-request, not sure why? + // the collection is injected into the controller & there's only 1 controller per request anyways + protected override ServiceLifetime CollectionLifetime => ServiceLifetime.Transient; // transient! } diff --git a/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs b/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs index 94867d8882ee..6ece967657ca 100644 --- a/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs +++ b/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs @@ -1,5 +1,3 @@ -using System; -using System.Threading.Tasks; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; @@ -8,89 +6,90 @@ using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.HealthChecks.NotificationMethods +namespace Umbraco.Cms.Core.HealthChecks.NotificationMethods; + +[HealthCheckNotificationMethod("email")] +public class EmailNotificationMethod : NotificationMethodBase { - [HealthCheckNotificationMethod("email")] - public class EmailNotificationMethod : NotificationMethodBase + private readonly IEmailSender? _emailSender; + private readonly IHostingEnvironment? _hostingEnvironment; + private readonly IMarkdownToHtmlConverter? _markdownToHtmlConverter; + private readonly ILocalizedTextService? _textService; + private ContentSettings? _contentSettings; + + public EmailNotificationMethod( + ILocalizedTextService textService, + IHostingEnvironment hostingEnvironment, + IEmailSender emailSender, + IOptionsMonitor healthChecksSettings, + IOptionsMonitor contentSettings, + IMarkdownToHtmlConverter markdownToHtmlConverter) + : base(healthChecksSettings) { - private readonly ILocalizedTextService? _textService; - private readonly IHostingEnvironment? _hostingEnvironment; - private readonly IEmailSender? _emailSender; - private readonly IMarkdownToHtmlConverter? _markdownToHtmlConverter; - private ContentSettings? _contentSettings; - - public EmailNotificationMethod( - ILocalizedTextService textService, - IHostingEnvironment hostingEnvironment, - IEmailSender emailSender, - IOptionsMonitor healthChecksSettings, - IOptionsMonitor contentSettings, - IMarkdownToHtmlConverter markdownToHtmlConverter) - : base(healthChecksSettings) + var recipientEmail = Settings?["RecipientEmail"]; + if (string.IsNullOrWhiteSpace(recipientEmail)) { - var recipientEmail = Settings?["RecipientEmail"]; - if (string.IsNullOrWhiteSpace(recipientEmail)) - { - Enabled = false; - return; - } + Enabled = false; + return; + } - RecipientEmail = recipientEmail; + RecipientEmail = recipientEmail; - _textService = textService ?? throw new ArgumentNullException(nameof(textService)); - _hostingEnvironment = hostingEnvironment; - _emailSender = emailSender; - _markdownToHtmlConverter = markdownToHtmlConverter; - _contentSettings = contentSettings.CurrentValue ?? throw new ArgumentNullException(nameof(contentSettings)); + _textService = textService ?? throw new ArgumentNullException(nameof(textService)); + _hostingEnvironment = hostingEnvironment; + _emailSender = emailSender; + _markdownToHtmlConverter = markdownToHtmlConverter; + _contentSettings = contentSettings.CurrentValue ?? throw new ArgumentNullException(nameof(contentSettings)); - contentSettings.OnChange(x => _contentSettings = x); - } + contentSettings.OnChange(x => _contentSettings = x); + } - public string? RecipientEmail { get; } + public string? RecipientEmail { get; } - public override async Task SendAsync(HealthCheckResults results) + public override async Task SendAsync(HealthCheckResults results) + { + if (ShouldSend(results) == false) { - if (ShouldSend(results) == false) - { - return; - } + return; + } - if (string.IsNullOrEmpty(RecipientEmail)) - { - return; - } + if (string.IsNullOrEmpty(RecipientEmail)) + { + return; + } - var message = _textService?.Localize("healthcheck","scheduledHealthCheckEmailBody", new[] + var message = _textService?.Localize("healthcheck", "scheduledHealthCheckEmailBody", + new[] { - DateTime.Now.ToShortDateString(), - DateTime.Now.ToShortTimeString(), + DateTime.Now.ToShortDateString(), DateTime.Now.ToShortTimeString(), _markdownToHtmlConverter?.ToHtml(results, Verbosity) }); - // Include the umbraco Application URL host in the message subject so that - // you can identify the site that these results are for. - var host = _hostingEnvironment?.ApplicationMainUrl?.ToString(); + // Include the umbraco Application URL host in the message subject so that + // you can identify the site that these results are for. + var host = _hostingEnvironment?.ApplicationMainUrl?.ToString(); - var subject = _textService?.Localize("healthcheck","scheduledHealthCheckEmailSubject", new[] { host }); + var subject = _textService?.Localize("healthcheck", "scheduledHealthCheckEmailSubject", new[] {host}); - var mailMessage = CreateMailMessage(subject, message); - Task? task = _emailSender?.SendAsync(mailMessage, Constants.Web.EmailTypes.HealthCheck); - if (task is not null) - { - await task; - } - } - - private EmailMessage CreateMailMessage(string? subject, string? message) + EmailMessage mailMessage = CreateMailMessage(subject, message); + Task? task = _emailSender?.SendAsync(mailMessage, Constants.Web.EmailTypes.HealthCheck); + if (task is not null) { - var to = _contentSettings?.Notifications.Email; + await task; + } + } - if (string.IsNullOrWhiteSpace(subject)) - subject = "Umbraco Health Check Status"; + private EmailMessage CreateMailMessage(string? subject, string? message) + { + var to = _contentSettings?.Notifications.Email; - var isBodyHtml = message.IsNullOrWhiteSpace() == false && message!.Contains("<") && message.Contains(" healthCheckSettings) { - protected NotificationMethodBase(IOptionsMonitor healthCheckSettings) + Type type = GetType(); + HealthCheckNotificationMethodAttribute attribute = + type.GetCustomAttribute(); + if (attribute == null) { - var type = GetType(); - var attribute = type.GetCustomAttribute(); - if (attribute == null) - { - Enabled = false; - return; - } - - var notificationMethods = healthCheckSettings.CurrentValue.Notification.NotificationMethods; - if (!notificationMethods.TryGetValue(attribute.Alias, out var notificationMethod)) - { - Enabled = false; - return; - } - - Enabled = notificationMethod.Enabled; - FailureOnly = notificationMethod.FailureOnly; - Verbosity = notificationMethod.Verbosity; - Settings = notificationMethod.Settings; + Enabled = false; + return; } - public bool Enabled { get; protected set; } + IDictionary notificationMethods = + healthCheckSettings.CurrentValue.Notification.NotificationMethods; + if (!notificationMethods.TryGetValue(attribute.Alias, + out HealthChecksNotificationMethodSettings notificationMethod)) + { + Enabled = false; + return; + } - public bool FailureOnly { get; protected set; } + Enabled = notificationMethod.Enabled; + FailureOnly = notificationMethod.FailureOnly; + Verbosity = notificationMethod.Verbosity; + Settings = notificationMethod.Settings; + } - public HealthCheckNotificationVerbosity Verbosity { get; protected set; } + public bool FailureOnly { get; protected set; } - public IDictionary? Settings { get; } + public HealthCheckNotificationVerbosity Verbosity { get; protected set; } - protected bool ShouldSend(HealthCheckResults results) - { - return Enabled && (!FailureOnly || !results.AllChecksSuccessful); - } + public IDictionary? Settings { get; } - public abstract Task SendAsync(HealthCheckResults results); - } + public bool Enabled { get; protected set; } + + public abstract Task SendAsync(HealthCheckResults results); + + protected bool ShouldSend(HealthCheckResults results) => Enabled && (!FailureOnly || !results.AllChecksSuccessful); } diff --git a/src/Umbraco.Core/HealthChecks/StatusResultType.cs b/src/Umbraco.Core/HealthChecks/StatusResultType.cs index b06322a267e5..7fa329429f9c 100644 --- a/src/Umbraco.Core/HealthChecks/StatusResultType.cs +++ b/src/Umbraco.Core/HealthChecks/StatusResultType.cs @@ -1,10 +1,9 @@ -namespace Umbraco.Cms.Core.HealthChecks +namespace Umbraco.Cms.Core.HealthChecks; + +public enum StatusResultType { - public enum StatusResultType - { - Success, - Warning, - Error, - Info - } + Success, + Warning, + Error, + Info } diff --git a/src/Umbraco.Core/HealthChecks/ValueComparisonType.cs b/src/Umbraco.Core/HealthChecks/ValueComparisonType.cs index 254a53c6fb91..20419087d781 100644 --- a/src/Umbraco.Core/HealthChecks/ValueComparisonType.cs +++ b/src/Umbraco.Core/HealthChecks/ValueComparisonType.cs @@ -1,8 +1,7 @@ -namespace Umbraco.Cms.Core.HealthChecks +namespace Umbraco.Cms.Core.HealthChecks; + +public enum ValueComparisonType { - public enum ValueComparisonType - { - ShouldEqual, - ShouldNotEqual, - } + ShouldEqual, + ShouldNotEqual } diff --git a/src/Umbraco.Core/HexEncoder.cs b/src/Umbraco.Core/HexEncoder.cs index ce4df997ab1b..3b6e7c854cdf 100644 --- a/src/Umbraco.Core/HexEncoder.cs +++ b/src/Umbraco.Core/HexEncoder.cs @@ -1,84 +1,85 @@ -using System.Linq; -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Provides methods for encoding byte arrays into hexadecimal strings. +/// +public static class HexEncoder { - /// - /// Provides methods for encoding byte arrays into hexadecimal strings. - /// - public static class HexEncoder + // LUT's that provide the hexadecimal representation of each possible byte value. + private static readonly char[] HexLutBase = { - // LUT's that provide the hexadecimal representation of each possible byte value. - private static readonly char[] HexLutBase = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; - // The base LUT arranged in 16x each item order. 0 * 16, 1 * 16, .... F * 16 - private static readonly char[] HexLutHi = Enumerable.Range(0, 256).Select(x => HexLutBase[x / 0x10]).ToArray(); + // The base LUT arranged in 16x each item order. 0 * 16, 1 * 16, .... F * 16 + private static readonly char[] HexLutHi = Enumerable.Range(0, 256).Select(x => HexLutBase[x / 0x10]).ToArray(); - // The base LUT repeated 16x. - private static readonly char[] HexLutLo = Enumerable.Range(0, 256).Select(x => HexLutBase[x % 0x10]).ToArray(); + // The base LUT repeated 16x. + private static readonly char[] HexLutLo = Enumerable.Range(0, 256).Select(x => HexLutBase[x % 0x10]).ToArray(); + + /// + /// Converts a to a hexadecimal formatted padded to 2 digits. + /// + /// The bytes. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string Encode(byte[] bytes) + { + var length = bytes.Length; + var chars = new char[length * 2]; - /// - /// Converts a to a hexadecimal formatted padded to 2 digits. - /// - /// The bytes. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string Encode(byte[] bytes) + var index = 0; + for (var i = 0; i < length; i++) { - var length = bytes.Length; - var chars = new char[length * 2]; + var byteIndex = bytes[i]; + chars[index++] = HexLutHi[byteIndex]; + chars[index++] = HexLutLo[byteIndex]; + } - var index = 0; - for (var i = 0; i < length; i++) - { - var byteIndex = bytes[i]; - chars[index++] = HexLutHi[byteIndex]; - chars[index++] = HexLutLo[byteIndex]; - } + return new string(chars, 0, chars.Length); + } - return new string(chars, 0, chars.Length); - } + /// + /// Converts a to a hexadecimal formatted padded to 2 digits + /// and split into blocks with the given char separator. + /// + /// The bytes. + /// The separator. + /// The block size. + /// The block count. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string Encode(byte[] bytes, char separator, int blockSize, int blockCount) + { + var length = bytes.Length; + var chars = new char[(length * 2) + blockCount]; + var count = 0; + var size = 0; + var index = 0; - /// - /// Converts a to a hexadecimal formatted padded to 2 digits - /// and split into blocks with the given char separator. - /// - /// The bytes. - /// The separator. - /// The block size. - /// The block count. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string Encode(byte[] bytes, char separator, int blockSize, int blockCount) + for (var i = 0; i < length; i++) { - var length = bytes.Length; - var chars = new char[(length * 2) + blockCount]; - var count = 0; - var size = 0; - var index = 0; + var byteIndex = bytes[i]; + chars[index++] = HexLutHi[byteIndex]; + chars[index++] = HexLutLo[byteIndex]; - for (var i = 0; i < length; i++) + if (count == blockCount) { - var byteIndex = bytes[i]; - chars[index++] = HexLutHi[byteIndex]; - chars[index++] = HexLutLo[byteIndex]; - - if (count == blockCount) - { - continue; - } - - if (++size < blockSize) - { - continue; - } + continue; + } - chars[index++] = separator; - size = 0; - count++; + if (++size < blockSize) + { + continue; } - return new string(chars, 0, chars.Length); + chars[index++] = separator; + size = 0; + count++; } + + return new string(chars, 0, chars.Length); } } diff --git a/src/Umbraco.Core/Hosting/IApplicationShutdownRegistry.cs b/src/Umbraco.Core/Hosting/IApplicationShutdownRegistry.cs index 2d1336ab905b..a642a9ab00b2 100644 --- a/src/Umbraco.Core/Hosting/IApplicationShutdownRegistry.cs +++ b/src/Umbraco.Core/Hosting/IApplicationShutdownRegistry.cs @@ -1,8 +1,7 @@ -namespace Umbraco.Cms.Core.Hosting +namespace Umbraco.Cms.Core.Hosting; + +public interface IApplicationShutdownRegistry { - public interface IApplicationShutdownRegistry - { - void RegisterObject(IRegisteredObject registeredObject); - void UnregisterObject(IRegisteredObject registeredObject); - } + void RegisterObject(IRegisteredObject registeredObject); + void UnregisterObject(IRegisteredObject registeredObject); } diff --git a/src/Umbraco.Core/Hosting/IHostingEnvironment.cs b/src/Umbraco.Core/Hosting/IHostingEnvironment.cs index c2c7cfe79256..b8960048f63d 100644 --- a/src/Umbraco.Core/Hosting/IHostingEnvironment.cs +++ b/src/Umbraco.Core/Hosting/IHostingEnvironment.cs @@ -1,101 +1,108 @@ -using System; +namespace Umbraco.Cms.Core.Hosting; -namespace Umbraco.Cms.Core.Hosting +public interface IHostingEnvironment { - public interface IHostingEnvironment - { - string SiteName { get; } + string SiteName { get; } - /// - /// The unique application ID for this Umbraco website. - /// - /// - /// - /// The returned value will be the same consistent value for an Umbraco website on a specific server and will the same - /// between restarts of that Umbraco website/application on that specific server. - /// - /// - /// The value of this does not distinguish between unique workers/servers for this Umbraco application. - /// Usage of this must take into account that the same may be returned for the same - /// Umbraco website hosted on different servers.
- /// Similarly the usage of this must take into account that a different - /// may be returned for the same Umbraco website hosted on different servers. - ///
- /// - /// This returns a hash of the value of IApplicationDiscriminator.Discriminator (which is most likely just the value of unless an alternative implementation of IApplicationDiscriminator has been registered).
- /// However during ConfigureServices a temporary instance of IHostingEnvironment is constructed which guarantees that this will be the hash of , so the value may differ depend on when the property is used. - ///
- /// - /// If you require this value during ConfigureServices it is probably a code smell. - /// - ///
- [Obsolete("Please use IApplicationDiscriminator.Discriminator instead.")] - string ApplicationId { get; } + /// + /// The unique application ID for this Umbraco website. + /// + /// + /// + /// The returned value will be the same consistent value for an Umbraco website on a specific server and will the + /// same + /// between restarts of that Umbraco website/application on that specific server. + /// + /// + /// The value of this does not distinguish between unique workers/servers for this Umbraco application. + /// Usage of this must take into account that the same may be returned for the same + /// Umbraco website hosted on different servers.
+ /// Similarly the usage of this must take into account that a different + /// may be returned for the same Umbraco website hosted on different servers. + ///
+ /// + /// This returns a hash of the value of IApplicationDiscriminator.Discriminator (which is most likely just the + /// value of unless an alternative + /// implementation of IApplicationDiscriminator has been registered).
+ /// However during ConfigureServices a temporary instance of IHostingEnvironment is constructed which guarantees + /// that this will be the hash of , so + /// the value may differ depend on when the property is used. + ///
+ /// + /// If you require this value during ConfigureServices it is probably a code smell. + /// + ///
+ [Obsolete("Please use IApplicationDiscriminator.Discriminator instead.")] + string ApplicationId { get; } - /// - /// Will return the physical path to the root of the application - /// - string ApplicationPhysicalPath { get; } + /// + /// Will return the physical path to the root of the application + /// + string ApplicationPhysicalPath { get; } - string LocalTempPath { get; } + string LocalTempPath { get; } - /// - /// The web application's hosted path - /// - /// - /// In most cases this will return "/" but if the site is hosted in a virtual directory then this will return the virtual directory's path such as "/mysite". - /// This value must begin with a "/" and cannot end with "/". - /// - string ApplicationVirtualPath { get; } + /// + /// The web application's hosted path + /// + /// + /// In most cases this will return "/" but if the site is hosted in a virtual directory then this will return the + /// virtual directory's path such as "/mysite". + /// This value must begin with a "/" and cannot end with "/". + /// + string ApplicationVirtualPath { get; } - bool IsDebugMode { get; } + bool IsDebugMode { get; } - /// - /// Gets a value indicating whether Umbraco is hosted. - /// - bool IsHosted { get; } + /// + /// Gets a value indicating whether Umbraco is hosted. + /// + bool IsHosted { get; } - /// - /// Gets the main application url. - /// - Uri ApplicationMainUrl { get; } + /// + /// Gets the main application url. + /// + Uri ApplicationMainUrl { get; } - /// - /// Maps a virtual path to a physical path to the application's web root - /// - /// - /// Depending on the runtime 'web root', this result can vary. For example in Net Framework the web root and the content root are the same, however - /// in netcore the web root is /www therefore this will Map to a physical path within www. - /// - [Obsolete("Please use the MapPathWebRoot extension method on an instance of IWebHostEnvironment instead")] - string MapPathWebRoot(string path); + /// + /// Maps a virtual path to a physical path to the application's web root + /// + /// + /// Depending on the runtime 'web root', this result can vary. For example in Net Framework the web root and the + /// content root are the same, however + /// in netcore the web root is /www therefore this will Map to a physical path within www. + /// + [Obsolete("Please use the MapPathWebRoot extension method on an instance of IWebHostEnvironment instead")] + string MapPathWebRoot(string path); - /// - /// Maps a virtual path to a physical path to the application's root (not always equal to the web root) - /// - /// - /// Depending on the runtime 'web root', this result can vary. For example in Net Framework the web root and the content root are the same, however - /// in netcore the web root is /www therefore this will Map to a physical path within www. - /// - [Obsolete("Please use the MapPathContentRoot extension method on an instance of IHostEnvironment (or IWebHostEnvironment) instead")] - string MapPathContentRoot(string path); + /// + /// Maps a virtual path to a physical path to the application's root (not always equal to the web root) + /// + /// + /// Depending on the runtime 'web root', this result can vary. For example in Net Framework the web root and the + /// content root are the same, however + /// in netcore the web root is /www therefore this will Map to a physical path within www. + /// + [Obsolete( + "Please use the MapPathContentRoot extension method on an instance of IHostEnvironment (or IWebHostEnvironment) instead")] + string MapPathContentRoot(string path); - /// - /// Converts a virtual path to an absolute URL path based on the application's web root - /// - /// The virtual path. Must start with either ~/ or / else an exception is thrown. - /// - /// This maps the virtual path syntax to the web root. For example when hosting in a virtual directory called "site" and the value "~/pages/test" is passed in, it will - /// map to "/site/pages/test" where "/site" is the value of . - /// - /// - /// If virtualPath does not start with ~/ or / - /// - string ToAbsolute(string virtualPath); + /// + /// Converts a virtual path to an absolute URL path based on the application's web root + /// + /// The virtual path. Must start with either ~/ or / else an exception is thrown. + /// + /// This maps the virtual path syntax to the web root. For example when hosting in a virtual directory called "site" + /// and the value "~/pages/test" is passed in, it will + /// map to "/site/pages/test" where "/site" is the value of . + /// + /// + /// If virtualPath does not start with ~/ or / + /// + string ToAbsolute(string virtualPath); - /// - /// Ensures that the application know its main Url. - /// - void EnsureApplicationMainUrl(Uri? currentApplicationUrl); - } + /// + /// Ensures that the application know its main Url. + /// + void EnsureApplicationMainUrl(Uri? currentApplicationUrl); } diff --git a/src/Umbraco.Core/Hosting/IUmbracoApplicationLifetime.cs b/src/Umbraco.Core/Hosting/IUmbracoApplicationLifetime.cs index f55040f96a93..493c3ab4dc1d 100644 --- a/src/Umbraco.Core/Hosting/IUmbracoApplicationLifetime.cs +++ b/src/Umbraco.Core/Hosting/IUmbracoApplicationLifetime.cs @@ -1,15 +1,14 @@ -namespace Umbraco.Cms.Core.Hosting +namespace Umbraco.Cms.Core.Hosting; + +public interface IUmbracoApplicationLifetime { - public interface IUmbracoApplicationLifetime - { - /// - /// A value indicating whether the application is restarting after the current request. - /// - bool IsRestarting { get; } + /// + /// A value indicating whether the application is restarting after the current request. + /// + bool IsRestarting { get; } - /// - /// Terminates the current application. The application restarts the next time a request is received for it. - /// - void Restart(); - } + /// + /// Terminates the current application. The application restarts the next time a request is received for it. + /// + void Restart(); } diff --git a/src/Umbraco.Core/Hosting/NoopApplicationShutdownRegistry.cs b/src/Umbraco.Core/Hosting/NoopApplicationShutdownRegistry.cs index 15b08d1ac6fb..49729aae80f1 100644 --- a/src/Umbraco.Core/Hosting/NoopApplicationShutdownRegistry.cs +++ b/src/Umbraco.Core/Hosting/NoopApplicationShutdownRegistry.cs @@ -1,8 +1,7 @@ -namespace Umbraco.Cms.Core.Hosting +namespace Umbraco.Cms.Core.Hosting; + +internal class NoopApplicationShutdownRegistry : IApplicationShutdownRegistry { - internal class NoopApplicationShutdownRegistry : IApplicationShutdownRegistry - { - public void RegisterObject(IRegisteredObject registeredObject) { } - public void UnregisterObject(IRegisteredObject registeredObject) { } - } + public void RegisterObject(IRegisteredObject registeredObject) { } + public void UnregisterObject(IRegisteredObject registeredObject) { } } diff --git a/src/Umbraco.Core/HybridAccessorBase.cs b/src/Umbraco.Core/HybridAccessorBase.cs index 3200f97d7dee..dc8db452a8c7 100644 --- a/src/Umbraco.Core/HybridAccessorBase.cs +++ b/src/Umbraco.Core/HybridAccessorBase.cs @@ -1,77 +1,77 @@ -using System; -using System.Threading; using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Scoping; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Provides a base class for hybrid accessors. +/// +/// The type of the accessed object. +/// +/// +/// Hybrid accessors store the accessed object in HttpContext if they can, +/// otherwise they rely on the logical call context, to maintain an ambient +/// object that flows with async. +/// +/// +public abstract class HybridAccessorBase + where T : class { - /// - /// Provides a base class for hybrid accessors. - /// - /// The type of the accessed object. - /// - /// Hybrid accessors store the accessed object in HttpContext if they can, - /// otherwise they rely on the logical call context, to maintain an ambient - /// object that flows with async. - /// - public abstract class HybridAccessorBase - where T : class - { - private static readonly AsyncLocal s_ambientContext = new AsyncLocal(); + private static readonly AsyncLocal s_ambientContext = new(); - private readonly IRequestCache _requestCache; - private string? _itemKey; - protected string ItemKey => _itemKey ??= GetType().FullName!; + private readonly IRequestCache _requestCache; + private string? _itemKey; - // read - // http://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html - // http://stackoverflow.com/questions/14176028/why-does-logicalcallcontext-not-work-with-async - // http://stackoverflow.com/questions/854976/will-values-in-my-threadstatic-variables-still-be-there-when-cycled-via-threadpo - // https://msdn.microsoft.com/en-us/library/dd642243.aspx?f=255&MSPPError=-2147217396 ThreadLocal - // http://stackoverflow.com/questions/29001266/cleaning-up-callcontext-in-tpl clearing call context - // - // anything that is ThreadStatic will stay with the thread and NOT flow in async threads - // the only thing that flows is the logical call context (safe in 4.5+) + protected HybridAccessorBase(IRequestCache requestCache) + => _requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache)); - // no! - //[ThreadStatic] - //private static T _value; + protected string ItemKey => _itemKey ??= GetType().FullName!; - // yes! flows with async! - private T? NonContextValue - { - get => s_ambientContext.Value ?? default; - set => s_ambientContext.Value = value; - } + // read + // http://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html + // http://stackoverflow.com/questions/14176028/why-does-logicalcallcontext-not-work-with-async + // http://stackoverflow.com/questions/854976/will-values-in-my-threadstatic-variables-still-be-there-when-cycled-via-threadpo + // https://msdn.microsoft.com/en-us/library/dd642243.aspx?f=255&MSPPError=-2147217396 ThreadLocal + // http://stackoverflow.com/questions/29001266/cleaning-up-callcontext-in-tpl clearing call context + // + // anything that is ThreadStatic will stay with the thread and NOT flow in async threads + // the only thing that flows is the logical call context (safe in 4.5+) - protected HybridAccessorBase(IRequestCache requestCache) - => _requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache)); + // no! + //[ThreadStatic] + //private static T _value; - protected T? Value + // yes! flows with async! + private T? NonContextValue + { + get => s_ambientContext.Value ?? default; + set => s_ambientContext.Value = value; + } + + protected T? Value + { + get { - get + if (!_requestCache.IsAvailable) { - if (!_requestCache.IsAvailable) - { - return NonContextValue; - } - return (T?) _requestCache.Get(ItemKey); + return NonContextValue; } - set + return (T?)_requestCache.Get(ItemKey); + } + + set + { + if (!_requestCache.IsAvailable) + { + NonContextValue = value; + } + else if (value == null) + { + _requestCache.Remove(ItemKey); + } + else { - if (!_requestCache.IsAvailable) - { - NonContextValue = value; - } - else if (value == null) - { - _requestCache.Remove(ItemKey); - } - else - { - _requestCache.Set(ItemKey, value); - } + _requestCache.Set(ItemKey, value); } } } diff --git a/src/Umbraco.Core/HybridEventMessagesAccessor.cs b/src/Umbraco.Core/HybridEventMessagesAccessor.cs index 14fa0433ce04..d129b9a117e1 100644 --- a/src/Umbraco.Core/HybridEventMessagesAccessor.cs +++ b/src/Umbraco.Core/HybridEventMessagesAccessor.cs @@ -1,19 +1,18 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public class HybridEventMessagesAccessor : HybridAccessorBase, IEventMessagesAccessor { - public class HybridEventMessagesAccessor : HybridAccessorBase, IEventMessagesAccessor + public HybridEventMessagesAccessor(IRequestCache requestCache) + : base(requestCache) { - public HybridEventMessagesAccessor(IRequestCache requestCache) - : base(requestCache) - { - } + } - public EventMessages? EventMessages - { - get { return Value; } - set { Value = value; } - } + public EventMessages? EventMessages + { + get => Value; + set => Value = value; } } diff --git a/src/Umbraco.Core/IBackOfficeInfo.cs b/src/Umbraco.Core/IBackOfficeInfo.cs index 66f5d97bd9e5..bc27eb7f1649 100644 --- a/src/Umbraco.Core/IBackOfficeInfo.cs +++ b/src/Umbraco.Core/IBackOfficeInfo.cs @@ -1,10 +1,10 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public interface IBackOfficeInfo { - public interface IBackOfficeInfo - { - /// - /// Gets the absolute url to the Umbraco Backoffice. This info can be used to build absolute urls for Backoffice to use in mails etc. - /// - string GetAbsoluteUrl { get; } - } + /// + /// Gets the absolute url to the Umbraco Backoffice. This info can be used to build absolute urls for Backoffice to use + /// in mails etc. + /// + string GetAbsoluteUrl { get; } } diff --git a/src/Umbraco.Core/ICompletable.cs b/src/Umbraco.Core/ICompletable.cs index 2061723575ec..53eebe667f1f 100644 --- a/src/Umbraco.Core/ICompletable.cs +++ b/src/Umbraco.Core/ICompletable.cs @@ -1,9 +1,6 @@ -using System; +namespace Umbraco.Cms.Core; -namespace Umbraco.Cms.Core +public interface ICompletable : IDisposable { - public interface ICompletable : IDisposable - { - void Complete(); - } + void Complete(); } diff --git a/src/Umbraco.Core/IO/CleanFolderResult.cs b/src/Umbraco.Core/IO/CleanFolderResult.cs index d2bed317a66e..2cba7d0d4d65 100644 --- a/src/Umbraco.Core/IO/CleanFolderResult.cs +++ b/src/Umbraco.Core/IO/CleanFolderResult.cs @@ -1,49 +1,33 @@ -using System; -using System.Collections.Generic; -using System.IO; +namespace Umbraco.Cms.Core.IO; -namespace Umbraco.Cms.Core.IO +public class CleanFolderResult { - public class CleanFolderResult + private CleanFolderResult() { - private CleanFolderResult() - { - } + } - public CleanFolderResultStatus Status { get; private set; } + public CleanFolderResultStatus Status { get; private set; } - public IReadOnlyCollection? Errors { get; private set; } + public IReadOnlyCollection? Errors { get; private set; } - public static CleanFolderResult Success() - { - return new CleanFolderResult { Status = CleanFolderResultStatus.Success }; - } + public static CleanFolderResult Success() => new CleanFolderResult {Status = CleanFolderResultStatus.Success}; - public static CleanFolderResult FailedAsDoesNotExist() - { - return new CleanFolderResult { Status = CleanFolderResultStatus.FailedAsDoesNotExist }; - } + public static CleanFolderResult FailedAsDoesNotExist() => + new CleanFolderResult {Status = CleanFolderResultStatus.FailedAsDoesNotExist}; - public static CleanFolderResult FailedWithErrors(List errors) - { - return new CleanFolderResult - { - Status = CleanFolderResultStatus.FailedWithException, - Errors = errors.AsReadOnly(), - }; - } + public static CleanFolderResult FailedWithErrors(List errors) => + new CleanFolderResult {Status = CleanFolderResultStatus.FailedWithException, Errors = errors.AsReadOnly()}; - public class Error + public class Error + { + public Error(Exception exception, FileInfo erroringFile) { - public Error(Exception exception, FileInfo erroringFile) - { - Exception = exception; - ErroringFile = erroringFile; - } + Exception = exception; + ErroringFile = erroringFile; + } - public Exception Exception { get; set; } + public Exception Exception { get; set; } - public FileInfo ErroringFile { get; set; } - } + public FileInfo ErroringFile { get; set; } } } diff --git a/src/Umbraco.Core/IO/CleanFolderResultStatus.cs b/src/Umbraco.Core/IO/CleanFolderResultStatus.cs index 3180677acb16..2d394bc52a91 100644 --- a/src/Umbraco.Core/IO/CleanFolderResultStatus.cs +++ b/src/Umbraco.Core/IO/CleanFolderResultStatus.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.IO +namespace Umbraco.Cms.Core.IO; + +public enum CleanFolderResultStatus { - public enum CleanFolderResultStatus - { - Success, - FailedAsDoesNotExist, - FailedWithException - } + Success, + FailedAsDoesNotExist, + FailedWithException } diff --git a/src/Umbraco.Core/IO/DefaultViewContentProvider.cs b/src/Umbraco.Core/IO/DefaultViewContentProvider.cs index e78118da6270..3e284ca7dfaf 100644 --- a/src/Umbraco.Core/IO/DefaultViewContentProvider.cs +++ b/src/Umbraco.Core/IO/DefaultViewContentProvider.cs @@ -1,62 +1,67 @@ using System.Text; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.IO +namespace Umbraco.Cms.Core.IO; + +public class DefaultViewContentProvider : IDefaultViewContentProvider { - public class DefaultViewContentProvider : IDefaultViewContentProvider + public string GetDefaultFileContent(string? layoutPageAlias = null, string? modelClassName = null, + string? modelNamespace = null, string? modelNamespaceAlias = null) { - public string GetDefaultFileContent(string? layoutPageAlias = null, string? modelClassName = null, string? modelNamespace = null, string? modelNamespaceAlias = null) - { - var content = new StringBuilder(); - - if (string.IsNullOrWhiteSpace(modelNamespaceAlias)) - modelNamespaceAlias = "ContentModels"; + var content = new StringBuilder(); - // either - // @inherits Umbraco.Web.Mvc.UmbracoViewPage - // @inherits Umbraco.Web.Mvc.UmbracoViewPage - content.AppendLine("@using Umbraco.Cms.Web.Common.PublishedModels;"); - content.Append("@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage"); - if (modelClassName.IsNullOrWhiteSpace() == false) - { - content.Append("<"); - if (modelNamespace.IsNullOrWhiteSpace() == false) - { - content.Append(modelNamespaceAlias); - content.Append("."); - } - content.Append(modelClassName); - content.Append(">"); - } - content.Append("\r\n"); + if (string.IsNullOrWhiteSpace(modelNamespaceAlias)) + { + modelNamespaceAlias = "ContentModels"; + } - // if required, add - // @using ContentModels = ModelNamespace; - if (modelClassName.IsNullOrWhiteSpace() == false && modelNamespace.IsNullOrWhiteSpace() == false) + // either + // @inherits Umbraco.Web.Mvc.UmbracoViewPage + // @inherits Umbraco.Web.Mvc.UmbracoViewPage + content.AppendLine("@using Umbraco.Cms.Web.Common.PublishedModels;"); + content.Append("@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage"); + if (modelClassName.IsNullOrWhiteSpace() == false) + { + content.Append("<"); + if (modelNamespace.IsNullOrWhiteSpace() == false) { - content.Append("@using "); content.Append(modelNamespaceAlias); - content.Append(" = "); - content.Append(modelNamespace); - content.Append(";\r\n"); + content.Append("."); } - // either - // Layout = null; - // Layout = "layoutPage.cshtml"; - content.Append("@{\r\n\tLayout = "); - if (layoutPageAlias.IsNullOrWhiteSpace()) - { - content.Append("null"); - } - else - { - content.Append("\""); - content.Append(layoutPageAlias); - content.Append(".cshtml\""); - } - content.Append(";\r\n}"); - return content.ToString(); + content.Append(modelClassName); + content.Append(">"); + } + + content.Append("\r\n"); + + // if required, add + // @using ContentModels = ModelNamespace; + if (modelClassName.IsNullOrWhiteSpace() == false && modelNamespace.IsNullOrWhiteSpace() == false) + { + content.Append("@using "); + content.Append(modelNamespaceAlias); + content.Append(" = "); + content.Append(modelNamespace); + content.Append(";\r\n"); } + + // either + // Layout = null; + // Layout = "layoutPage.cshtml"; + content.Append("@{\r\n\tLayout = "); + if (layoutPageAlias.IsNullOrWhiteSpace()) + { + content.Append("null"); + } + else + { + content.Append("\""); + content.Append(layoutPageAlias); + content.Append(".cshtml\""); + } + + content.Append(";\r\n}"); + return content.ToString(); } } diff --git a/src/Umbraco.Core/IO/FileSystemExtensions.cs b/src/Umbraco.Core/IO/FileSystemExtensions.cs index 16ac1b00415e..d758c34ed23c 100644 --- a/src/Umbraco.Core/IO/FileSystemExtensions.cs +++ b/src/Umbraco.Core/IO/FileSystemExtensions.cs @@ -1,112 +1,109 @@ -using System; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Security.Cryptography; using System.Text; -using System.Threading; using Microsoft.Extensions.FileProviders; using Umbraco.Cms.Core.IO; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class FileSystemExtensions { - public static class FileSystemExtensions + public static string GetStreamHash(this Stream fileStream) { - public static string GetStreamHash(this Stream fileStream) + if (fileStream.CanSeek) { - if (fileStream.CanSeek) - { - fileStream.Seek(0, SeekOrigin.Begin); - } + fileStream.Seek(0, SeekOrigin.Begin); + } - using HashAlgorithm alg = SHA1.Create(); + using HashAlgorithm alg = SHA1.Create(); - // create a string output for the hash - var stringBuilder = new StringBuilder(); - var hashedByteArray = alg.ComputeHash(fileStream); - foreach (var b in hashedByteArray) - { - stringBuilder.Append(b.ToString("x2")); - } - return stringBuilder.ToString(); + // create a string output for the hash + var stringBuilder = new StringBuilder(); + var hashedByteArray = alg.ComputeHash(fileStream); + foreach (var b in hashedByteArray) + { + stringBuilder.Append(b.ToString("x2")); } - /// - /// Attempts to open the file at filePath up to maxRetries times, - /// with a thread sleep time of sleepPerRetryInMilliseconds between retries. - /// - public static FileStream OpenReadWithRetry(this FileInfo file, int maxRetries = 5, int sleepPerRetryInMilliseconds = 50) - { - var retries = maxRetries; + return stringBuilder.ToString(); + } - while (retries > 0) + /// + /// Attempts to open the file at filePath up to maxRetries times, + /// with a thread sleep time of sleepPerRetryInMilliseconds between retries. + /// + public static FileStream OpenReadWithRetry(this FileInfo file, int maxRetries = 5, + int sleepPerRetryInMilliseconds = 50) + { + var retries = maxRetries; + + while (retries > 0) + { + try { - try + return File.OpenRead(file.FullName); + } + catch (IOException) + { + retries--; + + if (retries == 0) { - return File.OpenRead(file.FullName); + throw; } - catch(IOException) - { - retries--; - - if (retries == 0) - { - throw; - } - Thread.Sleep(sleepPerRetryInMilliseconds); - } + Thread.Sleep(sleepPerRetryInMilliseconds); } - - throw new ArgumentException("Retries must be greater than zero"); } - public static void CopyFile(this IFileSystem fs, string path, string newPath) - { - using (Stream stream = fs.OpenFile(path)) - { - fs.AddFile(newPath, stream); - } - } + throw new ArgumentException("Retries must be greater than zero"); + } - public static string GetExtension(this IFileSystem fs, string path) + public static void CopyFile(this IFileSystem fs, string path, string newPath) + { + using (Stream stream = fs.OpenFile(path)) { - return Path.GetExtension(fs.GetFullPath(path)); + fs.AddFile(newPath, stream); } + } - public static string GetFileName(this IFileSystem fs, string path) - { - return Path.GetFileName(fs.GetFullPath(path)); - } + public static string GetExtension(this IFileSystem fs, string path) => Path.GetExtension(fs.GetFullPath(path)); - // TODO: Currently this is the only way to do this - public static void CreateFolder(this IFileSystem fs, string folderPath) + public static string GetFileName(this IFileSystem fs, string path) => Path.GetFileName(fs.GetFullPath(path)); + + // TODO: Currently this is the only way to do this + public static void CreateFolder(this IFileSystem fs, string folderPath) + { + var path = fs.GetRelativePath(folderPath); + var tempFile = Path.Combine(path, Guid.NewGuid().ToString("N") + ".tmp"); + using (var s = new MemoryStream()) { - var path = fs.GetRelativePath(folderPath); - var tempFile = Path.Combine(path, Guid.NewGuid().ToString("N") + ".tmp"); - using (var s = new MemoryStream()) - { - fs.AddFile(tempFile, s); - } - fs.DeleteFile(tempFile); + fs.AddFile(tempFile, s); } - /// - /// Creates a new from the file system. - /// - /// The file system. - /// When this method returns, contains an created from the file system. - /// - /// true if the was successfully created; otherwise, false. - /// - public static bool TryCreateFileProvider(this IFileSystem fileSystem, [MaybeNullWhen(false)] out IFileProvider fileProvider) + fs.DeleteFile(tempFile); + } + + /// + /// Creates a new from the file system. + /// + /// The file system. + /// + /// When this method returns, contains an created from the file + /// system. + /// + /// + /// true if the was successfully created; otherwise, false. + /// + public static bool TryCreateFileProvider(this IFileSystem fileSystem, + [MaybeNullWhen(false)] out IFileProvider fileProvider) + { + fileProvider = fileSystem switch { - fileProvider = fileSystem switch - { - IFileProviderFactory fileProviderFactory => fileProviderFactory.Create(), - _ => null - }; + IFileProviderFactory fileProviderFactory => fileProviderFactory.Create(), + _ => null + }; - return fileProvider != null; - } + return fileProvider != null; } } diff --git a/src/Umbraco.Core/IO/FileSystems.cs b/src/Umbraco.Core/IO/FileSystems.cs index 5a4c92d50990..81d3d35e3b39 100644 --- a/src/Umbraco.Core/IO/FileSystems.cs +++ b/src/Umbraco.Core/IO/FileSystems.cs @@ -1,361 +1,375 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Threading; +using System.ComponentModel; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; -namespace Umbraco.Cms.Core.IO +namespace Umbraco.Cms.Core.IO; + +/// +/// Provides the system filesystems. +/// +public sealed class FileSystems { - /// - /// Provides the system filesystems. - /// - public sealed class FileSystems + private static string? _shadowCurrentId; // static - unique!! + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IIOHelper _ioHelper; + private readonly ILogger _logger; + private readonly ILoggerFactory _loggerFactory; + private readonly object _shadowLocker = new(); + + // shadow support + private readonly List _shadowWrappers = new(); + private readonly GlobalSettings _globalSettings; + + + // wrappers for shadow support + private ShadowWrapper? _macroPartialFileSystem; + private ShadowWrapper? _mvcViewsFileSystem; + private ShadowWrapper? _partialViewsFileSystem; + private ShadowWrapper? _scriptsFileSystem; + private ShadowWrapper? _stylesheetsFileSystem; + private bool _wkfsInitialized; + + // well-known file systems lazy initialization + private object _wkfsLock = new(); + private object? _wkfsObject; // unused + + #region Constructor + + // DI wants a public ctor + public FileSystems( + ILoggerFactory loggerFactory, + IIOHelper ioHelper, + IOptions globalSettings, + IHostingEnvironment hostingEnvironment) { - private readonly ILogger _logger; - private readonly ILoggerFactory _loggerFactory; - private readonly IIOHelper _ioHelper; - private GlobalSettings _globalSettings; - private readonly IHostingEnvironment _hostingEnvironment; - - - // wrappers for shadow support - private ShadowWrapper? _macroPartialFileSystem; - private ShadowWrapper? _partialViewsFileSystem; - private ShadowWrapper? _stylesheetsFileSystem; - private ShadowWrapper? _scriptsFileSystem; - private ShadowWrapper? _mvcViewsFileSystem; - - // well-known file systems lazy initialization - private object _wkfsLock = new object(); - private bool _wkfsInitialized; - private object? _wkfsObject; // unused - - // shadow support - private readonly List _shadowWrappers = new List(); - private readonly object _shadowLocker = new object(); - private static string? _shadowCurrentId; // static - unique!! - #region Constructor - - // DI wants a public ctor - public FileSystems( - ILoggerFactory loggerFactory, - IIOHelper ioHelper, - IOptions globalSettings, - IHostingEnvironment hostingEnvironment) - { - _logger = loggerFactory.CreateLogger(); - _loggerFactory = loggerFactory; - _ioHelper = ioHelper; - _globalSettings = globalSettings.Value; - _hostingEnvironment = hostingEnvironment; - } - - // Ctor for tests, allows you to set the various filesystems - internal FileSystems( - ILoggerFactory loggerFactory, - IIOHelper ioHelper, - IOptions globalSettings, - IHostingEnvironment hostingEnvironment, - IFileSystem macroPartialFileSystem, - IFileSystem partialViewsFileSystem, - IFileSystem stylesheetFileSystem, - IFileSystem scriptsFileSystem, - IFileSystem mvcViewFileSystem) : this(loggerFactory, ioHelper, globalSettings, hostingEnvironment) - { - _macroPartialFileSystem = CreateShadowWrapperInternal(macroPartialFileSystem, "macro-partials"); - _partialViewsFileSystem = CreateShadowWrapperInternal(partialViewsFileSystem, "partials"); - _stylesheetsFileSystem = CreateShadowWrapperInternal(stylesheetFileSystem, "css"); - _scriptsFileSystem = CreateShadowWrapperInternal(scriptsFileSystem, "scripts"); - _mvcViewsFileSystem = CreateShadowWrapperInternal(mvcViewFileSystem, "view"); - // Set initialized to true so the filesystems doesn't get overwritten. - _wkfsInitialized = true; + _logger = loggerFactory.CreateLogger(); + _loggerFactory = loggerFactory; + _ioHelper = ioHelper; + _globalSettings = globalSettings.Value; + _hostingEnvironment = hostingEnvironment; + } - } + // Ctor for tests, allows you to set the various filesystems + internal FileSystems( + ILoggerFactory loggerFactory, + IIOHelper ioHelper, + IOptions globalSettings, + IHostingEnvironment hostingEnvironment, + IFileSystem macroPartialFileSystem, + IFileSystem partialViewsFileSystem, + IFileSystem stylesheetFileSystem, + IFileSystem scriptsFileSystem, + IFileSystem mvcViewFileSystem) : this(loggerFactory, ioHelper, globalSettings, hostingEnvironment) + { + _macroPartialFileSystem = CreateShadowWrapperInternal(macroPartialFileSystem, "macro-partials"); + _partialViewsFileSystem = CreateShadowWrapperInternal(partialViewsFileSystem, "partials"); + _stylesheetsFileSystem = CreateShadowWrapperInternal(stylesheetFileSystem, "css"); + _scriptsFileSystem = CreateShadowWrapperInternal(scriptsFileSystem, "scripts"); + _mvcViewsFileSystem = CreateShadowWrapperInternal(mvcViewFileSystem, "view"); + // Set initialized to true so the filesystems doesn't get overwritten. + _wkfsInitialized = true; + } - /// - /// Used be Scope provider to take control over the filesystems, should never be used for anything else. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public Func? IsScoped { get; set; } = () => false; + /// + /// Used be Scope provider to take control over the filesystems, should never be used for anything else. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public Func? IsScoped { get; set; } = () => false; - #endregion + #endregion - #region Well-Known FileSystems + #region Well-Known FileSystems - /// - /// Gets the macro partials filesystem. - /// - public IFileSystem? MacroPartialsFileSystem + /// + /// Gets the macro partials filesystem. + /// + public IFileSystem? MacroPartialsFileSystem + { + get { - get + if (Volatile.Read(ref _wkfsInitialized) == false) { - if (Volatile.Read(ref _wkfsInitialized) == false) - { - EnsureWellKnownFileSystems(); - } - - return _macroPartialFileSystem; + EnsureWellKnownFileSystems(); } - } - /// - /// Gets the partial views filesystem. - /// - public IFileSystem? PartialViewsFileSystem - { - get - { - if (Volatile.Read(ref _wkfsInitialized) == false) - { - EnsureWellKnownFileSystems(); - } - - return _partialViewsFileSystem; - } + return _macroPartialFileSystem; } + } - /// - /// Gets the stylesheets filesystem. - /// - public IFileSystem? StylesheetsFileSystem + /// + /// Gets the partial views filesystem. + /// + public IFileSystem? PartialViewsFileSystem + { + get { - get + if (Volatile.Read(ref _wkfsInitialized) == false) { - if (Volatile.Read(ref _wkfsInitialized) == false) - { - EnsureWellKnownFileSystems(); - } - - return _stylesheetsFileSystem; + EnsureWellKnownFileSystems(); } + + return _partialViewsFileSystem; } + } - /// - /// Gets the scripts filesystem. - /// - public IFileSystem? ScriptsFileSystem + /// + /// Gets the stylesheets filesystem. + /// + public IFileSystem? StylesheetsFileSystem + { + get { - get + if (Volatile.Read(ref _wkfsInitialized) == false) { - if (Volatile.Read(ref _wkfsInitialized) == false) - { - EnsureWellKnownFileSystems(); - } - - return _scriptsFileSystem; + EnsureWellKnownFileSystems(); } + + return _stylesheetsFileSystem; } + } - /// - /// Gets the MVC views filesystem. - /// - public IFileSystem? MvcViewsFileSystem + /// + /// Gets the scripts filesystem. + /// + public IFileSystem? ScriptsFileSystem + { + get { - get + if (Volatile.Read(ref _wkfsInitialized) == false) { - if (Volatile.Read(ref _wkfsInitialized) == false) - { - EnsureWellKnownFileSystems(); - } - - return _mvcViewsFileSystem; + EnsureWellKnownFileSystems(); } + + return _scriptsFileSystem; } + } - /// - /// Sets the stylesheet filesystem. - /// - /// - /// Be careful when using this, the root path and root url must be correct for this to work. - /// - /// The . - /// If the is null - /// Throws exception if the StylesheetFileSystem has already been initialized. - /// Throws exception if full path can't be resolved successfully. - public void SetStylesheetFilesystem(IFileSystem fileSystem) + /// + /// Gets the MVC views filesystem. + /// + public IFileSystem? MvcViewsFileSystem + { + get { - if (fileSystem == null) - { - throw new ArgumentNullException(nameof(fileSystem)); - } - - if (_stylesheetsFileSystem != null) + if (Volatile.Read(ref _wkfsInitialized) == false) { - throw new InvalidOperationException( - "The StylesheetFileSystem cannot be changed when it's already been initialized."); + EnsureWellKnownFileSystems(); } - // Verify that _rootUrl/_rootPath is correct - // We have to do this because there's a tight coupling - // to the VirtualPath we get with CodeFileDisplay from the frontend. - try - { - var rootPath = fileSystem.GetFullPath("/css/"); - } - catch (UnauthorizedAccessException exception) - { - throw new UnauthorizedAccessException( - "Can't register the stylesheet filesystem, " - + "this is most likely caused by using a PhysicalFileSystem with an incorrect " - + "rootPath/rootUrl. RootPath must be \\wwwroot\\css" - + " and rootUrl must be /css", exception); - } + return _mvcViewsFileSystem; + } + } - _stylesheetsFileSystem = CreateShadowWrapperInternal(fileSystem, "css"); + /// + /// Sets the stylesheet filesystem. + /// + /// + /// Be careful when using this, the root path and root url must be correct for this to work. + /// + /// The . + /// If the is null + /// Throws exception if the StylesheetFileSystem has already been initialized. + /// Throws exception if full path can't be resolved successfully. + public void SetStylesheetFilesystem(IFileSystem fileSystem) + { + if (fileSystem == null) + { + throw new ArgumentNullException(nameof(fileSystem)); } - private void EnsureWellKnownFileSystems() => LazyInitializer.EnsureInitialized(ref _wkfsObject, ref _wkfsInitialized, ref _wkfsLock, CreateWellKnownFileSystems); + if (_stylesheetsFileSystem != null) + { + throw new InvalidOperationException( + "The StylesheetFileSystem cannot be changed when it's already been initialized."); + } - // need to return something to LazyInitializer.EnsureInitialized - // but it does not really matter what we return - here, null - private object? CreateWellKnownFileSystems() + // Verify that _rootUrl/_rootPath is correct + // We have to do this because there's a tight coupling + // to the VirtualPath we get with CodeFileDisplay from the frontend. + try + { + var rootPath = fileSystem.GetFullPath("/css/"); + } + catch (UnauthorizedAccessException exception) { - var logger = _loggerFactory.CreateLogger(); + throw new UnauthorizedAccessException( + "Can't register the stylesheet filesystem, " + + "this is most likely caused by using a PhysicalFileSystem with an incorrect " + + "rootPath/rootUrl. RootPath must be \\wwwroot\\css" + + " and rootUrl must be /css", exception); + } - //TODO this is fucked, why do PhysicalFileSystem has a root url? Mvc views cannot be accessed by url! - var macroPartialFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MacroPartials), _hostingEnvironment.ToAbsolute(Constants.SystemDirectories.MacroPartials)); - var partialViewsFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.PartialViews), _hostingEnvironment.ToAbsolute(Constants.SystemDirectories.PartialViews)); - var scriptsFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, _hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoScriptsPath), _hostingEnvironment.ToAbsolute(_globalSettings.UmbracoScriptsPath)); - var mvcViewsFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MvcViews), _hostingEnvironment.ToAbsolute(Constants.SystemDirectories.MvcViews)); + _stylesheetsFileSystem = CreateShadowWrapperInternal(fileSystem, "css"); + } - _macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, _ioHelper, _hostingEnvironment, _loggerFactory, "macro-partials", IsScoped); - _partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, _ioHelper, _hostingEnvironment, _loggerFactory, "partials", IsScoped); - _scriptsFileSystem = new ShadowWrapper(scriptsFileSystem, _ioHelper, _hostingEnvironment, _loggerFactory, "scripts", IsScoped); - _mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, _ioHelper, _hostingEnvironment, _loggerFactory, "views", IsScoped); + private void EnsureWellKnownFileSystems() => LazyInitializer.EnsureInitialized(ref _wkfsObject, + ref _wkfsInitialized, ref _wkfsLock, CreateWellKnownFileSystems); - if (_stylesheetsFileSystem == null) - { - var stylesheetsFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, - _hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoCssPath), - _hostingEnvironment.ToAbsolute(_globalSettings.UmbracoCssPath)); + // need to return something to LazyInitializer.EnsureInitialized + // but it does not really matter what we return - here, null + private object? CreateWellKnownFileSystems() + { + ILogger logger = _loggerFactory.CreateLogger(); + + //TODO this is fucked, why do PhysicalFileSystem has a root url? Mvc views cannot be accessed by url! + var macroPartialFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, + _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MacroPartials), + _hostingEnvironment.ToAbsolute(Constants.SystemDirectories.MacroPartials)); + var partialViewsFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, + _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.PartialViews), + _hostingEnvironment.ToAbsolute(Constants.SystemDirectories.PartialViews)); + var scriptsFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, + _hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoScriptsPath), + _hostingEnvironment.ToAbsolute(_globalSettings.UmbracoScriptsPath)); + var mvcViewsFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, + _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MvcViews), + _hostingEnvironment.ToAbsolute(Constants.SystemDirectories.MvcViews)); + + _macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, _ioHelper, _hostingEnvironment, + _loggerFactory, "macro-partials", IsScoped); + _partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, _ioHelper, _hostingEnvironment, + _loggerFactory, "partials", IsScoped); + _scriptsFileSystem = new ShadowWrapper(scriptsFileSystem, _ioHelper, _hostingEnvironment, _loggerFactory, + "scripts", IsScoped); + _mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, _ioHelper, _hostingEnvironment, _loggerFactory, + "views", IsScoped); + + if (_stylesheetsFileSystem == null) + { + var stylesheetsFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, + _hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoCssPath), + _hostingEnvironment.ToAbsolute(_globalSettings.UmbracoCssPath)); - _stylesheetsFileSystem = new ShadowWrapper(stylesheetsFileSystem, _ioHelper, _hostingEnvironment, _loggerFactory, "css", IsScoped); + _stylesheetsFileSystem = new ShadowWrapper(stylesheetsFileSystem, _ioHelper, _hostingEnvironment, + _loggerFactory, "css", IsScoped); - _shadowWrappers.Add(_stylesheetsFileSystem); - } + _shadowWrappers.Add(_stylesheetsFileSystem); + } - // TODO: do we need a lock here? - _shadowWrappers.Add(_macroPartialFileSystem); - _shadowWrappers.Add(_partialViewsFileSystem); - _shadowWrappers.Add(_scriptsFileSystem); - _shadowWrappers.Add(_mvcViewsFileSystem); + // TODO: do we need a lock here? + _shadowWrappers.Add(_macroPartialFileSystem); + _shadowWrappers.Add(_partialViewsFileSystem); + _shadowWrappers.Add(_scriptsFileSystem); + _shadowWrappers.Add(_mvcViewsFileSystem); - return null; - } + return null; + } - #endregion + #endregion - #region Shadow + #region Shadow - // note - // shadowing is thread-safe, but entering and exiting shadow mode is not, and there is only one - // global shadow for the entire application, so great care should be taken to ensure that the - // application is *not* doing anything else when using a shadow. + // note + // shadowing is thread-safe, but entering and exiting shadow mode is not, and there is only one + // global shadow for the entire application, so great care should be taken to ensure that the + // application is *not* doing anything else when using a shadow. - /// - /// Shadows the filesystem, should never be used outside the Scope class. - /// - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public ICompletable Shadow() + /// + /// Shadows the filesystem, should never be used outside the Scope class. + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public ICompletable Shadow() + { + if (Volatile.Read(ref _wkfsInitialized) == false) { - if (Volatile.Read(ref _wkfsInitialized) == false) - { - EnsureWellKnownFileSystems(); - } - - var id = ShadowWrapper.CreateShadowId(_hostingEnvironment); - return new ShadowFileSystems(this, id); // will invoke BeginShadow and EndShadow + EnsureWellKnownFileSystems(); } - internal void BeginShadow(string id) + var id = ShadowWrapper.CreateShadowId(_hostingEnvironment); + return new ShadowFileSystems(this, id); // will invoke BeginShadow and EndShadow + } + + internal void BeginShadow(string id) + { + lock (_shadowLocker) { - lock (_shadowLocker) + // if we throw here, it means that something very wrong happened. + if (_shadowCurrentId != null) { - // if we throw here, it means that something very wrong happened. - if (_shadowCurrentId != null) - { - throw new InvalidOperationException("Already shadowing."); - } + throw new InvalidOperationException("Already shadowing."); + } - _shadowCurrentId = id; + _shadowCurrentId = id; - _logger.LogDebug("Shadow '{ShadowId}'", _shadowCurrentId); + _logger.LogDebug("Shadow '{ShadowId}'", _shadowCurrentId); - foreach (ShadowWrapper wrapper in _shadowWrappers) - { - wrapper.Shadow(_shadowCurrentId); - } + foreach (ShadowWrapper wrapper in _shadowWrappers) + { + wrapper.Shadow(_shadowCurrentId); } } + } - internal void EndShadow(string id, bool completed) + internal void EndShadow(string id, bool completed) + { + lock (_shadowLocker) { - lock (_shadowLocker) + // if we throw here, it means that something very wrong happened. + if (_shadowCurrentId == null) { - // if we throw here, it means that something very wrong happened. - if (_shadowCurrentId == null) - { - throw new InvalidOperationException("Not shadowing."); - } + throw new InvalidOperationException("Not shadowing."); + } - if (id != _shadowCurrentId) - { - throw new InvalidOperationException("Not the current shadow."); - } + if (id != _shadowCurrentId) + { + throw new InvalidOperationException("Not the current shadow."); + } - _logger.LogDebug("UnShadow '{ShadowId}' {Status}", id, completed ? "complete" : "abort"); + _logger.LogDebug("UnShadow '{ShadowId}' {Status}", id, completed ? "complete" : "abort"); - var exceptions = new List(); - foreach (ShadowWrapper wrapper in _shadowWrappers) + var exceptions = new List(); + foreach (ShadowWrapper wrapper in _shadowWrappers) + { + try { - try - { - // this may throw an AggregateException if some of the changes could not be applied - wrapper.UnShadow(completed); - } - catch (AggregateException ae) - { - exceptions.Add(ae); - } + // this may throw an AggregateException if some of the changes could not be applied + wrapper.UnShadow(completed); } - - _shadowCurrentId = null; - - if (exceptions.Count > 0) + catch (AggregateException ae) { - throw new AggregateException(completed ? "Failed to apply all changes (see exceptions)." : "Failed to abort (see exceptions).", exceptions); + exceptions.Add(ae); } } - } - /// - /// Creates a shadow wrapper for a filesystem, should never be used outside UmbracoBuilder or testing - /// - /// - /// - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IFileSystem CreateShadowWrapper(IFileSystem filesystem, string shadowPath) => CreateShadowWrapperInternal(filesystem, shadowPath); + _shadowCurrentId = null; - private ShadowWrapper CreateShadowWrapperInternal(IFileSystem filesystem, string shadowPath) - { - lock (_shadowLocker) + if (exceptions.Count > 0) { - var wrapper = new ShadowWrapper(filesystem, _ioHelper, _hostingEnvironment, _loggerFactory, shadowPath,() => IsScoped?.Invoke()); - if (_shadowCurrentId != null) - { - wrapper.Shadow(_shadowCurrentId); - } - - _shadowWrappers.Add(wrapper); - return wrapper; + throw new AggregateException( + completed ? "Failed to apply all changes (see exceptions)." : "Failed to abort (see exceptions).", + exceptions); } } + } + + /// + /// Creates a shadow wrapper for a filesystem, should never be used outside UmbracoBuilder or testing + /// + /// + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public IFileSystem CreateShadowWrapper(IFileSystem filesystem, string shadowPath) => + CreateShadowWrapperInternal(filesystem, shadowPath); + + private ShadowWrapper CreateShadowWrapperInternal(IFileSystem filesystem, string shadowPath) + { + lock (_shadowLocker) + { + var wrapper = new ShadowWrapper(filesystem, _ioHelper, _hostingEnvironment, _loggerFactory, shadowPath, + () => IsScoped?.Invoke()); + if (_shadowCurrentId != null) + { + wrapper.Shadow(_shadowCurrentId); + } - #endregion + _shadowWrappers.Add(wrapper); + return wrapper; + } } + + #endregion } diff --git a/src/Umbraco.Core/IO/IDefaultViewContentProvider.cs b/src/Umbraco.Core/IO/IDefaultViewContentProvider.cs index a2937f3f8e75..5739024ebe90 100644 --- a/src/Umbraco.Core/IO/IDefaultViewContentProvider.cs +++ b/src/Umbraco.Core/IO/IDefaultViewContentProvider.cs @@ -1,8 +1,7 @@ -namespace Umbraco.Cms.Core.IO +namespace Umbraco.Cms.Core.IO; + +public interface IDefaultViewContentProvider { - public interface IDefaultViewContentProvider - { - string GetDefaultFileContent(string? layoutPageAlias = null, string? modelClassName = null, - string? modelNamespace = null, string? modelNamespaceAlias = null); - } + string GetDefaultFileContent(string? layoutPageAlias = null, string? modelClassName = null, + string? modelNamespace = null, string? modelNamespaceAlias = null); } diff --git a/src/Umbraco.Core/IO/IFileProviderFactory.cs b/src/Umbraco.Core/IO/IFileProviderFactory.cs index 981d5558fc2c..0e6cb0f0a8f2 100644 --- a/src/Umbraco.Core/IO/IFileProviderFactory.cs +++ b/src/Umbraco.Core/IO/IFileProviderFactory.cs @@ -1,18 +1,17 @@ using Microsoft.Extensions.FileProviders; -namespace Umbraco.Cms.Core.IO +namespace Umbraco.Cms.Core.IO; + +/// +/// Factory for creating instances. +/// +public interface IFileProviderFactory { /// - /// Factory for creating instances. + /// Creates a new instance. /// - public interface IFileProviderFactory - { - /// - /// Creates a new instance. - /// - /// - /// The newly created instance (or null if not supported). - /// - IFileProvider? Create(); - } + /// + /// The newly created instance (or null if not supported). + /// + IFileProvider? Create(); } diff --git a/src/Umbraco.Core/IO/IFileSystem.cs b/src/Umbraco.Core/IO/IFileSystem.cs index 54503b167b99..aa208afb3df6 100644 --- a/src/Umbraco.Core/IO/IFileSystem.cs +++ b/src/Umbraco.Core/IO/IFileSystem.cs @@ -1,178 +1,177 @@ -using System; -using System.Collections.Generic; -using System.IO; +namespace Umbraco.Cms.Core.IO; -namespace Umbraco.Cms.Core.IO +/// +/// Provides methods allowing the manipulation of files. +/// +public interface IFileSystem { /// - /// Provides methods allowing the manipulation of files. - /// - public interface IFileSystem - { - /// - /// Gets all directories matching the given path. - /// - /// The path to the directories. - /// - /// The representing the matched directories. - /// - IEnumerable GetDirectories(string path); - - /// - /// Deletes the specified directory. - /// - /// The name of the directory to remove. - void DeleteDirectory(string path); - - /// - /// Deletes the specified directory and, if indicated, any subdirectories and files in the directory. - /// - /// Azure blob storage has no real concept of directories so deletion is always recursive. - /// The name of the directory to remove. - /// Whether to remove directories, subdirectories, and files in path. - void DeleteDirectory(string path, bool recursive); - - /// - /// Determines whether the specified directory exists. - /// - /// The directory to check. - /// - /// True if the directory exists and the user has permission to view it; otherwise false. - /// - bool DirectoryExists(string path); - - /// - /// Adds a file to the file system. - /// - /// The path to the given file. - /// The containing the file contents. - void AddFile(string path, Stream stream); - - /// - /// Adds a file to the file system. - /// - /// The path to the given file. - /// The containing the file contents. - /// Whether to override the file if it already exists. - void AddFile(string path, Stream stream, bool overrideIfExists); - - /// - /// Gets all files matching the given path. - /// - /// The path to the files. - /// - /// The representing the matched files. - /// - IEnumerable GetFiles(string path); - - /// - /// Gets all files matching the given path and filter. - /// - /// The path to the files. - /// A filter that allows the querying of file extension. *.jpg - /// - /// The representing the matched files. - /// - IEnumerable GetFiles(string path, string filter); - - /// - /// Gets a representing the file at the given path. - /// - /// The path to the file. - /// - /// . - /// - Stream OpenFile(string path); - - /// - /// Deletes the specified file. - /// - /// The name of the file to remove. - void DeleteFile(string path); - - /// - /// Determines whether the specified file exists. - /// - /// The file to check. - /// - /// True if the file exists and the user has permission to view it; otherwise false. - /// - bool FileExists(string path); - - /// - /// Returns the application relative path to the file. - /// - /// The full path or URL. - /// - /// The representing the relative path. - /// - string GetRelativePath(string fullPathOrUrl); - - /// - /// Gets the full qualified path to the file. - /// - /// The file to return the full path for. - /// - /// The representing the full path. - /// - string GetFullPath(string path); - - /// - /// Returns the application relative URL to the file. - /// - /// The path to return the URL for. - /// - /// representing the relative URL. - /// - string GetUrl(string? path); - - /// - /// Gets the last modified date/time of the file, expressed as a UTC value. - /// - /// The path to the file. - /// - /// . - /// - DateTimeOffset GetLastModified(string path); - - /// - /// Gets the created date/time of the file, expressed as a UTC value. - /// - /// The path to the file. - /// - /// . - /// - DateTimeOffset GetCreated(string path); - - /// - /// Gets the size of a file. - /// - /// The path to the file. - /// The size (in bytes) of the file. - long GetSize(string path); - - /// - /// Gets a value indicating whether the filesystem can add/copy - /// a file which is on a physical filesystem. - /// - /// In other words, whether the filesystem can copy/move a file - /// that is on local disk, in a fast and efficient way. - bool CanAddPhysical { get; } - - /// - /// Adds a file which is on a physical filesystem. - /// - /// The path to the file. - /// The absolute physical path to the source file. - /// A value indicating what to do if the file already exists. - /// A value indicating whether to move (default) or copy. - void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false); - - // TODO: implement these - // - //void CreateDirectory(string path); - // - //// move or rename, directory or file - //void Move(string source, string target); - } + /// Gets a value indicating whether the filesystem can add/copy + /// a file which is on a physical filesystem. + /// + /// + /// In other words, whether the filesystem can copy/move a file + /// that is on local disk, in a fast and efficient way. + /// + bool CanAddPhysical { get; } + + /// + /// Gets all directories matching the given path. + /// + /// The path to the directories. + /// + /// The representing the matched directories. + /// + IEnumerable GetDirectories(string path); + + /// + /// Deletes the specified directory. + /// + /// The name of the directory to remove. + void DeleteDirectory(string path); + + /// + /// Deletes the specified directory and, if indicated, any subdirectories and files in the directory. + /// + /// Azure blob storage has no real concept of directories so deletion is always recursive. + /// The name of the directory to remove. + /// Whether to remove directories, subdirectories, and files in path. + void DeleteDirectory(string path, bool recursive); + + /// + /// Determines whether the specified directory exists. + /// + /// The directory to check. + /// + /// True if the directory exists and the user has permission to view it; otherwise false. + /// + bool DirectoryExists(string path); + + /// + /// Adds a file to the file system. + /// + /// The path to the given file. + /// The containing the file contents. + void AddFile(string path, Stream stream); + + /// + /// Adds a file to the file system. + /// + /// The path to the given file. + /// The containing the file contents. + /// Whether to override the file if it already exists. + void AddFile(string path, Stream stream, bool overrideIfExists); + + /// + /// Gets all files matching the given path. + /// + /// The path to the files. + /// + /// The representing the matched files. + /// + IEnumerable GetFiles(string path); + + /// + /// Gets all files matching the given path and filter. + /// + /// The path to the files. + /// A filter that allows the querying of file extension. + /// *.jpg + /// + /// + /// The representing the matched files. + /// + IEnumerable GetFiles(string path, string filter); + + /// + /// Gets a representing the file at the given path. + /// + /// The path to the file. + /// + /// . + /// + Stream OpenFile(string path); + + /// + /// Deletes the specified file. + /// + /// The name of the file to remove. + void DeleteFile(string path); + + /// + /// Determines whether the specified file exists. + /// + /// The file to check. + /// + /// True if the file exists and the user has permission to view it; otherwise false. + /// + bool FileExists(string path); + + /// + /// Returns the application relative path to the file. + /// + /// The full path or URL. + /// + /// The representing the relative path. + /// + string GetRelativePath(string fullPathOrUrl); + + /// + /// Gets the full qualified path to the file. + /// + /// The file to return the full path for. + /// + /// The representing the full path. + /// + string GetFullPath(string path); + + /// + /// Returns the application relative URL to the file. + /// + /// The path to return the URL for. + /// + /// representing the relative URL. + /// + string GetUrl(string? path); + + /// + /// Gets the last modified date/time of the file, expressed as a UTC value. + /// + /// The path to the file. + /// + /// . + /// + DateTimeOffset GetLastModified(string path); + + /// + /// Gets the created date/time of the file, expressed as a UTC value. + /// + /// The path to the file. + /// + /// . + /// + DateTimeOffset GetCreated(string path); + + /// + /// Gets the size of a file. + /// + /// The path to the file. + /// The size (in bytes) of the file. + long GetSize(string path); + + /// + /// Adds a file which is on a physical filesystem. + /// + /// The path to the file. + /// The absolute physical path to the source file. + /// A value indicating what to do if the file already exists. + /// A value indicating whether to move (default) or copy. + void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false); + + // TODO: implement these + // + //void CreateDirectory(string path); + // + //// move or rename, directory or file + //void Move(string source, string target); } diff --git a/src/Umbraco.Core/IO/IIOHelper.cs b/src/Umbraco.Core/IO/IIOHelper.cs index 5a814ab386db..53376dd48b2f 100644 --- a/src/Umbraco.Core/IO/IIOHelper.cs +++ b/src/Umbraco.Core/IO/IIOHelper.cs @@ -1,73 +1,68 @@ -using System; -using System.Collections.Generic; -using System.IO; +namespace Umbraco.Cms.Core.IO; -namespace Umbraco.Cms.Core.IO +public interface IIOHelper { - public interface IIOHelper - { - string FindFile(string virtualPath); + string FindFile(string virtualPath); - [Obsolete("Use IHostingEnvironment.ToAbsolute instead")] - string ResolveUrl(string virtualPath); + [Obsolete("Use IHostingEnvironment.ToAbsolute instead")] + string ResolveUrl(string virtualPath); - /// - /// Maps a virtual path to a physical path in the content root folder (i.e. www) - /// - /// - /// - [Obsolete("Use IHostingEnvironment.MapPathContentRoot or IHostingEnvironment.MapPathWebRoot instead")] - string MapPath(string path); + /// + /// Maps a virtual path to a physical path in the content root folder (i.e. www) + /// + /// + /// + [Obsolete("Use IHostingEnvironment.MapPathContentRoot or IHostingEnvironment.MapPathWebRoot instead")] + string MapPath(string path); - /// - /// Verifies that the current filepath matches a directory where the user is allowed to edit a file. - /// - /// The filepath to validate. - /// The valid directory. - /// A value indicating whether the filepath is valid. - bool VerifyEditPath(string filePath, string validDir); + /// + /// Verifies that the current filepath matches a directory where the user is allowed to edit a file. + /// + /// The filepath to validate. + /// The valid directory. + /// A value indicating whether the filepath is valid. + bool VerifyEditPath(string filePath, string validDir); - /// - /// Verifies that the current filepath matches one of several directories where the user is allowed to edit a file. - /// - /// The filepath to validate. - /// The valid directories. - /// A value indicating whether the filepath is valid. - bool VerifyEditPath(string filePath, IEnumerable validDirs); + /// + /// Verifies that the current filepath matches one of several directories where the user is allowed to edit a file. + /// + /// The filepath to validate. + /// The valid directories. + /// A value indicating whether the filepath is valid. + bool VerifyEditPath(string filePath, IEnumerable validDirs); - /// - /// Verifies that the current filepath has one of several authorized extensions. - /// - /// The filepath to validate. - /// The valid extensions. - /// A value indicating whether the filepath is valid. - bool VerifyFileExtension(string filePath, IEnumerable validFileExtensions); + /// + /// Verifies that the current filepath has one of several authorized extensions. + /// + /// The filepath to validate. + /// The valid extensions. + /// A value indicating whether the filepath is valid. + bool VerifyFileExtension(string filePath, IEnumerable validFileExtensions); - bool PathStartsWith(string path, string root, params char[] separators); + bool PathStartsWith(string path, string root, params char[] separators); - void EnsurePathExists(string path); + void EnsurePathExists(string path); - /// - /// Get properly formatted relative path from an existing absolute or relative path - /// - /// - /// - string GetRelativePath(string path); + /// + /// Get properly formatted relative path from an existing absolute or relative path + /// + /// + /// + string GetRelativePath(string path); - /// - /// Retrieves array of temporary folders from the hosting environment. - /// - /// Array of instances. - DirectoryInfo[] GetTempFolders(); + /// + /// Retrieves array of temporary folders from the hosting environment. + /// + /// Array of instances. + DirectoryInfo[] GetTempFolders(); - /// - /// Cleans contents of a folder by deleting all files older that the provided age. - /// If deletition of any file errors (e.g. due to a file lock) the process will continue to try to delete all that it can. - /// - /// Folder to clean. - /// Age of files within folder to delete. - /// Result of operation - CleanFolderResult CleanFolder(DirectoryInfo folder, TimeSpan age); - - } + /// + /// Cleans contents of a folder by deleting all files older that the provided age. + /// If deletition of any file errors (e.g. due to a file lock) the process will continue to try to delete all that it + /// can. + /// + /// Folder to clean. + /// Age of files within folder to delete. + /// Result of operation + CleanFolderResult CleanFolder(DirectoryInfo folder, TimeSpan age); } diff --git a/src/Umbraco.Core/IO/IMediaPathScheme.cs b/src/Umbraco.Core/IO/IMediaPathScheme.cs index da9a06d1b115..70ed6c7a3bd0 100644 --- a/src/Umbraco.Core/IO/IMediaPathScheme.cs +++ b/src/Umbraco.Core/IO/IMediaPathScheme.cs @@ -1,33 +1,29 @@ -using System; +namespace Umbraco.Cms.Core.IO; -namespace Umbraco.Cms.Core.IO +/// +/// Represents a media file path scheme. +/// +public interface IMediaPathScheme { /// - /// Represents a media file path scheme. + /// Gets a media file path. /// - public interface IMediaPathScheme - { - /// - /// Gets a media file path. - /// - /// The media filesystem. - /// The (content, media) item unique identifier. - /// The property type unique identifier. - /// The file name. - /// - /// The filesystem-relative complete file path. - string GetFilePath(MediaFileManager fileManager, Guid itemGuid, Guid propertyGuid, string filename); + /// The media filesystem. + /// The (content, media) item unique identifier. + /// The property type unique identifier. + /// The file name. + /// The filesystem-relative complete file path. + string GetFilePath(MediaFileManager fileManager, Guid itemGuid, Guid propertyGuid, string filename); - /// - /// Gets the directory that can be deleted when the file is deleted. - /// - /// The media filesystem. - /// The filesystem-relative path of the file. - /// The filesystem-relative path of the directory. - /// - /// The directory, and anything below it, will be deleted. - /// Can return null (or empty) when no directory should be deleted. - /// - string? GetDeleteDirectory(MediaFileManager fileSystem, string filepath); - } + /// + /// Gets the directory that can be deleted when the file is deleted. + /// + /// The media filesystem. + /// The filesystem-relative path of the file. + /// The filesystem-relative path of the directory. + /// + /// The directory, and anything below it, will be deleted. + /// Can return null (or empty) when no directory should be deleted. + /// + string? GetDeleteDirectory(MediaFileManager fileSystem, string filepath); } diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index d0f190868b6d..f6fc9a58a7e3 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -1,232 +1,246 @@ -using System; -using System.Collections.Generic; using System.Globalization; -using System.IO; -using System.Linq; using System.Reflection; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.IO +namespace Umbraco.Cms.Core.IO; + +public abstract class IOHelper : IIOHelper { - public abstract class IOHelper : IIOHelper + private readonly IHostingEnvironment _hostingEnvironment; + + public IOHelper(IHostingEnvironment hostingEnvironment) => _hostingEnvironment = + hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + + // static compiled regex for faster performance + //private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + //helper to try and match the old path to a new virtual one + public string FindFile(string virtualPath) { - private readonly IHostingEnvironment _hostingEnvironment; + var retval = virtualPath; - public IOHelper(IHostingEnvironment hostingEnvironment) + if (virtualPath.StartsWith("~")) { - _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + retval = virtualPath.Replace("~", _hostingEnvironment.ApplicationVirtualPath); } - // static compiled regex for faster performance - //private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - - //helper to try and match the old path to a new virtual one - public string FindFile(string virtualPath) + if (virtualPath.StartsWith("/") && !PathStartsWith(virtualPath, _hostingEnvironment.ApplicationVirtualPath)) { - string retval = virtualPath; - - if (virtualPath.StartsWith("~")) - retval = virtualPath.Replace("~", _hostingEnvironment.ApplicationVirtualPath); + retval = _hostingEnvironment.ApplicationVirtualPath + "/" + + virtualPath.TrimStart(Constants.CharArrays.ForwardSlash); + } - if (virtualPath.StartsWith("/") && !PathStartsWith(virtualPath, _hostingEnvironment.ApplicationVirtualPath)) - retval = _hostingEnvironment.ApplicationVirtualPath + "/" + virtualPath.TrimStart(Constants.CharArrays.ForwardSlash); + return retval; + } - return retval; + // TODO: This is the same as IHostingEnvironment.ToAbsolute - marked as obsolete in IIOHelper for now + public string ResolveUrl(string virtualPath) + { + if (string.IsNullOrWhiteSpace(virtualPath)) + { + return virtualPath; } - // TODO: This is the same as IHostingEnvironment.ToAbsolute - marked as obsolete in IIOHelper for now - public string ResolveUrl(string virtualPath) + return _hostingEnvironment.ToAbsolute(virtualPath); + } + + public string MapPath(string path) + { + if (path == null) { - if (string.IsNullOrWhiteSpace(virtualPath)) return virtualPath; - return _hostingEnvironment.ToAbsolute(virtualPath); + throw new ArgumentNullException(nameof(path)); + } + // Check if the path is already mapped - TODO: This should be switched to Path.IsPathFullyQualified once we are on Net Standard 2.1 + if (IsPathFullyQualified(path)) + { + return path; } - public string MapPath(string path) + if (_hostingEnvironment.IsHosted) { - if (path == null) throw new ArgumentNullException(nameof(path)); + var result = !string.IsNullOrEmpty(path) && + (path.StartsWith("~") || PathStartsWith(path, _hostingEnvironment.ApplicationVirtualPath)) + ? _hostingEnvironment.MapPathWebRoot(path) + : _hostingEnvironment.MapPathWebRoot("~/" + path.TrimStart(Constants.CharArrays.ForwardSlash)); - // Check if the path is already mapped - TODO: This should be switched to Path.IsPathFullyQualified once we are on Net Standard 2.1 - if (IsPathFullyQualified(path)) + if (result != null) { - return path; + return result; } + } - if (_hostingEnvironment.IsHosted) - { - var result = (!string.IsNullOrEmpty(path) && (path.StartsWith("~") || PathStartsWith(path, _hostingEnvironment.ApplicationVirtualPath))) - ? _hostingEnvironment.MapPathWebRoot(path) - : _hostingEnvironment.MapPathWebRoot("~/" + path.TrimStart(Constants.CharArrays.ForwardSlash)); + var dirSepChar = Path.DirectorySeparatorChar; + var root = Assembly.GetExecutingAssembly().GetRootDirectorySafe(); + var newPath = path.TrimStart(Constants.CharArrays.TildeForwardSlash).Replace('/', dirSepChar); + var retval = root + dirSepChar.ToString(CultureInfo.InvariantCulture) + newPath; - if (result != null) return result; - } + return retval; + } - var dirSepChar = Path.DirectorySeparatorChar; - var root = Assembly.GetExecutingAssembly().GetRootDirectorySafe(); - var newPath = path.TrimStart(Constants.CharArrays.TildeForwardSlash).Replace('/', dirSepChar); - var retval = root + dirSepChar.ToString(CultureInfo.InvariantCulture) + newPath; - return retval; - } + /// + /// Verifies that the current filepath matches a directory where the user is allowed to edit a file. + /// + /// The filepath to validate. + /// The valid directory. + /// A value indicating whether the filepath is valid. + public bool VerifyEditPath(string filePath, string validDir) => VerifyEditPath(filePath, new[] {validDir}); + + /// + /// Verifies that the current filepath matches one of several directories where the user is allowed to edit a file. + /// + /// The filepath to validate. + /// The valid directories. + /// A value indicating whether the filepath is valid. + public bool VerifyEditPath(string filePath, IEnumerable validDirs) + { + // this is called from ScriptRepository, PartialViewRepository, etc. + // filePath is the fullPath (rooted, filesystem path, can be trusted) + // validDirs are virtual paths (eg ~/Views) + // + // except that for templates, filePath actually is a virtual path + + // TODO: what's below is dirty, there are too many ways to get the root dir, etc. + // not going to fix everything today - /// - /// Returns true if the path has a root, and is considered fully qualified for the OS it is on - /// See https://github.com/dotnet/runtime/blob/30769e8f31b20be10ca26e27ec279cd4e79412b9/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs#L281 for the .NET Standard 2.1 version of this - /// - /// The path to check - /// True if the path is fully qualified, false otherwise - public abstract bool IsPathFullyQualified(string path); - - - /// - /// Verifies that the current filepath matches a directory where the user is allowed to edit a file. - /// - /// The filepath to validate. - /// The valid directory. - /// A value indicating whether the filepath is valid. - public bool VerifyEditPath(string filePath, string validDir) + var mappedRoot = MapPath(_hostingEnvironment.ApplicationVirtualPath); + if (!PathStartsWith(filePath, mappedRoot)) { - return VerifyEditPath(filePath, new[] { validDir }); + // TODO this is going to fail.. Scripts Stylesheets need to use WebRoot, PartialViews need to use ContentRoot + filePath = _hostingEnvironment.MapPathWebRoot(filePath); } - /// - /// Verifies that the current filepath matches one of several directories where the user is allowed to edit a file. - /// - /// The filepath to validate. - /// The valid directories. - /// A value indicating whether the filepath is valid. - public bool VerifyEditPath(string filePath, IEnumerable validDirs) - { - // this is called from ScriptRepository, PartialViewRepository, etc. - // filePath is the fullPath (rooted, filesystem path, can be trusted) - // validDirs are virtual paths (eg ~/Views) - // - // except that for templates, filePath actually is a virtual path + // yes we can (see above) + //// don't trust what we get, it may contain relative segments + //filePath = Path.GetFullPath(filePath); - // TODO: what's below is dirty, there are too many ways to get the root dir, etc. - // not going to fix everything today + foreach (var dir in validDirs) + { + var validDir = dir; + if (!PathStartsWith(validDir, mappedRoot)) + { + validDir = _hostingEnvironment.MapPathWebRoot(validDir); + } - var mappedRoot = MapPath(_hostingEnvironment.ApplicationVirtualPath); - if (!PathStartsWith(filePath, mappedRoot)) + if (PathStartsWith(filePath, validDir)) { - // TODO this is going to fail.. Scripts Stylesheets need to use WebRoot, PartialViews need to use ContentRoot - filePath = _hostingEnvironment.MapPathWebRoot(filePath); + return true; } + } - // yes we can (see above) - //// don't trust what we get, it may contain relative segments - //filePath = Path.GetFullPath(filePath); + return false; + } - foreach (var dir in validDirs) - { - var validDir = dir; - if (!PathStartsWith(validDir, mappedRoot)) - validDir = _hostingEnvironment.MapPathWebRoot(validDir); + /// + /// Verifies that the current filepath has one of several authorized extensions. + /// + /// The filepath to validate. + /// The valid extensions. + /// A value indicating whether the filepath is valid. + public bool VerifyFileExtension(string filePath, IEnumerable validFileExtensions) + { + var ext = Path.GetExtension(filePath); + return ext != null && validFileExtensions.Contains(ext.TrimStart(Constants.CharArrays.Period)); + } - if (PathStartsWith(filePath, validDir)) - return true; - } + public abstract bool PathStartsWith(string path, string root, params char[] separators); - return false; + public void EnsurePathExists(string path) + { + var absolutePath = MapPath(path); + if (Directory.Exists(absolutePath) == false) + { + Directory.CreateDirectory(absolutePath); } + } - /// - /// Verifies that the current filepath has one of several authorized extensions. - /// - /// The filepath to validate. - /// The valid extensions. - /// A value indicating whether the filepath is valid. - public bool VerifyFileExtension(string filePath, IEnumerable validFileExtensions) + /// + /// Get properly formatted relative path from an existing absolute or relative path + /// + /// + /// + public string GetRelativePath(string path) + { + if (path.IsFullPath()) { - var ext = Path.GetExtension(filePath); - return ext != null && validFileExtensions.Contains(ext.TrimStart(Constants.CharArrays.Period)); + var rootDirectory = MapPath("~"); + var relativePath = PathStartsWith(path, rootDirectory) ? path.Substring(rootDirectory.Length) : path; + path = relativePath; } - public abstract bool PathStartsWith(string path, string root, params char[] separators); + return PathUtility.EnsurePathIsApplicationRootPrefixed(path); + } - public void EnsurePathExists(string path) + /// + /// Retrieves array of temporary folders from the hosting environment. + /// + /// Array of instances. + public DirectoryInfo[] GetTempFolders() + { + var tempFolderPaths = new[] { - var absolutePath = MapPath(path); - if (Directory.Exists(absolutePath) == false) - Directory.CreateDirectory(absolutePath); - } + _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempFileUploads) + }; - /// - /// Get properly formatted relative path from an existing absolute or relative path - /// - /// - /// - public string GetRelativePath(string path) + foreach (var tempFolderPath in tempFolderPaths) { - if (path.IsFullPath()) - { - var rootDirectory = MapPath("~"); - var relativePath = PathStartsWith(path, rootDirectory) ? path.Substring(rootDirectory.Length) : path; - path = relativePath; - } - - return PathUtility.EnsurePathIsApplicationRootPrefixed(path); + // Ensure it exists + Directory.CreateDirectory(tempFolderPath); } - /// - /// Retrieves array of temporary folders from the hosting environment. - /// - /// Array of instances. - public DirectoryInfo[] GetTempFolders() - { - var tempFolderPaths = new[] - { - _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempFileUploads) - }; + return tempFolderPaths.Select(x => new DirectoryInfo(x)).ToArray(); + } - foreach (var tempFolderPath in tempFolderPaths) - { - // Ensure it exists - Directory.CreateDirectory(tempFolderPath); - } + /// + /// Cleans contents of a folder by deleting all files older that the provided age. + /// If deletition of any file errors (e.g. due to a file lock) the process will continue to try to delete all that it + /// can. + /// + /// Folder to clean. + /// Age of files within folder to delete. + /// Result of operation. + public CleanFolderResult CleanFolder(DirectoryInfo folder, TimeSpan age) + { + folder.Refresh(); // In case it's changed during runtime. - return tempFolderPaths.Select(x => new DirectoryInfo(x)).ToArray(); + if (!folder.Exists) + { + return CleanFolderResult.FailedAsDoesNotExist(); } - /// - /// Cleans contents of a folder by deleting all files older that the provided age. - /// If deletition of any file errors (e.g. due to a file lock) the process will continue to try to delete all that it can. - /// - /// Folder to clean. - /// Age of files within folder to delete. - /// Result of operation. - public CleanFolderResult CleanFolder(DirectoryInfo folder, TimeSpan age) + FileInfo[] files = folder.GetFiles("*.*", SearchOption.AllDirectories); + var errors = new List(); + foreach (FileInfo file in files) { - folder.Refresh(); // In case it's changed during runtime. - - if (!folder.Exists) - { - return CleanFolderResult.FailedAsDoesNotExist(); - } - - var files = folder.GetFiles("*.*", SearchOption.AllDirectories); - var errors = new List(); - foreach (var file in files) + if (DateTime.UtcNow - file.LastWriteTimeUtc > age) { - if (DateTime.UtcNow - file.LastWriteTimeUtc > age) + try + { + file.IsReadOnly = false; + file.Delete(); + } + catch (Exception ex) { - try - { - file.IsReadOnly = false; - file.Delete(); - } - catch (Exception ex) - { - errors.Add(new CleanFolderResult.Error(ex, file)); - } + errors.Add(new CleanFolderResult.Error(ex, file)); } } - - return errors.Any() - ? CleanFolderResult.FailedWithErrors(errors) - : CleanFolderResult.Success(); } + + return errors.Any() + ? CleanFolderResult.FailedWithErrors(errors) + : CleanFolderResult.Success(); } + + /// + /// Returns true if the path has a root, and is considered fully qualified for the OS it is on + /// See + /// https://github.com/dotnet/runtime/blob/30769e8f31b20be10ca26e27ec279cd4e79412b9/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs#L281 + /// for the .NET Standard 2.1 version of this + /// + /// The path to check + /// True if the path is fully qualified, false otherwise + public abstract bool IsPathFullyQualified(string path); } diff --git a/src/Umbraco.Core/IO/IOHelperExtensions.cs b/src/Umbraco.Core/IO/IOHelperExtensions.cs index 1625c239ff00..7ae90e7f8ebd 100644 --- a/src/Umbraco.Core/IO/IOHelperExtensions.cs +++ b/src/Umbraco.Core/IO/IOHelperExtensions.cs @@ -1,55 +1,54 @@ -using System; -using System.IO; using Umbraco.Cms.Core.IO; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class IOHelperExtensions { - public static class IOHelperExtensions + /// + /// Will resolve a virtual path URL to an absolute path, else if it is not a virtual path (i.e. starts with ~/) then + /// it will just return the path as-is (relative). + /// + /// + /// + /// + public static string? ResolveRelativeOrVirtualUrl(this IIOHelper ioHelper, string? path) { - /// - /// Will resolve a virtual path URL to an absolute path, else if it is not a virtual path (i.e. starts with ~/) then - /// it will just return the path as-is (relative). - /// - /// - /// - /// - public static string? ResolveRelativeOrVirtualUrl(this IIOHelper ioHelper, string? path) + if (string.IsNullOrWhiteSpace(path)) { - if (string.IsNullOrWhiteSpace(path)) return path; - return path.StartsWith("~/") ? ioHelper.ResolveUrl(path) : path; + return path; } - /// - /// Tries to create a directory. - /// - /// The IOHelper. - /// the directory path. - /// true if the directory was created, false otherwise. - public static bool TryCreateDirectory(this IIOHelper ioHelper, string dir) - { - try - { - var dirPath = ioHelper.MapPath(dir); + return path.StartsWith("~/") ? ioHelper.ResolveUrl(path) : path; + } - if (Directory.Exists(dirPath) == false) - Directory.CreateDirectory(dirPath); + /// + /// Tries to create a directory. + /// + /// The IOHelper. + /// the directory path. + /// true if the directory was created, false otherwise. + public static bool TryCreateDirectory(this IIOHelper ioHelper, string dir) + { + try + { + var dirPath = ioHelper.MapPath(dir); - var filePath = dirPath + "/" + CreateRandomFileName(ioHelper) + ".tmp"; - File.WriteAllText(filePath, "This is an Umbraco internal test file. It is safe to delete it."); - File.Delete(filePath); - return true; - } - catch + if (Directory.Exists(dirPath) == false) { - return false; + Directory.CreateDirectory(dirPath); } - } - public static string CreateRandomFileName(this IIOHelper ioHelper) + var filePath = dirPath + "/" + CreateRandomFileName(ioHelper) + ".tmp"; + File.WriteAllText(filePath, "This is an Umbraco internal test file. It is safe to delete it."); + File.Delete(filePath); + return true; + } + catch { - return "umbraco-test." + Guid.NewGuid().ToString("N").Substring(0, 8); + return false; } - - } + + public static string CreateRandomFileName(this IIOHelper ioHelper) => + "umbraco-test." + Guid.NewGuid().ToString("N").Substring(0, 8); } diff --git a/src/Umbraco.Core/IO/IOHelperLinux.cs b/src/Umbraco.Core/IO/IOHelperLinux.cs index 116a7200b3a5..4d050271b47b 100644 --- a/src/Umbraco.Core/IO/IOHelperLinux.cs +++ b/src/Umbraco.Core/IO/IOHelperLinux.cs @@ -1,28 +1,40 @@ -using System; -using System.IO; -using System.Linq; -using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Hosting; -namespace Umbraco.Cms.Core.IO +namespace Umbraco.Cms.Core.IO; + +public class IOHelperLinux : IOHelper { - public class IOHelperLinux : IOHelper + public IOHelperLinux(IHostingEnvironment hostingEnvironment) : base(hostingEnvironment) + { + } + + public override bool IsPathFullyQualified(string path) => Path.IsPathRooted(path); + + public override bool PathStartsWith(string path, string root, params char[] separators) { - public IOHelperLinux(IHostingEnvironment hostingEnvironment) : base(hostingEnvironment) + // either it is identical to root, + // or it is root + separator + anything + + if (separators == null || separators.Length == 0) { + separators = new[] {Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar}; } - public override bool IsPathFullyQualified(string path) => Path.IsPathRooted(path); + if (!path.StartsWith(root, StringComparison.Ordinal)) + { + return false; + } + + if (path.Length == root.Length) + { + return true; + } - public override bool PathStartsWith(string path, string root, params char[] separators) + if (path.Length < root.Length) { - // either it is identical to root, - // or it is root + separator + anything - - if (separators == null || separators.Length == 0) separators = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; - if (!path.StartsWith(root, StringComparison.Ordinal)) return false; - if (path.Length == root.Length) return true; - if (path.Length < root.Length) return false; - return separators.Contains(path[root.Length]); + return false; } + + return separators.Contains(path[root.Length]); } } diff --git a/src/Umbraco.Core/IO/IOHelperOSX.cs b/src/Umbraco.Core/IO/IOHelperOSX.cs index 53b9cb4dc027..b443f7e444b4 100644 --- a/src/Umbraco.Core/IO/IOHelperOSX.cs +++ b/src/Umbraco.Core/IO/IOHelperOSX.cs @@ -1,28 +1,40 @@ -using System; -using System.IO; -using System.Linq; -using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Hosting; -namespace Umbraco.Cms.Core.IO +namespace Umbraco.Cms.Core.IO; + +public class IOHelperOSX : IOHelper { - public class IOHelperOSX : IOHelper + public IOHelperOSX(IHostingEnvironment hostingEnvironment) : base(hostingEnvironment) + { + } + + public override bool IsPathFullyQualified(string path) => Path.IsPathRooted(path); + + public override bool PathStartsWith(string path, string root, params char[] separators) { - public IOHelperOSX(IHostingEnvironment hostingEnvironment) : base(hostingEnvironment) + // either it is identical to root, + // or it is root + separator + anything + + if (separators == null || separators.Length == 0) { + separators = new[] {Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar}; } - public override bool IsPathFullyQualified(string path) => Path.IsPathRooted(path); + if (!path.StartsWith(root, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (path.Length == root.Length) + { + return true; + } - public override bool PathStartsWith(string path, string root, params char[] separators) + if (path.Length < root.Length) { - // either it is identical to root, - // or it is root + separator + anything - - if (separators == null || separators.Length == 0) separators = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; - if (!path.StartsWith(root, StringComparison.OrdinalIgnoreCase)) return false; - if (path.Length == root.Length) return true; - if (path.Length < root.Length) return false; - return separators.Contains(path[root.Length]); + return false; } + + return separators.Contains(path[root.Length]); } } diff --git a/src/Umbraco.Core/IO/IOHelperWindows.cs b/src/Umbraco.Core/IO/IOHelperWindows.cs index cb60f164dcf0..ab7498d4e42d 100644 --- a/src/Umbraco.Core/IO/IOHelperWindows.cs +++ b/src/Umbraco.Core/IO/IOHelperWindows.cs @@ -1,54 +1,67 @@ -using System; -using System.IO; -using System.Linq; -using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Hosting; -namespace Umbraco.Cms.Core.IO +namespace Umbraco.Cms.Core.IO; + +public class IOHelperWindows : IOHelper { - public class IOHelperWindows : IOHelper + public IOHelperWindows(IHostingEnvironment hostingEnvironment) : base(hostingEnvironment) { - public IOHelperWindows(IHostingEnvironment hostingEnvironment) : base(hostingEnvironment) + } + + public override bool IsPathFullyQualified(string path) + { + // TODO: This implementation is taken from the .NET Standard 2.1 implementation. We should switch to using Path.IsPathFullyQualified once we are on .NET Standard 2.1 + + if (path.Length < 2) { + // It isn't fixed, it must be relative. There is no way to specify a fixed + // path with one character (or less). + return false; } - public override bool IsPathFullyQualified(string path) + if (path[0] == Path.DirectorySeparatorChar || path[0] == Path.AltDirectorySeparatorChar) { - // TODO: This implementation is taken from the .NET Standard 2.1 implementation. We should switch to using Path.IsPathFullyQualified once we are on .NET Standard 2.1 - - if (path.Length < 2) - { - // It isn't fixed, it must be relative. There is no way to specify a fixed - // path with one character (or less). - return false; - } - - if (path[0] == Path.DirectorySeparatorChar || path[0] == Path.AltDirectorySeparatorChar) - { - // There is no valid way to specify a relative path with two initial slashes or - // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\ - return path[1] == '?' || path[1] == Path.DirectorySeparatorChar || path[1] == Path.AltDirectorySeparatorChar; - } - - // The only way to specify a fixed path that doesn't begin with two slashes - // is the drive, colon, slash format- i.e. C:\ - return (path.Length >= 3) - && (path[1] == Path.VolumeSeparatorChar) - && (path[2] == Path.DirectorySeparatorChar || path[2] == Path.AltDirectorySeparatorChar) - // To match old behavior we'll check the drive character for validity as the path is technically - // not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream. - && ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')); + // There is no valid way to specify a relative path with two initial slashes or + // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\ + return path[1] == '?' || path[1] == Path.DirectorySeparatorChar || + path[1] == Path.AltDirectorySeparatorChar; } - public override bool PathStartsWith(string path, string root, params char[] separators) + // The only way to specify a fixed path that doesn't begin with two slashes + // is the drive, colon, slash format- i.e. C:\ + return path.Length >= 3 + && path[1] == Path.VolumeSeparatorChar + && (path[2] == Path.DirectorySeparatorChar || path[2] == Path.AltDirectorySeparatorChar) + // To match old behavior we'll check the drive character for validity as the path is technically + // not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream. + && ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')); + } + + public override bool PathStartsWith(string path, string root, params char[] separators) + { + // either it is identical to root, + // or it is root + separator + anything + + if (separators == null || separators.Length == 0) { - // either it is identical to root, - // or it is root + separator + anything - - if (separators == null || separators.Length == 0) separators = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; - if (!path.StartsWith(root, StringComparison.OrdinalIgnoreCase)) return false; - if (path.Length == root.Length) return true; - if (path.Length < root.Length) return false; - return separators.Contains(path[root.Length]); + separators = new[] {Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar}; } + + if (!path.StartsWith(root, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (path.Length == root.Length) + { + return true; + } + + if (path.Length < root.Length) + { + return false; + } + + return separators.Contains(path[root.Length]); } } diff --git a/src/Umbraco.Core/IO/IViewHelper.cs b/src/Umbraco.Core/IO/IViewHelper.cs index ae6f8698a48b..ef28e934faeb 100644 --- a/src/Umbraco.Core/IO/IViewHelper.cs +++ b/src/Umbraco.Core/IO/IViewHelper.cs @@ -1,13 +1,12 @@ using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.IO +namespace Umbraco.Cms.Core.IO; + +public interface IViewHelper { - public interface IViewHelper - { - bool ViewExists(ITemplate t); - string GetFileContents(ITemplate t); - string CreateView(ITemplate t, bool overWrite = false); - string? UpdateViewFile(ITemplate t, string? currentAlias = null); - string ViewPath(string alias); - } + bool ViewExists(ITemplate t); + string GetFileContents(ITemplate t); + string CreateView(ITemplate t, bool overWrite = false); + string? UpdateViewFile(ITemplate t, string? currentAlias = null); + string ViewPath(string alias); } diff --git a/src/Umbraco.Core/IO/MediaFileManager.cs b/src/Umbraco.Core/IO/MediaFileManager.cs index d5c421721e81..be457982cc4d 100644 --- a/src/Umbraco.Core/IO/MediaFileManager.cs +++ b/src/Umbraco.Core/IO/MediaFileManager.cs @@ -1,8 +1,3 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -12,237 +7,243 @@ using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.IO +namespace Umbraco.Cms.Core.IO; + +public sealed class MediaFileManager { - public sealed class MediaFileManager + private readonly ILogger _logger; + private readonly IMediaPathScheme _mediaPathScheme; + private readonly IServiceProvider _serviceProvider; + private readonly IShortStringHelper _shortStringHelper; + private MediaUrlGeneratorCollection? _mediaUrlGenerators; + + public MediaFileManager( + IFileSystem fileSystem, + IMediaPathScheme mediaPathScheme, + ILogger logger, + IShortStringHelper shortStringHelper, + IServiceProvider serviceProvider) { - private readonly IMediaPathScheme _mediaPathScheme; - private readonly ILogger _logger; - private readonly IShortStringHelper _shortStringHelper; - private readonly IServiceProvider _serviceProvider; - private MediaUrlGeneratorCollection? _mediaUrlGenerators; - - public MediaFileManager( - IFileSystem fileSystem, - IMediaPathScheme mediaPathScheme, - ILogger logger, - IShortStringHelper shortStringHelper, - IServiceProvider serviceProvider) - { - _mediaPathScheme = mediaPathScheme; - _logger = logger; - _shortStringHelper = shortStringHelper; - _serviceProvider = serviceProvider; - FileSystem = fileSystem; - } + _mediaPathScheme = mediaPathScheme; + _logger = logger; + _shortStringHelper = shortStringHelper; + _serviceProvider = serviceProvider; + FileSystem = fileSystem; + } - [Obsolete("Use the ctr that doesn't include unused parameters.")] - public MediaFileManager( - IFileSystem fileSystem, - IMediaPathScheme mediaPathScheme, - ILogger logger, - IShortStringHelper shortStringHelper, - IServiceProvider serviceProvider, - IOptions contentSettings) - : this(fileSystem, mediaPathScheme, logger, shortStringHelper, serviceProvider) - { } - - /// - /// Gets the media filesystem. - /// - public IFileSystem FileSystem { get; } - - /// - /// Delete media files. - /// - /// Files to delete (filesystem-relative paths). - public void DeleteMediaFiles(IEnumerable files) - { - files = files.Distinct(); + [Obsolete("Use the ctr that doesn't include unused parameters.")] + public MediaFileManager( + IFileSystem fileSystem, + IMediaPathScheme mediaPathScheme, + ILogger logger, + IShortStringHelper shortStringHelper, + IServiceProvider serviceProvider, + IOptions contentSettings) + : this(fileSystem, mediaPathScheme, logger, shortStringHelper, serviceProvider) + { + } + + /// + /// Gets the media filesystem. + /// + public IFileSystem FileSystem { get; } + + /// + /// Delete media files. + /// + /// Files to delete (filesystem-relative paths). + public void DeleteMediaFiles(IEnumerable files) + { + files = files.Distinct(); - // kinda try to keep things under control - var options = new ParallelOptions { MaxDegreeOfParallelism = 20 }; + // kinda try to keep things under control + var options = new ParallelOptions {MaxDegreeOfParallelism = 20}; - Parallel.ForEach(files, options, file => + Parallel.ForEach(files, options, file => + { + try { - try + if (file.IsNullOrWhiteSpace()) { - if (file.IsNullOrWhiteSpace()) - { - return; - } - - if (FileSystem.FileExists(file) == false) - { - return; - } - - FileSystem.DeleteFile(file); - - var directory = _mediaPathScheme.GetDeleteDirectory(this, file); - if (!directory.IsNullOrWhiteSpace()) - { - FileSystem.DeleteDirectory(directory!, true); - } + return; } - catch (Exception e) + + if (FileSystem.FileExists(file) == false) { - _logger.LogError(e, "Failed to delete media file '{File}'.", file); + return; } - }); - } - #region Media Path - - /// - /// Gets the file path of a media file. - /// - /// The file name. - /// The unique identifier of the content/media owning the file. - /// The unique identifier of the property type owning the file. - /// The filesystem-relative path to the media file. - /// With the old media path scheme, this CREATES a new media path each time it is invoked. - public string GetMediaPath(string? filename, Guid cuid, Guid puid) - { - filename = Path.GetFileName(filename); - if (filename == null) + FileSystem.DeleteFile(file); + + var directory = _mediaPathScheme.GetDeleteDirectory(this, file); + if (!directory.IsNullOrWhiteSpace()) + { + FileSystem.DeleteDirectory(directory!, true); + } + } + catch (Exception e) { - throw new ArgumentException("Cannot become a safe filename.", nameof(filename)); + _logger.LogError(e, "Failed to delete media file '{File}'.", file); } + }); + } - filename = _shortStringHelper.CleanStringForSafeFileName(filename.ToLowerInvariant()); - - return _mediaPathScheme.GetFilePath(this, cuid, puid, filename); + #region Media Path + + /// + /// Gets the file path of a media file. + /// + /// The file name. + /// The unique identifier of the content/media owning the file. + /// The unique identifier of the property type owning the file. + /// The filesystem-relative path to the media file. + /// With the old media path scheme, this CREATES a new media path each time it is invoked. + public string GetMediaPath(string? filename, Guid cuid, Guid puid) + { + filename = Path.GetFileName(filename); + if (filename == null) + { + throw new ArgumentException("Cannot become a safe filename.", nameof(filename)); } - #endregion - - #region Associated Media Files - - /// - /// Returns a stream (file) for a content item (or a null stream if there is no file). - /// - /// - /// The file path if a file was found - /// - /// - /// - public Stream GetFile( - IContentBase content, - out string? mediaFilePath, - string propertyTypeAlias = Constants.Conventions.Media.File, - string? culture = null, - string? segment = null) - { - // TODO: If collections were lazy we could just inject them - if (_mediaUrlGenerators == null) - { - _mediaUrlGenerators = _serviceProvider.GetRequiredService(); - } + filename = _shortStringHelper.CleanStringForSafeFileName(filename.ToLowerInvariant()); - if (!content.TryGetMediaPath(propertyTypeAlias, _mediaUrlGenerators!, out mediaFilePath, culture, segment)) - { - return Stream.Null; - } + return _mediaPathScheme.GetFilePath(this, cuid, puid, filename); + } - return FileSystem.OpenFile(mediaFilePath!); + #endregion + + #region Associated Media Files + + /// + /// Returns a stream (file) for a content item (or a null stream if there is no file). + /// + /// + /// The file path if a file was found + /// + /// + /// + public Stream GetFile( + IContentBase content, + out string? mediaFilePath, + string propertyTypeAlias = Constants.Conventions.Media.File, + string? culture = null, + string? segment = null) + { + // TODO: If collections were lazy we could just inject them + if (_mediaUrlGenerators == null) + { + _mediaUrlGenerators = _serviceProvider.GetRequiredService(); } - /// - /// Stores a media file associated to a property of a content item. - /// - /// The content item owning the media file. - /// The property type owning the media file. - /// The media file name. - /// A stream containing the media bytes. - /// An optional filesystem-relative filepath to the previous media file. - /// The filesystem-relative filepath to the media file. - /// - /// The file is considered "owned" by the content/propertyType. - /// If an is provided then that file (and associated thumbnails if any) is deleted - /// before the new file is saved, and depending on the media path scheme, the folder may be reused for the new file. - /// - public string StoreFile(IContentBase content, IPropertyType? propertyType, string filename, Stream filestream, string? oldpath) + if (!content.TryGetMediaPath(propertyTypeAlias, _mediaUrlGenerators!, out mediaFilePath, culture, segment)) { - if (content == null) - { - throw new ArgumentNullException(nameof(content)); - } + return Stream.Null; + } - if (propertyType == null) - { - throw new ArgumentNullException(nameof(propertyType)); - } + return FileSystem.OpenFile(mediaFilePath!); + } - if (filename == null) - { - throw new ArgumentNullException(nameof(filename)); - } + /// + /// Stores a media file associated to a property of a content item. + /// + /// The content item owning the media file. + /// The property type owning the media file. + /// The media file name. + /// A stream containing the media bytes. + /// An optional filesystem-relative filepath to the previous media file. + /// The filesystem-relative filepath to the media file. + /// + /// The file is considered "owned" by the content/propertyType. + /// + /// If an is provided then that file (and associated thumbnails if any) is deleted + /// before the new file is saved, and depending on the media path scheme, the folder may be reused for the new + /// file. + /// + /// + public string StoreFile(IContentBase content, IPropertyType? propertyType, string filename, Stream filestream, + string? oldpath) + { + if (content == null) + { + throw new ArgumentNullException(nameof(content)); + } - if (string.IsNullOrWhiteSpace(filename)) - { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(filename)); - } + if (propertyType == null) + { + throw new ArgumentNullException(nameof(propertyType)); + } - if (filestream == null) - { - throw new ArgumentNullException(nameof(filestream)); - } + if (filename == null) + { + throw new ArgumentNullException(nameof(filename)); + } - // clear the old file, if any - if (string.IsNullOrWhiteSpace(oldpath) == false) - { - FileSystem.DeleteFile(oldpath!); - } + if (string.IsNullOrWhiteSpace(filename)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(filename)); + } - // get the filepath, store the data - var filepath = GetMediaPath(filename, content.Key, propertyType.Key); - FileSystem.AddFile(filepath, filestream); - return filepath; + if (filestream == null) + { + throw new ArgumentNullException(nameof(filestream)); } - /// - /// Copies a media file as a new media file, associated to a property of a content item. - /// - /// The content item owning the copy of the media file. - /// The property type owning the copy of the media file. - /// The filesystem-relative path to the source media file. - /// The filesystem-relative path to the copy of the media file. - public string? CopyFile(IContentBase content, IPropertyType propertyType, string sourcepath) + // clear the old file, if any + if (string.IsNullOrWhiteSpace(oldpath) == false) { - if (content == null) - { - throw new ArgumentNullException(nameof(content)); - } + FileSystem.DeleteFile(oldpath!); + } - if (propertyType == null) - { - throw new ArgumentNullException(nameof(propertyType)); - } + // get the filepath, store the data + var filepath = GetMediaPath(filename, content.Key, propertyType.Key); + FileSystem.AddFile(filepath, filestream); + return filepath; + } - if (sourcepath == null) - { - throw new ArgumentNullException(nameof(sourcepath)); - } + /// + /// Copies a media file as a new media file, associated to a property of a content item. + /// + /// The content item owning the copy of the media file. + /// The property type owning the copy of the media file. + /// The filesystem-relative path to the source media file. + /// The filesystem-relative path to the copy of the media file. + public string? CopyFile(IContentBase content, IPropertyType propertyType, string sourcepath) + { + if (content == null) + { + throw new ArgumentNullException(nameof(content)); + } - if (string.IsNullOrWhiteSpace(sourcepath)) - { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(sourcepath)); - } + if (propertyType == null) + { + throw new ArgumentNullException(nameof(propertyType)); + } - // ensure we have a file to copy - if (FileSystem.FileExists(sourcepath) == false) - { - return null; - } + if (sourcepath == null) + { + throw new ArgumentNullException(nameof(sourcepath)); + } - // get the filepath - var filename = Path.GetFileName(sourcepath); - var filepath = GetMediaPath(filename, content.Key, propertyType.Key); - FileSystem.CopyFile(sourcepath, filepath); - return filepath; + if (string.IsNullOrWhiteSpace(sourcepath)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(sourcepath)); } - #endregion + // ensure we have a file to copy + if (FileSystem.FileExists(sourcepath) == false) + { + return null; + } + + // get the filepath + var filename = Path.GetFileName(sourcepath); + var filepath = GetMediaPath(filename, content.Key, propertyType.Key); + FileSystem.CopyFile(sourcepath, filepath); + return filepath; } + + #endregion } diff --git a/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs b/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs index 5adc81276b99..1cca8eba6a10 100644 --- a/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs +++ b/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs @@ -1,28 +1,26 @@ -using System; -using System.IO; +namespace Umbraco.Cms.Core.IO.MediaPathSchemes; -namespace Umbraco.Cms.Core.IO.MediaPathSchemes +/// +/// Implements a combined-guids media path scheme. +/// +/// +/// Path is "{combinedGuid}/{filename}" where combinedGuid is a combination of itemGuid and propertyGuid. +/// +public class CombinedGuidsMediaPathScheme : IMediaPathScheme { - /// - /// Implements a combined-guids media path scheme. - /// - /// - /// Path is "{combinedGuid}/{filename}" where combinedGuid is a combination of itemGuid and propertyGuid. - /// - public class CombinedGuidsMediaPathScheme : IMediaPathScheme + /// + public string GetFilePath(MediaFileManager fileManager, Guid itemGuid, Guid propertyGuid, string filename) { - /// - public string GetFilePath(MediaFileManager fileManager, Guid itemGuid, Guid propertyGuid, string filename) - { - // assumes that cuid and puid keys can be trusted - and that a single property type - // for a single content cannot store two different files with the same name + // assumes that cuid and puid keys can be trusted - and that a single property type + // for a single content cannot store two different files with the same name - var combinedGuid = GuidUtils.Combine(itemGuid, propertyGuid); - var directory = HexEncoder.Encode(combinedGuid.ToByteArray()/*'/', 2, 4*/); // could use ext to fragment path eg 12/e4/f2/... - return Path.Combine(directory, filename).Replace('\\', '/'); - } - - /// - public string GetDeleteDirectory(MediaFileManager fileSystem, string filepath) => Path.GetDirectoryName(filepath)!; + Guid combinedGuid = GuidUtils.Combine(itemGuid, propertyGuid); + var directory = + HexEncoder.Encode( + combinedGuid.ToByteArray() /*'/', 2, 4*/); // could use ext to fragment path eg 12/e4/f2/... + return Path.Combine(directory, filename).Replace('\\', '/'); } + + /// + public string GetDeleteDirectory(MediaFileManager fileSystem, string filepath) => Path.GetDirectoryName(filepath)!; } diff --git a/src/Umbraco.Core/IO/MediaPathSchemes/TwoGuidsMediaPathScheme.cs b/src/Umbraco.Core/IO/MediaPathSchemes/TwoGuidsMediaPathScheme.cs index 1ee821e3ed5a..a533a62c921f 100644 --- a/src/Umbraco.Core/IO/MediaPathSchemes/TwoGuidsMediaPathScheme.cs +++ b/src/Umbraco.Core/IO/MediaPathSchemes/TwoGuidsMediaPathScheme.cs @@ -1,26 +1,17 @@ -using System; -using System.IO; +namespace Umbraco.Cms.Core.IO.MediaPathSchemes; -namespace Umbraco.Cms.Core.IO.MediaPathSchemes +/// +/// Implements a two-guids media path scheme. +/// +/// +/// Path is "{itemGuid}/{propertyGuid}/{filename}". +/// +public class TwoGuidsMediaPathScheme : IMediaPathScheme { - /// - /// Implements a two-guids media path scheme. - /// - /// - /// Path is "{itemGuid}/{propertyGuid}/{filename}". - /// - public class TwoGuidsMediaPathScheme : IMediaPathScheme - { - /// - public string GetFilePath(MediaFileManager fileManager, Guid itemGuid, Guid propertyGuid, string filename) - { - return Path.Combine(itemGuid.ToString("N"), propertyGuid.ToString("N"), filename).Replace('\\', '/'); - } + /// + public string GetFilePath(MediaFileManager fileManager, Guid itemGuid, Guid propertyGuid, string filename) => + Path.Combine(itemGuid.ToString("N"), propertyGuid.ToString("N"), filename).Replace('\\', '/'); - /// - public string GetDeleteDirectory(MediaFileManager fileManager, string filepath) - { - return Path.GetDirectoryName(filepath)!; - } - } + /// + public string GetDeleteDirectory(MediaFileManager fileManager, string filepath) => Path.GetDirectoryName(filepath)!; } diff --git a/src/Umbraco.Core/IO/MediaPathSchemes/UniqueMediaPathScheme.cs b/src/Umbraco.Core/IO/MediaPathSchemes/UniqueMediaPathScheme.cs index a3fe36bde914..7b7061506d7e 100644 --- a/src/Umbraco.Core/IO/MediaPathSchemes/UniqueMediaPathScheme.cs +++ b/src/Umbraco.Core/IO/MediaPathSchemes/UniqueMediaPathScheme.cs @@ -1,37 +1,37 @@ -using System; -using System.IO; +namespace Umbraco.Cms.Core.IO.MediaPathSchemes; -namespace Umbraco.Cms.Core.IO.MediaPathSchemes +/// +/// Implements a unique directory media path scheme. +/// +/// +/// This scheme provides deterministic short paths, with potential collisions. +/// +public class UniqueMediaPathScheme : IMediaPathScheme { - /// - /// Implements a unique directory media path scheme. - /// - /// - /// This scheme provides deterministic short paths, with potential collisions. - /// - public class UniqueMediaPathScheme : IMediaPathScheme - { - private const int DirectoryLength = 8; - - /// - public string GetFilePath(MediaFileManager fileManager, Guid itemGuid, Guid propertyGuid, string filename) - { - var combinedGuid = GuidUtils.Combine(itemGuid, propertyGuid); - var directory = GuidUtils.ToBase32String(combinedGuid, DirectoryLength); + private const int DirectoryLength = 8; - return Path.Combine(directory, filename).Replace('\\', '/'); - } + /// + public string GetFilePath(MediaFileManager fileManager, Guid itemGuid, Guid propertyGuid, string filename) + { + Guid combinedGuid = GuidUtils.Combine(itemGuid, propertyGuid); + var directory = GuidUtils.ToBase32String(combinedGuid, DirectoryLength); - /// - /// - /// Returning null so that does *not* - /// delete any directory. This is because the above shortening of the Guid to 8 chars - /// means we're increasing the risk of collision, and we don't want to delete files - /// belonging to other media items. - /// And, at the moment, we cannot delete directory "only if it is empty" because of - /// race conditions. We'd need to implement locks in for - /// this. - /// - public string? GetDeleteDirectory(MediaFileManager fileManager, string filepath) => null; + return Path.Combine(directory, filename).Replace('\\', '/'); } + + /// + /// + /// + /// Returning null so that does *not* + /// delete any directory. This is because the above shortening of the Guid to 8 chars + /// means we're increasing the risk of collision, and we don't want to delete files + /// belonging to other media items. + /// + /// + /// And, at the moment, we cannot delete directory "only if it is empty" because of + /// race conditions. We'd need to implement locks in for + /// this. + /// + /// + public string? GetDeleteDirectory(MediaFileManager fileManager, string filepath) => null; } diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index 30d1893792e5..457aa0eb5c25 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -1,461 +1,510 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Hosting; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.IO -{ - public interface IPhysicalFileSystem : IFileSystem - { } - - public class PhysicalFileSystem : IPhysicalFileSystem, IFileProviderFactory - { - private readonly IIOHelper _ioHelper; - private readonly ILogger _logger; +namespace Umbraco.Cms.Core.IO; - // the rooted, filesystem path, using directory separator chars, NOT ending with a separator - // eg "c:" or "c:\path\to\site" or "\\server\path" - private readonly string _rootPath; - - // _rootPath, but with separators replaced by forward-slashes - // eg "c:" or "c:/path/to/site" or "//server/path" - // (is used in GetRelativePath) - private readonly string _rootPathFwd; +public interface IPhysicalFileSystem : IFileSystem +{ +} - // the relative URL, using URL separator chars, NOT ending with a separator - // eg "" or "/Views" or "/Media" or "//Media" in case of a virtual path - private readonly string _rootUrl; +public class PhysicalFileSystem : IPhysicalFileSystem, IFileProviderFactory +{ + private readonly IIOHelper _ioHelper; + private readonly ILogger _logger; - public PhysicalFileSystem(IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, ILogger logger, string rootPath, string rootUrl) - { - _ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + // the rooted, filesystem path, using directory separator chars, NOT ending with a separator + // eg "c:" or "c:\path\to\site" or "\\server\path" + private readonly string _rootPath; - if (rootPath == null) throw new ArgumentNullException(nameof(rootPath)); - if (string.IsNullOrEmpty(rootPath)) throw new ArgumentException("Value can't be empty.", nameof(rootPath)); - if (rootUrl == null) throw new ArgumentNullException(nameof(rootUrl)); - if (string.IsNullOrEmpty(rootUrl)) throw new ArgumentException("Value can't be empty.", nameof(rootUrl)); - if (rootPath.StartsWith("~/")) throw new ArgumentException("Value can't be a virtual path and start with '~/'.", nameof(rootPath)); + // _rootPath, but with separators replaced by forward-slashes + // eg "c:" or "c:/path/to/site" or "//server/path" + // (is used in GetRelativePath) + private readonly string _rootPathFwd; - // rootPath should be... rooted, as in, it's a root path! - if (Path.IsPathRooted(rootPath) == false) - { - // but the test suite App.config cannot really "root" anything so we have to do it here - var localRoot = hostingEnvironment.MapPathContentRoot("~"); - rootPath = Path.Combine(localRoot, rootPath); - } + // the relative URL, using URL separator chars, NOT ending with a separator + // eg "" or "/Views" or "/Media" or "//Media" in case of a virtual path + private readonly string _rootUrl; - // clean up root path - rootPath = Path.GetFullPath(rootPath); + public PhysicalFileSystem(IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, + ILogger logger, string rootPath, string rootUrl) + { + _ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _rootPath = EnsureDirectorySeparatorChar(rootPath).TrimEnd(Path.DirectorySeparatorChar); - _rootPathFwd = EnsureUrlSeparatorChar(_rootPath); - _rootUrl = EnsureUrlSeparatorChar(rootUrl).TrimEnd(Constants.CharArrays.ForwardSlash); + if (rootPath == null) + { + throw new ArgumentNullException(nameof(rootPath)); } - /// - /// Gets directories in a directory. - /// - /// The filesystem-relative path to the directory. - /// The filesystem-relative path to the directories in the directory. - /// Filesystem-relative paths use forward-slashes as directory separators. - public IEnumerable GetDirectories(string path) + if (string.IsNullOrEmpty(rootPath)) { - var fullPath = GetFullPath(path); - - try - { - if (Directory.Exists(fullPath)) - return Directory.EnumerateDirectories(fullPath).Select(GetRelativePath); - } - catch (UnauthorizedAccessException ex) - { - _logger.LogError(ex, "Not authorized to get directories for '{Path}'", fullPath); - } - catch (DirectoryNotFoundException ex) - { - _logger.LogError(ex, "Directory not found for '{Path}'", fullPath); - } - - return Enumerable.Empty(); + throw new ArgumentException("Value can't be empty.", nameof(rootPath)); } - /// - /// Deletes a directory. - /// - /// The filesystem-relative path of the directory. - public void DeleteDirectory(string path) + if (rootUrl == null) { - DeleteDirectory(path, false); + throw new ArgumentNullException(nameof(rootUrl)); } - /// - /// Deletes a directory. - /// - /// The filesystem-relative path of the directory. - /// A value indicating whether to recursively delete sub-directories. - public void DeleteDirectory(string path, bool recursive) + if (string.IsNullOrEmpty(rootUrl)) { - var fullPath = GetFullPath(path); - if (Directory.Exists(fullPath) == false) - return; - - try - { - WithRetry(() => Directory.Delete(fullPath, recursive)); - } - catch (DirectoryNotFoundException ex) - { - _logger.LogError(ex, "Directory not found for '{Path}'", fullPath); - } + throw new ArgumentException("Value can't be empty.", nameof(rootUrl)); } - /// - /// Gets a value indicating whether a directory exists. - /// - /// The filesystem-relative path of the directory. - /// A value indicating whether a directory exists. - public bool DirectoryExists(string path) + if (rootPath.StartsWith("~/")) { - var fullPath = GetFullPath(path); - return Directory.Exists(fullPath); + throw new ArgumentException("Value can't be a virtual path and start with '~/'.", nameof(rootPath)); } - /// - /// Saves a file. - /// - /// The filesystem-relative path of the file. - /// A stream containing the file data. - /// Overrides the existing file, if any. - public void AddFile(string path, Stream stream) + // rootPath should be... rooted, as in, it's a root path! + if (Path.IsPathRooted(rootPath) == false) { - AddFile(path, stream, true); + // but the test suite App.config cannot really "root" anything so we have to do it here + var localRoot = hostingEnvironment.MapPathContentRoot("~"); + rootPath = Path.Combine(localRoot, rootPath); } - /// - /// Saves a file. - /// - /// The filesystem-relative path of the file. - /// A stream containing the file data. - /// A value indicating whether to override the existing file, if any. - /// If a file exists and is false, an exception is thrown. - public void AddFile(string path, Stream stream, bool overrideExisting) - { - var fullPath = GetFullPath(path); - var exists = File.Exists(fullPath); - if (exists && overrideExisting == false) - { - throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); - } + // clean up root path + rootPath = Path.GetFullPath(rootPath); + + _rootPath = EnsureDirectorySeparatorChar(rootPath).TrimEnd(Path.DirectorySeparatorChar); + _rootPathFwd = EnsureUrlSeparatorChar(_rootPath); + _rootUrl = EnsureUrlSeparatorChar(rootUrl).TrimEnd(Constants.CharArrays.ForwardSlash); + } - var directory = Path.GetDirectoryName(fullPath); - if (directory == null) throw new InvalidOperationException("Could not get directory."); - Directory.CreateDirectory(directory); // ensure it exists + /// + /// Gets directories in a directory. + /// + /// The filesystem-relative path to the directory. + /// The filesystem-relative path to the directories in the directory. + /// Filesystem-relative paths use forward-slashes as directory separators. + public IEnumerable GetDirectories(string path) + { + var fullPath = GetFullPath(path); - if (stream.CanSeek) + try + { + if (Directory.Exists(fullPath)) { - stream.Seek(0, 0); + return Directory.EnumerateDirectories(fullPath).Select(GetRelativePath); } - - using var destination = (Stream)File.Create(fullPath); - stream.CopyTo(destination); + } + catch (UnauthorizedAccessException ex) + { + _logger.LogError(ex, "Not authorized to get directories for '{Path}'", fullPath); + } + catch (DirectoryNotFoundException ex) + { + _logger.LogError(ex, "Directory not found for '{Path}'", fullPath); } - /// - /// Gets files in a directory. - /// - /// The filesystem-relative path of the directory. - /// The filesystem-relative path to the files in the directory. - /// Filesystem-relative paths use forward-slashes as directory separators. - public IEnumerable GetFiles(string path) + return Enumerable.Empty(); + } + + /// + /// Deletes a directory. + /// + /// The filesystem-relative path of the directory. + public void DeleteDirectory(string path) => DeleteDirectory(path, false); + + /// + /// Deletes a directory. + /// + /// The filesystem-relative path of the directory. + /// A value indicating whether to recursively delete sub-directories. + public void DeleteDirectory(string path, bool recursive) + { + var fullPath = GetFullPath(path); + if (Directory.Exists(fullPath) == false) { - return GetFiles(path, "*.*"); + return; } - /// - /// Gets files in a directory. - /// - /// The filesystem-relative path of the directory. - /// A filter. - /// The filesystem-relative path to the matching files in the directory. - /// Filesystem-relative paths use forward-slashes as directory separators. //TODO check is this is true on linux and windows.. - public IEnumerable GetFiles(string path, string filter) + try + { + WithRetry(() => Directory.Delete(fullPath, recursive)); + } + catch (DirectoryNotFoundException ex) { - var fullPath = GetFullPath(path); + _logger.LogError(ex, "Directory not found for '{Path}'", fullPath); + } + } - try - { - if (Directory.Exists(fullPath)) - return Directory.EnumerateFiles(fullPath, filter).Select(GetRelativePath); - } - catch (UnauthorizedAccessException ex) - { - _logger.LogError(ex, "Not authorized to get directories for '{Path}'", fullPath); - } - catch (DirectoryNotFoundException ex) - { - _logger.LogError(ex, "Directory not found for '{FullPath}'", fullPath); - } + /// + /// Gets a value indicating whether a directory exists. + /// + /// The filesystem-relative path of the directory. + /// A value indicating whether a directory exists. + public bool DirectoryExists(string path) + { + var fullPath = GetFullPath(path); + return Directory.Exists(fullPath); + } - return Enumerable.Empty(); + /// + /// Saves a file. + /// + /// The filesystem-relative path of the file. + /// A stream containing the file data. + /// Overrides the existing file, if any. + public void AddFile(string path, Stream stream) => AddFile(path, stream, true); + + /// + /// Saves a file. + /// + /// The filesystem-relative path of the file. + /// A stream containing the file data. + /// A value indicating whether to override the existing file, if any. + /// If a file exists and is false, an exception is thrown. + public void AddFile(string path, Stream stream, bool overrideExisting) + { + var fullPath = GetFullPath(path); + var exists = File.Exists(fullPath); + if (exists && overrideExisting == false) + { + throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); } - /// - /// Opens a file. - /// - /// The filesystem-relative path to the file. - /// - public Stream OpenFile(string path) + var directory = Path.GetDirectoryName(fullPath); + if (directory == null) { - var fullPath = GetFullPath(path); - return File.OpenRead(fullPath); + throw new InvalidOperationException("Could not get directory."); } - /// - /// Deletes a file. - /// - /// The filesystem-relative path to the file. - public void DeleteFile(string path) + Directory.CreateDirectory(directory); // ensure it exists + + if (stream.CanSeek) { - var fullPath = GetFullPath(path); - if (File.Exists(fullPath) == false) - return; + stream.Seek(0, 0); + } - try - { - WithRetry(() => File.Delete(fullPath)); - } - catch (FileNotFoundException ex) + using var destination = (Stream)File.Create(fullPath); + stream.CopyTo(destination); + } + + /// + /// Gets files in a directory. + /// + /// The filesystem-relative path of the directory. + /// The filesystem-relative path to the files in the directory. + /// Filesystem-relative paths use forward-slashes as directory separators. + public IEnumerable GetFiles(string path) => GetFiles(path, "*.*"); + + /// + /// Gets files in a directory. + /// + /// The filesystem-relative path of the directory. + /// A filter. + /// The filesystem-relative path to the matching files in the directory. + /// Filesystem-relative paths use forward-slashes as directory separators. + /// //TODO check is this is true on linux and windows.. + public IEnumerable GetFiles(string path, string filter) + { + var fullPath = GetFullPath(path); + + try + { + if (Directory.Exists(fullPath)) { - _logger.LogError(ex.InnerException, "DeleteFile failed with FileNotFoundException for '{Path}'", fullPath); + return Directory.EnumerateFiles(fullPath, filter).Select(GetRelativePath); } } - - /// - /// Gets a value indicating whether a file exists. - /// - /// The filesystem-relative path to the file. - /// A value indicating whether the file exists. - public bool FileExists(string path) + catch (UnauthorizedAccessException ex) { - var fullpath = GetFullPath(path); - return File.Exists(fullpath); + _logger.LogError(ex, "Not authorized to get directories for '{Path}'", fullPath); } - - /// - /// Gets the filesystem-relative path of a full path or of an URL. - /// - /// The full path or URL. - /// The path, relative to this filesystem's root. - /// - /// The relative path is relative to this filesystem's root, not starting with any - /// directory separator. All separators are forward-slashes. - /// - public string GetRelativePath(string fullPathOrUrl) + catch (DirectoryNotFoundException ex) { - // test URL - var path = fullPathOrUrl.Replace('\\', '/'); // ensure URL separator char - - // if it starts with the root path, strip it and trim the starting slash to make it relative - // eg "c:/websites/test/root/Media/1234/img.jpg" => "1234/img.jpg" - // or on unix systems "/var/wwwroot/test/Meia/1234/img.jpg" - if (_ioHelper.PathStartsWith(path, _rootPathFwd, '/')) - return path.Substring(_rootPathFwd.Length).TrimStart(Constants.CharArrays.ForwardSlash); - - // if it starts with the root URL, strip it and trim the starting slash to make it relative - // eg "/Media/1234/img.jpg" => "1234/img.jpg" - if (_ioHelper.PathStartsWith(path, _rootUrl, '/')) - return path.Substring(_rootUrl.Length).TrimStart(Constants.CharArrays.ForwardSlash); - - // unchanged - what else? - return path.TrimStart(Constants.CharArrays.ForwardSlash); + _logger.LogError(ex, "Directory not found for '{FullPath}'", fullPath); } - /// - /// Gets the full path. - /// - /// The full or filesystem-relative path. - /// The full path. - /// - /// On the physical filesystem, the full path is the rooted (ie non-relative), safe (ie within this - /// filesystem's root) path. All separators are Path.DirectorySeparatorChar. - /// - public string GetFullPath(string path) + return Enumerable.Empty(); + } + + /// + /// Opens a file. + /// + /// The filesystem-relative path to the file. + /// + public Stream OpenFile(string path) + { + var fullPath = GetFullPath(path); + return File.OpenRead(fullPath); + } + + /// + /// Deletes a file. + /// + /// The filesystem-relative path to the file. + public void DeleteFile(string path) + { + var fullPath = GetFullPath(path); + if (File.Exists(fullPath) == false) { - // normalize - var originalPath = path; - path = EnsureDirectorySeparatorChar(path); - - // FIXME: this part should go! - // not sure what we are doing here - so if input starts with a (back) slash, - // we assume it's not a FS relative path and we try to convert it... but it - // really makes little sense? - if (path.StartsWith(Path.DirectorySeparatorChar.ToString())) - path = GetRelativePath(path); - - // if not already rooted, combine with the root path - if (_ioHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar) == false) - path = Path.Combine(_rootPath, path); - - // sanitize - GetFullPath will take care of any relative - // segments in path, eg '../../foo.tmp' - it may throw a SecurityException - // if the combined path reaches illegal parts of the filesystem - path = Path.GetFullPath(path); - - // at that point, path is within legal parts of the filesystem, ie we have - // permissions to reach that path, but it may nevertheless be outside of - // our root path, due to relative segments, so better check - if (_ioHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar)) - { - // this says that 4.7.2 supports long paths - but Windows does not - // https://docs.microsoft.com/en-us/dotnet/api/system.io.pathtoolongexception?view=netframework-4.7.2 - if (path.Length > 260) - throw new PathTooLongException($"Path {path} is too long."); - return path; - } + return; + } - // nothing prevents us to reach the file, security-wise, yet it is outside - // this filesystem's root - throw - throw new UnauthorizedAccessException($"File original: [{originalPath}] full: [{path}] is outside this filesystem's root."); + try + { + WithRetry(() => File.Delete(fullPath)); } + catch (FileNotFoundException ex) + { + _logger.LogError(ex.InnerException, "DeleteFile failed with FileNotFoundException for '{Path}'", fullPath); + } + } - /// - /// Gets the URL. - /// - /// The filesystem-relative path. - /// The URL. - /// All separators are forward-slashes. - public string GetUrl(string? path) + /// + /// Gets a value indicating whether a file exists. + /// + /// The filesystem-relative path to the file. + /// A value indicating whether the file exists. + public bool FileExists(string path) + { + var fullpath = GetFullPath(path); + return File.Exists(fullpath); + } + + /// + /// Gets the filesystem-relative path of a full path or of an URL. + /// + /// The full path or URL. + /// The path, relative to this filesystem's root. + /// + /// + /// The relative path is relative to this filesystem's root, not starting with any + /// directory separator. All separators are forward-slashes. + /// + /// + public string GetRelativePath(string fullPathOrUrl) + { + // test URL + var path = fullPathOrUrl.Replace('\\', '/'); // ensure URL separator char + + // if it starts with the root path, strip it and trim the starting slash to make it relative + // eg "c:/websites/test/root/Media/1234/img.jpg" => "1234/img.jpg" + // or on unix systems "/var/wwwroot/test/Meia/1234/img.jpg" + if (_ioHelper.PathStartsWith(path, _rootPathFwd, '/')) { - path = EnsureUrlSeparatorChar(path ?? string.Empty).Trim(Constants.CharArrays.ForwardSlash); - return _rootUrl + "/" + path; + return path.Substring(_rootPathFwd.Length).TrimStart(Constants.CharArrays.ForwardSlash); } - /// - /// Gets the last-modified date of a directory or file. - /// - /// The filesystem-relative path to the directory or the file. - /// The last modified date of the directory or the file. - public DateTimeOffset GetLastModified(string path) + // if it starts with the root URL, strip it and trim the starting slash to make it relative + // eg "/Media/1234/img.jpg" => "1234/img.jpg" + if (_ioHelper.PathStartsWith(path, _rootUrl, '/')) { - var fullpath = GetFullPath(path); - return DirectoryExists(fullpath) - ? new DirectoryInfo(fullpath).LastWriteTimeUtc - : new FileInfo(fullpath).LastWriteTimeUtc; + return path.Substring(_rootUrl.Length).TrimStart(Constants.CharArrays.ForwardSlash); } - /// - /// Gets the created date of a directory or file. - /// - /// The filesystem-relative path to the directory or the file. - /// The created date of the directory or the file. - public DateTimeOffset GetCreated(string path) + // unchanged - what else? + return path.TrimStart(Constants.CharArrays.ForwardSlash); + } + + /// + /// Gets the full path. + /// + /// The full or filesystem-relative path. + /// The full path. + /// + /// + /// On the physical filesystem, the full path is the rooted (ie non-relative), safe (ie within this + /// filesystem's root) path. All separators are Path.DirectorySeparatorChar. + /// + /// + public string GetFullPath(string path) + { + // normalize + var originalPath = path; + path = EnsureDirectorySeparatorChar(path); + + // FIXME: this part should go! + // not sure what we are doing here - so if input starts with a (back) slash, + // we assume it's not a FS relative path and we try to convert it... but it + // really makes little sense? + if (path.StartsWith(Path.DirectorySeparatorChar.ToString())) { - var fullpath = GetFullPath(path); - return DirectoryExists(fullpath) - ? Directory.GetCreationTimeUtc(fullpath) - : File.GetCreationTimeUtc(fullpath); + path = GetRelativePath(path); } - /// - /// Gets the size of a file. - /// - /// The filesystem-relative path to the file. - /// The file of the size, in bytes. - /// If the file does not exist, returns -1. - public long GetSize(string path) + // if not already rooted, combine with the root path + if (_ioHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar) == false) { - var fullPath = GetFullPath(path); - var file = new FileInfo(fullPath); - return file.Exists ? file.Length : -1; + path = Path.Combine(_rootPath, path); } - public bool CanAddPhysical => true; + // sanitize - GetFullPath will take care of any relative + // segments in path, eg '../../foo.tmp' - it may throw a SecurityException + // if the combined path reaches illegal parts of the filesystem + path = Path.GetFullPath(path); - public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) + // at that point, path is within legal parts of the filesystem, ie we have + // permissions to reach that path, but it may nevertheless be outside of + // our root path, due to relative segments, so better check + if (_ioHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar)) { - var fullPath = GetFullPath(path); - - if (File.Exists(fullPath)) + // this says that 4.7.2 supports long paths - but Windows does not + // https://docs.microsoft.com/en-us/dotnet/api/system.io.pathtoolongexception?view=netframework-4.7.2 + if (path.Length > 260) { - if (overrideIfExists == false) - throw new InvalidOperationException($"A file at path '{path}' already exists"); - WithRetry(() => File.Delete(fullPath)); + throw new PathTooLongException($"Path {path} is too long."); } - var directory = Path.GetDirectoryName(fullPath); - if (directory == null) throw new InvalidOperationException("Could not get directory."); - Directory.CreateDirectory(directory); // ensure it exists - - if (copy) - WithRetry(() => File.Copy(physicalPath, fullPath)); - else - WithRetry(() => File.Move(physicalPath, fullPath)); + return path; } - #region Helper Methods + // nothing prevents us to reach the file, security-wise, yet it is outside + // this filesystem's root - throw + throw new UnauthorizedAccessException( + $"File original: [{originalPath}] full: [{path}] is outside this filesystem's root."); + } + + /// + /// Gets the URL. + /// + /// The filesystem-relative path. + /// The URL. + /// All separators are forward-slashes. + public string GetUrl(string? path) + { + path = EnsureUrlSeparatorChar(path ?? string.Empty).Trim(Constants.CharArrays.ForwardSlash); + return _rootUrl + "/" + path; + } - protected virtual void EnsureDirectory(string path) + /// + /// Gets the last-modified date of a directory or file. + /// + /// The filesystem-relative path to the directory or the file. + /// The last modified date of the directory or the file. + public DateTimeOffset GetLastModified(string path) + { + var fullpath = GetFullPath(path); + return DirectoryExists(fullpath) + ? new DirectoryInfo(fullpath).LastWriteTimeUtc + : new FileInfo(fullpath).LastWriteTimeUtc; + } + + /// + /// Gets the created date of a directory or file. + /// + /// The filesystem-relative path to the directory or the file. + /// The created date of the directory or the file. + public DateTimeOffset GetCreated(string path) + { + var fullpath = GetFullPath(path); + return DirectoryExists(fullpath) + ? Directory.GetCreationTimeUtc(fullpath) + : File.GetCreationTimeUtc(fullpath); + } + + /// + /// Gets the size of a file. + /// + /// The filesystem-relative path to the file. + /// The file of the size, in bytes. + /// If the file does not exist, returns -1. + public long GetSize(string path) + { + var fullPath = GetFullPath(path); + var file = new FileInfo(fullPath); + return file.Exists ? file.Length : -1; + } + + public bool CanAddPhysical => true; + + public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) + { + var fullPath = GetFullPath(path); + + if (File.Exists(fullPath)) { - path = GetFullPath(path); - Directory.CreateDirectory(path); + if (overrideIfExists == false) + { + throw new InvalidOperationException($"A file at path '{path}' already exists"); + } + + WithRetry(() => File.Delete(fullPath)); } - protected string EnsureTrailingSeparator(string path) + var directory = Path.GetDirectoryName(fullPath); + if (directory == null) { - return path.EnsureEndsWith(Path.DirectorySeparatorChar); + throw new InvalidOperationException("Could not get directory."); } - protected string EnsureDirectorySeparatorChar(string path) + Directory.CreateDirectory(directory); // ensure it exists + + if (copy) { - path = path.Replace('/', Path.DirectorySeparatorChar); - path = path.Replace('\\', Path.DirectorySeparatorChar); - return path; + WithRetry(() => File.Copy(physicalPath, fullPath)); } - - protected string EnsureUrlSeparatorChar(string path) + else { - path = path.Replace('\\', '/'); - return path; + WithRetry(() => File.Move(physicalPath, fullPath)); } + } - protected void WithRetry(Action action) - { - // 10 times 100ms is 1s - const int count = 10; - const int pausems = 100; + #region Helper Methods + + protected virtual void EnsureDirectory(string path) + { + path = GetFullPath(path); + Directory.CreateDirectory(path); + } + + protected string EnsureTrailingSeparator(string path) => path.EnsureEndsWith(Path.DirectorySeparatorChar); - for (var i = 0;; i++) + protected string EnsureDirectorySeparatorChar(string path) + { + path = path.Replace('/', Path.DirectorySeparatorChar); + path = path.Replace('\\', Path.DirectorySeparatorChar); + return path; + } + + protected string EnsureUrlSeparatorChar(string path) + { + path = path.Replace('\\', '/'); + return path; + } + + protected void WithRetry(Action action) + { + // 10 times 100ms is 1s + const int count = 10; + const int pausems = 100; + + for (var i = 0;; i++) + { + try + { + action(); + break; // done + } + catch (IOException e) { - try + // if it's not *exactly* IOException then it could be + // some inherited exception such as FileNotFoundException, + // and then we don't want to retry + if (e.GetType() != typeof(IOException)) { - action(); - break; // done + throw; } - catch (IOException e) + + // if we have tried enough, throw, else swallow + // the exception and retry after a pause + if (i == count) { - // if it's not *exactly* IOException then it could be - // some inherited exception such as FileNotFoundException, - // and then we don't want to retry - if (e.GetType() != typeof(IOException)) throw; - - // if we have tried enough, throw, else swallow - // the exception and retry after a pause - if (i == count) throw; + throw; } - - Thread.Sleep(pausems); } + + Thread.Sleep(pausems); } + } - /// - public IFileProvider Create() => new PhysicalFileProvider(_rootPath); + /// + public IFileProvider Create() => new PhysicalFileProvider(_rootPath); - #endregion - } + #endregion } diff --git a/src/Umbraco.Core/IO/ShadowFileSystem.cs b/src/Umbraco.Core/IO/ShadowFileSystem.cs index bd3f9d770d3e..8094a137f100 100644 --- a/src/Umbraco.Core/IO/ShadowFileSystem.cs +++ b/src/Umbraco.Core/IO/ShadowFileSystem.cs @@ -1,386 +1,485 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; -namespace Umbraco.Cms.Core.IO +namespace Umbraco.Cms.Core.IO; + +internal class ShadowFileSystem : IFileSystem { - internal class ShadowFileSystem : IFileSystem + private readonly IFileSystem _sfs; + + private Dictionary? _nodes; + + public ShadowFileSystem(IFileSystem fs, IFileSystem sfs) + { + Inner = fs; + _sfs = sfs; + } + + public IFileSystem Inner { get; } + + private Dictionary Nodes => _nodes ?? (_nodes = new Dictionary()); + + public IEnumerable GetDirectories(string path) { - private readonly IFileSystem _fs; - private readonly IFileSystem _sfs; + var normPath = NormPath(path); + KeyValuePair[] shadows = Nodes.Where(kvp => IsChild(normPath, kvp.Key)).ToArray(); + IEnumerable directories = Inner.GetDirectories(path); + return directories + .Except(shadows + .Where(kvp => (kvp.Value.IsDir && kvp.Value.IsDelete) || (kvp.Value.IsFile && kvp.Value.IsExist)) + .Select(kvp => kvp.Key)) + .Union(shadows.Where(kvp => kvp.Value.IsDir && kvp.Value.IsExist).Select(kvp => kvp.Key)) + .Distinct(); + } - public ShadowFileSystem(IFileSystem fs, IFileSystem sfs) + public void DeleteDirectory(string path) => DeleteDirectory(path, false); + + public void DeleteDirectory(string path, bool recursive) + { + if (DirectoryExists(path) == false) { - _fs = fs; - _sfs = sfs; + return; } - public IFileSystem Inner => _fs; + var normPath = NormPath(path); + if (recursive) + { + Nodes[normPath] = new ShadowNode(true, true); + var remove = Nodes.Where(x => IsDescendant(normPath, x.Key)).ToList(); + foreach (KeyValuePair kvp in remove) + { + Nodes.Remove(kvp.Key); + } - public void Complete() + Delete(path, true); + } + else { - if (_nodes == null) return; - var exceptions = new List(); - foreach (var kvp in _nodes) + if (Nodes.Any(x => IsChild(normPath, x.Key) && x.Value.IsExist) // shadow content + || Inner.GetDirectories(path).Any() || Inner.GetFiles(path).Any()) // actual content + { + throw new InvalidOperationException("Directory is not empty."); + } + + Nodes[path] = new ShadowNode(true, true); + var remove = Nodes.Where(x => IsChild(normPath, x.Key)).ToList(); + foreach (KeyValuePair kvp in remove) { - if (kvp.Value.IsExist) + Nodes.Remove(kvp.Key); + } + + Delete(path, false); + } + } + + public bool DirectoryExists(string path) + { + ShadowNode? sf; + if (Nodes.TryGetValue(NormPath(path), out sf)) + { + return sf.IsDir && sf.IsExist; + } + + return Inner.DirectoryExists(path); + } + + public void AddFile(string path, Stream stream) => AddFile(path, stream, true); + + public void AddFile(string path, Stream stream, bool overrideIfExists) + { + ShadowNode? sf; + var normPath = NormPath(path); + if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false)) + { + throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); + } + + var parts = normPath.Split(Constants.CharArrays.ForwardSlash); + for (var i = 0; i < parts.Length - 1; i++) + { + var dirPath = string.Join("/", parts.Take(i + 1)); + ShadowNode? sd; + if (Nodes.TryGetValue(dirPath, out sd)) + { + if (sd.IsFile) { - if (kvp.Value.IsFile) - { - try - { - if (_fs.CanAddPhysical) - { - _fs.AddFile(kvp.Key, _sfs.GetFullPath(kvp.Key)); // overwrite, move - } - else - { - using (Stream stream = _sfs.OpenFile(kvp.Key)) - { - _fs.AddFile(kvp.Key, stream, true); - } - } - } - catch (Exception e) - { - exceptions.Add(new Exception("Could not save file \"" + kvp.Key + "\".", e)); - } - } + throw new InvalidOperationException("Invalid path."); } - else + + if (sd.IsDelete) { - try - { - if (kvp.Value.IsDir) - _fs.DeleteDirectory(kvp.Key, true); - else - _fs.DeleteFile(kvp.Key); - } - catch (Exception e) - { - exceptions.Add(new Exception("Could not delete " + (kvp.Value.IsDir ? "directory": "file") + " \"" + kvp.Key + "\".", e)); - } + Nodes[dirPath] = new ShadowNode(false, true); } } - _nodes.Clear(); + else + { + if (Inner.DirectoryExists(dirPath)) + { + continue; + } - if (exceptions.Count == 0) return; - throw new AggregateException("Failed to apply all changes (see exceptions).", exceptions); + if (Inner.FileExists(dirPath)) + { + throw new InvalidOperationException("Invalid path."); + } + + Nodes[dirPath] = new ShadowNode(false, true); + } } - private Dictionary? _nodes; + _sfs.AddFile(path, stream, overrideIfExists); + Nodes[normPath] = new ShadowNode(false, false); + } + + public IEnumerable GetFiles(string path) => GetFiles(path, null); - private Dictionary Nodes => _nodes ?? (_nodes = new Dictionary()); + public IEnumerable GetFiles(string path, string? filter) + { + var normPath = NormPath(path); + KeyValuePair[] shadows = Nodes.Where(kvp => IsChild(normPath, kvp.Key)).ToArray(); + IEnumerable files = filter != null ? Inner.GetFiles(path, filter) : Inner.GetFiles(path); + WildcardExpression wildcard = filter == null ? null : new WildcardExpression(filter); + return files + .Except(shadows.Where(kvp => (kvp.Value.IsFile && kvp.Value.IsDelete) || kvp.Value.IsDir) + .Select(kvp => kvp.Key)) + .Union(shadows + .Where(kvp => kvp.Value.IsFile && kvp.Value.IsExist && (wildcard == null || wildcard.IsMatch(kvp.Key))) + .Select(kvp => kvp.Key)) + .Distinct(); + } - private class ShadowNode + public Stream OpenFile(string path) + { + if (Nodes.TryGetValue(NormPath(path), out ShadowNode? sf)) { - public ShadowNode(bool isDelete, bool isdir) - { - IsDelete = isDelete; - IsDir = isdir; - } + return sf.IsDir || sf.IsDelete ? Stream.Null : _sfs.OpenFile(path); + } - public bool IsDelete { get; } - public bool IsDir { get; } + return Inner.OpenFile(path); + } - public bool IsExist => IsDelete == false; - public bool IsFile => IsDir == false; + public void DeleteFile(string path) + { + if (FileExists(path) == false) + { + return; } - private static string NormPath(string path) + Nodes[NormPath(path)] = new ShadowNode(true, false); + } + + public bool FileExists(string path) + { + ShadowNode? sf; + if (Nodes.TryGetValue(NormPath(path), out sf)) { - return path.ToLowerInvariant().Replace("\\", "/"); + return sf.IsFile && sf.IsExist; } - // values can be "" (root), "foo", "foo/bar"... - private static bool IsChild(string path, string input) + return Inner.FileExists(path); + } + + public string GetRelativePath(string fullPathOrUrl) => Inner.GetRelativePath(fullPathOrUrl); + + public string GetFullPath(string path) + { + ShadowNode? sf; + if (Nodes.TryGetValue(NormPath(path), out sf)) { - if (input.StartsWith(path) == false || input.Length < path.Length + 2) - return false; - if (path.Length > 0 && input[path.Length] != '/') return false; - var pos = input.IndexOf("/", path.Length + 1, StringComparison.OrdinalIgnoreCase); - return pos < 0; + return sf.IsDir || sf.IsDelete ? string.Empty : _sfs.GetFullPath(path); } - private static bool IsDescendant(string path, string input) + return Inner.GetFullPath(path); + } + + public string GetUrl(string? path) => Inner.GetUrl(path); + + public DateTimeOffset GetLastModified(string path) + { + ShadowNode? sf; + if (Nodes.TryGetValue(NormPath(path), out sf) == false) { - if (input.StartsWith(path) == false || input.Length < path.Length + 2) - return false; - return path.Length == 0 || input[path.Length] == '/'; + return Inner.GetLastModified(path); } - public IEnumerable GetDirectories(string path) + if (sf.IsDelete) { - var normPath = NormPath(path); - var shadows = Nodes.Where(kvp => IsChild(normPath, kvp.Key)).ToArray(); - var directories = _fs.GetDirectories(path); - return directories - .Except(shadows.Where(kvp => (kvp.Value.IsDir && kvp.Value.IsDelete) || (kvp.Value.IsFile && kvp.Value.IsExist)) - .Select(kvp => kvp.Key)) - .Union(shadows.Where(kvp => kvp.Value.IsDir && kvp.Value.IsExist).Select(kvp => kvp.Key)) - .Distinct(); + throw new InvalidOperationException("Invalid path."); } - public void DeleteDirectory(string path) + return _sfs.GetLastModified(path); + } + + public DateTimeOffset GetCreated(string path) + { + ShadowNode? sf; + if (Nodes.TryGetValue(NormPath(path), out sf) == false) { - DeleteDirectory(path, false); + return Inner.GetCreated(path); } - public void DeleteDirectory(string path, bool recursive) + if (sf.IsDelete) { - if (DirectoryExists(path) == false) return; - var normPath = NormPath(path); - if (recursive) - { - Nodes[normPath] = new ShadowNode(true, true); - var remove = Nodes.Where(x => IsDescendant(normPath, x.Key)).ToList(); - foreach (var kvp in remove) Nodes.Remove(kvp.Key); - Delete(path, true); - } - else - { - if (Nodes.Any(x => IsChild(normPath, x.Key) && x.Value.IsExist) // shadow content - || _fs.GetDirectories(path).Any() || _fs.GetFiles(path).Any()) // actual content - throw new InvalidOperationException("Directory is not empty."); - Nodes[path] = new ShadowNode(true, true); - var remove = Nodes.Where(x => IsChild(normPath, x.Key)).ToList(); - foreach (var kvp in remove) Nodes.Remove(kvp.Key); - Delete(path, false); - } + throw new InvalidOperationException("Invalid path."); } - private void Delete(string path, bool recurse) + return _sfs.GetCreated(path); + } + + public long GetSize(string path) + { + ShadowNode? sf; + if (Nodes.TryGetValue(NormPath(path), out sf) == false) { - foreach (var file in _fs.GetFiles(path)) - { - Nodes[NormPath(file)] = new ShadowNode(true, false); - } - foreach (var dir in _fs.GetDirectories(path)) - { - Nodes[NormPath(dir)] = new ShadowNode(true, true); - if (recurse) Delete(dir, true); - } + return Inner.GetSize(path); } - public bool DirectoryExists(string path) + if (sf.IsDelete || sf.IsDir) { - ShadowNode? sf; - if (Nodes.TryGetValue(NormPath(path), out sf)) - return sf.IsDir && sf.IsExist; - return _fs.DirectoryExists(path); + throw new InvalidOperationException("Invalid path."); } - public void AddFile(string path, Stream stream) + return _sfs.GetSize(path); + } + + public bool CanAddPhysical => true; + + public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) + { + ShadowNode? sf; + var normPath = NormPath(path); + if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false)) { - AddFile(path, stream, true); + throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); } - public void AddFile(string path, Stream stream, bool overrideIfExists) + var parts = normPath.Split(Constants.CharArrays.ForwardSlash); + for (var i = 0; i < parts.Length - 1; i++) { - ShadowNode? sf; - var normPath = NormPath(path); - if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false)) - throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); - - var parts = normPath.Split(Constants.CharArrays.ForwardSlash); - for (var i = 0; i < parts.Length - 1; i++) + var dirPath = string.Join("/", parts.Take(i + 1)); + ShadowNode? sd; + if (Nodes.TryGetValue(dirPath, out sd)) { - var dirPath = string.Join("/", parts.Take(i + 1)); - ShadowNode? sd; - if (Nodes.TryGetValue(dirPath, out sd)) + if (sd.IsFile) { - if (sd.IsFile) throw new InvalidOperationException("Invalid path."); - if (sd.IsDelete) Nodes[dirPath] = new ShadowNode(false, true); + throw new InvalidOperationException("Invalid path."); } - else + + if (sd.IsDelete) { - if (_fs.DirectoryExists(dirPath)) continue; - if (_fs.FileExists(dirPath)) throw new InvalidOperationException("Invalid path."); Nodes[dirPath] = new ShadowNode(false, true); } } + else + { + if (Inner.DirectoryExists(dirPath)) + { + continue; + } - _sfs.AddFile(path, stream, overrideIfExists); - Nodes[normPath] = new ShadowNode(false, false); - } + if (Inner.FileExists(dirPath)) + { + throw new InvalidOperationException("Invalid path."); + } - public IEnumerable GetFiles(string path) - { - return GetFiles(path, null); + Nodes[dirPath] = new ShadowNode(false, true); + } } - public IEnumerable GetFiles(string path, string? filter) + _sfs.AddFile(path, physicalPath, overrideIfExists, copy); + Nodes[normPath] = new ShadowNode(false, false); + } + + public void Complete() + { + if (_nodes == null) { - var normPath = NormPath(path); - var shadows = Nodes.Where(kvp => IsChild(normPath, kvp.Key)).ToArray(); - var files = filter != null ? _fs.GetFiles(path, filter) : _fs.GetFiles(path); - var wildcard = filter == null ? null : new WildcardExpression(filter); - return files - .Except(shadows.Where(kvp => (kvp.Value.IsFile && kvp.Value.IsDelete) || kvp.Value.IsDir) - .Select(kvp => kvp.Key)) - .Union(shadows.Where(kvp => kvp.Value.IsFile && kvp.Value.IsExist && (wildcard == null || wildcard.IsMatch(kvp.Key))).Select(kvp => kvp.Key)) - .Distinct(); + return; } - public Stream OpenFile(string path) + var exceptions = new List(); + foreach (KeyValuePair kvp in _nodes) { - if (Nodes.TryGetValue(NormPath(path), out ShadowNode? sf)) + if (kvp.Value.IsExist) { - return sf.IsDir || sf.IsDelete ? Stream.Null : _sfs.OpenFile(path); + if (kvp.Value.IsFile) + { + try + { + if (Inner.CanAddPhysical) + { + Inner.AddFile(kvp.Key, _sfs.GetFullPath(kvp.Key)); // overwrite, move + } + else + { + using (Stream stream = _sfs.OpenFile(kvp.Key)) + { + Inner.AddFile(kvp.Key, stream, true); + } + } + } + catch (Exception e) + { + exceptions.Add(new Exception("Could not save file \"" + kvp.Key + "\".", e)); + } + } + } + else + { + try + { + if (kvp.Value.IsDir) + { + Inner.DeleteDirectory(kvp.Key, true); + } + else + { + Inner.DeleteFile(kvp.Key); + } + } + catch (Exception e) + { + exceptions.Add(new Exception( + "Could not delete " + (kvp.Value.IsDir ? "directory" : "file") + " \"" + kvp.Key + "\".", e)); + } } - - return _fs.OpenFile(path); } - public void DeleteFile(string path) - { - if (FileExists(path) == false) return; - Nodes[NormPath(path)] = new ShadowNode(true, false); - } + _nodes.Clear(); - public bool FileExists(string path) + if (exceptions.Count == 0) { - ShadowNode? sf; - if (Nodes.TryGetValue(NormPath(path), out sf)) - return sf.IsFile && sf.IsExist; - return _fs.FileExists(path); + return; } - public string GetRelativePath(string fullPathOrUrl) + throw new AggregateException("Failed to apply all changes (see exceptions).", exceptions); + } + + private static string NormPath(string path) => path.ToLowerInvariant().Replace("\\", "/"); + + // values can be "" (root), "foo", "foo/bar"... + private static bool IsChild(string path, string input) + { + if (input.StartsWith(path) == false || input.Length < path.Length + 2) { - return _fs.GetRelativePath(fullPathOrUrl); + return false; } - public string GetFullPath(string path) + if (path.Length > 0 && input[path.Length] != '/') { - ShadowNode? sf; - if (Nodes.TryGetValue(NormPath(path), out sf)) - return sf.IsDir || sf.IsDelete ? string.Empty : _sfs.GetFullPath(path); - return _fs.GetFullPath(path); + return false; } - public string GetUrl(string? path) + var pos = input.IndexOf("/", path.Length + 1, StringComparison.OrdinalIgnoreCase); + return pos < 0; + } + + private static bool IsDescendant(string path, string input) + { + if (input.StartsWith(path) == false || input.Length < path.Length + 2) { - return _fs.GetUrl(path); + return false; } - public DateTimeOffset GetLastModified(string path) + return path.Length == 0 || input[path.Length] == '/'; + } + + private void Delete(string path, bool recurse) + { + foreach (var file in Inner.GetFiles(path)) { - ShadowNode? sf; - if (Nodes.TryGetValue(NormPath(path), out sf) == false) return _fs.GetLastModified(path); - if (sf.IsDelete) throw new InvalidOperationException("Invalid path."); - return _sfs.GetLastModified(path); + Nodes[NormPath(file)] = new ShadowNode(true, false); } - public DateTimeOffset GetCreated(string path) + foreach (var dir in Inner.GetDirectories(path)) { - ShadowNode? sf; - if (Nodes.TryGetValue(NormPath(path), out sf) == false) return _fs.GetCreated(path); - if (sf.IsDelete) throw new InvalidOperationException("Invalid path."); - return _sfs.GetCreated(path); + Nodes[NormPath(dir)] = new ShadowNode(true, true); + if (recurse) + { + Delete(dir, true); + } } + } - public long GetSize(string path) + private class ShadowNode + { + public ShadowNode(bool isDelete, bool isdir) { - ShadowNode? sf; - if (Nodes.TryGetValue(NormPath(path), out sf) == false) - return _fs.GetSize(path); - - if (sf.IsDelete || sf.IsDir) throw new InvalidOperationException("Invalid path."); - return _sfs.GetSize(path); + IsDelete = isDelete; + IsDir = isdir; } - public bool CanAddPhysical { get { return true; } } + public bool IsDelete { get; } + public bool IsDir { get; } - public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) - { - ShadowNode? sf; - var normPath = NormPath(path); - if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false)) - throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); - - var parts = normPath.Split(Constants.CharArrays.ForwardSlash); - for (var i = 0; i < parts.Length - 1; i++) - { - var dirPath = string.Join("/", parts.Take(i + 1)); - ShadowNode? sd; - if (Nodes.TryGetValue(dirPath, out sd)) - { - if (sd.IsFile) throw new InvalidOperationException("Invalid path."); - if (sd.IsDelete) Nodes[dirPath] = new ShadowNode(false, true); - } - else - { - if (_fs.DirectoryExists(dirPath)) continue; - if (_fs.FileExists(dirPath)) throw new InvalidOperationException("Invalid path."); - Nodes[dirPath] = new ShadowNode(false, true); - } - } + public bool IsExist => IsDelete == false; + public bool IsFile => IsDir == false; + } - _sfs.AddFile(path, physicalPath, overrideIfExists, copy); - Nodes[normPath] = new ShadowNode(false, false); + // copied from System.Web.Util.Wildcard internal + internal class WildcardExpression + { + private static readonly Regex metaRegex = new("[\\+\\{\\\\\\[\\|\\(\\)\\.\\^\\$]"); + private static readonly Regex questRegex = new("\\?"); + private static readonly Regex starRegex = new("\\*"); + private static readonly Regex commaRegex = new(","); + private static Regex slashRegex = new("(?=/)"); + private static Regex backslashRegex = new("(?=[\\\\:])"); + private readonly bool _caseInsensitive; + private readonly string _pattern; + private Regex? _regex; + + public WildcardExpression(string pattern, bool caseInsensitive = true) + { + _pattern = pattern; + _caseInsensitive = caseInsensitive; } - // copied from System.Web.Util.Wildcard internal - internal class WildcardExpression + private void EnsureRegex(string pattern) { - private readonly string _pattern; - private readonly bool _caseInsensitive; - private Regex? _regex; - - private static Regex metaRegex = new Regex("[\\+\\{\\\\\\[\\|\\(\\)\\.\\^\\$]"); - private static Regex questRegex = new Regex("\\?"); - private static Regex starRegex = new Regex("\\*"); - private static Regex commaRegex = new Regex(","); - private static Regex slashRegex = new Regex("(?=/)"); - private static Regex backslashRegex = new Regex("(?=[\\\\:])"); - - public WildcardExpression(string pattern, bool caseInsensitive = true) + if (_regex != null) { - _pattern = pattern; - _caseInsensitive = caseInsensitive; + return; } - private void EnsureRegex(string pattern) - { - if (_regex != null) return; - - var options = RegexOptions.None; + RegexOptions options = RegexOptions.None; - // match right-to-left (for speed) if the pattern starts with a * + // match right-to-left (for speed) if the pattern starts with a * - if (pattern.Length > 0 && pattern[0] == '*') - options = RegexOptions.RightToLeft | RegexOptions.Singleline; - else - options = RegexOptions.Singleline; + if (pattern.Length > 0 && pattern[0] == '*') + { + options = RegexOptions.RightToLeft | RegexOptions.Singleline; + } + else + { + options = RegexOptions.Singleline; + } - // case insensitivity + // case insensitivity - if (_caseInsensitive) - options |= RegexOptions.IgnoreCase | RegexOptions.CultureInvariant; + if (_caseInsensitive) + { + options |= RegexOptions.IgnoreCase | RegexOptions.CultureInvariant; + } - // Remove regex metacharacters + // Remove regex metacharacters - pattern = metaRegex.Replace(pattern, "\\$0"); + pattern = metaRegex.Replace(pattern, "\\$0"); - // Replace wildcard metacharacters with regex codes + // Replace wildcard metacharacters with regex codes - pattern = questRegex.Replace(pattern, "."); - pattern = starRegex.Replace(pattern, ".*"); - pattern = commaRegex.Replace(pattern, "\\z|\\A"); + pattern = questRegex.Replace(pattern, "."); + pattern = starRegex.Replace(pattern, ".*"); + pattern = commaRegex.Replace(pattern, "\\z|\\A"); - // anchor the pattern at beginning and end, and return the regex + // anchor the pattern at beginning and end, and return the regex - _regex = new Regex("\\A" + pattern + "\\z", options); - } + _regex = new Regex("\\A" + pattern + "\\z", options); + } - public bool IsMatch(string input) - { - EnsureRegex(_pattern); - return _regex?.IsMatch(input) ?? false; - } + public bool IsMatch(string input) + { + EnsureRegex(_pattern); + return _regex?.IsMatch(input) ?? false; } } } diff --git a/src/Umbraco.Core/IO/ShadowFileSystems.cs b/src/Umbraco.Core/IO/ShadowFileSystems.cs index 413cc73d8a38..c2fd54f489f3 100644 --- a/src/Umbraco.Core/IO/ShadowFileSystems.cs +++ b/src/Umbraco.Core/IO/ShadowFileSystems.cs @@ -1,34 +1,26 @@ -namespace Umbraco.Cms.Core.IO +namespace Umbraco.Cms.Core.IO; +// shadow filesystems is definitively ... too convoluted + +internal class ShadowFileSystems : ICompletable { - // shadow filesystems is definitively ... too convoluted + private readonly FileSystems _fileSystems; + private bool _completed; - internal class ShadowFileSystems : ICompletable + // invoked by the filesystems when shadowing + public ShadowFileSystems(FileSystems fileSystems, string id) { - private readonly FileSystems _fileSystems; - private bool _completed; - - // invoked by the filesystems when shadowing - public ShadowFileSystems(FileSystems fileSystems, string id) - { - _fileSystems = fileSystems; - Id = id; + _fileSystems = fileSystems; + Id = id; - _fileSystems.BeginShadow(id); - } + _fileSystems.BeginShadow(id); + } - // for tests - public string Id { get; } + // for tests + public string Id { get; } - // invoked by the scope when exiting, if completed - public void Complete() - { - _completed = true; - } + // invoked by the scope when exiting, if completed + public void Complete() => _completed = true; - // invoked by the scope when exiting - public void Dispose() - { - _fileSystems.EndShadow(Id, _completed); - } - } + // invoked by the scope when exiting + public void Dispose() => _fileSystems.EndShadow(Id, _completed); } diff --git a/src/Umbraco.Core/IO/ShadowWrapper.cs b/src/Umbraco.Core/IO/ShadowWrapper.cs index 67808e1fdb57..0b412812b885 100644 --- a/src/Umbraco.Core/IO/ShadowWrapper.cs +++ b/src/Umbraco.Core/IO/ShadowWrapper.cs @@ -1,233 +1,190 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Hosting; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.IO +namespace Umbraco.Cms.Core.IO; + +internal class ShadowWrapper : IFileSystem, IFileProviderFactory { - internal class ShadowWrapper : IFileSystem, IFileProviderFactory + private static readonly string ShadowFsPath = Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs"; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IIOHelper _ioHelper; + + private readonly Func? _isScoped; + private readonly ILoggerFactory _loggerFactory; + private readonly string _shadowPath; + private string? _shadowDir; + private ShadowFileSystem? _shadowFileSystem; + + public ShadowWrapper(IFileSystem innerFileSystem, IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, + ILoggerFactory loggerFactory, string shadowPath, Func? isScoped = null) { - private static readonly string ShadowFsPath = Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs"; - - private readonly Func? _isScoped; - private readonly IFileSystem _innerFileSystem; - private readonly string _shadowPath; - private ShadowFileSystem? _shadowFileSystem; - private string? _shadowDir; - private readonly IIOHelper _ioHelper; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly ILoggerFactory _loggerFactory; - - public ShadowWrapper(IFileSystem innerFileSystem, IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory, string shadowPath, Func? isScoped = null) - { - _innerFileSystem = innerFileSystem; - _ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper)); - _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); - _loggerFactory = loggerFactory; - _shadowPath = shadowPath; - _isScoped = isScoped; - } - - public static string CreateShadowId(IHostingEnvironment hostingEnvironment) - { - const int retries = 50; // avoid infinite loop - const int idLength = 8; // 6 chars + InnerFileSystem = innerFileSystem; + _ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper)); + _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + _loggerFactory = loggerFactory; + _shadowPath = shadowPath; + _isScoped = isScoped; + } - // shorten a Guid to idLength chars, and see whether it collides - // with an existing directory or not - if it does, try again, and - // we should end up with a unique identifier eventually - but just - // detect infinite loops (just in case) + public IFileSystem InnerFileSystem { get; } - for (var i = 0; i < retries; i++) + private IFileSystem FileSystem + { + get + { + if (_isScoped is not null && _shadowFileSystem is not null) { - var id = GuidUtils.ToBase32String(Guid.NewGuid(), idLength); + var isScoped = _isScoped!(); - var virt = ShadowFsPath + "/" + id; - var shadowDir = hostingEnvironment.MapPathContentRoot(virt); - if (Directory.Exists(shadowDir)) - continue; + // if the filesystem is created *after* shadowing starts, it won't be shadowing + // better not ignore that situation and raised a meaningful (?) exception + if (isScoped.HasValue && isScoped.Value && _shadowFileSystem == null) + { + throw new Exception("The filesystems are shadowing, but this filesystem is not."); + } - Directory.CreateDirectory(shadowDir); - return id; + return isScoped.HasValue && isScoped.Value + ? _shadowFileSystem + : InnerFileSystem; } - throw new Exception($"Could not get a shadow identifier (tried {retries} times)"); + return InnerFileSystem; } + } - internal void Shadow(string id) - { - // note: no thread-safety here, because ShadowFs is thread-safe due to the check - // on ShadowFileSystemsScope.None - and if None is false then we should be running - // in a single thread anyways - - var virt = Path.Combine(ShadowFsPath , id , _shadowPath); - _shadowDir = _hostingEnvironment.MapPathContentRoot(virt); - Directory.CreateDirectory(_shadowDir); - var tempfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _loggerFactory.CreateLogger(), _shadowDir, _hostingEnvironment.ToAbsolute(virt)); - _shadowFileSystem = new ShadowFileSystem(_innerFileSystem, tempfs); - } + /// + public IFileProvider? Create() => + InnerFileSystem.TryCreateFileProvider(out IFileProvider? fileProvider) ? fileProvider : null; - internal void UnShadow(bool complete) - { - var shadowFileSystem = _shadowFileSystem; - var dir = _shadowDir; - _shadowFileSystem = null; - _shadowDir = null; + public IEnumerable GetDirectories(string path) => FileSystem.GetDirectories(path); - try - { - // this may throw an AggregateException if some of the changes could not be applied - if (complete) shadowFileSystem?.Complete(); - } - finally - { - // in any case, cleanup - try - { - Directory.Delete(dir!, true); + public void DeleteDirectory(string path) => FileSystem.DeleteDirectory(path); - // shadowPath make be path/to/dir, remove each - dir = dir!.Replace('/', Path.DirectorySeparatorChar); - var min = _hostingEnvironment.MapPathContentRoot(ShadowFsPath).Length; - var pos = dir.LastIndexOf(Path.DirectorySeparatorChar); - while (pos > min) - { - dir = dir.Substring(0, pos); - if (Directory.EnumerateFileSystemEntries(dir).Any() == false) - Directory.Delete(dir, true); - else - break; - pos = dir.LastIndexOf(Path.DirectorySeparatorChar); - } - } - catch - { - // ugly, isn't it? but if we cannot cleanup, bah, just leave it there - } - } - } + public void DeleteDirectory(string path, bool recursive) => FileSystem.DeleteDirectory(path, recursive); - public IFileSystem InnerFileSystem => _innerFileSystem; + public bool DirectoryExists(string path) => FileSystem.DirectoryExists(path); - private IFileSystem FileSystem - { - get - { - if (_isScoped is not null && _shadowFileSystem is not null) - { - var isScoped = _isScoped!(); + public void AddFile(string path, Stream stream) => FileSystem.AddFile(path, stream); - // if the filesystem is created *after* shadowing starts, it won't be shadowing - // better not ignore that situation and raised a meaningful (?) exception - if ( isScoped.HasValue && isScoped.Value && _shadowFileSystem == null) - throw new Exception("The filesystems are shadowing, but this filesystem is not."); + public void AddFile(string path, Stream stream, bool overrideExisting) => + FileSystem.AddFile(path, stream, overrideExisting); - return isScoped.HasValue && isScoped.Value - ? _shadowFileSystem - : _innerFileSystem; - } + public IEnumerable GetFiles(string path) => FileSystem.GetFiles(path); - return _innerFileSystem; - } - } + public IEnumerable GetFiles(string path, string filter) => FileSystem.GetFiles(path, filter); - public IEnumerable GetDirectories(string path) - { - return FileSystem.GetDirectories(path); - } + public Stream OpenFile(string path) => FileSystem.OpenFile(path); - public void DeleteDirectory(string path) - { - FileSystem.DeleteDirectory(path); - } + public void DeleteFile(string path) => FileSystem.DeleteFile(path); - public void DeleteDirectory(string path, bool recursive) - { - FileSystem.DeleteDirectory(path, recursive); - } + public bool FileExists(string path) => FileSystem.FileExists(path); - public bool DirectoryExists(string path) - { - return FileSystem.DirectoryExists(path); - } + public string GetRelativePath(string fullPathOrUrl) => FileSystem.GetRelativePath(fullPathOrUrl); - public void AddFile(string path, Stream stream) - { - FileSystem.AddFile(path, stream); - } + public string GetFullPath(string path) => FileSystem.GetFullPath(path); - public void AddFile(string path, Stream stream, bool overrideExisting) - { - FileSystem.AddFile(path, stream, overrideExisting); - } + public string GetUrl(string? path) => FileSystem.GetUrl(path); - public IEnumerable GetFiles(string path) - { - return FileSystem.GetFiles(path); - } + public DateTimeOffset GetLastModified(string path) => FileSystem.GetLastModified(path); - public IEnumerable GetFiles(string path, string filter) - { - return FileSystem.GetFiles(path, filter); - } + public DateTimeOffset GetCreated(string path) => FileSystem.GetCreated(path); - public Stream OpenFile(string path) - { - return FileSystem.OpenFile(path); - } + public long GetSize(string path) => FileSystem.GetSize(path); - public void DeleteFile(string path) - { - FileSystem.DeleteFile(path); - } + public bool CanAddPhysical => FileSystem.CanAddPhysical; - public bool FileExists(string path) - { - return FileSystem.FileExists(path); - } + public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) => + FileSystem.AddFile(path, physicalPath, overrideIfExists, copy); - public string GetRelativePath(string fullPathOrUrl) - { - return FileSystem.GetRelativePath(fullPathOrUrl); - } + public static string CreateShadowId(IHostingEnvironment hostingEnvironment) + { + const int retries = 50; // avoid infinite loop + const int idLength = 8; // 6 chars - public string GetFullPath(string path) - { - return FileSystem.GetFullPath(path); - } + // shorten a Guid to idLength chars, and see whether it collides + // with an existing directory or not - if it does, try again, and + // we should end up with a unique identifier eventually - but just + // detect infinite loops (just in case) - public string GetUrl(string? path) + for (var i = 0; i < retries; i++) { - return FileSystem.GetUrl(path); - } + var id = GuidUtils.ToBase32String(Guid.NewGuid(), idLength); - public DateTimeOffset GetLastModified(string path) - { - return FileSystem.GetLastModified(path); - } + var virt = ShadowFsPath + "/" + id; + var shadowDir = hostingEnvironment.MapPathContentRoot(virt); + if (Directory.Exists(shadowDir)) + { + continue; + } - public DateTimeOffset GetCreated(string path) - { - return FileSystem.GetCreated(path); + Directory.CreateDirectory(shadowDir); + return id; } - public long GetSize(string path) - { - return FileSystem.GetSize(path); - } + throw new Exception($"Could not get a shadow identifier (tried {retries} times)"); + } + + internal void Shadow(string id) + { + // note: no thread-safety here, because ShadowFs is thread-safe due to the check + // on ShadowFileSystemsScope.None - and if None is false then we should be running + // in a single thread anyways + + var virt = Path.Combine(ShadowFsPath, id, _shadowPath); + _shadowDir = _hostingEnvironment.MapPathContentRoot(virt); + Directory.CreateDirectory(_shadowDir); + var tempfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, + _loggerFactory.CreateLogger(), _shadowDir, _hostingEnvironment.ToAbsolute(virt)); + _shadowFileSystem = new ShadowFileSystem(InnerFileSystem, tempfs); + } - public bool CanAddPhysical => FileSystem.CanAddPhysical; + internal void UnShadow(bool complete) + { + ShadowFileSystem shadowFileSystem = _shadowFileSystem; + var dir = _shadowDir; + _shadowFileSystem = null; + _shadowDir = null; - public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) + try { - FileSystem.AddFile(path, physicalPath, overrideIfExists, copy); + // this may throw an AggregateException if some of the changes could not be applied + if (complete) + { + shadowFileSystem?.Complete(); + } } + finally + { + // in any case, cleanup + try + { + Directory.Delete(dir!, true); - /// - public IFileProvider? Create() => _innerFileSystem.TryCreateFileProvider(out IFileProvider? fileProvider) ? fileProvider : null; + // shadowPath make be path/to/dir, remove each + dir = dir!.Replace('/', Path.DirectorySeparatorChar); + var min = _hostingEnvironment.MapPathContentRoot(ShadowFsPath).Length; + var pos = dir.LastIndexOf(Path.DirectorySeparatorChar); + while (pos > min) + { + dir = dir.Substring(0, pos); + if (Directory.EnumerateFileSystemEntries(dir).Any() == false) + { + Directory.Delete(dir, true); + } + else + { + break; + } + + pos = dir.LastIndexOf(Path.DirectorySeparatorChar); + } + } + catch + { + // ugly, isn't it? but if we cannot cleanup, bah, just leave it there + } + } } } diff --git a/src/Umbraco.Core/IO/ViewHelper.cs b/src/Umbraco.Core/IO/ViewHelper.cs index 9bf87c3407ff..3199b51ecbe8 100644 --- a/src/Umbraco.Core/IO/ViewHelper.cs +++ b/src/Umbraco.Core/IO/ViewHelper.cs @@ -1,133 +1,133 @@ -using System; -using System.IO; -using System.Linq; using System.Text; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.IO +namespace Umbraco.Cms.Core.IO; + +public class ViewHelper : IViewHelper { - public class ViewHelper : IViewHelper + private readonly IDefaultViewContentProvider _defaultViewContentProvider; + private readonly IFileSystem _viewFileSystem; + + [Obsolete("Use ctor with all params")] + public ViewHelper(IFileSystem viewFileSystem) { - private readonly IFileSystem _viewFileSystem; - private readonly IDefaultViewContentProvider _defaultViewContentProvider; + _viewFileSystem = viewFileSystem ?? throw new ArgumentNullException(nameof(viewFileSystem)); + _defaultViewContentProvider = StaticServiceProvider.Instance.GetRequiredService(); + } - [Obsolete("Use ctor with all params")] - public ViewHelper(IFileSystem viewFileSystem) - { - _viewFileSystem = viewFileSystem ?? throw new ArgumentNullException(nameof(viewFileSystem)); - _defaultViewContentProvider = StaticServiceProvider.Instance.GetRequiredService(); - } + public ViewHelper(FileSystems fileSystems, IDefaultViewContentProvider defaultViewContentProvider) + { + _viewFileSystem = fileSystems.MvcViewsFileSystem ?? throw new ArgumentNullException(nameof(fileSystems)); + _defaultViewContentProvider = defaultViewContentProvider ?? + throw new ArgumentNullException(nameof(defaultViewContentProvider)); + } - public ViewHelper(FileSystems fileSystems, IDefaultViewContentProvider defaultViewContentProvider) - { - _viewFileSystem = fileSystems.MvcViewsFileSystem ?? throw new ArgumentNullException(nameof(fileSystems)); - _defaultViewContentProvider = defaultViewContentProvider ?? throw new ArgumentNullException(nameof(defaultViewContentProvider)); - } + public bool ViewExists(ITemplate t) => t.Alias is not null && _viewFileSystem.FileExists(ViewPath(t.Alias)); - public bool ViewExists(ITemplate t) => t.Alias is not null && _viewFileSystem.FileExists(ViewPath(t.Alias)); + public string GetFileContents(ITemplate t) + { + var viewContent = ""; + var path = ViewPath(t.Alias ?? string.Empty); - public string GetFileContents(ITemplate t) + if (_viewFileSystem.FileExists(path)) { - var viewContent = ""; - var path = ViewPath(t.Alias ?? string.Empty); - - if (_viewFileSystem.FileExists(path)) + using (var tr = new StreamReader(_viewFileSystem.OpenFile(path))) { - using (var tr = new StreamReader(_viewFileSystem.OpenFile(path))) - { - viewContent = tr.ReadToEnd(); - tr.Close(); - } + viewContent = tr.ReadToEnd(); + tr.Close(); } - - return viewContent; } - public string CreateView(ITemplate t, bool overWrite = false) - { - string viewContent; - var path = ViewPath(t.Alias); + return viewContent; + } - if (_viewFileSystem.FileExists(path) == false || overWrite) - { - viewContent = SaveTemplateToFile(t); - } - else - { - using (var tr = new StreamReader(_viewFileSystem.OpenFile(path))) - { - viewContent = tr.ReadToEnd(); - tr.Close(); - } - } + public string CreateView(ITemplate t, bool overWrite = false) + { + string viewContent; + var path = ViewPath(t.Alias); - return viewContent; + if (_viewFileSystem.FileExists(path) == false || overWrite) + { + viewContent = SaveTemplateToFile(t); } - - [Obsolete("Inject IDefaultViewContentProvider instead")] - public static string GetDefaultFileContent(string? layoutPageAlias = null, string? modelClassName = null, - string? modelNamespace = null, string? modelNamespaceAlias = null) + else { - var viewContentProvider = StaticServiceProvider.Instance.GetRequiredService(); - return viewContentProvider.GetDefaultFileContent(layoutPageAlias, modelClassName, modelNamespace, - modelNamespaceAlias); + using (var tr = new StreamReader(_viewFileSystem.OpenFile(path))) + { + viewContent = tr.ReadToEnd(); + tr.Close(); + } } - private string SaveTemplateToFile(ITemplate template) - { - var design = template.Content.IsNullOrWhiteSpace() ? EnsureInheritedLayout(template) : template.Content!; - var path = ViewPath(template.Alias); + return viewContent; + } - var data = Encoding.UTF8.GetBytes(design); - var withBom = Encoding.UTF8.GetPreamble().Concat(data).ToArray(); + public string? UpdateViewFile(ITemplate t, string? currentAlias = null) + { + var path = ViewPath(t.Alias); - using (var ms = new MemoryStream(withBom)) + if (string.IsNullOrEmpty(currentAlias) == false && currentAlias != t.Alias) + { + //then kill the old file.. + var oldFile = ViewPath(currentAlias); + if (_viewFileSystem.FileExists(oldFile)) { - _viewFileSystem.AddFile(path, ms, true); + _viewFileSystem.DeleteFile(oldFile); } - - return design; } - public string? UpdateViewFile(ITemplate t, string? currentAlias = null) + var data = Encoding.UTF8.GetBytes(t.Content ?? string.Empty); + var withBom = Encoding.UTF8.GetPreamble().Concat(data).ToArray(); + + using (var ms = new MemoryStream(withBom)) { - var path = ViewPath(t.Alias); + _viewFileSystem.AddFile(path, ms, true); + } - if (string.IsNullOrEmpty(currentAlias) == false && currentAlias != t.Alias) - { - //then kill the old file.. - var oldFile = ViewPath(currentAlias); - if (_viewFileSystem.FileExists(oldFile)) - _viewFileSystem.DeleteFile(oldFile); - } + return t.Content; + } - var data = Encoding.UTF8.GetBytes(t.Content ?? string.Empty); - var withBom = Encoding.UTF8.GetPreamble().Concat(data).ToArray(); + public string ViewPath(string alias) => _viewFileSystem.GetRelativePath(alias.Replace(" ", "") + ".cshtml"); - using (var ms = new MemoryStream(withBom)) - { - _viewFileSystem.AddFile(path, ms, true); - } - return t.Content; - } + [Obsolete("Inject IDefaultViewContentProvider instead")] + public static string GetDefaultFileContent(string? layoutPageAlias = null, string? modelClassName = null, + string? modelNamespace = null, string? modelNamespaceAlias = null) + { + IDefaultViewContentProvider viewContentProvider = + StaticServiceProvider.Instance.GetRequiredService(); + return viewContentProvider.GetDefaultFileContent(layoutPageAlias, modelClassName, modelNamespace, + modelNamespaceAlias); + } + + private string SaveTemplateToFile(ITemplate template) + { + var design = template.Content.IsNullOrWhiteSpace() ? EnsureInheritedLayout(template) : template.Content!; + var path = ViewPath(template.Alias); + + var data = Encoding.UTF8.GetBytes(design); + var withBom = Encoding.UTF8.GetPreamble().Concat(data).ToArray(); - public string ViewPath(string alias) + using (var ms = new MemoryStream(withBom)) { - return _viewFileSystem.GetRelativePath(alias.Replace(" ", "") + ".cshtml"); + _viewFileSystem.AddFile(path, ms, true); } - private string EnsureInheritedLayout(ITemplate template) - { - var design = template.Content; + return design; + } - if (string.IsNullOrEmpty(design)) - design = _defaultViewContentProvider.GetDefaultFileContent(template.MasterTemplateAlias); + private string EnsureInheritedLayout(ITemplate template) + { + var design = template.Content; - return design; + if (string.IsNullOrEmpty(design)) + { + design = _defaultViewContentProvider.GetDefaultFileContent(template.MasterTemplateAlias); } + + return design; } } diff --git a/src/Umbraco.Core/IRegisteredObject.cs b/src/Umbraco.Core/IRegisteredObject.cs index 54ac6e1a5709..103e10dab162 100644 --- a/src/Umbraco.Core/IRegisteredObject.cs +++ b/src/Umbraco.Core/IRegisteredObject.cs @@ -1,7 +1,6 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public interface IRegisteredObject { - public interface IRegisteredObject - { - void Stop(bool immediate); - } + void Stop(bool immediate); } diff --git a/src/Umbraco.Core/Install/FilePermissionTest.cs b/src/Umbraco.Core/Install/FilePermissionTest.cs index f84d9a0a7bd9..d7c4b527ba60 100644 --- a/src/Umbraco.Core/Install/FilePermissionTest.cs +++ b/src/Umbraco.Core/Install/FilePermissionTest.cs @@ -1,10 +1,9 @@ -namespace Umbraco.Cms.Core.Install +namespace Umbraco.Cms.Core.Install; + +public enum FilePermissionTest { - public enum FilePermissionTest - { - FolderCreation, - FileWritingForPackages, - FileWriting, - MediaFolderCreation - } + FolderCreation, + FileWritingForPackages, + FileWriting, + MediaFolderCreation } diff --git a/src/Umbraco.Core/Install/IFilePermissionHelper.cs b/src/Umbraco.Core/Install/IFilePermissionHelper.cs index cfda3a396d41..88b6e23cbf5c 100644 --- a/src/Umbraco.Core/Install/IFilePermissionHelper.cs +++ b/src/Umbraco.Core/Install/IFilePermissionHelper.cs @@ -1,20 +1,16 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Install; -namespace Umbraco.Cms.Core.Install +/// +/// Helper to test File and folder permissions +/// +public interface IFilePermissionHelper { /// - /// Helper to test File and folder permissions + /// Run all tests for permissions of the required files and folders. /// - public interface IFilePermissionHelper - { - /// - /// Run all tests for permissions of the required files and folders. - /// - /// True if all permissions are correct. False otherwise. - bool RunFilePermissionTestSuite(out Dictionary> report); - - } + /// True if all permissions are correct. False otherwise. + bool RunFilePermissionTestSuite(out Dictionary> report); } diff --git a/src/Umbraco.Core/Install/InstallException.cs b/src/Umbraco.Core/Install/InstallException.cs index 2ec741d200c5..fcb878c67724 100644 --- a/src/Umbraco.Core/Install/InstallException.cs +++ b/src/Umbraco.Core/Install/InstallException.cs @@ -1,106 +1,122 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Install +namespace Umbraco.Cms.Core.Install; + +/// +/// Used for steps to be able to return a JSON structure back to the UI. +/// +/// +[Serializable] +public class InstallException : Exception { /// - /// Used for steps to be able to return a JSON structure back to the UI. + /// Initializes a new instance of the class. /// - /// - [Serializable] - public class InstallException : Exception + public InstallException() { - /// - /// Gets the view. - /// - /// - /// The view. - /// - public string? View { get; private set; } + } - /// - /// Gets the view model. - /// - /// - /// The view model. - /// - /// - /// This object is not included when serializing. - /// - public object? ViewModel { get; private set; } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public InstallException(string message) + : this(message, "error", null) + { + } - /// - /// Initializes a new instance of the class. - /// - public InstallException() - { } + /// + /// Initializes a new instance of the class. + /// + /// The message. + /// The view model. + public InstallException(string message, object viewModel) + : this(message, "error", viewModel) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public InstallException(string message) - : this(message, "error", null) - { } + /// + /// Initializes a new instance of the class. + /// + /// The message. + /// The view. + /// The view model. + public InstallException(string message, string view, object? viewModel) + : base(message) + { + View = view; + ViewModel = viewModel; + } - /// - /// Initializes a new instance of the class. - /// - /// The message. - /// The view model. - public InstallException(string message, object viewModel) - : this(message, "error", viewModel) - { } + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// + /// The exception that is the cause of the current exception, or a null reference ( + /// in Visual Basic) if no inner exception is specified. + /// + public InstallException(string message, Exception innerException) + : base(message, innerException) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The message. - /// The view. - /// The view model. - public InstallException(string message, string view, object? viewModel) - : base(message) - { - View = view; - ViewModel = viewModel; - } + /// + /// Initializes a new instance of the class. + /// + /// + /// The that holds the serialized object + /// data about the exception being thrown. + /// + /// + /// The that contains contextual + /// information about the source or destination. + /// + protected InstallException(SerializationInfo info, StreamingContext context) + : base(info, context) => + View = info.GetString(nameof(View)); - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. - public InstallException(string message, Exception innerException) - : base(message, innerException) - { } + /// + /// Gets the view. + /// + /// + /// The view. + /// + public string? View { get; private set; } - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected InstallException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - View = info.GetString(nameof(View)); - } + /// + /// Gets the view model. + /// + /// + /// The view model. + /// + /// + /// This object is not included when serializing. + /// + public object? ViewModel { get; private set; } - /// - /// When overridden in a derived class, sets the with information about the exception. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - /// info - public override void GetObjectData(SerializationInfo info, StreamingContext context) + /// + /// When overridden in a derived class, sets the with + /// information about the exception. + /// + /// + /// The that holds the serialized object + /// data about the exception being thrown. + /// + /// + /// The that contains contextual + /// information about the source or destination. + /// + /// info + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } + throw new ArgumentNullException(nameof(info)); + } - info.AddValue(nameof(View), View); + info.AddValue(nameof(View), View); - base.GetObjectData(info, context); - } + base.GetObjectData(info, context); } } diff --git a/src/Umbraco.Core/Install/InstallStatusTracker.cs b/src/Umbraco.Core/Install/InstallStatusTracker.cs index 1ee7f685d429..fa45db6b9c09 100644 --- a/src/Umbraco.Core/Install/InstallStatusTracker.cs +++ b/src/Umbraco.Core/Install/InstallStatusTracker.cs @@ -1,74 +1,101 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Umbraco.Cms.Core.Collections; +using Umbraco.Cms.Core.Collections; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Install.Models; using Umbraco.Cms.Core.Serialization; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Install +namespace Umbraco.Cms.Core.Install; + +/// +/// An internal in-memory status tracker for the current installation +/// +public class InstallStatusTracker { - /// - /// An internal in-memory status tracker for the current installation - /// - public class InstallStatusTracker + private static ConcurrentHashSet _steps = new(); + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IJsonSerializer _jsonSerializer; + + public InstallStatusTracker(IHostingEnvironment hostingEnvironment, IJsonSerializer jsonSerializer) { - private readonly IHostingEnvironment _hostingEnvironment; - private readonly IJsonSerializer _jsonSerializer; + _hostingEnvironment = hostingEnvironment; + _jsonSerializer = jsonSerializer; + } - public InstallStatusTracker(IHostingEnvironment hostingEnvironment, IJsonSerializer jsonSerializer) - { - _hostingEnvironment = hostingEnvironment; - _jsonSerializer = jsonSerializer; - } + private string GetFile(Guid installId) + { + var file = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempData.EnsureEndsWith('/') + + "Install/" + + "install_" + + installId.ToString("N") + + ".txt"); + return file; + } - private static ConcurrentHashSet _steps = new ConcurrentHashSet(); + public void Reset() + { + _steps = new ConcurrentHashSet(); + ClearFiles(); + } - private string GetFile(Guid installId) + public void ClearFiles() + { + var dir = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempData.EnsureEndsWith('/') + + "Install/"); + if (Directory.Exists(dir)) { - var file = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "Install/" - + "install_" - + installId.ToString("N") - + ".txt"); - return file; + var files = Directory.GetFiles(dir); + foreach (var f in files) + { + File.Delete(f); + } } - - public void Reset() + else { - _steps = new ConcurrentHashSet(); - ClearFiles(); + Directory.CreateDirectory(dir); } + } - public void ClearFiles() + public IEnumerable InitializeFromFile(Guid installId) + { + //check if we have our persisted file and read it + var file = GetFile(installId); + if (File.Exists(file)) { - var dir = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "Install/"); - if (Directory.Exists(dir)) + IEnumerable deserialized = + _jsonSerializer.Deserialize>( + File.ReadAllText(file)); + if (deserialized is not null) { - var files = Directory.GetFiles(dir); - foreach (var f in files) + foreach (InstallTrackingItem item in deserialized) { - File.Delete(f); + _steps.Add(item); } } - else - { - Directory.CreateDirectory(dir); - } + } + else + { + throw new InvalidOperationException("Cannot initialize from file, the installation file with id " + + installId + " does not exist"); } - public IEnumerable InitializeFromFile(Guid installId) + return new List(_steps); + } + + public IEnumerable Initialize(Guid installId, IEnumerable steps) + { + //if there are no steps in memory + if (_steps.Count == 0) { //check if we have our persisted file and read it var file = GetFile(installId); if (File.Exists(file)) { - var deserialized = _jsonSerializer.Deserialize>( - File.ReadAllText(file)); + IEnumerable deserialized = + _jsonSerializer.Deserialize>( + File.ReadAllText(file)); if (deserialized is not null) { - foreach (var item in deserialized) + foreach (InstallTrackingItem item in deserialized) { _steps.Add(item); } @@ -76,81 +103,54 @@ public IEnumerable InitializeFromFile(Guid installId) } else { - throw new InvalidOperationException("Cannot initialize from file, the installation file with id " + installId + " does not exist"); - } - return new List(_steps); - } + ClearFiles(); - public IEnumerable Initialize(Guid installId, IEnumerable steps) - { - //if there are no steps in memory - if (_steps.Count == 0) - { - //check if we have our persisted file and read it - var file = GetFile(installId); - if (File.Exists(file)) + //otherwise just create the steps in memory (brand new install) + foreach (InstallSetupStep step in steps.OrderBy(x => x.ServerOrder)) { - var deserialized = _jsonSerializer.Deserialize>( - File.ReadAllText(file)); - if (deserialized is not null) - { - foreach (var item in deserialized) - { - _steps.Add(item); - } - } + _steps.Add(new InstallTrackingItem(step.Name, step.ServerOrder)); } - else - { - ClearFiles(); - //otherwise just create the steps in memory (brand new install) - foreach (var step in steps.OrderBy(x => x.ServerOrder)) - { - _steps.Add(new InstallTrackingItem(step.Name, step.ServerOrder)); - } - //save the file - var serialized = _jsonSerializer.Serialize(new List(_steps)); - Directory.CreateDirectory(Path.GetDirectoryName(file)!); - File.WriteAllText(file, serialized); - } + //save the file + var serialized = _jsonSerializer.Serialize(new List(_steps)); + Directory.CreateDirectory(Path.GetDirectoryName(file)!); + File.WriteAllText(file, serialized); } - else - { - //ensure that the file exists with the current install id - var file = GetFile(installId); - if (File.Exists(file) == false) - { - ClearFiles(); - - //save the correct file - var serialized = _jsonSerializer.Serialize(new List(_steps)); - Directory.CreateDirectory(Path.GetDirectoryName(file)!); - File.WriteAllText(file, serialized); - } - } - - return new List(_steps); } - - public void SetComplete(Guid installId, string name, IDictionary? additionalData = null) + else { - var trackingItem = _steps.Single(x => x.Name == name); - if (additionalData != null) + //ensure that the file exists with the current install id + var file = GetFile(installId); + if (File.Exists(file) == false) { - trackingItem.AdditionalData = additionalData; - } - trackingItem.IsComplete = true; + ClearFiles(); - //save the file - var file = GetFile(installId); - var serialized = _jsonSerializer.Serialize(new List(_steps)); - File.WriteAllText(file, serialized); + //save the correct file + var serialized = _jsonSerializer.Serialize(new List(_steps)); + Directory.CreateDirectory(Path.GetDirectoryName(file)!); + File.WriteAllText(file, serialized); + } } - public static IEnumerable GetStatus() + return new List(_steps); + } + + public void SetComplete(Guid installId, string name, IDictionary? additionalData = null) + { + InstallTrackingItem trackingItem = _steps.Single(x => x.Name == name); + if (additionalData != null) { - return new List(_steps).OrderBy(x => x.ServerOrder); + trackingItem.AdditionalData = additionalData; } + + trackingItem.IsComplete = true; + + //save the file + var file = GetFile(installId); + var serialized = _jsonSerializer.Serialize(new List(_steps)); + File.WriteAllText(file, serialized); } + + public static IEnumerable GetStatus() => + new List(_steps).OrderBy(x => x.ServerOrder); } diff --git a/src/Umbraco.Core/Install/InstallSteps/FilePermissionsStep.cs b/src/Umbraco.Core/Install/InstallSteps/FilePermissionsStep.cs index 14d77ecb7711..ff42ac472988 100644 --- a/src/Umbraco.Core/Install/InstallSteps/FilePermissionsStep.cs +++ b/src/Umbraco.Core/Install/InstallSteps/FilePermissionsStep.cs @@ -1,56 +1,55 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Umbraco.Cms.Core.Install.Models; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Install.InstallSteps +namespace Umbraco.Cms.Core.Install.InstallSteps; + +/// +/// Represents a step in the installation that ensure all the required permissions on files and folders are correct. +/// +[InstallSetupStep( + InstallationType.NewInstall | InstallationType.Upgrade, + "Permissions", + 0, + "", + PerformsAppRestart = true)] +public class FilePermissionsStep : InstallSetupStep { + private readonly IFilePermissionHelper _filePermissionHelper; + private readonly ILocalizedTextService _localizedTextService; + /// - /// Represents a step in the installation that ensure all the required permissions on files and folders are correct. + /// Initializes a new instance of the class. /// - [InstallSetupStep( - InstallationType.NewInstall | InstallationType.Upgrade, - "Permissions", - 0, - "", - PerformsAppRestart = true)] - public class FilePermissionsStep : InstallSetupStep + public FilePermissionsStep( + IFilePermissionHelper filePermissionHelper, + ILocalizedTextService localizedTextService) { - private readonly IFilePermissionHelper _filePermissionHelper; - private readonly ILocalizedTextService _localizedTextService; - - /// - /// Initializes a new instance of the class. - /// - public FilePermissionsStep( - IFilePermissionHelper filePermissionHelper, - ILocalizedTextService localizedTextService) - { - _filePermissionHelper = filePermissionHelper; - _localizedTextService = localizedTextService; - } - - /// - public override Task ExecuteAsync(object model) - { - // validate file permissions - var permissionsOk = _filePermissionHelper.RunFilePermissionTestSuite(out Dictionary> report); + _filePermissionHelper = filePermissionHelper; + _localizedTextService = localizedTextService; + } - var translatedErrors = report.ToDictionary(x => _localizedTextService.Localize("permissions", x.Key), x => x.Value); - if (permissionsOk == false) - { - throw new InstallException("Permission check failed", "permissionsreport", new { errors = translatedErrors }); - } + /// + public override Task ExecuteAsync(object model) + { + // validate file permissions + var permissionsOk = + _filePermissionHelper.RunFilePermissionTestSuite( + out Dictionary> report); - return Task.FromResult(null); + var translatedErrors = + report.ToDictionary(x => _localizedTextService.Localize("permissions", x.Key), x => x.Value); + if (permissionsOk == false) + { + throw new InstallException("Permission check failed", "permissionsreport", new {errors = translatedErrors}); } - /// - public override bool RequiresExecution(object model) => true; + return Task.FromResult(null); } + + /// + public override bool RequiresExecution(object model) => true; } diff --git a/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs b/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs index 15286d249f53..8e21f6e28040 100644 --- a/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs +++ b/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs @@ -1,6 +1,4 @@ -using System; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration; @@ -9,47 +7,46 @@ using Umbraco.Cms.Core.Telemetry; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.Install.InstallSteps +namespace Umbraco.Cms.Core.Install.InstallSteps; + +[InstallSetupStep(InstallationType.NewInstall | InstallationType.Upgrade, + "TelemetryIdConfiguration", 0, "", + PerformsAppRestart = false)] +public class TelemetryIdentifierStep : InstallSetupStep { - [InstallSetupStep(InstallationType.NewInstall | InstallationType.Upgrade, - "TelemetryIdConfiguration", 0, "", - PerformsAppRestart = false)] - public class TelemetryIdentifierStep : InstallSetupStep - { - private readonly IOptions _globalSettings; - private readonly ISiteIdentifierService _siteIdentifierService; + private readonly IOptions _globalSettings; + private readonly ISiteIdentifierService _siteIdentifierService; - public TelemetryIdentifierStep( - IOptions globalSettings, - ISiteIdentifierService siteIdentifierService) - { - _globalSettings = globalSettings; - _siteIdentifierService = siteIdentifierService; - } + public TelemetryIdentifierStep( + IOptions globalSettings, + ISiteIdentifierService siteIdentifierService) + { + _globalSettings = globalSettings; + _siteIdentifierService = siteIdentifierService; + } - [Obsolete("Use constructor that takes GlobalSettings and ISiteIdentifierService")] - public TelemetryIdentifierStep( - ILogger logger, - IOptions globalSettings, - IConfigManipulator configManipulator) + [Obsolete("Use constructor that takes GlobalSettings and ISiteIdentifierService")] + public TelemetryIdentifierStep( + ILogger logger, + IOptions globalSettings, + IConfigManipulator configManipulator) : this(globalSettings, StaticServiceProvider.Instance.GetRequiredService()) - { - } + { + } - public override Task ExecuteAsync(object model) - { - _siteIdentifierService.TryCreateSiteIdentifier(out _); - return Task.FromResult(null); - } + public override Task ExecuteAsync(object model) + { + _siteIdentifierService.TryCreateSiteIdentifier(out _); + return Task.FromResult(null); + } - public override bool RequiresExecution(object model) - { - // Verify that Json value is not empty string - // Try & get a value stored in appSettings.json - var backofficeIdentifierRaw = _globalSettings.Value.Id; + public override bool RequiresExecution(object model) + { + // Verify that Json value is not empty string + // Try & get a value stored in appSettings.json + var backofficeIdentifierRaw = _globalSettings.Value.Id; - // No need to add Id again if already found - return string.IsNullOrEmpty(backofficeIdentifierRaw); - } + // No need to add Id again if already found + return string.IsNullOrEmpty(backofficeIdentifierRaw); } } diff --git a/src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs b/src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs index 6530983de22f..53973f4b0843 100644 --- a/src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs +++ b/src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs @@ -1,51 +1,67 @@ -using System; -using System.Threading.Tasks; -using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Install.Models; using Umbraco.Cms.Core.Semver; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Install.InstallSteps -{ - /// - /// This step is purely here to show the button to commence the upgrade - /// - [InstallSetupStep(InstallationType.Upgrade, "Upgrade", "upgrade", 1, "Upgrading Umbraco to the latest and greatest version.")] - public class UpgradeStep : InstallSetupStep - { - public override bool RequiresExecution(object model) => true; - private readonly IUmbracoVersion _umbracoVersion; - private readonly IRuntimeState _runtimeState; - public UpgradeStep(IUmbracoVersion umbracoVersion, IRuntimeState runtimeState) - { - _umbracoVersion = umbracoVersion; - _runtimeState = runtimeState; - } +namespace Umbraco.Cms.Core.Install.InstallSteps; - public override Task ExecuteAsync(object model) => Task.FromResult(null); +/// +/// This step is purely here to show the button to commence the upgrade +/// +[InstallSetupStep(InstallationType.Upgrade, "Upgrade", "upgrade", 1, + "Upgrading Umbraco to the latest and greatest version.")] +public class UpgradeStep : InstallSetupStep +{ + private readonly IRuntimeState _runtimeState; + private readonly IUmbracoVersion _umbracoVersion; - public override object ViewModel + public UpgradeStep(IUmbracoVersion umbracoVersion, IRuntimeState runtimeState) + { + _umbracoVersion = umbracoVersion; + _runtimeState = runtimeState; + } + + public override object ViewModel + { + get { - get + string FormatGuidState(string? value) { - string FormatGuidState(string? value) + if (string.IsNullOrWhiteSpace(value)) { - if (string.IsNullOrWhiteSpace(value)) value = "unknown"; - else if (Guid.TryParse(value, out var currentStateGuid)) - value = currentStateGuid.ToString("N").Substring(0, 8); - return value; + value = "unknown"; + } + else if (Guid.TryParse(value, out Guid currentStateGuid)) + { + value = currentStateGuid.ToString("N").Substring(0, 8); } - var currentState = FormatGuidState(_runtimeState.CurrentMigrationState); - var newState = FormatGuidState(_runtimeState.FinalMigrationState); - var newVersion = _umbracoVersion.SemanticVersion?.ToSemanticStringWithoutBuild(); - var oldVersion = new SemVersion(_umbracoVersion.SemanticVersion?.Major ?? 0, 0, 0).ToString(); //TODO can we find the old version somehow? e.g. from current state + return value; + } - var reportUrl = $"https://our.umbraco.com/contribute/releases/compare?from={oldVersion}&to={newVersion}¬es=1"; + var currentState = FormatGuidState(_runtimeState.CurrentMigrationState); + var newState = FormatGuidState(_runtimeState.FinalMigrationState); + var newVersion = _umbracoVersion.SemanticVersion?.ToSemanticStringWithoutBuild(); + var oldVersion = + new SemVersion(_umbracoVersion.SemanticVersion?.Major ?? 0) + .ToString(); //TODO can we find the old version somehow? e.g. from current state - return new { oldVersion, newVersion, currentState, newState, reportUrl }; - } + var reportUrl = + $"https://our.umbraco.com/contribute/releases/compare?from={oldVersion}&to={newVersion}¬es=1"; + + return new + { + oldVersion, + newVersion, + currentState, + newState, + reportUrl + }; } } + + public override bool RequiresExecution(object model) => true; + + public override Task ExecuteAsync(object model) => Task.FromResult(null); } diff --git a/src/Umbraco.Core/Install/Models/DatabaseModel.cs b/src/Umbraco.Core/Install/Models/DatabaseModel.cs index da2f61fce5dc..40310e4a984b 100644 --- a/src/Umbraco.Core/Install/Models/DatabaseModel.cs +++ b/src/Umbraco.Core/Install/Models/DatabaseModel.cs @@ -1,33 +1,25 @@ -using System; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Install.Models +namespace Umbraco.Cms.Core.Install.Models; + +[DataContract(Name = "database", Namespace = "")] +public class DatabaseModel { - [DataContract(Name = "database", Namespace = "")] - public class DatabaseModel - { - [DataMember(Name = "databaseProviderMetadataId")] - public Guid DatabaseProviderMetadataId { get; set; } + [DataMember(Name = "databaseProviderMetadataId")] + public Guid DatabaseProviderMetadataId { get; set; } - [DataMember(Name = "providerName")] - public string? ProviderName { get; set; } + [DataMember(Name = "providerName")] public string? ProviderName { get; set; } - [DataMember(Name = "server")] - public string Server { get; set; } = null!; + [DataMember(Name = "server")] public string Server { get; set; } = null!; - [DataMember(Name = "databaseName")] - public string DatabaseName { get; set; } = null!; + [DataMember(Name = "databaseName")] public string DatabaseName { get; set; } = null!; - [DataMember(Name = "login")] - public string Login { get; set; } = null!; + [DataMember(Name = "login")] public string Login { get; set; } = null!; - [DataMember(Name = "password")] - public string Password { get; set; } = null!; + [DataMember(Name = "password")] public string Password { get; set; } = null!; - [DataMember(Name = "integratedAuth")] - public bool IntegratedAuth { get; set; } + [DataMember(Name = "integratedAuth")] public bool IntegratedAuth { get; set; } - [DataMember(Name = "connectionString")] - public string? ConnectionString { get; set; } - } + [DataMember(Name = "connectionString")] + public string? ConnectionString { get; set; } } diff --git a/src/Umbraco.Core/Install/Models/InstallInstructions.cs b/src/Umbraco.Core/Install/Models/InstallInstructions.cs index 9dc42553e09a..2c99a1ef917a 100644 --- a/src/Umbraco.Core/Install/Models/InstallInstructions.cs +++ b/src/Umbraco.Core/Install/Models/InstallInstructions.cs @@ -1,16 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Install.Models +namespace Umbraco.Cms.Core.Install.Models; + +[DataContract(Name = "installInstructions", Namespace = "")] +public class InstallInstructions { - [DataContract(Name = "installInstructions", Namespace = "")] - public class InstallInstructions - { - [DataMember(Name = "instructions")] - public IDictionary? Instructions { get; set; } + [DataMember(Name = "instructions")] public IDictionary? Instructions { get; set; } - [DataMember(Name = "installId")] - public Guid InstallId { get; set; } - } + [DataMember(Name = "installId")] public Guid InstallId { get; set; } } diff --git a/src/Umbraco.Core/Install/Models/InstallProgressResultModel.cs b/src/Umbraco.Core/Install/Models/InstallProgressResultModel.cs index 43b3fc73fe16..178797ec5c29 100644 --- a/src/Umbraco.Core/Install/Models/InstallProgressResultModel.cs +++ b/src/Umbraco.Core/Install/Models/InstallProgressResultModel.cs @@ -1,42 +1,39 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Install.Models +namespace Umbraco.Cms.Core.Install.Models; + +/// +/// Returned to the UI for each installation step that is completed +/// +[DataContract(Name = "result", Namespace = "")] +public class InstallProgressResultModel { + public InstallProgressResultModel(bool processComplete, string stepCompleted, string nextStep, string? view = null, + object? viewModel = null) + { + ProcessComplete = processComplete; + StepCompleted = stepCompleted; + NextStep = nextStep; + ViewModel = viewModel; + View = view; + } /// - /// Returned to the UI for each installation step that is completed + /// The UI view to show when this step executes, by default no views are shown for the completion of a step unless + /// explicitly specified. /// - [DataContract(Name = "result", Namespace = "")] - public class InstallProgressResultModel - { - public InstallProgressResultModel(bool processComplete, string stepCompleted, string nextStep, string? view = null, object? viewModel = null) - { - ProcessComplete = processComplete; - StepCompleted = stepCompleted; - NextStep = nextStep; - ViewModel = viewModel; - View = view; - } + [DataMember(Name = "view")] + public string? View { get; private set; } - /// - /// The UI view to show when this step executes, by default no views are shown for the completion of a step unless explicitly specified. - /// - [DataMember(Name = "view")] - public string? View { get; private set; } + [DataMember(Name = "complete")] public bool ProcessComplete { get; set; } - [DataMember(Name = "complete")] - public bool ProcessComplete { get; set; } + [DataMember(Name = "stepCompleted")] public string StepCompleted { get; set; } - [DataMember(Name = "stepCompleted")] - public string StepCompleted { get; set; } + [DataMember(Name = "nextStep")] public string NextStep { get; set; } - [DataMember(Name = "nextStep")] - public string NextStep { get; set; } - - /// - /// The view model to return to the UI if this step is returning a view (optional) - /// - [DataMember(Name = "model")] - public object? ViewModel { get; private set; } - } + /// + /// The view model to return to the UI if this step is returning a view (optional) + /// + [DataMember(Name = "model")] + public object? ViewModel { get; private set; } } diff --git a/src/Umbraco.Core/Install/Models/InstallSetup.cs b/src/Umbraco.Core/Install/Models/InstallSetup.cs index 358bd922347b..824b993ca04c 100644 --- a/src/Umbraco.Core/Install/Models/InstallSetup.cs +++ b/src/Umbraco.Core/Install/Models/InstallSetup.cs @@ -1,26 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Install.Models +namespace Umbraco.Cms.Core.Install.Models; + +/// +/// Model containing all the install steps for setting up the UI +/// +[DataContract(Name = "installSetup", Namespace = "")] +public class InstallSetup { - /// - /// Model containing all the install steps for setting up the UI - /// - [DataContract(Name = "installSetup", Namespace = "")] - public class InstallSetup + public InstallSetup() { - public InstallSetup() - { - Steps = new List(); - InstallId = Guid.NewGuid(); - } - - [DataMember(Name = "installId")] - public Guid InstallId { get; private set; } + Steps = new List(); + InstallId = Guid.NewGuid(); + } - [DataMember(Name = "steps")] - public IEnumerable Steps { get; set; } + [DataMember(Name = "installId")] public Guid InstallId { get; private set; } - } + [DataMember(Name = "steps")] public IEnumerable Steps { get; set; } } diff --git a/src/Umbraco.Core/Install/Models/InstallSetupResult.cs b/src/Umbraco.Core/Install/Models/InstallSetupResult.cs index 15a4c12b4723..6072c64714b9 100644 --- a/src/Umbraco.Core/Install/Models/InstallSetupResult.cs +++ b/src/Umbraco.Core/Install/Models/InstallSetupResult.cs @@ -1,47 +1,42 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Install.Models; -namespace Umbraco.Cms.Core.Install.Models +/// +/// The object returned from each installation step +/// +public class InstallSetupResult { - /// - /// The object returned from each installation step - /// - public class InstallSetupResult + public InstallSetupResult() { - public InstallSetupResult() - { - } + } - public InstallSetupResult(IDictionary savedStepData, string view, object? viewModel = null) - { - ViewModel = viewModel; - SavedStepData = savedStepData; - View = view; - } + public InstallSetupResult(IDictionary savedStepData, string view, object? viewModel = null) + { + ViewModel = viewModel; + SavedStepData = savedStepData; + View = view; + } - public InstallSetupResult(IDictionary savedStepData) - { - SavedStepData = savedStepData; - } + public InstallSetupResult(IDictionary savedStepData) => SavedStepData = savedStepData; - public InstallSetupResult(string view, object? viewModel = null) - { - ViewModel = viewModel; - View = view; - } + public InstallSetupResult(string view, object? viewModel = null) + { + ViewModel = viewModel; + View = view; + } - /// - /// Data that is persisted to the installation file which can be used from other installation steps - /// - public IDictionary? SavedStepData { get; private set; } + /// + /// Data that is persisted to the installation file which can be used from other installation steps + /// + public IDictionary? SavedStepData { get; } - /// - /// The UI view to show when this step executes, by default no views are shown for the completion of a step unless explicitly specified. - /// - public string? View { get; private set; } + /// + /// The UI view to show when this step executes, by default no views are shown for the completion of a step unless + /// explicitly specified. + /// + public string? View { get; } - /// - /// The view model to return to the UI if this step is returning a view (optional) - /// - public object? ViewModel { get; private set; } - } + /// + /// The view model to return to the UI if this step is returning a view (optional) + /// + public object? ViewModel { get; } } diff --git a/src/Umbraco.Core/Install/Models/InstallSetupStep.cs b/src/Umbraco.Core/Install/Models/InstallSetupStep.cs index 766458f99fb5..3b855abc7f40 100644 --- a/src/Umbraco.Core/Install/Models/InstallSetupStep.cs +++ b/src/Umbraco.Core/Install/Models/InstallSetupStep.cs @@ -1,87 +1,79 @@ -using System; -using System.Runtime.Serialization; -using System.Threading.Tasks; +using System.Runtime.Serialization; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Install.Models +namespace Umbraco.Cms.Core.Install.Models; + +/// +/// Model to give to the front-end to collect the information for each step +/// +[DataContract(Name = "step", Namespace = "")] +public abstract class InstallSetupStep : InstallSetupStep { /// - /// Model to give to the front-end to collect the information for each step + /// Defines the step model type on the server side so we can bind it /// - [DataContract(Name = "step", Namespace = "")] - public abstract class InstallSetupStep : InstallSetupStep - { - /// - /// Defines the step model type on the server side so we can bind it - /// - [IgnoreDataMember] - public override Type StepType => typeof(T); + [IgnoreDataMember] + public override Type StepType => typeof(T); - /// - /// The step execution method - /// - /// - /// - public abstract Task ExecuteAsync(T model); + /// + /// The step execution method + /// + /// + /// + public abstract Task ExecuteAsync(T model); - /// - /// Determines if this step needs to execute based on the current state of the application and/or install process - /// - /// - public abstract bool RequiresExecution(T model); - } + /// + /// Determines if this step needs to execute based on the current state of the application and/or install process + /// + /// + public abstract bool RequiresExecution(T model); +} - [DataContract(Name = "step", Namespace = "")] - public abstract class InstallSetupStep +[DataContract(Name = "step", Namespace = "")] +public abstract class InstallSetupStep +{ + protected InstallSetupStep() { - protected InstallSetupStep() + InstallSetupStepAttribute att = GetType().GetCustomAttribute(false); + if (att == null) { - var att = GetType().GetCustomAttribute(false); - if (att == null) - { - throw new InvalidOperationException("Each step must be attributed"); - } - Name = att.Name; - View = att.View; - ServerOrder = att.ServerOrder; - Description = att.Description; - InstallTypeTarget = att.InstallTypeTarget; - PerformsAppRestart = att.PerformsAppRestart; + throw new InvalidOperationException("Each step must be attributed"); } - [DataMember(Name = "name")] - public string Name { get; private set; } + Name = att.Name; + View = att.View; + ServerOrder = att.ServerOrder; + Description = att.Description; + InstallTypeTarget = att.InstallTypeTarget; + PerformsAppRestart = att.PerformsAppRestart; + } - [DataMember(Name = "view")] - public virtual string View { get; private set; } + [DataMember(Name = "name")] public string Name { get; private set; } - /// - /// The view model used to render the view, by default is null but can be populated - /// - [DataMember(Name = "model")] - public virtual object? ViewModel { get; private set; } + [DataMember(Name = "view")] public virtual string View { get; private set; } - [DataMember(Name = "description")] - public string Description { get; private set; } + /// + /// The view model used to render the view, by default is null but can be populated + /// + [DataMember(Name = "model")] + public virtual object? ViewModel { get; private set; } - [IgnoreDataMember] - public InstallationType InstallTypeTarget { get; private set; } + [DataMember(Name = "description")] public string Description { get; private set; } - [IgnoreDataMember] - public bool PerformsAppRestart { get; private set; } + [IgnoreDataMember] public InstallationType InstallTypeTarget { get; } - /// - /// Defines what order this step needs to execute on the server side since the - /// steps might be shown out of order on the front-end - /// - [DataMember(Name = "serverOrder")] - public int ServerOrder { get; private set; } + [IgnoreDataMember] public bool PerformsAppRestart { get; } - /// - /// Defines the step model type on the server side so we can bind it - /// - [IgnoreDataMember] - public abstract Type StepType { get; } + /// + /// Defines what order this step needs to execute on the server side since the + /// steps might be shown out of order on the front-end + /// + [DataMember(Name = "serverOrder")] + public int ServerOrder { get; private set; } - } + /// + /// Defines the step model type on the server side so we can bind it + /// + [IgnoreDataMember] + public abstract Type StepType { get; } } diff --git a/src/Umbraco.Core/Install/Models/InstallSetupStepAttribute.cs b/src/Umbraco.Core/Install/Models/InstallSetupStepAttribute.cs index 7feaced0521f..ea6ef727cd0f 100644 --- a/src/Umbraco.Core/Install/Models/InstallSetupStepAttribute.cs +++ b/src/Umbraco.Core/Install/Models/InstallSetupStepAttribute.cs @@ -1,44 +1,45 @@ -using System; +namespace Umbraco.Cms.Core.Install.Models; -namespace Umbraco.Cms.Core.Install.Models +public sealed class InstallSetupStepAttribute : Attribute { - public sealed class InstallSetupStepAttribute : Attribute + public InstallSetupStepAttribute(InstallationType installTypeTarget, string name, string view, int serverOrder, + string description) { - public InstallSetupStepAttribute(InstallationType installTypeTarget, string name, string view, int serverOrder, string description) - { - InstallTypeTarget = installTypeTarget; - Name = name; - View = view; - ServerOrder = serverOrder; - Description = description; + InstallTypeTarget = installTypeTarget; + Name = name; + View = view; + ServerOrder = serverOrder; + Description = description; - //default - PerformsAppRestart = false; - } + //default + PerformsAppRestart = false; + } - public InstallSetupStepAttribute(InstallationType installTypeTarget, string name, int serverOrder, string description) - { - InstallTypeTarget = installTypeTarget; - Name = name; - View = string.Empty; - ServerOrder = serverOrder; - Description = description; + public InstallSetupStepAttribute(InstallationType installTypeTarget, string name, int serverOrder, + string description) + { + InstallTypeTarget = installTypeTarget; + Name = name; + View = string.Empty; + ServerOrder = serverOrder; + Description = description; - //default - PerformsAppRestart = false; - } + //default + PerformsAppRestart = false; + } - public InstallationType InstallTypeTarget { get; private set; } - public string Name { get; private set; } - public string View { get; private set; } - public int ServerOrder { get; private set; } - public string Description { get; private set; } + public InstallationType InstallTypeTarget { get; } + public string Name { get; } + public string View { get; } + public int ServerOrder { get; } + public string Description { get; } - /// - /// A flag to notify the installer that this step performs an app pool restart, this can be handy to know since if the current - /// step is performing a restart, we cannot 'look ahead' to see if the next step can execute since we won't know until the app pool - /// is restarted. - /// - public bool PerformsAppRestart { get; set; } - } + /// + /// A flag to notify the installer that this step performs an app pool restart, this can be handy to know since if the + /// current + /// step is performing a restart, we cannot 'look ahead' to see if the next step can execute since we won't know until + /// the app pool + /// is restarted. + /// + public bool PerformsAppRestart { get; set; } } diff --git a/src/Umbraco.Core/Install/Models/InstallTrackingItem.cs b/src/Umbraco.Core/Install/Models/InstallTrackingItem.cs index 3a34264d77fd..b12f1aabd5ac 100644 --- a/src/Umbraco.Core/Install/Models/InstallTrackingItem.cs +++ b/src/Umbraco.Core/Install/Models/InstallTrackingItem.cs @@ -1,37 +1,40 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Install.Models; -namespace Umbraco.Cms.Core.Install.Models +public class InstallTrackingItem { - public class InstallTrackingItem + public InstallTrackingItem(string name, int serverOrder) { - public InstallTrackingItem(string name, int serverOrder) - { - Name = name; - ServerOrder = serverOrder; - AdditionalData = new Dictionary(); - } + Name = name; + ServerOrder = serverOrder; + AdditionalData = new Dictionary(); + } + + public string Name { get; set; } + public int ServerOrder { get; set; } + public bool IsComplete { get; set; } + public IDictionary AdditionalData { get; set; } - public string Name { get; set; } - public int ServerOrder { get; set; } - public bool IsComplete { get; set; } - public IDictionary AdditionalData { get; set; } + protected bool Equals(InstallTrackingItem other) => string.Equals(Name, other.Name); - protected bool Equals(InstallTrackingItem other) + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) { - return string.Equals(Name, other.Name); + return false; } - public override bool Equals(object? obj) + if (ReferenceEquals(this, obj)) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((InstallTrackingItem) obj); + return true; } - public override int GetHashCode() + if (obj.GetType() != GetType()) { - return Name.GetHashCode(); + return false; } + + return Equals((InstallTrackingItem)obj); } + + public override int GetHashCode() => Name.GetHashCode(); } diff --git a/src/Umbraco.Core/Install/Models/InstallationType.cs b/src/Umbraco.Core/Install/Models/InstallationType.cs index 99ecf8ce1f7f..8fb11d761159 100644 --- a/src/Umbraco.Core/Install/Models/InstallationType.cs +++ b/src/Umbraco.Core/Install/Models/InstallationType.cs @@ -1,11 +1,8 @@ -using System; +namespace Umbraco.Cms.Core.Install.Models; -namespace Umbraco.Cms.Core.Install.Models +[Flags] +public enum InstallationType { - [Flags] - public enum InstallationType - { - NewInstall = 1 << 0, // 1 - Upgrade = 1 << 1, // 2 - } + NewInstall = 1 << 0, // 1 + Upgrade = 1 << 1 // 2 } diff --git a/src/Umbraco.Core/Install/Models/Package.cs b/src/Umbraco.Core/Install/Models/Package.cs index 3b9a204f1077..6ea6110da5e9 100644 --- a/src/Umbraco.Core/Install/Models/Package.cs +++ b/src/Umbraco.Core/Install/Models/Package.cs @@ -1,16 +1,13 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Install.Models +namespace Umbraco.Cms.Core.Install.Models; + +[DataContract(Name = "package")] +public class Package { - [DataContract(Name = "package")] - public class Package - { - [DataMember(Name = "name")] - public string? Name { get; set; } - [DataMember(Name = "thumbnail")] - public string? Thumbnail { get; set; } - [DataMember(Name = "id")] - public Guid Id { get; set; } - } + [DataMember(Name = "name")] public string? Name { get; set; } + + [DataMember(Name = "thumbnail")] public string? Thumbnail { get; set; } + + [DataMember(Name = "id")] public Guid Id { get; set; } } diff --git a/src/Umbraco.Core/Install/Models/UserModel.cs b/src/Umbraco.Core/Install/Models/UserModel.cs index 392730b3b6ef..034e34ed7c73 100644 --- a/src/Umbraco.Core/Install/Models/UserModel.cs +++ b/src/Umbraco.Core/Install/Models/UserModel.cs @@ -1,20 +1,16 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Install.Models +namespace Umbraco.Cms.Core.Install.Models; + +[DataContract(Name = "user", Namespace = "")] +public class UserModel { - [DataContract(Name = "user", Namespace = "")] - public class UserModel - { - [DataMember(Name = "name")] - public string Name { get; set; } = null!; + [DataMember(Name = "name")] public string Name { get; set; } = null!; - [DataMember(Name = "email")] - public string Email { get; set; } = null!; + [DataMember(Name = "email")] public string Email { get; set; } = null!; - [DataMember(Name = "password")] - public string Password { get; set; } = null!; + [DataMember(Name = "password")] public string Password { get; set; } = null!; - [DataMember(Name = "subscribeToNewsLetter")] - public bool SubscribeToNewsLetter { get; set; } - } + [DataMember(Name = "subscribeToNewsLetter")] + public bool SubscribeToNewsLetter { get; set; } } diff --git a/src/Umbraco.Core/InstallLog.cs b/src/Umbraco.Core/InstallLog.cs index 3d8ab26af96d..2cf2a1a59f04 100644 --- a/src/Umbraco.Core/InstallLog.cs +++ b/src/Umbraco.Core/InstallLog.cs @@ -1,34 +1,32 @@ -using System; +namespace Umbraco.Cms.Core; -namespace Umbraco.Cms.Core +public class InstallLog { - public class InstallLog + public InstallLog(Guid installId, bool isUpgrade, bool installCompleted, DateTime timestamp, int versionMajor, + int versionMinor, int versionPatch, string versionComment, string error, string? userAgent, string dbProvider) { - public Guid InstallId { get; } - public bool IsUpgrade { get; set; } - public bool InstallCompleted { get; set; } - public DateTime Timestamp { get; set; } - public int VersionMajor { get; } - public int VersionMinor { get; } - public int VersionPatch { get; } - public string VersionComment { get; } - public string Error { get; } - public string? UserAgent { get; } - public string DbProvider { get; set; } - - public InstallLog(Guid installId, bool isUpgrade, bool installCompleted, DateTime timestamp, int versionMajor, int versionMinor, int versionPatch, string versionComment, string error, string? userAgent, string dbProvider) - { - InstallId = installId; - IsUpgrade = isUpgrade; - InstallCompleted = installCompleted; - Timestamp = timestamp; - VersionMajor = versionMajor; - VersionMinor = versionMinor; - VersionPatch = versionPatch; - VersionComment = versionComment; - Error = error; - UserAgent = userAgent; - DbProvider = dbProvider; - } + InstallId = installId; + IsUpgrade = isUpgrade; + InstallCompleted = installCompleted; + Timestamp = timestamp; + VersionMajor = versionMajor; + VersionMinor = versionMinor; + VersionPatch = versionPatch; + VersionComment = versionComment; + Error = error; + UserAgent = userAgent; + DbProvider = dbProvider; } + + public Guid InstallId { get; } + public bool IsUpgrade { get; set; } + public bool InstallCompleted { get; set; } + public DateTime Timestamp { get; set; } + public int VersionMajor { get; } + public int VersionMinor { get; } + public int VersionPatch { get; } + public string VersionComment { get; } + public string Error { get; } + public string? UserAgent { get; } + public string DbProvider { get; set; } } diff --git a/src/Umbraco.Core/LambdaExpressionCacheKey.cs b/src/Umbraco.Core/LambdaExpressionCacheKey.cs index 123654bbe255..397732350fcf 100644 --- a/src/Umbraco.Core/LambdaExpressionCacheKey.cs +++ b/src/Umbraco.Core/LambdaExpressionCacheKey.cs @@ -1,83 +1,79 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; +using System.Linq.Expressions; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Represents a simple in a form which is suitable for using as a dictionary key +/// by exposing the return type, argument types and expression string form in a single concatenated string. +/// +public struct LambdaExpressionCacheKey { - /// - /// Represents a simple in a form which is suitable for using as a dictionary key - /// by exposing the return type, argument types and expression string form in a single concatenated string. - /// - public struct LambdaExpressionCacheKey + public LambdaExpressionCacheKey(string returnType, string expression, params string[] argTypes) { - public LambdaExpressionCacheKey(string returnType, string expression, params string[] argTypes) - { - ReturnType = returnType; - ExpressionAsString = expression; - ArgTypes = new HashSet(argTypes); - _toString = null; - } + ReturnType = returnType; + ExpressionAsString = expression; + ArgTypes = new HashSet(argTypes); + _toString = null; + } - public LambdaExpressionCacheKey(LambdaExpression obj) - { - ReturnType = obj.ReturnType.FullName; - ExpressionAsString = obj.ToString(); - ArgTypes = new HashSet(obj.Parameters.Select(x => x.Type.FullName)); - _toString = null; - } + public LambdaExpressionCacheKey(LambdaExpression obj) + { + ReturnType = obj.ReturnType.FullName; + ExpressionAsString = obj.ToString(); + ArgTypes = new HashSet(obj.Parameters.Select(x => x.Type.FullName)); + _toString = null; + } - /// - /// The argument type names of the - /// - public readonly HashSet ArgTypes; + /// + /// The argument type names of the + /// + public readonly HashSet ArgTypes; - /// - /// The return type of the - /// - public readonly string? ReturnType; + /// + /// The return type of the + /// + public readonly string? ReturnType; - /// - /// The original string representation of the - /// - public readonly string ExpressionAsString; + /// + /// The original string representation of the + /// + public readonly string ExpressionAsString; - private string? _toString; + private string? _toString; - /// - /// Returns a that represents this instance. - /// - /// - /// A that represents this instance. - /// - public override string ToString() - { - return _toString ?? (_toString = String.Concat(String.Join("|", ArgTypes), ",", ReturnType, ",", ExpressionAsString)); - } + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() => _toString ?? + (_toString = string.Concat(string.Join("|", ArgTypes), ",", ReturnType, ",", + ExpressionAsString)); - /// - /// Determines whether the specified is equal to this instance. - /// - /// The to compare with this instance. - /// - /// true if the specified is equal to this instance; otherwise, false. - /// - public override bool Equals(object? obj) + /// + /// Determines whether the specified is equal to this instance. + /// + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; otherwise, false. + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(obj, null)) { - if (ReferenceEquals(obj, null)) return false; - var casted = (LambdaExpressionCacheKey)obj; - return casted.ToString() == ToString(); + return false; } - /// - /// Returns a hash code for this instance. - /// - /// - /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - /// - public override int GetHashCode() - { - return ToString().GetHashCode(); - } + var casted = (LambdaExpressionCacheKey)obj; + return casted.ToString() == ToString(); } + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode() => ToString().GetHashCode(); } diff --git a/src/Umbraco.Core/Logging/DisposableTimer.cs b/src/Umbraco.Core/Logging/DisposableTimer.cs index a22ac75127b9..6c690b86f237 100644 --- a/src/Umbraco.Core/Logging/DisposableTimer.cs +++ b/src/Umbraco.Core/Logging/DisposableTimer.cs @@ -1,172 +1,178 @@ -using System; using System.Diagnostics; using Microsoft.Extensions.Logging; -namespace Umbraco.Cms.Core.Logging +namespace Umbraco.Cms.Core.Logging; + +/// +/// Starts the timer and invokes a callback upon disposal. Provides a simple way of timing an operation by wrapping it +/// in a using (C#) statement. +/// +public class DisposableTimer : DisposableObjectSlim { + private readonly string _endMessage; + private readonly object[]? _endMessageArgs; + private readonly object[]? _failMessageArgs; + private readonly LogLevel _level; + private readonly ILogger _logger; + private readonly Type _loggerType; + private readonly IDisposable? _profilerStep; + private readonly int _thresholdMilliseconds; + private readonly string _timingId; + private bool _failed; + private Exception? _failException; + private string? _failMessage; + + // internal - created by profiling logger + internal DisposableTimer( + ILogger logger, + LogLevel level, + IProfiler profiler, + Type loggerType, + string startMessage, + string endMessage, + string? failMessage = null, + object[]? startMessageArgs = null, + object[]? endMessageArgs = null, + object[]? failMessageArgs = null, + int thresholdMilliseconds = 0) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _level = level; + _loggerType = loggerType ?? throw new ArgumentNullException(nameof(loggerType)); + _endMessage = endMessage; + _failMessage = failMessage; + _endMessageArgs = endMessageArgs; + _failMessageArgs = failMessageArgs; + _thresholdMilliseconds = thresholdMilliseconds < 0 ? 0 : thresholdMilliseconds; + _timingId = Guid.NewGuid().ToString("N").Substring(0, 7); // keep it short-ish + + if (thresholdMilliseconds == 0) + { + switch (_level) + { + case LogLevel.Debug: + if (startMessageArgs == null) + { + logger.LogDebug("{StartMessage} [Timing {TimingId}]", startMessage, _timingId); + } + else + { + var args = new object[startMessageArgs.Length + 1]; + startMessageArgs.CopyTo(args, 0); + args[startMessageArgs.Length] = _timingId; + logger.LogDebug(startMessage + " [Timing {TimingId}]", args); + } + + break; + case LogLevel.Information: + if (startMessageArgs == null) + { + logger.LogInformation("{StartMessage} [Timing {TimingId}]", startMessage, _timingId); + } + else + { + var args = new object[startMessageArgs.Length + 1]; + startMessageArgs.CopyTo(args, 0); + args[startMessageArgs.Length] = _timingId; + logger.LogInformation(startMessage + " [Timing {TimingId}]", args); + } + + break; + default: + throw new ArgumentOutOfRangeException(nameof(level)); + } + } + + // else aren't logging the start message, this is output to the profiler but not the log, + // we just want the log to contain the result if it's more than the minimum ms threshold. + + _profilerStep = profiler?.Step(loggerType, startMessage); + } + + public Stopwatch Stopwatch { get; } = Stopwatch.StartNew(); + /// - /// Starts the timer and invokes a callback upon disposal. Provides a simple way of timing an operation by wrapping it in a using (C#) statement. + /// Reports a failure. /// - public class DisposableTimer : DisposableObjectSlim + /// The fail message. + /// The exception. + /// Completion of the timer will be reported as an error, with the specified message and exception. + public void Fail(string? failMessage = null, Exception? exception = null) { - private readonly ILogger _logger; - private readonly LogLevel _level; - private readonly Type _loggerType; - private readonly int _thresholdMilliseconds; - private readonly IDisposable? _profilerStep; - private readonly string _endMessage; - private string? _failMessage; - private readonly object[]? _endMessageArgs; - private readonly object[]? _failMessageArgs; - private Exception? _failException; - private bool _failed; - private readonly string _timingId; + _failed = true; + _failMessage = failMessage ?? _failMessage ?? "Failed."; + _failException = exception; + } - // internal - created by profiling logger - internal DisposableTimer( - ILogger logger, - LogLevel level, - IProfiler profiler, - Type loggerType, - string startMessage, - string endMessage, - string? failMessage = null, - object[]? startMessageArgs = null, - object[]? endMessageArgs = null, - object[]? failMessageArgs = null, - int thresholdMilliseconds = 0) - { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _level = level; - _loggerType = loggerType ?? throw new ArgumentNullException(nameof(loggerType)); - _endMessage = endMessage; - _failMessage = failMessage; - _endMessageArgs = endMessageArgs; - _failMessageArgs = failMessageArgs; - _thresholdMilliseconds = thresholdMilliseconds < 0 ? 0 : thresholdMilliseconds; - _timingId = Guid.NewGuid().ToString("N").Substring(0, 7); // keep it short-ish + /// + /// Disposes resources. + /// + /// Overrides abstract class which handles required locking. + protected override void DisposeResources() + { + Stopwatch.Stop(); + + _profilerStep?.Dispose(); - if (thresholdMilliseconds == 0) + if ((Stopwatch.ElapsedMilliseconds >= _thresholdMilliseconds || _failed) + && _loggerType != null && _logger != null + && (string.IsNullOrWhiteSpace(_endMessage) == false || _failed)) + { + if (_failed) + { + if (_failMessageArgs is null) + { + _logger.LogError(_failException, "{FailMessage} ({Duration}ms) [Timing {TimingId}]", _failMessage, + Stopwatch.ElapsedMilliseconds, _timingId); + } + else + { + var args = new object[_failMessageArgs.Length + 2]; + _failMessageArgs.CopyTo(args, 0); + args[_failMessageArgs.Length - 1] = Stopwatch.ElapsedMilliseconds; + args[_failMessageArgs.Length] = _timingId; + _logger.LogError(_failException, _failMessage + " ({Duration}ms) [Timing {TimingId}]", args); + } + } + else { switch (_level) { case LogLevel.Debug: - if (startMessageArgs == null) + if (_endMessageArgs == null) { - logger.LogDebug("{StartMessage} [Timing {TimingId}]", startMessage, _timingId); + _logger.LogDebug("{EndMessage} ({Duration}ms) [Timing {TimingId}]", _endMessage, + Stopwatch.ElapsedMilliseconds, _timingId); } else { - var args = new object[startMessageArgs.Length + 1]; - startMessageArgs.CopyTo(args, 0); - args[startMessageArgs.Length] = _timingId; - logger.LogDebug(startMessage + " [Timing {TimingId}]", args); + var args = new object[_endMessageArgs.Length + 2]; + _endMessageArgs.CopyTo(args, 0); + args[args.Length - 1] = Stopwatch.ElapsedMilliseconds; + args[args.Length] = _timingId; + _logger.LogDebug(_endMessage + " ({Duration}ms) [Timing {TimingId}]", args); } + break; case LogLevel.Information: - if (startMessageArgs == null) + if (_endMessageArgs == null) { - logger.LogInformation("{StartMessage} [Timing {TimingId}]", startMessage, _timingId); + _logger.LogInformation("{EndMessage} ({Duration}ms) [Timing {TimingId}]", _endMessage, + Stopwatch.ElapsedMilliseconds, _timingId); } else { - var args = new object[startMessageArgs.Length + 1]; - startMessageArgs.CopyTo(args, 0); - args[startMessageArgs.Length] = _timingId; - logger.LogInformation(startMessage + " [Timing {TimingId}]", args); + var args = new object[_endMessageArgs.Length + 2]; + _endMessageArgs.CopyTo(args, 0); + args[_endMessageArgs.Length - 1] = Stopwatch.ElapsedMilliseconds; + args[_endMessageArgs.Length] = _timingId; + _logger.LogInformation(_endMessage + " ({Duration}ms) [Timing {TimingId}]", args); } - break; - default: - throw new ArgumentOutOfRangeException(nameof(level)); - } - } - - // else aren't logging the start message, this is output to the profiler but not the log, - // we just want the log to contain the result if it's more than the minimum ms threshold. - - _profilerStep = profiler?.Step(loggerType, startMessage); - } - - /// - /// Reports a failure. - /// - /// The fail message. - /// The exception. - /// Completion of the timer will be reported as an error, with the specified message and exception. - public void Fail(string? failMessage = null, Exception? exception = null) - { - _failed = true; - _failMessage = failMessage ?? _failMessage ?? "Failed."; - _failException = exception; - } - - public Stopwatch Stopwatch { get; } = Stopwatch.StartNew(); - /// - ///Disposes resources. - /// - /// Overrides abstract class which handles required locking. - protected override void DisposeResources() - { - Stopwatch.Stop(); - - _profilerStep?.Dispose(); - - if ((Stopwatch.ElapsedMilliseconds >= _thresholdMilliseconds || _failed) - && _loggerType != null && _logger != null - && (string.IsNullOrWhiteSpace(_endMessage) == false || _failed)) - { - if (_failed) - { - if (_failMessageArgs is null) - { - _logger.LogError(_failException, "{FailMessage} ({Duration}ms) [Timing {TimingId}]", _failMessage, Stopwatch.ElapsedMilliseconds, _timingId); - } - else - { - var args = new object[_failMessageArgs.Length + 2]; - _failMessageArgs.CopyTo(args, 0); - args[_failMessageArgs.Length - 1] = Stopwatch.ElapsedMilliseconds; - args[_failMessageArgs.Length] = _timingId; - _logger.LogError(_failException, _failMessage + " ({Duration}ms) [Timing {TimingId}]", args); - } - } - else - { - switch (_level) - { - case LogLevel.Debug: - if (_endMessageArgs == null) - { - _logger.LogDebug("{EndMessage} ({Duration}ms) [Timing {TimingId}]", _endMessage, Stopwatch.ElapsedMilliseconds, _timingId); - } - else - { - var args = new object[_endMessageArgs.Length + 2]; - _endMessageArgs.CopyTo(args, 0); - args[args.Length - 1] = Stopwatch.ElapsedMilliseconds; - args[args.Length] = _timingId; - _logger.LogDebug(_endMessage + " ({Duration}ms) [Timing {TimingId}]", args); - } - break; - case LogLevel.Information: - if (_endMessageArgs == null) - { - _logger.LogInformation("{EndMessage} ({Duration}ms) [Timing {TimingId}]", _endMessage, Stopwatch.ElapsedMilliseconds, _timingId); - } - else - { - var args = new object[_endMessageArgs.Length + 2]; - _endMessageArgs.CopyTo(args, 0); - args[_endMessageArgs.Length - 1] = Stopwatch.ElapsedMilliseconds; - args[_endMessageArgs.Length] = _timingId; - _logger.LogInformation(_endMessage + " ({Duration}ms) [Timing {TimingId}]", args); - } - break; - // filtered in the ctor - //default: - // throw new Exception(); - } + break; + // filtered in the ctor + //default: + // throw new Exception(); } } } diff --git a/src/Umbraco.Core/Logging/ILoggingConfiguration.cs b/src/Umbraco.Core/Logging/ILoggingConfiguration.cs index 34e4d702c6b2..662ee7891c84 100644 --- a/src/Umbraco.Core/Logging/ILoggingConfiguration.cs +++ b/src/Umbraco.Core/Logging/ILoggingConfiguration.cs @@ -1,11 +1,9 @@ -namespace Umbraco.Cms.Core.Logging -{ +namespace Umbraco.Cms.Core.Logging; - public interface ILoggingConfiguration - { - /// - /// Gets the physical path where logs are stored - /// - string LogDirectory { get; } - } +public interface ILoggingConfiguration +{ + /// + /// Gets the physical path where logs are stored + /// + string LogDirectory { get; } } diff --git a/src/Umbraco.Core/Logging/IMessageTemplates.cs b/src/Umbraco.Core/Logging/IMessageTemplates.cs index 99d88ce926fb..252f91aaa532 100644 --- a/src/Umbraco.Core/Logging/IMessageTemplates.cs +++ b/src/Umbraco.Core/Logging/IMessageTemplates.cs @@ -1,10 +1,9 @@ -namespace Umbraco.Cms.Core.Logging +namespace Umbraco.Cms.Core.Logging; + +/// +/// Provides tools to support message templates. +/// +public interface IMessageTemplates { - /// - /// Provides tools to support message templates. - /// - public interface IMessageTemplates - { - string Render(string messageTemplate, params object[] args); - } + string Render(string messageTemplate, params object[] args); } diff --git a/src/Umbraco.Core/Logging/IProfiler.cs b/src/Umbraco.Core/Logging/IProfiler.cs index 4b2bf6fc4844..42a17014245a 100644 --- a/src/Umbraco.Core/Logging/IProfiler.cs +++ b/src/Umbraco.Core/Logging/IProfiler.cs @@ -1,32 +1,30 @@ -using System; +namespace Umbraco.Cms.Core.Logging; -namespace Umbraco.Cms.Core.Logging +/// +/// Defines the profiling service. +/// +public interface IProfiler { - /// - /// Defines the profiling service. + /// Gets an that will time the code between its creation and disposal. /// - public interface IProfiler - { - /// - /// Gets an that will time the code between its creation and disposal. - /// - /// The name of the step. - /// A step. - /// The returned is meant to be used within a using (...) {{ ... }} block. - IDisposable? Step(string name); + /// The name of the step. + /// A step. + /// The returned is meant to be used within a using (...) {{ ... }} block. + IDisposable? Step(string name); - /// - /// Starts the profiler. - /// - void Start(); + /// + /// Starts the profiler. + /// + void Start(); - /// - /// Stops the profiler. - /// - /// A value indicating whether to discard results. - /// Set discardResult to true to abandon all profiling - useful when eg someone is not - /// authenticated or you want to clear the results, based upon some other mechanism. - void Stop(bool discardResults = false); - } + /// + /// Stops the profiler. + /// + /// A value indicating whether to discard results. + /// + /// Set discardResult to true to abandon all profiling - useful when eg someone is not + /// authenticated or you want to clear the results, based upon some other mechanism. + /// + void Stop(bool discardResults = false); } diff --git a/src/Umbraco.Core/Logging/IProfilerHtml.cs b/src/Umbraco.Core/Logging/IProfilerHtml.cs index 30812fc1563e..0f7496187354 100644 --- a/src/Umbraco.Core/Logging/IProfilerHtml.cs +++ b/src/Umbraco.Core/Logging/IProfilerHtml.cs @@ -1,15 +1,14 @@ -namespace Umbraco.Cms.Core.Logging +namespace Umbraco.Cms.Core.Logging; + +/// +/// Used to render a profiler in a web page +/// +public interface IProfilerHtml { /// - /// Used to render a profiler in a web page + /// Renders the profiling results. /// - public interface IProfilerHtml - { - /// - /// Renders the profiling results. - /// - /// The profiling results. - /// Generally used for HTML rendering. - string Render(); - } + /// The profiling results. + /// Generally used for HTML rendering. + string Render(); } diff --git a/src/Umbraco.Core/Logging/IProfilingLogger.cs b/src/Umbraco.Core/Logging/IProfilingLogger.cs index 587361998858..75d9422d2072 100644 --- a/src/Umbraco.Core/Logging/IProfilingLogger.cs +++ b/src/Umbraco.Core/Logging/IProfilingLogger.cs @@ -1,40 +1,44 @@ -using System; +namespace Umbraco.Cms.Core.Logging; -namespace Umbraco.Cms.Core.Logging +/// +/// Defines the profiling logging service. +/// +public interface IProfilingLogger { /// - /// Defines the profiling logging service. + /// Profiles an action and log as information messages. /// - public interface IProfilingLogger - { - /// - /// Profiles an action and log as information messages. - /// - DisposableTimer TraceDuration(string startMessage, object[]? startMessageArgs = null); + DisposableTimer TraceDuration(string startMessage, object[]? startMessageArgs = null); - /// - /// Profiles an action and log as information messages. - /// - DisposableTimer TraceDuration(string startMessage, string completeMessage, string? failMessage = null, object[]? startMessageArgs = null, object[]? endMessageArgs = null, object[]? failMessageArgs = null); + /// + /// Profiles an action and log as information messages. + /// + DisposableTimer TraceDuration(string startMessage, string completeMessage, string? failMessage = null, + object[]? startMessageArgs = null, object[]? endMessageArgs = null, object[]? failMessageArgs = null); - /// - /// Profiles an action and log as information messages. - /// - DisposableTimer TraceDuration(Type loggerType, string startMessage, string completeMessage, string? failMessage = null, object[]? startMessageArgs = null, object[]? endMessageArgs = null, object[]? failMessageArgs = null); + /// + /// Profiles an action and log as information messages. + /// + DisposableTimer TraceDuration(Type loggerType, string startMessage, string completeMessage, + string? failMessage = null, object[]? startMessageArgs = null, object[]? endMessageArgs = null, + object[]? failMessageArgs = null); - /// - /// Profiles an action and log as debug messages. - /// - DisposableTimer? DebugDuration(string startMessage, object[]? startMessageArgs = null); + /// + /// Profiles an action and log as debug messages. + /// + DisposableTimer? DebugDuration(string startMessage, object[]? startMessageArgs = null); - /// - /// Profiles an action and log as debug messages. - /// - DisposableTimer? DebugDuration(string startMessage, string completeMessage, string? failMessage = null, int thresholdMilliseconds = 0, object[]? startMessageArgs = null, object[]? endMessageArgs = null, object[]? failMessageArgs = null); + /// + /// Profiles an action and log as debug messages. + /// + DisposableTimer? DebugDuration(string startMessage, string completeMessage, string? failMessage = null, + int thresholdMilliseconds = 0, object[]? startMessageArgs = null, object[]? endMessageArgs = null, + object[]? failMessageArgs = null); - /// - /// Profiles an action and log as debug messages. - /// - DisposableTimer? DebugDuration(Type loggerType, string startMessage, string completeMessage, string? failMessage = null, int thresholdMilliseconds = 0, object[]? startMessageArgs = null, object[]? endMessageArgs = null, object[]? failMessageArgs = null); - } + /// + /// Profiles an action and log as debug messages. + /// + DisposableTimer? DebugDuration(Type loggerType, string startMessage, string completeMessage, + string? failMessage = null, int thresholdMilliseconds = 0, object[]? startMessageArgs = null, + object[]? endMessageArgs = null, object[]? failMessageArgs = null); } diff --git a/src/Umbraco.Core/Logging/LogHttpRequestExtension.cs b/src/Umbraco.Core/Logging/LogHttpRequestExtension.cs index c9e1b09e0880..302145946265 100644 --- a/src/Umbraco.Core/Logging/LogHttpRequestExtension.cs +++ b/src/Umbraco.Core/Logging/LogHttpRequestExtension.cs @@ -1,24 +1,22 @@ -using System; -using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Cache; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class LogHttpRequest { - public static class LogHttpRequest - { - static readonly string RequestIdItemName = typeof(LogHttpRequest).Name + "+RequestId"; + private static readonly string RequestIdItemName = typeof(LogHttpRequest).Name + "+RequestId"; - /// - /// Retrieve the id assigned to the currently-executing HTTP request, if any. - /// - /// The request id. - /// - /// true if there is a request in progress; false otherwise. - public static bool TryGetCurrentHttpRequestId(out Guid? requestId, IRequestCache requestCache) - { - var requestIdItem = requestCache.Get(RequestIdItemName, () => Guid.NewGuid()); - requestId = (Guid?)requestIdItem; + /// + /// Retrieve the id assigned to the currently-executing HTTP request, if any. + /// + /// The request id. + /// + /// true if there is a request in progress; false otherwise. + public static bool TryGetCurrentHttpRequestId(out Guid? requestId, IRequestCache requestCache) + { + var requestIdItem = requestCache.Get(RequestIdItemName, () => Guid.NewGuid()); + requestId = (Guid?)requestIdItem; - return true; - } + return true; } } diff --git a/src/Umbraco.Core/Logging/LogLevel.cs b/src/Umbraco.Core/Logging/LogLevel.cs index 9e120023248e..45d4713b4638 100644 --- a/src/Umbraco.Core/Logging/LogLevel.cs +++ b/src/Umbraco.Core/Logging/LogLevel.cs @@ -1,15 +1,14 @@ -namespace Umbraco.Cms.Core.Logging +namespace Umbraco.Cms.Core.Logging; + +/// +/// Specifies the level of a log event. +/// +public enum LogLevel { - /// - /// Specifies the level of a log event. - /// - public enum LogLevel - { - Verbose, - Debug, - Information, - Warning, - Error, - Fatal - } + Verbose, + Debug, + Information, + Warning, + Error, + Fatal } diff --git a/src/Umbraco.Core/Logging/LogProfiler.cs b/src/Umbraco.Core/Logging/LogProfiler.cs index 1f4b4bbe9094..83b3248f0a40 100644 --- a/src/Umbraco.Core/Logging/LogProfiler.cs +++ b/src/Umbraco.Core/Logging/LogProfiler.cs @@ -1,57 +1,57 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; using Microsoft.Extensions.Logging; -namespace Umbraco.Cms.Core.Logging +namespace Umbraco.Cms.Core.Logging; + +/// +/// Implements by writing profiling results to an . +/// +public class LogProfiler : IProfiler { - /// - /// Implements by writing profiling results to an . - /// - public class LogProfiler : IProfiler - { - private readonly ILogger _logger; + private readonly ILogger _logger; - public LogProfiler(ILogger logger) - { - _logger = logger; - } + public LogProfiler(ILogger logger) => _logger = logger; - /// - public IDisposable Step(string name) - { - _logger.LogDebug("Begin: {ProfileName}", name); - return new LightDisposableTimer(duration => _logger.LogInformation("End {ProfileName} ({ProfileDuration}ms)", name, duration)); - } + /// + public IDisposable Step(string name) + { + _logger.LogDebug("Begin: {ProfileName}", name); + return new LightDisposableTimer(duration => + _logger.LogInformation("End {ProfileName} ({ProfileDuration}ms)", name, duration)); + } - /// - public void Start() - { - // the log will always be started - } + /// + public void Start() + { + // the log will always be started + } - /// - public void Stop(bool discardResults = false) - { - // the log never stops - } + /// + public void Stop(bool discardResults = false) + { + // the log never stops + } - // a lightweight disposable timer - private class LightDisposableTimer : DisposableObjectSlim - { - private readonly Action _callback; - private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); + // a lightweight disposable timer + private class LightDisposableTimer : DisposableObjectSlim + { + private readonly Action _callback; + private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); - protected internal LightDisposableTimer(Action callback) + protected internal LightDisposableTimer(Action callback) + { + if (callback == null) { - if (callback == null) throw new ArgumentNullException(nameof(callback)); - _callback = callback; + throw new ArgumentNullException(nameof(callback)); } - protected override void DisposeResources() - { - _stopwatch.Stop(); - _callback(_stopwatch.ElapsedMilliseconds); - } + _callback = callback; + } + + protected override void DisposeResources() + { + _stopwatch.Stop(); + _callback(_stopwatch.ElapsedMilliseconds); } } } diff --git a/src/Umbraco.Core/Logging/LoggingConfiguration.cs b/src/Umbraco.Core/Logging/LoggingConfiguration.cs index f191af302314..7def44988e7f 100644 --- a/src/Umbraco.Core/Logging/LoggingConfiguration.cs +++ b/src/Umbraco.Core/Logging/LoggingConfiguration.cs @@ -1,14 +1,9 @@ -using System; +namespace Umbraco.Cms.Core.Logging; -namespace Umbraco.Cms.Core.Logging +public class LoggingConfiguration : ILoggingConfiguration { - public class LoggingConfiguration : ILoggingConfiguration - { - public LoggingConfiguration(string logDirectory) - { - LogDirectory = logDirectory ?? throw new ArgumentNullException(nameof(logDirectory)); - } + public LoggingConfiguration(string logDirectory) => + LogDirectory = logDirectory ?? throw new ArgumentNullException(nameof(logDirectory)); - public string LogDirectory { get; } - } + public string LogDirectory { get; } } diff --git a/src/Umbraco.Core/Logging/LoggingTaskExtension.cs b/src/Umbraco.Core/Logging/LoggingTaskExtension.cs index 5a6f995dfa99..855d9f8f6026 100644 --- a/src/Umbraco.Core/Logging/LoggingTaskExtension.cs +++ b/src/Umbraco.Core/Logging/LoggingTaskExtension.cs @@ -1,52 +1,47 @@ -using System; -using System.Threading; -using System.Threading.Tasks; +namespace Umbraco.Cms.Core.Logging; -namespace Umbraco.Cms.Core.Logging +internal static class LoggingTaskExtension { - internal static class LoggingTaskExtension - { - /// - /// This task shouldn't be waited on (as it's not guaranteed to run), and you shouldn't wait on the parent task either (because it might throw an - /// exception that doesn't get handled). If you want to be waiting on something, use LogErrorsWaitable instead. - /// - /// None of these methods are suitable for tasks that return a value. If you're wanting a result, you should probably be handling - /// errors yourself. - /// - public static Task LogErrors(this Task task, Action logMethod) - { - return task.ContinueWith( - t => LogErrorsInner(t, logMethod), - CancellationToken.None, - TaskContinuationOptions.OnlyOnFaulted, - // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html - TaskScheduler.Default); - } + /// + /// This task shouldn't be waited on (as it's not guaranteed to run), and you shouldn't wait on the parent task either + /// (because it might throw an + /// exception that doesn't get handled). If you want to be waiting on something, use LogErrorsWaitable instead. + /// None of these methods are suitable for tasks that return a value. If you're wanting a result, you should probably + /// be handling + /// errors yourself. + /// + public static Task LogErrors(this Task task, Action logMethod) => + task.ContinueWith( + t => LogErrorsInner(t, logMethod), + CancellationToken.None, + TaskContinuationOptions.OnlyOnFaulted, + // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html + TaskScheduler.Default); - /// - /// This task can be waited on (as it's guaranteed to run), and you should wait on this rather than the parent task. Because it's - /// guaranteed to run, it may be slower than using LogErrors, and you should consider using that method if you don't want to wait. - /// - /// None of these methods are suitable for tasks that return a value. If you're wanting a result, you should probably be handling - /// errors yourself. - /// - public static Task LogErrorsWaitable(this Task task, Action logMethod) - { - return task.ContinueWith( - t => LogErrorsInner(t, logMethod), - // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html - TaskScheduler.Default); - } + /// + /// This task can be waited on (as it's guaranteed to run), and you should wait on this rather than the parent task. + /// Because it's + /// guaranteed to run, it may be slower than using LogErrors, and you should consider using that method if you don't + /// want to wait. + /// None of these methods are suitable for tasks that return a value. If you're wanting a result, you should probably + /// be handling + /// errors yourself. + /// + public static Task LogErrorsWaitable(this Task task, Action logMethod) => + task.ContinueWith( + t => LogErrorsInner(t, logMethod), + // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html + TaskScheduler.Default); - private static void LogErrorsInner(Task task, Action logAction) + private static void LogErrorsInner(Task task, Action logAction) + { + if (task.Exception != null) { - if (task.Exception != null) + logAction("Aggregate Exception with " + task.Exception.InnerExceptions.Count + " inner exceptions: ", + task.Exception); + foreach (Exception innerException in task.Exception.InnerExceptions) { - logAction("Aggregate Exception with " + task.Exception.InnerExceptions.Count + " inner exceptions: ", task.Exception); - foreach (var innerException in task.Exception.InnerExceptions) - { - logAction("Inner exception from aggregate exception: ", innerException); - } + logAction("Inner exception from aggregate exception: ", innerException); } } } diff --git a/src/Umbraco.Core/Logging/NoopProfiler.cs b/src/Umbraco.Core/Logging/NoopProfiler.cs index 89a0307515ec..821728c7a6ec 100644 --- a/src/Umbraco.Core/Logging/NoopProfiler.cs +++ b/src/Umbraco.Core/Logging/NoopProfiler.cs @@ -1,26 +1,23 @@ -using System; +namespace Umbraco.Cms.Core.Logging; -namespace Umbraco.Cms.Core.Logging +public class NoopProfiler : IProfiler { - public class NoopProfiler : IProfiler - { - private readonly VoidDisposable _disposable = new VoidDisposable(); + private readonly VoidDisposable _disposable = new(); - public IDisposable Step(string name) - { - return _disposable; - } + public IDisposable Step(string name) => _disposable; - public void Start() - { } + public void Start() + { + } - public void Stop(bool discardResults = false) - { } + public void Stop(bool discardResults = false) + { + } - private class VoidDisposable : DisposableObjectSlim + private class VoidDisposable : DisposableObjectSlim + { + protected override void DisposeResources() { - protected override void DisposeResources() - { } } } } diff --git a/src/Umbraco.Core/Logging/ProfilerExtensions.cs b/src/Umbraco.Core/Logging/ProfilerExtensions.cs index 67739c2f381d..4f7a1fee253d 100644 --- a/src/Umbraco.Core/Logging/ProfilerExtensions.cs +++ b/src/Umbraco.Core/Logging/ProfilerExtensions.cs @@ -1,39 +1,52 @@ -using System; +namespace Umbraco.Cms.Core.Logging; -namespace Umbraco.Cms.Core.Logging +internal static class ProfilerExtensions { - internal static class ProfilerExtensions + /// + /// Gets an that will time the code between its creation and disposal, + /// prefixing the name of the step with a reporting type name. + /// + /// The reporting type. + /// The profiler. + /// The name of the step. + /// A step. + /// The returned is meant to be used within a using (...) {{ ... }} block. + internal static IDisposable? Step(this IProfiler profiler, string name) { - /// - /// Gets an that will time the code between its creation and disposal, - /// prefixing the name of the step with a reporting type name. - /// - /// The reporting type. - /// The profiler. - /// The name of the step. - /// A step. - /// The returned is meant to be used within a using (...) {{ ... }} block. - internal static IDisposable? Step(this IProfiler profiler, string name) + if (profiler == null) { - if (profiler == null) throw new ArgumentNullException(nameof(profiler)); - return profiler.Step(typeof (T), name); + throw new ArgumentNullException(nameof(profiler)); } - /// - /// Gets an that will time the code between its creation and disposal, - /// prefixing the name of the step with a reporting type name. - /// - /// The profiler. - /// The reporting type. - /// The name of the step. - /// A step. - /// The returned is meant to be used within a using (...) {{ ... }} block. - internal static IDisposable? Step(this IProfiler profiler, Type reporting, string name) + return profiler.Step(typeof(T), name); + } + + /// + /// Gets an that will time the code between its creation and disposal, + /// prefixing the name of the step with a reporting type name. + /// + /// The profiler. + /// The reporting type. + /// The name of the step. + /// A step. + /// The returned is meant to be used within a using (...) {{ ... }} block. + internal static IDisposable? Step(this IProfiler profiler, Type reporting, string name) + { + if (profiler == null) + { + throw new ArgumentNullException(nameof(profiler)); + } + + if (reporting == null) { - if (profiler == null) throw new ArgumentNullException(nameof(profiler)); - if (reporting == null) throw new ArgumentNullException(nameof(reporting)); - if (name == null) throw new ArgumentNullException(nameof(name)); - return profiler.Step($"[{reporting.Name}] {name}"); + throw new ArgumentNullException(nameof(reporting)); } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return profiler.Step($"[{reporting.Name}] {name}"); } } diff --git a/src/Umbraco.Core/Logging/ProfilingLogger.cs b/src/Umbraco.Core/Logging/ProfilingLogger.cs index d3388bda01d0..3f03b1c75f54 100644 --- a/src/Umbraco.Core/Logging/ProfilingLogger.cs +++ b/src/Umbraco.Core/Logging/ProfilingLogger.cs @@ -1,99 +1,106 @@ -using System; using Microsoft.Extensions.Logging; -namespace Umbraco.Cms.Core.Logging +namespace Umbraco.Cms.Core.Logging; + +/// +/// Provides logging and profiling services. +/// +public sealed class ProfilingLogger : IProfilingLogger { /// - /// Provides logging and profiling services. + /// Initializes a new instance of the class. /// - public sealed class ProfilingLogger : IProfilingLogger + public ProfilingLogger(ILogger logger, IProfiler profiler) { - /// - /// Gets the underlying implementation. - /// - public ILogger Logger { get; } - - /// - /// Gets the underlying implementation. - /// - public IProfiler Profiler { get; } - - /// - /// Initializes a new instance of the class. - /// - public ProfilingLogger(ILogger logger, IProfiler profiler) - { - Logger = logger ?? throw new ArgumentNullException(nameof(logger)); - Profiler = profiler ?? throw new ArgumentNullException(nameof(profiler)); - } + Logger = logger ?? throw new ArgumentNullException(nameof(logger)); + Profiler = profiler ?? throw new ArgumentNullException(nameof(profiler)); + } - /// - /// Initializes a new instance of the class. - /// - public ProfilingLogger(ILogger logger, IProfiler profiler) - { - Logger = logger ?? throw new ArgumentNullException(nameof(logger)); - Profiler = profiler ?? throw new ArgumentNullException(nameof(profiler)); - } + /// + /// Initializes a new instance of the class. + /// + public ProfilingLogger(ILogger logger, IProfiler profiler) + { + Logger = logger ?? throw new ArgumentNullException(nameof(logger)); + Profiler = profiler ?? throw new ArgumentNullException(nameof(profiler)); + } - public DisposableTimer TraceDuration(string startMessage, object[]? startMessageArgs = null) - => TraceDuration(startMessage, "Completed.", startMessageArgs: startMessageArgs); + /// + /// Gets the underlying implementation. + /// + public ILogger Logger { get; } - public DisposableTimer TraceDuration(string startMessage, string completeMessage, string? failMessage = null, object[]? startMessageArgs = null, object[]? endMessageArgs = null, object[]? failMessageArgs = null) - => new DisposableTimer(Logger, LogLevel.Information, Profiler, typeof(T), startMessage, completeMessage, failMessage, startMessageArgs, endMessageArgs, failMessageArgs); + /// + /// Gets the underlying implementation. + /// + public IProfiler Profiler { get; } - public DisposableTimer TraceDuration(Type loggerType, string startMessage, string completeMessage, string? failMessage = null, object[]? startMessageArgs = null, object[]? endMessageArgs = null, object[]? failMessageArgs = null) - => new DisposableTimer(Logger, LogLevel.Information, Profiler, loggerType, startMessage, completeMessage, failMessage, startMessageArgs, endMessageArgs, failMessageArgs); + public DisposableTimer TraceDuration(string startMessage, object[]? startMessageArgs = null) + => TraceDuration(startMessage, "Completed.", startMessageArgs: startMessageArgs); - public DisposableTimer? DebugDuration(string startMessage, object[]? startMessageArgs = null) - => Logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug) - ? DebugDuration(startMessage, "Completed.", startMessageArgs: startMessageArgs) - : null; + public DisposableTimer TraceDuration(string startMessage, string completeMessage, string? failMessage = null, + object[]? startMessageArgs = null, object[]? endMessageArgs = null, object[]? failMessageArgs = null) + => new(Logger, LogLevel.Information, Profiler, typeof(T), startMessage, completeMessage, failMessage, + startMessageArgs, endMessageArgs, failMessageArgs); - public DisposableTimer? DebugDuration(string startMessage, string completeMessage, string? failMessage = null, int thresholdMilliseconds = 0, object[]? startMessageArgs = null, object[]? endMessageArgs = null, object[]? failMessageArgs = null) - => Logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug) - ? new DisposableTimer(Logger, LogLevel.Debug, Profiler, typeof(T), startMessage, completeMessage, failMessage, startMessageArgs, endMessageArgs, failMessageArgs, thresholdMilliseconds) - : null; + public DisposableTimer TraceDuration(Type loggerType, string startMessage, string completeMessage, + string? failMessage = null, object[]? startMessageArgs = null, object[]? endMessageArgs = null, + object[]? failMessageArgs = null) + => new(Logger, LogLevel.Information, Profiler, loggerType, startMessage, completeMessage, failMessage, + startMessageArgs, endMessageArgs, failMessageArgs); - public DisposableTimer? DebugDuration(Type loggerType, string startMessage, string completeMessage, string? failMessage = null, int thresholdMilliseconds = 0, object[]? startMessageArgs = null, object[]? endMessageArgs = null, object[]? failMessageArgs = null) - => Logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug) - ? new DisposableTimer(Logger, LogLevel.Debug, Profiler, loggerType, startMessage, completeMessage, failMessage, startMessageArgs, endMessageArgs, failMessageArgs, thresholdMilliseconds) - : null; + public DisposableTimer? DebugDuration(string startMessage, object[]? startMessageArgs = null) + => Logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug) + ? DebugDuration(startMessage, "Completed.", startMessageArgs: startMessageArgs) + : null; - #region ILogger + public DisposableTimer? DebugDuration(string startMessage, string completeMessage, string? failMessage = null, + int thresholdMilliseconds = 0, object[]? startMessageArgs = null, object[]? endMessageArgs = null, + object[]? failMessageArgs = null) + => Logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug) + ? new DisposableTimer(Logger, LogLevel.Debug, Profiler, typeof(T), startMessage, completeMessage, + failMessage, startMessageArgs, endMessageArgs, failMessageArgs, thresholdMilliseconds) + : null; - public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel level) - => Logger.IsEnabled(level); + public DisposableTimer? DebugDuration(Type loggerType, string startMessage, string completeMessage, + string? failMessage = null, int thresholdMilliseconds = 0, object[]? startMessageArgs = null, + object[]? endMessageArgs = null, object[]? failMessageArgs = null) + => Logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug) + ? new DisposableTimer(Logger, LogLevel.Debug, Profiler, loggerType, startMessage, completeMessage, + failMessage, startMessageArgs, endMessageArgs, failMessageArgs, thresholdMilliseconds) + : null; - public void LogCritical(Exception exception, string messageTemplate, params object[] propertyValues) - => Logger.LogCritical(exception, messageTemplate, propertyValues); + #region ILogger - public void LogCritical(string messageTemplate, params object[] propertyValues) - => Logger.LogCritical(messageTemplate, propertyValues); + public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel level) + => Logger.IsEnabled(level); - public void LogError(Exception exception, string messageTemplate, params object[] propertyValues) - => Logger.LogError(exception, messageTemplate, propertyValues); + public void LogCritical(Exception exception, string messageTemplate, params object[] propertyValues) + => Logger.LogCritical(exception, messageTemplate, propertyValues); - public void LogError(string messageTemplate, params object[] propertyValues) - => Logger.LogError(messageTemplate, propertyValues); + public void LogCritical(string messageTemplate, params object[] propertyValues) + => Logger.LogCritical(messageTemplate, propertyValues); - public void LogWarning(string messageTemplate, params object[] propertyValues) - => Logger.LogWarning(messageTemplate, propertyValues); + public void LogError(Exception exception, string messageTemplate, params object[] propertyValues) + => Logger.LogError(exception, messageTemplate, propertyValues); - public void LogWarning(Exception exception, string messageTemplate, params object[] propertyValues) - => Logger.LogWarning(exception, messageTemplate, propertyValues); + public void LogError(string messageTemplate, params object[] propertyValues) + => Logger.LogError(messageTemplate, propertyValues); - public void LogInformation(string messageTemplate, params object[] propertyValues) - => Logger.LogInformation(messageTemplate, propertyValues); + public void LogWarning(string messageTemplate, params object[] propertyValues) + => Logger.LogWarning(messageTemplate, propertyValues); - public void LogDebug(string messageTemplate, params object[] propertyValues) - => Logger.LogDebug(messageTemplate, propertyValues); + public void LogWarning(Exception exception, string messageTemplate, params object[] propertyValues) + => Logger.LogWarning(exception, messageTemplate, propertyValues); - public void LogTrace(string messageTemplate, params object[] propertyValues) - => Logger.LogTrace(messageTemplate, propertyValues); + public void LogInformation(string messageTemplate, params object[] propertyValues) + => Logger.LogInformation(messageTemplate, propertyValues); + public void LogDebug(string messageTemplate, params object[] propertyValues) + => Logger.LogDebug(messageTemplate, propertyValues); + public void LogTrace(string messageTemplate, params object[] propertyValues) + => Logger.LogTrace(messageTemplate, propertyValues); - #endregion - } + #endregion } diff --git a/src/Umbraco.Core/Macros/IMacroRenderer.cs b/src/Umbraco.Core/Macros/IMacroRenderer.cs index bac3d36268a9..9a982b7eefe3 100644 --- a/src/Umbraco.Core/Macros/IMacroRenderer.cs +++ b/src/Umbraco.Core/Macros/IMacroRenderer.cs @@ -1,14 +1,12 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.Macros +namespace Umbraco.Cms.Core.Macros; + +/// +/// Renders a macro +/// +public interface IMacroRenderer { - /// - /// Renders a macro - /// - public interface IMacroRenderer - { - Task RenderAsync(string macroAlias, IPublishedContent? content, IDictionary? macroParams); - } + Task RenderAsync(string macroAlias, IPublishedContent? content, + IDictionary? macroParams); } diff --git a/src/Umbraco.Core/Macros/MacroContent.cs b/src/Umbraco.Core/Macros/MacroContent.cs index 7998b00fd7fc..e230277370ec 100644 --- a/src/Umbraco.Core/Macros/MacroContent.cs +++ b/src/Umbraco.Core/Macros/MacroContent.cs @@ -1,20 +1,17 @@ -using System; +namespace Umbraco.Cms.Core.Macros; -namespace Umbraco.Cms.Core.Macros +// represents the content of a macro +public class MacroContent { - // represents the content of a macro - public class MacroContent - { - // gets or sets the text content - public string? Text { get; set; } + // gets or sets the text content + public string? Text { get; set; } - // gets or sets the date the content was generated - public DateTime Date { get; set; } = DateTime.Now; + // gets or sets the date the content was generated + public DateTime Date { get; set; } = DateTime.Now; - // a value indicating whether the content is empty - public bool IsEmpty => Text is null; + // a value indicating whether the content is empty + public bool IsEmpty => Text is null; - // gets an empty macro content - public static MacroContent Empty { get; } = new MacroContent(); - } + // gets an empty macro content + public static MacroContent Empty { get; } = new(); } diff --git a/src/Umbraco.Core/Macros/MacroErrorBehaviour.cs b/src/Umbraco.Core/Macros/MacroErrorBehaviour.cs index b3c505682a99..4d0d4535a792 100644 --- a/src/Umbraco.Core/Macros/MacroErrorBehaviour.cs +++ b/src/Umbraco.Core/Macros/MacroErrorBehaviour.cs @@ -1,29 +1,28 @@ -namespace Umbraco.Cms.Core.Macros +namespace Umbraco.Cms.Core.Macros; + +public enum MacroErrorBehaviour { - public enum MacroErrorBehaviour - { - /// - /// Default umbraco behavior - show an inline error within the - /// macro but allow the page to continue rendering. - /// - Inline, + /// + /// Default umbraco behavior - show an inline error within the + /// macro but allow the page to continue rendering. + /// + Inline, - /// - /// Silently eat the error and do not display the offending macro. - /// - Silent, + /// + /// Silently eat the error and do not display the offending macro. + /// + Silent, - /// - /// Throw an exception which can be caught by the global error handler - /// defined in Application_OnError. If no such error handler is defined - /// then you'll see the Yellow Screen Of Death (YSOD) error page. - /// - Throw, + /// + /// Throw an exception which can be caught by the global error handler + /// defined in Application_OnError. If no such error handler is defined + /// then you'll see the Yellow Screen Of Death (YSOD) error page. + /// + Throw, - /// - /// Silently eat the error and display the custom content reported in - /// the error event args - /// - Content - } + /// + /// Silently eat the error and display the custom content reported in + /// the error event args + /// + Content } diff --git a/src/Umbraco.Core/Macros/MacroModel.cs b/src/Umbraco.Core/Macros/MacroModel.cs index 5242b14d863d..224fb32e2f97 100644 --- a/src/Umbraco.Core/Macros/MacroModel.cs +++ b/src/Umbraco.Core/Macros/MacroModel.cs @@ -1,57 +1,61 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Macros +namespace Umbraco.Cms.Core.Macros; + +public class MacroModel { - public class MacroModel + public MacroModel() { - /// - /// The Macro Id - /// - public int Id { get; set; } + } - /// - /// The Macro Name - /// - public string? Name { get; set; } + public MacroModel(IMacro macro) + { + if (macro == null) + { + return; + } - /// - /// The Macro Alias - /// - public string? Alias { get; set; } + Id = macro.Id; + Name = macro.Name; + Alias = macro.Alias; + MacroSource = macro.MacroSource; + CacheDuration = macro.CacheDuration; + CacheByPage = macro.CacheByPage; + CacheByMember = macro.CacheByMember; + RenderInEditor = macro.UseInEditor; - public string? MacroSource { get; set; } + foreach (IMacroProperty prop in macro.Properties) + { + Properties.Add(new MacroPropertyModel(prop.Alias, string.Empty, prop.EditorAlias)); + } + } - public int CacheDuration { get; set; } + /// + /// The Macro Id + /// + public int Id { get; set; } - public bool CacheByPage { get; set; } + /// + /// The Macro Name + /// + public string? Name { get; set; } - public bool CacheByMember { get; set; } + /// + /// The Macro Alias + /// + public string? Alias { get; set; } - public bool RenderInEditor { get; set; } + public string? MacroSource { get; set; } - public string? CacheIdentifier { get; set; } + public int CacheDuration { get; set; } - public List Properties { get; } = new List(); + public bool CacheByPage { get; set; } - public MacroModel() - { } + public bool CacheByMember { get; set; } - public MacroModel(IMacro macro) - { - if (macro == null) return; - - Id = macro.Id; - Name = macro.Name; - Alias = macro.Alias; - MacroSource = macro.MacroSource; - CacheDuration = macro.CacheDuration; - CacheByPage = macro.CacheByPage; - CacheByMember = macro.CacheByMember; - RenderInEditor = macro.UseInEditor; - - foreach (var prop in macro.Properties) - Properties.Add(new MacroPropertyModel(prop.Alias, string.Empty, prop.EditorAlias)); - } - } + public bool RenderInEditor { get; set; } + + public string? CacheIdentifier { get; set; } + + public List Properties { get; } = new(); } diff --git a/src/Umbraco.Core/Macros/MacroPropertyModel.cs b/src/Umbraco.Core/Macros/MacroPropertyModel.cs index 643d154f2139..f5bfb1e535f9 100644 --- a/src/Umbraco.Core/Macros/MacroPropertyModel.cs +++ b/src/Umbraco.Core/Macros/MacroPropertyModel.cs @@ -1,29 +1,25 @@ -namespace Umbraco.Cms.Core.Macros +namespace Umbraco.Cms.Core.Macros; + +public class MacroPropertyModel { - public class MacroPropertyModel - { - public string Key { get; set; } + public MacroPropertyModel() => Key = string.Empty; - public string? Value { get; set; } + public MacroPropertyModel(string key, string value) + { + Key = key; + Value = value; + } - public string? Type { get; set; } + public MacroPropertyModel(string key, string value, string type) + { + Key = key; + Value = value; + Type = type; + } - public MacroPropertyModel() - { - Key = string.Empty; - } + public string Key { get; set; } - public MacroPropertyModel(string key, string value) - { - Key = key; - Value = value; - } + public string? Value { get; set; } - public MacroPropertyModel(string key, string value, string type) - { - Key = key; - Value = value; - Type = type; - } - } + public string? Type { get; set; } } diff --git a/src/Umbraco.Core/Mail/IEmailSender.cs b/src/Umbraco.Core/Mail/IEmailSender.cs index 0c573c542ccc..2eb8cc826358 100644 --- a/src/Umbraco.Core/Mail/IEmailSender.cs +++ b/src/Umbraco.Core/Mail/IEmailSender.cs @@ -1,17 +1,15 @@ -using System.Threading.Tasks; using Umbraco.Cms.Core.Models.Email; -namespace Umbraco.Cms.Core.Mail +namespace Umbraco.Cms.Core.Mail; + +/// +/// Simple abstraction to send an email message +/// +public interface IEmailSender { - /// - /// Simple abstraction to send an email message - /// - public interface IEmailSender - { - Task SendAsync(EmailMessage message, string emailType); + Task SendAsync(EmailMessage message, string emailType); - Task SendAsync(EmailMessage message, string emailType, bool enableNotification); + Task SendAsync(EmailMessage message, string emailType, bool enableNotification); - bool CanSendRequiredEmail(); - } + bool CanSendRequiredEmail(); } diff --git a/src/Umbraco.Core/Mail/ISmsSender.cs b/src/Umbraco.Core/Mail/ISmsSender.cs index 885ad89da2bf..985cb2c79b94 100644 --- a/src/Umbraco.Core/Mail/ISmsSender.cs +++ b/src/Umbraco.Core/Mail/ISmsSender.cs @@ -1,14 +1,11 @@ -using System.Threading.Tasks; +namespace Umbraco.Cms.Core.Mail; -namespace Umbraco.Cms.Core.Mail +/// +/// Service to send an SMS +/// +public interface ISmsSender { - /// - /// Service to send an SMS - /// - public interface ISmsSender - { - // borrowed from https://github.com/dotnet/AspNetCore.Docs/blob/master/aspnetcore/common/samples/WebApplication1/Services/ISmsSender.cs#L8 + // borrowed from https://github.com/dotnet/AspNetCore.Docs/blob/master/aspnetcore/common/samples/WebApplication1/Services/ISmsSender.cs#L8 - Task SendSmsAsync(string number, string message); - } + Task SendSmsAsync(string number, string message); } diff --git a/src/Umbraco.Core/Mail/NotImplementedEmailSender.cs b/src/Umbraco.Core/Mail/NotImplementedEmailSender.cs index 15e36767d95c..5b1fa0923ae6 100644 --- a/src/Umbraco.Core/Mail/NotImplementedEmailSender.cs +++ b/src/Umbraco.Core/Mail/NotImplementedEmailSender.cs @@ -1,19 +1,18 @@ -using System; -using System.Threading.Tasks; using Umbraco.Cms.Core.Models.Email; -namespace Umbraco.Cms.Core.Mail +namespace Umbraco.Cms.Core.Mail; + +internal class NotImplementedEmailSender : IEmailSender { - internal class NotImplementedEmailSender : IEmailSender - { - public Task SendAsync(EmailMessage message, string emailType) - => throw new NotImplementedException("To send an Email ensure IEmailSender is implemented with a custom implementation"); + public Task SendAsync(EmailMessage message, string emailType) + => throw new NotImplementedException( + "To send an Email ensure IEmailSender is implemented with a custom implementation"); - public Task SendAsync(EmailMessage message, string emailType, bool enableNotification) => - throw new NotImplementedException( - "To send an Email ensure IEmailSender is implemented with a custom implementation"); + public Task SendAsync(EmailMessage message, string emailType, bool enableNotification) => + throw new NotImplementedException( + "To send an Email ensure IEmailSender is implemented with a custom implementation"); - public bool CanSendRequiredEmail() - => throw new NotImplementedException("To send an Email ensure IEmailSender is implemented with a custom implementation"); - } + public bool CanSendRequiredEmail() + => throw new NotImplementedException( + "To send an Email ensure IEmailSender is implemented with a custom implementation"); } diff --git a/src/Umbraco.Core/Mail/NotImplementedSmsSender.cs b/src/Umbraco.Core/Mail/NotImplementedSmsSender.cs index 0cb5016a1b6b..b3901d5ab96d 100644 --- a/src/Umbraco.Core/Mail/NotImplementedSmsSender.cs +++ b/src/Umbraco.Core/Mail/NotImplementedSmsSender.cs @@ -1,14 +1,11 @@ -using System; -using System.Threading.Tasks; +namespace Umbraco.Cms.Core.Mail; -namespace Umbraco.Cms.Core.Mail +/// +/// An that throws +/// +internal class NotImplementedSmsSender : ISmsSender { - /// - /// An that throws - /// - internal class NotImplementedSmsSender : ISmsSender - { - public Task SendSmsAsync(string number, string message) - => throw new NotImplementedException("To send an SMS ensure ISmsSender is implemented with a custom implementation"); - } + public Task SendSmsAsync(string number, string message) + => throw new NotImplementedException( + "To send an SMS ensure ISmsSender is implemented with a custom implementation"); } diff --git a/src/Umbraco.Core/Manifest/BundleOptions.cs b/src/Umbraco.Core/Manifest/BundleOptions.cs index 810efb6c455f..6866fd23b2cc 100644 --- a/src/Umbraco.Core/Manifest/BundleOptions.cs +++ b/src/Umbraco.Core/Manifest/BundleOptions.cs @@ -1,26 +1,25 @@ -namespace Umbraco.Cms.Core.Manifest +namespace Umbraco.Cms.Core.Manifest; + +public enum BundleOptions { - public enum BundleOptions - { - /// - /// The default bundling behavior for assets in the package folder. - /// - /// - /// The assets will be bundled with the typical packages bundle. - /// - Default = 0, + /// + /// The default bundling behavior for assets in the package folder. + /// + /// + /// The assets will be bundled with the typical packages bundle. + /// + Default = 0, - /// - /// The assets in the package will not be processed at all and will all be requested as individual assets. - /// - /// - /// This will essentially be a bundle that has composite processing turned off for both debug and production. - /// - None = 1, + /// + /// The assets in the package will not be processed at all and will all be requested as individual assets. + /// + /// + /// This will essentially be a bundle that has composite processing turned off for both debug and production. + /// + None = 1, - /// - /// The packages assets will be processed as it's own separate bundle. (in debug, files will not be processed) - /// - Independent = 2 - } + /// + /// The packages assets will be processed as it's own separate bundle. (in debug, files will not be processed) + /// + Independent = 2 } diff --git a/src/Umbraco.Core/Manifest/CompositePackageManifest.cs b/src/Umbraco.Core/Manifest/CompositePackageManifest.cs index 939d635fc31f..5e41681ea608 100644 --- a/src/Umbraco.Core/Manifest/CompositePackageManifest.cs +++ b/src/Umbraco.Core/Manifest/CompositePackageManifest.cs @@ -1,67 +1,63 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.Manifest +namespace Umbraco.Cms.Core.Manifest; + +/// +/// A package manifest made up of all combined manifests +/// +public class CompositePackageManifest { + public CompositePackageManifest( + IReadOnlyList propertyEditors, + IReadOnlyList parameterEditors, + IReadOnlyList gridEditors, + IReadOnlyList contentApps, + IReadOnlyList dashboards, + IReadOnlyList sections, + IReadOnlyDictionary> scripts, + IReadOnlyDictionary> stylesheets) + { + PropertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors)); + ParameterEditors = parameterEditors ?? throw new ArgumentNullException(nameof(parameterEditors)); + GridEditors = gridEditors ?? throw new ArgumentNullException(nameof(gridEditors)); + ContentApps = contentApps ?? throw new ArgumentNullException(nameof(contentApps)); + Dashboards = dashboards ?? throw new ArgumentNullException(nameof(dashboards)); + Sections = sections ?? throw new ArgumentNullException(nameof(sections)); + Scripts = scripts ?? throw new ArgumentNullException(nameof(scripts)); + Stylesheets = stylesheets ?? throw new ArgumentNullException(nameof(stylesheets)); + } /// - /// A package manifest made up of all combined manifests + /// Gets or sets the property editors listed in the manifest. /// - public class CompositePackageManifest - { - public CompositePackageManifest( - IReadOnlyList propertyEditors, - IReadOnlyList parameterEditors, - IReadOnlyList gridEditors, - IReadOnlyList contentApps, - IReadOnlyList dashboards, - IReadOnlyList sections, - IReadOnlyDictionary> scripts, - IReadOnlyDictionary> stylesheets) - { - PropertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors)); - ParameterEditors = parameterEditors ?? throw new ArgumentNullException(nameof(parameterEditors)); - GridEditors = gridEditors ?? throw new ArgumentNullException(nameof(gridEditors)); - ContentApps = contentApps ?? throw new ArgumentNullException(nameof(contentApps)); - Dashboards = dashboards ?? throw new ArgumentNullException(nameof(dashboards)); - Sections = sections ?? throw new ArgumentNullException(nameof(sections)); - Scripts = scripts ?? throw new ArgumentNullException(nameof(scripts)); - Stylesheets = stylesheets ?? throw new ArgumentNullException(nameof(stylesheets)); - } + public IReadOnlyList PropertyEditors { get; } - /// - /// Gets or sets the property editors listed in the manifest. - /// - public IReadOnlyList PropertyEditors { get; } - - /// - /// Gets or sets the parameter editors listed in the manifest. - /// - public IReadOnlyList ParameterEditors { get; } + /// + /// Gets or sets the parameter editors listed in the manifest. + /// + public IReadOnlyList ParameterEditors { get; } - /// - /// Gets or sets the grid editors listed in the manifest. - /// - public IReadOnlyList GridEditors { get; } + /// + /// Gets or sets the grid editors listed in the manifest. + /// + public IReadOnlyList GridEditors { get; } - /// - /// Gets or sets the content apps listed in the manifest. - /// - public IReadOnlyList ContentApps { get; } + /// + /// Gets or sets the content apps listed in the manifest. + /// + public IReadOnlyList ContentApps { get; } - /// - /// Gets or sets the dashboards listed in the manifest. - /// - public IReadOnlyList Dashboards { get; } + /// + /// Gets or sets the dashboards listed in the manifest. + /// + public IReadOnlyList Dashboards { get; } - /// - /// Gets or sets the sections listed in the manifest. - /// - public IReadOnlyCollection Sections { get; } + /// + /// Gets or sets the sections listed in the manifest. + /// + public IReadOnlyCollection Sections { get; } - public IReadOnlyDictionary> Scripts { get; } + public IReadOnlyDictionary> Scripts { get; } - public IReadOnlyDictionary> Stylesheets { get; } - } + public IReadOnlyDictionary> Stylesheets { get; } } diff --git a/src/Umbraco.Core/Manifest/IManifestFilter.cs b/src/Umbraco.Core/Manifest/IManifestFilter.cs index 0984f1a889fd..075ca1ce284d 100644 --- a/src/Umbraco.Core/Manifest/IManifestFilter.cs +++ b/src/Umbraco.Core/Manifest/IManifestFilter.cs @@ -1,19 +1,16 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Manifest; -namespace Umbraco.Cms.Core.Manifest +/// +/// Provides filtering for package manifests. +/// +public interface IManifestFilter { /// - /// Provides filtering for package manifests. + /// Filters package manifests. /// - public interface IManifestFilter - { - /// - /// Filters package manifests. - /// - /// The package manifests. - /// - /// It is possible to remove, change, or add manifests. - /// - void Filter(List manifests); - } + /// The package manifests. + /// + /// It is possible to remove, change, or add manifests. + /// + void Filter(List manifests); } diff --git a/src/Umbraco.Core/Manifest/IManifestParser.cs b/src/Umbraco.Core/Manifest/IManifestParser.cs index 09d3ccbe1ccb..f8b29e9f561b 100644 --- a/src/Umbraco.Core/Manifest/IManifestParser.cs +++ b/src/Umbraco.Core/Manifest/IManifestParser.cs @@ -1,26 +1,23 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Manifest; -namespace Umbraco.Cms.Core.Manifest +public interface IManifestParser { - public interface IManifestParser - { - string AppPluginsPath { get; set; } + string AppPluginsPath { get; set; } - /// - /// Gets all manifests, merged into a single manifest object. - /// - /// - CompositePackageManifest CombinedManifest { get; } + /// + /// Gets all manifests, merged into a single manifest object. + /// + /// + CompositePackageManifest CombinedManifest { get; } - /// - /// Parses a manifest. - /// - PackageManifest ParseManifest(string text); + /// + /// Parses a manifest. + /// + PackageManifest ParseManifest(string text); - /// - /// Returns all package individual manifests - /// - /// - IEnumerable GetManifests(); - } + /// + /// Returns all package individual manifests + /// + /// + IEnumerable GetManifests(); } diff --git a/src/Umbraco.Core/Manifest/IPackageManifest.cs b/src/Umbraco.Core/Manifest/IPackageManifest.cs index 39e487823385..ba911b183cbd 100644 --- a/src/Umbraco.Core/Manifest/IPackageManifest.cs +++ b/src/Umbraco.Core/Manifest/IPackageManifest.cs @@ -1,65 +1,66 @@ using System.Runtime.Serialization; using Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.Manifest +namespace Umbraco.Cms.Core.Manifest; + +public interface IPackageManifest { - public interface IPackageManifest - { - /// - /// Gets the source path of the manifest. - /// - /// - /// Gets the full absolute file path of the manifest, - /// using system directory separators. - /// - string Source { get; set; } + /// + /// Gets the source path of the manifest. + /// + /// + /// + /// Gets the full absolute file path of the manifest, + /// using system directory separators. + /// + /// + string Source { get; set; } - /// - /// Gets or sets the scripts listed in the manifest. - /// - [DataMember(Name = "javascript")] - string[] Scripts { get; set; } + /// + /// Gets or sets the scripts listed in the manifest. + /// + [DataMember(Name = "javascript")] + string[] Scripts { get; set; } - /// - /// Gets or sets the stylesheets listed in the manifest. - /// - [DataMember(Name = "css")] - string[] Stylesheets { get; set; } + /// + /// Gets or sets the stylesheets listed in the manifest. + /// + [DataMember(Name = "css")] + string[] Stylesheets { get; set; } - /// - /// Gets or sets the property editors listed in the manifest. - /// - [DataMember(Name = "propertyEditors")] - IDataEditor[] PropertyEditors { get; set; } + /// + /// Gets or sets the property editors listed in the manifest. + /// + [DataMember(Name = "propertyEditors")] + IDataEditor[] PropertyEditors { get; set; } - /// - /// Gets or sets the parameter editors listed in the manifest. - /// - [DataMember(Name = "parameterEditors")] - IDataEditor[] ParameterEditors { get; set; } + /// + /// Gets or sets the parameter editors listed in the manifest. + /// + [DataMember(Name = "parameterEditors")] + IDataEditor[] ParameterEditors { get; set; } - /// - /// Gets or sets the grid editors listed in the manifest. - /// - [DataMember(Name = "gridEditors")] - GridEditor[] GridEditors { get; set; } + /// + /// Gets or sets the grid editors listed in the manifest. + /// + [DataMember(Name = "gridEditors")] + GridEditor[] GridEditors { get; set; } - /// - /// Gets or sets the content apps listed in the manifest. - /// - [DataMember(Name = "contentApps")] - ManifestContentAppDefinition[] ContentApps { get; set; } + /// + /// Gets or sets the content apps listed in the manifest. + /// + [DataMember(Name = "contentApps")] + ManifestContentAppDefinition[] ContentApps { get; set; } - /// - /// Gets or sets the dashboards listed in the manifest. - /// - [DataMember(Name = "dashboards")] - ManifestDashboard[] Dashboards { get; set; } + /// + /// Gets or sets the dashboards listed in the manifest. + /// + [DataMember(Name = "dashboards")] + ManifestDashboard[] Dashboards { get; set; } - /// - /// Gets or sets the sections listed in the manifest. - /// - [DataMember(Name = "sections")] - ManifestSection[] Sections { get; set; } - } + /// + /// Gets or sets the sections listed in the manifest. + /// + [DataMember(Name = "sections")] + ManifestSection[] Sections { get; set; } } diff --git a/src/Umbraco.Core/Manifest/ManifestAssets.cs b/src/Umbraco.Core/Manifest/ManifestAssets.cs index 6532e2f63d06..1dbed2e8105c 100644 --- a/src/Umbraco.Core/Manifest/ManifestAssets.cs +++ b/src/Umbraco.Core/Manifest/ManifestAssets.cs @@ -1,17 +1,13 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Manifest; -namespace Umbraco.Cms.Core.Manifest +public class ManifestAssets { - public class ManifestAssets + public ManifestAssets(string? packageName, IReadOnlyList assets) { - public ManifestAssets(string? packageName, IReadOnlyList assets) - { - PackageName = packageName ?? throw new ArgumentNullException(nameof(packageName)); - Assets = assets ?? throw new ArgumentNullException(nameof(assets)); - } - - public string PackageName { get; } - public IReadOnlyList Assets { get; } + PackageName = packageName ?? throw new ArgumentNullException(nameof(packageName)); + Assets = assets ?? throw new ArgumentNullException(nameof(assets)); } + + public string PackageName { get; } + public IReadOnlyList Assets { get; } } diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs index ed44742bc043..1eec4577f14c 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs @@ -1,75 +1,71 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Manifest +namespace Umbraco.Cms.Core.Manifest; +// contentApps: [ +// { +// name: 'App Name', // required +// alias: 'appAlias', // required +// weight: 0, // optional, default is 0, use values between -99 and +99 +// icon: 'icon.app', // required +// view: 'path/view.htm', // required +// show: [ // optional, default is always show +// '-content/foo', // hide for content type 'foo' +// '+content/*', // show for all other content types +// '+media/*', // show for all media types +// '+role/admin' // show for admin users. Role based permissions will override others. +// ] +// }, +// ... +// ] + +/// +/// Represents a content app definition, parsed from a manifest. +/// +/// Is used to create an actual . +[DataContract(Name = "appdef", Namespace = "")] +public class ManifestContentAppDefinition { - // contentApps: [ - // { - // name: 'App Name', // required - // alias: 'appAlias', // required - // weight: 0, // optional, default is 0, use values between -99 and +99 - // icon: 'icon.app', // required - // view: 'path/view.htm', // required - // show: [ // optional, default is always show - // '-content/foo', // hide for content type 'foo' - // '+content/*', // show for all other content types - // '+media/*', // show for all media types - // '+role/admin' // show for admin users. Role based permissions will override others. - // ] - // }, - // ... - // ] + private string? _view; /// - /// Represents a content app definition, parsed from a manifest. + /// Gets or sets the name of the content app. /// - /// Is used to create an actual . - [DataContract(Name = "appdef", Namespace = "")] - public class ManifestContentAppDefinition - { - private string? _view; - - /// - /// Gets or sets the name of the content app. - /// - [DataMember(Name = "name")] - public string? Name { get; set; } + [DataMember(Name = "name")] + public string? Name { get; set; } - /// - /// Gets or sets the unique alias of the content app. - /// - /// - /// Must be a valid javascript identifier, ie no spaces etc. - /// - [DataMember(Name = "alias")] - public string? Alias { get; set; } - - /// - /// Gets or sets the weight of the content app. - /// - [DataMember(Name = "weight")] - public int Weight { get; set; } + /// + /// Gets or sets the unique alias of the content app. + /// + /// + /// Must be a valid javascript identifier, ie no spaces etc. + /// + [DataMember(Name = "alias")] + public string? Alias { get; set; } - /// - /// Gets or sets the icon of the content app. - /// - /// - /// Must be a valid helveticons class name (see http://hlvticons.ch/). - /// - [DataMember(Name = "icon")] - public string? Icon { get; set; } + /// + /// Gets or sets the weight of the content app. + /// + [DataMember(Name = "weight")] + public int Weight { get; set; } - /// - /// Gets or sets the view for rendering the content app. - /// - [DataMember(Name = "view")] - public string? View { get; set; } + /// + /// Gets or sets the icon of the content app. + /// + /// + /// Must be a valid helveticons class name (see http://hlvticons.ch/). + /// + [DataMember(Name = "icon")] + public string? Icon { get; set; } - /// - /// Gets or sets the list of 'show' conditions for the content app. - /// - [DataMember(Name = "show")] - public string[] Show { get; set; } = Array.Empty(); + /// + /// Gets or sets the view for rendering the content app. + /// + [DataMember(Name = "view")] + public string? View { get; set; } - } + /// + /// Gets or sets the list of 'show' conditions for the content app. + /// + [DataMember(Name = "show")] + public string[] Show { get; set; } = Array.Empty(); } diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs b/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs index c4bc87e9a23b..e37ed66234ac 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Text.RegularExpressions; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; @@ -8,181 +5,195 @@ using Umbraco.Cms.Core.Models.Membership; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Manifest +namespace Umbraco.Cms.Core.Manifest; +// contentApps: [ +// { +// name: 'App Name', // required +// alias: 'appAlias', // required +// weight: 0, // optional, default is 0, use values between -99 and +99 +// icon: 'icon.app', // required +// view: 'path/view.htm', // required +// show: [ // optional, default is always show +// '-content/foo', // hide for content type 'foo' +// '+content/*', // show for all other content types +// '+media/*', // show for all media types +// '-member/foo' // hide for member type 'foo' +// '+member/*' // show for all member types +// '+role/admin' // show for admin users. Role based permissions will override others. +// ] +// }, +// ... +// ] + +/// +/// Represents a content app factory, for content apps parsed from the manifest. +/// +public class ManifestContentAppFactory : IContentAppFactory { - // contentApps: [ - // { - // name: 'App Name', // required - // alias: 'appAlias', // required - // weight: 0, // optional, default is 0, use values between -99 and +99 - // icon: 'icon.app', // required - // view: 'path/view.htm', // required - // show: [ // optional, default is always show - // '-content/foo', // hide for content type 'foo' - // '+content/*', // show for all other content types - // '+media/*', // show for all media types - // '-member/foo' // hide for member type 'foo' - // '+member/*' // show for all member types - // '+role/admin' // show for admin users. Role based permissions will override others. - // ] - // }, - // ... - // ] - - /// - /// Represents a content app factory, for content apps parsed from the manifest. - /// - public class ManifestContentAppFactory : IContentAppFactory - { - private readonly ManifestContentAppDefinition _definition; - private readonly IIOHelper _ioHelper; - - public ManifestContentAppFactory(ManifestContentAppDefinition definition, IIOHelper ioHelper) - { - _definition = definition; - _ioHelper = ioHelper; - } + private readonly ManifestContentAppDefinition _definition; + private readonly IIOHelper _ioHelper; - private ContentApp? _app; - private ShowRule[]? _showRules; + private ContentApp? _app; + private ShowRule[]? _showRules; - /// - public ContentApp? GetContentAppFor(object o,IEnumerable userGroups) - { - string? partA, partB; - - switch (o) - { - case IContent content: - partA = "content"; - partB = content.ContentType.Alias; - break; + public ManifestContentAppFactory(ManifestContentAppDefinition definition, IIOHelper ioHelper) + { + _definition = definition; + _ioHelper = ioHelper; + } - case IMedia media: - partA = "media"; - partB = media.ContentType.Alias; - break; - case IMember member: - partA = "member"; - partB = member.ContentType.Alias; - break; - case IContentType contentType: - partA = "contentType"; - partB = contentType?.Alias; - break; - case IDictionaryItem _: - partA = "dictionary"; - partB = "*"; //Not really a different type for dictionary items - break; + /// + public ContentApp? GetContentAppFor(object o, IEnumerable userGroups) + { + string? partA, partB; - default: - return null; - } + switch (o) + { + case IContent content: + partA = "content"; + partB = content.ContentType.Alias; + break; + + case IMedia media: + partA = "media"; + partB = media.ContentType.Alias; + break; + case IMember member: + partA = "member"; + partB = member.ContentType.Alias; + break; + case IContentType contentType: + partA = "contentType"; + partB = contentType?.Alias; + break; + case IDictionaryItem _: + partA = "dictionary"; + partB = "*"; //Not really a different type for dictionary items + break; + + default: + return null; + } - var rules = _showRules ?? (_showRules = ShowRule.Parse(_definition.Show).ToArray()); - var userGroupsList = userGroups.ToList(); + ShowRule[] rules = _showRules ?? (_showRules = ShowRule.Parse(_definition.Show).ToArray()); + var userGroupsList = userGroups.ToList(); - var okRole = false; - var hasRole = false; - var okType = false; - var hasType = false; + var okRole = false; + var hasRole = false; + var okType = false; + var hasType = false; - foreach (var rule in rules) + foreach (ShowRule rule in rules) + { + if (rule.PartA?.InvariantEquals("role") ?? false) { - if (rule.PartA?.InvariantEquals("role") ?? false) + // if roles have been ok-ed already, skip the rule + if (okRole) { - // if roles have been ok-ed already, skip the rule - if (okRole) - continue; - - // remember we have role rules - hasRole = true; - - foreach (var group in userGroupsList) - { - // if the entry does not apply, skip - if (!rule.Matches("role", group.Alias)) - continue; - - // if the entry applies, - // if it's an exclude entry, exit, do not display the content app - if (!rule.Show) - return null; - - // else ok to display, remember roles are ok, break from userGroupsList - okRole = rule.Show; - break; - } + continue; } - else // it is a type rule - { - // if type has been ok-ed already, skip the rule - if (okType) - continue; - // remember we have type rules - hasType = true; + // remember we have role rules + hasRole = true; - // if the entry does not apply, skip it - if (!rule.Matches(partA, partB)) + foreach (IReadOnlyUserGroup group in userGroupsList) + { + // if the entry does not apply, skip + if (!rule.Matches("role", group.Alias)) + { continue; + } // if the entry applies, // if it's an exclude entry, exit, do not display the content app if (!rule.Show) + { return null; + } - // else ok to display, remember type rules are ok - okType = true; + // else ok to display, remember roles are ok, break from userGroupsList + okRole = rule.Show; + break; } } + else // it is a type rule + { + // if type has been ok-ed already, skip the rule + if (okType) + { + continue; + } - // if roles rules are specified but not ok, - // or if type roles are specified but not ok, - // cannot display the content app - if ((hasRole && !okRole) || (hasType && !okType)) - return null; + // remember we have type rules + hasType = true; - // else - // content app can be displayed - return _app ??= new ContentApp - { - Alias = _definition.Alias, - Name = _definition.Name, - Icon = _definition.Icon, - View = _ioHelper.ResolveRelativeOrVirtualUrl(_definition.View), - Weight = _definition.Weight - }; + // if the entry does not apply, skip it + if (!rule.Matches(partA, partB)) + { + continue; + } + + // if the entry applies, + // if it's an exclude entry, exit, do not display the content app + if (!rule.Show) + { + return null; + } + + // else ok to display, remember type rules are ok + okType = true; + } + } + + // if roles rules are specified but not ok, + // or if type roles are specified but not ok, + // cannot display the content app + if ((hasRole && !okRole) || (hasType && !okType)) + { + return null; } - private class ShowRule + // else + // content app can be displayed + return _app ??= new ContentApp { - private static readonly Regex ShowRegex = new Regex("^([+-])?([a-z]+)/([a-z0-9_]+|\\*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + Alias = _definition.Alias, + Name = _definition.Name, + Icon = _definition.Icon, + View = _ioHelper.ResolveRelativeOrVirtualUrl(_definition.View), + Weight = _definition.Weight + }; + } - public bool Show { get; private set; } - public string? PartA { get; private set; } - public string? PartB { get; private set; } + private class ShowRule + { + private static readonly Regex ShowRegex = new("^([+-])?([a-z]+)/([a-z0-9_]+|\\*)$", + RegexOptions.Compiled | RegexOptions.IgnoreCase); - public bool Matches(string? partA, string? partB) - { - return (PartA == "*" || (PartA?.InvariantEquals(partA) ?? false)) && (PartB == "*" || (PartB?.InvariantEquals(partB) ?? false)); - } + public bool Show { get; private set; } + public string? PartA { get; private set; } + public string? PartB { get; private set; } - public static IEnumerable Parse(string[] rules) + public bool Matches(string? partA, string? partB) => + (PartA == "*" || (PartA?.InvariantEquals(partA) ?? false)) && + (PartB == "*" || (PartB?.InvariantEquals(partB) ?? false)); + + public static IEnumerable Parse(string[] rules) + { + foreach (var rule in rules) { - foreach (var rule in rules) + Match match = ShowRegex.Match(rule); + if (!match.Success) { - var match = ShowRegex.Match(rule); - if (!match.Success) - throw new FormatException($"Illegal 'show' entry \"{rule}\" in manifest."); - - yield return new ShowRule - { - Show = match.Groups[1].Value != "-", - PartA = match.Groups[2].Value, - PartB = match.Groups[3].Value - }; + throw new FormatException($"Illegal 'show' entry \"{rule}\" in manifest."); } + + yield return new ShowRule + { + Show = match.Groups[1].Value != "-", + PartA = match.Groups[2].Value, + PartB = match.Groups[3].Value + }; } } } diff --git a/src/Umbraco.Core/Manifest/ManifestDashboard.cs b/src/Umbraco.Core/Manifest/ManifestDashboard.cs index a10c3a217798..05e3eb8e9474 100644 --- a/src/Umbraco.Core/Manifest/ManifestDashboard.cs +++ b/src/Umbraco.Core/Manifest/ManifestDashboard.cs @@ -1,25 +1,20 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Dashboards; -namespace Umbraco.Cms.Core.Manifest +namespace Umbraco.Cms.Core.Manifest; + +[DataContract] +public class ManifestDashboard : IDashboard { - [DataContract] - public class ManifestDashboard : IDashboard - { - [DataMember(Name = "alias", IsRequired = true)] - public string Alias { get; set; } = null!; + [DataMember(Name = "weight")] public int Weight { get; set; } = 100; - [DataMember(Name = "weight")] - public int Weight { get; set; } = 100; + [DataMember(Name = "alias", IsRequired = true)] + public string Alias { get; set; } = null!; - [DataMember(Name = "view", IsRequired = true)] - public string View { get; set; } = null!; + [DataMember(Name = "view", IsRequired = true)] + public string View { get; set; } = null!; - [DataMember(Name = "sections")] - public string[] Sections { get; set; } = Array.Empty(); + [DataMember(Name = "sections")] public string[] Sections { get; set; } = Array.Empty(); - [DataMember(Name = "access")] - public IAccessRule[] AccessRules { get; set; } = Array.Empty(); - } + [DataMember(Name = "access")] public IAccessRule[] AccessRules { get; set; } = Array.Empty(); } diff --git a/src/Umbraco.Core/Manifest/ManifestFilterCollection.cs b/src/Umbraco.Core/Manifest/ManifestFilterCollection.cs index 9c692f69b342..2976077f90ed 100644 --- a/src/Umbraco.Core/Manifest/ManifestFilterCollection.cs +++ b/src/Umbraco.Core/Manifest/ManifestFilterCollection.cs @@ -1,26 +1,25 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Manifest +namespace Umbraco.Cms.Core.Manifest; + +/// +/// Contains the manifest filters. +/// +public class ManifestFilterCollection : BuilderCollectionBase { + public ManifestFilterCollection(Func> items) : base(items) + { + } + /// - /// Contains the manifest filters. + /// Filters package manifests. /// - public class ManifestFilterCollection : BuilderCollectionBase + /// The package manifests. + public void Filter(List manifests) { - public ManifestFilterCollection(Func> items) : base(items) - { - } - - /// - /// Filters package manifests. - /// - /// The package manifests. - public void Filter(List manifests) + foreach (IManifestFilter filter in this) { - foreach (var filter in this) - filter.Filter(manifests); + filter.Filter(manifests); } } } diff --git a/src/Umbraco.Core/Manifest/ManifestFilterCollectionBuilder.cs b/src/Umbraco.Core/Manifest/ManifestFilterCollectionBuilder.cs index 00ac3609dd27..9527fc31c299 100644 --- a/src/Umbraco.Core/Manifest/ManifestFilterCollectionBuilder.cs +++ b/src/Umbraco.Core/Manifest/ManifestFilterCollectionBuilder.cs @@ -1,13 +1,13 @@ using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Manifest +namespace Umbraco.Cms.Core.Manifest; + +public class ManifestFilterCollectionBuilder : OrderedCollectionBuilderBase { - public class ManifestFilterCollectionBuilder : OrderedCollectionBuilderBase - { - protected override ManifestFilterCollectionBuilder This => this; + protected override ManifestFilterCollectionBuilder This => this; - // do NOT cache this, it's only used once - protected override ServiceLifetime CollectionLifetime => ServiceLifetime.Transient; - } + // do NOT cache this, it's only used once + protected override ServiceLifetime CollectionLifetime => ServiceLifetime.Transient; } diff --git a/src/Umbraco.Core/Manifest/ManifestSection.cs b/src/Umbraco.Core/Manifest/ManifestSection.cs index 864a0734e266..b391ec89cadb 100644 --- a/src/Umbraco.Core/Manifest/ManifestSection.cs +++ b/src/Umbraco.Core/Manifest/ManifestSection.cs @@ -1,15 +1,12 @@ using System.Runtime.Serialization; using Umbraco.Cms.Core.Sections; -namespace Umbraco.Cms.Core.Manifest +namespace Umbraco.Cms.Core.Manifest; + +[DataContract(Name = "section", Namespace = "")] +public class ManifestSection : ISection { - [DataContract(Name = "section", Namespace = "")] - public class ManifestSection : ISection - { - [DataMember(Name = "alias")] - public string Alias { get; set; } = string.Empty; + [DataMember(Name = "alias")] public string Alias { get; set; } = string.Empty; - [DataMember(Name = "name")] - public string Name { get; set; } = string.Empty; - } + [DataMember(Name = "name")] public string Name { get; set; } = string.Empty; } diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index a71cf1f6f663..ffdd33cd6f89 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -1,115 +1,113 @@ -using System; -using System.IO; using System.Runtime.Serialization; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Manifest +namespace Umbraco.Cms.Core.Manifest; + +/// +/// Represents the content of a package manifest. +/// +[DataContract] +public class PackageManifest { + private string? _packageName; /// - /// Represents the content of a package manifest. + /// An optional package name. If not specified then the directory name is used. /// - [DataContract] - public class PackageManifest + [DataMember(Name = "name")] + public string? PackageName { - private string? _packageName; - - /// - /// An optional package name. If not specified then the directory name is used. - /// - [DataMember(Name = "name")] - public string? PackageName + get { - get + if (!_packageName.IsNullOrWhiteSpace()) { - if (!_packageName.IsNullOrWhiteSpace()) - { - return _packageName; - } - if (!Source.IsNullOrWhiteSpace()) - { - _packageName = Path.GetFileName(Path.GetDirectoryName(Source)); - } return _packageName; } - set => _packageName = value; - } - [DataMember(Name = "packageView")] - public string? PackageView { get; set; } - - /// - /// Gets the source path of the manifest. - /// - /// - /// Gets the full absolute file path of the manifest, - /// using system directory separators. - /// - [IgnoreDataMember] - public string Source { get; set; } = null!; - - /// - /// Gets or sets the version of the package - /// - [DataMember(Name = "version")] - public string Version { get; set; } = string.Empty; - - /// - /// Gets or sets a value indicating whether telemetry is allowed - /// - [DataMember(Name = "allowPackageTelemetry")] - public bool AllowPackageTelemetry { get; set; } = true; - - [DataMember(Name = "bundleOptions")] - public BundleOptions BundleOptions { get; set; } - - /// - /// Gets or sets the scripts listed in the manifest. - /// - [DataMember(Name = "javascript")] - public string[] Scripts { get; set; } = Array.Empty(); - - /// - /// Gets or sets the stylesheets listed in the manifest. - /// - [DataMember(Name = "css")] - public string[] Stylesheets { get; set; } = Array.Empty(); - - /// - /// Gets or sets the property editors listed in the manifest. - /// - [DataMember(Name = "propertyEditors")] - public IDataEditor[] PropertyEditors { get; set; } = Array.Empty(); - - /// - /// Gets or sets the parameter editors listed in the manifest. - /// - [DataMember(Name = "parameterEditors")] - public IDataEditor[] ParameterEditors { get; set; } = Array.Empty(); - - /// - /// Gets or sets the grid editors listed in the manifest. - /// - [DataMember(Name = "gridEditors")] - public GridEditor[] GridEditors { get; set; } = Array.Empty(); - - /// - /// Gets or sets the content apps listed in the manifest. - /// - [DataMember(Name = "contentApps")] - public ManifestContentAppDefinition[] ContentApps { get; set; } = Array.Empty(); - - /// - /// Gets or sets the dashboards listed in the manifest. - /// - [DataMember(Name = "dashboards")] - public ManifestDashboard[] Dashboards { get; set; } = Array.Empty(); - - /// - /// Gets or sets the sections listed in the manifest. - /// - [DataMember(Name = "sections")] - public ManifestSection[] Sections { get; set; } = Array.Empty(); + if (!Source.IsNullOrWhiteSpace()) + { + _packageName = Path.GetFileName(Path.GetDirectoryName(Source)); + } + + return _packageName; + } + set => _packageName = value; } + + [DataMember(Name = "packageView")] public string? PackageView { get; set; } + + /// + /// Gets the source path of the manifest. + /// + /// + /// + /// Gets the full absolute file path of the manifest, + /// using system directory separators. + /// + /// + [IgnoreDataMember] + public string Source { get; set; } = null!; + + /// + /// Gets or sets the version of the package + /// + [DataMember(Name = "version")] + public string Version { get; set; } = string.Empty; + + /// + /// Gets or sets a value indicating whether telemetry is allowed + /// + [DataMember(Name = "allowPackageTelemetry")] + public bool AllowPackageTelemetry { get; set; } = true; + + [DataMember(Name = "bundleOptions")] public BundleOptions BundleOptions { get; set; } + + /// + /// Gets or sets the scripts listed in the manifest. + /// + [DataMember(Name = "javascript")] + public string[] Scripts { get; set; } = Array.Empty(); + + /// + /// Gets or sets the stylesheets listed in the manifest. + /// + [DataMember(Name = "css")] + public string[] Stylesheets { get; set; } = Array.Empty(); + + /// + /// Gets or sets the property editors listed in the manifest. + /// + [DataMember(Name = "propertyEditors")] + public IDataEditor[] PropertyEditors { get; set; } = Array.Empty(); + + /// + /// Gets or sets the parameter editors listed in the manifest. + /// + [DataMember(Name = "parameterEditors")] + public IDataEditor[] ParameterEditors { get; set; } = Array.Empty(); + + /// + /// Gets or sets the grid editors listed in the manifest. + /// + [DataMember(Name = "gridEditors")] + public GridEditor[] GridEditors { get; set; } = Array.Empty(); + + /// + /// Gets or sets the content apps listed in the manifest. + /// + [DataMember(Name = "contentApps")] + public ManifestContentAppDefinition[] ContentApps { get; set; } = Array.Empty(); + + /// + /// Gets or sets the dashboards listed in the manifest. + /// + [DataMember(Name = "dashboards")] + public ManifestDashboard[] Dashboards { get; set; } = Array.Empty(); + + /// + /// Gets or sets the sections listed in the manifest. + /// + [DataMember(Name = "sections")] + public ManifestSection[] Sections { get; set; } = Array.Empty(); } diff --git a/src/Umbraco.Core/Mapping/IMapDefinition.cs b/src/Umbraco.Core/Mapping/IMapDefinition.cs index 3d4270c93e04..4e9c3849c17f 100644 --- a/src/Umbraco.Core/Mapping/IMapDefinition.cs +++ b/src/Umbraco.Core/Mapping/IMapDefinition.cs @@ -1,13 +1,12 @@ -namespace Umbraco.Cms.Core.Mapping +namespace Umbraco.Cms.Core.Mapping; + +/// +/// Defines maps for . +/// +public interface IMapDefinition { /// - /// Defines maps for . + /// Defines maps. /// - public interface IMapDefinition - { - /// - /// Defines maps. - /// - void DefineMaps(IUmbracoMapper mapper); - } + void DefineMaps(IUmbracoMapper mapper); } diff --git a/src/Umbraco.Core/Mapping/IUmbracoMapper.cs b/src/Umbraco.Core/Mapping/IUmbracoMapper.cs index c99359cbdf6e..b1010a2d1c18 100644 --- a/src/Umbraco.Core/Mapping/IUmbracoMapper.cs +++ b/src/Umbraco.Core/Mapping/IUmbracoMapper.cs @@ -1,156 +1,155 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Mapping; -namespace Umbraco.Cms.Core.Mapping +public interface IUmbracoMapper { - public interface IUmbracoMapper - { - /// - /// Defines a mapping. - /// - /// The source type. - /// The target type. - void Define(); - - /// - /// Defines a mapping. - /// - /// The source type. - /// The target type. - /// A mapping method. - void Define(Action map); - - /// - /// Defines a mapping. - /// - /// The source type. - /// The target type. - /// A constructor method. - void Define(Func ctor); - - /// - /// Defines a mapping. - /// - /// The source type. - /// The target type. - /// A constructor method. - /// A mapping method. - void Define(Func ctor, Action map); - - /// - /// Maps a source object to a new target object. - /// - /// The target type. - /// The source object. - /// The target object. - TTarget? Map(object? source); - - /// - /// Maps a source object to a new target object. - /// - /// The target type. - /// The source object. - /// A mapper context preparation method. - /// The target object. - TTarget? Map(object? source, Action f); - - /// - /// Maps a source object to a new target object. - /// - /// The target type. - /// The source object. - /// A mapper context. - /// The target object. - TTarget? Map(object? source, MapperContext context); - - /// - /// Maps a source object to a new target object. - /// - /// The source type. - /// The target type. - /// The source object. - /// The target object. - TTarget? Map(TSource? source); - - /// - /// Maps a source object to a new target object. - /// - /// The source type. - /// The target type. - /// The source object. - /// A mapper context preparation method. - /// The target object. - TTarget? Map(TSource source, Action f); - - /// - /// Maps a source object to a new target object. - /// - /// The source type. - /// The target type. - /// The source object. - /// A mapper context. - /// The target object. - TTarget? Map(TSource? source, MapperContext context); - - /// - /// Maps a source object to an existing target object. - /// - /// The source type. - /// The target type. - /// The source object. - /// The target object. - /// The target object. - TTarget Map(TSource source, TTarget target); - - /// - /// Maps a source object to an existing target object. - /// - /// The source type. - /// The target type. - /// The source object. - /// The target object. - /// A mapper context preparation method. - /// The target object. - TTarget Map(TSource source, TTarget target, Action f); - - /// - /// Maps a source object to an existing target object. - /// - /// The source type. - /// The target type. - /// The source object. - /// The target object. - /// A mapper context. - /// The target object. - TTarget Map(TSource source, TTarget target, MapperContext context); - - /// - /// Maps an enumerable of source objects to a new list of target objects. - /// - /// The type of the source objects. - /// The type of the target objects. - /// The source objects. - /// A list containing the target objects. - List MapEnumerable(IEnumerable source); - - /// - /// Maps an enumerable of source objects to a new list of target objects. - /// - /// The type of the source objects. - /// The type of the target objects. - /// The source objects. - /// A mapper context preparation method. - /// A list containing the target objects. - List MapEnumerable(IEnumerable source, Action f); - - /// - /// Maps an enumerable of source objects to a new list of target objects. - /// - /// The type of the source objects. - /// The type of the target objects. - /// The source objects. - /// A mapper context. - /// A list containing the target objects. - List MapEnumerable(IEnumerable source, MapperContext context); - } + /// + /// Defines a mapping. + /// + /// The source type. + /// The target type. + void Define(); + + /// + /// Defines a mapping. + /// + /// The source type. + /// The target type. + /// A mapping method. + void Define(Action map); + + /// + /// Defines a mapping. + /// + /// The source type. + /// The target type. + /// A constructor method. + void Define(Func ctor); + + /// + /// Defines a mapping. + /// + /// The source type. + /// The target type. + /// A constructor method. + /// A mapping method. + void Define(Func ctor, + Action map); + + /// + /// Maps a source object to a new target object. + /// + /// The target type. + /// The source object. + /// The target object. + TTarget? Map(object? source); + + /// + /// Maps a source object to a new target object. + /// + /// The target type. + /// The source object. + /// A mapper context preparation method. + /// The target object. + TTarget? Map(object? source, Action f); + + /// + /// Maps a source object to a new target object. + /// + /// The target type. + /// The source object. + /// A mapper context. + /// The target object. + TTarget? Map(object? source, MapperContext context); + + /// + /// Maps a source object to a new target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// The target object. + TTarget? Map(TSource? source); + + /// + /// Maps a source object to a new target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// A mapper context preparation method. + /// The target object. + TTarget? Map(TSource source, Action f); + + /// + /// Maps a source object to a new target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// A mapper context. + /// The target object. + TTarget? Map(TSource? source, MapperContext context); + + /// + /// Maps a source object to an existing target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// The target object. + /// The target object. + TTarget Map(TSource source, TTarget target); + + /// + /// Maps a source object to an existing target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// The target object. + /// A mapper context preparation method. + /// The target object. + TTarget Map(TSource source, TTarget target, Action f); + + /// + /// Maps a source object to an existing target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// The target object. + /// A mapper context. + /// The target object. + TTarget Map(TSource source, TTarget target, MapperContext context); + + /// + /// Maps an enumerable of source objects to a new list of target objects. + /// + /// The type of the source objects. + /// The type of the target objects. + /// The source objects. + /// A list containing the target objects. + List MapEnumerable(IEnumerable source); + + /// + /// Maps an enumerable of source objects to a new list of target objects. + /// + /// The type of the source objects. + /// The type of the target objects. + /// The source objects. + /// A mapper context preparation method. + /// A list containing the target objects. + List MapEnumerable(IEnumerable source, + Action f); + + /// + /// Maps an enumerable of source objects to a new list of target objects. + /// + /// The type of the source objects. + /// The type of the target objects. + /// The source objects. + /// A mapper context. + /// A list containing the target objects. + List MapEnumerable(IEnumerable source, + MapperContext context); } diff --git a/src/Umbraco.Core/Mapping/MapDefinitionCollection.cs b/src/Umbraco.Core/Mapping/MapDefinitionCollection.cs index 27d4ad73d004..5f6e7baf115b 100644 --- a/src/Umbraco.Core/Mapping/MapDefinitionCollection.cs +++ b/src/Umbraco.Core/Mapping/MapDefinitionCollection.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Mapping +namespace Umbraco.Cms.Core.Mapping; + +public class MapDefinitionCollection : BuilderCollectionBase { - public class MapDefinitionCollection : BuilderCollectionBase + public MapDefinitionCollection(Func> items) : base(items) { - public MapDefinitionCollection(Func> items) : base(items) - { - } } } diff --git a/src/Umbraco.Core/Mapping/MapDefinitionCollectionBuilder.cs b/src/Umbraco.Core/Mapping/MapDefinitionCollectionBuilder.cs index 698dce1648e1..906ee0c9368a 100644 --- a/src/Umbraco.Core/Mapping/MapDefinitionCollectionBuilder.cs +++ b/src/Umbraco.Core/Mapping/MapDefinitionCollectionBuilder.cs @@ -1,12 +1,12 @@ using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Mapping +namespace Umbraco.Cms.Core.Mapping; + +public class MapDefinitionCollectionBuilder : SetCollectionBuilderBase { - public class MapDefinitionCollectionBuilder : SetCollectionBuilderBase - { - protected override MapDefinitionCollectionBuilder This => this; + protected override MapDefinitionCollectionBuilder This => this; - protected override ServiceLifetime CollectionLifetime => ServiceLifetime.Transient; - } + protected override ServiceLifetime CollectionLifetime => ServiceLifetime.Transient; } diff --git a/src/Umbraco.Core/Mapping/MapperContext.cs b/src/Umbraco.Core/Mapping/MapperContext.cs index 2355e9bd05cb..b0f177af86ad 100644 --- a/src/Umbraco.Core/Mapping/MapperContext.cs +++ b/src/Umbraco.Core/Mapping/MapperContext.cs @@ -1,129 +1,120 @@ -using System.Collections.Generic; -using System.Linq; +namespace Umbraco.Cms.Core.Mapping; -namespace Umbraco.Cms.Core.Mapping +/// +/// Represents a mapper context. +/// +public class MapperContext { + private readonly IUmbracoMapper _mapper; + private IDictionary? _items; + /// - /// Represents a mapper context. + /// Initializes a new instance of the class. /// - public class MapperContext - { - private readonly IUmbracoMapper _mapper; - private IDictionary? _items; + public MapperContext(IUmbracoMapper mapper) => _mapper = mapper; - /// - /// Initializes a new instance of the class. - /// - public MapperContext(IUmbracoMapper mapper) - { - _mapper = mapper; - } - - /// - /// Gets a value indicating whether the context has items. - /// - public bool HasItems => _items != null; + /// + /// Gets a value indicating whether the context has items. + /// + public bool HasItems => _items != null; - /// - /// Gets the context items. - /// - public IDictionary Items => _items ?? (_items = new Dictionary()); + /// + /// Gets the context items. + /// + public IDictionary Items => _items ?? (_items = new Dictionary()); - #region Map + #region Map - /// - /// Maps a source object to a new target object. - /// - /// The target type. - /// The source object. - /// The target object. - public TTarget? Map(object? source) - => _mapper.Map(source, this); + /// + /// Maps a source object to a new target object. + /// + /// The target type. + /// The source object. + /// The target object. + public TTarget? Map(object? source) + => _mapper.Map(source, this); - // let's say this is a bad (dangerous) idea, and leave it out for now - /* - /// - /// Maps a source object to a new target object. - /// - /// The target type. - /// The source object. - /// A mapper context preparation method. - /// The target object. - public TTarget Map(object source, Action f) - { - f(this); - return _mapper.Map(source, this); - } - */ + // let's say this is a bad (dangerous) idea, and leave it out for now + /* + /// + /// Maps a source object to a new target object. + /// + /// The target type. + /// The source object. + /// A mapper context preparation method. + /// The target object. + public TTarget Map(object source, Action f) + { + f(this); + return _mapper.Map(source, this); + } + */ - /// - /// Maps a source object to a new target object. - /// - /// The source type. - /// The target type. - /// The source object. - /// The target object. - public TTarget? Map(TSource? source) - => _mapper.Map(source, this); + /// + /// Maps a source object to a new target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// The target object. + public TTarget? Map(TSource? source) + => _mapper.Map(source, this); - // let's say this is a bad (dangerous) idea, and leave it out for now - /* - /// - /// Maps a source object to a new target object. - /// - /// The source type. - /// The target type. - /// The source object. - /// A mapper context preparation method. - /// The target object. - public TTarget Map(TSource source, Action f) - { - f(this); - return _mapper.Map(source, this); - } - */ + // let's say this is a bad (dangerous) idea, and leave it out for now + /* + /// + /// Maps a source object to a new target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// A mapper context preparation method. + /// The target object. + public TTarget Map(TSource source, Action f) + { + f(this); + return _mapper.Map(source, this); + } + */ - /// - /// Maps a source object to an existing target object. - /// - /// The source type. - /// The target type. - /// The source object. - /// The target object. - /// The target object. - public TTarget Map(TSource source, TTarget target) - => _mapper.Map(source, target, this); + /// + /// Maps a source object to an existing target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// The target object. + /// The target object. + public TTarget Map(TSource source, TTarget target) + => _mapper.Map(source, target, this); - // let's say this is a bad (dangerous) idea, and leave it out for now - /* - /// - /// Maps a source object to an existing target object. - /// - /// The source type. - /// The target type. - /// The source object. - /// The target object. - /// A mapper context preparation method. - /// The target object. - public TTarget Map(TSource source, TTarget target, Action f) - { - f(this); - return _mapper.Map(source, target, this); - } - */ + // let's say this is a bad (dangerous) idea, and leave it out for now + /* + /// + /// Maps a source object to an existing target object. + /// + /// The source type. + /// The target type. + /// The source object. + /// The target object. + /// A mapper context preparation method. + /// The target object. + public TTarget Map(TSource source, TTarget target, Action f) + { + f(this); + return _mapper.Map(source, target, this); + } + */ - /// - /// Maps an enumerable of source objects to a new list of target objects. - /// - /// The type of the source objects. - /// The type of the target objects. - /// The source objects. - /// A list containing the target objects. - public List MapEnumerable(IEnumerable source) - { - return source.Select(Map).Where(x => x is not null).ToList()!; - } + /// + /// Maps an enumerable of source objects to a new list of target objects. + /// + /// The type of the source objects. + /// The type of the target objects. + /// The source objects. + /// A list containing the target objects. + public List MapEnumerable(IEnumerable source) => + source.Select(Map).Where(x => x is not null).ToList()!; - #endregion - } + #endregion } diff --git a/src/Umbraco.Core/Media/EmbedProviders/DailyMotion.cs b/src/Umbraco.Core/Media/EmbedProviders/DailyMotion.cs index b79e1a8de290..79a1faed5bc4 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/DailyMotion.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/DailyMotion.cs @@ -1,34 +1,30 @@ -using System.Collections.Generic; +using System.Xml; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Media.EmbedProviders +namespace Umbraco.Cms.Core.Media.EmbedProviders; + +// TODO (V10): change base class to OEmbedProviderBase +public class DailyMotion : EmbedProviderBase { - // TODO (V10): change base class to OEmbedProviderBase - public class DailyMotion : EmbedProviderBase + public DailyMotion(IJsonSerializer jsonSerializer) : base(jsonSerializer) { - public override string ApiEndpoint => "https://www.dailymotion.com/services/oembed"; + } - public override string[] UrlSchemeRegex => new string[] - { - @"dailymotion.com/video/.*" - }; + public override string ApiEndpoint => "https://www.dailymotion.com/services/oembed"; - public override Dictionary RequestParams => new Dictionary() - { - //ApiUrl/?format=xml - {"format", "xml"} - }; + public override string[] UrlSchemeRegex => new[] {@"dailymotion.com/video/.*"}; - public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - var xmlDocument = base.GetXmlResponse(requestUrl); + public override Dictionary RequestParams => new() + { + //ApiUrl/?format=xml + {"format", "xml"} + }; - return GetXmlProperty(xmlDocument, "/oembed/html"); - } + public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); - public DailyMotion(IJsonSerializer jsonSerializer) : base(jsonSerializer) - { - } + return GetXmlProperty(xmlDocument, "/oembed/html"); } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/EmbedProviderBase.cs b/src/Umbraco.Core/Media/EmbedProviders/EmbedProviderBase.cs index 6d745d3d4914..e51005b84bd6 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/EmbedProviderBase.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/EmbedProviderBase.cs @@ -1,14 +1,12 @@ -using System; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Media.EmbedProviders +namespace Umbraco.Cms.Core.Media.EmbedProviders; + +[Obsolete("Use OEmbedProviderBase instead")] +public abstract class EmbedProviderBase : OEmbedProviderBase { - [Obsolete("Use OEmbedProviderBase instead")] - public abstract class EmbedProviderBase : OEmbedProviderBase + protected EmbedProviderBase(IJsonSerializer jsonSerializer) + : base(jsonSerializer) { - protected EmbedProviderBase(IJsonSerializer jsonSerializer) - : base(jsonSerializer) - { - } } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/EmbedProvidersCollection.cs b/src/Umbraco.Core/Media/EmbedProviders/EmbedProvidersCollection.cs index 615d16f51c4c..5d517a5e091e 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/EmbedProvidersCollection.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/EmbedProvidersCollection.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Media.EmbedProviders +namespace Umbraco.Cms.Core.Media.EmbedProviders; + +public class EmbedProvidersCollection : BuilderCollectionBase { - public class EmbedProvidersCollection : BuilderCollectionBase + public EmbedProvidersCollection(Func> items) : base(items) { - public EmbedProvidersCollection(Func> items) : base(items) - { - } } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/EmbedProvidersCollectionBuilder.cs b/src/Umbraco.Core/Media/EmbedProviders/EmbedProvidersCollectionBuilder.cs index f79880b61f5c..a4856557c86b 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/EmbedProvidersCollectionBuilder.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/EmbedProvidersCollectionBuilder.cs @@ -1,9 +1,9 @@ using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Media.EmbedProviders +namespace Umbraco.Cms.Core.Media.EmbedProviders; + +public class EmbedProvidersCollectionBuilder : OrderedCollectionBuilderBase { - public class EmbedProvidersCollectionBuilder : OrderedCollectionBuilderBase - { - protected override EmbedProvidersCollectionBuilder This => this; - } + protected override EmbedProvidersCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Flickr.cs b/src/Umbraco.Core/Media/EmbedProviders/Flickr.cs index 2ea5fd8109ac..4d773a79d478 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Flickr.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Flickr.cs @@ -1,37 +1,33 @@ -using System.Collections.Generic; using System.Net; +using System.Xml; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Media.EmbedProviders +namespace Umbraco.Cms.Core.Media.EmbedProviders; + +// TODO(V10) : change base class to OEmbedProviderBase +public class Flickr : EmbedProviderBase { - // TODO(V10) : change base class to OEmbedProviderBase - public class Flickr : EmbedProviderBase + public Flickr(IJsonSerializer jsonSerializer) : base(jsonSerializer) { - public override string ApiEndpoint => "http://www.flickr.com/services/oembed/"; + } - public override string[] UrlSchemeRegex => new string[] - { - @"flickr.com\/photos\/*", - @"flic.kr\/p\/*" - }; + public override string ApiEndpoint => "http://www.flickr.com/services/oembed/"; - public override Dictionary RequestParams => new Dictionary(); + public override string[] UrlSchemeRegex => new[] {@"flickr.com\/photos\/*", @"flic.kr\/p\/*"}; - public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - var xmlDocument = base.GetXmlResponse(requestUrl); + public override Dictionary RequestParams => new(); - var imageUrl = GetXmlProperty(xmlDocument, "/oembed/url"); - var imageWidth = GetXmlProperty(xmlDocument, "/oembed/width"); - var imageHeight = GetXmlProperty(xmlDocument, "/oembed/height"); - var imageTitle = GetXmlProperty(xmlDocument, "/oembed/title"); + public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); - return string.Format("\"{3}\"", imageUrl, imageWidth, imageHeight, WebUtility.HtmlEncode(imageTitle)); - } + var imageUrl = GetXmlProperty(xmlDocument, "/oembed/url"); + var imageWidth = GetXmlProperty(xmlDocument, "/oembed/width"); + var imageHeight = GetXmlProperty(xmlDocument, "/oembed/height"); + var imageTitle = GetXmlProperty(xmlDocument, "/oembed/title"); - public Flickr(IJsonSerializer jsonSerializer) : base(jsonSerializer) - { - } + return string.Format("\"{3}\"", imageUrl, imageWidth, + imageHeight, WebUtility.HtmlEncode(imageTitle)); } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/GettyImages.cs b/src/Umbraco.Core/Media/EmbedProviders/GettyImages.cs index 2e0ea7864932..45fa425a4528 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/GettyImages.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/GettyImages.cs @@ -1,33 +1,27 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Media.EmbedProviders +namespace Umbraco.Cms.Core.Media.EmbedProviders; + +// TODO(V10) : change base class to OEmbedProviderBase +public class GettyImages : EmbedProviderBase { - // TODO(V10) : change base class to OEmbedProviderBase - public class GettyImages : EmbedProviderBase + public GettyImages(IJsonSerializer jsonSerializer) : base(jsonSerializer) { - public override string ApiEndpoint => "http://embed.gettyimages.com/oembed"; + } - //http://gty.im/74917285 - //http://www.gettyimages.com/detail/74917285 - public override string[] UrlSchemeRegex => new string[] - { - @"gty\.im/*", - @"gettyimages.com\/detail\/*" - }; + public override string ApiEndpoint => "http://embed.gettyimages.com/oembed"; - public override Dictionary RequestParams => new Dictionary(); + //http://gty.im/74917285 + //http://www.gettyimages.com/detail/74917285 + public override string[] UrlSchemeRegex => new[] {@"gty\.im/*", @"gettyimages.com\/detail\/*"}; - public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - var oembed = base.GetJsonResponse(requestUrl); + public override Dictionary RequestParams => new(); - return oembed?.GetHtml(); - } + public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse oembed = base.GetJsonResponse(requestUrl); - public GettyImages(IJsonSerializer jsonSerializer) : base(jsonSerializer) - { - } + return oembed?.GetHtml(); } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Giphy.cs b/src/Umbraco.Core/Media/EmbedProviders/Giphy.cs index 36df7e736212..84dbbd302f70 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Giphy.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Giphy.cs @@ -1,34 +1,28 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Media.EmbedProviders +namespace Umbraco.Cms.Core.Media.EmbedProviders; + +/// +/// Embed Provider for Giphy.com the popular online GIFs and animated sticker provider. +/// +/// TODO(V10) : change base class to OEmbedProviderBase +public class Giphy : EmbedProviderBase { - /// - /// Embed Provider for Giphy.com the popular online GIFs and animated sticker provider. - /// - /// TODO(V10) : change base class to OEmbedProviderBase - public class Giphy : EmbedProviderBase + public Giphy(IJsonSerializer jsonSerializer) : base(jsonSerializer) { - public override string ApiEndpoint => "https://giphy.com/services/oembed?url="; + } - public override string[] UrlSchemeRegex => new string[] - { - @"giphy\.com/*", - @"gph\.is/*" - }; + public override string ApiEndpoint => "https://giphy.com/services/oembed?url="; - public override Dictionary RequestParams => new Dictionary(); + public override string[] UrlSchemeRegex => new[] {@"giphy\.com/*", @"gph\.is/*"}; - public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - var oembed = base.GetJsonResponse(requestUrl); + public override Dictionary RequestParams => new(); - return oembed?.GetHtml(); - } + public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse oembed = base.GetJsonResponse(requestUrl); - public Giphy(IJsonSerializer jsonSerializer) : base(jsonSerializer) - { - } + return oembed?.GetHtml(); } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Hulu.cs b/src/Umbraco.Core/Media/EmbedProviders/Hulu.cs index 1d6bed791bf6..b621d46baf15 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Hulu.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Hulu.cs @@ -1,30 +1,25 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Media.EmbedProviders +namespace Umbraco.Cms.Core.Media.EmbedProviders; + +// TODO(V10) : change base class to OEmbedProviderBase +public class Hulu : EmbedProviderBase { - // TODO(V10) : change base class to OEmbedProviderBase - public class Hulu : EmbedProviderBase + public Hulu(IJsonSerializer jsonSerializer) : base(jsonSerializer) { - public override string ApiEndpoint => "http://www.hulu.com/api/oembed.json"; + } - public override string[] UrlSchemeRegex => new string[] - { - @"hulu.com/watch/.*" - }; + public override string ApiEndpoint => "http://www.hulu.com/api/oembed.json"; - public override Dictionary RequestParams => new Dictionary(); + public override string[] UrlSchemeRegex => new[] {@"hulu.com/watch/.*"}; - public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - var oembed = base.GetJsonResponse(requestUrl); + public override Dictionary RequestParams => new(); - return oembed?.GetHtml(); - } + public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse oembed = base.GetJsonResponse(requestUrl); - public Hulu(IJsonSerializer jsonSerializer) : base(jsonSerializer) - { - } + return oembed?.GetHtml(); } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs b/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs index 89179d40af16..b3f3401b5362 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs @@ -1,34 +1,30 @@ -using System.Collections.Generic; +using System.Xml; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Media.EmbedProviders +namespace Umbraco.Cms.Core.Media.EmbedProviders; + +// TODO(V10) : change base class to OEmbedProviderBase +public class Issuu : EmbedProviderBase { - // TODO(V10) : change base class to OEmbedProviderBase - public class Issuu : EmbedProviderBase + public Issuu(IJsonSerializer jsonSerializer) : base(jsonSerializer) { - public override string ApiEndpoint => "https://issuu.com/oembed"; + } - public override string[] UrlSchemeRegex => new string[] - { - @"issuu.com/.*/docs/.*" - }; + public override string ApiEndpoint => "https://issuu.com/oembed"; - public override Dictionary RequestParams => new Dictionary() - { - //ApiUrl/?format=xml - {"format", "xml"} - }; + public override string[] UrlSchemeRegex => new[] {@"issuu.com/.*/docs/.*"}; - public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - var xmlDocument = base.GetXmlResponse(requestUrl); + public override Dictionary RequestParams => new() + { + //ApiUrl/?format=xml + {"format", "xml"} + }; - return GetXmlProperty(xmlDocument, "/oembed/html"); - } + public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); - public Issuu(IJsonSerializer jsonSerializer) : base(jsonSerializer) - { - } + return GetXmlProperty(xmlDocument, "/oembed/html"); } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Kickstarter.cs b/src/Umbraco.Core/Media/EmbedProviders/Kickstarter.cs index e9ada74cf690..64faccf97ce1 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Kickstarter.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Kickstarter.cs @@ -1,30 +1,25 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Media.EmbedProviders +namespace Umbraco.Cms.Core.Media.EmbedProviders; + +// TODO(V10) : change base class to OEmbedProviderBase +public class Kickstarter : EmbedProviderBase { - // TODO(V10) : change base class to OEmbedProviderBase - public class Kickstarter : EmbedProviderBase + public Kickstarter(IJsonSerializer jsonSerializer) : base(jsonSerializer) { - public override string ApiEndpoint => "http://www.kickstarter.com/services/oembed"; + } - public override string[] UrlSchemeRegex => new string[] - { - @"kickstarter\.com/projects/*" - }; + public override string ApiEndpoint => "http://www.kickstarter.com/services/oembed"; - public override Dictionary RequestParams => new Dictionary(); + public override string[] UrlSchemeRegex => new[] {@"kickstarter\.com/projects/*"}; - public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - var oembed = base.GetJsonResponse(requestUrl); + public override Dictionary RequestParams => new(); - return oembed?.GetHtml(); - } + public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse oembed = base.GetJsonResponse(requestUrl); - public Kickstarter(IJsonSerializer jsonSerializer) : base(jsonSerializer) - { - } + return oembed?.GetHtml(); } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/LottieFiles.cs b/src/Umbraco.Core/Media/EmbedProviders/LottieFiles.cs index f79e78b8b3f6..5113fcda90ed 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/LottieFiles.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/LottieFiles.cs @@ -1,57 +1,48 @@ -using System; -using System.Collections.Generic; -using System.Text; using System.Text.RegularExpressions; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Media.EmbedProviders +namespace Umbraco.Cms.Core.Media.EmbedProviders; + +/// +/// Embed Provider for lottiefiles.com the popular opensource JSON-based animation format platform. +/// +public class LottieFiles : OEmbedProviderBase { - /// - /// Embed Provider for lottiefiles.com the popular opensource JSON-based animation format platform. - /// - public class LottieFiles : OEmbedProviderBase + public LottieFiles(IJsonSerializer jsonSerializer) : base(jsonSerializer) { - public LottieFiles(IJsonSerializer jsonSerializer) : base(jsonSerializer) - { + } - } + public override string ApiEndpoint => "https://embed.lottiefiles.com/oembed"; + + public override string[] UrlSchemeRegex => new[] {@"lottiefiles\.com/*"}; - public override string ApiEndpoint => "https://embed.lottiefiles.com/oembed"; + public override Dictionary RequestParams => new(); - public override string[] UrlSchemeRegex => new string[] + public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse? oembed = base.GetJsonResponse(requestUrl); + var html = oembed?.GetHtml(); + //LottieFiles doesn't seem to support maxwidth and maxheight via oembed + // this is therefore a hack... with regexes.. is that ok? HtmlAgility etc etc + // otherwise it always defaults to 300... + if (html is null) { - @"lottiefiles\.com/*" - }; - public override Dictionary RequestParams => new Dictionary(); + return null; + } - public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + if (maxWidth > 0 && maxHeight > 0) + { + html = Regex.Replace(html, "width=\"([0-9]{1,4})\"", "width=\"" + maxWidth + "\""); + html = Regex.Replace(html, "height=\"([0-9]{1,4})\"", "height=\"" + maxHeight + "\""); + } + else { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse? oembed = base.GetJsonResponse(requestUrl); - var html = oembed?.GetHtml(); - //LottieFiles doesn't seem to support maxwidth and maxheight via oembed - // this is therefore a hack... with regexes.. is that ok? HtmlAgility etc etc - // otherwise it always defaults to 300... - if (html is null) - { - return null; - } - - if (maxWidth > 0 && maxHeight > 0) - { - - html = Regex.Replace(html, "width=\"([0-9]{1,4})\"", "width=\"" + maxWidth + "\""); - html = Regex.Replace(html, "height=\"([0-9]{1,4})\"", "height=\"" + maxHeight + "\""); - - } - else - { - //if set to 0, let's default to 100% as an easter egg - html = Regex.Replace(html, "width=\"([0-9]{1,4})\"", "width=\"100%\""); - html = Regex.Replace(html, "height=\"([0-9]{1,4})\"", "height=\"100%\""); - } - return html; + //if set to 0, let's default to 100% as an easter egg + html = Regex.Replace(html, "width=\"([0-9]{1,4})\"", "width=\"100%\""); + html = Regex.Replace(html, "height=\"([0-9]{1,4})\"", "height=\"100%\""); } + return html; } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs b/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs index 031105033b4f..99adc504b6ed 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs @@ -1,85 +1,87 @@ -using System; -using System.Collections.Generic; using System.Net; -using System.Net.Http; using System.Text; using System.Xml; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Media.EmbedProviders -{ - public abstract class OEmbedProviderBase : IEmbedProvider - { - private readonly IJsonSerializer _jsonSerializer; +namespace Umbraco.Cms.Core.Media.EmbedProviders; - protected OEmbedProviderBase(IJsonSerializer jsonSerializer) - { - _jsonSerializer = jsonSerializer; - } +public abstract class OEmbedProviderBase : IEmbedProvider +{ + private static HttpClient? _httpClient; + private readonly IJsonSerializer _jsonSerializer; - private static HttpClient? _httpClient; + protected OEmbedProviderBase(IJsonSerializer jsonSerializer) => _jsonSerializer = jsonSerializer; - public abstract string ApiEndpoint { get; } + public abstract string ApiEndpoint { get; } - public abstract string[] UrlSchemeRegex { get; } + public abstract string[] UrlSchemeRegex { get; } - public abstract Dictionary RequestParams { get; } + public abstract Dictionary RequestParams { get; } - public abstract string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0); + public abstract string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0); - public virtual string GetEmbedProviderUrl(string url, int maxWidth, int maxHeight) + public virtual string GetEmbedProviderUrl(string url, int maxWidth, int maxHeight) + { + if (Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute) == false) { - if (Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute) == false) - throw new ArgumentException("Not a valid URL.", nameof(url)); - - var fullUrl = new StringBuilder(); - - fullUrl.Append(ApiEndpoint); - fullUrl.Append("?url=" + WebUtility.UrlEncode(url)); - - foreach (var param in RequestParams) - fullUrl.Append($"&{param.Key}={param.Value}"); + throw new ArgumentException("Not a valid URL.", nameof(url)); + } - if (maxWidth > 0) - fullUrl.Append("&maxwidth=" + maxWidth); + var fullUrl = new StringBuilder(); - if (maxHeight > 0) - fullUrl.Append("&maxheight=" + maxHeight); + fullUrl.Append(ApiEndpoint); + fullUrl.Append("?url=" + WebUtility.UrlEncode(url)); - return fullUrl.ToString(); + foreach (KeyValuePair param in RequestParams) + { + fullUrl.Append($"&{param.Key}={param.Value}"); } - public virtual string DownloadResponse(string url) + if (maxWidth > 0) { - if (_httpClient == null) - _httpClient = new HttpClient(); - - using (var request = new HttpRequestMessage(HttpMethod.Get, url)) - { - var response = _httpClient.SendAsync(request).Result; - return response.Content.ReadAsStringAsync().Result; - } + fullUrl.Append("&maxwidth=" + maxWidth); } - public virtual T? GetJsonResponse(string url) where T : class + if (maxHeight > 0) { - var response = DownloadResponse(url); - return _jsonSerializer.Deserialize(response); + fullUrl.Append("&maxheight=" + maxHeight); } - public virtual XmlDocument GetXmlResponse(string url) - { - var response = DownloadResponse(url); - var doc = new XmlDocument(); - doc.LoadXml(response); + return fullUrl.ToString(); + } - return doc; + public virtual string DownloadResponse(string url) + { + if (_httpClient == null) + { + _httpClient = new HttpClient(); } - public virtual string GetXmlProperty(XmlDocument doc, string property) + using (var request = new HttpRequestMessage(HttpMethod.Get, url)) { - var selectSingleNode = doc.SelectSingleNode(property); - return selectSingleNode != null ? selectSingleNode.InnerText : string.Empty; + HttpResponseMessage response = _httpClient.SendAsync(request).Result; + return response.Content.ReadAsStringAsync().Result; } } -}; + + public virtual T? GetJsonResponse(string url) where T : class + { + var response = DownloadResponse(url); + return _jsonSerializer.Deserialize(response); + } + + public virtual XmlDocument GetXmlResponse(string url) + { + var response = DownloadResponse(url); + var doc = new XmlDocument(); + doc.LoadXml(response); + + return doc; + } + + public virtual string GetXmlProperty(XmlDocument doc, string property) + { + XmlNode selectSingleNode = doc.SelectSingleNode(property); + return selectSingleNode != null ? selectSingleNode.InnerText : string.Empty; + } +} diff --git a/src/Umbraco.Core/Media/EmbedProviders/OEmbedResponse.cs b/src/Umbraco.Core/Media/EmbedProviders/OEmbedResponse.cs index f003aa841cec..32ff72c09f95 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/OEmbedResponse.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/OEmbedResponse.cs @@ -1,68 +1,55 @@ using System.Net; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Media.EmbedProviders +namespace Umbraco.Cms.Core.Media.EmbedProviders; + +/// +/// Wrapper class for OEmbed response +/// +[DataContract] +public class OEmbedResponse { - /// - /// Wrapper class for OEmbed response - /// - [DataContract] - public class OEmbedResponse - { - [DataMember(Name ="type")] - public string? Type { get; set; } + [DataMember(Name = "type")] public string? Type { get; set; } - [DataMember(Name ="version")] - public string? Version { get; set; } + [DataMember(Name = "version")] public string? Version { get; set; } - [DataMember(Name ="title")] - public string? Title { get; set; } + [DataMember(Name = "title")] public string? Title { get; set; } - [DataMember(Name ="author_name")] - public string? AuthorName { get; set; } + [DataMember(Name = "author_name")] public string? AuthorName { get; set; } - [DataMember(Name ="author_url")] - public string? AuthorUrl { get; set; } + [DataMember(Name = "author_url")] public string? AuthorUrl { get; set; } - [DataMember(Name ="provider_name")] - public string? ProviderName { get; set; } + [DataMember(Name = "provider_name")] public string? ProviderName { get; set; } - [DataMember(Name ="provider_url")] - public string? ProviderUrl { get; set; } + [DataMember(Name = "provider_url")] public string? ProviderUrl { get; set; } - [DataMember(Name ="thumbnail_url")] - public string? ThumbnailUrl { get; set; } + [DataMember(Name = "thumbnail_url")] public string? ThumbnailUrl { get; set; } - [DataMember(Name ="thumbnail_height")] - public double? ThumbnailHeight { get; set; } + [DataMember(Name = "thumbnail_height")] + public double? ThumbnailHeight { get; set; } - [DataMember(Name ="thumbnail_width")] - public double? ThumbnailWidth { get; set; } + [DataMember(Name = "thumbnail_width")] public double? ThumbnailWidth { get; set; } - [DataMember(Name ="html")] - public string? Html { get; set; } + [DataMember(Name = "html")] public string? Html { get; set; } - [DataMember(Name ="url")] - public string? Url { get; set; } + [DataMember(Name = "url")] public string? Url { get; set; } - [DataMember(Name ="height")] - public double? Height { get; set; } + [DataMember(Name = "height")] public double? Height { get; set; } - [DataMember(Name ="width")] - public double? Width { get; set; } + [DataMember(Name = "width")] public double? Width { get; set; } - /// - /// Gets the HTML. - /// - /// The response HTML - public string GetHtml() + /// + /// Gets the HTML. + /// + /// The response HTML + public string GetHtml() + { + if (Type == "photo") { - if (Type == "photo") - { - return "\"""; - } - - return string.IsNullOrEmpty(Html) == false ? Html : string.Empty; + return "\"""; } + + return string.IsNullOrEmpty(Html) == false ? Html : string.Empty; } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Slideshare.cs b/src/Umbraco.Core/Media/EmbedProviders/Slideshare.cs index 42e500aa5c03..19c9f997c55a 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Slideshare.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Slideshare.cs @@ -1,30 +1,26 @@ -using System.Collections.Generic; +using System.Xml; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Media.EmbedProviders +namespace Umbraco.Cms.Core.Media.EmbedProviders; + +// TODO(V10) : change base class to OEmbedProviderBase +public class Slideshare : EmbedProviderBase { - // TODO(V10) : change base class to OEmbedProviderBase - public class Slideshare : EmbedProviderBase + public Slideshare(IJsonSerializer jsonSerializer) : base(jsonSerializer) { - public override string ApiEndpoint => "http://www.slideshare.net/api/oembed/2"; + } - public override string[] UrlSchemeRegex => new string[] - { - @"slideshare\.net/" - }; + public override string ApiEndpoint => "http://www.slideshare.net/api/oembed/2"; - public override Dictionary RequestParams => new Dictionary(); + public override string[] UrlSchemeRegex => new[] {@"slideshare\.net/"}; - public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - var xmlDocument = base.GetXmlResponse(requestUrl); + public override Dictionary RequestParams => new(); - return GetXmlProperty(xmlDocument, "/oembed/html"); - } + public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); - public Slideshare(IJsonSerializer jsonSerializer) : base(jsonSerializer) - { - } + return GetXmlProperty(xmlDocument, "/oembed/html"); } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/SoundCloud.cs b/src/Umbraco.Core/Media/EmbedProviders/SoundCloud.cs index 687da98697e4..f48da2cc9df7 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/SoundCloud.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/SoundCloud.cs @@ -1,30 +1,26 @@ -using System.Collections.Generic; +using System.Xml; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Media.EmbedProviders +namespace Umbraco.Cms.Core.Media.EmbedProviders; + +// TODO(V10) : change base class to OEmbedProviderBase +public class Soundcloud : EmbedProviderBase { - // TODO(V10) : change base class to OEmbedProviderBase - public class Soundcloud : EmbedProviderBase + public Soundcloud(IJsonSerializer jsonSerializer) : base(jsonSerializer) { - public override string ApiEndpoint => "https://soundcloud.com/oembed"; + } - public override string[] UrlSchemeRegex => new string[] - { - @"soundcloud.com\/*" - }; + public override string ApiEndpoint => "https://soundcloud.com/oembed"; - public override Dictionary RequestParams => new Dictionary(); + public override string[] UrlSchemeRegex => new[] {@"soundcloud.com\/*"}; - public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - var xmlDocument = base.GetXmlResponse(requestUrl); + public override Dictionary RequestParams => new(); - return GetXmlProperty(xmlDocument, "/oembed/html"); - } + public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); - public Soundcloud(IJsonSerializer jsonSerializer) : base(jsonSerializer) - { - } + return GetXmlProperty(xmlDocument, "/oembed/html"); } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Ted.cs b/src/Umbraco.Core/Media/EmbedProviders/Ted.cs index 511cbf012d21..e6637cf598e8 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Ted.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Ted.cs @@ -1,30 +1,26 @@ -using System.Collections.Generic; +using System.Xml; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Media.EmbedProviders +namespace Umbraco.Cms.Core.Media.EmbedProviders; + +// TODO(V10) : change base class to OEmbedProviderBase +public class Ted : EmbedProviderBase { - // TODO(V10) : change base class to OEmbedProviderBase - public class Ted : EmbedProviderBase + public Ted(IJsonSerializer jsonSerializer) : base(jsonSerializer) { - public override string ApiEndpoint => "http://www.ted.com/talks/oembed.xml"; + } - public override string[] UrlSchemeRegex => new string[] - { - @"ted.com\/talks\/*" - }; + public override string ApiEndpoint => "http://www.ted.com/talks/oembed.xml"; - public override Dictionary RequestParams => new Dictionary(); + public override string[] UrlSchemeRegex => new[] {@"ted.com\/talks\/*"}; - public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - var xmlDocument = base.GetXmlResponse(requestUrl); + public override Dictionary RequestParams => new(); - return GetXmlProperty(xmlDocument, "/oembed/html"); - } + public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); - public Ted(IJsonSerializer jsonSerializer) : base(jsonSerializer) - { - } + return GetXmlProperty(xmlDocument, "/oembed/html"); } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs b/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs index 934ec4b5c102..66a08fa4d23b 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs @@ -1,30 +1,25 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Media.EmbedProviders +namespace Umbraco.Cms.Core.Media.EmbedProviders; + +// TODO(V10) : change base class to OEmbedProviderBase +public class Twitter : EmbedProviderBase { - // TODO(V10) : change base class to OEmbedProviderBase - public class Twitter : EmbedProviderBase + public Twitter(IJsonSerializer jsonSerializer) : base(jsonSerializer) { - public override string ApiEndpoint => "http://publish.twitter.com/oembed"; + } - public override string[] UrlSchemeRegex => new string[] - { - @"twitter.com/.*/status/.*" - }; + public override string ApiEndpoint => "http://publish.twitter.com/oembed"; - public override Dictionary RequestParams => new Dictionary(); + public override string[] UrlSchemeRegex => new[] {@"twitter.com/.*/status/.*"}; - public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - var oembed = base.GetJsonResponse(requestUrl); + public override Dictionary RequestParams => new(); - return oembed?.GetHtml(); - } + public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse oembed = base.GetJsonResponse(requestUrl); - public Twitter(IJsonSerializer jsonSerializer) : base(jsonSerializer) - { - } + return oembed?.GetHtml(); } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Vimeo.cs b/src/Umbraco.Core/Media/EmbedProviders/Vimeo.cs index db324bda1207..a8655f096c6c 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Vimeo.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Vimeo.cs @@ -1,30 +1,26 @@ -using System.Collections.Generic; +using System.Xml; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Media.EmbedProviders +namespace Umbraco.Cms.Core.Media.EmbedProviders; + +// TODO(V10) : change base class to OEmbedProviderBase +public class Vimeo : EmbedProviderBase { - // TODO(V10) : change base class to OEmbedProviderBase - public class Vimeo : EmbedProviderBase + public Vimeo(IJsonSerializer jsonSerializer) : base(jsonSerializer) { - public override string ApiEndpoint => "https://vimeo.com/api/oembed.xml"; + } - public override string[] UrlSchemeRegex => new string[] - { - @"vimeo\.com/" - }; + public override string ApiEndpoint => "https://vimeo.com/api/oembed.xml"; - public override Dictionary RequestParams => new Dictionary(); + public override string[] UrlSchemeRegex => new[] {@"vimeo\.com/"}; - public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - var xmlDocument = base.GetXmlResponse(requestUrl); + public override Dictionary RequestParams => new(); - return GetXmlProperty(xmlDocument, "/oembed/html"); - } + public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); - public Vimeo(IJsonSerializer jsonSerializer) : base(jsonSerializer) - { - } + return GetXmlProperty(xmlDocument, "/oembed/html"); } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Youtube.cs b/src/Umbraco.Core/Media/EmbedProviders/Youtube.cs index 3888462dbc03..02c34436e954 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Youtube.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Youtube.cs @@ -1,35 +1,29 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Media.EmbedProviders +namespace Umbraco.Cms.Core.Media.EmbedProviders; + +// TODO(V10) : change base class to OEmbedProviderBase +public class YouTube : EmbedProviderBase { - // TODO(V10) : change base class to OEmbedProviderBase - public class YouTube : EmbedProviderBase + public YouTube(IJsonSerializer jsonSerializer) : base(jsonSerializer) { - public override string ApiEndpoint => "https://www.youtube.com/oembed"; + } - public override string[] UrlSchemeRegex => new string[] - { - @"youtu.be/.*", - @"youtube.com/watch.*" - }; + public override string ApiEndpoint => "https://www.youtube.com/oembed"; - public override Dictionary RequestParams => new Dictionary() - { - //ApiUrl/?format=json - {"format", "json"} - }; + public override string[] UrlSchemeRegex => new[] {@"youtu.be/.*", @"youtube.com/watch.*"}; - public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) - { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - var oembed = base.GetJsonResponse(requestUrl); + public override Dictionary RequestParams => new() + { + //ApiUrl/?format=json + {"format", "json"} + }; - return oembed?.GetHtml(); - } + public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse oembed = base.GetJsonResponse(requestUrl); - public YouTube(IJsonSerializer jsonSerializer) : base(jsonSerializer) - { - } + return oembed?.GetHtml(); } } diff --git a/src/Umbraco.Core/Media/Exif/BitConverterEx.cs b/src/Umbraco.Core/Media/Exif/BitConverterEx.cs index 6afc6e4308a3..e8252d662df2 100644 --- a/src/Umbraco.Core/Media/Exif/BitConverterEx.cs +++ b/src/Umbraco.Core/Media/Exif/BitConverterEx.cs @@ -1,405 +1,346 @@ -using System; +namespace Umbraco.Cms.Core.Media.Exif; -namespace Umbraco.Cms.Core.Media.Exif +/// +/// An endian-aware converter for converting between base data types +/// and an array of bytes. +/// +internal class BitConverterEx { + #region Public Enums + /// - /// An endian-aware converter for converting between base data types - /// and an array of bytes. + /// Represents the byte order. /// - internal class BitConverterEx + public enum ByteOrder { - #region Public Enums - /// - /// Represents the byte order. - /// - public enum ByteOrder - { - LittleEndian = 1, - BigEndian = 2, - } - #endregion + LittleEndian = 1, + BigEndian = 2 + } - #region Member Variables - private ByteOrder mFrom, mTo; - #endregion + #endregion - #region Constructors - public BitConverterEx(ByteOrder from, ByteOrder to) - { - mFrom = from; - mTo = to; - } - #endregion + #region Member Variables - #region Properties - /// - /// Indicates the byte order in which data is stored in this platform. - /// - public static ByteOrder SystemByteOrder - { - get - { - return (BitConverter.IsLittleEndian ? ByteOrder.LittleEndian : ByteOrder.BigEndian); - } - } - #endregion + private readonly ByteOrder mFrom; + private readonly ByteOrder mTo; - #region Predefined Values - /// - /// Returns a bit converter that converts between little-endian and system byte-order. - /// - public static BitConverterEx LittleEndian - { - get - { - return new BitConverterEx(ByteOrder.LittleEndian, BitConverterEx.SystemByteOrder); - } - } + #endregion - /// - /// Returns a bit converter that converts between big-endian and system byte-order. - /// - public static BitConverterEx BigEndian - { - get - { - return new BitConverterEx(ByteOrder.BigEndian, BitConverterEx.SystemByteOrder); - } - } + #region Constructors - /// - /// Returns a bit converter that does not do any byte-order conversion. - /// - public static BitConverterEx SystemEndian - { - get - { - return new BitConverterEx(BitConverterEx.SystemByteOrder, BitConverterEx.SystemByteOrder); - } - } - #endregion + public BitConverterEx(ByteOrder from, ByteOrder to) + { + mFrom = from; + mTo = to; + } - #region Static Methods - /// - /// Converts the given array of bytes to a Unicode character. - /// - public static char ToChar(byte[] value, long startIndex, ByteOrder from, ByteOrder to) - { - byte[] data = CheckData(value, startIndex, 2, from, to); - return BitConverter.ToChar(data, 0); - } + #endregion - /// - /// Converts the given array of bytes to a 16-bit unsigned integer. - /// - public static ushort ToUInt16(byte[] value, long startIndex, ByteOrder from, ByteOrder to) - { - byte[] data = CheckData(value, startIndex, 2, from, to); - return BitConverter.ToUInt16(data, 0); - } + #region Properties - /// - /// Converts the given array of bytes to a 32-bit unsigned integer. - /// - public static uint ToUInt32(byte[] value, long startIndex, ByteOrder from, ByteOrder to) - { - byte[] data = CheckData(value, startIndex, 4, from, to); - return BitConverter.ToUInt32(data, 0); - } + /// + /// Indicates the byte order in which data is stored in this platform. + /// + public static ByteOrder SystemByteOrder => + BitConverter.IsLittleEndian ? ByteOrder.LittleEndian : ByteOrder.BigEndian; - /// - /// Converts the given array of bytes to a 64-bit unsigned integer. - /// - public static ulong ToUInt64(byte[] value, long startIndex, ByteOrder from, ByteOrder to) - { - byte[] data = CheckData(value, startIndex, 8, from, to); - return BitConverter.ToUInt64(data, 0); - } + #endregion - /// - /// Converts the given array of bytes to a 16-bit signed integer. - /// - public static short ToInt16(byte[] value, long startIndex, ByteOrder from, ByteOrder to) - { - byte[] data = CheckData(value, startIndex, 2, from, to); - return BitConverter.ToInt16(data, 0); - } + #region Predefined Values - /// - /// Converts the given array of bytes to a 32-bit signed integer. - /// - public static int ToInt32(byte[] value, long startIndex, ByteOrder from, ByteOrder to) - { - byte[] data = CheckData(value, startIndex, 4, from, to); - return BitConverter.ToInt32(data, 0); - } + /// + /// Returns a bit converter that converts between little-endian and system byte-order. + /// + public static BitConverterEx LittleEndian => new BitConverterEx(ByteOrder.LittleEndian, SystemByteOrder); - /// - /// Converts the given array of bytes to a 64-bit signed integer. - /// - public static long ToInt64(byte[] value, long startIndex, ByteOrder from, ByteOrder to) - { - byte[] data = CheckData(value, startIndex, 8, from, to); - return BitConverter.ToInt64(data, 0); - } + /// + /// Returns a bit converter that converts between big-endian and system byte-order. + /// + public static BitConverterEx BigEndian => new BitConverterEx(ByteOrder.BigEndian, SystemByteOrder); - /// - /// Converts the given array of bytes to a single precision floating number. - /// - public static float ToSingle(byte[] value, long startIndex, ByteOrder from, ByteOrder to) - { - byte[] data = CheckData(value, startIndex, 4, from, to); - return BitConverter.ToSingle(data, 0); - } + /// + /// Returns a bit converter that does not do any byte-order conversion. + /// + public static BitConverterEx SystemEndian => new BitConverterEx(SystemByteOrder, SystemByteOrder); - /// - /// Converts the given array of bytes to a double precision floating number. - /// - public static double ToDouble(byte[] value, long startIndex, ByteOrder from, ByteOrder to) - { - byte[] data = CheckData(value, startIndex, 8, from, to); - return BitConverter.ToDouble(data, 0); - } + #endregion - /// - /// Converts the given 16-bit unsigned integer to an array of bytes. - /// - public static byte[] GetBytes(ushort value, ByteOrder from, ByteOrder to) - { - byte[] data = BitConverter.GetBytes(value); - data = CheckData(data, from, to); - return data; - } + #region Static Methods - /// - /// Converts the given 32-bit unsigned integer to an array of bytes. - /// - public static byte[] GetBytes(uint value, ByteOrder from, ByteOrder to) - { - byte[] data = BitConverter.GetBytes(value); - data = CheckData(data, from, to); - return data; - } + /// + /// Converts the given array of bytes to a Unicode character. + /// + public static char ToChar(byte[] value, long startIndex, ByteOrder from, ByteOrder to) + { + var data = CheckData(value, startIndex, 2, from, to); + return BitConverter.ToChar(data, 0); + } - /// - /// Converts the given 64-bit unsigned integer to an array of bytes. - /// - public static byte[] GetBytes(ulong value, ByteOrder from, ByteOrder to) - { - byte[] data = BitConverter.GetBytes(value); - data = CheckData(data, from, to); - return data; - } + /// + /// Converts the given array of bytes to a 16-bit unsigned integer. + /// + public static ushort ToUInt16(byte[] value, long startIndex, ByteOrder from, ByteOrder to) + { + var data = CheckData(value, startIndex, 2, from, to); + return BitConverter.ToUInt16(data, 0); + } - /// - /// Converts the given 16-bit signed integer to an array of bytes. - /// - public static byte[] GetBytes(short value, ByteOrder from, ByteOrder to) - { - byte[] data = BitConverter.GetBytes(value); - data = CheckData(data, from, to); - return data; - } + /// + /// Converts the given array of bytes to a 32-bit unsigned integer. + /// + public static uint ToUInt32(byte[] value, long startIndex, ByteOrder from, ByteOrder to) + { + var data = CheckData(value, startIndex, 4, from, to); + return BitConverter.ToUInt32(data, 0); + } - /// - /// Converts the given 32-bit signed integer to an array of bytes. - /// - public static byte[] GetBytes(int value, ByteOrder from, ByteOrder to) - { - byte[] data = BitConverter.GetBytes(value); - data = CheckData(data, from, to); - return data; - } + /// + /// Converts the given array of bytes to a 64-bit unsigned integer. + /// + public static ulong ToUInt64(byte[] value, long startIndex, ByteOrder from, ByteOrder to) + { + var data = CheckData(value, startIndex, 8, from, to); + return BitConverter.ToUInt64(data, 0); + } - /// - /// Converts the given 64-bit signed integer to an array of bytes. - /// - public static byte[] GetBytes(long value, ByteOrder from, ByteOrder to) - { - byte[] data = BitConverter.GetBytes(value); - data = CheckData(data, from, to); - return data; - } + /// + /// Converts the given array of bytes to a 16-bit signed integer. + /// + public static short ToInt16(byte[] value, long startIndex, ByteOrder from, ByteOrder to) + { + var data = CheckData(value, startIndex, 2, from, to); + return BitConverter.ToInt16(data, 0); + } - /// - /// Converts the given single precision floating-point number to an array of bytes. - /// - public static byte[] GetBytes(float value, ByteOrder from, ByteOrder to) - { - byte[] data = BitConverter.GetBytes(value); - data = CheckData(data, from, to); - return data; - } + /// + /// Converts the given array of bytes to a 32-bit signed integer. + /// + public static int ToInt32(byte[] value, long startIndex, ByteOrder from, ByteOrder to) + { + var data = CheckData(value, startIndex, 4, from, to); + return BitConverter.ToInt32(data, 0); + } - /// - /// Converts the given double precision floating-point number to an array of bytes. - /// - public static byte[] GetBytes(double value, ByteOrder from, ByteOrder to) - { - byte[] data = BitConverter.GetBytes(value); - data = CheckData(data, from, to); - return data; - } - #endregion + /// + /// Converts the given array of bytes to a 64-bit signed integer. + /// + public static long ToInt64(byte[] value, long startIndex, ByteOrder from, ByteOrder to) + { + var data = CheckData(value, startIndex, 8, from, to); + return BitConverter.ToInt64(data, 0); + } - #region Instance Methods - /// - /// Converts the given array of bytes to a 16-bit unsigned integer. - /// - public char ToChar(byte[] value, long startIndex) - { - return BitConverterEx.ToChar(value, startIndex, mFrom, mTo); - } + /// + /// Converts the given array of bytes to a single precision floating number. + /// + public static float ToSingle(byte[] value, long startIndex, ByteOrder from, ByteOrder to) + { + var data = CheckData(value, startIndex, 4, from, to); + return BitConverter.ToSingle(data, 0); + } - /// - /// Converts the given array of bytes to a 16-bit unsigned integer. - /// - public ushort ToUInt16(byte[] value, long startIndex) - { - return BitConverterEx.ToUInt16(value, startIndex, mFrom, mTo); - } + /// + /// Converts the given array of bytes to a double precision floating number. + /// + public static double ToDouble(byte[] value, long startIndex, ByteOrder from, ByteOrder to) + { + var data = CheckData(value, startIndex, 8, from, to); + return BitConverter.ToDouble(data, 0); + } - /// - /// Converts the given array of bytes to a 32-bit unsigned integer. - /// - public uint ToUInt32(byte[] value, long startIndex) - { - return BitConverterEx.ToUInt32(value, startIndex, mFrom, mTo); - } + /// + /// Converts the given 16-bit unsigned integer to an array of bytes. + /// + public static byte[] GetBytes(ushort value, ByteOrder from, ByteOrder to) + { + var data = BitConverter.GetBytes(value); + data = CheckData(data, from, to); + return data; + } - /// - /// Converts the given array of bytes to a 64-bit unsigned integer. - /// - public ulong ToUInt64(byte[] value, long startIndex) - { - return BitConverterEx.ToUInt64(value, startIndex, mFrom, mTo); - } + /// + /// Converts the given 32-bit unsigned integer to an array of bytes. + /// + public static byte[] GetBytes(uint value, ByteOrder from, ByteOrder to) + { + var data = BitConverter.GetBytes(value); + data = CheckData(data, from, to); + return data; + } - /// - /// Converts the given array of bytes to a 16-bit signed integer. - /// - public short ToInt16(byte[] value, long startIndex) - { - return BitConverterEx.ToInt16(value, startIndex, mFrom, mTo); - } + /// + /// Converts the given 64-bit unsigned integer to an array of bytes. + /// + public static byte[] GetBytes(ulong value, ByteOrder from, ByteOrder to) + { + var data = BitConverter.GetBytes(value); + data = CheckData(data, from, to); + return data; + } - /// - /// Converts the given array of bytes to a 32-bit signed integer. - /// - public int ToInt32(byte[] value, long startIndex) - { - return BitConverterEx.ToInt32(value, startIndex, mFrom, mTo); - } + /// + /// Converts the given 16-bit signed integer to an array of bytes. + /// + public static byte[] GetBytes(short value, ByteOrder from, ByteOrder to) + { + var data = BitConverter.GetBytes(value); + data = CheckData(data, from, to); + return data; + } - /// - /// Converts the given array of bytes to a 64-bit signed integer. - /// - public long ToInt64(byte[] value, long startIndex) - { - return BitConverterEx.ToInt64(value, startIndex, mFrom, mTo); - } + /// + /// Converts the given 32-bit signed integer to an array of bytes. + /// + public static byte[] GetBytes(int value, ByteOrder from, ByteOrder to) + { + var data = BitConverter.GetBytes(value); + data = CheckData(data, from, to); + return data; + } - /// - /// Converts the given array of bytes to a single precision floating number. - /// - public float ToSingle(byte[] value, long startIndex) - { - return BitConverterEx.ToSingle(value, startIndex, mFrom, mTo); - } + /// + /// Converts the given 64-bit signed integer to an array of bytes. + /// + public static byte[] GetBytes(long value, ByteOrder from, ByteOrder to) + { + var data = BitConverter.GetBytes(value); + data = CheckData(data, from, to); + return data; + } - /// - /// Converts the given array of bytes to a double precision floating number. - /// - public double ToDouble(byte[] value, long startIndex) - { - return BitConverterEx.ToDouble(value, startIndex, mFrom, mTo); - } + /// + /// Converts the given single precision floating-point number to an array of bytes. + /// + public static byte[] GetBytes(float value, ByteOrder from, ByteOrder to) + { + var data = BitConverter.GetBytes(value); + data = CheckData(data, from, to); + return data; + } - /// - /// Converts the given 16-bit unsigned integer to an array of bytes. - /// - public byte[] GetBytes(ushort value) - { - return BitConverterEx.GetBytes(value, mFrom, mTo); - } + /// + /// Converts the given double precision floating-point number to an array of bytes. + /// + public static byte[] GetBytes(double value, ByteOrder from, ByteOrder to) + { + var data = BitConverter.GetBytes(value); + data = CheckData(data, from, to); + return data; + } - /// - /// Converts the given 32-bit unsigned integer to an array of bytes. - /// - public byte[] GetBytes(uint value) - { - return BitConverterEx.GetBytes(value, mFrom, mTo); - } + #endregion - /// - /// Converts the given 64-bit unsigned integer to an array of bytes. - /// - public byte[] GetBytes(ulong value) - { - return BitConverterEx.GetBytes(value, mFrom, mTo); - } + #region Instance Methods - /// - /// Converts the given 16-bit signed integer to an array of bytes. - /// - public byte[] GetBytes(short value) - { - return BitConverterEx.GetBytes(value, mFrom, mTo); - } + /// + /// Converts the given array of bytes to a 16-bit unsigned integer. + /// + public char ToChar(byte[] value, long startIndex) => ToChar(value, startIndex, mFrom, mTo); - /// - /// Converts the given 32-bit signed integer to an array of bytes. - /// - public byte[] GetBytes(int value) - { - return BitConverterEx.GetBytes(value, mFrom, mTo); - } + /// + /// Converts the given array of bytes to a 16-bit unsigned integer. + /// + public ushort ToUInt16(byte[] value, long startIndex) => ToUInt16(value, startIndex, mFrom, mTo); - /// - /// Converts the given 64-bit signed integer to an array of bytes. - /// - public byte[] GetBytes(long value) - { - return BitConverterEx.GetBytes(value, mFrom, mTo); - } + /// + /// Converts the given array of bytes to a 32-bit unsigned integer. + /// + public uint ToUInt32(byte[] value, long startIndex) => ToUInt32(value, startIndex, mFrom, mTo); - /// - /// Converts the given single precision floating-point number to an array of bytes. - /// - public byte[] GetBytes(float value) - { - return BitConverterEx.GetBytes(value, mFrom, mTo); - } + /// + /// Converts the given array of bytes to a 64-bit unsigned integer. + /// + public ulong ToUInt64(byte[] value, long startIndex) => ToUInt64(value, startIndex, mFrom, mTo); - /// - /// Converts the given double precision floating-point number to an array of bytes. - /// - public byte[] GetBytes(double value) - { - return BitConverterEx.GetBytes(value, mFrom, mTo); - } - #endregion + /// + /// Converts the given array of bytes to a 16-bit signed integer. + /// + public short ToInt16(byte[] value, long startIndex) => ToInt16(value, startIndex, mFrom, mTo); - #region Private Helpers - /// - /// Reverse the array of bytes as needed. - /// - private static byte[] CheckData(byte[] value, long startIndex, long length, ByteOrder from, ByteOrder to) - { - byte[] data = new byte[length]; - Array.Copy(value, startIndex, data, 0, length); - if (from != to) - Array.Reverse(data); - return data; - } + /// + /// Converts the given array of bytes to a 32-bit signed integer. + /// + public int ToInt32(byte[] value, long startIndex) => ToInt32(value, startIndex, mFrom, mTo); + + /// + /// Converts the given array of bytes to a 64-bit signed integer. + /// + public long ToInt64(byte[] value, long startIndex) => ToInt64(value, startIndex, mFrom, mTo); + + /// + /// Converts the given array of bytes to a single precision floating number. + /// + public float ToSingle(byte[] value, long startIndex) => ToSingle(value, startIndex, mFrom, mTo); + + /// + /// Converts the given array of bytes to a double precision floating number. + /// + public double ToDouble(byte[] value, long startIndex) => ToDouble(value, startIndex, mFrom, mTo); + + /// + /// Converts the given 16-bit unsigned integer to an array of bytes. + /// + public byte[] GetBytes(ushort value) => GetBytes(value, mFrom, mTo); + + /// + /// Converts the given 32-bit unsigned integer to an array of bytes. + /// + public byte[] GetBytes(uint value) => GetBytes(value, mFrom, mTo); + + /// + /// Converts the given 64-bit unsigned integer to an array of bytes. + /// + public byte[] GetBytes(ulong value) => GetBytes(value, mFrom, mTo); + + /// + /// Converts the given 16-bit signed integer to an array of bytes. + /// + public byte[] GetBytes(short value) => GetBytes(value, mFrom, mTo); + + /// + /// Converts the given 32-bit signed integer to an array of bytes. + /// + public byte[] GetBytes(int value) => GetBytes(value, mFrom, mTo); + + /// + /// Converts the given 64-bit signed integer to an array of bytes. + /// + public byte[] GetBytes(long value) => GetBytes(value, mFrom, mTo); + + /// + /// Converts the given single precision floating-point number to an array of bytes. + /// + public byte[] GetBytes(float value) => GetBytes(value, mFrom, mTo); - /// - /// Reverse the array of bytes as needed. - /// - private static byte[] CheckData(byte[] value, ByteOrder from, ByteOrder to) + /// + /// Converts the given double precision floating-point number to an array of bytes. + /// + public byte[] GetBytes(double value) => GetBytes(value, mFrom, mTo); + + #endregion + + #region Private Helpers + + /// + /// Reverse the array of bytes as needed. + /// + private static byte[] CheckData(byte[] value, long startIndex, long length, ByteOrder from, ByteOrder to) + { + var data = new byte[length]; + Array.Copy(value, startIndex, data, 0, length); + if (from != to) { - return CheckData(value, 0, value.Length, from, to); + Array.Reverse(data); } - #endregion + + return data; } + + /// + /// Reverse the array of bytes as needed. + /// + private static byte[] CheckData(byte[] value, ByteOrder from, ByteOrder to) => + CheckData(value, 0, value.Length, from, to); + + #endregion } diff --git a/src/Umbraco.Core/Media/Exif/ExifBitConverter.cs b/src/Umbraco.Core/Media/Exif/ExifBitConverter.cs index 74465a66840d..ed46cbb4888d 100644 --- a/src/Umbraco.Core/Media/Exif/ExifBitConverter.cs +++ b/src/Umbraco.Core/Media/Exif/ExifBitConverter.cs @@ -1,358 +1,386 @@ -using System; -using System.Globalization; +using System.Globalization; using System.Text; -namespace Umbraco.Cms.Core.Media.Exif +namespace Umbraco.Cms.Core.Media.Exif; + +/// +/// Converts between exif data types and array of bytes. +/// +internal class ExifBitConverter : BitConverterEx { - /// - /// Converts between exif data types and array of bytes. - /// - internal class ExifBitConverter : BitConverterEx + #region Constructors + + public ExifBitConverter(ByteOrder from, ByteOrder to) + : base(from, to) { - #region Constructors - public ExifBitConverter(ByteOrder from, ByteOrder to) - : base(from, to) - { + } - } - #endregion + #endregion - #region Static Methods - /// - /// Returns an ASCII string converted from the given byte array. - /// - public static string ToAscii(byte[] data, bool endatfirstnull, Encoding encoding) + #region Static Methods + + /// + /// Returns an ASCII string converted from the given byte array. + /// + public static string ToAscii(byte[] data, bool endatfirstnull, Encoding encoding) + { + var len = data.Length; + if (endatfirstnull) { - int len = data.Length; - if (endatfirstnull) + len = Array.IndexOf(data, (byte)0); + if (len == -1) { - len = Array.IndexOf(data, (byte)0); - if (len == -1) len = data.Length; + len = data.Length; } - return encoding.GetString(data, 0, len); } - /// - /// Returns an ASCII string converted from the given byte array. - /// - public static string ToAscii(byte[] data, Encoding encoding) - { - return ToAscii(data, true, encoding); - } + return encoding.GetString(data, 0, len); + } - /// - /// Returns a string converted from the given byte array. - /// from the numeric value of each byte. - /// - public static string ToString(byte[] data) + /// + /// Returns an ASCII string converted from the given byte array. + /// + public static string ToAscii(byte[] data, Encoding encoding) => ToAscii(data, true, encoding); + + /// + /// Returns a string converted from the given byte array. + /// from the numeric value of each byte. + /// + public static string ToString(byte[] data) + { + var sb = new StringBuilder(); + foreach (var b in data) { - StringBuilder sb = new StringBuilder(); - foreach (byte b in data) - sb.Append(b); - return sb.ToString(); + sb.Append(b); } - /// - /// Returns a DateTime object converted from the given byte array. - /// - public static DateTime ToDateTime(byte[] data, bool hastime) + return sb.ToString(); + } + + /// + /// Returns a DateTime object converted from the given byte array. + /// + public static DateTime ToDateTime(byte[] data, bool hastime) + { + var str = ToAscii(data, Encoding.ASCII); + var parts = str.Split(':', ' '); + try { - string str = ToAscii(data, Encoding.ASCII); - string[] parts = str.Split(new char[] { ':', ' ' }); - try - { - if (hastime && parts.Length == 6) - { - // yyyy:MM:dd HH:mm:ss - // This is the expected format though some cameras - // can use single digits. See Issue 21. - return new DateTime(int.Parse(parts[0], CultureInfo.InvariantCulture), int.Parse(parts[1], CultureInfo.InvariantCulture), int.Parse(parts[2], CultureInfo.InvariantCulture), int.Parse(parts[3], CultureInfo.InvariantCulture), int.Parse(parts[4], CultureInfo.InvariantCulture), int.Parse(parts[5], CultureInfo.InvariantCulture)); - } - else if (!hastime && parts.Length == 3) - { - // yyyy:MM:dd - return new DateTime(int.Parse(parts[0], CultureInfo.InvariantCulture), int.Parse(parts[1], CultureInfo.InvariantCulture), int.Parse(parts[2], CultureInfo.InvariantCulture)); - } - else - { - return DateTime.MinValue; - } - } - catch (ArgumentOutOfRangeException) + if (hastime && parts.Length == 6) { - return DateTime.MinValue; + // yyyy:MM:dd HH:mm:ss + // This is the expected format though some cameras + // can use single digits. See Issue 21. + return new DateTime(int.Parse(parts[0], CultureInfo.InvariantCulture), + int.Parse(parts[1], CultureInfo.InvariantCulture), + int.Parse(parts[2], CultureInfo.InvariantCulture), + int.Parse(parts[3], CultureInfo.InvariantCulture), + int.Parse(parts[4], CultureInfo.InvariantCulture), + int.Parse(parts[5], CultureInfo.InvariantCulture)); } - catch (ArgumentException) + + if (!hastime && parts.Length == 3) { - return DateTime.MinValue; + // yyyy:MM:dd + return new DateTime(int.Parse(parts[0], CultureInfo.InvariantCulture), + int.Parse(parts[1], CultureInfo.InvariantCulture), + int.Parse(parts[2], CultureInfo.InvariantCulture)); } - } - /// - /// Returns a DateTime object converted from the given byte array. - /// - public static DateTime ToDateTime(byte[] data) - { - return ToDateTime(data, true); + return DateTime.MinValue; } - - /// - /// Returns an unsigned rational number converted from the first - /// eight bytes of the given byte array. The first four bytes are - /// assumed to be the numerator and the next four bytes are the - /// denominator. - /// Numbers are converted from the given byte-order to platform byte-order. - /// - public static MathEx.UFraction32 ToURational(byte[] data, ByteOrder frombyteorder) + catch (ArgumentOutOfRangeException) { - byte[] num = new byte[4]; - byte[] den = new byte[4]; - Array.Copy(data, 0, num, 0, 4); - Array.Copy(data, 4, den, 0, 4); - return new MathEx.UFraction32(ToUInt32(num, 0, frombyteorder, BitConverterEx.SystemByteOrder), ToUInt32(den, 0, frombyteorder, BitConverterEx.SystemByteOrder)); + return DateTime.MinValue; } - - /// - /// Returns a signed rational number converted from the first - /// eight bytes of the given byte array. The first four bytes are - /// assumed to be the numerator and the next four bytes are the - /// denominator. - /// Numbers are converted from the given byte-order to platform byte-order. - /// - public static MathEx.Fraction32 ToSRational(byte[] data, ByteOrder frombyteorder) + catch (ArgumentException) { - byte[] num = new byte[4]; - byte[] den = new byte[4]; - Array.Copy(data, 0, num, 0, 4); - Array.Copy(data, 4, den, 0, 4); - return new MathEx.Fraction32(ToInt32(num, 0, frombyteorder, BitConverterEx.SystemByteOrder), ToInt32(den, 0, frombyteorder, BitConverterEx.SystemByteOrder)); + return DateTime.MinValue; } + } - /// - /// Returns an array of 16-bit unsigned integers converted from - /// the given byte array. - /// Numbers are converted from the given byte-order to platform byte-order. - /// - public static ushort[] ToUShortArray(byte[] data, int count, ByteOrder frombyteorder) - { - ushort[] numbers = new ushort[count]; - for (uint i = 0; i < count; i++) - { - byte[] num = new byte[2]; - Array.Copy(data, i * 2, num, 0, 2); - numbers[i] = ToUInt16(num, 0, frombyteorder, BitConverterEx.SystemByteOrder); - } - return numbers; - } + /// + /// Returns a DateTime object converted from the given byte array. + /// + public static DateTime ToDateTime(byte[] data) => ToDateTime(data, true); - /// - /// Returns an array of 32-bit unsigned integers converted from - /// the given byte array. - /// Numbers are converted from the given byte-order to platform byte-order. - /// - public static uint[] ToUIntArray(byte[] data, int count, ByteOrder frombyteorder) - { - uint[] numbers = new uint[count]; - for (uint i = 0; i < count; i++) - { - byte[] num = new byte[4]; - Array.Copy(data, i * 4, num, 0, 4); - numbers[i] = ToUInt32(num, 0, frombyteorder, BitConverterEx.SystemByteOrder); - } - return numbers; - } + /// + /// Returns an unsigned rational number converted from the first + /// eight bytes of the given byte array. The first four bytes are + /// assumed to be the numerator and the next four bytes are the + /// denominator. + /// Numbers are converted from the given byte-order to platform byte-order. + /// + public static MathEx.UFraction32 ToURational(byte[] data, ByteOrder frombyteorder) + { + var num = new byte[4]; + var den = new byte[4]; + Array.Copy(data, 0, num, 0, 4); + Array.Copy(data, 4, den, 0, 4); + return new MathEx.UFraction32(ToUInt32(num, 0, frombyteorder, SystemByteOrder), + ToUInt32(den, 0, frombyteorder, SystemByteOrder)); + } - /// - /// Returns an array of 32-bit signed integers converted from - /// the given byte array. - /// Numbers are converted from the given byte-order to platform byte-order. - /// - public static int[] ToSIntArray(byte[] data, int count, ByteOrder byteorder) + /// + /// Returns a signed rational number converted from the first + /// eight bytes of the given byte array. The first four bytes are + /// assumed to be the numerator and the next four bytes are the + /// denominator. + /// Numbers are converted from the given byte-order to platform byte-order. + /// + public static MathEx.Fraction32 ToSRational(byte[] data, ByteOrder frombyteorder) + { + var num = new byte[4]; + var den = new byte[4]; + Array.Copy(data, 0, num, 0, 4); + Array.Copy(data, 4, den, 0, 4); + return new MathEx.Fraction32(ToInt32(num, 0, frombyteorder, SystemByteOrder), + ToInt32(den, 0, frombyteorder, SystemByteOrder)); + } + + /// + /// Returns an array of 16-bit unsigned integers converted from + /// the given byte array. + /// Numbers are converted from the given byte-order to platform byte-order. + /// + public static ushort[] ToUShortArray(byte[] data, int count, ByteOrder frombyteorder) + { + var numbers = new ushort[count]; + for (uint i = 0; i < count; i++) { - int[] numbers = new int[count]; - for (uint i = 0; i < count; i++) - { - byte[] num = new byte[4]; - Array.Copy(data, i * 4, num, 0, 4); - numbers[i] = ToInt32(num, 0, byteorder, BitConverterEx.SystemByteOrder); - } - return numbers; + var num = new byte[2]; + Array.Copy(data, i * 2, num, 0, 2); + numbers[i] = ToUInt16(num, 0, frombyteorder, SystemByteOrder); } - /// - /// Returns an array of unsigned rational numbers converted from - /// the given byte array. - /// Numbers are converted from the given byte-order to platform byte-order. - /// - public static MathEx.UFraction32[] ToURationalArray(byte[] data, int count, ByteOrder frombyteorder) + return numbers; + } + + /// + /// Returns an array of 32-bit unsigned integers converted from + /// the given byte array. + /// Numbers are converted from the given byte-order to platform byte-order. + /// + public static uint[] ToUIntArray(byte[] data, int count, ByteOrder frombyteorder) + { + var numbers = new uint[count]; + for (uint i = 0; i < count; i++) { - MathEx.UFraction32[] numbers = new MathEx.UFraction32[count]; - for (uint i = 0; i < count; i++) - { - byte[] num = new byte[4]; - byte[] den = new byte[4]; - Array.Copy(data, i * 8, num, 0, 4); - Array.Copy(data, i * 8 + 4, den, 0, 4); - numbers[i].Set(ToUInt32(num, 0, frombyteorder, BitConverterEx.SystemByteOrder), ToUInt32(den, 0, frombyteorder, BitConverterEx.SystemByteOrder)); - } - return numbers; + var num = new byte[4]; + Array.Copy(data, i * 4, num, 0, 4); + numbers[i] = ToUInt32(num, 0, frombyteorder, SystemByteOrder); } - /// - /// Returns an array of signed rational numbers converted from - /// the given byte array. - /// Numbers are converted from the given byte-order to platform byte-order. - /// - public static MathEx.Fraction32[] ToSRationalArray(byte[] data, int count, ByteOrder frombyteorder) + return numbers; + } + + /// + /// Returns an array of 32-bit signed integers converted from + /// the given byte array. + /// Numbers are converted from the given byte-order to platform byte-order. + /// + public static int[] ToSIntArray(byte[] data, int count, ByteOrder byteorder) + { + var numbers = new int[count]; + for (uint i = 0; i < count; i++) { - MathEx.Fraction32[] numbers = new MathEx.Fraction32[count]; - for (uint i = 0; i < count; i++) - { - byte[] num = new byte[4]; - byte[] den = new byte[4]; - Array.Copy(data, i * 8, num, 0, 4); - Array.Copy(data, i * 8 + 4, den, 0, 4); - numbers[i].Set(ToInt32(num, 0, frombyteorder, BitConverterEx.SystemByteOrder), ToInt32(den, 0, frombyteorder, BitConverterEx.SystemByteOrder)); - } - return numbers; + var num = new byte[4]; + Array.Copy(data, i * 4, num, 0, 4); + numbers[i] = ToInt32(num, 0, byteorder, SystemByteOrder); } - /// - /// Converts the given ascii string to an array of bytes optionally adding a null terminator. - /// - public static byte[] GetBytes(string value, bool addnull, Encoding encoding) + return numbers; + } + + /// + /// Returns an array of unsigned rational numbers converted from + /// the given byte array. + /// Numbers are converted from the given byte-order to platform byte-order. + /// + public static MathEx.UFraction32[] ToURationalArray(byte[] data, int count, ByteOrder frombyteorder) + { + var numbers = new MathEx.UFraction32[count]; + for (uint i = 0; i < count; i++) { - if (addnull) value += '\0'; - return encoding.GetBytes(value); + var num = new byte[4]; + var den = new byte[4]; + Array.Copy(data, i * 8, num, 0, 4); + Array.Copy(data, (i * 8) + 4, den, 0, 4); + numbers[i].Set(ToUInt32(num, 0, frombyteorder, SystemByteOrder), + ToUInt32(den, 0, frombyteorder, SystemByteOrder)); } - /// - /// Converts the given ascii string to an array of bytes without adding a null terminator. - /// - public static byte[] GetBytes(string value, Encoding encoding) + return numbers; + } + + /// + /// Returns an array of signed rational numbers converted from + /// the given byte array. + /// Numbers are converted from the given byte-order to platform byte-order. + /// + public static MathEx.Fraction32[] ToSRationalArray(byte[] data, int count, ByteOrder frombyteorder) + { + var numbers = new MathEx.Fraction32[count]; + for (uint i = 0; i < count; i++) { - return GetBytes(value, false, encoding); + var num = new byte[4]; + var den = new byte[4]; + Array.Copy(data, i * 8, num, 0, 4); + Array.Copy(data, (i * 8) + 4, den, 0, 4); + numbers[i].Set(ToInt32(num, 0, frombyteorder, SystemByteOrder), + ToInt32(den, 0, frombyteorder, SystemByteOrder)); } - /// - /// Converts the given datetime to an array of bytes with a null terminator. - /// - public static byte[] GetBytes(DateTime value, bool hastime) + return numbers; + } + + /// + /// Converts the given ascii string to an array of bytes optionally adding a null terminator. + /// + public static byte[] GetBytes(string value, bool addnull, Encoding encoding) + { + if (addnull) { - string str = ""; - if (hastime) - str = value.ToString("yyyy:MM:dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture); - else - str = value.ToString("yyyy:MM:dd", System.Globalization.CultureInfo.InvariantCulture); - return GetBytes(str, true, Encoding.ASCII); + value += '\0'; } - /// - /// Converts the given unsigned rational number to an array of bytes. - /// Numbers are converted from the platform byte-order to the given byte-order. - /// - public static byte[] GetBytes(MathEx.UFraction32 value, ByteOrder tobyteorder) + return encoding.GetBytes(value); + } + + /// + /// Converts the given ascii string to an array of bytes without adding a null terminator. + /// + public static byte[] GetBytes(string value, Encoding encoding) => GetBytes(value, false, encoding); + + /// + /// Converts the given datetime to an array of bytes with a null terminator. + /// + public static byte[] GetBytes(DateTime value, bool hastime) + { + var str = ""; + if (hastime) { - byte[] num = GetBytes(value.Numerator, BitConverterEx.SystemByteOrder, tobyteorder); - byte[] den = GetBytes(value.Denominator, BitConverterEx.SystemByteOrder, tobyteorder); - byte[] data = new byte[8]; - Array.Copy(num, 0, data, 0, 4); - Array.Copy(den, 0, data, 4, 4); - return data; + str = value.ToString("yyyy:MM:dd HH:mm:ss", CultureInfo.InvariantCulture); } - - /// - /// Converts the given signed rational number to an array of bytes. - /// Numbers are converted from the platform byte-order to the given byte-order. - /// - public static byte[] GetBytes(MathEx.Fraction32 value, ByteOrder tobyteorder) + else { - byte[] num = GetBytes(value.Numerator, BitConverterEx.SystemByteOrder, tobyteorder); - byte[] den = GetBytes(value.Denominator, BitConverterEx.SystemByteOrder, tobyteorder); - byte[] data = new byte[8]; - Array.Copy(num, 0, data, 0, 4); - Array.Copy(den, 0, data, 4, 4); - return data; + str = value.ToString("yyyy:MM:dd", CultureInfo.InvariantCulture); } - /// - /// Converts the given array of 16-bit unsigned integers to an array of bytes. - /// Numbers are converted from the platform byte-order to the given byte-order. - /// - public static byte[] GetBytes(ushort[] value, ByteOrder tobyteorder) + return GetBytes(str, true, Encoding.ASCII); + } + + /// + /// Converts the given unsigned rational number to an array of bytes. + /// Numbers are converted from the platform byte-order to the given byte-order. + /// + public static byte[] GetBytes(MathEx.UFraction32 value, ByteOrder tobyteorder) + { + var num = GetBytes(value.Numerator, SystemByteOrder, tobyteorder); + var den = GetBytes(value.Denominator, SystemByteOrder, tobyteorder); + var data = new byte[8]; + Array.Copy(num, 0, data, 0, 4); + Array.Copy(den, 0, data, 4, 4); + return data; + } + + /// + /// Converts the given signed rational number to an array of bytes. + /// Numbers are converted from the platform byte-order to the given byte-order. + /// + public static byte[] GetBytes(MathEx.Fraction32 value, ByteOrder tobyteorder) + { + var num = GetBytes(value.Numerator, SystemByteOrder, tobyteorder); + var den = GetBytes(value.Denominator, SystemByteOrder, tobyteorder); + var data = new byte[8]; + Array.Copy(num, 0, data, 0, 4); + Array.Copy(den, 0, data, 4, 4); + return data; + } + + /// + /// Converts the given array of 16-bit unsigned integers to an array of bytes. + /// Numbers are converted from the platform byte-order to the given byte-order. + /// + public static byte[] GetBytes(ushort[] value, ByteOrder tobyteorder) + { + var data = new byte[2 * value.Length]; + for (var i = 0; i < value.Length; i++) { - byte[] data = new byte[2 * value.Length]; - for (int i = 0; i < value.Length; i++) - { - byte[] num = GetBytes(value[i], BitConverterEx.SystemByteOrder, tobyteorder); - Array.Copy(num, 0, data, i * 2, 2); - } - return data; + var num = GetBytes(value[i], SystemByteOrder, tobyteorder); + Array.Copy(num, 0, data, i * 2, 2); } - /// - /// Converts the given array of 32-bit unsigned integers to an array of bytes. - /// Numbers are converted from the platform byte-order to the given byte-order. - /// - public static byte[] GetBytes(uint[] value, ByteOrder tobyteorder) + return data; + } + + /// + /// Converts the given array of 32-bit unsigned integers to an array of bytes. + /// Numbers are converted from the platform byte-order to the given byte-order. + /// + public static byte[] GetBytes(uint[] value, ByteOrder tobyteorder) + { + var data = new byte[4 * value.Length]; + for (var i = 0; i < value.Length; i++) { - byte[] data = new byte[4 * value.Length]; - for (int i = 0; i < value.Length; i++) - { - byte[] num = GetBytes(value[i], BitConverterEx.SystemByteOrder, tobyteorder); - Array.Copy(num, 0, data, i * 4, 4); - } - return data; + var num = GetBytes(value[i], SystemByteOrder, tobyteorder); + Array.Copy(num, 0, data, i * 4, 4); } - /// - /// Converts the given array of 32-bit signed integers to an array of bytes. - /// Numbers are converted from the platform byte-order to the given byte-order. - /// - public static byte[] GetBytes(int[] value, ByteOrder tobyteorder) + return data; + } + + /// + /// Converts the given array of 32-bit signed integers to an array of bytes. + /// Numbers are converted from the platform byte-order to the given byte-order. + /// + public static byte[] GetBytes(int[] value, ByteOrder tobyteorder) + { + var data = new byte[4 * value.Length]; + for (var i = 0; i < value.Length; i++) { - byte[] data = new byte[4 * value.Length]; - for (int i = 0; i < value.Length; i++) - { - byte[] num = GetBytes(value[i], BitConverterEx.SystemByteOrder, tobyteorder); - Array.Copy(num, 0, data, i * 4, 4); - } - return data; + var num = GetBytes(value[i], SystemByteOrder, tobyteorder); + Array.Copy(num, 0, data, i * 4, 4); } - /// - /// Converts the given array of unsigned rationals to an array of bytes. - /// Numbers are converted from the platform byte-order to the given byte-order. - /// - public static byte[] GetBytes(MathEx.UFraction32[] value, ByteOrder tobyteorder) + return data; + } + + /// + /// Converts the given array of unsigned rationals to an array of bytes. + /// Numbers are converted from the platform byte-order to the given byte-order. + /// + public static byte[] GetBytes(MathEx.UFraction32[] value, ByteOrder tobyteorder) + { + var data = new byte[8 * value.Length]; + for (var i = 0; i < value.Length; i++) { - byte[] data = new byte[8 * value.Length]; - for (int i = 0; i < value.Length; i++) - { - byte[] num = GetBytes(value[i].Numerator, BitConverterEx.SystemByteOrder, tobyteorder); - byte[] den = GetBytes(value[i].Denominator, BitConverterEx.SystemByteOrder, tobyteorder); - Array.Copy(num, 0, data, i * 8, 4); - Array.Copy(den, 0, data, i * 8 + 4, 4); - } - return data; + var num = GetBytes(value[i].Numerator, SystemByteOrder, tobyteorder); + var den = GetBytes(value[i].Denominator, SystemByteOrder, tobyteorder); + Array.Copy(num, 0, data, i * 8, 4); + Array.Copy(den, 0, data, (i * 8) + 4, 4); } - /// - /// Converts the given array of signed rationals to an array of bytes. - /// Numbers are converted from the platform byte-order to the given byte-order. - /// - public static byte[] GetBytes(MathEx.Fraction32[] value, ByteOrder tobyteorder) + return data; + } + + /// + /// Converts the given array of signed rationals to an array of bytes. + /// Numbers are converted from the platform byte-order to the given byte-order. + /// + public static byte[] GetBytes(MathEx.Fraction32[] value, ByteOrder tobyteorder) + { + var data = new byte[8 * value.Length]; + for (var i = 0; i < value.Length; i++) { - byte[] data = new byte[8 * value.Length]; - for (int i = 0; i < value.Length; i++) - { - byte[] num = GetBytes(value[i].Numerator, BitConverterEx.SystemByteOrder, tobyteorder); - byte[] den = GetBytes(value[i].Denominator, BitConverterEx.SystemByteOrder, tobyteorder); - Array.Copy(num, 0, data, i * 8, 4); - Array.Copy(den, 0, data, i * 8 + 4, 4); - } - return data; + var num = GetBytes(value[i].Numerator, SystemByteOrder, tobyteorder); + var den = GetBytes(value[i].Denominator, SystemByteOrder, tobyteorder); + Array.Copy(num, 0, data, i * 8, 4); + Array.Copy(den, 0, data, (i * 8) + 4, 4); } - #endregion + + return data; } + + #endregion } diff --git a/src/Umbraco.Core/Media/Exif/ExifEnums.cs b/src/Umbraco.Core/Media/Exif/ExifEnums.cs index 1ce0ec4891ef..cb79c0d7dc2e 100644 --- a/src/Umbraco.Core/Media/Exif/ExifEnums.cs +++ b/src/Umbraco.Core/Media/Exif/ExifEnums.cs @@ -1,292 +1,297 @@ -using System; - -namespace Umbraco.Cms.Core.Media.Exif -{ - internal enum Compression : ushort - { - Uncompressed = 1, - CCITT1D = 2, - Group3Fax = 3, - Group4Fax = 4, - LZW = 5, - JPEG = 6, - PackBits = 32773, - } - - internal enum PhotometricInterpretation : ushort - { - WhiteIsZero = 0, - BlackIsZero = 1, - RGB = 2, - RGBPalette = 3, - TransparencyMask = 4, - CMYK = 5, - YCbCr = 6, - CIELab = 8, - } - - internal enum Orientation : ushort - { - Normal = 1, - MirroredVertically = 2, - Rotated180 = 3, - MirroredHorizontally = 4, - RotatedLeftAndMirroredVertically = 5, - RotatedRight = 6, - RotatedLeft = 7, - RotatedRightAndMirroredVertically = 8, - } - - internal enum PlanarConfiguration : ushort - { - ChunkyFormat = 1, - PlanarFormat = 2, - } - - internal enum YCbCrPositioning : ushort - { - Centered = 1, - CoSited = 2, - } - - internal enum ResolutionUnit : ushort - { - Inches = 2, - Centimeters = 3, - } - - internal enum ColorSpace : ushort - { - sRGB = 1, - Uncalibrated = 0xfff, - } - - internal enum ExposureProgram : ushort - { - NotDefined = 0, - Manual = 1, - Normal = 2, - AperturePriority = 3, - ShutterPriority = 4, - /// - /// Biased toward depth of field. - /// - Creative = 5, - /// - /// Biased toward fast shutter speed. - /// - Action = 6, - /// - /// For closeup photos with the background out of focus. - /// - Portrait = 7, - /// - /// For landscape photos with the background in focus. - /// - Landscape = 8, - } - - internal enum MeteringMode : ushort - { - Unknown = 0, - Average = 1, - CenterWeightedAverage = 2, - Spot = 3, - MultiSpot = 4, - Pattern = 5, - Partial = 6, - Other = 255, - } - - internal enum LightSource : ushort - { - Unknown = 0, - Daylight = 1, - Fluorescent = 2, - Tungsten = 3, - Flash = 4, - FineWeather = 9, - CloudyWeather = 10, - Shade = 11, - /// - /// D 5700 – 7100K - /// - DaylightFluorescent = 12, - /// - /// N 4600 – 5400K - /// - DayWhiteFluorescent = 13, - /// - /// W 3900 – 4500K - /// - CoolWhiteFluorescent = 14, - /// - /// WW 3200 – 3700K - /// - WhiteFluorescent = 15, - StandardLightA = 17, - StandardLightB = 18, - StandardLightC = 19, - D55 = 20, - D65 = 21, - D75 = 22, - D50 = 23, - ISOStudioTungsten = 24, - OtherLightSource = 255, - } - - [Flags] - internal enum Flash : ushort - { - FlashDidNotFire = 0, - StrobeReturnLightNotDetected = 4, - StrobeReturnLightDetected = 2, - FlashFired = 1, - CompulsoryFlashMode = 8, - AutoMode = 16, - NoFlashFunction = 32, - RedEyeReductionMode = 64, - } - - internal enum SensingMethod : ushort - { - NotDefined = 1, - OneChipColorAreaSensor = 2, - TwoChipColorAreaSensor = 3, - ThreeChipColorAreaSensor = 4, - ColorSequentialAreaSensor = 5, - TriLinearSensor = 7, - ColorSequentialLinearSensor = 8, - } - - internal enum FileSource : byte // UNDEFINED - { - DSC = 3, - } - - internal enum SceneType : byte // UNDEFINED - { - DirectlyPhotographedImage = 1, - } - - internal enum CustomRendered : ushort - { - NormalProcess = 0, - CustomProcess = 1, - } - - internal enum ExposureMode : ushort - { - Auto = 0, - Manual = 1, - AutoBracket = 2, - } - - internal enum WhiteBalance : ushort - { - Auto = 0, - Manual = 1, - } - - internal enum SceneCaptureType : ushort - { - Standard = 0, - Landscape = 1, - Portrait = 2, - NightScene = 3, - } - - internal enum GainControl : ushort - { - None = 0, - LowGainUp = 1, - HighGainUp = 2, - LowGainDown = 3, - HighGainDown = 4, - } - - internal enum Contrast : ushort - { - Normal = 0, - Soft = 1, - Hard = 2, - } - - internal enum Saturation : ushort - { - Normal = 0, - Low = 1, - High = 2, - } - - internal enum Sharpness : ushort - { - Normal = 0, - Soft = 1, - Hard = 2, - } - - internal enum SubjectDistanceRange : ushort - { - Unknown = 0, - Macro = 1, - CloseView = 2, - DistantView = 3, - } - - internal enum GPSLatitudeRef : byte // ASCII - { - North = 78, // 'N' - South = 83, // 'S' - } - - internal enum GPSLongitudeRef : byte // ASCII - { - West = 87, // 'W' - East = 69, // 'E' - } - - internal enum GPSAltitudeRef : byte - { - AboveSeaLevel = 0, - BelowSeaLevel = 1, - } - - internal enum GPSStatus : byte // ASCII - { - MeasurementInProgress = 65, // 'A' - MeasurementInteroperability = 86, // 'V' - } - - internal enum GPSMeasureMode : byte // ASCII - { - TwoDimensional = 50, // '2' - ThreeDimensional = 51, // '3' - } - - internal enum GPSSpeedRef : byte // ASCII - { - KilometersPerHour = 75, // 'K' - MilesPerHour = 77, // 'M' - Knots = 78, // 'N' - } - - internal enum GPSDirectionRef : byte // ASCII - { - TrueDirection = 84, // 'T' - MagneticDirection = 77, // 'M' - } - - internal enum GPSDistanceRef : byte // ASCII - { - Kilometers = 75, // 'K' - Miles = 77, // 'M' - Knots = 78, // 'N' - } - - internal enum GPSDifferential : ushort - { - MeasurementWithoutDifferentialCorrection = 0, - DifferentialCorrectionApplied = 1, - } +namespace Umbraco.Cms.Core.Media.Exif; + +internal enum Compression : ushort +{ + Uncompressed = 1, + CCITT1D = 2, + Group3Fax = 3, + Group4Fax = 4, + LZW = 5, + JPEG = 6, + PackBits = 32773 +} + +internal enum PhotometricInterpretation : ushort +{ + WhiteIsZero = 0, + BlackIsZero = 1, + RGB = 2, + RGBPalette = 3, + TransparencyMask = 4, + CMYK = 5, + YCbCr = 6, + CIELab = 8 +} + +internal enum Orientation : ushort +{ + Normal = 1, + MirroredVertically = 2, + Rotated180 = 3, + MirroredHorizontally = 4, + RotatedLeftAndMirroredVertically = 5, + RotatedRight = 6, + RotatedLeft = 7, + RotatedRightAndMirroredVertically = 8 +} + +internal enum PlanarConfiguration : ushort +{ + ChunkyFormat = 1, + PlanarFormat = 2 +} + +internal enum YCbCrPositioning : ushort +{ + Centered = 1, + CoSited = 2 +} + +internal enum ResolutionUnit : ushort +{ + Inches = 2, + Centimeters = 3 +} + +internal enum ColorSpace : ushort +{ + sRGB = 1, + Uncalibrated = 0xfff +} + +internal enum ExposureProgram : ushort +{ + NotDefined = 0, + Manual = 1, + Normal = 2, + AperturePriority = 3, + ShutterPriority = 4, + + /// + /// Biased toward depth of field. + /// + Creative = 5, + + /// + /// Biased toward fast shutter speed. + /// + Action = 6, + + /// + /// For closeup photos with the background out of focus. + /// + Portrait = 7, + + /// + /// For landscape photos with the background in focus. + /// + Landscape = 8 +} + +internal enum MeteringMode : ushort +{ + Unknown = 0, + Average = 1, + CenterWeightedAverage = 2, + Spot = 3, + MultiSpot = 4, + Pattern = 5, + Partial = 6, + Other = 255 +} + +internal enum LightSource : ushort +{ + Unknown = 0, + Daylight = 1, + Fluorescent = 2, + Tungsten = 3, + Flash = 4, + FineWeather = 9, + CloudyWeather = 10, + Shade = 11, + + /// + /// D 5700 – 7100K + /// + DaylightFluorescent = 12, + + /// + /// N 4600 – 5400K + /// + DayWhiteFluorescent = 13, + + /// + /// W 3900 – 4500K + /// + CoolWhiteFluorescent = 14, + + /// + /// WW 3200 – 3700K + /// + WhiteFluorescent = 15, + StandardLightA = 17, + StandardLightB = 18, + StandardLightC = 19, + D55 = 20, + D65 = 21, + D75 = 22, + D50 = 23, + ISOStudioTungsten = 24, + OtherLightSource = 255 +} + +[Flags] +internal enum Flash : ushort +{ + FlashDidNotFire = 0, + StrobeReturnLightNotDetected = 4, + StrobeReturnLightDetected = 2, + FlashFired = 1, + CompulsoryFlashMode = 8, + AutoMode = 16, + NoFlashFunction = 32, + RedEyeReductionMode = 64 +} + +internal enum SensingMethod : ushort +{ + NotDefined = 1, + OneChipColorAreaSensor = 2, + TwoChipColorAreaSensor = 3, + ThreeChipColorAreaSensor = 4, + ColorSequentialAreaSensor = 5, + TriLinearSensor = 7, + ColorSequentialLinearSensor = 8 +} + +internal enum FileSource : byte // UNDEFINED +{ + DSC = 3 +} + +internal enum SceneType : byte // UNDEFINED +{ + DirectlyPhotographedImage = 1 +} + +internal enum CustomRendered : ushort +{ + NormalProcess = 0, + CustomProcess = 1 +} + +internal enum ExposureMode : ushort +{ + Auto = 0, + Manual = 1, + AutoBracket = 2 +} + +internal enum WhiteBalance : ushort +{ + Auto = 0, + Manual = 1 +} + +internal enum SceneCaptureType : ushort +{ + Standard = 0, + Landscape = 1, + Portrait = 2, + NightScene = 3 +} + +internal enum GainControl : ushort +{ + None = 0, + LowGainUp = 1, + HighGainUp = 2, + LowGainDown = 3, + HighGainDown = 4 +} + +internal enum Contrast : ushort +{ + Normal = 0, + Soft = 1, + Hard = 2 +} + +internal enum Saturation : ushort +{ + Normal = 0, + Low = 1, + High = 2 +} + +internal enum Sharpness : ushort +{ + Normal = 0, + Soft = 1, + Hard = 2 +} + +internal enum SubjectDistanceRange : ushort +{ + Unknown = 0, + Macro = 1, + CloseView = 2, + DistantView = 3 +} + +internal enum GPSLatitudeRef : byte // ASCII +{ + North = 78, // 'N' + South = 83 // 'S' +} + +internal enum GPSLongitudeRef : byte // ASCII +{ + West = 87, // 'W' + East = 69 // 'E' +} + +internal enum GPSAltitudeRef : byte +{ + AboveSeaLevel = 0, + BelowSeaLevel = 1 +} + +internal enum GPSStatus : byte // ASCII +{ + MeasurementInProgress = 65, // 'A' + MeasurementInteroperability = 86 // 'V' +} + +internal enum GPSMeasureMode : byte // ASCII +{ + TwoDimensional = 50, // '2' + ThreeDimensional = 51 // '3' +} + +internal enum GPSSpeedRef : byte // ASCII +{ + KilometersPerHour = 75, // 'K' + MilesPerHour = 77, // 'M' + Knots = 78 // 'N' +} + +internal enum GPSDirectionRef : byte // ASCII +{ + TrueDirection = 84, // 'T' + MagneticDirection = 77 // 'M' +} + +internal enum GPSDistanceRef : byte // ASCII +{ + Kilometers = 75, // 'K' + Miles = 77, // 'M' + Knots = 78 // 'N' +} + +internal enum GPSDifferential : ushort +{ + MeasurementWithoutDifferentialCorrection = 0, + DifferentialCorrectionApplied = 1 } diff --git a/src/Umbraco.Core/Media/Exif/ExifExceptions.cs b/src/Umbraco.Core/Media/Exif/ExifExceptions.cs index 3d0472c100b3..03cc024ad27d 100644 --- a/src/Umbraco.Core/Media/Exif/ExifExceptions.cs +++ b/src/Umbraco.Core/Media/Exif/ExifExceptions.cs @@ -1,47 +1,57 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Media.Exif +namespace Umbraco.Cms.Core.Media.Exif; + +/// +/// The exception that is thrown when the format of the JPEG/EXIF file could not be understood. +/// +/// +[Serializable] +public class NotValidExifFileException : Exception { /// - /// The exception that is thrown when the format of the JPEG/EXIF file could not be understood. + /// Initializes a new instance of the class. /// - /// - [Serializable] - public class NotValidExifFileException : Exception + public NotValidExifFileException() + : base("Not a valid JPEG/EXIF file.") { - /// - /// Initializes a new instance of the class. - /// - public NotValidExifFileException() - : base("Not a valid JPEG/EXIF file.") - { } - - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public NotValidExifFileException(string message) - : base(message) - { } + } - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. - public NotValidExifFileException(string message, Exception innerException) - : base(message, innerException) - { } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public NotValidExifFileException(string message) + : base(message) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected NotValidExifFileException(SerializationInfo info, StreamingContext context) - : base(info, context) - { } + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// + /// The exception that is the cause of the current exception, or a null reference ( + /// in Visual Basic) if no inner exception is specified. + /// + public NotValidExifFileException(string message, Exception innerException) + : base(message, innerException) + { } + /// + /// Initializes a new instance of the class. + /// + /// + /// The that holds the serialized object + /// data about the exception being thrown. + /// + /// + /// The that contains contextual + /// information about the source or destination. + /// + protected NotValidExifFileException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } diff --git a/src/Umbraco.Core/Media/Exif/ExifExtendedProperty.cs b/src/Umbraco.Core/Media/Exif/ExifExtendedProperty.cs index 9aa62f4ea392..186c5442fe4d 100644 --- a/src/Umbraco.Core/Media/Exif/ExifExtendedProperty.cs +++ b/src/Umbraco.Core/Media/Exif/ExifExtendedProperty.cs @@ -1,374 +1,485 @@ -using System; -using System.Text; +using System.Text; -namespace Umbraco.Cms.Core.Media.Exif -{ - /// - /// Represents an enumerated value. - /// - internal class ExifEnumProperty : ExifProperty +namespace Umbraco.Cms.Core.Media.Exif; + +/// +/// Represents an enumerated value. +/// +internal class ExifEnumProperty : ExifProperty where T : notnull +{ + protected bool mIsBitField; + protected T mValue; + + public ExifEnumProperty(ExifTag tag, T value, bool isbitfield) + : base(tag) { - protected T mValue; - protected bool mIsBitField; - protected override object _Value { get { return Value; } set { Value = (T)value; } } - public new T Value { get { return mValue; } set { mValue = value; } } - public bool IsBitField { get { return mIsBitField; } } + mValue = value; + mIsBitField = isbitfield; + } - static public implicit operator T(ExifEnumProperty obj) { return (T)obj.mValue; } + public ExifEnumProperty(ExifTag tag, T value) + : this(tag, value, false) + { + } - public override string? ToString() { return mValue.ToString(); } + protected override object _Value + { + get => Value; + set => Value = (T)value; + } - public ExifEnumProperty(ExifTag tag, T value, bool isbitfield) - : base(tag) - { - mValue = value; - mIsBitField = isbitfield; - } + public new T Value + { + get => mValue; + set => mValue = value; + } + + public bool IsBitField => mIsBitField; - public ExifEnumProperty(ExifTag tag, T value) - : this(tag, value, false) + public override ExifInterOperability Interoperability + { + get { + var tagid = ExifTagFactory.GetTagID(mTag); - } + Type type = typeof(T); + Type basetype = Enum.GetUnderlyingType(type); - public override ExifInterOperability Interoperability - { - get + if (type == typeof(FileSource) || type == typeof(SceneType)) + { + // UNDEFINED + return new ExifInterOperability(tagid, 7, 1, new[] {(byte)(object)mValue}); + } + + if (type == typeof(GPSLatitudeRef) || type == typeof(GPSLongitudeRef) || + type == typeof(GPSStatus) || type == typeof(GPSMeasureMode) || + type == typeof(GPSSpeedRef) || type == typeof(GPSDirectionRef) || + type == typeof(GPSDistanceRef)) + { + // ASCII + return new ExifInterOperability(tagid, 2, 2, new byte[] {(byte)(object)mValue, 0}); + } + + if (basetype == typeof(byte)) { - ushort tagid = ExifTagFactory.GetTagID(mTag); - - Type type = typeof(T); - Type basetype = Enum.GetUnderlyingType(type); - - if (type == typeof(FileSource) || type == typeof(SceneType)) - { - // UNDEFINED - return new ExifInterOperability(tagid, 7, 1, new byte[] { (byte)((object)mValue) }); - } - else if (type == typeof(GPSLatitudeRef) || type == typeof(GPSLongitudeRef) || - type == typeof(GPSStatus) || type == typeof(GPSMeasureMode) || - type == typeof(GPSSpeedRef) || type == typeof(GPSDirectionRef) || - type == typeof(GPSDistanceRef)) - { - // ASCII - return new ExifInterOperability(tagid, 2, 2, new byte[] { (byte)((object)mValue), 0 }); - } - else if (basetype == typeof(byte)) - { - // BYTE - return new ExifInterOperability(tagid, 1, 1, new byte[] { (byte)((object)mValue) }); - } - else if (basetype == typeof(ushort)) - { - // SHORT - return new ExifInterOperability(tagid, 3, 1, ExifBitConverter.GetBytes((ushort)((object)mValue), BitConverterEx.SystemByteOrder, BitConverterEx.SystemByteOrder)); - } - else - throw new InvalidOperationException($"An invalid enum type ({basetype.FullName}) was provided for type {type.FullName}"); + // BYTE + return new ExifInterOperability(tagid, 1, 1, new[] {(byte)(object)mValue}); } + + if (basetype == typeof(ushort)) + { + // SHORT + return new ExifInterOperability(tagid, 3, 1, + BitConverterEx.GetBytes((ushort)(object)mValue, BitConverterEx.SystemByteOrder, + BitConverterEx.SystemByteOrder)); + } + + throw new InvalidOperationException( + $"An invalid enum type ({basetype.FullName}) was provided for type {type.FullName}"); } } - /// - /// Represents an ASCII string. (EXIF Specification: UNDEFINED) Used for the UserComment field. - /// - internal class ExifEncodedString : ExifProperty + public static implicit operator T(ExifEnumProperty obj) => obj.mValue; + + public override string? ToString() => mValue.ToString(); +} + +/// +/// Represents an ASCII string. (EXIF Specification: UNDEFINED) Used for the UserComment field. +/// +internal class ExifEncodedString : ExifProperty +{ + protected string mValue; + + public ExifEncodedString(ExifTag tag, string value, Encoding encoding) + : base(tag) { - protected string mValue; - private Encoding mEncoding; - protected override object _Value { get { return Value; } set { Value = (string)value; } } - public new string Value { get { return mValue; } set { mValue = value; } } - public Encoding Encoding { get { return mEncoding; } set { mEncoding = value; } } + mValue = value; + Encoding = encoding; + } - static public implicit operator string(ExifEncodedString obj) { return obj.mValue; } + protected override object _Value + { + get => Value; + set => Value = (string)value; + } - public override string ToString() { return mValue; } + public new string Value + { + get => mValue; + set => mValue = value; + } - public ExifEncodedString(ExifTag tag, string value, Encoding encoding) - : base(tag) - { - mValue = value; - mEncoding = encoding; - } + public Encoding Encoding { get; set; } - public override ExifInterOperability Interoperability + public override ExifInterOperability Interoperability + { + get { - get + var enc = ""; + if (Encoding == null) + { + enc = "\0\0\0\0\0\0\0\0"; + } + else if (Encoding.EncodingName == "US-ASCII") + { + enc = "ASCII\0\0\0"; + } + else if (Encoding.EncodingName == "Japanese (JIS 0208-1990 and 0212-1990)") + { + enc = "JIS\0\0\0\0\0"; + } + else if (Encoding.EncodingName == "Unicode") { - string enc = ""; - if (mEncoding == null) - enc = "\0\0\0\0\0\0\0\0"; - else if (mEncoding.EncodingName == "US-ASCII") - enc = "ASCII\0\0\0"; - else if (mEncoding.EncodingName == "Japanese (JIS 0208-1990 and 0212-1990)") - enc = "JIS\0\0\0\0\0"; - else if (mEncoding.EncodingName == "Unicode") - enc = "Unicode\0"; - else - enc = "\0\0\0\0\0\0\0\0"; - - byte[] benc = Encoding.ASCII.GetBytes(enc); - byte[] bstr = (mEncoding == null ? Encoding.ASCII.GetBytes(mValue) : mEncoding.GetBytes(mValue)); - byte[] data = new byte[benc.Length + bstr.Length]; - Array.Copy(benc, 0, data, 0, benc.Length); - Array.Copy(bstr, 0, data, benc.Length, bstr.Length); - - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 7, (uint)data.Length, data); + enc = "Unicode\0"; } + else + { + enc = "\0\0\0\0\0\0\0\0"; + } + + var benc = Encoding.ASCII.GetBytes(enc); + var bstr = Encoding == null ? Encoding.ASCII.GetBytes(mValue) : Encoding.GetBytes(mValue); + var data = new byte[benc.Length + bstr.Length]; + Array.Copy(benc, 0, data, 0, benc.Length); + Array.Copy(bstr, 0, data, benc.Length, bstr.Length); + + return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 7, (uint)data.Length, data); } } - /// - /// Represents an ASCII string formatted as DateTime. (EXIF Specification: ASCII) Used for the date time fields. - /// - internal class ExifDateTime : ExifProperty - { - protected DateTime mValue; - protected override object _Value { get { return Value; } set { Value = (DateTime)value; } } - public new DateTime Value { get { return mValue; } set { mValue = value; } } + public static implicit operator string(ExifEncodedString obj) => obj.mValue; - static public implicit operator DateTime(ExifDateTime obj) { return obj.mValue; } + public override string ToString() => mValue; +} - public override string ToString() { return mValue.ToString("yyyy.MM.dd HH:mm:ss"); } +/// +/// Represents an ASCII string formatted as DateTime. (EXIF Specification: ASCII) Used for the date time fields. +/// +internal class ExifDateTime : ExifProperty +{ + protected DateTime mValue; - public ExifDateTime(ExifTag tag, DateTime value) - : base(tag) - { - mValue = value; - } + public ExifDateTime(ExifTag tag, DateTime value) + : base(tag) => + mValue = value; - public override ExifInterOperability Interoperability - { - get - { - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 2, (uint)20, ExifBitConverter.GetBytes(mValue, true)); - } - } + protected override object _Value + { + get => Value; + set => Value = (DateTime)value; } - /// - /// Represents the exif version as a 4 byte ASCII string. (EXIF Specification: UNDEFINED) - /// Used for the ExifVersion, FlashpixVersion, InteroperabilityVersion and GPSVersionID fields. - /// - internal class ExifVersion : ExifProperty + public new DateTime Value { - protected string mValue; - protected override object _Value { get { return Value; } set { Value = (string)value; } } - public new string Value { get { return mValue; } set { mValue = value.Substring(0, 4); } } + get => mValue; + set => mValue = value; + } + + public override ExifInterOperability Interoperability => + new(ExifTagFactory.GetTagID(mTag), 2, 20, ExifBitConverter.GetBytes(mValue, true)); - public ExifVersion(ExifTag tag, string value) - : base(tag) + public static implicit operator DateTime(ExifDateTime obj) => obj.mValue; + + public override string ToString() => mValue.ToString("yyyy.MM.dd HH:mm:ss"); +} + +/// +/// Represents the exif version as a 4 byte ASCII string. (EXIF Specification: UNDEFINED) +/// Used for the ExifVersion, FlashpixVersion, InteroperabilityVersion and GPSVersionID fields. +/// +internal class ExifVersion : ExifProperty +{ + protected string mValue; + + public ExifVersion(ExifTag tag, string value) + : base(tag) + { + if (value.Length > 4) { - if (value.Length > 4) - mValue = value.Substring(0, 4); - else if (value.Length < 4) - mValue = value + new string(' ', 4 - value.Length); - else - mValue = value; + mValue = value.Substring(0, 4); } - - public override string ToString() + else if (value.Length < 4) { - return mValue; + mValue = value + new string(' ', 4 - value.Length); } - - public override ExifInterOperability Interoperability + else { - get - { - if (mTag == ExifTag.ExifVersion || mTag == ExifTag.FlashpixVersion || mTag == ExifTag.InteroperabilityVersion) - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 7, 4, Encoding.ASCII.GetBytes(mValue)); - else - { - byte[] data = new byte[4]; - for (int i = 0; i < 4; i++) - data[i] = byte.Parse(mValue[0].ToString()); - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 7, 4, data); - } - } + mValue = value; } } - /// - /// Represents the location and area of the subject (EXIF Specification: 2xSHORT) - /// The coordinate values, width, and height are expressed in relation to the - /// upper left as origin, prior to rotation processing as per the Rotation tag. - /// - internal class ExifPointSubjectArea : ExifUShortArray + protected override object _Value { - protected new ushort[] Value { get { return mValue; } set { mValue = value; } } - public ushort X { get { return mValue[0]; } set { mValue[0] = value; } } - public ushort Y { get { return mValue[1]; } set { mValue[1] = value; } } + get => Value; + set => Value = (string)value; + } - public override string ToString() - { - StringBuilder sb = new StringBuilder(); - sb.AppendFormat("({0:d}, {1:d})", mValue[0], mValue[1]); - return sb.ToString(); - } + public new string Value + { + get => mValue; + set => mValue = value.Substring(0, 4); + } - public ExifPointSubjectArea(ExifTag tag, ushort[] value) - : base(tag, value) + public override ExifInterOperability Interoperability + { + get { + if (mTag == ExifTag.ExifVersion || mTag == ExifTag.FlashpixVersion || + mTag == ExifTag.InteroperabilityVersion) + { + return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 7, 4, Encoding.ASCII.GetBytes(mValue)); + } + + var data = new byte[4]; + for (var i = 0; i < 4; i++) + { + data[i] = byte.Parse(mValue[0].ToString()); + } + return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 7, 4, data); } + } - public ExifPointSubjectArea(ExifTag tag, ushort x, ushort y) - : base(tag, new ushort[] {x, y}) - { + public override string ToString() => mValue; +} - } +/// +/// Represents the location and area of the subject (EXIF Specification: 2xSHORT) +/// The coordinate values, width, and height are expressed in relation to the +/// upper left as origin, prior to rotation processing as per the Rotation tag. +/// +internal class ExifPointSubjectArea : ExifUShortArray +{ + public ExifPointSubjectArea(ExifTag tag, ushort[] value) + : base(tag, value) + { } - /// - /// Represents the location and area of the subject (EXIF Specification: 3xSHORT) - /// The coordinate values, width, and height are expressed in relation to the - /// upper left as origin, prior to rotation processing as per the Rotation tag. - /// - internal class ExifCircularSubjectArea : ExifPointSubjectArea + public ExifPointSubjectArea(ExifTag tag, ushort x, ushort y) + : base(tag, new[] {x, y}) { - public ushort Diamater { get { return mValue[2]; } set { mValue[2] = value; } } + } - public override string ToString() - { - StringBuilder sb = new StringBuilder(); - sb.AppendFormat("({0:d}, {1:d}) {2:d}", mValue[0], mValue[1], mValue[2]); - return sb.ToString(); - } + protected new ushort[] Value + { + get => mValue; + set => mValue = value; + } - public ExifCircularSubjectArea(ExifTag tag, ushort[] value) - : base(tag, value) - { + public ushort X + { + get => mValue[0]; + set => mValue[0] = value; + } - } + public ushort Y + { + get => mValue[1]; + set => mValue[1] = value; + } - public ExifCircularSubjectArea(ExifTag tag, ushort x, ushort y, ushort d) - : base(tag, new ushort[] { x, y, d }) - { + public override string ToString() + { + var sb = new StringBuilder(); + sb.AppendFormat("({0:d}, {1:d})", mValue[0], mValue[1]); + return sb.ToString(); + } +} - } +/// +/// Represents the location and area of the subject (EXIF Specification: 3xSHORT) +/// The coordinate values, width, and height are expressed in relation to the +/// upper left as origin, prior to rotation processing as per the Rotation tag. +/// +internal class ExifCircularSubjectArea : ExifPointSubjectArea +{ + public ExifCircularSubjectArea(ExifTag tag, ushort[] value) + : base(tag, value) + { } - /// - /// Represents the location and area of the subject (EXIF Specification: 4xSHORT) - /// The coordinate values, width, and height are expressed in relation to the - /// upper left as origin, prior to rotation processing as per the Rotation tag. - /// - internal class ExifRectangularSubjectArea : ExifPointSubjectArea + public ExifCircularSubjectArea(ExifTag tag, ushort x, ushort y, ushort d) + : base(tag, new[] {x, y, d}) { - public ushort Width { get { return mValue[2]; } set { mValue[2] = value; } } - public ushort Height { get { return mValue[3]; } set { mValue[3] = value; } } + } - public override string ToString() - { - StringBuilder sb = new StringBuilder(); - sb.AppendFormat("({0:d}, {1:d}) ({2:d} x {3:d})", mValue[0], mValue[1], mValue[2], mValue[3]); - return sb.ToString(); - } + public ushort Diamater + { + get => mValue[2]; + set => mValue[2] = value; + } - public ExifRectangularSubjectArea(ExifTag tag, ushort[] value) - : base(tag, value) - { + public override string ToString() + { + var sb = new StringBuilder(); + sb.AppendFormat("({0:d}, {1:d}) {2:d}", mValue[0], mValue[1], mValue[2]); + return sb.ToString(); + } +} - } +/// +/// Represents the location and area of the subject (EXIF Specification: 4xSHORT) +/// The coordinate values, width, and height are expressed in relation to the +/// upper left as origin, prior to rotation processing as per the Rotation tag. +/// +internal class ExifRectangularSubjectArea : ExifPointSubjectArea +{ + public ExifRectangularSubjectArea(ExifTag tag, ushort[] value) + : base(tag, value) + { + } - public ExifRectangularSubjectArea(ExifTag tag, ushort x, ushort y, ushort w, ushort h) - : base(tag, new ushort[] { x, y, w, h }) - { + public ExifRectangularSubjectArea(ExifTag tag, ushort x, ushort y, ushort w, ushort h) + : base(tag, new[] {x, y, w, h}) + { + } - } + public ushort Width + { + get => mValue[2]; + set => mValue[2] = value; } - /// - /// Represents GPS latitudes and longitudes (EXIF Specification: 3xRATIONAL) - /// - internal class GPSLatitudeLongitude : ExifURationalArray + public ushort Height { - protected new MathEx.UFraction32[] Value { get { return mValue; } set { mValue = value; } } - public MathEx.UFraction32 Degrees { get { return mValue[0]; } set { mValue[0] = value; } } - public MathEx.UFraction32 Minutes { get { return mValue[1]; } set { mValue[1] = value; } } - public MathEx.UFraction32 Seconds { get { return mValue[2]; } set { mValue[2] = value; } } + get => mValue[3]; + set => mValue[3] = value; + } - public static explicit operator float(GPSLatitudeLongitude obj) { return obj.ToFloat(); } - public float ToFloat() - { - return (float)Degrees + ((float)Minutes) / 60.0f + ((float)Seconds) / 3600.0f; - } + public override string ToString() + { + var sb = new StringBuilder(); + sb.AppendFormat("({0:d}, {1:d}) ({2:d} x {3:d})", mValue[0], mValue[1], mValue[2], mValue[3]); + return sb.ToString(); + } +} - public override string ToString() - { - return string.Format("{0:F2}°{1:F2}'{2:F2}\"", (float)Degrees, (float)Minutes, (float)Seconds); - } +/// +/// Represents GPS latitudes and longitudes (EXIF Specification: 3xRATIONAL) +/// +internal class GPSLatitudeLongitude : ExifURationalArray +{ + public GPSLatitudeLongitude(ExifTag tag, MathEx.UFraction32[] value) + : base(tag, value) + { + } - public GPSLatitudeLongitude(ExifTag tag, MathEx.UFraction32[] value) - : base(tag, value) - { + public GPSLatitudeLongitude(ExifTag tag, float d, float m, float s) + : base(tag, new[] {new(d), new MathEx.UFraction32(m), new MathEx.UFraction32(s)}) + { + } - } + protected new MathEx.UFraction32[] Value + { + get => mValue; + set => mValue = value; + } - public GPSLatitudeLongitude(ExifTag tag, float d, float m, float s) - : base(tag, new MathEx.UFraction32[] { new MathEx.UFraction32(d), new MathEx.UFraction32(m), new MathEx.UFraction32(s) }) - { + public MathEx.UFraction32 Degrees + { + get => mValue[0]; + set => mValue[0] = value; + } - } + public MathEx.UFraction32 Minutes + { + get => mValue[1]; + set => mValue[1] = value; } - /// - /// Represents a GPS time stamp as UTC (EXIF Specification: 3xRATIONAL) - /// - internal class GPSTimeStamp : ExifURationalArray + public MathEx.UFraction32 Seconds { - protected new MathEx.UFraction32[] Value { get { return mValue; } set { mValue = value; } } - public MathEx.UFraction32 Hour { get { return mValue[0]; } set { mValue[0] = value; } } - public MathEx.UFraction32 Minute { get { return mValue[1]; } set { mValue[1] = value; } } - public MathEx.UFraction32 Second { get { return mValue[2]; } set { mValue[2] = value; } } + get => mValue[2]; + set => mValue[2] = value; + } - public override string ToString() - { - return string.Format("{0:F2}:{1:F2}:{2:F2}\"", (float)Hour, (float)Minute, (float)Second); - } + public static explicit operator float(GPSLatitudeLongitude obj) => obj.ToFloat(); - public GPSTimeStamp(ExifTag tag, MathEx.UFraction32[] value) - : base(tag, value) - { + public float ToFloat() => (float)Degrees + ((float)Minutes / 60.0f) + ((float)Seconds / 3600.0f); - } + public override string ToString() => + string.Format("{0:F2}°{1:F2}'{2:F2}\"", (float)Degrees, (float)Minutes, (float)Seconds); +} - public GPSTimeStamp(ExifTag tag, float h, float m, float s) - : base(tag, new MathEx.UFraction32[] { new MathEx.UFraction32(h), new MathEx.UFraction32(m), new MathEx.UFraction32(s) }) - { +/// +/// Represents a GPS time stamp as UTC (EXIF Specification: 3xRATIONAL) +/// +internal class GPSTimeStamp : ExifURationalArray +{ + public GPSTimeStamp(ExifTag tag, MathEx.UFraction32[] value) + : base(tag, value) + { + } - } + public GPSTimeStamp(ExifTag tag, float h, float m, float s) + : base(tag, new[] {new(h), new MathEx.UFraction32(m), new MathEx.UFraction32(s)}) + { } - /// - /// Represents an ASCII string. (EXIF Specification: BYTE) - /// Used by Windows XP. - /// - internal class WindowsByteString : ExifProperty + protected new MathEx.UFraction32[] Value { - protected string mValue; - protected override object _Value { get { return Value; } set { Value = (string)value; } } - public new string Value { get { return mValue; } set { mValue = value; } } + get => mValue; + set => mValue = value; + } - static public implicit operator string(WindowsByteString obj) { return obj.mValue; } + public MathEx.UFraction32 Hour + { + get => mValue[0]; + set => mValue[0] = value; + } - public override string ToString() { return mValue; } + public MathEx.UFraction32 Minute + { + get => mValue[1]; + set => mValue[1] = value; + } - public WindowsByteString(ExifTag tag, string value) - : base(tag) - { - mValue = value; - } + public MathEx.UFraction32 Second + { + get => mValue[2]; + set => mValue[2] = value; + } + + public override string ToString() => + string.Format("{0:F2}:{1:F2}:{2:F2}\"", (float)Hour, (float)Minute, (float)Second); +} + +/// +/// Represents an ASCII string. (EXIF Specification: BYTE) +/// Used by Windows XP. +/// +internal class WindowsByteString : ExifProperty +{ + protected string mValue; + + public WindowsByteString(ExifTag tag, string value) + : base(tag) => + mValue = value; - public override ExifInterOperability Interoperability + protected override object _Value + { + get => Value; + set => Value = (string)value; + } + + public new string Value + { + get => mValue; + set => mValue = value; + } + + public override ExifInterOperability Interoperability + { + get { - get - { - byte[] data = Encoding.Unicode.GetBytes(mValue); - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 1, (uint)data.Length, data); - } + var data = Encoding.Unicode.GetBytes(mValue); + return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 1, (uint)data.Length, data); } } + + public static implicit operator string(WindowsByteString obj) => obj.mValue; + + public override string ToString() => mValue; } diff --git a/src/Umbraco.Core/Media/Exif/ExifFileTypeDescriptor.cs b/src/Umbraco.Core/Media/Exif/ExifFileTypeDescriptor.cs index 61d6b70f308b..5bc4d44efbc9 100644 --- a/src/Umbraco.Core/Media/Exif/ExifFileTypeDescriptor.cs +++ b/src/Umbraco.Core/Media/Exif/ExifFileTypeDescriptor.cs @@ -1,131 +1,108 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; +using System.ComponentModel; -namespace Umbraco.Cms.Core.Media.Exif +namespace Umbraco.Cms.Core.Media.Exif; + +/// +/// Provides a custom type descriptor for an ExifFile instance. +/// +internal sealed class ExifFileTypeDescriptionProvider : TypeDescriptionProvider { + public ExifFileTypeDescriptionProvider() + : this(TypeDescriptor.GetProvider(typeof(ImageFile))) + { + } + + public ExifFileTypeDescriptionProvider(TypeDescriptionProvider parent) + : base(parent) + { + } + /// - /// Provides a custom type descriptor for an ExifFile instance. + /// Gets a custom type descriptor for the given type and object. /// - internal sealed class ExifFileTypeDescriptionProvider : TypeDescriptionProvider - { - public ExifFileTypeDescriptionProvider() - : this(TypeDescriptor.GetProvider(typeof(ImageFile))) - { - } + /// The type of object for which to retrieve the type descriptor. + /// + /// An instance of the type. Can be null if no instance was passed to the + /// . + /// + /// + /// An that can provide metadata for the type. + /// + public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object? instance) => + new ExifFileTypeDescriptor(base.GetTypeDescriptor(objectType, instance), instance); +} - public ExifFileTypeDescriptionProvider(TypeDescriptionProvider parent) - : base(parent) - { - } +/// +/// Expands ExifProperty objects contained in an ExifFile as separate properties. +/// +internal sealed class ExifFileTypeDescriptor : CustomTypeDescriptor +{ + private readonly ImageFile? owner; - /// - /// Gets a custom type descriptor for the given type and object. - /// - /// The type of object for which to retrieve the type descriptor. - /// An instance of the type. Can be null if no instance was passed to the . - /// - /// An that can provide metadata for the type. - /// - public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object? instance) - { - return new ExifFileTypeDescriptor(base.GetTypeDescriptor(objectType, instance), instance); - } - } + public ExifFileTypeDescriptor(ICustomTypeDescriptor? parent, object? instance) + : base(parent) => + owner = (ImageFile?)instance; + + public override PropertyDescriptorCollection GetProperties(Attribute[]? attributes) => GetProperties(); /// - /// Expands ExifProperty objects contained in an ExifFile as separate properties. + /// Returns a collection of property descriptors for the object represented by this type descriptor. /// - internal sealed class ExifFileTypeDescriptor : CustomTypeDescriptor + /// + /// A containing the property descriptions for the + /// object represented by this type descriptor. The default is + /// . + /// + public override PropertyDescriptorCollection GetProperties() { - ImageFile? owner; + // Enumerate the original set of properties and create our new set with it + var properties = new List(); - public ExifFileTypeDescriptor(ICustomTypeDescriptor? parent, object? instance) - : base(parent) - { - owner = (ImageFile?)instance; - } - public override PropertyDescriptorCollection GetProperties(Attribute[]? attributes) - { - return GetProperties(); - } - /// - /// Returns a collection of property descriptors for the object represented by this type descriptor. - /// - /// - /// A containing the property descriptions for the object represented by this type descriptor. The default is . - /// - public override PropertyDescriptorCollection GetProperties() + if (owner is not null) { - // Enumerate the original set of properties and create our new set with it - List properties = new List(); - - if (owner is not null) + foreach (ExifProperty prop in owner.Properties) { - foreach (ExifProperty prop in owner.Properties) - { - ExifPropertyDescriptor pd = new ExifPropertyDescriptor(prop); - properties.Add(pd); - } + var pd = new ExifPropertyDescriptor(prop); + properties.Add(pd); } - - // Finally return the list - return new PropertyDescriptorCollection(properties.ToArray(), true); } + + // Finally return the list + return new PropertyDescriptorCollection(properties.ToArray(), true); } - internal sealed class ExifPropertyDescriptor : PropertyDescriptor - { - object originalValue; - ExifProperty linkedProperty; +} - public ExifPropertyDescriptor(ExifProperty property) - : base(property.Name, new Attribute[] { new BrowsableAttribute(true) }) - { - linkedProperty = property; - originalValue = property.Value; - } +internal sealed class ExifPropertyDescriptor : PropertyDescriptor +{ + private readonly ExifProperty linkedProperty; + private readonly object originalValue; - public override bool CanResetValue(object component) - { - return true; - } + public ExifPropertyDescriptor(ExifProperty property) + : base(property.Name, new Attribute[] {new BrowsableAttribute(true)}) + { + linkedProperty = property; + originalValue = property.Value; + } - public override Type ComponentType - { - get { return typeof(JPEGFile); } - } + public override Type ComponentType => typeof(JPEGFile); - public override object GetValue(object? component) - { - return linkedProperty.Value; - } + public override bool IsReadOnly => false; - public override bool IsReadOnly - { - get { return false; } - } + public override Type PropertyType => linkedProperty.Value.GetType(); - public override Type PropertyType - { - get { return linkedProperty.Value.GetType(); } - } + public override bool CanResetValue(object component) => true; - public override void ResetValue(object component) - { - linkedProperty.Value = originalValue; - } + public override object GetValue(object? component) => linkedProperty.Value; - public override void SetValue(object? component, object? value) - { - if (value is not null) - { - linkedProperty.Value = value; - } - } + public override void ResetValue(object component) => linkedProperty.Value = originalValue; - public override bool ShouldSerializeValue(object component) + public override void SetValue(object? component, object? value) + { + if (value is not null) { - return false; + linkedProperty.Value = value; } } + + public override bool ShouldSerializeValue(object component) => false; } diff --git a/src/Umbraco.Core/Media/Exif/ExifInterOperability.cs b/src/Umbraco.Core/Media/Exif/ExifInterOperability.cs index 160ee3863600..55a893b224f4 100644 --- a/src/Umbraco.Core/Media/Exif/ExifInterOperability.cs +++ b/src/Umbraco.Core/Media/Exif/ExifInterOperability.cs @@ -1,60 +1,56 @@ -namespace Umbraco.Cms.Core.Media.Exif +namespace Umbraco.Cms.Core.Media.Exif; + +/// +/// Represents interoperability data for an exif tag in the platform byte order. +/// +internal struct ExifInterOperability { /// - /// Represents interoperability data for an exif tag in the platform byte order. + /// Gets the tag ID defined in the Exif standard. /// - internal struct ExifInterOperability - { - private ushort mTagID; - private ushort mTypeID; - private uint mCount; - private byte[] mData; + public ushort TagID { get; } + + /// + /// Gets the type code defined in the Exif standard. + /// + /// 1 = BYTE (byte) + /// 2 = ASCII (byte array) + /// 3 = SHORT (ushort) + /// 4 = LONG (uint) + /// 5 = RATIONAL (2 x uint: numerator, denominator) + /// 6 = BYTE (sbyte) + /// 7 = UNDEFINED (byte array) + /// 8 = SSHORT (short) + /// 9 = SLONG (int) + /// 10 = SRATIONAL (2 x int: numerator, denominator) + /// 11 = FLOAT (float) + /// 12 = DOUBLE (double) + /// + /// + public ushort TypeID { get; } + + /// + /// Gets the byte count or number of components. + /// + public uint Count { get; } - /// - /// Gets the tag ID defined in the Exif standard. - /// - public ushort TagID { get { return mTagID; } } - /// - /// Gets the type code defined in the Exif standard. - /// - /// 1 = BYTE (byte) - /// 2 = ASCII (byte array) - /// 3 = SHORT (ushort) - /// 4 = LONG (uint) - /// 5 = RATIONAL (2 x uint: numerator, denominator) - /// 6 = BYTE (sbyte) - /// 7 = UNDEFINED (byte array) - /// 8 = SSHORT (short) - /// 9 = SLONG (int) - /// 10 = SRATIONAL (2 x int: numerator, denominator) - /// 11 = FLOAT (float) - /// 12 = DOUBLE (double) - /// - /// - public ushort TypeID { get { return mTypeID; } } - /// - /// Gets the byte count or number of components. - /// - public uint Count { get { return mCount; } } - /// - /// Gets the field value as an array of bytes. - /// - public byte[] Data { get { return mData; } } - /// - /// Returns the string representation of this instance. - /// - /// - public override string ToString() - { - return string.Format("Tag: {0}, Type: {1}, Count: {2}, Data Length: {3}", mTagID, mTypeID, mCount, mData.Length); - } + /// + /// Gets the field value as an array of bytes. + /// + public byte[] Data { get; } - public ExifInterOperability(ushort tagid, ushort typeid, uint count, byte[] data) - { - mTagID = tagid; - mTypeID = typeid; - mCount = count; - mData = data; - } + /// + /// Returns the string representation of this instance. + /// + /// + public override string ToString() => string.Format("Tag: {0}, Type: {1}, Count: {2}, Data Length: {3}", TagID, + TypeID, Count, Data.Length); + + public ExifInterOperability(ushort tagid, ushort typeid, uint count, byte[] data) + { + TagID = tagid; + TypeID = typeid; + Count = count; + Data = data; } } diff --git a/src/Umbraco.Core/Media/Exif/ExifProperty.cs b/src/Umbraco.Core/Media/Exif/ExifProperty.cs index a3c28aabbc91..e31c2ce73272 100644 --- a/src/Umbraco.Core/Media/Exif/ExifProperty.cs +++ b/src/Umbraco.Core/Media/Exif/ExifProperty.cs @@ -1,578 +1,631 @@ -using System; -using System.Text; +using System.Text; -namespace Umbraco.Cms.Core.Media.Exif +namespace Umbraco.Cms.Core.Media.Exif; + +/// +/// Represents the abstract base class for an Exif property. +/// +internal abstract class ExifProperty { + protected IFD mIFD; + protected string? mName; + protected ExifTag mTag; + + public ExifProperty(ExifTag tag) + { + mTag = tag; + mIFD = ExifTagFactory.GetTagIFD(tag); + } + /// - /// Represents the abstract base class for an Exif property. + /// Gets the Exif tag associated with this property. /// - internal abstract class ExifProperty - { - protected ExifTag mTag; - protected IFD mIFD; - protected string? mName; - - /// - /// Gets the Exif tag associated with this property. - /// - public ExifTag Tag { get { return mTag; } } - /// - /// Gets the IFD section containing this property. - /// - public IFD IFD { get { return mIFD; } } - /// - /// Gets or sets the name of this property. - /// - public string Name + public ExifTag Tag => mTag; + + /// + /// Gets the IFD section containing this property. + /// + public IFD IFD => mIFD; + + /// + /// Gets or sets the name of this property. + /// + public string Name + { + get { - get - { - if (string.IsNullOrEmpty(mName)) - return ExifTagFactory.GetTagName(mTag); - else - return mName; - } - set + if (string.IsNullOrEmpty(mName)) { - mName = value; + return ExifTagFactory.GetTagName(mTag); } + + return mName; } - protected abstract object _Value { get; set; } - /// - /// Gets or sets the value of this property. - /// - public object Value { get { return _Value; } set { _Value = value; } } - /// - /// Gets interoperability data for this property. - /// - public abstract ExifInterOperability Interoperability { get; } - - public ExifProperty(ExifTag tag) - { - mTag = tag; - mIFD = ExifTagFactory.GetTagIFD(tag); - } + set => mName = value; } + protected abstract object _Value { get; set; } + /// - /// Represents an 8-bit unsigned integer. (EXIF Specification: BYTE) + /// Gets or sets the value of this property. /// - internal class ExifByte : ExifProperty + public object Value { - protected byte mValue; - protected override object _Value { get { return Value; } set { Value = Convert.ToByte(value); } } - public new byte Value { get { return mValue; } set { mValue = value; } } + get => _Value; + set => _Value = value; + } - static public implicit operator byte(ExifByte obj) { return obj.mValue; } + /// + /// Gets interoperability data for this property. + /// + public abstract ExifInterOperability Interoperability { get; } +} - public override string ToString() { return mValue.ToString(); } +/// +/// Represents an 8-bit unsigned integer. (EXIF Specification: BYTE) +/// +internal class ExifByte : ExifProperty +{ + protected byte mValue; - public ExifByte(ExifTag tag, byte value) - : base(tag) - { - mValue = value; - } + public ExifByte(ExifTag tag, byte value) + : base(tag) => + mValue = value; - public override ExifInterOperability Interoperability - { - get - { - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 1, 1, new byte[] { mValue }); - } - } + protected override object _Value + { + get => Value; + set => Value = Convert.ToByte(value); } - /// - /// Represents an array of 8-bit unsigned integers. (EXIF Specification: BYTE with count > 1) - /// - internal class ExifByteArray : ExifProperty + public new byte Value { - protected byte[] mValue; - protected override object _Value { get { return Value; } set { Value = (byte[])value; } } - public new byte[] Value { get { return mValue; } set { mValue = value; } } + get => mValue; + set => mValue = value; + } - static public implicit operator byte[](ExifByteArray obj) { return obj.mValue; } + public override ExifInterOperability Interoperability => + new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 1, 1, new[] {mValue}); - public override string ToString() - { - StringBuilder sb = new StringBuilder(); - sb.Append('['); - foreach (byte b in mValue) - { - sb.Append(b); - sb.Append(' '); - } - sb.Remove(sb.Length - 1, 1); - sb.Append(']'); - return sb.ToString(); - } + public static implicit operator byte(ExifByte obj) => obj.mValue; - public ExifByteArray(ExifTag tag, byte[] value) - : base(tag) - { - mValue = value; - } + public override string ToString() => mValue.ToString(); +} + +/// +/// Represents an array of 8-bit unsigned integers. (EXIF Specification: BYTE with count > 1) +/// +internal class ExifByteArray : ExifProperty +{ + protected byte[] mValue; + + public ExifByteArray(ExifTag tag, byte[] value) + : base(tag) => + mValue = value; + + protected override object _Value + { + get => Value; + set => Value = (byte[])value; + } + + public new byte[] Value + { + get => mValue; + set => mValue = value; + } + + public override ExifInterOperability Interoperability => + new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 1, (uint)mValue.Length, mValue); + + public static implicit operator byte[](ExifByteArray obj) => obj.mValue; - public override ExifInterOperability Interoperability + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append('['); + foreach (var b in mValue) { - get - { - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 1, (uint)mValue.Length, mValue); - } + sb.Append(b); + sb.Append(' '); } + + sb.Remove(sb.Length - 1, 1); + sb.Append(']'); + return sb.ToString(); } +} - /// - /// Represents an ASCII string. (EXIF Specification: ASCII) - /// - internal class ExifAscii : ExifProperty +/// +/// Represents an ASCII string. (EXIF Specification: ASCII) +/// +internal class ExifAscii : ExifProperty +{ + protected string mValue; + + public ExifAscii(ExifTag tag, string value, Encoding encoding) + : base(tag) { - protected string mValue; - protected override object _Value { get { return Value; } set { Value = (string)value; } } - public new string Value { get { return mValue; } set { mValue = value; } } + mValue = value; + Encoding = encoding; + } - public Encoding Encoding { get; private set; } + protected override object _Value + { + get => Value; + set => Value = (string)value; + } - static public implicit operator string(ExifAscii obj) { return obj.mValue; } + public new string Value + { + get => mValue; + set => mValue = value; + } - public override string ToString() { return mValue; } + public Encoding Encoding { get; } - public ExifAscii(ExifTag tag, string value, Encoding encoding) - : base(tag) - { - mValue = value; - Encoding = encoding; - } + public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 2, + (uint)mValue.Length + 1, ExifBitConverter.GetBytes(mValue, true, Encoding)); - public override ExifInterOperability Interoperability - { - get - { - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 2, (uint)mValue.Length + 1, ExifBitConverter.GetBytes(mValue, true, Encoding)); - } - } + public static implicit operator string(ExifAscii obj) => obj.mValue; + + public override string ToString() => mValue; +} + +/// +/// Represents a 16-bit unsigned integer. (EXIF Specification: SHORT) +/// +internal class ExifUShort : ExifProperty +{ + protected ushort mValue; + + public ExifUShort(ExifTag tag, ushort value) + : base(tag) => + mValue = value; + + protected override object _Value + { + get => Value; + set => Value = Convert.ToUInt16(value); } - /// - /// Represents a 16-bit unsigned integer. (EXIF Specification: SHORT) - /// - internal class ExifUShort : ExifProperty + public new ushort Value { - protected ushort mValue; - protected override object _Value { get { return Value; } set { Value = Convert.ToUInt16(value); } } - public new ushort Value { get { return mValue; } set { mValue = value; } } + get => mValue; + set => mValue = value; + } - static public implicit operator ushort(ExifUShort obj) { return obj.mValue; } + public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 3, + 1, BitConverterEx.GetBytes(mValue, BitConverterEx.SystemByteOrder, BitConverterEx.SystemByteOrder)); - public override string ToString() { return mValue.ToString(); } + public static implicit operator ushort(ExifUShort obj) => obj.mValue; - public ExifUShort(ExifTag tag, ushort value) - : base(tag) - { - mValue = value; - } + public override string ToString() => mValue.ToString(); +} - public override ExifInterOperability Interoperability - { - get - { - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 3, 1, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder, BitConverterEx.SystemByteOrder)); - } - } +/// +/// Represents an array of 16-bit unsigned integers. +/// (EXIF Specification: SHORT with count > 1) +/// +internal class ExifUShortArray : ExifProperty +{ + protected ushort[] mValue; + + public ExifUShortArray(ExifTag tag, ushort[] value) + : base(tag) => + mValue = value; + + protected override object _Value + { + get => Value; + set => Value = (ushort[])value; } - /// - /// Represents an array of 16-bit unsigned integers. - /// (EXIF Specification: SHORT with count > 1) - /// - internal class ExifUShortArray : ExifProperty + public new ushort[] Value { - protected ushort[] mValue; - protected override object _Value { get { return Value; } set { Value = (ushort[])value; } } - public new ushort[] Value { get { return mValue; } set { mValue = value; } } + get => mValue; + set => mValue = value; + } - static public implicit operator ushort[](ExifUShortArray obj) { return obj.mValue; } + public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 3, + (uint)mValue.Length, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); - public override string ToString() - { - StringBuilder sb = new StringBuilder(); - sb.Append('['); - foreach (ushort b in mValue) - { - sb.Append(b); - sb.Append(' '); - } - sb.Remove(sb.Length - 1, 1); - sb.Append(']'); - return sb.ToString(); - } + public static implicit operator ushort[](ExifUShortArray obj) => obj.mValue; - public ExifUShortArray(ExifTag tag, ushort[] value) - : base(tag) + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append('['); + foreach (var b in mValue) { - mValue = value; + sb.Append(b); + sb.Append(' '); } - public override ExifInterOperability Interoperability - { - get - { - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 3, (uint)mValue.Length, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); - } - } + sb.Remove(sb.Length - 1, 1); + sb.Append(']'); + return sb.ToString(); } +} - /// - /// Represents a 32-bit unsigned integer. (EXIF Specification: LONG) - /// - internal class ExifUInt : ExifProperty +/// +/// Represents a 32-bit unsigned integer. (EXIF Specification: LONG) +/// +internal class ExifUInt : ExifProperty +{ + protected uint mValue; + + public ExifUInt(ExifTag tag, uint value) + : base(tag) => + mValue = value; + + protected override object _Value { - protected uint mValue; - protected override object _Value { get { return Value; } set { Value = Convert.ToUInt32(value); } } - public new uint Value { get { return mValue; } set { mValue = value; } } + get => Value; + set => Value = Convert.ToUInt32(value); + } - static public implicit operator uint(ExifUInt obj) { return obj.mValue; } + public new uint Value + { + get => mValue; + set => mValue = value; + } - public override string ToString() { return mValue.ToString(); } + public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 4, + 1, BitConverterEx.GetBytes(mValue, BitConverterEx.SystemByteOrder, BitConverterEx.SystemByteOrder)); - public ExifUInt(ExifTag tag, uint value) - : base(tag) - { - mValue = value; - } + public static implicit operator uint(ExifUInt obj) => obj.mValue; - public override ExifInterOperability Interoperability - { - get - { - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 4, 1, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder, BitConverterEx.SystemByteOrder)); - } - } + public override string ToString() => mValue.ToString(); +} + +/// +/// Represents an array of 16-bit unsigned integers. +/// (EXIF Specification: LONG with count > 1) +/// +internal class ExifUIntArray : ExifProperty +{ + protected uint[] mValue; + + public ExifUIntArray(ExifTag tag, uint[] value) + : base(tag) => + mValue = value; + + protected override object _Value + { + get => Value; + set => Value = (uint[])value; } - /// - /// Represents an array of 16-bit unsigned integers. - /// (EXIF Specification: LONG with count > 1) - /// - internal class ExifUIntArray : ExifProperty + public new uint[] Value { - protected uint[] mValue; - protected override object _Value { get { return Value; } set { Value = (uint[])value; } } - public new uint[] Value { get { return mValue; } set { mValue = value; } } + get => mValue; + set => mValue = value; + } - static public implicit operator uint[](ExifUIntArray obj) { return obj.mValue; } + public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 3, + (uint)mValue.Length, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); - public override string ToString() - { - StringBuilder sb = new StringBuilder(); - sb.Append('['); - foreach (uint b in mValue) - { - sb.Append(b); - sb.Append(' '); - } - sb.Remove(sb.Length - 1, 1); - sb.Append(']'); - return sb.ToString(); - } + public static implicit operator uint[](ExifUIntArray obj) => obj.mValue; - public ExifUIntArray(ExifTag tag, uint[] value) - : base(tag) + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append('['); + foreach (var b in mValue) { - mValue = value; + sb.Append(b); + sb.Append(' '); } - public override ExifInterOperability Interoperability - { - get - { - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 3, (uint)mValue.Length, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); - } - } + sb.Remove(sb.Length - 1, 1); + sb.Append(']'); + return sb.ToString(); } +} - /// - /// Represents a rational number defined with a 32-bit unsigned numerator - /// and denominator. (EXIF Specification: RATIONAL) - /// - internal class ExifURational : ExifProperty +/// +/// Represents a rational number defined with a 32-bit unsigned numerator +/// and denominator. (EXIF Specification: RATIONAL) +/// +internal class ExifURational : ExifProperty +{ + protected MathEx.UFraction32 mValue; + + public ExifURational(ExifTag tag, uint numerator, uint denominator) + : base(tag) => + mValue = new MathEx.UFraction32(numerator, denominator); + + public ExifURational(ExifTag tag, MathEx.UFraction32 value) + : base(tag) => + mValue = value; + + protected override object _Value { - protected MathEx.UFraction32 mValue; - protected override object _Value { get { return Value; } set { Value = (MathEx.UFraction32)value; } } - public new MathEx.UFraction32 Value { get { return mValue; } set { mValue = value; } } + get => Value; + set => Value = (MathEx.UFraction32)value; + } - public override string ToString() { return mValue.ToString(); } - public float ToFloat() { return (float)mValue; } + public new MathEx.UFraction32 Value + { + get => mValue; + set => mValue = value; + } - static public explicit operator float(ExifURational obj) { return (float)obj.mValue; } + public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 5, + 1, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); - public uint[] ToArray() - { - return new uint[] { mValue.Numerator, mValue.Denominator }; - } + public override string ToString() => mValue.ToString(); + public float ToFloat() => (float)mValue; - public ExifURational(ExifTag tag, uint numerator, uint denominator) - : base(tag) - { - mValue = new MathEx.UFraction32(numerator, denominator); - } + public static explicit operator float(ExifURational obj) => (float)obj.mValue; - public ExifURational(ExifTag tag, MathEx.UFraction32 value) - : base(tag) - { - mValue = value; - } + public uint[] ToArray() => new[] {mValue.Numerator, mValue.Denominator}; +} - public override ExifInterOperability Interoperability - { - get - { - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 5, 1, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); - } - } +/// +/// Represents an array of unsigned rational numbers. +/// (EXIF Specification: RATIONAL with count > 1) +/// +internal class ExifURationalArray : ExifProperty +{ + protected MathEx.UFraction32[] mValue; + + public ExifURationalArray(ExifTag tag, MathEx.UFraction32[] value) + : base(tag) => + mValue = value; + + protected override object _Value + { + get => Value; + set => Value = (MathEx.UFraction32[])value; } - /// - /// Represents an array of unsigned rational numbers. - /// (EXIF Specification: RATIONAL with count > 1) - /// - internal class ExifURationalArray : ExifProperty + public new MathEx.UFraction32[] Value { - protected MathEx.UFraction32[] mValue; - protected override object _Value { get { return Value; } set { Value = (MathEx.UFraction32[])value; } } - public new MathEx.UFraction32[] Value { get { return mValue; } set { mValue = value; } } + get => mValue; + set => mValue = value; + } - static public explicit operator float[](ExifURationalArray obj) - { - float[] result = new float[obj.mValue.Length]; - for (int i = 0; i < obj.mValue.Length; i++) - result[i] = (float)obj.mValue[i]; - return result; - } + public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 5, + (uint)mValue.Length, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); - public override string ToString() + public static explicit operator float[](ExifURationalArray obj) + { + var result = new float[obj.mValue.Length]; + for (var i = 0; i < obj.mValue.Length; i++) { - StringBuilder sb = new StringBuilder(); - sb.Append('['); - foreach (MathEx.UFraction32 b in mValue) - { - sb.Append(b.ToString()); - sb.Append(' '); - } - sb.Remove(sb.Length - 1, 1); - sb.Append(']'); - return sb.ToString(); + result[i] = (float)obj.mValue[i]; } - public ExifURationalArray(ExifTag tag, MathEx.UFraction32[] value) - : base(tag) - { - mValue = value; - } + return result; + } - public override ExifInterOperability Interoperability + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append('['); + foreach (MathEx.UFraction32 b in mValue) { - get - { - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 5, (uint)mValue.Length, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); - } + sb.Append(b.ToString()); + sb.Append(' '); } + + sb.Remove(sb.Length - 1, 1); + sb.Append(']'); + return sb.ToString(); } +} - /// - /// Represents a byte array that can take any value. (EXIF Specification: UNDEFINED) - /// - internal class ExifUndefined : ExifProperty +/// +/// Represents a byte array that can take any value. (EXIF Specification: UNDEFINED) +/// +internal class ExifUndefined : ExifProperty +{ + protected byte[] mValue; + + public ExifUndefined(ExifTag tag, byte[] value) + : base(tag) => + mValue = value; + + protected override object _Value { - protected byte[] mValue; - protected override object _Value { get { return Value; } set { Value = (byte[])value; } } - public new byte[] Value { get { return mValue; } set { mValue = value; } } + get => Value; + set => Value = (byte[])value; + } - static public implicit operator byte[](ExifUndefined obj) { return obj.mValue; } + public new byte[] Value + { + get => mValue; + set => mValue = value; + } - public override string ToString() - { - StringBuilder sb = new StringBuilder(); - sb.Append('['); - foreach (byte b in mValue) - { - sb.Append(b); - sb.Append(' '); - } - sb.Remove(sb.Length - 1, 1); - sb.Append(']'); - return sb.ToString(); - } + public override ExifInterOperability Interoperability => + new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 7, (uint)mValue.Length, mValue); - public ExifUndefined(ExifTag tag, byte[] value) - : base(tag) - { - mValue = value; - } + public static implicit operator byte[](ExifUndefined obj) => obj.mValue; - public override ExifInterOperability Interoperability + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append('['); + foreach (var b in mValue) { - get - { - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 7, (uint)mValue.Length, mValue); - } + sb.Append(b); + sb.Append(' '); } + + sb.Remove(sb.Length - 1, 1); + sb.Append(']'); + return sb.ToString(); } +} - /// - /// Represents a 32-bit signed integer. (EXIF Specification: SLONG) - /// - internal class ExifSInt : ExifProperty +/// +/// Represents a 32-bit signed integer. (EXIF Specification: SLONG) +/// +internal class ExifSInt : ExifProperty +{ + protected int mValue; + + public ExifSInt(ExifTag tag, int value) + : base(tag) => + mValue = value; + + protected override object _Value { - protected int mValue; - protected override object _Value { get { return Value; } set { Value = Convert.ToInt32(value); } } - public new int Value { get { return mValue; } set { mValue = value; } } + get => Value; + set => Value = Convert.ToInt32(value); + } - public override string ToString() { return mValue.ToString(); } + public new int Value + { + get => mValue; + set => mValue = value; + } - static public implicit operator int(ExifSInt obj) { return obj.mValue; } + public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 9, + 1, BitConverterEx.GetBytes(mValue, BitConverterEx.SystemByteOrder, BitConverterEx.SystemByteOrder)); - public ExifSInt(ExifTag tag, int value) - : base(tag) - { - mValue = value; - } + public override string ToString() => mValue.ToString(); - public override ExifInterOperability Interoperability - { - get - { - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 9, 1, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder, BitConverterEx.SystemByteOrder)); - } - } + public static implicit operator int(ExifSInt obj) => obj.mValue; +} + +/// +/// Represents an array of 32-bit signed integers. +/// (EXIF Specification: SLONG with count > 1) +/// +internal class ExifSIntArray : ExifProperty +{ + protected int[] mValue; + + public ExifSIntArray(ExifTag tag, int[] value) + : base(tag) => + mValue = value; + + protected override object _Value + { + get => Value; + set => Value = (int[])value; } - /// - /// Represents an array of 32-bit signed integers. - /// (EXIF Specification: SLONG with count > 1) - /// - internal class ExifSIntArray : ExifProperty + public new int[] Value { - protected int[] mValue; - protected override object _Value { get { return Value; } set { Value = (int[])value; } } - public new int[] Value { get { return mValue; } set { mValue = value; } } + get => mValue; + set => mValue = value; + } + + public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 9, + (uint)mValue.Length, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); - public override string ToString() + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append('['); + foreach (var b in mValue) { - StringBuilder sb = new StringBuilder(); - sb.Append('['); - foreach (int b in mValue) - { - sb.Append(b); - sb.Append(' '); - } - sb.Remove(sb.Length - 1, 1); - sb.Append(']'); - return sb.ToString(); + sb.Append(b); + sb.Append(' '); } - static public implicit operator int[](ExifSIntArray obj) { return obj.mValue; } + sb.Remove(sb.Length - 1, 1); + sb.Append(']'); + return sb.ToString(); + } - public ExifSIntArray(ExifTag tag, int[] value) - : base(tag) - { - mValue = value; - } + public static implicit operator int[](ExifSIntArray obj) => obj.mValue; +} - public override ExifInterOperability Interoperability - { - get - { - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 9, (uint)mValue.Length, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); - } - } +/// +/// Represents a rational number defined with a 32-bit signed numerator +/// and denominator. (EXIF Specification: SRATIONAL) +/// +internal class ExifSRational : ExifProperty +{ + protected MathEx.Fraction32 mValue; + + public ExifSRational(ExifTag tag, int numerator, int denominator) + : base(tag) => + mValue = new MathEx.Fraction32(numerator, denominator); + + public ExifSRational(ExifTag tag, MathEx.Fraction32 value) + : base(tag) => + mValue = value; + + protected override object _Value + { + get => Value; + set => Value = (MathEx.Fraction32)value; } - /// - /// Represents a rational number defined with a 32-bit signed numerator - /// and denominator. (EXIF Specification: SRATIONAL) - /// - internal class ExifSRational : ExifProperty + public new MathEx.Fraction32 Value { - protected MathEx.Fraction32 mValue; - protected override object _Value { get { return Value; } set { Value = (MathEx.Fraction32)value; } } - public new MathEx.Fraction32 Value { get { return mValue; } set { mValue = value; } } + get => mValue; + set => mValue = value; + } - public override string ToString() { return mValue.ToString(); } - public float ToFloat() { return (float)mValue; } + public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 10, + 1, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); - static public explicit operator float(ExifSRational obj) { return (float)obj.mValue; } + public override string ToString() => mValue.ToString(); + public float ToFloat() => (float)mValue; - public int[] ToArray() - { - return new int[] { mValue.Numerator, mValue.Denominator }; - } + public static explicit operator float(ExifSRational obj) => (float)obj.mValue; - public ExifSRational(ExifTag tag, int numerator, int denominator) - : base(tag) - { - mValue = new MathEx.Fraction32(numerator, denominator); - } + public int[] ToArray() => new[] {mValue.Numerator, mValue.Denominator}; +} - public ExifSRational(ExifTag tag, MathEx.Fraction32 value) - : base(tag) - { - mValue = value; - } +/// +/// Represents an array of signed rational numbers. +/// (EXIF Specification: SRATIONAL with count > 1) +/// +internal class ExifSRationalArray : ExifProperty +{ + protected MathEx.Fraction32[] mValue; - public override ExifInterOperability Interoperability - { - get - { - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 10, 1, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); - } - } + public ExifSRationalArray(ExifTag tag, MathEx.Fraction32[] value) + : base(tag) => + mValue = value; + + protected override object _Value + { + get => Value; + set => Value = (MathEx.Fraction32[])value; } - /// - /// Represents an array of signed rational numbers. - /// (EXIF Specification: SRATIONAL with count > 1) - /// - internal class ExifSRationalArray : ExifProperty + public new MathEx.Fraction32[] Value { - protected MathEx.Fraction32[] mValue; - protected override object _Value { get { return Value; } set { Value = (MathEx.Fraction32[])value; } } - public new MathEx.Fraction32[] Value { get { return mValue; } set { mValue = value; } } + get => mValue; + set => mValue = value; + } - static public explicit operator float[](ExifSRationalArray obj) - { - float[] result = new float[obj.mValue.Length]; - for (int i = 0; i < obj.mValue.Length; i++) - result[i] = (float)obj.mValue[i]; - return result; - } + public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 10, + (uint)mValue.Length, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); - public override string ToString() + public static explicit operator float[](ExifSRationalArray obj) + { + var result = new float[obj.mValue.Length]; + for (var i = 0; i < obj.mValue.Length; i++) { - StringBuilder sb = new StringBuilder(); - sb.Append('['); - foreach (MathEx.Fraction32 b in mValue) - { - sb.Append(b.ToString()); - sb.Append(' '); - } - sb.Remove(sb.Length - 1, 1); - sb.Append(']'); - return sb.ToString(); + result[i] = (float)obj.mValue[i]; } - public ExifSRationalArray(ExifTag tag, MathEx.Fraction32[] value) - : base(tag) - { - mValue = value; - } + return result; + } - public override ExifInterOperability Interoperability + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append('['); + foreach (MathEx.Fraction32 b in mValue) { - get - { - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 10, (uint)mValue.Length, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); - } + sb.Append(b.ToString()); + sb.Append(' '); } + + sb.Remove(sb.Length - 1, 1); + sb.Append(']'); + return sb.ToString(); } } diff --git a/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs b/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs index 7114b2eb1416..fa7cc5b73363 100644 --- a/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs +++ b/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs @@ -1,384 +1,496 @@ -using System; -using System.Collections.Generic; +using System.Collections; using System.Diagnostics.CodeAnalysis; using System.Text; -namespace Umbraco.Cms.Core.Media.Exif +namespace Umbraco.Cms.Core.Media.Exif; + +/// +/// Represents a collection of objects. +/// +internal class ExifPropertyCollection : IDictionary { + #region Constructor + + internal ExifPropertyCollection(ImageFile parentFile) + { + parent = parentFile; + items = new Dictionary(); + } + + #endregion + + #region Member Variables + + private readonly ImageFile parent; + private readonly Dictionary items; + + #endregion + + #region Properties + /// - /// Represents a collection of objects. + /// Gets the number of elements contained in the collection. /// - internal class ExifPropertyCollection : IDictionary - { - #region Member Variables - private ImageFile parent; - private Dictionary items; - #endregion + public int Count => items.Count; - #region Constructor - internal ExifPropertyCollection (ImageFile parentFile) + /// + /// Gets a collection containing the keys in this collection. + /// + public ICollection Keys => items.Keys; + + /// + /// Gets a collection containing the values in this collection. + /// + public ICollection Values => items.Values; + + /// + /// Gets or sets the with the specified key. + /// + public ExifProperty this[ExifTag key] + { + get => items[key]; + set { - parent = parentFile; - items = new Dictionary (); - } - #endregion - - #region Properties - /// - /// Gets the number of elements contained in the collection. - /// - public int Count { - get { return items.Count; } - } - /// - /// Gets a collection containing the keys in this collection. - /// - public ICollection Keys { - get { return items.Keys; } - } - /// - /// Gets a collection containing the values in this collection. - /// - public ICollection Values { - get { return items.Values; } - } - /// - /// Gets or sets the with the specified key. - /// - public ExifProperty this[ExifTag key] { - get { return items[key]; } - set { - if (items.ContainsKey (key)) - items.Remove (key); - items.Add (key, value); + if (items.ContainsKey(key)) + { + items.Remove(key); } + + items.Add(key, value); } - #endregion - - #region ExifProperty Collection Setters - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// The value of tag. - public void Set (ExifTag key, byte value) - { - if (items.ContainsKey (key)) - items.Remove (key); - items.Add (key, new ExifByte (key, value)); - } - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// The value of tag. - public void Set (ExifTag key, string value) + } + + #endregion + + #region ExifProperty Collection Setters + + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// The value of tag. + public void Set(ExifTag key, byte value) + { + if (items.ContainsKey(key)) { - if (items.ContainsKey (key)) - items.Remove (key); - if (key == ExifTag.WindowsTitle || key == ExifTag.WindowsComment || key == ExifTag.WindowsAuthor || key == ExifTag.WindowsKeywords || key == ExifTag.WindowsSubject) { - items.Add (key, new WindowsByteString (key, value)); - } else { - items.Add (key, new ExifAscii (key, value, parent.Encoding)); - } + items.Remove(key); } - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// The value of tag. - public void Set (ExifTag key, ushort value) + + items.Add(key, new ExifByte(key, value)); + } + + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// The value of tag. + public void Set(ExifTag key, string value) + { + if (items.ContainsKey(key)) { - if (items.ContainsKey (key)) - items.Remove (key); - items.Add (key, new ExifUShort (key, value)); + items.Remove(key); } - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// The value of tag. - public void Set (ExifTag key, int value) + + if (key == ExifTag.WindowsTitle || key == ExifTag.WindowsComment || key == ExifTag.WindowsAuthor || + key == ExifTag.WindowsKeywords || key == ExifTag.WindowsSubject) { - if (items.ContainsKey (key)) - items.Remove (key); - items.Add (key, new ExifSInt (key, value)); + items.Add(key, new WindowsByteString(key, value)); } - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// The value of tag. - public void Set (ExifTag key, uint value) + else { - if (items.ContainsKey (key)) - items.Remove (key); - items.Add (key, new ExifUInt (key, value)); + items.Add(key, new ExifAscii(key, value, parent.Encoding)); } - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// The value of tag. - public void Set (ExifTag key, float value) + } + + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// The value of tag. + public void Set(ExifTag key, ushort value) + { + if (items.ContainsKey(key)) { - if (items.ContainsKey (key)) - items.Remove (key); - items.Add (key, new ExifURational (key, new MathEx.UFraction32 (value))); + items.Remove(key); } - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// The value of tag. - public void Set (ExifTag key, double value) + + items.Add(key, new ExifUShort(key, value)); + } + + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// The value of tag. + public void Set(ExifTag key, int value) + { + if (items.ContainsKey(key)) { - if (items.ContainsKey (key)) - items.Remove (key); - items.Add (key, new ExifURational (key, new MathEx.UFraction32 (value))); + items.Remove(key); } - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// The value of tag. - public void Set (ExifTag key, object value) + + items.Add(key, new ExifSInt(key, value)); + } + + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// The value of tag. + public void Set(ExifTag key, uint value) + { + if (items.ContainsKey(key)) { - Type type = value.GetType (); - if (type.IsEnum) { - Type etype = typeof(ExifEnumProperty<>).MakeGenericType (new Type[] { type }); - object? prop = Activator.CreateInstance (etype, new object[] { key, value }); - if (items.ContainsKey (key)) - items.Remove (key); - if (prop is ExifProperty exifProperty) - { - items.Add (key, exifProperty); - } - } else - throw new ArgumentException ("No exif property exists for this tag.", "value"); + items.Remove(key); } - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// The value of tag. - /// String encoding. - public void Set (ExifTag key, string value, Encoding encoding) + + items.Add(key, new ExifUInt(key, value)); + } + + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// The value of tag. + public void Set(ExifTag key, float value) + { + if (items.ContainsKey(key)) { - if (items.ContainsKey (key)) - items.Remove (key); - items.Add (key, new ExifEncodedString (key, value, encoding)); + items.Remove(key); } - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// The value of tag. - public void Set (ExifTag key, DateTime value) + + items.Add(key, new ExifURational(key, new MathEx.UFraction32(value))); + } + + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// The value of tag. + public void Set(ExifTag key, double value) + { + if (items.ContainsKey(key)) { - if (items.ContainsKey (key)) - items.Remove (key); - items.Add (key, new ExifDateTime (key, value)); + items.Remove(key); } - /// - /// Sets the with the specified key. - /// - /// The tag to set. - /// Angular degrees (or clock hours for a timestamp). - /// Angular minutes (or clock minutes for a timestamp). - /// Angular seconds (or clock seconds for a timestamp). - public void Set (ExifTag key, float d, float m, float s) + + items.Add(key, new ExifURational(key, new MathEx.UFraction32(value))); + } + + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// The value of tag. + public void Set(ExifTag key, object value) + { + Type type = value.GetType(); + if (type.IsEnum) { - if (items.ContainsKey (key)) - items.Remove (key); - items.Add (key, new ExifURationalArray (key, new MathEx.UFraction32[] { new MathEx.UFraction32 (d), new MathEx.UFraction32 (m), new MathEx.UFraction32 (s) })); + Type etype = typeof(ExifEnumProperty<>).MakeGenericType(type); + var prop = Activator.CreateInstance(etype, key, value); + if (items.ContainsKey(key)) + { + items.Remove(key); + } + + if (prop is ExifProperty exifProperty) + { + items.Add(key, exifProperty); + } } - #endregion - - #region Instance Methods - /// - /// Adds the specified item to the collection. - /// - /// The to add to the collection. - public void Add (ExifProperty item) + else { - ExifProperty? oldItem = null; - if (items.TryGetValue (item.Tag, out oldItem)) - items[item.Tag] = item; - else - items.Add (item.Tag, item); + throw new ArgumentException("No exif property exists for this tag.", "value"); } - /// - /// Removes all items from the collection. - /// - public void Clear () + } + + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// The value of tag. + /// String encoding. + public void Set(ExifTag key, string value, Encoding encoding) + { + if (items.ContainsKey(key)) { - items.Clear (); + items.Remove(key); } - /// - /// Determines whether the collection contains an element with the specified key. - /// - /// The key to locate in the collection. - /// - /// true if the collection contains an element with the key; otherwise, false. - /// - /// - /// is null. - public bool ContainsKey (ExifTag key) + + items.Add(key, new ExifEncodedString(key, value, encoding)); + } + + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// The value of tag. + public void Set(ExifTag key, DateTime value) + { + if (items.ContainsKey(key)) { - return items.ContainsKey (key); + items.Remove(key); } - /// - /// Removes the element with the specified key from the collection. - /// - /// The key of the element to remove. - /// - /// true if the element is successfully removed; otherwise, false. This method also returns false if was not found in the original collection. - /// - /// - /// is null. - public bool Remove (ExifTag key) + + items.Add(key, new ExifDateTime(key, value)); + } + + /// + /// Sets the with the specified key. + /// + /// The tag to set. + /// Angular degrees (or clock hours for a timestamp). + /// Angular minutes (or clock minutes for a timestamp). + /// Angular seconds (or clock seconds for a timestamp). + public void Set(ExifTag key, float d, float m, float s) + { + if (items.ContainsKey(key)) { - return items.Remove (key); + items.Remove(key); } - /// - /// Removes all items with the given IFD from the collection. - /// - /// The IFD section to remove. - public void Remove (IFD ifd) + + items.Add(key, + new ExifURationalArray(key, new[] {new(d), new MathEx.UFraction32(m), new MathEx.UFraction32(s)})); + } + + #endregion + + #region Instance Methods + + /// + /// Adds the specified item to the collection. + /// + /// The to add to the collection. + public void Add(ExifProperty item) + { + ExifProperty? oldItem = null; + if (items.TryGetValue(item.Tag, out oldItem)) { - List toRemove = new List (); - foreach (KeyValuePair item in items) { - if (item.Value.IFD == ifd) - toRemove.Add (item.Key); - } - foreach (ExifTag tag in toRemove) - items.Remove (tag); + items[item.Tag] = item; } - /// - /// Gets the value associated with the specified key. - /// - /// The key whose value to get. - /// When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type of the parameter. This parameter is passed uninitialized. - /// - /// true if the collection contains an element with the specified key; otherwise, false. - /// - /// - /// is null. - public bool TryGetValue (ExifTag key, [MaybeNullWhen(false)] out ExifProperty value) + else { - return items.TryGetValue (key, out value); + items.Add(item.Tag, item); } - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - public IEnumerator GetEnumerator () + } + + /// + /// Removes all items from the collection. + /// + public void Clear() => items.Clear(); + + /// + /// Determines whether the collection contains an element with the specified key. + /// + /// The key to locate in the collection. + /// + /// true if the collection contains an element with the key; otherwise, false. + /// + /// + /// is null. + /// + public bool ContainsKey(ExifTag key) => items.ContainsKey(key); + + /// + /// Removes the element with the specified key from the collection. + /// + /// The key of the element to remove. + /// + /// true if the element is successfully removed; otherwise, false. This method also returns false if + /// was not found in the original collection. + /// + /// + /// is null. + /// + public bool Remove(ExifTag key) => items.Remove(key); + + /// + /// Removes all items with the given IFD from the collection. + /// + /// The IFD section to remove. + public void Remove(IFD ifd) + { + var toRemove = new List(); + foreach (KeyValuePair item in items) { - return Values.GetEnumerator (); + if (item.Value.IFD == ifd) + { + toRemove.Add(item.Key); + } } - #endregion - - #region Hidden Interface - /// - /// Adds an element with the provided key and value to the . - /// - /// The object to use as the key of the element to add. - /// The object to use as the value of the element to add. - /// - /// is null. - /// An element with the same key already exists in the . - /// The is read-only. - void IDictionary.Add (ExifTag key, ExifProperty value) + + foreach (ExifTag tag in toRemove) { - Add (value); + items.Remove(tag); } - /// - /// Adds an item to the . - /// - /// The object to add to the . - /// The is read-only. - void ICollection>.Add (KeyValuePair item) + } + + /// + /// Gets the value associated with the specified key. + /// + /// The key whose value to get. + /// + /// When this method returns, the value associated with the specified key, if the key is found; + /// otherwise, the default value for the type of the parameter. This parameter is passed + /// uninitialized. + /// + /// + /// true if the collection contains an element with the specified key; otherwise, false. + /// + /// + /// is null. + /// + public bool TryGetValue(ExifTag key, [MaybeNullWhen(false)] out ExifProperty value) => + items.TryGetValue(key, out value); + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + public IEnumerator GetEnumerator() => Values.GetEnumerator(); + + #endregion + + #region Hidden Interface + + /// + /// Adds an element with the provided key and value to the . + /// + /// The object to use as the key of the element to add. + /// The object to use as the value of the element to add. + /// + /// is null. + /// + /// + /// An element with the same key already exists in the + /// . + /// + /// + /// The is + /// read-only. + /// + void IDictionary.Add(ExifTag key, ExifProperty value) => Add(value); + + /// + /// Adds an item to the . + /// + /// The object to add to the . + /// + /// The is + /// read-only. + /// + void ICollection>.Add(KeyValuePair item) => + Add(item.Value); + + bool ICollection>.Contains(KeyValuePair item) => + throw new NotSupportedException(); + + /// + /// Copies the elements of the to an + /// , starting at a particular index. + /// + /// + /// The one-dimensional that is the destination of the elements copied + /// from . The must have + /// zero-based indexing. + /// + /// The zero-based index in at which copying begins. + /// + /// is null. + /// + /// + /// is less than 0. + /// + /// + /// is multidimensional.-or- is equal to or greater than the + /// length of .-or-The number of elements in the source + /// is greater than the available space from + /// to the end of the destination .-or-Type + /// cannot be cast automatically to the type of the destination . + /// + void ICollection>.CopyTo(KeyValuePair[] array, + int arrayIndex) + { + if (array == null) { - Add (item.Value); + throw new ArgumentNullException("array"); } - bool ICollection>.Contains (KeyValuePair item) + + if (arrayIndex < 0) { - throw new NotSupportedException (); + throw new ArgumentOutOfRangeException("arrayIndex"); } - /// - /// Copies the elements of the to an , starting at a particular index. - /// - /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. - /// The zero-based index in at which copying begins. - /// - /// is null. - /// - /// is less than 0. - /// - /// is multidimensional.-or- is equal to or greater than the length of .-or-The number of elements in the source is greater than the available space from to the end of the destination .-or-Type cannot be cast automatically to the type of the destination . - void ICollection>.CopyTo (KeyValuePair[] array, int arrayIndex) + + if (array.Rank > 1) { - if (array == null) - throw new ArgumentNullException ("array"); - if (arrayIndex < 0) - throw new ArgumentOutOfRangeException ("arrayIndex"); - if (array.Rank > 1) - throw new ArgumentException ("Destination array is multidimensional.", "array"); - if (arrayIndex >= array.Length) - throw new ArgumentException ("arrayIndex is equal to or greater than the length of destination array", "array"); - if (arrayIndex + items.Count > array.Length) - throw new ArgumentException ("There is not enough space in destination array.", "array"); - - int i = 0; - foreach (KeyValuePair item in items) { - if (i >= arrayIndex) { - array[i] = item; - } - i++; - } - } - /// - /// Gets a value indicating whether the is read-only. - /// - /// true if the is read-only; otherwise, false. - bool ICollection>.IsReadOnly { - get { return false; } + throw new ArgumentException("Destination array is multidimensional.", "array"); } - /// - /// Removes the first occurrence of a specific object from the . - /// - /// The object to remove from the . - /// - /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . - /// - /// The is read-only. - bool ICollection>.Remove (KeyValuePair item) + + if (arrayIndex >= array.Length) { - throw new NotSupportedException (); + throw new ArgumentException("arrayIndex is equal to or greater than the length of destination array", + "array"); } - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () + + if (arrayIndex + items.Count > array.Length) { - return GetEnumerator (); + throw new ArgumentException("There is not enough space in destination array.", "array"); } - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// A that can be used to iterate through the collection. - /// - IEnumerator> IEnumerable>.GetEnumerator () + + var i = 0; + foreach (KeyValuePair item in items) { - return items.GetEnumerator (); + if (i >= arrayIndex) + { + array[i] = item; + } + + i++; } - #endregion } + + /// + /// Gets a value indicating whether the is read-only. + /// + /// true if the is read-only; otherwise, false. + bool ICollection>.IsReadOnly => false; + + /// + /// Removes the first occurrence of a specific object from the + /// . + /// + /// The object to remove from the . + /// + /// true if was successfully removed from the + /// ; otherwise, false. This method also returns false if + /// is not found in the original . + /// + /// + /// The is + /// read-only. + /// + bool ICollection>.Remove(KeyValuePair item) => + throw new NotSupportedException(); + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// A that can be used to iterate through the collection. + /// + IEnumerator> IEnumerable>.GetEnumerator() => + items.GetEnumerator(); + + #endregion } diff --git a/src/Umbraco.Core/Media/Exif/ExifPropertyFactory.cs b/src/Umbraco.Core/Media/Exif/ExifPropertyFactory.cs index f47cab1c35bd..39edcaee7af3 100644 --- a/src/Umbraco.Core/Media/Exif/ExifPropertyFactory.cs +++ b/src/Umbraco.Core/Media/Exif/ExifPropertyFactory.cs @@ -1,253 +1,500 @@ -using System; -using System.Text; +using System.Text; -namespace Umbraco.Cms.Core.Media.Exif +namespace Umbraco.Cms.Core.Media.Exif; + +/// +/// Creates exif properties from interoperability parameters. +/// +internal static class ExifPropertyFactory { + #region Static Methods + /// - /// Creates exif properties from interoperability parameters. + /// Creates an ExifProperty from the given interoperability parameters. /// - internal static class ExifPropertyFactory + /// The tag id of the exif property. + /// The type id of the exif property. + /// Byte or component count. + /// Field data as an array of bytes. + /// Byte order of value. + /// IFD section containing this property. + /// The encoding to be used for text metadata when the source encoding is unknown. + /// an ExifProperty initialized from the interoperability parameters. + public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value, + BitConverterEx.ByteOrder byteOrder, IFD ifd, Encoding encoding) { - #region Static Methods - /// - /// Creates an ExifProperty from the given interoperability parameters. - /// - /// The tag id of the exif property. - /// The type id of the exif property. - /// Byte or component count. - /// Field data as an array of bytes. - /// Byte order of value. - /// IFD section containing this property. - /// The encoding to be used for text metadata when the source encoding is unknown. - /// an ExifProperty initialized from the interoperability parameters. - public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value, BitConverterEx.ByteOrder byteOrder, IFD ifd, Encoding encoding) + var conv = new BitConverterEx(byteOrder, BitConverterEx.SystemByteOrder); + // Find the exif tag corresponding to given tag id + ExifTag etag = ExifTagFactory.GetExifTag(ifd, tag); + + if (ifd == IFD.Zeroth) { - BitConverterEx conv = new BitConverterEx(byteOrder, BitConverterEx.SystemByteOrder); - // Find the exif tag corresponding to given tag id - ExifTag etag = ExifTagFactory.GetExifTag(ifd, tag); - - if (ifd == IFD.Zeroth) - { - if (tag == 0x103) // Compression - return new ExifEnumProperty(ExifTag.Compression, (Compression)conv.ToUInt16(value, 0)); - else if (tag == 0x106) // PhotometricInterpretation - return new ExifEnumProperty(ExifTag.PhotometricInterpretation, (PhotometricInterpretation)conv.ToUInt16(value, 0)); - else if (tag == 0x112) // Orientation - return new ExifEnumProperty(ExifTag.Orientation, (Orientation)conv.ToUInt16(value, 0)); - else if (tag == 0x11c) // PlanarConfiguration - return new ExifEnumProperty(ExifTag.PlanarConfiguration, (PlanarConfiguration)conv.ToUInt16(value, 0)); - else if (tag == 0x213) // YCbCrPositioning - return new ExifEnumProperty(ExifTag.YCbCrPositioning, (YCbCrPositioning)conv.ToUInt16(value, 0)); - else if (tag == 0x128) // ResolutionUnit - return new ExifEnumProperty(ExifTag.ResolutionUnit, (ResolutionUnit)conv.ToUInt16(value, 0)); - else if (tag == 0x132) // DateTime - return new ExifDateTime(ExifTag.DateTime, ExifBitConverter.ToDateTime(value)); - else if (tag == 0x9c9b || tag == 0x9c9c || // Windows tags - tag == 0x9c9d || tag == 0x9c9e || tag == 0x9c9f) - return new WindowsByteString(etag, Encoding.Unicode.GetString(value).TrimEnd(Constants.CharArrays.NullTerminator)); - } - else if (ifd == IFD.EXIF) - { - if (tag == 0x9000) // ExifVersion - return new ExifVersion(ExifTag.ExifVersion, ExifBitConverter.ToAscii(value, Encoding.ASCII)); - else if (tag == 0xa000) // FlashpixVersion - return new ExifVersion(ExifTag.FlashpixVersion, ExifBitConverter.ToAscii(value, Encoding.ASCII)); - else if (tag == 0xa001) // ColorSpace - return new ExifEnumProperty(ExifTag.ColorSpace, (ColorSpace)conv.ToUInt16(value, 0)); - else if (tag == 0x9286) // UserComment + if (tag == 0x103) // Compression + { + return new ExifEnumProperty(ExifTag.Compression, (Compression)conv.ToUInt16(value, 0)); + } + + if (tag == 0x106) // PhotometricInterpretation + { + return new ExifEnumProperty(ExifTag.PhotometricInterpretation, + (PhotometricInterpretation)conv.ToUInt16(value, 0)); + } + + if (tag == 0x112) // Orientation + { + return new ExifEnumProperty(ExifTag.Orientation, (Orientation)conv.ToUInt16(value, 0)); + } + + if (tag == 0x11c) // PlanarConfiguration + { + return new ExifEnumProperty(ExifTag.PlanarConfiguration, + (PlanarConfiguration)conv.ToUInt16(value, 0)); + } + + if (tag == 0x213) // YCbCrPositioning + { + return new ExifEnumProperty(ExifTag.YCbCrPositioning, + (YCbCrPositioning)conv.ToUInt16(value, 0)); + } + + if (tag == 0x128) // ResolutionUnit + { + return new ExifEnumProperty(ExifTag.ResolutionUnit, + (ResolutionUnit)conv.ToUInt16(value, 0)); + } + + if (tag == 0x132) // DateTime + { + return new ExifDateTime(ExifTag.DateTime, ExifBitConverter.ToDateTime(value)); + } + + if (tag == 0x9c9b || tag == 0x9c9c || // Windows tags + tag == 0x9c9d || tag == 0x9c9e || tag == 0x9c9f) + { + return new WindowsByteString(etag, + Encoding.Unicode.GetString(value).TrimEnd(Constants.CharArrays.NullTerminator)); + } + } + else if (ifd == IFD.EXIF) + { + if (tag == 0x9000) // ExifVersion + { + return new ExifVersion(ExifTag.ExifVersion, ExifBitConverter.ToAscii(value, Encoding.ASCII)); + } + + if (tag == 0xa000) // FlashpixVersion + { + return new ExifVersion(ExifTag.FlashpixVersion, ExifBitConverter.ToAscii(value, Encoding.ASCII)); + } + + if (tag == 0xa001) // ColorSpace + { + return new ExifEnumProperty(ExifTag.ColorSpace, (ColorSpace)conv.ToUInt16(value, 0)); + } + + if (tag == 0x9286) // UserComment + { + // Default to ASCII + Encoding enc = Encoding.ASCII; + bool hasenc; + if (value.Length < 8) { - // Default to ASCII - Encoding enc = Encoding.ASCII; - bool hasenc; - if (value.Length < 8) - hasenc = false; + hasenc = false; + } + else + { + hasenc = true; + var encstr = enc.GetString(value, 0, 8); + if (string.Compare(encstr, "ASCII\0\0\0", StringComparison.OrdinalIgnoreCase) == 0) + { + enc = Encoding.ASCII; + } + else if (string.Compare(encstr, "JIS\0\0\0\0\0", StringComparison.OrdinalIgnoreCase) == 0) + { + enc = Encoding.GetEncoding("Japanese (JIS 0208-1990 and 0212-1990)"); + } + else if (string.Compare(encstr, "Unicode\0", StringComparison.OrdinalIgnoreCase) == 0) + { + enc = Encoding.Unicode; + } else { - hasenc = true; - string encstr = enc.GetString(value, 0, 8); - if (string.Compare(encstr, "ASCII\0\0\0", StringComparison.OrdinalIgnoreCase) == 0) - enc = Encoding.ASCII; - else if (string.Compare(encstr, "JIS\0\0\0\0\0", StringComparison.OrdinalIgnoreCase) == 0) - enc = Encoding.GetEncoding("Japanese (JIS 0208-1990 and 0212-1990)"); - else if (string.Compare(encstr, "Unicode\0", StringComparison.OrdinalIgnoreCase) == 0) - enc = Encoding.Unicode; - else - hasenc = false; + hasenc = false; } + } + + var val = (hasenc ? enc.GetString(value, 8, value.Length - 8) : enc.GetString(value)).Trim( + Constants.CharArrays.NullTerminator); + + return new ExifEncodedString(ExifTag.UserComment, val, enc); + } + + if (tag == 0x9003) // DateTimeOriginal + { + return new ExifDateTime(ExifTag.DateTimeOriginal, ExifBitConverter.ToDateTime(value)); + } + + if (tag == 0x9004) // DateTimeDigitized + { + return new ExifDateTime(ExifTag.DateTimeDigitized, ExifBitConverter.ToDateTime(value)); + } + + if (tag == 0x8822) // ExposureProgram + { + return new ExifEnumProperty(ExifTag.ExposureProgram, + (ExposureProgram)conv.ToUInt16(value, 0)); + } + + if (tag == 0x9207) // MeteringMode + { + return new ExifEnumProperty(ExifTag.MeteringMode, (MeteringMode)conv.ToUInt16(value, 0)); + } - string val = (hasenc ? enc.GetString(value, 8, value.Length - 8) : enc.GetString(value)).Trim(Constants.CharArrays.NullTerminator); + if (tag == 0x9208) // LightSource + { + return new ExifEnumProperty(ExifTag.LightSource, (LightSource)conv.ToUInt16(value, 0)); + } + + if (tag == 0x9209) // Flash + { + return new ExifEnumProperty(ExifTag.Flash, (Flash)conv.ToUInt16(value, 0), true); + } - return new ExifEncodedString(ExifTag.UserComment, val, enc); + if (tag == 0x9214) // SubjectArea + { + if (count == 3) + { + return new ExifCircularSubjectArea(ExifTag.SubjectArea, + ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); } - else if (tag == 0x9003) // DateTimeOriginal - return new ExifDateTime(ExifTag.DateTimeOriginal, ExifBitConverter.ToDateTime(value)); - else if (tag == 0x9004) // DateTimeDigitized - return new ExifDateTime(ExifTag.DateTimeDigitized, ExifBitConverter.ToDateTime(value)); - else if (tag == 0x8822) // ExposureProgram - return new ExifEnumProperty(ExifTag.ExposureProgram, (ExposureProgram)conv.ToUInt16(value, 0)); - else if (tag == 0x9207) // MeteringMode - return new ExifEnumProperty(ExifTag.MeteringMode, (MeteringMode)conv.ToUInt16(value, 0)); - else if (tag == 0x9208) // LightSource - return new ExifEnumProperty(ExifTag.LightSource, (LightSource)conv.ToUInt16(value, 0)); - else if (tag == 0x9209) // Flash - return new ExifEnumProperty(ExifTag.Flash, (Flash)conv.ToUInt16(value, 0), true); - else if (tag == 0x9214) // SubjectArea + + if (count == 4) { - if (count == 3) - return new ExifCircularSubjectArea(ExifTag.SubjectArea, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); - else if (count == 4) - return new ExifRectangularSubjectArea(ExifTag.SubjectArea, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); - else // count == 2 - return new ExifPointSubjectArea(ExifTag.SubjectArea, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); + return new ExifRectangularSubjectArea(ExifTag.SubjectArea, + ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); } - else if (tag == 0xa210) // FocalPlaneResolutionUnit - return new ExifEnumProperty(ExifTag.FocalPlaneResolutionUnit, (ResolutionUnit)conv.ToUInt16(value, 0), true); - else if (tag == 0xa214) // SubjectLocation - return new ExifPointSubjectArea(ExifTag.SubjectLocation, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); - else if (tag == 0xa217) // SensingMethod - return new ExifEnumProperty(ExifTag.SensingMethod, (SensingMethod)conv.ToUInt16(value, 0), true); - else if (tag == 0xa300) // FileSource - return new ExifEnumProperty(ExifTag.FileSource, (FileSource)conv.ToUInt16(value, 0), true); - else if (tag == 0xa301) // SceneType - return new ExifEnumProperty(ExifTag.SceneType, (SceneType)conv.ToUInt16(value, 0), true); - else if (tag == 0xa401) // CustomRendered - return new ExifEnumProperty(ExifTag.CustomRendered, (CustomRendered)conv.ToUInt16(value, 0), true); - else if (tag == 0xa402) // ExposureMode - return new ExifEnumProperty(ExifTag.ExposureMode, (ExposureMode)conv.ToUInt16(value, 0), true); - else if (tag == 0xa403) // WhiteBalance - return new ExifEnumProperty(ExifTag.WhiteBalance, (WhiteBalance)conv.ToUInt16(value, 0), true); - else if (tag == 0xa406) // SceneCaptureType - return new ExifEnumProperty(ExifTag.SceneCaptureType, (SceneCaptureType)conv.ToUInt16(value, 0), true); - else if (tag == 0xa407) // GainControl - return new ExifEnumProperty(ExifTag.GainControl, (GainControl)conv.ToUInt16(value, 0), true); - else if (tag == 0xa408) // Contrast - return new ExifEnumProperty(ExifTag.Contrast, (Contrast)conv.ToUInt16(value, 0), true); - else if (tag == 0xa409) // Saturation - return new ExifEnumProperty(ExifTag.Saturation, (Saturation)conv.ToUInt16(value, 0), true); - else if (tag == 0xa40a) // Sharpness - return new ExifEnumProperty(ExifTag.Sharpness, (Sharpness)conv.ToUInt16(value, 0), true); - else if (tag == 0xa40c) // SubjectDistanceRange - return new ExifEnumProperty(ExifTag.SubjectDistanceRange, (SubjectDistanceRange)conv.ToUInt16(value, 0), true); - } - else if (ifd == IFD.GPS) - { - if (tag == 0) // GPSVersionID - return new ExifVersion(ExifTag.GPSVersionID, ExifBitConverter.ToString(value)); - else if (tag == 1) // GPSLatitudeRef - return new ExifEnumProperty(ExifTag.GPSLatitudeRef, (GPSLatitudeRef)value[0]); - else if (tag == 2) // GPSLatitude - return new GPSLatitudeLongitude(ExifTag.GPSLatitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); - else if (tag == 3) // GPSLongitudeRef - return new ExifEnumProperty(ExifTag.GPSLongitudeRef, (GPSLongitudeRef)value[0]); - else if (tag == 4) // GPSLongitude - return new GPSLatitudeLongitude(ExifTag.GPSLongitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); - else if (tag == 5) // GPSAltitudeRef - return new ExifEnumProperty(ExifTag.GPSAltitudeRef, (GPSAltitudeRef)value[0]); - else if (tag == 7) // GPSTimeStamp - return new GPSTimeStamp(ExifTag.GPSTimeStamp, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); - else if (tag == 9) // GPSStatus - return new ExifEnumProperty(ExifTag.GPSStatus, (GPSStatus)value[0]); - else if (tag == 10) // GPSMeasureMode - return new ExifEnumProperty(ExifTag.GPSMeasureMode, (GPSMeasureMode)value[0]); - else if (tag == 12) // GPSSpeedRef - return new ExifEnumProperty(ExifTag.GPSSpeedRef, (GPSSpeedRef)value[0]); - else if (tag == 14) // GPSTrackRef - return new ExifEnumProperty(ExifTag.GPSTrackRef, (GPSDirectionRef)value[0]); - else if (tag == 16) // GPSImgDirectionRef - return new ExifEnumProperty(ExifTag.GPSImgDirectionRef, (GPSDirectionRef)value[0]); - else if (tag == 19) // GPSDestLatitudeRef - return new ExifEnumProperty(ExifTag.GPSDestLatitudeRef, (GPSLatitudeRef)value[0]); - else if (tag == 20) // GPSDestLatitude - return new GPSLatitudeLongitude(ExifTag.GPSDestLatitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); - else if (tag == 21) // GPSDestLongitudeRef - return new ExifEnumProperty(ExifTag.GPSDestLongitudeRef, (GPSLongitudeRef)value[0]); - else if (tag == 22) // GPSDestLongitude - return new GPSLatitudeLongitude(ExifTag.GPSDestLongitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); - else if (tag == 23) // GPSDestBearingRef - return new ExifEnumProperty(ExifTag.GPSDestBearingRef, (GPSDirectionRef)value[0]); - else if (tag == 25) // GPSDestDistanceRef - return new ExifEnumProperty(ExifTag.GPSDestDistanceRef, (GPSDistanceRef)value[0]); - else if (tag == 29) // GPSDate - return new ExifDateTime(ExifTag.GPSDateStamp, ExifBitConverter.ToDateTime(value, false)); - else if (tag == 30) // GPSDifferential - return new ExifEnumProperty(ExifTag.GPSDifferential, (GPSDifferential)conv.ToUInt16(value, 0)); - } - else if (ifd == IFD.Interop) - { - if (tag == 1) // InteroperabilityIndex - return new ExifAscii(ExifTag.InteroperabilityIndex, ExifBitConverter.ToAscii(value, Encoding.ASCII), Encoding.ASCII); - else if (tag == 2) // InteroperabilityVersion - return new ExifVersion(ExifTag.InteroperabilityVersion, ExifBitConverter.ToAscii(value, Encoding.ASCII)); - } - else if (ifd == IFD.First) - { - if (tag == 0x103) // Compression - return new ExifEnumProperty(ExifTag.ThumbnailCompression, (Compression)conv.ToUInt16(value, 0)); - else if (tag == 0x106) // PhotometricInterpretation - return new ExifEnumProperty(ExifTag.ThumbnailPhotometricInterpretation, (PhotometricInterpretation)conv.ToUInt16(value, 0)); - else if (tag == 0x112) // Orientation - return new ExifEnumProperty(ExifTag.ThumbnailOrientation, (Orientation)conv.ToUInt16(value, 0)); - else if (tag == 0x11c) // PlanarConfiguration - return new ExifEnumProperty(ExifTag.ThumbnailPlanarConfiguration, (PlanarConfiguration)conv.ToUInt16(value, 0)); - else if (tag == 0x213) // YCbCrPositioning - return new ExifEnumProperty(ExifTag.ThumbnailYCbCrPositioning, (YCbCrPositioning)conv.ToUInt16(value, 0)); - else if (tag == 0x128) // ResolutionUnit - return new ExifEnumProperty(ExifTag.ThumbnailResolutionUnit, (ResolutionUnit)conv.ToUInt16(value, 0)); - else if (tag == 0x132) // DateTime - return new ExifDateTime(ExifTag.ThumbnailDateTime, ExifBitConverter.ToDateTime(value)); - } - - if (type == 1) // 1 = BYTE An 8-bit unsigned integer. - { - if (count == 1) - return new ExifByte(etag, value[0]); - else - return new ExifByteArray(etag, value); + + return new ExifPointSubjectArea(ExifTag.SubjectArea, + ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); } - else if (type == 2) // 2 = ASCII An 8-bit byte containing one 7-bit ASCII code. + + if (tag == 0xa210) // FocalPlaneResolutionUnit { - return new ExifAscii(etag, ExifBitConverter.ToAscii(value, encoding), encoding); + return new ExifEnumProperty(ExifTag.FocalPlaneResolutionUnit, + (ResolutionUnit)conv.ToUInt16(value, 0), true); } - else if (type == 3) // 3 = SHORT A 16-bit (2-byte) unsigned integer. + + if (tag == 0xa214) // SubjectLocation { - if (count == 1) - return new ExifUShort(etag, conv.ToUInt16(value, 0)); - else - return new ExifUShortArray(etag, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); + return new ExifPointSubjectArea(ExifTag.SubjectLocation, + ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); } - else if (type == 4) // 4 = LONG A 32-bit (4-byte) unsigned integer. + + if (tag == 0xa217) // SensingMethod { - if (count == 1) - return new ExifUInt(etag, conv.ToUInt32(value, 0)); - else - return new ExifUIntArray(etag, ExifBitConverter.ToUIntArray(value, (int)count, byteOrder)); + return new ExifEnumProperty(ExifTag.SensingMethod, + (SensingMethod)conv.ToUInt16(value, 0), true); } - else if (type == 5) // 5 = RATIONAL Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator. + + if (tag == 0xa300) // FileSource { - if (count == 1) - return new ExifURational(etag, ExifBitConverter.ToURational(value, byteOrder)); - else - return new ExifURationalArray(etag, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); + return new ExifEnumProperty(ExifTag.FileSource, (FileSource)conv.ToUInt16(value, 0), true); } - else if (type == 7) // 7 = UNDEFINED An 8-bit byte that can take any value depending on the field definition. - return new ExifUndefined(etag, value); - else if (type == 9) // 9 = SLONG A 32-bit (4-byte) signed integer (2's complement notation). + + if (tag == 0xa301) // SceneType { - if (count == 1) - return new ExifSInt(etag, conv.ToInt32(value, 0)); - else - return new ExifSIntArray(etag, ExifBitConverter.ToSIntArray(value, (int)count, byteOrder)); + return new ExifEnumProperty(ExifTag.SceneType, (SceneType)conv.ToUInt16(value, 0), true); } - else if (type == 10) // 10 = SRATIONAL Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator. + + if (tag == 0xa401) // CustomRendered { - if (count == 1) - return new ExifSRational(etag, ExifBitConverter.ToSRational(value, byteOrder)); - else - return new ExifSRationalArray(etag, ExifBitConverter.ToSRationalArray(value, (int)count, byteOrder)); + return new ExifEnumProperty(ExifTag.CustomRendered, + (CustomRendered)conv.ToUInt16(value, 0), true); + } + + if (tag == 0xa402) // ExposureMode + { + return new ExifEnumProperty(ExifTag.ExposureMode, (ExposureMode)conv.ToUInt16(value, 0), + true); + } + + if (tag == 0xa403) // WhiteBalance + { + return new ExifEnumProperty(ExifTag.WhiteBalance, (WhiteBalance)conv.ToUInt16(value, 0), + true); + } + + if (tag == 0xa406) // SceneCaptureType + { + return new ExifEnumProperty(ExifTag.SceneCaptureType, + (SceneCaptureType)conv.ToUInt16(value, 0), true); + } + + if (tag == 0xa407) // GainControl + { + return new ExifEnumProperty(ExifTag.GainControl, (GainControl)conv.ToUInt16(value, 0), + true); + } + + if (tag == 0xa408) // Contrast + { + return new ExifEnumProperty(ExifTag.Contrast, (Contrast)conv.ToUInt16(value, 0), true); + } + + if (tag == 0xa409) // Saturation + { + return new ExifEnumProperty(ExifTag.Saturation, (Saturation)conv.ToUInt16(value, 0), true); + } + + if (tag == 0xa40a) // Sharpness + { + return new ExifEnumProperty(ExifTag.Sharpness, (Sharpness)conv.ToUInt16(value, 0), true); + } + + if (tag == 0xa40c) // SubjectDistanceRange + { + return new ExifEnumProperty(ExifTag.SubjectDistanceRange, + (SubjectDistanceRange)conv.ToUInt16(value, 0), true); + } + } + else if (ifd == IFD.GPS) + { + if (tag == 0) // GPSVersionID + { + return new ExifVersion(ExifTag.GPSVersionID, ExifBitConverter.ToString(value)); + } + + if (tag == 1) // GPSLatitudeRef + { + return new ExifEnumProperty(ExifTag.GPSLatitudeRef, (GPSLatitudeRef)value[0]); + } + + if (tag == 2) // GPSLatitude + { + return new GPSLatitudeLongitude(ExifTag.GPSLatitude, + ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); + } + + if (tag == 3) // GPSLongitudeRef + { + return new ExifEnumProperty(ExifTag.GPSLongitudeRef, (GPSLongitudeRef)value[0]); + } + + if (tag == 4) // GPSLongitude + { + return new GPSLatitudeLongitude(ExifTag.GPSLongitude, + ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); + } + + if (tag == 5) // GPSAltitudeRef + { + return new ExifEnumProperty(ExifTag.GPSAltitudeRef, (GPSAltitudeRef)value[0]); + } + + if (tag == 7) // GPSTimeStamp + { + return new GPSTimeStamp(ExifTag.GPSTimeStamp, + ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); + } + + if (tag == 9) // GPSStatus + { + return new ExifEnumProperty(ExifTag.GPSStatus, (GPSStatus)value[0]); + } + + if (tag == 10) // GPSMeasureMode + { + return new ExifEnumProperty(ExifTag.GPSMeasureMode, (GPSMeasureMode)value[0]); + } + + if (tag == 12) // GPSSpeedRef + { + return new ExifEnumProperty(ExifTag.GPSSpeedRef, (GPSSpeedRef)value[0]); + } + + if (tag == 14) // GPSTrackRef + { + return new ExifEnumProperty(ExifTag.GPSTrackRef, (GPSDirectionRef)value[0]); + } + + if (tag == 16) // GPSImgDirectionRef + { + return new ExifEnumProperty(ExifTag.GPSImgDirectionRef, (GPSDirectionRef)value[0]); + } + + if (tag == 19) // GPSDestLatitudeRef + { + return new ExifEnumProperty(ExifTag.GPSDestLatitudeRef, (GPSLatitudeRef)value[0]); + } + + if (tag == 20) // GPSDestLatitude + { + return new GPSLatitudeLongitude(ExifTag.GPSDestLatitude, + ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); + } + + if (tag == 21) // GPSDestLongitudeRef + { + return new ExifEnumProperty(ExifTag.GPSDestLongitudeRef, (GPSLongitudeRef)value[0]); + } + + if (tag == 22) // GPSDestLongitude + { + return new GPSLatitudeLongitude(ExifTag.GPSDestLongitude, + ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); + } + + if (tag == 23) // GPSDestBearingRef + { + return new ExifEnumProperty(ExifTag.GPSDestBearingRef, (GPSDirectionRef)value[0]); + } + + if (tag == 25) // GPSDestDistanceRef + { + return new ExifEnumProperty(ExifTag.GPSDestDistanceRef, (GPSDistanceRef)value[0]); + } + + if (tag == 29) // GPSDate + { + return new ExifDateTime(ExifTag.GPSDateStamp, ExifBitConverter.ToDateTime(value, false)); + } + + if (tag == 30) // GPSDifferential + { + return new ExifEnumProperty(ExifTag.GPSDifferential, + (GPSDifferential)conv.ToUInt16(value, 0)); + } + } + else if (ifd == IFD.Interop) + { + if (tag == 1) // InteroperabilityIndex + { + return new ExifAscii(ExifTag.InteroperabilityIndex, ExifBitConverter.ToAscii(value, Encoding.ASCII), + Encoding.ASCII); + } + + if (tag == 2) // InteroperabilityVersion + { + return new ExifVersion(ExifTag.InteroperabilityVersion, + ExifBitConverter.ToAscii(value, Encoding.ASCII)); + } + } + else if (ifd == IFD.First) + { + if (tag == 0x103) // Compression + { + return new ExifEnumProperty(ExifTag.ThumbnailCompression, + (Compression)conv.ToUInt16(value, 0)); + } + + if (tag == 0x106) // PhotometricInterpretation + { + return new ExifEnumProperty(ExifTag.ThumbnailPhotometricInterpretation, + (PhotometricInterpretation)conv.ToUInt16(value, 0)); + } + + if (tag == 0x112) // Orientation + { + return new ExifEnumProperty(ExifTag.ThumbnailOrientation, + (Orientation)conv.ToUInt16(value, 0)); + } + + if (tag == 0x11c) // PlanarConfiguration + { + return new ExifEnumProperty(ExifTag.ThumbnailPlanarConfiguration, + (PlanarConfiguration)conv.ToUInt16(value, 0)); } - else - throw new ArgumentException("Unknown property type."); + + if (tag == 0x213) // YCbCrPositioning + { + return new ExifEnumProperty(ExifTag.ThumbnailYCbCrPositioning, + (YCbCrPositioning)conv.ToUInt16(value, 0)); + } + + if (tag == 0x128) // ResolutionUnit + { + return new ExifEnumProperty(ExifTag.ThumbnailResolutionUnit, + (ResolutionUnit)conv.ToUInt16(value, 0)); + } + + if (tag == 0x132) // DateTime + { + return new ExifDateTime(ExifTag.ThumbnailDateTime, ExifBitConverter.ToDateTime(value)); + } + } + + if (type == 1) // 1 = BYTE An 8-bit unsigned integer. + { + if (count == 1) + { + return new ExifByte(etag, value[0]); + } + + return new ExifByteArray(etag, value); } - #endregion + + if (type == 2) // 2 = ASCII An 8-bit byte containing one 7-bit ASCII code. + { + return new ExifAscii(etag, ExifBitConverter.ToAscii(value, encoding), encoding); + } + + if (type == 3) // 3 = SHORT A 16-bit (2-byte) unsigned integer. + { + if (count == 1) + { + return new ExifUShort(etag, conv.ToUInt16(value, 0)); + } + + return new ExifUShortArray(etag, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); + } + + if (type == 4) // 4 = LONG A 32-bit (4-byte) unsigned integer. + { + if (count == 1) + { + return new ExifUInt(etag, conv.ToUInt32(value, 0)); + } + + return new ExifUIntArray(etag, ExifBitConverter.ToUIntArray(value, (int)count, byteOrder)); + } + + if (type == 5) // 5 = RATIONAL Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator. + { + if (count == 1) + { + return new ExifURational(etag, ExifBitConverter.ToURational(value, byteOrder)); + } + + return new ExifURationalArray(etag, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); + } + + if (type == 7) // 7 = UNDEFINED An 8-bit byte that can take any value depending on the field definition. + { + return new ExifUndefined(etag, value); + } + + if (type == 9) // 9 = SLONG A 32-bit (4-byte) signed integer (2's complement notation). + { + if (count == 1) + { + return new ExifSInt(etag, conv.ToInt32(value, 0)); + } + + return new ExifSIntArray(etag, ExifBitConverter.ToSIntArray(value, (int)count, byteOrder)); + } + + if (type == 10) // 10 = SRATIONAL Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator. + { + if (count == 1) + { + return new ExifSRational(etag, ExifBitConverter.ToSRational(value, byteOrder)); + } + + return new ExifSRationalArray(etag, ExifBitConverter.ToSRationalArray(value, (int)count, byteOrder)); + } + + throw new ArgumentException("Unknown property type."); } + + #endregion } diff --git a/src/Umbraco.Core/Media/Exif/ExifTag.cs b/src/Umbraco.Core/Media/Exif/ExifTag.cs index 22215044b2f9..854100405857 100644 --- a/src/Umbraco.Core/Media/Exif/ExifTag.cs +++ b/src/Umbraco.Core/Media/Exif/ExifTag.cs @@ -1,290 +1,309 @@ - -namespace Umbraco.Cms.Core.Media.Exif +namespace Umbraco.Cms.Core.Media.Exif; + +/// +/// Represents the tags associated with exif fields. +/// +internal enum ExifTag { + // **************************** + // Zeroth IFD + // **************************** + NewSubfileType = IFD.Zeroth + 254, + SubfileType = IFD.Zeroth + 255, + ImageWidth = IFD.Zeroth + 256, + ImageLength = IFD.Zeroth + 257, + BitsPerSample = IFD.Zeroth + 258, + Compression = IFD.Zeroth + 259, + PhotometricInterpretation = IFD.Zeroth + 262, + Threshholding = IFD.Zeroth + 263, + CellWidth = IFD.Zeroth + 264, + CellLength = IFD.Zeroth + 265, + FillOrder = IFD.Zeroth + 266, + DocumentName = IFD.Zeroth + 269, + ImageDescription = IFD.Zeroth + 270, + Make = IFD.Zeroth + 271, + Model = IFD.Zeroth + 272, + StripOffsets = IFD.Zeroth + 273, + Orientation = IFD.Zeroth + 274, + SamplesPerPixel = IFD.Zeroth + 277, + RowsPerStrip = IFD.Zeroth + 278, + StripByteCounts = IFD.Zeroth + 279, + MinSampleValue = IFD.Zeroth + 280, + MaxSampleValue = IFD.Zeroth + 281, + XResolution = IFD.Zeroth + 282, + YResolution = IFD.Zeroth + 283, + PlanarConfiguration = IFD.Zeroth + 284, + PageName = IFD.Zeroth + 285, + XPosition = IFD.Zeroth + 286, + YPosition = IFD.Zeroth + 287, + FreeOffsets = IFD.Zeroth + 288, + FreeByteCounts = IFD.Zeroth + 289, + GrayResponseUnit = IFD.Zeroth + 290, + GrayResponseCurve = IFD.Zeroth + 291, + T4Options = IFD.Zeroth + 292, + T6Options = IFD.Zeroth + 293, + ResolutionUnit = IFD.Zeroth + 296, + PageNumber = IFD.Zeroth + 297, + TransferFunction = IFD.Zeroth + 301, + Software = IFD.Zeroth + 305, + DateTime = IFD.Zeroth + 306, + Artist = IFD.Zeroth + 315, + HostComputer = IFD.Zeroth + 316, + Predictor = IFD.Zeroth + 317, + WhitePoint = IFD.Zeroth + 318, + PrimaryChromaticities = IFD.Zeroth + 319, + ColorMap = IFD.Zeroth + 320, + HalftoneHints = IFD.Zeroth + 321, + TileWidth = IFD.Zeroth + 322, + TileLength = IFD.Zeroth + 323, + TileOffsets = IFD.Zeroth + 324, + TileByteCounts = IFD.Zeroth + 325, + InkSet = IFD.Zeroth + 332, + InkNames = IFD.Zeroth + 333, + NumberOfInks = IFD.Zeroth + 334, + DotRange = IFD.Zeroth + 336, + TargetPrinter = IFD.Zeroth + 337, + ExtraSamples = IFD.Zeroth + 338, + SampleFormat = IFD.Zeroth + 339, + SMinSampleValue = IFD.Zeroth + 340, + SMaxSampleValue = IFD.Zeroth + 341, + TransferRange = IFD.Zeroth + 342, + JPEGProc = IFD.Zeroth + 512, + JPEGInterchangeFormat = IFD.Zeroth + 513, + JPEGInterchangeFormatLength = IFD.Zeroth + 514, + JPEGRestartInterval = IFD.Zeroth + 515, + JPEGLosslessPredictors = IFD.Zeroth + 517, + JPEGPointTransforms = IFD.Zeroth + 518, + JPEGQTables = IFD.Zeroth + 519, + JPEGDCTables = IFD.Zeroth + 520, + JPEGACTables = IFD.Zeroth + 521, + YCbCrCoefficients = IFD.Zeroth + 529, + YCbCrSubSampling = IFD.Zeroth + 530, + YCbCrPositioning = IFD.Zeroth + 531, + ReferenceBlackWhite = IFD.Zeroth + 532, + Copyright = IFD.Zeroth + 33432, + + // Pointers to other IFDs + EXIFIFDPointer = IFD.Zeroth + 34665, + GPSIFDPointer = IFD.Zeroth + 34853, + + // Windows Tags + WindowsTitle = IFD.Zeroth + 0x9c9b, + WindowsComment = IFD.Zeroth + 0x9c9c, + WindowsAuthor = IFD.Zeroth + 0x9c9d, + WindowsKeywords = IFD.Zeroth + 0x9c9e, + WindowsSubject = IFD.Zeroth + 0x9c9f, + + // Rating + Rating = IFD.Zeroth + 0x4746, + RatingPercent = IFD.Zeroth + 0x4749, + + // Microsoft specifying padding and offset tags + ZerothIFDPadding = IFD.Zeroth + 0xea1c, + + // **************************** + // EXIF Tags + // **************************** + ExifVersion = IFD.EXIF + 36864, + FlashpixVersion = IFD.EXIF + 40960, + ColorSpace = IFD.EXIF + 40961, + ComponentsConfiguration = IFD.EXIF + 37121, + CompressedBitsPerPixel = IFD.EXIF + 37122, + PixelXDimension = IFD.EXIF + 40962, + PixelYDimension = IFD.EXIF + 40963, + MakerNote = IFD.EXIF + 37500, + UserComment = IFD.EXIF + 37510, + RelatedSoundFile = IFD.EXIF + 40964, + DateTimeOriginal = IFD.EXIF + 36867, + DateTimeDigitized = IFD.EXIF + 36868, + SubSecTime = IFD.EXIF + 37520, + SubSecTimeOriginal = IFD.EXIF + 37521, + SubSecTimeDigitized = IFD.EXIF + 37522, + ExposureTime = IFD.EXIF + 33434, + FNumber = IFD.EXIF + 33437, + ExposureProgram = IFD.EXIF + 34850, + SpectralSensitivity = IFD.EXIF + 34852, + ISOSpeedRatings = IFD.EXIF + 34855, + OECF = IFD.EXIF + 34856, + ShutterSpeedValue = IFD.EXIF + 37377, + ApertureValue = IFD.EXIF + 37378, + BrightnessValue = IFD.EXIF + 37379, + ExposureBiasValue = IFD.EXIF + 37380, + MaxApertureValue = IFD.EXIF + 37381, + SubjectDistance = IFD.EXIF + 37382, + MeteringMode = IFD.EXIF + 37383, + LightSource = IFD.EXIF + 37384, + Flash = IFD.EXIF + 37385, + FocalLength = IFD.EXIF + 37386, + SubjectArea = IFD.EXIF + 37396, + FlashEnergy = IFD.EXIF + 41483, + SpatialFrequencyResponse = IFD.EXIF + 41484, + FocalPlaneXResolution = IFD.EXIF + 41486, + FocalPlaneYResolution = IFD.EXIF + 41487, + FocalPlaneResolutionUnit = IFD.EXIF + 41488, + SubjectLocation = IFD.EXIF + 41492, + ExposureIndex = IFD.EXIF + 41493, + SensingMethod = IFD.EXIF + 41495, + FileSource = IFD.EXIF + 41728, + SceneType = IFD.EXIF + 41729, + CFAPattern = IFD.EXIF + 41730, + CustomRendered = IFD.EXIF + 41985, + ExposureMode = IFD.EXIF + 41986, + WhiteBalance = IFD.EXIF + 41987, + DigitalZoomRatio = IFD.EXIF + 41988, + FocalLengthIn35mmFilm = IFD.EXIF + 41989, + SceneCaptureType = IFD.EXIF + 41990, + GainControl = IFD.EXIF + 41991, + Contrast = IFD.EXIF + 41992, + Saturation = IFD.EXIF + 41993, + Sharpness = IFD.EXIF + 41994, + DeviceSettingDescription = IFD.EXIF + 41995, + SubjectDistanceRange = IFD.EXIF + 41996, + ImageUniqueID = IFD.EXIF + 42016, + InteroperabilityIFDPointer = IFD.EXIF + 40965, + + // Microsoft specifying padding and offset tags + ExifIFDPadding = IFD.EXIF + 0xea1c, + OffsetSchema = IFD.EXIF + 0xea1d, + + // **************************** + // GPS Tags + // **************************** + GPSVersionID = IFD.GPS + 0, + GPSLatitudeRef = IFD.GPS + 1, + GPSLatitude = IFD.GPS + 2, + GPSLongitudeRef = IFD.GPS + 3, + GPSLongitude = IFD.GPS + 4, + GPSAltitudeRef = IFD.GPS + 5, + GPSAltitude = IFD.GPS + 6, + GPSTimeStamp = IFD.GPS + 7, + GPSSatellites = IFD.GPS + 8, + GPSStatus = IFD.GPS + 9, + GPSMeasureMode = IFD.GPS + 10, + GPSDOP = IFD.GPS + 11, + GPSSpeedRef = IFD.GPS + 12, + GPSSpeed = IFD.GPS + 13, + GPSTrackRef = IFD.GPS + 14, + GPSTrack = IFD.GPS + 15, + GPSImgDirectionRef = IFD.GPS + 16, + GPSImgDirection = IFD.GPS + 17, + GPSMapDatum = IFD.GPS + 18, + GPSDestLatitudeRef = IFD.GPS + 19, + GPSDestLatitude = IFD.GPS + 20, + GPSDestLongitudeRef = IFD.GPS + 21, + GPSDestLongitude = IFD.GPS + 22, + GPSDestBearingRef = IFD.GPS + 23, + GPSDestBearing = IFD.GPS + 24, + GPSDestDistanceRef = IFD.GPS + 25, + GPSDestDistance = IFD.GPS + 26, + GPSProcessingMethod = IFD.GPS + 27, + GPSAreaInformation = IFD.GPS + 28, + GPSDateStamp = IFD.GPS + 29, + GPSDifferential = IFD.GPS + 30, + + // **************************** + // InterOp Tags + // **************************** + InteroperabilityIndex = IFD.Interop + 1, + InteroperabilityVersion = IFD.Interop + 2, + RelatedImageWidth = IFD.Interop + 0x1001, + RelatedImageHeight = IFD.Interop + 0x1002, + + // **************************** + // First IFD TIFF Tags + // **************************** + ThumbnailImageWidth = IFD.First + 256, + ThumbnailImageLength = IFD.First + 257, + ThumbnailBitsPerSample = IFD.First + 258, + ThumbnailCompression = IFD.First + 259, + ThumbnailPhotometricInterpretation = IFD.First + 262, + ThumbnailOrientation = IFD.First + 274, + ThumbnailSamplesPerPixel = IFD.First + 277, + ThumbnailPlanarConfiguration = IFD.First + 284, + ThumbnailYCbCrSubSampling = IFD.First + 530, + ThumbnailYCbCrPositioning = IFD.First + 531, + ThumbnailXResolution = IFD.First + 282, + ThumbnailYResolution = IFD.First + 283, + ThumbnailResolutionUnit = IFD.First + 296, + ThumbnailStripOffsets = IFD.First + 273, + ThumbnailRowsPerStrip = IFD.First + 278, + ThumbnailStripByteCounts = IFD.First + 279, + ThumbnailJPEGInterchangeFormat = IFD.First + 513, + ThumbnailJPEGInterchangeFormatLength = IFD.First + 514, + ThumbnailTransferFunction = IFD.First + 301, + ThumbnailWhitePoint = IFD.First + 318, + ThumbnailPrimaryChromaticities = IFD.First + 319, + ThumbnailYCbCrCoefficients = IFD.First + 529, + ThumbnailReferenceBlackWhite = IFD.First + 532, + ThumbnailDateTime = IFD.First + 306, + ThumbnailImageDescription = IFD.First + 270, + ThumbnailMake = IFD.First + 271, + ThumbnailModel = IFD.First + 272, + ThumbnailSoftware = IFD.First + 305, + ThumbnailArtist = IFD.First + 315, + ThumbnailCopyright = IFD.First + 33432, + + // **************************** + // JFIF Tags + // **************************** /// - /// Represents the tags associated with exif fields. + /// Represents the JFIF version. /// - internal enum ExifTag : int - { - // **************************** - // Zeroth IFD - // **************************** - NewSubfileType = IFD.Zeroth + 254, - SubfileType = IFD.Zeroth + 255, - ImageWidth = IFD.Zeroth + 256, - ImageLength = IFD.Zeroth + 257, - BitsPerSample = IFD.Zeroth + 258, - Compression = IFD.Zeroth + 259, - PhotometricInterpretation = IFD.Zeroth + 262, - Threshholding = IFD.Zeroth + 263, - CellWidth = IFD.Zeroth + 264, - CellLength = IFD.Zeroth + 265, - FillOrder = IFD.Zeroth + 266, - DocumentName = IFD.Zeroth + 269, - ImageDescription = IFD.Zeroth + 270, - Make = IFD.Zeroth + 271, - Model = IFD.Zeroth + 272, - StripOffsets = IFD.Zeroth + 273, - Orientation = IFD.Zeroth + 274, - SamplesPerPixel = IFD.Zeroth + 277, - RowsPerStrip = IFD.Zeroth + 278, - StripByteCounts = IFD.Zeroth + 279, - MinSampleValue = IFD.Zeroth + 280, - MaxSampleValue = IFD.Zeroth + 281, - XResolution = IFD.Zeroth + 282, - YResolution = IFD.Zeroth + 283, - PlanarConfiguration = IFD.Zeroth + 284, - PageName = IFD.Zeroth + 285, - XPosition = IFD.Zeroth + 286, - YPosition = IFD.Zeroth + 287, - FreeOffsets = IFD.Zeroth + 288, - FreeByteCounts = IFD.Zeroth + 289, - GrayResponseUnit = IFD.Zeroth + 290, - GrayResponseCurve = IFD.Zeroth + 291, - T4Options = IFD.Zeroth + 292, - T6Options = IFD.Zeroth + 293, - ResolutionUnit = IFD.Zeroth + 296, - PageNumber = IFD.Zeroth + 297, - TransferFunction = IFD.Zeroth + 301, - Software = IFD.Zeroth + 305, - DateTime = IFD.Zeroth + 306, - Artist = IFD.Zeroth + 315, - HostComputer = IFD.Zeroth + 316, - Predictor = IFD.Zeroth + 317, - WhitePoint = IFD.Zeroth + 318, - PrimaryChromaticities = IFD.Zeroth + 319, - ColorMap = IFD.Zeroth + 320, - HalftoneHints = IFD.Zeroth + 321, - TileWidth = IFD.Zeroth + 322, - TileLength = IFD.Zeroth + 323, - TileOffsets = IFD.Zeroth + 324, - TileByteCounts = IFD.Zeroth + 325, - InkSet = IFD.Zeroth + 332, - InkNames = IFD.Zeroth + 333, - NumberOfInks = IFD.Zeroth + 334, - DotRange = IFD.Zeroth + 336, - TargetPrinter = IFD.Zeroth + 337, - ExtraSamples = IFD.Zeroth + 338, - SampleFormat = IFD.Zeroth + 339, - SMinSampleValue = IFD.Zeroth + 340, - SMaxSampleValue = IFD.Zeroth + 341, - TransferRange = IFD.Zeroth + 342, - JPEGProc = IFD.Zeroth + 512, - JPEGInterchangeFormat = IFD.Zeroth + 513, - JPEGInterchangeFormatLength = IFD.Zeroth + 514, - JPEGRestartInterval = IFD.Zeroth + 515, - JPEGLosslessPredictors = IFD.Zeroth + 517, - JPEGPointTransforms = IFD.Zeroth + 518, - JPEGQTables = IFD.Zeroth + 519, - JPEGDCTables = IFD.Zeroth + 520, - JPEGACTables = IFD.Zeroth + 521, - YCbCrCoefficients = IFD.Zeroth + 529, - YCbCrSubSampling = IFD.Zeroth + 530, - YCbCrPositioning = IFD.Zeroth + 531, - ReferenceBlackWhite = IFD.Zeroth + 532, - Copyright = IFD.Zeroth + 33432, - // Pointers to other IFDs - EXIFIFDPointer = IFD.Zeroth + 34665, - GPSIFDPointer = IFD.Zeroth + 34853, - // Windows Tags - WindowsTitle = IFD.Zeroth + 0x9c9b, - WindowsComment = IFD.Zeroth + 0x9c9c, - WindowsAuthor = IFD.Zeroth + 0x9c9d, - WindowsKeywords = IFD.Zeroth + 0x9c9e, - WindowsSubject = IFD.Zeroth + 0x9c9f, - // Rating - Rating = IFD.Zeroth + 0x4746, - RatingPercent = IFD.Zeroth + 0x4749, - // Microsoft specifying padding and offset tags - ZerothIFDPadding = IFD.Zeroth + 0xea1c, - // **************************** - // EXIF Tags - // **************************** - ExifVersion = IFD.EXIF + 36864, - FlashpixVersion = IFD.EXIF + 40960, - ColorSpace = IFD.EXIF + 40961, - ComponentsConfiguration = IFD.EXIF + 37121, - CompressedBitsPerPixel = IFD.EXIF + 37122, - PixelXDimension = IFD.EXIF + 40962, - PixelYDimension = IFD.EXIF + 40963, - MakerNote = IFD.EXIF + 37500, - UserComment = IFD.EXIF + 37510, - RelatedSoundFile = IFD.EXIF + 40964, - DateTimeOriginal = IFD.EXIF + 36867, - DateTimeDigitized = IFD.EXIF + 36868, - SubSecTime = IFD.EXIF + 37520, - SubSecTimeOriginal = IFD.EXIF + 37521, - SubSecTimeDigitized = IFD.EXIF + 37522, - ExposureTime = IFD.EXIF + 33434, - FNumber = IFD.EXIF + 33437, - ExposureProgram = IFD.EXIF + 34850, - SpectralSensitivity = IFD.EXIF + 34852, - ISOSpeedRatings = IFD.EXIF + 34855, - OECF = IFD.EXIF + 34856, - ShutterSpeedValue = IFD.EXIF + 37377, - ApertureValue = IFD.EXIF + 37378, - BrightnessValue = IFD.EXIF + 37379, - ExposureBiasValue = IFD.EXIF + 37380, - MaxApertureValue = IFD.EXIF + 37381, - SubjectDistance = IFD.EXIF + 37382, - MeteringMode = IFD.EXIF + 37383, - LightSource = IFD.EXIF + 37384, - Flash = IFD.EXIF + 37385, - FocalLength = IFD.EXIF + 37386, - SubjectArea = IFD.EXIF + 37396, - FlashEnergy = IFD.EXIF + 41483, - SpatialFrequencyResponse = IFD.EXIF + 41484, - FocalPlaneXResolution = IFD.EXIF + 41486, - FocalPlaneYResolution = IFD.EXIF + 41487, - FocalPlaneResolutionUnit = IFD.EXIF + 41488, - SubjectLocation = IFD.EXIF + 41492, - ExposureIndex = IFD.EXIF + 41493, - SensingMethod = IFD.EXIF + 41495, - FileSource = IFD.EXIF + 41728, - SceneType = IFD.EXIF + 41729, - CFAPattern = IFD.EXIF + 41730, - CustomRendered = IFD.EXIF + 41985, - ExposureMode = IFD.EXIF + 41986, - WhiteBalance = IFD.EXIF + 41987, - DigitalZoomRatio = IFD.EXIF + 41988, - FocalLengthIn35mmFilm = IFD.EXIF + 41989, - SceneCaptureType = IFD.EXIF + 41990, - GainControl = IFD.EXIF + 41991, - Contrast = IFD.EXIF + 41992, - Saturation = IFD.EXIF + 41993, - Sharpness = IFD.EXIF + 41994, - DeviceSettingDescription = IFD.EXIF + 41995, - SubjectDistanceRange = IFD.EXIF + 41996, - ImageUniqueID = IFD.EXIF + 42016, - InteroperabilityIFDPointer = IFD.EXIF + 40965, - // Microsoft specifying padding and offset tags - ExifIFDPadding = IFD.EXIF + 0xea1c, - OffsetSchema = IFD.EXIF + 0xea1d, - // **************************** - // GPS Tags - // **************************** - GPSVersionID = IFD.GPS + 0, - GPSLatitudeRef = IFD.GPS + 1, - GPSLatitude = IFD.GPS + 2, - GPSLongitudeRef = IFD.GPS + 3, - GPSLongitude = IFD.GPS + 4, - GPSAltitudeRef = IFD.GPS + 5, - GPSAltitude = IFD.GPS + 6, - GPSTimeStamp = IFD.GPS + 7, - GPSSatellites = IFD.GPS + 8, - GPSStatus = IFD.GPS + 9, - GPSMeasureMode = IFD.GPS + 10, - GPSDOP = IFD.GPS + 11, - GPSSpeedRef = IFD.GPS + 12, - GPSSpeed = IFD.GPS + 13, - GPSTrackRef = IFD.GPS + 14, - GPSTrack = IFD.GPS + 15, - GPSImgDirectionRef = IFD.GPS + 16, - GPSImgDirection = IFD.GPS + 17, - GPSMapDatum = IFD.GPS + 18, - GPSDestLatitudeRef = IFD.GPS + 19, - GPSDestLatitude = IFD.GPS + 20, - GPSDestLongitudeRef = IFD.GPS + 21, - GPSDestLongitude = IFD.GPS + 22, - GPSDestBearingRef = IFD.GPS + 23, - GPSDestBearing = IFD.GPS + 24, - GPSDestDistanceRef = IFD.GPS + 25, - GPSDestDistance = IFD.GPS + 26, - GPSProcessingMethod = IFD.GPS + 27, - GPSAreaInformation = IFD.GPS + 28, - GPSDateStamp = IFD.GPS + 29, - GPSDifferential = IFD.GPS + 30, - // **************************** - // InterOp Tags - // **************************** - InteroperabilityIndex = IFD.Interop + 1, - InteroperabilityVersion = IFD.Interop + 2, - RelatedImageWidth = IFD.Interop + 0x1001, - RelatedImageHeight = IFD.Interop + 0x1002, - // **************************** - // First IFD TIFF Tags - // **************************** - ThumbnailImageWidth = IFD.First + 256, - ThumbnailImageLength = IFD.First + 257, - ThumbnailBitsPerSample = IFD.First + 258, - ThumbnailCompression = IFD.First + 259, - ThumbnailPhotometricInterpretation = IFD.First + 262, - ThumbnailOrientation = IFD.First + 274, - ThumbnailSamplesPerPixel = IFD.First + 277, - ThumbnailPlanarConfiguration = IFD.First + 284, - ThumbnailYCbCrSubSampling = IFD.First + 530, - ThumbnailYCbCrPositioning = IFD.First + 531, - ThumbnailXResolution = IFD.First + 282, - ThumbnailYResolution = IFD.First + 283, - ThumbnailResolutionUnit = IFD.First + 296, - ThumbnailStripOffsets = IFD.First + 273, - ThumbnailRowsPerStrip = IFD.First + 278, - ThumbnailStripByteCounts = IFD.First + 279, - ThumbnailJPEGInterchangeFormat = IFD.First + 513, - ThumbnailJPEGInterchangeFormatLength = IFD.First + 514, - ThumbnailTransferFunction = IFD.First + 301, - ThumbnailWhitePoint = IFD.First + 318, - ThumbnailPrimaryChromaticities = IFD.First + 319, - ThumbnailYCbCrCoefficients = IFD.First + 529, - ThumbnailReferenceBlackWhite = IFD.First + 532, - ThumbnailDateTime = IFD.First + 306, - ThumbnailImageDescription = IFD.First + 270, - ThumbnailMake = IFD.First + 271, - ThumbnailModel = IFD.First + 272, - ThumbnailSoftware = IFD.First + 305, - ThumbnailArtist = IFD.First + 315, - ThumbnailCopyright = IFD.First + 33432, - // **************************** - // JFIF Tags - // **************************** - /// - /// Represents the JFIF version. - /// - JFIFVersion = IFD.JFIF + 1, - /// - /// Represents units for X and Y densities. - /// - JFIFUnits = IFD.JFIF + 101, - /// - /// Horizontal pixel density. - /// - XDensity = IFD.JFIF + 102, - /// - /// Vertical pixel density - /// - YDensity = IFD.JFIF + 103, - /// - /// Thumbnail horizontal pixel count. - /// - JFIFXThumbnail = IFD.JFIF + 201, - /// - /// Thumbnail vertical pixel count. - /// - JFIFYThumbnail = IFD.JFIF + 202, - /// - /// JFIF JPEG thumbnail. - /// - JFIFThumbnail = IFD.JFIF + 203, - /// - /// Code which identifies the JFIF extension. - /// - JFXXExtensionCode = IFD.JFXX + 1, - /// - /// Thumbnail horizontal pixel count. - /// - JFXXXThumbnail = IFD.JFXX + 101, - /// - /// Thumbnail vertical pixel count. - /// - JFXXYThumbnail = IFD.JFXX + 102, - /// - /// The 256-Color RGB palette. - /// - JFXXPalette = IFD.JFXX + 201, - /// - /// JFIF thumbnail. The thumbnail will be either a JPEG, - /// a 256 color palette bitmap, or a 24-bit RGB bitmap. - /// - JFXXThumbnail = IFD.JFXX + 202, - } + JFIFVersion = IFD.JFIF + 1, + + /// + /// Represents units for X and Y densities. + /// + JFIFUnits = IFD.JFIF + 101, + + /// + /// Horizontal pixel density. + /// + XDensity = IFD.JFIF + 102, + + /// + /// Vertical pixel density + /// + YDensity = IFD.JFIF + 103, + + /// + /// Thumbnail horizontal pixel count. + /// + JFIFXThumbnail = IFD.JFIF + 201, + + /// + /// Thumbnail vertical pixel count. + /// + JFIFYThumbnail = IFD.JFIF + 202, + + /// + /// JFIF JPEG thumbnail. + /// + JFIFThumbnail = IFD.JFIF + 203, + + /// + /// Code which identifies the JFIF extension. + /// + JFXXExtensionCode = IFD.JFXX + 1, + + /// + /// Thumbnail horizontal pixel count. + /// + JFXXXThumbnail = IFD.JFXX + 101, + + /// + /// Thumbnail vertical pixel count. + /// + JFXXYThumbnail = IFD.JFXX + 102, + + /// + /// The 256-Color RGB palette. + /// + JFXXPalette = IFD.JFXX + 201, + + /// + /// JFIF thumbnail. The thumbnail will be either a JPEG, + /// a 256 color palette bitmap, or a 24-bit RGB bitmap. + /// + JFXXThumbnail = IFD.JFXX + 202 } diff --git a/src/Umbraco.Core/Media/Exif/ExifTagFactory.cs b/src/Umbraco.Core/Media/Exif/ExifTagFactory.cs index 6a5ea8494424..cf054dea4b45 100644 --- a/src/Umbraco.Core/Media/Exif/ExifTagFactory.cs +++ b/src/Umbraco.Core/Media/Exif/ExifTagFactory.cs @@ -1,68 +1,63 @@ -using System; +namespace Umbraco.Cms.Core.Media.Exif; -namespace Umbraco.Cms.Core.Media.Exif +internal static class ExifTagFactory { - internal static class ExifTagFactory + #region Static Methods + + /// + /// Returns the ExifTag corresponding to the given tag id. + /// + public static ExifTag GetExifTag(IFD ifd, ushort tagid) => (ExifTag)(ifd + tagid); + + /// + /// Returns the tag id corresponding to the given ExifTag. + /// + public static ushort GetTagID(ExifTag exiftag) { - #region Static Methods - /// - /// Returns the ExifTag corresponding to the given tag id. - /// - public static ExifTag GetExifTag(IFD ifd, ushort tagid) - { - return (ExifTag)(ifd + tagid); - } + IFD ifd = GetTagIFD(exiftag); + return (ushort)((int)exiftag - (int)ifd); + } - /// - /// Returns the tag id corresponding to the given ExifTag. - /// - public static ushort GetTagID(ExifTag exiftag) - { - IFD ifd = GetTagIFD(exiftag); - return (ushort)((int)exiftag - (int)ifd); - } + /// + /// Returns the IFD section containing the given tag. + /// + public static IFD GetTagIFD(ExifTag tag) => (IFD)((int)tag / 100000 * 100000); - /// - /// Returns the IFD section containing the given tag. - /// - public static IFD GetTagIFD(ExifTag tag) + /// + /// Returns the string representation for the given exif tag. + /// + public static string GetTagName(ExifTag tag) + { + var name = Enum.GetName(typeof(ExifTag), tag); + if (name == null) { - return (IFD)(((int)tag / 100000) * 100000); + return "Unknown"; } - /// - /// Returns the string representation for the given exif tag. - /// - public static string GetTagName(ExifTag tag) - { - string? name = Enum.GetName(typeof(ExifTag), tag); - if (name == null) - return "Unknown"; - else - return name; - } + return name; + } - /// - /// Returns the string representation for the given tag id. - /// - public static string GetTagName(IFD ifd, ushort tagid) - { - return GetTagName(GetExifTag(ifd, tagid)); - } + /// + /// Returns the string representation for the given tag id. + /// + public static string GetTagName(IFD ifd, ushort tagid) => GetTagName(GetExifTag(ifd, tagid)); - /// - /// Returns the string representation for the given exif tag including - /// IFD section and tag id. - /// - public static string GetTagLongName(ExifTag tag) + /// + /// Returns the string representation for the given exif tag including + /// IFD section and tag id. + /// + public static string GetTagLongName(ExifTag tag) + { + var ifdname = Enum.GetName(typeof(IFD), GetTagIFD(tag)); + var name = Enum.GetName(typeof(ExifTag), tag); + if (name == null) { - string? ifdname = Enum.GetName(typeof(IFD), GetTagIFD(tag)); - string? name = Enum.GetName(typeof(ExifTag), tag); - if (name == null) - name = "Unknown"; - string tagidname = GetTagID(tag).ToString(); - return ifdname + ": " + name + " (" + tagidname + ")"; + name = "Unknown"; } - #endregion + + var tagidname = GetTagID(tag).ToString(); + return ifdname + ": " + name + " (" + tagidname + ")"; } + + #endregion } diff --git a/src/Umbraco.Core/Media/Exif/IFD.cs b/src/Umbraco.Core/Media/Exif/IFD.cs index e275e8d52a85..56ab84579be2 100644 --- a/src/Umbraco.Core/Media/Exif/IFD.cs +++ b/src/Umbraco.Core/Media/Exif/IFD.cs @@ -1,18 +1,17 @@ -namespace Umbraco.Cms.Core.Media.Exif +namespace Umbraco.Cms.Core.Media.Exif; + +/// +/// Represents the IFD section containing tags. +/// +internal enum IFD { - /// - /// Represents the IFD section containing tags. - /// - internal enum IFD : int - { - Unknown = 0, - Zeroth = 100000, - EXIF = 200000, - GPS = 300000, - Interop = 400000, - First = 500000, - MakerNote = 600000, - JFIF = 700000, - JFXX = 800000, - } + Unknown = 0, + Zeroth = 100000, + EXIF = 200000, + GPS = 300000, + Interop = 400000, + First = 500000, + MakerNote = 600000, + JFIF = 700000, + JFXX = 800000 } diff --git a/src/Umbraco.Core/Media/Exif/ImageFile.cs b/src/Umbraco.Core/Media/Exif/ImageFile.cs index cb783d3ee9fc..23ea615be9e9 100644 --- a/src/Umbraco.Core/Media/Exif/ImageFile.cs +++ b/src/Umbraco.Core/Media/Exif/ImageFile.cs @@ -1,139 +1,144 @@ using System.ComponentModel; -using System.IO; using System.Text; using Umbraco.Cms.Core.Media.TypeDetector; -namespace Umbraco.Cms.Core.Media.Exif +namespace Umbraco.Cms.Core.Media.Exif; + +/// +/// Represents the base class for image files. +/// +[TypeDescriptionProvider(typeof(ExifFileTypeDescriptionProvider))] +internal abstract class ImageFile { + #region Constructor + /// - /// Represents the base class for image files. + /// Initializes a new instance of the class. /// - [TypeDescriptionProvider(typeof(ExifFileTypeDescriptionProvider))] - internal abstract class ImageFile + protected ImageFile() { - #region Constructor - /// - /// Initializes a new instance of the class. - /// - protected ImageFile () - { - Format = ImageFileFormat.Unknown; - Properties = new ExifPropertyCollection (this); - Encoding = Encoding.Default; - } - #endregion - - #region Properties - /// - /// Returns the format of the . - /// - public ImageFileFormat Format { get; protected set; } - /// - /// Gets the collection of Exif properties contained in the . - /// - public ExifPropertyCollection Properties { get; private set; } - /// - /// Gets or sets the embedded thumbnail image. - /// - public ImageFile? Thumbnail { get; set; } - /// - /// Gets or sets the Exif property with the given key. - /// - /// The Exif tag associated with the Exif property. - public ExifProperty this[ExifTag key] { - get { return Properties[key]; } - set { Properties[key] = value; } - } - /// - /// Gets the encoding used for text metadata when the source encoding is unknown. - /// - public Encoding Encoding { get; protected set; } - #endregion - - #region Instance Methods - - /// - /// Saves the to the specified file. - /// - /// A string that contains the name of the file. - public virtual void Save (string filename) + Format = ImageFileFormat.Unknown; + Properties = new ExifPropertyCollection(this); + Encoding = Encoding.Default; + } + + #endregion + + #region Properties + + /// + /// Returns the format of the . + /// + public ImageFileFormat Format { get; protected set; } + + /// + /// Gets the collection of Exif properties contained in the . + /// + public ExifPropertyCollection Properties { get; } + + /// + /// Gets or sets the embedded thumbnail image. + /// + public ImageFile? Thumbnail { get; set; } + + /// + /// Gets or sets the Exif property with the given key. + /// + /// The Exif tag associated with the Exif property. + public ExifProperty this[ExifTag key] + { + get => Properties[key]; + set => Properties[key] = value; + } + + /// + /// Gets the encoding used for text metadata when the source encoding is unknown. + /// + public Encoding Encoding { get; protected set; } + + #endregion + + #region Instance Methods + + /// + /// Saves the to the specified file. + /// + /// A string that contains the name of the file. + public virtual void Save(string filename) + { + using (var stream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None)) { - using (FileStream stream = new FileStream (filename, FileMode.Create, FileAccess.Write, FileShare.None)) { - Save (stream); - } + Save(stream); } + } + + /// + /// Saves the to the specified stream. + /// + /// A to save image data to. + public abstract void Save(Stream stream); + + #endregion + + #region Static Methods + + /// + /// Creates an from the specified file. + /// + /// A string that contains the name of the file. + /// The created from the file. + public static ImageFile? FromFile(string filename) => FromFile(filename, Encoding.Default); - /// - /// Saves the to the specified stream. - /// - /// A to save image data to. - public abstract void Save (Stream stream); - #endregion - - #region Static Methods - /// - /// Creates an from the specified file. - /// - /// A string that contains the name of the file. - /// The created from the file. - public static ImageFile? FromFile (string filename) + /// + /// Creates an from the specified file. + /// + /// A string that contains the name of the file. + /// The encoding to be used for text metadata when the source encoding is unknown. + /// The created from the file. + public static ImageFile? FromFile(string filename, Encoding encoding) + { + using (var stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) { - return FromFile(filename, Encoding.Default); + return FromStream(stream, encoding); } + } - /// - /// Creates an from the specified file. - /// - /// A string that contains the name of the file. - /// The encoding to be used for text metadata when the source encoding is unknown. - /// The created from the file. - public static ImageFile? FromFile(string filename, Encoding encoding) + /// + /// Creates an from the specified data stream. + /// + /// A that contains image data. + /// The created from the file. + public static ImageFile? FromStream(Stream stream) => FromStream(stream, Encoding.Default); + + /// + /// Creates an from the specified data stream. + /// + /// A that contains image data. + /// The encoding to be used for text metadata when the source encoding is unknown. + /// The created from the file. + public static ImageFile? FromStream(Stream stream, Encoding encoding) + { + // JPEG + if (JpegDetector.IsOfType(stream)) { - using (FileStream stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - return FromStream(stream, encoding); - } + return new JPEGFile(stream, encoding); } - /// - /// Creates an from the specified data stream. - /// - /// A that contains image data. - /// The created from the file. - public static ImageFile? FromStream(Stream stream) + // TIFF + if (TIFFDetector.IsOfType(stream)) { - return FromStream(stream, Encoding.Default); + return new TIFFFile(stream, encoding); } - /// - /// Creates an from the specified data stream. - /// - /// A that contains image data. - /// The encoding to be used for text metadata when the source encoding is unknown. - /// The created from the file. - public static ImageFile? FromStream(Stream stream, Encoding encoding) + // SVG + if (SvgDetector.IsOfType(stream)) { - // JPEG - if (JpegDetector.IsOfType(stream)) - { - return new JPEGFile(stream, encoding); - } - - // TIFF - if (TIFFDetector.IsOfType(stream)) - { - return new TIFFFile(stream, encoding); - } - - // SVG - if (SvgDetector.IsOfType(stream)) - { - return new SvgFile(stream); - } - - // We don't know - return null; + return new SvgFile(stream); } - #endregion + + // We don't know + return null; } + + #endregion } diff --git a/src/Umbraco.Core/Media/Exif/ImageFileDirectory.cs b/src/Umbraco.Core/Media/Exif/ImageFileDirectory.cs index ed4564a48603..35a0538ab090 100644 --- a/src/Umbraco.Core/Media/Exif/ImageFileDirectory.cs +++ b/src/Umbraco.Core/Media/Exif/ImageFileDirectory.cs @@ -1,97 +1,100 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Media.Exif; -namespace Umbraco.Cms.Core.Media.Exif +/// +/// Represents an image file directory. +/// +internal class ImageFileDirectory { /// - /// Represents an image file directory. + /// Initializes a new instance of the class. /// - internal class ImageFileDirectory + public ImageFileDirectory() { - /// - /// The fields contained in this IFD. - /// - public List Fields { get; private set; } - /// - /// Offset to the next IFD. - /// - public uint NextIFDOffset { get; private set; } - /// - /// Compressed image data. - /// - public List Strips { get; private set; } + Fields = new List(); + Strips = new List(); + } - /// - /// Initializes a new instance of the class. - /// - public ImageFileDirectory() - { - Fields = new List(); - Strips = new List(); - } + /// + /// The fields contained in this IFD. + /// + public List Fields { get; } - /// - /// Returns a initialized from the given byte data. - /// - /// The data. - /// The offset into . - /// The byte order of . - /// A initialized from the given byte data. - public static ImageFileDirectory FromBytes(byte[] data, uint offset, BitConverterEx.ByteOrder byteOrder) - { - ImageFileDirectory ifd = new ImageFileDirectory(); - BitConverterEx conv = new BitConverterEx(byteOrder, BitConverterEx.SystemByteOrder); + /// + /// Offset to the next IFD. + /// + public uint NextIFDOffset { get; private set; } - List stripOffsets = new List(); - List stripLengths = new List(); + /// + /// Compressed image data. + /// + public List Strips { get; } - // Count - ushort fieldcount = conv.ToUInt16(data, offset); + /// + /// Returns a initialized from the given byte data. + /// + /// The data. + /// The offset into . + /// The byte order of . + /// A initialized from the given byte data. + public static ImageFileDirectory FromBytes(byte[] data, uint offset, BitConverterEx.ByteOrder byteOrder) + { + var ifd = new ImageFileDirectory(); + var conv = new BitConverterEx(byteOrder, BitConverterEx.SystemByteOrder); - // Read fields - for (uint i = 0; i < fieldcount; i++) - { - uint fieldoffset = offset + 2 + 12 * i; - ImageFileDirectoryEntry field = ImageFileDirectoryEntry.FromBytes(data, fieldoffset, byteOrder); - ifd.Fields.Add(field); + var stripOffsets = new List(); + var stripLengths = new List(); - // Read strip offsets - if (field.Tag == 273) + // Count + var fieldcount = conv.ToUInt16(data, offset); + + // Read fields + for (uint i = 0; i < fieldcount; i++) + { + var fieldoffset = offset + 2 + (12 * i); + var field = ImageFileDirectoryEntry.FromBytes(data, fieldoffset, byteOrder); + ifd.Fields.Add(field); + + // Read strip offsets + if (field.Tag == 273) + { + var baselen = field.Data.Length / (int)field.Count; + for (uint j = 0; j < field.Count; j++) { - int baselen = field.Data.Length / (int)field.Count; - for (uint j = 0; j < field.Count; j++) - { - byte[] val = new byte[baselen]; - Array.Copy(field.Data, j * baselen, val, 0, baselen); - uint stripOffset = (field.Type == 3 ? (uint)BitConverter.ToUInt16(val, 0) : BitConverter.ToUInt32(val, 0)); - stripOffsets.Add(stripOffset); - } + var val = new byte[baselen]; + Array.Copy(field.Data, j * baselen, val, 0, baselen); + var stripOffset = field.Type == 3 ? BitConverter.ToUInt16(val, 0) : BitConverter.ToUInt32(val, 0); + stripOffsets.Add(stripOffset); } + } - // Read strip lengths - if (field.Tag == 279) + // Read strip lengths + if (field.Tag == 279) + { + var baselen = field.Data.Length / (int)field.Count; + for (uint j = 0; j < field.Count; j++) { - int baselen = field.Data.Length / (int)field.Count; - for (uint j = 0; j < field.Count; j++) - { - byte[] val = new byte[baselen]; - Array.Copy(field.Data, j * baselen, val, 0, baselen); - uint stripLength = (field.Type == 3 ? (uint)BitConverter.ToUInt16(val, 0) : BitConverter.ToUInt32(val, 0)); - stripLengths.Add(stripLength); - } + var val = new byte[baselen]; + Array.Copy(field.Data, j * baselen, val, 0, baselen); + var stripLength = field.Type == 3 ? BitConverter.ToUInt16(val, 0) : BitConverter.ToUInt32(val, 0); + stripLengths.Add(stripLength); } } + } - // Save strips - if (stripOffsets.Count != stripLengths.Count) - throw new NotValidTIFFileException(); - for (int i = 0; i < stripOffsets.Count; i++) - ifd.Strips.Add(new TIFFStrip(data, stripOffsets[i], stripLengths[i])); - - // Offset to next ifd - ifd.NextIFDOffset = conv.ToUInt32(data, offset + 2 + 12 * fieldcount); + // Save strips + if (stripOffsets.Count != stripLengths.Count) + { + throw new NotValidTIFFileException(); + } - return ifd; + for (var i = 0; i < stripOffsets.Count; i++) + { + ifd.Strips.Add(new TIFFStrip(data, stripOffsets[i], stripLengths[i])); } + + // Offset to next ifd + ifd.NextIFDOffset = conv.ToUInt32(data, offset + 2 + (12 * fieldcount)); + + return ifd; } } diff --git a/src/Umbraco.Core/Media/Exif/ImageFileDirectoryEntry.cs b/src/Umbraco.Core/Media/Exif/ImageFileDirectoryEntry.cs index 7d1568afb321..da8a8bf77ace 100644 --- a/src/Umbraco.Core/Media/Exif/ImageFileDirectoryEntry.cs +++ b/src/Umbraco.Core/Media/Exif/ImageFileDirectoryEntry.cs @@ -1,117 +1,137 @@ -using System; +namespace Umbraco.Cms.Core.Media.Exif; -namespace Umbraco.Cms.Core.Media.Exif +/// +/// Represents an entry in the image file directory. +/// +internal struct ImageFileDirectoryEntry { /// - /// Represents an entry in the image file directory. + /// The tag that identifies the field. /// - internal struct ImageFileDirectoryEntry + public ushort Tag; + + /// + /// Field type identifier. + /// + public ushort Type; + + /// + /// Count of Type. + /// + public uint Count; + + /// + /// Field data. + /// + public byte[] Data; + + /// + /// Initializes a new instance of the struct. + /// + /// The tag that identifies the field. + /// Field type identifier. + /// Count of Type. + /// Field data. + public ImageFileDirectoryEntry(ushort tag, ushort type, uint count, byte[] data) { - /// - /// The tag that identifies the field. - /// - public ushort Tag; - /// - /// Field type identifier. - /// - public ushort Type; - /// - /// Count of Type. - /// - public uint Count; - /// - /// Field data. - /// - public byte[] Data; - - /// - /// Initializes a new instance of the struct. - /// - /// The tag that identifies the field. - /// Field type identifier. - /// Count of Type. - /// Field data. - public ImageFileDirectoryEntry(ushort tag, ushort type, uint count, byte[] data) + Tag = tag; + Type = type; + Count = count; + Data = data; + } + + /// + /// Returns a initialized from the given byte data. + /// + /// The data. + /// The offset into . + /// The byte order of . + /// A initialized from the given byte data. + public static ImageFileDirectoryEntry FromBytes(byte[] data, uint offset, BitConverterEx.ByteOrder byteOrder) + { + // Tag ID + var tag = BitConverterEx.ToUInt16(data, offset, byteOrder, BitConverterEx.SystemByteOrder); + + // Tag Type + var type = BitConverterEx.ToUInt16(data, offset + 2, byteOrder, BitConverterEx.SystemByteOrder); + + // Count of Type + var count = BitConverterEx.ToUInt32(data, offset + 4, byteOrder, BitConverterEx.SystemByteOrder); + + // Field value or offset to field data + var value = new byte[4]; + Array.Copy(data, offset + 8, value, 0, 4); + + // Calculate the bytes we need to read + var baselength = GetBaseLength(type); + var totallength = count * baselength; + + // If field value does not fit in 4 bytes + // the value field is an offset to the actual + // field value + if (totallength > 4) { - Tag = tag; - Type = type; - Count = count; - Data = data; + var dataoffset = BitConverterEx.ToUInt32(value, 0, byteOrder, BitConverterEx.SystemByteOrder); + value = new byte[totallength]; + Array.Copy(data, dataoffset, value, 0, totallength); } - /// - /// Returns a initialized from the given byte data. - /// - /// The data. - /// The offset into . - /// The byte order of . - /// A initialized from the given byte data. - public static ImageFileDirectoryEntry FromBytes(byte[] data, uint offset, BitConverterEx.ByteOrder byteOrder) + // Reverse array order if byte orders are different + if (byteOrder != BitConverterEx.SystemByteOrder) { - // Tag ID - ushort tag = BitConverterEx.ToUInt16(data, offset, byteOrder, BitConverterEx.SystemByteOrder); + for (uint i = 0; i < count; i++) + { + var val = new byte[baselength]; + Array.Copy(value, i * baselength, val, 0, baselength); + Array.Reverse(val); + Array.Copy(val, 0, value, i * baselength, baselength); + } + } - // Tag Type - ushort type = BitConverterEx.ToUInt16(data, offset + 2, byteOrder, BitConverterEx.SystemByteOrder); + return new ImageFileDirectoryEntry(tag, type, count, value); + } - // Count of Type - uint count = BitConverterEx.ToUInt32(data, offset + 4, byteOrder, BitConverterEx.SystemByteOrder); + /// + /// Gets the base byte length for the given type. + /// + /// Type identifier. + private static uint GetBaseLength(ushort type) + { + if (type == 1 || type == 6) // BYTE and SBYTE + { + return 1; + } - // Field value or offset to field data - byte[] value = new byte[4]; - Array.Copy(data, offset + 8, value, 0, 4); + if (type == 2 || type == 7) // ASCII and UNDEFINED + { + return 1; + } - // Calculate the bytes we need to read - uint baselength = GetBaseLength(type); - uint totallength = count * baselength; + if (type == 3 || type == 8) // SHORT and SSHORT + { + return 2; + } - // If field value does not fit in 4 bytes - // the value field is an offset to the actual - // field value - if (totallength > 4) - { - uint dataoffset = BitConverterEx.ToUInt32(value, 0, byteOrder, BitConverterEx.SystemByteOrder); - value = new byte[totallength]; - Array.Copy(data, dataoffset, value, 0, totallength); - } + if (type == 4 || type == 9) // LONG and SLONG + { + return 4; + } - // Reverse array order if byte orders are different - if (byteOrder != BitConverterEx.SystemByteOrder) - { - for (uint i = 0; i < count; i++) - { - byte[] val = new byte[baselength]; - Array.Copy(value, i * baselength, val, 0, baselength); - Array.Reverse(val); - Array.Copy(val, 0, value, i * baselength, baselength); - } - } + if (type == 5 || type == 10) // RATIONAL (2xLONG) and SRATIONAL (2xSLONG) + { + return 8; + } - return new ImageFileDirectoryEntry(tag, type, count, value); + if (type == 11) // FLOAT + { + return 4; } - /// - /// Gets the base byte length for the given type. - /// - /// Type identifier. - private static uint GetBaseLength(ushort type) + if (type == 12) // DOUBLE { - if (type == 1 || type == 6) // BYTE and SBYTE - return 1; - else if (type == 2 || type == 7) // ASCII and UNDEFINED - return 1; - else if (type == 3 || type == 8) // SHORT and SSHORT - return 2; - else if (type == 4 || type == 9) // LONG and SLONG - return 4; - else if (type == 5 || type == 10) // RATIONAL (2xLONG) and SRATIONAL (2xSLONG) - return 8; - else if (type == 11) // FLOAT - return 4; - else if (type == 12) // DOUBLE - return 8; - - throw new ArgumentException("Unknown type identifier.", "type"); + return 8; } + + throw new ArgumentException("Unknown type identifier.", "type"); } } diff --git a/src/Umbraco.Core/Media/Exif/ImageFileFormat.cs b/src/Umbraco.Core/Media/Exif/ImageFileFormat.cs index 09cfcce589cd..0797e78d54fe 100644 --- a/src/Umbraco.Core/Media/Exif/ImageFileFormat.cs +++ b/src/Umbraco.Core/Media/Exif/ImageFileFormat.cs @@ -1,25 +1,27 @@ -namespace Umbraco.Cms.Core.Media.Exif +namespace Umbraco.Cms.Core.Media.Exif; + +/// +/// Represents the format of the . +/// +internal enum ImageFileFormat { /// - /// Represents the format of the . + /// The file is not recognized. /// - internal enum ImageFileFormat - { - /// - /// The file is not recognized. - /// - Unknown, - /// - /// The file is a JPEG/Exif or JPEG/JFIF file. - /// - JPEG, - /// - /// The file is a TIFF File. - /// - TIFF, - /// - /// The file is a SVG File. - /// - SVG, - } + Unknown, + + /// + /// The file is a JPEG/Exif or JPEG/JFIF file. + /// + JPEG, + + /// + /// The file is a TIFF File. + /// + TIFF, + + /// + /// The file is a SVG File. + /// + SVG } diff --git a/src/Umbraco.Core/Media/Exif/JFIFEnums.cs b/src/Umbraco.Core/Media/Exif/JFIFEnums.cs index ff6b0463ed3b..a9d112e3ea77 100644 --- a/src/Umbraco.Core/Media/Exif/JFIFEnums.cs +++ b/src/Umbraco.Core/Media/Exif/JFIFEnums.cs @@ -1,40 +1,44 @@ -namespace Umbraco.Cms.Core.Media.Exif +namespace Umbraco.Cms.Core.Media.Exif; + +/// +/// Represents the units for the X and Y densities +/// for a JFIF file. +/// +internal enum JFIFDensityUnit : byte { /// - /// Represents the units for the X and Y densities - /// for a JFIF file. + /// No units, XDensity and YDensity specify the pixel aspect ratio. /// - internal enum JFIFDensityUnit : byte - { - /// - /// No units, XDensity and YDensity specify the pixel aspect ratio. - /// - None = 0, - /// - /// XDensity and YDensity are dots per inch. - /// - DotsPerInch = 1, - /// - /// XDensity and YDensity are dots per cm. - /// - DotsPerCm = 2, - } + None = 0, + /// - /// Represents the JFIF extension. + /// XDensity and YDensity are dots per inch. /// - internal enum JFIFExtension : byte - { - /// - /// Thumbnail coded using JPEG. - /// - ThumbnailJPEG = 0x10, - /// - /// Thumbnail stored using a 256-Color RGB palette. - /// - ThumbnailPaletteRGB = 0x11, - /// - /// Thumbnail stored using 3 bytes/pixel (24-bit) RGB values. - /// - Thumbnail24BitRGB = 0x13, - } + DotsPerInch = 1, + + /// + /// XDensity and YDensity are dots per cm. + /// + DotsPerCm = 2 +} + +/// +/// Represents the JFIF extension. +/// +internal enum JFIFExtension : byte +{ + /// + /// Thumbnail coded using JPEG. + /// + ThumbnailJPEG = 0x10, + + /// + /// Thumbnail stored using a 256-Color RGB palette. + /// + ThumbnailPaletteRGB = 0x11, + + /// + /// Thumbnail stored using 3 bytes/pixel (24-bit) RGB values. + /// + Thumbnail24BitRGB = 0x13 } diff --git a/src/Umbraco.Core/Media/Exif/JFIFExtendedProperty.cs b/src/Umbraco.Core/Media/Exif/JFIFExtendedProperty.cs index d3a0e7fb46d7..eb8d2ba493a9 100644 --- a/src/Umbraco.Core/Media/Exif/JFIFExtendedProperty.cs +++ b/src/Umbraco.Core/Media/Exif/JFIFExtendedProperty.cs @@ -1,67 +1,78 @@ -using System; +namespace Umbraco.Cms.Core.Media.Exif; -namespace Umbraco.Cms.Core.Media.Exif +/// +/// Represents the JFIF version as a 16 bit unsigned integer. (EXIF Specification: SHORT) +/// +internal class JFIFVersion : ExifUShort { - /// - /// Represents the JFIF version as a 16 bit unsigned integer. (EXIF Specification: SHORT) - /// - internal class JFIFVersion : ExifUShort + public JFIFVersion(ExifTag tag, ushort value) + : base(tag, value) { - /// - /// Gets the major version. - /// - public byte Major { get { return (byte)(mValue >> 8); } } - /// - /// Gets the minor version. - /// - public byte Minor { get { return (byte)(mValue - (mValue >> 8) * 256); } } - - public JFIFVersion(ExifTag tag, ushort value) - : base(tag, value) - { + } - } + /// + /// Gets the major version. + /// + public byte Major => (byte)(mValue >> 8); - public override string ToString() - { - return string.Format("{0}.{1:00}", Major, Minor); - } - } /// - /// Represents a JFIF thumbnail. (EXIF Specification: BYTE) + /// Gets the minor version. /// - internal class JFIFThumbnailProperty : ExifProperty + public byte Minor => (byte)(mValue - ((mValue >> 8) * 256)); + + public override string ToString() => string.Format("{0}.{1:00}", Major, Minor); +} + +/// +/// Represents a JFIF thumbnail. (EXIF Specification: BYTE) +/// +internal class JFIFThumbnailProperty : ExifProperty +{ + protected JFIFThumbnail mValue; + + public JFIFThumbnailProperty(ExifTag tag, JFIFThumbnail value) + : base(tag) => + mValue = value; + + protected override object _Value { - protected JFIFThumbnail mValue; - protected override object _Value { get { return Value; } set { Value = (JFIFThumbnail)value; } } - public new JFIFThumbnail Value { get { return mValue; } set { mValue = value; } } + get => Value; + set => Value = (JFIFThumbnail)value; + } - public override string ToString() { return mValue.Format.ToString(); } + public new JFIFThumbnail Value + { + get => mValue; + set => mValue = value; + } - public JFIFThumbnailProperty(ExifTag tag, JFIFThumbnail value) - : base(tag) + public override ExifInterOperability Interoperability + { + get { - mValue = value; - } + if (mValue.Format == JFIFThumbnail.ImageFormat.BMP24Bit) + { + return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 1, (uint)mValue.PixelData.Length, + mValue.PixelData); + } - public override ExifInterOperability Interoperability - { - get + if (mValue.Format == JFIFThumbnail.ImageFormat.BMPPalette) { - if (mValue.Format == JFIFThumbnail.ImageFormat.BMP24Bit) - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 1, (uint)mValue.PixelData.Length, mValue.PixelData); - else if (mValue.Format == JFIFThumbnail.ImageFormat.BMPPalette) - { - byte[] data = new byte[mValue.Palette.Length + mValue.PixelData.Length]; - Array.Copy(mValue.Palette, data, mValue.Palette.Length); - Array.Copy(mValue.PixelData, 0, data, mValue.Palette.Length, mValue.PixelData.Length); - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 1, (uint)data.Length, data); - } - else if (mValue.Format == JFIFThumbnail.ImageFormat.JPEG) - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 1, (uint)mValue.PixelData.Length, mValue.PixelData); - else - throw new InvalidOperationException("Unknown thumbnail type."); + var data = new byte[mValue.Palette.Length + mValue.PixelData.Length]; + Array.Copy(mValue.Palette, data, mValue.Palette.Length); + Array.Copy(mValue.PixelData, 0, data, mValue.Palette.Length, mValue.PixelData.Length); + return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 1, (uint)data.Length, data); } + + if (mValue.Format == JFIFThumbnail.ImageFormat.JPEG) + { + return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 1, (uint)mValue.PixelData.Length, + mValue.PixelData); + } + + throw new InvalidOperationException("Unknown thumbnail type."); } } + + public override string ToString() => mValue.Format.ToString(); } diff --git a/src/Umbraco.Core/Media/Exif/JFIFThumbnail.cs b/src/Umbraco.Core/Media/Exif/JFIFThumbnail.cs index de9fe8f76fb7..ac85377190dd 100644 --- a/src/Umbraco.Core/Media/Exif/JFIFThumbnail.cs +++ b/src/Umbraco.Core/Media/Exif/JFIFThumbnail.cs @@ -1,55 +1,62 @@ -namespace Umbraco.Cms.Core.Media.Exif +namespace Umbraco.Cms.Core.Media.Exif; + +/// +/// Represents a JFIF thumbnail. +/// +internal class JFIFThumbnail { + #region Public Enums + + public enum ImageFormat + { + JPEG, + BMPPalette, + BMP24Bit + } + + #endregion + + #region Properties + + /// + /// Gets the 256 color RGB palette. + /// + public byte[] Palette { get; } + /// - /// Represents a JFIF thumbnail. + /// Gets raw image data. /// - internal class JFIFThumbnail + public byte[] PixelData { get; } + + /// + /// Gets the image format. + /// + public ImageFormat Format { get; } + + #endregion + + #region Constructors + + protected JFIFThumbnail() { - #region Properties - /// - /// Gets the 256 color RGB palette. - /// - public byte[] Palette { get; private set; } - /// - /// Gets raw image data. - /// - public byte[] PixelData { get; private set; } - /// - /// Gets the image format. - /// - public ImageFormat Format { get; private set; } - #endregion - - #region Public Enums - public enum ImageFormat - { - JPEG, - BMPPalette, - BMP24Bit, - } - #endregion - - #region Constructors - protected JFIFThumbnail() - { - Palette = new byte[0]; - PixelData = new byte[0]; - } - - public JFIFThumbnail(ImageFormat format, byte[] data) - : this() - { - Format = format; - PixelData = data; - } - - public JFIFThumbnail(byte[] palette, byte[] data) - : this() - { - Format = ImageFormat.BMPPalette; - Palette = palette; - PixelData = data; - } - #endregion + Palette = new byte[0]; + PixelData = new byte[0]; } + + public JFIFThumbnail(ImageFormat format, byte[] data) + : this() + { + Format = format; + PixelData = data; + } + + public JFIFThumbnail(byte[] palette, byte[] data) + : this() + { + Format = ImageFormat.BMPPalette; + Palette = palette; + PixelData = data; + } + + #endregion } diff --git a/src/Umbraco.Core/Media/Exif/JPEGExceptions.cs b/src/Umbraco.Core/Media/Exif/JPEGExceptions.cs index dde0326f9996..a0ba1035f0ee 100644 --- a/src/Umbraco.Core/Media/Exif/JPEGExceptions.cs +++ b/src/Umbraco.Core/Media/Exif/JPEGExceptions.cs @@ -1,171 +1,219 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Media.Exif +namespace Umbraco.Cms.Core.Media.Exif; + +/// +/// The exception that is thrown when the format of the JPEG file could not be understood. +/// +/// +[Serializable] +public class NotValidJPEGFileException : Exception +{ + /// + /// Initializes a new instance of the class. + /// + public NotValidJPEGFileException() + : base("Not a valid JPEG file.") + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public NotValidJPEGFileException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// + /// The exception that is the cause of the current exception, or a null reference ( + /// in Visual Basic) if no inner exception is specified. + /// + public NotValidJPEGFileException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The that holds the serialized object + /// data about the exception being thrown. + /// + /// + /// The that contains contextual + /// information about the source or destination. + /// + protected NotValidJPEGFileException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } +} + +/// +/// The exception that is thrown when the format of the TIFF file could not be understood. +/// +/// +[Serializable] +public class NotValidTIFFileException : Exception +{ + /// + /// Initializes a new instance of the class. + /// + public NotValidTIFFileException() + : base("Not a valid TIFF file.") + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public NotValidTIFFileException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// + /// The exception that is the cause of the current exception, or a null reference ( + /// in Visual Basic) if no inner exception is specified. + /// + public NotValidTIFFileException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The that holds the serialized object + /// data about the exception being thrown. + /// + /// + /// The that contains contextual + /// information about the source or destination. + /// + protected NotValidTIFFileException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } +} + +/// +/// The exception that is thrown when the format of the TIFF header could not be understood. +/// +/// +[Serializable] +internal class NotValidTIFFHeader : Exception { + /// + /// Initializes a new instance of the class. + /// + public NotValidTIFFHeader() + : base("Not a valid TIFF header.") + { + } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public NotValidTIFFHeader(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// + /// The exception that is the cause of the current exception, or a null reference ( + /// in Visual Basic) if no inner exception is specified. + /// + public NotValidTIFFHeader(string message, Exception innerException) + : base(message, innerException) + { + } /// - /// The exception that is thrown when the format of the JPEG file could not be understood. - /// - /// - [Serializable] - public class NotValidJPEGFileException : Exception - { - /// - /// Initializes a new instance of the class. - /// - public NotValidJPEGFileException() - : base("Not a valid JPEG file.") - { } - - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public NotValidJPEGFileException(string message) - : base(message) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. - public NotValidJPEGFileException(string message, Exception innerException) - : base(message, innerException) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected NotValidJPEGFileException(SerializationInfo info, StreamingContext context) - : base(info, context) - { } - } - - /// - /// The exception that is thrown when the format of the TIFF file could not be understood. - /// - /// - [Serializable] - public class NotValidTIFFileException : Exception - { - /// - /// Initializes a new instance of the class. - /// - public NotValidTIFFileException() - : base("Not a valid TIFF file.") - { } - - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public NotValidTIFFileException(string message) - : base(message) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. - public NotValidTIFFileException(string message, Exception innerException) - : base(message, innerException) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected NotValidTIFFileException(SerializationInfo info, StreamingContext context) - : base(info, context) - { } - } - - /// - /// The exception that is thrown when the format of the TIFF header could not be understood. - /// - /// - [Serializable] - internal class NotValidTIFFHeader : Exception - { - /// - /// Initializes a new instance of the class. - /// - public NotValidTIFFHeader() - : base("Not a valid TIFF header.") - { } - - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public NotValidTIFFHeader(string message) - : base(message) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. - public NotValidTIFFHeader(string message, Exception innerException) - : base(message, innerException) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected NotValidTIFFHeader(SerializationInfo info, StreamingContext context) - : base(info, context) - { } - } - - /// - /// The exception that is thrown when the length of a section exceeds 64 kB. - /// - /// - [Serializable] - public class SectionExceeds64KBException : Exception - { - /// - /// Initializes a new instance of the class. - /// - public SectionExceeds64KBException() - : base("Section length exceeds 64 kB.") - { } - - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public SectionExceeds64KBException(string message) - : base(message) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception, or a null reference ( in Visual Basic) if no inner exception is specified. - public SectionExceeds64KBException(string message, Exception innerException) - : base(message, innerException) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected SectionExceeds64KBException(SerializationInfo info, StreamingContext context) - : base(info, context) - { } + /// Initializes a new instance of the class. + /// + /// + /// The that holds the serialized object + /// data about the exception being thrown. + /// + /// + /// The that contains contextual + /// information about the source or destination. + /// + protected NotValidTIFFHeader(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } +} + +/// +/// The exception that is thrown when the length of a section exceeds 64 kB. +/// +/// +[Serializable] +public class SectionExceeds64KBException : Exception +{ + /// + /// Initializes a new instance of the class. + /// + public SectionExceeds64KBException() + : base("Section length exceeds 64 kB.") + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public SectionExceeds64KBException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// + /// The exception that is the cause of the current exception, or a null reference ( + /// in Visual Basic) if no inner exception is specified. + /// + public SectionExceeds64KBException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The that holds the serialized object + /// data about the exception being thrown. + /// + /// + /// The that contains contextual + /// information about the source or destination. + /// + protected SectionExceeds64KBException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } } diff --git a/src/Umbraco.Core/Media/Exif/JPEGFile.cs b/src/Umbraco.Core/Media/Exif/JPEGFile.cs index f0f732b520f1..963ae3be56b1 100644 --- a/src/Umbraco.Core/Media/Exif/JPEGFile.cs +++ b/src/Umbraco.Core/Media/Exif/JPEGFile.cs @@ -1,924 +1,1094 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; +using System.Text; -namespace Umbraco.Cms.Core.Media.Exif +namespace Umbraco.Cms.Core.Media.Exif; + +/// +/// Represents the binary view of a JPEG compressed file. +/// +internal class JPEGFile : ImageFile { + #region Constructor + /// - /// Represents the binary view of a JPEG compressed file. + /// Initializes a new instance of the class. /// - internal class JPEGFile : ImageFile + /// A that contains image data. + /// The encoding to be used for text metadata when the source encoding is unknown. + protected internal JPEGFile(Stream stream, Encoding encoding) { - #region Member Variables - private JPEGSection? jfifApp0; - private JPEGSection? jfxxApp0; - private JPEGSection? exifApp1; - private uint makerNoteOffset; - private long exifIFDFieldOffset, gpsIFDFieldOffset, interopIFDFieldOffset, firstIFDFieldOffset; - private long thumbOffsetLocation, thumbSizeLocation; - private uint thumbOffsetValue, thumbSizeValue; - private bool makerNoteProcessed; - #endregion - - #region Properties - /// - /// Gets or sets the byte-order of the Exif properties. - /// - public BitConverterEx.ByteOrder ByteOrder { get; set; } - /// - /// Gets or sets the sections contained in the . - /// - public List Sections { get; private set; } - /// - /// Gets or sets non-standard trailing data following the End of Image (EOI) marker. - /// - public byte[] TrailingData { get; private set; } - #endregion - - #region Constructor - /// - /// Initializes a new instance of the class. - /// - /// A that contains image data. - /// The encoding to be used for text metadata when the source encoding is unknown. - protected internal JPEGFile(Stream stream, Encoding encoding) - { - Format = ImageFileFormat.JPEG; - Sections = new List(); - TrailingData = new byte[0]; - Encoding = encoding; - - stream.Seek(0, SeekOrigin.Begin); - - // Read the Start of Image (SOI) marker. SOI marker is represented - // with two bytes: 0xFF, 0xD8. - byte[] markerbytes = new byte[2]; - if (stream.Read(markerbytes, 0, 2) != 2 || markerbytes[0] != 0xFF || markerbytes[1] != 0xD8) + Format = ImageFileFormat.JPEG; + Sections = new List(); + TrailingData = new byte[0]; + Encoding = encoding; + + stream.Seek(0, SeekOrigin.Begin); + + // Read the Start of Image (SOI) marker. SOI marker is represented + // with two bytes: 0xFF, 0xD8. + var markerbytes = new byte[2]; + if (stream.Read(markerbytes, 0, 2) != 2 || markerbytes[0] != 0xFF || markerbytes[1] != 0xD8) + { + throw new NotValidJPEGFileException(); + } + + stream.Seek(0, SeekOrigin.Begin); + + // Search and read sections until we reach the end of file. + while (stream.Position != stream.Length) + { + // Read the next section marker. Section markers are two bytes + // with values 0xFF, 0x?? where ?? must not be 0x00 or 0xFF. + if (stream.Read(markerbytes, 0, 2) != 2 || markerbytes[0] != 0xFF || markerbytes[1] == 0x00 || + markerbytes[1] == 0xFF) + { throw new NotValidJPEGFileException(); - stream.Seek(0, SeekOrigin.Begin); + } - // Search and read sections until we reach the end of file. - while (stream.Position != stream.Length) + var marker = (JPEGMarker)markerbytes[1]; + + var header = new byte[0]; + // SOI, EOI and RST markers do not contain any header + if (marker != JPEGMarker.SOI && marker != JPEGMarker.EOI && + !(marker >= JPEGMarker.RST0 && marker <= JPEGMarker.RST7)) { - // Read the next section marker. Section markers are two bytes - // with values 0xFF, 0x?? where ?? must not be 0x00 or 0xFF. - if (stream.Read(markerbytes, 0, 2) != 2 || markerbytes[0] != 0xFF || markerbytes[1] == 0x00 || markerbytes[1] == 0xFF) + // Length of the header including the length bytes. + // This value is a 16-bit unsigned integer + // in big endian byte-order. + var lengthbytes = new byte[2]; + if (stream.Read(lengthbytes, 0, 2) != 2) + { throw new NotValidJPEGFileException(); + } - JPEGMarker marker = (JPEGMarker)markerbytes[1]; + long length = BitConverterEx.BigEndian.ToUInt16(lengthbytes, 0); - byte[] header = new byte[0]; - // SOI, EOI and RST markers do not contain any header - if (marker != JPEGMarker.SOI && marker != JPEGMarker.EOI && !(marker >= JPEGMarker.RST0 && marker <= JPEGMarker.RST7)) + // Read section header. + header = new byte[length - 2]; + var bytestoread = header.Length; + while (bytestoread > 0) { - // Length of the header including the length bytes. - // This value is a 16-bit unsigned integer - // in big endian byte-order. - byte[] lengthbytes = new byte[2]; - if (stream.Read(lengthbytes, 0, 2) != 2) - throw new NotValidJPEGFileException(); - long length = (long)BitConverterEx.BigEndian.ToUInt16(lengthbytes, 0); - - // Read section header. - header = new byte[length - 2]; - int bytestoread = header.Length; - while (bytestoread > 0) + var count = Math.Min(bytestoread, 4 * 1024); + var bytesread = stream.Read(header, header.Length - bytestoread, count); + if (bytesread == 0) { - int count = Math.Min(bytestoread, 4 * 1024); - int bytesread = stream.Read(header, header.Length - bytestoread, count); - if (bytesread == 0) - throw new NotValidJPEGFileException(); - bytestoread -= bytesread; + throw new NotValidJPEGFileException(); } + + bytestoread -= bytesread; } + } - // Start of Scan (SOS) sections and RST sections are immediately - // followed by entropy coded data. For that, we need to read until - // the next section marker once we reach a SOS or RST. - byte[] entropydata = new byte[0]; - if (marker == JPEGMarker.SOS || (marker >= JPEGMarker.RST0 && marker <= JPEGMarker.RST7)) - { - long position = stream.Position; + // Start of Scan (SOS) sections and RST sections are immediately + // followed by entropy coded data. For that, we need to read until + // the next section marker once we reach a SOS or RST. + var entropydata = new byte[0]; + if (marker == JPEGMarker.SOS || (marker >= JPEGMarker.RST0 && marker <= JPEGMarker.RST7)) + { + var position = stream.Position; - // Search for the next section marker - while (true) + // Search for the next section marker + while (true) + { + // Search for an 0xFF indicating start of a marker + var nextbyte = 0; + do { - // Search for an 0xFF indicating start of a marker - int nextbyte = 0; - do + nextbyte = stream.ReadByte(); + if (nextbyte == -1) { - nextbyte = stream.ReadByte(); - if (nextbyte == -1) - throw new NotValidJPEGFileException(); - } while ((byte)nextbyte != 0xFF); + throw new NotValidJPEGFileException(); + } + } while ((byte)nextbyte != 0xFF); - // Skip filler bytes (0xFF) - do + // Skip filler bytes (0xFF) + do + { + nextbyte = stream.ReadByte(); + if (nextbyte == -1) { - nextbyte = stream.ReadByte(); - if (nextbyte == -1) - throw new NotValidJPEGFileException(); - } while ((byte)nextbyte == 0xFF); + throw new NotValidJPEGFileException(); + } + } while ((byte)nextbyte == 0xFF); - // Looks like a section marker. The next byte must not be 0x00. - if ((byte)nextbyte != 0x00) + // Looks like a section marker. The next byte must not be 0x00. + if ((byte)nextbyte != 0x00) + { + // We reached a section marker. Calculate the + // length of the entropy coded data. + stream.Seek(-2, SeekOrigin.Current); + var edlength = stream.Position - position; + stream.Seek(-edlength, SeekOrigin.Current); + + // Read entropy coded data + entropydata = new byte[edlength]; + var bytestoread = entropydata.Length; + while (bytestoread > 0) { - // We reached a section marker. Calculate the - // length of the entropy coded data. - stream.Seek(-2, SeekOrigin.Current); - long edlength = stream.Position - position; - stream.Seek(-edlength, SeekOrigin.Current); - - // Read entropy coded data - entropydata = new byte[edlength]; - int bytestoread = entropydata.Length; - while (bytestoread > 0) + var count = Math.Min(bytestoread, 4 * 1024); + var bytesread = stream.Read(entropydata, entropydata.Length - bytestoread, count); + if (bytesread == 0) { - int count = Math.Min(bytestoread, 4 * 1024); - int bytesread = stream.Read(entropydata, entropydata.Length - bytestoread, count); - if (bytesread == 0) - throw new NotValidJPEGFileException(); - bytestoread -= bytesread; + throw new NotValidJPEGFileException(); } - break; + bytestoread -= bytesread; } + + break; } } + } - // Store section. - JPEGSection section = new JPEGSection(marker, header, entropydata); - Sections.Add(section); + // Store section. + var section = new JPEGSection(marker, header, entropydata); + Sections.Add(section); - // Some propriety formats store data past the EOI marker - if (marker == JPEGMarker.EOI) + // Some propriety formats store data past the EOI marker + if (marker == JPEGMarker.EOI) + { + var bytestoread = (int)(stream.Length - stream.Position); + TrailingData = new byte[bytestoread]; + while (bytestoread > 0) { - int bytestoread = (int)(stream.Length - stream.Position); - TrailingData = new byte[bytestoread]; - while (bytestoread > 0) + var count = Math.Min(bytestoread, 4 * 1024); + var bytesread = stream.Read(TrailingData, TrailingData.Length - bytestoread, count); + if (bytesread == 0) { - int count = (int)Math.Min(bytestoread, 4 * 1024); - int bytesread = stream.Read(TrailingData, TrailingData.Length - bytestoread, count); - if (bytesread == 0) - throw new NotValidJPEGFileException(); - bytestoread -= bytesread; + throw new NotValidJPEGFileException(); } + + bytestoread -= bytesread; } } + } - // Read metadata sections - ReadJFIFAPP0(); - ReadJFXXAPP0(); - ReadExifAPP1(); + // Read metadata sections + ReadJFIFAPP0(); + ReadJFXXAPP0(); + ReadExifAPP1(); - // Process the maker note - makerNoteProcessed = false; - } - #endregion + // Process the maker note + makerNoteProcessed = false; + } + + #endregion + + #region Member Variables - #region Instance Methods - /// - /// Saves the JPEG/Exif image to the given stream. - /// - /// The path to the JPEG/Exif file. - /// Determines whether the maker note offset of - /// the original file will be preserved. - public void Save(Stream stream, bool preserveMakerNote) + private JPEGSection? jfifApp0; + private JPEGSection? jfxxApp0; + private JPEGSection? exifApp1; + private uint makerNoteOffset; + private long exifIFDFieldOffset, gpsIFDFieldOffset, interopIFDFieldOffset, firstIFDFieldOffset; + private long thumbOffsetLocation, thumbSizeLocation; + private uint thumbOffsetValue, thumbSizeValue; + private readonly bool makerNoteProcessed; + + #endregion + + #region Properties + + /// + /// Gets or sets the byte-order of the Exif properties. + /// + public BitConverterEx.ByteOrder ByteOrder { get; set; } + + /// + /// Gets or sets the sections contained in the . + /// + public List Sections { get; } + + /// + /// Gets or sets non-standard trailing data following the End of Image (EOI) marker. + /// + public byte[] TrailingData { get; } + + #endregion + + #region Instance Methods + + /// + /// Saves the JPEG/Exif image to the given stream. + /// + /// The path to the JPEG/Exif file. + /// + /// Determines whether the maker note offset of + /// the original file will be preserved. + /// + public void Save(Stream stream, bool preserveMakerNote) + { + WriteJFIFApp0(); + WriteJFXXApp0(); + WriteExifApp1(preserveMakerNote); + + // Write sections + foreach (JPEGSection section in Sections) { - WriteJFIFApp0(); - WriteJFXXApp0(); - WriteExifApp1(preserveMakerNote); + // Section header (including length bytes and section marker) + // must not exceed 64 kB. + if (section.Header.Length + 2 + 2 > 64 * 1024) + { + throw new SectionExceeds64KBException(); + } - // Write sections - foreach (JPEGSection section in Sections) + // APP sections must have a header. + // Otherwise skip the entire section. + if (section.Marker >= JPEGMarker.APP0 && section.Marker <= JPEGMarker.APP15 && section.Header.Length == 0) { - // Section header (including length bytes and section marker) - // must not exceed 64 kB. - if (section.Header.Length + 2 + 2 > 64 * 1024) - throw new SectionExceeds64KBException(); + continue; + } - // APP sections must have a header. - // Otherwise skip the entire section. - if (section.Marker >= JPEGMarker.APP0 && section.Marker <= JPEGMarker.APP15 && section.Header.Length == 0) - continue; + // Write section marker + stream.Write(new byte[] {0xFF, (byte)section.Marker}, 0, 2); - // Write section marker - stream.Write(new byte[] { 0xFF, (byte)section.Marker }, 0, 2); + // SOI, EOI and RST markers do not contain any header + if (section.Marker != JPEGMarker.SOI && section.Marker != JPEGMarker.EOI && + !(section.Marker >= JPEGMarker.RST0 && section.Marker <= JPEGMarker.RST7)) + { + // Header length including the length field itself + stream.Write(BitConverterEx.BigEndian.GetBytes((ushort)(section.Header.Length + 2)), 0, 2); - // SOI, EOI and RST markers do not contain any header - if (section.Marker != JPEGMarker.SOI && section.Marker != JPEGMarker.EOI && !(section.Marker >= JPEGMarker.RST0 && section.Marker <= JPEGMarker.RST7)) + // Write section header + if (section.Header.Length != 0) { - // Header length including the length field itself - stream.Write(BitConverterEx.BigEndian.GetBytes((ushort)(section.Header.Length + 2)), 0, 2); - - // Write section header - if (section.Header.Length != 0) - stream.Write(section.Header, 0, section.Header.Length); + stream.Write(section.Header, 0, section.Header.Length); } - - // Write entropy coded data - if (section.EntropyData.Length != 0) - stream.Write(section.EntropyData, 0, section.EntropyData.Length); } - // Write trailing data, if any - if (TrailingData.Length != 0) - stream.Write(TrailingData, 0, TrailingData.Length); - } - - /// - /// Saves the JPEG/Exif image with the given filename. - /// - /// The path to the JPEG/Exif file. - /// Determines whether the maker note offset of - /// the original file will be preserved. - public void Save(string filename, bool preserveMakerNote) - { - using (FileStream stream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None)) + // Write entropy coded data + if (section.EntropyData.Length != 0) { - Save(stream, preserveMakerNote); + stream.Write(section.EntropyData, 0, section.EntropyData.Length); } } - /// - /// Saves the JPEG/Exif image with the given filename. - /// - /// The path to the JPEG/Exif file. - public override void Save(string filename) + // Write trailing data, if any + if (TrailingData.Length != 0) { - Save(filename, true); + stream.Write(TrailingData, 0, TrailingData.Length); } + } - /// - /// Saves the JPEG/Exif image to the given stream. - /// - /// The path to the JPEG/Exif file. - public override void Save(Stream stream) + /// + /// Saves the JPEG/Exif image with the given filename. + /// + /// The path to the JPEG/Exif file. + /// + /// Determines whether the maker note offset of + /// the original file will be preserved. + /// + public void Save(string filename, bool preserveMakerNote) + { + using (var stream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None)) { - Save(stream, true); + Save(stream, preserveMakerNote); } + } - #endregion - - #region Private Helper Methods - /// - /// Reads the APP0 section containing JFIF metadata. - /// - private void ReadJFIFAPP0() - { - // Find the APP0 section containing JFIF metadata - jfifApp0 = Sections.Find(a => (a.Marker == JPEGMarker.APP0) && - a.Header.Length >= 5 && - (Encoding.ASCII.GetString(a.Header, 0, 5) == "JFIF\0")); + /// + /// Saves the JPEG/Exif image with the given filename. + /// + /// The path to the JPEG/Exif file. + public override void Save(string filename) => Save(filename, true); - // If there is no APP0 section, return. - if (jfifApp0 == null) - return; + /// + /// Saves the JPEG/Exif image to the given stream. + /// + /// The path to the JPEG/Exif file. + public override void Save(Stream stream) => Save(stream, true); - byte[] header = jfifApp0.Header; - BitConverterEx jfifConv = BitConverterEx.BigEndian; + #endregion - // Version - ushort version = jfifConv.ToUInt16(header, 5); - Properties.Add(new JFIFVersion(ExifTag.JFIFVersion, version)); + #region Private Helper Methods - // Units - byte unit = header[7]; - Properties.Add(new ExifEnumProperty(ExifTag.JFIFUnits, (JFIFDensityUnit)unit)); + /// + /// Reads the APP0 section containing JFIF metadata. + /// + private void ReadJFIFAPP0() + { + // Find the APP0 section containing JFIF metadata + jfifApp0 = Sections.Find(a => a.Marker == JPEGMarker.APP0 && + a.Header.Length >= 5 && + Encoding.ASCII.GetString(a.Header, 0, 5) == "JFIF\0"); - // X and Y densities - ushort xdensity = jfifConv.ToUInt16(header, 8); - Properties.Add(new ExifUShort(ExifTag.XDensity, xdensity)); - ushort ydensity = jfifConv.ToUInt16(header, 10); - Properties.Add(new ExifUShort(ExifTag.YDensity, ydensity)); + // If there is no APP0 section, return. + if (jfifApp0 == null) + { + return; + } - // Thumbnails pixel count - byte xthumbnail = header[12]; - Properties.Add(new ExifByte(ExifTag.JFIFXThumbnail, xthumbnail)); - byte ythumbnail = header[13]; - Properties.Add(new ExifByte(ExifTag.JFIFYThumbnail, ythumbnail)); + var header = jfifApp0.Header; + BitConverterEx jfifConv = BitConverterEx.BigEndian; + + // Version + var version = jfifConv.ToUInt16(header, 5); + Properties.Add(new JFIFVersion(ExifTag.JFIFVersion, version)); + + // Units + var unit = header[7]; + Properties.Add(new ExifEnumProperty(ExifTag.JFIFUnits, (JFIFDensityUnit)unit)); + + // X and Y densities + var xdensity = jfifConv.ToUInt16(header, 8); + Properties.Add(new ExifUShort(ExifTag.XDensity, xdensity)); + var ydensity = jfifConv.ToUInt16(header, 10); + Properties.Add(new ExifUShort(ExifTag.YDensity, ydensity)); + + // Thumbnails pixel count + var xthumbnail = header[12]; + Properties.Add(new ExifByte(ExifTag.JFIFXThumbnail, xthumbnail)); + var ythumbnail = header[13]; + Properties.Add(new ExifByte(ExifTag.JFIFYThumbnail, ythumbnail)); + + // Read JFIF thumbnail + var n = xthumbnail * ythumbnail; + var jfifThumbnail = new byte[n]; + Array.Copy(header, 14, jfifThumbnail, 0, n); + Properties.Add(new JFIFThumbnailProperty(ExifTag.JFIFThumbnail, + new JFIFThumbnail(JFIFThumbnail.ImageFormat.JPEG, jfifThumbnail))); + } - // Read JFIF thumbnail - int n = xthumbnail * ythumbnail; - byte[] jfifThumbnail = new byte[n]; - Array.Copy(header, 14, jfifThumbnail, 0, n); - Properties.Add(new JFIFThumbnailProperty(ExifTag.JFIFThumbnail, new JFIFThumbnail(JFIFThumbnail.ImageFormat.JPEG, jfifThumbnail))); - } - /// - /// Replaces the contents of the APP0 section with the JFIF properties. - /// - private bool WriteJFIFApp0() + /// + /// Replaces the contents of the APP0 section with the JFIF properties. + /// + private bool WriteJFIFApp0() + { + // Which IFD sections do we have? + var ifdjfef = new List(); + foreach (ExifProperty prop in Properties) { - // Which IFD sections do we have? - List ifdjfef = new List(); - foreach (ExifProperty prop in Properties) + if (prop.IFD == IFD.JFIF) { - if (prop.IFD == IFD.JFIF) - ifdjfef.Add(prop); + ifdjfef.Add(prop); } + } - if (ifdjfef.Count == 0) - { - // Nothing to write - return false; - } + if (ifdjfef.Count == 0) + { + // Nothing to write + return false; + } - // Create a memory stream to write the APP0 section to - MemoryStream ms = new MemoryStream(); + // Create a memory stream to write the APP0 section to + var ms = new MemoryStream(); - // JFIF identifier - ms.Write(Encoding.ASCII.GetBytes("JFIF\0"), 0, 5); + // JFIF identifier + ms.Write(Encoding.ASCII.GetBytes("JFIF\0"), 0, 5); - // Write tags - foreach (ExifProperty prop in ifdjfef) + // Write tags + foreach (ExifProperty prop in ifdjfef) + { + ExifInterOperability interop = prop.Interoperability; + var data = interop.Data; + if (BitConverterEx.SystemByteOrder != BitConverterEx.ByteOrder.BigEndian && interop.TypeID == 3) { - ExifInterOperability interop = prop.Interoperability; - byte[] data = interop.Data; - if (BitConverterEx.SystemByteOrder != BitConverterEx.ByteOrder.BigEndian && interop.TypeID == 3) - Array.Reverse(data); - ms.Write(data, 0, data.Length); + Array.Reverse(data); } - ms.Close(); + ms.Write(data, 0, data.Length); + } - // Return APP0 header - if (jfifApp0 is not null) - { - jfifApp0.Header = ms.ToArray(); - return true; - } + ms.Close(); - return false; + // Return APP0 header + if (jfifApp0 is not null) + { + jfifApp0.Header = ms.ToArray(); + return true; } - /// - /// Reads the APP0 section containing JFIF extension metadata. - /// - private void ReadJFXXAPP0() + return false; + } + + /// + /// Reads the APP0 section containing JFIF extension metadata. + /// + private void ReadJFXXAPP0() + { + // Find the APP0 section containing JFIF metadata + jfxxApp0 = Sections.Find(a => a.Marker == JPEGMarker.APP0 && + a.Header.Length >= 5 && + Encoding.ASCII.GetString(a.Header, 0, 5) == "JFXX\0"); + + // If there is no APP0 section, return. + if (jfxxApp0 == null) { - // Find the APP0 section containing JFIF metadata - jfxxApp0 = Sections.Find(a => (a.Marker == JPEGMarker.APP0) && - a.Header.Length >= 5 && - (Encoding.ASCII.GetString(a.Header, 0, 5) == "JFXX\0")); + return; + } - // If there is no APP0 section, return. - if (jfxxApp0 == null) - return; + var header = jfxxApp0.Header; - byte[] header = jfxxApp0.Header; + // Version + var version = (JFIFExtension)header[5]; + Properties.Add(new ExifEnumProperty(ExifTag.JFXXExtensionCode, version)); - // Version - JFIFExtension version = (JFIFExtension)header[5]; - Properties.Add(new ExifEnumProperty(ExifTag.JFXXExtensionCode, version)); + // Read thumbnail + if (version == JFIFExtension.ThumbnailJPEG) + { + var data = new byte[header.Length - 6]; + Array.Copy(header, 6, data, 0, data.Length); + Properties.Add(new JFIFThumbnailProperty(ExifTag.JFXXThumbnail, + new JFIFThumbnail(JFIFThumbnail.ImageFormat.JPEG, data))); + } + else if (version == JFIFExtension.Thumbnail24BitRGB) + { + // Thumbnails pixel count + var xthumbnail = header[6]; + Properties.Add(new ExifByte(ExifTag.JFXXXThumbnail, xthumbnail)); + var ythumbnail = header[7]; + Properties.Add(new ExifByte(ExifTag.JFXXYThumbnail, ythumbnail)); + var data = new byte[3 * xthumbnail * ythumbnail]; + Array.Copy(header, 8, data, 0, data.Length); + Properties.Add(new JFIFThumbnailProperty(ExifTag.JFXXThumbnail, + new JFIFThumbnail(JFIFThumbnail.ImageFormat.BMP24Bit, data))); + } + else if (version == JFIFExtension.ThumbnailPaletteRGB) + { + // Thumbnails pixel count + var xthumbnail = header[6]; + Properties.Add(new ExifByte(ExifTag.JFXXXThumbnail, xthumbnail)); + var ythumbnail = header[7]; + Properties.Add(new ExifByte(ExifTag.JFXXYThumbnail, ythumbnail)); + var palette = new byte[768]; + Array.Copy(header, 8, palette, 0, palette.Length); + var data = new byte[xthumbnail * ythumbnail]; + Array.Copy(header, 8 + 768, data, 0, data.Length); + Properties.Add(new JFIFThumbnailProperty(ExifTag.JFXXThumbnail, new JFIFThumbnail(palette, data))); + } + } - // Read thumbnail - if (version == JFIFExtension.ThumbnailJPEG) - { - byte[] data = new byte[header.Length - 6]; - Array.Copy(header, 6, data, 0, data.Length); - Properties.Add(new JFIFThumbnailProperty(ExifTag.JFXXThumbnail, new JFIFThumbnail(JFIFThumbnail.ImageFormat.JPEG, data))); - } - else if (version == JFIFExtension.Thumbnail24BitRGB) - { - // Thumbnails pixel count - byte xthumbnail = header[6]; - Properties.Add(new ExifByte(ExifTag.JFXXXThumbnail, xthumbnail)); - byte ythumbnail = header[7]; - Properties.Add(new ExifByte(ExifTag.JFXXYThumbnail, ythumbnail)); - byte[] data = new byte[3 * xthumbnail * ythumbnail]; - Array.Copy(header, 8, data, 0, data.Length); - Properties.Add(new JFIFThumbnailProperty(ExifTag.JFXXThumbnail, new JFIFThumbnail(JFIFThumbnail.ImageFormat.BMP24Bit, data))); - } - else if (version == JFIFExtension.ThumbnailPaletteRGB) + /// + /// Replaces the contents of the APP0 section with the JFIF extension properties. + /// + private bool WriteJFXXApp0() + { + // Which IFD sections do we have? + var ifdjfef = new List(); + foreach (ExifProperty prop in Properties) + { + if (prop.IFD == IFD.JFXX) { - // Thumbnails pixel count - byte xthumbnail = header[6]; - Properties.Add(new ExifByte(ExifTag.JFXXXThumbnail, xthumbnail)); - byte ythumbnail = header[7]; - Properties.Add(new ExifByte(ExifTag.JFXXYThumbnail, ythumbnail)); - byte[] palette = new byte[768]; - Array.Copy(header, 8, palette, 0, palette.Length); - byte[] data = new byte[xthumbnail * ythumbnail]; - Array.Copy(header, 8 + 768, data, 0, data.Length); - Properties.Add(new JFIFThumbnailProperty(ExifTag.JFXXThumbnail, new JFIFThumbnail(palette, data))); + ifdjfef.Add(prop); } } - /// - /// Replaces the contents of the APP0 section with the JFIF extension properties. - /// - private bool WriteJFXXApp0() + + if (ifdjfef.Count == 0) { - // Which IFD sections do we have? - List ifdjfef = new List(); - foreach (ExifProperty prop in Properties) - { - if (prop.IFD == IFD.JFXX) - ifdjfef.Add(prop); - } + // Nothing to write + return false; + } + + // Create a memory stream to write the APP0 section to + var ms = new MemoryStream(); + + // JFIF identifier + ms.Write(Encoding.ASCII.GetBytes("JFXX\0"), 0, 5); - if (ifdjfef.Count == 0) + // Write tags + foreach (ExifProperty prop in ifdjfef) + { + ExifInterOperability interop = prop.Interoperability; + var data = interop.Data; + if (BitConverterEx.SystemByteOrder != BitConverterEx.ByteOrder.BigEndian && interop.TypeID == 3) { - // Nothing to write - return false; + Array.Reverse(data); } - // Create a memory stream to write the APP0 section to - MemoryStream ms = new MemoryStream(); + ms.Write(data, 0, data.Length); + } + + ms.Close(); + + if (jfxxApp0 is not null) + { + // Return APP0 header + jfxxApp0.Header = ms.ToArray(); + return true; + } + + return false; + } - // JFIF identifier - ms.Write(Encoding.ASCII.GetBytes("JFXX\0"), 0, 5); + /// + /// Reads the APP1 section containing Exif metadata. + /// + private void ReadExifAPP1() + { + // Find the APP1 section containing Exif metadata + exifApp1 = Sections.Find(a => a.Marker == JPEGMarker.APP1 && + a.Header.Length >= 6 && + Encoding.ASCII.GetString(a.Header, 0, 6) == "Exif\0\0"); - // Write tags - foreach (ExifProperty prop in ifdjfef) + // If there is no APP1 section, add a new one after the last APP0 section (if any). + if (exifApp1 == null) + { + var insertionIndex = Sections.FindLastIndex(a => a.Marker == JPEGMarker.APP0); + if (insertionIndex == -1) { - ExifInterOperability interop = prop.Interoperability; - byte[] data = interop.Data; - if (BitConverterEx.SystemByteOrder != BitConverterEx.ByteOrder.BigEndian && interop.TypeID == 3) - Array.Reverse(data); - ms.Write(data, 0, data.Length); + insertionIndex = 0; } - ms.Close(); - - if (jfxxApp0 is not null) + insertionIndex++; + exifApp1 = new JPEGSection(JPEGMarker.APP1); + Sections.Insert(insertionIndex, exifApp1); + if (BitConverterEx.SystemByteOrder == BitConverterEx.ByteOrder.LittleEndian) { - // Return APP0 header - jfxxApp0.Header = ms.ToArray(); - return true; + ByteOrder = BitConverterEx.ByteOrder.LittleEndian; + } + else + { + ByteOrder = BitConverterEx.ByteOrder.BigEndian; } - return false; + return; } - /// - /// Reads the APP1 section containing Exif metadata. - /// - private void ReadExifAPP1() + var header = exifApp1.Header; + var ifdqueue = new SortedList(); + makerNoteOffset = 0; + + // TIFF header + var tiffoffset = 6; + if (header[tiffoffset] == 0x49 && header[tiffoffset + 1] == 0x49) { - // Find the APP1 section containing Exif metadata - exifApp1 = Sections.Find(a => (a.Marker == JPEGMarker.APP1) && - a.Header.Length >= 6 && - (Encoding.ASCII.GetString(a.Header, 0, 6) == "Exif\0\0")); + ByteOrder = BitConverterEx.ByteOrder.LittleEndian; + } + else if (header[tiffoffset] == 0x4D && header[tiffoffset + 1] == 0x4D) + { + ByteOrder = BitConverterEx.ByteOrder.BigEndian; + } + else + { + throw new NotValidExifFileException(); + } - // If there is no APP1 section, add a new one after the last APP0 section (if any). - if (exifApp1 == null) + // TIFF header may have a different byte order + BitConverterEx.ByteOrder tiffByteOrder = ByteOrder; + if (BitConverterEx.LittleEndian.ToUInt16(header, tiffoffset + 2) == 42) + { + tiffByteOrder = BitConverterEx.ByteOrder.LittleEndian; + } + else if (BitConverterEx.BigEndian.ToUInt16(header, tiffoffset + 2) == 42) + { + tiffByteOrder = BitConverterEx.ByteOrder.BigEndian; + } + else + { + throw new NotValidExifFileException(); + } + + // Offset to 0th IFD + var ifd0offset = + (int)BitConverterEx.ToUInt32(header, tiffoffset + 4, tiffByteOrder, BitConverterEx.SystemByteOrder); + ifdqueue.Add(ifd0offset, IFD.Zeroth); + + var conv = new BitConverterEx(ByteOrder, BitConverterEx.SystemByteOrder); + var thumboffset = -1; + var thumblength = 0; + var thumbtype = -1; + // Read IFDs + while (ifdqueue.Count != 0) + { + var ifdoffset = tiffoffset + ifdqueue.Keys[0]; + IFD currentifd = ifdqueue.Values[0]; + ifdqueue.RemoveAt(0); + + // Field count + var fieldcount = conv.ToUInt16(header, ifdoffset); + for (short i = 0; i < fieldcount; i++) { - int insertionIndex = Sections.FindLastIndex(a => a.Marker == JPEGMarker.APP0); - if (insertionIndex == -1) insertionIndex = 0; - insertionIndex++; - exifApp1 = new JPEGSection(JPEGMarker.APP1); - Sections.Insert(insertionIndex, exifApp1); - if (BitConverterEx.SystemByteOrder == BitConverterEx.ByteOrder.LittleEndian) - ByteOrder = BitConverterEx.ByteOrder.LittleEndian; - else - ByteOrder = BitConverterEx.ByteOrder.BigEndian; - return; - } + // Read field info + var fieldoffset = ifdoffset + 2 + (12 * i); + var tag = conv.ToUInt16(header, fieldoffset); + var type = conv.ToUInt16(header, fieldoffset + 2); + var count = conv.ToUInt32(header, fieldoffset + 4); + var value = new byte[4]; + Array.Copy(header, fieldoffset + 8, value, 0, 4); - byte[] header = exifApp1.Header; - SortedList ifdqueue = new SortedList(); - makerNoteOffset = 0; + // Fields containing offsets to other IFDs + if (currentifd == IFD.Zeroth && tag == 0x8769) + { + var exififdpointer = (int)conv.ToUInt32(value, 0); + ifdqueue.Add(exififdpointer, IFD.EXIF); + } + else if (currentifd == IFD.Zeroth && tag == 0x8825) + { + var gpsifdpointer = (int)conv.ToUInt32(value, 0); + ifdqueue.Add(gpsifdpointer, IFD.GPS); + } + else if (currentifd == IFD.EXIF && tag == 0xa005) + { + var interopifdpointer = (int)conv.ToUInt32(value, 0); + ifdqueue.Add(interopifdpointer, IFD.Interop); + } - // TIFF header - int tiffoffset = 6; - if (header[tiffoffset] == 0x49 && header[tiffoffset + 1] == 0x49) - ByteOrder = BitConverterEx.ByteOrder.LittleEndian; - else if (header[tiffoffset] == 0x4D && header[tiffoffset + 1] == 0x4D) - ByteOrder = BitConverterEx.ByteOrder.BigEndian; - else - throw new NotValidExifFileException(); - - // TIFF header may have a different byte order - BitConverterEx.ByteOrder tiffByteOrder = ByteOrder; - if (BitConverterEx.LittleEndian.ToUInt16(header, tiffoffset + 2) == 42) - tiffByteOrder = BitConverterEx.ByteOrder.LittleEndian; - else if (BitConverterEx.BigEndian.ToUInt16(header, tiffoffset + 2) == 42) - tiffByteOrder = BitConverterEx.ByteOrder.BigEndian; - else - throw new NotValidExifFileException(); - - // Offset to 0th IFD - int ifd0offset = (int)BitConverterEx.ToUInt32(header, tiffoffset + 4, tiffByteOrder, BitConverterEx.SystemByteOrder); - ifdqueue.Add(ifd0offset, IFD.Zeroth); - - BitConverterEx conv = new BitConverterEx(ByteOrder, BitConverterEx.SystemByteOrder); - int thumboffset = -1; - int thumblength = 0; - int thumbtype = -1; - // Read IFDs - while (ifdqueue.Count != 0) - { - int ifdoffset = tiffoffset + ifdqueue.Keys[0]; - IFD currentifd = ifdqueue.Values[0]; - ifdqueue.RemoveAt(0); - - // Field count - ushort fieldcount = conv.ToUInt16(header, ifdoffset); - for (short i = 0; i < fieldcount; i++) + // Save the offset to maker note data + if (currentifd == IFD.EXIF && tag == 37500) { - // Read field info - int fieldoffset = ifdoffset + 2 + 12 * i; - ushort tag = conv.ToUInt16(header, fieldoffset); - ushort type = conv.ToUInt16(header, fieldoffset + 2); - uint count = conv.ToUInt32(header, fieldoffset + 4); - byte[] value = new byte[4]; - Array.Copy(header, fieldoffset + 8, value, 0, 4); - - // Fields containing offsets to other IFDs - if (currentifd == IFD.Zeroth && tag == 0x8769) - { - int exififdpointer = (int)conv.ToUInt32(value, 0); - ifdqueue.Add(exififdpointer, IFD.EXIF); - } - else if (currentifd == IFD.Zeroth && tag == 0x8825) - { - int gpsifdpointer = (int)conv.ToUInt32(value, 0); - ifdqueue.Add(gpsifdpointer, IFD.GPS); - } - else if (currentifd == IFD.EXIF && tag == 0xa005) - { - int interopifdpointer = (int)conv.ToUInt32(value, 0); - ifdqueue.Add(interopifdpointer, IFD.Interop); - } + makerNoteOffset = conv.ToUInt32(value, 0); + } + + // Calculate the bytes we need to read + uint baselength = 0; + if (type == 1 || type == 2 || type == 7) + { + baselength = 1; + } + else if (type == 3) + { + baselength = 2; + } + else if (type == 4 || type == 9) + { + baselength = 4; + } + else if (type == 5 || type == 10) + { + baselength = 8; + } + + var totallength = count * baselength; + + // If field value does not fit in 4 bytes + // the value field is an offset to the actual + // field value + var fieldposition = 0; + if (totallength > 4) + { + fieldposition = tiffoffset + (int)conv.ToUInt32(value, 0); + value = new byte[totallength]; + Array.Copy(header, fieldposition, value, 0, totallength); + } + + // Compressed thumbnail data + if (currentifd == IFD.First && tag == 0x201) + { + thumbtype = 0; + thumboffset = (int)conv.ToUInt32(value, 0); + } + else if (currentifd == IFD.First && tag == 0x202) + { + thumblength = (int)conv.ToUInt32(value, 0); + } - // Save the offset to maker note data - if (currentifd == IFD.EXIF && tag == 37500) - makerNoteOffset = conv.ToUInt32(value, 0); - - // Calculate the bytes we need to read - uint baselength = 0; - if (type == 1 || type == 2 || type == 7) - baselength = 1; - else if (type == 3) - baselength = 2; - else if (type == 4 || type == 9) - baselength = 4; - else if (type == 5 || type == 10) - baselength = 8; - uint totallength = count * baselength; - - // If field value does not fit in 4 bytes - // the value field is an offset to the actual - // field value - int fieldposition = 0; - if (totallength > 4) + // Uncompressed thumbnail data + if (currentifd == IFD.First && tag == 0x111) + { + thumbtype = 1; + // Offset to first strip + if (type == 3) { - fieldposition = tiffoffset + (int)conv.ToUInt32(value, 0); - value = new byte[totallength]; - Array.Copy(header, fieldposition, value, 0, totallength); + thumboffset = conv.ToUInt16(value, 0); } - - // Compressed thumbnail data - if (currentifd == IFD.First && tag == 0x201) + else { - thumbtype = 0; thumboffset = (int)conv.ToUInt32(value, 0); } - else if (currentifd == IFD.First && tag == 0x202) - thumblength = (int)conv.ToUInt32(value, 0); - - // Uncompressed thumbnail data - if (currentifd == IFD.First && tag == 0x111) + } + else if (currentifd == IFD.First && tag == 0x117) + { + thumblength = 0; + for (var j = 0; j < count; j++) { - thumbtype = 1; - // Offset to first strip if (type == 3) - thumboffset = (int)conv.ToUInt16(value, 0); + { + thumblength += conv.ToUInt16(value, 0); + } else - thumboffset = (int)conv.ToUInt32(value, 0); - } - else if (currentifd == IFD.First && tag == 0x117) - { - thumblength = 0; - for (int j = 0; j < count; j++) { - if (type == 3) - thumblength += (int)conv.ToUInt16(value, 0); - else - thumblength += (int)conv.ToUInt32(value, 0); + thumblength += (int)conv.ToUInt32(value, 0); } } - - // Create the exif property from the interop data - ExifProperty prop = ExifPropertyFactory.Get(tag, type, count, value, ByteOrder, currentifd, Encoding); - Properties.Add(prop); } - // 1st IFD pointer - int firstifdpointer = (int)conv.ToUInt32(header, ifdoffset + 2 + 12 * fieldcount); - if (firstifdpointer != 0) - ifdqueue.Add(firstifdpointer, IFD.First); - // Read thumbnail - if (thumboffset != -1 && thumblength != 0 && Thumbnail == null) + // Create the exif property from the interop data + ExifProperty prop = ExifPropertyFactory.Get(tag, type, count, value, ByteOrder, currentifd, Encoding); + Properties.Add(prop); + } + + // 1st IFD pointer + var firstifdpointer = (int)conv.ToUInt32(header, ifdoffset + 2 + (12 * fieldcount)); + if (firstifdpointer != 0) + { + ifdqueue.Add(firstifdpointer, IFD.First); + } + + // Read thumbnail + if (thumboffset != -1 && thumblength != 0 && Thumbnail == null) + { + if (thumbtype == 0) { - if (thumbtype == 0) + using (var ts = new MemoryStream(header, tiffoffset + thumboffset, thumblength)) { - using (MemoryStream ts = new MemoryStream(header, tiffoffset + thumboffset, thumblength)) - { - Thumbnail = ImageFile.FromStream(ts); - } + Thumbnail = FromStream(ts); } } } } + } - /// - /// Replaces the contents of the APP1 section with the Exif properties. - /// - private bool WriteExifApp1(bool preserveMakerNote) + /// + /// Replaces the contents of the APP1 section with the Exif properties. + /// + private bool WriteExifApp1(bool preserveMakerNote) + { + // Zero out IFD field offsets. We will fill those as we write the IFD sections + exifIFDFieldOffset = 0; + gpsIFDFieldOffset = 0; + interopIFDFieldOffset = 0; + firstIFDFieldOffset = 0; + // We also do not know the location of the embedded thumbnail yet + thumbOffsetLocation = 0; + thumbOffsetValue = 0; + thumbSizeLocation = 0; + thumbSizeValue = 0; + // Write thumbnail tags if they are missing, remove otherwise + if (Thumbnail == null) { - // Zero out IFD field offsets. We will fill those as we write the IFD sections - exifIFDFieldOffset = 0; - gpsIFDFieldOffset = 0; - interopIFDFieldOffset = 0; - firstIFDFieldOffset = 0; - // We also do not know the location of the embedded thumbnail yet - thumbOffsetLocation = 0; - thumbOffsetValue = 0; - thumbSizeLocation = 0; - thumbSizeValue = 0; - // Write thumbnail tags if they are missing, remove otherwise - if (Thumbnail == null) + Properties.Remove(ExifTag.ThumbnailJPEGInterchangeFormat); + Properties.Remove(ExifTag.ThumbnailJPEGInterchangeFormatLength); + } + else + { + if (!Properties.ContainsKey(ExifTag.ThumbnailJPEGInterchangeFormat)) { - Properties.Remove(ExifTag.ThumbnailJPEGInterchangeFormat); - Properties.Remove(ExifTag.ThumbnailJPEGInterchangeFormatLength); + Properties.Add(new ExifUInt(ExifTag.ThumbnailJPEGInterchangeFormat, 0)); } - else + + if (!Properties.ContainsKey(ExifTag.ThumbnailJPEGInterchangeFormatLength)) { - if (!Properties.ContainsKey(ExifTag.ThumbnailJPEGInterchangeFormat)) - Properties.Add(new ExifUInt(ExifTag.ThumbnailJPEGInterchangeFormat, 0)); - if (!Properties.ContainsKey(ExifTag.ThumbnailJPEGInterchangeFormatLength)) - Properties.Add(new ExifUInt(ExifTag.ThumbnailJPEGInterchangeFormatLength, 0)); + Properties.Add(new ExifUInt(ExifTag.ThumbnailJPEGInterchangeFormatLength, 0)); } + } - // Which IFD sections do we have? - Dictionary ifdzeroth = new Dictionary(); - Dictionary ifdexif = new Dictionary(); - Dictionary ifdgps = new Dictionary(); - Dictionary ifdinterop = new Dictionary(); - Dictionary ifdfirst = new Dictionary(); + // Which IFD sections do we have? + var ifdzeroth = new Dictionary(); + var ifdexif = new Dictionary(); + var ifdgps = new Dictionary(); + var ifdinterop = new Dictionary(); + var ifdfirst = new Dictionary(); - foreach (ExifProperty prop in Properties) + foreach (ExifProperty prop in Properties) + { + switch (prop.IFD) { - switch (prop.IFD) - { - case IFD.Zeroth: - ifdzeroth.Add(prop.Tag, prop); - break; - case IFD.EXIF: - ifdexif.Add(prop.Tag, prop); - break; - case IFD.GPS: - ifdgps.Add(prop.Tag, prop); - break; - case IFD.Interop: - ifdinterop.Add(prop.Tag, prop); - break; - case IFD.First: - ifdfirst.Add(prop.Tag, prop); - break; - } + case IFD.Zeroth: + ifdzeroth.Add(prop.Tag, prop); + break; + case IFD.EXIF: + ifdexif.Add(prop.Tag, prop); + break; + case IFD.GPS: + ifdgps.Add(prop.Tag, prop); + break; + case IFD.Interop: + ifdinterop.Add(prop.Tag, prop); + break; + case IFD.First: + ifdfirst.Add(prop.Tag, prop); + break; } + } - // Add IFD pointers if they are missing - // We will write the pointer values later on - if (ifdexif.Count != 0 && !ifdzeroth.ContainsKey(ExifTag.EXIFIFDPointer)) - ifdzeroth.Add(ExifTag.EXIFIFDPointer, new ExifUInt(ExifTag.EXIFIFDPointer, 0)); - if (ifdgps.Count != 0 && !ifdzeroth.ContainsKey(ExifTag.GPSIFDPointer)) - ifdzeroth.Add(ExifTag.GPSIFDPointer, new ExifUInt(ExifTag.GPSIFDPointer, 0)); - if (ifdinterop.Count != 0 && !ifdexif.ContainsKey(ExifTag.InteroperabilityIFDPointer)) - ifdexif.Add(ExifTag.InteroperabilityIFDPointer, new ExifUInt(ExifTag.InteroperabilityIFDPointer, 0)); + // Add IFD pointers if they are missing + // We will write the pointer values later on + if (ifdexif.Count != 0 && !ifdzeroth.ContainsKey(ExifTag.EXIFIFDPointer)) + { + ifdzeroth.Add(ExifTag.EXIFIFDPointer, new ExifUInt(ExifTag.EXIFIFDPointer, 0)); + } - // Remove IFD pointers if IFD sections are missing - if (ifdexif.Count == 0 && ifdzeroth.ContainsKey(ExifTag.EXIFIFDPointer)) - ifdzeroth.Remove(ExifTag.EXIFIFDPointer); - if (ifdgps.Count == 0 && ifdzeroth.ContainsKey(ExifTag.GPSIFDPointer)) - ifdzeroth.Remove(ExifTag.GPSIFDPointer); - if (ifdinterop.Count == 0 && ifdexif.ContainsKey(ExifTag.InteroperabilityIFDPointer)) - ifdexif.Remove(ExifTag.InteroperabilityIFDPointer); + if (ifdgps.Count != 0 && !ifdzeroth.ContainsKey(ExifTag.GPSIFDPointer)) + { + ifdzeroth.Add(ExifTag.GPSIFDPointer, new ExifUInt(ExifTag.GPSIFDPointer, 0)); + } - if (ifdzeroth.Count == 0 && ifdgps.Count == 0 && ifdinterop.Count == 0 && ifdfirst.Count == 0 && Thumbnail == null) - { - // Nothing to write - return false; - } + if (ifdinterop.Count != 0 && !ifdexif.ContainsKey(ExifTag.InteroperabilityIFDPointer)) + { + ifdexif.Add(ExifTag.InteroperabilityIFDPointer, new ExifUInt(ExifTag.InteroperabilityIFDPointer, 0)); + } - // We will need these BitConverters to write byte-ordered data - BitConverterEx bceExif = new BitConverterEx(BitConverterEx.SystemByteOrder, ByteOrder); + // Remove IFD pointers if IFD sections are missing + if (ifdexif.Count == 0 && ifdzeroth.ContainsKey(ExifTag.EXIFIFDPointer)) + { + ifdzeroth.Remove(ExifTag.EXIFIFDPointer); + } - // Create a memory stream to write the APP1 section to - MemoryStream ms = new MemoryStream(); + if (ifdgps.Count == 0 && ifdzeroth.ContainsKey(ExifTag.GPSIFDPointer)) + { + ifdzeroth.Remove(ExifTag.GPSIFDPointer); + } - // Exif identifier - ms.Write(Encoding.ASCII.GetBytes("Exif\0\0"), 0, 6); + if (ifdinterop.Count == 0 && ifdexif.ContainsKey(ExifTag.InteroperabilityIFDPointer)) + { + ifdexif.Remove(ExifTag.InteroperabilityIFDPointer); + } - // TIFF header - // Byte order - long tiffoffset = ms.Position; - ms.Write((ByteOrder == BitConverterEx.ByteOrder.LittleEndian ? new byte[] { 0x49, 0x49 } : new byte[] { 0x4D, 0x4D }), 0, 2); - // TIFF ID - ms.Write(bceExif.GetBytes((ushort)42), 0, 2); - // Offset to 0th IFD - ms.Write(bceExif.GetBytes((uint)8), 0, 4); + if (ifdzeroth.Count == 0 && ifdgps.Count == 0 && ifdinterop.Count == 0 && ifdfirst.Count == 0 && + Thumbnail == null) + { + // Nothing to write + return false; + } - // Write IFDs - WriteIFD(ms, ifdzeroth, IFD.Zeroth, tiffoffset, preserveMakerNote); - uint exififdrelativeoffset = (uint)(ms.Position - tiffoffset); - WriteIFD(ms, ifdexif, IFD.EXIF, tiffoffset, preserveMakerNote); - uint gpsifdrelativeoffset = (uint)(ms.Position - tiffoffset); - WriteIFD(ms, ifdgps, IFD.GPS, tiffoffset, preserveMakerNote); - uint interopifdrelativeoffset = (uint)(ms.Position - tiffoffset); - WriteIFD(ms, ifdinterop, IFD.Interop, tiffoffset, preserveMakerNote); - uint firstifdrelativeoffset = (uint)(ms.Position - tiffoffset); - WriteIFD(ms, ifdfirst, IFD.First, tiffoffset, preserveMakerNote); + // We will need these BitConverters to write byte-ordered data + var bceExif = new BitConverterEx(BitConverterEx.SystemByteOrder, ByteOrder); + + // Create a memory stream to write the APP1 section to + var ms = new MemoryStream(); + + // Exif identifier + ms.Write(Encoding.ASCII.GetBytes("Exif\0\0"), 0, 6); + + // TIFF header + // Byte order + var tiffoffset = ms.Position; + ms.Write(ByteOrder == BitConverterEx.ByteOrder.LittleEndian ? new byte[] {0x49, 0x49} : new byte[] {0x4D, 0x4D}, + 0, 2); + // TIFF ID + ms.Write(bceExif.GetBytes((ushort)42), 0, 2); + // Offset to 0th IFD + ms.Write(bceExif.GetBytes((uint)8), 0, 4); + + // Write IFDs + WriteIFD(ms, ifdzeroth, IFD.Zeroth, tiffoffset, preserveMakerNote); + var exififdrelativeoffset = (uint)(ms.Position - tiffoffset); + WriteIFD(ms, ifdexif, IFD.EXIF, tiffoffset, preserveMakerNote); + var gpsifdrelativeoffset = (uint)(ms.Position - tiffoffset); + WriteIFD(ms, ifdgps, IFD.GPS, tiffoffset, preserveMakerNote); + var interopifdrelativeoffset = (uint)(ms.Position - tiffoffset); + WriteIFD(ms, ifdinterop, IFD.Interop, tiffoffset, preserveMakerNote); + var firstifdrelativeoffset = (uint)(ms.Position - tiffoffset); + WriteIFD(ms, ifdfirst, IFD.First, tiffoffset, preserveMakerNote); + + // Now that we now the location of IFDs we can go back and write IFD offsets + if (exifIFDFieldOffset != 0) + { + ms.Seek(exifIFDFieldOffset, SeekOrigin.Begin); + ms.Write(bceExif.GetBytes(exififdrelativeoffset), 0, 4); + } - // Now that we now the location of IFDs we can go back and write IFD offsets - if (exifIFDFieldOffset != 0) - { - ms.Seek(exifIFDFieldOffset, SeekOrigin.Begin); - ms.Write(bceExif.GetBytes(exififdrelativeoffset), 0, 4); - } - if (gpsIFDFieldOffset != 0) - { - ms.Seek(gpsIFDFieldOffset, SeekOrigin.Begin); - ms.Write(bceExif.GetBytes(gpsifdrelativeoffset), 0, 4); - } - if (interopIFDFieldOffset != 0) - { - ms.Seek(interopIFDFieldOffset, SeekOrigin.Begin); - ms.Write(bceExif.GetBytes(interopifdrelativeoffset), 0, 4); - } - if (firstIFDFieldOffset != 0) - { - ms.Seek(firstIFDFieldOffset, SeekOrigin.Begin); - ms.Write(bceExif.GetBytes(firstifdrelativeoffset), 0, 4); - } - // We can write thumbnail location now - if (thumbOffsetLocation != 0) - { - ms.Seek(thumbOffsetLocation, SeekOrigin.Begin); - ms.Write(bceExif.GetBytes(thumbOffsetValue), 0, 4); - } - if (thumbSizeLocation != 0) - { - ms.Seek(thumbSizeLocation, SeekOrigin.Begin); - ms.Write(bceExif.GetBytes(thumbSizeValue), 0, 4); - } + if (gpsIFDFieldOffset != 0) + { + ms.Seek(gpsIFDFieldOffset, SeekOrigin.Begin); + ms.Write(bceExif.GetBytes(gpsifdrelativeoffset), 0, 4); + } + + if (interopIFDFieldOffset != 0) + { + ms.Seek(interopIFDFieldOffset, SeekOrigin.Begin); + ms.Write(bceExif.GetBytes(interopifdrelativeoffset), 0, 4); + } + + if (firstIFDFieldOffset != 0) + { + ms.Seek(firstIFDFieldOffset, SeekOrigin.Begin); + ms.Write(bceExif.GetBytes(firstifdrelativeoffset), 0, 4); + } + + // We can write thumbnail location now + if (thumbOffsetLocation != 0) + { + ms.Seek(thumbOffsetLocation, SeekOrigin.Begin); + ms.Write(bceExif.GetBytes(thumbOffsetValue), 0, 4); + } + + if (thumbSizeLocation != 0) + { + ms.Seek(thumbSizeLocation, SeekOrigin.Begin); + ms.Write(bceExif.GetBytes(thumbSizeValue), 0, 4); + } + + ms.Close(); - ms.Close(); + if (exifApp1 is not null) + { + // Return APP1 header + exifApp1.Header = ms.ToArray(); + return true; + } + + return false; + } + + private void WriteIFD(MemoryStream stream, Dictionary ifd, IFD ifdtype, long tiffoffset, + bool preserveMakerNote) + { + var conv = new BitConverterEx(BitConverterEx.SystemByteOrder, ByteOrder); - if (exifApp1 is not null) + // Create a queue of fields to write + var fieldqueue = new Queue(); + foreach (ExifProperty prop in ifd.Values) + { + if (prop.Tag != ExifTag.MakerNote) { - // Return APP1 header - exifApp1.Header = ms.ToArray(); - return true; + fieldqueue.Enqueue(prop); } - - return false; } - private void WriteIFD(MemoryStream stream, Dictionary ifd, IFD ifdtype, long tiffoffset, bool preserveMakerNote) + // Push the maker note data to the end + if (ifd.ContainsKey(ExifTag.MakerNote)) { - BitConverterEx conv = new BitConverterEx(BitConverterEx.SystemByteOrder, ByteOrder); + fieldqueue.Enqueue(ifd[ExifTag.MakerNote]); + } - // Create a queue of fields to write - Queue fieldqueue = new Queue(); - foreach (ExifProperty prop in ifd.Values) - if (prop.Tag != ExifTag.MakerNote) - fieldqueue.Enqueue(prop); - // Push the maker note data to the end - if (ifd.ContainsKey(ExifTag.MakerNote)) - fieldqueue.Enqueue(ifd[ExifTag.MakerNote]); + // Offset to start of field data from start of TIFF header + var dataoffset = (uint)(2 + (ifd.Count * 12) + 4 + stream.Position - tiffoffset); + var currentdataoffset = dataoffset; + var absolutedataoffset = stream.Position + (2 + (ifd.Count * 12) + 4); - // Offset to start of field data from start of TIFF header - uint dataoffset = (uint)(2 + ifd.Count * 12 + 4 + stream.Position - tiffoffset); - uint currentdataoffset = dataoffset; - long absolutedataoffset = stream.Position + (2 + ifd.Count * 12 + 4); + var makernotewritten = false; + // Field count + stream.Write(conv.GetBytes((ushort)ifd.Count), 0, 2); + // Fields + while (fieldqueue.Count != 0) + { + ExifProperty field = fieldqueue.Dequeue(); + ExifInterOperability interop = field.Interoperability; + + uint fillerbytecount = 0; + + // Try to preserve the makernote data offset + if (!makernotewritten && + !makerNoteProcessed && + makerNoteOffset != 0 && + ifdtype == IFD.EXIF && + field.Tag != ExifTag.MakerNote && + interop.Data.Length > 4 && + currentdataoffset + interop.Data.Length > makerNoteOffset && + ifd.ContainsKey(ExifTag.MakerNote)) + { + // Delay writing this field until we write the creator's note data + fieldqueue.Enqueue(field); + continue; + } - bool makernotewritten = false; - // Field count - stream.Write(conv.GetBytes((ushort)ifd.Count), 0, 2); - // Fields - while (fieldqueue.Count != 0) - { - ExifProperty field = fieldqueue.Dequeue(); - ExifInterOperability interop = field.Interoperability; - - uint fillerbytecount = 0; - - // Try to preserve the makernote data offset - if (!makernotewritten && - !makerNoteProcessed && - makerNoteOffset != 0 && - ifdtype == IFD.EXIF && - field.Tag != ExifTag.MakerNote && - interop.Data.Length > 4 && - currentdataoffset + interop.Data.Length > makerNoteOffset && - ifd.ContainsKey(ExifTag.MakerNote)) + if (field.Tag == ExifTag.MakerNote) + { + makernotewritten = true; + // We may need to write filler bytes to preserve maker note offset + if (preserveMakerNote && !makerNoteProcessed && makerNoteOffset > currentdataoffset) { - // Delay writing this field until we write the creator's note data - fieldqueue.Enqueue(field); - continue; + fillerbytecount = makerNoteOffset - currentdataoffset; } - else if (field.Tag == ExifTag.MakerNote) + else { - makernotewritten = true; - // We may need to write filler bytes to preserve maker note offset - if (preserveMakerNote && !makerNoteProcessed && (makerNoteOffset > currentdataoffset)) - fillerbytecount = makerNoteOffset - currentdataoffset; - else - fillerbytecount = 0; + fillerbytecount = 0; } + } - // Tag - stream.Write(conv.GetBytes(interop.TagID), 0, 2); - // Type - stream.Write(conv.GetBytes(interop.TypeID), 0, 2); - // Count - stream.Write(conv.GetBytes(interop.Count), 0, 4); - // Field data - byte[] data = interop.Data; - if (ByteOrder != BitConverterEx.SystemByteOrder && - (interop.TypeID == 3 || interop.TypeID == 4 || interop.TypeID == 9 || - interop.TypeID == 5 || interop.TypeID == 10)) + // Tag + stream.Write(conv.GetBytes(interop.TagID), 0, 2); + // Type + stream.Write(conv.GetBytes(interop.TypeID), 0, 2); + // Count + stream.Write(conv.GetBytes(interop.Count), 0, 4); + // Field data + var data = interop.Data; + if (ByteOrder != BitConverterEx.SystemByteOrder && + (interop.TypeID == 3 || interop.TypeID == 4 || interop.TypeID == 9 || + interop.TypeID == 5 || interop.TypeID == 10)) + { + var vlen = 4; + if (interop.TypeID == 3) { - int vlen = 4; - if (interop.TypeID == 3) vlen = 2; - int n = data.Length / vlen; - - for (int i = 0; i < n; i++) - Array.Reverse(data, i * vlen, vlen); + vlen = 2; } - // Fields containing offsets to other IFDs - // Just store their offsets, we will write the values later on when we know the lengths of IFDs - if (ifdtype == IFD.Zeroth && interop.TagID == 0x8769) - exifIFDFieldOffset = stream.Position; - else if (ifdtype == IFD.Zeroth && interop.TagID == 0x8825) - gpsIFDFieldOffset = stream.Position; - else if (ifdtype == IFD.EXIF && interop.TagID == 0xa005) - interopIFDFieldOffset = stream.Position; - else if (ifdtype == IFD.First && interop.TagID == 0x201) - thumbOffsetLocation = stream.Position; - else if (ifdtype == IFD.First && interop.TagID == 0x202) - thumbSizeLocation = stream.Position; - - // Write 4 byte field value or field data - if (data.Length <= 4) - { - stream.Write(data, 0, data.Length); - for (int i = data.Length; i < 4; i++) - stream.WriteByte(0); - } - else + var n = data.Length / vlen; + + for (var i = 0; i < n; i++) { - // Pointer to data area relative to TIFF header - stream.Write(conv.GetBytes(currentdataoffset + fillerbytecount), 0, 4); - // Actual data - long currentoffset = stream.Position; - stream.Seek(absolutedataoffset, SeekOrigin.Begin); - // Write filler bytes - for (int i = 0; i < fillerbytecount; i++) - stream.WriteByte(0xFF); - stream.Write(data, 0, data.Length); - stream.Seek(currentoffset, SeekOrigin.Begin); - // Increment pointers - currentdataoffset += fillerbytecount + (uint)data.Length; - absolutedataoffset += fillerbytecount + data.Length; + Array.Reverse(data, i * vlen, vlen); } } - // Offset to 1st IFD - // We will write zeros for now. This will be filled after we write all IFDs - if (ifdtype == IFD.Zeroth) - firstIFDFieldOffset = stream.Position; - stream.Write(new byte[] { 0, 0, 0, 0 }, 0, 4); - // Seek to end of IFD - stream.Seek(absolutedataoffset, SeekOrigin.Begin); + // Fields containing offsets to other IFDs + // Just store their offsets, we will write the values later on when we know the lengths of IFDs + if (ifdtype == IFD.Zeroth && interop.TagID == 0x8769) + { + exifIFDFieldOffset = stream.Position; + } + else if (ifdtype == IFD.Zeroth && interop.TagID == 0x8825) + { + gpsIFDFieldOffset = stream.Position; + } + else if (ifdtype == IFD.EXIF && interop.TagID == 0xa005) + { + interopIFDFieldOffset = stream.Position; + } + else if (ifdtype == IFD.First && interop.TagID == 0x201) + { + thumbOffsetLocation = stream.Position; + } + else if (ifdtype == IFD.First && interop.TagID == 0x202) + { + thumbSizeLocation = stream.Position; + } - // Write thumbnail data - if (ifdtype == IFD.First) + // Write 4 byte field value or field data + if (data.Length <= 4) { - if (Thumbnail != null) + stream.Write(data, 0, data.Length); + for (var i = data.Length; i < 4; i++) { - MemoryStream ts = new MemoryStream(); - Thumbnail.Save(ts); - ts.Close(); - byte[] thumb = ts.ToArray(); - thumbOffsetValue = (uint)(stream.Position - tiffoffset); - thumbSizeValue = (uint)thumb.Length; - stream.Write(thumb, 0, thumb.Length); - ts.Dispose(); + stream.WriteByte(0); } - else + } + else + { + // Pointer to data area relative to TIFF header + stream.Write(conv.GetBytes(currentdataoffset + fillerbytecount), 0, 4); + // Actual data + var currentoffset = stream.Position; + stream.Seek(absolutedataoffset, SeekOrigin.Begin); + // Write filler bytes + for (var i = 0; i < fillerbytecount; i++) { - thumbOffsetValue = 0; - thumbSizeValue = 0; + stream.WriteByte(0xFF); } + + stream.Write(data, 0, data.Length); + stream.Seek(currentoffset, SeekOrigin.Begin); + // Increment pointers + currentdataoffset += fillerbytecount + (uint)data.Length; + absolutedataoffset += fillerbytecount + data.Length; + } + } + + // Offset to 1st IFD + // We will write zeros for now. This will be filled after we write all IFDs + if (ifdtype == IFD.Zeroth) + { + firstIFDFieldOffset = stream.Position; + } + + stream.Write(new byte[] {0, 0, 0, 0}, 0, 4); + + // Seek to end of IFD + stream.Seek(absolutedataoffset, SeekOrigin.Begin); + + // Write thumbnail data + if (ifdtype == IFD.First) + { + if (Thumbnail != null) + { + var ts = new MemoryStream(); + Thumbnail.Save(ts); + ts.Close(); + var thumb = ts.ToArray(); + thumbOffsetValue = (uint)(stream.Position - tiffoffset); + thumbSizeValue = (uint)thumb.Length; + stream.Write(thumb, 0, thumb.Length); + ts.Dispose(); + } + else + { + thumbOffsetValue = 0; + thumbSizeValue = 0; } } - #endregion } + + #endregion } diff --git a/src/Umbraco.Core/Media/Exif/JPEGMarker.cs b/src/Umbraco.Core/Media/Exif/JPEGMarker.cs index a7a3b4a9b192..5235b101ece9 100644 --- a/src/Umbraco.Core/Media/Exif/JPEGMarker.cs +++ b/src/Umbraco.Core/Media/Exif/JPEGMarker.cs @@ -1,85 +1,95 @@ -namespace Umbraco.Cms.Core.Media.Exif +namespace Umbraco.Cms.Core.Media.Exif; + +/// +/// Represents a JPEG marker byte. +/// +internal enum JPEGMarker : byte { - /// - /// Represents a JPEG marker byte. - /// - internal enum JPEGMarker : byte - { - // Start Of Frame markers, non-differential, Huffman coding - SOF0 = 0xc0, - SOF1 = 0xc1, - SOF2 = 0xc2, - SOF3 = 0xc3, - // Start Of Frame markers, differential, Huffman coding - SOF5 = 0xc5, - SOF6 = 0xc6, - SOF7 = 0xc7, - // Start Of Frame markers, non-differential, arithmetic coding - JPG = 0xc8, - SOF9 = 0xc9, - SOF10 = 0xca, - SOF11 = 0xcb, - // Start Of Frame markers, differential, arithmetic coding - SOF13 = 0xcd, - SOF14 = 0xce, - SOF15 = 0xcf, - // Huffman table specification - DHT = 0xc4, - // Arithmetic coding conditioning specification - DAC = 0xcc, - // Restart interval termination - RST0 = 0xd0, - RST1 = 0xd1, - RST2 = 0xd2, - RST3 = 0xd3, - RST4 = 0xd4, - RST5 = 0xd5, - RST6 = 0xd6, - RST7 = 0xd7, - // Other markers - SOI = 0xd8, - EOI = 0xd9, - SOS = 0xda, - DQT = 0xdb, - DNL = 0xdc, - DRI = 0xdd, - DHP = 0xde, - EXP = 0xdf, - // application segments - APP0 = 0xe0, - APP1 = 0xe1, - APP2 = 0xe2, - APP3 = 0xe3, - APP4 = 0xe4, - APP5 = 0xe5, - APP6 = 0xe6, - APP7 = 0xe7, - APP8 = 0xe8, - APP9 = 0xe9, - APP10 = 0xea, - APP11 = 0xeb, - APP12 = 0xec, - APP13 = 0xed, - APP14 = 0xee, - APP15 = 0xef, - // JPEG extensions - JPG0 = 0xf0, - JPG1 = 0xf1, - JPG2 = 0xf2, - JPG3 = 0xf3, - JPG4 = 0xf4, - JPG5 = 0xf5, - JPG6 = 0xf6, - JPG7 = 0xf7, - JPG8 = 0xf8, - JPG9 = 0xf9, - JPG10 = 0xfa, - JPG11 = 0xfb, - JP1G2 = 0xfc, - JPG13 = 0xfd, - // Comment - COM = 0xfe, - // Temporary - TEM = 0x01, - } + // Start Of Frame markers, non-differential, Huffman coding + SOF0 = 0xc0, + SOF1 = 0xc1, + SOF2 = 0xc2, + SOF3 = 0xc3, + + // Start Of Frame markers, differential, Huffman coding + SOF5 = 0xc5, + SOF6 = 0xc6, + SOF7 = 0xc7, + + // Start Of Frame markers, non-differential, arithmetic coding + JPG = 0xc8, + SOF9 = 0xc9, + SOF10 = 0xca, + SOF11 = 0xcb, + + // Start Of Frame markers, differential, arithmetic coding + SOF13 = 0xcd, + SOF14 = 0xce, + SOF15 = 0xcf, + + // Huffman table specification + DHT = 0xc4, + + // Arithmetic coding conditioning specification + DAC = 0xcc, + + // Restart interval termination + RST0 = 0xd0, + RST1 = 0xd1, + RST2 = 0xd2, + RST3 = 0xd3, + RST4 = 0xd4, + RST5 = 0xd5, + RST6 = 0xd6, + RST7 = 0xd7, + + // Other markers + SOI = 0xd8, + EOI = 0xd9, + SOS = 0xda, + DQT = 0xdb, + DNL = 0xdc, + DRI = 0xdd, + DHP = 0xde, + EXP = 0xdf, + + // application segments + APP0 = 0xe0, + APP1 = 0xe1, + APP2 = 0xe2, + APP3 = 0xe3, + APP4 = 0xe4, + APP5 = 0xe5, + APP6 = 0xe6, + APP7 = 0xe7, + APP8 = 0xe8, + APP9 = 0xe9, + APP10 = 0xea, + APP11 = 0xeb, + APP12 = 0xec, + APP13 = 0xed, + APP14 = 0xee, + APP15 = 0xef, + + // JPEG extensions + JPG0 = 0xf0, + JPG1 = 0xf1, + JPG2 = 0xf2, + JPG3 = 0xf3, + JPG4 = 0xf4, + JPG5 = 0xf5, + JPG6 = 0xf6, + JPG7 = 0xf7, + JPG8 = 0xf8, + JPG9 = 0xf9, + JPG10 = 0xfa, + JPG11 = 0xfb, + JP1G2 = 0xfc, + JPG13 = 0xfd, + + // Comment + COM = 0xfe, + + // Temporary + TEM = 0x01 } diff --git a/src/Umbraco.Core/Media/Exif/JPEGSection.cs b/src/Umbraco.Core/Media/Exif/JPEGSection.cs index 07dd48838447..d87d876755c4 100644 --- a/src/Umbraco.Core/Media/Exif/JPEGSection.cs +++ b/src/Umbraco.Core/Media/Exif/JPEGSection.cs @@ -1,63 +1,67 @@ -namespace Umbraco.Cms.Core.Media.Exif +namespace Umbraco.Cms.Core.Media.Exif; + +/// +/// Represents the memory view of a JPEG section. +/// A JPEG section is the data between markers of the JPEG file. +/// +internal class JPEGSection { + #region Instance Methods + + /// + /// Returns a string representation of the current section. + /// + /// A System.String that represents the current section. + public override string ToString() => string.Format("{0} => Header: {1} bytes, Entropy Data: {2} bytes", Marker, + Header.Length, EntropyData.Length); + + #endregion + + #region Properties + + /// + /// The marker byte representing the section. + /// + public JPEGMarker Marker { get; } + + /// + /// Section header as a byte array. This is different from the header + /// definition in JPEG specification in that it does not include the + /// two byte section length. + /// + public byte[] Header { get; set; } + + /// + /// For the SOS and RST markers, this contains the entropy coded data. + /// + public byte[] EntropyData { get; set; } + + #endregion + + #region Constructors + /// - /// Represents the memory view of a JPEG section. - /// A JPEG section is the data between markers of the JPEG file. + /// Constructs a JPEGSection represented by the marker byte and containing + /// the given data. /// - internal class JPEGSection + /// The marker byte representing the section. + /// Section data. + /// Entropy coded data. + public JPEGSection(JPEGMarker marker, byte[] data, byte[] entropydata) { - #region Properties - /// - /// The marker byte representing the section. - /// - public JPEGMarker Marker { get; private set; } - /// - /// Section header as a byte array. This is different from the header - /// definition in JPEG specification in that it does not include the - /// two byte section length. - /// - public byte[] Header { get; set; } - /// - /// For the SOS and RST markers, this contains the entropy coded data. - /// - public byte[] EntropyData { get; set; } - #endregion - - #region Constructors - /// - /// Constructs a JPEGSection represented by the marker byte and containing - /// the given data. - /// - /// The marker byte representing the section. - /// Section data. - /// Entropy coded data. - public JPEGSection(JPEGMarker marker, byte[] data, byte[] entropydata) - { - Marker = marker; - Header = data; - EntropyData = entropydata; - } - - /// - /// Constructs a JPEGSection represented by the marker byte. - /// - /// The marker byte representing the section. - public JPEGSection(JPEGMarker marker) - : this(marker, new byte[0], new byte[0]) - { - - } - #endregion - - #region Instance Methods - /// - /// Returns a string representation of the current section. - /// - /// A System.String that represents the current section. - public override string ToString() - { - return string.Format("{0} => Header: {1} bytes, Entropy Data: {2} bytes", Marker, Header.Length, EntropyData.Length); - } - #endregion + Marker = marker; + Header = data; + EntropyData = entropydata; } + + /// + /// Constructs a JPEGSection represented by the marker byte. + /// + /// The marker byte representing the section. + public JPEGSection(JPEGMarker marker) + : this(marker, new byte[0], new byte[0]) + { + } + + #endregion } diff --git a/src/Umbraco.Core/Media/Exif/MathEx.cs b/src/Umbraco.Core/Media/Exif/MathEx.cs index d49ccf924f31..f29f58179543 100644 --- a/src/Umbraco.Core/Media/Exif/MathEx.cs +++ b/src/Umbraco.Core/Media/Exif/MathEx.cs @@ -1,1370 +1,1313 @@ -using System; -using System.Globalization; +using System.Globalization; using System.Text; -namespace Umbraco.Cms.Core.Media.Exif +namespace Umbraco.Cms.Core.Media.Exif; + +/// +/// Contains extended Math functions. +/// +internal static class MathEx { /// - /// Contains extended Math functions. + /// Returns the greatest common divisor of two numbers. /// - internal static class MathEx + /// First number. + /// Second number. + public static uint GCD(uint a, uint b) { - /// - /// Returns the greatest common divisor of two numbers. - /// - /// First number. - /// Second number. - public static uint GCD(uint a, uint b) + while (b != 0) { - while (b != 0) - { - uint rem = a % b; - a = b; - b = rem; - } - - return a; + var rem = a % b; + a = b; + b = rem; } - /// - /// Returns the greatest common divisor of two numbers. - /// - /// First number. - /// Second number. - public static ulong GCD(ulong a, ulong b) - { - while (b != 0) - { - ulong rem = a % b; - a = b; - b = rem; - } + return a; + } - return a; + /// + /// Returns the greatest common divisor of two numbers. + /// + /// First number. + /// Second number. + public static ulong GCD(ulong a, ulong b) + { + while (b != 0) + { + var rem = a % b; + a = b; + b = rem; } + return a; + } + + /// + /// Represents a generic rational number represented by 32-bit signed numerator and denominator. + /// + public struct Fraction32 : IComparable, IFormattable, IComparable, IEquatable + { + #region Constants + + private const uint MaximumIterations = 10000000; + + #endregion + + #region Member Variables + + private int mNumerator; + private int mDenominator; + + #endregion + + #region Properties + /// - /// Represents a generic rational number represented by 32-bit signed numerator and denominator. + /// Gets or sets the numerator. /// - public struct Fraction32 : IComparable, IFormattable, IComparable, IEquatable + public int Numerator { - #region Constants - private const uint MaximumIterations = 10000000; - #endregion - - #region Member Variables - private bool mIsNegative; - private int mNumerator; - private int mDenominator; - private double mError; - #endregion - - #region Properties - /// - /// Gets or sets the numerator. - /// - public int Numerator + get => (IsNegative ? -1 : 1) * mNumerator; + set { - get - { - return (mIsNegative ? -1 : 1) * mNumerator; - } - set - { - if (value < 0) - { - mIsNegative = true; - mNumerator = -1 * value; - } - else - { - mIsNegative = false; - mNumerator = value; - } - Reduce(ref mNumerator, ref mDenominator); - } - } - /// - /// Gets or sets the denominator. - /// - public int Denominator - { - get + if (value < 0) { - return ((int)mDenominator); + IsNegative = true; + mNumerator = -1 * value; } - set + else { - mDenominator = System.Math.Abs(value); - Reduce(ref mNumerator, ref mDenominator); + IsNegative = false; + mNumerator = value; } - } - /// - /// Gets the error term. - /// - public double Error - { - get - { - return mError; - } + Reduce(ref mNumerator, ref mDenominator); } + } - /// - /// Gets or sets a value determining id the fraction is a negative value. - /// - public bool IsNegative - { - get - { - return mIsNegative; - } - set - { - mIsNegative = value; - } - } - #endregion - - #region Predefined Values - public static readonly Fraction32 NaN = new Fraction32(0, 0); - public static readonly Fraction32 NegativeInfinity = new Fraction32(-1, 0); - public static readonly Fraction32 PositiveInfinity = new Fraction32(1, 0); - #endregion - - #region Static Methods - /// - /// Returns a value indicating whether the specified number evaluates to a value - /// that is not a number. - /// - /// A fraction. - /// true if f evaluates to Fraction.NaN; otherwise, false. - public static bool IsNan(Fraction32 f) - { - return (f.Numerator == 0 && f.Denominator == 0); - } - /// - /// Returns a value indicating whether the specified number evaluates to negative - /// infinity. - /// - /// A fraction. - /// true if f evaluates to Fraction.NegativeInfinity; otherwise, false. - public static bool IsNegativeInfinity(Fraction32 f) - { - return (f.Numerator < 0 && f.Denominator == 0); - } - /// - /// Returns a value indicating whether the specified number evaluates to positive - /// infinity. - /// - /// A fraction. - /// true if f evaluates to Fraction.PositiveInfinity; otherwise, false. - public static bool IsPositiveInfinity(Fraction32 f) - { - return (f.Numerator > 0 && f.Denominator == 0); - } - /// - /// Returns a value indicating whether the specified number evaluates to negative - /// or positive infinity. - /// - /// A fraction. - /// true if f evaluates to Fraction.NegativeInfinity or Fraction.PositiveInfinity; otherwise, false. - public static bool IsInfinity(Fraction32 f) - { - return (f.Denominator == 0); - } - /// - /// Returns the multiplicative inverse of a given value. - /// - /// A fraction. - /// Multiplicative inverse of f. - public static Fraction32 Inverse(Fraction32 f) + /// + /// Gets or sets the denominator. + /// + public int Denominator + { + get => mDenominator; + set { - return new Fraction32(f.Denominator, f.Numerator); + mDenominator = Math.Abs(value); + Reduce(ref mNumerator, ref mDenominator); } + } - /// - /// Converts the string representation of a fraction to a fraction object. - /// - /// A string formatted as numerator/denominator - /// A fraction object converted from s. - /// s is null - /// s is not in the correct format - /// - /// s represents a number less than System.UInt32.MinValue or greater than - /// System.UInt32.MaxValue. - /// - public static Fraction32 Parse(string s) - { - return FromString(s); - } + /// + /// Gets the error term. + /// + public double Error { get; } - /// - /// Converts the string representation of a fraction to a fraction object. - /// A return value indicates whether the conversion succeeded. - /// - /// A string formatted as numerator/denominator - /// true if s was converted successfully; otherwise, false. - public static bool TryParse(string s, out Fraction32 f) - { - try - { - f = Parse(s); - return true; - } - catch - { - f = new Fraction32(); - return false; - } - } - #endregion + /// + /// Gets or sets a value determining id the fraction is a negative value. + /// + public bool IsNegative { get; set; } - #region Operators - #region Arithmetic Operators - // Multiplication - public static Fraction32 operator *(Fraction32 f, int n) - { - return new Fraction32(f.Numerator * n, f.Denominator * System.Math.Abs(n)); - } - public static Fraction32 operator *(int n, Fraction32 f) - { - return f * n; - } - public static Fraction32 operator *(Fraction32 f, float n) - { - return new Fraction32(((float)f) * n); - } - public static Fraction32 operator *(float n, Fraction32 f) + #endregion + + #region Predefined Values + + public static readonly Fraction32 NaN = new(0, 0); + public static readonly Fraction32 NegativeInfinity = new(-1, 0); + public static readonly Fraction32 PositiveInfinity = new(1, 0); + + #endregion + + #region Static Methods + + /// + /// Returns a value indicating whether the specified number evaluates to a value + /// that is not a number. + /// + /// A fraction. + /// true if f evaluates to Fraction.NaN; otherwise, false. + public static bool IsNan(Fraction32 f) => f.Numerator == 0 && f.Denominator == 0; + + /// + /// Returns a value indicating whether the specified number evaluates to negative + /// infinity. + /// + /// A fraction. + /// true if f evaluates to Fraction.NegativeInfinity; otherwise, false. + public static bool IsNegativeInfinity(Fraction32 f) => f.Numerator < 0 && f.Denominator == 0; + + /// + /// Returns a value indicating whether the specified number evaluates to positive + /// infinity. + /// + /// A fraction. + /// true if f evaluates to Fraction.PositiveInfinity; otherwise, false. + public static bool IsPositiveInfinity(Fraction32 f) => f.Numerator > 0 && f.Denominator == 0; + + /// + /// Returns a value indicating whether the specified number evaluates to negative + /// or positive infinity. + /// + /// A fraction. + /// true if f evaluates to Fraction.NegativeInfinity or Fraction.PositiveInfinity; otherwise, false. + public static bool IsInfinity(Fraction32 f) => f.Denominator == 0; + + /// + /// Returns the multiplicative inverse of a given value. + /// + /// A fraction. + /// Multiplicative inverse of f. + public static Fraction32 Inverse(Fraction32 f) => new(f.Denominator, f.Numerator); + + /// + /// Converts the string representation of a fraction to a fraction object. + /// + /// A string formatted as numerator/denominator + /// A fraction object converted from s. + /// s is null + /// s is not in the correct format + /// + /// s represents a number less than System.UInt32.MinValue or greater than + /// System.UInt32.MaxValue. + /// + public static Fraction32 Parse(string s) => FromString(s); + + /// + /// Converts the string representation of a fraction to a fraction object. + /// A return value indicates whether the conversion succeeded. + /// + /// A string formatted as numerator/denominator + /// true if s was converted successfully; otherwise, false. + public static bool TryParse(string s, out Fraction32 f) + { + try { - return f * n; + f = Parse(s); + return true; } - public static Fraction32 operator *(Fraction32 f, double n) + catch { - return new Fraction32(((double)f) * n); + f = new Fraction32(); + return false; } - public static Fraction32 operator *(double n, Fraction32 f) + } + + #endregion + + #region Operators + + #region Arithmetic Operators + + // Multiplication + public static Fraction32 operator *(Fraction32 f, int n) => new(f.Numerator * n, f.Denominator * Math.Abs(n)); + public static Fraction32 operator *(int n, Fraction32 f) => f * n; + + public static Fraction32 operator *(Fraction32 f, float n) => new((float)f * n); + public static Fraction32 operator *(float n, Fraction32 f) => f * n; + + public static Fraction32 operator *(Fraction32 f, double n) => new((double)f * n); + + public static Fraction32 operator *(double n, Fraction32 f) => f * n; + + public static Fraction32 operator *(Fraction32 f1, Fraction32 f2) => + new(f1.Numerator * f2.Numerator, f1.Denominator * f2.Denominator); + + // Division + public static Fraction32 operator /(Fraction32 f, int n) => new(f.Numerator / n, f.Denominator / Math.Abs(n)); + + public static Fraction32 operator /(Fraction32 f, float n) => new((float)f / n); + public static Fraction32 operator /(Fraction32 f, double n) => new((double)f / n); + + public static Fraction32 operator /(Fraction32 f1, Fraction32 f2) => f1 * Inverse(f2); + + // Addition + public static Fraction32 operator +(Fraction32 f, int n) => f + new Fraction32(n, 1); + + public static Fraction32 operator +(int n, Fraction32 f) => f + n; + + public static Fraction32 operator +(Fraction32 f, float n) => new((float)f + n); + public static Fraction32 operator +(float n, Fraction32 f) => f + n; + + public static Fraction32 operator +(Fraction32 f, double n) => new((double)f + n); + public static Fraction32 operator +(double n, Fraction32 f) => f + n; + + public static Fraction32 operator +(Fraction32 f1, Fraction32 f2) + { + int n1 = f1.Numerator, d1 = f1.Denominator; + int n2 = f2.Numerator, d2 = f2.Denominator; + + return new Fraction32((n1 * d2) + (n2 * d1), d1 * d2); + } + + // Subtraction + public static Fraction32 operator -(Fraction32 f, int n) => f - new Fraction32(n, 1); + + public static Fraction32 operator -(int n, Fraction32 f) => new Fraction32(n, 1) - f; + + public static Fraction32 operator -(Fraction32 f, float n) => new((float)f - n); + public static Fraction32 operator -(float n, Fraction32 f) => new Fraction32(n) - f; + public static Fraction32 operator -(Fraction32 f, double n) => new((double)f - n); + public static Fraction32 operator -(double n, Fraction32 f) => new Fraction32(n) - f; + + public static Fraction32 operator -(Fraction32 f1, Fraction32 f2) + { + int n1 = f1.Numerator, d1 = f1.Denominator; + int n2 = f2.Numerator, d2 = f2.Denominator; + + return new Fraction32((n1 * d2) - (n2 * d1), d1 * d2); + } + + // Increment + public static Fraction32 operator ++(Fraction32 f) => f + new Fraction32(1, 1); + + // Decrement + public static Fraction32 operator --(Fraction32 f) => f - new Fraction32(1, 1); + + #endregion + + #region Casts To Integral Types + + public static explicit operator int(Fraction32 f) => f.Numerator / f.Denominator; + + public static explicit operator float(Fraction32 f) => f.Numerator / (float)f.Denominator; + public static explicit operator double(Fraction32 f) => f.Numerator / (double)f.Denominator; + + #endregion + + #region Comparison Operators + + public static bool operator ==(Fraction32 f1, Fraction32 f2) => + f1.Numerator == f2.Numerator && f1.Denominator == f2.Denominator; + + public static bool operator !=(Fraction32 f1, Fraction32 f2) => + f1.Numerator != f2.Numerator || f1.Denominator != f2.Denominator; + + public static bool operator <(Fraction32 f1, Fraction32 f2) => + f1.Numerator * f2.Denominator < f2.Numerator * f1.Denominator; + + public static bool operator >(Fraction32 f1, Fraction32 f2) => + f1.Numerator * f2.Denominator > f2.Numerator * f1.Denominator; + + #endregion + + #endregion + + #region Constructors + + private Fraction32(int numerator, int denominator, double error) + { + IsNegative = false; + if (numerator < 0) { - return f * n; + numerator = -numerator; + IsNegative = !IsNegative; } - public static Fraction32 operator *(Fraction32 f1, Fraction32 f2) + + if (denominator < 0) { - return new Fraction32(f1.Numerator * f2.Numerator, f1.Denominator * f2.Denominator); + denominator = -denominator; + IsNegative = !IsNegative; } - // Division - public static Fraction32 operator /(Fraction32 f, int n) + + mNumerator = numerator; + mDenominator = denominator; + Error = error; + + if (mDenominator != 0) { - return new Fraction32(f.Numerator / n, f.Denominator / System.Math.Abs(n)); + Reduce(ref mNumerator, ref mDenominator); } - public static Fraction32 operator /(Fraction32 f, float n) + } + + public Fraction32(int numerator, int denominator) + : this(numerator, denominator, 0) + { + } + + public Fraction32(int numerator) + : this(numerator, 1) + { + } + + public Fraction32(Fraction32 f) + : this(f.Numerator, f.Denominator, f.Error) + { + } + + public Fraction32(float value) + : this((double)value) + { + } + + public Fraction32(double value) + : this(FromDouble(value)) + { + } + + public Fraction32(string s) + : this(FromString(s)) + { + } + + #endregion + + #region Instance Methods + + /// + /// Sets the value of this instance to the fraction represented + /// by the given numerator and denominator. + /// + /// The new numerator. + /// The new denominator. + public void Set(int numerator, int denominator) + { + IsNegative = false; + if (numerator < 0) { - return new Fraction32(((float)f) / n); + IsNegative = !IsNegative; + numerator = -numerator; } - public static Fraction32 operator /(Fraction32 f, double n) + + if (denominator < 0) { - return new Fraction32(((double)f) / n); + IsNegative = !IsNegative; + denominator = -denominator; } - public static Fraction32 operator /(Fraction32 f1, Fraction32 f2) + + mNumerator = numerator; + mDenominator = denominator; + + if (mDenominator != 0) { - return f1 * Inverse(f2); + Reduce(ref mNumerator, ref mDenominator); } - // Addition - public static Fraction32 operator +(Fraction32 f, int n) + } + + /// + /// Indicates whether this instance and a specified object are equal value-wise. + /// + /// Another object to compare to. + /// + /// true if obj and this instance are the same type and represent + /// the same value; otherwise, false. + /// + public override bool Equals(object? obj) + { + if (obj == null) { - return f + new Fraction32(n, 1); + return false; } - public static Fraction32 operator +(int n, Fraction32 f) + + if (obj is Fraction32) { - return f + n; + return Equals((Fraction32)obj); } - public static Fraction32 operator +(Fraction32 f, float n) + + return false; + } + + /// + /// Indicates whether this instance and a specified object are equal value-wise. + /// + /// Another fraction object to compare to. + /// + /// true if obj and this instance represent the same value; + /// otherwise, false. + /// + public bool Equals(Fraction32 obj) => IsNegative == obj.IsNegative && mNumerator == obj.Numerator && + mDenominator == obj.Denominator; + + /// + /// Returns the hash code for this instance. + /// + /// A 32-bit signed integer that is the hash code for this instance. + public override int GetHashCode() => mDenominator ^ ((IsNegative ? -1 : 1) * mNumerator); + + /// + /// Returns a string representation of the fraction. + /// + /// A numeric format string. + /// + /// An System.IFormatProvider that supplies culture-specific + /// formatting information. + /// + /// + /// The string representation of the value of this instance as + /// specified by format and provider. + /// + /// + /// format is invalid or not supported. + /// + public string ToString(string? format, IFormatProvider? formatProvider) + { + var sb = new StringBuilder(); + sb.Append(((IsNegative ? -1 : 1) * mNumerator).ToString(format, formatProvider)); + sb.Append('/'); + sb.Append(mDenominator.ToString(format, formatProvider)); + return sb.ToString(); + } + + /// + /// Returns a string representation of the fraction. + /// + /// A numeric format string. + /// + /// The string representation of the value of this instance as + /// specified by format. + /// + /// + /// format is invalid or not supported. + /// + public string ToString(string format) + { + var sb = new StringBuilder(); + sb.Append(((IsNegative ? -1 : 1) * mNumerator).ToString(format)); + sb.Append('/'); + sb.Append(mDenominator.ToString(format)); + return sb.ToString(); + } + + /// + /// Returns a string representation of the fraction. + /// + /// + /// An System.IFormatProvider that supplies culture-specific + /// formatting information. + /// + /// + /// The string representation of the value of this instance as + /// specified by provider. + /// + public string ToString(IFormatProvider formatProvider) + { + var sb = new StringBuilder(); + sb.Append(((IsNegative ? -1 : 1) * mNumerator).ToString(formatProvider)); + sb.Append('/'); + sb.Append(mDenominator.ToString(formatProvider)); + return sb.ToString(); + } + + /// + /// Returns a string representation of the fraction. + /// + /// A string formatted as numerator/denominator. + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append(((IsNegative ? -1 : 1) * mNumerator).ToString()); + sb.Append('/'); + sb.Append(mDenominator.ToString()); + return sb.ToString(); + } + + /// + /// Compares this instance to a specified object and returns an indication of + /// their relative values. + /// + /// An object to compare, or null. + /// + /// A signed number indicating the relative values of this instance and value. + /// Less than zero: This instance is less than obj. + /// Zero: This instance is equal to obj. + /// Greater than zero: This instance is greater than obj or obj is null. + /// + /// obj is not a Fraction. + public int CompareTo(object? obj) + { + if (!(obj is Fraction32)) { - return new Fraction32(((float)f) + n); + throw new ArgumentException("obj must be of type Fraction", "obj"); } - public static Fraction32 operator +(float n, Fraction32 f) + + return CompareTo((Fraction32)obj); + } + + /// + /// Compares this instance to a specified object and returns an indication of + /// their relative values. + /// + /// An fraction to compare with this instance. + /// + /// A signed number indicating the relative values of this instance and value. + /// Less than zero: This instance is less than obj. + /// Zero: This instance is equal to obj. + /// Greater than zero: This instance is greater than obj or obj is null. + /// + public int CompareTo(Fraction32 obj) + { + if (this < obj) { - return f + n; + return -1; } - public static Fraction32 operator +(Fraction32 f, double n) + + if (this > obj) { - return new Fraction32(((double)f) + n); + return 1; } - public static Fraction32 operator +(double n, Fraction32 f) + + return 0; + } + + #endregion + + #region Private Helper Methods + + /// + /// Converts the given floating-point number to its rational representation. + /// + /// The floating-point number to be converted. + /// The rational representation of value. + private static Fraction32 FromDouble(double value) + { + if (double.IsNaN(value)) { - return f + n; + return NaN; } - public static Fraction32 operator +(Fraction32 f1, Fraction32 f2) - { - int n1 = f1.Numerator, d1 = f1.Denominator; - int n2 = f2.Numerator, d2 = f2.Denominator; - return new Fraction32(n1 * d2 + n2 * d1, d1 * d2); - } - // Subtraction - public static Fraction32 operator -(Fraction32 f, int n) + if (double.IsNegativeInfinity(value)) { - return f - new Fraction32(n, 1); + return NegativeInfinity; } - public static Fraction32 operator -(int n, Fraction32 f) + + if (double.IsPositiveInfinity(value)) { - return new Fraction32(n, 1) - f; + return PositiveInfinity; } - public static Fraction32 operator -(Fraction32 f, float n) + + var isneg = value < 0; + if (isneg) { - return new Fraction32(((float)f) - n); + value = -value; } - public static Fraction32 operator -(float n, Fraction32 f) + + var f = value; + var forg = f; + var lnum = 0; + var lden = 1; + var num = 1; + var den = 0; + var lasterr = 1.0; + var a = 0; + var currIteration = 0; + while (true) { - return new Fraction32(n) - f; + if (++currIteration > MaximumIterations) + { + break; + } + + a = (int)Math.Floor(f); + f = f - a; + if (Math.Abs(f) < double.Epsilon) + { + break; + } + + f = 1.0 / f; + if (double.IsInfinity(f)) + { + break; + } + + var cnum = (num * a) + lnum; + var cden = (den * a) + lden; + if (Math.Abs((cnum / (double)cden) - forg) < double.Epsilon) + { + break; + } + + var err = ((cnum / (double)cden) - (num / (double)den)) / (num / (double)den); + // Are we converging? + if (err >= lasterr) + { + break; + } + + lasterr = err; + lnum = num; + lden = den; + num = cnum; + den = cden; } - public static Fraction32 operator -(Fraction32 f, double n) + + if (den > 0) { - return new Fraction32(((double)f) - n); + lasterr = value - (num / (double)den); } - public static Fraction32 operator -(double n, Fraction32 f) + else { - return new Fraction32(n) - f; + lasterr = double.PositiveInfinity; } - public static Fraction32 operator -(Fraction32 f1, Fraction32 f2) - { - int n1 = f1.Numerator, d1 = f1.Denominator; - int n2 = f2.Numerator, d2 = f2.Denominator; - return new Fraction32(n1 * d2 - n2 * d1, d1 * d2); - } - // Increment - public static Fraction32 operator ++(Fraction32 f) + return new Fraction32((isneg ? -1 : 1) * num, den, lasterr); + } + + /// Converts the string representation of a fraction to a Fraction type. + /// The input string formatted as numerator/denominator. + /// s is null. + /// s is not formatted as numerator/denominator. + /// + /// s represents numbers less than System.Int32.MinValue or greater than + /// System.Int32.MaxValue. + /// + private static Fraction32 FromString(string s) + { + if (s == null) { - return f + new Fraction32(1, 1); + throw new ArgumentNullException("s"); } - // Decrement - public static Fraction32 operator --(Fraction32 f) + + var sa = s.Split(Constants.CharArrays.ForwardSlash); + var numerator = 1; + var denominator = 1; + + if (sa.Length == 1) { - return f - new Fraction32(1, 1); + // Try to parse as int + if (int.TryParse(sa[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out numerator)) + { + denominator = 1; + } + else + { + // Parse as double + var dval = double.Parse(sa[0]); + return FromDouble(dval); + } } - #endregion - #region Casts To Integral Types - public static explicit operator int(Fraction32 f) + else if (sa.Length == 2) { - return f.Numerator / f.Denominator; + numerator = int.Parse(sa[0], CultureInfo.InvariantCulture); + denominator = int.Parse(sa[1], CultureInfo.InvariantCulture); } - public static explicit operator float(Fraction32 f) + else { - return ((float)f.Numerator) / ((float)f.Denominator); + throw new FormatException("The input string must be formatted as n/d where n and d are integers"); } - public static explicit operator double(Fraction32 f) + + return new Fraction32(numerator, denominator); + } + + /// + /// Reduces the given numerator and denominator by dividing with their + /// greatest common divisor. + /// + /// numerator to be reduced. + /// denominator to be reduced. + private static void Reduce(ref int numerator, ref int denominator) + { + var gcd = GCD((uint)numerator, (uint)denominator); + if (gcd == 0) { - return ((double)f.Numerator) / ((double)f.Denominator); + gcd = 1; } - #endregion - #region Comparison Operators - public static bool operator ==(Fraction32 f1, Fraction32 f2) + + numerator = numerator / (int)gcd; + denominator = denominator / (int)gcd; + } + + #endregion + } + + /// + /// Represents a generic rational number represented by 32-bit unsigned numerator and denominator. + /// + public struct UFraction32 : IComparable, IFormattable, IComparable, IEquatable + { + #region Constants + + private const uint MaximumIterations = 10000000; + + #endregion + + #region Member Variables + + private uint mNumerator; + private uint mDenominator; + + #endregion + + #region Properties + + /// + /// Gets or sets the numerator. + /// + public uint Numerator + { + get => mNumerator; + set { - return (f1.Numerator == f2.Numerator) && (f1.Denominator == f2.Denominator); + mNumerator = value; + Reduce(ref mNumerator, ref mDenominator); } - public static bool operator !=(Fraction32 f1, Fraction32 f2) + } + + /// + /// Gets or sets the denominator. + /// + public uint Denominator + { + get => mDenominator; + set { - return (f1.Numerator != f2.Numerator) || (f1.Denominator != f2.Denominator); + mDenominator = value; + Reduce(ref mNumerator, ref mDenominator); } - public static bool operator <(Fraction32 f1, Fraction32 f2) + } + + + /// + /// Gets the error term. + /// + public double Error { get; } + + #endregion + + #region Predefined Values + + public static readonly UFraction32 NaN = new(0, 0); + public static readonly UFraction32 Infinity = new(1, 0); + + #endregion + + #region Static Methods + + /// + /// Returns a value indicating whether the specified number evaluates to a value + /// that is not a number. + /// + /// A fraction. + /// true if f evaluates to Fraction.NaN; otherwise, false. + public static bool IsNan(UFraction32 f) => f.Numerator == 0 && f.Denominator == 0; + + /// + /// Returns a value indicating whether the specified number evaluates to infinity. + /// + /// A fraction. + /// true if f evaluates to Fraction.Infinity; otherwise, false. + public static bool IsInfinity(UFraction32 f) => f.Denominator == 0; + + /// + /// Converts the string representation of a fraction to a fraction object. + /// + /// A string formatted as numerator/denominator + /// A fraction object converted from s. + /// s is null + /// s is not in the correct format + /// + /// s represents a number less than System.UInt32.MinValue or greater than + /// System.UInt32.MaxValue. + /// + public static UFraction32 Parse(string s) => FromString(s); + + /// + /// Converts the string representation of a fraction to a fraction object. + /// A return value indicates whether the conversion succeeded. + /// + /// A string formatted as numerator/denominator + /// true if s was converted successfully; otherwise, false. + public static bool TryParse(string s, out UFraction32 f) + { + try { - return (f1.Numerator * f2.Denominator) < (f2.Numerator * f1.Denominator); + f = Parse(s); + return true; } - public static bool operator >(Fraction32 f1, Fraction32 f2) + catch { - return (f1.Numerator * f2.Denominator) > (f2.Numerator * f1.Denominator); + f = new UFraction32(); + return false; } - #endregion - #endregion + } - #region Constructors - private Fraction32(int numerator, int denominator, double error) - { - mIsNegative = false; - if (numerator < 0) - { - numerator = -numerator; - mIsNegative = !mIsNegative; - } - if (denominator < 0) - { - denominator = -denominator; - mIsNegative = !mIsNegative; - } + #endregion - mNumerator = numerator; - mDenominator = denominator; - mError = error; + #region Operators - if (mDenominator != 0) - Reduce(ref mNumerator, ref mDenominator); - } + #region Arithmetic Operators - public Fraction32(int numerator, int denominator) - : this(numerator, denominator, 0) - { + // Multiplication + public static UFraction32 operator *(UFraction32 f, uint n) => new(f.Numerator * n, f.Denominator * n); + public static UFraction32 operator *(uint n, UFraction32 f) => f * n; + + public static UFraction32 operator *(UFraction32 f, float n) => new((float)f * n); + + public static UFraction32 operator *(float n, UFraction32 f) => f * n; + + public static UFraction32 operator *(UFraction32 f, double n) => new((double)f * n); + public static UFraction32 operator *(double n, UFraction32 f) => f * n; + + public static UFraction32 operator *(UFraction32 f1, UFraction32 f2) => + new(f1.Numerator * f2.Numerator, f1.Denominator * f2.Denominator); + + // Division + public static UFraction32 operator /(UFraction32 f, uint n) => new(f.Numerator / n, f.Denominator / n); + + public static UFraction32 operator /(UFraction32 f, float n) => new((float)f / n); + public static UFraction32 operator /(UFraction32 f, double n) => new((double)f / n); + + public static UFraction32 operator /(UFraction32 f1, UFraction32 f2) => f1 * Inverse(f2); + + // Addition + public static UFraction32 operator +(UFraction32 f, uint n) => f + new UFraction32(n, 1); + + public static UFraction32 operator +(uint n, UFraction32 f) => f + n; + + public static UFraction32 operator +(UFraction32 f, float n) => new((float)f + n); + public static UFraction32 operator +(float n, UFraction32 f) => f + n; + + public static UFraction32 operator +(UFraction32 f, double n) => new((double)f + n); + public static UFraction32 operator +(double n, UFraction32 f) => f + n; + + public static UFraction32 operator +(UFraction32 f1, UFraction32 f2) + { + uint n1 = f1.Numerator, d1 = f1.Denominator; + uint n2 = f2.Numerator, d2 = f2.Denominator; + + return new UFraction32((n1 * d2) + (n2 * d1), d1 * d2); + } + + // Subtraction + public static UFraction32 operator -(UFraction32 f, uint n) => f - new UFraction32(n, 1); + + public static UFraction32 operator -(uint n, UFraction32 f) => new UFraction32(n, 1) - f; + + public static UFraction32 operator -(UFraction32 f, float n) => new((float)f - n); + public static UFraction32 operator -(float n, UFraction32 f) => new UFraction32(n) - f; - } + public static UFraction32 operator -(UFraction32 f, double n) => new((double)f - n); - public Fraction32(int numerator) - : this(numerator, (int)1) - { + public static UFraction32 operator -(double n, UFraction32 f) => new UFraction32(n) - f; - } + public static UFraction32 operator -(UFraction32 f1, UFraction32 f2) + { + uint n1 = f1.Numerator, d1 = f1.Denominator; + uint n2 = f2.Numerator, d2 = f2.Denominator; - public Fraction32(Fraction32 f) - : this(f.Numerator, f.Denominator, f.Error) - { + return new UFraction32((n1 * d2) - (n2 * d1), d1 * d2); + } - } + // Increment + public static UFraction32 operator ++(UFraction32 f) => f + new UFraction32(1, 1); - public Fraction32(float value) - : this((double)value) - { + // Decrement + public static UFraction32 operator --(UFraction32 f) => f - new UFraction32(1, 1); - } + #endregion - public Fraction32(double value) - : this(FromDouble(value)) - { + #region Casts To Integral Types - } + public static explicit operator uint(UFraction32 f) => f.Numerator / f.Denominator; - public Fraction32(string s) - : this(FromString(s)) - { + public static explicit operator float(UFraction32 f) => f.Numerator / (float)f.Denominator; + public static explicit operator double(UFraction32 f) => f.Numerator / (double)f.Denominator; - } - #endregion - - #region Instance Methods - /// - /// Sets the value of this instance to the fraction represented - /// by the given numerator and denominator. - /// - /// The new numerator. - /// The new denominator. - public void Set(int numerator, int denominator) - { - mIsNegative = false; - if (numerator < 0) - { - mIsNegative = !mIsNegative; - numerator = -numerator; - } - if (denominator < 0) - { - mIsNegative = !mIsNegative; - denominator = -denominator; - } + #endregion - mNumerator = numerator; - mDenominator = denominator; + #region Comparison Operators - if (mDenominator != 0) - Reduce(ref mNumerator, ref mDenominator); - } + public static bool operator ==(UFraction32 f1, UFraction32 f2) => + f1.Numerator == f2.Numerator && f1.Denominator == f2.Denominator; - /// - /// Indicates whether this instance and a specified object are equal value-wise. - /// - /// Another object to compare to. - /// true if obj and this instance are the same type and represent - /// the same value; otherwise, false. - public override bool Equals(object? obj) - { - if (obj == null) - return false; + public static bool operator !=(UFraction32 f1, UFraction32 f2) => + f1.Numerator != f2.Numerator || f1.Denominator != f2.Denominator; - if (obj is Fraction32) - return Equals((Fraction32)obj); - else - return false; - } + public static bool operator <(UFraction32 f1, UFraction32 f2) => + f1.Numerator * f2.Denominator < f2.Numerator * f1.Denominator; - /// - /// Indicates whether this instance and a specified object are equal value-wise. - /// - /// Another fraction object to compare to. - /// true if obj and this instance represent the same value; - /// otherwise, false. - public bool Equals(Fraction32 obj) - { - return (mIsNegative == obj.IsNegative) && (mNumerator == obj.Numerator) && (mDenominator == obj.Denominator); - } + public static bool operator >(UFraction32 f1, UFraction32 f2) => + f1.Numerator * f2.Denominator > f2.Numerator * f1.Denominator; - /// - /// Returns the hash code for this instance. - /// - /// A 32-bit signed integer that is the hash code for this instance. - public override int GetHashCode() - { - return mDenominator ^ ((mIsNegative ? -1 : 1) * mNumerator); - } + #endregion - /// - /// Returns a string representation of the fraction. - /// - /// A numeric format string. - /// - /// An System.IFormatProvider that supplies culture-specific - /// formatting information. - /// - /// - /// The string representation of the value of this instance as - /// specified by format and provider. - /// - /// - /// format is invalid or not supported. - /// - public string ToString(string? format, IFormatProvider? formatProvider) - { - StringBuilder sb = new StringBuilder(); - sb.Append(((mIsNegative ? -1 : 1) * mNumerator).ToString(format, formatProvider)); - sb.Append('/'); - sb.Append(mDenominator.ToString(format, formatProvider)); - return sb.ToString(); - } + #endregion - /// - /// Returns a string representation of the fraction. - /// - /// A numeric format string. - /// - /// The string representation of the value of this instance as - /// specified by format. - /// - /// - /// format is invalid or not supported. - /// - public string ToString(string format) - { - StringBuilder sb = new StringBuilder(); - sb.Append(((mIsNegative ? -1 : 1) * mNumerator).ToString(format)); - sb.Append('/'); - sb.Append(mDenominator.ToString(format)); - return sb.ToString(); - } + #region Constructors - /// - /// Returns a string representation of the fraction. - /// - /// - /// An System.IFormatProvider that supplies culture-specific - /// formatting information. - /// - /// - /// The string representation of the value of this instance as - /// specified by provider. - /// - public string ToString(IFormatProvider formatProvider) - { - StringBuilder sb = new StringBuilder(); - sb.Append(((mIsNegative ? -1 : 1) * mNumerator).ToString(formatProvider)); - sb.Append('/'); - sb.Append(mDenominator.ToString(formatProvider)); - return sb.ToString(); - } + public UFraction32(uint numerator, uint denominator, double error) + { + mNumerator = numerator; + mDenominator = denominator; + Error = error; - /// - /// Returns a string representation of the fraction. - /// - /// A string formatted as numerator/denominator. - public override string ToString() + if (mDenominator != 0) { - StringBuilder sb = new StringBuilder(); - sb.Append(((mIsNegative ? -1 : 1) * mNumerator).ToString()); - sb.Append('/'); - sb.Append(mDenominator.ToString()); - return sb.ToString(); + Reduce(ref mNumerator, ref mDenominator); } + } - /// - /// Compares this instance to a specified object and returns an indication of - /// their relative values. - /// - /// An object to compare, or null. - /// - /// A signed number indicating the relative values of this instance and value. - /// Less than zero: This instance is less than obj. - /// Zero: This instance is equal to obj. - /// Greater than zero: This instance is greater than obj or obj is null. - /// - /// obj is not a Fraction. - public int CompareTo(object? obj) - { - if (!(obj is Fraction32)) - throw new ArgumentException("obj must be of type Fraction", "obj"); + public UFraction32(uint numerator, uint denominator) + : this(numerator, denominator, 0) + { + } - return CompareTo((Fraction32)obj); - } + public UFraction32(uint numerator) + : this(numerator, 1) + { + } - /// - /// Compares this instance to a specified object and returns an indication of - /// their relative values. - /// - /// An fraction to compare with this instance. - /// - /// A signed number indicating the relative values of this instance and value. - /// Less than zero: This instance is less than obj. - /// Zero: This instance is equal to obj. - /// Greater than zero: This instance is greater than obj or obj is null. - /// - public int CompareTo(Fraction32 obj) - { - if (this < obj) - return -1; - else if (this > obj) - return 1; - return 0; - } - #endregion - - #region Private Helper Methods - /// - /// Converts the given floating-point number to its rational representation. - /// - /// The floating-point number to be converted. - /// The rational representation of value. - private static Fraction32 FromDouble(double value) - { - if (double.IsNaN(value)) - return Fraction32.NaN; - else if (double.IsNegativeInfinity(value)) - return Fraction32.NegativeInfinity; - else if (double.IsPositiveInfinity(value)) - return Fraction32.PositiveInfinity; - - bool isneg = (value < 0); - if (isneg) value = -value; - - double f = value; - double forg = f; - int lnum = 0; - int lden = 1; - int num = 1; - int den = 0; - double lasterr = 1.0; - int a = 0; - int currIteration = 0; - while (true) - { - if (++currIteration > MaximumIterations) break; - - a = (int)Math.Floor(f); - f = f - (double)a; - if (Math.Abs(f) < double.Epsilon) - break; - f = 1.0 / f; - if (double.IsInfinity(f)) - break; - int cnum = num * a + lnum; - int cden = den * a + lden; - if (Math.Abs((double)cnum / (double)cden - forg) < double.Epsilon) - break; - double err = ((double)cnum / (double)cden - (double)num / (double)den) / ((double)num / (double)den); - // Are we converging? - if (err >= lasterr) - break; - lasterr = err; - lnum = num; - lden = den; - num = cnum; - den = cden; - } + public UFraction32(UFraction32 f) + : this(f.Numerator, f.Denominator, f.Error) + { + } - if (den > 0) - lasterr = value - ((double)num / (double)den); - else - lasterr = double.PositiveInfinity; + public UFraction32(float value) + : this((double)value) + { + } - return new Fraction32((isneg ? -1 : 1) * num, den, lasterr); - } + public UFraction32(double value) + : this(FromDouble(value)) + { + } - /// Converts the string representation of a fraction to a Fraction type. - /// The input string formatted as numerator/denominator. - /// s is null. - /// s is not formatted as numerator/denominator. - /// - /// s represents numbers less than System.Int32.MinValue or greater than - /// System.Int32.MaxValue. - /// - private static Fraction32 FromString(string s) - { - if (s == null) - throw new ArgumentNullException("s"); + public UFraction32(string s) + : this(FromString(s)) + { + } - string[] sa = s.Split(Constants.CharArrays.ForwardSlash); - int numerator = 1; - int denominator = 1; + #endregion - if (sa.Length == 1) - { - // Try to parse as int - if (int.TryParse(sa[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out numerator)) - { - denominator = 1; - } - else - { - // Parse as double - double dval = double.Parse(sa[0]); - return FromDouble(dval); - } - } - else if (sa.Length == 2) - { - numerator = int.Parse(sa[0], CultureInfo.InvariantCulture); - denominator = int.Parse(sa[1], CultureInfo.InvariantCulture); - } - else - throw new FormatException("The input string must be formatted as n/d where n and d are integers"); + #region Instance Methods - return new Fraction32(numerator, denominator); - } + /// + /// Sets the value of this instance to the fraction represented + /// by the given numerator and denominator. + /// + /// The new numerator. + /// The new denominator. + public void Set(uint numerator, uint denominator) + { + mNumerator = numerator; + mDenominator = denominator; - /// - /// Reduces the given numerator and denominator by dividing with their - /// greatest common divisor. - /// - /// numerator to be reduced. - /// denominator to be reduced. - private static void Reduce(ref int numerator, ref int denominator) + if (mDenominator != 0) { - uint gcd = MathEx.GCD((uint)numerator, (uint)denominator); - if (gcd == 0) gcd = 1; - numerator = numerator / (int)gcd; - denominator = denominator / (int)gcd; + Reduce(ref mNumerator, ref mDenominator); } - #endregion } /// - /// Represents a generic rational number represented by 32-bit unsigned numerator and denominator. + /// Returns the multiplicative inverse of a given value. + /// + /// A fraction. + /// Multiplicative inverse of f. + public static UFraction32 Inverse(UFraction32 f) => new(f.Denominator, f.Numerator); + + /// + /// Indicates whether this instance and a specified object are equal value-wise. /// - public struct UFraction32 : IComparable, IFormattable, IComparable, IEquatable + /// Another object to compare to. + /// + /// true if obj and this instance are the same type and represent + /// the same value; otherwise, false. + /// + public override bool Equals(object? obj) { - #region Constants - private const uint MaximumIterations = 10000000; - #endregion - - #region Member Variables - private uint mNumerator; - private uint mDenominator; - private double mError; - #endregion - - #region Properties - /// - /// Gets or sets the numerator. - /// - public uint Numerator - { - get - { - return mNumerator; - } - set - { - mNumerator = value; - Reduce(ref mNumerator, ref mDenominator); - } - } - /// - /// Gets or sets the denominator. - /// - public uint Denominator + if (obj == null) { - get - { - return mDenominator; - } - set - { - mDenominator = value; - Reduce(ref mNumerator, ref mDenominator); - } + return false; } - - /// - /// Gets the error term. - /// - public double Error + if (obj is UFraction32) { - get - { - return mError; - } - } - #endregion - - #region Predefined Values - public static readonly UFraction32 NaN = new UFraction32(0, 0); - public static readonly UFraction32 Infinity = new UFraction32(1, 0); - #endregion - - #region Static Methods - /// - /// Returns a value indicating whether the specified number evaluates to a value - /// that is not a number. - /// - /// A fraction. - /// true if f evaluates to Fraction.NaN; otherwise, false. - public static bool IsNan(UFraction32 f) - { - return (f.Numerator == 0 && f.Denominator == 0); - } - /// - /// Returns a value indicating whether the specified number evaluates to infinity. - /// - /// A fraction. - /// true if f evaluates to Fraction.Infinity; otherwise, false. - public static bool IsInfinity(UFraction32 f) - { - return (f.Denominator == 0); + return Equals((UFraction32)obj); } - /// - /// Converts the string representation of a fraction to a fraction object. - /// - /// A string formatted as numerator/denominator - /// A fraction object converted from s. - /// s is null - /// s is not in the correct format - /// - /// s represents a number less than System.UInt32.MinValue or greater than - /// System.UInt32.MaxValue. - /// - public static UFraction32 Parse(string s) - { - return FromString(s); - } + return false; + } - /// - /// Converts the string representation of a fraction to a fraction object. - /// A return value indicates whether the conversion succeeded. - /// - /// A string formatted as numerator/denominator - /// true if s was converted successfully; otherwise, false. - public static bool TryParse(string s, out UFraction32 f) - { - try - { - f = Parse(s); - return true; - } - catch - { - f = new UFraction32(); - return false; - } - } - #endregion + /// + /// Indicates whether this instance and a specified object are equal value-wise. + /// + /// Another fraction object to compare to. + /// + /// true if obj and this instance represent the same value; + /// otherwise, false. + /// + public bool Equals(UFraction32 obj) => mNumerator == obj.Numerator && mDenominator == obj.Denominator; - #region Operators - #region Arithmetic Operators - // Multiplication - public static UFraction32 operator *(UFraction32 f, uint n) - { - return new UFraction32(f.Numerator * n, f.Denominator * n); - } - public static UFraction32 operator *(uint n, UFraction32 f) - { - return f * n; - } - public static UFraction32 operator *(UFraction32 f, float n) - { - return new UFraction32(((float)f) * n); - } - public static UFraction32 operator *(float n, UFraction32 f) - { - return f * n; - } - public static UFraction32 operator *(UFraction32 f, double n) - { - return new UFraction32(((double)f) * n); - } - public static UFraction32 operator *(double n, UFraction32 f) - { - return f * n; - } - public static UFraction32 operator *(UFraction32 f1, UFraction32 f2) - { - return new UFraction32(f1.Numerator * f2.Numerator, f1.Denominator * f2.Denominator); - } - // Division - public static UFraction32 operator /(UFraction32 f, uint n) - { - return new UFraction32(f.Numerator / n, f.Denominator / n); - } - public static UFraction32 operator /(UFraction32 f, float n) - { - return new UFraction32(((float)f) / n); - } - public static UFraction32 operator /(UFraction32 f, double n) - { - return new UFraction32(((double)f) / n); - } - public static UFraction32 operator /(UFraction32 f1, UFraction32 f2) - { - return f1 * Inverse(f2); - } - // Addition - public static UFraction32 operator +(UFraction32 f, uint n) - { - return f + new UFraction32(n, 1); - } - public static UFraction32 operator +(uint n, UFraction32 f) - { - return f + n; - } - public static UFraction32 operator +(UFraction32 f, float n) - { - return new UFraction32(((float)f) + n); - } - public static UFraction32 operator +(float n, UFraction32 f) - { - return f + n; - } - public static UFraction32 operator +(UFraction32 f, double n) - { - return new UFraction32(((double)f) + n); - } - public static UFraction32 operator +(double n, UFraction32 f) - { - return f + n; - } - public static UFraction32 operator +(UFraction32 f1, UFraction32 f2) - { - uint n1 = f1.Numerator, d1 = f1.Denominator; - uint n2 = f2.Numerator, d2 = f2.Denominator; + /// + /// Returns the hash code for this instance. + /// + /// A 32-bit signed integer that is the hash code for this instance. + public override int GetHashCode() => (int)mDenominator ^ (int)mNumerator; - return new UFraction32(n1 * d2 + n2 * d1, d1 * d2); - } - // Subtraction - public static UFraction32 operator -(UFraction32 f, uint n) - { - return f - new UFraction32(n, 1); - } - public static UFraction32 operator -(uint n, UFraction32 f) - { - return new UFraction32(n, 1) - f; - } - public static UFraction32 operator -(UFraction32 f, float n) - { - return new UFraction32(((float)f) - n); - } - public static UFraction32 operator -(float n, UFraction32 f) - { - return new UFraction32(n) - f; - } - public static UFraction32 operator -(UFraction32 f, double n) - { - return new UFraction32(((double)f) - n); - } - public static UFraction32 operator -(double n, UFraction32 f) - { - return new UFraction32(n) - f; - } - public static UFraction32 operator -(UFraction32 f1, UFraction32 f2) - { - uint n1 = f1.Numerator, d1 = f1.Denominator; - uint n2 = f2.Numerator, d2 = f2.Denominator; + /// + /// Returns a string representation of the fraction. + /// + /// A numeric format string. + /// + /// An System.IFormatProvider that supplies culture-specific + /// formatting information. + /// + /// + /// The string representation of the value of this instance as + /// specified by format and provider. + /// + /// + /// format is invalid or not supported. + /// + public string ToString(string? format, IFormatProvider? formatProvider) + { + var sb = new StringBuilder(); + sb.Append(mNumerator.ToString(format, formatProvider)); + sb.Append('/'); + sb.Append(mDenominator.ToString(format, formatProvider)); + return sb.ToString(); + } - return new UFraction32(n1 * d2 - n2 * d1, d1 * d2); - } - // Increment - public static UFraction32 operator ++(UFraction32 f) - { - return f + new UFraction32(1, 1); - } - // Decrement - public static UFraction32 operator --(UFraction32 f) - { - return f - new UFraction32(1, 1); - } - #endregion - #region Casts To Integral Types - public static explicit operator uint(UFraction32 f) - { - return ((uint)f.Numerator) / ((uint)f.Denominator); - } - public static explicit operator float(UFraction32 f) - { - return ((float)f.Numerator) / ((float)f.Denominator); - } - public static explicit operator double(UFraction32 f) - { - return ((double)f.Numerator) / ((double)f.Denominator); - } - #endregion - #region Comparison Operators - public static bool operator ==(UFraction32 f1, UFraction32 f2) - { - return (f1.Numerator == f2.Numerator) && (f1.Denominator == f2.Denominator); - } - public static bool operator !=(UFraction32 f1, UFraction32 f2) - { - return (f1.Numerator != f2.Numerator) || (f1.Denominator != f2.Denominator); - } - public static bool operator <(UFraction32 f1, UFraction32 f2) - { - return (f1.Numerator * f2.Denominator) < (f2.Numerator * f1.Denominator); - } - public static bool operator >(UFraction32 f1, UFraction32 f2) - { - return (f1.Numerator * f2.Denominator) > (f2.Numerator * f1.Denominator); - } - #endregion - #endregion + /// + /// Returns a string representation of the fraction. + /// + /// A numeric format string. + /// + /// The string representation of the value of this instance as + /// specified by format. + /// + /// + /// format is invalid or not supported. + /// + public string ToString(string format) + { + var sb = new StringBuilder(); + sb.Append(mNumerator.ToString(format)); + sb.Append('/'); + sb.Append(mDenominator.ToString(format)); + return sb.ToString(); + } - #region Constructors - public UFraction32(uint numerator, uint denominator, double error) - { - mNumerator = numerator; - mDenominator = denominator; - mError = error; + /// + /// Returns a string representation of the fraction. + /// + /// + /// An System.IFormatProvider that supplies culture-specific + /// formatting information. + /// + /// + /// The string representation of the value of this instance as + /// specified by provider. + /// + public string ToString(IFormatProvider formatProvider) + { + var sb = new StringBuilder(); + sb.Append(mNumerator.ToString(formatProvider)); + sb.Append('/'); + sb.Append(mDenominator.ToString(formatProvider)); + return sb.ToString(); + } - if (mDenominator != 0) - Reduce(ref mNumerator, ref mDenominator); - } + /// + /// Returns a string representation of the fraction. + /// + /// A string formatted as numerator/denominator. + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append(mNumerator.ToString()); + sb.Append('/'); + sb.Append(mDenominator.ToString()); + return sb.ToString(); + } - public UFraction32(uint numerator, uint denominator) - : this(numerator, denominator, 0) + /// + /// Compares this instance to a specified object and returns an indication of + /// their relative values. + /// + /// An object to compare, or null. + /// + /// A signed number indicating the relative values of this instance and value. + /// Less than zero: This instance is less than obj. + /// Zero: This instance is equal to obj. + /// Greater than zero: This instance is greater than obj or obj is null. + /// + /// obj is not a Fraction. + public int CompareTo(object? obj) + { + if (!(obj is UFraction32)) { - + throw new ArgumentException("obj must be of type UFraction32", "obj"); } - public UFraction32(uint numerator) - : this(numerator, (uint)1) - { - - } + return CompareTo((UFraction32)obj); + } - public UFraction32(UFraction32 f) - : this(f.Numerator, f.Denominator, f.Error) + /// + /// Compares this instance to a specified object and returns an indication of + /// their relative values. + /// + /// An fraction to compare with this instance. + /// + /// A signed number indicating the relative values of this instance and value. + /// Less than zero: This instance is less than obj. + /// Zero: This instance is equal to obj. + /// Greater than zero: This instance is greater than obj or obj is null. + /// + public int CompareTo(UFraction32 obj) + { + if (this < obj) { - + return -1; } - public UFraction32(float value) - : this((double)value) + if (this > obj) { - + return 1; } - public UFraction32(double value) - : this(FromDouble(value)) - { + return 0; + } - } + #endregion - public UFraction32(string s) - : this(FromString(s)) - { + #region Private Helper Methods - } - #endregion - - #region Instance Methods - /// - /// Sets the value of this instance to the fraction represented - /// by the given numerator and denominator. - /// - /// The new numerator. - /// The new denominator. - public void Set(uint numerator, uint denominator) + /// + /// Converts the given floating-point number to its rational representation. + /// + /// The floating-point number to be converted. + /// The rational representation of value. + private static UFraction32 FromDouble(double value) + { + if (value < 0) { - mNumerator = numerator; - mDenominator = denominator; - - if (mDenominator != 0) - Reduce(ref mNumerator, ref mDenominator); + throw new ArgumentException("value cannot be negative.", "value"); } - /// - /// Returns the multiplicative inverse of a given value. - /// - /// A fraction. - /// Multiplicative inverse of f. - public static UFraction32 Inverse(UFraction32 f) + if (double.IsNaN(value)) { - return new UFraction32(f.Denominator, f.Numerator); + return NaN; } - /// - /// Indicates whether this instance and a specified object are equal value-wise. - /// - /// Another object to compare to. - /// true if obj and this instance are the same type and represent - /// the same value; otherwise, false. - public override bool Equals(object? obj) + if (double.IsInfinity(value)) { - if (obj == null) - return false; - - if (obj is UFraction32) - return Equals((UFraction32)obj); - else - return false; + return Infinity; } - /// - /// Indicates whether this instance and a specified object are equal value-wise. - /// - /// Another fraction object to compare to. - /// true if obj and this instance represent the same value; - /// otherwise, false. - public bool Equals(UFraction32 obj) + var f = value; + var forg = f; + uint lnum = 0; + uint lden = 1; + uint num = 1; + uint den = 0; + var lasterr = 1.0; + uint a = 0; + var currIteration = 0; + while (true) { - return (mNumerator == obj.Numerator) && (mDenominator == obj.Denominator); - } + if (++currIteration > MaximumIterations) + { + break; + } - /// - /// Returns the hash code for this instance. - /// - /// A 32-bit signed integer that is the hash code for this instance. - public override int GetHashCode() - { - return ((int)mDenominator) ^ ((int)mNumerator); - } + a = (uint)Math.Floor(f); + f = f - a; + if (Math.Abs(f) < double.Epsilon) + { + break; + } - /// - /// Returns a string representation of the fraction. - /// - /// A numeric format string. - /// - /// An System.IFormatProvider that supplies culture-specific - /// formatting information. - /// - /// - /// The string representation of the value of this instance as - /// specified by format and provider. - /// - /// - /// format is invalid or not supported. - /// - public string ToString(string? format, IFormatProvider? formatProvider) - { - StringBuilder sb = new StringBuilder(); - sb.Append(mNumerator.ToString(format, formatProvider)); - sb.Append('/'); - sb.Append(mDenominator.ToString(format, formatProvider)); - return sb.ToString(); - } + f = 1.0 / f; + if (double.IsInfinity(f)) + { + break; + } - /// - /// Returns a string representation of the fraction. - /// - /// A numeric format string. - /// - /// The string representation of the value of this instance as - /// specified by format. - /// - /// - /// format is invalid or not supported. - /// - public string ToString(string format) - { - StringBuilder sb = new StringBuilder(); - sb.Append(mNumerator.ToString(format)); - sb.Append('/'); - sb.Append(mDenominator.ToString(format)); - return sb.ToString(); - } + var cnum = (num * a) + lnum; + var cden = (den * a) + lden; + if (Math.Abs((cnum / (double)cden) - forg) < double.Epsilon) + { + break; + } - /// - /// Returns a string representation of the fraction. - /// - /// - /// An System.IFormatProvider that supplies culture-specific - /// formatting information. - /// - /// - /// The string representation of the value of this instance as - /// specified by provider. - /// - public string ToString(IFormatProvider formatProvider) - { - StringBuilder sb = new StringBuilder(); - sb.Append(mNumerator.ToString(formatProvider)); - sb.Append('/'); - sb.Append(mDenominator.ToString(formatProvider)); - return sb.ToString(); - } + var err = ((cnum / (double)cden) - (num / (double)den)) / (num / (double)den); + // Are we converging? + if (err >= lasterr) + { + break; + } - /// - /// Returns a string representation of the fraction. - /// - /// A string formatted as numerator/denominator. - public override string ToString() - { - StringBuilder sb = new StringBuilder(); - sb.Append(mNumerator.ToString()); - sb.Append('/'); - sb.Append(mDenominator.ToString()); - return sb.ToString(); + lasterr = err; + lnum = num; + lden = den; + num = cnum; + den = cden; } - /// - /// Compares this instance to a specified object and returns an indication of - /// their relative values. - /// - /// An object to compare, or null. - /// - /// A signed number indicating the relative values of this instance and value. - /// Less than zero: This instance is less than obj. - /// Zero: This instance is equal to obj. - /// Greater than zero: This instance is greater than obj or obj is null. - /// - /// obj is not a Fraction. - public int CompareTo(object? obj) - { - if (!(obj is UFraction32)) - throw new ArgumentException("obj must be of type UFraction32", "obj"); - - return CompareTo((UFraction32)obj); - } + var fnum = (num * a) + lnum; + var fden = (den * a) + lden; - /// - /// Compares this instance to a specified object and returns an indication of - /// their relative values. - /// - /// An fraction to compare with this instance. - /// - /// A signed number indicating the relative values of this instance and value. - /// Less than zero: This instance is less than obj. - /// Zero: This instance is equal to obj. - /// Greater than zero: This instance is greater than obj or obj is null. - /// - public int CompareTo(UFraction32 obj) + if (fden > 0) { - if (this < obj) - return -1; - else if (this > obj) - return 1; - return 0; + lasterr = value - (fnum / (double)fden); } - #endregion - - #region Private Helper Methods - /// - /// Converts the given floating-point number to its rational representation. - /// - /// The floating-point number to be converted. - /// The rational representation of value. - private static UFraction32 FromDouble(double value) + else { - if (value < 0) - throw new ArgumentException("value cannot be negative.", "value"); - - if (double.IsNaN(value)) - return UFraction32.NaN; - else if (double.IsInfinity(value)) - return UFraction32.Infinity; - - double f = value; - double forg = f; - uint lnum = 0; - uint lden = 1; - uint num = 1; - uint den = 0; - double lasterr = 1.0; - uint a = 0; - int currIteration = 0; - while (true) - { - if (++currIteration > MaximumIterations) break; - - a = (uint)Math.Floor(f); - f = f - (double)a; - if (Math.Abs(f) < double.Epsilon) - break; - f = 1.0 / f; - if (double.IsInfinity(f)) - break; - uint cnum = num * a + lnum; - uint cden = den * a + lden; - if (Math.Abs((double)cnum / (double)cden - forg) < double.Epsilon) - break; - double err = ((double)cnum / (double)cden - (double)num / (double)den) / ((double)num / (double)den); - // Are we converging? - if (err >= lasterr) - break; - lasterr = err; - lnum = num; - lden = den; - num = cnum; - den = cden; - } - uint fnum = num * a + lnum; - uint fden = den * a + lden; - - if (fden > 0) - lasterr = value - ((double)fnum / (double)fden); - else - lasterr = double.PositiveInfinity; - - return new UFraction32(fnum, fden, lasterr); + lasterr = double.PositiveInfinity; } - /// Converts the string representation of a fraction to a Fraction type. - /// The input string formatted as numerator/denominator. - /// s is null. - /// s is not formatted as numerator/denominator. - /// - /// s represents numbers less than System.UInt32.MinValue or greater than - /// System.UInt32.MaxValue. - /// - private static UFraction32 FromString(string s) + return new UFraction32(fnum, fden, lasterr); + } + + /// Converts the string representation of a fraction to a Fraction type. + /// The input string formatted as numerator/denominator. + /// s is null. + /// s is not formatted as numerator/denominator. + /// + /// s represents numbers less than System.UInt32.MinValue or greater than + /// System.UInt32.MaxValue. + /// + private static UFraction32 FromString(string s) + { + if (s == null) { - if (s == null) - throw new ArgumentNullException("s"); + throw new ArgumentNullException("s"); + } - string[] sa = s.Split(Constants.CharArrays.ForwardSlash); - uint numerator = 1; - uint denominator = 1; + var sa = s.Split(Constants.CharArrays.ForwardSlash); + uint numerator = 1; + uint denominator = 1; - if (sa.Length == 1) + if (sa.Length == 1) + { + // Try to parse as uint + if (uint.TryParse(sa[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out numerator)) { - // Try to parse as uint - if (uint.TryParse(sa[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out numerator)) - { - denominator = 1; - } - else - { - // Parse as double - double dval = double.Parse(sa[0]); - return FromDouble(dval); - } + denominator = 1; } - else if (sa.Length == 2) + else { - numerator = uint.Parse(sa[0], CultureInfo.InvariantCulture); - denominator = uint.Parse(sa[1], CultureInfo.InvariantCulture); + // Parse as double + var dval = double.Parse(sa[0]); + return FromDouble(dval); } - else - throw new FormatException("The input string must be formatted as n/d where n and d are integers"); - - return new UFraction32(numerator, denominator); } - - /// - /// Reduces the given numerator and denominator by dividing with their - /// greatest common divisor. - /// - /// numerator to be reduced. - /// denominator to be reduced. - private static void Reduce(ref uint numerator, ref uint denominator) + else if (sa.Length == 2) + { + numerator = uint.Parse(sa[0], CultureInfo.InvariantCulture); + denominator = uint.Parse(sa[1], CultureInfo.InvariantCulture); + } + else { - uint gcd = MathEx.GCD(numerator, denominator); - numerator = numerator / gcd; - denominator = denominator / gcd; + throw new FormatException("The input string must be formatted as n/d where n and d are integers"); } - #endregion + + return new UFraction32(numerator, denominator); + } + + /// + /// Reduces the given numerator and denominator by dividing with their + /// greatest common divisor. + /// + /// numerator to be reduced. + /// denominator to be reduced. + private static void Reduce(ref uint numerator, ref uint denominator) + { + var gcd = GCD(numerator, denominator); + numerator = numerator / gcd; + denominator = denominator / gcd; } + + #endregion } } diff --git a/src/Umbraco.Core/Media/Exif/SvgFile.cs b/src/Umbraco.Core/Media/Exif/SvgFile.cs index b83aebe1fbe6..9223d8f76fd0 100644 --- a/src/Umbraco.Core/Media/Exif/SvgFile.cs +++ b/src/Umbraco.Core/Media/Exif/SvgFile.cs @@ -1,32 +1,31 @@ using System.Globalization; -using System.IO; -using System.Linq; using System.Xml.Linq; -namespace Umbraco.Cms.Core.Media.Exif +namespace Umbraco.Cms.Core.Media.Exif; + +internal class SvgFile : ImageFile { - internal class SvgFile : ImageFile + public SvgFile(Stream fileStream) { - public SvgFile(Stream fileStream) - { - fileStream.Position = 0; - - var document = XDocument.Load(fileStream); //if it throws an exception the ugly try catch in MediaFileSystem will catch it + fileStream.Position = 0; - var width = document.Root?.Attributes().Where(x => x.Name == "width").Select(x => x.Value).FirstOrDefault(); - var height = document.Root?.Attributes().Where(x => x.Name == "height").Select(x => x.Value).FirstOrDefault(); + var document = + XDocument.Load(fileStream); //if it throws an exception the ugly try catch in MediaFileSystem will catch it - Properties.Add(new ExifSInt(ExifTag.PixelYDimension, - height == null ? Constants.Conventions.Media.DefaultSize : int.Parse(height, CultureInfo.InvariantCulture))); - Properties.Add(new ExifSInt(ExifTag.PixelXDimension, - width == null ? Constants.Conventions.Media.DefaultSize : int.Parse(width, CultureInfo.InvariantCulture))); + var width = document.Root?.Attributes().Where(x => x.Name == "width").Select(x => x.Value).FirstOrDefault(); + var height = document.Root?.Attributes().Where(x => x.Name == "height").Select(x => x.Value).FirstOrDefault(); - Format = ImageFileFormat.SVG; - } + Properties.Add(new ExifSInt(ExifTag.PixelYDimension, + height == null + ? Constants.Conventions.Media.DefaultSize + : int.Parse(height, CultureInfo.InvariantCulture))); + Properties.Add(new ExifSInt(ExifTag.PixelXDimension, + width == null ? Constants.Conventions.Media.DefaultSize : int.Parse(width, CultureInfo.InvariantCulture))); - public override void Save(Stream stream) - { - } + Format = ImageFileFormat.SVG; + } + public override void Save(Stream stream) + { } } diff --git a/src/Umbraco.Core/Media/Exif/TIFFFile.cs b/src/Umbraco.Core/Media/Exif/TIFFFile.cs index 8841e8337b85..a23d9b91cd2f 100644 --- a/src/Umbraco.Core/Media/Exif/TIFFFile.cs +++ b/src/Umbraco.Core/Media/Exif/TIFFFile.cs @@ -1,166 +1,180 @@ -using System; -using System.Collections.Generic; -using System.IO; +using System.Text; -namespace Umbraco.Cms.Core.Media.Exif +namespace Umbraco.Cms.Core.Media.Exif; + +/// +/// Represents the binary view of a TIFF file. +/// +internal class TIFFFile : ImageFile { + #region Constructor + /// - /// Represents the binary view of a TIFF file. + /// Initializes a new instance of the class from the + /// specified data stream. /// - internal class TIFFFile : ImageFile + /// A that contains image data. + /// The encoding to be used for text metadata when the source encoding is unknown. + protected internal TIFFFile(Stream stream, Encoding encoding) { - #region Properties - /// - /// Gets the TIFF header. - /// - public TIFFHeader TIFFHeader { get; private set; } - /// - /// Gets the image file directories. - /// - public List IFDs { get; private set; } - #endregion - - #region Constructor - /// - /// Initializes a new instance of the class from the - /// specified data stream. - /// - /// A that contains image data. - /// The encoding to be used for text metadata when the source encoding is unknown. - protected internal TIFFFile(Stream stream, System.Text.Encoding encoding) - { - Format = ImageFileFormat.TIFF; - IFDs = new List(); - Encoding = encoding; + Format = ImageFileFormat.TIFF; + IFDs = new List(); + Encoding = encoding; - // Read the entire stream - byte[] data = Utility.GetStreamBytes(stream); + // Read the entire stream + var data = Utility.GetStreamBytes(stream); - // Read the TIFF header - TIFFHeader = TIFFHeader.FromBytes(data, 0); - uint nextIFDOffset = TIFFHeader.IFDOffset; - if (nextIFDOffset == 0) - throw new NotValidTIFFileException("The first IFD offset is zero."); + // Read the TIFF header + TIFFHeader = TIFFHeader.FromBytes(data, 0); + var nextIFDOffset = TIFFHeader.IFDOffset; + if (nextIFDOffset == 0) + { + throw new NotValidTIFFileException("The first IFD offset is zero."); + } - // Read IFDs in order - while (nextIFDOffset != 0) - { - ImageFileDirectory ifd = ImageFileDirectory.FromBytes(data, nextIFDOffset, TIFFHeader.ByteOrder); - nextIFDOffset = ifd.NextIFDOffset; - IFDs.Add(ifd); - } + // Read IFDs in order + while (nextIFDOffset != 0) + { + var ifd = ImageFileDirectory.FromBytes(data, nextIFDOffset, TIFFHeader.ByteOrder); + nextIFDOffset = ifd.NextIFDOffset; + IFDs.Add(ifd); + } - // Process IFDs - // TODO: Add support for multiple frames - foreach (ImageFileDirectoryEntry field in IFDs[0].Fields) - { - Properties.Add(ExifPropertyFactory.Get(field.Tag, field.Type, field.Count, field.Data, BitConverterEx.SystemByteOrder, IFD.Zeroth, Encoding)); - } + // Process IFDs + // TODO: Add support for multiple frames + foreach (ImageFileDirectoryEntry field in IFDs[0].Fields) + { + Properties.Add(ExifPropertyFactory.Get(field.Tag, field.Type, field.Count, field.Data, + BitConverterEx.SystemByteOrder, IFD.Zeroth, Encoding)); } - #endregion - - #region Instance Methods - /// - /// Saves the to the given stream. - /// - /// The data stream used to save the image. - public override void Save(Stream stream) + } + + #endregion + + #region Instance Methods + + /// + /// Saves the to the given stream. + /// + /// The data stream used to save the image. + public override void Save(Stream stream) + { + BitConverterEx conv = BitConverterEx.SystemEndian; + + // Write TIFF header + uint ifdoffset = 8; + // Byte order + stream.Write( + BitConverterEx.SystemByteOrder == BitConverterEx.ByteOrder.LittleEndian + ? new byte[] {0x49, 0x49} + : new byte[] {0x4D, 0x4D}, 0, 2); + // TIFF ID + stream.Write(conv.GetBytes((ushort)42), 0, 2); + // Offset to 0th IFD, will be corrected below + stream.Write(conv.GetBytes(ifdoffset), 0, 4); + + // Write IFD sections + for (var i = 0; i < IFDs.Count; i++) { - BitConverterEx conv = BitConverterEx.SystemEndian; - - // Write TIFF header - uint ifdoffset = 8; - // Byte order - stream.Write((BitConverterEx.SystemByteOrder == BitConverterEx.ByteOrder.LittleEndian ? new byte[] { 0x49, 0x49 } : new byte[] { 0x4D, 0x4D }), 0, 2); - // TIFF ID - stream.Write(conv.GetBytes((ushort)42), 0, 2); - // Offset to 0th IFD, will be corrected below - stream.Write(conv.GetBytes(ifdoffset), 0, 4); + ImageFileDirectory ifd = IFDs[i]; - // Write IFD sections - for (int i = 0; i < IFDs.Count; i++) - { - ImageFileDirectory ifd = IFDs[i]; + // Save the location of IFD offset + var ifdLocation = stream.Position - 4; - // Save the location of IFD offset - long ifdLocation = stream.Position - 4; + // Write strips first + var stripOffsets = new byte[4 * ifd.Strips.Count]; + var stripLengths = new byte[4 * ifd.Strips.Count]; + var stripOffset = ifdoffset; + for (var j = 0; j < ifd.Strips.Count; j++) + { + var stripData = ifd.Strips[j].Data; + var oBytes = BitConverter.GetBytes(stripOffset); + var lBytes = BitConverter.GetBytes((uint)stripData.Length); + Array.Copy(oBytes, 0, stripOffsets, 4 * j, 4); + Array.Copy(lBytes, 0, stripLengths, 4 * j, 4); + stream.Write(stripData, 0, stripData.Length); + stripOffset += (uint)stripData.Length; + } - // Write strips first - byte[] stripOffsets = new byte[4 * ifd.Strips.Count]; - byte[] stripLengths = new byte[4 * ifd.Strips.Count]; - uint stripOffset = ifdoffset; - for (int j = 0; j < ifd.Strips.Count; j++) + // Remove old strip tags + for (var j = ifd.Fields.Count - 1; j > 0; j--) + { + var tag = ifd.Fields[j].Tag; + if (tag == 273 || tag == 279) { - byte[] stripData = ifd.Strips[j].Data; - byte[] oBytes = BitConverter.GetBytes(stripOffset); - byte[] lBytes = BitConverter.GetBytes((uint)stripData.Length); - Array.Copy(oBytes, 0, stripOffsets, 4 * j, 4); - Array.Copy(lBytes, 0, stripLengths, 4 * j, 4); - stream.Write(stripData, 0, stripData.Length); - stripOffset += (uint)stripData.Length; + ifd.Fields.RemoveAt(j); } + } - // Remove old strip tags - for (int j = ifd.Fields.Count - 1; j > 0; j--) - { - ushort tag = ifd.Fields[j].Tag; - if (tag == 273 || tag == 279) - ifd.Fields.RemoveAt(j); - } - // Write new strip tags - ifd.Fields.Add(new ImageFileDirectoryEntry(273, 4, (uint)ifd.Strips.Count, stripOffsets)); - ifd.Fields.Add(new ImageFileDirectoryEntry(279, 4, (uint)ifd.Strips.Count, stripLengths)); + // Write new strip tags + ifd.Fields.Add(new ImageFileDirectoryEntry(273, 4, (uint)ifd.Strips.Count, stripOffsets)); + ifd.Fields.Add(new ImageFileDirectoryEntry(279, 4, (uint)ifd.Strips.Count, stripLengths)); - // Write fields after strips - ifdoffset = stripOffset; + // Write fields after strips + ifdoffset = stripOffset; - // Correct IFD offset - long currentLocation = stream.Position; - stream.Seek(ifdLocation, SeekOrigin.Begin); - stream.Write(conv.GetBytes(ifdoffset), 0, 4); - stream.Seek(currentLocation, SeekOrigin.Begin); + // Correct IFD offset + var currentLocation = stream.Position; + stream.Seek(ifdLocation, SeekOrigin.Begin); + stream.Write(conv.GetBytes(ifdoffset), 0, 4); + stream.Seek(currentLocation, SeekOrigin.Begin); - // Offset to field data - uint dataOffset = ifdoffset + 2 + (uint)ifd.Fields.Count * 12 + 4; + // Offset to field data + var dataOffset = ifdoffset + 2 + ((uint)ifd.Fields.Count * 12) + 4; - // Field count - stream.Write(conv.GetBytes((ushort)ifd.Fields.Count), 0, 2); + // Field count + stream.Write(conv.GetBytes((ushort)ifd.Fields.Count), 0, 2); - // Fields - foreach (ImageFileDirectoryEntry field in ifd.Fields) + // Fields + foreach (ImageFileDirectoryEntry field in ifd.Fields) + { + // Tag + stream.Write(conv.GetBytes(field.Tag), 0, 2); + // Type + stream.Write(conv.GetBytes(field.Type), 0, 2); + // Count + stream.Write(conv.GetBytes(field.Count), 0, 4); + + // Field data + var data = field.Data; + if (data.Length <= 4) { - // Tag - stream.Write(conv.GetBytes(field.Tag), 0, 2); - // Type - stream.Write(conv.GetBytes(field.Type), 0, 2); - // Count - stream.Write(conv.GetBytes(field.Count), 0, 4); - - // Field data - byte[] data = field.Data; - if (data.Length <= 4) + stream.Write(data, 0, data.Length); + for (var j = data.Length; j < 4; j++) { - stream.Write(data, 0, data.Length); - for (int j = data.Length; j < 4; j++) - stream.WriteByte(0); - } - else - { - stream.Write(conv.GetBytes(dataOffset), 0, 4); - long currentOffset = stream.Position; - stream.Seek(dataOffset, SeekOrigin.Begin); - stream.Write(data, 0, data.Length); - dataOffset += (uint)data.Length; - stream.Seek(currentOffset, SeekOrigin.Begin); + stream.WriteByte(0); } } - - // Offset to next IFD - ifdoffset = dataOffset; - stream.Write(conv.GetBytes(i == IFDs.Count - 1 ? 0 : ifdoffset), 0, 4); + else + { + stream.Write(conv.GetBytes(dataOffset), 0, 4); + var currentOffset = stream.Position; + stream.Seek(dataOffset, SeekOrigin.Begin); + stream.Write(data, 0, data.Length); + dataOffset += (uint)data.Length; + stream.Seek(currentOffset, SeekOrigin.Begin); + } } - } - #endregion + // Offset to next IFD + ifdoffset = dataOffset; + stream.Write(conv.GetBytes(i == IFDs.Count - 1 ? 0 : ifdoffset), 0, 4); + } } + + #endregion + + #region Properties + + /// + /// Gets the TIFF header. + /// + public TIFFHeader TIFFHeader { get; } + + /// + /// Gets the image file directories. + /// + public List IFDs { get; } + + #endregion } diff --git a/src/Umbraco.Core/Media/Exif/TIFFHeader.cs b/src/Umbraco.Core/Media/Exif/TIFFHeader.cs index ac7c503d0c9a..a68af4ceb0ac 100644 --- a/src/Umbraco.Core/Media/Exif/TIFFHeader.cs +++ b/src/Umbraco.Core/Media/Exif/TIFFHeader.cs @@ -1,78 +1,99 @@ -namespace Umbraco.Cms.Core.Media.Exif +namespace Umbraco.Cms.Core.Media.Exif; + +/// +/// Represents a TIFF Header. +/// +internal struct TIFFHeader { /// - /// Represents a TIFF Header. + /// The byte order of the image file. /// - internal struct TIFFHeader - { - /// - /// The byte order of the image file. - /// - public BitConverterEx.ByteOrder ByteOrder; - /// - /// TIFF ID. This value should always be 42. - /// - public byte ID; - /// - /// The offset to the first IFD section from the - /// start of the TIFF header. - /// - public uint IFDOffset; - /// - /// The byte order of the TIFF header itself. - /// - public BitConverterEx.ByteOrder TIFFHeaderByteOrder; + public BitConverterEx.ByteOrder ByteOrder; - /// - /// Initializes a new instance of the struct. - /// - /// The byte order. - /// The TIFF ID. This value should always be 42. - /// The offset to the first IFD section from the - /// start of the TIFF header. - /// The byte order of the TIFF header itself. - public TIFFHeader(BitConverterEx.ByteOrder byteOrder, byte id, uint ifdOffset, BitConverterEx.ByteOrder headerByteOrder) - { - if (id != 42) - throw new NotValidTIFFHeader(); + /// + /// TIFF ID. This value should always be 42. + /// + public byte ID; - ByteOrder = byteOrder; - ID = id; - IFDOffset = ifdOffset; - TIFFHeaderByteOrder = headerByteOrder; - } + /// + /// The offset to the first IFD section from the + /// start of the TIFF header. + /// + public uint IFDOffset; - /// - /// Returns a initialized from the given byte data. - /// - /// The data. - /// The offset into . - /// A initialized from the given byte data. - public static TIFFHeader FromBytes(byte[] data, int offset) + /// + /// The byte order of the TIFF header itself. + /// + public BitConverterEx.ByteOrder TIFFHeaderByteOrder; + + /// + /// Initializes a new instance of the struct. + /// + /// The byte order. + /// The TIFF ID. This value should always be 42. + /// + /// The offset to the first IFD section from the + /// start of the TIFF header. + /// + /// The byte order of the TIFF header itself. + public TIFFHeader(BitConverterEx.ByteOrder byteOrder, byte id, uint ifdOffset, + BitConverterEx.ByteOrder headerByteOrder) + { + if (id != 42) { - TIFFHeader header = new TIFFHeader(); + throw new NotValidTIFFHeader(); + } - // TIFF header - if (data[offset] == 0x49 && data[offset + 1] == 0x49) - header.ByteOrder = BitConverterEx.ByteOrder.LittleEndian; - else if (data[offset] == 0x4D && data[offset + 1] == 0x4D) - header.ByteOrder = BitConverterEx.ByteOrder.BigEndian; - else - throw new NotValidTIFFHeader(); + ByteOrder = byteOrder; + ID = id; + IFDOffset = ifdOffset; + TIFFHeaderByteOrder = headerByteOrder; + } - // TIFF header may have a different byte order - if (BitConverterEx.LittleEndian.ToUInt16(data, offset + 2) == 42) - header.TIFFHeaderByteOrder = BitConverterEx.ByteOrder.LittleEndian; - else if (BitConverterEx.BigEndian.ToUInt16(data, offset + 2) == 42) - header.TIFFHeaderByteOrder = BitConverterEx.ByteOrder.BigEndian; - else - throw new NotValidTIFFHeader(); - header.ID = 42; + /// + /// Returns a initialized from the given byte data. + /// + /// The data. + /// The offset into . + /// A initialized from the given byte data. + public static TIFFHeader FromBytes(byte[] data, int offset) + { + var header = new TIFFHeader(); - // IFD offset - header.IFDOffset = BitConverterEx.ToUInt32(data, offset + 4, header.TIFFHeaderByteOrder, BitConverterEx.SystemByteOrder); + // TIFF header + if (data[offset] == 0x49 && data[offset + 1] == 0x49) + { + header.ByteOrder = BitConverterEx.ByteOrder.LittleEndian; + } + else if (data[offset] == 0x4D && data[offset + 1] == 0x4D) + { + header.ByteOrder = BitConverterEx.ByteOrder.BigEndian; + } + else + { + throw new NotValidTIFFHeader(); + } - return header; + // TIFF header may have a different byte order + if (BitConverterEx.LittleEndian.ToUInt16(data, offset + 2) == 42) + { + header.TIFFHeaderByteOrder = BitConverterEx.ByteOrder.LittleEndian; + } + else if (BitConverterEx.BigEndian.ToUInt16(data, offset + 2) == 42) + { + header.TIFFHeaderByteOrder = BitConverterEx.ByteOrder.BigEndian; + } + else + { + throw new NotValidTIFFHeader(); } + + header.ID = 42; + + // IFD offset + header.IFDOffset = + BitConverterEx.ToUInt32(data, offset + 4, header.TIFFHeaderByteOrder, BitConverterEx.SystemByteOrder); + + return header; } } diff --git a/src/Umbraco.Core/Media/Exif/TIFFStrip.cs b/src/Umbraco.Core/Media/Exif/TIFFStrip.cs index 9930961e207c..b8a954f77fd0 100644 --- a/src/Umbraco.Core/Media/Exif/TIFFStrip.cs +++ b/src/Umbraco.Core/Media/Exif/TIFFStrip.cs @@ -1,27 +1,24 @@ -using System; +namespace Umbraco.Cms.Core.Media.Exif; -namespace Umbraco.Cms.Core.Media.Exif +/// +/// Represents a strip of compressed image data in a TIFF file. +/// +internal class TIFFStrip { /// - /// Represents a strip of compressed image data in a TIFF file. + /// Initializes a new instance of the class. /// - internal class TIFFStrip + /// The byte array to copy strip from. + /// The offset to the beginning of strip. + /// The length of strip. + public TIFFStrip(byte[] data, uint offset, uint length) { - /// - /// Compressed image data contained in this strip. - /// - public byte[] Data { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The byte array to copy strip from. - /// The offset to the beginning of strip. - /// The length of strip. - public TIFFStrip(byte[] data, uint offset, uint length) - { - Data = new byte[length]; - Array.Copy(data, offset, Data, 0, length); - } + Data = new byte[length]; + Array.Copy(data, offset, Data, 0, length); } + + /// + /// Compressed image data contained in this strip. + /// + public byte[] Data { get; } } diff --git a/src/Umbraco.Core/Media/Exif/Utility.cs b/src/Umbraco.Core/Media/Exif/Utility.cs index 033b97ecc79d..e3a3cb049dc6 100644 --- a/src/Umbraco.Core/Media/Exif/Utility.cs +++ b/src/Umbraco.Core/Media/Exif/Utility.cs @@ -1,30 +1,29 @@ -using System.IO; +namespace Umbraco.Cms.Core.Media.Exif; -namespace Umbraco.Cms.Core.Media.Exif +/// +/// Contains utility functions. +/// +internal class Utility { /// - /// Contains utility functions. + /// Reads the entire stream and returns its contents as a byte array. /// - internal class Utility + /// The to read. + /// Contents of the as a byte array. + public static byte[] GetStreamBytes(Stream stream) { - /// - /// Reads the entire stream and returns its contents as a byte array. - /// - /// The to read. - /// Contents of the as a byte array. - public static byte[] GetStreamBytes(Stream stream) + using (var mem = new MemoryStream()) { - using (MemoryStream mem = new MemoryStream()) - { - stream.Seek(0, SeekOrigin.Begin); - - byte[] b = new byte[32768]; - int r; - while ((r = stream.Read(b, 0, b.Length)) > 0) - mem.Write(b, 0, r); + stream.Seek(0, SeekOrigin.Begin); - return mem.ToArray(); + var b = new byte[32768]; + int r; + while ((r = stream.Read(b, 0, b.Length)) > 0) + { + mem.Write(b, 0, r); } + + return mem.ToArray(); } } } diff --git a/src/Umbraco.Core/Media/IEmbedProvider.cs b/src/Umbraco.Core/Media/IEmbedProvider.cs index e7937904bd6d..ec2a9b7dfe89 100644 --- a/src/Umbraco.Core/Media/IEmbedProvider.cs +++ b/src/Umbraco.Core/Media/IEmbedProvider.cs @@ -1,25 +1,22 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Media; -namespace Umbraco.Cms.Core.Media +public interface IEmbedProvider { - public interface IEmbedProvider - { - /// - /// The OEmbed API Endpoint - /// - string ApiEndpoint { get; } + /// + /// The OEmbed API Endpoint + /// + string ApiEndpoint { get; } - /// - /// A string array of Regex patterns to match against the pasted OEmbed URL - /// - string[] UrlSchemeRegex { get; } + /// + /// A string array of Regex patterns to match against the pasted OEmbed URL + /// + string[] UrlSchemeRegex { get; } - /// - /// A collection of querystring request parameters to append to the API URL - /// - /// ?key=value&key2=value2 - Dictionary RequestParams { get; } + /// + /// A collection of querystring request parameters to append to the API URL + /// + /// ?key=value&key2=value2 + Dictionary RequestParams { get; } - string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0); - } + string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0); } diff --git a/src/Umbraco.Core/Media/IImageDimensionExtractor.cs b/src/Umbraco.Core/Media/IImageDimensionExtractor.cs index 2eaf632e54bd..67f11415d364 100644 --- a/src/Umbraco.Core/Media/IImageDimensionExtractor.cs +++ b/src/Umbraco.Core/Media/IImageDimensionExtractor.cs @@ -1,10 +1,8 @@ using System.Drawing; -using System.IO; -namespace Umbraco.Cms.Core.Media +namespace Umbraco.Cms.Core.Media; + +public interface IImageDimensionExtractor { - public interface IImageDimensionExtractor - { - public Size? GetDimensions(Stream? stream); - } + public Size? GetDimensions(Stream? stream); } diff --git a/src/Umbraco.Core/Media/IImageUrlGenerator.cs b/src/Umbraco.Core/Media/IImageUrlGenerator.cs index 25bb1ac899e9..d8fdf72005ee 100644 --- a/src/Umbraco.Core/Media/IImageUrlGenerator.cs +++ b/src/Umbraco.Core/Media/IImageUrlGenerator.cs @@ -1,28 +1,26 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Media +namespace Umbraco.Cms.Core.Media; + +/// +/// Exposes a method that generates an image URL based on the specified options. +/// +public interface IImageUrlGenerator { /// - /// Exposes a method that generates an image URL based on the specified options. + /// Gets the supported image file types/extensions. /// - public interface IImageUrlGenerator - { - /// - /// Gets the supported image file types/extensions. - /// - /// - /// The supported image file types/extensions. - /// - IEnumerable SupportedImageFileTypes { get; } + /// + /// The supported image file types/extensions. + /// + IEnumerable SupportedImageFileTypes { get; } - /// - /// Gets the image URL based on the specified . - /// - /// The image URL generation options. - /// - /// The generated image URL. - /// - string? GetImageUrl(ImageUrlGenerationOptions options); - } + /// + /// Gets the image URL based on the specified . + /// + /// The image URL generation options. + /// + /// The generated image URL. + /// + string? GetImageUrl(ImageUrlGenerationOptions options); } diff --git a/src/Umbraco.Core/Media/ImageUrlGeneratorExtensions.cs b/src/Umbraco.Core/Media/ImageUrlGeneratorExtensions.cs index 9cdc6869f411..ab904f20940f 100644 --- a/src/Umbraco.Core/Media/ImageUrlGeneratorExtensions.cs +++ b/src/Umbraco.Core/Media/ImageUrlGeneratorExtensions.cs @@ -1,29 +1,31 @@ -using System; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Media; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class ImageUrlGeneratorExtensions { - public static class ImageUrlGeneratorExtensions + /// + /// Gets a value indicating whether the file extension corresponds to a supported image. + /// + /// + /// The image URL generator implementation that provides detail on which image extensions + /// are supported. + /// + /// The file extension. + /// + /// A value indicating whether the file extension corresponds to an image. + /// + /// imageUrlGenerator + public static bool IsSupportedImageFormat(this IImageUrlGenerator imageUrlGenerator, string extension) { - /// - /// Gets a value indicating whether the file extension corresponds to a supported image. - /// - /// The image URL generator implementation that provides detail on which image extensions are supported. - /// The file extension. - /// - /// A value indicating whether the file extension corresponds to an image. - /// - /// imageUrlGenerator - public static bool IsSupportedImageFormat(this IImageUrlGenerator imageUrlGenerator, string extension) + if (imageUrlGenerator == null) { - if (imageUrlGenerator == null) - { - throw new ArgumentNullException(nameof(imageUrlGenerator)); - } - - return string.IsNullOrWhiteSpace(extension) == false && - imageUrlGenerator.SupportedImageFileTypes.InvariantContains(extension.TrimStart(Constants.CharArrays.Period)); + throw new ArgumentNullException(nameof(imageUrlGenerator)); } + + return string.IsNullOrWhiteSpace(extension) == false && + imageUrlGenerator.SupportedImageFileTypes.InvariantContains( + extension.TrimStart(Constants.CharArrays.Period)); } } diff --git a/src/Umbraco.Core/Media/OEmbedResult.cs b/src/Umbraco.Core/Media/OEmbedResult.cs index b370efc1ae61..bf7e36e61e83 100644 --- a/src/Umbraco.Core/Media/OEmbedResult.cs +++ b/src/Umbraco.Core/Media/OEmbedResult.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.Media +namespace Umbraco.Cms.Core.Media; + +public class OEmbedResult { - public class OEmbedResult - { - public OEmbedStatus OEmbedStatus { get; set; } - public bool SupportsDimensions { get; set; } - public string? Markup { get; set; } - } + public OEmbedStatus OEmbedStatus { get; set; } + public bool SupportsDimensions { get; set; } + public string? Markup { get; set; } } diff --git a/src/Umbraco.Core/Media/OEmbedStatus.cs b/src/Umbraco.Core/Media/OEmbedStatus.cs index 268fc1cd0d81..3e1f2024aa14 100644 --- a/src/Umbraco.Core/Media/OEmbedStatus.cs +++ b/src/Umbraco.Core/Media/OEmbedStatus.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.Media +namespace Umbraco.Cms.Core.Media; + +public enum OEmbedStatus { - public enum OEmbedStatus - { - NotSupported, - Error, - Success - } + NotSupported, + Error, + Success } diff --git a/src/Umbraco.Core/Media/TypeDetector/JpegDetector.cs b/src/Umbraco.Core/Media/TypeDetector/JpegDetector.cs index 0481323a4a15..7edb69609f0f 100644 --- a/src/Umbraco.Core/Media/TypeDetector/JpegDetector.cs +++ b/src/Umbraco.Core/Media/TypeDetector/JpegDetector.cs @@ -1,13 +1,10 @@ -using System.IO; +namespace Umbraco.Cms.Core.Media.TypeDetector; -namespace Umbraco.Cms.Core.Media.TypeDetector +public class JpegDetector : RasterizedTypeDetector { - public class JpegDetector : RasterizedTypeDetector + public static bool IsOfType(Stream fileStream) { - public static bool IsOfType(Stream fileStream) - { - var header = GetFileHeader(fileStream); - return header != null && header[0] == 0xff && header[1] == 0xD8; - } + var header = GetFileHeader(fileStream); + return header != null && header[0] == 0xff && header[1] == 0xD8; } } diff --git a/src/Umbraco.Core/Media/TypeDetector/RasterizedTypeDetector.cs b/src/Umbraco.Core/Media/TypeDetector/RasterizedTypeDetector.cs index 167fbe5e0e6f..562dc7a1dc0e 100644 --- a/src/Umbraco.Core/Media/TypeDetector/RasterizedTypeDetector.cs +++ b/src/Umbraco.Core/Media/TypeDetector/RasterizedTypeDetector.cs @@ -1,20 +1,19 @@ -using System.IO; +namespace Umbraco.Cms.Core.Media.TypeDetector; -namespace Umbraco.Cms.Core.Media.TypeDetector +public abstract class RasterizedTypeDetector { - public abstract class RasterizedTypeDetector + public static byte[]? GetFileHeader(Stream fileStream) { - public static byte[]? GetFileHeader(Stream fileStream) - { - fileStream.Seek(0, SeekOrigin.Begin); - var header = new byte[8]; - fileStream.Seek(0, SeekOrigin.Begin); - - // Invalid header - if (fileStream.Read(header, 0, header.Length) != header.Length) - return null; + fileStream.Seek(0, SeekOrigin.Begin); + var header = new byte[8]; + fileStream.Seek(0, SeekOrigin.Begin); - return header; + // Invalid header + if (fileStream.Read(header, 0, header.Length) != header.Length) + { + return null; } + + return header; } } diff --git a/src/Umbraco.Core/Media/TypeDetector/SvgDetector.cs b/src/Umbraco.Core/Media/TypeDetector/SvgDetector.cs index 81f13b199d95..b9b1318b06d6 100644 --- a/src/Umbraco.Core/Media/TypeDetector/SvgDetector.cs +++ b/src/Umbraco.Core/Media/TypeDetector/SvgDetector.cs @@ -1,24 +1,22 @@ -using System.IO; -using System.Xml.Linq; +using System.Xml.Linq; -namespace Umbraco.Cms.Core.Media.TypeDetector +namespace Umbraco.Cms.Core.Media.TypeDetector; + +public class SvgDetector { - public class SvgDetector + public static bool IsOfType(Stream fileStream) { - public static bool IsOfType(Stream fileStream) - { - var document = new XDocument(); + var document = new XDocument(); - try - { - document = XDocument.Load(fileStream); - } - catch (System.Exception) - { - return false; - } - - return document.Root?.Name.LocalName == "svg"; + try + { + document = XDocument.Load(fileStream); } + catch (Exception) + { + return false; + } + + return document.Root?.Name.LocalName == "svg"; } } diff --git a/src/Umbraco.Core/Media/TypeDetector/TIFFDetector.cs b/src/Umbraco.Core/Media/TypeDetector/TIFFDetector.cs index 1eda8efe7ac1..16acf7032630 100644 --- a/src/Umbraco.Core/Media/TypeDetector/TIFFDetector.cs +++ b/src/Umbraco.Core/Media/TypeDetector/TIFFDetector.cs @@ -1,24 +1,24 @@ -using System.IO; -using System.Text; +using System.Text; -namespace Umbraco.Cms.Core.Media.TypeDetector +namespace Umbraco.Cms.Core.Media.TypeDetector; + +public class TIFFDetector { - public class TIFFDetector + public static bool IsOfType(Stream fileStream) { - public static bool IsOfType(Stream fileStream) - { - var tiffHeader = GetFileHeader(fileStream); - return tiffHeader != null && tiffHeader == "MM\x00\x2a" || tiffHeader == "II\x2a\x00"; - } + var tiffHeader = GetFileHeader(fileStream); + return (tiffHeader != null && tiffHeader == "MM\x00\x2a") || tiffHeader == "II\x2a\x00"; + } - public static string? GetFileHeader(Stream fileStream) + public static string? GetFileHeader(Stream fileStream) + { + var header = RasterizedTypeDetector.GetFileHeader(fileStream); + if (header == null) { - var header = RasterizedTypeDetector.GetFileHeader(fileStream); - if (header == null) - return null; - - var tiffHeader = Encoding.ASCII.GetString(header, 0, 4); - return tiffHeader; + return null; } + + var tiffHeader = Encoding.ASCII.GetString(header, 0, 4); + return tiffHeader; } } diff --git a/src/Umbraco.Core/Media/UploadAutoFillProperties.cs b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs index 459866a8d97b..bb66995515be 100644 --- a/src/Umbraco.Core/Media/UploadAutoFillProperties.cs +++ b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs @@ -1,162 +1,223 @@ -using System; using System.Drawing; -using System.IO; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Media +namespace Umbraco.Cms.Core.Media; + +/// +/// Provides methods to manage auto-fill properties for upload fields. +/// +public class UploadAutoFillProperties { + private readonly IImageDimensionExtractor _imageDimensionExtractor; + private readonly IImageUrlGenerator _imageUrlGenerator; + private readonly ILogger _logger; + private readonly MediaFileManager _mediaFileManager; + + public UploadAutoFillProperties( + MediaFileManager mediaFileManager, + ILogger logger, + IImageUrlGenerator imageUrlGenerator, + IImageDimensionExtractor imageDimensionExtractor) + { + _mediaFileManager = mediaFileManager ?? throw new ArgumentNullException(nameof(mediaFileManager)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _imageUrlGenerator = imageUrlGenerator ?? throw new ArgumentNullException(nameof(imageUrlGenerator)); + _imageDimensionExtractor = + imageDimensionExtractor ?? throw new ArgumentNullException(nameof(imageDimensionExtractor)); + } + /// - /// Provides methods to manage auto-fill properties for upload fields. + /// Resets the auto-fill properties of a content item, for a specified auto-fill configuration. /// - public class UploadAutoFillProperties + /// The content item. + /// The auto-fill configuration. + /// Variation language. + /// Variation segment. + public void Reset(IContentBase content, ImagingAutoFillUploadField autoFillConfig, string? culture, string? segment) { - private readonly MediaFileManager _mediaFileManager; - private readonly ILogger _logger; - private readonly IImageUrlGenerator _imageUrlGenerator; - private readonly IImageDimensionExtractor _imageDimensionExtractor; - - public UploadAutoFillProperties( - MediaFileManager mediaFileManager, - ILogger logger, - IImageUrlGenerator imageUrlGenerator, - IImageDimensionExtractor imageDimensionExtractor) - { - _mediaFileManager = mediaFileManager ?? throw new ArgumentNullException(nameof(mediaFileManager)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _imageUrlGenerator = imageUrlGenerator ?? throw new ArgumentNullException(nameof(imageUrlGenerator)); - _imageDimensionExtractor = imageDimensionExtractor ?? throw new ArgumentNullException(nameof(imageDimensionExtractor)); - } - - /// - /// Resets the auto-fill properties of a content item, for a specified auto-fill configuration. - /// - /// The content item. - /// The auto-fill configuration. - /// Variation language. - /// Variation segment. - public void Reset(IContentBase content, ImagingAutoFillUploadField autoFillConfig, string? culture, string? segment) - { - if (content == null) throw new ArgumentNullException(nameof(content)); - if (autoFillConfig == null) throw new ArgumentNullException(nameof(autoFillConfig)); + if (content == null) + { + throw new ArgumentNullException(nameof(content)); + } - ResetProperties(content, autoFillConfig, culture, segment); + if (autoFillConfig == null) + { + throw new ArgumentNullException(nameof(autoFillConfig)); } - /// - /// Populates the auto-fill properties of a content item, for a specified auto-fill configuration. - /// - /// The content item. - /// The auto-fill configuration. - /// The filesystem path to the uploaded file. - /// The parameter is the path relative to the filesystem. - /// Variation language. - /// Variation segment. - public void Populate(IContentBase content, ImagingAutoFillUploadField autoFillConfig, string filepath, string? culture, string? segment) + ResetProperties(content, autoFillConfig, culture, segment); + } + + /// + /// Populates the auto-fill properties of a content item, for a specified auto-fill configuration. + /// + /// The content item. + /// The auto-fill configuration. + /// The filesystem path to the uploaded file. + /// The parameter is the path relative to the filesystem. + /// Variation language. + /// Variation segment. + public void Populate(IContentBase content, ImagingAutoFillUploadField autoFillConfig, string filepath, + string? culture, string? segment) + { + if (content == null) { - if (content == null) throw new ArgumentNullException(nameof(content)); - if (autoFillConfig == null) throw new ArgumentNullException(nameof(autoFillConfig)); + throw new ArgumentNullException(nameof(content)); + } - // no file = reset, file = auto-fill - if (filepath.IsNullOrWhiteSpace()) - { - ResetProperties(content, autoFillConfig, culture, segment); - } - else + if (autoFillConfig == null) + { + throw new ArgumentNullException(nameof(autoFillConfig)); + } + + // no file = reset, file = auto-fill + if (filepath.IsNullOrWhiteSpace()) + { + ResetProperties(content, autoFillConfig, culture, segment); + } + else + { + // it might not exist if the media item has been created programatically but doesn't have a file persisted yet. + if (_mediaFileManager.FileSystem.FileExists(filepath)) { - // it might not exist if the media item has been created programatically but doesn't have a file persisted yet. - if (_mediaFileManager.FileSystem.FileExists(filepath)) + // if anything goes wrong, just reset the properties + try { - // if anything goes wrong, just reset the properties - try + using (Stream filestream = _mediaFileManager.FileSystem.OpenFile(filepath)) { - using (Stream filestream = _mediaFileManager.FileSystem.OpenFile(filepath)) - { - SetProperties(content, autoFillConfig, filepath, filestream, culture, segment); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Could not populate upload auto-fill properties for file '{File}'.", filepath); - ResetProperties(content, autoFillConfig, culture, segment); + SetProperties(content, autoFillConfig, filepath, filestream, culture, segment); } } + catch (Exception ex) + { + _logger.LogError(ex, "Could not populate upload auto-fill properties for file '{File}'.", filepath); + ResetProperties(content, autoFillConfig, culture, segment); + } } } + } - /// - /// Populates the auto-fill properties of a content item. - /// - /// The content item. - /// - /// The filesystem-relative filepath, or null to clear properties. - /// The stream containing the file data. - /// Variation language. - /// Variation segment. - public void Populate(IContentBase content, ImagingAutoFillUploadField autoFillConfig, string filepath, Stream filestream, string culture, string segment) + /// + /// Populates the auto-fill properties of a content item. + /// + /// The content item. + /// + /// The filesystem-relative filepath, or null to clear properties. + /// The stream containing the file data. + /// Variation language. + /// Variation segment. + public void Populate(IContentBase content, ImagingAutoFillUploadField autoFillConfig, string filepath, + Stream filestream, string culture, string segment) + { + if (content == null) { - if (content == null) throw new ArgumentNullException(nameof(content)); - if (autoFillConfig == null) throw new ArgumentNullException(nameof(autoFillConfig)); + throw new ArgumentNullException(nameof(content)); + } - // no file = reset, file = auto-fill - if (filepath.IsNullOrWhiteSpace() || filestream == null) - { - ResetProperties(content, autoFillConfig, culture, segment); - } - else - { - SetProperties(content, autoFillConfig, filepath, filestream, culture, segment); - } + if (autoFillConfig == null) + { + throw new ArgumentNullException(nameof(autoFillConfig)); } - private void SetProperties(IContentBase content, ImagingAutoFillUploadField autoFillConfig, string filepath, Stream? filestream, string? culture, string? segment) + // no file = reset, file = auto-fill + if (filepath.IsNullOrWhiteSpace() || filestream == null) + { + ResetProperties(content, autoFillConfig, culture, segment); + } + else { - var extension = (Path.GetExtension(filepath) ?? string.Empty).TrimStart(Constants.CharArrays.Period); + SetProperties(content, autoFillConfig, filepath, filestream, culture, segment); + } + } + + private void SetProperties(IContentBase content, ImagingAutoFillUploadField autoFillConfig, string filepath, + Stream? filestream, string? culture, string? segment) + { + var extension = (Path.GetExtension(filepath) ?? string.Empty).TrimStart(Constants.CharArrays.Period); - var size = _imageUrlGenerator.IsSupportedImageFormat(extension) - ? _imageDimensionExtractor.GetDimensions(filestream) ?? (Size?)new Size(Constants.Conventions.Media.DefaultSize, Constants.Conventions.Media.DefaultSize) - : null; + Size? size = _imageUrlGenerator.IsSupportedImageFormat(extension) + ? _imageDimensionExtractor.GetDimensions(filestream) ?? + (Size?)new Size(Constants.Conventions.Media.DefaultSize, Constants.Conventions.Media.DefaultSize) + : null; - SetProperties(content, autoFillConfig, size, filestream?.Length, extension, culture, segment); + SetProperties(content, autoFillConfig, size, filestream?.Length, extension, culture, segment); + } + + private static void SetProperties(IContentBase content, ImagingAutoFillUploadField autoFillConfig, Size? size, + long? length, string extension, string? culture, string? segment) + { + if (content == null) + { + throw new ArgumentNullException(nameof(content)); } - private static void SetProperties(IContentBase content, ImagingAutoFillUploadField autoFillConfig, Size? size, long? length, string extension, string? culture, string? segment) + if (autoFillConfig == null) { - if (content == null) throw new ArgumentNullException(nameof(content)); - if (autoFillConfig == null) throw new ArgumentNullException(nameof(autoFillConfig)); + throw new ArgumentNullException(nameof(autoFillConfig)); + } + + if (!string.IsNullOrWhiteSpace(autoFillConfig.WidthFieldAlias) && + content.Properties.Contains(autoFillConfig.WidthFieldAlias)) + { + content.Properties[autoFillConfig.WidthFieldAlias]!.SetValue( + size.HasValue ? size.Value.Width.ToInvariantString() : string.Empty, culture, segment); + } - if (!string.IsNullOrWhiteSpace(autoFillConfig.WidthFieldAlias) && content.Properties.Contains(autoFillConfig.WidthFieldAlias)) - content.Properties[autoFillConfig.WidthFieldAlias]!.SetValue(size.HasValue ? size.Value.Width.ToInvariantString() : string.Empty, culture, segment); + if (!string.IsNullOrWhiteSpace(autoFillConfig.HeightFieldAlias) && + content.Properties.Contains(autoFillConfig.HeightFieldAlias)) + { + content.Properties[autoFillConfig.HeightFieldAlias]!.SetValue( + size.HasValue ? size.Value.Height.ToInvariantString() : string.Empty, culture, segment); + } - if (!string.IsNullOrWhiteSpace(autoFillConfig.HeightFieldAlias) && content.Properties.Contains(autoFillConfig.HeightFieldAlias)) - content.Properties[autoFillConfig.HeightFieldAlias]!.SetValue(size.HasValue ? size.Value.Height.ToInvariantString() : string.Empty, culture, segment); + if (!string.IsNullOrWhiteSpace(autoFillConfig.LengthFieldAlias) && + content.Properties.Contains(autoFillConfig.LengthFieldAlias)) + { + content.Properties[autoFillConfig.LengthFieldAlias]!.SetValue(length, culture, segment); + } - if (!string.IsNullOrWhiteSpace(autoFillConfig.LengthFieldAlias) && content.Properties.Contains(autoFillConfig.LengthFieldAlias)) - content.Properties[autoFillConfig.LengthFieldAlias]!.SetValue(length, culture, segment); + if (!string.IsNullOrWhiteSpace(autoFillConfig.ExtensionFieldAlias) && + content.Properties.Contains(autoFillConfig.ExtensionFieldAlias)) + { + content.Properties[autoFillConfig.ExtensionFieldAlias]!.SetValue(extension, culture, segment); + } + } - if (!string.IsNullOrWhiteSpace(autoFillConfig.ExtensionFieldAlias) && content.Properties.Contains(autoFillConfig.ExtensionFieldAlias)) - content.Properties[autoFillConfig.ExtensionFieldAlias]!.SetValue(extension, culture, segment); + private static void ResetProperties(IContentBase content, ImagingAutoFillUploadField autoFillConfig, + string? culture, string? segment) + { + if (content == null) + { + throw new ArgumentNullException(nameof(content)); } - private static void ResetProperties(IContentBase content, ImagingAutoFillUploadField autoFillConfig, string? culture, string? segment) + if (autoFillConfig == null) { - if (content == null) throw new ArgumentNullException(nameof(content)); - if (autoFillConfig == null) throw new ArgumentNullException(nameof(autoFillConfig)); + throw new ArgumentNullException(nameof(autoFillConfig)); + } - if (content.Properties.Contains(autoFillConfig.WidthFieldAlias)) - content.Properties[autoFillConfig.WidthFieldAlias]?.SetValue(string.Empty, culture, segment); + if (content.Properties.Contains(autoFillConfig.WidthFieldAlias)) + { + content.Properties[autoFillConfig.WidthFieldAlias]?.SetValue(string.Empty, culture, segment); + } - if (content.Properties.Contains(autoFillConfig.HeightFieldAlias)) - content.Properties[autoFillConfig.HeightFieldAlias]?.SetValue(string.Empty, culture, segment); + if (content.Properties.Contains(autoFillConfig.HeightFieldAlias)) + { + content.Properties[autoFillConfig.HeightFieldAlias]?.SetValue(string.Empty, culture, segment); + } - if (content.Properties.Contains(autoFillConfig.LengthFieldAlias)) - content.Properties[autoFillConfig.LengthFieldAlias]?.SetValue(string.Empty, culture, segment); + if (content.Properties.Contains(autoFillConfig.LengthFieldAlias)) + { + content.Properties[autoFillConfig.LengthFieldAlias]?.SetValue(string.Empty, culture, segment); + } - if (content.Properties.Contains(autoFillConfig.ExtensionFieldAlias)) - content.Properties[autoFillConfig.ExtensionFieldAlias]?.SetValue(string.Empty, culture, segment); + if (content.Properties.Contains(autoFillConfig.ExtensionFieldAlias)) + { + content.Properties[autoFillConfig.ExtensionFieldAlias]?.SetValue(string.Empty, culture, segment); } } } diff --git a/src/Umbraco.Core/Models/AnchorsModel.cs b/src/Umbraco.Core/Models/AnchorsModel.cs index 466751c82d0c..90faa01da1e6 100644 --- a/src/Umbraco.Core/Models/AnchorsModel.cs +++ b/src/Umbraco.Core/Models/AnchorsModel.cs @@ -1,7 +1,6 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public class AnchorsModel { - public class AnchorsModel - { - public string? RteContent { get; set; } - } + public string? RteContent { get; set; } } diff --git a/src/Umbraco.Core/Models/AuditEntry.cs b/src/Umbraco.Core/Models/AuditEntry.cs index e0bb52375b22..6d15e9ef316b 100644 --- a/src/Umbraco.Core/Models/AuditEntry.cs +++ b/src/Umbraco.Core/Models/AuditEntry.cs @@ -1,78 +1,76 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents an audited event. +/// +[Serializable] +[DataContract(IsReference = true)] +public class AuditEntry : EntityBase, IAuditEntry { - /// - /// Represents an audited event. - /// - [Serializable] - [DataContract(IsReference = true)] - public class AuditEntry : EntityBase, IAuditEntry - { - private int _performingUserId; - private string? _performingDetails; - private string? _performingIp; - private int _affectedUserId; - private string? _affectedDetails; - private string? _eventType; - private string? _eventDetails; + private string? _affectedDetails; + private int _affectedUserId; + private string? _eventDetails; + private string? _eventType; + private string? _performingDetails; + private string? _performingIp; + private int _performingUserId; - /// - public int PerformingUserId - { - get => _performingUserId; - set => SetPropertyValueAndDetectChanges(value, ref _performingUserId, nameof(PerformingUserId)); - } + /// + public int PerformingUserId + { + get => _performingUserId; + set => SetPropertyValueAndDetectChanges(value, ref _performingUserId, nameof(PerformingUserId)); + } - /// - public string? PerformingDetails - { - get => _performingDetails; - set => SetPropertyValueAndDetectChanges(value, ref _performingDetails, nameof(PerformingDetails)); - } + /// + public string? PerformingDetails + { + get => _performingDetails; + set => SetPropertyValueAndDetectChanges(value, ref _performingDetails, nameof(PerformingDetails)); + } - /// - public string? PerformingIp - { - get => _performingIp; - set => SetPropertyValueAndDetectChanges(value, ref _performingIp, nameof(PerformingIp)); - } + /// + public string? PerformingIp + { + get => _performingIp; + set => SetPropertyValueAndDetectChanges(value, ref _performingIp, nameof(PerformingIp)); + } - /// - public DateTime EventDateUtc - { - get => CreateDate; - set => CreateDate = value; - } + /// + public DateTime EventDateUtc + { + get => CreateDate; + set => CreateDate = value; + } - /// - public int AffectedUserId - { - get => _affectedUserId; - set => SetPropertyValueAndDetectChanges(value, ref _affectedUserId, nameof(AffectedUserId)); - } + /// + public int AffectedUserId + { + get => _affectedUserId; + set => SetPropertyValueAndDetectChanges(value, ref _affectedUserId, nameof(AffectedUserId)); + } - /// - public string? AffectedDetails - { - get => _affectedDetails; - set => SetPropertyValueAndDetectChanges(value, ref _affectedDetails, nameof(AffectedDetails)); - } + /// + public string? AffectedDetails + { + get => _affectedDetails; + set => SetPropertyValueAndDetectChanges(value, ref _affectedDetails, nameof(AffectedDetails)); + } - /// - public string? EventType - { - get => _eventType; - set => SetPropertyValueAndDetectChanges(value, ref _eventType, nameof(EventType)); - } + /// + public string? EventType + { + get => _eventType; + set => SetPropertyValueAndDetectChanges(value, ref _eventType, nameof(EventType)); + } - /// - public string? EventDetails - { - get => _eventDetails; - set => SetPropertyValueAndDetectChanges(value, ref _eventDetails, nameof(EventDetails)); - } + /// + public string? EventDetails + { + get => _eventDetails; + set => SetPropertyValueAndDetectChanges(value, ref _eventDetails, nameof(EventDetails)); } } diff --git a/src/Umbraco.Core/Models/AuditItem.cs b/src/Umbraco.Core/Models/AuditItem.cs index 83ecad0878a1..0afdc5a0500f 100644 --- a/src/Umbraco.Core/Models/AuditItem.cs +++ b/src/Umbraco.Core/Models/AuditItem.cs @@ -1,39 +1,39 @@ using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public sealed class AuditItem : EntityBase, IAuditItem { - public sealed class AuditItem : EntityBase, IAuditItem + /// + /// Initializes a new instance of the class. + /// + public AuditItem(int objectId, AuditType type, int userId, string? entityType, string? comment = null, + string? parameters = null) { - /// - /// Initializes a new instance of the class. - /// - public AuditItem(int objectId, AuditType type, int userId, string? entityType, string? comment = null, string? parameters = null) - { - DisableChangeTracking(); + DisableChangeTracking(); - Id = objectId; - Comment = comment; - AuditType = type; - UserId = userId; - EntityType = entityType; - Parameters = parameters; + Id = objectId; + Comment = comment; + AuditType = type; + UserId = userId; + EntityType = entityType; + Parameters = parameters; - EnableChangeTracking(); - } + EnableChangeTracking(); + } - /// - public AuditType AuditType { get; } + /// + public AuditType AuditType { get; } - /// - public string? EntityType { get; } + /// + public string? EntityType { get; } - /// - public int UserId { get; } + /// + public int UserId { get; } - /// - public string? Comment { get; } + /// + public string? Comment { get; } - /// - public string? Parameters { get; } - } + /// + public string? Parameters { get; } } diff --git a/src/Umbraco.Core/Models/AuditType.cs b/src/Umbraco.Core/Models/AuditType.cs index b6a36be5ff03..c421ffd7ea5a 100644 --- a/src/Umbraco.Core/Models/AuditType.cs +++ b/src/Umbraco.Core/Models/AuditType.cs @@ -1,128 +1,127 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Defines audit types. +/// +public enum AuditType { /// - /// Defines audit types. - /// - public enum AuditType - { - /// - /// New node(s) being added. - /// - New, - - /// - /// Node(s) being saved. - /// - Save, - - /// - /// Variant(s) being saved. - /// - SaveVariant, - - /// - /// Node(s) being opened. - /// - Open, - - /// - /// Node(s) being deleted. - /// - Delete, - - /// - /// Node(s) being published. - /// - Publish, - - /// - /// Variant(s) being published. - /// - PublishVariant, - - /// - /// Node(s) being sent to publishing. - /// - SendToPublish, - - /// - /// Variant(s) being sent to publishing. - /// - SendToPublishVariant, - - /// - /// Node(s) being unpublished. - /// - Unpublish, - - /// - /// Variant(s) being unpublished. - /// - UnpublishVariant, - - /// - /// Node(s) being moved. - /// - Move, - - /// - /// Node(s) being copied. - /// - Copy, - - /// - /// Node(s) being assigned domains. - /// - AssignDomain, - - /// - /// Node(s) public access changing. - /// - PublicAccess, - - /// - /// Node(s) being sorted. - /// - Sort, - - /// - /// Notification(s) being sent to user. - /// - Notify, - - /// - /// General system audit message. - /// - System, - - /// - /// Node's content being rolled back to a previous version. - /// - RollBack, - - /// - /// Package being installed. - /// - PackagerInstall, - - /// - /// Package being uninstalled. - /// - PackagerUninstall, - - /// - /// Custom audit message. - /// - Custom, - - /// - /// Content version preventCleanup set to true - /// - ContentVersionPreventCleanup, - - /// - /// Content version preventCleanup set to false - /// - ContentVersionEnableCleanup - } + /// New node(s) being added. + /// + New, + + /// + /// Node(s) being saved. + /// + Save, + + /// + /// Variant(s) being saved. + /// + SaveVariant, + + /// + /// Node(s) being opened. + /// + Open, + + /// + /// Node(s) being deleted. + /// + Delete, + + /// + /// Node(s) being published. + /// + Publish, + + /// + /// Variant(s) being published. + /// + PublishVariant, + + /// + /// Node(s) being sent to publishing. + /// + SendToPublish, + + /// + /// Variant(s) being sent to publishing. + /// + SendToPublishVariant, + + /// + /// Node(s) being unpublished. + /// + Unpublish, + + /// + /// Variant(s) being unpublished. + /// + UnpublishVariant, + + /// + /// Node(s) being moved. + /// + Move, + + /// + /// Node(s) being copied. + /// + Copy, + + /// + /// Node(s) being assigned domains. + /// + AssignDomain, + + /// + /// Node(s) public access changing. + /// + PublicAccess, + + /// + /// Node(s) being sorted. + /// + Sort, + + /// + /// Notification(s) being sent to user. + /// + Notify, + + /// + /// General system audit message. + /// + System, + + /// + /// Node's content being rolled back to a previous version. + /// + RollBack, + + /// + /// Package being installed. + /// + PackagerInstall, + + /// + /// Package being uninstalled. + /// + PackagerUninstall, + + /// + /// Custom audit message. + /// + Custom, + + /// + /// Content version preventCleanup set to true + /// + ContentVersionPreventCleanup, + + /// + /// Content version preventCleanup set to false + /// + ContentVersionEnableCleanup } diff --git a/src/Umbraco.Core/Models/BackOfficeTour.cs b/src/Umbraco.Core/Models/BackOfficeTour.cs index d6a5d8971ee6..43770e6ad4c3 100644 --- a/src/Umbraco.Core/Models/BackOfficeTour.cs +++ b/src/Umbraco.Core/Models/BackOfficeTour.cs @@ -1,47 +1,33 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// A model representing a tour. +/// +[DataContract(Name = "tour", Namespace = "")] +public class BackOfficeTour { - /// - /// A model representing a tour. - /// - [DataContract(Name = "tour", Namespace = "")] - public class BackOfficeTour - { - public BackOfficeTour() - { - RequiredSections = new List(); - } + public BackOfficeTour() => RequiredSections = new List(); - [DataMember(Name = "name")] - public string? Name { get; set; } + [DataMember(Name = "name")] public string? Name { get; set; } - [DataMember(Name = "alias")] - public string Alias { get; set; } = null!; + [DataMember(Name = "alias")] public string Alias { get; set; } = null!; - [DataMember(Name = "group")] - public string? Group { get; set; } + [DataMember(Name = "group")] public string? Group { get; set; } - [DataMember(Name = "groupOrder")] - public int GroupOrder { get; set; } + [DataMember(Name = "groupOrder")] public int GroupOrder { get; set; } - [DataMember(Name = "hidden")] - public bool Hidden { get; set; } + [DataMember(Name = "hidden")] public bool Hidden { get; set; } - [DataMember(Name = "allowDisable")] - public bool AllowDisable { get; set; } + [DataMember(Name = "allowDisable")] public bool AllowDisable { get; set; } - [DataMember(Name = "requiredSections")] - public List RequiredSections { get; set; } + [DataMember(Name = "requiredSections")] + public List RequiredSections { get; set; } - [DataMember(Name = "steps")] - public BackOfficeTourStep[]? Steps { get; set; } + [DataMember(Name = "steps")] public BackOfficeTourStep[]? Steps { get; set; } - [DataMember(Name = "culture")] - public string? Culture { get; set; } + [DataMember(Name = "culture")] public string? Culture { get; set; } - [DataMember(Name = "contentType")] - public string? ContentType { get; set; } - } + [DataMember(Name = "contentType")] public string? ContentType { get; set; } } diff --git a/src/Umbraco.Core/Models/BackOfficeTourFile.cs b/src/Umbraco.Core/Models/BackOfficeTourFile.cs index 21b769f94e5f..e73946f5af8f 100644 --- a/src/Umbraco.Core/Models/BackOfficeTourFile.cs +++ b/src/Umbraco.Core/Models/BackOfficeTourFile.cs @@ -1,35 +1,29 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// A model representing the file used to load a tour. +/// +[DataContract(Name = "tourFile", Namespace = "")] +public class BackOfficeTourFile { + public BackOfficeTourFile() => Tours = new List(); + /// - /// A model representing the file used to load a tour. + /// The file name for the tour /// - [DataContract(Name = "tourFile", Namespace = "")] - public class BackOfficeTourFile - { - public BackOfficeTourFile() - { - Tours = new List(); - } - - /// - /// The file name for the tour - /// - [DataMember(Name = "fileName")] - public string? FileName { get; set; } + [DataMember(Name = "fileName")] + public string? FileName { get; set; } - /// - /// The plugin folder that the tour comes from - /// - /// - /// If this is null it means it's a Core tour - /// - [DataMember(Name = "pluginName")] - public string? PluginName { get; set; } + /// + /// The plugin folder that the tour comes from + /// + /// + /// If this is null it means it's a Core tour + /// + [DataMember(Name = "pluginName")] + public string? PluginName { get; set; } - [DataMember(Name = "tours")] - public IEnumerable Tours { get; set; } - } + [DataMember(Name = "tours")] public IEnumerable Tours { get; set; } } diff --git a/src/Umbraco.Core/Models/BackOfficeTourStep.cs b/src/Umbraco.Core/Models/BackOfficeTourStep.cs index aa2aaf7f533c..377bc84b6ac5 100644 --- a/src/Umbraco.Core/Models/BackOfficeTourStep.cs +++ b/src/Umbraco.Core/Models/BackOfficeTourStep.cs @@ -1,34 +1,35 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// A model representing a step in a tour. +/// +[DataContract(Name = "step", Namespace = "")] +public class BackOfficeTourStep { - /// - /// A model representing a step in a tour. - /// - [DataContract(Name = "step", Namespace = "")] - public class BackOfficeTourStep - { - [DataMember(Name = "title")] - public string? Title { get; set; } - [DataMember(Name = "content")] - public string? Content { get; set; } - [DataMember(Name = "type")] - public string? Type { get; set; } - [DataMember(Name = "element")] - public string? Element { get; set; } - [DataMember(Name = "elementPreventClick")] - public bool ElementPreventClick { get; set; } - [DataMember(Name = "backdropOpacity")] - public float? BackdropOpacity { get; set; } - [DataMember(Name = "event")] - public string? Event { get; set; } - [DataMember(Name = "view")] - public string? View { get; set; } - [DataMember(Name = "eventElement")] - public string? EventElement { get; set; } - [DataMember(Name = "customProperties")] - public object? CustomProperties { get; set; } - [DataMember(Name = "skipStepIfVisible")] - public string? SkipStepIfVisible { get; set; } - } + [DataMember(Name = "title")] public string? Title { get; set; } + + [DataMember(Name = "content")] public string? Content { get; set; } + + [DataMember(Name = "type")] public string? Type { get; set; } + + [DataMember(Name = "element")] public string? Element { get; set; } + + [DataMember(Name = "elementPreventClick")] + public bool ElementPreventClick { get; set; } + + [DataMember(Name = "backdropOpacity")] public float? BackdropOpacity { get; set; } + + [DataMember(Name = "event")] public string? Event { get; set; } + + [DataMember(Name = "view")] public string? View { get; set; } + + [DataMember(Name = "eventElement")] public string? EventElement { get; set; } + + [DataMember(Name = "customProperties")] + public object? CustomProperties { get; set; } + + [DataMember(Name = "skipStepIfVisible")] + public string? SkipStepIfVisible { get; set; } } diff --git a/src/Umbraco.Core/Models/Blocks/BlockListItem.cs b/src/Umbraco.Core/Models/Blocks/BlockListItem.cs index 400649ff0541..d159c490b231 100644 --- a/src/Umbraco.Core/Models/Blocks/BlockListItem.cs +++ b/src/Umbraco.Core/Models/Blocks/BlockListItem.cs @@ -1,130 +1,126 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.Models.Blocks +namespace Umbraco.Cms.Core.Models.Blocks; + +/// +/// Represents a layout item for the Block List editor. +/// +/// +[DataContract(Name = "block", Namespace = "")] +public class BlockListItem : IBlockReference { /// - /// Represents a layout item for the Block List editor. + /// Initializes a new instance of the class. /// - /// - [DataContract(Name = "block", Namespace = "")] - public class BlockListItem : IBlockReference + /// The content UDI. + /// The content. + /// The settings UDI. + /// The settings. + /// + /// contentUdi + /// or + /// content + /// + public BlockListItem(Udi contentUdi, IPublishedElement content, Udi settingsUdi, IPublishedElement settings) { - /// - /// Initializes a new instance of the class. - /// - /// The content UDI. - /// The content. - /// The settings UDI. - /// The settings. - /// contentUdi - /// or - /// content - public BlockListItem(Udi contentUdi, IPublishedElement content, Udi settingsUdi, IPublishedElement settings) - { - ContentUdi = contentUdi ?? throw new ArgumentNullException(nameof(contentUdi)); - Content = content ?? throw new ArgumentNullException(nameof(content)); - SettingsUdi = settingsUdi; - Settings = settings; - } + ContentUdi = contentUdi ?? throw new ArgumentNullException(nameof(contentUdi)); + Content = content ?? throw new ArgumentNullException(nameof(content)); + SettingsUdi = settingsUdi; + Settings = settings; + } - /// - /// Gets the content UDI. - /// - /// - /// The content UDI. - /// - [DataMember(Name = "contentUdi")] - public Udi ContentUdi { get; } + /// + /// Gets the content. + /// + /// + /// The content. + /// + [DataMember(Name = "content")] + public IPublishedElement Content { get; } - /// - /// Gets the content. - /// - /// - /// The content. - /// - [DataMember(Name = "content")] - public IPublishedElement Content { get; } + /// + /// Gets the settings UDI. + /// + /// + /// The settings UDI. + /// + [DataMember(Name = "settingsUdi")] + public Udi SettingsUdi { get; } - /// - /// Gets the settings UDI. - /// - /// - /// The settings UDI. - /// - [DataMember(Name = "settingsUdi")] - public Udi SettingsUdi { get; } + /// + /// Gets the content UDI. + /// + /// + /// The content UDI. + /// + [DataMember(Name = "contentUdi")] + public Udi ContentUdi { get; } - /// - /// Gets the settings. - /// - /// - /// The settings. - /// - [DataMember(Name = "settings")] - public IPublishedElement Settings { get; } - } + /// + /// Gets the settings. + /// + /// + /// The settings. + /// + [DataMember(Name = "settings")] + public IPublishedElement Settings { get; } +} +/// +/// Represents a layout item with a generic content type for the Block List editor. +/// +/// The type of the content. +/// +public class BlockListItem : BlockListItem + where T : IPublishedElement +{ /// - /// Represents a layout item with a generic content type for the Block List editor. + /// Initializes a new instance of the class. /// - /// The type of the content. - /// - public class BlockListItem : BlockListItem - where T : IPublishedElement - { - /// - /// Initializes a new instance of the class. - /// - /// The content UDI. - /// The content. - /// The settings UDI. - /// The settings. - public BlockListItem(Udi contentUdi, T content, Udi settingsUdi, IPublishedElement settings) - : base(contentUdi, content, settingsUdi, settings) - { - Content = content; - } + /// The content UDI. + /// The content. + /// The settings UDI. + /// The settings. + public BlockListItem(Udi contentUdi, T content, Udi settingsUdi, IPublishedElement settings) + : base(contentUdi, content, settingsUdi, settings) => + Content = content; - /// - /// Gets the content. - /// - /// - /// The content. - /// - public new T Content { get; } - } + /// + /// Gets the content. + /// + /// + /// The content. + /// + public new T Content { get; } +} +/// +/// Represents a layout item with generic content and settings types for the Block List editor. +/// +/// The type of the content. +/// The type of the settings. +/// +public class BlockListItem : BlockListItem + where TContent : IPublishedElement + where TSettings : IPublishedElement +{ /// - /// Represents a layout item with generic content and settings types for the Block List editor. + /// Initializes a new instance of the class. /// - /// The type of the content. - /// The type of the settings. - /// - public class BlockListItem : BlockListItem - where TContent : IPublishedElement - where TSettings : IPublishedElement - { - /// - /// Initializes a new instance of the class. - /// - /// The content udi. - /// The content. - /// The settings udi. - /// The settings. - public BlockListItem(Udi contentUdi, TContent content, Udi settingsUdi, TSettings settings) - : base(contentUdi, content, settingsUdi, settings) - { - Settings = settings; - } + /// The content udi. + /// The content. + /// The settings udi. + /// The settings. + public BlockListItem(Udi contentUdi, TContent content, Udi settingsUdi, TSettings settings) + : base(contentUdi, content, settingsUdi, settings) => + Settings = settings; - /// - /// Gets the settings. - /// - /// - /// The settings. - /// - public new TSettings Settings { get; } - } + /// + /// Gets the settings. + /// + /// + /// The settings. + /// + public new TSettings Settings { get; } } diff --git a/src/Umbraco.Core/Models/Blocks/BlockListModel.cs b/src/Umbraco.Core/Models/Blocks/BlockListModel.cs index 33a711520b4b..359c269164ce 100644 --- a/src/Umbraco.Core/Models/Blocks/BlockListModel.cs +++ b/src/Umbraco.Core/Models/Blocks/BlockListModel.cs @@ -1,63 +1,63 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; +using System.Collections.ObjectModel; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.Blocks +namespace Umbraco.Cms.Core.Models.Blocks; + +/// +/// The strongly typed model for the Block List editor. +/// +/// +[DataContract(Name = "blockList", Namespace = "")] +public class BlockListModel : ReadOnlyCollection { /// - /// The strongly typed model for the Block List editor. + /// Prevents a default instance of the class from being created. /// - /// - [DataContract(Name = "blockList", Namespace = "")] - public class BlockListModel : ReadOnlyCollection + private BlockListModel() + : this(new List()) { - /// - /// Gets the empty . - /// - /// - /// The empty . - /// - public static BlockListModel Empty { get; } = new BlockListModel(); + } - /// - /// Prevents a default instance of the class from being created. - /// - private BlockListModel() - : this(new List()) - { } + /// + /// Initializes a new instance of the class. + /// + /// The list to wrap. + public BlockListModel(IList list) + : base(list) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The list to wrap. - public BlockListModel(IList list) - : base(list) - { } + /// + /// Gets the empty . + /// + /// + /// The empty . + /// + public static BlockListModel Empty { get; } = new(); - /// - /// Gets the with the specified content key. - /// - /// - /// The . - /// - /// The content key. - /// - /// The with the specified content key. - /// - public BlockListItem? this[Guid contentKey] => this.FirstOrDefault(x => x.Content.Key == contentKey); + /// + /// Gets the with the specified content key. + /// + /// + /// The . + /// + /// The content key. + /// + /// The with the specified content key. + /// + public BlockListItem? this[Guid contentKey] => this.FirstOrDefault(x => x.Content.Key == contentKey); - /// - /// Gets the with the specified content UDI. - /// - /// - /// The . - /// - /// The content UDI. - /// - /// The with the specified content UDI. - /// - public BlockListItem? this[Udi contentUdi] => contentUdi is GuidUdi guidUdi ? this.FirstOrDefault(x => x.Content.Key == guidUdi.Guid) : null; - } + /// + /// Gets the with the specified content UDI. + /// + /// + /// The . + /// + /// The content UDI. + /// + /// The with the specified content UDI. + /// + public BlockListItem? this[Udi contentUdi] => contentUdi is GuidUdi guidUdi + ? this.FirstOrDefault(x => x.Content.Key == guidUdi.Guid) + : null; } diff --git a/src/Umbraco.Core/Models/Blocks/ContentAndSettingsReference.cs b/src/Umbraco.Core/Models/Blocks/ContentAndSettingsReference.cs index f8677490ee74..0112e2875e21 100644 --- a/src/Umbraco.Core/Models/Blocks/ContentAndSettingsReference.cs +++ b/src/Umbraco.Core/Models/Blocks/ContentAndSettingsReference.cs @@ -1,36 +1,30 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models.Blocks; -namespace Umbraco.Cms.Core.Models.Blocks +public struct ContentAndSettingsReference : IEquatable { - public struct ContentAndSettingsReference : IEquatable + public ContentAndSettingsReference(Udi? contentUdi, Udi? settingsUdi) { - public ContentAndSettingsReference(Udi? contentUdi, Udi? settingsUdi) - { - ContentUdi = contentUdi ?? throw new ArgumentNullException(nameof(contentUdi)); - SettingsUdi = settingsUdi; - } + ContentUdi = contentUdi ?? throw new ArgumentNullException(nameof(contentUdi)); + SettingsUdi = settingsUdi; + } - public Udi ContentUdi { get; } + public Udi ContentUdi { get; } - public Udi? SettingsUdi { get; } + public Udi? SettingsUdi { get; } - public override bool Equals(object? obj) => obj is ContentAndSettingsReference reference && Equals(reference); + public override bool Equals(object? obj) => obj is ContentAndSettingsReference reference && Equals(reference); - public bool Equals(ContentAndSettingsReference other) => other != null - && EqualityComparer.Default.Equals(ContentUdi, other.ContentUdi) - && EqualityComparer.Default.Equals(SettingsUdi, other.SettingsUdi); + public bool Equals(ContentAndSettingsReference other) => other != null + && EqualityComparer.Default.Equals(ContentUdi, + other.ContentUdi) + && EqualityComparer.Default.Equals(SettingsUdi, + other.SettingsUdi); - public override int GetHashCode() => (ContentUdi, SettingsUdi).GetHashCode(); + public override int GetHashCode() => (ContentUdi, SettingsUdi).GetHashCode(); - public static bool operator ==(ContentAndSettingsReference left, ContentAndSettingsReference right) - { - return left.Equals(right); - } + public static bool operator ==(ContentAndSettingsReference left, ContentAndSettingsReference right) => + left.Equals(right); - public static bool operator !=(ContentAndSettingsReference left, ContentAndSettingsReference right) - { - return !(left == right); - } - } + public static bool operator !=(ContentAndSettingsReference left, ContentAndSettingsReference right) => + !(left == right); } diff --git a/src/Umbraco.Core/Models/Blocks/IBlockReference.cs b/src/Umbraco.Core/Models/Blocks/IBlockReference.cs index 48c2b8563760..cdd1c3958430 100644 --- a/src/Umbraco.Core/Models/Blocks/IBlockReference.cs +++ b/src/Umbraco.Core/Models/Blocks/IBlockReference.cs @@ -1,37 +1,38 @@ -namespace Umbraco.Cms.Core.Models.Blocks +namespace Umbraco.Cms.Core.Models.Blocks; + +/// +/// Represents a data item reference for a Block Editor implementation. +/// +/// +/// See: +/// https://github.com/umbraco/rfcs/blob/907f3758cf59a7b6781296a60d57d537b3b60b8c/cms/0011-block-data-structure.md#strongly-typed +/// +public interface IBlockReference { /// - /// Represents a data item reference for a Block Editor implementation. + /// Gets the content UDI. /// - /// - /// See: https://github.com/umbraco/rfcs/blob/907f3758cf59a7b6781296a60d57d537b3b60b8c/cms/0011-block-data-structure.md#strongly-typed - /// - public interface IBlockReference - { - /// - /// Gets the content UDI. - /// - /// - /// The content UDI. - /// - Udi ContentUdi { get; } - } + /// + /// The content UDI. + /// + Udi ContentUdi { get; } +} +/// +/// Represents a data item reference with settings for a Block editor implementation. +/// +/// The type of the settings. +/// +/// See: +/// https://github.com/umbraco/rfcs/blob/907f3758cf59a7b6781296a60d57d537b3b60b8c/cms/0011-block-data-structure.md#strongly-typed +/// +public interface IBlockReference : IBlockReference +{ /// - /// Represents a data item reference with settings for a Block editor implementation. + /// Gets the settings. /// - /// The type of the settings. - /// - /// See: https://github.com/umbraco/rfcs/blob/907f3758cf59a7b6781296a60d57d537b3b60b8c/cms/0011-block-data-structure.md#strongly-typed - /// - public interface IBlockReference : IBlockReference - { - /// - /// Gets the settings. - /// - /// - /// The settings. - /// - TSettings Settings { get; } - } + /// + /// The settings. + /// + TSettings Settings { get; } } diff --git a/src/Umbraco.Core/Models/CacheInstruction.cs b/src/Umbraco.Core/Models/CacheInstruction.cs index 5434f443a0fd..a93ec030c8f0 100644 --- a/src/Umbraco.Core/Models/CacheInstruction.cs +++ b/src/Umbraco.Core/Models/CacheInstruction.cs @@ -1,51 +1,48 @@ -using System; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a cache instruction. +/// +[Serializable] +[DataContract(IsReference = true)] +public class CacheInstruction { /// - /// Represents a cache instruction. + /// Initializes a new instance of the class. /// - [Serializable] - [DataContract(IsReference = true)] - public class CacheInstruction + public CacheInstruction(int id, DateTime utcStamp, string instructions, string originIdentity, int instructionCount) { - /// - /// Initializes a new instance of the class. - /// - public CacheInstruction(int id, DateTime utcStamp, string instructions, string originIdentity, int instructionCount) - { - Id = id; - UtcStamp = utcStamp; - Instructions = instructions; - OriginIdentity = originIdentity; - InstructionCount = instructionCount; - } - - /// - /// Cache instruction Id. - /// - public int Id { get; } + Id = id; + UtcStamp = utcStamp; + Instructions = instructions; + OriginIdentity = originIdentity; + InstructionCount = instructionCount; + } - /// - /// Cache instruction created date. - /// - public DateTime UtcStamp { get; } + /// + /// Cache instruction Id. + /// + public int Id { get; } - /// - /// Serialized instructions. - /// - public string Instructions { get; } + /// + /// Cache instruction created date. + /// + public DateTime UtcStamp { get; } - /// - /// Identity of server originating the instruction. - /// - public string OriginIdentity { get; } + /// + /// Serialized instructions. + /// + public string Instructions { get; } - /// - /// Count of instructions. - /// - public int InstructionCount { get; } + /// + /// Identity of server originating the instruction. + /// + public string OriginIdentity { get; } - } + /// + /// Count of instructions. + /// + public int InstructionCount { get; } } diff --git a/src/Umbraco.Core/Models/ChangingPasswordModel.cs b/src/Umbraco.Core/Models/ChangingPasswordModel.cs index be19f13b752c..946bcde9abc0 100644 --- a/src/Umbraco.Core/Models/ChangingPasswordModel.cs +++ b/src/Umbraco.Core/Models/ChangingPasswordModel.cs @@ -1,29 +1,28 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// A model representing the data required to set a member/user password depending on the provider installed. +/// +public class ChangingPasswordModel { /// - /// A model representing the data required to set a member/user password depending on the provider installed. + /// The password value /// - public class ChangingPasswordModel - { - /// - /// The password value - /// - [DataMember(Name = "newPassword")] - public string? NewPassword { get; set; } + [DataMember(Name = "newPassword")] + public string? NewPassword { get; set; } - /// - /// The old password - used to change a password when: EnablePasswordRetrieval = false - /// - [DataMember(Name = "oldPassword")] - public string? OldPassword { get; set; } + /// + /// The old password - used to change a password when: EnablePasswordRetrieval = false + /// + [DataMember(Name = "oldPassword")] + public string? OldPassword { get; set; } - /// - /// The ID of the current user/member requesting the password change - /// For users, required to allow changing password without the entire UserSave model - /// - [DataMember(Name = "id")] - public int Id { get; set; } - } + /// + /// The ID of the current user/member requesting the password change + /// For users, required to allow changing password without the entire UserSave model + /// + [DataMember(Name = "id")] + public int Id { get; set; } } diff --git a/src/Umbraco.Core/Models/Consent.cs b/src/Umbraco.Core/Models/Consent.cs index 2354c67b1e38..6a1f3f4c8acc 100644 --- a/src/Umbraco.Core/Models/Consent.cs +++ b/src/Umbraco.Core/Models/Consent.cs @@ -1,86 +1,95 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a consent. +/// +[Serializable] +[DataContract(IsReference = true)] +public class Consent : EntityBase, IConsent { + private string? _action; + private string? _comment; + private string? _context; + private bool _current; + private string? _source; + private ConsentState _state; + /// - /// Represents a consent. + /// Gets the previous states of this consent. /// - [Serializable] - [DataContract(IsReference = true)] - public class Consent : EntityBase, IConsent - { - private bool _current; - private string? _source; - private string? _context; - private string? _action; - private ConsentState _state; - private string? _comment; + public List? HistoryInternal { get; set; } - /// - public bool Current - { - get => _current; - set => SetPropertyValueAndDetectChanges(value, ref _current, nameof(Current)); - } + /// + public bool Current + { + get => _current; + set => SetPropertyValueAndDetectChanges(value, ref _current, nameof(Current)); + } - /// - public string? Source + /// + public string? Source + { + get => _source; + set { - get => _source; - set + if (string.IsNullOrWhiteSpace(value)) { - if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value)); - SetPropertyValueAndDetectChanges(value, ref _source, nameof(Source)); + throw new ArgumentException(nameof(value)); } + + SetPropertyValueAndDetectChanges(value, ref _source, nameof(Source)); } + } - /// - public string? Context + /// + public string? Context + { + get => _context; + set { - get => _context; - set + if (string.IsNullOrWhiteSpace(value)) { - if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value)); - SetPropertyValueAndDetectChanges(value, ref _context, nameof(Context)); + throw new ArgumentException(nameof(value)); } + + SetPropertyValueAndDetectChanges(value, ref _context, nameof(Context)); } + } - /// - public string? Action + /// + public string? Action + { + get => _action; + set { - get => _action; - set + if (string.IsNullOrWhiteSpace(value)) { - if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException(nameof(value)); - SetPropertyValueAndDetectChanges(value, ref _action, nameof(Action)); + throw new ArgumentException(nameof(value)); } - } - - /// - public ConsentState State - { - get => _state; - // note: we probably should validate the state here, but since the - // enum is [Flags] with many combinations, this could be expensive - set => SetPropertyValueAndDetectChanges(value, ref _state, nameof(State)); - } - /// - public string? Comment - { - get => _comment; - set => SetPropertyValueAndDetectChanges(value, ref _comment, nameof(Comment)); + SetPropertyValueAndDetectChanges(value, ref _action, nameof(Action)); } + } - /// - public IEnumerable? History => HistoryInternal; + /// + public ConsentState State + { + get => _state; + // note: we probably should validate the state here, but since the + // enum is [Flags] with many combinations, this could be expensive + set => SetPropertyValueAndDetectChanges(value, ref _state, nameof(State)); + } - /// - /// Gets the previous states of this consent. - /// - public List? HistoryInternal { get; set; } + /// + public string? Comment + { + get => _comment; + set => SetPropertyValueAndDetectChanges(value, ref _comment, nameof(Comment)); } + + /// + public IEnumerable? History => HistoryInternal; } diff --git a/src/Umbraco.Core/Models/ConsentExtensions.cs b/src/Umbraco.Core/Models/ConsentExtensions.cs index b95c7b66f980..1e869eadeefc 100644 --- a/src/Umbraco.Core/Models/ConsentExtensions.cs +++ b/src/Umbraco.Core/Models/ConsentExtensions.cs @@ -1,20 +1,19 @@ using Umbraco.Cms.Core.Models; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Provides extension methods for the interface. +/// +public static class ConsentExtensions { /// - /// Provides extension methods for the interface. + /// Determines whether the consent is granted. /// - public static class ConsentExtensions - { - /// - /// Determines whether the consent is granted. - /// - public static bool IsGranted(this IConsent consent) => (consent.State & ConsentState.Granted) > 0; + public static bool IsGranted(this IConsent consent) => (consent.State & ConsentState.Granted) > 0; - /// - /// Determines whether the consent is revoked. - /// - public static bool IsRevoked(this IConsent consent) => (consent.State & ConsentState.Revoked) > 0; - } + /// + /// Determines whether the consent is revoked. + /// + public static bool IsRevoked(this IConsent consent) => (consent.State & ConsentState.Revoked) > 0; } diff --git a/src/Umbraco.Core/Models/ConsentState.cs b/src/Umbraco.Core/Models/ConsentState.cs index 0828561ff833..368ddb9d8ff5 100644 --- a/src/Umbraco.Core/Models/ConsentState.cs +++ b/src/Umbraco.Core/Models/ConsentState.cs @@ -1,38 +1,35 @@ -using System; +namespace Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Models +/// +/// Represents the state of a consent. +/// +[Flags] +public enum ConsentState // : int { + // note - this is a [Flags] enumeration + // on can create detailed flags such as: + //GrantedOptIn = Granted | 0x0001 + //GrandedByForce = Granted | 0x0002 + // + // 16 situations for each Pending/Granted/Revoked should be ok + /// - /// Represents the state of a consent. + /// There is no consent. /// - [Flags] - public enum ConsentState // : int - { - // note - this is a [Flags] enumeration - // on can create detailed flags such as: - //GrantedOptIn = Granted | 0x0001 - //GrandedByForce = Granted | 0x0002 - // - // 16 situations for each Pending/Granted/Revoked should be ok - - /// - /// There is no consent. - /// - None = 0, + None = 0, - /// - /// Consent is pending and has not been granted yet. - /// - Pending = 0x10000, + /// + /// Consent is pending and has not been granted yet. + /// + Pending = 0x10000, - /// - /// Consent has been granted. - /// - Granted = 0x20000, + /// + /// Consent has been granted. + /// + Granted = 0x20000, - /// - /// Consent has been revoked. - /// - Revoked = 0x40000 - } + /// + /// Consent has been revoked. + /// + Revoked = 0x40000 } diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index bc77e52624b8..abbdcd35345b 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -1,470 +1,563 @@ -using System; -using System.Collections.Generic; using System.Collections.Specialized; -using System.Linq; using System.Runtime.Serialization; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a Content object +/// +[Serializable] +[DataContract(IsReference = true)] +public class Content : ContentBase, IContent { + private HashSet? _editedCultures; + private bool _published; + private PublishedState _publishedState; + private ContentCultureInfosCollection? _publishInfos; + private int? _templateId; + /// - /// Represents a Content object + /// Constructor for creating a Content object /// - [Serializable] - [DataContract(IsReference = true)] - public class Content : ContentBase, IContent + /// Name of the content + /// Parent object + /// ContentType for the current Content object + /// An optional culture. + public Content(string name, IContent parent, IContentType contentType, string? culture = null) + : this(name, parent, contentType, new PropertyCollection(), culture) { - private int? _templateId; - private bool _published; - private PublishedState _publishedState; - private HashSet? _editedCultures; - private ContentCultureInfosCollection? _publishInfos; - - #region Used for change tracking - - private (HashSet? addedCultures, HashSet? removedCultures, HashSet? updatedCultures) _currentPublishCultureChanges; - private (HashSet? addedCultures, HashSet? removedCultures, HashSet? updatedCultures) _previousPublishCultureChanges; - - #endregion - - /// - /// Constructor for creating a Content object - /// - /// Name of the content - /// Parent object - /// ContentType for the current Content object - /// An optional culture. - public Content(string name, IContent parent, IContentType contentType, string? culture = null) - : this(name, parent, contentType, new PropertyCollection(), culture) - { } - - /// - /// Constructor for creating a Content object - /// - /// Name of the content - /// Parent object - /// ContentType for the current Content object - /// The identifier of the user creating the Content object - /// An optional culture. - public Content(string name, IContent parent, IContentType contentType, int userId, string? culture = null) - : this(name, parent, contentType, new PropertyCollection(), culture) - { - CreatorId = userId; - WriterId = userId; - } + } - /// - /// Constructor for creating a Content object - /// - /// Name of the content - /// Parent object - /// ContentType for the current Content object - /// Collection of properties - /// An optional culture. - public Content(string name, IContent parent, IContentType contentType, PropertyCollection properties, string? culture = null) - : base(name, parent, contentType, properties, culture) - { - if (contentType == null) throw new ArgumentNullException(nameof(contentType)); - _publishedState = PublishedState.Unpublished; - PublishedVersionId = 0; - } + /// + /// Constructor for creating a Content object + /// + /// Name of the content + /// Parent object + /// ContentType for the current Content object + /// The identifier of the user creating the Content object + /// An optional culture. + public Content(string name, IContent parent, IContentType contentType, int userId, string? culture = null) + : this(name, parent, contentType, new PropertyCollection(), culture) + { + CreatorId = userId; + WriterId = userId; + } - /// - /// Constructor for creating a Content object - /// - /// Name of the content - /// Id of the Parent content - /// ContentType for the current Content object - /// An optional culture. - public Content(string? name, int parentId, IContentType? contentType, string? culture = null) - : this(name, parentId, contentType, new PropertyCollection(), culture) - { } - - /// - /// Constructor for creating a Content object - /// - /// Name of the content - /// Id of the Parent content - /// ContentType for the current Content object - /// The identifier of the user creating the Content object - /// An optional culture. - public Content(string name, int parentId, IContentType contentType, int userId, string? culture = null) - : this(name, parentId, contentType, new PropertyCollection(), culture) + /// + /// Constructor for creating a Content object + /// + /// Name of the content + /// Parent object + /// ContentType for the current Content object + /// Collection of properties + /// An optional culture. + public Content(string name, IContent parent, IContentType contentType, PropertyCollection properties, + string? culture = null) + : base(name, parent, contentType, properties, culture) + { + if (contentType == null) { - CreatorId = userId; - WriterId = userId; + throw new ArgumentNullException(nameof(contentType)); } - /// - /// Constructor for creating a Content object - /// - /// Name of the content - /// Id of the Parent content - /// ContentType for the current Content object - /// Collection of properties - /// An optional culture. - public Content(string? name, int parentId, IContentType? contentType, PropertyCollection properties, string? culture = null) - : base(name, parentId, contentType, properties, culture) - { - if (contentType == null) throw new ArgumentNullException(nameof(contentType)); - _publishedState = PublishedState.Unpublished; - PublishedVersionId = 0; - } + _publishedState = PublishedState.Unpublished; + PublishedVersionId = 0; + } - /// - /// Gets or sets the template used by the Content. - /// This is used to override the default one from the ContentType. - /// - /// - /// If no template is explicitly set on the Content object, - /// the Default template from the ContentType will be returned. - /// - [DataMember] - public int? TemplateId + /// + /// Constructor for creating a Content object + /// + /// Name of the content + /// Id of the Parent content + /// ContentType for the current Content object + /// An optional culture. + public Content(string? name, int parentId, IContentType? contentType, string? culture = null) + : this(name, parentId, contentType, new PropertyCollection(), culture) + { + } + + /// + /// Constructor for creating a Content object + /// + /// Name of the content + /// Id of the Parent content + /// ContentType for the current Content object + /// The identifier of the user creating the Content object + /// An optional culture. + public Content(string name, int parentId, IContentType contentType, int userId, string? culture = null) + : this(name, parentId, contentType, new PropertyCollection(), culture) + { + CreatorId = userId; + WriterId = userId; + } + + /// + /// Constructor for creating a Content object + /// + /// Name of the content + /// Id of the Parent content + /// ContentType for the current Content object + /// Collection of properties + /// An optional culture. + public Content(string? name, int parentId, IContentType? contentType, PropertyCollection properties, + string? culture = null) + : base(name, parentId, contentType, properties, culture) + { + if (contentType == null) { - get => _templateId; - set => SetPropertyValueAndDetectChanges(value, ref _templateId, nameof(TemplateId)); + throw new ArgumentNullException(nameof(contentType)); } - /// - /// Gets or sets a value indicating whether this content item is published or not. - /// - /// - /// the setter is should only be invoked from - /// - the ContentFactory when creating a content entity from a dto - /// - the ContentRepository when updating a content entity - /// - [DataMember] - public bool Published + _publishedState = PublishedState.Unpublished; + PublishedVersionId = 0; + } + + /// + /// Gets or sets the template used by the Content. + /// This is used to override the default one from the ContentType. + /// + /// + /// If no template is explicitly set on the Content object, + /// the Default template from the ContentType will be returned. + /// + [DataMember] + public int? TemplateId + { + get => _templateId; + set => SetPropertyValueAndDetectChanges(value, ref _templateId, nameof(TemplateId)); + } + + /// + /// Gets or sets a value indicating whether this content item is published or not. + /// + /// + /// the setter is should only be invoked from + /// - the ContentFactory when creating a content entity from a dto + /// - the ContentRepository when updating a content entity + /// + [DataMember] + public bool Published + { + get => _published; + set { - get => _published; - set - { - SetPropertyValueAndDetectChanges(value, ref _published, nameof(Published)); - _publishedState = _published ? PublishedState.Published : PublishedState.Unpublished; - } + SetPropertyValueAndDetectChanges(value, ref _published, nameof(Published)); + _publishedState = _published ? PublishedState.Published : PublishedState.Unpublished; } + } - /// - /// Gets the published state of the content item. - /// - /// The state should be Published or Unpublished, depending on whether Published - /// is true or false, but can also temporarily be Publishing or Unpublishing when the - /// content item is about to be saved. - [DataMember] - public PublishedState PublishedState + /// + /// Gets the published state of the content item. + /// + /// + /// The state should be Published or Unpublished, depending on whether Published + /// is true or false, but can also temporarily be Publishing or Unpublishing when the + /// content item is about to be saved. + /// + [DataMember] + public PublishedState PublishedState + { + get => _publishedState; + set { - get => _publishedState; - set + if (value != PublishedState.Publishing && value != PublishedState.Unpublishing) { - if (value != PublishedState.Publishing && value != PublishedState.Unpublishing) - throw new ArgumentException("Invalid state, only Publishing and Unpublishing are accepted."); - _publishedState = value; + throw new ArgumentException("Invalid state, only Publishing and Unpublishing are accepted."); } + + _publishedState = value; } + } - [IgnoreDataMember] - public bool Edited { get; set; } + [IgnoreDataMember] public bool Edited { get; set; } - /// - [IgnoreDataMember] - public DateTime? PublishDate { get; set; } // set by persistence + /// + [IgnoreDataMember] + public DateTime? PublishDate { get; set; } // set by persistence - /// - [IgnoreDataMember] - public int? PublisherId { get; set; } // set by persistence + /// + [IgnoreDataMember] + public int? PublisherId { get; set; } // set by persistence - /// - [IgnoreDataMember] - public int? PublishTemplateId { get; set; } // set by persistence + /// + [IgnoreDataMember] + public int? PublishTemplateId { get; set; } // set by persistence - /// - [IgnoreDataMember] - public string? PublishName { get; set; } // set by persistence + /// + [IgnoreDataMember] + public string? PublishName { get; set; } // set by persistence - /// - [IgnoreDataMember] - public IEnumerable? EditedCultures - { - get => CultureInfos?.Keys.Where(IsCultureEdited); - set => _editedCultures = value == null ? null : new HashSet(value, StringComparer.OrdinalIgnoreCase); - } + /// + [IgnoreDataMember] + public IEnumerable? EditedCultures + { + get => CultureInfos?.Keys.Where(IsCultureEdited); + set => _editedCultures = value == null ? null : new HashSet(value, StringComparer.OrdinalIgnoreCase); + } - /// - [IgnoreDataMember] - public IEnumerable PublishedCultures => _publishInfos?.Keys ?? Enumerable.Empty(); - - /// - public bool IsCulturePublished(string culture) - // just check _publishInfos - // a non-available culture could not become published anyways - => !culture.IsNullOrWhiteSpace() && _publishInfos != null && _publishInfos.ContainsKey(culture); - - /// - public bool IsCultureEdited(string culture) - => IsCultureAvailable(culture) && // is available, and - (!IsCulturePublished(culture) || // is not published, or - (_editedCultures != null && _editedCultures.Contains(culture))); // is edited - - /// - [IgnoreDataMember] - public ContentCultureInfosCollection? PublishCultureInfos + /// + [IgnoreDataMember] + public IEnumerable PublishedCultures => _publishInfos?.Keys ?? Enumerable.Empty(); + + /// + public bool IsCulturePublished(string culture) + // just check _publishInfos + // a non-available culture could not become published anyways + => !culture.IsNullOrWhiteSpace() && _publishInfos != null && _publishInfos.ContainsKey(culture); + + /// + public bool IsCultureEdited(string culture) + => IsCultureAvailable(culture) && // is available, and + (!IsCulturePublished(culture) || // is not published, or + (_editedCultures != null && _editedCultures.Contains(culture))); // is edited + + /// + [IgnoreDataMember] + public ContentCultureInfosCollection? PublishCultureInfos + { + get { - get + if (_publishInfos != null) { - if (_publishInfos != null) return _publishInfos; - _publishInfos = new ContentCultureInfosCollection(); - _publishInfos.CollectionChanged += PublishNamesCollectionChanged; return _publishInfos; } - set + + _publishInfos = new ContentCultureInfosCollection(); + _publishInfos.CollectionChanged += PublishNamesCollectionChanged; + return _publishInfos; + } + set + { + if (_publishInfos != null) { - if (_publishInfos != null) - { - _publishInfos.ClearCollectionChangedEvents(); - } + _publishInfos.ClearCollectionChangedEvents(); + } - _publishInfos = value; - if (_publishInfos != null) - { - _publishInfos.CollectionChanged += PublishNamesCollectionChanged; - } + _publishInfos = value; + if (_publishInfos != null) + { + _publishInfos.CollectionChanged += PublishNamesCollectionChanged; } } + } - /// - public string? GetPublishName(string? culture) + /// + public string? GetPublishName(string? culture) + { + if (culture.IsNullOrWhiteSpace()) { - if (culture.IsNullOrWhiteSpace()) return PublishName; - if (!ContentType.VariesByCulture()) return null; - if (_publishInfos == null) return null; - return _publishInfos.TryGetValue(culture!, out var infos) ? infos.Name : null; + return PublishName; } - /// - public DateTime? GetPublishDate(string culture) + if (!ContentType.VariesByCulture()) { - if (culture.IsNullOrWhiteSpace()) return PublishDate; - if (!ContentType.VariesByCulture()) return null; - if (_publishInfos == null) return null; - return _publishInfos.TryGetValue(culture, out var infos) ? infos.Date : (DateTime?)null; + return null; } - /// - /// Handles culture infos collection changes. - /// - private void PublishNamesCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + if (_publishInfos == null) { - OnPropertyChanged(nameof(PublishCultureInfos)); - - //we don't need to handle other actions, only add/remove, however we could implement Replace and track updated cultures in _updatedCultures too - //which would allows us to continue doing WasCulturePublished, but don't think we need it anymore - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - { - var cultureInfo = e.NewItems?.Cast().First(); - if (_currentPublishCultureChanges.addedCultures == null) _currentPublishCultureChanges.addedCultures = new HashSet(StringComparer.InvariantCultureIgnoreCase); - if (_currentPublishCultureChanges.updatedCultures == null) _currentPublishCultureChanges.updatedCultures = new HashSet(StringComparer.InvariantCultureIgnoreCase); - if (cultureInfo is not null) - { - _currentPublishCultureChanges.addedCultures.Add(cultureInfo.Culture); - _currentPublishCultureChanges.updatedCultures.Add(cultureInfo.Culture); - _currentPublishCultureChanges.removedCultures?.Remove(cultureInfo.Culture); - } - break; - } - case NotifyCollectionChangedAction.Remove: - { - //remove listening for changes - var cultureInfo = e.OldItems?.Cast().First(); - if (_currentPublishCultureChanges.removedCultures == null) _currentPublishCultureChanges.removedCultures = new HashSet(StringComparer.InvariantCultureIgnoreCase); - if (cultureInfo is not null) - { - _currentPublishCultureChanges.removedCultures.Add(cultureInfo.Culture); - _currentPublishCultureChanges.updatedCultures?.Remove(cultureInfo.Culture); - _currentPublishCultureChanges.addedCultures?.Remove(cultureInfo.Culture); - } - break; - } - case NotifyCollectionChangedAction.Replace: - { - //replace occurs when an Update occurs - var cultureInfo = e.NewItems?.Cast().First(); - if (_currentPublishCultureChanges.updatedCultures == null) _currentPublishCultureChanges.updatedCultures = new HashSet(StringComparer.InvariantCultureIgnoreCase); - if (cultureInfo is not null) - { - _currentPublishCultureChanges.updatedCultures.Add(cultureInfo.Culture); - } - break; - } - } + return null; } - [IgnoreDataMember] - public int PublishedVersionId { get; set; } + return _publishInfos.TryGetValue(culture!, out ContentCultureInfos infos) ? infos.Name : null; + } - [DataMember] - public bool Blueprint { get; set; } + /// + public DateTime? GetPublishDate(string culture) + { + if (culture.IsNullOrWhiteSpace()) + { + return PublishDate; + } - /// - /// Changes the for the current content object - /// - /// New ContentType for this content - /// Leaves PropertyTypes intact after change - internal void ChangeContentType(IContentType contentType) + if (!ContentType.VariesByCulture()) { - ChangeContentType(contentType, false); + return null; } - /// - /// Changes the for the current content object and removes PropertyTypes, - /// which are not part of the new ContentType. - /// - /// New ContentType for this content - /// Boolean indicating whether to clear PropertyTypes upon change - internal void ChangeContentType(IContentType contentType, bool clearProperties) + if (_publishInfos == null) { - ChangeContentType(new SimpleContentType(contentType)); + return null; + } - if (clearProperties) - Properties.EnsureCleanPropertyTypes(contentType.CompositionPropertyTypes); - else - Properties.EnsurePropertyTypes(contentType.CompositionPropertyTypes); + return _publishInfos.TryGetValue(culture, out ContentCultureInfos infos) ? infos.Date : null; + } - Properties.ClearCollectionChangedEvents(); // be sure not to double add - Properties.CollectionChanged += PropertiesChanged; - } + [IgnoreDataMember] public int PublishedVersionId { get; set; } + + [DataMember] public bool Blueprint { get; set; } + + public override void ResetWereDirtyProperties() + { + base.ResetWereDirtyProperties(); + _previousPublishCultureChanges.updatedCultures = null; + _previousPublishCultureChanges.removedCultures = null; + _previousPublishCultureChanges.addedCultures = null; + } + + public override void ResetDirtyProperties(bool rememberDirty) + { + base.ResetDirtyProperties(rememberDirty); - public override void ResetWereDirtyProperties() + if (rememberDirty) + { + _previousPublishCultureChanges.addedCultures = + _currentPublishCultureChanges.addedCultures == null || + _currentPublishCultureChanges.addedCultures.Count == 0 + ? null + : new HashSet(_currentPublishCultureChanges.addedCultures, + StringComparer.InvariantCultureIgnoreCase); + _previousPublishCultureChanges.removedCultures = + _currentPublishCultureChanges.removedCultures == null || + _currentPublishCultureChanges.removedCultures.Count == 0 + ? null + : new HashSet(_currentPublishCultureChanges.removedCultures, + StringComparer.InvariantCultureIgnoreCase); + _previousPublishCultureChanges.updatedCultures = + _currentPublishCultureChanges.updatedCultures == null || + _currentPublishCultureChanges.updatedCultures.Count == 0 + ? null + : new HashSet(_currentPublishCultureChanges.updatedCultures, + StringComparer.InvariantCultureIgnoreCase); + } + else { - base.ResetWereDirtyProperties(); - _previousPublishCultureChanges.updatedCultures = null; - _previousPublishCultureChanges.removedCultures = null; _previousPublishCultureChanges.addedCultures = null; + _previousPublishCultureChanges.removedCultures = null; + _previousPublishCultureChanges.updatedCultures = null; + } + + _currentPublishCultureChanges.addedCultures?.Clear(); + _currentPublishCultureChanges.removedCultures?.Clear(); + _currentPublishCultureChanges.updatedCultures?.Clear(); + + // take care of the published state + _publishedState = _published ? PublishedState.Published : PublishedState.Unpublished; + + if (_publishInfos == null) + { + return; } - public override void ResetDirtyProperties(bool rememberDirty) + foreach (ContentCultureInfos infos in _publishInfos) { - base.ResetDirtyProperties(rememberDirty); + infos.ResetDirtyProperties(rememberDirty); + } + } - if (rememberDirty) - { - _previousPublishCultureChanges.addedCultures = _currentPublishCultureChanges.addedCultures == null || _currentPublishCultureChanges.addedCultures.Count == 0 ? null : new HashSet(_currentPublishCultureChanges.addedCultures, StringComparer.InvariantCultureIgnoreCase); - _previousPublishCultureChanges.removedCultures = _currentPublishCultureChanges.removedCultures == null || _currentPublishCultureChanges.removedCultures.Count == 0 ? null : new HashSet(_currentPublishCultureChanges.removedCultures, StringComparer.InvariantCultureIgnoreCase); - _previousPublishCultureChanges.updatedCultures = _currentPublishCultureChanges.updatedCultures == null || _currentPublishCultureChanges.updatedCultures.Count == 0 ? null : new HashSet(_currentPublishCultureChanges.updatedCultures, StringComparer.InvariantCultureIgnoreCase); - } - else - { - _previousPublishCultureChanges.addedCultures = null; - _previousPublishCultureChanges.removedCultures = null; - _previousPublishCultureChanges.updatedCultures = null; - } - _currentPublishCultureChanges.addedCultures?.Clear(); - _currentPublishCultureChanges.removedCultures?.Clear(); - _currentPublishCultureChanges.updatedCultures?.Clear(); + /// + /// Overridden to check special keys. + public override bool IsPropertyDirty(string propertyName) + { + //Special check here since we want to check if the request is for changed cultures + if (propertyName.StartsWith(ChangeTrackingPrefix.PublishedCulture)) + { + var culture = propertyName.TrimStart(ChangeTrackingPrefix.PublishedCulture); + return _currentPublishCultureChanges.addedCultures?.Contains(culture) ?? false; + } - // take care of the published state - _publishedState = _published ? PublishedState.Published : PublishedState.Unpublished; + if (propertyName.StartsWith(ChangeTrackingPrefix.UnpublishedCulture)) + { + var culture = propertyName.TrimStart(ChangeTrackingPrefix.UnpublishedCulture); + return _currentPublishCultureChanges.removedCultures?.Contains(culture) ?? false; + } - if (_publishInfos == null) return; + if (propertyName.StartsWith(ChangeTrackingPrefix.ChangedCulture)) + { + var culture = propertyName.TrimStart(ChangeTrackingPrefix.ChangedCulture); + return _currentPublishCultureChanges.updatedCultures?.Contains(culture) ?? false; + } - foreach (var infos in _publishInfos) - infos.ResetDirtyProperties(rememberDirty); + return base.IsPropertyDirty(propertyName); + } + + /// + /// Overridden to check special keys. + public override bool WasPropertyDirty(string propertyName) + { + //Special check here since we want to check if the request is for changed cultures + if (propertyName.StartsWith(ChangeTrackingPrefix.PublishedCulture)) + { + var culture = propertyName.TrimStart(ChangeTrackingPrefix.PublishedCulture); + return _previousPublishCultureChanges.addedCultures?.Contains(culture) ?? false; } - /// - /// Overridden to check special keys. - public override bool IsPropertyDirty(string propertyName) + if (propertyName.StartsWith(ChangeTrackingPrefix.UnpublishedCulture)) { - //Special check here since we want to check if the request is for changed cultures - if (propertyName.StartsWith(ChangeTrackingPrefix.PublishedCulture)) - { - var culture = propertyName.TrimStart(ChangeTrackingPrefix.PublishedCulture); - return _currentPublishCultureChanges.addedCultures?.Contains(culture) ?? false; - } - if (propertyName.StartsWith(ChangeTrackingPrefix.UnpublishedCulture)) - { - var culture = propertyName.TrimStart(ChangeTrackingPrefix.UnpublishedCulture); - return _currentPublishCultureChanges.removedCultures?.Contains(culture) ?? false; - } - if (propertyName.StartsWith(ChangeTrackingPrefix.ChangedCulture)) - { - var culture = propertyName.TrimStart(ChangeTrackingPrefix.ChangedCulture); - return _currentPublishCultureChanges.updatedCultures?.Contains(culture) ?? false; - } + var culture = propertyName.TrimStart(ChangeTrackingPrefix.UnpublishedCulture); + return _previousPublishCultureChanges.removedCultures?.Contains(culture) ?? false; + } + + if (propertyName.StartsWith(ChangeTrackingPrefix.ChangedCulture)) + { + var culture = propertyName.TrimStart(ChangeTrackingPrefix.ChangedCulture); + return _previousPublishCultureChanges.updatedCultures?.Contains(culture) ?? false; + } + + return base.WasPropertyDirty(propertyName); + } - return base.IsPropertyDirty(propertyName); + /// + /// Creates a deep clone of the current entity with its identity and it's property identities reset + /// + /// + public IContent DeepCloneWithResetIdentities() + { + var clone = (Content)DeepClone(); + clone.Key = Guid.Empty; + clone.VersionId = clone.PublishedVersionId = 0; + clone.ResetIdentity(); + + foreach (IProperty property in clone.Properties) + { + property.ResetIdentity(); } - /// - /// Overridden to check special keys. - public override bool WasPropertyDirty(string propertyName) + return clone; + } + + /// + /// Handles culture infos collection changes. + /// + private void PublishNamesCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + OnPropertyChanged(nameof(PublishCultureInfos)); + + //we don't need to handle other actions, only add/remove, however we could implement Replace and track updated cultures in _updatedCultures too + //which would allows us to continue doing WasCulturePublished, but don't think we need it anymore + switch (e.Action) { - //Special check here since we want to check if the request is for changed cultures - if (propertyName.StartsWith(ChangeTrackingPrefix.PublishedCulture)) + case NotifyCollectionChangedAction.Add: { - var culture = propertyName.TrimStart(ChangeTrackingPrefix.PublishedCulture); - return _previousPublishCultureChanges.addedCultures?.Contains(culture) ?? false; + ContentCultureInfos cultureInfo = e.NewItems?.Cast().First(); + if (_currentPublishCultureChanges.addedCultures == null) + { + _currentPublishCultureChanges.addedCultures = + new HashSet(StringComparer.InvariantCultureIgnoreCase); + } + + if (_currentPublishCultureChanges.updatedCultures == null) + { + _currentPublishCultureChanges.updatedCultures = + new HashSet(StringComparer.InvariantCultureIgnoreCase); + } + + if (cultureInfo is not null) + { + _currentPublishCultureChanges.addedCultures.Add(cultureInfo.Culture); + _currentPublishCultureChanges.updatedCultures.Add(cultureInfo.Culture); + _currentPublishCultureChanges.removedCultures?.Remove(cultureInfo.Culture); + } + + break; } - if (propertyName.StartsWith(ChangeTrackingPrefix.UnpublishedCulture)) + case NotifyCollectionChangedAction.Remove: { - var culture = propertyName.TrimStart(ChangeTrackingPrefix.UnpublishedCulture); - return _previousPublishCultureChanges.removedCultures?.Contains(culture) ?? false; + //remove listening for changes + ContentCultureInfos cultureInfo = e.OldItems?.Cast().First(); + if (_currentPublishCultureChanges.removedCultures == null) + { + _currentPublishCultureChanges.removedCultures = + new HashSet(StringComparer.InvariantCultureIgnoreCase); + } + + if (cultureInfo is not null) + { + _currentPublishCultureChanges.removedCultures.Add(cultureInfo.Culture); + _currentPublishCultureChanges.updatedCultures?.Remove(cultureInfo.Culture); + _currentPublishCultureChanges.addedCultures?.Remove(cultureInfo.Culture); + } + + break; } - if (propertyName.StartsWith(ChangeTrackingPrefix.ChangedCulture)) + case NotifyCollectionChangedAction.Replace: { - var culture = propertyName.TrimStart(ChangeTrackingPrefix.ChangedCulture); - return _previousPublishCultureChanges.updatedCultures?.Contains(culture) ?? false; - } + //replace occurs when an Update occurs + ContentCultureInfos cultureInfo = e.NewItems?.Cast().First(); + if (_currentPublishCultureChanges.updatedCultures == null) + { + _currentPublishCultureChanges.updatedCultures = + new HashSet(StringComparer.InvariantCultureIgnoreCase); + } - return base.WasPropertyDirty(propertyName); + if (cultureInfo is not null) + { + _currentPublishCultureChanges.updatedCultures.Add(cultureInfo.Culture); + } + + break; + } } + } - /// - /// Creates a deep clone of the current entity with its identity and it's property identities reset - /// - /// - public IContent DeepCloneWithResetIdentities() - { - var clone = (Content)DeepClone(); - clone.Key = Guid.Empty; - clone.VersionId = clone.PublishedVersionId = 0; - clone.ResetIdentity(); + /// + /// Changes the for the current content object + /// + /// New ContentType for this content + /// Leaves PropertyTypes intact after change + internal void ChangeContentType(IContentType contentType) => ChangeContentType(contentType, false); - foreach (var property in clone.Properties) - property.ResetIdentity(); + /// + /// Changes the for the current content object and removes PropertyTypes, + /// which are not part of the new ContentType. + /// + /// New ContentType for this content + /// Boolean indicating whether to clear PropertyTypes upon change + internal void ChangeContentType(IContentType contentType, bool clearProperties) + { + ChangeContentType(new SimpleContentType(contentType)); - return clone; + if (clearProperties) + { + Properties.EnsureCleanPropertyTypes(contentType.CompositionPropertyTypes); } - - protected override void PerformDeepClone(object clone) + else { - base.PerformDeepClone(clone); + Properties.EnsurePropertyTypes(contentType.CompositionPropertyTypes); + } + + Properties.ClearCollectionChangedEvents(); // be sure not to double add + Properties.CollectionChanged += PropertiesChanged; + } - var clonedContent = (Content)clone; + protected override void PerformDeepClone(object clone) + { + base.PerformDeepClone(clone); + + var clonedContent = (Content)clone; - //fixme - need to reset change tracking bits + //fixme - need to reset change tracking bits - //if culture infos exist then deal with event bindings - if (clonedContent._publishInfos != null) + //if culture infos exist then deal with event bindings + if (clonedContent._publishInfos != null) + { + clonedContent._publishInfos.ClearCollectionChangedEvents(); //clear this event handler if any + clonedContent._publishInfos = + (ContentCultureInfosCollection?)_publishInfos?.DeepClone(); //manually deep clone + if (clonedContent._publishInfos is not null) { - clonedContent._publishInfos.ClearCollectionChangedEvents(); //clear this event handler if any - clonedContent._publishInfos = (ContentCultureInfosCollection?)_publishInfos?.DeepClone(); //manually deep clone - if (clonedContent._publishInfos is not null) - { - clonedContent._publishInfos.CollectionChanged += - clonedContent.PublishNamesCollectionChanged; //re-assign correct event handler - } + clonedContent._publishInfos.CollectionChanged += + clonedContent.PublishNamesCollectionChanged; //re-assign correct event handler } + } - clonedContent._currentPublishCultureChanges.updatedCultures = null; - clonedContent._currentPublishCultureChanges.addedCultures = null; - clonedContent._currentPublishCultureChanges.removedCultures = null; + clonedContent._currentPublishCultureChanges.updatedCultures = null; + clonedContent._currentPublishCultureChanges.addedCultures = null; + clonedContent._currentPublishCultureChanges.removedCultures = null; - clonedContent._previousPublishCultureChanges.updatedCultures = null; - clonedContent._previousPublishCultureChanges.addedCultures = null; - clonedContent._previousPublishCultureChanges.removedCultures = null; - } + clonedContent._previousPublishCultureChanges.updatedCultures = null; + clonedContent._previousPublishCultureChanges.addedCultures = null; + clonedContent._previousPublishCultureChanges.removedCultures = null; } + + #region Used for change tracking + + private (HashSet? addedCultures, HashSet? removedCultures, HashSet? updatedCultures) + _currentPublishCultureChanges; + + private (HashSet? addedCultures, HashSet? removedCultures, HashSet? updatedCultures) + _previousPublishCultureChanges; + + #endregion } diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index d9223130d631..cc0a2c43a1da 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -1,530 +1,628 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; +using System.Collections.Specialized; using System.Diagnostics; -using System.Linq; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents an abstract class for base Content properties and methods +/// +[Serializable] +[DataContract(IsReference = true)] +[DebuggerDisplay("Id: {Id}, Name: {Name}, ContentType: {ContentType.Alias}")] +public abstract class ContentBase : TreeEntityBase, IContentBase { + private int _contentTypeId; + private ContentCultureInfosCollection? _cultureInfos; + private IPropertyCollection _properties; + private int _writerId; + /// - /// Represents an abstract class for base Content properties and methods + /// Initializes a new instance of the class. /// - [Serializable] - [DataContract(IsReference = true)] - [DebuggerDisplay("Id: {Id}, Name: {Name}, ContentType: {ContentType.Alias}")] - public abstract class ContentBase : TreeEntityBase, IContentBase + protected ContentBase(string? name, int parentId, IContentTypeComposition? contentType, + IPropertyCollection properties, string? culture = null) + : this(name, contentType, properties, culture) { - private int _contentTypeId; - private int _writerId; - private IPropertyCollection _properties; - private ContentCultureInfosCollection? _cultureInfos; - internal IReadOnlyList AllPropertyTypes { get; } - - #region Used for change tracking - - private (HashSet? addedCultures, HashSet? removedCultures, HashSet? updatedCultures) _currentCultureChanges; - private (HashSet? addedCultures, HashSet? removedCultures, HashSet? updatedCultures) _previousCultureChanges; - - public static class ChangeTrackingPrefix + if (parentId == 0) { - public const string UpdatedCulture = "_updatedCulture_"; - public const string ChangedCulture = "_changedCulture_"; - public const string PublishedCulture = "_publishedCulture_"; - public const string UnpublishedCulture = "_unpublishedCulture_"; - public const string AddedCulture = "_addedCulture_"; - public const string RemovedCulture = "_removedCulture_"; + throw new ArgumentOutOfRangeException(nameof(parentId)); } - #endregion + ParentId = parentId; + } - /// - /// Initializes a new instance of the class. - /// - protected ContentBase(string? name, int parentId, IContentTypeComposition? contentType, IPropertyCollection properties, string? culture = null) - : this(name, contentType, properties, culture) + /// + /// Initializes a new instance of the class. + /// + protected ContentBase(string? name, IContentBase? parent, IContentTypeComposition contentType, + IPropertyCollection properties, string? culture = null) + : this(name, contentType, properties, culture) + { + if (parent == null) { - if (parentId == 0) throw new ArgumentOutOfRangeException(nameof(parentId)); - ParentId = parentId; + throw new ArgumentNullException(nameof(parent)); } - /// - /// Initializes a new instance of the class. - /// - protected ContentBase(string? name, IContentBase? parent, IContentTypeComposition contentType, IPropertyCollection properties, string? culture = null) - : this(name, contentType, properties, culture) + SetParent(parent); + } + + private ContentBase(string? name, IContentTypeComposition? contentType, IPropertyCollection properties, + string? culture = null) + { + ContentType = contentType?.ToSimple() ?? throw new ArgumentNullException(nameof(contentType)); + + // initially, all new instances have + Id = 0; // no identity + VersionId = 0; // no versions + + SetCultureName(name, culture); + + _contentTypeId = contentType.Id; + _properties = properties ?? throw new ArgumentNullException(nameof(properties)); + _properties.EnsurePropertyTypes(contentType.CompositionPropertyTypes); + + //track all property types on this content type, these can never change during the lifetime of this single instance + //there is no real extra memory overhead of doing this since these property types are already cached on this object via the + //properties already. + AllPropertyTypes = new List(contentType.CompositionPropertyTypes); + } + + internal IReadOnlyList AllPropertyTypes { get; } + + [IgnoreDataMember] public ISimpleContentType ContentType { get; private set; } + + /// + /// Id of the user who wrote/updated this entity + /// + [DataMember] + public int WriterId + { + get => _writerId; + set => SetPropertyValueAndDetectChanges(value, ref _writerId, nameof(WriterId)); + } + + [IgnoreDataMember] public int VersionId { get; set; } + + /// + /// Integer Id of the default ContentType + /// + [DataMember] + public int ContentTypeId + { + get { - if (parent == null) throw new ArgumentNullException(nameof(parent)); - SetParent(parent); + //There will be cases where this has not been updated to reflect the true content type ID. + //This will occur when inserting new content. + if (_contentTypeId == 0 && ContentType != null) + { + _contentTypeId = ContentType.Id; + } + + return _contentTypeId; } + private set => SetPropertyValueAndDetectChanges(value, ref _contentTypeId, nameof(ContentTypeId)); + } - private ContentBase(string? name, IContentTypeComposition? contentType, IPropertyCollection properties, string? culture = null) + /// + /// Gets or sets the collection of properties for the entity. + /// + /// + /// Marked DoNotClone since we'll manually clone the underlying field to deal with the event handling + /// + [DataMember] + [DoNotClone] + public IPropertyCollection Properties + { + get => _properties; + set { - ContentType = contentType?.ToSimple() ?? throw new ArgumentNullException(nameof(contentType)); + if (_properties != null) + { + _properties.ClearCollectionChangedEvents(); + } + + _properties = value; + _properties.CollectionChanged += PropertiesChanged; + } + } - // initially, all new instances have - Id = 0; // no identity - VersionId = 0; // no versions + public void ChangeContentType(ISimpleContentType contentType) + { + ContentType = contentType; + ContentTypeId = contentType.Id; + } - SetCultureName(name, culture); + protected void PropertiesChanged(object? sender, NotifyCollectionChangedEventArgs e) => + OnPropertyChanged(nameof(Properties)); - _contentTypeId = contentType.Id; - _properties = properties ?? throw new ArgumentNullException(nameof(properties)); - _properties.EnsurePropertyTypes(contentType.CompositionPropertyTypes); + /// + /// + /// Overridden to deal with specific object instances + /// + protected override void PerformDeepClone(object clone) + { + base.PerformDeepClone(clone); - //track all property types on this content type, these can never change during the lifetime of this single instance - //there is no real extra memory overhead of doing this since these property types are already cached on this object via the - //properties already. - AllPropertyTypes = new List(contentType.CompositionPropertyTypes); - } + var clonedContent = (ContentBase)clone; - [IgnoreDataMember] - public ISimpleContentType ContentType { get; private set; } + //need to manually clone this since it's not settable + clonedContent.ContentType = ContentType; - public void ChangeContentType(ISimpleContentType contentType) + //if culture infos exist then deal with event bindings + if (clonedContent._cultureInfos != null) { - ContentType = contentType; - ContentTypeId = contentType.Id; + clonedContent._cultureInfos.ClearCollectionChangedEvents(); //clear this event handler if any + clonedContent._cultureInfos = + (ContentCultureInfosCollection?)_cultureInfos?.DeepClone(); //manually deep clone + if (clonedContent._cultureInfos is not null) + { + clonedContent._cultureInfos.CollectionChanged += + clonedContent.CultureInfosCollectionChanged; //re-assign correct event handler + } } - protected void PropertiesChanged(object? sender, NotifyCollectionChangedEventArgs e) + //if properties exist then deal with event bindings + if (clonedContent._properties != null) { - OnPropertyChanged(nameof(Properties)); + clonedContent._properties.ClearCollectionChangedEvents(); //clear this event handler if any + clonedContent._properties = (IPropertyCollection)_properties.DeepClone(); //manually deep clone + clonedContent._properties.CollectionChanged += + clonedContent.PropertiesChanged; //re-assign correct event handler } - /// - /// Id of the user who wrote/updated this entity - /// - [DataMember] - public int WriterId - { - get => _writerId; - set => SetPropertyValueAndDetectChanges(value, ref _writerId, nameof(WriterId)); - } + clonedContent._currentCultureChanges.updatedCultures = null; + clonedContent._currentCultureChanges.addedCultures = null; + clonedContent._currentCultureChanges.removedCultures = null; + + clonedContent._previousCultureChanges.updatedCultures = null; + clonedContent._previousCultureChanges.addedCultures = null; + clonedContent._previousCultureChanges.removedCultures = null; + } + + #region Used for change tracking + + private (HashSet? addedCultures, HashSet? removedCultures, HashSet? updatedCultures) + _currentCultureChanges; - [IgnoreDataMember] - public int VersionId { get; set; } + private (HashSet? addedCultures, HashSet? removedCultures, HashSet? updatedCultures) + _previousCultureChanges; - /// - /// Integer Id of the default ContentType - /// - [DataMember] - public int ContentTypeId + public static class ChangeTrackingPrefix + { + public const string UpdatedCulture = "_updatedCulture_"; + public const string ChangedCulture = "_changedCulture_"; + public const string PublishedCulture = "_publishedCulture_"; + public const string UnpublishedCulture = "_unpublishedCulture_"; + public const string AddedCulture = "_addedCulture_"; + public const string RemovedCulture = "_removedCulture_"; + } + + #endregion + + #region Cultures + + // notes - common rules + // - setting a variant value on an invariant content type throws + // - getting a variant value on an invariant content type returns null + // - setting and getting the invariant value is always possible + // - setting a null value clears the value + + /// + public IEnumerable AvailableCultures + => _cultureInfos?.Keys ?? Enumerable.Empty(); + + /// + public bool IsCultureAvailable(string culture) + => _cultureInfos != null && _cultureInfos.ContainsKey(culture); + + /// + [DataMember] + public ContentCultureInfosCollection? CultureInfos + { + get { - get + if (_cultureInfos != null) { - //There will be cases where this has not been updated to reflect the true content type ID. - //This will occur when inserting new content. - if (_contentTypeId == 0 && ContentType != null) - { - _contentTypeId = ContentType.Id; - } - return _contentTypeId; + return _cultureInfos; } - private set => SetPropertyValueAndDetectChanges(value, ref _contentTypeId, nameof(ContentTypeId)); - } - /// - /// Gets or sets the collection of properties for the entity. - /// - /// - /// Marked DoNotClone since we'll manually clone the underlying field to deal with the event handling - /// - [DataMember] - [DoNotClone] - public IPropertyCollection Properties + _cultureInfos = new ContentCultureInfosCollection(); + _cultureInfos.CollectionChanged += CultureInfosCollectionChanged; + return _cultureInfos; + } + set { - get => _properties; - set + if (_cultureInfos != null) { - if (_properties != null) - { - _properties.ClearCollectionChangedEvents(); - } + _cultureInfos.ClearCollectionChangedEvents(); + } - _properties = value; - _properties.CollectionChanged += PropertiesChanged; + _cultureInfos = value; + if (_cultureInfos != null) + { + _cultureInfos.CollectionChanged += CultureInfosCollectionChanged; } } + } - #region Cultures + /// + public string? GetCultureName(string? culture) + { + if (culture.IsNullOrWhiteSpace()) + { + return Name; + } - // notes - common rules - // - setting a variant value on an invariant content type throws - // - getting a variant value on an invariant content type returns null - // - setting and getting the invariant value is always possible - // - setting a null value clears the value + if (!ContentType.VariesByCulture()) + { + return null; + } - /// - public IEnumerable AvailableCultures - => _cultureInfos?.Keys ?? Enumerable.Empty(); + if (_cultureInfos == null) + { + return null; + } - /// - public bool IsCultureAvailable(string culture) - => _cultureInfos != null && _cultureInfos.ContainsKey(culture); + return _cultureInfos.TryGetValue(culture!, out ContentCultureInfos infos) ? infos.Name : null; + } + + /// + public DateTime? GetUpdateDate(string culture) + { + if (culture.IsNullOrWhiteSpace()) + { + return null; + } + + if (!ContentType.VariesByCulture()) + { + return null; + } + + if (_cultureInfos == null) + { + return null; + } + + return _cultureInfos.TryGetValue(culture, out ContentCultureInfos infos) ? infos.Date : null; + } - /// - [DataMember] - public ContentCultureInfosCollection? CultureInfos + /// + public void SetCultureName(string? name, string? culture) + { + if (ContentType.VariesByCulture()) // set on variant content type { - get + if (culture.IsNullOrWhiteSpace()) // invariant is ok { - if (_cultureInfos != null) return _cultureInfos; - _cultureInfos = new ContentCultureInfosCollection(); - _cultureInfos.CollectionChanged += CultureInfosCollectionChanged; - return _cultureInfos; + Name = name; // may be null } - set + else if (name.IsNullOrWhiteSpace()) // clear { - if (_cultureInfos != null) - { - _cultureInfos.ClearCollectionChangedEvents(); - } - _cultureInfos = value; - if (_cultureInfos != null) - { - _cultureInfos.CollectionChanged += CultureInfosCollectionChanged; - } + ClearCultureInfo(culture!); + } + else // set + { + this.SetCultureInfo(culture!, name, DateTime.Now); + } + } + else // set on invariant content type + { + if (!culture.IsNullOrWhiteSpace()) // invariant is NOT ok + { + throw new NotSupportedException("Content type does not vary by culture."); } + + Name = name; // may be null + } + } + + private void ClearCultureInfo(string culture) + { + if (culture == null) + { + throw new ArgumentNullException(nameof(culture)); } - /// - public string? GetCultureName(string? culture) + if (string.IsNullOrWhiteSpace(culture)) { - if (culture.IsNullOrWhiteSpace()) return Name; - if (!ContentType.VariesByCulture()) return null; - if (_cultureInfos == null) return null; - return _cultureInfos.TryGetValue(culture!, out var infos) ? infos.Name : null; + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(culture)); } - /// - public DateTime? GetUpdateDate(string culture) + if (_cultureInfos == null) { - if (culture.IsNullOrWhiteSpace()) return null; - if (!ContentType.VariesByCulture()) return null; - if (_cultureInfos == null) return null; - return _cultureInfos.TryGetValue(culture, out var infos) ? infos.Date : (DateTime?)null; + return; } - /// - public void SetCultureName(string? name, string? culture) + _cultureInfos.Remove(culture); + if (_cultureInfos.Count == 0) { - if (ContentType.VariesByCulture()) // set on variant content type + _cultureInfos = null; + } + } + + /// + /// Handles culture infos collection changes. + /// + private void CultureInfosCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + OnPropertyChanged(nameof(CultureInfos)); + + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: { - if (culture.IsNullOrWhiteSpace()) // invariant is ok + ContentCultureInfos cultureInfo = e.NewItems?.Cast().First(); + if (_currentCultureChanges.addedCultures == null) { - Name = name; // may be null + _currentCultureChanges.addedCultures = + new HashSet(StringComparer.InvariantCultureIgnoreCase); } - else if (name.IsNullOrWhiteSpace()) // clear + + if (_currentCultureChanges.updatedCultures == null) { - ClearCultureInfo(culture!); + _currentCultureChanges.updatedCultures = + new HashSet(StringComparer.InvariantCultureIgnoreCase); } - else // set + + if (cultureInfo is not null) { - this.SetCultureInfo(culture!, name, DateTime.Now); + _currentCultureChanges.addedCultures.Add(cultureInfo.Culture); + _currentCultureChanges.updatedCultures.Add(cultureInfo.Culture); + _currentCultureChanges.removedCultures?.Remove(cultureInfo.Culture); } - } - else // set on invariant content type - { - if (!culture.IsNullOrWhiteSpace()) // invariant is NOT ok - throw new NotSupportedException("Content type does not vary by culture."); - Name = name; // may be null + break; } - } + case NotifyCollectionChangedAction.Remove: + { + //remove listening for changes + ContentCultureInfos cultureInfo = e.OldItems?.Cast().First(); + if (_currentCultureChanges.removedCultures == null) + { + _currentCultureChanges.removedCultures = + new HashSet(StringComparer.InvariantCultureIgnoreCase); + } - private void ClearCultureInfo(string culture) - { - if (culture == null) throw new ArgumentNullException(nameof(culture)); - if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(culture)); + if (cultureInfo is not null) + { + _currentCultureChanges.removedCultures.Add(cultureInfo.Culture); + _currentCultureChanges.updatedCultures?.Remove(cultureInfo.Culture); + _currentCultureChanges.addedCultures?.Remove(cultureInfo.Culture); + } - if (_cultureInfos == null) return; - _cultureInfos.Remove(culture); - if (_cultureInfos.Count == 0) - _cultureInfos = null; - } + break; + } + case NotifyCollectionChangedAction.Replace: + { + //replace occurs when an Update occurs + ContentCultureInfos cultureInfo = e.NewItems?.Cast().First(); + if (_currentCultureChanges.updatedCultures == null) + { + _currentCultureChanges.updatedCultures = + new HashSet(StringComparer.InvariantCultureIgnoreCase); + } - /// - /// Handles culture infos collection changes. - /// - private void CultureInfosCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) - { - OnPropertyChanged(nameof(CultureInfos)); + if (cultureInfo is not null) + { + _currentCultureChanges.updatedCultures.Add(cultureInfo.Culture); + } - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - { - var cultureInfo = e.NewItems?.Cast().First(); - if (_currentCultureChanges.addedCultures == null) _currentCultureChanges.addedCultures = new HashSet(StringComparer.InvariantCultureIgnoreCase); - if (_currentCultureChanges.updatedCultures == null) _currentCultureChanges.updatedCultures = new HashSet(StringComparer.InvariantCultureIgnoreCase); - if (cultureInfo is not null) - { - _currentCultureChanges.addedCultures.Add(cultureInfo.Culture); - _currentCultureChanges.updatedCultures.Add(cultureInfo.Culture); - _currentCultureChanges.removedCultures?.Remove(cultureInfo.Culture); - } - - break; - } - case NotifyCollectionChangedAction.Remove: - { - //remove listening for changes - var cultureInfo = e.OldItems?.Cast().First(); - if (_currentCultureChanges.removedCultures == null) _currentCultureChanges.removedCultures = new HashSet(StringComparer.InvariantCultureIgnoreCase); - if (cultureInfo is not null) - { - _currentCultureChanges.removedCultures.Add(cultureInfo.Culture); - _currentCultureChanges.updatedCultures?.Remove(cultureInfo.Culture); - _currentCultureChanges.addedCultures?.Remove(cultureInfo.Culture); - } - - break; - } - case NotifyCollectionChangedAction.Replace: - { - //replace occurs when an Update occurs - var cultureInfo = e.NewItems?.Cast().First(); - if (_currentCultureChanges.updatedCultures == null) _currentCultureChanges.updatedCultures = new HashSet(StringComparer.InvariantCultureIgnoreCase); - if (cultureInfo is not null) - { - _currentCultureChanges.updatedCultures.Add(cultureInfo.Culture); - } - - break; - } + break; } } + } - #endregion + #endregion - #region Has, Get, Set, Publish Property Value + #region Has, Get, Set, Publish Property Value - /// - public bool HasProperty(string propertyTypeAlias) - => Properties.Contains(propertyTypeAlias); + /// + public bool HasProperty(string propertyTypeAlias) + => Properties.Contains(propertyTypeAlias); - /// - public object? GetValue(string propertyTypeAlias, string? culture = null, string? segment = null, bool published = false) + /// + public object? GetValue(string propertyTypeAlias, string? culture = null, string? segment = null, + bool published = false) => + Properties.TryGetValue(propertyTypeAlias, out IProperty property) + ? property?.GetValue(culture, segment, published) + : null; + + /// + public TValue? GetValue(string propertyTypeAlias, string? culture = null, string? segment = null, + bool published = false) + { + if (!Properties.TryGetValue(propertyTypeAlias, out IProperty property)) { - return Properties.TryGetValue(propertyTypeAlias, out var property) - ? property?.GetValue(culture, segment, published) - : null; + return default; } - /// - public TValue? GetValue(string propertyTypeAlias, string? culture = null, string? segment = null, bool published = false) - { - if (!Properties.TryGetValue(propertyTypeAlias, out var property)) - return default; + Attempt? convertAttempt = property?.GetValue(culture, segment, published).TryConvertTo(); + return convertAttempt?.Success is not null && (convertAttempt?.Success ?? false) + ? convertAttempt.Value.Result + : default; + } - var convertAttempt = property?.GetValue(culture, segment, published).TryConvertTo(); - return convertAttempt?.Success is not null && (convertAttempt?.Success ?? false) ? convertAttempt.Value.Result : default; + /// + public void SetValue(string propertyTypeAlias, object? value, string? culture = null, string? segment = null) + { + if (!Properties.TryGetValue(propertyTypeAlias, out IProperty property)) + { + throw new InvalidOperationException( + $"No PropertyType exists with the supplied alias \"{propertyTypeAlias}\"."); } - /// - public void SetValue(string propertyTypeAlias, object? value, string? culture = null, string? segment = null) - { - if (!Properties.TryGetValue(propertyTypeAlias, out var property)) - throw new InvalidOperationException($"No PropertyType exists with the supplied alias \"{propertyTypeAlias}\"."); + property?.SetValue(value, culture, segment); - property?.SetValue(value, culture, segment); + //bump the culture to be flagged for updating + this.TouchCulture(culture); + } - //bump the culture to be flagged for updating - this.TouchCulture(culture); - } + #endregion - #endregion + #region Dirty - #region Dirty + public override void ResetWereDirtyProperties() + { + base.ResetWereDirtyProperties(); + _previousCultureChanges.addedCultures = null; + _previousCultureChanges.removedCultures = null; + _previousCultureChanges.updatedCultures = null; + } + + /// + /// Overridden to include user properties. + public override void ResetDirtyProperties(bool rememberDirty) + { + base.ResetDirtyProperties(rememberDirty); - public override void ResetWereDirtyProperties() + if (rememberDirty) + { + _previousCultureChanges.addedCultures = + _currentCultureChanges.addedCultures == null || _currentCultureChanges.addedCultures.Count == 0 + ? null + : new HashSet(_currentCultureChanges.addedCultures, + StringComparer.InvariantCultureIgnoreCase); + _previousCultureChanges.removedCultures = + _currentCultureChanges.removedCultures == null || _currentCultureChanges.removedCultures.Count == 0 + ? null + : new HashSet(_currentCultureChanges.removedCultures, + StringComparer.InvariantCultureIgnoreCase); + _previousCultureChanges.updatedCultures = + _currentCultureChanges.updatedCultures == null || _currentCultureChanges.updatedCultures.Count == 0 + ? null + : new HashSet(_currentCultureChanges.updatedCultures, + StringComparer.InvariantCultureIgnoreCase); + } + else { - base.ResetWereDirtyProperties(); _previousCultureChanges.addedCultures = null; _previousCultureChanges.removedCultures = null; _previousCultureChanges.updatedCultures = null; } - /// - /// Overridden to include user properties. - public override void ResetDirtyProperties(bool rememberDirty) - { - base.ResetDirtyProperties(rememberDirty); - - if (rememberDirty) - { - _previousCultureChanges.addedCultures = _currentCultureChanges.addedCultures == null || _currentCultureChanges.addedCultures.Count == 0 ? null : new HashSet(_currentCultureChanges.addedCultures, StringComparer.InvariantCultureIgnoreCase); - _previousCultureChanges.removedCultures = _currentCultureChanges.removedCultures == null || _currentCultureChanges.removedCultures.Count == 0 ? null : new HashSet(_currentCultureChanges.removedCultures, StringComparer.InvariantCultureIgnoreCase); - _previousCultureChanges.updatedCultures = _currentCultureChanges.updatedCultures == null || _currentCultureChanges.updatedCultures.Count == 0 ? null : new HashSet(_currentCultureChanges.updatedCultures, StringComparer.InvariantCultureIgnoreCase); - } - else - { - _previousCultureChanges.addedCultures = null; - _previousCultureChanges.removedCultures = null; - _previousCultureChanges.updatedCultures = null; - } - _currentCultureChanges.addedCultures?.Clear(); - _currentCultureChanges.removedCultures?.Clear(); - _currentCultureChanges.updatedCultures?.Clear(); - - // also reset dirty changes made to user's properties - foreach (var prop in Properties) - prop.ResetDirtyProperties(rememberDirty); - - // take care of culture infos - if (_cultureInfos == null) return; + _currentCultureChanges.addedCultures?.Clear(); + _currentCultureChanges.removedCultures?.Clear(); + _currentCultureChanges.updatedCultures?.Clear(); - foreach (var cultureInfo in _cultureInfos) - cultureInfo.ResetDirtyProperties(rememberDirty); + // also reset dirty changes made to user's properties + foreach (IProperty prop in Properties) + { + prop.ResetDirtyProperties(rememberDirty); } - /// - /// Overridden to include user properties. - public override bool IsDirty() + // take care of culture infos + if (_cultureInfos == null) { - return IsEntityDirty() || this.IsAnyUserPropertyDirty(); + return; } - /// - /// Overridden to include user properties. - public override bool WasDirty() + foreach (ContentCultureInfos cultureInfo in _cultureInfos) { - return WasEntityDirty() || this.WasAnyUserPropertyDirty(); + cultureInfo.ResetDirtyProperties(rememberDirty); } + } + + /// + /// Overridden to include user properties. + public override bool IsDirty() => IsEntityDirty() || this.IsAnyUserPropertyDirty(); + + /// + /// Overridden to include user properties. + public override bool WasDirty() => WasEntityDirty() || this.WasAnyUserPropertyDirty(); + + /// + /// Gets a value indicating whether the current entity's own properties (not user) are dirty. + /// + public bool IsEntityDirty() => base.IsDirty(); - /// - /// Gets a value indicating whether the current entity's own properties (not user) are dirty. - /// - public bool IsEntityDirty() + /// + /// Gets a value indicating whether the current entity's own properties (not user) were dirty. + /// + public bool WasEntityDirty() => base.WasDirty(); + + /// + /// Overridden to include user properties. + public override bool IsPropertyDirty(string propertyName) + { + if (base.IsPropertyDirty(propertyName)) { - return base.IsDirty(); + return true; } - /// - /// Gets a value indicating whether the current entity's own properties (not user) were dirty. - /// - public bool WasEntityDirty() + //Special check here since we want to check if the request is for changed cultures + if (propertyName.StartsWith(ChangeTrackingPrefix.AddedCulture)) { - return base.WasDirty(); + var culture = propertyName.TrimStart(ChangeTrackingPrefix.AddedCulture); + return _currentCultureChanges.addedCultures?.Contains(culture) ?? false; } - /// - /// Overridden to include user properties. - public override bool IsPropertyDirty(string propertyName) + if (propertyName.StartsWith(ChangeTrackingPrefix.RemovedCulture)) { - if (base.IsPropertyDirty(propertyName)) - return true; - - //Special check here since we want to check if the request is for changed cultures - if (propertyName.StartsWith(ChangeTrackingPrefix.AddedCulture)) - { - var culture = propertyName.TrimStart(ChangeTrackingPrefix.AddedCulture); - return _currentCultureChanges.addedCultures?.Contains(culture) ?? false; - } - if (propertyName.StartsWith(ChangeTrackingPrefix.RemovedCulture)) - { - var culture = propertyName.TrimStart(ChangeTrackingPrefix.RemovedCulture); - return _currentCultureChanges.removedCultures?.Contains(culture) ?? false; - } - if (propertyName.StartsWith(ChangeTrackingPrefix.UpdatedCulture)) - { - var culture = propertyName.TrimStart(ChangeTrackingPrefix.UpdatedCulture); - return _currentCultureChanges.updatedCultures?.Contains(culture) ?? false; - } - - return Properties.Contains(propertyName) && (Properties[propertyName]?.IsDirty() ?? false); + var culture = propertyName.TrimStart(ChangeTrackingPrefix.RemovedCulture); + return _currentCultureChanges.removedCultures?.Contains(culture) ?? false; } - /// - /// Overridden to include user properties. - public override bool WasPropertyDirty(string propertyName) + if (propertyName.StartsWith(ChangeTrackingPrefix.UpdatedCulture)) { - if (base.WasPropertyDirty(propertyName)) - return true; + var culture = propertyName.TrimStart(ChangeTrackingPrefix.UpdatedCulture); + return _currentCultureChanges.updatedCultures?.Contains(culture) ?? false; + } - //Special check here since we want to check if the request is for changed cultures - if (propertyName.StartsWith(ChangeTrackingPrefix.AddedCulture)) - { - var culture = propertyName.TrimStart(ChangeTrackingPrefix.AddedCulture); - return _previousCultureChanges.addedCultures?.Contains(culture) ?? false; - } - if (propertyName.StartsWith(ChangeTrackingPrefix.RemovedCulture)) - { - var culture = propertyName.TrimStart(ChangeTrackingPrefix.RemovedCulture); - return _previousCultureChanges.removedCultures?.Contains(culture) ?? false; - } - if (propertyName.StartsWith(ChangeTrackingPrefix.UpdatedCulture)) - { - var culture = propertyName.TrimStart(ChangeTrackingPrefix.UpdatedCulture); - return _previousCultureChanges.updatedCultures?.Contains(culture) ?? false; - } + return Properties.Contains(propertyName) && (Properties[propertyName]?.IsDirty() ?? false); + } - return Properties.Contains(propertyName) && (Properties[propertyName]?.WasDirty() ?? false); + /// + /// Overridden to include user properties. + public override bool WasPropertyDirty(string propertyName) + { + if (base.WasPropertyDirty(propertyName)) + { + return true; } - /// - /// Overridden to include user properties. - public override IEnumerable GetDirtyProperties() + //Special check here since we want to check if the request is for changed cultures + if (propertyName.StartsWith(ChangeTrackingPrefix.AddedCulture)) { - var instanceProperties = base.GetDirtyProperties(); - var propertyTypes = Properties.Where(x => x.IsDirty()).Select(x => x.Alias); - return instanceProperties.Concat(propertyTypes); + var culture = propertyName.TrimStart(ChangeTrackingPrefix.AddedCulture); + return _previousCultureChanges.addedCultures?.Contains(culture) ?? false; } - /// - /// Overridden to include user properties. - public override IEnumerable GetWereDirtyProperties() + if (propertyName.StartsWith(ChangeTrackingPrefix.RemovedCulture)) { - var instanceProperties = base.GetWereDirtyProperties(); - var propertyTypes = Properties.Where(x => x.WasDirty()).Select(x => x.Alias); - return instanceProperties.Concat(propertyTypes); + var culture = propertyName.TrimStart(ChangeTrackingPrefix.RemovedCulture); + return _previousCultureChanges.removedCultures?.Contains(culture) ?? false; } - #endregion - - /// - /// - /// Overridden to deal with specific object instances - /// - protected override void PerformDeepClone(object clone) + if (propertyName.StartsWith(ChangeTrackingPrefix.UpdatedCulture)) { - base.PerformDeepClone(clone); - - var clonedContent = (ContentBase)clone; - - //need to manually clone this since it's not settable - clonedContent.ContentType = ContentType; - - //if culture infos exist then deal with event bindings - if (clonedContent._cultureInfos != null) - { - clonedContent._cultureInfos.ClearCollectionChangedEvents(); //clear this event handler if any - clonedContent._cultureInfos = (ContentCultureInfosCollection?)_cultureInfos?.DeepClone(); //manually deep clone - if (clonedContent._cultureInfos is not null) - { - clonedContent._cultureInfos.CollectionChanged += - clonedContent.CultureInfosCollectionChanged; //re-assign correct event handler - } - } + var culture = propertyName.TrimStart(ChangeTrackingPrefix.UpdatedCulture); + return _previousCultureChanges.updatedCultures?.Contains(culture) ?? false; + } - //if properties exist then deal with event bindings - if (clonedContent._properties != null) - { - clonedContent._properties.ClearCollectionChangedEvents(); //clear this event handler if any - clonedContent._properties = (IPropertyCollection)_properties.DeepClone(); //manually deep clone - clonedContent._properties.CollectionChanged += clonedContent.PropertiesChanged; //re-assign correct event handler - } + return Properties.Contains(propertyName) && (Properties[propertyName]?.WasDirty() ?? false); + } - clonedContent._currentCultureChanges.updatedCultures = null; - clonedContent._currentCultureChanges.addedCultures = null; - clonedContent._currentCultureChanges.removedCultures = null; + /// + /// Overridden to include user properties. + public override IEnumerable GetDirtyProperties() + { + IEnumerable instanceProperties = base.GetDirtyProperties(); + IEnumerable propertyTypes = Properties.Where(x => x.IsDirty()).Select(x => x.Alias); + return instanceProperties.Concat(propertyTypes); + } - clonedContent._previousCultureChanges.updatedCultures = null; - clonedContent._previousCultureChanges.addedCultures = null; - clonedContent._previousCultureChanges.removedCultures = null; - } + /// + /// Overridden to include user properties. + public override IEnumerable GetWereDirtyProperties() + { + IEnumerable instanceProperties = base.GetWereDirtyProperties(); + IEnumerable propertyTypes = Properties.Where(x => x.WasDirty()).Select(x => x.Alias); + return instanceProperties.Concat(propertyTypes); } + + #endregion } diff --git a/src/Umbraco.Core/Models/ContentBaseExtensions.cs b/src/Umbraco.Core/Models/ContentBaseExtensions.cs index b81cf398bfc5..40642161aa9e 100644 --- a/src/Umbraco.Core/Models/ContentBaseExtensions.cs +++ b/src/Umbraco.Core/Models/ContentBaseExtensions.cs @@ -1,43 +1,47 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Strings; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Provides extension methods to IContentBase to get URL segments. +/// +public static class ContentBaseExtensions { + private static DefaultUrlSegmentProvider? s_defaultUrlSegmentProvider; + /// - /// Provides extension methods to IContentBase to get URL segments. + /// Gets the URL segment for a specified content and culture. /// - public static class ContentBaseExtensions + /// The content. + /// + /// + /// The culture. + /// The URL segment. + public static string? GetUrlSegment(this IContentBase content, IShortStringHelper shortStringHelper, + IEnumerable urlSegmentProviders, string? culture = null) { - /// - /// Gets the URL segment for a specified content and culture. - /// - /// The content. - /// - /// - /// The culture. - /// The URL segment. - public static string? GetUrlSegment(this IContentBase content, IShortStringHelper shortStringHelper, IEnumerable urlSegmentProviders, string? culture = null) + if (content == null) { - if (content == null) throw new ArgumentNullException(nameof(content)); - if (urlSegmentProviders == null) throw new ArgumentNullException(nameof(urlSegmentProviders)); + throw new ArgumentNullException(nameof(content)); + } - var url = urlSegmentProviders.Select(p => p.GetUrlSegment(content, culture)).FirstOrDefault(u => u != null); - if (url == null) - { - if (s_defaultUrlSegmentProvider == null) - { - s_defaultUrlSegmentProvider = new DefaultUrlSegmentProvider(shortStringHelper); - } + if (urlSegmentProviders == null) + { + throw new ArgumentNullException(nameof(urlSegmentProviders)); + } - url = s_defaultUrlSegmentProvider.GetUrlSegment(content, culture); // be safe + var url = urlSegmentProviders.Select(p => p.GetUrlSegment(content, culture)).FirstOrDefault(u => u != null); + if (url == null) + { + if (s_defaultUrlSegmentProvider == null) + { + s_defaultUrlSegmentProvider = new DefaultUrlSegmentProvider(shortStringHelper); } - return url; + url = s_defaultUrlSegmentProvider.GetUrlSegment(content, culture); // be safe } - private static DefaultUrlSegmentProvider? s_defaultUrlSegmentProvider; + return url; } } diff --git a/src/Umbraco.Core/Models/ContentCultureInfos.cs b/src/Umbraco.Core/Models/ContentCultureInfos.cs index 47f0765d6344..eb6776dde2b2 100644 --- a/src/Umbraco.Core/Models/ContentCultureInfos.cs +++ b/src/Umbraco.Core/Models/ContentCultureInfos.cs @@ -1,109 +1,105 @@ -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// The name of a content variant for a given culture +/// +public class ContentCultureInfos : BeingDirtyBase, IDeepCloneable, IEquatable { + private DateTime _date; + private string? _name; + /// - /// The name of a content variant for a given culture + /// Initializes a new instance of the class. /// - public class ContentCultureInfos : BeingDirtyBase, IDeepCloneable, IEquatable + public ContentCultureInfos(string culture) { - private DateTime _date; - private string? _name; - - /// - /// Initializes a new instance of the class. - /// - public ContentCultureInfos(string culture) + if (culture == null) { - if (culture == null) throw new ArgumentNullException(nameof(culture)); - if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(culture)); - - Culture = culture; + throw new ArgumentNullException(nameof(culture)); } - /// - /// Initializes a new instance of the class. - /// - /// Used for cloning, without change tracking. - internal ContentCultureInfos(ContentCultureInfos other) - : this(other.Culture) + if (string.IsNullOrWhiteSpace(culture)) { - _name = other.Name; - _date = other.Date; + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(culture)); } - /// - /// Gets the culture. - /// - public string Culture { get; } + Culture = culture; + } - /// - /// Gets the name. - /// - public string? Name - { - get => _name; - set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); - } + /// + /// Initializes a new instance of the class. + /// + /// Used for cloning, without change tracking. + internal ContentCultureInfos(ContentCultureInfos other) + : this(other.Culture) + { + _name = other.Name; + _date = other.Date; + } - /// - /// Gets the date. - /// - public DateTime Date - { - get => _date; - set => SetPropertyValueAndDetectChanges(value, ref _date, nameof(Date)); - } + /// + /// Gets the culture. + /// + public string Culture { get; } - /// - public object DeepClone() - { - return new ContentCultureInfos(this); - } + /// + /// Gets the name. + /// + public string? Name + { + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); + } - /// - public override bool Equals(object? obj) - { - return obj is ContentCultureInfos other && Equals(other); - } + /// + /// Gets the date. + /// + public DateTime Date + { + get => _date; + set => SetPropertyValueAndDetectChanges(value, ref _date, nameof(Date)); + } - /// - public bool Equals(ContentCultureInfos? other) - { - return other != null && Culture == other.Culture && Name == other.Name; - } + /// + public object DeepClone() => new ContentCultureInfos(this); - /// - public override int GetHashCode() - { - var hashCode = 479558943; - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Culture); - if (Name is not null) - { - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Name); - } - - return hashCode; - } + /// + public bool Equals(ContentCultureInfos? other) => other != null && Culture == other.Culture && Name == other.Name; - /// - /// Deconstructs into culture and name. - /// - public void Deconstruct(out string culture, out string? name) - { - culture = Culture; - name = Name; - } + /// + public override bool Equals(object? obj) => obj is ContentCultureInfos other && Equals(other); - /// - /// Deconstructs into culture, name and date. - /// - public void Deconstruct(out string culture, out string? name, out DateTime date) + /// + public override int GetHashCode() + { + var hashCode = 479558943; + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(Culture); + if (Name is not null) { - Deconstruct(out culture, out name); - date = Date; + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(Name); } + + return hashCode; + } + + /// + /// Deconstructs into culture and name. + /// + public void Deconstruct(out string culture, out string? name) + { + culture = Culture; + name = Name; + } + + /// + /// Deconstructs into culture, name and date. + /// + public void Deconstruct(out string culture, out string? name, out DateTime date) + { + Deconstruct(out culture, out name); + date = Date; } } diff --git a/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs b/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs index 0d480ede6db5..74df246a3485 100644 --- a/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs +++ b/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs @@ -1,60 +1,64 @@ -using System; -using System.Collections.Specialized; +using System.Collections.Specialized; using Umbraco.Cms.Core.Collections; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// The culture names of a content's variants +/// +public class ContentCultureInfosCollection : ObservableDictionary, IDeepCloneable { /// - /// The culture names of a content's variants + /// Initializes a new instance of the class. /// - public class ContentCultureInfosCollection : ObservableDictionary, IDeepCloneable + public ContentCultureInfosCollection() + : base(x => x.Culture, StringComparer.InvariantCultureIgnoreCase) { - /// - /// Initializes a new instance of the class. - /// - public ContentCultureInfosCollection() - : base(x => x.Culture, StringComparer.InvariantCultureIgnoreCase) - { } - - /// - /// Adds or updates a instance. - /// - public void AddOrUpdate(string culture, string? name, DateTime date) + } + + /// + public object DeepClone() + { + var clone = new ContentCultureInfosCollection(); + + foreach (ContentCultureInfos item in this) { - if (culture == null) throw new ArgumentNullException(nameof(culture)); - if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(culture)); - - culture = culture.ToLowerInvariant(); - - if (TryGetValue(culture, out var item)) - { - item.Name = name; - item.Date = date; - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, item)); - } - else - { - Add(new ContentCultureInfos(culture) - { - Name = name, - Date = date - }); - } + var itemClone = (ContentCultureInfos)item.DeepClone(); + itemClone.ResetDirtyProperties(false); + clone.Add(itemClone); } - /// - public object DeepClone() + return clone; + } + + /// + /// Adds or updates a instance. + /// + public void AddOrUpdate(string culture, string? name, DateTime date) + { + if (culture == null) + { + throw new ArgumentNullException(nameof(culture)); + } + + if (string.IsNullOrWhiteSpace(culture)) { - var clone = new ContentCultureInfosCollection(); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(culture)); + } - foreach (var item in this) - { - var itemClone = (ContentCultureInfos) item.DeepClone(); - itemClone.ResetDirtyProperties(false); - clone.Add(itemClone); - } + culture = culture.ToLowerInvariant(); - return clone; + if (TryGetValue(culture, out ContentCultureInfos item)) + { + item.Name = name; + item.Date = date; + OnCollectionChanged( + new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, item)); + } + else + { + Add(new ContentCultureInfos(culture) {Name = name, Date = date}); } } } diff --git a/src/Umbraco.Core/Models/ContentDataIntegrityReport.cs b/src/Umbraco.Core/Models/ContentDataIntegrityReport.cs index 8a13a26e402a..7940c0430765 100644 --- a/src/Umbraco.Core/Models/ContentDataIntegrityReport.cs +++ b/src/Umbraco.Core/Models/ContentDataIntegrityReport.cs @@ -1,48 +1,42 @@ -using System.Collections.Generic; -using System.Linq; +namespace Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Models +public class ContentDataIntegrityReport { - public class ContentDataIntegrityReport + public enum IssueType { - public ContentDataIntegrityReport(IReadOnlyDictionary detectedIssues) - { - DetectedIssues = detectedIssues; - } - - public bool Ok => DetectedIssues.Count == 0 || DetectedIssues.Count == DetectedIssues.Values.Count(x => x.Fixed); - - public IReadOnlyDictionary DetectedIssues { get; } - - public IReadOnlyDictionary FixedIssues - => DetectedIssues.Where(x => x.Value.Fixed).ToDictionary(x => x.Key, x => x.Value); - - public enum IssueType - { - /// - /// The item's level and path are inconsistent with it's parent's path and level - /// - InvalidPathAndLevelByParentId, - - /// - /// The item's path doesn't contain all required parts - /// - InvalidPathEmpty, - - /// - /// The item's path parts are inconsistent with it's level value - /// - InvalidPathLevelMismatch, - - /// - /// The item's path does not end with it's own ID - /// - InvalidPathById, - - /// - /// The item's path does not have it's parent Id as the 2nd last entry - /// - InvalidPathByParentId, - } + /// + /// The item's level and path are inconsistent with it's parent's path and level + /// + InvalidPathAndLevelByParentId, + + /// + /// The item's path doesn't contain all required parts + /// + InvalidPathEmpty, + + /// + /// The item's path parts are inconsistent with it's level value + /// + InvalidPathLevelMismatch, + + /// + /// The item's path does not end with it's own ID + /// + InvalidPathById, + + /// + /// The item's path does not have it's parent Id as the 2nd last entry + /// + InvalidPathByParentId } + + public ContentDataIntegrityReport(IReadOnlyDictionary detectedIssues) => + DetectedIssues = detectedIssues; + + public bool Ok => DetectedIssues.Count == 0 || DetectedIssues.Count == DetectedIssues.Values.Count(x => x.Fixed); + + public IReadOnlyDictionary DetectedIssues { get; } + + public IReadOnlyDictionary FixedIssues + => DetectedIssues.Where(x => x.Value.Fixed).ToDictionary(x => x.Key, x => x.Value); } diff --git a/src/Umbraco.Core/Models/ContentDataIntegrityReportEntry.cs b/src/Umbraco.Core/Models/ContentDataIntegrityReportEntry.cs index e6138addbc42..ed80ff0ea063 100644 --- a/src/Umbraco.Core/Models/ContentDataIntegrityReportEntry.cs +++ b/src/Umbraco.Core/Models/ContentDataIntegrityReportEntry.cs @@ -1,13 +1,9 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public class ContentDataIntegrityReportEntry { - public class ContentDataIntegrityReportEntry - { - public ContentDataIntegrityReportEntry(ContentDataIntegrityReport.IssueType issueType) - { - IssueType = issueType; - } + public ContentDataIntegrityReportEntry(ContentDataIntegrityReport.IssueType issueType) => IssueType = issueType; - public ContentDataIntegrityReport.IssueType IssueType { get; } - public bool Fixed { get; set; } - } + public ContentDataIntegrityReport.IssueType IssueType { get; } + public bool Fixed { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentDataIntegrityReportOptions.cs b/src/Umbraco.Core/Models/ContentDataIntegrityReportOptions.cs index 52ea3d403218..df67854bae23 100644 --- a/src/Umbraco.Core/Models/ContentDataIntegrityReportOptions.cs +++ b/src/Umbraco.Core/Models/ContentDataIntegrityReportOptions.cs @@ -1,13 +1,12 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public class ContentDataIntegrityReportOptions { - public class ContentDataIntegrityReportOptions - { - /// - /// Set to true to try to automatically resolve data integrity issues - /// - public bool FixIssues { get; set; } + /// + /// Set to true to try to automatically resolve data integrity issues + /// + public bool FixIssues { get; set; } - // TODO: We could define all sorts of options for the data integrity check like what to check for, what to fix, etc... - // things like Tag data consistency, etc... - } + // TODO: We could define all sorts of options for the data integrity check like what to check for, what to fix, etc... + // things like Tag data consistency, etc... } diff --git a/src/Umbraco.Core/Models/ContentEditing/AssignedContentPermissions.cs b/src/Umbraco.Core/Models/ContentEditing/AssignedContentPermissions.cs index 2c73bcd5904f..fe5c57071a80 100644 --- a/src/Umbraco.Core/Models/ContentEditing/AssignedContentPermissions.cs +++ b/src/Umbraco.Core/Models/ContentEditing/AssignedContentPermissions.cs @@ -1,21 +1,19 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// The permissions assigned to a content node +/// +/// +/// The underlying data such as Name, etc... is that of the Content item +/// +[DataContract(Name = "contentPermissions", Namespace = "")] +public class AssignedContentPermissions : EntityBasic { /// - /// The permissions assigned to a content node + /// The assigned permissions to the content item organized by permission group name /// - /// - /// The underlying data such as Name, etc... is that of the Content item - /// - [DataContract(Name = "contentPermissions", Namespace = "")] - public class AssignedContentPermissions : EntityBasic - { - /// - /// The assigned permissions to the content item organized by permission group name - /// - [DataMember(Name = "permissions")] - public IDictionary>? AssignedPermissions { get; set; } - } + [DataMember(Name = "permissions")] + public IDictionary>? AssignedPermissions { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/AssignedUserGroupPermissions.cs b/src/Umbraco.Core/Models/ContentEditing/AssignedUserGroupPermissions.cs index b6aca055155b..287e8eb484d5 100644 --- a/src/Umbraco.Core/Models/ContentEditing/AssignedUserGroupPermissions.cs +++ b/src/Umbraco.Core/Models/ContentEditing/AssignedUserGroupPermissions.cs @@ -1,42 +1,40 @@ -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// The user group permissions assigned to a content node +/// +/// +/// The underlying data such as Name, etc... is that of the User Group +/// +[DataContract(Name = "userGroupPermissions", Namespace = "")] +public class AssignedUserGroupPermissions : EntityBasic { /// - /// The user group permissions assigned to a content node + /// The assigned permissions for the user group organized by permission group name /// - /// - /// The underlying data such as Name, etc... is that of the User Group - /// - [DataContract(Name = "userGroupPermissions", Namespace = "")] - public class AssignedUserGroupPermissions : EntityBasic - { - /// - /// The assigned permissions for the user group organized by permission group name - /// - [DataMember(Name = "permissions")] - public IDictionary>? AssignedPermissions { get; set; } + [DataMember(Name = "permissions")] + public IDictionary>? AssignedPermissions { get; set; } - /// - /// The default permissions for the user group organized by permission group name - /// - [DataMember(Name = "defaultPermissions")] - public IDictionary>? DefaultPermissions { get; set; } + /// + /// The default permissions for the user group organized by permission group name + /// + [DataMember(Name = "defaultPermissions")] + public IDictionary>? DefaultPermissions { get; set; } - public static IDictionary> ClonePermissions(IDictionary>? permissions) + public static IDictionary> ClonePermissions( + IDictionary>? permissions) + { + var result = new Dictionary>(); + if (permissions is not null) { - var result = new Dictionary>(); - if (permissions is not null) + foreach (KeyValuePair> permission in permissions) { - foreach (var permission in permissions) - { - result[permission.Key] = new List(permission.Value.Select(x => (Permission)x.Clone())); - } + result[permission.Key] = new List(permission.Value.Select(x => (Permission)x.Clone())); } - - return result; } + + return result; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/AuditLog.cs b/src/Umbraco.Core/Models/ContentEditing/AuditLog.cs index 5f40ace6ca29..3de1348560dc 100644 --- a/src/Umbraco.Core/Models/ContentEditing/AuditLog.cs +++ b/src/Umbraco.Core/Models/ContentEditing/AuditLog.cs @@ -1,36 +1,25 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "auditLog", Namespace = "")] +public class AuditLog { - [DataContract(Name = "auditLog", Namespace = "")] - public class AuditLog - { - [DataMember(Name = "userId")] - public int UserId { get; set; } + [DataMember(Name = "userId")] public int UserId { get; set; } - [DataMember(Name = "userName")] - public string? UserName { get; set; } + [DataMember(Name = "userName")] public string? UserName { get; set; } - [DataMember(Name = "userAvatars")] - public string[]? UserAvatars { get; set; } + [DataMember(Name = "userAvatars")] public string[]? UserAvatars { get; set; } - [DataMember(Name = "nodeId")] - public int NodeId { get; set; } + [DataMember(Name = "nodeId")] public int NodeId { get; set; } - [DataMember(Name = "timestamp")] - public DateTime Timestamp { get; set; } + [DataMember(Name = "timestamp")] public DateTime Timestamp { get; set; } - [DataMember(Name = "logType")] - public string? LogType { get; set; } + [DataMember(Name = "logType")] public string? LogType { get; set; } - [DataMember(Name = "entityType")] - public string? EntityType { get; set; } + [DataMember(Name = "entityType")] public string? EntityType { get; set; } - [DataMember(Name = "comment")] - public string? Comment { get; set; } + [DataMember(Name = "comment")] public string? Comment { get; set; } - [DataMember(Name = "parameters")] - public string? Parameters { get; set; } - } + [DataMember(Name = "parameters")] public string? Parameters { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/BackOfficeNotification.cs b/src/Umbraco.Core/Models/ContentEditing/BackOfficeNotification.cs index 982f46d91236..f89517c9eee8 100644 --- a/src/Umbraco.Core/Models/ContentEditing/BackOfficeNotification.cs +++ b/src/Umbraco.Core/Models/ContentEditing/BackOfficeNotification.cs @@ -1,29 +1,24 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "notification", Namespace = "")] +public class BackOfficeNotification { - [DataContract(Name = "notification", Namespace = "")] - public class BackOfficeNotification + public BackOfficeNotification() { - public BackOfficeNotification() - { - - } + } - public BackOfficeNotification(string header, string message, NotificationStyle notificationType) - { - Header = header; - Message = message; - NotificationType = notificationType; - } + public BackOfficeNotification(string header, string message, NotificationStyle notificationType) + { + Header = header; + Message = message; + NotificationType = notificationType; + } - [DataMember(Name = "header")] - public string? Header { get; set; } + [DataMember(Name = "header")] public string? Header { get; set; } - [DataMember(Name = "message")] - public string? Message { get; set; } + [DataMember(Name = "message")] public string? Message { get; set; } - [DataMember(Name = "type")] - public NotificationStyle NotificationType { get; set; } - } + [DataMember(Name = "type")] public NotificationStyle NotificationType { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/CodeFileDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/CodeFileDisplay.cs index 37243c702cb2..adc83d985cad 100644 --- a/src/Umbraco.Core/Models/ContentEditing/CodeFileDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/CodeFileDisplay.cs @@ -1,76 +1,70 @@ -using System.Collections.Generic; -using System.ComponentModel; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "scriptFile", Namespace = "")] +public class CodeFileDisplay : INotificationModel, IValidatableObject { - [DataContract(Name = "scriptFile", Namespace = "")] - public class CodeFileDisplay : INotificationModel, IValidatableObject - { - public CodeFileDisplay() - { - Notifications = new List(); - } + public CodeFileDisplay() => Notifications = new List(); - /// - /// VirtualPath is the path to the file on disk - /// /views/partials/file.cshtml - /// - [DataMember(Name = "virtualPath", IsRequired = true)] - public string? VirtualPath { get; set; } + /// + /// VirtualPath is the path to the file on disk + /// /views/partials/file.cshtml + /// + [DataMember(Name = "virtualPath", IsRequired = true)] + public string? VirtualPath { get; set; } - /// - /// Path represents the path used by the backoffice tree - /// For files stored on disk, this is a URL encoded, comma separated - /// path to the file, always starting with -1. - /// - /// -1,Partials,Parials%2FFolder,Partials%2FFolder%2FFile.cshtml - /// - [DataMember(Name = "path")] - [ReadOnly(true)] - public string? Path { get; set; } + /// + /// Path represents the path used by the backoffice tree + /// For files stored on disk, this is a URL encoded, comma separated + /// path to the file, always starting with -1. + /// -1,Partials,Parials%2FFolder,Partials%2FFolder%2FFile.cshtml + /// + [DataMember(Name = "path")] + [ReadOnly(true)] + public string? Path { get; set; } - [DataMember(Name = "name", IsRequired = true)] - public string? Name { get; set; } + [DataMember(Name = "name", IsRequired = true)] + public string? Name { get; set; } - [DataMember(Name = "content", IsRequired = true)] - public string? Content { get; set; } + [DataMember(Name = "content", IsRequired = true)] + public string? Content { get; set; } - [DataMember(Name = "fileType", IsRequired = true)] - public string? FileType { get; set; } + [DataMember(Name = "fileType", IsRequired = true)] + public string? FileType { get; set; } - [DataMember(Name = "snippet")] - [ReadOnly(true)] - public string? Snippet { get; set; } + [DataMember(Name = "snippet")] + [ReadOnly(true)] + public string? Snippet { get; set; } - [DataMember(Name = "id")] - [ReadOnly(true)] - public string? Id { get; set; } + [DataMember(Name = "id")] + [ReadOnly(true)] + public string? Id { get; set; } - public List Notifications { get; private set; } + public List Notifications { get; } - /// - /// Some custom validation is required for valid file names - /// - /// - /// - public IEnumerable Validate(ValidationContext validationContext) + /// + /// Some custom validation is required for valid file names + /// + /// + /// + public IEnumerable Validate(ValidationContext validationContext) + { + var illegalChars = System.IO.Path.GetInvalidFileNameChars(); + if (Name?.ContainsAny(illegalChars) ?? false) + { + yield return new ValidationResult( + "The file name cannot contain illegal characters", + new[] {"Name"}); + } + else if (System.IO.Path.GetFileNameWithoutExtension(Name).IsNullOrWhiteSpace()) { - var illegalChars = System.IO.Path.GetInvalidFileNameChars(); - if (Name?.ContainsAny(illegalChars) ?? false) - { - yield return new ValidationResult( - "The file name cannot contain illegal characters", - new[] { "Name" }); - } - else if (System.IO.Path.GetFileNameWithoutExtension(Name).IsNullOrWhiteSpace()) - { - yield return new ValidationResult( - "The file name cannot be empty", - new[] { "Name" }); - } + yield return new ValidationResult( + "The file name cannot be empty", + new[] {"Name"}); } } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentApp.cs b/src/Umbraco.Core/Models/ContentEditing/ContentApp.cs index 3dc88df3ddd2..054edb747205 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentApp.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentApp.cs @@ -1,78 +1,78 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Represents a content app. +/// +/// +/// Content apps are editor extensions. +/// +[DataContract(Name = "app", Namespace = "")] +public class ContentApp { /// - /// Represents a content app. + /// Gets the name of the content app. + /// + [DataMember(Name = "name")] + public string? Name { get; set; } + + /// + /// Gets the unique alias of the content app. /// /// - /// Content apps are editor extensions. + /// Must be a valid javascript identifier, ie no spaces etc. /// - [DataContract(Name = "app", Namespace = "")] - public class ContentApp - { - /// - /// Gets the name of the content app. - /// - [DataMember(Name = "name")] - public string? Name { get; set; } - - /// - /// Gets the unique alias of the content app. - /// - /// - /// Must be a valid javascript identifier, ie no spaces etc. - /// - [DataMember(Name = "alias")] - public string? Alias { get; set; } + [DataMember(Name = "alias")] + public string? Alias { get; set; } - /// - /// Gets or sets the weight of the content app. - /// - /// - /// Content apps are ordered by weight, from left (lowest values) to right (highest values). - /// Some built-in apps have special weights: listview is -666, content is -100 and infos is +100. - /// The default weight is 0, meaning somewhere in-between content and infos, but weight could - /// be used for ordering between user-level apps, or anything really. - /// - [DataMember(Name = "weight")] - public int Weight { get; set; } + /// + /// Gets or sets the weight of the content app. + /// + /// + /// Content apps are ordered by weight, from left (lowest values) to right (highest values). + /// Some built-in apps have special weights: listview is -666, content is -100 and infos is +100. + /// + /// The default weight is 0, meaning somewhere in-between content and infos, but weight could + /// be used for ordering between user-level apps, or anything really. + /// + /// + [DataMember(Name = "weight")] + public int Weight { get; set; } - /// - /// Gets the icon of the content app. - /// - /// - /// Must be a valid helveticons class name (see http://hlvticons.ch/). - /// - [DataMember(Name = "icon")] - public string? Icon { get; set; } + /// + /// Gets the icon of the content app. + /// + /// + /// Must be a valid helveticons class name (see http://hlvticons.ch/). + /// + [DataMember(Name = "icon")] + public string? Icon { get; set; } - /// - /// Gets the view for rendering the content app. - /// - [DataMember(Name = "view")] - public string? View { get; set; } + /// + /// Gets the view for rendering the content app. + /// + [DataMember(Name = "view")] + public string? View { get; set; } - /// - /// The view model specific to this app - /// - [DataMember(Name = "viewModel")] - public object? ViewModel { get; set; } + /// + /// The view model specific to this app + /// + [DataMember(Name = "viewModel")] + public object? ViewModel { get; set; } - /// - /// Gets a value indicating whether the app is active. - /// - /// - /// Normally reserved for Angular to deal with but in some cases this can be set on the server side. - /// - [DataMember(Name = "active")] - public bool Active { get; set; } + /// + /// Gets a value indicating whether the app is active. + /// + /// + /// Normally reserved for Angular to deal with but in some cases this can be set on the server side. + /// + [DataMember(Name = "active")] + public bool Active { get; set; } - /// - /// Gets or sets the content app badge. - /// - [DataMember(Name = "badge")] - public ContentAppBadge? Badge { get; set; } - } + /// + /// Gets or sets the content app badge. + /// + [DataMember(Name = "badge")] + public ContentAppBadge? Badge { get; set; } } - diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentAppBadge.cs b/src/Umbraco.Core/Models/ContentEditing/ContentAppBadge.cs index 4e1089c97b5b..b3afc3181335 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentAppBadge.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentAppBadge.cs @@ -1,37 +1,33 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Represents a content app badge +/// +[DataContract(Name = "badge", Namespace = "")] +public class ContentAppBadge { /// - /// Represents a content app badge + /// Initializes a new instance of the class. /// - [DataContract(Name = "badge", Namespace = "")] - public class ContentAppBadge - { - /// - /// Initializes a new instance of the class. - /// - public ContentAppBadge() - { - this.Type = ContentAppBadgeType.Default; - } + public ContentAppBadge() => Type = ContentAppBadgeType.Default; - /// - /// Gets or sets the number displayed in the badge - /// - [DataMember(Name = "count")] - public int Count { get; set; } + /// + /// Gets or sets the number displayed in the badge + /// + [DataMember(Name = "count")] + public int Count { get; set; } - /// - /// Gets or sets the type of badge to display - /// - /// - /// This controls the background color of the badge. - /// Warning will display a dark yellow badge - /// Alert will display a red badge - /// Default will display a turquoise badge - /// - [DataMember(Name = "type")] - public ContentAppBadgeType Type { get; set; } - } + /// + /// Gets or sets the type of badge to display + /// + /// + /// This controls the background color of the badge. + /// Warning will display a dark yellow badge + /// Alert will display a red badge + /// Default will display a turquoise badge + /// + [DataMember(Name = "type")] + public ContentAppBadgeType Type { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentAppBadgeType.cs b/src/Umbraco.Core/Models/ContentEditing/ContentAppBadgeType.cs index 9bcadd13831b..e41c37ee8202 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentAppBadgeType.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentAppBadgeType.cs @@ -1,25 +1,20 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing -{ - // TODO: This was marked with `[StringEnumConverter]` to inform the serializer - // to serialize the values to string instead of INT (which is the default) - // so we need to either invent our own attribute and make the implementation aware of it - // or ... something else? +namespace Umbraco.Cms.Core.Models.ContentEditing; +// TODO: This was marked with `[StringEnumConverter]` to inform the serializer +// to serialize the values to string instead of INT (which is the default) +// so we need to either invent our own attribute and make the implementation aware of it +// or ... something else? - /// - /// Represent the content app badge types - /// - [DataContract(Name = "contentAppBadgeType")] - public enum ContentAppBadgeType - { - [EnumMember(Value = "default")] - Default = 0, +/// +/// Represent the content app badge types +/// +[DataContract(Name = "contentAppBadgeType")] +public enum ContentAppBadgeType +{ + [EnumMember(Value = "default")] Default = 0, - [EnumMember(Value = "warning")] - Warning = 1, + [EnumMember(Value = "warning")] Warning = 1, - [EnumMember(Value = "alert")] - Alert = 2 - } + [EnumMember(Value = "alert")] Alert = 2 } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentBaseSave.cs b/src/Umbraco.Core/Models/ContentEditing/ContentBaseSave.cs index d7f026aeab6e..6205f0846afd 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentBaseSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentBaseSave.cs @@ -1,62 +1,59 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Editors; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// A model representing a content item to be saved +/// +[DataContract(Name = "content", Namespace = "")] +public abstract class ContentBaseSave : ContentItemBasic, IContentSave + where TPersisted : IContentBase { - /// - /// A model representing a content item to be saved - /// - [DataContract(Name = "content", Namespace = "")] - public abstract class ContentBaseSave : ContentItemBasic, IContentSave - where TPersisted : IContentBase + protected ContentBaseSave() => UploadedFiles = new List(); + + #region IContentSave + + /// + [DataMember(Name = "action", IsRequired = true)] + [Required] + public ContentSaveAction Action { get; set; } + + [DataMember(Name = "properties")] + public override IEnumerable Properties + { + get => base.Properties; + set => base.Properties = value; + } + + [IgnoreDataMember] public List UploadedFiles { get; } + + //These need explicit implementation because we are using internal models + /// + [IgnoreDataMember] + TPersisted IContentSave.PersistedContent { get; set; } = default!; + + //Non explicit internal getter so we don't need to explicitly cast in our own code + [IgnoreDataMember] + public TPersisted PersistedContent { - protected ContentBaseSave() - { - UploadedFiles = new List(); - } - - #region IContentSave - /// - [DataMember(Name = "action", IsRequired = true)] - [Required] - public ContentSaveAction Action { get; set; } - - [DataMember(Name = "properties")] - public override IEnumerable Properties - { - get => base.Properties; - set => base.Properties = value; - } - - [IgnoreDataMember] - public List UploadedFiles { get; } - - //These need explicit implementation because we are using internal models - /// - [IgnoreDataMember] - TPersisted IContentSave.PersistedContent { get; set; } = default!; - - //Non explicit internal getter so we don't need to explicitly cast in our own code - [IgnoreDataMember] - public TPersisted PersistedContent - { - get => ((IContentSave)this).PersistedContent; - set => ((IContentSave) this).PersistedContent = value; - } - - /// - /// The property DTO object is used to gather all required property data including data type information etc... for use with validation - used during inbound model binding - /// - /// - /// We basically use this object to hydrate all required data from the database into one object so we can validate everything we need - /// instead of having to look up all the data individually. - /// This is not used for outgoing model information. - /// - [IgnoreDataMember] - public ContentPropertyCollectionDto? PropertyCollectionDto { get; set; } - - #endregion + get => ((IContentSave)this).PersistedContent; + set => ((IContentSave)this).PersistedContent = value; } + + /// + /// The property DTO object is used to gather all required property data including data type information etc... for use + /// with validation - used during inbound model binding + /// + /// + /// We basically use this object to hydrate all required data from the database into one object so we can validate + /// everything we need + /// instead of having to look up all the data individually. + /// This is not used for outgoing model information. + /// + [IgnoreDataMember] + public ContentPropertyCollectionDto? PropertyCollectionDto { get; set; } + + #endregion } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentDomainsAndCulture.cs b/src/Umbraco.Core/Models/ContentEditing/ContentDomainsAndCulture.cs index 86af3de89a03..835f22027224 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentDomainsAndCulture.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentDomainsAndCulture.cs @@ -1,15 +1,11 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "ContentDomainsAndCulture")] +public class ContentDomainsAndCulture { - [DataContract(Name = "ContentDomainsAndCulture")] - public class ContentDomainsAndCulture - { - [DataMember(Name = "domains")] - public IEnumerable? Domains { get; set; } + [DataMember(Name = "domains")] public IEnumerable? Domains { get; set; } - [DataMember(Name = "language")] - public string? Language { get; set; } - } + [DataMember(Name = "language")] public string? Language { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentItemBasic.cs b/src/Umbraco.Core/Models/ContentEditing/ContentItemBasic.cs index 9b1fcdc12907..d3c0d305c198 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentItemBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentItemBasic.cs @@ -1,106 +1,99 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// A model representing a basic content item +/// +[DataContract(Name = "content", Namespace = "")] +public class ContentItemBasic : EntityBasic { + [DataMember(Name = "updateDate")] public DateTime UpdateDate { get; set; } + + [DataMember(Name = "createDate")] public DateTime CreateDate { get; set; } + /// - /// A model representing a basic content item + /// Boolean indicating if this item is published or not based on it's /// - [DataContract(Name = "content", Namespace = "")] - public class ContentItemBasic : EntityBasic - { - [DataMember(Name = "updateDate")] - public DateTime UpdateDate { get; set; } - - [DataMember(Name = "createDate")] - public DateTime CreateDate { get; set; } + [DataMember(Name = "published")] + public bool Published => State == ContentSavedState.Published || State == ContentSavedState.PublishedPendingChanges; - /// - /// Boolean indicating if this item is published or not based on it's - /// - [DataMember(Name = "published")] - public bool Published => State == ContentSavedState.Published || State == ContentSavedState.PublishedPendingChanges; + /// + /// Determines if the content item is a draft + /// + [DataMember(Name = "edited")] + public bool Edited { get; set; } - /// - /// Determines if the content item is a draft - /// - [DataMember(Name = "edited")] - public bool Edited { get; set; } + [DataMember(Name = "owner")] public UserProfile? Owner { get; set; } - [DataMember(Name = "owner")] - public UserProfile? Owner { get; set; } + [DataMember(Name = "updater")] public UserProfile? Updater { get; set; } - [DataMember(Name = "updater")] - public UserProfile? Updater { get; set; } + public int ContentTypeId { get; set; } - public int ContentTypeId { get; set; } + [DataMember(Name = "contentTypeAlias", IsRequired = true)] + [Required(AllowEmptyStrings = false)] + public string ContentTypeAlias { get; set; } = null!; - [DataMember(Name = "contentTypeAlias", IsRequired = true)] - [Required(AllowEmptyStrings = false)] - public string ContentTypeAlias { get; set; } = null!; + [DataMember(Name = "sortOrder")] public int SortOrder { get; set; } - [DataMember(Name = "sortOrder")] - public int SortOrder { get; set; } + /// + /// The saved/published state of an item + /// + /// + /// This is nullable since it's only relevant for content (non-content like media + members will be null) + /// + [DataMember(Name = "state")] + public ContentSavedState? State { get; set; } - /// - /// The saved/published state of an item - /// - /// - /// This is nullable since it's only relevant for content (non-content like media + members will be null) - /// - [DataMember(Name = "state")] - public ContentSavedState? State { get; set; } + [DataMember(Name = "variesByCulture")] public bool VariesByCulture { get; set; } - [DataMember(Name = "variesByCulture")] - public bool VariesByCulture { get; set; } + protected bool Equals(ContentItemBasic other) => Id == other.Id; - protected bool Equals(ContentItemBasic other) + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) { - return Id == other.Id; + return false; } - public override bool Equals(object? obj) + if (ReferenceEquals(this, obj)) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - var other = obj as ContentItemBasic; - return other != null && Equals(other); + return true; } - public override int GetHashCode() - { - if (Id is not null) - { - return Id.GetHashCode(); - } - - return base.GetHashCode(); - } + var other = obj as ContentItemBasic; + return other != null && Equals(other); } - /// - /// A model representing a basic content item with properties - /// - [DataContract(Name = "content", Namespace = "")] - public class ContentItemBasic : ContentItemBasic, IContentProperties - where T : ContentPropertyBasic + public override int GetHashCode() { - public ContentItemBasic() + if (Id is not null) { - //ensure its not null - _properties = Enumerable.Empty(); + return Id.GetHashCode(); } - private IEnumerable _properties; + return base.GetHashCode(); + } +} - [DataMember(Name = "properties")] - public virtual IEnumerable Properties - { - get => _properties; - set => _properties = value; - } +/// +/// A model representing a basic content item with properties +/// +[DataContract(Name = "content", Namespace = "")] +public class ContentItemBasic : ContentItemBasic, IContentProperties + where T : ContentPropertyBasic +{ + private IEnumerable _properties; + + public ContentItemBasic() => + //ensure its not null + _properties = Enumerable.Empty(); + + [DataMember(Name = "properties")] + public virtual IEnumerable Properties + { + get => _properties; + set => _properties = value; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs index 85968222e736..36e11eb5419a 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs @@ -1,219 +1,213 @@ -using System; -using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Cms.Core.Routing; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +public class ContentItemDisplay : ContentItemDisplay { - public class ContentItemDisplay : ContentItemDisplay { } +} - public class ContentItemDisplayWithSchedule : ContentItemDisplay { } +public class ContentItemDisplayWithSchedule : ContentItemDisplay +{ +} - /// - /// A model representing a content item to be displayed in the back office - /// - [DataContract(Name = "content", Namespace = "")] - public class ContentItemDisplay : INotificationModel, IErrorModel //ListViewAwareContentItemDisplayBase - where TVariant : ContentVariantDisplay +/// +/// A model representing a content item to be displayed in the back office +/// +[DataContract(Name = "content", Namespace = "")] +public class + ContentItemDisplay : INotificationModel, + IErrorModel //ListViewAwareContentItemDisplayBase + where TVariant : ContentVariantDisplay +{ + public ContentItemDisplay() { - public ContentItemDisplay() - { - AllowPreview = true; - Notifications = new List(); - Errors = new Dictionary(); - Variants = new List(); - ContentApps = new List(); - } - - [DataMember(Name = "id", IsRequired = true)] - [Required] - public int Id { get; set; } - - [DataMember(Name = "udi")] - [ReadOnly(true)] - public Udi? Udi { get; set; } - - [DataMember(Name = "icon")] - public string? Icon { get; set; } - - [DataMember(Name = "trashed")] - [ReadOnly(true)] - public bool Trashed { get; set; } - - /// - /// This is the unique Id stored in the database - but could also be the unique id for a custom membership provider - /// - [DataMember(Name = "key")] - public Guid? Key { get; set; } - - [DataMember(Name = "parentId", IsRequired = true)] - [Required] - public int? ParentId { get; set; } - - /// - /// The path of the entity - /// - [DataMember(Name = "path")] - public string? Path { get; set; } - - /// - /// A collection of content variants - /// - /// - /// If a content item is invariant, this collection will only contain one item, else it will contain all culture variants - /// - [DataMember(Name = "variants")] - public IEnumerable? Variants { get; set; } - - [DataMember(Name = "owner")] - public UserProfile? Owner { get; set; } - - [DataMember(Name = "updater")] - public UserProfile? Updater { get; set; } - - /// - /// The name of the content type - /// - [DataMember(Name = "contentTypeName")] - public string? ContentTypeName { get; set; } - - /// - /// Indicates if the content is configured as a list view container - /// - [DataMember(Name = "isContainer")] - public bool IsContainer { get; set; } - - /// - /// Indicates if the content is configured as an element - /// - [DataMember(Name = "isElement")] - public bool IsElement { get; set; } - - /// - /// Property indicating if this item is part of a list view parent - /// - [DataMember(Name = "isChildOfListView")] - public bool IsChildOfListView { get; set; } - - /// - /// Property for the entity's individual tree node URL - /// - /// - /// This is required if the item is a child of a list view since the tree won't actually be loaded, - /// so the app will need to go fetch the individual tree node in order to be able to load it's action list (menu) - /// - [DataMember(Name = "treeNodeUrl")] - public string? TreeNodeUrl { get; set; } - - [DataMember(Name = "contentTypeId")] - public int? ContentTypeId { get; set; } - - [DataMember(Name = "contentTypeKey")] - public Guid ContentTypeKey { get; set; } - - [DataMember(Name = "contentTypeAlias", IsRequired = true)] - [Required(AllowEmptyStrings = false)] - public string ContentTypeAlias { get; set; } = null!; - - [DataMember(Name = "sortOrder")] - public int SortOrder { get; set; } - - /// - /// This is the last updated date for the entire content object regardless of variants - /// - /// - /// Each variant has it's own update date assigned as well - /// - [DataMember(Name = "updateDate")] - public DateTime UpdateDate { get; set; } - - [DataMember(Name = "template")] - public string? TemplateAlias { get; set; } - - [DataMember(Name = "templateId")] - public int TemplateId { get; set; } - - [DataMember(Name = "allowedTemplates")] - public IDictionary? AllowedTemplates { get; set; } - - [DataMember(Name = "documentType")] - public ContentTypeBasic? DocumentType { get; set; } - - [DataMember(Name = "urls")] - public UrlInfo[]? Urls { get; set; } - - /// - /// Determines whether previewing is allowed for this node - /// - /// - /// By default this is true but by using events developers can toggle this off for certain documents if there is nothing to preview - /// - [DataMember(Name = "allowPreview")] - public bool AllowPreview { get; set; } - - /// - /// The allowed 'actions' based on the user's permissions - Create, Update, Publish, Send to publish - /// - /// - /// Each char represents a button which we can then map on the front-end to the correct actions - /// - [DataMember(Name = "allowedActions")] - public IEnumerable? AllowedActions { get; set; } - - [DataMember(Name = "isBlueprint")] - public bool IsBlueprint { get; set; } - - [DataMember(Name = "apps")] - public IEnumerable ContentApps { get; set; } - - /// - /// The real persisted content object - used during inbound model binding - /// - /// - /// This is not used for outgoing model information. - /// - [IgnoreDataMember] - public IContent? PersistedContent { get; set; } - - /// - /// The DTO object used to gather all required content data including data type information etc... for use with validation - used during inbound model binding - /// - /// - /// We basically use this object to hydrate all required data from the database into one object so we can validate everything we need - /// instead of having to look up all the data individually. - /// This is not used for outgoing model information. - /// - [IgnoreDataMember] - public ContentPropertyCollectionDto? ContentDto { get; set; } - - /// - /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. - /// - [DataMember(Name = "notifications")] - [ReadOnly(true)] - public List Notifications { get; private set; } - - /// - /// This is used for validation of a content item. - /// - /// - /// A content item can be invalid but still be saved. This occurs when there's property validation errors, we will - /// still save the item but it cannot be published. So we need a way of returning validation errors as well as the - /// updated model. - /// - /// NOTE: The ProperCase is important because when we return ModeState normally it will always be proper case. - /// - [DataMember(Name = "ModelState")] - [ReadOnly(true)] - public IDictionary Errors { get; set; } - - /// - /// A collection of extra data that is available for this specific entity/entity type - /// - [DataMember(Name = "metaData")] - [ReadOnly(true)] - public IDictionary? AdditionalData { get; private set; } + AllowPreview = true; + Notifications = new List(); + Errors = new Dictionary(); + Variants = new List(); + ContentApps = new List(); } + + [DataMember(Name = "id", IsRequired = true)] + [Required] + public int Id { get; set; } + + [DataMember(Name = "udi")] + [ReadOnly(true)] + public Udi? Udi { get; set; } + + [DataMember(Name = "icon")] public string? Icon { get; set; } + + [DataMember(Name = "trashed")] + [ReadOnly(true)] + public bool Trashed { get; set; } + + /// + /// This is the unique Id stored in the database - but could also be the unique id for a custom membership provider + /// + [DataMember(Name = "key")] + public Guid? Key { get; set; } + + [DataMember(Name = "parentId", IsRequired = true)] + [Required] + public int? ParentId { get; set; } + + /// + /// The path of the entity + /// + [DataMember(Name = "path")] + public string? Path { get; set; } + + /// + /// A collection of content variants + /// + /// + /// If a content item is invariant, this collection will only contain one item, else it will contain all culture + /// variants + /// + [DataMember(Name = "variants")] + public IEnumerable? Variants { get; set; } + + [DataMember(Name = "owner")] public UserProfile? Owner { get; set; } + + [DataMember(Name = "updater")] public UserProfile? Updater { get; set; } + + /// + /// The name of the content type + /// + [DataMember(Name = "contentTypeName")] + public string? ContentTypeName { get; set; } + + /// + /// Indicates if the content is configured as a list view container + /// + [DataMember(Name = "isContainer")] + public bool IsContainer { get; set; } + + /// + /// Indicates if the content is configured as an element + /// + [DataMember(Name = "isElement")] + public bool IsElement { get; set; } + + /// + /// Property indicating if this item is part of a list view parent + /// + [DataMember(Name = "isChildOfListView")] + public bool IsChildOfListView { get; set; } + + /// + /// Property for the entity's individual tree node URL + /// + /// + /// This is required if the item is a child of a list view since the tree won't actually be loaded, + /// so the app will need to go fetch the individual tree node in order to be able to load it's action list (menu) + /// + [DataMember(Name = "treeNodeUrl")] + public string? TreeNodeUrl { get; set; } + + [DataMember(Name = "contentTypeId")] public int? ContentTypeId { get; set; } + + [DataMember(Name = "contentTypeKey")] public Guid ContentTypeKey { get; set; } + + [DataMember(Name = "contentTypeAlias", IsRequired = true)] + [Required(AllowEmptyStrings = false)] + public string ContentTypeAlias { get; set; } = null!; + + [DataMember(Name = "sortOrder")] public int SortOrder { get; set; } + + /// + /// This is the last updated date for the entire content object regardless of variants + /// + /// + /// Each variant has it's own update date assigned as well + /// + [DataMember(Name = "updateDate")] + public DateTime UpdateDate { get; set; } + + [DataMember(Name = "template")] public string? TemplateAlias { get; set; } + + [DataMember(Name = "templateId")] public int TemplateId { get; set; } + + [DataMember(Name = "allowedTemplates")] + public IDictionary? AllowedTemplates { get; set; } + + [DataMember(Name = "documentType")] public ContentTypeBasic? DocumentType { get; set; } + + [DataMember(Name = "urls")] public UrlInfo[]? Urls { get; set; } + + /// + /// Determines whether previewing is allowed for this node + /// + /// + /// By default this is true but by using events developers can toggle this off for certain documents if there is + /// nothing to preview + /// + [DataMember(Name = "allowPreview")] + public bool AllowPreview { get; set; } + + /// + /// The allowed 'actions' based on the user's permissions - Create, Update, Publish, Send to publish + /// + /// + /// Each char represents a button which we can then map on the front-end to the correct actions + /// + [DataMember(Name = "allowedActions")] + public IEnumerable? AllowedActions { get; set; } + + [DataMember(Name = "isBlueprint")] public bool IsBlueprint { get; set; } + + [DataMember(Name = "apps")] public IEnumerable ContentApps { get; set; } + + /// + /// The real persisted content object - used during inbound model binding + /// + /// + /// This is not used for outgoing model information. + /// + [IgnoreDataMember] + public IContent? PersistedContent { get; set; } + + /// + /// The DTO object used to gather all required content data including data type information etc... for use with + /// validation - used during inbound model binding + /// + /// + /// We basically use this object to hydrate all required data from the database into one object so we can validate + /// everything we need + /// instead of having to look up all the data individually. + /// This is not used for outgoing model information. + /// + [IgnoreDataMember] + public ContentPropertyCollectionDto? ContentDto { get; set; } + + /// + /// A collection of extra data that is available for this specific entity/entity type + /// + [DataMember(Name = "metaData")] + [ReadOnly(true)] + public IDictionary? AdditionalData { get; private set; } + + /// + /// This is used for validation of a content item. + /// + /// + /// A content item can be invalid but still be saved. This occurs when there's property validation errors, we will + /// still save the item but it cannot be published. So we need a way of returning validation errors as well as the + /// updated model. + /// NOTE: The ProperCase is important because when we return ModeState normally it will always be proper case. + /// + [DataMember(Name = "ModelState")] + [ReadOnly(true)] + public IDictionary Errors { get; set; } + + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + [ReadOnly(true)] + public List Notifications { get; private set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplayBase.cs b/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplayBase.cs index a06fa62b1a8f..909e58f550a7 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplayBase.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplayBase.cs @@ -1,49 +1,46 @@ -using System.Collections.Generic; -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +public abstract class ContentItemDisplayBase : TabbedContentItem, INotificationModel, IErrorModel + where T : ContentPropertyBasic { - public abstract class ContentItemDisplayBase : TabbedContentItem, INotificationModel, IErrorModel - where T : ContentPropertyBasic + protected ContentItemDisplayBase() { - protected ContentItemDisplayBase() - { - Notifications = new List(); - Errors = new Dictionary(); - } + Notifications = new List(); + Errors = new Dictionary(); + } - /// - /// The name of the content type - /// - [DataMember(Name = "contentTypeName")] - public string? ContentTypeName { get; set; } + /// + /// The name of the content type + /// + [DataMember(Name = "contentTypeName")] + public string? ContentTypeName { get; set; } - /// - /// Indicates if the content is configured as a list view container - /// - [DataMember(Name = "isContainer")] - public bool IsContainer { get; set; } + /// + /// Indicates if the content is configured as a list view container + /// + [DataMember(Name = "isContainer")] + public bool IsContainer { get; set; } - /// - /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. - /// - [DataMember(Name = "notifications")] - [ReadOnly(true)] - public List Notifications { get; private set; } + /// + /// This is used for validation of a content item. + /// + /// + /// A content item can be invalid but still be saved. This occurs when there's property validation errors, we will + /// still save the item but it cannot be published. So we need a way of returning validation errors as well as the + /// updated model. + /// NOTE: The ProperCase is important because when we return ModeState normally it will always be proper case. + /// + [DataMember(Name = "ModelState")] + [ReadOnly(true)] + public IDictionary Errors { get; set; } - /// - /// This is used for validation of a content item. - /// - /// - /// A content item can be invalid but still be saved. This occurs when there's property validation errors, we will - /// still save the item but it cannot be published. So we need a way of returning validation errors as well as the - /// updated model. - /// - /// NOTE: The ProperCase is important because when we return ModeState normally it will always be proper case. - /// - [DataMember(Name = "ModelState")] - [ReadOnly(true)] - public IDictionary Errors { get; set; } - } + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + [ReadOnly(true)] + public List Notifications { get; private set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentItemSave.cs b/src/Umbraco.Core/Models/ContentEditing/ContentItemSave.cs index fed33c52b08d..deb807a598c8 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentItemSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentItemSave.cs @@ -1,66 +1,62 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Editors; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// A model representing a content item to be saved +/// +[DataContract(Name = "content", Namespace = "")] +public class ContentItemSave : IContentSave { - /// - /// A model representing a content item to be saved - /// - [DataContract(Name = "content", Namespace = "")] - public class ContentItemSave : IContentSave + public ContentItemSave() { - public ContentItemSave() - { - UploadedFiles = new List(); - Variants = new List(); - } - - [DataMember(Name = "id", IsRequired = true)] - [Required] - public int Id { get; set; } - - [DataMember(Name = "parentId", IsRequired = true)] - [Required] - public int ParentId { get; set; } + UploadedFiles = new List(); + Variants = new List(); + } - [DataMember(Name = "variants", IsRequired = true)] - public IEnumerable Variants { get; set; } + [DataMember(Name = "id", IsRequired = true)] + [Required] + public int Id { get; set; } - [DataMember(Name = "contentTypeAlias", IsRequired = true)] - [Required(AllowEmptyStrings = false)] - public string ContentTypeAlias { get; set; } = null!; + [DataMember(Name = "parentId", IsRequired = true)] + [Required] + public int ParentId { get; set; } - /// - /// The template alias to save - /// - [DataMember(Name = "templateAlias")] - public string? TemplateAlias { get; set; } + [DataMember(Name = "variants", IsRequired = true)] + public IEnumerable Variants { get; set; } - #region IContentSave + [DataMember(Name = "contentTypeAlias", IsRequired = true)] + [Required(AllowEmptyStrings = false)] + public string ContentTypeAlias { get; set; } = null!; - [DataMember(Name = "action", IsRequired = true)] - [Required] - public ContentSaveAction Action { get; set; } + /// + /// The template alias to save + /// + [DataMember(Name = "templateAlias")] + public string? TemplateAlias { get; set; } - [IgnoreDataMember] - public List UploadedFiles { get; } + #region IContentSave - //These need explicit implementation because we are using internal models - /// - [IgnoreDataMember] - IContent IContentSave.PersistedContent { get; set; } = null!; + [DataMember(Name = "action", IsRequired = true)] + [Required] + public ContentSaveAction Action { get; set; } - //Non explicit internal getter so we don't need to explicitly cast in our own code - [IgnoreDataMember] - public IContent PersistedContent - { - get => ((IContentSave)this).PersistedContent; - set => ((IContentSave)this).PersistedContent = value; - } + [IgnoreDataMember] public List UploadedFiles { get; } - #endregion + //These need explicit implementation because we are using internal models + /// + [IgnoreDataMember] + IContent IContentSave.PersistedContent { get; set; } = null!; + //Non explicit internal getter so we don't need to explicitly cast in our own code + [IgnoreDataMember] + public IContent PersistedContent + { + get => ((IContentSave)this).PersistedContent; + set => ((IContentSave)this).PersistedContent = value; } + + #endregion } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyBasic.cs b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyBasic.cs index ee5a4600d4df..27fe09f877dc 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyBasic.cs @@ -1,73 +1,70 @@ -using System; -using System.ComponentModel; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Represents a content property to be saved +/// +[DataContract(Name = "property", Namespace = "")] +public class ContentPropertyBasic { /// - /// Represents a content property to be saved + /// This is the PropertyData ID /// - [DataContract(Name = "property", Namespace = "")] - public class ContentPropertyBasic - { - /// - /// This is the PropertyData ID - /// - /// - /// This is not really used for anything - /// - [DataMember(Name = "id", IsRequired = true)] - [Required] - public int Id { get; set; } + /// + /// This is not really used for anything + /// + [DataMember(Name = "id", IsRequired = true)] + [Required] + public int Id { get; set; } - [DataMember(Name = "dataTypeKey", IsRequired = false)] - [ReadOnly(true)] - public Guid DataTypeKey { get; set; } + [DataMember(Name = "dataTypeKey", IsRequired = false)] + [ReadOnly(true)] + public Guid DataTypeKey { get; set; } - [DataMember(Name = "value")] - public object? Value { get; set; } + [DataMember(Name = "value")] public object? Value { get; set; } - [DataMember(Name = "alias", IsRequired = true)] - [Required(AllowEmptyStrings = false)] - public string Alias { get; set; } = null!; + [DataMember(Name = "alias", IsRequired = true)] + [Required(AllowEmptyStrings = false)] + public string Alias { get; set; } = null!; - [DataMember(Name = "editor", IsRequired = false)] - public string? Editor { get; set; } + [DataMember(Name = "editor", IsRequired = false)] + public string? Editor { get; set; } - /// - /// Flags the property to denote that it can contain sensitive data - /// - [DataMember(Name = "isSensitive", IsRequired = false)] - public bool IsSensitive { get; set; } + /// + /// Flags the property to denote that it can contain sensitive data + /// + [DataMember(Name = "isSensitive", IsRequired = false)] + public bool IsSensitive { get; set; } - /// - /// The culture of the property - /// - /// - /// If this is a variant property then this culture value will be the same as it's variant culture but if this - /// is an invariant property then this will be a null value. - /// - [DataMember(Name = "culture")] - [ReadOnly(true)] - public string? Culture { get; set; } + /// + /// The culture of the property + /// + /// + /// If this is a variant property then this culture value will be the same as it's variant culture but if this + /// is an invariant property then this will be a null value. + /// + [DataMember(Name = "culture")] + [ReadOnly(true)] + public string? Culture { get; set; } - /// - /// The segment of the property - /// - /// - /// The segment value of a property can always be null but can only have a non-null value - /// when the property can be varied by segment. - /// - [DataMember(Name = "segment")] - [ReadOnly(true)] - public string? Segment { get; set; } + /// + /// The segment of the property + /// + /// + /// The segment value of a property can always be null but can only have a non-null value + /// when the property can be varied by segment. + /// + [DataMember(Name = "segment")] + [ReadOnly(true)] + public string? Segment { get; set; } - /// - /// Used internally during model mapping - /// - [IgnoreDataMember] - public IDataEditor? PropertyEditor { get; set; } - } + /// + /// Used internally during model mapping + /// + [IgnoreDataMember] + public IDataEditor? PropertyEditor { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyCollectionDto.cs b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyCollectionDto.cs index 3c772c08663f..a66c9ba4fb1c 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyCollectionDto.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyCollectionDto.cs @@ -1,21 +1,14 @@ -using System.Collections.Generic; -using System.Linq; +namespace Umbraco.Cms.Core.Models.ContentEditing; -namespace Umbraco.Cms.Core.Models.ContentEditing +/// +/// Used to map property values when saving content/media/members +/// +/// +/// This is only used during mapping operations, it is not used for angular purposes +/// +public class ContentPropertyCollectionDto : IContentProperties { - /// - /// Used to map property values when saving content/media/members - /// - /// - /// This is only used during mapping operations, it is not used for angular purposes - /// - public class ContentPropertyCollectionDto : IContentProperties - { - public ContentPropertyCollectionDto() - { - Properties = Enumerable.Empty(); - } + public ContentPropertyCollectionDto() => Properties = Enumerable.Empty(); - public IEnumerable Properties { get; set; } - } + public IEnumerable Properties { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDisplay.cs index b31caaa901eb..923d7c2cd385 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDisplay.cs @@ -1,45 +1,37 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Represents a content property that is displayed in the UI +/// +[DataContract(Name = "property", Namespace = "")] +public class ContentPropertyDisplay : ContentPropertyBasic { - /// - /// Represents a content property that is displayed in the UI - /// - [DataContract(Name = "property", Namespace = "")] - public class ContentPropertyDisplay : ContentPropertyBasic + public ContentPropertyDisplay() { - public ContentPropertyDisplay() - { - Config = new Dictionary(); - Validation = new PropertyTypeValidation(); - } + Config = new Dictionary(); + Validation = new PropertyTypeValidation(); + } - [DataMember(Name = "label", IsRequired = true)] - [Required] - public string? Label { get; set; } + [DataMember(Name = "label", IsRequired = true)] + [Required] + public string? Label { get; set; } - [DataMember(Name = "description")] - public string? Description { get; set; } + [DataMember(Name = "description")] public string? Description { get; set; } - [DataMember(Name = "view", IsRequired = true)] - [Required(AllowEmptyStrings = false)] - public string? View { get; set; } + [DataMember(Name = "view", IsRequired = true)] + [Required(AllowEmptyStrings = false)] + public string? View { get; set; } - [DataMember(Name = "config")] - public IDictionary? Config { get; set; } + [DataMember(Name = "config")] public IDictionary? Config { get; set; } - [DataMember(Name = "hideLabel")] - public bool HideLabel { get; set; } + [DataMember(Name = "hideLabel")] public bool HideLabel { get; set; } - [DataMember(Name = "labelOnTop")] - public bool? LabelOnTop { get; set; } + [DataMember(Name = "labelOnTop")] public bool? LabelOnTop { get; set; } - [DataMember(Name = "validation")] - public PropertyTypeValidation Validation { get; set; } + [DataMember(Name = "validation")] public PropertyTypeValidation Validation { get; set; } - [DataMember(Name = "readonly")] - public bool Readonly { get; set; } - } + [DataMember(Name = "readonly")] public bool Readonly { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDto.cs b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDto.cs index d40a25805e9c..c2972ce8b800 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDto.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDto.cs @@ -1,24 +1,23 @@ -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Represents a content property from the database +/// +public class ContentPropertyDto : ContentPropertyBasic { - /// - /// Represents a content property from the database - /// - public class ContentPropertyDto : ContentPropertyBasic - { - public IDataType? DataType { get; set; } + public IDataType? DataType { get; set; } - public string? Label { get; set; } + public string? Label { get; set; } - public string? Description { get; set; } + public string? Description { get; set; } - public bool? IsRequired { get; set; } + public bool? IsRequired { get; set; } - public bool? LabelOnTop { get; set; } + public bool? LabelOnTop { get; set; } - public string? IsRequiredMessage { get; set; } + public string? IsRequiredMessage { get; set; } - public string? ValidationRegExp { get; set; } + public string? ValidationRegExp { get; set; } - public string? ValidationRegExpMessage { get; set; } - } + public string? ValidationRegExpMessage { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentRedirectUrl.cs b/src/Umbraco.Core/Models/ContentEditing/ContentRedirectUrl.cs index 99ea69b364d1..8b7c7263616e 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentRedirectUrl.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentRedirectUrl.cs @@ -1,27 +1,19 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "contentRedirectUrl", Namespace = "")] +public class ContentRedirectUrl { - [DataContract(Name = "contentRedirectUrl", Namespace = "")] - public class ContentRedirectUrl - { - [DataMember(Name = "redirectId")] - public Guid RedirectId { get; set; } + [DataMember(Name = "redirectId")] public Guid RedirectId { get; set; } - [DataMember(Name = "originalUrl")] - public string? OriginalUrl { get; set; } + [DataMember(Name = "originalUrl")] public string? OriginalUrl { get; set; } - [DataMember(Name = "destinationUrl")] - public string? DestinationUrl { get; set; } + [DataMember(Name = "destinationUrl")] public string? DestinationUrl { get; set; } - [DataMember(Name = "createDateUtc")] - public DateTime CreateDateUtc { get; set; } + [DataMember(Name = "createDateUtc")] public DateTime CreateDateUtc { get; set; } - [DataMember(Name = "contentId")] - public int ContentId { get; set; } + [DataMember(Name = "contentId")] public int ContentId { get; set; } - [DataMember(Name = "culture")] - public string? Culture { get; set; } - } + [DataMember(Name = "culture")] public string? Culture { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentSaveAction.cs b/src/Umbraco.Core/Models/ContentEditing/ContentSaveAction.cs index 3beb9705644d..064e6385f396 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentSaveAction.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentSaveAction.cs @@ -1,68 +1,69 @@ -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// The action associated with saving a content item +/// +public enum ContentSaveAction { /// - /// The action associated with saving a content item + /// Saves the content item, no publish /// - public enum ContentSaveAction - { - /// - /// Saves the content item, no publish - /// - Save = 0, + Save = 0, - /// - /// Creates a new content item - /// - SaveNew = 1, + /// + /// Creates a new content item + /// + SaveNew = 1, - /// - /// Saves and publishes the content item - /// - Publish = 2, + /// + /// Saves and publishes the content item + /// + Publish = 2, - /// - /// Creates and publishes a new content item - /// - PublishNew = 3, + /// + /// Creates and publishes a new content item + /// + PublishNew = 3, - /// - /// Saves and sends publish notification - /// - SendPublish = 4, + /// + /// Saves and sends publish notification + /// + SendPublish = 4, - /// - /// Creates and sends publish notification - /// - SendPublishNew = 5, + /// + /// Creates and sends publish notification + /// + SendPublishNew = 5, - /// - /// Saves and schedules publishing - /// - Schedule = 6, + /// + /// Saves and schedules publishing + /// + Schedule = 6, - /// - /// Creates and schedules publishing - /// - ScheduleNew = 7, + /// + /// Creates and schedules publishing + /// + ScheduleNew = 7, - /// - /// Saves and publishes the content item including all descendants that have a published version - /// - PublishWithDescendants = 8, + /// + /// Saves and publishes the content item including all descendants that have a published version + /// + PublishWithDescendants = 8, - /// - /// Creates and publishes the content item including all descendants that have a published version - /// - PublishWithDescendantsNew = 9, + /// + /// Creates and publishes the content item including all descendants that have a published version + /// + PublishWithDescendantsNew = 9, - /// - /// Saves and publishes the content item including all descendants regardless of whether they have a published version or not - /// - PublishWithDescendantsForce = 10, + /// + /// Saves and publishes the content item including all descendants regardless of whether they have a published version + /// or not + /// + PublishWithDescendantsForce = 10, - /// - /// Creates and publishes the content item including all descendants regardless of whether they have a published version or not - /// - PublishWithDescendantsForceNew = 11 - } + /// + /// Creates and publishes the content item including all descendants regardless of whether they have a published + /// version or not + /// + PublishWithDescendantsForceNew = 11 } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentSavedState.cs b/src/Umbraco.Core/Models/ContentEditing/ContentSavedState.cs index 00fce177b4b5..bf1958d77b05 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentSavedState.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentSavedState.cs @@ -1,28 +1,27 @@ -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// The saved state of a content item +/// +public enum ContentSavedState { /// - /// The saved state of a content item + /// The item isn't created yet /// - public enum ContentSavedState - { - /// - /// The item isn't created yet - /// - NotCreated = 1, + NotCreated = 1, - /// - /// The item is saved but isn't published - /// - Draft = 2, + /// + /// The item is saved but isn't published + /// + Draft = 2, - /// - /// The item is published and there are no pending changes - /// - Published = 3, + /// + /// The item is published and there are no pending changes + /// + Published = 3, - /// - /// The item is published and there are pending changes - /// - PublishedPendingChanges = 4 - } + /// + /// The item is published and there are pending changes + /// + PublishedPendingChanges = 4 } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentSortOrder.cs b/src/Umbraco.Core/Models/ContentEditing/ContentSortOrder.cs index 80c24b8cc0e9..f17d457486e1 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentSortOrder.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentSortOrder.cs @@ -1,31 +1,28 @@ using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// A model representing a new sort order for a content/media item +/// +[DataContract(Name = "content", Namespace = "")] +public class ContentSortOrder { /// - /// A model representing a new sort order for a content/media item + /// The parent Id of the nodes being sorted /// - [DataContract(Name = "content", Namespace = "")] - public class ContentSortOrder - { - /// - /// The parent Id of the nodes being sorted - /// - [DataMember(Name = "parentId", IsRequired = true)] - [Required] - public int ParentId { get; set; } - - /// - /// An array of integer Ids representing the sort order - /// - /// - /// Of course all of these Ids should be at the same level in the hierarchy!! - /// - [DataMember(Name = "idSortOrder", IsRequired = true)] - [Required] - public int[]? IdSortOrder { get; set; } - - } + [DataMember(Name = "parentId", IsRequired = true)] + [Required] + public int ParentId { get; set; } + /// + /// An array of integer Ids representing the sort order + /// + /// + /// Of course all of these Ids should be at the same level in the hierarchy!! + /// + [DataMember(Name = "idSortOrder", IsRequired = true)] + [Required] + public int[]? IdSortOrder { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentTypeBasic.cs b/src/Umbraco.Core/Models/ContentEditing/ContentTypeBasic.cs index f46d8dc8f802..7b4015946c16 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentTypeBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentTypeBasic.cs @@ -1,109 +1,106 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// A basic version of a content type +/// +/// +/// Generally used to return the minimal amount of data about a content type +/// +[DataContract(Name = "contentType", Namespace = "")] +public class ContentTypeBasic : EntityBasic { + public ContentTypeBasic() + { + Blueprints = new Dictionary(); + Alias = string.Empty; + } + /// - /// A basic version of a content type + /// Overridden to apply our own validation attributes since this is not always required for other classes /// - /// - /// Generally used to return the minimal amount of data about a content type - /// - [DataContract(Name = "contentType", Namespace = "")] - public class ContentTypeBasic : EntityBasic - { - public ContentTypeBasic() - { - Blueprints = new Dictionary(); - Alias = string.Empty; - } + [Required] + [RegularExpression(@"^([a-zA-Z]\w.*)$", ErrorMessage = "Invalid alias")] + [DataMember(Name = "alias")] + public override string Alias { get; set; } - /// - /// Overridden to apply our own validation attributes since this is not always required for other classes - /// - [Required] - [RegularExpression(@"^([a-zA-Z]\w.*)$", ErrorMessage = "Invalid alias")] - [DataMember(Name = "alias")] - public override string Alias { get; set; } - - [DataMember(Name = "updateDate")] - [ReadOnly(true)] - public DateTime UpdateDate { get; set; } - - [DataMember(Name = "createDate")] - [ReadOnly(true)] - public DateTime CreateDate { get; set; } - - [DataMember(Name = "description")] - public string? Description { get; set; } - - [DataMember(Name = "thumbnail")] - public string? Thumbnail { get; set; } - - /// - /// Returns true if the icon represents a CSS class instead of a file path - /// - [DataMember(Name = "iconIsClass")] - [ReadOnly(true)] - public bool IconIsClass + [DataMember(Name = "updateDate")] + [ReadOnly(true)] + public DateTime UpdateDate { get; set; } + + [DataMember(Name = "createDate")] + [ReadOnly(true)] + public DateTime CreateDate { get; set; } + + [DataMember(Name = "description")] public string? Description { get; set; } + + [DataMember(Name = "thumbnail")] public string? Thumbnail { get; set; } + + /// + /// Returns true if the icon represents a CSS class instead of a file path + /// + [DataMember(Name = "iconIsClass")] + [ReadOnly(true)] + public bool IconIsClass + { + get { - get + if (Icon.IsNullOrWhiteSpace()) { - if (Icon.IsNullOrWhiteSpace()) - { - return true; - } - //if it starts with a '.' or doesn't contain a '.' at all then it is a class - return (Icon?.StartsWith(".") ?? false) || Icon?.Contains(".") == false; + return true; } + + //if it starts with a '.' or doesn't contain a '.' at all then it is a class + return (Icon?.StartsWith(".") ?? false) || Icon?.Contains(".") == false; } + } - /// - /// Returns the icon file path if the icon is not a class, otherwise returns an empty string - /// - [DataMember(Name = "iconFilePath")] - [ReadOnly(true)] - public string? IconFilePath { get; set; } - - /// - /// Returns true if the icon represents a CSS class instead of a file path - /// - [DataMember(Name = "thumbnailIsClass")] - [ReadOnly(true)] - public bool ThumbnailIsClass + /// + /// Returns the icon file path if the icon is not a class, otherwise returns an empty string + /// + [DataMember(Name = "iconFilePath")] + [ReadOnly(true)] + public string? IconFilePath { get; set; } + + /// + /// Returns true if the icon represents a CSS class instead of a file path + /// + [DataMember(Name = "thumbnailIsClass")] + [ReadOnly(true)] + public bool ThumbnailIsClass + { + get { - get + if (Thumbnail.IsNullOrWhiteSpace()) { - if (Thumbnail.IsNullOrWhiteSpace()) - { - return true; - } - //if it starts with a '.' or doesn't contain a '.' at all then it is a class - return (Thumbnail?.StartsWith(".") ?? false) || Thumbnail?.Contains(".") == false; + return true; } + + //if it starts with a '.' or doesn't contain a '.' at all then it is a class + return (Thumbnail?.StartsWith(".") ?? false) || Thumbnail?.Contains(".") == false; } + } - /// - /// Returns the icon file path if the icon is not a class, otherwise returns an empty string - /// - [DataMember(Name = "thumbnailFilePath")] - [ReadOnly(true)] - public string? ThumbnailFilePath { get; set; } + /// + /// Returns the icon file path if the icon is not a class, otherwise returns an empty string + /// + [DataMember(Name = "thumbnailFilePath")] + [ReadOnly(true)] + public string? ThumbnailFilePath { get; set; } - [DataMember(Name = "blueprints")] - [ReadOnly(true)] - public IDictionary Blueprints { get; set; } + [DataMember(Name = "blueprints")] + [ReadOnly(true)] + public IDictionary Blueprints { get; set; } - [DataMember(Name = "isContainer")] - [ReadOnly(true)] - public bool IsContainer { get; set; } + [DataMember(Name = "isContainer")] + [ReadOnly(true)] + public bool IsContainer { get; set; } - [DataMember(Name = "isElement")] - [ReadOnly(true)] - public bool IsElement { get; set; } - } + [DataMember(Name = "isElement")] + [ReadOnly(true)] + public bool IsElement { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentTypeCompositionDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/ContentTypeCompositionDisplay.cs index 4eb8563a6e9e..29d23c05656b 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentTypeCompositionDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentTypeCompositionDisplay.cs @@ -1,74 +1,67 @@ -using System.Collections.Generic; -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +public abstract class ContentTypeCompositionDisplay : ContentTypeBasic, INotificationModel { - public abstract class ContentTypeCompositionDisplay : ContentTypeBasic, INotificationModel + protected ContentTypeCompositionDisplay() { - protected ContentTypeCompositionDisplay() - { - //initialize collections so at least their never null - AllowedContentTypes = new List(); - CompositeContentTypes = new List(); - Notifications = new List(); - } + //initialize collections so at least their never null + AllowedContentTypes = new List(); + CompositeContentTypes = new List(); + Notifications = new List(); + } - //name, alias, icon, thumb, desc, inherited from basic + //name, alias, icon, thumb, desc, inherited from basic - [DataMember(Name = "listViewEditorName")] - [ReadOnly(true)] - public string? ListViewEditorName { get; set; } + [DataMember(Name = "listViewEditorName")] + [ReadOnly(true)] + public string? ListViewEditorName { get; set; } - //Allowed child types - [DataMember(Name = "allowedContentTypes")] - public IEnumerable? AllowedContentTypes { get; set; } + //Allowed child types + [DataMember(Name = "allowedContentTypes")] + public IEnumerable? AllowedContentTypes { get; set; } - //Compositions - [DataMember(Name = "compositeContentTypes")] - public IEnumerable CompositeContentTypes { get; set; } + //Compositions + [DataMember(Name = "compositeContentTypes")] + public IEnumerable CompositeContentTypes { get; set; } - //Locked compositions - [DataMember(Name = "lockedCompositeContentTypes")] - public IEnumerable? LockedCompositeContentTypes { get; set; } + //Locked compositions + [DataMember(Name = "lockedCompositeContentTypes")] + public IEnumerable? LockedCompositeContentTypes { get; set; } - [DataMember(Name = "allowAsRoot")] - public bool AllowAsRoot { get; set; } + [DataMember(Name = "allowAsRoot")] public bool AllowAsRoot { get; set; } - /// - /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. - /// - [DataMember(Name = "notifications")] - [ReadOnly(true)] - public List Notifications { get; private set; } + /// + /// This is used for validation of a content item. + /// + /// + /// A content item can be invalid but still be saved. This occurs when there's property validation errors, we will + /// still save the item but it cannot be published. So we need a way of returning validation errors as well as the + /// updated model. + /// NOTE: The ProperCase is important because when we return ModeState normally it will always be proper case. + /// + [DataMember(Name = "ModelState")] + [ReadOnly(true)] + public IDictionary? Errors { get; set; } - /// - /// This is used for validation of a content item. - /// - /// - /// A content item can be invalid but still be saved. This occurs when there's property validation errors, we will - /// still save the item but it cannot be published. So we need a way of returning validation errors as well as the - /// updated model. - /// - /// NOTE: The ProperCase is important because when we return ModeState normally it will always be proper case. - /// - [DataMember(Name = "ModelState")] - [ReadOnly(true)] - public IDictionary? Errors { get; set; } - } + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + [ReadOnly(true)] + public List Notifications { get; private set; } +} - [DataContract(Name = "contentType", Namespace = "")] - public abstract class ContentTypeCompositionDisplay : ContentTypeCompositionDisplay - where TPropertyTypeDisplay : PropertyTypeDisplay - { - protected ContentTypeCompositionDisplay() - { - //initialize collections so at least their never null - Groups = new List>(); - } +[DataContract(Name = "contentType", Namespace = "")] +public abstract class ContentTypeCompositionDisplay : ContentTypeCompositionDisplay + where TPropertyTypeDisplay : PropertyTypeDisplay +{ + protected ContentTypeCompositionDisplay() => + //initialize collections so at least their never null + Groups = new List>(); - //Tabs - [DataMember(Name = "groups")] - public IEnumerable> Groups { get; set; } - } + //Tabs + [DataMember(Name = "groups")] public IEnumerable> Groups { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentTypeSave.cs b/src/Umbraco.Core/Models/ContentEditing/ContentTypeSave.cs index 55c4a07cfd0d..c58a74fd23ef 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentTypeSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentTypeSave.cs @@ -1,121 +1,114 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Abstract model used to save content types +/// +[DataContract(Name = "contentType", Namespace = "")] +public abstract class ContentTypeSave : ContentTypeBasic, IValidatableObject { - /// - /// Abstract model used to save content types - /// - [DataContract(Name = "contentType", Namespace = "")] - public abstract class ContentTypeSave : ContentTypeBasic, IValidatableObject + protected ContentTypeSave() { - protected ContentTypeSave() - { - AllowedContentTypes = new List(); - CompositeContentTypes = new List(); - } + AllowedContentTypes = new List(); + CompositeContentTypes = new List(); + } - //Compositions - [DataMember(Name = "compositeContentTypes")] - public IEnumerable CompositeContentTypes { get; set; } + //Compositions + [DataMember(Name = "compositeContentTypes")] + public IEnumerable CompositeContentTypes { get; set; } - [DataMember(Name = "allowAsRoot")] - public bool AllowAsRoot { get; set; } + [DataMember(Name = "allowAsRoot")] public bool AllowAsRoot { get; set; } - //Allowed child types - [DataMember(Name = "allowedContentTypes")] - public IEnumerable AllowedContentTypes { get; set; } + //Allowed child types + [DataMember(Name = "allowedContentTypes")] + public IEnumerable AllowedContentTypes { get; set; } - [DataMember(Name = "historyCleanup")] - public HistoryCleanupViewModel? HistoryCleanup { get; set; } + [DataMember(Name = "historyCleanup")] public HistoryCleanupViewModel? HistoryCleanup { get; set; } - /// - /// Custom validation - /// - /// - /// - public virtual IEnumerable Validate(ValidationContext validationContext) + /// + /// Custom validation + /// + /// + /// + public virtual IEnumerable Validate(ValidationContext validationContext) + { + if (CompositeContentTypes.Any(x => x.IsNullOrWhiteSpace())) { - if (CompositeContentTypes.Any(x => x.IsNullOrWhiteSpace())) - yield return new ValidationResult("Composite Content Type value cannot be null", new[] { "CompositeContentTypes" }); + yield return new ValidationResult("Composite Content Type value cannot be null", + new[] {"CompositeContentTypes"}); } } +} + +/// +/// Abstract model used to save content types +/// +/// +[DataContract(Name = "contentType", Namespace = "")] +public abstract class ContentTypeSave : ContentTypeSave + where TPropertyType : PropertyTypeBasic +{ + protected ContentTypeSave() => Groups = new List>(); + + /// + /// A rule for defining how a content type can be varied + /// + /// + /// This is only supported on document types right now but in the future it could be media types too + /// + [DataMember(Name = "allowCultureVariant")] + public bool AllowCultureVariant { get; set; } + + [DataMember(Name = "allowSegmentVariant")] + public bool AllowSegmentVariant { get; set; } + + //Tabs + [DataMember(Name = "groups")] public IEnumerable> Groups { get; set; } /// - /// Abstract model used to save content types + /// Custom validation /// - /// - [DataContract(Name = "contentType", Namespace = "")] - public abstract class ContentTypeSave : ContentTypeSave - where TPropertyType : PropertyTypeBasic + /// + /// + public override IEnumerable Validate(ValidationContext validationContext) { - protected ContentTypeSave() + foreach (ValidationResult validationResult in base.Validate(validationContext)) { - Groups = new List>(); + yield return validationResult; } - /// - /// A rule for defining how a content type can be varied - /// - /// - /// This is only supported on document types right now but in the future it could be media types too - /// - [DataMember(Name = "allowCultureVariant")] - public bool AllowCultureVariant { get; set; } - - [DataMember(Name = "allowSegmentVariant")] - public bool AllowSegmentVariant { get; set; } - - //Tabs - [DataMember(Name = "groups")] - public IEnumerable> Groups { get; set; } - - /// - /// Custom validation - /// - /// - /// - public override IEnumerable Validate(ValidationContext validationContext) + foreach (IGrouping> duplicateGroupAlias in Groups + .GroupBy(x => x.Alias).Where(x => x.Count() > 1)) { - foreach (var validationResult in base.Validate(validationContext)) + var lastGroupIndex = Groups.IndexOf(duplicateGroupAlias.Last()); + yield return new ValidationResult("Duplicate aliases are not allowed: " + duplicateGroupAlias.Key, new[] { - yield return validationResult; - } + // TODO: We don't display the alias yet, so add the validation message to the name + $"Groups[{lastGroupIndex}].Name" + }); + } - foreach (var duplicateGroupAlias in Groups.GroupBy(x => x.Alias).Where(x => x.Count() > 1)) - { - var lastGroupIndex = Groups.IndexOf(duplicateGroupAlias.Last()); - yield return new ValidationResult("Duplicate aliases are not allowed: " + duplicateGroupAlias.Key, new[] - { - // TODO: We don't display the alias yet, so add the validation message to the name - $"Groups[{lastGroupIndex}].Name" - }); - } - - foreach (var duplicateGroupName in Groups.GroupBy(x => (x.GetParentAlias(), x.Name)).Where(x => x.Count() > 1)) - { - var lastGroupIndex = Groups.IndexOf(duplicateGroupName.Last()); - yield return new ValidationResult("Duplicate names are not allowed", new[] - { - $"Groups[{lastGroupIndex}].Name" - }); - } - - foreach (var duplicatePropertyAlias in Groups.SelectMany(x => x.Properties).GroupBy(x => x.Alias).Where(x => x.Count() > 1)) - { - var lastProperty = duplicatePropertyAlias.Last(); - var propertyGroup = Groups.Single(x => x.Properties.Contains(lastProperty)); - var lastPropertyIndex = propertyGroup.Properties.IndexOf(lastProperty); - var propertyGroupIndex = Groups.IndexOf(propertyGroup); - - yield return new ValidationResult("Duplicate property aliases not allowed: " + duplicatePropertyAlias.Key, new[] - { - $"Groups[{propertyGroupIndex}].Properties[{lastPropertyIndex}].Alias" - }); - } + foreach (IGrouping<(string, string Name), PropertyGroupBasic> duplicateGroupName in Groups + .GroupBy(x => (x.GetParentAlias(), x.Name)).Where(x => x.Count() > 1)) + { + var lastGroupIndex = Groups.IndexOf(duplicateGroupName.Last()); + yield return new ValidationResult("Duplicate names are not allowed", + new[] {$"Groups[{lastGroupIndex}].Name"}); + } + + foreach (IGrouping duplicatePropertyAlias in Groups.SelectMany(x => x.Properties) + .GroupBy(x => x.Alias).Where(x => x.Count() > 1)) + { + TPropertyType lastProperty = duplicatePropertyAlias.Last(); + PropertyGroupBasic propertyGroup = Groups.Single(x => x.Properties.Contains(lastProperty)); + var lastPropertyIndex = propertyGroup.Properties.IndexOf(lastProperty); + var propertyGroupIndex = Groups.IndexOf(propertyGroup); + + yield return new ValidationResult("Duplicate property aliases not allowed: " + duplicatePropertyAlias.Key, + new[] {$"Groups[{propertyGroupIndex}].Properties[{lastPropertyIndex}].Alias"}); } } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentTypesByAliases.cs b/src/Umbraco.Core/Models/ContentEditing/ContentTypesByAliases.cs index 57b1c98d543b..476e772743a6 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentTypesByAliases.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentTypesByAliases.cs @@ -1,26 +1,25 @@ using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// A model for retrieving multiple content types based on their aliases. +/// +[DataContract(Name = "contentTypes", Namespace = "")] +public class ContentTypesByAliases { /// - /// A model for retrieving multiple content types based on their aliases. + /// Id of the parent of the content type. /// - [DataContract(Name = "contentTypes", Namespace = "")] - public class ContentTypesByAliases - { - /// - /// Id of the parent of the content type. - /// - [DataMember(Name = "parentId")] - [Required] - public int ParentId { get; set; } + [DataMember(Name = "parentId")] + [Required] + public int ParentId { get; set; } - /// - /// The alias of every content type to get. - /// - [DataMember(Name = "contentTypeAliases")] - [Required] - public string[]? ContentTypeAliases { get; set; } - } + /// + /// The alias of every content type to get. + /// + [DataMember(Name = "contentTypeAliases")] + [Required] + public string[]? ContentTypeAliases { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentTypesByKeys.cs b/src/Umbraco.Core/Models/ContentEditing/ContentTypesByKeys.cs index 0a2bea7f8856..2b728c04da79 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentTypesByKeys.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentTypesByKeys.cs @@ -1,27 +1,25 @@ -using System; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// A model for retrieving multiple content types based on their keys. +/// +[DataContract(Name = "contentTypes", Namespace = "")] +public class ContentTypesByKeys { /// - /// A model for retrieving multiple content types based on their keys. + /// ID of the parent of the content type. /// - [DataContract(Name = "contentTypes", Namespace = "")] - public class ContentTypesByKeys - { - /// - /// ID of the parent of the content type. - /// - [DataMember(Name = "parentId")] - [Required] - public int ParentId { get; set; } + [DataMember(Name = "parentId")] + [Required] + public int ParentId { get; set; } - /// - /// The id of every content type to get. - /// - [DataMember(Name = "contentTypeKeys")] - [Required] - public Guid[]? ContentTypeKeys { get; set; } - } + /// + /// The id of every content type to get. + /// + [DataMember(Name = "contentTypeKeys")] + [Required] + public Guid[]? ContentTypeKeys { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentVariantSave.cs b/src/Umbraco.Core/Models/ContentEditing/ContentVariantSave.cs index f7ea69f7ce88..da82ebf9b788 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentVariantSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentVariantSave.cs @@ -1,73 +1,66 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Validation; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "contentVariant", Namespace = "")] +public class ContentVariantSave : IContentProperties { - [DataContract(Name = "contentVariant", Namespace = "")] - public class ContentVariantSave : IContentProperties - { - public ContentVariantSave() - { - Properties = new List(); - } + public ContentVariantSave() => Properties = new List(); - [DataMember(Name = "name", IsRequired = true)] - [RequiredForPersistence(AllowEmptyStrings = false, ErrorMessage = "Required")] - [MaxLength(255, ErrorMessage ="Name must be less than 255 characters")] - public string? Name { get; set; } + [DataMember(Name = "name", IsRequired = true)] + [RequiredForPersistence(AllowEmptyStrings = false, ErrorMessage = "Required")] + [MaxLength(255, ErrorMessage = "Name must be less than 255 characters")] + public string? Name { get; set; } - [DataMember(Name = "properties")] - public IEnumerable Properties { get; set; } + /// + /// The culture of this variant, if this is invariant than this is null or empty + /// + [DataMember(Name = "culture")] + public string? Culture { get; set; } - /// - /// The culture of this variant, if this is invariant than this is null or empty - /// - [DataMember(Name = "culture")] - public string? Culture { get; set; } + /// + /// The segment of this variant, if this is invariant than this is null or empty + /// + [DataMember(Name = "segment")] + public string? Segment { get; set; } - /// - /// The segment of this variant, if this is invariant than this is null or empty - /// - [DataMember(Name = "segment")] - public string? Segment { get; set; } + /// + /// Indicates if the variant should be updated + /// + /// + /// If this is false, this variant data will not be updated at all + /// + [DataMember(Name = "save")] + public bool Save { get; set; } - /// - /// Indicates if the variant should be updated - /// - /// - /// If this is false, this variant data will not be updated at all - /// - [DataMember(Name = "save")] - public bool Save { get; set; } + /// + /// Indicates if the variant should be published + /// + /// + /// This option will have no affect if is false. + /// This is not used to unpublish. + /// + [DataMember(Name = "publish")] + public bool Publish { get; set; } - /// - /// Indicates if the variant should be published - /// - /// - /// This option will have no affect if is false. - /// This is not used to unpublish. - /// - [DataMember(Name = "publish")] - public bool Publish { get; set; } + [DataMember(Name = "expireDate")] public DateTime? ExpireDate { get; set; } - [DataMember(Name = "expireDate")] - public DateTime? ExpireDate { get; set; } + [DataMember(Name = "releaseDate")] public DateTime? ReleaseDate { get; set; } - [DataMember(Name = "releaseDate")] - public DateTime? ReleaseDate { get; set; } + /// + /// The property DTO object is used to gather all required property data including data type information etc... for use + /// with validation - used during inbound model binding + /// + /// + /// We basically use this object to hydrate all required data from the database into one object so we can validate + /// everything we need + /// instead of having to look up all the data individually. + /// This is not used for outgoing model information. + /// + [IgnoreDataMember] + public ContentPropertyCollectionDto? PropertyCollectionDto { get; set; } - /// - /// The property DTO object is used to gather all required property data including data type information etc... for use with validation - used during inbound model binding - /// - /// - /// We basically use this object to hydrate all required data from the database into one object so we can validate everything we need - /// instead of having to look up all the data individually. - /// This is not used for outgoing model information. - /// - [IgnoreDataMember] - public ContentPropertyCollectionDto? PropertyCollectionDto { get; set; } - } + [DataMember(Name = "properties")] public IEnumerable Properties { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentVariationDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/ContentVariationDisplay.cs index 44f0b31c2552..886bb5299fbd 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentVariationDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentVariationDisplay.cs @@ -1,82 +1,73 @@ -using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Linq; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Represents the variant info for a content item +/// +[DataContract(Name = "contentVariant", Namespace = "")] +public class ContentVariantDisplay : ITabbedContent, IContentProperties, + INotificationModel { - /// - /// Represents the variant info for a content item - /// - [DataContract(Name = "contentVariant", Namespace = "")] - public class ContentVariantDisplay : ITabbedContent, IContentProperties, INotificationModel + public ContentVariantDisplay() { - public ContentVariantDisplay() - { - Tabs = new List>(); - Notifications = new List(); - } + Tabs = new List>(); + Notifications = new List(); + } - [DataMember(Name = "name", IsRequired = true)] - public string? Name { get; set; } + [DataMember(Name = "name", IsRequired = true)] + public string? Name { get; set; } - [DataMember(Name = "displayName")] - public string? DisplayName { get; set; } + [DataMember(Name = "displayName")] public string? DisplayName { get; set; } - /// - /// Defines the tabs containing display properties - /// - [DataMember(Name = "tabs")] - public IEnumerable> Tabs { get; set; } + /// + /// The language/culture assigned to this content variation + /// + /// + /// If this is null it means this content variant is an invariant culture + /// + [DataMember(Name = "language")] + public Language? Language { get; set; } - /// - /// Internal property used for tests to get all properties from all tabs - /// - [IgnoreDataMember] - IEnumerable IContentProperties.Properties => Tabs.Where(x => x.Properties is not null).SelectMany(x => x.Properties!); + [DataMember(Name = "segment")] public string? Segment { get; set; } - /// - /// The language/culture assigned to this content variation - /// - /// - /// If this is null it means this content variant is an invariant culture - /// - [DataMember(Name = "language")] - public Language? Language { get; set; } + [DataMember(Name = "state")] public ContentSavedState State { get; set; } - [DataMember(Name = "segment")] - public string? Segment { get; set; } + [DataMember(Name = "updateDate")] public DateTime UpdateDate { get; set; } - [DataMember(Name = "state")] - public ContentSavedState State { get; set; } + [DataMember(Name = "createDate")] public DateTime CreateDate { get; set; } - [DataMember(Name = "updateDate")] - public DateTime UpdateDate { get; set; } + [DataMember(Name = "publishDate")] public DateTime? PublishDate { get; set; } - [DataMember(Name = "createDate")] - public DateTime CreateDate { get; set; } + /// + /// Internal property used for tests to get all properties from all tabs + /// + [IgnoreDataMember] + IEnumerable IContentProperties.Properties => + Tabs.Where(x => x.Properties is not null).SelectMany(x => x.Properties!); - [DataMember(Name = "publishDate")] - public DateTime? PublishDate { get; set; } + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + /// + /// The notifications assigned to a variant are currently only used to show custom messages in the save/publish + /// dialogs. + /// + [DataMember(Name = "notifications")] + [ReadOnly(true)] + public List Notifications { get; private set; } - /// - /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. - /// - /// - /// The notifications assigned to a variant are currently only used to show custom messages in the save/publish dialogs. - /// - [DataMember(Name = "notifications")] - [ReadOnly(true)] - public List Notifications { get; private set; } - } + /// + /// Defines the tabs containing display properties + /// + [DataMember(Name = "tabs")] + public IEnumerable> Tabs { get; set; } +} - public class ContentVariantScheduleDisplay : ContentVariantDisplay - { - [DataMember(Name = "releaseDate")] - public DateTime? ReleaseDate { get; set; } +public class ContentVariantScheduleDisplay : ContentVariantDisplay +{ + [DataMember(Name = "releaseDate")] public DateTime? ReleaseDate { get; set; } - [DataMember(Name = "expireDate")] - public DateTime? ExpireDate { get; set; } - } + [DataMember(Name = "expireDate")] public DateTime? ExpireDate { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/CreatedDocumentTypeCollectionResult.cs b/src/Umbraco.Core/Models/ContentEditing/CreatedDocumentTypeCollectionResult.cs index b1db2759f0e1..94aa0aaac632 100644 --- a/src/Umbraco.Core/Models/ContentEditing/CreatedDocumentTypeCollectionResult.cs +++ b/src/Umbraco.Core/Models/ContentEditing/CreatedDocumentTypeCollectionResult.cs @@ -1,17 +1,14 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// The result of creating a content type collection in the UI +/// +[DataContract(Name = "contentTypeCollection", Namespace = "")] +public class CreatedContentTypeCollectionResult { - /// - /// The result of creating a content type collection in the UI - /// - [DataContract(Name = "contentTypeCollection", Namespace = "")] - public class CreatedContentTypeCollectionResult - { - [DataMember(Name = "collectionId")] - public int CollectionId { get; set; } + [DataMember(Name = "collectionId")] public int CollectionId { get; set; } - [DataMember(Name = "containerId")] - public int ContainerId { get; set; } - } + [DataMember(Name = "containerId")] public int ContainerId { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/DataTypeBasic.cs b/src/Umbraco.Core/Models/ContentEditing/DataTypeBasic.cs index 153f495a70f3..d282212c948c 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DataTypeBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DataTypeBasic.cs @@ -1,27 +1,26 @@ using System.ComponentModel; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// The basic data type information +/// +[DataContract(Name = "dataType", Namespace = "")] +public class DataTypeBasic : EntityBasic { /// - /// The basic data type information + /// Whether or not this is a system data type, in which case it cannot be deleted /// - [DataContract(Name = "dataType", Namespace = "")] - public class DataTypeBasic : EntityBasic - { - /// - /// Whether or not this is a system data type, in which case it cannot be deleted - /// - [DataMember(Name = "isSystem")] - [ReadOnly(true)] - public bool IsSystemDataType { get; set; } + [DataMember(Name = "isSystem")] + [ReadOnly(true)] + public bool IsSystemDataType { get; set; } - [DataMember(Name = "group")] - [ReadOnly(true)] - public string? Group { get; set; } + [DataMember(Name = "group")] + [ReadOnly(true)] + public string? Group { get; set; } - [DataMember(Name = "hasPrevalues")] - [ReadOnly(true)] - public bool HasPrevalues { get; set; } - } + [DataMember(Name = "hasPrevalues")] + [ReadOnly(true)] + public bool HasPrevalues { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/DataTypeConfigurationFieldDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DataTypeConfigurationFieldDisplay.cs index 97a217716784..f00fd76f3c2f 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DataTypeConfigurationFieldDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DataTypeConfigurationFieldDisplay.cs @@ -1,42 +1,40 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Represents a datatype configuration field model for editing. +/// +[DataContract(Name = "preValue", Namespace = "")] +public class DataTypeConfigurationFieldDisplay : DataTypeConfigurationFieldSave { /// - /// Represents a datatype configuration field model for editing. + /// The name to display for this pre-value field /// - [DataContract(Name = "preValue", Namespace = "")] - public class DataTypeConfigurationFieldDisplay : DataTypeConfigurationFieldSave - { - /// - /// The name to display for this pre-value field - /// - [DataMember(Name = "label", IsRequired = true)] - public string? Name { get; set; } + [DataMember(Name = "label", IsRequired = true)] + public string? Name { get; set; } - /// - /// The description to display for this pre-value field - /// - [DataMember(Name = "description")] - public string? Description { get; set; } + /// + /// The description to display for this pre-value field + /// + [DataMember(Name = "description")] + public string? Description { get; set; } - /// - /// Specifies whether to hide the label for the pre-value - /// - [DataMember(Name = "hideLabel")] - public bool HideLabel { get; set; } + /// + /// Specifies whether to hide the label for the pre-value + /// + [DataMember(Name = "hideLabel")] + public bool HideLabel { get; set; } - /// - /// The view to render for the field - /// - [DataMember(Name = "view", IsRequired = true)] - public string? View { get; set; } + /// + /// The view to render for the field + /// + [DataMember(Name = "view", IsRequired = true)] + public string? View { get; set; } - /// - /// This allows for custom configuration to be injected into the pre-value editor - /// - [DataMember(Name = "config")] - public IDictionary? Config { get; set; } - } + /// + /// This allows for custom configuration to be injected into the pre-value editor + /// + [DataMember(Name = "config")] + public IDictionary? Config { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/DataTypeConfigurationFieldSave.cs b/src/Umbraco.Core/Models/ContentEditing/DataTypeConfigurationFieldSave.cs index a82a6eb257c2..bd1a8c49e3e3 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DataTypeConfigurationFieldSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DataTypeConfigurationFieldSave.cs @@ -1,23 +1,22 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Represents a datatype configuration field model for editing. +/// +[DataContract(Name = "preValue", Namespace = "")] +public class DataTypeConfigurationFieldSave { /// - /// Represents a datatype configuration field model for editing. + /// Gets or sets the configuration field key. /// - [DataContract(Name = "preValue", Namespace = "")] - public class DataTypeConfigurationFieldSave - { - /// - /// Gets or sets the configuration field key. - /// - [DataMember(Name = "key", IsRequired = true)] - public string Key { get; set; } = null!; + [DataMember(Name = "key", IsRequired = true)] + public string Key { get; set; } = null!; - /// - /// Gets or sets the configuration field value. - /// - [DataMember(Name = "value", IsRequired = true)] - public object? Value { get; set; } - } + /// + /// Gets or sets the configuration field value. + /// + [DataMember(Name = "value", IsRequired = true)] + public object? Value { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/DataTypeDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DataTypeDisplay.cs index cbe5552b1e94..5cc25ddc2471 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DataTypeDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DataTypeDisplay.cs @@ -1,37 +1,31 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Represents a data type that is being edited +/// +[DataContract(Name = "dataType", Namespace = "")] +public class DataTypeDisplay : DataTypeBasic, INotificationModel { + public DataTypeDisplay() => Notifications = new List(); + /// - /// Represents a data type that is being edited + /// The alias of the property editor /// - [DataContract(Name = "dataType", Namespace = "")] - public class DataTypeDisplay : DataTypeBasic, INotificationModel - { - public DataTypeDisplay() - { - Notifications = new List(); - } - - /// - /// The alias of the property editor - /// - [DataMember(Name = "selectedEditor", IsRequired = true)] - [Required] - public string? SelectedEditor { get; set; } + [DataMember(Name = "selectedEditor", IsRequired = true)] + [Required] + public string? SelectedEditor { get; set; } - [DataMember(Name = "availableEditors")] - public IEnumerable? AvailableEditors { get; set; } + [DataMember(Name = "availableEditors")] + public IEnumerable? AvailableEditors { get; set; } - [DataMember(Name = "preValues")] - public IEnumerable? PreValues { get; set; } + [DataMember(Name = "preValues")] public IEnumerable? PreValues { get; set; } - /// - /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. - /// - [DataMember(Name = "notifications")] - public List Notifications { get; private set; } - } + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/DataTypeReferences.cs b/src/Umbraco.Core/Models/ContentEditing/DataTypeReferences.cs index c8699472d5da..8a3159e46611 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DataTypeReferences.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DataTypeReferences.cs @@ -1,36 +1,30 @@ -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "dataTypeReferences", Namespace = "")] +public class DataTypeReferences { - [DataContract(Name = "dataTypeReferences", Namespace = "")] - public class DataTypeReferences - { - [DataMember(Name = "documentTypes")] - public IEnumerable DocumentTypes { get; set; } = Enumerable.Empty(); + [DataMember(Name = "documentTypes")] + public IEnumerable DocumentTypes { get; set; } = Enumerable.Empty(); - [DataMember(Name = "mediaTypes")] - public IEnumerable MediaTypes { get; set; } = Enumerable.Empty(); + [DataMember(Name = "mediaTypes")] + public IEnumerable MediaTypes { get; set; } = Enumerable.Empty(); - [DataMember(Name = "memberTypes")] - public IEnumerable MemberTypes { get; set; } = Enumerable.Empty(); + [DataMember(Name = "memberTypes")] + public IEnumerable MemberTypes { get; set; } = Enumerable.Empty(); - [DataContract(Name = "contentType", Namespace = "")] - public class ContentTypeReferences : EntityBasic - { - [DataMember(Name = "properties")] - public object? Properties { get; set; } + [DataContract(Name = "contentType", Namespace = "")] + public class ContentTypeReferences : EntityBasic + { + [DataMember(Name = "properties")] public object? Properties { get; set; } - [DataContract(Name = "property", Namespace = "")] - public class PropertyTypeReferences - { - [DataMember(Name = "name")] - public string? Name { get; set; } + [DataContract(Name = "property", Namespace = "")] + public class PropertyTypeReferences + { + [DataMember(Name = "name")] public string? Name { get; set; } - [DataMember(Name = "alias")] - public string? Alias { get; set; } - } + [DataMember(Name = "alias")] public string? Alias { get; set; } } } } diff --git a/src/Umbraco.Core/Models/ContentEditing/DataTypeSave.cs b/src/Umbraco.Core/Models/ContentEditing/DataTypeSave.cs index 3795e42782e8..d664b5227914 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DataTypeSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DataTypeSave.cs @@ -1,49 +1,47 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Represents a datatype model for editing. +/// +[DataContract(Name = "dataType", Namespace = "")] +public class DataTypeSave : EntityBasic { /// - /// Represents a datatype model for editing. + /// Gets or sets the action to perform. /// - [DataContract(Name = "dataType", Namespace = "")] - public class DataTypeSave : EntityBasic - { - /// - /// Gets or sets the action to perform. - /// - /// - /// Some values (publish) are illegal here. - /// - [DataMember(Name = "action", IsRequired = true)] - [Required] - public ContentSaveAction Action { get; set; } + /// + /// Some values (publish) are illegal here. + /// + [DataMember(Name = "action", IsRequired = true)] + [Required] + public ContentSaveAction Action { get; set; } - /// - /// Gets or sets the datatype editor. - /// - [DataMember(Name = "selectedEditor", IsRequired = true)] - [Required] - public string? EditorAlias { get; set; } + /// + /// Gets or sets the datatype editor. + /// + [DataMember(Name = "selectedEditor", IsRequired = true)] + [Required] + public string? EditorAlias { get; set; } - /// - /// Gets or sets the datatype configuration fields. - /// - [DataMember(Name = "preValues")] - public IEnumerable? ConfigurationFields { get; set; } + /// + /// Gets or sets the datatype configuration fields. + /// + [DataMember(Name = "preValues")] + public IEnumerable? ConfigurationFields { get; set; } - /// - /// Gets or sets the persisted data type. - /// - [IgnoreDataMember] - public IDataType? PersistedDataType { get; set; } + /// + /// Gets or sets the persisted data type. + /// + [IgnoreDataMember] + public IDataType? PersistedDataType { get; set; } - /// - /// Gets or sets the property editor. - /// - [IgnoreDataMember] - public IDataEditor? PropertyEditor { get; set; } - } + /// + /// Gets or sets the property editor. + /// + [IgnoreDataMember] + public IDataEditor? PropertyEditor { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/DictionaryDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DictionaryDisplay.cs index d8cfaf110462..59e5bffb4b4d 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DictionaryDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DictionaryDisplay.cs @@ -1,48 +1,45 @@ -using System; -using System.Collections.Generic; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// The dictionary display model +/// +[DataContract(Name = "dictionary", Namespace = "")] +public class DictionaryDisplay : EntityBasic, INotificationModel { /// - /// The dictionary display model + /// Initializes a new instance of the class. /// - [DataContract(Name = "dictionary", Namespace = "")] - public class DictionaryDisplay : EntityBasic, INotificationModel + public DictionaryDisplay() { - /// - /// Initializes a new instance of the class. - /// - public DictionaryDisplay() - { - Notifications = new List(); - Translations = new List(); - ContentApps = new List(); - } + Notifications = new List(); + Translations = new List(); + ContentApps = new List(); + } - /// - /// - /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. - /// - [DataMember(Name = "notifications")] - public List Notifications { get; private set; } + /// + /// Gets or sets the parent id. + /// + [DataMember(Name = "parentId")] + public new Guid ParentId { get; set; } - /// - /// Gets or sets the parent id. - /// - [DataMember(Name = "parentId")] - public new Guid ParentId { get; set; } + /// + /// Gets the translations. + /// + [DataMember(Name = "translations")] + public List Translations { get; private set; } - /// - /// Gets the translations. - /// - [DataMember(Name = "translations")] - public List Translations { get; private set; } + /// + /// Apps for the dictionary item + /// + [DataMember(Name = "apps")] + public List ContentApps { get; private set; } - /// - /// Apps for the dictionary item - /// - [DataMember(Name = "apps")] - public List ContentApps { get; private set; } - } + /// + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/DictionaryOverviewDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DictionaryOverviewDisplay.cs index adf279c4121e..2a8d842c39a4 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DictionaryOverviewDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DictionaryOverviewDisplay.cs @@ -1,44 +1,39 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// The dictionary overview display. +/// +[DataContract(Name = "dictionary", Namespace = "")] +public class DictionaryOverviewDisplay { /// - /// The dictionary overview display. + /// Initializes a new instance of the class. /// - [DataContract(Name = "dictionary", Namespace = "")] - public class DictionaryOverviewDisplay - { - /// - /// Initializes a new instance of the class. - /// - public DictionaryOverviewDisplay() - { - Translations = new List(); - } + public DictionaryOverviewDisplay() => Translations = new List(); - /// - /// Gets or sets the key. - /// - [DataMember(Name = "name")] - public string? Name { get; set; } + /// + /// Gets or sets the key. + /// + [DataMember(Name = "name")] + public string? Name { get; set; } - /// - /// Gets or sets the id. - /// - [DataMember(Name = "id")] - public int Id { get; set; } + /// + /// Gets or sets the id. + /// + [DataMember(Name = "id")] + public int Id { get; set; } - /// - /// Gets or sets the level. - /// - [DataMember(Name = "level")] - public int Level { get; set; } + /// + /// Gets or sets the level. + /// + [DataMember(Name = "level")] + public int Level { get; set; } - /// - /// Gets or sets the translations. - /// - [DataMember(Name = "translations")] - public List Translations { get; set; } - } + /// + /// Gets or sets the translations. + /// + [DataMember(Name = "translations")] + public List Translations { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/DictionaryOverviewTranslationDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DictionaryOverviewTranslationDisplay.cs index 00d8b339f9be..87b42da70754 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DictionaryOverviewTranslationDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DictionaryOverviewTranslationDisplay.cs @@ -1,23 +1,22 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// The dictionary translation overview display. +/// +[DataContract(Name = "dictionaryTranslation", Namespace = "")] +public class DictionaryOverviewTranslationDisplay { /// - /// The dictionary translation overview display. + /// Gets or sets the display name. /// - [DataContract(Name = "dictionaryTranslation", Namespace = "")] - public class DictionaryOverviewTranslationDisplay - { - /// - /// Gets or sets the display name. - /// - [DataMember(Name = "displayName")] - public string? DisplayName { get; set; } + [DataMember(Name = "displayName")] + public string? DisplayName { get; set; } - /// - /// Gets or sets a value indicating whether has translation. - /// - [DataMember(Name = "hasTranslation")] - public bool HasTranslation { get; set; } - } + /// + /// Gets or sets a value indicating whether has translation. + /// + [DataMember(Name = "hasTranslation")] + public bool HasTranslation { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/DictionarySave.cs b/src/Umbraco.Core/Models/ContentEditing/DictionarySave.cs index 0e652e7160be..cc6647ff9de9 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DictionarySave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DictionarySave.cs @@ -1,39 +1,33 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Dictionary Save model +/// +[DataContract(Name = "dictionary", Namespace = "")] +public class DictionarySave : EntityBasic { /// - /// Dictionary Save model + /// Initializes a new instance of the class. /// - [DataContract(Name = "dictionary", Namespace = "")] - public class DictionarySave : EntityBasic - { - /// - /// Initializes a new instance of the class. - /// - public DictionarySave() - { - Translations = new List(); - } + public DictionarySave() => Translations = new List(); - /// - /// Gets or sets a value indicating whether name is dirty. - /// - [DataMember(Name = "nameIsDirty")] - public bool NameIsDirty { get; set; } + /// + /// Gets or sets a value indicating whether name is dirty. + /// + [DataMember(Name = "nameIsDirty")] + public bool NameIsDirty { get; set; } - /// - /// Gets the translations. - /// - [DataMember(Name = "translations")] - public List Translations { get; private set; } + /// + /// Gets the translations. + /// + [DataMember(Name = "translations")] + public List Translations { get; private set; } - /// - /// Gets or sets the parent id. - /// - [DataMember(Name = "parentId")] - public new Guid ParentId { get; set; } - } + /// + /// Gets or sets the parent id. + /// + [DataMember(Name = "parentId")] + public new Guid ParentId { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/DictionaryTranslationDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DictionaryTranslationDisplay.cs index 4ad4002b7713..6ca31d1d4d16 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DictionaryTranslationDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DictionaryTranslationDisplay.cs @@ -1,18 +1,17 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// +/// The dictionary translation display model +/// +[DataContract(Name = "dictionaryTranslation", Namespace = "")] +public class DictionaryTranslationDisplay : DictionaryTranslationSave { - /// /// - /// The dictionary translation display model + /// Gets or sets the display name. /// - [DataContract(Name = "dictionaryTranslation", Namespace = "")] - public class DictionaryTranslationDisplay : DictionaryTranslationSave - { - /// - /// Gets or sets the display name. - /// - [DataMember(Name = "displayName")] - public string? DisplayName { get; set; } - } + [DataMember(Name = "displayName")] + public string? DisplayName { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/DictionaryTranslationSave.cs b/src/Umbraco.Core/Models/ContentEditing/DictionaryTranslationSave.cs index aa42abbf5622..cf58bcb2ec5a 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DictionaryTranslationSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DictionaryTranslationSave.cs @@ -1,29 +1,28 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// The dictionary translation save model +/// +[DataContract(Name = "dictionaryTranslation", Namespace = "")] +public class DictionaryTranslationSave { /// - /// The dictionary translation save model + /// Gets or sets the ISO code. /// - [DataContract(Name = "dictionaryTranslation", Namespace = "")] - public class DictionaryTranslationSave - { - /// - /// Gets or sets the ISO code. - /// - [DataMember(Name = "isoCode")] - public string? IsoCode { get; set; } + [DataMember(Name = "isoCode")] + public string? IsoCode { get; set; } - /// - /// Gets or sets the translation. - /// - [DataMember(Name = "translation")] - public string Translation { get; set; } = null!; + /// + /// Gets or sets the translation. + /// + [DataMember(Name = "translation")] + public string Translation { get; set; } = null!; - /// - /// Gets or sets the language id. - /// - [DataMember(Name = "languageId")] - public int LanguageId { get; set; } - } + /// + /// Gets or sets the language id. + /// + [DataMember(Name = "languageId")] + public int LanguageId { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs index 6f56c9229270..1a4672827059 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs @@ -1,34 +1,29 @@ -using System.Collections.Generic; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "contentType", Namespace = "")] +public class DocumentTypeDisplay : ContentTypeCompositionDisplay { - [DataContract(Name = "contentType", Namespace = "")] - public class DocumentTypeDisplay : ContentTypeCompositionDisplay - { - public DocumentTypeDisplay() => - //initialize collections so at least their never null - AllowedTemplates = new List(); + public DocumentTypeDisplay() => + //initialize collections so at least their never null + AllowedTemplates = new List(); - //name, alias, icon, thumb, desc, inherited from the content type + //name, alias, icon, thumb, desc, inherited from the content type - // Templates - [DataMember(Name = "allowedTemplates")] - public IEnumerable AllowedTemplates { get; set; } + // Templates + [DataMember(Name = "allowedTemplates")] + public IEnumerable AllowedTemplates { get; set; } - [DataMember(Name = "defaultTemplate")] - public EntityBasic? DefaultTemplate { get; set; } + [DataMember(Name = "defaultTemplate")] public EntityBasic? DefaultTemplate { get; set; } - [DataMember(Name = "allowCultureVariant")] - public bool AllowCultureVariant { get; set; } + [DataMember(Name = "allowCultureVariant")] + public bool AllowCultureVariant { get; set; } - [DataMember(Name = "allowSegmentVariant")] - public bool AllowSegmentVariant { get; set; } + [DataMember(Name = "allowSegmentVariant")] + public bool AllowSegmentVariant { get; set; } - [DataMember(Name = "apps")] - public IEnumerable? ContentApps { get; set; } + [DataMember(Name = "apps")] public IEnumerable? ContentApps { get; set; } - [DataMember(Name = "historyCleanup")] - public HistoryCleanupViewModel? HistoryCleanup { get; set; } - } + [DataMember(Name = "historyCleanup")] public HistoryCleanupViewModel? HistoryCleanup { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/DocumentTypeSave.cs b/src/Umbraco.Core/Models/ContentEditing/DocumentTypeSave.cs index 2e509ea5db59..d8ebf6985261 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DocumentTypeSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DocumentTypeSave.cs @@ -1,43 +1,42 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Model used to save a document type +/// +[DataContract(Name = "contentType", Namespace = "")] +public class DocumentTypeSave : ContentTypeSave { /// - /// Model used to save a document type + /// The list of allowed templates to assign (template alias) /// - [DataContract(Name = "contentType", Namespace = "")] - public class DocumentTypeSave : ContentTypeSave - { - /// - /// The list of allowed templates to assign (template alias) - /// - [DataMember(Name = "allowedTemplates")] - public IEnumerable? AllowedTemplates { get; set; } + [DataMember(Name = "allowedTemplates")] + public IEnumerable? AllowedTemplates { get; set; } - /// - /// The default template to assign (template alias) - /// - [DataMember(Name = "defaultTemplate")] - public string? DefaultTemplate { get; set; } + /// + /// The default template to assign (template alias) + /// + [DataMember(Name = "defaultTemplate")] + public string? DefaultTemplate { get; set; } - /// - /// Custom validation - /// - /// - /// - public override IEnumerable Validate(ValidationContext validationContext) + /// + /// Custom validation + /// + /// + /// + public override IEnumerable Validate(ValidationContext validationContext) + { + if (AllowedTemplates?.Any(x => x.IsNullOrWhiteSpace()) ?? false) { - if (AllowedTemplates?.Any(x => x.IsNullOrWhiteSpace()) ?? false) - yield return new ValidationResult("Template value cannot be null", new[] { "AllowedTemplates" }); + yield return new ValidationResult("Template value cannot be null", new[] {"AllowedTemplates"}); + } - foreach (var v in base.Validate(validationContext)) - { - yield return v; - } + foreach (ValidationResult v in base.Validate(validationContext)) + { + yield return v; } } } diff --git a/src/Umbraco.Core/Models/ContentEditing/DomainDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DomainDisplay.cs index 573909a6105f..1147a8cdaa17 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DomainDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DomainDisplay.cs @@ -1,26 +1,21 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "DomainDisplay")] +public class DomainDisplay { - [DataContract(Name = "DomainDisplay")] - public class DomainDisplay + public DomainDisplay(string name, int lang) { - public DomainDisplay(string name, int lang) - { - Name = name; - Lang = lang; - } + Name = name; + Lang = lang; + } - [DataMember(Name = "name")] - public string Name { get; } + [DataMember(Name = "name")] public string Name { get; } - [DataMember(Name = "lang")] - public int Lang { get; } + [DataMember(Name = "lang")] public int Lang { get; } - [DataMember(Name = "duplicate")] - public bool Duplicate { get; set; } + [DataMember(Name = "duplicate")] public bool Duplicate { get; set; } - [DataMember(Name = "other")] - public string? Other { get; set; } - } + [DataMember(Name = "other")] public string? Other { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/DomainSave.cs b/src/Umbraco.Core/Models/ContentEditing/DomainSave.cs index a91e740e795b..84ea5ebd819d 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DomainSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DomainSave.cs @@ -1,20 +1,15 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "DomainSave")] +public class DomainSave { - [DataContract(Name = "DomainSave")] - public class DomainSave - { - [DataMember(Name = "valid")] - public bool Valid { get; set; } + [DataMember(Name = "valid")] public bool Valid { get; set; } - [DataMember(Name = "nodeId")] - public int NodeId { get; set; } + [DataMember(Name = "nodeId")] public int NodeId { get; set; } - [DataMember(Name = "language")] - public int Language { get; set; } + [DataMember(Name = "language")] public int Language { get; set; } - [DataMember(Name = "domains")] - public DomainDisplay[]? Domains { get; set; } - } + [DataMember(Name = "domains")] public DomainDisplay[]? Domains { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/EditorNavigation.cs b/src/Umbraco.Core/Models/ContentEditing/EditorNavigation.cs index 6c8c1b50e35d..9500265e11d6 100644 --- a/src/Umbraco.Core/Models/ContentEditing/EditorNavigation.cs +++ b/src/Umbraco.Core/Models/ContentEditing/EditorNavigation.cs @@ -1,26 +1,20 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// A model representing the navigation ("apps") inside an editor in the back office +/// +[DataContract(Name = "user", Namespace = "")] +public class EditorNavigation { - /// - /// A model representing the navigation ("apps") inside an editor in the back office - /// - [DataContract(Name = "user", Namespace = "")] - public class EditorNavigation - { - [DataMember(Name = "name")] - public string? Name { get; set; } + [DataMember(Name = "name")] public string? Name { get; set; } - [DataMember(Name = "alias")] - public string? Alias { get; set; } + [DataMember(Name = "alias")] public string? Alias { get; set; } - [DataMember(Name = "icon")] - public string? Icon { get; set; } + [DataMember(Name = "icon")] public string? Icon { get; set; } - [DataMember(Name = "view")] - public string? View { get; set; } + [DataMember(Name = "view")] public string? View { get; set; } - [DataMember(Name = "active")] - public bool Active { get; set; } - } + [DataMember(Name = "active")] public bool Active { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/EntityBasic.cs b/src/Umbraco.Core/Models/ContentEditing/EntityBasic.cs index 772da930e9d3..86fcebe70604 100644 --- a/src/Umbraco.Core/Models/ContentEditing/EntityBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/EntityBasic.cs @@ -1,70 +1,67 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Validation; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "entity", Namespace = "")] +public class EntityBasic { - [DataContract(Name = "entity", Namespace = "")] - public class EntityBasic + public EntityBasic() { - public EntityBasic() - { - AdditionalData = new Dictionary(); - Alias = string.Empty; - Path = string.Empty; - } + AdditionalData = new Dictionary(); + Alias = string.Empty; + Path = string.Empty; + } - [DataMember(Name = "name", IsRequired = true)] - [RequiredForPersistence(AllowEmptyStrings = false, ErrorMessage = "Required")] - public string? Name { get; set; } + [DataMember(Name = "name", IsRequired = true)] + [RequiredForPersistence(AllowEmptyStrings = false, ErrorMessage = "Required")] + public string? Name { get; set; } - [DataMember(Name = "id", IsRequired = true)] - [Required] - public object? Id { get; set; } + [DataMember(Name = "id", IsRequired = true)] + [Required] + public object? Id { get; set; } - [DataMember(Name = "udi")] - [ReadOnly(true)] - public Udi? Udi { get; set; } + [DataMember(Name = "udi")] + [ReadOnly(true)] + public Udi? Udi { get; set; } - [DataMember(Name = "icon")] - public string? Icon { get; set; } + [DataMember(Name = "icon")] public string? Icon { get; set; } - [DataMember(Name = "trashed")] - [ReadOnly(true)] - public bool Trashed { get; set; } + [DataMember(Name = "trashed")] + [ReadOnly(true)] + public bool Trashed { get; set; } - /// - /// This is the unique Id stored in the database - but could also be the unique id for a custom membership provider - /// - [DataMember(Name = "key")] - public Guid Key { get; set; } + /// + /// This is the unique Id stored in the database - but could also be the unique id for a custom membership provider + /// + [DataMember(Name = "key")] + public Guid Key { get; set; } - [DataMember(Name = "parentId", IsRequired = true)] - [Required] - public int ParentId { get; set; } + [DataMember(Name = "parentId", IsRequired = true)] + [Required] + public int ParentId { get; set; } - /// - /// This will only be populated for some entities like macros - /// - /// - /// It is possible to override this to specify different validation attributes if required - /// - [DataMember(Name = "alias")] - public virtual string Alias { get; set; } + /// + /// This will only be populated for some entities like macros + /// + /// + /// It is possible to override this to specify different validation attributes if required + /// + [DataMember(Name = "alias")] + public virtual string Alias { get; set; } - /// - /// The path of the entity - /// - [DataMember(Name = "path")] - public string Path { get; set; } - /// - /// A collection of extra data that is available for this specific entity/entity type - /// - [DataMember(Name = "metaData")] - [ReadOnly(true)] - public IDictionary AdditionalData { get; private set; } - } + /// + /// The path of the entity + /// + [DataMember(Name = "path")] + public string Path { get; set; } + + /// + /// A collection of extra data that is available for this specific entity/entity type + /// + [DataMember(Name = "metaData")] + [ReadOnly(true)] + public IDictionary AdditionalData { get; private set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/EntitySearchResults.cs b/src/Umbraco.Core/Models/ContentEditing/EntitySearchResults.cs index ff77e3aeb5ce..728707a22f8b 100644 --- a/src/Umbraco.Core/Models/ContentEditing/EntitySearchResults.cs +++ b/src/Umbraco.Core/Models/ContentEditing/EntitySearchResults.cs @@ -1,25 +1,21 @@ using System.Collections; -using System.Collections.Generic; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "searchResults", Namespace = "")] +public class EntitySearchResults : IEnumerable { + private readonly IEnumerable _results; - [DataContract(Name = "searchResults", Namespace = "")] - public class EntitySearchResults : IEnumerable + public EntitySearchResults(IEnumerable results, long totalFound) { - private readonly IEnumerable _results; - - public EntitySearchResults(IEnumerable results, long totalFound) - { - _results = results; - TotalResults = totalFound; - } + _results = results; + TotalResults = totalFound; + } - [DataMember(Name = "totalResults")] - public long TotalResults { get; set; } + [DataMember(Name = "totalResults")] public long TotalResults { get; set; } - public IEnumerator GetEnumerator() => _results.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_results).GetEnumerator(); - } + public IEnumerator GetEnumerator() => _results.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_results).GetEnumerator(); } diff --git a/src/Umbraco.Core/Models/ContentEditing/GetAvailableCompositionsFilter.cs b/src/Umbraco.Core/Models/ContentEditing/GetAvailableCompositionsFilter.cs index d73687c03901..29419533a8d9 100644 --- a/src/Umbraco.Core/Models/ContentEditing/GetAvailableCompositionsFilter.cs +++ b/src/Umbraco.Core/Models/ContentEditing/GetAvailableCompositionsFilter.cs @@ -1,25 +1,27 @@ -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +public class GetAvailableCompositionsFilter { - public class GetAvailableCompositionsFilter - { - public int ContentTypeId { get; set; } + public int ContentTypeId { get; set; } - /// - /// This is normally an empty list but if additional property type aliases are passed in, any content types that have these aliases will be filtered out. - /// This is required because in the case of creating/modifying a content type because new property types being added to it are not yet persisted so cannot - /// be looked up via the db, they need to be passed in. - /// - public string[]? FilterPropertyTypes { get; set; } + /// + /// This is normally an empty list but if additional property type aliases are passed in, any content types that have + /// these aliases will be filtered out. + /// This is required because in the case of creating/modifying a content type because new property types being added to + /// it are not yet persisted so cannot + /// be looked up via the db, they need to be passed in. + /// + public string[]? FilterPropertyTypes { get; set; } - /// - /// This is normally an empty list but if additional content type aliases are passed in, any content types containing those aliases will be filtered out - /// along with any content types that have matching property types that are included in the filtered content types - /// - public string[]? FilterContentTypes { get; set; } + /// + /// This is normally an empty list but if additional content type aliases are passed in, any content types containing + /// those aliases will be filtered out + /// along with any content types that have matching property types that are included in the filtered content types + /// + public string[]? FilterContentTypes { get; set; } - /// - /// Wether the content type is currently marked as an element type - /// - public bool IsElement { get; set; } - } + /// + /// Wether the content type is currently marked as an element type + /// + public bool IsElement { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs b/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs index a0d9bbbcb3a1..2488517a3512 100644 --- a/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs +++ b/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs @@ -1,34 +1,35 @@ using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "historyCleanup", Namespace = "")] +public class HistoryCleanup : BeingDirtyBase { - [DataContract(Name = "historyCleanup", Namespace = "")] - public class HistoryCleanup : BeingDirtyBase - { - private bool _preventCleanup; - private int? _keepAllVersionsNewerThanDays; - private int? _keepLatestVersionPerDayForDays; + private int? _keepAllVersionsNewerThanDays; + private int? _keepLatestVersionPerDayForDays; + private bool _preventCleanup; - [DataMember(Name = "preventCleanup")] - public bool PreventCleanup - { - get => _preventCleanup; - set => SetPropertyValueAndDetectChanges(value, ref _preventCleanup, nameof(PreventCleanup)); - } + [DataMember(Name = "preventCleanup")] + public bool PreventCleanup + { + get => _preventCleanup; + set => SetPropertyValueAndDetectChanges(value, ref _preventCleanup, nameof(PreventCleanup)); + } - [DataMember(Name = "keepAllVersionsNewerThanDays")] - public int? KeepAllVersionsNewerThanDays - { - get => _keepAllVersionsNewerThanDays; - set => SetPropertyValueAndDetectChanges(value, ref _keepAllVersionsNewerThanDays, nameof(KeepAllVersionsNewerThanDays)); - } + [DataMember(Name = "keepAllVersionsNewerThanDays")] + public int? KeepAllVersionsNewerThanDays + { + get => _keepAllVersionsNewerThanDays; + set => SetPropertyValueAndDetectChanges(value, ref _keepAllVersionsNewerThanDays, + nameof(KeepAllVersionsNewerThanDays)); + } - [DataMember(Name = "keepLatestVersionPerDayForDays")] - public int? KeepLatestVersionPerDayForDays - { - get => _keepLatestVersionPerDayForDays; - set => SetPropertyValueAndDetectChanges(value, ref _keepLatestVersionPerDayForDays, nameof(KeepLatestVersionPerDayForDays)); - } + [DataMember(Name = "keepLatestVersionPerDayForDays")] + public int? KeepLatestVersionPerDayForDays + { + get => _keepLatestVersionPerDayForDays; + set => SetPropertyValueAndDetectChanges(value, ref _keepLatestVersionPerDayForDays, + nameof(KeepLatestVersionPerDayForDays)); } } diff --git a/src/Umbraco.Core/Models/ContentEditing/HistoryCleanupViewModel.cs b/src/Umbraco.Core/Models/ContentEditing/HistoryCleanupViewModel.cs index 303ff4eda375..1860dc8feb3d 100644 --- a/src/Umbraco.Core/Models/ContentEditing/HistoryCleanupViewModel.cs +++ b/src/Umbraco.Core/Models/ContentEditing/HistoryCleanupViewModel.cs @@ -1,18 +1,16 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing -{ - [DataContract(Name = "historyCleanup", Namespace = "")] - public class HistoryCleanupViewModel : HistoryCleanup - { +namespace Umbraco.Cms.Core.Models.ContentEditing; - [DataMember(Name = "globalEnableCleanup")] - public bool GlobalEnableCleanup { get; set; } +[DataContract(Name = "historyCleanup", Namespace = "")] +public class HistoryCleanupViewModel : HistoryCleanup +{ + [DataMember(Name = "globalEnableCleanup")] + public bool GlobalEnableCleanup { get; set; } - [DataMember(Name = "globalKeepAllVersionsNewerThanDays")] - public int? GlobalKeepAllVersionsNewerThanDays { get; set; } + [DataMember(Name = "globalKeepAllVersionsNewerThanDays")] + public int? GlobalKeepAllVersionsNewerThanDays { get; set; } - [DataMember(Name = "globalKeepLatestVersionPerDayForDays")] - public int? GlobalKeepLatestVersionPerDayForDays { get; set; } - } + [DataMember(Name = "globalKeepLatestVersionPerDayForDays")] + public int? GlobalKeepLatestVersionPerDayForDays { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/IContentAppFactory.cs b/src/Umbraco.Core/Models/ContentEditing/IContentAppFactory.cs index fc263a3b9179..15fa3fd99e0f 100644 --- a/src/Umbraco.Core/Models/ContentEditing/IContentAppFactory.cs +++ b/src/Umbraco.Core/Models/ContentEditing/IContentAppFactory.cs @@ -1,23 +1,23 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Represents a content app factory. +/// +public interface IContentAppFactory { /// - /// Represents a content app factory. + /// Gets the content app for an object. /// - public interface IContentAppFactory - { - /// - /// Gets the content app for an object. - /// - /// The source object. - /// The content app for the object, or null. - /// - /// The definition must determine, based on , whether - /// the content app should be displayed or not, and return either a - /// instance, or null. - /// - ContentApp? GetContentAppFor(object source, IEnumerable userGroups); - } + /// The source object. + /// The content app for the object, or null. + /// + /// + /// The definition must determine, based on , whether + /// the content app should be displayed or not, and return either a + /// instance, or null. + /// + /// + ContentApp? GetContentAppFor(object source, IEnumerable userGroups); } diff --git a/src/Umbraco.Core/Models/ContentEditing/IContentProperties.cs b/src/Umbraco.Core/Models/ContentEditing/IContentProperties.cs index ca8b2439c26d..af7fbaea41ca 100644 --- a/src/Umbraco.Core/Models/ContentEditing/IContentProperties.cs +++ b/src/Umbraco.Core/Models/ContentEditing/IContentProperties.cs @@ -1,11 +1,7 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models.ContentEditing; -namespace Umbraco.Cms.Core.Models.ContentEditing +public interface IContentProperties + where T : ContentPropertyBasic { - - public interface IContentProperties - where T : ContentPropertyBasic - { - IEnumerable Properties { get; } - } + IEnumerable Properties { get; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/IContentSave.cs b/src/Umbraco.Core/Models/ContentEditing/IContentSave.cs index dfaf18347921..30503a6cc27c 100644 --- a/src/Umbraco.Core/Models/ContentEditing/IContentSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/IContentSave.cs @@ -1,23 +1,23 @@ -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// An interface exposes the shared parts of content, media, members that we use during model binding in order to share +/// logic +/// +/// +public interface IContentSave : IHaveUploadedFiles + where TPersisted : IContentBase { /// - /// An interface exposes the shared parts of content, media, members that we use during model binding in order to share logic + /// The action to perform when saving this content item /// - /// - public interface IContentSave : IHaveUploadedFiles - where TPersisted : IContentBase - { - /// - /// The action to perform when saving this content item - /// - ContentSaveAction Action { get; set; } + ContentSaveAction Action { get; set; } - /// - /// The real persisted content object - used during inbound model binding - /// - /// - /// This is not used for outgoing model information. - /// - TPersisted PersistedContent { get; set; } - } + /// + /// The real persisted content object - used during inbound model binding + /// + /// + /// This is not used for outgoing model information. + /// + TPersisted PersistedContent { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/IErrorModel.cs b/src/Umbraco.Core/Models/ContentEditing/IErrorModel.cs index 4352771cacfd..3884f20860f1 100644 --- a/src/Umbraco.Core/Models/ContentEditing/IErrorModel.cs +++ b/src/Umbraco.Core/Models/ContentEditing/IErrorModel.cs @@ -1,17 +1,14 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models.ContentEditing; -namespace Umbraco.Cms.Core.Models.ContentEditing +public interface IErrorModel { - public interface IErrorModel - { - /// - /// This is used for validation of a content item. - /// - /// - /// A content item can be invalid but still be saved. This occurs when there's property validation errors, we will - /// still save the item but it cannot be published. So we need a way of returning validation errors as well as the - /// updated model. - /// - IDictionary Errors { get; set; } - } + /// + /// This is used for validation of a content item. + /// + /// + /// A content item can be invalid but still be saved. This occurs when there's property validation errors, we will + /// still save the item but it cannot be published. So we need a way of returning validation errors as well as the + /// updated model. + /// + IDictionary Errors { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/IHaveUploadedFiles.cs b/src/Umbraco.Core/Models/ContentEditing/IHaveUploadedFiles.cs index a1d4198427d0..2bd451a649d3 100644 --- a/src/Umbraco.Core/Models/ContentEditing/IHaveUploadedFiles.cs +++ b/src/Umbraco.Core/Models/ContentEditing/IHaveUploadedFiles.cs @@ -1,10 +1,8 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models.Editors; +using Umbraco.Cms.Core.Models.Editors; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +public interface IHaveUploadedFiles { - public interface IHaveUploadedFiles - { - List UploadedFiles { get; } - } + List UploadedFiles { get; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/INotificationModel.cs b/src/Umbraco.Core/Models/ContentEditing/INotificationModel.cs index ac104c0e1b5b..50e0363798e3 100644 --- a/src/Umbraco.Core/Models/ContentEditing/INotificationModel.cs +++ b/src/Umbraco.Core/Models/ContentEditing/INotificationModel.cs @@ -1,14 +1,12 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +public interface INotificationModel { - public interface INotificationModel - { - /// - /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. - /// - [DataMember(Name = "notifications")] - List? Notifications { get; } - } + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + List? Notifications { get; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ITabbedContent.cs b/src/Umbraco.Core/Models/ContentEditing/ITabbedContent.cs index 3f1d84715139..4188c89058ac 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ITabbedContent.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ITabbedContent.cs @@ -1,11 +1,7 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models.ContentEditing; -namespace Umbraco.Cms.Core.Models.ContentEditing +public interface ITabbedContent + where T : ContentPropertyBasic { - - public interface ITabbedContent - where T : ContentPropertyBasic - { - IEnumerable> Tabs { get; } - } + IEnumerable> Tabs { get; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/Language.cs b/src/Umbraco.Core/Models/ContentEditing/Language.cs index 0a0ed03a2a16..8395fe948203 100644 --- a/src/Umbraco.Core/Models/ContentEditing/Language.cs +++ b/src/Umbraco.Core/Models/ContentEditing/Language.cs @@ -1,28 +1,23 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "language", Namespace = "")] +public class Language { - [DataContract(Name = "language", Namespace = "")] - public class Language - { - [DataMember(Name = "id")] - public int Id { get; set; } + [DataMember(Name = "id")] public int Id { get; set; } - [DataMember(Name = "culture", IsRequired = true)] - [Required(AllowEmptyStrings = false)] - public string IsoCode { get; set; } = null!; + [DataMember(Name = "culture", IsRequired = true)] + [Required(AllowEmptyStrings = false)] + public string IsoCode { get; set; } = null!; - [DataMember(Name = "name")] - public string? Name { get; set; } + [DataMember(Name = "name")] public string? Name { get; set; } - [DataMember(Name = "isDefault")] - public bool IsDefault { get; set; } + [DataMember(Name = "isDefault")] public bool IsDefault { get; set; } - [DataMember(Name = "isMandatory")] - public bool IsMandatory { get; set; } + [DataMember(Name = "isMandatory")] public bool IsMandatory { get; set; } - [DataMember(Name = "fallbackLanguageId")] - public int? FallbackLanguageId { get; set; } - } + [DataMember(Name = "fallbackLanguageId")] + public int? FallbackLanguageId { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/LinkDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/LinkDisplay.cs index 551065c56699..e36e75fa0abb 100644 --- a/src/Umbraco.Core/Models/ContentEditing/LinkDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/LinkDisplay.cs @@ -1,32 +1,23 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "link", Namespace = "")] +public class LinkDisplay { - [DataContract(Name = "link", Namespace = "")] - public class LinkDisplay - { - [DataMember(Name = "icon")] - public string? Icon { get; set; } + [DataMember(Name = "icon")] public string? Icon { get; set; } - [DataMember(Name = "name")] - public string? Name { get; set; } + [DataMember(Name = "name")] public string? Name { get; set; } - [DataMember(Name = "published")] - public bool Published { get; set; } + [DataMember(Name = "published")] public bool Published { get; set; } - [DataMember(Name = "queryString")] - public string? QueryString { get; set; } + [DataMember(Name = "queryString")] public string? QueryString { get; set; } - [DataMember(Name = "target")] - public string? Target { get; set; } + [DataMember(Name = "target")] public string? Target { get; set; } - [DataMember(Name = "trashed")] - public bool Trashed { get; set; } + [DataMember(Name = "trashed")] public bool Trashed { get; set; } - [DataMember(Name = "udi")] - public GuidUdi? Udi { get; set; } + [DataMember(Name = "udi")] public GuidUdi? Udi { get; set; } - [DataMember(Name = "url")] - public string? Url { get; set; } - } + [DataMember(Name = "url")] public string? Url { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ListViewAwareContentItemDisplayBase.cs b/src/Umbraco.Core/Models/ContentEditing/ListViewAwareContentItemDisplayBase.cs index 729a086864c8..57bedd7f28bf 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ListViewAwareContentItemDisplayBase.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ListViewAwareContentItemDisplayBase.cs @@ -1,28 +1,27 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// An abstract model representing a content item that can be contained in a list view +/// +/// +public abstract class ListViewAwareContentItemDisplayBase : ContentItemDisplayBase + where T : ContentPropertyBasic { /// - /// An abstract model representing a content item that can be contained in a list view + /// Property indicating if this item is part of a list view parent /// - /// - public abstract class ListViewAwareContentItemDisplayBase : ContentItemDisplayBase - where T : ContentPropertyBasic - { - /// - /// Property indicating if this item is part of a list view parent - /// - [DataMember(Name = "isChildOfListView")] - public bool IsChildOfListView { get; set; } + [DataMember(Name = "isChildOfListView")] + public bool IsChildOfListView { get; set; } - /// - /// Property for the entity's individual tree node URL - /// - /// - /// This is required if the item is a child of a list view since the tree won't actually be loaded, - /// so the app will need to go fetch the individual tree node in order to be able to load it's action list (menu) - /// - [DataMember(Name = "treeNodeUrl")] - public string? TreeNodeUrl { get; set; } - } + /// + /// Property for the entity's individual tree node URL + /// + /// + /// This is required if the item is a child of a list view since the tree won't actually be loaded, + /// so the app will need to go fetch the individual tree node in order to be able to load it's action list (menu) + /// + [DataMember(Name = "treeNodeUrl")] + public string? TreeNodeUrl { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MacroDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/MacroDisplay.cs index f794143aaba2..9daaa3640709 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MacroDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MacroDisplay.cs @@ -1,67 +1,65 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// The macro display model +/// +[DataContract(Name = "dictionary", Namespace = "")] +public class MacroDisplay : EntityBasic, INotificationModel { /// - /// The macro display model + /// Initializes a new instance of the class. /// - [DataContract(Name = "dictionary", Namespace = "")] - public class MacroDisplay : EntityBasic, INotificationModel + public MacroDisplay() { - /// - /// Initializes a new instance of the class. - /// - public MacroDisplay() - { - Notifications = new List(); - Parameters = new List(); - } + Notifications = new List(); + Parameters = new List(); + } - /// - [DataMember(Name = "notifications")] - public List Notifications { get; } + /// + /// Gets or sets a value indicating whether the macro can be used in a rich text editor. + /// + [DataMember(Name = "useInEditor")] + public bool UseInEditor { get; set; } - /// - /// Gets or sets a value indicating whether the macro can be used in a rich text editor. - /// - [DataMember(Name = "useInEditor")] - public bool UseInEditor { get; set; } + /// + /// Gets or sets a value indicating whether the macro should be rendered a rich text editor. + /// + [DataMember(Name = "renderInEditor")] + public bool RenderInEditor { get; set; } - /// - /// Gets or sets a value indicating whether the macro should be rendered a rich text editor. - /// - [DataMember(Name = "renderInEditor")] - public bool RenderInEditor { get; set; } + /// + /// Gets or sets the cache period. + /// + [DataMember(Name = "cachePeriod")] + public int CachePeriod { get; set; } - /// - /// Gets or sets the cache period. - /// - [DataMember(Name = "cachePeriod")] - public int CachePeriod { get; set; } + /// + /// Gets or sets a value indicating whether the macro should be cached by page + /// + [DataMember(Name = "cacheByPage")] + public bool CacheByPage { get; set; } - /// - /// Gets or sets a value indicating whether the macro should be cached by page - /// - [DataMember(Name = "cacheByPage")] - public bool CacheByPage { get; set; } + /// + /// Gets or sets a value indicating whether the macro should be cached by user + /// + [DataMember(Name = "cacheByUser")] + public bool CacheByUser { get; set; } - /// - /// Gets or sets a value indicating whether the macro should be cached by user - /// - [DataMember(Name = "cacheByUser")] - public bool CacheByUser { get; set; } + /// + /// Gets or sets the view. + /// + [DataMember(Name = "view")] + public string View { get; set; } = null!; - /// - /// Gets or sets the view. - /// - [DataMember(Name = "view")] - public string View { get; set; } = null!; + /// + /// Gets or sets the parameters. + /// + [DataMember(Name = "parameters")] + public IEnumerable Parameters { get; set; } - /// - /// Gets or sets the parameters. - /// - [DataMember(Name = "parameters")] - public IEnumerable Parameters { get; set; } - } + /// + [DataMember(Name = "notifications")] + public List Notifications { get; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MacroParameter.cs b/src/Umbraco.Core/Models/ContentEditing/MacroParameter.cs index 233a58cd08a6..dca97e962cd7 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MacroParameter.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MacroParameter.cs @@ -1,43 +1,39 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Represents a macro parameter with an editor +/// +[DataContract(Name = "macroParameter", Namespace = "")] +public class MacroParameter { - /// - /// Represents a macro parameter with an editor - /// - [DataContract(Name = "macroParameter", Namespace = "")] - public class MacroParameter - { - [DataMember(Name = "alias", IsRequired = true)] - [Required] - public string Alias { get; set; } = null!; + [DataMember(Name = "alias", IsRequired = true)] + [Required] + public string Alias { get; set; } = null!; - [DataMember(Name = "name")] - public string? Name { get; set; } + [DataMember(Name = "name")] public string? Name { get; set; } - [DataMember(Name = "sortOrder")] - public int SortOrder { get; set; } + [DataMember(Name = "sortOrder")] public int SortOrder { get; set; } - /// - /// The editor view to render for this parameter - /// - [DataMember(Name = "view", IsRequired = true)] - [Required(AllowEmptyStrings = false)] - public string? View { get; set; } + /// + /// The editor view to render for this parameter + /// + [DataMember(Name = "view", IsRequired = true)] + [Required(AllowEmptyStrings = false)] + public string? View { get; set; } - /// - /// The configuration for this parameter editor - /// - [DataMember(Name = "config", IsRequired = true)] - [Required(AllowEmptyStrings = false)] - public IDictionary? Configuration { get; set; } + /// + /// The configuration for this parameter editor + /// + [DataMember(Name = "config", IsRequired = true)] + [Required(AllowEmptyStrings = false)] + public IDictionary? Configuration { get; set; } - /// - /// Since we don't post this back this isn't currently really used on the server side - /// - [DataMember(Name = "value")] - public object? Value { get; set; } - } + /// + /// Since we don't post this back this isn't currently really used on the server side + /// + [DataMember(Name = "value")] + public object? Value { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MacroParameterDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/MacroParameterDisplay.cs index 8cd630d66f73..e24fd5a53db4 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MacroParameterDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MacroParameterDisplay.cs @@ -1,35 +1,34 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// The macro parameter display. +/// +[DataContract(Name = "parameter", Namespace = "")] +public class MacroParameterDisplay { /// - /// The macro parameter display. + /// Gets or sets the key. /// - [DataContract(Name = "parameter", Namespace = "")] - public class MacroParameterDisplay - { - /// - /// Gets or sets the key. - /// - [DataMember(Name = "key")] - public string Key { get; set; } = null!; + [DataMember(Name = "key")] + public string Key { get; set; } = null!; - /// - /// Gets or sets the label. - /// - [DataMember(Name = "label")] - public string? Label { get; set; } + /// + /// Gets or sets the label. + /// + [DataMember(Name = "label")] + public string? Label { get; set; } - /// - /// Gets or sets the editor. - /// - [DataMember(Name = "editor")] - public string Editor { get; set; } = null!; + /// + /// Gets or sets the editor. + /// + [DataMember(Name = "editor")] + public string Editor { get; set; } = null!; - /// - /// Gets or sets the id. - /// - [DataMember(Name = "id")] - public int Id { get; set; } - } + /// + /// Gets or sets the id. + /// + [DataMember(Name = "id")] + public int Id { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MediaItemDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/MediaItemDisplay.cs index a56911f70725..e913eb926bb8 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MediaItemDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MediaItemDisplay.cs @@ -1,26 +1,18 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// A model representing a media item to be displayed in the back office +/// +[DataContract(Name = "content", Namespace = "")] +public class MediaItemDisplay : ListViewAwareContentItemDisplayBase { - /// - /// A model representing a media item to be displayed in the back office - /// - [DataContract(Name = "content", Namespace = "")] - public class MediaItemDisplay : ListViewAwareContentItemDisplayBase - { - public MediaItemDisplay() - { - ContentApps = new List(); - } + public MediaItemDisplay() => ContentApps = new List(); - [DataMember(Name = "contentType")] - public ContentTypeBasic? ContentType { get; set; } + [DataMember(Name = "contentType")] public ContentTypeBasic? ContentType { get; set; } - [DataMember(Name = "mediaLink")] - public string? MediaLink { get; set; } + [DataMember(Name = "mediaLink")] public string? MediaLink { get; set; } - [DataMember(Name = "apps")] - public IEnumerable ContentApps { get; set; } - } + [DataMember(Name = "apps")] public IEnumerable ContentApps { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MediaItemSave.cs b/src/Umbraco.Core/Models/ContentEditing/MediaItemSave.cs index 06c201ab6760..52fdb575d83c 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MediaItemSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MediaItemSave.cs @@ -1,12 +1,11 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// A model representing a media item to be saved +/// +[DataContract(Name = "content", Namespace = "")] +public class MediaItemSave : ContentBaseSave { - /// - /// A model representing a media item to be saved - /// - [DataContract(Name = "content", Namespace = "")] - public class MediaItemSave : ContentBaseSave - { - } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MediaTypeDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/MediaTypeDisplay.cs index 2c7c50550d31..b7a1290601d4 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MediaTypeDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MediaTypeDisplay.cs @@ -1,11 +1,10 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "contentType", Namespace = "")] +public class MediaTypeDisplay : ContentTypeCompositionDisplay { - [DataContract(Name = "contentType", Namespace = "")] - public class MediaTypeDisplay : ContentTypeCompositionDisplay - { - [DataMember(Name = "isSystemMediaType")] - public bool IsSystemMediaType { get; set; } - } + [DataMember(Name = "isSystemMediaType")] + public bool IsSystemMediaType { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MediaTypeSave.cs b/src/Umbraco.Core/Models/ContentEditing/MediaTypeSave.cs index 1ef2a1988b5a..5d5ccd03b358 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MediaTypeSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MediaTypeSave.cs @@ -1,12 +1,11 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Model used to save a media type +/// +[DataContract(Name = "contentType", Namespace = "")] +public class MediaTypeSave : ContentTypeSave { - /// - /// Model used to save a media type - /// - [DataContract(Name = "contentType", Namespace = "")] - public class MediaTypeSave : ContentTypeSave - { - } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberBasic.cs b/src/Umbraco.Core/Models/ContentEditing/MemberBasic.cs index d148d889210c..a54e585635fe 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberBasic.cs @@ -1,24 +1,20 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Used for basic member information +/// +public class MemberBasic : ContentItemBasic { - /// - /// Used for basic member information - /// - public class MemberBasic : ContentItemBasic - { - [DataMember(Name = "username")] - public string? Username { get; set; } + [DataMember(Name = "username")] public string? Username { get; set; } - [DataMember(Name = "email")] - public string? Email { get; set; } + [DataMember(Name = "email")] public string? Email { get; set; } - [DataMember(Name = "properties")] - public override IEnumerable Properties - { - get => base.Properties; - set => base.Properties = value; - } + [DataMember(Name = "properties")] + public override IEnumerable Properties + { + get => base.Properties; + set => base.Properties = value; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/MemberDisplay.cs index 5448c40b1e36..0e8849b969b4 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberDisplay.cs @@ -1,51 +1,41 @@ -using System.Collections.Generic; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// A model representing a member to be displayed in the back office +/// +[DataContract(Name = "content", Namespace = "")] +public class MemberDisplay : ListViewAwareContentItemDisplayBase { - /// - /// A model representing a member to be displayed in the back office - /// - [DataContract(Name = "content", Namespace = "")] - public class MemberDisplay : ListViewAwareContentItemDisplayBase - { - public MemberDisplay() - { + public MemberDisplay() => // MemberProviderFieldMapping = new Dictionary(); - ContentApps = new List(); - } + ContentApps = new List(); - [DataMember(Name = "contentType")] - public ContentTypeBasic? ContentType { get; set; } + [DataMember(Name = "contentType")] public ContentTypeBasic? ContentType { get; set; } - [DataMember(Name = "username")] - public string? Username { get; set; } + [DataMember(Name = "username")] public string? Username { get; set; } - [DataMember(Name = "email")] - public string? Email { get; set; } + [DataMember(Name = "email")] public string? Email { get; set; } - [DataMember(Name = "isLockedOut")] - public bool IsLockedOut { get; set; } + [DataMember(Name = "isLockedOut")] public bool IsLockedOut { get; set; } - [DataMember(Name = "isApproved")] - public bool IsApproved { get; set; } + [DataMember(Name = "isApproved")] public bool IsApproved { get; set; } - //[DataMember(Name = "membershipScenario")] - //public MembershipScenario MembershipScenario { get; set; } + //[DataMember(Name = "membershipScenario")] + //public MembershipScenario MembershipScenario { get; set; } - // /// - // /// This is used to indicate how to map the membership provider properties to the save model, this mapping - // /// will change if a developer has opted to have custom member property aliases specified in their membership provider config, - // /// or if we are editing a member that is not an Umbraco member (custom provider) - // /// - // [DataMember(Name = "fieldConfig")] - // public IDictionary MemberProviderFieldMapping { get; set; } + // /// + // /// This is used to indicate how to map the membership provider properties to the save model, this mapping + // /// will change if a developer has opted to have custom member property aliases specified in their membership provider config, + // /// or if we are editing a member that is not an Umbraco member (custom provider) + // /// + // [DataMember(Name = "fieldConfig")] + // public IDictionary MemberProviderFieldMapping { get; set; } - [DataMember(Name = "apps")] - public IEnumerable ContentApps { get; set; } + [DataMember(Name = "apps")] public IEnumerable ContentApps { get; set; } - [DataMember(Name = "membershipProperties")] - public IEnumerable? MembershipProperties { get; set; } - } + [DataMember(Name = "membershipProperties")] + public IEnumerable? MembershipProperties { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberGroupDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/MemberGroupDisplay.cs index 2d930727aa99..2c0bf10a25a6 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberGroupDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberGroupDisplay.cs @@ -1,20 +1,15 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "memberGroup", Namespace = "")] +public class MemberGroupDisplay : EntityBasic, INotificationModel { - [DataContract(Name = "memberGroup", Namespace = "")] - public class MemberGroupDisplay : EntityBasic, INotificationModel - { - public MemberGroupDisplay() - { - Notifications = new List(); - } + public MemberGroupDisplay() => Notifications = new List(); - /// - /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. - /// - [DataMember(Name = "notifications")] - public List Notifications { get; private set; } - } + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberGroupSave.cs b/src/Umbraco.Core/Models/ContentEditing/MemberGroupSave.cs index 2b863a758d64..25033258eacb 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberGroupSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberGroupSave.cs @@ -1,9 +1,8 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "memberGroup", Namespace = "")] +public class MemberGroupSave : EntityBasic { - [DataContract(Name = "memberGroup", Namespace = "")] - public class MemberGroupSave : EntityBasic - { - } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberListDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/MemberListDisplay.cs index c4a5382e84fa..7a533c01fa3c 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberListDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberListDisplay.cs @@ -1,15 +1,12 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// A model representing a member list to be displayed in the back office +/// +[DataContract(Name = "content", Namespace = "")] +public class MemberListDisplay : ContentItemDisplayBase { - /// - /// A model representing a member list to be displayed in the back office - /// - [DataContract(Name = "content", Namespace = "")] - public class MemberListDisplay : ContentItemDisplayBase - { - [DataMember(Name = "apps")] - public IEnumerable? ContentApps { get; set; } - } + [DataMember(Name = "apps")] public IEnumerable? ContentApps { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberPropertyTypeBasic.cs b/src/Umbraco.Core/Models/ContentEditing/MemberPropertyTypeBasic.cs index b25f2ae5c8a9..fb25776ee9a8 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberPropertyTypeBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberPropertyTypeBasic.cs @@ -1,20 +1,17 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Basic member property type +/// +[DataContract(Name = "contentType", Namespace = "")] +public class MemberPropertyTypeBasic : PropertyTypeBasic { - /// - /// Basic member property type - /// - [DataContract(Name = "contentType", Namespace = "")] - public class MemberPropertyTypeBasic : PropertyTypeBasic - { - [DataMember(Name = "showOnMemberProfile")] - public bool MemberCanViewProperty { get; set; } + [DataMember(Name = "showOnMemberProfile")] + public bool MemberCanViewProperty { get; set; } - [DataMember(Name = "memberCanEdit")] - public bool MemberCanEditProperty { get; set; } + [DataMember(Name = "memberCanEdit")] public bool MemberCanEditProperty { get; set; } - [DataMember(Name = "isSensitiveData")] - public bool IsSensitiveData { get; set; } - } + [DataMember(Name = "isSensitiveData")] public bool IsSensitiveData { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberPropertyTypeDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/MemberPropertyTypeDisplay.cs index 873883c8db8c..59beb56f6b09 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberPropertyTypeDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberPropertyTypeDisplay.cs @@ -1,17 +1,14 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "propertyType")] +public class MemberPropertyTypeDisplay : PropertyTypeDisplay { - [DataContract(Name = "propertyType")] - public class MemberPropertyTypeDisplay : PropertyTypeDisplay - { - [DataMember(Name = "showOnMemberProfile")] - public bool MemberCanViewProperty { get; set; } + [DataMember(Name = "showOnMemberProfile")] + public bool MemberCanViewProperty { get; set; } - [DataMember(Name = "memberCanEdit")] - public bool MemberCanEditProperty { get; set; } + [DataMember(Name = "memberCanEdit")] public bool MemberCanEditProperty { get; set; } - [DataMember(Name = "isSensitiveData")] - public bool IsSensitiveData { get; set; } - } + [DataMember(Name = "isSensitiveData")] public bool IsSensitiveData { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberSave.cs b/src/Umbraco.Core/Models/ContentEditing/MemberSave.cs index 903c87341a45..3d44de4bdcc7 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberSave.cs @@ -1,48 +1,44 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Validation; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.ContentEditing -{ - /// - public class MemberSave : ContentBaseSave - { +namespace Umbraco.Cms.Core.Models.ContentEditing; - [DataMember(Name = "username", IsRequired = true)] - [RequiredForPersistence(AllowEmptyStrings = false, ErrorMessage = "Required")] - public string Username { get; set; } = null!; +/// +public class MemberSave : ContentBaseSave +{ + [DataMember(Name = "username", IsRequired = true)] + [RequiredForPersistence(AllowEmptyStrings = false, ErrorMessage = "Required")] + public string Username { get; set; } = null!; - [DataMember(Name = "email", IsRequired = true)] - [RequiredForPersistence(AllowEmptyStrings = false, ErrorMessage = "Required")] - [EmailAddress] - public string Email { get; set; } = null!; + [DataMember(Name = "email", IsRequired = true)] + [RequiredForPersistence(AllowEmptyStrings = false, ErrorMessage = "Required")] + [EmailAddress] + public string Email { get; set; } = null!; - [DataMember(Name = "password")] - public ChangingPasswordModel? Password { get; set; } + [DataMember(Name = "password")] public ChangingPasswordModel? Password { get; set; } - [DataMember(Name = "memberGroups")] - public IEnumerable? Groups { get; set; } + [DataMember(Name = "memberGroups")] public IEnumerable? Groups { get; set; } - /// - /// Returns the value from the Comments property - /// - public string? Comments => GetPropertyValue(Constants.Conventions.Member.Comments); + /// + /// Returns the value from the Comments property + /// + public string? Comments => GetPropertyValue(Constants.Conventions.Member.Comments); - [DataMember(Name = "isLockedOut")] - public bool IsLockedOut { get; set; } + [DataMember(Name = "isLockedOut")] public bool IsLockedOut { get; set; } - [DataMember(Name = "isApproved")] - public bool IsApproved { get; set; } + [DataMember(Name = "isApproved")] public bool IsApproved { get; set; } - private T? GetPropertyValue(string alias) + private T? GetPropertyValue(string alias) + { + ContentPropertyBasic prop = Properties.FirstOrDefault(x => x.Alias == alias); + if (prop == null) { - var prop = Properties.FirstOrDefault(x => x.Alias == alias); - if (prop == null) return default; - var converted = prop.Value.TryConvertTo(); - return converted.Result ?? default; + return default; } + + Attempt converted = prop.Value.TryConvertTo(); + return converted.Result ?? default; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberTypeDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/MemberTypeDisplay.cs index 67e390f37868..98819c1016b2 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberTypeDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberTypeDisplay.cs @@ -1,9 +1,8 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "contentType", Namespace = "")] +public class MemberTypeDisplay : ContentTypeCompositionDisplay { - [DataContract(Name = "contentType", Namespace = "")] - public class MemberTypeDisplay : ContentTypeCompositionDisplay - { - } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberTypeSave.cs b/src/Umbraco.Core/Models/ContentEditing/MemberTypeSave.cs index 80ac46ae099b..f9c49190241d 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberTypeSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberTypeSave.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Model used to save a member type +/// +public class MemberTypeSave : ContentTypeSave { - /// - /// Model used to save a member type - /// - public class MemberTypeSave : ContentTypeSave - { - } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MessagesExtensions.cs b/src/Umbraco.Core/Models/ContentEditing/MessagesExtensions.cs index 5a93ae94c906..760f6b1ba031 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MessagesExtensions.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MessagesExtensions.cs @@ -1,70 +1,73 @@ -using System.Linq; -using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Models.ContentEditing; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class MessagesExtensions { - public static class MessagesExtensions + public static void AddNotification(this INotificationModel model, string header, string msg, NotificationStyle type) { - public static void AddNotification(this INotificationModel model, string header, string msg, NotificationStyle type) + if (model.Exists(header, msg, type)) { - if (model.Exists(header, msg, type)) return; - - model.Notifications?.Add(new BackOfficeNotification() - { - Header = header, - Message = msg, - NotificationType = type - }); + return; } - public static void AddSuccessNotification(this INotificationModel model, string header, string msg) - { - if (model.Exists(header, msg, NotificationStyle.Success)) return; + model.Notifications?.Add(new BackOfficeNotification {Header = header, Message = msg, NotificationType = type}); + } - model.Notifications?.Add(new BackOfficeNotification() - { - Header = header, - Message = msg, - NotificationType = NotificationStyle.Success - }); + public static void AddSuccessNotification(this INotificationModel model, string header, string msg) + { + if (model.Exists(header, msg, NotificationStyle.Success)) + { + return; } - public static void AddErrorNotification(this INotificationModel model, string? header, string msg) + model.Notifications?.Add(new BackOfficeNotification { - if (model.Exists(header, msg, NotificationStyle.Error)) return; + Header = header, Message = msg, NotificationType = NotificationStyle.Success + }); + } - model.Notifications?.Add(new BackOfficeNotification() - { - Header = header, - Message = msg, - NotificationType = NotificationStyle.Error - }); + public static void AddErrorNotification(this INotificationModel model, string? header, string msg) + { + if (model.Exists(header, msg, NotificationStyle.Error)) + { + return; } - public static void AddWarningNotification(this INotificationModel model, string header, string msg) + model.Notifications?.Add(new BackOfficeNotification { - if (model.Exists(header, msg, NotificationStyle.Warning)) return; + Header = header, Message = msg, NotificationType = NotificationStyle.Error + }); + } - model.Notifications?.Add(new BackOfficeNotification() - { - Header = header, - Message = msg, - NotificationType = NotificationStyle.Warning - }); + public static void AddWarningNotification(this INotificationModel model, string header, string msg) + { + if (model.Exists(header, msg, NotificationStyle.Warning)) + { + return; } - public static void AddInfoNotification(this INotificationModel model, string header, string msg) + model.Notifications?.Add(new BackOfficeNotification { - if (model.Exists(header, msg, NotificationStyle.Info)) return; + Header = header, Message = msg, NotificationType = NotificationStyle.Warning + }); + } - model.Notifications?.Add(new BackOfficeNotification() - { - Header = header, - Message = msg, - NotificationType = NotificationStyle.Info - }); + public static void AddInfoNotification(this INotificationModel model, string header, string msg) + { + if (model.Exists(header, msg, NotificationStyle.Info)) + { + return; } - private static bool Exists(this INotificationModel model, string? header, string message, NotificationStyle notificationType) => model.Notifications?.Any(x => (x.Header?.InvariantEquals(header) ?? false) && (x.Message?.InvariantEquals(message) ?? false) && x.NotificationType == notificationType) ?? false; + model.Notifications?.Add(new BackOfficeNotification + { + Header = header, Message = msg, NotificationType = NotificationStyle.Info + }); } + + private static bool Exists(this INotificationModel model, string? header, string message, + NotificationStyle notificationType) => model.Notifications?.Any(x => + (x.Header?.InvariantEquals(header) ?? false) && (x.Message?.InvariantEquals(message) ?? false) && + x.NotificationType == notificationType) ?? false; } diff --git a/src/Umbraco.Core/Models/ContentEditing/ModelWithNotifications.cs b/src/Umbraco.Core/Models/ContentEditing/ModelWithNotifications.cs index d79be81725e5..a93acf28aa07 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ModelWithNotifications.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ModelWithNotifications.cs @@ -1,31 +1,30 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// A generic model supporting notifications, this is useful for returning any model type to include notifications from +/// api controllers +/// +/// +[DataContract(Name = "model", Namespace = "")] +public class ModelWithNotifications : INotificationModel { - /// - /// A generic model supporting notifications, this is useful for returning any model type to include notifications from api controllers - /// - /// - [DataContract(Name = "model", Namespace = "")] - public class ModelWithNotifications : INotificationModel + public ModelWithNotifications(T value) { - public ModelWithNotifications(T value) - { - Value = value; - Notifications = new List(); - } + Value = value; + Notifications = new List(); + } - /// - /// The generic value - /// - [DataMember(Name = "value")] - public T Value { get; private set; } + /// + /// The generic value + /// + [DataMember(Name = "value")] + public T Value { get; private set; } - /// - /// The notifications - /// - [DataMember(Name = "notifications")] - public List Notifications { get; private set; } - } + /// + /// The notifications + /// + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MoveOrCopy.cs b/src/Umbraco.Core/Models/ContentEditing/MoveOrCopy.cs index c27cf70ccfe5..3fa3d25b73d8 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MoveOrCopy.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MoveOrCopy.cs @@ -1,41 +1,39 @@ using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// A model representing a model for moving or copying +/// +[DataContract(Name = "content", Namespace = "")] +public class MoveOrCopy { /// - /// A model representing a model for moving or copying + /// The Id of the node to move or copy to /// - [DataContract(Name = "content", Namespace = "")] - public class MoveOrCopy - { - /// - /// The Id of the node to move or copy to - /// - [DataMember(Name = "parentId", IsRequired = true)] - [Required] - public int ParentId { get; set; } - - /// - /// The id of the node to move or copy - /// - [DataMember(Name = "id", IsRequired = true)] - [Required] - public int Id { get; set; } + [DataMember(Name = "parentId", IsRequired = true)] + [Required] + public int ParentId { get; set; } - /// - /// Boolean indicating whether copying the object should create a relation to it's original - /// - [DataMember(Name = "relateToOriginal", IsRequired = true)] - [Required] - public bool RelateToOriginal { get; set; } + /// + /// The id of the node to move or copy + /// + [DataMember(Name = "id", IsRequired = true)] + [Required] + public int Id { get; set; } - /// - /// Boolean indicating whether copying the object should be recursive - /// - [DataMember(Name = "recursive", IsRequired = true)] - [Required] - public bool Recursive { get; set; } - } + /// + /// Boolean indicating whether copying the object should create a relation to it's original + /// + [DataMember(Name = "relateToOriginal", IsRequired = true)] + [Required] + public bool RelateToOriginal { get; set; } + /// + /// Boolean indicating whether copying the object should be recursive + /// + [DataMember(Name = "recursive", IsRequired = true)] + [Required] + public bool Recursive { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/NotificationStyle.cs b/src/Umbraco.Core/Models/ContentEditing/NotificationStyle.cs index a8c17d1850a5..d645b135fdbd 100644 --- a/src/Umbraco.Core/Models/ContentEditing/NotificationStyle.cs +++ b/src/Umbraco.Core/Models/ContentEditing/NotificationStyle.cs @@ -1,29 +1,32 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract] +public enum NotificationStyle { - [DataContract] - public enum NotificationStyle - { - /// - /// Save icon - /// - Save = 0, - /// - /// Info icon - /// - Info = 1, - /// - /// Error icon - /// - Error = 2, - /// - /// Success icon - /// - Success = 3, - /// - /// Warning icon - /// - Warning = 4 - } + /// + /// Save icon + /// + Save = 0, + + /// + /// Info icon + /// + Info = 1, + + /// + /// Error icon + /// + Error = 2, + + /// + /// Success icon + /// + Success = 3, + + /// + /// Warning icon + /// + Warning = 4 } diff --git a/src/Umbraco.Core/Models/ContentEditing/NotifySetting.cs b/src/Umbraco.Core/Models/ContentEditing/NotifySetting.cs index ee4029cab3fb..8fefe34065a0 100644 --- a/src/Umbraco.Core/Models/ContentEditing/NotifySetting.cs +++ b/src/Umbraco.Core/Models/ContentEditing/NotifySetting.cs @@ -1,20 +1,17 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "notifySetting", Namespace = "")] +public class NotifySetting { - [DataContract(Name = "notifySetting", Namespace = "")] - public class NotifySetting - { - [DataMember(Name = "name")] - public string? Name { get; set; } + [DataMember(Name = "name")] public string? Name { get; set; } - [DataMember(Name = "checked")] - public bool Checked { get; set; } + [DataMember(Name = "checked")] public bool Checked { get; set; } - /// - /// The letter from the IAction - /// - [DataMember(Name = "notifyCode")] - public string? NotifyCode { get; set; } - } + /// + /// The letter from the IAction + /// + [DataMember(Name = "notifyCode")] + public string? NotifyCode { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ObjectType.cs b/src/Umbraco.Core/Models/ContentEditing/ObjectType.cs index 4682b752b9f1..e95fb7484a3d 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ObjectType.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ObjectType.cs @@ -1,15 +1,11 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "objectType", Namespace = "")] +public class ObjectType { - [DataContract(Name = "objectType", Namespace = "")] - public class ObjectType - { - [DataMember(Name = "name")] - public string? Name { get; set; } + [DataMember(Name = "name")] public string? Name { get; set; } - [DataMember(Name = "id")] - public Guid Id { get; set; } - } + [DataMember(Name = "id")] public Guid Id { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/Permission.cs b/src/Umbraco.Core/Models/ContentEditing/Permission.cs index c6e446fc39ba..388c842de0b4 100644 --- a/src/Umbraco.Core/Models/ContentEditing/Permission.cs +++ b/src/Umbraco.Core/Models/ContentEditing/Permission.cs @@ -1,38 +1,29 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "permission", Namespace = "")] +public class Permission : ICloneable { - [DataContract(Name = "permission", Namespace = "")] - public class Permission : ICloneable - { - [DataMember(Name = "name")] - public string? Name { get; set; } - - [DataMember(Name = "description")] - public string? Description { get; set; } - - [DataMember(Name = "checked")] - public bool Checked { get; set; } - - [DataMember(Name = "icon")] - public string? Icon { get; set; } - - /// - /// We'll use this to map the categories but it wont' be returned in the json - /// - [IgnoreDataMember] - public string Category { get; set; } = null!; - - /// - /// The letter from the IAction - /// - [DataMember(Name = "permissionCode")] - public string? PermissionCode { get; set; } - - public object Clone() - { - return this.MemberwiseClone(); - } - } + [DataMember(Name = "name")] public string? Name { get; set; } + + [DataMember(Name = "description")] public string? Description { get; set; } + + [DataMember(Name = "checked")] public bool Checked { get; set; } + + [DataMember(Name = "icon")] public string? Icon { get; set; } + + /// + /// We'll use this to map the categories but it wont' be returned in the json + /// + [IgnoreDataMember] + public string Category { get; set; } = null!; + + /// + /// The letter from the IAction + /// + [DataMember(Name = "permissionCode")] + public string? PermissionCode { get; set; } + + public object Clone() => MemberwiseClone(); } diff --git a/src/Umbraco.Core/Models/ContentEditing/PostedFiles.cs b/src/Umbraco.Core/Models/ContentEditing/PostedFiles.cs index 69029c961a33..e2e4dae6c5cc 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PostedFiles.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PostedFiles.cs @@ -1,24 +1,22 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Editors; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// This is used for the response of PostAddFile so that we can analyze the response in a filter and remove the +/// temporary files that were created. +/// +[DataContract] +public class PostedFiles : IHaveUploadedFiles, INotificationModel { - /// - /// This is used for the response of PostAddFile so that we can analyze the response in a filter and remove the - /// temporary files that were created. - /// - [DataContract] - public class PostedFiles : IHaveUploadedFiles, INotificationModel + public PostedFiles() { - public PostedFiles() - { - UploadedFiles = new List(); - Notifications = new List(); - } - public List UploadedFiles { get; private set; } - - [DataMember(Name = "notifications")] - public List Notifications { get; private set; } + UploadedFiles = new List(); + Notifications = new List(); } + + public List UploadedFiles { get; } + + [DataMember(Name = "notifications")] public List Notifications { get; private set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/PostedFolder.cs b/src/Umbraco.Core/Models/ContentEditing/PostedFolder.cs index 56ca1c19071d..148d15f93da6 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PostedFolder.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PostedFolder.cs @@ -1,17 +1,14 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Used to create a folder with the MediaController +/// +[DataContract] +public class PostedFolder { - /// - /// Used to create a folder with the MediaController - /// - [DataContract] - public class PostedFolder - { - [DataMember(Name = "parentId")] - public string? ParentId { get; set; } + [DataMember(Name = "parentId")] public string? ParentId { get; set; } - [DataMember(Name = "name")] - public string? Name { get; set; } - } + [DataMember(Name = "name")] public string? Name { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/PropertyEditorBasic.cs b/src/Umbraco.Core/Models/ContentEditing/PropertyEditorBasic.cs index b73f2897e7d4..5f82d4361005 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PropertyEditorBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PropertyEditorBasic.cs @@ -1,20 +1,16 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Defines an available property editor to be able to select for a data type +/// +[DataContract(Name = "propertyEditor", Namespace = "")] +public class PropertyEditorBasic { - /// - /// Defines an available property editor to be able to select for a data type - /// - [DataContract(Name = "propertyEditor", Namespace = "")] - public class PropertyEditorBasic - { - [DataMember(Name = "alias")] - public string? Alias { get; set; } + [DataMember(Name = "alias")] public string? Alias { get; set; } - [DataMember(Name = "name")] - public string? Name { get; set; } + [DataMember(Name = "name")] public string? Name { get; set; } - [DataMember(Name = "icon")] - public string? Icon { get; set; } - } + [DataMember(Name = "icon")] public string? Icon { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/PropertyGroupBasic.cs b/src/Umbraco.Core/Models/ContentEditing/PropertyGroupBasic.cs index 0431fb270fed..8d5dbb5b6adb 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PropertyGroupBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PropertyGroupBasic.cs @@ -1,66 +1,55 @@ -using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing -{ - [DataContract(Name = "propertyGroup", Namespace = "")] - public abstract class PropertyGroupBasic - { - /// - /// Gets the special generic properties tab identifier. - /// - public const int GenericPropertiesGroupId = -666; - - /// - /// Gets a value indicating whether this tab is the generic properties tab. - /// - [IgnoreDataMember] - public bool IsGenericProperties => Id == GenericPropertiesGroupId; - - /// - /// Gets a value indicating whether the property group is inherited through - /// content types composition. - /// - /// A property group can be inherited and defined on the content type - /// currently being edited, at the same time. Inherited is true when there exists at least - /// one property group higher in the composition, with the same alias. - [DataMember(Name = "inherited")] - public bool Inherited { get; set; } - - // needed - so we can handle alias renames - [DataMember(Name = "id")] - public int Id { get; set; } - - [DataMember(Name = "key")] - public Guid Key { get; set; } - - [DataMember(Name = "type")] - public PropertyGroupType Type { get; set; } +namespace Umbraco.Cms.Core.Models.ContentEditing; - [Required] - [DataMember(Name = "name")] - public string? Name { get; set; } - - [Required] - [DataMember(Name = "alias")] - public string Alias { get; set; } = null!; - - [DataMember(Name = "sortOrder")] - public int SortOrder { get; set; } - } +[DataContract(Name = "propertyGroup", Namespace = "")] +public abstract class PropertyGroupBasic +{ + /// + /// Gets the special generic properties tab identifier. + /// + public const int GenericPropertiesGroupId = -666; + + /// + /// Gets a value indicating whether this tab is the generic properties tab. + /// + [IgnoreDataMember] + public bool IsGenericProperties => Id == GenericPropertiesGroupId; + + /// + /// Gets a value indicating whether the property group is inherited through + /// content types composition. + /// + /// + /// A property group can be inherited and defined on the content type + /// currently being edited, at the same time. Inherited is true when there exists at least + /// one property group higher in the composition, with the same alias. + /// + [DataMember(Name = "inherited")] + public bool Inherited { get; set; } + + // needed - so we can handle alias renames + [DataMember(Name = "id")] public int Id { get; set; } + + [DataMember(Name = "key")] public Guid Key { get; set; } + + [DataMember(Name = "type")] public PropertyGroupType Type { get; set; } + + [Required] [DataMember(Name = "name")] public string? Name { get; set; } + + [Required] + [DataMember(Name = "alias")] + public string Alias { get; set; } = null!; + + [DataMember(Name = "sortOrder")] public int SortOrder { get; set; } +} - [DataContract(Name = "propertyGroup", Namespace = "")] - public class PropertyGroupBasic : PropertyGroupBasic - where TPropertyType: PropertyTypeBasic - { - public PropertyGroupBasic() - { - Properties = new List(); - } +[DataContract(Name = "propertyGroup", Namespace = "")] +public class PropertyGroupBasic : PropertyGroupBasic + where TPropertyType : PropertyTypeBasic +{ + public PropertyGroupBasic() => Properties = new List(); - [DataMember(Name = "properties")] - public IEnumerable Properties { get; set; } - } + [DataMember(Name = "properties")] public IEnumerable Properties { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/PropertyGroupBasicExtensions.cs b/src/Umbraco.Core/Models/ContentEditing/PropertyGroupBasicExtensions.cs index 6f1317f3eb86..1e38ceed3569 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PropertyGroupBasicExtensions.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PropertyGroupBasicExtensions.cs @@ -1,8 +1,7 @@ -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +internal static class PropertyGroupBasicExtensions { - internal static class PropertyGroupBasicExtensions - { - public static string? GetParentAlias(this PropertyGroupBasic propertyGroup) - => PropertyGroupExtensions.GetParentAlias(propertyGroup.Alias); - } + public static string? GetParentAlias(this PropertyGroupBasic propertyGroup) + => PropertyGroupExtensions.GetParentAlias(propertyGroup.Alias); } diff --git a/src/Umbraco.Core/Models/ContentEditing/PropertyGroupDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/PropertyGroupDisplay.cs index a543d8534766..9c8cd30b8750 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PropertyGroupDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PropertyGroupDisplay.cs @@ -1,39 +1,37 @@ -using System.Collections.Generic; -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "propertyGroup", Namespace = "")] +public class PropertyGroupDisplay : PropertyGroupBasic + where TPropertyTypeDisplay : PropertyTypeDisplay { - [DataContract(Name = "propertyGroup", Namespace = "")] - public class PropertyGroupDisplay : PropertyGroupBasic - where TPropertyTypeDisplay : PropertyTypeDisplay + public PropertyGroupDisplay() { - public PropertyGroupDisplay() - { - Properties = new List(); - ParentTabContentTypeNames = new List(); - ParentTabContentTypes = new List(); - } + Properties = new List(); + ParentTabContentTypeNames = new List(); + ParentTabContentTypes = new List(); + } - /// - /// Gets the context content type. - /// - [DataMember(Name = "contentTypeId")] - [ReadOnly(true)] - public int ContentTypeId { get; set; } + /// + /// Gets the context content type. + /// + [DataMember(Name = "contentTypeId")] + [ReadOnly(true)] + public int ContentTypeId { get; set; } - /// - /// Gets the identifiers of the content types that define this group. - /// - [DataMember(Name = "parentTabContentTypes")] - [ReadOnly(true)] - public IEnumerable ParentTabContentTypes { get; set; } + /// + /// Gets the identifiers of the content types that define this group. + /// + [DataMember(Name = "parentTabContentTypes")] + [ReadOnly(true)] + public IEnumerable ParentTabContentTypes { get; set; } - /// - /// Gets the name of the content types that define this group. - /// - [DataMember(Name = "parentTabContentTypeNames")] - [ReadOnly(true)] - public IEnumerable ParentTabContentTypeNames { get; set; } - } + /// + /// Gets the name of the content types that define this group. + /// + [DataMember(Name = "parentTabContentTypeNames")] + [ReadOnly(true)] + public IEnumerable ParentTabContentTypeNames { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/PropertyTypeBasic.cs b/src/Umbraco.Core/Models/ContentEditing/PropertyTypeBasic.cs index 0aded31a1861..4e4b5e33d2b3 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PropertyTypeBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PropertyTypeBasic.cs @@ -1,72 +1,66 @@ -using System; -using System.ComponentModel; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "propertyType")] +public class PropertyTypeBasic { - [DataContract(Name = "propertyType")] - public class PropertyTypeBasic - { - /// - /// Gets a value indicating whether the property type is inherited through - /// content types composition. - /// - /// Inherited is true when the property is defined by a content type - /// higher in the composition, and not by the content type currently being - /// edited. - [DataMember(Name = "inherited")] - public bool Inherited { get; set; } - - // needed - so we can handle alias renames - [DataMember(Name = "id")] - public int Id { get; set; } - - [Required] - [RegularExpression(@"^([a-zA-Z]\w.*)$", ErrorMessage = "Invalid alias")] - [DataMember(Name = "alias")] - public string Alias { get; set; } = null!; - - [DataMember(Name = "description")] - public string? Description { get; set; } - - [DataMember(Name = "validation")] - public PropertyTypeValidation? Validation { get; set; } - - [DataMember(Name = "label")] - [Required] - public string Label { get; set; } = null!; - - [DataMember(Name = "sortOrder")] - public int SortOrder { get; set; } - - [DataMember(Name = "dataTypeId")] - [Required] - public int DataTypeId { get; set; } - - [DataMember(Name = "dataTypeKey")] - [ReadOnly(true)] - public Guid DataTypeKey { get; set; } - - [DataMember(Name = "dataTypeName")] - [ReadOnly(true)] - public string? DataTypeName { get; set; } - - [DataMember(Name = "dataTypeIcon")] - [ReadOnly(true)] - public string? DataTypeIcon { get; set; } - - //SD: Is this really needed ? - [DataMember(Name = "groupId")] - public int GroupId { get; set; } - - [DataMember(Name = "allowCultureVariant")] - public bool AllowCultureVariant { get; set; } - - [DataMember(Name = "allowSegmentVariant")] - public bool AllowSegmentVariant { get; set; } - - [DataMember(Name = "labelOnTop")] - public bool LabelOnTop { get; set; } - } + /// + /// Gets a value indicating whether the property type is inherited through + /// content types composition. + /// + /// + /// Inherited is true when the property is defined by a content type + /// higher in the composition, and not by the content type currently being + /// edited. + /// + [DataMember(Name = "inherited")] + public bool Inherited { get; set; } + + // needed - so we can handle alias renames + [DataMember(Name = "id")] public int Id { get; set; } + + [Required] + [RegularExpression(@"^([a-zA-Z]\w.*)$", ErrorMessage = "Invalid alias")] + [DataMember(Name = "alias")] + public string Alias { get; set; } = null!; + + [DataMember(Name = "description")] public string? Description { get; set; } + + [DataMember(Name = "validation")] public PropertyTypeValidation? Validation { get; set; } + + [DataMember(Name = "label")] + [Required] + public string Label { get; set; } = null!; + + [DataMember(Name = "sortOrder")] public int SortOrder { get; set; } + + [DataMember(Name = "dataTypeId")] + [Required] + public int DataTypeId { get; set; } + + [DataMember(Name = "dataTypeKey")] + [ReadOnly(true)] + public Guid DataTypeKey { get; set; } + + [DataMember(Name = "dataTypeName")] + [ReadOnly(true)] + public string? DataTypeName { get; set; } + + [DataMember(Name = "dataTypeIcon")] + [ReadOnly(true)] + public string? DataTypeIcon { get; set; } + + //SD: Is this really needed ? + [DataMember(Name = "groupId")] public int GroupId { get; set; } + + [DataMember(Name = "allowCultureVariant")] + public bool AllowCultureVariant { get; set; } + + [DataMember(Name = "allowSegmentVariant")] + public bool AllowSegmentVariant { get; set; } + + [DataMember(Name = "labelOnTop")] public bool LabelOnTop { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/PropertyTypeDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/PropertyTypeDisplay.cs index 5ca3e4de5c28..17edd12c0b9c 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PropertyTypeDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PropertyTypeDisplay.cs @@ -1,48 +1,47 @@ -using System.Collections.Generic; -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing -{ - [DataContract(Name = "propertyType")] - public class PropertyTypeDisplay : PropertyTypeBasic - { - [DataMember(Name = "editor")] - [ReadOnly(true)] - public string? Editor { get; set; } +namespace Umbraco.Cms.Core.Models.ContentEditing; - [DataMember(Name = "view")] - [ReadOnly(true)] - public string? View { get; set; } +[DataContract(Name = "propertyType")] +public class PropertyTypeDisplay : PropertyTypeBasic +{ + [DataMember(Name = "editor")] + [ReadOnly(true)] + public string? Editor { get; set; } - [DataMember(Name = "config")] - [ReadOnly(true)] - public IDictionary? Config { get; set; } + [DataMember(Name = "view")] + [ReadOnly(true)] + public string? View { get; set; } - /// - /// Gets a value indicating whether this property should be locked when editing. - /// - /// This is used for built in properties like the default MemberType - /// properties that should not be editable from the backoffice. - [DataMember(Name = "locked")] - [ReadOnly(true)] - public bool Locked { get; set; } + [DataMember(Name = "config")] + [ReadOnly(true)] + public IDictionary? Config { get; set; } - /// - /// This is required for the UI editor to know if this particular property belongs to - /// an inherited item or the current item. - /// - [DataMember(Name = "contentTypeId")] - [ReadOnly(true)] - public int ContentTypeId { get; set; } + /// + /// Gets a value indicating whether this property should be locked when editing. + /// + /// + /// This is used for built in properties like the default MemberType + /// properties that should not be editable from the backoffice. + /// + [DataMember(Name = "locked")] + [ReadOnly(true)] + public bool Locked { get; set; } - /// - /// This is required for the UI editor to know which content type name this property belongs - /// to based on the property inheritance structure - /// - [DataMember(Name = "contentTypeName")] - [ReadOnly(true)] - public string? ContentTypeName { get; set; } + /// + /// This is required for the UI editor to know if this particular property belongs to + /// an inherited item or the current item. + /// + [DataMember(Name = "contentTypeId")] + [ReadOnly(true)] + public int ContentTypeId { get; set; } - } + /// + /// This is required for the UI editor to know which content type name this property belongs + /// to based on the property inheritance structure + /// + [DataMember(Name = "contentTypeName")] + [ReadOnly(true)] + public string? ContentTypeName { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/PropertyTypeValidation.cs b/src/Umbraco.Core/Models/ContentEditing/PropertyTypeValidation.cs index 5db1ab813919..3fac28a49b8e 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PropertyTypeValidation.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PropertyTypeValidation.cs @@ -1,23 +1,19 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// An object representing the property type validation settings +/// +[DataContract(Name = "propertyValidation", Namespace = "")] +public class PropertyTypeValidation { - /// - /// An object representing the property type validation settings - /// - [DataContract(Name = "propertyValidation", Namespace = "")] - public class PropertyTypeValidation - { - [DataMember(Name = "mandatory")] - public bool Mandatory { get; set; } + [DataMember(Name = "mandatory")] public bool Mandatory { get; set; } - [DataMember(Name = "mandatoryMessage")] - public string? MandatoryMessage { get; set; } + [DataMember(Name = "mandatoryMessage")] + public string? MandatoryMessage { get; set; } - [DataMember(Name = "pattern")] - public string? Pattern { get; set; } + [DataMember(Name = "pattern")] public string? Pattern { get; set; } - [DataMember(Name = "patternMessage")] - public string? PatternMessage { get; set; } - } + [DataMember(Name = "patternMessage")] public string? PatternMessage { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/PublicAccess.cs b/src/Umbraco.Core/Models/ContentEditing/PublicAccess.cs index 199ca34ceb3e..1772158cce30 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PublicAccess.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PublicAccess.cs @@ -1,20 +1,15 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "publicAccess", Namespace = "")] +public class PublicAccess { - [DataContract(Name = "publicAccess", Namespace = "")] - public class PublicAccess - { - [DataMember(Name = "groups")] - public MemberGroupDisplay[]? Groups { get; set; } + [DataMember(Name = "groups")] public MemberGroupDisplay[]? Groups { get; set; } - [DataMember(Name = "loginPage")] - public EntityBasic? LoginPage { get; set; } + [DataMember(Name = "loginPage")] public EntityBasic? LoginPage { get; set; } - [DataMember(Name = "errorPage")] - public EntityBasic? ErrorPage { get; set; } + [DataMember(Name = "errorPage")] public EntityBasic? ErrorPage { get; set; } - [DataMember(Name = "members")] - public MemberDisplay[]? Members { get; set; } - } + [DataMember(Name = "members")] public MemberDisplay[]? Members { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/RedirectUrlSearchResults.cs b/src/Umbraco.Core/Models/ContentEditing/RedirectUrlSearchResults.cs index e4b026b6eb36..e2116b53b11c 100644 --- a/src/Umbraco.Core/Models/ContentEditing/RedirectUrlSearchResults.cs +++ b/src/Umbraco.Core/Models/ContentEditing/RedirectUrlSearchResults.cs @@ -1,21 +1,15 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "redirectUrlSearchResult", Namespace = "")] +public class RedirectUrlSearchResult { - [DataContract(Name = "redirectUrlSearchResult", Namespace = "")] - public class RedirectUrlSearchResult - { - [DataMember(Name = "searchResults")] - public IEnumerable? SearchResults { get; set; } + [DataMember(Name = "searchResults")] public IEnumerable? SearchResults { get; set; } - [DataMember(Name = "totalCount")] - public long TotalCount { get; set; } + [DataMember(Name = "totalCount")] public long TotalCount { get; set; } - [DataMember(Name = "pageCount")] - public int PageCount { get; set; } + [DataMember(Name = "pageCount")] public int PageCount { get; set; } - [DataMember(Name = "currentPage")] - public int CurrentPage { get; set; } - } + [DataMember(Name = "currentPage")] public int CurrentPage { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/RelationDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/RelationDisplay.cs index 0decb18414a9..de1c3b3db0bb 100644 --- a/src/Umbraco.Core/Models/ContentEditing/RelationDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/RelationDisplay.cs @@ -1,52 +1,50 @@ -using System; -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "relation", Namespace = "")] +public class RelationDisplay { - [DataContract(Name = "relation", Namespace = "")] - public class RelationDisplay - { - /// - /// Gets or sets the Parent Id of the Relation (Source). - /// - [DataMember(Name = "parentId")] - [ReadOnly(true)] - public int ParentId { get; set; } + /// + /// Gets or sets the Parent Id of the Relation (Source). + /// + [DataMember(Name = "parentId")] + [ReadOnly(true)] + public int ParentId { get; set; } - /// - /// Gets or sets the Parent Name of the relation (Source). - /// - [DataMember(Name = "parentName")] - [ReadOnly(true)] - public string? ParentName { get; set; } + /// + /// Gets or sets the Parent Name of the relation (Source). + /// + [DataMember(Name = "parentName")] + [ReadOnly(true)] + public string? ParentName { get; set; } - /// - /// Gets or sets the Child Id of the Relation (Destination). - /// - [DataMember(Name = "childId")] - [ReadOnly(true)] - public int ChildId { get; set; } + /// + /// Gets or sets the Child Id of the Relation (Destination). + /// + [DataMember(Name = "childId")] + [ReadOnly(true)] + public int ChildId { get; set; } - /// - /// Gets or sets the Child Name of the relation (Destination). - /// - [DataMember(Name = "childName")] - [ReadOnly(true)] - public string? ChildName { get; set; } + /// + /// Gets or sets the Child Name of the relation (Destination). + /// + [DataMember(Name = "childName")] + [ReadOnly(true)] + public string? ChildName { get; set; } - /// - /// Gets or sets the date when the Relation was created. - /// - [DataMember(Name = "createDate")] - [ReadOnly(true)] - public DateTime CreateDate { get; set; } + /// + /// Gets or sets the date when the Relation was created. + /// + [DataMember(Name = "createDate")] + [ReadOnly(true)] + public DateTime CreateDate { get; set; } - /// - /// Gets or sets a comment for the Relation. - /// - [DataMember(Name = "comment")] - [ReadOnly(true)] - public string? Comment { get; set; } - } + /// + /// Gets or sets a comment for the Relation. + /// + [DataMember(Name = "comment")] + [ReadOnly(true)] + public string? Comment { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/RelationTypeDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/RelationTypeDisplay.cs index 906fdf3a40e2..e3fee5931c8f 100644 --- a/src/Umbraco.Core/Models/ContentEditing/RelationTypeDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/RelationTypeDisplay.cs @@ -1,65 +1,59 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing -{ - [DataContract(Name = "relationType", Namespace = "")] - public class RelationTypeDisplay : EntityBasic, INotificationModel - { - public RelationTypeDisplay() - { - Notifications = new List(); - } - - [DataMember(Name = "isSystemRelationType")] - public bool IsSystemRelationType { get; set; } - - /// - /// Gets or sets a boolean indicating whether the RelationType is Bidirectional (true) or Parent to Child (false) - /// - [DataMember(Name = "isBidirectional", IsRequired = true)] - public bool IsBidirectional { get; set; } - - /// - /// Gets or sets the Parents object type id - /// - /// Corresponds to the NodeObjectType in the umbracoNode table - [DataMember(Name = "parentObjectType", IsRequired = true)] - public Guid? ParentObjectType { get; set; } - - /// - /// Gets or sets the Parent's object type name. - /// - [DataMember(Name = "parentObjectTypeName")] - [ReadOnly(true)] - public string? ParentObjectTypeName { get; set; } +namespace Umbraco.Cms.Core.Models.ContentEditing; - /// - /// Gets or sets the Child's object type id - /// - /// Corresponds to the NodeObjectType in the umbracoNode table - [DataMember(Name = "childObjectType", IsRequired = true)] - public Guid? ChildObjectType { get; set; } - - /// - /// Gets or sets the Child's object type name. - /// - [DataMember(Name = "childObjectTypeName")] - [ReadOnly(true)] - public string? ChildObjectTypeName { get; set; } - - /// - /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. - /// - [DataMember(Name = "notifications")] - public List Notifications { get; private set; } - - /// - /// Gets or sets a boolean indicating whether the RelationType should be returned in "Used by"-queries. - /// - [DataMember(Name = "isDependency", IsRequired = true)] - public bool IsDependency { get; set; } - } +[DataContract(Name = "relationType", Namespace = "")] +public class RelationTypeDisplay : EntityBasic, INotificationModel +{ + public RelationTypeDisplay() => Notifications = new List(); + + [DataMember(Name = "isSystemRelationType")] + public bool IsSystemRelationType { get; set; } + + /// + /// Gets or sets a boolean indicating whether the RelationType is Bidirectional (true) or Parent to Child (false) + /// + [DataMember(Name = "isBidirectional", IsRequired = true)] + public bool IsBidirectional { get; set; } + + /// + /// Gets or sets the Parents object type id + /// + /// Corresponds to the NodeObjectType in the umbracoNode table + [DataMember(Name = "parentObjectType", IsRequired = true)] + public Guid? ParentObjectType { get; set; } + + /// + /// Gets or sets the Parent's object type name. + /// + [DataMember(Name = "parentObjectTypeName")] + [ReadOnly(true)] + public string? ParentObjectTypeName { get; set; } + + /// + /// Gets or sets the Child's object type id + /// + /// Corresponds to the NodeObjectType in the umbracoNode table + [DataMember(Name = "childObjectType", IsRequired = true)] + public Guid? ChildObjectType { get; set; } + + /// + /// Gets or sets the Child's object type name. + /// + [DataMember(Name = "childObjectTypeName")] + [ReadOnly(true)] + public string? ChildObjectTypeName { get; set; } + + /// + /// Gets or sets a boolean indicating whether the RelationType should be returned in "Used by"-queries. + /// + [DataMember(Name = "isDependency", IsRequired = true)] + public bool IsDependency { get; set; } + + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/RelationTypeSave.cs b/src/Umbraco.Core/Models/ContentEditing/RelationTypeSave.cs index f541158095d9..12f9c64b0c88 100644 --- a/src/Umbraco.Core/Models/ContentEditing/RelationTypeSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/RelationTypeSave.cs @@ -1,33 +1,31 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "relationType", Namespace = "")] +public class RelationTypeSave : EntityBasic { - [DataContract(Name = "relationType", Namespace = "")] - public class RelationTypeSave : EntityBasic - { - /// - /// Gets or sets a boolean indicating whether the RelationType is Bidirectional (true) or Parent to Child (false) - /// - [DataMember(Name = "isBidirectional", IsRequired = true)] - public bool IsBidirectional { get; set; } + /// + /// Gets or sets a boolean indicating whether the RelationType is Bidirectional (true) or Parent to Child (false) + /// + [DataMember(Name = "isBidirectional", IsRequired = true)] + public bool IsBidirectional { get; set; } - /// - /// Gets or sets the parent object type ID. - /// - [DataMember(Name = "parentObjectType", IsRequired = false)] - public Guid? ParentObjectType { get; set; } + /// + /// Gets or sets the parent object type ID. + /// + [DataMember(Name = "parentObjectType", IsRequired = false)] + public Guid? ParentObjectType { get; set; } - /// - /// Gets or sets the child object type ID. - /// - [DataMember(Name = "childObjectType", IsRequired = false)] - public Guid? ChildObjectType { get; set; } + /// + /// Gets or sets the child object type ID. + /// + [DataMember(Name = "childObjectType", IsRequired = false)] + public Guid? ChildObjectType { get; set; } - /// - /// Gets or sets a boolean indicating whether the RelationType should be returned in "Used by"-queries. - /// - [DataMember(Name = "isDependency", IsRequired = true)] - public bool IsDependency { get; set; } - } + /// + /// Gets or sets a boolean indicating whether the RelationType should be returned in "Used by"-queries. + /// + [DataMember(Name = "isDependency", IsRequired = true)] + public bool IsDependency { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/RichTextEditorCommand.cs b/src/Umbraco.Core/Models/ContentEditing/RichTextEditorCommand.cs index 06fcc5d12415..2bee6173fd96 100644 --- a/src/Umbraco.Core/Models/ContentEditing/RichTextEditorCommand.cs +++ b/src/Umbraco.Core/Models/ContentEditing/RichTextEditorCommand.cs @@ -1,24 +1,20 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "richtexteditorcommand", Namespace = "")] +public class RichTextEditorCommand { - [DataContract(Name = "richtexteditorcommand", Namespace = "")] - public class RichTextEditorCommand - { - [DataMember(Name = "name")] - public string? Name { get; set; } + [DataMember(Name = "name")] public string? Name { get; set; } - [DataMember(Name = "alias")] - public string? Alias { get; set; } + [DataMember(Name = "alias")] public string? Alias { get; set; } - [DataMember(Name = "mode")] - public RichTextEditorCommandMode Mode { get; set; } - } + [DataMember(Name = "mode")] public RichTextEditorCommandMode Mode { get; set; } +} - public enum RichTextEditorCommandMode - { - Insert, - Selection, - All - } +public enum RichTextEditorCommandMode +{ + Insert, + Selection, + All } diff --git a/src/Umbraco.Core/Models/ContentEditing/RichTextEditorConfiguration.cs b/src/Umbraco.Core/Models/ContentEditing/RichTextEditorConfiguration.cs index e80b25f4aecd..e791a9759286 100644 --- a/src/Umbraco.Core/Models/ContentEditing/RichTextEditorConfiguration.cs +++ b/src/Umbraco.Core/Models/ContentEditing/RichTextEditorConfiguration.cs @@ -1,24 +1,17 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "richtexteditorconfiguration", Namespace = "")] +public class RichTextEditorConfiguration { - [DataContract(Name = "richtexteditorconfiguration", Namespace = "")] - public class RichTextEditorConfiguration - { - [DataMember(Name = "plugins")] - public IEnumerable? Plugins { get; set; } + [DataMember(Name = "plugins")] public IEnumerable? Plugins { get; set; } - [DataMember(Name = "commands")] - public IEnumerable? Commands { get; set; } + [DataMember(Name = "commands")] public IEnumerable? Commands { get; set; } - [DataMember(Name = "validElements")] - public string? ValidElements { get; set; } + [DataMember(Name = "validElements")] public string? ValidElements { get; set; } - [DataMember(Name = "inValidElements")] - public string? InvalidElements { get; set; } + [DataMember(Name = "inValidElements")] public string? InvalidElements { get; set; } - [DataMember(Name = "customConfig")] - public IDictionary? CustomConfig { get; set; } - } + [DataMember(Name = "customConfig")] public IDictionary? CustomConfig { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/RichTextEditorPlugin.cs b/src/Umbraco.Core/Models/ContentEditing/RichTextEditorPlugin.cs index 3740f47fc648..49cbdc63ab4e 100644 --- a/src/Umbraco.Core/Models/ContentEditing/RichTextEditorPlugin.cs +++ b/src/Umbraco.Core/Models/ContentEditing/RichTextEditorPlugin.cs @@ -1,11 +1,9 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "richtexteditorplugin", Namespace = "")] +public class RichTextEditorPlugin { - [DataContract(Name = "richtexteditorplugin", Namespace = "")] - public class RichTextEditorPlugin - { - [DataMember(Name = "name")] - public string? Name { get; set; } - } + [DataMember(Name = "name")] public string? Name { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/RollbackVersion.cs b/src/Umbraco.Core/Models/ContentEditing/RollbackVersion.cs index ca0e3ff9affe..4ea93e7da74b 100644 --- a/src/Umbraco.Core/Models/ContentEditing/RollbackVersion.cs +++ b/src/Umbraco.Core/Models/ContentEditing/RollbackVersion.cs @@ -1,21 +1,16 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "rollbackVersion", Namespace = "")] +public class RollbackVersion { - [DataContract(Name = "rollbackVersion", Namespace = "")] - public class RollbackVersion - { - [DataMember(Name = "versionId")] - public int VersionId { get; set; } + [DataMember(Name = "versionId")] public int VersionId { get; set; } - [DataMember(Name = "versionDate")] - public DateTime? VersionDate { get; set; } + [DataMember(Name = "versionDate")] public DateTime? VersionDate { get; set; } - [DataMember(Name = "versionAuthorId")] - public int VersionAuthorId { get; set; } + [DataMember(Name = "versionAuthorId")] public int VersionAuthorId { get; set; } - [DataMember(Name = "versionAuthorName")] - public string? VersionAuthorName { get; set; } - } + [DataMember(Name = "versionAuthorName")] + public string? VersionAuthorName { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/SearchResult.cs b/src/Umbraco.Core/Models/ContentEditing/SearchResult.cs index 53facfe990cf..d4f556cd6736 100644 --- a/src/Umbraco.Core/Models/ContentEditing/SearchResult.cs +++ b/src/Umbraco.Core/Models/ContentEditing/SearchResult.cs @@ -1,21 +1,15 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "result", Namespace = "")] +public class SearchResult { - [DataContract(Name = "result", Namespace = "")] - public class SearchResult - { - [DataMember(Name = "id")] - public string? Id { get; set; } + [DataMember(Name = "id")] public string? Id { get; set; } - [DataMember(Name = "score")] - public float Score { get; set; } + [DataMember(Name = "score")] public float Score { get; set; } - [DataMember(Name = "fieldCount")] - public int FieldCount => Values?.Count ?? 0; + [DataMember(Name = "fieldCount")] public int FieldCount => Values?.Count ?? 0; - [DataMember(Name = "values")] - public IReadOnlyDictionary>? Values { get; set; } - } + [DataMember(Name = "values")] public IReadOnlyDictionary>? Values { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/SearchResultEntity.cs b/src/Umbraco.Core/Models/ContentEditing/SearchResultEntity.cs index e2fc1ff2d7f7..0544c37c62b2 100644 --- a/src/Umbraco.Core/Models/ContentEditing/SearchResultEntity.cs +++ b/src/Umbraco.Core/Models/ContentEditing/SearchResultEntity.cs @@ -1,16 +1,13 @@ -using System.Collections; -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "searchResult", Namespace = "")] +public class SearchResultEntity : EntityBasic { - [DataContract(Name = "searchResult", Namespace = "")] - public class SearchResultEntity : EntityBasic - { - /// - /// The score of the search result - /// - [DataMember(Name = "score")] - public float Score { get; set; } - } + /// + /// The score of the search result + /// + [DataMember(Name = "score")] + public float Score { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/SearchResults.cs b/src/Umbraco.Core/Models/ContentEditing/SearchResults.cs index 2d550a4457ca..485347479d42 100644 --- a/src/Umbraco.Core/Models/ContentEditing/SearchResults.cs +++ b/src/Umbraco.Core/Models/ContentEditing/SearchResults.cs @@ -1,22 +1,13 @@ -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "results", Namespace = "")] +public class SearchResults { - [DataContract(Name = "results", Namespace = "")] - public class SearchResults - { - public static SearchResults Empty() => new SearchResults - { - Results = Enumerable.Empty(), - TotalRecords = 0 - }; + [DataMember(Name = "totalRecords")] public long TotalRecords { get; set; } - [DataMember(Name = "totalRecords")] - public long TotalRecords { get; set; } + [DataMember(Name = "results")] public IEnumerable? Results { get; set; } - [DataMember(Name = "results")] - public IEnumerable? Results { get; set; } - } + public static SearchResults Empty() => new() {Results = Enumerable.Empty(), TotalRecords = 0}; } diff --git a/src/Umbraco.Core/Models/ContentEditing/Section.cs b/src/Umbraco.Core/Models/ContentEditing/Section.cs index 558d73b49bec..9ff238536cec 100644 --- a/src/Umbraco.Core/Models/ContentEditing/Section.cs +++ b/src/Umbraco.Core/Models/ContentEditing/Section.cs @@ -1,24 +1,21 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Represents a section (application) in the back office +/// +[DataContract(Name = "section", Namespace = "")] +public class Section { - /// - /// Represents a section (application) in the back office - /// - [DataContract(Name = "section", Namespace = "")] - public class Section - { - [DataMember(Name = "name")] - public string Name { get; set; } = null!; + [DataMember(Name = "name")] public string Name { get; set; } = null!; - [DataMember(Name = "alias")] - public string Alias { get; set; } = null!; + [DataMember(Name = "alias")] public string Alias { get; set; } = null!; - /// - /// In some cases a custom route path can be specified so that when clicking on a section it goes to this - /// path instead of the normal dashboard path - /// - [DataMember(Name = "routePath")] - public string? RoutePath { get; set; } - } + /// + /// In some cases a custom route path can be specified so that when clicking on a section it goes to this + /// path instead of the normal dashboard path + /// + [DataMember(Name = "routePath")] + public string? RoutePath { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/SimpleNotificationModel.cs b/src/Umbraco.Core/Models/ContentEditing/SimpleNotificationModel.cs index e6db2b933a19..e8bb0533022a 100644 --- a/src/Umbraco.Core/Models/ContentEditing/SimpleNotificationModel.cs +++ b/src/Umbraco.Core/Models/ContentEditing/SimpleNotificationModel.cs @@ -1,31 +1,24 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "notificationModel", Namespace = "")] +public class SimpleNotificationModel : INotificationModel { - [DataContract(Name = "notificationModel", Namespace = "")] - public class SimpleNotificationModel : INotificationModel - { - public SimpleNotificationModel() - { - Notifications = new List(); - } + public SimpleNotificationModel() => Notifications = new List(); - public SimpleNotificationModel(params BackOfficeNotification[] notifications) - { - Notifications = new List(notifications); - } + public SimpleNotificationModel(params BackOfficeNotification[] notifications) => + Notifications = new List(notifications); - /// - /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. - /// - [DataMember(Name = "notifications")] - public List Notifications { get; private set; } + /// + /// A default message + /// + [DataMember(Name = "message")] + public string? Message { get; set; } - /// - /// A default message - /// - [DataMember(Name = "message")] - public string? Message { get; set; } - } + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/SnippetDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/SnippetDisplay.cs index 39e2027b274d..922ff045d0b4 100644 --- a/src/Umbraco.Core/Models/ContentEditing/SnippetDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/SnippetDisplay.cs @@ -1,14 +1,13 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "scriptFile", Namespace = "")] +public class SnippetDisplay { - [DataContract(Name = "scriptFile", Namespace = "")] - public class SnippetDisplay - { - [DataMember(Name = "name", IsRequired = true)] - public string? Name { get; set; } + [DataMember(Name = "name", IsRequired = true)] + public string? Name { get; set; } - [DataMember(Name = "fileName", IsRequired = true)] - public string? FileName { get; set; } - } + [DataMember(Name = "fileName", IsRequired = true)] + public string? FileName { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/StyleSheet.cs b/src/Umbraco.Core/Models/ContentEditing/StyleSheet.cs index 11d3b814c16f..af2193d64236 100644 --- a/src/Umbraco.Core/Models/ContentEditing/StyleSheet.cs +++ b/src/Umbraco.Core/Models/ContentEditing/StyleSheet.cs @@ -1,14 +1,11 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "stylesheet", Namespace = "")] +public class Stylesheet { - [DataContract(Name = "stylesheet", Namespace = "")] - public class Stylesheet - { - [DataMember(Name="name")] - public string? Name { get; set; } + [DataMember(Name = "name")] public string? Name { get; set; } - [DataMember(Name = "path")] - public string? Path { get; set; } - } + [DataMember(Name = "path")] public string? Path { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/StylesheetRule.cs b/src/Umbraco.Core/Models/ContentEditing/StylesheetRule.cs index c5f827300a7c..0c37b86e9ded 100644 --- a/src/Umbraco.Core/Models/ContentEditing/StylesheetRule.cs +++ b/src/Umbraco.Core/Models/ContentEditing/StylesheetRule.cs @@ -1,17 +1,13 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "stylesheetRule", Namespace = "")] +public class StylesheetRule { - [DataContract(Name = "stylesheetRule", Namespace = "")] - public class StylesheetRule - { - [DataMember(Name = "name")] - public string Name { get; set; } = null!; + [DataMember(Name = "name")] public string Name { get; set; } = null!; - [DataMember(Name = "selector")] - public string Selector { get; set; } = null!; + [DataMember(Name = "selector")] public string Selector { get; set; } = null!; - [DataMember(Name = "styles")] - public string Styles { get; set; } = null!; - } + [DataMember(Name = "styles")] public string Styles { get; set; } = null!; } diff --git a/src/Umbraco.Core/Models/ContentEditing/Tab.cs b/src/Umbraco.Core/Models/ContentEditing/Tab.cs index 4bcd824670da..54345733b8cb 100644 --- a/src/Umbraco.Core/Models/ContentEditing/Tab.cs +++ b/src/Umbraco.Core/Models/ContentEditing/Tab.cs @@ -1,40 +1,30 @@ -using System; -using System.Collections.Generic; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Represents a tab in the UI +/// +[DataContract(Name = "tab", Namespace = "")] +public class Tab { - /// - /// Represents a tab in the UI - /// - [DataContract(Name = "tab", Namespace = "")] - public class Tab - { - [DataMember(Name = "id")] - public int Id { get; set; } + [DataMember(Name = "id")] public int Id { get; set; } - [DataMember(Name = "key")] - public Guid Key { get; set; } + [DataMember(Name = "key")] public Guid Key { get; set; } - [DataMember(Name = "type")] - public string? Type { get; set; } + [DataMember(Name = "type")] public string? Type { get; set; } - [DataMember(Name = "active")] - public bool IsActive { get; set; } + [DataMember(Name = "active")] public bool IsActive { get; set; } - [DataMember(Name = "label")] - public string? Label { get; set; } + [DataMember(Name = "label")] public string? Label { get; set; } - [DataMember(Name = "alias")] - public string? Alias { get; set; } + [DataMember(Name = "alias")] public string? Alias { get; set; } - /// - /// The expanded state of the tab - /// - [DataMember(Name = "open")] - public bool Expanded { get; set; } = true; + /// + /// The expanded state of the tab + /// + [DataMember(Name = "open")] + public bool Expanded { get; set; } = true; - [DataMember(Name = "properties")] - public IEnumerable? Properties { get; set; } - } + [DataMember(Name = "properties")] public IEnumerable? Properties { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/TabbedContentItem.cs b/src/Umbraco.Core/Models/ContentEditing/TabbedContentItem.cs index afc64e7fafbe..2c9ee4e2e9c8 100644 --- a/src/Umbraco.Core/Models/ContentEditing/TabbedContentItem.cs +++ b/src/Umbraco.Core/Models/ContentEditing/TabbedContentItem.cs @@ -1,35 +1,28 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing -{ - public abstract class TabbedContentItem : ContentItemBasic, ITabbedContent where T : ContentPropertyBasic - { - protected TabbedContentItem() - { - Tabs = new List>(); - } +namespace Umbraco.Cms.Core.Models.ContentEditing; - /// - /// Defines the tabs containing display properties - /// - [DataMember(Name = "tabs")] - public IEnumerable> Tabs { get; set; } +public abstract class TabbedContentItem : ContentItemBasic, ITabbedContent where T : ContentPropertyBasic +{ + protected TabbedContentItem() => Tabs = new List>(); - /// - /// Override the properties property to ensure we don't serialize this - /// and to simply return the properties based on the properties in the tabs collection - /// - /// - /// This property cannot be set - /// - [IgnoreDataMember] - public override IEnumerable Properties - { - get => Tabs.Where(x => x.Properties is not null).SelectMany(x => x.Properties!); - set => throw new NotImplementedException(); - } + /// + /// Override the properties property to ensure we don't serialize this + /// and to simply return the properties based on the properties in the tabs collection + /// + /// + /// This property cannot be set + /// + [IgnoreDataMember] + public override IEnumerable Properties + { + get => Tabs.Where(x => x.Properties is not null).SelectMany(x => x.Properties!); + set => throw new NotImplementedException(); } + + /// + /// Defines the tabs containing display properties + /// + [DataMember(Name = "tabs")] + public IEnumerable> Tabs { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/TemplateDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/TemplateDisplay.cs index fd67d5593659..ee40bd57b008 100644 --- a/src/Umbraco.Core/Models/ContentEditing/TemplateDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/TemplateDisplay.cs @@ -1,47 +1,36 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing -{ - [DataContract(Name = "template", Namespace = "")] - public class TemplateDisplay : INotificationModel - { +namespace Umbraco.Cms.Core.Models.ContentEditing; - [DataMember(Name = "id")] - public int Id { get; set; } +[DataContract(Name = "template", Namespace = "")] +public class TemplateDisplay : INotificationModel +{ + [DataMember(Name = "id")] public int Id { get; set; } - [Required] - [DataMember(Name = "name")] - public string? Name { get; set; } + [Required] [DataMember(Name = "name")] public string? Name { get; set; } - [Required] - [DataMember(Name = "alias")] - public string Alias { get; set; } = string.Empty; + [Required] + [DataMember(Name = "alias")] + public string Alias { get; set; } = string.Empty; - [DataMember(Name = "key")] - public Guid Key { get; set; } + [DataMember(Name = "key")] public Guid Key { get; set; } - [DataMember(Name = "content")] - public string? Content { get; set; } + [DataMember(Name = "content")] public string? Content { get; set; } - [DataMember(Name = "path")] - public string? Path { get; set; } + [DataMember(Name = "path")] public string? Path { get; set; } - [DataMember(Name = "virtualPath")] - public string? VirtualPath { get; set; } + [DataMember(Name = "virtualPath")] public string? VirtualPath { get; set; } - [DataMember(Name = "masterTemplateAlias")] - public string? MasterTemplateAlias { get; set; } + [DataMember(Name = "masterTemplateAlias")] + public string? MasterTemplateAlias { get; set; } - [DataMember(Name = "isMasterTemplate")] - public bool IsMasterTemplate { get; set; } + [DataMember(Name = "isMasterTemplate")] + public bool IsMasterTemplate { get; set; } - /// - /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. - /// - [DataMember(Name = "notifications")] - public List? Notifications { get; private set; } - } + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + public List? Notifications { get; private set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/TreeSearchResult.cs b/src/Umbraco.Core/Models/ContentEditing/TreeSearchResult.cs index 99533facc87b..949b9b0616ca 100644 --- a/src/Umbraco.Core/Models/ContentEditing/TreeSearchResult.cs +++ b/src/Umbraco.Core/Models/ContentEditing/TreeSearchResult.cs @@ -1,34 +1,29 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Represents a search result by entity type +/// +[DataContract(Name = "searchResult", Namespace = "")] +public class TreeSearchResult { - /// - /// Represents a search result by entity type - /// - [DataContract(Name = "searchResult", Namespace = "")] - public class TreeSearchResult - { - [DataMember(Name = "appAlias")] - public string? AppAlias { get; set; } + [DataMember(Name = "appAlias")] public string? AppAlias { get; set; } - [DataMember(Name = "treeAlias")] - public string? TreeAlias { get; set; } + [DataMember(Name = "treeAlias")] public string? TreeAlias { get; set; } - /// - /// This is optional but if specified should be the name of an angular service to format the search result. - /// - [DataMember(Name = "jsSvc")] - public string? JsFormatterService { get; set; } + /// + /// This is optional but if specified should be the name of an angular service to format the search result. + /// + [DataMember(Name = "jsSvc")] + public string? JsFormatterService { get; set; } - /// - /// This is optional but if specified should be the name of a method on the jsSvc angular service to use, if not - /// specified than it will expect the method to be called `format(searchResult, appAlias, treeAlias)` - /// - [DataMember(Name = "jsMethod")] - public string? JsFormatterMethod { get; set; } + /// + /// This is optional but if specified should be the name of a method on the jsSvc angular service to use, if not + /// specified than it will expect the method to be called `format(searchResult, appAlias, treeAlias)` + /// + [DataMember(Name = "jsMethod")] + public string? JsFormatterMethod { get; set; } - [DataMember(Name = "results")] - public IEnumerable? Results { get; set; } - } + [DataMember(Name = "results")] public IEnumerable? Results { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/UmbracoEntityTypes.cs b/src/Umbraco.Core/Models/ContentEditing/UmbracoEntityTypes.cs index c77500c531b7..e194101d7d80 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UmbracoEntityTypes.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UmbracoEntityTypes.cs @@ -1,98 +1,97 @@ -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Represents the type's of Umbraco entities that can be resolved from the EntityController +/// +public enum UmbracoEntityTypes { /// - /// Represents the type's of Umbraco entities that can be resolved from the EntityController - /// - public enum UmbracoEntityTypes - { - /// - /// Language - /// - Language, - - /// - /// User - /// - User, - - /// - /// Macro - /// - Macro, - - /// - /// Document - /// - Document, - - /// - /// Media - /// - Media, - - /// - /// Member Type - /// - MemberType, - - /// - /// Template - /// - Template, - - /// - /// Member Group - /// - MemberGroup, - - /// - /// "Media Type - /// - MediaType, - - /// - /// Document Type - /// - DocumentType, - - /// - /// Stylesheet - /// - Stylesheet, - - /// - /// Script - /// - Script, - - /// - /// Partial View - /// - PartialView, - - /// - /// Member - /// - Member, - - /// - /// Data Type - /// - DataType, - - /// - /// Property Type - /// - PropertyType, - - /// - /// Property Group - /// - PropertyGroup, - - /// - /// Dictionary Item - /// - DictionaryItem - } + /// Language + /// + Language, + + /// + /// User + /// + User, + + /// + /// Macro + /// + Macro, + + /// + /// Document + /// + Document, + + /// + /// Media + /// + Media, + + /// + /// Member Type + /// + MemberType, + + /// + /// Template + /// + Template, + + /// + /// Member Group + /// + MemberGroup, + + /// + /// "Media Type + /// + MediaType, + + /// + /// Document Type + /// + DocumentType, + + /// + /// Stylesheet + /// + Stylesheet, + + /// + /// Script + /// + Script, + + /// + /// Partial View + /// + PartialView, + + /// + /// Member + /// + Member, + + /// + /// Data Type + /// + DataType, + + /// + /// Property Type + /// + PropertyType, + + /// + /// Property Group + /// + PropertyGroup, + + /// + /// Dictionary Item + /// + DictionaryItem } diff --git a/src/Umbraco.Core/Models/ContentEditing/UnpublishContent.cs b/src/Umbraco.Core/Models/ContentEditing/UnpublishContent.cs index 7a4e6d28d8fa..cf8dfeea7e68 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UnpublishContent.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UnpublishContent.cs @@ -1,17 +1,14 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Used to unpublish content and variants +/// +[DataContract(Name = "unpublish", Namespace = "")] +public class UnpublishContent { - /// - /// Used to unpublish content and variants - /// - [DataContract(Name = "unpublish", Namespace = "")] - public class UnpublishContent - { - [DataMember(Name = "id")] - public int Id { get; set; } + [DataMember(Name = "id")] public int Id { get; set; } - [DataMember(Name = "cultures")] - public string[]? Cultures { get; set; } - } + [DataMember(Name = "cultures")] public string[]? Cultures { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/UrlAndAnchors.cs b/src/Umbraco.Core/Models/ContentEditing/UrlAndAnchors.cs index 0e8c711e833d..55814dc2bc3e 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UrlAndAnchors.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UrlAndAnchors.cs @@ -1,21 +1,17 @@ -using System.Collections.Generic; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "urlAndAnchors", Namespace = "")] +public class UrlAndAnchors { - [DataContract(Name = "urlAndAnchors", Namespace = "")] - public class UrlAndAnchors + public UrlAndAnchors(string url, IEnumerable anchorValues) { - public UrlAndAnchors(string url, IEnumerable anchorValues) - { - Url = url; - AnchorValues = anchorValues; - } + Url = url; + AnchorValues = anchorValues; + } - [DataMember(Name = "url")] - public string Url { get; } + [DataMember(Name = "url")] public string Url { get; } - [DataMember(Name = "anchorValues")] - public IEnumerable AnchorValues { get; } - } + [DataMember(Name = "anchorValues")] public IEnumerable AnchorValues { get; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/UserBasic.cs b/src/Umbraco.Core/Models/ContentEditing/UserBasic.cs index b2dc4ceb4a0a..9a6d085288d9 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UserBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UserBasic.cs @@ -1,68 +1,62 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// The user model used for paging and listing users in the UI +/// +[DataContract(Name = "user", Namespace = "")] +[ReadOnly(true)] +public class UserBasic : EntityBasic, INotificationModel { - /// - /// The user model used for paging and listing users in the UI - /// - [DataContract(Name = "user", Namespace = "")] - [ReadOnly(true)] - public class UserBasic : EntityBasic, INotificationModel + public UserBasic() { - public UserBasic() - { - Notifications = new List(); - UserGroups = new List(); - } + Notifications = new List(); + UserGroups = new List(); + } - [DataMember(Name = "username")] - public string? Username { get; set; } + [DataMember(Name = "username")] public string? Username { get; set; } - /// - /// The MD5 lowercase hash of the email which can be used by gravatar - /// - [DataMember(Name = "emailHash")] - public string? EmailHash { get; set; } + /// + /// The MD5 lowercase hash of the email which can be used by gravatar + /// + [DataMember(Name = "emailHash")] + public string? EmailHash { get; set; } - [DataMember(Name = "lastLoginDate")] - public DateTime? LastLoginDate { get; set; } + [DataMember(Name = "lastLoginDate")] public DateTime? LastLoginDate { get; set; } - /// - /// Returns a list of different size avatars - /// - [DataMember(Name = "avatars")] - public string[]? Avatars { get; set; } + /// + /// Returns a list of different size avatars + /// + [DataMember(Name = "avatars")] + public string[]? Avatars { get; set; } - [DataMember(Name = "userState")] - public UserState UserState { get; set; } + [DataMember(Name = "userState")] public UserState UserState { get; set; } - [DataMember(Name = "culture", IsRequired = true)] - public string? Culture { get; set; } + [DataMember(Name = "culture", IsRequired = true)] + public string? Culture { get; set; } - [DataMember(Name = "email", IsRequired = true)] - public string? Email { get; set; } + [DataMember(Name = "email", IsRequired = true)] + public string? Email { get; set; } - /// - /// The list of group aliases assigned to the user - /// - [DataMember(Name = "userGroups")] - public IEnumerable UserGroups { get; set; } + /// + /// The list of group aliases assigned to the user + /// + [DataMember(Name = "userGroups")] + public IEnumerable UserGroups { get; set; } - /// - /// This is an info flag to denote if this object is the equivalent of the currently logged in user - /// - [DataMember(Name = "isCurrentUser")] - [ReadOnly(true)] - public bool IsCurrentUser { get; set; } + /// + /// This is an info flag to denote if this object is the equivalent of the currently logged in user + /// + [DataMember(Name = "isCurrentUser")] + [ReadOnly(true)] + public bool IsCurrentUser { get; set; } - /// - /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. - /// - [DataMember(Name = "notifications")] - public List Notifications { get; private set; } - } + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/UserDetail.cs b/src/Umbraco.Core/Models/ContentEditing/UserDetail.cs index 01c2bcb70cec..bcbf426225f7 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UserDetail.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UserDetail.cs @@ -1,62 +1,62 @@ -using System.Collections.Generic; -using System.ComponentModel; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Represents information for the current user +/// +[DataContract(Name = "user", Namespace = "")] +public class UserDetail : UserProfile { + [DataMember(Name = "email", IsRequired = true)] + [Required] + public string? Email { get; set; } + + [DataMember(Name = "locale", IsRequired = true)] + [Required] + public string? Culture { get; set; } + + /// + /// The MD5 lowercase hash of the email which can be used by gravatar + /// + [DataMember(Name = "emailHash")] + public string? EmailHash { get; set; } + + [ReadOnly(true)] + [DataMember(Name = "userGroups")] + public string?[]? UserGroups { get; set; } + + /// + /// Gets/sets the number of seconds for the user's auth ticket to expire + /// + [DataMember(Name = "remainingAuthSeconds")] + public double SecondsUntilTimeout { get; set; } + + /// + /// The user's calculated start nodes based on the start nodes they have assigned directly to them and via the groups + /// they're assigned to + /// + [DataMember(Name = "startContentIds")] + public int[]? StartContentIds { get; set; } + + /// + /// The user's calculated start nodes based on the start nodes they have assigned directly to them and via the groups + /// they're assigned to + /// + [DataMember(Name = "startMediaIds")] + public int[]? StartMediaIds { get; set; } + + /// + /// Returns a list of different size avatars + /// + [DataMember(Name = "avatars")] + public string[]? Avatars { get; set; } + /// - /// Represents information for the current user + /// A list of sections the user is allowed to view. /// - [DataContract(Name = "user", Namespace = "")] - public class UserDetail : UserProfile - { - [DataMember(Name = "email", IsRequired = true)] - [Required] - public string? Email { get; set; } - - [DataMember(Name = "locale", IsRequired = true)] - [Required] - public string? Culture { get; set; } - - /// - /// The MD5 lowercase hash of the email which can be used by gravatar - /// - [DataMember(Name = "emailHash")] - public string? EmailHash { get; set; } - - [ReadOnly(true)] - [DataMember(Name = "userGroups")] - public string?[]? UserGroups { get; set; } - - /// - /// Gets/sets the number of seconds for the user's auth ticket to expire - /// - [DataMember(Name = "remainingAuthSeconds")] - public double SecondsUntilTimeout { get; set; } - - /// - /// The user's calculated start nodes based on the start nodes they have assigned directly to them and via the groups they're assigned to - /// - [DataMember(Name = "startContentIds")] - public int[]? StartContentIds { get; set; } - - /// - /// The user's calculated start nodes based on the start nodes they have assigned directly to them and via the groups they're assigned to - /// - [DataMember(Name = "startMediaIds")] - public int[]? StartMediaIds { get; set; } - - /// - /// Returns a list of different size avatars - /// - [DataMember(Name = "avatars")] - public string[]? Avatars { get; set; } - - /// - /// A list of sections the user is allowed to view. - /// - [DataMember(Name = "allowedSections")] - public IEnumerable? AllowedSections { get; set; } - } + [DataMember(Name = "allowedSections")] + public IEnumerable? AllowedSections { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/UserDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/UserDisplay.cs index 20e517cefca3..52a1c6234190 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UserDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UserDisplay.cs @@ -1,81 +1,76 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Represents a user that is being edited +/// +[DataContract(Name = "user", Namespace = "")] +[ReadOnly(true)] +public class UserDisplay : UserBasic { - /// - /// Represents a user that is being edited - /// - [DataContract(Name = "user", Namespace = "")] - [ReadOnly(true)] - public class UserDisplay : UserBasic + public UserDisplay() { - public UserDisplay() - { - AvailableCultures = new Dictionary(); - StartContentIds = new List(); - StartMediaIds = new List(); - Navigation = new List(); - } + AvailableCultures = new Dictionary(); + StartContentIds = new List(); + StartMediaIds = new List(); + Navigation = new List(); + } - [DataMember(Name = "navigation")] - [ReadOnly(true)] - public IEnumerable Navigation { get; set; } + [DataMember(Name = "navigation")] + [ReadOnly(true)] + public IEnumerable Navigation { get; set; } - /// - /// Gets the available cultures (i.e. to populate a drop down) - /// The key is the culture stored in the database, the value is the Name - /// - [DataMember(Name = "availableCultures")] - public IDictionary AvailableCultures { get; set; } + /// + /// Gets the available cultures (i.e. to populate a drop down) + /// The key is the culture stored in the database, the value is the Name + /// + [DataMember(Name = "availableCultures")] + public IDictionary AvailableCultures { get; set; } - [DataMember(Name = "startContentIds")] - public IEnumerable StartContentIds { get; set; } + [DataMember(Name = "startContentIds")] public IEnumerable StartContentIds { get; set; } - [DataMember(Name = "startMediaIds")] - public IEnumerable StartMediaIds { get; set; } + [DataMember(Name = "startMediaIds")] public IEnumerable StartMediaIds { get; set; } - /// - /// If the password is reset on save, this value will be populated - /// - [DataMember(Name = "resetPasswordValue")] - [ReadOnly(true)] - public string? ResetPasswordValue { get; set; } + /// + /// If the password is reset on save, this value will be populated + /// + [DataMember(Name = "resetPasswordValue")] + [ReadOnly(true)] + public string? ResetPasswordValue { get; set; } - /// - /// A readonly value showing the user's current calculated start content ids - /// - [DataMember(Name = "calculatedStartContentIds")] - [ReadOnly(true)] - public IEnumerable? CalculatedStartContentIds { get; set; } + /// + /// A readonly value showing the user's current calculated start content ids + /// + [DataMember(Name = "calculatedStartContentIds")] + [ReadOnly(true)] + public IEnumerable? CalculatedStartContentIds { get; set; } - /// - /// A readonly value showing the user's current calculated start media ids - /// - [DataMember(Name = "calculatedStartMediaIds")] - [ReadOnly(true)] - public IEnumerable? CalculatedStartMediaIds { get; set; } + /// + /// A readonly value showing the user's current calculated start media ids + /// + [DataMember(Name = "calculatedStartMediaIds")] + [ReadOnly(true)] + public IEnumerable? CalculatedStartMediaIds { get; set; } - [DataMember(Name = "failedPasswordAttempts")] - [ReadOnly(true)] - public int FailedPasswordAttempts { get; set; } + [DataMember(Name = "failedPasswordAttempts")] + [ReadOnly(true)] + public int FailedPasswordAttempts { get; set; } - [DataMember(Name = "lastLockoutDate")] - [ReadOnly(true)] - public DateTime? LastLockoutDate { get; set; } + [DataMember(Name = "lastLockoutDate")] + [ReadOnly(true)] + public DateTime? LastLockoutDate { get; set; } - [DataMember(Name = "lastPasswordChangeDate")] - [ReadOnly(true)] - public DateTime? LastPasswordChangeDate { get; set; } + [DataMember(Name = "lastPasswordChangeDate")] + [ReadOnly(true)] + public DateTime? LastPasswordChangeDate { get; set; } - [DataMember(Name = "createDate")] - [ReadOnly(true)] - public DateTime CreateDate { get; set; } + [DataMember(Name = "createDate")] + [ReadOnly(true)] + public DateTime CreateDate { get; set; } - [DataMember(Name = "updateDate")] - [ReadOnly(true)] - public DateTime UpdateDate { get; set; } - } + [DataMember(Name = "updateDate")] + [ReadOnly(true)] + public DateTime UpdateDate { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/UserGroupBasic.cs b/src/Umbraco.Core/Models/ContentEditing/UserGroupBasic.cs index ffcfde8368a4..8821f4012f0b 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UserGroupBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UserGroupBasic.cs @@ -1,43 +1,38 @@ -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "userGroup", Namespace = "")] +public class UserGroupBasic : EntityBasic, INotificationModel { - [DataContract(Name = "userGroup", Namespace = "")] - public class UserGroupBasic : EntityBasic, INotificationModel + public UserGroupBasic() { - public UserGroupBasic() - { - Notifications = new List(); - Sections = Enumerable.Empty
(); - } - - /// - /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. - /// - [DataMember(Name = "notifications")] - public List Notifications { get; private set; } - - [DataMember(Name = "sections")] - public IEnumerable
Sections { get; set; } - - [DataMember(Name = "contentStartNode")] - public EntityBasic? ContentStartNode { get; set; } - - [DataMember(Name = "mediaStartNode")] - public EntityBasic? MediaStartNode { get; set; } - - /// - /// The number of users assigned to this group - /// - [DataMember(Name = "userCount")] - public int UserCount { get; set; } - - /// - /// Is the user group a system group e.g. "Administrators", "Sensitive data" or "Translators" - /// - [DataMember(Name = "isSystemUserGroup")] - public bool IsSystemUserGroup { get; set; } + Notifications = new List(); + Sections = Enumerable.Empty
(); } + + [DataMember(Name = "sections")] public IEnumerable
Sections { get; set; } + + [DataMember(Name = "contentStartNode")] + public EntityBasic? ContentStartNode { get; set; } + + [DataMember(Name = "mediaStartNode")] public EntityBasic? MediaStartNode { get; set; } + + /// + /// The number of users assigned to this group + /// + [DataMember(Name = "userCount")] + public int UserCount { get; set; } + + /// + /// Is the user group a system group e.g. "Administrators", "Sensitive data" or "Translators" + /// + [DataMember(Name = "isSystemUserGroup")] + public bool IsSystemUserGroup { get; set; } + + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/UserGroupDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/UserGroupDisplay.cs index 697a0a21004f..2b7f59bd996b 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UserGroupDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UserGroupDisplay.cs @@ -1,31 +1,27 @@ -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "userGroup", Namespace = "")] +public class UserGroupDisplay : UserGroupBasic { - [DataContract(Name = "userGroup", Namespace = "")] - public class UserGroupDisplay : UserGroupBasic + public UserGroupDisplay() { - public UserGroupDisplay() - { - Users = Enumerable.Empty(); - AssignedPermissions = Enumerable.Empty(); - } + Users = Enumerable.Empty(); + AssignedPermissions = Enumerable.Empty(); + } - [DataMember(Name = "users")] - public IEnumerable Users { get; set; } + [DataMember(Name = "users")] public IEnumerable Users { get; set; } - /// - /// The default permissions for the user group organized by permission group name - /// - [DataMember(Name = "defaultPermissions")] - public IDictionary>? DefaultPermissions { get; set; } + /// + /// The default permissions for the user group organized by permission group name + /// + [DataMember(Name = "defaultPermissions")] + public IDictionary>? DefaultPermissions { get; set; } - /// - /// The assigned permissions for the user group organized by permission group name - /// - [DataMember(Name = "assignedPermissions")] - public IEnumerable AssignedPermissions { get; set; } - } + /// + /// The assigned permissions for the user group organized by permission group name + /// + [DataMember(Name = "assignedPermissions")] + public IEnumerable AssignedPermissions { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/UserGroupPermissionsSave.cs b/src/Umbraco.Core/Models/ContentEditing/UserGroupPermissionsSave.cs index e782d6963561..96e90ca37be5 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UserGroupPermissionsSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UserGroupPermissionsSave.cs @@ -1,42 +1,35 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Used to assign user group permissions to a content node +/// +[DataContract(Name = "contentPermission", Namespace = "")] +public class UserGroupPermissionsSave { - /// - /// Used to assign user group permissions to a content node - /// - [DataContract(Name = "contentPermission", Namespace = "")] - public class UserGroupPermissionsSave - { - public UserGroupPermissionsSave() - { - AssignedPermissions = new Dictionary>(); - } + public UserGroupPermissionsSave() => AssignedPermissions = new Dictionary>(); - // TODO: we should have an option to clear the permissions assigned to this node and instead just have them inherit - yes once we actually have inheritance! + // TODO: we should have an option to clear the permissions assigned to this node and instead just have them inherit - yes once we actually have inheritance! - [DataMember(Name = "contentId", IsRequired = true)] - [Required] - public int ContentId { get; set; } + [DataMember(Name = "contentId", IsRequired = true)] + [Required] + public int ContentId { get; set; } - /// - /// A dictionary of permissions to assign, the key is the user group id - /// - [DataMember(Name = "permissions")] - public IDictionary> AssignedPermissions { get; set; } + /// + /// A dictionary of permissions to assign, the key is the user group id + /// + [DataMember(Name = "permissions")] + public IDictionary> AssignedPermissions { get; set; } - [Obsolete("This is not used and will be removed in Umbraco 10")] - public IEnumerable Validate(ValidationContext validationContext) + [Obsolete("This is not used and will be removed in Umbraco 10")] + public IEnumerable Validate(ValidationContext validationContext) + { + if (AssignedPermissions.SelectMany(x => x.Value).Any(x => x.IsNullOrWhiteSpace())) { - if (AssignedPermissions.SelectMany(x => x.Value).Any(x => x.IsNullOrWhiteSpace())) - { - yield return new ValidationResult("A permission value cannot be null or empty", new[] { "Permissions" }); - } + yield return new ValidationResult("A permission value cannot be null or empty", new[] {"Permissions"}); } } } diff --git a/src/Umbraco.Core/Models/ContentEditing/UserGroupSave.cs b/src/Umbraco.Core/Models/ContentEditing/UserGroupSave.cs index 1bf792381785..f26473464274 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UserGroupSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UserGroupSave.cs @@ -1,77 +1,73 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +[DataContract(Name = "userGroup", Namespace = "")] +public class UserGroupSave : EntityBasic, IValidatableObject { - [DataContract(Name = "userGroup", Namespace = "")] - public class UserGroupSave : EntityBasic, IValidatableObject - { - /// - /// The action to perform when saving this user group - /// - /// - /// If either of the Publish actions are specified an exception will be thrown. - /// - [DataMember(Name = "action", IsRequired = true)] - [Required] - public ContentSaveAction Action { get; set; } + /// + /// The action to perform when saving this user group + /// + /// + /// If either of the Publish actions are specified an exception will be thrown. + /// + [DataMember(Name = "action", IsRequired = true)] + [Required] + public ContentSaveAction Action { get; set; } - [DataMember(Name = "alias", IsRequired = true)] - [Required] - public override string Alias { get; set; } = string.Empty; + [DataMember(Name = "alias", IsRequired = true)] + [Required] + public override string Alias { get; set; } = string.Empty; - [DataMember(Name = "sections")] - public IEnumerable? Sections { get; set; } + [DataMember(Name = "sections")] public IEnumerable? Sections { get; set; } - [DataMember(Name = "users")] - public IEnumerable? Users { get; set; } + [DataMember(Name = "users")] public IEnumerable? Users { get; set; } - [DataMember(Name = "startContentId")] - public int? StartContentId { get; set; } + [DataMember(Name = "startContentId")] public int? StartContentId { get; set; } - [DataMember(Name = "startMediaId")] - public int? StartMediaId { get; set; } + [DataMember(Name = "startMediaId")] public int? StartMediaId { get; set; } - /// - /// The list of letters (permission codes) to assign as the default for the user group - /// - [DataMember(Name = "defaultPermissions")] - public IEnumerable? DefaultPermissions { get; set; } + /// + /// The list of letters (permission codes) to assign as the default for the user group + /// + [DataMember(Name = "defaultPermissions")] + public IEnumerable? DefaultPermissions { get; set; } - /// - /// The assigned permissions for content - /// - /// - /// The key is the content id and the list is the list of letters (permission codes) to assign - /// - [DataMember(Name = "assignedPermissions")] - public IDictionary>? AssignedPermissions { get; set; } + /// + /// The assigned permissions for content + /// + /// + /// The key is the content id and the list is the list of letters (permission codes) to assign + /// + [DataMember(Name = "assignedPermissions")] + public IDictionary>? AssignedPermissions { get; set; } - /// - /// The real persisted user group - /// - [IgnoreDataMember] - public IUserGroup? PersistedUserGroup { get; set; } + /// + /// The real persisted user group + /// + [IgnoreDataMember] + public IUserGroup? PersistedUserGroup { get; set; } - public IEnumerable Validate(ValidationContext validationContext) + public IEnumerable Validate(ValidationContext validationContext) + { + if (DefaultPermissions?.Any(x => x.IsNullOrWhiteSpace()) ?? false) { - if (DefaultPermissions?.Any(x => x.IsNullOrWhiteSpace()) ?? false) - { - yield return new ValidationResult("A permission value cannot be null or empty", new[] { "Permissions" }); - } + yield return new ValidationResult("A permission value cannot be null or empty", new[] {"Permissions"}); + } - if (AssignedPermissions is not null) + if (AssignedPermissions is not null) + { + foreach (KeyValuePair> assignedPermission in AssignedPermissions) { - foreach (var assignedPermission in AssignedPermissions) + foreach (var permission in assignedPermission.Value) { - foreach (var permission in assignedPermission.Value) + if (permission.IsNullOrWhiteSpace()) { - if (permission.IsNullOrWhiteSpace()) - yield return new ValidationResult("A permission value cannot be null or empty", new[] { "AssignedPermissions" }); + yield return new ValidationResult("A permission value cannot be null or empty", + new[] {"AssignedPermissions"}); } } } diff --git a/src/Umbraco.Core/Models/ContentEditing/UserInvite.cs b/src/Umbraco.Core/Models/ContentEditing/UserInvite.cs index 7b3014369aa6..f27dc1f2318b 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UserInvite.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UserInvite.cs @@ -1,44 +1,45 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Represents the data used to invite a user +/// +[DataContract(Name = "user", Namespace = "")] +public class UserInvite : EntityBasic, IValidatableObject { - /// - /// Represents the data used to invite a user - /// - [DataContract(Name = "user", Namespace = "")] - public class UserInvite : EntityBasic, IValidatableObject - { - [DataMember(Name = "userGroups")] - [Required] - public IEnumerable UserGroups { get; set; } = null!; + [DataMember(Name = "userGroups")] + [Required] + public IEnumerable UserGroups { get; set; } = null!; - [DataMember(Name = "email", IsRequired = true)] - [Required] - [EmailAddress] - public string Email { get; set; } = null!; + [DataMember(Name = "email", IsRequired = true)] + [Required] + [EmailAddress] + public string Email { get; set; } = null!; - [DataMember(Name = "username")] - public string? Username { get; set; } + [DataMember(Name = "username")] public string? Username { get; set; } - [DataMember(Name = "message")] - public string? Message { get; set; } + [DataMember(Name = "message")] public string? Message { get; set; } - public IEnumerable Validate(ValidationContext validationContext) + public IEnumerable Validate(ValidationContext validationContext) + { + if (UserGroups.Any() == false) { - if (UserGroups.Any() == false) - yield return new ValidationResult("A user must be assigned to at least one group", new[] { nameof(UserGroups) }); + yield return new ValidationResult("A user must be assigned to at least one group", + new[] {nameof(UserGroups)}); + } - var securitySettings = validationContext.GetRequiredService>(); + IOptionsSnapshot securitySettings = + validationContext.GetRequiredService>(); - if (securitySettings.Value.UsernameIsEmail == false && Username.IsNullOrWhiteSpace()) - yield return new ValidationResult("A username cannot be empty", new[] { nameof(Username) }); + if (securitySettings.Value.UsernameIsEmail == false && Username.IsNullOrWhiteSpace()) + { + yield return new ValidationResult("A username cannot be empty", new[] {nameof(Username)}); } } } diff --git a/src/Umbraco.Core/Models/ContentEditing/UserProfile.cs b/src/Umbraco.Core/Models/ContentEditing/UserProfile.cs index 9ade7735e7b1..b5f3a2f8d0f3 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UserProfile.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UserProfile.cs @@ -1,27 +1,22 @@ -using System; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// A bare minimum structure that represents a user, usually attached to other objects +/// +[DataContract(Name = "user", Namespace = "")] +public class UserProfile : IComparable { - /// - /// A bare minimum structure that represents a user, usually attached to other objects - /// - [DataContract(Name = "user", Namespace = "")] - public class UserProfile : IComparable - { - [DataMember(Name = "id", IsRequired = true)] - [Required] - public int UserId { get; set; } + [DataMember(Name = "id", IsRequired = true)] + [Required] + public int UserId { get; set; } - [DataMember(Name = "name", IsRequired = true)] - [Required] - public string? Name { get; set; } + [DataMember(Name = "name", IsRequired = true)] + [Required] + public string? Name { get; set; } - int IComparable.CompareTo(object? obj) - { - return String.Compare(Name, ((UserProfile?)obj)?.Name, StringComparison.Ordinal); - } - } + int IComparable.CompareTo(object? obj) => string.Compare(Name, ((UserProfile?)obj)?.Name, StringComparison.Ordinal); } diff --git a/src/Umbraco.Core/Models/ContentEditing/UserSave.cs b/src/Umbraco.Core/Models/ContentEditing/UserSave.cs index 6e03248a316b..ee0ef916cd18 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UserSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UserSave.cs @@ -1,55 +1,54 @@ -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Linq; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.ContentEditing +namespace Umbraco.Cms.Core.Models.ContentEditing; + +/// +/// Represents the data used to persist a user +/// +/// +/// This will be different from the model used to display a user and we don't want to "Overpost" data back to the +/// server, +/// and there will most likely be different bits of data required for updating passwords which will be different from +/// the +/// data used to display vs save +/// +[DataContract(Name = "user", Namespace = "")] +public class UserSave : EntityBasic, IValidatableObject { - /// - /// Represents the data used to persist a user - /// - /// - /// This will be different from the model used to display a user and we don't want to "Overpost" data back to the server, - /// and there will most likely be different bits of data required for updating passwords which will be different from the - /// data used to display vs save - /// - [DataContract(Name = "user", Namespace = "")] - public class UserSave : EntityBasic, IValidatableObject - { - [DataMember(Name = "changePassword", IsRequired = true)] - public ChangingPasswordModel? ChangePassword { get; set; } + [DataMember(Name = "changePassword", IsRequired = true)] + public ChangingPasswordModel? ChangePassword { get; set; } - [DataMember(Name = "id", IsRequired = true)] - [Required] - public new int Id { get; set; } + [DataMember(Name = "id", IsRequired = true)] + [Required] + public new int Id { get; set; } - [DataMember(Name = "username", IsRequired = true)] - [Required] - public string Username { get; set; } = null!; + [DataMember(Name = "username", IsRequired = true)] + [Required] + public string Username { get; set; } = null!; - [DataMember(Name = "culture", IsRequired = true)] - [Required] - public string Culture { get; set; } = null!; + [DataMember(Name = "culture", IsRequired = true)] + [Required] + public string Culture { get; set; } = null!; - [DataMember(Name = "email", IsRequired = true)] - [Required] - [EmailAddress] - public string Email { get; set; } = null!; + [DataMember(Name = "email", IsRequired = true)] + [Required] + [EmailAddress] + public string Email { get; set; } = null!; - [DataMember(Name = "userGroups")] - [Required] - public IEnumerable UserGroups { get; set; } = null!; + [DataMember(Name = "userGroups")] + [Required] + public IEnumerable UserGroups { get; set; } = null!; - [DataMember(Name = "startContentIds")] - public int[]? StartContentIds { get; set; } + [DataMember(Name = "startContentIds")] public int[]? StartContentIds { get; set; } - [DataMember(Name = "startMediaIds")] - public int[]? StartMediaIds { get; set; } + [DataMember(Name = "startMediaIds")] public int[]? StartMediaIds { get; set; } - public IEnumerable Validate(ValidationContext validationContext) + public IEnumerable Validate(ValidationContext validationContext) + { + if (UserGroups.Any() == false) { - if (UserGroups.Any() == false) - yield return new ValidationResult("A user must be assigned to at least one group", new[] { "UserGroups" }); + yield return new ValidationResult("A user must be assigned to at least one group", new[] {"UserGroups"}); } } } diff --git a/src/Umbraco.Core/Models/ContentModel.cs b/src/Umbraco.Core/Models/ContentModel.cs index cead39f01939..5d81ea367efd 100644 --- a/src/Umbraco.Core/Models/ContentModel.cs +++ b/src/Umbraco.Core/Models/ContentModel.cs @@ -1,21 +1,20 @@ -using System; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents the model for the current Umbraco view. +/// +public class ContentModel : IContentModel { /// - /// Represents the model for the current Umbraco view. + /// Initializes a new instance of the class with a content. /// - public class ContentModel : IContentModel - { - /// - /// Initializes a new instance of the class with a content. - /// - public ContentModel(IPublishedContent? content) => Content = content ?? throw new ArgumentNullException(nameof(content)); + public ContentModel(IPublishedContent? content) => + Content = content ?? throw new ArgumentNullException(nameof(content)); - /// - /// Gets the content. - /// - public IPublishedContent Content { get; } - } + /// + /// Gets the content. + /// + public IPublishedContent Content { get; } } diff --git a/src/Umbraco.Core/Models/ContentModelOfTContent.cs b/src/Umbraco.Core/Models/ContentModelOfTContent.cs index ab882342b538..32889331e002 100644 --- a/src/Umbraco.Core/Models/ContentModelOfTContent.cs +++ b/src/Umbraco.Core/Models/ContentModelOfTContent.cs @@ -1,19 +1,18 @@ using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public class ContentModel : ContentModel + where TContent : IPublishedContent { - public class ContentModel : ContentModel - where TContent : IPublishedContent - { - /// - /// Initializes a new instance of the class with a content. - /// - public ContentModel(TContent content) - : base(content) => Content = content; + /// + /// Initializes a new instance of the class with a content. + /// + public ContentModel(TContent content) + : base(content) => Content = content; - /// - /// Gets the content. - /// - public new TContent Content { get; } - } + /// + /// Gets the content. + /// + public new TContent Content { get; } } diff --git a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs index 4ab39f1669e4..1a55a7219bc2 100644 --- a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs +++ b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs @@ -1,353 +1,434 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core.Models; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Extension methods used to manipulate content variations by the document repository +/// +public static class ContentRepositoryExtensions { - /// - /// Extension methods used to manipulate content variations by the document repository - /// - public static class ContentRepositoryExtensions + public static void SetCultureInfo(this IContentBase content, string? culture, string? name, DateTime date) { - public static void SetCultureInfo(this IContentBase content, string? culture, string? name, DateTime date) + if (name == null) { - if (name == null) throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); - - if (culture == null) throw new ArgumentNullException(nameof(culture)); - if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(culture)); - - content.CultureInfos?.AddOrUpdate(culture, name, date); + throw new ArgumentNullException(nameof(name)); } - /// - /// Updates a culture date, if the culture exists. - /// - public static void TouchCulture(this IContentBase content, string? culture) + if (string.IsNullOrWhiteSpace(name)) { - if (culture.IsNullOrWhiteSpace() || content.CultureInfos is null) - { - return; - } - - if (!content.CultureInfos.TryGetValue(culture!, out var infos)) - { - return; - } - - content.CultureInfos?.AddOrUpdate(culture!, infos.Name, DateTime.Now); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(name)); } - /// - /// Used to synchronize all culture dates to the same date if they've been modified - /// - /// - /// - /// - /// This is so that in an operation where (for example) 2 languages are updates like french and english, it is possible that - /// these dates assigned to them differ by a couple of Ticks, but we need to ensure they are persisted at the exact same time. - /// - public static void AdjustDates(this IContent content, DateTime date, bool publishing) + if (culture == null) { - if (content.EditedCultures is not null) - { - foreach(var culture in content.EditedCultures.ToList()) - { - if (content.CultureInfos is null) - { - continue; - } + throw new ArgumentNullException(nameof(culture)); + } - if (!content.CultureInfos.TryGetValue(culture, out var editedInfos)) - { - continue; - } + if (string.IsNullOrWhiteSpace(culture)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(culture)); + } - // if it's not dirty, it means it hasn't changed so there's nothing to adjust - if (!editedInfos.IsDirty()) - { - continue; - } + content.CultureInfos?.AddOrUpdate(culture, name, date); + } - content.CultureInfos?.AddOrUpdate(culture, editedInfos?.Name, date); - } - } + /// + /// Updates a culture date, if the culture exists. + /// + public static void TouchCulture(this IContentBase content, string? culture) + { + if (culture.IsNullOrWhiteSpace() || content.CultureInfos is null) + { + return; + } + if (!content.CultureInfos.TryGetValue(culture!, out ContentCultureInfos infos)) + { + return; + } - if (!publishing) - { - return; - } + content.CultureInfos?.AddOrUpdate(culture!, infos.Name, DateTime.Now); + } - foreach (var culture in content.PublishedCultures.ToList()) + /// + /// Used to synchronize all culture dates to the same date if they've been modified + /// + /// + /// + /// + /// This is so that in an operation where (for example) 2 languages are updates like french and english, it is possible + /// that + /// these dates assigned to them differ by a couple of Ticks, but we need to ensure they are persisted at the exact + /// same time. + /// + public static void AdjustDates(this IContent content, DateTime date, bool publishing) + { + if (content.EditedCultures is not null) + { + foreach (var culture in content.EditedCultures.ToList()) { - if (content.PublishCultureInfos is null) + if (content.CultureInfos is null) { continue; } - if (!content.PublishCultureInfos.TryGetValue(culture, out ContentCultureInfos publishInfos)) + + if (!content.CultureInfos.TryGetValue(culture, out ContentCultureInfos editedInfos)) { continue; } // if it's not dirty, it means it hasn't changed so there's nothing to adjust - if (!publishInfos.IsDirty()) + if (!editedInfos.IsDirty()) { continue; } - content.PublishCultureInfos.AddOrUpdate(culture, publishInfos.Name, date); - - if (content.CultureInfos?.TryGetValue(culture, out ContentCultureInfos infos) ?? false) - { - SetCultureInfo(content, culture, infos.Name, date); - } + content.CultureInfos?.AddOrUpdate(culture, editedInfos?.Name, date); } } - /// - /// Gets the cultures that have been flagged for unpublishing. - /// - /// Gets cultures for which content.UnpublishCulture() has been invoked. - public static IReadOnlyList? GetCulturesUnpublishing(this IContent content) + + if (!publishing) { - if (!content.Published || !content.ContentType.VariesByCulture() || !content.IsPropertyDirty("PublishCultureInfos")) - return Array.Empty(); + return; + } - var culturesUnpublishing = content.CultureInfos?.Values - .Where(x => content.IsPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + x.Culture)) - .Select(x => x.Culture); + foreach (var culture in content.PublishedCultures.ToList()) + { + if (content.PublishCultureInfos is null) + { + continue; + } - return culturesUnpublishing?.ToList(); + if (!content.PublishCultureInfos.TryGetValue(culture, out ContentCultureInfos publishInfos)) + { + continue; + } + + // if it's not dirty, it means it hasn't changed so there's nothing to adjust + if (!publishInfos.IsDirty()) + { + continue; + } + + content.PublishCultureInfos.AddOrUpdate(culture, publishInfos.Name, date); + + if (content.CultureInfos?.TryGetValue(culture, out ContentCultureInfos infos) ?? false) + { + SetCultureInfo(content, culture, infos.Name, date); + } } + } - /// - /// Copies values from another document. - /// - public static void CopyFrom(this IContent content, IContent other, string? culture = "*") + /// + /// Gets the cultures that have been flagged for unpublishing. + /// + /// Gets cultures for which content.UnpublishCulture() has been invoked. + public static IReadOnlyList? GetCulturesUnpublishing(this IContent content) + { + if (!content.Published || !content.ContentType.VariesByCulture() || + !content.IsPropertyDirty("PublishCultureInfos")) { - if (other.ContentTypeId != content.ContentTypeId) - throw new InvalidOperationException("Cannot copy values from a different content type."); + return Array.Empty(); + } - culture = culture?.ToLowerInvariant().NullOrWhiteSpaceAsNull(); + IEnumerable culturesUnpublishing = content.CultureInfos?.Values + .Where(x => content.IsPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + x.Culture)) + .Select(x => x.Culture); - // the variation should be supported by the content type properties - // if the content type is invariant, only '*' and 'null' is ok - // if the content type varies, everything is ok because some properties may be invariant - if (!content.ContentType.SupportsPropertyVariation(culture, "*", true)) - throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\"."); + return culturesUnpublishing?.ToList(); + } - // copying from the same Id and VersionPk - var copyingFromSelf = content.Id == other.Id && content.VersionId == other.VersionId; - var published = copyingFromSelf; + /// + /// Copies values from another document. + /// + public static void CopyFrom(this IContent content, IContent other, string? culture = "*") + { + if (other.ContentTypeId != content.ContentTypeId) + { + throw new InvalidOperationException("Cannot copy values from a different content type."); + } + + culture = culture?.ToLowerInvariant().NullOrWhiteSpaceAsNull(); + + // the variation should be supported by the content type properties + // if the content type is invariant, only '*' and 'null' is ok + // if the content type varies, everything is ok because some properties may be invariant + if (!content.ContentType.SupportsPropertyVariation(culture, "*", true)) + { + throw new NotSupportedException( + $"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\"."); + } + + // copying from the same Id and VersionPk + var copyingFromSelf = content.Id == other.Id && content.VersionId == other.VersionId; + var published = copyingFromSelf; - // note: use property.SetValue(), don't assign pvalue.EditValue, else change tracking fails + // note: use property.SetValue(), don't assign pvalue.EditValue, else change tracking fails - // clear all existing properties for the specified culture - foreach (var property in content.Properties) + // clear all existing properties for the specified culture + foreach (IProperty property in content.Properties) + { + // each property type may or may not support the variation + if (!property.PropertyType?.SupportsVariation(culture, "*", true) ?? false) { - // each property type may or may not support the variation - if (!property.PropertyType?.SupportsVariation(culture, "*", wildcards: true) ?? false) - continue; + continue; + } - foreach (var pvalue in property.Values) - if ((property.PropertyType?.SupportsVariation(pvalue.Culture, pvalue.Segment, wildcards: true) ?? false) && - (culture == "*" || (pvalue.Culture?.InvariantEquals(culture) ?? false))) - { - property.SetValue(null, pvalue.Culture, pvalue.Segment); - } + foreach (IPropertyValue pvalue in property.Values) + { + if ((property.PropertyType?.SupportsVariation(pvalue.Culture, pvalue.Segment, true) ?? false) && + (culture == "*" || (pvalue.Culture?.InvariantEquals(culture) ?? false))) + { + property.SetValue(null, pvalue.Culture, pvalue.Segment); + } } + } - // copy properties from 'other' - var otherProperties = other.Properties; - foreach (var otherProperty in otherProperties) + // copy properties from 'other' + IPropertyCollection otherProperties = other.Properties; + foreach (IProperty otherProperty in otherProperties) + { + if (!otherProperty?.PropertyType?.SupportsVariation(culture, "*", true) ?? true) { - if (!otherProperty?.PropertyType?.SupportsVariation(culture, "*", wildcards: true) ?? true) - continue; + continue; + } - var alias = otherProperty?.PropertyType.Alias; - if (otherProperty is not null && alias is not null) + var alias = otherProperty?.PropertyType.Alias; + if (otherProperty is not null && alias is not null) + { + foreach (IPropertyValue pvalue in otherProperty.Values) { - foreach (var pvalue in otherProperty.Values) + if (otherProperty.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment, true) && + (culture == "*" || (pvalue.Culture?.InvariantEquals(culture) ?? false))) { - if (otherProperty.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment, wildcards: true) && - (culture == "*" || (pvalue.Culture?.InvariantEquals(culture) ?? false))) - { - var value = published ? pvalue.PublishedValue : pvalue.EditedValue; - content.SetValue(alias, value, pvalue.Culture, pvalue.Segment); - } + var value = published ? pvalue.PublishedValue : pvalue.EditedValue; + content.SetValue(alias, value, pvalue.Culture, pvalue.Segment); } } } + } - // copy names, too + // copy names, too - if (culture == "*") - { - content.CultureInfos?.Clear(); - content.CultureInfos = null; - } + if (culture == "*") + { + content.CultureInfos?.Clear(); + content.CultureInfos = null; + } - if (culture == null || culture == "*") - content.Name = other.Name; + if (culture == null || culture == "*") + { + content.Name = other.Name; + } - // ReSharper disable once UseDeconstruction - if (other.CultureInfos is not null) + // ReSharper disable once UseDeconstruction + if (other.CultureInfos is not null) + { + foreach (ContentCultureInfos cultureInfo in other.CultureInfos) { - foreach (var cultureInfo in other.CultureInfos) + if (culture == "*" || culture == cultureInfo.Culture) { - if (culture == "*" || culture == cultureInfo.Culture) - content.SetCultureName(cultureInfo.Name, cultureInfo.Culture); + content.SetCultureName(cultureInfo.Name, cultureInfo.Culture); } } } + } - public static void SetPublishInfo(this IContent content, string? culture, string? name, DateTime date) + public static void SetPublishInfo(this IContent content, string? culture, string? name, DateTime date) + { + if (name == null) { - if (name == null) throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); + throw new ArgumentNullException(nameof(name)); + } - if (culture == null) throw new ArgumentNullException(nameof(culture)); - if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(culture)); + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(name)); + } - content.PublishCultureInfos?.AddOrUpdate(culture, name, date); + if (culture == null) + { + throw new ArgumentNullException(nameof(culture)); } - // sets the edited cultures on the content - public static void SetCultureEdited(this IContent content, IEnumerable? cultures) + if (string.IsNullOrWhiteSpace(culture)) { - if (cultures == null) - content.EditedCultures = null; - else - { - var editedCultures = new HashSet(cultures.Where(x => !x.IsNullOrWhiteSpace())!, StringComparer.OrdinalIgnoreCase); - content.EditedCultures = editedCultures.Count > 0 ? editedCultures : null; - } + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(culture)); } - /// - /// Sets the publishing values for names and properties. - /// - /// - /// - /// A value indicating whether it was possible to publish the names and values for the specified - /// culture(s). The method may fail if required names are not set, but it does NOT validate property data - public static bool PublishCulture(this IContent content, CultureImpact? impact) + content.PublishCultureInfos?.AddOrUpdate(culture, name, date); + } + + // sets the edited cultures on the content + public static void SetCultureEdited(this IContent content, IEnumerable? cultures) + { + if (cultures == null) + { + content.EditedCultures = null; + } + else + { + var editedCultures = new HashSet(cultures.Where(x => !x.IsNullOrWhiteSpace())!, + StringComparer.OrdinalIgnoreCase); + content.EditedCultures = editedCultures.Count > 0 ? editedCultures : null; + } + } + + /// + /// Sets the publishing values for names and properties. + /// + /// + /// + /// + /// A value indicating whether it was possible to publish the names and values for the specified + /// culture(s). The method may fail if required names are not set, but it does NOT validate property data + /// + public static bool PublishCulture(this IContent content, CultureImpact? impact) + { + if (impact == null) { - if (impact == null) throw new ArgumentNullException(nameof(impact)); + throw new ArgumentNullException(nameof(impact)); + } - // the variation should be supported by the content type properties - // if the content type is invariant, only '*' and 'null' is ok - // if the content type varies, everything is ok because some properties may be invariant - if (!content.ContentType.SupportsPropertyVariation(impact.Culture, "*", true)) - throw new NotSupportedException($"Culture \"{impact.Culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\"."); + // the variation should be supported by the content type properties + // if the content type is invariant, only '*' and 'null' is ok + // if the content type varies, everything is ok because some properties may be invariant + if (!content.ContentType.SupportsPropertyVariation(impact.Culture, "*", true)) + { + throw new NotSupportedException( + $"Culture \"{impact.Culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\"."); + } - // set names - if (impact.ImpactsAllCultures) + // set names + if (impact.ImpactsAllCultures) + { + foreach (var c in content.AvailableCultures) // does NOT contain the invariant culture { - foreach (var c in content.AvailableCultures) // does NOT contain the invariant culture + var name = content.GetCultureName(c); + if (string.IsNullOrWhiteSpace(name)) { - var name = content.GetCultureName(c); - if (string.IsNullOrWhiteSpace(name)) - return false; - content.SetPublishInfo(c, name, DateTime.Now); + return false; } + + content.SetPublishInfo(c, name, DateTime.Now); } - else if (impact.ImpactsOnlyInvariantCulture) + } + else if (impact.ImpactsOnlyInvariantCulture) + { + if (string.IsNullOrWhiteSpace(content.Name)) { - if (string.IsNullOrWhiteSpace(content.Name)) - return false; - // PublishName set by repository - nothing to do here + return false; } - else if (impact.ImpactsExplicitCulture) + // PublishName set by repository - nothing to do here + } + else if (impact.ImpactsExplicitCulture) + { + var name = content.GetCultureName(impact.Culture); + if (string.IsNullOrWhiteSpace(name)) { - var name = content.GetCultureName(impact.Culture); - if (string.IsNullOrWhiteSpace(name)) - return false; - content.SetPublishInfo(impact.Culture, name, DateTime.Now); + return false; } - // set values - // property.PublishValues only publishes what is valid, variation-wise, - // but accepts any culture arg: null, all, specific - foreach (var property in content.Properties) - { - // for the specified culture (null or all or specific) - property.PublishValues(impact.Culture); + content.SetPublishInfo(impact.Culture, name, DateTime.Now); + } - // maybe the specified culture did not impact the invariant culture, so PublishValues - // above would skip it, yet it *also* impacts invariant properties - if (impact.ImpactsAlsoInvariantProperties) - property.PublishValues(null); - } + // set values + // property.PublishValues only publishes what is valid, variation-wise, + // but accepts any culture arg: null, all, specific + foreach (IProperty property in content.Properties) + { + // for the specified culture (null or all or specific) + property.PublishValues(impact.Culture); - content.PublishedState = PublishedState.Publishing; - return true; + // maybe the specified culture did not impact the invariant culture, so PublishValues + // above would skip it, yet it *also* impacts invariant properties + if (impact.ImpactsAlsoInvariantProperties) + { + property.PublishValues(null); + } } - /// - /// Returns false if the culture is already unpublished - /// - /// - /// - /// - public static bool UnpublishCulture(this IContent content, string? culture = "*") + content.PublishedState = PublishedState.Publishing; + return true; + } + + /// + /// Returns false if the culture is already unpublished + /// + /// + /// + /// + public static bool UnpublishCulture(this IContent content, string? culture = "*") + { + culture = culture?.NullOrWhiteSpaceAsNull(); + + // the variation should be supported by the content type properties + if (!content.ContentType.SupportsPropertyVariation(culture, "*", true)) { - culture = culture?.NullOrWhiteSpaceAsNull(); + throw new NotSupportedException( + $"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\"."); + } - // the variation should be supported by the content type properties - if (!content.ContentType.SupportsPropertyVariation(culture, "*", true)) - throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\"."); + var keepProcessing = true; - var keepProcessing = true; + if (culture == "*") + { + // all cultures + content.ClearPublishInfos(); + } + else + { + // one single culture + keepProcessing = content.ClearPublishInfo(culture); + } - if (culture == "*") - { - // all cultures - content.ClearPublishInfos(); - } - else + if (keepProcessing) + { + // property.PublishValues only publishes what is valid, variation-wise + foreach (IProperty property in content.Properties) { - // one single culture - keepProcessing = content.ClearPublishInfo(culture); + property.UnpublishValues(culture); } - if (keepProcessing) - { - // property.PublishValues only publishes what is valid, variation-wise - foreach (var property in content.Properties) - property.UnpublishValues(culture); + content.PublishedState = PublishedState.Publishing; + } - content.PublishedState = PublishedState.Publishing; - } + return keepProcessing; + } - return keepProcessing; - } + public static void ClearPublishInfos(this IContent content) => content.PublishCultureInfos = null; - public static void ClearPublishInfos(this IContent content) + /// + /// Returns false if the culture is already unpublished + /// + /// + /// + /// + public static bool ClearPublishInfo(this IContent content, string? culture) + { + if (culture == null) { - content.PublishCultureInfos = null; + throw new ArgumentNullException(nameof(culture)); } - /// - /// Returns false if the culture is already unpublished - /// - /// - /// - /// - public static bool ClearPublishInfo(this IContent content, string? culture) + if (string.IsNullOrWhiteSpace(culture)) { - if (culture == null) throw new ArgumentNullException(nameof(culture)); - if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(culture)); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(culture)); + } - var removed = content.PublishCultureInfos?.Remove(culture); - if (removed ?? false) - { - // set the culture to be dirty - it's been modified - content.TouchCulture(culture); - } - return removed ?? false; + var removed = content.PublishCultureInfos?.Remove(culture); + if (removed ?? false) + { + // set the culture to be dirty - it's been modified + content.TouchCulture(culture); } + + return removed ?? false; } } diff --git a/src/Umbraco.Core/Models/ContentSchedule.cs b/src/Umbraco.Core/Models/ContentSchedule.cs index 77526f254a3b..589a051f7656 100644 --- a/src/Umbraco.Core/Models/ContentSchedule.cs +++ b/src/Umbraco.Core/Models/ContentSchedule.cs @@ -1,78 +1,71 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a scheduled action for a document. +/// +[Serializable] +[DataContract(IsReference = true)] +public class ContentSchedule : IDeepCloneable { /// - /// Represents a scheduled action for a document. + /// Initializes a new instance of the class. /// - [Serializable] - [DataContract(IsReference = true)] - public class ContentSchedule : IDeepCloneable + public ContentSchedule(string culture, DateTime date, ContentScheduleAction action) { - /// - /// Initializes a new instance of the class. - /// - public ContentSchedule(string culture, DateTime date, ContentScheduleAction action) - { - Id = Guid.Empty; // will be assigned by document repository - Culture = culture; - Date = date; - Action = action; - } + Id = Guid.Empty; // will be assigned by document repository + Culture = culture; + Date = date; + Action = action; + } - /// - /// Initializes a new instance of the class. - /// - public ContentSchedule(Guid id, string culture, DateTime date, ContentScheduleAction action) - { - Id = id; - Culture = culture; - Date = date; - Action = action; - } + /// + /// Initializes a new instance of the class. + /// + public ContentSchedule(Guid id, string culture, DateTime date, ContentScheduleAction action) + { + Id = id; + Culture = culture; + Date = date; + Action = action; + } - /// - /// Gets the unique identifier of the document targeted by the scheduled action. - /// - [DataMember] - public Guid Id { get; set; } + /// + /// Gets the unique identifier of the document targeted by the scheduled action. + /// + [DataMember] + public Guid Id { get; set; } - /// - /// Gets the culture of the scheduled action. - /// - /// - /// string.Empty represents the invariant culture. - /// - [DataMember] - public string Culture { get; } + /// + /// Gets the culture of the scheduled action. + /// + /// + /// string.Empty represents the invariant culture. + /// + [DataMember] + public string Culture { get; } - /// - /// Gets the date of the scheduled action. - /// - [DataMember] - public DateTime Date { get; } + /// + /// Gets the date of the scheduled action. + /// + [DataMember] + public DateTime Date { get; } - /// - /// Gets the action to take. - /// - [DataMember] - public ContentScheduleAction Action { get; } + /// + /// Gets the action to take. + /// + [DataMember] + public ContentScheduleAction Action { get; } - public override bool Equals(object? obj) - => obj is ContentSchedule other && Equals(other); + public object DeepClone() => new ContentSchedule(Id, Culture, Date, Action); - public bool Equals(ContentSchedule other) - { - // don't compare Ids, two ContentSchedule are equal if they are for the same change - // for the same culture, on the same date - and the collection deals w/duplicates - return Culture.InvariantEquals(other.Culture) && Date == other.Date && Action == other.Action; - } + public override bool Equals(object? obj) + => obj is ContentSchedule other && Equals(other); - public object DeepClone() - { - return new ContentSchedule(Id, Culture, Date, Action); - } - } + public bool Equals(ContentSchedule other) => + // don't compare Ids, two ContentSchedule are equal if they are for the same change + // for the same culture, on the same date - and the collection deals w/duplicates + Culture.InvariantEquals(other.Culture) && Date == other.Date && Action == other.Action; } diff --git a/src/Umbraco.Core/Models/ContentScheduleAction.cs b/src/Umbraco.Core/Models/ContentScheduleAction.cs index 03be526814ed..ab6452c64ade 100644 --- a/src/Umbraco.Core/Models/ContentScheduleAction.cs +++ b/src/Umbraco.Core/Models/ContentScheduleAction.cs @@ -1,18 +1,17 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Defines scheduled actions for documents. +/// +public enum ContentScheduleAction { /// - /// Defines scheduled actions for documents. + /// Release the document. /// - public enum ContentScheduleAction - { - /// - /// Release the document. - /// - Release, + Release, - /// - /// Expire the document. - /// - Expire - } + /// + /// Expire the document. + /// + Expire } diff --git a/src/Umbraco.Core/Models/ContentScheduleCollection.cs b/src/Umbraco.Core/Models/ContentScheduleCollection.cs index 12a53fd103ee..4f333bd3a31f 100644 --- a/src/Umbraco.Core/Models/ContentScheduleCollection.cs +++ b/src/Umbraco.Core/Models/ContentScheduleCollection.cs @@ -1,242 +1,258 @@ -using System; -using System.Collections.Generic; using System.Collections.Specialized; -using System.Linq; -using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models -{ - public class ContentScheduleCollection : INotifyCollectionChanged, IDeepCloneable, IEquatable - { - //underlying storage for the collection backed by a sorted list so that the schedule is always in order of date and that duplicate dates per culture are not allowed - private readonly Dictionary> _schedule - = new Dictionary>(StringComparer.InvariantCultureIgnoreCase); +namespace Umbraco.Cms.Core.Models; - public event NotifyCollectionChangedEventHandler? CollectionChanged; - - /// - /// Clears all event handlers - /// - public void ClearCollectionChangedEvents() => CollectionChanged = null; +public class ContentScheduleCollection : INotifyCollectionChanged, IDeepCloneable, IEquatable +{ + //underlying storage for the collection backed by a sorted list so that the schedule is always in order of date and that duplicate dates per culture are not allowed + private readonly Dictionary> _schedule + = new(StringComparer.InvariantCultureIgnoreCase); - private void OnCollectionChanged(NotifyCollectionChangedEventArgs args) - { - CollectionChanged?.Invoke(this, args); - } + /// + /// Returns all schedules registered + /// + /// + public IReadOnlyList FullSchedule => _schedule.SelectMany(x => x.Value.Values).ToList(); - /// - /// Add an existing schedule - /// - /// - public void Add(ContentSchedule schedule) + public object DeepClone() + { + var clone = new ContentScheduleCollection(); + foreach (KeyValuePair> cultureSched in _schedule) { - if (!_schedule.TryGetValue(schedule.Culture, out var changes)) + var list = new SortedList(); + foreach (KeyValuePair schedEntry in cultureSched.Value) { - changes = new SortedList(); - _schedule[schedule.Culture] = changes; + list.Add(schedEntry.Key, (ContentSchedule)schedEntry.Value.DeepClone()); } - // TODO: Below will throw if there are duplicate dates added, validate/return bool? - changes.Add(schedule.Date, schedule); + clone._schedule[cultureSched.Key] = list; + } + + return clone; + } - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, schedule)); + public bool Equals(ContentScheduleCollection? other) + { + if (other == null) + { + return false; } - /// - /// Adds a new schedule for invariant content - /// - /// - /// - public bool Add(DateTime? releaseDate, DateTime? expireDate) + Dictionary> thisSched = _schedule; + Dictionary> thatSched = other._schedule; + + if (thisSched.Count != thatSched.Count) { - return Add(string.Empty, releaseDate, expireDate); + return false; } - /// - /// Adds a new schedule for a culture - /// - /// - /// - /// - /// true if successfully added, false if validation fails - public bool Add(string? culture, DateTime? releaseDate, DateTime? expireDate) + foreach ((var culture, SortedList thisList) in thisSched) { - if (culture == null) throw new ArgumentNullException(nameof(culture)); - if (releaseDate.HasValue && expireDate.HasValue && releaseDate >= expireDate) + // if culture is missing, or actions differ, false + if (!thatSched.TryGetValue(culture, out SortedList thatList) || + !thatList.SequenceEqual(thisList)) + { return false; + } + } - if (!releaseDate.HasValue && !expireDate.HasValue) return false; + return true; + } - // TODO: Do we allow passing in a release or expiry date that is before now? + public event NotifyCollectionChangedEventHandler? CollectionChanged; - if (!_schedule.TryGetValue(culture, out var changes)) - { - changes = new SortedList(); - _schedule[culture] = changes; - } + /// + /// Clears all event handlers + /// + public void ClearCollectionChangedEvents() => CollectionChanged = null; - // TODO: Below will throw if there are duplicate dates added, should validate/return bool? - // but the bool won't indicate which date was in error, maybe have 2 diff methods to schedule start/end? + private void OnCollectionChanged(NotifyCollectionChangedEventArgs args) => CollectionChanged?.Invoke(this, args); - if (releaseDate.HasValue) - { - var entry = new ContentSchedule(culture, releaseDate.Value, ContentScheduleAction.Release); - changes.Add(releaseDate.Value, entry); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, entry)); - } + /// + /// Add an existing schedule + /// + /// + public void Add(ContentSchedule schedule) + { + if (!_schedule.TryGetValue(schedule.Culture, out SortedList changes)) + { + changes = new SortedList(); + _schedule[schedule.Culture] = changes; + } - if (expireDate.HasValue) - { - var entry = new ContentSchedule(culture, expireDate.Value, ContentScheduleAction.Expire); - changes.Add(expireDate.Value, entry); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, entry)); - } + // TODO: Below will throw if there are duplicate dates added, validate/return bool? + changes.Add(schedule.Date, schedule); - return true; - } + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, schedule)); + } - /// - /// Remove a scheduled change - /// - /// - public void Remove(ContentSchedule change) + /// + /// Adds a new schedule for invariant content + /// + /// + /// + public bool Add(DateTime? releaseDate, DateTime? expireDate) => Add(string.Empty, releaseDate, expireDate); + + /// + /// Adds a new schedule for a culture + /// + /// + /// + /// + /// true if successfully added, false if validation fails + public bool Add(string? culture, DateTime? releaseDate, DateTime? expireDate) + { + if (culture == null) { - if (_schedule.TryGetValue(change.Culture, out var s)) - { - var removed = s.Remove(change.Date); - if (removed) - { - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, change)); - if (s.Count == 0) - _schedule.Remove(change.Culture); - } - - } + throw new ArgumentNullException(nameof(culture)); } - /// - /// Clear all of the scheduled change type for invariant content - /// - /// - /// If specified, will clear all entries with dates less than or equal to the value - public void Clear(ContentScheduleAction action, DateTime? changeDate = null) + if (releaseDate.HasValue && expireDate.HasValue && releaseDate >= expireDate) { - Clear(string.Empty, action, changeDate); + return false; } - /// - /// Clear all of the scheduled change type for the culture - /// - /// - /// - /// If specified, will clear all entries with dates less than or equal to the value - public void Clear(string? culture, ContentScheduleAction action, DateTime? date = null) + if (!releaseDate.HasValue && !expireDate.HasValue) { - if (culture is null || !_schedule.TryGetValue(culture, out var schedules)) - return; - - var removes = schedules.Where(x => x.Value.Action == action && (!date.HasValue || x.Value.Date <= date.Value)).ToList(); - - foreach (var remove in removes) - { - var removed = schedules.Remove(remove.Value.Date); - if (!removed) - continue; - - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, remove.Value)); - } - - if (schedules.Count == 0) - _schedule.Remove(culture); + return false; } - /// - /// Returns all pending schedules based on the date and type provided - /// - /// - /// - /// - public IReadOnlyList GetPending(ContentScheduleAction action, DateTime date) + // TODO: Do we allow passing in a release or expiry date that is before now? + + if (!_schedule.TryGetValue(culture, out SortedList changes)) { - return _schedule.Values.SelectMany(x => x.Values).Where(x => x.Date <= date).ToList(); + changes = new SortedList(); + _schedule[culture] = changes; } - /// - /// Gets the schedule for invariant content - /// - /// - public IEnumerable GetSchedule(ContentScheduleAction? action = null) + // TODO: Below will throw if there are duplicate dates added, should validate/return bool? + // but the bool won't indicate which date was in error, maybe have 2 diff methods to schedule start/end? + + if (releaseDate.HasValue) { - return GetSchedule(string.Empty, action); + var entry = new ContentSchedule(culture, releaseDate.Value, ContentScheduleAction.Release); + changes.Add(releaseDate.Value, entry); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, entry)); } - /// - /// Gets the schedule for a culture - /// - /// - /// - /// - public IEnumerable GetSchedule(string? culture, ContentScheduleAction? action = null) + if (expireDate.HasValue) { - if (culture is not null && _schedule.TryGetValue(culture, out var changes)) - return action == null ? changes.Values : changes.Values.Where(x => x.Action == action.Value); - return Enumerable.Empty(); + var entry = new ContentSchedule(culture, expireDate.Value, ContentScheduleAction.Expire); + changes.Add(expireDate.Value, entry); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, entry)); } - /// - /// Returns all schedules registered - /// - /// - public IReadOnlyList FullSchedule => _schedule.SelectMany(x => x.Value.Values).ToList(); + return true; + } - public object DeepClone() + /// + /// Remove a scheduled change + /// + /// + public void Remove(ContentSchedule change) + { + if (_schedule.TryGetValue(change.Culture, out SortedList s)) { - var clone = new ContentScheduleCollection(); - foreach(var cultureSched in _schedule) + var removed = s.Remove(change.Date); + if (removed) { - var list = new SortedList(); - foreach (var schedEntry in cultureSched.Value) - list.Add(schedEntry.Key, (ContentSchedule)schedEntry.Value.DeepClone()); - clone._schedule[cultureSched.Key] = list; + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, change)); + if (s.Count == 0) + { + _schedule.Remove(change.Culture); + } } - return clone; } + } - public override bool Equals(object? obj) - => obj is ContentScheduleCollection other && Equals(other); - - public bool Equals(ContentScheduleCollection? other) + /// + /// Clear all of the scheduled change type for invariant content + /// + /// + /// If specified, will clear all entries with dates less than or equal to the value + public void Clear(ContentScheduleAction action, DateTime? changeDate = null) => + Clear(string.Empty, action, changeDate); + + /// + /// Clear all of the scheduled change type for the culture + /// + /// + /// + /// If specified, will clear all entries with dates less than or equal to the value + public void Clear(string? culture, ContentScheduleAction action, DateTime? date = null) + { + if (culture is null || !_schedule.TryGetValue(culture, out SortedList schedules)) { - if (other == null) return false; - - var thisSched = _schedule; - var thatSched = other._schedule; + return; + } - if (thisSched.Count != thatSched.Count) - return false; + var removes = schedules.Where(x => x.Value.Action == action && (!date.HasValue || x.Value.Date <= date.Value)) + .ToList(); - foreach (var (culture, thisList) in thisSched) + foreach (KeyValuePair remove in removes) + { + var removed = schedules.Remove(remove.Value.Date); + if (!removed) { - // if culture is missing, or actions differ, false - if (!thatSched.TryGetValue(culture, out var thatList) || !thatList.SequenceEqual(thisList)) - return false; + continue; } - return true; + OnCollectionChanged( + new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, remove.Value)); } - public static ContentScheduleCollection CreateWithEntry(DateTime? release, DateTime? expire) + if (schedules.Count == 0) { - var schedule = new ContentScheduleCollection(); - schedule.Add(string.Empty, release, expire); - return schedule; + _schedule.Remove(culture); } + } - public static ContentScheduleCollection CreateWithEntry(string culture, DateTime? release, DateTime? expire) + /// + /// Returns all pending schedules based on the date and type provided + /// + /// + /// + /// + public IReadOnlyList GetPending(ContentScheduleAction action, DateTime date) => + _schedule.Values.SelectMany(x => x.Values).Where(x => x.Date <= date).ToList(); + + /// + /// Gets the schedule for invariant content + /// + /// + public IEnumerable GetSchedule(ContentScheduleAction? action = null) => + GetSchedule(string.Empty, action); + + /// + /// Gets the schedule for a culture + /// + /// + /// + /// + public IEnumerable GetSchedule(string? culture, ContentScheduleAction? action = null) + { + if (culture is not null && _schedule.TryGetValue(culture, out SortedList changes)) { - var schedule = new ContentScheduleCollection(); - schedule.Add(culture, release, expire); - return schedule; + return action == null ? changes.Values : changes.Values.Where(x => x.Action == action.Value); } + + return Enumerable.Empty(); + } + + public override bool Equals(object? obj) + => obj is ContentScheduleCollection other && Equals(other); + + public static ContentScheduleCollection CreateWithEntry(DateTime? release, DateTime? expire) + { + var schedule = new ContentScheduleCollection(); + schedule.Add(string.Empty, release, expire); + return schedule; + } + + public static ContentScheduleCollection CreateWithEntry(string culture, DateTime? release, DateTime? expire) + { + var schedule = new ContentScheduleCollection(); + schedule.Add(culture, release, expire); + return schedule; } } diff --git a/src/Umbraco.Core/Models/ContentStatus.cs b/src/Umbraco.Core/Models/ContentStatus.cs index 15d5d5986121..8d59dc9f9da4 100644 --- a/src/Umbraco.Core/Models/ContentStatus.cs +++ b/src/Umbraco.Core/Models/ContentStatus.cs @@ -1,46 +1,39 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Describes the states of a document, with regard to (schedule) publishing. +/// +[Serializable] +[DataContract] +public enum ContentStatus { + // typical flow: + // Unpublished (add release date)-> AwaitingRelease (release)-> Published (expire)-> Expired + /// - /// Describes the states of a document, with regard to (schedule) publishing. + /// The document is not trashed, and not published. /// - [Serializable] - [DataContract] - public enum ContentStatus - { - // typical flow: - // Unpublished (add release date)-> AwaitingRelease (release)-> Published (expire)-> Expired - - /// - /// The document is not trashed, and not published. - /// - [EnumMember] - Unpublished, + [EnumMember] Unpublished, - /// - /// The document is published. - /// - [EnumMember] - Published, + /// + /// The document is published. + /// + [EnumMember] Published, - /// - /// The document is not trashed, not published, after being unpublished by a scheduled action. - /// - [EnumMember] - Expired, + /// + /// The document is not trashed, not published, after being unpublished by a scheduled action. + /// + [EnumMember] Expired, - /// - /// The document is trashed. - /// - [EnumMember] - Trashed, + /// + /// The document is trashed. + /// + [EnumMember] Trashed, - /// - /// The document is not trashed, not published, and pending publication by a scheduled action. - /// - [EnumMember] - AwaitingRelease - } + /// + /// The document is not trashed, not published, and pending publication by a scheduled action. + /// + [EnumMember] AwaitingRelease } diff --git a/src/Umbraco.Core/Models/ContentTagsExtensions.cs b/src/Umbraco.Core/Models/ContentTagsExtensions.cs index 0dacd788445e..d0f76de4f9f1 100644 --- a/src/Umbraco.Core/Models/ContentTagsExtensions.cs +++ b/src/Umbraco.Core/Models/ContentTagsExtensions.cs @@ -1,55 +1,59 @@ -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Provides extension methods for the class, to manage tags. +/// +public static class ContentTagsExtensions { /// - /// Provides extension methods for the class, to manage tags. + /// Assign tags. /// - public static class ContentTagsExtensions + /// The content item. + /// + /// The property alias. + /// The tags. + /// A value indicating whether to merge the tags with existing tags instead of replacing them. + /// A culture, for multi-lingual properties. + /// + public static void AssignTags(this IContentBase content, PropertyEditorCollection propertyEditors, + IDataTypeService dataTypeService, IJsonSerializer serializer, string propertyTypeAlias, + IEnumerable tags, bool merge = false, string? culture = null) => content + .GetTagProperty(propertyTypeAlias) + .AssignTags(propertyEditors, dataTypeService, serializer, tags, merge, culture); + + /// + /// Remove tags. + /// + /// The content item. + /// + /// The property alias. + /// The tags. + /// A culture, for multi-lingual properties. + /// + public static void RemoveTags(this IContentBase content, PropertyEditorCollection propertyEditors, + IDataTypeService dataTypeService, IJsonSerializer serializer, string propertyTypeAlias, + IEnumerable tags, string? culture = null) => content.GetTagProperty(propertyTypeAlias) + .RemoveTags(propertyEditors, dataTypeService, serializer, tags, culture); + + // gets and validates the property + private static IProperty GetTagProperty(this IContentBase content, string propertyTypeAlias) { - /// - /// Assign tags. - /// - /// The content item. - /// - /// The property alias. - /// The tags. - /// A value indicating whether to merge the tags with existing tags instead of replacing them. - /// A culture, for multi-lingual properties. - /// - public static void AssignTags(this IContentBase content, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IJsonSerializer serializer, string propertyTypeAlias, IEnumerable tags, bool merge = false, string? culture = null) + if (content == null) { - content.GetTagProperty(propertyTypeAlias).AssignTags(propertyEditors, dataTypeService, serializer, tags, merge, culture); + throw new ArgumentNullException(nameof(content)); } - /// - /// Remove tags. - /// - /// The content item. - /// - /// The property alias. - /// The tags. - /// A culture, for multi-lingual properties. - /// - public static void RemoveTags(this IContentBase content, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IJsonSerializer serializer, string propertyTypeAlias, IEnumerable tags, string? culture = null) + IProperty property = content.Properties[propertyTypeAlias]; + if (property != null) { - content.GetTagProperty(propertyTypeAlias).RemoveTags(propertyEditors, dataTypeService, serializer, tags, culture); + return property; } - // gets and validates the property - private static IProperty GetTagProperty(this IContentBase content, string propertyTypeAlias) - { - if (content == null) throw new ArgumentNullException(nameof(content)); - - var property = content.Properties[propertyTypeAlias]; - if (property != null) return property; - - throw new IndexOutOfRangeException($"Could not find a property with alias \"{propertyTypeAlias}\"."); - } + throw new IndexOutOfRangeException($"Could not find a property with alias \"{propertyTypeAlias}\"."); } } diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index 9c21cf5e80e3..2f84336c83f8 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -1,175 +1,171 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents the content type that a object is based on +/// +[Serializable] +[DataContract(IsReference = true)] +public class ContentType : ContentTypeCompositionBase, IContentTypeWithHistoryCleanup { - /// - /// Represents the content type that a object is based on - /// - [Serializable] - [DataContract(IsReference = true)] - public class ContentType : ContentTypeCompositionBase, IContentTypeWithHistoryCleanup - { - public const bool SupportsPublishingConst = true; + public const bool SupportsPublishingConst = true; - // Custom comparer for enumerable - private static readonly DelegateEqualityComparer> TemplateComparer = new ( - (templates, enumerable) => templates.UnsortedSequenceEqual(enumerable), - templates => templates.GetHashCode()); + // Custom comparer for enumerable + private static readonly DelegateEqualityComparer> TemplateComparer = new( + (templates, enumerable) => templates.UnsortedSequenceEqual(enumerable), + templates => templates.GetHashCode()); - private IEnumerable? _allowedTemplates; + private IEnumerable? _allowedTemplates; - private int _defaultTemplate; + private int _defaultTemplate; - /// - /// Constuctor for creating a ContentType with the parent's id. - /// - /// Only use this for creating ContentTypes at the root (with ParentId -1). - /// - public ContentType(IShortStringHelper shortStringHelper, int parentId) : base(shortStringHelper, parentId) - { - _allowedTemplates = new List(); - HistoryCleanup = new HistoryCleanup(); - } + private HistoryCleanup? _historyCleanup; + /// + /// Constuctor for creating a ContentType with the parent's id. + /// + /// Only use this for creating ContentTypes at the root (with ParentId -1). + /// + public ContentType(IShortStringHelper shortStringHelper, int parentId) : base(shortStringHelper, parentId) + { + _allowedTemplates = new List(); + HistoryCleanup = new HistoryCleanup(); + } - /// - /// Constuctor for creating a ContentType with the parent as an inherited type. - /// - /// Use this to ensure inheritance from parent. - /// - /// - public ContentType(IShortStringHelper shortStringHelper, IContentType parent, string alias) - : base(shortStringHelper, parent, alias) - { - _allowedTemplates = new List(); - HistoryCleanup = new HistoryCleanup(); - } - /// - public override bool SupportsPublishing => SupportsPublishingConst; + /// + /// Constuctor for creating a ContentType with the parent as an inherited type. + /// + /// Use this to ensure inheritance from parent. + /// + /// + public ContentType(IShortStringHelper shortStringHelper, IContentType parent, string alias) + : base(shortStringHelper, parent, alias) + { + _allowedTemplates = new List(); + HistoryCleanup = new HistoryCleanup(); + } + + /// + public override bool SupportsPublishing => SupportsPublishingConst; - /// - public override ISimpleContentType ToSimple() => new SimpleContentType(this); + /// + public override ISimpleContentType ToSimple() => new SimpleContentType(this); - /// - /// Gets or sets the alias of the default Template. - /// TODO: This should be ignored from cloning!!!!!!!!!!!!!! - /// - but to do that we have to implement callback hacks, this needs to be fixed in v8, - /// we should not store direct entity - /// - [IgnoreDataMember] - public ITemplate? DefaultTemplate => - AllowedTemplates?.FirstOrDefault(x => x != null && x.Id == DefaultTemplateId); + /// + /// Gets or sets the alias of the default Template. + /// TODO: This should be ignored from cloning!!!!!!!!!!!!!! + /// - but to do that we have to implement callback hacks, this needs to be fixed in v8, + /// we should not store direct entity + /// + [IgnoreDataMember] + public ITemplate? DefaultTemplate => + AllowedTemplates?.FirstOrDefault(x => x != null && x.Id == DefaultTemplateId); - [DataMember] - public int DefaultTemplateId - { - get => _defaultTemplate; - set => SetPropertyValueAndDetectChanges(value, ref _defaultTemplate, nameof(DefaultTemplateId)); - } + [DataMember] + public int DefaultTemplateId + { + get => _defaultTemplate; + set => SetPropertyValueAndDetectChanges(value, ref _defaultTemplate, nameof(DefaultTemplateId)); + } - /// - /// Gets or Sets a list of Templates which are allowed for the ContentType - /// TODO: This should be ignored from cloning!!!!!!!!!!!!!! - /// - but to do that we have to implement callback hacks, this needs to be fixed in v8, - /// we should not store direct entity - /// - [DataMember] - public IEnumerable? AllowedTemplates + /// + /// Gets or Sets a list of Templates which are allowed for the ContentType + /// TODO: This should be ignored from cloning!!!!!!!!!!!!!! + /// - but to do that we have to implement callback hacks, this needs to be fixed in v8, + /// we should not store direct entity + /// + [DataMember] + public IEnumerable? AllowedTemplates + { + get => _allowedTemplates; + set { - get => _allowedTemplates; - set - { - SetPropertyValueAndDetectChanges(value, ref _allowedTemplates, nameof(AllowedTemplates), TemplateComparer); + SetPropertyValueAndDetectChanges(value, ref _allowedTemplates, nameof(AllowedTemplates), TemplateComparer); - if (_allowedTemplates?.Any(x => x.Id == _defaultTemplate) == false) - { - DefaultTemplateId = 0; - } + if (_allowedTemplates?.Any(x => x.Id == _defaultTemplate) == false) + { + DefaultTemplateId = 0; } } + } - private HistoryCleanup? _historyCleanup; + public HistoryCleanup? HistoryCleanup + { + get => _historyCleanup; + set => SetPropertyValueAndDetectChanges(value, ref _historyCleanup, nameof(HistoryCleanup)); + } - public HistoryCleanup? HistoryCleanup - { - get => _historyCleanup; - set => SetPropertyValueAndDetectChanges(value, ref _historyCleanup, nameof(HistoryCleanup)); - } + /// + /// Determines if AllowedTemplates contains templateId + /// + /// The template id to check + /// True if AllowedTemplates contains the templateId else False + public bool IsAllowedTemplate(int templateId) => + AllowedTemplates == null + ? false + : AllowedTemplates.Any(t => t.Id == templateId); - /// - /// Determines if AllowedTemplates contains templateId - /// - /// The template id to check - /// True if AllowedTemplates contains the templateId else False - public bool IsAllowedTemplate(int templateId) => - AllowedTemplates == null - ? false - : AllowedTemplates.Any(t => t.Id == templateId); - - /// - /// Determines if AllowedTemplates contains templateId - /// - /// The template alias to check - /// True if AllowedTemplates contains the templateAlias else False - public bool IsAllowedTemplate(string templateAlias) => - AllowedTemplates == null - ? false - : AllowedTemplates.Any(t => t.Alias.Equals(templateAlias, StringComparison.InvariantCultureIgnoreCase)); - - /// - /// Sets the default template for the ContentType - /// - /// Default - public void SetDefaultTemplate(ITemplate? template) - { - if (template == null) - { - DefaultTemplateId = 0; - return; - } + /// + /// Determines if AllowedTemplates contains templateId + /// + /// The template alias to check + /// True if AllowedTemplates contains the templateAlias else False + public bool IsAllowedTemplate(string templateAlias) => + AllowedTemplates == null + ? false + : AllowedTemplates.Any(t => t.Alias.Equals(templateAlias, StringComparison.InvariantCultureIgnoreCase)); - DefaultTemplateId = template.Id; - if (_allowedTemplates?.Any(x => x != null && x.Id == template.Id) == false) - { - var templates = AllowedTemplates?.ToList(); - templates?.Add(template); - AllowedTemplates = templates; - } + /// + /// Sets the default template for the ContentType + /// + /// Default + public void SetDefaultTemplate(ITemplate? template) + { + if (template == null) + { + DefaultTemplateId = 0; + return; } - /// - /// Removes a template from the list of allowed templates - /// - /// to remove - /// True if template was removed, otherwise False - public bool RemoveTemplate(ITemplate template) + DefaultTemplateId = template.Id; + if (_allowedTemplates?.Any(x => x != null && x.Id == template.Id) == false) { - if (DefaultTemplateId == template.Id) - { - DefaultTemplateId = default; - } - var templates = AllowedTemplates?.ToList(); - ITemplate? remove = templates?.FirstOrDefault(x => x.Id == template.Id); - var result = remove is not null && templates is not null && templates.Remove(remove); + templates?.Add(template); AllowedTemplates = templates; + } + } - return result; + /// + /// Removes a template from the list of allowed templates + /// + /// to remove + /// True if template was removed, otherwise False + public bool RemoveTemplate(ITemplate template) + { + if (DefaultTemplateId == template.Id) + { + DefaultTemplateId = default; } - /// - IContentType IContentType.DeepCloneWithResetIdentities(string newAlias) => - (IContentType)DeepCloneWithResetIdentities(newAlias); + var templates = AllowedTemplates?.ToList(); + ITemplate? remove = templates?.FirstOrDefault(x => x.Id == template.Id); + var result = remove is not null && templates is not null && templates.Remove(remove); + AllowedTemplates = templates; - /// - public override bool IsDirty() => base.IsDirty() || (HistoryCleanup?.IsDirty() ?? false); + return result; } + + /// + IContentType IContentType.DeepCloneWithResetIdentities(string newAlias) => + (IContentType)DeepCloneWithResetIdentities(newAlias); + + /// + public override bool IsDirty() => base.IsDirty() || (HistoryCleanup?.IsDirty() ?? false); } diff --git a/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResult.cs b/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResult.cs index 529ae0bbe6f5..44bc7884adcb 100644 --- a/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResult.cs +++ b/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResult.cs @@ -1,17 +1,16 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Used when determining available compositions for a given content type +/// +public class ContentTypeAvailableCompositionsResult { - /// - /// Used when determining available compositions for a given content type - /// - public class ContentTypeAvailableCompositionsResult + public ContentTypeAvailableCompositionsResult(IContentTypeComposition composition, bool allowed) { - public ContentTypeAvailableCompositionsResult(IContentTypeComposition composition, bool allowed) - { - Composition = composition; - Allowed = allowed; - } - - public IContentTypeComposition Composition { get; private set; } - public bool Allowed { get; private set; } + Composition = composition; + Allowed = allowed; } + + public IContentTypeComposition Composition { get; } + public bool Allowed { get; } } diff --git a/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResults.cs b/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResults.cs index 180552cd7497..eaac950c2daa 100644 --- a/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResults.cs +++ b/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResults.cs @@ -1,26 +1,23 @@ -using System.Collections.Generic; -using System.Linq; +namespace Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Models +/// +/// Used when determining available compositions for a given content type +/// +public class ContentTypeAvailableCompositionsResults { - /// - /// Used when determining available compositions for a given content type - /// - public class ContentTypeAvailableCompositionsResults + public ContentTypeAvailableCompositionsResults() { - public ContentTypeAvailableCompositionsResults() - { - Ancestors = Enumerable.Empty(); - Results = Enumerable.Empty(); - } - - public ContentTypeAvailableCompositionsResults(IEnumerable ancestors, IEnumerable results) - { - Ancestors = ancestors; - Results = results; - } + Ancestors = Enumerable.Empty(); + Results = Enumerable.Empty(); + } - public IEnumerable Ancestors { get; private set; } - public IEnumerable Results { get; private set; } + public ContentTypeAvailableCompositionsResults(IEnumerable ancestors, + IEnumerable results) + { + Ancestors = ancestors; + Results = results; } + + public IEnumerable Ancestors { get; } + public IEnumerable Results { get; } } diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index bf7cd0d8e3b7..34aa4097a305 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -1,527 +1,540 @@ -using System; -using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; -using System.Linq; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents an abstract class for base ContentType properties and methods +/// +[Serializable] +[DataContract(IsReference = true)] +[DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] +public abstract class ContentTypeBase : TreeEntityBase, IContentTypeBase { - /// - /// Represents an abstract class for base ContentType properties and methods - /// - [Serializable] - [DataContract(IsReference = true)] - [DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] - public abstract class ContentTypeBase : TreeEntityBase, IContentTypeBase + //Custom comparer for enumerable + private static readonly DelegateEqualityComparer> ContentTypeSortComparer = + new( + (sorts, enumerable) => sorts.UnsortedSequenceEqual(enumerable), + sorts => sorts.GetHashCode()); + + private readonly IShortStringHelper _shortStringHelper; + + private string _alias; + private bool _allowedAsRoot; // note: only one that's not 'pure element type' + private IEnumerable? _allowedContentTypes; + private string? _description; + private bool _hasPropertyTypeBeenRemoved; + private string? _icon = "icon-folder"; + private bool _isContainer; + private bool _isElement; + private PropertyGroupCollection _propertyGroups; + private string? _thumbnail = "folder.png"; + private ContentVariation _variations; + + protected ContentTypeBase(IShortStringHelper shortStringHelper, int parentId) { - private readonly IShortStringHelper _shortStringHelper; - - private string _alias; - private string? _description; - private string? _icon = "icon-folder"; - private string? _thumbnail = "folder.png"; - private bool _allowedAsRoot; // note: only one that's not 'pure element type' - private bool _isContainer; - private bool _isElement; - private PropertyGroupCollection _propertyGroups; - private PropertyTypeCollection _noGroupPropertyTypes; - private IEnumerable? _allowedContentTypes; - private bool _hasPropertyTypeBeenRemoved; - private ContentVariation _variations; - - protected ContentTypeBase(IShortStringHelper shortStringHelper, int parentId) + _alias = string.Empty; + _shortStringHelper = shortStringHelper; + if (parentId == 0) { - _alias = string.Empty; - _shortStringHelper = shortStringHelper; - if (parentId == 0) throw new ArgumentOutOfRangeException(nameof(parentId)); - ParentId = parentId; + throw new ArgumentOutOfRangeException(nameof(parentId)); + } - _allowedContentTypes = new List(); - _propertyGroups = new PropertyGroupCollection(); + ParentId = parentId; - // actually OK as IsPublishing is constant - // ReSharper disable once VirtualMemberCallInConstructor - _noGroupPropertyTypes = new PropertyTypeCollection(SupportsPublishing); - _noGroupPropertyTypes.CollectionChanged += PropertyTypesChanged; + _allowedContentTypes = new List(); + _propertyGroups = new PropertyGroupCollection(); - _variations = ContentVariation.Nothing; - } + // actually OK as IsPublishing is constant + // ReSharper disable once VirtualMemberCallInConstructor + PropertyTypeCollection = new PropertyTypeCollection(SupportsPublishing); + PropertyTypeCollection.CollectionChanged += PropertyTypesChanged; - protected ContentTypeBase(IShortStringHelper shortStringHelper, IContentTypeBase parent) - : this(shortStringHelper, parent, string.Empty) - { } + _variations = ContentVariation.Nothing; + } - protected ContentTypeBase(IShortStringHelper shortStringHelper, IContentTypeBase parent, string alias) + protected ContentTypeBase(IShortStringHelper shortStringHelper, IContentTypeBase parent) + : this(shortStringHelper, parent, string.Empty) + { + } + + protected ContentTypeBase(IShortStringHelper shortStringHelper, IContentTypeBase parent, string alias) + { + if (parent == null) { - if (parent == null) throw new ArgumentNullException(nameof(parent)); - SetParent(parent); + throw new ArgumentNullException(nameof(parent)); + } - _shortStringHelper = shortStringHelper; - _alias = alias; - _allowedContentTypes = new List(); - _propertyGroups = new PropertyGroupCollection(); + SetParent(parent); - // actually OK as IsPublishing is constant - // ReSharper disable once VirtualMemberCallInConstructor - _noGroupPropertyTypes = new PropertyTypeCollection(SupportsPublishing); - _noGroupPropertyTypes.CollectionChanged += PropertyTypesChanged; + _shortStringHelper = shortStringHelper; + _alias = alias; + _allowedContentTypes = new List(); + _propertyGroups = new PropertyGroupCollection(); - _variations = ContentVariation.Nothing; - } + // actually OK as IsPublishing is constant + // ReSharper disable once VirtualMemberCallInConstructor + PropertyTypeCollection = new PropertyTypeCollection(SupportsPublishing); + PropertyTypeCollection.CollectionChanged += PropertyTypesChanged; - public abstract ISimpleContentType ToSimple(); - - /// - /// Gets a value indicating whether the content type supports publishing. - /// - /// - /// A publishing content type supports draft and published values for properties. - /// It is possible to retrieve either the draft (default) or published value of a property. - /// Setting the value always sets the draft value, which then needs to be published. - /// A non-publishing content type only supports one value for properties. Getting - /// the draft or published value of a property returns the same thing, and publishing - /// a value property has no effect. - /// - public abstract bool SupportsPublishing { get; } - - //Custom comparer for enumerable - private static readonly DelegateEqualityComparer> ContentTypeSortComparer = - new DelegateEqualityComparer>( - (sorts, enumerable) => sorts.UnsortedSequenceEqual(enumerable), - sorts => sorts.GetHashCode()); - - protected void PropertyGroupsChanged(object? sender, NotifyCollectionChangedEventArgs e) - { - OnPropertyChanged(nameof(PropertyGroups)); - } + _variations = ContentVariation.Nothing; + } - protected void PropertyTypesChanged(object? sender, NotifyCollectionChangedEventArgs e) - { - //enable this to detect duplicate property aliases. We do want this, however making this change in a - //patch release might be a little dangerous - - ////detect if there are any duplicate aliases - this cannot be allowed - //if (e.Action == NotifyCollectionChangedAction.Add - // || e.Action == NotifyCollectionChangedAction.Replace) - //{ - // var allAliases = _noGroupPropertyTypes.Concat(PropertyGroups.SelectMany(x => x.PropertyTypes)).Select(x => x.Alias); - // if (allAliases.HasDuplicates(false)) - // { - // var newAliases = string.Join(", ", e.NewItems.Cast().Select(x => x.Alias)); - // throw new InvalidOperationException($"Other property types already exist with the aliases: {newAliases}"); - // } - //} - - OnPropertyChanged(nameof(PropertyTypes)); - } + /// + /// Gets a value indicating whether the content type supports publishing. + /// + /// + /// + /// A publishing content type supports draft and published values for properties. + /// It is possible to retrieve either the draft (default) or published value of a property. + /// Setting the value always sets the draft value, which then needs to be published. + /// + /// + /// A non-publishing content type only supports one value for properties. Getting + /// the draft or published value of a property returns the same thing, and publishing + /// a value property has no effect. + /// + /// + public abstract bool SupportsPublishing { get; } - /// - /// The Alias of the ContentType - /// - [DataMember] - public virtual string Alias + /// + /// A boolean flag indicating if a property type has been removed from this instance. + /// + /// + /// This is currently (specifically) used in order to know that we need to refresh the content cache which + /// needs to occur when a property has been removed from a content type + /// + [IgnoreDataMember] + internal bool HasPropertyTypeBeenRemoved + { + get => _hasPropertyTypeBeenRemoved; + private set { - get => _alias; - set => SetPropertyValueAndDetectChanges( - value.ToCleanString(_shortStringHelper, CleanStringType.Alias | CleanStringType.UmbracoCase), - ref _alias!, - nameof(Alias)); + _hasPropertyTypeBeenRemoved = value; + OnPropertyChanged(nameof(HasPropertyTypeBeenRemoved)); } + } - /// - /// Description for the ContentType - /// - [DataMember] - public string? Description - { - get => _description; - set => SetPropertyValueAndDetectChanges(value, ref _description, nameof(Description)); - } + /// + /// PropertyTypes that are not part of a PropertyGroup + /// + [IgnoreDataMember] + // TODO: should we mark this as EditorBrowsable hidden since it really isn't ever used? + internal PropertyTypeCollection PropertyTypeCollection { get; private set; } - /// - /// Name of the icon (sprite class) used to identify the ContentType - /// - [DataMember] - public string? Icon - { - get => _icon; - set => SetPropertyValueAndDetectChanges(value, ref _icon, nameof(Icon)); - } + public abstract ISimpleContentType ToSimple(); - /// - /// Name of the thumbnail used to identify the ContentType - /// - [DataMember] - public string? Thumbnail - { - get => _thumbnail; - set => SetPropertyValueAndDetectChanges(value, ref _thumbnail, nameof(Thumbnail)); - } + /// + /// The Alias of the ContentType + /// + [DataMember] + public virtual string Alias + { + get => _alias; + set => SetPropertyValueAndDetectChanges( + value.ToCleanString(_shortStringHelper, CleanStringType.Alias | CleanStringType.UmbracoCase), + ref _alias!, + nameof(Alias)); + } - /// - /// Gets or Sets a boolean indicating whether this ContentType is allowed at the root - /// - [DataMember] - public bool AllowedAsRoot - { - get => _allowedAsRoot; - set => SetPropertyValueAndDetectChanges(value, ref _allowedAsRoot, nameof(AllowedAsRoot)); - } + /// + /// Description for the ContentType + /// + [DataMember] + public string? Description + { + get => _description; + set => SetPropertyValueAndDetectChanges(value, ref _description, nameof(Description)); + } - /// - /// Gets or Sets a boolean indicating whether this ContentType is a Container - /// - /// - /// ContentType Containers doesn't show children in the tree, but rather in grid-type view. - /// - [DataMember] - public bool IsContainer - { - get => _isContainer; - set => SetPropertyValueAndDetectChanges(value, ref _isContainer, nameof(IsContainer)); - } + /// + /// Name of the icon (sprite class) used to identify the ContentType + /// + [DataMember] + public string? Icon + { + get => _icon; + set => SetPropertyValueAndDetectChanges(value, ref _icon, nameof(Icon)); + } - /// - [DataMember] - public bool IsElement - { - get => _isElement; - set => SetPropertyValueAndDetectChanges(value, ref _isElement, nameof(IsElement)); - } + /// + /// Name of the thumbnail used to identify the ContentType + /// + [DataMember] + public string? Thumbnail + { + get => _thumbnail; + set => SetPropertyValueAndDetectChanges(value, ref _thumbnail, nameof(Thumbnail)); + } - /// - /// Gets or sets a list of integer Ids for allowed ContentTypes - /// - [DataMember] - public IEnumerable? AllowedContentTypes - { - get => _allowedContentTypes; - set => SetPropertyValueAndDetectChanges(value, ref _allowedContentTypes, nameof(AllowedContentTypes), - ContentTypeSortComparer); - } + /// + /// Gets or Sets a boolean indicating whether this ContentType is allowed at the root + /// + [DataMember] + public bool AllowedAsRoot + { + get => _allowedAsRoot; + set => SetPropertyValueAndDetectChanges(value, ref _allowedAsRoot, nameof(AllowedAsRoot)); + } - /// - /// Gets or sets the content variation of the content type. - /// - public virtual ContentVariation Variations - { - get => _variations; - set => SetPropertyValueAndDetectChanges(value, ref _variations, nameof(Variations)); - } + /// + /// Gets or Sets a boolean indicating whether this ContentType is a Container + /// + /// + /// ContentType Containers doesn't show children in the tree, but rather in grid-type view. + /// + [DataMember] + public bool IsContainer + { + get => _isContainer; + set => SetPropertyValueAndDetectChanges(value, ref _isContainer, nameof(IsContainer)); + } - /// - public bool SupportsVariation(string culture, string segment, bool wildcards = false) - { - // exact validation: cannot accept a 'null' culture if the property type varies - // by culture, and likewise for segment - // wildcard validation: can accept a '*' culture or segment - return Variations.ValidateVariation(culture, segment, true, wildcards, false); - } + /// + [DataMember] + public bool IsElement + { + get => _isElement; + set => SetPropertyValueAndDetectChanges(value, ref _isElement, nameof(IsElement)); + } - /// - public bool SupportsPropertyVariation(string culture, string segment, bool wildcards = false) - { - // non-exact validation: can accept a 'null' culture if the property type varies - // by culture, and likewise for segment - // wildcard validation: can accept a '*' culture or segment - return Variations.ValidateVariation(culture, segment, false, true, false); - } + /// + /// Gets or sets a list of integer Ids for allowed ContentTypes + /// + [DataMember] + public IEnumerable? AllowedContentTypes + { + get => _allowedContentTypes; + set => SetPropertyValueAndDetectChanges(value, ref _allowedContentTypes, nameof(AllowedContentTypes), + ContentTypeSortComparer); + } + + /// + /// Gets or sets the content variation of the content type. + /// + public virtual ContentVariation Variations + { + get => _variations; + set => SetPropertyValueAndDetectChanges(value, ref _variations, nameof(Variations)); + } - /// - /// - /// A PropertyGroup corresponds to a Tab in the UI - /// Marked DoNotClone because we will manually deal with cloning and the event handlers - /// - [DataMember] - [DoNotClone] - public PropertyGroupCollection PropertyGroups + /// + public bool SupportsVariation(string culture, string segment, bool wildcards = false) => + // exact validation: cannot accept a 'null' culture if the property type varies + // by culture, and likewise for segment + // wildcard validation: can accept a '*' culture or segment + Variations.ValidateVariation(culture, segment, true, wildcards, false); + + /// + public bool SupportsPropertyVariation(string culture, string segment, bool wildcards = false) => + // non-exact validation: can accept a 'null' culture if the property type varies + // by culture, and likewise for segment + // wildcard validation: can accept a '*' culture or segment + Variations.ValidateVariation(culture, segment, false, true, false); + + /// + /// + /// A PropertyGroup corresponds to a Tab in the UI + /// Marked DoNotClone because we will manually deal with cloning and the event handlers + /// + [DataMember] + [DoNotClone] + public PropertyGroupCollection PropertyGroups + { + get => _propertyGroups; + set { - get => _propertyGroups; - set - { - _propertyGroups = value; - _propertyGroups.CollectionChanged += PropertyGroupsChanged; - PropertyGroupsChanged(_propertyGroups, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } + _propertyGroups = value; + _propertyGroups.CollectionChanged += PropertyGroupsChanged; + PropertyGroupsChanged(_propertyGroups, + new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } + } + + /// + [IgnoreDataMember] + [DoNotClone] + public IEnumerable PropertyTypes => + PropertyTypeCollection.Union(PropertyGroups.SelectMany(x => x.PropertyTypes!)); - /// - [IgnoreDataMember] - [DoNotClone] - public IEnumerable PropertyTypes + /// + [DoNotClone] + public IEnumerable NoGroupPropertyTypes + { + get => PropertyTypeCollection; + set { - get + if (PropertyTypeCollection != null) { - return _noGroupPropertyTypes.Union(PropertyGroups.SelectMany(x => x.PropertyTypes!)); + PropertyTypeCollection.ClearCollectionChangedEvents(); } + + PropertyTypeCollection = new PropertyTypeCollection(SupportsPublishing, value); + PropertyTypeCollection.CollectionChanged += PropertyTypesChanged; + PropertyTypesChanged(PropertyTypeCollection, + new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } + } - /// - [DoNotClone] - public IEnumerable NoGroupPropertyTypes - { - get => _noGroupPropertyTypes; - set - { - if (_noGroupPropertyTypes != null) - { - _noGroupPropertyTypes.ClearCollectionChangedEvents(); - } + /// + /// Checks whether a PropertyType with a given alias already exists + /// + /// Alias of the PropertyType + /// Returns True if a PropertyType with the passed in alias exists, otherwise False + public abstract bool PropertyTypeExists(string? alias); - _noGroupPropertyTypes = new PropertyTypeCollection(SupportsPublishing, value); - _noGroupPropertyTypes.CollectionChanged += PropertyTypesChanged; - PropertyTypesChanged(_noGroupPropertyTypes, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - } + /// + public abstract bool AddPropertyGroup(string alias, string name); + + /// + public abstract bool AddPropertyType(IPropertyType propertyType, string propertyGroupAlias, + string? propertyGroupName = null); - /// - /// A boolean flag indicating if a property type has been removed from this instance. - /// - /// - /// This is currently (specifically) used in order to know that we need to refresh the content cache which - /// needs to occur when a property has been removed from a content type - /// - [IgnoreDataMember] - internal bool HasPropertyTypeBeenRemoved + /// + /// Adds a PropertyType, which does not belong to a PropertyGroup. + /// + /// to add + /// Returns True if PropertyType was added, otherwise False + public bool AddPropertyType(IPropertyType propertyType) + { + if (PropertyTypeExists(propertyType.Alias) == false) { - get => _hasPropertyTypeBeenRemoved; - private set - { - _hasPropertyTypeBeenRemoved = value; - OnPropertyChanged(nameof(HasPropertyTypeBeenRemoved)); - } + PropertyTypeCollection.Add(propertyType); + return true; } - /// - /// Checks whether a PropertyType with a given alias already exists - /// - /// Alias of the PropertyType - /// Returns True if a PropertyType with the passed in alias exists, otherwise False - public abstract bool PropertyTypeExists(string? alias); - - /// - public abstract bool AddPropertyGroup(string alias, string name); - - /// - public abstract bool AddPropertyType(IPropertyType propertyType, string propertyGroupAlias, string? propertyGroupName = null); - - /// - /// Adds a PropertyType, which does not belong to a PropertyGroup. - /// - /// to add - /// Returns True if PropertyType was added, otherwise False - public bool AddPropertyType(IPropertyType propertyType) - { - if (PropertyTypeExists(propertyType.Alias) == false) - { - _noGroupPropertyTypes.Add(propertyType); - return true; - } + return false; + } + /// + /// Moves a PropertyType to a specified PropertyGroup + /// + /// Alias of the PropertyType to move + /// Alias of the PropertyGroup to move the PropertyType to + /// + /// + /// If is null then the property is moved back to + /// "generic properties" ie does not have a tab anymore. + /// + public bool MovePropertyType(string propertyTypeAlias, string propertyGroupAlias) + { + // get property, ensure it exists + IPropertyType propertyType = PropertyTypes.FirstOrDefault(x => x.Alias == propertyTypeAlias); + if (propertyType == null) + { return false; } - /// - /// Moves a PropertyType to a specified PropertyGroup - /// - /// Alias of the PropertyType to move - /// Alias of the PropertyGroup to move the PropertyType to - /// - /// If is null then the property is moved back to - /// "generic properties" ie does not have a tab anymore. - public bool MovePropertyType(string propertyTypeAlias, string propertyGroupAlias) + // get new group, if required, and ensure it exists + PropertyGroup? newPropertyGroup = null; + if (propertyGroupAlias != null) { - // get property, ensure it exists - var propertyType = PropertyTypes.FirstOrDefault(x => x.Alias == propertyTypeAlias); - if (propertyType == null) return false; - - // get new group, if required, and ensure it exists - PropertyGroup? newPropertyGroup = null; - if (propertyGroupAlias != null) + var index = PropertyGroups.IndexOfKey(propertyGroupAlias); + if (index == -1) { - var index = PropertyGroups.IndexOfKey(propertyGroupAlias); - if (index == -1) return false; - - newPropertyGroup = PropertyGroups[index]; + return false; } - // get old group - var oldPropertyGroup = PropertyGroups.FirstOrDefault(x => - x.PropertyTypes?.Any(y => y.Alias == propertyTypeAlias) ?? false); + newPropertyGroup = PropertyGroups[index]; + } - // set new group - propertyType.PropertyGroupId = newPropertyGroup == null ? null : new Lazy(() => newPropertyGroup.Id, false); + // get old group + PropertyGroup oldPropertyGroup = PropertyGroups.FirstOrDefault(x => + x.PropertyTypes?.Any(y => y.Alias == propertyTypeAlias) ?? false); - // remove from old group, if any - add to new group, if any - oldPropertyGroup?.PropertyTypes?.RemoveItem(propertyTypeAlias); - newPropertyGroup?.PropertyTypes?.Add(propertyType); + // set new group + propertyType.PropertyGroupId = + newPropertyGroup == null ? null : new Lazy(() => newPropertyGroup.Id, false); - return true; - } + // remove from old group, if any - add to new group, if any + oldPropertyGroup?.PropertyTypes?.RemoveItem(propertyTypeAlias); + newPropertyGroup?.PropertyTypes?.Add(propertyType); - /// - /// Removes a PropertyType from the current ContentType - /// - /// Alias of the to remove - public void RemovePropertyType(string alias) - { - //check through each property group to see if we can remove the property type by alias from it - foreach (var propertyGroup in PropertyGroups) - { - if (propertyGroup.PropertyTypes?.RemoveItem(alias) ?? false) - { - if (!HasPropertyTypeBeenRemoved) - { - HasPropertyTypeBeenRemoved = true; - OnPropertyChanged(nameof(PropertyTypes)); - } - break; - } - } + return true; + } - //check through each local property type collection (not assigned to a tab) - if (_noGroupPropertyTypes.RemoveItem(alias)) + /// + /// Removes a PropertyType from the current ContentType + /// + /// Alias of the to remove + public void RemovePropertyType(string alias) + { + //check through each property group to see if we can remove the property type by alias from it + foreach (PropertyGroup propertyGroup in PropertyGroups) + { + if (propertyGroup.PropertyTypes?.RemoveItem(alias) ?? false) { if (!HasPropertyTypeBeenRemoved) { HasPropertyTypeBeenRemoved = true; OnPropertyChanged(nameof(PropertyTypes)); } + + break; + } + } + + //check through each local property type collection (not assigned to a tab) + if (PropertyTypeCollection.RemoveItem(alias)) + { + if (!HasPropertyTypeBeenRemoved) + { + HasPropertyTypeBeenRemoved = true; + OnPropertyChanged(nameof(PropertyTypes)); } } + } - /// - /// Removes a PropertyGroup from the current ContentType - /// - /// Alias of the to remove - public void RemovePropertyGroup(string alias) + /// + /// Removes a PropertyGroup from the current ContentType + /// + /// Alias of the to remove + public void RemovePropertyGroup(string alias) + { + // if no group exists with that alias, do nothing + var index = PropertyGroups.IndexOfKey(alias); + if (index == -1) { - // if no group exists with that alias, do nothing - var index = PropertyGroups.IndexOfKey(alias); - if (index == -1) return; + return; + } - var group = PropertyGroups[index]; + PropertyGroup group = PropertyGroups[index]; - // first remove the group - PropertyGroups.Remove(group); + // first remove the group + PropertyGroups.Remove(group); - if (group.PropertyTypes is not null) + if (group.PropertyTypes is not null) + { + // Then re-assign the group's properties to no group + foreach (IPropertyType property in group.PropertyTypes) { - // Then re-assign the group's properties to no group - foreach (var property in group.PropertyTypes) - { - property.PropertyGroupId = null; - _noGroupPropertyTypes.Add(property); - } + property.PropertyGroupId = null; + PropertyTypeCollection.Add(property); } - - OnPropertyChanged(nameof(PropertyGroups)); } - /// - /// PropertyTypes that are not part of a PropertyGroup - /// - [IgnoreDataMember] - // TODO: should we mark this as EditorBrowsable hidden since it really isn't ever used? - internal PropertyTypeCollection PropertyTypeCollection => _noGroupPropertyTypes; - - /// - /// Indicates whether the current entity is dirty. - /// - /// True if entity is dirty, otherwise False - public override bool IsDirty() - { - bool dirtyEntity = base.IsDirty(); + OnPropertyChanged(nameof(PropertyGroups)); + } - bool dirtyGroups = PropertyGroups.Any(x => x.IsDirty()); - bool dirtyTypes = PropertyTypes.Any(x => x.IsDirty()); + /// + /// Indicates whether the current entity is dirty. + /// + /// True if entity is dirty, otherwise False + public override bool IsDirty() + { + var dirtyEntity = base.IsDirty(); - return dirtyEntity || dirtyGroups || dirtyTypes; - } + var dirtyGroups = PropertyGroups.Any(x => x.IsDirty()); + var dirtyTypes = PropertyTypes.Any(x => x.IsDirty()); - /// - /// Resets dirty properties by clearing the dictionary used to track changes. - /// - /// - /// Please note that resetting the dirty properties could potentially - /// obstruct the saving of a new or updated entity. - /// - public override void ResetDirtyProperties() - { - base.ResetDirtyProperties(); + return dirtyEntity || dirtyGroups || dirtyTypes; + } - //loop through each property group to reset the property types - var propertiesReset = new List(); + /// + /// Resets dirty properties by clearing the dictionary used to track changes. + /// + /// + /// Please note that resetting the dirty properties could potentially + /// obstruct the saving of a new or updated entity. + /// + public override void ResetDirtyProperties() + { + base.ResetDirtyProperties(); - foreach (var propertyGroup in PropertyGroups) + //loop through each property group to reset the property types + var propertiesReset = new List(); + + foreach (PropertyGroup propertyGroup in PropertyGroups) + { + propertyGroup.ResetDirtyProperties(); + if (propertyGroup.PropertyTypes is not null) { - propertyGroup.ResetDirtyProperties(); - if (propertyGroup.PropertyTypes is not null) + foreach (IPropertyType propertyType in propertyGroup.PropertyTypes) { - foreach (var propertyType in propertyGroup.PropertyTypes) - { - propertyType.ResetDirtyProperties(); - propertiesReset.Add(propertyType.Id); - } + propertyType.ResetDirtyProperties(); + propertiesReset.Add(propertyType.Id); } } - - //then loop through our property type collection since some might not exist on a property group - //but don't re-reset ones we've already done. - foreach (var propertyType in PropertyTypes.Where(x => propertiesReset.Contains(x.Id) == false)) - { - propertyType.ResetDirtyProperties(); - } } - protected override void PerformDeepClone(object clone) + //then loop through our property type collection since some might not exist on a property group + //but don't re-reset ones we've already done. + foreach (IPropertyType propertyType in PropertyTypes.Where(x => propertiesReset.Contains(x.Id) == false)) { - base.PerformDeepClone(clone); + propertyType.ResetDirtyProperties(); + } + } - var clonedEntity = (ContentTypeBase) clone; + protected void PropertyGroupsChanged(object? sender, NotifyCollectionChangedEventArgs e) => + OnPropertyChanged(nameof(PropertyGroups)); + + protected void PropertyTypesChanged(object? sender, NotifyCollectionChangedEventArgs e) => + //enable this to detect duplicate property aliases. We do want this, however making this change in a + //patch release might be a little dangerous + ////detect if there are any duplicate aliases - this cannot be allowed + //if (e.Action == NotifyCollectionChangedAction.Add + // || e.Action == NotifyCollectionChangedAction.Replace) + //{ + // var allAliases = _noGroupPropertyTypes.Concat(PropertyGroups.SelectMany(x => x.PropertyTypes)).Select(x => x.Alias); + // if (allAliases.HasDuplicates(false)) + // { + // var newAliases = string.Join(", ", e.NewItems.Cast().Select(x => x.Alias)); + // throw new InvalidOperationException($"Other property types already exist with the aliases: {newAliases}"); + // } + //} + OnPropertyChanged(nameof(PropertyTypes)); + + protected override void PerformDeepClone(object clone) + { + base.PerformDeepClone(clone); - if (clonedEntity._noGroupPropertyTypes != null) - { - //need to manually wire up the event handlers for the property type collections - we've ensured - // its ignored from the auto-clone process because its return values are unions, not raw and - // we end up with duplicates, see: http://issues.umbraco.org/issue/U4-4842 + var clonedEntity = (ContentTypeBase)clone; - clonedEntity._noGroupPropertyTypes.ClearCollectionChangedEvents(); //clear this event handler if any - clonedEntity._noGroupPropertyTypes = (PropertyTypeCollection) _noGroupPropertyTypes.DeepClone(); //manually deep clone - clonedEntity._noGroupPropertyTypes.CollectionChanged += clonedEntity.PropertyTypesChanged; //re-assign correct event handler - } + if (clonedEntity.PropertyTypeCollection != null) + { + //need to manually wire up the event handlers for the property type collections - we've ensured + // its ignored from the auto-clone process because its return values are unions, not raw and + // we end up with duplicates, see: http://issues.umbraco.org/issue/U4-4842 + + clonedEntity.PropertyTypeCollection.ClearCollectionChangedEvents(); //clear this event handler if any + clonedEntity.PropertyTypeCollection = + (PropertyTypeCollection)PropertyTypeCollection.DeepClone(); //manually deep clone + clonedEntity.PropertyTypeCollection.CollectionChanged += + clonedEntity.PropertyTypesChanged; //re-assign correct event handler + } - if (clonedEntity._propertyGroups != null) - { - clonedEntity._propertyGroups.ClearCollectionChangedEvents(); //clear this event handler if any - clonedEntity._propertyGroups = (PropertyGroupCollection) _propertyGroups.DeepClone(); //manually deep clone - clonedEntity._propertyGroups.CollectionChanged += clonedEntity.PropertyGroupsChanged; //re-assign correct event handler - } + if (clonedEntity._propertyGroups != null) + { + clonedEntity._propertyGroups.ClearCollectionChangedEvents(); //clear this event handler if any + clonedEntity._propertyGroups = (PropertyGroupCollection)_propertyGroups.DeepClone(); //manually deep clone + clonedEntity._propertyGroups.CollectionChanged += + clonedEntity.PropertyGroupsChanged; //re-assign correct event handler } + } - public ContentTypeBase DeepCloneWithResetIdentities(string alias) + public ContentTypeBase DeepCloneWithResetIdentities(string alias) + { + var clone = (ContentTypeBase)DeepClone(); + clone.Alias = alias; + clone.Key = Guid.Empty; + foreach (PropertyGroup propertyGroup in clone.PropertyGroups) { - var clone = (ContentTypeBase)DeepClone(); - clone.Alias = alias; - clone.Key = Guid.Empty; - foreach (var propertyGroup in clone.PropertyGroups) - { - propertyGroup.ResetIdentity(); - propertyGroup.ResetDirtyProperties(false); - } - foreach (var propertyType in clone.PropertyTypes) - { - propertyType.ResetIdentity(); - propertyType.ResetDirtyProperties(false); - } + propertyGroup.ResetIdentity(); + propertyGroup.ResetDirtyProperties(false); + } - clone.ResetIdentity(); - clone.ResetDirtyProperties(false); - return clone; + foreach (IPropertyType propertyType in clone.PropertyTypes) + { + propertyType.ResetIdentity(); + propertyType.ResetDirtyProperties(false); } + + clone.ResetIdentity(); + clone.ResetDirtyProperties(false); + return clone; } } diff --git a/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs b/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs index d771efa12b0f..765e79b4fd58 100644 --- a/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs +++ b/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs @@ -1,64 +1,77 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Provides extensions methods for . +/// +public static class ContentTypeBaseExtensions { - /// - /// Provides extensions methods for . - /// - public static class ContentTypeBaseExtensions + public static PublishedItemType GetItemType(this IContentTypeBase contentType) { - public static PublishedItemType GetItemType(this IContentTypeBase contentType) + Type type = contentType.GetType(); + PublishedItemType itemType = PublishedItemType.Unknown; + if (contentType.IsElement) { - var type = contentType.GetType(); - var itemType = PublishedItemType.Unknown; - if (contentType.IsElement) itemType = PublishedItemType.Element; - else if (typeof(IContentType).IsAssignableFrom(type)) itemType = PublishedItemType.Content; - else if (typeof(IMediaType).IsAssignableFrom(type)) itemType = PublishedItemType.Media; - else if (typeof(IMemberType).IsAssignableFrom(type)) itemType = PublishedItemType.Member; - return itemType; + itemType = PublishedItemType.Element; } - - /// - /// Used to check if any property type was changed between variant/invariant - /// - /// - /// - public static bool WasPropertyTypeVariationChanged(this IContentTypeBase contentType) + else if (typeof(IContentType).IsAssignableFrom(type)) { - return contentType.WasPropertyTypeVariationChanged(out var _); + itemType = PublishedItemType.Content; } - - /// - /// Used to check if any property type was changed between variant/invariant - /// - /// - /// - internal static bool WasPropertyTypeVariationChanged(this IContentTypeBase contentType, out IReadOnlyCollection aliases) + else if (typeof(IMediaType).IsAssignableFrom(type)) { - var a = new List(); + itemType = PublishedItemType.Media; + } + else if (typeof(IMemberType).IsAssignableFrom(type)) + { + itemType = PublishedItemType.Member; + } - // property variation change? - var hasAnyPropertyVariationChanged = contentType.PropertyTypes.Any(propertyType => - { - // skip new properties - // TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly - var isNewProperty = propertyType.WasPropertyDirty("Id"); - if (isNewProperty) return false; + return itemType; + } - // variation change? - var dirty = propertyType.WasPropertyDirty("Variations"); - if (dirty) - a.Add(propertyType.Alias); + /// + /// Used to check if any property type was changed between variant/invariant + /// + /// + /// + public static bool WasPropertyTypeVariationChanged(this IContentTypeBase contentType) => + contentType.WasPropertyTypeVariationChanged(out IReadOnlyCollection _); + + /// + /// Used to check if any property type was changed between variant/invariant + /// + /// + /// + internal static bool WasPropertyTypeVariationChanged(this IContentTypeBase contentType, + out IReadOnlyCollection aliases) + { + var a = new List(); - return dirty; + // property variation change? + var hasAnyPropertyVariationChanged = contentType.PropertyTypes.Any(propertyType => + { + // skip new properties + // TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly + var isNewProperty = propertyType.WasPropertyDirty("Id"); + if (isNewProperty) + { + return false; + } - }); + // variation change? + var dirty = propertyType.WasPropertyDirty("Variations"); + if (dirty) + { + a.Add(propertyType.Alias); + } - aliases = a; - return hasAnyPropertyVariationChanged; - } + return dirty; + }); + + aliases = a; + return hasAnyPropertyVariationChanged; } } diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs index 18dc1189f2e8..a86284ff39d2 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -1,319 +1,342 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.Serialization; using Umbraco.Cms.Core.Exceptions; using Umbraco.Cms.Core.Strings; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents an abstract class for composition specific ContentType properties and methods +/// +[Serializable] +[DataContract(IsReference = true)] +public abstract class ContentTypeCompositionBase : ContentTypeBase, IContentTypeComposition { - /// - /// Represents an abstract class for composition specific ContentType properties and methods - /// - [Serializable] - [DataContract(IsReference = true)] - public abstract class ContentTypeCompositionBase : ContentTypeBase, IContentTypeComposition + private List _contentTypeComposition = new(); + private List _removedContentTypeKeyTracker = new(); + + protected ContentTypeCompositionBase(IShortStringHelper shortStringHelper, int parentId) + : base(shortStringHelper, parentId) { - private List _contentTypeComposition = new List(); - private List _removedContentTypeKeyTracker = new List(); + } + + protected ContentTypeCompositionBase(IShortStringHelper shortStringHelper, IContentTypeComposition parent) + : this(shortStringHelper, parent, string.Empty) + { + } - protected ContentTypeCompositionBase(IShortStringHelper shortStringHelper, int parentId) - : base(shortStringHelper, parentId) - { } + protected ContentTypeCompositionBase(IShortStringHelper shortStringHelper, IContentTypeComposition parent, + string alias) + : base(shortStringHelper, parent, alias) => + AddContentType(parent); - protected ContentTypeCompositionBase(IShortStringHelper shortStringHelper,IContentTypeComposition parent) - : this(shortStringHelper, parent, string.Empty) - { } + public IEnumerable RemovedContentTypes => _removedContentTypeKeyTracker; - protected ContentTypeCompositionBase(IShortStringHelper shortStringHelper, IContentTypeComposition parent, string alias) - : base(shortStringHelper, parent, alias) + /// + /// Gets or sets the content types that compose this content type. + /// + [DataMember] + public IEnumerable ContentTypeComposition + { + get => _contentTypeComposition; + set { - AddContentType(parent); + _contentTypeComposition = value.ToList(); + OnPropertyChanged(nameof(ContentTypeComposition)); } + } - public IEnumerable RemovedContentTypes => _removedContentTypeKeyTracker; - - /// - /// Gets or sets the content types that compose this content type. - /// - [DataMember] - public IEnumerable ContentTypeComposition + /// + [IgnoreDataMember] + public IEnumerable CompositionPropertyGroups + { + get { - get => _contentTypeComposition; - set + // we need to "acquire" composition groups and properties here, ie get our own clones, + // so that we can change their variation according to this content type variations. + // + // it would be nice to cache the resulting enumerable, but alas we cannot, otherwise + // any change to compositions are ignored and that breaks many things - and tracking + // changes to refresh the cache would be expensive. + + void AcquireProperty(IPropertyType propertyType) { - _contentTypeComposition = value.ToList(); - OnPropertyChanged(nameof(ContentTypeComposition)); + propertyType.Variations &= Variations; + propertyType.ResetDirtyProperties(false); } - } - /// - [IgnoreDataMember] - public IEnumerable CompositionPropertyGroups - { - get - { - // we need to "acquire" composition groups and properties here, ie get our own clones, - // so that we can change their variation according to this content type variations. - // - // it would be nice to cache the resulting enumerable, but alas we cannot, otherwise - // any change to compositions are ignored and that breaks many things - and tracking - // changes to refresh the cache would be expensive. - - void AcquireProperty(IPropertyType propertyType) + return PropertyGroups.Union(ContentTypeComposition.SelectMany(x => x.CompositionPropertyGroups) + .Select(group => { - propertyType.Variations &= Variations; - propertyType.ResetDirtyProperties(false); - } - - return PropertyGroups.Union(ContentTypeComposition.SelectMany(x => x.CompositionPropertyGroups) - .Select(group => + group = (PropertyGroup)group.DeepClone(); + if (group.PropertyTypes is not null) { - group = (PropertyGroup) group.DeepClone(); - if (group.PropertyTypes is not null) + foreach (IPropertyType property in group.PropertyTypes) { - foreach (var property in group.PropertyTypes) - AcquireProperty(property); + AcquireProperty(property); } - return group; - })); - } + } + + return group; + })); } + } - /// - [IgnoreDataMember] - public IEnumerable CompositionPropertyTypes + /// + [IgnoreDataMember] + public IEnumerable CompositionPropertyTypes + { + get { - get - { - // we need to "acquire" composition properties here, ie get our own clones, - // so that we can change their variation according to this content type variations. - // - // see note in CompositionPropertyGroups for comments on caching the resulting enumerable + // we need to "acquire" composition properties here, ie get our own clones, + // so that we can change their variation according to this content type variations. + // + // see note in CompositionPropertyGroups for comments on caching the resulting enumerable - IPropertyType AcquireProperty(IPropertyType propertyType) - { - propertyType = (IPropertyType) propertyType.DeepClone(); - propertyType.Variations &= Variations; - propertyType.ResetDirtyProperties(false); - return propertyType; - } - - return ContentTypeComposition - .SelectMany(x => x.CompositionPropertyTypes) - .Select(AcquireProperty) - .Union(PropertyTypes); + IPropertyType AcquireProperty(IPropertyType propertyType) + { + propertyType = (IPropertyType)propertyType.DeepClone(); + propertyType.Variations &= Variations; + propertyType.ResetDirtyProperties(false); + return propertyType; } + + return ContentTypeComposition + .SelectMany(x => x.CompositionPropertyTypes) + .Select(AcquireProperty) + .Union(PropertyTypes); } + } - /// - public IEnumerable GetOriginalComposedPropertyTypes() => GetRawComposedPropertyTypes(); + /// + public IEnumerable GetOriginalComposedPropertyTypes() => GetRawComposedPropertyTypes(); - private IEnumerable GetRawComposedPropertyTypes(bool start = true) + /// + /// Adds a content type to the composition. + /// + /// The content type to add. + /// True if the content type was added, otherwise false. + public bool AddContentType(IContentTypeComposition? contentType) + { + if (contentType is null) { - var propertyTypes = ContentTypeComposition - .Cast() - .SelectMany(x => start ? x.GetRawComposedPropertyTypes(false) : x.CompositionPropertyTypes); + return false; + } - if (!start) - propertyTypes = propertyTypes.Union(PropertyTypes); + if (contentType.ContentTypeComposition.Any(x => x.CompositionAliases().Any(ContentTypeCompositionExists))) + { + return false; + } - return propertyTypes; + if (string.IsNullOrEmpty(Alias) == false && Alias.Equals(contentType.Alias)) + { + return false; } - /// - /// Adds a content type to the composition. - /// - /// The content type to add. - /// True if the content type was added, otherwise false. - public bool AddContentType(IContentTypeComposition? contentType) + if (ContentTypeCompositionExists(contentType.Alias) == false) { - if (contentType is null) + // Before we actually go ahead and add the ContentType as a Composition we ensure that we don't + // end up with duplicate PropertyType aliases - in which case we throw an exception. + var conflictingPropertyTypeAliases = CompositionPropertyTypes.SelectMany( + x => contentType.CompositionPropertyTypes + .Where(y => y.Alias.Equals(x.Alias, StringComparison.InvariantCultureIgnoreCase)) + .Select(p => p.Alias)).ToList(); + + if (conflictingPropertyTypeAliases.Any()) { - return false; + throw new InvalidCompositionException(Alias, contentType.Alias, + conflictingPropertyTypeAliases.ToArray()); } - if (contentType.ContentTypeComposition.Any(x => x.CompositionAliases().Any(ContentTypeCompositionExists))) - return false; - if (string.IsNullOrEmpty(Alias) == false && Alias.Equals(contentType.Alias)) - return false; + _contentTypeComposition.Add(contentType); - if (ContentTypeCompositionExists(contentType.Alias) == false) - { - // Before we actually go ahead and add the ContentType as a Composition we ensure that we don't - // end up with duplicate PropertyType aliases - in which case we throw an exception. - var conflictingPropertyTypeAliases = CompositionPropertyTypes.SelectMany( - x => contentType.CompositionPropertyTypes - .Where(y => y.Alias.Equals(x.Alias, StringComparison.InvariantCultureIgnoreCase)) - .Select(p => p.Alias)).ToList(); + OnPropertyChanged(nameof(ContentTypeComposition)); - if (conflictingPropertyTypeAliases.Any()) - throw new InvalidCompositionException(Alias, contentType.Alias, conflictingPropertyTypeAliases.ToArray()); + return true; + } - _contentTypeComposition.Add(contentType); + return false; + } - OnPropertyChanged(nameof(ContentTypeComposition)); + /// + /// Removes a content type with a specified alias from the composition. + /// + /// The alias of the content type to remove. + /// True if the content type was removed, otherwise false. + public bool RemoveContentType(string alias) + { + if (ContentTypeCompositionExists(alias)) + { + IContentTypeComposition contentTypeComposition = + ContentTypeComposition.FirstOrDefault(x => x.Alias == alias); + if (contentTypeComposition == null) // You can't remove a composition from another composition + { + return false; + } + + _removedContentTypeKeyTracker.Add(contentTypeComposition.Id); - return true; + // If the ContentType we are removing has Compositions of its own these needs to be removed as well + var compositionIdsToRemove = contentTypeComposition.CompositionIds().ToList(); + if (compositionIdsToRemove.Any()) + { + _removedContentTypeKeyTracker.AddRange(compositionIdsToRemove); } - return false; + OnPropertyChanged(nameof(ContentTypeComposition)); + + return _contentTypeComposition.Remove(contentTypeComposition); } - /// - /// Removes a content type with a specified alias from the composition. - /// - /// The alias of the content type to remove. - /// True if the content type was removed, otherwise false. - public bool RemoveContentType(string alias) + return false; + } + + /// + /// Checks if a ContentType with the supplied alias exists in the list of composite ContentTypes + /// + /// Alias of a + /// True if ContentType with alias exists, otherwise returns False + public bool ContentTypeCompositionExists(string alias) + { + if (ContentTypeComposition.Any(x => x.Alias.Equals(alias))) { - if (ContentTypeCompositionExists(alias)) - { - var contentTypeComposition = ContentTypeComposition.FirstOrDefault(x => x.Alias == alias); - if (contentTypeComposition == null) // You can't remove a composition from another composition - return false; + return true; + } - _removedContentTypeKeyTracker.Add(contentTypeComposition.Id); + if (ContentTypeComposition.Any(x => x.ContentTypeCompositionExists(alias))) + { + return true; + } - // If the ContentType we are removing has Compositions of its own these needs to be removed as well - var compositionIdsToRemove = contentTypeComposition.CompositionIds().ToList(); - if (compositionIdsToRemove.Any()) - _removedContentTypeKeyTracker.AddRange(compositionIdsToRemove); + return false; + } - OnPropertyChanged(nameof(ContentTypeComposition)); + /// + /// Checks whether a PropertyType with a given alias already exists + /// + /// Alias of the PropertyType + /// Returns True if a PropertyType with the passed in alias exists, otherwise False + public override bool PropertyTypeExists(string? alias) => CompositionPropertyTypes.Any(x => x.Alias == alias); - return _contentTypeComposition.Remove(contentTypeComposition); - } + /// + public override bool AddPropertyGroup(string alias, string name) => AddAndReturnPropertyGroup(alias, name) != null; + /// + public override bool AddPropertyType(IPropertyType propertyType, string propertyGroupAlias, + string? propertyGroupName = null) + { + // ensure no duplicate alias - over all composition properties + if (PropertyTypeExists(propertyType.Alias)) + { return false; } - /// - /// Checks if a ContentType with the supplied alias exists in the list of composite ContentTypes - /// - /// Alias of a - /// True if ContentType with alias exists, otherwise returns False - public bool ContentTypeCompositionExists(string alias) + // get and ensure a group local to this content type + PropertyGroup? group; + var index = PropertyGroups.IndexOfKey(propertyGroupAlias); + if (index != -1) { - if (ContentTypeComposition.Any(x => x.Alias.Equals(alias))) - return true; - - if (ContentTypeComposition.Any(x => x.ContentTypeCompositionExists(alias))) - return true; - + group = PropertyGroups[index]; + } + else if (!string.IsNullOrEmpty(propertyGroupName)) + { + group = AddAndReturnPropertyGroup(propertyGroupAlias, propertyGroupName); + if (group == null) + { + return false; + } + } + else + { + // No group name specified, so we can't create a new one and add the property type return false; } - /// - /// Checks whether a PropertyType with a given alias already exists - /// - /// Alias of the PropertyType - /// Returns True if a PropertyType with the passed in alias exists, otherwise False - public override bool PropertyTypeExists(string? alias) => CompositionPropertyTypes.Any(x => x.Alias == alias); + // add property to group + propertyType.PropertyGroupId = new Lazy(() => group.Id); + group.PropertyTypes?.Add(propertyType); - /// - public override bool AddPropertyGroup(string alias, string name) => AddAndReturnPropertyGroup(alias, name) != null; - - private PropertyGroup? AddAndReturnPropertyGroup(string alias, string name) - { - // Ensure we don't have it already - if (PropertyGroups.Contains(alias)) - return null; + return true; + } - // Add new group - var group = new PropertyGroup(SupportsPublishing) - { - Alias = alias, - Name = name - }; - - // check if it is inherited - there might be more than 1 but we want the 1st, to - // reuse its sort order - if there are more than 1 and they have different sort - // orders... there isn't much we can do anyways - var inheritGroup = CompositionPropertyGroups.FirstOrDefault(x => x.Alias == alias); - if (inheritGroup == null) - { - // no, just local, set sort order - var lastGroup = PropertyGroups.LastOrDefault(); - if (lastGroup != null) - group.SortOrder = lastGroup.SortOrder + 1; - } - else - { - // yes, inherited, re-use sort order - group.SortOrder = inheritGroup.SortOrder; - } + /// + /// Gets a list of ContentType aliases from the current composition + /// + /// An enumerable list of string aliases + /// Does not contain the alias of the Current ContentType + public IEnumerable CompositionAliases() + => ContentTypeComposition + .Select(x => x.Alias) + .Union(ContentTypeComposition.SelectMany(x => x.CompositionAliases())); - // add - PropertyGroups.Add(group); + /// + /// Gets a list of ContentType Ids from the current composition + /// + /// An enumerable list of integer ids + /// Does not contain the Id of the Current ContentType + public IEnumerable CompositionIds() + => ContentTypeComposition + .Select(x => x.Id) + .Union(ContentTypeComposition.SelectMany(x => x.CompositionIds())); + + private IEnumerable GetRawComposedPropertyTypes(bool start = true) + { + IEnumerable propertyTypes = ContentTypeComposition + .Cast() + .SelectMany(x => start ? x.GetRawComposedPropertyTypes(false) : x.CompositionPropertyTypes); - return group; + if (!start) + { + propertyTypes = propertyTypes.Union(PropertyTypes); } - /// - public override bool AddPropertyType(IPropertyType propertyType, string propertyGroupAlias, string? propertyGroupName = null) + return propertyTypes; + } + + private PropertyGroup? AddAndReturnPropertyGroup(string alias, string name) + { + // Ensure we don't have it already + if (PropertyGroups.Contains(alias)) { - // ensure no duplicate alias - over all composition properties - if (PropertyTypeExists(propertyType.Alias)) - return false; + return null; + } - // get and ensure a group local to this content type - PropertyGroup? group; - var index = PropertyGroups.IndexOfKey(propertyGroupAlias); - if (index != -1) - { - group = PropertyGroups[index]; - } - else if (!string.IsNullOrEmpty(propertyGroupName)) - { - group = AddAndReturnPropertyGroup(propertyGroupAlias, propertyGroupName); - if (group == null) - { - return false; - } - } - else + // Add new group + var group = new PropertyGroup(SupportsPublishing) {Alias = alias, Name = name}; + + // check if it is inherited - there might be more than 1 but we want the 1st, to + // reuse its sort order - if there are more than 1 and they have different sort + // orders... there isn't much we can do anyways + PropertyGroup inheritGroup = CompositionPropertyGroups.FirstOrDefault(x => x.Alias == alias); + if (inheritGroup == null) + { + // no, just local, set sort order + PropertyGroup lastGroup = PropertyGroups.LastOrDefault(); + if (lastGroup != null) { - // No group name specified, so we can't create a new one and add the property type - return false; + group.SortOrder = lastGroup.SortOrder + 1; } + } + else + { + // yes, inherited, re-use sort order + group.SortOrder = inheritGroup.SortOrder; + } - // add property to group - propertyType.PropertyGroupId = new Lazy(() => group.Id); - group.PropertyTypes?.Add(propertyType); + // add + PropertyGroups.Add(group); - return true; - } + return group; + } - /// - /// Gets a list of ContentType aliases from the current composition - /// - /// An enumerable list of string aliases - /// Does not contain the alias of the Current ContentType - public IEnumerable CompositionAliases() - => ContentTypeComposition - .Select(x => x.Alias) - .Union(ContentTypeComposition.SelectMany(x => x.CompositionAliases())); - - /// - /// Gets a list of ContentType Ids from the current composition - /// - /// An enumerable list of integer ids - /// Does not contain the Id of the Current ContentType - public IEnumerable CompositionIds() - => ContentTypeComposition - .Select(x => x.Id) - .Union(ContentTypeComposition.SelectMany(x => x.CompositionIds())); - - protected override void PerformDeepClone(object clone) - { - base.PerformDeepClone(clone); + protected override void PerformDeepClone(object clone) + { + base.PerformDeepClone(clone); - var clonedEntity = (ContentTypeCompositionBase)clone; + var clonedEntity = (ContentTypeCompositionBase)clone; - // need to manually assign since this is an internal field and will not be automatically mapped - clonedEntity._removedContentTypeKeyTracker = new List(); - clonedEntity._contentTypeComposition = ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList(); - } + // need to manually assign since this is an internal field and will not be automatically mapped + clonedEntity._removedContentTypeKeyTracker = new List(); + clonedEntity._contentTypeComposition = + ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList(); } } diff --git a/src/Umbraco.Core/Models/ContentTypeImportModel.cs b/src/Umbraco.Core/Models/ContentTypeImportModel.cs index 49d09c68213c..de11555b7725 100644 --- a/src/Umbraco.Core/Models/ContentTypeImportModel.cs +++ b/src/Umbraco.Core/Models/ContentTypeImportModel.cs @@ -1,22 +1,16 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.ContentEditing; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +[DataContract(Name = "contentTypeImportModel")] +public class ContentTypeImportModel : INotificationModel { - [DataContract(Name = "contentTypeImportModel")] - public class ContentTypeImportModel : INotificationModel - { - [DataMember(Name = "alias")] - public string? Alias { get; set; } + [DataMember(Name = "alias")] public string? Alias { get; set; } - [DataMember(Name = "name")] - public string? Name { get; set; } + [DataMember(Name = "name")] public string? Name { get; set; } - [DataMember(Name = "notifications")] - public List Notifications { get; } = new List(); + [DataMember(Name = "tempFileName")] public string? TempFileName { get; set; } - [DataMember(Name = "tempFileName")] - public string? TempFileName { get; set; } - } + [DataMember(Name = "notifications")] public List Notifications { get; } = new(); } diff --git a/src/Umbraco.Core/Models/ContentTypeSort.cs b/src/Umbraco.Core/Models/ContentTypeSort.cs index e7a11bad47c1..ecbf62e195c0 100644 --- a/src/Umbraco.Core/Models/ContentTypeSort.cs +++ b/src/Umbraco.Core/Models/ContentTypeSort.cs @@ -1,78 +1,85 @@ -using System; -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a POCO for setting sort order on a ContentType reference +/// +public class ContentTypeSort : IValueObject, IDeepCloneable { + // this parameterless ctor should never be used BUT is required by AutoMapper in EntityMapperProfile + public ContentTypeSort() { } + /// - /// Represents a POCO for setting sort order on a ContentType reference + /// Initializes a new instance of the class. /// - public class ContentTypeSort : IValueObject, IDeepCloneable + public ContentTypeSort(int id, int sortOrder) { - // this parameterless ctor should never be used BUT is required by AutoMapper in EntityMapperProfile - public ContentTypeSort() { } + Id = new Lazy(() => id); + SortOrder = sortOrder; + } - /// - /// Initializes a new instance of the class. - /// - public ContentTypeSort(int id, int sortOrder) - { - Id = new Lazy(() => id); - SortOrder = sortOrder; - } + public ContentTypeSort(Lazy id, int sortOrder, string alias) + { + Id = id; + SortOrder = sortOrder; + Alias = alias; + } - public ContentTypeSort(Lazy id, int sortOrder, string @alias) - { - Id = id; - SortOrder = sortOrder; - Alias = alias; - } + /// + /// Gets or sets the Id of the ContentType + /// + public Lazy Id { get; set; } = new(() => 0); - /// - /// Gets or sets the Id of the ContentType - /// - public Lazy Id { get; set; } = new Lazy(() => 0); + /// + /// Gets or sets the Sort Order of the ContentType + /// + public int SortOrder { get; set; } + + /// + /// Gets or sets the Alias of the ContentType + /// + public string Alias { get; set; } = string.Empty; - /// - /// Gets or sets the Sort Order of the ContentType - /// - public int SortOrder { get; set; } - /// - /// Gets or sets the Alias of the ContentType - /// - public string Alias { get; set; } = string.Empty; + public object DeepClone() + { + var clone = (ContentTypeSort)MemberwiseClone(); + var id = Id.Value; + clone.Id = new Lazy(() => id); + return clone; + } + protected bool Equals(ContentTypeSort other) => + Id.Value.Equals(other.Id.Value) && string.Equals(Alias, other.Alias); - public object DeepClone() + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) { - var clone = (ContentTypeSort)MemberwiseClone(); - var id = Id.Value; - clone.Id = new Lazy(() => id); - return clone; + return false; } - protected bool Equals(ContentTypeSort other) + if (ReferenceEquals(this, obj)) { - return Id.Value.Equals(other.Id.Value) && string.Equals(Alias, other.Alias); + return true; } - public override bool Equals(object? obj) + if (obj.GetType() != GetType()) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((ContentTypeSort) obj); + return false; } - public override int GetHashCode() + return Equals((ContentTypeSort)obj); + } + + public override int GetHashCode() + { + unchecked { - unchecked - { - //The hash code will just be the alias if one is assigned, otherwise it will be the hash code of the Id. - //In some cases the alias can be null of the non lazy ctor is used, in that case, the lazy Id will already have a value created. - return Alias != null ? Alias.GetHashCode() : (Id.Value.GetHashCode() * 397); - } + //The hash code will just be the alias if one is assigned, otherwise it will be the hash code of the Id. + //In some cases the alias can be null of the non lazy ctor is used, in that case, the lazy Id will already have a value created. + return Alias != null ? Alias.GetHashCode() : Id.Value.GetHashCode() * 397; } - } } diff --git a/src/Umbraco.Core/Models/ContentVariation.cs b/src/Umbraco.Core/Models/ContentVariation.cs index 00c7f197a807..939f3d10d338 100644 --- a/src/Umbraco.Core/Models/ContentVariation.cs +++ b/src/Umbraco.Core/Models/ContentVariation.cs @@ -1,37 +1,36 @@ -using System; +namespace Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Models +/// +/// Indicates how values can vary. +/// +/// +/// Values can vary by nothing, or culture, or segment, or both. +/// +/// Varying by culture implies that each culture version of a document can +/// be available or not, and published or not, individually. Varying by segment +/// is a property-level thing. +/// +/// +[Flags] +public enum ContentVariation : byte { /// - /// Indicates how values can vary. + /// Values do not vary. /// - /// - /// Values can vary by nothing, or culture, or segment, or both. - /// Varying by culture implies that each culture version of a document can - /// be available or not, and published or not, individually. Varying by segment - /// is a property-level thing. - /// - [Flags] - public enum ContentVariation : byte - { - /// - /// Values do not vary. - /// - Nothing = 0, + Nothing = 0, - /// - /// Values vary by culture. - /// - Culture = 1, + /// + /// Values vary by culture. + /// + Culture = 1, - /// - /// Values vary by segment. - /// - Segment = 2, + /// + /// Values vary by segment. + /// + Segment = 2, - /// - /// Values vary by culture and segment. - /// - CultureAndSegment = Culture | Segment - } + /// + /// Values vary by culture and segment. + /// + CultureAndSegment = Culture | Segment } diff --git a/src/Umbraco.Core/Models/ContentVersionCleanupPolicySettings.cs b/src/Umbraco.Core/Models/ContentVersionCleanupPolicySettings.cs index 5fa0e9895822..7d7cc6c57832 100644 --- a/src/Umbraco.Core/Models/ContentVersionCleanupPolicySettings.cs +++ b/src/Umbraco.Core/Models/ContentVersionCleanupPolicySettings.cs @@ -1,17 +1,14 @@ -using System; +namespace Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Models +public class ContentVersionCleanupPolicySettings { - public class ContentVersionCleanupPolicySettings - { - public int ContentTypeId { get; set; } + public int ContentTypeId { get; set; } - public bool PreventCleanup { get; set; } + public bool PreventCleanup { get; set; } - public int? KeepAllVersionsNewerThanDays { get; set; } + public int? KeepAllVersionsNewerThanDays { get; set; } - public int? KeepLatestVersionPerDayForDays { get; set; } + public int? KeepLatestVersionPerDayForDays { get; set; } - public DateTime Updated { get; set; } - } + public DateTime Updated { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentVersionMeta.cs b/src/Umbraco.Core/Models/ContentVersionMeta.cs index dbcd8540a0ed..94b93abf744e 100644 --- a/src/Umbraco.Core/Models/ContentVersionMeta.cs +++ b/src/Umbraco.Core/Models/ContentVersionMeta.cs @@ -1,45 +1,42 @@ -using System; +namespace Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Models +public class ContentVersionMeta { - public class ContentVersionMeta - { - public int ContentId { get; } - public int ContentTypeId { get; } - public int VersionId { get; } - public int UserId { get; } + public ContentVersionMeta() { } - public DateTime VersionDate { get; } - public bool CurrentPublishedVersion { get; } - public bool CurrentDraftVersion { get; } - public bool PreventCleanup { get; } - public string? Username { get; } + public ContentVersionMeta( + int versionId, + int contentId, + int contentTypeId, + int userId, + DateTime versionDate, + bool currentPublishedVersion, + bool currentDraftVersion, + bool preventCleanup, + string username) + { + VersionId = versionId; + ContentId = contentId; + ContentTypeId = contentTypeId; - public ContentVersionMeta() { } + UserId = userId; + VersionDate = versionDate; + CurrentPublishedVersion = currentPublishedVersion; + CurrentDraftVersion = currentDraftVersion; + PreventCleanup = preventCleanup; + Username = username; + } - public ContentVersionMeta( - int versionId, - int contentId, - int contentTypeId, - int userId, - DateTime versionDate, - bool currentPublishedVersion, - bool currentDraftVersion, - bool preventCleanup, - string username) - { - VersionId = versionId; - ContentId = contentId; - ContentTypeId = contentTypeId; + public int ContentId { get; } + public int ContentTypeId { get; } + public int VersionId { get; } + public int UserId { get; } - UserId = userId; - VersionDate = versionDate; - CurrentPublishedVersion = currentPublishedVersion; - CurrentDraftVersion = currentDraftVersion; - PreventCleanup = preventCleanup; - Username = username; - } + public DateTime VersionDate { get; } + public bool CurrentPublishedVersion { get; } + public bool CurrentDraftVersion { get; } + public bool PreventCleanup { get; } + public string? Username { get; } - public override string ToString() => $"ContentVersionMeta(versionId: {VersionId}, versionDate: {VersionDate:s}"; - } + public override string ToString() => $"ContentVersionMeta(versionId: {VersionId}, versionDate: {VersionDate:s}"; } diff --git a/src/Umbraco.Core/Models/CultureImpact.cs b/src/Umbraco.Core/Models/CultureImpact.cs index fec02093d7f3..56b3ba02dc1e 100644 --- a/src/Umbraco.Core/Models/CultureImpact.cs +++ b/src/Umbraco.Core/Models/CultureImpact.cs @@ -1,258 +1,311 @@ -using System; -using System.Linq; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Core.Models +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents the impact of a culture set. +/// +/// +/// +/// A set of cultures can be either all cultures (including the invariant culture), or +/// the invariant culture, or a specific culture. +/// +/// +public sealed class CultureImpact { + [Flags] + public enum Behavior : byte + { + AllCultures = 1, + InvariantCulture = 2, + ExplicitCulture = 4, + InvariantProperties = 8 + } + + /// + /// Initializes a new instance of the class. + /// + /// The culture code. + /// A value indicating whether the culture is the default culture. + private CultureImpact(string? culture, bool isDefault = false) + { + if (culture != null && culture.IsNullOrWhiteSpace()) + { + throw new ArgumentException("Culture \"\" is not valid here."); + } + + Culture = culture; + + if ((culture == null || culture == "*") && isDefault) + { + throw new ArgumentException("The invariant or 'all' culture can not be the default culture."); + } + + ImpactsOnlyDefaultCulture = isDefault; + } + + /// + /// Gets the impact of 'all' cultures (including the invariant culture). + /// + public static CultureImpact All { get; } = new("*"); + + /// + /// Gets the impact of the invariant culture. + /// + public static CultureImpact Invariant { get; } = new(null); + /// - /// Represents the impact of a culture set. + /// Gets the culture code. /// /// - /// A set of cultures can be either all cultures (including the invariant culture), or - /// the invariant culture, or a specific culture. + /// Can be null (invariant) or * (all cultures) or a specific culture code. /// - public sealed class CultureImpact + public string? Culture { get; } + + /// + /// Gets a value indicating whether this impact impacts all cultures, including, + /// indirectly, the invariant culture. + /// + public bool ImpactsAllCultures => Culture == "*"; + + /// + /// Gets a value indicating whether this impact impacts only the invariant culture, + /// directly, not because all cultures are impacted. + /// + public bool ImpactsOnlyInvariantCulture => Culture == null; + + /// + /// Gets a value indicating whether this impact impacts an implicit culture. + /// + /// + /// And then it does not impact the invariant culture. The impacted + /// explicit culture could be the default culture. + /// + public bool ImpactsExplicitCulture => Culture != null && Culture != "*"; + + /// + /// Gets a value indicating whether this impact impacts the default culture, directly, + /// not because all cultures are impacted. + /// + public bool ImpactsOnlyDefaultCulture { get; } + + /// + /// Gets a value indicating whether this impact impacts the invariant properties, either + /// directly, or because all cultures are impacted, or because the default culture is impacted. + /// + public bool ImpactsInvariantProperties => Culture == null || Culture == "*" || ImpactsOnlyDefaultCulture; + + /// + /// Gets a value indicating whether this also impact impacts the invariant properties, + /// even though it does not impact the invariant culture, neither directly (ImpactsInvariantCulture) + /// nor indirectly (ImpactsAllCultures). + /// + public bool ImpactsAlsoInvariantProperties => !ImpactsOnlyInvariantCulture && + !ImpactsAllCultures && + ImpactsOnlyDefaultCulture; + + public Behavior CultureBehavior + { + get + { + //null can only be invariant + if (Culture == null) + { + return Behavior.InvariantCulture | Behavior.InvariantProperties; + } + + // * is All which means its also invariant properties since this will include the default language + if (Culture == "*") + { + return Behavior.AllCultures | Behavior.InvariantProperties; + } + + //else it's explicit + Behavior result = Behavior.ExplicitCulture; + + //if the explicit culture is the default, then the behavior is also InvariantProperties + if (ImpactsOnlyDefaultCulture) + { + result |= Behavior.InvariantProperties; + } + + return result; + } + } + + /// + /// Utility method to return the culture used for invariant property errors based on what cultures are being actively + /// saved, + /// the default culture and the state of the current content item + /// + /// + /// + /// + /// + public static string? GetCultureForInvariantErrors(IContent? content, string?[] savingCultures, + string? defaultCulture) { - /// - /// Utility method to return the culture used for invariant property errors based on what cultures are being actively saved, - /// the default culture and the state of the current content item - /// - /// - /// - /// - /// - public static string? GetCultureForInvariantErrors(IContent? content, string?[] savingCultures, string? defaultCulture) + if (content == null) { - if (content == null) throw new ArgumentNullException(nameof(content)); - if (savingCultures == null) throw new ArgumentNullException(nameof(savingCultures)); - if (savingCultures.Length == 0) throw new ArgumentException(nameof(savingCultures)); - - var cultureForInvariantErrors = savingCultures.Any(x => x.InvariantEquals(defaultCulture)) - //the default culture is being flagged for saving so use it - ? defaultCulture - //If the content has no published version, we need to affiliate validation with the first variant being saved. - //If the content has a published version we will not affiliate the validation with any culture (null) - : !content.Published ? savingCultures[0] : null; - - return cultureForInvariantErrors; + throw new ArgumentNullException(nameof(content)); } - /// - /// Initializes a new instance of the class. - /// - /// The culture code. - /// A value indicating whether the culture is the default culture. - private CultureImpact(string? culture, bool isDefault = false) + if (savingCultures == null) { - if (culture != null && culture.IsNullOrWhiteSpace()) - throw new ArgumentException("Culture \"\" is not valid here."); + throw new ArgumentNullException(nameof(savingCultures)); + } - Culture = culture; + if (savingCultures.Length == 0) + { + throw new ArgumentException(nameof(savingCultures)); + } - if ((culture == null || culture == "*") && isDefault) - throw new ArgumentException("The invariant or 'all' culture can not be the default culture."); + var cultureForInvariantErrors = savingCultures.Any(x => x.InvariantEquals(defaultCulture)) + //the default culture is being flagged for saving so use it + ? defaultCulture + //If the content has no published version, we need to affiliate validation with the first variant being saved. + //If the content has a published version we will not affiliate the validation with any culture (null) + : !content.Published + ? savingCultures[0] + : null; - ImpactsOnlyDefaultCulture = isDefault; - } + return cultureForInvariantErrors; + } - /// - /// Gets the impact of 'all' cultures (including the invariant culture). - /// - public static CultureImpact All { get; } = new CultureImpact("*"); - - /// - /// Gets the impact of the invariant culture. - /// - public static CultureImpact Invariant { get; } = new CultureImpact(null); - - /// - /// Creates an impact instance representing the impact of a specific culture. - /// - /// The culture code. - /// A value indicating whether the culture is the default culture. - public static CultureImpact Explicit(string? culture, bool isDefault) + /// + /// Creates an impact instance representing the impact of a specific culture. + /// + /// The culture code. + /// A value indicating whether the culture is the default culture. + public static CultureImpact Explicit(string? culture, bool isDefault) + { + if (culture == null) { - if (culture == null) - throw new ArgumentException("Culture is not explicit."); - if (culture.IsNullOrWhiteSpace()) - throw new ArgumentException("Culture \"\" is not explicit."); - if (culture == "*") - throw new ArgumentException("Culture \"*\" is not explicit."); - - return new CultureImpact(culture, isDefault); + throw new ArgumentException("Culture is not explicit."); } - /// - /// Creates an impact instance representing the impact of a culture set, - /// in the context of a content item variation. - /// - /// The culture code. - /// A value indicating whether the culture is the default culture. - /// The content item. - /// - /// Validates that the culture is compatible with the variation. - /// - public static CultureImpact? Create(string culture, bool isDefault, IContent content) + if (culture.IsNullOrWhiteSpace()) { - // throws if not successful - TryCreate(culture, isDefault, content.ContentType.Variations, true, out var impact); - return impact; + throw new ArgumentException("Culture \"\" is not explicit."); } - /// - /// Tries to create an impact instance representing the impact of a culture set, - /// in the context of a content item variation. - /// - /// The culture code. - /// A value indicating whether the culture is the default culture. - /// A content variation. - /// A value indicating whether to throw if the impact cannot be created. - /// The impact if it could be created, otherwise null. - /// A value indicating whether the impact could be created. - /// - /// Validates that the culture is compatible with the variation. - /// - internal static bool TryCreate(string culture, bool isDefault, ContentVariation variation, bool throwOnFail, out CultureImpact? impact) + if (culture == "*") { - impact = null; + throw new ArgumentException("Culture \"*\" is not explicit."); + } - // if culture is invariant... - if (culture == null) - { - // ... then variation must not vary by culture ... - if (variation.VariesByCulture()) - { - if (throwOnFail) - throw new InvalidOperationException("The invariant culture is not compatible with a varying variation."); - return false; - } + return new CultureImpact(culture, isDefault); + } - // ... and it cannot be default - if (isDefault) - { - if (throwOnFail) - throw new InvalidOperationException("The invariant culture can not be the default culture."); - return false; - } + /// + /// Creates an impact instance representing the impact of a culture set, + /// in the context of a content item variation. + /// + /// The culture code. + /// A value indicating whether the culture is the default culture. + /// The content item. + /// + /// Validates that the culture is compatible with the variation. + /// + public static CultureImpact? Create(string culture, bool isDefault, IContent content) + { + // throws if not successful + TryCreate(culture, isDefault, content.ContentType.Variations, true, out CultureImpact impact); + return impact; + } - impact = Invariant; - return true; - } + /// + /// Tries to create an impact instance representing the impact of a culture set, + /// in the context of a content item variation. + /// + /// The culture code. + /// A value indicating whether the culture is the default culture. + /// A content variation. + /// A value indicating whether to throw if the impact cannot be created. + /// The impact if it could be created, otherwise null. + /// A value indicating whether the impact could be created. + /// + /// Validates that the culture is compatible with the variation. + /// + internal static bool TryCreate(string culture, bool isDefault, ContentVariation variation, bool throwOnFail, + out CultureImpact? impact) + { + impact = null; - // if culture is 'all'... - if (culture == "*") + // if culture is invariant... + if (culture == null) + { + // ... then variation must not vary by culture ... + if (variation.VariesByCulture()) { - // ... it cannot be default - if (isDefault) + if (throwOnFail) { - if (throwOnFail) - throw new InvalidOperationException("The 'all' culture can not be the default culture."); - return false; + throw new InvalidOperationException( + "The invariant culture is not compatible with a varying variation."); } - // if variation does not vary by culture, then impact is invariant - impact = variation.VariesByCulture() ? All : Invariant; - return true; + return false; } - // neither null nor "*" - cannot be the empty string - if (culture.IsNullOrWhiteSpace()) + // ... and it cannot be default + if (isDefault) { if (throwOnFail) - throw new ArgumentException("Cannot be the empty string.", nameof(culture)); + { + throw new InvalidOperationException("The invariant culture can not be the default culture."); + } + return false; } - // if culture is specific, then variation must vary - if (!variation.VariesByCulture()) + impact = Invariant; + return true; + } + + // if culture is 'all'... + if (culture == "*") + { + // ... it cannot be default + if (isDefault) { if (throwOnFail) - throw new InvalidOperationException($"The variant culture {culture} is not compatible with an invariant variation."); + { + throw new InvalidOperationException("The 'all' culture can not be the default culture."); + } + return false; } - // return specific impact - impact = new CultureImpact(culture, isDefault); + // if variation does not vary by culture, then impact is invariant + impact = variation.VariesByCulture() ? All : Invariant; return true; } - /// - /// Gets the culture code. - /// - /// - /// Can be null (invariant) or * (all cultures) or a specific culture code. - /// - public string? Culture { get; } - - /// - /// Gets a value indicating whether this impact impacts all cultures, including, - /// indirectly, the invariant culture. - /// - public bool ImpactsAllCultures => Culture == "*"; - - /// - /// Gets a value indicating whether this impact impacts only the invariant culture, - /// directly, not because all cultures are impacted. - /// - public bool ImpactsOnlyInvariantCulture => Culture == null; - - /// - /// Gets a value indicating whether this impact impacts an implicit culture. - /// - /// And then it does not impact the invariant culture. The impacted - /// explicit culture could be the default culture. - public bool ImpactsExplicitCulture => Culture != null && Culture != "*"; - - /// - /// Gets a value indicating whether this impact impacts the default culture, directly, - /// not because all cultures are impacted. - /// - public bool ImpactsOnlyDefaultCulture {get; } - - /// - /// Gets a value indicating whether this impact impacts the invariant properties, either - /// directly, or because all cultures are impacted, or because the default culture is impacted. - /// - public bool ImpactsInvariantProperties => Culture == null || Culture == "*" || ImpactsOnlyDefaultCulture; - - /// - /// Gets a value indicating whether this also impact impacts the invariant properties, - /// even though it does not impact the invariant culture, neither directly (ImpactsInvariantCulture) - /// nor indirectly (ImpactsAllCultures). - /// - public bool ImpactsAlsoInvariantProperties => !ImpactsOnlyInvariantCulture && - !ImpactsAllCultures && - ImpactsOnlyDefaultCulture; - - public Behavior CultureBehavior + // neither null nor "*" - cannot be the empty string + if (culture.IsNullOrWhiteSpace()) { - get + if (throwOnFail) { - //null can only be invariant - if (Culture == null) return Behavior.InvariantCulture | Behavior.InvariantProperties; - - // * is All which means its also invariant properties since this will include the default language - if (Culture == "*") return (Behavior.AllCultures | Behavior.InvariantProperties); - - //else it's explicit - var result = Behavior.ExplicitCulture; - - //if the explicit culture is the default, then the behavior is also InvariantProperties - if (ImpactsOnlyDefaultCulture) - result |= Behavior.InvariantProperties; - - return result; + throw new ArgumentException("Cannot be the empty string.", nameof(culture)); } - } + return false; + } - [Flags] - public enum Behavior : byte + // if culture is specific, then variation must vary + if (!variation.VariesByCulture()) { - AllCultures = 1, - InvariantCulture = 2, - ExplicitCulture = 4, - InvariantProperties = 8 + if (throwOnFail) + { + throw new InvalidOperationException( + $"The variant culture {culture} is not compatible with an invariant variation."); + } + + return false; } + + // return specific impact + impact = new CultureImpact(culture, isDefault); + return true; } } diff --git a/src/Umbraco.Core/Models/DataType.cs b/src/Umbraco.Core/Models/DataType.cs index 6b33f0738553..9709612cb651 100644 --- a/src/Umbraco.Core/Models/DataType.cs +++ b/src/Umbraco.Core/Models/DataType.cs @@ -1,196 +1,222 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Implements . +/// +[Serializable] +[DataContract(IsReference = true)] +public class DataType : TreeEntityBase, IDataType { + private readonly IConfigurationEditorJsonSerializer _serializer; + private object? _configuration; + private string? _configurationJson; + private ValueStorageType _databaseType; + private IDataEditor? _editor; + private bool _hasConfiguration; + /// - /// Implements . + /// Initializes a new instance of the class. /// - [Serializable] - [DataContract(IsReference = true)] - public class DataType : TreeEntityBase, IDataType + public DataType(IDataEditor? editor, IConfigurationEditorJsonSerializer serializer, int parentId = -1) { - private IDataEditor? _editor; - private ValueStorageType _databaseType; - private readonly IConfigurationEditorJsonSerializer _serializer; - private object? _configuration; - private bool _hasConfiguration; - private string? _configurationJson; - - /// - /// Initializes a new instance of the class. - /// - public DataType(IDataEditor? editor, IConfigurationEditorJsonSerializer serializer, int parentId = -1) - { - _editor = editor ?? throw new ArgumentNullException(nameof(editor)); - _serializer = serializer ?? throw new ArgumentNullException(nameof(editor)); - ParentId = parentId; + _editor = editor ?? throw new ArgumentNullException(nameof(editor)); + _serializer = serializer ?? throw new ArgumentNullException(nameof(editor)); + ParentId = parentId; - // set a default configuration - Configuration = _editor.GetConfigurationEditor().DefaultConfigurationObject; - } + // set a default configuration + Configuration = _editor.GetConfigurationEditor().DefaultConfigurationObject; + } - /// - [IgnoreDataMember] - public IDataEditor? Editor + /// + [IgnoreDataMember] + public IDataEditor? Editor + { + get => _editor; + set { - get => _editor; - set + // ignore if no change + if (_editor?.Alias == value?.Alias) { - // ignore if no change - if (_editor?.Alias == value?.Alias) return; - OnPropertyChanged(nameof(Editor)); + return; + } - // try to map the existing configuration to the new configuration - // simulate saving to db and reloading (ie go via json) - var configuration = Configuration; - var json = _serializer.Serialize(configuration); - _editor = value; + OnPropertyChanged(nameof(Editor)); - try - { - Configuration = _editor?.GetConfigurationEditor().FromDatabase(json, _serializer); - } - catch (Exception e) - { - throw new InvalidOperationException($"The configuration for data type {Id} : {EditorAlias} is invalid (see inner exception)." - + " Please fix the configuration and ensure it is valid. The site may fail to start and / or load data types and run.", e); - } + // try to map the existing configuration to the new configuration + // simulate saving to db and reloading (ie go via json) + var configuration = Configuration; + var json = _serializer.Serialize(configuration); + _editor = value; + + try + { + Configuration = _editor?.GetConfigurationEditor().FromDatabase(json, _serializer); + } + catch (Exception e) + { + throw new InvalidOperationException( + $"The configuration for data type {Id} : {EditorAlias} is invalid (see inner exception)." + + " Please fix the configuration and ensure it is valid. The site may fail to start and / or load data types and run.", + e); } } + } - /// - [DataMember] - public string EditorAlias => _editor?.Alias ?? string.Empty; + /// + [DataMember] + public string EditorAlias => _editor?.Alias ?? string.Empty; - /// - [DataMember] - public ValueStorageType DatabaseType - { - get => _databaseType; - set => SetPropertyValueAndDetectChanges(value, ref _databaseType, nameof(DatabaseType)); - } + /// + [DataMember] + public ValueStorageType DatabaseType + { + get => _databaseType; + set => SetPropertyValueAndDetectChanges(value, ref _databaseType, nameof(DatabaseType)); + } - /// - [DataMember] - public object? Configuration + /// + [DataMember] + public object? Configuration + { + get { - get + // if we know we have a configuration (which may be null), return it + // if we don't have an editor, then we have no configuration, return null + // else, use the editor to get the configuration object + + if (_hasConfiguration) { - // if we know we have a configuration (which may be null), return it - // if we don't have an editor, then we have no configuration, return null - // else, use the editor to get the configuration object + return _configuration; + } - if (_hasConfiguration) return _configuration; + try + { + _configuration = _editor?.GetConfigurationEditor().FromDatabase(_configurationJson, _serializer); + } + catch (Exception e) + { + throw new InvalidOperationException( + $"The configuration for data type {Id} : {EditorAlias} is invalid (see inner exception)." + + " Please fix the configuration and ensure it is valid. The site may fail to start and / or load data types and run.", + e); + } - try - { - _configuration = _editor?.GetConfigurationEditor().FromDatabase(_configurationJson, _serializer); - } - catch (Exception e) - { - throw new InvalidOperationException($"The configuration for data type {Id} : {EditorAlias} is invalid (see inner exception)." - + " Please fix the configuration and ensure it is valid. The site may fail to start and / or load data types and run.", e); - } + _hasConfiguration = true; + _configurationJson = null; - _hasConfiguration = true; - _configurationJson = null; + return _configuration; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } - return _configuration; + // we don't support re-assigning the same object + // configurations are kinda non-mutable, mainly because detecting changes would be a pain + if (_configuration == value) // reference comparison + { + throw new ArgumentException("Configurations are kinda non-mutable. Do not reassign the same object.", + nameof(value)); + } + + // validate configuration type + if (!_editor?.GetConfigurationEditor().IsConfiguration(value) ?? true) + { + throw new ArgumentException( + $"Value of type {value.GetType().Name} cannot be a configuration for editor {_editor?.Alias}, expecting.", + nameof(value)); } - set + + // extract database type from configuration object, if appropriate + if (value is IConfigureValueType valueTypeConfiguration) { - if (value == null) - throw new ArgumentNullException(nameof(value)); - - // we don't support re-assigning the same object - // configurations are kinda non-mutable, mainly because detecting changes would be a pain - if (_configuration == value) // reference comparison - throw new ArgumentException("Configurations are kinda non-mutable. Do not reassign the same object.", nameof(value)); - - // validate configuration type - if (!_editor?.GetConfigurationEditor().IsConfiguration(value) ?? true) - throw new ArgumentException($"Value of type {value.GetType().Name} cannot be a configuration for editor {_editor?.Alias}, expecting.", nameof(value)); - - // extract database type from configuration object, if appropriate - if (value is IConfigureValueType valueTypeConfiguration) - DatabaseType = ValueTypes.ToStorageType(valueTypeConfiguration.ValueType); - - // extract database type from dictionary, if appropriate - if (value is IDictionary dictionaryConfiguration - && dictionaryConfiguration.TryGetValue(Constants.PropertyEditors.ConfigurationKeys.DataValueType, out var valueTypeObject) - && valueTypeObject is string valueTypeString - && ValueTypes.IsValue(valueTypeString)) - DatabaseType = ValueTypes.ToStorageType(valueTypeString); - - _configuration = value; - _hasConfiguration = true; - _configurationJson = null; - - // it's always a change - OnPropertyChanged(nameof(Configuration)); + DatabaseType = ValueTypes.ToStorageType(valueTypeConfiguration.ValueType); } + + // extract database type from dictionary, if appropriate + if (value is IDictionary dictionaryConfiguration + && dictionaryConfiguration.TryGetValue(Constants.PropertyEditors.ConfigurationKeys.DataValueType, + out var valueTypeObject) + && valueTypeObject is string valueTypeString + && ValueTypes.IsValue(valueTypeString)) + { + DatabaseType = ValueTypes.ToStorageType(valueTypeString); + } + + _configuration = value; + _hasConfiguration = true; + _configurationJson = null; + + // it's always a change + OnPropertyChanged(nameof(Configuration)); } + } + + /// + /// Lazily set the configuration as a serialized json string. + /// + /// + /// Will be de-serialized on-demand. + /// + /// This method is meant to be used when building entities from database, exclusively. + /// It does NOT register a property change to dirty. It ignores the fact that the configuration + /// may contain the database type, because the datatype DTO should also contain that database + /// type, and they should be the same. + /// + /// Think before using! + /// + public void SetLazyConfiguration(string? configurationJson) + { + _hasConfiguration = false; + _configuration = null; + _configurationJson = configurationJson; + } - /// - /// Lazily set the configuration as a serialized json string. - /// - /// - /// Will be de-serialized on-demand. - /// This method is meant to be used when building entities from database, exclusively. - /// It does NOT register a property change to dirty. It ignores the fact that the configuration - /// may contain the database type, because the datatype DTO should also contain that database - /// type, and they should be the same. - /// Think before using! - /// - public void SetLazyConfiguration(string? configurationJson) + /// + /// Gets a lazy configuration. + /// + /// + /// The configuration object will be lazily de-serialized. + /// This method is meant to be used when creating published datatypes, exclusively. + /// Think before using! + /// + internal Lazy GetLazyConfiguration() + { + // note: in both cases, make sure we capture what we need - we don't want + // to capture a reference to this full, potentially heavy, DataType instance. + + if (_hasConfiguration) { - _hasConfiguration = false; - _configuration = null; - _configurationJson = configurationJson; + // if configuration has already been de-serialized, return + var capturedConfiguration = _configuration; + return new Lazy(() => capturedConfiguration); } - - /// - /// Gets a lazy configuration. - /// - /// - /// The configuration object will be lazily de-serialized. - /// This method is meant to be used when creating published datatypes, exclusively. - /// Think before using! - /// - internal Lazy GetLazyConfiguration() + else { - // note: in both cases, make sure we capture what we need - we don't want - // to capture a reference to this full, potentially heavy, DataType instance. - - if (_hasConfiguration) - { - // if configuration has already been de-serialized, return - var capturedConfiguration = _configuration; - return new Lazy(() => capturedConfiguration); - } - else + // else, create a Lazy de-serializer + var capturedConfiguration = _configurationJson; + IDataEditor capturedEditor = _editor; + return new Lazy(() => { - // else, create a Lazy de-serializer - var capturedConfiguration = _configurationJson; - var capturedEditor = _editor; - return new Lazy(() => + try { - try - { - return capturedEditor?.GetConfigurationEditor().FromDatabase(capturedConfiguration, _serializer); - } - catch (Exception e) - { - throw new InvalidOperationException($"The configuration for data type {Id} : {EditorAlias} is invalid (see inner exception)." - + " Please fix the configuration and ensure it is valid. The site may fail to start and / or load data types and run.", e); - } - }); - } + return capturedEditor?.GetConfigurationEditor().FromDatabase(capturedConfiguration, _serializer); + } + catch (Exception e) + { + throw new InvalidOperationException( + $"The configuration for data type {Id} : {EditorAlias} is invalid (see inner exception)." + + " Please fix the configuration and ensure it is valid. The site may fail to start and / or load data types and run.", + e); + } + }); } } } diff --git a/src/Umbraco.Core/Models/DataTypeExtensions.cs b/src/Umbraco.Core/Models/DataTypeExtensions.cs index 10419dca881e..2f9f4ebd744e 100644 --- a/src/Umbraco.Core/Models/DataTypeExtensions.cs +++ b/src/Umbraco.Core/Models/DataTypeExtensions.cs @@ -1,95 +1,88 @@ -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Provides extensions methods for . +/// +public static class DataTypeExtensions { + private static readonly ISet IdsOfBuildInDataTypes = new HashSet + { + Constants.DataTypes.Guids.ContentPickerGuid, + Constants.DataTypes.Guids.MemberPickerGuid, + Constants.DataTypes.Guids.MediaPickerGuid, + Constants.DataTypes.Guids.MultipleMediaPickerGuid, + Constants.DataTypes.Guids.RelatedLinksGuid, + Constants.DataTypes.Guids.MemberGuid, + Constants.DataTypes.Guids.ImageCropperGuid, + Constants.DataTypes.Guids.TagsGuid, + Constants.DataTypes.Guids.ListViewContentGuid, + Constants.DataTypes.Guids.ListViewMediaGuid, + Constants.DataTypes.Guids.ListViewMembersGuid, + Constants.DataTypes.Guids.DatePickerWithTimeGuid, + Constants.DataTypes.Guids.ApprovedColorGuid, + Constants.DataTypes.Guids.DropdownMultipleGuid, + Constants.DataTypes.Guids.RadioboxGuid, + Constants.DataTypes.Guids.DatePickerGuid, + Constants.DataTypes.Guids.DropdownGuid, + Constants.DataTypes.Guids.CheckboxListGuid, + Constants.DataTypes.Guids.CheckboxGuid, + Constants.DataTypes.Guids.NumericGuid, + Constants.DataTypes.Guids.RichtextEditorGuid, + Constants.DataTypes.Guids.TextstringGuid, + Constants.DataTypes.Guids.TextareaGuid, + Constants.DataTypes.Guids.UploadGuid, + Constants.DataTypes.Guids.UploadArticleGuid, + Constants.DataTypes.Guids.UploadAudioGuid, + Constants.DataTypes.Guids.UploadVectorGraphicsGuid, + Constants.DataTypes.Guids.UploadVideoGuid, + Constants.DataTypes.Guids.LabelStringGuid, + Constants.DataTypes.Guids.LabelDecimalGuid, + Constants.DataTypes.Guids.LabelDateTimeGuid, + Constants.DataTypes.Guids.LabelBigIntGuid, + Constants.DataTypes.Guids.LabelTimeGuid, + Constants.DataTypes.Guids.LabelDateTimeGuid + }; + /// - /// Provides extensions methods for . + /// Gets the configuration object. /// - public static class DataTypeExtensions + /// The expected type of the configuration object. + /// This datatype. + /// When the datatype configuration is not of the expected type. + public static T? ConfigurationAs(this IDataType dataType) + where T : class { - /// - /// Gets the configuration object. - /// - /// The expected type of the configuration object. - /// This datatype. - /// When the datatype configuration is not of the expected type. - public static T? ConfigurationAs(this IDataType dataType) - where T : class + if (dataType == null) { - if (dataType == null) - throw new ArgumentNullException(nameof(dataType)); - - var configuration = dataType.Configuration; - - switch (configuration) - { - case null: - return null; - case T configurationAsT: - return configurationAsT; - } - - throw new InvalidCastException($"Cannot cast dataType configuration, of type {configuration.GetType().Name}, to {typeof(T).Name}."); + throw new ArgumentNullException(nameof(dataType)); } - private static readonly ISet IdsOfBuildInDataTypes = new HashSet() - { - Constants.DataTypes.Guids.ContentPickerGuid, - Constants.DataTypes.Guids.MemberPickerGuid, - Constants.DataTypes.Guids.MediaPickerGuid, - Constants.DataTypes.Guids.MultipleMediaPickerGuid, - Constants.DataTypes.Guids.RelatedLinksGuid, - Constants.DataTypes.Guids.MemberGuid, - Constants.DataTypes.Guids.ImageCropperGuid, - Constants.DataTypes.Guids.TagsGuid, - Constants.DataTypes.Guids.ListViewContentGuid, - Constants.DataTypes.Guids.ListViewMediaGuid, - Constants.DataTypes.Guids.ListViewMembersGuid, - Constants.DataTypes.Guids.DatePickerWithTimeGuid, - Constants.DataTypes.Guids.ApprovedColorGuid, - Constants.DataTypes.Guids.DropdownMultipleGuid, - Constants.DataTypes.Guids.RadioboxGuid, - Constants.DataTypes.Guids.DatePickerGuid, - Constants.DataTypes.Guids.DropdownGuid, - Constants.DataTypes.Guids.CheckboxListGuid, - Constants.DataTypes.Guids.CheckboxGuid, - Constants.DataTypes.Guids.NumericGuid, - Constants.DataTypes.Guids.RichtextEditorGuid, - Constants.DataTypes.Guids.TextstringGuid, - Constants.DataTypes.Guids.TextareaGuid, - Constants.DataTypes.Guids.UploadGuid, - Constants.DataTypes.Guids.UploadArticleGuid, - Constants.DataTypes.Guids.UploadAudioGuid, - Constants.DataTypes.Guids.UploadVectorGraphicsGuid, - Constants.DataTypes.Guids.UploadVideoGuid, - Constants.DataTypes.Guids.LabelStringGuid, - Constants.DataTypes.Guids.LabelDecimalGuid, - Constants.DataTypes.Guids.LabelDateTimeGuid, - Constants.DataTypes.Guids.LabelBigIntGuid, - Constants.DataTypes.Guids.LabelTimeGuid, - Constants.DataTypes.Guids.LabelDateTimeGuid, - }; - - /// - /// Returns true if this date type is build-in/default. - /// - /// The data type definition. - /// - public static bool IsBuildInDataType(this IDataType dataType) - { - return IsBuildInDataType(dataType.Key); - } + var configuration = dataType.Configuration; - /// - /// Returns true if this date type is build-in/default. - /// - public static bool IsBuildInDataType(Guid key) + switch (configuration) { - return IdsOfBuildInDataTypes.Contains(key); + case null: + return null; + case T configurationAsT: + return configurationAsT; } + throw new InvalidCastException( + $"Cannot cast dataType configuration, of type {configuration.GetType().Name}, to {typeof(T).Name}."); } + + /// + /// Returns true if this date type is build-in/default. + /// + /// The data type definition. + /// + public static bool IsBuildInDataType(this IDataType dataType) => IsBuildInDataType(dataType.Key); + + /// + /// Returns true if this date type is build-in/default. + /// + public static bool IsBuildInDataType(Guid key) => IdsOfBuildInDataTypes.Contains(key); } diff --git a/src/Umbraco.Core/Models/DeepCloneHelper.cs b/src/Umbraco.Core/Models/DeepCloneHelper.cs index 4dc293641ce0..b6bf03b558ab 100644 --- a/src/Umbraco.Core/Models/DeepCloneHelper.cs +++ b/src/Umbraco.Core/Models/DeepCloneHelper.cs @@ -1,205 +1,214 @@ -using System; -using System.Collections; +using System.Collections; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; using System.Reflection; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public static class DeepCloneHelper { - public static class DeepCloneHelper + /// + /// Used to avoid constant reflection (perf) + /// + private static readonly ConcurrentDictionary PropCache = new(); + + /// + /// Used to deep clone any reference properties on the object (should be done after a MemberwiseClone for which the + /// outcome is 'output') + /// + /// + /// + /// + public static void DeepCloneRefProperties(IDeepCloneable input, IDeepCloneable output) { - /// - /// Stores the metadata for the properties for a given type so we know how to create them - /// - private struct ClonePropertyInfo - { - public ClonePropertyInfo(PropertyInfo propertyInfo) : this() - { - if (propertyInfo == null) throw new ArgumentNullException("propertyInfo"); - PropertyInfo = propertyInfo; - } + Type inputType = input.GetType(); + Type outputType = output.GetType(); - public PropertyInfo PropertyInfo { get; private set; } - public bool IsDeepCloneable { get; set; } - public Type? GenericListType { get; set; } - public bool IsList - { - get { return GenericListType != null; } - } + if (inputType != outputType) + { + throw new InvalidOperationException("Both the input and output types must be the same"); } - /// - /// Used to avoid constant reflection (perf) - /// - private static readonly ConcurrentDictionary PropCache = new ConcurrentDictionary(); - - /// - /// Used to deep clone any reference properties on the object (should be done after a MemberwiseClone for which the outcome is 'output') - /// - /// - /// - /// - public static void DeepCloneRefProperties(IDeepCloneable input, IDeepCloneable output) - { - var inputType = input.GetType(); - var outputType = output.GetType(); + //get the property metadata from cache so we only have to figure this out once per type + ClonePropertyInfo[] refProperties = PropCache.GetOrAdd(inputType, type => + inputType.GetProperties() + .Select(propertyInfo => + { + if ( + //is not attributed with the ignore clone attribute + propertyInfo.GetCustomAttribute() != null + //reference type but not string + || propertyInfo.PropertyType.IsValueType || propertyInfo.PropertyType == typeof(string) + //settable + || propertyInfo.CanWrite == false + //non-indexed + || propertyInfo.GetIndexParameters().Any()) + { + return null; + } - if (inputType != outputType) - { - throw new InvalidOperationException("Both the input and output types must be the same"); - } - //get the property metadata from cache so we only have to figure this out once per type - var refProperties = PropCache.GetOrAdd(inputType, type => - inputType.GetProperties() - .Select(propertyInfo => + if (TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType)) { - if ( - //is not attributed with the ignore clone attribute - propertyInfo.GetCustomAttribute() != null - //reference type but not string - || propertyInfo.PropertyType.IsValueType || propertyInfo.PropertyType == typeof (string) - //settable - || propertyInfo.CanWrite == false - //non-indexed - || propertyInfo.GetIndexParameters().Any()) + return new ClonePropertyInfo(propertyInfo) {IsDeepCloneable = true}; + } + + if (TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType) + && TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType) == false) + { + if (propertyInfo.PropertyType.IsGenericType + && (propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>) + || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>) + || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>) + || propertyInfo.PropertyType.GetGenericTypeDefinition() == + typeof(IReadOnlyCollection<>))) { - return null; + //if it is a IEnumerable<>, IReadOnlyCollection, IList or ICollection<> we'll use a List<> since it implements them all + Type genericType = + typeof(List<>).MakeGenericType(propertyInfo.PropertyType.GetGenericArguments()); + return new ClonePropertyInfo(propertyInfo) {GenericListType = genericType}; } + if (propertyInfo.PropertyType.IsArray + || (propertyInfo.PropertyType.IsInterface && + propertyInfo.PropertyType.IsGenericType == false)) + { + //if its an array, we'll create a list to work with first and then convert to array later + //otherwise if its just a regular derivative of IEnumerable, we can use a list too + return new ClonePropertyInfo(propertyInfo) {GenericListType = typeof(List)}; + } - if (TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType)) + //skip instead of trying to create instance of abstract or interface + if (propertyInfo.PropertyType.IsAbstract || propertyInfo.PropertyType.IsInterface) { - return new ClonePropertyInfo(propertyInfo) { IsDeepCloneable = true }; + return null; } - if (TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType) - && TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType) == false) + //its a custom IEnumerable, we'll try to create it + try { - if (propertyInfo.PropertyType.IsGenericType - && (propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>) - || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>) - || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>) - || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IReadOnlyCollection<>))) - { - //if it is a IEnumerable<>, IReadOnlyCollection, IList or ICollection<> we'll use a List<> since it implements them all - var genericType = typeof(List<>).MakeGenericType(propertyInfo.PropertyType.GetGenericArguments()); - return new ClonePropertyInfo(propertyInfo) { GenericListType = genericType }; - } - if (propertyInfo.PropertyType.IsArray - || (propertyInfo.PropertyType.IsInterface && propertyInfo.PropertyType.IsGenericType == false)) - { - //if its an array, we'll create a list to work with first and then convert to array later - //otherwise if its just a regular derivative of IEnumerable, we can use a list too - return new ClonePropertyInfo(propertyInfo) { GenericListType = typeof(List) }; - } - //skip instead of trying to create instance of abstract or interface - if (propertyInfo.PropertyType.IsAbstract || propertyInfo.PropertyType.IsInterface) + var custom = Activator.CreateInstance(propertyInfo.PropertyType); + //if it's an IList we can work with it, otherwise we cannot + var newList = custom as IList; + if (newList == null) { return null; } - //its a custom IEnumerable, we'll try to create it - try - { - var custom = Activator.CreateInstance(propertyInfo.PropertyType); - //if it's an IList we can work with it, otherwise we cannot - var newList = custom as IList; - if (newList == null) - { - return null; - } - return new ClonePropertyInfo(propertyInfo) {GenericListType = propertyInfo.PropertyType}; - } - catch (Exception) - { - //could not create this type so we'll skip it - return null; - } + return new ClonePropertyInfo(propertyInfo) {GenericListType = propertyInfo.PropertyType}; } - return new ClonePropertyInfo(propertyInfo); - }) - .Where(x => x.HasValue) - .Select(x => x!.Value) - .ToArray()); + catch (Exception) + { + //could not create this type so we'll skip it + return null; + } + } - foreach (var clonePropertyInfo in refProperties) + return new ClonePropertyInfo(propertyInfo); + }) + .Where(x => x.HasValue) + .Select(x => x!.Value) + .ToArray()); + + foreach (ClonePropertyInfo clonePropertyInfo in refProperties) + { + if (clonePropertyInfo.IsDeepCloneable) { - if (clonePropertyInfo.IsDeepCloneable) - { - //this ref property is also deep cloneable so clone it - var result = (IDeepCloneable?)clonePropertyInfo.PropertyInfo.GetValue(input, null); + //this ref property is also deep cloneable so clone it + var result = (IDeepCloneable?)clonePropertyInfo.PropertyInfo.GetValue(input, null); - if (result != null) - { - //set the cloned value to the property - clonePropertyInfo.PropertyInfo.SetValue(output, result.DeepClone(), null); - } + if (result != null) + { + //set the cloned value to the property + clonePropertyInfo.PropertyInfo.SetValue(output, result.DeepClone(), null); } - else if (clonePropertyInfo.IsList) + } + else if (clonePropertyInfo.IsList) + { + var enumerable = (IEnumerable?)clonePropertyInfo.PropertyInfo.GetValue(input, null); + if (enumerable == null) { - var enumerable = (IEnumerable?)clonePropertyInfo.PropertyInfo.GetValue(input, null); - if (enumerable == null) continue; + continue; + } - var newList = clonePropertyInfo.GenericListType is not null ? (IList?)Activator.CreateInstance(clonePropertyInfo.GenericListType) : null; + IList newList = clonePropertyInfo.GenericListType is not null + ? (IList?)Activator.CreateInstance(clonePropertyInfo.GenericListType) + : null; - var isUsableType = true; + var isUsableType = true; - //now clone each item - foreach (var o in enumerable) + //now clone each item + foreach (var o in enumerable) + { + //first check if the item is deep cloneable and copy that way + var dc = o as IDeepCloneable; + if (dc != null) { - //first check if the item is deep cloneable and copy that way - var dc = o as IDeepCloneable; - if (dc != null) - { - newList?.Add(dc.DeepClone()); - } - else if (o is string || o.GetType().IsValueType) - { - //check if the item is a value type or a string, then we can just use it - newList?.Add(o); - } - else - { - //this will occur if the item is not a string or value type or IDeepCloneable, in this case we cannot - // clone each element, we'll need to skip this property, people will have to manually clone this list - isUsableType = false; - break; - } + newList?.Add(dc.DeepClone()); } - - //if this was not usable, skip this property - if (isUsableType == false) + else if (o is string || o.GetType().IsValueType) { - continue; + //check if the item is a value type or a string, then we can just use it + newList?.Add(o); } + else + { + //this will occur if the item is not a string or value type or IDeepCloneable, in this case we cannot + // clone each element, we'll need to skip this property, people will have to manually clone this list + isUsableType = false; + break; + } + } - if (clonePropertyInfo.PropertyInfo.PropertyType.IsArray) + //if this was not usable, skip this property + if (isUsableType == false) + { + continue; + } + + if (clonePropertyInfo.PropertyInfo.PropertyType.IsArray) + { + //need to convert to array + var arr = (object?[]?)Activator.CreateInstance(clonePropertyInfo.PropertyInfo.PropertyType, + newList?.Count ?? 0); + for (var i = 0; i < newList?.Count; i++) { - //need to convert to array - var arr = (object?[]?)Activator.CreateInstance(clonePropertyInfo.PropertyInfo.PropertyType, newList?.Count ?? 0); - for (int i = 0; i < newList?.Count; i++) + if (arr != null) { - if (arr != null) - { - arr[i] = newList[i]; - } + arr[i] = newList[i]; } - - //set the cloned collection - clonePropertyInfo.PropertyInfo.SetValue(output, arr, null); - } - else - { - //set the cloned collection - clonePropertyInfo.PropertyInfo.SetValue(output, newList, null); } + //set the cloned collection + clonePropertyInfo.PropertyInfo.SetValue(output, arr, null); + } + else + { + //set the cloned collection + clonePropertyInfo.PropertyInfo.SetValue(output, newList, null); } } } + } + + /// + /// Stores the metadata for the properties for a given type so we know how to create them + /// + private struct ClonePropertyInfo + { + public ClonePropertyInfo(PropertyInfo propertyInfo) : this() + { + if (propertyInfo == null) + { + throw new ArgumentNullException("propertyInfo"); + } + + PropertyInfo = propertyInfo; + } + public PropertyInfo PropertyInfo { get; } + public bool IsDeepCloneable { get; set; } + public Type? GenericListType { get; set; } + public bool IsList => GenericListType != null; } } diff --git a/src/Umbraco.Core/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs index 14cd3bb2e58f..efa285e45b73 100644 --- a/src/Umbraco.Core/Models/DictionaryItem.cs +++ b/src/Umbraco.Core/Models/DictionaryItem.cs @@ -1,83 +1,82 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a Dictionary Item +/// +[Serializable] +[DataContract(IsReference = true)] +public class DictionaryItem : EntityBase, IDictionaryItem { - /// - /// Represents a Dictionary Item - /// - [Serializable] - [DataContract(IsReference = true)] - public class DictionaryItem : EntityBase, IDictionaryItem - { - public Func? GetLanguage { get; set; } - private Guid? _parentId; - private string _itemKey; - private IEnumerable _translations; + //Custom comparer for enumerable + private static readonly DelegateEqualityComparer> + DictionaryTranslationComparer = + new( + (enumerable, translations) => enumerable.UnsortedSequenceEqual(translations), + enumerable => enumerable.GetHashCode()); - public DictionaryItem(string itemKey) - : this(null, itemKey) - {} + private string _itemKey; + private Guid? _parentId; + private IEnumerable _translations; - public DictionaryItem(Guid? parentId, string itemKey) - { - _parentId = parentId; - _itemKey = itemKey; - _translations = new List(); - } + public DictionaryItem(string itemKey) + : this(null, itemKey) + { + } - //Custom comparer for enumerable - private static readonly DelegateEqualityComparer> DictionaryTranslationComparer = - new DelegateEqualityComparer>( - (enumerable, translations) => enumerable.UnsortedSequenceEqual(translations), - enumerable => enumerable.GetHashCode()); + public DictionaryItem(Guid? parentId, string itemKey) + { + _parentId = parentId; + _itemKey = itemKey; + _translations = new List(); + } - /// - /// Gets or Sets the Parent Id of the Dictionary Item - /// - [DataMember] - public Guid? ParentId - { - get { return _parentId; } - set { SetPropertyValueAndDetectChanges(value, ref _parentId, nameof(ParentId)); } - } + public Func? GetLanguage { get; set; } - /// - /// Gets or sets the Key for the Dictionary Item - /// - [DataMember] - public string ItemKey - { - get { return _itemKey; } - set { SetPropertyValueAndDetectChanges(value, ref _itemKey!, nameof(ItemKey)); } - } + /// + /// Gets or Sets the Parent Id of the Dictionary Item + /// + [DataMember] + public Guid? ParentId + { + get => _parentId; + set => SetPropertyValueAndDetectChanges(value, ref _parentId, nameof(ParentId)); + } + + /// + /// Gets or sets the Key for the Dictionary Item + /// + [DataMember] + public string ItemKey + { + get => _itemKey; + set => SetPropertyValueAndDetectChanges(value, ref _itemKey!, nameof(ItemKey)); + } - /// - /// Gets or sets a list of translations for the Dictionary Item - /// - [DataMember] - public IEnumerable Translations + /// + /// Gets or sets a list of translations for the Dictionary Item + /// + [DataMember] + public IEnumerable Translations + { + get => _translations; + set { - get { return _translations; } - set + IDictionaryTranslation[] asArray = value?.ToArray(); + //ensure the language callback is set on each translation + if (GetLanguage != null && asArray is not null) { - var asArray = value?.ToArray(); - //ensure the language callback is set on each translation - if (GetLanguage != null && asArray is not null) + foreach (DictionaryTranslation translation in asArray.OfType()) { - foreach (var translation in asArray.OfType()) - { - translation.GetLanguage = GetLanguage; - } + translation.GetLanguage = GetLanguage; } - - SetPropertyValueAndDetectChanges(asArray, ref _translations!, nameof(Translations), - DictionaryTranslationComparer); } + + SetPropertyValueAndDetectChanges(asArray, ref _translations!, nameof(Translations), + DictionaryTranslationComparer); } } } diff --git a/src/Umbraco.Core/Models/DictionaryItemExtensions.cs b/src/Umbraco.Core/Models/DictionaryItemExtensions.cs index 137680aa27f1..1fb6500e2069 100644 --- a/src/Umbraco.Core/Models/DictionaryItemExtensions.cs +++ b/src/Umbraco.Core/Models/DictionaryItemExtensions.cs @@ -1,31 +1,29 @@ -using System.Linq; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class DictionaryItemExtensions { - public static class DictionaryItemExtensions + /// + /// Returns the translation value for the language id, if no translation is found it returns an empty string + /// + /// + /// + /// + public static string? GetTranslatedValue(this IDictionaryItem d, int languageId) { - /// - /// Returns the translation value for the language id, if no translation is found it returns an empty string - /// - /// - /// - /// - public static string? GetTranslatedValue(this IDictionaryItem d, int languageId) - { - var trans = d.Translations?.FirstOrDefault(x => x.LanguageId == languageId); - return trans == null ? string.Empty : trans.Value; - } + IDictionaryTranslation trans = d.Translations?.FirstOrDefault(x => x.LanguageId == languageId); + return trans == null ? string.Empty : trans.Value; + } - /// - /// Returns the default translated value based on the default language - /// - /// - /// - public static string? GetDefaultValue(this IDictionaryItem d) - { - var defaultTranslation = d.Translations?.FirstOrDefault(x => x.Language?.Id == 1); - return defaultTranslation == null ? string.Empty : defaultTranslation.Value; - } + /// + /// Returns the default translated value based on the default language + /// + /// + /// + public static string? GetDefaultValue(this IDictionaryItem d) + { + IDictionaryTranslation defaultTranslation = d.Translations?.FirstOrDefault(x => x.Language?.Id == 1); + return defaultTranslation == null ? string.Empty : defaultTranslation.Value; } } diff --git a/src/Umbraco.Core/Models/DictionaryTranslation.cs b/src/Umbraco.Core/Models/DictionaryTranslation.cs index d0d98a64dbc9..6a7dacc22593 100644 --- a/src/Umbraco.Core/Models/DictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/DictionaryTranslation.cs @@ -1,107 +1,116 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a translation for a +/// +[Serializable] +[DataContract(IsReference = true)] +public class DictionaryTranslation : EntityBase, IDictionaryTranslation { - /// - /// Represents a translation for a - /// - [Serializable] - [DataContract(IsReference = true)] - public class DictionaryTranslation : EntityBase, IDictionaryTranslation - { - public Func? GetLanguage { get; set; } + private ILanguage? _language; - private ILanguage? _language; - private string _value; - //note: this will be memberwise cloned - private int _languageId; + //note: this will be memberwise cloned + private string _value; - public DictionaryTranslation(ILanguage language, string value) + public DictionaryTranslation(ILanguage language, string value) + { + if (language == null) { - if (language == null) throw new ArgumentNullException("language"); - _language = language; - _languageId = _language.Id; - _value = value; + throw new ArgumentNullException("language"); } - public DictionaryTranslation(ILanguage language, string value, Guid uniqueId) - { - if (language == null) throw new ArgumentNullException("language"); - _language = language; - _languageId = _language.Id; - _value = value; - Key = uniqueId; - } + _language = language; + LanguageId = _language.Id; + _value = value; + } - public DictionaryTranslation(int languageId, string value) + public DictionaryTranslation(ILanguage language, string value, Guid uniqueId) + { + if (language == null) { - _languageId = languageId; - _value = value; + throw new ArgumentNullException("language"); } - public DictionaryTranslation(int languageId, string value, Guid uniqueId) - { - _languageId = languageId; - _value = value; - Key = uniqueId; - } + _language = language; + LanguageId = _language.Id; + _value = value; + Key = uniqueId; + } + + public DictionaryTranslation(int languageId, string value) + { + LanguageId = languageId; + _value = value; + } + + public DictionaryTranslation(int languageId, string value, Guid uniqueId) + { + LanguageId = languageId; + _value = value; + Key = uniqueId; + } + + public Func? GetLanguage { get; set; } - /// - /// Gets or sets the for the translation - /// - /// - /// Marked as DoNotClone - TODO: this member shouldn't really exist here in the first place, the DictionaryItem - /// class will have a deep hierarchy of objects which all get deep cloned which we don't want. This should have simply - /// just referenced a language ID not the actual language object. In v8 we need to fix this. - /// We're going to have to do the same hacky stuff we had to do with the Template/File contents so that this is returned - /// on a callback. - /// - [DataMember] - [DoNotClone] - public ILanguage? Language + /// + /// Gets or sets the for the translation + /// + /// + /// Marked as DoNotClone - TODO: this member shouldn't really exist here in the first place, the DictionaryItem + /// class will have a deep hierarchy of objects which all get deep cloned which we don't want. This should have simply + /// just referenced a language ID not the actual language object. In v8 we need to fix this. + /// We're going to have to do the same hacky stuff we had to do with the Template/File contents so that this is + /// returned + /// on a callback. + /// + [DataMember] + [DoNotClone] + public ILanguage? Language + { + get { - get + if (_language != null) { - if (_language != null) - return _language; - - // else, must lazy-load - if (GetLanguage != null && _languageId > 0) - _language = GetLanguage(_languageId); return _language; } - set + + // else, must lazy-load + if (GetLanguage != null && LanguageId > 0) { - SetPropertyValueAndDetectChanges(value, ref _language, nameof(Language)); - _languageId = _language == null ? -1 : _language.Id; + _language = GetLanguage(LanguageId); } - } - public int LanguageId - { - get { return _languageId; } + return _language; } - - /// - /// Gets or sets the translated text - /// - [DataMember] - public string Value + set { - get { return _value; } - set { SetPropertyValueAndDetectChanges(value, ref _value!, nameof(Value)); } + SetPropertyValueAndDetectChanges(value, ref _language, nameof(Language)); + LanguageId = _language == null ? -1 : _language.Id; } + } - protected override void PerformDeepClone(object clone) - { - base.PerformDeepClone(clone); + public int LanguageId { get; private set; } - var clonedEntity = (DictionaryTranslation)clone; + /// + /// Gets or sets the translated text + /// + [DataMember] + public string Value + { + get => _value; + set => SetPropertyValueAndDetectChanges(value, ref _value!, nameof(Value)); + } - // clear fields that were memberwise-cloned and that we don't want to clone - clonedEntity._language = null; - } + protected override void PerformDeepClone(object clone) + { + base.PerformDeepClone(clone); + + var clonedEntity = (DictionaryTranslation)clone; + + // clear fields that were memberwise-cloned and that we don't want to clone + clonedEntity._language = null; } } diff --git a/src/Umbraco.Core/Models/DoNotCloneAttribute.cs b/src/Umbraco.Core/Models/DoNotCloneAttribute.cs index 39a7bcd90025..ea25f055eea5 100644 --- a/src/Umbraco.Core/Models/DoNotCloneAttribute.cs +++ b/src/Umbraco.Core/Models/DoNotCloneAttribute.cs @@ -1,23 +1,16 @@ -using System; +namespace Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Models +/// +/// Used to attribute properties that have a setter and are a reference type +/// that should be ignored for cloning when using the DeepCloneHelper +/// +/// +/// This attribute must be used: +/// * when the property is backed by a field but the result of the property is the un-natural data stored in the field +/// This attribute should not be used: +/// * when the property is virtual +/// * when the setter performs additional required logic other than just setting the underlying field +/// +public class DoNotCloneAttribute : Attribute { - /// - /// Used to attribute properties that have a setter and are a reference type - /// that should be ignored for cloning when using the DeepCloneHelper - /// - /// - /// - /// This attribute must be used: - /// * when the property is backed by a field but the result of the property is the un-natural data stored in the field - /// - /// This attribute should not be used: - /// * when the property is virtual - /// * when the setter performs additional required logic other than just setting the underlying field - /// - /// - public class DoNotCloneAttribute : Attribute - { - - } } diff --git a/src/Umbraco.Core/Models/Editors/ContentPropertyData.cs b/src/Umbraco.Core/Models/Editors/ContentPropertyData.cs index 0255cfd40e0d..d396a1dd7517 100644 --- a/src/Umbraco.Core/Models/Editors/ContentPropertyData.cs +++ b/src/Umbraco.Core/Models/Editors/ContentPropertyData.cs @@ -1,45 +1,42 @@ -using System; +namespace Umbraco.Cms.Core.Models.Editors; -namespace Umbraco.Cms.Core.Models.Editors +/// +/// Represents data that has been submitted to be saved for a content property +/// +/// +/// This object exists because we may need to save additional data for each property, more than just +/// the string representation of the value being submitted. An example of this is uploaded files. +/// +public class ContentPropertyData { - /// - /// Represents data that has been submitted to be saved for a content property - /// - /// - /// This object exists because we may need to save additional data for each property, more than just - /// the string representation of the value being submitted. An example of this is uploaded files. - /// - public class ContentPropertyData + public ContentPropertyData(object? value, object? dataTypeConfiguration) { - public ContentPropertyData(object? value, object? dataTypeConfiguration) - { - Value = value; - DataTypeConfiguration = dataTypeConfiguration; - } + Value = value; + DataTypeConfiguration = dataTypeConfiguration; + } - /// - /// The value submitted for the property - /// - public object? Value { get; } + /// + /// The value submitted for the property + /// + public object? Value { get; } - /// - /// The data type configuration for the property. - /// - public object? DataTypeConfiguration { get; } + /// + /// The data type configuration for the property. + /// + public object? DataTypeConfiguration { get; } - /// - /// Gets or sets the unique identifier of the content owning the property. - /// - public Guid ContentKey { get; set; } + /// + /// Gets or sets the unique identifier of the content owning the property. + /// + public Guid ContentKey { get; set; } - /// - /// Gets or sets the unique identifier of the property type. - /// - public Guid PropertyTypeKey { get; set; } + /// + /// Gets or sets the unique identifier of the property type. + /// + public Guid PropertyTypeKey { get; set; } - /// - /// Gets or sets the uploaded files. - /// - public ContentPropertyFile[]? Files { get; set; } - } + /// + /// Gets or sets the uploaded files. + /// + public ContentPropertyFile[]? Files { get; set; } } diff --git a/src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs b/src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs index d1bc9127ce8c..a2b74fff736e 100644 --- a/src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs +++ b/src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs @@ -1,43 +1,42 @@ -namespace Umbraco.Cms.Core.Models.Editors -{ +namespace Umbraco.Cms.Core.Models.Editors; +/// +/// Represents an uploaded file for a property. +/// +public class ContentPropertyFile +{ /// - /// Represents an uploaded file for a property. + /// Gets or sets the property alias. /// - public class ContentPropertyFile - { - /// - /// Gets or sets the property alias. - /// - public string? PropertyAlias { get; set; } + public string? PropertyAlias { get; set; } - /// - /// When dealing with content variants, this is the culture for the variant - /// - public string? Culture { get; set; } + /// + /// When dealing with content variants, this is the culture for the variant + /// + public string? Culture { get; set; } - /// - /// When dealing with content variants, this is the segment for the variant - /// - public string? Segment { get; set; } + /// + /// When dealing with content variants, this is the segment for the variant + /// + public string? Segment { get; set; } - /// - /// An array of metadata that is parsed out from the file info posted to the server which is set on the client. - /// - /// - /// This can be used for property types like Nested Content that need to have special unique identifiers for each file since there might be multiple files - /// per property. - /// - public string[]? Metadata { get; set; } + /// + /// An array of metadata that is parsed out from the file info posted to the server which is set on the client. + /// + /// + /// This can be used for property types like Nested Content that need to have special unique identifiers for each file + /// since there might be multiple files + /// per property. + /// + public string[]? Metadata { get; set; } - /// - /// Gets or sets the name of the file. - /// - public string? FileName { get; set; } + /// + /// Gets or sets the name of the file. + /// + public string? FileName { get; set; } - /// - /// Gets or sets the temporary path where the file has been uploaded. - /// - public string TempFilePath { get; set; } = string.Empty; - } + /// + /// Gets or sets the temporary path where the file has been uploaded. + /// + public string TempFilePath { get; set; } = string.Empty; } diff --git a/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs b/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs index 4efc5017e113..81395c99d975 100644 --- a/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs +++ b/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs @@ -1,70 +1,55 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models.Editors; -namespace Umbraco.Cms.Core.Models.Editors +/// +/// Used to track reference to other entities in a property value +/// +public struct UmbracoEntityReference : IEquatable { - /// - /// Used to track reference to other entities in a property value - /// - public struct UmbracoEntityReference : IEquatable + private static readonly UmbracoEntityReference _empty = new(UnknownTypeUdi.Instance, string.Empty); + + public UmbracoEntityReference(Udi udi, string relationTypeAlias) { - private static readonly UmbracoEntityReference _empty = new UmbracoEntityReference(UnknownTypeUdi.Instance, string.Empty); + Udi = udi ?? throw new ArgumentNullException(nameof(udi)); + RelationTypeAlias = relationTypeAlias ?? throw new ArgumentNullException(nameof(relationTypeAlias)); + } - public UmbracoEntityReference(Udi udi, string relationTypeAlias) - { - Udi = udi ?? throw new ArgumentNullException(nameof(udi)); - RelationTypeAlias = relationTypeAlias ?? throw new ArgumentNullException(nameof(relationTypeAlias)); - } + public UmbracoEntityReference(Udi udi) + { + Udi = udi ?? throw new ArgumentNullException(nameof(udi)); - public UmbracoEntityReference(Udi udi) + switch (udi.EntityType) { - Udi = udi ?? throw new ArgumentNullException(nameof(udi)); - - switch (udi.EntityType) - { - case Constants.UdiEntityType.Media: - RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedMediaAlias; - break; - default: - RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedDocumentAlias; - break; - } + case Constants.UdiEntityType.Media: + RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedMediaAlias; + break; + default: + RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedDocumentAlias; + break; } + } - public static UmbracoEntityReference Empty() => _empty; + public static UmbracoEntityReference Empty() => _empty; - public static bool IsEmpty(UmbracoEntityReference reference) => reference == Empty(); + public static bool IsEmpty(UmbracoEntityReference reference) => reference == Empty(); - public Udi Udi { get; } - public string RelationTypeAlias { get; } + public Udi Udi { get; } + public string RelationTypeAlias { get; } - public override bool Equals(object? obj) - { - return obj is UmbracoEntityReference reference && Equals(reference); - } + public override bool Equals(object? obj) => obj is UmbracoEntityReference reference && Equals(reference); - public bool Equals(UmbracoEntityReference other) - { - return EqualityComparer.Default.Equals(Udi, other.Udi) && - RelationTypeAlias == other.RelationTypeAlias; - } + public bool Equals(UmbracoEntityReference other) => + EqualityComparer.Default.Equals(Udi, other.Udi) && + RelationTypeAlias == other.RelationTypeAlias; - public override int GetHashCode() - { - var hashCode = -487348478; - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Udi); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(RelationTypeAlias); - return hashCode; - } + public override int GetHashCode() + { + var hashCode = -487348478; + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(Udi); + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(RelationTypeAlias); + return hashCode; + } - public static bool operator ==(UmbracoEntityReference left, UmbracoEntityReference right) - { - return left.Equals(right); - } + public static bool operator ==(UmbracoEntityReference left, UmbracoEntityReference right) => left.Equals(right); - public static bool operator !=(UmbracoEntityReference left, UmbracoEntityReference right) - { - return !(left == right); - } - } + public static bool operator !=(UmbracoEntityReference left, UmbracoEntityReference right) => !(left == right); } diff --git a/src/Umbraco.Core/Models/Email/EmailMessage.cs b/src/Umbraco.Core/Models/Email/EmailMessage.cs index b012bbfeb38b..5df9084144e3 100644 --- a/src/Umbraco.Core/Models/Email/EmailMessage.cs +++ b/src/Umbraco.Core/Models/Email/EmailMessage.cs @@ -1,82 +1,78 @@ -using System; -using System.Collections.Generic; -using System.Linq; +namespace Umbraco.Cms.Core.Models.Email; -namespace Umbraco.Cms.Core.Models.Email +public class EmailMessage { - public class EmailMessage + public EmailMessage(string? from, string? to, string? subject, string? body, bool isBodyHtml) + : this(from, new[] {to}, null, null, null, subject, body, isBodyHtml, null) { - public string? From { get; } + } + + public EmailMessage(string? from, string?[] to, string[]? cc, string[]? bcc, string[]? replyTo, string? subject, + string? body, bool isBodyHtml, IEnumerable? attachments) + { + ArgumentIsNotNullOrEmpty(to, nameof(to)); + ArgumentIsNotNullOrEmpty(subject, nameof(subject)); + ArgumentIsNotNullOrEmpty(body, nameof(body)); + + From = from; + To = to; + Cc = cc; + Bcc = bcc; + ReplyTo = replyTo; + Subject = subject; + Body = body; + IsBodyHtml = isBodyHtml; + Attachments = attachments?.ToList(); + } + + public string? From { get; } - public string?[] To { get; } + public string?[] To { get; } - public string[]? Cc { get; } + public string[]? Cc { get; } - public string[]? Bcc { get; } + public string[]? Bcc { get; } - public string[]? ReplyTo { get; } + public string[]? ReplyTo { get; } - public string? Subject { get; } + public string? Subject { get; } - public string? Body { get; } + public string? Body { get; } - public bool IsBodyHtml { get; } + public bool IsBodyHtml { get; } - public IList? Attachments { get; } + public IList? Attachments { get; } - public bool HasAttachments => Attachments != null && Attachments.Count > 0; + public bool HasAttachments => Attachments != null && Attachments.Count > 0; - public EmailMessage(string? from, string? to, string? subject, string? body, bool isBodyHtml) - : this(from, new[] { to }, null, null, null, subject, body, isBodyHtml, null) + private static void ArgumentIsNotNullOrEmpty(string? arg, string argName) + { + if (arg == null) { + throw new ArgumentNullException(argName); } - public EmailMessage(string? from, string?[] to, string[]? cc, string[]? bcc, string[]? replyTo, string? subject, string? body, bool isBodyHtml, IEnumerable? attachments) + if (arg.Length == 0) + { + throw new ArgumentException("Value cannot be empty.", argName); + } + } + + private static void ArgumentIsNotNullOrEmpty(string?[]? arg, string argName) + { + if (arg == null) { - ArgumentIsNotNullOrEmpty(to, nameof(to)); - ArgumentIsNotNullOrEmpty(subject, nameof(subject)); - ArgumentIsNotNullOrEmpty(body, nameof(body)); - - From = from; - To = to; - Cc = cc; - Bcc = bcc; - ReplyTo = replyTo; - Subject = subject; - Body = body; - IsBodyHtml = isBodyHtml; - Attachments = attachments?.ToList(); + throw new ArgumentNullException(argName); } - private static void ArgumentIsNotNullOrEmpty(string? arg, string argName) + if (arg.Length == 0) { - if (arg == null) - { - throw new ArgumentNullException(argName); - } - - if (arg.Length == 0) - { - throw new ArgumentException("Value cannot be empty.", argName); - } + throw new ArgumentException("Value cannot be an empty array.", argName); } - private static void ArgumentIsNotNullOrEmpty(string?[]? arg, string argName) + if (arg.Any(x => x is not null && x.Length > 0) == false) { - if (arg == null) - { - throw new ArgumentNullException(argName); - } - - if (arg.Length == 0) - { - throw new ArgumentException("Value cannot be an empty array.", argName); - } - - if (arg.Any(x => x is not null && x.Length > 0) == false) - { - throw new ArgumentException("Value cannot be an array containing only null or empty elements.", argName); - } + throw new ArgumentException("Value cannot be an array containing only null or empty elements.", argName); } } } diff --git a/src/Umbraco.Core/Models/Email/EmailMessageAttachment.cs b/src/Umbraco.Core/Models/Email/EmailMessageAttachment.cs index bbb24b69f7f6..96c52ef9e751 100644 --- a/src/Umbraco.Core/Models/Email/EmailMessageAttachment.cs +++ b/src/Umbraco.Core/Models/Email/EmailMessageAttachment.cs @@ -1,17 +1,14 @@ -using System.IO; +namespace Umbraco.Cms.Core.Models.Email; -namespace Umbraco.Cms.Core.Models.Email +public class EmailMessageAttachment { - public class EmailMessageAttachment + public EmailMessageAttachment(Stream stream, string fileName) { - public Stream Stream { get; } + Stream = stream; + FileName = fileName; + } - public string FileName { get; } + public Stream Stream { get; } - public EmailMessageAttachment(Stream stream, string fileName) - { - Stream = stream; - FileName = fileName; - } - } + public string FileName { get; } } diff --git a/src/Umbraco.Core/Models/Email/NotificationEmailAddress.cs b/src/Umbraco.Core/Models/Email/NotificationEmailAddress.cs index 755947c6a424..c9488f0798de 100644 --- a/src/Umbraco.Core/Models/Email/NotificationEmailAddress.cs +++ b/src/Umbraco.Core/Models/Email/NotificationEmailAddress.cs @@ -1,18 +1,17 @@ -namespace Umbraco.Cms.Core.Models.Email +namespace Umbraco.Cms.Core.Models.Email; + +/// +/// Represents an email address used for notifications. Contains both the address and its display name. +/// +public class NotificationEmailAddress { - /// - /// Represents an email address used for notifications. Contains both the address and its display name. - /// - public class NotificationEmailAddress + public NotificationEmailAddress(string address, string displayName) { - public string DisplayName { get; } + Address = address; + DisplayName = displayName; + } - public string Address { get; } + public string DisplayName { get; } - public NotificationEmailAddress(string address, string displayName) - { - Address = address; - DisplayName = displayName; - } - } + public string Address { get; } } diff --git a/src/Umbraco.Core/Models/Email/NotificationEmailModel.cs b/src/Umbraco.Core/Models/Email/NotificationEmailModel.cs index c71519d83f5c..abfea360d919 100644 --- a/src/Umbraco.Core/Models/Email/NotificationEmailModel.cs +++ b/src/Umbraco.Core/Models/Email/NotificationEmailModel.cs @@ -1,54 +1,49 @@ -using System.Collections.Generic; -using System.Linq; +namespace Umbraco.Cms.Core.Models.Email; -namespace Umbraco.Cms.Core.Models.Email +/// +/// Represents an email when sent with notifications. +/// +public class NotificationEmailModel { - /// - /// Represents an email when sent with notifications. - /// - public class NotificationEmailModel + public NotificationEmailModel( + NotificationEmailAddress? from, + IEnumerable? to, + IEnumerable? cc, + IEnumerable? bcc, + IEnumerable? replyTo, + string? subject, + string? body, + IEnumerable? attachments, + bool isBodyHtml) { - public NotificationEmailAddress? From { get; } - - public IEnumerable? To { get; } + From = from; + To = to; + Cc = cc; + Bcc = bcc; + ReplyTo = replyTo; + Subject = subject; + Body = body; + IsBodyHtml = isBodyHtml; + Attachments = attachments?.ToList(); + } - public IEnumerable? Cc { get; } + public NotificationEmailAddress? From { get; } - public IEnumerable? Bcc { get; } + public IEnumerable? To { get; } - public IEnumerable? ReplyTo { get; } + public IEnumerable? Cc { get; } - public string? Subject { get; } + public IEnumerable? Bcc { get; } - public string? Body { get; } + public IEnumerable? ReplyTo { get; } - public bool IsBodyHtml { get; } + public string? Subject { get; } - public IList? Attachments { get; } + public string? Body { get; } - public bool HasAttachments => Attachments != null && Attachments.Count > 0; + public bool IsBodyHtml { get; } - public NotificationEmailModel( - NotificationEmailAddress? from, - IEnumerable? to, - IEnumerable? cc, - IEnumerable? bcc, - IEnumerable? replyTo, - string? subject, - string? body, - IEnumerable? attachments, - bool isBodyHtml) - { - From = from; - To = to; - Cc = cc; - Bcc = bcc; - ReplyTo = replyTo; - Subject = subject; - Body = body; - IsBodyHtml = isBodyHtml; - Attachments = attachments?.ToList(); - } + public IList? Attachments { get; } - } + public bool HasAttachments => Attachments != null && Attachments.Count > 0; } diff --git a/src/Umbraco.Core/Models/Entities/BeingDirty.cs b/src/Umbraco.Core/Models/Entities/BeingDirty.cs index 7b078b35b880..0f40d9368c7c 100644 --- a/src/Umbraco.Core/Models/Entities/BeingDirty.cs +++ b/src/Umbraco.Core/Models/Entities/BeingDirty.cs @@ -1,36 +1,31 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models.Entities +/// +/// Provides a concrete implementation of . +/// +/// +/// +/// This class is provided for classes that cannot inherit from +/// and therefore need to implement , by re-using some of +/// logic. +/// +/// +public sealed class BeingDirty : BeingDirtyBase { /// - /// Provides a concrete implementation of . + /// Sets a property value, detects changes and manages the dirty flag. /// - /// - /// This class is provided for classes that cannot inherit from - /// and therefore need to implement , by re-using some of - /// logic. - /// - public sealed class BeingDirty : BeingDirtyBase - { - /// - /// Sets a property value, detects changes and manages the dirty flag. - /// - /// The type of the value. - /// The new value. - /// A reference to the value to set. - /// The property name. - /// A comparer to compare property values. - public new void SetPropertyValueAndDetectChanges(T value, ref T? valueRef, string propertyName, IEqualityComparer? comparer = null) - { - base.SetPropertyValueAndDetectChanges(value, ref valueRef, propertyName, comparer); - } + /// The type of the value. + /// The new value. + /// A reference to the value to set. + /// The property name. + /// A comparer to compare property values. + public new void SetPropertyValueAndDetectChanges(T value, ref T? valueRef, string propertyName, + IEqualityComparer? comparer = null) => + base.SetPropertyValueAndDetectChanges(value, ref valueRef, propertyName, comparer); - /// - /// Registers that a property has changed. - /// - public new void OnPropertyChanged(string propertyName) - { - base.OnPropertyChanged(propertyName); - } - } + /// + /// Registers that a property has changed. + /// + public new void OnPropertyChanged(string propertyName) => base.OnPropertyChanged(propertyName); } diff --git a/src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs b/src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs index c63ee54a6dbe..d453da5a245d 100644 --- a/src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs +++ b/src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs @@ -1,191 +1,175 @@ -using System; -using System.Collections; -using System.Collections.Generic; +using System.Collections; using System.ComponentModel; -using System.Linq; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.Entities +namespace Umbraco.Cms.Core.Models.Entities; + +/// +/// Provides a base implementation of and . +/// +[Serializable] +[DataContract(IsReference = true)] +public abstract class BeingDirtyBase : IRememberBeingDirty { - /// - /// Provides a base implementation of and . - /// - [Serializable] - [DataContract(IsReference = true)] - public abstract class BeingDirtyBase : IRememberBeingDirty - { - private bool _withChanges = true; // should we track changes? - private Dictionary? _currentChanges; // which properties have changed? - private Dictionary? _savedChanges; // which properties had changed at last commit? + private Dictionary? _currentChanges; // which properties have changed? + private Dictionary? _savedChanges; // which properties had changed at last commit? + private bool _withChanges = true; // should we track changes? - #region ICanBeDirty + #region ICanBeDirty - /// - public virtual bool IsDirty() - { - return _currentChanges != null && _currentChanges.Any(); - } + /// + public virtual bool IsDirty() => _currentChanges != null && _currentChanges.Any(); - /// - public virtual bool IsPropertyDirty(string propertyName) - { - return _currentChanges != null && _currentChanges.ContainsKey(propertyName); - } + /// + public virtual bool IsPropertyDirty(string propertyName) => + _currentChanges != null && _currentChanges.ContainsKey(propertyName); - /// - public virtual IEnumerable GetDirtyProperties() - { - // ReSharper disable once MergeConditionalExpression - return _currentChanges == null - ? Enumerable.Empty() - : _currentChanges.Where(x => x.Value).Select(x => x.Key); - } + /// + public virtual IEnumerable GetDirtyProperties() => + // ReSharper disable once MergeConditionalExpression + _currentChanges == null + ? Enumerable.Empty() + : _currentChanges.Where(x => x.Value).Select(x => x.Key); - /// - /// Saves dirty properties so they can be checked with WasDirty. - public virtual void ResetDirtyProperties() - { - ResetDirtyProperties(true); - } + /// + /// Saves dirty properties so they can be checked with WasDirty. + public virtual void ResetDirtyProperties() => ResetDirtyProperties(true); - #endregion + #endregion - #region IRememberBeingDirty + #region IRememberBeingDirty - /// - public virtual bool WasDirty() - { - return _savedChanges != null && _savedChanges.Any(); - } + /// + public virtual bool WasDirty() => _savedChanges != null && _savedChanges.Any(); - /// - public virtual bool WasPropertyDirty(string propertyName) - { - return _savedChanges != null && _savedChanges.ContainsKey(propertyName); - } + /// + public virtual bool WasPropertyDirty(string propertyName) => + _savedChanges != null && _savedChanges.ContainsKey(propertyName); - /// - public virtual void ResetWereDirtyProperties() - { - // note: cannot .Clear() because when memberwise-cloning this will be the SAME - // instance as the one on the clone, so we need to create a new instance. - _savedChanges = null; - } + /// + public virtual void ResetWereDirtyProperties() => + // note: cannot .Clear() because when memberwise-cloning this will be the SAME + // instance as the one on the clone, so we need to create a new instance. + _savedChanges = null; - /// - public virtual void ResetDirtyProperties(bool rememberDirty) - { - // capture changes if remembering - // clone the dictionary in case it's shared by an entity clone - _savedChanges = rememberDirty && _currentChanges != null - ? _currentChanges.ToDictionary(v => v.Key, v => v.Value) - : null; - - // note: cannot .Clear() because when memberwise-clone this will be the SAME - // instance as the one on the clone, so we need to create a new instance. - _currentChanges = null; - } + /// + public virtual void ResetDirtyProperties(bool rememberDirty) + { + // capture changes if remembering + // clone the dictionary in case it's shared by an entity clone + _savedChanges = rememberDirty && _currentChanges != null + ? _currentChanges.ToDictionary(v => v.Key, v => v.Value) + : null; + + // note: cannot .Clear() because when memberwise-clone this will be the SAME + // instance as the one on the clone, so we need to create a new instance. + _currentChanges = null; + } - /// - public virtual IEnumerable GetWereDirtyProperties() - { - // ReSharper disable once MergeConditionalExpression - return _savedChanges == null - ? Enumerable.Empty() - : _savedChanges.Where(x => x.Value).Select(x => x.Key); - } + /// + public virtual IEnumerable GetWereDirtyProperties() => + // ReSharper disable once MergeConditionalExpression + _savedChanges == null + ? Enumerable.Empty() + : _savedChanges.Where(x => x.Value).Select(x => x.Key); - #endregion + #endregion - #region Change Tracking + #region Change Tracking - /// - /// Occurs when a property changes. - /// - public event PropertyChangedEventHandler? PropertyChanged; + /// + /// Occurs when a property changes. + /// + public event PropertyChangedEventHandler? PropertyChanged; - /// - /// Registers that a property has changed. - /// - protected virtual void OnPropertyChanged(string propertyName) + /// + /// Registers that a property has changed. + /// + protected virtual void OnPropertyChanged(string propertyName) + { + if (_withChanges == false) { - if (_withChanges == false) - return; - - if (_currentChanges == null) - _currentChanges = new Dictionary(); - - _currentChanges[propertyName] = true; - - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + return; } - /// - /// Disables change tracking. - /// - public void DisableChangeTracking() + if (_currentChanges == null) { - _withChanges = false; + _currentChanges = new Dictionary(); } - /// - /// Enables change tracking. - /// - public void EnableChangeTracking() - { - _withChanges = true; - } + _currentChanges[propertyName] = true; + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + /// + /// Disables change tracking. + /// + public void DisableChangeTracking() => _withChanges = false; + + /// + /// Enables change tracking. + /// + public void EnableChangeTracking() => _withChanges = true; - /// - /// Sets a property value, detects changes and manages the dirty flag. - /// - /// The type of the value. - /// The new value. - /// A reference to the value to set. - /// The property name. - /// A comparer to compare property values. - protected void SetPropertyValueAndDetectChanges(T? value, ref T? valueRef, string propertyName, IEqualityComparer? comparer = null) + /// + /// Sets a property value, detects changes and manages the dirty flag. + /// + /// The type of the value. + /// The new value. + /// A reference to the value to set. + /// The property name. + /// A comparer to compare property values. + protected void SetPropertyValueAndDetectChanges(T? value, ref T? valueRef, string propertyName, + IEqualityComparer? comparer = null) + { + if (comparer == null) { - if (comparer == null) + // if no comparer is provided, use the default provider, as long as the value is not + // an IEnumerable - exclude strings, which are IEnumerable but have a default comparer + Type typeofT = typeof(T); + if (!(typeofT == typeof(string)) && typeof(IEnumerable).IsAssignableFrom(typeofT)) { - // if no comparer is provided, use the default provider, as long as the value is not - // an IEnumerable - exclude strings, which are IEnumerable but have a default comparer - var typeofT = typeof(T); - if (!(typeofT == typeof(string)) && typeof(IEnumerable).IsAssignableFrom(typeofT)) - throw new ArgumentNullException(nameof(comparer), "A custom comparer must be supplied for IEnumerable values."); - comparer = EqualityComparer.Default; + throw new ArgumentNullException(nameof(comparer), + "A custom comparer must be supplied for IEnumerable values."); } - // compare values - var changed = _withChanges && comparer.Equals(valueRef, value) == false; + comparer = EqualityComparer.Default; + } - // assign the new value - valueRef = value; + // compare values + var changed = _withChanges && comparer.Equals(valueRef, value) == false; - // handle change - if (changed) - OnPropertyChanged(propertyName); - } + // assign the new value + valueRef = value; - /// - /// Detects changes and manages the dirty flag. - /// - /// The type of the value. - /// The new value. - /// The original value. - /// The property name. - /// A comparer to compare property values. - /// A value indicating whether we know values have changed and no comparison is required. - protected void DetectChanges(T value, T orig, string propertyName, IEqualityComparer comparer, bool changed) + // handle change + if (changed) { - // compare values - changed = _withChanges && (changed || !comparer.Equals(orig, value)); - - // handle change - if (changed) - OnPropertyChanged(propertyName); + OnPropertyChanged(propertyName); } + } - #endregion + /// + /// Detects changes and manages the dirty flag. + /// + /// The type of the value. + /// The new value. + /// The original value. + /// The property name. + /// A comparer to compare property values. + /// A value indicating whether we know values have changed and no comparison is required. + protected void DetectChanges(T value, T orig, string propertyName, IEqualityComparer comparer, bool changed) + { + // compare values + changed = _withChanges && (changed || !comparer.Equals(orig, value)); + + // handle change + if (changed) + { + OnPropertyChanged(propertyName); + } } + + #endregion } diff --git a/src/Umbraco.Core/Models/Entities/ContentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/ContentEntitySlim.cs index 74bd4e4f44a1..9db19aedff7f 100644 --- a/src/Umbraco.Core/Models/Entities/ContentEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/ContentEntitySlim.cs @@ -1,17 +1,16 @@ -namespace Umbraco.Cms.Core.Models.Entities +namespace Umbraco.Cms.Core.Models.Entities; + +/// +/// Implements . +/// +public class ContentEntitySlim : EntitySlim, IContentEntitySlim { - /// - /// Implements . - /// - public class ContentEntitySlim : EntitySlim, IContentEntitySlim - { - /// - public string ContentTypeAlias { get; set; } = string.Empty; + /// + public string ContentTypeAlias { get; set; } = string.Empty; - /// - public string? ContentTypeIcon { get; set; } + /// + public string? ContentTypeIcon { get; set; } - /// - public string? ContentTypeThumbnail { get; set; } - } + /// + public string? ContentTypeThumbnail { get; set; } } diff --git a/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs index 3bc410fc9b9f..94f21d9bb164 100644 --- a/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs @@ -1,48 +1,42 @@ -using System.Collections.Generic; -using System.Linq; +namespace Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models.Entities +/// +/// Implements . +/// +public class DocumentEntitySlim : ContentEntitySlim, IDocumentEntitySlim { + private static readonly IReadOnlyDictionary Empty = new Dictionary(); - /// - /// Implements . - /// - public class DocumentEntitySlim : ContentEntitySlim, IDocumentEntitySlim - { - private static readonly IReadOnlyDictionary Empty = new Dictionary(); - - private IReadOnlyDictionary? _cultureNames; - private IEnumerable? _publishedCultures; - private IEnumerable? _editedCultures; - - /// - public IReadOnlyDictionary CultureNames - { - get => _cultureNames ?? Empty; - set => _cultureNames = value; - } + private IReadOnlyDictionary? _cultureNames; + private IEnumerable? _editedCultures; + private IEnumerable? _publishedCultures; - /// - public IEnumerable PublishedCultures - { - get => _publishedCultures ?? Enumerable.Empty(); - set => _publishedCultures = value; - } + /// + public IReadOnlyDictionary CultureNames + { + get => _cultureNames ?? Empty; + set => _cultureNames = value; + } - /// - public IEnumerable EditedCultures - { - get => _editedCultures ?? Enumerable.Empty(); - set => _editedCultures = value; - } + /// + public IEnumerable PublishedCultures + { + get => _publishedCultures ?? Enumerable.Empty(); + set => _publishedCultures = value; + } - public ContentVariation Variations { get; set; } + /// + public IEnumerable EditedCultures + { + get => _editedCultures ?? Enumerable.Empty(); + set => _editedCultures = value; + } - /// - public bool Published { get; set; } + public ContentVariation Variations { get; set; } - /// - public bool Edited { get; set; } + /// + public bool Published { get; set; } - } + /// + public bool Edited { get; set; } } diff --git a/src/Umbraco.Core/Models/Entities/EntityBase.cs b/src/Umbraco.Core/Models/Entities/EntityBase.cs index 57b9eeae1fe7..f57dd31933aa 100644 --- a/src/Umbraco.Core/Models/Entities/EntityBase.cs +++ b/src/Umbraco.Core/Models/Entities/EntityBase.cs @@ -1,155 +1,157 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.Entities +namespace Umbraco.Cms.Core.Models.Entities; + +/// +/// Provides a base class for entities. +/// +[Serializable] +[DataContract(IsReference = true)] +[DebuggerDisplay("Id: {" + nameof(Id) + "}")] +public abstract class EntityBase : BeingDirtyBase, IEntity { - /// - /// Provides a base class for entities. - /// - [Serializable] - [DataContract(IsReference = true)] - [DebuggerDisplay("Id: {" + nameof(Id) + "}")] - public abstract class EntityBase : BeingDirtyBase, IEntity - { #if DEBUG_MODEL public Guid InstanceId = Guid.NewGuid(); #endif - private bool _hasIdentity; - private int _id; - private Guid _key; - private DateTime _createDate; - private DateTime _updateDate; + private bool _hasIdentity; + private int _id; + private Guid _key; + private DateTime _createDate; + private DateTime _updateDate; - /// - [DataMember] - public int Id + /// + [DataMember] + public int Id + { + get => _id; + set { - get => _id; - set - { - SetPropertyValueAndDetectChanges(value, ref _id, nameof(Id)); - _hasIdentity = value != 0; - } + SetPropertyValueAndDetectChanges(value, ref _id, nameof(Id)); + _hasIdentity = value != 0; } + } - /// - [DataMember] - public Guid Key + /// + [DataMember] + public Guid Key + { + get { - get + // if an entity does NOT have a key yet, assign one now + if (_key == Guid.Empty) { - // if an entity does NOT have a key yet, assign one now - if (_key == Guid.Empty) - _key = Guid.NewGuid(); - return _key; + _key = Guid.NewGuid(); } - set => SetPropertyValueAndDetectChanges(value, ref _key, nameof(Key)); - } - /// - [DataMember] - public DateTime CreateDate - { - get => _createDate; - set => SetPropertyValueAndDetectChanges(value, ref _createDate, nameof(CreateDate)); + return _key; } + set => SetPropertyValueAndDetectChanges(value, ref _key, nameof(Key)); + } - /// - [DataMember] - public DateTime UpdateDate - { - get => _updateDate; - set => SetPropertyValueAndDetectChanges(value, ref _updateDate, nameof(UpdateDate)); - } + /// + [DataMember] + public DateTime CreateDate + { + get => _createDate; + set => SetPropertyValueAndDetectChanges(value, ref _createDate, nameof(CreateDate)); + } - /// - [DataMember] - public DateTime? DeleteDate { get; set; } // no change tracking - not persisted + /// + [DataMember] + public DateTime UpdateDate + { + get => _updateDate; + set => SetPropertyValueAndDetectChanges(value, ref _updateDate, nameof(UpdateDate)); + } - /// - [DataMember] - public virtual bool HasIdentity => _hasIdentity; + /// + [DataMember] + public DateTime? DeleteDate { get; set; } // no change tracking - not persisted - /// - /// Resets the entity identity. - /// - public virtual void ResetIdentity() - { - _id = default; - _key = Guid.Empty; - _hasIdentity = false; - } + /// + [DataMember] + public virtual bool HasIdentity => _hasIdentity; - public virtual bool Equals(EntityBase? other) - { - return other != null && (ReferenceEquals(this, other) || SameIdentityAs(other)); - } + /// + /// Resets the entity identity. + /// + public virtual void ResetIdentity() + { + _id = default; + _key = Guid.Empty; + _hasIdentity = false; + } - public override bool Equals(object? obj) - { - return obj != null && (ReferenceEquals(this, obj) || SameIdentityAs(obj as EntityBase)); - } + public virtual bool Equals(EntityBase? other) => + other != null && (ReferenceEquals(this, other) || SameIdentityAs(other)); - private bool SameIdentityAs(EntityBase? other) - { - if (other == null) return false; + public override bool Equals(object? obj) => + obj != null && (ReferenceEquals(this, obj) || SameIdentityAs(obj as EntityBase)); - // same identity if - // - same object (reference equals) - // - or same CLR type, both have identities, and they are identical + private bool SameIdentityAs(EntityBase? other) + { + if (other == null) + { + return false; + } - if (ReferenceEquals(this, other)) - return true; + // same identity if + // - same object (reference equals) + // - or same CLR type, both have identities, and they are identical - return GetType() == other.GetType() && HasIdentity && other.HasIdentity && Id == other.Id; + if (ReferenceEquals(this, other)) + { + return true; } - public override int GetHashCode() + return GetType() == other.GetType() && HasIdentity && other.HasIdentity && Id == other.Id; + } + + public override int GetHashCode() + { + unchecked { - unchecked - { - var hashCode = HasIdentity.GetHashCode(); - hashCode = (hashCode * 397) ^ Id; - hashCode = (hashCode * 397) ^ GetType().GetHashCode(); - return hashCode; - } + var hashCode = HasIdentity.GetHashCode(); + hashCode = (hashCode * 397) ^ Id; + hashCode = (hashCode * 397) ^ GetType().GetHashCode(); + return hashCode; } + } - public object DeepClone() - { - // memberwise-clone (ie shallow clone) the entity - var unused = Key; // ensure that 'this' has a key, before cloning - var clone = (EntityBase) MemberwiseClone(); + public object DeepClone() + { + // memberwise-clone (ie shallow clone) the entity + Guid unused = Key; // ensure that 'this' has a key, before cloning + var clone = (EntityBase)MemberwiseClone(); #if DEBUG_MODEL clone.InstanceId = Guid.NewGuid(); #endif - //disable change tracking while we deep clone IDeepCloneable properties - clone.DisableChangeTracking(); + //disable change tracking while we deep clone IDeepCloneable properties + clone.DisableChangeTracking(); - // deep clone ref properties that are IDeepCloneable - DeepCloneHelper.DeepCloneRefProperties(this, clone); + // deep clone ref properties that are IDeepCloneable + DeepCloneHelper.DeepCloneRefProperties(this, clone); - PerformDeepClone(clone); + PerformDeepClone(clone); - // clear changes (ensures the clone has its own dictionaries) - clone.ResetDirtyProperties(false); + // clear changes (ensures the clone has its own dictionaries) + clone.ResetDirtyProperties(false); - //re-enable change tracking - clone.EnableChangeTracking(); + //re-enable change tracking + clone.EnableChangeTracking(); - return clone; - } + return clone; + } - /// - /// Used by inheritors to modify the DeepCloning logic - /// - /// - protected virtual void PerformDeepClone(object clone) - { - } + /// + /// Used by inheritors to modify the DeepCloning logic + /// + /// + protected virtual void PerformDeepClone(object clone) + { } } diff --git a/src/Umbraco.Core/Models/Entities/EntityExtensions.cs b/src/Umbraco.Core/Models/Entities/EntityExtensions.cs index ba3421349d46..0c63e0786713 100644 --- a/src/Umbraco.Core/Models/Entities/EntityExtensions.cs +++ b/src/Umbraco.Core/Models/Entities/EntityExtensions.cs @@ -1,49 +1,49 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class EntityExtensions { - public static class EntityExtensions + /// + /// Updates the entity when it is being saved. + /// + public static void UpdatingEntity(this IEntity entity) { - /// - /// Updates the entity when it is being saved. - /// - public static void UpdatingEntity(this IEntity entity) + DateTime now = DateTime.Now; + + if (entity.CreateDate == default) + { + entity.CreateDate = now; + } + + // set the update date if not already set + if (entity.UpdateDate == default || + (entity is ICanBeDirty canBeDirty && canBeDirty.IsPropertyDirty("UpdateDate") == false)) + { + entity.UpdateDate = now; + } + } + + /// + /// Updates the entity when it is being saved for the first time. + /// + public static void AddingEntity(this IEntity entity) + { + DateTime now = DateTime.Now; + var canBeDirty = entity as ICanBeDirty; + + // set the create and update dates, if not already set + if (entity.CreateDate == default || canBeDirty?.IsPropertyDirty("CreateDate") == false) { - var now = DateTime.Now; - - if (entity.CreateDate == default) - { - entity.CreateDate = now; - } - - // set the update date if not already set - if (entity.UpdateDate == default || (entity is ICanBeDirty canBeDirty && canBeDirty.IsPropertyDirty("UpdateDate") == false)) - { - entity.UpdateDate = now; - } + entity.CreateDate = now; } - /// - /// Updates the entity when it is being saved for the first time. - /// - public static void AddingEntity(this IEntity entity) + if (entity.UpdateDate == default || canBeDirty?.IsPropertyDirty("UpdateDate") == false) { - var now = DateTime.Now; - var canBeDirty = entity as ICanBeDirty; - - // set the create and update dates, if not already set - if (entity.CreateDate == default || canBeDirty?.IsPropertyDirty("CreateDate") == false) - { - entity.CreateDate = now; - } - if (entity.UpdateDate == default || canBeDirty?.IsPropertyDirty("UpdateDate") == false) - { - entity.UpdateDate = now; - } + entity.UpdateDate = now; } } } diff --git a/src/Umbraco.Core/Models/Entities/EntitySlim.cs b/src/Umbraco.Core/Models/Entities/EntitySlim.cs index c4bc473661d3..59cb3214e3ca 100644 --- a/src/Umbraco.Core/Models/Entities/EntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/EntitySlim.cs @@ -1,181 +1,158 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace Umbraco.Cms.Core.Models.Entities +using System.Runtime.Serialization; + +namespace Umbraco.Cms.Core.Models.Entities; + +/// +/// Implementation of for internal use. +/// +/// +/// +/// Although it implements , this class does not +/// implement and everything this interface defines, throws. +/// +/// +/// Although it implements , this class does not +/// implement and deep-cloning throws. +/// +/// +public class EntitySlim : IEntitySlim { /// - /// Implementation of for internal use. + /// Gets an entity representing "root". /// - /// - /// Although it implements , this class does not - /// implement and everything this interface defines, throws. - /// Although it implements , this class does not - /// implement and deep-cloning throws. - /// - public class EntitySlim : IEntitySlim - { - private IDictionary? _additionalData; - - /// - /// Gets an entity representing "root". - /// - public static readonly IEntitySlim Root = new EntitySlim { Path = "-1", Name = "root", HasChildren = true }; + public static readonly IEntitySlim Root = new EntitySlim {Path = "-1", Name = "root", HasChildren = true}; - // implement IEntity + private IDictionary? _additionalData; - /// - [DataMember] - public int Id { get; set; } + // implement IEntity - /// - [DataMember] - public Guid Key { get; set; } + /// + [DataMember] + public int Id { get; set; } - /// - [DataMember] - public DateTime CreateDate { get; set; } + /// + [DataMember] + public Guid Key { get; set; } - /// - [DataMember] - public DateTime UpdateDate { get; set; } + /// + [DataMember] + public DateTime CreateDate { get; set; } - /// - [DataMember] - public DateTime? DeleteDate { get; set; } + /// + [DataMember] + public DateTime UpdateDate { get; set; } - /// - [DataMember] - public bool HasIdentity => Id != 0; + /// + [DataMember] + public DateTime? DeleteDate { get; set; } + /// + [DataMember] + public bool HasIdentity => Id != 0; - // implement ITreeEntity - /// - [DataMember] - public string? Name { get; set; } + // implement ITreeEntity - /// - [DataMember] - public int CreatorId { get; set; } + /// + [DataMember] + public string? Name { get; set; } - /// - [DataMember] - public int ParentId { get; set; } + /// + [DataMember] + public int CreatorId { get; set; } - /// - public void SetParent(ITreeEntity? parent) => throw new InvalidOperationException("This property won't be implemented."); + /// + [DataMember] + public int ParentId { get; set; } - /// - [DataMember] - public int Level { get; set; } + /// + public void SetParent(ITreeEntity? parent) => + throw new InvalidOperationException("This property won't be implemented."); - /// - [DataMember] - public string Path { get; set; } = string.Empty; + /// + [DataMember] + public int Level { get; set; } - /// - [DataMember] - public int SortOrder { get; set; } + /// + [DataMember] + public string Path { get; set; } = string.Empty; - /// - [DataMember] - public bool Trashed { get; set; } + /// + [DataMember] + public int SortOrder { get; set; } + /// + [DataMember] + public bool Trashed { get; set; } - // implement IUmbracoEntity - /// - [DataMember] - public IDictionary? AdditionalData => _additionalData ?? (_additionalData = new Dictionary()); + // implement IUmbracoEntity - /// - [IgnoreDataMember] - public bool HasAdditionalData => _additionalData != null; + /// + [DataMember] + public IDictionary? AdditionalData => + _additionalData ?? (_additionalData = new Dictionary()); + /// + [IgnoreDataMember] + public bool HasAdditionalData => _additionalData != null; - // implement IEntitySlim - /// - [DataMember] - public Guid NodeObjectType { get; set; } + // implement IEntitySlim - /// - [DataMember] - public bool HasChildren { get; set; } + /// + [DataMember] + public Guid NodeObjectType { get; set; } - /// - [DataMember] - public virtual bool IsContainer { get; set; } + /// + [DataMember] + public bool HasChildren { get; set; } + /// + [DataMember] + public virtual bool IsContainer { get; set; } - #region IDeepCloneable - /// - public object DeepClone() - { - throw new InvalidOperationException("This method won't be implemented."); - } + #region IDeepCloneable - #endregion + /// + public object DeepClone() => throw new InvalidOperationException("This method won't be implemented."); - public void ResetIdentity() - { - Id = default; - Key = Guid.Empty; - } + #endregion - #region IRememberBeingDirty + public void ResetIdentity() + { + Id = default; + Key = Guid.Empty; + } - // IEntitySlim does *not* track changes, but since it indirectly implements IUmbracoEntity, - // and therefore IRememberBeingDirty, we have to have those methods - which all throw. + #region IRememberBeingDirty - public bool IsDirty() - { - throw new InvalidOperationException("This method won't be implemented."); - } + // IEntitySlim does *not* track changes, but since it indirectly implements IUmbracoEntity, + // and therefore IRememberBeingDirty, we have to have those methods - which all throw. - public bool IsPropertyDirty(string propName) - { - throw new InvalidOperationException("This method won't be implemented."); - } + public bool IsDirty() => throw new InvalidOperationException("This method won't be implemented."); - public IEnumerable GetDirtyProperties() - { - throw new InvalidOperationException("This method won't be implemented."); - } + public bool IsPropertyDirty(string propName) => + throw new InvalidOperationException("This method won't be implemented."); - public void ResetDirtyProperties() - { - throw new InvalidOperationException("This method won't be implemented."); - } + public IEnumerable GetDirtyProperties() => + throw new InvalidOperationException("This method won't be implemented."); - public bool WasDirty() - { - throw new InvalidOperationException("This method won't be implemented."); - } + public void ResetDirtyProperties() => throw new InvalidOperationException("This method won't be implemented."); - public bool WasPropertyDirty(string propertyName) - { - throw new InvalidOperationException("This method won't be implemented."); - } + public bool WasDirty() => throw new InvalidOperationException("This method won't be implemented."); - public void ResetWereDirtyProperties() - { - throw new InvalidOperationException("This method won't be implemented."); - } + public bool WasPropertyDirty(string propertyName) => + throw new InvalidOperationException("This method won't be implemented."); - public void ResetDirtyProperties(bool rememberDirty) - { - throw new InvalidOperationException("This method won't be implemented."); - } + public void ResetWereDirtyProperties() => throw new InvalidOperationException("This method won't be implemented."); - public IEnumerable GetWereDirtyProperties() - { - throw new InvalidOperationException("This method won't be implemented."); - } + public void ResetDirtyProperties(bool rememberDirty) => + throw new InvalidOperationException("This method won't be implemented."); - #endregion + public IEnumerable GetWereDirtyProperties() => + throw new InvalidOperationException("This method won't be implemented."); - } + #endregion } diff --git a/src/Umbraco.Core/Models/Entities/ICanBeDirty.cs b/src/Umbraco.Core/Models/Entities/ICanBeDirty.cs index d8644431d526..669305c3c957 100644 --- a/src/Umbraco.Core/Models/Entities/ICanBeDirty.cs +++ b/src/Umbraco.Core/Models/Entities/ICanBeDirty.cs @@ -1,43 +1,41 @@ -using System.Collections.Generic; -using System.ComponentModel; +using System.ComponentModel; -namespace Umbraco.Cms.Core.Models.Entities +namespace Umbraco.Cms.Core.Models.Entities; + +/// +/// Defines an entity that tracks property changes and can be dirty. +/// +public interface ICanBeDirty { /// - /// Defines an entity that tracks property changes and can be dirty. + /// Determines whether the current entity is dirty. /// - public interface ICanBeDirty - { - /// - /// Determines whether the current entity is dirty. - /// - bool IsDirty(); - - /// - /// Determines whether a specific property is dirty. - /// - bool IsPropertyDirty(string propName); - - /// - /// Gets properties that are dirty. - /// - IEnumerable GetDirtyProperties(); - - /// - /// Resets dirty properties. - /// - void ResetDirtyProperties(); - - /// - /// Disables change tracking. - /// - void DisableChangeTracking(); - - /// - /// Enables change tracking. - /// - void EnableChangeTracking(); - - event PropertyChangedEventHandler PropertyChanged; - } + bool IsDirty(); + + /// + /// Determines whether a specific property is dirty. + /// + bool IsPropertyDirty(string propName); + + /// + /// Gets properties that are dirty. + /// + IEnumerable GetDirtyProperties(); + + /// + /// Resets dirty properties. + /// + void ResetDirtyProperties(); + + /// + /// Disables change tracking. + /// + void DisableChangeTracking(); + + /// + /// Enables change tracking. + /// + void EnableChangeTracking(); + + event PropertyChangedEventHandler PropertyChanged; } diff --git a/src/Umbraco.Core/Models/Entities/IContentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IContentEntitySlim.cs index 52ea701af3a6..72ad85c35e3d 100644 --- a/src/Umbraco.Core/Models/Entities/IContentEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/IContentEntitySlim.cs @@ -1,23 +1,22 @@ -namespace Umbraco.Cms.Core.Models.Entities +namespace Umbraco.Cms.Core.Models.Entities; + +/// +/// Represents a lightweight content entity, managed by the entity service. +/// +public interface IContentEntitySlim : IEntitySlim { /// - /// Represents a lightweight content entity, managed by the entity service. + /// Gets the content type alias. /// - public interface IContentEntitySlim : IEntitySlim - { - /// - /// Gets the content type alias. - /// - string ContentTypeAlias { get; } + string ContentTypeAlias { get; } - /// - /// Gets the content type icon. - /// - string? ContentTypeIcon { get; } + /// + /// Gets the content type icon. + /// + string? ContentTypeIcon { get; } - /// - /// Gets the content type thumbnail. - /// - string? ContentTypeThumbnail { get; } - } + /// + /// Gets the content type thumbnail. + /// + string? ContentTypeThumbnail { get; } } diff --git a/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs index d160e144bb7e..6772454c3acf 100644 --- a/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs @@ -1,42 +1,37 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models.Entities +/// +/// Represents a lightweight document entity, managed by the entity service. +/// +public interface IDocumentEntitySlim : IContentEntitySlim { - /// - /// Represents a lightweight document entity, managed by the entity service. + /// Gets the variant name for each culture /// - public interface IDocumentEntitySlim : IContentEntitySlim - { - /// - /// Gets the variant name for each culture - /// - IReadOnlyDictionary CultureNames { get; } - - /// - /// Gets the published cultures. - /// - IEnumerable PublishedCultures { get; } + IReadOnlyDictionary CultureNames { get; } - /// - /// Gets the edited cultures. - /// - IEnumerable EditedCultures { get; } + /// + /// Gets the published cultures. + /// + IEnumerable PublishedCultures { get; } - /// - /// Gets the content variation of the content type. - /// - ContentVariation Variations { get; } + /// + /// Gets the edited cultures. + /// + IEnumerable EditedCultures { get; } - /// - /// Gets a value indicating whether the content is published. - /// - bool Published { get; } + /// + /// Gets the content variation of the content type. + /// + ContentVariation Variations { get; } - /// - /// Gets a value indicating whether the content has been edited. - /// - bool Edited { get; } + /// + /// Gets a value indicating whether the content is published. + /// + bool Published { get; } - } + /// + /// Gets a value indicating whether the content has been edited. + /// + bool Edited { get; } } diff --git a/src/Umbraco.Core/Models/Entities/IEntity.cs b/src/Umbraco.Core/Models/Entities/IEntity.cs index 6aeea5855392..2118736b0163 100644 --- a/src/Umbraco.Core/Models/Entities/IEntity.cs +++ b/src/Umbraco.Core/Models/Entities/IEntity.cs @@ -1,47 +1,46 @@ -using System; +namespace Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models.Entities +/// +/// Defines an entity. +/// +public interface IEntity : IDeepCloneable { /// - /// Defines an entity. + /// Gets or sets the integer identifier of the entity. /// - public interface IEntity : IDeepCloneable - { - /// - /// Gets or sets the integer identifier of the entity. - /// - int Id { get; set; } + int Id { get; set; } - /// - /// Gets or sets the Guid unique identifier of the entity. - /// - Guid Key { get; set; } + /// + /// Gets or sets the Guid unique identifier of the entity. + /// + Guid Key { get; set; } - /// - /// Gets or sets the creation date. - /// - DateTime CreateDate { get; set; } + /// + /// Gets or sets the creation date. + /// + DateTime CreateDate { get; set; } - /// - /// Gets or sets the last update date. - /// - DateTime UpdateDate { get; set; } + /// + /// Gets or sets the last update date. + /// + DateTime UpdateDate { get; set; } - /// - /// Gets or sets the delete date. - /// - /// - /// The delete date is null when the entity has not been deleted. - /// The delete date has a value when the entity instance has been deleted, but this value - /// is transient and not persisted in database (since the entity does not exist anymore). - /// - DateTime? DeleteDate { get; set; } + /// + /// Gets or sets the delete date. + /// + /// + /// The delete date is null when the entity has not been deleted. + /// + /// The delete date has a value when the entity instance has been deleted, but this value + /// is transient and not persisted in database (since the entity does not exist anymore). + /// + /// + DateTime? DeleteDate { get; set; } - /// - /// Gets a value indicating whether the entity has an identity. - /// - bool HasIdentity { get; } + /// + /// Gets a value indicating whether the entity has an identity. + /// + bool HasIdentity { get; } - void ResetIdentity(); - } + void ResetIdentity(); } diff --git a/src/Umbraco.Core/Models/Entities/IEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IEntitySlim.cs index dfdb00edaa7d..67aefdc55705 100644 --- a/src/Umbraco.Core/Models/Entities/IEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/IEntitySlim.cs @@ -1,25 +1,22 @@ -using System; +namespace Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models.Entities +/// +/// Represents a lightweight entity, managed by the entity service. +/// +public interface IEntitySlim : IUmbracoEntity, IHaveAdditionalData { /// - /// Represents a lightweight entity, managed by the entity service. + /// Gets or sets the entity object type. /// - public interface IEntitySlim : IUmbracoEntity, IHaveAdditionalData - { - /// - /// Gets or sets the entity object type. - /// - Guid NodeObjectType { get; } + Guid NodeObjectType { get; } - /// - /// Gets or sets a value indicating whether the entity has children. - /// - bool HasChildren { get; } + /// + /// Gets or sets a value indicating whether the entity has children. + /// + bool HasChildren { get; } - /// - /// Gets a value indicating whether the entity is a container. - /// - bool IsContainer { get; } - } + /// + /// Gets a value indicating whether the entity is a container. + /// + bool IsContainer { get; } } diff --git a/src/Umbraco.Core/Models/Entities/IHaveAdditionalData.cs b/src/Umbraco.Core/Models/Entities/IHaveAdditionalData.cs index 651e6a5f7a93..06ee6c675a59 100644 --- a/src/Umbraco.Core/Models/Entities/IHaveAdditionalData.cs +++ b/src/Umbraco.Core/Models/Entities/IHaveAdditionalData.cs @@ -1,42 +1,43 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models.Entities +/// +/// Provides support for additional data. +/// +/// +/// Additional data are transient, not deep-cloned. +/// +public interface IHaveAdditionalData { /// - /// Provides support for additional data. + /// Gets additional data for this entity. /// /// - /// Additional data are transient, not deep-cloned. + /// Can be empty, but never null. To avoid allocating, do not + /// test for emptiness, but use instead. /// - public interface IHaveAdditionalData - { - /// - /// Gets additional data for this entity. - /// - /// Can be empty, but never null. To avoid allocating, do not - /// test for emptiness, but use instead. - IDictionary? AdditionalData { get; } + IDictionary? AdditionalData { get; } - /// - /// Determines whether this entity has additional data. - /// - /// Use this property to check for additional data without - /// getting , to avoid allocating. - bool HasAdditionalData { get; } + /// + /// Determines whether this entity has additional data. + /// + /// + /// Use this property to check for additional data without + /// getting , to avoid allocating. + /// + bool HasAdditionalData { get; } - // how to implement: + // how to implement: - /* - private IDictionary _additionalData; + /* + private IDictionary _additionalData; - /// - [DataMember] - [DoNotClone] - PublicAccessEntry IDictionary AdditionalData => _additionalData ?? (_additionalData = new Dictionary()); + /// + [DataMember] + [DoNotClone] + PublicAccessEntry IDictionary AdditionalData => _additionalData ?? (_additionalData = new Dictionary()); - /// - [IgnoreDataMember] - PublicAccessEntry bool HasAdditionalData => _additionalData != null; - */ - } + /// + [IgnoreDataMember] + PublicAccessEntry bool HasAdditionalData => _additionalData != null; + */ } diff --git a/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs index 3a2996c6fee2..beb27ba6642a 100644 --- a/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs @@ -1,14 +1,12 @@ -namespace Umbraco.Cms.Core.Models.Entities +namespace Umbraco.Cms.Core.Models.Entities; + +/// +/// Represents a lightweight media entity, managed by the entity service. +/// +public interface IMediaEntitySlim : IContentEntitySlim { /// - /// Represents a lightweight media entity, managed by the entity service. + /// The media file's path/URL /// - public interface IMediaEntitySlim : IContentEntitySlim - { - - /// - /// The media file's path/URL - /// - string? MediaPath { get; } - } + string? MediaPath { get; } } diff --git a/src/Umbraco.Core/Models/Entities/IMemberEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IMemberEntitySlim.cs index a43607fda77c..b397997d6733 100644 --- a/src/Umbraco.Core/Models/Entities/IMemberEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/IMemberEntitySlim.cs @@ -1,7 +1,5 @@ -namespace Umbraco.Cms.Core.Models.Entities -{ - public interface IMemberEntitySlim : IContentEntitySlim - { +namespace Umbraco.Cms.Core.Models.Entities; - } +public interface IMemberEntitySlim : IContentEntitySlim +{ } diff --git a/src/Umbraco.Core/Models/Entities/IRememberBeingDirty.cs b/src/Umbraco.Core/Models/Entities/IRememberBeingDirty.cs index 618bab2698d9..da9a4a2d2b15 100644 --- a/src/Umbraco.Core/Models/Entities/IRememberBeingDirty.cs +++ b/src/Umbraco.Core/Models/Entities/IRememberBeingDirty.cs @@ -1,40 +1,40 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models.Entities +/// +/// Defines an entity that tracks property changes and can be dirty, and remembers +/// which properties were dirty when the changes were committed. +/// +public interface IRememberBeingDirty : ICanBeDirty { /// - /// Defines an entity that tracks property changes and can be dirty, and remembers - /// which properties were dirty when the changes were committed. + /// Determines whether the current entity is dirty. /// - public interface IRememberBeingDirty : ICanBeDirty - { - /// - /// Determines whether the current entity is dirty. - /// - /// A property was dirty if it had been changed and the changes were committed. - bool WasDirty(); + /// A property was dirty if it had been changed and the changes were committed. + bool WasDirty(); - /// - /// Determines whether a specific property was dirty. - /// - /// A property was dirty if it had been changed and the changes were committed. - bool WasPropertyDirty(string propertyName); + /// + /// Determines whether a specific property was dirty. + /// + /// A property was dirty if it had been changed and the changes were committed. + bool WasPropertyDirty(string propertyName); - /// - /// Resets properties that were dirty. - /// - void ResetWereDirtyProperties(); + /// + /// Resets properties that were dirty. + /// + void ResetWereDirtyProperties(); - /// - /// Resets dirty properties. - /// - /// A value indicating whether to remember dirty properties. - /// When is true, dirty properties are saved so they can be checked with WasDirty. - void ResetDirtyProperties(bool rememberDirty); + /// + /// Resets dirty properties. + /// + /// A value indicating whether to remember dirty properties. + /// + /// When is true, dirty properties are saved so they can be checked with + /// WasDirty. + /// + void ResetDirtyProperties(bool rememberDirty); - /// - /// Gets properties that were dirty. - /// - IEnumerable GetWereDirtyProperties(); - } + /// + /// Gets properties that were dirty. + /// + IEnumerable GetWereDirtyProperties(); } diff --git a/src/Umbraco.Core/Models/Entities/ITreeEntity.cs b/src/Umbraco.Core/Models/Entities/ITreeEntity.cs index af105d63ff7f..e6f2a5c4418c 100644 --- a/src/Umbraco.Core/Models/Entities/ITreeEntity.cs +++ b/src/Umbraco.Core/Models/Entities/ITreeEntity.cs @@ -1,56 +1,57 @@ -namespace Umbraco.Cms.Core.Models.Entities +namespace Umbraco.Cms.Core.Models.Entities; + +/// +/// Defines an entity that belongs to a tree. +/// +public interface ITreeEntity : IEntity { /// - /// Defines an entity that belongs to a tree. - /// - public interface ITreeEntity : IEntity - { - /// - /// Gets or sets the name of the entity. - /// - string? Name { get; set; } - - /// - /// Gets or sets the identifier of the user who created this entity. - /// - int CreatorId { get; set; } - - /// - /// Gets or sets the identifier of the parent entity. - /// - int ParentId { get; set; } - - /// - /// Sets the parent entity. - /// - /// Use this method to set the parent entity when the parent entity is known, but has not - /// been persistent and does not yet have an identity. The parent identifier will be retrieved - /// from the parent entity when needed. If the parent entity still does not have an entity by that - /// time, an exception will be thrown by getter. - void SetParent(ITreeEntity? parent); - - /// - /// Gets or sets the level of the entity. - /// - int Level { get; set; } - - /// - /// Gets or sets the path to the entity. - /// - string Path { get; set; } - - /// - /// Gets or sets the sort order of the entity. - /// - int SortOrder { get; set; } - - /// - /// Gets a value indicating whether this entity is trashed. - /// - /// - /// Trashed entities are located in the recycle bin. - /// Always false for entities that do not support being trashed. - /// - bool Trashed { get; } - } + /// Gets or sets the name of the entity. + /// + string? Name { get; set; } + + /// + /// Gets or sets the identifier of the user who created this entity. + /// + int CreatorId { get; set; } + + /// + /// Gets or sets the identifier of the parent entity. + /// + int ParentId { get; set; } + + /// + /// Gets or sets the level of the entity. + /// + int Level { get; set; } + + /// + /// Gets or sets the path to the entity. + /// + string Path { get; set; } + + /// + /// Gets or sets the sort order of the entity. + /// + int SortOrder { get; set; } + + /// + /// Gets a value indicating whether this entity is trashed. + /// + /// + /// Trashed entities are located in the recycle bin. + /// Always false for entities that do not support being trashed. + /// + bool Trashed { get; } + + /// + /// Sets the parent entity. + /// + /// + /// Use this method to set the parent entity when the parent entity is known, but has not + /// been persistent and does not yet have an identity. The parent identifier will be retrieved + /// from the parent entity when needed. If the parent entity still does not have an entity by that + /// time, an exception will be thrown by getter. + /// + void SetParent(ITreeEntity? parent); } diff --git a/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs b/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs index d89e5d93125b..a1a54fa84415 100644 --- a/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs +++ b/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs @@ -1,14 +1,13 @@ -namespace Umbraco.Cms.Core.Models.Entities -{ +namespace Umbraco.Cms.Core.Models.Entities; - /// - /// Represents an entity that can be managed by the entity service. - /// - /// - /// An IUmbracoEntity can be related to another via the IRelationService. - /// IUmbracoEntities can be retrieved with the IEntityService. - /// An IUmbracoEntity can participate in notifications. - /// - public interface IUmbracoEntity : ITreeEntity - { } +/// +/// Represents an entity that can be managed by the entity service. +/// +/// +/// An IUmbracoEntity can be related to another via the IRelationService. +/// IUmbracoEntities can be retrieved with the IEntityService. +/// An IUmbracoEntity can participate in notifications. +/// +public interface IUmbracoEntity : ITreeEntity +{ } diff --git a/src/Umbraco.Core/Models/Entities/IValueObject.cs b/src/Umbraco.Core/Models/Entities/IValueObject.cs index e1b7ea01a628..8f1371ff07bc 100644 --- a/src/Umbraco.Core/Models/Entities/IValueObject.cs +++ b/src/Umbraco.Core/Models/Entities/IValueObject.cs @@ -1,11 +1,9 @@ -namespace Umbraco.Cms.Core.Models.Entities -{ - /// - /// Marker interface for value object, eg. objects without - /// the same kind of identity as an Entity (with its Id). - /// - public interface IValueObject - { +namespace Umbraco.Cms.Core.Models.Entities; - } +/// +/// Marker interface for value object, eg. objects without +/// the same kind of identity as an Entity (with its Id). +/// +public interface IValueObject +{ } diff --git a/src/Umbraco.Core/Models/Entities/MediaEntitySlim.cs b/src/Umbraco.Core/Models/Entities/MediaEntitySlim.cs index fd3c01e15c42..f2b72dae6511 100644 --- a/src/Umbraco.Core/Models/Entities/MediaEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/MediaEntitySlim.cs @@ -1,10 +1,9 @@ -namespace Umbraco.Cms.Core.Models.Entities +namespace Umbraco.Cms.Core.Models.Entities; + +/// +/// Implements . +/// +public class MediaEntitySlim : ContentEntitySlim, IMediaEntitySlim { - /// - /// Implements . - /// - public class MediaEntitySlim : ContentEntitySlim, IMediaEntitySlim - { - public string? MediaPath { get; set; } - } + public string? MediaPath { get; set; } } diff --git a/src/Umbraco.Core/Models/Entities/MemberEntitySlim.cs b/src/Umbraco.Core/Models/Entities/MemberEntitySlim.cs index 66e3650fc51b..00639405c068 100644 --- a/src/Umbraco.Core/Models/Entities/MemberEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/MemberEntitySlim.cs @@ -1,6 +1,5 @@ -namespace Umbraco.Cms.Core.Models.Entities +namespace Umbraco.Cms.Core.Models.Entities; + +public class MemberEntitySlim : ContentEntitySlim, IMemberEntitySlim { - public class MemberEntitySlim : ContentEntitySlim, IMemberEntitySlim - { - } } diff --git a/src/Umbraco.Core/Models/Entities/TreeEntityBase.cs b/src/Umbraco.Core/Models/Entities/TreeEntityBase.cs index b5d6f40a4c9f..06f1683eb250 100644 --- a/src/Umbraco.Core/Models/Entities/TreeEntityBase.cs +++ b/src/Umbraco.Core/Models/Entities/TreeEntityBase.cs @@ -1,106 +1,119 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.Entities +namespace Umbraco.Cms.Core.Models.Entities; + +/// +/// Provides a base class for tree entities. +/// +public abstract class TreeEntityBase : EntityBase, ITreeEntity { - /// - /// Provides a base class for tree entities. - /// - public abstract class TreeEntityBase : EntityBase, ITreeEntity - { - private string _name = null!; - private int _creatorId; - private int _parentId; - private bool _hasParentId; - private ITreeEntity? _parent; - private int _level; - private string _path = String.Empty; - private int _sortOrder; - private bool _trashed; + private int _creatorId; + private bool _hasParentId; + private int _level; + private string _name = null!; + private ITreeEntity? _parent; + private int _parentId; + private string _path = string.Empty; + private int _sortOrder; + private bool _trashed; - /// - [DataMember] - public string? Name - { - get => _name; - set => SetPropertyValueAndDetectChanges(value, ref _name!, nameof(Name)); - } + /// + [DataMember] + public string? Name + { + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name!, nameof(Name)); + } - /// - [DataMember] - public int CreatorId - { - get => _creatorId; - set => SetPropertyValueAndDetectChanges(value, ref _creatorId, nameof(CreatorId)); - } + /// + [DataMember] + public int CreatorId + { + get => _creatorId; + set => SetPropertyValueAndDetectChanges(value, ref _creatorId, nameof(CreatorId)); + } - /// - [DataMember] - public int ParentId + /// + [DataMember] + public int ParentId + { + get { - get + if (_hasParentId) { - if (_hasParentId) return _parentId; - - if (_parent == null) throw new InvalidOperationException("Content does not have a parent."); - if (!_parent.HasIdentity) throw new InvalidOperationException("Content's parent does not have an identity."); + return _parentId; + } - _parentId = _parent.Id; - if (_parentId == 0) - throw new Exception("Panic: parent has an identity but id is zero."); + if (_parent == null) + { + throw new InvalidOperationException("Content does not have a parent."); + } - _hasParentId = true; - _parent = null; - return _parentId; + if (!_parent.HasIdentity) + { + throw new InvalidOperationException("Content's parent does not have an identity."); } - set + + _parentId = _parent.Id; + if (_parentId == 0) { - if (value == 0) - throw new ArgumentException("Value cannot be zero.", nameof(value)); - SetPropertyValueAndDetectChanges(value, ref _parentId, nameof(ParentId)); - _hasParentId = true; - _parent = null; + throw new Exception("Panic: parent has an identity but id is zero."); } - } - /// - public void SetParent(ITreeEntity? parent) - { - _hasParentId = false; - _parent = parent; - OnPropertyChanged(nameof(ParentId)); + _hasParentId = true; + _parent = null; + return _parentId; } - - /// - [DataMember] - public int Level + set { - get => _level; - set => SetPropertyValueAndDetectChanges(value, ref _level, nameof(Level)); - } + if (value == 0) + { + throw new ArgumentException("Value cannot be zero.", nameof(value)); + } - /// - [DataMember] - public string Path - { - get => _path; - set => SetPropertyValueAndDetectChanges(value, ref _path!, nameof(Path)); + SetPropertyValueAndDetectChanges(value, ref _parentId, nameof(ParentId)); + _hasParentId = true; + _parent = null; } + } - /// - [DataMember] - public int SortOrder - { - get => _sortOrder; - set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, nameof(SortOrder)); - } + /// + public void SetParent(ITreeEntity? parent) + { + _hasParentId = false; + _parent = parent; + OnPropertyChanged(nameof(ParentId)); + } - /// - [DataMember] - public bool Trashed - { - get => _trashed; - set => SetPropertyValueAndDetectChanges(value, ref _trashed, nameof(Trashed)); - } + /// + [DataMember] + public int Level + { + get => _level; + set => SetPropertyValueAndDetectChanges(value, ref _level, nameof(Level)); + } + + /// + [DataMember] + public string Path + { + get => _path; + set => SetPropertyValueAndDetectChanges(value, ref _path!, nameof(Path)); + } + + /// + [DataMember] + public int SortOrder + { + get => _sortOrder; + set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, nameof(SortOrder)); + } + + /// + [DataMember] + public bool Trashed + { + get => _trashed; + set => SetPropertyValueAndDetectChanges(value, ref _trashed, nameof(Trashed)); } } diff --git a/src/Umbraco.Core/Models/Entities/TreeEntityPath.cs b/src/Umbraco.Core/Models/Entities/TreeEntityPath.cs index 6fd147ace769..018a865a7674 100644 --- a/src/Umbraco.Core/Models/Entities/TreeEntityPath.cs +++ b/src/Umbraco.Core/Models/Entities/TreeEntityPath.cs @@ -1,18 +1,17 @@ -namespace Umbraco.Cms.Core.Models.Entities +namespace Umbraco.Cms.Core.Models.Entities; + +/// +/// Represents the path of a tree entity. +/// +public class TreeEntityPath { /// - /// Represents the path of a tree entity. + /// Gets or sets the identifier of the entity. /// - public class TreeEntityPath - { - /// - /// Gets or sets the identifier of the entity. - /// - public int Id { get; set; } + public int Id { get; set; } - /// - /// Gets or sets the path of the entity. - /// - public string Path { get; set; } = null!; - } + /// + /// Gets or sets the path of the entity. + /// + public string Path { get; set; } = null!; } diff --git a/src/Umbraco.Core/Models/EntityContainer.cs b/src/Umbraco.Core/Models/EntityContainer.cs index 114d78605c24..76f5733a3899 100644 --- a/src/Umbraco.Core/Models/EntityContainer.cs +++ b/src/Umbraco.Core/Models/EntityContainer.cs @@ -1,88 +1,92 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a folder for organizing entities such as content types and data types. +/// +public sealed class EntityContainer : TreeEntityBase, IUmbracoEntity { + private static readonly Dictionary ObjectTypeMap = new() + { + {Constants.ObjectTypes.DataType, Constants.ObjectTypes.DataTypeContainer}, + {Constants.ObjectTypes.DocumentType, Constants.ObjectTypes.DocumentTypeContainer}, + {Constants.ObjectTypes.MediaType, Constants.ObjectTypes.MediaTypeContainer} + }; + /// - /// Represents a folder for organizing entities such as content types and data types. + /// Initializes a new instance of an class. /// - public sealed class EntityContainer : TreeEntityBase, IUmbracoEntity + public EntityContainer(Guid containedObjectType) { - private readonly Guid _containedObjectType; - - private static readonly Dictionary ObjectTypeMap = new Dictionary + if (ObjectTypeMap.ContainsKey(containedObjectType) == false) { - { Constants.ObjectTypes.DataType, Constants.ObjectTypes.DataTypeContainer }, - { Constants.ObjectTypes.DocumentType, Constants.ObjectTypes.DocumentTypeContainer }, - { Constants.ObjectTypes.MediaType, Constants.ObjectTypes.MediaTypeContainer } - }; + throw new ArgumentException("Not a contained object type.", nameof(containedObjectType)); + } - /// - /// Initializes a new instance of an class. - /// - public EntityContainer(Guid containedObjectType) - { - if (ObjectTypeMap.ContainsKey(containedObjectType) == false) - throw new ArgumentException("Not a contained object type.", nameof(containedObjectType)); - _containedObjectType = containedObjectType; + ContainedObjectType = containedObjectType; - ParentId = -1; - Path = "-1"; - Level = 0; - SortOrder = 0; - } + ParentId = -1; + Path = "-1"; + Level = 0; + SortOrder = 0; + } - /// - /// Initializes a new instance of an class. - /// - public EntityContainer(int id, Guid uniqueId, int parentId, string path, int level, int sortOrder, Guid containedObjectType, string? name, int userId) - : this(containedObjectType) - { - Id = id; - Key = uniqueId; - ParentId = parentId; - Name = name; - Path = path; - Level = level; - SortOrder = sortOrder; - CreatorId = userId; - } + /// + /// Initializes a new instance of an class. + /// + public EntityContainer(int id, Guid uniqueId, int parentId, string path, int level, int sortOrder, + Guid containedObjectType, string? name, int userId) + : this(containedObjectType) + { + Id = id; + Key = uniqueId; + ParentId = parentId; + Name = name; + Path = path; + Level = level; + SortOrder = sortOrder; + CreatorId = userId; + } - /// - /// Gets or sets the node object type of the contained objects. - /// - public Guid ContainedObjectType => _containedObjectType; + /// + /// Gets or sets the node object type of the contained objects. + /// + public Guid ContainedObjectType { get; } - /// - /// Gets the node object type of the container objects. - /// - public Guid ContainerObjectType => ObjectTypeMap[_containedObjectType]; + /// + /// Gets the node object type of the container objects. + /// + public Guid ContainerObjectType => ObjectTypeMap[ContainedObjectType]; - /// - /// Gets the container object type corresponding to a contained object type. - /// - /// The contained object type. - /// The object type of containers containing objects of the contained object type. - public static Guid GetContainerObjectType(Guid containedObjectType) + /// + /// Gets the container object type corresponding to a contained object type. + /// + /// The contained object type. + /// The object type of containers containing objects of the contained object type. + public static Guid GetContainerObjectType(Guid containedObjectType) + { + if (ObjectTypeMap.ContainsKey(containedObjectType) == false) { - if (ObjectTypeMap.ContainsKey(containedObjectType) == false) - throw new ArgumentException("Not a contained object type.", nameof(containedObjectType)); - return ObjectTypeMap[containedObjectType]; + throw new ArgumentException("Not a contained object type.", nameof(containedObjectType)); } - /// - /// Gets the contained object type corresponding to a container object type. - /// - /// The container object type. - /// The object type of objects that containers of the container object type can contain. - public static Guid GetContainedObjectType(Guid containerObjectType) + return ObjectTypeMap[containedObjectType]; + } + + /// + /// Gets the contained object type corresponding to a container object type. + /// + /// The container object type. + /// The object type of objects that containers of the container object type can contain. + public static Guid GetContainedObjectType(Guid containerObjectType) + { + Guid contained = ObjectTypeMap.FirstOrDefault(x => x.Value == containerObjectType).Key; + if (contained == null) { - var contained = ObjectTypeMap.FirstOrDefault(x => x.Value == containerObjectType).Key; - if (contained == null) - throw new ArgumentException("Not a container object type.", nameof(containerObjectType)); - return contained; + throw new ArgumentException("Not a container object type.", nameof(containerObjectType)); } + + return contained; } } diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index 3865d4eee79d..2b596352347c 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -1,160 +1,153 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models -{ - /// - /// Represents an abstract file which provides basic functionality for a File with an Alias and Name - /// - [Serializable] - [DataContract(IsReference = true)] - public abstract class File : EntityBase, IFile - { - private string _path; - private string _originalPath; +namespace Umbraco.Cms.Core.Models; - // initialize to string.Empty so that it is possible to save a new file, - // should use the lazyContent ctor to set it to null when loading existing. - // cannot simply use HasIdentity as some classes (eg Script) override it - // in a weird way. - private string? _content; - public Func? GetFileContent { get; set; } - - protected File(string path, Func? getFileContent = null) - { - _path = SanitizePath(path); - _originalPath = _path; - GetFileContent = getFileContent; - _content = getFileContent != null ? null : string.Empty; - } +/// +/// Represents an abstract file which provides basic functionality for a File with an Alias and Name +/// +[Serializable] +[DataContract(IsReference = true)] +public abstract class File : EntityBase, IFile +{ + private string? _alias; - private string? _alias; - private string? _name; + // initialize to string.Empty so that it is possible to save a new file, + // should use the lazyContent ctor to set it to null when loading existing. + // cannot simply use HasIdentity as some classes (eg Script) override it + // in a weird way. + private string? _content; + private string? _name; + private string _path; - private static string SanitizePath(string path) - { - return path - .Replace('\\', System.IO.Path.DirectorySeparatorChar) - .Replace('/', System.IO.Path.DirectorySeparatorChar); + protected File(string path, Func? getFileContent = null) + { + _path = SanitizePath(path); + OriginalPath = _path; + GetFileContent = getFileContent; + _content = getFileContent != null ? null : string.Empty; + } - //Don't strip the start - this was a bug fixed in 7.3, see ScriptRepositoryTests.PathTests - //.TrimStart(System.IO.Path.DirectorySeparatorChar) - //.TrimStart('/'); - } + public Func? GetFileContent { get; set; } - /// - /// Gets or sets the Name of the File including extension - /// - [DataMember] - public virtual string Name - { - get { return _name ?? (_name = System.IO.Path.GetFileName(Path)); } - } + /// + /// Gets or sets the Name of the File including extension + /// + [DataMember] + public virtual string Name => _name ?? (_name = System.IO.Path.GetFileName(Path)); - /// - /// Gets or sets the Alias of the File, which is the name without the extension - /// - [DataMember] - public virtual string Alias + /// + /// Gets or sets the Alias of the File, which is the name without the extension + /// + [DataMember] + public virtual string Alias + { + get { - get + if (_alias == null) { - if (_alias == null) + var name = System.IO.Path.GetFileName(Path); + if (name == null) { - var name = System.IO.Path.GetFileName(Path); - if (name == null) return string.Empty; - var lastIndexOf = name.LastIndexOf(".", StringComparison.InvariantCultureIgnoreCase); - _alias = name.Substring(0, lastIndexOf); + return string.Empty; } - return _alias; + + var lastIndexOf = name.LastIndexOf(".", StringComparison.InvariantCultureIgnoreCase); + _alias = name.Substring(0, lastIndexOf); } + + return _alias; } + } - /// - /// Gets or sets the Path to the File from the root of the file's associated IFileSystem - /// - [DataMember] - public virtual string Path + /// + /// Gets or sets the Path to the File from the root of the file's associated IFileSystem + /// + [DataMember] + public virtual string Path + { + get => _path; + set { - get { return _path; } - set - { - //reset - _alias = null; - _name = null; + //reset + _alias = null; + _name = null; - SetPropertyValueAndDetectChanges(SanitizePath(value), ref _path!, nameof(Path)); - } + SetPropertyValueAndDetectChanges(SanitizePath(value), ref _path!, nameof(Path)); } + } - /// - /// Gets the original path of the file - /// - public string OriginalPath - { - get { return _originalPath; } - } + /// + /// Gets the original path of the file + /// + public string OriginalPath { get; private set; } - /// - /// Called to re-set the OriginalPath to the Path - /// - public void ResetOriginalPath() - { - _originalPath = _path; - } + /// + /// Called to re-set the OriginalPath to the Path + /// + public void ResetOriginalPath() => OriginalPath = _path; - /// - /// Gets or sets the Content of a File - /// - /// Marked as DoNotClone, because it should be lazy-reloaded from disk. - [DataMember] - [DoNotClone] - public virtual string? Content + /// + /// Gets or sets the Content of a File + /// + /// Marked as DoNotClone, because it should be lazy-reloaded from disk. + [DataMember] + [DoNotClone] + public virtual string? Content + { + get { - get + if (_content != null) { - if (_content != null) - return _content; - - // else, must lazy-load, and ensure it's not null - if (GetFileContent != null) - _content = GetFileContent(this); - return _content ?? (_content = string.Empty); + return _content; } - set + + // else, must lazy-load, and ensure it's not null + if (GetFileContent != null) { - SetPropertyValueAndDetectChanges( - value ?? string.Empty, // cannot set to null - ref _content, nameof(Content)); + _content = GetFileContent(this); } - } - /// - /// Gets or sets the file's virtual path (i.e. the file path relative to the root of the website) - /// - public string? VirtualPath { get; set; } - - // this exists so that class that manage name and alias differently, eg Template, - // can implement their own cloning - (though really, not sure it's even needed) - protected virtual void DeepCloneNameAndAlias(File clone) - { - // set fields that have a lazy value, by forcing evaluation of the lazy - clone._name = Name; - clone._alias = Alias; + return _content ?? (_content = string.Empty); } + set => + SetPropertyValueAndDetectChanges( + value ?? string.Empty, // cannot set to null + ref _content, nameof(Content)); + } - protected override void PerformDeepClone(object clone) - { - base.PerformDeepClone(clone); + /// + /// Gets or sets the file's virtual path (i.e. the file path relative to the root of the website) + /// + public string? VirtualPath { get; set; } + + private static string SanitizePath(string path) => + path + .Replace('\\', System.IO.Path.DirectorySeparatorChar) + .Replace('/', System.IO.Path.DirectorySeparatorChar); + + //Don't strip the start - this was a bug fixed in 7.3, see ScriptRepositoryTests.PathTests + //.TrimStart(System.IO.Path.DirectorySeparatorChar) + //.TrimStart('/'); + // this exists so that class that manage name and alias differently, eg Template, + // can implement their own cloning - (though really, not sure it's even needed) + protected virtual void DeepCloneNameAndAlias(File clone) + { + // set fields that have a lazy value, by forcing evaluation of the lazy + clone._name = Name; + clone._alias = Alias; + } - var clonedFile = (File)clone; + protected override void PerformDeepClone(object clone) + { + base.PerformDeepClone(clone); - // clear fields that were memberwise-cloned and that we don't want to clone - clonedFile._content = null; + var clonedFile = (File)clone; - // ... - DeepCloneNameAndAlias(clonedFile); - } + // clear fields that were memberwise-cloned and that we don't want to clone + clonedFile._content = null; + + // ... + DeepCloneNameAndAlias(clonedFile); } } diff --git a/src/Umbraco.Core/Models/Folder.cs b/src/Umbraco.Core/Models/Folder.cs index 810bcaf3b3de..7f6b5f7f6785 100644 --- a/src/Umbraco.Core/Models/Folder.cs +++ b/src/Umbraco.Core/Models/Folder.cs @@ -1,14 +1,10 @@ using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public sealed class Folder : EntityBase { - public sealed class Folder : EntityBase - { - public Folder(string folderPath) - { - Path = folderPath; - } + public Folder(string folderPath) => Path = folderPath; - public string Path { get; set; } - } + public string Path { get; set; } } diff --git a/src/Umbraco.Core/Models/HaveAdditionalDataExtensions.cs b/src/Umbraco.Core/Models/HaveAdditionalDataExtensions.cs index 1c1c3774033f..a94bb9525bbd 100644 --- a/src/Umbraco.Core/Models/HaveAdditionalDataExtensions.cs +++ b/src/Umbraco.Core/Models/HaveAdditionalDataExtensions.cs @@ -3,18 +3,26 @@ using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class HaveAdditionalDataExtensions { - public static class HaveAdditionalDataExtensions + /// + /// Gets additional data. + /// + public static object? GetAdditionalDataValueIgnoreCase(this IHaveAdditionalData entity, string key, + object? defaultValue) { - /// - /// Gets additional data. - /// - public static object? GetAdditionalDataValueIgnoreCase(this IHaveAdditionalData entity, string key, object? defaultValue) + if (!entity.HasAdditionalData) { - if (!entity.HasAdditionalData) return defaultValue; - if (entity.AdditionalData?.ContainsKeyIgnoreCase(key) == false) return defaultValue; - return entity.AdditionalData?.GetValueIgnoreCase(key, defaultValue); + return defaultValue; } + + if (entity.AdditionalData?.ContainsKeyIgnoreCase(key) == false) + { + return defaultValue; + } + + return entity.AdditionalData?.GetValueIgnoreCase(key, defaultValue); } } diff --git a/src/Umbraco.Core/Models/IAuditEntry.cs b/src/Umbraco.Core/Models/IAuditEntry.cs index e12237f06d0e..7bb40ac875a7 100644 --- a/src/Umbraco.Core/Models/IAuditEntry.cs +++ b/src/Umbraco.Core/Models/IAuditEntry.cs @@ -1,60 +1,62 @@ -using System; -using Umbraco.Cms.Core.Models.Entities; - -namespace Umbraco.Cms.Core.Models +using Umbraco.Cms.Core.Models.Entities; + +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents an audited event. +/// +/// +/// +/// The free-form details properties can be used to capture relevant infos (for example, +/// a user email and identifier) at the time of the audited event, even though they may change +/// later on - but we want to keep a track of their value at that time. +/// +/// +/// Depending on audit loggers, these properties can be purely free-form text, or +/// contain json serialized objects. +/// +/// +public interface IAuditEntry : IEntity, IRememberBeingDirty { /// - /// Represents an audited event. - /// - /// - /// The free-form details properties can be used to capture relevant infos (for example, - /// a user email and identifier) at the time of the audited event, even though they may change - /// later on - but we want to keep a track of their value at that time. - /// Depending on audit loggers, these properties can be purely free-form text, or - /// contain json serialized objects. - /// - public interface IAuditEntry : IEntity, IRememberBeingDirty - { - /// - /// Gets or sets the identifier of the user triggering the audited event. - /// - int PerformingUserId { get; set; } - - /// - /// Gets or sets free-form details about the user triggering the audited event. - /// - string? PerformingDetails { get; set; } - - /// - /// Gets or sets the IP address or the request triggering the audited event. - /// - string? PerformingIp { get; set; } - - /// - /// Gets or sets the date and time of the audited event. - /// - DateTime EventDateUtc { get; set; } - - /// - /// Gets or sets the identifier of the user affected by the audited event. - /// - /// Not used when no single user is affected by the event. - int AffectedUserId { get; set; } - - /// - /// Gets or sets free-form details about the entity affected by the audited event. - /// - /// The entity affected by the event can be another user, a member... - string? AffectedDetails { get; set; } - - /// - /// Gets or sets the type of the audited event. - /// - string? EventType { get; set; } - - /// - /// Gets or sets free-form details about the audited event. - /// - string? EventDetails { get; set; } - } + /// Gets or sets the identifier of the user triggering the audited event. + /// + int PerformingUserId { get; set; } + + /// + /// Gets or sets free-form details about the user triggering the audited event. + /// + string? PerformingDetails { get; set; } + + /// + /// Gets or sets the IP address or the request triggering the audited event. + /// + string? PerformingIp { get; set; } + + /// + /// Gets or sets the date and time of the audited event. + /// + DateTime EventDateUtc { get; set; } + + /// + /// Gets or sets the identifier of the user affected by the audited event. + /// + /// Not used when no single user is affected by the event. + int AffectedUserId { get; set; } + + /// + /// Gets or sets free-form details about the entity affected by the audited event. + /// + /// The entity affected by the event can be another user, a member... + string? AffectedDetails { get; set; } + + /// + /// Gets or sets the type of the audited event. + /// + string? EventType { get; set; } + + /// + /// Gets or sets free-form details about the audited event. + /// + string? EventDetails { get; set; } } diff --git a/src/Umbraco.Core/Models/IAuditItem.cs b/src/Umbraco.Core/Models/IAuditItem.cs index dbc7ad1fd4ad..5989289f4b0d 100644 --- a/src/Umbraco.Core/Models/IAuditItem.cs +++ b/src/Umbraco.Core/Models/IAuditItem.cs @@ -1,35 +1,34 @@ using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents an audit item. +/// +public interface IAuditItem : IEntity { /// - /// Represents an audit item. + /// Gets the audit type. /// - public interface IAuditItem : IEntity - { - /// - /// Gets the audit type. - /// - AuditType AuditType { get; } + AuditType AuditType { get; } - /// - /// Gets the audited entity type. - /// - string? EntityType { get; } + /// + /// Gets the audited entity type. + /// + string? EntityType { get; } - /// - /// Gets the audit user identifier. - /// - int UserId { get; } + /// + /// Gets the audit user identifier. + /// + int UserId { get; } - /// - /// Gets the audit comments. - /// - string? Comment { get; } + /// + /// Gets the audit comments. + /// + string? Comment { get; } - /// - /// Gets optional additional data parameters. - /// - string? Parameters { get; } - } + /// + /// Gets optional additional data parameters. + /// + string? Parameters { get; } } diff --git a/src/Umbraco.Core/Models/IConsent.cs b/src/Umbraco.Core/Models/IConsent.cs index 747e7a145c74..f070a9988f35 100644 --- a/src/Umbraco.Core/Models/IConsent.cs +++ b/src/Umbraco.Core/Models/IConsent.cs @@ -1,55 +1,55 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a consent state. +/// +/// +/// +/// A consent is fully identified by a source (whoever is consenting), a context (for +/// example, an application), and an action (whatever is consented). +/// +/// A consent state registers the state of the consent (granted, revoked...). +/// +public interface IConsent : IEntity, IRememberBeingDirty { /// - /// Represents a consent state. + /// Determines whether the consent entity represents the current state. + /// + bool Current { get; } + + /// + /// Gets the unique identifier of whoever is consenting. + /// + string? Source { get; } + + /// + /// Gets the unique identifier of the context of the consent. /// /// - /// A consent is fully identified by a source (whoever is consenting), a context (for - /// example, an application), and an action (whatever is consented). - /// A consent state registers the state of the consent (granted, revoked...). + /// Represents the domain, application, scope... of the action. + /// When the action is a Udi, this should be the Udi type. /// - public interface IConsent : IEntity, IRememberBeingDirty - { - /// - /// Determines whether the consent entity represents the current state. - /// - bool Current { get; } - - /// - /// Gets the unique identifier of whoever is consenting. - /// - string? Source { get; } - - /// - /// Gets the unique identifier of the context of the consent. - /// - /// - /// Represents the domain, application, scope... of the action. - /// When the action is a Udi, this should be the Udi type. - /// - string? Context { get; } - - /// - /// Gets the unique identifier of the consented action. - /// - string? Action { get; } - - /// - /// Gets the state of the consent. - /// - ConsentState State { get; } - - /// - /// Gets some additional free text. - /// - string? Comment { get; } - - /// - /// Gets the previous states of this consent. - /// - IEnumerable? History { get; } - } + string? Context { get; } + + /// + /// Gets the unique identifier of the consented action. + /// + string? Action { get; } + + /// + /// Gets the state of the consent. + /// + ConsentState State { get; } + + /// + /// Gets some additional free text. + /// + string? Comment { get; } + + /// + /// Gets the previous states of this consent. + /// + IEnumerable? History { get; } } diff --git a/src/Umbraco.Core/Models/IContent.cs b/src/Umbraco.Core/Models/IContent.cs index e538b307e2d6..9e36306cfc78 100644 --- a/src/Umbraco.Core/Models/IContent.cs +++ b/src/Umbraco.Core/Models/IContent.cs @@ -1,131 +1,138 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a document. +/// +/// +/// A document can be published, rendered by a template. +/// +public interface IContent : IContentBase { + /// + /// Gets or sets the template id used to render the content. + /// + int? TemplateId { get; set; } + + /// + /// Gets a value indicating whether the content is published. + /// + /// The property tells you which version of the content is currently published. + bool Published { get; set; } + + PublishedState PublishedState { get; set; } /// - /// Represents a document. + /// Gets a value indicating whether the content has been edited. /// /// - /// A document can be published, rendered by a template. + /// Will return `true` once unpublished edits have been made after the version with + /// has been published. /// - public interface IContent : IContentBase - { - /// - /// Gets or sets the template id used to render the content. - /// - int? TemplateId { get; set; } - - /// - /// Gets a value indicating whether the content is published. - /// - /// The property tells you which version of the content is currently published. - bool Published { get; set; } - - PublishedState PublishedState { get; set; } - - /// - /// Gets a value indicating whether the content has been edited. - /// - /// Will return `true` once unpublished edits have been made after the version with has been published. - bool Edited { get; set; } - - /// - /// Gets the version identifier for the currently published version of the content. - /// - int PublishedVersionId { get; set; } - - /// - /// Gets a value indicating whether the content item is a blueprint. - /// - bool Blueprint { get; set; } - - /// - /// Gets the template id used to render the published version of the content. - /// - /// When editing the content, the template can change, but this will not until the content is published. - int? PublishTemplateId { get; set; } - - /// - /// Gets the name of the published version of the content. - /// - /// When editing the content, the name can change, but this will not until the content is published. - string? PublishName { get; set; } - - /// - /// Gets the identifier of the user who published the content. - /// - int? PublisherId { get; set; } - - /// - /// Gets the date and time the content was published. - /// - DateTime? PublishDate { get; set; } - - /// - /// Gets a value indicating whether a culture is published. - /// - /// - /// A culture becomes published whenever values for this culture are published, - /// and the content published name for this culture is non-null. It becomes non-published - /// whenever values for this culture are unpublished. - /// A culture becomes published as soon as PublishCulture has been invoked, - /// even though the document might not have been saved yet (and can have no identity). - /// Does not support the '*' wildcard (returns false). - /// - bool IsCulturePublished(string culture); - - /// - /// Gets the date a culture was published. - /// - DateTime? GetPublishDate(string culture); - - /// - /// Gets a value indicated whether a given culture is edited. - /// - /// - /// A culture is edited when it is available, and not published or published but - /// with changes. - /// A culture can be edited even though the document might now have been saved yet (and can have no identity). - /// Does not support the '*' wildcard (returns false). - /// - bool IsCultureEdited(string culture); - - /// - /// Gets the name of the published version of the content for a given culture. - /// - /// - /// When editing the content, the name can change, but this will not until the content is published. - /// When is null, gets the invariant - /// language, which is the value of the property. - /// - string? GetPublishName(string? culture); - - /// - /// Gets the published culture infos of the content. - /// - /// - /// Because a dictionary key cannot be null this cannot get the invariant - /// name, which must be get via the property. - /// - ContentCultureInfosCollection? PublishCultureInfos { get; set; } - - /// - /// Gets the published cultures. - /// - IEnumerable PublishedCultures { get; } - - /// - /// Gets the edited cultures. - /// - IEnumerable? EditedCultures { get; set; } - - /// - /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset - /// - /// - IContent DeepCloneWithResetIdentities(); - - } + bool Edited { get; set; } + + /// + /// Gets the version identifier for the currently published version of the content. + /// + int PublishedVersionId { get; set; } + + /// + /// Gets a value indicating whether the content item is a blueprint. + /// + bool Blueprint { get; set; } + + /// + /// Gets the template id used to render the published version of the content. + /// + /// When editing the content, the template can change, but this will not until the content is published. + int? PublishTemplateId { get; set; } + + /// + /// Gets the name of the published version of the content. + /// + /// When editing the content, the name can change, but this will not until the content is published. + string? PublishName { get; set; } + + /// + /// Gets the identifier of the user who published the content. + /// + int? PublisherId { get; set; } + + /// + /// Gets the date and time the content was published. + /// + DateTime? PublishDate { get; set; } + + /// + /// Gets the published culture infos of the content. + /// + /// + /// + /// Because a dictionary key cannot be null this cannot get the invariant + /// name, which must be get via the property. + /// + /// + ContentCultureInfosCollection? PublishCultureInfos { get; set; } + + /// + /// Gets the published cultures. + /// + IEnumerable PublishedCultures { get; } + + /// + /// Gets the edited cultures. + /// + IEnumerable? EditedCultures { get; set; } + + /// + /// Gets a value indicating whether a culture is published. + /// + /// + /// + /// A culture becomes published whenever values for this culture are published, + /// and the content published name for this culture is non-null. It becomes non-published + /// whenever values for this culture are unpublished. + /// + /// + /// A culture becomes published as soon as PublishCulture has been invoked, + /// even though the document might not have been saved yet (and can have no identity). + /// + /// Does not support the '*' wildcard (returns false). + /// + bool IsCulturePublished(string culture); + + /// + /// Gets the date a culture was published. + /// + DateTime? GetPublishDate(string culture); + + /// + /// Gets a value indicated whether a given culture is edited. + /// + /// + /// + /// A culture is edited when it is available, and not published or published but + /// with changes. + /// + /// A culture can be edited even though the document might now have been saved yet (and can have no identity). + /// Does not support the '*' wildcard (returns false). + /// + bool IsCultureEdited(string culture); + + /// + /// Gets the name of the published version of the content for a given culture. + /// + /// + /// When editing the content, the name can change, but this will not until the content is published. + /// + /// When is null, gets the invariant + /// language, which is the value of the property. + /// + /// + string? GetPublishName(string? culture); + + /// + /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset + /// + /// + IContent DeepCloneWithResetIdentities(); } diff --git a/src/Umbraco.Core/Models/IContentBase.cs b/src/Umbraco.Core/Models/IContentBase.cs index 20e78816ae45..e22444e3872c 100644 --- a/src/Umbraco.Core/Models/IContentBase.cs +++ b/src/Umbraco.Core/Models/IContentBase.cs @@ -1,130 +1,142 @@ -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core.Models.Entities; - -namespace Umbraco.Cms.Core.Models +using Umbraco.Cms.Core.Models.Entities; + +namespace Umbraco.Cms.Core.Models; + +/// +/// Provides a base class for content items. +/// +/// +/// Content items are documents, medias and members. +/// Content items have a content type, and properties. +/// +public interface IContentBase : IUmbracoEntity, IRememberBeingDirty { + /// + /// Integer Id of the default ContentType + /// + int ContentTypeId { get; } + + /// + /// Gets the content type of this content. + /// + ISimpleContentType ContentType { get; } + + /// + /// Gets the identifier of the writer. + /// + int WriterId { get; set; } + + /// + /// Gets the version identifier. + /// + int VersionId { get; set; } + + /// + /// Gets culture infos of the content item. + /// + /// + /// + /// Because a dictionary key cannot be null this cannot contain the invariant + /// culture name, which must be get or set via the property. + /// + /// + ContentCultureInfosCollection? CultureInfos { get; set; } + + /// + /// Gets the available cultures. + /// + /// + /// Cannot contain the invariant culture, which is always available. + /// + IEnumerable AvailableCultures { get; } + + /// + /// List of properties, which make up all the data available for this Content object + /// + /// Properties are loaded as part of the Content object graph + IPropertyCollection Properties { get; set; } + + /// + /// Sets the name of the content item for a specified culture. + /// + /// + /// + /// When is null, sets the invariant + /// culture name, which sets the property. + /// + /// + /// When is not null, throws if the content + /// type does not vary by culture. + /// + /// + void SetCultureName(string? value, string? culture); + + /// + /// Gets the name of the content item for a specified language. + /// + /// + /// + /// When is null, gets the invariant + /// culture name, which is the value of the property. + /// + /// + /// When is not null, and the content type + /// does not vary by culture, returns null. + /// + /// + string? GetCultureName(string? culture); /// - /// Provides a base class for content items. + /// Gets a value indicating whether a given culture is available. /// /// - /// Content items are documents, medias and members. - /// Content items have a content type, and properties. + /// + /// A culture becomes available whenever the content name for this culture is + /// non-null, and it becomes unavailable whenever the content name is null. + /// + /// + /// Returns false for the invariant culture, in order to be consistent + /// with , even though the invariant culture is + /// always available. + /// + /// Does not support the '*' wildcard (returns false). /// - public interface IContentBase : IUmbracoEntity, IRememberBeingDirty - { - /// - /// Integer Id of the default ContentType - /// - int ContentTypeId { get; } - - /// - /// Gets the content type of this content. - /// - ISimpleContentType ContentType { get; } - - /// - /// Gets the identifier of the writer. - /// - int WriterId { get; set; } - - /// - /// Gets the version identifier. - /// - int VersionId { get; set; } - - /// - /// Sets the name of the content item for a specified culture. - /// - /// - /// When is null, sets the invariant - /// culture name, which sets the property. - /// When is not null, throws if the content - /// type does not vary by culture. - /// - void SetCultureName(string? value, string? culture); - - /// - /// Gets the name of the content item for a specified language. - /// - /// - /// When is null, gets the invariant - /// culture name, which is the value of the property. - /// When is not null, and the content type - /// does not vary by culture, returns null. - /// - string? GetCultureName(string? culture); - - /// - /// Gets culture infos of the content item. - /// - /// - /// Because a dictionary key cannot be null this cannot contain the invariant - /// culture name, which must be get or set via the property. - /// - ContentCultureInfosCollection? CultureInfos { get; set; } - - /// - /// Gets the available cultures. - /// - /// - /// Cannot contain the invariant culture, which is always available. - /// - IEnumerable AvailableCultures { get; } - - /// - /// Gets a value indicating whether a given culture is available. - /// - /// - /// A culture becomes available whenever the content name for this culture is - /// non-null, and it becomes unavailable whenever the content name is null. - /// Returns false for the invariant culture, in order to be consistent - /// with , even though the invariant culture is - /// always available. - /// Does not support the '*' wildcard (returns false). - /// - bool IsCultureAvailable(string culture); - - /// - /// Gets the date a culture was updated. - /// - /// - /// When is null, returns null. - /// If the specified culture is not available, returns null. - /// - DateTime? GetUpdateDate(string culture); - - /// - /// List of properties, which make up all the data available for this Content object - /// - /// Properties are loaded as part of the Content object graph - IPropertyCollection Properties { get; set; } - - /// - /// Gets a value indicating whether the content entity has a property with the supplied alias. - /// - /// Indicates that the content entity has a property with the supplied alias, but - /// not necessarily that the content has a value for that property. Could be missing. - bool HasProperty(string propertyTypeAlias); - - /// - /// Gets the value of a Property - /// - /// Values 'null' and 'empty' are equivalent for culture and segment. - object? GetValue(string propertyTypeAlias, string? culture = null, string? segment = null, bool published = false); - - /// - /// Gets the typed value of a Property - /// - /// Values 'null' and 'empty' are equivalent for culture and segment. - TValue? GetValue(string propertyTypeAlias, string? culture = null, string? segment = null, bool published = false); - - /// - /// Sets the (edited) value of a Property - /// - /// Values 'null' and 'empty' are equivalent for culture and segment. - void SetValue(string propertyTypeAlias, object? value, string? culture = null, string? segment = null); - - } + bool IsCultureAvailable(string culture); + + /// + /// Gets the date a culture was updated. + /// + /// + /// When is null, returns null. + /// If the specified culture is not available, returns null. + /// + DateTime? GetUpdateDate(string culture); + + /// + /// Gets a value indicating whether the content entity has a property with the supplied alias. + /// + /// + /// Indicates that the content entity has a property with the supplied alias, but + /// not necessarily that the content has a value for that property. Could be missing. + /// + bool HasProperty(string propertyTypeAlias); + + /// + /// Gets the value of a Property + /// + /// Values 'null' and 'empty' are equivalent for culture and segment. + object? GetValue(string propertyTypeAlias, string? culture = null, string? segment = null, bool published = false); + + /// + /// Gets the typed value of a Property + /// + /// Values 'null' and 'empty' are equivalent for culture and segment. + TValue? GetValue(string propertyTypeAlias, string? culture = null, string? segment = null, + bool published = false); + + /// + /// Sets the (edited) value of a Property + /// + /// Values 'null' and 'empty' are equivalent for culture and segment. + void SetValue(string propertyTypeAlias, object? value, string? culture = null, string? segment = null); } diff --git a/src/Umbraco.Core/Models/IContentModel.cs b/src/Umbraco.Core/Models/IContentModel.cs index 8aa8c1830676..c7669dfbe4f1 100644 --- a/src/Umbraco.Core/Models/IContentModel.cs +++ b/src/Umbraco.Core/Models/IContentModel.cs @@ -1,29 +1,34 @@ using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// The basic view model returned for front-end Umbraco controllers +/// +/// +/// +/// exists in order to unify all view models in Umbraco, whether it's a normal +/// template view or a partial view macro, or +/// a user's custom model that they have created when doing route hijacking or custom routes. +/// +/// +/// By default all front-end template views inherit from UmbracoViewPage which has a model of +/// but the model returned +/// from the controllers is which in normal circumstances would not work. This works +/// with UmbracoViewPage because it +/// performs model binding between IContentModel and IPublishedContent. This offers a lot of flexibility when +/// rendering views. In some cases if you +/// are route hijacking and returning a custom implementation of and your view is +/// strongly typed to this model, you can still +/// render partial views created in the back office that have the default model of IPublishedContent without having +/// to worry about explicitly passing +/// that model to the view. +/// +/// +public interface IContentModel { /// - /// The basic view model returned for front-end Umbraco controllers + /// Gets the /// - /// - /// - /// exists in order to unify all view models in Umbraco, whether it's a normal template view or a partial view macro, or - /// a user's custom model that they have created when doing route hijacking or custom routes. - /// - /// - /// By default all front-end template views inherit from UmbracoViewPage which has a model of but the model returned - /// from the controllers is which in normal circumstances would not work. This works with UmbracoViewPage because it - /// performs model binding between IContentModel and IPublishedContent. This offers a lot of flexibility when rendering views. In some cases if you - /// are route hijacking and returning a custom implementation of and your view is strongly typed to this model, you can still - /// render partial views created in the back office that have the default model of IPublishedContent without having to worry about explicitly passing - /// that model to the view. - /// - /// - public interface IContentModel - { - /// - /// Gets the - /// - IPublishedContent Content { get; } - } + IPublishedContent Content { get; } } diff --git a/src/Umbraco.Core/Models/IContentType.cs b/src/Umbraco.Core/Models/IContentType.cs index e3fd83fcd340..5d76c49b88a1 100644 --- a/src/Umbraco.Core/Models/IContentType.cs +++ b/src/Umbraco.Core/Models/IContentType.cs @@ -1,74 +1,71 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Models.ContentEditing; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Defines a content type that contains a history cleanup policy. +/// +[Obsolete("This will be merged into IContentType in Umbraco 10.")] +public interface IContentTypeWithHistoryCleanup : IContentType { /// - /// Defines a content type that contains a history cleanup policy. + /// Gets or sets the history cleanup configuration. /// - [Obsolete("This will be merged into IContentType in Umbraco 10.")] - public interface IContentTypeWithHistoryCleanup : IContentType - { - /// - /// Gets or sets the history cleanup configuration. - /// - /// The history cleanup configuration. - HistoryCleanup? HistoryCleanup { get; set; } - } + /// The history cleanup configuration. + HistoryCleanup? HistoryCleanup { get; set; } +} +/// +/// Defines a ContentType, which Content is based on +/// +public interface IContentType : IContentTypeComposition +{ /// - /// Defines a ContentType, which Content is based on + /// Internal property to store the Id of the default template /// - public interface IContentType : IContentTypeComposition - { - /// - /// Internal property to store the Id of the default template - /// - int DefaultTemplateId { get; set; } + int DefaultTemplateId { get; set; } - /// - /// Gets the default Template of the ContentType - /// - ITemplate? DefaultTemplate { get; } + /// + /// Gets the default Template of the ContentType + /// + ITemplate? DefaultTemplate { get; } - /// - /// Gets or Sets a list of Templates which are allowed for the ContentType - /// - IEnumerable? AllowedTemplates { get; set; } + /// + /// Gets or Sets a list of Templates which are allowed for the ContentType + /// + IEnumerable? AllowedTemplates { get; set; } - /// - /// Determines if AllowedTemplates contains templateId - /// - /// The template id to check - /// True if AllowedTemplates contains the templateId else False - bool IsAllowedTemplate(int templateId); + /// + /// Determines if AllowedTemplates contains templateId + /// + /// The template id to check + /// True if AllowedTemplates contains the templateId else False + bool IsAllowedTemplate(int templateId); - /// - /// Determines if AllowedTemplates contains templateId - /// - /// The template alias to check - /// True if AllowedTemplates contains the templateAlias else False - bool IsAllowedTemplate(string templateAlias); + /// + /// Determines if AllowedTemplates contains templateId + /// + /// The template alias to check + /// True if AllowedTemplates contains the templateAlias else False + bool IsAllowedTemplate(string templateAlias); - /// - /// Sets the default template for the ContentType - /// - /// Default - void SetDefaultTemplate(ITemplate? template); + /// + /// Sets the default template for the ContentType + /// + /// Default + void SetDefaultTemplate(ITemplate? template); - /// - /// Removes a template from the list of allowed templates - /// - /// to remove - /// True if template was removed, otherwise False - bool RemoveTemplate(ITemplate template); + /// + /// Removes a template from the list of allowed templates + /// + /// to remove + /// True if template was removed, otherwise False + bool RemoveTemplate(ITemplate template); - /// - /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset - /// - /// - /// - IContentType DeepCloneWithResetIdentities(string newAlias); - } + /// + /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset + /// + /// + /// + IContentType DeepCloneWithResetIdentities(string newAlias); } diff --git a/src/Umbraco.Core/Models/IContentTypeBase.cs b/src/Umbraco.Core/Models/IContentTypeBase.cs index eb3d4489d4e9..adcb4074f9ab 100644 --- a/src/Umbraco.Core/Models/IContentTypeBase.cs +++ b/src/Umbraco.Core/Models/IContentTypeBase.cs @@ -1,175 +1,182 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Defines the base for a ContentType with properties that +/// are shared between ContentTypes and MediaTypes. +/// +public interface IContentTypeBase : IUmbracoEntity, IRememberBeingDirty { /// - /// Defines the base for a ContentType with properties that - /// are shared between ContentTypes and MediaTypes. - /// - public interface IContentTypeBase : IUmbracoEntity, IRememberBeingDirty - { - /// - /// Gets or Sets the Alias of the ContentType - /// - string Alias { get; set; } - - /// - /// Gets or Sets the Description for the ContentType - /// - string? Description { get; set; } - - /// - /// Gets or sets the icon for the content type. The value is a CSS class name representing - /// the icon (eg. icon-home) along with an optional CSS class name representing the - /// color (eg. icon-blue). Put together, the value for this scenario would be - /// icon-home color-blue. - /// - /// If a class name for the color isn't specified, the icon color will default to black. - /// - string? Icon { get; set; } - - /// - /// Gets or Sets the Thumbnail for the ContentType - /// - string? Thumbnail { get; set; } - - /// - /// Gets or Sets a boolean indicating whether this ContentType is allowed at the root - /// - bool AllowedAsRoot { get; set; } - - /// - /// Gets or Sets a boolean indicating whether this ContentType is a Container - /// - /// - /// ContentType Containers doesn't show children in the tree, but rather in grid-type view. - /// - bool IsContainer { get; set; } - - /// - /// Gets or sets a value indicating whether this content type is for an element. - /// - /// - /// By default a content type is for a true media, member or document, but - /// it can also be for an element, ie a subset that can for instance be used in - /// nested content. - /// - bool IsElement { get; set; } - - /// - /// Gets or sets the content variation of the content type. - /// - ContentVariation Variations { get; set; } - - /// - /// Validates that a combination of culture and segment is valid for the content type. - /// - /// The culture. - /// The segment. - /// A value indicating whether wildcard are supported. - /// True if the combination is valid; otherwise false. - /// - /// The combination must match the content type variation exactly. For instance, if the content type varies by culture, - /// then an invariant culture would be invalid. - /// - bool SupportsVariation(string culture, string segment, bool wildcards = false); - - /// - /// Validates that a combination of culture and segment is valid for the content type properties. - /// - /// The culture. - /// The segment. - /// A value indicating whether wildcard are supported. - /// True if the combination is valid; otherwise false. - /// - /// The combination must be valid for properties of the content type. For instance, if the content type varies by culture, - /// then an invariant culture is valid, because some properties may be invariant. On the other hand, if the content type is invariant, - /// then a variant culture is invalid, because no property could possibly vary by culture. - /// - bool SupportsPropertyVariation(string culture, string segment, bool wildcards = false); - - /// - /// Gets or Sets a list of integer Ids of the ContentTypes allowed under the ContentType - /// - IEnumerable? AllowedContentTypes { get; set; } - - /// - /// Gets or sets the local property groups. - /// - PropertyGroupCollection PropertyGroups { get; set; } - - /// - /// Gets all local property types all local property groups or ungrouped. - /// - IEnumerable PropertyTypes { get; } - - /// - /// Gets or sets the local property types that do not belong to a group. - /// - IEnumerable NoGroupPropertyTypes { get; set; } - - /// - /// Removes a PropertyType from the current ContentType - /// - /// Alias of the to remove - void RemovePropertyType(string alias); - - /// - /// Removes a property group from the current content type. - /// - /// Alias of the to remove - void RemovePropertyGroup(string alias); - - /// - /// Checks whether a PropertyType with a given alias already exists - /// - /// Alias of the PropertyType - /// Returns True if a PropertyType with the passed in alias exists, otherwise False - bool PropertyTypeExists(string? alias); - - /// - /// Adds the property type to the specified property group (creates a new group if not found and a name is specified). - /// - /// The property type to add. - /// The alias of the property group to add the property type to. - /// The name of the property group to create when not found. - /// - /// Returns true if the property type was added; otherwise, false. - /// - bool AddPropertyType(IPropertyType propertyType, string propertyGroupAlias, string? propertyGroupName = null); - - /// - /// Adds a PropertyType, which does not belong to a PropertyGroup. - /// - /// to add - /// Returns True if PropertyType was added, otherwise False - bool AddPropertyType(IPropertyType propertyType); - - /// - /// Adds a property group with the specified and . - /// - /// The alias. - /// Name of the group. - /// - /// Returns true if a property group with specified was added; otherwise, false. - /// - /// - /// This method will also check if a group already exists with the same alias. - /// - bool AddPropertyGroup(string alias, string name); - - /// - /// Moves a PropertyType to a specified PropertyGroup - /// - /// Alias of the PropertyType to move - /// Alias of the PropertyGroup to move the PropertyType to - /// - bool MovePropertyType(string propertyTypeAlias, string propertyGroupAlias); - - /// - /// Gets an corresponding to this content type. - /// - ISimpleContentType ToSimple(); - } + /// Gets or Sets the Alias of the ContentType + /// + string Alias { get; set; } + + /// + /// Gets or Sets the Description for the ContentType + /// + string? Description { get; set; } + + /// + /// Gets or sets the icon for the content type. The value is a CSS class name representing + /// the icon (eg. icon-home) along with an optional CSS class name representing the + /// color (eg. icon-blue). Put together, the value for this scenario would be + /// icon-home color-blue. + /// If a class name for the color isn't specified, the icon color will default to black. + /// + string? Icon { get; set; } + + /// + /// Gets or Sets the Thumbnail for the ContentType + /// + string? Thumbnail { get; set; } + + /// + /// Gets or Sets a boolean indicating whether this ContentType is allowed at the root + /// + bool AllowedAsRoot { get; set; } + + /// + /// Gets or Sets a boolean indicating whether this ContentType is a Container + /// + /// + /// ContentType Containers doesn't show children in the tree, but rather in grid-type view. + /// + bool IsContainer { get; set; } + + /// + /// Gets or sets a value indicating whether this content type is for an element. + /// + /// + /// + /// By default a content type is for a true media, member or document, but + /// it can also be for an element, ie a subset that can for instance be used in + /// nested content. + /// + /// + bool IsElement { get; set; } + + /// + /// Gets or sets the content variation of the content type. + /// + ContentVariation Variations { get; set; } + + /// + /// Gets or Sets a list of integer Ids of the ContentTypes allowed under the ContentType + /// + IEnumerable? AllowedContentTypes { get; set; } + + /// + /// Gets or sets the local property groups. + /// + PropertyGroupCollection PropertyGroups { get; set; } + + /// + /// Gets all local property types all local property groups or ungrouped. + /// + IEnumerable PropertyTypes { get; } + + /// + /// Gets or sets the local property types that do not belong to a group. + /// + IEnumerable NoGroupPropertyTypes { get; set; } + + /// + /// Validates that a combination of culture and segment is valid for the content type. + /// + /// The culture. + /// The segment. + /// A value indicating whether wildcard are supported. + /// True if the combination is valid; otherwise false. + /// + /// + /// The combination must match the content type variation exactly. For instance, if the content type varies by + /// culture, + /// then an invariant culture would be invalid. + /// + /// + bool SupportsVariation(string culture, string segment, bool wildcards = false); + + /// + /// Validates that a combination of culture and segment is valid for the content type properties. + /// + /// The culture. + /// The segment. + /// A value indicating whether wildcard are supported. + /// True if the combination is valid; otherwise false. + /// + /// + /// The combination must be valid for properties of the content type. For instance, if the content type varies by + /// culture, + /// then an invariant culture is valid, because some properties may be invariant. On the other hand, if the content + /// type is invariant, + /// then a variant culture is invalid, because no property could possibly vary by culture. + /// + /// + bool SupportsPropertyVariation(string culture, string segment, bool wildcards = false); + + /// + /// Removes a PropertyType from the current ContentType + /// + /// Alias of the to remove + void RemovePropertyType(string alias); + + /// + /// Removes a property group from the current content type. + /// + /// Alias of the to remove + void RemovePropertyGroup(string alias); + + /// + /// Checks whether a PropertyType with a given alias already exists + /// + /// Alias of the PropertyType + /// Returns True if a PropertyType with the passed in alias exists, otherwise False + bool PropertyTypeExists(string? alias); + + /// + /// Adds the property type to the specified property group (creates a new group if not found and a name is specified). + /// + /// The property type to add. + /// The alias of the property group to add the property type to. + /// The name of the property group to create when not found. + /// + /// Returns true if the property type was added; otherwise, false. + /// + bool AddPropertyType(IPropertyType propertyType, string propertyGroupAlias, string? propertyGroupName = null); + + /// + /// Adds a PropertyType, which does not belong to a PropertyGroup. + /// + /// to add + /// Returns True if PropertyType was added, otherwise False + bool AddPropertyType(IPropertyType propertyType); + + /// + /// Adds a property group with the specified and . + /// + /// The alias. + /// Name of the group. + /// + /// Returns true if a property group with specified was added; otherwise, false + /// . + /// + /// + /// This method will also check if a group already exists with the same alias. + /// + bool AddPropertyGroup(string alias, string name); + + /// + /// Moves a PropertyType to a specified PropertyGroup + /// + /// Alias of the PropertyType to move + /// Alias of the PropertyGroup to move the PropertyType to + /// + bool MovePropertyType(string propertyTypeAlias, string propertyGroupAlias); + + /// + /// Gets an corresponding to this content type. + /// + ISimpleContentType ToSimple(); } diff --git a/src/Umbraco.Core/Models/IContentTypeComposition.cs b/src/Umbraco.Core/Models/IContentTypeComposition.cs index de3e8fb41647..d9f1fe4e6973 100644 --- a/src/Umbraco.Core/Models/IContentTypeComposition.cs +++ b/src/Umbraco.Core/Models/IContentTypeComposition.cs @@ -1,72 +1,69 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Models +/// +/// Defines the Composition of a ContentType +/// +public interface IContentTypeComposition : IContentTypeBase { /// - /// Defines the Composition of a ContentType + /// Gets or sets the content types that compose this content type. /// - public interface IContentTypeComposition : IContentTypeBase - { - /// - /// Gets or sets the content types that compose this content type. - /// - // TODO: we should be storing key references, not the object else we are caching way too much - IEnumerable ContentTypeComposition { get; set; } + // TODO: we should be storing key references, not the object else we are caching way too much + IEnumerable ContentTypeComposition { get; set; } - /// - /// Gets the property groups for the entire composition. - /// - IEnumerable CompositionPropertyGroups { get; } + /// + /// Gets the property groups for the entire composition. + /// + IEnumerable CompositionPropertyGroups { get; } - /// - /// Gets the property types for the entire composition. - /// - IEnumerable CompositionPropertyTypes { get; } + /// + /// Gets the property types for the entire composition. + /// + IEnumerable CompositionPropertyTypes { get; } - /// - /// Adds a new ContentType to the list of composite ContentTypes - /// - /// to add - /// True if ContentType was added, otherwise returns False - bool AddContentType(IContentTypeComposition? contentType); + /// + /// Returns a list of content type ids that have been removed from this instance's composition + /// + IEnumerable RemovedContentTypes { get; } - /// - /// Removes a ContentType with the supplied alias from the list of composite ContentTypes - /// - /// Alias of a - /// True if ContentType was removed, otherwise returns False - bool RemoveContentType(string alias); + /// + /// Adds a new ContentType to the list of composite ContentTypes + /// + /// to add + /// True if ContentType was added, otherwise returns False + bool AddContentType(IContentTypeComposition? contentType); - /// - /// Checks if a ContentType with the supplied alias exists in the list of composite ContentTypes - /// - /// Alias of a - /// True if ContentType with alias exists, otherwise returns False - bool ContentTypeCompositionExists(string alias); + /// + /// Removes a ContentType with the supplied alias from the list of composite ContentTypes + /// + /// Alias of a + /// True if ContentType was removed, otherwise returns False + bool RemoveContentType(string alias); - /// - /// Gets a list of ContentType aliases from the current composition - /// - /// An enumerable list of string aliases - IEnumerable CompositionAliases(); + /// + /// Checks if a ContentType with the supplied alias exists in the list of composite ContentTypes + /// + /// Alias of a + /// True if ContentType with alias exists, otherwise returns False + bool ContentTypeCompositionExists(string alias); - /// - /// Gets a list of ContentType Ids from the current composition - /// - /// An enumerable list of integer ids - IEnumerable CompositionIds(); + /// + /// Gets a list of ContentType aliases from the current composition + /// + /// An enumerable list of string aliases + IEnumerable CompositionAliases(); - /// - /// Returns a list of content type ids that have been removed from this instance's composition - /// - IEnumerable RemovedContentTypes { get; } + /// + /// Gets a list of ContentType Ids from the current composition + /// + /// An enumerable list of integer ids + IEnumerable CompositionIds(); - /// - /// Gets the property types obtained via composition. - /// - /// - /// Gets them raw, ie with their original variation. - /// - IEnumerable GetOriginalComposedPropertyTypes(); - } + /// + /// Gets the property types obtained via composition. + /// + /// + /// Gets them raw, ie with their original variation. + /// + IEnumerable GetOriginalComposedPropertyTypes(); } diff --git a/src/Umbraco.Core/Models/IDataType.cs b/src/Umbraco.Core/Models/IDataType.cs index 2fdc67dfcc8a..91f8d2457c79 100644 --- a/src/Umbraco.Core/Models/IDataType.cs +++ b/src/Umbraco.Core/Models/IDataType.cs @@ -1,38 +1,41 @@ using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a data type. +/// +public interface IDataType : IUmbracoEntity, IRememberBeingDirty { /// - /// Represents a data type. + /// Gets or sets the property editor. /// - public interface IDataType : IUmbracoEntity, IRememberBeingDirty - { - /// - /// Gets or sets the property editor. - /// - IDataEditor? Editor { get; set; } + IDataEditor? Editor { get; set; } - /// - /// Gets the property editor alias. - /// - string EditorAlias { get; } + /// + /// Gets the property editor alias. + /// + string EditorAlias { get; } - /// - /// Gets or sets the database type for the data type values. - /// - /// In most cases this is imposed by the property editor, but some editors - /// may support storing different types. - ValueStorageType DatabaseType { get; set; } + /// + /// Gets or sets the database type for the data type values. + /// + /// + /// In most cases this is imposed by the property editor, but some editors + /// may support storing different types. + /// + ValueStorageType DatabaseType { get; set; } - /// - /// Gets or sets the configuration object. - /// - /// - /// The configuration object is serialized to Json and stored into the database. - /// The serialized Json is deserialized by the property editor, which by default should - /// return a Dictionary{string, object} but could return a typed object e.g. MyEditor.Configuration. - /// - object? Configuration { get; set; } - } + /// + /// Gets or sets the configuration object. + /// + /// + /// The configuration object is serialized to Json and stored into the database. + /// + /// The serialized Json is deserialized by the property editor, which by default should + /// return a Dictionary{string, object} but could return a typed object e.g. MyEditor.Configuration. + /// + /// + object? Configuration { get; set; } } diff --git a/src/Umbraco.Core/Models/IDataValueEditor.cs b/src/Umbraco.Core/Models/IDataValueEditor.cs index 8d4841a11479..5ea762b62088 100644 --- a/src/Umbraco.Core/Models/IDataValueEditor.cs +++ b/src/Umbraco.Core/Models/IDataValueEditor.cs @@ -1,85 +1,82 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Xml.Linq; using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.Models -{ +namespace Umbraco.Cms.Core.Models; +/// +/// Represents an editor for editing data values. +/// +/// This is the base interface for parameter and property value editors. +public interface IDataValueEditor +{ /// - /// Represents an editor for editing data values. + /// Gets the editor view. /// - /// This is the base interface for parameter and property value editors. - public interface IDataValueEditor - { - /// - /// Gets the editor view. - /// - string? View { get; } + string? View { get; } - /// - /// Gets the type of the value. - /// - /// The value has to be a valid value. - string ValueType { get; set; } + /// + /// Gets the type of the value. + /// + /// The value has to be a valid value. + string ValueType { get; set; } - /// - /// Gets a value indicating whether the edited value is read-only. - /// - bool IsReadOnly { get; } + /// + /// Gets a value indicating whether the edited value is read-only. + /// + bool IsReadOnly { get; } - /// - /// Gets a value indicating whether to display the associated label. - /// - bool HideLabel { get; } + /// + /// Gets a value indicating whether to display the associated label. + /// + bool HideLabel { get; } - /// - /// Validates a property value. - /// - /// The property value. - /// A value indicating whether the property value is required. - /// A specific format (regex) that the property value must respect. - IEnumerable Validate(object? value, bool required, string? format); + /// + /// Gets the validators to use to validate the edited value. + /// + /// + /// Use this property to add validators, not to validate. Use instead. + /// TODO: replace with AddValidator? WithValidator? + /// + List Validators { get; } - /// - /// Gets the validators to use to validate the edited value. - /// - /// - /// Use this property to add validators, not to validate. Use instead. - /// TODO: replace with AddValidator? WithValidator? - /// - List Validators { get; } + /// + /// Validates a property value. + /// + /// The property value. + /// A value indicating whether the property value is required. + /// A specific format (regex) that the property value must respect. + IEnumerable Validate(object? value, bool required, string? format); - /// - /// Converts a value posted by the editor to a property value. - /// - object? FromEditor(ContentPropertyData editorValue, object? currentValue); + /// + /// Converts a value posted by the editor to a property value. + /// + object? FromEditor(ContentPropertyData editorValue, object? currentValue); - /// - /// Converts a property value to a value for the editor. - /// - object? ToEditor(IProperty property, string? culture = null, string? segment = null); + /// + /// Converts a property value to a value for the editor. + /// + object? ToEditor(IProperty property, string? culture = null, string? segment = null); - // TODO: / deal with this when unplugging the xml cache - // why property vs propertyType? services should be injected! etc... + // TODO: / deal with this when unplugging the xml cache + // why property vs propertyType? services should be injected! etc... - /// - /// Used for serializing an item for packaging - /// - /// - /// - /// - IEnumerable ConvertDbToXml(IProperty property, bool published); + /// + /// Used for serializing an item for packaging + /// + /// + /// + /// + IEnumerable ConvertDbToXml(IProperty property, bool published); - /// - /// Used for serializing an item for packaging - /// - /// - /// - /// - XNode ConvertDbToXml(IPropertyType propertyType, object value); + /// + /// Used for serializing an item for packaging + /// + /// + /// + /// + XNode ConvertDbToXml(IPropertyType propertyType, object value); - string ConvertDbToString(IPropertyType propertyType, object? value); - } + string ConvertDbToString(IPropertyType propertyType, object? value); } diff --git a/src/Umbraco.Core/Models/IDeepCloneable.cs b/src/Umbraco.Core/Models/IDeepCloneable.cs index a7568b7e815c..122dd8116a22 100644 --- a/src/Umbraco.Core/Models/IDeepCloneable.cs +++ b/src/Umbraco.Core/Models/IDeepCloneable.cs @@ -1,10 +1,9 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Provides a mean to deep-clone an object. +/// +public interface IDeepCloneable { - /// - /// Provides a mean to deep-clone an object. - /// - public interface IDeepCloneable - { - object DeepClone(); - } + object DeepClone(); } diff --git a/src/Umbraco.Core/Models/IDictionaryItem.cs b/src/Umbraco.Core/Models/IDictionaryItem.cs index f299ce2ac5a8..3656af8f01eb 100644 --- a/src/Umbraco.Core/Models/IDictionaryItem.cs +++ b/src/Umbraco.Core/Models/IDictionaryItem.cs @@ -1,28 +1,25 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public interface IDictionaryItem : IEntity, IRememberBeingDirty { - public interface IDictionaryItem : IEntity, IRememberBeingDirty - { - /// - /// Gets or Sets the Parent Id of the Dictionary Item - /// - [DataMember] - Guid? ParentId { get; set; } + /// + /// Gets or Sets the Parent Id of the Dictionary Item + /// + [DataMember] + Guid? ParentId { get; set; } - /// - /// Gets or sets the Key for the Dictionary Item - /// - [DataMember] - string ItemKey { get; set; } + /// + /// Gets or sets the Key for the Dictionary Item + /// + [DataMember] + string ItemKey { get; set; } - /// - /// Gets or sets a list of translations for the Dictionary Item - /// - [DataMember] - IEnumerable Translations { get; set; } - } + /// + /// Gets or sets a list of translations for the Dictionary Item + /// + [DataMember] + IEnumerable Translations { get; set; } } diff --git a/src/Umbraco.Core/Models/IDictionaryTranslation.cs b/src/Umbraco.Core/Models/IDictionaryTranslation.cs index 445bafd4bab7..54d91881c12f 100644 --- a/src/Umbraco.Core/Models/IDictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/IDictionaryTranslation.cs @@ -1,22 +1,21 @@ using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public interface IDictionaryTranslation : IEntity, IRememberBeingDirty { - public interface IDictionaryTranslation : IEntity, IRememberBeingDirty - { - /// - /// Gets or sets the for the translation - /// - [DataMember] - ILanguage? Language { get; set; } + /// + /// Gets or sets the for the translation + /// + [DataMember] + ILanguage? Language { get; set; } - int LanguageId { get; } + int LanguageId { get; } - /// - /// Gets or sets the translated text - /// - [DataMember] - string Value { get; set; } - } + /// + /// Gets or sets the translated text + /// + [DataMember] + string Value { get; set; } } diff --git a/src/Umbraco.Core/Models/IDomain.cs b/src/Umbraco.Core/Models/IDomain.cs index f9d90dd9eb4b..03449190f07d 100644 --- a/src/Umbraco.Core/Models/IDomain.cs +++ b/src/Umbraco.Core/Models/IDomain.cs @@ -1,17 +1,16 @@ using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public interface IDomain : IEntity, IRememberBeingDirty { - public interface IDomain : IEntity, IRememberBeingDirty - { - int? LanguageId { get; set; } - string DomainName { get; set; } - int? RootContentId { get; set; } - bool IsWildcard { get; } + int? LanguageId { get; set; } + string DomainName { get; set; } + int? RootContentId { get; set; } + bool IsWildcard { get; } - /// - /// Readonly value of the language ISO code for the domain - /// - string? LanguageIsoCode { get; } - } + /// + /// Readonly value of the language ISO code for the domain + /// + string? LanguageIsoCode { get; } } diff --git a/src/Umbraco.Core/Models/IFile.cs b/src/Umbraco.Core/Models/IFile.cs index 216d45d2772d..431307a612bf 100644 --- a/src/Umbraco.Core/Models/IFile.cs +++ b/src/Umbraco.Core/Models/IFile.cs @@ -1,47 +1,45 @@ using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Defines a File +/// +/// Used for Scripts, Stylesheets and Templates +public interface IFile : IEntity, IRememberBeingDirty { /// - /// Defines a File + /// Gets the Name of the File including extension + /// + string? Name { get; } + + /// + /// Gets the Alias of the File, which is the name without the extension + /// + string Alias { get; } + + /// + /// Gets or sets the Path to the File from the root of the file's associated IFileSystem + /// + string Path { get; set; } + + /// + /// Gets the original path of the file + /// + string OriginalPath { get; } + + /// + /// Gets or sets the Content of a File + /// + string? Content { get; set; } + + /// + /// Gets or sets the file's virtual path (i.e. the file path relative to the root of the website) + /// + string? VirtualPath { get; set; } + + /// + /// Called to re-set the OriginalPath to the Path /// - /// Used for Scripts, Stylesheets and Templates - public interface IFile : IEntity, IRememberBeingDirty - { - /// - /// Gets the Name of the File including extension - /// - string? Name { get; } - - /// - /// Gets the Alias of the File, which is the name without the extension - /// - string Alias { get; } - - /// - /// Gets or sets the Path to the File from the root of the file's associated IFileSystem - /// - string Path { get; set; } - - /// - /// Gets the original path of the file - /// - string OriginalPath { get; } - - /// - /// Called to re-set the OriginalPath to the Path - /// - void ResetOriginalPath(); - - /// - /// Gets or sets the Content of a File - /// - string? Content { get; set; } - - /// - /// Gets or sets the file's virtual path (i.e. the file path relative to the root of the website) - /// - string? VirtualPath { get; set; } - - } + void ResetOriginalPath(); } diff --git a/src/Umbraco.Core/Models/IKeyValue.cs b/src/Umbraco.Core/Models/IKeyValue.cs index 2a8c6528bf2c..211690964450 100644 --- a/src/Umbraco.Core/Models/IKeyValue.cs +++ b/src/Umbraco.Core/Models/IKeyValue.cs @@ -1,11 +1,10 @@ using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public interface IKeyValue : IEntity { - public interface IKeyValue : IEntity - { - string Identifier { get; set; } + string Identifier { get; set; } - string? Value { get; set; } - } + string? Value { get; set; } } diff --git a/src/Umbraco.Core/Models/ILanguage.cs b/src/Umbraco.Core/Models/ILanguage.cs index de5170cff651..70663de05683 100644 --- a/src/Umbraco.Core/Models/ILanguage.cs +++ b/src/Umbraco.Core/Models/ILanguage.cs @@ -1,57 +1,60 @@ -using System.Globalization; +using System.Globalization; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a language. +/// +public interface ILanguage : IEntity, IRememberBeingDirty { /// - /// Represents a language. + /// Gets or sets the ISO code of the language. /// - public interface ILanguage : IEntity, IRememberBeingDirty - { - /// - /// Gets or sets the ISO code of the language. - /// - [DataMember] - string IsoCode { get; set; } + [DataMember] + string IsoCode { get; set; } - /// - /// Gets or sets the culture name of the language. - /// - [DataMember] - string CultureName { get; set; } + /// + /// Gets or sets the culture name of the language. + /// + [DataMember] + string CultureName { get; set; } - /// - /// Gets the object for the language. - /// - [IgnoreDataMember] - CultureInfo? CultureInfo { get; } + /// + /// Gets the object for the language. + /// + [IgnoreDataMember] + CultureInfo? CultureInfo { get; } - /// - /// Gets or sets a value indicating whether the language is the default language. - /// - [DataMember] - bool IsDefault { get; set; } + /// + /// Gets or sets a value indicating whether the language is the default language. + /// + [DataMember] + bool IsDefault { get; set; } - /// - /// Gets or sets a value indicating whether the language is mandatory. - /// - /// - /// When a language is mandatory, a multi-lingual document cannot be published - /// without that language being published, and unpublishing that language unpublishes - /// the entire document. - /// - [DataMember] - bool IsMandatory { get; set; } + /// + /// Gets or sets a value indicating whether the language is mandatory. + /// + /// + /// + /// When a language is mandatory, a multi-lingual document cannot be published + /// without that language being published, and unpublishing that language unpublishes + /// the entire document. + /// + /// + [DataMember] + bool IsMandatory { get; set; } - /// - /// Gets or sets the identifier of a fallback language. - /// - /// - /// The fallback language can be used in multi-lingual scenarios, to help - /// define fallback strategies when a value does not exist for a requested language. - /// - [DataMember] - int? FallbackLanguageId { get; set; } - } + /// + /// Gets or sets the identifier of a fallback language. + /// + /// + /// + /// The fallback language can be used in multi-lingual scenarios, to help + /// define fallback strategies when a value does not exist for a requested language. + /// + /// + [DataMember] + int? FallbackLanguageId { get; set; } } diff --git a/src/Umbraco.Core/Models/ILogViewerQuery.cs b/src/Umbraco.Core/Models/ILogViewerQuery.cs index 59a567a6359b..836be53bbbcf 100644 --- a/src/Umbraco.Core/Models/ILogViewerQuery.cs +++ b/src/Umbraco.Core/Models/ILogViewerQuery.cs @@ -1,10 +1,9 @@ using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public interface ILogViewerQuery : IEntity { - public interface ILogViewerQuery : IEntity - { - string? Name { get; set; } - string? Query { get; set; } - } + string? Name { get; set; } + string? Query { get; set; } } diff --git a/src/Umbraco.Core/Models/IMacro.cs b/src/Umbraco.Core/Models/IMacro.cs index e8102b7768b4..a3e071b24b46 100644 --- a/src/Umbraco.Core/Models/IMacro.cs +++ b/src/Umbraco.Core/Models/IMacro.cs @@ -1,65 +1,64 @@ using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Defines a Macro +/// +public interface IMacro : IEntity, IRememberBeingDirty { /// - /// Defines a Macro + /// Gets or sets the alias of the Macro /// - public interface IMacro : IEntity, IRememberBeingDirty - { - /// - /// Gets or sets the alias of the Macro - /// - [DataMember] - string Alias { get; set; } + [DataMember] + string Alias { get; set; } - /// - /// Gets or sets the name of the Macro - /// - [DataMember] - string? Name { get; set; } + /// + /// Gets or sets the name of the Macro + /// + [DataMember] + string? Name { get; set; } - /// - /// Gets or sets a boolean indicating whether the Macro can be used in an Editor - /// - [DataMember] - bool UseInEditor { get; set; } + /// + /// Gets or sets a boolean indicating whether the Macro can be used in an Editor + /// + [DataMember] + bool UseInEditor { get; set; } - /// - /// Gets or sets the Cache Duration for the Macro - /// - [DataMember] - int CacheDuration { get; set; } + /// + /// Gets or sets the Cache Duration for the Macro + /// + [DataMember] + int CacheDuration { get; set; } - /// - /// Gets or sets a boolean indicating whether the Macro should be Cached by Page - /// - [DataMember] - bool CacheByPage { get; set; } + /// + /// Gets or sets a boolean indicating whether the Macro should be Cached by Page + /// + [DataMember] + bool CacheByPage { get; set; } - /// - /// Gets or sets a boolean indicating whether the Macro should be Cached Personally - /// - [DataMember] - bool CacheByMember { get; set; } + /// + /// Gets or sets a boolean indicating whether the Macro should be Cached Personally + /// + [DataMember] + bool CacheByMember { get; set; } - /// - /// Gets or sets a boolean indicating whether the Macro should be rendered in an Editor - /// - [DataMember] - bool DontRender { get; set; } + /// + /// Gets or sets a boolean indicating whether the Macro should be rendered in an Editor + /// + [DataMember] + bool DontRender { get; set; } - /// - /// Gets or set the path to the macro source to render - /// - [DataMember] - string MacroSource { get; set; } + /// + /// Gets or set the path to the macro source to render + /// + [DataMember] + string MacroSource { get; set; } - /// - /// Gets or sets a list of Macro Properties - /// - [DataMember] - MacroPropertyCollection Properties { get; } - } + /// + /// Gets or sets a list of Macro Properties + /// + [DataMember] + MacroPropertyCollection Properties { get; } } diff --git a/src/Umbraco.Core/Models/IMacroProperty.cs b/src/Umbraco.Core/Models/IMacroProperty.cs index d3d589a31e8a..5300e17ae80f 100644 --- a/src/Umbraco.Core/Models/IMacroProperty.cs +++ b/src/Umbraco.Core/Models/IMacroProperty.cs @@ -1,42 +1,38 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Defines a Property for a Macro +/// +public interface IMacroProperty : IValueObject, IDeepCloneable, IRememberBeingDirty { - /// - /// Defines a Property for a Macro - /// - public interface IMacroProperty : IValueObject, IDeepCloneable, IRememberBeingDirty - { - [DataMember] - int Id { get; set; } + [DataMember] int Id { get; set; } - [DataMember] - Guid Key { get; set; } + [DataMember] Guid Key { get; set; } - /// - /// Gets or sets the Alias of the Property - /// - [DataMember] - string Alias { get; set; } + /// + /// Gets or sets the Alias of the Property + /// + [DataMember] + string Alias { get; set; } - /// - /// Gets or sets the Name of the Property - /// - [DataMember] - string? Name { get; set; } + /// + /// Gets or sets the Name of the Property + /// + [DataMember] + string? Name { get; set; } - /// - /// Gets or sets the Sort Order of the Property - /// - [DataMember] - int SortOrder { get; set; } + /// + /// Gets or sets the Sort Order of the Property + /// + [DataMember] + int SortOrder { get; set; } - /// - /// Gets or sets the parameter editor alias - /// - [DataMember] - string EditorAlias { get; set; } - } + /// + /// Gets or sets the parameter editor alias + /// + [DataMember] + string EditorAlias { get; set; } } diff --git a/src/Umbraco.Core/Models/IMedia.cs b/src/Umbraco.Core/Models/IMedia.cs index cbb80fdd5967..597b7f44add4 100644 --- a/src/Umbraco.Core/Models/IMedia.cs +++ b/src/Umbraco.Core/Models/IMedia.cs @@ -1,5 +1,5 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public interface IMedia : IContentBase { - public interface IMedia : IContentBase - { } } diff --git a/src/Umbraco.Core/Models/IMediaType.cs b/src/Umbraco.Core/Models/IMediaType.cs index 13655f0f55dc..11658099dfd0 100644 --- a/src/Umbraco.Core/Models/IMediaType.cs +++ b/src/Umbraco.Core/Models/IMediaType.cs @@ -1,16 +1,14 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Defines a ContentType, which Media is based on +/// +public interface IMediaType : IContentTypeComposition { /// - /// Defines a ContentType, which Media is based on + /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset /// - public interface IMediaType : IContentTypeComposition - { - - /// - /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset - /// - /// - /// - IMediaType DeepCloneWithResetIdentities(string newAlias); - } + /// + /// + IMediaType DeepCloneWithResetIdentities(string newAlias); } diff --git a/src/Umbraco.Core/Models/IMediaUrlGenerator.cs b/src/Umbraco.Core/Models/IMediaUrlGenerator.cs index 4565117dfd3a..a0af9dcc0e20 100644 --- a/src/Umbraco.Core/Models/IMediaUrlGenerator.cs +++ b/src/Umbraco.Core/Models/IMediaUrlGenerator.cs @@ -1,18 +1,17 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Used to generate paths to media items for a specified property editor alias +/// +public interface IMediaUrlGenerator { /// - /// Used to generate paths to media items for a specified property editor alias + /// Tries to get a media path for a given property editor alias /// - public interface IMediaUrlGenerator - { - /// - /// Tries to get a media path for a given property editor alias - /// - /// The property editor alias - /// The value of the property - /// - /// True if a media path was returned - /// - bool TryGetMediaPath(string? propertyEditorAlias, object? value, out string? mediaPath); - } + /// The property editor alias + /// The value of the property + /// + /// True if a media path was returned + /// + bool TryGetMediaPath(string? propertyEditorAlias, object? value, out string? mediaPath); } diff --git a/src/Umbraco.Core/Models/IMember.cs b/src/Umbraco.Core/Models/IMember.cs index 0dba1d804984..ac7e3f08ae9e 100644 --- a/src/Umbraco.Core/Models/IMember.cs +++ b/src/Umbraco.Core/Models/IMember.cs @@ -1,64 +1,67 @@ -using System; -using System.ComponentModel; +using System.ComponentModel; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public interface IMember : IContentBase, IMembershipUser, IHaveAdditionalData { - public interface IMember : IContentBase, IMembershipUser, IHaveAdditionalData - { - /// - /// String alias of the default ContentType - /// - string ContentTypeAlias { get; } + /// + /// String alias of the default ContentType + /// + string ContentTypeAlias { get; } + + /// + /// Internal/Experimental - only used for mapping queries. + /// + /// + /// Adding these to have first level properties instead of the Properties collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + string? LongStringPropertyValue { get; set; } + + /// + /// Internal/Experimental - only used for mapping queries. + /// + /// + /// Adding these to have first level properties instead of the Properties collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + string? ShortStringPropertyValue { get; set; } + + /// + /// Internal/Experimental - only used for mapping queries. + /// + /// + /// Adding these to have first level properties instead of the Properties collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + int IntegerPropertyValue { get; set; } + + /// + /// Internal/Experimental - only used for mapping queries. + /// + /// + /// Adding these to have first level properties instead of the Properties collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + bool BoolPropertyValue { get; set; } + + /// + /// Internal/Experimental - only used for mapping queries. + /// + /// + /// Adding these to have first level properties instead of the Properties collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + DateTime DateTimePropertyValue { get; set; } - /// - /// Internal/Experimental - only used for mapping queries. - /// - /// - /// Adding these to have first level properties instead of the Properties collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - string? LongStringPropertyValue { get; set; } - /// - /// Internal/Experimental - only used for mapping queries. - /// - /// - /// Adding these to have first level properties instead of the Properties collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - string? ShortStringPropertyValue { get; set; } - /// - /// Internal/Experimental - only used for mapping queries. - /// - /// - /// Adding these to have first level properties instead of the Properties collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - int IntegerPropertyValue { get; set; } - /// - /// Internal/Experimental - only used for mapping queries. - /// - /// - /// Adding these to have first level properties instead of the Properties collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - bool BoolPropertyValue { get; set; } - /// - /// Internal/Experimental - only used for mapping queries. - /// - /// - /// Adding these to have first level properties instead of the Properties collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - DateTime DateTimePropertyValue { get; set; } - /// - /// Internal/Experimental - only used for mapping queries. - /// - /// - /// Adding these to have first level properties instead of the Properties collection. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - string? PropertyTypeAlias { get; set; } - } + /// + /// Internal/Experimental - only used for mapping queries. + /// + /// + /// Adding these to have first level properties instead of the Properties collection. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + string? PropertyTypeAlias { get; set; } } diff --git a/src/Umbraco.Core/Models/IMemberGroup.cs b/src/Umbraco.Core/Models/IMemberGroup.cs index 80d4a16ad6a2..5c3de74e5930 100644 --- a/src/Umbraco.Core/Models/IMemberGroup.cs +++ b/src/Umbraco.Core/Models/IMemberGroup.cs @@ -1,20 +1,19 @@ using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a member type +/// +public interface IMemberGroup : IEntity, IRememberBeingDirty, IHaveAdditionalData { /// - /// Represents a member type + /// The name of the member group /// - public interface IMemberGroup : IEntity, IRememberBeingDirty, IHaveAdditionalData - { - /// - /// The name of the member group - /// - string? Name { get; set; } + string? Name { get; set; } - /// - /// Profile of the user who created this Entity - /// - int CreatorId { get; set; } - } + /// + /// Profile of the user who created this Entity + /// + int CreatorId { get; set; } } diff --git a/src/Umbraco.Core/Models/IMemberType.cs b/src/Umbraco.Core/Models/IMemberType.cs index 324601efde11..bd698ec58e78 100644 --- a/src/Umbraco.Core/Models/IMemberType.cs +++ b/src/Umbraco.Core/Models/IMemberType.cs @@ -1,50 +1,49 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Defines a MemberType, which Member is based on +/// +public interface IMemberType : IContentTypeComposition { /// - /// Defines a MemberType, which Member is based on + /// Gets a boolean indicating whether a Property is editable by the Member. /// - public interface IMemberType : IContentTypeComposition - { - /// - /// Gets a boolean indicating whether a Property is editable by the Member. - /// - /// PropertyType Alias of the Property to check - /// - bool MemberCanEditProperty(string? propertyTypeAlias); + /// PropertyType Alias of the Property to check + /// + bool MemberCanEditProperty(string? propertyTypeAlias); - /// - /// Gets a boolean indicating whether a Property is visible on the Members profile. - /// - /// PropertyType Alias of the Property to check - /// - bool MemberCanViewProperty(string propertyTypeAlias); + /// + /// Gets a boolean indicating whether a Property is visible on the Members profile. + /// + /// PropertyType Alias of the Property to check + /// + bool MemberCanViewProperty(string propertyTypeAlias); - /// - /// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile. - /// - /// PropertyType Alias of the Property to check - /// - bool IsSensitiveProperty(string propertyTypeAlias); + /// + /// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile. + /// + /// PropertyType Alias of the Property to check + /// + bool IsSensitiveProperty(string propertyTypeAlias); - /// - /// Sets a boolean indicating whether a Property is editable by the Member. - /// - /// PropertyType Alias of the Property to set - /// Boolean value, true or false - void SetMemberCanEditProperty(string propertyTypeAlias, bool value); + /// + /// Sets a boolean indicating whether a Property is editable by the Member. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + void SetMemberCanEditProperty(string propertyTypeAlias, bool value); - /// - /// Sets a boolean indicating whether a Property is visible on the Members profile. - /// - /// PropertyType Alias of the Property to set - /// Boolean value, true or false - void SetMemberCanViewProperty(string propertyTypeAlias, bool value); + /// + /// Sets a boolean indicating whether a Property is visible on the Members profile. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + void SetMemberCanViewProperty(string propertyTypeAlias, bool value); - /// - /// Sets a boolean indicating whether a Property is a sensitive value on the Members profile. - /// - /// PropertyType Alias of the Property to set - /// Boolean value, true or false - void SetIsSensitiveProperty(string propertyTypeAlias, bool value); - } + /// + /// Sets a boolean indicating whether a Property is a sensitive value on the Members profile. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + void SetIsSensitiveProperty(string propertyTypeAlias, bool value); } diff --git a/src/Umbraco.Core/Models/IMigrationEntry.cs b/src/Umbraco.Core/Models/IMigrationEntry.cs index a3d11e851a76..2f864b750bfb 100644 --- a/src/Umbraco.Core/Models/IMigrationEntry.cs +++ b/src/Umbraco.Core/Models/IMigrationEntry.cs @@ -1,11 +1,10 @@ using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Semver; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public interface IMigrationEntry : IEntity, IRememberBeingDirty { - public interface IMigrationEntry : IEntity, IRememberBeingDirty - { - string? MigrationName { get; set; } - SemVersion? Version { get; set; } - } + string? MigrationName { get; set; } + SemVersion? Version { get; set; } } diff --git a/src/Umbraco.Core/Models/IPartialView.cs b/src/Umbraco.Core/Models/IPartialView.cs index c45b76534de1..613bd77391a1 100644 --- a/src/Umbraco.Core/Models/IPartialView.cs +++ b/src/Umbraco.Core/Models/IPartialView.cs @@ -1,7 +1,6 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public interface IPartialView : IFile { - public interface IPartialView : IFile - { - PartialViewType ViewType { get; } - } + PartialViewType ViewType { get; } } diff --git a/src/Umbraco.Core/Models/IProperty.cs b/src/Umbraco.Core/Models/IProperty.cs index 9ed37c34e1ad..a5f5abfdf67e 100644 --- a/src/Umbraco.Core/Models/IProperty.cs +++ b/src/Umbraco.Core/Models/IProperty.cs @@ -1,40 +1,38 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public interface IProperty : IEntity, IRememberBeingDirty { - public interface IProperty : IEntity, IRememberBeingDirty - { - - ValueStorageType ValueStorageType { get; } - /// - /// Returns the PropertyType, which this Property is based on - /// - IPropertyType PropertyType { get; } - - /// - /// Gets the list of values. - /// - IReadOnlyCollection Values { get; set; } - - /// - /// Returns the Alias of the PropertyType, which this Property is based on - /// - string Alias { get; } - - /// - /// Gets the value. - /// - object? GetValue(string? culture = null, string? segment = null, bool published = false); - - /// - /// Sets a value. - /// - void SetValue(object? value, string? culture = null, string? segment = null); - - int PropertyTypeId { get; } - void PublishValues(string? culture = "*", string segment = "*"); - void UnpublishValues(string? culture = "*", string segment = "*"); - - } + ValueStorageType ValueStorageType { get; } + + /// + /// Returns the PropertyType, which this Property is based on + /// + IPropertyType PropertyType { get; } + + /// + /// Gets the list of values. + /// + IReadOnlyCollection Values { get; set; } + + /// + /// Returns the Alias of the PropertyType, which this Property is based on + /// + string Alias { get; } + + int PropertyTypeId { get; } + + /// + /// Gets the value. + /// + object? GetValue(string? culture = null, string? segment = null, bool published = false); + + /// + /// Sets a value. + /// + void SetValue(object? value, string? culture = null, string? segment = null); + + void PublishValues(string? culture = "*", string segment = "*"); + void UnpublishValues(string? culture = "*", string segment = "*"); } diff --git a/src/Umbraco.Core/Models/IPropertyCollection.cs b/src/Umbraco.Core/Models/IPropertyCollection.cs index d39a214fdd97..78893e4a9bac 100644 --- a/src/Umbraco.Core/Models/IPropertyCollection.cs +++ b/src/Umbraco.Core/Models/IPropertyCollection.cs @@ -1,40 +1,38 @@ -using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public interface IPropertyCollection : IEnumerable, IDeepCloneable, INotifyCollectionChanged { - public interface IPropertyCollection : IEnumerable, IDeepCloneable, INotifyCollectionChanged - { - bool TryGetValue(string propertyTypeAlias, [MaybeNullWhen(false)] out IProperty property); - bool Contains(string key); - - /// - /// Ensures that the collection contains properties for the specified property types. - /// - void EnsurePropertyTypes(IEnumerable propertyTypes); - - /// - /// Ensures that the collection does not contain properties not in the specified property types. - /// - void EnsureCleanPropertyTypes(IEnumerable propertyTypes); - - /// - /// Gets the property with the specified alias. - /// - IProperty? this[string name] { get; } - - /// - /// Gets the property at the specified index. - /// - IProperty? this[int index] { get; } - - /// - /// Adds or updates a property. - /// - void Add(IProperty property); - - int Count { get; } - void ClearCollectionChangedEvents(); - } + /// + /// Gets the property with the specified alias. + /// + IProperty? this[string name] { get; } + + /// + /// Gets the property at the specified index. + /// + IProperty? this[int index] { get; } + + int Count { get; } + bool TryGetValue(string propertyTypeAlias, [MaybeNullWhen(false)] out IProperty property); + bool Contains(string key); + + /// + /// Ensures that the collection contains properties for the specified property types. + /// + void EnsurePropertyTypes(IEnumerable propertyTypes); + + /// + /// Ensures that the collection does not contain properties not in the specified property types. + /// + void EnsureCleanPropertyTypes(IEnumerable propertyTypes); + + /// + /// Adds or updates a property. + /// + void Add(IProperty property); + + void ClearCollectionChangedEvents(); } diff --git a/src/Umbraco.Core/Models/IPropertyType.cs b/src/Umbraco.Core/Models/IPropertyType.cs index b820c1d7aabb..a48f8e01ae74 100644 --- a/src/Umbraco.Core/Models/IPropertyType.cs +++ b/src/Umbraco.Core/Models/IPropertyType.cs @@ -1,91 +1,89 @@ -using System; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public interface IPropertyType : IEntity, IRememberBeingDirty { - public interface IPropertyType : IEntity, IRememberBeingDirty - { - /// - /// Gets of sets the name of the property type. - /// - string Name { get; set; } - - /// - /// Gets of sets the alias of the property type. - /// - string Alias { get; set; } - - /// - /// Gets of sets the description of the property type. - /// - string? Description { get; set; } - - /// - /// Gets or sets the identifier of the datatype for this property type. - /// - int DataTypeId { get; set; } - - Guid DataTypeKey { get; set; } - - /// - /// Gets or sets the alias of the property editor for this property type. - /// - string PropertyEditorAlias { get; set; } - - /// - /// Gets or sets the database type for storing value for this property type. - /// - ValueStorageType ValueStorageType { get; set; } - - /// - /// Gets or sets the identifier of the property group this property type belongs to. - /// - /// For generic properties, the value is null. - Lazy? PropertyGroupId { get; set; } - - /// - /// Gets of sets a value indicating whether a value for this property type is required. - /// - bool Mandatory { get; set; } - - /// - /// Gets or sets a value indicating whether the label of this property type should be displayed on top. - /// - bool LabelOnTop { get; set; } - - /// - /// Gets of sets the sort order of the property type. - /// - int SortOrder { get; set; } - - /// - /// Gets or sets the regular expression validating the property values. - /// - string? ValidationRegExp { get; set; } - - bool SupportsPublishing { get; set; } - - /// - /// Gets or sets the content variation of the property type. - /// - ContentVariation Variations { get; set; } - - /// - /// Determines whether the property type supports a combination of culture and segment. - /// - /// The culture. - /// The segment. - /// A value indicating whether wildcards are valid. - bool SupportsVariation(string? culture, string? segment, bool wildcards = false); - - /// - /// Gets or sets the custom validation message used when a value for this PropertyType is required - /// - string? MandatoryMessage { get; set; } - - /// - /// Gets or sets the custom validation message used when a pattern for this PropertyType must be matched - /// - string? ValidationRegExpMessage { get; set; } - } + /// + /// Gets of sets the name of the property type. + /// + string Name { get; set; } + + /// + /// Gets of sets the alias of the property type. + /// + string Alias { get; set; } + + /// + /// Gets of sets the description of the property type. + /// + string? Description { get; set; } + + /// + /// Gets or sets the identifier of the datatype for this property type. + /// + int DataTypeId { get; set; } + + Guid DataTypeKey { get; set; } + + /// + /// Gets or sets the alias of the property editor for this property type. + /// + string PropertyEditorAlias { get; set; } + + /// + /// Gets or sets the database type for storing value for this property type. + /// + ValueStorageType ValueStorageType { get; set; } + + /// + /// Gets or sets the identifier of the property group this property type belongs to. + /// + /// For generic properties, the value is null. + Lazy? PropertyGroupId { get; set; } + + /// + /// Gets of sets a value indicating whether a value for this property type is required. + /// + bool Mandatory { get; set; } + + /// + /// Gets or sets a value indicating whether the label of this property type should be displayed on top. + /// + bool LabelOnTop { get; set; } + + /// + /// Gets of sets the sort order of the property type. + /// + int SortOrder { get; set; } + + /// + /// Gets or sets the regular expression validating the property values. + /// + string? ValidationRegExp { get; set; } + + bool SupportsPublishing { get; set; } + + /// + /// Gets or sets the content variation of the property type. + /// + ContentVariation Variations { get; set; } + + /// + /// Gets or sets the custom validation message used when a value for this PropertyType is required + /// + string? MandatoryMessage { get; set; } + + /// + /// Gets or sets the custom validation message used when a pattern for this PropertyType must be matched + /// + string? ValidationRegExpMessage { get; set; } + + /// + /// Determines whether the property type supports a combination of culture and segment. + /// + /// The culture. + /// The segment. + /// A value indicating whether wildcards are valid. + bool SupportsVariation(string? culture, string? segment, bool wildcards = false); } diff --git a/src/Umbraco.Core/Models/IPropertyValue.cs b/src/Umbraco.Core/Models/IPropertyValue.cs index 77e9e1dc2568..ef95cd2a01fa 100644 --- a/src/Umbraco.Core/Models/IPropertyValue.cs +++ b/src/Umbraco.Core/Models/IPropertyValue.cs @@ -1,34 +1,37 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public interface IPropertyValue { - public interface IPropertyValue - { - /// - /// Gets or sets the culture of the property. - /// - /// The culture is either null (invariant) or a non-empty string. If the property is - /// set with an empty or whitespace value, its value is converted to null. - string? Culture { get; set; } + /// + /// Gets or sets the culture of the property. + /// + /// + /// The culture is either null (invariant) or a non-empty string. If the property is + /// set with an empty or whitespace value, its value is converted to null. + /// + string? Culture { get; set; } - /// - /// Gets or sets the segment of the property. - /// - /// The segment is either null (neutral) or a non-empty string. If the property is - /// set with an empty or whitespace value, its value is converted to null. - string? Segment { get; set; } + /// + /// Gets or sets the segment of the property. + /// + /// + /// The segment is either null (neutral) or a non-empty string. If the property is + /// set with an empty or whitespace value, its value is converted to null. + /// + string? Segment { get; set; } - /// - /// Gets or sets the edited value of the property. - /// - object? EditedValue { get; set; } + /// + /// Gets or sets the edited value of the property. + /// + object? EditedValue { get; set; } - /// - /// Gets or sets the published value of the property. - /// - object? PublishedValue { get; set; } + /// + /// Gets or sets the published value of the property. + /// + object? PublishedValue { get; set; } - /// - /// Clones the property value. - /// - IPropertyValue Clone(); - } + /// + /// Clones the property value. + /// + IPropertyValue Clone(); } diff --git a/src/Umbraco.Core/Models/IReadOnlyContentBase.cs b/src/Umbraco.Core/Models/IReadOnlyContentBase.cs index f7518140f531..37b5a5ddea5e 100644 --- a/src/Umbraco.Core/Models/IReadOnlyContentBase.cs +++ b/src/Umbraco.Core/Models/IReadOnlyContentBase.cs @@ -1,72 +1,69 @@ -using System; +namespace Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Models +public interface IReadOnlyContentBase { - public interface IReadOnlyContentBase - { - /// - /// Gets the integer identifier of the entity. - /// - int Id { get; } + /// + /// Gets the integer identifier of the entity. + /// + int Id { get; } - /// - /// Gets the Guid unique identifier of the entity. - /// - Guid Key { get; } + /// + /// Gets the Guid unique identifier of the entity. + /// + Guid Key { get; } - /// - /// Gets the creation date. - /// - DateTime CreateDate { get; } + /// + /// Gets the creation date. + /// + DateTime CreateDate { get; } - /// - /// Gets the last update date. - /// - DateTime UpdateDate { get; } + /// + /// Gets the last update date. + /// + DateTime UpdateDate { get; } - /// - /// Gets the name of the entity. - /// - string? Name { get; } + /// + /// Gets the name of the entity. + /// + string? Name { get; } - /// - /// Gets the identifier of the user who created this entity. - /// - int CreatorId { get; } + /// + /// Gets the identifier of the user who created this entity. + /// + int CreatorId { get; } - /// - /// Gets the identifier of the parent entity. - /// - int ParentId { get; } + /// + /// Gets the identifier of the parent entity. + /// + int ParentId { get; } - /// - /// Gets the level of the entity. - /// - int Level { get; } + /// + /// Gets the level of the entity. + /// + int Level { get; } - /// - /// Gets the path to the entity. - /// - string? Path { get; } + /// + /// Gets the path to the entity. + /// + string? Path { get; } - /// - /// Gets the sort order of the entity. - /// - int SortOrder { get; } + /// + /// Gets the sort order of the entity. + /// + int SortOrder { get; } - /// - /// Gets the content type id - /// - int ContentTypeId { get; } + /// + /// Gets the content type id + /// + int ContentTypeId { get; } - /// - /// Gets the identifier of the writer. - /// - int WriterId { get; } + /// + /// Gets the identifier of the writer. + /// + int WriterId { get; } - /// - /// Gets the version identifier. - /// - int VersionId { get; } - } + /// + /// Gets the version identifier. + /// + int VersionId { get; } } diff --git a/src/Umbraco.Core/Models/IRedirectUrl.cs b/src/Umbraco.Core/Models/IRedirectUrl.cs index 18498837b416..23fa75ecca47 100644 --- a/src/Umbraco.Core/Models/IRedirectUrl.cs +++ b/src/Umbraco.Core/Models/IRedirectUrl.cs @@ -1,44 +1,41 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a redirect URL. +/// +public interface IRedirectUrl : IEntity, IRememberBeingDirty { /// - /// Represents a redirect URL. + /// Gets or sets the identifier of the content item. /// - public interface IRedirectUrl : IEntity, IRememberBeingDirty - { - /// - /// Gets or sets the identifier of the content item. - /// - [DataMember] - int ContentId { get; set; } - - /// - /// Gets or sets the unique key identifying the content item. - /// - [DataMember] - Guid ContentKey { get; set; } + [DataMember] + int ContentId { get; set; } - /// - /// Gets or sets the redirect URL creation date. - /// - [DataMember] - DateTime CreateDateUtc { get; set; } + /// + /// Gets or sets the unique key identifying the content item. + /// + [DataMember] + Guid ContentKey { get; set; } - /// - /// Gets or sets the culture. - /// - [DataMember] - string? Culture { get; set; } + /// + /// Gets or sets the redirect URL creation date. + /// + [DataMember] + DateTime CreateDateUtc { get; set; } - /// - /// Gets or sets the redirect URL route. - /// - /// Is a proper Umbraco route eg /path/to/foo or 123/path/tofoo. - [DataMember] - string Url { get; set; } + /// + /// Gets or sets the culture. + /// + [DataMember] + string? Culture { get; set; } - } + /// + /// Gets or sets the redirect URL route. + /// + /// Is a proper Umbraco route eg /path/to/foo or 123/path/tofoo. + [DataMember] + string Url { get; set; } } diff --git a/src/Umbraco.Core/Models/IRelation.cs b/src/Umbraco.Core/Models/IRelation.cs index 0370bbe61f2f..16cba492e747 100644 --- a/src/Umbraco.Core/Models/IRelation.cs +++ b/src/Umbraco.Core/Models/IRelation.cs @@ -1,45 +1,41 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public interface IRelation : IEntity, IRememberBeingDirty { - public interface IRelation : IEntity, IRememberBeingDirty - { - /// - /// Gets or sets the Parent Id of the Relation (Source) - /// - [DataMember] - int ParentId { get; set; } - - [DataMember] - Guid ParentObjectType { get; set; } - - /// - /// Gets or sets the Child Id of the Relation (Destination) - /// - [DataMember] - int ChildId { get; set; } - - [DataMember] - Guid ChildObjectType { get; set; } - - /// - /// Gets or sets the for the Relation - /// - [DataMember] - IRelationType RelationType { get; set; } - - /// - /// Gets or sets a comment for the Relation - /// - [DataMember] - string? Comment { get; set; } - - /// - /// Gets the Id of the that this Relation is based on. - /// - [IgnoreDataMember] - int RelationTypeId { get; } - } + /// + /// Gets or sets the Parent Id of the Relation (Source) + /// + [DataMember] + int ParentId { get; set; } + + [DataMember] Guid ParentObjectType { get; set; } + + /// + /// Gets or sets the Child Id of the Relation (Destination) + /// + [DataMember] + int ChildId { get; set; } + + [DataMember] Guid ChildObjectType { get; set; } + + /// + /// Gets or sets the for the Relation + /// + [DataMember] + IRelationType RelationType { get; set; } + + /// + /// Gets or sets a comment for the Relation + /// + [DataMember] + string? Comment { get; set; } + + /// + /// Gets the Id of the that this Relation is based on. + /// + [IgnoreDataMember] + int RelationTypeId { get; } } diff --git a/src/Umbraco.Core/Models/IRelationType.cs b/src/Umbraco.Core/Models/IRelationType.cs index cbc485f64bfe..262b8835f858 100644 --- a/src/Umbraco.Core/Models/IRelationType.cs +++ b/src/Umbraco.Core/Models/IRelationType.cs @@ -1,50 +1,48 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public interface IRelationTypeWithIsDependency : IRelationType { - public interface IRelationTypeWithIsDependency : IRelationType - { - /// - /// Gets or sets a boolean indicating whether the RelationType should be returned in "Used by"-queries. - /// - [DataMember] - bool IsDependency { get; set; } - } + /// + /// Gets or sets a boolean indicating whether the RelationType should be returned in "Used by"-queries. + /// + [DataMember] + bool IsDependency { get; set; } +} - public interface IRelationType : IEntity, IRememberBeingDirty - { - /// - /// Gets or sets the Name of the RelationType - /// - [DataMember] - string? Name { get; set; } +public interface IRelationType : IEntity, IRememberBeingDirty +{ + /// + /// Gets or sets the Name of the RelationType + /// + [DataMember] + string? Name { get; set; } - /// - /// Gets or sets the Alias of the RelationType - /// - [DataMember] - string Alias { get; set; } + /// + /// Gets or sets the Alias of the RelationType + /// + [DataMember] + string Alias { get; set; } - /// - /// Gets or sets a boolean indicating whether the RelationType is Bidirectional (true) or Parent to Child (false) - /// - [DataMember] - bool IsBidirectional { get; set; } + /// + /// Gets or sets a boolean indicating whether the RelationType is Bidirectional (true) or Parent to Child (false) + /// + [DataMember] + bool IsBidirectional { get; set; } - /// - /// Gets or sets the Parents object type id - /// - /// Corresponds to the NodeObjectType in the umbracoNode table - [DataMember] - Guid? ParentObjectType { get; set; } + /// + /// Gets or sets the Parents object type id + /// + /// Corresponds to the NodeObjectType in the umbracoNode table + [DataMember] + Guid? ParentObjectType { get; set; } - /// - /// Gets or sets the Childs object type id - /// - /// Corresponds to the NodeObjectType in the umbracoNode table - [DataMember] - Guid? ChildObjectType { get; set; } - } + /// + /// Gets or sets the Childs object type id + /// + /// Corresponds to the NodeObjectType in the umbracoNode table + [DataMember] + Guid? ChildObjectType { get; set; } } diff --git a/src/Umbraco.Core/Models/IScript.cs b/src/Umbraco.Core/Models/IScript.cs index 6a07d2aa2540..b12bc131b123 100644 --- a/src/Umbraco.Core/Models/IScript.cs +++ b/src/Umbraco.Core/Models/IScript.cs @@ -1,7 +1,5 @@ -namespace Umbraco.Cms.Core.Models -{ - public interface IScript : IFile - { +namespace Umbraco.Cms.Core.Models; - } +public interface IScript : IFile +{ } diff --git a/src/Umbraco.Core/Models/IServerRegistration.cs b/src/Umbraco.Core/Models/IServerRegistration.cs index 7d8c0f58c101..525ed30163e2 100644 --- a/src/Umbraco.Core/Models/IServerRegistration.cs +++ b/src/Umbraco.Core/Models/IServerRegistration.cs @@ -1,36 +1,34 @@ -using System; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public interface IServerRegistration : IServerAddress, IEntity, IRememberBeingDirty { - public interface IServerRegistration : IServerAddress, IEntity, IRememberBeingDirty - { - /// - /// Gets or sets the server unique identity. - /// - string? ServerIdentity { get; set; } + /// + /// Gets or sets the server unique identity. + /// + string? ServerIdentity { get; set; } - new string? ServerAddress { get; set; } + new string? ServerAddress { get; set; } - /// - /// Gets or sets a value indicating whether the server is active. - /// - bool IsActive { get; set; } + /// + /// Gets or sets a value indicating whether the server is active. + /// + bool IsActive { get; set; } - /// - /// Gets or sets a value indicating whether the server is has the SchedulingPublisher role. - /// - bool IsSchedulingPublisher { get; set; } + /// + /// Gets or sets a value indicating whether the server is has the SchedulingPublisher role. + /// + bool IsSchedulingPublisher { get; set; } - /// - /// Gets the date and time the registration was created. - /// - DateTime Registered { get; set; } + /// + /// Gets the date and time the registration was created. + /// + DateTime Registered { get; set; } - /// - /// Gets the date and time the registration was last accessed. - /// - DateTime Accessed { get; set; } - } + /// + /// Gets the date and time the registration was last accessed. + /// + DateTime Accessed { get; set; } } diff --git a/src/Umbraco.Core/Models/ISimpleContentType.cs b/src/Umbraco.Core/Models/ISimpleContentType.cs index 503946ba9630..af7a0a1506d6 100644 --- a/src/Umbraco.Core/Models/ISimpleContentType.cs +++ b/src/Umbraco.Core/Models/ISimpleContentType.cs @@ -1,63 +1,64 @@ -using System; +namespace Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Models +/// +/// Represents a simplified view of a content type. +/// +public interface ISimpleContentType { + int Id { get; } + Guid Key { get; } + string? Name { get; } + + /// + /// Gets the alias of the content type. + /// + string Alias { get; } + + /// + /// Gets the default template of the content type. + /// + ITemplate? DefaultTemplate { get; } + + /// + /// Gets the content variation of the content type. + /// + ContentVariation Variations { get; } + /// - /// Represents a simplified view of a content type. - /// - public interface ISimpleContentType - { - int Id { get; } - Guid Key { get; } - string? Name { get; } - - /// - /// Gets the alias of the content type. - /// - string Alias { get; } - - /// - /// Gets the default template of the content type. - /// - ITemplate? DefaultTemplate { get; } - - /// - /// Gets the content variation of the content type. - /// - ContentVariation Variations { get; } - - /// - /// Gets the icon of the content type. - /// - string? Icon { get; } - - /// - /// Gets a value indicating whether the content type is a container. - /// - bool IsContainer { get; } - - /// - /// Gets a value indicating whether content of that type can be created at the root of the tree. - /// - bool AllowedAsRoot { get; } - - /// - /// Gets a value indicating whether the content type is an element content type. - /// - bool IsElement { get; } - - /// - /// Validates that a combination of culture and segment is valid for the content type properties. - /// - /// The culture. - /// The segment. - /// A value indicating whether wildcard are supported. - /// True if the combination is valid; otherwise false. - /// - /// The combination must be valid for properties of the content type. For instance, if the content type varies by culture, - /// then an invariant culture is valid, because some properties may be invariant. On the other hand, if the content type is invariant, - /// then a variant culture is invalid, because no property could possibly vary by culture. - /// - bool SupportsPropertyVariation(string? culture, string segment, bool wildcards = false); - } + /// Gets the icon of the content type. + /// + string? Icon { get; } + + /// + /// Gets a value indicating whether the content type is a container. + /// + bool IsContainer { get; } + + /// + /// Gets a value indicating whether content of that type can be created at the root of the tree. + /// + bool AllowedAsRoot { get; } + + /// + /// Gets a value indicating whether the content type is an element content type. + /// + bool IsElement { get; } + + /// + /// Validates that a combination of culture and segment is valid for the content type properties. + /// + /// The culture. + /// The segment. + /// A value indicating whether wildcard are supported. + /// True if the combination is valid; otherwise false. + /// + /// + /// The combination must be valid for properties of the content type. For instance, if the content type varies by + /// culture, + /// then an invariant culture is valid, because some properties may be invariant. On the other hand, if the content + /// type is invariant, + /// then a variant culture is invalid, because no property could possibly vary by culture. + /// + /// + bool SupportsPropertyVariation(string? culture, string segment, bool wildcards = false); } diff --git a/src/Umbraco.Core/Models/IStylesheet.cs b/src/Umbraco.Core/Models/IStylesheet.cs index e7710f26df3b..f1024f7154f2 100644 --- a/src/Umbraco.Core/Models/IStylesheet.cs +++ b/src/Umbraco.Core/Models/IStylesheet.cs @@ -1,29 +1,25 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Models +public interface IStylesheet : IFile { - public interface IStylesheet : IFile - { - /// - /// Returns a list of umbraco back office enabled stylesheet properties - /// - /// - /// An umbraco back office enabled stylesheet property has a special prefix, for example: - /// - /// /** umb_name: MyPropertyName */ p { font-size: 1em; } - /// - IEnumerable? Properties { get; } + /// + /// Returns a list of umbraco back office enabled stylesheet properties + /// + /// + /// An umbraco back office enabled stylesheet property has a special prefix, for example: + /// /** umb_name: MyPropertyName */ p { font-size: 1em; } + /// + IEnumerable? Properties { get; } - /// - /// Adds an Umbraco stylesheet property for use in the back office - /// - /// - void AddProperty(IStylesheetProperty property); + /// + /// Adds an Umbraco stylesheet property for use in the back office + /// + /// + void AddProperty(IStylesheetProperty property); - /// - /// Removes an Umbraco stylesheet property - /// - /// - void RemoveProperty(string name); - } + /// + /// Removes an Umbraco stylesheet property + /// + /// + void RemoveProperty(string name); } diff --git a/src/Umbraco.Core/Models/IStylesheetProperty.cs b/src/Umbraco.Core/Models/IStylesheetProperty.cs index 781fb474b2ee..12aad52a8ccc 100644 --- a/src/Umbraco.Core/Models/IStylesheetProperty.cs +++ b/src/Umbraco.Core/Models/IStylesheetProperty.cs @@ -1,11 +1,10 @@ using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public interface IStylesheetProperty : IRememberBeingDirty { - public interface IStylesheetProperty : IRememberBeingDirty - { - string Alias { get; set; } - string Name { get; } - string Value { get; set; } - } + string Alias { get; set; } + string Name { get; } + string Value { get; set; } } diff --git a/src/Umbraco.Core/Models/ITag.cs b/src/Umbraco.Core/Models/ITag.cs index 79840481bb2e..621b8ef9d70e 100644 --- a/src/Umbraco.Core/Models/ITag.cs +++ b/src/Umbraco.Core/Models/ITag.cs @@ -1,35 +1,34 @@ using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a tag entity. +/// +public interface ITag : IEntity, IRememberBeingDirty { /// - /// Represents a tag entity. + /// Gets or sets the tag group. /// - public interface ITag : IEntity, IRememberBeingDirty - { - /// - /// Gets or sets the tag group. - /// - [DataMember] - string Group { get; set; } + [DataMember] + string Group { get; set; } - /// - /// Gets or sets the tag text. - /// - [DataMember] - string Text { get; set; } + /// + /// Gets or sets the tag text. + /// + [DataMember] + string Text { get; set; } - /// - /// Gets or sets the tag language. - /// - [DataMember] - int? LanguageId { get; set; } + /// + /// Gets or sets the tag language. + /// + [DataMember] + int? LanguageId { get; set; } - /// - /// Gets the number of nodes tagged with this tag. - /// - /// Only when returning from queries. - int NodeCount { get; } - } + /// + /// Gets the number of nodes tagged with this tag. + /// + /// Only when returning from queries. + int NodeCount { get; } } diff --git a/src/Umbraco.Core/Models/ITemplate.cs b/src/Umbraco.Core/Models/ITemplate.cs index e20dcc55fa0b..17972ab5a0b9 100644 --- a/src/Umbraco.Core/Models/ITemplate.cs +++ b/src/Umbraco.Core/Models/ITemplate.cs @@ -1,36 +1,35 @@ using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Defines a Template File (Mvc View) +/// +public interface ITemplate : IFile, IRememberBeingDirty, ICanBeDirty { /// - /// Defines a Template File (Mvc View) + /// Gets the Name of the File including extension /// - public interface ITemplate : IFile, IRememberBeingDirty, ICanBeDirty - { - /// - /// Gets the Name of the File including extension - /// - new string? Name { get; set; } + new string? Name { get; set; } - /// - /// Gets the Alias of the File, which is the name without the extension - /// - new string Alias { get; set; } + /// + /// Gets the Alias of the File, which is the name without the extension + /// + new string Alias { get; set; } - /// - /// Returns true if the template is used as a layout for other templates (i.e. it has 'children') - /// - bool IsMasterTemplate { get; set; } + /// + /// Returns true if the template is used as a layout for other templates (i.e. it has 'children') + /// + bool IsMasterTemplate { get; set; } - /// - /// returns the master template alias - /// - string? MasterTemplateAlias { get; } + /// + /// returns the master template alias + /// + string? MasterTemplateAlias { get; } - /// - /// Set the mastertemplate - /// - /// - void SetMasterTemplate(ITemplate? masterTemplate); - } + /// + /// Set the mastertemplate + /// + /// + void SetMasterTemplate(ITemplate? masterTemplate); } diff --git a/src/Umbraco.Core/Models/ITwoFactorLogin.cs b/src/Umbraco.Core/Models/ITwoFactorLogin.cs index ca005309b26c..ee68d48ca2c7 100644 --- a/src/Umbraco.Core/Models/ITwoFactorLogin.cs +++ b/src/Umbraco.Core/Models/ITwoFactorLogin.cs @@ -1,12 +1,10 @@ -using System; -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public interface ITwoFactorLogin : IEntity, IRememberBeingDirty { - public interface ITwoFactorLogin: IEntity, IRememberBeingDirty - { - string ProviderName { get; } - string Secret { get; } - Guid UserOrMemberKey { get; } - } + string ProviderName { get; } + string Secret { get; } + Guid UserOrMemberKey { get; } } diff --git a/src/Umbraco.Core/Models/IconModel.cs b/src/Umbraco.Core/Models/IconModel.cs index 6b09c0860225..efefb13d6b23 100644 --- a/src/Umbraco.Core/Models/IconModel.cs +++ b/src/Umbraco.Core/Models/IconModel.cs @@ -1,8 +1,7 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public class IconModel { - public class IconModel - { - public string Name { get; set; } = null!; - public string SvgString { get; set; } = null!; - } + public string Name { get; set; } = null!; + public string SvgString { get; set; } = null!; } diff --git a/src/Umbraco.Core/Models/ImageCropAnchor.cs b/src/Umbraco.Core/Models/ImageCropAnchor.cs index 118f7348aed6..f809a8e6c21d 100644 --- a/src/Umbraco.Core/Models/ImageCropAnchor.cs +++ b/src/Umbraco.Core/Models/ImageCropAnchor.cs @@ -1,15 +1,14 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public enum ImageCropAnchor { - public enum ImageCropAnchor - { - Center, - Top, - Right, - Bottom, - Left, - TopLeft, - TopRight, - BottomLeft, - BottomRight - } + Center, + Top, + Right, + Bottom, + Left, + TopLeft, + TopRight, + BottomLeft, + BottomRight } diff --git a/src/Umbraco.Core/Models/ImageCropMode.cs b/src/Umbraco.Core/Models/ImageCropMode.cs index 1cd7294a58df..9589b5557218 100644 --- a/src/Umbraco.Core/Models/ImageCropMode.cs +++ b/src/Umbraco.Core/Models/ImageCropMode.cs @@ -1,35 +1,41 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public enum ImageCropMode { - public enum ImageCropMode - { - /// - /// Resizes the image to the given dimensions. If the set dimensions do not match the aspect ratio of the original image then the output is cropped to match the new aspect ratio. - /// - Crop, + /// + /// Resizes the image to the given dimensions. If the set dimensions do not match the aspect ratio of the original + /// image then the output is cropped to match the new aspect ratio. + /// + Crop, - /// - /// Resizes the image to the given dimensions. If the set dimensions do not match the aspect ratio of the original image then the output is resized to the maximum possible value in each direction while maintaining the original aspect ratio. - /// - Max, + /// + /// Resizes the image to the given dimensions. If the set dimensions do not match the aspect ratio of the original + /// image then the output is resized to the maximum possible value in each direction while maintaining the original + /// aspect ratio. + /// + Max, - /// - /// Resizes the image to the given dimensions. If the set dimensions do not match the aspect ratio of the original image then the output is stretched to match the new aspect ratio. - /// - Stretch, + /// + /// Resizes the image to the given dimensions. If the set dimensions do not match the aspect ratio of the original + /// image then the output is stretched to match the new aspect ratio. + /// + Stretch, - /// - /// Passing a single dimension will automatically preserve the aspect ratio of the original image. If the requested aspect ratio is different then the image will be padded to fit. - /// - Pad, + /// + /// Passing a single dimension will automatically preserve the aspect ratio of the original image. If the requested + /// aspect ratio is different then the image will be padded to fit. + /// + Pad, - /// - /// When upscaling an image the image pixels themselves are not resized, rather the image is padded to fit the given dimensions. - /// - BoxPad, + /// + /// When upscaling an image the image pixels themselves are not resized, rather the image is padded to fit the given + /// dimensions. + /// + BoxPad, - /// - /// Resizes the image until the shortest side reaches the set given dimension. This will maintain the aspect ratio of the original image. Upscaling is disabled in this mode and the original image will be returned if attempted. - /// - Min - } + /// + /// Resizes the image until the shortest side reaches the set given dimension. This will maintain the aspect ratio of + /// the original image. Upscaling is disabled in this mode and the original image will be returned if attempted. + /// + Min } diff --git a/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs b/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs index 876b2bfddb7e..e5c56c65d240 100644 --- a/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs +++ b/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs @@ -1,124 +1,122 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Models +/// +/// These are options that are passed to the IImageUrlGenerator implementation to determine the URL that is generated. +/// +public class ImageUrlGenerationOptions : IEquatable { - /// - /// These are options that are passed to the IImageUrlGenerator implementation to determine the URL that is generated. - /// - public class ImageUrlGenerationOptions : IEquatable - { - public ImageUrlGenerationOptions(string? imageUrl) => ImageUrl = imageUrl; + public ImageUrlGenerationOptions(string? imageUrl) => ImageUrl = imageUrl; - public string? ImageUrl { get; } + public string? ImageUrl { get; } - public int? Width { get; set; } + public int? Width { get; set; } - public int? Height { get; set; } + public int? Height { get; set; } - public int? Quality { get; set; } + public int? Quality { get; set; } - public ImageCropMode? ImageCropMode { get; set; } + public ImageCropMode? ImageCropMode { get; set; } - public ImageCropAnchor? ImageCropAnchor { get; set; } + public ImageCropAnchor? ImageCropAnchor { get; set; } - public FocalPointPosition? FocalPoint { get; set; } + public FocalPointPosition? FocalPoint { get; set; } - public CropCoordinates? Crop { get; set; } + public CropCoordinates? Crop { get; set; } - public string? CacheBusterValue { get; set; } + public string? CacheBusterValue { get; set; } - public string? FurtherOptions { get; set; } + public string? FurtherOptions { get; set; } - public override bool Equals(object? obj) => Equals(obj as ImageUrlGenerationOptions); + public bool Equals(ImageUrlGenerationOptions? other) + => other != null && + ImageUrl == other.ImageUrl && + Width == other.Width && + Height == other.Height && + Quality == other.Quality && + ImageCropMode == other.ImageCropMode && + ImageCropAnchor == other.ImageCropAnchor && + EqualityComparer.Default.Equals(FocalPoint, other.FocalPoint) && + EqualityComparer.Default.Equals(Crop, other.Crop) && + CacheBusterValue == other.CacheBusterValue && + FurtherOptions == other.FurtherOptions; - public bool Equals(ImageUrlGenerationOptions? other) - => other != null && - ImageUrl == other.ImageUrl && - Width == other.Width && - Height == other.Height && - Quality == other.Quality && - ImageCropMode == other.ImageCropMode && - ImageCropAnchor == other.ImageCropAnchor && - EqualityComparer.Default.Equals(FocalPoint, other.FocalPoint) && - EqualityComparer.Default.Equals(Crop, other.Crop) && - CacheBusterValue == other.CacheBusterValue && - FurtherOptions == other.FurtherOptions; - - public override int GetHashCode() - { - var hash = new HashCode(); - - hash.Add(ImageUrl); - hash.Add(Width); - hash.Add(Height); - hash.Add(Quality); - hash.Add(ImageCropMode); - hash.Add(ImageCropAnchor); - hash.Add(FocalPoint); - hash.Add(Crop); - hash.Add(CacheBusterValue); - hash.Add(FurtherOptions); - - return hash.ToHashCode(); - } + public override bool Equals(object? obj) => Equals(obj as ImageUrlGenerationOptions); - /// - /// The focal point position, in whatever units the registered IImageUrlGenerator uses, typically a percentage of the total image from 0.0 to 1.0. - /// - public class FocalPointPosition : IEquatable + public override int GetHashCode() + { + var hash = new HashCode(); + + hash.Add(ImageUrl); + hash.Add(Width); + hash.Add(Height); + hash.Add(Quality); + hash.Add(ImageCropMode); + hash.Add(ImageCropAnchor); + hash.Add(FocalPoint); + hash.Add(Crop); + hash.Add(CacheBusterValue); + hash.Add(FurtherOptions); + + return hash.ToHashCode(); + } + + /// + /// The focal point position, in whatever units the registered IImageUrlGenerator uses, typically a percentage of the + /// total image from 0.0 to 1.0. + /// + public class FocalPointPosition : IEquatable + { + public FocalPointPosition(decimal left, decimal top) { - public FocalPointPosition(decimal left, decimal top) - { - Left = left; - Top = top; - } + Left = left; + Top = top; + } - public decimal Left { get; } + public decimal Left { get; } - public decimal Top { get; } + public decimal Top { get; } - public override bool Equals(object? obj) => Equals(obj as FocalPointPosition); + public bool Equals(FocalPointPosition? other) + => other != null && + Left == other.Left && + Top == other.Top; - public bool Equals(FocalPointPosition? other) - => other != null && - Left == other.Left && - Top == other.Top; + public override bool Equals(object? obj) => Equals(obj as FocalPointPosition); - public override int GetHashCode() => HashCode.Combine(Left, Top); - } + public override int GetHashCode() => HashCode.Combine(Left, Top); + } - /// - /// The bounds of the crop within the original image, in whatever units the registered IImageUrlGenerator uses, typically a percentage between 0.0 and 1.0. - /// - public class CropCoordinates : IEquatable + /// + /// The bounds of the crop within the original image, in whatever units the registered IImageUrlGenerator uses, + /// typically a percentage between 0.0 and 1.0. + /// + public class CropCoordinates : IEquatable + { + public CropCoordinates(decimal left, decimal top, decimal right, decimal bottom) { - public CropCoordinates(decimal left, decimal top, decimal right, decimal bottom) - { - Left = left; - Top = top; - Right = right; - Bottom = bottom; - } + Left = left; + Top = top; + Right = right; + Bottom = bottom; + } - public decimal Left { get; } + public decimal Left { get; } - public decimal Top { get; } + public decimal Top { get; } - public decimal Right { get; } + public decimal Right { get; } - public decimal Bottom { get; } + public decimal Bottom { get; } - public override bool Equals(object? obj) => Equals(obj as CropCoordinates); + public bool Equals(CropCoordinates? other) + => other != null && + Left == other.Left && + Top == other.Top && + Right == other.Right && + Bottom == other.Bottom; - public bool Equals(CropCoordinates? other) - => other != null && - Left == other.Left && - Top == other.Top && - Right == other.Right && - Bottom == other.Bottom; + public override bool Equals(object? obj) => Equals(obj as CropCoordinates); - public override int GetHashCode() => HashCode.Combine(Left, Top, Right, Bottom); - } + public override int GetHashCode() => HashCode.Combine(Left, Top, Right, Bottom); } } diff --git a/src/Umbraco.Core/Models/KeyValue.cs b/src/Umbraco.Core/Models/KeyValue.cs index 4e38ee3390d6..3aed1d0cf5f9 100644 --- a/src/Umbraco.Core/Models/KeyValue.cs +++ b/src/Umbraco.Core/Models/KeyValue.cs @@ -1,33 +1,31 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models -{ - /// - /// Implements . - /// - [Serializable] - [DataContract(IsReference = true)] - public class KeyValue : EntityBase, IKeyValue, IEntity - { - private string _identifier = null!; - private string? _value; +namespace Umbraco.Cms.Core.Models; - /// - public string Identifier - { - get => _identifier; - set => SetPropertyValueAndDetectChanges(value, ref _identifier!, nameof(Identifier)); - } +/// +/// Implements . +/// +[Serializable] +[DataContract(IsReference = true)] +public class KeyValue : EntityBase, IKeyValue, IEntity +{ + private string _identifier = null!; + private string? _value; - /// - public string? Value - { - get => _value; - set => SetPropertyValueAndDetectChanges(value, ref _value, nameof(Value)); - } + /// + public string Identifier + { + get => _identifier; + set => SetPropertyValueAndDetectChanges(value, ref _identifier!, nameof(Identifier)); + } - bool IEntity.HasIdentity => !string.IsNullOrEmpty(Identifier); + /// + public string? Value + { + get => _value; + set => SetPropertyValueAndDetectChanges(value, ref _value, nameof(Value)); } + + bool IEntity.HasIdentity => !string.IsNullOrEmpty(Identifier); } diff --git a/src/Umbraco.Core/Models/Language.cs b/src/Umbraco.Core/Models/Language.cs index 20d936af616c..9c7ea289e43f 100644 --- a/src/Umbraco.Core/Models/Language.cs +++ b/src/Umbraco.Core/Models/Language.cs @@ -1,90 +1,90 @@ -using System.Globalization; +using System.Globalization; using System.Runtime.Serialization; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a Language. +/// +[Serializable] +[DataContract(IsReference = true)] +public class Language : EntityBase, ILanguage { + private string _cultureName; + private int? _fallbackLanguageId; + private bool _isDefaultVariantLanguage; + private string _isoCode; + private bool _mandatory; + /// - /// Represents a Language. + /// Initializes a new instance of the class. /// - [Serializable] - [DataContract(IsReference = true)] - public class Language : EntityBase, ILanguage + /// The ISO code of the language. + /// The name of the language. + public Language(string isoCode, string cultureName) { - private string _isoCode; - private string _cultureName; - private bool _isDefaultVariantLanguage; - private bool _mandatory; - private int? _fallbackLanguageId; - - /// - /// Initializes a new instance of the class. - /// - /// The ISO code of the language. - /// The name of the language. - public Language(string isoCode, string cultureName) - { - _isoCode = isoCode ?? throw new ArgumentNullException(nameof(isoCode)); - _cultureName = cultureName ?? throw new ArgumentNullException(nameof(cultureName)); - } + _isoCode = isoCode ?? throw new ArgumentNullException(nameof(isoCode)); + _cultureName = cultureName ?? throw new ArgumentNullException(nameof(cultureName)); + } - [Obsolete("Use the constructor not requiring global settings and accepting an explicit name instead, scheduled for removal in V11.")] - public Language(GlobalSettings globalSettings, string isoCode) - { - _isoCode = isoCode ?? throw new ArgumentNullException(nameof(isoCode)); - _cultureName = CultureInfo.GetCultureInfo(isoCode).EnglishName; - } + [Obsolete( + "Use the constructor not requiring global settings and accepting an explicit name instead, scheduled for removal in V11.")] + public Language(GlobalSettings globalSettings, string isoCode) + { + _isoCode = isoCode ?? throw new ArgumentNullException(nameof(isoCode)); + _cultureName = CultureInfo.GetCultureInfo(isoCode).EnglishName; + } - /// - [DataMember] - public string IsoCode + /// + [DataMember] + public string IsoCode + { + get => _isoCode; + set { - get => _isoCode; - set - { - ArgumentNullException.ThrowIfNull(value); + ArgumentNullException.ThrowIfNull(value); - SetPropertyValueAndDetectChanges(value, ref _isoCode!, nameof(IsoCode)); - } + SetPropertyValueAndDetectChanges(value, ref _isoCode!, nameof(IsoCode)); } + } - /// - [DataMember] - public string CultureName + /// + [DataMember] + public string CultureName + { + get => _cultureName; + set { - get => _cultureName; - set - { - ArgumentNullException.ThrowIfNull(value); + ArgumentNullException.ThrowIfNull(value); - SetPropertyValueAndDetectChanges(value, ref _cultureName!, nameof(CultureName)); - } + SetPropertyValueAndDetectChanges(value, ref _cultureName!, nameof(CultureName)); } + } - /// - [IgnoreDataMember] - public CultureInfo? CultureInfo => IsoCode is not null ? CultureInfo.GetCultureInfo(IsoCode) : null; + /// + [IgnoreDataMember] + public CultureInfo? CultureInfo => IsoCode is not null ? CultureInfo.GetCultureInfo(IsoCode) : null; - /// - public bool IsDefault - { - get => _isDefaultVariantLanguage; - set => SetPropertyValueAndDetectChanges(value, ref _isDefaultVariantLanguage, nameof(IsDefault)); - } + /// + public bool IsDefault + { + get => _isDefaultVariantLanguage; + set => SetPropertyValueAndDetectChanges(value, ref _isDefaultVariantLanguage, nameof(IsDefault)); + } - /// - public bool IsMandatory - { - get => _mandatory; - set => SetPropertyValueAndDetectChanges(value, ref _mandatory, nameof(IsMandatory)); - } + /// + public bool IsMandatory + { + get => _mandatory; + set => SetPropertyValueAndDetectChanges(value, ref _mandatory, nameof(IsMandatory)); + } - /// - public int? FallbackLanguageId - { - get => _fallbackLanguageId; - set => SetPropertyValueAndDetectChanges(value, ref _fallbackLanguageId, nameof(FallbackLanguageId)); - } + /// + public int? FallbackLanguageId + { + get => _fallbackLanguageId; + set => SetPropertyValueAndDetectChanges(value, ref _fallbackLanguageId, nameof(FallbackLanguageId)); } } diff --git a/src/Umbraco.Core/Models/Link.cs b/src/Umbraco.Core/Models/Link.cs index 3bfc9c5a0db0..9a83d340d7dd 100644 --- a/src/Umbraco.Core/Models/Link.cs +++ b/src/Umbraco.Core/Models/Link.cs @@ -1,11 +1,10 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public class Link { - public class Link - { - public string? Name { get; set; } - public string? Target { get; set; } - public LinkType Type { get; set; } - public Udi? Udi { get; set; } - public string? Url { get; set; } - } + public string? Name { get; set; } + public string? Target { get; set; } + public LinkType Type { get; set; } + public Udi? Udi { get; set; } + public string? Url { get; set; } } diff --git a/src/Umbraco.Core/Models/LinkType.cs b/src/Umbraco.Core/Models/LinkType.cs index e4879249d8b7..7d60d9b67fb1 100644 --- a/src/Umbraco.Core/Models/LinkType.cs +++ b/src/Umbraco.Core/Models/LinkType.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public enum LinkType { - public enum LinkType - { - Content, - Media, - External - } + Content, + Media, + External } diff --git a/src/Umbraco.Core/Models/LogViewerQuery.cs b/src/Umbraco.Core/Models/LogViewerQuery.cs index e9c0dc3180da..615350f6e533 100644 --- a/src/Umbraco.Core/Models/LogViewerQuery.cs +++ b/src/Umbraco.Core/Models/LogViewerQuery.cs @@ -1,34 +1,32 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +[Serializable] +[DataContract(IsReference = true)] +public class LogViewerQuery : EntityBase, ILogViewerQuery { - [Serializable] - [DataContract(IsReference = true)] - public class LogViewerQuery : EntityBase, ILogViewerQuery - { - private string? _name; - private string? _query; + private string? _name; + private string? _query; - public LogViewerQuery(string? name, string? query) - { - Name = name; - _query = query; - } + public LogViewerQuery(string? name, string? query) + { + Name = name; + _query = query; + } - [DataMember] - public string? Name - { - get => _name; - set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); - } + [DataMember] + public string? Name + { + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); + } - [DataMember] - public string? Query - { - get => _query; - set => SetPropertyValueAndDetectChanges(value, ref _query, nameof(Query)); - } + [DataMember] + public string? Query + { + get => _query; + set => SetPropertyValueAndDetectChanges(value, ref _query, nameof(Query)); } } diff --git a/src/Umbraco.Core/Models/Macro.cs b/src/Umbraco.Core/Models/Macro.cs index 1e395c2158c7..3c73709662de 100644 --- a/src/Umbraco.Core/Models/Macro.cs +++ b/src/Umbraco.Core/Models/Macro.cs @@ -1,275 +1,269 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; +using System.Collections.Specialized; using System.ComponentModel; -using System.Linq; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a Macro +/// +[Serializable] +[DataContract(IsReference = true)] +public class Macro : EntityBase, IMacro { + private readonly IShortStringHelper _shortStringHelper; + private List _addedProperties; + + private string _alias; + private bool _cacheByMember; + private bool _cacheByPage; + private int _cacheDuration; + private bool _dontRender; + private string _macroSource; + private string? _name; + private List _removedProperties; + private bool _useInEditor; + + public Macro(IShortStringHelper shortStringHelper) + { + _alias = string.Empty; + _shortStringHelper = shortStringHelper; + Properties = new MacroPropertyCollection(); + Properties.CollectionChanged += PropertiesChanged; + _addedProperties = new List(); + _removedProperties = new List(); + _macroSource = string.Empty; + } + /// - /// Represents a Macro + /// Creates an item with pre-filled properties /// - [Serializable] - [DataContract(IsReference = true)] - public class Macro : EntityBase, IMacro + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public Macro(IShortStringHelper shortStringHelper, int id, Guid key, bool useInEditor, int cacheDuration, + string alias, string? name, bool cacheByPage, bool cacheByMember, bool dontRender, string macroSource) + : this(shortStringHelper) { - private readonly IShortStringHelper _shortStringHelper; - - public Macro(IShortStringHelper shortStringHelper) - { - _alias = string.Empty; - _shortStringHelper = shortStringHelper; - _properties = new MacroPropertyCollection(); - _properties.CollectionChanged += PropertiesChanged; - _addedProperties = new List(); - _removedProperties = new List(); - _macroSource = string.Empty; - } - - /// - /// Creates an item with pre-filled properties - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public Macro(IShortStringHelper shortStringHelper, int id, Guid key, bool useInEditor, int cacheDuration, string @alias, string? name, bool cacheByPage, bool cacheByMember, bool dontRender, string macroSource) - : this(shortStringHelper) - { - Id = id; - Key = key; - UseInEditor = useInEditor; - CacheDuration = cacheDuration; - Alias = alias.ToCleanString(shortStringHelper,CleanStringType.Alias); - Name = name; - CacheByPage = cacheByPage; - CacheByMember = cacheByMember; - DontRender = dontRender; - MacroSource = macroSource; - } + Id = id; + Key = key; + UseInEditor = useInEditor; + CacheDuration = cacheDuration; + Alias = alias.ToCleanString(shortStringHelper, CleanStringType.Alias); + Name = name; + CacheByPage = cacheByPage; + CacheByMember = cacheByMember; + DontRender = dontRender; + MacroSource = macroSource; + } - /// - /// Creates an instance for persisting a new item - /// - /// - /// - /// - /// - /// - /// - /// - /// - public Macro(IShortStringHelper shortStringHelper, string @alias, string? name, - string macroSource, - bool cacheByPage = false, - bool cacheByMember = false, - bool dontRender = true, - bool useInEditor = false, - int cacheDuration = 0) - : this(shortStringHelper) - { - UseInEditor = useInEditor; - CacheDuration = cacheDuration; - Alias = alias.ToCleanString(shortStringHelper, CleanStringType.Alias); - Name = name; - CacheByPage = cacheByPage; - CacheByMember = cacheByMember; - DontRender = dontRender; - MacroSource = macroSource; - } + /// + /// Creates an instance for persisting a new item + /// + /// + /// + /// + /// + /// + /// + /// + /// + public Macro(IShortStringHelper shortStringHelper, string alias, string? name, + string macroSource, + bool cacheByPage = false, + bool cacheByMember = false, + bool dontRender = true, + bool useInEditor = false, + int cacheDuration = 0) + : this(shortStringHelper) + { + UseInEditor = useInEditor; + CacheDuration = cacheDuration; + Alias = alias.ToCleanString(shortStringHelper, CleanStringType.Alias); + Name = name; + CacheByPage = cacheByPage; + CacheByMember = cacheByMember; + DontRender = dontRender; + MacroSource = macroSource; + } - private string _alias; - private string? _name; - private bool _useInEditor; - private int _cacheDuration; - private bool _cacheByPage; - private bool _cacheByMember; - private bool _dontRender; - private string _macroSource; - private MacroPropertyCollection _properties; - private List _addedProperties; - private List _removedProperties; + /// + /// Used internally to check if we need to add a section in the repository to the db + /// + internal IEnumerable AddedProperties => _addedProperties; - void PropertiesChanged(object? sender, NotifyCollectionChangedEventArgs e) - { - OnPropertyChanged(nameof(Properties)); + /// + /// Used internally to check if we need to remove a section in the repository to the db + /// + internal IEnumerable RemovedProperties => _removedProperties; - if (e.Action == NotifyCollectionChangedAction.Add) - { - //listen for changes - MacroProperty? prop = e.NewItems?.Cast().First(); - if (prop is not null) - { - prop.PropertyChanged += PropertyDataChanged; + public override void ResetDirtyProperties(bool rememberDirty) + { + base.ResetDirtyProperties(rememberDirty); - var alias = prop.Alias; + _addedProperties.Clear(); + _removedProperties.Clear(); - if (_addedProperties.Contains(alias) == false) - { - //add to the added props - _addedProperties.Add(alias); - } - } - } - else if (e.Action == NotifyCollectionChangedAction.Remove) - { - //remove listening for changes - var prop = e.OldItems?.Cast().First(); - if (prop is not null) - { - prop.PropertyChanged -= PropertyDataChanged; + foreach (IMacroProperty prop in Properties) + { + prop.ResetDirtyProperties(rememberDirty); + } + } - var alias = prop.Alias; + /// + /// Gets or sets the alias of the Macro + /// + [DataMember] + public string Alias + { + get => _alias; + set => SetPropertyValueAndDetectChanges(value.ToCleanString(_shortStringHelper, CleanStringType.Alias), + ref _alias!, nameof(Alias)); + } - if (_removedProperties.Contains(alias) == false) - { - _removedProperties.Add(alias); - } - } - } - } + /// + /// Gets or sets the name of the Macro + /// + [DataMember] + public string? Name + { + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); + } - /// - /// When some data of a property has changed ensure our Properties flag is dirty - /// - /// - /// - void PropertyDataChanged(object? sender, PropertyChangedEventArgs e) - { - OnPropertyChanged(nameof(Properties)); - } + /// + /// Gets or sets a boolean indicating whether the Macro can be used in an Editor + /// + [DataMember] + public bool UseInEditor + { + get => _useInEditor; + set => SetPropertyValueAndDetectChanges(value, ref _useInEditor, nameof(UseInEditor)); + } - public override void ResetDirtyProperties(bool rememberDirty) - { - base.ResetDirtyProperties(rememberDirty); + /// + /// Gets or sets the Cache Duration for the Macro + /// + [DataMember] + public int CacheDuration + { + get => _cacheDuration; + set => SetPropertyValueAndDetectChanges(value, ref _cacheDuration, nameof(CacheDuration)); + } - _addedProperties.Clear(); - _removedProperties.Clear(); + /// + /// Gets or sets a boolean indicating whether the Macro should be Cached by Page + /// + [DataMember] + public bool CacheByPage + { + get => _cacheByPage; + set => SetPropertyValueAndDetectChanges(value, ref _cacheByPage, nameof(CacheByPage)); + } - foreach (var prop in Properties) - { - prop.ResetDirtyProperties(rememberDirty); - } - } + /// + /// Gets or sets a boolean indicating whether the Macro should be Cached Personally + /// + [DataMember] + public bool CacheByMember + { + get => _cacheByMember; + set => SetPropertyValueAndDetectChanges(value, ref _cacheByMember, nameof(CacheByMember)); + } - /// - /// Used internally to check if we need to add a section in the repository to the db - /// - internal IEnumerable AddedProperties => _addedProperties; + /// + /// Gets or sets a boolean indicating whether the Macro should be rendered in an Editor + /// + [DataMember] + public bool DontRender + { + get => _dontRender; + set => SetPropertyValueAndDetectChanges(value, ref _dontRender, nameof(DontRender)); + } - /// - /// Used internally to check if we need to remove a section in the repository to the db - /// - internal IEnumerable RemovedProperties => _removedProperties; + /// + /// Gets or set the path to the Partial View to render + /// + [DataMember] + public string MacroSource + { + get => _macroSource; + set => SetPropertyValueAndDetectChanges(value, ref _macroSource!, nameof(MacroSource)); + } - /// - /// Gets or sets the alias of the Macro - /// - [DataMember] - public string Alias - { - get => _alias; - set => SetPropertyValueAndDetectChanges(value.ToCleanString(_shortStringHelper, CleanStringType.Alias), ref _alias!, nameof(Alias)); - } + /// + /// Gets or sets a list of Macro Properties + /// + [DataMember] + public MacroPropertyCollection Properties { get; private set; } - /// - /// Gets or sets the name of the Macro - /// - [DataMember] - public string? Name - { - get => _name; - set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); - } + private void PropertiesChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + OnPropertyChanged(nameof(Properties)); - /// - /// Gets or sets a boolean indicating whether the Macro can be used in an Editor - /// - [DataMember] - public bool UseInEditor + if (e.Action == NotifyCollectionChangedAction.Add) { - get => _useInEditor; - set => SetPropertyValueAndDetectChanges(value, ref _useInEditor, nameof(UseInEditor)); - } + //listen for changes + MacroProperty? prop = e.NewItems?.Cast().First(); + if (prop is not null) + { + prop.PropertyChanged += PropertyDataChanged; - /// - /// Gets or sets the Cache Duration for the Macro - /// - [DataMember] - public int CacheDuration - { - get => _cacheDuration; - set => SetPropertyValueAndDetectChanges(value, ref _cacheDuration, nameof(CacheDuration)); - } + var alias = prop.Alias; - /// - /// Gets or sets a boolean indicating whether the Macro should be Cached by Page - /// - [DataMember] - public bool CacheByPage - { - get => _cacheByPage; - set => SetPropertyValueAndDetectChanges(value, ref _cacheByPage, nameof(CacheByPage)); + if (_addedProperties.Contains(alias) == false) + { + //add to the added props + _addedProperties.Add(alias); + } + } } - - /// - /// Gets or sets a boolean indicating whether the Macro should be Cached Personally - /// - [DataMember] - public bool CacheByMember + else if (e.Action == NotifyCollectionChangedAction.Remove) { - get => _cacheByMember; - set => SetPropertyValueAndDetectChanges(value, ref _cacheByMember, nameof(CacheByMember)); - } + //remove listening for changes + MacroProperty prop = e.OldItems?.Cast().First(); + if (prop is not null) + { + prop.PropertyChanged -= PropertyDataChanged; - /// - /// Gets or sets a boolean indicating whether the Macro should be rendered in an Editor - /// - [DataMember] - public bool DontRender - { - get => _dontRender; - set => SetPropertyValueAndDetectChanges(value, ref _dontRender, nameof(DontRender)); - } + var alias = prop.Alias; - /// - /// Gets or set the path to the Partial View to render - /// - [DataMember] - public string MacroSource - { - get => _macroSource; - set => SetPropertyValueAndDetectChanges(value, ref _macroSource!, nameof(MacroSource)); + if (_removedProperties.Contains(alias) == false) + { + _removedProperties.Add(alias); + } + } } + } - /// - /// Gets or sets a list of Macro Properties - /// - [DataMember] - public MacroPropertyCollection Properties => _properties; - - protected override void PerformDeepClone(object clone) - { - base.PerformDeepClone(clone); + /// + /// When some data of a property has changed ensure our Properties flag is dirty + /// + /// + /// + private void PropertyDataChanged(object? sender, PropertyChangedEventArgs e) => + OnPropertyChanged(nameof(Properties)); - var clonedEntity = (Macro)clone; + protected override void PerformDeepClone(object clone) + { + base.PerformDeepClone(clone); - clonedEntity._addedProperties = new List(); - clonedEntity._removedProperties = new List(); - clonedEntity._properties = (MacroPropertyCollection)Properties.DeepClone(); - //re-assign the event handler - clonedEntity._properties.CollectionChanged += clonedEntity.PropertiesChanged; + var clonedEntity = (Macro)clone; - } + clonedEntity._addedProperties = new List(); + clonedEntity._removedProperties = new List(); + clonedEntity.Properties = (MacroPropertyCollection)Properties.DeepClone(); + //re-assign the event handler + clonedEntity.Properties.CollectionChanged += clonedEntity.PropertiesChanged; } } diff --git a/src/Umbraco.Core/Models/MacroProperty.cs b/src/Umbraco.Core/Models/MacroProperty.cs index 659334258e75..2164de658cb0 100644 --- a/src/Umbraco.Core/Models/MacroProperty.cs +++ b/src/Umbraco.Core/Models/MacroProperty.cs @@ -1,159 +1,167 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a Macro Property +/// +[Serializable] +[DataContract(IsReference = true)] +public class MacroProperty : BeingDirtyBase, IMacroProperty { + private string _alias; + private string _editorAlias; + private int _id; + + private Guid _key; + private string? _name; + private int _sortOrder; + + public MacroProperty() + { + _editorAlias = string.Empty; + _alias = string.Empty; + _key = Guid.NewGuid(); + } + /// - /// Represents a Macro Property + /// Ctor for creating a new property /// - [Serializable] - [DataContract(IsReference = true)] - public class MacroProperty : BeingDirtyBase, IMacroProperty + /// + /// + /// + /// + public MacroProperty(string alias, string? name, int sortOrder, string editorAlias) { - public MacroProperty() - { - _editorAlias = string.Empty; - _alias = string.Empty; - _key = Guid.NewGuid(); - } + _alias = alias; + _name = name; + _sortOrder = sortOrder; + _key = Guid.NewGuid(); + _editorAlias = editorAlias; + } - /// - /// Ctor for creating a new property - /// - /// - /// - /// - /// - public MacroProperty(string @alias, string? name, int sortOrder, string editorAlias) - { - _alias = alias; - _name = name; - _sortOrder = sortOrder; - _key = Guid.NewGuid(); - _editorAlias = editorAlias; - } + /// + /// Ctor for creating an existing property + /// + /// + /// + /// + /// + /// + /// + public MacroProperty(int id, Guid key, string alias, string? name, int sortOrder, string editorAlias) + { + _id = id; + _alias = alias; + _name = name; + _sortOrder = sortOrder; + _key = key; + _editorAlias = editorAlias; + } - /// - /// Ctor for creating an existing property - /// - /// - /// - /// - /// - /// - /// - public MacroProperty(int id, Guid key, string @alias, string? name, int sortOrder, string editorAlias) - { - _id = id; - _alias = alias; - _name = name; - _sortOrder = sortOrder; - _key = key; - _editorAlias = editorAlias; - } + /// + /// Gets or sets the Key of the Property + /// + [DataMember] + public Guid Key + { + get => _key; + set => SetPropertyValueAndDetectChanges(value, ref _key, nameof(Key)); + } - private Guid _key; - private string _alias; - private string? _name; - private int _sortOrder; - private int _id; - private string _editorAlias; - - /// - /// Gets or sets the Key of the Property - /// - [DataMember] - public Guid Key - { - get => _key; - set => SetPropertyValueAndDetectChanges(value, ref _key, nameof(Key)); - } + /// + /// Gets or sets the Alias of the Property + /// + [DataMember] + public int Id + { + get => _id; + set => SetPropertyValueAndDetectChanges(value, ref _id, nameof(Id)); + } - /// - /// Gets or sets the Alias of the Property - /// - [DataMember] - public int Id - { - get => _id; - set => SetPropertyValueAndDetectChanges(value, ref _id, nameof(Id)); - } + /// + /// Gets or sets the Alias of the Property + /// + [DataMember] + public string Alias + { + get => _alias; + set => SetPropertyValueAndDetectChanges(value, ref _alias!, nameof(Alias)); + } - /// - /// Gets or sets the Alias of the Property - /// - [DataMember] - public string Alias - { - get => _alias; - set => SetPropertyValueAndDetectChanges(value, ref _alias!, nameof(Alias)); - } + /// + /// Gets or sets the Name of the Property + /// + [DataMember] + public string? Name + { + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); + } - /// - /// Gets or sets the Name of the Property - /// - [DataMember] - public string? Name - { - get => _name; - set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); - } + /// + /// Gets or sets the Sort Order of the Property + /// + [DataMember] + public int SortOrder + { + get => _sortOrder; + set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, nameof(SortOrder)); + } - /// - /// Gets or sets the Sort Order of the Property - /// - [DataMember] - public int SortOrder - { - get => _sortOrder; - set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, nameof(SortOrder)); - } + /// + /// Gets or sets the Type for this Property + /// + /// + /// The MacroPropertyTypes acts as a plugin for Macros. + /// All types was previously contained in the database, but has been ported to code. + /// + [DataMember] + public string EditorAlias + { + get => _editorAlias; + set => SetPropertyValueAndDetectChanges(value, ref _editorAlias!, nameof(EditorAlias)); + } - /// - /// Gets or sets the Type for this Property - /// - /// - /// The MacroPropertyTypes acts as a plugin for Macros. - /// All types was previously contained in the database, but has been ported to code. - /// - [DataMember] - public string EditorAlias - { - get => _editorAlias; - set => SetPropertyValueAndDetectChanges(value, ref _editorAlias!, nameof(EditorAlias)); - } + public object DeepClone() + { + //Memberwise clone on MacroProperty will work since it doesn't have any deep elements + // for any sub class this will work for standard properties as well that aren't complex object's themselves. + var clone = (MacroProperty)MemberwiseClone(); + //Automatically deep clone ref properties that are IDeepCloneable + DeepCloneHelper.DeepCloneRefProperties(this, clone); + clone.ResetDirtyProperties(false); + return clone; + } - public object DeepClone() + protected bool Equals(MacroProperty other) => string.Equals(_alias, other._alias) && _id == other._id; + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) { - //Memberwise clone on MacroProperty will work since it doesn't have any deep elements - // for any sub class this will work for standard properties as well that aren't complex object's themselves. - var clone = (MacroProperty)MemberwiseClone(); - //Automatically deep clone ref properties that are IDeepCloneable - DeepCloneHelper.DeepCloneRefProperties(this, clone); - clone.ResetDirtyProperties(false); - return clone; + return false; } - protected bool Equals(MacroProperty other) + if (ReferenceEquals(this, obj)) { - return string.Equals(_alias, other._alias) && _id == other._id; + return true; } - public override bool Equals(object? obj) + if (obj.GetType() != GetType()) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((MacroProperty) obj); + return false; } - public override int GetHashCode() + return Equals((MacroProperty)obj); + } + + public override int GetHashCode() + { + unchecked { - unchecked - { - return ((_alias != null ? _alias.GetHashCode() : 0)*397) ^ _id; - } + return ((_alias != null ? _alias.GetHashCode() : 0) * 397) ^ _id; } } } diff --git a/src/Umbraco.Core/Models/MacroPropertyCollection.cs b/src/Umbraco.Core/Models/MacroPropertyCollection.cs index cda46d2af7d9..b763c1fd418d 100644 --- a/src/Umbraco.Core/Models/MacroPropertyCollection.cs +++ b/src/Umbraco.Core/Models/MacroPropertyCollection.cs @@ -1,66 +1,67 @@ -using System; -using Umbraco.Cms.Core.Collections; +using Umbraco.Cms.Core.Collections; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// A macro's property collection +/// +public class MacroPropertyCollection : ObservableDictionary, IDeepCloneable { + public MacroPropertyCollection() + : base(property => property.Alias) + { + } + + public object DeepClone() + { + var clone = new MacroPropertyCollection(); + foreach (IMacroProperty item in this) + { + clone.Add((IMacroProperty)item.DeepClone()); + } + + return clone; + } + /// - /// A macro's property collection + /// Used to update an existing macro property /// - public class MacroPropertyCollection : ObservableDictionary, IDeepCloneable + /// + /// + /// + /// + /// The existing property alias + /// + /// + public void UpdateProperty(string currentAlias, string? name = null, int? sortOrder = null, + string? editorAlias = null, string? newAlias = null) { - public MacroPropertyCollection() - : base(property => property.Alias) + IMacroProperty prop = this[currentAlias]; + if (prop == null) { + throw new InvalidOperationException("No property exists with alias " + currentAlias); } - public object DeepClone() + if (name.IsNullOrWhiteSpace() == false) { - var clone = new MacroPropertyCollection(); - foreach (var item in this) - { - clone.Add((IMacroProperty)item.DeepClone()); - } - return clone; + prop.Name = name; } - /// - /// Used to update an existing macro property - /// - /// - /// - /// - /// - /// The existing property alias - /// - /// - public void UpdateProperty(string currentAlias, string? name = null, int? sortOrder = null, string? editorAlias = null, string? newAlias = null) + if (sortOrder.HasValue) { - var prop = this[currentAlias]; - if (prop == null) - { - throw new InvalidOperationException("No property exists with alias " + currentAlias); - } + prop.SortOrder = sortOrder.Value; + } - if (name.IsNullOrWhiteSpace() == false) - { - prop.Name = name; - } - if (sortOrder.HasValue) - { - prop.SortOrder = sortOrder.Value; - } - if (name.IsNullOrWhiteSpace() == false && editorAlias is not null) - { - prop.EditorAlias = editorAlias; - } + if (name.IsNullOrWhiteSpace() == false && editorAlias is not null) + { + prop.EditorAlias = editorAlias; + } - if (newAlias.IsNullOrWhiteSpace() == false && currentAlias != newAlias && newAlias is not null) - { - prop.Alias = newAlias; - ChangeKey(currentAlias, newAlias); - } + if (newAlias.IsNullOrWhiteSpace() == false && currentAlias != newAlias && newAlias is not null) + { + prop.Alias = newAlias; + ChangeKey(currentAlias, newAlias); } } - } diff --git a/src/Umbraco.Core/Models/Mapping/AuditMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/AuditMapDefinition.cs index 072611da4c6f..7485d9ac45bf 100644 --- a/src/Umbraco.Core/Models/Mapping/AuditMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/AuditMapDefinition.cs @@ -1,25 +1,22 @@ using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +public class AuditMapDefinition : IMapDefinition { - public class AuditMapDefinition : IMapDefinition - { - public void DefineMaps(IUmbracoMapper mapper) - { - mapper.Define((source, context) => new AuditLog(), Map); - } + public void DefineMaps(IUmbracoMapper mapper) => + mapper.Define((source, context) => new AuditLog(), Map); - // Umbraco.Code.MapAll -UserAvatars -UserName - private void Map(IAuditItem source, AuditLog target, MapperContext context) - { - target.UserId = source.UserId; - target.NodeId = source.Id; - target.Timestamp = source.CreateDate; - target.LogType = source.AuditType.ToString(); - target.EntityType = source.EntityType; - target.Comment = source.Comment; - target.Parameters = source.Parameters; - } + // Umbraco.Code.MapAll -UserAvatars -UserName + private void Map(IAuditItem source, AuditLog target, MapperContext context) + { + target.UserId = source.UserId; + target.NodeId = source.Id; + target.Timestamp = source.CreateDate; + target.LogType = source.AuditType.ToString(); + target.EntityType = source.EntityType; + target.Comment = source.Comment; + target.Parameters = source.Parameters; } } diff --git a/src/Umbraco.Core/Models/Mapping/CodeFileMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/CodeFileMapDefinition.cs index b185bb586e14..e9ba018f9ce0 100644 --- a/src/Umbraco.Core/Models/Mapping/CodeFileMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/CodeFileMapDefinition.cs @@ -1,100 +1,98 @@ using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +public class CodeFileMapDefinition : IMapDefinition { - public class CodeFileMapDefinition : IMapDefinition + public void DefineMaps(IUmbracoMapper mapper) { - public void DefineMaps(IUmbracoMapper mapper) - { - mapper.Define((source, context) => new EntityBasic(), Map); - mapper.Define((source, context) => new CodeFileDisplay(), Map); - - mapper.Define((source, context) => new EntityBasic(), Map); - mapper.Define((source, context) => new CodeFileDisplay(), Map); + mapper.Define((source, context) => new EntityBasic(), Map); + mapper.Define((source, context) => new CodeFileDisplay(), Map); - mapper.Define((source, context) => new EntityBasic(), Map); - mapper.Define((source, context) => new CodeFileDisplay(), Map); + mapper.Define((source, context) => new EntityBasic(), Map); + mapper.Define((source, context) => new CodeFileDisplay(), Map); - mapper.Define(Map); - mapper.Define(Map); + mapper.Define((source, context) => new EntityBasic(), Map); + mapper.Define((source, context) => new CodeFileDisplay(), Map); - } + mapper.Define(Map); + mapper.Define(Map); + } - // Umbraco.Code.MapAll -Trashed -Udi -Icon - private static void Map(IStylesheet source, EntityBasic target, MapperContext context) - { - target.Alias = source.Alias; - target.Id = source.Id; - target.Key = source.Key; - target.Name = source.Name; - target.ParentId = -1; - target.Path = source.Path; - } + // Umbraco.Code.MapAll -Trashed -Udi -Icon + private static void Map(IStylesheet source, EntityBasic target, MapperContext context) + { + target.Alias = source.Alias; + target.Id = source.Id; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = -1; + target.Path = source.Path; + } - // Umbraco.Code.MapAll -Trashed -Udi -Icon - private static void Map(IScript source, EntityBasic target, MapperContext context) - { - target.Alias = source.Alias; - target.Id = source.Id; - target.Key = source.Key; - target.Name = source.Name; - target.ParentId = -1; - target.Path = source.Path; - } + // Umbraco.Code.MapAll -Trashed -Udi -Icon + private static void Map(IScript source, EntityBasic target, MapperContext context) + { + target.Alias = source.Alias; + target.Id = source.Id; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = -1; + target.Path = source.Path; + } - // Umbraco.Code.MapAll -Trashed -Udi -Icon - private static void Map(IPartialView source, EntityBasic target, MapperContext context) - { - target.Alias = source.Alias; - target.Id = source.Id; - target.Key = source.Key; - target.Name = source.Name; - target.ParentId = -1; - target.Path = source.Path; - } + // Umbraco.Code.MapAll -Trashed -Udi -Icon + private static void Map(IPartialView source, EntityBasic target, MapperContext context) + { + target.Alias = source.Alias; + target.Id = source.Id; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = -1; + target.Path = source.Path; + } - // Umbraco.Code.MapAll -FileType -Notifications -Path -Snippet - private static void Map(IPartialView source, CodeFileDisplay target, MapperContext context) - { - target.Content = source.Content; - target.Id = source.Id.ToString(); - target.Name = source.Name; - target.VirtualPath = source.VirtualPath; - } + // Umbraco.Code.MapAll -FileType -Notifications -Path -Snippet + private static void Map(IPartialView source, CodeFileDisplay target, MapperContext context) + { + target.Content = source.Content; + target.Id = source.Id.ToString(); + target.Name = source.Name; + target.VirtualPath = source.VirtualPath; + } - // Umbraco.Code.MapAll -FileType -Notifications -Path -Snippet - private static void Map(IScript source, CodeFileDisplay target, MapperContext context) - { - target.Content = source.Content; - target.Id = source.Id.ToString(); - target.Name = source.Name; - target.VirtualPath = source.VirtualPath; - } + // Umbraco.Code.MapAll -FileType -Notifications -Path -Snippet + private static void Map(IScript source, CodeFileDisplay target, MapperContext context) + { + target.Content = source.Content; + target.Id = source.Id.ToString(); + target.Name = source.Name; + target.VirtualPath = source.VirtualPath; + } - // Umbraco.Code.MapAll -FileType -Notifications -Path -Snippet - private static void Map(IStylesheet source, CodeFileDisplay target, MapperContext context) - { - target.Content = source.Content; - target.Id = source.Id.ToString(); - target.Name = source.Name; - target.VirtualPath = source.VirtualPath; - } + // Umbraco.Code.MapAll -FileType -Notifications -Path -Snippet + private static void Map(IStylesheet source, CodeFileDisplay target, MapperContext context) + { + target.Content = source.Content; + target.Id = source.Id.ToString(); + target.Name = source.Name; + target.VirtualPath = source.VirtualPath; + } - // Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate - // Umbraco.Code.MapAll -Id -Key -Alias -Name -OriginalPath -Path - private static void Map(CodeFileDisplay source, IPartialView target, MapperContext context) - { - target.Content = source.Content; - target.VirtualPath = source.VirtualPath; - } + // Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate + // Umbraco.Code.MapAll -Id -Key -Alias -Name -OriginalPath -Path + private static void Map(CodeFileDisplay source, IPartialView target, MapperContext context) + { + target.Content = source.Content; + target.VirtualPath = source.VirtualPath; + } - // Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate -GetFileContent - // Umbraco.Code.MapAll -Id -Key -Alias -Name -OriginalPath -Path - private static void Map(CodeFileDisplay source, IScript target, MapperContext context) - { - target.Content = source.Content; - target.VirtualPath = source.VirtualPath; - } + // Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate -GetFileContent + // Umbraco.Code.MapAll -Id -Key -Alias -Name -OriginalPath -Path + private static void Map(CodeFileDisplay source, IScript target, MapperContext context) + { + target.Content = source.Content; + target.VirtualPath = source.VirtualPath; } } diff --git a/src/Umbraco.Core/Models/Mapping/CommonMapper.cs b/src/Umbraco.Core/Models/Mapping/CommonMapper.cs index 3832654f451e..e925cb7820f8 100644 --- a/src/Umbraco.Core/Models/Mapping/CommonMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/CommonMapper.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core.ContentApps; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; @@ -10,63 +7,59 @@ using Umbraco.Extensions; using UserProfile = Umbraco.Cms.Core.Models.ContentEditing.UserProfile; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +public class CommonMapper { - public class CommonMapper + private readonly ContentAppFactoryCollection _contentAppDefinitions; + private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; + private readonly ILocalizedTextService _localizedTextService; + private readonly IUserService _userService; + + public CommonMapper(IUserService userService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, + ContentAppFactoryCollection contentAppDefinitions, ILocalizedTextService localizedTextService) { - private readonly IUserService _userService; - private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; - private readonly ContentAppFactoryCollection _contentAppDefinitions; - private readonly ILocalizedTextService _localizedTextService; + _userService = userService; + _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; + _contentAppDefinitions = contentAppDefinitions; + _localizedTextService = localizedTextService; + } - public CommonMapper(IUserService userService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, - ContentAppFactoryCollection contentAppDefinitions, ILocalizedTextService localizedTextService) - { - _userService = userService; - _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; - _contentAppDefinitions = contentAppDefinitions; - _localizedTextService = localizedTextService; - } + public UserProfile? GetOwner(IContentBase source, MapperContext context) + { + IProfile profile = source.GetCreatorProfile(_userService); + return profile == null ? null : context.Map(profile); + } - public UserProfile? GetOwner(IContentBase source, MapperContext context) - { - var profile = source.GetCreatorProfile(_userService); - return profile == null ? null : context.Map(profile); - } + public UserProfile? GetCreator(IContent source, MapperContext context) + { + IProfile profile = source.GetWriterProfile(_userService); + return profile == null ? null : context.Map(profile); + } - public UserProfile? GetCreator(IContent source, MapperContext context) - { - var profile = source.GetWriterProfile(_userService); - return profile == null ? null : context.Map(profile); - } + public ContentTypeBasic? GetContentType(IContentBase source, MapperContext context) + { + IContentTypeComposition contentType = _contentTypeBaseServiceProvider.GetContentTypeOf(source); + ContentTypeBasic contentTypeBasic = context.Map(contentType); + return contentTypeBasic; + } - public ContentTypeBasic? GetContentType(IContentBase source, MapperContext context) - { - var contentType = _contentTypeBaseServiceProvider.GetContentTypeOf(source); - var contentTypeBasic = context.Map(contentType); - return contentTypeBasic; - } + public IEnumerable GetContentApps(IUmbracoEntity source) => GetContentAppsForEntity(source); - public IEnumerable GetContentApps(IUmbracoEntity source) - { - return GetContentAppsForEntity(source); - } + public IEnumerable GetContentAppsForEntity(IEntity source) + { + ContentApp[] apps = _contentAppDefinitions.GetContentAppsFor(source).ToArray(); - public IEnumerable GetContentAppsForEntity(IEntity source) + // localize content app names + foreach (ContentApp app in apps) { - var apps = _contentAppDefinitions.GetContentAppsFor(source).ToArray(); - - // localize content app names - foreach (var app in apps) + var localizedAppName = _localizedTextService.Localize("apps", app.Alias); + if (localizedAppName.Equals($"[{app.Alias}]", StringComparison.OrdinalIgnoreCase) == false) { - var localizedAppName = _localizedTextService.Localize("apps", app.Alias); - if (localizedAppName.Equals($"[{app.Alias}]", StringComparison.OrdinalIgnoreCase) == false) - { - app.Name = localizedAppName; - } + app.Name = localizedAppName; } - - return apps; } + + return apps; } } diff --git a/src/Umbraco.Core/Models/Mapping/ContentPropertyBasicMapper.cs b/src/Umbraco.Core/Models/Mapping/ContentPropertyBasicMapper.cs index 4becc8f21a2f..d5461fec07b6 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentPropertyBasicMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentPropertyBasicMapper.cs @@ -1,85 +1,94 @@ -using System; -using System.Linq; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +/// +/// Creates a base generic ContentPropertyBasic from a Property +/// +internal class ContentPropertyBasicMapper + where TDestination : ContentPropertyBasic, new() { + private readonly IEntityService _entityService; + private readonly ILogger> _logger; + private readonly PropertyEditorCollection _propertyEditors; + + public ContentPropertyBasicMapper(IDataTypeService dataTypeService, IEntityService entityService, + ILogger> logger, PropertyEditorCollection propertyEditors) + { + _logger = logger; + _propertyEditors = propertyEditors; + DataTypeService = dataTypeService; + _entityService = entityService; + } + + protected IDataTypeService DataTypeService { get; } + /// - /// Creates a base generic ContentPropertyBasic from a Property + /// Assigns the PropertyEditor, Id, Alias and Value to the property /// - internal class ContentPropertyBasicMapper - where TDestination : ContentPropertyBasic, new() + /// + public virtual void Map(IProperty property, TDestination dest, MapperContext context) { - private readonly IEntityService _entityService; - private readonly ILogger> _logger; - private readonly PropertyEditorCollection _propertyEditors; - protected IDataTypeService DataTypeService { get; } - - public ContentPropertyBasicMapper(IDataTypeService dataTypeService, IEntityService entityService, ILogger> logger, PropertyEditorCollection propertyEditors) + IDataEditor editor = property.PropertyType is not null + ? _propertyEditors[property.PropertyType.PropertyEditorAlias] + : null; + if (editor == null) { - _logger = logger; - _propertyEditors = propertyEditors; - DataTypeService = dataTypeService; - _entityService = entityService; - } + _logger.LogError( + new NullReferenceException("The property editor with alias " + + property.PropertyType?.PropertyEditorAlias + " does not exist"), + "No property editor '{PropertyEditorAlias}' found, converting to a Label", + property.PropertyType?.PropertyEditorAlias); + + editor = _propertyEditors[Constants.PropertyEditors.Aliases.Label]; - /// - /// Assigns the PropertyEditor, Id, Alias and Value to the property - /// - /// - public virtual void Map(IProperty property, TDestination dest, MapperContext context) - { - var editor = property.PropertyType is not null ? _propertyEditors[property.PropertyType.PropertyEditorAlias] : null; if (editor == null) { - _logger.LogError( - new NullReferenceException("The property editor with alias " + property.PropertyType?.PropertyEditorAlias + " does not exist"), - "No property editor '{PropertyEditorAlias}' found, converting to a Label", - property.PropertyType?.PropertyEditorAlias); - - editor = _propertyEditors[Constants.PropertyEditors.Aliases.Label]; - - if (editor == null) - throw new InvalidOperationException($"Could not resolve the property editor {Constants.PropertyEditors.Aliases.Label}"); + throw new InvalidOperationException( + $"Could not resolve the property editor {Constants.PropertyEditors.Aliases.Label}"); } + } - dest.Id = property.Id; - dest.Alias = property.Alias; - dest.PropertyEditor = editor; - dest.Editor = editor.Alias; - dest.DataTypeKey = property.PropertyType!.DataTypeKey; - - // if there's a set of property aliases specified, we will check if the current property's value should be mapped. - // if it isn't one of the ones specified in 'includeProperties', we will just return the result without mapping the Value. - var includedProperties = context.GetIncludedProperties(); - if (includedProperties != null && !includedProperties.Contains(property.Alias)) - return; + dest.Id = property.Id; + dest.Alias = property.Alias; + dest.PropertyEditor = editor; + dest.Editor = editor.Alias; + dest.DataTypeKey = property.PropertyType!.DataTypeKey; - //Get the culture from the context which will be set during the mapping operation for each property - var culture = context.GetCulture(); + // if there's a set of property aliases specified, we will check if the current property's value should be mapped. + // if it isn't one of the ones specified in 'includeProperties', we will just return the result without mapping the Value. + var includedProperties = context.GetIncludedProperties(); + if (includedProperties != null && !includedProperties.Contains(property.Alias)) + { + return; + } - //a culture needs to be in the context for a property type that can vary - if (culture == null && property.PropertyType.VariesByCulture()) - throw new InvalidOperationException($"No culture found in mapping operation when one is required for the culture variant property type {property.PropertyType.Alias}"); + //Get the culture from the context which will be set during the mapping operation for each property + var culture = context.GetCulture(); - //set the culture to null if it's an invariant property type - culture = !property.PropertyType.VariesByCulture() ? null : culture; + //a culture needs to be in the context for a property type that can vary + if (culture == null && property.PropertyType.VariesByCulture()) + { + throw new InvalidOperationException( + $"No culture found in mapping operation when one is required for the culture variant property type {property.PropertyType.Alias}"); + } - dest.Culture = culture; + //set the culture to null if it's an invariant property type + culture = !property.PropertyType.VariesByCulture() ? null : culture; - // Get the segment, which is always allowed to be null even if the propertyType *can* be varied by segment. - // There is therefore no need to perform the null check like with culture above. - var segment = !property.PropertyType.VariesBySegment() ? null : context.GetSegment(); - dest.Segment = segment; + dest.Culture = culture; - // if no 'IncludeProperties' were specified or this property is set to be included - we will map the value and return. - dest.Value = editor.GetValueEditor().ToEditor(property, culture, segment); + // Get the segment, which is always allowed to be null even if the propertyType *can* be varied by segment. + // There is therefore no need to perform the null check like with culture above. + var segment = !property.PropertyType.VariesBySegment() ? null : context.GetSegment(); + dest.Segment = segment; - } + // if no 'IncludeProperties' were specified or this property is set to be included - we will map the value and return. + dest.Value = editor.GetValueEditor().ToEditor(property, culture, segment); } } diff --git a/src/Umbraco.Core/Models/Mapping/ContentPropertyDisplayMapper.cs b/src/Umbraco.Core/Models/Mapping/ContentPropertyDisplayMapper.cs index a31d9e9c2754..468c98f889a5 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentPropertyDisplayMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentPropertyDisplayMapper.cs @@ -9,67 +9,71 @@ using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +/// +/// Creates a ContentPropertyDisplay from a Property +/// +internal class ContentPropertyDisplayMapper : ContentPropertyBasicMapper { - /// - /// Creates a ContentPropertyDisplay from a Property - /// - internal class ContentPropertyDisplayMapper : ContentPropertyBasicMapper - { - private readonly ICultureDictionary _cultureDictionary; - private readonly ILocalizedTextService _textService; + private readonly ICultureDictionary _cultureDictionary; + private readonly ILocalizedTextService _textService; - public ContentPropertyDisplayMapper(ICultureDictionary cultureDictionary, IDataTypeService dataTypeService, IEntityService entityService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors) - : base(dataTypeService, entityService, logger, propertyEditors) - { - _cultureDictionary = cultureDictionary; - _textService = textService; - } - public override void Map(IProperty originalProp, ContentPropertyDisplay dest, MapperContext context) - { - base.Map(originalProp, dest, context); + public ContentPropertyDisplayMapper(ICultureDictionary cultureDictionary, IDataTypeService dataTypeService, + IEntityService entityService, ILocalizedTextService textService, ILogger logger, + PropertyEditorCollection propertyEditors) + : base(dataTypeService, entityService, logger, propertyEditors) + { + _cultureDictionary = cultureDictionary; + _textService = textService; + } - var config = originalProp.PropertyType is null ? null : DataTypeService.GetDataType(originalProp.PropertyType.DataTypeId)?.Configuration; + public override void Map(IProperty originalProp, ContentPropertyDisplay dest, MapperContext context) + { + base.Map(originalProp, dest, context); - // TODO: IDataValueEditor configuration - general issue - // GetValueEditor() returns a non-configured IDataValueEditor - // - for richtext and nested, configuration determines HideLabel, so we need to configure the value editor - // - could configuration also determines ValueType, everywhere? - // - does it make any sense to use a IDataValueEditor without configuring it? + var config = originalProp.PropertyType is null + ? null + : DataTypeService.GetDataType(originalProp.PropertyType.DataTypeId)?.Configuration; - // configure the editor for display with configuration - var valEditor = dest.PropertyEditor?.GetValueEditor(config); + // TODO: IDataValueEditor configuration - general issue + // GetValueEditor() returns a non-configured IDataValueEditor + // - for richtext and nested, configuration determines HideLabel, so we need to configure the value editor + // - could configuration also determines ValueType, everywhere? + // - does it make any sense to use a IDataValueEditor without configuring it? - //set the display properties after mapping - dest.Alias = originalProp.Alias; - dest.Description = originalProp.PropertyType?.Description; - dest.Label = originalProp.PropertyType?.Name; - dest.HideLabel = valEditor?.HideLabel ?? false; - dest.LabelOnTop = originalProp.PropertyType?.LabelOnTop; + // configure the editor for display with configuration + IDataValueEditor valEditor = dest.PropertyEditor?.GetValueEditor(config); - //add the validation information - dest.Validation.Mandatory = originalProp.PropertyType?.Mandatory ?? false; - dest.Validation.MandatoryMessage = originalProp.PropertyType?.MandatoryMessage; - dest.Validation.Pattern = originalProp.PropertyType?.ValidationRegExp; - dest.Validation.PatternMessage = originalProp.PropertyType?.ValidationRegExpMessage; + //set the display properties after mapping + dest.Alias = originalProp.Alias; + dest.Description = originalProp.PropertyType?.Description; + dest.Label = originalProp.PropertyType?.Name; + dest.HideLabel = valEditor?.HideLabel ?? false; + dest.LabelOnTop = originalProp.PropertyType?.LabelOnTop; - if (dest.PropertyEditor == null) - { - //display.Config = PreValueCollection.AsDictionary(preVals); - //if there is no property editor it means that it is a legacy data type - // we cannot support editing with that so we'll just render the readonly value view. - dest.View = "views/propertyeditors/readonlyvalue/readonlyvalue.html"; - } - else - { - //let the property editor format the pre-values - dest.Config = dest.PropertyEditor.GetConfigurationEditor().ToValueEditor(config); - dest.View = valEditor?.View; - } + //add the validation information + dest.Validation.Mandatory = originalProp.PropertyType?.Mandatory ?? false; + dest.Validation.MandatoryMessage = originalProp.PropertyType?.MandatoryMessage; + dest.Validation.Pattern = originalProp.PropertyType?.ValidationRegExp; + dest.Validation.PatternMessage = originalProp.PropertyType?.ValidationRegExpMessage; - //Translate - dest.Label = _textService.UmbracoDictionaryTranslate(_cultureDictionary, dest.Label); - dest.Description = _textService.UmbracoDictionaryTranslate(_cultureDictionary, dest.Description); + if (dest.PropertyEditor == null) + { + //display.Config = PreValueCollection.AsDictionary(preVals); + //if there is no property editor it means that it is a legacy data type + // we cannot support editing with that so we'll just render the readonly value view. + dest.View = "views/propertyeditors/readonlyvalue/readonlyvalue.html"; } + else + { + //let the property editor format the pre-values + dest.Config = dest.PropertyEditor.GetConfigurationEditor().ToValueEditor(config); + dest.View = valEditor?.View; + } + + //Translate + dest.Label = _textService.UmbracoDictionaryTranslate(_cultureDictionary, dest.Label); + dest.Description = _textService.UmbracoDictionaryTranslate(_cultureDictionary, dest.Description); } } diff --git a/src/Umbraco.Core/Models/Mapping/ContentPropertyDtoMapper.cs b/src/Umbraco.Core/Models/Mapping/ContentPropertyDtoMapper.cs index fe1eff99caab..6f645ab7d80b 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentPropertyDtoMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentPropertyDtoMapper.cs @@ -4,29 +4,32 @@ using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +/// +/// Creates a ContentPropertyDto from a Property +/// +internal class ContentPropertyDtoMapper : ContentPropertyBasicMapper { - /// - /// Creates a ContentPropertyDto from a Property - /// - internal class ContentPropertyDtoMapper : ContentPropertyBasicMapper + public ContentPropertyDtoMapper(IDataTypeService dataTypeService, IEntityService entityService, + ILogger logger, PropertyEditorCollection propertyEditors) + : base(dataTypeService, entityService, logger, propertyEditors) { - public ContentPropertyDtoMapper(IDataTypeService dataTypeService, IEntityService entityService, ILogger logger, PropertyEditorCollection propertyEditors) - : base(dataTypeService, entityService, logger, propertyEditors) - { } + } - public override void Map(IProperty property, ContentPropertyDto dest, MapperContext context) - { - base.Map(property, dest, context); + public override void Map(IProperty property, ContentPropertyDto dest, MapperContext context) + { + base.Map(property, dest, context); - dest.IsRequired = property.PropertyType?.Mandatory; - dest.IsRequiredMessage = property.PropertyType?.MandatoryMessage; - dest.ValidationRegExp = property.PropertyType?.ValidationRegExp; - dest.ValidationRegExpMessage = property.PropertyType?.ValidationRegExpMessage; - dest.Description = property.PropertyType?.Description; - dest.Label = property.PropertyType?.Name; - dest.DataType = property.PropertyType is null ? null : DataTypeService.GetDataType(property.PropertyType.DataTypeId); - dest.LabelOnTop = property.PropertyType?.LabelOnTop; - } + dest.IsRequired = property.PropertyType?.Mandatory; + dest.IsRequiredMessage = property.PropertyType?.MandatoryMessage; + dest.ValidationRegExp = property.PropertyType?.ValidationRegExp; + dest.ValidationRegExpMessage = property.PropertyType?.ValidationRegExpMessage; + dest.Description = property.PropertyType?.Description; + dest.Label = property.PropertyType?.Name; + dest.DataType = property.PropertyType is null + ? null + : DataTypeService.GetDataType(property.PropertyType.DataTypeId); + dest.LabelOnTop = property.PropertyType?.LabelOnTop; } } diff --git a/src/Umbraco.Core/Models/Mapping/ContentPropertyMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/ContentPropertyMapDefinition.cs index 270d82138066..6df8943ae7de 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentPropertyMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentPropertyMapDefinition.cs @@ -5,60 +5,61 @@ using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +/// +/// A mapper which declares how to map content properties. These mappings are shared among media (and probably members) +/// which is +/// why they are in their own mapper +/// +public class ContentPropertyMapDefinition : IMapDefinition { - /// - /// A mapper which declares how to map content properties. These mappings are shared among media (and probably members) which is - /// why they are in their own mapper - /// - public class ContentPropertyMapDefinition : IMapDefinition - { - private readonly ContentPropertyBasicMapper _contentPropertyBasicConverter; - private readonly ContentPropertyDtoMapper _contentPropertyDtoConverter; - private readonly ContentPropertyDisplayMapper _contentPropertyDisplayMapper; + private readonly ContentPropertyBasicMapper _contentPropertyBasicConverter; + private readonly ContentPropertyDisplayMapper _contentPropertyDisplayMapper; + private readonly ContentPropertyDtoMapper _contentPropertyDtoConverter; - public ContentPropertyMapDefinition(ICultureDictionary cultureDictionary, IDataTypeService dataTypeService, IEntityService entityService, ILocalizedTextService textService, ILoggerFactory loggerFactory, PropertyEditorCollection propertyEditors) - { - _contentPropertyBasicConverter = new ContentPropertyBasicMapper(dataTypeService, entityService, loggerFactory.CreateLogger>(), propertyEditors); - _contentPropertyDtoConverter = new ContentPropertyDtoMapper(dataTypeService, entityService, loggerFactory.CreateLogger(), propertyEditors); - _contentPropertyDisplayMapper = new ContentPropertyDisplayMapper(cultureDictionary, dataTypeService, entityService, textService, loggerFactory.CreateLogger(), propertyEditors); - } + public ContentPropertyMapDefinition(ICultureDictionary cultureDictionary, IDataTypeService dataTypeService, + IEntityService entityService, ILocalizedTextService textService, ILoggerFactory loggerFactory, + PropertyEditorCollection propertyEditors) + { + _contentPropertyBasicConverter = new ContentPropertyBasicMapper(dataTypeService, + entityService, loggerFactory.CreateLogger>(), + propertyEditors); + _contentPropertyDtoConverter = new ContentPropertyDtoMapper(dataTypeService, entityService, + loggerFactory.CreateLogger(), propertyEditors); + _contentPropertyDisplayMapper = new ContentPropertyDisplayMapper(cultureDictionary, dataTypeService, + entityService, textService, loggerFactory.CreateLogger(), propertyEditors); + } - public void DefineMaps(IUmbracoMapper mapper) - { - mapper.Define>((source, context) => new Tab(), Map); - mapper.Define((source, context) => new ContentPropertyBasic(), Map); - mapper.Define((source, context) => new ContentPropertyDto(), Map); - mapper.Define((source, context) => new ContentPropertyDisplay(), Map); - } + public void DefineMaps(IUmbracoMapper mapper) + { + mapper.Define>( + (source, context) => new Tab(), Map); + mapper.Define((source, context) => new ContentPropertyBasic(), Map); + mapper.Define((source, context) => new ContentPropertyDto(), Map); + mapper.Define((source, context) => new ContentPropertyDisplay(), Map); + } - // Umbraco.Code.MapAll -Properties -Alias -Expanded - private void Map(PropertyGroup source, Tab target, MapperContext mapper) - { - target.Id = source.Id; - target.Key = source.Key; - target.Type = source.Type.ToString(); - target.Label = source.Name; - target.Alias = source.Alias; - target.IsActive = true; - } + // Umbraco.Code.MapAll -Properties -Alias -Expanded + private void Map(PropertyGroup source, Tab target, MapperContext mapper) + { + target.Id = source.Id; + target.Key = source.Key; + target.Type = source.Type.ToString(); + target.Label = source.Name; + target.Alias = source.Alias; + target.IsActive = true; + } - private void Map(IProperty source, ContentPropertyBasic target, MapperContext context) - { - // assume this is mapping everything and no MapAll is required - _contentPropertyBasicConverter.Map(source, target, context); - } + private void Map(IProperty source, ContentPropertyBasic target, MapperContext context) => + // assume this is mapping everything and no MapAll is required + _contentPropertyBasicConverter.Map(source, target, context); - private void Map(IProperty source, ContentPropertyDto target, MapperContext context) - { - // assume this is mapping everything and no MapAll is required - _contentPropertyDtoConverter.Map(source, target, context); - } + private void Map(IProperty source, ContentPropertyDto target, MapperContext context) => + // assume this is mapping everything and no MapAll is required + _contentPropertyDtoConverter.Map(source, target, context); - private void Map(IProperty source, ContentPropertyDisplay target, MapperContext context) - { - // assume this is mapping everything and no MapAll is required - _contentPropertyDisplayMapper.Map(source, target, context); - } - } + private void Map(IProperty source, ContentPropertyDisplay target, MapperContext context) => + // assume this is mapping everything and no MapAll is required + _contentPropertyDisplayMapper.Map(source, target, context); } diff --git a/src/Umbraco.Core/Models/Mapping/ContentSavedStateMapper.cs b/src/Umbraco.Core/Models/Mapping/ContentSavedStateMapper.cs index a087ce0d3e67..bf9cfcad50cb 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentSavedStateMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentSavedStateMapper.cs @@ -1,76 +1,83 @@ -using System; -using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +/// +/// Returns the for an item +/// +/// +public class ContentBasicSavedStateMapper + where T : ContentPropertyBasic { - /// - /// Returns the for an item - /// - /// - public class ContentBasicSavedStateMapper - where T : ContentPropertyBasic - { - private readonly ContentSavedStateMapper _inner = new ContentSavedStateMapper(); + private readonly ContentSavedStateMapper _inner = new(); - public ContentSavedState? Map(IContent source, MapperContext context) - { - return _inner.Map(source, context); - } - } + public ContentSavedState? Map(IContent source, MapperContext context) => _inner.Map(source, context); +} - /// - /// Returns the for an item - /// - /// - public class ContentSavedStateMapper - where T : ContentPropertyBasic +/// +/// Returns the for an item +/// +/// +public class ContentSavedStateMapper + where T : ContentPropertyBasic +{ + public ContentSavedState Map(IContent source, MapperContext context) { - public ContentSavedState Map(IContent source, MapperContext context) + PublishedState publishedState; + bool isEdited; + bool isCreated; + + if (source.ContentType.VariesByCulture()) { - PublishedState publishedState; - bool isEdited; - bool isCreated; + //Get the culture from the context which will be set during the mapping operation for each variant + var culture = context.GetCulture(); - if (source.ContentType.VariesByCulture()) + //a culture needs to be in the context for a variant content item + if (culture == null) { - //Get the culture from the context which will be set during the mapping operation for each variant - var culture = context.GetCulture(); - - //a culture needs to be in the context for a variant content item - if (culture == null) - throw new InvalidOperationException($"No culture found in mapping operation when one is required for a culture variant"); + throw new InvalidOperationException( + "No culture found in mapping operation when one is required for a culture variant"); + } - publishedState = source.PublishedState == PublishedState.Unpublished //if the entire document is unpublished, then flag every variant as unpublished + publishedState = + source.PublishedState == + PublishedState + .Unpublished //if the entire document is unpublished, then flag every variant as unpublished ? PublishedState.Unpublished : source.IsCulturePublished(culture) ? PublishedState.Published : PublishedState.Unpublished; - isEdited = source.IsCultureEdited(culture); - isCreated = source.Id > 0 && source.IsCultureAvailable(culture); - } - else - { - publishedState = source.PublishedState == PublishedState.Unpublished - ? PublishedState.Unpublished - : PublishedState.Published; - - isEdited = source.Edited; - isCreated = source.Id > 0; - } + isEdited = source.IsCultureEdited(culture); + isCreated = source.Id > 0 && source.IsCultureAvailable(culture); + } + else + { + publishedState = source.PublishedState == PublishedState.Unpublished + ? PublishedState.Unpublished + : PublishedState.Published; - if (!isCreated) - return ContentSavedState.NotCreated; + isEdited = source.Edited; + isCreated = source.Id > 0; + } - if (publishedState == PublishedState.Unpublished) - return ContentSavedState.Draft; + if (!isCreated) + { + return ContentSavedState.NotCreated; + } - if (publishedState == PublishedState.Published) - return isEdited ? ContentSavedState.PublishedPendingChanges : ContentSavedState.Published; + if (publishedState == PublishedState.Unpublished) + { + return ContentSavedState.Draft; + } - throw new NotSupportedException($"PublishedState {publishedState} is not supported."); + if (publishedState == PublishedState.Published) + { + return isEdited ? ContentSavedState.PublishedPendingChanges : ContentSavedState.Published; } + + throw new NotSupportedException($"PublishedState {publishedState} is not supported."); } } diff --git a/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs index bacab0b7cf7b..1404986e8d17 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -16,899 +13,906 @@ using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +/// +/// Defines mappings for content/media/members type mappings +/// +public class ContentTypeMapDefinition : IMapDefinition { - /// - /// Defines mappings for content/media/members type mappings - /// - public class ContentTypeMapDefinition : IMapDefinition + private readonly CommonMapper _commonMapper; + private readonly IContentTypeService _contentTypeService; + private readonly IDataTypeService _dataTypeService; + private readonly IFileService _fileService; + private readonly GlobalSettings _globalSettings; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly ILogger _logger; + private readonly ILoggerFactory _loggerFactory; + private readonly IMediaTypeService _mediaTypeService; + private readonly IMemberTypeService _memberTypeService; + private readonly PropertyEditorCollection _propertyEditors; + private readonly IShortStringHelper _shortStringHelper; + private ContentSettings _contentSettings; + + + [Obsolete("Use ctor with all params injected")] + public ContentTypeMapDefinition(CommonMapper commonMapper, PropertyEditorCollection propertyEditors, + IDataTypeService dataTypeService, IFileService fileService, + IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, + IMemberTypeService memberTypeService, + ILoggerFactory loggerFactory, IShortStringHelper shortStringHelper, IOptions globalSettings, + IHostingEnvironment hostingEnvironment) + : this(commonMapper, propertyEditors, dataTypeService, fileService, contentTypeService, mediaTypeService, + memberTypeService, loggerFactory, shortStringHelper, globalSettings, hostingEnvironment, + StaticServiceProvider.Instance.GetRequiredService>()) { - private readonly CommonMapper _commonMapper; - private readonly IContentTypeService _contentTypeService; - private readonly IDataTypeService _dataTypeService; - private readonly IFileService _fileService; - private readonly GlobalSettings _globalSettings; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly ILogger _logger; - private readonly ILoggerFactory _loggerFactory; - private readonly IMediaTypeService _mediaTypeService; - private readonly IMemberTypeService _memberTypeService; - private readonly PropertyEditorCollection _propertyEditors; - private readonly IShortStringHelper _shortStringHelper; - private ContentSettings _contentSettings; - - - [Obsolete("Use ctor with all params injected")] - public ContentTypeMapDefinition(CommonMapper commonMapper, PropertyEditorCollection propertyEditors, - IDataTypeService dataTypeService, IFileService fileService, - IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, - IMemberTypeService memberTypeService, - ILoggerFactory loggerFactory, IShortStringHelper shortStringHelper, IOptions globalSettings, - IHostingEnvironment hostingEnvironment) - : this(commonMapper, propertyEditors, dataTypeService, fileService, contentTypeService, mediaTypeService, - memberTypeService, loggerFactory, shortStringHelper, globalSettings, hostingEnvironment, - StaticServiceProvider.Instance.GetRequiredService>()) - { - } - - public ContentTypeMapDefinition(CommonMapper commonMapper, PropertyEditorCollection propertyEditors, - IDataTypeService dataTypeService, IFileService fileService, - IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, - IMemberTypeService memberTypeService, - ILoggerFactory loggerFactory, IShortStringHelper shortStringHelper, IOptions globalSettings, - IHostingEnvironment hostingEnvironment, IOptionsMonitor contentSettings) - { - _commonMapper = commonMapper; - _propertyEditors = propertyEditors; - _dataTypeService = dataTypeService; - _fileService = fileService; - _contentTypeService = contentTypeService; - _mediaTypeService = mediaTypeService; - _memberTypeService = memberTypeService; - _loggerFactory = loggerFactory; - _logger = _loggerFactory.CreateLogger(); - _shortStringHelper = shortStringHelper; - _globalSettings = globalSettings.Value; - _hostingEnvironment = hostingEnvironment; - - _contentSettings = contentSettings.CurrentValue; - contentSettings.OnChange(x => _contentSettings = x); - } - - public void DefineMaps(IUmbracoMapper mapper) - { - mapper.Define( - (source, context) => new ContentType(_shortStringHelper, source.ParentId), Map); - mapper.Define( - (source, context) => new MediaType(_shortStringHelper, source.ParentId), Map); - mapper.Define( - (source, context) => new MemberType(_shortStringHelper, source.ParentId), Map); - - mapper.Define((source, context) => new DocumentTypeDisplay(), Map); - mapper.Define((source, context) => new MediaTypeDisplay(), Map); - mapper.Define((source, context) => new MemberTypeDisplay(), Map); - - mapper.Define( - (source, context) => - { - IDataType? dataType = _dataTypeService.GetDataType(source.DataTypeId); - if (dataType == null) - { - throw new NullReferenceException("No data type found with id " + source.DataTypeId); - } - - return new PropertyType(_shortStringHelper, dataType, source.Alias); - }, Map); + } - // TODO: isPublishing in ctor? - mapper.Define, PropertyGroup>( - (source, context) => new PropertyGroup(false), Map); - mapper.Define, PropertyGroup>( - (source, context) => new PropertyGroup(false), Map); + public ContentTypeMapDefinition(CommonMapper commonMapper, PropertyEditorCollection propertyEditors, + IDataTypeService dataTypeService, IFileService fileService, + IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, + IMemberTypeService memberTypeService, + ILoggerFactory loggerFactory, IShortStringHelper shortStringHelper, IOptions globalSettings, + IHostingEnvironment hostingEnvironment, IOptionsMonitor contentSettings) + { + _commonMapper = commonMapper; + _propertyEditors = propertyEditors; + _dataTypeService = dataTypeService; + _fileService = fileService; + _contentTypeService = contentTypeService; + _mediaTypeService = mediaTypeService; + _memberTypeService = memberTypeService; + _loggerFactory = loggerFactory; + _logger = _loggerFactory.CreateLogger(); + _shortStringHelper = shortStringHelper; + _globalSettings = globalSettings.Value; + _hostingEnvironment = hostingEnvironment; + + _contentSettings = contentSettings.CurrentValue; + contentSettings.OnChange(x => _contentSettings = x); + } - mapper.Define((source, context) => new ContentTypeBasic(), Map); - mapper.Define((source, context) => new ContentTypeBasic(), Map); - mapper.Define((source, context) => new ContentTypeBasic(), Map); - mapper.Define((source, context) => new ContentTypeBasic(), Map); + public void DefineMaps(IUmbracoMapper mapper) + { + mapper.Define( + (source, context) => new ContentType(_shortStringHelper, source.ParentId), Map); + mapper.Define( + (source, context) => new MediaType(_shortStringHelper, source.ParentId), Map); + mapper.Define( + (source, context) => new MemberType(_shortStringHelper, source.ParentId), Map); + + mapper.Define((source, context) => new DocumentTypeDisplay(), Map); + mapper.Define((source, context) => new MediaTypeDisplay(), Map); + mapper.Define((source, context) => new MemberTypeDisplay(), Map); + + mapper.Define( + (source, context) => + { + IDataType? dataType = _dataTypeService.GetDataType(source.DataTypeId); + if (dataType == null) + { + throw new NullReferenceException("No data type found with id " + source.DataTypeId); + } - mapper.Define((source, context) => new DocumentTypeDisplay(), Map); - mapper.Define((source, context) => new MediaTypeDisplay(), Map); - mapper.Define((source, context) => new MemberTypeDisplay(), Map); + return new PropertyType(_shortStringHelper, dataType, source.Alias); + }, Map); - mapper.Define, PropertyGroupDisplay>( - (source, context) => new PropertyGroupDisplay(), Map); - mapper.Define, PropertyGroupDisplay>( - (source, context) => new PropertyGroupDisplay(), Map); + // TODO: isPublishing in ctor? + mapper.Define, PropertyGroup>( + (source, context) => new PropertyGroup(false), Map); + mapper.Define, PropertyGroup>( + (source, context) => new PropertyGroup(false), Map); - mapper.Define((source, context) => new PropertyTypeDisplay(), Map); - mapper.Define( - (source, context) => new MemberPropertyTypeDisplay(), Map); - } + mapper.Define((source, context) => new ContentTypeBasic(), Map); + mapper.Define((source, context) => new ContentTypeBasic(), Map); + mapper.Define((source, context) => new ContentTypeBasic(), Map); + mapper.Define((source, context) => new ContentTypeBasic(), Map); - // no MapAll - take care - private void Map(DocumentTypeSave source, IContentType target, MapperContext context) - { - MapSaveToTypeBase(source, target, context); - MapComposition(source, target, alias => _contentTypeService.Get(alias)); + mapper.Define((source, context) => new DocumentTypeDisplay(), Map); + mapper.Define((source, context) => new MediaTypeDisplay(), Map); + mapper.Define((source, context) => new MemberTypeDisplay(), Map); - if (target is IContentTypeWithHistoryCleanup targetWithHistoryCleanup) - { - MapHistoryCleanup(source, targetWithHistoryCleanup); - } + mapper.Define, PropertyGroupDisplay>( + (source, context) => new PropertyGroupDisplay(), Map); + mapper.Define, PropertyGroupDisplay>( + (source, context) => new PropertyGroupDisplay(), Map); - target.AllowedTemplates = source.AllowedTemplates? - .Where(x => x != null) - .Select(_fileService.GetTemplate) - .WhereNotNull() - .ToArray(); + mapper.Define((source, context) => new PropertyTypeDisplay(), Map); + mapper.Define( + (source, context) => new MemberPropertyTypeDisplay(), Map); + } - target.SetDefaultTemplate(source.DefaultTemplate == null - ? null - : _fileService.GetTemplate(source.DefaultTemplate)); - } + // no MapAll - take care + private void Map(DocumentTypeSave source, IContentType target, MapperContext context) + { + MapSaveToTypeBase(source, target, context); + MapComposition(source, target, alias => _contentTypeService.Get(alias)); - private static void MapHistoryCleanup(DocumentTypeSave source, IContentTypeWithHistoryCleanup target) + if (target is IContentTypeWithHistoryCleanup targetWithHistoryCleanup) { - // If source history cleanup is null we don't have to map all properties - if (source.HistoryCleanup is null) - { - target.HistoryCleanup = null; - return; - } + MapHistoryCleanup(source, targetWithHistoryCleanup); + } - // We need to reset the dirty properties, because it is otherwise true, just because the json serializer has set properties - target.HistoryCleanup!.ResetDirtyProperties(false); - if (target.HistoryCleanup.PreventCleanup != source.HistoryCleanup.PreventCleanup) - { - target.HistoryCleanup.PreventCleanup = source.HistoryCleanup.PreventCleanup; - } + target.AllowedTemplates = source.AllowedTemplates? + .Where(x => x != null) + .Select(_fileService.GetTemplate) + .WhereNotNull() + .ToArray(); - if (target.HistoryCleanup.KeepAllVersionsNewerThanDays != source.HistoryCleanup.KeepAllVersionsNewerThanDays) - { - target.HistoryCleanup.KeepAllVersionsNewerThanDays = source.HistoryCleanup.KeepAllVersionsNewerThanDays; - } + target.SetDefaultTemplate(source.DefaultTemplate == null + ? null + : _fileService.GetTemplate(source.DefaultTemplate)); + } - if (target.HistoryCleanup.KeepLatestVersionPerDayForDays != - source.HistoryCleanup.KeepLatestVersionPerDayForDays) - { - target.HistoryCleanup.KeepLatestVersionPerDayForDays = source.HistoryCleanup.KeepLatestVersionPerDayForDays; - } + private static void MapHistoryCleanup(DocumentTypeSave source, IContentTypeWithHistoryCleanup target) + { + // If source history cleanup is null we don't have to map all properties + if (source.HistoryCleanup is null) + { + target.HistoryCleanup = null; + return; } - // no MapAll - take care - private void Map(MediaTypeSave source, IMediaType target, MapperContext context) + // We need to reset the dirty properties, because it is otherwise true, just because the json serializer has set properties + target.HistoryCleanup!.ResetDirtyProperties(false); + if (target.HistoryCleanup.PreventCleanup != source.HistoryCleanup.PreventCleanup) { - MapSaveToTypeBase(source, target, context); - MapComposition(source, target, alias => _mediaTypeService.Get(alias)); + target.HistoryCleanup.PreventCleanup = source.HistoryCleanup.PreventCleanup; } - // no MapAll - take care - private void Map(MemberTypeSave source, IMemberType target, MapperContext context) + if (target.HistoryCleanup.KeepAllVersionsNewerThanDays != source.HistoryCleanup.KeepAllVersionsNewerThanDays) { - MapSaveToTypeBase(source, target, context); - MapComposition(source, target, alias => _memberTypeService.Get(alias)); - - foreach (MemberPropertyTypeBasic propertyType in source.Groups.SelectMany(x => x.Properties)) - { - MemberPropertyTypeBasic localCopy = propertyType; - IPropertyType? destProp = - target.PropertyTypes.SingleOrDefault(x => x.Alias?.InvariantEquals(localCopy.Alias) ?? false); - if (destProp == null) - { - continue; - } - - target.SetMemberCanEditProperty(localCopy.Alias, localCopy.MemberCanEditProperty); - target.SetMemberCanViewProperty(localCopy.Alias, localCopy.MemberCanViewProperty); - target.SetIsSensitiveProperty(localCopy.Alias, localCopy.IsSensitiveData); - } + target.HistoryCleanup.KeepAllVersionsNewerThanDays = source.HistoryCleanup.KeepAllVersionsNewerThanDays; } - // no MapAll - take care - private void Map(IContentType source, DocumentTypeDisplay target, MapperContext context) + if (target.HistoryCleanup.KeepLatestVersionPerDayForDays != + source.HistoryCleanup.KeepLatestVersionPerDayForDays) { - MapTypeToDisplayBase(source, target); - - if (source is IContentTypeWithHistoryCleanup sourceWithHistoryCleanup) - { - target.HistoryCleanup = new HistoryCleanupViewModel - { - PreventCleanup = sourceWithHistoryCleanup.HistoryCleanup?.PreventCleanup ?? false, - KeepAllVersionsNewerThanDays = sourceWithHistoryCleanup.HistoryCleanup?.KeepAllVersionsNewerThanDays, - KeepLatestVersionPerDayForDays = sourceWithHistoryCleanup.HistoryCleanup?.KeepLatestVersionPerDayForDays, - GlobalKeepAllVersionsNewerThanDays = _contentSettings.ContentVersionCleanupPolicy.KeepAllVersionsNewerThanDays, - GlobalKeepLatestVersionPerDayForDays = _contentSettings.ContentVersionCleanupPolicy.KeepLatestVersionPerDayForDays, - GlobalEnableCleanup = _contentSettings.ContentVersionCleanupPolicy.EnableCleanup - }; - } + target.HistoryCleanup.KeepLatestVersionPerDayForDays = source.HistoryCleanup.KeepLatestVersionPerDayForDays; + } + } - target.AllowCultureVariant = source.VariesByCulture(); - target.AllowSegmentVariant = source.VariesBySegment(); - target.ContentApps = _commonMapper.GetContentAppsForEntity(source); + // no MapAll - take care + private void Map(MediaTypeSave source, IMediaType target, MapperContext context) + { + MapSaveToTypeBase(source, target, context); + MapComposition(source, target, alias => _mediaTypeService.Get(alias)); + } - //sync templates - if (source.AllowedTemplates is not null) - { - target.AllowedTemplates = context.MapEnumerable(source.AllowedTemplates).WhereNotNull(); - } + // no MapAll - take care + private void Map(MemberTypeSave source, IMemberType target, MapperContext context) + { + MapSaveToTypeBase(source, target, context); + MapComposition(source, target, alias => _memberTypeService.Get(alias)); - if (source.DefaultTemplate != null) + foreach (MemberPropertyTypeBasic propertyType in source.Groups.SelectMany(x => x.Properties)) + { + MemberPropertyTypeBasic localCopy = propertyType; + IPropertyType? destProp = + target.PropertyTypes.SingleOrDefault(x => x.Alias?.InvariantEquals(localCopy.Alias) ?? false); + if (destProp == null) { - target.DefaultTemplate = context.Map(source.DefaultTemplate); + continue; } - //default listview - target.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Content"; + target.SetMemberCanEditProperty(localCopy.Alias, localCopy.MemberCanEditProperty); + target.SetMemberCanViewProperty(localCopy.Alias, localCopy.MemberCanViewProperty); + target.SetIsSensitiveProperty(localCopy.Alias, localCopy.IsSensitiveData); + } + } - if (string.IsNullOrEmpty(source.Alias)) - { - return; - } + // no MapAll - take care + private void Map(IContentType source, DocumentTypeDisplay target, MapperContext context) + { + MapTypeToDisplayBase(source, target); - var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Alias; - if (_dataTypeService.GetDataType(name) != null) - { - target.ListViewEditorName = name; - } + if (source is IContentTypeWithHistoryCleanup sourceWithHistoryCleanup) + { + target.HistoryCleanup = new HistoryCleanupViewModel + { + PreventCleanup = sourceWithHistoryCleanup.HistoryCleanup?.PreventCleanup ?? false, + KeepAllVersionsNewerThanDays = + sourceWithHistoryCleanup.HistoryCleanup?.KeepAllVersionsNewerThanDays, + KeepLatestVersionPerDayForDays = + sourceWithHistoryCleanup.HistoryCleanup?.KeepLatestVersionPerDayForDays, + GlobalKeepAllVersionsNewerThanDays = + _contentSettings.ContentVersionCleanupPolicy.KeepAllVersionsNewerThanDays, + GlobalKeepLatestVersionPerDayForDays = + _contentSettings.ContentVersionCleanupPolicy.KeepLatestVersionPerDayForDays, + GlobalEnableCleanup = _contentSettings.ContentVersionCleanupPolicy.EnableCleanup + }; } - // no MapAll - take care - private void Map(IMediaType source, MediaTypeDisplay target, MapperContext context) + target.AllowCultureVariant = source.VariesByCulture(); + target.AllowSegmentVariant = source.VariesBySegment(); + target.ContentApps = _commonMapper.GetContentAppsForEntity(source); + + //sync templates + if (source.AllowedTemplates is not null) { - MapTypeToDisplayBase(source, target); + target.AllowedTemplates = + context.MapEnumerable(source.AllowedTemplates).WhereNotNull(); + } - //default listview - target.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Media"; - target.IsSystemMediaType = source.IsSystemMediaType(); + if (source.DefaultTemplate != null) + { + target.DefaultTemplate = context.Map(source.DefaultTemplate); + } - if (string.IsNullOrEmpty(source.Name)) - { - return; - } + //default listview + target.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Content"; - var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Name; - if (_dataTypeService.GetDataType(name) != null) - { - target.ListViewEditorName = name; - } + if (string.IsNullOrEmpty(source.Alias)) + { + return; } - // no MapAll - take care - private void Map(IMemberType source, MemberTypeDisplay target, MapperContext context) + var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Alias; + if (_dataTypeService.GetDataType(name) != null) { - MapTypeToDisplayBase(source, target); + target.ListViewEditorName = name; + } + } - //map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData - foreach (IPropertyType propertyType in source.PropertyTypes) - { - IPropertyType localCopy = propertyType; - MemberPropertyTypeDisplay? displayProp = target.Groups.SelectMany(dest => dest.Properties) - .SingleOrDefault(dest => dest.Alias?.InvariantEquals(localCopy.Alias) ?? false); - if (displayProp == null) - { - continue; - } + // no MapAll - take care + private void Map(IMediaType source, MediaTypeDisplay target, MapperContext context) + { + MapTypeToDisplayBase(source, target); - displayProp.MemberCanEditProperty = source.MemberCanEditProperty(localCopy.Alias); - displayProp.MemberCanViewProperty = source.MemberCanViewProperty(localCopy.Alias); - displayProp.IsSensitiveData = source.IsSensitiveProperty(localCopy.Alias); - } - } + //default listview + target.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Media"; + target.IsSystemMediaType = source.IsSystemMediaType(); - // Umbraco.Code.MapAll -Blueprints - private void Map(IContentTypeBase source, ContentTypeBasic target, string entityType) + if (string.IsNullOrEmpty(source.Name)) { - target.Udi = Udi.Create(entityType, source.Key); - target.Alias = source.Alias; - target.CreateDate = source.CreateDate; - target.Description = source.Description; - target.Icon = source.Icon; - target.IconFilePath = target.IconIsClass - ? string.Empty - : $"{_globalSettings.GetBackOfficePath(_hostingEnvironment).EnsureEndsWith("/")}images/umbraco/{source.Icon}"; + return; + } - target.Trashed = source.Trashed; - target.Id = source.Id; - target.IsContainer = source.IsContainer; - target.IsElement = source.IsElement; - target.Key = source.Key; - target.Name = source.Name; - target.ParentId = source.ParentId; - target.Path = source.Path; - target.Thumbnail = source.Thumbnail; - target.ThumbnailFilePath = target.ThumbnailIsClass - ? string.Empty - : _hostingEnvironment.ToAbsolute("~/umbraco/images/thumbnails/" + source.Thumbnail); - target.UpdateDate = source.UpdateDate; - } - - // no MapAll - uses the IContentTypeBase map method, which has MapAll - private void Map(IContentTypeComposition source, ContentTypeBasic target, MapperContext context) => - Map(source, target, Constants.UdiEntityType.MemberType); - - // no MapAll - uses the IContentTypeBase map method, which has MapAll - private void Map(IContentType source, ContentTypeBasic target, MapperContext context) => - Map(source, target, Constants.UdiEntityType.DocumentType); - - // no MapAll - uses the IContentTypeBase map method, which has MapAll - private void Map(IMediaType source, ContentTypeBasic target, MapperContext context) => - Map(source, target, Constants.UdiEntityType.MediaType); - - // no MapAll - uses the IContentTypeBase map method, which has MapAll - private void Map(IMemberType source, ContentTypeBasic target, MapperContext context) => - Map(source, target, Constants.UdiEntityType.MemberType); - - // Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate - // Umbraco.Code.MapAll -SupportsPublishing -Key -PropertyEditorAlias -ValueStorageType -Variations - private static void Map(PropertyTypeBasic source, IPropertyType target, MapperContext context) - { - target.Name = source.Label; - target.DataTypeId = source.DataTypeId; - target.DataTypeKey = source.DataTypeKey; - target.Mandatory = source.Validation?.Mandatory ?? false; - target.MandatoryMessage = source.Validation?.MandatoryMessage; - target.ValidationRegExp = source.Validation?.Pattern; - target.ValidationRegExpMessage = source.Validation?.PatternMessage; - target.SetVariesBy(ContentVariation.Culture, source.AllowCultureVariant); - target.SetVariesBy(ContentVariation.Segment, source.AllowSegmentVariant); + var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Name; + if (_dataTypeService.GetDataType(name) != null) + { + target.ListViewEditorName = name; + } + } - if (source.Id > 0) - { - target.Id = source.Id; - } + // no MapAll - take care + private void Map(IMemberType source, MemberTypeDisplay target, MapperContext context) + { + MapTypeToDisplayBase(source, target); - if (source.GroupId > 0) + //map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData + foreach (IPropertyType propertyType in source.PropertyTypes) + { + IPropertyType localCopy = propertyType; + MemberPropertyTypeDisplay? displayProp = target.Groups.SelectMany(dest => dest.Properties) + .SingleOrDefault(dest => dest.Alias?.InvariantEquals(localCopy.Alias) ?? false); + if (displayProp == null) { - if (target.PropertyGroupId?.Value != source.GroupId) - { - target.PropertyGroupId = new Lazy(() => source.GroupId, false); - } + continue; } - target.Alias = source.Alias; - target.Description = source.Description; - target.SortOrder = source.SortOrder; - target.LabelOnTop = source.LabelOnTop; + displayProp.MemberCanEditProperty = source.MemberCanEditProperty(localCopy.Alias); + displayProp.MemberCanViewProperty = source.MemberCanViewProperty(localCopy.Alias); + displayProp.IsSensitiveData = source.IsSensitiveProperty(localCopy.Alias); } + } - // no MapAll - take care - private void Map(DocumentTypeSave source, DocumentTypeDisplay target, MapperContext context) - { - MapTypeToDisplayBase(source, - target, context); + // Umbraco.Code.MapAll -Blueprints + private void Map(IContentTypeBase source, ContentTypeBasic target, string entityType) + { + target.Udi = Udi.Create(entityType, source.Key); + target.Alias = source.Alias; + target.CreateDate = source.CreateDate; + target.Description = source.Description; + target.Icon = source.Icon; + target.IconFilePath = target.IconIsClass + ? string.Empty + : $"{_globalSettings.GetBackOfficePath(_hostingEnvironment).EnsureEndsWith("/")}images/umbraco/{source.Icon}"; + + target.Trashed = source.Trashed; + target.Id = source.Id; + target.IsContainer = source.IsContainer; + target.IsElement = source.IsElement; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = source.ParentId; + target.Path = source.Path; + target.Thumbnail = source.Thumbnail; + target.ThumbnailFilePath = target.ThumbnailIsClass + ? string.Empty + : _hostingEnvironment.ToAbsolute("~/umbraco/images/thumbnails/" + source.Thumbnail); + target.UpdateDate = source.UpdateDate; + } - //sync templates - IEnumerable destAllowedTemplateAliases = target.AllowedTemplates.Select(x => x.Alias); - //if the dest is set and it's the same as the source, then don't change - if (source.AllowedTemplates is not null && destAllowedTemplateAliases.SequenceEqual(source.AllowedTemplates) == false) - { - IEnumerable? templates = _fileService.GetTemplates(source.AllowedTemplates.ToArray()); - target.AllowedTemplates = source.AllowedTemplates - .Select(x => - { - ITemplate? template = templates?.SingleOrDefault(t => t.Alias == x); - return template != null - ? context.Map(template) - : null; - }) - .WhereNotNull() - .ToArray(); - } + // no MapAll - uses the IContentTypeBase map method, which has MapAll + private void Map(IContentTypeComposition source, ContentTypeBasic target, MapperContext context) => + Map(source, target, Constants.UdiEntityType.MemberType); - if (source.DefaultTemplate.IsNullOrWhiteSpace() == false) - { - //if the dest is set and it's the same as the source, then don't change - if (target.DefaultTemplate == null || source.DefaultTemplate != target.DefaultTemplate.Alias) - { - ITemplate? template = _fileService.GetTemplate(source.DefaultTemplate); - target.DefaultTemplate = template == null ? null : context.Map(template); - } - } - else - { - target.DefaultTemplate = null; - } - } + // no MapAll - uses the IContentTypeBase map method, which has MapAll + private void Map(IContentType source, ContentTypeBasic target, MapperContext context) => + Map(source, target, Constants.UdiEntityType.DocumentType); - // no MapAll - take care - private void Map(MediaTypeSave source, MediaTypeDisplay target, MapperContext context) => - MapTypeToDisplayBase(source, - target, context); + // no MapAll - uses the IContentTypeBase map method, which has MapAll + private void Map(IMediaType source, ContentTypeBasic target, MapperContext context) => + Map(source, target, Constants.UdiEntityType.MediaType); - // no MapAll - take care - private void Map(MemberTypeSave source, MemberTypeDisplay target, MapperContext context) => - MapTypeToDisplayBase( - source, target, context); + // no MapAll - uses the IContentTypeBase map method, which has MapAll + private void Map(IMemberType source, ContentTypeBasic target, MapperContext context) => + Map(source, target, Constants.UdiEntityType.MemberType); - // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate -Key -PropertyTypes - private static void Map(PropertyGroupBasic source, PropertyGroup target, - MapperContext context) + // Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate + // Umbraco.Code.MapAll -SupportsPublishing -Key -PropertyEditorAlias -ValueStorageType -Variations + private static void Map(PropertyTypeBasic source, IPropertyType target, MapperContext context) + { + target.Name = source.Label; + target.DataTypeId = source.DataTypeId; + target.DataTypeKey = source.DataTypeKey; + target.Mandatory = source.Validation?.Mandatory ?? false; + target.MandatoryMessage = source.Validation?.MandatoryMessage; + target.ValidationRegExp = source.Validation?.Pattern; + target.ValidationRegExpMessage = source.Validation?.PatternMessage; + target.SetVariesBy(ContentVariation.Culture, source.AllowCultureVariant); + target.SetVariesBy(ContentVariation.Segment, source.AllowSegmentVariant); + + if (source.Id > 0) { - if (source.Id > 0) - { - target.Id = source.Id; - } - - target.Key = source.Key; - target.Type = source.Type; - target.Name = source.Name; - target.Alias = source.Alias; - target.SortOrder = source.SortOrder; + target.Id = source.Id; } - // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate -Key -PropertyTypes - private static void Map(PropertyGroupBasic source, PropertyGroup target, - MapperContext context) + if (source.GroupId > 0) { - if (source.Id > 0) + if (target.PropertyGroupId?.Value != source.GroupId) { - target.Id = source.Id; + target.PropertyGroupId = new Lazy(() => source.GroupId, false); } - - target.Key = source.Key; - target.Type = source.Type; - target.Name = source.Name; - target.Alias = source.Alias; - target.SortOrder = source.SortOrder; } - // Umbraco.Code.MapAll -ContentTypeId -ParentTabContentTypes -ParentTabContentTypeNames - private static void Map(PropertyGroupBasic source, - PropertyGroupDisplay target, MapperContext context) - { - target.Inherited = source.Inherited; - if (source.Id > 0) - { - target.Id = source.Id; - } + target.Alias = source.Alias; + target.Description = source.Description; + target.SortOrder = source.SortOrder; + target.LabelOnTop = source.LabelOnTop; + } - target.Key = source.Key; - target.Type = source.Type; - target.Name = source.Name; - target.Alias = source.Alias; - target.SortOrder = source.SortOrder; - target.Properties = context.MapEnumerable(source.Properties).WhereNotNull(); + // no MapAll - take care + private void Map(DocumentTypeSave source, DocumentTypeDisplay target, MapperContext context) + { + MapTypeToDisplayBase(source, + target, context); + + //sync templates + IEnumerable destAllowedTemplateAliases = target.AllowedTemplates.Select(x => x.Alias); + //if the dest is set and it's the same as the source, then don't change + if (source.AllowedTemplates is not null && + destAllowedTemplateAliases.SequenceEqual(source.AllowedTemplates) == false) + { + IEnumerable? templates = _fileService.GetTemplates(source.AllowedTemplates.ToArray()); + target.AllowedTemplates = source.AllowedTemplates + .Select(x => + { + ITemplate? template = templates?.SingleOrDefault(t => t.Alias == x); + return template != null + ? context.Map(template) + : null; + }) + .WhereNotNull() + .ToArray(); } - // Umbraco.Code.MapAll -ContentTypeId -ParentTabContentTypes -ParentTabContentTypeNames - private static void Map(PropertyGroupBasic source, - PropertyGroupDisplay target, MapperContext context) + if (source.DefaultTemplate.IsNullOrWhiteSpace() == false) { - target.Inherited = source.Inherited; - if (source.Id > 0) + //if the dest is set and it's the same as the source, then don't change + if (target.DefaultTemplate == null || source.DefaultTemplate != target.DefaultTemplate.Alias) { - target.Id = source.Id; + ITemplate? template = _fileService.GetTemplate(source.DefaultTemplate); + target.DefaultTemplate = template == null ? null : context.Map(template); } + } + else + { + target.DefaultTemplate = null; + } + } + + // no MapAll - take care + private void Map(MediaTypeSave source, MediaTypeDisplay target, MapperContext context) => + MapTypeToDisplayBase(source, + target, context); - target.Key = source.Key; - target.Type = source.Type; - target.Name = source.Name; - target.Alias = source.Alias; - target.SortOrder = source.SortOrder; - target.Properties = - context.MapEnumerable(source.Properties).WhereNotNull(); - } - - // Umbraco.Code.MapAll -Editor -View -Config -ContentTypeId -ContentTypeName -Locked -DataTypeIcon -DataTypeName - private static void Map(PropertyTypeBasic source, PropertyTypeDisplay target, MapperContext context) - { - target.Alias = source.Alias; - target.AllowCultureVariant = source.AllowCultureVariant; - target.AllowSegmentVariant = source.AllowSegmentVariant; - target.DataTypeId = source.DataTypeId; - target.DataTypeKey = source.DataTypeKey; - target.Description = source.Description; - target.GroupId = source.GroupId; + // no MapAll - take care + private void Map(MemberTypeSave source, MemberTypeDisplay target, MapperContext context) => + MapTypeToDisplayBase( + source, target, context); + + // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate -Key -PropertyTypes + private static void Map(PropertyGroupBasic source, PropertyGroup target, + MapperContext context) + { + if (source.Id > 0) + { target.Id = source.Id; - target.Inherited = source.Inherited; - target.Label = source.Label; - target.SortOrder = source.SortOrder; - target.Validation = source.Validation; - target.LabelOnTop = source.LabelOnTop; - } - - // Umbraco.Code.MapAll -Editor -View -Config -ContentTypeId -ContentTypeName -Locked -DataTypeIcon -DataTypeName - private static void Map(MemberPropertyTypeBasic source, MemberPropertyTypeDisplay target, MapperContext context) - { - target.Alias = source.Alias; - target.AllowCultureVariant = source.AllowCultureVariant; - target.AllowSegmentVariant = source.AllowSegmentVariant; - target.DataTypeId = source.DataTypeId; - target.DataTypeKey = source.DataTypeKey; - target.Description = source.Description; - target.GroupId = source.GroupId; + } + + target.Key = source.Key; + target.Type = source.Type; + target.Name = source.Name; + target.Alias = source.Alias; + target.SortOrder = source.SortOrder; + } + + // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate -Key -PropertyTypes + private static void Map(PropertyGroupBasic source, PropertyGroup target, + MapperContext context) + { + if (source.Id > 0) + { target.Id = source.Id; - target.Inherited = source.Inherited; - target.IsSensitiveData = source.IsSensitiveData; - target.Label = source.Label; - target.MemberCanEditProperty = source.MemberCanEditProperty; - target.MemberCanViewProperty = source.MemberCanViewProperty; - target.SortOrder = source.SortOrder; - target.Validation = source.Validation; - target.LabelOnTop = source.LabelOnTop; - } - - // Umbraco.Code.MapAll -CreatorId -Level -SortOrder -Variations - // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate - // Umbraco.Code.MapAll -ContentTypeComposition (done by AfterMapSaveToType) - private static void MapSaveToTypeBase(TSource source, - IContentTypeComposition target, MapperContext context) - where TSource : ContentTypeSave - where TSourcePropertyType : PropertyTypeBasic - { - // TODO: not so clean really - var isPublishing = target is IContentType; - - var id = Convert.ToInt32(source.Id); - if (id > 0) - { - target.Id = id; - } + } - target.Alias = source.Alias; - target.Description = source.Description; - target.Icon = source.Icon; - target.IsContainer = source.IsContainer; - target.IsElement = source.IsElement; - target.Key = source.Key; - target.Name = source.Name; - target.ParentId = source.ParentId; - target.Path = source.Path; - target.Thumbnail = source.Thumbnail; + target.Key = source.Key; + target.Type = source.Type; + target.Name = source.Name; + target.Alias = source.Alias; + target.SortOrder = source.SortOrder; + } - target.AllowedAsRoot = source.AllowAsRoot; + // Umbraco.Code.MapAll -ContentTypeId -ParentTabContentTypes -ParentTabContentTypeNames + private static void Map(PropertyGroupBasic source, + PropertyGroupDisplay target, MapperContext context) + { + target.Inherited = source.Inherited; + if (source.Id > 0) + { + target.Id = source.Id; + } - bool allowedContentTypesUnchanged = target.AllowedContentTypes?.Select(x => x.Id.Value) - .SequenceEqual(source.AllowedContentTypes) ?? false; + target.Key = source.Key; + target.Type = source.Type; + target.Name = source.Name; + target.Alias = source.Alias; + target.SortOrder = source.SortOrder; + target.Properties = context.MapEnumerable(source.Properties) + .WhereNotNull(); + } - if (allowedContentTypesUnchanged is false) - { - target.AllowedContentTypes = source.AllowedContentTypes.Select((t, i) => new ContentTypeSort(t, i)); - } + // Umbraco.Code.MapAll -ContentTypeId -ParentTabContentTypes -ParentTabContentTypeNames + private static void Map(PropertyGroupBasic source, + PropertyGroupDisplay target, MapperContext context) + { + target.Inherited = source.Inherited; + if (source.Id > 0) + { + target.Id = source.Id; + } + target.Key = source.Key; + target.Type = source.Type; + target.Name = source.Name; + target.Alias = source.Alias; + target.SortOrder = source.SortOrder; + target.Properties = + context.MapEnumerable(source.Properties).WhereNotNull(); + } - if (!(target is IMemberType)) - { - target.SetVariesBy(ContentVariation.Culture, source.AllowCultureVariant); - target.SetVariesBy(ContentVariation.Segment, source.AllowSegmentVariant); - } + // Umbraco.Code.MapAll -Editor -View -Config -ContentTypeId -ContentTypeName -Locked -DataTypeIcon -DataTypeName + private static void Map(PropertyTypeBasic source, PropertyTypeDisplay target, MapperContext context) + { + target.Alias = source.Alias; + target.AllowCultureVariant = source.AllowCultureVariant; + target.AllowSegmentVariant = source.AllowSegmentVariant; + target.DataTypeId = source.DataTypeId; + target.DataTypeKey = source.DataTypeKey; + target.Description = source.Description; + target.GroupId = source.GroupId; + target.Id = source.Id; + target.Inherited = source.Inherited; + target.Label = source.Label; + target.SortOrder = source.SortOrder; + target.Validation = source.Validation; + target.LabelOnTop = source.LabelOnTop; + } - // handle property groups and property types - // note that ContentTypeSave has - // - all groups, inherited and local; only *one* occurrence per group *name* - // - potentially including the generic properties group - // - all properties, inherited and local - // - // also, see PropertyTypeGroupResolver.ResolveCore: - // - if a group is local *and* inherited, then Inherited is true - // and the identifier is the identifier of the *local* group - // - // IContentTypeComposition AddPropertyGroup, AddPropertyType methods do some - // unique-alias-checking, etc that is *not* compatible with re-mapping everything - // the way we do it here, so we should exclusively do it by - // - managing a property group's PropertyTypes collection - // - managing the content type's PropertyTypes collection (for generic properties) - - // handle actual groups (non-generic-properties) - PropertyGroup[] destOrigGroups = target.PropertyGroups.ToArray(); // local groups - IPropertyType[] destOrigProperties = target.PropertyTypes.ToArray(); // all properties, in groups or not - var destGroups = new List(); - PropertyGroupBasic[] sourceGroups = - source.Groups.Where(x => x.IsGenericProperties == false).ToArray(); - var sourceGroupParentAliases = sourceGroups.Select(x => x.GetParentAlias()).Distinct().ToArray(); - foreach (PropertyGroupBasic sourceGroup in sourceGroups) - { - // get the dest group - PropertyGroup? destGroup = MapSaveGroup(sourceGroup, destOrigGroups, context); - - // handle local properties - IPropertyType[] destProperties = sourceGroup.Properties - .Where(x => x.Inherited == false) - .Select(x => MapSaveProperty(x, destOrigProperties, context)) - .WhereNotNull() - .ToArray(); - - // if the group has no local properties and is not used as parent, skip it, ie sort-of garbage-collect - // local groups which would not have local properties anymore - if (destProperties.Length == 0 && !sourceGroupParentAliases.Contains(sourceGroup.Alias)) - { - continue; - } + // Umbraco.Code.MapAll -Editor -View -Config -ContentTypeId -ContentTypeName -Locked -DataTypeIcon -DataTypeName + private static void Map(MemberPropertyTypeBasic source, MemberPropertyTypeDisplay target, MapperContext context) + { + target.Alias = source.Alias; + target.AllowCultureVariant = source.AllowCultureVariant; + target.AllowSegmentVariant = source.AllowSegmentVariant; + target.DataTypeId = source.DataTypeId; + target.DataTypeKey = source.DataTypeKey; + target.Description = source.Description; + target.GroupId = source.GroupId; + target.Id = source.Id; + target.Inherited = source.Inherited; + target.IsSensitiveData = source.IsSensitiveData; + target.Label = source.Label; + target.MemberCanEditProperty = source.MemberCanEditProperty; + target.MemberCanViewProperty = source.MemberCanViewProperty; + target.SortOrder = source.SortOrder; + target.Validation = source.Validation; + target.LabelOnTop = source.LabelOnTop; + } - // ensure no duplicate alias, then assign the group properties collection - EnsureUniqueAliases(destProperties); + // Umbraco.Code.MapAll -CreatorId -Level -SortOrder -Variations + // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate + // Umbraco.Code.MapAll -ContentTypeComposition (done by AfterMapSaveToType) + private static void MapSaveToTypeBase(TSource source, + IContentTypeComposition target, MapperContext context) + where TSource : ContentTypeSave + where TSourcePropertyType : PropertyTypeBasic + { + // TODO: not so clean really + var isPublishing = target is IContentType; - if (destGroup is not null) - { - if (destGroup.PropertyTypes?.SupportsPublishing != isPublishing || destGroup.PropertyTypes.SequenceEqual(destProperties) is false) - { - destGroup.PropertyTypes = new PropertyTypeCollection(isPublishing, destProperties); - } + var id = Convert.ToInt32(source.Id); + if (id > 0) + { + target.Id = id; + } - destGroups.Add(destGroup); - } - } + target.Alias = source.Alias; + target.Description = source.Description; + target.Icon = source.Icon; + target.IsContainer = source.IsContainer; + target.IsElement = source.IsElement; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = source.ParentId; + target.Path = source.Path; + target.Thumbnail = source.Thumbnail; - // ensure no duplicate name, then assign the groups collection - EnsureUniqueAliases(destGroups); + target.AllowedAsRoot = source.AllowAsRoot; - if (target.PropertyGroups.SequenceEqual(destGroups) is false) - { - target.PropertyGroups = new PropertyGroupCollection(destGroups); - } + var allowedContentTypesUnchanged = target.AllowedContentTypes?.Select(x => x.Id.Value) + .SequenceEqual(source.AllowedContentTypes) ?? false; - // because the property groups collection was rebuilt, there is no need to remove - // the old groups - they are just gone and will be cleared by the repository + if (allowedContentTypesUnchanged is false) + { + target.AllowedContentTypes = source.AllowedContentTypes.Select((t, i) => new ContentTypeSort(t, i)); + } - // handle non-grouped (ie generic) properties - PropertyGroupBasic? genericPropertiesGroup = - source.Groups.FirstOrDefault(x => x.IsGenericProperties); - if (genericPropertiesGroup != null) - { - // handle local properties - IPropertyType[] destProperties = genericPropertiesGroup.Properties - .Where(x => x.Inherited == false) - .Select(x => MapSaveProperty(x, destOrigProperties, context)) - .WhereNotNull() - .ToArray(); - - // ensure no duplicate alias, then assign the generic properties collection - EnsureUniqueAliases(destProperties); - target.NoGroupPropertyTypes = new PropertyTypeCollection(isPublishing, destProperties); - } - // because all property collections were rebuilt, there is no need to remove - // some old properties, they are just gone and will be cleared by the repository + if (!(target is IMemberType)) + { + target.SetVariesBy(ContentVariation.Culture, source.AllowCultureVariant); + target.SetVariesBy(ContentVariation.Segment, source.AllowSegmentVariant); } - // Umbraco.Code.MapAll -Blueprints -Errors -ListViewEditorName -Trashed - private void MapTypeToDisplayBase(IContentTypeComposition source, ContentTypeCompositionDisplay target) + // handle property groups and property types + // note that ContentTypeSave has + // - all groups, inherited and local; only *one* occurrence per group *name* + // - potentially including the generic properties group + // - all properties, inherited and local + // + // also, see PropertyTypeGroupResolver.ResolveCore: + // - if a group is local *and* inherited, then Inherited is true + // and the identifier is the identifier of the *local* group + // + // IContentTypeComposition AddPropertyGroup, AddPropertyType methods do some + // unique-alias-checking, etc that is *not* compatible with re-mapping everything + // the way we do it here, so we should exclusively do it by + // - managing a property group's PropertyTypes collection + // - managing the content type's PropertyTypes collection (for generic properties) + + // handle actual groups (non-generic-properties) + PropertyGroup[] destOrigGroups = target.PropertyGroups.ToArray(); // local groups + IPropertyType[] destOrigProperties = target.PropertyTypes.ToArray(); // all properties, in groups or not + var destGroups = new List(); + PropertyGroupBasic[] sourceGroups = + source.Groups.Where(x => x.IsGenericProperties == false).ToArray(); + var sourceGroupParentAliases = sourceGroups.Select(x => x.GetParentAlias()).Distinct().ToArray(); + foreach (PropertyGroupBasic sourceGroup in sourceGroups) { - target.Alias = source.Alias; - target.AllowAsRoot = source.AllowedAsRoot; - target.CreateDate = source.CreateDate; - target.Description = source.Description; - target.Icon = source.Icon; - target.IconFilePath = target.IconIsClass - ? string.Empty - : $"{_globalSettings.GetBackOfficePath(_hostingEnvironment).EnsureEndsWith("/")}images/umbraco/{source.Icon}"; - target.Id = source.Id; - target.IsContainer = source.IsContainer; - target.IsElement = source.IsElement; - target.Key = source.Key; - target.Name = source.Name; - target.ParentId = source.ParentId; - target.Path = source.Path; - target.Thumbnail = source.Thumbnail; - target.ThumbnailFilePath = target.ThumbnailIsClass - ? string.Empty - : _hostingEnvironment.ToAbsolute("~/umbraco/images/thumbnails/" + source.Thumbnail); - target.Udi = MapContentTypeUdi(source); - target.UpdateDate = source.UpdateDate; - - target.AllowedContentTypes = source.AllowedContentTypes?.OrderBy(c => c.SortOrder).Select(x => x.Id.Value); - target.CompositeContentTypes = source.ContentTypeComposition.Select(x => x.Alias); - target.LockedCompositeContentTypes = MapLockedCompositions(source); - } - - // no MapAll - relies on the non-generic method - private void MapTypeToDisplayBase(IContentTypeComposition source, TTarget target) - where TTarget : ContentTypeCompositionDisplay - where TTargetPropertyType : PropertyTypeDisplay, new() - { - MapTypeToDisplayBase(source, target); - - var groupsMapper = new PropertyTypeGroupMapper(_propertyEditors, _dataTypeService, - _shortStringHelper, _loggerFactory.CreateLogger>()); - target.Groups = groupsMapper.Map(source); - } - - // Umbraco.Code.MapAll -CreateDate -UpdateDate -ListViewEditorName -Errors -LockedCompositeContentTypes - private void MapTypeToDisplayBase(ContentTypeSave source, ContentTypeCompositionDisplay target) - { - target.Alias = source.Alias; - target.AllowAsRoot = source.AllowAsRoot; - target.AllowedContentTypes = source.AllowedContentTypes; - target.Blueprints = source.Blueprints; - target.CompositeContentTypes = source.CompositeContentTypes; - target.Description = source.Description; - target.Icon = source.Icon; - target.IconFilePath = target.IconIsClass - ? string.Empty - : $"{_globalSettings.GetBackOfficePath(_hostingEnvironment).EnsureEndsWith("/")}images/umbraco/{source.Icon}"; - target.Id = source.Id; - target.IsContainer = source.IsContainer; - target.IsElement = source.IsElement; - target.Key = source.Key; - target.Name = source.Name; - target.ParentId = source.ParentId; - target.Path = source.Path; - target.Thumbnail = source.Thumbnail; - target.ThumbnailFilePath = target.ThumbnailIsClass - ? string.Empty - : _hostingEnvironment.ToAbsolute("~/umbraco/images/thumbnails/" + source.Thumbnail); - target.Trashed = source.Trashed; - target.Udi = source.Udi; - } - - // no MapAll - relies on the non-generic method - private void MapTypeToDisplayBase(TSource source, - TTarget target, MapperContext context) - where TSource : ContentTypeSave - where TSourcePropertyType : PropertyTypeBasic - where TTarget : ContentTypeCompositionDisplay - where TTargetPropertyType : PropertyTypeDisplay - { - MapTypeToDisplayBase(source, target); - - target.Groups = - context - .MapEnumerable, PropertyGroupDisplay>( - source.Groups).WhereNotNull(); - } - - private IEnumerable MapLockedCompositions(IContentTypeComposition source) - { - // get ancestor ids from path of parent if not root - if (source.ParentId == Constants.System.Root) - { - return Enumerable.Empty(); - } + // get the dest group + PropertyGroup? destGroup = MapSaveGroup(sourceGroup, destOrigGroups, context); + + // handle local properties + IPropertyType[] destProperties = sourceGroup.Properties + .Where(x => x.Inherited == false) + .Select(x => MapSaveProperty(x, destOrigProperties, context)) + .WhereNotNull() + .ToArray(); - IContentType? parent = _contentTypeService.Get(source.ParentId); - if (parent == null) + // if the group has no local properties and is not used as parent, skip it, ie sort-of garbage-collect + // local groups which would not have local properties anymore + if (destProperties.Length == 0 && !sourceGroupParentAliases.Contains(sourceGroup.Alias)) { - return Enumerable.Empty(); + continue; } - var aliases = new List(); - IEnumerable? ancestorIds = parent.Path?.Split(Constants.CharArrays.Comma) - .Select(s => int.Parse(s, CultureInfo.InvariantCulture)); - // loop through all content types and return ordered aliases of ancestors - IContentType[] allContentTypes = _contentTypeService.GetAll().ToArray(); - if (ancestorIds is not null) + // ensure no duplicate alias, then assign the group properties collection + EnsureUniqueAliases(destProperties); + + if (destGroup is not null) { - foreach (var ancestorId in ancestorIds) + if (destGroup.PropertyTypes?.SupportsPublishing != isPublishing || + destGroup.PropertyTypes.SequenceEqual(destProperties) is false) { - IContentType? ancestor = allContentTypes.FirstOrDefault(x => x.Id == ancestorId); - if (ancestor is not null && ancestor.Alias is not null) - { - aliases.Add(ancestor.Alias); - } + destGroup.PropertyTypes = new PropertyTypeCollection(isPublishing, destProperties); } + + destGroups.Add(destGroup); } + } + // ensure no duplicate name, then assign the groups collection + EnsureUniqueAliases(destGroups); - return aliases.OrderBy(x => x); + if (target.PropertyGroups.SequenceEqual(destGroups) is false) + { + target.PropertyGroups = new PropertyGroupCollection(destGroups); } - public static Udi? MapContentTypeUdi(IContentTypeComposition source) + // because the property groups collection was rebuilt, there is no need to remove + // the old groups - they are just gone and will be cleared by the repository + + // handle non-grouped (ie generic) properties + PropertyGroupBasic? genericPropertiesGroup = + source.Groups.FirstOrDefault(x => x.IsGenericProperties); + if (genericPropertiesGroup != null) { - if (source == null) - { - return null; - } + // handle local properties + IPropertyType[] destProperties = genericPropertiesGroup.Properties + .Where(x => x.Inherited == false) + .Select(x => MapSaveProperty(x, destOrigProperties, context)) + .WhereNotNull() + .ToArray(); - string udiType; - switch (source) - { - case IMemberType _: - udiType = Constants.UdiEntityType.MemberType; - break; - case IMediaType _: - udiType = Constants.UdiEntityType.MediaType; - break; - case IContentType _: - udiType = Constants.UdiEntityType.DocumentType; - break; - default: - throw new PanicException($"Source is of type {source.GetType()} which isn't supported here"); - } + // ensure no duplicate alias, then assign the generic properties collection + EnsureUniqueAliases(destProperties); + target.NoGroupPropertyTypes = new PropertyTypeCollection(isPublishing, destProperties); + } + + // because all property collections were rebuilt, there is no need to remove + // some old properties, they are just gone and will be cleared by the repository + } + + // Umbraco.Code.MapAll -Blueprints -Errors -ListViewEditorName -Trashed + private void MapTypeToDisplayBase(IContentTypeComposition source, ContentTypeCompositionDisplay target) + { + target.Alias = source.Alias; + target.AllowAsRoot = source.AllowedAsRoot; + target.CreateDate = source.CreateDate; + target.Description = source.Description; + target.Icon = source.Icon; + target.IconFilePath = target.IconIsClass + ? string.Empty + : $"{_globalSettings.GetBackOfficePath(_hostingEnvironment).EnsureEndsWith("/")}images/umbraco/{source.Icon}"; + target.Id = source.Id; + target.IsContainer = source.IsContainer; + target.IsElement = source.IsElement; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = source.ParentId; + target.Path = source.Path; + target.Thumbnail = source.Thumbnail; + target.ThumbnailFilePath = target.ThumbnailIsClass + ? string.Empty + : _hostingEnvironment.ToAbsolute("~/umbraco/images/thumbnails/" + source.Thumbnail); + target.Udi = MapContentTypeUdi(source); + target.UpdateDate = source.UpdateDate; + + target.AllowedContentTypes = source.AllowedContentTypes?.OrderBy(c => c.SortOrder).Select(x => x.Id.Value); + target.CompositeContentTypes = source.ContentTypeComposition.Select(x => x.Alias); + target.LockedCompositeContentTypes = MapLockedCompositions(source); + } - return Udi.Create(udiType, source.Key); + // no MapAll - relies on the non-generic method + private void MapTypeToDisplayBase(IContentTypeComposition source, TTarget target) + where TTarget : ContentTypeCompositionDisplay + where TTargetPropertyType : PropertyTypeDisplay, new() + { + MapTypeToDisplayBase(source, target); + + var groupsMapper = new PropertyTypeGroupMapper(_propertyEditors, _dataTypeService, + _shortStringHelper, _loggerFactory.CreateLogger>()); + target.Groups = groupsMapper.Map(source); + } + + // Umbraco.Code.MapAll -CreateDate -UpdateDate -ListViewEditorName -Errors -LockedCompositeContentTypes + private void MapTypeToDisplayBase(ContentTypeSave source, ContentTypeCompositionDisplay target) + { + target.Alias = source.Alias; + target.AllowAsRoot = source.AllowAsRoot; + target.AllowedContentTypes = source.AllowedContentTypes; + target.Blueprints = source.Blueprints; + target.CompositeContentTypes = source.CompositeContentTypes; + target.Description = source.Description; + target.Icon = source.Icon; + target.IconFilePath = target.IconIsClass + ? string.Empty + : $"{_globalSettings.GetBackOfficePath(_hostingEnvironment).EnsureEndsWith("/")}images/umbraco/{source.Icon}"; + target.Id = source.Id; + target.IsContainer = source.IsContainer; + target.IsElement = source.IsElement; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = source.ParentId; + target.Path = source.Path; + target.Thumbnail = source.Thumbnail; + target.ThumbnailFilePath = target.ThumbnailIsClass + ? string.Empty + : _hostingEnvironment.ToAbsolute("~/umbraco/images/thumbnails/" + source.Thumbnail); + target.Trashed = source.Trashed; + target.Udi = source.Udi; + } + + // no MapAll - relies on the non-generic method + private void MapTypeToDisplayBase(TSource source, + TTarget target, MapperContext context) + where TSource : ContentTypeSave + where TSourcePropertyType : PropertyTypeBasic + where TTarget : ContentTypeCompositionDisplay + where TTargetPropertyType : PropertyTypeDisplay + { + MapTypeToDisplayBase(source, target); + + target.Groups = + context + .MapEnumerable, PropertyGroupDisplay>( + source.Groups).WhereNotNull(); + } + + private IEnumerable MapLockedCompositions(IContentTypeComposition source) + { + // get ancestor ids from path of parent if not root + if (source.ParentId == Constants.System.Root) + { + return Enumerable.Empty(); + } + + IContentType? parent = _contentTypeService.Get(source.ParentId); + if (parent == null) + { + return Enumerable.Empty(); } - private static PropertyGroup? MapSaveGroup(PropertyGroupBasic sourceGroup, - IEnumerable destOrigGroups, MapperContext context) - where TPropertyType : PropertyTypeBasic + var aliases = new List(); + IEnumerable? ancestorIds = parent.Path?.Split(Constants.CharArrays.Comma) + .Select(s => int.Parse(s, CultureInfo.InvariantCulture)); + // loop through all content types and return ordered aliases of ancestors + IContentType[] allContentTypes = _contentTypeService.GetAll().ToArray(); + if (ancestorIds is not null) { - PropertyGroup? destGroup; - if (sourceGroup.Id > 0) + foreach (var ancestorId in ancestorIds) { - // update an existing group - // ensure it is still there, then map/update - destGroup = destOrigGroups.FirstOrDefault(x => x.Id == sourceGroup.Id); - if (destGroup != null) + IContentType? ancestor = allContentTypes.FirstOrDefault(x => x.Id == ancestorId); + if (ancestor is not null && ancestor.Alias is not null) { - context.Map(sourceGroup, destGroup); - return destGroup; + aliases.Add(ancestor.Alias); } - - // force-clear the ID as it does not match anything - sourceGroup.Id = 0; } + } + + + return aliases.OrderBy(x => x); + } + + public static Udi? MapContentTypeUdi(IContentTypeComposition source) + { + if (source == null) + { + return null; + } - // insert a new group, or update an existing group that has - // been deleted in the meantime and we need to re-create - // map/create - destGroup = context.Map(sourceGroup); - return destGroup; + string udiType; + switch (source) + { + case IMemberType _: + udiType = Constants.UdiEntityType.MemberType; + break; + case IMediaType _: + udiType = Constants.UdiEntityType.MediaType; + break; + case IContentType _: + udiType = Constants.UdiEntityType.DocumentType; + break; + default: + throw new PanicException($"Source is of type {source.GetType()} which isn't supported here"); } - private static IPropertyType? MapSaveProperty(PropertyTypeBasic sourceProperty, - IEnumerable destOrigProperties, MapperContext context) + return Udi.Create(udiType, source.Key); + } + + private static PropertyGroup? MapSaveGroup(PropertyGroupBasic sourceGroup, + IEnumerable destOrigGroups, MapperContext context) + where TPropertyType : PropertyTypeBasic + { + PropertyGroup? destGroup; + if (sourceGroup.Id > 0) { - IPropertyType? destProperty; - if (sourceProperty.Id > 0) + // update an existing group + // ensure it is still there, then map/update + destGroup = destOrigGroups.FirstOrDefault(x => x.Id == sourceGroup.Id); + if (destGroup != null) { - // updating an existing property - // ensure it is still there, then map/update - destProperty = destOrigProperties.FirstOrDefault(x => x.Id == sourceProperty.Id); - if (destProperty != null) - { - context.Map(sourceProperty, destProperty); - return destProperty; - } - - // force-clear the ID as it does not match anything - sourceProperty.Id = 0; + context.Map(sourceGroup, destGroup); + return destGroup; } - // insert a new property, or update an existing property that has - // been deleted in the meantime and we need to re-create - // map/create - destProperty = context.Map(sourceProperty); - return destProperty; + // force-clear the ID as it does not match anything + sourceGroup.Id = 0; } - private static void EnsureUniqueAliases(IEnumerable properties) + // insert a new group, or update an existing group that has + // been deleted in the meantime and we need to re-create + // map/create + destGroup = context.Map(sourceGroup); + return destGroup; + } + + private static IPropertyType? MapSaveProperty(PropertyTypeBasic sourceProperty, + IEnumerable destOrigProperties, MapperContext context) + { + IPropertyType? destProperty; + if (sourceProperty.Id > 0) { - IPropertyType[] propertiesA = properties.ToArray(); - var distinctProperties = propertiesA - .Select(x => x.Alias?.ToUpperInvariant()) - .Distinct() - .Count(); - if (distinctProperties != propertiesA.Length) + // updating an existing property + // ensure it is still there, then map/update + destProperty = destOrigProperties.FirstOrDefault(x => x.Id == sourceProperty.Id); + if (destProperty != null) { - throw new InvalidOperationException("Cannot map properties due to alias conflict."); + context.Map(sourceProperty, destProperty); + return destProperty; } + + // force-clear the ID as it does not match anything + sourceProperty.Id = 0; } - private static void EnsureUniqueAliases(IEnumerable groups) + // insert a new property, or update an existing property that has + // been deleted in the meantime and we need to re-create + // map/create + destProperty = context.Map(sourceProperty); + return destProperty; + } + + private static void EnsureUniqueAliases(IEnumerable properties) + { + IPropertyType[] propertiesA = properties.ToArray(); + var distinctProperties = propertiesA + .Select(x => x.Alias?.ToUpperInvariant()) + .Distinct() + .Count(); + if (distinctProperties != propertiesA.Length) { - PropertyGroup[] groupsA = groups.ToArray(); - var distinctProperties = groupsA - .Select(x => x.Alias) - .Distinct() - .Count(); - if (distinctProperties != groupsA.Length) - { - throw new InvalidOperationException("Cannot map groups due to alias conflict."); - } + throw new InvalidOperationException("Cannot map properties due to alias conflict."); } + } - private static void MapComposition(ContentTypeSave source, IContentTypeComposition target, - Func getContentType) + private static void EnsureUniqueAliases(IEnumerable groups) + { + PropertyGroup[] groupsA = groups.ToArray(); + var distinctProperties = groupsA + .Select(x => x.Alias) + .Distinct() + .Count(); + if (distinctProperties != groupsA.Length) { - var current = target.CompositionAliases().ToArray(); - IEnumerable proposed = source.CompositeContentTypes; + throw new InvalidOperationException("Cannot map groups due to alias conflict."); + } + } - IEnumerable remove = current.Where(x => !proposed.Contains(x)); - IEnumerable add = proposed.Where(x => !current.Contains(x)); + private static void MapComposition(ContentTypeSave source, IContentTypeComposition target, + Func getContentType) + { + var current = target.CompositionAliases().ToArray(); + IEnumerable proposed = source.CompositeContentTypes; - foreach (var alias in remove) - { - target.RemoveContentType(alias); - } + IEnumerable remove = current.Where(x => !proposed.Contains(x)); + IEnumerable add = proposed.Where(x => !current.Contains(x)); + + foreach (var alias in remove) + { + target.RemoveContentType(alias); + } - foreach (var alias in add) + foreach (var alias in add) + { + // TODO: Remove N+1 lookup + IContentTypeComposition? contentType = getContentType(alias); + if (contentType != null) { - // TODO: Remove N+1 lookup - IContentTypeComposition? contentType = getContentType(alias); - if (contentType != null) - { - target.AddContentType(contentType); - } + target.AddContentType(contentType); } } } diff --git a/src/Umbraco.Core/Models/Mapping/ContentVariantMapper.cs b/src/Umbraco.Core/Models/Mapping/ContentVariantMapper.cs index 4685fb9cc455..95bef6a170e9 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentVariantMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentVariantMapper.cs @@ -1,180 +1,174 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +public class ContentVariantMapper { - public class ContentVariantMapper - { - private readonly ILocalizationService _localizationService; - private readonly ILocalizedTextService _localizedTextService; + private readonly ILocalizationService _localizationService; + private readonly ILocalizedTextService _localizedTextService; - public ContentVariantMapper(ILocalizationService localizationService, ILocalizedTextService localizedTextService) - { - _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); - _localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); - } + public ContentVariantMapper(ILocalizationService localizationService, ILocalizedTextService localizedTextService) + { + _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); + _localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); + } - public IEnumerable? Map(IContent source, MapperContext context) where TVariant : ContentVariantDisplay - { - var variesByCulture = source.ContentType.VariesByCulture(); - var variesBySegment = source.ContentType.VariesBySegment(); + public IEnumerable? Map(IContent source, MapperContext context) + where TVariant : ContentVariantDisplay + { + var variesByCulture = source.ContentType.VariesByCulture(); + var variesBySegment = source.ContentType.VariesBySegment(); - IList? variants = new List(); + IList? variants = new List(); - if (!variesByCulture && !variesBySegment) - { - // this is invariant so just map the IContent instance to ContentVariationDisplay - var variantDisplay = context.Map(source); - if (variantDisplay is not null) - { - variants.Add(variantDisplay); - } - } - else if (variesByCulture && !variesBySegment) - { - var languages = GetLanguages(context); - variants = languages? - .Select(language => CreateVariantDisplay(context, source, language, null)) - .WhereNotNull() - .ToList(); - } - else if (variesBySegment && !variesByCulture) - { - // Segment only - var segments = GetSegments(source); - variants = segments? - .Select(segment => CreateVariantDisplay(context, source, null, segment)) - .WhereNotNull() - .ToList(); - } - else + if (!variesByCulture && !variesBySegment) + { + // this is invariant so just map the IContent instance to ContentVariationDisplay + TVariant variantDisplay = context.Map(source); + if (variantDisplay is not null) { - // Culture and segment - var languages = GetLanguages(context).ToList(); - var segments = GetSegments(source).ToList(); - - if (languages.Count == 0 || segments.Count == 0) - { - // This should not happen - throw new InvalidOperationException("No languages or segments available"); - } - - variants = languages? - .SelectMany(language => segments - .Select(segment => CreateVariantDisplay(context, source, language, segment))) - .WhereNotNull() - .ToList(); + variants.Add(variantDisplay); } - - return SortVariants(variants); } - - - - private IList? SortVariants(IList? variants) where TVariant : ContentVariantDisplay + else if (variesByCulture && !variesBySegment) + { + IEnumerable languages = GetLanguages(context); + variants = languages? + .Select(language => CreateVariantDisplay(context, source, language, null)) + .WhereNotNull() + .ToList(); + } + else if (variesBySegment && !variesByCulture) { - if (variants == null || variants.Count <= 1) + // Segment only + IEnumerable segments = GetSegments(source); + variants = segments? + .Select(segment => CreateVariantDisplay(context, source, null, segment)) + .WhereNotNull() + .ToList(); + } + else + { + // Culture and segment + var languages = GetLanguages(context).ToList(); + var segments = GetSegments(source).ToList(); + + if (languages.Count == 0 || segments.Count == 0) { - return variants; + // This should not happen + throw new InvalidOperationException("No languages or segments available"); } - // Default variant first, then order by language, segment. - return variants - .OrderBy(v => IsDefaultLanguage(v) ? 0 : 1) - .ThenBy(v => IsDefaultSegment(v) ? 0 : 1) - .ThenBy(v => v?.Language?.Name) - .ThenBy(v => v.Segment) + variants = languages? + .SelectMany(language => segments + .Select(segment => CreateVariantDisplay(context, source, language, segment))) + .WhereNotNull() .ToList(); } - private static bool IsDefaultSegment(ContentVariantDisplay variant) - { - return variant.Segment == null; - } + return SortVariants(variants); + } + - private static bool IsDefaultLanguage(ContentVariantDisplay variant) + private IList? SortVariants(IList? variants) where TVariant : ContentVariantDisplay + { + if (variants == null || variants.Count <= 1) { - return variant.Language == null || variant.Language.IsDefault; + return variants; } - private IEnumerable GetLanguages(MapperContext context) + // Default variant first, then order by language, segment. + return variants + .OrderBy(v => IsDefaultLanguage(v) ? 0 : 1) + .ThenBy(v => IsDefaultSegment(v) ? 0 : 1) + .ThenBy(v => v?.Language?.Name) + .ThenBy(v => v.Segment) + .ToList(); + } + + private static bool IsDefaultSegment(ContentVariantDisplay variant) => variant.Segment == null; + + private static bool IsDefaultLanguage(ContentVariantDisplay variant) => + variant.Language == null || variant.Language.IsDefault; + + private IEnumerable GetLanguages(MapperContext context) + { + var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList(); + if (allLanguages.Count == 0) { - var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList(); - if (allLanguages.Count == 0) - { - // This should never happen - return Enumerable.Empty(); - } - else - { - return context.MapEnumerable(allLanguages).WhereNotNull().ToList(); - } + // This should never happen + return Enumerable.Empty(); } - /// - /// Returns all segments assigned to the content - /// - /// - /// - /// Returns all segments assigned to the content including the default `null` segment. - /// - private IEnumerable GetSegments(IContent content) - { - // The default segment (null) is always there, - // even when there is no property data at all yet - var segments = new List { null }; + return context.MapEnumerable(allLanguages).WhereNotNull().ToList(); + } - // Add actual segments based on the property values - segments.AddRange(content.Properties.SelectMany(p => p.Values.Select(v => v.Segment))); + /// + /// Returns all segments assigned to the content + /// + /// + /// + /// Returns all segments assigned to the content including the default `null` segment. + /// + private IEnumerable GetSegments(IContent content) + { + // The default segment (null) is always there, + // even when there is no property data at all yet + var segments = new List {null}; - // Do not return a segment more than once - return segments.Distinct(); - } + // Add actual segments based on the property values + segments.AddRange(content.Properties.SelectMany(p => p.Values.Select(v => v.Segment))); - private TVariant? CreateVariantDisplay(MapperContext context, IContent content, ContentEditing.Language? language, string? segment) where TVariant : ContentVariantDisplay - { - context.SetCulture(language?.IsoCode); - context.SetSegment(segment); + // Do not return a segment more than once + return segments.Distinct(); + } - var variantDisplay = context.Map(content); + private TVariant? CreateVariantDisplay(MapperContext context, IContent content, + ContentEditing.Language? language, string? segment) where TVariant : ContentVariantDisplay + { + context.SetCulture(language?.IsoCode); + context.SetSegment(segment); - if (variantDisplay is null) - { - return null; - } - variantDisplay.Segment = segment; - variantDisplay.Language = language; - variantDisplay.Name = content.GetCultureName(language?.IsoCode); - variantDisplay.DisplayName = GetDisplayName(language, segment); + TVariant variantDisplay = context.Map(content); - return variantDisplay; + if (variantDisplay is null) + { + return null; } - private string GetDisplayName(ContentEditing.Language? language, string? segment) - { - var isCultureVariant = language is not null; - var isSegmentVariant = !segment.IsNullOrWhiteSpace(); + variantDisplay.Segment = segment; + variantDisplay.Language = language; + variantDisplay.Name = content.GetCultureName(language?.IsoCode); + variantDisplay.DisplayName = GetDisplayName(language, segment); - if(!isCultureVariant && !isSegmentVariant) - { - return _localizedTextService.Localize("general", "default"); - } + return variantDisplay; + } - var parts = new List(); + private string GetDisplayName(ContentEditing.Language? language, string? segment) + { + var isCultureVariant = language is not null; + var isSegmentVariant = !segment.IsNullOrWhiteSpace(); - if (isSegmentVariant) - parts.Add(segment!); + if (!isCultureVariant && !isSegmentVariant) + { + return _localizedTextService.Localize("general", "default"); + } - if (isCultureVariant) - parts.Add(language?.Name!); + var parts = new List(); - return string.Join(" — ", parts); + if (isSegmentVariant) + { + parts.Add(segment!); + } + if (isCultureVariant) + { + parts.Add(language?.Name!); } + + return string.Join(" — ", parts); } } diff --git a/src/Umbraco.Core/Models/Mapping/DataTypeMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/DataTypeMapDefinition.cs index f1fc81cd2420..fc0b0507d59a 100644 --- a/src/Umbraco.Core/Models/Mapping/DataTypeMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/DataTypeMapDefinition.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Mapping; @@ -10,205 +7,233 @@ using Umbraco.Cms.Core.Serialization; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +public class DataTypeMapDefinition : IMapDefinition { - public class DataTypeMapDefinition : IMapDefinition + private static readonly int[] SystemIds = { - private readonly PropertyEditorCollection _propertyEditors; - private readonly ILogger _logger; - private readonly ContentSettings _contentSettings; - private readonly IConfigurationEditorJsonSerializer _serializer; + Constants.DataTypes.DefaultContentListView, Constants.DataTypes.DefaultMediaListView, + Constants.DataTypes.DefaultMembersListView + }; - public DataTypeMapDefinition(PropertyEditorCollection propertyEditors, ILogger logger, IOptions contentSettings, IConfigurationEditorJsonSerializer serializer) - { - _propertyEditors = propertyEditors; - _logger = logger; - _contentSettings = contentSettings.Value ?? throw new ArgumentNullException(nameof(contentSettings)); - _serializer = serializer; - } + private readonly ContentSettings _contentSettings; + private readonly ILogger _logger; + private readonly PropertyEditorCollection _propertyEditors; + private readonly IConfigurationEditorJsonSerializer _serializer; - private static readonly int[] SystemIds = - { - Constants.DataTypes.DefaultContentListView, - Constants.DataTypes.DefaultMediaListView, - Constants.DataTypes.DefaultMembersListView - }; + public DataTypeMapDefinition(PropertyEditorCollection propertyEditors, ILogger logger, + IOptions contentSettings, IConfigurationEditorJsonSerializer serializer) + { + _propertyEditors = propertyEditors; + _logger = logger; + _contentSettings = contentSettings.Value ?? throw new ArgumentNullException(nameof(contentSettings)); + _serializer = serializer; + } - public void DefineMaps(IUmbracoMapper mapper) - { - mapper.Define((source, context) => new PropertyEditorBasic(), Map); - mapper.Define((source, context) => new DataTypeConfigurationFieldDisplay(), Map); - mapper.Define((source, context) => new DataTypeBasic(), Map); - mapper.Define((source, context) => new DataTypeBasic(), Map); - mapper.Define((source, context) => new DataTypeDisplay(), Map); - mapper.Define>(MapPreValues); - mapper.Define((source, context) => new DataType(_propertyEditors[source.EditorAlias], _serializer) { CreateDate = DateTime.Now }, Map); - mapper.Define>(MapPreValues); - } + public void DefineMaps(IUmbracoMapper mapper) + { + mapper.Define((source, context) => new PropertyEditorBasic(), Map); + mapper.Define( + (source, context) => new DataTypeConfigurationFieldDisplay(), Map); + mapper.Define((source, context) => new DataTypeBasic(), Map); + mapper.Define((source, context) => new DataTypeBasic(), Map); + mapper.Define((source, context) => new DataTypeDisplay(), Map); + mapper.Define>(MapPreValues); + mapper.Define( + (source, context) => + new DataType(_propertyEditors[source.EditorAlias], _serializer) {CreateDate = DateTime.Now}, Map); + mapper.Define>(MapPreValues); + } - // Umbraco.Code.MapAll - private static void Map(IDataEditor source, PropertyEditorBasic target, MapperContext context) - { - target.Alias = source.Alias; - target.Icon = source.Icon; - target.Name = source.Name; - } + // Umbraco.Code.MapAll + private static void Map(IDataEditor source, PropertyEditorBasic target, MapperContext context) + { + target.Alias = source.Alias; + target.Icon = source.Icon; + target.Name = source.Name; + } - // Umbraco.Code.MapAll -Value - private static void Map(ConfigurationField source, DataTypeConfigurationFieldDisplay target, MapperContext context) - { - target.Config = source.Config; - target.Description = source.Description; - target.HideLabel = source.HideLabel; - target.Key = source.Key; - target.Name = source.Name; - target.View = source.View; - } + // Umbraco.Code.MapAll -Value + private static void Map(ConfigurationField source, DataTypeConfigurationFieldDisplay target, MapperContext context) + { + target.Config = source.Config; + target.Description = source.Description; + target.HideLabel = source.HideLabel; + target.Key = source.Key; + target.Name = source.Name; + target.View = source.View; + } - // Umbraco.Code.MapAll -Udi -HasPrevalues -IsSystemDataType -Id -Trashed -Key - // Umbraco.Code.MapAll -ParentId -Path - private static void Map(IDataEditor source, DataTypeBasic target, MapperContext context) - { - target.Alias = source.Alias; - target.Group = source.Group; - target.Icon = source.Icon; - target.Name = source.Name; - } + // Umbraco.Code.MapAll -Udi -HasPrevalues -IsSystemDataType -Id -Trashed -Key + // Umbraco.Code.MapAll -ParentId -Path + private static void Map(IDataEditor source, DataTypeBasic target, MapperContext context) + { + target.Alias = source.Alias; + target.Group = source.Group; + target.Icon = source.Icon; + target.Name = source.Name; + } - // Umbraco.Code.MapAll -HasPrevalues - private void Map(IDataType source, DataTypeBasic target, MapperContext context) + // Umbraco.Code.MapAll -HasPrevalues + private void Map(IDataType source, DataTypeBasic target, MapperContext context) + { + target.Id = source.Id; + target.IsSystemDataType = SystemIds.Contains(source.Id); + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = source.ParentId; + target.Path = source.Path; + target.Trashed = source.Trashed; + target.Udi = Udi.Create(Constants.UdiEntityType.DataType, source.Key); + + if (!_propertyEditors.TryGet(source.EditorAlias, out IDataEditor editor)) { - target.Id = source.Id; - target.IsSystemDataType = SystemIds.Contains(source.Id); - target.Key = source.Key; - target.Name = source.Name; - target.ParentId = source.ParentId; - target.Path = source.Path; - target.Trashed = source.Trashed; - target.Udi = Udi.Create(Constants.UdiEntityType.DataType, source.Key); - - if (!_propertyEditors.TryGet(source.EditorAlias, out var editor)) - return; - - target.Alias = editor!.Alias; - target.Group = editor.Group; - target.Icon = editor.Icon; + return; } - // Umbraco.Code.MapAll -HasPrevalues - private void Map(IDataType source, DataTypeDisplay target, MapperContext context) - { - target.AvailableEditors = MapAvailableEditors(source, context); - target.Id = source.Id; - target.IsSystemDataType = SystemIds.Contains(source.Id); - target.Key = source.Key; - target.Name = source.Name; - target.ParentId = source.ParentId; - target.Path = source.Path; - target.PreValues = MapPreValues(source, context); - target.SelectedEditor = source.EditorAlias.IsNullOrWhiteSpace() ? null : source.EditorAlias; - target.Trashed = source.Trashed; - target.Udi = Udi.Create(Constants.UdiEntityType.DataType, source.Key); - - if (!_propertyEditors.TryGet(source.EditorAlias, out var editor)) - return; - - target.Alias = editor!.Alias; - target.Group = editor.Group; - target.Icon = editor.Icon; - } + target.Alias = editor!.Alias; + target.Group = editor.Group; + target.Icon = editor.Icon; + } - // Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate - // Umbraco.Code.MapAll -Key -Path -CreatorId -Level -SortOrder -Configuration - private void Map(DataTypeSave source, IDataType target, MapperContext context) + // Umbraco.Code.MapAll -HasPrevalues + private void Map(IDataType source, DataTypeDisplay target, MapperContext context) + { + target.AvailableEditors = MapAvailableEditors(source, context); + target.Id = source.Id; + target.IsSystemDataType = SystemIds.Contains(source.Id); + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = source.ParentId; + target.Path = source.Path; + target.PreValues = MapPreValues(source, context); + target.SelectedEditor = source.EditorAlias.IsNullOrWhiteSpace() ? null : source.EditorAlias; + target.Trashed = source.Trashed; + target.Udi = Udi.Create(Constants.UdiEntityType.DataType, source.Key); + + if (!_propertyEditors.TryGet(source.EditorAlias, out IDataEditor editor)) { - target.DatabaseType = MapDatabaseType(source); - target.Editor = _propertyEditors[source.EditorAlias]; - target.Id = Convert.ToInt32(source.Id); - target.Name = source.Name; - target.ParentId = source.ParentId; + return; } - private IEnumerable MapAvailableEditors(IDataType source, MapperContext context) + target.Alias = editor!.Alias; + target.Group = editor.Group; + target.Icon = editor.Icon; + } + + // Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate + // Umbraco.Code.MapAll -Key -Path -CreatorId -Level -SortOrder -Configuration + private void Map(DataTypeSave source, IDataType target, MapperContext context) + { + target.DatabaseType = MapDatabaseType(source); + target.Editor = _propertyEditors[source.EditorAlias]; + target.Id = Convert.ToInt32(source.Id); + target.Name = source.Name; + target.ParentId = source.ParentId; + } + + private IEnumerable MapAvailableEditors(IDataType source, MapperContext context) + { + IOrderedEnumerable properties = _propertyEditors + .Where(x => !x.IsDeprecated || _contentSettings.ShowDeprecatedPropertyEditors || + source.EditorAlias == x.Alias) + .OrderBy(x => x.Name); + return context.MapEnumerable(properties).WhereNotNull(); + } + + private IEnumerable MapPreValues(IDataType dataType, MapperContext context) + { + // in v7 it was apparently fine to have an empty .EditorAlias here, in which case we would map onto + // an empty fields list, which made no sense since there would be nothing to map to - and besides, + // a datatype without an editor alias is a serious issue - v8 wants an editor here + + if (string.IsNullOrWhiteSpace(dataType.EditorAlias) || + !_propertyEditors.TryGet(dataType.EditorAlias, out IDataEditor editor)) { - var properties = _propertyEditors - .Where(x => !x.IsDeprecated || _contentSettings.ShowDeprecatedPropertyEditors || source.EditorAlias == x.Alias) - .OrderBy(x => x.Name); - return context.MapEnumerable(properties).WhereNotNull(); + throw new InvalidOperationException( + $"Could not find a property editor with alias \"{dataType.EditorAlias}\"."); } - private IEnumerable MapPreValues(IDataType dataType, MapperContext context) - { - // in v7 it was apparently fine to have an empty .EditorAlias here, in which case we would map onto - // an empty fields list, which made no sense since there would be nothing to map to - and besides, - // a datatype without an editor alias is a serious issue - v8 wants an editor here + IConfigurationEditor configurationEditor = editor!.GetConfigurationEditor(); + var fields = context + .MapEnumerable(configurationEditor.Fields) + .WhereNotNull().ToList(); + IDictionary configurationDictionary = + configurationEditor.ToConfigurationEditor(dataType.Configuration); - if (string.IsNullOrWhiteSpace(dataType.EditorAlias) || !_propertyEditors.TryGet(dataType.EditorAlias, out var editor)) - throw new InvalidOperationException($"Could not find a property editor with alias \"{dataType.EditorAlias}\"."); + MapConfigurationFields(dataType, fields, configurationDictionary); - var configurationEditor = editor!.GetConfigurationEditor(); - var fields = context.MapEnumerable(configurationEditor.Fields).WhereNotNull().ToList(); - var configurationDictionary = configurationEditor.ToConfigurationEditor(dataType.Configuration); + return fields; + } - MapConfigurationFields(dataType, fields, configurationDictionary); + private void MapConfigurationFields(IDataType? dataType, List fields, + IDictionary? configuration) + { + if (fields == null) + { + throw new ArgumentNullException(nameof(fields)); + } - return fields; + if (configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); } - private void MapConfigurationFields(IDataType? dataType, List fields, IDictionary? configuration) + // now we need to wire up the pre-values values with the actual fields defined + foreach (DataTypeConfigurationFieldDisplay field in fields.ToList()) { - if (fields == null) throw new ArgumentNullException(nameof(fields)); - if (configuration == null) throw new ArgumentNullException(nameof(configuration)); + //filter out the not-supported pre-values for built-in data types + if (dataType != null && dataType.IsBuildInDataType() && + (field.Key?.InvariantEquals(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes) ?? false)) + { + fields.Remove(field); + continue; + } - // now we need to wire up the pre-values values with the actual fields defined - foreach (var field in fields.ToList()) + if (field.Key is not null && configuration.TryGetValue(field.Key, out var value)) { - //filter out the not-supported pre-values for built-in data types - if (dataType != null && dataType.IsBuildInDataType() && (field.Key?.InvariantEquals(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes) ?? false)) - { - fields.Remove(field); - continue; - } - - if (field.Key is not null && configuration.TryGetValue(field.Key, out var value)) - { - field.Value = value; - } - else - { - // weird - just leave the field without a value - but warn - _logger.LogWarning("Could not find a value for configuration field '{ConfigField}'", field.Key); - } + field.Value = value; + } + else + { + // weird - just leave the field without a value - but warn + _logger.LogWarning("Could not find a value for configuration field '{ConfigField}'", field.Key); } } + } - private ValueStorageType MapDatabaseType(DataTypeSave source) + private ValueStorageType MapDatabaseType(DataTypeSave source) + { + if (!_propertyEditors.TryGet(source.EditorAlias, out IDataEditor editor)) { - if (!_propertyEditors.TryGet(source.EditorAlias, out var editor)) - throw new InvalidOperationException($"Could not find property editor \"{source.EditorAlias}\"."); - - // TODO: what about source.PropertyEditor? can we get the configuration here? 'cos it may change the storage type?! - var valueType = editor!.GetValueEditor().ValueType; - return ValueTypes.ToStorageType(valueType); + throw new InvalidOperationException($"Could not find property editor \"{source.EditorAlias}\"."); } - private IEnumerable MapPreValues(IDataEditor source, MapperContext context) - { - // this is a new data type, initialize default configuration - // get the configuration editor, - // get the configuration fields and map to UI, - // get the configuration default values and map to UI + // TODO: what about source.PropertyEditor? can we get the configuration here? 'cos it may change the storage type?! + var valueType = editor!.GetValueEditor().ValueType; + return ValueTypes.ToStorageType(valueType); + } - var configurationEditor = source.GetConfigurationEditor(); + private IEnumerable MapPreValues(IDataEditor source, MapperContext context) + { + // this is a new data type, initialize default configuration + // get the configuration editor, + // get the configuration fields and map to UI, + // get the configuration default values and map to UI - var fields = - context.MapEnumerable(configurationEditor.Fields).WhereNotNull().ToList(); + IConfigurationEditor configurationEditor = source.GetConfigurationEditor(); - var defaultConfiguration = configurationEditor.DefaultConfiguration; - if (defaultConfiguration != null) - MapConfigurationFields(null, fields, defaultConfiguration); + var fields = + context.MapEnumerable(configurationEditor.Fields) + .WhereNotNull().ToList(); - return fields; + IDictionary defaultConfiguration = configurationEditor.DefaultConfiguration; + if (defaultConfiguration != null) + { + MapConfigurationFields(null, fields, defaultConfiguration); } + + return fields; } } diff --git a/src/Umbraco.Core/Models/Mapping/DictionaryMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/DictionaryMapDefinition.cs index a5db1d4b9621..2e6714ead4f2 100644 --- a/src/Umbraco.Core/Models/Mapping/DictionaryMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/DictionaryMapDefinition.cs @@ -1,127 +1,125 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +/// +/// +/// The dictionary model mapper. +/// +public class DictionaryMapDefinition : IMapDefinition { - /// - /// - /// The dictionary model mapper. - /// - public class DictionaryMapDefinition : IMapDefinition + private readonly CommonMapper? _commonMapper; + private readonly ILocalizationService _localizationService; + + [Obsolete("Use the constructor with the CommonMapper")] + public DictionaryMapDefinition(ILocalizationService localizationService) => + _localizationService = localizationService; + + public DictionaryMapDefinition(ILocalizationService localizationService, CommonMapper commonMapper) { - private readonly ILocalizationService _localizationService; - private readonly CommonMapper? _commonMapper; + _localizationService = localizationService; + _commonMapper = commonMapper; + } - [Obsolete("Use the constructor with the CommonMapper")] - public DictionaryMapDefinition(ILocalizationService localizationService) - { - _localizationService = localizationService; - } + public void DefineMaps(IUmbracoMapper mapper) + { + mapper.Define((source, context) => new EntityBasic(), Map); + mapper.Define((source, context) => new DictionaryDisplay(), Map); + mapper.Define((source, context) => new DictionaryOverviewDisplay(), + Map); + } + + // Umbraco.Code.MapAll -ParentId -Path -Trashed -Udi -Icon + private static void Map(IDictionaryItem source, EntityBasic target, MapperContext context) + { + target.Alias = source.ItemKey; + target.Id = source.Id; + target.Key = source.Key; + target.Name = source.ItemKey; + } - public DictionaryMapDefinition(ILocalizationService localizationService, CommonMapper commonMapper) + // Umbraco.Code.MapAll -Icon -Trashed -Alias + private void Map(IDictionaryItem source, DictionaryDisplay target, MapperContext context) + { + target.Id = source.Id; + target.Key = source.Key; + target.Name = source.ItemKey; + target.ParentId = source.ParentId ?? Guid.Empty; + target.Udi = Udi.Create(Constants.UdiEntityType.DictionaryItem, source.Key); + if (_commonMapper != null) { - _localizationService = localizationService; - _commonMapper = commonMapper; + target.ContentApps.AddRange(_commonMapper.GetContentAppsForEntity(source)); } - public void DefineMaps(IUmbracoMapper mapper) + // build up the path to make it possible to set active item in tree + // TODO: check if there is a better way + if (source.ParentId.HasValue) { - mapper.Define((source, context) => new EntityBasic(), Map); - mapper.Define((source, context) => new DictionaryDisplay(), Map); - mapper.Define((source, context) => new DictionaryOverviewDisplay(), Map); + var ids = new List {-1}; + var parentIds = new List(); + GetParentId(source.ParentId.Value, _localizationService, parentIds); + parentIds.Reverse(); + ids.AddRange(parentIds); + ids.Add(source.Id); + target.Path = string.Join(",", ids); } - - // Umbraco.Code.MapAll -ParentId -Path -Trashed -Udi -Icon - private static void Map(IDictionaryItem source, EntityBasic target, MapperContext context) + else { - target.Alias = source.ItemKey; - target.Id = source.Id; - target.Key = source.Key; - target.Name = source.ItemKey; + target.Path = "-1," + source.Id; } - // Umbraco.Code.MapAll -Icon -Trashed -Alias - private void Map(IDictionaryItem source, DictionaryDisplay target, MapperContext context) + // add all languages and the translations + foreach (ILanguage lang in _localizationService.GetAllLanguages()) { - target.Id = source.Id; - target.Key = source.Key; - target.Name = source.ItemKey; - target.ParentId = source.ParentId ?? Guid.Empty; - target.Udi = Udi.Create(Constants.UdiEntityType.DictionaryItem, source.Key); - if (_commonMapper != null) - { - target.ContentApps.AddRange(_commonMapper.GetContentAppsForEntity(source)); - } + var langId = lang.Id; + IDictionaryTranslation translation = source.Translations?.FirstOrDefault(x => x.LanguageId == langId); - // build up the path to make it possible to set active item in tree - // TODO: check if there is a better way - if (source.ParentId.HasValue) + target.Translations.Add(new DictionaryTranslationDisplay { - var ids = new List { -1 }; - var parentIds = new List(); - GetParentId(source.ParentId.Value, _localizationService, parentIds); - parentIds.Reverse(); - ids.AddRange(parentIds); - ids.Add(source.Id); - target.Path = string.Join(",", ids); - } - else - { - target.Path = "-1," + source.Id; - } + IsoCode = lang.IsoCode, + DisplayName = lang.CultureName, + Translation = translation?.Value ?? string.Empty, + LanguageId = lang.Id + }); + } + } - // add all languages and the translations - foreach (var lang in _localizationService.GetAllLanguages()) - { - var langId = lang.Id; - var translation = source.Translations?.FirstOrDefault(x => x.LanguageId == langId); + // Umbraco.Code.MapAll -Level -Translations + private void Map(IDictionaryItem source, DictionaryOverviewDisplay target, MapperContext context) + { + target.Id = source.Id; + target.Name = source.ItemKey; + + // add all languages and the translations + foreach (ILanguage lang in _localizationService.GetAllLanguages()) + { + var langId = lang.Id; + IDictionaryTranslation translation = source.Translations?.FirstOrDefault(x => x.LanguageId == langId); - target.Translations.Add(new DictionaryTranslationDisplay + target.Translations.Add( + new DictionaryOverviewTranslationDisplay { - IsoCode = lang.IsoCode, DisplayName = lang.CultureName, - Translation = translation?.Value ?? string.Empty, - LanguageId = lang.Id + HasTranslation = translation != null && string.IsNullOrEmpty(translation.Value) == false }); - } } + } - // Umbraco.Code.MapAll -Level -Translations - private void Map(IDictionaryItem source, DictionaryOverviewDisplay target, MapperContext context) + private static void GetParentId(Guid parentId, ILocalizationService localizationService, List ids) + { + IDictionaryItem dictionary = localizationService.GetDictionaryItemById(parentId); + if (dictionary == null) { - target.Id = source.Id; - target.Name = source.ItemKey; - - // add all languages and the translations - foreach (var lang in _localizationService.GetAllLanguages()) - { - var langId = lang.Id; - var translation = source.Translations?.FirstOrDefault(x => x.LanguageId == langId); - - target.Translations.Add( - new DictionaryOverviewTranslationDisplay - { - DisplayName = lang.CultureName, - HasTranslation = translation != null && string.IsNullOrEmpty(translation.Value) == false - }); - } + return; } - private static void GetParentId(Guid parentId, ILocalizationService localizationService, List ids) - { - var dictionary = localizationService.GetDictionaryItemById(parentId); - if (dictionary == null) - return; - - ids.Add(dictionary.Id); + ids.Add(dictionary.Id); - if (dictionary.ParentId.HasValue) - GetParentId(dictionary.ParentId.Value, localizationService, ids); + if (dictionary.ParentId.HasValue) + { + GetParentId(dictionary.ParentId.Value, localizationService, ids); } } } diff --git a/src/Umbraco.Core/Models/Mapping/LanguageMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/LanguageMapDefinition.cs index 7450ec62b43c..7c17eb3477cb 100644 --- a/src/Umbraco.Core/Models/Mapping/LanguageMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/LanguageMapDefinition.cs @@ -1,60 +1,63 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +public class LanguageMapDefinition : IMapDefinition { - public class LanguageMapDefinition : IMapDefinition + public void DefineMaps(IUmbracoMapper mapper) { - public void DefineMaps(IUmbracoMapper mapper) - { - mapper.Define((source, context) => new EntityBasic(), Map); - mapper.Define((source, context) => new ContentEditing.Language(), Map); - mapper.Define, IEnumerable>((source, context) => new List(), Map); - } + mapper.Define((source, context) => new EntityBasic(), Map); + mapper.Define((source, context) => new ContentEditing.Language(), Map); + mapper.Define, IEnumerable>( + (source, context) => new List(), Map); + } + + // Umbraco.Code.MapAll -Udi -Path -Trashed -AdditionalData -Icon + private static void Map(ILanguage source, EntityBasic target, MapperContext context) + { + target.Name = source.CultureName; + target.Key = source.Key; + target.ParentId = -1; + target.Alias = source.IsoCode; + target.Id = source.Id; + } - // Umbraco.Code.MapAll -Udi -Path -Trashed -AdditionalData -Icon - private static void Map(ILanguage source, EntityBasic target, MapperContext context) + // Umbraco.Code.MapAll + private static void Map(ILanguage source, ContentEditing.Language target, MapperContext context) + { + target.Id = source.Id; + target.IsoCode = source.IsoCode; + target.Name = source.CultureName; + target.IsDefault = source.IsDefault; + target.IsMandatory = source.IsMandatory; + target.FallbackLanguageId = source.FallbackLanguageId; + } + + private static void Map(IEnumerable source, IEnumerable target, + MapperContext context) + { + if (target == null) { - target.Name = source.CultureName; - target.Key = source.Key; - target.ParentId = -1; - target.Alias = source.IsoCode; - target.Id = source.Id; + throw new ArgumentNullException(nameof(target)); } - // Umbraco.Code.MapAll - private static void Map(ILanguage source, ContentEditing.Language target, MapperContext context) + if (!(target is List list)) { - target.Id = source.Id; - target.IsoCode = source.IsoCode; - target.Name = source.CultureName; - target.IsDefault = source.IsDefault; - target.IsMandatory = source.IsMandatory; - target.FallbackLanguageId = source.FallbackLanguageId; + throw new NotSupportedException($"{nameof(target)} must be a List."); } - private static void Map(IEnumerable source, IEnumerable target, MapperContext context) + List temp = context.MapEnumerable(source); + + //Put the default language first in the list & then sort rest by a-z + ContentEditing.Language defaultLang = temp.SingleOrDefault(x => x!.IsDefault); + + // insert default lang first, then remaining language a-z + if (defaultLang is not null) { - if (target == null) - throw new ArgumentNullException(nameof(target)); - if (!(target is List list)) - throw new NotSupportedException($"{nameof(target)} must be a List."); - - var temp = context.MapEnumerable(source); - - //Put the default language first in the list & then sort rest by a-z - var defaultLang = temp.SingleOrDefault(x => x!.IsDefault); - - // insert default lang first, then remaining language a-z - if (defaultLang is not null) - { - list.Add(defaultLang); - list.AddRange(temp.Where(x => x != defaultLang).OrderBy(x => x!.Name).WhereNotNull()); - } + list.Add(defaultLang); + list.AddRange(temp.Where(x => x != defaultLang).OrderBy(x => x!.Name).WhereNotNull()); } } } diff --git a/src/Umbraco.Core/Models/Mapping/MacroMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/MacroMapDefinition.cs index 13fe7f7c335c..7f036603c054 100644 --- a/src/Umbraco.Core/Models/Mapping/MacroMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/MacroMapDefinition.cs @@ -1,88 +1,90 @@ -using System.Collections.Generic; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +public class MacroMapDefinition : IMapDefinition { - public class MacroMapDefinition : IMapDefinition + private readonly ILogger _logger; + private readonly ParameterEditorCollection _parameterEditors; + + public MacroMapDefinition(ParameterEditorCollection parameterEditors, ILogger logger) { - private readonly ParameterEditorCollection _parameterEditors; - private readonly ILogger _logger; + _parameterEditors = parameterEditors; + _logger = logger; + } - public MacroMapDefinition(ParameterEditorCollection parameterEditors, ILogger logger) - { - _parameterEditors = parameterEditors; - _logger = logger; - } + public void DefineMaps(IUmbracoMapper mapper) + { + mapper.Define((source, context) => new EntityBasic(), Map); + mapper.Define((source, context) => new MacroDisplay(), Map); + mapper.Define>((source, context) => + context.MapEnumerable(source.Properties.Values).WhereNotNull()); + mapper.Define((source, context) => new MacroParameter(), Map); + } - public void DefineMaps(IUmbracoMapper mapper) - { - mapper.Define((source, context) => new EntityBasic(), Map); - mapper.Define((source, context) => new MacroDisplay(), Map); - mapper.Define>((source, context) => context.MapEnumerable(source.Properties.Values).WhereNotNull()); - mapper.Define((source, context) => new MacroParameter(), Map); - } + // Umbraco.Code.MapAll -Trashed -AdditionalData + private static void Map(IMacro source, EntityBasic target, MapperContext context) + { + target.Alias = source.Alias; + target.Icon = Constants.Icons.Macro; + target.Id = source.Id; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = -1; + target.Path = "-1," + source.Id; + target.Udi = Udi.Create(Constants.UdiEntityType.Macro, source.Key); + } - // Umbraco.Code.MapAll -Trashed -AdditionalData - private static void Map(IMacro source, EntityBasic target, MapperContext context) - { - target.Alias = source.Alias; - target.Icon = Constants.Icons.Macro; - target.Id = source.Id; - target.Key = source.Key; - target.Name = source.Name; - target.ParentId = -1; - target.Path = "-1," + source.Id; - target.Udi = Udi.Create(Constants.UdiEntityType.Macro, source.Key); - } + private void Map(IMacro source, MacroDisplay target, MapperContext context) + { + target.Alias = source.Alias; + target.Icon = Constants.Icons.Macro; + target.Id = source.Id; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = -1; + target.Path = "-1," + source.Id; + target.Udi = Udi.Create(Constants.UdiEntityType.Macro, source.Key); + target.CacheByPage = source.CacheByPage; + target.CacheByUser = source.CacheByMember; + target.CachePeriod = source.CacheDuration; + target.UseInEditor = source.UseInEditor; + target.RenderInEditor = !source.DontRender; + target.View = source.MacroSource; + } - private void Map(IMacro source, MacroDisplay target, MapperContext context) + // Umbraco.Code.MapAll -Value + private void Map(IMacroProperty source, MacroParameter target, MapperContext context) + { + target.Alias = source.Alias; + target.Name = source.Name; + target.SortOrder = source.SortOrder; + + //map the view and the config + // we need to show the deprecated ones for backwards compatibility + IDataEditor paramEditor = _parameterEditors[source.EditorAlias]; // TODO: include/filter deprecated?! + if (paramEditor == null) { - target.Alias = source.Alias; - target.Icon = Constants.Icons.Macro; - target.Id = source.Id; - target.Key = source.Key; - target.Name = source.Name; - target.ParentId = -1; - target.Path = "-1," + source.Id; - target.Udi = Udi.Create(Constants.UdiEntityType.Macro, source.Key); - target.CacheByPage = source.CacheByPage; - target.CacheByUser = source.CacheByMember; - target.CachePeriod = source.CacheDuration; - target.UseInEditor = source.UseInEditor; - target.RenderInEditor = !source.DontRender; - target.View = source.MacroSource; + //we'll just map this to a text box + paramEditor = _parameterEditors[Constants.PropertyEditors.Aliases.TextBox]; + _logger.LogWarning( + "Could not resolve a parameter editor with alias {PropertyEditorAlias}, a textbox will be rendered in it's place", + source.EditorAlias); } - // Umbraco.Code.MapAll -Value - private void Map(IMacroProperty source, MacroParameter target, MapperContext context) - { - target.Alias = source.Alias; - target.Name = source.Name; - target.SortOrder = source.SortOrder; - //map the view and the config - // we need to show the deprecated ones for backwards compatibility - var paramEditor = _parameterEditors[source.EditorAlias]; // TODO: include/filter deprecated?! - if (paramEditor == null) - { - //we'll just map this to a text box - paramEditor = _parameterEditors[Constants.PropertyEditors.Aliases.TextBox]; - _logger.LogWarning("Could not resolve a parameter editor with alias {PropertyEditorAlias}, a textbox will be rendered in it's place", source.EditorAlias); - } + target.View = paramEditor?.GetValueEditor().View; - target.View = paramEditor?.GetValueEditor().View; + // sets the parameter configuration to be the default configuration editor's configuration, + // ie configurationEditor.DefaultConfigurationObject, prepared for the value editor, ie + // after ToValueEditor - important to use DefaultConfigurationObject here, because depending + // on editors, ToValueEditor expects the actual strongly typed configuration - not the + // dictionary thing returned by DefaultConfiguration - // sets the parameter configuration to be the default configuration editor's configuration, - // ie configurationEditor.DefaultConfigurationObject, prepared for the value editor, ie - // after ToValueEditor - important to use DefaultConfigurationObject here, because depending - // on editors, ToValueEditor expects the actual strongly typed configuration - not the - // dictionary thing returned by DefaultConfiguration - - var configurationEditor = paramEditor?.GetConfigurationEditor(); - target.Configuration = configurationEditor?.ToValueEditor(configurationEditor.DefaultConfigurationObject); - } + IConfigurationEditor configurationEditor = paramEditor?.GetConfigurationEditor(); + target.Configuration = configurationEditor?.ToValueEditor(configurationEditor.DefaultConfigurationObject); } } diff --git a/src/Umbraco.Core/Models/Mapping/MapperContextExtensions.cs b/src/Umbraco.Core/Models/Mapping/MapperContextExtensions.cs index 89cdc2210685..89b95b1129f1 100644 --- a/src/Umbraco.Core/Models/Mapping/MapperContextExtensions.cs +++ b/src/Umbraco.Core/Models/Mapping/MapperContextExtensions.cs @@ -1,68 +1,61 @@ using Umbraco.Cms.Core.Mapping; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Provides extension methods for the class. +/// +public static class MapperContextExtensions { + private const string CultureKey = "Map.Culture"; + private const string SegmentKey = "Map.Segment"; + private const string IncludedPropertiesKey = "Map.IncludedProperties"; + /// - /// Provides extension methods for the class. + /// Gets the context culture. /// - public static class MapperContextExtensions - { - private const string CultureKey = "Map.Culture"; - private const string SegmentKey = "Map.Segment"; - private const string IncludedPropertiesKey = "Map.IncludedProperties"; + public static string? GetCulture(this MapperContext context) => + context.HasItems && context.Items.TryGetValue(CultureKey, out var obj) && obj is string s ? s : null; - /// - /// Gets the context culture. - /// - public static string? GetCulture(this MapperContext context) - { - return context.HasItems && context.Items.TryGetValue(CultureKey, out var obj) && obj is string s ? s : null; - } - - /// - /// Gets the context segment. - /// - public static string? GetSegment(this MapperContext context) - { - return context.HasItems && context.Items.TryGetValue(SegmentKey, out var obj) && obj is string s ? s : null; - } + /// + /// Gets the context segment. + /// + public static string? GetSegment(this MapperContext context) => + context.HasItems && context.Items.TryGetValue(SegmentKey, out var obj) && obj is string s ? s : null; - /// - /// Sets a context culture. - /// - public static void SetCulture(this MapperContext context, string? culture) + /// + /// Sets a context culture. + /// + public static void SetCulture(this MapperContext context, string? culture) + { + if (culture is not null) { - if (culture is not null) - { - context.Items[CultureKey] = culture; - } + context.Items[CultureKey] = culture; } + } - /// - /// Sets a context segment. - /// - public static void SetSegment(this MapperContext context, string? segment) + /// + /// Sets a context segment. + /// + public static void SetSegment(this MapperContext context, string? segment) + { + if (segment is not null) { - if (segment is not null) - { - context.Items[SegmentKey] = segment; - } + context.Items[SegmentKey] = segment; } + } - /// - /// Get included properties. - /// - public static string[]? GetIncludedProperties(this MapperContext context) - { - return context.HasItems && context.Items.TryGetValue(IncludedPropertiesKey, out var obj) && obj is string[] s ? s : null; - } + /// + /// Get included properties. + /// + public static string[]? GetIncludedProperties(this MapperContext context) => context.HasItems && + context.Items.TryGetValue(IncludedPropertiesKey, out var obj) && obj is string[] s + ? s + : null; - /// - /// Sets included properties. - /// - public static void SetIncludedProperties(this MapperContext context, string[] properties) - { - context.Items[IncludedPropertiesKey] = properties; - } - } + /// + /// Sets included properties. + /// + public static void SetIncludedProperties(this MapperContext context, string[] properties) => + context.Items[IncludedPropertiesKey] = properties; } diff --git a/src/Umbraco.Core/Models/Mapping/MemberMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/MemberMapDefinition.cs index c2c3e14f5da9..69fefe6bb7a1 100644 --- a/src/Umbraco.Core/Models/Mapping/MemberMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/MemberMapDefinition.cs @@ -1,32 +1,31 @@ using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +/// +public class MemberMapDefinition : IMapDefinition { /// - public class MemberMapDefinition : IMapDefinition - { - /// - public void DefineMaps(IUmbracoMapper mapper) => mapper.Define(Map); + public void DefineMaps(IUmbracoMapper mapper) => mapper.Define(Map); - private static void Map(MemberSave source, IMember target, MapperContext context) - { - target.IsApproved = source.IsApproved; - target.Name = source.Name; - target.Email = source.Email; - target.Key = source.Key; - target.Username = source.Username; - target.Comments = source.Comments; - target.CreateDate = source.CreateDate; - target.UpdateDate = source.UpdateDate; - target.Email = source.Email; + private static void Map(MemberSave source, IMember target, MapperContext context) + { + target.IsApproved = source.IsApproved; + target.Name = source.Name; + target.Email = source.Email; + target.Key = source.Key; + target.Username = source.Username; + target.Comments = source.Comments; + target.CreateDate = source.CreateDate; + target.UpdateDate = source.UpdateDate; + target.Email = source.Email; - // TODO: ensure all properties are mapped as required - //target.Id = source.Id; - //target.ParentId = -1; - //target.Path = "-1," + source.Id; + // TODO: ensure all properties are mapped as required + //target.Id = source.Id; + //target.ParentId = -1; + //target.Path = "-1," + source.Id; - //TODO: add groups as required - } + //TODO: add groups as required } } diff --git a/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs b/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs index d6d3453a26fa..2ab6242b2bd0 100644 --- a/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml.Schema; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Dictionary; @@ -12,286 +8,285 @@ using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +/// +/// A custom tab/property resolver for members which will ensure that the built-in membership properties are or aren't +/// displayed +/// depending on if the member type has these properties +/// +/// +/// This also ensures that the IsLocked out property is readonly when the member is not locked out - this is because +/// an admin cannot actually set isLockedOut = true, they can only unlock. +/// +public class MemberTabsAndPropertiesMapper : TabsAndPropertiesMapper { - /// - /// A custom tab/property resolver for members which will ensure that the built-in membership properties are or aren't displayed - /// depending on if the member type has these properties - /// - /// - /// This also ensures that the IsLocked out property is readonly when the member is not locked out - this is because - /// an admin cannot actually set isLockedOut = true, they can only unlock. - /// - public class MemberTabsAndPropertiesMapper : TabsAndPropertiesMapper + private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; + private readonly ILocalizedTextService _localizedTextService; + private readonly IMemberGroupService _memberGroupService; + private readonly MemberPasswordConfigurationSettings _memberPasswordConfiguration; + private readonly IMemberService _memberService; + private readonly IMemberTypeService _memberTypeService; + private readonly PropertyEditorCollection _propertyEditorCollection; + + public MemberTabsAndPropertiesMapper(ICultureDictionary cultureDictionary, + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + ILocalizedTextService localizedTextService, + IMemberTypeService memberTypeService, + IMemberService memberService, + IMemberGroupService memberGroupService, + IOptions memberPasswordConfiguration, + IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, + PropertyEditorCollection propertyEditorCollection) + : base(cultureDictionary, localizedTextService, contentTypeBaseServiceProvider) { - private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; - private readonly ILocalizedTextService _localizedTextService; - private readonly IMemberTypeService _memberTypeService; - private readonly IMemberService _memberService; - private readonly IMemberGroupService _memberGroupService; - private readonly MemberPasswordConfigurationSettings _memberPasswordConfiguration; - private readonly PropertyEditorCollection _propertyEditorCollection; + _backofficeSecurityAccessor = backofficeSecurityAccessor ?? + throw new ArgumentNullException(nameof(backofficeSecurityAccessor)); + _localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); + _memberTypeService = memberTypeService ?? throw new ArgumentNullException(nameof(memberTypeService)); + _memberService = memberService ?? throw new ArgumentNullException(nameof(memberService)); + _memberGroupService = memberGroupService ?? throw new ArgumentNullException(nameof(memberGroupService)); + _memberPasswordConfiguration = memberPasswordConfiguration.Value; + _propertyEditorCollection = propertyEditorCollection; + } - public MemberTabsAndPropertiesMapper(ICultureDictionary cultureDictionary, - IBackOfficeSecurityAccessor backofficeSecurityAccessor, - ILocalizedTextService localizedTextService, - IMemberTypeService memberTypeService, - IMemberService memberService, - IMemberGroupService memberGroupService, - IOptions memberPasswordConfiguration, - IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, - PropertyEditorCollection propertyEditorCollection) - : base(cultureDictionary, localizedTextService, contentTypeBaseServiceProvider) - { - _backofficeSecurityAccessor = backofficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backofficeSecurityAccessor)); - _localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); - _memberTypeService = memberTypeService ?? throw new ArgumentNullException(nameof(memberTypeService)); - _memberService = memberService ?? throw new ArgumentNullException(nameof(memberService)); - _memberGroupService = memberGroupService ?? throw new ArgumentNullException(nameof(memberGroupService)); - _memberPasswordConfiguration = memberPasswordConfiguration.Value; - _propertyEditorCollection = propertyEditorCollection; - } + /// + /// Overridden to deal with custom member properties and permissions. + public override IEnumerable> Map(IMember source, MapperContext context) + { + IMemberType memberType = _memberTypeService.Get(source.ContentTypeId); - /// - /// Overridden to deal with custom member properties and permissions. - public override IEnumerable> Map(IMember source, MapperContext context) + if (memberType is not null) { + IgnoreProperties = memberType.CompositionPropertyTypes + .Where(x => x.HasIdentity == false) + .Select(x => x.Alias) + .ToArray(); + } - var memberType = _memberTypeService.Get(source.ContentTypeId); - - if (memberType is not null) - { - - IgnoreProperties = memberType.CompositionPropertyTypes - .Where(x => x.HasIdentity == false) - .Select(x => x.Alias) - .ToArray(); - } + IEnumerable> resolved = base.Map(source, context); - var resolved = base.Map(source, context); + return resolved; + } - return resolved; - } + [Obsolete("Use MapMembershipProperties. Will be removed in Umbraco 10.")] + protected override IEnumerable GetCustomGenericProperties(IContentBase content) + { + var member = (IMember)content; + return MapMembershipProperties(member, null); + } - [Obsolete("Use MapMembershipProperties. Will be removed in Umbraco 10.")] - protected override IEnumerable GetCustomGenericProperties(IContentBase content) + private Dictionary GetPasswordConfig(IMember member) + { + var result = new Dictionary(_memberPasswordConfiguration.GetConfiguration(true)) { - var member = (IMember)content; - return MapMembershipProperties(member, null); - } + // the password change toggle will only be displayed if there is already a password assigned. + {"hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false} + }; - private Dictionary GetPasswordConfig(IMember member) - { - var result = new Dictionary(_memberPasswordConfiguration.GetConfiguration(true)) - { - // the password change toggle will only be displayed if there is already a password assigned. - {"hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false} - }; + // This will always be true for members since we always want to allow admins to change a password - so long as that + // user has access to edit members (but that security is taken care of separately) + result["allowManuallyChangingPassword"] = true; - // This will always be true for members since we always want to allow admins to change a password - so long as that - // user has access to edit members (but that security is taken care of separately) - result["allowManuallyChangingPassword"] = true; + return result; + } - return result; - } + /// + /// Overridden to assign the IsSensitive property values + /// + /// + /// + /// + /// + protected override List MapProperties(IContentBase content, List properties, + MapperContext context) + { + List result = base.MapProperties(content, properties, context); + var member = (IMember)content; + IMemberType memberType = _memberTypeService.Get(member.ContentTypeId); - /// - /// Overridden to assign the IsSensitive property values - /// - /// - /// - /// - /// - protected override List MapProperties(IContentBase content, List properties, MapperContext context) + // now update the IsSensitive value + foreach (ContentPropertyDisplay prop in result) { - var result = base.MapProperties(content, properties, context); - var member = (IMember)content; - var memberType = _memberTypeService.Get(member.ContentTypeId); - - // now update the IsSensitive value - foreach (var prop in result) + // check if this property is flagged as sensitive + var isSensitiveProperty = memberType?.IsSensitiveProperty(prop.Alias) ?? false; + // check permissions for viewing sensitive data + if (isSensitiveProperty && + _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasAccessToSensitiveData() == false) { - // check if this property is flagged as sensitive - var isSensitiveProperty = memberType?.IsSensitiveProperty(prop.Alias) ?? false; - // check permissions for viewing sensitive data - if (isSensitiveProperty && (_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasAccessToSensitiveData() == false)) - { - // mark this property as sensitive - prop.IsSensitive = true; - // mark this property as readonly so that it does not post any data - prop.Readonly = true; - // replace this editor with a sensitive value - prop.View = "sensitivevalue"; - // clear the value - prop.Value = null; - } + // mark this property as sensitive + prop.IsSensitive = true; + // mark this property as readonly so that it does not post any data + prop.Readonly = true; + // replace this editor with a sensitive value + prop.View = "sensitivevalue"; + // clear the value + prop.Value = null; } - return result; } - /// - /// Returns the login property display field - /// - /// - /// - /// - /// - /// - /// If the membership provider installed is the umbraco membership provider, then we will allow changing the username, however if - /// the membership provider is a custom one, we cannot allow changing the username because MembershipProvider's do not actually natively - /// allow that. - /// - internal static ContentPropertyDisplay GetLoginProperty(IMember member, ILocalizedTextService localizedText) - { - var prop = new ContentPropertyDisplay - { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}login", - Label = localizedText.Localize(null,"login"), - Value = member.Username - }; - - prop.View = "textbox"; - prop.Validation.Mandatory = true; - return prop; - } + return result; + } - internal IDictionary GetMemberGroupValue(string? username) + /// + /// Returns the login property display field + /// + /// + /// + /// + /// + /// + /// If the membership provider installed is the umbraco membership provider, then we will allow changing the username, + /// however if + /// the membership provider is a custom one, we cannot allow changing the username because MembershipProvider's do not + /// actually natively + /// allow that. + /// + internal static ContentPropertyDisplay GetLoginProperty(IMember member, ILocalizedTextService localizedText) + { + var prop = new ContentPropertyDisplay { - IEnumerable? userRoles = username.IsNullOrWhiteSpace() ? null : _memberService.GetAllRoles(username); + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}login", + Label = localizedText.Localize(null, "login"), + Value = member.Username + }; - // create a dictionary of all roles (except internal roles) + "false" - var result = _memberGroupService.GetAll() - .Select(x => x.Name!) - // if a role starts with __umbracoRole we won't show it as it's an internal role used for public access - .Where(x => x?.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false) - .OrderBy(x => x, StringComparer.OrdinalIgnoreCase) - .ToDictionary(x => x, x => false); + prop.View = "textbox"; + prop.Validation.Mandatory = true; + return prop; + } - // if user has no roles, just return the dictionary - if (userRoles == null) - { - return result; - } + internal IDictionary GetMemberGroupValue(string? username) + { + IEnumerable? userRoles = username.IsNullOrWhiteSpace() ? null : _memberService.GetAllRoles(username); - // else update the dictionary to "true" for the user roles (except internal roles) - foreach (var userRole in userRoles.Where(x => x?.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false)) - { - result[userRole] = true; - } + // create a dictionary of all roles (except internal roles) + "false" + var result = _memberGroupService.GetAll() + .Select(x => x.Name!) + // if a role starts with __umbracoRole we won't show it as it's an internal role used for public access + .Where(x => x?.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false) + .OrderBy(x => x, StringComparer.OrdinalIgnoreCase) + .ToDictionary(x => x, x => false); + // if user has no roles, just return the dictionary + if (userRoles == null) + { return result; } - public IEnumerable MapMembershipProperties(IMember member, MapperContext? context) + // else update the dictionary to "true" for the user roles (except internal roles) + foreach (var userRole in userRoles.Where(x => + x?.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false)) { - var properties = new List - { - GetLoginProperty(member, _localizedTextService), - new() - { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}email", - Label = _localizedTextService.Localize("general","email"), - Value = member.Email, - View = "email", - Validation = { Mandatory = true } - }, - new() - { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}password", - Label = _localizedTextService.Localize(null,"password"), - Value = new Dictionary - { - // TODO: why ignoreCase, what are we doing here?! - { "newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null) } - }, - View = "changepassword", - Config = GetPasswordConfig(member) // Initialize the dictionary with the configuration from the default membership provider - }, - new() - { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}membergroup", - Label = _localizedTextService.Localize("content","membergroup"), - Value = GetMemberGroupValue(member.Username), - View = "membergroups", - Config = new Dictionary - { - { "IsRequired", true } - }, - }, - - // These properties used to live on the member as property data, defaulting to sensitive, so we set them to sensitive here too - new() - { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}failedPasswordAttempts", - Label = _localizedTextService.Localize("user", "failedPasswordAttempts"), - Value = member.FailedPasswordAttempts, - View = "readonlyvalue", - IsSensitive = true, - }, - - new() - { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}approved", - Label = _localizedTextService.Localize("user", "stateApproved"), - Value = member.IsApproved, - View = "boolean", - IsSensitive = true, - Readonly = false, - }, - - new() - { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lockedOut", - Label = _localizedTextService.Localize("user", "stateLockedOut"), - Value = member.IsLockedOut, - View = "boolean", - IsSensitive = true, - Readonly = !member.IsLockedOut, // IMember.IsLockedOut can't be set to true, so make it readonly when that's the case (you can only unlock) - }, + result[userRole] = true; + } - new() - { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lastLockoutDate", - Label = _localizedTextService.Localize("user", "lastLockoutDate"), - Value = member.LastLockoutDate?.ToString(), - View = "readonlyvalue", - IsSensitive = true, - }, + return result; + } - new() + public IEnumerable MapMembershipProperties(IMember member, MapperContext? context) + { + var properties = new List + { + GetLoginProperty(member, _localizedTextService), + new() + { + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}email", + Label = _localizedTextService.Localize("general", "email"), + Value = member.Email, + View = "email", + Validation = {Mandatory = true} + }, + new() + { + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}password", + Label = _localizedTextService.Localize(null, "password"), + Value = new Dictionary { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lastLoginDate", - Label = _localizedTextService.Localize("user", "lastLogin"), - Value = member.LastLoginDate?.ToString(), - View = "readonlyvalue", - IsSensitive = true, + // TODO: why ignoreCase, what are we doing here?! + {"newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null)} }, + View = "changepassword", + Config = GetPasswordConfig( + member) // Initialize the dictionary with the configuration from the default membership provider + }, + new() + { + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}membergroup", + Label = _localizedTextService.Localize("content", "membergroup"), + Value = GetMemberGroupValue(member.Username), + View = "membergroups", + Config = new Dictionary {{"IsRequired", true}} + }, - new() - { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lastPasswordChangeDate", - Label = _localizedTextService.Localize("user", "lastPasswordChangeDate"), - Value = member.LastPasswordChangeDate?.ToString(), - View = "readonlyvalue", - IsSensitive = true, - }, - }; + // These properties used to live on the member as property data, defaulting to sensitive, so we set them to sensitive here too + new() + { + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}failedPasswordAttempts", + Label = _localizedTextService.Localize("user", "failedPasswordAttempts"), + Value = member.FailedPasswordAttempts, + View = "readonlyvalue", + IsSensitive = true + }, + new() + { + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}approved", + Label = _localizedTextService.Localize("user", "stateApproved"), + Value = member.IsApproved, + View = "boolean", + IsSensitive = true, + Readonly = false + }, + new() + { + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lockedOut", + Label = _localizedTextService.Localize("user", "stateLockedOut"), + Value = member.IsLockedOut, + View = "boolean", + IsSensitive = true, + Readonly = !member + .IsLockedOut // IMember.IsLockedOut can't be set to true, so make it readonly when that's the case (you can only unlock) + }, + new() + { + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lastLockoutDate", + Label = _localizedTextService.Localize("user", "lastLockoutDate"), + Value = member.LastLockoutDate?.ToString(), + View = "readonlyvalue", + IsSensitive = true + }, + new() + { + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lastLoginDate", + Label = _localizedTextService.Localize("user", "lastLogin"), + Value = member.LastLoginDate?.ToString(), + View = "readonlyvalue", + IsSensitive = true + }, + new() + { + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lastPasswordChangeDate", + Label = _localizedTextService.Localize("user", "lastPasswordChangeDate"), + Value = member.LastPasswordChangeDate?.ToString(), + View = "readonlyvalue", + IsSensitive = true + } + }; - if (_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasAccessToSensitiveData() is false) + if (_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasAccessToSensitiveData() is false) + { + // Current user doesn't have access to sensitive data so explicitly set the views and remove the value from sensitive data + foreach (ContentPropertyDisplay property in properties) { - // Current user doesn't have access to sensitive data so explicitly set the views and remove the value from sensitive data - foreach (var property in properties) + if (property.IsSensitive) { - if (property.IsSensitive) - { - property.Value = null; - property.View = "sensitivevalue"; - property.Readonly = true; - } + property.Value = null; + property.View = "sensitivevalue"; + property.Readonly = true; } } - - return properties; } + + return properties; } } diff --git a/src/Umbraco.Core/Models/Mapping/PropertyTypeGroupMapper.cs b/src/Umbraco.Core/Models/Mapping/PropertyTypeGroupMapper.cs index 5d4d9ba485bf..d398298b0115 100644 --- a/src/Umbraco.Core/Models/Mapping/PropertyTypeGroupMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/PropertyTypeGroupMapper.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.PropertyEditors; @@ -8,258 +5,288 @@ using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +public class PropertyTypeGroupMapper + where TPropertyType : PropertyTypeDisplay, new() { - public class PropertyTypeGroupMapper - where TPropertyType : PropertyTypeDisplay, new() + private readonly IDataTypeService _dataTypeService; + private readonly ILogger> _logger; + private readonly PropertyEditorCollection _propertyEditors; + private readonly IShortStringHelper _shortStringHelper; + + public PropertyTypeGroupMapper(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, + IShortStringHelper shortStringHelper, ILogger> logger) { - private readonly PropertyEditorCollection _propertyEditors; - private readonly IDataTypeService _dataTypeService; - private readonly IShortStringHelper _shortStringHelper; - private readonly ILogger> _logger; + _propertyEditors = propertyEditors; + _dataTypeService = dataTypeService; + _shortStringHelper = shortStringHelper; + _logger = logger; + } - public PropertyTypeGroupMapper(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IShortStringHelper shortStringHelper, ILogger> logger) + /// + /// Gets the content type that defines a property group, within a composition. + /// + /// The composition. + /// The identifier of the property group. + /// The composition content type that defines the specified property group. + private static IContentTypeComposition? GetContentTypeForPropertyGroup(IContentTypeComposition contentType, + int propertyGroupId) + { + // test local groups + if (contentType.PropertyGroups.Any(x => x.Id == propertyGroupId)) { - _propertyEditors = propertyEditors; - _dataTypeService = dataTypeService; - _shortStringHelper = shortStringHelper; - _logger = logger; + return contentType; } - /// - /// Gets the content type that defines a property group, within a composition. - /// - /// The composition. - /// The identifier of the property group. - /// The composition content type that defines the specified property group. - private static IContentTypeComposition? GetContentTypeForPropertyGroup(IContentTypeComposition contentType, int propertyGroupId) + // test composition types groups + // .ContentTypeComposition is just the local ones, not recursive, + // so we have to recurse here + return contentType.ContentTypeComposition + .Select(x => GetContentTypeForPropertyGroup(x, propertyGroupId)) + .FirstOrDefault(x => x != null); + } + + /// + /// Gets the content type that defines a property group, within a composition. + /// + /// The composition. + /// The identifier of the property type. + /// The composition content type that defines the specified property group. + private static IContentTypeComposition? GetContentTypeForPropertyType(IContentTypeComposition contentType, + int propertyTypeId) + { + // test local property types + if (contentType.PropertyTypes.Any(x => x.Id == propertyTypeId)) { - // test local groups - if (contentType.PropertyGroups.Any(x => x.Id == propertyGroupId)) - return contentType; - - // test composition types groups - // .ContentTypeComposition is just the local ones, not recursive, - // so we have to recurse here - return contentType.ContentTypeComposition - .Select(x => GetContentTypeForPropertyGroup(x, propertyGroupId)) - .FirstOrDefault(x => x != null); + return contentType; } - /// - /// Gets the content type that defines a property group, within a composition. - /// - /// The composition. - /// The identifier of the property type. - /// The composition content type that defines the specified property group. - private static IContentTypeComposition? GetContentTypeForPropertyType(IContentTypeComposition contentType, int propertyTypeId) + // test composition property types + // .ContentTypeComposition is just the local ones, not recursive, + // so we have to recurse here + return contentType.ContentTypeComposition + .Select(x => GetContentTypeForPropertyType(x, propertyTypeId)) + .FirstOrDefault(x => x != null); + } + + public IEnumerable> Map(IContentTypeComposition source) + { + // deal with groups + var groups = new List>(); + + // add groups local to this content type + foreach (PropertyGroup propertyGroup in source.PropertyGroups) { - // test local property types - if (contentType.PropertyTypes.Any(x => x.Id == propertyTypeId)) - return contentType; - - // test composition property types - // .ContentTypeComposition is just the local ones, not recursive, - // so we have to recurse here - return contentType.ContentTypeComposition - .Select(x => GetContentTypeForPropertyType(x, propertyTypeId)) - .FirstOrDefault(x => x != null); + var group = new PropertyGroupDisplay + { + Id = propertyGroup.Id, + Key = propertyGroup.Key, + Type = propertyGroup.Type, + Name = propertyGroup.Name, + Alias = propertyGroup.Alias, + SortOrder = propertyGroup.SortOrder, + Properties = MapProperties(propertyGroup.PropertyTypes, source, propertyGroup.Id, false), + ContentTypeId = source.Id + }; + + groups.Add(group); } - public IEnumerable> Map(IContentTypeComposition source) + // add groups inherited through composition + var localGroupIds = groups.Select(x => x.Id).ToArray(); + foreach (PropertyGroup propertyGroup in source.CompositionPropertyGroups) { - // deal with groups - var groups = new List>(); - - // add groups local to this content type - foreach (var propertyGroup in source.PropertyGroups) + // skip those that are local to this content type + if (localGroupIds.Contains(propertyGroup.Id)) { - var group = new PropertyGroupDisplay - { - Id = propertyGroup.Id, - Key = propertyGroup.Key, - Type = propertyGroup.Type, - Name = propertyGroup.Name, - Alias = propertyGroup.Alias, - SortOrder = propertyGroup.SortOrder, - Properties = MapProperties(propertyGroup.PropertyTypes, source, propertyGroup.Id, false), - ContentTypeId = source.Id - }; - - groups.Add(group); + continue; } - // add groups inherited through composition - var localGroupIds = groups.Select(x => x.Id).ToArray(); - foreach (var propertyGroup in source.CompositionPropertyGroups) + // get the content type that defines this group + IContentTypeComposition definingContentType = GetContentTypeForPropertyGroup(source, propertyGroup.Id); + if (definingContentType == null) { - // skip those that are local to this content type - if (localGroupIds.Contains(propertyGroup.Id)) continue; + throw new Exception("PropertyGroup with id=" + propertyGroup.Id + + " was not found on any of the content type's compositions."); + } - // get the content type that defines this group - var definingContentType = GetContentTypeForPropertyGroup(source, propertyGroup.Id); - if (definingContentType == null) - throw new Exception("PropertyGroup with id=" + propertyGroup.Id + " was not found on any of the content type's compositions."); + var group = new PropertyGroupDisplay + { + Inherited = true, + Id = propertyGroup.Id, + Key = propertyGroup.Key, + Type = propertyGroup.Type, + Name = propertyGroup.Name, + Alias = propertyGroup.Alias, + SortOrder = propertyGroup.SortOrder, + Properties = + MapProperties(propertyGroup.PropertyTypes, definingContentType, propertyGroup.Id, true), + ContentTypeId = definingContentType.Id, + ParentTabContentTypes = new[] {definingContentType.Id}, + ParentTabContentTypeNames = new[] {definingContentType.Name} + }; - var group = new PropertyGroupDisplay - { - Inherited = true, - Id = propertyGroup.Id, - Key = propertyGroup.Key, - Type = propertyGroup.Type, - Name = propertyGroup.Name, - Alias = propertyGroup.Alias, - SortOrder = propertyGroup.SortOrder, - Properties = MapProperties(propertyGroup.PropertyTypes, definingContentType, propertyGroup.Id, true), - ContentTypeId = definingContentType.Id, - ParentTabContentTypes = new[] { definingContentType.Id }, - ParentTabContentTypeNames = new[] { definingContentType.Name } - }; - - groups.Add(group); - } + groups.Add(group); + } - // deal with generic properties - var genericProperties = new List(); + // deal with generic properties + var genericProperties = new List(); - // add generic properties local to this content type - var entityGenericProperties = source.PropertyTypes.Where(x => x.PropertyGroupId == null); - genericProperties.AddRange(MapProperties(entityGenericProperties, source, PropertyGroupBasic.GenericPropertiesGroupId, false)); + // add generic properties local to this content type + IEnumerable entityGenericProperties = source.PropertyTypes.Where(x => x.PropertyGroupId == null); + genericProperties.AddRange(MapProperties(entityGenericProperties, source, + PropertyGroupBasic.GenericPropertiesGroupId, false)); - // add generic properties inherited through compositions - var localGenericPropertyIds = genericProperties.Select(x => x.Id).ToArray(); - var compositionGenericProperties = source.CompositionPropertyTypes - .Where(x => x.PropertyGroupId == null // generic - && localGenericPropertyIds.Contains(x.Id) == false); // skip those that are local - foreach (var compositionGenericProperty in compositionGenericProperties) + // add generic properties inherited through compositions + var localGenericPropertyIds = genericProperties.Select(x => x.Id).ToArray(); + IEnumerable compositionGenericProperties = source.CompositionPropertyTypes + .Where(x => x.PropertyGroupId == null // generic + && localGenericPropertyIds.Contains(x.Id) == false); // skip those that are local + foreach (IPropertyType compositionGenericProperty in compositionGenericProperties) + { + IContentTypeComposition definingContentType = + GetContentTypeForPropertyType(source, compositionGenericProperty.Id); + if (definingContentType == null) { - var definingContentType = GetContentTypeForPropertyType(source, compositionGenericProperty.Id); - if (definingContentType == null) - throw new Exception("PropertyType with id=" + compositionGenericProperty.Id + " was not found on any of the content type's compositions."); - genericProperties.AddRange(MapProperties(new[] { compositionGenericProperty }, definingContentType, PropertyGroupBasic.GenericPropertiesGroupId, true)); + throw new Exception("PropertyType with id=" + compositionGenericProperty.Id + + " was not found on any of the content type's compositions."); } - // if there are any generic properties, add the corresponding tab - if (genericProperties.Any()) - { - var genericGroup = new PropertyGroupDisplay - { - Id = PropertyGroupBasic.GenericPropertiesGroupId, - Name = "Generic properties", - Alias = "genericProperties", - SortOrder = 999, - Properties = genericProperties, - ContentTypeId = source.Id - }; - - groups.Add(genericGroup); - } + genericProperties.AddRange(MapProperties(new[] {compositionGenericProperty}, definingContentType, + PropertyGroupBasic.GenericPropertiesGroupId, true)); + } - // handle locked properties - var lockedPropertyAliases = new List(); - // add built-in member property aliases to list of aliases to be locked - foreach (var propertyAlias in ConventionsHelper.GetStandardPropertyTypeStubs(_shortStringHelper).Keys) + // if there are any generic properties, add the corresponding tab + if (genericProperties.Any()) + { + var genericGroup = new PropertyGroupDisplay { - lockedPropertyAliases.Add(propertyAlias); - } - // lock properties by aliases - foreach (var property in groups.SelectMany(x => x.Properties)) + Id = PropertyGroupBasic.GenericPropertiesGroupId, + Name = "Generic properties", + Alias = "genericProperties", + SortOrder = 999, + Properties = genericProperties, + ContentTypeId = source.Id + }; + + groups.Add(genericGroup); + } + + // handle locked properties + var lockedPropertyAliases = new List(); + // add built-in member property aliases to list of aliases to be locked + foreach (var propertyAlias in ConventionsHelper.GetStandardPropertyTypeStubs(_shortStringHelper).Keys) + { + lockedPropertyAliases.Add(propertyAlias); + } + + // lock properties by aliases + foreach (TPropertyType property in groups.SelectMany(x => x.Properties)) + { + if (property.Alias is not null) { - if (property.Alias is not null) - { - property.Locked = lockedPropertyAliases.Contains(property.Alias); - } + property.Locked = lockedPropertyAliases.Contains(property.Alias); } + } - // now merge tabs based on alias - // as for one name, we might have one local tab, plus some inherited tabs - var groupsGroupsByAlias = groups.GroupBy(x => x.Alias).ToArray(); - groups = new List>(); // start with a fresh list - foreach (var groupsByAlias in groupsGroupsByAlias) + // now merge tabs based on alias + // as for one name, we might have one local tab, plus some inherited tabs + IGrouping>[] groupsGroupsByAlias = + groups.GroupBy(x => x.Alias).ToArray(); + groups = new List>(); // start with a fresh list + foreach (IGrouping> groupsByAlias in groupsGroupsByAlias) + { + // single group, just use it + if (groupsByAlias.Count() == 1) { - // single group, just use it - if (groupsByAlias.Count() == 1) - { - groups.Add(groupsByAlias.First()); - continue; - } - - // multiple groups, merge - var group = groupsByAlias.FirstOrDefault(x => x.Inherited == false) // try local - ?? groupsByAlias.First(); // else pick one randomly - groups.Add(group); - - // in case we use the local one, flag as inherited - group.Inherited = true; // TODO Remove to allow changing sort order of the local one (and use the inherited group order below) - - // merge (and sort) properties - var properties = groupsByAlias.SelectMany(x => x.Properties).OrderBy(x => x.SortOrder).ToArray(); - group.Properties = properties; - - // collect parent group info - var parentGroups = groupsByAlias.Where(x => x.ContentTypeId != source.Id).ToArray(); - group.ParentTabContentTypes = parentGroups.SelectMany(x => x.ParentTabContentTypes).ToArray(); - group.ParentTabContentTypeNames = parentGroups.SelectMany(x => x.ParentTabContentTypeNames).ToArray(); + groups.Add(groupsByAlias.First()); + continue; } - return groups.OrderBy(x => x.SortOrder); + // multiple groups, merge + PropertyGroupDisplay group = + groupsByAlias.FirstOrDefault(x => x.Inherited == false) // try local + ?? groupsByAlias.First(); // else pick one randomly + groups.Add(group); + + // in case we use the local one, flag as inherited + group.Inherited = + true; // TODO Remove to allow changing sort order of the local one (and use the inherited group order below) + + // merge (and sort) properties + TPropertyType[] properties = + groupsByAlias.SelectMany(x => x.Properties).OrderBy(x => x.SortOrder).ToArray(); + group.Properties = properties; + + // collect parent group info + PropertyGroupDisplay[] parentGroups = + groupsByAlias.Where(x => x.ContentTypeId != source.Id).ToArray(); + group.ParentTabContentTypes = parentGroups.SelectMany(x => x.ParentTabContentTypes).ToArray(); + group.ParentTabContentTypeNames = parentGroups.SelectMany(x => x.ParentTabContentTypeNames).ToArray(); } - private IEnumerable MapProperties(IEnumerable? properties, IContentTypeBase contentType, int groupId, bool inherited) + return groups.OrderBy(x => x.SortOrder); + } + + private IEnumerable MapProperties(IEnumerable? properties, + IContentTypeBase contentType, int groupId, bool inherited) + { + var mappedProperties = new List(); + + foreach (IPropertyType p in properties?.Where(x => x.DataTypeId != 0).OrderBy(x => x.SortOrder) ?? + Enumerable.Empty()) { - var mappedProperties = new List(); + var propertyEditorAlias = p.PropertyEditorAlias; + IDataEditor propertyEditor = _propertyEditors[propertyEditorAlias]; + IDataType dataType = _dataTypeService.GetDataType(p.DataTypeId); - foreach (var p in properties?.Where(x => x.DataTypeId != 0).OrderBy(x => x.SortOrder) ?? Enumerable.Empty()) + //fixme: Don't explode if we can't find this, log an error and change this to a label + if (propertyEditor == null) { - var propertyEditorAlias = p.PropertyEditorAlias; - var propertyEditor = _propertyEditors[propertyEditorAlias]; - var dataType = _dataTypeService.GetDataType(p.DataTypeId); - - //fixme: Don't explode if we can't find this, log an error and change this to a label - if (propertyEditor == null) - { - _logger.LogError("No property editor could be resolved with the alias: {PropertyEditorAlias}, defaulting to label", p.PropertyEditorAlias); - propertyEditorAlias = Constants.PropertyEditors.Aliases.Label; - propertyEditor = _propertyEditors[propertyEditorAlias]; - } + _logger.LogError( + "No property editor could be resolved with the alias: {PropertyEditorAlias}, defaulting to label", + p.PropertyEditorAlias); + propertyEditorAlias = Constants.PropertyEditors.Aliases.Label; + propertyEditor = _propertyEditors[propertyEditorAlias]; + } - var config = propertyEditor is null || dataType is null - ? new Dictionary() - : dataType.Editor?.GetConfigurationEditor().ToConfigurationEditor(dataType.Configuration); + IDictionary config = propertyEditor is null || dataType is null + ? new Dictionary() + : dataType.Editor?.GetConfigurationEditor().ToConfigurationEditor(dataType.Configuration); - mappedProperties.Add(new TPropertyType + mappedProperties.Add(new TPropertyType + { + Id = p.Id, + Alias = p.Alias, + Description = p.Description, + LabelOnTop = p.LabelOnTop, + Editor = p.PropertyEditorAlias, + Validation = new PropertyTypeValidation { - Id = p.Id, - Alias = p.Alias, - Description = p.Description, - LabelOnTop = p.LabelOnTop, - Editor = p.PropertyEditorAlias, - Validation = new PropertyTypeValidation - { - Mandatory = p.Mandatory, - MandatoryMessage = p.MandatoryMessage, - Pattern = p.ValidationRegExp, - PatternMessage = p.ValidationRegExpMessage, - }, - Label = p.Name, - View = propertyEditor?.GetValueEditor().View, - Config = config, - //Value = "", - GroupId = groupId, - Inherited = inherited, - DataTypeId = p.DataTypeId, - DataTypeKey = p.DataTypeKey, - DataTypeName = dataType?.Name, - DataTypeIcon = propertyEditor?.Icon, - SortOrder = p.SortOrder, - ContentTypeId = contentType.Id, - ContentTypeName = contentType.Name, - AllowCultureVariant = p.VariesByCulture(), - AllowSegmentVariant = p.VariesBySegment() - }); - } - - return mappedProperties; + Mandatory = p.Mandatory, + MandatoryMessage = p.MandatoryMessage, + Pattern = p.ValidationRegExp, + PatternMessage = p.ValidationRegExpMessage + }, + Label = p.Name, + View = propertyEditor?.GetValueEditor().View, + Config = config, + //Value = "", + GroupId = groupId, + Inherited = inherited, + DataTypeId = p.DataTypeId, + DataTypeKey = p.DataTypeKey, + DataTypeName = dataType?.Name, + DataTypeIcon = propertyEditor?.Icon, + SortOrder = p.SortOrder, + ContentTypeId = contentType.Id, + ContentTypeName = contentType.Name, + AllowCultureVariant = p.VariesByCulture(), + AllowSegmentVariant = p.VariesBySegment() + }); } + + return mappedProperties; } } diff --git a/src/Umbraco.Core/Models/Mapping/RedirectUrlMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/RedirectUrlMapDefinition.cs index f4715b3a6b3c..dd463cb8103e 100644 --- a/src/Umbraco.Core/Models/Mapping/RedirectUrlMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/RedirectUrlMapDefinition.cs @@ -2,31 +2,28 @@ using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Routing; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +public class RedirectUrlMapDefinition : IMapDefinition { - public class RedirectUrlMapDefinition : IMapDefinition - { - private readonly IPublishedUrlProvider _publishedUrlProvider; + private readonly IPublishedUrlProvider _publishedUrlProvider; - public RedirectUrlMapDefinition(IPublishedUrlProvider publishedUrlProvider) - { - _publishedUrlProvider = publishedUrlProvider; - } + public RedirectUrlMapDefinition(IPublishedUrlProvider publishedUrlProvider) => + _publishedUrlProvider = publishedUrlProvider; - public void DefineMaps(IUmbracoMapper mapper) - { - mapper.Define((source, context) => new ContentRedirectUrl(), Map); - } + public void DefineMaps(IUmbracoMapper mapper) => + mapper.Define((source, context) => new ContentRedirectUrl(), Map); - // Umbraco.Code.MapAll - private void Map(IRedirectUrl source, ContentRedirectUrl target, MapperContext context) - { - target.ContentId = source.ContentId; - target.CreateDateUtc = source.CreateDateUtc; - target.Culture = source.Culture; - target.DestinationUrl = source.ContentId > 0 ? _publishedUrlProvider?.GetUrl(source.ContentId, culture: source.Culture) : "#"; - target.OriginalUrl = _publishedUrlProvider?.GetUrlFromRoute(source.ContentId, source.Url, source.Culture); - target.RedirectId = source.Key; - } + // Umbraco.Code.MapAll + private void Map(IRedirectUrl source, ContentRedirectUrl target, MapperContext context) + { + target.ContentId = source.ContentId; + target.CreateDateUtc = source.CreateDateUtc; + target.Culture = source.Culture; + target.DestinationUrl = source.ContentId > 0 + ? _publishedUrlProvider?.GetUrl(source.ContentId, culture: source.Culture) + : "#"; + target.OriginalUrl = _publishedUrlProvider?.GetUrlFromRoute(source.ContentId, source.Url, source.Culture); + target.RedirectId = source.Key; } } diff --git a/src/Umbraco.Core/Models/Mapping/RelationMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/RelationMapDefinition.cs index b0aaab953750..12d16e4f4856 100644 --- a/src/Umbraco.Core/Models/Mapping/RelationMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/RelationMapDefinition.cs @@ -1,95 +1,96 @@ using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +public class RelationMapDefinition : IMapDefinition { - public class RelationMapDefinition : IMapDefinition + private readonly IEntityService _entityService; + private readonly IRelationService _relationService; + + public RelationMapDefinition(IEntityService entityService, IRelationService relationService) { - private readonly IEntityService _entityService; - private readonly IRelationService _relationService; + _entityService = entityService; + _relationService = relationService; + } - public RelationMapDefinition(IEntityService entityService, IRelationService relationService) - { - _entityService = entityService; - _relationService = relationService; - } + public void DefineMaps(IUmbracoMapper mapper) + { + mapper.Define((source, context) => new RelationTypeDisplay(), Map); + mapper.Define((source, context) => new RelationDisplay(), Map); + mapper.Define(Map); + } - public void DefineMaps(IUmbracoMapper mapper) - { - mapper.Define((source, context) => new RelationTypeDisplay(), Map); - mapper.Define((source, context) => new RelationDisplay(), Map); - mapper.Define(Map); - } + // Umbraco.Code.MapAll -Icon -Trashed -AdditionalData + // Umbraco.Code.MapAll -ParentId -Notifications + private void Map(IRelationType source, RelationTypeDisplay target, MapperContext context) + { + target.ChildObjectType = source.ChildObjectType; + target.Id = source.Id; + target.IsBidirectional = source.IsBidirectional; - // Umbraco.Code.MapAll -Icon -Trashed -AdditionalData - // Umbraco.Code.MapAll -ParentId -Notifications - private void Map(IRelationType source, RelationTypeDisplay target, MapperContext context) + if (source is IRelationTypeWithIsDependency sourceWithIsDependency) { - target.ChildObjectType = source.ChildObjectType; - target.Id = source.Id; - target.IsBidirectional = source.IsBidirectional; - - if (source is IRelationTypeWithIsDependency sourceWithIsDependency) - { - target.IsDependency = sourceWithIsDependency.IsDependency; - } - target.Key = source.Key; - target.Name = source.Name; - target.Alias = source.Alias; - target.ParentObjectType = source.ParentObjectType; - target.Udi = Udi.Create(Constants.UdiEntityType.RelationType, source.Key); - target.Path = "-1," + source.Id; + target.IsDependency = sourceWithIsDependency.IsDependency; + } - target.IsSystemRelationType = source.IsSystemRelationType(); + target.Key = source.Key; + target.Name = source.Name; + target.Alias = source.Alias; + target.ParentObjectType = source.ParentObjectType; + target.Udi = Udi.Create(Constants.UdiEntityType.RelationType, source.Key); + target.Path = "-1," + source.Id; - // Set the "friendly" and entity names for the parent and child object types - if (source.ParentObjectType.HasValue) - { - var objType = ObjectTypes.GetUmbracoObjectType(source.ParentObjectType.Value); - target.ParentObjectTypeName = objType.GetFriendlyName(); - } + target.IsSystemRelationType = source.IsSystemRelationType(); - if (source.ChildObjectType.HasValue) - { - var objType = ObjectTypes.GetUmbracoObjectType(source.ChildObjectType.Value); - target.ChildObjectTypeName = objType.GetFriendlyName(); - } + // Set the "friendly" and entity names for the parent and child object types + if (source.ParentObjectType.HasValue) + { + UmbracoObjectTypes objType = ObjectTypes.GetUmbracoObjectType(source.ParentObjectType.Value); + target.ParentObjectTypeName = objType.GetFriendlyName(); } - // Umbraco.Code.MapAll -ParentName -ChildName - private void Map(IRelation source, RelationDisplay target, MapperContext context) + if (source.ChildObjectType.HasValue) { - target.ChildId = source.ChildId; - target.Comment = source.Comment; - target.CreateDate = source.CreateDate; - target.ParentId = source.ParentId; + UmbracoObjectTypes objType = ObjectTypes.GetUmbracoObjectType(source.ChildObjectType.Value); + target.ChildObjectTypeName = objType.GetFriendlyName(); + } + } - var entities = _relationService.GetEntitiesFromRelation(source); + // Umbraco.Code.MapAll -ParentName -ChildName + private void Map(IRelation source, RelationDisplay target, MapperContext context) + { + target.ChildId = source.ChildId; + target.Comment = source.Comment; + target.CreateDate = source.CreateDate; + target.ParentId = source.ParentId; - if (entities is not null) - { - target.ParentName = entities.Item1.Name; - target.ChildName = entities.Item2.Name; - } - } + Tuple entities = _relationService.GetEntitiesFromRelation(source); - // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate - private static void Map(RelationTypeSave source, IRelationType target, MapperContext context) + if (entities is not null) { - target.Alias = source.Alias; - target.ChildObjectType = source.ChildObjectType; - target.Id = source.Id.TryConvertTo().Result; - target.IsBidirectional = source.IsBidirectional; - if (target is IRelationTypeWithIsDependency targetWithIsDependency) - { - targetWithIsDependency.IsDependency = source.IsDependency; - } + target.ParentName = entities.Item1.Name; + target.ChildName = entities.Item2.Name; + } + } - target.Key = source.Key; - target.Name = source.Name; - target.ParentObjectType = source.ParentObjectType; + // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate + private static void Map(RelationTypeSave source, IRelationType target, MapperContext context) + { + target.Alias = source.Alias; + target.ChildObjectType = source.ChildObjectType; + target.Id = source.Id.TryConvertTo().Result; + target.IsBidirectional = source.IsBidirectional; + if (target is IRelationTypeWithIsDependency targetWithIsDependency) + { + targetWithIsDependency.IsDependency = source.IsDependency; } + + target.Key = source.Key; + target.Name = source.Name; + target.ParentObjectType = source.ParentObjectType; } } diff --git a/src/Umbraco.Core/Models/Mapping/SectionMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/SectionMapDefinition.cs index b7bdbccd2634..e66a08addf39 100644 --- a/src/Umbraco.Core/Models/Mapping/SectionMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/SectionMapDefinition.cs @@ -5,44 +5,41 @@ using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +public class SectionMapDefinition : IMapDefinition { - public class SectionMapDefinition : IMapDefinition - { - private readonly ILocalizedTextService _textService; - public SectionMapDefinition(ILocalizedTextService textService) - { - _textService = textService; - } + private readonly ILocalizedTextService _textService; - public void DefineMaps(IUmbracoMapper mapper) - { - mapper.Define((source, context) => new Section(), Map); + public SectionMapDefinition(ILocalizedTextService textService) => _textService = textService; + + public void DefineMaps(IUmbracoMapper mapper) + { + mapper.Define((source, context) => new Section(), Map); - // this is for AutoMapper ReverseMap - but really? - mapper.Define(); - mapper.Define(); - mapper.Define(Map); - mapper.Define(); - mapper.Define(); - mapper.Define(); - mapper.Define(); - mapper.Define(); - mapper.Define(); - } + // this is for AutoMapper ReverseMap - but really? + mapper.Define(); + mapper.Define(); + mapper.Define(Map); + mapper.Define(); + mapper.Define(); + mapper.Define(); + mapper.Define(); + mapper.Define(); + mapper.Define(); + } - // Umbraco.Code.MapAll -RoutePath - private void Map(ISection source, Section target, MapperContext context) - { - target.Alias = source.Alias; - target.Name = _textService.Localize("sections", source.Alias); - } + // Umbraco.Code.MapAll -RoutePath + private void Map(ISection source, Section target, MapperContext context) + { + target.Alias = source.Alias; + target.Name = _textService.Localize("sections", source.Alias); + } - // Umbraco.Code.MapAll - private static void Map(Section source, ManifestSection target, MapperContext context) - { - target.Alias = source.Alias; - target.Name = source.Name; - } + // Umbraco.Code.MapAll + private static void Map(Section source, ManifestSection target, MapperContext context) + { + target.Alias = source.Alias; + target.Name = source.Name; } } diff --git a/src/Umbraco.Core/Models/Mapping/TabsAndPropertiesMapper.cs b/src/Umbraco.Core/Models/Mapping/TabsAndPropertiesMapper.cs index be4b6bae61b4..7a68f803ac9b 100644 --- a/src/Umbraco.Core/Models/Mapping/TabsAndPropertiesMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/TabsAndPropertiesMapper.cs @@ -1,159 +1,159 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core.Dictionary; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +public abstract class TabsAndPropertiesMapper { - public abstract class TabsAndPropertiesMapper + protected TabsAndPropertiesMapper(ICultureDictionary cultureDictionary, ILocalizedTextService localizedTextService) + : this(cultureDictionary, localizedTextService, new List()) { - protected ICultureDictionary CultureDictionary { get; } - protected ILocalizedTextService LocalizedTextService { get; } - protected IEnumerable IgnoreProperties { get; set; } - - protected TabsAndPropertiesMapper(ICultureDictionary cultureDictionary, ILocalizedTextService localizedTextService) - : this(cultureDictionary, localizedTextService, new List()) - { } - - protected TabsAndPropertiesMapper(ICultureDictionary cultureDictionary, ILocalizedTextService localizedTextService, IEnumerable ignoreProperties) - { - CultureDictionary = cultureDictionary ?? throw new ArgumentNullException(nameof(cultureDictionary)); - LocalizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); - IgnoreProperties = ignoreProperties ?? throw new ArgumentNullException(nameof(ignoreProperties)); - } + } - /// - /// Returns a collection of custom generic properties that exist on the generic properties tab - /// - /// - protected virtual IEnumerable GetCustomGenericProperties(IContentBase content) - { - return Enumerable.Empty(); - } + protected TabsAndPropertiesMapper(ICultureDictionary cultureDictionary, ILocalizedTextService localizedTextService, + IEnumerable ignoreProperties) + { + CultureDictionary = cultureDictionary ?? throw new ArgumentNullException(nameof(cultureDictionary)); + LocalizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); + IgnoreProperties = ignoreProperties ?? throw new ArgumentNullException(nameof(ignoreProperties)); + } - /// - /// Maps properties on to the generic properties tab - /// - /// - /// - /// - /// - /// The generic properties tab is responsible for - /// setting up the properties such as Created date, updated date, template selected, etc... - /// - protected virtual void MapGenericProperties(IContentBase content, List> tabs, MapperContext context) - { - // add the generic properties tab, for properties that don't belong to a tab - // get the properties, map and translate them, then add the tab - var noGroupProperties = content.GetNonGroupedProperties() - .Where(x => IgnoreProperties.Contains(x.Alias) == false) // skip ignored - .ToList(); - var genericProperties = MapProperties(content, noGroupProperties, context); + protected ICultureDictionary CultureDictionary { get; } + protected ILocalizedTextService LocalizedTextService { get; } + protected IEnumerable IgnoreProperties { get; set; } + /// + /// Returns a collection of custom generic properties that exist on the generic properties tab + /// + /// + protected virtual IEnumerable GetCustomGenericProperties(IContentBase content) => + Enumerable.Empty(); + /// + /// Maps properties on to the generic properties tab + /// + /// + /// + /// + /// + /// The generic properties tab is responsible for + /// setting up the properties such as Created date, updated date, template selected, etc... + /// + protected virtual void MapGenericProperties(IContentBase content, List> tabs, + MapperContext context) + { + // add the generic properties tab, for properties that don't belong to a tab + // get the properties, map and translate them, then add the tab + var noGroupProperties = content.GetNonGroupedProperties() + .Where(x => IgnoreProperties.Contains(x.Alias) == false) // skip ignored + .ToList(); + List genericProperties = MapProperties(content, noGroupProperties, context); - var customProperties = GetCustomGenericProperties(content); - if (customProperties != null) - { - genericProperties.AddRange(customProperties); - } - if (genericProperties.Count > 0) - { - tabs.Add(new Tab - { - Id = 0, - Label = LocalizedTextService.Localize("general", "properties"), - Alias = "Generic properties", - Properties = genericProperties - }); - } + IEnumerable customProperties = GetCustomGenericProperties(content); + if (customProperties != null) + { + genericProperties.AddRange(customProperties); } - /// - /// Maps a list of to a list of - /// - /// - /// - /// - /// - protected virtual List MapProperties(IContentBase content, List properties, MapperContext context) + if (genericProperties.Count > 0) { - return context.MapEnumerable(properties.OrderBy(x => x.PropertyType?.SortOrder)).WhereNotNull().ToList(); + tabs.Add(new Tab + { + Id = 0, + Label = LocalizedTextService.Localize("general", "properties"), + Alias = "Generic properties", + Properties = genericProperties + }); } } /// - /// Creates the tabs collection with properties assigned for display models + /// Maps a list of to a list of /// - public class TabsAndPropertiesMapper : TabsAndPropertiesMapper - where TSource : IContentBase + /// + /// + /// + /// + protected virtual List MapProperties(IContentBase content, List properties, + MapperContext context) => + context.MapEnumerable(properties.OrderBy(x => x.PropertyType?.SortOrder)) + .WhereNotNull().ToList(); +} + +/// +/// Creates the tabs collection with properties assigned for display models +/// +public class TabsAndPropertiesMapper : TabsAndPropertiesMapper + where TSource : IContentBase +{ + private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; + + public TabsAndPropertiesMapper(ICultureDictionary cultureDictionary, ILocalizedTextService localizedTextService, + IContentTypeBaseServiceProvider contentTypeBaseServiceProvider) + : base(cultureDictionary, localizedTextService) => + _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider ?? + throw new ArgumentNullException(nameof(contentTypeBaseServiceProvider)); + + public virtual IEnumerable> Map(TSource source, MapperContext context) { - private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; + var tabs = new List>(); - public TabsAndPropertiesMapper(ICultureDictionary cultureDictionary, ILocalizedTextService localizedTextService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider) - : base(cultureDictionary, localizedTextService) - { - _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider ?? throw new ArgumentNullException(nameof(contentTypeBaseServiceProvider)); - } + // Property groups only exist on the content type (as it's only used for display purposes) + IContentTypeComposition contentType = _contentTypeBaseServiceProvider.GetContentTypeOf(source); - public virtual IEnumerable> Map(TSource source, MapperContext context) + // Merge the groups, as compositions can introduce duplicate aliases + PropertyGroup[] groups = contentType?.CompositionPropertyGroups.OrderBy(x => x.SortOrder).ToArray(); + var parentAliases = groups?.Select(x => x.GetParentAlias()).Distinct().ToArray(); + if (groups is not null) { - var tabs = new List>(); + foreach (IGrouping groupsByAlias in groups.GroupBy(x => x.Alias)) + { + var properties = new List(); - // Property groups only exist on the content type (as it's only used for display purposes) - var contentType = _contentTypeBaseServiceProvider.GetContentTypeOf(source); + // Merge properties for groups with the same alias + foreach (PropertyGroup group in groupsByAlias) + { + IEnumerable groupProperties = source.GetPropertiesForGroup(group) + .Where(x => IgnoreProperties.Contains(x.Alias) == false); // Skip ignored properties - // Merge the groups, as compositions can introduce duplicate aliases - var groups = contentType?.CompositionPropertyGroups.OrderBy(x => x.SortOrder).ToArray(); - var parentAliases = groups?.Select(x => x.GetParentAlias()).Distinct().ToArray(); - if (groups is not null) - { - foreach (var groupsByAlias in groups.GroupBy(x => x.Alias)) + properties.AddRange(groupProperties); + } + + if (properties.Count == 0 && (!parentAliases?.Contains(groupsByAlias.Key) ?? false)) { - var properties = new List(); - - // Merge properties for groups with the same alias - foreach (var group in groupsByAlias) - { - var groupProperties = source.GetPropertiesForGroup(group) - .Where(x => IgnoreProperties.Contains(x.Alias) == false); // Skip ignored properties - - properties.AddRange(groupProperties); - } - - if (properties.Count == 0 && (!parentAliases?.Contains(groupsByAlias.Key) ?? false)) - continue; - - // Map the properties - var mappedProperties = MapProperties(source, properties, context); - - // Add the tab (the first is closest to the content type, e.g. local, then direct composition) - var g = groupsByAlias.First(); - - tabs.Add(new Tab - { - Id = g.Id, - Key = g.Key, - Type = g.Type.ToString(), - Alias = g.Alias, - Label = LocalizedTextService.UmbracoDictionaryTranslate(CultureDictionary, g.Name), - Properties = mappedProperties - }); + continue; } - } - MapGenericProperties(source, tabs, context); + // Map the properties + List mappedProperties = MapProperties(source, properties, context); - // Activate the first tab, if any - if (tabs.Count > 0) - tabs[0].IsActive = true; + // Add the tab (the first is closest to the content type, e.g. local, then direct composition) + PropertyGroup g = groupsByAlias.First(); - return tabs; + tabs.Add(new Tab + { + Id = g.Id, + Key = g.Key, + Type = g.Type.ToString(), + Alias = g.Alias, + Label = LocalizedTextService.UmbracoDictionaryTranslate(CultureDictionary, g.Name), + Properties = mappedProperties + }); + } } + + MapGenericProperties(source, tabs, context); + + // Activate the first tab, if any + if (tabs.Count > 0) + { + tabs[0].IsActive = true; + } + + return tabs; } } diff --git a/src/Umbraco.Core/Models/Mapping/TagMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/TagMapDefinition.cs index 7bd436fa5448..85d8e5cad671 100644 --- a/src/Umbraco.Core/Models/Mapping/TagMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/TagMapDefinition.cs @@ -1,21 +1,18 @@ using Umbraco.Cms.Core.Mapping; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +public class TagMapDefinition : IMapDefinition { - public class TagMapDefinition : IMapDefinition - { - public void DefineMaps(IUmbracoMapper mapper) - { - mapper.Define((source, context) => new TagModel(), Map); - } + public void DefineMaps(IUmbracoMapper mapper) => + mapper.Define((source, context) => new TagModel(), Map); - // Umbraco.Code.MapAll - private static void Map(ITag source, TagModel target, MapperContext context) - { - target.Id = source.Id; - target.Text = source.Text; - target.Group = source.Group; - target.NodeCount = source.NodeCount; - } + // Umbraco.Code.MapAll + private static void Map(ITag source, TagModel target, MapperContext context) + { + target.Id = source.Id; + target.Text = source.Text; + target.Group = source.Group; + target.NodeCount = source.NodeCount; } } diff --git a/src/Umbraco.Core/Models/Mapping/TemplateMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/TemplateMapDefinition.cs index 624868f3f435..176e07753894 100644 --- a/src/Umbraco.Core/Models/Mapping/TemplateMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/TemplateMapDefinition.cs @@ -2,49 +2,46 @@ using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Strings; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +public class TemplateMapDefinition : IMapDefinition { - public class TemplateMapDefinition : IMapDefinition - { - private readonly IShortStringHelper _shortStringHelper; + private readonly IShortStringHelper _shortStringHelper; - public TemplateMapDefinition(IShortStringHelper shortStringHelper) - { - _shortStringHelper = shortStringHelper; - } + public TemplateMapDefinition(IShortStringHelper shortStringHelper) => _shortStringHelper = shortStringHelper; - public void DefineMaps(IUmbracoMapper mapper) - { - mapper.Define((source, context) => new TemplateDisplay(), Map); - mapper.Define((source, context) => new Template(_shortStringHelper, source.Name, source.Alias), Map); - } + public void DefineMaps(IUmbracoMapper mapper) + { + mapper.Define((source, context) => new TemplateDisplay(), Map); + mapper.Define( + (source, context) => new Template(_shortStringHelper, source.Name, source.Alias), Map); + } - // Umbraco.Code.MapAll - private static void Map(ITemplate source, TemplateDisplay target, MapperContext context) - { - target.Id = source.Id; - target.Name = source.Name; - target.Alias = source.Alias; - target.Key = source.Key; - target.Content = source.Content; - target.Path = source.Path; - target.VirtualPath = source.VirtualPath; - target.MasterTemplateAlias = source.MasterTemplateAlias; - target.IsMasterTemplate = source.IsMasterTemplate; - } + // Umbraco.Code.MapAll + private static void Map(ITemplate source, TemplateDisplay target, MapperContext context) + { + target.Id = source.Id; + target.Name = source.Name; + target.Alias = source.Alias; + target.Key = source.Key; + target.Content = source.Content; + target.Path = source.Path; + target.VirtualPath = source.VirtualPath; + target.MasterTemplateAlias = source.MasterTemplateAlias; + target.IsMasterTemplate = source.IsMasterTemplate; + } - // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate - // Umbraco.Code.MapAll -Path -VirtualPath -MasterTemplateId -IsMasterTemplate - // Umbraco.Code.MapAll -GetFileContent - private static void Map(TemplateDisplay source, ITemplate target, MapperContext context) - { - // don't need to worry about mapping MasterTemplateAlias here; - // the template controller handles any changes made to the master template - target.Name = source.Name; - target.Alias = source.Alias; - target.Content = source.Content; - target.Id = source.Id; - target.Key = source.Key; - } + // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate + // Umbraco.Code.MapAll -Path -VirtualPath -MasterTemplateId -IsMasterTemplate + // Umbraco.Code.MapAll -GetFileContent + private static void Map(TemplateDisplay source, ITemplate target, MapperContext context) + { + // don't need to worry about mapping MasterTemplateAlias here; + // the template controller handles any changes made to the master template + target.Name = source.Name; + target.Alias = source.Alias; + target.Content = source.Content; + target.Id = source.Id; + target.Key = source.Key; } } diff --git a/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs index a2c3fa7f282c..a030eb9d6061 100644 --- a/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; +using System.Globalization; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Actions; using Umbraco.Cms.Core.Cache; @@ -16,449 +13,502 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; +using UserProfile = Umbraco.Cms.Core.Models.ContentEditing.UserProfile; -namespace Umbraco.Cms.Core.Models.Mapping +namespace Umbraco.Cms.Core.Models.Mapping; + +public class UserMapDefinition : IMapDefinition { - public class UserMapDefinition : IMapDefinition + private readonly ActionCollection _actions; + private readonly AppCaches _appCaches; + private readonly IEntityService _entityService; + private readonly GlobalSettings _globalSettings; + private readonly IImageUrlGenerator _imageUrlGenerator; + private readonly MediaFileManager _mediaFileManager; + private readonly ISectionService _sectionService; + private readonly IShortStringHelper _shortStringHelper; + private readonly ILocalizedTextService _textService; + private readonly IUserService _userService; + + public UserMapDefinition(ILocalizedTextService textService, IUserService userService, IEntityService entityService, + ISectionService sectionService, + AppCaches appCaches, ActionCollection actions, IOptions globalSettings, + MediaFileManager mediaFileManager, IShortStringHelper shortStringHelper, + IImageUrlGenerator imageUrlGenerator) { - private readonly ISectionService _sectionService; - private readonly IEntityService _entityService; - private readonly IUserService _userService; - private readonly ILocalizedTextService _textService; - private readonly ActionCollection _actions; - private readonly AppCaches _appCaches; - private readonly GlobalSettings _globalSettings; - private readonly MediaFileManager _mediaFileManager; - private readonly IShortStringHelper _shortStringHelper; - private readonly IImageUrlGenerator _imageUrlGenerator; - - public UserMapDefinition(ILocalizedTextService textService, IUserService userService, IEntityService entityService, ISectionService sectionService, - AppCaches appCaches, ActionCollection actions, IOptions globalSettings, MediaFileManager mediaFileManager, IShortStringHelper shortStringHelper, - IImageUrlGenerator imageUrlGenerator) - { - _sectionService = sectionService; - _entityService = entityService; - _userService = userService; - _textService = textService; - _actions = actions; - _appCaches = appCaches; - _globalSettings = globalSettings.Value; - _mediaFileManager = mediaFileManager; - _shortStringHelper = shortStringHelper; - _imageUrlGenerator = imageUrlGenerator; - } + _sectionService = sectionService; + _entityService = entityService; + _userService = userService; + _textService = textService; + _actions = actions; + _appCaches = appCaches; + _globalSettings = globalSettings.Value; + _mediaFileManager = mediaFileManager; + _shortStringHelper = shortStringHelper; + _imageUrlGenerator = imageUrlGenerator; + } - public void DefineMaps(IUmbracoMapper mapper) + public void DefineMaps(IUmbracoMapper mapper) + { + mapper.Define( + (source, context) => new UserGroup(_shortStringHelper) {CreateDate = DateTime.UtcNow}, Map); + mapper.Define(Map); + mapper.Define((source, context) => new UserProfile(), Map); + mapper.Define((source, context) => new UserGroupBasic(), Map); + mapper.Define((source, context) => new UserGroupBasic(), Map); + mapper.Define((source, context) => new AssignedUserGroupPermissions(), + Map); + mapper.Define((source, context) => new AssignedContentPermissions(), + Map); + mapper.Define((source, context) => new UserGroupDisplay(), Map); + mapper.Define((source, context) => new UserBasic(), Map); + mapper.Define((source, context) => new UserDetail(), Map); + + // used for merging existing UserSave to an existing IUser instance - this will not create an IUser instance! + mapper.Define(Map); + + // important! Currently we are never mapping to multiple UserDisplay objects but if we start doing that + // this will cause an N+1 and we'll need to change how this works. + mapper.Define((source, context) => new UserDisplay(), Map); + } + + // mappers + + private static void Map(UserGroupSave source, IUserGroup target, MapperContext context) + { + if (!(target is UserGroup ttarget)) { - mapper.Define((source, context) => new UserGroup(_shortStringHelper) { CreateDate = DateTime.UtcNow }, Map); - mapper.Define(Map); - mapper.Define((source, context) => new ContentEditing.UserProfile(), Map); - mapper.Define((source, context) => new UserGroupBasic(), Map); - mapper.Define((source, context) => new UserGroupBasic(), Map); - mapper.Define((source, context) => new AssignedUserGroupPermissions(), Map); - mapper.Define((source, context) => new AssignedContentPermissions(), Map); - mapper.Define((source, context) => new UserGroupDisplay(), Map); - mapper.Define((source, context) => new UserBasic(), Map); - mapper.Define((source, context) => new UserDetail(), Map); - - // used for merging existing UserSave to an existing IUser instance - this will not create an IUser instance! - mapper.Define(Map); - - // important! Currently we are never mapping to multiple UserDisplay objects but if we start doing that - // this will cause an N+1 and we'll need to change how this works. - mapper.Define((source, context) => new UserDisplay(), Map); + throw new NotSupportedException($"{nameof(target)} must be a UserGroup."); } - // mappers + Map(source, ttarget); + } - private static void Map(UserGroupSave source, IUserGroup target, MapperContext context) + // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate + private static void Map(UserGroupSave source, UserGroup target) + { + target.StartMediaId = source.StartMediaId; + target.StartContentId = source.StartContentId; + target.Icon = source.Icon; + target.Alias = source.Alias; + target.Name = source.Name; + target.Permissions = source.DefaultPermissions; + target.Key = source.Key; + + var id = GetIntId(source.Id); + if (id > 0) { - if (!(target is UserGroup ttarget)) - throw new NotSupportedException($"{nameof(target)} must be a UserGroup."); - Map(source, ttarget); + target.Id = id; } - // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate - private static void Map(UserGroupSave source, UserGroup target) + target.ClearAllowedSections(); + if (source.Sections is not null) { - target.StartMediaId = source.StartMediaId; - target.StartContentId = source.StartContentId; - target.Icon = source.Icon; - target.Alias = source.Alias; - target.Name = source.Name; - target.Permissions = source.DefaultPermissions; - target.Key = source.Key; - - var id = GetIntId(source.Id); - if (id > 0) - target.Id = id; - - target.ClearAllowedSections(); - if (source.Sections is not null) + foreach (var section in source.Sections) { - foreach (var section in source.Sections) - { - target.AddAllowedSection(section); - } + target.AddAllowedSection(section); } - } + } - // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate - // Umbraco.Code.MapAll -Id -TourData -StartContentIds -StartMediaIds -Language -Username - // Umbraco.Code.MapAll -PasswordQuestion -SessionTimeout -EmailConfirmedDate -InvitedDate - // Umbraco.Code.MapAll -SecurityStamp -Avatar -ProviderUserKey -RawPasswordValue - // Umbraco.Code.MapAll -RawPasswordAnswerValue -Comments -IsApproved -IsLockedOut -LastLoginDate - // Umbraco.Code.MapAll -LastPasswordChangeDate -LastLockoutDate -FailedPasswordAttempts - // Umbraco.Code.MapAll -PasswordConfiguration - private void Map(UserInvite source, IUser target, MapperContext context) + // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate + // Umbraco.Code.MapAll -Id -TourData -StartContentIds -StartMediaIds -Language -Username + // Umbraco.Code.MapAll -PasswordQuestion -SessionTimeout -EmailConfirmedDate -InvitedDate + // Umbraco.Code.MapAll -SecurityStamp -Avatar -ProviderUserKey -RawPasswordValue + // Umbraco.Code.MapAll -RawPasswordAnswerValue -Comments -IsApproved -IsLockedOut -LastLoginDate + // Umbraco.Code.MapAll -LastPasswordChangeDate -LastLockoutDate -FailedPasswordAttempts + // Umbraco.Code.MapAll -PasswordConfiguration + private void Map(UserInvite source, IUser target, MapperContext context) + { + target.Email = source.Email; + target.Key = source.Key; + target.Name = source.Name; + target.IsApproved = false; + + target.ClearGroups(); + IEnumerable groups = _userService.GetUserGroupsByAlias(source.UserGroups.ToArray()); + foreach (IUserGroup group in groups) { - target.Email = source.Email; - target.Key = source.Key; - target.Name = source.Name; - target.IsApproved = false; - - target.ClearGroups(); - var groups = _userService.GetUserGroupsByAlias(source.UserGroups.ToArray()); - foreach (var group in groups) - target.AddGroup(group.ToReadOnlyGroup()); + target.AddGroup(group.ToReadOnlyGroup()); } + } - // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate - // Umbraco.Code.MapAll -TourData -SessionTimeout -EmailConfirmedDate -InvitedDate -SecurityStamp -Avatar - // Umbraco.Code.MapAll -ProviderUserKey -RawPasswordValue -RawPasswordAnswerValue -PasswordQuestion -Comments - // Umbraco.Code.MapAll -IsApproved -IsLockedOut -LastLoginDate -LastPasswordChangeDate -LastLockoutDate - // Umbraco.Code.MapAll -FailedPasswordAttempts - // Umbraco.Code.MapAll -PasswordConfiguration - private void Map(UserSave source, IUser target, MapperContext context) + // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate + // Umbraco.Code.MapAll -TourData -SessionTimeout -EmailConfirmedDate -InvitedDate -SecurityStamp -Avatar + // Umbraco.Code.MapAll -ProviderUserKey -RawPasswordValue -RawPasswordAnswerValue -PasswordQuestion -Comments + // Umbraco.Code.MapAll -IsApproved -IsLockedOut -LastLoginDate -LastPasswordChangeDate -LastLockoutDate + // Umbraco.Code.MapAll -FailedPasswordAttempts + // Umbraco.Code.MapAll -PasswordConfiguration + private void Map(UserSave source, IUser target, MapperContext context) + { + target.Name = source.Name; + target.StartContentIds = source.StartContentIds ?? Array.Empty(); + target.StartMediaIds = source.StartMediaIds ?? Array.Empty(); + target.Language = source.Culture; + target.Email = source.Email; + target.Key = source.Key; + target.Username = source.Username; + target.Id = source.Id; + + target.ClearGroups(); + IEnumerable groups = _userService.GetUserGroupsByAlias(source.UserGroups.ToArray()); + foreach (IUserGroup group in groups) { - target.Name = source.Name; - target.StartContentIds = source.StartContentIds ?? Array.Empty(); - target.StartMediaIds = source.StartMediaIds ?? Array.Empty(); - target.Language = source.Culture; - target.Email = source.Email; - target.Key = source.Key; - target.Username = source.Username; - target.Id = source.Id; - - target.ClearGroups(); - var groups = _userService.GetUserGroupsByAlias(source.UserGroups.ToArray()); - foreach (var group in groups) - target.AddGroup(group.ToReadOnlyGroup()); + target.AddGroup(group.ToReadOnlyGroup()); } + } - // Umbraco.Code.MapAll - private static void Map(IProfile source, ContentEditing.UserProfile target, MapperContext context) - { - target.Name = source.Name; - target.UserId = source.Id; - } + // Umbraco.Code.MapAll + private static void Map(IProfile source, UserProfile target, MapperContext context) + { + target.Name = source.Name; + target.UserId = source.Id; + } - // Umbraco.Code.MapAll -ContentStartNode -UserCount -MediaStartNode -Key -Sections - // Umbraco.Code.MapAll -Notifications -Udi -Trashed -AdditionalData -IsSystemUserGroup - private void Map(IReadOnlyUserGroup source, UserGroupBasic target, MapperContext context) - { - target.Alias = source.Alias; - target.Icon = source.Icon; - target.Id = source.Id; - target.Name = source.Name; - target.ParentId = -1; - target.Path = "-1," + source.Id; - target.IsSystemUserGroup = source.IsSystemUserGroup(); - - MapUserGroupBasic(target, source.AllowedSections, source.StartContentId, source.StartMediaId, context); - } + // Umbraco.Code.MapAll -ContentStartNode -UserCount -MediaStartNode -Key -Sections + // Umbraco.Code.MapAll -Notifications -Udi -Trashed -AdditionalData -IsSystemUserGroup + private void Map(IReadOnlyUserGroup source, UserGroupBasic target, MapperContext context) + { + target.Alias = source.Alias; + target.Icon = source.Icon; + target.Id = source.Id; + target.Name = source.Name; + target.ParentId = -1; + target.Path = "-1," + source.Id; + target.IsSystemUserGroup = source.IsSystemUserGroup(); + + MapUserGroupBasic(target, source.AllowedSections, source.StartContentId, source.StartMediaId, context); + } + + // Umbraco.Code.MapAll -ContentStartNode -MediaStartNode -Sections -Notifications + // Umbraco.Code.MapAll -Udi -Trashed -AdditionalData -IsSystemUserGroup + private void Map(IUserGroup source, UserGroupBasic target, MapperContext context) + { + target.Alias = source.Alias; + target.Icon = source.Icon; + target.Id = source.Id; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = -1; + target.Path = "-1," + source.Id; + target.UserCount = source.UserCount; + target.IsSystemUserGroup = source.IsSystemUserGroup(); + + MapUserGroupBasic(target, source.AllowedSections, source.StartContentId, source.StartMediaId, context); + } - // Umbraco.Code.MapAll -ContentStartNode -MediaStartNode -Sections -Notifications - // Umbraco.Code.MapAll -Udi -Trashed -AdditionalData -IsSystemUserGroup - private void Map(IUserGroup source, UserGroupBasic target, MapperContext context) + // Umbraco.Code.MapAll -Udi -Trashed -AdditionalData -AssignedPermissions + private void Map(IUserGroup source, AssignedUserGroupPermissions target, MapperContext context) + { + target.Id = source.Id; + target.Alias = source.Alias; + target.Icon = source.Icon; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = -1; + target.Path = "-1," + source.Id; + + target.DefaultPermissions = MapUserGroupDefaultPermissions(source); + + if (target.Icon.IsNullOrWhiteSpace()) { - target.Alias = source.Alias; - target.Icon = source.Icon; - target.Id = source.Id; - target.Key = source.Key; - target.Name = source.Name; - target.ParentId = -1; - target.Path = "-1," + source.Id; - target.UserCount = source.UserCount; - target.IsSystemUserGroup = source.IsSystemUserGroup(); - - MapUserGroupBasic(target, source.AllowedSections, source.StartContentId, source.StartMediaId, context); + target.Icon = Constants.Icons.UserGroup; } + } - // Umbraco.Code.MapAll -Udi -Trashed -AdditionalData -AssignedPermissions - private void Map(IUserGroup source, AssignedUserGroupPermissions target, MapperContext context) + // Umbraco.Code.MapAll -Trashed -Alias -AssignedPermissions + private static void Map(EntitySlim source, AssignedContentPermissions target, MapperContext context) + { + target.Icon = MapContentTypeIcon(source); + target.Id = source.Id; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = source.ParentId; + target.Path = source.Path; + target.Udi = Udi.Create(ObjectTypes.GetUdiType(source.NodeObjectType), source.Key); + + if (source.NodeObjectType == Constants.ObjectTypes.Member && target.Icon.IsNullOrWhiteSpace()) { - target.Id = source.Id; - target.Alias = source.Alias; - target.Icon = source.Icon; - target.Key = source.Key; - target.Name = source.Name; - target.ParentId = -1; - target.Path = "-1," + source.Id; - - target.DefaultPermissions = MapUserGroupDefaultPermissions(source); - - if (target.Icon.IsNullOrWhiteSpace()) - target.Icon = Constants.Icons.UserGroup; + target.Icon = Constants.Icons.Member; } + } - // Umbraco.Code.MapAll -Trashed -Alias -AssignedPermissions - private static void Map(EntitySlim source, AssignedContentPermissions target, MapperContext context) + // Umbraco.Code.MapAll -ContentStartNode -MediaStartNode -Sections -Notifications -Udi + // Umbraco.Code.MapAll -Trashed -AdditionalData -Users -AssignedPermissions + private void Map(IUserGroup source, UserGroupDisplay target, MapperContext context) + { + target.Alias = source.Alias; + target.DefaultPermissions = MapUserGroupDefaultPermissions(source); + target.Icon = source.Icon; + target.Id = source.Id; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = -1; + target.Path = "-1," + source.Id; + target.UserCount = source.UserCount; + target.IsSystemUserGroup = source.IsSystemUserGroup(); + + MapUserGroupBasic(target, source.AllowedSections, source.StartContentId, source.StartMediaId, context); + + //Important! Currently we are never mapping to multiple UserGroupDisplay objects but if we start doing that + // this will cause an N+1 and we'll need to change how this works. + IEnumerable users = _userService.GetAllInGroup(source.Id); + target.Users = context.MapEnumerable(users).WhereNotNull(); + + //Deal with assigned permissions: + + var allContentPermissions = _userService.GetPermissions(source, true) + .ToDictionary(x => x.EntityId, x => x); + + IEntitySlim[] contentEntities; + if (allContentPermissions.Keys.Count == 0) { - target.Icon = MapContentTypeIcon(source); - target.Id = source.Id; - target.Key = source.Key; - target.Name = source.Name; - target.ParentId = source.ParentId; - target.Path = source.Path; - target.Udi = Udi.Create(ObjectTypes.GetUdiType(source.NodeObjectType), source.Key); - - if (source.NodeObjectType == Constants.ObjectTypes.Member && target.Icon.IsNullOrWhiteSpace()) - target.Icon = Constants.Icons.Member; + contentEntities = Array.Empty(); } - - // Umbraco.Code.MapAll -ContentStartNode -MediaStartNode -Sections -Notifications -Udi - // Umbraco.Code.MapAll -Trashed -AdditionalData -Users -AssignedPermissions - private void Map(IUserGroup source, UserGroupDisplay target, MapperContext context) + else { - target.Alias = source.Alias; - target.DefaultPermissions = MapUserGroupDefaultPermissions(source); - target.Icon = source.Icon; - target.Id = source.Id; - target.Key = source.Key; - target.Name = source.Name; - target.ParentId = -1; - target.Path = "-1," + source.Id; - target.UserCount = source.UserCount; - target.IsSystemUserGroup = source.IsSystemUserGroup(); - - MapUserGroupBasic(target, source.AllowedSections, source.StartContentId, source.StartMediaId, context); - - //Important! Currently we are never mapping to multiple UserGroupDisplay objects but if we start doing that - // this will cause an N+1 and we'll need to change how this works. - var users = _userService.GetAllInGroup(source.Id); - target.Users = context.MapEnumerable(users).WhereNotNull(); - - //Deal with assigned permissions: - - var allContentPermissions = _userService.GetPermissions(source, true) - .ToDictionary(x => x.EntityId, x => x); - - IEntitySlim[] contentEntities; - if (allContentPermissions.Keys.Count == 0) + // a group can end up with way more than 2000 assigned permissions, + // so we need to break them into groups in order to avoid breaking + // the entity service due to too many Sql parameters. + + var list = new List(); + foreach (IEnumerable idGroup in allContentPermissions.Keys.InGroupsOf(Constants.Sql.MaxParameterCount)) { - contentEntities = Array.Empty(); + list.AddRange(_entityService.GetAll(UmbracoObjectTypes.Document, idGroup.ToArray())); } - else + + contentEntities = list.ToArray(); + } + + var allAssignedPermissions = new List(); + foreach (IEntitySlim entity in contentEntities) + { + EntityPermission contentPermissions = allContentPermissions[entity.Id]; + + AssignedContentPermissions assignedContentPermissions = context.Map(entity); + if (assignedContentPermissions is null) { - // a group can end up with way more than 2000 assigned permissions, - // so we need to break them into groups in order to avoid breaking - // the entity service due to too many Sql parameters. - - var list = new List(); - foreach (var idGroup in allContentPermissions.Keys.InGroupsOf(Constants.Sql.MaxParameterCount)) - list.AddRange(_entityService.GetAll(UmbracoObjectTypes.Document, idGroup.ToArray())); - contentEntities = list.ToArray(); + continue; } - var allAssignedPermissions = new List(); - foreach (var entity in contentEntities) + assignedContentPermissions.AssignedPermissions = + AssignedUserGroupPermissions.ClonePermissions(target.DefaultPermissions); + + //since there is custom permissions assigned to this node for this group, we need to clear all of the default permissions + //and we'll re-check it if it's one of the explicitly assigned ones + foreach (Permission permission in assignedContentPermissions.AssignedPermissions.SelectMany(x => x.Value)) { - var contentPermissions = allContentPermissions[entity.Id]; - - var assignedContentPermissions = context.Map(entity); - if (assignedContentPermissions is null) - { - continue; - } - assignedContentPermissions.AssignedPermissions = AssignedUserGroupPermissions.ClonePermissions(target.DefaultPermissions); - - //since there is custom permissions assigned to this node for this group, we need to clear all of the default permissions - //and we'll re-check it if it's one of the explicitly assigned ones - foreach (var permission in assignedContentPermissions.AssignedPermissions.SelectMany(x => x.Value)) - { - permission.Checked = false; - permission.Checked = contentPermissions.AssignedPermissions.Contains(permission.PermissionCode, StringComparer.InvariantCulture); - } - - allAssignedPermissions.Add(assignedContentPermissions); + permission.Checked = false; + permission.Checked = + contentPermissions.AssignedPermissions.Contains(permission.PermissionCode, + StringComparer.InvariantCulture); } - target.AssignedPermissions = allAssignedPermissions; + allAssignedPermissions.Add(assignedContentPermissions); } - // Umbraco.Code.MapAll -Notifications -Udi -Icon -IsCurrentUser -Trashed -ResetPasswordValue - // Umbraco.Code.MapAll -Alias -AdditionalData - private void Map(IUser source, UserDisplay target, MapperContext context) + target.AssignedPermissions = allAssignedPermissions; + } + + // Umbraco.Code.MapAll -Notifications -Udi -Icon -IsCurrentUser -Trashed -ResetPasswordValue + // Umbraco.Code.MapAll -Alias -AdditionalData + private void Map(IUser source, UserDisplay target, MapperContext context) + { + target.AvailableCultures = _textService.GetSupportedCultures().ToDictionary(x => x.Name, x => x.DisplayName); + target.Avatars = source.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileManager, _imageUrlGenerator); + target.CalculatedStartContentIds = + GetStartNodes(source.CalculateContentStartNodeIds(_entityService, _appCaches), UmbracoObjectTypes.Document, + "content", "contentRoot", context); + target.CalculatedStartMediaIds = GetStartNodes(source.CalculateMediaStartNodeIds(_entityService, _appCaches), + UmbracoObjectTypes.Media, "media", "mediaRoot", context); + target.CreateDate = source.CreateDate; + target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString(); + target.Email = source.Email; + target.EmailHash = source.Email?.ToLowerInvariant().Trim().GenerateHash(); + target.FailedPasswordAttempts = source.FailedPasswordAttempts; + target.Id = source.Id; + target.Key = source.Key; + target.LastLockoutDate = source.LastLockoutDate; + target.LastLoginDate = source.LastLoginDate == default(DateTime) ? null : source.LastLoginDate; + target.LastPasswordChangeDate = source.LastPasswordChangeDate; + target.Name = source.Name; + target.Navigation = CreateUserEditorNavigation(); + target.ParentId = -1; + target.Path = "-1," + source.Id; + target.StartContentIds = GetStartNodes(source.StartContentIds?.ToArray(), UmbracoObjectTypes.Document, + "content", "contentRoot", context); + target.StartMediaIds = GetStartNodes(source.StartMediaIds?.ToArray(), UmbracoObjectTypes.Media, "media", + "mediaRoot", context); + target.UpdateDate = source.UpdateDate; + target.UserGroups = context.MapEnumerable(source.Groups).WhereNotNull(); + target.Username = source.Username; + target.UserState = source.UserState; + } + + // Umbraco.Code.MapAll -Notifications -IsCurrentUser -Udi -Icon -Trashed -Alias -AdditionalData + private void Map(IUser source, UserBasic target, MapperContext context) + { + //Loading in the user avatar's requires an external request if they don't have a local file avatar, this means that initial load of paging may incur a cost + //Alternatively, if this is annoying the back office UI would need to be updated to request the avatars for the list of users separately so it doesn't look + //like the load time is waiting. + target.Avatars = source.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileManager, _imageUrlGenerator); + target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString(); + target.Email = source.Email; + target.EmailHash = source.Email?.ToLowerInvariant().Trim().GenerateHash(); + target.Id = source.Id; + target.Key = source.Key; + target.LastLoginDate = source.LastLoginDate == default ? null : source.LastLoginDate; + target.Name = source.Name; + target.ParentId = -1; + target.Path = "-1," + source.Id; + target.UserGroups = context.MapEnumerable(source.Groups).WhereNotNull(); + target.Username = source.Username; + target.UserState = source.UserState; + } + + // Umbraco.Code.MapAll -SecondsUntilTimeout + private void Map(IUser source, UserDetail target, MapperContext context) + { + target.AllowedSections = source.AllowedSections; + target.Avatars = source.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileManager, _imageUrlGenerator); + target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString(); + target.Email = source.Email; + target.EmailHash = source.Email?.ToLowerInvariant().Trim().GenerateHash(); + target.Name = source.Name; + target.StartContentIds = source.CalculateContentStartNodeIds(_entityService, _appCaches); + target.StartMediaIds = source.CalculateMediaStartNodeIds(_entityService, _appCaches); + target.UserId = source.Id; + + //we need to map the legacy UserType + //the best we can do here is to return the user's first user group as a IUserType object + //but we should attempt to return any group that is the built in ones first + target.UserGroups = source.Groups.Select(x => x.Alias).ToArray(); + } + + // helpers + + private void MapUserGroupBasic(UserGroupBasic target, IEnumerable sourceAllowedSections, + int? sourceStartContentId, int? sourceStartMediaId, MapperContext context) + { + IEnumerable allSections = _sectionService.GetSections(); + target.Sections = context + .MapEnumerable(allSections.Where(x => sourceAllowedSections.Contains(x.Alias))) + .WhereNotNull(); + + if (sourceStartMediaId > 0) { - target.AvailableCultures = _textService.GetSupportedCultures().ToDictionary(x => x.Name, x => x.DisplayName); - target.Avatars = source.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileManager, _imageUrlGenerator); - target.CalculatedStartContentIds = GetStartNodes(source.CalculateContentStartNodeIds(_entityService,_appCaches), UmbracoObjectTypes.Document, "content","contentRoot", context); - target.CalculatedStartMediaIds = GetStartNodes(source.CalculateMediaStartNodeIds(_entityService, _appCaches), UmbracoObjectTypes.Media, "media","mediaRoot", context); - target.CreateDate = source.CreateDate; - target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString(); - target.Email = source.Email; - target.EmailHash = source.Email?.ToLowerInvariant().Trim().GenerateHash(); - target.FailedPasswordAttempts = source.FailedPasswordAttempts; - target.Id = source.Id; - target.Key = source.Key; - target.LastLockoutDate = source.LastLockoutDate; - target.LastLoginDate = source.LastLoginDate == default(DateTime) ? null : (DateTime?)source.LastLoginDate; - target.LastPasswordChangeDate = source.LastPasswordChangeDate; - target.Name = source.Name; - target.Navigation = CreateUserEditorNavigation(); - target.ParentId = -1; - target.Path = "-1," + source.Id; - target.StartContentIds = GetStartNodes(source.StartContentIds?.ToArray(), UmbracoObjectTypes.Document, "content","contentRoot", context); - target.StartMediaIds = GetStartNodes(source.StartMediaIds?.ToArray(), UmbracoObjectTypes.Media, "media","mediaRoot", context); - target.UpdateDate = source.UpdateDate; - target.UserGroups = context.MapEnumerable(source.Groups).WhereNotNull(); - target.Username = source.Username; - target.UserState = source.UserState; + target.MediaStartNode = + context.Map(_entityService.Get(sourceStartMediaId.Value, UmbracoObjectTypes.Media)); } - - // Umbraco.Code.MapAll -Notifications -IsCurrentUser -Udi -Icon -Trashed -Alias -AdditionalData - private void Map(IUser source, UserBasic target, MapperContext context) + else if (sourceStartMediaId == -1) { - //Loading in the user avatar's requires an external request if they don't have a local file avatar, this means that initial load of paging may incur a cost - //Alternatively, if this is annoying the back office UI would need to be updated to request the avatars for the list of users separately so it doesn't look - //like the load time is waiting. - target.Avatars = source.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileManager, _imageUrlGenerator); - target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString(); - target.Email = source.Email; - target.EmailHash = source.Email?.ToLowerInvariant().Trim().GenerateHash(); - target.Id = source.Id; - target.Key = source.Key; - target.LastLoginDate = source.LastLoginDate == default ? null : (DateTime?)source.LastLoginDate; - target.Name = source.Name; - target.ParentId = -1; - target.Path = "-1," + source.Id; - target.UserGroups = context.MapEnumerable(source.Groups).WhereNotNull(); - target.Username = source.Username; - target.UserState = source.UserState; + target.MediaStartNode = CreateRootNode(_textService.Localize("media", "mediaRoot")); } - // Umbraco.Code.MapAll -SecondsUntilTimeout - private void Map(IUser source, UserDetail target, MapperContext context) + if (sourceStartContentId > 0) { - target.AllowedSections = source.AllowedSections; - target.Avatars = source.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileManager, _imageUrlGenerator); - target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString(); - target.Email = source.Email; - target.EmailHash = source.Email?.ToLowerInvariant().Trim().GenerateHash(); - target.Name = source.Name; - target.StartContentIds = source.CalculateContentStartNodeIds(_entityService, _appCaches); - target.StartMediaIds = source.CalculateMediaStartNodeIds(_entityService, _appCaches); - target.UserId = source.Id; - - //we need to map the legacy UserType - //the best we can do here is to return the user's first user group as a IUserType object - //but we should attempt to return any group that is the built in ones first - target.UserGroups = source.Groups.Select(x => x.Alias).ToArray(); + target.ContentStartNode = + context.Map(_entityService.Get(sourceStartContentId.Value, UmbracoObjectTypes.Document)); } - - // helpers - - private void MapUserGroupBasic(UserGroupBasic target, IEnumerable sourceAllowedSections, int? sourceStartContentId, int? sourceStartMediaId, MapperContext context) + else if (sourceStartContentId == -1) { - var allSections = _sectionService.GetSections(); - target.Sections = context.MapEnumerable(allSections.Where(x => sourceAllowedSections.Contains(x.Alias))).WhereNotNull(); - - if (sourceStartMediaId > 0) - target.MediaStartNode = context.Map(_entityService.Get(sourceStartMediaId.Value, UmbracoObjectTypes.Media)); - else if (sourceStartMediaId == -1) - target.MediaStartNode = CreateRootNode(_textService.Localize("media", "mediaRoot")); - - if (sourceStartContentId > 0) - target.ContentStartNode = context.Map(_entityService.Get(sourceStartContentId.Value, UmbracoObjectTypes.Document)); - else if (sourceStartContentId == -1) - target.ContentStartNode = CreateRootNode(_textService.Localize("content", "contentRoot")); - - if (target.Icon.IsNullOrWhiteSpace()) - target.Icon = Constants.Icons.UserGroup; + target.ContentStartNode = CreateRootNode(_textService.Localize("content", "contentRoot")); } - private IDictionary> MapUserGroupDefaultPermissions(IUserGroup source) + if (target.Icon.IsNullOrWhiteSpace()) { - Permission GetPermission(IAction action) - => new Permission - { - Category = action.Category.IsNullOrWhiteSpace() - ? _textService.Localize("actionCategories",Constants.Conventions.PermissionCategories.OtherCategory) - : _textService.Localize("actionCategories", action.Category), - Name = _textService.Localize("actions", action.Alias), - Description = _textService.Localize("actionDescriptions", action.Alias), - Icon = action.Icon, - Checked = source.Permissions != null && source.Permissions.Contains(action.Letter.ToString(CultureInfo.InvariantCulture)), - PermissionCode = action.Letter.ToString(CultureInfo.InvariantCulture) - }; - - return _actions - .Where(x => x.CanBePermissionAssigned) - .Select(GetPermission) - .GroupBy(x => x.Category) - .ToDictionary(x => x.Key, x => (IEnumerable)x.ToArray()); + target.Icon = Constants.Icons.UserGroup; } + } - private static string? MapContentTypeIcon(IEntitySlim entity) - => entity is IContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null; - - private IEnumerable GetStartNodes(int[]? startNodeIds, UmbracoObjectTypes objectType, string localizedArea,string localizedAlias, MapperContext context) + private IDictionary> MapUserGroupDefaultPermissions(IUserGroup source) + { + Permission GetPermission(IAction action) { - if (startNodeIds is null || startNodeIds.Length <= 0) - return Enumerable.Empty(); + return new() + { + Category = action.Category.IsNullOrWhiteSpace() + ? _textService.Localize("actionCategories", + Constants.Conventions.PermissionCategories.OtherCategory) + : _textService.Localize("actionCategories", action.Category), + Name = _textService.Localize("actions", action.Alias), + Description = _textService.Localize("actionDescriptions", action.Alias), + Icon = action.Icon, + Checked = source.Permissions != null && + source.Permissions.Contains(action.Letter.ToString(CultureInfo.InvariantCulture)), + PermissionCode = action.Letter.ToString(CultureInfo.InvariantCulture) + }; + } - var startNodes = new List(); - if (startNodeIds.Contains(-1)) - startNodes.Add(CreateRootNode(_textService.Localize(localizedArea, localizedAlias))); + return _actions + .Where(x => x.CanBePermissionAssigned) + .Select(GetPermission) + .GroupBy(x => x.Category) + .ToDictionary(x => x.Key, x => (IEnumerable)x.ToArray()); + } + + private static string? MapContentTypeIcon(IEntitySlim entity) + => entity is IContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null; - var mediaItems = _entityService.GetAll(objectType, startNodeIds); - startNodes.AddRange(context.MapEnumerable(mediaItems).WhereNotNull()); - return startNodes; + private IEnumerable GetStartNodes(int[]? startNodeIds, UmbracoObjectTypes objectType, + string localizedArea, string localizedAlias, MapperContext context) + { + if (startNodeIds is null || startNodeIds.Length <= 0) + { + return Enumerable.Empty(); } - private IEnumerable CreateUserEditorNavigation() + var startNodes = new List(); + if (startNodeIds.Contains(-1)) { - return new[] - { - new EditorNavigation - { - Active = true, - Alias = "details", - Icon = "icon-umb-users", - Name = _textService.Localize("general","user"), - View = "views/users/views/user/details.html" - } - }; + startNodes.Add(CreateRootNode(_textService.Localize(localizedArea, localizedAlias))); } - private static int GetIntId(object? id) + IEnumerable mediaItems = _entityService.GetAll(objectType, startNodeIds); + startNodes.AddRange(context.MapEnumerable(mediaItems).WhereNotNull()); + return startNodes; + } + + private IEnumerable CreateUserEditorNavigation() => + new[] { - if (id is string strId && int.TryParse(strId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var asInt)) + new EditorNavigation { - return asInt; + Active = true, + Alias = "details", + Icon = "icon-umb-users", + Name = _textService.Localize("general", "user"), + View = "views/users/views/user/details.html" } - var result = id.TryConvertTo(); - if (result.Success == false) - { - throw new InvalidOperationException( - "Cannot convert the profile to a " + typeof(UserDetail).Name + " object since the id is not an integer"); - } - return result.Result; + }; + + private static int GetIntId(object? id) + { + if (id is string strId && + int.TryParse(strId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var asInt)) + { + return asInt; } - private EntityBasic CreateRootNode(string name) + Attempt result = id.TryConvertTo(); + if (result.Success == false) { - return new EntityBasic - { - Name = name, - Path = "-1", - Icon = "icon-folder", - Id = -1, - Trashed = false, - ParentId = -1 - }; + throw new InvalidOperationException( + "Cannot convert the profile to a " + typeof(UserDetail).Name + + " object since the id is not an integer"); } + + return result.Result; } + + private EntityBasic CreateRootNode(string name) => + new EntityBasic + { + Name = name, + Path = "-1", + Icon = "icon-folder", + Id = -1, + Trashed = false, + ParentId = -1 + }; } diff --git a/src/Umbraco.Core/Models/Media.cs b/src/Umbraco.Core/Models/Media.cs index 926fe2ef0966..d0cf05b8b982 100644 --- a/src/Umbraco.Core/Models/Media.cs +++ b/src/Umbraco.Core/Models/Media.cs @@ -1,84 +1,87 @@ -using System; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a Media object +/// +[Serializable] +[DataContract(IsReference = true)] +public class Media : ContentBase, IMedia { /// - /// Represents a Media object + /// Constructor for creating a Media object /// - [Serializable] - [DataContract(IsReference = true)] - public class Media : ContentBase, IMedia + /// name of the Media object + /// Parent object + /// MediaType for the current Media object + public Media(string? name, IMedia? parent, IMediaType mediaType) + : this(name, parent, mediaType, new PropertyCollection()) { - /// - /// Constructor for creating a Media object - /// - /// name of the Media object - /// Parent object - /// MediaType for the current Media object - public Media(string? name, IMedia? parent, IMediaType mediaType) - : this(name, parent, mediaType, new PropertyCollection()) - { } + } - /// - /// Constructor for creating a Media object - /// - /// name of the Media object - /// Parent object - /// MediaType for the current Media object - /// Collection of properties - public Media(string? name, IMedia? parent, IMediaType mediaType, IPropertyCollection properties) - : base(name, parent, mediaType, properties) - { } + /// + /// Constructor for creating a Media object + /// + /// name of the Media object + /// Parent object + /// MediaType for the current Media object + /// Collection of properties + public Media(string? name, IMedia? parent, IMediaType mediaType, IPropertyCollection properties) + : base(name, parent, mediaType, properties) + { + } - /// - /// Constructor for creating a Media object - /// - /// name of the Media object - /// Id of the Parent IMedia - /// MediaType for the current Media object - public Media(string? name, int parentId, IMediaType? mediaType) - : this(name, parentId, mediaType, new PropertyCollection()) - { } + /// + /// Constructor for creating a Media object + /// + /// name of the Media object + /// Id of the Parent IMedia + /// MediaType for the current Media object + public Media(string? name, int parentId, IMediaType? mediaType) + : this(name, parentId, mediaType, new PropertyCollection()) + { + } - /// - /// Constructor for creating a Media object - /// - /// Name of the Media object - /// Id of the Parent IMedia - /// MediaType for the current Media object - /// Collection of properties - public Media(string? name, int parentId, IMediaType? mediaType, IPropertyCollection properties) - : base(name, parentId, mediaType, properties) - { } + /// + /// Constructor for creating a Media object + /// + /// Name of the Media object + /// Id of the Parent IMedia + /// MediaType for the current Media object + /// Collection of properties + public Media(string? name, int parentId, IMediaType? mediaType, IPropertyCollection properties) + : base(name, parentId, mediaType, properties) + { + } + + /// + /// Changes the for the current Media object + /// + /// New MediaType for this Media + /// Leaves PropertyTypes intact after change + internal void ChangeContentType(IMediaType mediaType) => ChangeContentType(mediaType, false); + + /// + /// Changes the for the current Media object and removes PropertyTypes, + /// which are not part of the new MediaType. + /// + /// New MediaType for this Media + /// Boolean indicating whether to clear PropertyTypes upon change + internal void ChangeContentType(IMediaType mediaType, bool clearProperties) + { + ChangeContentType(new SimpleContentType(mediaType)); - /// - /// Changes the for the current Media object - /// - /// New MediaType for this Media - /// Leaves PropertyTypes intact after change - internal void ChangeContentType(IMediaType mediaType) + if (clearProperties) { - ChangeContentType(mediaType, false); + Properties.EnsureCleanPropertyTypes(mediaType.CompositionPropertyTypes); } - - /// - /// Changes the for the current Media object and removes PropertyTypes, - /// which are not part of the new MediaType. - /// - /// New MediaType for this Media - /// Boolean indicating whether to clear PropertyTypes upon change - internal void ChangeContentType(IMediaType mediaType, bool clearProperties) + else { - ChangeContentType(new SimpleContentType(mediaType)); - - if (clearProperties) - Properties.EnsureCleanPropertyTypes(mediaType.CompositionPropertyTypes); - else - Properties.EnsurePropertyTypes(mediaType.CompositionPropertyTypes); - - Properties.ClearCollectionChangedEvents(); // be sure not to double add - Properties.CollectionChanged += PropertiesChanged; + Properties.EnsurePropertyTypes(mediaType.CompositionPropertyTypes); } + + Properties.ClearCollectionChangedEvents(); // be sure not to double add + Properties.CollectionChanged += PropertiesChanged; } } diff --git a/src/Umbraco.Core/Models/MediaExtensions.cs b/src/Umbraco.Core/Models/MediaExtensions.cs index 236ec9deb73a..f665bda9c2cb 100644 --- a/src/Umbraco.Core/Models/MediaExtensions.cs +++ b/src/Umbraco.Core/Models/MediaExtensions.cs @@ -1,32 +1,32 @@ -using System.Linq; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class MediaExtensions { - public static class MediaExtensions + /// + /// Gets the URL of a media item. + /// + public static string? GetUrl(this IMedia media, string propertyAlias, + MediaUrlGeneratorCollection mediaUrlGenerators) { - /// - /// Gets the URL of a media item. - /// - public static string? GetUrl(this IMedia media, string propertyAlias, MediaUrlGeneratorCollection mediaUrlGenerators) + if (media.TryGetMediaPath(propertyAlias, mediaUrlGenerators, out var mediaPath)) { - if (media.TryGetMediaPath(propertyAlias, mediaUrlGenerators, out var mediaPath)) - { - return mediaPath; - } - - return string.Empty; + return mediaPath; } - /// - /// Gets the URLs of a media item. - /// - public static string?[] GetUrls(this IMedia media, ContentSettings contentSettings, MediaUrlGeneratorCollection mediaUrlGenerators) - => contentSettings.Imaging.AutoFillImageProperties - .Select(field => media.GetUrl(field.Alias, mediaUrlGenerators)) - .Where(link => string.IsNullOrWhiteSpace(link) == false) - .ToArray(); + return string.Empty; } + + /// + /// Gets the URLs of a media item. + /// + public static string?[] GetUrls(this IMedia media, ContentSettings contentSettings, + MediaUrlGeneratorCollection mediaUrlGenerators) + => contentSettings.Imaging.AutoFillImageProperties + .Select(field => media.GetUrl(field.Alias, mediaUrlGenerators)) + .Where(link => string.IsNullOrWhiteSpace(link) == false) + .ToArray(); } diff --git a/src/Umbraco.Core/Models/MediaType.cs b/src/Umbraco.Core/Models/MediaType.cs index a529dc318999..6e4d7c9eaabc 100644 --- a/src/Umbraco.Core/Models/MediaType.cs +++ b/src/Umbraco.Core/Models/MediaType.cs @@ -1,54 +1,54 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Strings; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents the content type that a object is based on +/// +[Serializable] +[DataContract(IsReference = true)] +public class MediaType : ContentTypeCompositionBase, IMediaType { + public const bool SupportsPublishingConst = false; + + /// + /// Constuctor for creating a MediaType with the parent's id. + /// + /// Only use this for creating MediaTypes at the root (with ParentId -1). + /// + public MediaType(IShortStringHelper shortStringHelper, int parentId) : base(shortStringHelper, parentId) + { + } + /// - /// Represents the content type that a object is based on + /// Constuctor for creating a MediaType with the parent as an inherited type. /// - [Serializable] - [DataContract(IsReference = true)] - public class MediaType : ContentTypeCompositionBase, IMediaType + /// Use this to ensure inheritance from parent. + /// + public MediaType(IShortStringHelper shortStringHelper, IMediaType parent) : this(shortStringHelper, parent, + string.Empty) { - public const bool SupportsPublishingConst = false; - - /// - /// Constuctor for creating a MediaType with the parent's id. - /// - /// Only use this for creating MediaTypes at the root (with ParentId -1). - /// - public MediaType(IShortStringHelper shortStringHelper, int parentId) : base(shortStringHelper, parentId) - { - } - - /// - /// Constuctor for creating a MediaType with the parent as an inherited type. - /// - /// Use this to ensure inheritance from parent. - /// - public MediaType(IShortStringHelper shortStringHelper,IMediaType parent) : this(shortStringHelper, parent, string.Empty) - { - } - - /// - /// Constuctor for creating a MediaType with the parent as an inherited type. - /// - /// Use this to ensure inheritance from parent. - /// - /// - public MediaType(IShortStringHelper shortStringHelper, IMediaType parent, string alias) - : base(shortStringHelper, parent, alias) - { - } - - /// - public override ISimpleContentType ToSimple() => new SimpleContentType(this); - - /// - public override bool SupportsPublishing => SupportsPublishingConst; - - /// - IMediaType IMediaType.DeepCloneWithResetIdentities(string newAlias) => (IMediaType)DeepCloneWithResetIdentities(newAlias); } + + /// + /// Constuctor for creating a MediaType with the parent as an inherited type. + /// + /// Use this to ensure inheritance from parent. + /// + /// + public MediaType(IShortStringHelper shortStringHelper, IMediaType parent, string alias) + : base(shortStringHelper, parent, alias) + { + } + + /// + public override bool SupportsPublishing => SupportsPublishingConst; + + /// + public override ISimpleContentType ToSimple() => new SimpleContentType(this); + + /// + IMediaType IMediaType.DeepCloneWithResetIdentities(string newAlias) => + (IMediaType)DeepCloneWithResetIdentities(newAlias); } diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 4244e1ba44dc..65553f497862 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -1,500 +1,573 @@ -using System; -using System.Collections.Generic; using System.ComponentModel; using System.Runtime.Serialization; using Microsoft.Extensions.Logging; -using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a Member object +/// +[Serializable] +[DataContract(IsReference = true)] +public class Member : ContentBase, IMember { + private IDictionary? _additionalData; + private string _email; + private DateTime? _emailConfirmedDate; + private int _failedPasswordAttempts; + private bool _isApproved; + private bool _isLockedOut; + private DateTime? _lastLockoutDate; + private DateTime? _lastLoginDate; + private DateTime? _lastPasswordChangeDate; + private string? _passwordConfig; + private string? _rawPasswordValue; + private string? _securityStamp; + private string _username; + /// - /// Represents a Member object + /// Initializes a new instance of the class. + /// Constructor for creating an empty Member object /// - [Serializable] - [DataContract(IsReference = true)] - public class Member : ContentBase, IMember + /// ContentType for the current Content object + public Member(IMemberType contentType) + : base("", -1, contentType, new PropertyCollection()) { - private IDictionary? _additionalData; - private string _username; - private string _email; - private string? _rawPasswordValue; - private string? _passwordConfig; - private DateTime? _emailConfirmedDate; - private string? _securityStamp; - private int _failedPasswordAttempts; - private bool _isApproved; - private bool _isLockedOut; - private DateTime? _lastLockoutDate; - private DateTime? _lastLoginDate; - private DateTime? _lastPasswordChangeDate; - - /// - /// Initializes a new instance of the class. - /// Constructor for creating an empty Member object - /// - /// ContentType for the current Content object - public Member(IMemberType contentType) - : base("", -1, contentType, new PropertyCollection()) - { - IsApproved = true; - - // this cannot be null but can be empty - _rawPasswordValue = ""; - _email = ""; - _username = ""; - } + IsApproved = true; - /// - /// Initializes a new instance of the class. - /// Constructor for creating a Member object - /// - /// Name of the content - /// ContentType for the current Content object - public Member(string name, IMemberType contentType) - : base(name, -1, contentType, new PropertyCollection()) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); - - IsApproved = true; - - // this cannot be null but can be empty - _rawPasswordValue = ""; - _email = ""; - _username = ""; - } + // this cannot be null but can be empty + _rawPasswordValue = ""; + _email = ""; + _username = ""; + } - /// - /// Initializes a new instance of the class. - /// Constructor for creating a Member object - /// - /// - /// - /// - /// - public Member(string name, string email, string username, IMemberType contentType, bool isApproved = true) - : base(name, -1, contentType, new PropertyCollection()) + /// + /// Initializes a new instance of the class. + /// Constructor for creating a Member object + /// + /// Name of the content + /// ContentType for the current Content object + public Member(string name, IMemberType contentType) + : base(name, -1, contentType, new PropertyCollection()) + { + if (name == null) { - if (name == null) - throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); - if (email == null) - throw new ArgumentNullException(nameof(email)); - if (string.IsNullOrWhiteSpace(email)) - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(email)); - if (username == null) - throw new ArgumentNullException(nameof(username)); - if (string.IsNullOrWhiteSpace(username)) - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(username)); - - _email = email; - _username = username; - IsApproved = isApproved; - - // this cannot be null but can be empty - _rawPasswordValue = ""; + throw new ArgumentNullException(nameof(name)); } - /// - /// Initializes a new instance of the class. - /// Constructor for creating a Member object - /// - /// - /// - /// - /// - /// - /// - public Member(string name, string email, string username, IMemberType contentType, int userId, bool isApproved = true) - : base(name, -1, contentType, new PropertyCollection()) + if (string.IsNullOrWhiteSpace(name)) { - if (name == null) throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); - if (email == null) throw new ArgumentNullException(nameof(email)); - if (string.IsNullOrWhiteSpace(email)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(email)); - if (username == null) throw new ArgumentNullException(nameof(username)); - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(username)); - - _email = email; - _username = username; - CreatorId = userId; - IsApproved = isApproved; - - //this cannot be null but can be empty - _rawPasswordValue = ""; + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(name)); } - /// - /// Constructor for creating a Member object - /// - /// - /// - /// - /// - /// The password value passed in to this parameter should be the encoded/encrypted/hashed format of the member's password - /// - /// - public Member(string? name, string email, string username, string? rawPasswordValue, IMemberType? contentType) - : base(name, -1, contentType, new PropertyCollection()) - { - _email = email; - _username = username; - _rawPasswordValue = rawPasswordValue; - IsApproved = true; - } + IsApproved = true; - /// - /// Initializes a new instance of the class. - /// Constructor for creating a Member object - /// - /// - /// - /// - /// - /// The password value passed in to this parameter should be the encoded/encrypted/hashed format of the member's password - /// - /// - /// - public Member(string name, string email, string username, string rawPasswordValue, IMemberType contentType, bool isApproved) - : base(name, -1, contentType, new PropertyCollection()) - { - _email = email; - _username = username; - _rawPasswordValue = rawPasswordValue; - IsApproved = isApproved; - } + // this cannot be null but can be empty + _rawPasswordValue = ""; + _email = ""; + _username = ""; + } - /// - /// Constructor for creating a Member object - /// - /// - /// - /// - /// - /// The password value passed in to this parameter should be the encoded/encrypted/hashed format of the member's password - /// - /// - /// - /// - public Member(string name, string email, string username, string rawPasswordValue, IMemberType contentType, bool isApproved, int userId) - : base(name, -1, contentType, new PropertyCollection()) + /// + /// Initializes a new instance of the class. + /// Constructor for creating a Member object + /// + /// + /// + /// + /// + public Member(string name, string email, string username, IMemberType contentType, bool isApproved = true) + : base(name, -1, contentType, new PropertyCollection()) + { + if (name == null) { - _email = email; - _username = username; - _rawPasswordValue = rawPasswordValue; - IsApproved = isApproved; - CreatorId = userId; + throw new ArgumentNullException(nameof(name)); } - /// - /// Gets or sets the Username - /// - [DataMember] - public string Username + if (string.IsNullOrWhiteSpace(name)) { - get => _username; - set => SetPropertyValueAndDetectChanges(value, ref _username!, nameof(Username)); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(name)); } - /// - /// Gets or sets the Email - /// - [DataMember] - public string Email + if (email == null) { - get => _email; - set => SetPropertyValueAndDetectChanges(value, ref _email!, nameof(Email)); + throw new ArgumentNullException(nameof(email)); } - [DataMember] - public DateTime? EmailConfirmedDate + if (string.IsNullOrWhiteSpace(email)) { - get => _emailConfirmedDate; - set => SetPropertyValueAndDetectChanges(value, ref _emailConfirmedDate, nameof(EmailConfirmedDate)); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(email)); } - /// - /// Gets or sets the raw password value - /// - [IgnoreDataMember] - public string? RawPasswordValue + if (username == null) { - get => _rawPasswordValue; - set - { - if (value == null) - { - //special case, this is used to ensure that the password is not updated when persisting, in this case - //we don't want to track changes either - _rawPasswordValue = null; - } - else - { - SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, nameof(RawPasswordValue)); - } - } + throw new ArgumentNullException(nameof(username)); } - [IgnoreDataMember] - public string? PasswordConfiguration + if (string.IsNullOrWhiteSpace(username)) { - get => _passwordConfig; - set => SetPropertyValueAndDetectChanges(value, ref _passwordConfig, nameof(PasswordConfiguration)); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(username)); } - /// - /// Gets or sets the Groups that Member is part of - /// - [DataMember] - public IEnumerable? Groups { get; set; } - - // TODO: When get/setting all of these properties we MUST: - // * Check if we are using the umbraco membership provider, if so then we need to use the configured fields - not the explicit fields below - // * If any of the fields don't exist, what should we do? Currently it will throw an exception! - - /// - /// Gets or set the comments for the member - /// - /// - /// Alias: umbracoMemberComments - /// Part of the standard properties collection. - /// - [DataMember] - public string? Comments - { - get - { - var a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.Comments, nameof(Comments), default(string)); - if (a.Success == false) - return a.Result; - - return Properties[Constants.Conventions.Member.Comments]?.GetValue() == null - ? string.Empty - : Properties[Constants.Conventions.Member.Comments]?.GetValue()?.ToString(); - } - set - { - if (WarnIfPropertyTypeNotFoundOnSet( - Constants.Conventions.Member.Comments, - nameof(Comments)) == false) - return; + _email = email; + _username = username; + IsApproved = isApproved; - Properties[Constants.Conventions.Member.Comments]?.SetValue(value); - } - } + // this cannot be null but can be empty + _rawPasswordValue = ""; + } - /// - /// Gets or sets a value indicating whether the Member is approved - /// - [DataMember] - public bool IsApproved + /// + /// Initializes a new instance of the class. + /// Constructor for creating a Member object + /// + /// + /// + /// + /// + /// + /// + public Member(string name, string email, string username, IMemberType contentType, int userId, + bool isApproved = true) + : base(name, -1, contentType, new PropertyCollection()) + { + if (name == null) { - get => _isApproved; - set => SetPropertyValueAndDetectChanges(value, ref _isApproved, nameof(IsApproved)); + throw new ArgumentNullException(nameof(name)); } - /// - /// Gets or sets a boolean indicating whether the Member is locked out - /// - /// - /// Alias: umbracoMemberLockedOut - /// Part of the standard properties collection. - /// - [DataMember] - public bool IsLockedOut + if (string.IsNullOrWhiteSpace(name)) { - get => _isLockedOut; - set => SetPropertyValueAndDetectChanges(value, ref _isLockedOut, nameof(IsLockedOut)); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(name)); } - /// - /// Gets or sets the date for last login - /// - /// - /// Alias: umbracoMemberLastLogin - /// Part of the standard properties collection. - /// - [DataMember] - public DateTime? LastLoginDate + if (email == null) { - get => _lastLoginDate; - set => SetPropertyValueAndDetectChanges(value, ref _lastLoginDate, nameof(LastLoginDate)); + throw new ArgumentNullException(nameof(email)); } - /// - /// Gest or sets the date for last password change - /// - /// - /// Alias: umbracoMemberLastPasswordChangeDate - /// Part of the standard properties collection. - /// - [DataMember] - public DateTime? LastPasswordChangeDate + if (string.IsNullOrWhiteSpace(email)) { - get => _lastPasswordChangeDate; - set => SetPropertyValueAndDetectChanges(value, ref _lastPasswordChangeDate, nameof(LastPasswordChangeDate)); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(email)); } - /// - /// Gets or sets the date for when Member was locked out - /// - /// - /// Alias: umbracoMemberLastLockoutDate - /// Part of the standard properties collection. - /// - [DataMember] - public DateTime? LastLockoutDate + if (username == null) { - get => _lastLockoutDate; - set => SetPropertyValueAndDetectChanges(value, ref _lastLockoutDate, nameof(LastLockoutDate)); + throw new ArgumentNullException(nameof(username)); } - /// - /// Gets or sets the number of failed password attempts. - /// This is the number of times the password was entered incorrectly upon login. - /// - /// - /// Alias: umbracoMemberFailedPasswordAttempts - /// Part of the standard properties collection. - /// - [DataMember] - public int FailedPasswordAttempts + if (string.IsNullOrWhiteSpace(username)) { - get => _failedPasswordAttempts; - set => SetPropertyValueAndDetectChanges(value, ref _failedPasswordAttempts, nameof(FailedPasswordAttempts)); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(username)); } - /// - /// String alias of the default ContentType - /// - [DataMember] - public virtual string ContentTypeAlias => ContentType.Alias; - - /// - /// The security stamp used by ASP.Net identity - /// - [IgnoreDataMember] - public string? SecurityStamp + _email = email; + _username = username; + CreatorId = userId; + IsApproved = isApproved; + + //this cannot be null but can be empty + _rawPasswordValue = ""; + } + + /// + /// Constructor for creating a Member object + /// + /// + /// + /// + /// + /// The password value passed in to this parameter should be the encoded/encrypted/hashed format of the member's + /// password + /// + /// + public Member(string? name, string email, string username, string? rawPasswordValue, IMemberType? contentType) + : base(name, -1, contentType, new PropertyCollection()) + { + _email = email; + _username = username; + _rawPasswordValue = rawPasswordValue; + IsApproved = true; + } + + /// + /// Initializes a new instance of the class. + /// Constructor for creating a Member object + /// + /// + /// + /// + /// + /// The password value passed in to this parameter should be the encoded/encrypted/hashed format of the member's + /// password + /// + /// + /// + public Member(string name, string email, string username, string rawPasswordValue, IMemberType contentType, + bool isApproved) + : base(name, -1, contentType, new PropertyCollection()) + { + _email = email; + _username = username; + _rawPasswordValue = rawPasswordValue; + IsApproved = isApproved; + } + + /// + /// Constructor for creating a Member object + /// + /// + /// + /// + /// + /// The password value passed in to this parameter should be the encoded/encrypted/hashed format of the member's + /// password + /// + /// + /// + /// + public Member(string name, string email, string username, string rawPasswordValue, IMemberType contentType, + bool isApproved, int userId) + : base(name, -1, contentType, new PropertyCollection()) + { + _email = email; + _username = username; + _rawPasswordValue = rawPasswordValue; + IsApproved = isApproved; + CreatorId = userId; + } + + /// + /// Gets or sets the Groups that Member is part of + /// + [DataMember] + public IEnumerable? Groups { get; set; } + + /// + /// Gets or sets the Username + /// + [DataMember] + public string Username + { + get => _username; + set => SetPropertyValueAndDetectChanges(value, ref _username!, nameof(Username)); + } + + /// + /// Gets or sets the Email + /// + [DataMember] + public string Email + { + get => _email; + set => SetPropertyValueAndDetectChanges(value, ref _email!, nameof(Email)); + } + + [DataMember] + public DateTime? EmailConfirmedDate + { + get => _emailConfirmedDate; + set => SetPropertyValueAndDetectChanges(value, ref _emailConfirmedDate, nameof(EmailConfirmedDate)); + } + + /// + /// Gets or sets the raw password value + /// + [IgnoreDataMember] + public string? RawPasswordValue + { + get => _rawPasswordValue; + set { - get => _securityStamp; - set => SetPropertyValueAndDetectChanges(value, ref _securityStamp, nameof(SecurityStamp)); + if (value == null) + { + //special case, this is used to ensure that the password is not updated when persisting, in this case + //we don't want to track changes either + _rawPasswordValue = null; + } + else + { + SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, nameof(RawPasswordValue)); + } } + } + [IgnoreDataMember] + public string? PasswordConfiguration + { + get => _passwordConfig; + set => SetPropertyValueAndDetectChanges(value, ref _passwordConfig, nameof(PasswordConfiguration)); + } - /// - /// Internal/Experimental - only used for mapping queries. - /// - /// - /// Adding these to have first level properties instead of the Properties collection. - /// - [IgnoreDataMember] - [EditorBrowsable(EditorBrowsableState.Never)] - public string? LongStringPropertyValue { get; set; } - /// - /// Internal/Experimental - only used for mapping queries. - /// - /// - /// Adding these to have first level properties instead of the Properties collection. - /// - [IgnoreDataMember] - [EditorBrowsable(EditorBrowsableState.Never)] - public string? ShortStringPropertyValue { get; set; } - /// - /// Internal/Experimental - only used for mapping queries. - /// - /// - /// Adding these to have first level properties instead of the Properties collection. - /// - [IgnoreDataMember] - [EditorBrowsable(EditorBrowsableState.Never)] - public int IntegerPropertyValue { get; set; } - /// - /// Internal/Experimental - only used for mapping queries. - /// - /// - /// Adding these to have first level properties instead of the Properties collection. - /// - [IgnoreDataMember] - [EditorBrowsable(EditorBrowsableState.Never)] - public bool BoolPropertyValue { get; set; } - /// - /// Internal/Experimental - only used for mapping queries. - /// - /// - /// Adding these to have first level properties instead of the Properties collection. - /// - [IgnoreDataMember] - [EditorBrowsable(EditorBrowsableState.Never)] - public DateTime DateTimePropertyValue { get; set; } - /// - /// Internal/Experimental - only used for mapping queries. - /// - /// - /// Adding these to have first level properties instead of the Properties collection. - /// - [IgnoreDataMember] - [EditorBrowsable(EditorBrowsableState.Never)] - public string? PropertyTypeAlias { get; set; } - - private Attempt WarnIfPropertyTypeNotFoundOnGet(string propertyAlias, string propertyName, T defaultVal) + // TODO: When get/setting all of these properties we MUST: + // * Check if we are using the umbraco membership provider, if so then we need to use the configured fields - not the explicit fields below + // * If any of the fields don't exist, what should we do? Currently it will throw an exception! + + /// + /// Gets or set the comments for the member + /// + /// + /// Alias: umbracoMemberComments + /// Part of the standard properties collection. + /// + [DataMember] + public string? Comments + { + get { - void DoLog(string logPropertyAlias, string logPropertyName) + Attempt a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.Comments, nameof(Comments), + default(string)); + if (a.Success == false) { - StaticApplicationLogging.Logger.LogWarning("Trying to access the '{PropertyName}' property on '{MemberType}' " + - "but the {PropertyAlias} property does not exist on the member type so a default value is returned. " + - "Ensure that you have a property type with alias: {PropertyAlias} configured on your member type in order to use the '{PropertyName}' property on the model correctly.", - logPropertyName, - typeof(Member), - logPropertyAlias); + return a.Result; } - // if the property doesn't exist, - if (Properties.Contains(propertyAlias) == false) + return Properties[Constants.Conventions.Member.Comments]?.GetValue() == null + ? string.Empty + : Properties[Constants.Conventions.Member.Comments]?.GetValue()?.ToString(); + } + set + { + if (WarnIfPropertyTypeNotFoundOnSet( + Constants.Conventions.Member.Comments, + nameof(Comments)) == false) { - // put a warn in the log if this entity has been persisted - // then return a failure - if (HasIdentity) - DoLog(propertyAlias, propertyName); - return Attempt.Fail(defaultVal); + return; } - return Attempt.Succeed(); + Properties[Constants.Conventions.Member.Comments]?.SetValue(value); } + } + + /// + /// Gets or sets a value indicating whether the Member is approved + /// + [DataMember] + public bool IsApproved + { + get => _isApproved; + set => SetPropertyValueAndDetectChanges(value, ref _isApproved, nameof(IsApproved)); + } + + /// + /// Gets or sets a boolean indicating whether the Member is locked out + /// + /// + /// Alias: umbracoMemberLockedOut + /// Part of the standard properties collection. + /// + [DataMember] + public bool IsLockedOut + { + get => _isLockedOut; + set => SetPropertyValueAndDetectChanges(value, ref _isLockedOut, nameof(IsLockedOut)); + } + + /// + /// Gets or sets the date for last login + /// + /// + /// Alias: umbracoMemberLastLogin + /// Part of the standard properties collection. + /// + [DataMember] + public DateTime? LastLoginDate + { + get => _lastLoginDate; + set => SetPropertyValueAndDetectChanges(value, ref _lastLoginDate, nameof(LastLoginDate)); + } + + /// + /// Gest or sets the date for last password change + /// + /// + /// Alias: umbracoMemberLastPasswordChangeDate + /// Part of the standard properties collection. + /// + [DataMember] + public DateTime? LastPasswordChangeDate + { + get => _lastPasswordChangeDate; + set => SetPropertyValueAndDetectChanges(value, ref _lastPasswordChangeDate, nameof(LastPasswordChangeDate)); + } + + /// + /// Gets or sets the date for when Member was locked out + /// + /// + /// Alias: umbracoMemberLastLockoutDate + /// Part of the standard properties collection. + /// + [DataMember] + public DateTime? LastLockoutDate + { + get => _lastLockoutDate; + set => SetPropertyValueAndDetectChanges(value, ref _lastLockoutDate, nameof(LastLockoutDate)); + } + + /// + /// Gets or sets the number of failed password attempts. + /// This is the number of times the password was entered incorrectly upon login. + /// + /// + /// Alias: umbracoMemberFailedPasswordAttempts + /// Part of the standard properties collection. + /// + [DataMember] + public int FailedPasswordAttempts + { + get => _failedPasswordAttempts; + set => SetPropertyValueAndDetectChanges(value, ref _failedPasswordAttempts, nameof(FailedPasswordAttempts)); + } - private bool WarnIfPropertyTypeNotFoundOnSet(string propertyAlias, string propertyName) + /// + /// String alias of the default ContentType + /// + [DataMember] + public virtual string ContentTypeAlias => ContentType.Alias; + + /// + /// The security stamp used by ASP.Net identity + /// + [IgnoreDataMember] + public string? SecurityStamp + { + get => _securityStamp; + set => SetPropertyValueAndDetectChanges(value, ref _securityStamp, nameof(SecurityStamp)); + } + + + /// + /// Internal/Experimental - only used for mapping queries. + /// + /// + /// Adding these to have first level properties instead of the Properties collection. + /// + [IgnoreDataMember] + [EditorBrowsable(EditorBrowsableState.Never)] + public string? LongStringPropertyValue { get; set; } + + /// + /// Internal/Experimental - only used for mapping queries. + /// + /// + /// Adding these to have first level properties instead of the Properties collection. + /// + [IgnoreDataMember] + [EditorBrowsable(EditorBrowsableState.Never)] + public string? ShortStringPropertyValue { get; set; } + + /// + /// Internal/Experimental - only used for mapping queries. + /// + /// + /// Adding these to have first level properties instead of the Properties collection. + /// + [IgnoreDataMember] + [EditorBrowsable(EditorBrowsableState.Never)] + public int IntegerPropertyValue { get; set; } + + /// + /// Internal/Experimental - only used for mapping queries. + /// + /// + /// Adding these to have first level properties instead of the Properties collection. + /// + [IgnoreDataMember] + [EditorBrowsable(EditorBrowsableState.Never)] + public bool BoolPropertyValue { get; set; } + + /// + /// Internal/Experimental - only used for mapping queries. + /// + /// + /// Adding these to have first level properties instead of the Properties collection. + /// + [IgnoreDataMember] + [EditorBrowsable(EditorBrowsableState.Never)] + public DateTime DateTimePropertyValue { get; set; } + + /// + /// Internal/Experimental - only used for mapping queries. + /// + /// + /// Adding these to have first level properties instead of the Properties collection. + /// + [IgnoreDataMember] + [EditorBrowsable(EditorBrowsableState.Never)] + public string? PropertyTypeAlias { get; set; } + + /// + [DataMember] + [DoNotClone] + public IDictionary? AdditionalData => + _additionalData ?? (_additionalData = new Dictionary()); + + /// + [IgnoreDataMember] + public bool HasAdditionalData => _additionalData != null; + + private Attempt WarnIfPropertyTypeNotFoundOnGet(string propertyAlias, string propertyName, T defaultVal) + { + void DoLog(string logPropertyAlias, string logPropertyName) { - void DoLog(string logPropertyAlias, string logPropertyName) + StaticApplicationLogging.Logger.LogWarning( + "Trying to access the '{PropertyName}' property on '{MemberType}' " + + "but the {PropertyAlias} property does not exist on the member type so a default value is returned. " + + "Ensure that you have a property type with alias: {PropertyAlias} configured on your member type in order to use the '{PropertyName}' property on the model correctly.", + logPropertyName, + typeof(Member), + logPropertyAlias); + } + + // if the property doesn't exist, + if (Properties.Contains(propertyAlias) == false) + { + // put a warn in the log if this entity has been persisted + // then return a failure + if (HasIdentity) { - StaticApplicationLogging.Logger.LogWarning("An attempt was made to set a value on the property '{PropertyName}' on type '{MemberType}' but the " + - "property type {PropertyAlias} does not exist on the member type, ensure that this property type exists so that setting this property works correctly.", - logPropertyName, - typeof(Member), - logPropertyAlias); + DoLog(propertyAlias, propertyName); } - // if the property doesn't exist, - if (Properties.Contains(propertyAlias) == false) + return Attempt.Fail(defaultVal); + } + + return Attempt.Succeed(); + } + + private bool WarnIfPropertyTypeNotFoundOnSet(string propertyAlias, string propertyName) + { + void DoLog(string logPropertyAlias, string logPropertyName) + { + StaticApplicationLogging.Logger.LogWarning( + "An attempt was made to set a value on the property '{PropertyName}' on type '{MemberType}' but the " + + "property type {PropertyAlias} does not exist on the member type, ensure that this property type exists so that setting this property works correctly.", + logPropertyName, + typeof(Member), + logPropertyAlias); + } + + // if the property doesn't exist, + if (Properties.Contains(propertyAlias) == false) + { + // put a warn in the log if this entity has been persisted + // then return a failure + if (HasIdentity) { - // put a warn in the log if this entity has been persisted - // then return a failure - if (HasIdentity) - DoLog(propertyAlias, propertyName); - return false; + DoLog(propertyAlias, propertyName); } - return true; + return false; } - /// - [DataMember] - [DoNotClone] - public IDictionary? AdditionalData => _additionalData ?? (_additionalData = new Dictionary()); - - /// - [IgnoreDataMember] - public bool HasAdditionalData => _additionalData != null; + return true; } } diff --git a/src/Umbraco.Core/Models/MemberGroup.cs b/src/Umbraco.Core/Models/MemberGroup.cs index 7a35b78875fd..b544dfe7c999 100644 --- a/src/Umbraco.Core/Models/MemberGroup.cs +++ b/src/Umbraco.Core/Models/MemberGroup.cs @@ -1,53 +1,51 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a member type +/// +[Serializable] +[DataContract(IsReference = true)] +public class MemberGroup : EntityBase, IMemberGroup { - /// - /// Represents a member type - /// - [Serializable] - [DataContract(IsReference = true)] - public class MemberGroup : EntityBase, IMemberGroup - { - private IDictionary? _additionalData; - private string? _name; - private int _creatorId; + private IDictionary? _additionalData; + private int _creatorId; + private string? _name; - /// - [DataMember] - [DoNotClone] - public IDictionary AdditionalData => _additionalData ?? (_additionalData = new Dictionary()); + /// + [DataMember] + [DoNotClone] + public IDictionary AdditionalData => + _additionalData ?? (_additionalData = new Dictionary()); - /// - [IgnoreDataMember] - public bool HasAdditionalData => _additionalData != null; + /// + [IgnoreDataMember] + public bool HasAdditionalData => _additionalData != null; - [DataMember] - public string? Name + [DataMember] + public string? Name + { + get => _name; + set { - get => _name; - set + if (_name != value) { - if (_name != value) - { - //if the name has changed, add the value to the additional data, - //this is required purely for event handlers to know the previous name of the group - //so we can keep the public access up to date. - AdditionalData["previousName"] = _name; - } - - SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); + //if the name has changed, add the value to the additional data, + //this is required purely for event handlers to know the previous name of the group + //so we can keep the public access up to date. + AdditionalData["previousName"] = _name; } - } - [DataMember] - public int CreatorId - { - get => _creatorId; - set => SetPropertyValueAndDetectChanges(value, ref _creatorId, nameof(CreatorId)); + SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); } } + + [DataMember] + public int CreatorId + { + get => _creatorId; + set => SetPropertyValueAndDetectChanges(value, ref _creatorId, nameof(CreatorId)); + } } diff --git a/src/Umbraco.Core/Models/MemberPropertyModel.cs b/src/Umbraco.Core/Models/MemberPropertyModel.cs index f6d06956e50e..80807a353073 100644 --- a/src/Umbraco.Core/Models/MemberPropertyModel.cs +++ b/src/Umbraco.Core/Models/MemberPropertyModel.cs @@ -1,37 +1,33 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; -namespace Umbraco.Cms.Core.Models -{ - /// - /// A simple representation of an Umbraco member property - /// - public class MemberPropertyModel - { - [Required] - public string Alias { get; set; } = null!; +namespace Umbraco.Cms.Core.Models; - //NOTE: This has to be a string currently, if it is an object it will bind as an array which we don't want. - // If we want to have this as an 'object' with a true type on it, we have to create a custom model binder - // for an UmbracoProperty and then bind with the correct type based on the property type for this alias. This - // would be a bit long winded and perhaps unnecessary. The reason is because it is always posted as a string anyways - // and when we set this value on the property object that gets sent to the database we do a TryConvertTo to the - // real type anyways. +/// +/// A simple representation of an Umbraco member property +/// +public class MemberPropertyModel +{ + [Required] public string Alias { get; set; } = null!; - [DataType(System.ComponentModel.DataAnnotations.DataType.Text)] - public string? Value { get; set; } + //NOTE: This has to be a string currently, if it is an object it will bind as an array which we don't want. + // If we want to have this as an 'object' with a true type on it, we have to create a custom model binder + // for an UmbracoProperty and then bind with the correct type based on the property type for this alias. This + // would be a bit long winded and perhaps unnecessary. The reason is because it is always posted as a string anyways + // and when we set this value on the property object that gets sent to the database we do a TryConvertTo to the + // real type anyways. - [ReadOnly(true)] - public string? Name { get; set; } + [DataType(System.ComponentModel.DataAnnotations.DataType.Text)] + public string? Value { get; set; } - // TODO: Perhaps one day we'll ship with our own EditorTempates but for now developers can just render their own inside the view + [ReadOnly(true)] public string? Name { get; set; } - ///// - ///// This can dynamically be set to a custom template name to change - ///// the editor type for this property - ///// - //[ReadOnly(true)] - //public string EditorTemplate { get; set; } + // TODO: Perhaps one day we'll ship with our own EditorTempates but for now developers can just render their own inside the view - } + ///// + ///// This can dynamically be set to a custom template name to change + ///// the editor type for this property + ///// + //[ReadOnly(true)] + //public string EditorTemplate { get; set; } } diff --git a/src/Umbraco.Core/Models/MemberType.cs b/src/Umbraco.Core/Models/MemberType.cs index 4db8388b9437..eacd2967230e 100644 --- a/src/Umbraco.Core/Models/MemberType.cs +++ b/src/Umbraco.Core/Models/MemberType.cs @@ -1,169 +1,173 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents the content type that a object is based on +/// +[Serializable] +[DataContract(IsReference = true)] +public class MemberType : ContentTypeCompositionBase, IMemberType { + public const bool SupportsPublishingConst = false; + private readonly IShortStringHelper _shortStringHelper; + + //Dictionary is divided into string: PropertyTypeAlias, Tuple: MemberCanEdit, VisibleOnProfile, PropertyTypeId + private string _alias = string.Empty; + /// - /// Represents the content type that a object is based on + /// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile, IsSensitive) by the PropertyTypes' alias. /// - [Serializable] - [DataContract(IsReference = true)] - public class MemberType : ContentTypeCompositionBase, IMemberType + private IDictionary _memberTypePropertyTypes; + + public MemberType(IShortStringHelper shortStringHelper, int parentId) : base(shortStringHelper, parentId) { - private readonly IShortStringHelper _shortStringHelper; - public const bool SupportsPublishingConst = false; + _shortStringHelper = shortStringHelper; + _memberTypePropertyTypes = new Dictionary(); + } - //Dictionary is divided into string: PropertyTypeAlias, Tuple: MemberCanEdit, VisibleOnProfile, PropertyTypeId - private string _alias = string.Empty; + public MemberType(IShortStringHelper shortStringHelper, IContentTypeComposition parent) : this(shortStringHelper, + parent, string.Empty) + { + } - public MemberType(IShortStringHelper shortStringHelper, int parentId) : base(shortStringHelper, parentId) - { - _shortStringHelper = shortStringHelper; - _memberTypePropertyTypes = new Dictionary(); - } + public MemberType(IShortStringHelper shortStringHelper, IContentTypeComposition parent, string alias) + : base(shortStringHelper, parent, alias) + { + _shortStringHelper = shortStringHelper; + _memberTypePropertyTypes = new Dictionary(); + } - public MemberType(IShortStringHelper shortStringHelper, IContentTypeComposition parent) : this(shortStringHelper, parent, string.Empty) - { - } + /// + public override bool SupportsPublishing => SupportsPublishingConst; - public MemberType(IShortStringHelper shortStringHelper, IContentTypeComposition parent, string alias) - : base(shortStringHelper, parent, alias) - { - _shortStringHelper = shortStringHelper; - _memberTypePropertyTypes = new Dictionary(); - } + /// + public override ISimpleContentType ToSimple() => new SimpleContentType(this); - /// - public override ISimpleContentType ToSimple() => new SimpleContentType(this); + public override ContentVariation Variations + { + // note: although technically possible, variations on members don't make much sense + // and therefore are disabled - they are fully supported at service level, though, + // but not at published snapshot level. - /// - public override bool SupportsPublishing => SupportsPublishingConst; + get => base.Variations; + set => throw new NotSupportedException("Variations are not supported on members."); + } - public override ContentVariation Variations + /// + /// The Alias of the ContentType + /// + [DataMember] + public override string Alias + { + get => _alias; + set { - // note: although technically possible, variations on members don't make much sense - // and therefore are disabled - they are fully supported at service level, though, - // but not at published snapshot level. - - get => base.Variations; - set => throw new NotSupportedException("Variations are not supported on members."); + //NOTE: WE are overriding this because we don't want to do a ToSafeAlias when the alias is the special case of + // "_umbracoSystemDefaultProtectType" which is used internally, currently there is an issue with the safe alias as it strips + // leading underscores which we don't want in this case. + // see : http://issues.umbraco.org/issue/U4-3968 + + // TODO: BUT, I'm pretty sure we could do this with regards to underscores now: + // .ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase) + // Need to ask Stephen + + var newVal = value == "_umbracoSystemDefaultProtectType" + ? value + : value == null + ? string.Empty + : value.ToSafeAlias(_shortStringHelper); + + SetPropertyValueAndDetectChanges(newVal, ref _alias!, nameof(Alias)); } + } - /// - /// The Alias of the ContentType - /// - [DataMember] - public override string Alias - { - get => _alias; - set - { - //NOTE: WE are overriding this because we don't want to do a ToSafeAlias when the alias is the special case of - // "_umbracoSystemDefaultProtectType" which is used internally, currently there is an issue with the safe alias as it strips - // leading underscores which we don't want in this case. - // see : http://issues.umbraco.org/issue/U4-3968 - - // TODO: BUT, I'm pretty sure we could do this with regards to underscores now: - // .ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase) - // Need to ask Stephen - - var newVal = value == "_umbracoSystemDefaultProtectType" - ? value - : (value == null ? string.Empty : value.ToSafeAlias(_shortStringHelper)); - - SetPropertyValueAndDetectChanges(newVal, ref _alias!, nameof(Alias)); - } - } + /// + /// Gets a boolean indicating whether a Property is editable by the Member. + /// + /// PropertyType Alias of the Property to check + /// + public bool MemberCanEditProperty(string? propertyTypeAlias) => propertyTypeAlias is not null && + _memberTypePropertyTypes.TryGetValue( + propertyTypeAlias, + out MemberTypePropertyProfileAccess + propertyProfile) && + propertyProfile.IsEditable; - /// - /// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile, IsSensitive) by the PropertyTypes' alias. - /// - private IDictionary _memberTypePropertyTypes; - - /// - /// Gets a boolean indicating whether a Property is editable by the Member. - /// - /// PropertyType Alias of the Property to check - /// - public bool MemberCanEditProperty(string? propertyTypeAlias) - { - return propertyTypeAlias is not null && _memberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile) && propertyProfile.IsEditable; - } + /// + /// Gets a boolean indicating whether a Property is visible on the Members profile. + /// + /// PropertyType Alias of the Property to check + /// + public bool MemberCanViewProperty(string propertyTypeAlias) => + _memberTypePropertyTypes.TryGetValue(propertyTypeAlias, out MemberTypePropertyProfileAccess propertyProfile) && + propertyProfile.IsVisible; - /// - /// Gets a boolean indicating whether a Property is visible on the Members profile. - /// - /// PropertyType Alias of the Property to check - /// - public bool MemberCanViewProperty(string propertyTypeAlias) + /// + /// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile. + /// + /// PropertyType Alias of the Property to check + /// + public bool IsSensitiveProperty(string propertyTypeAlias) => + _memberTypePropertyTypes.TryGetValue(propertyTypeAlias, out MemberTypePropertyProfileAccess propertyProfile) && + propertyProfile.IsSensitive; + + /// + /// Sets a boolean indicating whether a Property is editable by the Member. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + public void SetMemberCanEditProperty(string propertyTypeAlias, bool value) + { + if (_memberTypePropertyTypes.TryGetValue(propertyTypeAlias, + out MemberTypePropertyProfileAccess propertyProfile)) { - return _memberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile) && propertyProfile.IsVisible; + propertyProfile.IsEditable = value; } - /// - /// Gets a boolean indicating whether a Property is marked as storing sensitive values on the Members profile. - /// - /// PropertyType Alias of the Property to check - /// - public bool IsSensitiveProperty(string propertyTypeAlias) + else { - return _memberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile) && propertyProfile.IsSensitive; + var tuple = new MemberTypePropertyProfileAccess(false, value, false); + _memberTypePropertyTypes.Add(propertyTypeAlias, tuple); } + } - /// - /// Sets a boolean indicating whether a Property is editable by the Member. - /// - /// PropertyType Alias of the Property to set - /// Boolean value, true or false - public void SetMemberCanEditProperty(string propertyTypeAlias, bool value) + /// + /// Sets a boolean indicating whether a Property is visible on the Members profile. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + public void SetMemberCanViewProperty(string propertyTypeAlias, bool value) + { + if (_memberTypePropertyTypes.TryGetValue(propertyTypeAlias, + out MemberTypePropertyProfileAccess propertyProfile)) { - if (_memberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile)) - { - propertyProfile.IsEditable = value; - } - else - { - var tuple = new MemberTypePropertyProfileAccess(false, value, false); - _memberTypePropertyTypes.Add(propertyTypeAlias, tuple); - } + propertyProfile.IsVisible = value; } - - /// - /// Sets a boolean indicating whether a Property is visible on the Members profile. - /// - /// PropertyType Alias of the Property to set - /// Boolean value, true or false - public void SetMemberCanViewProperty(string propertyTypeAlias, bool value) + else { - if (_memberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile)) - { - propertyProfile.IsVisible = value; - } - else - { - var tuple = new MemberTypePropertyProfileAccess(value, false, false); - _memberTypePropertyTypes.Add(propertyTypeAlias, tuple); - } + var tuple = new MemberTypePropertyProfileAccess(value, false, false); + _memberTypePropertyTypes.Add(propertyTypeAlias, tuple); } + } - /// - /// Sets a boolean indicating whether a Property is a sensitive value on the Members profile. - /// - /// PropertyType Alias of the Property to set - /// Boolean value, true or false - public void SetIsSensitiveProperty(string propertyTypeAlias, bool value) + /// + /// Sets a boolean indicating whether a Property is a sensitive value on the Members profile. + /// + /// PropertyType Alias of the Property to set + /// Boolean value, true or false + public void SetIsSensitiveProperty(string propertyTypeAlias, bool value) + { + if (_memberTypePropertyTypes.TryGetValue(propertyTypeAlias, + out MemberTypePropertyProfileAccess propertyProfile)) + { + propertyProfile.IsSensitive = value; + } + else { - if (_memberTypePropertyTypes.TryGetValue(propertyTypeAlias, out var propertyProfile)) - { - propertyProfile.IsSensitive = value; - } - else - { - var tuple = new MemberTypePropertyProfileAccess(false, false, value); - _memberTypePropertyTypes.Add(propertyTypeAlias, tuple); - } + var tuple = new MemberTypePropertyProfileAccess(false, false, value); + _memberTypePropertyTypes.Add(propertyTypeAlias, tuple); } } } diff --git a/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs b/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs index 89bf2f283dd4..35962402d268 100644 --- a/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs +++ b/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs @@ -1,19 +1,18 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Used to track the property types that are visible/editable on member profiles +/// +public class MemberTypePropertyProfileAccess { - /// - /// Used to track the property types that are visible/editable on member profiles - /// - public class MemberTypePropertyProfileAccess + public MemberTypePropertyProfileAccess(bool isVisible, bool isEditable, bool isSenstive) { - public MemberTypePropertyProfileAccess(bool isVisible, bool isEditable, bool isSenstive) - { - IsVisible = isVisible; - IsEditable = isEditable; - IsSensitive = isSenstive; - } - - public bool IsVisible { get; set; } - public bool IsEditable { get; set; } - public bool IsSensitive { get; set; } + IsVisible = isVisible; + IsEditable = isEditable; + IsSensitive = isSenstive; } + + public bool IsVisible { get; set; } + public bool IsEditable { get; set; } + public bool IsSensitive { get; set; } } diff --git a/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs b/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs index 9c585589fac0..b0e294c2876a 100644 --- a/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs +++ b/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs @@ -1,55 +1,46 @@ -using System; -using Umbraco.Cms.Core.Models.Entities; - -namespace Umbraco.Cms.Core.Models.Membership +using Umbraco.Cms.Core.Models.Entities; + +namespace Umbraco.Cms.Core.Models.Membership; + +/// +/// Represents an -> user group & permission key value pair collection +/// +/// +/// This implements purely so it can be used with the repository layer which is why it's +/// explicitly implemented. +/// +public class ContentPermissionSet : EntityPermissionSet, IEntity { - /// - /// Represents an -> user group & permission key value pair collection - /// - /// - /// This implements purely so it can be used with the repository layer which is why it's explicitly implemented. - /// - public class ContentPermissionSet : EntityPermissionSet, IEntity - { - private readonly IContent _content; + private readonly IContent _content; - public ContentPermissionSet(IContent content, EntityPermissionCollection permissionsSet) - : base(content.Id, permissionsSet) - { - _content = content; - } + public ContentPermissionSet(IContent content, EntityPermissionCollection permissionsSet) + : base(content.Id, permissionsSet) => + _content = content; - public override int EntityId - { - get { return _content.Id; } - } + public override int EntityId => _content.Id; - #region Explicit implementation of IAggregateRoot - int IEntity.Id - { - get { return EntityId; } - set { throw new NotImplementedException(); } - } + #region Explicit implementation of IAggregateRoot - bool IEntity.HasIdentity - { - get { return EntityId > 0; } - } + int IEntity.Id + { + get => EntityId; + set => throw new NotImplementedException(); + } - void IEntity.ResetIdentity() => throw new InvalidOperationException($"Resetting identity on {nameof(ContentPermissionSet)} is invalid"); + bool IEntity.HasIdentity => EntityId > 0; - Guid IEntity.Key { get; set; } + void IEntity.ResetIdentity() => + throw new InvalidOperationException($"Resetting identity on {nameof(ContentPermissionSet)} is invalid"); - DateTime IEntity.CreateDate { get; set; } + Guid IEntity.Key { get; set; } - DateTime IEntity.UpdateDate { get; set; } + DateTime IEntity.CreateDate { get; set; } - DateTime? IEntity.DeleteDate { get; set; } + DateTime IEntity.UpdateDate { get; set; } - object IDeepCloneable.DeepClone() - { - throw new NotImplementedException(); - } - #endregion - } + DateTime? IEntity.DeleteDate { get; set; } + + object IDeepCloneable.DeepClone() => throw new NotImplementedException(); + + #endregion } diff --git a/src/Umbraco.Core/Models/Membership/EntityPermission.cs b/src/Umbraco.Core/Models/Membership/EntityPermission.cs index a86c844622cb..b900cff5afac 100644 --- a/src/Umbraco.Core/Models/Membership/EntityPermission.cs +++ b/src/Umbraco.Core/Models/Membership/EntityPermission.cs @@ -1,66 +1,83 @@ -using System; +namespace Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Models.Membership +/// +/// Represents an entity permission (defined on the user group and derived to retrieve permissions for a given user) +/// +public class EntityPermission : IEquatable { + public EntityPermission(int groupId, int entityId, string[] assignedPermissions) + { + UserGroupId = groupId; + EntityId = entityId; + AssignedPermissions = assignedPermissions; + IsDefaultPermissions = false; + } + + public EntityPermission(int groupId, int entityId, string[] assignedPermissions, bool isDefaultPermissions) + { + UserGroupId = groupId; + EntityId = entityId; + AssignedPermissions = assignedPermissions; + IsDefaultPermissions = isDefaultPermissions; + } + + public int EntityId { get; } + public int UserGroupId { get; } + + /// + /// The assigned permissions for the user/entity combo + /// + public string[] AssignedPermissions { get; } + /// - /// Represents an entity permission (defined on the user group and derived to retrieve permissions for a given user) + /// True if the permissions assigned to this object are the group's default permissions and not explicitly defined + /// permissions /// - public class EntityPermission : IEquatable + /// + /// This will be the case when looking up entity permissions and falling back to the default permissions + /// + public bool IsDefaultPermissions { get; } + + public bool Equals(EntityPermission? other) { - public EntityPermission(int groupId, int entityId, string[] assignedPermissions) + if (ReferenceEquals(null, other)) { - UserGroupId = groupId; - EntityId = entityId; - AssignedPermissions = assignedPermissions; - IsDefaultPermissions = false; + return false; } - public EntityPermission(int groupId, int entityId, string[] assignedPermissions, bool isDefaultPermissions) + if (ReferenceEquals(this, other)) { - UserGroupId = groupId; - EntityId = entityId; - AssignedPermissions = assignedPermissions; - IsDefaultPermissions = isDefaultPermissions; + return true; } - public int EntityId { get; private set; } - public int UserGroupId { get; private set; } - - /// - /// The assigned permissions for the user/entity combo - /// - public string[] AssignedPermissions { get; private set; } - - /// - /// True if the permissions assigned to this object are the group's default permissions and not explicitly defined permissions - /// - /// - /// This will be the case when looking up entity permissions and falling back to the default permissions - /// - public bool IsDefaultPermissions { get; private set; } + return EntityId == other.EntityId && UserGroupId == other.UserGroupId; + } - public bool Equals(EntityPermission? other) + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return EntityId == other.EntityId && UserGroupId == other.UserGroupId; + return false; } - public override bool Equals(object? obj) + if (ReferenceEquals(this, obj)) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((EntityPermission) obj); + return true; } - public override int GetHashCode() + if (obj.GetType() != GetType()) { - unchecked - { - return (EntityId * 397) ^ UserGroupId; - } + return false; } + + return Equals((EntityPermission)obj); } + public override int GetHashCode() + { + unchecked + { + return (EntityId * 397) ^ UserGroupId; + } + } } diff --git a/src/Umbraco.Core/Models/Membership/EntityPermissionCollection.cs b/src/Umbraco.Core/Models/Membership/EntityPermissionCollection.cs index ac03ef75d8f1..8d96fd7163e3 100644 --- a/src/Umbraco.Core/Models/Membership/EntityPermissionCollection.cs +++ b/src/Umbraco.Core/Models/Membership/EntityPermissionCollection.cs @@ -1,57 +1,55 @@ -using System.Collections.Generic; -using System.Linq; +namespace Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Models.Membership +/// +/// A of +/// +public class EntityPermissionCollection : HashSet { - /// - /// A of - /// - public class EntityPermissionCollection : HashSet + private Dictionary? _aggregateNodePermissions; + + private string[]? _aggregatePermissions; + + public EntityPermissionCollection() { - public EntityPermissionCollection() - { - } + } - public EntityPermissionCollection(IEnumerable collection) : base(collection) - { - } + public EntityPermissionCollection(IEnumerable collection) : base(collection) + { + } - /// - /// Returns the aggregate permissions in the permission set for a single node - /// - /// - /// - /// This value is only calculated once per node - /// - public IEnumerable GetAllPermissions(int entityId) + /// + /// Returns the aggregate permissions in the permission set for a single node + /// + /// + /// + /// This value is only calculated once per node + /// + public IEnumerable GetAllPermissions(int entityId) + { + if (_aggregateNodePermissions == null) { - if (_aggregateNodePermissions == null) - _aggregateNodePermissions = new Dictionary(); - - string[]? entityPermissions; - if (_aggregateNodePermissions.TryGetValue(entityId, out entityPermissions) == false) - { - entityPermissions = this.Where(x => x.EntityId == entityId).SelectMany(x => x.AssignedPermissions).Distinct().ToArray(); - _aggregateNodePermissions[entityId] = entityPermissions; - } - return entityPermissions; + _aggregateNodePermissions = new Dictionary(); } - private Dictionary? _aggregateNodePermissions; - - /// - /// Returns the aggregate permissions in the permission set for all nodes - /// - /// - /// - /// This value is only calculated once - /// - public IEnumerable GetAllPermissions() + string[]? entityPermissions; + if (_aggregateNodePermissions.TryGetValue(entityId, out entityPermissions) == false) { - return _aggregatePermissions ?? (_aggregatePermissions = - this.SelectMany(x => x.AssignedPermissions).Distinct().ToArray()); + entityPermissions = this.Where(x => x.EntityId == entityId).SelectMany(x => x.AssignedPermissions) + .Distinct().ToArray(); + _aggregateNodePermissions[entityId] = entityPermissions; } - private string[]? _aggregatePermissions; + return entityPermissions; } + + /// + /// Returns the aggregate permissions in the permission set for all nodes + /// + /// + /// + /// This value is only calculated once + /// + public IEnumerable GetAllPermissions() => + _aggregatePermissions ?? (_aggregatePermissions = + this.SelectMany(x => x.AssignedPermissions).Distinct().ToArray()); } diff --git a/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs b/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs index 68e97a5d9fa7..3b16bb679f74 100644 --- a/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs +++ b/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs @@ -1,54 +1,42 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Models.Membership +/// +/// Represents an entity -> user group & permission key value pair collection +/// +public class EntityPermissionSet { - /// - /// Represents an entity -> user group & permission key value pair collection - /// - public class EntityPermissionSet - { - private static readonly Lazy EmptyInstance = new Lazy(() => new EntityPermissionSet(-1, new EntityPermissionCollection())); - /// - /// Returns an empty permission set - /// - /// - public static EntityPermissionSet Empty() - { - return EmptyInstance.Value; - } - - public EntityPermissionSet(int entityId, EntityPermissionCollection permissionsSet) - { - EntityId = entityId; - PermissionsSet = permissionsSet; - } - - /// - /// The entity id with permissions assigned - /// - public virtual int EntityId { get; private set; } - - /// - /// The key/value pairs of user group id & single permission - /// - public EntityPermissionCollection PermissionsSet { get; private set; } + private static readonly Lazy EmptyInstance = + new(() => new EntityPermissionSet(-1, new EntityPermissionCollection())); + public EntityPermissionSet(int entityId, EntityPermissionCollection permissionsSet) + { + EntityId = entityId; + PermissionsSet = permissionsSet; + } - /// - /// Returns the aggregate permissions in the permission set - /// - /// - /// - /// This value is only calculated once - /// - public IEnumerable GetAllPermissions() - { - return PermissionsSet.GetAllPermissions(); - } + /// + /// The entity id with permissions assigned + /// + public virtual int EntityId { get; } + /// + /// The key/value pairs of user group id & single permission + /// + public EntityPermissionCollection PermissionsSet { get; } + /// + /// Returns an empty permission set + /// + /// + public static EntityPermissionSet Empty() => EmptyInstance.Value; - } + /// + /// Returns the aggregate permissions in the permission set + /// + /// + /// + /// This value is only calculated once + /// + public IEnumerable GetAllPermissions() => PermissionsSet.GetAllPermissions(); } diff --git a/src/Umbraco.Core/Models/Membership/IMembershipUser.cs b/src/Umbraco.Core/Models/Membership/IMembershipUser.cs index f8efe5588524..50c06b169a7d 100644 --- a/src/Umbraco.Core/Models/Membership/IMembershipUser.cs +++ b/src/Umbraco.Core/Models/Membership/IMembershipUser.cs @@ -1,50 +1,48 @@ -using System; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models.Membership +namespace Umbraco.Cms.Core.Models.Membership; + +/// +/// Defines the base contract for and +/// +public interface IMembershipUser : IEntity { + string Username { get; set; } + string Email { get; set; } + DateTime? EmailConfirmedDate { get; set; } + /// - /// Defines the base contract for and + /// Gets or sets the raw password value /// - public interface IMembershipUser : IEntity - { - string Username { get; set; } - string Email { get; set; } - DateTime? EmailConfirmedDate { get; set; } - - /// - /// Gets or sets the raw password value - /// - string? RawPasswordValue { get; set; } - - /// - /// The user's specific password config (i.e. algorithm type, etc...) - /// - string? PasswordConfiguration { get; set; } - - string? Comments { get; set; } - bool IsApproved { get; set; } - bool IsLockedOut { get; set; } - DateTime? LastLoginDate { get; set; } - DateTime? LastPasswordChangeDate { get; set; } - DateTime? LastLockoutDate { get; set; } - - /// - /// Gets or sets the number of failed password attempts. - /// This is the number of times the password was entered incorrectly upon login. - /// - /// - /// Alias: umbracoMemberFailedPasswordAttempts - /// Part of the standard properties collection. - /// - int FailedPasswordAttempts { get; set; } - - /// - /// Gets or sets the security stamp used by ASP.NET Identity - /// - string? SecurityStamp { get; set; } - - //object ProfileId { get; set; } - //IEnumerable Groups { get; set; } - } + string? RawPasswordValue { get; set; } + + /// + /// The user's specific password config (i.e. algorithm type, etc...) + /// + string? PasswordConfiguration { get; set; } + + string? Comments { get; set; } + bool IsApproved { get; set; } + bool IsLockedOut { get; set; } + DateTime? LastLoginDate { get; set; } + DateTime? LastPasswordChangeDate { get; set; } + DateTime? LastLockoutDate { get; set; } + + /// + /// Gets or sets the number of failed password attempts. + /// This is the number of times the password was entered incorrectly upon login. + /// + /// + /// Alias: umbracoMemberFailedPasswordAttempts + /// Part of the standard properties collection. + /// + int FailedPasswordAttempts { get; set; } + + /// + /// Gets or sets the security stamp used by ASP.NET Identity + /// + string? SecurityStamp { get; set; } + + //object ProfileId { get; set; } + //IEnumerable Groups { get; set; } } diff --git a/src/Umbraco.Core/Models/Membership/IProfile.cs b/src/Umbraco.Core/Models/Membership/IProfile.cs index 395ebe0de82f..77d2255d9b59 100644 --- a/src/Umbraco.Core/Models/Membership/IProfile.cs +++ b/src/Umbraco.Core/Models/Membership/IProfile.cs @@ -1,11 +1,10 @@ -namespace Umbraco.Cms.Core.Models.Membership +namespace Umbraco.Cms.Core.Models.Membership; + +/// +/// Defines the User Profile interface +/// +public interface IProfile { - /// - /// Defines the User Profile interface - /// - public interface IProfile - { - int Id { get; } - string? Name { get; } - } + int Id { get; } + string? Name { get; } } diff --git a/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs b/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs index be84b4bca6eb..046a88ae70b3 100644 --- a/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs @@ -1,31 +1,29 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Models.Membership +/// +/// A readonly user group providing basic information +/// +public interface IReadOnlyUserGroup { + string? Name { get; } + string? Icon { get; } + int Id { get; } + int? StartContentId { get; } + int? StartMediaId { get; } + /// - /// A readonly user group providing basic information + /// The alias /// - public interface IReadOnlyUserGroup - { - string? Name { get; } - string? Icon { get; } - int Id { get; } - int? StartContentId { get; } - int? StartMediaId { get; } - - /// - /// The alias - /// - string Alias { get; } + string Alias { get; } - /// - /// The set of default permissions - /// - /// - /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future. - /// - IEnumerable? Permissions { get; set; } + /// + /// The set of default permissions + /// + /// + /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more + /// flexible permissions structure in the future. + /// + IEnumerable? Permissions { get; set; } - IEnumerable AllowedSections { get; } - } + IEnumerable AllowedSections { get; } } diff --git a/src/Umbraco.Core/Models/Membership/IUser.cs b/src/Umbraco.Core/Models/Membership/IUser.cs index c7c68dabda0c..acc5774f8e16 100644 --- a/src/Umbraco.Core/Models/Membership/IUser.cs +++ b/src/Umbraco.Core/Models/Membership/IUser.cs @@ -1,50 +1,46 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models.Membership +namespace Umbraco.Cms.Core.Models.Membership; + +/// +/// Defines the interface for a +/// +/// Will be left internal until a proper Membership implementation is part of the roadmap +public interface IUser : IMembershipUser, IRememberBeingDirty, ICanBeDirty { + UserState UserState { get; } + + string? Name { get; set; } + int SessionTimeout { get; set; } + int[]? StartContentIds { get; set; } + int[]? StartMediaIds { get; set; } + string? Language { get; set; } + + DateTime? InvitedDate { get; set; } + + /// + /// Gets the groups that user is part of + /// + IEnumerable Groups { get; } + + IEnumerable AllowedSections { get; } /// - /// Defines the interface for a + /// Exposes the basic profile data /// - /// Will be left internal until a proper Membership implementation is part of the roadmap - public interface IUser : IMembershipUser, IRememberBeingDirty, ICanBeDirty - { - UserState UserState { get; } - - string? Name { get; set; } - int SessionTimeout { get; set; } - int[]? StartContentIds { get; set; } - int[]? StartMediaIds { get; set; } - string? Language { get; set; } - - DateTime? InvitedDate { get; set; } - - /// - /// Gets the groups that user is part of - /// - IEnumerable Groups { get; } - - void RemoveGroup(string group); - void ClearGroups(); - void AddGroup(IReadOnlyUserGroup group); - - IEnumerable AllowedSections { get; } - - /// - /// Exposes the basic profile data - /// - IProfile ProfileData { get; } - - /// - /// Will hold the media file system relative path of the users custom avatar if they uploaded one - /// - string? Avatar { get; set; } - - /// - /// A Json blob stored for recording tour data for a user - /// - string? TourData { get; set; } - } + IProfile ProfileData { get; } + + /// + /// Will hold the media file system relative path of the users custom avatar if they uploaded one + /// + string? Avatar { get; set; } + + /// + /// A Json blob stored for recording tour data for a user + /// + string? TourData { get; set; } + + void RemoveGroup(string group); + void ClearGroups(); + void AddGroup(IReadOnlyUserGroup group); } diff --git a/src/Umbraco.Core/Models/Membership/IUserGroup.cs b/src/Umbraco.Core/Models/Membership/IUserGroup.cs index 96ae3c6dfb23..5e6bff9c68fb 100644 --- a/src/Umbraco.Core/Models/Membership/IUserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/IUserGroup.cs @@ -1,44 +1,43 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models.Membership +namespace Umbraco.Cms.Core.Models.Membership; + +public interface IUserGroup : IEntity, IRememberBeingDirty { - public interface IUserGroup : IEntity, IRememberBeingDirty - { - string Alias { get; set; } + string Alias { get; set; } - int? StartContentId { get; set; } - int? StartMediaId { get; set; } + int? StartContentId { get; set; } + int? StartMediaId { get; set; } - /// - /// The icon - /// - string? Icon { get; set; } + /// + /// The icon + /// + string? Icon { get; set; } - /// - /// The name - /// - string? Name { get; set; } + /// + /// The name + /// + string? Name { get; set; } - /// - /// The set of default permissions - /// - /// - /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future. - /// - IEnumerable? Permissions { get; set; } + /// + /// The set of default permissions + /// + /// + /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more + /// flexible permissions structure in the future. + /// + IEnumerable? Permissions { get; set; } - IEnumerable AllowedSections { get; } + IEnumerable AllowedSections { get; } - void RemoveAllowedSection(string sectionAlias); + /// + /// Specifies the number of users assigned to this group + /// + int UserCount { get; } - void AddAllowedSection(string sectionAlias); + void RemoveAllowedSection(string sectionAlias); - void ClearAllowedSections(); + void AddAllowedSection(string sectionAlias); - /// - /// Specifies the number of users assigned to this group - /// - int UserCount { get; } - } + void ClearAllowedSections(); } diff --git a/src/Umbraco.Core/Models/Membership/MemberCountType.cs b/src/Umbraco.Core/Models/Membership/MemberCountType.cs index 89990994e812..504212fa5fdc 100644 --- a/src/Umbraco.Core/Models/Membership/MemberCountType.cs +++ b/src/Umbraco.Core/Models/Membership/MemberCountType.cs @@ -1,12 +1,11 @@ -namespace Umbraco.Cms.Core.Models.Membership +namespace Umbraco.Cms.Core.Models.Membership; + +/// +/// The types of members to count +/// +public enum MemberCountType { - /// - /// The types of members to count - /// - public enum MemberCountType - { - All, - LockedOut, - Approved - } + All, + LockedOut, + Approved } diff --git a/src/Umbraco.Core/Models/Membership/MemberExportModel.cs b/src/Umbraco.Core/Models/Membership/MemberExportModel.cs index e577c86e704e..9ec3b1c0eeb2 100644 --- a/src/Umbraco.Core/Models/Membership/MemberExportModel.cs +++ b/src/Umbraco.Core/Models/Membership/MemberExportModel.cs @@ -1,19 +1,15 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Models.Membership +public class MemberExportModel { - public class MemberExportModel - { - public int Id { get; set; } - public Guid Key { get; set; } - public string? Name { get; set; } - public string? Username { get; set; } - public string? Email { get; set; } - public List? Groups { get; set; } - public string? ContentTypeAlias { get; set; } - public DateTime CreateDate { get; set; } - public DateTime UpdateDate { get; set; } - public List? Properties { get; set; } - } + public int Id { get; set; } + public Guid Key { get; set; } + public string? Name { get; set; } + public string? Username { get; set; } + public string? Email { get; set; } + public List? Groups { get; set; } + public string? ContentTypeAlias { get; set; } + public DateTime CreateDate { get; set; } + public DateTime UpdateDate { get; set; } + public List? Properties { get; set; } } diff --git a/src/Umbraco.Core/Models/Membership/MemberExportProperty.cs b/src/Umbraco.Core/Models/Membership/MemberExportProperty.cs index fec933190cc0..4b82e4689c65 100644 --- a/src/Umbraco.Core/Models/Membership/MemberExportProperty.cs +++ b/src/Umbraco.Core/Models/Membership/MemberExportProperty.cs @@ -1,14 +1,11 @@ -using System; +namespace Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Models.Membership +public class MemberExportProperty { - public class MemberExportProperty - { - public int Id { get; set; } - public string? Alias { get; set; } - public string? Name { get; set; } - public object? Value { get; set; } - public DateTime? CreateDate { get; set; } - public DateTime? UpdateDate { get; set; } - } + public int Id { get; set; } + public string? Alias { get; set; } + public string? Name { get; set; } + public object? Value { get; set; } + public DateTime? CreateDate { get; set; } + public DateTime? UpdateDate { get; set; } } diff --git a/src/Umbraco.Core/Models/Membership/PersistedPasswordSettings.cs b/src/Umbraco.Core/Models/Membership/PersistedPasswordSettings.cs index 3e4831d9c337..f1c0463bdd7b 100644 --- a/src/Umbraco.Core/Models/Membership/PersistedPasswordSettings.cs +++ b/src/Umbraco.Core/Models/Membership/PersistedPasswordSettings.cs @@ -1,22 +1,22 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models.Membership +namespace Umbraco.Cms.Core.Models.Membership; + +/// +/// The data stored against the user for their password configuration +/// +[DataContract(Name = "userPasswordSettings", Namespace = "")] +public class PersistedPasswordSettings { /// - /// The data stored against the user for their password configuration + /// The algorithm name /// - [DataContract(Name = "userPasswordSettings", Namespace = "")] - public class PersistedPasswordSettings - { - /// - /// The algorithm name - /// - /// - /// This doesn't explicitly need to map to a 'true' algorithm name, this may match an algorithm name alias that - /// uses many different options such as PBKDF2.ASPNETCORE.V3 which would map to the aspnetcore's v3 implementation of PBKDF2 - /// PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations. - /// - [DataMember(Name = "hashAlgorithm")] - public string? HashAlgorithm { get; set; } - } + /// + /// This doesn't explicitly need to map to a 'true' algorithm name, this may match an algorithm name alias that + /// uses many different options such as PBKDF2.ASPNETCORE.V3 which would map to the aspnetcore's v3 implementation of + /// PBKDF2 + /// PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations. + /// + [DataMember(Name = "hashAlgorithm")] + public string? HashAlgorithm { get; set; } } diff --git a/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs b/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs index 24543337baea..66b1306e82d2 100644 --- a/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs @@ -1,70 +1,78 @@ -using System; -using System.Collections.Generic; -using System.Linq; +namespace Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Models.Membership +public class ReadOnlyUserGroup : IReadOnlyUserGroup, IEquatable { - public class ReadOnlyUserGroup : IReadOnlyUserGroup, IEquatable + public ReadOnlyUserGroup(int id, string? name, string? icon, int? startContentId, int? startMediaId, string? alias, + IEnumerable allowedSections, IEnumerable? permissions) { - public ReadOnlyUserGroup(int id, string? name, string? icon, int? startContentId, int? startMediaId, string? @alias, - IEnumerable allowedSections, IEnumerable? permissions) - { - Name = name ?? string.Empty; - Icon = icon; - Id = id; - Alias = alias ?? string.Empty; - AllowedSections = allowedSections.ToArray(); - Permissions = permissions?.ToArray(); - - //Zero is invalid and will be treated as Null - StartContentId = startContentId == 0 ? null : startContentId; - StartMediaId = startMediaId == 0 ? null : startMediaId; - } - - public int Id { get; private set; } - public string Name { get; private set; } - public string? Icon { get; private set; } - public int? StartContentId { get; private set; } - public int? StartMediaId { get; private set; } - public string Alias { get; private set; } + Name = name ?? string.Empty; + Icon = icon; + Id = id; + Alias = alias ?? string.Empty; + AllowedSections = allowedSections.ToArray(); + Permissions = permissions?.ToArray(); - /// - /// The set of default permissions - /// - /// - /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future. - /// - public IEnumerable? Permissions { get; set; } - public IEnumerable AllowedSections { get; private set; } + //Zero is invalid and will be treated as Null + StartContentId = startContentId == 0 ? null : startContentId; + StartMediaId = startMediaId == 0 ? null : startMediaId; + } - public bool Equals(ReadOnlyUserGroup? other) + public bool Equals(ReadOnlyUserGroup? other) + { + if (ReferenceEquals(null, other)) { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return string.Equals(Alias, other.Alias); + return false; } - public override bool Equals(object? obj) + if (ReferenceEquals(this, other)) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((ReadOnlyUserGroup) obj); + return true; } - public override int GetHashCode() + return string.Equals(Alias, other.Alias); + } + + public int Id { get; } + public string Name { get; } + public string? Icon { get; } + public int? StartContentId { get; } + public int? StartMediaId { get; } + public string Alias { get; } + + /// + /// The set of default permissions + /// + /// + /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more + /// flexible permissions structure in the future. + /// + public IEnumerable? Permissions { get; set; } + + public IEnumerable AllowedSections { get; } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) { - return Alias?.GetHashCode() ?? base.GetHashCode(); + return false; } - public static bool operator ==(ReadOnlyUserGroup left, ReadOnlyUserGroup right) + if (ReferenceEquals(this, obj)) { - return Equals(left, right); + return true; } - public static bool operator !=(ReadOnlyUserGroup left, ReadOnlyUserGroup right) + if (obj.GetType() != GetType()) { - return !Equals(left, right); + return false; } + + return Equals((ReadOnlyUserGroup)obj); } + + public override int GetHashCode() => Alias?.GetHashCode() ?? base.GetHashCode(); + + public static bool operator ==(ReadOnlyUserGroup left, ReadOnlyUserGroup right) => Equals(left, right); + + public static bool operator !=(ReadOnlyUserGroup left, ReadOnlyUserGroup right) => !Equals(left, right); } diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 463b44c73e10..2a53342f3bcf 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -1,421 +1,465 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; using System.Runtime.Serialization; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.Membership +namespace Umbraco.Cms.Core.Models.Membership; + +/// +/// Represents a backoffice user +/// +[Serializable] +[DataContract(IsReference = true)] +public class User : EntityBase, IUser, IProfile { + //Custom comparer for enumerable + private static readonly DelegateEqualityComparer> IntegerEnumerableComparer = + new( + (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), + enum1 => enum1.GetHashCode()); + + private IEnumerable? _allowedSections; + private string? _avatar; + private string _email; + private DateTime? _emailConfirmedDate; + private int _failedLoginAttempts; + private DateTime? _invitedDate; + private bool _isApproved; + private bool _isLockedOut; + private string? _language; + private DateTime? _lastLockoutDate; + private DateTime? _lastLoginDate; + private DateTime? _lastPasswordChangedDate; + + private string _name; + private string? _passwordConfig; + private string? _rawPasswordValue; + private string? _securityStamp; + private int _sessionTimeout; + private int[]? _startContentIds; + private int[]? _startMediaIds; + private string? _tourData; + private HashSet _userGroups; + + private string _username; + /// - /// Represents a backoffice user + /// Constructor for creating a new/empty user /// - [Serializable] - [DataContract(IsReference = true)] - public class User : EntityBase, IUser, IProfile + public User(GlobalSettings globalSettings) { - /// - /// Constructor for creating a new/empty user - /// - public User(GlobalSettings globalSettings) - { - SessionTimeout = 60; - _userGroups = new HashSet(); - _language = globalSettings.DefaultUILanguage; - _isApproved = true; - _isLockedOut = false; - _startContentIds = new int[] { }; - _startMediaIds = new int[] { }; - //cannot be null - _rawPasswordValue = ""; - _username = string.Empty; - _email = string.Empty; - _name = string.Empty; - } + SessionTimeout = 60; + _userGroups = new HashSet(); + _language = globalSettings.DefaultUILanguage; + _isApproved = true; + _isLockedOut = false; + _startContentIds = new int[] { }; + _startMediaIds = new int[] { }; + //cannot be null + _rawPasswordValue = ""; + _username = string.Empty; + _email = string.Empty; + _name = string.Empty; + } - /// - /// Constructor for creating a new/empty user - /// - /// - /// - /// - /// - public User(GlobalSettings globalSettings, string? name, string email, string username, string rawPasswordValue) - : this(globalSettings) + /// + /// Constructor for creating a new/empty user + /// + /// + /// + /// + /// + public User(GlobalSettings globalSettings, string? name, string email, string username, string rawPasswordValue) + : this(globalSettings) + { + if (string.IsNullOrWhiteSpace(name)) { - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); - if (string.IsNullOrWhiteSpace(email)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(email)); - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); - if (string.IsNullOrEmpty(rawPasswordValue)) throw new ArgumentException("Value cannot be null or empty.", nameof(rawPasswordValue)); - - _name = name; - _email = email; - _username = username; - _rawPasswordValue = rawPasswordValue; - _userGroups = new HashSet(); - _isApproved = true; - _isLockedOut = false; - _startContentIds = new int[] { }; - _startMediaIds = new int[] { }; + throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); } - /// - /// Constructor for creating a new User instance for an existing user - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public User(GlobalSettings globalSettings, int id, string? name, string email, string? username, - string? rawPasswordValue, string? passwordConfig, - IEnumerable userGroups, int[] startContentIds, int[] startMediaIds) - : this(globalSettings) + if (string.IsNullOrWhiteSpace(email)) { - //we allow whitespace for this value so just check null - if (rawPasswordValue == null) throw new ArgumentNullException(nameof(rawPasswordValue)); - if (userGroups == null) throw new ArgumentNullException(nameof(userGroups)); - if (startContentIds == null) throw new ArgumentNullException(nameof(startContentIds)); - if (startMediaIds == null) throw new ArgumentNullException(nameof(startMediaIds)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); - - Id = id; - _name = name; - _email = email; - _username = username; - _rawPasswordValue = rawPasswordValue; - _passwordConfig = passwordConfig; - _userGroups = new HashSet(userGroups); - _isApproved = true; - _isLockedOut = false; - _startContentIds = startContentIds; - _startMediaIds = startMediaIds; + throw new ArgumentException("Value cannot be null or whitespace.", nameof(email)); } - private string _name; - private string? _securityStamp; - private string? _avatar; - private string? _tourData; - private int _sessionTimeout; - private int[]? _startContentIds; - private int[]? _startMediaIds; - private int _failedLoginAttempts; - - private string _username; - private DateTime? _emailConfirmedDate; - private DateTime? _invitedDate; - private string _email; - private string? _rawPasswordValue; - private string? _passwordConfig; - private IEnumerable? _allowedSections; - private HashSet _userGroups; - private bool _isApproved; - private bool _isLockedOut; - private string? _language; - private DateTime? _lastPasswordChangedDate; - private DateTime? _lastLoginDate; - private DateTime? _lastLockoutDate; - - //Custom comparer for enumerable - private static readonly DelegateEqualityComparer> IntegerEnumerableComparer = - new DelegateEqualityComparer>( - (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), - enum1 => enum1.GetHashCode()); - - - [DataMember] - public DateTime? EmailConfirmedDate + if (string.IsNullOrWhiteSpace(username)) { - get => _emailConfirmedDate; - set => SetPropertyValueAndDetectChanges(value, ref _emailConfirmedDate, nameof(EmailConfirmedDate)); + throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); } - [DataMember] - public DateTime? InvitedDate + if (string.IsNullOrEmpty(rawPasswordValue)) { - get => _invitedDate; - set => SetPropertyValueAndDetectChanges(value, ref _invitedDate, nameof(InvitedDate)); + throw new ArgumentException("Value cannot be null or empty.", nameof(rawPasswordValue)); } - [DataMember] - public string Username - { - get => _username; - set => SetPropertyValueAndDetectChanges(value, ref _username!, nameof(Username)); - } + _name = name; + _email = email; + _username = username; + _rawPasswordValue = rawPasswordValue; + _userGroups = new HashSet(); + _isApproved = true; + _isLockedOut = false; + _startContentIds = new int[] { }; + _startMediaIds = new int[] { }; + } - [DataMember] - public string Email + /// + /// Constructor for creating a new User instance for an existing user + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public User(GlobalSettings globalSettings, int id, string? name, string email, string? username, + string? rawPasswordValue, string? passwordConfig, + IEnumerable userGroups, int[] startContentIds, int[] startMediaIds) + : this(globalSettings) + { + //we allow whitespace for this value so just check null + if (rawPasswordValue == null) { - get => _email; - set => SetPropertyValueAndDetectChanges(value, ref _email!, nameof(Email)); + throw new ArgumentNullException(nameof(rawPasswordValue)); } - [IgnoreDataMember] - public string? RawPasswordValue + if (userGroups == null) { - get => _rawPasswordValue; - set => SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, nameof(RawPasswordValue)); + throw new ArgumentNullException(nameof(userGroups)); } - [IgnoreDataMember] - public string? PasswordConfiguration + if (startContentIds == null) { - get => _passwordConfig; - set => SetPropertyValueAndDetectChanges(value, ref _passwordConfig, nameof(PasswordConfiguration)); + throw new ArgumentNullException(nameof(startContentIds)); } - [DataMember] - public bool IsApproved + if (startMediaIds == null) { - get => _isApproved; - set => SetPropertyValueAndDetectChanges(value, ref _isApproved, nameof(IsApproved)); + throw new ArgumentNullException(nameof(startMediaIds)); } - [IgnoreDataMember] - public bool IsLockedOut + if (string.IsNullOrWhiteSpace(name)) { - get => _isLockedOut; - set => SetPropertyValueAndDetectChanges(value, ref _isLockedOut, nameof(IsLockedOut)); + throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); } - [IgnoreDataMember] - public DateTime? LastLoginDate + if (string.IsNullOrWhiteSpace(username)) { - get => _lastLoginDate; - set => SetPropertyValueAndDetectChanges(value, ref _lastLoginDate, nameof(LastLoginDate)); + throw new ArgumentException("Value cannot be null or whitespace.", nameof(username)); } - [IgnoreDataMember] - public DateTime? LastPasswordChangeDate - { - get => _lastPasswordChangedDate; - set => SetPropertyValueAndDetectChanges(value, ref _lastPasswordChangedDate, nameof(LastPasswordChangeDate)); - } + Id = id; + _name = name; + _email = email; + _username = username; + _rawPasswordValue = rawPasswordValue; + _passwordConfig = passwordConfig; + _userGroups = new HashSet(userGroups); + _isApproved = true; + _isLockedOut = false; + _startContentIds = startContentIds; + _startMediaIds = startMediaIds; + } - [IgnoreDataMember] - public DateTime? LastLockoutDate - { - get => _lastLockoutDate; - set => SetPropertyValueAndDetectChanges(value, ref _lastLockoutDate, nameof(LastLockoutDate)); - } - [IgnoreDataMember] - public int FailedPasswordAttempts - { - get => _failedLoginAttempts; - set => SetPropertyValueAndDetectChanges(value, ref _failedLoginAttempts, nameof(FailedPasswordAttempts)); - } + [DataMember] + public DateTime? EmailConfirmedDate + { + get => _emailConfirmedDate; + set => SetPropertyValueAndDetectChanges(value, ref _emailConfirmedDate, nameof(EmailConfirmedDate)); + } - [IgnoreDataMember] - public string? Comments { get; set; } + [DataMember] + public DateTime? InvitedDate + { + get => _invitedDate; + set => SetPropertyValueAndDetectChanges(value, ref _invitedDate, nameof(InvitedDate)); + } - public UserState UserState + [DataMember] + public string Username + { + get => _username; + set => SetPropertyValueAndDetectChanges(value, ref _username!, nameof(Username)); + } + + [DataMember] + public string Email + { + get => _email; + set => SetPropertyValueAndDetectChanges(value, ref _email!, nameof(Email)); + } + + [IgnoreDataMember] + public string? RawPasswordValue + { + get => _rawPasswordValue; + set => SetPropertyValueAndDetectChanges(value, ref _rawPasswordValue, nameof(RawPasswordValue)); + } + + [IgnoreDataMember] + public string? PasswordConfiguration + { + get => _passwordConfig; + set => SetPropertyValueAndDetectChanges(value, ref _passwordConfig, nameof(PasswordConfiguration)); + } + + [DataMember] + public bool IsApproved + { + get => _isApproved; + set => SetPropertyValueAndDetectChanges(value, ref _isApproved, nameof(IsApproved)); + } + + [IgnoreDataMember] + public bool IsLockedOut + { + get => _isLockedOut; + set => SetPropertyValueAndDetectChanges(value, ref _isLockedOut, nameof(IsLockedOut)); + } + + [IgnoreDataMember] + public DateTime? LastLoginDate + { + get => _lastLoginDate; + set => SetPropertyValueAndDetectChanges(value, ref _lastLoginDate, nameof(LastLoginDate)); + } + + [IgnoreDataMember] + public DateTime? LastPasswordChangeDate + { + get => _lastPasswordChangedDate; + set => SetPropertyValueAndDetectChanges(value, ref _lastPasswordChangedDate, nameof(LastPasswordChangeDate)); + } + + [IgnoreDataMember] + public DateTime? LastLockoutDate + { + get => _lastLockoutDate; + set => SetPropertyValueAndDetectChanges(value, ref _lastLockoutDate, nameof(LastLockoutDate)); + } + + [IgnoreDataMember] + public int FailedPasswordAttempts + { + get => _failedLoginAttempts; + set => SetPropertyValueAndDetectChanges(value, ref _failedLoginAttempts, nameof(FailedPasswordAttempts)); + } + + [IgnoreDataMember] public string? Comments { get; set; } + + public UserState UserState + { + get { - get + if (LastLoginDate == default && IsApproved == false && InvitedDate != null) { - if (LastLoginDate == default && IsApproved == false && InvitedDate != null) - return UserState.Invited; + return UserState.Invited; + } - if (IsLockedOut) - return UserState.LockedOut; - if (IsApproved == false) - return UserState.Disabled; + if (IsLockedOut) + { + return UserState.LockedOut; + } - // User is not disabled or locked and has never logged in before - if (LastLoginDate == default && IsApproved && IsLockedOut == false) - return UserState.Inactive; + if (IsApproved == false) + { + return UserState.Disabled; + } - return UserState.Active; + // User is not disabled or locked and has never logged in before + if (LastLoginDate == default && IsApproved && IsLockedOut == false) + { + return UserState.Inactive; } - } - [DataMember] - public string? Name - { - get => _name; - set => SetPropertyValueAndDetectChanges(value, ref _name!, nameof(Name)); + return UserState.Active; } + } - public IEnumerable AllowedSections - { - get { return _allowedSections ?? (_allowedSections = new List(_userGroups.SelectMany(x => x.AllowedSections).Distinct())); } - } + [DataMember] + public string? Name + { + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name!, nameof(Name)); + } - public IProfile ProfileData => new WrappedUserProfile(this); + public IEnumerable AllowedSections => _allowedSections ?? + (_allowedSections = new List(_userGroups + .SelectMany(x => x.AllowedSections).Distinct())); - /// - /// The security stamp used by ASP.Net identity - /// - [IgnoreDataMember] - public string? SecurityStamp - { - get => _securityStamp; - set => SetPropertyValueAndDetectChanges(value, ref _securityStamp, nameof(SecurityStamp)); - } + public IProfile ProfileData => new WrappedUserProfile(this); - [DataMember] - public string? Avatar - { - get => _avatar; - set => SetPropertyValueAndDetectChanges(value, ref _avatar, nameof(Avatar)); - } + /// + /// The security stamp used by ASP.Net identity + /// + [IgnoreDataMember] + public string? SecurityStamp + { + get => _securityStamp; + set => SetPropertyValueAndDetectChanges(value, ref _securityStamp, nameof(SecurityStamp)); + } - /// - /// A Json blob stored for recording tour data for a user - /// - [DataMember] - public string? TourData - { - get => _tourData; - set => SetPropertyValueAndDetectChanges(value, ref _tourData, nameof(TourData)); - } + [DataMember] + public string? Avatar + { + get => _avatar; + set => SetPropertyValueAndDetectChanges(value, ref _avatar, nameof(Avatar)); + } - /// - /// Gets or sets the session timeout. - /// - /// - /// The session timeout. - /// - [DataMember] - public int SessionTimeout - { - get => _sessionTimeout; - set => SetPropertyValueAndDetectChanges(value, ref _sessionTimeout, nameof(SessionTimeout)); - } + /// + /// A Json blob stored for recording tour data for a user + /// + [DataMember] + public string? TourData + { + get => _tourData; + set => SetPropertyValueAndDetectChanges(value, ref _tourData, nameof(TourData)); + } - /// - /// Gets or sets the start content id. - /// - /// - /// The start content id. - /// - [DataMember] - [DoNotClone] - public int[]? StartContentIds - { - get => _startContentIds; - set => SetPropertyValueAndDetectChanges(value, ref _startContentIds, nameof(StartContentIds), IntegerEnumerableComparer); - } + /// + /// Gets or sets the session timeout. + /// + /// + /// The session timeout. + /// + [DataMember] + public int SessionTimeout + { + get => _sessionTimeout; + set => SetPropertyValueAndDetectChanges(value, ref _sessionTimeout, nameof(SessionTimeout)); + } - /// - /// Gets or sets the start media id. - /// - /// - /// The start media id. - /// - [DataMember] - [DoNotClone] - public int[]? StartMediaIds - { - get => _startMediaIds; - set => SetPropertyValueAndDetectChanges(value, ref _startMediaIds, nameof(StartMediaIds), IntegerEnumerableComparer); - } + /// + /// Gets or sets the start content id. + /// + /// + /// The start content id. + /// + [DataMember] + [DoNotClone] + public int[]? StartContentIds + { + get => _startContentIds; + set => SetPropertyValueAndDetectChanges(value, ref _startContentIds, nameof(StartContentIds), + IntegerEnumerableComparer); + } - [DataMember] - public string? Language - { - get => _language; - set => SetPropertyValueAndDetectChanges(value, ref _language, nameof(Language)); - } + /// + /// Gets or sets the start media id. + /// + /// + /// The start media id. + /// + [DataMember] + [DoNotClone] + public int[]? StartMediaIds + { + get => _startMediaIds; + set => SetPropertyValueAndDetectChanges(value, ref _startMediaIds, nameof(StartMediaIds), + IntegerEnumerableComparer); + } - /// - /// Gets the groups that user is part of - /// - [DataMember] - public IEnumerable Groups => _userGroups; + [DataMember] + public string? Language + { + get => _language; + set => SetPropertyValueAndDetectChanges(value, ref _language, nameof(Language)); + } - public void RemoveGroup(string group) - { - foreach (var userGroup in _userGroups.ToArray()) - { - if (userGroup.Alias == group) - { - _userGroups.Remove(userGroup); - //reset this flag so it's rebuilt with the assigned groups - _allowedSections = null; - OnPropertyChanged(nameof(Groups)); - } - } - } + /// + /// Gets the groups that user is part of + /// + [DataMember] + public IEnumerable Groups => _userGroups; - public void ClearGroups() + public void RemoveGroup(string group) + { + foreach (IReadOnlyUserGroup userGroup in _userGroups.ToArray()) { - if (_userGroups.Count > 0) + if (userGroup.Alias == group) { - _userGroups.Clear(); + _userGroups.Remove(userGroup); //reset this flag so it's rebuilt with the assigned groups _allowedSections = null; OnPropertyChanged(nameof(Groups)); } } + } - public void AddGroup(IReadOnlyUserGroup group) + public void ClearGroups() + { + if (_userGroups.Count > 0) { - if (_userGroups.Add(group)) - { - //reset this flag so it's rebuilt with the assigned groups - _allowedSections = null; - OnPropertyChanged(nameof(Groups)); - } + _userGroups.Clear(); + //reset this flag so it's rebuilt with the assigned groups + _allowedSections = null; + OnPropertyChanged(nameof(Groups)); } + } - protected override void PerformDeepClone(object clone) + public void AddGroup(IReadOnlyUserGroup group) + { + if (_userGroups.Add(group)) { - base.PerformDeepClone(clone); + //reset this flag so it's rebuilt with the assigned groups + _allowedSections = null; + OnPropertyChanged(nameof(Groups)); + } + } - var clonedEntity = (User)clone; + protected override void PerformDeepClone(object clone) + { + base.PerformDeepClone(clone); - //manually clone the start node props - clonedEntity._startContentIds = _startContentIds?.ToArray(); - clonedEntity._startMediaIds = _startMediaIds?.ToArray(); - //need to create new collections otherwise they'll get copied by ref - clonedEntity._userGroups = new HashSet(_userGroups); - clonedEntity._allowedSections = _allowedSections != null ? new List(_allowedSections) : null; + var clonedEntity = (User)clone; - } + //manually clone the start node props + clonedEntity._startContentIds = _startContentIds?.ToArray(); + clonedEntity._startMediaIds = _startMediaIds?.ToArray(); + //need to create new collections otherwise they'll get copied by ref + clonedEntity._userGroups = new HashSet(_userGroups); + clonedEntity._allowedSections = _allowedSections != null ? new List(_allowedSections) : null; + } - /// - /// Internal class used to wrap the user in a profile - /// - private class WrappedUserProfile : IProfile - { - private readonly IUser _user; + /// + /// Internal class used to wrap the user in a profile + /// + private class WrappedUserProfile : IProfile + { + private readonly IUser _user; - public WrappedUserProfile(IUser user) - { - _user = user; - } + public WrappedUserProfile(IUser user) => _user = user; + + public int Id => _user.Id; - public int Id => _user.Id; + public string? Name => _user.Name; - public string? Name => _user.Name; + private bool Equals(WrappedUserProfile other) => _user.Equals(other._user); - private bool Equals(WrappedUserProfile other) + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) { - return _user.Equals(other._user); + return false; } - public override bool Equals(object? obj) + if (ReferenceEquals(this, obj)) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((WrappedUserProfile) obj); + return true; } - public override int GetHashCode() + if (obj.GetType() != GetType()) { - return _user.GetHashCode(); + return false; } + + return Equals((WrappedUserProfile)obj); } + public override int GetHashCode() => _user.GetHashCode(); } } diff --git a/src/Umbraco.Core/Models/Membership/UserGroup.cs b/src/Umbraco.Core/Models/Membership/UserGroup.cs index 5807a83abec2..6ac1156e41b8 100644 --- a/src/Umbraco.Core/Models/Membership/UserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/UserGroup.cs @@ -1,144 +1,142 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.Membership +namespace Umbraco.Cms.Core.Models.Membership; + +/// +/// Represents a Group for a Backoffice User +/// +[Serializable] +[DataContract(IsReference = true)] +public class UserGroup : EntityBase, IUserGroup, IReadOnlyUserGroup { + //Custom comparer for enumerable + private static readonly DelegateEqualityComparer> StringEnumerableComparer = + new( + (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), + enum1 => enum1.GetHashCode()); + + private readonly IShortStringHelper _shortStringHelper; + private string _alias; + private string? _icon; + private string _name; + private IEnumerable? _permissions; + private List _sectionCollection; + private int? _startContentId; + private int? _startMediaId; + /// - /// Represents a Group for a Backoffice User + /// Constructor to create a new user group /// - [Serializable] - [DataContract(IsReference = true)] - public class UserGroup : EntityBase, IUserGroup, IReadOnlyUserGroup + public UserGroup(IShortStringHelper shortStringHelper) { - private readonly IShortStringHelper _shortStringHelper; - private int? _startContentId; - private int? _startMediaId; - private string _alias; - private string? _icon; - private string _name; - private IEnumerable? _permissions; - private List _sectionCollection; - - //Custom comparer for enumerable - private static readonly DelegateEqualityComparer> StringEnumerableComparer = - new DelegateEqualityComparer>( - (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), - enum1 => enum1.GetHashCode()); - - /// - /// Constructor to create a new user group - /// - public UserGroup(IShortStringHelper shortStringHelper) - { - _alias = string.Empty; - _name = string.Empty; - _shortStringHelper = shortStringHelper; - _sectionCollection = new List(); - } - - /// - /// Constructor to create an existing user group - /// - /// - /// - /// - /// - /// - public UserGroup(IShortStringHelper shortStringHelper, int userCount, string? alias, string? name, IEnumerable permissions, string? icon) - : this(shortStringHelper) - { - UserCount = userCount; - _alias = alias ?? string.Empty; - _name = name ?? string.Empty; - _permissions = permissions; - _icon = icon; - } + _alias = string.Empty; + _name = string.Empty; + _shortStringHelper = shortStringHelper; + _sectionCollection = new List(); + } - [DataMember] - public int? StartMediaId - { - get => _startMediaId; - set => SetPropertyValueAndDetectChanges(value, ref _startMediaId, nameof(StartMediaId)); - } + /// + /// Constructor to create an existing user group + /// + /// + /// + /// + /// + /// + public UserGroup(IShortStringHelper shortStringHelper, int userCount, string? alias, string? name, + IEnumerable permissions, string? icon) + : this(shortStringHelper) + { + UserCount = userCount; + _alias = alias ?? string.Empty; + _name = name ?? string.Empty; + _permissions = permissions; + _icon = icon; + } - [DataMember] - public int? StartContentId - { - get => _startContentId; - set => SetPropertyValueAndDetectChanges(value, ref _startContentId, nameof(StartContentId)); - } + [DataMember] + public int? StartMediaId + { + get => _startMediaId; + set => SetPropertyValueAndDetectChanges(value, ref _startMediaId, nameof(StartMediaId)); + } - [DataMember] - public string? Icon - { - get => _icon; - set => SetPropertyValueAndDetectChanges(value, ref _icon, nameof(Icon)); - } + [DataMember] + public int? StartContentId + { + get => _startContentId; + set => SetPropertyValueAndDetectChanges(value, ref _startContentId, nameof(StartContentId)); + } - [DataMember] - public string Alias - { - get => _alias; - set => SetPropertyValueAndDetectChanges(value.ToCleanString(_shortStringHelper, CleanStringType.Alias | CleanStringType.UmbracoCase), ref _alias!, nameof(Alias)); - } + [DataMember] + public string? Icon + { + get => _icon; + set => SetPropertyValueAndDetectChanges(value, ref _icon, nameof(Icon)); + } - [DataMember] - public string? Name - { - get => _name; - set => SetPropertyValueAndDetectChanges(value, ref _name!, nameof(Name)); - } + [DataMember] + public string Alias + { + get => _alias; + set => SetPropertyValueAndDetectChanges( + value.ToCleanString(_shortStringHelper, CleanStringType.Alias | CleanStringType.UmbracoCase), ref _alias!, + nameof(Alias)); + } - /// - /// The set of default permissions for the user group - /// - /// - /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future. - /// - [DataMember] - public IEnumerable? Permissions - { - get => _permissions; - set => SetPropertyValueAndDetectChanges(value, ref _permissions, nameof(Permissions), StringEnumerableComparer); - } + [DataMember] + public string? Name + { + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name!, nameof(Name)); + } - public IEnumerable AllowedSections - { - get => _sectionCollection; - } + /// + /// The set of default permissions for the user group + /// + /// + /// By default each permission is simply a single char but we've made this an enumerable{string} to support a more + /// flexible permissions structure in the future. + /// + [DataMember] + public IEnumerable? Permissions + { + get => _permissions; + set => SetPropertyValueAndDetectChanges(value, ref _permissions, nameof(Permissions), StringEnumerableComparer); + } - public void RemoveAllowedSection(string sectionAlias) - { - if (_sectionCollection.Contains(sectionAlias)) - _sectionCollection.Remove(sectionAlias); - } + public IEnumerable AllowedSections => _sectionCollection; - public void AddAllowedSection(string sectionAlias) + public void RemoveAllowedSection(string sectionAlias) + { + if (_sectionCollection.Contains(sectionAlias)) { - if (_sectionCollection.Contains(sectionAlias) == false) - _sectionCollection.Add(sectionAlias); + _sectionCollection.Remove(sectionAlias); } + } - public void ClearAllowedSections() + public void AddAllowedSection(string sectionAlias) + { + if (_sectionCollection.Contains(sectionAlias) == false) { - _sectionCollection.Clear(); + _sectionCollection.Add(sectionAlias); } + } - public int UserCount { get; } + public void ClearAllowedSections() => _sectionCollection.Clear(); - protected override void PerformDeepClone(object clone) - { + public int UserCount { get; } - base.PerformDeepClone(clone); + protected override void PerformDeepClone(object clone) + { + base.PerformDeepClone(clone); - var clonedEntity = (UserGroup)clone; + var clonedEntity = (UserGroup)clone; - //manually clone the start node props - clonedEntity._sectionCollection = new List(_sectionCollection); - } + //manually clone the start node props + clonedEntity._sectionCollection = new List(_sectionCollection); } } diff --git a/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs b/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs index 84b165b81e7c..62d2886fd102 100644 --- a/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs +++ b/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs @@ -1,31 +1,32 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class UserGroupExtensions { - public static class UserGroupExtensions + public static IReadOnlyUserGroup ToReadOnlyGroup(this IUserGroup group) { - public static IReadOnlyUserGroup ToReadOnlyGroup(this IUserGroup group) + //this will generally always be the case + var readonlyGroup = group as IReadOnlyUserGroup; + if (readonlyGroup != null) { - //this will generally always be the case - var readonlyGroup = group as IReadOnlyUserGroup; - if (readonlyGroup != null) return readonlyGroup; - - //otherwise create one - return new ReadOnlyUserGroup(group.Id, group.Name, group.Icon, group.StartContentId, group.StartMediaId, group.Alias, group.AllowedSections, group.Permissions); + return readonlyGroup; } - public static bool IsSystemUserGroup(this IUserGroup group) => - IsSystemUserGroup(group.Alias); + //otherwise create one + return new ReadOnlyUserGroup(group.Id, group.Name, group.Icon, group.StartContentId, group.StartMediaId, + group.Alias, group.AllowedSections, group.Permissions); + } - public static bool IsSystemUserGroup(this IReadOnlyUserGroup group) => - IsSystemUserGroup(group.Alias); + public static bool IsSystemUserGroup(this IUserGroup group) => + IsSystemUserGroup(group.Alias); - private static bool IsSystemUserGroup(this string? groupAlias) - { - return groupAlias == Constants.Security.AdminGroupAlias - || groupAlias == Constants.Security.SensitiveDataGroupAlias - || groupAlias == Constants.Security.TranslatorGroupAlias; - } - } + public static bool IsSystemUserGroup(this IReadOnlyUserGroup group) => + IsSystemUserGroup(group.Alias); + + private static bool IsSystemUserGroup(this string? groupAlias) => + groupAlias == Constants.Security.AdminGroupAlias + || groupAlias == Constants.Security.SensitiveDataGroupAlias + || groupAlias == Constants.Security.TranslatorGroupAlias; } diff --git a/src/Umbraco.Core/Models/Membership/UserProfile.cs b/src/Umbraco.Core/Models/Membership/UserProfile.cs index aca757b317e2..58eb68ba4244 100644 --- a/src/Umbraco.Core/Models/Membership/UserProfile.cs +++ b/src/Umbraco.Core/Models/Membership/UserProfile.cs @@ -1,46 +1,54 @@ -using System; +namespace Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Models.Membership +public class UserProfile : IProfile, IEquatable { - public class UserProfile : IProfile, IEquatable + public UserProfile(int id, string? name) { - public UserProfile(int id, string? name) - { - Id = id; - Name = name; - } - - public int Id { get; private set; } - public string? Name { get; private set; } + Id = id; + Name = name; + } - public bool Equals(UserProfile? other) + public bool Equals(UserProfile? other) + { + if (ReferenceEquals(null, other)) { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Id == other.Id; + return false; } - public override bool Equals(object? obj) + if (ReferenceEquals(this, other)) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((UserProfile) obj); + return true; } - public override int GetHashCode() + return Id == other.Id; + } + + public int Id { get; } + public string? Name { get; } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) { - return Id; + return false; } - public static bool operator ==(UserProfile left, UserProfile right) + if (ReferenceEquals(this, obj)) { - return Equals(left, right); + return true; } - public static bool operator !=(UserProfile left, UserProfile right) + if (obj.GetType() != GetType()) { - return Equals(left, right) == false; + return false; } + + return Equals((UserProfile)obj); } + + public override int GetHashCode() => Id; + + public static bool operator ==(UserProfile left, UserProfile right) => Equals(left, right); + + public static bool operator !=(UserProfile left, UserProfile right) => Equals(left, right) == false; } diff --git a/src/Umbraco.Core/Models/Membership/UserState.cs b/src/Umbraco.Core/Models/Membership/UserState.cs index 13d20771050b..a941c6fd6bf2 100644 --- a/src/Umbraco.Core/Models/Membership/UserState.cs +++ b/src/Umbraco.Core/Models/Membership/UserState.cs @@ -1,15 +1,14 @@ -namespace Umbraco.Cms.Core.Models.Membership +namespace Umbraco.Cms.Core.Models.Membership; + +/// +/// The state of a user +/// +public enum UserState { - /// - /// The state of a user - /// - public enum UserState - { - All = -1, - Active = 0, - Disabled = 1, - LockedOut = 2, - Invited = 3, - Inactive = 4 - } + All = -1, + Active = 0, + Disabled = 1, + LockedOut = 2, + Invited = 3, + Inactive = 4 } diff --git a/src/Umbraco.Core/Models/MigrationEntry.cs b/src/Umbraco.Core/Models/MigrationEntry.cs index f62dc7eb6030..aab68d8ed230 100644 --- a/src/Umbraco.Core/Models/MigrationEntry.cs +++ b/src/Umbraco.Core/Models/MigrationEntry.cs @@ -1,36 +1,34 @@ -using System; -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Semver; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public class MigrationEntry : EntityBase, IMigrationEntry { - public class MigrationEntry : EntityBase, IMigrationEntry - { - public MigrationEntry() - { - } + private string? _migrationName; + private SemVersion? _version; - public MigrationEntry(int id, DateTime createDate, string migrationName, SemVersion version) - { - Id = id; - CreateDate = createDate; - _migrationName = migrationName; - _version = version; - } + public MigrationEntry() + { + } - private string? _migrationName; - private SemVersion? _version; + public MigrationEntry(int id, DateTime createDate, string migrationName, SemVersion version) + { + Id = id; + CreateDate = createDate; + _migrationName = migrationName; + _version = version; + } - public string? MigrationName - { - get => _migrationName; - set => SetPropertyValueAndDetectChanges(value, ref _migrationName, nameof(MigrationName)); - } + public string? MigrationName + { + get => _migrationName; + set => SetPropertyValueAndDetectChanges(value, ref _migrationName, nameof(MigrationName)); + } - public SemVersion? Version - { - get => _version; - set => SetPropertyValueAndDetectChanges(value, ref _version, nameof(Version)); - } + public SemVersion? Version + { + get => _version; + set => SetPropertyValueAndDetectChanges(value, ref _version, nameof(Version)); } } diff --git a/src/Umbraco.Core/Models/Notification.cs b/src/Umbraco.Core/Models/Notification.cs index 95091efe1f5c..2cc1ded652f3 100644 --- a/src/Umbraco.Core/Models/Notification.cs +++ b/src/Umbraco.Core/Models/Notification.cs @@ -1,20 +1,17 @@ -using System; +namespace Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Models +public class Notification { - public class Notification + public Notification(int entityId, int userId, string action, Guid? entityType) { - public Notification(int entityId, int userId, string action, Guid? entityType) - { - EntityId = entityId; - UserId = userId; - Action = action; - EntityType = entityType; - } - - public int EntityId { get; private set; } - public int UserId { get; private set; } - public string Action { get; private set; } - public Guid? EntityType { get; private set; } + EntityId = entityId; + UserId = userId; + Action = action; + EntityType = entityType; } + + public int EntityId { get; } + public int UserId { get; } + public string Action { get; } + public Guid? EntityType { get; } } diff --git a/src/Umbraco.Core/Models/NotificationEmailBodyParams.cs b/src/Umbraco.Core/Models/NotificationEmailBodyParams.cs index 5174ee636b23..01cf6c5ebdfa 100644 --- a/src/Umbraco.Core/Models/NotificationEmailBodyParams.cs +++ b/src/Umbraco.Core/Models/NotificationEmailBodyParams.cs @@ -1,32 +1,31 @@ -using System; +namespace Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Models +public class NotificationEmailBodyParams { - public class NotificationEmailBodyParams + public NotificationEmailBodyParams(string? recipientName, string? action, string? itemName, string itemId, + string itemUrl, string? editedUser, string siteUrl, string summary) { - public NotificationEmailBodyParams(string? recipientName, string? action, string? itemName, string itemId, string itemUrl, string? editedUser, string siteUrl, string summary) - { - RecipientName = recipientName ?? throw new ArgumentNullException(nameof(recipientName)); - Action = action ?? throw new ArgumentNullException(nameof(action)); - ItemName = itemName ?? throw new ArgumentNullException(nameof(itemName)); - ItemId = itemId ?? throw new ArgumentNullException(nameof(itemId)); - ItemUrl = itemUrl ?? throw new ArgumentNullException(nameof(itemUrl)); - Summary = summary ?? throw new ArgumentNullException(nameof(summary)); - EditedUser = editedUser ?? throw new ArgumentNullException(nameof(editedUser)); - SiteUrl = siteUrl ?? throw new ArgumentNullException(nameof(siteUrl)); - } + RecipientName = recipientName ?? throw new ArgumentNullException(nameof(recipientName)); + Action = action ?? throw new ArgumentNullException(nameof(action)); + ItemName = itemName ?? throw new ArgumentNullException(nameof(itemName)); + ItemId = itemId ?? throw new ArgumentNullException(nameof(itemId)); + ItemUrl = itemUrl ?? throw new ArgumentNullException(nameof(itemUrl)); + Summary = summary ?? throw new ArgumentNullException(nameof(summary)); + EditedUser = editedUser ?? throw new ArgumentNullException(nameof(editedUser)); + SiteUrl = siteUrl ?? throw new ArgumentNullException(nameof(siteUrl)); + } - public string RecipientName { get; } - public string Action { get; } - public string ItemName { get; } - public string ItemId { get; } - public string ItemUrl { get; } + public string RecipientName { get; } + public string Action { get; } + public string ItemName { get; } + public string ItemId { get; } + public string ItemUrl { get; } - /// - /// This will either be an HTML or text based summary depending on the email type being sent - /// - public string Summary { get; } - public string EditedUser { get; } - public string SiteUrl { get; } - } + /// + /// This will either be an HTML or text based summary depending on the email type being sent + /// + public string Summary { get; } + + public string EditedUser { get; } + public string SiteUrl { get; } } diff --git a/src/Umbraco.Core/Models/NotificationEmailSubjectParams.cs b/src/Umbraco.Core/Models/NotificationEmailSubjectParams.cs index c644f7c1a698..91fe2275e329 100644 --- a/src/Umbraco.Core/Models/NotificationEmailSubjectParams.cs +++ b/src/Umbraco.Core/Models/NotificationEmailSubjectParams.cs @@ -1,19 +1,15 @@ -using System; +namespace Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Models +public class NotificationEmailSubjectParams { - - public class NotificationEmailSubjectParams + public NotificationEmailSubjectParams(string siteUrl, string? action, string? itemName) { - public NotificationEmailSubjectParams(string siteUrl, string? action, string? itemName) - { - SiteUrl = siteUrl ?? throw new ArgumentNullException(nameof(siteUrl)); - Action = action ?? throw new ArgumentNullException(nameof(action)); - ItemName = itemName ?? throw new ArgumentNullException(nameof(itemName)); - } - - public string SiteUrl { get; } - public string Action { get; } - public string ItemName { get; } + SiteUrl = siteUrl ?? throw new ArgumentNullException(nameof(siteUrl)); + Action = action ?? throw new ArgumentNullException(nameof(action)); + ItemName = itemName ?? throw new ArgumentNullException(nameof(itemName)); } + + public string SiteUrl { get; } + public string Action { get; } + public string ItemName { get; } } diff --git a/src/Umbraco.Core/Models/ObjectTypes.cs b/src/Umbraco.Core/Models/ObjectTypes.cs index 8e4eef3246d1..b2f21c342431 100644 --- a/src/Umbraco.Core/Models/ObjectTypes.cs +++ b/src/Umbraco.Core/Models/ObjectTypes.cs @@ -1,163 +1,153 @@ -using System; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Reflection; using Umbraco.Cms.Core.CodeAnnotations; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Provides utilities and extension methods to handle object types. +/// +public static class ObjectTypes { - /// - /// Provides utilities and extension methods to handle object types. - /// - public static class ObjectTypes + // must be concurrent to avoid thread collisions! + private static readonly ConcurrentDictionary UmbracoGuids = new(); + private static readonly ConcurrentDictionary UmbracoUdiTypes = new(); + private static readonly ConcurrentDictionary UmbracoFriendlyNames = new(); + private static readonly ConcurrentDictionary UmbracoTypes = new(); + private static readonly ConcurrentDictionary GuidUdiTypes = new(); + private static readonly ConcurrentDictionary GuidObjectTypes = new(); + private static readonly ConcurrentDictionary GuidTypes = new(); + + private static FieldInfo? GetEnumField(string name) => + typeof(UmbracoObjectTypes).GetField(name, BindingFlags.Public | BindingFlags.Static); + + private static FieldInfo? GetEnumField(Guid guid) { - // must be concurrent to avoid thread collisions! - private static readonly ConcurrentDictionary UmbracoGuids = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary UmbracoUdiTypes = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary UmbracoFriendlyNames = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary UmbracoTypes = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary GuidUdiTypes = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary GuidObjectTypes = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary GuidTypes = new ConcurrentDictionary(); - - private static FieldInfo? GetEnumField(string name) - { - return typeof (UmbracoObjectTypes).GetField(name, BindingFlags.Public | BindingFlags.Static); - } - - private static FieldInfo? GetEnumField(Guid guid) + FieldInfo[] fields = typeof(UmbracoObjectTypes).GetFields(BindingFlags.Public | BindingFlags.Static); + foreach (FieldInfo field in fields) { - var fields = typeof (UmbracoObjectTypes).GetFields(BindingFlags.Public | BindingFlags.Static); - foreach (var field in fields) + UmbracoObjectTypeAttribute attribute = field.GetCustomAttribute(false); + if (attribute != null && attribute.ObjectId == guid) { - var attribute = field.GetCustomAttribute(false); - if (attribute != null && attribute.ObjectId == guid) return field; + return field; } - - return null; } - /// - /// Gets the Umbraco object type corresponding to a name. - /// - public static UmbracoObjectTypes GetUmbracoObjectType(string name) - { - return (UmbracoObjectTypes) Enum.Parse(typeof (UmbracoObjectTypes), name, true); - } + return null; + } - #region Guid object type utilities + /// + /// Gets the Umbraco object type corresponding to a name. + /// + public static UmbracoObjectTypes GetUmbracoObjectType(string name) => + (UmbracoObjectTypes)Enum.Parse(typeof(UmbracoObjectTypes), name, true); - /// - /// Gets the Umbraco object type corresponding to an object type Guid. - /// - public static UmbracoObjectTypes GetUmbracoObjectType(Guid objectType) + #region Guid object type utilities + + /// + /// Gets the Umbraco object type corresponding to an object type Guid. + /// + public static UmbracoObjectTypes GetUmbracoObjectType(Guid objectType) => + GuidObjectTypes.GetOrAdd(objectType, t => { - return GuidObjectTypes.GetOrAdd(objectType, t => + FieldInfo field = GetEnumField(objectType); + if (field == null) { - var field = GetEnumField(objectType); - if (field == null) return UmbracoObjectTypes.Unknown; + return UmbracoObjectTypes.Unknown; + } - return (UmbracoObjectTypes?)field.GetValue(null) ?? UmbracoObjectTypes.Unknown; - }); - } + return (UmbracoObjectTypes?)field.GetValue(null) ?? UmbracoObjectTypes.Unknown; + }); - /// - /// Gets the Udi type corresponding to an object type Guid. - /// - public static string GetUdiType(Guid objectType) + /// + /// Gets the Udi type corresponding to an object type Guid. + /// + public static string GetUdiType(Guid objectType) => + GuidUdiTypes.GetOrAdd(objectType, t => { - return GuidUdiTypes.GetOrAdd(objectType, t => + FieldInfo field = GetEnumField(objectType); + if (field == null) { - var field = GetEnumField(objectType); - if (field == null) return Constants.UdiEntityType.Unknown; + return Constants.UdiEntityType.Unknown; + } - var attribute = field.GetCustomAttribute(false); - return attribute?.UdiType ?? Constants.UdiEntityType.Unknown; - }); - } + UmbracoUdiTypeAttribute attribute = field.GetCustomAttribute(false); + return attribute?.UdiType ?? Constants.UdiEntityType.Unknown; + }); - /// - /// Gets the CLR type corresponding to an object type Guid. - /// - public static Type? GetClrType(Guid objectType) + /// + /// Gets the CLR type corresponding to an object type Guid. + /// + public static Type? GetClrType(Guid objectType) => + GuidTypes.GetOrAdd(objectType, t => { - return GuidTypes.GetOrAdd(objectType, t => + FieldInfo field = GetEnumField(objectType); + if (field == null) { - var field = GetEnumField(objectType); - if (field == null) return null; + return null; + } - var attribute = field.GetCustomAttribute(false); - return attribute?.ModelType; - }); - } + UmbracoObjectTypeAttribute attribute = field.GetCustomAttribute(false); + return attribute?.ModelType; + }); - #endregion + #endregion - #region UmbracoObjectTypes extension methods + #region UmbracoObjectTypes extension methods - /// - /// Gets the object type Guid corresponding to this Umbraco object type. - /// - public static Guid GetGuid(this UmbracoObjectTypes objectType) + /// + /// Gets the object type Guid corresponding to this Umbraco object type. + /// + public static Guid GetGuid(this UmbracoObjectTypes objectType) => + UmbracoGuids.GetOrAdd(objectType, t => { - return UmbracoGuids.GetOrAdd(objectType, t => - { - var field = GetEnumField(t.ToString()); - var attribute = field?.GetCustomAttribute(false); + FieldInfo field = GetEnumField(t.ToString()); + UmbracoObjectTypeAttribute attribute = field?.GetCustomAttribute(false); - return attribute?.ObjectId ?? Guid.Empty; - }); - } + return attribute?.ObjectId ?? Guid.Empty; + }); - /// - /// Gets the Udi type corresponding to this Umbraco object type. - /// - public static string GetUdiType(this UmbracoObjectTypes objectType) + /// + /// Gets the Udi type corresponding to this Umbraco object type. + /// + public static string GetUdiType(this UmbracoObjectTypes objectType) => + UmbracoUdiTypes.GetOrAdd(objectType, t => { - return UmbracoUdiTypes.GetOrAdd(objectType, t => - { - var field = GetEnumField(t.ToString()); - var attribute = field?.GetCustomAttribute(false); + FieldInfo field = GetEnumField(t.ToString()); + UmbracoUdiTypeAttribute attribute = field?.GetCustomAttribute(false); - return attribute?.UdiType ?? Constants.UdiEntityType.Unknown; - }); - } + return attribute?.UdiType ?? Constants.UdiEntityType.Unknown; + }); - /// - /// Gets the name corresponding to this Umbraco object type. - /// - public static string? GetName(this UmbracoObjectTypes objectType) - { - return Enum.GetName(typeof (UmbracoObjectTypes), objectType); - } + /// + /// Gets the name corresponding to this Umbraco object type. + /// + public static string? GetName(this UmbracoObjectTypes objectType) => + Enum.GetName(typeof(UmbracoObjectTypes), objectType); - /// - /// Gets the friendly name corresponding to this Umbraco object type. - /// - public static string GetFriendlyName(this UmbracoObjectTypes objectType) + /// + /// Gets the friendly name corresponding to this Umbraco object type. + /// + public static string GetFriendlyName(this UmbracoObjectTypes objectType) => + UmbracoFriendlyNames.GetOrAdd(objectType, t => { - return UmbracoFriendlyNames.GetOrAdd(objectType, t => - { - var field = GetEnumField(t.ToString()); - var attribute = field?.GetCustomAttribute(false); + FieldInfo field = GetEnumField(t.ToString()); + FriendlyNameAttribute attribute = field?.GetCustomAttribute(false); - return attribute?.ToString() ?? string.Empty; - }); - } + return attribute?.ToString() ?? string.Empty; + }); - /// - /// Gets the CLR type corresponding to this Umbraco object type. - /// - public static Type? GetClrType(this UmbracoObjectTypes objectType) + /// + /// Gets the CLR type corresponding to this Umbraco object type. + /// + public static Type? GetClrType(this UmbracoObjectTypes objectType) => + UmbracoTypes.GetOrAdd(objectType, t => { - return UmbracoTypes.GetOrAdd(objectType, t => - { - var field = GetEnumField(t.ToString()); - var attribute = field?.GetCustomAttribute(false); + FieldInfo field = GetEnumField(t.ToString()); + UmbracoObjectTypeAttribute attribute = field?.GetCustomAttribute(false); - return attribute?.ModelType; - }); - } + return attribute?.ModelType; + }); - #endregion - } + #endregion } diff --git a/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs b/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs index e6c430627cf3..c30d435c297e 100644 --- a/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs +++ b/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs @@ -1,30 +1,26 @@ -using System; -using System.Collections.Generic; -using System.IO; using System.Xml.Linq; -namespace Umbraco.Cms.Core.Models.Packaging +namespace Umbraco.Cms.Core.Models.Packaging; + +/// +/// The model of the umbraco package data manifest (xml file) +/// +public class CompiledPackage { - /// - /// The model of the umbraco package data manifest (xml file) - /// - public class CompiledPackage - { - public FileInfo? PackageFile { get; set; } - public string Name { get; set; } = null!; - public InstallWarnings Warnings { get; set; } = new InstallWarnings(); - public IEnumerable Macros { get; set; } = null!; // TODO: make strongly typed - public IEnumerable MacroPartialViews { get; set; } = null!; // TODO: make strongly typed - public IEnumerable Templates { get; set; } = null!; // TODO: make strongly typed - public IEnumerable Stylesheets { get; set; } = null!; // TODO: make strongly typed - public IEnumerable Scripts { get; set; } = null!; // TODO: make strongly typed - public IEnumerable PartialViews { get; set; } = null!; // TODO: make strongly typed - public IEnumerable DataTypes { get; set; } = null!; // TODO: make strongly typed - public IEnumerable Languages { get; set; } = null!; // TODO: make strongly typed - public IEnumerable DictionaryItems { get; set; } = null!; // TODO: make strongly typed - public IEnumerable DocumentTypes { get; set; } = null!; // TODO: make strongly typed - public IEnumerable MediaTypes { get; set; } = null!; // TODO: make strongly typed - public IEnumerable Documents { get; set; } = null!; - public IEnumerable Media { get; set; } = null!; - } + public FileInfo? PackageFile { get; set; } + public string Name { get; set; } = null!; + public InstallWarnings Warnings { get; set; } = new(); + public IEnumerable Macros { get; set; } = null!; // TODO: make strongly typed + public IEnumerable MacroPartialViews { get; set; } = null!; // TODO: make strongly typed + public IEnumerable Templates { get; set; } = null!; // TODO: make strongly typed + public IEnumerable Stylesheets { get; set; } = null!; // TODO: make strongly typed + public IEnumerable Scripts { get; set; } = null!; // TODO: make strongly typed + public IEnumerable PartialViews { get; set; } = null!; // TODO: make strongly typed + public IEnumerable DataTypes { get; set; } = null!; // TODO: make strongly typed + public IEnumerable Languages { get; set; } = null!; // TODO: make strongly typed + public IEnumerable DictionaryItems { get; set; } = null!; // TODO: make strongly typed + public IEnumerable DocumentTypes { get; set; } = null!; // TODO: make strongly typed + public IEnumerable MediaTypes { get; set; } = null!; // TODO: make strongly typed + public IEnumerable Documents { get; set; } = null!; + public IEnumerable Media { get; set; } = null!; } diff --git a/src/Umbraco.Core/Models/Packaging/CompiledPackageContentBase.cs b/src/Umbraco.Core/Models/Packaging/CompiledPackageContentBase.cs index 0fb1c609085e..2066d27932c0 100644 --- a/src/Umbraco.Core/Models/Packaging/CompiledPackageContentBase.cs +++ b/src/Umbraco.Core/Models/Packaging/CompiledPackageContentBase.cs @@ -1,25 +1,20 @@ using System.Xml.Linq; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.Packaging +namespace Umbraco.Cms.Core.Models.Packaging; + +/// +/// Compiled representation of a content base (Document or Media) +/// +public class CompiledPackageContentBase { + public string? ImportMode { get; set; } //this is never used + /// - /// Compiled representation of a content base (Document or Media) + /// The serialized version of the content /// - public class CompiledPackageContentBase - { - public static CompiledPackageContentBase Create(XElement xml) => - new CompiledPackageContentBase - { - XmlData = xml, - ImportMode = xml.AttributeValue("importMode") - }; - - public string? ImportMode { get; set; } //this is never used + public XElement XmlData { get; set; } = null!; - /// - /// The serialized version of the content - /// - public XElement XmlData { get; set; } = null!; - } + public static CompiledPackageContentBase Create(XElement xml) => + new() {XmlData = xml, ImportMode = xml.AttributeValue("importMode")}; } diff --git a/src/Umbraco.Core/Models/Packaging/InstallWarnings.cs b/src/Umbraco.Core/Models/Packaging/InstallWarnings.cs index 7cad9b5b9a0d..8f7c6646375a 100644 --- a/src/Umbraco.Core/Models/Packaging/InstallWarnings.cs +++ b/src/Umbraco.Core/Models/Packaging/InstallWarnings.cs @@ -1,14 +1,9 @@ -using System.Collections.Generic; -using System.Linq; +namespace Umbraco.Cms.Core.Models.Packaging; -namespace Umbraco.Cms.Core.Models.Packaging +public class InstallWarnings { - - public class InstallWarnings - { - // TODO: Shouldn't we detect other conflicting entities too ? - public IEnumerable? ConflictingMacros { get; set; } = Enumerable.Empty(); - public IEnumerable? ConflictingTemplates { get; set; } = Enumerable.Empty(); - public IEnumerable? ConflictingStylesheets { get; set; } = Enumerable.Empty(); - } + // TODO: Shouldn't we detect other conflicting entities too ? + public IEnumerable? ConflictingMacros { get; set; } = Enumerable.Empty(); + public IEnumerable? ConflictingTemplates { get; set; } = Enumerable.Empty(); + public IEnumerable? ConflictingStylesheets { get; set; } = Enumerable.Empty(); } diff --git a/src/Umbraco.Core/Models/PagedResult.cs b/src/Umbraco.Core/Models/PagedResult.cs index f15768cc2d9d..24133efdf2a2 100644 --- a/src/Umbraco.Core/Models/PagedResult.cs +++ b/src/Umbraco.Core/Models/PagedResult.cs @@ -1,56 +1,51 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a paged result for a model collection +/// +/// +[DataContract(Name = "pagedCollection", Namespace = "")] +public abstract class PagedResult { - /// - /// Represents a paged result for a model collection - /// - /// - [DataContract(Name = "pagedCollection", Namespace = "")] - public abstract class PagedResult + public PagedResult(long totalItems, long pageNumber, long pageSize) { - public PagedResult(long totalItems, long pageNumber, long pageSize) + TotalItems = totalItems; + PageNumber = pageNumber; + PageSize = pageSize; + + if (pageSize > 0) + { + TotalPages = (long)Math.Ceiling(totalItems / (decimal)pageSize); + } + else { - TotalItems = totalItems; - PageNumber = pageNumber; - PageSize = pageSize; - - if (pageSize > 0) - { - TotalPages = (long)Math.Ceiling(totalItems / (decimal)pageSize); - } - else - { - TotalPages = 1; - } + TotalPages = 1; } + } - [DataMember(Name = "pageNumber")] - public long PageNumber { get; private set; } + [DataMember(Name = "pageNumber")] public long PageNumber { get; private set; } - [DataMember(Name = "pageSize")] - public long PageSize { get; private set; } + [DataMember(Name = "pageSize")] public long PageSize { get; private set; } - [DataMember(Name = "totalPages")] - public long TotalPages { get; private set; } + [DataMember(Name = "totalPages")] public long TotalPages { get; private set; } - [DataMember(Name = "totalItems")] - public long TotalItems { get; private set; } + [DataMember(Name = "totalItems")] public long TotalItems { get; private set; } - /// - /// Calculates the skip size based on the paged parameters specified - /// - /// - /// Returns 0 if the page number or page size is zero - /// - public int GetSkipSize() + /// + /// Calculates the skip size based on the paged parameters specified + /// + /// + /// Returns 0 if the page number or page size is zero + /// + public int GetSkipSize() + { + if (PageNumber > 0 && PageSize > 0) { - if (PageNumber > 0 && PageSize > 0) - { - return Convert.ToInt32((PageNumber - 1) * PageSize); - } - return 0; + return Convert.ToInt32((PageNumber - 1) * PageSize); } + + return 0; } } diff --git a/src/Umbraco.Core/Models/PagedResultOfT.cs b/src/Umbraco.Core/Models/PagedResultOfT.cs index 125256ec3bb9..eb0a28a0925f 100644 --- a/src/Umbraco.Core/Models/PagedResultOfT.cs +++ b/src/Umbraco.Core/Models/PagedResultOfT.cs @@ -1,20 +1,18 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a paged result for a model collection +/// +/// +[DataContract(Name = "pagedCollection", Namespace = "")] +public class PagedResult : PagedResult { - /// - /// Represents a paged result for a model collection - /// - /// - [DataContract(Name = "pagedCollection", Namespace = "")] - public class PagedResult : PagedResult + public PagedResult(long totalItems, long pageNumber, long pageSize) + : base(totalItems, pageNumber, pageSize) { - public PagedResult(long totalItems, long pageNumber, long pageSize) - : base(totalItems, pageNumber, pageSize) - { } - - [DataMember(Name = "items")] - public IEnumerable? Items { get; set; } } + + [DataMember(Name = "items")] public IEnumerable? Items { get; set; } } diff --git a/src/Umbraco.Core/Models/PartialView.cs b/src/Umbraco.Core/Models/PartialView.cs index ffa9412c5195..132404a9ecec 100644 --- a/src/Umbraco.Core/Models/PartialView.cs +++ b/src/Umbraco.Core/Models/PartialView.cs @@ -1,25 +1,22 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a Partial View file +/// +[Serializable] +[DataContract(IsReference = true)] +public class PartialView : File, IPartialView { - /// - /// Represents a Partial View file - /// - [Serializable] - [DataContract(IsReference = true)] - public class PartialView : File, IPartialView + public PartialView(PartialViewType viewType, string path) + : this(viewType, path, null) { - public PartialView(PartialViewType viewType, string path) - : this(viewType, path, null) - { } + } - public PartialView(PartialViewType viewType, string path, Func? getFileContent) - : base(path, getFileContent) - { - ViewType = viewType; - } + public PartialView(PartialViewType viewType, string path, Func? getFileContent) + : base(path, getFileContent) => + ViewType = viewType; - public PartialViewType ViewType { get; set; } - } + public PartialViewType ViewType { get; set; } } diff --git a/src/Umbraco.Core/Models/PartialViewMacroModel.cs b/src/Umbraco.Core/Models/PartialViewMacroModel.cs index 662894b39faa..2a8ce781f016 100644 --- a/src/Umbraco.Core/Models/PartialViewMacroModel.cs +++ b/src/Umbraco.Core/Models/PartialViewMacroModel.cs @@ -1,31 +1,29 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// The model used when rendering Partial View Macros +/// +public class PartialViewMacroModel : IContentModel { - /// - /// The model used when rendering Partial View Macros - /// - public class PartialViewMacroModel : IContentModel + public PartialViewMacroModel(IPublishedContent page, + int macroId, + string? macroAlias, + string? macroName, + IDictionary macroParams) { + Content = page; + MacroParameters = macroParams; + MacroName = macroName; + MacroAlias = macroAlias; + MacroId = macroId; + } - public PartialViewMacroModel(IPublishedContent page, - int macroId, - string? macroAlias, - string? macroName, - IDictionary macroParams) - { - Content = page; - MacroParameters = macroParams; - MacroName = macroName; - MacroAlias = macroAlias; - MacroId = macroId; - } + public string? MacroName { get; } + public string? MacroAlias { get; } + public int MacroId { get; } + public IDictionary MacroParameters { get; } - public IPublishedContent Content { get; } - public string? MacroName { get; } - public string? MacroAlias { get; } - public int MacroId { get; } - public IDictionary MacroParameters { get; } - } + public IPublishedContent Content { get; } } diff --git a/src/Umbraco.Core/Models/PartialViewMacroModelExtensions.cs b/src/Umbraco.Core/Models/PartialViewMacroModelExtensions.cs index aea801719f69..f36c75723ced 100644 --- a/src/Umbraco.Core/Models/PartialViewMacroModelExtensions.cs +++ b/src/Umbraco.Core/Models/PartialViewMacroModelExtensions.cs @@ -1,38 +1,40 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Extension methods for the PartialViewMacroModel object +/// +public static class PartialViewMacroModelExtensions { /// - /// Extension methods for the PartialViewMacroModel object + /// Attempt to get a Macro parameter from a PartialViewMacroModel and return a default value otherwise /// - public static class PartialViewMacroModelExtensions + /// + /// + /// + /// Parameter value if available, the default value that was passed otherwise. + public static T? GetParameterValue(this PartialViewMacroModel partialViewMacroModel, string parameterAlias, + T defaultValue) { - /// - /// Attempt to get a Macro parameter from a PartialViewMacroModel and return a default value otherwise - /// - /// - /// - /// - /// Parameter value if available, the default value that was passed otherwise. - public static T? GetParameterValue(this PartialViewMacroModel partialViewMacroModel, string parameterAlias, T defaultValue) + if (partialViewMacroModel.MacroParameters.ContainsKey(parameterAlias) == false || + string.IsNullOrEmpty(partialViewMacroModel.MacroParameters[parameterAlias]?.ToString())) { - if (partialViewMacroModel.MacroParameters.ContainsKey(parameterAlias) == false || string.IsNullOrEmpty(partialViewMacroModel.MacroParameters[parameterAlias]?.ToString())) - return defaultValue; - - var attempt = partialViewMacroModel.MacroParameters[parameterAlias].TryConvertTo(typeof(T)); - - return attempt.Success ? (T?) attempt.Result : defaultValue; + return defaultValue; } - /// - /// Attempt to get a Macro parameter from a PartialViewMacroModel - /// - /// - /// - /// Parameter value if available, the default value for the type otherwise. - public static T? GetParameterValue(this PartialViewMacroModel partialViewMacroModel, string parameterAlias) - { - return partialViewMacroModel.GetParameterValue(parameterAlias, default(T)); - } + Attempt attempt = partialViewMacroModel.MacroParameters[parameterAlias].TryConvertTo(typeof(T)); + + return attempt.Success ? (T?)attempt.Result : defaultValue; } + + /// + /// Attempt to get a Macro parameter from a PartialViewMacroModel + /// + /// + /// + /// Parameter value if available, the default value for the type otherwise. + public static T? GetParameterValue(this PartialViewMacroModel partialViewMacroModel, string parameterAlias) => + partialViewMacroModel.GetParameterValue(parameterAlias, default(T)); } diff --git a/src/Umbraco.Core/Models/PartialViewType.cs b/src/Umbraco.Core/Models/PartialViewType.cs index 5dc6dbc59cad..761a963cbfa3 100644 --- a/src/Umbraco.Core/Models/PartialViewType.cs +++ b/src/Umbraco.Core/Models/PartialViewType.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public enum PartialViewType : byte { - public enum PartialViewType : byte - { - Unknown = 0, // default - PartialView = 1, - PartialViewMacro = 2 - } + Unknown = 0, // default + PartialView = 1, + PartialViewMacro = 2 } diff --git a/src/Umbraco.Core/Models/PasswordChangedModel.cs b/src/Umbraco.Core/Models/PasswordChangedModel.cs index 231940f105f7..5f70171831c5 100644 --- a/src/Umbraco.Core/Models/PasswordChangedModel.cs +++ b/src/Umbraco.Core/Models/PasswordChangedModel.cs @@ -1,20 +1,19 @@ using System.ComponentModel.DataAnnotations; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// A model representing an attempt at changing a password +/// +public class PasswordChangedModel { /// - /// A model representing an attempt at changing a password + /// The error affiliated with the failing password changes, null if changing was successful /// - public class PasswordChangedModel - { - /// - /// The error affiliated with the failing password changes, null if changing was successful - /// - public ValidationResult? ChangeError { get; set; } + public ValidationResult? ChangeError { get; set; } - /// - /// If the password was reset, this is the value it has been changed to - /// - public string? ResetPassword { get; set; } - } + /// + /// If the password was reset, this is the value it has been changed to + /// + public string? ResetPassword { get; set; } } diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index f4bba10c2c98..469d091fa484 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -1,557 +1,655 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; +using System.Collections; using System.Runtime.Serialization; using Umbraco.Cms.Core.Collections; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models -{ - /// - /// Represents a property. - /// - [Serializable] - [DataContract(IsReference = true)] - public class Property : EntityBase, IProperty - { - // _values contains all property values, including the invariant-neutral value - private List _values = new List(); - - // _pvalue contains the invariant-neutral property value - private IPropertyValue? _pvalue; +namespace Umbraco.Cms.Core.Models; - // _vvalues contains the (indexed) variant property values - private Dictionary? _vvalues; - - /// - /// Initializes a new instance of the class. - /// - public Property(IPropertyType propertyType) - { - PropertyType = propertyType; - } - - /// - /// Initializes a new instance of the class. - /// - public Property(int id, IPropertyType propertyType) +/// +/// Represents a property. +/// +[Serializable] +[DataContract(IsReference = true)] +public class Property : EntityBase, IProperty +{ + private static readonly DelegateEqualityComparer PropertyValueComparer = new( + (o, o1) => { - Id = id; - PropertyType = propertyType; - } - - /// - /// Creates a new instance for existing - /// - /// - /// - /// - /// Generally will contain a published and an unpublished property values - /// - /// - public static Property CreateWithValues(int id, IPropertyType propertyType, params InitialPropertyValue[] values) - { - var property = new Property(propertyType); - try - { - property.DisableChangeTracking(); - property.Id = id; - foreach(var value in values) - { - property.FactorySetValue(value.Culture, value.Segment, value.Published, value.Value); - } - property.ResetDirtyProperties(false); - return property; - } - finally + if (o == null && o1 == null) { - property.EnableChangeTracking(); + return true; } - } - /// - /// Used for constructing a new instance - /// - public class InitialPropertyValue - { - public InitialPropertyValue(string? culture, string? segment, bool published, object? value) + // custom comparer for strings. + // if one is null and another is empty then they are the same + if (o is string || o1 is string) { - Culture = culture; - Segment = segment; - Published = published; - Value = value; + return ((o as string).IsNullOrWhiteSpace() && (o1 as string).IsNullOrWhiteSpace()) || + (o != null && o1 != null && o.Equals(o1)); } - public string? Culture { get; } - public string? Segment { get; } - public bool Published { get; } - public object? Value { get; } - } - - /// - /// Represents a property value. - /// - public class PropertyValue : IPropertyValue, IDeepCloneable, IEquatable - { - // TODO: Either we allow change tracking at this class level, or we add some special change tracking collections to the Property - // class to deal with change tracking which variants have changed - - private string? _culture; - private string? _segment; - - /// - /// Gets or sets the culture of the property. - /// - /// The culture is either null (invariant) or a non-empty string. If the property is - /// set with an empty or whitespace value, its value is converted to null. - public string? Culture + if (o == null || o1 == null) { - get => _culture; - set => _culture = value.IsNullOrWhiteSpace() ? null : value!.ToLowerInvariant(); + return false; } - /// - /// Gets or sets the segment of the property. - /// - /// The segment is either null (neutral) or a non-empty string. If the property is - /// set with an empty or whitespace value, its value is converted to null. - public string? Segment + // custom comparer for enumerable + // ReSharper disable once MergeCastWithTypeCheck + if (o is IEnumerable && o1 is IEnumerable enumerable) { - get => _segment; - set => _segment = value?.ToLowerInvariant(); + return ((IEnumerable)o).Cast().UnsortedSequenceEqual(enumerable.Cast()); } - /// - /// Gets or sets the edited value of the property. - /// - public object? EditedValue { get; set; } + return o.Equals(o1); + }, o => o!.GetHashCode()); - /// - /// Gets or sets the published value of the property. - /// - public object? PublishedValue { get; set; } + // _pvalue contains the invariant-neutral property value + private IPropertyValue? _pvalue; - /// - /// Clones the property value. - /// - public IPropertyValue Clone() - => new PropertyValue { _culture = _culture, _segment = _segment, PublishedValue = PublishedValue, EditedValue = EditedValue }; + // _values contains all property values, including the invariant-neutral value + private List _values = new(); - public object DeepClone() => Clone(); + // _vvalues contains the (indexed) variant property values + private Dictionary? _vvalues; - public override bool Equals(object? obj) - { - return Equals(obj as PropertyValue); - } - - public bool Equals(PropertyValue? other) - { - return other != null && - _culture == other._culture && - _segment == other._segment && - EqualityComparer.Default.Equals(EditedValue, other.EditedValue) && - EqualityComparer.Default.Equals(PublishedValue, other.PublishedValue); - } - - public override int GetHashCode() - { - var hashCode = 1885328050; - if (_culture is not null) - { - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_culture); - } + /// + /// Initializes a new instance of the class. + /// + public Property(IPropertyType propertyType) => PropertyType = propertyType; - if (_segment is not null) - { - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_segment); - } + /// + /// Initializes a new instance of the class. + /// + public Property(int id, IPropertyType propertyType) + { + Id = id; + PropertyType = propertyType; + } - if (EditedValue is not null) - { - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(EditedValue); - } + /// + /// Returns the PropertyType, which this Property is based on + /// + [IgnoreDataMember] + public IPropertyType PropertyType { get; private set; } - if (PublishedValue is not null) - { - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(PublishedValue); - } - return hashCode; - } + /// + /// Gets the list of values. + /// + [DataMember] + public IReadOnlyCollection Values + { + get => _values; + set + { + // make sure we filter out invalid variations + // make sure we leave _vvalues null if possible + _values = value.Where(x => PropertyType?.SupportsVariation(x.Culture, x.Segment) ?? false).ToList(); + _pvalue = _values.FirstOrDefault(x => x.Culture == null && x.Segment == null); + _vvalues = _values.Count > (_pvalue == null ? 0 : 1) + ? _values.Where(x => x != _pvalue) + .ToDictionary(x => new CompositeNStringNStringKey(x.Culture, x.Segment), x => x) + : null; } + } - private static readonly DelegateEqualityComparer PropertyValueComparer = new DelegateEqualityComparer( - (o, o1) => - { - if (o == null && o1 == null) return true; - - // custom comparer for strings. - // if one is null and another is empty then they are the same - if (o is string || o1 is string) - return ((o as string).IsNullOrWhiteSpace() && (o1 as string).IsNullOrWhiteSpace()) || (o != null && o1 != null && o.Equals(o1)); + /// + /// Returns the Alias of the PropertyType, which this Property is based on + /// + [DataMember] + public string Alias => PropertyType.Alias; - if (o == null || o1 == null) return false; + /// + /// Returns the Id of the PropertyType, which this Property is based on + /// + [IgnoreDataMember] + public int PropertyTypeId => PropertyType.Id; - // custom comparer for enumerable - // ReSharper disable once MergeCastWithTypeCheck - if (o is IEnumerable && o1 is IEnumerable enumerable) - return ((IEnumerable)o).Cast().UnsortedSequenceEqual(enumerable.Cast()); + /// + /// Returns the DatabaseType that the underlaying DataType is using to store its values + /// + /// + /// Only used internally when saving the property value. + /// + [IgnoreDataMember] + public ValueStorageType ValueStorageType => PropertyType.ValueStorageType; - return o.Equals(o1); - }, o => o!.GetHashCode()); + /// + /// Gets the value. + /// + public object? GetValue(string? culture = null, string? segment = null, bool published = false) + { + // ensure null or whitespace are nulls + culture = culture?.NullOrWhiteSpaceAsNull(); + segment = segment?.NullOrWhiteSpaceAsNull(); - /// - /// Returns the PropertyType, which this Property is based on - /// - [IgnoreDataMember] - public IPropertyType PropertyType { get; private set; } + if (!PropertyType.SupportsVariation(culture, segment)) + { + return null; + } - /// - /// Gets the list of values. - /// - [DataMember] - public IReadOnlyCollection Values + if (culture == null && segment == null) { - get => _values; - set - { - // make sure we filter out invalid variations - // make sure we leave _vvalues null if possible - _values = value.Where(x => PropertyType?.SupportsVariation(x.Culture, x.Segment) ?? false).ToList(); - _pvalue = _values.FirstOrDefault(x => x.Culture == null && x.Segment == null); - _vvalues = _values.Count > (_pvalue == null ? 0 : 1) - ? _values.Where(x => x != _pvalue).ToDictionary(x => new CompositeNStringNStringKey(x.Culture, x.Segment), x => x) - : null; - } + return GetPropertyValue(_pvalue, published); } - /// - /// Returns the Alias of the PropertyType, which this Property is based on - /// - [DataMember] - public string Alias => PropertyType.Alias; + if (_vvalues == null) + { + return null; + } - /// - /// Returns the Id of the PropertyType, which this Property is based on - /// - [IgnoreDataMember] - public int PropertyTypeId => PropertyType.Id; + return _vvalues.TryGetValue(new CompositeNStringNStringKey(culture, segment), out IPropertyValue pvalue) + ? GetPropertyValue(pvalue, published) + : null; + } - /// - /// Returns the DatabaseType that the underlaying DataType is using to store its values - /// - /// - /// Only used internally when saving the property value. - /// - [IgnoreDataMember] - public ValueStorageType ValueStorageType => PropertyType.ValueStorageType; + // internal - must be invoked by the content item + // does *not* validate the value - content item must validate first + public void PublishValues(string? culture = "*", string? segment = "*") + { + culture = culture?.NullOrWhiteSpaceAsNull(); + segment = segment?.NullOrWhiteSpaceAsNull(); - /// - /// Gets the value. - /// - public object? GetValue(string? culture = null, string? segment = null, bool published = false) + // if invariant or all, and invariant-neutral is supported, publish invariant-neutral + if ((culture == null || culture == "*") && (segment == null || segment == "*") && + PropertyType.SupportsVariation(null, null)) { - // ensure null or whitespace are nulls - culture = culture?.NullOrWhiteSpaceAsNull(); - segment = segment?.NullOrWhiteSpaceAsNull(); - - if (!PropertyType.SupportsVariation(culture, segment)) return null; - if (culture == null && segment == null) return GetPropertyValue(_pvalue, published); - if (_vvalues == null) return null; - return _vvalues.TryGetValue(new CompositeNStringNStringKey(culture, segment), out var pvalue) - ? GetPropertyValue(pvalue, published) - : null; + PublishValue(_pvalue); } - private object? GetPropertyValue(IPropertyValue? pvalue, bool published) + // then deal with everything that varies + if (_vvalues == null) { - if (pvalue == null) return null; - - return PropertyType.SupportsPublishing - ? (published ? pvalue.PublishedValue : pvalue.EditedValue) - : pvalue.EditedValue; + return; } - // internal - must be invoked by the content item - // does *not* validate the value - content item must validate first - public void PublishValues(string? culture = "*", string? segment = "*") - { - culture = culture?.NullOrWhiteSpaceAsNull(); - segment = segment?.NullOrWhiteSpaceAsNull(); - - // if invariant or all, and invariant-neutral is supported, publish invariant-neutral - if ((culture == null || culture == "*") && (segment == null || segment == "*") && PropertyType.SupportsVariation(null, null)) - PublishValue(_pvalue); + // get the property values that are still relevant (wrt the property type variation), + // and match the specified culture and segment (or anything when '*'). + IEnumerable pvalues = _vvalues.Where(x => + PropertyType.SupportsVariation(x.Value.Culture, x.Value.Segment, true) && // the value variation is ok + (culture == "*" || (x.Value.Culture?.InvariantEquals(culture) ?? false)) && // the culture matches + (segment == "*" || (x.Value.Segment?.InvariantEquals(segment) ?? false))) // the segment matches + .Select(x => x.Value); - // then deal with everything that varies - if (_vvalues == null) return; + foreach (IPropertyValue pvalue in pvalues) + { + PublishValue(pvalue); + } + } - // get the property values that are still relevant (wrt the property type variation), - // and match the specified culture and segment (or anything when '*'). - var pvalues = _vvalues.Where(x => - PropertyType.SupportsVariation(x.Value.Culture, x.Value.Segment, true) && // the value variation is ok - (culture == "*" || (x.Value.Culture?.InvariantEquals(culture) ?? false)) && // the culture matches - (segment == "*" || (x.Value.Segment?.InvariantEquals(segment) ?? false))) // the segment matches - .Select(x => x.Value); + // internal - must be invoked by the content item + public void UnpublishValues(string? culture = "*", string? segment = "*") + { + culture = culture?.NullOrWhiteSpaceAsNull(); + segment = segment?.NullOrWhiteSpaceAsNull(); - foreach (var pvalue in pvalues) - PublishValue(pvalue); + // if invariant or all, and invariant-neutral is supported, publish invariant-neutral + if ((culture == null || culture == "*") && (segment == null || segment == "*") && + PropertyType.SupportsVariation(null, null)) + { + UnpublishValue(_pvalue); } - // internal - must be invoked by the content item - public void UnpublishValues(string? culture = "*", string? segment = "*") + // then deal with everything that varies + if (_vvalues == null) { - culture = culture?.NullOrWhiteSpaceAsNull(); - segment = segment?.NullOrWhiteSpaceAsNull(); + return; + } - // if invariant or all, and invariant-neutral is supported, publish invariant-neutral - if ((culture == null || culture == "*") && (segment == null || segment == "*") && PropertyType.SupportsVariation(null, null)) - UnpublishValue(_pvalue); + // get the property values that are still relevant (wrt the property type variation), + // and match the specified culture and segment (or anything when '*'). + IEnumerable pvalues = _vvalues.Where(x => + PropertyType.SupportsVariation(x.Value.Culture, x.Value.Segment, true) && // the value variation is ok + (culture == "*" || (x.Value.Culture?.InvariantEquals(culture) ?? false)) && // the culture matches + (segment == "*" || (x.Value.Segment?.InvariantEquals(segment) ?? false))) // the segment matches + .Select(x => x.Value); - // then deal with everything that varies - if (_vvalues == null) return; + foreach (IPropertyValue pvalue in pvalues) + { + UnpublishValue(pvalue); + } + } - // get the property values that are still relevant (wrt the property type variation), - // and match the specified culture and segment (or anything when '*'). - var pvalues = _vvalues.Where(x => - PropertyType.SupportsVariation(x.Value.Culture, x.Value.Segment, true) && // the value variation is ok - (culture == "*" || (x.Value.Culture?.InvariantEquals(culture) ?? false)) && // the culture matches - (segment == "*" || (x.Value.Segment?.InvariantEquals(segment) ?? false))) // the segment matches - .Select(x => x.Value); + /// + /// Sets a value. + /// + public void SetValue(object? value, string? culture = null, string? segment = null) + { + culture = culture?.NullOrWhiteSpaceAsNull(); + segment = segment?.NullOrWhiteSpaceAsNull(); - foreach (var pvalue in pvalues) - UnpublishValue(pvalue); + if (!PropertyType.SupportsVariation(culture, segment)) + { + throw new NotSupportedException( + $"Variation \"{culture ?? ""},{segment ?? ""}\" is not supported by the property type."); } - private void PublishValue(IPropertyValue? pvalue) + (IPropertyValue pvalue, var change) = GetPValue(culture, segment, true); + + if (pvalue is not null) { - if (pvalue == null) return; + var origValue = pvalue.EditedValue; + var setValue = ConvertAssignedValue(value); + + pvalue.EditedValue = setValue; - if (!PropertyType.SupportsPublishing) - throw new NotSupportedException("Property type does not support publishing."); - var origValue = pvalue.PublishedValue; - pvalue.PublishedValue = ConvertAssignedValue(pvalue.EditedValue); - DetectChanges(pvalue.EditedValue, origValue, nameof(Values), PropertyValueComparer, false); + DetectChanges(setValue, origValue, nameof(Values), PropertyValueComparer, change); } + } - private void UnpublishValue(IPropertyValue? pvalue) + /// + /// Creates a new instance for existing + /// + /// + /// + /// + /// Generally will contain a published and an unpublished property values + /// + /// + public static Property CreateWithValues(int id, IPropertyType propertyType, params InitialPropertyValue[] values) + { + var property = new Property(propertyType); + try { - if (pvalue == null) return; + property.DisableChangeTracking(); + property.Id = id; + foreach (InitialPropertyValue value in values) + { + property.FactorySetValue(value.Culture, value.Segment, value.Published, value.Value); + } - if (!PropertyType.SupportsPublishing) - throw new NotSupportedException("Property type does not support publishing."); - var origValue = pvalue.PublishedValue; - pvalue.PublishedValue = ConvertAssignedValue(null); - DetectChanges(pvalue.EditedValue, origValue, nameof(Values), PropertyValueComparer, false); + property.ResetDirtyProperties(false); + return property; + } + finally + { + property.EnableChangeTracking(); } + } - /// - /// Sets a value. - /// - public void SetValue(object? value, string? culture = null, string? segment = null) + private object? GetPropertyValue(IPropertyValue? pvalue, bool published) + { + if (pvalue == null) { - culture = culture?.NullOrWhiteSpaceAsNull(); - segment = segment?.NullOrWhiteSpaceAsNull(); + return null; + } - if (!PropertyType.SupportsVariation(culture, segment)) - throw new NotSupportedException($"Variation \"{culture??""},{segment??""}\" is not supported by the property type."); + return PropertyType.SupportsPublishing + ? published ? pvalue.PublishedValue : pvalue.EditedValue + : pvalue.EditedValue; + } - var (pvalue, change) = GetPValue(culture, segment, true); + private void PublishValue(IPropertyValue? pvalue) + { + if (pvalue == null) + { + return; + } - if (pvalue is not null) - { - var origValue = pvalue.EditedValue; - var setValue = ConvertAssignedValue(value); + if (!PropertyType.SupportsPublishing) + { + throw new NotSupportedException("Property type does not support publishing."); + } - pvalue.EditedValue = setValue; + var origValue = pvalue.PublishedValue; + pvalue.PublishedValue = ConvertAssignedValue(pvalue.EditedValue); + DetectChanges(pvalue.EditedValue, origValue, nameof(Values), PropertyValueComparer, false); + } - DetectChanges(setValue, origValue, nameof(Values), PropertyValueComparer, change); - } + private void UnpublishValue(IPropertyValue? pvalue) + { + if (pvalue == null) + { + return; } - // bypasses all changes detection and is the *only* way to set the published value - private void FactorySetValue(string? culture, string? segment, bool published, object? value) + if (!PropertyType.SupportsPublishing) { - var (pvalue, _) = GetPValue(culture, segment, true); + throw new NotSupportedException("Property type does not support publishing."); + } - if (pvalue is not null) + var origValue = pvalue.PublishedValue; + pvalue.PublishedValue = ConvertAssignedValue(null); + DetectChanges(pvalue.EditedValue, origValue, nameof(Values), PropertyValueComparer, false); + } + + // bypasses all changes detection and is the *only* way to set the published value + private void FactorySetValue(string? culture, string? segment, bool published, object? value) + { + (IPropertyValue pvalue, _) = GetPValue(culture, segment, true); + + if (pvalue is not null) + { + if (published && PropertyType.SupportsPublishing) + { + pvalue.PublishedValue = value; + } + else { - if (published && PropertyType.SupportsPublishing) - pvalue.PublishedValue = value; - else - pvalue.EditedValue = value; + pvalue.EditedValue = value; } } + } - private (IPropertyValue?, bool) GetPValue(bool create) + private (IPropertyValue?, bool) GetPValue(bool create) + { + var change = false; + if (_pvalue == null) { - var change = false; - if (_pvalue == null) + if (!create) { - if (!create) return (null, false); - _pvalue = new PropertyValue(); - _values.Add(_pvalue); - change = true; + return (null, false); } - return (_pvalue, change); + + _pvalue = new PropertyValue(); + _values.Add(_pvalue); + change = true; } - private (IPropertyValue?, bool) GetPValue(string? culture, string? segment, bool create) + return (_pvalue, change); + } + + private (IPropertyValue?, bool) GetPValue(string? culture, string? segment, bool create) + { + if (culture == null && segment == null) { - if (culture == null && segment == null) - return GetPValue(create); + return GetPValue(create); + } - var change = false; - if (_vvalues == null) + var change = false; + if (_vvalues == null) + { + if (!create) { - if (!create) return (null, false); - _vvalues = new Dictionary(); - change = true; + return (null, false); } - var k = new CompositeNStringNStringKey(culture, segment); - if (!_vvalues.TryGetValue(k, out var pvalue)) + + _vvalues = new Dictionary(); + change = true; + } + + var k = new CompositeNStringNStringKey(culture, segment); + if (!_vvalues.TryGetValue(k, out IPropertyValue pvalue)) + { + if (!create) { - if (!create) return (null, false); - pvalue = _vvalues[k] = new PropertyValue(); - pvalue.Culture = culture; - pvalue.Segment = segment; - _values.Add(pvalue); - change = true; + return (null, false); } - return (pvalue, change); + + pvalue = _vvalues[k] = new PropertyValue(); + pvalue.Culture = culture; + pvalue.Segment = segment; + _values.Add(pvalue); + change = true; } - /// - public object? ConvertAssignedValue(object? value) => TryConvertAssignedValue(value, true, out var converted) ? converted : null; + return (pvalue, change); + } - /// - /// Tries to convert a value assigned to a property. - /// - /// - /// - /// - private bool TryConvertAssignedValue(object? value, bool throwOnError, out object? converted) + /// + public object? ConvertAssignedValue(object? value) => + TryConvertAssignedValue(value, true, out var converted) ? converted : null; + + /// + /// Tries to convert a value assigned to a property. + /// + /// + /// + /// + private bool TryConvertAssignedValue(object? value, bool throwOnError, out object? converted) + { + var isOfExpectedType = IsOfExpectedPropertyType(value); + if (isOfExpectedType) { - var isOfExpectedType = IsOfExpectedPropertyType(value); - if (isOfExpectedType) + converted = value; + return true; + } + + // isOfExpectedType is true if value is null - so if false, value is *not* null + // "garbage-in", accept what we can & convert + // throw only if conversion is not possible + + var s = value?.ToString(); + converted = null; + + switch (ValueStorageType) + { + case ValueStorageType.Nvarchar: + case ValueStorageType.Ntext: { - converted = value; + converted = s; return true; } - // isOfExpectedType is true if value is null - so if false, value is *not* null - // "garbage-in", accept what we can & convert - // throw only if conversion is not possible + case ValueStorageType.Integer: + if (s.IsNullOrWhiteSpace()) + { + return true; // assume empty means null + } - var s = value?.ToString(); - converted = null; + Attempt convInt = value.TryConvertTo(); + if (convInt.Success) + { + converted = convInt.Result; + return true; + } - switch (ValueStorageType) - { - case ValueStorageType.Nvarchar: - case ValueStorageType.Ntext: - { - converted = s; - return true; - } - - case ValueStorageType.Integer: - if (s.IsNullOrWhiteSpace()) - return true; // assume empty means null - var convInt = value.TryConvertTo(); - if (convInt.Success) - { - converted = convInt.Result; - return true; - } - - if (throwOnError) - ThrowTypeException(value, typeof(int), Alias ?? string.Empty); - return false; - - case ValueStorageType.Decimal: - if (s.IsNullOrWhiteSpace()) - return true; // assume empty means null - var convDecimal = value.TryConvertTo(); - if (convDecimal.Success) - { - // need to normalize the value (change the scaling factor and remove trailing zeros) - // because the underlying database is going to mess with the scaling factor anyways. - converted = convDecimal.Result.Normalize(); - return true; - } - - if (throwOnError) - ThrowTypeException(value, typeof(decimal), Alias ?? string.Empty); - return false; - - case ValueStorageType.Date: - if (s.IsNullOrWhiteSpace()) - return true; // assume empty means null - var convDateTime = value.TryConvertTo(); - if (convDateTime.Success) - { - converted = convDateTime.Result; - return true; - } - - if (throwOnError) - ThrowTypeException(value, typeof(DateTime), Alias ?? string.Empty); - return false; - - default: - throw new NotSupportedException($"Not supported storage type \"{ValueStorageType}\"."); - } + if (throwOnError) + { + ThrowTypeException(value, typeof(int), Alias ?? string.Empty); + } + + return false; + + case ValueStorageType.Decimal: + if (s.IsNullOrWhiteSpace()) + { + return true; // assume empty means null + } + + Attempt convDecimal = value.TryConvertTo(); + if (convDecimal.Success) + { + // need to normalize the value (change the scaling factor and remove trailing zeros) + // because the underlying database is going to mess with the scaling factor anyways. + converted = convDecimal.Result.Normalize(); + return true; + } + + if (throwOnError) + { + ThrowTypeException(value, typeof(decimal), Alias ?? string.Empty); + } + + return false; + + case ValueStorageType.Date: + if (s.IsNullOrWhiteSpace()) + { + return true; // assume empty means null + } + + Attempt convDateTime = value.TryConvertTo(); + if (convDateTime.Success) + { + converted = convDateTime.Result; + return true; + } + + if (throwOnError) + { + ThrowTypeException(value, typeof(DateTime), Alias ?? string.Empty); + } + + return false; + + default: + throw new NotSupportedException($"Not supported storage type \"{ValueStorageType}\"."); } + } + + private static void ThrowTypeException(object? value, Type expected, string alias) => + throw new InvalidOperationException( + $"Cannot assign value \"{value}\" of type \"{value?.GetType()}\" to property \"{alias}\" expecting type \"{expected}\"."); + + /// + /// Determines whether a value is of the expected type for this property type. + /// + /// + /// + /// If the value is of the expected type, it can be directly assigned to the property. + /// Otherwise, some conversion is required. + /// + /// + private bool IsOfExpectedPropertyType(object? value) + { + // null values are assumed to be ok + if (value == null) + { + return true; + } + + // check if the type of the value matches the type from the DataType/PropertyEditor + // then it can be directly assigned, anything else requires conversion + Type valueType = value.GetType(); + switch (ValueStorageType) + { + case ValueStorageType.Integer: + return valueType == typeof(int); + case ValueStorageType.Decimal: + return valueType == typeof(decimal); + case ValueStorageType.Date: + return valueType == typeof(DateTime); + case ValueStorageType.Nvarchar: + return valueType == typeof(string); + case ValueStorageType.Ntext: + return valueType == typeof(string); + default: + throw new NotSupportedException($"Not supported storage type \"{ValueStorageType}\"."); + } + } + + + protected override void PerformDeepClone(object clone) + { + base.PerformDeepClone(clone); + + var clonedEntity = (Property)clone; + + //need to manually assign since this is a readonly property + clonedEntity.PropertyType = (PropertyType)PropertyType.DeepClone(); + } + + /// + /// Used for constructing a new instance + /// + public class InitialPropertyValue + { + public InitialPropertyValue(string? culture, string? segment, bool published, object? value) + { + Culture = culture; + Segment = segment; + Published = published; + Value = value; + } + + public string? Culture { get; } + public string? Segment { get; } + public bool Published { get; } + public object? Value { get; } + } - private static void ThrowTypeException(object? value, Type expected, string alias) + /// + /// Represents a property value. + /// + public class PropertyValue : IPropertyValue, IDeepCloneable, IEquatable + { + // TODO: Either we allow change tracking at this class level, or we add some special change tracking collections to the Property + // class to deal with change tracking which variants have changed + + private string? _culture; + private string? _segment; + + public object DeepClone() => Clone(); + + public bool Equals(PropertyValue? other) => + other != null && + _culture == other._culture && + _segment == other._segment && + EqualityComparer.Default.Equals(EditedValue, other.EditedValue) && + EqualityComparer.Default.Equals(PublishedValue, other.PublishedValue); + + /// + /// Gets or sets the culture of the property. + /// + /// + /// The culture is either null (invariant) or a non-empty string. If the property is + /// set with an empty or whitespace value, its value is converted to null. + /// + public string? Culture { - throw new InvalidOperationException($"Cannot assign value \"{value}\" of type \"{value?.GetType()}\" to property \"{alias}\" expecting type \"{expected}\"."); + get => _culture; + set => _culture = value.IsNullOrWhiteSpace() ? null : value!.ToLowerInvariant(); } /// - /// Determines whether a value is of the expected type for this property type. + /// Gets or sets the segment of the property. /// /// - /// If the value is of the expected type, it can be directly assigned to the property. - /// Otherwise, some conversion is required. + /// The segment is either null (neutral) or a non-empty string. If the property is + /// set with an empty or whitespace value, its value is converted to null. /// - private bool IsOfExpectedPropertyType(object? value) + public string? Segment { - // null values are assumed to be ok - if (value == null) - return true; + get => _segment; + set => _segment = value?.ToLowerInvariant(); + } - // check if the type of the value matches the type from the DataType/PropertyEditor - // then it can be directly assigned, anything else requires conversion - var valueType = value.GetType(); - switch (ValueStorageType) + /// + /// Gets or sets the edited value of the property. + /// + public object? EditedValue { get; set; } + + /// + /// Gets or sets the published value of the property. + /// + public object? PublishedValue { get; set; } + + /// + /// Clones the property value. + /// + public IPropertyValue Clone() + => new PropertyValue { - case ValueStorageType.Integer: - return valueType == typeof(int); - case ValueStorageType.Decimal: - return valueType == typeof(decimal); - case ValueStorageType.Date: - return valueType == typeof(DateTime); - case ValueStorageType.Nvarchar: - return valueType == typeof(string); - case ValueStorageType.Ntext: - return valueType == typeof(string); - default: - throw new NotSupportedException($"Not supported storage type \"{ValueStorageType}\"."); - } - } + _culture = _culture, _segment = _segment, PublishedValue = PublishedValue, EditedValue = EditedValue + }; + public override bool Equals(object? obj) => Equals(obj as PropertyValue); - protected override void PerformDeepClone(object clone) + public override int GetHashCode() { - base.PerformDeepClone(clone); + var hashCode = 1885328050; + if (_culture is not null) + { + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(_culture); + } - var clonedEntity = (Property)clone; + if (_segment is not null) + { + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(_segment); + } + + if (EditedValue is not null) + { + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(EditedValue); + } + + if (PublishedValue is not null) + { + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(PublishedValue); + } - //need to manually assign since this is a readonly property - clonedEntity.PropertyType = (PropertyType) PropertyType.DeepClone(); + return hashCode; } } } diff --git a/src/Umbraco.Core/Models/PropertyCollection.cs b/src/Umbraco.Core/Models/PropertyCollection.cs index 49b392ba67bb..f1a41311bb3f 100644 --- a/src/Umbraco.Core/Models/PropertyCollection.cs +++ b/src/Umbraco.Core/Models/PropertyCollection.cs @@ -1,215 +1,219 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Runtime.Serialization; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a collection of property values. +/// +[Serializable] +[DataContract(IsReference = true)] +public class PropertyCollection : KeyedCollection, IPropertyCollection { + private readonly object _addLocker = new(); /// - /// Represents a collection of property values. + /// Initializes a new instance of the class. /// - [Serializable] - [DataContract(IsReference = true)] - public class PropertyCollection : KeyedCollection, IPropertyCollection + public PropertyCollection() + : base(StringComparer.InvariantCultureIgnoreCase) { - private readonly object _addLocker = new object(); - - /// - /// Initializes a new instance of the class. - /// - public PropertyCollection() - : base(StringComparer.InvariantCultureIgnoreCase) - { } - - /// - /// Initializes a new instance of the class. - /// - public PropertyCollection(IEnumerable properties) - : this() - { - Reset(properties); - } + } - /// - /// Replaces all properties, whilst maintaining validation delegates. - /// - private void Reset(IEnumerable properties) - { - //collection events will be raised in each of these calls - Clear(); + /// + /// Initializes a new instance of the class. + /// + public PropertyCollection(IEnumerable properties) + : this() => + Reset(properties); - //collection events will be raised in each of these calls - foreach (var property in properties) - Add(property); - } + /// + /// Gets the property with the specified PropertyType. + /// + internal IProperty? this[IPropertyType propertyType] => + this.FirstOrDefault(x => x.Alias.InvariantEquals(propertyType.Alias)); - /// - /// Replaces the property at the specified index with the specified property. - /// - protected override void SetItem(int index, IProperty property) + /// + public new void Add(IProperty property) + { + lock (_addLocker) // TODO: why are we locking here and not everywhere else?! { - var oldItem = index >= 0 ? this[index] : property; - base.SetItem(index, property); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, property, oldItem)); - } + var key = GetKeyForItem(property); + if (key != null) + { + if (Contains(key)) + { + // transfer id and values if ... + IProperty existing = this[key]; - /// - /// Removes the property at the specified index. - /// - protected override void RemoveItem(int index) - { - var removed = this[index]; - base.RemoveItem(index); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); - } + if (property.Id == 0 && existing.Id != 0) + { + property.Id = existing.Id; + } - /// - /// Inserts the specified property at the specified index. - /// - protected override void InsertItem(int index, IProperty property) - { - base.InsertItem(index, property); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property)); - } + if (property.Values.Count == 0 && existing.Values.Count > 0) + { + property.Values = existing.Values.Select(x => x.Clone()).ToList(); + } - /// - /// Removes all properties. - /// - protected override void ClearItems() - { - base.ClearItems(); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + // replace existing with property and return, + // SetItem invokes OnCollectionChanged (but not OnAdd) + SetItem(IndexOfKey(key), property); + return; + } + } + + //collection events will be raised in InsertItem with Add + base.Add(property); } + } - /// - public new void Add(IProperty property) - { - lock (_addLocker) // TODO: why are we locking here and not everywhere else?! - { - var key = GetKeyForItem(property); - if (key != null) - { - if (Contains(key)) - { - // transfer id and values if ... - var existing = this[key]; + public bool TryGetValue(string propertyTypeAlias, [MaybeNullWhen(false)] out IProperty property) + { + property = this.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); + return property != null; + } - if (property.Id == 0 && existing.Id != 0) - property.Id = existing.Id; + /// + /// Occurs when the collection changes. + /// + public event NotifyCollectionChangedEventHandler? CollectionChanged; - if (property.Values.Count == 0 && existing.Values.Count > 0) - property.Values = existing.Values.Select(x => x.Clone()).ToList(); + public void ClearCollectionChangedEvents() => CollectionChanged = null; - // replace existing with property and return, - // SetItem invokes OnCollectionChanged (but not OnAdd) - SetItem(IndexOfKey(key), property); - return; - } - } - //collection events will be raised in InsertItem with Add - base.Add(property); - } + /// + public void EnsurePropertyTypes(IEnumerable propertyTypes) + { + if (propertyTypes == null) + { + return; } - /// - /// Gets the index for a specified property alias. - /// - private int IndexOfKey(string key) + foreach (IPropertyType propertyType in propertyTypes) { - for (var i = 0; i < Count; i++) - { - if (this[i].Alias?.InvariantEquals(key) ?? false) - return i; - } - return -1; + Add(new Property(propertyType)); } + } + - protected override string GetKeyForItem(IProperty item) + /// + public void EnsureCleanPropertyTypes(IEnumerable propertyTypes) + { + if (propertyTypes == null) { - return item.Alias!; + return; } - /// - /// Gets the property with the specified PropertyType. - /// - internal IProperty? this[IPropertyType propertyType] + IPropertyType[] propertyTypesA = propertyTypes.ToArray(); + + IEnumerable thisAliases = this.Select(x => x.Alias); + IEnumerable typeAliases = propertyTypesA.Select(x => x.Alias); + var remove = thisAliases.Except(typeAliases).ToArray(); + foreach (var alias in remove) { - get + if (alias is not null) { - return this.FirstOrDefault(x => x.Alias.InvariantEquals(propertyType.Alias)); + Remove(alias); } } - public bool TryGetValue(string propertyTypeAlias, [MaybeNullWhen(false)] out IProperty property) + + foreach (IPropertyType propertyType in propertyTypesA) { - property = this.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); - return property != null; + Add(new Property(propertyType)); } + } - /// - /// Occurs when the collection changes. - /// - public event NotifyCollectionChangedEventHandler? CollectionChanged; - - public void ClearCollectionChangedEvents() => CollectionChanged = null; - - protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) + /// + /// Deep clones. + /// + public object DeepClone() + { + var clone = new PropertyCollection(); + foreach (IProperty property in this) { - CollectionChanged?.Invoke(this, args); + clone.Add((Property)property.DeepClone()); } + return clone; + } - /// - public void EnsurePropertyTypes(IEnumerable propertyTypes) - { - if (propertyTypes == null) - return; + /// + /// Replaces all properties, whilst maintaining validation delegates. + /// + private void Reset(IEnumerable properties) + { + //collection events will be raised in each of these calls + Clear(); - foreach (var propertyType in propertyTypes) - Add(new Property(propertyType)); + //collection events will be raised in each of these calls + foreach (IProperty property in properties) + { + Add(property); } + } + /// + /// Replaces the property at the specified index with the specified property. + /// + protected override void SetItem(int index, IProperty property) + { + IProperty oldItem = index >= 0 ? this[index] : property; + base.SetItem(index, property); + OnCollectionChanged( + new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, property, oldItem)); + } - /// - public void EnsureCleanPropertyTypes(IEnumerable propertyTypes) - { - if (propertyTypes == null) - return; + /// + /// Removes the property at the specified index. + /// + protected override void RemoveItem(int index) + { + IProperty removed = this[index]; + base.RemoveItem(index); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); + } - var propertyTypesA = propertyTypes.ToArray(); + /// + /// Inserts the specified property at the specified index. + /// + protected override void InsertItem(int index, IProperty property) + { + base.InsertItem(index, property); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property)); + } - var thisAliases = this.Select(x => x.Alias); - var typeAliases = propertyTypesA.Select(x => x.Alias); - var remove = thisAliases.Except(typeAliases).ToArray(); - foreach (var alias in remove) - { - if (alias is not null) - { - Remove(alias); - } + /// + /// Removes all properties. + /// + protected override void ClearItems() + { + base.ClearItems(); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + /// + /// Gets the index for a specified property alias. + /// + private int IndexOfKey(string key) + { + for (var i = 0; i < Count; i++) + { + if (this[i].Alias?.InvariantEquals(key) ?? false) + { + return i; } - - - foreach (var propertyType in propertyTypesA) - Add(new Property(propertyType)); } - /// - /// Deep clones. - /// - public object DeepClone() - { - var clone = new PropertyCollection(); - foreach (var property in this) - clone.Add((Property) property.DeepClone()); - return clone; - } + return -1; } + + protected override string GetKeyForItem(IProperty item) => item.Alias!; + + protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) => + CollectionChanged?.Invoke(this, args); } diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs index 17e66032841b..68a10fe7177b 100644 --- a/src/Umbraco.Core/Models/PropertyGroup.cs +++ b/src/Umbraco.Core/Models/PropertyGroup.cs @@ -1,159 +1,158 @@ -using System; using System.Collections.Specialized; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a group of property types. +/// +[Serializable] +[DataContract(IsReference = true)] +[DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] +public class PropertyGroup : EntityBase, IEquatable { + private string _alias; + private string? _name; + private PropertyTypeCollection? _propertyTypes; + private int _sortOrder; + + private PropertyGroupType _type; + + [SuppressMessage("Style", "IDE1006:Naming Styles", + Justification = "This field is for internal use only (to allow changing item keys).")] + internal PropertyGroupCollection? Collection; + + public PropertyGroup(bool isPublishing) + : this(new PropertyTypeCollection(isPublishing)) + { + } + + public PropertyGroup(PropertyTypeCollection propertyTypeCollection) + { + PropertyTypes = propertyTypeCollection; + _alias = string.Empty; + } + /// - /// Represents a group of property types. + /// Gets or sets the type of the group. /// - [Serializable] - [DataContract(IsReference = true)] - [DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] - public class PropertyGroup : EntityBase, IEquatable + /// + /// The type. + /// + [DataMember] + public PropertyGroupType Type { - [SuppressMessage("Style", "IDE1006:Naming Styles", - Justification = "This field is for internal use only (to allow changing item keys).")] - internal PropertyGroupCollection? Collection; - - private PropertyGroupType _type; - private string? _name; - private string _alias; - private int _sortOrder; - private PropertyTypeCollection? _propertyTypes; - - public PropertyGroup(bool isPublishing) - : this(new PropertyTypeCollection(isPublishing)) - { - } + get => _type; + set => SetPropertyValueAndDetectChanges(value, ref _type, nameof(Type)); + } - public PropertyGroup(PropertyTypeCollection propertyTypeCollection) - { - PropertyTypes = propertyTypeCollection; - _alias = string.Empty; - } + /// + /// Gets or sets the name of the group. + /// + /// + /// The name. + /// + [DataMember] + public string? Name + { + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); + } - private void PropertyTypesChanged(object? sender, NotifyCollectionChangedEventArgs e) + /// + /// Gets or sets the alias of the group. + /// + /// + /// The alias. + /// + [DataMember] + public string Alias + { + get => _alias; + set { - OnPropertyChanged(nameof(PropertyTypes)); - } + // If added to a collection, ensure the key is changed before setting it (this ensures the internal lookup dictionary is updated) + Collection?.ChangeKey(this, value); - /// - /// Gets or sets the type of the group. - /// - /// - /// The type. - /// - [DataMember] - public PropertyGroupType Type - { - get => _type; - set => SetPropertyValueAndDetectChanges(value, ref _type, nameof(Type)); + SetPropertyValueAndDetectChanges(value, ref _alias!, nameof(Alias)); } + } - /// - /// Gets or sets the name of the group. - /// - /// - /// The name. - /// - [DataMember] - public string? Name - { - get => _name; - set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); - } + /// + /// Gets or sets the sort order of the group. + /// + /// + /// The sort order. + /// + [DataMember] + public int SortOrder + { + get => _sortOrder; + set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, nameof(SortOrder)); + } - /// - /// Gets or sets the alias of the group. - /// - /// - /// The alias. - /// - [DataMember] - public string Alias + /// + /// Gets or sets a collection of property types for the group. + /// + /// + /// The property types. + /// + /// + /// Marked with DoNotClone, because we will manually deal with cloning and the event handlers. + /// + [DataMember] + [DoNotClone] + public PropertyTypeCollection? PropertyTypes + { + get => _propertyTypes; + set { - get => _alias; - set + if (_propertyTypes != null) { - // If added to a collection, ensure the key is changed before setting it (this ensures the internal lookup dictionary is updated) - Collection?.ChangeKey(this, value); - - SetPropertyValueAndDetectChanges(value, ref _alias!, nameof(Alias)); + _propertyTypes.ClearCollectionChangedEvents(); } - } - /// - /// Gets or sets the sort order of the group. - /// - /// - /// The sort order. - /// - [DataMember] - public int SortOrder - { - get => _sortOrder; - set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, nameof(SortOrder)); - } + _propertyTypes = value; - /// - /// Gets or sets a collection of property types for the group. - /// - /// - /// The property types. - /// - /// - /// Marked with DoNotClone, because we will manually deal with cloning and the event handlers. - /// - [DataMember] - [DoNotClone] - public PropertyTypeCollection? PropertyTypes - { - get => _propertyTypes; - set + if (_propertyTypes is not null) { - if (_propertyTypes != null) + // since we're adding this collection to this group, + // we need to ensure that all the lazy values are set. + foreach (IPropertyType propertyType in _propertyTypes) { - _propertyTypes.ClearCollectionChangedEvents(); + propertyType.PropertyGroupId = new Lazy(() => Id); } - _propertyTypes = value; - - if (_propertyTypes is not null) - { - // since we're adding this collection to this group, - // we need to ensure that all the lazy values are set. - foreach (var propertyType in _propertyTypes) - propertyType.PropertyGroupId = new Lazy(() => Id); - - OnPropertyChanged(nameof(PropertyTypes)); - _propertyTypes.CollectionChanged += PropertyTypesChanged; - } + OnPropertyChanged(nameof(PropertyTypes)); + _propertyTypes.CollectionChanged += PropertyTypesChanged; } } + } - public bool Equals(PropertyGroup? other) => - base.Equals(other) || (other != null && Type == other.Type && Alias == other.Alias); + public bool Equals(PropertyGroup? other) => + base.Equals(other) || (other != null && Type == other.Type && Alias == other.Alias); - public override int GetHashCode() => (base.GetHashCode(), Type, Alias).GetHashCode(); + private void PropertyTypesChanged(object? sender, NotifyCollectionChangedEventArgs e) => + OnPropertyChanged(nameof(PropertyTypes)); - protected override void PerformDeepClone(object clone) - { - base.PerformDeepClone(clone); + public override int GetHashCode() => (base.GetHashCode(), Type, Alias).GetHashCode(); - var clonedEntity = (PropertyGroup)clone; - clonedEntity.Collection = null; + protected override void PerformDeepClone(object clone) + { + base.PerformDeepClone(clone); - if (clonedEntity._propertyTypes != null) - { - clonedEntity._propertyTypes.ClearCollectionChangedEvents(); //clear this event handler if any - clonedEntity._propertyTypes = (PropertyTypeCollection)_propertyTypes!.DeepClone(); //manually deep clone - clonedEntity._propertyTypes.CollectionChanged += - clonedEntity.PropertyTypesChanged; //re-assign correct event handler - } + var clonedEntity = (PropertyGroup)clone; + clonedEntity.Collection = null; + + if (clonedEntity._propertyTypes != null) + { + clonedEntity._propertyTypes.ClearCollectionChangedEvents(); //clear this event handler if any + clonedEntity._propertyTypes = (PropertyTypeCollection)_propertyTypes!.DeepClone(); //manually deep clone + clonedEntity._propertyTypes.CollectionChanged += + clonedEntity.PropertyTypesChanged; //re-assign correct event handler } } } diff --git a/src/Umbraco.Core/Models/PropertyGroupCollection.cs b/src/Umbraco.Core/Models/PropertyGroupCollection.cs index f248b12811f1..a018d4963cc5 100644 --- a/src/Umbraco.Core/Models/PropertyGroupCollection.cs +++ b/src/Umbraco.Core/Models/PropertyGroupCollection.cs @@ -1,156 +1,161 @@ -using System; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Runtime.Serialization; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a collection of objects +/// +[Serializable] +[DataContract] +// TODO: Change this to ObservableDictionary so we can reduce the INotifyCollectionChanged implementation details +public class PropertyGroupCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable { /// - /// Represents a collection of objects + /// Initializes a new instance of the class. /// - [Serializable] - [DataContract] - // TODO: Change this to ObservableDictionary so we can reduce the INotifyCollectionChanged implementation details - public class PropertyGroupCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable + public PropertyGroupCollection() { - /// - /// Initializes a new instance of the class. - /// - public PropertyGroupCollection() - { } - - /// - /// Initializes a new instance of the class. - /// - /// The groups. - public PropertyGroupCollection(IEnumerable groups) => Reset(groups); - - /// - /// Resets the collection to only contain the instances referenced in the parameter. - /// - /// The property groups. - /// - internal void Reset(IEnumerable groups) - { - // Collection events will be raised in each of these calls - Clear(); + } - // Collection events will be raised in each of these calls - foreach (var group in groups) - Add(group); - } + /// + /// Initializes a new instance of the class. + /// + /// The groups. + public PropertyGroupCollection(IEnumerable groups) => Reset(groups); - protected override void SetItem(int index, PropertyGroup item) + public object DeepClone() + { + var clone = new PropertyGroupCollection(); + foreach (PropertyGroup group in this) { - var oldItem = index >= 0 ? this[index] : item; + clone.Add((PropertyGroup)group.DeepClone()); + } - base.SetItem(index, item); + return clone; + } - oldItem.Collection = null; - item.Collection = this; + public event NotifyCollectionChangedEventHandler? CollectionChanged; - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, oldItem)); - } + /// + /// Resets the collection to only contain the instances referenced in the + /// parameter. + /// + /// The property groups. + /// + internal void Reset(IEnumerable groups) + { + // Collection events will be raised in each of these calls + Clear(); - protected override void RemoveItem(int index) + // Collection events will be raised in each of these calls + foreach (PropertyGroup group in groups) { - var removed = this[index]; + Add(group); + } + } - base.RemoveItem(index); + protected override void SetItem(int index, PropertyGroup item) + { + PropertyGroup oldItem = index >= 0 ? this[index] : item; - removed.Collection = null; + base.SetItem(index, item); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); - } + oldItem.Collection = null; + item.Collection = this; - protected override void InsertItem(int index, PropertyGroup item) - { - base.InsertItem(index, item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, oldItem)); + } - item.Collection = this; + protected override void RemoveItem(int index) + { + PropertyGroup removed = this[index]; - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); - } + base.RemoveItem(index); - protected override void ClearItems() - { - foreach (var item in this) - { - item.Collection = null; - } + removed.Collection = null; + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); + } + + protected override void InsertItem(int index, PropertyGroup item) + { + base.InsertItem(index, item); + + item.Collection = this; - base.ClearItems(); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); + } + + protected override void ClearItems() + { + foreach (PropertyGroup item in this) + { + item.Collection = null; } - public new void Add(PropertyGroup item) + base.ClearItems(); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + public new void Add(PropertyGroup item) + { + // Ensure alias is set + if (string.IsNullOrEmpty(item.Alias)) { - // Ensure alias is set - if (string.IsNullOrEmpty(item.Alias)) - { - throw new InvalidOperationException("Set an alias before adding the property group."); - } + throw new InvalidOperationException("Set an alias before adding the property group."); + } - // Note this is done to ensure existing groups can be renamed - if (item.HasIdentity && item.Id > 0) + // Note this is done to ensure existing groups can be renamed + if (item.HasIdentity && item.Id > 0) + { + var index = IndexOfKey(item.Id); + if (index != -1) { - var index = IndexOfKey(item.Id); - if (index != -1) + var keyExists = Contains(item.Alias); + if (keyExists) { - var keyExists = Contains(item.Alias); - if (keyExists) - throw new ArgumentException($"Naming conflict: changing the alias of property group '{item.Name}' would result in duplicates."); - - // Collection events will be raised in SetItem - SetItem(index, item); - return; + throw new ArgumentException( + $"Naming conflict: changing the alias of property group '{item.Name}' would result in duplicates."); } + + // Collection events will be raised in SetItem + SetItem(index, item); + return; } - else + } + else + { + var index = IndexOfKey(item.Alias); + if (index != -1) { - var index = IndexOfKey(item.Alias); - if (index != -1) - { - // Collection events will be raised in SetItem - SetItem(index, item); - return; - } + // Collection events will be raised in SetItem + SetItem(index, item); + return; } - - // Collection events will be raised in InsertItem - base.Add(item); } - internal void ChangeKey(PropertyGroup item, string newKey) => ChangeItemKey(item, newKey); - - public bool Contains(int id) => this.IndexOfKey(id) != -1; - - public int IndexOfKey(string key) => this.FindIndex(x => x.Alias == key); + // Collection events will be raised in InsertItem + base.Add(item); + } - public int IndexOfKey(int id) => this.FindIndex(x => x.Id == id); + internal void ChangeKey(PropertyGroup item, string newKey) => ChangeItemKey(item, newKey); - protected override string GetKeyForItem(PropertyGroup item) => item.Alias; + public bool Contains(int id) => IndexOfKey(id) != -1; - public event NotifyCollectionChangedEventHandler? CollectionChanged; + public int IndexOfKey(string key) => this.FindIndex(x => x.Alias == key); - /// - /// Clears all event handlers - /// - public void ClearCollectionChangedEvents() => CollectionChanged = null; + public int IndexOfKey(int id) => this.FindIndex(x => x.Id == id); - protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) => CollectionChanged?.Invoke(this, args); + protected override string GetKeyForItem(PropertyGroup item) => item.Alias; - public object DeepClone() - { - var clone = new PropertyGroupCollection(); - foreach (var group in this) - { - clone.Add((PropertyGroup)group.DeepClone()); - } + /// + /// Clears all event handlers + /// + public void ClearCollectionChangedEvents() => CollectionChanged = null; - return clone; - } - } + protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) => + CollectionChanged?.Invoke(this, args); } diff --git a/src/Umbraco.Core/Models/PropertyGroupExtensions.cs b/src/Umbraco.Core/Models/PropertyGroupExtensions.cs index bb12e1bc1bd6..4aad70b194d6 100644 --- a/src/Umbraco.Core/Models/PropertyGroupExtensions.cs +++ b/src/Umbraco.Core/Models/PropertyGroupExtensions.cs @@ -1,83 +1,82 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public static class PropertyGroupExtensions { - public static class PropertyGroupExtensions - { - private const char AliasSeparator = '/'; + private const char AliasSeparator = '/'; - internal static string? GetLocalAlias(string alias) + internal static string? GetLocalAlias(string alias) + { + var lastIndex = alias?.LastIndexOf(AliasSeparator) ?? -1; + if (lastIndex != -1) { - var lastIndex = alias?.LastIndexOf(AliasSeparator) ?? -1; - if (lastIndex != -1) - { - return alias?.Substring(lastIndex + 1); - } - - return alias; + return alias?.Substring(lastIndex + 1); } - internal static string? GetParentAlias(string? alias) - { - var lastIndex = alias?.LastIndexOf(AliasSeparator) ?? -1; - if (lastIndex == -1) - { - return null; - } + return alias; + } - return alias?.Substring(0, lastIndex); + internal static string? GetParentAlias(string? alias) + { + var lastIndex = alias?.LastIndexOf(AliasSeparator) ?? -1; + if (lastIndex == -1) + { + return null; } - /// - /// Gets the local alias. - /// - /// The property group. - /// - /// The local alias. - /// - public static string? GetLocalAlias(this PropertyGroup propertyGroup) => GetLocalAlias(propertyGroup.Alias); + return alias?.Substring(0, lastIndex); + } - /// - /// Updates the local alias. - /// - /// The property group. - /// The local alias. - public static void UpdateLocalAlias(this PropertyGroup propertyGroup, string localAlias) + /// + /// Gets the local alias. + /// + /// The property group. + /// + /// The local alias. + /// + public static string? GetLocalAlias(this PropertyGroup propertyGroup) => GetLocalAlias(propertyGroup.Alias); + + /// + /// Updates the local alias. + /// + /// The property group. + /// The local alias. + public static void UpdateLocalAlias(this PropertyGroup propertyGroup, string localAlias) + { + var parentAlias = propertyGroup.GetParentAlias(); + if (string.IsNullOrEmpty(parentAlias)) + { + propertyGroup.Alias = localAlias; + } + else { - var parentAlias = propertyGroup.GetParentAlias(); - if (string.IsNullOrEmpty(parentAlias)) - { - propertyGroup.Alias = localAlias; - } - else - { - propertyGroup.Alias = parentAlias + AliasSeparator + localAlias; - } + propertyGroup.Alias = parentAlias + AliasSeparator + localAlias; } + } - /// - /// Gets the parent alias. - /// - /// The property group. - /// - /// The parent alias. - /// - public static string? GetParentAlias(this PropertyGroup propertyGroup) => GetParentAlias(propertyGroup.Alias); + /// + /// Gets the parent alias. + /// + /// The property group. + /// + /// The parent alias. + /// + public static string? GetParentAlias(this PropertyGroup propertyGroup) => GetParentAlias(propertyGroup.Alias); - /// - /// Updates the parent alias. - /// - /// The property group. - /// The parent alias. - public static void UpdateParentAlias(this PropertyGroup propertyGroup, string parentAlias) + /// + /// Updates the parent alias. + /// + /// The property group. + /// The parent alias. + public static void UpdateParentAlias(this PropertyGroup propertyGroup, string parentAlias) + { + var localAlias = propertyGroup.GetLocalAlias(); + if (string.IsNullOrEmpty(parentAlias)) + { + propertyGroup.Alias = localAlias!; + } + else { - var localAlias = propertyGroup.GetLocalAlias(); - if (string.IsNullOrEmpty(parentAlias)) - { - propertyGroup.Alias = localAlias!; - } - else - { - propertyGroup.Alias = parentAlias + AliasSeparator + localAlias; - } + propertyGroup.Alias = parentAlias + AliasSeparator + localAlias; } } } diff --git a/src/Umbraco.Core/Models/PropertyGroupType.cs b/src/Umbraco.Core/Models/PropertyGroupType.cs index 03bcbc08f098..50b53ac3e1b0 100644 --- a/src/Umbraco.Core/Models/PropertyGroupType.cs +++ b/src/Umbraco.Core/Models/PropertyGroupType.cs @@ -1,17 +1,17 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents the type of a property group. +/// +public enum PropertyGroupType : short { /// - /// Represents the type of a property group. + /// Display property types in a group. /// - public enum PropertyGroupType : short - { - /// - /// Display property types in a group. - /// - Group = 0, - /// - /// Display property types in a tab. - /// - Tab = 1 - } + Group = 0, + + /// + /// Display property types in a tab. + /// + Tab = 1 } diff --git a/src/Umbraco.Core/Models/PropertyTagsExtensions.cs b/src/Umbraco.Core/Models/PropertyTagsExtensions.cs index 7bd3a49baf0c..d85b4e56e217 100644 --- a/src/Umbraco.Core/Models/PropertyTagsExtensions.cs +++ b/src/Umbraco.Core/Models/PropertyTagsExtensions.cs @@ -1,242 +1,303 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Provides extension methods for the class to manage tags. +/// +public static class PropertyTagsExtensions { - /// - /// Provides extension methods for the class to manage tags. - /// - public static class PropertyTagsExtensions + // gets the tag configuration for a property + // from the datatype configuration, and the editor tag configuration attribute + public static TagConfiguration? GetTagConfiguration(this IProperty property, + PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService) { - // gets the tag configuration for a property - // from the datatype configuration, and the editor tag configuration attribute - public static TagConfiguration? GetTagConfiguration(this IProperty property, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService) + if (property == null) { - if (property == null) throw new ArgumentNullException(nameof(property)); - - var editor = propertyEditors[property.PropertyType?.PropertyEditorAlias]; - var tagAttribute = editor?.GetTagAttribute(); - if (tagAttribute == null) return null; - - var configurationObject = property.PropertyType is null ? null : dataTypeService.GetDataType(property.PropertyType.DataTypeId)?.Configuration; - var configuration = ConfigurationEditor.ConfigurationAs(configurationObject); - - if (configuration?.Delimiter == default && configuration?.Delimiter is not null) - configuration.Delimiter = tagAttribute.Delimiter; - - return configuration; + throw new ArgumentNullException(nameof(property)); } - /// - /// Assign tags. - /// - /// The property. - /// - /// The tags. - /// A value indicating whether to merge the tags with existing tags instead of replacing them. - /// A culture, for multi-lingual properties. - /// - /// - public static void AssignTags(this IProperty property, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IJsonSerializer serializer, IEnumerable tags, bool merge = false, string? culture = null) + IDataEditor editor = propertyEditors[property.PropertyType?.PropertyEditorAlias]; + TagsPropertyEditorAttribute tagAttribute = editor?.GetTagAttribute(); + if (tagAttribute == null) { - if (property == null) throw new ArgumentNullException(nameof(property)); - - var configuration = property.GetTagConfiguration(propertyEditors, dataTypeService); - if (configuration == null) - throw new NotSupportedException($"Property with alias \"{property.Alias}\" does not support tags."); - - property.AssignTags(tags, merge, configuration.StorageType, serializer, configuration.Delimiter, culture); + return null; } - // assumes that parameters are consistent with the datatype configuration - private static void AssignTags(this IProperty property, IEnumerable tags, bool merge, TagsStorageType storageType, IJsonSerializer serializer, char delimiter, string? culture) + var configurationObject = property.PropertyType is null + ? null + : dataTypeService.GetDataType(property.PropertyType.DataTypeId)?.Configuration; + TagConfiguration configuration = ConfigurationEditor.ConfigurationAs(configurationObject); + + if (configuration?.Delimiter == default && configuration?.Delimiter is not null) { - // set the property value - var trimmedTags = tags.Select(x => x.Trim()).ToArray(); + configuration.Delimiter = tagAttribute.Delimiter; + } - if (merge) - { - var currentTags = property.GetTagsValue(storageType, serializer, delimiter); + return configuration; + } - switch (storageType) - { - case TagsStorageType.Csv: - property.SetValue(string.Join(delimiter.ToString(), currentTags.Union(trimmedTags)).NullOrWhiteSpaceAsNull(), culture); // csv string - break; - - case TagsStorageType.Json: - var updatedTags = currentTags.Union(trimmedTags).ToArray(); - var updatedValue = updatedTags.Length == 0 ? null : serializer.Serialize(updatedTags); - property.SetValue(updatedValue, culture); // json array - break; - } - } - else - { - switch (storageType) - { - case TagsStorageType.Csv: - property.SetValue(string.Join(delimiter.ToString(), trimmedTags).NullOrWhiteSpaceAsNull(), culture); // csv string - break; - - case TagsStorageType.Json: - var updatedValue = trimmedTags.Length == 0 ? null : serializer.Serialize(trimmedTags); - property.SetValue(updatedValue, culture); // json array - break; - } - } + /// + /// Assign tags. + /// + /// The property. + /// + /// The tags. + /// A value indicating whether to merge the tags with existing tags instead of replacing them. + /// A culture, for multi-lingual properties. + /// + /// + public static void AssignTags(this IProperty property, PropertyEditorCollection propertyEditors, + IDataTypeService dataTypeService, IJsonSerializer serializer, IEnumerable tags, bool merge = false, + string? culture = null) + { + if (property == null) + { + throw new ArgumentNullException(nameof(property)); } - /// - /// Removes tags. - /// - /// The property. - /// - /// The tags. - /// A culture, for multi-lingual properties. - /// - /// - public static void RemoveTags(this IProperty property, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IJsonSerializer serializer, IEnumerable tags, string? culture = null) + TagConfiguration configuration = property.GetTagConfiguration(propertyEditors, dataTypeService); + if (configuration == null) { - if (property == null) throw new ArgumentNullException(nameof(property)); + throw new NotSupportedException($"Property with alias \"{property.Alias}\" does not support tags."); + } - var configuration = property.GetTagConfiguration(propertyEditors, dataTypeService); - if (configuration == null) - throw new NotSupportedException($"Property with alias \"{property.Alias}\" does not support tags."); + property.AssignTags(tags, merge, configuration.StorageType, serializer, configuration.Delimiter, culture); + } - property.RemoveTags(tags, configuration.StorageType, serializer, configuration.Delimiter, culture); - } + // assumes that parameters are consistent with the datatype configuration + private static void AssignTags(this IProperty property, IEnumerable tags, bool merge, + TagsStorageType storageType, IJsonSerializer serializer, char delimiter, string? culture) + { + // set the property value + var trimmedTags = tags.Select(x => x.Trim()).ToArray(); - // assumes that parameters are consistent with the datatype configuration - private static void RemoveTags(this IProperty property, IEnumerable tags, TagsStorageType storageType, IJsonSerializer serializer, char delimiter, string? culture) + if (merge) { - // already empty = nothing to do - var value = property.GetValue(culture)?.ToString(); - if (string.IsNullOrWhiteSpace(value)) return; + IEnumerable currentTags = property.GetTagsValue(storageType, serializer, delimiter); - // set the property value - var trimmedTags = tags.Select(x => x.Trim()).ToArray(); - var currentTags = property.GetTagsValue(storageType, serializer, delimiter, culture); switch (storageType) { case TagsStorageType.Csv: - property.SetValue(string.Join(delimiter.ToString(), currentTags.Except(trimmedTags)).NullOrWhiteSpaceAsNull(), culture); // csv string + property.SetValue( + string.Join(delimiter.ToString(), currentTags.Union(trimmedTags)).NullOrWhiteSpaceAsNull(), + culture); // csv string break; case TagsStorageType.Json: - var updatedTags = currentTags.Except(trimmedTags).ToArray(); + var updatedTags = currentTags.Union(trimmedTags).ToArray(); var updatedValue = updatedTags.Length == 0 ? null : serializer.Serialize(updatedTags); property.SetValue(updatedValue, culture); // json array break; } } + else + { + switch (storageType) + { + case TagsStorageType.Csv: + property.SetValue(string.Join(delimiter.ToString(), trimmedTags).NullOrWhiteSpaceAsNull(), + culture); // csv string + break; + + case TagsStorageType.Json: + var updatedValue = trimmedTags.Length == 0 ? null : serializer.Serialize(trimmedTags); + property.SetValue(updatedValue, culture); // json array + break; + } + } + } + + /// + /// Removes tags. + /// + /// The property. + /// + /// The tags. + /// A culture, for multi-lingual properties. + /// + /// + public static void RemoveTags(this IProperty property, PropertyEditorCollection propertyEditors, + IDataTypeService dataTypeService, IJsonSerializer serializer, IEnumerable tags, string? culture = null) + { + if (property == null) + { + throw new ArgumentNullException(nameof(property)); + } - // used by ContentRepositoryBase - public static IEnumerable GetTagsValue(this IProperty property, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IJsonSerializer serializer, string? culture = null) + TagConfiguration configuration = property.GetTagConfiguration(propertyEditors, dataTypeService); + if (configuration == null) { - if (property == null) throw new ArgumentNullException(nameof(property)); + throw new NotSupportedException($"Property with alias \"{property.Alias}\" does not support tags."); + } - var configuration = property.GetTagConfiguration(propertyEditors, dataTypeService); - if (configuration == null) - throw new NotSupportedException($"Property with alias \"{property.Alias}\" does not support tags."); + property.RemoveTags(tags, configuration.StorageType, serializer, configuration.Delimiter, culture); + } - return property.GetTagsValue(configuration.StorageType, serializer, configuration.Delimiter, culture); + // assumes that parameters are consistent with the datatype configuration + private static void RemoveTags(this IProperty property, IEnumerable tags, TagsStorageType storageType, + IJsonSerializer serializer, char delimiter, string? culture) + { + // already empty = nothing to do + var value = property.GetValue(culture)?.ToString(); + if (string.IsNullOrWhiteSpace(value)) + { + return; } - private static IEnumerable GetTagsValue(this IProperty property, TagsStorageType storageType, IJsonSerializer serializer, char delimiter, string? culture = null) + // set the property value + var trimmedTags = tags.Select(x => x.Trim()).ToArray(); + IEnumerable currentTags = property.GetTagsValue(storageType, serializer, delimiter, culture); + switch (storageType) { - if (property == null) throw new ArgumentNullException(nameof(property)); + case TagsStorageType.Csv: + property.SetValue( + string.Join(delimiter.ToString(), currentTags.Except(trimmedTags)).NullOrWhiteSpaceAsNull(), + culture); // csv string + break; + + case TagsStorageType.Json: + var updatedTags = currentTags.Except(trimmedTags).ToArray(); + var updatedValue = updatedTags.Length == 0 ? null : serializer.Serialize(updatedTags); + property.SetValue(updatedValue, culture); // json array + break; + } + } - var value = property.GetValue(culture)?.ToString(); - if (string.IsNullOrWhiteSpace(value)) return Enumerable.Empty(); + // used by ContentRepositoryBase + public static IEnumerable GetTagsValue(this IProperty property, PropertyEditorCollection propertyEditors, + IDataTypeService dataTypeService, IJsonSerializer serializer, string? culture = null) + { + if (property == null) + { + throw new ArgumentNullException(nameof(property)); + } - switch (storageType) - { - case TagsStorageType.Csv: - return value.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()); + TagConfiguration configuration = property.GetTagConfiguration(propertyEditors, dataTypeService); + if (configuration == null) + { + throw new NotSupportedException($"Property with alias \"{property.Alias}\" does not support tags."); + } - case TagsStorageType.Json: - try - { - return serializer.Deserialize(value)?.Select(x => x.Trim()) ?? Enumerable.Empty(); - } - catch (Exception) - { - //cannot parse, malformed - return Enumerable.Empty(); - } - - default: - throw new NotSupportedException($"Value \"{storageType}\" is not a valid TagsStorageType."); - } + return property.GetTagsValue(configuration.StorageType, serializer, configuration.Delimiter, culture); + } + + private static IEnumerable GetTagsValue(this IProperty property, TagsStorageType storageType, + IJsonSerializer serializer, char delimiter, string? culture = null) + { + if (property == null) + { + throw new ArgumentNullException(nameof(property)); } - /// - /// Sets tags on a content property, based on the property editor tags configuration. - /// - /// The property. - /// The property value. - /// The datatype configuration. - /// A culture, for multi-lingual properties. - /// - /// The value is either a string (delimited string) or an enumeration of strings (tag list). - /// This is used both by the content repositories to initialize a property with some tag values, and by the - /// content controllers to update a property with values received from the property editor. - /// - public static void SetTagsValue(this IProperty property, IJsonSerializer serializer, object? value, TagConfiguration? tagConfiguration, string? culture) + var value = property.GetValue(culture)?.ToString(); + if (string.IsNullOrWhiteSpace(value)) { - if (property == null) throw new ArgumentNullException(nameof(property)); - if (tagConfiguration == null) throw new ArgumentNullException(nameof(tagConfiguration)); + return Enumerable.Empty(); + } + + switch (storageType) + { + case TagsStorageType.Csv: + return value.Split(new[] {delimiter}, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()); - var storageType = tagConfiguration.StorageType; - var delimiter = tagConfiguration.Delimiter; + case TagsStorageType.Json: + try + { + return serializer.Deserialize(value)?.Select(x => x.Trim()) ?? Enumerable.Empty(); + } + catch (Exception) + { + //cannot parse, malformed + return Enumerable.Empty(); + } - SetTagsValue(property, value, storageType, serializer, delimiter, culture); + default: + throw new NotSupportedException($"Value \"{storageType}\" is not a valid TagsStorageType."); } + } - // assumes that parameters are consistent with the datatype configuration - // value can be an enumeration of string, or a serialized value using storageType format - private static void SetTagsValue(IProperty property, object? value, TagsStorageType storageType, IJsonSerializer serializer, char delimiter, string? culture) + /// + /// Sets tags on a content property, based on the property editor tags configuration. + /// + /// The property. + /// The property value. + /// The datatype configuration. + /// A culture, for multi-lingual properties. + /// + /// The value is either a string (delimited string) or an enumeration of strings (tag list). + /// + /// This is used both by the content repositories to initialize a property with some tag values, and by the + /// content controllers to update a property with values received from the property editor. + /// + /// + public static void SetTagsValue(this IProperty property, IJsonSerializer serializer, object? value, + TagConfiguration? tagConfiguration, string? culture) + { + if (property == null) { - if (value == null) value = Enumerable.Empty(); + throw new ArgumentNullException(nameof(property)); + } - // if value is already an enumeration of strings, just use it - if (value is IEnumerable tags1) - { - property.AssignTags(tags1, false, storageType, serializer, delimiter, culture); - return; - } + if (tagConfiguration == null) + { + throw new ArgumentNullException(nameof(tagConfiguration)); + } - // otherwise, deserialize value based upon storage type - switch (storageType) - { - case TagsStorageType.Csv: - var tags2 = value.ToString()!.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries); - property.AssignTags(tags2, false, storageType, serializer, delimiter, culture); - break; + TagsStorageType storageType = tagConfiguration.StorageType; + var delimiter = tagConfiguration.Delimiter; - case TagsStorageType.Json: - try - { - var tags3 = serializer.Deserialize>(value.ToString()!); - property.AssignTags(tags3 ?? Enumerable.Empty(), false, storageType, serializer, delimiter, culture); - } - catch (Exception ex) - { - StaticApplicationLogging.Logger.LogWarning(ex, "Could not automatically convert stored json value to an enumerable string '{Json}'", value.ToString()); - } - break; + SetTagsValue(property, value, storageType, serializer, delimiter, culture); + } - default: - throw new ArgumentOutOfRangeException(nameof(storageType)); - } + // assumes that parameters are consistent with the datatype configuration + // value can be an enumeration of string, or a serialized value using storageType format + private static void SetTagsValue(IProperty property, object? value, TagsStorageType storageType, + IJsonSerializer serializer, char delimiter, string? culture) + { + if (value == null) + { + value = Enumerable.Empty(); + } + + // if value is already an enumeration of strings, just use it + if (value is IEnumerable tags1) + { + property.AssignTags(tags1, false, storageType, serializer, delimiter, culture); + return; + } + + // otherwise, deserialize value based upon storage type + switch (storageType) + { + case TagsStorageType.Csv: + var tags2 = value.ToString()!.Split(new[] {delimiter}, StringSplitOptions.RemoveEmptyEntries); + property.AssignTags(tags2, false, storageType, serializer, delimiter, culture); + break; + + case TagsStorageType.Json: + try + { + IEnumerable tags3 = serializer.Deserialize>(value.ToString()!); + property.AssignTags(tags3 ?? Enumerable.Empty(), false, storageType, serializer, delimiter, + culture); + } + catch (Exception ex) + { + StaticApplicationLogging.Logger.LogWarning(ex, + "Could not automatically convert stored json value to an enumerable string '{Json}'", + value.ToString()); + } + + break; + + default: + throw new ArgumentOutOfRangeException(nameof(storageType)); } } } diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 3acbad2720bc..696b9dc9239c 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -1,295 +1,307 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a property type. +/// +[Serializable] +[DataContract(IsReference = true)] +[DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] +public class PropertyType : EntityBase, IPropertyType, IEquatable { + private readonly bool _forceValueStorageType; + private readonly IShortStringHelper _shortStringHelper; + private string _alias; + private int _dataTypeId; + private Guid _dataTypeKey; + private string? _description; + private bool _labelOnTop; + private bool _mandatory; + private string? _mandatoryMessage; + private string _name; + private string _propertyEditorAlias; + private Lazy? _propertyGroupId; + private int _sortOrder; + private string? _validationRegExp; + private string? _validationRegExpMessage; + private ValueStorageType _valueStorageType; + private ContentVariation _variations; + /// - /// Represents a property type. + /// Initializes a new instance of the class. /// - [Serializable] - [DataContract(IsReference = true)] - [DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] - public class PropertyType : EntityBase, IPropertyType, IEquatable + public PropertyType(IShortStringHelper shortStringHelper, IDataType dataType) { - private readonly IShortStringHelper _shortStringHelper; - private readonly bool _forceValueStorageType; - private string _name; - private string _alias; - private string? _description; - private int _dataTypeId; - private Guid _dataTypeKey; - private Lazy? _propertyGroupId; - private string _propertyEditorAlias; - private ValueStorageType _valueStorageType; - private bool _mandatory; - private string? _mandatoryMessage; - private int _sortOrder; - private string? _validationRegExp; - private string? _validationRegExpMessage; - private ContentVariation _variations; - private bool _labelOnTop; - - /// - /// Initializes a new instance of the class. - /// - public PropertyType(IShortStringHelper shortStringHelper, IDataType dataType) + if (dataType == null) { - if (dataType == null) throw new ArgumentNullException(nameof(dataType)); - _shortStringHelper = shortStringHelper; - - if (dataType.HasIdentity) - _dataTypeId = dataType.Id; - - _propertyEditorAlias = dataType.EditorAlias; - _valueStorageType = dataType.DatabaseType; - _variations = ContentVariation.Nothing; - _alias = string.Empty; - _name = string.Empty; + throw new ArgumentNullException(nameof(dataType)); } - /// - /// Initializes a new instance of the class. - /// - public PropertyType(IShortStringHelper shortStringHelper, IDataType dataType, string propertyTypeAlias) - : this(shortStringHelper, dataType) - { - _alias = SanitizeAlias(propertyTypeAlias); - } + _shortStringHelper = shortStringHelper; - /// - /// Initializes a new instance of the class. - /// - public PropertyType(IShortStringHelper shortStringHelper,string propertyEditorAlias, ValueStorageType valueStorageType) - : this(shortStringHelper, propertyEditorAlias, valueStorageType, false) + if (dataType.HasIdentity) { + _dataTypeId = dataType.Id; } - /// - /// Initializes a new instance of the class. - /// - public PropertyType(IShortStringHelper shortStringHelper,string propertyEditorAlias, ValueStorageType valueStorageType, string propertyTypeAlias) - : this(shortStringHelper, propertyEditorAlias, valueStorageType, false, propertyTypeAlias) - { - } + _propertyEditorAlias = dataType.EditorAlias; + _valueStorageType = dataType.DatabaseType; + _variations = ContentVariation.Nothing; + _alias = string.Empty; + _name = string.Empty; + } - /// - /// Initializes a new instance of the class. - /// - /// Set to true to force the value storage type. Values assigned to - /// the property, eg from the underlying datatype, will be ignored. - public PropertyType(IShortStringHelper shortStringHelper, string propertyEditorAlias, ValueStorageType valueStorageType, bool forceValueStorageType, string? propertyTypeAlias = null) - { - _shortStringHelper = shortStringHelper; - _propertyEditorAlias = propertyEditorAlias; - _valueStorageType = valueStorageType; - _forceValueStorageType = forceValueStorageType; - _alias = propertyTypeAlias == null ? string.Empty : SanitizeAlias(propertyTypeAlias); - _variations = ContentVariation.Nothing; - _name = string.Empty; - } + /// + /// Initializes a new instance of the class. + /// + public PropertyType(IShortStringHelper shortStringHelper, IDataType dataType, string propertyTypeAlias) + : this(shortStringHelper, dataType) => + _alias = SanitizeAlias(propertyTypeAlias); - /// - /// Gets a value indicating whether the content type owning this property type is publishing. - /// - /// - /// A publishing content type supports draft and published values for properties. - /// It is possible to retrieve either the draft (default) or published value of a property. - /// Setting the value always sets the draft value, which then needs to be published. - /// A non-publishing content type only supports one value for properties. Getting - /// the draft or published value of a property returns the same thing, and publishing - /// a value property has no effect. - /// When true, getting the property value returns the edited value by default, but - /// it is possible to get the published value using the appropriate 'published' method - /// parameter. - /// When false, getting the property value always return the edited value, - /// regardless of the 'published' method parameter. - /// - public bool SupportsPublishing { get; set; } - - /// - [DataMember] - public string Name - { - get => _name; - set => SetPropertyValueAndDetectChanges(value, ref _name!, nameof(Name)); - } + /// + /// Initializes a new instance of the class. + /// + public PropertyType(IShortStringHelper shortStringHelper, string propertyEditorAlias, + ValueStorageType valueStorageType) + : this(shortStringHelper, propertyEditorAlias, valueStorageType, false) + { + } - /// - [DataMember] - public virtual string Alias - { - get => _alias; - set => SetPropertyValueAndDetectChanges(SanitizeAlias(value), ref _alias!, nameof(Alias)); - } + /// + /// Initializes a new instance of the class. + /// + public PropertyType(IShortStringHelper shortStringHelper, string propertyEditorAlias, + ValueStorageType valueStorageType, string propertyTypeAlias) + : this(shortStringHelper, propertyEditorAlias, valueStorageType, false, propertyTypeAlias) + { + } - /// - [DataMember] - public string? Description - { - get => _description; - set => SetPropertyValueAndDetectChanges(value, ref _description, nameof(Description)); - } + /// + /// Initializes a new instance of the class. + /// + /// + /// Set to true to force the value storage type. Values assigned to + /// the property, eg from the underlying datatype, will be ignored. + /// + public PropertyType(IShortStringHelper shortStringHelper, string propertyEditorAlias, + ValueStorageType valueStorageType, bool forceValueStorageType, string? propertyTypeAlias = null) + { + _shortStringHelper = shortStringHelper; + _propertyEditorAlias = propertyEditorAlias; + _valueStorageType = valueStorageType; + _forceValueStorageType = forceValueStorageType; + _alias = propertyTypeAlias == null ? string.Empty : SanitizeAlias(propertyTypeAlias); + _variations = ContentVariation.Nothing; + _name = string.Empty; + } - /// - [DataMember] - public int DataTypeId - { - get => _dataTypeId; - set => SetPropertyValueAndDetectChanges(value, ref _dataTypeId, nameof(DataTypeId)); - } + /// + public bool Equals(PropertyType? other) => + other != null && (base.Equals(other) || (Alias?.InvariantEquals(other.Alias) ?? false)); - [DataMember] - public Guid DataTypeKey - { - get => _dataTypeKey; - set => SetPropertyValueAndDetectChanges(value, ref _dataTypeKey, nameof(DataTypeKey)); - } + /// + /// Gets a value indicating whether the content type owning this property type is publishing. + /// + /// + /// + /// A publishing content type supports draft and published values for properties. + /// It is possible to retrieve either the draft (default) or published value of a property. + /// Setting the value always sets the draft value, which then needs to be published. + /// + /// + /// A non-publishing content type only supports one value for properties. Getting + /// the draft or published value of a property returns the same thing, and publishing + /// a value property has no effect. + /// + /// + /// When true, getting the property value returns the edited value by default, but + /// it is possible to get the published value using the appropriate 'published' method + /// parameter. + /// + /// + /// When false, getting the property value always return the edited value, + /// regardless of the 'published' method parameter. + /// + /// + public bool SupportsPublishing { get; set; } + + /// + [DataMember] + public string Name + { + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name!, nameof(Name)); + } - /// - [DataMember] - public string PropertyEditorAlias - { - get => _propertyEditorAlias; - set => SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias!, nameof(PropertyEditorAlias)); - } + /// + [DataMember] + public virtual string Alias + { + get => _alias; + set => SetPropertyValueAndDetectChanges(SanitizeAlias(value), ref _alias!, nameof(Alias)); + } + + /// + [DataMember] + public string? Description + { + get => _description; + set => SetPropertyValueAndDetectChanges(value, ref _description, nameof(Description)); + } + + /// + [DataMember] + public int DataTypeId + { + get => _dataTypeId; + set => SetPropertyValueAndDetectChanges(value, ref _dataTypeId, nameof(DataTypeId)); + } + + [DataMember] + public Guid DataTypeKey + { + get => _dataTypeKey; + set => SetPropertyValueAndDetectChanges(value, ref _dataTypeKey, nameof(DataTypeKey)); + } + + /// + [DataMember] + public string PropertyEditorAlias + { + get => _propertyEditorAlias; + set => SetPropertyValueAndDetectChanges(value, ref _propertyEditorAlias!, nameof(PropertyEditorAlias)); + } - /// - [DataMember] - public ValueStorageType ValueStorageType + /// + [DataMember] + public ValueStorageType ValueStorageType + { + get => _valueStorageType; + set { - get => _valueStorageType; - set + if (_forceValueStorageType) { - if (_forceValueStorageType) return; // ignore changes - SetPropertyValueAndDetectChanges(value, ref _valueStorageType, nameof(ValueStorageType)); + return; // ignore changes } - } - /// - [DataMember] - [DoNotClone] - public Lazy? PropertyGroupId - { - get => _propertyGroupId; - set => SetPropertyValueAndDetectChanges(value, ref _propertyGroupId, nameof(PropertyGroupId)); + SetPropertyValueAndDetectChanges(value, ref _valueStorageType, nameof(ValueStorageType)); } + } - /// - [DataMember] - public bool Mandatory - { - get => _mandatory; - set => SetPropertyValueAndDetectChanges(value, ref _mandatory, nameof(Mandatory)); - } + /// + [DataMember] + [DoNotClone] + public Lazy? PropertyGroupId + { + get => _propertyGroupId; + set => SetPropertyValueAndDetectChanges(value, ref _propertyGroupId, nameof(PropertyGroupId)); + } + /// + [DataMember] + public bool Mandatory + { + get => _mandatory; + set => SetPropertyValueAndDetectChanges(value, ref _mandatory, nameof(Mandatory)); + } - /// - [DataMember] - public string? MandatoryMessage - { - get => _mandatoryMessage; - set => SetPropertyValueAndDetectChanges(value, ref _mandatoryMessage, nameof(MandatoryMessage)); - } - /// - [DataMember] - public bool LabelOnTop - { - get => _labelOnTop; - set => SetPropertyValueAndDetectChanges(value, ref _labelOnTop, nameof(LabelOnTop)); - } - - /// - [DataMember] - public int SortOrder - { - get => _sortOrder; - set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, nameof(SortOrder)); - } + /// + [DataMember] + public string? MandatoryMessage + { + get => _mandatoryMessage; + set => SetPropertyValueAndDetectChanges(value, ref _mandatoryMessage, nameof(MandatoryMessage)); + } - /// - [DataMember] - public string? ValidationRegExp - { - get => _validationRegExp; - set => SetPropertyValueAndDetectChanges(value, ref _validationRegExp, nameof(ValidationRegExp)); - } + /// + [DataMember] + public bool LabelOnTop + { + get => _labelOnTop; + set => SetPropertyValueAndDetectChanges(value, ref _labelOnTop, nameof(LabelOnTop)); + } + /// + [DataMember] + public int SortOrder + { + get => _sortOrder; + set => SetPropertyValueAndDetectChanges(value, ref _sortOrder, nameof(SortOrder)); + } - /// - /// Gets or sets the custom validation message used when a pattern for this PropertyType must be matched - /// - [DataMember] - public string? ValidationRegExpMessage - { - get => _validationRegExpMessage; - set => SetPropertyValueAndDetectChanges(value, ref _validationRegExpMessage, nameof(ValidationRegExpMessage)); - } + /// + [DataMember] + public string? ValidationRegExp + { + get => _validationRegExp; + set => SetPropertyValueAndDetectChanges(value, ref _validationRegExp, nameof(ValidationRegExp)); + } - /// - public ContentVariation Variations - { - get => _variations; - set => SetPropertyValueAndDetectChanges(value, ref _variations, nameof(Variations)); - } - /// - public bool SupportsVariation(string? culture, string? segment, bool wildcards = false) - { - // exact validation: cannot accept a 'null' culture if the property type varies - // by culture, and likewise for segment - // wildcard validation: can accept a '*' culture or segment - return Variations.ValidateVariation(culture, segment, true, wildcards, false); - } + /// + /// Gets or sets the custom validation message used when a pattern for this PropertyType must be matched + /// + [DataMember] + public string? ValidationRegExpMessage + { + get => _validationRegExpMessage; + set => SetPropertyValueAndDetectChanges(value, ref _validationRegExpMessage, nameof(ValidationRegExpMessage)); + } - /// - /// Sanitizes a property type alias. - /// - private string SanitizeAlias(string value) - { - //NOTE: WE are doing this because we don't want to do a ToSafeAlias when the alias is the special case of - // being prefixed with Constants.PropertyEditors.InternalGenericPropertiesPrefix - // which is used internally + /// + public ContentVariation Variations + { + get => _variations; + set => SetPropertyValueAndDetectChanges(value, ref _variations, nameof(Variations)); + } - return value.StartsWith(Constants.PropertyEditors.InternalGenericPropertiesPrefix) - ? value - : value.ToCleanString(_shortStringHelper, CleanStringType.Alias | CleanStringType.UmbracoCase); - } + /// + public bool SupportsVariation(string? culture, string? segment, bool wildcards = false) => + // exact validation: cannot accept a 'null' culture if the property type varies + // by culture, and likewise for segment + // wildcard validation: can accept a '*' culture or segment + Variations.ValidateVariation(culture, segment, true, wildcards, false); - /// - public bool Equals(PropertyType? other) - { - return other != null && (base.Equals(other) || (Alias?.InvariantEquals(other.Alias) ?? false)); - } + /// + /// Sanitizes a property type alias. + /// + private string SanitizeAlias(string value) => + //NOTE: WE are doing this because we don't want to do a ToSafeAlias when the alias is the special case of + // being prefixed with Constants.PropertyEditors.InternalGenericPropertiesPrefix + // which is used internally + value.StartsWith(Constants.PropertyEditors.InternalGenericPropertiesPrefix) + ? value + : value.ToCleanString(_shortStringHelper, CleanStringType.Alias | CleanStringType.UmbracoCase); + + /// + public override int GetHashCode() + { + //Get hash code for the Name field if it is not null. + var baseHash = base.GetHashCode(); - /// - public override int GetHashCode() - { - //Get hash code for the Name field if it is not null. - int baseHash = base.GetHashCode(); + //Get hash code for the Alias field. + var hashAlias = Alias?.ToLowerInvariant().GetHashCode(); - //Get hash code for the Alias field. - int? hashAlias = Alias?.ToLowerInvariant().GetHashCode(); + //Calculate the hash code for the product. + return baseHash ^ hashAlias ?? baseHash; + } - //Calculate the hash code for the product. - return baseHash ^ hashAlias ?? baseHash; - } + /// + protected override void PerformDeepClone(object clone) + { + base.PerformDeepClone(clone); - /// - protected override void PerformDeepClone(object clone) + var clonedEntity = (PropertyType)clone; + //need to manually assign the Lazy value as it will not be automatically mapped + if (PropertyGroupId != null) { - base.PerformDeepClone(clone); - - var clonedEntity = (PropertyType) clone; - //need to manually assign the Lazy value as it will not be automatically mapped - if (PropertyGroupId != null) - { - clonedEntity._propertyGroupId = new Lazy(() => PropertyGroupId.Value); - } + clonedEntity._propertyGroupId = new Lazy(() => PropertyGroupId.Value); } } } diff --git a/src/Umbraco.Core/Models/PropertyTypeCollection.cs b/src/Umbraco.Core/Models/PropertyTypeCollection.cs index 96133f667703..2dccca5279e0 100644 --- a/src/Umbraco.Core/Models/PropertyTypeCollection.cs +++ b/src/Umbraco.Core/Models/PropertyTypeCollection.cs @@ -1,179 +1,183 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; using System.Collections.Specialized; -using System.Linq; +using System.ComponentModel; using System.Runtime.Serialization; -using System.Threading; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +//public interface IPropertyTypeCollection: IEnumerable +/// +/// Represents a collection of objects. +/// +[Serializable] +[DataContract] +// TODO: Change this to ObservableDictionary so we can reduce the INotifyCollectionChanged implementation details +public class PropertyTypeCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable, + ICollection { + public PropertyTypeCollection(bool supportsPublishing) => SupportsPublishing = supportsPublishing; - //public interface IPropertyTypeCollection: IEnumerable - /// - /// Represents a collection of objects. - /// - [Serializable] - [DataContract] - // TODO: Change this to ObservableDictionary so we can reduce the INotifyCollectionChanged implementation details - public class PropertyTypeCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable, ICollection - { - public PropertyTypeCollection(bool supportsPublishing) - { - SupportsPublishing = supportsPublishing; - } - - public PropertyTypeCollection(bool supportsPublishing, IEnumerable properties) - : this(supportsPublishing) - { - Reset(properties); - } + public PropertyTypeCollection(bool supportsPublishing, IEnumerable properties) + : this(supportsPublishing) => + Reset(properties); - public bool SupportsPublishing { get; } + public bool SupportsPublishing { get; } - // This baseclass calling is needed, else compiler will complain about nullability + // This baseclass calling is needed, else compiler will complain about nullability - /// - public bool IsReadOnly => ((ICollection)this).IsReadOnly; + /// + public bool IsReadOnly => ((ICollection)this).IsReadOnly; - /// - /// Resets the collection to only contain the instances referenced in the parameter. - /// - /// The properties. - /// - internal void Reset(IEnumerable properties) - { - //collection events will be raised in each of these calls - Clear(); + // 'new' keyword is required! we can explicitly implement ICollection.Add BUT since normally a concrete PropertyType type + // is passed in, the explicit implementation doesn't get called, this ensures it does get called. + public new void Add(IPropertyType item) + { + item.SupportsPublishing = SupportsPublishing; - //collection events will be raised in each of these calls - foreach (var property in properties) - Add(property); - } + // TODO: this is not pretty and should be refactored - protected override void SetItem(int index, IPropertyType item) + var key = GetKeyForItem(item); + if (key != null) { - item.SupportsPublishing = SupportsPublishing; - var oldItem = index >= 0 ? this[index] : item; - base.SetItem(index, item); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, oldItem)); - item.PropertyChanged += Item_PropertyChanged; + var exists = Contains(key); + if (exists) + { + //collection events will be raised in SetItem + SetItem(IndexOfKey(key), item); + return; + } } - protected override void RemoveItem(int index) + //check if the item's sort order is already in use + if (this.Any(x => x.SortOrder == item.SortOrder)) { - var removed = this[index]; - base.RemoveItem(index); - removed.PropertyChanged -= Item_PropertyChanged; - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); + //make it the next iteration + item.SortOrder = this.Max(x => x.SortOrder) + 1; } - protected override void InsertItem(int index, IPropertyType item) - { - item.SupportsPublishing = SupportsPublishing; - base.InsertItem(index, item); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); - item.PropertyChanged += Item_PropertyChanged; - } + //collection events will be raised in InsertItem + base.Add(item); + } - protected override void ClearItems() + public object DeepClone() + { + var clone = new PropertyTypeCollection(SupportsPublishing); + foreach (IPropertyType propertyType in this) { - base.ClearItems(); - foreach (var item in this) - item.PropertyChanged -= Item_PropertyChanged; - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + clone.Add((IPropertyType)propertyType.DeepClone()); } - // 'new' keyword is required! we can explicitly implement ICollection.Add BUT since normally a concrete PropertyType type - // is passed in, the explicit implementation doesn't get called, this ensures it does get called. - public new void Add(IPropertyType item) - { - item.SupportsPublishing = SupportsPublishing; - - // TODO: this is not pretty and should be refactored - - var key = GetKeyForItem(item); - if (key != null) - { - var exists = Contains(key); - if (exists) - { - //collection events will be raised in SetItem - SetItem(IndexOfKey(key), item); - return; - } - } + return clone; + } - //check if the item's sort order is already in use - if (this.Any(x => x.SortOrder == item.SortOrder)) - { - //make it the next iteration - item.SortOrder = this.Max(x => x.SortOrder) + 1; - } + public event NotifyCollectionChangedEventHandler? CollectionChanged; - //collection events will be raised in InsertItem - base.Add(item); - } + /// + /// Resets the collection to only contain the instances referenced in the + /// parameter. + /// + /// The properties. + /// + internal void Reset(IEnumerable properties) + { + //collection events will be raised in each of these calls + Clear(); - /// - /// Occurs when a property changes on a IPropertyType that exists in this collection - /// - /// - /// - private void Item_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + //collection events will be raised in each of these calls + foreach (IPropertyType property in properties) { - var propType = (IPropertyType?)sender; - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, propType, propType)); + Add(property); } + } - /// - /// Determines whether this collection contains a whose alias matches the specified PropertyType. - /// - /// Alias of the PropertyType. - /// true if the collection contains the specified alias; otherwise, false. - /// - public new bool Contains(string propertyAlias) - { - return this.Any(x => x.Alias == propertyAlias); - } + protected override void SetItem(int index, IPropertyType item) + { + item.SupportsPublishing = SupportsPublishing; + IPropertyType oldItem = index >= 0 ? this[index] : item; + base.SetItem(index, item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, oldItem)); + item.PropertyChanged += Item_PropertyChanged; + } - public bool RemoveItem(string propertyTypeAlias) - { - var key = IndexOfKey(propertyTypeAlias); - if (key != -1) RemoveItem(key); - return key != -1; - } + protected override void RemoveItem(int index) + { + IPropertyType removed = this[index]; + base.RemoveItem(index); + removed.PropertyChanged -= Item_PropertyChanged; + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); + } - public int IndexOfKey(string key) - { - for (var i = 0; i < Count; i++) - if (this[i].Alias == key) - return i; - return -1; - } + protected override void InsertItem(int index, IPropertyType item) + { + item.SupportsPublishing = SupportsPublishing; + base.InsertItem(index, item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); + item.PropertyChanged += Item_PropertyChanged; + } - protected override string GetKeyForItem(IPropertyType item) + protected override void ClearItems() + { + base.ClearItems(); + foreach (IPropertyType item in this) { - return item.Alias!; + item.PropertyChanged -= Item_PropertyChanged; } - public event NotifyCollectionChangedEventHandler? CollectionChanged; + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + /// + /// Occurs when a property changes on a IPropertyType that exists in this collection + /// + /// + /// + private void Item_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + var propType = (IPropertyType?)sender; + OnCollectionChanged( + new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, propType, propType)); + } + + /// + /// Determines whether this collection contains a whose alias matches the specified + /// PropertyType. + /// + /// Alias of the PropertyType. + /// true if the collection contains the specified alias; otherwise, false. + /// + public new bool Contains(string propertyAlias) => this.Any(x => x.Alias == propertyAlias); - /// - /// Clears all event handlers - /// - public void ClearCollectionChangedEvents() => CollectionChanged = null; - protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) + public bool RemoveItem(string propertyTypeAlias) + { + var key = IndexOfKey(propertyTypeAlias); + if (key != -1) { - CollectionChanged?.Invoke(this, args); + RemoveItem(key); } - public object DeepClone() + return key != -1; + } + + public int IndexOfKey(string key) + { + for (var i = 0; i < Count; i++) { - var clone = new PropertyTypeCollection(SupportsPublishing); - foreach (var propertyType in this) - clone.Add((IPropertyType) propertyType.DeepClone()); - return clone; + if (this[i].Alias == key) + { + return i; + } } + + return -1; } + + protected override string GetKeyForItem(IPropertyType item) => item.Alias!; + + /// + /// Clears all event handlers + /// + public void ClearCollectionChangedEvents() => CollectionChanged = null; + + protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) => + CollectionChanged?.Invoke(this, args); } diff --git a/src/Umbraco.Core/Models/PublicAccessEntry.cs b/src/Umbraco.Core/Models/PublicAccessEntry.cs index 00e05442d8a2..e391d56b675b 100644 --- a/src/Umbraco.Core/Models/PublicAccessEntry.cs +++ b/src/Umbraco.Core/Models/PublicAccessEntry.cs @@ -1,158 +1,158 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; +using System.Collections.Specialized; using System.Runtime.Serialization; using Umbraco.Cms.Core.Collections; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +[Serializable] +[DataContract(IsReference = true)] +public class PublicAccessEntry : EntityBase { - [Serializable] - [DataContract(IsReference = true)] - public class PublicAccessEntry : EntityBase + private readonly List _removedRules = new(); + private readonly EventClearingObservableCollection _ruleCollection; + private int _loginNodeId; + private int _noAccessNodeId; + private int _protectedNodeId; + + public PublicAccessEntry(IContent protectedNode, IContent loginNode, IContent noAccessNode, + IEnumerable ruleCollection) { - private readonly EventClearingObservableCollection _ruleCollection; - private int _protectedNodeId; - private int _noAccessNodeId; - private int _loginNodeId; - private readonly List _removedRules = new List(); + if (protectedNode == null) + { + throw new ArgumentNullException(nameof(protectedNode)); + } - public PublicAccessEntry(IContent protectedNode, IContent loginNode, IContent noAccessNode, IEnumerable ruleCollection) + if (loginNode == null) { - if (protectedNode == null) throw new ArgumentNullException(nameof(protectedNode)); - if (loginNode == null) throw new ArgumentNullException(nameof(loginNode)); - if (noAccessNode == null) throw new ArgumentNullException(nameof(noAccessNode)); + throw new ArgumentNullException(nameof(loginNode)); + } - LoginNodeId = loginNode.Id; - NoAccessNodeId = noAccessNode.Id; - _protectedNodeId = protectedNode.Id; + if (noAccessNode == null) + { + throw new ArgumentNullException(nameof(noAccessNode)); + } - _ruleCollection = new EventClearingObservableCollection(ruleCollection); - _ruleCollection.CollectionChanged += _ruleCollection_CollectionChanged; + LoginNodeId = loginNode.Id; + NoAccessNodeId = noAccessNode.Id; + _protectedNodeId = protectedNode.Id; - foreach (var rule in _ruleCollection) - rule.AccessEntryId = Key; - } + _ruleCollection = new EventClearingObservableCollection(ruleCollection); + _ruleCollection.CollectionChanged += _ruleCollection_CollectionChanged; - public PublicAccessEntry(Guid id, int protectedNodeId, int loginNodeId, int noAccessNodeId, IEnumerable ruleCollection) + foreach (PublicAccessRule rule in _ruleCollection) { - Key = id; - Id = Key.GetHashCode(); + rule.AccessEntryId = Key; + } + } - LoginNodeId = loginNodeId; - NoAccessNodeId = noAccessNodeId; - _protectedNodeId = protectedNodeId; + public PublicAccessEntry(Guid id, int protectedNodeId, int loginNodeId, int noAccessNodeId, + IEnumerable ruleCollection) + { + Key = id; + Id = Key.GetHashCode(); - _ruleCollection = new EventClearingObservableCollection(ruleCollection); - _ruleCollection.CollectionChanged += _ruleCollection_CollectionChanged; + LoginNodeId = loginNodeId; + NoAccessNodeId = noAccessNodeId; + _protectedNodeId = protectedNodeId; - foreach (var rule in _ruleCollection) - rule.AccessEntryId = Key; - } + _ruleCollection = new EventClearingObservableCollection(ruleCollection); + _ruleCollection.CollectionChanged += _ruleCollection_CollectionChanged; - void _ruleCollection_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + foreach (PublicAccessRule rule in _ruleCollection) { - OnPropertyChanged(nameof(Rules)); + rule.AccessEntryId = Key; + } + } - //if (e.Action == NotifyCollectionChangedAction.Add) - //{ - // var item = e.NewItems.Cast().First(); + public IEnumerable RemovedRules => _removedRules; - // if (_addedSections.Contains(item) == false) - // { - // _addedSections.Add(item); - // } - //} + public IEnumerable Rules => _ruleCollection; - if (e.Action == NotifyCollectionChangedAction.Remove) - { - var item = e.OldItems?.Cast().First(); + [DataMember] + public int LoginNodeId + { + get => _loginNodeId; + set => SetPropertyValueAndDetectChanges(value, ref _loginNodeId, nameof(LoginNodeId)); + } - if (item is not null) - { - if (_removedRules.Contains(item.Key) == false) - { - _removedRules.Add(item.Key); - } - } - } - } + [DataMember] + public int NoAccessNodeId + { + get => _noAccessNodeId; + set => SetPropertyValueAndDetectChanges(value, ref _noAccessNodeId, nameof(NoAccessNodeId)); + } - public IEnumerable RemovedRules => _removedRules; + [DataMember] + public int ProtectedNodeId + { + get => _protectedNodeId; + set => SetPropertyValueAndDetectChanges(value, ref _protectedNodeId, nameof(ProtectedNodeId)); + } - public IEnumerable Rules => _ruleCollection; + private void _ruleCollection_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + OnPropertyChanged(nameof(Rules)); - public PublicAccessRule AddRule(string ruleValue, string ruleType) - { - var rule = new PublicAccessRule - { - AccessEntryId = Key, - RuleValue = ruleValue, - RuleType = ruleType - }; - _ruleCollection.Add(rule); - return rule; - } + //if (e.Action == NotifyCollectionChangedAction.Add) + //{ + // var item = e.NewItems.Cast().First(); - public void RemoveRule(PublicAccessRule rule) - { - _ruleCollection.Remove(rule); - } + // if (_addedSections.Contains(item) == false) + // { + // _addedSections.Add(item); + // } + //} - public void ClearRules() + if (e.Action == NotifyCollectionChangedAction.Remove) { - _ruleCollection.Clear(); + PublicAccessRule item = e.OldItems?.Cast().First(); + + if (item is not null) + { + if (_removedRules.Contains(item.Key) == false) + { + _removedRules.Add(item.Key); + } + } } + } + public PublicAccessRule AddRule(string ruleValue, string ruleType) + { + var rule = new PublicAccessRule {AccessEntryId = Key, RuleValue = ruleValue, RuleType = ruleType}; + _ruleCollection.Add(rule); + return rule; + } - internal void ClearRemovedRules() - { - _removedRules.Clear(); - } + public void RemoveRule(PublicAccessRule rule) => _ruleCollection.Remove(rule); - [DataMember] - public int LoginNodeId - { - get => _loginNodeId; - set => SetPropertyValueAndDetectChanges(value, ref _loginNodeId, nameof(LoginNodeId)); - } + public void ClearRules() => _ruleCollection.Clear(); - [DataMember] - public int NoAccessNodeId - { - get => _noAccessNodeId; - set => SetPropertyValueAndDetectChanges(value, ref _noAccessNodeId, nameof(NoAccessNodeId)); - } - [DataMember] - public int ProtectedNodeId - { - get => _protectedNodeId; - set => SetPropertyValueAndDetectChanges(value, ref _protectedNodeId, nameof(ProtectedNodeId)); - } + internal void ClearRemovedRules() => _removedRules.Clear(); - public override void ResetDirtyProperties(bool rememberDirty) + public override void ResetDirtyProperties(bool rememberDirty) + { + _removedRules.Clear(); + base.ResetDirtyProperties(rememberDirty); + foreach (PublicAccessRule publicAccessRule in _ruleCollection) { - _removedRules.Clear(); - base.ResetDirtyProperties(rememberDirty); - foreach (var publicAccessRule in _ruleCollection) - { - publicAccessRule.ResetDirtyProperties(rememberDirty); - } + publicAccessRule.ResetDirtyProperties(rememberDirty); } + } - protected override void PerformDeepClone(object clone) - { - base.PerformDeepClone(clone); + protected override void PerformDeepClone(object clone) + { + base.PerformDeepClone(clone); - var cloneEntity = (PublicAccessEntry)clone; + var cloneEntity = (PublicAccessEntry)clone; - if (cloneEntity._ruleCollection != null) - { - cloneEntity._ruleCollection.ClearCollectionChangedEvents(); //clear this event handler if any - cloneEntity._ruleCollection.CollectionChanged += cloneEntity._ruleCollection_CollectionChanged; //re-assign correct event handler - } + if (cloneEntity._ruleCollection != null) + { + cloneEntity._ruleCollection.ClearCollectionChangedEvents(); //clear this event handler if any + cloneEntity._ruleCollection.CollectionChanged += + cloneEntity._ruleCollection_CollectionChanged; //re-assign correct event handler } } } diff --git a/src/Umbraco.Core/Models/PublicAccessRule.cs b/src/Umbraco.Core/Models/PublicAccessRule.cs index 790d8b6a1be7..613643bb7c99 100644 --- a/src/Umbraco.Core/Models/PublicAccessRule.cs +++ b/src/Umbraco.Core/Models/PublicAccessRule.cs @@ -1,41 +1,37 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models -{ - [Serializable] - [DataContract(IsReference = true)] - public class PublicAccessRule : EntityBase - { - private string? _ruleValue; - private string? _ruleType; - - public PublicAccessRule(Guid id, Guid accessEntryId) - { - AccessEntryId = accessEntryId; - Key = id; - Id = Key.GetHashCode(); - } +namespace Umbraco.Cms.Core.Models; - public PublicAccessRule() - { - } +[Serializable] +[DataContract(IsReference = true)] +public class PublicAccessRule : EntityBase +{ + private string? _ruleType; + private string? _ruleValue; - public Guid AccessEntryId { get; set; } + public PublicAccessRule(Guid id, Guid accessEntryId) + { + AccessEntryId = accessEntryId; + Key = id; + Id = Key.GetHashCode(); + } - public string? RuleValue - { - get => _ruleValue; - set => SetPropertyValueAndDetectChanges(value, ref _ruleValue, nameof(RuleValue)); - } + public PublicAccessRule() + { + } - public string? RuleType - { - get => _ruleType; - set => SetPropertyValueAndDetectChanges(value, ref _ruleType, nameof(RuleType)); - } + public Guid AccessEntryId { get; set; } + public string? RuleValue + { + get => _ruleValue; + set => SetPropertyValueAndDetectChanges(value, ref _ruleValue, nameof(RuleValue)); + } + public string? RuleType + { + get => _ruleType; + set => SetPropertyValueAndDetectChanges(value, ref _ruleType, nameof(RuleType)); } } diff --git a/src/Umbraco.Core/Models/PublishedContent/Fallback.cs b/src/Umbraco.Core/Models/PublishedContent/Fallback.cs index 1aaa0d98142e..e8f057c6afc6 100644 --- a/src/Umbraco.Core/Models/PublishedContent/Fallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/Fallback.cs @@ -1,75 +1,63 @@ -using System; -using System.Collections; -using System.Collections.Generic; +using System.Collections; -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Manages the built-in fallback policies. +/// +public struct Fallback : IEnumerable { + private readonly int[] _values; + /// - /// Manages the built-in fallback policies. + /// Initializes a new instance of the struct with values. /// - public struct Fallback : IEnumerable - { - private readonly int[] _values; - - /// - /// Initializes a new instance of the struct with values. - /// - private Fallback(int[] values) - { - _values = values; - } + private Fallback(int[] values) => _values = values; - /// - /// Gets an ordered set of fallback policies. - /// - /// - public static Fallback To(params int[] values) => new Fallback(values); + /// + /// Gets an ordered set of fallback policies. + /// + /// + public static Fallback To(params int[] values) => new(values); - /// - /// Do not fallback. - /// - public const int None = 0; + /// + /// Do not fallback. + /// + public const int None = 0; - /// - /// Fallback to default value. - /// - public const int DefaultValue = 1; + /// + /// Fallback to default value. + /// + public const int DefaultValue = 1; - /// - /// Gets the fallback to default value policy. - /// - public static Fallback ToDefaultValue => new Fallback(new[] { DefaultValue }); + /// + /// Gets the fallback to default value policy. + /// + public static Fallback ToDefaultValue => new(new[] {DefaultValue}); - /// - /// Fallback to other languages. - /// - public const int Language = 2; + /// + /// Fallback to other languages. + /// + public const int Language = 2; - /// - /// Gets the fallback to language policy. - /// - public static Fallback ToLanguage => new Fallback(new[] { Language }); + /// + /// Gets the fallback to language policy. + /// + public static Fallback ToLanguage => new(new[] {Language}); - /// - /// Fallback to tree ancestors. - /// - public const int Ancestors = 3; + /// + /// Fallback to tree ancestors. + /// + public const int Ancestors = 3; - /// - /// Gets the fallback to tree ancestors policy. - /// - public static Fallback ToAncestors => new Fallback(new[] { Ancestors }); + /// + /// Gets the fallback to tree ancestors policy. + /// + public static Fallback ToAncestors => new(new[] {Ancestors}); - /// - public IEnumerator GetEnumerator() - { - return ((IEnumerable)_values ?? Array.Empty()).GetEnumerator(); - } + /// + public IEnumerator GetEnumerator() => ((IEnumerable)_values ?? Array.Empty()).GetEnumerator(); - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/src/Umbraco.Core/Models/PublishedContent/HttpContextVariationContextAccessor.cs b/src/Umbraco.Core/Models/PublishedContent/HttpContextVariationContextAccessor.cs index 3fb18fad2dcf..6d8fe9e54790 100644 --- a/src/Umbraco.Core/Models/PublishedContent/HttpContextVariationContextAccessor.cs +++ b/src/Umbraco.Core/Models/PublishedContent/HttpContextVariationContextAccessor.cs @@ -1,25 +1,24 @@ using Umbraco.Cms.Core.Cache; -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Implements on top of . +/// +public class HttpContextVariationContextAccessor : IVariationContextAccessor { + private const string ContextKey = "Umbraco.Web.Models.PublishedContent.DefaultVariationContextAccessor"; + private readonly IRequestCache _requestCache; + /// - /// Implements on top of . + /// Initializes a new instance of the class. /// - public class HttpContextVariationContextAccessor : IVariationContextAccessor - { - private readonly IRequestCache _requestCache; - private const string ContextKey = "Umbraco.Web.Models.PublishedContent.DefaultVariationContextAccessor"; + public HttpContextVariationContextAccessor(IRequestCache requestCache) => _requestCache = requestCache; - /// - /// Initializes a new instance of the class. - /// - public HttpContextVariationContextAccessor(IRequestCache requestCache) => _requestCache = requestCache; - - /// - public VariationContext? VariationContext - { - get => (VariationContext?) _requestCache.Get(ContextKey); - set => _requestCache.Set(ContextKey, value); - } + /// + public VariationContext? VariationContext + { + get => (VariationContext?)_requestCache.Get(ContextKey); + set => _requestCache.Set(ContextKey, value); } } diff --git a/src/Umbraco.Core/Models/PublishedContent/HybridVariationContextAccessor.cs b/src/Umbraco.Core/Models/PublishedContent/HybridVariationContextAccessor.cs index d974041d3b4b..2be963843856 100644 --- a/src/Umbraco.Core/Models/PublishedContent/HybridVariationContextAccessor.cs +++ b/src/Umbraco.Core/Models/PublishedContent/HybridVariationContextAccessor.cs @@ -1,23 +1,23 @@ using Umbraco.Cms.Core.Cache; -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Implements a hybrid . +/// +public class HybridVariationContextAccessor : HybridAccessorBase, IVariationContextAccessor { + public HybridVariationContextAccessor(IRequestCache requestCache) + : base(requestCache) + { + } + /// - /// Implements a hybrid . + /// Gets or sets the object. /// - public class HybridVariationContextAccessor : HybridAccessorBase, IVariationContextAccessor + public VariationContext? VariationContext { - public HybridVariationContextAccessor(IRequestCache requestCache) - : base(requestCache) - { } - - /// - /// Gets or sets the object. - /// - public VariationContext? VariationContext - { - get => Value; - set => Value = value; - } + get => Value; + set => Value = value; } } diff --git a/src/Umbraco.Core/Models/PublishedContent/IAutoPublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/IAutoPublishedModelFactory.cs index 2838297a8e20..37ca5b3733c2 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IAutoPublishedModelFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IAutoPublishedModelFactory.cs @@ -1,24 +1,22 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent -{ +namespace Umbraco.Cms.Core.Models.PublishedContent; +/// +/// Provides a live published model creation service. +/// +public interface IAutoPublishedModelFactory : IPublishedModelFactory +{ /// - /// Provides a live published model creation service. + /// Gets an object that can be used to synchronize access to the factory. /// - public interface IAutoPublishedModelFactory : IPublishedModelFactory - { - /// - /// Gets an object that can be used to synchronize access to the factory. - /// - object SyncRoot { get; } + object SyncRoot { get; } - /// - /// Tells the factory that it should build a new generation of models - /// - void Reset(); + /// + /// If the live model factory + /// + bool Enabled { get; } - /// - /// If the live model factory - /// - bool Enabled { get; } - } + /// + /// Tells the factory that it should build a new generation of models + /// + void Reset(); } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index eb5233993687..727b9afa8455 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -1,150 +1,158 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// +/// Represents a published content item. +/// +/// +/// Can be a published document, media or member. +/// +public interface IPublishedContent : IPublishedElement { + #region Content + + // TODO: IPublishedContent properties colliding with models + // we need to find a way to remove as much clutter as possible from IPublishedContent, + // since this is preventing someone from creating a property named 'Path' and have it + // in a model, for instance. we could move them all under one unique property eg + // Infos, so we would do .Infos.SortOrder - just an idea - not going to do it in v8 + + /// + /// Gets the unique identifier of the content item. + /// + int Id { get; } + + /// + /// Gets the name of the content item for the current culture. + /// + string? Name { get; } + + /// + /// Gets the URL segment of the content item for the current culture. + /// + string? UrlSegment { get; } + + /// + /// Gets the sort order of the content item. + /// + int SortOrder { get; } + + /// + /// Gets the tree level of the content item. + /// + int Level { get; } + + /// + /// Gets the tree path of the content item. + /// + string Path { get; } + + /// + /// Gets the identifier of the template to use to render the content item. + /// + int? TemplateId { get; } + + /// + /// Gets the identifier of the user who created the content item. + /// + int CreatorId { get; } + + /// + /// Gets the date the content item was created. + /// + DateTime CreateDate { get; } + + /// + /// Gets the identifier of the user who last updated the content item. + /// + int WriterId { get; } + + /// + /// Gets the date the content item was last updated. + /// + /// + /// For published content items, this is also the date the item was published. + /// + /// This date is always global to the content item, see CultureDate() for the + /// date each culture was published. + /// + /// + DateTime UpdateDate { get; } + + /// + /// Gets available culture infos. + /// + /// + /// + /// Contains only those culture that are available. For a published content, these are + /// the cultures that are published. For a draft content, those that are 'available' ie + /// have a non-empty content name. + /// + /// Does not contain the invariant culture. + /// // fixme? + /// + IReadOnlyDictionary Cultures { get; } + + /// + /// Gets the type of the content item (document, media...). + /// + PublishedItemType ItemType { get; } - /// /// - /// Represents a published content item. + /// Gets a value indicating whether the content is draft. /// /// - /// Can be a published document, media or member. + /// + /// A content is draft when it is the unpublished version of a content, which may + /// have a published version, or not. + /// + /// + /// When retrieving documents from cache in non-preview mode, IsDraft is always false, + /// as only published documents are returned. When retrieving in preview mode, IsDraft can + /// either be true (document is not published, or has been edited, and what is returned + /// is the edited version) or false (document is published, and has not been edited, and + /// what is returned is the published version). + /// /// - public interface IPublishedContent : IPublishedElement - { - #region Content - - // TODO: IPublishedContent properties colliding with models - // we need to find a way to remove as much clutter as possible from IPublishedContent, - // since this is preventing someone from creating a property named 'Path' and have it - // in a model, for instance. we could move them all under one unique property eg - // Infos, so we would do .Infos.SortOrder - just an idea - not going to do it in v8 - - /// - /// Gets the unique identifier of the content item. - /// - int Id { get; } - - /// - /// Gets the name of the content item for the current culture. - /// - string? Name { get; } - - /// - /// Gets the URL segment of the content item for the current culture. - /// - string? UrlSegment { get; } - - /// - /// Gets the sort order of the content item. - /// - int SortOrder { get; } - - /// - /// Gets the tree level of the content item. - /// - int Level { get; } - - /// - /// Gets the tree path of the content item. - /// - string Path { get; } - - /// - /// Gets the identifier of the template to use to render the content item. - /// - int? TemplateId { get; } - - /// - /// Gets the identifier of the user who created the content item. - /// - int CreatorId { get; } - - /// - /// Gets the date the content item was created. - /// - DateTime CreateDate { get; } - - /// - /// Gets the identifier of the user who last updated the content item. - /// - int WriterId { get; } - - /// - /// Gets the date the content item was last updated. - /// - /// - /// For published content items, this is also the date the item was published. - /// This date is always global to the content item, see CultureDate() for the - /// date each culture was published. - /// - DateTime UpdateDate { get; } - - /// - /// Gets available culture infos. - /// - /// - /// Contains only those culture that are available. For a published content, these are - /// the cultures that are published. For a draft content, those that are 'available' ie - /// have a non-empty content name. - /// Does not contain the invariant culture. // fixme? - /// - IReadOnlyDictionary Cultures { get; } - - /// - /// Gets the type of the content item (document, media...). - /// - PublishedItemType ItemType { get; } - - /// - /// Gets a value indicating whether the content is draft. - /// - /// - /// A content is draft when it is the unpublished version of a content, which may - /// have a published version, or not. - /// When retrieving documents from cache in non-preview mode, IsDraft is always false, - /// as only published documents are returned. When retrieving in preview mode, IsDraft can - /// either be true (document is not published, or has been edited, and what is returned - /// is the edited version) or false (document is published, and has not been edited, and - /// what is returned is the published version). - /// - bool IsDraft(string? culture = null); - - /// - /// Gets a value indicating whether the content is published. - /// - /// - /// A content is published when it has a published version. - /// When retrieving documents from cache in non-preview mode, IsPublished is always - /// true, as only published documents are returned. When retrieving in draft mode, IsPublished - /// can either be true (document has a published version) or false (document has no - /// published version). - /// It is therefore possible for both IsDraft and IsPublished to be true at the same - /// time, meaning that the content is the draft version, and a published version exists. - /// - bool IsPublished(string? culture = null); - - #endregion - - #region Tree - - /// - /// Gets the parent of the content item. - /// - /// The parent of root content is null. - IPublishedContent? Parent { get; } - - /// - /// Gets the children of the content item that are available for the current culture. - /// - IEnumerable? Children { get; } - - /// - /// Gets all the children of the content item, regardless of whether they are available for the current culture. - /// - IEnumerable? ChildrenForAllCultures { get; } - - #endregion - } + bool IsDraft(string? culture = null); + + /// + /// Gets a value indicating whether the content is published. + /// + /// + /// A content is published when it has a published version. + /// + /// When retrieving documents from cache in non-preview mode, IsPublished is always + /// true, as only published documents are returned. When retrieving in draft mode, IsPublished + /// can either be true (document has a published version) or false (document has no + /// published version). + /// + /// + /// It is therefore possible for both IsDraft and IsPublished to be true at the same + /// time, meaning that the content is the draft version, and a published version exists. + /// + /// + bool IsPublished(string? culture = null); + + #endregion + + #region Tree + + /// + /// Gets the parent of the content item. + /// + /// The parent of root content is null. + IPublishedContent? Parent { get; } + + /// + /// Gets the children of the content item that are available for the current culture. + /// + IEnumerable? Children { get; } + + /// + /// Gets all the children of the content item, regardless of whether they are available for the current culture. + /// + IEnumerable? ChildrenForAllCultures { get; } + + #endregion } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs index bd3f77152d85..5ce8bef875b8 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs @@ -1,69 +1,67 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.Models.PublishedContent +/// +/// Represents an type. +/// +/// +/// Instances implementing the interface should be +/// immutable, ie if the content type changes, then a new instance needs to be created. +/// +public interface IPublishedContentType { /// - /// Represents an type. + /// Gets the unique key for the content type. /// - /// Instances implementing the interface should be - /// immutable, ie if the content type changes, then a new instance needs to be created. - public interface IPublishedContentType - { - /// - /// Gets the unique key for the content type. - /// - Guid Key { get; } + Guid Key { get; } - /// - /// Gets the content type identifier. - /// - int Id { get; } + /// + /// Gets the content type identifier. + /// + int Id { get; } - /// - /// Gets the content type alias. - /// - string Alias { get; } + /// + /// Gets the content type alias. + /// + string Alias { get; } - /// - /// Gets the content item type. - /// - PublishedItemType ItemType { get; } + /// + /// Gets the content item type. + /// + PublishedItemType ItemType { get; } - /// - /// Gets the aliases of the content types participating in the composition. - /// - HashSet CompositionAliases { get; } + /// + /// Gets the aliases of the content types participating in the composition. + /// + HashSet CompositionAliases { get; } - /// - /// Gets the content variations of the content type. - /// - ContentVariation Variations { get; } + /// + /// Gets the content variations of the content type. + /// + ContentVariation Variations { get; } - /// - /// Gets a value indicating whether this content type is for an element. - /// - bool IsElement { get; } + /// + /// Gets a value indicating whether this content type is for an element. + /// + bool IsElement { get; } - /// - /// Gets the content type properties. - /// - IEnumerable PropertyTypes { get; } + /// + /// Gets the content type properties. + /// + IEnumerable PropertyTypes { get; } - /// - /// Gets a property type index. - /// - /// The alias is case-insensitive. This is the only place where alias strings are compared. - int GetPropertyIndex(string alias); + /// + /// Gets a property type index. + /// + /// The alias is case-insensitive. This is the only place where alias strings are compared. + int GetPropertyIndex(string alias); - /// - /// Gets a property type. - /// - IPublishedPropertyType? GetPropertyType(string alias); + /// + /// Gets a property type. + /// + IPublishedPropertyType? GetPropertyType(string alias); - /// - /// Gets a property type. - /// - IPublishedPropertyType? GetPropertyType(int index); - } + /// + /// Gets a property type. + /// + IPublishedPropertyType? GetPropertyType(int index); } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs index b1a1740b31dd..a4dc8bc4290f 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs @@ -1,58 +1,58 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent -{ +namespace Umbraco.Cms.Core.Models.PublishedContent; +/// +/// Creates published content types. +/// +public interface IPublishedContentTypeFactory +{ /// - /// Creates published content types. + /// Creates a published content type. /// - public interface IPublishedContentTypeFactory - { - /// - /// Creates a published content type. - /// - /// An content type. - /// A published content type corresponding to the item type and content type. - IPublishedContentType CreateContentType(IContentTypeComposition contentType); + /// An content type. + /// A published content type corresponding to the item type and content type. + IPublishedContentType CreateContentType(IContentTypeComposition contentType); - /// - /// Creates a published property type. - /// - /// The published content type owning the property. - /// A property type. - /// Is used by constructor to create property types. - IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, IPropertyType propertyType); + /// + /// Creates a published property type. + /// + /// The published content type owning the property. + /// A property type. + /// Is used by constructor to create property types. + IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, IPropertyType propertyType); - /// - /// Creates a published property type. - /// - /// The published content type owning the property. - /// The property type alias. - /// The datatype identifier. - /// The variations. - /// Is used by constructor to create special property types. - IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations); + /// + /// Creates a published property type. + /// + /// The published content type owning the property. + /// The property type alias. + /// The datatype identifier. + /// The variations. + /// Is used by constructor to create special property types. + IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, string propertyTypeAlias, + int dataTypeId, ContentVariation variations); - /// - /// Creates a core (non-user) published property type. - /// - /// The published content type owning the property. - /// The property type alias. - /// The datatype identifier. - /// The variations. - /// Is used by constructor to create special property types. - IPublishedPropertyType CreateCorePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations); + /// + /// Creates a core (non-user) published property type. + /// + /// The published content type owning the property. + /// The property type alias. + /// The datatype identifier. + /// The variations. + /// Is used by constructor to create special property types. + IPublishedPropertyType CreateCorePropertyType(IPublishedContentType contentType, string propertyTypeAlias, + int dataTypeId, ContentVariation variations); - /// - /// Gets a published datatype. - /// - PublishedDataType GetDataType(int id); + /// + /// Gets a published datatype. + /// + PublishedDataType GetDataType(int id); - /// - /// Notifies the factory of datatype changes. - /// - /// - /// This is so the factory can flush its caches. - /// Invoked by the IPublishedSnapshotService. - /// - void NotifyDataTypeChanges(int[] ids); - } + /// + /// Notifies the factory of datatype changes. + /// + /// + /// This is so the factory can flush its caches. + /// Invoked by the IPublishedSnapshotService. + /// + void NotifyDataTypeChanges(int[] ids); } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs index 767d3eadc01c..4744adaaceb4 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs @@ -1,52 +1,50 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.Models.PublishedContent +/// +/// Represents a published element. +/// +public interface IPublishedElement { + #region ContentType + + /// + /// Gets the content type. + /// + IPublishedContentType ContentType { get; } + + #endregion + + #region PublishedElement + + /// + /// Gets the unique key of the published element. + /// + Guid Key { get; } + + #endregion + + #region Properties + + /// + /// Gets the properties of the element. + /// + /// + /// Contains one IPublishedProperty for each property defined for the content type, including + /// inherited properties. Some properties may have no value. + /// + IEnumerable Properties { get; } + /// - /// Represents a published element. + /// Gets a property identified by its alias. /// - public interface IPublishedElement - { - #region ContentType - - /// - /// Gets the content type. - /// - IPublishedContentType ContentType { get; } - - #endregion - - #region PublishedElement - - /// - /// Gets the unique key of the published element. - /// - Guid Key { get; } - - #endregion - - #region Properties - - /// - /// Gets the properties of the element. - /// - /// Contains one IPublishedProperty for each property defined for the content type, including - /// inherited properties. Some properties may have no value. - IEnumerable Properties { get; } - - /// - /// Gets a property identified by its alias. - /// - /// The property alias. - /// The property identified by the alias. - /// - /// If the content type has no property with that alias, including inherited properties, returns null, - /// otherwise return a property -- that may have no value (ie HasValue is false). - /// The alias is case-insensitive. - /// - IPublishedProperty? GetProperty(string alias); - - #endregion - } + /// The property alias. + /// The property identified by the alias. + /// + /// If the content type has no property with that alias, including inherited properties, returns null, + /// otherwise return a property -- that may have no value (ie HasValue is false). + /// The alias is case-insensitive. + /// + IPublishedProperty? GetProperty(string alias); + + #endregion } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedMemberCache.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedMemberCache.cs index ba8bdc43d480..cefb51241ef2 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedMemberCache.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedMemberCache.cs @@ -1,31 +1,29 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.Security; -namespace Umbraco.Cms.Core.PublishedCache +namespace Umbraco.Cms.Core.PublishedCache; + +public interface IPublishedMemberCache { - public interface IPublishedMemberCache - { - /// - /// Get an from an - /// - /// - /// - IPublishedContent? Get(IMember member); + /// + /// Get an from an + /// + /// + /// + IPublishedContent? Get(IMember member); - /// - /// Gets a content type identified by its unique identifier. - /// - /// The content type unique identifier. - /// The content type, or null. - IPublishedContentType GetContentType(int id); + /// + /// Gets a content type identified by its unique identifier. + /// + /// The content type unique identifier. + /// The content type, or null. + IPublishedContentType GetContentType(int id); - /// - /// Gets a content type identified by its alias. - /// - /// The content type alias. - /// The content type, or null. - /// The alias is case-insensitive. - IPublishedContentType GetContentType(string alias); - } + /// + /// Gets a content type identified by its alias. + /// + /// The content type alias. + /// The content type, or null. + /// The alias is case-insensitive. + IPublishedContentType GetContentType(string alias); } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs index c34a4a6ba4cc..754ea527c436 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs @@ -1,50 +1,49 @@ -using System.Collections; +using System.Collections; -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Provides the published model creation service. +/// +public interface IPublishedModelFactory { /// - /// Provides the published model creation service. + /// Creates a strongly-typed model representing a published element. /// - public interface IPublishedModelFactory - { - /// - /// Creates a strongly-typed model representing a published element. - /// - /// The original published element. - /// - /// The strongly-typed model representing the published element, - /// or the published element itself it the factory has no model for the corresponding element type. - /// - IPublishedElement CreateModel(IPublishedElement element); + /// The original published element. + /// + /// The strongly-typed model representing the published element, + /// or the published element itself it the factory has no model for the corresponding element type. + /// + IPublishedElement CreateModel(IPublishedElement element); - /// - /// Creates a List{T} of a strongly-typed model for a model type alias. - /// - /// The model type alias. - /// - /// A List{T} of the strongly-typed model, exposed as an IList. - /// - IList? CreateModelList(string? alias); + /// + /// Creates a List{T} of a strongly-typed model for a model type alias. + /// + /// The model type alias. + /// + /// A List{T} of the strongly-typed model, exposed as an IList. + /// + IList? CreateModelList(string? alias); - /// - /// Gets the Type of a strongly-typed model for a model type alias. - /// - /// The model type alias. - /// - /// The type of the strongly-typed model. - /// - Type GetModelType(string? alias); + /// + /// Gets the Type of a strongly-typed model for a model type alias. + /// + /// The model type alias. + /// + /// The type of the strongly-typed model. + /// + Type GetModelType(string? alias); - /// - /// Maps a CLR type that may contain model types, to an actual CLR type. - /// - /// The CLR type. - /// - /// The actual CLR type. - /// - /// - /// See for more details. - /// - Type MapModelType(Type type); - } + /// + /// Maps a CLR type that may contain model types, to an actual CLR type. + /// + /// The CLR type. + /// + /// The actual CLR type. + /// + /// + /// See for more details. + /// + Type MapModelType(Type type); } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs index 804d0972daf0..d3dd63594d55 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs @@ -1,62 +1,73 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Represents a property of an IPublishedElement. +/// +public interface IPublishedProperty { + IPublishedPropertyType PropertyType { get; } + /// - /// Represents a property of an IPublishedElement. + /// Gets the alias of the property. /// - public interface IPublishedProperty - { - IPublishedPropertyType PropertyType { get; } - - /// - /// Gets the alias of the property. - /// - string Alias { get; } + string Alias { get; } - /// - /// Gets a value indicating whether the property has a value. - /// - /// - /// This is somewhat implementation-dependent -- depending on whatever IPublishedCache considers - /// a missing value. - /// The XmlPublishedCache raw values are strings, and it will consider missing, null or empty (and - /// that includes whitespace-only) strings as "no value". - /// Other caches that get their raw value from the database would consider that a property has "no - /// value" if it is missing, null, or an empty string (including whitespace-only). - /// - bool HasValue(string? culture = null, string? segment = null); + /// + /// Gets a value indicating whether the property has a value. + /// + /// + /// + /// This is somewhat implementation-dependent -- depending on whatever IPublishedCache considers + /// a missing value. + /// + /// + /// The XmlPublishedCache raw values are strings, and it will consider missing, null or empty (and + /// that includes whitespace-only) strings as "no value". + /// + /// + /// Other caches that get their raw value from the database would consider that a property has "no + /// value" if it is missing, null, or an empty string (including whitespace-only). + /// + /// + bool HasValue(string? culture = null, string? segment = null); - /// - /// Gets the source value of the property. - /// - /// - /// The source value is whatever was passed to the property when it was instantiated, and it is - /// somewhat implementation-dependent -- depending on how the IPublishedCache is implemented. - /// The XmlPublishedCache source values are strings exclusively since they come from the Xml cache. - /// For other caches that get their source value from the database, it would be either a string, - /// an integer (Int32), a date and time (DateTime) or a decimal (double). - /// If you're using that value, you're probably wrong, unless you're doing some internal - /// Umbraco stuff. - /// - object? GetSourceValue(string? culture = null, string? segment = null); + /// + /// Gets the source value of the property. + /// + /// + /// + /// The source value is whatever was passed to the property when it was instantiated, and it is + /// somewhat implementation-dependent -- depending on how the IPublishedCache is implemented. + /// + /// The XmlPublishedCache source values are strings exclusively since they come from the Xml cache. + /// + /// For other caches that get their source value from the database, it would be either a string, + /// an integer (Int32), a date and time (DateTime) or a decimal (double). + /// + /// + /// If you're using that value, you're probably wrong, unless you're doing some internal + /// Umbraco stuff. + /// + /// + object? GetSourceValue(string? culture = null, string? segment = null); - /// - /// Gets the object value of the property. - /// - /// - /// The value is what you want to use when rendering content in an MVC view ie in C#. - /// It can be null, or any type of CLR object. - /// It has been fully prepared and processed by the appropriate converter. - /// - object? GetValue(string? culture = null, string? segment = null); + /// + /// Gets the object value of the property. + /// + /// + /// The value is what you want to use when rendering content in an MVC view ie in C#. + /// It can be null, or any type of CLR object. + /// It has been fully prepared and processed by the appropriate converter. + /// + object? GetValue(string? culture = null, string? segment = null); - /// - /// Gets the XPath value of the property. - /// - /// - /// The XPath value is what you want to use when navigating content via XPath eg in the XSLT engine. - /// It must be either null, or a string, or an XPathNavigator. - /// It has been fully prepared and processed by the appropriate converter. - /// - object? GetXPathValue(string? culture = null, string? segment = null); - } + /// + /// Gets the XPath value of the property. + /// + /// + /// The XPath value is what you want to use when navigating content via XPath eg in the XSLT engine. + /// It must be either null, or a string, or an XPathNavigator. + /// It has been fully prepared and processed by the appropriate converter. + /// + object? GetXPathValue(string? culture = null, string? segment = null); } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs index 3ab21d15f6b2..db92323a55a7 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs @@ -1,108 +1,114 @@ -using System; -using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Represents a published property type. +/// +/// +/// Instances implementing the interface should be +/// immutable, ie if the property type changes, then a new instance needs to be created. +/// +public interface IPublishedPropertyType { /// - /// Represents a published property type. + /// Gets the published content type containing the property type. /// - /// Instances implementing the interface should be - /// immutable, ie if the property type changes, then a new instance needs to be created. - public interface IPublishedPropertyType - { - /// - /// Gets the published content type containing the property type. - /// - IPublishedContentType? ContentType { get; } + IPublishedContentType? ContentType { get; } - /// - /// Gets the data type. - /// - PublishedDataType DataType { get; } + /// + /// Gets the data type. + /// + PublishedDataType DataType { get; } - /// - /// Gets property type alias. - /// - string Alias { get; } + /// + /// Gets property type alias. + /// + string Alias { get; } - /// - /// Gets the property editor alias. - /// - string EditorAlias { get; } + /// + /// Gets the property editor alias. + /// + string EditorAlias { get; } - /// - /// Gets a value indicating whether the property is a user content property. - /// - /// A non-user content property is a property that has been added to a - /// published content type by Umbraco but does not corresponds to a user-defined - /// published property. - bool IsUserProperty { get; } + /// + /// Gets a value indicating whether the property is a user content property. + /// + /// + /// A non-user content property is a property that has been added to a + /// published content type by Umbraco but does not corresponds to a user-defined + /// published property. + /// + bool IsUserProperty { get; } - /// - /// Gets the content variations of the property type. - /// - ContentVariation Variations { get; } + /// + /// Gets the content variations of the property type. + /// + ContentVariation Variations { get; } - /// - /// Determines whether a value is an actual value, or not a value. - /// - /// Used by property.HasValue and, for instance, in fallback scenarios. - bool? IsValue(object? value, PropertyValueLevel level); + /// + /// Gets the property cache level. + /// + PropertyCacheLevel CacheLevel { get; } - /// - /// Gets the property cache level. - /// - PropertyCacheLevel CacheLevel { get; } + /// + /// Gets the property model CLR type. + /// + /// + /// The model CLR type may be a type, or may contain types. + /// For the actual CLR type, see . + /// + Type ModelClrType { get; } - /// - /// Converts the source value into the intermediate value. - /// - /// The published element owning the property. - /// The source value. - /// A value indicating whether content should be considered draft. - /// The intermediate value. - object? ConvertSourceToInter(IPublishedElement owner, object? source, bool preview); + /// + /// Gets the property CLR type. + /// + /// + /// Returns the actual CLR type which does not contain types. + /// + /// Mapping from may throw if some instances + /// could not be mapped to actual CLR types. + /// + /// + Type? ClrType { get; } - /// - /// Converts the intermediate value into the object value. - /// - /// The published element owning the property. - /// The reference cache level. - /// The intermediate value. - /// A value indicating whether content should be considered draft. - /// The object value. - object? ConvertInterToObject(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview); + /// + /// Determines whether a value is an actual value, or not a value. + /// + /// Used by property.HasValue and, for instance, in fallback scenarios. + bool? IsValue(object? value, PropertyValueLevel level); - /// - /// Converts the intermediate value into the XPath value. - /// - /// The published element owning the property. - /// The reference cache level. - /// The intermediate value. - /// A value indicating whether content should be considered draft. - /// The XPath value. - /// - /// The XPath value can be either a string or an XPathNavigator. - /// - object? ConvertInterToXPath(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview); + /// + /// Converts the source value into the intermediate value. + /// + /// The published element owning the property. + /// The source value. + /// A value indicating whether content should be considered draft. + /// The intermediate value. + object? ConvertSourceToInter(IPublishedElement owner, object? source, bool preview); - /// - /// Gets the property model CLR type. - /// - /// - /// The model CLR type may be a type, or may contain types. - /// For the actual CLR type, see . - /// - Type ModelClrType { get; } + /// + /// Converts the intermediate value into the object value. + /// + /// The published element owning the property. + /// The reference cache level. + /// The intermediate value. + /// A value indicating whether content should be considered draft. + /// The object value. + object? ConvertInterToObject(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, + bool preview); - /// - /// Gets the property CLR type. - /// - /// - /// Returns the actual CLR type which does not contain types. - /// Mapping from may throw if some instances - /// could not be mapped to actual CLR types. - /// - Type? ClrType { get; } - } + /// + /// Converts the intermediate value into the XPath value. + /// + /// The published element owning the property. + /// The reference cache level. + /// The intermediate value. + /// A value indicating whether content should be considered draft. + /// The XPath value. + /// + /// The XPath value can be either a string or an XPathNavigator. + /// + object? ConvertInterToXPath(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, + bool preview); } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs index c1ecf1909a45..ca6d63089481 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs @@ -1,132 +1,163 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Provides a fallback strategy for getting values. +/// +public interface IPublishedValueFallback { /// - /// Provides a fallback strategy for getting values. + /// Tries to get a fallback value for a property. /// - public interface IPublishedValueFallback - { - /// - /// Tries to get a fallback value for a property. - /// - /// The property. - /// The requested culture. - /// The requested segment. - /// A fallback strategy. - /// An optional default value. - /// The fallback value. - /// A value indicating whether a fallback value could be provided. - /// - /// This method is called whenever property.Value(culture, segment, defaultValue) is called, and - /// property.HasValue(culture, segment) is false. - /// It can only fallback at property level (no recurse). - /// At property level, property.GetValue() does *not* implement fallback, and one has to - /// get property.Value() or property.Value{T}() to trigger fallback. - /// Note that and may not be contextualized, - /// so the variant context should be used to contextualize them (see our default implementation in - /// the web project. - /// - bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, object? defaultValue, out object? value); + /// The property. + /// The requested culture. + /// The requested segment. + /// A fallback strategy. + /// An optional default value. + /// The fallback value. + /// A value indicating whether a fallback value could be provided. + /// + /// + /// This method is called whenever property.Value(culture, segment, defaultValue) is called, and + /// property.HasValue(culture, segment) is false. + /// + /// It can only fallback at property level (no recurse). + /// + /// At property level, property.GetValue() does *not* implement fallback, and one has to + /// get property.Value() or property.Value{T}() to trigger fallback. + /// + /// + /// Note that and may not be contextualized, + /// so the variant context should be used to contextualize them (see our default implementation in + /// the web project. + /// + /// + bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, + object? defaultValue, out object? value); - /// - /// Tries to get a fallback value for a property. - /// - /// The type of the value. - /// The property. - /// The requested culture. - /// The requested segment. - /// A fallback strategy. - /// An optional default value. - /// The fallback value. - /// A value indicating whether a fallback value could be provided. - /// - /// This method is called whenever property.Value{T}(culture, segment, defaultValue) is called, and - /// property.HasValue(culture, segment) is false. - /// It can only fallback at property level (no recurse). - /// At property level, property.GetValue() does *not* implement fallback, and one has to - /// get property.Value() or property.Value{T}() to trigger fallback. - /// - bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, T? defaultValue, out T? value); + /// + /// Tries to get a fallback value for a property. + /// + /// The type of the value. + /// The property. + /// The requested culture. + /// The requested segment. + /// A fallback strategy. + /// An optional default value. + /// The fallback value. + /// A value indicating whether a fallback value could be provided. + /// + /// + /// This method is called whenever property.Value{T}(culture, segment, defaultValue) is called, and + /// property.HasValue(culture, segment) is false. + /// + /// It can only fallback at property level (no recurse). + /// + /// At property level, property.GetValue() does *not* implement fallback, and one has to + /// get property.Value() or property.Value{T}() to trigger fallback. + /// + /// + bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, + T? defaultValue, out T? value); - /// - /// Tries to get a fallback value for a published element property. - /// - /// The published element. - /// The property alias. - /// The requested culture. - /// The requested segment. - /// A fallback strategy. - /// An optional default value. - /// The fallback value. - /// A value indicating whether a fallback value could be provided. - /// - /// This method is called whenever getting the property value for the specified alias, culture and - /// segment, either returned no property at all, or a property with HasValue(culture, segment) being false. - /// It can only fallback at element level (no recurse). - /// - bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, Fallback fallback, object? defaultValue, out object? value); + /// + /// Tries to get a fallback value for a published element property. + /// + /// The published element. + /// The property alias. + /// The requested culture. + /// The requested segment. + /// A fallback strategy. + /// An optional default value. + /// The fallback value. + /// A value indicating whether a fallback value could be provided. + /// + /// + /// This method is called whenever getting the property value for the specified alias, culture and + /// segment, either returned no property at all, or a property with HasValue(culture, segment) being false. + /// + /// It can only fallback at element level (no recurse). + /// + bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, Fallback fallback, + object? defaultValue, out object? value); - /// - /// Tries to get a fallback value for a published element property. - /// - /// The type of the value. - /// The published element. - /// The property alias. - /// The requested culture. - /// The requested segment. - /// A fallback strategy. - /// An optional default value. - /// The fallback value. - /// A value indicating whether a fallback value could be provided. - /// - /// This method is called whenever getting the property value for the specified alias, culture and - /// segment, either returned no property at all, or a property with HasValue(culture, segment) being false. - /// It can only fallback at element level (no recurse). - /// - bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, Fallback fallback, T? defaultValue, out T? value); + /// + /// Tries to get a fallback value for a published element property. + /// + /// The type of the value. + /// The published element. + /// The property alias. + /// The requested culture. + /// The requested segment. + /// A fallback strategy. + /// An optional default value. + /// The fallback value. + /// A value indicating whether a fallback value could be provided. + /// + /// + /// This method is called whenever getting the property value for the specified alias, culture and + /// segment, either returned no property at all, or a property with HasValue(culture, segment) being false. + /// + /// It can only fallback at element level (no recurse). + /// + bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, Fallback fallback, + T? defaultValue, out T? value); - /// - /// Tries to get a fallback value for a published content property. - /// - /// The published element. - /// The property alias. - /// The requested culture. - /// The requested segment. - /// A fallback strategy. - /// An optional default value. - /// The fallback value. - /// The property that does not have a value. - /// A value indicating whether a fallback value could be provided. - /// - /// This method is called whenever getting the property value for the specified alias, culture and - /// segment, either returned no property at all, or a property with HasValue(culture, segment) being false. - /// In an , because walking up the tree is possible, the content itself may not even - /// have a property with the specified alias, but such a property may exist up in the tree. The - /// parameter is used to return a property with no value. That can then be used to invoke a converter and get the - /// converter's interpretation of "no value". - /// - bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, Fallback fallback, object? defaultValue, out object? value, out IPublishedProperty? noValueProperty); + /// + /// Tries to get a fallback value for a published content property. + /// + /// The published element. + /// The property alias. + /// The requested culture. + /// The requested segment. + /// A fallback strategy. + /// An optional default value. + /// The fallback value. + /// The property that does not have a value. + /// A value indicating whether a fallback value could be provided. + /// + /// + /// This method is called whenever getting the property value for the specified alias, culture and + /// segment, either returned no property at all, or a property with HasValue(culture, segment) being false. + /// + /// + /// In an , because walking up the tree is possible, the content itself may not + /// even + /// have a property with the specified alias, but such a property may exist up in the tree. The + /// + /// parameter is used to return a property with no value. That can then be used to invoke a converter and get the + /// converter's interpretation of "no value". + /// + /// + bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, Fallback fallback, + object? defaultValue, out object? value, out IPublishedProperty? noValueProperty); - /// - /// Tries to get a fallback value for a published content property. - /// - /// The type of the value. - /// The published element. - /// The property alias. - /// The requested culture. - /// The requested segment. - /// A fallback strategy. - /// An optional default value. - /// The fallback value. - /// The property that does not have a value. - /// A value indicating whether a fallback value could be provided. - /// - /// This method is called whenever getting the property value for the specified alias, culture and - /// segment, either returned no property at all, or a property with HasValue(culture, segment) being false. - /// In an , because walking up the tree is possible, the content itself may not even - /// have a property with the specified alias, but such a property may exist up in the tree. The - /// parameter is used to return a property with no value. That can then be used to invoke a converter and get the - /// converter's interpretation of "no value". - /// - bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, Fallback fallback, T defaultValue, out T? value, out IPublishedProperty? noValueProperty); - } + /// + /// Tries to get a fallback value for a published content property. + /// + /// The type of the value. + /// The published element. + /// The property alias. + /// The requested culture. + /// The requested segment. + /// A fallback strategy. + /// An optional default value. + /// The fallback value. + /// The property that does not have a value. + /// A value indicating whether a fallback value could be provided. + /// + /// + /// This method is called whenever getting the property value for the specified alias, culture and + /// segment, either returned no property at all, or a property with HasValue(culture, segment) being false. + /// + /// + /// In an , because walking up the tree is possible, the content itself may not + /// even + /// have a property with the specified alias, but such a property may exist up in the tree. The + /// + /// parameter is used to return a property with no value. That can then be used to invoke a converter and get the + /// converter's interpretation of "no value". + /// + /// + bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, Fallback fallback, + T defaultValue, out T? value, out IPublishedProperty? noValueProperty); } diff --git a/src/Umbraco.Core/Models/PublishedContent/IVariationContextAccessor.cs b/src/Umbraco.Core/Models/PublishedContent/IVariationContextAccessor.cs index 83c5f19c9edc..d0fb6ffe342b 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IVariationContextAccessor.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IVariationContextAccessor.cs @@ -1,13 +1,12 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Gives access to the current . +/// +public interface IVariationContextAccessor { /// - /// Gives access to the current . + /// Gets or sets the current . /// - public interface IVariationContextAccessor - { - /// - /// Gets or sets the current . - /// - VariationContext? VariationContext { get; set; } - } + VariationContext? VariationContext { get; set; } } diff --git a/src/Umbraco.Core/Models/PublishedContent/IndexedArrayItem.cs b/src/Umbraco.Core/Models/PublishedContent/IndexedArrayItem.cs index 7c7049c02639..b31588fb7d37 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IndexedArrayItem.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IndexedArrayItem.cs @@ -1,444 +1,387 @@ using System.Net; using Umbraco.Cms.Core.Strings; -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Represents an item in an array that stores its own index and the total count. +/// +/// The type of the content. +public class IndexedArrayItem { /// - /// Represents an item in an array that stores its own index and the total count. + /// Initializes a new instance of the class. /// - /// The type of the content. - public class IndexedArrayItem + /// The content. + /// The index. + public IndexedArrayItem(TContent content, int index) { - /// - /// Initializes a new instance of the class. - /// - /// The content. - /// The index. - public IndexedArrayItem(TContent content, int index) - { - Content = content; - Index = index; - } - - /// - /// Gets the content. - /// - /// - /// The content. - /// - public TContent Content { get; } - - /// - /// Gets the index. - /// - /// - /// The index. - /// - public int Index { get; } - - /// - /// Gets the total count. - /// - /// - /// The total count. - /// - public int TotalCount { get; set; } - - /// - /// Determines whether this item is the first. - /// - /// - /// true if this item is the first; otherwise, false. - /// - public bool IsFirst() - { - return Index == 0; - } - - /// - /// If this item is the first, the HTML encoded will be returned; otherwise, . - /// - /// The value if true. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public IHtmlEncodedString IsFirst(string valueIfTrue) - { - return IsFirst(valueIfTrue, string.Empty); - } - - /// - /// If this item is the first, the HTML encoded will be returned; otherwise, . - /// - /// The value if true. - /// The value if false. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public IHtmlEncodedString IsFirst(string valueIfTrue, string valueIfFalse) - { - return new HtmlEncodedString(WebUtility.HtmlEncode(IsFirst() ? valueIfTrue : valueIfFalse)); - } - - /// - /// Determines whether this item is not the first. - /// - /// - /// true if this item is not the first; otherwise, false. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public bool IsNotFirst() - { - return IsFirst() == false; - } - - - /// - /// If this item is not the first, the HTML encoded will be returned; otherwise, . - /// - /// The value if true. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public IHtmlEncodedString IsNotFirst(string valueIfTrue) - { - return IsNotFirst(valueIfTrue, string.Empty); - } - - /// - /// If this item is not the first, the HTML encoded will be returned; otherwise, . - /// - /// The value if true. - /// The value if false. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public IHtmlEncodedString IsNotFirst(string valueIfTrue, string valueIfFalse) - { - return new HtmlEncodedString(WebUtility.HtmlEncode(IsNotFirst() ? valueIfTrue : valueIfFalse)); - } - - /// - /// Determines whether this item is at the specified . - /// - /// The index. - /// - /// true if this item is at the specified ; otherwise, false. - /// - public bool IsIndex(int index) - { - return Index == index; - } - - /// - /// If this item is at the specified , the HTML encoded will be returned; otherwise, . - /// - /// The index. - /// The value if true. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public IHtmlEncodedString IsIndex(int index, string valueIfTrue) - { - return IsIndex(index, valueIfTrue, string.Empty); - } - - /// - /// If this item is at the specified , the HTML encoded will be returned; otherwise, . - /// - /// The index. - /// The value if true. - /// The value if false. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public IHtmlEncodedString IsIndex(int index, string valueIfTrue, string valueIfFalse) - { - return new HtmlEncodedString(WebUtility.HtmlEncode(IsIndex(index) ? valueIfTrue : valueIfFalse)); - } - - /// - /// Determines whether this item is at an index that can be divided by the specified . - /// - /// The modulus. - /// - /// true if this item is at an index that can be divided by the specified ; otherwise, false. - /// - public bool IsModZero(int modulus) - { - return Index % modulus == 0; - } - - /// - /// If this item is at an index that can be divided by the specified , the HTML encoded will be returned; otherwise, . - /// - /// The modulus. - /// The value if true. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public IHtmlEncodedString IsModZero(int modulus, string valueIfTrue) - { - return IsModZero(modulus, valueIfTrue, string.Empty); - } - - /// - /// If this item is at an index that can be divided by the specified , the HTML encoded will be returned; otherwise, . - /// - /// The modulus. - /// The value if true. - /// The value if false. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public IHtmlEncodedString IsModZero(int modulus, string valueIfTrue, string valueIfFalse) - { - return new HtmlEncodedString(WebUtility.HtmlEncode(IsModZero(modulus) ? valueIfTrue : valueIfFalse)); - } - - /// - /// Determines whether this item is not at an index that can be divided by the specified . - /// - /// The modulus. - /// - /// true if this item is not at an index that can be divided by the specified ; otherwise, false. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public bool IsNotModZero(int modulus) - { - return IsModZero(modulus) == false; - } - - /// - /// If this item is not at an index that can be divided by the specified , the HTML encoded will be returned; otherwise, . - /// - /// The modulus. - /// The value if true. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public IHtmlEncodedString IsNotModZero(int modulus, string valueIfTrue) - { - return IsNotModZero(modulus, valueIfTrue, string.Empty); - } - - /// - /// If this item is not at an index that can be divided by the specified , the HTML encoded will be returned; otherwise, . - /// - /// The modulus. - /// The value if true. - /// The value if false. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public IHtmlEncodedString IsNotModZero(int modulus, string valueIfTrue, string valueIfFalse) - { - return new HtmlEncodedString(WebUtility.HtmlEncode(IsNotModZero(modulus) ? valueIfTrue : valueIfFalse)); - } - - /// - /// Determines whether this item is not at the specified . - /// - /// The index. - /// - /// true if this item is not at the specified ; otherwise, false. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public bool IsNotIndex(int index) - { - return IsIndex(index) == false; - } - - /// - /// If this item is not at the specified , the HTML encoded will be returned; otherwise, . - /// - /// The index. - /// The value if true. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public IHtmlEncodedString IsNotIndex(int index, string valueIfTrue) - { - return IsNotIndex(index, valueIfTrue, string.Empty); - } - - /// - /// If this item is at the specified , the HTML encoded will be returned; otherwise, . - /// - /// The index. - /// The value if true. - /// The value if false. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public IHtmlEncodedString IsNotIndex(int index, string valueIfTrue, string valueIfFalse) - { - return new HtmlEncodedString(WebUtility.HtmlEncode(IsNotIndex(index) ? valueIfTrue : valueIfFalse)); - } - - /// - /// Determines whether this item is the last. - /// - /// - /// true if this item is the last; otherwise, false. - /// - public bool IsLast() - { - return Index == TotalCount - 1; - } - - /// - /// If this item is the last, the HTML encoded will be returned; otherwise, . - /// - /// The value if true. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public IHtmlEncodedString IsLast(string valueIfTrue) - { - return IsLast(valueIfTrue, string.Empty); - } - - /// - /// If this item is the last, the HTML encoded will be returned; otherwise, . - /// - /// The value if true. - /// The value if false. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public IHtmlEncodedString IsLast(string valueIfTrue, string valueIfFalse) - { - return new HtmlEncodedString(WebUtility.HtmlEncode(IsLast() ? valueIfTrue : valueIfFalse)); - } - - /// - /// Determines whether this item is not the last. - /// - /// - /// true if this item is not the last; otherwise, false. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public bool IsNotLast() - { - return IsLast() == false; - } - - /// - /// If this item is not the last, the HTML encoded will be returned; otherwise, . - /// - /// The value if true. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public IHtmlEncodedString IsNotLast(string valueIfTrue) - { - return IsNotLast(valueIfTrue, string.Empty); - } - - /// - /// If this item is not the last, the HTML encoded will be returned; otherwise, . - /// - /// The value if true. - /// The value if false. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public IHtmlEncodedString IsNotLast(string valueIfTrue, string valueIfFalse) - { - return new HtmlEncodedString(WebUtility.HtmlEncode(IsNotLast() ? valueIfTrue : valueIfFalse)); - } - - /// - /// Determines whether this item is at an even index. - /// - /// - /// true if this item is at an even index; otherwise, false. - /// - public bool IsEven() - { - return Index % 2 == 0; - } - - /// - /// If this item is at an even index, the HTML encoded will be returned; otherwise, . - /// - /// The value if true. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public IHtmlEncodedString IsEven(string valueIfTrue) - { - return IsEven(valueIfTrue, string.Empty); - } - - /// - /// If this item is at an even index, the HTML encoded will be returned; otherwise, . - /// - /// The value if true. - /// The value if false. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public IHtmlEncodedString IsEven(string valueIfTrue, string valueIfFalse) - { - return new HtmlEncodedString(WebUtility.HtmlEncode(IsEven() ? valueIfTrue : valueIfFalse)); - } - - /// - /// Determines whether this item is at an odd index. - /// - /// - /// true if this item is at an odd index; otherwise, false. - /// - public bool IsOdd() - { - return Index % 2 == 1; - } - - /// - /// If this item is at an odd index, the HTML encoded will be returned; otherwise, . - /// - /// The value if true. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public IHtmlEncodedString IsOdd(string valueIfTrue) - { - return IsOdd(valueIfTrue, string.Empty); - } - - /// - /// If this item is at an odd index, the HTML encoded will be returned; otherwise, . - /// - /// The value if true. - /// The value if false. - /// - /// The HTML encoded value. - /// - // TODO: This method should be removed or moved to an extension method on HtmlHelper. - public IHtmlEncodedString IsOdd(string valueIfTrue, string valueIfFalse) - { - return new HtmlEncodedString(WebUtility.HtmlEncode(IsOdd() ? valueIfTrue : valueIfFalse)); - } + Content = content; + Index = index; } + + /// + /// Gets the content. + /// + /// + /// The content. + /// + public TContent Content { get; } + + /// + /// Gets the index. + /// + /// + /// The index. + /// + public int Index { get; } + + /// + /// Gets the total count. + /// + /// + /// The total count. + /// + public int TotalCount { get; set; } + + /// + /// Determines whether this item is the first. + /// + /// + /// true if this item is the first; otherwise, false. + /// + public bool IsFirst() => Index == 0; + + /// + /// If this item is the first, the HTML encoded will be returned; otherwise, + /// . + /// + /// The value if true. + /// + /// The HTML encoded value. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public IHtmlEncodedString IsFirst(string valueIfTrue) => IsFirst(valueIfTrue, string.Empty); + + /// + /// If this item is the first, the HTML encoded will be returned; otherwise, + /// . + /// + /// The value if true. + /// The value if false. + /// + /// The HTML encoded value. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public IHtmlEncodedString IsFirst(string valueIfTrue, string valueIfFalse) => + new HtmlEncodedString(WebUtility.HtmlEncode(IsFirst() ? valueIfTrue : valueIfFalse)); + + /// + /// Determines whether this item is not the first. + /// + /// + /// true if this item is not the first; otherwise, false. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public bool IsNotFirst() => IsFirst() == false; + + + /// + /// If this item is not the first, the HTML encoded will be returned; otherwise, + /// . + /// + /// The value if true. + /// + /// The HTML encoded value. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public IHtmlEncodedString IsNotFirst(string valueIfTrue) => IsNotFirst(valueIfTrue, string.Empty); + + /// + /// If this item is not the first, the HTML encoded will be returned; otherwise, + /// . + /// + /// The value if true. + /// The value if false. + /// + /// The HTML encoded value. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public IHtmlEncodedString IsNotFirst(string valueIfTrue, string valueIfFalse) => + new HtmlEncodedString(WebUtility.HtmlEncode(IsNotFirst() ? valueIfTrue : valueIfFalse)); + + /// + /// Determines whether this item is at the specified . + /// + /// The index. + /// + /// true if this item is at the specified ; otherwise, false. + /// + public bool IsIndex(int index) => Index == index; + + /// + /// If this item is at the specified , the HTML encoded will + /// be returned; otherwise, . + /// + /// The index. + /// The value if true. + /// + /// The HTML encoded value. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public IHtmlEncodedString IsIndex(int index, string valueIfTrue) => IsIndex(index, valueIfTrue, string.Empty); + + /// + /// If this item is at the specified , the HTML encoded will + /// be returned; otherwise, . + /// + /// The index. + /// The value if true. + /// The value if false. + /// + /// The HTML encoded value. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public IHtmlEncodedString IsIndex(int index, string valueIfTrue, string valueIfFalse) => + new HtmlEncodedString(WebUtility.HtmlEncode(IsIndex(index) ? valueIfTrue : valueIfFalse)); + + /// + /// Determines whether this item is at an index that can be divided by the specified . + /// + /// The modulus. + /// + /// true if this item is at an index that can be divided by the specified ; + /// otherwise, false. + /// + public bool IsModZero(int modulus) => Index % modulus == 0; + + /// + /// If this item is at an index that can be divided by the specified , the HTML encoded + /// will be returned; otherwise, . + /// + /// The modulus. + /// The value if true. + /// + /// The HTML encoded value. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public IHtmlEncodedString IsModZero(int modulus, string valueIfTrue) => + IsModZero(modulus, valueIfTrue, string.Empty); + + /// + /// If this item is at an index that can be divided by the specified , the HTML encoded + /// will be returned; otherwise, . + /// + /// The modulus. + /// The value if true. + /// The value if false. + /// + /// The HTML encoded value. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public IHtmlEncodedString IsModZero(int modulus, string valueIfTrue, string valueIfFalse) => + new HtmlEncodedString(WebUtility.HtmlEncode(IsModZero(modulus) ? valueIfTrue : valueIfFalse)); + + /// + /// Determines whether this item is not at an index that can be divided by the specified . + /// + /// The modulus. + /// + /// true if this item is not at an index that can be divided by the specified ; + /// otherwise, false. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public bool IsNotModZero(int modulus) => IsModZero(modulus) == false; + + /// + /// If this item is not at an index that can be divided by the specified , the HTML encoded + /// will be returned; otherwise, . + /// + /// The modulus. + /// The value if true. + /// + /// The HTML encoded value. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public IHtmlEncodedString IsNotModZero(int modulus, string valueIfTrue) => + IsNotModZero(modulus, valueIfTrue, string.Empty); + + /// + /// If this item is not at an index that can be divided by the specified , the HTML encoded + /// will be returned; otherwise, . + /// + /// The modulus. + /// The value if true. + /// The value if false. + /// + /// The HTML encoded value. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public IHtmlEncodedString IsNotModZero(int modulus, string valueIfTrue, string valueIfFalse) => + new HtmlEncodedString(WebUtility.HtmlEncode(IsNotModZero(modulus) ? valueIfTrue : valueIfFalse)); + + /// + /// Determines whether this item is not at the specified . + /// + /// The index. + /// + /// true if this item is not at the specified ; otherwise, false. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public bool IsNotIndex(int index) => IsIndex(index) == false; + + /// + /// If this item is not at the specified , the HTML encoded + /// will be returned; otherwise, . + /// + /// The index. + /// The value if true. + /// + /// The HTML encoded value. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public IHtmlEncodedString IsNotIndex(int index, string valueIfTrue) => IsNotIndex(index, valueIfTrue, string.Empty); + + /// + /// If this item is at the specified , the HTML encoded will + /// be returned; otherwise, . + /// + /// The index. + /// The value if true. + /// The value if false. + /// + /// The HTML encoded value. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public IHtmlEncodedString IsNotIndex(int index, string valueIfTrue, string valueIfFalse) => + new HtmlEncodedString(WebUtility.HtmlEncode(IsNotIndex(index) ? valueIfTrue : valueIfFalse)); + + /// + /// Determines whether this item is the last. + /// + /// + /// true if this item is the last; otherwise, false. + /// + public bool IsLast() => Index == TotalCount - 1; + + /// + /// If this item is the last, the HTML encoded will be returned; otherwise, + /// . + /// + /// The value if true. + /// + /// The HTML encoded value. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public IHtmlEncodedString IsLast(string valueIfTrue) => IsLast(valueIfTrue, string.Empty); + + /// + /// If this item is the last, the HTML encoded will be returned; otherwise, + /// . + /// + /// The value if true. + /// The value if false. + /// + /// The HTML encoded value. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public IHtmlEncodedString IsLast(string valueIfTrue, string valueIfFalse) => + new HtmlEncodedString(WebUtility.HtmlEncode(IsLast() ? valueIfTrue : valueIfFalse)); + + /// + /// Determines whether this item is not the last. + /// + /// + /// true if this item is not the last; otherwise, false. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public bool IsNotLast() => IsLast() == false; + + /// + /// If this item is not the last, the HTML encoded will be returned; otherwise, + /// . + /// + /// The value if true. + /// + /// The HTML encoded value. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public IHtmlEncodedString IsNotLast(string valueIfTrue) => IsNotLast(valueIfTrue, string.Empty); + + /// + /// If this item is not the last, the HTML encoded will be returned; otherwise, + /// . + /// + /// The value if true. + /// The value if false. + /// + /// The HTML encoded value. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public IHtmlEncodedString IsNotLast(string valueIfTrue, string valueIfFalse) => + new HtmlEncodedString(WebUtility.HtmlEncode(IsNotLast() ? valueIfTrue : valueIfFalse)); + + /// + /// Determines whether this item is at an even index. + /// + /// + /// true if this item is at an even index; otherwise, false. + /// + public bool IsEven() => Index % 2 == 0; + + /// + /// If this item is at an even index, the HTML encoded will be returned; otherwise, + /// . + /// + /// The value if true. + /// + /// The HTML encoded value. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public IHtmlEncodedString IsEven(string valueIfTrue) => IsEven(valueIfTrue, string.Empty); + + /// + /// If this item is at an even index, the HTML encoded will be returned; otherwise, + /// . + /// + /// The value if true. + /// The value if false. + /// + /// The HTML encoded value. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public IHtmlEncodedString IsEven(string valueIfTrue, string valueIfFalse) => + new HtmlEncodedString(WebUtility.HtmlEncode(IsEven() ? valueIfTrue : valueIfFalse)); + + /// + /// Determines whether this item is at an odd index. + /// + /// + /// true if this item is at an odd index; otherwise, false. + /// + public bool IsOdd() => Index % 2 == 1; + + /// + /// If this item is at an odd index, the HTML encoded will be returned; otherwise, + /// . + /// + /// The value if true. + /// + /// The HTML encoded value. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public IHtmlEncodedString IsOdd(string valueIfTrue) => IsOdd(valueIfTrue, string.Empty); + + /// + /// If this item is at an odd index, the HTML encoded will be returned; otherwise, + /// . + /// + /// The value if true. + /// The value if false. + /// + /// The HTML encoded value. + /// + // TODO: This method should be removed or moved to an extension method on HtmlHelper. + public IHtmlEncodedString IsOdd(string valueIfTrue, string valueIfFalse) => + new HtmlEncodedString(WebUtility.HtmlEncode(IsOdd() ? valueIfTrue : valueIfFalse)); } diff --git a/src/Umbraco.Core/Models/PublishedContent/ModelType.cs b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs index 0de838fa0eff..096dcb245dfe 100644 --- a/src/Umbraco.Core/Models/PublishedContent/ModelType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs @@ -1,412 +1,467 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; +using System.Globalization; using System.Reflection; using Umbraco.Cms.Core.Exceptions; -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// +/// Represents the CLR type of a model. +/// +/// +/// ModelType.For("alias") +/// typeof (IEnumerable{}).MakeGenericType(ModelType.For("alias")) +/// Model.For("alias").MakeArrayType() +/// +public class ModelType : Type { - /// - /// - /// Represents the CLR type of a model. - /// - /// - /// ModelType.For("alias") - /// typeof (IEnumerable{}).MakeGenericType(ModelType.For("alias")) - /// Model.For("alias").MakeArrayType() - /// - public class ModelType : Type + private ModelType(string? contentTypeAlias) { - private ModelType(string? contentTypeAlias) + if (contentTypeAlias == null) { - if (contentTypeAlias == null) throw new ArgumentNullException(nameof(contentTypeAlias)); - if (string.IsNullOrWhiteSpace(contentTypeAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(contentTypeAlias)); - - ContentTypeAlias = contentTypeAlias; - Name = "{" + ContentTypeAlias + "}"; + throw new ArgumentNullException(nameof(contentTypeAlias)); } - /// - /// Gets the content type alias. - /// - public string ContentTypeAlias { get; } - - /// - public override string ToString() - => Name; - - /// - /// Gets the model type for a published element type. - /// - /// The published element type alias. - /// The model type for the published element type. - public static ModelType For(string? alias) - => new ModelType(alias); - - /// - /// Gets the actual CLR type by replacing model types, if any. - /// - /// The type. - /// The model types map. - /// The actual CLR type. - public static Type Map(Type type, Dictionary? modelTypes) - => Map(type, modelTypes, false); - - public static Type Map(Type type, Dictionary? modelTypes, bool dictionaryIsInvariant) + if (string.IsNullOrWhiteSpace(contentTypeAlias)) { - // it may be that senders forgot to send an invariant dictionary (garbage-in) - if (modelTypes is not null && !dictionaryIsInvariant) - modelTypes = new Dictionary(modelTypes, StringComparer.InvariantCultureIgnoreCase); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(contentTypeAlias)); + } - if (type is ModelType modelType) - { - if (modelTypes?.TryGetValue(modelType.ContentTypeAlias, out var actualType) ?? false) - return actualType; - throw new InvalidOperationException($"Don't know how to map ModelType with content type alias \"{modelType.ContentTypeAlias}\"."); - } + ContentTypeAlias = contentTypeAlias; + Name = "{" + ContentTypeAlias + "}"; + } - if (type is ModelTypeArrayType arrayType) - { - if (modelTypes?.TryGetValue(arrayType.ContentTypeAlias, out var actualType) ?? false) - return actualType.MakeArrayType(); - throw new InvalidOperationException($"Don't know how to map ModelType with content type alias \"{arrayType.ContentTypeAlias}\"."); - } + /// + /// Gets the content type alias. + /// + public string ContentTypeAlias { get; } - if (type.IsGenericType == false) - return type; - var def = type.GetGenericTypeDefinition(); - if (def == null) - throw new PanicException($"The type {type} has not generic type definition"); + /// + public override Type UnderlyingSystemType => this; - var args = type.GetGenericArguments().Select(x => Map(x, modelTypes, true)).ToArray(); - return def.MakeGenericType(args); - } + /// + public override Type? BaseType => null; + + /// + public override string Name { get; } + + /// + public override Guid GUID { get; } = Guid.NewGuid(); + + /// + public override Module Module => GetType().Module; // hackish but FullName requires something + + /// + public override Assembly Assembly => GetType().Assembly; // hackish but FullName requires something + + /// + public override string FullName => Name; + + /// + public override string Namespace => string.Empty; + + /// + public override string AssemblyQualifiedName => Name; + + /// + public override string ToString() + => Name; - /// - /// Gets the actual CLR type name by replacing model types, if any. - /// - /// The type. - /// The model types map. - /// The actual CLR type name. - public static string MapToName(Type type, Dictionary map) - => MapToName(type, map, false); + /// + /// Gets the model type for a published element type. + /// + /// The published element type alias. + /// The model type for the published element type. + public static ModelType For(string? alias) + => new(alias); + + /// + /// Gets the actual CLR type by replacing model types, if any. + /// + /// The type. + /// The model types map. + /// The actual CLR type. + public static Type Map(Type type, Dictionary? modelTypes) + => Map(type, modelTypes, false); - private static string MapToName(Type type, Dictionary map, bool dictionaryIsInvariant) + public static Type Map(Type type, Dictionary? modelTypes, bool dictionaryIsInvariant) + { + // it may be that senders forgot to send an invariant dictionary (garbage-in) + if (modelTypes is not null && !dictionaryIsInvariant) { - // it may be that senders forgot to send an invariant dictionary (garbage-in) - if (!dictionaryIsInvariant) - map = new Dictionary(map, StringComparer.InvariantCultureIgnoreCase); + modelTypes = new Dictionary(modelTypes, StringComparer.InvariantCultureIgnoreCase); + } - if (type is ModelType modelType) + if (type is ModelType modelType) + { + if (modelTypes?.TryGetValue(modelType.ContentTypeAlias, out Type actualType) ?? false) { - if (map.TryGetValue(modelType.ContentTypeAlias, out var actualTypeName)) - return actualTypeName; - throw new InvalidOperationException($"Don't know how to map ModelType with content type alias \"{modelType.ContentTypeAlias}\"."); + return actualType; } - if (type is ModelTypeArrayType arrayType) + throw new InvalidOperationException( + $"Don't know how to map ModelType with content type alias \"{modelType.ContentTypeAlias}\"."); + } + + if (type is ModelTypeArrayType arrayType) + { + if (modelTypes?.TryGetValue(arrayType.ContentTypeAlias, out Type actualType) ?? false) { - if (map.TryGetValue(arrayType.ContentTypeAlias, out var actualTypeName)) - return actualTypeName + "[]"; - throw new InvalidOperationException($"Don't know how to map ModelType with content type alias \"{arrayType.ContentTypeAlias}\"."); + return actualType.MakeArrayType(); } - if (type.IsGenericType == false) - return type.FullName!; - var def = type.GetGenericTypeDefinition(); - if (def == null) - throw new PanicException($"The type {type} has not generic type definition"); + throw new InvalidOperationException( + $"Don't know how to map ModelType with content type alias \"{arrayType.ContentTypeAlias}\"."); + } - var args = type.GetGenericArguments().Select(x => MapToName(x, map, true)).ToArray(); - var defFullName = def.FullName?.Substring(0, def.FullName.IndexOf('`')); - return defFullName + "<" + string.Join(", ", args) + ">"; + if (type.IsGenericType == false) + { + return type; } - /// - /// Gets a value indicating whether two instances are equal. - /// - /// The first instance. - /// The second instance. - /// A value indicating whether the two instances are equal. - /// Knows how to compare instances. - public static bool Equals(Type t1, Type t2) + Type def = type.GetGenericTypeDefinition(); + if (def == null) { - if (t1 == t2) - return true; + throw new PanicException($"The type {type} has not generic type definition"); + } - if (t1 is ModelType m1 && t2 is ModelType m2) - return m1.ContentTypeAlias == m2.ContentTypeAlias; + Type[] args = type.GetGenericArguments().Select(x => Map(x, modelTypes, true)).ToArray(); + return def.MakeGenericType(args); + } - if (t1 is ModelTypeArrayType a1 && t2 is ModelTypeArrayType a2) - return a1.ContentTypeAlias == a2.ContentTypeAlias; + /// + /// Gets the actual CLR type name by replacing model types, if any. + /// + /// The type. + /// The model types map. + /// The actual CLR type name. + public static string MapToName(Type type, Dictionary map) + => MapToName(type, map, false); - if (t1.IsGenericType == false || t2.IsGenericType == false) - return false; + private static string MapToName(Type type, Dictionary map, bool dictionaryIsInvariant) + { + // it may be that senders forgot to send an invariant dictionary (garbage-in) + if (!dictionaryIsInvariant) + { + map = new Dictionary(map, StringComparer.InvariantCultureIgnoreCase); + } - var args1 = t1.GetGenericArguments(); - var args2 = t2.GetGenericArguments(); - if (args1.Length != args2.Length) return false; + if (type is ModelType modelType) + { + if (map.TryGetValue(modelType.ContentTypeAlias, out var actualTypeName)) + { + return actualTypeName; + } - for (var i = 0; i < args1.Length; i++) + throw new InvalidOperationException( + $"Don't know how to map ModelType with content type alias \"{modelType.ContentTypeAlias}\"."); + } + + if (type is ModelTypeArrayType arrayType) + { + if (map.TryGetValue(arrayType.ContentTypeAlias, out var actualTypeName)) { - // ReSharper disable once CheckForReferenceEqualityInstead.2 - if (Equals(args1[i], args2[i]) == false) return false; + return actualTypeName + "[]"; } - return true; + throw new InvalidOperationException( + $"Don't know how to map ModelType with content type alias \"{arrayType.ContentTypeAlias}\"."); } - /// - protected override TypeAttributes GetAttributeFlagsImpl() - => TypeAttributes.Class; + if (type.IsGenericType == false) + { + return type.FullName!; + } - /// - public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) - => Array.Empty(); + Type def = type.GetGenericTypeDefinition(); + if (def == null) + { + throw new PanicException($"The type {type} has not generic type definition"); + } - /// - protected override ConstructorInfo? GetConstructorImpl(BindingFlags bindingAttr, Binder? binder, CallingConventions callConvention, Type[] types, ParameterModifier[]? modifiers) - => null; + var args = type.GetGenericArguments().Select(x => MapToName(x, map, true)).ToArray(); + var defFullName = def.FullName?.Substring(0, def.FullName.IndexOf('`')); + return defFullName + "<" + string.Join(", ", args) + ">"; + } - /// - public override Type[] GetInterfaces() - => Array.Empty(); + /// + /// Gets a value indicating whether two instances are equal. + /// + /// The first instance. + /// The second instance. + /// A value indicating whether the two instances are equal. + /// Knows how to compare instances. + public static bool Equals(Type t1, Type t2) + { + if (t1 == t2) + { + return true; + } - /// - public override Type? GetInterface(string name, bool ignoreCase) - => null; + if (t1 is ModelType m1 && t2 is ModelType m2) + { + return m1.ContentTypeAlias == m2.ContentTypeAlias; + } - /// - public override EventInfo[] GetEvents(BindingFlags bindingAttr) - => Array.Empty(); + if (t1 is ModelTypeArrayType a1 && t2 is ModelTypeArrayType a2) + { + return a1.ContentTypeAlias == a2.ContentTypeAlias; + } - /// - public override EventInfo? GetEvent(string name, BindingFlags bindingAttr) - => null; + if (t1.IsGenericType == false || t2.IsGenericType == false) + { + return false; + } - /// - public override Type[] GetNestedTypes(BindingFlags bindingAttr) - => Array.Empty(); + Type[] args1 = t1.GetGenericArguments(); + Type[] args2 = t2.GetGenericArguments(); + if (args1.Length != args2.Length) + { + return false; + } - /// - public override Type? GetNestedType(string name, BindingFlags bindingAttr) - => null; + for (var i = 0; i < args1.Length; i++) + { + // ReSharper disable once CheckForReferenceEqualityInstead.2 + if (Equals(args1[i], args2[i]) == false) + { + return false; + } + } - /// - public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) - => Array.Empty(); + return true; + } - /// - protected override PropertyInfo? GetPropertyImpl(string name, BindingFlags bindingAttr, Binder? binder, Type? returnType, Type[]? types, ParameterModifier[]? modifiers) - => null; + /// + protected override TypeAttributes GetAttributeFlagsImpl() + => TypeAttributes.Class; - /// - public override MethodInfo[] GetMethods(BindingFlags bindingAttr) - => Array.Empty(); + /// + public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) + => Array.Empty(); - /// - protected override MethodInfo? GetMethodImpl(string name, BindingFlags bindingAttr, Binder? binder, CallingConventions callConvention, Type[]? types, ParameterModifier[]? modifiers) - => null; + /// + protected override ConstructorInfo? GetConstructorImpl(BindingFlags bindingAttr, Binder? binder, + CallingConventions callConvention, Type[] types, ParameterModifier[]? modifiers) + => null; - /// - public override FieldInfo[] GetFields(BindingFlags bindingAttr) - => Array.Empty(); + /// + public override Type[] GetInterfaces() + => Array.Empty(); - /// - public override FieldInfo? GetField(string name, BindingFlags bindingAttr) - => null; + /// + public override Type? GetInterface(string name, bool ignoreCase) + => null; - /// - public override MemberInfo[] GetMembers(BindingFlags bindingAttr) - => Array.Empty(); + /// + public override EventInfo[] GetEvents(BindingFlags bindingAttr) + => Array.Empty(); - /// - public override object[] GetCustomAttributes(Type attributeType, bool inherit) - => Array.Empty(); + /// + public override EventInfo? GetEvent(string name, BindingFlags bindingAttr) + => null; - /// - public override object[] GetCustomAttributes(bool inherit) - => Array.Empty(); + /// + public override Type[] GetNestedTypes(BindingFlags bindingAttr) + => Array.Empty(); - /// - public override bool IsDefined(Type attributeType, bool inherit) - => false; + /// + public override Type? GetNestedType(string name, BindingFlags bindingAttr) + => null; - /// - public override Type? GetElementType() - => null; + /// + public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) + => Array.Empty(); - /// - protected override bool HasElementTypeImpl() - => false; + /// + protected override PropertyInfo? GetPropertyImpl(string name, BindingFlags bindingAttr, Binder? binder, + Type? returnType, Type[]? types, ParameterModifier[]? modifiers) + => null; - /// - protected override bool IsArrayImpl() - => false; + /// + public override MethodInfo[] GetMethods(BindingFlags bindingAttr) + => Array.Empty(); - /// - protected override bool IsByRefImpl() - => false; + /// + protected override MethodInfo? GetMethodImpl(string name, BindingFlags bindingAttr, Binder? binder, + CallingConventions callConvention, Type[]? types, ParameterModifier[]? modifiers) + => null; - /// - protected override bool IsPointerImpl() - => false; + /// + public override FieldInfo[] GetFields(BindingFlags bindingAttr) + => Array.Empty(); - /// - protected override bool IsPrimitiveImpl() - => false; + /// + public override FieldInfo? GetField(string name, BindingFlags bindingAttr) + => null; - /// - protected override bool IsCOMObjectImpl() - => false; + /// + public override MemberInfo[] GetMembers(BindingFlags bindingAttr) + => Array.Empty(); - /// - public override object InvokeMember(string name, BindingFlags invokeAttr, Binder? binder, object? target, object?[]? args, ParameterModifier[]? modifiers, CultureInfo? culture, string[]? namedParameters) - => throw new NotSupportedException(); + /// + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + => Array.Empty(); - /// - public override Type UnderlyingSystemType => this; + /// + public override object[] GetCustomAttributes(bool inherit) + => Array.Empty(); + + /// + public override bool IsDefined(Type attributeType, bool inherit) + => false; - /// - public override Type? BaseType => null; + /// + public override Type? GetElementType() + => null; - /// - public override string Name { get; } + /// + protected override bool HasElementTypeImpl() + => false; + + /// + protected override bool IsArrayImpl() + => false; - /// - public override Guid GUID { get; } = Guid.NewGuid(); + /// + protected override bool IsByRefImpl() + => false; - /// - public override Module Module => GetType().Module; // hackish but FullName requires something + /// + protected override bool IsPointerImpl() + => false; - /// - public override Assembly Assembly => GetType().Assembly; // hackish but FullName requires something + /// + protected override bool IsPrimitiveImpl() + => false; - /// - public override string FullName => Name; + /// + protected override bool IsCOMObjectImpl() + => false; - /// - public override string Namespace => string.Empty; + /// + public override object InvokeMember(string name, BindingFlags invokeAttr, Binder? binder, object? target, + object?[]? args, ParameterModifier[]? modifiers, CultureInfo? culture, string[]? namedParameters) + => throw new NotSupportedException(); - /// - public override string AssemblyQualifiedName => Name; + /// + public override Type MakeArrayType() + => new ModelTypeArrayType(this); +} - /// - public override Type MakeArrayType() - => new ModelTypeArrayType(this); - } +internal class ModelTypeArrayType : Type +{ + private readonly Type _elementType; - internal class ModelTypeArrayType : Type + public ModelTypeArrayType(ModelType type) { - private readonly Type _elementType; + _elementType = type; + ContentTypeAlias = type.ContentTypeAlias; + Name = "{" + type.ContentTypeAlias + "}[*]"; + } - public ModelTypeArrayType(ModelType type) - { - _elementType = type; - ContentTypeAlias = type.ContentTypeAlias; - Name = "{" + type.ContentTypeAlias + "}[*]"; - } + public string ContentTypeAlias { get; } - public string ContentTypeAlias { get; } + public override Type UnderlyingSystemType => this; + public override Type? BaseType => null; - public override string ToString() - => Name; + public override string Name { get; } + public override Guid GUID { get; } = Guid.NewGuid(); + public override Module Module => GetType().Module; // hackish but FullName requires something + public override Assembly Assembly => GetType().Assembly; // hackish but FullName requires something + public override string FullName => Name; + public override string Namespace => string.Empty; + public override string AssemblyQualifiedName => Name; - protected override TypeAttributes GetAttributeFlagsImpl() - => TypeAttributes.Class; + public override string ToString() + => Name; - public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) - => Array.Empty(); + protected override TypeAttributes GetAttributeFlagsImpl() + => TypeAttributes.Class; - protected override ConstructorInfo? GetConstructorImpl(BindingFlags bindingAttr, Binder? binder, CallingConventions callConvention, Type[] types, ParameterModifier[]? modifiers) - => null; + public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) + => Array.Empty(); - public override Type[] GetInterfaces() - => Array.Empty(); + protected override ConstructorInfo? GetConstructorImpl(BindingFlags bindingAttr, Binder? binder, + CallingConventions callConvention, Type[] types, ParameterModifier[]? modifiers) + => null; - public override Type? GetInterface(string name, bool ignoreCase) - => null; + public override Type[] GetInterfaces() + => Array.Empty(); - public override EventInfo[] GetEvents(BindingFlags bindingAttr) - => Array.Empty(); + public override Type? GetInterface(string name, bool ignoreCase) + => null; - public override EventInfo? GetEvent(string name, BindingFlags bindingAttr) - => null; + public override EventInfo[] GetEvents(BindingFlags bindingAttr) + => Array.Empty(); - public override Type[] GetNestedTypes(BindingFlags bindingAttr) - => Array.Empty(); + public override EventInfo? GetEvent(string name, BindingFlags bindingAttr) + => null; - public override Type? GetNestedType(string name, BindingFlags bindingAttr) - => null; + public override Type[] GetNestedTypes(BindingFlags bindingAttr) + => Array.Empty(); - public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) - => Array.Empty(); + public override Type? GetNestedType(string name, BindingFlags bindingAttr) + => null; - protected override PropertyInfo? GetPropertyImpl(string name, BindingFlags bindingAttr, Binder? binder, Type? returnType, Type[]? types, ParameterModifier[]? modifiers) - => null; + public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) + => Array.Empty(); - public override MethodInfo[] GetMethods(BindingFlags bindingAttr) - => Array.Empty(); + protected override PropertyInfo? GetPropertyImpl(string name, BindingFlags bindingAttr, Binder? binder, + Type? returnType, Type[]? types, ParameterModifier[]? modifiers) + => null; - protected override MethodInfo? GetMethodImpl(string name, BindingFlags bindingAttr, Binder? binder, CallingConventions callConvention, Type[]? types, ParameterModifier[]? modifiers) - => null; + public override MethodInfo[] GetMethods(BindingFlags bindingAttr) + => Array.Empty(); - public override FieldInfo[] GetFields(BindingFlags bindingAttr) - => Array.Empty(); + protected override MethodInfo? GetMethodImpl(string name, BindingFlags bindingAttr, Binder? binder, + CallingConventions callConvention, Type[]? types, ParameterModifier[]? modifiers) + => null; - public override FieldInfo? GetField(string name, BindingFlags bindingAttr) - => null; + public override FieldInfo[] GetFields(BindingFlags bindingAttr) + => Array.Empty(); - public override MemberInfo[] GetMembers(BindingFlags bindingAttr) - => Array.Empty(); + public override FieldInfo? GetField(string name, BindingFlags bindingAttr) + => null; - public override object[] GetCustomAttributes(Type attributeType, bool inherit) - => Array.Empty(); + public override MemberInfo[] GetMembers(BindingFlags bindingAttr) + => Array.Empty(); - public override object[] GetCustomAttributes(bool inherit) - => Array.Empty(); + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + => Array.Empty(); - public override bool IsDefined(Type attributeType, bool inherit) - => false; + public override object[] GetCustomAttributes(bool inherit) + => Array.Empty(); - public override Type GetElementType() - => _elementType; + public override bool IsDefined(Type attributeType, bool inherit) + => false; - protected override bool HasElementTypeImpl() - => true; + public override Type GetElementType() + => _elementType; - protected override bool IsArrayImpl() - => true; + protected override bool HasElementTypeImpl() + => true; - protected override bool IsByRefImpl() - => false; + protected override bool IsArrayImpl() + => true; - protected override bool IsPointerImpl() - => false; + protected override bool IsByRefImpl() + => false; - protected override bool IsPrimitiveImpl() - => false; + protected override bool IsPointerImpl() + => false; - protected override bool IsCOMObjectImpl() - => false; - public override object InvokeMember(string name, BindingFlags invokeAttr, Binder? binder, object? target, object?[]? args, ParameterModifier[]? modifiers, CultureInfo? culture, string[]? namedParameters) - { - throw new NotSupportedException(); - } + protected override bool IsPrimitiveImpl() + => false; - public override Type UnderlyingSystemType => this; - public override Type? BaseType => null; + protected override bool IsCOMObjectImpl() + => false; - public override string Name { get; } - public override Guid GUID { get; } = Guid.NewGuid(); - public override Module Module =>GetType().Module; // hackish but FullName requires something - public override Assembly Assembly => GetType().Assembly; // hackish but FullName requires something - public override string FullName => Name; - public override string Namespace => string.Empty; - public override string AssemblyQualifiedName => Name; + public override object InvokeMember(string name, BindingFlags invokeAttr, Binder? binder, object? target, + object?[]? args, ParameterModifier[]? modifiers, CultureInfo? culture, string[]? namedParameters) => + throw new NotSupportedException(); - public override int GetArrayRank() - => 1; - } + public override int GetArrayRank() + => 1; } diff --git a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedModelFactory.cs index 93b6948edc69..05a7b305b0f9 100644 --- a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedModelFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedModelFactory.cs @@ -1,21 +1,20 @@ -using System.Collections; +using System.Collections; -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Represents a no-operation factory. +public class NoopPublishedModelFactory : IPublishedModelFactory { /// - /// Represents a no-operation factory. - public class NoopPublishedModelFactory : IPublishedModelFactory - { - /// - public IPublishedElement CreateModel(IPublishedElement element) => element; + public IPublishedElement CreateModel(IPublishedElement element) => element; - /// - public IList CreateModelList(string? alias) => new List(); + /// + public IList CreateModelList(string? alias) => new List(); - /// - public Type GetModelType(string? alias) => typeof(IPublishedElement); + /// + public Type GetModelType(string? alias) => typeof(IPublishedElement); - /// - public Type MapModelType(Type type) => typeof(IPublishedElement); - } + /// + public Type MapModelType(Type type) => typeof(IPublishedElement); } diff --git a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs index a08a20658dac..20e78a2bfcee 100644 --- a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs @@ -1,55 +1,60 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Provides a noop implementation for . +/// +/// +/// This is for tests etc - does not implement fallback at all. +/// +public class NoopPublishedValueFallback : IPublishedValueFallback { - /// - /// Provides a noop implementation for . - /// - /// - /// This is for tests etc - does not implement fallback at all. - /// - public class NoopPublishedValueFallback : IPublishedValueFallback + /// + public bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, + object? defaultValue, out object? value) { - /// - public bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, object? defaultValue, out object? value) - { - value = default; - return false; - } + value = default; + return false; + } - /// - public bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, T? defaultValue, out T? value) - { - value = default; - return false; - } + /// + public bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, + T? defaultValue, out T? value) + { + value = default; + return false; + } - /// - public bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, Fallback fallback, object? defaultValue, out object? value) - { - value = default; - return false; - } + /// + public bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, + Fallback fallback, object? defaultValue, out object? value) + { + value = default; + return false; + } - /// - public bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, Fallback fallback, T? defaultValue, out T? value) - { - value = default; - return false; - } + /// + public bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, + Fallback fallback, T? defaultValue, out T? value) + { + value = default; + return false; + } - /// - public bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, Fallback fallback, object? defaultValue, out object? value, out IPublishedProperty? noValueProperty) - { - value = default; - noValueProperty = default; - return false; - } + /// + public bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, + Fallback fallback, object? defaultValue, out object? value, out IPublishedProperty? noValueProperty) + { + value = default; + noValueProperty = default; + return false; + } - /// - public bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, Fallback fallback, T defaultValue, out T? value, out IPublishedProperty? noValueProperty) - { - value = default; - noValueProperty = default; - return false; - } + /// + public bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, + Fallback fallback, T defaultValue, out T? value, out IPublishedProperty? noValueProperty) + { + value = default; + noValueProperty = default; + return false; } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentBase.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentBase.cs index 60d3cd4a02cd..01dabef96214 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentBase.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentBase.cs @@ -1,108 +1,105 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; +using System.Diagnostics; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Provide an abstract base class for IPublishedContent implementations. +/// +/// +/// This base class does which (a) consistently resolves and caches the URL, (b) provides an implementation +/// for this[alias], and (c) provides basic content set management. +/// +[DebuggerDisplay("Content Id: {Id}")] +public abstract class PublishedContentBase : IPublishedContent { - /// - /// Provide an abstract base class for IPublishedContent implementations. - /// - /// This base class does which (a) consistently resolves and caches the URL, (b) provides an implementation - /// for this[alias], and (c) provides basic content set management. - [DebuggerDisplay("Content Id: {Id}")] - public abstract class PublishedContentBase : IPublishedContent - { - private readonly IVariationContextAccessor? _variationContextAccessor; + private readonly IVariationContextAccessor? _variationContextAccessor; - protected PublishedContentBase(IVariationContextAccessor? variationContextAccessor) - { - _variationContextAccessor = variationContextAccessor; - } + protected PublishedContentBase(IVariationContextAccessor? variationContextAccessor) => + _variationContextAccessor = variationContextAccessor; - #region ContentType + #region ContentType - public abstract IPublishedContentType ContentType { get; } + public abstract IPublishedContentType ContentType { get; } - #endregion + #endregion - #region PublishedElement + #region PublishedElement - /// - public abstract Guid Key { get; } + /// + public abstract Guid Key { get; } - #endregion + #endregion - #region PublishedContent + #region PublishedContent - /// - public abstract int Id { get; } + /// + public abstract int Id { get; } - /// - public virtual string? Name => this.Name(_variationContextAccessor); + /// + public virtual string? Name => this.Name(_variationContextAccessor); - /// - public virtual string? UrlSegment => this.UrlSegment(_variationContextAccessor); + /// + public virtual string? UrlSegment => this.UrlSegment(_variationContextAccessor); - /// - public abstract int SortOrder { get; } + /// + public abstract int SortOrder { get; } - /// - public abstract int Level { get; } + /// + public abstract int Level { get; } - /// - public abstract string Path { get; } + /// + public abstract string Path { get; } - /// - public abstract int? TemplateId { get; } + /// + public abstract int? TemplateId { get; } - /// - public abstract int CreatorId { get; } + /// + public abstract int CreatorId { get; } - /// - public abstract DateTime CreateDate { get; } + /// + public abstract DateTime CreateDate { get; } - /// - public abstract int WriterId { get; } + /// + public abstract int WriterId { get; } - /// - public abstract DateTime UpdateDate { get; } + /// + public abstract DateTime UpdateDate { get; } - /// - public abstract IReadOnlyDictionary Cultures { get; } + /// + public abstract IReadOnlyDictionary Cultures { get; } - /// - public abstract PublishedItemType ItemType { get; } + /// + public abstract PublishedItemType ItemType { get; } - /// - public abstract bool IsDraft(string? culture = null); + /// + public abstract bool IsDraft(string? culture = null); - /// - public abstract bool IsPublished(string? culture = null); + /// + public abstract bool IsPublished(string? culture = null); - #endregion + #endregion - #region Tree + #region Tree - /// - public abstract IPublishedContent? Parent { get; } + /// + public abstract IPublishedContent? Parent { get; } - /// - public virtual IEnumerable? Children => this.Children(_variationContextAccessor); + /// + public virtual IEnumerable? Children => this.Children(_variationContextAccessor); - /// - public abstract IEnumerable ChildrenForAllCultures { get; } + /// + public abstract IEnumerable ChildrenForAllCultures { get; } - #endregion + #endregion - #region Properties + #region Properties - /// - public abstract IEnumerable Properties { get; } + /// + public abstract IEnumerable Properties { get; } - /// - public abstract IPublishedProperty? GetProperty(string alias); + /// + public abstract IPublishedProperty? GetProperty(string alias); - #endregion - } + #endregion } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs index f1d348d2ffe1..66b1d70370c4 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs @@ -1,35 +1,45 @@ -using System; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Provides strongly typed published content models services. +/// +public static class PublishedContentExtensionsForModels { /// - /// Provides strongly typed published content models services. + /// Creates a strongly typed published content model for an internal published content. /// - public static class PublishedContentExtensionsForModels + /// The internal published content. + /// The strongly typed published content model. + public static IPublishedContent? CreateModel(this IPublishedContent content, + IPublishedModelFactory? publishedModelFactory) { - /// - /// Creates a strongly typed published content model for an internal published content. - /// - /// The internal published content. - /// The strongly typed published content model. - public static IPublishedContent? CreateModel(this IPublishedContent content, IPublishedModelFactory? publishedModelFactory) + if (publishedModelFactory == null) { - if (publishedModelFactory == null) throw new ArgumentNullException(nameof(publishedModelFactory)); - if (content == null) - return null; + throw new ArgumentNullException(nameof(publishedModelFactory)); + } - // get model - // if factory returns nothing, throw - var model = publishedModelFactory.CreateModel(content); - if (model == null) - throw new InvalidOperationException("Factory returned null."); + if (content == null) + { + return null; + } - // if factory returns a different type, throw - if (!(model is IPublishedContent publishedContent)) - throw new InvalidOperationException($"Factory returned model of type {model.GetType().FullName} which does not implement IPublishedContent."); + // get model + // if factory returns nothing, throw + IPublishedElement model = publishedModelFactory.CreateModel(content); + if (model == null) + { + throw new InvalidOperationException("Factory returned null."); + } - return publishedContent; + // if factory returns a different type, throw + if (!(model is IPublishedContent publishedContent)) + { + throw new InvalidOperationException( + $"Factory returned model of type {model.GetType().FullName} which does not implement IPublishedContent."); } + + return publishedContent; } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModel.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentModel.cs index 249c2cb465a0..feb9383092f1 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModel.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentModel.cs @@ -1,21 +1,21 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Represents a strongly-typed published content. +/// +/// +/// Every strongly-typed published content class should inherit from PublishedContentModel +/// (or inherit from a class that inherits from... etc.) so they are picked by the factory. +/// +public abstract class PublishedContentModel : PublishedContentWrapped { /// - /// Represents a strongly-typed published content. + /// Initializes a new instance of the class with + /// an original instance. /// - /// Every strongly-typed published content class should inherit from PublishedContentModel - /// (or inherit from a class that inherits from... etc.) so they are picked by the factory. - public abstract class PublishedContentModel : PublishedContentWrapped + /// The original content. + protected PublishedContentModel(IPublishedContent content, IPublishedValueFallback publishedValueFallback) + : base(content, publishedValueFallback) { - /// - /// Initializes a new instance of the class with - /// an original instance. - /// - /// The original content. - protected PublishedContentModel(IPublishedContent content, IPublishedValueFallback publishedValueFallback) - : base(content, publishedValueFallback) - { } - - } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index 592c2eff5e55..d2fcb9e9f4ee 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -1,204 +1,232 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Represents an type. +/// +/// +/// Instances of the class are immutable, ie +/// if the content type changes, then a new class needs to be created. +/// +[DebuggerDisplay("{Alias}")] +public class PublishedContentType : IPublishedContentType { + // TODO: this list somehow also exists in constants, see memberTypeRepository => remove duplicate! + private static readonly Dictionary BuiltinMemberProperties = new() + { + {nameof(IMember.Email), Constants.DataTypes.Textbox}, + {nameof(IMember.Username), Constants.DataTypes.Textbox}, + {nameof(IMember.Comments), Constants.DataTypes.Textbox}, + {nameof(IMember.IsApproved), Constants.DataTypes.Boolean}, + {nameof(IMember.IsLockedOut), Constants.DataTypes.Boolean}, + {nameof(IMember.LastLockoutDate), Constants.DataTypes.LabelDateTime}, + {nameof(IMember.CreateDate), Constants.DataTypes.LabelDateTime}, + {nameof(IMember.LastLoginDate), Constants.DataTypes.LabelDateTime}, + {nameof(IMember.LastPasswordChangeDate), Constants.DataTypes.LabelDateTime} + }; + + // fast alias-to-index xref containing both the raw alias and its lowercase version + private readonly Dictionary _indexes = new(); + private readonly IPublishedPropertyType[] _propertyTypes = null!; + /// - /// Represents an type. + /// Initializes a new instance of the class with a content type. /// - /// Instances of the class are immutable, ie - /// if the content type changes, then a new class needs to be created. - [DebuggerDisplay("{Alias}")] - public class PublishedContentType : IPublishedContentType + public PublishedContentType(IContentTypeComposition contentType, IPublishedContentTypeFactory factory) + : this(contentType.Key, contentType.Id, contentType.Alias, contentType.GetItemType(), + contentType.CompositionAliases(), contentType.Variations, contentType.IsElement) { - private readonly IPublishedPropertyType[] _propertyTypes = null!; + var propertyTypes = contentType.CompositionPropertyTypes + .Select(x => factory.CreatePropertyType(this, x)) + .ToList(); - // fast alias-to-index xref containing both the raw alias and its lowercase version - private readonly Dictionary _indexes = new Dictionary(); - - /// - /// Initializes a new instance of the class with a content type. - /// - public PublishedContentType(IContentTypeComposition contentType, IPublishedContentTypeFactory factory) - : this(contentType.Key, contentType.Id, contentType.Alias, contentType.GetItemType(), contentType.CompositionAliases(), contentType.Variations, contentType.IsElement) + if (ItemType == PublishedItemType.Member) { - var propertyTypes = contentType.CompositionPropertyTypes - .Select(x => factory.CreatePropertyType(this, x)) - .ToList(); + EnsureMemberProperties(propertyTypes, factory); + } - if (ItemType == PublishedItemType.Member) - EnsureMemberProperties(propertyTypes, factory); + _propertyTypes = propertyTypes.ToArray(); - _propertyTypes = propertyTypes.ToArray(); + InitializeIndexes(); + } - InitializeIndexes(); + /// + /// This constructor is for tests and is not intended to be used directly from application code. + /// + /// + /// Values are assumed to be consistent and are not checked. + /// + public PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, + IEnumerable compositionAliases, IEnumerable propertyTypes, + ContentVariation variations, bool isElement = false) + : this(key, id, alias, itemType, compositionAliases, variations, isElement) + { + PublishedPropertyType[] propertyTypesA = propertyTypes.ToArray(); + foreach (PublishedPropertyType propertyType in propertyTypesA) + { + propertyType.ContentType = this; } - /// - /// This constructor is for tests and is not intended to be used directly from application code. - /// - /// - /// Values are assumed to be consistent and are not checked. - /// - public PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations, bool isElement = false) - : this(key, id, alias, itemType, compositionAliases, variations, isElement) - { - var propertyTypesA = propertyTypes.ToArray(); - foreach (var propertyType in propertyTypesA) - propertyType.ContentType = this; - _propertyTypes = propertyTypesA; + _propertyTypes = propertyTypesA; - InitializeIndexes(); - } + InitializeIndexes(); + } - [Obsolete("Use the overload specifying a key instead")] - public PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations, bool isElement = false) - : this (Guid.Empty, id, alias, itemType, compositionAliases, variations, isElement) + [Obsolete("Use the overload specifying a key instead")] + public PublishedContentType(int id, string alias, PublishedItemType itemType, + IEnumerable compositionAliases, IEnumerable propertyTypes, + ContentVariation variations, bool isElement = false) + : this(Guid.Empty, id, alias, itemType, compositionAliases, variations, isElement) + { + PublishedPropertyType[] propertyTypesA = propertyTypes.ToArray(); + foreach (PublishedPropertyType propertyType in propertyTypesA) { - var propertyTypesA = propertyTypes.ToArray(); - foreach (var propertyType in propertyTypesA) - propertyType.ContentType = this; - _propertyTypes = propertyTypesA; - - InitializeIndexes(); + propertyType.ContentType = this; } - /// - /// This constructor is for tests and is not intended to be used directly from application code. - /// - /// - /// Values are assumed to be consistent and are not checked. - /// - public PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, Func> propertyTypes, ContentVariation variations, bool isElement = false) - : this(key, id, alias, itemType, compositionAliases, variations, isElement) - { - _propertyTypes = propertyTypes(this).ToArray(); + _propertyTypes = propertyTypesA; - InitializeIndexes(); - } + InitializeIndexes(); + } - [Obsolete("Use the overload specifying a key instead")] - public PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, Func> propertyTypes, ContentVariation variations, bool isElement = false) - : this(Guid.Empty, id, alias, itemType, compositionAliases, variations, isElement) - { - _propertyTypes = propertyTypes(this).ToArray(); + /// + /// This constructor is for tests and is not intended to be used directly from application code. + /// + /// + /// Values are assumed to be consistent and are not checked. + /// + public PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, + IEnumerable compositionAliases, + Func> propertyTypes, ContentVariation variations, + bool isElement = false) + : this(key, id, alias, itemType, compositionAliases, variations, isElement) + { + _propertyTypes = propertyTypes(this).ToArray(); - InitializeIndexes(); - } + InitializeIndexes(); + } - private PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, ContentVariation variations, bool isElement) - { - Key = key; - Id = id; - Alias = alias; - ItemType = itemType; - CompositionAliases = new HashSet(compositionAliases, StringComparer.InvariantCultureIgnoreCase); - Variations = variations; - IsElement = isElement; - } + [Obsolete("Use the overload specifying a key instead")] + public PublishedContentType(int id, string alias, PublishedItemType itemType, + IEnumerable compositionAliases, + Func> propertyTypes, ContentVariation variations, + bool isElement = false) + : this(Guid.Empty, id, alias, itemType, compositionAliases, variations, isElement) + { + _propertyTypes = propertyTypes(this).ToArray(); + + InitializeIndexes(); + } + + private PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, + IEnumerable compositionAliases, ContentVariation variations, bool isElement) + { + Key = key; + Id = id; + Alias = alias; + ItemType = itemType; + CompositionAliases = new HashSet(compositionAliases, StringComparer.InvariantCultureIgnoreCase); + Variations = variations; + IsElement = isElement; + } - private void InitializeIndexes() + private void InitializeIndexes() + { + if (_propertyTypes is not null) { - if (_propertyTypes is not null) + for (var i = 0; i < _propertyTypes.Length; i++) { - for (var i = 0; i < _propertyTypes.Length; i++) + IPublishedPropertyType propertyType = _propertyTypes[i]; + if (propertyType.Alias is not null) { - IPublishedPropertyType propertyType = _propertyTypes[i]; - if (propertyType.Alias is not null) - { - _indexes[propertyType.Alias] = i; - _indexes[propertyType.Alias.ToLowerInvariant()] = i; - } + _indexes[propertyType.Alias] = i; + _indexes[propertyType.Alias.ToLowerInvariant()] = i; } } } + } - // Members have properties such as IMember LastLoginDate which are plain C# properties and not content - // properties; they are exposed as pseudo content properties, as long as a content property with the - // same alias does not exist already. - private void EnsureMemberProperties(List propertyTypes, IPublishedContentTypeFactory factory) - { - var aliases = new HashSet(propertyTypes.Select(x => x.Alias), StringComparer.OrdinalIgnoreCase); + // Members have properties such as IMember LastLoginDate which are plain C# properties and not content + // properties; they are exposed as pseudo content properties, as long as a content property with the + // same alias does not exist already. + private void EnsureMemberProperties(List propertyTypes, + IPublishedContentTypeFactory factory) + { + var aliases = new HashSet(propertyTypes.Select(x => x.Alias), StringComparer.OrdinalIgnoreCase); - foreach (var (alias, dataTypeId) in BuiltinMemberProperties) + foreach (var (alias, dataTypeId) in BuiltinMemberProperties) + { + if (aliases.Contains(alias)) { - if (aliases.Contains(alias)) continue; - propertyTypes.Add(factory.CreateCorePropertyType(this, alias, dataTypeId, ContentVariation.Nothing)); + continue; } - } - // TODO: this list somehow also exists in constants, see memberTypeRepository => remove duplicate! - private static readonly Dictionary BuiltinMemberProperties = new Dictionary - { - { nameof(IMember.Email), Constants.DataTypes.Textbox }, - { nameof(IMember.Username), Constants.DataTypes.Textbox }, - { nameof(IMember.Comments), Constants.DataTypes.Textbox }, - { nameof(IMember.IsApproved), Constants.DataTypes.Boolean }, - { nameof(IMember.IsLockedOut), Constants.DataTypes.Boolean }, - { nameof(IMember.LastLockoutDate), Constants.DataTypes.LabelDateTime }, - { nameof(IMember.CreateDate), Constants.DataTypes.LabelDateTime }, - { nameof(IMember.LastLoginDate), Constants.DataTypes.LabelDateTime }, - { nameof(IMember.LastPasswordChangeDate), Constants.DataTypes.LabelDateTime }, - }; - - #region Content type + propertyTypes.Add(factory.CreateCorePropertyType(this, alias, dataTypeId, ContentVariation.Nothing)); + } + } - /// - public Guid Key { get; } + #region Content type - /// - public int Id { get; } + /// + public Guid Key { get; } - /// - public string Alias { get; } + /// + public int Id { get; } - /// - public PublishedItemType ItemType { get; } + /// + public string Alias { get; } - /// - public HashSet CompositionAliases { get; } + /// + public PublishedItemType ItemType { get; } - /// - public ContentVariation Variations { get; } + /// + public HashSet CompositionAliases { get; } - #endregion + /// + public ContentVariation Variations { get; } - #region Properties + #endregion - /// - public IEnumerable PropertyTypes => _propertyTypes; + #region Properties - /// - public int GetPropertyIndex(string alias) - { - if (_indexes.TryGetValue(alias, out var index)) return index; // fastest - if (_indexes.TryGetValue(alias.ToLowerInvariant(), out index)) return index; // slower - return -1; - } + /// + public IEnumerable PropertyTypes => _propertyTypes; - // virtual for unit tests - // TODO: explain why - /// - public virtual IPublishedPropertyType? GetPropertyType(string alias) + /// + public int GetPropertyIndex(string alias) + { + if (_indexes.TryGetValue(alias, out var index)) { - var index = GetPropertyIndex(alias); - return GetPropertyType(index); + return index; // fastest } - // virtual for unit tests - // TODO: explain why - /// - public virtual IPublishedPropertyType? GetPropertyType(int index) + if (_indexes.TryGetValue(alias.ToLowerInvariant(), out index)) { - return index >= 0 && _propertyTypes is not null && index < _propertyTypes.Length ? _propertyTypes[index] : null; + return index; // slower } - /// - public bool IsElement { get; } + return -1; + } - #endregion + // virtual for unit tests + // TODO: explain why + /// + public virtual IPublishedPropertyType? GetPropertyType(string alias) + { + var index = GetPropertyIndex(alias); + return GetPropertyType(index); } + + // virtual for unit tests + // TODO: explain why + /// + public virtual IPublishedPropertyType? GetPropertyType(int index) => + index >= 0 && _propertyTypes is not null && index < _propertyTypes.Length ? _propertyTypes[index] : null; + + /// + public bool IsElement { get; } + + #endregion } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeConverter.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeConverter.cs index 23adf358ca0f..6d1601ee29b3 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeConverter.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeConverter.cs @@ -1,28 +1,26 @@ -using System; -using System.ComponentModel; +using System.ComponentModel; using System.Globalization; -using System.Linq; -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +internal class PublishedContentTypeConverter : TypeConverter { - internal class PublishedContentTypeConverter : TypeConverter - { - private static readonly Type[] ConvertingTypes = { typeof(int) }; + private static readonly Type[] ConvertingTypes = {typeof(int)}; - public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType) - { - return ConvertingTypes.Any(x => x.IsAssignableFrom(destinationType)) - || (destinationType is not null && CanConvertFrom(context, destinationType)); - } + public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType) => + ConvertingTypes.Any(x => x.IsAssignableFrom(destinationType)) + || (destinationType is not null && CanConvertFrom(context, destinationType)); - public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) + public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, + Type destinationType) + { + if (!(value is IPublishedContent publishedContent)) { - if (!(value is IPublishedContent publishedContent)) - return null; - - return typeof(int).IsAssignableFrom(destinationType) - ? publishedContent.Id - : base.ConvertTo(context, culture, value, destinationType); + return null; } + + return typeof(int).IsAssignableFrom(destinationType) + ? publishedContent.Id + : base.ConvertTo(context, culture, value, destinationType); } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs index 5a43295981e5..581097de3e57 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs @@ -1,124 +1,124 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Provides a default implementation for . +/// +public class PublishedContentTypeFactory : IPublishedContentTypeFactory { - /// - /// Provides a default implementation for . - /// - public class PublishedContentTypeFactory : IPublishedContentTypeFactory + private readonly IDataTypeService _dataTypeService; + private readonly PropertyValueConverterCollection _propertyValueConverters; + private readonly object _publishedDataTypesLocker = new(); + private readonly IPublishedModelFactory _publishedModelFactory; + private Dictionary? _publishedDataTypes; + + public PublishedContentTypeFactory(IPublishedModelFactory publishedModelFactory, + PropertyValueConverterCollection propertyValueConverters, IDataTypeService dataTypeService) { - private readonly IPublishedModelFactory _publishedModelFactory; - private readonly PropertyValueConverterCollection _propertyValueConverters; - private readonly IDataTypeService _dataTypeService; - private readonly object _publishedDataTypesLocker = new object(); - private Dictionary? _publishedDataTypes; + _publishedModelFactory = publishedModelFactory; + _propertyValueConverters = propertyValueConverters; + _dataTypeService = dataTypeService; + } - public PublishedContentTypeFactory(IPublishedModelFactory publishedModelFactory, PropertyValueConverterCollection propertyValueConverters, IDataTypeService dataTypeService) - { - _publishedModelFactory = publishedModelFactory; - _propertyValueConverters = propertyValueConverters; - _dataTypeService = dataTypeService; - } + /// + public IPublishedContentType CreateContentType(IContentTypeComposition contentType) => + new PublishedContentType(contentType, this); - /// - public IPublishedContentType CreateContentType(IContentTypeComposition contentType) - { - return new PublishedContentType(contentType, this); - } + /// + public IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, IPropertyType propertyType) => + new PublishedPropertyType(contentType, propertyType, _propertyValueConverters, _publishedModelFactory, this); - /// - /// This method is for tests and is not intended to be used directly from application code. - /// - /// Values are assumed to be consisted and are not checked. - internal IPublishedContentType CreateContentType(Guid key, int id, string alias, Func> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) - { - return new PublishedContentType(key, id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, variations, isElement); - } + /// + public IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, string propertyTypeAlias, + int dataTypeId, ContentVariation variations = ContentVariation.Nothing) => new PublishedPropertyType( + contentType, propertyTypeAlias, dataTypeId, true, variations, _propertyValueConverters, _publishedModelFactory, + this); - /// - /// This method is for tests and is not intended to be used directly from application code. - /// - /// Values are assumed to be consisted and are not checked. - internal IPublishedContentType CreateContentType(Guid key, int id, string alias, IEnumerable compositionAliases, Func> propertyTypes, ContentVariation variations = ContentVariation.Nothing, bool isElement = false) - { - return new PublishedContentType(key, id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, variations, isElement); - } + /// + public IPublishedPropertyType CreateCorePropertyType(IPublishedContentType contentType, string propertyTypeAlias, + int dataTypeId, ContentVariation variations = ContentVariation.Nothing) => new PublishedPropertyType( + contentType, propertyTypeAlias, dataTypeId, false, variations, _propertyValueConverters, _publishedModelFactory, + this); - /// - public IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, IPropertyType propertyType) + /// + public PublishedDataType GetDataType(int id) + { + Dictionary? publishedDataTypes; + lock (_publishedDataTypesLocker) { - return new PublishedPropertyType(contentType, propertyType, _propertyValueConverters, _publishedModelFactory, this); - } + if (_publishedDataTypes == null) + { + IEnumerable dataTypes = _dataTypeService.GetAll(); + _publishedDataTypes = dataTypes.ToDictionary(x => x.Id, CreatePublishedDataType); + } - /// - public IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations = ContentVariation.Nothing) - { - return new PublishedPropertyType(contentType, propertyTypeAlias, dataTypeId, true, variations, _propertyValueConverters, _publishedModelFactory, this); + publishedDataTypes = _publishedDataTypes; } - /// - public IPublishedPropertyType CreateCorePropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, ContentVariation variations = ContentVariation.Nothing) + if (publishedDataTypes is null || !publishedDataTypes.TryGetValue(id, out PublishedDataType dataType)) { - return new PublishedPropertyType(contentType, propertyTypeAlias, dataTypeId, false, variations, _propertyValueConverters, _publishedModelFactory, this); + throw new ArgumentException($"Could not find a datatype with identifier {id}.", nameof(id)); } - /// - /// This method is for tests and is not intended to be used directly from application code. - /// - /// Values are assumed to be consisted and are not checked. - internal IPublishedPropertyType CreatePropertyType(string propertyTypeAlias, int dataTypeId, bool umbraco = false, ContentVariation variations = ContentVariation.Nothing) - { - return new PublishedPropertyType(propertyTypeAlias, dataTypeId, umbraco, variations, _propertyValueConverters, _publishedModelFactory, this); - } + return dataType; + } - /// - public PublishedDataType GetDataType(int id) + /// + public void NotifyDataTypeChanges(int[] ids) + { + lock (_publishedDataTypesLocker) { - Dictionary? publishedDataTypes; - lock (_publishedDataTypesLocker) + if (_publishedDataTypes == null) { - if (_publishedDataTypes == null) - { - var dataTypes = _dataTypeService.GetAll(); - _publishedDataTypes = dataTypes.ToDictionary(x => x.Id, CreatePublishedDataType); - } - - publishedDataTypes = _publishedDataTypes; + IEnumerable dataTypes = _dataTypeService.GetAll(); + _publishedDataTypes = dataTypes.ToDictionary(x => x.Id, CreatePublishedDataType); } - - if (publishedDataTypes is null || !publishedDataTypes.TryGetValue(id, out var dataType)) - throw new ArgumentException($"Could not find a datatype with identifier {id}.", nameof(id)); - - return dataType; - } - - /// - public void NotifyDataTypeChanges(int[] ids) - { - lock (_publishedDataTypesLocker) + else { - if (_publishedDataTypes == null) + foreach (var id in ids) { - var dataTypes = _dataTypeService.GetAll(); - _publishedDataTypes = dataTypes.ToDictionary(x => x.Id, CreatePublishedDataType); + _publishedDataTypes.Remove(id); } - else - { - foreach (var id in ids) - _publishedDataTypes.Remove(id); - var dataTypes = _dataTypeService.GetAll(ids); - foreach (var dataType in dataTypes) - _publishedDataTypes[dataType.Id] = CreatePublishedDataType(dataType); + IEnumerable dataTypes = _dataTypeService.GetAll(ids); + foreach (IDataType dataType in dataTypes) + { + _publishedDataTypes[dataType.Id] = CreatePublishedDataType(dataType); } } } - - private PublishedDataType CreatePublishedDataType(IDataType dataType) - => new PublishedDataType(dataType.Id, dataType.EditorAlias, dataType is DataType d ? d.GetLazyConfiguration() : new Lazy(() => dataType.Configuration)); } + + /// + /// This method is for tests and is not intended to be used directly from application code. + /// + /// Values are assumed to be consisted and are not checked. + internal IPublishedContentType CreateContentType(Guid key, int id, string alias, + Func> propertyTypes, + ContentVariation variations = ContentVariation.Nothing, bool isElement = false) => new PublishedContentType(key, + id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, variations, isElement); + + /// + /// This method is for tests and is not intended to be used directly from application code. + /// + /// Values are assumed to be consisted and are not checked. + internal IPublishedContentType CreateContentType(Guid key, int id, string alias, + IEnumerable compositionAliases, + Func> propertyTypes, + ContentVariation variations = ContentVariation.Nothing, bool isElement = false) => new PublishedContentType(key, + id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, variations, isElement); + + /// + /// This method is for tests and is not intended to be used directly from application code. + /// + /// Values are assumed to be consisted and are not checked. + internal IPublishedPropertyType CreatePropertyType(string propertyTypeAlias, int dataTypeId, bool umbraco = false, + ContentVariation variations = ContentVariation.Nothing) => new PublishedPropertyType(propertyTypeAlias, + dataTypeId, umbraco, variations, _propertyValueConverters, _publishedModelFactory, this); + + private PublishedDataType CreatePublishedDataType(IDataType dataType) + => new(dataType.Id, dataType.EditorAlias, + dataType is DataType d ? d.GetLazyConfiguration() : new Lazy(() => dataType.Configuration)); } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index 9d16de743d8a..62ec95238127 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -1,135 +1,128 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; - -namespace Umbraco.Cms.Core.Models.PublishedContent +using System.Diagnostics; + +namespace Umbraco.Cms.Core.Models.PublishedContent; +// +// we cannot implement strongly-typed content by inheriting from some sort +// of "master content" because that master content depends on the actual content cache +// that is being used. It can be an XmlPublishedContent with the XmlPublishedCache, +// or just anything else. +// +// So we implement strongly-typed content by encapsulating whatever content is +// returned by the content cache, and providing extra properties (mostly) or +// methods or whatever. This class provides the base for such encapsulation. +// + +/// +/// Provides an abstract base class for IPublishedContent implementations that +/// wrap and extend another IPublishedContent. +/// +[DebuggerDisplay("{Id}: {Name} ({ContentType?.Alias})")] +public abstract class PublishedContentWrapped : IPublishedContent { - // - // we cannot implement strongly-typed content by inheriting from some sort - // of "master content" because that master content depends on the actual content cache - // that is being used. It can be an XmlPublishedContent with the XmlPublishedCache, - // or just anything else. - // - // So we implement strongly-typed content by encapsulating whatever content is - // returned by the content cache, and providing extra properties (mostly) or - // methods or whatever. This class provides the base for such encapsulation. - // + private readonly IPublishedContent _content; + private readonly IPublishedValueFallback _publishedValueFallback; /// - /// Provides an abstract base class for IPublishedContent implementations that - /// wrap and extend another IPublishedContent. + /// Initialize a new instance of the class + /// with an IPublishedContent instance to wrap. /// - [DebuggerDisplay("{Id}: {Name} ({ContentType?.Alias})")] - public abstract class PublishedContentWrapped : IPublishedContent + /// The content to wrap. + /// The published value fallback. + protected PublishedContentWrapped(IPublishedContent content, IPublishedValueFallback publishedValueFallback) { - private readonly IPublishedContent _content; - private readonly IPublishedValueFallback _publishedValueFallback; - - /// - /// Initialize a new instance of the class - /// with an IPublishedContent instance to wrap. - /// - /// The content to wrap. - /// The published value fallback. - protected PublishedContentWrapped(IPublishedContent content, IPublishedValueFallback publishedValueFallback) - { - _content = content; - _publishedValueFallback = publishedValueFallback; - } + _content = content; + _publishedValueFallback = publishedValueFallback; + } - /// - /// Gets the wrapped content. - /// - /// The wrapped content, that was passed as an argument to the constructor. - public IPublishedContent Unwrap() => _content; + #region ContentType - #region ContentType + /// + public virtual IPublishedContentType ContentType => _content.ContentType; - /// - public virtual IPublishedContentType ContentType => _content.ContentType; + #endregion - #endregion + #region PublishedElement - #region PublishedElement + /// + public Guid Key => _content.Key; - /// - public Guid Key => _content.Key; + #endregion - #endregion + /// + /// Gets the wrapped content. + /// + /// The wrapped content, that was passed as an argument to the constructor. + public IPublishedContent Unwrap() => _content; - #region PublishedContent + #region PublishedContent - /// - public virtual int Id => _content.Id; + /// + public virtual int Id => _content.Id; - /// - public virtual string? Name => _content.Name; + /// + public virtual string? Name => _content.Name; - /// - public virtual string? UrlSegment => _content.UrlSegment; + /// + public virtual string? UrlSegment => _content.UrlSegment; - /// - public virtual int SortOrder => _content.SortOrder; + /// + public virtual int SortOrder => _content.SortOrder; - /// - public virtual int Level => _content.Level; + /// + public virtual int Level => _content.Level; - /// - public virtual string Path => _content.Path; + /// + public virtual string Path => _content.Path; - /// - public virtual int? TemplateId => _content.TemplateId; + /// + public virtual int? TemplateId => _content.TemplateId; - /// - public virtual int CreatorId => _content.CreatorId; + /// + public virtual int CreatorId => _content.CreatorId; - /// - public virtual DateTime CreateDate => _content.CreateDate; + /// + public virtual DateTime CreateDate => _content.CreateDate; - /// - public virtual int WriterId => _content.WriterId; + /// + public virtual int WriterId => _content.WriterId; - /// - public virtual DateTime UpdateDate => _content.UpdateDate; + /// + public virtual DateTime UpdateDate => _content.UpdateDate; - /// - public IReadOnlyDictionary Cultures => _content.Cultures; + /// + public IReadOnlyDictionary Cultures => _content.Cultures; - /// - public virtual PublishedItemType ItemType => _content.ItemType; + /// + public virtual PublishedItemType ItemType => _content.ItemType; - /// - public virtual bool IsDraft(string? culture = null) => _content.IsDraft(culture); + /// + public virtual bool IsDraft(string? culture = null) => _content.IsDraft(culture); - /// - public virtual bool IsPublished(string? culture = null) => _content.IsPublished(culture); + /// + public virtual bool IsPublished(string? culture = null) => _content.IsPublished(culture); - #endregion + #endregion - #region Tree + #region Tree - /// - public virtual IPublishedContent? Parent => _content.Parent; + /// + public virtual IPublishedContent? Parent => _content.Parent; - /// - public virtual IEnumerable? Children => _content.Children; + /// + public virtual IEnumerable? Children => _content.Children; - /// - public virtual IEnumerable? ChildrenForAllCultures => _content.ChildrenForAllCultures; + /// + public virtual IEnumerable? ChildrenForAllCultures => _content.ChildrenForAllCultures; - #endregion + #endregion - #region Properties + #region Properties - /// - public virtual IEnumerable Properties => _content.Properties; + /// + public virtual IEnumerable Properties => _content.Properties; - /// - public virtual IPublishedProperty? GetProperty(string alias) - { - return _content.GetProperty(alias); - } + /// + public virtual IPublishedProperty? GetProperty(string alias) => _content.GetProperty(alias); - #endregion - } + #endregion } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs index 9525a9d7ac8e..81735da01bf7 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs @@ -1,51 +1,59 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Contains culture specific values for . +/// +[DebuggerDisplay("{Culture}")] +public class PublishedCultureInfo { /// - /// Contains culture specific values for . + /// Initializes a new instance of the class. /// - [DebuggerDisplay("{Culture}")] - public class PublishedCultureInfo + public PublishedCultureInfo(string culture, string? name, string? urlSegment, DateTime date) { - /// - /// Initializes a new instance of the class. - /// - public PublishedCultureInfo(string culture, string? name, string? urlSegment, DateTime date) + if (name == null) { - if (name == null) throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); + throw new ArgumentNullException(nameof(name)); + } - Culture = culture ?? throw new ArgumentNullException(nameof(culture)); - Name = name; - UrlSegment = urlSegment; - Date = date; + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(name)); } - /// - /// Gets the culture. - /// - public string Culture { get; } - - /// - /// Gets the name of the item. - /// - public string Name { get; } - - /// - /// Gets the URL segment of the item. - /// - public string? UrlSegment { get; } - - /// - /// Gets the date associated with the culture. - /// - /// - /// For published culture, this is the date the culture was published. For draft - /// cultures, this is the date the culture was made available, ie the last time its - /// name changed. - /// - public DateTime Date { get; } + Culture = culture ?? throw new ArgumentNullException(nameof(culture)); + Name = name; + UrlSegment = urlSegment; + Date = date; } + + /// + /// Gets the culture. + /// + public string Culture { get; } + + /// + /// Gets the name of the item. + /// + public string Name { get; } + + /// + /// Gets the URL segment of the item. + /// + public string? UrlSegment { get; } + + /// + /// Gets the date associated with the culture. + /// + /// + /// + /// For published culture, this is the date the culture was published. For draft + /// cultures, this is the date the culture was made available, ie the last time its + /// name changed. + /// + /// + public DateTime Date { get; } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedDataType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedDataType.cs index de590c2531a7..05fd97e08d2b 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedDataType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedDataType.cs @@ -1,64 +1,65 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Represents a published data type. +/// +/// +/// +/// Instances of the class are immutable, ie +/// if the data type changes, then a new class needs to be created. +/// +/// These instances should be created by an . +/// +[DebuggerDisplay("{EditorAlias}")] +public class PublishedDataType { + private readonly Lazy _lazyConfiguration; + /// - /// Represents a published data type. + /// Initializes a new instance of the class. /// - /// - /// Instances of the class are immutable, ie - /// if the data type changes, then a new class needs to be created. - /// These instances should be created by an . - /// - [DebuggerDisplay("{EditorAlias}")] - public class PublishedDataType + public PublishedDataType(int id, string editorAlias, Lazy lazyConfiguration) { - private readonly Lazy _lazyConfiguration; + _lazyConfiguration = lazyConfiguration; - /// - /// Initializes a new instance of the class. - /// - public PublishedDataType(int id, string editorAlias, Lazy lazyConfiguration) - { - _lazyConfiguration = lazyConfiguration; - - Id = id; - EditorAlias = editorAlias; - } + Id = id; + EditorAlias = editorAlias; + } - /// - /// Gets the datatype identifier. - /// - public int Id { get; } + /// + /// Gets the datatype identifier. + /// + public int Id { get; } - /// - /// Gets the data type editor alias. - /// - public string EditorAlias { get; } + /// + /// Gets the data type editor alias. + /// + public string EditorAlias { get; } - /// - /// Gets the data type configuration. - /// - public object? Configuration => _lazyConfiguration?.Value; + /// + /// Gets the data type configuration. + /// + public object? Configuration => _lazyConfiguration?.Value; - /// - /// Gets the configuration object. - /// - /// The expected type of the configuration object. - /// When the datatype configuration is not of the expected type. - public T? ConfigurationAs() - where T : class + /// + /// Gets the configuration object. + /// + /// The expected type of the configuration object. + /// When the datatype configuration is not of the expected type. + public T? ConfigurationAs() + where T : class + { + switch (Configuration) { - switch (Configuration) - { - case null: - return null; - case T configurationAsT: - return configurationAsT; - } - - throw new InvalidCastException($"Cannot cast dataType configuration, of type {Configuration.GetType().Name}, to {typeof(T).Name}."); + case null: + return null; + case T configurationAsT: + return configurationAsT; } + + throw new InvalidCastException( + $"Cannot cast dataType configuration, of type {Configuration.GetType().Name}, to {typeof(T).Name}."); } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedElementModel.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedElementModel.cs index f093e7b20c18..bb220e5e7d9c 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedElementModel.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedElementModel.cs @@ -1,22 +1,24 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// +/// Represents a strongly-typed published element. +/// +/// +/// Every strongly-typed property set class should inherit from PublishedElementModel +/// (or inherit from a class that inherits from... etc.) so they are picked by the factory. +/// +public abstract class PublishedElementModel : PublishedElementWrapped { /// /// - /// Represents a strongly-typed published element. + /// Initializes a new instance of the class with + /// an original instance. /// - /// Every strongly-typed property set class should inherit from PublishedElementModel - /// (or inherit from a class that inherits from... etc.) so they are picked by the factory. - public abstract class PublishedElementModel : PublishedElementWrapped + /// The original content. + /// The published value fallback. + protected PublishedElementModel(IPublishedElement content, IPublishedValueFallback publishedValueFallback) + : base(content, publishedValueFallback) { - /// - /// - /// Initializes a new instance of the class with - /// an original instance. - /// - /// The original content. - /// The published value fallback. - protected PublishedElementModel(IPublishedElement content, IPublishedValueFallback publishedValueFallback) - : base(content, publishedValueFallback) - { } } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedElementWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedElementWrapped.cs index cc0c6b963aca..3293627f3760 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedElementWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedElementWrapped.cs @@ -1,45 +1,41 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.Models.PublishedContent +/// +/// Provides an abstract base class for IPublishedElement implementations that +/// wrap and extend another IPublishedElement. +/// +public abstract class PublishedElementWrapped : IPublishedElement { + private readonly IPublishedElement _content; + private readonly IPublishedValueFallback _publishedValueFallback; + /// - /// Provides an abstract base class for IPublishedElement implementations that - /// wrap and extend another IPublishedElement. + /// Initializes a new instance of the class + /// with an IPublishedElement instance to wrap. /// - public abstract class PublishedElementWrapped : IPublishedElement + /// The content to wrap. + /// The published value fallback. + protected PublishedElementWrapped(IPublishedElement content, IPublishedValueFallback publishedValueFallback) { - private readonly IPublishedElement _content; - private readonly IPublishedValueFallback _publishedValueFallback; - - /// - /// Initializes a new instance of the class - /// with an IPublishedElement instance to wrap. - /// - /// The content to wrap. - /// The published value fallback. - protected PublishedElementWrapped(IPublishedElement content, IPublishedValueFallback publishedValueFallback) - { - _content = content; - _publishedValueFallback = publishedValueFallback; - } + _content = content; + _publishedValueFallback = publishedValueFallback; + } - /// - /// Gets the wrapped content. - /// - /// The wrapped content, that was passed as an argument to the constructor. - public IPublishedElement Unwrap() => _content; + /// + public IPublishedContentType ContentType => _content.ContentType; - /// - public IPublishedContentType ContentType => _content.ContentType; + /// + public Guid Key => _content.Key; - /// - public Guid Key => _content.Key; + /// + public IEnumerable Properties => _content.Properties; - /// - public IEnumerable Properties => _content.Properties; + /// + public IPublishedProperty? GetProperty(string alias) => _content.GetProperty(alias); - /// - public IPublishedProperty? GetProperty(string alias) => _content.GetProperty(alias); - } + /// + /// Gets the wrapped content. + /// + /// The wrapped content, that was passed as an argument to the constructor. + public IPublishedElement Unwrap() => _content; } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs index 7d16152b6ee7..d36fee270b4b 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs @@ -1,34 +1,33 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// The type of published element. +/// +/// Can be a simple element, or a document, a media, a member. +public enum PublishedItemType { /// - /// The type of published element. + /// Unknown. /// - /// Can be a simple element, or a document, a media, a member. - public enum PublishedItemType - { - /// - /// Unknown. - /// - Unknown = 0, + Unknown = 0, - /// - /// An element. - /// - Element, + /// + /// An element. + /// + Element, - /// - /// A document. - /// - Content, + /// + /// A document. + /// + Content, - /// - /// A media. - /// - Media, + /// + /// A media. + /// + Media, - /// - /// A member. - /// - Member - } + /// + /// A member. + /// + Member } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedModelAttribute.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedModelAttribute.cs index 035c8a213a48..3444783e8f71 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedModelAttribute.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedModelAttribute.cs @@ -1,32 +1,39 @@ -using System; +namespace Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.Models.PublishedContent +/// +/// +/// Indicates that the class is a published content model for a specified content type. +/// +/// +/// By default, the name of the class is assumed to be the content type alias. The +/// PublishedContentModelAttribute can be used to indicate a different alias. +/// +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +public sealed class PublishedModelAttribute : Attribute { /// /// - /// Indicates that the class is a published content model for a specified content type. + /// Initializes a new instance of the class with a content type alias. /// - /// By default, the name of the class is assumed to be the content type alias. The - /// PublishedContentModelAttribute can be used to indicate a different alias. - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public sealed class PublishedModelAttribute : Attribute + /// The content type alias. + public PublishedModelAttribute(string contentTypeAlias) { - /// - /// - /// Initializes a new instance of the class with a content type alias. - /// - /// The content type alias. - public PublishedModelAttribute(string contentTypeAlias) + if (contentTypeAlias == null) { - if (contentTypeAlias == null) throw new ArgumentNullException(nameof(contentTypeAlias)); - if (string.IsNullOrWhiteSpace(contentTypeAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(contentTypeAlias)); + throw new ArgumentNullException(nameof(contentTypeAlias)); + } - ContentTypeAlias = contentTypeAlias; + if (string.IsNullOrWhiteSpace(contentTypeAlias)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(contentTypeAlias)); } - /// - /// Gets or sets the content type alias. - /// - public string ContentTypeAlias { get; } + ContentTypeAlias = contentTypeAlias; } + + /// + /// Gets or sets the content type alias. + /// + public string ContentTypeAlias { get; } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs index 7053a238e677..96fc909f3a3e 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs @@ -1,156 +1,165 @@ using System.Collections; using System.Reflection; -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Implements a strongly typed content model factory +/// +public class PublishedModelFactory : IPublishedModelFactory { + private readonly Dictionary? _modelInfos; + private readonly Dictionary _modelTypeMap; + private readonly IPublishedValueFallback _publishedValueFallback; + /// - /// Implements a strongly typed content model factory + /// Initializes a new instance of the class with types. /// - public class PublishedModelFactory : IPublishedModelFactory + /// The model types. + /// + /// + /// Types must implement IPublishedContent and have a unique constructor that + /// accepts one IPublishedContent as a parameter. + /// + /// To activate, + /// + /// var types = TypeLoader.Current.GetTypes{PublishedContentModel}(); + /// var factory = new PublishedContentModelFactoryImpl(types); + /// PublishedContentModelFactoryResolver.Current.SetFactory(factory); + /// + /// + public PublishedModelFactory(IEnumerable types, IPublishedValueFallback publishedValueFallback) { - private readonly Dictionary? _modelInfos; - private readonly Dictionary _modelTypeMap; - private readonly IPublishedValueFallback _publishedValueFallback; + var modelInfos = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + var modelTypeMap = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - private class ModelInfo + foreach (Type type in types) { - public Type? ParameterType { get; set; } - - public Func? Ctor { get; set; } - - public Type? ModelType { get; set; } + // so... the model type has to implement a ctor with one parameter being, or inheriting from, + // IPublishedElement - but it can be IPublishedContent - so we cannot get one precise ctor, + // we have to iterate over all ctors and try to find the right one - public Func? ListCtor { get; set; } - } - - /// - /// Initializes a new instance of the class with types. - /// - /// The model types. - /// - /// Types must implement IPublishedContent and have a unique constructor that - /// accepts one IPublishedContent as a parameter. - /// To activate, - /// - /// var types = TypeLoader.Current.GetTypes{PublishedContentModel}(); - /// var factory = new PublishedContentModelFactoryImpl(types); - /// PublishedContentModelFactoryResolver.Current.SetFactory(factory); - /// - /// - public PublishedModelFactory(IEnumerable types, IPublishedValueFallback publishedValueFallback) - { - var modelInfos = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - var modelTypeMap = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + ConstructorInfo? constructor = null; + Type? parameterType = null; - foreach (var type in types) + foreach (ConstructorInfo ctor in type.GetConstructors()) { - // so... the model type has to implement a ctor with one parameter being, or inheriting from, - // IPublishedElement - but it can be IPublishedContent - so we cannot get one precise ctor, - // we have to iterate over all ctors and try to find the right one - - ConstructorInfo? constructor = null; - Type? parameterType = null; - - foreach (var ctor in type.GetConstructors()) + ParameterInfo[] parms = ctor.GetParameters(); + if (parms.Length == 2 && typeof(IPublishedElement).IsAssignableFrom(parms[0].ParameterType) && + typeof(IPublishedValueFallback).IsAssignableFrom(parms[1].ParameterType)) { - var parms = ctor.GetParameters(); - if (parms.Length == 2 && typeof(IPublishedElement).IsAssignableFrom(parms[0].ParameterType) && typeof(IPublishedValueFallback).IsAssignableFrom(parms[1].ParameterType)) + if (constructor != null) { - if (constructor != null) - { - throw new InvalidOperationException($"Type {type.FullName} has more than one public constructor with one argument of type, or implementing, IPublishedElement."); - } - - constructor = ctor; - parameterType = parms[0].ParameterType; + throw new InvalidOperationException( + $"Type {type.FullName} has more than one public constructor with one argument of type, or implementing, IPublishedElement."); } - } - if (constructor == null) - { - throw new InvalidOperationException($"Type {type.FullName} is missing a public constructor with one argument of type, or implementing, IPublishedElement."); + constructor = ctor; + parameterType = parms[0].ParameterType; } - - var attribute = type.GetCustomAttribute(false); - var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias; - - if (modelInfos.TryGetValue(typeName, out var modelInfo)) - { - throw new InvalidOperationException($"Both types '{type.AssemblyQualifiedName}' and '{modelInfo.ModelType?.AssemblyQualifiedName}' want to be a model type for content type with alias \"{typeName}\"."); - } - - // have to use an unsafe ctor because we don't know the types, really - var modelCtor = ReflectionUtilities.EmitConstructorUnsafe>(constructor); - modelInfos[typeName] = new ModelInfo { ParameterType = parameterType, ModelType = type, Ctor = modelCtor }; - modelTypeMap[typeName] = type; } - _modelInfos = modelInfos.Count > 0 ? modelInfos : null; - _modelTypeMap = modelTypeMap; - _publishedValueFallback = publishedValueFallback; - } - - /// - public IPublishedElement CreateModel(IPublishedElement element) - { - // fail fast - if (_modelInfos is null || element.ContentType.Alias is null || !_modelInfos.TryGetValue(element.ContentType.Alias, out var modelInfo)) + if (constructor == null) { - return element; + throw new InvalidOperationException( + $"Type {type.FullName} is missing a public constructor with one argument of type, or implementing, IPublishedElement."); } - // ReSharper disable once UseMethodIsInstanceOfType - if (modelInfo.ParameterType?.IsAssignableFrom(element.GetType()) == false) + PublishedModelAttribute attribute = type.GetCustomAttribute(false); + var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias; + + if (modelInfos.TryGetValue(typeName, out ModelInfo modelInfo)) { - throw new InvalidOperationException($"Model {modelInfo.ModelType} expects argument of type {modelInfo.ParameterType.FullName}, but got {element.GetType().FullName}."); + throw new InvalidOperationException( + $"Both types '{type.AssemblyQualifiedName}' and '{modelInfo.ModelType?.AssemblyQualifiedName}' want to be a model type for content type with alias \"{typeName}\"."); } - // can cast, because we checked when creating the ctor - return (IPublishedElement)modelInfo.Ctor!(element, _publishedValueFallback); + // have to use an unsafe ctor because we don't know the types, really + Func modelCtor = + ReflectionUtilities.EmitConstructorUnsafe>(constructor); + modelInfos[typeName] = new ModelInfo {ParameterType = parameterType, ModelType = type, Ctor = modelCtor}; + modelTypeMap[typeName] = type; } - /// - public IList? CreateModelList(string? alias) + _modelInfos = modelInfos.Count > 0 ? modelInfos : null; + _modelTypeMap = modelTypeMap; + _publishedValueFallback = publishedValueFallback; + } + + /// + public IPublishedElement CreateModel(IPublishedElement element) + { + // fail fast + if (_modelInfos is null || element.ContentType.Alias is null || + !_modelInfos.TryGetValue(element.ContentType.Alias, out ModelInfo modelInfo)) { - // fail fast - if (_modelInfos is null || alias is null || !_modelInfos.TryGetValue(alias, out var modelInfo) || modelInfo.ModelType is null) - { - return new List(); - } + return element; + } - var ctor = modelInfo.ListCtor; - if (ctor != null) - { - return ctor(); - } + // ReSharper disable once UseMethodIsInstanceOfType + if (modelInfo.ParameterType?.IsAssignableFrom(element.GetType()) == false) + { + throw new InvalidOperationException( + $"Model {modelInfo.ModelType} expects argument of type {modelInfo.ParameterType.FullName}, but got {element.GetType().FullName}."); + } - var listType = typeof(List<>).MakeGenericType(modelInfo.ModelType); - ctor = modelInfo.ListCtor = ReflectionUtilities.EmitConstructor>(declaring: listType); - if (ctor is not null) - { - return ctor(); - } + // can cast, because we checked when creating the ctor + return (IPublishedElement)modelInfo.Ctor!(element, _publishedValueFallback); + } - return null; + /// + public IList? CreateModelList(string? alias) + { + // fail fast + if (_modelInfos is null || alias is null || !_modelInfos.TryGetValue(alias, out ModelInfo modelInfo) || + modelInfo.ModelType is null) + { + return new List(); } - /// - public Type GetModelType(string? alias) + Func ctor = modelInfo.ListCtor; + if (ctor != null) { - // fail fast - if (_modelInfos is null || - alias is null || - !_modelInfos.TryGetValue(alias, out var modelInfo) || - modelInfo.ModelType is null) - { - return typeof(IPublishedElement); - } + return ctor(); + } + + Type listType = typeof(List<>).MakeGenericType(modelInfo.ModelType); + ctor = modelInfo.ListCtor = ReflectionUtilities.EmitConstructor>(declaring: listType); + if (ctor is not null) + { + return ctor(); + } + + return null; + } - return modelInfo.ModelType; + /// + public Type GetModelType(string? alias) + { + // fail fast + if (_modelInfos is null || + alias is null || + !_modelInfos.TryGetValue(alias, out ModelInfo modelInfo) || + modelInfo.ModelType is null) + { + return typeof(IPublishedElement); } - /// - public Type MapModelType(Type type) - => ModelType.Map(type, _modelTypeMap); + return modelInfo.ModelType; + } + + /// + public Type MapModelType(Type type) + => ModelType.Map(type, _modelTypeMap); + + private class ModelInfo + { + public Type? ParameterType { get; set; } + + public Func? Ctor { get; set; } + + public Type? ModelType { get; set; } + + public Func? ListCtor { get; set; } } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs index 6cdbd85c7483..a953653b8b50 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs @@ -1,69 +1,71 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; using Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Provides a base class for IPublishedProperty implementations which converts and caches +/// the value source to the actual value to use when rendering content. +/// +[DebuggerDisplay("{Alias} ({PropertyType?.EditorAlias})")] +public abstract class PublishedPropertyBase : IPublishedProperty { /// - /// Provides a base class for IPublishedProperty implementations which converts and caches - /// the value source to the actual value to use when rendering content. + /// Initializes a new instance of the class. /// - [DebuggerDisplay("{Alias} ({PropertyType?.EditorAlias})")] - public abstract class PublishedPropertyBase : IPublishedProperty + protected PublishedPropertyBase(IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel) { - /// - /// Initializes a new instance of the class. - /// - protected PublishedPropertyBase(IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel) - { - PropertyType = propertyType ?? throw new ArgumentNullException(nameof(propertyType)); - ReferenceCacheLevel = referenceCacheLevel; + PropertyType = propertyType ?? throw new ArgumentNullException(nameof(propertyType)); + ReferenceCacheLevel = referenceCacheLevel; - ValidateCacheLevel(ReferenceCacheLevel, true); - ValidateCacheLevel(PropertyType.CacheLevel, false); - } + ValidateCacheLevel(ReferenceCacheLevel, true); + ValidateCacheLevel(PropertyType.CacheLevel, false); + } - // validates the cache level - private static void ValidateCacheLevel(PropertyCacheLevel cacheLevel, bool validateUnknown) - { - switch (cacheLevel) - { - case PropertyCacheLevel.Element: - case PropertyCacheLevel.Elements: - case PropertyCacheLevel.Snapshot: - case PropertyCacheLevel.None: - break; - case PropertyCacheLevel.Unknown: - if (!validateUnknown) goto default; - break; - default: - throw new Exception($"Invalid cache level \"{cacheLevel}\"."); - } - } + /// + /// Gets the property reference cache level. + /// + public PropertyCacheLevel ReferenceCacheLevel { get; } - /// - /// Gets the property type. - /// - public IPublishedPropertyType PropertyType { get; } + /// + /// Gets the property type. + /// + public IPublishedPropertyType PropertyType { get; } - /// - /// Gets the property reference cache level. - /// - public PropertyCacheLevel ReferenceCacheLevel { get; } + /// + public string Alias => PropertyType.Alias; - /// - public string Alias => PropertyType.Alias; + /// + public abstract bool HasValue(string? culture = null, string? segment = null); - /// - public abstract bool HasValue(string? culture = null, string? segment = null); + /// + public abstract object? GetSourceValue(string? culture = null, string? segment = null); - /// - public abstract object? GetSourceValue(string? culture = null, string? segment = null); + /// + public abstract object? GetValue(string? culture = null, string? segment = null); - /// - public abstract object? GetValue(string? culture = null, string? segment = null); + /// + public abstract object? GetXPathValue(string? culture = null, string? segment = null); - /// - public abstract object? GetXPathValue(string? culture = null, string? segment = null); + // validates the cache level + private static void ValidateCacheLevel(PropertyCacheLevel cacheLevel, bool validateUnknown) + { + switch (cacheLevel) + { + case PropertyCacheLevel.Element: + case PropertyCacheLevel.Elements: + case PropertyCacheLevel.Snapshot: + case PropertyCacheLevel.None: + break; + case PropertyCacheLevel.Unknown: + if (!validateUnknown) + { + goto default; + } + + break; + default: + throw new Exception($"Invalid cache level \"{cacheLevel}\"."); + } } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs index 9420811f24f6..5ae3bd564124 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs @@ -1,264 +1,321 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; using System.Xml.Linq; using System.Xml.XPath; using Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Represents a published property type. +/// +/// +/// Instances of the class are immutable, ie +/// if the property type changes, then a new class needs to be created. +/// +[DebuggerDisplay("{Alias} ({EditorAlias})")] +public class PublishedPropertyType : IPublishedPropertyType { + private readonly object _locker = new(); + private readonly PropertyValueConverterCollection _propertyValueConverters; + private readonly IPublishedModelFactory _publishedModelFactory; + private PropertyCacheLevel _cacheLevel; + private Type? _clrType; + private IPropertyValueConverter? _converter; + private volatile bool _initialized; + + private Type? _modelClrType; + + #region Constructors + /// - /// Represents a published property type. + /// Initialize a new instance of the class with a property type. /// - /// Instances of the class are immutable, ie - /// if the property type changes, then a new class needs to be created. - [DebuggerDisplay("{Alias} ({EditorAlias})")] - public class PublishedPropertyType : IPublishedPropertyType - { - private readonly IPublishedModelFactory _publishedModelFactory; - private readonly PropertyValueConverterCollection _propertyValueConverters; - private readonly object _locker = new object(); - private volatile bool _initialized; - private IPropertyValueConverter? _converter; - private PropertyCacheLevel _cacheLevel; - - private Type? _modelClrType; - private Type? _clrType; - - #region Constructors - - /// - /// Initialize a new instance of the class with a property type. - /// - /// - /// The new published property type belongs to the published content type. - /// - public PublishedPropertyType(IPublishedContentType contentType, IPropertyType propertyType, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) - : this(propertyType.Alias, propertyType.DataTypeId, true, propertyType.Variations, propertyValueConverters, publishedModelFactory, factory) - { - ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - } + /// + /// The new published property type belongs to the published content type. + /// + public PublishedPropertyType(IPublishedContentType contentType, IPropertyType propertyType, + PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, + IPublishedContentTypeFactory factory) + : this(propertyType.Alias, propertyType.DataTypeId, true, propertyType.Variations, propertyValueConverters, + publishedModelFactory, factory) => + ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - /// - /// This constructor is for tests and is not intended to be used directly from application code. - /// - /// - /// Values are assumed to be consisted and are not checked. - /// The new published property type belongs to the published content type. - /// - public PublishedPropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, bool isUserProperty, ContentVariation variations, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) - : this(propertyTypeAlias, dataTypeId, isUserProperty, variations, propertyValueConverters, publishedModelFactory, factory) - { - ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - } + /// + /// This constructor is for tests and is not intended to be used directly from application code. + /// + /// + /// Values are assumed to be consisted and are not checked. + /// The new published property type belongs to the published content type. + /// + public PublishedPropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, + bool isUserProperty, ContentVariation variations, PropertyValueConverterCollection propertyValueConverters, + IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) + : this(propertyTypeAlias, dataTypeId, isUserProperty, variations, propertyValueConverters, + publishedModelFactory, factory) => + ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - /// - /// This constructor is for tests and is not intended to be used directly from application code. - /// - /// - /// Values are assumed to be consistent and are not checked. - /// The new published property type does not belong to a published content type. - /// - public PublishedPropertyType(string propertyTypeAlias, int dataTypeId, bool isUserProperty, ContentVariation variations, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) - { - _publishedModelFactory = publishedModelFactory ?? throw new ArgumentNullException(nameof(publishedModelFactory)); - _propertyValueConverters = propertyValueConverters ?? throw new ArgumentNullException(nameof(propertyValueConverters)); + /// + /// This constructor is for tests and is not intended to be used directly from application code. + /// + /// + /// Values are assumed to be consistent and are not checked. + /// The new published property type does not belong to a published content type. + /// + public PublishedPropertyType(string propertyTypeAlias, int dataTypeId, bool isUserProperty, + ContentVariation variations, PropertyValueConverterCollection propertyValueConverters, + IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) + { + _publishedModelFactory = + publishedModelFactory ?? throw new ArgumentNullException(nameof(publishedModelFactory)); + _propertyValueConverters = + propertyValueConverters ?? throw new ArgumentNullException(nameof(propertyValueConverters)); - Alias = propertyTypeAlias; + Alias = propertyTypeAlias; - IsUserProperty = isUserProperty; - Variations = variations; + IsUserProperty = isUserProperty; + Variations = variations; - DataType = factory.GetDataType(dataTypeId); - } + DataType = factory.GetDataType(dataTypeId); + } - #endregion + #endregion - #region Property type + #region Property type - /// - public IPublishedContentType? ContentType { get; internal set; } // internally set by PublishedContentType constructor + /// + public IPublishedContentType? + ContentType { get; internal set; } // internally set by PublishedContentType constructor - /// - public PublishedDataType DataType { get; } + /// + public PublishedDataType DataType { get; } - /// - public string Alias { get; } + /// + public string Alias { get; } - /// - public string EditorAlias => DataType.EditorAlias; + /// + public string EditorAlias => DataType.EditorAlias; - /// - public bool IsUserProperty { get; } + /// + public bool IsUserProperty { get; } - /// - public ContentVariation Variations { get; } + /// + public ContentVariation Variations { get; } - #endregion + #endregion - #region Converters + #region Converters - private void Initialize() + private void Initialize() + { + if (_initialized) { - if (_initialized) return; - lock (_locker) + return; + } + + lock (_locker) + { + if (_initialized) { - if (_initialized) return; - InitializeLocked(); - _initialized = true; + return; } + + InitializeLocked(); + _initialized = true; } + } - private void InitializeLocked() - { - _converter = null; - var isdefault = false; + private void InitializeLocked() + { + _converter = null; + var isdefault = false; - foreach (var converter in _propertyValueConverters) + foreach (IPropertyValueConverter converter in _propertyValueConverters) + { + if (!converter.IsConverter(this)) { - if (!converter.IsConverter(this)) - continue; + continue; + } - if (_converter == null) - { - _converter = converter; - isdefault = _propertyValueConverters.IsDefault(converter); - continue; - } + if (_converter == null) + { + _converter = converter; + isdefault = _propertyValueConverters.IsDefault(converter); + continue; + } - if (isdefault) + if (isdefault) + { + if (_propertyValueConverters.IsDefault(converter)) { - if (_propertyValueConverters.IsDefault(converter)) + // previous was default, and got another default + if (_propertyValueConverters.Shadows(_converter, converter)) { - // previous was default, and got another default - if (_propertyValueConverters.Shadows(_converter, converter)) - { - // previous shadows, ignore - } - else if (_propertyValueConverters.Shadows(converter, _converter)) - { - // shadows previous, replace - _converter = converter; - } - else - { - // no shadow - bad - throw new InvalidOperationException(string.Format("Type '{2}' cannot be an IPropertyValueConverter" - + " for property '{1}' of content type '{0}' because type '{3}' has already been detected as a converter" - + " for that property, and only one converter can exist for a property.", - ContentType?.Alias, Alias, - converter.GetType().FullName, _converter.GetType().FullName)); - } + // previous shadows, ignore } - else + else if (_propertyValueConverters.Shadows(converter, _converter)) { - // previous was default, replaced by non-default + // shadows previous, replace _converter = converter; - isdefault = false; - } - } - else - { - if (_propertyValueConverters.IsDefault(converter)) - { - // previous was non-default, ignore default } else { - // previous was non-default, and got another non-default - bad - throw new InvalidOperationException(string.Format("Type '{2}' cannot be an IPropertyValueConverter" - + " for property '{1}' of content type '{0}' because type '{3}' has already been detected as a converter" - + " for that property, and only one converter can exist for a property.", + // no shadow - bad + throw new InvalidOperationException(string.Format( + "Type '{2}' cannot be an IPropertyValueConverter" + + " for property '{1}' of content type '{0}' because type '{3}' has already been detected as a converter" + + " for that property, and only one converter can exist for a property.", ContentType?.Alias, Alias, converter.GetType().FullName, _converter.GetType().FullName)); } } + else + { + // previous was default, replaced by non-default + _converter = converter; + isdefault = false; + } + } + else + { + if (_propertyValueConverters.IsDefault(converter)) + { + // previous was non-default, ignore default + } + else + { + // previous was non-default, and got another non-default - bad + throw new InvalidOperationException(string.Format("Type '{2}' cannot be an IPropertyValueConverter" + + " for property '{1}' of content type '{0}' because type '{3}' has already been detected as a converter" + + " for that property, and only one converter can exist for a property.", + ContentType?.Alias, Alias, + converter.GetType().FullName, _converter.GetType().FullName)); + } } - - _cacheLevel = _converter?.GetPropertyCacheLevel(this) ?? PropertyCacheLevel.Snapshot; - _modelClrType = _converter == null ? typeof (object) : _converter.GetPropertyValueType(this); } - /// - public bool? IsValue(object? value, PropertyValueLevel level) - { - if (!_initialized) Initialize(); + _cacheLevel = _converter?.GetPropertyCacheLevel(this) ?? PropertyCacheLevel.Snapshot; + _modelClrType = _converter == null ? typeof(object) : _converter.GetPropertyValueType(this); + } - // if we have a converter, use the converter - if (_converter != null) - return _converter.IsValue(value, level); + /// + public bool? IsValue(object? value, PropertyValueLevel level) + { + if (!_initialized) + { + Initialize(); + } - // otherwise use the old magic null & string comparisons - return value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); + // if we have a converter, use the converter + if (_converter != null) + { + return _converter.IsValue(value, level); } - /// - public PropertyCacheLevel CacheLevel + // otherwise use the old magic null & string comparisons + return value != null && (!(value is string) || string.IsNullOrWhiteSpace((string)value) == false); + } + + /// + public PropertyCacheLevel CacheLevel + { + get { - get + if (!_initialized) { - if (!_initialized) Initialize(); - return _cacheLevel; + Initialize(); } + + return _cacheLevel; } + } - /// - public object? ConvertSourceToInter(IPublishedElement owner, object? source, bool preview) + /// + public object? ConvertSourceToInter(IPublishedElement owner, object? source, bool preview) + { + if (!_initialized) { - if (!_initialized) Initialize(); - - // use the converter if any, else just return the source value - return _converter != null - ? _converter.ConvertSourceToIntermediate(owner, this, source, preview) - : source; + Initialize(); } - /// - public object? ConvertInterToObject(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + // use the converter if any, else just return the source value + return _converter != null + ? _converter.ConvertSourceToIntermediate(owner, this, source, preview) + : source; + } + + /// + public object? ConvertInterToObject(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, + bool preview) + { + if (!_initialized) { - if (!_initialized) Initialize(); + Initialize(); + } + + // use the converter if any, else just return the inter value + return _converter != null + ? _converter.ConvertIntermediateToObject(owner, this, referenceCacheLevel, inter, preview) + : inter; + } - // use the converter if any, else just return the inter value - return _converter != null - ? _converter.ConvertIntermediateToObject(owner, this, referenceCacheLevel, inter, preview) - : inter; + /// + public object? ConvertInterToXPath(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, + bool preview) + { + if (!_initialized) + { + Initialize(); } - /// - public object? ConvertInterToXPath(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + // use the converter if any + if (_converter != null) { - if (!_initialized) Initialize(); + return _converter.ConvertIntermediateToXPath(owner, this, referenceCacheLevel, inter, preview); + } - // use the converter if any - if (_converter != null) - return _converter.ConvertIntermediateToXPath(owner, this, referenceCacheLevel, inter, preview); + // else just return the inter value as a string or an XPathNavigator + if (inter == null) + { + return null; + } - // else just return the inter value as a string or an XPathNavigator - if (inter == null) return null; - if (inter is XElement xElement) - return xElement.CreateNavigator(); - return inter.ToString()?.Trim(); + if (inter is XElement xElement) + { + return xElement.CreateNavigator(); } - /// - public Type ModelClrType + return inter.ToString()?.Trim(); + } + + /// + public Type ModelClrType + { + get { - get + if (!_initialized) { - if (!_initialized) Initialize(); - return _modelClrType!; + Initialize(); } + + return _modelClrType!; } + } - /// - public Type? ClrType + /// + public Type? ClrType + { + get { - get + if (!_initialized) { - if (!_initialized) Initialize(); - return _clrType ?? (_modelClrType is not null ? _clrType = _publishedModelFactory.MapModelType(_modelClrType) : null); + Initialize(); } - } - #endregion + return _clrType ?? (_modelClrType is not null + ? _clrType = _publishedModelFactory.MapModelType(_modelClrType) + : null); + } } + + #endregion } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedSearchResult.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedSearchResult.cs index edc6cd915024..d489b6678f8e 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedSearchResult.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedSearchResult.cs @@ -1,17 +1,16 @@ using System.Diagnostics; -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +[DebuggerDisplay("{Content?.Name} ({Score})")] +public class PublishedSearchResult { - [DebuggerDisplay("{Content?.Name} ({Score})")] - public class PublishedSearchResult + public PublishedSearchResult(IPublishedContent content, float score) { - public PublishedSearchResult(IPublishedContent content, float score) - { - Content = content; - Score = score; - } - - public IPublishedContent Content { get; } - public float Score { get; } + Content = content; + Score = score; } + + public IPublishedContent Content { get; } + public float Score { get; } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs index ed8acf27367a..2f319e0e5d49 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs @@ -1,296 +1,364 @@ -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Provides a default implementation for . +/// +public class PublishedValueFallback : IPublishedValueFallback { + private readonly ILocalizationService? _localizationService; + private readonly IVariationContextAccessor _variationContextAccessor; + /// - /// Provides a default implementation for . + /// Initializes a new instance of the class. /// - public class PublishedValueFallback : IPublishedValueFallback + public PublishedValueFallback(ServiceContext serviceContext, IVariationContextAccessor variationContextAccessor) { - private readonly ILocalizationService? _localizationService; - private readonly IVariationContextAccessor _variationContextAccessor; + _localizationService = serviceContext.LocalizationService; + _variationContextAccessor = variationContextAccessor; + } - /// - /// Initializes a new instance of the class. - /// - public PublishedValueFallback(ServiceContext serviceContext, IVariationContextAccessor variationContextAccessor) - { - _localizationService = serviceContext.LocalizationService; - _variationContextAccessor = variationContextAccessor; - } + /// + public bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, + object? defaultValue, out object? value) => + TryGetValue(property, culture, segment, fallback, defaultValue, out value); - /// - public bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, object? defaultValue, out object? value) - { - return TryGetValue(property, culture, segment, fallback, defaultValue, out value); - } + /// + public bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, + T? defaultValue, out T? value) + { + _variationContextAccessor.ContextualizeVariation(property.PropertyType.Variations, ref culture, ref segment); - /// - public bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, T? defaultValue, out T? value) + foreach (var f in fallback) { - _variationContextAccessor.ContextualizeVariation(property.PropertyType.Variations, ref culture, ref segment); - - foreach (var f in fallback) + switch (f) { - switch (f) - { - case Fallback.None: - continue; - case Fallback.DefaultValue: - value = defaultValue; + case Fallback.None: + continue; + case Fallback.DefaultValue: + value = defaultValue; + return true; + case Fallback.Language: + if (TryGetValueWithLanguageFallback(property, culture, segment, out value)) + { return true; - case Fallback.Language: - if (TryGetValueWithLanguageFallback(property, culture, segment, out value)) - return true; - break; - default: - throw NotSupportedFallbackMethod(f, "property"); - } + } + + break; + default: + throw NotSupportedFallbackMethod(f, "property"); } + } + + value = default; + return false; + } + + /// + public bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, + Fallback fallback, object? defaultValue, out object? value) => + TryGetValue(content, alias, culture, segment, fallback, defaultValue, out value); + /// + public bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, + Fallback fallback, T? defaultValue, out T? value) + { + IPublishedPropertyType propertyType = content.ContentType.GetPropertyType(alias); + if (propertyType == null) + { value = default; return false; } - /// - public bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, Fallback fallback, object? defaultValue, out object? value) - { - return TryGetValue(content, alias, culture, segment, fallback, defaultValue, out value); - } + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment); - /// - public bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, Fallback fallback, T? defaultValue, out T? value) + foreach (var f in fallback) { - var propertyType = content.ContentType.GetPropertyType(alias); - if (propertyType == null) + switch (f) { - value = default; - return false; + case Fallback.None: + continue; + case Fallback.DefaultValue: + value = defaultValue; + return true; + case Fallback.Language: + if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value)) + { + return true; + } + + break; + default: + throw NotSupportedFallbackMethod(f, "element"); } + } - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment); + value = default; + return false; + } - foreach (var f in fallback) - { - switch (f) - { - case Fallback.None: - continue; - case Fallback.DefaultValue: - value = defaultValue; - return true; - case Fallback.Language: - if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value)) - return true; - break; - default: - throw NotSupportedFallbackMethod(f, "element"); - } - } + /// + public bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, + Fallback fallback, object? defaultValue, out object? value, out IPublishedProperty? noValueProperty) => + TryGetValue(content, alias, culture, segment, fallback, defaultValue, out value, out noValueProperty); - value = default; - return false; - } + /// + public virtual bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, + Fallback fallback, T? defaultValue, out T? value, out IPublishedProperty? noValueProperty) + { + noValueProperty = default; - /// - public bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, Fallback fallback, object? defaultValue, out object? value, out IPublishedProperty? noValueProperty) + IPublishedPropertyType propertyType = content.ContentType.GetPropertyType(alias); + if (propertyType != null) { - return TryGetValue(content, alias, culture, segment, fallback, defaultValue, out value, out noValueProperty); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, + ref segment); + noValueProperty = content.GetProperty(alias); } - /// - public virtual bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, Fallback fallback, T? defaultValue, out T? value, out IPublishedProperty? noValueProperty) - { - noValueProperty = default; + // note: we don't support "recurse & language" which would walk up the tree, + // looking at languages at each level - should someone need it... they'll have + // to implement it. - var propertyType = content.ContentType.GetPropertyType(alias); - if (propertyType != null) + foreach (var f in fallback) + { + switch (f) { - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment); - noValueProperty = content.GetProperty(alias); - } + case Fallback.None: + continue; + case Fallback.DefaultValue: + value = defaultValue; + return true; + case Fallback.Language: + if (propertyType == null) + { + continue; + } - // note: we don't support "recurse & language" which would walk up the tree, - // looking at languages at each level - should someone need it... they'll have - // to implement it. + if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value)) + { + return true; + } - foreach (var f in fallback) - { - switch (f) - { - case Fallback.None: - continue; - case Fallback.DefaultValue: - value = defaultValue; + break; + case Fallback.Ancestors: + if (TryGetValueWithAncestorsFallback(content, alias, culture, segment, out value, + ref noValueProperty)) + { return true; - case Fallback.Language: - if (propertyType == null) - continue; - if (TryGetValueWithLanguageFallback(content, alias, culture, segment, out value)) - return true; - break; - case Fallback.Ancestors: - if (TryGetValueWithAncestorsFallback(content, alias, culture, segment, out value, ref noValueProperty)) - return true; - break; - default: - throw NotSupportedFallbackMethod(f, "content"); - } - } + } - value = default; - return false; + break; + default: + throw NotSupportedFallbackMethod(f, "content"); + } } - private NotSupportedException NotSupportedFallbackMethod(int fallback, string level) - { - return new NotSupportedException($"Fallback {GetType().Name} does not support fallback code '{fallback}' at {level} level."); - } + value = default; + return false; + } - // tries to get a value, recursing the tree - // because we recurse, content may not even have the a property with the specified alias (but only some ancestor) - // in case no value was found, noValueProperty contains the first property that was found (which does not have a value) - private bool TryGetValueWithAncestorsFallback(IPublishedContent? content, string alias, string? culture, string? segment, out T? value, ref IPublishedProperty? noValueProperty) + private NotSupportedException NotSupportedFallbackMethod(int fallback, string level) => + new NotSupportedException( + $"Fallback {GetType().Name} does not support fallback code '{fallback}' at {level} level."); + + // tries to get a value, recursing the tree + // because we recurse, content may not even have the a property with the specified alias (but only some ancestor) + // in case no value was found, noValueProperty contains the first property that was found (which does not have a value) + private bool TryGetValueWithAncestorsFallback(IPublishedContent? content, string alias, string? culture, + string? segment, out T? value, ref IPublishedProperty? noValueProperty) + { + IPublishedProperty? property; // if we are here, content's property has no value + do { - IPublishedProperty? property; // if we are here, content's property has no value - do + content = content?.Parent; + + IPublishedPropertyType propertyType = content?.ContentType.GetPropertyType(alias); + + if (propertyType != null && content is not null) { - content = content?.Parent; - - var propertyType = content?.ContentType.GetPropertyType(alias); - - if (propertyType != null && content is not null) - { - culture = null; - segment = null; - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment); - } - - property = content?.GetProperty(alias); - if (property != null && noValueProperty == null) - { - noValueProperty = property; - } + culture = null; + segment = null; + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, + ref segment); } - while (content != null && (property == null || property.HasValue(culture, segment) == false)); - // if we found a content with the property having a value, return that property value - if (property != null && property.HasValue(culture, segment)) + property = content?.GetProperty(alias); + if (property != null && noValueProperty == null) { - value = property.Value(this, culture, segment); - return true; + noValueProperty = property; } + } while (content != null && (property == null || property.HasValue(culture, segment) == false)); - value = default; - return false; + // if we found a content with the property having a value, return that property value + if (property != null && property.HasValue(culture, segment)) + { + value = property.Value(this, culture, segment); + return true; } - // tries to get a value, falling back onto other languages - private bool TryGetValueWithLanguageFallback(IPublishedProperty property, string? culture, string? segment, out T? value) + value = default; + return false; + } + + // tries to get a value, falling back onto other languages + private bool TryGetValueWithLanguageFallback(IPublishedProperty property, string? culture, string? segment, + out T? value) + { + value = default; + + if (culture.IsNullOrWhiteSpace()) { - value = default; + return false; + } - if (culture.IsNullOrWhiteSpace()) return false; + var visited = new HashSet(); - var visited = new HashSet(); + ILanguage language = culture is not null ? _localizationService?.GetLanguageByIsoCode(culture) : null; + if (language == null) + { + return false; + } - var language = culture is not null ? _localizationService?.GetLanguageByIsoCode(culture) : null; - if (language == null) return false; + while (true) + { + if (language.FallbackLanguageId == null) + { + return false; + } - while (true) + var language2Id = language.FallbackLanguageId.Value; + if (visited.Contains(language2Id)) { - if (language.FallbackLanguageId == null) return false; + return false; + } - var language2Id = language.FallbackLanguageId.Value; - if (visited.Contains(language2Id)) return false; - visited.Add(language2Id); + visited.Add(language2Id); - var language2 = _localizationService?.GetLanguageById(language2Id); - if (language2 == null) return false; - var culture2 = language2.IsoCode; + ILanguage language2 = _localizationService?.GetLanguageById(language2Id); + if (language2 == null) + { + return false; + } - if (property.HasValue(culture2, segment)) - { - value = property.Value(this, culture2, segment); - return true; - } + var culture2 = language2.IsoCode; - language = language2; + if (property.HasValue(culture2, segment)) + { + value = property.Value(this, culture2, segment); + return true; } + + language = language2; } + } + + // tries to get a value, falling back onto other languages + private bool TryGetValueWithLanguageFallback(IPublishedElement content, string alias, string? culture, + string? segment, out T? value) + { + value = default; - // tries to get a value, falling back onto other languages - private bool TryGetValueWithLanguageFallback(IPublishedElement content, string alias, string? culture, string? segment, out T? value) + if (culture.IsNullOrWhiteSpace()) { - value = default; + return false; + } - if (culture.IsNullOrWhiteSpace()) return false; + var visited = new HashSet(); - var visited = new HashSet(); + ILanguage language = culture is not null ? _localizationService?.GetLanguageByIsoCode(culture) : null; + if (language == null) + { + return false; + } - var language = culture is not null ? _localizationService?.GetLanguageByIsoCode(culture) : null; - if (language == null) return false; + while (true) + { + if (language.FallbackLanguageId == null) + { + return false; + } - while (true) + var language2Id = language.FallbackLanguageId.Value; + if (visited.Contains(language2Id)) { - if (language.FallbackLanguageId == null) return false; + return false; + } - var language2Id = language.FallbackLanguageId.Value; - if (visited.Contains(language2Id)) return false; - visited.Add(language2Id); + visited.Add(language2Id); - var language2 = _localizationService?.GetLanguageById(language2Id); - if (language2 == null) return false; - var culture2 = language2.IsoCode; + ILanguage language2 = _localizationService?.GetLanguageById(language2Id); + if (language2 == null) + { + return false; + } - if (content.HasValue(alias, culture2, segment)) - { - value = content.Value(this, alias, culture2, segment); - return true; - } + var culture2 = language2.IsoCode; - language = language2; + if (content.HasValue(alias, culture2, segment)) + { + value = content.Value(this, alias, culture2, segment); + return true; } + + language = language2; } + } + + // tries to get a value, falling back onto other languages + private bool TryGetValueWithLanguageFallback(IPublishedContent content, string alias, string? culture, + string? segment, out T? value) + { + value = default; - // tries to get a value, falling back onto other languages - private bool TryGetValueWithLanguageFallback(IPublishedContent content, string alias, string? culture, string? segment, out T? value) + if (culture.IsNullOrWhiteSpace()) { - value = default; + return false; + } - if (culture.IsNullOrWhiteSpace()) return false; + var visited = new HashSet(); - var visited = new HashSet(); + // TODO: _localizationService.GetXxx() is expensive, it deep clones objects + // we want _localizationService.GetReadOnlyXxx() returning IReadOnlyLanguage which cannot be saved back = no need to clone - // TODO: _localizationService.GetXxx() is expensive, it deep clones objects - // we want _localizationService.GetReadOnlyXxx() returning IReadOnlyLanguage which cannot be saved back = no need to clone + ILanguage language = culture is not null ? _localizationService?.GetLanguageByIsoCode(culture) : null; + if (language == null) + { + return false; + } - var language = culture is not null ? _localizationService?.GetLanguageByIsoCode(culture) : null; - if (language == null) return false; + while (true) + { + if (language.FallbackLanguageId == null) + { + return false; + } - while (true) + var language2Id = language.FallbackLanguageId.Value; + if (visited.Contains(language2Id)) { - if (language.FallbackLanguageId == null) return false; + return false; + } - var language2Id = language.FallbackLanguageId.Value; - if (visited.Contains(language2Id)) return false; - visited.Add(language2Id); + visited.Add(language2Id); - var language2 = _localizationService?.GetLanguageById(language2Id); - if (language2 == null) return false; - var culture2 = language2.IsoCode; + ILanguage language2 = _localizationService?.GetLanguageById(language2Id); + if (language2 == null) + { + return false; + } - if (content.HasValue(alias, culture2, segment)) - { - value = content.Value(this, alias, culture2, segment); - return true; - } + var culture2 = language2.IsoCode; - language = language2; + if (content.HasValue(alias, culture2, segment)) + { + value = content.Value(this, alias, culture2, segment); + return true; } + + language = language2; } } } diff --git a/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs index 2ae0ce6c1dbf..4de6d3a4d0a3 100644 --- a/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs +++ b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs @@ -1,54 +1,62 @@ -using System; -using Umbraco.Cms.Core.PropertyEditors; - -namespace Umbraco.Cms.Core.Models.PublishedContent +using Umbraco.Cms.Core.PropertyEditors; + +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// +/// Represents a published property that has a unique invariant-neutral value +/// and caches conversion results locally. +/// +/// +/// +/// Conversions results are stored within the property and will not +/// be refreshed, so this class is not suitable for cached properties. +/// +/// +/// Does not support variations: the ctor throws if the property type +/// supports variations. +/// +/// +public class RawValueProperty : PublishedPropertyBase { - /// - /// - /// Represents a published property that has a unique invariant-neutral value - /// and caches conversion results locally. - /// - /// - /// Conversions results are stored within the property and will not - /// be refreshed, so this class is not suitable for cached properties. - /// Does not support variations: the ctor throws if the property type - /// supports variations. - /// - public class RawValueProperty : PublishedPropertyBase - { - private readonly object _sourceValue; //the value in the db - private readonly Lazy _objectValue; - private readonly Lazy _xpathValue; - - // RawValueProperty does not (yet?) support variants, - // only manages the current "default" value + private readonly Lazy _objectValue; + private readonly object _sourceValue; //the value in the db + private readonly Lazy _xpathValue; - public override object? GetSourceValue(string? culture = null, string? segment = null) - => string.IsNullOrEmpty(culture) & string.IsNullOrEmpty(segment) ? _sourceValue : null; - - public override bool HasValue(string? culture = null, string? segment = null) + public RawValueProperty(IPublishedPropertyType propertyType, IPublishedElement content, object sourceValue, + bool isPreviewing = false) + : base(propertyType, PropertyCacheLevel.Unknown) // cache level is ignored + { + if (propertyType.Variations != ContentVariation.Nothing) { - var sourceValue = GetSourceValue(culture, segment); - return sourceValue is string s ? !string.IsNullOrWhiteSpace(s) : sourceValue != null; + throw new ArgumentException("Property types with variations are not supported here.", nameof(propertyType)); } - public override object? GetValue(string? culture = null, string? segment = null) - => string.IsNullOrEmpty(culture) & string.IsNullOrEmpty(segment) ? _objectValue.Value : null; + _sourceValue = sourceValue; - public override object? GetXPathValue(string? culture = null, string? segment = null) - => string.IsNullOrEmpty(culture) & string.IsNullOrEmpty(segment) ? _xpathValue.Value : null; + var interValue = + new Lazy(() => PropertyType.ConvertSourceToInter(content, _sourceValue, isPreviewing)); + _objectValue = new Lazy(() => + PropertyType.ConvertInterToObject(content, PropertyCacheLevel.Unknown, interValue?.Value, isPreviewing)); + _xpathValue = new Lazy(() => + PropertyType.ConvertInterToXPath(content, PropertyCacheLevel.Unknown, interValue?.Value, isPreviewing)); + } - public RawValueProperty(IPublishedPropertyType propertyType, IPublishedElement content, object sourceValue, bool isPreviewing = false) - : base(propertyType, PropertyCacheLevel.Unknown) // cache level is ignored - { - if (propertyType.Variations != ContentVariation.Nothing) - throw new ArgumentException("Property types with variations are not supported here.", nameof(propertyType)); + // RawValueProperty does not (yet?) support variants, + // only manages the current "default" value - _sourceValue = sourceValue; + public override object? GetSourceValue(string? culture = null, string? segment = null) + => string.IsNullOrEmpty(culture) & string.IsNullOrEmpty(segment) ? _sourceValue : null; - var interValue = new Lazy(() => PropertyType.ConvertSourceToInter(content, _sourceValue, isPreviewing)); - _objectValue = new Lazy(() => PropertyType.ConvertInterToObject(content, PropertyCacheLevel.Unknown, interValue?.Value, isPreviewing)); - _xpathValue = new Lazy(() => PropertyType.ConvertInterToXPath(content, PropertyCacheLevel.Unknown, interValue?.Value, isPreviewing)); - } + public override bool HasValue(string? culture = null, string? segment = null) + { + var sourceValue = GetSourceValue(culture, segment); + return sourceValue is string s ? !string.IsNullOrWhiteSpace(s) : sourceValue != null; } + + public override object? GetValue(string? culture = null, string? segment = null) + => string.IsNullOrEmpty(culture) & string.IsNullOrEmpty(segment) ? _objectValue.Value : null; + + public override object? GetXPathValue(string? culture = null, string? segment = null) + => string.IsNullOrEmpty(culture) & string.IsNullOrEmpty(segment) ? _xpathValue.Value : null; } diff --git a/src/Umbraco.Core/Models/PublishedContent/ThreadCultureVariationContextAccessor.cs b/src/Umbraco.Core/Models/PublishedContent/ThreadCultureVariationContextAccessor.cs index a9d06e521f8f..130f7c62b39a 100644 --- a/src/Umbraco.Core/Models/PublishedContent/ThreadCultureVariationContextAccessor.cs +++ b/src/Umbraco.Core/Models/PublishedContent/ThreadCultureVariationContextAccessor.cs @@ -1,23 +1,20 @@ -using System; -using System.Collections.Concurrent; -using System.Threading; +using System.Collections.Concurrent; -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Provides a CurrentUICulture-based implementation of . +/// +/// +/// This accessor does not support segments. There is no need to set the current context. +/// +public class ThreadCultureVariationContextAccessor : IVariationContextAccessor { - /// - /// Provides a CurrentUICulture-based implementation of . - /// - /// - /// This accessor does not support segments. There is no need to set the current context. - /// - public class ThreadCultureVariationContextAccessor : IVariationContextAccessor - { - private readonly ConcurrentDictionary _contexts = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _contexts = new(); - public VariationContext? VariationContext - { - get => _contexts.GetOrAdd(Thread.CurrentThread.CurrentUICulture.Name, culture => new VariationContext(culture)); - set => throw new NotSupportedException(); - } + public VariationContext? VariationContext + { + get => _contexts.GetOrAdd(Thread.CurrentThread.CurrentUICulture.Name, culture => new VariationContext(culture)); + set => throw new NotSupportedException(); } } diff --git a/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs b/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs index 8e24f25332c6..f25ce4bfe1ba 100644 --- a/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs +++ b/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs @@ -1,28 +1,27 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Specifies the type of URLs that the URL provider should produce, Auto is the default. +/// +public enum UrlMode { /// - /// Specifies the type of URLs that the URL provider should produce, Auto is the default. + /// Indicates that the URL provider should do what it has been configured to do. /// - public enum UrlMode - { - /// - /// Indicates that the URL provider should do what it has been configured to do. - /// - Default = 0, + Default = 0, - /// - /// Indicates that the URL provider should produce relative URLs exclusively. - /// - Relative, + /// + /// Indicates that the URL provider should produce relative URLs exclusively. + /// + Relative, - /// - /// Indicates that the URL provider should produce absolute URLs exclusively. - /// - Absolute, + /// + /// Indicates that the URL provider should produce absolute URLs exclusively. + /// + Absolute, - /// - /// Indicates that the URL provider should determine automatically whether to return relative or absolute URLs. - /// - Auto - } + /// + /// Indicates that the URL provider should determine automatically whether to return relative or absolute URLs. + /// + Auto } diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs index 9b8ae302457f..3ca99e1e3ae0 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs @@ -1,34 +1,33 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent; + +/// +/// Represents the variation context. +/// +public class VariationContext { /// - /// Represents the variation context. + /// Initializes a new instance of the class. /// - public class VariationContext + public VariationContext(string? culture = null, string? segment = null) { - /// - /// Initializes a new instance of the class. - /// - public VariationContext(string? culture = null, string? segment = null) - { - Culture = culture ?? ""; // cannot be null, default to invariant - Segment = segment ?? ""; // cannot be null, default to neutral - } + Culture = culture ?? ""; // cannot be null, default to invariant + Segment = segment ?? ""; // cannot be null, default to neutral + } - /// - /// Gets the culture. - /// - public string Culture { get; } + /// + /// Gets the culture. + /// + public string Culture { get; } - /// - /// Gets the segment. - /// - public string Segment { get; } + /// + /// Gets the segment. + /// + public string Segment { get; } - /// - /// Gets the segment for the content item - /// - /// - /// - public virtual string GetSegment(int contentId) => Segment; - } + /// + /// Gets the segment for the content item + /// + /// + /// + public virtual string GetSegment(int contentId) => Segment; } diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs index 4a986597bd6e..dec81b10a750 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs @@ -4,39 +4,44 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class VariationContextAccessorExtensions { - public static class VariationContextAccessorExtensions - { - public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, ref string? culture, ref string? segment) - => variationContextAccessor.ContextualizeVariation(variations, null, ref culture, ref segment); + public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, + ContentVariation variations, ref string? culture, ref string? segment) + => variationContextAccessor.ContextualizeVariation(variations, null, ref culture, ref segment); - public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int contentId, ref string? culture, ref string? segment) - => variationContextAccessor.ContextualizeVariation(variations, (int?)contentId, ref culture, ref segment); + public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, + ContentVariation variations, int contentId, ref string? culture, ref string? segment) + => variationContextAccessor.ContextualizeVariation(variations, (int?)contentId, ref culture, ref segment); - private static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int? contentId, ref string? culture, ref string? segment) + private static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, + ContentVariation variations, int? contentId, ref string? culture, ref string? segment) + { + if (culture != null && segment != null) { - if (culture != null && segment != null) return; + return; + } - // use context values - var publishedVariationContext = variationContextAccessor?.VariationContext; - if (culture == null) + // use context values + VariationContext publishedVariationContext = variationContextAccessor?.VariationContext; + if (culture == null) + { + culture = variations.VariesByCulture() ? publishedVariationContext?.Culture : ""; + } + + if (segment == null) + { + if (variations.VariesBySegment()) { - culture = variations.VariesByCulture() ? publishedVariationContext?.Culture : ""; + segment = contentId == null + ? publishedVariationContext?.Segment + : publishedVariationContext?.GetSegment(contentId.Value); } - - if (segment == null) + else { - if (variations.VariesBySegment()) - { - segment = contentId == null - ? publishedVariationContext?.Segment - : publishedVariationContext?.GetSegment(contentId.Value); - } - else - { - segment = ""; - } + segment = ""; } } } diff --git a/src/Umbraco.Core/Models/PublishedState.cs b/src/Umbraco.Core/Models/PublishedState.cs index 87c106e11ec5..33668b57035e 100644 --- a/src/Umbraco.Core/Models/PublishedState.cs +++ b/src/Umbraco.Core/Models/PublishedState.cs @@ -1,60 +1,69 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// The states of a content item. +/// +public enum PublishedState { + // versions management in repo: + // + // - published = the content is published + // repo: saving draft values + // update current version (draft) values + // + // - unpublished = the content is not published + // repo: saving draft values + // update current version (draft) values + // + // - publishing = the content is being published (transitory) + // if currently published: + // delete all draft values from current version, not current anymore + // create new version with published+draft values + // + // - unpublishing = the content is being unpublished (transitory) + // if currently published (just in case): + // delete all draft values from current version, not current anymore + // create new version with published+draft values (should be managed by service) + + // when a content item is loaded, its state is one of those two: /// - /// The states of a content item. + /// The content item is published. /// - public enum PublishedState - { - // versions management in repo: - // - // - published = the content is published - // repo: saving draft values - // update current version (draft) values - // - // - unpublished = the content is not published - // repo: saving draft values - // update current version (draft) values - // - // - publishing = the content is being published (transitory) - // if currently published: - // delete all draft values from current version, not current anymore - // create new version with published+draft values - // - // - unpublishing = the content is being unpublished (transitory) - // if currently published (just in case): - // delete all draft values from current version, not current anymore - // create new version with published+draft values (should be managed by service) - - // when a content item is loaded, its state is one of those two: + Published, + // also: handled over to repo to save draft values for a published content - /// - /// The content item is published. - /// - Published, - // also: handled over to repo to save draft values for a published content - - /// - /// The content item is not published. - /// - Unpublished, - // also: handled over to repo to save draft values for an unpublished content - - // when it is handled over to the repository, its state can also be one of those: + /// + /// The content item is not published. + /// + Unpublished, + // also: handled over to repo to save draft values for an unpublished content - /// - /// The version is being saved, in order to publish the content. - /// - /// The Publishing state is transitional. Once the version - /// is saved, its state changes to Published. - Publishing, + // when it is handled over to the repository, its state can also be one of those: - /// - /// The version is being saved, in order to unpublish the content. - /// - /// The Unpublishing state is transitional. Once the version - /// is saved, its state changes to Unpublished. - Unpublishing + /// + /// The version is being saved, in order to publish the content. + /// + /// + /// The + /// Publishing + /// state is transitional. Once the version + /// is saved, its state changes to + /// Published + /// . + /// + Publishing, - } + /// + /// The version is being saved, in order to unpublish the content. + /// + /// + /// The + /// Unpublishing + /// state is transitional. Once the version + /// is saved, its state changes to + /// Unpublished + /// . + /// + Unpublishing } diff --git a/src/Umbraco.Core/Models/Range.cs b/src/Umbraco.Core/Models/Range.cs index 9c5da2087e50..7cf035c7c3fd 100644 --- a/src/Umbraco.Core/Models/Range.cs +++ b/src/Umbraco.Core/Models/Range.cs @@ -1,130 +1,144 @@ -using System; -using System.Globalization; +using System.Globalization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a range with a minimum and maximum value. +/// +/// The type of the minimum and maximum values. +/// +public class Range : IEquatable> + where T : IComparable { /// - /// Represents a range with a minimum and maximum value. + /// Gets or sets the minimum value. /// - /// The type of the minimum and maximum values. - /// - public class Range : IEquatable> - where T : IComparable - { - /// - /// Gets or sets the minimum value. - /// - /// - /// The minimum value. - /// - public T? Minimum { get; set; } + /// + /// The minimum value. + /// + public T? Minimum { get; set; } - /// - /// Gets or sets the maximum value. - /// - /// - /// The maximum value. - /// - public T? Maximum { get; set; } + /// + /// Gets or sets the maximum value. + /// + /// + /// The maximum value. + /// + public T? Maximum { get; set; } - /// - /// Returns a that represents this instance. - /// - /// - /// A that represents this instance. - /// - public override string ToString() => this.ToString("{0},{1}", CultureInfo.InvariantCulture); + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// An object to compare with this object. + /// + /// if the current object is equal to the parameter; otherwise, + /// . + /// + public bool Equals(Range? other) => other != null && Equals(other.Minimum, other.Maximum); - /// - /// Returns a that represents this instance. - /// - /// A composite format string for a single value (minimum and maximum are equal). Use {0} for the minimum and {1} for the maximum value. - /// A composite format string for the range values. Use {0} for the minimum and {1} for the maximum value. - /// An object that supplies culture-specific formatting information. - /// - /// A that represents this instance. - /// - public string ToString(string format, string formatRange, IFormatProvider? provider = null) => this.ToString(this.Minimum?.CompareTo(this.Maximum) == 0 ? format : formatRange, provider); + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() => ToString("{0},{1}", CultureInfo.InvariantCulture); - /// - /// Returns a that represents this instance. - /// - /// A composite format string for the range values. Use {0} for the minimum and {1} for the maximum value. - /// An object that supplies culture-specific formatting information. - /// - /// A that represents this instance. - /// - public string ToString(string format, IFormatProvider? provider = null) => string.Format(provider, format, this.Minimum, this.Maximum); + /// + /// Returns a that represents this instance. + /// + /// + /// A composite format string for a single value (minimum and maximum are equal). Use {0} for the + /// minimum and {1} for the maximum value. + /// + /// + /// A composite format string for the range values. Use {0} for the minimum and {1} for the + /// maximum value. + /// + /// An object that supplies culture-specific formatting information. + /// + /// A that represents this instance. + /// + public string ToString(string format, string formatRange, IFormatProvider? provider = null) => + ToString(Minimum?.CompareTo(Maximum) == 0 ? format : formatRange, provider); - /// - /// Determines whether this range is valid (the minimum value is lower than or equal to the maximum value). - /// - /// - /// true if this range is valid; otherwise, false. - /// - public bool IsValid() => this.Minimum?.CompareTo(this.Maximum) <= 0; + /// + /// Returns a that represents this instance. + /// + /// + /// A composite format string for the range values. Use {0} for the minimum and {1} for the maximum + /// value. + /// + /// An object that supplies culture-specific formatting information. + /// + /// A that represents this instance. + /// + public string ToString(string format, IFormatProvider? provider = null) => + string.Format(provider, format, Minimum, Maximum); - /// - /// Determines whether this range contains the specified value. - /// - /// The value. - /// - /// true if this range contains the specified value; otherwise, false. - /// - public bool ContainsValue(T? value) => this.Minimum?.CompareTo(value) <= 0 && value?.CompareTo(this.Maximum) <= 0; + /// + /// Determines whether this range is valid (the minimum value is lower than or equal to the maximum value). + /// + /// + /// true if this range is valid; otherwise, false. + /// + public bool IsValid() => Minimum?.CompareTo(Maximum) <= 0; - /// - /// Determines whether this range is inside the specified range. - /// - /// The range. - /// - /// true if this range is inside the specified range; otherwise, false. - /// - public bool IsInsideRange(Range range) => this.IsValid() && range.IsValid() && range.ContainsValue(this.Minimum) && range.ContainsValue(this.Maximum); + /// + /// Determines whether this range contains the specified value. + /// + /// The value. + /// + /// true if this range contains the specified value; otherwise, false. + /// + public bool ContainsValue(T? value) => Minimum?.CompareTo(value) <= 0 && value?.CompareTo(Maximum) <= 0; - /// - /// Determines whether this range contains the specified range. - /// - /// The range. - /// - /// true if this range contains the specified range; otherwise, false. - /// - public bool ContainsRange(Range range) => this.IsValid() && range.IsValid() && this.ContainsValue(range.Minimum) && this.ContainsValue(range.Maximum); + /// + /// Determines whether this range is inside the specified range. + /// + /// The range. + /// + /// true if this range is inside the specified range; otherwise, false. + /// + public bool IsInsideRange(Range range) => + IsValid() && range.IsValid() && range.ContainsValue(Minimum) && range.ContainsValue(Maximum); - /// - /// Determines whether the specified , is equal to this instance. - /// - /// The to compare with this instance. - /// - /// true if the specified is equal to this instance; otherwise, false. - /// - public override bool Equals(object? obj) => obj is Range other && this.Equals(other); + /// + /// Determines whether this range contains the specified range. + /// + /// The range. + /// + /// true if this range contains the specified range; otherwise, false. + /// + public bool ContainsRange(Range range) => + IsValid() && range.IsValid() && ContainsValue(range.Minimum) && ContainsValue(range.Maximum); - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// An object to compare with this object. - /// - /// if the current object is equal to the parameter; otherwise, . - /// - public bool Equals(Range? other) => other != null && this.Equals(other.Minimum, other.Maximum); + /// + /// Determines whether the specified , is equal to this instance. + /// + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; otherwise, false. + /// + public override bool Equals(object? obj) => obj is Range other && Equals(other); - /// - /// Determines whether the specified and values are equal to this instance values. - /// - /// The minimum value. - /// The maximum value. - /// - /// true if the specified and values are equal to this instance values; otherwise, false. - /// - public bool Equals(T? minimum, T? maximum) => this.Minimum?.CompareTo(minimum) == 0 && this.Maximum?.CompareTo(maximum) == 0; + /// + /// Determines whether the specified and values are equal to + /// this instance values. + /// + /// The minimum value. + /// The maximum value. + /// + /// true if the specified and values are equal to this + /// instance values; otherwise, false. + /// + public bool Equals(T? minimum, T? maximum) => Minimum?.CompareTo(minimum) == 0 && Maximum?.CompareTo(maximum) == 0; - /// - /// Returns a hash code for this instance. - /// - /// - /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - /// - public override int GetHashCode() => (this.Minimum, this.Maximum).GetHashCode(); - } + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode() => (Minimum, Maximum).GetHashCode(); } diff --git a/src/Umbraco.Core/Models/ReadOnlyContentBaseAdapter.cs b/src/Umbraco.Core/Models/ReadOnlyContentBaseAdapter.cs index cbb1e51a3e84..795bf517d839 100644 --- a/src/Umbraco.Core/Models/ReadOnlyContentBaseAdapter.cs +++ b/src/Umbraco.Core/Models/ReadOnlyContentBaseAdapter.cs @@ -1,42 +1,37 @@ -using System; +namespace Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Models +public struct ReadOnlyContentBaseAdapter : IReadOnlyContentBase { - public struct ReadOnlyContentBaseAdapter : IReadOnlyContentBase - { - private readonly IContentBase _content; + private readonly IContentBase _content; - private ReadOnlyContentBaseAdapter(IContentBase content) - { - _content = content ?? throw new ArgumentNullException(nameof(content)); - } + private ReadOnlyContentBaseAdapter(IContentBase content) => + _content = content ?? throw new ArgumentNullException(nameof(content)); - public static ReadOnlyContentBaseAdapter Create(IContentBase content) => new ReadOnlyContentBaseAdapter(content); + public static ReadOnlyContentBaseAdapter Create(IContentBase content) => new(content); - public int Id => _content.Id; + public int Id => _content.Id; - public Guid Key => _content.Key; + public Guid Key => _content.Key; - public DateTime CreateDate => _content.CreateDate; + public DateTime CreateDate => _content.CreateDate; - public DateTime UpdateDate => _content.UpdateDate; + public DateTime UpdateDate => _content.UpdateDate; - public string? Name => _content.Name; + public string? Name => _content.Name; - public int CreatorId => _content.CreatorId; + public int CreatorId => _content.CreatorId; - public int ParentId => _content.ParentId; + public int ParentId => _content.ParentId; - public int Level => _content.Level; + public int Level => _content.Level; - public string? Path => _content.Path; + public string? Path => _content.Path; - public int SortOrder => _content.SortOrder; + public int SortOrder => _content.SortOrder; - public int ContentTypeId => _content.ContentTypeId; + public int ContentTypeId => _content.ContentTypeId; - public int WriterId => _content.WriterId; + public int WriterId => _content.WriterId; - public int VersionId => _content.VersionId; - } + public int VersionId => _content.VersionId; } diff --git a/src/Umbraco.Core/Models/ReadOnlyRelation.cs b/src/Umbraco.Core/Models/ReadOnlyRelation.cs index a57a5ba7e1b6..765a84d265c2 100644 --- a/src/Umbraco.Core/Models/ReadOnlyRelation.cs +++ b/src/Umbraco.Core/Models/ReadOnlyRelation.cs @@ -1,35 +1,32 @@ -using System; +namespace Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Models +/// +/// A read only relation. Can be used to bulk save witch performs better than the normal save operation, +/// but do not populate Ids back to the model +/// +public class ReadOnlyRelation { - /// - /// A read only relation. Can be used to bulk save witch performs better than the normal save operation, - /// but do not populate Ids back to the model - /// - public class ReadOnlyRelation + public ReadOnlyRelation(int id, int parentId, int childId, int relationTypeId, DateTime createDate, string comment) { - public ReadOnlyRelation(int id, int parentId, int childId, int relationTypeId, DateTime createDate, string comment) - { - Id = id; - ParentId = parentId; - ChildId = childId; - RelationTypeId = relationTypeId; - CreateDate = createDate; - Comment = comment; - } - - public ReadOnlyRelation(int parentId, int childId, int relationTypeId): this(0, parentId, childId, relationTypeId, DateTime.Now, string.Empty) - { + Id = id; + ParentId = parentId; + ChildId = childId; + RelationTypeId = relationTypeId; + CreateDate = createDate; + Comment = comment; + } - } + public ReadOnlyRelation(int parentId, int childId, int relationTypeId) : this(0, parentId, childId, relationTypeId, + DateTime.Now, string.Empty) + { + } - public int Id { get; } - public int ParentId { get; } - public int ChildId { get; } - public int RelationTypeId { get; } - public DateTime CreateDate { get; } - public string Comment { get; } + public int Id { get; } + public int ParentId { get; } + public int ChildId { get; } + public int RelationTypeId { get; } + public DateTime CreateDate { get; } + public string Comment { get; } - public bool HasIdentity => Id != 0; - } + public bool HasIdentity => Id != 0; } diff --git a/src/Umbraco.Core/Models/RedirectUrl.cs b/src/Umbraco.Core/Models/RedirectUrl.cs index d4acc0b66d64..496f9e85b070 100644 --- a/src/Umbraco.Core/Models/RedirectUrl.cs +++ b/src/Umbraco.Core/Models/RedirectUrl.cs @@ -1,64 +1,62 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Implements . +/// +[Serializable] +[DataContract(IsReference = true)] +public class RedirectUrl : EntityBase, IRedirectUrl { + private int _contentId; + private Guid _contentKey; + private DateTime _createDateUtc; + private string? _culture; + private string _url; + /// - /// Implements . + /// Initializes a new instance of the class. /// - [Serializable] - [DataContract(IsReference = true)] - public class RedirectUrl : EntityBase, IRedirectUrl + public RedirectUrl() { - /// - /// Initializes a new instance of the class. - /// - public RedirectUrl() - { - CreateDateUtc = DateTime.UtcNow; - _url = string.Empty; - } - - private int _contentId; - private Guid _contentKey; - private DateTime _createDateUtc; - private string? _culture; - private string _url; + CreateDateUtc = DateTime.UtcNow; + _url = string.Empty; + } - /// - public int ContentId - { - get => _contentId; - set => SetPropertyValueAndDetectChanges(value, ref _contentId, nameof(ContentId)); - } + /// + public int ContentId + { + get => _contentId; + set => SetPropertyValueAndDetectChanges(value, ref _contentId, nameof(ContentId)); + } - /// - public Guid ContentKey - { - get => _contentKey; - set => SetPropertyValueAndDetectChanges(value, ref _contentKey, nameof(ContentKey)); - } + /// + public Guid ContentKey + { + get => _contentKey; + set => SetPropertyValueAndDetectChanges(value, ref _contentKey, nameof(ContentKey)); + } - /// - public DateTime CreateDateUtc - { - get => _createDateUtc; - set => SetPropertyValueAndDetectChanges(value, ref _createDateUtc, nameof(CreateDateUtc)); - } + /// + public DateTime CreateDateUtc + { + get => _createDateUtc; + set => SetPropertyValueAndDetectChanges(value, ref _createDateUtc, nameof(CreateDateUtc)); + } - /// - public string? Culture - { - get => _culture; - set => SetPropertyValueAndDetectChanges(value, ref _culture, nameof(Culture)); - } + /// + public string? Culture + { + get => _culture; + set => SetPropertyValueAndDetectChanges(value, ref _culture, nameof(Culture)); + } - /// - public string Url - { - get => _url; - set => SetPropertyValueAndDetectChanges(value, ref _url!, nameof(Url)); - } + /// + public string Url + { + get => _url; + set => SetPropertyValueAndDetectChanges(value, ref _url!, nameof(Url)); } } diff --git a/src/Umbraco.Core/Models/Relation.cs b/src/Umbraco.Core/Models/Relation.cs index 54227db9108f..a7eee839e8e3 100644 --- a/src/Umbraco.Core/Models/Relation.cs +++ b/src/Umbraco.Core/Models/Relation.cs @@ -1,103 +1,101 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a Relation between two items +/// +[Serializable] +[DataContract(IsReference = true)] +public class Relation : EntityBase, IRelation { + private int _childId; + + private string? _comment; + + //NOTE: The datetime column from umbracoRelation is set on CreateDate on the Entity + private int _parentId; + private IRelationType _relationType; + /// - /// Represents a Relation between two items + /// Constructor for constructing the entity to be created /// - [Serializable] - [DataContract(IsReference = true)] - public class Relation : EntityBase, IRelation + /// + /// + /// + public Relation(int parentId, int childId, IRelationType relationType) { - //NOTE: The datetime column from umbracoRelation is set on CreateDate on the Entity - private int _parentId; - private int _childId; - private IRelationType _relationType; - private string? _comment; - - /// - /// Constructor for constructing the entity to be created - /// - /// - /// - /// - public Relation(int parentId, int childId, IRelationType relationType) - { - _parentId = parentId; - _childId = childId; - _relationType = relationType; - } - - /// - /// Constructor for reconstructing the entity from the data source - /// - /// - /// - /// - /// - /// - public Relation(int parentId, int childId, Guid parentObjectType, Guid childObjectType, IRelationType relationType) - { - _parentId = parentId; - _childId = childId; - _relationType = relationType; - ParentObjectType = parentObjectType; - ChildObjectType = childObjectType; - } + _parentId = parentId; + _childId = childId; + _relationType = relationType; + } + /// + /// Constructor for reconstructing the entity from the data source + /// + /// + /// + /// + /// + /// + public Relation(int parentId, int childId, Guid parentObjectType, Guid childObjectType, IRelationType relationType) + { + _parentId = parentId; + _childId = childId; + _relationType = relationType; + ParentObjectType = parentObjectType; + ChildObjectType = childObjectType; + } - /// - /// Gets or sets the Parent Id of the Relation (Source) - /// - [DataMember] - public int ParentId - { - get => _parentId; - set => SetPropertyValueAndDetectChanges(value, ref _parentId, nameof(ParentId)); - } - [DataMember] - public Guid ParentObjectType { get; set; } + /// + /// Gets or sets the Parent Id of the Relation (Source) + /// + [DataMember] + public int ParentId + { + get => _parentId; + set => SetPropertyValueAndDetectChanges(value, ref _parentId, nameof(ParentId)); + } - /// - /// Gets or sets the Child Id of the Relation (Destination) - /// - [DataMember] - public int ChildId - { - get => _childId; - set => SetPropertyValueAndDetectChanges(value, ref _childId, nameof(ChildId)); - } + [DataMember] public Guid ParentObjectType { get; set; } - [DataMember] - public Guid ChildObjectType { get; set; } + /// + /// Gets or sets the Child Id of the Relation (Destination) + /// + [DataMember] + public int ChildId + { + get => _childId; + set => SetPropertyValueAndDetectChanges(value, ref _childId, nameof(ChildId)); + } - /// - /// Gets or sets the for the Relation - /// - [DataMember] - public IRelationType RelationType - { - get => _relationType; - set => SetPropertyValueAndDetectChanges(value, ref _relationType!, nameof(RelationType)); - } + [DataMember] public Guid ChildObjectType { get; set; } - /// - /// Gets or sets a comment for the Relation - /// - [DataMember] - public string? Comment - { - get => _comment; - set => SetPropertyValueAndDetectChanges(value, ref _comment, nameof(Comment)); - } + /// + /// Gets or sets the for the Relation + /// + [DataMember] + public IRelationType RelationType + { + get => _relationType; + set => SetPropertyValueAndDetectChanges(value, ref _relationType!, nameof(RelationType)); + } - /// - /// Gets the Id of the that this Relation is based on. - /// - [IgnoreDataMember] - public int RelationTypeId => _relationType.Id; + /// + /// Gets or sets a comment for the Relation + /// + [DataMember] + public string? Comment + { + get => _comment; + set => SetPropertyValueAndDetectChanges(value, ref _comment, nameof(Comment)); } + + /// + /// Gets the Id of the that this Relation is based on. + /// + [IgnoreDataMember] + public int RelationTypeId => _relationType.Id; } diff --git a/src/Umbraco.Core/Models/RelationItem.cs b/src/Umbraco.Core/Models/RelationItem.cs index 75344914f04b..d2782afd7d8b 100644 --- a/src/Umbraco.Core/Models/RelationItem.cs +++ b/src/Umbraco.Core/Models/RelationItem.cs @@ -1,44 +1,32 @@ -using System; -using System.Runtime.Serialization; -using Umbraco.Cms.Core.Models.Entities; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models -{ - [DataContract(Name = "relationItem", Namespace = "")] - public class RelationItem - { - [DataMember(Name = "id")] - public int NodeId { get; set; } +namespace Umbraco.Cms.Core.Models; - [DataMember(Name = "key")] - public Guid NodeKey { get; set; } +[DataContract(Name = "relationItem", Namespace = "")] +public class RelationItem +{ + [DataMember(Name = "id")] public int NodeId { get; set; } - [DataMember(Name = "name")] - public string? NodeName { get; set; } + [DataMember(Name = "key")] public Guid NodeKey { get; set; } - [DataMember(Name = "type")] - public string? NodeType { get; set; } + [DataMember(Name = "name")] public string? NodeName { get; set; } - [DataMember(Name = "udi")] - public Udi NodeUdi => Udi.Create(NodeType, NodeKey); + [DataMember(Name = "type")] public string? NodeType { get; set; } - [DataMember(Name = "icon")] - public string? ContentTypeIcon { get; set; } + [DataMember(Name = "udi")] public Udi NodeUdi => Udi.Create(NodeType, NodeKey); - [DataMember(Name = "alias")] - public string? ContentTypeAlias { get; set; } + [DataMember(Name = "icon")] public string? ContentTypeIcon { get; set; } - [DataMember(Name = "contentTypeName")] - public string? ContentTypeName { get; set; } + [DataMember(Name = "alias")] public string? ContentTypeAlias { get; set; } - [DataMember(Name = "relationTypeName")] - public string? RelationTypeName { get; set; } + [DataMember(Name = "contentTypeName")] public string? ContentTypeName { get; set; } - [DataMember(Name = "relationTypeIsBidirectional")] - public bool RelationTypeIsBidirectional { get; set; } + [DataMember(Name = "relationTypeName")] + public string? RelationTypeName { get; set; } - [DataMember(Name = "relationTypeIsDependency")] - public bool RelationTypeIsDependency { get; set; } + [DataMember(Name = "relationTypeIsBidirectional")] + public bool RelationTypeIsBidirectional { get; set; } - } + [DataMember(Name = "relationTypeIsDependency")] + public bool RelationTypeIsDependency { get; set; } } diff --git a/src/Umbraco.Core/Models/RelationType.cs b/src/Umbraco.Core/Models/RelationType.cs index 4c4c69c5f18e..b34d507dcb9a 100644 --- a/src/Umbraco.Core/Models/RelationType.cs +++ b/src/Umbraco.Core/Models/RelationType.cs @@ -1,107 +1,122 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a RelationType +/// +[Serializable] +[DataContract(IsReference = true)] +public class RelationType : EntityBase, IRelationType, IRelationTypeWithIsDependency { - /// - /// Represents a RelationType - /// - [Serializable] - [DataContract(IsReference = true)] - public class RelationType : EntityBase, IRelationType, IRelationTypeWithIsDependency - { - private string _name; - private string _alias; - private bool _isBidirectional; - private bool _isDependency; - private Guid? _parentObjectType; - private Guid? _childObjectType; - - public RelationType(string alias, string name) - : this(name: name, alias: alias, false, null, null, false) - { - } + private string _alias; + private Guid? _childObjectType; + private bool _isBidirectional; + private bool _isDependency; + private string _name; + private Guid? _parentObjectType; - [Obsolete("Use ctor with isDependency parameter")] - public RelationType(string name, string alias, bool isBidrectional, Guid? parentObjectType, Guid? childObjectType) - :this(name,alias,isBidrectional, parentObjectType, childObjectType, false) - { + public RelationType(string alias, string name) + : this(name, alias, false, null, null, false) + { + } - } + [Obsolete("Use ctor with isDependency parameter")] + public RelationType(string name, string alias, bool isBidrectional, Guid? parentObjectType, Guid? childObjectType) + : this(name, alias, isBidrectional, parentObjectType, childObjectType, false) + { + } - public RelationType(string? name, string? alias, bool isBidrectional, Guid? parentObjectType, Guid? childObjectType, bool isDependency) + public RelationType(string? name, string? alias, bool isBidrectional, Guid? parentObjectType, Guid? childObjectType, + bool isDependency) + { + if (name == null) { - if (name == null) throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); - if (alias == null) throw new ArgumentNullException(nameof(alias)); - if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(alias)); - - _name = name; - _alias = alias; - _isBidirectional = isBidrectional; - _isDependency = isDependency; - _parentObjectType = parentObjectType; - _childObjectType = childObjectType; + throw new ArgumentNullException(nameof(name)); } - /// - /// Gets or sets the Name of the RelationType - /// - [DataMember] - public string? Name + if (string.IsNullOrWhiteSpace(name)) { - get => _name; - set => SetPropertyValueAndDetectChanges(value, ref _name!, nameof(Name)); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(name)); } - /// - /// Gets or sets the Alias of the RelationType - /// - [DataMember] - public string Alias + if (alias == null) { - get => _alias; - set => SetPropertyValueAndDetectChanges(value, ref _alias!, nameof(Alias)); + throw new ArgumentNullException(nameof(alias)); } - /// - /// Gets or sets a boolean indicating whether the RelationType is Bidirectional (true) or Parent to Child (false) - /// - [DataMember] - public bool IsBidirectional + if (string.IsNullOrWhiteSpace(alias)) { - get => _isBidirectional; - set => SetPropertyValueAndDetectChanges(value, ref _isBidirectional, nameof(IsBidirectional)); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(alias)); } - /// - /// Gets or sets the Parents object type id - /// - /// Corresponds to the NodeObjectType in the umbracoNode table - [DataMember] - public Guid? ParentObjectType - { - get => _parentObjectType; - set => SetPropertyValueAndDetectChanges(value, ref _parentObjectType, nameof(ParentObjectType)); - } + _name = name; + _alias = alias; + _isBidirectional = isBidrectional; + _isDependency = isDependency; + _parentObjectType = parentObjectType; + _childObjectType = childObjectType; + } - /// - /// Gets or sets the Childs object type id - /// - /// Corresponds to the NodeObjectType in the umbracoNode table - [DataMember] - public Guid? ChildObjectType - { - get => _childObjectType; - set => SetPropertyValueAndDetectChanges(value, ref _childObjectType, nameof(ChildObjectType)); - } + /// + /// Gets or sets the Name of the RelationType + /// + [DataMember] + public string? Name + { + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name!, nameof(Name)); + } + /// + /// Gets or sets the Alias of the RelationType + /// + [DataMember] + public string Alias + { + get => _alias; + set => SetPropertyValueAndDetectChanges(value, ref _alias!, nameof(Alias)); + } - public bool IsDependency - { - get => _isDependency; - set => SetPropertyValueAndDetectChanges(value, ref _isDependency, nameof(IsDependency)); - } + /// + /// Gets or sets a boolean indicating whether the RelationType is Bidirectional (true) or Parent to Child (false) + /// + [DataMember] + public bool IsBidirectional + { + get => _isBidirectional; + set => SetPropertyValueAndDetectChanges(value, ref _isBidirectional, nameof(IsBidirectional)); + } + + /// + /// Gets or sets the Parents object type id + /// + /// Corresponds to the NodeObjectType in the umbracoNode table + [DataMember] + public Guid? ParentObjectType + { + get => _parentObjectType; + set => SetPropertyValueAndDetectChanges(value, ref _parentObjectType, nameof(ParentObjectType)); + } + + /// + /// Gets or sets the Childs object type id + /// + /// Corresponds to the NodeObjectType in the umbracoNode table + [DataMember] + public Guid? ChildObjectType + { + get => _childObjectType; + set => SetPropertyValueAndDetectChanges(value, ref _childObjectType, nameof(ChildObjectType)); + } + + + public bool IsDependency + { + get => _isDependency; + set => SetPropertyValueAndDetectChanges(value, ref _isDependency, nameof(IsDependency)); } } diff --git a/src/Umbraco.Core/Models/RelationTypeExtensions.cs b/src/Umbraco.Core/Models/RelationTypeExtensions.cs index 1e7282b66bbe..94ca81f3eebe 100644 --- a/src/Umbraco.Core/Models/RelationTypeExtensions.cs +++ b/src/Umbraco.Core/Models/RelationTypeExtensions.cs @@ -4,15 +4,14 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class RelationTypeExtensions { - public static class RelationTypeExtensions - { - public static bool IsSystemRelationType(this IRelationType relationType) => - relationType.Alias == Constants.Conventions.RelationTypes.RelatedDocumentAlias - || relationType.Alias == Constants.Conventions.RelationTypes.RelatedMediaAlias - || relationType.Alias == Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias - || relationType.Alias == Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias - || relationType.Alias == Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias; - } + public static bool IsSystemRelationType(this IRelationType relationType) => + relationType.Alias == Constants.Conventions.RelationTypes.RelatedDocumentAlias + || relationType.Alias == Constants.Conventions.RelationTypes.RelatedMediaAlias + || relationType.Alias == Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias + || relationType.Alias == Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias + || relationType.Alias == Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias; } diff --git a/src/Umbraco.Core/Models/RequestPasswordResetModel.cs b/src/Umbraco.Core/Models/RequestPasswordResetModel.cs index 438e97fb30bc..973f183323b3 100644 --- a/src/Umbraco.Core/Models/RequestPasswordResetModel.cs +++ b/src/Umbraco.Core/Models/RequestPasswordResetModel.cs @@ -1,14 +1,12 @@ using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models -{ +namespace Umbraco.Cms.Core.Models; - [DataContract(Name = "requestPasswordReset", Namespace = "")] - public class RequestPasswordResetModel - { - [Required] - [DataMember(Name = "email", IsRequired = true)] - public string Email { get; set; } = null!; - } +[DataContract(Name = "requestPasswordReset", Namespace = "")] +public class RequestPasswordResetModel +{ + [Required] + [DataMember(Name = "email", IsRequired = true)] + public string Email { get; set; } = null!; } diff --git a/src/Umbraco.Core/Models/Script.cs b/src/Umbraco.Core/Models/Script.cs index 0d121368f860..bb0fc10a33ba 100644 --- a/src/Umbraco.Core/Models/Script.cs +++ b/src/Umbraco.Core/Models/Script.cs @@ -1,29 +1,29 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a Script file +/// +[Serializable] +[DataContract(IsReference = true)] +public class Script : File, IScript { - /// - /// Represents a Script file - /// - [Serializable] - [DataContract(IsReference = true)] - public class Script : File, IScript + public Script(string path) + : this(path, null) { - public Script(string path) - : this(path, (Func?) null) - { } - - public Script(string path, Func? getFileContent) - : base(path, getFileContent) - { } + } - /// - /// Indicates whether the current entity has an identity, which in this case is a path/name. - /// - /// - /// Overrides the default Entity identity check. - /// - public override bool HasIdentity => string.IsNullOrEmpty(Path) == false; + public Script(string path, Func? getFileContent) + : base(path, getFileContent) + { } + + /// + /// Indicates whether the current entity has an identity, which in this case is a path/name. + /// + /// + /// Overrides the default Entity identity check. + /// + public override bool HasIdentity => string.IsNullOrEmpty(Path) == false; } diff --git a/src/Umbraco.Core/Models/SendCodeViewModel.cs b/src/Umbraco.Core/Models/SendCodeViewModel.cs index 783bcdeec28c..ef90eace8aeb 100644 --- a/src/Umbraco.Core/Models/SendCodeViewModel.cs +++ b/src/Umbraco.Core/Models/SendCodeViewModel.cs @@ -1,32 +1,32 @@ using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Used for 2FA verification +/// +[DataContract(Name = "code", Namespace = "")] +public class Verify2FACodeModel { - /// - /// Used for 2FA verification - /// - [DataContract(Name = "code", Namespace = "")] - public class Verify2FACodeModel - { - [Required] - [DataMember(Name = "code", IsRequired = true)] - public string? Code { get; set; } + [Required] + [DataMember(Name = "code", IsRequired = true)] + public string? Code { get; set; } - [Required] - [DataMember(Name = "provider", IsRequired = true)] - public string? Provider { get; set; } + [Required] + [DataMember(Name = "provider", IsRequired = true)] + public string? Provider { get; set; } - /// - /// Flag indicating whether the sign-in cookie should persist after the browser is closed. - /// - [DataMember(Name = "isPersistent", IsRequired = true)] - public bool IsPersistent { get; set; } + /// + /// Flag indicating whether the sign-in cookie should persist after the browser is closed. + /// + [DataMember(Name = "isPersistent", IsRequired = true)] + public bool IsPersistent { get; set; } - /// - /// Flag indicating whether the current browser should be remember, suppressing all further two factor authentication prompts. - /// - [DataMember(Name = "rememberClient", IsRequired = true)] - public bool RememberClient { get; set; } - } + /// + /// Flag indicating whether the current browser should be remember, suppressing all further two factor authentication + /// prompts. + /// + [DataMember(Name = "rememberClient", IsRequired = true)] + public bool RememberClient { get; set; } } diff --git a/src/Umbraco.Core/Models/ServerRegistration.cs b/src/Umbraco.Core/Models/ServerRegistration.cs index 553460eb5bc4..a6970ffee097 100644 --- a/src/Umbraco.Core/Models/ServerRegistration.cs +++ b/src/Umbraco.Core/Models/ServerRegistration.cs @@ -1,124 +1,122 @@ -using System; using System.Globalization; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a registered server in a multiple-servers environment. +/// +public class ServerRegistration : EntityBase, IServerRegistration { + private bool _isActive; + private bool _isSchedulingPublisher; + private string? _serverAddress; + private string? _serverIdentity; + /// - /// Represents a registered server in a multiple-servers environment. + /// Initializes a new instance of the class. /// - public class ServerRegistration : EntityBase, IServerRegistration + public ServerRegistration() { - private string? _serverAddress; - private string? _serverIdentity; - private bool _isActive; - private bool _isSchedulingPublisher; - - /// - /// Initializes a new instance of the class. - /// - public ServerRegistration() - { } - - /// - /// Initializes a new instance of the class. - /// - /// The unique id of the server registration. - /// The server URL. - /// The unique server identity. - /// The date and time the registration was created. - /// The date and time the registration was last accessed. - /// A value indicating whether the registration is active. - /// A value indicating whether the registration is master. - public ServerRegistration(int id, string? serverAddress, string? serverIdentity, DateTime registered, DateTime accessed, bool isActive, bool isSchedulingPublisher) - { - UpdateDate = accessed; - CreateDate = registered; - Key = id.ToString(CultureInfo.InvariantCulture).EncodeAsGuid(); - Id = id; - ServerAddress = serverAddress; - ServerIdentity = serverIdentity; - IsActive = isActive; - IsSchedulingPublisher = isSchedulingPublisher; - } + } - /// - /// Initializes a new instance of the class. - /// - /// The server URL. - /// The unique server identity. - /// The date and time the registration was created. - public ServerRegistration(string serverAddress, string serverIdentity, DateTime registered) - { - CreateDate = registered; - UpdateDate = registered; - Key = 0.ToString(CultureInfo.InvariantCulture).EncodeAsGuid(); - ServerAddress = serverAddress; - ServerIdentity = serverIdentity; - } + /// + /// Initializes a new instance of the class. + /// + /// The unique id of the server registration. + /// The server URL. + /// The unique server identity. + /// The date and time the registration was created. + /// The date and time the registration was last accessed. + /// A value indicating whether the registration is active. + /// A value indicating whether the registration is master. + public ServerRegistration(int id, string? serverAddress, string? serverIdentity, DateTime registered, + DateTime accessed, bool isActive, bool isSchedulingPublisher) + { + UpdateDate = accessed; + CreateDate = registered; + Key = id.ToString(CultureInfo.InvariantCulture).EncodeAsGuid(); + Id = id; + ServerAddress = serverAddress; + ServerIdentity = serverIdentity; + IsActive = isActive; + IsSchedulingPublisher = isSchedulingPublisher; + } - /// - /// Gets or sets the server URL. - /// - public string? ServerAddress - { - get => _serverAddress; - set => SetPropertyValueAndDetectChanges(value, ref _serverAddress, nameof(ServerAddress)); - } + /// + /// Initializes a new instance of the class. + /// + /// The server URL. + /// The unique server identity. + /// The date and time the registration was created. + public ServerRegistration(string serverAddress, string serverIdentity, DateTime registered) + { + CreateDate = registered; + UpdateDate = registered; + Key = 0.ToString(CultureInfo.InvariantCulture).EncodeAsGuid(); + ServerAddress = serverAddress; + ServerIdentity = serverIdentity; + } - /// - /// Gets or sets the server unique identity. - /// - public string? ServerIdentity - { - get => _serverIdentity; - set => SetPropertyValueAndDetectChanges(value, ref _serverIdentity, nameof(ServerIdentity)); - } + /// + /// Gets or sets the server URL. + /// + public string? ServerAddress + { + get => _serverAddress; + set => SetPropertyValueAndDetectChanges(value, ref _serverAddress, nameof(ServerAddress)); + } - /// - /// Gets or sets a value indicating whether the server is active. - /// - public bool IsActive - { - get => _isActive; - set => SetPropertyValueAndDetectChanges(value, ref _isActive, nameof(IsActive)); - } + /// + /// Gets or sets the server unique identity. + /// + public string? ServerIdentity + { + get => _serverIdentity; + set => SetPropertyValueAndDetectChanges(value, ref _serverIdentity, nameof(ServerIdentity)); + } - /// - /// Gets or sets a value indicating whether the server has the SchedulingPublisher role - /// - public bool IsSchedulingPublisher - { - get => _isSchedulingPublisher; - set => SetPropertyValueAndDetectChanges(value, ref _isSchedulingPublisher, nameof(IsSchedulingPublisher)); - } + /// + /// Gets or sets a value indicating whether the server is active. + /// + public bool IsActive + { + get => _isActive; + set => SetPropertyValueAndDetectChanges(value, ref _isActive, nameof(IsActive)); + } - /// - /// Gets the date and time the registration was created. - /// - public DateTime Registered - { - get => CreateDate; - set => CreateDate = value; - } + /// + /// Gets or sets a value indicating whether the server has the SchedulingPublisher role + /// + public bool IsSchedulingPublisher + { + get => _isSchedulingPublisher; + set => SetPropertyValueAndDetectChanges(value, ref _isSchedulingPublisher, nameof(IsSchedulingPublisher)); + } - /// - /// Gets the date and time the registration was last accessed. - /// - public DateTime Accessed - { - get => UpdateDate; - set => UpdateDate = value; - } + /// + /// Gets the date and time the registration was created. + /// + public DateTime Registered + { + get => CreateDate; + set => CreateDate = value; + } - /// - /// Converts the value of this instance to its equivalent string representation. - /// - /// - public override string ToString() - { - return string.Format("{{\"{0}\", \"{1}\", {2}active, {3}master}}", ServerAddress, ServerIdentity, IsActive ? "" : "!", IsSchedulingPublisher ? "" : "!"); - } + /// + /// Gets the date and time the registration was last accessed. + /// + public DateTime Accessed + { + get => UpdateDate; + set => UpdateDate = value; } + + /// + /// Converts the value of this instance to its equivalent string representation. + /// + /// + public override string ToString() => string.Format("{{\"{0}\", \"{1}\", {2}active, {3}master}}", ServerAddress, + ServerIdentity, IsActive ? "" : "!", IsSchedulingPublisher ? "" : "!"); } diff --git a/src/Umbraco.Core/Models/SetPasswordModel.cs b/src/Umbraco.Core/Models/SetPasswordModel.cs index c904f98694fd..790c90b41ae3 100644 --- a/src/Umbraco.Core/Models/SetPasswordModel.cs +++ b/src/Umbraco.Core/Models/SetPasswordModel.cs @@ -1,21 +1,20 @@ using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +[DataContract(Name = "setPassword", Namespace = "")] +public class SetPasswordModel { - [DataContract(Name = "setPassword", Namespace = "")] - public class SetPasswordModel - { - [Required] - [DataMember(Name = "userId", IsRequired = true)] - public int UserId { get; set; } + [Required] + [DataMember(Name = "userId", IsRequired = true)] + public int UserId { get; set; } - [Required] - [DataMember(Name = "password", IsRequired = true)] - public string? Password { get; set; } + [Required] + [DataMember(Name = "password", IsRequired = true)] + public string? Password { get; set; } - [Required] - [DataMember(Name = "resetCode", IsRequired = true)] - public string? ResetCode { get; set; } - } + [Required] + [DataMember(Name = "resetCode", IsRequired = true)] + public string? ResetCode { get; set; } } diff --git a/src/Umbraco.Core/Models/SimpleContentType.cs b/src/Umbraco.Core/Models/SimpleContentType.cs index 31e061362cd1..9476ce7f8dc5 100644 --- a/src/Umbraco.Core/Models/SimpleContentType.cs +++ b/src/Umbraco.Core/Models/SimpleContentType.cs @@ -1,99 +1,107 @@ -using System; -using Umbraco.Extensions; +using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Implements . +/// +public class SimpleContentType : ISimpleContentType { /// - /// Implements . + /// Initializes a new instance of the class. /// - public class SimpleContentType : ISimpleContentType + public SimpleContentType(IContentType contentType) + : this((IContentTypeBase)contentType) => + DefaultTemplate = contentType.DefaultTemplate; + + /// + /// Initializes a new instance of the class. + /// + public SimpleContentType(IMediaType mediaType) + : this((IContentTypeBase)mediaType) { - /// - /// Initializes a new instance of the class. - /// - public SimpleContentType(IContentType contentType) - : this((IContentTypeBase)contentType) - { - DefaultTemplate = contentType.DefaultTemplate; - } + } - /// - /// Initializes a new instance of the class. - /// - public SimpleContentType(IMediaType mediaType) - : this((IContentTypeBase)mediaType) - { } - - /// - /// Initializes a new instance of the class. - /// - public SimpleContentType(IMemberType memberType) - : this((IContentTypeBase)memberType) - { } - - private SimpleContentType(IContentTypeBase contentType) + /// + /// Initializes a new instance of the class. + /// + public SimpleContentType(IMemberType memberType) + : this((IContentTypeBase)memberType) + { + } + + private SimpleContentType(IContentTypeBase contentType) + { + if (contentType == null) { - if (contentType == null) throw new ArgumentNullException(nameof(contentType)); - - Id = contentType.Id; - Key = contentType.Key; - Alias = contentType.Alias; - Variations = contentType.Variations; - Icon = contentType.Icon; - IsContainer = contentType.IsContainer; - Name = contentType.Name; - AllowedAsRoot = contentType.AllowedAsRoot; - IsElement = contentType.IsElement; + throw new ArgumentNullException(nameof(contentType)); } - public string Alias { get; } + Id = contentType.Id; + Key = contentType.Key; + Alias = contentType.Alias; + Variations = contentType.Variations; + Icon = contentType.Icon; + IsContainer = contentType.IsContainer; + Name = contentType.Name; + AllowedAsRoot = contentType.AllowedAsRoot; + IsElement = contentType.IsElement; + } + + public string Alias { get; } + + public int Id { get; } - public int Id { get; } + public Guid Key { get; } - public Guid Key { get; } + /// + public ITemplate? DefaultTemplate { get; } - /// - public ITemplate? DefaultTemplate { get; } + public ContentVariation Variations { get; } - public ContentVariation Variations { get; } + public string? Icon { get; } - public string? Icon { get; } + public bool IsContainer { get; } - public bool IsContainer { get; } + public string? Name { get; } - public string? Name { get; } + public bool AllowedAsRoot { get; } - public bool AllowedAsRoot { get; } + public bool IsElement { get; } - public bool IsElement { get; } + public bool SupportsPropertyVariation(string? culture, string segment, bool wildcards = false) => + // non-exact validation: can accept a 'null' culture if the property type varies + // by culture, and likewise for segment + // wildcard validation: can accept a '*' culture or segment + Variations.ValidateVariation(culture, segment, false, wildcards, false); - public bool SupportsPropertyVariation(string? culture, string segment, bool wildcards = false) + protected bool Equals(SimpleContentType other) => string.Equals(Alias, other.Alias) && Id == other.Id; + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) { - // non-exact validation: can accept a 'null' culture if the property type varies - // by culture, and likewise for segment - // wildcard validation: can accept a '*' culture or segment - return Variations.ValidateVariation(culture, segment, false, wildcards, false); + return false; } - protected bool Equals(SimpleContentType other) + if (ReferenceEquals(this, obj)) { - return string.Equals(Alias, other.Alias) && Id == other.Id; + return true; } - public override bool Equals(object? obj) + if (obj.GetType() != GetType()) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != GetType()) return false; - return Equals((SimpleContentType) obj); + return false; } - public override int GetHashCode() + return Equals((SimpleContentType)obj); + } + + public override int GetHashCode() + { + unchecked { - unchecked - { - return ((Alias != null ? Alias.GetHashCode() : 0) * 397) ^ Id; - } + return ((Alias != null ? Alias.GetHashCode() : 0) * 397) ^ Id; } - } + } } diff --git a/src/Umbraco.Core/Models/SimpleValidationModel.cs b/src/Umbraco.Core/Models/SimpleValidationModel.cs index 30efec7dfe06..5e7ecbb176ac 100644 --- a/src/Umbraco.Core/Models/SimpleValidationModel.cs +++ b/src/Umbraco.Core/Models/SimpleValidationModel.cs @@ -1,16 +1,13 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Models +public class SimpleValidationModel { - public class SimpleValidationModel + public SimpleValidationModel(IDictionary modelState, string message = "The request is invalid.") { - public SimpleValidationModel(IDictionary modelState, string message = "The request is invalid.") - { - Message = message; - ModelState = modelState; - } - - public string Message { get; } - public IDictionary ModelState { get; } + Message = message; + ModelState = modelState; } + + public string Message { get; } + public IDictionary ModelState { get; } } diff --git a/src/Umbraco.Core/Models/Stylesheet.cs b/src/Umbraco.Core/Models/Stylesheet.cs index 7b1d971434b9..e9dbc27f5c62 100644 --- a/src/Umbraco.Core/Models/Stylesheet.cs +++ b/src/Umbraco.Core/Models/Stylesheet.cs @@ -1,178 +1,162 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; +using System.ComponentModel; using System.Data; -using System.Linq; using System.Runtime.Serialization; using Umbraco.Cms.Core.Strings.Css; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a Stylesheet file +/// +[Serializable] +[DataContract(IsReference = true)] +public class Stylesheet : File, IStylesheet { + private Lazy>? _properties; + + public Stylesheet(string path) + : this(path, null) + { + } + + public Stylesheet(string path, Func? getFileContent) + : base(string.IsNullOrEmpty(path) ? path : path.EnsureEndsWith(".css"), getFileContent) => + InitializeProperties(); + /// - /// Represents a Stylesheet file + /// Gets or sets the Content of a File /// - [Serializable] - [DataContract(IsReference = true)] - public class Stylesheet : File, IStylesheet + public override string? Content { - public Stylesheet(string path) - : this(path, null) - { } - - public Stylesheet(string path, Func? getFileContent) - : base(string.IsNullOrEmpty(path) ? path : path.EnsureEndsWith(".css"), getFileContent) + get => base.Content; + set { + base.Content = value; + //re-set the properties so they are re-read from the content InitializeProperties(); } + } - private Lazy>? _properties; + /// + /// Returns a list of umbraco back office enabled stylesheet properties + /// + /// + /// An umbraco back office enabled stylesheet property has a special prefix, for example: + /// /** umb_name: MyPropertyName */ p { font-size: 1em; } + /// + [IgnoreDataMember] + public IEnumerable? Properties => _properties?.Value; - private void InitializeProperties() + /// + /// Adds an Umbraco stylesheet property for use in the back office + /// + /// + public void AddProperty(IStylesheetProperty property) + { + if (Properties is not null && Properties.Any(x => x.Name.InvariantEquals(property.Name))) { - //if the value is already created, we need to be created and update the collection according to - //what is now in the content - if (_properties != null && _properties.IsValueCreated) - { - //re-parse it so we can check what properties are different and adjust the event handlers - var parsed = StylesheetHelper.ParseRules(Content).ToArray(); - var names = parsed.Select(x => x.Name).ToArray(); - var existing = _properties.Value.Where(x => names.InvariantContains(x.Name)).ToArray(); - //update existing - foreach (var stylesheetProperty in existing) - { - var updateFrom = parsed.Single(x => x.Name.InvariantEquals(stylesheetProperty.Name)); - //remove current event handler while we update, we'll reset it after - stylesheetProperty.PropertyChanged -= Property_PropertyChanged; - stylesheetProperty.Alias = updateFrom.Selector; - stylesheetProperty.Value = updateFrom.Styles; - //re-add - stylesheetProperty.PropertyChanged += Property_PropertyChanged; - } - //remove no longer existing - var nonExisting = _properties.Value.Where(x => names.InvariantContains(x.Name) == false).ToArray(); - foreach (var stylesheetProperty in nonExisting) - { - stylesheetProperty.PropertyChanged -= Property_PropertyChanged; - _properties.Value.Remove(stylesheetProperty); - } - //add new ones - var newItems = parsed.Where(x => _properties.Value.Select(p => p.Name).InvariantContains(x.Name) == false); - foreach (var stylesheetRule in newItems) - { - var prop = new StylesheetProperty(stylesheetRule.Name, stylesheetRule.Selector, stylesheetRule.Styles); - prop.PropertyChanged += Property_PropertyChanged; - _properties.Value.Add(prop); - } - } - - //we haven't read the properties yet so create the lazy delegate - _properties = new Lazy>(() => - { - var parsed = StylesheetHelper.ParseRules(Content); - return parsed.Select(statement => - { - var property = new StylesheetProperty(statement.Name, statement.Selector, statement.Styles); - property.PropertyChanged += Property_PropertyChanged; - return property; - - }).ToList(); - }); + throw new DuplicateNameException("The property with the name " + property.Name + + " already exists in the collection"); } - /// - /// If the property has changed then we need to update the content - /// - /// - /// - void Property_PropertyChanged(object? sender, PropertyChangedEventArgs e) - { - var prop = (StylesheetProperty?) sender; + //now we need to serialize out the new property collection over-top of the string Content. + Content = StylesheetHelper.AppendRule(Content, + new StylesheetRule {Name = property.Name, Selector = property.Alias, Styles = property.Value}); - if (prop is not null) - { - //Ensure we are setting base.Content here so that the properties don't get reset and thus any event handlers would get reset too - base.Content = StylesheetHelper.ReplaceRule(Content, prop.Name, new StylesheetRule - { - Name = prop.Name, - Selector = prop.Alias, - Styles = prop.Value - }); - } - } + //re-set lazy collection + InitializeProperties(); + } - /// - /// Gets or sets the Content of a File - /// - public override string? Content + /// + /// Removes an Umbraco stylesheet property + /// + /// + public void RemoveProperty(string name) + { + if (Properties is not null && Properties.Any(x => x.Name.InvariantEquals(name))) { - get { return base.Content; } - set - { - base.Content = value; - //re-set the properties so they are re-read from the content - InitializeProperties(); - } + Content = StylesheetHelper.ReplaceRule(Content, name, null); } + } - /// - /// Returns a list of umbraco back office enabled stylesheet properties - /// - /// - /// An umbraco back office enabled stylesheet property has a special prefix, for example: - /// - /// /** umb_name: MyPropertyName */ p { font-size: 1em; } - /// - [IgnoreDataMember] - public IEnumerable? Properties - { - get { return _properties?.Value; } - } + /// + /// Indicates whether the current entity has an identity, which in this case is a path/name. + /// + /// + /// Overrides the default Entity identity check. + /// + public override bool HasIdentity => string.IsNullOrEmpty(Path) == false; - /// - /// Adds an Umbraco stylesheet property for use in the back office - /// - /// - public void AddProperty(IStylesheetProperty property) + private void InitializeProperties() + { + //if the value is already created, we need to be created and update the collection according to + //what is now in the content + if (_properties != null && _properties.IsValueCreated) { - if (Properties is not null && Properties.Any(x => x.Name.InvariantEquals(property.Name))) + //re-parse it so we can check what properties are different and adjust the event handlers + StylesheetRule[] parsed = StylesheetHelper.ParseRules(Content).ToArray(); + var names = parsed.Select(x => x.Name).ToArray(); + StylesheetProperty[] existing = _properties.Value.Where(x => names.InvariantContains(x.Name)).ToArray(); + //update existing + foreach (StylesheetProperty stylesheetProperty in existing) { - throw new DuplicateNameException("The property with the name " + property.Name + " already exists in the collection"); + StylesheetRule updateFrom = parsed.Single(x => x.Name.InvariantEquals(stylesheetProperty.Name)); + //remove current event handler while we update, we'll reset it after + stylesheetProperty.PropertyChanged -= Property_PropertyChanged; + stylesheetProperty.Alias = updateFrom.Selector; + stylesheetProperty.Value = updateFrom.Styles; + //re-add + stylesheetProperty.PropertyChanged += Property_PropertyChanged; } - //now we need to serialize out the new property collection over-top of the string Content. - Content = StylesheetHelper.AppendRule(Content, new StylesheetRule + //remove no longer existing + StylesheetProperty[] nonExisting = + _properties.Value.Where(x => names.InvariantContains(x.Name) == false).ToArray(); + foreach (StylesheetProperty stylesheetProperty in nonExisting) { - Name = property.Name, - Selector = property.Alias, - Styles = property.Value - }); + stylesheetProperty.PropertyChanged -= Property_PropertyChanged; + _properties.Value.Remove(stylesheetProperty); + } - //re-set lazy collection - InitializeProperties(); + //add new ones + IEnumerable newItems = parsed.Where(x => + _properties.Value.Select(p => p.Name).InvariantContains(x.Name) == false); + foreach (StylesheetRule stylesheetRule in newItems) + { + var prop = new StylesheetProperty(stylesheetRule.Name, stylesheetRule.Selector, stylesheetRule.Styles); + prop.PropertyChanged += Property_PropertyChanged; + _properties.Value.Add(prop); + } } - /// - /// Removes an Umbraco stylesheet property - /// - /// - public void RemoveProperty(string name) + //we haven't read the properties yet so create the lazy delegate + _properties = new Lazy>(() => { - if (Properties is not null && Properties.Any(x => x.Name.InvariantEquals(name))) + IEnumerable parsed = StylesheetHelper.ParseRules(Content); + return parsed.Select(statement => { - Content = StylesheetHelper.ReplaceRule(Content, name, null); - } - } + var property = new StylesheetProperty(statement.Name, statement.Selector, statement.Styles); + property.PropertyChanged += Property_PropertyChanged; + return property; + }).ToList(); + }); + } + + /// + /// If the property has changed then we need to update the content + /// + /// + /// + private void Property_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + var prop = (StylesheetProperty?)sender; - /// - /// Indicates whether the current entity has an identity, which in this case is a path/name. - /// - /// - /// Overrides the default Entity identity check. - /// - public override bool HasIdentity + if (prop is not null) { - get { return string.IsNullOrEmpty(Path) == false; } + //Ensure we are setting base.Content here so that the properties don't get reset and thus any event handlers would get reset too + base.Content = StylesheetHelper.ReplaceRule(Content, prop.Name, + new StylesheetRule {Name = prop.Name, Selector = prop.Alias, Styles = prop.Value}); } } } diff --git a/src/Umbraco.Core/Models/StylesheetProperty.cs b/src/Umbraco.Core/Models/StylesheetProperty.cs index af6f347a63cd..1f90572d4adf 100644 --- a/src/Umbraco.Core/Models/StylesheetProperty.cs +++ b/src/Umbraco.Core/Models/StylesheetProperty.cs @@ -1,51 +1,48 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models -{ - /// - /// Represents a Stylesheet Property - /// - /// - /// Properties are always formatted to have a single selector, so it can be used in the backoffice - /// - [Serializable] - [DataContract(IsReference = true)] - public class StylesheetProperty : BeingDirtyBase, IValueObject, IStylesheetProperty - { - private string _alias; - private string _value; +namespace Umbraco.Cms.Core.Models; - public StylesheetProperty(string name, string @alias, string value) - { - Name = name; - _alias = alias; - _value = value; - } +/// +/// Represents a Stylesheet Property +/// +/// +/// Properties are always formatted to have a single selector, so it can be used in the backoffice +/// +[Serializable] +[DataContract(IsReference = true)] +public class StylesheetProperty : BeingDirtyBase, IValueObject, IStylesheetProperty +{ + private string _alias; + private string _value; - /// - /// The CSS rule name that can be used by Umbraco in the back office - /// - public string Name { get; private set; } + public StylesheetProperty(string name, string alias, string value) + { + Name = name; + _alias = alias; + _value = value; + } - /// - /// This is the CSS Selector - /// - public string Alias - { - get => _alias; - set => SetPropertyValueAndDetectChanges(value, ref _alias!, nameof(Alias)); - } + /// + /// The CSS rule name that can be used by Umbraco in the back office + /// + public string Name { get; private set; } - /// - /// The CSS value for the selector - /// - public string Value - { - get => _value; - set => SetPropertyValueAndDetectChanges(value, ref _value!, nameof(Value)); - } + /// + /// This is the CSS Selector + /// + public string Alias + { + get => _alias; + set => SetPropertyValueAndDetectChanges(value, ref _alias!, nameof(Alias)); + } + /// + /// The CSS value for the selector + /// + public string Value + { + get => _value; + set => SetPropertyValueAndDetectChanges(value, ref _value!, nameof(Value)); } } diff --git a/src/Umbraco.Core/Models/Tag.cs b/src/Umbraco.Core/Models/Tag.cs index 92436d068b89..d59492262a19 100644 --- a/src/Umbraco.Core/Models/Tag.cs +++ b/src/Umbraco.Core/Models/Tag.cs @@ -1,59 +1,58 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a tag entity. +/// +[Serializable] +[DataContract(IsReference = true)] +public class Tag : EntityBase, ITag { + private string _group = string.Empty; + private int? _languageId; + private string _text = string.Empty; + /// - /// Represents a tag entity. + /// Initializes a new instance of the class. /// - [Serializable] - [DataContract(IsReference = true)] - public class Tag : EntityBase, ITag + public Tag() { - private string _group = string.Empty; - private string _text = string.Empty; - private int? _languageId; - - /// - /// Initializes a new instance of the class. - /// - public Tag() - { } - - /// - /// Initializes a new instance of the class. - /// - public Tag(int id, string group, string text, int? languageId = null) - { - Id = id; - Text = text; - Group = group; - LanguageId = languageId; - } - - /// - public string Group - { - get => _group; - set => SetPropertyValueAndDetectChanges(value, ref _group!, nameof(Group)); - } - - /// - public string Text - { - get => _text; - set => SetPropertyValueAndDetectChanges(value, ref _text!, nameof(Text)); - } - - /// - public int? LanguageId - { - get => _languageId; - set => SetPropertyValueAndDetectChanges(value, ref _languageId, nameof(LanguageId)); - } - - /// - public int NodeCount { get; set; } } + + /// + /// Initializes a new instance of the class. + /// + public Tag(int id, string group, string text, int? languageId = null) + { + Id = id; + Text = text; + Group = group; + LanguageId = languageId; + } + + /// + public string Group + { + get => _group; + set => SetPropertyValueAndDetectChanges(value, ref _group!, nameof(Group)); + } + + /// + public string Text + { + get => _text; + set => SetPropertyValueAndDetectChanges(value, ref _text!, nameof(Text)); + } + + /// + public int? LanguageId + { + get => _languageId; + set => SetPropertyValueAndDetectChanges(value, ref _languageId, nameof(LanguageId)); + } + + /// + public int NodeCount { get; set; } } diff --git a/src/Umbraco.Core/Models/TagModel.cs b/src/Umbraco.Core/Models/TagModel.cs index 6a0430a492e1..57ea5d04fa63 100644 --- a/src/Umbraco.Core/Models/TagModel.cs +++ b/src/Umbraco.Core/Models/TagModel.cs @@ -1,20 +1,17 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +[DataContract(Name = "tag", Namespace = "")] +public class TagModel { - [DataContract(Name = "tag", Namespace = "")] - public class TagModel - { - [DataMember(Name = "id", IsRequired = true)] - public int Id { get; set; } + [DataMember(Name = "id", IsRequired = true)] + public int Id { get; set; } - [DataMember(Name = "text", IsRequired = true)] - public string? Text { get; set; } + [DataMember(Name = "text", IsRequired = true)] + public string? Text { get; set; } - [DataMember(Name = "group")] - public string? Group { get; set; } + [DataMember(Name = "group")] public string? Group { get; set; } - [DataMember(Name = "nodeCount")] - public int NodeCount { get; set; } - } + [DataMember(Name = "nodeCount")] public int NodeCount { get; set; } } diff --git a/src/Umbraco.Core/Models/TaggableObjectTypes.cs b/src/Umbraco.Core/Models/TaggableObjectTypes.cs index 8a9384ec7436..d06f2d9829d2 100644 --- a/src/Umbraco.Core/Models/TaggableObjectTypes.cs +++ b/src/Umbraco.Core/Models/TaggableObjectTypes.cs @@ -1,13 +1,12 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Enum representing the taggable object types +/// +public enum TaggableObjectTypes { - /// - /// Enum representing the taggable object types - /// - public enum TaggableObjectTypes - { - All, - Content, - Media, - Member - } + All, + Content, + Media, + Member } diff --git a/src/Umbraco.Core/Models/TaggedEntity.cs b/src/Umbraco.Core/Models/TaggedEntity.cs index 9bc05eae15f6..39658769e363 100644 --- a/src/Umbraco.Core/Models/TaggedEntity.cs +++ b/src/Umbraco.Core/Models/TaggedEntity.cs @@ -1,31 +1,30 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Models +/// +/// Represents a tagged entity. +/// +/// +/// Note that it is the properties of an entity (like Content, Media, Members, etc.) that are tagged, +/// which is why this class is composed of a list of tagged properties and the identifier the actual entity. +/// +public class TaggedEntity { /// - /// Represents a tagged entity. + /// Initializes a new instance of the class. /// - /// Note that it is the properties of an entity (like Content, Media, Members, etc.) that are tagged, - /// which is why this class is composed of a list of tagged properties and the identifier the actual entity. - public class TaggedEntity + public TaggedEntity(int entityId, IEnumerable taggedProperties) { - /// - /// Initializes a new instance of the class. - /// - public TaggedEntity(int entityId, IEnumerable taggedProperties) - { - EntityId = entityId; - TaggedProperties = taggedProperties; - } + EntityId = entityId; + TaggedProperties = taggedProperties; + } - /// - /// Gets the identifier of the entity. - /// - public int EntityId { get; } + /// + /// Gets the identifier of the entity. + /// + public int EntityId { get; } - /// - /// Gets the tagged properties. - /// - public IEnumerable TaggedProperties { get; } - } + /// + /// Gets the tagged properties. + /// + public IEnumerable TaggedProperties { get; } } diff --git a/src/Umbraco.Core/Models/TaggedProperty.cs b/src/Umbraco.Core/Models/TaggedProperty.cs index 24ef9ccc4562..70bf33abbc6a 100644 --- a/src/Umbraco.Core/Models/TaggedProperty.cs +++ b/src/Umbraco.Core/Models/TaggedProperty.cs @@ -1,35 +1,32 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Models +/// +/// Represents a tagged property on an entity. +/// +public class TaggedProperty { /// - /// Represents a tagged property on an entity. + /// Initializes a new instance of the class. /// - public class TaggedProperty + public TaggedProperty(int propertyTypeId, string? propertyTypeAlias, IEnumerable tags) { - /// - /// Initializes a new instance of the class. - /// - public TaggedProperty(int propertyTypeId, string? propertyTypeAlias, IEnumerable tags) - { - PropertyTypeId = propertyTypeId; - PropertyTypeAlias = propertyTypeAlias; - Tags = tags; - } + PropertyTypeId = propertyTypeId; + PropertyTypeAlias = propertyTypeAlias; + Tags = tags; + } - /// - /// Gets the identifier of the property type. - /// - public int PropertyTypeId { get; } + /// + /// Gets the identifier of the property type. + /// + public int PropertyTypeId { get; } - /// - /// Gets the alias of the property type. - /// - public string? PropertyTypeAlias { get; } + /// + /// Gets the alias of the property type. + /// + public string? PropertyTypeAlias { get; } - /// - /// Gets the tags. - /// - public IEnumerable Tags { get; } - } + /// + /// Gets the tags. + /// + public IEnumerable Tags { get; } } diff --git a/src/Umbraco.Core/Models/TagsStorageType.cs b/src/Umbraco.Core/Models/TagsStorageType.cs index 7bd8ea7937a5..58a12640a753 100644 --- a/src/Umbraco.Core/Models/TagsStorageType.cs +++ b/src/Umbraco.Core/Models/TagsStorageType.cs @@ -1,20 +1,21 @@ -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Defines how tags are stored. +/// +/// +/// Tags are always stored as a string, but the string can +/// either be a delimited string, or a serialized Json array. +/// +public enum TagsStorageType { /// - /// Defines how tags are stored. + /// Store tags as a delimited string. /// - /// Tags are always stored as a string, but the string can - /// either be a delimited string, or a serialized Json array. - public enum TagsStorageType - { - /// - /// Store tags as a delimited string. - /// - Csv, + Csv, - /// - /// Store tags as serialized Json. - /// - Json - } + /// + /// Store tags as serialized Json. + /// + Json } diff --git a/src/Umbraco.Core/Models/TelemetryLevel.cs b/src/Umbraco.Core/Models/TelemetryLevel.cs index 26a714b38578..e5ab213067c9 100644 --- a/src/Umbraco.Core/Models/TelemetryLevel.cs +++ b/src/Umbraco.Core/Models/TelemetryLevel.cs @@ -1,12 +1,11 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +[DataContract] +public enum TelemetryLevel { - [DataContract] - public enum TelemetryLevel - { - Minimal, - Basic, - Detailed, - } + Minimal, + Basic, + Detailed } diff --git a/src/Umbraco.Core/Models/TelemetryResource.cs b/src/Umbraco.Core/Models/TelemetryResource.cs index 401e07848f58..d783fe500f74 100644 --- a/src/Umbraco.Core/Models/TelemetryResource.cs +++ b/src/Umbraco.Core/Models/TelemetryResource.cs @@ -1,11 +1,9 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +[DataContract] +public class TelemetryResource { - [DataContract] - public class TelemetryResource - { - [DataMember] - public TelemetryLevel TelemetryLevel { get; set; } - } + [DataMember] public TelemetryLevel TelemetryLevel { get; set; } } diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index 7efccf1e7d22..4331711461e7 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -1,86 +1,86 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a Template file. +/// +[Serializable] +[DataContract(IsReference = true)] +public class Template : File, ITemplate { - /// - /// Represents a Template file. - /// - [Serializable] - [DataContract(IsReference = true)] - public class Template : File, ITemplate - { - private string _alias; - private readonly IShortStringHelper _shortStringHelper; - private string? _name; - private string? _masterTemplateAlias; - private Lazy? _masterTemplateId; + private readonly IShortStringHelper _shortStringHelper; + private string _alias; + private string? _masterTemplateAlias; + private Lazy? _masterTemplateId; + private string? _name; - public Template(IShortStringHelper shortStringHelper, string? name, string? alias) - : this(shortStringHelper, name, alias, null) - { } + public Template(IShortStringHelper shortStringHelper, string? name, string? alias) + : this(shortStringHelper, name, alias, null) + { + } - public Template(IShortStringHelper shortStringHelper, string? name, string? alias, Func? getFileContent) - : base(string.Empty, getFileContent) - { - _shortStringHelper = shortStringHelper; - _name = name; - _alias = alias?.ToCleanString(shortStringHelper, CleanStringType.UnderscoreAlias) ?? string.Empty; - _masterTemplateId = new Lazy(() => -1); - } + public Template(IShortStringHelper shortStringHelper, string? name, string? alias, + Func? getFileContent) + : base(string.Empty, getFileContent) + { + _shortStringHelper = shortStringHelper; + _name = name; + _alias = alias?.ToCleanString(shortStringHelper, CleanStringType.UnderscoreAlias) ?? string.Empty; + _masterTemplateId = new Lazy(() => -1); + } - [DataMember] - public Lazy? MasterTemplateId - { - get => _masterTemplateId; - set => SetPropertyValueAndDetectChanges(value, ref _masterTemplateId, nameof(MasterTemplateId)); - } + [DataMember] + public Lazy? MasterTemplateId + { + get => _masterTemplateId; + set => SetPropertyValueAndDetectChanges(value, ref _masterTemplateId, nameof(MasterTemplateId)); + } - public string? MasterTemplateAlias - { - get => _masterTemplateAlias; - set => SetPropertyValueAndDetectChanges(value, ref _masterTemplateAlias, nameof(MasterTemplateAlias)); - } + public string? MasterTemplateAlias + { + get => _masterTemplateAlias; + set => SetPropertyValueAndDetectChanges(value, ref _masterTemplateAlias, nameof(MasterTemplateAlias)); + } - [DataMember] - public new string? Name - { - get => _name; - set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); - } + [DataMember] + public new string? Name + { + get => _name; + set => SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name)); + } - [DataMember] - public new string Alias - { - get => _alias; - set => SetPropertyValueAndDetectChanges(value.ToCleanString(_shortStringHelper, CleanStringType.UnderscoreAlias), ref _alias!, nameof(Alias)); - } + [DataMember] + public new string Alias + { + get => _alias; + set => SetPropertyValueAndDetectChanges( + value.ToCleanString(_shortStringHelper, CleanStringType.UnderscoreAlias), ref _alias!, nameof(Alias)); + } - /// - /// Returns true if the template is used as a layout for other templates (i.e. it has 'children') - /// - public bool IsMasterTemplate { get; set; } + /// + /// Returns true if the template is used as a layout for other templates (i.e. it has 'children') + /// + public bool IsMasterTemplate { get; set; } - public void SetMasterTemplate(ITemplate? masterTemplate) + public void SetMasterTemplate(ITemplate? masterTemplate) + { + if (masterTemplate == null) { - if (masterTemplate == null) - { - MasterTemplateId = new Lazy(() => -1); - MasterTemplateAlias = null; - } - else - { - MasterTemplateId = new Lazy(() => masterTemplate.Id); - MasterTemplateAlias = masterTemplate.Alias; - } - + MasterTemplateId = new Lazy(() => -1); + MasterTemplateAlias = null; } - - protected override void DeepCloneNameAndAlias(File clone) + else { - // do nothing - prevents File from doing its stuff + MasterTemplateId = new Lazy(() => masterTemplate.Id); + MasterTemplateAlias = masterTemplate.Alias; } } + + protected override void DeepCloneNameAndAlias(File clone) + { + // do nothing - prevents File from doing its stuff + } } diff --git a/src/Umbraco.Core/Models/TemplateNode.cs b/src/Umbraco.Core/Models/TemplateNode.cs index 339f4efee319..c973f17b00ba 100644 --- a/src/Umbraco.Core/Models/TemplateNode.cs +++ b/src/Umbraco.Core/Models/TemplateNode.cs @@ -1,34 +1,31 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Models +/// +/// Represents a template in a template tree +/// +public class TemplateNode { - /// - /// Represents a template in a template tree - /// - public class TemplateNode + public TemplateNode(ITemplate template) { - public TemplateNode(ITemplate template) - { - Template = template; - Children = new List(); - } + Template = template; + Children = new List(); + } - /// - /// The current template - /// - public ITemplate Template { get; set; } + /// + /// The current template + /// + public ITemplate Template { get; set; } - /// - /// The children of the current template - /// - public IEnumerable Children { get; set; } + /// + /// The children of the current template + /// + public IEnumerable Children { get; set; } - /// - /// The parent template to the current template - /// - /// - /// Will be null if there is no parent - /// - public TemplateNode? Parent { get; set; } - } + /// + /// The parent template to the current template + /// + /// + /// Will be null if there is no parent + /// + public TemplateNode? Parent { get; set; } } diff --git a/src/Umbraco.Core/Models/TemplateOnDisk.cs b/src/Umbraco.Core/Models/TemplateOnDisk.cs index 61c10ba456ed..9115ca963219 100644 --- a/src/Umbraco.Core/Models/TemplateOnDisk.cs +++ b/src/Umbraco.Core/Models/TemplateOnDisk.cs @@ -1,52 +1,49 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Strings; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a Template file that can have its content on disk. +/// +[Serializable] +[DataContract(IsReference = true)] +public class TemplateOnDisk : Template { /// - /// Represents a Template file that can have its content on disk. + /// Initializes a new instance of the class. /// - [Serializable] - [DataContract(IsReference = true)] - public class TemplateOnDisk : Template - { - /// - /// Initializes a new instance of the class. - /// - /// The name of the template. - /// The alias of the template. - public TemplateOnDisk(IShortStringHelper shortStringHelper, string name, string alias) - : base(shortStringHelper, name, alias) - { - IsOnDisk = true; - } + /// The name of the template. + /// The alias of the template. + public TemplateOnDisk(IShortStringHelper shortStringHelper, string name, string alias) + : base(shortStringHelper, name, alias) => + IsOnDisk = true; - /// - /// Gets or sets a value indicating whether the content is on disk already. - /// - public bool IsOnDisk { get; set; } + /// + /// Gets or sets a value indicating whether the content is on disk already. + /// + public bool IsOnDisk { get; set; } - /// - /// Gets or sets the content. - /// - /// - /// Getting the content while the template is "on disk" throws, - /// the template must be saved before its content can be retrieved. - /// Setting the content means it is not "on disk" anymore, and the - /// template becomes (and behaves like) a normal template. - /// - public override string? Content + /// + /// Gets or sets the content. + /// + /// + /// + /// Getting the content while the template is "on disk" throws, + /// the template must be saved before its content can be retrieved. + /// + /// + /// Setting the content means it is not "on disk" anymore, and the + /// template becomes (and behaves like) a normal template. + /// + /// + public override string? Content + { + get => IsOnDisk ? string.Empty : base.Content; + set { - get - { - return IsOnDisk ? string.Empty : base.Content; - } - set - { - base.Content = value; - IsOnDisk = false; - } + base.Content = value; + IsOnDisk = false; } } } diff --git a/src/Umbraco.Core/Models/TemplateQuery/ContentTypeModel.cs b/src/Umbraco.Core/Models/TemplateQuery/ContentTypeModel.cs index f4f3e7bc59bd..c3e05df2a352 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/ContentTypeModel.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/ContentTypeModel.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.Models.TemplateQuery +namespace Umbraco.Cms.Core.Models.TemplateQuery; + +public class ContentTypeModel { - public class ContentTypeModel - { - public string? Alias { get; set; } + public string? Alias { get; set; } - public string? Name { get; set; } - } + public string? Name { get; set; } } diff --git a/src/Umbraco.Core/Models/TemplateQuery/Operator.cs b/src/Umbraco.Core/Models/TemplateQuery/Operator.cs index eb3fe4be29bb..86100e972106 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/Operator.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/Operator.cs @@ -1,14 +1,13 @@ -namespace Umbraco.Cms.Core.Models.TemplateQuery +namespace Umbraco.Cms.Core.Models.TemplateQuery; + +public enum Operator { - public enum Operator - { - Equals = 1, - NotEquals = 2, - Contains = 3, - NotContains = 4, - LessThan = 5, - LessThanEqualTo = 6, - GreaterThan = 7, - GreaterThanEqualTo = 8 - } + Equals = 1, + NotEquals = 2, + Contains = 3, + NotContains = 4, + LessThan = 5, + LessThanEqualTo = 6, + GreaterThan = 7, + GreaterThanEqualTo = 8 } diff --git a/src/Umbraco.Core/Models/TemplateQuery/OperatorFactory.cs b/src/Umbraco.Core/Models/TemplateQuery/OperatorFactory.cs index a8e3b40fefb7..fc23ebdb3d5c 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/OperatorFactory.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/OperatorFactory.cs @@ -1,32 +1,34 @@ -using System; +namespace Umbraco.Cms.Core.Models.TemplateQuery; -namespace Umbraco.Cms.Core.Models.TemplateQuery +public static class OperatorFactory { - public static class OperatorFactory + public static Operator FromString(string stringOperator) { - public static Operator FromString(string stringOperator) + if (stringOperator == null) { - if (stringOperator == null) throw new ArgumentNullException(nameof(stringOperator)); + throw new ArgumentNullException(nameof(stringOperator)); + } - switch (stringOperator) - { - case "=": - case "==": - return Operator.Equals; - case "!=": - case "<>": - return Operator.NotEquals; - case "<": - return Operator.LessThan; - case "<=": - return Operator.LessThanEqualTo; - case ">": - return Operator.GreaterThan; - case ">=": - return Operator.GreaterThanEqualTo; - default: - throw new ArgumentException($"A operator cannot be created from the specified string '{stringOperator}'", nameof(stringOperator)); - } + switch (stringOperator) + { + case "=": + case "==": + return Operator.Equals; + case "!=": + case "<>": + return Operator.NotEquals; + case "<": + return Operator.LessThan; + case "<=": + return Operator.LessThanEqualTo; + case ">": + return Operator.GreaterThan; + case ">=": + return Operator.GreaterThanEqualTo; + default: + throw new ArgumentException( + $"A operator cannot be created from the specified string '{stringOperator}'", + nameof(stringOperator)); } } } diff --git a/src/Umbraco.Core/Models/TemplateQuery/OperatorTerm.cs b/src/Umbraco.Core/Models/TemplateQuery/OperatorTerm.cs index ce66965c689d..d1f1a104062a 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/OperatorTerm.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/OperatorTerm.cs @@ -1,25 +1,22 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models.TemplateQuery; -namespace Umbraco.Cms.Core.Models.TemplateQuery +public class OperatorTerm { - public class OperatorTerm + public OperatorTerm() { - public OperatorTerm() - { - Name = "is"; - Operator = Operator.Equals; - AppliesTo = new [] { "string" }; - } - - public OperatorTerm(string name, Operator @operator, IEnumerable appliesTo) - { - Name = name; - Operator = @operator; - AppliesTo = appliesTo; - } + Name = "is"; + Operator = Operator.Equals; + AppliesTo = new[] {"string"}; + } - public string Name { get; set; } - public Operator Operator { get; set; } - public IEnumerable AppliesTo { get; set; } + public OperatorTerm(string name, Operator @operator, IEnumerable appliesTo) + { + Name = name; + Operator = @operator; + AppliesTo = appliesTo; } + + public string Name { get; set; } + public Operator Operator { get; set; } + public IEnumerable AppliesTo { get; set; } } diff --git a/src/Umbraco.Core/Models/TemplateQuery/PropertyModel.cs b/src/Umbraco.Core/Models/TemplateQuery/PropertyModel.cs index 3ea4059b7e69..bd117534f964 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/PropertyModel.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/PropertyModel.cs @@ -1,11 +1,10 @@ -namespace Umbraco.Cms.Core.Models.TemplateQuery +namespace Umbraco.Cms.Core.Models.TemplateQuery; + +public class PropertyModel { - public class PropertyModel - { - public string? Name { get; set; } + public string? Name { get; set; } - public string Alias { get; set; } = string.Empty; + public string Alias { get; set; } = string.Empty; - public string? Type { get; set; } - } + public string? Type { get; set; } } diff --git a/src/Umbraco.Core/Models/TemplateQuery/QueryCondition.cs b/src/Umbraco.Core/Models/TemplateQuery/QueryCondition.cs index b6305f16a8ec..9ab5919d32ef 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/QueryCondition.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/QueryCondition.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.Models.TemplateQuery +namespace Umbraco.Cms.Core.Models.TemplateQuery; + +public class QueryCondition { - public class QueryCondition - { - public PropertyModel Property { get; set; } = new PropertyModel(); - public OperatorTerm Term { get; set; } = new OperatorTerm(); - public string ConstraintValue { get; set; } = string.Empty; - } + public PropertyModel Property { get; set; } = new(); + public OperatorTerm Term { get; set; } = new(); + public string ConstraintValue { get; set; } = string.Empty; } diff --git a/src/Umbraco.Core/Models/TemplateQuery/QueryConditionExtensions.cs b/src/Umbraco.Core/Models/TemplateQuery/QueryConditionExtensions.cs index 962cf9255854..ac12061760c0 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/QueryConditionExtensions.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/QueryConditionExtensions.cs @@ -1,75 +1,74 @@ -using System; -using System.Linq.Expressions; +using System.Linq.Expressions; using System.Reflection; using Umbraco.Cms.Core.Models.TemplateQuery; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class QueryConditionExtensions { - public static class QueryConditionExtensions - { - private static Lazy StringContainsMethodInfo => - new Lazy(() => typeof(string).GetMethod("Contains", new[] {typeof(string)})!); + private static Lazy StringContainsMethodInfo => + new(() => typeof(string).GetMethod("Contains", new[] {typeof(string)})!); - public static Expression> BuildCondition(this QueryCondition condition, string parameterAlias) + public static Expression> BuildCondition(this QueryCondition condition, string parameterAlias) + { + object constraintValue; + switch (condition.Property.Type?.ToLowerInvariant()) { - object constraintValue; - switch (condition.Property.Type?.ToLowerInvariant()) - { - case "string": - constraintValue = condition.ConstraintValue; - break; - case "datetime": - constraintValue = DateTime.Parse(condition.ConstraintValue); - break; - case "boolean": - constraintValue = Boolean.Parse(condition.ConstraintValue); - break; - default: - constraintValue = Convert.ChangeType(condition.ConstraintValue, typeof(int)); - break; - } + case "string": + constraintValue = condition.ConstraintValue; + break; + case "datetime": + constraintValue = DateTime.Parse(condition.ConstraintValue); + break; + case "boolean": + constraintValue = bool.Parse(condition.ConstraintValue); + break; + default: + constraintValue = Convert.ChangeType(condition.ConstraintValue, typeof(int)); + break; + } - var parameterExpression = Expression.Parameter(typeof(T), parameterAlias); - var propertyExpression = Expression.Property(parameterExpression, condition.Property.Alias); + ParameterExpression parameterExpression = Expression.Parameter(typeof(T), parameterAlias); + MemberExpression propertyExpression = Expression.Property(parameterExpression, condition.Property.Alias); - var valueExpression = Expression.Constant(constraintValue); - Expression bodyExpression; - switch (condition.Term.Operator) - { - case Operator.NotEquals: - bodyExpression = Expression.NotEqual(propertyExpression, valueExpression); - break; - case Operator.GreaterThan: - bodyExpression = Expression.GreaterThan(propertyExpression, valueExpression); - break; - case Operator.GreaterThanEqualTo: - bodyExpression = Expression.GreaterThanOrEqual(propertyExpression, valueExpression); - break; - case Operator.LessThan: - bodyExpression = Expression.LessThan(propertyExpression, valueExpression); - break; - case Operator.LessThanEqualTo: - bodyExpression = Expression.LessThanOrEqual(propertyExpression, valueExpression); - break; - case Operator.Contains: - bodyExpression = Expression.Call(propertyExpression, StringContainsMethodInfo.Value, - valueExpression); - break; - case Operator.NotContains: - var tempExpression = Expression.Call(propertyExpression, StringContainsMethodInfo.Value, - valueExpression); - bodyExpression = Expression.Equal(tempExpression, Expression.Constant(false)); - break; - default: - case Operator.Equals: - bodyExpression = Expression.Equal(propertyExpression, valueExpression); - break; - } + ConstantExpression valueExpression = Expression.Constant(constraintValue); + Expression bodyExpression; + switch (condition.Term.Operator) + { + case Operator.NotEquals: + bodyExpression = Expression.NotEqual(propertyExpression, valueExpression); + break; + case Operator.GreaterThan: + bodyExpression = Expression.GreaterThan(propertyExpression, valueExpression); + break; + case Operator.GreaterThanEqualTo: + bodyExpression = Expression.GreaterThanOrEqual(propertyExpression, valueExpression); + break; + case Operator.LessThan: + bodyExpression = Expression.LessThan(propertyExpression, valueExpression); + break; + case Operator.LessThanEqualTo: + bodyExpression = Expression.LessThanOrEqual(propertyExpression, valueExpression); + break; + case Operator.Contains: + bodyExpression = Expression.Call(propertyExpression, StringContainsMethodInfo.Value, + valueExpression); + break; + case Operator.NotContains: + MethodCallExpression tempExpression = Expression.Call(propertyExpression, + StringContainsMethodInfo.Value, + valueExpression); + bodyExpression = Expression.Equal(tempExpression, Expression.Constant(false)); + break; + default: + case Operator.Equals: + bodyExpression = Expression.Equal(propertyExpression, valueExpression); + break; + } - var predicate = - Expression.Lambda>(bodyExpression.Reduce(), parameterExpression); + var predicate = + Expression.Lambda>(bodyExpression.Reduce(), parameterExpression); - return predicate; - } + return predicate; } } diff --git a/src/Umbraco.Core/Models/TemplateQuery/QueryModel.cs b/src/Umbraco.Core/Models/TemplateQuery/QueryModel.cs index 48d6506143c1..caf26d093c6d 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/QueryModel.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/QueryModel.cs @@ -1,13 +1,10 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models.TemplateQuery; -namespace Umbraco.Cms.Core.Models.TemplateQuery +public class QueryModel { - public class QueryModel - { - public ContentTypeModel? ContentType { get; set; } - public SourceModel? Source { get; set; } - public IEnumerable? Filters { get; set; } - public SortExpression? Sort { get; set; } - public int Take { get; set; } - } + public ContentTypeModel? ContentType { get; set; } + public SourceModel? Source { get; set; } + public IEnumerable? Filters { get; set; } + public SortExpression? Sort { get; set; } + public int Take { get; set; } } diff --git a/src/Umbraco.Core/Models/TemplateQuery/QueryResultModel.cs b/src/Umbraco.Core/Models/TemplateQuery/QueryResultModel.cs index 8605f924235f..80c33286306a 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/QueryResultModel.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/QueryResultModel.cs @@ -1,15 +1,10 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Models.TemplateQuery; -namespace Umbraco.Cms.Core.Models.TemplateQuery +public class QueryResultModel { - - public class QueryResultModel - { - - public string? QueryExpression { get; set; } - public IEnumerable? SampleResults { get; set; } - public int ResultCount { get; set; } - public long ExecutionTime { get; set; } - public int Take { get; set; } - } + public string? QueryExpression { get; set; } + public IEnumerable? SampleResults { get; set; } + public int ResultCount { get; set; } + public long ExecutionTime { get; set; } + public int Take { get; set; } } diff --git a/src/Umbraco.Core/Models/TemplateQuery/SortExpression.cs b/src/Umbraco.Core/Models/TemplateQuery/SortExpression.cs index c68b366ba547..1a41eef4ab72 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/SortExpression.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/SortExpression.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.Models.TemplateQuery +namespace Umbraco.Cms.Core.Models.TemplateQuery; + +public class SortExpression { - public class SortExpression - { - public PropertyModel? Property { get; set; } + public PropertyModel? Property { get; set; } - public string? Direction { get; set; } - } + public string? Direction { get; set; } } diff --git a/src/Umbraco.Core/Models/TemplateQuery/SourceModel.cs b/src/Umbraco.Core/Models/TemplateQuery/SourceModel.cs index 4b67f7e73cb9..bdaaf54841d9 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/SourceModel.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/SourceModel.cs @@ -1,8 +1,7 @@ -namespace Umbraco.Cms.Core.Models.TemplateQuery +namespace Umbraco.Cms.Core.Models.TemplateQuery; + +public class SourceModel { - public class SourceModel - { - public int Id { get; set; } - public string? Name { get; set; } - } + public int Id { get; set; } + public string? Name { get; set; } } diff --git a/src/Umbraco.Core/Models/TemplateQuery/TemplateQueryResult.cs b/src/Umbraco.Core/Models/TemplateQuery/TemplateQueryResult.cs index 95615b4d0d9e..40cede0c92f6 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/TemplateQueryResult.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/TemplateQueryResult.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.Models.TemplateQuery +namespace Umbraco.Cms.Core.Models.TemplateQuery; + +public class TemplateQueryResult { - public class TemplateQueryResult - { - public string? Icon { get; set; } + public string? Icon { get; set; } - public string? Name { get; set; } - } + public string? Name { get; set; } } diff --git a/src/Umbraco.Core/Models/Trees/ActionMenuItem.cs b/src/Umbraco.Core/Models/Trees/ActionMenuItem.cs index c89fb402d071..4eb552abd1ab 100644 --- a/src/Umbraco.Core/Models/Trees/ActionMenuItem.cs +++ b/src/Umbraco.Core/Models/Trees/ActionMenuItem.cs @@ -1,54 +1,50 @@ using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.Trees +namespace Umbraco.Cms.Core.Models.Trees; + +/// +/// +/// A menu item that represents some JS that needs to execute when the menu item is clicked. +/// +/// +/// These types of menu items are rare but they do exist. Things like refresh node simply execute +/// JS and don't launch a dialog. +/// Each action menu item describes what angular service that it's method exists in and what the method name is. +/// An action menu item must describe the angular service name for which it's method exists. It may also define what +/// the +/// method name is that will be called in this service but if one is not specified then we will assume the method name +/// is the +/// same as the Type name of the current action menu class. +/// +public abstract class ActionMenuItem : MenuItem { - /// - /// - /// A menu item that represents some JS that needs to execute when the menu item is clicked. - /// - /// - /// These types of menu items are rare but they do exist. Things like refresh node simply execute - /// JS and don't launch a dialog. - /// Each action menu item describes what angular service that it's method exists in and what the method name is. - /// An action menu item must describe the angular service name for which it's method exists. It may also define what the - /// method name is that will be called in this service but if one is not specified then we will assume the method name is the - /// same as the Type name of the current action menu class. - /// - public abstract class ActionMenuItem : MenuItem - { - /// - /// The angular service name containing the - /// - public abstract string AngularServiceName { get; } + protected ActionMenuItem(string alias, string name) : base(alias, name) => Initialize(); - /// - /// The angular service method name to call for this menu item - /// - public virtual string? AngularServiceMethodName { get; } = null; + protected ActionMenuItem(string alias, ILocalizedTextService textService) : base(alias, textService) => + Initialize(); - protected ActionMenuItem(string alias, string name) : base(alias, name) - { - Initialize(); - } + /// + /// The angular service name containing the + /// + public abstract string AngularServiceName { get; } - protected ActionMenuItem(string alias, ILocalizedTextService textService) : base(alias, textService) + /// + /// The angular service method name to call for this menu item + /// + public virtual string? AngularServiceMethodName { get; } = null; + + private void Initialize() + { + //add the current type to the metadata + if (AngularServiceMethodName.IsNullOrWhiteSpace()) { - Initialize(); + //if no method name is supplied we will assume that the menu action is the type name of the current menu class + ExecuteJsMethod($"{AngularServiceName}.{GetType().Name}"); } - - private void Initialize() + else { - //add the current type to the metadata - if (AngularServiceMethodName.IsNullOrWhiteSpace()) - { - //if no method name is supplied we will assume that the menu action is the type name of the current menu class - ExecuteJsMethod($"{AngularServiceName}.{this.GetType().Name}"); - } - else - { - ExecuteJsMethod($"{AngularServiceName}.{AngularServiceMethodName}"); - } + ExecuteJsMethod($"{AngularServiceName}.{AngularServiceMethodName}"); } } } diff --git a/src/Umbraco.Core/Models/Trees/CreateChildEntity.cs b/src/Umbraco.Core/Models/Trees/CreateChildEntity.cs index a8d945242e29..c858c16f8a74 100644 --- a/src/Umbraco.Core/Models/Trees/CreateChildEntity.cs +++ b/src/Umbraco.Core/Models/Trees/CreateChildEntity.cs @@ -1,27 +1,27 @@ using Umbraco.Cms.Core.Actions; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.Models.Trees +namespace Umbraco.Cms.Core.Models.Trees; + +/// +/// Represents the refresh node menu item +/// +public sealed class CreateChildEntity : ActionMenuItem { - /// - /// Represents the refresh node menu item - /// - public sealed class CreateChildEntity : ActionMenuItem + public CreateChildEntity(string name, bool separatorBefore = false) + : base(ActionNew.ActionAlias, name) { - public override string AngularServiceName => "umbracoMenuActions"; - - public CreateChildEntity(string name, bool separatorBefore = false) - : base(ActionNew.ActionAlias, name) - { - Icon = "add"; Name = name; - SeparatorBefore = separatorBefore; - } + Icon = "add"; + Name = name; + SeparatorBefore = separatorBefore; + } - public CreateChildEntity(ILocalizedTextService textService, bool separatorBefore = false) - : base(ActionNew.ActionAlias, textService) - { - Icon = "add"; - SeparatorBefore = separatorBefore; - } + public CreateChildEntity(ILocalizedTextService textService, bool separatorBefore = false) + : base(ActionNew.ActionAlias, textService) + { + Icon = "add"; + SeparatorBefore = separatorBefore; } + + public override string AngularServiceName => "umbracoMenuActions"; } diff --git a/src/Umbraco.Core/Models/Trees/ExportMember.cs b/src/Umbraco.Core/Models/Trees/ExportMember.cs index 30f904f95291..4c401782a553 100644 --- a/src/Umbraco.Core/Models/Trees/ExportMember.cs +++ b/src/Umbraco.Core/Models/Trees/ExportMember.cs @@ -1,17 +1,13 @@ using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.Models.Trees +namespace Umbraco.Cms.Core.Models.Trees; + +/// +/// Represents the export member menu item +/// +public sealed class ExportMember : ActionMenuItem { - /// - /// Represents the export member menu item - /// - public sealed class ExportMember : ActionMenuItem - { - public override string AngularServiceName => "umbracoMenuActions"; + public ExportMember(ILocalizedTextService textService) : base("export", textService) => Icon = "download-alt"; - public ExportMember(ILocalizedTextService textService) : base("export", textService) - { - Icon = "download-alt"; - } - } + public override string AngularServiceName => "umbracoMenuActions"; } diff --git a/src/Umbraco.Core/Models/Trees/MenuItem.cs b/src/Umbraco.Core/Models/Trees/MenuItem.cs index e56a2440a8ce..1680c3d35585 100644 --- a/src/Umbraco.Core/Models/Trees/MenuItem.cs +++ b/src/Umbraco.Core/Models/Trees/MenuItem.cs @@ -1,213 +1,196 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -using System.Threading; using Umbraco.Cms.Core.Actions; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Trees; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.Trees +namespace Umbraco.Cms.Core.Models.Trees; + +/// +/// A context menu item +/// +[DataContract(Name = "menuItem", Namespace = "")] +public class MenuItem { + #region Constructors + + public MenuItem() + { + AdditionalData = new Dictionary(); + Icon = "folder"; + } + + public MenuItem(string alias, string name) + : this() + { + Alias = alias; + Name = name; + } + + public MenuItem(string alias, ILocalizedTextService textService) + : this() + { + Alias = alias; + Name = textService.Localize("actions", Alias); + TextDescription = textService.Localize("visuallyHiddenTexts", alias + "_description", + Thread.CurrentThread.CurrentUICulture); + } + + /// + /// Create a menu item based on an definition + /// + /// + /// + public MenuItem(IAction action, string name = "") + : this() + { + Name = name.IsNullOrWhiteSpace() ? action.Alias : name; + Alias = action.Alias; + SeparatorBefore = false; + Icon = action.Icon; + Action = action; + } + + #endregion + + #region Properties + + [IgnoreDataMember] public IAction? Action { get; set; } + + /// + /// A dictionary to support any additional meta data that should be rendered for the node which is + /// useful for custom action commands such as 'create', 'copy', etc... + /// + /// + /// We will also use the meta data collection for dealing with legacy menu items (i.e. for loading custom URLs or + /// executing custom JS). + /// + [DataMember(Name = "metaData")] + public Dictionary AdditionalData { get; private set; } + + [DataMember(Name = "name", IsRequired = true)] + [Required] + public string? Name { get; set; } + + [DataMember(Name = "alias", IsRequired = true)] + [Required] + public string? Alias { get; set; } + + [DataMember(Name = "textDescription")] public string? TextDescription { get; set; } + + /// + /// Ensures a menu separator will exist before this menu item + /// + [DataMember(Name = "separator")] + public bool SeparatorBefore { get; set; } + + [DataMember(Name = "cssclass")] public string Icon { get; set; } + + /// + /// Used in the UI to inform the user that the menu item will open a dialog/confirmation + /// + [DataMember(Name = "opensDialog")] + public bool OpensDialog { get; set; } + + #endregion + + #region Constants + + /// + /// Used as a key for the AdditionalData to specify a specific dialog title instead of the menu title + /// + internal const string DialogTitleKey = "dialogTitle"; + + /// + /// Used to specify the URL that the dialog will launch to in an iframe + /// + internal const string ActionUrlKey = "actionUrl"; + + // TODO: some action's want to launch a new window like live editing, we support this in the menu item's metadata with + // a key called: "actionUrlMethod" which can be set to either: Dialog, BlankWindow. Normally this is always set to Dialog + // if a URL is specified in the "actionUrl" metadata. For now I'm not going to implement launching in a blank window, + // though would be v-easy, just not sure we want to ever support that? + internal const string ActionUrlMethodKey = "actionUrlMethod"; + + /// + /// Used to specify the angular view that the dialog will launch + /// + internal const string ActionViewKey = "actionView"; + + /// + /// Used to specify the js method to execute for the menu item + /// + internal const string JsActionKey = "jsAction"; + + /// + /// Used to specify an angular route to go to for the menu item + /// + internal const string ActionRouteKey = "actionRoute"; + + #endregion + + #region Methods + + /// + /// Sets the menu item to navigate to the specified angular route path + /// + /// + public void NavigateToRoute(string route) => AdditionalData[ActionRouteKey] = route; + + /// + /// Adds the required meta data to the menu item so that angular knows to attempt to call the Js method. + /// + /// + public void ExecuteJsMethod(string jsToExecute) => SetJsAction(jsToExecute); + + /// + /// Sets the menu item to display a dialog based on an angular view path + /// + /// + /// + public void LaunchDialogView(string view, string dialogTitle) + { + SetDialogTitle(dialogTitle); + SetActionView(view); + } + /// - /// A context menu item + /// Sets the menu item to display a dialog based on a URL path in an iframe /// - [DataContract(Name = "menuItem", Namespace = "")] - public class MenuItem + /// + /// + public void LaunchDialogUrl(string url, string dialogTitle) { - #region Constructors - public MenuItem() - { - AdditionalData = new Dictionary(); - Icon = "folder"; - } - - public MenuItem(string alias, string name) - : this() - { - Alias = alias; - Name = name; - } - - public MenuItem(string alias, ILocalizedTextService textService) - : this() - { - Alias = alias; - Name = textService.Localize("actions", Alias); - TextDescription = textService.Localize("visuallyHiddenTexts", alias + "_description", Thread.CurrentThread.CurrentUICulture); - } - - /// - /// Create a menu item based on an definition - /// - /// - /// - public MenuItem(IAction action, string name = "") - : this() - { - Name = name.IsNullOrWhiteSpace() ? action.Alias : name; - Alias = action.Alias; - SeparatorBefore = false; - Icon = action.Icon; - Action = action; - } - #endregion - - #region Properties - [IgnoreDataMember] - public IAction? Action { get; set; } - - /// - /// A dictionary to support any additional meta data that should be rendered for the node which is - /// useful for custom action commands such as 'create', 'copy', etc... - /// - /// - /// We will also use the meta data collection for dealing with legacy menu items (i.e. for loading custom URLs or - /// executing custom JS). - /// - [DataMember(Name = "metaData")] - public Dictionary AdditionalData { get; private set; } - - [DataMember(Name = "name", IsRequired = true)] - [Required] - public string? Name { get; set; } - - [DataMember(Name = "alias", IsRequired = true)] - [Required] - public string? Alias { get; set; } - - [DataMember(Name = "textDescription")] - public string? TextDescription { get; set; } - - /// - /// Ensures a menu separator will exist before this menu item - /// - [DataMember(Name = "separator")] - public bool SeparatorBefore { get; set; } - - [DataMember(Name = "cssclass")] - public string Icon { get; set; } - - /// - /// Used in the UI to inform the user that the menu item will open a dialog/confirmation - /// - [DataMember(Name = "opensDialog")] - public bool OpensDialog { get; set; } - - #endregion - - #region Constants - - /// - /// Used as a key for the AdditionalData to specify a specific dialog title instead of the menu title - /// - internal const string DialogTitleKey = "dialogTitle"; - - /// - /// Used to specify the URL that the dialog will launch to in an iframe - /// - internal const string ActionUrlKey = "actionUrl"; - - // TODO: some action's want to launch a new window like live editing, we support this in the menu item's metadata with - // a key called: "actionUrlMethod" which can be set to either: Dialog, BlankWindow. Normally this is always set to Dialog - // if a URL is specified in the "actionUrl" metadata. For now I'm not going to implement launching in a blank window, - // though would be v-easy, just not sure we want to ever support that? - internal const string ActionUrlMethodKey = "actionUrlMethod"; - - /// - /// Used to specify the angular view that the dialog will launch - /// - internal const string ActionViewKey = "actionView"; - - /// - /// Used to specify the js method to execute for the menu item - /// - internal const string JsActionKey = "jsAction"; - - /// - /// Used to specify an angular route to go to for the menu item - /// - internal const string ActionRouteKey = "actionRoute"; - - #endregion - - #region Methods - - /// - /// Sets the menu item to navigate to the specified angular route path - /// - /// - public void NavigateToRoute(string route) - { - AdditionalData[ActionRouteKey] = route; - } - - /// - /// Adds the required meta data to the menu item so that angular knows to attempt to call the Js method. - /// - /// - public void ExecuteJsMethod(string jsToExecute) - { - SetJsAction(jsToExecute); - } - - /// - /// Sets the menu item to display a dialog based on an angular view path - /// - /// - /// - public void LaunchDialogView(string view, string dialogTitle) - { - SetDialogTitle(dialogTitle); - SetActionView(view); - } - - /// - /// Sets the menu item to display a dialog based on a URL path in an iframe - /// - /// - /// - public void LaunchDialogUrl(string url, string dialogTitle) - { - SetDialogTitle(dialogTitle); - SetActionUrl(url); - } - - private void SetJsAction(string jsToExecute) - { - AdditionalData[JsActionKey] = jsToExecute; - } - - /// - /// Puts a dialog title into the meta data to be displayed on the dialog of the menu item (if there is one) - /// instead of the menu name - /// - /// - private void SetDialogTitle(string dialogTitle) - { - AdditionalData[DialogTitleKey] = dialogTitle; - } - - /// - /// Configures the menu item to launch a specific view - /// - /// - private void SetActionView(string view) - { - AdditionalData[ActionViewKey] = view; - } - - /// - /// Configures the menu item to launch a URL with the specified action (dialog or new window) - /// - /// - /// - private void SetActionUrl(string url, ActionUrlMethod method = ActionUrlMethod.Dialog) - { - AdditionalData[ActionUrlKey] = url; - AdditionalData[ActionUrlMethodKey] = method; - } - - #endregion + SetDialogTitle(dialogTitle); + SetActionUrl(url); } + + private void SetJsAction(string jsToExecute) => AdditionalData[JsActionKey] = jsToExecute; + + /// + /// Puts a dialog title into the meta data to be displayed on the dialog of the menu item (if there is one) + /// instead of the menu name + /// + /// + private void SetDialogTitle(string dialogTitle) => AdditionalData[DialogTitleKey] = dialogTitle; + + /// + /// Configures the menu item to launch a specific view + /// + /// + private void SetActionView(string view) => AdditionalData[ActionViewKey] = view; + + /// + /// Configures the menu item to launch a URL with the specified action (dialog or new window) + /// + /// + /// + private void SetActionUrl(string url, ActionUrlMethod method = ActionUrlMethod.Dialog) + { + AdditionalData[ActionUrlKey] = url; + AdditionalData[ActionUrlMethodKey] = method; + } + + #endregion } diff --git a/src/Umbraco.Core/Models/Trees/RefreshNode.cs b/src/Umbraco.Core/Models/Trees/RefreshNode.cs index 01eb2fa34adc..9dfdc3ec29dc 100644 --- a/src/Umbraco.Core/Models/Trees/RefreshNode.cs +++ b/src/Umbraco.Core/Models/Trees/RefreshNode.cs @@ -1,27 +1,26 @@ using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.Models.Trees +namespace Umbraco.Cms.Core.Models.Trees; + +/// +/// +/// Represents the refresh node menu item +/// +public sealed class RefreshNode : ActionMenuItem { - /// - /// - /// Represents the refresh node menu item - /// - public sealed class RefreshNode : ActionMenuItem + public RefreshNode(string name, bool separatorBefore = false) + : base("refreshNode", name) { - public override string AngularServiceName => "umbracoMenuActions"; - - public RefreshNode(string name, bool separatorBefore = false) - : base("refreshNode", name) - { - Icon = "refresh"; - SeparatorBefore = separatorBefore; - } + Icon = "refresh"; + SeparatorBefore = separatorBefore; + } - public RefreshNode(ILocalizedTextService textService, bool separatorBefore = false) - : base("refreshNode", textService) - { - Icon = "refresh"; - SeparatorBefore = separatorBefore; - } + public RefreshNode(ILocalizedTextService textService, bool separatorBefore = false) + : base("refreshNode", textService) + { + Icon = "refresh"; + SeparatorBefore = separatorBefore; } + + public override string AngularServiceName => "umbracoMenuActions"; } diff --git a/src/Umbraco.Core/Models/TwoFactorLogin.cs b/src/Umbraco.Core/Models/TwoFactorLogin.cs index c38105626c94..fd1483c689d6 100644 --- a/src/Umbraco.Core/Models/TwoFactorLogin.cs +++ b/src/Umbraco.Core/Models/TwoFactorLogin.cs @@ -1,13 +1,11 @@ -using System; -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public class TwoFactorLogin : EntityBase, ITwoFactorLogin { - public class TwoFactorLogin : EntityBase, ITwoFactorLogin - { - public string ProviderName { get; set; } = null!; - public string Secret { get; set; } = null!; - public Guid UserOrMemberKey { get; set; } - public bool Confirmed { get; set; } - } + public bool Confirmed { get; set; } + public string ProviderName { get; set; } = null!; + public string Secret { get; set; } = null!; + public Guid UserOrMemberKey { get; set; } } diff --git a/src/Umbraco.Core/Models/UmbracoDomain.cs b/src/Umbraco.Core/Models/UmbracoDomain.cs index 3f2eb00f51ef..6ae269981df7 100644 --- a/src/Umbraco.Core/Models/UmbracoDomain.cs +++ b/src/Umbraco.Core/Models/UmbracoDomain.cs @@ -1,54 +1,47 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +[Serializable] +[DataContract(IsReference = true)] +public class UmbracoDomain : EntityBase, IDomain { - [Serializable] - [DataContract(IsReference = true)] - public class UmbracoDomain : EntityBase, IDomain + private int? _contentId; + private string _domainName; + private int? _languageId; + + public UmbracoDomain(string domainName) => _domainName = domainName; + + public UmbracoDomain(string domainName, string languageIsoCode) + : this(domainName) => + LanguageIsoCode = languageIsoCode; + + [DataMember] + public int? LanguageId + { + get => _languageId; + set => SetPropertyValueAndDetectChanges(value, ref _languageId, nameof(LanguageId)); + } + + [DataMember] + public string DomainName + { + get => _domainName; + set => SetPropertyValueAndDetectChanges(value, ref _domainName!, nameof(DomainName)); + } + + [DataMember] + public int? RootContentId { - public UmbracoDomain(string domainName) - { - _domainName = domainName; - } - - public UmbracoDomain(string domainName, string languageIsoCode) - : this(domainName) - { - LanguageIsoCode = languageIsoCode; - } - - private int? _contentId; - private int? _languageId; - private string _domainName; - - [DataMember] - public int? LanguageId - { - get => _languageId; - set => SetPropertyValueAndDetectChanges(value, ref _languageId, nameof(LanguageId)); - } - - [DataMember] - public string DomainName - { - get => _domainName; - set => SetPropertyValueAndDetectChanges(value, ref _domainName!, nameof(DomainName)); - } - - [DataMember] - public int? RootContentId - { - get => _contentId; - set => SetPropertyValueAndDetectChanges(value, ref _contentId, nameof(RootContentId)); - } - - public bool IsWildcard => string.IsNullOrWhiteSpace(DomainName) || DomainName.StartsWith("*"); - - /// - /// Readonly value of the language ISO code for the domain - /// - public string? LanguageIsoCode { get; set; } + get => _contentId; + set => SetPropertyValueAndDetectChanges(value, ref _contentId, nameof(RootContentId)); } + + public bool IsWildcard => string.IsNullOrWhiteSpace(DomainName) || DomainName.StartsWith("*"); + + /// + /// Readonly value of the language ISO code for the domain + /// + public string? LanguageIsoCode { get; set; } } diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs index 00dbd490f8d7..598f8127d4b6 100644 --- a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs +++ b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs @@ -1,178 +1,169 @@ using Umbraco.Cms.Core.CodeAnnotations; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Enum used to represent the Umbraco Object Types and their associated GUIDs +/// +public enum UmbracoObjectTypes { /// - /// Enum used to represent the Umbraco Object Types and their associated GUIDs - /// - public enum UmbracoObjectTypes - { - /// - /// Default value - /// - Unknown, - - - /// - /// Root - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.SystemRoot)] - [FriendlyName("Root")] - ROOT, - - /// - /// Document - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.Document, typeof(IContent))] - [FriendlyName("Document")] - [UmbracoUdiType(Constants.UdiEntityType.Document)] - Document, - - /// - /// Media - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.Media, typeof(IMedia))] - [FriendlyName("Media")] - [UmbracoUdiType(Constants.UdiEntityType.Media)] - Media, - - /// - /// Member Type - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.MemberType, typeof(IMemberType))] - [FriendlyName("Member Type")] - [UmbracoUdiType(Constants.UdiEntityType.MemberType)] - MemberType, - - /// - /// Template - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.Template, typeof(ITemplate))] - [FriendlyName("Template")] - [UmbracoUdiType(Constants.UdiEntityType.Template)] - Template, - - /// - /// Member Group - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.MemberGroup)] - [FriendlyName("Member Group")] - [UmbracoUdiType(Constants.UdiEntityType.MemberGroup)] - MemberGroup, - - /// - /// "Media Type - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.MediaType, typeof(IMediaType))] - [FriendlyName("Media Type")] - [UmbracoUdiType(Constants.UdiEntityType.MediaType)] - MediaType, - - /// - /// Document Type - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.DocumentType, typeof(IContentType))] - [FriendlyName("Document Type")] - [UmbracoUdiType(Constants.UdiEntityType.DocumentType)] - DocumentType, - - /// - /// Recycle Bin - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.ContentRecycleBin)] - [FriendlyName("Recycle Bin")] - RecycleBin, - - /// - /// Member - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.Member, typeof(IMember))] - [FriendlyName("Member")] - [UmbracoUdiType(Constants.UdiEntityType.Member)] - Member, - - /// - /// Data Type - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.DataType, typeof(IDataType))] - [FriendlyName("Data Type")] - [UmbracoUdiType(Constants.UdiEntityType.DataType)] - DataType, - - /// - /// Document type container - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.DocumentTypeContainer)] - [FriendlyName("Document Type Container")] - [UmbracoUdiType(Constants.UdiEntityType.DocumentTypeContainer)] - DocumentTypeContainer, - - /// - /// Media type container - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.MediaTypeContainer)] - [FriendlyName("Media Type Container")] - [UmbracoUdiType(Constants.UdiEntityType.MediaTypeContainer)] - MediaTypeContainer, - - /// - /// Media type container - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.DataTypeContainer)] - [FriendlyName("Data Type Container")] - [UmbracoUdiType(Constants.UdiEntityType.DataTypeContainer)] - DataTypeContainer, - - /// - /// Relation type - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.RelationType)] - [FriendlyName("Relation Type")] - [UmbracoUdiType(Constants.UdiEntityType.RelationType)] - RelationType, - - /// - /// Forms Form - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsForm)] - [FriendlyName("Form")] - FormsForm, - - /// - /// Forms PreValue - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsPreValue)] - [FriendlyName("PreValue")] - FormsPreValue, - - /// - /// Forms DataSource - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsDataSource)] - [FriendlyName("DataSource")] - FormsDataSource, - - /// - /// Language - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.Language)] - [FriendlyName("Language")] - Language, - - /// - /// Document Blueprint - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.DocumentBlueprint, typeof(IContent))] - [FriendlyName("DocumentBlueprint")] - [UmbracoUdiType(Constants.UdiEntityType.DocumentBlueprint)] - DocumentBlueprint, - - /// - /// Reserved Identifier - /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.IdReservation)] - [FriendlyName("Identifier Reservation")] - IdReservation - - } + /// Default value + /// + Unknown, + + + /// + /// Root + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.SystemRoot)] [FriendlyName("Root")] + ROOT, + + /// + /// Document + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.Document, typeof(IContent))] + [FriendlyName("Document")] + [UmbracoUdiType(Constants.UdiEntityType.Document)] + Document, + + /// + /// Media + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.Media, typeof(IMedia))] + [FriendlyName("Media")] + [UmbracoUdiType(Constants.UdiEntityType.Media)] + Media, + + /// + /// Member Type + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.MemberType, typeof(IMemberType))] + [FriendlyName("Member Type")] + [UmbracoUdiType(Constants.UdiEntityType.MemberType)] + MemberType, + + /// + /// Template + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.Template, typeof(ITemplate))] + [FriendlyName("Template")] + [UmbracoUdiType(Constants.UdiEntityType.Template)] + Template, + + /// + /// Member Group + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.MemberGroup)] + [FriendlyName("Member Group")] + [UmbracoUdiType(Constants.UdiEntityType.MemberGroup)] + MemberGroup, + + /// + /// "Media Type + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.MediaType, typeof(IMediaType))] + [FriendlyName("Media Type")] + [UmbracoUdiType(Constants.UdiEntityType.MediaType)] + MediaType, + + /// + /// Document Type + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.DocumentType, typeof(IContentType))] + [FriendlyName("Document Type")] + [UmbracoUdiType(Constants.UdiEntityType.DocumentType)] + DocumentType, + + /// + /// Recycle Bin + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.ContentRecycleBin)] [FriendlyName("Recycle Bin")] + RecycleBin, + + /// + /// Member + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.Member, typeof(IMember))] + [FriendlyName("Member")] + [UmbracoUdiType(Constants.UdiEntityType.Member)] + Member, + + /// + /// Data Type + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.DataType, typeof(IDataType))] + [FriendlyName("Data Type")] + [UmbracoUdiType(Constants.UdiEntityType.DataType)] + DataType, + + /// + /// Document type container + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.DocumentTypeContainer)] + [FriendlyName("Document Type Container")] + [UmbracoUdiType(Constants.UdiEntityType.DocumentTypeContainer)] + DocumentTypeContainer, + + /// + /// Media type container + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.MediaTypeContainer)] + [FriendlyName("Media Type Container")] + [UmbracoUdiType(Constants.UdiEntityType.MediaTypeContainer)] + MediaTypeContainer, + + /// + /// Media type container + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.DataTypeContainer)] + [FriendlyName("Data Type Container")] + [UmbracoUdiType(Constants.UdiEntityType.DataTypeContainer)] + DataTypeContainer, + + /// + /// Relation type + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.RelationType)] + [FriendlyName("Relation Type")] + [UmbracoUdiType(Constants.UdiEntityType.RelationType)] + RelationType, + + /// + /// Forms Form + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsForm)] [FriendlyName("Form")] + FormsForm, + + /// + /// Forms PreValue + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsPreValue)] [FriendlyName("PreValue")] + FormsPreValue, + + /// + /// Forms DataSource + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsDataSource)] [FriendlyName("DataSource")] + FormsDataSource, + + /// + /// Language + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.Language)] [FriendlyName("Language")] + Language, + + /// + /// Document Blueprint + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.DocumentBlueprint, typeof(IContent))] + [FriendlyName("DocumentBlueprint")] + [UmbracoUdiType(Constants.UdiEntityType.DocumentBlueprint)] + DocumentBlueprint, + + /// + /// Reserved Identifier + /// + [UmbracoObjectType(Constants.ObjectTypes.Strings.IdReservation)] [FriendlyName("Identifier Reservation")] + IdReservation } diff --git a/src/Umbraco.Core/Models/UmbracoUserExtensions.cs b/src/Umbraco.Core/Models/UmbracoUserExtensions.cs index 71612f353124..4581ef19a390 100644 --- a/src/Umbraco.Core/Models/UmbracoUserExtensions.cs +++ b/src/Umbraco.Core/Models/UmbracoUserExtensions.cs @@ -1,79 +1,91 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Services; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class UmbracoUserExtensions { - public static class UmbracoUserExtensions + public static IEnumerable GetPermissions(this IUser user, string path, IUserService userService) => + userService.GetPermissionsForPath(user, path).GetAllPermissions(); + + public static bool HasSectionAccess(this IUser user, string app) + { + IEnumerable apps = user.AllowedSections; + return apps.Any(uApp => uApp.InvariantEquals(app)); + } + + /// + /// Determines whether this user is the 'super' user. + /// + public static bool IsSuper(this IUser user) { - public static IEnumerable GetPermissions(this IUser user, string path, IUserService userService) + if (user == null) { - return userService.GetPermissionsForPath(user, path).GetAllPermissions(); + throw new ArgumentNullException(nameof(user)); } - public static bool HasSectionAccess(this IUser user, string app) + return user.Id == Constants.Security.SuperUserId; + } + + /// + /// Determines whether this user belongs to the administrators group. + /// + /// The 'super' user does not automatically belongs to the administrators group. + public static bool IsAdmin(this IUser user) + { + if (user == null) { - var apps = user.AllowedSections; - return apps.Any(uApp => uApp.InvariantEquals(app)); + throw new ArgumentNullException(nameof(user)); } - /// - /// Determines whether this user is the 'super' user. - /// - public static bool IsSuper(this IUser user) + return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.AdminGroupAlias); + } + + /// + /// Returns the culture info associated with this user, based on the language they're assigned to in the back office + /// + /// + /// + /// + /// + public static CultureInfo GetUserCulture(this IUser user, ILocalizedTextService textService, + GlobalSettings globalSettings) + { + if (user == null) { - if (user == null) throw new ArgumentNullException(nameof(user)); - return user.Id == Constants.Security.SuperUserId; + throw new ArgumentNullException(nameof(user)); } - /// - /// Determines whether this user belongs to the administrators group. - /// - /// The 'super' user does not automatically belongs to the administrators group. - public static bool IsAdmin(this IUser user) + if (textService == null) { - if (user == null) throw new ArgumentNullException(nameof(user)); - return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.AdminGroupAlias); + throw new ArgumentNullException(nameof(textService)); } - /// - /// Returns the culture info associated with this user, based on the language they're assigned to in the back office - /// - /// - /// - /// - /// - public static CultureInfo GetUserCulture(this IUser user, ILocalizedTextService textService, GlobalSettings globalSettings) + return GetUserCulture(user.Language, textService, globalSettings); + } + + public static CultureInfo GetUserCulture(string? userLanguage, ILocalizedTextService textService, + GlobalSettings globalSettings) + { + try { - if (user == null) throw new ArgumentNullException(nameof(user)); - if (textService == null) throw new ArgumentNullException(nameof(textService)); - return GetUserCulture(user.Language, textService, globalSettings); + var culture = CultureInfo.GetCultureInfo(userLanguage!.Replace("_", "-")); + // TODO: This is a hack because we store the user language as 2 chars instead of the full culture + // which is actually stored in the language files (which are also named with 2 chars!) so we need to attempt + // to convert to a supported full culture + CultureInfo result = textService.ConvertToSupportedCultureWithRegionCode(culture); + return result; } - - public static CultureInfo GetUserCulture(string? userLanguage, ILocalizedTextService textService, GlobalSettings globalSettings) + catch (CultureNotFoundException) { - try - { - var culture = CultureInfo.GetCultureInfo(userLanguage!.Replace("_", "-")); - // TODO: This is a hack because we store the user language as 2 chars instead of the full culture - // which is actually stored in the language files (which are also named with 2 chars!) so we need to attempt - // to convert to a supported full culture - var result = textService.ConvertToSupportedCultureWithRegionCode(culture); - return result; - } - catch (CultureNotFoundException) - { - //return the default one - return CultureInfo.GetCultureInfo(globalSettings.DefaultUILanguage); - } + //return the default one + return CultureInfo.GetCultureInfo(globalSettings.DefaultUILanguage); } } } diff --git a/src/Umbraco.Core/Models/UnLinkLoginModel.cs b/src/Umbraco.Core/Models/UnLinkLoginModel.cs index d8c9920c5e0d..2dbc487c2b26 100644 --- a/src/Umbraco.Core/Models/UnLinkLoginModel.cs +++ b/src/Umbraco.Core/Models/UnLinkLoginModel.cs @@ -1,16 +1,15 @@ using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public class UnLinkLoginModel { - public class UnLinkLoginModel - { - [Required] - [DataMember(Name = "loginProvider", IsRequired = true)] - public string? LoginProvider { get; set; } + [Required] + [DataMember(Name = "loginProvider", IsRequired = true)] + public string? LoginProvider { get; set; } - [Required] - [DataMember(Name = "providerKey", IsRequired = true)] - public string? ProviderKey { get; set; } - } + [Required] + [DataMember(Name = "providerKey", IsRequired = true)] + public string? ProviderKey { get; set; } } diff --git a/src/Umbraco.Core/Models/UpgradeCheckResponse.cs b/src/Umbraco.Core/Models/UpgradeCheckResponse.cs index 3238720541b2..79797620dbc3 100644 --- a/src/Umbraco.Core/Models/UpgradeCheckResponse.cs +++ b/src/Umbraco.Core/Models/UpgradeCheckResponse.cs @@ -2,26 +2,24 @@ using System.Runtime.Serialization; using Umbraco.Cms.Core.Configuration; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +[DataContract(Name = "upgrade", Namespace = "")] +public class UpgradeCheckResponse { - [DataContract(Name = "upgrade", Namespace = "")] - public class UpgradeCheckResponse + public UpgradeCheckResponse() { } + + public UpgradeCheckResponse(string upgradeType, string upgradeComment, string upgradeUrl, + IUmbracoVersion umbracoVersion) { - [DataMember(Name = "type")] - public string? Type { get; set; } + Type = upgradeType; + Comment = upgradeComment; + Url = upgradeUrl + "?version=" + WebUtility.UrlEncode(umbracoVersion.Version?.ToString(3)); + } - [DataMember(Name = "comment")] - public string? Comment { get; set; } + [DataMember(Name = "type")] public string? Type { get; set; } - [DataMember(Name = "url")] - public string? Url { get; set; } + [DataMember(Name = "comment")] public string? Comment { get; set; } - public UpgradeCheckResponse() { } - public UpgradeCheckResponse(string upgradeType, string upgradeComment, string upgradeUrl, IUmbracoVersion umbracoVersion) - { - Type = upgradeType; - Comment = upgradeComment; - Url = upgradeUrl + "?version=" + WebUtility.UrlEncode(umbracoVersion.Version?.ToString(3)); - } - } + [DataMember(Name = "url")] public string? Url { get; set; } } diff --git a/src/Umbraco.Core/Models/UsageInformation.cs b/src/Umbraco.Core/Models/UsageInformation.cs index e2bedd6f0f0b..6656a18c4e1b 100644 --- a/src/Umbraco.Core/Models/UsageInformation.cs +++ b/src/Umbraco.Core/Models/UsageInformation.cs @@ -1,20 +1,17 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +[DataContract] +public class UsageInformation { - [DataContract] - public class UsageInformation + public UsageInformation(string name, object data) { - [DataMember(Name = "name")] - public string Name { get; } + Name = name; + Data = data; + } - [DataMember(Name = "data")] - public object Data { get; } + [DataMember(Name = "name")] public string Name { get; } - public UsageInformation(string name, object data) - { - Name = name; - Data = data; - } - } + [DataMember(Name = "data")] public object Data { get; } } diff --git a/src/Umbraco.Core/Models/UserData.cs b/src/Umbraco.Core/Models/UserData.cs index 07b45b3c547b..e35cd5a2d737 100644 --- a/src/Umbraco.Core/Models/UserData.cs +++ b/src/Umbraco.Core/Models/UserData.cs @@ -1,19 +1,17 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +[DataContract] +public class UserData { - [DataContract] - public class UserData + public UserData(string name, string data) { - [DataMember(Name = "name")] - public string Name { get; } - [DataMember(Name = "data")] - public string Data { get; } - - public UserData(string name, string data) - { - Name = name; - Data = data; - } + Name = name; + Data = data; } + + [DataMember(Name = "name")] public string Name { get; } + + [DataMember(Name = "data")] public string Data { get; } } diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 924b11bcc4ca..4eca05c0e3e2 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; +using System.Net; using System.Security.Cryptography; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.IO; @@ -12,285 +9,345 @@ using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +public static class UserExtensions { - public static class UserExtensions + /// + /// Tries to lookup the user's Gravatar to see if the endpoint can be reached, if so it returns the valid URL + /// + /// + /// + /// + /// + /// A list of 5 different sized avatar URLs + /// + public static string[] GetUserAvatarUrls(this IUser user, IAppCache cache, MediaFileManager mediaFileManager, + IImageUrlGenerator imageUrlGenerator) { - /// - /// Tries to lookup the user's Gravatar to see if the endpoint can be reached, if so it returns the valid URL - /// - /// - /// - /// - /// - /// A list of 5 different sized avatar URLs - /// - public static string[] GetUserAvatarUrls(this IUser user, IAppCache cache, MediaFileManager mediaFileManager, IImageUrlGenerator imageUrlGenerator) + // If FIPS is required, never check the Gravatar service as it only supports MD5 hashing. + // Unfortunately, if the FIPS setting is enabled on Windows, using MD5 will throw an exception + // and the website will not run. + // Also, check if the user has explicitly removed all avatars including a Gravatar, this will be possible and the value will be "none" + if (user.Avatar == "none" || CryptoConfig.AllowOnlyFipsAlgorithms) { - // If FIPS is required, never check the Gravatar service as it only supports MD5 hashing. - // Unfortunately, if the FIPS setting is enabled on Windows, using MD5 will throw an exception - // and the website will not run. - // Also, check if the user has explicitly removed all avatars including a Gravatar, this will be possible and the value will be "none" - if (user.Avatar == "none" || CryptoConfig.AllowOnlyFipsAlgorithms) - { - return new string[0]; - } + return new string[0]; + } - if (user.Avatar.IsNullOrWhiteSpace()) - { - var gravatarHash = user.Email?.GenerateHash(); - var gravatarUrl = "https://www.gravatar.com/avatar/" + gravatarHash + "?d=404"; + if (user.Avatar.IsNullOrWhiteSpace()) + { + var gravatarHash = user.Email?.GenerateHash(); + var gravatarUrl = "https://www.gravatar.com/avatar/" + gravatarHash + "?d=404"; - //try Gravatar - var gravatarAccess = cache.GetCacheItem("UserAvatar" + user.Id, () => + //try Gravatar + var gravatarAccess = cache.GetCacheItem("UserAvatar" + user.Id, () => + { + // Test if we can reach this URL, will fail when there's network or firewall errors + var request = (HttpWebRequest)WebRequest.Create(gravatarUrl); + // Require response within 10 seconds + request.Timeout = 10000; + try { - // Test if we can reach this URL, will fail when there's network or firewall errors - var request = (HttpWebRequest)WebRequest.Create(gravatarUrl); - // Require response within 10 seconds - request.Timeout = 10000; - try - { - using ((HttpWebResponse)request.GetResponse()) { } - } - catch (Exception) - { - // There was an HTTP or other error, return an null instead - return false; - } - return true; - }); - - if (gravatarAccess) + using ((HttpWebResponse)request.GetResponse()) { } + } + catch (Exception) { - return new[] - { - gravatarUrl + "&s=30", - gravatarUrl + "&s=60", - gravatarUrl + "&s=90", - gravatarUrl + "&s=150", - gravatarUrl + "&s=300" - }; + // There was an HTTP or other error, return an null instead + return false; } - return new string[0]; - } + return true; + }); - //use the custom avatar - var avatarUrl = mediaFileManager.FileSystem.GetUrl(user.Avatar); - return new[] + if (gravatarAccess) { - imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = ImageCropMode.Crop, Width = 30, Height = 30 }), - imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = ImageCropMode.Crop, Width = 60, Height = 60 }), - imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = ImageCropMode.Crop, Width = 90, Height = 90 }), - imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = ImageCropMode.Crop, Width = 150, Height = 150 }), - imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { ImageCropMode = ImageCropMode.Crop, Width = 300, Height = 300 }), - }.WhereNotNull().ToArray(); + return new[] + { + gravatarUrl + "&s=30", gravatarUrl + "&s=60", gravatarUrl + "&s=90", gravatarUrl + "&s=150", + gravatarUrl + "&s=300" + }; + } + return new string[0]; } + //use the custom avatar + var avatarUrl = mediaFileManager.FileSystem.GetUrl(user.Avatar); + return new[] + { + imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) + { + ImageCropMode = ImageCropMode.Crop, Width = 30, Height = 30 + }), + imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) + { + ImageCropMode = ImageCropMode.Crop, Width = 60, Height = 60 + }), + imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) + { + ImageCropMode = ImageCropMode.Crop, Width = 90, Height = 90 + }), + imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) + { + ImageCropMode = ImageCropMode.Crop, Width = 150, Height = 150 + }), + imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) + { + ImageCropMode = ImageCropMode.Crop, Width = 300, Height = 300 + }) + }.WhereNotNull().ToArray(); + } - internal static bool HasContentRootAccess(this IUser user, IEntityService entityService, AppCaches appCaches) - { - return ContentPermissions.HasPathAccess(Constants.System.RootString, user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent); - } + internal static bool HasContentRootAccess(this IUser user, IEntityService entityService, AppCaches appCaches) => + ContentPermissions.HasPathAccess(Constants.System.RootString, + user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent); - internal static bool HasContentBinAccess(this IUser user, IEntityService entityService, AppCaches appCaches) - { - return ContentPermissions.HasPathAccess(Constants.System.RecycleBinContentString, user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent); - } + internal static bool HasContentBinAccess(this IUser user, IEntityService entityService, AppCaches appCaches) => + ContentPermissions.HasPathAccess(Constants.System.RecycleBinContentString, + user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent); - internal static bool HasMediaRootAccess(this IUser user, IEntityService entityService, AppCaches appCaches) - { - return ContentPermissions.HasPathAccess(Constants.System.RootString, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia); - } + internal static bool HasMediaRootAccess(this IUser user, IEntityService entityService, AppCaches appCaches) => + ContentPermissions.HasPathAccess(Constants.System.RootString, + user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia); - internal static bool HasMediaBinAccess(this IUser user, IEntityService entityService, AppCaches appCaches) - { - return ContentPermissions.HasPathAccess(Constants.System.RecycleBinMediaString, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia); - } + internal static bool HasMediaBinAccess(this IUser user, IEntityService entityService, AppCaches appCaches) => + ContentPermissions.HasPathAccess(Constants.System.RecycleBinMediaString, + user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia); - public static bool HasPathAccess(this IUser user, IContent content, IEntityService entityService, AppCaches appCaches) + public static bool HasPathAccess(this IUser user, IContent content, IEntityService entityService, + AppCaches appCaches) + { + if (content == null) { - if (content == null) throw new ArgumentNullException(nameof(content)); - return ContentPermissions.HasPathAccess(content.Path, user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent); + throw new ArgumentNullException(nameof(content)); } - public static bool HasPathAccess(this IUser user, IMedia? media, IEntityService entityService, AppCaches appCaches) + return ContentPermissions.HasPathAccess(content.Path, + user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent); + } + + public static bool HasPathAccess(this IUser user, IMedia? media, IEntityService entityService, AppCaches appCaches) + { + if (media == null) { - if (media == null) throw new ArgumentNullException(nameof(media)); - return ContentPermissions.HasPathAccess(media.Path, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia); + throw new ArgumentNullException(nameof(media)); } - public static bool HasContentPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService, AppCaches appCaches) + return ContentPermissions.HasPathAccess(media.Path, user.CalculateMediaStartNodeIds(entityService, appCaches), + Constants.System.RecycleBinMedia); + } + + public static bool HasContentPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService, + AppCaches appCaches) + { + if (entity == null) { - if (entity == null) throw new ArgumentNullException(nameof(entity)); - return ContentPermissions.HasPathAccess(entity.Path, user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent); + throw new ArgumentNullException(nameof(entity)); } - public static bool HasMediaPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService, AppCaches appCaches) + return ContentPermissions.HasPathAccess(entity.Path, + user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent); + } + + public static bool HasMediaPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService, + AppCaches appCaches) + { + if (entity == null) { - if (entity == null) throw new ArgumentNullException(nameof(entity)); - return ContentPermissions.HasPathAccess(entity.Path, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia); + throw new ArgumentNullException(nameof(entity)); } - /// - /// Determines whether this user has access to view sensitive data - /// - /// - public static bool HasAccessToSensitiveData(this IUser user) + return ContentPermissions.HasPathAccess(entity.Path, user.CalculateMediaStartNodeIds(entityService, appCaches), + Constants.System.RecycleBinMedia); + } + + /// + /// Determines whether this user has access to view sensitive data + /// + /// + public static bool HasAccessToSensitiveData(this IUser user) + { + if (user == null) { - if (user == null) throw new ArgumentNullException("user"); - return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.SensitiveDataGroupAlias); + throw new ArgumentNullException("user"); } - /// - /// Calculate start nodes, combining groups' and user's, and excluding what's in the bin - /// - public static int[]? CalculateContentStartNodeIds(this IUser user, IEntityService entityService, AppCaches appCaches) + return user.Groups != null && user.Groups.Any(x => x.Alias == Constants.Security.SensitiveDataGroupAlias); + } + + /// + /// Calculate start nodes, combining groups' and user's, and excluding what's in the bin + /// + public static int[]? CalculateContentStartNodeIds(this IUser user, IEntityService entityService, + AppCaches appCaches) + { + var cacheKey = CacheKeys.UserAllContentStartNodesPrefix + user.Id; + IAppPolicyCache runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); + var result = runtimeCache.GetCacheItem(cacheKey, () => { - var cacheKey = CacheKeys.UserAllContentStartNodesPrefix + user.Id; - var runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); - var result = runtimeCache.GetCacheItem(cacheKey, () => + // This returns a nullable array even though we're checking if items have value and there cannot be null + // We use Cast to recast into non-nullable array + var gsn = user.Groups.Where(x => x.StartContentId is not null).Select(x => x.StartContentId).Distinct() + .Cast().ToArray(); + var usn = user.StartContentIds; + if (usn is not null) { - // This returns a nullable array even though we're checking if items have value and there cannot be null - // We use Cast to recast into non-nullable array - var gsn = user.Groups.Where(x => x.StartContentId is not null).Select(x => x.StartContentId).Distinct().Cast().ToArray(); - var usn = user.StartContentIds; - if (usn is not null) - { - var vals = CombineStartNodes(UmbracoObjectTypes.Document, gsn, usn, entityService); - return vals; - } + var vals = CombineStartNodes(UmbracoObjectTypes.Document, gsn, usn, entityService); + return vals; + } - return null; - }, TimeSpan.FromMinutes(2), true); + return null; + }, TimeSpan.FromMinutes(2), true); - return result; - } + return result; + } - /// - /// Calculate start nodes, combining groups' and user's, and excluding what's in the bin - /// - /// - /// - /// - /// - public static int[]? CalculateMediaStartNodeIds(this IUser user, IEntityService entityService, AppCaches appCaches) + /// + /// Calculate start nodes, combining groups' and user's, and excluding what's in the bin + /// + /// + /// + /// + /// + public static int[]? CalculateMediaStartNodeIds(this IUser user, IEntityService entityService, AppCaches appCaches) + { + var cacheKey = CacheKeys.UserAllMediaStartNodesPrefix + user.Id; + IAppPolicyCache runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); + var result = runtimeCache.GetCacheItem(cacheKey, () => { - var cacheKey = CacheKeys.UserAllMediaStartNodesPrefix + user.Id; - var runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); - var result = runtimeCache.GetCacheItem(cacheKey, () => + var gsn = user.Groups.Where(x => x.StartMediaId.HasValue).Select(x => x.StartMediaId!.Value).Distinct() + .ToArray(); + var usn = user.StartMediaIds; + if (usn is not null) { - var gsn = user.Groups.Where(x => x.StartMediaId.HasValue).Select(x => x.StartMediaId!.Value).Distinct().ToArray(); - var usn = user.StartMediaIds; - if (usn is not null) - { - var vals = CombineStartNodes(UmbracoObjectTypes.Media, gsn, usn, entityService); - return vals; - } + var vals = CombineStartNodes(UmbracoObjectTypes.Media, gsn, usn, entityService); + return vals; + } - return null; - }, TimeSpan.FromMinutes(2), true); + return null; + }, TimeSpan.FromMinutes(2), true); - return result; - } + return result; + } - public static string[]? GetMediaStartNodePaths(this IUser user, IEntityService entityService, AppCaches appCaches) + public static string[]? GetMediaStartNodePaths(this IUser user, IEntityService entityService, AppCaches appCaches) + { + var cacheKey = CacheKeys.UserMediaStartNodePathsPrefix + user.Id; + IAppPolicyCache runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); + var result = runtimeCache.GetCacheItem(cacheKey, () => { - var cacheKey = CacheKeys.UserMediaStartNodePathsPrefix + user.Id; - var runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); - var result = runtimeCache.GetCacheItem(cacheKey, () => - { - var startNodeIds = user.CalculateMediaStartNodeIds(entityService, appCaches); - var vals = entityService.GetAllPaths(UmbracoObjectTypes.Media, startNodeIds).Select(x => x.Path).ToArray(); - return vals; - }, TimeSpan.FromMinutes(2), true); + var startNodeIds = user.CalculateMediaStartNodeIds(entityService, appCaches); + var vals = entityService.GetAllPaths(UmbracoObjectTypes.Media, startNodeIds).Select(x => x.Path).ToArray(); + return vals; + }, TimeSpan.FromMinutes(2), true); - return result; - } + return result; + } - public static string[]? GetContentStartNodePaths(this IUser user, IEntityService entityService, AppCaches appCaches) + public static string[]? GetContentStartNodePaths(this IUser user, IEntityService entityService, AppCaches appCaches) + { + var cacheKey = CacheKeys.UserContentStartNodePathsPrefix + user.Id; + IAppPolicyCache runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); + var result = runtimeCache.GetCacheItem(cacheKey, () => { - var cacheKey = CacheKeys.UserContentStartNodePathsPrefix + user.Id; - var runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); - var result = runtimeCache.GetCacheItem(cacheKey, () => - { - var startNodeIds = user.CalculateContentStartNodeIds(entityService, appCaches); - var vals = entityService.GetAllPaths(UmbracoObjectTypes.Document, startNodeIds).Select(x => x.Path).ToArray(); - return vals; - }, TimeSpan.FromMinutes(2), true); + var startNodeIds = user.CalculateContentStartNodeIds(entityService, appCaches); + var vals = entityService.GetAllPaths(UmbracoObjectTypes.Document, startNodeIds).Select(x => x.Path) + .ToArray(); + return vals; + }, TimeSpan.FromMinutes(2), true); - return result; - } + return result; + } - private static bool StartsWithPath(string test, string path) - { - return test.StartsWith(path) && test.Length > path.Length && test[path.Length] == ','; - } + private static bool StartsWithPath(string test, string path) => + test.StartsWith(path) && test.Length > path.Length && test[path.Length] == ','; - private static string GetBinPath(UmbracoObjectTypes objectType) + private static string GetBinPath(UmbracoObjectTypes objectType) + { + var binPath = Constants.System.RootString + ","; + switch (objectType) { - var binPath = Constants.System.RootString + ","; - switch (objectType) - { - case UmbracoObjectTypes.Document: - binPath += Constants.System.RecycleBinContentString; - break; - case UmbracoObjectTypes.Media: - binPath += Constants.System.RecycleBinMediaString; - break; - default: - throw new ArgumentOutOfRangeException(nameof(objectType)); - } - return binPath; + case UmbracoObjectTypes.Document: + binPath += Constants.System.RecycleBinContentString; + break; + case UmbracoObjectTypes.Media: + binPath += Constants.System.RecycleBinMediaString; + break; + default: + throw new ArgumentOutOfRangeException(nameof(objectType)); } - internal static int[] CombineStartNodes(UmbracoObjectTypes objectType, int[] groupSn, int[] userSn, IEntityService entityService) - { - // assume groupSn and userSn each don't contain duplicates + return binPath; + } - var asn = groupSn.Concat(userSn).Distinct().ToArray(); - var paths = asn.Length > 0 - ? entityService.GetAllPaths(objectType, asn).ToDictionary(x => x.Id, x => x.Path) - : new Dictionary(); + internal static int[] CombineStartNodes(UmbracoObjectTypes objectType, int[] groupSn, int[] userSn, + IEntityService entityService) + { + // assume groupSn and userSn each don't contain duplicates - paths[Constants.System.Root] = Constants.System.RootString; // entityService does not get that one + var asn = groupSn.Concat(userSn).Distinct().ToArray(); + Dictionary paths = asn.Length > 0 + ? entityService.GetAllPaths(objectType, asn).ToDictionary(x => x.Id, x => x.Path) + : new Dictionary(); - var binPath = GetBinPath(objectType); + paths[Constants.System.Root] = Constants.System.RootString; // entityService does not get that one - var lsn = new List(); - foreach (var sn in groupSn) - { - if (paths.TryGetValue(sn, out var snp) == false) continue; // ignore rogue node (no path) + var binPath = GetBinPath(objectType); - if (StartsWithPath(snp, binPath)) continue; // ignore bin + var lsn = new List(); + foreach (var sn in groupSn) + { + if (paths.TryGetValue(sn, out var snp) == false) + { + continue; // ignore rogue node (no path) + } - if (lsn.Any(x => StartsWithPath(snp, paths[x]))) continue; // skip if something above this sn - lsn.RemoveAll(x => StartsWithPath(paths[x], snp)); // remove anything below this sn - lsn.Add(sn); + if (StartsWithPath(snp, binPath)) + { + continue; // ignore bin } - var usn = new List(); - foreach (var sn in userSn) + if (lsn.Any(x => StartsWithPath(snp, paths[x]))) { - if (paths.TryGetValue(sn, out var snp) == false) continue; // ignore rogue node (no path) + continue; // skip if something above this sn + } - if (StartsWithPath(snp, binPath)) continue; // ignore bin + lsn.RemoveAll(x => StartsWithPath(paths[x], snp)); // remove anything below this sn + lsn.Add(sn); + } - if (usn.Any(x => StartsWithPath(paths[x], snp))) continue; // skip if something below this sn - usn.RemoveAll(x => StartsWithPath(snp, paths[x])); // remove anything above this sn - usn.Add(sn); + var usn = new List(); + foreach (var sn in userSn) + { + if (paths.TryGetValue(sn, out var snp) == false) + { + continue; // ignore rogue node (no path) } - foreach (var sn in usn) + if (StartsWithPath(snp, binPath)) { - var snp = paths[sn]; // has to be here now - lsn.RemoveAll(x => StartsWithPath(snp, paths[x]) || StartsWithPath(paths[x], snp)); // remove anything above or below this sn - lsn.Add(sn); + continue; // ignore bin } - return lsn.ToArray(); + if (usn.Any(x => StartsWithPath(paths[x], snp))) + { + continue; // skip if something below this sn + } + + usn.RemoveAll(x => StartsWithPath(snp, paths[x])); // remove anything above this sn + usn.Add(sn); } + + foreach (var sn in usn) + { + var snp = paths[sn]; // has to be here now + lsn.RemoveAll(x => + StartsWithPath(snp, paths[x]) || + StartsWithPath(paths[x], snp)); // remove anything above or below this sn + lsn.Add(sn); + } + + return lsn.ToArray(); } } diff --git a/src/Umbraco.Core/Models/UserTourStatus.cs b/src/Umbraco.Core/Models/UserTourStatus.cs index 72e0a81cbafd..ef076ca23423 100644 --- a/src/Umbraco.Core/Models/UserTourStatus.cs +++ b/src/Umbraco.Core/Models/UserTourStatus.cs @@ -1,60 +1,69 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// A model representing the tours a user has taken/completed +/// +[DataContract(Name = "userTourStatus", Namespace = "")] +public class UserTourStatus : IEquatable { /// - /// A model representing the tours a user has taken/completed + /// The tour alias + /// + [DataMember(Name = "alias")] + public string Alias { get; set; } = string.Empty; + + /// + /// If the tour is completed /// - [DataContract(Name = "userTourStatus", Namespace = "")] - public class UserTourStatus : IEquatable + [DataMember(Name = "completed")] + public bool Completed { get; set; } + + /// + /// If the tour is disabled + /// + [DataMember(Name = "disabled")] + public bool Disabled { get; set; } + + public bool Equals(UserTourStatus? other) { - /// - /// The tour alias - /// - [DataMember(Name = "alias")] - public string Alias { get; set; } = string.Empty; - - /// - /// If the tour is completed - /// - [DataMember(Name = "completed")] - public bool Completed { get; set; } - - /// - /// If the tour is disabled - /// - [DataMember(Name = "disabled")] - public bool Disabled { get; set; } - - public bool Equals(UserTourStatus? other) + if (ReferenceEquals(null, other)) { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return string.Equals(Alias, other.Alias); + return false; } - public override bool Equals(object? obj) + if (ReferenceEquals(this, other)) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((UserTourStatus) obj); + return true; } - public override int GetHashCode() + return string.Equals(Alias, other.Alias); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) { - return Alias.GetHashCode(); + return false; } - public static bool operator ==(UserTourStatus? left, UserTourStatus? right) + if (ReferenceEquals(this, obj)) { - return Equals(left, right); + return true; } - public static bool operator !=(UserTourStatus? left, UserTourStatus? right) + if (obj.GetType() != GetType()) { - return !Equals(left, right); + return false; } + + return Equals((UserTourStatus)obj); } + + public override int GetHashCode() => Alias.GetHashCode(); + + public static bool operator ==(UserTourStatus? left, UserTourStatus? right) => Equals(left, right); + + public static bool operator !=(UserTourStatus? left, UserTourStatus? right) => !Equals(left, right); } diff --git a/src/Umbraco.Core/Models/UserTwoFactorProviderModel.cs b/src/Umbraco.Core/Models/UserTwoFactorProviderModel.cs index 095d4f50a954..d79aefd14e04 100644 --- a/src/Umbraco.Core/Models/UserTwoFactorProviderModel.cs +++ b/src/Umbraco.Core/Models/UserTwoFactorProviderModel.cs @@ -1,20 +1,17 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +[DataContract] +public class UserTwoFactorProviderModel { - [DataContract] - public class UserTwoFactorProviderModel + public UserTwoFactorProviderModel(string providerName, bool isEnabledOnUser) { - public UserTwoFactorProviderModel(string providerName, bool isEnabledOnUser) - { - ProviderName = providerName; - IsEnabledOnUser = isEnabledOnUser; - } + ProviderName = providerName; + IsEnabledOnUser = isEnabledOnUser; + } - [DataMember(Name = "providerName")] - public string ProviderName { get; } + [DataMember(Name = "providerName")] public string ProviderName { get; } - [DataMember(Name = "isEnabledOnUser")] - public bool IsEnabledOnUser { get; } - } + [DataMember(Name = "isEnabledOnUser")] public bool IsEnabledOnUser { get; } } diff --git a/src/Umbraco.Core/Models/ValidatePasswordResetCodeModel.cs b/src/Umbraco.Core/Models/ValidatePasswordResetCodeModel.cs index d104383b38c7..f892b51f44c2 100644 --- a/src/Umbraco.Core/Models/ValidatePasswordResetCodeModel.cs +++ b/src/Umbraco.Core/Models/ValidatePasswordResetCodeModel.cs @@ -1,19 +1,17 @@ -using System; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +[Serializable] +[DataContract(Name = "validatePasswordReset", Namespace = "")] +public class ValidatePasswordResetCodeModel { - [Serializable] - [DataContract(Name = "validatePasswordReset", Namespace = "")] - public class ValidatePasswordResetCodeModel - { - [Required] - [DataMember(Name = "userId", IsRequired = true)] - public int UserId { get; set; } + [Required] + [DataMember(Name = "userId", IsRequired = true)] + public int UserId { get; set; } - [Required] - [DataMember(Name = "resetCode", IsRequired = true)] - public string? ResetCode { get; set; } - } + [Required] + [DataMember(Name = "resetCode", IsRequired = true)] + public string? ResetCode { get; set; } } diff --git a/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs b/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs index 10133a7f3694..af969154b01a 100644 --- a/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs +++ b/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs @@ -1,31 +1,32 @@ using System.ComponentModel.DataAnnotations; -using System.Linq; using System.Reflection; using Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.Models.Validation +namespace Umbraco.Cms.Core.Models.Validation; + +/// +/// Specifies that a data field value is required in order to persist an object. +/// +/// +/// +/// There are two levels of validation in Umbraco. (1) value validation is performed by +/// +/// instances; it can prevent a content item from being published, but not from being saved. (2) required +/// validation +/// of properties marked with ; it does prevent an object from being +/// saved +/// and is used for properties that are absolutely mandatory, such as the name of a content item. +/// +/// +public class RequiredForPersistenceAttribute : RequiredAttribute { /// - /// Specifies that a data field value is required in order to persist an object. + /// Determines whether an object has all required values for persistence. /// - /// - /// There are two levels of validation in Umbraco. (1) value validation is performed by - /// instances; it can prevent a content item from being published, but not from being saved. (2) required validation - /// of properties marked with ; it does prevent an object from being saved - /// and is used for properties that are absolutely mandatory, such as the name of a content item. - /// - public class RequiredForPersistenceAttribute : RequiredAttribute - { - /// - /// Determines whether an object has all required values for persistence. - /// - public static bool HasRequiredValuesForPersistence(object model) + public static bool HasRequiredValuesForPersistence(object model) => + model.GetType().GetProperties().All(x => { - return model.GetType().GetProperties().All(x => - { - var a = x.GetCustomAttribute(); - return a == null || a.IsValid(x.GetValue(model)); - }); - } - } + RequiredForPersistenceAttribute a = x.GetCustomAttribute(); + return a == null || a.IsValid(x.GetValue(model)); + }); } diff --git a/src/Umbraco.Core/Models/ValueStorageType.cs b/src/Umbraco.Core/Models/ValueStorageType.cs index cca84b72b795..ae83356f95ee 100644 --- a/src/Umbraco.Core/Models/ValueStorageType.cs +++ b/src/Umbraco.Core/Models/ValueStorageType.cs @@ -1,47 +1,40 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Models +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents the supported database types for storing a value. +/// +[Serializable] +[DataContract] +public enum ValueStorageType { + // note: these values are written out in the database in some places, + // and then parsed back in a case-sensitive way - think about it before + // changing the casing of values. + /// - /// Represents the supported database types for storing a value. + /// Store property value as NText. /// - [Serializable] - [DataContract] - public enum ValueStorageType - { - // note: these values are written out in the database in some places, - // and then parsed back in a case-sensitive way - think about it before - // changing the casing of values. - - /// - /// Store property value as NText. - /// - [EnumMember] - Ntext, + [EnumMember] Ntext, - /// - /// Store property value as NVarChar. - /// - [EnumMember] - Nvarchar, + /// + /// Store property value as NVarChar. + /// + [EnumMember] Nvarchar, - /// - /// Store property value as Integer. - /// - [EnumMember] - Integer, + /// + /// Store property value as Integer. + /// + [EnumMember] Integer, - /// - /// Store property value as Date. - /// - [EnumMember] - Date, + /// + /// Store property value as Date. + /// + [EnumMember] Date, - /// - /// Store property value as Decimal. - /// - [EnumMember] - Decimal - } + /// + /// Store property value as Decimal. + /// + [EnumMember] Decimal } diff --git a/src/Umbraco.Core/MonitorLock.cs b/src/Umbraco.Core/MonitorLock.cs index 11651aaa6ce4..3ab559f72723 100644 --- a/src/Umbraco.Core/MonitorLock.cs +++ b/src/Umbraco.Core/MonitorLock.cs @@ -1,32 +1,31 @@ -using System; +namespace Umbraco.Cms.Core; -namespace Umbraco.Cms.Core +/// +/// Provides an equivalent to the c# lock statement, to be used in a using block. +/// +/// Ie replace lock (o) {...} by using (new MonitorLock(o)) { ... } +public class MonitorLock : IDisposable { + private readonly bool _entered; + private readonly object _locker; + /// - /// Provides an equivalent to the c# lock statement, to be used in a using block. + /// Initializes a new instance of the class with an object to lock. /// - /// Ie replace lock (o) {...} by using (new MonitorLock(o)) { ... } - public class MonitorLock : IDisposable + /// The object to lock. + /// Should always be used within a using block. + public MonitorLock(object locker) { - private readonly object _locker; - private readonly bool _entered; - - /// - /// Initializes a new instance of the class with an object to lock. - /// - /// The object to lock. - /// Should always be used within a using block. - public MonitorLock(object locker) - { - _locker = locker; - _entered = false; - System.Threading.Monitor.Enter(_locker, ref _entered); - } + _locker = locker; + _entered = false; + Monitor.Enter(_locker, ref _entered); + } - void IDisposable.Dispose() + void IDisposable.Dispose() + { + if (_entered) { - if (_entered) - System.Threading.Monitor.Exit(_locker); + Monitor.Exit(_locker); } } } diff --git a/src/Umbraco.Core/NamedUdiRange.cs b/src/Umbraco.Core/NamedUdiRange.cs index 5855f279266a..becc6f58b57d 100644 --- a/src/Umbraco.Core/NamedUdiRange.cs +++ b/src/Umbraco.Core/NamedUdiRange.cs @@ -1,34 +1,34 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Represents a complemented with a name. +/// +public class NamedUdiRange : UdiRange { /// - /// Represents a complemented with a name. + /// Initializes a new instance of the class with a and an optional + /// selector. /// - public class NamedUdiRange : UdiRange + /// A . + /// An optional selector. + public NamedUdiRange(Udi udi, string selector = Constants.DeploySelector.This) + : base(udi, selector) { - /// - /// Initializes a new instance of the class with a and an optional selector. - /// - /// A . - /// An optional selector. - public NamedUdiRange(Udi udi, string selector = Constants.DeploySelector.This) - : base(udi, selector) - { } + } - /// - /// Initializes a new instance of the class with a , a name, and an optional selector. - /// - /// A . - /// A name. - /// An optional selector. - public NamedUdiRange(Udi udi, string name, string selector = Constants.DeploySelector.This) - : base(udi, selector) - { - Name = name; - } + /// + /// Initializes a new instance of the class with a , a name, and an + /// optional selector. + /// + /// A . + /// A name. + /// An optional selector. + public NamedUdiRange(Udi udi, string name, string selector = Constants.DeploySelector.This) + : base(udi, selector) => + Name = name; - /// - /// Gets or sets the name of the range. - /// - public string? Name { get; set; } - } + /// + /// Gets or sets the name of the range. + /// + public string? Name { get; set; } } diff --git a/src/Umbraco.Core/Net/IIpResolver.cs b/src/Umbraco.Core/Net/IIpResolver.cs index 6c7ab72deca1..edc9c6428c86 100644 --- a/src/Umbraco.Core/Net/IIpResolver.cs +++ b/src/Umbraco.Core/Net/IIpResolver.cs @@ -1,7 +1,6 @@ -namespace Umbraco.Cms.Core.Net +namespace Umbraco.Cms.Core.Net; + +public interface IIpResolver { - public interface IIpResolver - { - string GetCurrentRequestIpAddress(); - } + string GetCurrentRequestIpAddress(); } diff --git a/src/Umbraco.Core/Net/ISessionIdResolver.cs b/src/Umbraco.Core/Net/ISessionIdResolver.cs index f5d6b4de290c..4ec6248b3958 100644 --- a/src/Umbraco.Core/Net/ISessionIdResolver.cs +++ b/src/Umbraco.Core/Net/ISessionIdResolver.cs @@ -1,7 +1,6 @@ -namespace Umbraco.Cms.Core.Net +namespace Umbraco.Cms.Core.Net; + +public interface ISessionIdResolver { - public interface ISessionIdResolver - { - string? SessionId { get; } - } + string? SessionId { get; } } diff --git a/src/Umbraco.Core/Net/IUserAgentProvider.cs b/src/Umbraco.Core/Net/IUserAgentProvider.cs index ba4f61b89796..6916ee8d374b 100644 --- a/src/Umbraco.Core/Net/IUserAgentProvider.cs +++ b/src/Umbraco.Core/Net/IUserAgentProvider.cs @@ -1,7 +1,6 @@ -namespace Umbraco.Cms.Core.Net +namespace Umbraco.Cms.Core.Net; + +public interface IUserAgentProvider { - public interface IUserAgentProvider - { - string? GetUserAgent(); - } + string? GetUserAgent(); } diff --git a/src/Umbraco.Core/Net/NullSessionIdResolver.cs b/src/Umbraco.Core/Net/NullSessionIdResolver.cs index 207a9c6048bc..79ea1457c863 100644 --- a/src/Umbraco.Core/Net/NullSessionIdResolver.cs +++ b/src/Umbraco.Core/Net/NullSessionIdResolver.cs @@ -1,7 +1,6 @@ -namespace Umbraco.Cms.Core.Net +namespace Umbraco.Cms.Core.Net; + +public class NullSessionIdResolver : ISessionIdResolver { - public class NullSessionIdResolver : ISessionIdResolver - { - public string? SessionId => null; - } + public string? SessionId => null; } diff --git a/src/Umbraco.Core/Notifications/ApplicationCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/ApplicationCacheRefresherNotification.cs index eb596a3a0b96..57487ff4d456 100644 --- a/src/Umbraco.Core/Notifications/ApplicationCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/ApplicationCacheRefresherNotification.cs @@ -1,11 +1,11 @@ using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class ApplicationCacheRefresherNotification : CacheRefresherNotification { - public class ApplicationCacheRefresherNotification : CacheRefresherNotification + public ApplicationCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + messageType) { - public ApplicationCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) - { - } } } diff --git a/src/Umbraco.Core/Notifications/AssignedMemberRolesNotification.cs b/src/Umbraco.Core/Notifications/AssignedMemberRolesNotification.cs index adcf14d6364b..7a25295e9e83 100644 --- a/src/Umbraco.Core/Notifications/AssignedMemberRolesNotification.cs +++ b/src/Umbraco.Core/Notifications/AssignedMemberRolesNotification.cs @@ -1,10 +1,8 @@ -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class AssignedMemberRolesNotification : MemberRolesNotification { - public class AssignedMemberRolesNotification : MemberRolesNotification + public AssignedMemberRolesNotification(int[] memberIds, string[] roles) : base(memberIds, roles) { - public AssignedMemberRolesNotification(int[] memberIds, string[] roles) : base(memberIds, roles) - { - - } } } diff --git a/src/Umbraco.Core/Notifications/AssignedUserGroupPermissionsNotification.cs b/src/Umbraco.Core/Notifications/AssignedUserGroupPermissionsNotification.cs index 18425f23933d..ef0a28d3866e 100644 --- a/src/Umbraco.Core/Notifications/AssignedUserGroupPermissionsNotification.cs +++ b/src/Umbraco.Core/Notifications/AssignedUserGroupPermissionsNotification.cs @@ -1,15 +1,14 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class AssignedUserGroupPermissionsNotification : EnumerableObjectNotification { - public class AssignedUserGroupPermissionsNotification : EnumerableObjectNotification + public AssignedUserGroupPermissionsNotification(IEnumerable target, EventMessages messages) : + base(target, messages) { - public AssignedUserGroupPermissionsNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } - - public IEnumerable EntityPermissions => Target; } + + public IEnumerable EntityPermissions => Target; } diff --git a/src/Umbraco.Core/Notifications/CacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/CacheRefresherNotification.cs index bd110ad8786c..e07e016888fd 100644 --- a/src/Umbraco.Core/Notifications/CacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/CacheRefresherNotification.cs @@ -1,20 +1,18 @@ -using System; using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +/// +/// Base class for cache refresher notifications +/// +public abstract class CacheRefresherNotification : INotification { - /// - /// Base class for cache refresher notifications - /// - public abstract class CacheRefresherNotification : INotification + public CacheRefresherNotification(object messageObject, MessageType messageType) { - public CacheRefresherNotification(object messageObject, MessageType messageType) - { - MessageObject = messageObject ?? throw new ArgumentNullException(nameof(messageObject)); - MessageType = messageType; - } - - public object MessageObject { get; } - public MessageType MessageType { get; } + MessageObject = messageObject ?? throw new ArgumentNullException(nameof(messageObject)); + MessageType = messageType; } + + public object MessageObject { get; } + public MessageType MessageType { get; } } diff --git a/src/Umbraco.Core/Notifications/CancelableEnumerableObjectNotification.cs b/src/Umbraco.Core/Notifications/CancelableEnumerableObjectNotification.cs index ea7476cd3f73..49e845f386bf 100644 --- a/src/Umbraco.Core/Notifications/CancelableEnumerableObjectNotification.cs +++ b/src/Umbraco.Core/Notifications/CancelableEnumerableObjectNotification.cs @@ -1,18 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class CancelableEnumerableObjectNotification : CancelableObjectNotification> { - public abstract class CancelableEnumerableObjectNotification : CancelableObjectNotification> + protected CancelableEnumerableObjectNotification(T target, EventMessages messages) : base(new[] {target}, messages) + { + } + + protected CancelableEnumerableObjectNotification(IEnumerable target, EventMessages messages) : base(target, + messages) { - protected CancelableEnumerableObjectNotification(T target, EventMessages messages) : base(new [] {target}, messages) - { - } - protected CancelableEnumerableObjectNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/CancelableNotification.cs b/src/Umbraco.Core/Notifications/CancelableNotification.cs index 13989d50dad2..438bc1ee99d3 100644 --- a/src/Umbraco.Core/Notifications/CancelableNotification.cs +++ b/src/Umbraco.Core/Notifications/CancelableNotification.cs @@ -1,20 +1,19 @@ using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class CancelableNotification : StatefulNotification, ICancelableNotification { - public class CancelableNotification : StatefulNotification, ICancelableNotification - { - public CancelableNotification(EventMessages messages) => Messages = messages; + public CancelableNotification(EventMessages messages) => Messages = messages; - public EventMessages Messages { get; } + public EventMessages Messages { get; } - public bool Cancel { get; set; } + public bool Cancel { get; set; } - public void CancelOperation(EventMessage cancellationMessage) - { - Cancel = true; - cancellationMessage.IsDefaultEventMessage = true; - Messages.Add(cancellationMessage); - } + public void CancelOperation(EventMessage cancellationMessage) + { + Cancel = true; + cancellationMessage.IsDefaultEventMessage = true; + Messages.Add(cancellationMessage); } } diff --git a/src/Umbraco.Core/Notifications/CancelableObjectNotification.cs b/src/Umbraco.Core/Notifications/CancelableObjectNotification.cs index 25f6a4474fe0..d333f0cc3c77 100644 --- a/src/Umbraco.Core/Notifications/CancelableObjectNotification.cs +++ b/src/Umbraco.Core/Notifications/CancelableObjectNotification.cs @@ -3,21 +3,20 @@ using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class CancelableObjectNotification : ObjectNotification, ICancelableNotification where T : class { - public abstract class CancelableObjectNotification : ObjectNotification, ICancelableNotification where T : class + protected CancelableObjectNotification(T target, EventMessages messages) : base(target, messages) { - protected CancelableObjectNotification(T target, EventMessages messages) : base(target, messages) - { - } + } - public bool Cancel { get; set; } + public bool Cancel { get; set; } - public void CancelOperation(EventMessage cancelationMessage) - { - Cancel = true; - cancelationMessage.IsDefaultEventMessage = true; - Messages.Add(cancelationMessage); - } + public void CancelOperation(EventMessage cancelationMessage) + { + Cancel = true; + cancelationMessage.IsDefaultEventMessage = true; + Messages.Add(cancelationMessage); } } diff --git a/src/Umbraco.Core/Notifications/ContentCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/ContentCacheRefresherNotification.cs index 35b4f472c758..1f5eab40f4e7 100644 --- a/src/Umbraco.Core/Notifications/ContentCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentCacheRefresherNotification.cs @@ -1,11 +1,11 @@ using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class ContentCacheRefresherNotification : CacheRefresherNotification { - public class ContentCacheRefresherNotification : CacheRefresherNotification + public ContentCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + messageType) { - public ContentCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) - { - } } } diff --git a/src/Umbraco.Core/Notifications/ContentCopiedNotification.cs b/src/Umbraco.Core/Notifications/ContentCopiedNotification.cs index 6399fb714d6c..b28d6e444237 100644 --- a/src/Umbraco.Core/Notifications/ContentCopiedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentCopiedNotification.cs @@ -4,13 +4,13 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentCopiedNotification : CopiedNotification { - public sealed class ContentCopiedNotification : CopiedNotification + public ContentCopiedNotification(IContent original, IContent copy, int parentId, bool relateToOriginal, + EventMessages messages) + : base(original, copy, parentId, relateToOriginal, messages) { - public ContentCopiedNotification(IContent original, IContent copy, int parentId, bool relateToOriginal, EventMessages messages) - : base(original, copy, parentId, relateToOriginal, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/ContentCopyingNotification.cs b/src/Umbraco.Core/Notifications/ContentCopyingNotification.cs index d30d49efeb40..ef8eb48058f6 100644 --- a/src/Umbraco.Core/Notifications/ContentCopyingNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentCopyingNotification.cs @@ -4,13 +4,12 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentCopyingNotification : CopyingNotification { - public sealed class ContentCopyingNotification : CopyingNotification + public ContentCopyingNotification(IContent original, IContent copy, int parentId, EventMessages messages) + : base(original, copy, parentId, messages) { - public ContentCopyingNotification(IContent original, IContent copy, int parentId, EventMessages messages) - : base(original, copy, parentId, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/ContentDeletedBlueprintNotification.cs b/src/Umbraco.Core/Notifications/ContentDeletedBlueprintNotification.cs index 1c516a295fe9..6f36b2c2a52c 100644 --- a/src/Umbraco.Core/Notifications/ContentDeletedBlueprintNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentDeletedBlueprintNotification.cs @@ -1,22 +1,21 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentDeletedBlueprintNotification : EnumerableObjectNotification { - public sealed class ContentDeletedBlueprintNotification : EnumerableObjectNotification + public ContentDeletedBlueprintNotification(IContent target, EventMessages messages) : base(target, messages) { - public ContentDeletedBlueprintNotification(IContent target, EventMessages messages) : base(target, messages) - { - } - - public ContentDeletedBlueprintNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + } - public IEnumerable DeletedBlueprints => Target; + public ContentDeletedBlueprintNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } + + public IEnumerable DeletedBlueprints => Target; } diff --git a/src/Umbraco.Core/Notifications/ContentDeletedNotification.cs b/src/Umbraco.Core/Notifications/ContentDeletedNotification.cs index 6398c4f28e30..1a517a5c778d 100644 --- a/src/Umbraco.Core/Notifications/ContentDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentDeletedNotification.cs @@ -4,12 +4,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentDeletedNotification : DeletedNotification { - public sealed class ContentDeletedNotification : DeletedNotification + public ContentDeletedNotification(IContent target, EventMessages messages) : base(target, messages) { - public ContentDeletedNotification(IContent target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/ContentDeletedVersionsNotification.cs b/src/Umbraco.Core/Notifications/ContentDeletedVersionsNotification.cs index 30f00b52bff5..80e94eb9338f 100644 --- a/src/Umbraco.Core/Notifications/ContentDeletedVersionsNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentDeletedVersionsNotification.cs @@ -1,16 +1,16 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentDeletedVersionsNotification : DeletedVersionsNotification { - public sealed class ContentDeletedVersionsNotification : DeletedVersionsNotification + public ContentDeletedVersionsNotification(int id, EventMessages messages, int specificVersion = default, + bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, + deletePriorVersions, dateToRetain) { - public ContentDeletedVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) - { - } } } diff --git a/src/Umbraco.Core/Notifications/ContentDeletingNotification.cs b/src/Umbraco.Core/Notifications/ContentDeletingNotification.cs index ee02c6f339dd..7af4ac42eac6 100644 --- a/src/Umbraco.Core/Notifications/ContentDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentDeletingNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentDeletingNotification : DeletingNotification { - public sealed class ContentDeletingNotification : DeletingNotification + public ContentDeletingNotification(IContent target, EventMessages messages) : base(target, messages) { - public ContentDeletingNotification(IContent target, EventMessages messages) : base(target, messages) - { - } + } - public ContentDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public ContentDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/ContentDeletingVersionsNotification.cs b/src/Umbraco.Core/Notifications/ContentDeletingVersionsNotification.cs index 340aaaa55914..f97d1dd82349 100644 --- a/src/Umbraco.Core/Notifications/ContentDeletingVersionsNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentDeletingVersionsNotification.cs @@ -1,16 +1,16 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentDeletingVersionsNotification : DeletingVersionsNotification { - public sealed class ContentDeletingVersionsNotification : DeletingVersionsNotification + public ContentDeletingVersionsNotification(int id, EventMessages messages, int specificVersion = default, + bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, + deletePriorVersions, dateToRetain) { - public ContentDeletingVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) - { - } } } diff --git a/src/Umbraco.Core/Notifications/ContentEmptiedRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/ContentEmptiedRecycleBinNotification.cs index 1453553efa64..96b9e4f32ff1 100644 --- a/src/Umbraco.Core/Notifications/ContentEmptiedRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentEmptiedRecycleBinNotification.cs @@ -1,16 +1,15 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentEmptiedRecycleBinNotification : EmptiedRecycleBinNotification { - public sealed class ContentEmptiedRecycleBinNotification : EmptiedRecycleBinNotification + public ContentEmptiedRecycleBinNotification(IEnumerable deletedEntities, EventMessages messages) : base( + deletedEntities, messages) { - public ContentEmptiedRecycleBinNotification(IEnumerable deletedEntities, EventMessages messages) : base(deletedEntities, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/ContentEmptyingRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/ContentEmptyingRecycleBinNotification.cs index 134e65d98295..0aae4245c1c5 100644 --- a/src/Umbraco.Core/Notifications/ContentEmptyingRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentEmptyingRecycleBinNotification.cs @@ -1,16 +1,15 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentEmptyingRecycleBinNotification : EmptyingRecycleBinNotification { - public sealed class ContentEmptyingRecycleBinNotification : EmptyingRecycleBinNotification + public ContentEmptyingRecycleBinNotification(IEnumerable? deletedEntities, EventMessages messages) : base( + deletedEntities, messages) { - public ContentEmptyingRecycleBinNotification(IEnumerable? deletedEntities, EventMessages messages) : base(deletedEntities, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/ContentMovedNotification.cs b/src/Umbraco.Core/Notifications/ContentMovedNotification.cs index 607d6780493b..067c022ad052 100644 --- a/src/Umbraco.Core/Notifications/ContentMovedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentMovedNotification.cs @@ -1,20 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentMovedNotification : MovedNotification { - public sealed class ContentMovedNotification : MovedNotification + public ContentMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) { - public ContentMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) - { - } + } - public ContentMovedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public ContentMovedNotification(IEnumerable> target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/ContentMovedToRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/ContentMovedToRecycleBinNotification.cs index 3b736b140959..dba95a2044b3 100644 --- a/src/Umbraco.Core/Notifications/ContentMovedToRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentMovedToRecycleBinNotification.cs @@ -1,20 +1,20 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentMovedToRecycleBinNotification : MovedToRecycleBinNotification { - public sealed class ContentMovedToRecycleBinNotification : MovedToRecycleBinNotification + public ContentMovedToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(target, + messages) { - public ContentMovedToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) - { - } + } - public ContentMovedToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public ContentMovedToRecycleBinNotification(IEnumerable> target, EventMessages messages) : + base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/ContentMovingNotification.cs b/src/Umbraco.Core/Notifications/ContentMovingNotification.cs index 01c04eb226a5..533a6eee4141 100644 --- a/src/Umbraco.Core/Notifications/ContentMovingNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentMovingNotification.cs @@ -1,20 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentMovingNotification : MovingNotification { - public sealed class ContentMovingNotification : MovingNotification + public ContentMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) { - public ContentMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) - { - } + } - public ContentMovingNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public ContentMovingNotification(IEnumerable> target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/ContentMovingToRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/ContentMovingToRecycleBinNotification.cs index 88aa48c7b8b6..c43c02f103bc 100644 --- a/src/Umbraco.Core/Notifications/ContentMovingToRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentMovingToRecycleBinNotification.cs @@ -1,20 +1,20 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentMovingToRecycleBinNotification : MovingToRecycleBinNotification { - public sealed class ContentMovingToRecycleBinNotification : MovingToRecycleBinNotification + public ContentMovingToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(target, + messages) { - public ContentMovingToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) - { - } + } - public ContentMovingToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public ContentMovingToRecycleBinNotification(IEnumerable> target, EventMessages messages) : + base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/ContentNotificationExtensions.cs b/src/Umbraco.Core/Notifications/ContentNotificationExtensions.cs index c009b1cb62a4..490aab65e4c7 100644 --- a/src/Umbraco.Core/Notifications/ContentNotificationExtensions.cs +++ b/src/Umbraco.Core/Notifications/ContentNotificationExtensions.cs @@ -3,65 +3,74 @@ using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public static class ContentNotificationExtensions { - public static class ContentNotificationExtensions - { - /// - /// Determines whether a culture is being saved, during a Saving notification - /// - public static bool IsSavingCulture(this SavingNotification notification, T content, string culture) where T : IContentBase - => (content.CultureInfos?.TryGetValue(culture, out ContentCultureInfos cultureInfo) ?? false) && cultureInfo.IsDirty(); + /// + /// Determines whether a culture is being saved, during a Saving notification + /// + public static bool IsSavingCulture(this SavingNotification notification, T content, string culture) + where T : IContentBase + => (content.CultureInfos?.TryGetValue(culture, out ContentCultureInfos cultureInfo) ?? false) && + cultureInfo.IsDirty(); - /// - /// Determines whether a culture has been saved, during a Saved notification - /// - public static bool HasSavedCulture(this SavedNotification notification, T content, string culture) where T : IContentBase - => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.UpdatedCulture + culture); + /// + /// Determines whether a culture has been saved, during a Saved notification + /// + public static bool HasSavedCulture(this SavedNotification notification, T content, string culture) + where T : IContentBase + => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.UpdatedCulture + culture); - /// - /// Determines whether a culture is being published, during a Publishing notification - /// - public static bool IsPublishingCulture(this ContentPublishingNotification notification, IContent content, string culture) - => IsPublishingCulture(content, culture); + /// + /// Determines whether a culture is being published, during a Publishing notification + /// + public static bool IsPublishingCulture(this ContentPublishingNotification notification, IContent content, + string culture) + => IsPublishingCulture(content, culture); - /// - /// Determines whether a culture is being unpublished, during an Publishing notification - /// - public static bool IsUnpublishingCulture(this ContentPublishingNotification notification, IContent content, string culture) - => IsUnpublishingCulture(content, culture); + /// + /// Determines whether a culture is being unpublished, during an Publishing notification + /// + public static bool IsUnpublishingCulture(this ContentPublishingNotification notification, IContent content, + string culture) + => IsUnpublishingCulture(content, culture); - /// - /// Determines whether a culture is being unpublished, during a Unpublishing notification - /// - public static bool IsUnpublishingCulture(this ContentUnpublishingNotification notification, IContent content, string culture) - => IsUnpublishingCulture(content, culture); + /// + /// Determines whether a culture is being unpublished, during a Unpublishing notification + /// + public static bool IsUnpublishingCulture(this ContentUnpublishingNotification notification, IContent content, + string culture) + => IsUnpublishingCulture(content, culture); - /// - /// Determines whether a culture has been published, during a Published notification - /// - public static bool HasPublishedCulture(this ContentPublishedNotification notification, IContent content, string culture) - => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.ChangedCulture + culture); + /// + /// Determines whether a culture has been published, during a Published notification + /// + public static bool HasPublishedCulture(this ContentPublishedNotification notification, IContent content, + string culture) + => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.ChangedCulture + culture); - /// - /// Determines whether a culture has been unpublished, during a Published notification - /// - public static bool HasUnpublishedCulture(this ContentPublishedNotification notification, IContent content, string culture) - => HasUnpublishedCulture(content, culture); + /// + /// Determines whether a culture has been unpublished, during a Published notification + /// + public static bool HasUnpublishedCulture(this ContentPublishedNotification notification, IContent content, + string culture) + => HasUnpublishedCulture(content, culture); - /// - /// Determines whether a culture has been unpublished, during an Unpublished notification - /// - public static bool HasUnpublishedCulture(this ContentUnpublishedNotification notification, IContent content, string culture) - => HasUnpublishedCulture(content, culture); + /// + /// Determines whether a culture has been unpublished, during an Unpublished notification + /// + public static bool HasUnpublishedCulture(this ContentUnpublishedNotification notification, IContent content, + string culture) + => HasUnpublishedCulture(content, culture); - private static bool IsUnpublishingCulture(IContent content, string culture) - => content.IsPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + culture); + private static bool IsUnpublishingCulture(IContent content, string culture) + => content.IsPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + culture); - public static bool IsPublishingCulture(IContent content, string culture) - => (content.PublishCultureInfos?.TryGetValue(culture, out ContentCultureInfos cultureInfo) ?? false) && cultureInfo.IsDirty(); + public static bool IsPublishingCulture(IContent content, string culture) + => (content.PublishCultureInfos?.TryGetValue(culture, out ContentCultureInfos cultureInfo) ?? false) && + cultureInfo.IsDirty(); - public static bool HasUnpublishedCulture(IContent content, string culture) - => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + culture); - } + public static bool HasUnpublishedCulture(IContent content, string culture) + => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + culture); } diff --git a/src/Umbraco.Core/Notifications/ContentPublishedNotification.cs b/src/Umbraco.Core/Notifications/ContentPublishedNotification.cs index 69d1751e5832..4b256170fb45 100644 --- a/src/Umbraco.Core/Notifications/ContentPublishedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentPublishedNotification.cs @@ -1,22 +1,20 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentPublishedNotification : EnumerableObjectNotification { - public sealed class ContentPublishedNotification : EnumerableObjectNotification + public ContentPublishedNotification(IContent target, EventMessages messages) : base(target, messages) { - public ContentPublishedNotification(IContent target, EventMessages messages) : base(target, messages) - { - } - - public ContentPublishedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + } - public IEnumerable PublishedEntities => Target; + public ContentPublishedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } + + public IEnumerable PublishedEntities => Target; } diff --git a/src/Umbraco.Core/Notifications/ContentPublishingNotification.cs b/src/Umbraco.Core/Notifications/ContentPublishingNotification.cs index 65a8efdadf90..8b1abcef3b4b 100644 --- a/src/Umbraco.Core/Notifications/ContentPublishingNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentPublishingNotification.cs @@ -1,22 +1,20 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentPublishingNotification : CancelableEnumerableObjectNotification { - public sealed class ContentPublishingNotification : CancelableEnumerableObjectNotification + public ContentPublishingNotification(IContent target, EventMessages messages) : base(target, messages) { - public ContentPublishingNotification(IContent target, EventMessages messages) : base(target, messages) - { - } - - public ContentPublishingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + } - public IEnumerable PublishedEntities => Target; + public ContentPublishingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } + + public IEnumerable PublishedEntities => Target; } diff --git a/src/Umbraco.Core/Notifications/ContentRefreshNotification.cs b/src/Umbraco.Core/Notifications/ContentRefreshNotification.cs index b9cda7722cc0..42f85d6da330 100644 --- a/src/Umbraco.Core/Notifications/ContentRefreshNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentRefreshNotification.cs @@ -1,17 +1,14 @@ -using System; using System.ComponentModel; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications -{ +namespace Umbraco.Cms.Core.Notifications; - [Obsolete("This is only used for the internal cache and will change, use saved notifications instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public class ContentRefreshNotification : EntityRefreshNotification +[Obsolete("This is only used for the internal cache and will change, use saved notifications instead")] +[EditorBrowsable(EditorBrowsableState.Never)] +public class ContentRefreshNotification : EntityRefreshNotification +{ + public ContentRefreshNotification(IContent target, EventMessages messages) : base(target, messages) { - public ContentRefreshNotification(IContent target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/ContentRolledBackNotification.cs b/src/Umbraco.Core/Notifications/ContentRolledBackNotification.cs index a1f370bd9434..08bce7a92cc8 100644 --- a/src/Umbraco.Core/Notifications/ContentRolledBackNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentRolledBackNotification.cs @@ -4,12 +4,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentRolledBackNotification : RolledBackNotification { - public sealed class ContentRolledBackNotification : RolledBackNotification + public ContentRolledBackNotification(IContent target, EventMessages messages) : base(target, messages) { - public ContentRolledBackNotification(IContent target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/ContentRollingBackNotification.cs b/src/Umbraco.Core/Notifications/ContentRollingBackNotification.cs index e12bfa16311b..e459d88bd2f7 100644 --- a/src/Umbraco.Core/Notifications/ContentRollingBackNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentRollingBackNotification.cs @@ -4,12 +4,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentRollingBackNotification : RollingBackNotification { - public sealed class ContentRollingBackNotification : RollingBackNotification + public ContentRollingBackNotification(IContent target, EventMessages messages) : base(target, messages) { - public ContentRollingBackNotification(IContent target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/ContentSavedBlueprintNotification.cs b/src/Umbraco.Core/Notifications/ContentSavedBlueprintNotification.cs index 6addde88c1b4..85785f4e8220 100644 --- a/src/Umbraco.Core/Notifications/ContentSavedBlueprintNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentSavedBlueprintNotification.cs @@ -4,14 +4,13 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentSavedBlueprintNotification : ObjectNotification { - public sealed class ContentSavedBlueprintNotification : ObjectNotification + public ContentSavedBlueprintNotification(IContent target, EventMessages messages) : base(target, messages) { - public ContentSavedBlueprintNotification(IContent target, EventMessages messages) : base(target, messages) - { - } - - public IContent SavedBlueprint => Target; } + + public IContent SavedBlueprint => Target; } diff --git a/src/Umbraco.Core/Notifications/ContentSavedNotification.cs b/src/Umbraco.Core/Notifications/ContentSavedNotification.cs index b58a366368ec..2bd476bd8af8 100644 --- a/src/Umbraco.Core/Notifications/ContentSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentSavedNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentSavedNotification : SavedNotification { - public sealed class ContentSavedNotification : SavedNotification + public ContentSavedNotification(IContent target, EventMessages messages) : base(target, messages) { - public ContentSavedNotification(IContent target, EventMessages messages) : base(target, messages) - { - } + } - public ContentSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public ContentSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/ContentSavingNotification.cs b/src/Umbraco.Core/Notifications/ContentSavingNotification.cs index afe21bf8708d..92a6d4384fb1 100644 --- a/src/Umbraco.Core/Notifications/ContentSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentSavingNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentSavingNotification : SavingNotification { - public sealed class ContentSavingNotification : SavingNotification + public ContentSavingNotification(IContent target, EventMessages messages) : base(target, messages) { - public ContentSavingNotification(IContent target, EventMessages messages) : base(target, messages) - { - } + } - public ContentSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public ContentSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/ContentSendingToPublishNotification.cs b/src/Umbraco.Core/Notifications/ContentSendingToPublishNotification.cs index 0a5c01888304..3e2364501700 100644 --- a/src/Umbraco.Core/Notifications/ContentSendingToPublishNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentSendingToPublishNotification.cs @@ -4,14 +4,13 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentSendingToPublishNotification : CancelableObjectNotification { - public sealed class ContentSendingToPublishNotification : CancelableObjectNotification + public ContentSendingToPublishNotification(IContent target, EventMessages messages) : base(target, messages) { - public ContentSendingToPublishNotification(IContent target, EventMessages messages) : base(target, messages) - { - } - - public IContent Entity => Target; } + + public IContent Entity => Target; } diff --git a/src/Umbraco.Core/Notifications/ContentSentToPublishNotification.cs b/src/Umbraco.Core/Notifications/ContentSentToPublishNotification.cs index c5e2e5dc3b8d..9ebbb926e047 100644 --- a/src/Umbraco.Core/Notifications/ContentSentToPublishNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentSentToPublishNotification.cs @@ -4,14 +4,13 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentSentToPublishNotification : ObjectNotification { - public sealed class ContentSentToPublishNotification : ObjectNotification + public ContentSentToPublishNotification(IContent target, EventMessages messages) : base(target, messages) { - public ContentSentToPublishNotification(IContent target, EventMessages messages) : base(target, messages) - { - } - - public IContent Entity => Target; } + + public IContent Entity => Target; } diff --git a/src/Umbraco.Core/Notifications/ContentSortedNotification.cs b/src/Umbraco.Core/Notifications/ContentSortedNotification.cs index 0a299e3c0a08..e0e46dbcfc58 100644 --- a/src/Umbraco.Core/Notifications/ContentSortedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentSortedNotification.cs @@ -1,16 +1,14 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentSortedNotification : SortedNotification { - public sealed class ContentSortedNotification : SortedNotification + public ContentSortedNotification(IEnumerable target, EventMessages messages) : base(target, messages) { - public ContentSortedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/ContentSortingNotification.cs b/src/Umbraco.Core/Notifications/ContentSortingNotification.cs index 1d6cd31c5ad9..0077e5945f34 100644 --- a/src/Umbraco.Core/Notifications/ContentSortingNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentSortingNotification.cs @@ -1,16 +1,14 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentSortingNotification : SortingNotification { - public sealed class ContentSortingNotification : SortingNotification + public ContentSortingNotification(IEnumerable target, EventMessages messages) : base(target, messages) { - public ContentSortingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/ContentTreeChangeNotification.cs b/src/Umbraco.Core/Notifications/ContentTreeChangeNotification.cs index b5b100038b21..0dce6b079795 100644 --- a/src/Umbraco.Core/Notifications/ContentTreeChangeNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTreeChangeNotification.cs @@ -1,31 +1,29 @@ -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services.Changes; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class ContentTreeChangeNotification : TreeChangeNotification { - public class ContentTreeChangeNotification : TreeChangeNotification + public ContentTreeChangeNotification(TreeChange target, EventMessages messages) : base(target, messages) { - public ContentTreeChangeNotification(TreeChange target, EventMessages messages) : base(target, messages) - { - } + } - public ContentTreeChangeNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public ContentTreeChangeNotification(IEnumerable> target, EventMessages messages) : base( + target, messages) + { + } - public ContentTreeChangeNotification(IEnumerable target, - TreeChangeTypes changeTypes, - EventMessages messages) : base(target.Select(x => new TreeChange(x, changeTypes)), messages) - { - } + public ContentTreeChangeNotification(IEnumerable target, + TreeChangeTypes changeTypes, + EventMessages messages) : base(target.Select(x => new TreeChange(x, changeTypes)), messages) + { + } - public ContentTreeChangeNotification(IContent target, - TreeChangeTypes changeTypes, - EventMessages messages) : base(new TreeChange(target, changeTypes), messages) - { - } + public ContentTreeChangeNotification(IContent target, + TreeChangeTypes changeTypes, + EventMessages messages) : base(new TreeChange(target, changeTypes), messages) + { } } diff --git a/src/Umbraco.Core/Notifications/ContentTypeCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeCacheRefresherNotification.cs index 8bd06a4c46f0..ae5d904a153e 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeCacheRefresherNotification.cs @@ -1,11 +1,11 @@ using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class ContentTypeCacheRefresherNotification : CacheRefresherNotification { - public class ContentTypeCacheRefresherNotification : CacheRefresherNotification + public ContentTypeCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + messageType) { - public ContentTypeCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) - { - } } } diff --git a/src/Umbraco.Core/Notifications/ContentTypeChangeNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeChangeNotification.cs index e03f38131866..28dd96e6fec7 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeChangeNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeChangeNotification.cs @@ -1,20 +1,21 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services.Changes; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class ContentTypeChangeNotification : EnumerableObjectNotification> + where T : class, IContentTypeComposition { - public abstract class ContentTypeChangeNotification : EnumerableObjectNotification> where T : class, IContentTypeComposition + protected ContentTypeChangeNotification(ContentTypeChange target, EventMessages messages) : base(target, + messages) { - protected ContentTypeChangeNotification(ContentTypeChange target, EventMessages messages) : base(target, messages) - { - } - - protected ContentTypeChangeNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + } - public IEnumerable> Changes => Target; + protected ContentTypeChangeNotification(IEnumerable> target, EventMessages messages) : base( + target, messages) + { } + + public IEnumerable> Changes => Target; } diff --git a/src/Umbraco.Core/Notifications/ContentTypeChangedNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeChangedNotification.cs index e0aca73cd2b2..52ac4f087ce5 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeChangedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeChangedNotification.cs @@ -1,18 +1,18 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services.Changes; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class ContentTypeChangedNotification : ContentTypeChangeNotification { - public class ContentTypeChangedNotification : ContentTypeChangeNotification + public ContentTypeChangedNotification(ContentTypeChange target, EventMessages messages) : base(target, + messages) { - public ContentTypeChangedNotification(ContentTypeChange target, EventMessages messages) : base(target, messages) - { - } + } - public ContentTypeChangedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public ContentTypeChangedNotification(IEnumerable> target, EventMessages messages) : + base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/ContentTypeDeletedNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeDeletedNotification.cs index d5b2b3e28e95..32f2a6f41ef2 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeDeletedNotification.cs @@ -1,17 +1,16 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class ContentTypeDeletedNotification : DeletedNotification { - public class ContentTypeDeletedNotification : DeletedNotification + public ContentTypeDeletedNotification(IContentType target, EventMessages messages) : base(target, messages) { - public ContentTypeDeletedNotification(IContentType target, EventMessages messages) : base(target, messages) - { - } + } - public ContentTypeDeletedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public ContentTypeDeletedNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/ContentTypeDeletingNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeDeletingNotification.cs index 56863b93fb13..27ed7a92b343 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeDeletingNotification.cs @@ -1,17 +1,16 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class ContentTypeDeletingNotification : DeletingNotification { - public class ContentTypeDeletingNotification : DeletingNotification + public ContentTypeDeletingNotification(IContentType target, EventMessages messages) : base(target, messages) { - public ContentTypeDeletingNotification(IContentType target, EventMessages messages) : base(target, messages) - { - } + } - public ContentTypeDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public ContentTypeDeletingNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/ContentTypeMovedNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeMovedNotification.cs index d4794329cfc1..f3d57321ce17 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeMovedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeMovedNotification.cs @@ -1,17 +1,17 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class ContentTypeMovedNotification : MovedNotification { - public class ContentTypeMovedNotification : MovedNotification + public ContentTypeMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, + messages) { - public ContentTypeMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) - { - } + } - public ContentTypeMovedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public ContentTypeMovedNotification(IEnumerable> target, EventMessages messages) : base( + target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/ContentTypeMovingNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeMovingNotification.cs index a8881500979a..2a55e619a202 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeMovingNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeMovingNotification.cs @@ -1,17 +1,17 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class ContentTypeMovingNotification : MovingNotification { - public class ContentTypeMovingNotification : MovingNotification + public ContentTypeMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, + messages) { - public ContentTypeMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) - { - } + } - public ContentTypeMovingNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public ContentTypeMovingNotification(IEnumerable> target, EventMessages messages) : + base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/ContentTypeRefreshNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeRefreshNotification.cs index 717225db2d8e..b522be928076 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeRefreshNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeRefreshNotification.cs @@ -1,18 +1,19 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services.Changes; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class ContentTypeRefreshNotification : ContentTypeChangeNotification + where T : class, IContentTypeComposition { - public abstract class ContentTypeRefreshNotification : ContentTypeChangeNotification where T: class, IContentTypeComposition + protected ContentTypeRefreshNotification(ContentTypeChange target, EventMessages messages) : base(target, + messages) { - protected ContentTypeRefreshNotification(ContentTypeChange target, EventMessages messages) : base(target, messages) - { - } + } - protected ContentTypeRefreshNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + protected ContentTypeRefreshNotification(IEnumerable> target, EventMessages messages) : base( + target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/ContentTypeRefreshedNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeRefreshedNotification.cs index 72d111bb6748..629f8843dedd 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeRefreshedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeRefreshedNotification.cs @@ -1,22 +1,21 @@ -using System; -using System.Collections.Generic; using System.ComponentModel; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services.Changes; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +[Obsolete("This is only used for the internal cache and will change, use saved notifications instead")] +[EditorBrowsable(EditorBrowsableState.Never)] +public class ContentTypeRefreshedNotification : ContentTypeRefreshNotification { - [Obsolete("This is only used for the internal cache and will change, use saved notifications instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public class ContentTypeRefreshedNotification : ContentTypeRefreshNotification + public ContentTypeRefreshedNotification(ContentTypeChange target, EventMessages messages) : base( + target, messages) { - public ContentTypeRefreshedNotification(ContentTypeChange target, EventMessages messages) : base(target, messages) - { - } + } - public ContentTypeRefreshedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public ContentTypeRefreshedNotification(IEnumerable> target, EventMessages messages) + : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/ContentTypeSavedNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeSavedNotification.cs index 5b9a231d60fb..3d5c794a533e 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeSavedNotification.cs @@ -1,17 +1,16 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class ContentTypeSavedNotification : SavedNotification { - public class ContentTypeSavedNotification : SavedNotification + public ContentTypeSavedNotification(IContentType target, EventMessages messages) : base(target, messages) { - public ContentTypeSavedNotification(IContentType target, EventMessages messages) : base(target, messages) - { - } + } - public ContentTypeSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public ContentTypeSavedNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/ContentTypeSavingNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeSavingNotification.cs index 85deb91418ac..fc58c30fe2fa 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeSavingNotification.cs @@ -1,17 +1,16 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class ContentTypeSavingNotification : SavingNotification { - public class ContentTypeSavingNotification : SavingNotification + public ContentTypeSavingNotification(IContentType target, EventMessages messages) : base(target, messages) { - public ContentTypeSavingNotification(IContentType target, EventMessages messages) : base(target, messages) - { - } + } - public ContentTypeSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public ContentTypeSavingNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/ContentUnpublishedNotification.cs b/src/Umbraco.Core/Notifications/ContentUnpublishedNotification.cs index c08d79ac59e1..548a3f108226 100644 --- a/src/Umbraco.Core/Notifications/ContentUnpublishedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentUnpublishedNotification.cs @@ -1,22 +1,20 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentUnpublishedNotification : EnumerableObjectNotification { - public sealed class ContentUnpublishedNotification : EnumerableObjectNotification + public ContentUnpublishedNotification(IContent target, EventMessages messages) : base(target, messages) { - public ContentUnpublishedNotification(IContent target, EventMessages messages) : base(target, messages) - { - } - - public ContentUnpublishedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + } - public IEnumerable UnpublishedEntities => Target; + public ContentUnpublishedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } + + public IEnumerable UnpublishedEntities => Target; } diff --git a/src/Umbraco.Core/Notifications/ContentUnpublishingNotification.cs b/src/Umbraco.Core/Notifications/ContentUnpublishingNotification.cs index 5fb5034515b8..b05a32b7b19c 100644 --- a/src/Umbraco.Core/Notifications/ContentUnpublishingNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentUnpublishingNotification.cs @@ -1,22 +1,21 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class ContentUnpublishingNotification : CancelableEnumerableObjectNotification { - public sealed class ContentUnpublishingNotification : CancelableEnumerableObjectNotification + public ContentUnpublishingNotification(IContent target, EventMessages messages) : base(target, messages) { - public ContentUnpublishingNotification(IContent target, EventMessages messages) : base(target, messages) - { - } - - public ContentUnpublishingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + } - public IEnumerable UnpublishedEntities => Target; + public ContentUnpublishingNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } + + public IEnumerable UnpublishedEntities => Target; } diff --git a/src/Umbraco.Core/Notifications/CopiedNotification.cs b/src/Umbraco.Core/Notifications/CopiedNotification.cs index c7d6c88bcd8a..5142c700fb8a 100644 --- a/src/Umbraco.Core/Notifications/CopiedNotification.cs +++ b/src/Umbraco.Core/Notifications/CopiedNotification.cs @@ -3,22 +3,22 @@ using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class CopiedNotification : ObjectNotification where T : class { - public abstract class CopiedNotification : ObjectNotification where T : class + protected CopiedNotification(T original, T copy, int parentId, bool relateToOriginal, EventMessages messages) : + base(original, messages) { - protected CopiedNotification(T original, T copy, int parentId, bool relateToOriginal, EventMessages messages) : base(original, messages) - { - Copy = copy; - ParentId = parentId; - RelateToOriginal = relateToOriginal; - } + Copy = copy; + ParentId = parentId; + RelateToOriginal = relateToOriginal; + } - public T Original => Target; + public T Original => Target; - public T Copy { get; } + public T Copy { get; } - public int ParentId { get; } - public bool RelateToOriginal { get; } - } + public int ParentId { get; } + public bool RelateToOriginal { get; } } diff --git a/src/Umbraco.Core/Notifications/CopyingNotification.cs b/src/Umbraco.Core/Notifications/CopyingNotification.cs index 99f46f8b435f..9aa2ac237410 100644 --- a/src/Umbraco.Core/Notifications/CopyingNotification.cs +++ b/src/Umbraco.Core/Notifications/CopyingNotification.cs @@ -3,20 +3,19 @@ using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class CopyingNotification : CancelableObjectNotification where T : class { - public abstract class CopyingNotification : CancelableObjectNotification where T : class + protected CopyingNotification(T original, T copy, int parentId, EventMessages messages) : base(original, messages) { - protected CopyingNotification(T original, T copy, int parentId, EventMessages messages) : base(original, messages) - { - Copy = copy; - ParentId = parentId; - } + Copy = copy; + ParentId = parentId; + } - public T Original => Target; + public T Original => Target; - public T Copy { get; } + public T Copy { get; } - public int ParentId { get; } - } + public int ParentId { get; } } diff --git a/src/Umbraco.Core/Notifications/CreatedNotification.cs b/src/Umbraco.Core/Notifications/CreatedNotification.cs index 2108b5fb5cda..0c5bf0bd601c 100644 --- a/src/Umbraco.Core/Notifications/CreatedNotification.cs +++ b/src/Umbraco.Core/Notifications/CreatedNotification.cs @@ -3,14 +3,13 @@ using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class CreatedNotification : ObjectNotification where T : class { - public abstract class CreatedNotification : ObjectNotification where T : class + protected CreatedNotification(T target, EventMessages messages) : base(target, messages) { - protected CreatedNotification(T target, EventMessages messages) : base(target, messages) - { - } - - public T CreatedEntity => Target; } + + public T CreatedEntity => Target; } diff --git a/src/Umbraco.Core/Notifications/CreatingNotification.cs b/src/Umbraco.Core/Notifications/CreatingNotification.cs index da4fbfe742a3..c9c2b28f2c08 100644 --- a/src/Umbraco.Core/Notifications/CreatingNotification.cs +++ b/src/Umbraco.Core/Notifications/CreatingNotification.cs @@ -3,14 +3,13 @@ using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class CreatingNotification : CancelableObjectNotification where T : class { - public abstract class CreatingNotification : CancelableObjectNotification where T : class + protected CreatingNotification(T target, EventMessages messages) : base(target, messages) { - protected CreatingNotification(T target, EventMessages messages) : base(target, messages) - { - } - - public T CreatedEntity => Target; } + + public T CreatedEntity => Target; } diff --git a/src/Umbraco.Core/Notifications/CreatingRequestNotification.cs b/src/Umbraco.Core/Notifications/CreatingRequestNotification.cs index aacca17afb10..2ea921ceb63c 100644 --- a/src/Umbraco.Core/Notifications/CreatingRequestNotification.cs +++ b/src/Umbraco.Core/Notifications/CreatingRequestNotification.cs @@ -1,20 +1,17 @@ -using System; +namespace Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Notifications +/// +/// Used for notifying when an Umbraco request is being created +/// +public class CreatingRequestNotification : INotification { /// - /// Used for notifying when an Umbraco request is being created + /// Initializes a new instance of the class. /// - public class CreatingRequestNotification : INotification - { - /// - /// Initializes a new instance of the class. - /// - public CreatingRequestNotification(Uri url) => Url = url; + public CreatingRequestNotification(Uri url) => Url = url; - /// - /// Gets or sets the URL for the request - /// - public Uri Url { get; set; } - } + /// + /// Gets or sets the URL for the request + /// + public Uri Url { get; set; } } diff --git a/src/Umbraco.Core/Notifications/DataTypeCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/DataTypeCacheRefresherNotification.cs index f59de3ebd0ef..34743bf0c18b 100644 --- a/src/Umbraco.Core/Notifications/DataTypeCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/DataTypeCacheRefresherNotification.cs @@ -1,11 +1,11 @@ using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class DataTypeCacheRefresherNotification : CacheRefresherNotification { - public class DataTypeCacheRefresherNotification : CacheRefresherNotification + public DataTypeCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + messageType) { - public DataTypeCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) - { - } } } diff --git a/src/Umbraco.Core/Notifications/DataTypeDeletedNotification.cs b/src/Umbraco.Core/Notifications/DataTypeDeletedNotification.cs index 405af74c1c67..87039bf32928 100644 --- a/src/Umbraco.Core/Notifications/DataTypeDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/DataTypeDeletedNotification.cs @@ -1,12 +1,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class DataTypeDeletedNotification : DeletedNotification { - public class DataTypeDeletedNotification : DeletedNotification + public DataTypeDeletedNotification(IDataType target, EventMessages messages) : base(target, messages) { - public DataTypeDeletedNotification(IDataType target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/DataTypeDeletingNotification.cs b/src/Umbraco.Core/Notifications/DataTypeDeletingNotification.cs index ab997a0defe0..ffaa6a1c9392 100644 --- a/src/Umbraco.Core/Notifications/DataTypeDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/DataTypeDeletingNotification.cs @@ -1,12 +1,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class DataTypeDeletingNotification : DeletingNotification { - public class DataTypeDeletingNotification : DeletingNotification + public DataTypeDeletingNotification(IDataType target, EventMessages messages) : base(target, messages) { - public DataTypeDeletingNotification(IDataType target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/DataTypeMovedNotification.cs b/src/Umbraco.Core/Notifications/DataTypeMovedNotification.cs index 150582547b17..d37a19261a60 100644 --- a/src/Umbraco.Core/Notifications/DataTypeMovedNotification.cs +++ b/src/Umbraco.Core/Notifications/DataTypeMovedNotification.cs @@ -1,12 +1,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class DataTypeMovedNotification : MovedNotification { - public class DataTypeMovedNotification : MovedNotification + public DataTypeMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) { - public DataTypeMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/DataTypeMovingNotification.cs b/src/Umbraco.Core/Notifications/DataTypeMovingNotification.cs index ae8fb4be6e37..58ee94ee8b74 100644 --- a/src/Umbraco.Core/Notifications/DataTypeMovingNotification.cs +++ b/src/Umbraco.Core/Notifications/DataTypeMovingNotification.cs @@ -1,12 +1,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class DataTypeMovingNotification : MovingNotification { - public class DataTypeMovingNotification : MovingNotification + public DataTypeMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) { - public DataTypeMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/DataTypeSavedNotification.cs b/src/Umbraco.Core/Notifications/DataTypeSavedNotification.cs index 6c1a806069d4..df8ea82c7b5c 100644 --- a/src/Umbraco.Core/Notifications/DataTypeSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/DataTypeSavedNotification.cs @@ -1,17 +1,15 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class DataTypeSavedNotification : SavedNotification { - public class DataTypeSavedNotification : SavedNotification + public DataTypeSavedNotification(IDataType target, EventMessages messages) : base(target, messages) { - public DataTypeSavedNotification(IDataType target, EventMessages messages) : base(target, messages) - { - } + } - public DataTypeSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public DataTypeSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/DataTypeSavingNotification.cs b/src/Umbraco.Core/Notifications/DataTypeSavingNotification.cs index 3538950b12f5..b0ac80601db8 100644 --- a/src/Umbraco.Core/Notifications/DataTypeSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/DataTypeSavingNotification.cs @@ -1,17 +1,15 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class DataTypeSavingNotification : SavingNotification { - public class DataTypeSavingNotification : SavingNotification + public DataTypeSavingNotification(IDataType target, EventMessages messages) : base(target, messages) { - public DataTypeSavingNotification(IDataType target, EventMessages messages) : base(target, messages) - { - } + } - public DataTypeSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public DataTypeSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/DeletedNotification.cs b/src/Umbraco.Core/Notifications/DeletedNotification.cs index 3b2a370388de..b6313e3a38bc 100644 --- a/src/Umbraco.Core/Notifications/DeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/DeletedNotification.cs @@ -1,21 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class DeletedNotification : EnumerableObjectNotification { - public abstract class DeletedNotification : EnumerableObjectNotification + protected DeletedNotification(T target, EventMessages messages) : base(target, messages) { - protected DeletedNotification(T target, EventMessages messages) : base(target, messages) - { - } - - protected DeletedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + } - public IEnumerable DeletedEntities => Target; + protected DeletedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } + + public IEnumerable DeletedEntities => Target; } diff --git a/src/Umbraco.Core/Notifications/DeletedVersionsNotification.cs b/src/Umbraco.Core/Notifications/DeletedVersionsNotification.cs index 420323afafd6..d84008aaf130 100644 --- a/src/Umbraco.Core/Notifications/DeletedVersionsNotification.cs +++ b/src/Umbraco.Core/Notifications/DeletedVersionsNotification.cs @@ -1,16 +1,15 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class DeletedVersionsNotification : DeletedVersionsNotificationBase where T : class { - public abstract class DeletedVersionsNotification : DeletedVersionsNotificationBase where T : class + protected DeletedVersionsNotification(int id, EventMessages messages, int specificVersion = default, + bool deletePriorVersions = false, DateTime dateToRetain = default) + : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) { - protected DeletedVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) - : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) - { - } } } diff --git a/src/Umbraco.Core/Notifications/DeletedVersionsNotificationBase.cs b/src/Umbraco.Core/Notifications/DeletedVersionsNotificationBase.cs index 352eee8caea8..2e399e5720fa 100644 --- a/src/Umbraco.Core/Notifications/DeletedVersionsNotificationBase.cs +++ b/src/Umbraco.Core/Notifications/DeletedVersionsNotificationBase.cs @@ -1,30 +1,29 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class DeletedVersionsNotificationBase : StatefulNotification where T : class { - public abstract class DeletedVersionsNotificationBase : StatefulNotification where T : class + protected DeletedVersionsNotificationBase(int id, EventMessages messages, int specificVersion = default, + bool deletePriorVersions = false, DateTime dateToRetain = default) { - protected DeletedVersionsNotificationBase(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) - { - Id = id; - Messages = messages; - SpecificVersion = specificVersion; - DeletePriorVersions = deletePriorVersions; - DateToRetain = dateToRetain; - } + Id = id; + Messages = messages; + SpecificVersion = specificVersion; + DeletePriorVersions = deletePriorVersions; + DateToRetain = dateToRetain; + } - public int Id { get; } + public int Id { get; } - public EventMessages Messages { get; } + public EventMessages Messages { get; } - public int SpecificVersion { get; } + public int SpecificVersion { get; } - public bool DeletePriorVersions { get; } + public bool DeletePriorVersions { get; } - public DateTime DateToRetain { get; } - } + public DateTime DateToRetain { get; } } diff --git a/src/Umbraco.Core/Notifications/DeletingNotification.cs b/src/Umbraco.Core/Notifications/DeletingNotification.cs index b4090a5b8346..56bcb8d66d4e 100644 --- a/src/Umbraco.Core/Notifications/DeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/DeletingNotification.cs @@ -1,21 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class DeletingNotification : CancelableEnumerableObjectNotification { - public abstract class DeletingNotification : CancelableEnumerableObjectNotification + protected DeletingNotification(T target, EventMessages messages) : base(target, messages) { - protected DeletingNotification(T target, EventMessages messages) : base(target, messages) - { - } - - protected DeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + } - public IEnumerable DeletedEntities => Target; + protected DeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } + + public IEnumerable DeletedEntities => Target; } diff --git a/src/Umbraco.Core/Notifications/DeletingVersionsNotification.cs b/src/Umbraco.Core/Notifications/DeletingVersionsNotification.cs index ca8f1f2514df..2709fed0eac8 100644 --- a/src/Umbraco.Core/Notifications/DeletingVersionsNotification.cs +++ b/src/Umbraco.Core/Notifications/DeletingVersionsNotification.cs @@ -1,18 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class DeletingVersionsNotification : DeletedVersionsNotificationBase, ICancelableNotification + where T : class { - public abstract class DeletingVersionsNotification : DeletedVersionsNotificationBase, ICancelableNotification where T : class + protected DeletingVersionsNotification(int id, EventMessages messages, int specificVersion = default, + bool deletePriorVersions = false, DateTime dateToRetain = default) + : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) { - protected DeletingVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) - : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) - { - } - - public bool Cancel { get; set; } } + + public bool Cancel { get; set; } } diff --git a/src/Umbraco.Core/Notifications/DictionaryCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/DictionaryCacheRefresherNotification.cs index b36939800e58..b9fed23cb981 100644 --- a/src/Umbraco.Core/Notifications/DictionaryCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryCacheRefresherNotification.cs @@ -1,11 +1,11 @@ using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class DictionaryCacheRefresherNotification : CacheRefresherNotification { - public class DictionaryCacheRefresherNotification : CacheRefresherNotification + public DictionaryCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + messageType) { - public DictionaryCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) - { - } } } diff --git a/src/Umbraco.Core/Notifications/DictionaryItemDeletedNotification.cs b/src/Umbraco.Core/Notifications/DictionaryItemDeletedNotification.cs index c151e7ec6002..db1d8dbe29f4 100644 --- a/src/Umbraco.Core/Notifications/DictionaryItemDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryItemDeletedNotification.cs @@ -4,12 +4,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class DictionaryItemDeletedNotification : DeletedNotification { - public class DictionaryItemDeletedNotification : DeletedNotification + public DictionaryItemDeletedNotification(IDictionaryItem target, EventMessages messages) : base(target, messages) { - public DictionaryItemDeletedNotification(IDictionaryItem target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/DictionaryItemDeletingNotification.cs b/src/Umbraco.Core/Notifications/DictionaryItemDeletingNotification.cs index 5be95c478b1a..bd6d6ccd1a83 100644 --- a/src/Umbraco.Core/Notifications/DictionaryItemDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryItemDeletingNotification.cs @@ -1,20 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class DictionaryItemDeletingNotification : DeletingNotification { - public class DictionaryItemDeletingNotification : DeletingNotification + public DictionaryItemDeletingNotification(IDictionaryItem target, EventMessages messages) : base(target, messages) { - public DictionaryItemDeletingNotification(IDictionaryItem target, EventMessages messages) : base(target, messages) - { - } + } - public DictionaryItemDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public DictionaryItemDeletingNotification(IEnumerable target, EventMessages messages) : base( + target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/DictionaryItemSavedNotification.cs b/src/Umbraco.Core/Notifications/DictionaryItemSavedNotification.cs index dc5194b847bf..e3b935955dac 100644 --- a/src/Umbraco.Core/Notifications/DictionaryItemSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryItemSavedNotification.cs @@ -1,20 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class DictionaryItemSavedNotification : SavedNotification { - public class DictionaryItemSavedNotification : SavedNotification + public DictionaryItemSavedNotification(IDictionaryItem target, EventMessages messages) : base(target, messages) { - public DictionaryItemSavedNotification(IDictionaryItem target, EventMessages messages) : base(target, messages) - { - } + } - public DictionaryItemSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public DictionaryItemSavedNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/DictionaryItemSavingNotification.cs b/src/Umbraco.Core/Notifications/DictionaryItemSavingNotification.cs index 79fef15aef96..e45627afc07b 100644 --- a/src/Umbraco.Core/Notifications/DictionaryItemSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryItemSavingNotification.cs @@ -1,20 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class DictionaryItemSavingNotification : SavingNotification { - public class DictionaryItemSavingNotification : SavingNotification + public DictionaryItemSavingNotification(IDictionaryItem target, EventMessages messages) : base(target, messages) { - public DictionaryItemSavingNotification(IDictionaryItem target, EventMessages messages) : base(target, messages) - { - } + } - public DictionaryItemSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public DictionaryItemSavingNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/DomainCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/DomainCacheRefresherNotification.cs index 326a7d2b6455..58f262dd5211 100644 --- a/src/Umbraco.Core/Notifications/DomainCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/DomainCacheRefresherNotification.cs @@ -1,11 +1,11 @@ using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class DomainCacheRefresherNotification : CacheRefresherNotification { - public class DomainCacheRefresherNotification : CacheRefresherNotification + public DomainCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + messageType) { - public DomainCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) - { - } } } diff --git a/src/Umbraco.Core/Notifications/DomainDeletedNotification.cs b/src/Umbraco.Core/Notifications/DomainDeletedNotification.cs index b1b3a80ba145..7bfd897ead79 100644 --- a/src/Umbraco.Core/Notifications/DomainDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/DomainDeletedNotification.cs @@ -4,12 +4,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class DomainDeletedNotification : DeletedNotification { - public class DomainDeletedNotification : DeletedNotification + public DomainDeletedNotification(IDomain target, EventMessages messages) : base(target, messages) { - public DomainDeletedNotification(IDomain target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/DomainDeletingNotification.cs b/src/Umbraco.Core/Notifications/DomainDeletingNotification.cs index cd678d368990..c69d88d90d0d 100644 --- a/src/Umbraco.Core/Notifications/DomainDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/DomainDeletingNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class DomainDeletingNotification : DeletingNotification { - public class DomainDeletingNotification : DeletingNotification + public DomainDeletingNotification(IDomain target, EventMessages messages) : base(target, messages) { - public DomainDeletingNotification(IDomain target, EventMessages messages) : base(target, messages) - { - } + } - public DomainDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public DomainDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/DomainSavedNotification.cs b/src/Umbraco.Core/Notifications/DomainSavedNotification.cs index 61bd8ba3a426..edd36338358c 100644 --- a/src/Umbraco.Core/Notifications/DomainSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/DomainSavedNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class DomainSavedNotification : SavedNotification { - public class DomainSavedNotification : SavedNotification + public DomainSavedNotification(IDomain target, EventMessages messages) : base(target, messages) { - public DomainSavedNotification(IDomain target, EventMessages messages) : base(target, messages) - { - } + } - public DomainSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public DomainSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/DomainSavingNotification.cs b/src/Umbraco.Core/Notifications/DomainSavingNotification.cs index 32a2d71a73b5..33072d1ff504 100644 --- a/src/Umbraco.Core/Notifications/DomainSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/DomainSavingNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class DomainSavingNotification : SavingNotification { - public class DomainSavingNotification : SavingNotification + public DomainSavingNotification(IDomain target, EventMessages messages) : base(target, messages) { - public DomainSavingNotification(IDomain target, EventMessages messages) : base(target, messages) - { - } + } - public DomainSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public DomainSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/EmptiedRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/EmptiedRecycleBinNotification.cs index 2773f3140fc7..fb5e55cb5629 100644 --- a/src/Umbraco.Core/Notifications/EmptiedRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/EmptiedRecycleBinNotification.cs @@ -1,21 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class EmptiedRecycleBinNotification : StatefulNotification where T : class { - public abstract class EmptiedRecycleBinNotification : StatefulNotification where T : class + protected EmptiedRecycleBinNotification(IEnumerable deletedEntities, EventMessages messages) { - protected EmptiedRecycleBinNotification(IEnumerable deletedEntities, EventMessages messages) - { - DeletedEntities = deletedEntities; - Messages = messages; - } + DeletedEntities = deletedEntities; + Messages = messages; + } - public IEnumerable DeletedEntities { get; } + public IEnumerable DeletedEntities { get; } - public EventMessages Messages { get; } - } + public EventMessages Messages { get; } } diff --git a/src/Umbraco.Core/Notifications/EmptyingRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/EmptyingRecycleBinNotification.cs index 42005fc9f429..8e52204cee00 100644 --- a/src/Umbraco.Core/Notifications/EmptyingRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/EmptyingRecycleBinNotification.cs @@ -1,23 +1,21 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class EmptyingRecycleBinNotification : StatefulNotification, ICancelableNotification where T : class { - public abstract class EmptyingRecycleBinNotification : StatefulNotification, ICancelableNotification where T : class + protected EmptyingRecycleBinNotification(IEnumerable? deletedEntities, EventMessages messages) { - protected EmptyingRecycleBinNotification(IEnumerable? deletedEntities, EventMessages messages) - { - DeletedEntities = deletedEntities; - Messages = messages; - } + DeletedEntities = deletedEntities; + Messages = messages; + } - public IEnumerable? DeletedEntities { get; } + public IEnumerable? DeletedEntities { get; } - public EventMessages Messages { get; } + public EventMessages Messages { get; } - public bool Cancel { get; set; } - } + public bool Cancel { get; set; } } diff --git a/src/Umbraco.Core/Notifications/EntityContainerDeletedNotification.cs b/src/Umbraco.Core/Notifications/EntityContainerDeletedNotification.cs index 66c55e94ad8f..55d612bdc012 100644 --- a/src/Umbraco.Core/Notifications/EntityContainerDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/EntityContainerDeletedNotification.cs @@ -1,12 +1,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class EntityContainerDeletedNotification : DeletedNotification { - public class EntityContainerDeletedNotification : DeletedNotification + public EntityContainerDeletedNotification(EntityContainer target, EventMessages messages) : base(target, messages) { - public EntityContainerDeletedNotification(EntityContainer target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/EntityContainerDeletingNotification.cs b/src/Umbraco.Core/Notifications/EntityContainerDeletingNotification.cs index 45a7a5b6c89b..6f9846ebaf1d 100644 --- a/src/Umbraco.Core/Notifications/EntityContainerDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/EntityContainerDeletingNotification.cs @@ -1,12 +1,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class EntityContainerDeletingNotification : DeletingNotification { - public class EntityContainerDeletingNotification : DeletingNotification + public EntityContainerDeletingNotification(EntityContainer target, EventMessages messages) : base(target, messages) { - public EntityContainerDeletingNotification(EntityContainer target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/EntityContainerRenamedNotification.cs b/src/Umbraco.Core/Notifications/EntityContainerRenamedNotification.cs index e6046c9a58f2..d3c10b07396a 100644 --- a/src/Umbraco.Core/Notifications/EntityContainerRenamedNotification.cs +++ b/src/Umbraco.Core/Notifications/EntityContainerRenamedNotification.cs @@ -1,12 +1,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class EntityContainerRenamedNotification : RenamedNotification { - public class EntityContainerRenamedNotification : RenamedNotification + public EntityContainerRenamedNotification(EntityContainer target, EventMessages messages) : base(target, messages) { - public EntityContainerRenamedNotification(EntityContainer target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/EntityContainerRenamingNotification.cs b/src/Umbraco.Core/Notifications/EntityContainerRenamingNotification.cs index c03d5f5ee30b..bfea4b6f8bb8 100644 --- a/src/Umbraco.Core/Notifications/EntityContainerRenamingNotification.cs +++ b/src/Umbraco.Core/Notifications/EntityContainerRenamingNotification.cs @@ -1,12 +1,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class EntityContainerRenamingNotification : RenamingNotification { - public class EntityContainerRenamingNotification : RenamingNotification + public EntityContainerRenamingNotification(EntityContainer target, EventMessages messages) : base(target, messages) { - public EntityContainerRenamingNotification(EntityContainer target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/EntityContainerSavedNotification.cs b/src/Umbraco.Core/Notifications/EntityContainerSavedNotification.cs index 33cac9effd5d..cd73440b83be 100644 --- a/src/Umbraco.Core/Notifications/EntityContainerSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/EntityContainerSavedNotification.cs @@ -1,12 +1,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class EntityContainerSavedNotification : SavedNotification { - public class EntityContainerSavedNotification : SavedNotification + public EntityContainerSavedNotification(EntityContainer target, EventMessages messages) : base(target, messages) { - public EntityContainerSavedNotification(EntityContainer target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/EntityContainerSavingNotification.cs b/src/Umbraco.Core/Notifications/EntityContainerSavingNotification.cs index 25cbfc9311ec..8994476bdef5 100644 --- a/src/Umbraco.Core/Notifications/EntityContainerSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/EntityContainerSavingNotification.cs @@ -1,12 +1,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class EntityContainerSavingNotification : SavingNotification { - public class EntityContainerSavingNotification : SavingNotification + public EntityContainerSavingNotification(EntityContainer target, EventMessages messages) : base(target, messages) { - public EntityContainerSavingNotification(EntityContainer target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/EntityRefreshNotification.cs b/src/Umbraco.Core/Notifications/EntityRefreshNotification.cs index 1afc1fa07866..1dd4fc122a4c 100644 --- a/src/Umbraco.Core/Notifications/EntityRefreshNotification.cs +++ b/src/Umbraco.Core/Notifications/EntityRefreshNotification.cs @@ -1,14 +1,13 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class EntityRefreshNotification : ObjectNotification where T : class, IContentBase { - public class EntityRefreshNotification : ObjectNotification where T : class, IContentBase + public EntityRefreshNotification(T target, EventMessages messages) : base(target, messages) { - public EntityRefreshNotification(T target, EventMessages messages) : base(target, messages) - { - } - - public T Entity => Target; } + + public T Entity => Target; } diff --git a/src/Umbraco.Core/Notifications/EnumerableObjectNotification.cs b/src/Umbraco.Core/Notifications/EnumerableObjectNotification.cs index fde93f01396e..4cb28de7c460 100644 --- a/src/Umbraco.Core/Notifications/EnumerableObjectNotification.cs +++ b/src/Umbraco.Core/Notifications/EnumerableObjectNotification.cs @@ -1,19 +1,17 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class EnumerableObjectNotification : ObjectNotification> { - public abstract class EnumerableObjectNotification : ObjectNotification> + protected EnumerableObjectNotification(T target, EventMessages messages) : base(new[] {target}, messages) { - protected EnumerableObjectNotification(T target, EventMessages messages) : base(new [] {target}, messages) - { - } + } - protected EnumerableObjectNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + protected EnumerableObjectNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/ExportedMemberNotification.cs b/src/Umbraco.Core/Notifications/ExportedMemberNotification.cs index 9cf69032e620..29c843945ca9 100644 --- a/src/Umbraco.Core/Notifications/ExportedMemberNotification.cs +++ b/src/Umbraco.Core/Notifications/ExportedMemberNotification.cs @@ -1,18 +1,17 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class ExportedMemberNotification : INotification { - public class ExportedMemberNotification : INotification + public ExportedMemberNotification(IMember member, MemberExportModel exported) { - public ExportedMemberNotification(IMember member, MemberExportModel exported) - { - Member = member; - Exported = exported; - } + Member = member; + Exported = exported; + } - public IMember Member { get; } + public IMember Member { get; } - public MemberExportModel Exported { get; } - } + public MemberExportModel Exported { get; } } diff --git a/src/Umbraco.Core/Notifications/ICancelableNotification.cs b/src/Umbraco.Core/Notifications/ICancelableNotification.cs index c30e6613feba..e4d1b61309e1 100644 --- a/src/Umbraco.Core/Notifications/ICancelableNotification.cs +++ b/src/Umbraco.Core/Notifications/ICancelableNotification.cs @@ -1,10 +1,9 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public interface ICancelableNotification : INotification { - public interface ICancelableNotification : INotification - { - bool Cancel { get; set; } - } + bool Cancel { get; set; } } diff --git a/src/Umbraco.Core/Notifications/INotification.cs b/src/Umbraco.Core/Notifications/INotification.cs index 2427da1454c6..3d106b37d796 100644 --- a/src/Umbraco.Core/Notifications/INotification.cs +++ b/src/Umbraco.Core/Notifications/INotification.cs @@ -1,12 +1,11 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +/// +/// A marker interface to represent a notification. +/// +public interface INotification { - /// - /// A marker interface to represent a notification. - /// - public interface INotification - { - } } diff --git a/src/Umbraco.Core/Notifications/IStatefulNotification.cs b/src/Umbraco.Core/Notifications/IStatefulNotification.cs index c7319524ffc3..65603f5bfa76 100644 --- a/src/Umbraco.Core/Notifications/IStatefulNotification.cs +++ b/src/Umbraco.Core/Notifications/IStatefulNotification.cs @@ -1,9 +1,6 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Notifications +public interface IStatefulNotification : INotification { - public interface IStatefulNotification : INotification - { - IDictionary State { get; set; } - } + IDictionary State { get; set; } } diff --git a/src/Umbraco.Core/Notifications/IUmbracoApplicationLifetimeNotification.cs b/src/Umbraco.Core/Notifications/IUmbracoApplicationLifetimeNotification.cs index 4b0ea6826afb..8d8ea73fe64b 100644 --- a/src/Umbraco.Core/Notifications/IUmbracoApplicationLifetimeNotification.cs +++ b/src/Umbraco.Core/Notifications/IUmbracoApplicationLifetimeNotification.cs @@ -1,17 +1,16 @@ -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +/// +/// Represents an Umbraco application lifetime (starting, started, stopping, stopped) notification. +/// +/// +public interface IUmbracoApplicationLifetimeNotification : INotification { /// - /// Represents an Umbraco application lifetime (starting, started, stopping, stopped) notification. + /// Gets a value indicating whether Umbraco is restarting (e.g. after an install or upgrade). /// - /// - public interface IUmbracoApplicationLifetimeNotification : INotification - { - /// - /// Gets a value indicating whether Umbraco is restarting (e.g. after an install or upgrade). - /// - /// - /// true if Umbraco is restarting; otherwise, false. - /// - bool IsRestarting { get; } - } + /// + /// true if Umbraco is restarting; otherwise, false. + /// + bool IsRestarting { get; } } diff --git a/src/Umbraco.Core/Notifications/ImportedPackageNotification.cs b/src/Umbraco.Core/Notifications/ImportedPackageNotification.cs index 8f3538d44819..62114722c1cc 100644 --- a/src/Umbraco.Core/Notifications/ImportedPackageNotification.cs +++ b/src/Umbraco.Core/Notifications/ImportedPackageNotification.cs @@ -1,15 +1,11 @@ -using Umbraco.Cms.Core.Models.Packaging; using Umbraco.Cms.Core.Packaging; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class ImportedPackageNotification : StatefulNotification { - public class ImportedPackageNotification : StatefulNotification - { - public ImportedPackageNotification(InstallationSummary installationSummary) - { - InstallationSummary = installationSummary; - } + public ImportedPackageNotification(InstallationSummary installationSummary) => + InstallationSummary = installationSummary; - public InstallationSummary InstallationSummary { get; } - } + public InstallationSummary InstallationSummary { get; } } diff --git a/src/Umbraco.Core/Notifications/ImportingPackageNotification.cs b/src/Umbraco.Core/Notifications/ImportingPackageNotification.cs index 7fb6c8f9fc81..67a02f254c88 100644 --- a/src/Umbraco.Core/Notifications/ImportingPackageNotification.cs +++ b/src/Umbraco.Core/Notifications/ImportingPackageNotification.cs @@ -1,16 +1,10 @@ -using Umbraco.Cms.Core.Models.Packaging; +namespace Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Notifications +public class ImportingPackageNotification : StatefulNotification, ICancelableNotification { - public class ImportingPackageNotification : StatefulNotification, ICancelableNotification - { - public ImportingPackageNotification(string packageName) - { - PackageName = packageName; - } + public ImportingPackageNotification(string packageName) => PackageName = packageName; - public string PackageName { get; } + public string PackageName { get; } - public bool Cancel { get; set; } - } + public bool Cancel { get; set; } } diff --git a/src/Umbraco.Core/Notifications/LanguageCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/LanguageCacheRefresherNotification.cs index 436d8a4fbfaa..cd4c7f8a79db 100644 --- a/src/Umbraco.Core/Notifications/LanguageCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/LanguageCacheRefresherNotification.cs @@ -1,11 +1,11 @@ using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class LanguageCacheRefresherNotification : CacheRefresherNotification { - public class LanguageCacheRefresherNotification : CacheRefresherNotification + public LanguageCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + messageType) { - public LanguageCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) - { - } } } diff --git a/src/Umbraco.Core/Notifications/LanguageDeletedNotification.cs b/src/Umbraco.Core/Notifications/LanguageDeletedNotification.cs index ccc17c8a902b..dd6b299ed76d 100644 --- a/src/Umbraco.Core/Notifications/LanguageDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/LanguageDeletedNotification.cs @@ -4,12 +4,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class LanguageDeletedNotification : DeletedNotification { - public class LanguageDeletedNotification : DeletedNotification + public LanguageDeletedNotification(ILanguage target, EventMessages messages) : base(target, messages) { - public LanguageDeletedNotification(ILanguage target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/LanguageDeletingNotification.cs b/src/Umbraco.Core/Notifications/LanguageDeletingNotification.cs index c4e468250022..16b731d5c6b0 100644 --- a/src/Umbraco.Core/Notifications/LanguageDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/LanguageDeletingNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class LanguageDeletingNotification : DeletingNotification { - public class LanguageDeletingNotification : DeletingNotification + public LanguageDeletingNotification(ILanguage target, EventMessages messages) : base(target, messages) { - public LanguageDeletingNotification(ILanguage target, EventMessages messages) : base(target, messages) - { - } + } - public LanguageDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public LanguageDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/LanguageSavedNotification.cs b/src/Umbraco.Core/Notifications/LanguageSavedNotification.cs index 29265c86ca0d..0c7184b2d38d 100644 --- a/src/Umbraco.Core/Notifications/LanguageSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/LanguageSavedNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class LanguageSavedNotification : SavedNotification { - public class LanguageSavedNotification : SavedNotification + public LanguageSavedNotification(ILanguage target, EventMessages messages) : base(target, messages) { - public LanguageSavedNotification(ILanguage target, EventMessages messages) : base(target, messages) - { - } + } - public LanguageSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public LanguageSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/LanguageSavingNotification.cs b/src/Umbraco.Core/Notifications/LanguageSavingNotification.cs index 5fcb892e2504..fc46aa80694d 100644 --- a/src/Umbraco.Core/Notifications/LanguageSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/LanguageSavingNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class LanguageSavingNotification : SavingNotification { - public class LanguageSavingNotification : SavingNotification + public LanguageSavingNotification(ILanguage target, EventMessages messages) : base(target, messages) { - public LanguageSavingNotification(ILanguage target, EventMessages messages) : base(target, messages) - { - } + } - public LanguageSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public LanguageSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MacroCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/MacroCacheRefresherNotification.cs index 5fb5554b1b42..b7fab71d846f 100644 --- a/src/Umbraco.Core/Notifications/MacroCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/MacroCacheRefresherNotification.cs @@ -1,11 +1,11 @@ using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MacroCacheRefresherNotification : CacheRefresherNotification { - public class MacroCacheRefresherNotification : CacheRefresherNotification + public MacroCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + messageType) { - public MacroCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) - { - } } } diff --git a/src/Umbraco.Core/Notifications/MacroDeletedNotification.cs b/src/Umbraco.Core/Notifications/MacroDeletedNotification.cs index 237cce38fe3a..c46c40657277 100644 --- a/src/Umbraco.Core/Notifications/MacroDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/MacroDeletedNotification.cs @@ -1,12 +1,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MacroDeletedNotification : DeletedNotification { - public class MacroDeletedNotification : DeletedNotification + public MacroDeletedNotification(IMacro target, EventMessages messages) : base(target, messages) { - public MacroDeletedNotification(IMacro target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/MacroDeletingNotification.cs b/src/Umbraco.Core/Notifications/MacroDeletingNotification.cs index d36a9896bc43..f318b8aa3aea 100644 --- a/src/Umbraco.Core/Notifications/MacroDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/MacroDeletingNotification.cs @@ -1,17 +1,15 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MacroDeletingNotification : DeletingNotification { - public class MacroDeletingNotification : DeletingNotification + public MacroDeletingNotification(IMacro target, EventMessages messages) : base(target, messages) { - public MacroDeletingNotification(IMacro target, EventMessages messages) : base(target, messages) - { - } + } - public MacroDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public MacroDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MacroSavedNotification.cs b/src/Umbraco.Core/Notifications/MacroSavedNotification.cs index 8aa776dcc682..3471e32d2f56 100644 --- a/src/Umbraco.Core/Notifications/MacroSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/MacroSavedNotification.cs @@ -1,17 +1,15 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MacroSavedNotification : SavedNotification { - public class MacroSavedNotification : SavedNotification + public MacroSavedNotification(IMacro target, EventMessages messages) : base(target, messages) { - public MacroSavedNotification(IMacro target, EventMessages messages) : base(target, messages) - { - } + } - public MacroSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public MacroSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MacroSavingNotification.cs b/src/Umbraco.Core/Notifications/MacroSavingNotification.cs index 965ee6b22ed7..3ee1c6426296 100644 --- a/src/Umbraco.Core/Notifications/MacroSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/MacroSavingNotification.cs @@ -1,17 +1,15 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MacroSavingNotification : SavingNotification { - public class MacroSavingNotification : SavingNotification + public MacroSavingNotification(IMacro target, EventMessages messages) : base(target, messages) { - public MacroSavingNotification(IMacro target, EventMessages messages) : base(target, messages) - { - } + } - public MacroSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public MacroSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MediaCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/MediaCacheRefresherNotification.cs index 079475232d4f..683da92047d1 100644 --- a/src/Umbraco.Core/Notifications/MediaCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaCacheRefresherNotification.cs @@ -1,11 +1,11 @@ using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MediaCacheRefresherNotification : CacheRefresherNotification { - public class MediaCacheRefresherNotification : CacheRefresherNotification + public MediaCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + messageType) { - public MediaCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) - { - } } } diff --git a/src/Umbraco.Core/Notifications/MediaDeletedNotification.cs b/src/Umbraco.Core/Notifications/MediaDeletedNotification.cs index b8cce7e74761..a95c31834f2c 100644 --- a/src/Umbraco.Core/Notifications/MediaDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaDeletedNotification.cs @@ -4,12 +4,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class MediaDeletedNotification : DeletedNotification { - public sealed class MediaDeletedNotification : DeletedNotification + public MediaDeletedNotification(IMedia target, EventMessages messages) : base(target, messages) { - public MediaDeletedNotification(IMedia target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/MediaDeletedVersionsNotification.cs b/src/Umbraco.Core/Notifications/MediaDeletedVersionsNotification.cs index 6bbdb3c09890..9d46eed0580f 100644 --- a/src/Umbraco.Core/Notifications/MediaDeletedVersionsNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaDeletedVersionsNotification.cs @@ -1,16 +1,16 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class MediaDeletedVersionsNotification : DeletedVersionsNotification { - public sealed class MediaDeletedVersionsNotification : DeletedVersionsNotification + public MediaDeletedVersionsNotification(int id, EventMessages messages, int specificVersion = default, + bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, + deletePriorVersions, dateToRetain) { - public MediaDeletedVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) - { - } } } diff --git a/src/Umbraco.Core/Notifications/MediaDeletingNotification.cs b/src/Umbraco.Core/Notifications/MediaDeletingNotification.cs index 358a553b286d..574f7ba5391b 100644 --- a/src/Umbraco.Core/Notifications/MediaDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaDeletingNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class MediaDeletingNotification : DeletingNotification { - public sealed class MediaDeletingNotification : DeletingNotification + public MediaDeletingNotification(IMedia target, EventMessages messages) : base(target, messages) { - public MediaDeletingNotification(IMedia target, EventMessages messages) : base(target, messages) - { - } + } - public MediaDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public MediaDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MediaDeletingVersionsNotification.cs b/src/Umbraco.Core/Notifications/MediaDeletingVersionsNotification.cs index fa7b3ba8e083..e186ca540c30 100644 --- a/src/Umbraco.Core/Notifications/MediaDeletingVersionsNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaDeletingVersionsNotification.cs @@ -1,16 +1,16 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class MediaDeletingVersionsNotification : DeletingVersionsNotification { - public sealed class MediaDeletingVersionsNotification : DeletingVersionsNotification + public MediaDeletingVersionsNotification(int id, EventMessages messages, int specificVersion = default, + bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, + deletePriorVersions, dateToRetain) { - public MediaDeletingVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) - { - } } } diff --git a/src/Umbraco.Core/Notifications/MediaEmptiedRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/MediaEmptiedRecycleBinNotification.cs index 086229692546..8ead4f4ad1c7 100644 --- a/src/Umbraco.Core/Notifications/MediaEmptiedRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaEmptiedRecycleBinNotification.cs @@ -1,16 +1,15 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class MediaEmptiedRecycleBinNotification : EmptiedRecycleBinNotification { - public sealed class MediaEmptiedRecycleBinNotification : EmptiedRecycleBinNotification + public MediaEmptiedRecycleBinNotification(IEnumerable deletedEntities, EventMessages messages) : base( + deletedEntities, messages) { - public MediaEmptiedRecycleBinNotification(IEnumerable deletedEntities,EventMessages messages) : base(deletedEntities, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/MediaEmptyingRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/MediaEmptyingRecycleBinNotification.cs index 4e257cfb38fb..f9f064b54f0b 100644 --- a/src/Umbraco.Core/Notifications/MediaEmptyingRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaEmptyingRecycleBinNotification.cs @@ -1,16 +1,15 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class MediaEmptyingRecycleBinNotification : EmptyingRecycleBinNotification { - public sealed class MediaEmptyingRecycleBinNotification : EmptyingRecycleBinNotification + public MediaEmptyingRecycleBinNotification(IEnumerable deletedEntities, EventMessages messages) : base( + deletedEntities, messages) { - public MediaEmptyingRecycleBinNotification(IEnumerable deletedEntities, EventMessages messages) : base(deletedEntities, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/MediaMovedNotification.cs b/src/Umbraco.Core/Notifications/MediaMovedNotification.cs index 2012f16f4b13..cd75bc474bb4 100644 --- a/src/Umbraco.Core/Notifications/MediaMovedNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaMovedNotification.cs @@ -1,20 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class MediaMovedNotification : MovedNotification { - public sealed class MediaMovedNotification : MovedNotification + public MediaMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) { - public MediaMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) - { - } + } - public MediaMovedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public MediaMovedNotification(IEnumerable> target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MediaMovedToRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/MediaMovedToRecycleBinNotification.cs index 44120674bdc7..3dadf540cbfc 100644 --- a/src/Umbraco.Core/Notifications/MediaMovedToRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaMovedToRecycleBinNotification.cs @@ -1,20 +1,20 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class MediaMovedToRecycleBinNotification : MovedToRecycleBinNotification { - public sealed class MediaMovedToRecycleBinNotification : MovedToRecycleBinNotification + public MediaMovedToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(target, + messages) { - public MediaMovedToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) - { - } + } - public MediaMovedToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public MediaMovedToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base( + target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MediaMovingNotification.cs b/src/Umbraco.Core/Notifications/MediaMovingNotification.cs index fcfb50787baf..ba22d93f00ab 100644 --- a/src/Umbraco.Core/Notifications/MediaMovingNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaMovingNotification.cs @@ -1,20 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class MediaMovingNotification : MovingNotification { - public sealed class MediaMovingNotification : MovingNotification + public MediaMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) { - public MediaMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) - { - } + } - public MediaMovingNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public MediaMovingNotification(IEnumerable> target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MediaMovingToRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/MediaMovingToRecycleBinNotification.cs index 856b66c0c430..58b4522aba5f 100644 --- a/src/Umbraco.Core/Notifications/MediaMovingToRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaMovingToRecycleBinNotification.cs @@ -1,20 +1,20 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class MediaMovingToRecycleBinNotification : MovingToRecycleBinNotification { - public sealed class MediaMovingToRecycleBinNotification : MovingToRecycleBinNotification + public MediaMovingToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(target, + messages) { - public MediaMovingToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) - { - } + } - public MediaMovingToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public MediaMovingToRecycleBinNotification(IEnumerable> target, EventMessages messages) : + base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MediaRefreshNotification.cs b/src/Umbraco.Core/Notifications/MediaRefreshNotification.cs index 1c8b8b9bea65..7edb1e4b6493 100644 --- a/src/Umbraco.Core/Notifications/MediaRefreshNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaRefreshNotification.cs @@ -1,16 +1,14 @@ -using System; using System.ComponentModel; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +[Obsolete("This is only used for the internal cache and will change, use tree change notifications instead")] +[EditorBrowsable(EditorBrowsableState.Never)] +public class MediaRefreshNotification : EntityRefreshNotification { - [Obsolete("This is only used for the internal cache and will change, use tree change notifications instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public class MediaRefreshNotification : EntityRefreshNotification + public MediaRefreshNotification(IMedia target, EventMessages messages) : base(target, messages) { - public MediaRefreshNotification(IMedia target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/MediaSavedNotification.cs b/src/Umbraco.Core/Notifications/MediaSavedNotification.cs index addeda617eb3..a1e4e0419494 100644 --- a/src/Umbraco.Core/Notifications/MediaSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaSavedNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class MediaSavedNotification : SavedNotification { - public sealed class MediaSavedNotification : SavedNotification + public MediaSavedNotification(IMedia target, EventMessages messages) : base(target, messages) { - public MediaSavedNotification(IMedia target, EventMessages messages) : base(target, messages) - { - } + } - public MediaSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public MediaSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MediaSavingNotification.cs b/src/Umbraco.Core/Notifications/MediaSavingNotification.cs index 638d27c96880..9e3edd488746 100644 --- a/src/Umbraco.Core/Notifications/MediaSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaSavingNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class MediaSavingNotification : SavingNotification { - public sealed class MediaSavingNotification : SavingNotification + public MediaSavingNotification(IMedia target, EventMessages messages) : base(target, messages) { - public MediaSavingNotification(IMedia target, EventMessages messages) : base(target, messages) - { - } + } - public MediaSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public MediaSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MediaTreeChangeNotification.cs b/src/Umbraco.Core/Notifications/MediaTreeChangeNotification.cs index 00e0e6b42cad..c1686210c85e 100644 --- a/src/Umbraco.Core/Notifications/MediaTreeChangeNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTreeChangeNotification.cs @@ -1,30 +1,28 @@ -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services.Changes; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MediaTreeChangeNotification : TreeChangeNotification { - public class MediaTreeChangeNotification : TreeChangeNotification + public MediaTreeChangeNotification(TreeChange target, EventMessages messages) : base(target, messages) { - public MediaTreeChangeNotification(TreeChange target, EventMessages messages) : base(target, messages) - { - } + } - public MediaTreeChangeNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public MediaTreeChangeNotification(IEnumerable> target, EventMessages messages) : base(target, + messages) + { + } - public MediaTreeChangeNotification(IEnumerable target, - TreeChangeTypes changeTypes, - EventMessages messages) : base(target.Select(x => new TreeChange(x, changeTypes)), messages) - { - } + public MediaTreeChangeNotification(IEnumerable target, + TreeChangeTypes changeTypes, + EventMessages messages) : base(target.Select(x => new TreeChange(x, changeTypes)), messages) + { + } - public MediaTreeChangeNotification(IMedia target, TreeChangeTypes changeTypes, EventMessages messages) : base( - new TreeChange(target, changeTypes), messages) - { - } + public MediaTreeChangeNotification(IMedia target, TreeChangeTypes changeTypes, EventMessages messages) : base( + new TreeChange(target, changeTypes), messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MediaTypeChangedNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeChangedNotification.cs index 322a6bb1ab09..56c9d94472cc 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeChangedNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeChangedNotification.cs @@ -1,18 +1,18 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services.Changes; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MediaTypeChangedNotification : ContentTypeChangeNotification { - public class MediaTypeChangedNotification : ContentTypeChangeNotification + public MediaTypeChangedNotification(ContentTypeChange target, EventMessages messages) : base(target, + messages) { - public MediaTypeChangedNotification(ContentTypeChange target, EventMessages messages) : base(target, messages) - { - } + } - public MediaTypeChangedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public MediaTypeChangedNotification(IEnumerable> target, EventMessages messages) : + base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MediaTypeDeletedNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeDeletedNotification.cs index 59c7114ca01e..2db2449b23e3 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeDeletedNotification.cs @@ -1,17 +1,15 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MediaTypeDeletedNotification : DeletedNotification { - public class MediaTypeDeletedNotification : DeletedNotification + public MediaTypeDeletedNotification(IMediaType target, EventMessages messages) : base(target, messages) { - public MediaTypeDeletedNotification(IMediaType target, EventMessages messages) : base(target, messages) - { - } + } - public MediaTypeDeletedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public MediaTypeDeletedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MediaTypeDeletingNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeDeletingNotification.cs index 1cb4f7c99d03..9aaedd924904 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeDeletingNotification.cs @@ -1,17 +1,16 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MediaTypeDeletingNotification : DeletingNotification { - public class MediaTypeDeletingNotification : DeletingNotification + public MediaTypeDeletingNotification(IMediaType target, EventMessages messages) : base(target, messages) { - public MediaTypeDeletingNotification(IMediaType target, EventMessages messages) : base(target, messages) - { - } + } - public MediaTypeDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public MediaTypeDeletingNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MediaTypeMovedNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeMovedNotification.cs index c17aa222ded9..aa9de887e72c 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeMovedNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeMovedNotification.cs @@ -1,17 +1,16 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MediaTypeMovedNotification : MovedNotification { - public class MediaTypeMovedNotification : MovedNotification + public MediaTypeMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) { - public MediaTypeMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) - { - } + } - public MediaTypeMovedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public MediaTypeMovedNotification(IEnumerable> target, EventMessages messages) : base( + target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MediaTypeMovingNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeMovingNotification.cs index 43499430b0e0..7ac77445590e 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeMovingNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeMovingNotification.cs @@ -1,17 +1,17 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MediaTypeMovingNotification : MovingNotification { - public class MediaTypeMovingNotification : MovingNotification + public MediaTypeMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, + messages) { - public MediaTypeMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) - { - } + } - public MediaTypeMovingNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public MediaTypeMovingNotification(IEnumerable> target, EventMessages messages) : base( + target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MediaTypeRefreshedNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeRefreshedNotification.cs index 6b59e3220ed6..cc35bb31f9a7 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeRefreshedNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeRefreshedNotification.cs @@ -1,22 +1,21 @@ -using System; -using System.Collections.Generic; using System.ComponentModel; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services.Changes; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +[Obsolete("This is only used for the internal cache and will change, use tree change notifications instead")] +[EditorBrowsable(EditorBrowsableState.Never)] +public class MediaTypeRefreshedNotification : ContentTypeRefreshNotification { - [Obsolete("This is only used for the internal cache and will change, use tree change notifications instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public class MediaTypeRefreshedNotification : ContentTypeRefreshNotification + public MediaTypeRefreshedNotification(ContentTypeChange target, EventMessages messages) : base(target, + messages) { - public MediaTypeRefreshedNotification(ContentTypeChange target, EventMessages messages) : base(target, messages) - { - } + } - public MediaTypeRefreshedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public MediaTypeRefreshedNotification(IEnumerable> target, EventMessages messages) : + base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MediaTypeSavedNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeSavedNotification.cs index b4b2372b7f71..2ffc42ce47be 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeSavedNotification.cs @@ -1,17 +1,15 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MediaTypeSavedNotification : SavedNotification { - public class MediaTypeSavedNotification : SavedNotification + public MediaTypeSavedNotification(IMediaType target, EventMessages messages) : base(target, messages) { - public MediaTypeSavedNotification(IMediaType target, EventMessages messages) : base(target, messages) - { - } + } - public MediaTypeSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public MediaTypeSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MediaTypeSavingNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeSavingNotification.cs index 0a93f08671bb..f6a9bac2ae02 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeSavingNotification.cs @@ -1,17 +1,15 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MediaTypeSavingNotification : SavingNotification { - public class MediaTypeSavingNotification : SavingNotification + public MediaTypeSavingNotification(IMediaType target, EventMessages messages) : base(target, messages) { - public MediaTypeSavingNotification(IMediaType target, EventMessages messages) : base(target, messages) - { - } + } - public MediaTypeSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public MediaTypeSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MemberCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/MemberCacheRefresherNotification.cs index c2d920843d20..d305ab8b139d 100644 --- a/src/Umbraco.Core/Notifications/MemberCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberCacheRefresherNotification.cs @@ -1,11 +1,11 @@ using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MemberCacheRefresherNotification : CacheRefresherNotification { - public class MemberCacheRefresherNotification : CacheRefresherNotification + public MemberCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + messageType) { - public MemberCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) - { - } } } diff --git a/src/Umbraco.Core/Notifications/MemberDeletedNotification.cs b/src/Umbraco.Core/Notifications/MemberDeletedNotification.cs index 7539d6b13375..d06694a2af74 100644 --- a/src/Umbraco.Core/Notifications/MemberDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberDeletedNotification.cs @@ -4,12 +4,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class MemberDeletedNotification : DeletedNotification { - public sealed class MemberDeletedNotification : DeletedNotification + public MemberDeletedNotification(IMember target, EventMessages messages) : base(target, messages) { - public MemberDeletedNotification(IMember target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/MemberDeletingNotification.cs b/src/Umbraco.Core/Notifications/MemberDeletingNotification.cs index 9d09d40e1566..22c732b61cd4 100644 --- a/src/Umbraco.Core/Notifications/MemberDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberDeletingNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class MemberDeletingNotification : DeletingNotification { - public sealed class MemberDeletingNotification : DeletingNotification + public MemberDeletingNotification(IMember target, EventMessages messages) : base(target, messages) { - public MemberDeletingNotification(IMember target, EventMessages messages) : base(target, messages) - { - } + } - public MemberDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public MemberDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MemberGroupCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/MemberGroupCacheRefresherNotification.cs index f882b611673c..a858f03820aa 100644 --- a/src/Umbraco.Core/Notifications/MemberGroupCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberGroupCacheRefresherNotification.cs @@ -1,11 +1,11 @@ using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MemberGroupCacheRefresherNotification : CacheRefresherNotification { - public class MemberGroupCacheRefresherNotification : CacheRefresherNotification + public MemberGroupCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + messageType) { - public MemberGroupCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) - { - } } } diff --git a/src/Umbraco.Core/Notifications/MemberGroupDeletedNotification.cs b/src/Umbraco.Core/Notifications/MemberGroupDeletedNotification.cs index 8665cc5f7187..dc0c8194529c 100644 --- a/src/Umbraco.Core/Notifications/MemberGroupDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberGroupDeletedNotification.cs @@ -1,12 +1,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MemberGroupDeletedNotification : DeletedNotification { - public class MemberGroupDeletedNotification : DeletedNotification + public MemberGroupDeletedNotification(IMemberGroup target, EventMessages messages) : base(target, messages) { - public MemberGroupDeletedNotification(IMemberGroup target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/MemberGroupDeletingNotification.cs b/src/Umbraco.Core/Notifications/MemberGroupDeletingNotification.cs index 2b0f94af6426..93bbc2b2e00f 100644 --- a/src/Umbraco.Core/Notifications/MemberGroupDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberGroupDeletingNotification.cs @@ -1,17 +1,16 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MemberGroupDeletingNotification : DeletingNotification { - public class MemberGroupDeletingNotification : DeletingNotification + public MemberGroupDeletingNotification(IMemberGroup target, EventMessages messages) : base(target, messages) { - public MemberGroupDeletingNotification(IMemberGroup target, EventMessages messages) : base(target, messages) - { - } + } - public MemberGroupDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public MemberGroupDeletingNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MemberGroupSavedNotification.cs b/src/Umbraco.Core/Notifications/MemberGroupSavedNotification.cs index e5beffe76bac..8e9040785e39 100644 --- a/src/Umbraco.Core/Notifications/MemberGroupSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberGroupSavedNotification.cs @@ -1,17 +1,16 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MemberGroupSavedNotification : SavedNotification { - public class MemberGroupSavedNotification : SavedNotification + public MemberGroupSavedNotification(IMemberGroup target, EventMessages messages) : base(target, messages) { - public MemberGroupSavedNotification(IMemberGroup target, EventMessages messages) : base(target, messages) - { - } + } - public MemberGroupSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public MemberGroupSavedNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MemberGroupSavingNotification.cs b/src/Umbraco.Core/Notifications/MemberGroupSavingNotification.cs index a0341ab2ef05..95585480c578 100644 --- a/src/Umbraco.Core/Notifications/MemberGroupSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberGroupSavingNotification.cs @@ -1,17 +1,16 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MemberGroupSavingNotification : SavingNotification { - public class MemberGroupSavingNotification : SavingNotification + public MemberGroupSavingNotification(IMemberGroup target, EventMessages messages) : base(target, messages) { - public MemberGroupSavingNotification(IMemberGroup target, EventMessages messages) : base(target, messages) - { - } + } - public MemberGroupSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public MemberGroupSavingNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MemberRefreshNotification.cs b/src/Umbraco.Core/Notifications/MemberRefreshNotification.cs index a22c48348fac..db089a96223c 100644 --- a/src/Umbraco.Core/Notifications/MemberRefreshNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberRefreshNotification.cs @@ -1,16 +1,14 @@ -using System; using System.ComponentModel; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +[Obsolete("This is only used for the internal cache and will change, use tree change notifications instead")] +[EditorBrowsable(EditorBrowsableState.Never)] +public class MemberRefreshNotification : EntityRefreshNotification { - [Obsolete("This is only used for the internal cache and will change, use tree change notifications instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public class MemberRefreshNotification : EntityRefreshNotification + public MemberRefreshNotification(IMember target, EventMessages messages) : base(target, messages) { - public MemberRefreshNotification(IMember target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/MemberRolesNotification.cs b/src/Umbraco.Core/Notifications/MemberRolesNotification.cs index 9ea65488339d..446faee2371e 100644 --- a/src/Umbraco.Core/Notifications/MemberRolesNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberRolesNotification.cs @@ -1,15 +1,14 @@ -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class MemberRolesNotification : INotification { - public abstract class MemberRolesNotification : INotification + protected MemberRolesNotification(int[] memberIds, string[] roles) { - protected MemberRolesNotification(int[] memberIds, string[] roles) - { - MemberIds = memberIds; - Roles = roles; - } + MemberIds = memberIds; + Roles = roles; + } - public int[] MemberIds { get; } + public int[] MemberIds { get; } - public string[] Roles { get; } - } + public string[] Roles { get; } } diff --git a/src/Umbraco.Core/Notifications/MemberSavedNotification.cs b/src/Umbraco.Core/Notifications/MemberSavedNotification.cs index 2c4f4755eba6..cb29e0866930 100644 --- a/src/Umbraco.Core/Notifications/MemberSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberSavedNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class MemberSavedNotification : SavedNotification { - public sealed class MemberSavedNotification : SavedNotification + public MemberSavedNotification(IMember target, EventMessages messages) : base(target, messages) { - public MemberSavedNotification(IMember target, EventMessages messages) : base(target, messages) - { - } + } - public MemberSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public MemberSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MemberSavingNotification.cs b/src/Umbraco.Core/Notifications/MemberSavingNotification.cs index fc8198c6f976..095be39b74ca 100644 --- a/src/Umbraco.Core/Notifications/MemberSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberSavingNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class MemberSavingNotification : SavingNotification { - public sealed class MemberSavingNotification : SavingNotification + public MemberSavingNotification(IMember target, EventMessages messages) : base(target, messages) { - public MemberSavingNotification(IMember target, EventMessages messages) : base(target, messages) - { - } + } - public MemberSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public MemberSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MemberTwoFactorRequestedNotification.cs b/src/Umbraco.Core/Notifications/MemberTwoFactorRequestedNotification.cs index e06de2624eda..5182f4a46f90 100644 --- a/src/Umbraco.Core/Notifications/MemberTwoFactorRequestedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTwoFactorRequestedNotification.cs @@ -1,14 +1,8 @@ -using System; +namespace Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Notifications +public class MemberTwoFactorRequestedNotification : INotification { - public class MemberTwoFactorRequestedNotification : INotification - { - public MemberTwoFactorRequestedNotification(Guid? memberKey) - { - MemberKey = memberKey; - } + public MemberTwoFactorRequestedNotification(Guid? memberKey) => MemberKey = memberKey; - public Guid? MemberKey { get; } - } + public Guid? MemberKey { get; } } diff --git a/src/Umbraco.Core/Notifications/MemberTypeChangedNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeChangedNotification.cs index c22908c108d3..ae43a3e34898 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeChangedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeChangedNotification.cs @@ -1,18 +1,18 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services.Changes; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MemberTypeChangedNotification : ContentTypeChangeNotification { - public class MemberTypeChangedNotification : ContentTypeChangeNotification + public MemberTypeChangedNotification(ContentTypeChange target, EventMessages messages) : base(target, + messages) { - public MemberTypeChangedNotification(ContentTypeChange target, EventMessages messages) : base(target, messages) - { - } + } - public MemberTypeChangedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public MemberTypeChangedNotification(IEnumerable> target, EventMessages messages) : + base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MemberTypeDeletedNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeDeletedNotification.cs index 490db24cf360..561db465cbc4 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeDeletedNotification.cs @@ -1,17 +1,16 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MemberTypeDeletedNotification : DeletedNotification { - public class MemberTypeDeletedNotification : DeletedNotification + public MemberTypeDeletedNotification(IMemberType target, EventMessages messages) : base(target, messages) { - public MemberTypeDeletedNotification(IMemberType target, EventMessages messages) : base(target, messages) - { - } + } - public MemberTypeDeletedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public MemberTypeDeletedNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MemberTypeDeletingNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeDeletingNotification.cs index 04821eb0c253..1c8a0a54fda8 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeDeletingNotification.cs @@ -1,17 +1,16 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MemberTypeDeletingNotification : DeletingNotification { - public class MemberTypeDeletingNotification : DeletingNotification + public MemberTypeDeletingNotification(IMemberType target, EventMessages messages) : base(target, messages) { - public MemberTypeDeletingNotification(IMemberType target, EventMessages messages) : base(target, messages) - { - } + } - public MemberTypeDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public MemberTypeDeletingNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MemberTypeMovedNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeMovedNotification.cs index 8e74076119b6..64a4d3ea50bc 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeMovedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeMovedNotification.cs @@ -1,17 +1,17 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MemberTypeMovedNotification : MovedNotification { - public class MemberTypeMovedNotification : MovedNotification + public MemberTypeMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, + messages) { - public MemberTypeMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) - { - } + } - public MemberTypeMovedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public MemberTypeMovedNotification(IEnumerable> target, EventMessages messages) : base( + target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MemberTypeMovingNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeMovingNotification.cs index b4627aaf30f7..d6cf2c62c86e 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeMovingNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeMovingNotification.cs @@ -1,17 +1,17 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MemberTypeMovingNotification : MovingNotification { - public class MemberTypeMovingNotification : MovingNotification + public MemberTypeMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, + messages) { - public MemberTypeMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) - { - } + } - public MemberTypeMovingNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public MemberTypeMovingNotification(IEnumerable> target, EventMessages messages) : base( + target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MemberTypeRefreshedNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeRefreshedNotification.cs index 89147a523f83..d9cdc51f8427 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeRefreshedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeRefreshedNotification.cs @@ -1,22 +1,21 @@ -using System; -using System.Collections.Generic; using System.ComponentModel; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services.Changes; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +[Obsolete("This is only used for the internal cache and will change, use tree change notifications instead")] +[EditorBrowsable(EditorBrowsableState.Never)] +public class MemberTypeRefreshedNotification : ContentTypeRefreshNotification { - [Obsolete("This is only used for the internal cache and will change, use tree change notifications instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public class MemberTypeRefreshedNotification : ContentTypeRefreshNotification + public MemberTypeRefreshedNotification(ContentTypeChange target, EventMessages messages) : base(target, + messages) { - public MemberTypeRefreshedNotification(ContentTypeChange target, EventMessages messages) : base(target, messages) - { - } + } - public MemberTypeRefreshedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + public MemberTypeRefreshedNotification(IEnumerable> target, EventMessages messages) : + base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MemberTypeSavedNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeSavedNotification.cs index 768f9e8bb0bc..5cc464f680c2 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeSavedNotification.cs @@ -1,17 +1,15 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MemberTypeSavedNotification : SavedNotification { - public class MemberTypeSavedNotification : SavedNotification + public MemberTypeSavedNotification(IMemberType target, EventMessages messages) : base(target, messages) { - public MemberTypeSavedNotification(IMemberType target, EventMessages messages) : base(target, messages) - { - } + } - public MemberTypeSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public MemberTypeSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/MemberTypeSavingNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeSavingNotification.cs index 598aadffa4fd..c5f457f856e8 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeSavingNotification.cs @@ -1,17 +1,16 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class MemberTypeSavingNotification : SavingNotification { - public class MemberTypeSavingNotification : SavingNotification + public MemberTypeSavingNotification(IMemberType target, EventMessages messages) : base(target, messages) { - public MemberTypeSavingNotification(IMemberType target, EventMessages messages) : base(target, messages) - { - } + } - public MemberTypeSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public MemberTypeSavingNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/ModelBindingErrorNotification.cs b/src/Umbraco.Core/Notifications/ModelBindingErrorNotification.cs index e4adadcd5231..0048699e0984 100644 --- a/src/Umbraco.Core/Notifications/ModelBindingErrorNotification.cs +++ b/src/Umbraco.Core/Notifications/ModelBindingErrorNotification.cs @@ -1,37 +1,35 @@ -using System; using System.Text; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +/// +/// Contains event data for the event. +/// +public class ModelBindingErrorNotification : INotification { /// - /// Contains event data for the event. + /// Initializes a new instance of the class. /// - public class ModelBindingErrorNotification : INotification + public ModelBindingErrorNotification(Type sourceType, Type modelType, StringBuilder message) { - /// - /// Initializes a new instance of the class. - /// - public ModelBindingErrorNotification(Type sourceType, Type modelType, StringBuilder message) - { - SourceType = sourceType; - ModelType = modelType; - Message = message; - } + SourceType = sourceType; + ModelType = modelType; + Message = message; + } - /// - /// Gets the type of the source object. - /// - public Type SourceType { get; } + /// + /// Gets the type of the source object. + /// + public Type SourceType { get; } - /// - /// Gets the type of the view model. - /// - public Type ModelType { get; } + /// + /// Gets the type of the view model. + /// + public Type ModelType { get; } - /// - /// Gets the message string builder. - /// - /// Handlers of the event can append text to the message. - public StringBuilder Message { get; } - } + /// + /// Gets the message string builder. + /// + /// Handlers of the event can append text to the message. + public StringBuilder Message { get; } } diff --git a/src/Umbraco.Core/Notifications/MovedNotification.cs b/src/Umbraco.Core/Notifications/MovedNotification.cs index 4573d5e45a72..123f94b2b58f 100644 --- a/src/Umbraco.Core/Notifications/MovedNotification.cs +++ b/src/Umbraco.Core/Notifications/MovedNotification.cs @@ -1,21 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class MovedNotification : ObjectNotification>> { - public abstract class MovedNotification : ObjectNotification>> + protected MovedNotification(MoveEventInfo target, EventMessages messages) : base(new[] {target}, messages) { - protected MovedNotification(MoveEventInfo target, EventMessages messages) : base(new[] { target }, messages) - { - } - - protected MovedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + } - public IEnumerable> MoveInfoCollection => Target; + protected MovedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { } + + public IEnumerable> MoveInfoCollection => Target; } diff --git a/src/Umbraco.Core/Notifications/MovedToRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/MovedToRecycleBinNotification.cs index 1e02d30eb74e..7ce074bfdd4e 100644 --- a/src/Umbraco.Core/Notifications/MovedToRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/MovedToRecycleBinNotification.cs @@ -1,21 +1,21 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class MovedToRecycleBinNotification : ObjectNotification>> { - public abstract class MovedToRecycleBinNotification : ObjectNotification>> + protected MovedToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(new[] {target}, + messages) { - protected MovedToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(new[] { target }, messages) - { - } - - protected MovedToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + } - public IEnumerable> MoveInfoCollection => Target; + protected MovedToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base(target, + messages) + { } + + public IEnumerable> MoveInfoCollection => Target; } diff --git a/src/Umbraco.Core/Notifications/MovingNotification.cs b/src/Umbraco.Core/Notifications/MovingNotification.cs index 6bf493fc1b32..729bd57ad424 100644 --- a/src/Umbraco.Core/Notifications/MovingNotification.cs +++ b/src/Umbraco.Core/Notifications/MovingNotification.cs @@ -1,21 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class MovingNotification : CancelableObjectNotification>> { - public abstract class MovingNotification : CancelableObjectNotification>> + protected MovingNotification(MoveEventInfo target, EventMessages messages) : base(new[] {target}, messages) { - protected MovingNotification(MoveEventInfo target, EventMessages messages) : base(new[] {target}, messages) - { - } - - protected MovingNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + } - public IEnumerable> MoveInfoCollection => Target; + protected MovingNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { } + + public IEnumerable> MoveInfoCollection => Target; } diff --git a/src/Umbraco.Core/Notifications/MovingToRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/MovingToRecycleBinNotification.cs index ef8c36ce6f13..d5e5842b3aad 100644 --- a/src/Umbraco.Core/Notifications/MovingToRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/MovingToRecycleBinNotification.cs @@ -1,21 +1,21 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class MovingToRecycleBinNotification : CancelableObjectNotification>> { - public abstract class MovingToRecycleBinNotification : CancelableObjectNotification>> + protected MovingToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(new[] {target}, + messages) { - protected MovingToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(new[] { target }, messages) - { - } - - protected MovingToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + } - public IEnumerable> MoveInfoCollection => Target; + protected MovingToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base( + target, messages) + { } + + public IEnumerable> MoveInfoCollection => Target; } diff --git a/src/Umbraco.Core/Notifications/NotificationExtensions.cs b/src/Umbraco.Core/Notifications/NotificationExtensions.cs index d907d3dcfac2..30b259acaa94 100644 --- a/src/Umbraco.Core/Notifications/NotificationExtensions.cs +++ b/src/Umbraco.Core/Notifications/NotificationExtensions.cs @@ -1,17 +1,15 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Notifications +public static class NotificationExtensions { - public static class NotificationExtensions + public static T WithState(this T notification, IDictionary? state) + where T : IStatefulNotification { - public static T WithState(this T notification, IDictionary? state) where T : IStatefulNotification - { - notification.State = state!; - return notification; - } - - public static T WithStateFrom(this T notification, TSource source) - where T : IStatefulNotification where TSource : IStatefulNotification - => notification.WithState(source.State); + notification.State = state!; + return notification; } + + public static T WithStateFrom(this T notification, TSource source) + where T : IStatefulNotification where TSource : IStatefulNotification + => notification.WithState(source.State); } diff --git a/src/Umbraco.Core/Notifications/ObjectNotification.cs b/src/Umbraco.Core/Notifications/ObjectNotification.cs index a550754d322b..2cf605544e9e 100644 --- a/src/Umbraco.Core/Notifications/ObjectNotification.cs +++ b/src/Umbraco.Core/Notifications/ObjectNotification.cs @@ -3,18 +3,17 @@ using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class ObjectNotification : StatefulNotification where T : class { - public abstract class ObjectNotification : StatefulNotification where T : class + protected ObjectNotification(T target, EventMessages messages) { - protected ObjectNotification(T target, EventMessages messages) - { - Messages = messages; - Target = target; - } + Messages = messages; + Target = target; + } - public EventMessages Messages { get; } + public EventMessages Messages { get; } - protected T Target { get; } - } + protected T Target { get; } } diff --git a/src/Umbraco.Core/Notifications/PartialViewCreatedNotification.cs b/src/Umbraco.Core/Notifications/PartialViewCreatedNotification.cs index 3f34c4b1c663..81d235c91c4e 100644 --- a/src/Umbraco.Core/Notifications/PartialViewCreatedNotification.cs +++ b/src/Umbraco.Core/Notifications/PartialViewCreatedNotification.cs @@ -4,12 +4,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class PartialViewCreatedNotification : CreatedNotification { - public class PartialViewCreatedNotification : CreatedNotification + public PartialViewCreatedNotification(IPartialView target, EventMessages messages) : base(target, messages) { - public PartialViewCreatedNotification(IPartialView target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/PartialViewCreatingNotification.cs b/src/Umbraco.Core/Notifications/PartialViewCreatingNotification.cs index 425879fb06b1..de8c71bac9d7 100644 --- a/src/Umbraco.Core/Notifications/PartialViewCreatingNotification.cs +++ b/src/Umbraco.Core/Notifications/PartialViewCreatingNotification.cs @@ -4,12 +4,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class PartialViewCreatingNotification : CreatingNotification { - public class PartialViewCreatingNotification : CreatingNotification + public PartialViewCreatingNotification(IPartialView target, EventMessages messages) : base(target, messages) { - public PartialViewCreatingNotification(IPartialView target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/PartialViewDeletedNotification.cs b/src/Umbraco.Core/Notifications/PartialViewDeletedNotification.cs index 4ef4058b5c3b..6140caf011a9 100644 --- a/src/Umbraco.Core/Notifications/PartialViewDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/PartialViewDeletedNotification.cs @@ -4,12 +4,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class PartialViewDeletedNotification : DeletedNotification { - public class PartialViewDeletedNotification : DeletedNotification + public PartialViewDeletedNotification(IPartialView target, EventMessages messages) : base(target, messages) { - public PartialViewDeletedNotification(IPartialView target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/PartialViewDeletingNotification.cs b/src/Umbraco.Core/Notifications/PartialViewDeletingNotification.cs index 647371340861..a6d4a543783f 100644 --- a/src/Umbraco.Core/Notifications/PartialViewDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/PartialViewDeletingNotification.cs @@ -1,20 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class PartialViewDeletingNotification : DeletingNotification { - public class PartialViewDeletingNotification : DeletingNotification + public PartialViewDeletingNotification(IPartialView target, EventMessages messages) : base(target, messages) { - public PartialViewDeletingNotification(IPartialView target, EventMessages messages) : base(target, messages) - { - } + } - public PartialViewDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public PartialViewDeletingNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/PartialViewSavedNotification.cs b/src/Umbraco.Core/Notifications/PartialViewSavedNotification.cs index d50ed08faf56..ba8f974c98ee 100644 --- a/src/Umbraco.Core/Notifications/PartialViewSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/PartialViewSavedNotification.cs @@ -1,20 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class PartialViewSavedNotification : SavedNotification { - public class PartialViewSavedNotification : SavedNotification + public PartialViewSavedNotification(IPartialView target, EventMessages messages) : base(target, messages) { - public PartialViewSavedNotification(IPartialView target, EventMessages messages) : base(target, messages) - { - } + } - public PartialViewSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public PartialViewSavedNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/PartialViewSavingNotification.cs b/src/Umbraco.Core/Notifications/PartialViewSavingNotification.cs index fd2e0ee34a33..adf73510b7c6 100644 --- a/src/Umbraco.Core/Notifications/PartialViewSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/PartialViewSavingNotification.cs @@ -1,20 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class PartialViewSavingNotification : SavingNotification { - public class PartialViewSavingNotification : SavingNotification + public PartialViewSavingNotification(IPartialView target, EventMessages messages) : base(target, messages) { - public PartialViewSavingNotification(IPartialView target, EventMessages messages) : base(target, messages) - { - } + } - public PartialViewSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public PartialViewSavingNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/PublicAccessCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/PublicAccessCacheRefresherNotification.cs index 1e753217ab3a..b50ef47ac328 100644 --- a/src/Umbraco.Core/Notifications/PublicAccessCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/PublicAccessCacheRefresherNotification.cs @@ -1,11 +1,11 @@ using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class PublicAccessCacheRefresherNotification : CacheRefresherNotification { - public class PublicAccessCacheRefresherNotification : CacheRefresherNotification + public PublicAccessCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + messageType) { - public PublicAccessCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) - { - } } } diff --git a/src/Umbraco.Core/Notifications/PublicAccessEntryDeletedNotification.cs b/src/Umbraco.Core/Notifications/PublicAccessEntryDeletedNotification.cs index f6aa16500a18..e15b6381c889 100644 --- a/src/Umbraco.Core/Notifications/PublicAccessEntryDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/PublicAccessEntryDeletedNotification.cs @@ -4,12 +4,12 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class PublicAccessEntryDeletedNotification : DeletedNotification { - public sealed class PublicAccessEntryDeletedNotification : DeletedNotification + public PublicAccessEntryDeletedNotification(PublicAccessEntry target, EventMessages messages) : base(target, + messages) { - public PublicAccessEntryDeletedNotification(PublicAccessEntry target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/PublicAccessEntryDeletingNotification.cs b/src/Umbraco.Core/Notifications/PublicAccessEntryDeletingNotification.cs index 42c4c1bdb976..3b88d4cc100e 100644 --- a/src/Umbraco.Core/Notifications/PublicAccessEntryDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/PublicAccessEntryDeletingNotification.cs @@ -1,20 +1,20 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class PublicAccessEntryDeletingNotification : DeletingNotification { - public sealed class PublicAccessEntryDeletingNotification : DeletingNotification + public PublicAccessEntryDeletingNotification(PublicAccessEntry target, EventMessages messages) : base(target, + messages) { - public PublicAccessEntryDeletingNotification(PublicAccessEntry target, EventMessages messages) : base(target, messages) - { - } + } - public PublicAccessEntryDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public PublicAccessEntryDeletingNotification(IEnumerable target, EventMessages messages) : base( + target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/PublicAccessEntrySavedNotification.cs b/src/Umbraco.Core/Notifications/PublicAccessEntrySavedNotification.cs index 8c0d253500da..2e6abf1f47fc 100644 --- a/src/Umbraco.Core/Notifications/PublicAccessEntrySavedNotification.cs +++ b/src/Umbraco.Core/Notifications/PublicAccessEntrySavedNotification.cs @@ -1,20 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class PublicAccessEntrySavedNotification : SavedNotification { - public sealed class PublicAccessEntrySavedNotification : SavedNotification + public PublicAccessEntrySavedNotification(PublicAccessEntry target, EventMessages messages) : base(target, messages) { - public PublicAccessEntrySavedNotification(PublicAccessEntry target, EventMessages messages) : base(target, messages) - { - } + } - public PublicAccessEntrySavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public PublicAccessEntrySavedNotification(IEnumerable target, EventMessages messages) : base( + target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/PublicAccessEntrySavingNotification.cs b/src/Umbraco.Core/Notifications/PublicAccessEntrySavingNotification.cs index 3fbd666b8dd3..4a284a61a204 100644 --- a/src/Umbraco.Core/Notifications/PublicAccessEntrySavingNotification.cs +++ b/src/Umbraco.Core/Notifications/PublicAccessEntrySavingNotification.cs @@ -1,20 +1,20 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class PublicAccessEntrySavingNotification : SavingNotification { - public sealed class PublicAccessEntrySavingNotification : SavingNotification + public PublicAccessEntrySavingNotification(PublicAccessEntry target, EventMessages messages) : base(target, + messages) { - public PublicAccessEntrySavingNotification(PublicAccessEntry target, EventMessages messages) : base(target, messages) - { - } + } - public PublicAccessEntrySavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public PublicAccessEntrySavingNotification(IEnumerable target, EventMessages messages) : base( + target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/RelationDeletedNotification.cs b/src/Umbraco.Core/Notifications/RelationDeletedNotification.cs index f7af0e9b2993..bd282c8a8e76 100644 --- a/src/Umbraco.Core/Notifications/RelationDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationDeletedNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class RelationDeletedNotification : DeletedNotification { - public class RelationDeletedNotification : DeletedNotification + public RelationDeletedNotification(IRelation target, EventMessages messages) : base(target, messages) { - public RelationDeletedNotification(IRelation target, EventMessages messages) : base(target, messages) - { - } + } - public RelationDeletedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public RelationDeletedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/RelationDeletingNotification.cs b/src/Umbraco.Core/Notifications/RelationDeletingNotification.cs index 8873d95226f0..07878334aefd 100644 --- a/src/Umbraco.Core/Notifications/RelationDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationDeletingNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class RelationDeletingNotification : DeletingNotification { - public class RelationDeletingNotification : DeletingNotification + public RelationDeletingNotification(IRelation target, EventMessages messages) : base(target, messages) { - public RelationDeletingNotification(IRelation target, EventMessages messages) : base(target, messages) - { - } + } - public RelationDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public RelationDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/RelationSavedNotification.cs b/src/Umbraco.Core/Notifications/RelationSavedNotification.cs index 8b0313f87cc7..8bbe427e6234 100644 --- a/src/Umbraco.Core/Notifications/RelationSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationSavedNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class RelationSavedNotification : SavedNotification { - public class RelationSavedNotification : SavedNotification + public RelationSavedNotification(IRelation target, EventMessages messages) : base(target, messages) { - public RelationSavedNotification(IRelation target, EventMessages messages) : base(target, messages) - { - } + } - public RelationSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public RelationSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/RelationSavingNotification.cs b/src/Umbraco.Core/Notifications/RelationSavingNotification.cs index 5afe71da53a0..65646bd3b696 100644 --- a/src/Umbraco.Core/Notifications/RelationSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationSavingNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class RelationSavingNotification : SavingNotification { - public class RelationSavingNotification : SavingNotification + public RelationSavingNotification(IRelation target, EventMessages messages) : base(target, messages) { - public RelationSavingNotification(IRelation target, EventMessages messages) : base(target, messages) - { - } + } - public RelationSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public RelationSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/RelationTypeCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/RelationTypeCacheRefresherNotification.cs index ff8cf528916d..82c4478dbd7d 100644 --- a/src/Umbraco.Core/Notifications/RelationTypeCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationTypeCacheRefresherNotification.cs @@ -1,11 +1,11 @@ using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class RelationTypeCacheRefresherNotification : CacheRefresherNotification { - public class RelationTypeCacheRefresherNotification : CacheRefresherNotification + public RelationTypeCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + messageType) { - public RelationTypeCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) - { - } } } diff --git a/src/Umbraco.Core/Notifications/RelationTypeDeletedNotification.cs b/src/Umbraco.Core/Notifications/RelationTypeDeletedNotification.cs index 8534edcb49f9..602b1c5fd2c9 100644 --- a/src/Umbraco.Core/Notifications/RelationTypeDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationTypeDeletedNotification.cs @@ -4,12 +4,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class RelationTypeDeletedNotification : DeletedNotification { - public class RelationTypeDeletedNotification : DeletedNotification + public RelationTypeDeletedNotification(IRelationType target, EventMessages messages) : base(target, messages) { - public RelationTypeDeletedNotification(IRelationType target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/RelationTypeDeletingNotification.cs b/src/Umbraco.Core/Notifications/RelationTypeDeletingNotification.cs index 904a82c08bc4..aa44256b4219 100644 --- a/src/Umbraco.Core/Notifications/RelationTypeDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationTypeDeletingNotification.cs @@ -1,20 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class RelationTypeDeletingNotification : DeletingNotification { - public class RelationTypeDeletingNotification : DeletingNotification + public RelationTypeDeletingNotification(IRelationType target, EventMessages messages) : base(target, messages) { - public RelationTypeDeletingNotification(IRelationType target, EventMessages messages) : base(target, messages) - { - } + } - public RelationTypeDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public RelationTypeDeletingNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/RelationTypeSavedNotification.cs b/src/Umbraco.Core/Notifications/RelationTypeSavedNotification.cs index e2e69475d77a..e0c815536689 100644 --- a/src/Umbraco.Core/Notifications/RelationTypeSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationTypeSavedNotification.cs @@ -1,20 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class RelationTypeSavedNotification : SavedNotification { - public class RelationTypeSavedNotification : SavedNotification + public RelationTypeSavedNotification(IRelationType target, EventMessages messages) : base(target, messages) { - public RelationTypeSavedNotification(IRelationType target, EventMessages messages) : base(target, messages) - { - } + } - public RelationTypeSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public RelationTypeSavedNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/RelationTypeSavingNotification.cs b/src/Umbraco.Core/Notifications/RelationTypeSavingNotification.cs index 2fdebe97e778..a978d55fc9c7 100644 --- a/src/Umbraco.Core/Notifications/RelationTypeSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationTypeSavingNotification.cs @@ -1,20 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class RelationTypeSavingNotification : SavingNotification { - public class RelationTypeSavingNotification : SavingNotification + public RelationTypeSavingNotification(IRelationType target, EventMessages messages) : base(target, messages) { - public RelationTypeSavingNotification(IRelationType target, EventMessages messages) : base(target, messages) - { - } + } - public RelationTypeSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public RelationTypeSavingNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/RemovedMemberRolesNotification.cs b/src/Umbraco.Core/Notifications/RemovedMemberRolesNotification.cs index ed76cfbf6957..601c29eccb8d 100644 --- a/src/Umbraco.Core/Notifications/RemovedMemberRolesNotification.cs +++ b/src/Umbraco.Core/Notifications/RemovedMemberRolesNotification.cs @@ -1,10 +1,8 @@ -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class RemovedMemberRolesNotification : MemberRolesNotification { - public class RemovedMemberRolesNotification : MemberRolesNotification + public RemovedMemberRolesNotification(int[] memberIds, string[] roles) : base(memberIds, roles) { - public RemovedMemberRolesNotification(int[] memberIds, string[] roles) : base(memberIds, roles) - { - - } } } diff --git a/src/Umbraco.Core/Notifications/RenamedNotification.cs b/src/Umbraco.Core/Notifications/RenamedNotification.cs index 724069aba76c..07856fc66552 100644 --- a/src/Umbraco.Core/Notifications/RenamedNotification.cs +++ b/src/Umbraco.Core/Notifications/RenamedNotification.cs @@ -1,21 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class RenamedNotification : EnumerableObjectNotification { - public abstract class RenamedNotification : EnumerableObjectNotification + protected RenamedNotification(T target, EventMessages messages) : base(target, messages) { - protected RenamedNotification(T target, EventMessages messages) : base(target, messages) - { - } - - protected RenamedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + } - public IEnumerable Entities => Target; + protected RenamedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } + + public IEnumerable Entities => Target; } diff --git a/src/Umbraco.Core/Notifications/RenamingNotification.cs b/src/Umbraco.Core/Notifications/RenamingNotification.cs index 1e4184bc3d7b..3940a1cfa0dc 100644 --- a/src/Umbraco.Core/Notifications/RenamingNotification.cs +++ b/src/Umbraco.Core/Notifications/RenamingNotification.cs @@ -1,21 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class RenamingNotification : CancelableEnumerableObjectNotification { - public abstract class RenamingNotification : CancelableEnumerableObjectNotification + protected RenamingNotification(T target, EventMessages messages) : base(target, messages) { - protected RenamingNotification(T target, EventMessages messages) : base(target, messages) - { - } - - protected RenamingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + } - public IEnumerable Entities => Target; + protected RenamingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } + + public IEnumerable Entities => Target; } diff --git a/src/Umbraco.Core/Notifications/RolledBackNotification.cs b/src/Umbraco.Core/Notifications/RolledBackNotification.cs index fded45c6b1aa..862c8d5c31e1 100644 --- a/src/Umbraco.Core/Notifications/RolledBackNotification.cs +++ b/src/Umbraco.Core/Notifications/RolledBackNotification.cs @@ -3,14 +3,13 @@ using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class RolledBackNotification : ObjectNotification where T : class { - public abstract class RolledBackNotification : ObjectNotification where T : class + protected RolledBackNotification(T target, EventMessages messages) : base(target, messages) { - protected RolledBackNotification(T target, EventMessages messages) : base(target, messages) - { - } - - public T Entity => Target; } + + public T Entity => Target; } diff --git a/src/Umbraco.Core/Notifications/RollingBackNotification.cs b/src/Umbraco.Core/Notifications/RollingBackNotification.cs index 1064a7897c62..64d5789cae90 100644 --- a/src/Umbraco.Core/Notifications/RollingBackNotification.cs +++ b/src/Umbraco.Core/Notifications/RollingBackNotification.cs @@ -3,14 +3,13 @@ using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class RollingBackNotification : CancelableObjectNotification where T : class { - public abstract class RollingBackNotification : CancelableObjectNotification where T : class + protected RollingBackNotification(T target, EventMessages messages) : base(target, messages) { - protected RollingBackNotification(T target, EventMessages messages) : base(target, messages) - { - } - - public T Entity => Target; } + + public T Entity => Target; } diff --git a/src/Umbraco.Core/Notifications/RoutingRequestNotification.cs b/src/Umbraco.Core/Notifications/RoutingRequestNotification.cs index c8b2d8e0d68b..b5169aa0abfe 100644 --- a/src/Umbraco.Core/Notifications/RoutingRequestNotification.cs +++ b/src/Umbraco.Core/Notifications/RoutingRequestNotification.cs @@ -1,20 +1,19 @@ using Umbraco.Cms.Core.Routing; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +/// +/// Used for notifying when an Umbraco request is being built +/// +public class RoutingRequestNotification : INotification { /// - /// Used for notifying when an Umbraco request is being built + /// Initializes a new instance of the class. /// - public class RoutingRequestNotification : INotification - { - /// - /// Initializes a new instance of the class. - /// - public RoutingRequestNotification(IPublishedRequestBuilder requestBuilder) => RequestBuilder = requestBuilder; + public RoutingRequestNotification(IPublishedRequestBuilder requestBuilder) => RequestBuilder = requestBuilder; - /// - /// Gets the - /// - public IPublishedRequestBuilder RequestBuilder { get; } - } + /// + /// Gets the + /// + public IPublishedRequestBuilder RequestBuilder { get; } } diff --git a/src/Umbraco.Core/Notifications/RuntimeUnattendedInstallNotification.cs b/src/Umbraco.Core/Notifications/RuntimeUnattendedInstallNotification.cs index f638ec2d3c3b..e0ef991e70c4 100644 --- a/src/Umbraco.Core/Notifications/RuntimeUnattendedInstallNotification.cs +++ b/src/Umbraco.Core/Notifications/RuntimeUnattendedInstallNotification.cs @@ -1,13 +1,12 @@ -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +/// +/// Used to notify when the core runtime can do an unattended install. +/// +/// +/// It is entirely up to the handler to determine if an unattended installation should occur and +/// to perform the logic. +/// +public class RuntimeUnattendedInstallNotification : INotification { - /// - /// Used to notify when the core runtime can do an unattended install. - /// - /// - /// It is entirely up to the handler to determine if an unattended installation should occur and - /// to perform the logic. - /// - public class RuntimeUnattendedInstallNotification : INotification - { - } } diff --git a/src/Umbraco.Core/Notifications/RuntimeUnattendedUpgradeNotification.cs b/src/Umbraco.Core/Notifications/RuntimeUnattendedUpgradeNotification.cs index 4d676f68ce44..30fc2025f015 100644 --- a/src/Umbraco.Core/Notifications/RuntimeUnattendedUpgradeNotification.cs +++ b/src/Umbraco.Core/Notifications/RuntimeUnattendedUpgradeNotification.cs @@ -1,26 +1,24 @@ -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +/// +/// Used to notify when the core runtime can do an unattended upgrade. +/// +/// +/// It is entirely up to the handler to determine if an unattended upgrade should occur and +/// to perform the logic. +/// +public class RuntimeUnattendedUpgradeNotification : INotification { + public enum UpgradeResult + { + NotRequired = 0, + HasErrors = 1, + CoreUpgradeComplete = 100, + PackageMigrationComplete = 101 + } /// - /// Used to notify when the core runtime can do an unattended upgrade. + /// Gets/sets the result of the unattended upgrade /// - /// - /// It is entirely up to the handler to determine if an unattended upgrade should occur and - /// to perform the logic. - /// - public class RuntimeUnattendedUpgradeNotification : INotification - { - /// - /// Gets/sets the result of the unattended upgrade - /// - public UpgradeResult UnattendedUpgradeResult { get; set; } = UpgradeResult.NotRequired; - - public enum UpgradeResult - { - NotRequired = 0, - HasErrors = 1, - CoreUpgradeComplete = 100, - PackageMigrationComplete = 101 - } - } + public UpgradeResult UnattendedUpgradeResult { get; set; } = UpgradeResult.NotRequired; } diff --git a/src/Umbraco.Core/Notifications/SavedNotification.cs b/src/Umbraco.Core/Notifications/SavedNotification.cs index 0a9af8c1ff38..5fcae2a39eb4 100644 --- a/src/Umbraco.Core/Notifications/SavedNotification.cs +++ b/src/Umbraco.Core/Notifications/SavedNotification.cs @@ -1,21 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class SavedNotification : EnumerableObjectNotification { - public abstract class SavedNotification : EnumerableObjectNotification + protected SavedNotification(T target, EventMessages messages) : base(target, messages) { - protected SavedNotification(T target, EventMessages messages) : base(target, messages) - { - } - - protected SavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + } - public IEnumerable SavedEntities => Target; + protected SavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } + + public IEnumerable SavedEntities => Target; } diff --git a/src/Umbraco.Core/Notifications/SavingNotification.cs b/src/Umbraco.Core/Notifications/SavingNotification.cs index 34962f53963c..0af55857c208 100644 --- a/src/Umbraco.Core/Notifications/SavingNotification.cs +++ b/src/Umbraco.Core/Notifications/SavingNotification.cs @@ -1,21 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class SavingNotification : CancelableEnumerableObjectNotification { - public abstract class SavingNotification : CancelableEnumerableObjectNotification + protected SavingNotification(T target, EventMessages messages) : base(target, messages) { - protected SavingNotification(T target, EventMessages messages) : base(target, messages) - { - } - - protected SavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + } - public IEnumerable SavedEntities => Target; + protected SavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } + + public IEnumerable SavedEntities => Target; } diff --git a/src/Umbraco.Core/Notifications/ScopedEntityRemoveNotification.cs b/src/Umbraco.Core/Notifications/ScopedEntityRemoveNotification.cs index 307ae2103cfe..865f95e083a3 100644 --- a/src/Umbraco.Core/Notifications/ScopedEntityRemoveNotification.cs +++ b/src/Umbraco.Core/Notifications/ScopedEntityRemoveNotification.cs @@ -1,18 +1,16 @@ -using System; using System.ComponentModel; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +[Obsolete("This is only used for the internal cache and will change, use tree change notifications instead")] +[EditorBrowsable(EditorBrowsableState.Never)] +public class ScopedEntityRemoveNotification : ObjectNotification { - [Obsolete("This is only used for the internal cache and will change, use tree change notifications instead")] - [EditorBrowsable(EditorBrowsableState.Never)] - public class ScopedEntityRemoveNotification : ObjectNotification + public ScopedEntityRemoveNotification(IContentBase target, EventMessages messages) : base(target, messages) { - public ScopedEntityRemoveNotification(IContentBase target, EventMessages messages) : base(target, messages) - { - } - - public IContentBase Entity => Target; } + + public IContentBase Entity => Target; } diff --git a/src/Umbraco.Core/Notifications/ScriptDeletedNotification.cs b/src/Umbraco.Core/Notifications/ScriptDeletedNotification.cs index 650f2d056451..d4150b2e94c1 100644 --- a/src/Umbraco.Core/Notifications/ScriptDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/ScriptDeletedNotification.cs @@ -4,12 +4,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class ScriptDeletedNotification : DeletedNotification { - public class ScriptDeletedNotification : DeletedNotification + public ScriptDeletedNotification(IScript target, EventMessages messages) : base(target, messages) { - public ScriptDeletedNotification(IScript target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/ScriptDeletingNotification.cs b/src/Umbraco.Core/Notifications/ScriptDeletingNotification.cs index 085c98d600a6..aa4675416c9c 100644 --- a/src/Umbraco.Core/Notifications/ScriptDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/ScriptDeletingNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class ScriptDeletingNotification : DeletingNotification { - public class ScriptDeletingNotification : DeletingNotification + public ScriptDeletingNotification(IScript target, EventMessages messages) : base(target, messages) { - public ScriptDeletingNotification(IScript target, EventMessages messages) : base(target, messages) - { - } + } - public ScriptDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public ScriptDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/ScriptSavedNotification.cs b/src/Umbraco.Core/Notifications/ScriptSavedNotification.cs index 6ccb9f144605..8e70f3d9d68d 100644 --- a/src/Umbraco.Core/Notifications/ScriptSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/ScriptSavedNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class ScriptSavedNotification : SavedNotification { - public class ScriptSavedNotification : SavedNotification + public ScriptSavedNotification(IScript target, EventMessages messages) : base(target, messages) { - public ScriptSavedNotification(IScript target, EventMessages messages) : base(target, messages) - { - } + } - public ScriptSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public ScriptSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/ScriptSavingNotification.cs b/src/Umbraco.Core/Notifications/ScriptSavingNotification.cs index 92ad0ded4e94..87c8fd290b39 100644 --- a/src/Umbraco.Core/Notifications/ScriptSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/ScriptSavingNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class ScriptSavingNotification : SavingNotification { - public class ScriptSavingNotification : SavingNotification + public ScriptSavingNotification(IScript target, EventMessages messages) : base(target, messages) { - public ScriptSavingNotification(IScript target, EventMessages messages) : base(target, messages) - { - } + } - public ScriptSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public ScriptSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/SendEmailNotification.cs b/src/Umbraco.Core/Notifications/SendEmailNotification.cs index f87a2a0ba8e4..66d7ee038aa2 100644 --- a/src/Umbraco.Core/Notifications/SendEmailNotification.cs +++ b/src/Umbraco.Core/Notifications/SendEmailNotification.cs @@ -1,30 +1,29 @@ using Umbraco.Cms.Core.Models.Email; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class SendEmailNotification : INotification { - public class SendEmailNotification : INotification + public SendEmailNotification(NotificationEmailModel message, string emailType) { - public SendEmailNotification(NotificationEmailModel message, string emailType) - { - Message = message; - EmailType = emailType; - } + Message = message; + EmailType = emailType; + } - public NotificationEmailModel Message { get; } + public NotificationEmailModel Message { get; } - /// - /// Some metadata about the email which can be used by handlers to determine if they should handle the email or not - /// - public string EmailType { get; } + /// + /// Some metadata about the email which can be used by handlers to determine if they should handle the email or not + /// + public string EmailType { get; } - /// - /// Call to tell Umbraco that the email sending is handled. - /// - public void HandleEmail() => IsHandled = true; + /// + /// Returns true if the email sending is handled. + /// + public bool IsHandled { get; private set; } - /// - /// Returns true if the email sending is handled. - /// - public bool IsHandled { get; private set; } - } + /// + /// Call to tell Umbraco that the email sending is handled. + /// + public void HandleEmail() => IsHandled = true; } diff --git a/src/Umbraco.Core/Notifications/SendingAllowedChildrenNotification.cs b/src/Umbraco.Core/Notifications/SendingAllowedChildrenNotification.cs index 07ab3c362680..ff57f9c902bc 100644 --- a/src/Umbraco.Core/Notifications/SendingAllowedChildrenNotification.cs +++ b/src/Umbraco.Core/Notifications/SendingAllowedChildrenNotification.cs @@ -1,19 +1,17 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Web; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class SendingAllowedChildrenNotification : INotification { - public class SendingAllowedChildrenNotification : INotification + public SendingAllowedChildrenNotification(IEnumerable children, IUmbracoContext umbracoContext) { - public IUmbracoContext UmbracoContext { get; } + UmbracoContext = umbracoContext; + Children = children; + } - public IEnumerable Children { get; set; } + public IUmbracoContext UmbracoContext { get; } - public SendingAllowedChildrenNotification(IEnumerable children, IUmbracoContext umbracoContext) - { - UmbracoContext = umbracoContext; - Children = children; - } - } + public IEnumerable Children { get; set; } } diff --git a/src/Umbraco.Core/Notifications/SendingContentNotification.cs b/src/Umbraco.Core/Notifications/SendingContentNotification.cs index 4d8d93ce7593..a42fefca68c6 100644 --- a/src/Umbraco.Core/Notifications/SendingContentNotification.cs +++ b/src/Umbraco.Core/Notifications/SendingContentNotification.cs @@ -1,18 +1,17 @@ using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Web; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class SendingContentNotification : INotification { - public class SendingContentNotification : INotification + public SendingContentNotification(ContentItemDisplay content, IUmbracoContext umbracoContext) { - public IUmbracoContext UmbracoContext { get; } + Content = content; + UmbracoContext = umbracoContext; + } - public ContentItemDisplay Content { get; } + public IUmbracoContext UmbracoContext { get; } - public SendingContentNotification(ContentItemDisplay content, IUmbracoContext umbracoContext) - { - Content = content; - UmbracoContext = umbracoContext; - } - } + public ContentItemDisplay Content { get; } } diff --git a/src/Umbraco.Core/Notifications/SendingDashboardsNotification.cs b/src/Umbraco.Core/Notifications/SendingDashboardsNotification.cs index b81339fcbf39..886e25752940 100644 --- a/src/Umbraco.Core/Notifications/SendingDashboardsNotification.cs +++ b/src/Umbraco.Core/Notifications/SendingDashboardsNotification.cs @@ -1,20 +1,18 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Dashboards; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Web; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class SendingDashboardsNotification : INotification { - public class SendingDashboardsNotification : INotification + public SendingDashboardsNotification(IEnumerable> dashboards, IUmbracoContext umbracoContext) { - public IUmbracoContext UmbracoContext { get; } + Dashboards = dashboards; + UmbracoContext = umbracoContext; + } - public IEnumerable> Dashboards { get; } + public IUmbracoContext UmbracoContext { get; } - public SendingDashboardsNotification(IEnumerable> dashboards, IUmbracoContext umbracoContext) - { - Dashboards = dashboards; - UmbracoContext = umbracoContext; - } - } + public IEnumerable> Dashboards { get; } } diff --git a/src/Umbraco.Core/Notifications/SendingMediaNotification.cs b/src/Umbraco.Core/Notifications/SendingMediaNotification.cs index 2fd8f65a4de4..cca282b3eaaf 100644 --- a/src/Umbraco.Core/Notifications/SendingMediaNotification.cs +++ b/src/Umbraco.Core/Notifications/SendingMediaNotification.cs @@ -1,18 +1,17 @@ using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Web; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class SendingMediaNotification : INotification { - public class SendingMediaNotification : INotification + public SendingMediaNotification(MediaItemDisplay media, IUmbracoContext umbracoContext) { - public IUmbracoContext UmbracoContext { get; } + Media = media; + UmbracoContext = umbracoContext; + } - public MediaItemDisplay Media { get; } + public IUmbracoContext UmbracoContext { get; } - public SendingMediaNotification(MediaItemDisplay media, IUmbracoContext umbracoContext) - { - Media = media; - UmbracoContext = umbracoContext; - } - } + public MediaItemDisplay Media { get; } } diff --git a/src/Umbraco.Core/Notifications/SendingMemberNotification.cs b/src/Umbraco.Core/Notifications/SendingMemberNotification.cs index cc868836f919..e9e03a868f53 100644 --- a/src/Umbraco.Core/Notifications/SendingMemberNotification.cs +++ b/src/Umbraco.Core/Notifications/SendingMemberNotification.cs @@ -1,18 +1,17 @@ using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Web; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class SendingMemberNotification : INotification { - public class SendingMemberNotification : INotification + public SendingMemberNotification(MemberDisplay member, IUmbracoContext umbracoContext) { - public IUmbracoContext UmbracoContext { get; } + Member = member; + UmbracoContext = umbracoContext; + } - public MemberDisplay Member { get; } + public IUmbracoContext UmbracoContext { get; } - public SendingMemberNotification(MemberDisplay member, IUmbracoContext umbracoContext) - { - Member = member; - UmbracoContext = umbracoContext; - } - } + public MemberDisplay Member { get; } } diff --git a/src/Umbraco.Core/Notifications/SendingUserNotification.cs b/src/Umbraco.Core/Notifications/SendingUserNotification.cs index 9e3422f1d9ba..da46ec749e5b 100644 --- a/src/Umbraco.Core/Notifications/SendingUserNotification.cs +++ b/src/Umbraco.Core/Notifications/SendingUserNotification.cs @@ -1,18 +1,17 @@ using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Web; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class SendingUserNotification : INotification { - public class SendingUserNotification : INotification + public SendingUserNotification(UserDisplay user, IUmbracoContext umbracoContext) { - public IUmbracoContext UmbracoContext { get; } + User = user; + UmbracoContext = umbracoContext; + } - public UserDisplay User { get; } + public IUmbracoContext UmbracoContext { get; } - public SendingUserNotification(UserDisplay user, IUmbracoContext umbracoContext) - { - User = user; - UmbracoContext = umbracoContext; - } - } + public UserDisplay User { get; } } diff --git a/src/Umbraco.Core/Notifications/ServerVariablesParsingNotification.cs b/src/Umbraco.Core/Notifications/ServerVariablesParsingNotification.cs index 7fa83a5a6d94..cfeb075de4ac 100644 --- a/src/Umbraco.Core/Notifications/ServerVariablesParsingNotification.cs +++ b/src/Umbraco.Core/Notifications/ServerVariablesParsingNotification.cs @@ -1,20 +1,18 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Notifications +/// +/// A notification for when server variables are parsing +/// +public class ServerVariablesParsingNotification : INotification { /// - /// A notification for when server variables are parsing + /// Initializes a new instance of the class. /// - public class ServerVariablesParsingNotification : INotification - { - /// - /// Initializes a new instance of the class. - /// - public ServerVariablesParsingNotification(IDictionary serverVariables) => ServerVariables = serverVariables; + public ServerVariablesParsingNotification(IDictionary serverVariables) => + ServerVariables = serverVariables; - /// - /// Gets a mutable dictionary of server variables - /// - public IDictionary ServerVariables { get; } - } + /// + /// Gets a mutable dictionary of server variables + /// + public IDictionary ServerVariables { get; } } diff --git a/src/Umbraco.Core/Notifications/SortedNotification.cs b/src/Umbraco.Core/Notifications/SortedNotification.cs index ffc50d6bc966..fd3af5b028d3 100644 --- a/src/Umbraco.Core/Notifications/SortedNotification.cs +++ b/src/Umbraco.Core/Notifications/SortedNotification.cs @@ -1,17 +1,15 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class SortedNotification : EnumerableObjectNotification { - public abstract class SortedNotification : EnumerableObjectNotification + protected SortedNotification(IEnumerable target, EventMessages messages) : base(target, messages) { - protected SortedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } - - public IEnumerable SortedEntities => Target; } + + public IEnumerable SortedEntities => Target; } diff --git a/src/Umbraco.Core/Notifications/SortingNotification.cs b/src/Umbraco.Core/Notifications/SortingNotification.cs index 1801bfa65604..d46768f42b20 100644 --- a/src/Umbraco.Core/Notifications/SortingNotification.cs +++ b/src/Umbraco.Core/Notifications/SortingNotification.cs @@ -1,17 +1,15 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class SortingNotification : CancelableEnumerableObjectNotification { - public abstract class SortingNotification : CancelableEnumerableObjectNotification + protected SortingNotification(IEnumerable target, EventMessages messages) : base(target, messages) { - protected SortingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } - - public IEnumerable SortedEntities => Target; } + + public IEnumerable SortedEntities => Target; } diff --git a/src/Umbraco.Core/Notifications/StatefulNotification.cs b/src/Umbraco.Core/Notifications/StatefulNotification.cs index 15ee320a40f0..8306d976fb97 100644 --- a/src/Umbraco.Core/Notifications/StatefulNotification.cs +++ b/src/Umbraco.Core/Notifications/StatefulNotification.cs @@ -1,21 +1,19 @@ // Copyright (c) Umbraco. -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Notifications +public abstract class StatefulNotification : IStatefulNotification { - public abstract class StatefulNotification : IStatefulNotification - { - private IDictionary? _state; + private IDictionary? _state; - /// - /// This can be used by event subscribers to store state in the notification so they easily deal with custom state data between - /// a starting ("ing") and an ending ("ed") notification - /// - public IDictionary State - { - get => _state ??= new Dictionary(); - set => _state = value; - } + /// + /// This can be used by event subscribers to store state in the notification so they easily deal with custom state data + /// between + /// a starting ("ing") and an ending ("ed") notification + /// + public IDictionary State + { + get => _state ??= new Dictionary(); + set => _state = value; } } diff --git a/src/Umbraco.Core/Notifications/StylesheetDeletedNotification.cs b/src/Umbraco.Core/Notifications/StylesheetDeletedNotification.cs index 743cadab6314..0afdfdbbdc5f 100644 --- a/src/Umbraco.Core/Notifications/StylesheetDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/StylesheetDeletedNotification.cs @@ -4,12 +4,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class StylesheetDeletedNotification : DeletedNotification { - public class StylesheetDeletedNotification : DeletedNotification + public StylesheetDeletedNotification(IStylesheet target, EventMessages messages) : base(target, messages) { - public StylesheetDeletedNotification(IStylesheet target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/StylesheetDeletingNotification.cs b/src/Umbraco.Core/Notifications/StylesheetDeletingNotification.cs index 8a0c411b1346..0bc6fe2a4e8c 100644 --- a/src/Umbraco.Core/Notifications/StylesheetDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/StylesheetDeletingNotification.cs @@ -1,20 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class StylesheetDeletingNotification : DeletingNotification { - public class StylesheetDeletingNotification : DeletingNotification + public StylesheetDeletingNotification(IStylesheet target, EventMessages messages) : base(target, messages) { - public StylesheetDeletingNotification(IStylesheet target, EventMessages messages) : base(target, messages) - { - } + } - public StylesheetDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public StylesheetDeletingNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/StylesheetSavedNotification.cs b/src/Umbraco.Core/Notifications/StylesheetSavedNotification.cs index 0ceeb209e0a2..97bb16830865 100644 --- a/src/Umbraco.Core/Notifications/StylesheetSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/StylesheetSavedNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class StylesheetSavedNotification : SavedNotification { - public class StylesheetSavedNotification : SavedNotification + public StylesheetSavedNotification(IStylesheet target, EventMessages messages) : base(target, messages) { - public StylesheetSavedNotification(IStylesheet target, EventMessages messages) : base(target, messages) - { - } + } - public StylesheetSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public StylesheetSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/StylesheetSavingNotification.cs b/src/Umbraco.Core/Notifications/StylesheetSavingNotification.cs index d08bdebac420..fca2846b811b 100644 --- a/src/Umbraco.Core/Notifications/StylesheetSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/StylesheetSavingNotification.cs @@ -1,20 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class StylesheetSavingNotification : SavingNotification { - public class StylesheetSavingNotification : SavingNotification + public StylesheetSavingNotification(IStylesheet target, EventMessages messages) : base(target, messages) { - public StylesheetSavingNotification(IStylesheet target, EventMessages messages) : base(target, messages) - { - } + } - public StylesheetSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public StylesheetSavingNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/TemplateCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/TemplateCacheRefresherNotification.cs index 689d2a52ffb3..c1f76cfb8a18 100644 --- a/src/Umbraco.Core/Notifications/TemplateCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/TemplateCacheRefresherNotification.cs @@ -1,11 +1,11 @@ using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class TemplateCacheRefresherNotification : CacheRefresherNotification { - public class TemplateCacheRefresherNotification : CacheRefresherNotification + public TemplateCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + messageType) { - public TemplateCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) - { - } } } diff --git a/src/Umbraco.Core/Notifications/TemplateDeletedNotification.cs b/src/Umbraco.Core/Notifications/TemplateDeletedNotification.cs index 01d6dc7e6de1..f619b26aeb21 100644 --- a/src/Umbraco.Core/Notifications/TemplateDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/TemplateDeletedNotification.cs @@ -4,12 +4,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class TemplateDeletedNotification : DeletedNotification { - public class TemplateDeletedNotification : DeletedNotification + public TemplateDeletedNotification(ITemplate target, EventMessages messages) : base(target, messages) { - public TemplateDeletedNotification(ITemplate target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/TemplateDeletingNotification.cs b/src/Umbraco.Core/Notifications/TemplateDeletingNotification.cs index 6434c47c4655..61463d331f3f 100644 --- a/src/Umbraco.Core/Notifications/TemplateDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/TemplateDeletingNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class TemplateDeletingNotification : DeletingNotification { - public class TemplateDeletingNotification : DeletingNotification + public TemplateDeletingNotification(ITemplate target, EventMessages messages) : base(target, messages) { - public TemplateDeletingNotification(ITemplate target, EventMessages messages) : base(target, messages) - { - } + } - public TemplateDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public TemplateDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/TemplateSavedNotification.cs b/src/Umbraco.Core/Notifications/TemplateSavedNotification.cs index ad75a32c02a6..4505f1b80c68 100644 --- a/src/Umbraco.Core/Notifications/TemplateSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/TemplateSavedNotification.cs @@ -1,68 +1,66 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class TemplateSavedNotification : SavedNotification { - public class TemplateSavedNotification : SavedNotification - { - private const string s_templateForContentTypeKey = "CreateTemplateForContentType"; - private const string s_contentTypeAliasKey = "ContentTypeAlias"; + private const string s_templateForContentTypeKey = "CreateTemplateForContentType"; + private const string s_contentTypeAliasKey = "ContentTypeAlias"; - public TemplateSavedNotification(ITemplate target, EventMessages messages) : base(target, messages) - { - } + public TemplateSavedNotification(ITemplate target, EventMessages messages) : base(target, messages) + { + } - public TemplateSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public TemplateSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } - public bool CreateTemplateForContentType + public bool CreateTemplateForContentType + { + get { - get + if (State?.TryGetValue(s_templateForContentTypeKey, out var result) ?? false) { - if (State?.TryGetValue(s_templateForContentTypeKey, out var result) ?? false) + if (result is not bool createTemplate) { - if (result is not bool createTemplate) - { - return false; - } - - return createTemplate; + return false; } - return false; + return createTemplate; } - set + + return false; + } + set + { + if (!value is bool && State is not null) { - if (!value is bool && State is not null) - { - State[s_templateForContentTypeKey] = value; - } + State[s_templateForContentTypeKey] = value; } } + } - public string? ContentTypeAlias + public string? ContentTypeAlias + { + get { - get + if (State?.TryGetValue(s_contentTypeAliasKey, out var result) ?? false) { - if (State?.TryGetValue(s_contentTypeAliasKey, out var result) ?? false) - { - return result as string; - } - - return null; + return result as string; } - set + return null; + } + + set + { + if (value is not null && State is not null) { - if (value is not null && State is not null) - { - State[s_contentTypeAliasKey] = value; - } + State[s_contentTypeAliasKey] = value; } } } diff --git a/src/Umbraco.Core/Notifications/TemplateSavingNotification.cs b/src/Umbraco.Core/Notifications/TemplateSavingNotification.cs index 95a681d2f827..ec03747e4e93 100644 --- a/src/Umbraco.Core/Notifications/TemplateSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/TemplateSavingNotification.cs @@ -1,83 +1,81 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class TemplateSavingNotification : SavingNotification { - public class TemplateSavingNotification : SavingNotification - { - private const string s_templateForContentTypeKey = "CreateTemplateForContentType"; - private const string s_contentTypeAliasKey = "ContentTypeAlias"; + private const string s_templateForContentTypeKey = "CreateTemplateForContentType"; + private const string s_contentTypeAliasKey = "ContentTypeAlias"; - public TemplateSavingNotification(ITemplate target, EventMessages messages) : base(target, messages) - { - } + public TemplateSavingNotification(ITemplate target, EventMessages messages) : base(target, messages) + { + } - public TemplateSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public TemplateSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { + } - public TemplateSavingNotification(ITemplate target, EventMessages messages, bool createTemplateForContentType, - string contentTypeAlias) : base(target, messages) - { - CreateTemplateForContentType = createTemplateForContentType; - ContentTypeAlias = contentTypeAlias; - } + public TemplateSavingNotification(ITemplate target, EventMessages messages, bool createTemplateForContentType, + string contentTypeAlias) : base(target, messages) + { + CreateTemplateForContentType = createTemplateForContentType; + ContentTypeAlias = contentTypeAlias; + } - public TemplateSavingNotification(IEnumerable target, EventMessages messages, - bool createTemplateForContentType, - string contentTypeAlias) : base(target, messages) - { - CreateTemplateForContentType = createTemplateForContentType; - ContentTypeAlias = contentTypeAlias; - } + public TemplateSavingNotification(IEnumerable target, EventMessages messages, + bool createTemplateForContentType, + string contentTypeAlias) : base(target, messages) + { + CreateTemplateForContentType = createTemplateForContentType; + ContentTypeAlias = contentTypeAlias; + } - public bool CreateTemplateForContentType + public bool CreateTemplateForContentType + { + get { - get + if (State?.TryGetValue(s_templateForContentTypeKey, out var result) ?? false) { - if (State?.TryGetValue(s_templateForContentTypeKey, out var result) ?? false) + if (result is not bool createTemplate) { - if (result is not bool createTemplate) - { - return false; - } - - return createTemplate; + return false; } - return false; + return createTemplate; } - set + + return false; + } + set + { + if (!value is bool && State is not null) { - if (!value is bool && State is not null) - { - State[s_templateForContentTypeKey] = value; - } + State[s_templateForContentTypeKey] = value; } } + } - public string? ContentTypeAlias + public string? ContentTypeAlias + { + get { - get + if (State?.TryGetValue(s_contentTypeAliasKey, out var result) ?? false) { - if (State?.TryGetValue(s_contentTypeAliasKey, out var result) ?? false) - { - return result as string; - } - - return null; + return result as string; } - set + return null; + } + + set + { + if (value is not null && State is not null) { - if (value is not null && State is not null) - { - State[s_contentTypeAliasKey] = value; - } + State[s_contentTypeAliasKey] = value; } } } diff --git a/src/Umbraco.Core/Notifications/TreeChangeNotification.cs b/src/Umbraco.Core/Notifications/TreeChangeNotification.cs index bdbd0fc044a3..6cd65682838a 100644 --- a/src/Umbraco.Core/Notifications/TreeChangeNotification.cs +++ b/src/Umbraco.Core/Notifications/TreeChangeNotification.cs @@ -1,19 +1,17 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Services.Changes; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public abstract class TreeChangeNotification : EnumerableObjectNotification> { - public abstract class TreeChangeNotification : EnumerableObjectNotification> + protected TreeChangeNotification(TreeChange target, EventMessages messages) : base(target, messages) { - protected TreeChangeNotification(TreeChange target, EventMessages messages) : base(target, messages) - { - } - - protected TreeChangeNotification(IEnumerable> target, EventMessages messages) : base(target, messages) - { - } + } - public IEnumerable> Changes => Target; + protected TreeChangeNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + { } + + public IEnumerable> Changes => Target; } diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationComponentsInstallingNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationComponentsInstallingNotification.cs index 7f8d8521155a..715ef5a81fce 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationComponentsInstallingNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationComponentsInstallingNotification.cs @@ -1,29 +1,26 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; +namespace Umbraco.Cms.Core.Notifications; +// TODO (V10): Remove this class. -namespace Umbraco.Cms.Core.Notifications +/// +/// Notification that occurs during the Umbraco boot process, before instances of initialize. +/// +[Obsolete( + "This notification was added to the core runtime start-up as a hook for Umbraco Cloud local connection string and database setup. " + + "Following re-work they are no longer used (from Deploy 9.2.0)." + + "Given they are non-documented and no other use is expected, they can be removed in the next major release")] +public class UmbracoApplicationComponentsInstallingNotification : INotification { - // TODO (V10): Remove this class. - /// - /// Notification that occurs during the Umbraco boot process, before instances of initialize. + /// Initializes a new instance of the class. /// - [Obsolete("This notification was added to the core runtime start-up as a hook for Umbraco Cloud local connection string and database setup. " + - "Following re-work they are no longer used (from Deploy 9.2.0)." + - "Given they are non-documented and no other use is expected, they can be removed in the next major release")] - public class UmbracoApplicationComponentsInstallingNotification : INotification - { - /// - /// Initializes a new instance of the class. - /// - /// The runtime level - public UmbracoApplicationComponentsInstallingNotification(RuntimeLevel runtimeLevel) => RuntimeLevel = runtimeLevel; + /// The runtime level + public UmbracoApplicationComponentsInstallingNotification(RuntimeLevel runtimeLevel) => RuntimeLevel = runtimeLevel; - /// - /// Gets the runtime level of execution. - /// - public RuntimeLevel RuntimeLevel { get; } - } + /// + /// Gets the runtime level of execution. + /// + public RuntimeLevel RuntimeLevel { get; } } diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationMainDomAcquiredNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationMainDomAcquiredNotification.cs index 66593ab08654..7aa1da0e5067 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationMainDomAcquiredNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationMainDomAcquiredNotification.cs @@ -1,26 +1,23 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; +namespace Umbraco.Cms.Core.Notifications; +// TODO (V10): Remove this class. -namespace Umbraco.Cms.Core.Notifications +/// +/// Notification that occurs during Umbraco boot after the MainDom has been acquired. +/// +[Obsolete( + "This notification was added to the core runtime start-up as a hook for Umbraco Cloud local connection string and database setup. " + + "Following re-work they are no longer used (from Deploy 9.2.0)." + + "Given they are non-documented and no other use is expected, they can be removed in the next major release")] +public class UmbracoApplicationMainDomAcquiredNotification : INotification { - // TODO (V10): Remove this class. - /// - /// Notification that occurs during Umbraco boot after the MainDom has been acquired. + /// Initializes a new instance of the class. /// - [Obsolete("This notification was added to the core runtime start-up as a hook for Umbraco Cloud local connection string and database setup. " + - "Following re-work they are no longer used (from Deploy 9.2.0)." + - "Given they are non-documented and no other use is expected, they can be removed in the next major release")] - public class UmbracoApplicationMainDomAcquiredNotification : INotification + /// The runtime level + public UmbracoApplicationMainDomAcquiredNotification() { - /// - /// Initializes a new instance of the class. - /// - /// The runtime level - public UmbracoApplicationMainDomAcquiredNotification() - { - } } } diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs index 196af7dfe1f5..1e3f2b7bfd5e 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStartedNotification.cs @@ -1,18 +1,17 @@ -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +/// +/// Notification that occurs when Umbraco has completely booted up and the request processing pipeline is configured. +/// +/// +public class UmbracoApplicationStartedNotification : IUmbracoApplicationLifetimeNotification { /// - /// Notification that occurs when Umbraco has completely booted up and the request processing pipeline is configured. + /// Initializes a new instance of the class. /// - /// - public class UmbracoApplicationStartedNotification : IUmbracoApplicationLifetimeNotification - { - /// - /// Initializes a new instance of the class. - /// - /// Indicates whether Umbraco is restarting. - public UmbracoApplicationStartedNotification(bool isRestarting) => IsRestarting = isRestarting; + /// Indicates whether Umbraco is restarting. + public UmbracoApplicationStartedNotification(bool isRestarting) => IsRestarting = isRestarting; - /// - public bool IsRestarting { get; } - } + /// + public bool IsRestarting { get; } } diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs index 82b87aa3bfc9..7c7e97f29f84 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStartingNotification.cs @@ -1,44 +1,42 @@ -using System; +namespace Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Notifications +/// +/// Notification that occurs at the very end of the Umbraco boot process (after all s are +/// initialized). +/// +/// +public class UmbracoApplicationStartingNotification : IUmbracoApplicationLifetimeNotification { /// - /// Notification that occurs at the very end of the Umbraco boot process (after all s are initialized). + /// Initializes a new instance of the class. /// - /// - public class UmbracoApplicationStartingNotification : IUmbracoApplicationLifetimeNotification + /// The runtime level + [Obsolete("Use ctor with all params")] + public UmbracoApplicationStartingNotification(RuntimeLevel runtimeLevel) + : this(runtimeLevel, false) { - /// - /// Initializes a new instance of the class. - /// - /// The runtime level - [Obsolete("Use ctor with all params")] - public UmbracoApplicationStartingNotification(RuntimeLevel runtimeLevel) - : this(runtimeLevel, false) - { - // TODO: Remove this constructor in V10 - } + // TODO: Remove this constructor in V10 + } - /// - /// Initializes a new instance of the class. - /// - /// The runtime level - /// Indicates whether Umbraco is restarting. - public UmbracoApplicationStartingNotification(RuntimeLevel runtimeLevel, bool isRestarting) - { - RuntimeLevel = runtimeLevel; - IsRestarting = isRestarting; - } + /// + /// Initializes a new instance of the class. + /// + /// The runtime level + /// Indicates whether Umbraco is restarting. + public UmbracoApplicationStartingNotification(RuntimeLevel runtimeLevel, bool isRestarting) + { + RuntimeLevel = runtimeLevel; + IsRestarting = isRestarting; + } - /// - /// Gets the runtime level. - /// - /// - /// The runtime level. - /// - public RuntimeLevel RuntimeLevel { get; } + /// + /// Gets the runtime level. + /// + /// + /// The runtime level. + /// + public RuntimeLevel RuntimeLevel { get; } - /// - public bool IsRestarting { get; } - } + /// + public bool IsRestarting { get; } } diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs index c6dac40a268c..ce9936a137fb 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppedNotification.cs @@ -1,18 +1,17 @@ -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +/// +/// Notification that occurs when Umbraco has completely shutdown. +/// +/// +public class UmbracoApplicationStoppedNotification : IUmbracoApplicationLifetimeNotification { /// - /// Notification that occurs when Umbraco has completely shutdown. + /// Initializes a new instance of the class. /// - /// - public class UmbracoApplicationStoppedNotification : IUmbracoApplicationLifetimeNotification - { - /// - /// Initializes a new instance of the class. - /// - /// Indicates whether Umbraco is restarting. - public UmbracoApplicationStoppedNotification(bool isRestarting) => IsRestarting = isRestarting; + /// Indicates whether Umbraco is restarting. + public UmbracoApplicationStoppedNotification(bool isRestarting) => IsRestarting = isRestarting; - /// - public bool IsRestarting { get; } - } + /// + public bool IsRestarting { get; } } diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs index 062ca954d94d..a877bd31626c 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationStoppingNotification.cs @@ -1,30 +1,27 @@ -using System; +namespace Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Notifications +/// +/// Notification that occurs when Umbraco is shutting down (after all s are terminated). +/// +/// +public class UmbracoApplicationStoppingNotification : IUmbracoApplicationLifetimeNotification { /// - /// Notification that occurs when Umbraco is shutting down (after all s are terminated). + /// Initializes a new instance of the class. /// - /// - public class UmbracoApplicationStoppingNotification : IUmbracoApplicationLifetimeNotification + [Obsolete("Use ctor with all params")] + public UmbracoApplicationStoppingNotification() + : this(false) { - /// - /// Initializes a new instance of the class. - /// - [Obsolete("Use ctor with all params")] - public UmbracoApplicationStoppingNotification() - : this(false) - { - // TODO: Remove this constructor in V10 - } + // TODO: Remove this constructor in V10 + } - /// - /// Initializes a new instance of the class. - /// - /// Indicates whether Umbraco is restarting. - public UmbracoApplicationStoppingNotification(bool isRestarting) => IsRestarting = isRestarting; + /// + /// Initializes a new instance of the class. + /// + /// Indicates whether Umbraco is restarting. + public UmbracoApplicationStoppingNotification(bool isRestarting) => IsRestarting = isRestarting; - /// - public bool IsRestarting { get; } - } + /// + public bool IsRestarting { get; } } diff --git a/src/Umbraco.Core/Notifications/UmbracoRequestBeginNotification.cs b/src/Umbraco.Core/Notifications/UmbracoRequestBeginNotification.cs index 76683f8d65e3..fedbb6c35b93 100644 --- a/src/Umbraco.Core/Notifications/UmbracoRequestBeginNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoRequestBeginNotification.cs @@ -3,21 +3,20 @@ using Umbraco.Cms.Core.Web; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +/// +/// Notification raised on each request begin. +/// +public class UmbracoRequestBeginNotification : INotification { /// - /// Notification raised on each request begin. + /// Initializes a new instance of the class. /// - public class UmbracoRequestBeginNotification : INotification - { - /// - /// Initializes a new instance of the class. - /// - public UmbracoRequestBeginNotification(IUmbracoContext umbracoContext) => UmbracoContext = umbracoContext; + public UmbracoRequestBeginNotification(IUmbracoContext umbracoContext) => UmbracoContext = umbracoContext; - /// - /// Gets the - /// - public IUmbracoContext UmbracoContext { get; } - } + /// + /// Gets the + /// + public IUmbracoContext UmbracoContext { get; } } diff --git a/src/Umbraco.Core/Notifications/UmbracoRequestEndNotification.cs b/src/Umbraco.Core/Notifications/UmbracoRequestEndNotification.cs index 27fb6ff09d33..a3f977115385 100644 --- a/src/Umbraco.Core/Notifications/UmbracoRequestEndNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoRequestEndNotification.cs @@ -3,21 +3,20 @@ using Umbraco.Cms.Core.Web; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +/// +/// Notification raised on each request end. +/// +public class UmbracoRequestEndNotification : INotification { /// - /// Notification raised on each request end. + /// Initializes a new instance of the class. /// - public class UmbracoRequestEndNotification : INotification - { - /// - /// Initializes a new instance of the class. - /// - public UmbracoRequestEndNotification(IUmbracoContext umbracoContext) => UmbracoContext = umbracoContext; + public UmbracoRequestEndNotification(IUmbracoContext umbracoContext) => UmbracoContext = umbracoContext; - /// - /// Gets the - /// - public IUmbracoContext UmbracoContext { get; } - } + /// + /// Gets the + /// + public IUmbracoContext UmbracoContext { get; } } diff --git a/src/Umbraco.Core/Notifications/UnattendedInstallNotification.cs b/src/Umbraco.Core/Notifications/UnattendedInstallNotification.cs index 7f9b239ce209..c2e4f27b490a 100644 --- a/src/Umbraco.Core/Notifications/UnattendedInstallNotification.cs +++ b/src/Umbraco.Core/Notifications/UnattendedInstallNotification.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +/// +/// Used to notify that an Unattended install has completed +/// +public class UnattendedInstallNotification : INotification { - /// - /// Used to notify that an Unattended install has completed - /// - public class UnattendedInstallNotification : INotification - { - } } diff --git a/src/Umbraco.Core/Notifications/UserCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/UserCacheRefresherNotification.cs index 4181d74dd765..60ea00bc3f9f 100644 --- a/src/Umbraco.Core/Notifications/UserCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/UserCacheRefresherNotification.cs @@ -1,11 +1,11 @@ using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class UserCacheRefresherNotification : CacheRefresherNotification { - public class UserCacheRefresherNotification : CacheRefresherNotification + public UserCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + messageType) { - public UserCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) - { - } } } diff --git a/src/Umbraco.Core/Notifications/UserDeletedNotification.cs b/src/Umbraco.Core/Notifications/UserDeletedNotification.cs index c272e51b22ad..7518340c679e 100644 --- a/src/Umbraco.Core/Notifications/UserDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserDeletedNotification.cs @@ -4,12 +4,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class UserDeletedNotification : DeletedNotification { - public sealed class UserDeletedNotification : DeletedNotification + public UserDeletedNotification(IUser target, EventMessages messages) : base(target, messages) { - public UserDeletedNotification(IUser target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/UserDeletingNotification.cs b/src/Umbraco.Core/Notifications/UserDeletingNotification.cs index febfa27d9463..3fca9e799ba7 100644 --- a/src/Umbraco.Core/Notifications/UserDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/UserDeletingNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class UserDeletingNotification : DeletingNotification { - public sealed class UserDeletingNotification : DeletingNotification + public UserDeletingNotification(IUser target, EventMessages messages) : base(target, messages) { - public UserDeletingNotification(IUser target, EventMessages messages) : base(target, messages) - { - } + } - public UserDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public UserDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/UserForgotPasswordChangedNotification.cs b/src/Umbraco.Core/Notifications/UserForgotPasswordChangedNotification.cs index b4e93f8b6798..e23b6b4f9925 100644 --- a/src/Umbraco.Core/Notifications/UserForgotPasswordChangedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserForgotPasswordChangedNotification.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class UserForgotPasswordChangedNotification : UserNotification { - public class UserForgotPasswordChangedNotification : UserNotification + public UserForgotPasswordChangedNotification(string ipAddress, string affectedUserId, string performingUserId) : + base(ipAddress, affectedUserId, performingUserId) { - public UserForgotPasswordChangedNotification(string ipAddress, string affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) - { - } } } diff --git a/src/Umbraco.Core/Notifications/UserForgotPasswordRequestedNotification.cs b/src/Umbraco.Core/Notifications/UserForgotPasswordRequestedNotification.cs index 608e5c0f6332..bd6f35da0cc6 100644 --- a/src/Umbraco.Core/Notifications/UserForgotPasswordRequestedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserForgotPasswordRequestedNotification.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class UserForgotPasswordRequestedNotification : UserNotification { - public class UserForgotPasswordRequestedNotification : UserNotification + public UserForgotPasswordRequestedNotification(string ipAddress, string affectedUserId, string performingUserId) : + base(ipAddress, affectedUserId, performingUserId) { - public UserForgotPasswordRequestedNotification(string ipAddress, string affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) - { - } } } diff --git a/src/Umbraco.Core/Notifications/UserGroupCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/UserGroupCacheRefresherNotification.cs index 7aca0d5edb76..3e2075a0ad23 100644 --- a/src/Umbraco.Core/Notifications/UserGroupCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/UserGroupCacheRefresherNotification.cs @@ -1,11 +1,11 @@ using Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class UserGroupCacheRefresherNotification : CacheRefresherNotification { - public class UserGroupCacheRefresherNotification : CacheRefresherNotification + public UserGroupCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + messageType) { - public UserGroupCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, messageType) - { - } } } diff --git a/src/Umbraco.Core/Notifications/UserGroupDeletedNotification.cs b/src/Umbraco.Core/Notifications/UserGroupDeletedNotification.cs index 9877d9544197..f83e176cae11 100644 --- a/src/Umbraco.Core/Notifications/UserGroupDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserGroupDeletedNotification.cs @@ -4,12 +4,11 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class UserGroupDeletedNotification : DeletedNotification { - public sealed class UserGroupDeletedNotification : DeletedNotification + public UserGroupDeletedNotification(IUserGroup target, EventMessages messages) : base(target, messages) { - public UserGroupDeletedNotification(IUserGroup target, EventMessages messages) : base(target, messages) - { - } } } diff --git a/src/Umbraco.Core/Notifications/UserGroupDeletingNotification.cs b/src/Umbraco.Core/Notifications/UserGroupDeletingNotification.cs index af0e8d76d6bc..bceea00f01ec 100644 --- a/src/Umbraco.Core/Notifications/UserGroupDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/UserGroupDeletingNotification.cs @@ -1,20 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class UserGroupDeletingNotification : DeletingNotification { - public sealed class UserGroupDeletingNotification : DeletingNotification + public UserGroupDeletingNotification(IUserGroup target, EventMessages messages) : base(target, messages) { - public UserGroupDeletingNotification(IUserGroup target, EventMessages messages) : base(target, messages) - { - } + } - public UserGroupDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public UserGroupDeletingNotification(IEnumerable target, EventMessages messages) : base(target, + messages) + { } } diff --git a/src/Umbraco.Core/Notifications/UserGroupSavedNotification.cs b/src/Umbraco.Core/Notifications/UserGroupSavedNotification.cs index fee23c06ea8d..75e95be87834 100644 --- a/src/Umbraco.Core/Notifications/UserGroupSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserGroupSavedNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class UserGroupSavedNotification : SavedNotification { - public sealed class UserGroupSavedNotification : SavedNotification + public UserGroupSavedNotification(IUserGroup target, EventMessages messages) : base(target, messages) { - public UserGroupSavedNotification(IUserGroup target, EventMessages messages) : base(target, messages) - { - } + } - public UserGroupSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public UserGroupSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/UserGroupSavingNotification.cs b/src/Umbraco.Core/Notifications/UserGroupSavingNotification.cs index 0dc074bfdcd6..f1e2840493af 100644 --- a/src/Umbraco.Core/Notifications/UserGroupSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/UserGroupSavingNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class UserGroupSavingNotification : SavingNotification { - public sealed class UserGroupSavingNotification : SavingNotification + public UserGroupSavingNotification(IUserGroup target, EventMessages messages) : base(target, messages) { - public UserGroupSavingNotification(IUserGroup target, EventMessages messages) : base(target, messages) - { - } + } - public UserGroupSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public UserGroupSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/UserGroupWithUsersSavedNotification.cs b/src/Umbraco.Core/Notifications/UserGroupWithUsersSavedNotification.cs index 5e239660aa61..22d6ebff7880 100644 --- a/src/Umbraco.Core/Notifications/UserGroupWithUsersSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserGroupWithUsersSavedNotification.cs @@ -1,19 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class UserGroupWithUsersSavedNotification : SavedNotification { - public sealed class UserGroupWithUsersSavedNotification : SavedNotification + public UserGroupWithUsersSavedNotification(UserGroupWithUsers target, EventMessages messages) : base(target, + messages) { - public UserGroupWithUsersSavedNotification(UserGroupWithUsers target, EventMessages messages) : base(target, messages) - { - } + } - public UserGroupWithUsersSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public UserGroupWithUsersSavedNotification(IEnumerable target, EventMessages messages) : base( + target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/UserGroupWithUsersSavingNotification.cs b/src/Umbraco.Core/Notifications/UserGroupWithUsersSavingNotification.cs index f3dd362c20f1..2c5513e64485 100644 --- a/src/Umbraco.Core/Notifications/UserGroupWithUsersSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/UserGroupWithUsersSavingNotification.cs @@ -1,19 +1,19 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class UserGroupWithUsersSavingNotification : SavingNotification { - public sealed class UserGroupWithUsersSavingNotification : SavingNotification + public UserGroupWithUsersSavingNotification(UserGroupWithUsers target, EventMessages messages) : base(target, + messages) { - public UserGroupWithUsersSavingNotification(UserGroupWithUsers target, EventMessages messages) : base(target, messages) - { - } + } - public UserGroupWithUsersSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public UserGroupWithUsersSavingNotification(IEnumerable target, EventMessages messages) : base( + target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/UserLockedNotification.cs b/src/Umbraco.Core/Notifications/UserLockedNotification.cs index b7485d98524d..2a366987bad1 100644 --- a/src/Umbraco.Core/Notifications/UserLockedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserLockedNotification.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class UserLockedNotification : UserNotification { - public class UserLockedNotification : UserNotification + public UserLockedNotification(string ipAddress, string? affectedUserId, string performingUserId) : base(ipAddress, + affectedUserId, performingUserId) { - public UserLockedNotification(string ipAddress, string? affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) - { - } } } diff --git a/src/Umbraco.Core/Notifications/UserLoginFailedNotification.cs b/src/Umbraco.Core/Notifications/UserLoginFailedNotification.cs index ff07b57832d9..d5d1b1dbc79b 100644 --- a/src/Umbraco.Core/Notifications/UserLoginFailedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserLoginFailedNotification.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class UserLoginFailedNotification : UserNotification { - public class UserLoginFailedNotification : UserNotification + public UserLoginFailedNotification(string ipAddress, string affectedUserId, string performingUserId) : base( + ipAddress, affectedUserId, performingUserId) { - public UserLoginFailedNotification(string ipAddress, string affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) - { - } } } diff --git a/src/Umbraco.Core/Notifications/UserLoginRequiresVerificationNotification.cs b/src/Umbraco.Core/Notifications/UserLoginRequiresVerificationNotification.cs index 5a975a19515b..57f037712c69 100644 --- a/src/Umbraco.Core/Notifications/UserLoginRequiresVerificationNotification.cs +++ b/src/Umbraco.Core/Notifications/UserLoginRequiresVerificationNotification.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class UserLoginRequiresVerificationNotification : UserNotification { - public class UserLoginRequiresVerificationNotification : UserNotification + public UserLoginRequiresVerificationNotification(string ipAddress, string? affectedUserId, string performingUserId) + : base(ipAddress, affectedUserId, performingUserId) { - public UserLoginRequiresVerificationNotification(string ipAddress, string? affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) - { - } } } diff --git a/src/Umbraco.Core/Notifications/UserLoginSuccessNotification.cs b/src/Umbraco.Core/Notifications/UserLoginSuccessNotification.cs index e9b79c68fecf..f2b8d09e5de7 100644 --- a/src/Umbraco.Core/Notifications/UserLoginSuccessNotification.cs +++ b/src/Umbraco.Core/Notifications/UserLoginSuccessNotification.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class UserLoginSuccessNotification : UserNotification { - public class UserLoginSuccessNotification : UserNotification + public UserLoginSuccessNotification(string ipAddress, string affectedUserId, string performingUserId) : base( + ipAddress, affectedUserId, performingUserId) { - public UserLoginSuccessNotification(string ipAddress, string affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) - { - } } } diff --git a/src/Umbraco.Core/Notifications/UserLogoutSuccessNotification.cs b/src/Umbraco.Core/Notifications/UserLogoutSuccessNotification.cs index 92e7dea03f6b..6af01f9d13bd 100644 --- a/src/Umbraco.Core/Notifications/UserLogoutSuccessNotification.cs +++ b/src/Umbraco.Core/Notifications/UserLogoutSuccessNotification.cs @@ -1,11 +1,11 @@ -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class UserLogoutSuccessNotification : UserNotification { - public class UserLogoutSuccessNotification : UserNotification + public UserLogoutSuccessNotification(string ipAddress, string? affectedUserId, string performingUserId) : base( + ipAddress, affectedUserId, performingUserId) { - public UserLogoutSuccessNotification(string ipAddress, string? affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) - { - } - - public string? SignOutRedirectUrl { get; set; } } + + public string? SignOutRedirectUrl { get; set; } } diff --git a/src/Umbraco.Core/Notifications/UserNotification.cs b/src/Umbraco.Core/Notifications/UserNotification.cs index f0ce83c8fb05..6141cdf389e0 100644 --- a/src/Umbraco.Core/Notifications/UserNotification.cs +++ b/src/Umbraco.Core/Notifications/UserNotification.cs @@ -1,35 +1,32 @@ -using System; +namespace Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Notifications +public abstract class UserNotification : INotification { - public abstract class UserNotification : INotification + protected UserNotification(string ipAddress, string? affectedUserId, string performingUserId) { - protected UserNotification(string ipAddress, string? affectedUserId, string performingUserId) - { - DateTimeUtc = DateTime.UtcNow; - IpAddress = ipAddress; - AffectedUserId = affectedUserId; - PerformingUserId = performingUserId; - } + DateTimeUtc = DateTime.UtcNow; + IpAddress = ipAddress; + AffectedUserId = affectedUserId; + PerformingUserId = performingUserId; + } - /// - /// Current date/time in UTC format - /// - public DateTime DateTimeUtc { get; } + /// + /// Current date/time in UTC format + /// + public DateTime DateTimeUtc { get; } - /// - /// The source IP address of the user performing the action - /// - public string IpAddress { get; } + /// + /// The source IP address of the user performing the action + /// + public string IpAddress { get; } - /// - /// The user affected by the event raised - /// - public string? AffectedUserId { get; } + /// + /// The user affected by the event raised + /// + public string? AffectedUserId { get; } - /// - /// If a user is performing an action on a different user, then this will be set. Otherwise it will be -1 - /// - public string PerformingUserId { get; } - } + /// + /// If a user is performing an action on a different user, then this will be set. Otherwise it will be -1 + /// + public string PerformingUserId { get; } } diff --git a/src/Umbraco.Core/Notifications/UserPasswordChangedNotification.cs b/src/Umbraco.Core/Notifications/UserPasswordChangedNotification.cs index 098be36867d9..1aec4f485ae5 100644 --- a/src/Umbraco.Core/Notifications/UserPasswordChangedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserPasswordChangedNotification.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class UserPasswordChangedNotification : UserNotification { - public class UserPasswordChangedNotification : UserNotification + public UserPasswordChangedNotification(string ipAddress, string affectedUserId, string performingUserId) : base( + ipAddress, affectedUserId, performingUserId) { - public UserPasswordChangedNotification(string ipAddress, string affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) - { - } } } diff --git a/src/Umbraco.Core/Notifications/UserPasswordResetNotification.cs b/src/Umbraco.Core/Notifications/UserPasswordResetNotification.cs index fc60eef61e21..b79f2333aa03 100644 --- a/src/Umbraco.Core/Notifications/UserPasswordResetNotification.cs +++ b/src/Umbraco.Core/Notifications/UserPasswordResetNotification.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class UserPasswordResetNotification : UserNotification { - public class UserPasswordResetNotification : UserNotification + public UserPasswordResetNotification(string ipAddress, string affectedUserId, string performingUserId) : base( + ipAddress, affectedUserId, performingUserId) { - public UserPasswordResetNotification(string ipAddress, string affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) - { - } } } diff --git a/src/Umbraco.Core/Notifications/UserResetAccessFailedCountNotification.cs b/src/Umbraco.Core/Notifications/UserResetAccessFailedCountNotification.cs index 5cd03cc140da..03fcdebbd8f1 100644 --- a/src/Umbraco.Core/Notifications/UserResetAccessFailedCountNotification.cs +++ b/src/Umbraco.Core/Notifications/UserResetAccessFailedCountNotification.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class UserResetAccessFailedCountNotification : UserNotification { - public class UserResetAccessFailedCountNotification : UserNotification + public UserResetAccessFailedCountNotification(string ipAddress, string affectedUserId, string performingUserId) : + base(ipAddress, affectedUserId, performingUserId) { - public UserResetAccessFailedCountNotification(string ipAddress, string affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) - { - } } } diff --git a/src/Umbraco.Core/Notifications/UserSavedNotification.cs b/src/Umbraco.Core/Notifications/UserSavedNotification.cs index 892218af821d..fa80753c8470 100644 --- a/src/Umbraco.Core/Notifications/UserSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserSavedNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class UserSavedNotification : SavedNotification { - public sealed class UserSavedNotification : SavedNotification + public UserSavedNotification(IUser target, EventMessages messages) : base(target, messages) { - public UserSavedNotification(IUser target, EventMessages messages) : base(target, messages) - { - } + } - public UserSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public UserSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/UserSavingNotification.cs b/src/Umbraco.Core/Notifications/UserSavingNotification.cs index 57c0d867fa97..2922b9a1b152 100644 --- a/src/Umbraco.Core/Notifications/UserSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/UserSavingNotification.cs @@ -1,20 +1,18 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public sealed class UserSavingNotification : SavingNotification { - public sealed class UserSavingNotification : SavingNotification + public UserSavingNotification(IUser target, EventMessages messages) : base(target, messages) { - public UserSavingNotification(IUser target, EventMessages messages) : base(target, messages) - { - } + } - public UserSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) - { - } + public UserSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + { } } diff --git a/src/Umbraco.Core/Notifications/UserTwoFactorRequestedNotification.cs b/src/Umbraco.Core/Notifications/UserTwoFactorRequestedNotification.cs index ccb07c593ca5..93c9432b5e8a 100644 --- a/src/Umbraco.Core/Notifications/UserTwoFactorRequestedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserTwoFactorRequestedNotification.cs @@ -1,14 +1,8 @@ -using System; +namespace Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Notifications +public class UserTwoFactorRequestedNotification : INotification { - public class UserTwoFactorRequestedNotification : INotification - { - public UserTwoFactorRequestedNotification(Guid userKey) - { - UserKey = userKey; - } + public UserTwoFactorRequestedNotification(Guid userKey) => UserKey = userKey; - public Guid UserKey { get; } - } + public Guid UserKey { get; } } diff --git a/src/Umbraco.Core/Notifications/UserUnlockedNotification.cs b/src/Umbraco.Core/Notifications/UserUnlockedNotification.cs index 0c6cc7b9fdea..1fbdbe1589e9 100644 --- a/src/Umbraco.Core/Notifications/UserUnlockedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserUnlockedNotification.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Cms.Core.Notifications +namespace Umbraco.Cms.Core.Notifications; + +public class UserUnlockedNotification : UserNotification { - public class UserUnlockedNotification : UserNotification + public UserUnlockedNotification(string ipAddress, string affectedUserId, string performingUserId) : base(ipAddress, + affectedUserId, performingUserId) { - public UserUnlockedNotification(string ipAddress, string affectedUserId, string performingUserId) : base(ipAddress, affectedUserId, performingUserId) - { - } } } diff --git a/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs b/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs index 16cd4ad0a426..932f919c4bb2 100644 --- a/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs +++ b/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs @@ -1,73 +1,86 @@ -using System; -using System.IO; -using System.Linq; using System.Xml.Linq; using Umbraco.Cms.Core.Models.Packaging; -using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Packaging +namespace Umbraco.Cms.Core.Packaging; + +/// +/// Parses the xml document contained in a compiled (zip) Umbraco package +/// +public class CompiledPackageXmlParser { - /// - /// Parses the xml document contained in a compiled (zip) Umbraco package - /// - public class CompiledPackageXmlParser - { - private readonly ConflictingPackageData _conflictingPackageData; + private readonly ConflictingPackageData _conflictingPackageData; - public CompiledPackageXmlParser(ConflictingPackageData conflictingPackageData) => _conflictingPackageData = conflictingPackageData; + public CompiledPackageXmlParser(ConflictingPackageData conflictingPackageData) => + _conflictingPackageData = conflictingPackageData; - public CompiledPackage ToCompiledPackage(XDocument xml) + public CompiledPackage ToCompiledPackage(XDocument xml) + { + if (xml is null) { - if (xml is null) - { - throw new ArgumentNullException(nameof(xml)); - } - - if (xml.Root == null) throw new InvalidOperationException("The xml document is invalid"); - if (xml.Root.Name != "umbPackage") throw new FormatException("The xml document is invalid"); + throw new ArgumentNullException(nameof(xml)); + } - var info = xml.Root.Element("info"); - if (info == null) throw new FormatException("The xml document is invalid"); - var package = info.Element("package"); - if (package == null) throw new FormatException("The xml document is invalid"); + if (xml.Root == null) + { + throw new InvalidOperationException("The xml document is invalid"); + } - var def = new CompiledPackage - { - // will be null because we don't know where this data is coming from and - // this value is irrelevant during install. - PackageFile = null, - Name = package.Element("name")?.Value ?? string.Empty, - Macros = xml.Root.Element("Macros")?.Elements("macro") ?? Enumerable.Empty(), - MacroPartialViews = xml.Root.Element("MacroPartialViews")?.Elements("View") ?? Enumerable.Empty(), - PartialViews = xml.Root.Element("PartialViews")?.Elements("View") ?? Enumerable.Empty(), - Templates = xml.Root.Element("Templates")?.Elements("Template") ?? Enumerable.Empty(), - Stylesheets = xml.Root.Element("Stylesheets")?.Elements("Stylesheet") ?? Enumerable.Empty(), - Scripts = xml.Root.Element("Scripts")?.Elements("Script") ?? Enumerable.Empty(), - DataTypes = xml.Root.Element("DataTypes")?.Elements("DataType") ?? Enumerable.Empty(), - Languages = xml.Root.Element("Languages")?.Elements("Language") ?? Enumerable.Empty(), - DictionaryItems = xml.Root.Element("DictionaryItems")?.Elements("DictionaryItem") ?? Enumerable.Empty(), - DocumentTypes = xml.Root.Element("DocumentTypes")?.Elements("DocumentType") ?? Enumerable.Empty(), - MediaTypes = xml.Root.Element("MediaTypes")?.Elements("MediaType") ?? Enumerable.Empty(), - Documents = xml.Root.Element("Documents")?.Elements("DocumentSet")?.Select(CompiledPackageContentBase.Create) ?? Enumerable.Empty(), - Media = xml.Root.Element("MediaItems")?.Elements()?.Select(CompiledPackageContentBase.Create) ?? Enumerable.Empty(), - }; + if (xml.Root.Name != "umbPackage") + { + throw new FormatException("The xml document is invalid"); + } - def.Warnings = GetInstallWarnings(def); + XElement info = xml.Root.Element("info"); + if (info == null) + { + throw new FormatException("The xml document is invalid"); + } - return def; + XElement package = info.Element("package"); + if (package == null) + { + throw new FormatException("The xml document is invalid"); } - private InstallWarnings GetInstallWarnings(CompiledPackage package) + var def = new CompiledPackage { - var installWarnings = new InstallWarnings - { - ConflictingMacros = _conflictingPackageData.FindConflictingMacros(package.Macros), - ConflictingTemplates = _conflictingPackageData.FindConflictingTemplates(package.Templates), - ConflictingStylesheets = _conflictingPackageData.FindConflictingStylesheets(package.Stylesheets) - }; + // will be null because we don't know where this data is coming from and + // this value is irrelevant during install. + PackageFile = null, + Name = package.Element("name")?.Value ?? string.Empty, + Macros = xml.Root.Element("Macros")?.Elements("macro") ?? Enumerable.Empty(), + MacroPartialViews = xml.Root.Element("MacroPartialViews")?.Elements("View") ?? Enumerable.Empty(), + PartialViews = xml.Root.Element("PartialViews")?.Elements("View") ?? Enumerable.Empty(), + Templates = xml.Root.Element("Templates")?.Elements("Template") ?? Enumerable.Empty(), + Stylesheets = xml.Root.Element("Stylesheets")?.Elements("Stylesheet") ?? Enumerable.Empty(), + Scripts = xml.Root.Element("Scripts")?.Elements("Script") ?? Enumerable.Empty(), + DataTypes = xml.Root.Element("DataTypes")?.Elements("DataType") ?? Enumerable.Empty(), + Languages = xml.Root.Element("Languages")?.Elements("Language") ?? Enumerable.Empty(), + DictionaryItems = + xml.Root.Element("DictionaryItems")?.Elements("DictionaryItem") ?? Enumerable.Empty(), + DocumentTypes = xml.Root.Element("DocumentTypes")?.Elements("DocumentType") ?? Enumerable.Empty(), + MediaTypes = xml.Root.Element("MediaTypes")?.Elements("MediaType") ?? Enumerable.Empty(), + Documents = + xml.Root.Element("Documents")?.Elements("DocumentSet")?.Select(CompiledPackageContentBase.Create) ?? + Enumerable.Empty(), + Media = xml.Root.Element("MediaItems")?.Elements()?.Select(CompiledPackageContentBase.Create) ?? + Enumerable.Empty() + }; - return installWarnings; - } + def.Warnings = GetInstallWarnings(def); + + return def; + } + + private InstallWarnings GetInstallWarnings(CompiledPackage package) + { + var installWarnings = new InstallWarnings + { + ConflictingMacros = _conflictingPackageData.FindConflictingMacros(package.Macros), + ConflictingTemplates = _conflictingPackageData.FindConflictingTemplates(package.Templates), + ConflictingStylesheets = _conflictingPackageData.FindConflictingStylesheets(package.Stylesheets) + }; + return installWarnings; } } diff --git a/src/Umbraco.Core/Packaging/ConflictingPackageData.cs b/src/Umbraco.Core/Packaging/ConflictingPackageData.cs index 239f1ba66d47..aa437c0d66f9 100644 --- a/src/Umbraco.Core/Packaging/ConflictingPackageData.cs +++ b/src/Umbraco.Core/Packaging/ConflictingPackageData.cs @@ -1,64 +1,60 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; +using System.Xml.Linq; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Packaging +namespace Umbraco.Cms.Core.Packaging; + +public class ConflictingPackageData { - public class ConflictingPackageData - { - private readonly IMacroService _macroService; - private readonly IFileService _fileService; + private readonly IFileService _fileService; + private readonly IMacroService _macroService; - public ConflictingPackageData(IMacroService macroService, IFileService fileService) - { - _fileService = fileService ?? throw new ArgumentNullException(nameof(fileService)); - _macroService = macroService ?? throw new ArgumentNullException(nameof(macroService)); - } + public ConflictingPackageData(IMacroService macroService, IFileService fileService) + { + _fileService = fileService ?? throw new ArgumentNullException(nameof(fileService)); + _macroService = macroService ?? throw new ArgumentNullException(nameof(macroService)); + } - public IEnumerable? FindConflictingStylesheets(IEnumerable? stylesheetNodes) - { - return stylesheetNodes? - .Select(n => + public IEnumerable? FindConflictingStylesheets(IEnumerable? stylesheetNodes) => + stylesheetNodes? + .Select(n => + { + XElement xElement = n.Element("Name") ?? n.Element("name"); + if (xElement == null) { - var xElement = n.Element("Name") ?? n.Element("name"); - if (xElement == null) - throw new FormatException("Missing \"Name\" element"); - - return _fileService.GetStylesheet(xElement.Value) as IFile; - }) - .Where(v => v != null); - } - - public IEnumerable? FindConflictingTemplates(IEnumerable? templateNodes) - { - return templateNodes? - .Select(n => + throw new FormatException("Missing \"Name\" element"); + } + + return _fileService.GetStylesheet(xElement.Value) as IFile; + }) + .Where(v => v != null); + + public IEnumerable? FindConflictingTemplates(IEnumerable? templateNodes) => + templateNodes? + .Select(n => + { + XElement xElement = n.Element("Alias") ?? n.Element("alias"); + if (xElement == null) { - var xElement = n.Element("Alias") ?? n.Element("alias"); - if (xElement == null) - throw new FormatException("missing a \"Alias\" element"); - - return _fileService.GetTemplate(xElement.Value); - }) - .WhereNotNull(); - } - - public IEnumerable? FindConflictingMacros(IEnumerable? macroNodes) - { - return macroNodes? - .Select(n => + throw new FormatException("missing a \"Alias\" element"); + } + + return _fileService.GetTemplate(xElement.Value); + }) + .WhereNotNull(); + + public IEnumerable? FindConflictingMacros(IEnumerable? macroNodes) => + macroNodes? + .Select(n => + { + XElement xElement = n.Element("alias") ?? n.Element("Alias"); + if (xElement == null) { - var xElement = n.Element("alias") ?? n.Element("Alias"); - if (xElement == null) - throw new FormatException("missing a \"alias\" element in alias element"); + throw new FormatException("missing a \"alias\" element in alias element"); + } - return _macroService.GetByAlias(xElement.Value); - }) - .Where(v => v != null); - } - } + return _macroService.GetByAlias(xElement.Value); + }) + .Where(v => v != null); } diff --git a/src/Umbraco.Core/Packaging/ICreatedPackagesRepository.cs b/src/Umbraco.Core/Packaging/ICreatedPackagesRepository.cs index ba99fdd9a7a0..3c873eb90808 100644 --- a/src/Umbraco.Core/Packaging/ICreatedPackagesRepository.cs +++ b/src/Umbraco.Core/Packaging/ICreatedPackagesRepository.cs @@ -1,14 +1,13 @@ -namespace Umbraco.Cms.Core.Packaging +namespace Umbraco.Cms.Core.Packaging; + +/// +/// Manages the storage of created package definitions +/// +public interface ICreatedPackagesRepository : IPackageDefinitionRepository { /// - /// Manages the storage of created package definitions + /// Creates the package file and returns it's physical path /// - public interface ICreatedPackagesRepository : IPackageDefinitionRepository - { - /// - /// Creates the package file and returns it's physical path - /// - /// - string ExportPackage(PackageDefinition definition); - } + /// + string ExportPackage(PackageDefinition definition); } diff --git a/src/Umbraco.Core/Packaging/IPackageDefinitionRepository.cs b/src/Umbraco.Core/Packaging/IPackageDefinitionRepository.cs index fe015006a82b..6f57bee885f6 100644 --- a/src/Umbraco.Core/Packaging/IPackageDefinitionRepository.cs +++ b/src/Umbraco.Core/Packaging/IPackageDefinitionRepository.cs @@ -1,22 +1,19 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Packaging; -namespace Umbraco.Cms.Core.Packaging +/// +/// Defines methods for persisting package definitions to storage +/// +public interface IPackageDefinitionRepository { + IEnumerable GetAll(); + PackageDefinition? GetById(int id); + void Delete(int id); + /// - /// Defines methods for persisting package definitions to storage + /// Persists a package definition to storage /// - public interface IPackageDefinitionRepository - { - IEnumerable GetAll(); - PackageDefinition? GetById(int id); - void Delete(int id); - - /// - /// Persists a package definition to storage - /// - /// - /// true if creating/updating the package was successful, otherwise false - /// - bool SavePackage(PackageDefinition definition); - } + /// + /// true if creating/updating the package was successful, otherwise false + /// + bool SavePackage(PackageDefinition definition); } diff --git a/src/Umbraco.Core/Packaging/IPackageInstallation.cs b/src/Umbraco.Core/Packaging/IPackageInstallation.cs index 9a744a91faf1..6417eb979b62 100644 --- a/src/Umbraco.Core/Packaging/IPackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/IPackageInstallation.cs @@ -1,30 +1,29 @@ -using System.IO; using System.Xml.Linq; using Umbraco.Cms.Core.Models.Packaging; -namespace Umbraco.Cms.Core.Packaging +namespace Umbraco.Cms.Core.Packaging; + +public interface IPackageInstallation { - public interface IPackageInstallation - { - /// - /// Installs a packages data and entities - /// - /// - /// - /// - /// - // TODO: The resulting PackageDefinition is only if we wanted to persist what was saved during package data installation. - // This used to be for the installedPackages.config but we don't have that anymore and don't really want it if we can help it. - // Possibly, we could continue to persist that file so that you could uninstall package data for an installed package in the - // back office (but it won't actually uninstall the package until you do that via nuget). If we want that functionality we'll have - // to restore a bunch of deleted code. - InstallationSummary InstallPackageData(CompiledPackage compiledPackage, int userId, out PackageDefinition packageDefinition); + /// + /// Installs a packages data and entities + /// + /// + /// + /// + /// + // TODO: The resulting PackageDefinition is only if we wanted to persist what was saved during package data installation. + // This used to be for the installedPackages.config but we don't have that anymore and don't really want it if we can help it. + // Possibly, we could continue to persist that file so that you could uninstall package data for an installed package in the + // back office (but it won't actually uninstall the package until you do that via nuget). If we want that functionality we'll have + // to restore a bunch of deleted code. + InstallationSummary InstallPackageData(CompiledPackage compiledPackage, int userId, + out PackageDefinition packageDefinition); - /// - /// Reads the package xml and returns the model - /// - /// - /// - CompiledPackage ReadPackage(XDocument? packageXmlFile); - } + /// + /// Reads the package xml and returns the model + /// + /// + /// + CompiledPackage ReadPackage(XDocument? packageXmlFile); } diff --git a/src/Umbraco.Core/Packaging/InstallationSummary.cs b/src/Umbraco.Core/Packaging/InstallationSummary.cs index d5d7ad343be2..9f154bb8ab40 100644 --- a/src/Umbraco.Core/Packaging/InstallationSummary.cs +++ b/src/Umbraco.Core/Packaging/InstallationSummary.cs @@ -1,88 +1,88 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.Serialization; using System.Text; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Packaging; -namespace Umbraco.Cms.Core.Packaging +namespace Umbraco.Cms.Core.Packaging; + +[Serializable] +[DataContract(IsReference = true)] +public class InstallationSummary { - [Serializable] - [DataContract(IsReference = true)] - public class InstallationSummary - { - public InstallationSummary(string packageName) - => PackageName = packageName; + public InstallationSummary(string packageName) + => PackageName = packageName; - public string PackageName { get; } + public string PackageName { get; } - public InstallWarnings Warnings { get; set; } = new InstallWarnings(); + public InstallWarnings Warnings { get; set; } = new(); - public IEnumerable DataTypesInstalled { get; set; } = Enumerable.Empty(); - public IEnumerable LanguagesInstalled { get; set; } = Enumerable.Empty(); - public IEnumerable DictionaryItemsInstalled { get; set; } = Enumerable.Empty(); - public IEnumerable MacrosInstalled { get; set; } = Enumerable.Empty(); - public IEnumerable MacroPartialViewsInstalled { get; set; } = Enumerable.Empty(); - public IEnumerable TemplatesInstalled { get; set; } = Enumerable.Empty(); - public IEnumerable DocumentTypesInstalled { get; set; } = Enumerable.Empty(); - public IEnumerable MediaTypesInstalled { get; set; } = Enumerable.Empty(); - public IEnumerable StylesheetsInstalled { get; set; } = Enumerable.Empty(); - public IEnumerable ScriptsInstalled { get; set; } = Enumerable.Empty(); - public IEnumerable PartialViewsInstalled { get; set; } = Enumerable.Empty(); - public IEnumerable ContentInstalled { get; set; } = Enumerable.Empty(); - public IEnumerable MediaInstalled { get; set; } = Enumerable.Empty(); - public IEnumerable EntityContainersInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable DataTypesInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable LanguagesInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable DictionaryItemsInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable MacrosInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable MacroPartialViewsInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable TemplatesInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable DocumentTypesInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable MediaTypesInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable StylesheetsInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable ScriptsInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable PartialViewsInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable ContentInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable MediaInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable EntityContainersInstalled { get; set; } = Enumerable.Empty(); - public override string ToString() - { - var sb = new StringBuilder(); - - void WriteConflicts(IEnumerable? source, Func selector, string message, bool appendLine = true) - { - var result = source?.Select(selector).ToList(); - if (result?.Count > 0) - { - sb.Append(message); - sb.Append(string.Join(", ", result)); - - if (appendLine) - { - sb.AppendLine(); - } - } - } + public override string ToString() + { + var sb = new StringBuilder(); - void WriteCount(string message, IEnumerable source, bool appendLine = true) + void WriteConflicts(IEnumerable? source, Func selector, string message, + bool appendLine = true) + { + var result = source?.Select(selector).ToList(); + if (result?.Count > 0) { sb.Append(message); - sb.Append(source?.Count() ?? 0); + sb.Append(string.Join(", ", result)); if (appendLine) { sb.AppendLine(); } } + } - WriteConflicts(Warnings?.ConflictingMacros, x => x?.Alias, "Conflicting macros found, they will be overwritten: "); - WriteConflicts(Warnings?.ConflictingTemplates, x => x.Alias, "Conflicting templates found, they will be overwritten: "); - WriteConflicts(Warnings?.ConflictingStylesheets, x => x?.Alias, "Conflicting stylesheets found, they will be overwritten: "); - WriteCount("Data types installed: ", DataTypesInstalled); - WriteCount("Languages installed: ", LanguagesInstalled); - WriteCount("Dictionary items installed: ", DictionaryItemsInstalled); - WriteCount("Macros installed: ", MacrosInstalled); - WriteCount("Macro partial views installed: ", MacroPartialViewsInstalled); - WriteCount("Templates installed: ", TemplatesInstalled); - WriteCount("Document types installed: ", DocumentTypesInstalled); - WriteCount("Media types installed: ", MediaTypesInstalled); - WriteCount("Stylesheets installed: ", StylesheetsInstalled); - WriteCount("Scripts installed: ", ScriptsInstalled); - WriteCount("Partial views installed: ", PartialViewsInstalled); - WriteCount("Entity containers installed: ", EntityContainersInstalled); - WriteCount("Content items installed: ", ContentInstalled); - WriteCount("Media items installed: ", MediaInstalled, false); + void WriteCount(string message, IEnumerable source, bool appendLine = true) + { + sb.Append(message); + sb.Append(source?.Count() ?? 0); - return sb.ToString(); + if (appendLine) + { + sb.AppendLine(); + } } + + WriteConflicts(Warnings?.ConflictingMacros, x => x?.Alias, + "Conflicting macros found, they will be overwritten: "); + WriteConflicts(Warnings?.ConflictingTemplates, x => x.Alias, + "Conflicting templates found, they will be overwritten: "); + WriteConflicts(Warnings?.ConflictingStylesheets, x => x?.Alias, + "Conflicting stylesheets found, they will be overwritten: "); + WriteCount("Data types installed: ", DataTypesInstalled); + WriteCount("Languages installed: ", LanguagesInstalled); + WriteCount("Dictionary items installed: ", DictionaryItemsInstalled); + WriteCount("Macros installed: ", MacrosInstalled); + WriteCount("Macro partial views installed: ", MacroPartialViewsInstalled); + WriteCount("Templates installed: ", TemplatesInstalled); + WriteCount("Document types installed: ", DocumentTypesInstalled); + WriteCount("Media types installed: ", MediaTypesInstalled); + WriteCount("Stylesheets installed: ", StylesheetsInstalled); + WriteCount("Scripts installed: ", ScriptsInstalled); + WriteCount("Partial views installed: ", PartialViewsInstalled); + WriteCount("Entity containers installed: ", EntityContainersInstalled); + WriteCount("Content items installed: ", ContentInstalled); + WriteCount("Media items installed: ", MediaInstalled, false); + + return sb.ToString(); } } diff --git a/src/Umbraco.Core/Packaging/InstalledPackage.cs b/src/Umbraco.Core/Packaging/InstalledPackage.cs index ded901512b9e..00b8d2feca75 100644 --- a/src/Umbraco.Core/Packaging/InstalledPackage.cs +++ b/src/Umbraco.Core/Packaging/InstalledPackage.cs @@ -1,36 +1,32 @@ -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Linq; using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Packaging -{ - [DataContract(Name = "installedPackage")] - public class InstalledPackage - { - [DataMember(Name = "name", IsRequired = true)] - [Required] - public string? PackageName { get; set; } +namespace Umbraco.Cms.Core.Packaging; - // TODO: Version? Icon? Other metadata? This would need to come from querying the package on Our +[DataContract(Name = "installedPackage")] +public class InstalledPackage +{ + [DataMember(Name = "name", IsRequired = true)] + [Required] + public string? PackageName { get; set; } - [DataMember(Name = "packageView")] - public string? PackageView { get; set; } + // TODO: Version? Icon? Other metadata? This would need to come from querying the package on Our - [DataMember(Name = "plans")] - public IEnumerable PackageMigrationPlans { get; set; } = Enumerable.Empty(); + [DataMember(Name = "packageView")] public string? PackageView { get; set; } - /// - /// It the package contains any migrations at all - /// - [DataMember(Name = "hasMigrations")] - public bool HasMigrations => PackageMigrationPlans.Any(); + [DataMember(Name = "plans")] + public IEnumerable PackageMigrationPlans { get; set; } = + Enumerable.Empty(); - /// - /// If the package has any pending migrations to run - /// - [DataMember(Name = "hasPendingMigrations")] - public bool HasPendingMigrations => PackageMigrationPlans.Any(x => x.HasPendingMigrations); - } + /// + /// It the package contains any migrations at all + /// + [DataMember(Name = "hasMigrations")] + public bool HasMigrations => PackageMigrationPlans.Any(); + /// + /// If the package has any pending migrations to run + /// + [DataMember(Name = "hasPendingMigrations")] + public bool HasPendingMigrations => PackageMigrationPlans.Any(x => x.HasPendingMigrations); } diff --git a/src/Umbraco.Core/Packaging/InstalledPackageMigrationPlans.cs b/src/Umbraco.Core/Packaging/InstalledPackageMigrationPlans.cs index 5aaca2e9f2cb..a59b075f493c 100644 --- a/src/Umbraco.Core/Packaging/InstalledPackageMigrationPlans.cs +++ b/src/Umbraco.Core/Packaging/InstalledPackageMigrationPlans.cs @@ -1,27 +1,25 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Packaging -{ - [DataContract(Name = "installedPackageMigrations")] - public class InstalledPackageMigrationPlans - { - [DataMember(Name = "hasPendingMigrations")] - public bool HasPendingMigrations => FinalMigrationId != CurrentMigrationId; +namespace Umbraco.Cms.Core.Packaging; - /// - /// If the package has migrations, this will be it's final migration Id - /// - /// - /// This can be used to determine if the package advertises any migrations - /// - [DataMember(Name = "finalMigrationId")] - public string? FinalMigrationId { get; set; } +[DataContract(Name = "installedPackageMigrations")] +public class InstalledPackageMigrationPlans +{ + [DataMember(Name = "hasPendingMigrations")] + public bool HasPendingMigrations => FinalMigrationId != CurrentMigrationId; - /// - /// If the package has migrations, this will be it's current migration Id - /// - [DataMember(Name = "currentMigrationId")] - public string? CurrentMigrationId { get; set; } - } + /// + /// If the package has migrations, this will be it's final migration Id + /// + /// + /// This can be used to determine if the package advertises any migrations + /// + [DataMember(Name = "finalMigrationId")] + public string? FinalMigrationId { get; set; } + /// + /// If the package has migrations, this will be it's current migration Id + /// + [DataMember(Name = "currentMigrationId")] + public string? CurrentMigrationId { get; set; } } diff --git a/src/Umbraco.Core/Packaging/PackageDefinition.cs b/src/Umbraco.Core/Packaging/PackageDefinition.cs index 66a0a9e102aa..cbf682ad106b 100644 --- a/src/Umbraco.Core/Packaging/PackageDefinition.cs +++ b/src/Umbraco.Core/Packaging/PackageDefinition.cs @@ -1,79 +1,58 @@ -using System; -using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; -using Umbraco.Cms.Core.Models.Packaging; -namespace Umbraco.Cms.Core.Packaging -{ +namespace Umbraco.Cms.Core.Packaging; - /// - /// A created package in the back office. - /// - /// - /// This data structure is persisted to createdPackages.config when creating packages in the back office. - /// - [DataContract(Name = "packageInstance")] - public class PackageDefinition - { - [DataMember(Name = "id")] - public int Id { get; set; } +/// +/// A created package in the back office. +/// +/// +/// This data structure is persisted to createdPackages.config when creating packages in the back office. +/// +[DataContract(Name = "packageInstance")] +public class PackageDefinition +{ + [DataMember(Name = "id")] public int Id { get; set; } - [DataMember(Name = "packageGuid")] - public Guid PackageId { get; set; } + [DataMember(Name = "packageGuid")] public Guid PackageId { get; set; } - [DataMember(Name = "name")] - [Required] - public string Name { get; set; } = string.Empty; + [DataMember(Name = "name")] [Required] public string Name { get; set; } = string.Empty; - /// - /// The full path to the package's XML file. - /// - [ReadOnly(true)] - [DataMember(Name = "packagePath")] - public string PackagePath { get; set; } = string.Empty; + /// + /// The full path to the package's XML file. + /// + [ReadOnly(true)] + [DataMember(Name = "packagePath")] + public string PackagePath { get; set; } = string.Empty; - [DataMember(Name = "contentLoadChildNodes")] - public bool ContentLoadChildNodes { get; set; } + [DataMember(Name = "contentLoadChildNodes")] + public bool ContentLoadChildNodes { get; set; } - [DataMember(Name = "contentNodeId")] - public string? ContentNodeId { get; set; } + [DataMember(Name = "contentNodeId")] public string? ContentNodeId { get; set; } - [DataMember(Name = "macros")] - public IList Macros { get; set; } = new List(); + [DataMember(Name = "macros")] public IList Macros { get; set; } = new List(); - [DataMember(Name = "languages")] - public IList Languages { get; set; } = new List(); + [DataMember(Name = "languages")] public IList Languages { get; set; } = new List(); - [DataMember(Name = "dictionaryItems")] - public IList DictionaryItems { get; set; } = new List(); + [DataMember(Name = "dictionaryItems")] public IList DictionaryItems { get; set; } = new List(); - [DataMember(Name = "templates")] - public IList Templates { get; set; } = new List(); + [DataMember(Name = "templates")] public IList Templates { get; set; } = new List(); - [DataMember(Name = "partialViews")] - public IList PartialViews { get; set; } = new List(); + [DataMember(Name = "partialViews")] public IList PartialViews { get; set; } = new List(); - [DataMember(Name = "documentTypes")] - public IList DocumentTypes { get; set; } = new List(); + [DataMember(Name = "documentTypes")] public IList DocumentTypes { get; set; } = new List(); - [DataMember(Name = "mediaTypes")] - public IList MediaTypes { get; set; } = new List(); + [DataMember(Name = "mediaTypes")] public IList MediaTypes { get; set; } = new List(); - [DataMember(Name = "stylesheets")] - public IList Stylesheets { get; set; } = new List(); + [DataMember(Name = "stylesheets")] public IList Stylesheets { get; set; } = new List(); - [DataMember(Name = "scripts")] - public IList Scripts { get; set; } = new List(); + [DataMember(Name = "scripts")] public IList Scripts { get; set; } = new List(); - [DataMember(Name = "dataTypes")] - public IList DataTypes { get; set; } = new List(); + [DataMember(Name = "dataTypes")] public IList DataTypes { get; set; } = new List(); - [DataMember(Name = "mediaUdis")] - public IList MediaUdis { get; set; } = new List(); + [DataMember(Name = "mediaUdis")] public IList MediaUdis { get; set; } = new List(); - [DataMember(Name = "mediaLoadChildNodes")] - public bool MediaLoadChildNodes { get; set; } - } + [DataMember(Name = "mediaLoadChildNodes")] + public bool MediaLoadChildNodes { get; set; } } diff --git a/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs b/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs index df5375ad9231..9bb2d0d69c4d 100644 --- a/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs +++ b/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs @@ -1,84 +1,104 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Xml.Linq; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Packaging +namespace Umbraco.Cms.Core.Packaging; + +/// +/// Converts a to and from XML +/// +public class PackageDefinitionXmlParser { - /// - /// Converts a to and from XML - /// - public class PackageDefinitionXmlParser - { - private static readonly IList s_emptyStringList = new List(); - private static readonly IList s_emptyGuidUdiList = new List(); + private static readonly IList s_emptyStringList = new List(); + private static readonly IList s_emptyGuidUdiList = new List(); - public PackageDefinition? ToPackageDefinition(XElement xml) + public PackageDefinition? ToPackageDefinition(XElement xml) + { + if (xml == null) { - if (xml == null) - { - return null; - } - - var retVal = new PackageDefinition - { - Id = xml.AttributeValue("id"), - Name = xml.AttributeValue("name") ?? string.Empty, - PackagePath = xml.AttributeValue("packagePath") ?? string.Empty, - PackageId = xml.AttributeValue("packageGuid"), - ContentNodeId = xml.Element("content")?.AttributeValue("nodeId") ?? string.Empty, - ContentLoadChildNodes = xml.Element("content")?.AttributeValue("loadChildNodes") ?? false, - MediaUdis = xml.Element("media")?.Elements("nodeUdi").Select(x => (GuidUdi)UdiParser.Parse(x.Value)).ToList() ?? s_emptyGuidUdiList, - MediaLoadChildNodes = xml.Element("media")?.AttributeValue("loadChildNodes") ?? false, - Macros = xml.Element("macros")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? s_emptyStringList, - Templates = xml.Element("templates")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? s_emptyStringList, - Stylesheets = xml.Element("stylesheets")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? s_emptyStringList, - Scripts = xml.Element("scripts")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? s_emptyStringList, - PartialViews = xml.Element("partialViews")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? s_emptyStringList, - DocumentTypes = xml.Element("documentTypes")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? s_emptyStringList, - MediaTypes = xml.Element("mediaTypes")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? s_emptyStringList, - Languages = xml.Element("languages")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? s_emptyStringList, - DictionaryItems = xml.Element("dictionaryitems")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? s_emptyStringList, - DataTypes = xml.Element("datatypes")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? s_emptyStringList, - }; - - return retVal; + return null; } - public XElement ToXml(PackageDefinition def) + var retVal = new PackageDefinition { - var packageXml = new XElement("package", - new XAttribute("id", def.Id), - new XAttribute("name", def.Name ?? string.Empty), - new XAttribute("packagePath", def.PackagePath ?? string.Empty), - new XAttribute("packageGuid", def.PackageId), - new XElement("datatypes", string.Join(",", def.DataTypes ?? Array.Empty())), - - new XElement("content", - new XAttribute("nodeId", def.ContentNodeId ?? string.Empty), - new XAttribute("loadChildNodes", def.ContentLoadChildNodes)), - - new XElement("templates", string.Join(",", def.Templates ?? Array.Empty())), - new XElement("stylesheets", string.Join(",", def.Stylesheets ?? Array.Empty())), - new XElement("scripts", string.Join(",", def.Scripts ?? Array.Empty())), - new XElement("partialViews", string.Join(",", def.PartialViews ?? Array.Empty())), - new XElement("documentTypes", string.Join(",", def.DocumentTypes ?? Array.Empty())), - new XElement("mediaTypes", string.Join(",", def.MediaTypes ?? Array.Empty())), - new XElement("macros", string.Join(",", def.Macros ?? Array.Empty())), - new XElement("languages", string.Join(",", def.Languages ?? Array.Empty())), - new XElement("dictionaryitems", string.Join(",", def.DictionaryItems ?? Array.Empty())), - - new XElement( - "media", - def.MediaUdis.Select(x => (object)new XElement("nodeUdi", x)) - .Union(new[] { new XAttribute("loadChildNodes", def.MediaLoadChildNodes) })) - ); - return packageXml; - } + Id = xml.AttributeValue("id"), + Name = xml.AttributeValue("name") ?? string.Empty, + PackagePath = xml.AttributeValue("packagePath") ?? string.Empty, + PackageId = xml.AttributeValue("packageGuid"), + ContentNodeId = xml.Element("content")?.AttributeValue("nodeId") ?? string.Empty, + ContentLoadChildNodes = xml.Element("content")?.AttributeValue("loadChildNodes") ?? false, + MediaUdis = + xml.Element("media")?.Elements("nodeUdi").Select(x => (GuidUdi)UdiParser.Parse(x.Value)).ToList() ?? + s_emptyGuidUdiList, + MediaLoadChildNodes = xml.Element("media")?.AttributeValue("loadChildNodes") ?? false, + Macros = + xml.Element("macros")?.Value + .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? + s_emptyStringList, + Templates = + xml.Element("templates")?.Value + .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? + s_emptyStringList, + Stylesheets = + xml.Element("stylesheets")?.Value + .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? + s_emptyStringList, + Scripts = + xml.Element("scripts")?.Value + .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? + s_emptyStringList, + PartialViews = + xml.Element("partialViews")?.Value + .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? + s_emptyStringList, + DocumentTypes = + xml.Element("documentTypes")?.Value + .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? + s_emptyStringList, + MediaTypes = + xml.Element("mediaTypes")?.Value.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries) + .ToList() ?? s_emptyStringList, + Languages = + xml.Element("languages")?.Value + .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? + s_emptyStringList, + DictionaryItems = + xml.Element("dictionaryitems")?.Value + .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? + s_emptyStringList, + DataTypes = xml.Element("datatypes")?.Value + .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? + s_emptyStringList + }; + return retVal; + } + public XElement ToXml(PackageDefinition def) + { + var packageXml = new XElement("package", + new XAttribute("id", def.Id), + new XAttribute("name", def.Name ?? string.Empty), + new XAttribute("packagePath", def.PackagePath ?? string.Empty), + new XAttribute("packageGuid", def.PackageId), + new XElement("datatypes", string.Join(",", def.DataTypes ?? Array.Empty())), + new XElement("content", + new XAttribute("nodeId", def.ContentNodeId ?? string.Empty), + new XAttribute("loadChildNodes", def.ContentLoadChildNodes)), + new XElement("templates", string.Join(",", def.Templates ?? Array.Empty())), + new XElement("stylesheets", string.Join(",", def.Stylesheets ?? Array.Empty())), + new XElement("scripts", string.Join(",", def.Scripts ?? Array.Empty())), + new XElement("partialViews", string.Join(",", def.PartialViews ?? Array.Empty())), + new XElement("documentTypes", string.Join(",", def.DocumentTypes ?? Array.Empty())), + new XElement("mediaTypes", string.Join(",", def.MediaTypes ?? Array.Empty())), + new XElement("macros", string.Join(",", def.Macros ?? Array.Empty())), + new XElement("languages", string.Join(",", def.Languages ?? Array.Empty())), + new XElement("dictionaryitems", string.Join(",", def.DictionaryItems ?? Array.Empty())), + new XElement( + "media", + def.MediaUdis.Select(x => (object)new XElement("nodeUdi", x)) + .Union(new[] {new XAttribute("loadChildNodes", def.MediaLoadChildNodes)})) + ); + return packageXml; } } diff --git a/src/Umbraco.Core/Packaging/PackageMigrationResource.cs b/src/Umbraco.Core/Packaging/PackageMigrationResource.cs index b972a2cf0881..6f22a6d32e30 100644 --- a/src/Umbraco.Core/Packaging/PackageMigrationResource.cs +++ b/src/Umbraco.Core/Packaging/PackageMigrationResource.cs @@ -1,127 +1,120 @@ -using System; -using System.IO; using System.IO.Compression; using System.Reflection; -using System.Security.Cryptography; -using System.Text; using System.Xml; using System.Xml.Linq; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Packaging +namespace Umbraco.Cms.Core.Packaging; + +public static class PackageMigrationResource { - public static class PackageMigrationResource + private static Stream? GetEmbeddedPackageZipStream(Type planType) { - private static Stream? GetEmbeddedPackageZipStream(Type planType) - { - // lookup the embedded resource by convention - Assembly currentAssembly = planType.Assembly; - var fileName = $"{planType.Namespace}.package.zip"; - Stream? stream = currentAssembly.GetManifestResourceStream(fileName); + // lookup the embedded resource by convention + Assembly currentAssembly = planType.Assembly; + var fileName = $"{planType.Namespace}.package.zip"; + Stream? stream = currentAssembly.GetManifestResourceStream(fileName); - return stream; - } + return stream; + } - public static XDocument? GetEmbeddedPackageDataManifest(Type planType, out ZipArchive? zipArchive) + public static XDocument? GetEmbeddedPackageDataManifest(Type planType, out ZipArchive? zipArchive) + { + XDocument? packageXml; + Stream zipStream = GetEmbeddedPackageZipStream(planType); + if (zipStream is not null) { - XDocument? packageXml; - var zipStream = GetEmbeddedPackageZipStream(planType); - if (zipStream is not null) - { - zipArchive = GetPackageDataManifest(zipStream, out packageXml); - return packageXml; - } - - zipArchive = null; - packageXml = GetEmbeddedPackageXmlDoc(planType); + zipArchive = GetPackageDataManifest(zipStream, out packageXml); return packageXml; } - public static XDocument? GetEmbeddedPackageDataManifest(Type planType) + zipArchive = null; + packageXml = GetEmbeddedPackageXmlDoc(planType); + return packageXml; + } + + public static XDocument? GetEmbeddedPackageDataManifest(Type planType) => + GetEmbeddedPackageDataManifest(planType, out _); + + private static XDocument? GetEmbeddedPackageXmlDoc(Type planType) + { + // lookup the embedded resource by convention + Assembly currentAssembly = planType.Assembly; + var fileName = $"{planType.Namespace}.package.xml"; + Stream? stream = currentAssembly.GetManifestResourceStream(fileName); + if (stream == null) { - return GetEmbeddedPackageDataManifest(planType, out _); + return null; } - private static XDocument? GetEmbeddedPackageXmlDoc(Type planType) + XDocument xml; + using (stream) { - // lookup the embedded resource by convention - Assembly currentAssembly = planType.Assembly; - var fileName = $"{planType.Namespace}.package.xml"; - Stream? stream = currentAssembly.GetManifestResourceStream(fileName); - if (stream == null) - { - return null; - } - XDocument xml; - using (stream) - { - xml = XDocument.Load(stream); - } - return xml; + xml = XDocument.Load(stream); } - public static string GetEmbeddedPackageDataManifestHash(Type planType) + return xml; + } + + public static string GetEmbeddedPackageDataManifestHash(Type planType) + { + // SEE: HashFromStreams in the benchmarks project for how fast this is. It will run + // on every startup for every embedded package.zip. The bigger the zip, the more time it takes. + // But it is still very fast ~303ms for a 100MB file. This will only be an issue if there are + // several very large package.zips. + + using Stream? stream = GetEmbeddedPackageZipStream(planType); + + if (stream is not null) { - // SEE: HashFromStreams in the benchmarks project for how fast this is. It will run - // on every startup for every embedded package.zip. The bigger the zip, the more time it takes. - // But it is still very fast ~303ms for a 100MB file. This will only be an issue if there are - // several very large package.zips. + return stream.GetStreamHash(); + } - using Stream? stream = GetEmbeddedPackageZipStream(planType); + XDocument xml = GetEmbeddedPackageXmlDoc(planType); - if (stream is not null) - { - return stream.GetStreamHash(); - } + if (xml is not null) + { + return xml.ToString(); + } - var xml = GetEmbeddedPackageXmlDoc(planType); + throw new IOException("Missing embedded files for planType: " + planType); + } - if (xml is not null) - { - return xml.ToString(); - } + public static bool TryGetEmbeddedPackageDataManifest(Type planType, out XDocument? packageXml, + out ZipArchive? zipArchive) + { + Stream zipStream = GetEmbeddedPackageZipStream(planType); + if (zipStream is not null) + { + zipArchive = GetPackageDataManifest(zipStream, out packageXml); + return true; + } + + zipArchive = null; + packageXml = GetEmbeddedPackageXmlDoc(planType); + return packageXml is not null; + } - throw new IOException("Missing embedded files for planType: " + planType); + public static ZipArchive GetPackageDataManifest(Stream packageZipStream, out XDocument packageXml) + { + if (packageZipStream == null) + { + throw new ArgumentNullException(nameof(packageZipStream)); } - public static bool TryGetEmbeddedPackageDataManifest(Type planType, out XDocument? packageXml, out ZipArchive? zipArchive) + var zip = new ZipArchive(packageZipStream, ZipArchiveMode.Read); + ZipArchiveEntry? packageXmlEntry = zip.GetEntry("package.xml"); + if (packageXmlEntry == null) { - var zipStream = GetEmbeddedPackageZipStream(planType); - if (zipStream is not null) - { - zipArchive = GetPackageDataManifest(zipStream, out packageXml); - return true; - } - - zipArchive = null; - packageXml = GetEmbeddedPackageXmlDoc(planType); - return packageXml is not null; + throw new InvalidOperationException("Zip package does not contain the required package.xml file"); } - public static ZipArchive GetPackageDataManifest(Stream packageZipStream, out XDocument packageXml) + using (Stream packageXmlStream = packageXmlEntry.Open()) + using (var xmlReader = XmlReader.Create(packageXmlStream, new XmlReaderSettings {IgnoreWhitespace = true})) { - if (packageZipStream == null) - { - throw new ArgumentNullException(nameof(packageZipStream)); - } - - var zip = new ZipArchive(packageZipStream, ZipArchiveMode.Read); - ZipArchiveEntry? packageXmlEntry = zip.GetEntry("package.xml"); - if (packageXmlEntry == null) - { - throw new InvalidOperationException("Zip package does not contain the required package.xml file"); - } - - using (Stream packageXmlStream = packageXmlEntry.Open()) - using (var xmlReader = XmlReader.Create(packageXmlStream, new XmlReaderSettings - { - IgnoreWhitespace = true - })) - { - packageXml = XDocument.Load(xmlReader); - } - - return zip; + packageXml = XDocument.Load(xmlReader); } + + return zip; } } diff --git a/src/Umbraco.Core/Packaging/PackagesRepository.cs b/src/Umbraco.Core/Packaging/PackagesRepository.cs index 174faa37fd8b..0ced74cc3d86 100644 --- a/src/Umbraco.Core/Packaging/PackagesRepository.cs +++ b/src/Umbraco.Core/Packaging/PackagesRepository.cs @@ -1,10 +1,6 @@ -using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Globalization; -using System.IO; using System.IO.Compression; -using System.Linq; using System.Xml.Linq; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; @@ -15,749 +11,838 @@ using Umbraco.Extensions; using File = System.IO.File; -namespace Umbraco.Cms.Core.Packaging +namespace Umbraco.Cms.Core.Packaging; + +/// +/// Manages the storage of installed/created package definitions +/// +[Obsolete( + "Packages have now been moved to the database instead of local files, please use CreatedPackageSchemaRepository instead")] +public class PackagesRepository : ICreatedPackagesRepository { + private readonly IContentService _contentService; + private readonly IContentTypeService _contentTypeService; + private readonly string _createdPackagesFolderPath; + private readonly IDataTypeService _dataTypeService; + private readonly IFileService _fileService; + private readonly FileSystems _fileSystems; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly ILocalizationService _languageService; + private readonly IMacroService _macroService; + private readonly MediaFileManager _mediaFileManager; + private readonly IMediaService _mediaService; + private readonly IMediaTypeService _mediaTypeService; + private readonly string _packageRepositoryFileName; + private readonly string _packagesFolderPath; + private readonly PackageDefinitionXmlParser _parser; + private readonly IEntityXmlSerializer _serializer; + private readonly string _tempFolderPath; + /// - /// Manages the storage of installed/created package definitions + /// Constructor /// - [Obsolete("Packages have now been moved to the database instead of local files, please use CreatedPackageSchemaRepository instead")] - public class PackagesRepository : ICreatedPackagesRepository + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// The file name for storing the package definitions (i.e. "createdPackages.config") + /// + /// + /// + /// + public PackagesRepository( + IContentService contentService, + IContentTypeService contentTypeService, + IDataTypeService dataTypeService, + IFileService fileService, + IMacroService macroService, + ILocalizationService languageService, + IHostingEnvironment hostingEnvironment, + IEntityXmlSerializer serializer, + IOptions globalSettings, + IMediaService mediaService, + IMediaTypeService mediaTypeService, + MediaFileManager mediaFileManager, + FileSystems fileSystems, + string packageRepositoryFileName, + string? tempFolderPath = null, + string? packagesFolderPath = null, + string? mediaFolderPath = null) { - private readonly IContentService _contentService; - private readonly IContentTypeService _contentTypeService; - private readonly IDataTypeService _dataTypeService; - private readonly IFileService _fileService; - private readonly IMacroService _macroService; - private readonly ILocalizationService _languageService; - private readonly IEntityXmlSerializer _serializer; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly string _packageRepositoryFileName; - private readonly string _createdPackagesFolderPath; - private readonly string _packagesFolderPath; - private readonly string _tempFolderPath; - private readonly PackageDefinitionXmlParser _parser; - private readonly IMediaService _mediaService; - private readonly IMediaTypeService _mediaTypeService; - private readonly MediaFileManager _mediaFileManager; - private readonly FileSystems _fileSystems; - - /// - /// Constructor - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// The file name for storing the package definitions (i.e. "createdPackages.config") - /// - /// - /// - /// - public PackagesRepository( - IContentService contentService, - IContentTypeService contentTypeService, - IDataTypeService dataTypeService, - IFileService fileService, - IMacroService macroService, - ILocalizationService languageService, - IHostingEnvironment hostingEnvironment, - IEntityXmlSerializer serializer, - IOptions globalSettings, - IMediaService mediaService, - IMediaTypeService mediaTypeService, - MediaFileManager mediaFileManager, - FileSystems fileSystems, - string packageRepositoryFileName, - string? tempFolderPath = null, - string? packagesFolderPath = null, - string? mediaFolderPath = null) - { - if (string.IsNullOrWhiteSpace(packageRepositoryFileName)) - throw new ArgumentException("Value cannot be null or whitespace.", nameof(packageRepositoryFileName)); - _contentService = contentService; - _contentTypeService = contentTypeService; - _dataTypeService = dataTypeService; - _fileService = fileService; - _macroService = macroService; - _languageService = languageService; - _serializer = serializer; - _hostingEnvironment = hostingEnvironment; - _packageRepositoryFileName = packageRepositoryFileName; - - _tempFolderPath = tempFolderPath ?? Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "PackageFiles"; - _packagesFolderPath = packagesFolderPath ?? Constants.SystemDirectories.Packages; - _createdPackagesFolderPath = mediaFolderPath ?? Constants.SystemDirectories.CreatedPackages; - - _parser = new PackageDefinitionXmlParser(); - _mediaService = mediaService; - _mediaTypeService = mediaTypeService; - _mediaFileManager = mediaFileManager; - _fileSystems = fileSystems; - } - - private string CreatedPackagesFile => _packagesFolderPath.EnsureEndsWith('/') + _packageRepositoryFileName; - - public IEnumerable GetAll() - { - var packagesXml = EnsureStorage(out _); - if (packagesXml?.Root == null) - yield break; - - foreach (var packageXml in packagesXml.Root.Elements("package")) - yield return _parser.ToPackageDefinition(packageXml); - } - - public PackageDefinition? GetById(int id) - { - var packagesXml = EnsureStorage(out var packageFile); - var packageXml = packagesXml?.Root?.Elements("package").FirstOrDefault(x => x.AttributeValue("id") == id); - return packageXml == null ? null : _parser.ToPackageDefinition(packageXml); - } - - public void Delete(int id) - { - var packagesXml = EnsureStorage(out var packagesFile); - var packageXml = packagesXml?.Root?.Elements("package").FirstOrDefault(x => x.AttributeValue("id") == id); - if (packageXml == null) - return; + if (string.IsNullOrWhiteSpace(packageRepositoryFileName)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(packageRepositoryFileName)); + } + + _contentService = contentService; + _contentTypeService = contentTypeService; + _dataTypeService = dataTypeService; + _fileService = fileService; + _macroService = macroService; + _languageService = languageService; + _serializer = serializer; + _hostingEnvironment = hostingEnvironment; + _packageRepositoryFileName = packageRepositoryFileName; + + _tempFolderPath = tempFolderPath ?? Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "PackageFiles"; + _packagesFolderPath = packagesFolderPath ?? Constants.SystemDirectories.Packages; + _createdPackagesFolderPath = mediaFolderPath ?? Constants.SystemDirectories.CreatedPackages; + + _parser = new PackageDefinitionXmlParser(); + _mediaService = mediaService; + _mediaTypeService = mediaTypeService; + _mediaFileManager = mediaFileManager; + _fileSystems = fileSystems; + } - packageXml.Remove(); + private string CreatedPackagesFile => _packagesFolderPath.EnsureEndsWith('/') + _packageRepositoryFileName; - packagesXml?.Save(packagesFile); + public IEnumerable GetAll() + { + XDocument packagesXml = EnsureStorage(out _); + if (packagesXml?.Root == null) + { + yield break; } - public bool SavePackage(PackageDefinition definition) + foreach (XElement packageXml in packagesXml.Root.Elements("package")) { - if (definition == null) - throw new ArgumentNullException(nameof(definition)); + yield return _parser.ToPackageDefinition(packageXml); + } + } - var packagesXml = EnsureStorage(out var packagesFile); + public PackageDefinition? GetById(int id) + { + XDocument packagesXml = EnsureStorage(out var packageFile); + XElement packageXml = packagesXml?.Root?.Elements("package") + .FirstOrDefault(x => x.AttributeValue("id") == id); + return packageXml == null ? null : _parser.ToPackageDefinition(packageXml); + } - if (packagesXml?.Root == null) - return false; + public void Delete(int id) + { + XDocument packagesXml = EnsureStorage(out var packagesFile); + XElement packageXml = packagesXml?.Root?.Elements("package") + .FirstOrDefault(x => x.AttributeValue("id") == id); + if (packageXml == null) + { + return; + } + + packageXml.Remove(); + + packagesXml?.Save(packagesFile); + } + + public bool SavePackage(PackageDefinition definition) + { + if (definition == null) + { + throw new ArgumentNullException(nameof(definition)); + } - //ensure it's valid - ValidatePackage(definition); + XDocument packagesXml = EnsureStorage(out var packagesFile); - if (definition.Id == default) + if (packagesXml?.Root == null) + { + return false; + } + + //ensure it's valid + ValidatePackage(definition); + + if (definition.Id == default) + { + //need to gen an id and persist + // Find max id + var maxId = packagesXml.Root.Elements("package").Max(x => x.AttributeValue("id")) ?? 0; + var newId = maxId + 1; + definition.Id = newId; + definition.PackageId = definition.PackageId == default ? Guid.NewGuid() : definition.PackageId; + XElement packageXml = _parser.ToXml(definition); + packagesXml.Root.Add(packageXml); + } + else + { + //existing + XElement packageXml = packagesXml.Root.Elements("package") + .FirstOrDefault(x => x.AttributeValue("id") == definition.Id); + if (packageXml == null) { - //need to gen an id and persist - // Find max id - var maxId = packagesXml.Root.Elements("package").Max(x => x.AttributeValue("id")) ?? 0; - var newId = maxId + 1; - definition.Id = newId; - definition.PackageId = definition.PackageId == default ? Guid.NewGuid() : definition.PackageId; - var packageXml = _parser.ToXml(definition); - packagesXml.Root.Add(packageXml); + return false; } - else - { - //existing - var packageXml = packagesXml.Root.Elements("package").FirstOrDefault(x => x.AttributeValue("id") == definition.Id); - if (packageXml == null) - return false; - var updatedXml = _parser.ToXml(definition); - packageXml.ReplaceWith(updatedXml); - } + XElement updatedXml = _parser.ToXml(definition); + packageXml.ReplaceWith(updatedXml); + } - packagesXml.Save(packagesFile); + packagesXml.Save(packagesFile); + + return true; + } - return true; + public string ExportPackage(PackageDefinition definition) + { + if (definition.Id == default) + { + throw new ArgumentException( + "The package definition does not have an ID, it must be saved before being exported"); } - public string ExportPackage(PackageDefinition definition) + if (definition.PackageId == default) { - if (definition.Id == default) - throw new ArgumentException("The package definition does not have an ID, it must be saved before being exported"); - if (definition.PackageId == default) - throw new ArgumentException("the package definition does not have a GUID, it must be saved before being exported"); + throw new ArgumentException( + "the package definition does not have a GUID, it must be saved before being exported"); + } - //ensure it's valid - ValidatePackage(definition); + //ensure it's valid + ValidatePackage(definition); - //Create a folder for building this package - var temporaryPath = _hostingEnvironment.MapPathContentRoot(_tempFolderPath.EnsureEndsWith('/') + Guid.NewGuid()); - if (Directory.Exists(temporaryPath) == false) - { - Directory.CreateDirectory(temporaryPath); - } + //Create a folder for building this package + var temporaryPath = + _hostingEnvironment.MapPathContentRoot(_tempFolderPath.EnsureEndsWith('/') + Guid.NewGuid()); + if (Directory.Exists(temporaryPath) == false) + { + Directory.CreateDirectory(temporaryPath); + } - try + try + { + //Init package file + XDocument compiledPackageXml = CreateCompiledPackageXml(out XElement root); + + //Info section + root.Add(GetPackageInfoXml(definition)); + + PackageDocumentsAndTags(definition, root); + PackageDocumentTypes(definition, root); + PackageMediaTypes(definition, root); + PackageTemplates(definition, root); + PackageStylesheets(definition, root); + PackageStaticFiles(definition.Scripts, root, "Scripts", "Script", _fileSystems.ScriptsFileSystem); + PackageStaticFiles(definition.PartialViews, root, "PartialViews", "View", + _fileSystems.PartialViewsFileSystem); + PackageMacros(definition, root); + PackageDictionaryItems(definition, root); + PackageLanguages(definition, root); + PackageDataTypes(definition, root); + Dictionary mediaFiles = PackageMedia(definition, root); + + string fileName; + string tempPackagePath; + if (mediaFiles.Count > 0) { - //Init package file - XDocument compiledPackageXml = CreateCompiledPackageXml(out XElement root); - - //Info section - root.Add(GetPackageInfoXml(definition)); - - PackageDocumentsAndTags(definition, root); - PackageDocumentTypes(definition, root); - PackageMediaTypes(definition, root); - PackageTemplates(definition, root); - PackageStylesheets(definition, root); - PackageStaticFiles(definition.Scripts, root, "Scripts", "Script", _fileSystems.ScriptsFileSystem); - PackageStaticFiles(definition.PartialViews, root, "PartialViews", "View", _fileSystems.PartialViewsFileSystem); - PackageMacros(definition, root); - PackageDictionaryItems(definition, root); - PackageLanguages(definition, root); - PackageDataTypes(definition, root); - Dictionary mediaFiles = PackageMedia(definition, root); - - string fileName; - string tempPackagePath; - if (mediaFiles.Count > 0) + fileName = "package.zip"; + tempPackagePath = Path.Combine(temporaryPath, fileName); + using (FileStream fileStream = File.OpenWrite(tempPackagePath)) + using (var archive = new ZipArchive(fileStream, ZipArchiveMode.Create, true)) { - fileName = "package.zip"; - tempPackagePath = Path.Combine(temporaryPath, fileName); - using (FileStream fileStream = File.OpenWrite(tempPackagePath)) - using (var archive = new ZipArchive(fileStream, ZipArchiveMode.Create, true)) + ZipArchiveEntry packageXmlEntry = archive.CreateEntry("package.xml"); + using (Stream entryStream = packageXmlEntry.Open()) { - ZipArchiveEntry packageXmlEntry = archive.CreateEntry("package.xml"); - using (Stream entryStream = packageXmlEntry.Open()) - { - compiledPackageXml.Save(entryStream); - } - - foreach (KeyValuePair mediaFile in mediaFiles) - { - var entryPath = $"media{mediaFile.Key.EnsureStartsWith('/')}"; - ZipArchiveEntry mediaEntry = archive.CreateEntry(entryPath); - using (Stream entryStream = mediaEntry.Open()) - using (mediaFile.Value) - { - mediaFile.Value.Seek(0, SeekOrigin.Begin); - mediaFile.Value.CopyTo(entryStream); - } - } + compiledPackageXml.Save(entryStream); } - } - else - { - fileName = "package.xml"; - tempPackagePath = Path.Combine(temporaryPath, fileName); - using (FileStream fileStream = File.OpenWrite(tempPackagePath)) + foreach (KeyValuePair mediaFile in mediaFiles) { - compiledPackageXml.Save(fileStream); + var entryPath = $"media{mediaFile.Key.EnsureStartsWith('/')}"; + ZipArchiveEntry mediaEntry = archive.CreateEntry(entryPath); + using (Stream entryStream = mediaEntry.Open()) + using (mediaFile.Value) + { + mediaFile.Value.Seek(0, SeekOrigin.Begin); + mediaFile.Value.CopyTo(entryStream); + } } } + } + else + { + fileName = "package.xml"; + tempPackagePath = Path.Combine(temporaryPath, fileName); - var directoryName = _hostingEnvironment.MapPathContentRoot(Path.Combine(_createdPackagesFolderPath, definition.Name.Replace(' ', '_'))); - Directory.CreateDirectory(directoryName); - - var finalPackagePath = Path.Combine(directoryName, fileName); - - if (File.Exists(finalPackagePath)) + using (FileStream fileStream = File.OpenWrite(tempPackagePath)) { - File.Delete(finalPackagePath); + compiledPackageXml.Save(fileStream); } + } - File.Move(tempPackagePath, finalPackagePath); + var directoryName = + _hostingEnvironment.MapPathContentRoot(Path.Combine(_createdPackagesFolderPath, + definition.Name.Replace(' ', '_'))); + Directory.CreateDirectory(directoryName); - definition.PackagePath = finalPackagePath; - SavePackage(definition); + var finalPackagePath = Path.Combine(directoryName, fileName); - return finalPackagePath; - } - finally + if (File.Exists(finalPackagePath)) { - // Clean up - Directory.Delete(temporaryPath, true); + File.Delete(finalPackagePath); } + + File.Move(tempPackagePath, finalPackagePath); + + definition.PackagePath = finalPackagePath; + SavePackage(definition); + + return finalPackagePath; + } + finally + { + // Clean up + Directory.Delete(temporaryPath, true); } + } - private void ValidatePackage(PackageDefinition definition) + private void ValidatePackage(PackageDefinition definition) + { + // ensure it's valid + var context = new ValidationContext(definition, null, null); + var results = new List(); + var isValid = Validator.TryValidateObject(definition, context, results); + if (!isValid) { - // ensure it's valid - var context = new ValidationContext(definition, serviceProvider: null, items: null); - var results = new List(); - var isValid = Validator.TryValidateObject(definition, context, results); - if (!isValid) - throw new InvalidOperationException("Validation failed, there is invalid data on the model: " + string.Join(", ", results.Select(x => x.ErrorMessage))); + throw new InvalidOperationException("Validation failed, there is invalid data on the model: " + + string.Join(", ", results.Select(x => x.ErrorMessage))); } + } - private void PackageDataTypes(PackageDefinition definition, XContainer root) + private void PackageDataTypes(PackageDefinition definition, XContainer root) + { + var dataTypes = new XElement("DataTypes"); + foreach (var dtId in definition.DataTypes) { - var dataTypes = new XElement("DataTypes"); - foreach (var dtId in definition.DataTypes) + if (!int.TryParse(dtId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) { - if (!int.TryParse(dtId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) - continue; - var dataType = _dataTypeService.GetDataType(outInt); - if (dataType == null) - continue; - dataTypes.Add(_serializer.Serialize(dataType)); + continue; } - root.Add(dataTypes); - } - private void PackageLanguages(PackageDefinition definition, XContainer root) - { - var languages = new XElement("Languages"); - foreach (var langId in definition.Languages) + IDataType dataType = _dataTypeService.GetDataType(outInt); + if (dataType == null) { - if (!int.TryParse(langId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) - continue; - var lang = _languageService.GetLanguageById(outInt); - if (lang == null) - continue; - languages.Add(_serializer.Serialize(lang)); + continue; } - root.Add(languages); + + dataTypes.Add(_serializer.Serialize(dataType)); } - private void PackageDictionaryItems(PackageDefinition definition, XContainer root) + root.Add(dataTypes); + } + + private void PackageLanguages(PackageDefinition definition, XContainer root) + { + var languages = new XElement("Languages"); + foreach (var langId in definition.Languages) { - var rootDictionaryItems = new XElement("DictionaryItems"); - var items = new Dictionary(); + if (!int.TryParse(langId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) + { + continue; + } - foreach (var dictionaryId in definition.DictionaryItems) + ILanguage lang = _languageService.GetLanguageById(outInt); + if (lang == null) { - if (!int.TryParse(dictionaryId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) - { - continue; - } + continue; + } - IDictionaryItem? di = _languageService.GetDictionaryItemById(outInt); + languages.Add(_serializer.Serialize(lang)); + } - if (di == null) - { - continue; - } + root.Add(languages); + } + + private void PackageDictionaryItems(PackageDefinition definition, XContainer root) + { + var rootDictionaryItems = new XElement("DictionaryItems"); + var items = new Dictionary(); + + foreach (var dictionaryId in definition.DictionaryItems) + { + if (!int.TryParse(dictionaryId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) + { + continue; + } + + IDictionaryItem? di = _languageService.GetDictionaryItemById(outInt); - items[di.Key] = (di, _serializer.Serialize(di, false)); + if (di == null) + { + continue; } - // organize them in hierarchy ... - var itemCount = items.Count; - var processed = new Dictionary(); - while (processed.Count < itemCount) + items[di.Key] = (di, _serializer.Serialize(di, false)); + } + + // organize them in hierarchy ... + var itemCount = items.Count; + var processed = new Dictionary(); + while (processed.Count < itemCount) + { + foreach (Guid key in items.Keys.ToList()) { - foreach (Guid key in items.Keys.ToList()) - { - (IDictionaryItem dictionaryItem, XElement serializedDictionaryValue) = items[key]; + (IDictionaryItem dictionaryItem, XElement serializedDictionaryValue) = items[key]; - if (!dictionaryItem.ParentId.HasValue) + if (!dictionaryItem.ParentId.HasValue) + { + // if it has no parent, its definitely just at the root + AppendDictionaryElement(rootDictionaryItems, items, processed, key, serializedDictionaryValue); + } + else + { + if (processed.ContainsKey(dictionaryItem.ParentId.Value)) { - // if it has no parent, its definitely just at the root - AppendDictionaryElement(rootDictionaryItems, items, processed, key, serializedDictionaryValue); + // we've processed this parent element already so we can just append this xml child to it + AppendDictionaryElement(processed[dictionaryItem.ParentId.Value], items, processed, key, + serializedDictionaryValue); + } + else if (items.ContainsKey(dictionaryItem.ParentId.Value)) + { + // we know the parent exists in the dictionary but + // we haven't processed it yet so we'll leave it for the next loop } else { - if (processed.ContainsKey(dictionaryItem.ParentId.Value)) - { - // we've processed this parent element already so we can just append this xml child to it - AppendDictionaryElement(processed[dictionaryItem.ParentId.Value], items, processed, key, serializedDictionaryValue); - } - else if (items.ContainsKey(dictionaryItem.ParentId.Value)) - { - // we know the parent exists in the dictionary but - // we haven't processed it yet so we'll leave it for the next loop - continue; - } - else - { - // in this case, the parent of this item doesn't exist in our collection, we have no - // choice but to add it to the root. - AppendDictionaryElement(rootDictionaryItems, items, processed, key, serializedDictionaryValue); - } + // in this case, the parent of this item doesn't exist in our collection, we have no + // choice but to add it to the root. + AppendDictionaryElement(rootDictionaryItems, items, processed, key, serializedDictionaryValue); } } } + } - root.Add(rootDictionaryItems); + root.Add(rootDictionaryItems); - static void AppendDictionaryElement(XElement rootDictionaryItems, Dictionary items, Dictionary processed, Guid key, XElement serializedDictionaryValue) - { - // track it - processed.Add(key, serializedDictionaryValue); - // append it - rootDictionaryItems.Add(serializedDictionaryValue); - // remove it so its not re-processed - items.Remove(key); - } + static void AppendDictionaryElement(XElement rootDictionaryItems, + Dictionary items, + Dictionary processed, Guid key, XElement serializedDictionaryValue) + { + // track it + processed.Add(key, serializedDictionaryValue); + // append it + rootDictionaryItems.Add(serializedDictionaryValue); + // remove it so its not re-processed + items.Remove(key); } + } - private void PackageMacros(PackageDefinition definition, XContainer root) + private void PackageMacros(PackageDefinition definition, XContainer root) + { + var packagedMacros = new List(); + var macros = new XElement("Macros"); + foreach (var macroId in definition.Macros) { - var packagedMacros = new List(); - var macros = new XElement("Macros"); - foreach (var macroId in definition.Macros) + if (!int.TryParse(macroId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) { - if (!int.TryParse(macroId, NumberStyles.Integer, CultureInfo.InvariantCulture, out int outInt)) - { - continue; - } + continue; + } - XElement? macroXml = GetMacroXml(outInt, out IMacro? macro); - if (macroXml == null) - { - continue; - } + XElement? macroXml = GetMacroXml(outInt, out IMacro? macro); + if (macroXml == null) + { + continue; + } - macros.Add(macroXml); - if (macro is not null) - { - packagedMacros.Add(macro); - } + macros.Add(macroXml); + if (macro is not null) + { + packagedMacros.Add(macro); } + } - root.Add(macros); + root.Add(macros); - // Get the partial views for macros and package those (exclude views outside of the default directory, e.g. App_Plugins\*\Views) - IEnumerable views = packagedMacros.Where(x => x.MacroSource is not null).Where(x => x.MacroSource!.StartsWith(Constants.SystemDirectories.MacroPartials)) - .Select(x => x.MacroSource!.Substring(Constants.SystemDirectories.MacroPartials.Length).Replace('/', '\\')); - PackageStaticFiles(views, root, "MacroPartialViews", "View", _fileSystems.MacroPartialsFileSystem); - } + // Get the partial views for macros and package those (exclude views outside of the default directory, e.g. App_Plugins\*\Views) + IEnumerable views = packagedMacros.Where(x => x.MacroSource is not null) + .Where(x => x.MacroSource!.StartsWith(Constants.SystemDirectories.MacroPartials)) + .Select(x => x.MacroSource!.Substring(Constants.SystemDirectories.MacroPartials.Length).Replace('/', '\\')); + PackageStaticFiles(views, root, "MacroPartialViews", "View", _fileSystems.MacroPartialsFileSystem); + } - private void PackageStylesheets(PackageDefinition definition, XContainer root) + private void PackageStylesheets(PackageDefinition definition, XContainer root) + { + var stylesheetsXml = new XElement("Stylesheets"); + foreach (var stylesheet in definition.Stylesheets) { - var stylesheetsXml = new XElement("Stylesheets"); - foreach (var stylesheet in definition.Stylesheets) + if (stylesheet.IsNullOrWhiteSpace()) { - if (stylesheet.IsNullOrWhiteSpace()) - { - continue; - } + continue; + } - XElement? xml = GetStylesheetXml(stylesheet, true); - if (xml is not null) - { - stylesheetsXml.Add(xml); - } + XElement? xml = GetStylesheetXml(stylesheet, true); + if (xml is not null) + { + stylesheetsXml.Add(xml); } - root.Add(stylesheetsXml); } - private void PackageStaticFiles( - IEnumerable filePaths, - XContainer root, - string containerName, - string elementName, - IFileSystem? fileSystem) + root.Add(stylesheetsXml); + } + + private void PackageStaticFiles( + IEnumerable filePaths, + XContainer root, + string containerName, + string elementName, + IFileSystem? fileSystem) + { + var scriptsXml = new XElement(containerName); + foreach (var file in filePaths) { - var scriptsXml = new XElement(containerName); - foreach (var file in filePaths) + if (file.IsNullOrWhiteSpace()) { - if (file.IsNullOrWhiteSpace()) - { - continue; - } + continue; + } - if (!fileSystem?.FileExists(file) ?? false) - { - throw new InvalidOperationException("No file found with path " + file); - } + if (!fileSystem?.FileExists(file) ?? false) + { + throw new InvalidOperationException("No file found with path " + file); + } - using (Stream stream = fileSystem!.OpenFile(file)) + using (Stream stream = fileSystem!.OpenFile(file)) + { + using (var reader = new StreamReader(stream)) { - using (var reader = new StreamReader(stream)) - { - var fileContents = reader.ReadToEnd(); - scriptsXml.Add( - new XElement( - elementName, - new XAttribute("path", file), - new XCData(fileContents))); - } + var fileContents = reader.ReadToEnd(); + scriptsXml.Add( + new XElement( + elementName, + new XAttribute("path", file), + new XCData(fileContents))); } } - - root.Add(scriptsXml); } - private void PackageTemplates(PackageDefinition definition, XContainer root) + root.Add(scriptsXml); + } + + private void PackageTemplates(PackageDefinition definition, XContainer root) + { + var templatesXml = new XElement("Templates"); + foreach (var templateId in definition.Templates) { - var templatesXml = new XElement("Templates"); - foreach (var templateId in definition.Templates) + if (!int.TryParse(templateId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) { - if (!int.TryParse(templateId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) - continue; - var template = _fileService.GetTemplate(outInt); - if (template == null) - continue; - templatesXml.Add(_serializer.Serialize(template)); + continue; } - root.Add(templatesXml); + + ITemplate template = _fileService.GetTemplate(outInt); + if (template == null) + { + continue; + } + + templatesXml.Add(_serializer.Serialize(template)); } - private void PackageDocumentTypes(PackageDefinition definition, XContainer root) + root.Add(templatesXml); + } + + private void PackageDocumentTypes(PackageDefinition definition, XContainer root) + { + var contentTypes = new HashSet(); + var docTypesXml = new XElement("DocumentTypes"); + foreach (var dtId in definition.DocumentTypes) { - var contentTypes = new HashSet(); - var docTypesXml = new XElement("DocumentTypes"); - foreach (var dtId in definition.DocumentTypes) + if (!int.TryParse(dtId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) + { + continue; + } + + IContentType contentType = _contentTypeService.Get(outInt); + if (contentType == null) { - if (!int.TryParse(dtId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) - continue; - var contentType = _contentTypeService.Get(outInt); - if (contentType == null) - continue; - AddDocumentType(contentType, contentTypes); + continue; } - foreach (var contentType in contentTypes) - docTypesXml.Add(_serializer.Serialize(contentType)); - root.Add(docTypesXml); + AddDocumentType(contentType, contentTypes); + } + + foreach (IContentType contentType in contentTypes) + { + docTypesXml.Add(_serializer.Serialize(contentType)); } - private void PackageMediaTypes(PackageDefinition definition, XContainer root) + root.Add(docTypesXml); + } + + private void PackageMediaTypes(PackageDefinition definition, XContainer root) + { + var mediaTypes = new HashSet(); + var mediaTypesXml = new XElement("MediaTypes"); + foreach (var mediaTypeId in definition.MediaTypes) { - var mediaTypes = new HashSet(); - var mediaTypesXml = new XElement("MediaTypes"); - foreach (var mediaTypeId in definition.MediaTypes) + if (!int.TryParse(mediaTypeId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) { - if (!int.TryParse(mediaTypeId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var outInt)) - continue; - var mediaType = _mediaTypeService.Get(outInt); - if (mediaType == null) - continue; - AddMediaType(mediaType, mediaTypes); + continue; } - foreach (var mediaType in mediaTypes) - mediaTypesXml.Add(_serializer.Serialize(mediaType)); - root.Add(mediaTypesXml); + IMediaType mediaType = _mediaTypeService.Get(outInt); + if (mediaType == null) + { + continue; + } + + AddMediaType(mediaType, mediaTypes); } - private void PackageDocumentsAndTags(PackageDefinition definition, XContainer root) + foreach (IMediaType mediaType in mediaTypes) + { + mediaTypesXml.Add(_serializer.Serialize(mediaType)); + } + + root.Add(mediaTypesXml); + } + + private void PackageDocumentsAndTags(PackageDefinition definition, XContainer root) + { + //Documents and tags + if (string.IsNullOrEmpty(definition.ContentNodeId) == false && int.TryParse(definition.ContentNodeId, + NumberStyles.Integer, CultureInfo.InvariantCulture, out var contentNodeId)) { - //Documents and tags - if (string.IsNullOrEmpty(definition.ContentNodeId) == false && int.TryParse(definition.ContentNodeId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var contentNodeId)) + if (contentNodeId > 0) { - if (contentNodeId > 0) + //load content from umbraco. + IContent content = _contentService.GetById(contentNodeId); + if (content != null) { - //load content from umbraco. - var content = _contentService.GetById(contentNodeId); - if (content != null) - { - var contentXml = definition.ContentLoadChildNodes ? content.ToDeepXml(_serializer) : content.ToXml(_serializer); - - //Create the Documents/DocumentSet node - - root.Add( - new XElement("Documents", - new XElement("DocumentSet", - new XAttribute("importMode", "root"), - contentXml))); - - // TODO: I guess tags has been broken for a very long time for packaging, we should get this working again sometime - ////Create the TagProperties node - this is used to store a definition for all - //// document properties that are tags, this ensures that we can re-import tags properly - //XmlNode tagProps = new XElement("TagProperties"); - - ////before we try to populate this, we'll do a quick lookup to see if any of the documents - //// being exported contain published tags. - //var allExportedIds = documents.SelectNodes("//@id").Cast() - // .Select(x => x.Value.TryConvertTo()) - // .Where(x => x.Success) - // .Select(x => x.Result) - // .ToArray(); - //var allContentTags = new List(); - //foreach (var exportedId in allExportedIds) - //{ - // allContentTags.AddRange( - // Current.Services.TagService.GetTagsForEntity(exportedId)); - //} - - ////This is pretty round-about but it works. Essentially we need to get the properties that are tagged - //// but to do that we need to lookup by a tag (string) - //var allTaggedEntities = new List(); - //foreach (var group in allContentTags.Select(x => x.Group).Distinct()) - //{ - // allTaggedEntities.AddRange( - // Current.Services.TagService.GetTaggedContentByTagGroup(group)); - //} - - ////Now, we have all property Ids/Aliases and their referenced document Ids and tags - //var allExportedTaggedEntities = allTaggedEntities.Where(x => allExportedIds.Contains(x.EntityId)) - // .DistinctBy(x => x.EntityId) - // .OrderBy(x => x.EntityId); - - //foreach (var taggedEntity in allExportedTaggedEntities) - //{ - // foreach (var taggedProperty in taggedEntity.TaggedProperties.Where(x => x.Tags.Any())) - // { - // XmlNode tagProp = new XElement("TagProperty"); - // var docId = packageManifest.CreateAttribute("docId", ""); - // docId.Value = taggedEntity.EntityId.ToString(CultureInfo.InvariantCulture); - // tagProp.Attributes.Append(docId); - - // var propertyAlias = packageManifest.CreateAttribute("propertyAlias", ""); - // propertyAlias.Value = taggedProperty.PropertyTypeAlias; - // tagProp.Attributes.Append(propertyAlias); - - // var group = packageManifest.CreateAttribute("group", ""); - // group.Value = taggedProperty.Tags.First().Group; - // tagProp.Attributes.Append(group); - - // tagProp.AppendChild(packageManifest.CreateCDataSection( - // JsonConvert.SerializeObject(taggedProperty.Tags.Select(x => x.Text).ToArray()))); - - // tagProps.AppendChild(tagProp); - // } - //} - - //manifestRoot.Add(tagProps); - } + XElement contentXml = definition.ContentLoadChildNodes + ? content.ToDeepXml(_serializer) + : content.ToXml(_serializer); + + //Create the Documents/DocumentSet node + + root.Add( + new XElement("Documents", + new XElement("DocumentSet", + new XAttribute("importMode", "root"), + contentXml))); + + // TODO: I guess tags has been broken for a very long time for packaging, we should get this working again sometime + ////Create the TagProperties node - this is used to store a definition for all + //// document properties that are tags, this ensures that we can re-import tags properly + //XmlNode tagProps = new XElement("TagProperties"); + + ////before we try to populate this, we'll do a quick lookup to see if any of the documents + //// being exported contain published tags. + //var allExportedIds = documents.SelectNodes("//@id").Cast() + // .Select(x => x.Value.TryConvertTo()) + // .Where(x => x.Success) + // .Select(x => x.Result) + // .ToArray(); + //var allContentTags = new List(); + //foreach (var exportedId in allExportedIds) + //{ + // allContentTags.AddRange( + // Current.Services.TagService.GetTagsForEntity(exportedId)); + //} + + ////This is pretty round-about but it works. Essentially we need to get the properties that are tagged + //// but to do that we need to lookup by a tag (string) + //var allTaggedEntities = new List(); + //foreach (var group in allContentTags.Select(x => x.Group).Distinct()) + //{ + // allTaggedEntities.AddRange( + // Current.Services.TagService.GetTaggedContentByTagGroup(group)); + //} + + ////Now, we have all property Ids/Aliases and their referenced document Ids and tags + //var allExportedTaggedEntities = allTaggedEntities.Where(x => allExportedIds.Contains(x.EntityId)) + // .DistinctBy(x => x.EntityId) + // .OrderBy(x => x.EntityId); + + //foreach (var taggedEntity in allExportedTaggedEntities) + //{ + // foreach (var taggedProperty in taggedEntity.TaggedProperties.Where(x => x.Tags.Any())) + // { + // XmlNode tagProp = new XElement("TagProperty"); + // var docId = packageManifest.CreateAttribute("docId", ""); + // docId.Value = taggedEntity.EntityId.ToString(CultureInfo.InvariantCulture); + // tagProp.Attributes.Append(docId); + + // var propertyAlias = packageManifest.CreateAttribute("propertyAlias", ""); + // propertyAlias.Value = taggedProperty.PropertyTypeAlias; + // tagProp.Attributes.Append(propertyAlias); + + // var group = packageManifest.CreateAttribute("group", ""); + // group.Value = taggedProperty.Tags.First().Group; + // tagProp.Attributes.Append(group); + + // tagProp.AppendChild(packageManifest.CreateCDataSection( + // JsonConvert.SerializeObject(taggedProperty.Tags.Select(x => x.Text).ToArray()))); + + // tagProps.AppendChild(tagProp); + // } + //} + + //manifestRoot.Add(tagProps); } } } + } - private Dictionary PackageMedia(PackageDefinition definition, XElement root) - { - var mediaStreams = new Dictionary(); + private Dictionary PackageMedia(PackageDefinition definition, XElement root) + { + var mediaStreams = new Dictionary(); - // callback that occurs on each serialized media item - void OnSerializedMedia(IMedia media, XElement xmlMedia) + // callback that occurs on each serialized media item + void OnSerializedMedia(IMedia media, XElement xmlMedia) + { + // get the media file path and store that separately in the XML. + // the media file path is different from the URL and is specifically + // extracted using the property editor for this media file and the current media file system. + Stream? mediaStream = _mediaFileManager.GetFile(media, out var mediaFilePath); + if (mediaStream != null && mediaFilePath is not null) { - // get the media file path and store that separately in the XML. - // the media file path is different from the URL and is specifically - // extracted using the property editor for this media file and the current media file system. - Stream? mediaStream = _mediaFileManager.GetFile(media, out var mediaFilePath); - if (mediaStream != null && mediaFilePath is not null) - { - xmlMedia.Add(new XAttribute("mediaFilePath", mediaFilePath)); + xmlMedia.Add(new XAttribute("mediaFilePath", mediaFilePath)); - // add the stream to our outgoing stream - mediaStreams.Add(mediaFilePath, mediaStream); - } + // add the stream to our outgoing stream + mediaStreams.Add(mediaFilePath, mediaStream); } + } - IEnumerable medias = _mediaService.GetByIds(definition.MediaUdis); + IEnumerable medias = _mediaService.GetByIds(definition.MediaUdis); - var mediaXml = new XElement( - "MediaItems", - medias.Select(media => - { - XElement serializedMedia = _serializer.Serialize( - media, - definition.MediaLoadChildNodes, - OnSerializedMedia); + var mediaXml = new XElement( + "MediaItems", + medias.Select(media => + { + XElement serializedMedia = _serializer.Serialize( + media, + definition.MediaLoadChildNodes, + OnSerializedMedia); - return new XElement("MediaSet", serializedMedia); - })); + return new XElement("MediaSet", serializedMedia); + })); - root.Add(mediaXml); + root.Add(mediaXml); - return mediaStreams; - } + return mediaStreams; + } - // TODO: Delete this - /// - private XElement? GetMacroXml(int macroId, out IMacro? macro) + // TODO: Delete this + /// + private XElement? GetMacroXml(int macroId, out IMacro? macro) + { + macro = _macroService.GetById(macroId); + if (macro == null) { - macro = _macroService.GetById(macroId); - if (macro == null) - return null; - var xml = _serializer.Serialize(macro); - return xml; + return null; } - /// - /// Converts a umbraco stylesheet to a package xml node - /// - /// The path of the stylesheet. - /// if set to true [include properties]. - /// - private XElement? GetStylesheetXml(string path, bool includeProperties) - { - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentException("Value cannot be null or whitespace.", nameof(path)); - } + XElement xml = _serializer.Serialize(macro); + return xml; + } - IStylesheet? stylesheet = _fileService.GetStylesheet(path); - if (stylesheet == null) - { - return null; - } + /// + /// Converts a umbraco stylesheet to a package xml node + /// + /// The path of the stylesheet. + /// if set to true [include properties]. + /// + private XElement? GetStylesheetXml(string path, bool includeProperties) + { + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(path)); + } - return _serializer.Serialize(stylesheet, includeProperties); + IStylesheet? stylesheet = _fileService.GetStylesheet(path); + if (stylesheet == null) + { + return null; } - private void AddDocumentType(IContentType dt, HashSet dtl) + return _serializer.Serialize(stylesheet, includeProperties); + } + + private void AddDocumentType(IContentType dt, HashSet dtl) + { + if (dt.ParentId > 0) { - if (dt.ParentId > 0) + IContentType parent = _contentTypeService.Get(dt.ParentId); + if (parent != null) // could be a container { - var parent = _contentTypeService.Get(dt.ParentId); - if (parent != null) // could be a container - AddDocumentType(parent, dtl); + AddDocumentType(parent, dtl); } + } - if (!dtl.Contains(dt)) - dtl.Add(dt); + if (!dtl.Contains(dt)) + { + dtl.Add(dt); } + } - private void AddMediaType(IMediaType mediaType, HashSet mediaTypes) + private void AddMediaType(IMediaType mediaType, HashSet mediaTypes) + { + if (mediaType.ParentId > 0) { - if (mediaType.ParentId > 0) + IMediaType parent = _mediaTypeService.Get(mediaType.ParentId); + if (parent != null) // could be a container { - var parent = _mediaTypeService.Get(mediaType.ParentId); - if (parent != null) // could be a container - AddMediaType(parent, mediaTypes); + AddMediaType(parent, mediaTypes); } - - if (!mediaTypes.Contains(mediaType)) - mediaTypes.Add(mediaType); } - private static XElement GetPackageInfoXml(PackageDefinition definition) + if (!mediaTypes.Contains(mediaType)) { - var info = new XElement("info"); - - //Package info - var package = new XElement("package"); - package.Add(new XElement("name", definition.Name)); - info.Add(package); - return info; + mediaTypes.Add(mediaType); } + } - private static XDocument CreateCompiledPackageXml(out XElement root) - { - root = new XElement("umbPackage"); - var compiledPackageXml = new XDocument(root); - return compiledPackageXml; - } + private static XElement GetPackageInfoXml(PackageDefinition definition) + { + var info = new XElement("info"); - private XDocument EnsureStorage(out string packagesFile) - { - var packagesFolder = _hostingEnvironment.MapPathContentRoot(_packagesFolderPath); - Directory.CreateDirectory(packagesFolder); + //Package info + var package = new XElement("package"); + package.Add(new XElement("name", definition.Name)); + info.Add(package); + return info; + } - packagesFile = _hostingEnvironment.MapPathContentRoot(CreatedPackagesFile); - if (!File.Exists(packagesFile)) - { - var xml = new XDocument(new XElement("packages")); - xml.Save(packagesFile); + private static XDocument CreateCompiledPackageXml(out XElement root) + { + root = new XElement("umbPackage"); + var compiledPackageXml = new XDocument(root); + return compiledPackageXml; + } - return xml; - } + private XDocument EnsureStorage(out string packagesFile) + { + var packagesFolder = _hostingEnvironment.MapPathContentRoot(_packagesFolderPath); + Directory.CreateDirectory(packagesFolder); + + packagesFile = _hostingEnvironment.MapPathContentRoot(CreatedPackagesFile); + if (!File.Exists(packagesFile)) + { + var xml = new XDocument(new XElement("packages")); + xml.Save(packagesFile); - var packagesXml = XDocument.Load(packagesFile); - return packagesXml; + return xml; } - public void DeleteLocalRepositoryFiles() + var packagesXml = XDocument.Load(packagesFile); + return packagesXml; + } + + public void DeleteLocalRepositoryFiles() + { + var packagesFile = _hostingEnvironment.MapPathContentRoot(CreatedPackagesFile); + if (File.Exists(packagesFile)) { - var packagesFile = _hostingEnvironment.MapPathContentRoot(CreatedPackagesFile); - if (File.Exists(packagesFile)) - { - File.Delete(packagesFile); - } + File.Delete(packagesFile); + } - var packagesFolder = _hostingEnvironment.MapPathContentRoot(_packagesFolderPath); - if (Directory.Exists(packagesFolder)) - { - Directory.Delete(packagesFolder); - } + var packagesFolder = _hostingEnvironment.MapPathContentRoot(_packagesFolderPath); + if (Directory.Exists(packagesFolder)) + { + Directory.Delete(packagesFolder); } } } diff --git a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs index de5b8c04ae1c..89eb1e2aa67d 100644 --- a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs +++ b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs @@ -1,90 +1,90 @@ // ReSharper disable once CheckNamespace -namespace Umbraco.Cms.Core + +namespace Umbraco.Cms.Core; + +public static partial class Constants { - public static partial class Constants + public static class DatabaseSchema { - public static class DatabaseSchema + //TODO: Why aren't all table names with the same prefix? + public const string TableNamePrefix = "umbraco"; + + public static class Tables { - //TODO: Why aren't all table names with the same prefix? - public const string TableNamePrefix = "umbraco"; - - public static class Tables - { - public const string Lock = TableNamePrefix + "Lock"; - public const string Log = TableNamePrefix + "Log"; - - public const string Node = TableNamePrefix + "Node"; - public const string NodeData = /*TableNamePrefix*/ "cms" + "ContentNu"; - - public const string ContentType = /*TableNamePrefix*/ "cms" + "ContentType"; - public const string ContentChildType = /*TableNamePrefix*/ "cms" + "ContentTypeAllowedContentType"; - public const string DocumentType = /*TableNamePrefix*/ "cms" + "DocumentType"; - public const string ElementTypeTree = /*TableNamePrefix*/ "cms" + "ContentType2ContentType"; - public const string DataType = TableNamePrefix + "DataType"; - public const string Template = /*TableNamePrefix*/ "cms" + "Template"; - - public const string Content = TableNamePrefix + "Content"; - public const string ContentVersion = TableNamePrefix + "ContentVersion"; - public const string ContentVersionCultureVariation = TableNamePrefix + "ContentVersionCultureVariation"; - public const string ContentVersionCleanupPolicy = TableNamePrefix + "ContentVersionCleanupPolicy"; - - public const string Document = TableNamePrefix + "Document"; - public const string DocumentCultureVariation = TableNamePrefix + "DocumentCultureVariation"; - public const string DocumentVersion = TableNamePrefix + "DocumentVersion"; - public const string MediaVersion = TableNamePrefix + "MediaVersion"; - public const string ContentSchedule = TableNamePrefix + "ContentSchedule"; - - public const string PropertyType = /*TableNamePrefix*/ "cms" + "PropertyType"; - public const string PropertyTypeGroup = /*TableNamePrefix*/ "cms" + "PropertyTypeGroup"; - public const string PropertyData = TableNamePrefix + "PropertyData"; - - public const string RelationType = TableNamePrefix + "RelationType"; - public const string Relation = TableNamePrefix + "Relation"; - - public const string Domain = TableNamePrefix + "Domain"; - public const string Language = TableNamePrefix + "Language"; - public const string DictionaryEntry = /*TableNamePrefix*/ "cms" + "Dictionary"; - public const string DictionaryValue = /*TableNamePrefix*/ "cms" + "LanguageText"; - - public const string User = TableNamePrefix + "User"; - public const string UserGroup = TableNamePrefix + "UserGroup"; - public const string UserStartNode = TableNamePrefix + "UserStartNode"; - public const string User2UserGroup = TableNamePrefix + "User2UserGroup"; - public const string User2NodeNotify = TableNamePrefix + "User2NodeNotify"; - public const string UserGroup2App = TableNamePrefix + "UserGroup2App"; - public const string UserGroup2Node = TableNamePrefix + "UserGroup2Node"; - public const string UserGroup2NodePermission = TableNamePrefix + "UserGroup2NodePermission"; - public const string ExternalLogin = TableNamePrefix + "ExternalLogin"; - public const string TwoFactorLogin = TableNamePrefix + "TwoFactorLogin"; - public const string ExternalLoginToken = TableNamePrefix + "ExternalLoginToken"; - - public const string Macro = /*TableNamePrefix*/ "cms" + "Macro"; - public const string MacroProperty = /*TableNamePrefix*/ "cms" + "MacroProperty"; - - public const string Member = /*TableNamePrefix*/ "cms" + "Member"; - public const string MemberPropertyType = /*TableNamePrefix*/ "cms" + "MemberType"; - public const string Member2MemberGroup = /*TableNamePrefix*/ "cms" + "Member2MemberGroup"; - - public const string Access = TableNamePrefix + "Access"; - public const string AccessRule = TableNamePrefix + "AccessRule"; - public const string RedirectUrl = TableNamePrefix + "RedirectUrl"; - - public const string CacheInstruction = TableNamePrefix + "CacheInstruction"; - public const string Server = TableNamePrefix + "Server"; - - public const string Tag = /*TableNamePrefix*/ "cms" + "Tags"; - public const string TagRelationship = /*TableNamePrefix*/ "cms" + "TagRelationship"; - - public const string KeyValue = TableNamePrefix + "KeyValue"; - - public const string AuditEntry = TableNamePrefix + "Audit"; - public const string Consent = TableNamePrefix + "Consent"; - public const string UserLogin = TableNamePrefix + "UserLogin"; - - public const string LogViewerQuery = TableNamePrefix + "LogViewerQuery"; - - public const string CreatedPackageSchema = TableNamePrefix + "CreatedPackageSchema"; - } + public const string Lock = TableNamePrefix + "Lock"; + public const string Log = TableNamePrefix + "Log"; + + public const string Node = TableNamePrefix + "Node"; + public const string NodeData = /*TableNamePrefix*/ "cms" + "ContentNu"; + + public const string ContentType = /*TableNamePrefix*/ "cms" + "ContentType"; + public const string ContentChildType = /*TableNamePrefix*/ "cms" + "ContentTypeAllowedContentType"; + public const string DocumentType = /*TableNamePrefix*/ "cms" + "DocumentType"; + public const string ElementTypeTree = /*TableNamePrefix*/ "cms" + "ContentType2ContentType"; + public const string DataType = TableNamePrefix + "DataType"; + public const string Template = /*TableNamePrefix*/ "cms" + "Template"; + + public const string Content = TableNamePrefix + "Content"; + public const string ContentVersion = TableNamePrefix + "ContentVersion"; + public const string ContentVersionCultureVariation = TableNamePrefix + "ContentVersionCultureVariation"; + public const string ContentVersionCleanupPolicy = TableNamePrefix + "ContentVersionCleanupPolicy"; + + public const string Document = TableNamePrefix + "Document"; + public const string DocumentCultureVariation = TableNamePrefix + "DocumentCultureVariation"; + public const string DocumentVersion = TableNamePrefix + "DocumentVersion"; + public const string MediaVersion = TableNamePrefix + "MediaVersion"; + public const string ContentSchedule = TableNamePrefix + "ContentSchedule"; + + public const string PropertyType = /*TableNamePrefix*/ "cms" + "PropertyType"; + public const string PropertyTypeGroup = /*TableNamePrefix*/ "cms" + "PropertyTypeGroup"; + public const string PropertyData = TableNamePrefix + "PropertyData"; + + public const string RelationType = TableNamePrefix + "RelationType"; + public const string Relation = TableNamePrefix + "Relation"; + + public const string Domain = TableNamePrefix + "Domain"; + public const string Language = TableNamePrefix + "Language"; + public const string DictionaryEntry = /*TableNamePrefix*/ "cms" + "Dictionary"; + public const string DictionaryValue = /*TableNamePrefix*/ "cms" + "LanguageText"; + + public const string User = TableNamePrefix + "User"; + public const string UserGroup = TableNamePrefix + "UserGroup"; + public const string UserStartNode = TableNamePrefix + "UserStartNode"; + public const string User2UserGroup = TableNamePrefix + "User2UserGroup"; + public const string User2NodeNotify = TableNamePrefix + "User2NodeNotify"; + public const string UserGroup2App = TableNamePrefix + "UserGroup2App"; + public const string UserGroup2Node = TableNamePrefix + "UserGroup2Node"; + public const string UserGroup2NodePermission = TableNamePrefix + "UserGroup2NodePermission"; + public const string ExternalLogin = TableNamePrefix + "ExternalLogin"; + public const string TwoFactorLogin = TableNamePrefix + "TwoFactorLogin"; + public const string ExternalLoginToken = TableNamePrefix + "ExternalLoginToken"; + + public const string Macro = /*TableNamePrefix*/ "cms" + "Macro"; + public const string MacroProperty = /*TableNamePrefix*/ "cms" + "MacroProperty"; + + public const string Member = /*TableNamePrefix*/ "cms" + "Member"; + public const string MemberPropertyType = /*TableNamePrefix*/ "cms" + "MemberType"; + public const string Member2MemberGroup = /*TableNamePrefix*/ "cms" + "Member2MemberGroup"; + + public const string Access = TableNamePrefix + "Access"; + public const string AccessRule = TableNamePrefix + "AccessRule"; + public const string RedirectUrl = TableNamePrefix + "RedirectUrl"; + + public const string CacheInstruction = TableNamePrefix + "CacheInstruction"; + public const string Server = TableNamePrefix + "Server"; + + public const string Tag = /*TableNamePrefix*/ "cms" + "Tags"; + public const string TagRelationship = /*TableNamePrefix*/ "cms" + "TagRelationship"; + + public const string KeyValue = TableNamePrefix + "KeyValue"; + + public const string AuditEntry = TableNamePrefix + "Audit"; + public const string Consent = TableNamePrefix + "Consent"; + public const string UserLogin = TableNamePrefix + "UserLogin"; + + public const string LogViewerQuery = TableNamePrefix + "LogViewerQuery"; + + public const string CreatedPackageSchema = TableNamePrefix + "CreatedPackageSchema"; } } } diff --git a/src/Umbraco.Core/Persistence/Constants-Locks.cs b/src/Umbraco.Core/Persistence/Constants-Locks.cs index 3c0b2c4d2823..67e8564dbc69 100644 --- a/src/Umbraco.Core/Persistence/Constants-Locks.cs +++ b/src/Umbraco.Core/Persistence/Constants-Locks.cs @@ -2,74 +2,73 @@ using Umbraco.Cms.Core.Runtime; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +static partial class Constants { - static partial class Constants + /// + /// Defines lock objects. + /// + public static class Locks { /// - /// Defines lock objects. + /// The lock /// - public static class Locks - { - /// - /// The lock - /// - public const int MainDom = -1000; + public const int MainDom = -1000; - /// - /// All servers. - /// - public const int Servers = -331; + /// + /// All servers. + /// + public const int Servers = -331; - /// - /// All content and media types. - /// - public const int ContentTypes = -332; + /// + /// All content and media types. + /// + public const int ContentTypes = -332; - /// - /// The entire content tree, i.e. all content items. - /// - public const int ContentTree = -333; + /// + /// The entire content tree, i.e. all content items. + /// + public const int ContentTree = -333; - /// - /// The entire media tree, i.e. all media items. - /// - public const int MediaTree = -334; + /// + /// The entire media tree, i.e. all media items. + /// + public const int MediaTree = -334; - /// - /// The entire member tree, i.e. all members. - /// - public const int MemberTree = -335; + /// + /// The entire member tree, i.e. all members. + /// + public const int MemberTree = -335; - /// - /// All media types. - /// - public const int MediaTypes = -336; + /// + /// All media types. + /// + public const int MediaTypes = -336; - /// - /// All member types. - /// - public const int MemberTypes = -337; + /// + /// All member types. + /// + public const int MemberTypes = -337; - /// - /// All domains. - /// - public const int Domains = -338; + /// + /// All domains. + /// + public const int Domains = -338; - /// - /// All key-values. - /// - public const int KeyValues = -339; + /// + /// All key-values. + /// + public const int KeyValues = -339; - /// - /// All languages. - /// - public const int Languages = -340; + /// + /// All languages. + /// + public const int Languages = -340; - /// - /// ScheduledPublishing job. - /// - public const int ScheduledPublishing = -341; - } + /// + /// ScheduledPublishing job. + /// + public const int ScheduledPublishing = -341; } } diff --git a/src/Umbraco.Core/Persistence/IQueryRepository.cs b/src/Umbraco.Core/Persistence/IQueryRepository.cs index 1a8dbaf97119..bc3b612e7fef 100644 --- a/src/Umbraco.Core/Persistence/IQueryRepository.cs +++ b/src/Umbraco.Core/Persistence/IQueryRepository.cs @@ -1,21 +1,19 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Persistence.Querying; +using Umbraco.Cms.Core.Persistence.Querying; -namespace Umbraco.Cms.Core.Persistence +namespace Umbraco.Cms.Core.Persistence; + +/// +/// Defines the base implementation of a querying repository. +/// +public interface IQueryRepository : IRepository { /// - /// Defines the base implementation of a querying repository. + /// Gets entities. /// - public interface IQueryRepository : IRepository - { - /// - /// Gets entities. - /// - IEnumerable Get(IQuery query); + IEnumerable Get(IQuery query); - /// - /// Counts entities. - /// - int Count(IQuery query); - } + /// + /// Counts entities. + /// + int Count(IQuery query); } diff --git a/src/Umbraco.Core/Persistence/IReadRepository.cs b/src/Umbraco.Core/Persistence/IReadRepository.cs index 0f757ae04aa9..3b2d37500224 100644 --- a/src/Umbraco.Core/Persistence/IReadRepository.cs +++ b/src/Umbraco.Core/Persistence/IReadRepository.cs @@ -1,25 +1,22 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Persistence; -namespace Umbraco.Cms.Core.Persistence +/// +/// Defines the base implementation of a reading repository. +/// +public interface IReadRepository : IRepository { /// - /// Defines the base implementation of a reading repository. + /// Gets an entity. /// - public interface IReadRepository : IRepository - { - /// - /// Gets an entity. - /// - TEntity? Get(TId? id); + TEntity? Get(TId? id); - /// - /// Gets entities. - /// - IEnumerable GetMany(params TId[]? ids); + /// + /// Gets entities. + /// + IEnumerable GetMany(params TId[]? ids); - /// - /// Gets a value indicating whether an entity exists. - /// - bool Exists(TId id); - } + /// + /// Gets a value indicating whether an entity exists. + /// + bool Exists(TId id); } diff --git a/src/Umbraco.Core/Persistence/IReadWriteQueryRepository.cs b/src/Umbraco.Core/Persistence/IReadWriteQueryRepository.cs index b260144de62a..148831352e9e 100644 --- a/src/Umbraco.Core/Persistence/IReadWriteQueryRepository.cs +++ b/src/Umbraco.Core/Persistence/IReadWriteQueryRepository.cs @@ -1,8 +1,9 @@ -namespace Umbraco.Cms.Core.Persistence +namespace Umbraco.Cms.Core.Persistence; + +/// +/// Defines the base implementation of a reading, writing and querying repository. +/// +public interface IReadWriteQueryRepository : IReadRepository, IWriteRepository, + IQueryRepository { - /// - /// Defines the base implementation of a reading, writing and querying repository. - /// - public interface IReadWriteQueryRepository : IReadRepository, IWriteRepository, IQueryRepository - { } } diff --git a/src/Umbraco.Core/Persistence/IRepository.cs b/src/Umbraco.Core/Persistence/IRepository.cs index f91c4c998b95..c99bcf6f30e7 100644 --- a/src/Umbraco.Core/Persistence/IRepository.cs +++ b/src/Umbraco.Core/Persistence/IRepository.cs @@ -1,8 +1,8 @@ -namespace Umbraco.Cms.Core.Persistence +namespace Umbraco.Cms.Core.Persistence; + +/// +/// Defines the base implementation of a repository. +/// +public interface IRepository { - /// - /// Defines the base implementation of a repository. - /// - public interface IRepository - { } } diff --git a/src/Umbraco.Core/Persistence/IWriteRepository.cs b/src/Umbraco.Core/Persistence/IWriteRepository.cs index ff766fbe369e..b44ee112a7a4 100644 --- a/src/Umbraco.Core/Persistence/IWriteRepository.cs +++ b/src/Umbraco.Core/Persistence/IWriteRepository.cs @@ -1,19 +1,18 @@ -namespace Umbraco.Cms.Core.Persistence +namespace Umbraco.Cms.Core.Persistence; + +/// +/// Defines the base implementation of a writing repository. +/// +public interface IWriteRepository : IRepository { /// - /// Defines the base implementation of a writing repository. + /// Saves an entity. /// - public interface IWriteRepository : IRepository - { - /// - /// Saves an entity. - /// - void Save(TEntity entity); + void Save(TEntity entity); - /// - /// Deletes an entity. - /// - /// - void Delete(TEntity entity); - } + /// + /// Deletes an entity. + /// + /// + void Delete(TEntity entity); } diff --git a/src/Umbraco.Core/Persistence/Querying/IQuery.cs b/src/Umbraco.Core/Persistence/Querying/IQuery.cs index d2a3b0830f64..a53aeccbfa21 100644 --- a/src/Umbraco.Core/Persistence/Querying/IQuery.cs +++ b/src/Umbraco.Core/Persistence/Querying/IQuery.cs @@ -1,42 +1,39 @@ -using System; -using System.Collections; -using System.Collections.Generic; +using System.Collections; using System.Linq.Expressions; -namespace Umbraco.Cms.Core.Persistence.Querying +namespace Umbraco.Cms.Core.Persistence.Querying; + +/// +/// Represents a query for building Linq translatable SQL queries +/// +/// +public interface IQuery { /// - /// Represents a query for building Linq translatable SQL queries + /// Adds a where clause to the query /// - /// - public interface IQuery - { - /// - /// Adds a where clause to the query - /// - /// - /// This instance so calls to this method are chainable - IQuery Where(Expression> predicate); + /// + /// This instance so calls to this method are chainable + IQuery Where(Expression> predicate); - /// - /// Returns all translated where clauses and their sql parameters - /// - /// - IEnumerable> GetWhereClauses(); + /// + /// Returns all translated where clauses and their sql parameters + /// + /// + IEnumerable> GetWhereClauses(); - /// - /// Adds a where-in clause to the query - /// - /// - /// - /// This instance so calls to this method are chainable - IQuery WhereIn(Expression> fieldSelector, IEnumerable? values); + /// + /// Adds a where-in clause to the query + /// + /// + /// + /// This instance so calls to this method are chainable + IQuery WhereIn(Expression> fieldSelector, IEnumerable? values); - /// - /// Adds a set of OR-ed where clauses to the query. - /// - /// - /// This instance so calls to this method are chainable. - IQuery WhereAny(IEnumerable>> predicates); - } + /// + /// Adds a set of OR-ed where clauses to the query. + /// + /// + /// This instance so calls to this method are chainable. + IQuery WhereAny(IEnumerable>> predicates); } diff --git a/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs b/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs index 3e48a00d0513..abcfd7a4d7b2 100644 --- a/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs +++ b/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs @@ -1,15 +1,15 @@ -namespace Umbraco.Cms.Core.Persistence.Querying +namespace Umbraco.Cms.Core.Persistence.Querying; + +/// +/// Determines how to match a string property value +/// +public enum StringPropertyMatchType { - /// - /// Determines how to match a string property value - /// - public enum StringPropertyMatchType - { - Exact, - Contains, - StartsWith, - EndsWith, - //Deals with % as wildcard chars in a string - Wildcard - } + Exact, + Contains, + StartsWith, + EndsWith, + + //Deals with % as wildcard chars in a string + Wildcard } diff --git a/src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs b/src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs index 58daf2e57752..4002b4b07962 100644 --- a/src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs +++ b/src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs @@ -1,14 +1,13 @@ -namespace Umbraco.Cms.Core.Persistence.Querying +namespace Umbraco.Cms.Core.Persistence.Querying; + +/// +/// Determine how to match a number or data value +/// +public enum ValuePropertyMatchType { - /// - /// Determine how to match a number or data value - /// - public enum ValuePropertyMatchType - { - Exact, - GreaterThan, - LessThan, - GreaterThanOrEqualTo, - LessThanOrEqualTo - } + Exact, + GreaterThan, + LessThan, + GreaterThanOrEqualTo, + LessThanOrEqualTo } diff --git a/src/Umbraco.Core/Persistence/Repositories/IAuditEntryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IAuditEntryRepository.cs index 159267c16e5d..bb9958b5a333 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IAuditEntryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IAuditEntryRepository.cs @@ -1,22 +1,20 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +/// +/// Represents a repository for entities. +/// +public interface IAuditEntryRepository : IReadWriteQueryRepository { /// - /// Represents a repository for entities. + /// Gets a page of entries. /// - public interface IAuditEntryRepository : IReadWriteQueryRepository - { - /// - /// Gets a page of entries. - /// - IEnumerable GetPage(long pageIndex, int pageCount, out long records); + IEnumerable GetPage(long pageIndex, int pageCount, out long records); - /// - /// Determines whether the repository is available. - /// - /// During an upgrade, the repository may not be available, until the table has been created. - bool IsAvailable(); - } + /// + /// Determines whether the repository is available. + /// + /// During an upgrade, the repository may not be available, until the table has been created. + bool IsAvailable(); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs index 6d28a86b64db..57aa15a176a1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs @@ -1,38 +1,38 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IAuditRepository : IReadRepository, IWriteRepository, + IQueryRepository { - public interface IAuditRepository : IReadRepository, IWriteRepository, IQueryRepository - { - void CleanLogs(int maximumAgeOfLogsInMinutes); + void CleanLogs(int maximumAgeOfLogsInMinutes); - /// - /// Return the audit items as paged result - /// - /// - /// The query coming from the service - /// - /// - /// - /// - /// - /// - /// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter - /// so we need to do that here - /// - /// - /// A user supplied custom filter - /// - /// - IEnumerable GetPagedResultsByQuery( - IQuery query, - long pageIndex, int pageSize, out long totalRecords, - Direction orderDirection, - AuditType[]? auditTypeFilter, - IQuery? customFilter); + /// + /// Return the audit items as paged result + /// + /// + /// The query coming from the service + /// + /// + /// + /// + /// + /// + /// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query + /// or the custom filter + /// so we need to do that here + /// + /// + /// A user supplied custom filter + /// + /// + IEnumerable GetPagedResultsByQuery( + IQuery query, + long pageIndex, int pageSize, out long totalRecords, + Direction orderDirection, + AuditType[]? auditTypeFilter, + IQuery? customFilter); - IEnumerable Get(AuditType type, IQuery query); - } + IEnumerable Get(AuditType type, IQuery query); } diff --git a/src/Umbraco.Core/Persistence/Repositories/ICacheInstructionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ICacheInstructionRepository.cs index e93f5829a1a7..f11ddf10e34a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ICacheInstructionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ICacheInstructionRepository.cs @@ -1,50 +1,47 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +/// +/// Represents a repository for entities. +/// +public interface ICacheInstructionRepository : IRepository { /// - /// Represents a repository for entities. + /// Gets the count of pending cache instruction records. + /// + int CountAll(); + + /// + /// Gets the count of pending cache instructions. + /// + int CountPendingInstructions(int lastId); + + /// + /// Gets the most recent cache instruction record Id. + /// + /// + int GetMaxId(); + + /// + /// Checks to see if a single cache instruction by Id exists. + /// + bool Exists(int id); + + /// + /// Adds a new cache instruction record. + /// + void Add(CacheInstruction cacheInstruction); + + /// + /// Gets a collection of cache instructions created later than the provided Id. + /// + /// Last id processed. + /// The maximum number of instructions to retrieve. + IEnumerable GetPendingInstructions(int lastId, int maxNumberToRetrieve); + + /// + /// Deletes cache instructions older than the provided date. /// - public interface ICacheInstructionRepository : IRepository - { - /// - /// Gets the count of pending cache instruction records. - /// - int CountAll(); - - /// - /// Gets the count of pending cache instructions. - /// - int CountPendingInstructions(int lastId); - - /// - /// Gets the most recent cache instruction record Id. - /// - /// - int GetMaxId(); - - /// - /// Checks to see if a single cache instruction by Id exists. - /// - bool Exists(int id); - - /// - /// Adds a new cache instruction record. - /// - void Add(CacheInstruction cacheInstruction); - - /// - /// Gets a collection of cache instructions created later than the provided Id. - /// - /// Last id processed. - /// The maximum number of instructions to retrieve. - IEnumerable GetPendingInstructions(int lastId, int maxNumberToRetrieve); - - /// - /// Deletes cache instructions older than the provided date. - /// - void DeleteInstructionsOlderThan(DateTime pruneDate); - } + void DeleteInstructionsOlderThan(DateTime pruneDate); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IConsentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IConsentRepository.cs index a89ed5628516..eb710d2e6ab2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IConsentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IConsentRepository.cs @@ -1,15 +1,14 @@ using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +/// +/// Represents a repository for entities. +/// +public interface IConsentRepository : IReadWriteQueryRepository { /// - /// Represents a repository for entities. + /// Clears the current flag. /// - public interface IConsentRepository : IReadWriteQueryRepository - { - /// - /// Clears the current flag. - /// - void ClearCurrent(string source, string context, string action); - } + void ClearCurrent(string source, string context, string action); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs index b753d355444a..2aa55b5232d5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs @@ -1,83 +1,80 @@ -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +/// +/// Defines the base implementation of a repository for content items. +/// +public interface IContentRepository : IReadWriteQueryRepository + where TEntity : IUmbracoEntity { /// - /// Defines the base implementation of a repository for content items. + /// Gets the recycle bin identifier. /// - public interface IContentRepository : IReadWriteQueryRepository - where TEntity : IUmbracoEntity - { - /// - /// Gets versions. - /// - /// Current version is first, and then versions are ordered with most recent first. - IEnumerable GetAllVersions(int nodeId); + int RecycleBinId { get; } - /// - /// Gets versions. - /// - /// Current version is first, and then versions are ordered with most recent first. - IEnumerable GetAllVersionsSlim(int nodeId, int skip, int take); + /// + /// Gets versions. + /// + /// Current version is first, and then versions are ordered with most recent first. + IEnumerable GetAllVersions(int nodeId); - /// - /// Gets version identifiers. - /// - /// Current version is first, and then versions are ordered with most recent first. - IEnumerable GetVersionIds(int id, int topRows); + /// + /// Gets versions. + /// + /// Current version is first, and then versions are ordered with most recent first. + IEnumerable GetAllVersionsSlim(int nodeId, int skip, int take); - /// - /// Gets a version. - /// - TEntity? GetVersion(int versionId); + /// + /// Gets version identifiers. + /// + /// Current version is first, and then versions are ordered with most recent first. + IEnumerable GetVersionIds(int id, int topRows); - /// - /// Deletes a version. - /// - void DeleteVersion(int versionId); + /// + /// Gets a version. + /// + TEntity? GetVersion(int versionId); - /// - /// Deletes all versions older than a date. - /// - void DeleteVersions(int nodeId, DateTime versionDate); + /// + /// Deletes a version. + /// + void DeleteVersion(int versionId); - /// - /// Gets the recycle bin identifier. - /// - int RecycleBinId { get; } + /// + /// Deletes all versions older than a date. + /// + void DeleteVersions(int nodeId, DateTime versionDate); - /// - /// Gets the recycle bin content. - /// - IEnumerable? GetRecycleBin(); + /// + /// Gets the recycle bin content. + /// + IEnumerable? GetRecycleBin(); - /// - /// Gets the count of content items of a given content type. - /// - int Count(string? contentTypeAlias = null); + /// + /// Gets the count of content items of a given content type. + /// + int Count(string? contentTypeAlias = null); - /// - /// Gets the count of child content items of a given parent content, of a given content type. - /// - int CountChildren(int parentId, string? contentTypeAlias = null); + /// + /// Gets the count of child content items of a given parent content, of a given content type. + /// + int CountChildren(int parentId, string? contentTypeAlias = null); - /// - /// Gets the count of descendant content items of a given parent content, of a given content type. - /// - int CountDescendants(int parentId, string? contentTypeAlias = null); + /// + /// Gets the count of descendant content items of a given parent content, of a given content type. + /// + int CountDescendants(int parentId, string? contentTypeAlias = null); - /// - /// Gets paged content items. - /// - /// Here, can be null but cannot. - IEnumerable GetPage(IQuery? query, long pageIndex, int pageSize, out long totalRecords, - IQuery? filter, Ordering? ordering); + /// + /// Gets paged content items. + /// + /// Here, can be null but cannot. + IEnumerable GetPage(IQuery? query, long pageIndex, int pageSize, out long totalRecords, + IQuery? filter, Ordering? ordering); - ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options); - } + ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentTypeCommonRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentTypeCommonRepository.cs index 7bdfa294c888..b37701d27cf6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IContentTypeCommonRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IContentTypeCommonRepository.cs @@ -1,25 +1,22 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories -{ - // TODO - // this should be IContentTypeRepository, and what is IContentTypeRepository at the moment should - // become IDocumentTypeRepository - but since these interfaces are public, that would be breaking +namespace Umbraco.Cms.Core.Persistence.Repositories; +// TODO +// this should be IContentTypeRepository, and what is IContentTypeRepository at the moment should +// become IDocumentTypeRepository - but since these interfaces are public, that would be breaking +/// +/// Represents the content types common repository, dealing with document, media and member types. +/// +public interface IContentTypeCommonRepository +{ /// - /// Represents the content types common repository, dealing with document, media and member types. + /// Gets and cache all types. /// - public interface IContentTypeCommonRepository - { - /// - /// Gets and cache all types. - /// - IEnumerable? GetAllTypes(); + IEnumerable? GetAllTypes(); - /// - /// Clears the cache. - /// - void ClearCache(); - } + /// + /// Clears the cache. + /// + void ClearCache(); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepository.cs index 148132dc2931..819d462ccc5d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepository.cs @@ -1,35 +1,32 @@ -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IContentTypeRepository : IContentTypeRepositoryBase { - public interface IContentTypeRepository : IContentTypeRepositoryBase - { - /// - /// Gets all entities of the specified query - /// - /// - /// An enumerable list of objects - IEnumerable GetByQuery(IQuery query); + /// + /// Gets all entities of the specified query + /// + /// + /// An enumerable list of objects + IEnumerable GetByQuery(IQuery query); - /// - /// Gets all property type aliases. - /// - /// - IEnumerable GetAllPropertyTypeAliases(); + /// + /// Gets all property type aliases. + /// + /// + IEnumerable GetAllPropertyTypeAliases(); - /// - /// Gets all content type aliases - /// - /// - /// If this list is empty, it will return all content type aliases for media, members and content, otherwise - /// it will only return content type aliases for the object types specified - /// - /// - IEnumerable GetAllContentTypeAliases(params Guid[] objectTypes); + /// + /// Gets all content type aliases + /// + /// + /// If this list is empty, it will return all content type aliases for media, members and content, otherwise + /// it will only return content type aliases for the object types specified + /// + /// + IEnumerable GetAllContentTypeAliases(params Guid[] objectTypes); - IEnumerable GetAllContentTypeIds(string[] aliases); - } + IEnumerable GetAllContentTypeIds(string[] aliases); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs index 2a427da9dd7c..95f352891609 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs @@ -1,45 +1,42 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IContentTypeRepositoryBase : IReadWriteQueryRepository, IReadRepository + where TItem : IContentTypeComposition { - public interface IContentTypeRepositoryBase : IReadWriteQueryRepository, IReadRepository - where TItem : IContentTypeComposition - { - TItem? Get(string alias); - IEnumerable> Move(TItem moving, EntityContainer container); - - /// - /// Derives a unique alias from an existing alias. - /// - /// The original alias. - /// The original alias with a number appended to it, so that it is unique. - /// Unique across all content, media and member types. - string GetUniqueAlias(string alias); - - - /// - /// Gets a value indicating whether there is a list view content item in the path. - /// - /// - /// - bool HasContainerInPath(string contentPath); - - /// - /// Gets a value indicating whether there is a list view content item in the path. - /// - /// - /// - bool HasContainerInPath(params int[] ids); - - /// - /// Returns true or false depending on whether content nodes have been created based on the provided content type id. - /// - bool HasContentNodes(int id); - } + TItem? Get(string alias); + IEnumerable> Move(TItem moving, EntityContainer container); + + /// + /// Derives a unique alias from an existing alias. + /// + /// The original alias. + /// The original alias with a number appended to it, so that it is unique. + /// Unique across all content, media and member types. + string GetUniqueAlias(string alias); + + + /// + /// Gets a value indicating whether there is a list view content item in the path. + /// + /// + /// + bool HasContainerInPath(string contentPath); + + /// + /// Gets a value indicating whether there is a list view content item in the path. + /// + /// + /// + bool HasContainerInPath(params int[] ids); + + /// + /// Returns true or false depending on whether content nodes have been created based on the provided content type id. + /// + bool HasContentNodes(int id); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IDataTypeContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDataTypeContainerRepository.cs index 3e19c08f9993..aeb8c3bf9a57 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDataTypeContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDataTypeContainerRepository.cs @@ -1,5 +1,5 @@ -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IDataTypeContainerRepository : IEntityContainerRepository { - public interface IDataTypeContainerRepository : IEntityContainerRepository - { } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs index e9063416af22..923737358006 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs @@ -1,18 +1,17 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IDataTypeRepository : IReadWriteQueryRepository { - public interface IDataTypeRepository : IReadWriteQueryRepository - { - IEnumerable> Move(IDataType toMove, EntityContainer? container); + IEnumerable> Move(IDataType toMove, EntityContainer? container); - /// - /// Returns a dictionary of content type s and the property type aliases that use a - /// - /// - /// - IReadOnlyDictionary> FindUsages(int id); - } + /// + /// Returns a dictionary of content type s and the property type aliases that use a + /// + /// + /// + /// + IReadOnlyDictionary> FindUsages(int id); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IDictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDictionaryRepository.cs index 555624b1a0b9..d9f535c3d27a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDictionaryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDictionaryRepository.cs @@ -1,14 +1,11 @@ -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IDictionaryRepository : IReadWriteQueryRepository { - public interface IDictionaryRepository : IReadWriteQueryRepository - { - IDictionaryItem? Get(Guid uniqueId); - IDictionaryItem? Get(string key); - IEnumerable GetDictionaryItemDescendants(Guid? parentId); - Dictionary GetDictionaryItemKeyMap(); - } + IDictionaryItem? Get(Guid uniqueId); + IDictionaryItem? Get(string key); + IEnumerable GetDictionaryItemDescendants(Guid? parentId); + Dictionary GetDictionaryItemKeyMap(); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IDocumentBlueprintRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentBlueprintRepository.cs index e5e6e0f41825..deab54cee07c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDocumentBlueprintRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDocumentBlueprintRepository.cs @@ -1,5 +1,5 @@ -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IDocumentBlueprintRepository : IDocumentRepository { - public interface IDocumentBlueprintRepository : IDocumentRepository - { } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs index e0b7f234ec12..3d5c77576bb9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs @@ -1,96 +1,97 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IDocumentRepository : IContentRepository, IReadRepository { - public interface IDocumentRepository : IContentRepository, IReadRepository - { - /// - /// Gets publish/unpublish schedule for a content node. - /// - /// - /// - ContentScheduleCollection GetContentSchedule(int contentId); + /// + /// Gets publish/unpublish schedule for a content node. + /// + /// + /// + /// + /// + ContentScheduleCollection GetContentSchedule(int contentId); - /// - /// Persists publish/unpublish schedule for a content node. - /// - /// - /// - void PersistContentSchedule(IContent content, ContentScheduleCollection schedule); + /// + /// Persists publish/unpublish schedule for a content node. + /// + /// + /// + void PersistContentSchedule(IContent content, ContentScheduleCollection schedule); - /// - /// Clears the publishing schedule for all entries having an a date before (lower than, or equal to) a specified date. - /// - void ClearSchedule(DateTime date); + /// + /// Clears the publishing schedule for all entries having an a date before (lower than, or equal to) a specified date. + /// + void ClearSchedule(DateTime date); - void ClearSchedule(DateTime date, ContentScheduleAction action); + void ClearSchedule(DateTime date, ContentScheduleAction action); - bool HasContentForExpiration(DateTime date); - bool HasContentForRelease(DateTime date); + bool HasContentForExpiration(DateTime date); + bool HasContentForRelease(DateTime date); - /// - /// Gets objects having an expiration date before (lower than, or equal to) a specified date. - /// - /// - /// The content returned from this method may be culture variant, in which case the resulting should be queried - /// for which culture(s) have been scheduled. - /// - IEnumerable GetContentForExpiration(DateTime date); + /// + /// Gets objects having an expiration date before (lower than, or equal to) a specified date. + /// + /// + /// The content returned from this method may be culture variant, in which case the resulting + /// should be queried + /// for which culture(s) have been scheduled. + /// + IEnumerable GetContentForExpiration(DateTime date); - /// - /// Gets objects having a release date before (lower than, or equal to) a specified date. - /// - /// - /// The content returned from this method may be culture variant, in which case the resulting should be queried - /// for which culture(s) have been scheduled. - /// - IEnumerable GetContentForRelease(DateTime date); + /// + /// Gets objects having a release date before (lower than, or equal to) a specified date. + /// + /// + /// The content returned from this method may be culture variant, in which case the resulting + /// should be queried + /// for which culture(s) have been scheduled. + /// + IEnumerable GetContentForRelease(DateTime date); - /// - /// Get the count of published items - /// - /// - /// - /// We require this on the repo because the IQuery{IContent} cannot supply the 'newest' parameter - /// - int CountPublished(string? contentTypeAlias = null); + /// + /// Get the count of published items + /// + /// + /// + /// We require this on the repo because the IQuery{IContent} cannot supply the 'newest' parameter + /// + int CountPublished(string? contentTypeAlias = null); - bool IsPathPublished(IContent? content); + bool IsPathPublished(IContent? content); - /// - /// Used to bulk update the permissions set for a content item. This will replace all permissions - /// assigned to an entity with a list of user id & permission pairs. - /// - /// - void ReplaceContentPermissions(EntityPermissionSet permissionSet); + /// + /// Used to bulk update the permissions set for a content item. This will replace all permissions + /// assigned to an entity with a list of user id & permission pairs. + /// + /// + void ReplaceContentPermissions(EntityPermissionSet permissionSet); - /// - /// Assigns a single permission to the current content item for the specified user group ids - /// - /// - /// - /// - void AssignEntityPermission(IContent entity, char permission, IEnumerable groupIds); + /// + /// Assigns a single permission to the current content item for the specified user group ids + /// + /// + /// + /// + void AssignEntityPermission(IContent entity, char permission, IEnumerable groupIds); - /// - /// Gets the explicit list of permissions for the content item - /// - /// - /// - EntityPermissionCollection GetPermissionsForEntity(int entityId); + /// + /// Gets the explicit list of permissions for the content item + /// + /// + /// + EntityPermissionCollection GetPermissionsForEntity(int entityId); - /// - /// Used to add/update a permission for a content item - /// - /// - void AddOrUpdatePermissions(ContentPermissionSet permission); + /// + /// Used to add/update a permission for a content item + /// + /// + void AddOrUpdatePermissions(ContentPermissionSet permission); - /// - /// Returns true if there is any content in the recycle bin - /// - bool RecycleBinSmells(); - } + /// + /// Returns true if there is any content in the recycle bin + /// + bool RecycleBinSmells(); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IDocumentTypeContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentTypeContainerRepository.cs index 53fd62fdbe62..8cd5318f9677 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDocumentTypeContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDocumentTypeContainerRepository.cs @@ -1,5 +1,5 @@ -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IDocumentTypeContainerRepository : IEntityContainerRepository { - public interface IDocumentTypeContainerRepository : IEntityContainerRepository - { } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IDocumentVersionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentVersionRepository.cs index ee46db369036..652dbd12b871 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDocumentVersionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDocumentVersionRepository.cs @@ -1,38 +1,37 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IDocumentVersionRepository : IRepository { - public interface IDocumentVersionRepository : IRepository - { - /// - /// Gets a list of all historic content versions. - /// - public IReadOnlyCollection? GetDocumentVersionsEligibleForCleanup(); + /// + /// Gets a list of all historic content versions. + /// + public IReadOnlyCollection? GetDocumentVersionsEligibleForCleanup(); - /// - /// Gets cleanup policy override settings per content type. - /// - public IReadOnlyCollection? GetCleanupPolicies(); + /// + /// Gets cleanup policy override settings per content type. + /// + public IReadOnlyCollection? GetCleanupPolicies(); - /// - /// Gets paginated content versions for given content id paginated. - /// - public IEnumerable? GetPagedItemsByContentId(int contentId, long pageIndex, int pageSize, out long totalRecords, int? languageId = null); + /// + /// Gets paginated content versions for given content id paginated. + /// + public IEnumerable? GetPagedItemsByContentId(int contentId, long pageIndex, int pageSize, + out long totalRecords, int? languageId = null); - /// - /// Deletes multiple content versions by ID. - /// - void DeleteVersions(IEnumerable versionIds); + /// + /// Deletes multiple content versions by ID. + /// + void DeleteVersions(IEnumerable versionIds); - /// - /// Updates the prevent cleanup flag on a content version. - /// - void SetPreventCleanup(int versionId, bool preventCleanup); + /// + /// Updates the prevent cleanup flag on a content version. + /// + void SetPreventCleanup(int versionId, bool preventCleanup); - /// - /// Gets the content version metadata for a specific version. - /// - ContentVersionMeta? Get(int versionId); - } + /// + /// Gets the content version metadata for a specific version. + /// + ContentVersionMeta? Get(int versionId); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IDomainRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDomainRepository.cs index a24b76f90a2a..21aaf74c1c2d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDomainRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDomainRepository.cs @@ -1,13 +1,11 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IDomainRepository : IReadWriteQueryRepository { - public interface IDomainRepository : IReadWriteQueryRepository - { - IDomain? GetByName(string domainName); - bool Exists(string domainName); - IEnumerable GetAll(bool includeWildcards); - IEnumerable GetAssignedDomains(int contentId, bool includeWildcards); - } + IDomain? GetByName(string domainName); + bool Exists(string domainName); + IEnumerable GetAll(bool includeWildcards); + IEnumerable GetAssignedDomains(int contentId, bool includeWildcards); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs index 6b8ece1bfd92..4649f41f033f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IEntityContainerRepository : IReadRepository, IWriteRepository { - public interface IEntityContainerRepository : IReadRepository, IWriteRepository - { - EntityContainer? Get(Guid id); + EntityContainer? Get(Guid id); - IEnumerable Get(string name, int level); - } + IEnumerable Get(string name, int level); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs index 8eeab0b83416..fba04ec7fcfb 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs @@ -1,59 +1,57 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IEntityRepository : IRepository { - public interface IEntityRepository : IRepository - { - IEntitySlim? Get(int id); - IEntitySlim? Get(Guid key); - IEntitySlim? Get(int id, Guid objectTypeId); - IEntitySlim? Get(Guid key, Guid objectTypeId); - - IEnumerable GetAll(Guid objectType, params int[] ids); - IEnumerable GetAll(Guid objectType, params Guid[] keys); - - /// - /// Gets entities for a query - /// - /// - /// - IEnumerable GetByQuery(IQuery query); - - /// - /// Gets entities for a query and a specific object type allowing the query to be slightly more optimized - /// - /// - /// - /// - IEnumerable GetByQuery(IQuery query, Guid objectType); - - UmbracoObjectTypes GetObjectType(int id); - UmbracoObjectTypes GetObjectType(Guid key); - int ReserveId(Guid key); - - IEnumerable GetAllPaths(Guid objectType, params int[]? ids); - IEnumerable GetAllPaths(Guid objectType, params Guid[] keys); - - bool Exists(int id); - bool Exists(Guid key); - - /// - /// Gets paged entities for a query and a specific object type - /// - /// - /// - /// - /// - /// - /// - /// - /// - IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectType, long pageIndex, int pageSize, out long totalRecords, - IQuery? filter, Ordering? ordering); - } + IEntitySlim? Get(int id); + IEntitySlim? Get(Guid key); + IEntitySlim? Get(int id, Guid objectTypeId); + IEntitySlim? Get(Guid key, Guid objectTypeId); + + IEnumerable GetAll(Guid objectType, params int[] ids); + IEnumerable GetAll(Guid objectType, params Guid[] keys); + + /// + /// Gets entities for a query + /// + /// + /// + IEnumerable GetByQuery(IQuery query); + + /// + /// Gets entities for a query and a specific object type allowing the query to be slightly more optimized + /// + /// + /// + /// + IEnumerable GetByQuery(IQuery query, Guid objectType); + + UmbracoObjectTypes GetObjectType(int id); + UmbracoObjectTypes GetObjectType(Guid key); + int ReserveId(Guid key); + + IEnumerable GetAllPaths(Guid objectType, params int[]? ids); + IEnumerable GetAllPaths(Guid objectType, params Guid[] keys); + + bool Exists(int id); + bool Exists(Guid key); + + /// + /// Gets paged entities for a query and a specific object type + /// + /// + /// + /// + /// + /// + /// + /// + /// + IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectType, long pageIndex, + int pageSize, out long totalRecords, + IQuery? filter, Ordering? ordering); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs index 7d9594a3c6f4..6d7370768cb7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IExternalLoginRepository.cs @@ -1,29 +1,26 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Security; -namespace Umbraco.Cms.Core.Persistence.Repositories -{ +namespace Umbraco.Cms.Core.Persistence.Repositories; - public interface IExternalLoginRepository : IReadWriteQueryRepository, IQueryRepository - { +public interface IExternalLoginRepository : IReadWriteQueryRepository, + IQueryRepository +{ + /// + /// Replaces all external login providers for the user + /// + /// + /// + [Obsolete("Use method that takes guid as param from IExternalLoginWithKeyRepository")] + void Save(int userId, IEnumerable logins); - /// - /// Replaces all external login providers for the user - /// - /// - /// - [Obsolete("Use method that takes guid as param from IExternalLoginWithKeyRepository")] - void Save(int userId, IEnumerable logins); + /// + /// Replaces all external login provider tokens for the providers specified for the user + /// + /// + /// + [Obsolete("Use method that takes guid as param from IExternalLoginWithKeyRepository")] + void Save(int userId, IEnumerable tokens); - /// - /// Replaces all external login provider tokens for the providers specified for the user - /// - /// - /// - [Obsolete("Use method that takes guid as param from IExternalLoginWithKeyRepository")] - void Save(int userId, IEnumerable tokens); - [Obsolete("Use method that takes guid as param from IExternalLoginWithKeyRepository")] - void DeleteUserLogins(int memberId); - } + [Obsolete("Use method that takes guid as param from IExternalLoginWithKeyRepository")] + void DeleteUserLogins(int memberId); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IExternalLoginWithKeyRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IExternalLoginWithKeyRepository.cs index 0a4b9e76cf05..ec9a79530cdb 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IExternalLoginWithKeyRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IExternalLoginWithKeyRepository.cs @@ -1,28 +1,25 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Security; -namespace Umbraco.Cms.Core.Persistence.Repositories -{ +namespace Umbraco.Cms.Core.Persistence.Repositories; +/// +/// Repository for external logins with Guid as key, so it can be shared for members and users +/// +public interface IExternalLoginWithKeyRepository : IReadWriteQueryRepository, + IQueryRepository +{ /// - /// Repository for external logins with Guid as key, so it can be shared for members and users + /// Replaces all external login providers for the user/member key /// - public interface IExternalLoginWithKeyRepository : IReadWriteQueryRepository, IQueryRepository - { - /// - /// Replaces all external login providers for the user/member key - /// - void Save(Guid userOrMemberKey, IEnumerable logins); + void Save(Guid userOrMemberKey, IEnumerable logins); - /// - /// Replaces all external login provider tokens for the providers specified for the user/member key - /// - void Save(Guid userOrMemberKey, IEnumerable tokens); + /// + /// Replaces all external login provider tokens for the providers specified for the user/member key + /// + void Save(Guid userOrMemberKey, IEnumerable tokens); - /// - /// Deletes all external logins for the specified the user/member key - /// - void DeleteUserLogins(Guid userOrMemberKey); - } + /// + /// Deletes all external logins for the specified the user/member key + /// + void DeleteUserLogins(Guid userOrMemberKey); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IFileRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IFileRepository.cs index ce76086ed243..53e1bb40741b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IFileRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IFileRepository.cs @@ -1,13 +1,10 @@ -using System.IO; +namespace Umbraco.Cms.Core.Persistence.Repositories; -namespace Umbraco.Cms.Core.Persistence.Repositories +public interface IFileRepository { - public interface IFileRepository - { - Stream GetFileContentStream(string filepath); + Stream GetFileContentStream(string filepath); - void SetFileContent(string filepath, Stream content); + void SetFileContent(string filepath, Stream content); - long GetFileSize(string filepath); - } + long GetFileSize(string filepath); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IFileWithFoldersRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IFileWithFoldersRepository.cs index 77c2f9d40b08..9914e49b26f3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IFileWithFoldersRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IFileWithFoldersRepository.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IFileWithFoldersRepository { - public interface IFileWithFoldersRepository - { - void AddFolder(string folderPath); + void AddFolder(string folderPath); - void DeleteFolder(string folderPath); - } + void DeleteFolder(string folderPath); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IIdKeyMapRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IIdKeyMapRepository.cs index b2c7bc9aa11f..0a501941b35a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IIdKeyMapRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IIdKeyMapRepository.cs @@ -1,5 +1,4 @@ -using System; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories; diff --git a/src/Umbraco.Core/Persistence/Repositories/IInstallationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IInstallationRepository.cs index 5dc7ab0555c3..405029662285 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IInstallationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IInstallationRepository.cs @@ -1,9 +1,6 @@ -using System.Threading.Tasks; +namespace Umbraco.Cms.Core.Persistence.Repositories; -namespace Umbraco.Cms.Core.Persistence.Repositories +public interface IInstallationRepository { - public interface IInstallationRepository - { - Task SaveInstallLogAsync(InstallLog installLog); - } + Task SaveInstallLogAsync(InstallLog installLog); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IKeyValueRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IKeyValueRepository.cs index c9ee7a9d257d..c9792f009d9c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IKeyValueRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IKeyValueRepository.cs @@ -1,15 +1,13 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IKeyValueRepository : IReadRepository, IWriteRepository { - public interface IKeyValueRepository : IReadRepository, IWriteRepository - { - /// - /// Returns key/value pairs for all keys with the specified prefix. - /// - /// - /// - IReadOnlyDictionary? FindByKeyPrefix(string keyPrefix); - } + /// + /// Returns key/value pairs for all keys with the specified prefix. + /// + /// + /// + IReadOnlyDictionary? FindByKeyPrefix(string keyPrefix); } diff --git a/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs index 1be32de98972..b0aaef8b0b48 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs @@ -1,41 +1,40 @@ using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface ILanguageRepository : IReadWriteQueryRepository { - public interface ILanguageRepository : IReadWriteQueryRepository - { - ILanguage? GetByIsoCode(string isoCode); + ILanguage? GetByIsoCode(string isoCode); - /// - /// Gets a language identifier from its ISO code. - /// - /// - /// This can be optimized and bypass all deep cloning. - /// - int? GetIdByIsoCode(string? isoCode, bool throwOnNotFound = true); + /// + /// Gets a language identifier from its ISO code. + /// + /// + /// This can be optimized and bypass all deep cloning. + /// + int? GetIdByIsoCode(string? isoCode, bool throwOnNotFound = true); - /// - /// Gets a language ISO code from its identifier. - /// - /// - /// This can be optimized and bypass all deep cloning. - /// - string? GetIsoCodeById(int? id, bool throwOnNotFound = true); + /// + /// Gets a language ISO code from its identifier. + /// + /// + /// This can be optimized and bypass all deep cloning. + /// + string? GetIsoCodeById(int? id, bool throwOnNotFound = true); - /// - /// Gets the default language ISO code. - /// - /// - /// This can be optimized and bypass all deep cloning. - /// - string GetDefaultIsoCode(); + /// + /// Gets the default language ISO code. + /// + /// + /// This can be optimized and bypass all deep cloning. + /// + string GetDefaultIsoCode(); - /// - /// Gets the default language identifier. - /// - /// - /// This can be optimized and bypass all deep cloning. - /// - int? GetDefaultId(); - } + /// + /// Gets the default language identifier. + /// + /// + /// This can be optimized and bypass all deep cloning. + /// + int? GetDefaultId(); } diff --git a/src/Umbraco.Core/Persistence/Repositories/ILogViewerQueryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ILogViewerQueryRepository.cs index 8e3d779b9dd5..c5f21d2b7ae8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ILogViewerQueryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ILogViewerQueryRepository.cs @@ -1,9 +1,8 @@ using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface ILogViewerQueryRepository : IReadWriteQueryRepository { - public interface ILogViewerQueryRepository : IReadWriteQueryRepository - { - ILogViewerQuery? GetByName(string name); - } + ILogViewerQuery? GetByName(string name); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IMacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMacroRepository.cs index 44ab86b80a0c..8c6e21b2ae2b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMacroRepository.cs @@ -1,12 +1,8 @@ -using System; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories -{ - public interface IMacroRepository : IReadWriteQueryRepository, IReadRepository - { - - //IEnumerable GetAll(params string[] aliases); +namespace Umbraco.Cms.Core.Persistence.Repositories; - } +public interface IMacroRepository : IReadWriteQueryRepository, IReadRepository +{ + //IEnumerable GetAll(params string[] aliases); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IMacroWithAliasRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMacroWithAliasRepository.cs index 46705d0ded6d..48ead78759ca 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMacroWithAliasRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMacroWithAliasRepository.cs @@ -1,14 +1,11 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +[Obsolete("This interface will be merged with IMacroRepository in Umbraco 11")] +public interface IMacroWithAliasRepository : IMacroRepository { - [Obsolete("This interface will be merged with IMacroRepository in Umbraco 11")] - public interface IMacroWithAliasRepository : IMacroRepository - { - IMacro? GetByAlias(string alias); + IMacro? GetByAlias(string alias); - IEnumerable GetAllByAlias(string[] aliases); - } + IEnumerable GetAllByAlias(string[] aliases); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IMediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMediaRepository.cs index ad268c62924c..c0372f021ec1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMediaRepository.cs @@ -1,11 +1,9 @@ -using System; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IMediaRepository : IContentRepository, IReadRepository { - public interface IMediaRepository : IContentRepository, IReadRepository - { - IMedia? GetMediaByPath(string mediaPath); - bool RecycleBinSmells(); - } + IMedia? GetMediaByPath(string mediaPath); + bool RecycleBinSmells(); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IMediaTypeContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMediaTypeContainerRepository.cs index cf2c181d5f5e..c8470922a512 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMediaTypeContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMediaTypeContainerRepository.cs @@ -1,5 +1,5 @@ -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IMediaTypeContainerRepository : IEntityContainerRepository { - public interface IMediaTypeContainerRepository : IEntityContainerRepository - { } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IMediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMediaTypeRepository.cs index 2a1168ae5776..41e7dd0e2dc1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMediaTypeRepository.cs @@ -1,7 +1,7 @@ using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IMediaTypeRepository : IContentTypeRepositoryBase { - public interface IMediaTypeRepository : IContentTypeRepositoryBase - { } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IMemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMemberGroupRepository.cs index a7187ec1ca62..fc12afe1d3ac 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMemberGroupRepository.cs @@ -1,51 +1,46 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IMemberGroupRepository : IReadWriteQueryRepository { - public interface IMemberGroupRepository : IReadWriteQueryRepository - { - /// - /// Gets a member group by it's uniqueId - /// - /// - /// - IMemberGroup? Get(Guid uniqueId); - - /// - /// Gets a member group by it's name - /// - /// - /// - IMemberGroup? GetByName(string? name); - - /// - /// Creates the new member group if it doesn't already exist - /// - /// - IMemberGroup? CreateIfNotExists(string roleName); - - /// - /// Returns the member groups for a given member - /// - /// - /// - IEnumerable GetMemberGroupsForMember(int memberId); - - /// - /// Returns the member groups for a given member - /// - /// - /// - IEnumerable GetMemberGroupsForMember(string? username); - - void ReplaceRoles(int[] memberIds, string[] roleNames); - - void AssignRoles(int[] memberIds, string[] roleNames); - - void DissociateRoles(int[] memberIds, string[] roleNames); - - - } + /// + /// Gets a member group by it's uniqueId + /// + /// + /// + IMemberGroup? Get(Guid uniqueId); + + /// + /// Gets a member group by it's name + /// + /// + /// + IMemberGroup? GetByName(string? name); + + /// + /// Creates the new member group if it doesn't already exist + /// + /// + IMemberGroup? CreateIfNotExists(string roleName); + + /// + /// Returns the member groups for a given member + /// + /// + /// + IEnumerable GetMemberGroupsForMember(int memberId); + + /// + /// Returns the member groups for a given member + /// + /// + /// + IEnumerable GetMemberGroupsForMember(string? username); + + void ReplaceRoles(int[] memberIds, string[] roleNames); + + void AssignRoles(int[] memberIds, string[] roleNames); + + void DissociateRoles(int[] memberIds, string[] roleNames); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs index 28a89ff43a5a..09f6513b2951 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs @@ -1,57 +1,58 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IMemberRepository : IContentRepository { - public interface IMemberRepository : IContentRepository - { - int[] GetMemberIds(string[] names); - - IMember? GetByUsername(string? username); - - /// - /// Finds members in a given role - /// - /// - /// - /// - /// - IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); - - /// - /// Get all members in a specific group - /// - /// - /// - IEnumerable GetByMemberGroup(string groupName); - - /// - /// Checks if a member with the username exists - /// - /// - /// - bool Exists(string username); - - /// - /// Gets the count of items based on a complex query - /// - /// - /// - int GetCountByQuery(IQuery? query); - - /// - /// Sets a members last login date based on their username - /// - /// - /// - /// - /// This is a specialized method because whenever a member logs in, the membership provider requires us to set the 'online' which requires - /// updating their login date. This operation must be fast and cannot use database locks which is fine if we are only executing a single query - /// for this data since there won't be any other data contention issues. - /// - [Obsolete("This is now a NoOp since last login date is no longer an umbraco property, set the date on the IMember directly and Save it instead, scheduled for removal in V11.")] - void SetLastLogin(string username, DateTime date); - } + int[] GetMemberIds(string[] names); + + IMember? GetByUsername(string? username); + + /// + /// Finds members in a given role + /// + /// + /// + /// + /// + IEnumerable FindMembersInRole(string roleName, string usernameToMatch, + StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + + /// + /// Get all members in a specific group + /// + /// + /// + IEnumerable GetByMemberGroup(string groupName); + + /// + /// Checks if a member with the username exists + /// + /// + /// + bool Exists(string username); + + /// + /// Gets the count of items based on a complex query + /// + /// + /// + int GetCountByQuery(IQuery? query); + + /// + /// Sets a members last login date based on their username + /// + /// + /// + /// + /// This is a specialized method because whenever a member logs in, the membership provider requires us to set the + /// 'online' which requires + /// updating their login date. This operation must be fast and cannot use database locks which is fine if we are only + /// executing a single query + /// for this data since there won't be any other data contention issues. + /// + [Obsolete( + "This is now a NoOp since last login date is no longer an umbraco property, set the date on the IMember directly and Save it instead, scheduled for removal in V11.")] + void SetLastLogin(string username, DateTime date); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IMemberTypeContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMemberTypeContainerRepository.cs index 1ccf3e756c06..255e87220684 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMemberTypeContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMemberTypeContainerRepository.cs @@ -1,5 +1,5 @@ -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IMemberTypeContainerRepository : IEntityContainerRepository { - public interface IMemberTypeContainerRepository : IEntityContainerRepository - { } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IMemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMemberTypeRepository.cs index 0b31f0ba46ef..8a276f667c7d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMemberTypeRepository.cs @@ -1,7 +1,7 @@ using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IMemberTypeRepository : IContentTypeRepositoryBase { - public interface IMemberTypeRepository : IContentTypeRepositoryBase - { } } diff --git a/src/Umbraco.Core/Persistence/Repositories/INodeCountRepository.cs b/src/Umbraco.Core/Persistence/Repositories/INodeCountRepository.cs index 4ae191fa7290..72dcd1a13b27 100644 --- a/src/Umbraco.Core/Persistence/Repositories/INodeCountRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/INodeCountRepository.cs @@ -1,6 +1,4 @@ -using System; - -namespace Umbraco.Cms.Core.Persistence.Repositories; +namespace Umbraco.Cms.Core.Persistence.Repositories; public interface INodeCountRepository { diff --git a/src/Umbraco.Core/Persistence/Repositories/INotificationsRepository.cs b/src/Umbraco.Core/Persistence/Repositories/INotificationsRepository.cs index be1a00a13077..1028f4ab84e8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/INotificationsRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/INotificationsRepository.cs @@ -1,20 +1,20 @@ -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface INotificationsRepository : IRepository { - public interface INotificationsRepository : IRepository - { - Notification CreateNotification(IUser user, IEntity entity, string action); - int DeleteNotifications(IUser user); - int DeleteNotifications(IEntity entity); - int DeleteNotifications(IUser user, IEntity entity); - IEnumerable? GetEntityNotifications(IEntity entity); - IEnumerable? GetUserNotifications(IUser user); - IEnumerable? GetUsersNotifications(IEnumerable userIds, string? action, IEnumerable nodeIds, Guid objectType); - IEnumerable SetNotifications(IUser user, IEntity entity, string[] actions); - } + Notification CreateNotification(IUser user, IEntity entity, string action); + int DeleteNotifications(IUser user); + int DeleteNotifications(IEntity entity); + int DeleteNotifications(IUser user, IEntity entity); + IEnumerable? GetEntityNotifications(IEntity entity); + IEnumerable? GetUserNotifications(IUser user); + + IEnumerable? GetUsersNotifications(IEnumerable userIds, string? action, IEnumerable nodeIds, + Guid objectType); + + IEnumerable SetNotifications(IUser user, IEntity entity, string[] actions); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IPartialViewMacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IPartialViewMacroRepository.cs index c731d39780d5..50e56f2cee2e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IPartialViewMacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IPartialViewMacroRepository.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +// this only exists to differentiate with IPartialViewRepository in IoC +// without resorting to constants, names, whatever - and IPartialViewRepository +// is implemented by PartialViewRepository and IPartialViewMacroRepository by +// PartialViewMacroRepository - just to inject the proper filesystem. +public interface IPartialViewMacroRepository : IPartialViewRepository { - // this only exists to differentiate with IPartialViewRepository in IoC - // without resorting to constants, names, whatever - and IPartialViewRepository - // is implemented by PartialViewRepository and IPartialViewMacroRepository by - // PartialViewMacroRepository - just to inject the proper filesystem. - public interface IPartialViewMacroRepository : IPartialViewRepository - { } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IPartialViewRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IPartialViewRepository.cs index a8a84079fa53..72b8fa2af05b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IPartialViewRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IPartialViewRepository.cs @@ -1,8 +1,8 @@ using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IPartialViewRepository : IReadRepository, IWriteRepository, + IFileRepository, IFileWithFoldersRepository { - public interface IPartialViewRepository : IReadRepository, IWriteRepository, IFileRepository, IFileWithFoldersRepository - { - } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IPublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IPublicAccessRepository.cs index 2190782d3b77..a2fbd7c67c89 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IPublicAccessRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IPublicAccessRepository.cs @@ -1,8 +1,7 @@ -using System; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IPublicAccessRepository : IReadWriteQueryRepository { - public interface IPublicAccessRepository : IReadWriteQueryRepository - { } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs index 17be5b385681..73871778e9f2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs @@ -1,89 +1,86 @@ -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +/// +/// Defines the repository. +/// +public interface IRedirectUrlRepository : IReadWriteQueryRepository { /// - /// Defines the repository. + /// Gets a redirect URL. /// - public interface IRedirectUrlRepository : IReadWriteQueryRepository - { - /// - /// Gets a redirect URL. - /// - /// The Umbraco redirect URL route. - /// The content unique key. - /// The culture. - /// - IRedirectUrl? Get(string url, Guid contentKey, string? culture); + /// The Umbraco redirect URL route. + /// The content unique key. + /// The culture. + /// + IRedirectUrl? Get(string url, Guid contentKey, string? culture); - /// - /// Deletes a redirect URL. - /// - /// The redirect URL identifier. - void Delete(Guid id); + /// + /// Deletes a redirect URL. + /// + /// The redirect URL identifier. + void Delete(Guid id); - /// - /// Deletes all redirect URLs. - /// - void DeleteAll(); + /// + /// Deletes all redirect URLs. + /// + void DeleteAll(); - /// - /// Deletes all redirect URLs for a given content. - /// - /// The content unique key. - void DeleteContentUrls(Guid contentKey); + /// + /// Deletes all redirect URLs for a given content. + /// + /// The content unique key. + void DeleteContentUrls(Guid contentKey); - /// - /// Gets the most recent redirect URL corresponding to an Umbraco redirect URL route. - /// - /// The Umbraco redirect URL route. - /// The most recent redirect URL corresponding to the route. - IRedirectUrl? GetMostRecentUrl(string url); + /// + /// Gets the most recent redirect URL corresponding to an Umbraco redirect URL route. + /// + /// The Umbraco redirect URL route. + /// The most recent redirect URL corresponding to the route. + IRedirectUrl? GetMostRecentUrl(string url); - /// - /// Gets the most recent redirect URL corresponding to an Umbraco redirect URL route. - /// - /// The Umbraco redirect URL route. - /// The culture the domain is associated with - /// The most recent redirect URL corresponding to the route. - IRedirectUrl? GetMostRecentUrl(string url, string culture); + /// + /// Gets the most recent redirect URL corresponding to an Umbraco redirect URL route. + /// + /// The Umbraco redirect URL route. + /// The culture the domain is associated with + /// The most recent redirect URL corresponding to the route. + IRedirectUrl? GetMostRecentUrl(string url, string culture); - /// - /// Gets all redirect URLs for a content item. - /// - /// The content unique key. - /// All redirect URLs for the content item. - IEnumerable GetContentUrls(Guid contentKey); + /// + /// Gets all redirect URLs for a content item. + /// + /// The content unique key. + /// All redirect URLs for the content item. + IEnumerable GetContentUrls(Guid contentKey); - /// - /// Gets all redirect URLs. - /// - /// The page index. - /// The page size. - /// The total count of redirect URLs. - /// The redirect URLs. - IEnumerable GetAllUrls(long pageIndex, int pageSize, out long total); + /// + /// Gets all redirect URLs. + /// + /// The page index. + /// The page size. + /// The total count of redirect URLs. + /// The redirect URLs. + IEnumerable GetAllUrls(long pageIndex, int pageSize, out long total); - /// - /// Gets all redirect URLs below a given content item. - /// - /// The content unique identifier. - /// The page index. - /// The page size. - /// The total count of redirect URLs. - /// The redirect URLs. - IEnumerable GetAllUrls(int rootContentId, long pageIndex, int pageSize, out long total); + /// + /// Gets all redirect URLs below a given content item. + /// + /// The content unique identifier. + /// The page index. + /// The page size. + /// The total count of redirect URLs. + /// The redirect URLs. + IEnumerable GetAllUrls(int rootContentId, long pageIndex, int pageSize, out long total); - /// - /// Searches for all redirect URLs that contain a given search term in their URL property. - /// - /// The term to search for. - /// The page index. - /// The page size. - /// The total count of redirect URLs. - /// The redirect URLs. - IEnumerable SearchUrls(string searchTerm, long pageIndex, int pageSize, out long total); - } + /// + /// Searches for all redirect URLs that contain a given search term in their URL property. + /// + /// The term to search for. + /// The page index. + /// The page size. + /// The total count of redirect URLs. + /// The redirect URLs. + IEnumerable SearchUrls(string searchTerm, long pageIndex, int pageSize, out long total); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs index 0165b9eb395b..bea9de47d2c5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs @@ -1,39 +1,40 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IRelationRepository : IReadWriteQueryRepository { - public interface IRelationRepository : IReadWriteQueryRepository - { - IEnumerable GetPagedRelationsByQuery(IQuery? query, long pageIndex, int pageSize, out long totalRecords, Ordering? ordering); + IEnumerable GetPagedRelationsByQuery(IQuery? query, long pageIndex, int pageSize, + out long totalRecords, Ordering? ordering); - /// - /// Persist multiple at once - /// - /// - void Save(IEnumerable relations); + /// + /// Persist multiple at once + /// + /// + void Save(IEnumerable relations); - /// - /// Persist multiple at once but Ids are not returned on created relations - /// - /// - void SaveBulk(IEnumerable relations); + /// + /// Persist multiple at once but Ids are not returned on created relations + /// + /// + void SaveBulk(IEnumerable relations); - /// - /// Deletes all relations for a parent for any specified relation type alias - /// - /// - /// - /// A list of relation types to match for deletion, if none are specified then all relations for this parent id are deleted - /// - void DeleteByParent(int parentId, params string[] relationTypeAliases); + /// + /// Deletes all relations for a parent for any specified relation type alias + /// + /// + /// + /// A list of relation types to match for deletion, if none are specified then all relations for this parent id are + /// deleted + /// + void DeleteByParent(int parentId, params string[] relationTypeAliases); - IEnumerable GetPagedParentEntitiesByChildId(int childId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes); + IEnumerable GetPagedParentEntitiesByChildId(int childId, long pageIndex, int pageSize, + out long totalRecords, params Guid[] entityTypes); - IEnumerable GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes); - } + IEnumerable GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, + out long totalRecords, params Guid[] entityTypes); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IRelationTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRelationTypeRepository.cs index 26dfe4acba73..a5674b97886e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRelationTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRelationTypeRepository.cs @@ -1,8 +1,8 @@ -using System; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IRelationTypeRepository : IReadWriteQueryRepository, + IReadRepository { - public interface IRelationTypeRepository : IReadWriteQueryRepository, IReadRepository - { } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IScriptRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IScriptRepository.cs index 604e1da8d248..f0cfe9490267 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IScriptRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IScriptRepository.cs @@ -1,9 +1,8 @@ -using System.IO; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IScriptRepository : IReadRepository, IWriteRepository, IFileRepository, + IFileWithFoldersRepository { - public interface IScriptRepository : IReadRepository, IWriteRepository, IFileRepository, IFileWithFoldersRepository - { - } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IServerRegistrationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IServerRegistrationRepository.cs index af3555160efb..4ef292a827dd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IServerRegistrationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IServerRegistrationRepository.cs @@ -1,12 +1,10 @@ -using System; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IServerRegistrationRepository : IReadWriteQueryRepository { - public interface IServerRegistrationRepository : IReadWriteQueryRepository - { - void DeactiveStaleServers(TimeSpan staleTimeout); + void DeactiveStaleServers(TimeSpan staleTimeout); - void ClearCache(); - } + void ClearCache(); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IStylesheetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IStylesheetRepository.cs index dcdb5debe768..29f132a74a4e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IStylesheetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IStylesheetRepository.cs @@ -1,8 +1,8 @@ using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IStylesheetRepository : IReadRepository, IWriteRepository, + IFileRepository, IFileWithFoldersRepository { - public interface IStylesheetRepository : IReadRepository, IWriteRepository, IFileRepository, IFileWithFoldersRepository - { - } } diff --git a/src/Umbraco.Core/Persistence/Repositories/ITagRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITagRepository.cs index e2fa2e4406a6..0b90bb838ab6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ITagRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ITagRepository.cs @@ -1,95 +1,103 @@ -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface ITagRepository : IReadWriteQueryRepository { - public interface ITagRepository : IReadWriteQueryRepository - { - #region Assign and Remove Tags - - /// - /// Assign tags to a content property. - /// - /// The identifier of the content item. - /// The identifier of the property type. - /// The tags to assign. - /// A value indicating whether to replace already assigned tags. - /// - /// When is false, the tags specified in are added to those already assigned. - /// When is empty and is true, all assigned tags are removed. - /// - // TODO: replaceTags is used as 'false' in tests exclusively - should get rid of it - void Assign(int contentId, int propertyTypeId, IEnumerable tags, bool replaceTags = true); - - /// - /// Removes assigned tags from a content property. - /// - /// The identifier of the content item. - /// The identifier of the property type. - /// The tags to remove. - void Remove(int contentId, int propertyTypeId, IEnumerable tags); - - /// - /// Removes all assigned tags from a content item. - /// - /// The identifier of the content item. - void RemoveAll(int contentId); - - /// - /// Removes all assigned tags from a content property. - /// - /// The identifier of the content item. - /// The identifier of the property type. - void RemoveAll(int contentId, int propertyTypeId); - - #endregion - - #region Queries - - /// - /// Gets a tagged entity. - /// - TaggedEntity? GetTaggedEntityByKey(Guid key); - - /// - /// Gets a tagged entity. - /// - TaggedEntity? GetTaggedEntityById(int id); - - /// Gets all entities of a type, tagged with any tag in the specified group. - IEnumerable GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string group, string? culture = null); - - /// - /// Gets all entities of a type, tagged with the specified tag. - /// - IEnumerable GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, string? group = null, string? culture = null); - - /// - /// Gets all tags for an entity type. - /// - IEnumerable GetTagsForEntityType(TaggableObjectTypes objectType, string? group = null, string? culture = null); - - /// - /// Gets all tags attached to an entity. - /// - IEnumerable GetTagsForEntity(int contentId, string? group = null, string? culture = null); - - /// - /// Gets all tags attached to an entity. - /// - IEnumerable GetTagsForEntity(Guid contentId, string? group = null, string? culture = null); - - /// - /// Gets all tags attached to an entity via a property. - /// - IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string? group = null, string? culture = null); - - /// - /// Gets all tags attached to an entity via a property. - /// - IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string? group = null, string? culture = null); - - #endregion - } + #region Assign and Remove Tags + + /// + /// Assign tags to a content property. + /// + /// The identifier of the content item. + /// The identifier of the property type. + /// The tags to assign. + /// A value indicating whether to replace already assigned tags. + /// + /// + /// When is false, the tags specified in are added to + /// those already assigned. + /// + /// + /// When is empty and is true, all assigned tags are + /// removed. + /// + /// + // TODO: replaceTags is used as 'false' in tests exclusively - should get rid of it + void Assign(int contentId, int propertyTypeId, IEnumerable tags, bool replaceTags = true); + + /// + /// Removes assigned tags from a content property. + /// + /// The identifier of the content item. + /// The identifier of the property type. + /// The tags to remove. + void Remove(int contentId, int propertyTypeId, IEnumerable tags); + + /// + /// Removes all assigned tags from a content item. + /// + /// The identifier of the content item. + void RemoveAll(int contentId); + + /// + /// Removes all assigned tags from a content property. + /// + /// The identifier of the content item. + /// The identifier of the property type. + void RemoveAll(int contentId, int propertyTypeId); + + #endregion + + #region Queries + + /// + /// Gets a tagged entity. + /// + TaggedEntity? GetTaggedEntityByKey(Guid key); + + /// + /// Gets a tagged entity. + /// + TaggedEntity? GetTaggedEntityById(int id); + + /// Gets all entities of a type, tagged with any tag in the specified group. + IEnumerable GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string group, + string? culture = null); + + /// + /// Gets all entities of a type, tagged with the specified tag. + /// + IEnumerable GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, string? group = null, + string? culture = null); + + /// + /// Gets all tags for an entity type. + /// + IEnumerable GetTagsForEntityType(TaggableObjectTypes objectType, string? group = null, + string? culture = null); + + /// + /// Gets all tags attached to an entity. + /// + IEnumerable GetTagsForEntity(int contentId, string? group = null, string? culture = null); + + /// + /// Gets all tags attached to an entity. + /// + IEnumerable GetTagsForEntity(Guid contentId, string? group = null, string? culture = null); + + /// + /// Gets all tags attached to an entity via a property. + /// + IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string? group = null, + string? culture = null); + + /// + /// Gets all tags attached to an entity via a property. + /// + IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string? group = null, + string? culture = null); + + #endregion } diff --git a/src/Umbraco.Core/Persistence/Repositories/ITemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITemplateRepository.cs index fd206d5afffe..d4cdf397e236 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ITemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ITemplateRepository.cs @@ -1,16 +1,14 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface ITemplateRepository : IReadWriteQueryRepository, IFileRepository { - public interface ITemplateRepository : IReadWriteQueryRepository, IFileRepository - { - ITemplate? Get(string? alias); + ITemplate? Get(string? alias); - IEnumerable? GetAll(params string[] aliases); + IEnumerable? GetAll(params string[] aliases); - IEnumerable? GetChildren(int masterTemplateId); + IEnumerable? GetChildren(int masterTemplateId); - IEnumerable GetDescendants(int masterTemplateId); - } + IEnumerable GetDescendants(int masterTemplateId); } diff --git a/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs index e6ca8eaa507c..7121a2b956e9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs @@ -1,42 +1,52 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface ITrackedReferencesRepository { - public interface ITrackedReferencesRepository - { - /// - /// Gets a page of items which are in relation with the current item. - /// Basically, shows the items which depend on the current item. - /// - /// The identifier of the entity to retrieve relations for. - /// The page index. - /// The page size. - /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true). - /// The total count of the items with reference to the current item. - /// An enumerable list of objects. - IEnumerable GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords); + /// + /// Gets a page of items which are in relation with the current item. + /// Basically, shows the items which depend on the current item. + /// + /// The identifier of the entity to retrieve relations for. + /// The page index. + /// The page size. + /// + /// A boolean indicating whether to filter only the RelationTypes which are + /// dependencies (isDependency field is set to true). + /// + /// The total count of the items with reference to the current item. + /// An enumerable list of objects. + IEnumerable GetPagedRelationsForItem(int id, long pageIndex, int pageSize, + bool filterMustBeIsDependency, out long totalRecords); - /// - /// Gets a page of items used in any kind of relation from selected integer ids. - /// - /// The identifiers of the entities to check for relations. - /// The page index. - /// The page size. - /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true). - /// The total count of the items in any kind of relation. - /// An enumerable list of objects. - IEnumerable GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords); + /// + /// Gets a page of items used in any kind of relation from selected integer ids. + /// + /// The identifiers of the entities to check for relations. + /// The page index. + /// The page size. + /// + /// A boolean indicating whether to filter only the RelationTypes which are + /// dependencies (isDependency field is set to true). + /// + /// The total count of the items in any kind of relation. + /// An enumerable list of objects. + IEnumerable GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, + bool filterMustBeIsDependency, out long totalRecords); - /// - /// Gets a page of the descending items that have any references, given a parent id. - /// - /// The unique identifier of the parent to retrieve descendants for. - /// The page index. - /// The page size. - /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true). - /// The total count of descending items. - /// An enumerable list of objects. - IEnumerable GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords); - } + /// + /// Gets a page of the descending items that have any references, given a parent id. + /// + /// The unique identifier of the parent to retrieve descendants for. + /// The page index. + /// The page size. + /// + /// A boolean indicating whether to filter only the RelationTypes which are + /// dependencies (isDependency field is set to true). + /// + /// The total count of descending items. + /// An enumerable list of objects. + IEnumerable GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, + bool filterMustBeIsDependency, out long totalRecords); } diff --git a/src/Umbraco.Core/Persistence/Repositories/ITwoFactorLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITwoFactorLoginRepository.cs index 63622f8e82d2..a732c9e3a9a6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ITwoFactorLoginRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ITwoFactorLoginRepository.cs @@ -1,16 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Persistence.Repositories -{ - public interface ITwoFactorLoginRepository: IReadRepository, IWriteRepository - { - Task DeleteUserLoginsAsync(Guid userOrMemberKey); - Task DeleteUserLoginsAsync(Guid userOrMemberKey, string providerName); +namespace Umbraco.Cms.Core.Persistence.Repositories; - Task> GetByUserOrMemberKeyAsync(Guid userOrMemberKey); - } +public interface ITwoFactorLoginRepository : IReadRepository, IWriteRepository +{ + Task DeleteUserLoginsAsync(Guid userOrMemberKey); + Task DeleteUserLoginsAsync(Guid userOrMemberKey, string providerName); + Task> GetByUserOrMemberKeyAsync(Guid userOrMemberKey); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IUpgradeCheckRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IUpgradeCheckRepository.cs index d64f177f1468..e509bcfc6738 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IUpgradeCheckRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IUpgradeCheckRepository.cs @@ -1,10 +1,8 @@ -using System.Threading.Tasks; -using Umbraco.Cms.Core.Semver; +using Umbraco.Cms.Core.Semver; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IUpgradeCheckRepository { - public interface IUpgradeCheckRepository - { - Task CheckUpgradeAsync(SemVersion version); - } + Task CheckUpgradeAsync(SemVersion version); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IUserGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IUserGroupRepository.cs index d5cf6fd762b1..5d245d7eda37 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IUserGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IUserGroupRepository.cs @@ -1,59 +1,64 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IUserGroupRepository : IReadWriteQueryRepository { - public interface IUserGroupRepository : IReadWriteQueryRepository - { - /// - /// Gets a group by it's alias - /// - /// - /// - IUserGroup? Get(string alias); - - /// - /// This is useful when an entire section is removed from config - /// - /// - IEnumerable GetGroupsAssignedToSection(string sectionAlias); - - /// - /// Used to add or update a user group and assign users to it - /// - /// - /// - void AddOrUpdateGroupWithUsers(IUserGroup userGroup, int[]? userIds); - - /// - /// Gets explicitly defined permissions for the group for specified entities - /// - /// - /// Array of entity Ids, if empty will return permissions for the group for all entities - EntityPermissionCollection GetPermissions(int[] groupIds, params int[] entityIds); - - /// - /// Gets explicit and default permissions (if requested) permissions for the group for specified entities - /// - /// - /// If true will include the group's default permissions if no permissions are explicitly assigned - /// Array of entity Ids, if empty will return permissions for the group for all entities - EntityPermissionCollection GetPermissions(IReadOnlyUserGroup[]? groups, bool fallbackToDefaultPermissions, params int[] nodeIds); - - /// - /// Replaces the same permission set for a single group to any number of entities - /// - /// Id of group - /// Permissions as enumerable list of - /// Specify the nodes to replace permissions for. If nothing is specified all permissions are removed. - void ReplaceGroupPermissions(int groupId, IEnumerable? permissions, params int[] entityIds); - - /// - /// Assigns the same permission set for a single group to any number of entities - /// - /// Id of group - /// Permissions as enumerable list of - /// Specify the nodes to replace permissions for - void AssignGroupPermission(int groupId, char permission, params int[] entityIds); - } + /// + /// Gets a group by it's alias + /// + /// + /// + IUserGroup? Get(string alias); + + /// + /// This is useful when an entire section is removed from config + /// + /// + IEnumerable GetGroupsAssignedToSection(string sectionAlias); + + /// + /// Used to add or update a user group and assign users to it + /// + /// + /// + void AddOrUpdateGroupWithUsers(IUserGroup userGroup, int[]? userIds); + + /// + /// Gets explicitly defined permissions for the group for specified entities + /// + /// + /// Array of entity Ids, if empty will return permissions for the group for all entities + EntityPermissionCollection GetPermissions(int[] groupIds, params int[] entityIds); + + /// + /// Gets explicit and default permissions (if requested) permissions for the group for specified entities + /// + /// + /// + /// If true will include the group's default permissions if no permissions are + /// explicitly assigned + /// + /// Array of entity Ids, if empty will return permissions for the group for all entities + EntityPermissionCollection GetPermissions(IReadOnlyUserGroup[]? groups, bool fallbackToDefaultPermissions, + params int[] nodeIds); + + /// + /// Replaces the same permission set for a single group to any number of entities + /// + /// Id of group + /// Permissions as enumerable list of + /// + /// Specify the nodes to replace permissions for. If nothing is specified all permissions are + /// removed. + /// + void ReplaceGroupPermissions(int groupId, IEnumerable? permissions, params int[] entityIds); + + /// + /// Assigns the same permission set for a single group to any number of entities + /// + /// Id of group + /// Permissions as enumerable list of + /// Specify the nodes to replace permissions for + void AssignGroupPermission(int groupId, char permission, params int[] entityIds); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs index 8357729f3811..5067436eec6d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs @@ -1,112 +1,109 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; +using System.Linq.Expressions; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Persistence.Querying; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public interface IUserRepository : IReadWriteQueryRepository { - public interface IUserRepository : IReadWriteQueryRepository - { - /// - /// Gets the count of items based on a complex query - /// - /// - /// - int GetCountByQuery(IQuery? query); + /// + /// Gets the count of items based on a complex query + /// + /// + /// + int GetCountByQuery(IQuery? query); - /// - /// Checks if a user with the username exists - /// - /// - /// - [Obsolete("This method will be removed in future versions. Please use ExistsByUserName instead.")] - bool Exists(string username); + /// + /// Checks if a user with the username exists + /// + /// + /// + [Obsolete("This method will be removed in future versions. Please use ExistsByUserName instead.")] + bool Exists(string username); - /// - /// Checks if a user with the username exists - /// - /// - /// - bool ExistsByUserName(string username); + /// + /// Checks if a user with the username exists + /// + /// + /// + bool ExistsByUserName(string username); - /// - /// Checks if a user with the login exists - /// - /// - /// - bool ExistsByLogin(string login); + /// + /// Checks if a user with the login exists + /// + /// + /// + bool ExistsByLogin(string login); - /// - /// Gets a list of objects associated with a given group - /// - /// Id of group - IEnumerable GetAllInGroup(int groupId); + /// + /// Gets a list of objects associated with a given group + /// + /// Id of group + IEnumerable GetAllInGroup(int groupId); - /// - /// Gets a list of objects not associated with a given group - /// - /// Id of group - IEnumerable GetAllNotInGroup(int groupId); + /// + /// Gets a list of objects not associated with a given group + /// + /// Id of group + IEnumerable GetAllNotInGroup(int groupId); - /// - /// Gets paged user results - /// - /// - /// - /// - /// - /// - /// - /// - /// A filter to only include user that belong to these user groups - /// - /// - /// A filter to only include users that do not belong to these user groups - /// - /// Optional parameter to filter by specified user state - /// - /// - IEnumerable GetPagedResultsByQuery(IQuery? query, long pageIndex, int pageSize, out long totalRecords, - Expression> orderBy, Direction orderDirection = Direction.Ascending, - string[]? includeUserGroups = null, string[]? excludeUserGroups = null, UserState[]? userState = null, - IQuery? filter = null); + /// + /// Gets paged user results + /// + /// + /// + /// + /// + /// + /// + /// + /// A filter to only include user that belong to these user groups + /// + /// + /// A filter to only include users that do not belong to these user groups + /// + /// Optional parameter to filter by specified user state + /// + /// + IEnumerable GetPagedResultsByQuery(IQuery? query, long pageIndex, int pageSize, out long totalRecords, + Expression> orderBy, Direction orderDirection = Direction.Ascending, + string[]? includeUserGroups = null, string[]? excludeUserGroups = null, UserState[]? userState = null, + IQuery? filter = null); - /// - /// Returns a user by username - /// - /// - /// - /// This is only used for a shim in order to upgrade to 7.7 - /// - /// - /// A non cached instance - /// - IUser? GetByUsername(string username, bool includeSecurityData); + /// + /// Returns a user by username + /// + /// + /// + /// This is only used for a shim in order to upgrade to 7.7 + /// + /// + /// A non cached instance + /// + IUser? GetByUsername(string username, bool includeSecurityData); - /// - /// Returns a user by id - /// - /// - /// - /// This is only used for a shim in order to upgrade to 7.7 - /// - /// - /// A non cached instance - /// - IUser? Get(int? id, bool includeSecurityData); + /// + /// Returns a user by id + /// + /// + /// + /// This is only used for a shim in order to upgrade to 7.7 + /// + /// + /// A non cached instance + /// + IUser? Get(int? id, bool includeSecurityData); - IProfile? GetProfile(string username); - IProfile? GetProfile(int id); - IDictionary GetUserStates(); + IProfile? GetProfile(string username); + IProfile? GetProfile(int id); + IDictionary GetUserStates(); - Guid CreateLoginSession(int? userId, string requestingIpAddress, bool cleanStaleSessions = true); - bool ValidateLoginSession(int userId, Guid sessionId); - int ClearLoginSessions(int userId); - int ClearLoginSessions(TimeSpan timespan); - void ClearLoginSession(Guid sessionId); + Guid CreateLoginSession(int? userId, string requestingIpAddress, bool cleanStaleSessions = true); + bool ValidateLoginSession(int userId, Guid sessionId); + int ClearLoginSessions(int userId); + int ClearLoginSessions(TimeSpan timespan); + void ClearLoginSession(Guid sessionId); - IEnumerable GetNextUsers(int id, int count); - } + IEnumerable GetNextUsers(int id, int count); } diff --git a/src/Umbraco.Core/Persistence/Repositories/InstallationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/InstallationRepository.cs index cd3e31559b58..51b2f6bc8c33 100644 --- a/src/Umbraco.Core/Persistence/Repositories/InstallationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/InstallationRepository.cs @@ -1,35 +1,32 @@ -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; +using System.Text; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public class InstallationRepository : IInstallationRepository { - public class InstallationRepository : IInstallationRepository - { - private readonly IJsonSerializer _jsonSerializer; - private static HttpClient? _httpClient; - private const string RestApiInstallUrl = "https://our.umbraco.com/umbraco/api/Installation/Install"; + private const string RestApiInstallUrl = "https://our.umbraco.com/umbraco/api/Installation/Install"; + private static HttpClient? _httpClient; + private readonly IJsonSerializer _jsonSerializer; - public InstallationRepository(IJsonSerializer jsonSerializer) - { - _jsonSerializer = jsonSerializer; - } + public InstallationRepository(IJsonSerializer jsonSerializer) => _jsonSerializer = jsonSerializer; - public async Task SaveInstallLogAsync(InstallLog installLog) + public async Task SaveInstallLogAsync(InstallLog installLog) + { + try { - try + if (_httpClient == null) { - if (_httpClient == null) - _httpClient = new HttpClient(); + _httpClient = new HttpClient(); + } - var content = new StringContent(_jsonSerializer.Serialize(installLog), Encoding.UTF8, "application/json"); + var content = new StringContent(_jsonSerializer.Serialize(installLog), Encoding.UTF8, "application/json"); - await _httpClient.PostAsync(RestApiInstallUrl, content); - } - // this occurs if the server for Our is down or cannot be reached - catch (HttpRequestException) - { } + await _httpClient.PostAsync(RestApiInstallUrl, content); + } + // this occurs if the server for Our is down or cannot be reached + catch (HttpRequestException) + { } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeys.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeys.cs index db0ebd7be5da..98047356fc50 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeys.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeys.cs @@ -1,37 +1,31 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Persistence.Repositories; -namespace Umbraco.Cms.Core.Persistence.Repositories +/// +/// Provides cache keys for repositories. +/// +public static class RepositoryCacheKeys { - /// - /// Provides cache keys for repositories. - /// - public static class RepositoryCacheKeys + // used to cache keys so we don't keep allocating strings + private static readonly Dictionary s_keys = new(); + + public static string GetKey() { - // used to cache keys so we don't keep allocating strings - private static readonly Dictionary s_keys = new Dictionary(); + Type type = typeof(T); + return s_keys.TryGetValue(type, out var key) ? key : s_keys[type] = "uRepo_" + type.Name + "_"; + } - public static string GetKey() + public static string GetKey(TId? id) + { + if (EqualityComparer.Default.Equals(id, default)) { - Type type = typeof(T); - return s_keys.TryGetValue(type, out var key) ? key : (s_keys[type] = "uRepo_" + type.Name + "_"); + return string.Empty; } - public static string GetKey(TId? id) + if (typeof(TId).IsValueType) { - if (EqualityComparer.Default.Equals(id, default)) - { - return string.Empty; - } - - if (typeof(TId).IsValueType) - { - return GetKey() + id; - } - else - { - return GetKey() + id?.ToString()?.ToUpperInvariant(); - } + return GetKey() + id; } + + return GetKey() + id?.ToString()?.ToUpperInvariant(); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/UpgradeCheckRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UpgradeCheckRepository.cs index c36156e54b43..a95da5becf6f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UpgradeCheckRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UpgradeCheckRepository.cs @@ -1,59 +1,56 @@ -using System; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; +using System.Text; using Umbraco.Cms.Core.Semver; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Persistence.Repositories +namespace Umbraco.Cms.Core.Persistence.Repositories; + +public class UpgradeCheckRepository : IUpgradeCheckRepository { - public class UpgradeCheckRepository : IUpgradeCheckRepository - { - private readonly IJsonSerializer _jsonSerializer; - private static HttpClient? _httpClient; - private const string RestApiUpgradeChecklUrl = "https://our.umbraco.com/umbraco/api/UpgradeCheck/CheckUpgrade"; + private const string RestApiUpgradeChecklUrl = "https://our.umbraco.com/umbraco/api/UpgradeCheck/CheckUpgrade"; + private static HttpClient? _httpClient; + private readonly IJsonSerializer _jsonSerializer; - public UpgradeCheckRepository(IJsonSerializer jsonSerializer) - { - _jsonSerializer = jsonSerializer; - } + public UpgradeCheckRepository(IJsonSerializer jsonSerializer) => _jsonSerializer = jsonSerializer; - public async Task CheckUpgradeAsync(SemVersion version) + public async Task CheckUpgradeAsync(SemVersion version) + { + try { - try + if (_httpClient == null) { - if (_httpClient == null) - _httpClient = new HttpClient(); + _httpClient = new HttpClient(); + } - var content = new StringContent(_jsonSerializer.Serialize(new CheckUpgradeDto(version)), Encoding.UTF8, "application/json"); + var content = new StringContent(_jsonSerializer.Serialize(new CheckUpgradeDto(version)), Encoding.UTF8, + "application/json"); - _httpClient.Timeout = TimeSpan.FromSeconds(1); - var task = await _httpClient.PostAsync(RestApiUpgradeChecklUrl,content); - var json = await task.Content.ReadAsStringAsync(); - var result = _jsonSerializer.Deserialize(json); + _httpClient.Timeout = TimeSpan.FromSeconds(1); + HttpResponseMessage task = await _httpClient.PostAsync(RestApiUpgradeChecklUrl, content); + var json = await task.Content.ReadAsStringAsync(); + UpgradeResult result = _jsonSerializer.Deserialize(json); - return result ?? new UpgradeResult("None", "", ""); - } - catch (HttpRequestException) - { - // this occurs if the server for Our is down or cannot be reached - return new UpgradeResult("None", "", ""); - } + return result ?? new UpgradeResult("None", "", ""); } - private class CheckUpgradeDto + catch (HttpRequestException) { - public CheckUpgradeDto(SemVersion version) - { - VersionMajor = version.Major; - VersionMinor = version.Minor; - VersionPatch = version.Patch; - VersionComment = version.Prerelease; - } + // this occurs if the server for Our is down or cannot be reached + return new UpgradeResult("None", "", ""); + } + } - public int VersionMajor { get; } - public int VersionMinor { get; } - public int VersionPatch { get; } - public string VersionComment { get; } + private class CheckUpgradeDto + { + public CheckUpgradeDto(SemVersion version) + { + VersionMajor = version.Major; + VersionMinor = version.Minor; + VersionPatch = version.Patch; + VersionComment = version.Prerelease; } + + public int VersionMajor { get; } + public int VersionMinor { get; } + public int VersionPatch { get; } + public string VersionComment { get; } } } diff --git a/src/Umbraco.Core/Persistence/SqlExpressionExtensions.cs b/src/Umbraco.Core/Persistence/SqlExpressionExtensions.cs index 8eb27f1a81c5..5851f2862bd2 100644 --- a/src/Umbraco.Core/Persistence/SqlExpressionExtensions.cs +++ b/src/Umbraco.Core/Persistence/SqlExpressionExtensions.cs @@ -1,49 +1,47 @@ -using System.Collections.Generic; -using System.Linq; using System.Text.RegularExpressions; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Persistence +namespace Umbraco.Cms.Core.Persistence; + +/// +/// String extension methods used specifically to translate into SQL +/// +public static class SqlExpressionExtensions { /// - /// String extension methods used specifically to translate into SQL + /// Indicates whether two nullable values are equal, substituting a fallback value for nulls. /// - public static class SqlExpressionExtensions + /// The nullable type. + /// The value to compare. + /// The value to compare to. + /// The value to use when any value is null. + /// Do not use outside of Sql expressions. + // see usage in ExpressionVisitorBase + public static bool SqlNullableEquals(this T? value, T? other, T fallbackValue) + where T : struct => + (value ?? fallbackValue).Equals(other ?? fallbackValue); + + public static bool SqlIn(this IEnumerable collection, T item) => collection.Contains(item); + + public static bool SqlWildcard(this string str, string txt, TextColumnType columnType) { - /// - /// Indicates whether two nullable values are equal, substituting a fallback value for nulls. - /// - /// The nullable type. - /// The value to compare. - /// The value to compare to. - /// The value to use when any value is null. - /// Do not use outside of Sql expressions. - // see usage in ExpressionVisitorBase - public static bool SqlNullableEquals(this T? value, T? other, T fallbackValue) - where T : struct - { - return (value ?? fallbackValue).Equals(other ?? fallbackValue); - } - - public static bool SqlIn(this IEnumerable collection, T item) => collection.Contains(item); - - public static bool SqlWildcard(this string str, string txt, TextColumnType columnType) - { - var wildcardmatch = new Regex("^" + Regex.Escape(txt). - //deal with any wildcard chars % - Replace(@"\%", ".*") + "$"); - - return wildcardmatch.IsMatch(str); - } + var wildcardmatch = new Regex("^" + Regex.Escape(txt). + //deal with any wildcard chars % + Replace(@"\%", ".*") + "$"); + + return wildcardmatch.IsMatch(str); + } #pragma warning disable IDE0060 // Remove unused parameter - public static bool SqlContains(this string str, string txt, TextColumnType columnType) => str.InvariantContains(txt); + public static bool SqlContains(this string str, string txt, TextColumnType columnType) => + str.InvariantContains(txt); - public static bool SqlEquals(this string str, string txt, TextColumnType columnType) => str.InvariantEquals(txt); + public static bool SqlEquals(this string str, string txt, TextColumnType columnType) => str.InvariantEquals(txt); - public static bool SqlStartsWith(this string? str, string txt, TextColumnType columnType) => str?.InvariantStartsWith(txt) ?? false; + public static bool SqlStartsWith(this string? str, string txt, TextColumnType columnType) => + str?.InvariantStartsWith(txt) ?? false; - public static bool SqlEndsWith(this string str, string txt, TextColumnType columnType) => str.InvariantEndsWith(txt); + public static bool SqlEndsWith(this string str, string txt, TextColumnType columnType) => + str.InvariantEndsWith(txt); #pragma warning restore IDE0060 // Remove unused parameter - } } diff --git a/src/Umbraco.Core/Persistence/SqlExtensionsStatics.cs b/src/Umbraco.Core/Persistence/SqlExtensionsStatics.cs index d0f32fb97172..906bb4246d7e 100644 --- a/src/Umbraco.Core/Persistence/SqlExtensionsStatics.cs +++ b/src/Umbraco.Core/Persistence/SqlExtensionsStatics.cs @@ -1,45 +1,45 @@ -using System; +namespace Umbraco.Cms.Core.Persistence; -namespace Umbraco.Cms.Core.Persistence +/// +/// Provides a mean to express aliases in SELECT Sql statements. +/// +/// +/// +/// First register with using static Umbraco.Core.Persistence.NPocoSqlExtensions.Aliaser, +/// then use eg Sql{Foo}(x => Alias(x.Id, "id")). +/// +/// +public static class SqlExtensionsStatics { /// - /// Provides a mean to express aliases in SELECT Sql statements. + /// Aliases a field. /// - /// - /// First register with using static Umbraco.Core.Persistence.NPocoSqlExtensions.Aliaser, - /// then use eg Sql{Foo}(x => Alias(x.Id, "id")). - /// - public static class SqlExtensionsStatics - { - /// - /// Aliases a field. - /// - /// The field to alias. - /// The alias. - public static object? Alias(object? field, string alias) => field; + /// The field to alias. + /// The alias. + public static object? Alias(object? field, string alias) => field; - /// - /// Produces Sql text. - /// - /// The name of the field. - /// A function producing Sql text. - public static T? SqlText(string field, Func expr) => default; + /// + /// Produces Sql text. + /// + /// The name of the field. + /// A function producing Sql text. + public static T? SqlText(string field, Func expr) => default; - /// - /// Produces Sql text. - /// - /// The name of the first field. - /// The name of the second field. - /// A function producing Sql text. - public static T? SqlText(string field1, string field2, Func expr) => default; + /// + /// Produces Sql text. + /// + /// The name of the first field. + /// The name of the second field. + /// A function producing Sql text. + public static T? SqlText(string field1, string field2, Func expr) => default; - /// - /// Produces Sql text. - /// - /// The name of the first field. - /// The name of the second field. - /// The name of the third field. - /// A function producing Sql text. - public static T? SqlText(string field1, string field2, string field3, Func expr) => default; - } + /// + /// Produces Sql text. + /// + /// The name of the first field. + /// The name of the second field. + /// The name of the third field. + /// A function producing Sql text. + public static T? SqlText(string field1, string field2, string field3, + Func expr) => default; } diff --git a/src/Umbraco.Core/Persistence/TextColumnType.cs b/src/Umbraco.Core/Persistence/TextColumnType.cs index dc0b8d56bd1a..335f9c32d202 100644 --- a/src/Umbraco.Core/Persistence/TextColumnType.cs +++ b/src/Umbraco.Core/Persistence/TextColumnType.cs @@ -1,8 +1,7 @@ -namespace Umbraco.Cms.Core.Persistence +namespace Umbraco.Cms.Core.Persistence; + +public enum TextColumnType { - public enum TextColumnType - { - NVarchar, - NText - } + NVarchar, + NText } diff --git a/src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs b/src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs index c799a00df6ef..5c54f582f619 100644 --- a/src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs @@ -1,71 +1,65 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.PropertyEditors -{ - /// - /// The configuration object for the Block List editor - /// - public class BlockListConfiguration - { - [ConfigurationField("blocks", "Available Blocks", "views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.html", Description = "Define the available blocks.")] - public BlockConfiguration[] Blocks { get; set; } = null!; +namespace Umbraco.Cms.Core.PropertyEditors; - [DataContract] - public class BlockConfiguration - { +/// +/// The configuration object for the Block List editor +/// +public class BlockListConfiguration +{ + [ConfigurationField("blocks", "Available Blocks", + "views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.html", + Description = "Define the available blocks.")] + public BlockConfiguration[] Blocks { get; set; } = null!; - [DataMember(Name ="backgroundColor")] - public string? BackgroundColor { get; set; } + [ConfigurationField("validationLimit", "Amount", "numberrange", Description = "Set a required range of blocks")] + public NumberRange ValidationLimit { get; set; } = new(); - [DataMember(Name ="iconColor")] - public string? IconColor { get; set; } + [ConfigurationField("useLiveEditing", "Live editing mode", "boolean", + Description = + "Live editing in editor overlays for live updated custom views or labels using custom expression.")] + public bool UseLiveEditing { get; set; } - [DataMember(Name ="thumbnail")] - public string? Thumbnail { get; set; } + [ConfigurationField("useInlineEditingAsDefault", "Inline editing mode", "boolean", + Description = "Use the inline editor as the default block view.")] + public bool UseInlineEditingAsDefault { get; set; } - [DataMember(Name ="contentElementTypeKey")] - public Guid ContentElementTypeKey { get; set; } + [ConfigurationField("maxPropertyWidth", "Property editor width", "textstring", + Description = "optional css overwrite, example: 800px or 100%")] + public string? MaxPropertyWidth { get; set; } - [DataMember(Name ="settingsElementTypeKey")] - public Guid? SettingsElementTypeKey { get; set; } + [DataContract] + public class BlockConfiguration + { + [DataMember(Name = "backgroundColor")] public string? BackgroundColor { get; set; } - [DataMember(Name ="view")] - public string? View { get; set; } + [DataMember(Name = "iconColor")] public string? IconColor { get; set; } - [DataMember(Name ="stylesheet")] - public string? Stylesheet { get; set; } + [DataMember(Name = "thumbnail")] public string? Thumbnail { get; set; } - [DataMember(Name ="label")] - public string? Label { get; set; } + [DataMember(Name = "contentElementTypeKey")] + public Guid ContentElementTypeKey { get; set; } - [DataMember(Name ="editorSize")] - public string? EditorSize { get; set; } + [DataMember(Name = "settingsElementTypeKey")] + public Guid? SettingsElementTypeKey { get; set; } - [DataMember(Name ="forceHideContentEditorInOverlay")] - public bool ForceHideContentEditorInOverlay { get; set; } - } + [DataMember(Name = "view")] public string? View { get; set; } - [ConfigurationField("validationLimit", "Amount", "numberrange", Description = "Set a required range of blocks")] - public NumberRange ValidationLimit { get; set; } = new NumberRange(); + [DataMember(Name = "stylesheet")] public string? Stylesheet { get; set; } - [DataContract] - public class NumberRange - { - [DataMember(Name ="min")] - public int? Min { get; set; } + [DataMember(Name = "label")] public string? Label { get; set; } - [DataMember(Name ="max")] - public int? Max { get; set; } - } + [DataMember(Name = "editorSize")] public string? EditorSize { get; set; } - [ConfigurationField("useLiveEditing", "Live editing mode", "boolean", Description = "Live editing in editor overlays for live updated custom views or labels using custom expression.")] - public bool UseLiveEditing { get; set; } + [DataMember(Name = "forceHideContentEditorInOverlay")] + public bool ForceHideContentEditorInOverlay { get; set; } + } - [ConfigurationField("useInlineEditingAsDefault", "Inline editing mode", "boolean", Description = "Use the inline editor as the default block view.")] - public bool UseInlineEditingAsDefault { get; set; } + [DataContract] + public class NumberRange + { + [DataMember(Name = "min")] public int? Min { get; set; } - [ConfigurationField("maxPropertyWidth", "Property editor width", "textstring", Description = "optional css overwrite, example: 800px or 100%")] - public string? MaxPropertyWidth { get; set; } + [DataMember(Name = "max")] public int? Max { get; set; } } } diff --git a/src/Umbraco.Core/PropertyEditors/ColorPickerConfiguration.cs b/src/Umbraco.Core/PropertyEditors/ColorPickerConfiguration.cs index 80350bb350d8..3c3e648d1084 100644 --- a/src/Umbraco.Core/PropertyEditors/ColorPickerConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/ColorPickerConfiguration.cs @@ -1,11 +1,12 @@ -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration for the color picker value editor. +/// +public class ColorPickerConfiguration : ValueListConfiguration { - /// - /// Represents the configuration for the color picker value editor. - /// - public class ColorPickerConfiguration : ValueListConfiguration - { - [ConfigurationField("useLabel", "Include labels?", "boolean", Description = "Stores colors as a Json object containing both the color hex string and label, rather than just the hex string.")] - public bool UseLabel { get; set; } - } + [ConfigurationField("useLabel", "Include labels?", "boolean", + Description = + "Stores colors as a Json object containing both the color hex string and label, rather than just the hex string.")] + public bool UseLabel { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs index 89d19c5115bd..549ccb111d21 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs @@ -1,137 +1,150 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Serialization; -using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents a data type configuration editor. +/// +[DataContract] +public class ConfigurationEditor : IConfigurationEditor { + private IDictionary _defaultConfiguration; + + /// + /// Initializes a new instance of the class. + /// + public ConfigurationEditor() + { + Fields = new List(); + _defaultConfiguration = new Dictionary(); + } + + /// + /// Initializes a new instance of the class. + /// + protected ConfigurationEditor(List fields) + { + Fields = fields; + _defaultConfiguration = new Dictionary(); + } + /// - /// Represents a data type configuration editor. + /// Gets the fields. /// - [DataContract] - public class ConfigurationEditor : IConfigurationEditor + [DataMember(Name = "fields")] + public List Fields { get; } + + /// + [DataMember(Name = "defaultConfig")] + public virtual IDictionary DefaultConfiguration { - private IDictionary _defaultConfiguration; + get => _defaultConfiguration; + set => _defaultConfiguration = value; + } + + /// + public virtual object? DefaultConfigurationObject => DefaultConfiguration; + + /// + public virtual bool IsConfiguration(object obj) => obj is IDictionary; + - /// - /// Initializes a new instance of the class. - /// - public ConfigurationEditor() + /// + public virtual object FromDatabase(string? configurationJson, + IConfigurationEditorJsonSerializer configurationEditorJsonSerializer) + => string.IsNullOrWhiteSpace(configurationJson) + ? new Dictionary() + : configurationEditorJsonSerializer.Deserialize>(configurationJson)!; + + /// + public virtual object? FromConfigurationEditor(IDictionary? editorValues, object? configuration) + { + // by default, return the posted dictionary + // but only keep entries that have a non-null/empty value + // rest will fall back to default during ToConfigurationEditor() + + var keys = editorValues?.Where(x => + x.Value == null || (x.Value is string stringValue && string.IsNullOrWhiteSpace(stringValue))) + .Select(x => x.Key).ToList(); + + if (keys is not null) { - Fields = new List(); - _defaultConfiguration = new Dictionary(); + foreach (var key in keys) + { + editorValues?.Remove(key); + } } - /// - /// Initializes a new instance of the class. - /// - protected ConfigurationEditor(List fields) + return editorValues; + } + + /// + public virtual IDictionary ToConfigurationEditor(object? configuration) + { + // editors that do not override ToEditor/FromEditor have their configuration + // as a dictionary of and, by default, we merge their default + // configuration with their current configuration + + if (configuration == null) { - Fields = fields; - _defaultConfiguration = new Dictionary(); + configuration = new Dictionary(); } - /// - /// Gets the fields. - /// - [DataMember(Name = "fields")] - public List Fields { get; } - - /// - /// Gets a field by its property name. - /// - /// Can be used in constructors to add infos to a field that has been defined - /// by a property marked with the . - protected ConfigurationField Field(string name) - => Fields.First(x => x.PropertyName == name); - - /// - /// Gets the configuration as a typed object. - /// - public static TConfiguration? ConfigurationAs(object? obj) + if (!(configuration is IDictionary c)) { - if (obj == null) return default; - if (obj is TConfiguration configuration) return configuration; - throw new InvalidCastException( - $"Cannot cast configuration of type {obj.GetType().Name} to {typeof(TConfiguration).Name}."); + throw new ArgumentException( + $"Expecting a {typeof(Dictionary).Name} instance but got {configuration.GetType().Name}.", + nameof(configuration)); } - /// - /// Converts a configuration object into a serialized database value. - /// - public static string? ToDatabase(object? configuration, IConfigurationEditorJsonSerializer configurationEditorJsonSerializer) - => configuration == null ? null : configurationEditorJsonSerializer.Serialize(configuration); - - /// - [DataMember(Name = "defaultConfig")] - public virtual IDictionary DefaultConfiguration + // clone the default configuration, and apply the current configuration values + var d = new Dictionary(DefaultConfiguration); + foreach (var (key, value) in c) { - get => _defaultConfiguration; - set => _defaultConfiguration = value; + d[key] = value; } - /// - public virtual object? DefaultConfigurationObject => DefaultConfiguration; - - /// - public virtual bool IsConfiguration(object obj) => obj is IDictionary; + return d; + } + /// + public virtual IDictionary ToValueEditor(object? configuration) + => ToConfigurationEditor(configuration); - /// - public virtual object FromDatabase(string? configurationJson, IConfigurationEditorJsonSerializer configurationEditorJsonSerializer) - => string.IsNullOrWhiteSpace(configurationJson) - ? new Dictionary() - : configurationEditorJsonSerializer.Deserialize>(configurationJson)!; + /// + /// Gets a field by its property name. + /// + /// + /// Can be used in constructors to add infos to a field that has been defined + /// by a property marked with the . + /// + protected ConfigurationField Field(string name) + => Fields.First(x => x.PropertyName == name); - /// - public virtual object? FromConfigurationEditor(IDictionary? editorValues, object? configuration) + /// + /// Gets the configuration as a typed object. + /// + public static TConfiguration? ConfigurationAs(object? obj) + { + if (obj == null) { - // by default, return the posted dictionary - // but only keep entries that have a non-null/empty value - // rest will fall back to default during ToConfigurationEditor() - - var keys = editorValues?.Where(x => - x.Value == null || x.Value is string stringValue && string.IsNullOrWhiteSpace(stringValue)) - .Select(x => x.Key).ToList(); - - if (keys is not null) - { - foreach (var key in keys) - { - editorValues?.Remove(key); - } - } - - return editorValues; + return default; } - /// - public virtual IDictionary ToConfigurationEditor(object? configuration) + if (obj is TConfiguration configuration) { - // editors that do not override ToEditor/FromEditor have their configuration - // as a dictionary of and, by default, we merge their default - // configuration with their current configuration - - if (configuration == null) - configuration = new Dictionary(); - - if (!(configuration is IDictionary c)) - throw new ArgumentException( - $"Expecting a {typeof(Dictionary).Name} instance but got {configuration.GetType().Name}.", - nameof(configuration)); - - // clone the default configuration, and apply the current configuration values - var d = new Dictionary(DefaultConfiguration); - foreach (var (key, value) in c) - d[key] = value; - return d; + return configuration; } - /// - public virtual IDictionary ToValueEditor(object? configuration) - => ToConfigurationEditor(configuration); - + throw new InvalidCastException( + $"Cannot cast configuration of type {obj.GetType().Name} to {typeof(TConfiguration).Name}."); } + + /// + /// Converts a configuration object into a serialized database value. + /// + public static string? ToDatabase(object? configuration, + IConfigurationEditorJsonSerializer configurationEditorJsonSerializer) + => configuration == null ? null : configurationEditorJsonSerializer.Serialize(configuration); } diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs index fa2427a048aa..9f6e36ce7802 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs @@ -1,8 +1,6 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Composing; @@ -12,151 +10,174 @@ using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents a data type configuration editor with a typed configuration. +/// +public abstract class ConfigurationEditor : ConfigurationEditor + where TConfiguration : new() { - /// - /// Represents a data type configuration editor with a typed configuration. - /// - public abstract class ConfigurationEditor : ConfigurationEditor - where TConfiguration : new() - { - private readonly IEditorConfigurationParser _editorConfigurationParser; + private readonly IEditorConfigurationParser _editorConfigurationParser; - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - protected ConfigurationEditor(IIOHelper ioHelper) + // Scheduled for removal in v12 + [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + protected ConfigurationEditor(IIOHelper ioHelper) : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) - { - } + { + } - /// - /// Initializes a new instance of the class. - /// - protected ConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) + /// + /// Initializes a new instance of the class. + /// + protected ConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base(DiscoverFields(ioHelper)) => - _editorConfigurationParser = editorConfigurationParser; + _editorConfigurationParser = editorConfigurationParser; - /// - /// Discovers fields from configuration properties marked with the field attribute. - /// - private static List DiscoverFields(IIOHelper ioHelper) - { - var fields = new List(); - var properties = TypeHelper.CachedDiscoverableProperties(typeof(TConfiguration)); + /// + public override IDictionary DefaultConfiguration => + ToConfigurationEditor(DefaultConfigurationObject); + + /// + public override object DefaultConfigurationObject => new TConfiguration(); + + /// + /// Discovers fields from configuration properties marked with the field attribute. + /// + private static List DiscoverFields(IIOHelper ioHelper) + { + var fields = new List(); + PropertyInfo[] properties = TypeHelper.CachedDiscoverableProperties(typeof(TConfiguration)); - foreach (var property in properties) + foreach (PropertyInfo property in properties) + { + ConfigurationFieldAttribute attribute = property.GetCustomAttribute(false); + if (attribute == null) { - var attribute = property.GetCustomAttribute(false); - if (attribute == null) continue; + continue; + } - ConfigurationField field; + ConfigurationField field; - var attributeView = ioHelper.ResolveRelativeOrVirtualUrl(attribute.View); - // if the field does not have its own type, use the base type - if (attribute.Type == null) - { - field = new ConfigurationField - { - // if the key is empty then use the property name - Key = string.IsNullOrWhiteSpace(attribute.Key) ? property.Name : attribute.Key, - Name = attribute.Name, - PropertyName = property.Name, - PropertyType = property.PropertyType, - Description = attribute.Description, - HideLabel = attribute.HideLabel, - View = attributeView - }; - - fields.Add(field); - continue; - } - - // if the field has its own type, instantiate it - try - { - field = (ConfigurationField) Activator.CreateInstance(attribute.Type)!; - } - catch (Exception ex) + var attributeView = ioHelper.ResolveRelativeOrVirtualUrl(attribute.View); + // if the field does not have its own type, use the base type + if (attribute.Type == null) + { + field = new ConfigurationField { - throw new Exception($"Failed to create an instance of type \"{attribute.Type}\" for property \"{property.Name}\" of configuration \"{typeof(TConfiguration).Name}\" (see inner exception).", ex); - } + // if the key is empty then use the property name + Key = string.IsNullOrWhiteSpace(attribute.Key) ? property.Name : attribute.Key, + Name = attribute.Name, + PropertyName = property.Name, + PropertyType = property.PropertyType, + Description = attribute.Description, + HideLabel = attribute.HideLabel, + View = attributeView + }; - // then add it, and overwrite values if they are assigned in the attribute fields.Add(field); + continue; + } + + // if the field has its own type, instantiate it + try + { + field = (ConfigurationField)Activator.CreateInstance(attribute.Type)!; + } + catch (Exception ex) + { + throw new Exception( + $"Failed to create an instance of type \"{attribute.Type}\" for property \"{property.Name}\" of configuration \"{typeof(TConfiguration).Name}\" (see inner exception).", + ex); + } - field.PropertyName = property.Name; - field.PropertyType = property.PropertyType; + // then add it, and overwrite values if they are assigned in the attribute + fields.Add(field); - if (!string.IsNullOrWhiteSpace(attribute.Key)) - field.Key = attribute.Key; + field.PropertyName = property.Name; + field.PropertyType = property.PropertyType; - // if the key is still empty then use the property name - if (string.IsNullOrWhiteSpace(field.Key)) - field.Key = property.Name; + if (!string.IsNullOrWhiteSpace(attribute.Key)) + { + field.Key = attribute.Key; + } - if (!string.IsNullOrWhiteSpace(attribute.Name)) - field.Name = attribute.Name; + // if the key is still empty then use the property name + if (string.IsNullOrWhiteSpace(field.Key)) + { + field.Key = property.Name; + } - if (!string.IsNullOrWhiteSpace(attribute.View)) - field.View = attributeView; + if (!string.IsNullOrWhiteSpace(attribute.Name)) + { + field.Name = attribute.Name; + } - if (!string.IsNullOrWhiteSpace(attribute.Description)) - field.Description = attribute.Description; + if (!string.IsNullOrWhiteSpace(attribute.View)) + { + field.View = attributeView; + } - if (attribute.HideLabelSettable.HasValue) - field.HideLabel = attribute.HideLabel; + if (!string.IsNullOrWhiteSpace(attribute.Description)) + { + field.Description = attribute.Description; } - return fields; + if (attribute.HideLabelSettable.HasValue) + { + field.HideLabel = attribute.HideLabel; + } } - /// - public override IDictionary DefaultConfiguration => ToConfigurationEditor(DefaultConfigurationObject); - - /// - public override object DefaultConfigurationObject => new TConfiguration(); + return fields; + } - /// - public override bool IsConfiguration(object obj) - => obj is TConfiguration; + /// + public override bool IsConfiguration(object obj) + => obj is TConfiguration; - /// - public override object FromDatabase(string? configuration, IConfigurationEditorJsonSerializer configurationEditorJsonSerializer) + /// + public override object FromDatabase(string? configuration, + IConfigurationEditorJsonSerializer configurationEditorJsonSerializer) + { + try { - try - { - if (string.IsNullOrWhiteSpace(configuration)) return new TConfiguration(); - return configurationEditorJsonSerializer.Deserialize(configuration)!; - } - catch (Exception e) + if (string.IsNullOrWhiteSpace(configuration)) { - throw new InvalidOperationException($"Failed to parse configuration \"{configuration}\" as \"{typeof(TConfiguration).Name}\" (see inner exception).", e); + return new TConfiguration(); } - } - /// - public sealed override object? FromConfigurationEditor(IDictionary? editorValues, object? configuration) + return configurationEditorJsonSerializer.Deserialize(configuration)!; + } + catch (Exception e) { - return FromConfigurationEditor(editorValues, (TConfiguration?) configuration); + throw new InvalidOperationException( + $"Failed to parse configuration \"{configuration}\" as \"{typeof(TConfiguration).Name}\" (see inner exception).", + e); } + } - /// - /// Converts the configuration posted by the editor. - /// - /// The configuration object posted by the editor. - /// The current configuration object. - public virtual TConfiguration? FromConfigurationEditor(IDictionary? editorValues, TConfiguration? configuration) => _editorConfigurationParser.ParseFromConfigurationEditor(editorValues, Fields); + /// + public sealed override object? FromConfigurationEditor(IDictionary? editorValues, + object? configuration) => FromConfigurationEditor(editorValues, (TConfiguration?)configuration); - /// - public sealed override IDictionary ToConfigurationEditor(object? configuration) - { - return ToConfigurationEditor((TConfiguration?) configuration); - } + /// + /// Converts the configuration posted by the editor. + /// + /// The configuration object posted by the editor. + /// The current configuration object. + public virtual TConfiguration? FromConfigurationEditor(IDictionary? editorValues, + TConfiguration? configuration) => + _editorConfigurationParser.ParseFromConfigurationEditor(editorValues, Fields); - /// - /// Converts configuration values to values for the editor. - /// - /// The configuration. - public virtual Dictionary ToConfigurationEditor(TConfiguration? configuration) => _editorConfigurationParser.ParseToConfigurationEditor(configuration); - } + /// + public sealed override IDictionary ToConfigurationEditor(object? configuration) => + ToConfigurationEditor((TConfiguration?)configuration); + + /// + /// Converts configuration values to values for the editor. + /// + /// The configuration. + public virtual Dictionary ToConfigurationEditor(TConfiguration? configuration) => + _editorConfigurationParser.ParseToConfigurationEditor(configuration); } diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationField.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationField.cs index 0e679f9dc14e..ea08b87c41cf 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationField.cs +++ b/src/Umbraco.Core/PropertyEditors/ConfigurationField.cs @@ -1,106 +1,109 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents a datatype configuration field for editing. +/// +[DataContract] +public class ConfigurationField { + private string? _view; + + /// + /// Initializes a new instance of the class. + /// + public ConfigurationField() + : this(new List()) + { + } + + /// + /// Initializes a new instance of the class. + /// + public ConfigurationField(params IValueValidator[] validators) + : this(validators.ToList()) + { + } + /// - /// Represents a datatype configuration field for editing. + /// Initializes a new instance of the class. /// - [DataContract] - public class ConfigurationField + private ConfigurationField(List validators) { - private string? _view; - - /// - /// Initializes a new instance of the class. - /// - public ConfigurationField() - : this(new List()) - { } - - /// - /// Initializes a new instance of the class. - /// - public ConfigurationField(params IValueValidator[] validators) - : this(validators.ToList()) - { } - - /// - /// Initializes a new instance of the class. - /// - private ConfigurationField(List validators) + Validators = validators; + Config = new Dictionary(); + + // fill details from attribute, if any + ConfigurationFieldAttribute attribute = GetType().GetCustomAttribute(false); + if (attribute is null) { - Validators = validators; - Config = new Dictionary(); - - // fill details from attribute, if any - var attribute = GetType().GetCustomAttribute(false); - if (attribute is null) return; - - Name = attribute.Name; - Description = attribute.Description; - HideLabel = attribute.HideLabel; - Key = attribute.Key; - View = attribute.View; + return; } - /// - /// Gets or sets the key of the field. - /// - [DataMember(Name = "key", IsRequired = true)] - public string Key { get; set; } = null!; - - /// - /// Gets or sets the name of the field. - /// - [DataMember(Name = "label", IsRequired = true)] - public string? Name { get; set; } - - /// - /// Gets or sets the property name of the field. - /// - public string? PropertyName { get; set; } - - /// - /// Gets or sets the property CLR type of the field. - /// - public Type? PropertyType { get; set; } - - /// - /// Gets or sets the description of the field. - /// - [DataMember(Name = "description")] - public string? Description { get; set; } - - /// - /// Gets or sets a value indicating whether to hide the label of the field. - /// - [DataMember(Name = "hideLabel")] - public bool HideLabel { get; set; } - - /// - /// Gets or sets the view to used in the editor. - /// - /// - /// Can be the full virtual path, or the relative path to the Umbraco folder, - /// or a simple view name which will map to ~/Views/PreValueEditors/{view}.html. - /// - [DataMember(Name = "view", IsRequired = true)] - public string? View { get; set; } - - /// - /// Gets the validators of the field. - /// - [DataMember(Name = "validation")] - public List Validators { get; } - - /// - /// Gets or sets extra configuration properties for the editor. - /// - [DataMember(Name = "config")] - public IDictionary Config { get; set; } + Name = attribute.Name; + Description = attribute.Description; + HideLabel = attribute.HideLabel; + Key = attribute.Key; + View = attribute.View; } + + /// + /// Gets or sets the key of the field. + /// + [DataMember(Name = "key", IsRequired = true)] + public string Key { get; set; } = null!; + + /// + /// Gets or sets the name of the field. + /// + [DataMember(Name = "label", IsRequired = true)] + public string? Name { get; set; } + + /// + /// Gets or sets the property name of the field. + /// + public string? PropertyName { get; set; } + + /// + /// Gets or sets the property CLR type of the field. + /// + public Type? PropertyType { get; set; } + + /// + /// Gets or sets the description of the field. + /// + [DataMember(Name = "description")] + public string? Description { get; set; } + + /// + /// Gets or sets a value indicating whether to hide the label of the field. + /// + [DataMember(Name = "hideLabel")] + public bool HideLabel { get; set; } + + /// + /// Gets or sets the view to used in the editor. + /// + /// + /// + /// Can be the full virtual path, or the relative path to the Umbraco folder, + /// or a simple view name which will map to ~/Views/PreValueEditors/{view}.html. + /// + /// + [DataMember(Name = "view", IsRequired = true)] + public string? View { get; set; } + + /// + /// Gets the validators of the field. + /// + [DataMember(Name = "validation")] + public List Validators { get; } + + /// + /// Gets or sets extra configuration properties for the editor. + /// + [DataMember(Name = "config")] + public IDictionary Config { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationFieldAttribute.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationFieldAttribute.cs index 79e9655e2510..c607c23c9ddf 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationFieldAttribute.cs +++ b/src/Umbraco.Core/PropertyEditors/ConfigurationFieldAttribute.cs @@ -1,117 +1,165 @@ -using System; +namespace Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.PropertyEditors +/// +/// Marks a ConfigurationEditor property as a configuration field, and a class as a configuration field type. +/// +/// Properties marked with this attribute are discovered as fields. +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)] +public class ConfigurationFieldAttribute : Attribute { + private Type? _type; + /// - /// Marks a ConfigurationEditor property as a configuration field, and a class as a configuration field type. + /// Initializes a new instance of the class. /// - /// Properties marked with this attribute are discovered as fields. - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)] - public class ConfigurationFieldAttribute : Attribute + public ConfigurationFieldAttribute(Type type) { - private Type? _type; + Type = type; + Key = string.Empty; + } + + /// + /// Initializes a new instance of the class. + /// + /// The unique identifier of the field. + /// The friendly name of the field. + /// The view to use to render the field editor. + public ConfigurationFieldAttribute(string key, string name, string view) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (string.IsNullOrWhiteSpace(key)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(key)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } - /// - /// Initializes a new instance of the class. - /// - public ConfigurationFieldAttribute(Type type) + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(name)); + } + + if (view == null) + { + throw new ArgumentNullException(nameof(view)); + } + + if (string.IsNullOrWhiteSpace(view)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(view)); + } + + Key = key; + Name = name; + View = view; + } + + /// + /// Initializes a new instance of the class. + /// + /// The friendly name of the field. + /// The view to use to render the field editor. + /// + /// When no key is specified, the will derive a key + /// from the name of the property marked with this attribute. + /// + public ConfigurationFieldAttribute(string name, string view) + { + if (name == null) { - Type = type; - Key = string.Empty; + throw new ArgumentNullException(nameof(name)); } - /// - /// Initializes a new instance of the class. - /// - /// The unique identifier of the field. - /// The friendly name of the field. - /// The view to use to render the field editor. - public ConfigurationFieldAttribute(string key, string name, string view) + if (string.IsNullOrWhiteSpace(name)) { - if (key == null) throw new ArgumentNullException(nameof(key)); - if (string.IsNullOrWhiteSpace(key)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(key)); - if (name == null) throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); - if (view == null) throw new ArgumentNullException(nameof(view)); - if (string.IsNullOrWhiteSpace(view)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(view)); - - Key = key; - Name = name; - View = view; + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(name)); } - /// - /// Initializes a new instance of the class. - /// - /// The friendly name of the field. - /// The view to use to render the field editor. - /// When no key is specified, the will derive a key - /// from the name of the property marked with this attribute. - public ConfigurationFieldAttribute(string name, string view) + if (view == null) { - if (name == null) throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); - if (view == null) throw new ArgumentNullException(nameof(view)); - if (string.IsNullOrWhiteSpace(view)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(view)); - - Name = name; - View = view; - Key = string.Empty; + throw new ArgumentNullException(nameof(view)); } - /// - /// Gets or sets the key of the field. - /// - /// When null or empty, the should derive a key - /// from the name of the property marked with this attribute. - public string Key { get; } - - /// - /// Gets the friendly name of the field. - /// - public string? Name { get; } - - /// - /// Gets or sets the view to use to render the field editor. - /// - public string? View { get; } - - /// - /// Gets or sets the description of the field. - /// - public string? Description { get; set; } - - /// - /// Gets or sets a value indicating whether the field editor should be displayed without its label. - /// - public bool HideLabel + if (string.IsNullOrWhiteSpace(view)) { - get => HideLabelSettable.ValueOrDefault(false); - set => HideLabelSettable.Set(value); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(view)); } - /// - /// Gets the settable underlying . - /// - public Settable HideLabelSettable { get; } = new Settable(); - - /// - /// Gets or sets the type of the field. - /// - /// - /// By default, fields are created as instances, - /// unless specified otherwise through this property. - /// The specified type must inherit from . - /// - public Type? Type + Name = name; + View = view; + Key = string.Empty; + } + + /// + /// Gets or sets the key of the field. + /// + /// + /// When null or empty, the should derive a key + /// from the name of the property marked with this attribute. + /// + public string Key { get; } + + /// + /// Gets the friendly name of the field. + /// + public string? Name { get; } + + /// + /// Gets or sets the view to use to render the field editor. + /// + public string? View { get; } + + /// + /// Gets or sets the description of the field. + /// + public string? Description { get; set; } + + /// + /// Gets or sets a value indicating whether the field editor should be displayed without its label. + /// + public bool HideLabel + { + get => HideLabelSettable.ValueOrDefault(false); + set => HideLabelSettable.Set(value); + } + + /// + /// Gets the settable underlying . + /// + public Settable HideLabelSettable { get; } = new(); + + /// + /// Gets or sets the type of the field. + /// + /// + /// + /// By default, fields are created as instances, + /// unless specified otherwise through this property. + /// + /// The specified type must inherit from . + /// + public Type? Type + { + get => _type; + set { - get => _type; - set + if (!typeof(ConfigurationField).IsAssignableFrom(value)) { - if (!typeof(ConfigurationField).IsAssignableFrom(value)) - throw new ArgumentException("Type must inherit from ConfigurationField.", nameof(value)); - _type = value; + throw new ArgumentException("Type must inherit from ConfigurationField.", nameof(value)); } + + _type = value; } } } diff --git a/src/Umbraco.Core/PropertyEditors/ContentPickerConfiguration.cs b/src/Umbraco.Core/PropertyEditors/ContentPickerConfiguration.cs index 555d6f841881..0280b4686e0d 100644 --- a/src/Umbraco.Core/PropertyEditors/ContentPickerConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/ContentPickerConfiguration.cs @@ -1,16 +1,15 @@ -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +public class ContentPickerConfiguration : IIgnoreUserStartNodesConfig { - public class ContentPickerConfiguration : IIgnoreUserStartNodesConfig - { - [ConfigurationField("showOpenButton", "Show open button", "boolean", Description = "Opens the node in a dialog")] - public bool ShowOpenButton { get; set; } + [ConfigurationField("showOpenButton", "Show open button", "boolean", Description = "Opens the node in a dialog")] + public bool ShowOpenButton { get; set; } - [ConfigurationField("startNodeId", "Start node", "treepicker")] // + config in configuration editor ctor - public Udi? StartNodeId { get; set; } + [ConfigurationField("startNodeId", "Start node", "treepicker")] // + config in configuration editor ctor + public Udi? StartNodeId { get; set; } - [ConfigurationField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, - "Ignore User Start Nodes", "boolean", - Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] - public bool IgnoreUserStartNodes { get; set; } - } + [ConfigurationField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, + "Ignore User Start Nodes", "boolean", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] + public bool IgnoreUserStartNodes { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/ContentPickerConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/ContentPickerConfigurationEditor.cs index 4932030db28a..c6de028ada2c 100644 --- a/src/Umbraco.Core/PropertyEditors/ContentPickerConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ContentPickerConfigurationEditor.cs @@ -1,38 +1,32 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +internal class ContentPickerConfigurationEditor : ConfigurationEditor { - internal class ContentPickerConfigurationEditor : ConfigurationEditor - { - public ContentPickerConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) - { - // configure fields - // this is not part of ContentPickerConfiguration, - // but is required to configure the UI editor (when editing the configuration) - Field(nameof(ContentPickerConfiguration.StartNodeId)) - .Config = new Dictionary { { "idType", "udi" } }; - } + public ContentPickerConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : + base(ioHelper, editorConfigurationParser) => + // configure fields + // this is not part of ContentPickerConfiguration, + // but is required to configure the UI editor (when editing the configuration) + Field(nameof(ContentPickerConfiguration.StartNodeId)) + .Config = new Dictionary {{"idType", "udi"}}; - public override IDictionary ToValueEditor(object? configuration) - { - // get the configuration fields - var d = base.ToValueEditor(configuration); + public override IDictionary ToValueEditor(object? configuration) + { + // get the configuration fields + IDictionary d = base.ToValueEditor(configuration); - // add extra fields - // not part of ContentPickerConfiguration but used to configure the UI editor - d["showEditButton"] = false; - d["showPathOnHover"] = false; - d["idType"] = "udi"; + // add extra fields + // not part of ContentPickerConfiguration but used to configure the UI editor + d["showEditButton"] = false; + d["showPathOnHover"] = false; + d["idType"] = "udi"; - return d; - } + return d; } } diff --git a/src/Umbraco.Core/PropertyEditors/ContentPickerPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/ContentPickerPropertyEditor.cs index 5ca0564e693d..ad0c0710ddb4 100644 --- a/src/Umbraco.Core/PropertyEditors/ContentPickerPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ContentPickerPropertyEditor.cs @@ -1,11 +1,7 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Editors; @@ -14,69 +10,73 @@ using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Content property editor that stores UDI +/// +[DataEditor( + Constants.PropertyEditors.Aliases.ContentPicker, + EditorType.PropertyValue | EditorType.MacroParameter, + "Content Picker", + "contentpicker", + ValueType = ValueTypes.String, + Group = Constants.PropertyEditors.Groups.Pickers)] +public class ContentPickerPropertyEditor : DataEditor { - /// - /// Content property editor that stores UDI - /// - [DataEditor( - Constants.PropertyEditors.Aliases.ContentPicker, - EditorType.PropertyValue | EditorType.MacroParameter, - "Content Picker", - "contentpicker", - ValueType = ValueTypes.String, - Group = Constants.PropertyEditors.Groups.Pickers)] - public class ContentPickerPropertyEditor : DataEditor + private readonly IEditorConfigurationParser _editorConfigurationParser; + private readonly IIOHelper _ioHelper; + + // Scheduled for removal in v12 + [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + public ContentPickerPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory, + IIOHelper ioHelper) + : this(dataValueEditorFactory, ioHelper, + StaticServiceProvider.Instance.GetRequiredService()) { - private readonly IIOHelper _ioHelper; - private readonly IEditorConfigurationParser _editorConfigurationParser; + } - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public ContentPickerPropertyEditor( - IDataValueEditorFactory dataValueEditorFactory, - IIOHelper ioHelper) - : this(dataValueEditorFactory, ioHelper, StaticServiceProvider.Instance.GetRequiredService()) - { - } + public ContentPickerPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory, + IIOHelper ioHelper, + IEditorConfigurationParser editorConfigurationParser) + : base(dataValueEditorFactory) + { + _ioHelper = ioHelper; + _editorConfigurationParser = editorConfigurationParser; + } + + protected override IConfigurationEditor CreateConfigurationEditor() => + new ContentPickerConfigurationEditor(_ioHelper, _editorConfigurationParser); - public ContentPickerPropertyEditor( - IDataValueEditorFactory dataValueEditorFactory, + protected override IDataValueEditor CreateValueEditor() => + DataValueEditorFactory.Create(Attribute!); + + internal class ContentPickerPropertyValueEditor : DataValueEditor, IDataValueReference + { + public ContentPickerPropertyValueEditor( + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, IIOHelper ioHelper, - IEditorConfigurationParser editorConfigurationParser) - : base(dataValueEditorFactory) + DataEditorAttribute attribute) + : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) { - _ioHelper = ioHelper; - _editorConfigurationParser = editorConfigurationParser; } - protected override IConfigurationEditor CreateConfigurationEditor() + public IEnumerable GetReferences(object? value) { - return new ContentPickerConfigurationEditor(_ioHelper, _editorConfigurationParser); - } - - protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create(Attribute!); + var asString = value is string str ? str : value?.ToString(); - internal class ContentPickerPropertyValueEditor : DataValueEditor, IDataValueReference - { - public ContentPickerPropertyValueEditor( - ILocalizedTextService localizedTextService, - IShortStringHelper shortStringHelper, - IJsonSerializer jsonSerializer, - IIOHelper ioHelper, - DataEditorAttribute attribute) - : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) + if (string.IsNullOrEmpty(asString)) { + yield break; } - public IEnumerable GetReferences(object? value) + if (UdiParser.TryParse(asString, out Udi udi)) { - var asString = value is string str ? str : value?.ToString(); - - if (string.IsNullOrEmpty(asString)) yield break; - - if (UdiParser.TryParse(asString, out var udi)) - yield return new UmbracoEntityReference(udi); + yield return new UmbracoEntityReference(udi); } } } diff --git a/src/Umbraco.Core/PropertyEditors/DataEditor.cs b/src/Umbraco.Core/PropertyEditors/DataEditor.cs index 5619a1bb87ba..327a4ae90445 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditor.cs @@ -1,201 +1,219 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; +using System.Diagnostics; using System.Runtime.Serialization; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Models; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents a data editor. +/// +/// +/// +/// Editors can be deserialized from e.g. manifests, which is. why the class is not abstract, +/// the json serialization attributes are required, and the properties have an internal setter. +/// +/// +[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "(),nq}")] +[HideFromTypeFinder] +[DataContract] +public class DataEditor : IDataEditor { + private IDictionary? _defaultConfiguration; + + /// + /// Initializes a new instance of the class. + /// + public DataEditor(IDataValueEditorFactory dataValueEditorFactory, EditorType type = EditorType.PropertyValue) + { + // defaults + DataValueEditorFactory = dataValueEditorFactory; + Type = type; + Icon = Constants.Icons.PropertyEditor; + Group = Constants.PropertyEditors.Groups.Common; + + // assign properties based on the attribute, if it is found + Attribute = GetType().GetCustomAttribute(false); + if (Attribute == null) + { + Alias = string.Empty; + Name = string.Empty; + return; + } + + Alias = Attribute.Alias; + Type = Attribute.Type; + Name = Attribute.Name; + Icon = Attribute.Icon; + Group = Attribute.Group; + IsDeprecated = Attribute.IsDeprecated; + } + + /// + /// Gets the editor attribute. + /// + protected DataEditorAttribute? Attribute { get; } + + protected IDataValueEditorFactory DataValueEditorFactory { get; } + /// - /// Represents a data editor. + /// Gets or sets an explicit value editor. /// + /// Used for manifest data editors. + [DataMember(Name = "editor")] + public IDataValueEditor? ExplicitValueEditor { get; set; } + + /// + /// Gets or sets an explicit configuration editor. + /// + /// Used for manifest data editors. + [DataMember(Name = "config")] + public IConfigurationEditor? ExplicitConfigurationEditor { get; set; } + + /// + [DataMember(Name = "alias", IsRequired = true)] + public string Alias { get; set; } + + /// + [IgnoreDataMember] + public EditorType Type { get; } + + /// + [DataMember(Name = "name", IsRequired = true)] + public string Name { get; internal set; } + + /// + [DataMember(Name = "icon")] + public string Icon { get; internal set; } + + /// + [DataMember(Name = "group")] + public string Group { get; internal set; } + + /// + [IgnoreDataMember] + public bool IsDeprecated { get; } + + /// /// - /// Editors can be deserialized from e.g. manifests, which is. why the class is not abstract, - /// the json serialization attributes are required, and the properties have an internal setter. + /// + /// If an explicit value editor has been assigned, then this explicit + /// instance is returned. Otherwise, a new instance is created by CreateValueEditor. + /// + /// + /// The instance created by CreateValueEditor is not cached, i.e. + /// a new instance is created each time the property value is retrieved. The + /// property editor is a singleton, and the value editor cannot be a singleton + /// since it depends on the datatype configuration. + /// + /// + /// Technically, it could be cached by datatype but let's keep things + /// simple enough for now. + /// /// - [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "(),nq}")] - [HideFromTypeFinder] - [DataContract] - public class DataEditor : IDataEditor - { - private IDictionary? _defaultConfiguration; + // TODO: point of that one? shouldn't we always configure? + public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? CreateValueEditor(); - /// - /// Initializes a new instance of the class. - /// - public DataEditor(IDataValueEditorFactory dataValueEditorFactory, EditorType type = EditorType.PropertyValue) + /// + /// + /// + /// If an explicit value editor has been assigned, then this explicit + /// instance is returned. Otherwise, a new instance is created by CreateValueEditor, + /// and configured with the configuration. + /// + /// + /// The instance created by CreateValueEditor is not cached, i.e. + /// a new instance is created each time the property value is retrieved. The + /// property editor is a singleton, and the value editor cannot be a singleton + /// since it depends on the datatype configuration. + /// + /// + /// Technically, it could be cached by datatype but let's keep things + /// simple enough for now. + /// + /// + public virtual IDataValueEditor GetValueEditor(object? configuration) + { + // if an explicit value editor has been set (by the manifest parser) + // then return it, and ignore the configuration, which is going to be + // empty anyways + if (ExplicitValueEditor != null) { - - // defaults - DataValueEditorFactory = dataValueEditorFactory; - Type = type; - Icon = Constants.Icons.PropertyEditor; - Group = Constants.PropertyEditors.Groups.Common; - - // assign properties based on the attribute, if it is found - Attribute = GetType().GetCustomAttribute(false); - if (Attribute == null) - { - Alias = string.Empty; - Name = string.Empty; - return; - } - - Alias = Attribute.Alias; - Type = Attribute.Type; - Name = Attribute.Name; - Icon = Attribute.Icon; - Group = Attribute.Group; - IsDeprecated = Attribute.IsDeprecated; + return ExplicitValueEditor; } - /// - /// Gets the editor attribute. - /// - protected DataEditorAttribute? Attribute { get; } - - /// - [DataMember(Name = "alias", IsRequired = true)] - public string Alias { get; set; } - - protected IDataValueEditorFactory DataValueEditorFactory { get; } - - /// - [IgnoreDataMember] - public EditorType Type { get; } - - /// - [DataMember(Name = "name", IsRequired = true)] - public string Name { get; internal set; } - - /// - [DataMember(Name = "icon")] - public string Icon { get; internal set; } - - /// - [DataMember(Name = "group")] - public string Group { get; internal set; } - - /// - [IgnoreDataMember] - public bool IsDeprecated { get; } - - /// - /// - /// If an explicit value editor has been assigned, then this explicit - /// instance is returned. Otherwise, a new instance is created by CreateValueEditor. - /// The instance created by CreateValueEditor is not cached, i.e. - /// a new instance is created each time the property value is retrieved. The - /// property editor is a singleton, and the value editor cannot be a singleton - /// since it depends on the datatype configuration. - /// Technically, it could be cached by datatype but let's keep things - /// simple enough for now. - /// - // TODO: point of that one? shouldn't we always configure? - public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? CreateValueEditor(); - - /// - /// - /// If an explicit value editor has been assigned, then this explicit - /// instance is returned. Otherwise, a new instance is created by CreateValueEditor, - /// and configured with the configuration. - /// The instance created by CreateValueEditor is not cached, i.e. - /// a new instance is created each time the property value is retrieved. The - /// property editor is a singleton, and the value editor cannot be a singleton - /// since it depends on the datatype configuration. - /// Technically, it could be cached by datatype but let's keep things - /// simple enough for now. - /// - public virtual IDataValueEditor GetValueEditor(object? configuration) + IDataValueEditor editor = CreateValueEditor(); + if (configuration is not null) { - // if an explicit value editor has been set (by the manifest parser) - // then return it, and ignore the configuration, which is going to be - // empty anyways - if (ExplicitValueEditor != null) - return ExplicitValueEditor; - - var editor = CreateValueEditor(); - if (configuration is not null) - { - ((DataValueEditor)editor).Configuration = configuration; // TODO: casting is bad - } - - return editor; + ((DataValueEditor)editor).Configuration = configuration; // TODO: casting is bad } - /// - /// Gets or sets an explicit value editor. - /// - /// Used for manifest data editors. - [DataMember(Name = "editor")] - public IDataValueEditor? ExplicitValueEditor { get; set; } - - /// - /// - /// If an explicit configuration editor has been assigned, then this explicit - /// instance is returned. Otherwise, a new instance is created by CreateConfigurationEditor. - /// The instance created by CreateConfigurationEditor is not cached, i.e. - /// a new instance is created each time. The property editor is a singleton, and although the - /// configuration editor could technically be a singleton too, we'd rather not keep configuration editor - /// cached. - /// - public IConfigurationEditor GetConfigurationEditor() => ExplicitConfigurationEditor ?? CreateConfigurationEditor(); - - /// - /// Gets or sets an explicit configuration editor. - /// - /// Used for manifest data editors. - [DataMember(Name = "config")] - public IConfigurationEditor? ExplicitConfigurationEditor { get; set; } - - /// - [DataMember(Name = "defaultConfig")] - public IDictionary DefaultConfiguration - { - // for property value editors, get the ConfigurationEditor.DefaultConfiguration - // else fallback to a default, empty dictionary + return editor; + } - get => _defaultConfiguration ?? ((Type & EditorType.PropertyValue) > 0 ? GetConfigurationEditor().DefaultConfiguration : (_defaultConfiguration = new Dictionary())); - set => _defaultConfiguration = value; - } + /// + /// + /// + /// If an explicit configuration editor has been assigned, then this explicit + /// instance is returned. Otherwise, a new instance is created by CreateConfigurationEditor. + /// + /// + /// The instance created by CreateConfigurationEditor is not cached, i.e. + /// a new instance is created each time. The property editor is a singleton, and although the + /// configuration editor could technically be a singleton too, we'd rather not keep configuration editor + /// cached. + /// + /// + public IConfigurationEditor GetConfigurationEditor() => ExplicitConfigurationEditor ?? CreateConfigurationEditor(); - /// - public virtual IPropertyIndexValueFactory PropertyIndexValueFactory => new DefaultPropertyIndexValueFactory(); + /// + [DataMember(Name = "defaultConfig")] + public IDictionary DefaultConfiguration + { + // for property value editors, get the ConfigurationEditor.DefaultConfiguration + // else fallback to a default, empty dictionary - /// - /// Creates a value editor instance. - /// - /// - protected virtual IDataValueEditor CreateValueEditor() - { - if (Attribute == null) - throw new InvalidOperationException($"The editor is not attributed with {nameof(DataEditorAttribute)}"); + get => _defaultConfiguration ?? ((Type & EditorType.PropertyValue) > 0 + ? GetConfigurationEditor().DefaultConfiguration + : _defaultConfiguration = new Dictionary()); + set => _defaultConfiguration = value; + } - return DataValueEditorFactory.Create(Attribute); - } + /// + public virtual IPropertyIndexValueFactory PropertyIndexValueFactory => new DefaultPropertyIndexValueFactory(); - /// - /// Creates a configuration editor instance. - /// - protected virtual IConfigurationEditor CreateConfigurationEditor() + /// + /// Creates a value editor instance. + /// + /// + protected virtual IDataValueEditor CreateValueEditor() + { + if (Attribute == null) { - var editor = new ConfigurationEditor(); - // pass the default configuration if this is not a property value editor - if ((Type & EditorType.PropertyValue) == 0 && _defaultConfiguration is not null) - { - editor.DefaultConfiguration = _defaultConfiguration; - } - return editor; + throw new InvalidOperationException($"The editor is not attributed with {nameof(DataEditorAttribute)}"); } - /// - /// Provides a summary of the PropertyEditor for use with the . - /// - protected virtual string DebuggerDisplay() + return DataValueEditorFactory.Create(Attribute); + } + + /// + /// Creates a configuration editor instance. + /// + protected virtual IConfigurationEditor CreateConfigurationEditor() + { + var editor = new ConfigurationEditor(); + // pass the default configuration if this is not a property value editor + if ((Type & EditorType.PropertyValue) == 0 && _defaultConfiguration is not null) { - return $"Name: {Name}, Alias: {Alias}"; + editor.DefaultConfiguration = _defaultConfiguration; } + + return editor; } + + /// + /// Provides a summary of the PropertyEditor for use with the . + /// + protected virtual string DebuggerDisplay() => $"Name: {Name}, Alias: {Alias}"; } diff --git a/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs b/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs index d99acb478169..69705ef76da3 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs @@ -1,134 +1,177 @@ -using System; +namespace Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.PropertyEditors +/// +/// Marks a class that represents a data editor. +/// +[AttributeUsage(AttributeTargets.Class)] +public sealed class DataEditorAttribute : Attribute { + /// + /// Gets a special value indicating that the view should be null. + /// + public const string + NullView = "EXPLICITELY-SET-VIEW-TO-NULL-2B5B0B73D3DD47B28DDB84E02C349DFB"; // just a random string + + private string _valueType = ValueTypes.String; + + /// + /// Initializes a new instance of the class for a property editor. + /// + /// The unique identifier of the editor. + /// The friendly name of the editor. + public DataEditorAttribute(string alias, string name) + : this(alias, EditorType.PropertyValue, name, NullView) + { + } /// - /// Marks a class that represents a data editor. + /// Initializes a new instance of the class for a property editor. /// - [AttributeUsage(AttributeTargets.Class)] - public sealed class DataEditorAttribute : Attribute + /// The unique identifier of the editor. + /// The friendly name of the editor. + /// The view to use to render the editor. + public DataEditorAttribute(string alias, string name, string view) + : this(alias, EditorType.PropertyValue, name, view) { - private string _valueType = ValueTypes.String; - - /// - /// Initializes a new instance of the class for a property editor. - /// - /// The unique identifier of the editor. - /// The friendly name of the editor. - public DataEditorAttribute(string alias, string name) - : this(alias, EditorType.PropertyValue, name, NullView) - { } - - /// - /// Initializes a new instance of the class for a property editor. - /// - /// The unique identifier of the editor. - /// The friendly name of the editor. - /// The view to use to render the editor. - public DataEditorAttribute(string alias, string name, string view) - : this(alias, EditorType.PropertyValue, name, view) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The unique identifier of the editor. - /// The type of the editor. - /// The friendly name of the editor. - public DataEditorAttribute(string alias, EditorType type, string name) - : this(alias, type, name, NullView) - { } - - /// - /// Initializes a new instance of the class. - /// - /// The unique identifier of the editor. - /// The type of the editor. - /// The friendly name of the editor. - /// The view to use to render the editor. - /// - /// Set to to explicitly set the view to null. - /// Otherwise, cannot be null nor empty. - /// - public DataEditorAttribute(string alias, EditorType type, string name, string view) + } + + /// + /// Initializes a new instance of the class. + /// + /// The unique identifier of the editor. + /// The type of the editor. + /// The friendly name of the editor. + public DataEditorAttribute(string alias, EditorType type, string name) + : this(alias, type, name, NullView) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The unique identifier of the editor. + /// The type of the editor. + /// The friendly name of the editor. + /// The view to use to render the editor. + /// + /// Set to to explicitly set the view to null. + /// Otherwise, cannot be null nor empty. + /// + public DataEditorAttribute(string alias, EditorType type, string name, string view) + { + if (alias == null) { - if (alias == null) throw new ArgumentNullException(nameof(alias)); - if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(alias)); - if ((type & ~(EditorType.PropertyValue | EditorType.MacroParameter)) > 0) throw new ArgumentOutOfRangeException(nameof(type), type, $"Not a valid {typeof(EditorType)} value."); - if (name == null) throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); - if (view == null) throw new ArgumentNullException(nameof(view)); - if (string.IsNullOrWhiteSpace(view)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(view)); - - Type = type; - Alias = alias; - Name = name; - View = view == NullView ? null : view; + throw new ArgumentNullException(nameof(alias)); } - /// - /// Gets a special value indicating that the view should be null. - /// - public const string NullView = "EXPLICITELY-SET-VIEW-TO-NULL-2B5B0B73D3DD47B28DDB84E02C349DFB"; // just a random string - - /// - /// Gets the unique alias of the editor. - /// - public string Alias { get; } - - /// - /// Gets the type of the editor. - /// - public EditorType Type { get; } - - /// - /// Gets the friendly name of the editor. - /// - public string Name { get; } - - /// - /// Gets the view to use to render the editor. - /// - public string? View { get; } - - /// - /// Gets or sets the type of the edited value. - /// - /// Must be a valid value. - public string ValueType { - get => _valueType; - set + if (string.IsNullOrWhiteSpace(alias)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(alias)); + } + + if ((type & ~(EditorType.PropertyValue | EditorType.MacroParameter)) > 0) + { + throw new ArgumentOutOfRangeException(nameof(type), type, $"Not a valid {typeof(EditorType)} value."); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(name)); + } + + if (view == null) + { + throw new ArgumentNullException(nameof(view)); + } + + if (string.IsNullOrWhiteSpace(view)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(view)); + } + + Type = type; + Alias = alias; + Name = name; + View = view == NullView ? null : view; + } + + /// + /// Gets the unique alias of the editor. + /// + public string Alias { get; } + + /// + /// Gets the type of the editor. + /// + public EditorType Type { get; } + + /// + /// Gets the friendly name of the editor. + /// + public string Name { get; } + + /// + /// Gets the view to use to render the editor. + /// + public string? View { get; } + + /// + /// Gets or sets the type of the edited value. + /// + /// Must be a valid value. + public string ValueType + { + get => _valueType; + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (string.IsNullOrWhiteSpace(value)) { - if (value == null) throw new ArgumentNullException(nameof(value)); - if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(value)); - if (!ValueTypes.IsValue(value)) throw new ArgumentOutOfRangeException(nameof(value), value, $"Not a valid {typeof(ValueTypes)} value."); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(value)); + } - _valueType = value; + if (!ValueTypes.IsValue(value)) + { + throw new ArgumentOutOfRangeException(nameof(value), value, $"Not a valid {typeof(ValueTypes)} value."); } - } - /// - /// Gets or sets a value indicating whether the editor should be displayed without its label. - /// - public bool HideLabel { get; set; } - - /// - /// Gets or sets an optional icon. - /// - /// The icon can be used for example when presenting datatypes based upon the editor. - public string Icon { get; set; } = Constants.Icons.PropertyEditor; - - /// - /// Gets or sets an optional group. - /// - /// The group can be used for example to group the editors by category. - public string Group { get; set; } = Constants.PropertyEditors.Groups.Common; - - /// - /// Gets or sets a value indicating whether the value editor is deprecated. - /// - /// A deprecated editor is still supported but not proposed in the UI. - public bool IsDeprecated { get; set; } + _valueType = value; + } } + + /// + /// Gets or sets a value indicating whether the editor should be displayed without its label. + /// + public bool HideLabel { get; set; } + + /// + /// Gets or sets an optional icon. + /// + /// The icon can be used for example when presenting datatypes based upon the editor. + public string Icon { get; set; } = Constants.Icons.PropertyEditor; + + /// + /// Gets or sets an optional group. + /// + /// The group can be used for example to group the editors by category. + public string Group { get; set; } = Constants.PropertyEditors.Groups.Common; + + /// + /// Gets or sets a value indicating whether the value editor is deprecated. + /// + /// A deprecated editor is still supported but not proposed in the UI. + public bool IsDeprecated { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/DataEditorCollection.cs b/src/Umbraco.Core/PropertyEditors/DataEditorCollection.cs index 0c4ca93fc1d8..8079064e847e 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditorCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditorCollection.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +public class DataEditorCollection : BuilderCollectionBase { - public class DataEditorCollection : BuilderCollectionBase + public DataEditorCollection(Func> items) : base(items) { - public DataEditorCollection(Func> items) : base(items) - { - } } } diff --git a/src/Umbraco.Core/PropertyEditors/DataEditorCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/DataEditorCollectionBuilder.cs index 4794d37c21c9..3b2b2e570678 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditorCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditorCollectionBuilder.cs @@ -1,9 +1,10 @@ using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +public class + DataEditorCollectionBuilder : LazyCollectionBuilderBase { - public class DataEditorCollectionBuilder : LazyCollectionBuilderBase - { - protected override DataEditorCollectionBuilder This => this; - } + protected override DataEditorCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs index b8e3e597a4aa..37301856d759 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Globalization; -using System.Linq; using System.Runtime.Serialization; using System.Xml.Linq; using Microsoft.Extensions.Logging; @@ -15,385 +12,434 @@ using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents a value editor. +/// +[DataContract] +public class DataValueEditor : IDataValueEditor { + private readonly IJsonSerializer? _jsonSerializer; + private readonly ILocalizedTextService _localizedTextService; + private readonly IShortStringHelper _shortStringHelper; + + /// + /// Initializes a new instance of the class. + /// + public DataValueEditor( + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer? jsonSerializer) // for tests, and manifest + { + _localizedTextService = localizedTextService; + _shortStringHelper = shortStringHelper; + _jsonSerializer = jsonSerializer; + ValueType = ValueTypes.String; + Validators = new List(); + } + /// - /// Represents a value editor. + /// Initializes a new instance of the class. /// - [DataContract] - public class DataValueEditor : IDataValueEditor + public DataValueEditor( + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper, + DataEditorAttribute attribute) { - private readonly ILocalizedTextService _localizedTextService; - private readonly IShortStringHelper _shortStringHelper; - private readonly IJsonSerializer? _jsonSerializer; - - /// - /// Initializes a new instance of the class. - /// - public DataValueEditor( - ILocalizedTextService localizedTextService, - IShortStringHelper shortStringHelper, - IJsonSerializer? jsonSerializer) // for tests, and manifest + if (attribute == null) { - _localizedTextService = localizedTextService; - _shortStringHelper = shortStringHelper; - _jsonSerializer = jsonSerializer; - ValueType = ValueTypes.String; - Validators = new List(); + throw new ArgumentNullException(nameof(attribute)); } - /// - /// Initializes a new instance of the class. - /// - public DataValueEditor( - ILocalizedTextService localizedTextService, - IShortStringHelper shortStringHelper, - IJsonSerializer jsonSerializer, - IIOHelper ioHelper, - DataEditorAttribute attribute) + _localizedTextService = localizedTextService; + _shortStringHelper = shortStringHelper; + _jsonSerializer = jsonSerializer; + + var view = attribute.View; + if (string.IsNullOrWhiteSpace(view)) { - if (attribute == null) throw new ArgumentNullException(nameof(attribute)); - _localizedTextService = localizedTextService; - _shortStringHelper = shortStringHelper; - _jsonSerializer = jsonSerializer; + throw new ArgumentException("The attribute does not specify a view.", nameof(attribute)); + } - var view = attribute.View; - if (string.IsNullOrWhiteSpace(view)) - throw new ArgumentException("The attribute does not specify a view.", nameof(attribute)); + if (view.StartsWith("~/")) + { + view = ioHelper.ResolveRelativeOrVirtualUrl(view); + } - if (view.StartsWith("~/")) - { - view = ioHelper.ResolveRelativeOrVirtualUrl(view); - } + View = view; + ValueType = attribute.ValueType; + HideLabel = attribute.HideLabel; + } - View = view; - ValueType = attribute.ValueType; - HideLabel = attribute.HideLabel; - } + /// + /// Gets or sets the value editor configuration. + /// + public virtual object? Configuration { get; set; } - /// - /// Gets or sets the value editor configuration. - /// - public virtual object? Configuration { get; set; } - - /// - /// Gets or sets the editor view. - /// - /// - /// The view can be three things: (1) the full virtual path, or (2) the relative path to the current Umbraco - /// folder, or (3) a view name which maps to views/propertyeditors/{view}/{view}.html. - /// - [Required] - [DataMember(Name = "view")] - public string? View { get; set; } - - /// - /// The value type which reflects how it is validated and stored in the database - /// - [DataMember(Name = "valueType")] - public string ValueType { get; set; } - - /// - public IEnumerable Validate(object? value, bool required, string? format) - { - List? results = null; - var r = Validators.SelectMany(v => v.Validate(value, ValueType, Configuration)).ToList(); - if (r.Any()) { results = r; } + /// + /// Gets the validator used to validate the special property type -level "required". + /// + public virtual IValueRequiredValidator RequiredValidator => new RequiredValidator(_localizedTextService); - // mandatory and regex validators cannot be part of valueEditor.Validators because they - // depend on values that are not part of the configuration, .Mandatory and .ValidationRegEx, - // so they have to be explicitly invoked here. + /// + /// Gets the validator used to validate the special property type -level "format". + /// + public virtual IValueFormatValidator FormatValidator => new RegexValidator(_localizedTextService); - if (required) - { - r = RequiredValidator.ValidateRequired(value, ValueType).ToList(); - if (r.Any()) { if (results == null) results = r; else results.AddRange(r); } - } + /// + /// Gets or sets the editor view. + /// + /// + /// + /// The view can be three things: (1) the full virtual path, or (2) the relative path to the current Umbraco + /// folder, or (3) a view name which maps to views/propertyeditors/{view}/{view}.html. + /// + /// + [Required] + [DataMember(Name = "view")] + public string? View { get; set; } - var stringValue = value?.ToString(); - if (!string.IsNullOrWhiteSpace(format) && !string.IsNullOrWhiteSpace(stringValue)) - { - r = FormatValidator.ValidateFormat(value, ValueType, format).ToList(); - if (r.Any()) { if (results == null) results = r; else results.AddRange(r); } - } + /// + /// The value type which reflects how it is validated and stored in the database + /// + [DataMember(Name = "valueType")] + public string ValueType { get; set; } - return results ?? Enumerable.Empty(); - } + /// + public IEnumerable Validate(object? value, bool required, string? format) + { + List? results = null; + var r = Validators.SelectMany(v => v.Validate(value, ValueType, Configuration)).ToList(); + if (r.Any()) { results = r; } - /// - /// A collection of validators for the pre value editor - /// - [DataMember(Name = "validation")] - public List Validators { get; private set; } = new List(); - - /// - /// Gets the validator used to validate the special property type -level "required". - /// - public virtual IValueRequiredValidator RequiredValidator => new RequiredValidator(_localizedTextService); - - /// - /// Gets the validator used to validate the special property type -level "format". - /// - public virtual IValueFormatValidator FormatValidator => new RegexValidator(_localizedTextService); - - /// - /// If this is true than the editor will be displayed full width without a label - /// - [DataMember(Name = "hideLabel")] - public bool HideLabel { get; set; } - - /// - /// Set this to true if the property editor is for display purposes only - /// - public virtual bool IsReadOnly => false; - - /// - /// Used to try to convert the string value to the correct CLR type based on the specified for this value editor. - /// - /// The value. - /// - /// The result of the conversion attempt. - /// - /// ValueType was out of range. - internal Attempt TryConvertValueToCrlType(object? value) + // mandatory and regex validators cannot be part of valueEditor.Validators because they + // depend on values that are not part of the configuration, .Mandatory and .ValidationRegEx, + // so they have to be explicitly invoked here. + + if (required) { - // Ensure empty string and JSON values are converted to null - if (value is string stringValue && string.IsNullOrWhiteSpace(stringValue)) - { - value = null; - } - else if (value is not string && ValueType.InvariantEquals(ValueTypes.Json)) + r = RequiredValidator.ValidateRequired(value, ValueType).ToList(); + if (r.Any()) { - // Only serialize value when it's not already a string - var jsonValue = _jsonSerializer?.Serialize(value); - if (jsonValue?.DetectIsEmptyJson() ?? false) + if (results == null) { - value = null; + results = r; } else { - value = jsonValue; + results.AddRange(r); } } - - // Convert the string to a known type - Type valueType; - switch (ValueTypes.ToStorageType(ValueType)) - { - case ValueStorageType.Ntext: - case ValueStorageType.Nvarchar: - valueType = typeof(string); - break; - - case ValueStorageType.Integer: - // Ensure these are nullable so we can return a null if required - // NOTE: This is allowing type of 'long' because I think JSON.NEt will deserialize a numerical value as long instead of int - // Even though our DB will not support this (will get truncated), we'll at least parse to this - valueType = typeof(long?); - - // If parsing is successful, we need to return as an int, we're only dealing with long's here because of JSON.NET, - // we actually don't support long values and if we return a long value, it will get set as a 'long' on the Property.Value (object) and then - // when we compare the values for dirty tracking we'll be comparing an int -> long and they will not match. - var result = value.TryConvertTo(valueType); - - return result.Success && result.Result != null - ? Attempt.Succeed((int)(long)result.Result) - : result; - - case ValueStorageType.Decimal: - // Ensure these are nullable so we can return a null if required - valueType = typeof(decimal?); - break; - - case ValueStorageType.Date: - // Ensure these are nullable so we can return a null if required - valueType = typeof(DateTime?); - break; - - default: - throw new ArgumentOutOfRangeException("ValueType was out of range."); - } - - return value.TryConvertTo(valueType); } - /// - /// A method to deserialize the string value that has been saved in the content editor to an object to be stored in the database. - /// - /// The value returned by the editor. - /// The current value that has been persisted to the database for this editor. This value may be useful for how the value then get's deserialized again to be re-persisted. In most cases it will probably not be used. - /// The value that gets persisted to the database. - /// - /// By default this will attempt to automatically convert the string value to the value type supplied by ValueType. - /// If overridden then the object returned must match the type supplied in the ValueType, otherwise persisting the - /// value to the DB will fail when it tries to validate the value type. - /// - public virtual object? FromEditor(ContentPropertyData editorValue, object? currentValue) + var stringValue = value?.ToString(); + if (!string.IsNullOrWhiteSpace(format) && !string.IsNullOrWhiteSpace(stringValue)) { - var result = TryConvertValueToCrlType(editorValue.Value); - if (result.Success == false) + r = FormatValidator.ValidateFormat(value, ValueType, format).ToList(); + if (r.Any()) { - StaticApplicationLogging.Logger.LogWarning("The value {EditorValue} cannot be converted to the type {StorageTypeValue}", editorValue.Value, ValueTypes.ToStorageType(ValueType)); - return null; + if (results == null) + { + results = r; + } + else + { + results.AddRange(r); + } } + } + + return results ?? Enumerable.Empty(); + } - return result.Result; + /// + /// A collection of validators for the pre value editor + /// + [DataMember(Name = "validation")] + public List Validators { get; private set; } = new(); + + /// + /// If this is true than the editor will be displayed full width without a label + /// + [DataMember(Name = "hideLabel")] + public bool HideLabel { get; set; } + + /// + /// Set this to true if the property editor is for display purposes only + /// + public virtual bool IsReadOnly => false; + + /// + /// A method to deserialize the string value that has been saved in the content editor to an object to be stored in the + /// database. + /// + /// The value returned by the editor. + /// + /// The current value that has been persisted to the database for this editor. This value may be + /// useful for how the value then get's deserialized again to be re-persisted. In most cases it will probably not be + /// used. + /// + /// The value that gets persisted to the database. + /// + /// By default this will attempt to automatically convert the string value to the value type supplied by ValueType. + /// If overridden then the object returned must match the type supplied in the ValueType, otherwise persisting the + /// value to the DB will fail when it tries to validate the value type. + /// + public virtual object? FromEditor(ContentPropertyData editorValue, object? currentValue) + { + Attempt result = TryConvertValueToCrlType(editorValue.Value); + if (result.Success == false) + { + StaticApplicationLogging.Logger.LogWarning( + "The value {EditorValue} cannot be converted to the type {StorageTypeValue}", editorValue.Value, + ValueTypes.ToStorageType(ValueType)); + return null; } - /// - /// A method used to format the database value to a value that can be used by the editor. - /// - /// The property. - /// The culture. - /// The segment. - /// - /// ValueType was out of range. - /// - /// The object returned will automatically be serialized into JSON notation. For most property editors - /// the value returned is probably just a string, but in some cases a JSON structure will be returned. - /// - public virtual object? ToEditor(IProperty property, string? culture = null, string? segment = null) + return result.Result; + } + + /// + /// A method used to format the database value to a value that can be used by the editor. + /// + /// The property. + /// The culture. + /// The segment. + /// + /// ValueType was out of range. + /// + /// The object returned will automatically be serialized into JSON notation. For most property editors + /// the value returned is probably just a string, but in some cases a JSON structure will be returned. + /// + public virtual object? ToEditor(IProperty property, string? culture = null, string? segment = null) + { + var value = property.GetValue(culture, segment); + if (value == null) { - var value = property.GetValue(culture, segment); - if (value == null) - { - return string.Empty; - } + return string.Empty; + } - switch (ValueTypes.ToStorageType(ValueType)) - { - case ValueStorageType.Ntext: - case ValueStorageType.Nvarchar: - // If it is a string type, we will attempt to see if it is JSON stored data, if it is we'll try to convert - // to a real JSON object so we can pass the true JSON object directly to Angular! - var stringValue = value as string ?? value.ToString(); - if (stringValue!.DetectIsJson()) + switch (ValueTypes.ToStorageType(ValueType)) + { + case ValueStorageType.Ntext: + case ValueStorageType.Nvarchar: + // If it is a string type, we will attempt to see if it is JSON stored data, if it is we'll try to convert + // to a real JSON object so we can pass the true JSON object directly to Angular! + var stringValue = value as string ?? value.ToString(); + if (stringValue!.DetectIsJson()) + { + try + { + var json = _jsonSerializer?.Deserialize(stringValue!); + return json; + } + catch { - try - { - var json = _jsonSerializer?.Deserialize(stringValue!); - return json; - } - catch - { - // Swallow this exception, we thought it was JSON but it really isn't so continue returning a string - } + // Swallow this exception, we thought it was JSON but it really isn't so continue returning a string } + } - return stringValue; + return stringValue; - case ValueStorageType.Integer: - case ValueStorageType.Decimal: - // Decimals need to be formatted with invariant culture (dots, not commas) - // Anything else falls back to ToString() - var decimalValue = value.TryConvertTo(); + case ValueStorageType.Integer: + case ValueStorageType.Decimal: + // Decimals need to be formatted with invariant culture (dots, not commas) + // Anything else falls back to ToString() + Attempt decimalValue = value.TryConvertTo(); - return decimalValue.Success - ? decimalValue.Result.ToString(NumberFormatInfo.InvariantInfo) - : value.ToString(); + return decimalValue.Success + ? decimalValue.Result.ToString(NumberFormatInfo.InvariantInfo) + : value.ToString(); - case ValueStorageType.Date: - var dateValue = value.TryConvertTo(); - if (dateValue.Success == false || dateValue.Result == null) - { - return string.Empty; - } + case ValueStorageType.Date: + Attempt dateValue = value.TryConvertTo(); + if (dateValue.Success == false || dateValue.Result == null) + { + return string.Empty; + } - // Dates will be formatted as yyyy-MM-dd HH:mm:ss - return dateValue.Result.Value.ToIsoString(); + // Dates will be formatted as yyyy-MM-dd HH:mm:ss + return dateValue.Result.Value.ToIsoString(); - default: - throw new ArgumentOutOfRangeException("ValueType was out of range."); - } + default: + throw new ArgumentOutOfRangeException("ValueType was out of range."); } + } + + // TODO: the methods below should be replaced by proper property value convert ToXPath usage! + + /// + /// Converts a property to Xml fragments. + /// + public IEnumerable ConvertDbToXml(IProperty property, bool published) + { + published &= property.PropertyType.SupportsPublishing; - // TODO: the methods below should be replaced by proper property value convert ToXPath usage! + var nodeName = property.PropertyType.Alias.ToSafeAlias(_shortStringHelper); - /// - /// Converts a property to Xml fragments. - /// - public IEnumerable ConvertDbToXml(IProperty property, bool published) + foreach (IPropertyValue pvalue in property.Values) { - published &= property.PropertyType.SupportsPublishing; + var value = published ? pvalue.PublishedValue : pvalue.EditedValue; + if (value == null || (value is string stringValue && string.IsNullOrWhiteSpace(stringValue))) + { + continue; + } - var nodeName = property.PropertyType.Alias.ToSafeAlias(_shortStringHelper); + var xElement = new XElement(nodeName); + if (pvalue.Culture != null) + { + xElement.Add(new XAttribute("lang", pvalue.Culture)); + } - foreach (var pvalue in property.Values) + if (pvalue.Segment != null) { - var value = published ? pvalue.PublishedValue : pvalue.EditedValue; - if (value == null || value is string stringValue && string.IsNullOrWhiteSpace(stringValue)) - continue; + xElement.Add(new XAttribute("segment", pvalue.Segment)); + } - var xElement = new XElement(nodeName); - if (pvalue.Culture != null) - xElement.Add(new XAttribute("lang", pvalue.Culture)); - if (pvalue.Segment != null) - xElement.Add(new XAttribute("segment", pvalue.Segment)); + XNode xValue = ConvertDbToXml(property.PropertyType, value); + xElement.Add(xValue); - var xValue = ConvertDbToXml(property.PropertyType, value); - xElement.Add(xValue); + yield return xElement; + } + } - yield return xElement; - } + /// + /// Converts a property value to an Xml fragment. + /// + /// + /// + /// By default, this returns the value of ConvertDbToString but ensures that if the db value type is + /// NVarchar or NText, the value is returned as a CDATA fragment - else it's a Text fragment. + /// + /// Returns an XText or XCData instance which must be wrapped in a element. + /// If the value is empty we will not return as CDATA since that will just take up more space in the file. + /// + public XNode ConvertDbToXml(IPropertyType propertyType, object? value) + { + //check for null or empty value, we don't want to return CDATA if that is the case + if (value == null || value.ToString().IsNullOrWhiteSpace()) + { + return new XText(ConvertDbToString(propertyType, value)); } - /// - /// Converts a property value to an Xml fragment. - /// - /// - /// By default, this returns the value of ConvertDbToString but ensures that if the db value type is - /// NVarchar or NText, the value is returned as a CDATA fragment - else it's a Text fragment. - /// Returns an XText or XCData instance which must be wrapped in a element. - /// If the value is empty we will not return as CDATA since that will just take up more space in the file. - /// - public XNode ConvertDbToXml(IPropertyType propertyType, object? value) + switch (ValueTypes.ToStorageType(ValueType)) { - //check for null or empty value, we don't want to return CDATA if that is the case - if (value == null || value.ToString().IsNullOrWhiteSpace()) - { + case ValueStorageType.Date: + case ValueStorageType.Integer: + case ValueStorageType.Decimal: return new XText(ConvertDbToString(propertyType, value)); - } + case ValueStorageType.Nvarchar: + case ValueStorageType.Ntext: + //put text in cdata + return new XCData(ConvertDbToString(propertyType, value)); + default: + throw new ArgumentOutOfRangeException(); + } + } - switch (ValueTypes.ToStorageType(ValueType)) - { - case ValueStorageType.Date: - case ValueStorageType.Integer: - case ValueStorageType.Decimal: - return new XText(ConvertDbToString(propertyType, value)); - case ValueStorageType.Nvarchar: - case ValueStorageType.Ntext: - //put text in cdata - return new XCData(ConvertDbToString(propertyType, value)); - default: - throw new ArgumentOutOfRangeException(); - } + /// + /// Converts a property value to a string. + /// + public virtual string ConvertDbToString(IPropertyType propertyType, object? value) + { + if (value == null) + { + return string.Empty; } - /// - /// Converts a property value to a string. - /// - public virtual string ConvertDbToString(IPropertyType propertyType, object? value) + switch (ValueTypes.ToStorageType(ValueType)) { - if (value == null) - return string.Empty; + case ValueStorageType.Nvarchar: + case ValueStorageType.Ntext: + return value.ToXmlString(); + case ValueStorageType.Integer: + case ValueStorageType.Decimal: + return value.ToXmlString(value.GetType()); + case ValueStorageType.Date: + //treat dates differently, output the format as xml format + Attempt date = value.TryConvertTo(); + if (date.Success == false || date.Result == null) + { + return string.Empty; + } - switch (ValueTypes.ToStorageType(ValueType)) + return date.Result.ToXmlString(); + default: + throw new ArgumentOutOfRangeException(); + } + } + + /// + /// Used to try to convert the string value to the correct CLR type based on the specified for + /// this value editor. + /// + /// The value. + /// + /// The result of the conversion attempt. + /// + /// ValueType was out of range. + internal Attempt TryConvertValueToCrlType(object? value) + { + // Ensure empty string and JSON values are converted to null + if (value is string stringValue && string.IsNullOrWhiteSpace(stringValue)) + { + value = null; + } + else if (value is not string && ValueType.InvariantEquals(ValueTypes.Json)) + { + // Only serialize value when it's not already a string + var jsonValue = _jsonSerializer?.Serialize(value); + if (jsonValue?.DetectIsEmptyJson() ?? false) { - case ValueStorageType.Nvarchar: - case ValueStorageType.Ntext: - return value.ToXmlString(); - case ValueStorageType.Integer: - case ValueStorageType.Decimal: - return value.ToXmlString(value.GetType()); - case ValueStorageType.Date: - //treat dates differently, output the format as xml format - var date = value.TryConvertTo(); - if (date.Success == false || date.Result == null) - return string.Empty; - return date.Result.ToXmlString(); - default: - throw new ArgumentOutOfRangeException(); + value = null; + } + else + { + value = jsonValue; } } + + // Convert the string to a known type + Type valueType; + switch (ValueTypes.ToStorageType(ValueType)) + { + case ValueStorageType.Ntext: + case ValueStorageType.Nvarchar: + valueType = typeof(string); + break; + + case ValueStorageType.Integer: + // Ensure these are nullable so we can return a null if required + // NOTE: This is allowing type of 'long' because I think JSON.NEt will deserialize a numerical value as long instead of int + // Even though our DB will not support this (will get truncated), we'll at least parse to this + valueType = typeof(long?); + + // If parsing is successful, we need to return as an int, we're only dealing with long's here because of JSON.NET, + // we actually don't support long values and if we return a long value, it will get set as a 'long' on the Property.Value (object) and then + // when we compare the values for dirty tracking we'll be comparing an int -> long and they will not match. + Attempt result = value.TryConvertTo(valueType); + + return result.Success && result.Result != null + ? Attempt.Succeed((int)(long)result.Result) + : result; + + case ValueStorageType.Decimal: + // Ensure these are nullable so we can return a null if required + valueType = typeof(decimal?); + break; + + case ValueStorageType.Date: + // Ensure these are nullable so we can return a null if required + valueType = typeof(DateTime?); + break; + + default: + throw new ArgumentOutOfRangeException("ValueType was out of range."); + } + + return value.TryConvertTo(valueType); } } diff --git a/src/Umbraco.Core/PropertyEditors/DataValueEditorFactory.cs b/src/Umbraco.Core/PropertyEditors/DataValueEditorFactory.cs index 300bdde67214..d697a0fe0c86 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueEditorFactory.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueEditorFactory.cs @@ -1,18 +1,15 @@ -using System; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors -{ - public class DataValueEditorFactory : IDataValueEditorFactory - { - private readonly IServiceProvider _serviceProvider; +namespace Umbraco.Cms.Core.PropertyEditors; - public DataValueEditorFactory(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; +public class DataValueEditorFactory : IDataValueEditorFactory +{ + private readonly IServiceProvider _serviceProvider; - public TDataValueEditor Create(params object[] args) - where TDataValueEditor: class, IDataValueEditor - => _serviceProvider.CreateInstance(args); + public DataValueEditorFactory(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; - } + public TDataValueEditor Create(params object[] args) + where TDataValueEditor : class, IDataValueEditor + => _serviceProvider.CreateInstance(args); } diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs index 099fa9126f9d..7ff6bf446646 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs @@ -1,61 +1,67 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Editors; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +public class DataValueReferenceFactoryCollection : BuilderCollectionBase { - public class DataValueReferenceFactoryCollection : BuilderCollectionBase + public DataValueReferenceFactoryCollection(Func> items) : base(items) { - public DataValueReferenceFactoryCollection(System.Func> items) : base(items) - { - } + } - // TODO: We could further reduce circular dependencies with PropertyEditorCollection by not having IDataValueReference implemented - // by property editors and instead just use the already built in IDataValueReferenceFactory and/or refactor that into a more normal collection + // TODO: We could further reduce circular dependencies with PropertyEditorCollection by not having IDataValueReference implemented + // by property editors and instead just use the already built in IDataValueReferenceFactory and/or refactor that into a more normal collection - public IEnumerable GetAllReferences(IPropertyCollection properties, PropertyEditorCollection propertyEditors) - { - var trackedRelations = new HashSet(); + public IEnumerable GetAllReferences(IPropertyCollection properties, + PropertyEditorCollection propertyEditors) + { + var trackedRelations = new HashSet(); - foreach (var p in properties) + foreach (IProperty p in properties) + { + if (!propertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out IDataEditor editor)) { - if (!propertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out var editor)) continue; + continue; + } - //TODO: We will need to change this once we support tracking via variants/segments - // for now, we are tracking values from ALL variants + //TODO: We will need to change this once we support tracking via variants/segments + // for now, we are tracking values from ALL variants - foreach (var propertyVal in p.Values) - { - var val = propertyVal.EditedValue; + foreach (IPropertyValue propertyVal in p.Values) + { + var val = propertyVal.EditedValue; - var valueEditor = editor?.GetValueEditor(); - if (valueEditor is IDataValueReference reference) + IDataValueEditor valueEditor = editor?.GetValueEditor(); + if (valueEditor is IDataValueReference reference) + { + IEnumerable refs = reference.GetReferences(val); + foreach (UmbracoEntityReference r in refs) { - var refs = reference.GetReferences(val); - foreach (var r in refs) - trackedRelations.Add(r); + trackedRelations.Add(r); } + } - // Loop over collection that may be add to existing property editors - // implementation of GetReferences in IDataValueReference. - // Allows developers to add support for references by a - // package /property editor that did not implement IDataValueReference themselves - foreach (var item in this) + // Loop over collection that may be add to existing property editors + // implementation of GetReferences in IDataValueReference. + // Allows developers to add support for references by a + // package /property editor that did not implement IDataValueReference themselves + foreach (IDataValueReferenceFactory item in this) + { + // Check if this value reference is for this datatype/editor + // Then call it's GetReferences method - to see if the value stored + // in the dataeditor/property has referecnes to media/content items + if (item.IsForEditor(editor)) { - // Check if this value reference is for this datatype/editor - // Then call it's GetReferences method - to see if the value stored - // in the dataeditor/property has referecnes to media/content items - if (item.IsForEditor(editor)) + foreach (UmbracoEntityReference r in item.GetDataValueReference().GetReferences(val)) { - foreach (var r in item.GetDataValueReference().GetReferences(val)) - trackedRelations.Add(r); + trackedRelations.Add(r); } } } } - - return trackedRelations; } + + return trackedRelations; } } diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs index b42ea74e88ea..e889b4893318 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs @@ -1,9 +1,9 @@ using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +public class DataValueReferenceFactoryCollectionBuilder : OrderedCollectionBuilderBase< + DataValueReferenceFactoryCollectionBuilder, DataValueReferenceFactoryCollection, IDataValueReferenceFactory> { - public class DataValueReferenceFactoryCollectionBuilder : OrderedCollectionBuilderBase - { - protected override DataValueReferenceFactoryCollectionBuilder This => this; - } + protected override DataValueReferenceFactoryCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/PropertyEditors/DateTimeConfiguration.cs b/src/Umbraco.Core/PropertyEditors/DateTimeConfiguration.cs index 985d58f06dff..d149324253fb 100644 --- a/src/Umbraco.Core/PropertyEditors/DateTimeConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/DateTimeConfiguration.cs @@ -1,20 +1,20 @@ -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration for the datetime value editor. +/// +public class DateTimeConfiguration { - /// - /// Represents the configuration for the datetime value editor. - /// - public class DateTimeConfiguration - { - [ConfigurationField("format", "Date format", "textstring", Description = "If left empty then the format is YYYY-MM-DD. (see momentjs.com for supported formats)")] - public string Format { get; set; } + public DateTimeConfiguration() => + // different default values + Format = "YYYY-MM-DD HH:mm:ss"; - public DateTimeConfiguration() - { - // different default values - Format = "YYYY-MM-DD HH:mm:ss"; - } + [ConfigurationField("format", "Date format", "textstring", + Description = "If left empty then the format is YYYY-MM-DD. (see momentjs.com for supported formats)")] + public string Format { get; set; } - [ConfigurationField("offsetTime", "Offset time", "boolean", Description = "When enabled the time displayed will be offset with the server's timezone, this is useful for scenarios like scheduled publishing when an editor is in a different timezone than the hosted server")] - public bool OffsetTime { get; set; } - } + [ConfigurationField("offsetTime", "Offset time", "boolean", + Description = + "When enabled the time displayed will be offset with the server's timezone, this is useful for scenarios like scheduled publishing when an editor is in a different timezone than the hosted server")] + public bool OffsetTime { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/DateTimeConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/DateTimeConfigurationEditor.cs index 36c82175c2fb..460170e19dc9 100644 --- a/src/Umbraco.Core/PropertyEditors/DateTimeConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DateTimeConfigurationEditor.cs @@ -1,40 +1,39 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration editor for the datetime value editor. +/// +public class DateTimeConfigurationEditor : ConfigurationEditor { - /// - /// Represents the configuration editor for the datetime value editor. - /// - public class DateTimeConfigurationEditor : ConfigurationEditor + // Scheduled for removal in v12 + [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + public DateTimeConfigurationEditor(IIOHelper ioHelper) : this(ioHelper, + StaticServiceProvider.Instance.GetRequiredService()) { - public override IDictionary ToValueEditor(object? configuration) - { - var d = base.ToValueEditor(configuration); + } - var format = d["format"].ToString()!; + public DateTimeConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base( + ioHelper, editorConfigurationParser) + { + } - d["pickTime"] = format.ContainsAny(new string[] { "H", "m", "s" }); + public override IDictionary ToValueEditor(object? configuration) + { + IDictionary d = base.ToValueEditor(configuration); - return d; - } + var format = d["format"].ToString()!; - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public DateTimeConfigurationEditor(IIOHelper ioHelper) : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) - { - } + d["pickTime"] = format.ContainsAny(new[] {"H", "m", "s"}); - public DateTimeConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) - { - } + return d; } } diff --git a/src/Umbraco.Core/PropertyEditors/DateValueEditor.cs b/src/Umbraco.Core/PropertyEditors/DateValueEditor.cs index 1e65429b6e53..177aca5b746e 100644 --- a/src/Umbraco.Core/PropertyEditors/DateValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DateValueEditor.cs @@ -1,6 +1,4 @@ -using System; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors.Validators; using Umbraco.Cms.Core.Serialization; @@ -8,34 +6,32 @@ using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// CUstom value editor so we can serialize with the correct date format (excluding time) +/// and includes the date validator +/// +internal class DateValueEditor : DataValueEditor { - /// - /// CUstom value editor so we can serialize with the correct date format (excluding time) - /// and includes the date validator - /// - internal class DateValueEditor : DataValueEditor + public DateValueEditor( + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper, + DataEditorAttribute attribute) + : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) => + Validators.Add(new DateTimeValidator()); + + public override object ToEditor(IProperty property, string? culture = null, string? segment = null) { - public DateValueEditor( - ILocalizedTextService localizedTextService, - IShortStringHelper shortStringHelper, - IJsonSerializer jsonSerializer, - IIOHelper ioHelper, - DataEditorAttribute attribute) - : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) + Attempt date = property.GetValue(culture, segment).TryConvertTo(); + if (date.Success == false || date.Result == null) { - Validators.Add(new DateTimeValidator()); + return string.Empty; } - public override object ToEditor(IProperty property, string? culture= null, string? segment = null) - { - var date = property.GetValue(culture, segment).TryConvertTo(); - if (date.Success == false || date.Result == null) - { - return String.Empty; - } - //Dates will be formatted as yyyy-MM-dd - return date.Result.Value.ToString("yyyy-MM-dd"); - } + //Dates will be formatted as yyyy-MM-dd + return date.Result.Value.ToString("yyyy-MM-dd"); } } diff --git a/src/Umbraco.Core/PropertyEditors/DecimalConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/DecimalConfigurationEditor.cs index 52eefbd40072..41958a181053 100644 --- a/src/Umbraco.Core/PropertyEditors/DecimalConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DecimalConfigurationEditor.cs @@ -1,37 +1,36 @@ using Umbraco.Cms.Core.PropertyEditors.Validators; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// A custom pre-value editor class to deal with the legacy way that the pre-value data is stored. +/// +public class DecimalConfigurationEditor : ConfigurationEditor { - /// - /// A custom pre-value editor class to deal with the legacy way that the pre-value data is stored. - /// - public class DecimalConfigurationEditor : ConfigurationEditor + public DecimalConfigurationEditor() { - public DecimalConfigurationEditor() + Fields.Add(new ConfigurationField(new DecimalValidator()) { - Fields.Add(new ConfigurationField(new DecimalValidator()) - { - Description = "Enter the minimum amount of number to be entered", - Key = "min", - View = "decimal", - Name = "Minimum" - }); + Description = "Enter the minimum amount of number to be entered", + Key = "min", + View = "decimal", + Name = "Minimum" + }); - Fields.Add(new ConfigurationField(new DecimalValidator()) - { - Description = "Enter the intervals amount between each step of number to be entered", - Key = "step", - View = "decimal", - Name = "Step Size" - }); + Fields.Add(new ConfigurationField(new DecimalValidator()) + { + Description = "Enter the intervals amount between each step of number to be entered", + Key = "step", + View = "decimal", + Name = "Step Size" + }); - Fields.Add(new ConfigurationField(new DecimalValidator()) - { - Description = "Enter the maximum amount of number to be entered", - Key = "max", - View = "decimal", - Name = "Maximum" - }); - } + Fields.Add(new ConfigurationField(new DecimalValidator()) + { + Description = "Enter the maximum amount of number to be entered", + Key = "max", + View = "decimal", + Name = "Maximum" + }); } } diff --git a/src/Umbraco.Core/PropertyEditors/DecimalPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/DecimalPropertyEditor.cs index c940560c90c9..50d7e3c03880 100644 --- a/src/Umbraco.Core/PropertyEditors/DecimalPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DecimalPropertyEditor.cs @@ -1,41 +1,36 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors.Validators; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents a decimal property and parameter editor. +/// +[DataEditor( + Constants.PropertyEditors.Aliases.Decimal, + EditorType.PropertyValue | EditorType.MacroParameter, + "Decimal", + "decimal", + ValueType = ValueTypes.Decimal)] +public class DecimalPropertyEditor : DataEditor { /// - /// Represents a decimal property and parameter editor. + /// Initializes a new instance of the class. /// - [DataEditor( - Constants.PropertyEditors.Aliases.Decimal, - EditorType.PropertyValue | EditorType.MacroParameter, - "Decimal", - "decimal", - ValueType = ValueTypes.Decimal)] - public class DecimalPropertyEditor : DataEditor + public DecimalPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory) + : base(dataValueEditorFactory) { - /// - /// Initializes a new instance of the class. - /// - public DecimalPropertyEditor( - IDataValueEditorFactory dataValueEditorFactory) - : base(dataValueEditorFactory) - { } - - /// - protected override IDataValueEditor CreateValueEditor() - { - var editor = base.CreateValueEditor(); - editor.Validators.Add(new DecimalValidator()); - return editor; - } + } - /// - protected override IConfigurationEditor CreateConfigurationEditor() => new DecimalConfigurationEditor(); + /// + protected override IDataValueEditor CreateValueEditor() + { + IDataValueEditor editor = base.CreateValueEditor(); + editor.Validators.Add(new DecimalValidator()); + return editor; } + + /// + protected override IConfigurationEditor CreateConfigurationEditor() => new DecimalConfigurationEditor(); } diff --git a/src/Umbraco.Core/PropertyEditors/DefaultPropertyIndexValueFactory.cs b/src/Umbraco.Core/PropertyEditors/DefaultPropertyIndexValueFactory.cs index 5cb11b707105..c9b149222ea6 100644 --- a/src/Umbraco.Core/PropertyEditors/DefaultPropertyIndexValueFactory.cs +++ b/src/Umbraco.Core/PropertyEditors/DefaultPropertyIndexValueFactory.cs @@ -1,20 +1,20 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Provides a default implementation for +/// , returning a single field to index containing the property value. +/// +public class DefaultPropertyIndexValueFactory : IPropertyIndexValueFactory { - /// - /// Provides a default implementation for , returning a single field to index containing the property value. - /// - public class DefaultPropertyIndexValueFactory : IPropertyIndexValueFactory + /// + public IEnumerable>> GetIndexValues(IProperty property, string? culture, + string? segment, bool published) { - /// - public IEnumerable>> GetIndexValues(IProperty property, string? culture, string? segment, bool published) - { - yield return new KeyValuePair>( - property.Alias, - property.GetValue(culture, segment, published).Yield()); - } + yield return new KeyValuePair>( + property.Alias, + property.GetValue(culture, segment, published).Yield()); } } diff --git a/src/Umbraco.Core/PropertyEditors/DefaultPropertyValueConverterAttribute.cs b/src/Umbraco.Core/PropertyEditors/DefaultPropertyValueConverterAttribute.cs index a38ea29e0b1c..61b4e29f4b2b 100644 --- a/src/Umbraco.Core/PropertyEditors/DefaultPropertyValueConverterAttribute.cs +++ b/src/Umbraco.Core/PropertyEditors/DefaultPropertyValueConverterAttribute.cs @@ -1,33 +1,26 @@ -using System; +namespace Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.PropertyEditors +/// +/// Indicates that this is a default property value converter (shipped with Umbraco) +/// +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +public class DefaultPropertyValueConverterAttribute : Attribute { - /// - /// Indicates that this is a default property value converter (shipped with Umbraco) - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public class DefaultPropertyValueConverterAttribute : Attribute - { - public DefaultPropertyValueConverterAttribute() - { - DefaultConvertersToShadow = Array.Empty(); - } + public DefaultPropertyValueConverterAttribute() => DefaultConvertersToShadow = Array.Empty(); - public DefaultPropertyValueConverterAttribute(params Type[] convertersToShadow) - { - DefaultConvertersToShadow = convertersToShadow; - } + public DefaultPropertyValueConverterAttribute(params Type[] convertersToShadow) => + DefaultConvertersToShadow = convertersToShadow; - /// - /// A DefaultPropertyValueConverter can 'shadow' other default property value converters so that - /// a DefaultPropertyValueConverter can be more specific than another one. - /// - /// - /// An example where this is useful is that both the RelatedLiksEditorValueConverter and the JsonValueConverter - /// will be returned as value converters for the Related Links Property editor, however the JsonValueConverter - /// is a very generic converter and the RelatedLiksEditorValueConverter is more specific than it, so the RelatedLiksEditorValueConverter - /// can specify that it 'shadows' the JsonValueConverter. - /// - public Type[] DefaultConvertersToShadow { get; } - } + /// + /// A DefaultPropertyValueConverter can 'shadow' other default property value converters so that + /// a DefaultPropertyValueConverter can be more specific than another one. + /// + /// + /// An example where this is useful is that both the RelatedLiksEditorValueConverter and the JsonValueConverter + /// will be returned as value converters for the Related Links Property editor, however the JsonValueConverter + /// is a very generic converter and the RelatedLiksEditorValueConverter is more specific than it, so the + /// RelatedLiksEditorValueConverter + /// can specify that it 'shadows' the JsonValueConverter. + /// + public Type[] DefaultConvertersToShadow { get; } } diff --git a/src/Umbraco.Core/PropertyEditors/DropDownFlexibleConfiguration.cs b/src/Umbraco.Core/PropertyEditors/DropDownFlexibleConfiguration.cs index 4d74f4aec2c4..2f9d7ae283cc 100644 --- a/src/Umbraco.Core/PropertyEditors/DropDownFlexibleConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/DropDownFlexibleConfiguration.cs @@ -1,8 +1,8 @@ -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +public class DropDownFlexibleConfiguration : ValueListConfiguration { - public class DropDownFlexibleConfiguration : ValueListConfiguration - { - [ConfigurationField("multiple", "Enable multiple choice", "boolean", Description = "When checked, the dropdown will be a select multiple / combo box style dropdown.")] - public bool Multiple { get; set; } - } + [ConfigurationField("multiple", "Enable multiple choice", "boolean", + Description = "When checked, the dropdown will be a select multiple / combo box style dropdown.")] + public bool Multiple { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/EditorType.cs b/src/Umbraco.Core/PropertyEditors/EditorType.cs index 93d0b91b1844..19346f0748a8 100644 --- a/src/Umbraco.Core/PropertyEditors/EditorType.cs +++ b/src/Umbraco.Core/PropertyEditors/EditorType.cs @@ -1,26 +1,23 @@ -using System; +namespace Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.PropertyEditors +/// +/// Represents the type of an editor. +/// +[Flags] +public enum EditorType { /// - /// Represents the type of an editor. + /// Nothing. /// - [Flags] - public enum EditorType - { - /// - /// Nothing. - /// - Nothing = 0, + Nothing = 0, - /// - /// Property value editor. - /// - PropertyValue = 1, + /// + /// Property value editor. + /// + PropertyValue = 1, - /// - /// Macro parameter editor. - /// - MacroParameter = 2 - } + /// + /// Macro parameter editor. + /// + MacroParameter = 2 } diff --git a/src/Umbraco.Core/PropertyEditors/EmailAddressConfiguration.cs b/src/Umbraco.Core/PropertyEditors/EmailAddressConfiguration.cs index 380d54dcadc5..92f7fda58b09 100644 --- a/src/Umbraco.Core/PropertyEditors/EmailAddressConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/EmailAddressConfiguration.cs @@ -1,14 +1,12 @@ -using System; +namespace Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.PropertyEditors +/// +/// Represents the configuration for the email address value editor. +/// +public class EmailAddressConfiguration { - /// - /// Represents the configuration for the email address value editor. - /// - public class EmailAddressConfiguration - { - [ConfigurationField("IsRequired", "Required?", "hidden", Description = "Deprecated; Make this required by selecting mandatory when adding to the document type")] - [Obsolete("No longer used, use `Mandatory` for the property instead. Will be removed in the next major version")] - public bool IsRequired { get; set; } - } + [ConfigurationField("IsRequired", "Required?", "hidden", + Description = "Deprecated; Make this required by selecting mandatory when adding to the document type")] + [Obsolete("No longer used, use `Mandatory` for the property instead. Will be removed in the next major version")] + public bool IsRequired { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/EmailAddressConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/EmailAddressConfigurationEditor.cs index e1e528dda20d..d089df1c932a 100644 --- a/src/Umbraco.Core/PropertyEditors/EmailAddressConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/EmailAddressConfigurationEditor.cs @@ -1,28 +1,27 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration editor for the email address value editor. +/// +public class EmailAddressConfigurationEditor : ConfigurationEditor { - /// - /// Represents the configuration editor for the email address value editor. - /// - public class EmailAddressConfigurationEditor : ConfigurationEditor + // Scheduled for removal in v12 + [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + public EmailAddressConfigurationEditor(IIOHelper ioHelper) + : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) { - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public EmailAddressConfigurationEditor(IIOHelper ioHelper) - : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) - { - } + } - public EmailAddressConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) - { - } + public EmailAddressConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : + base(ioHelper, editorConfigurationParser) + { } } diff --git a/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerConfiguration.cs b/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerConfiguration.cs index 9c2dffb61d2b..dfa91de3b398 100644 --- a/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerConfiguration.cs @@ -1,14 +1,14 @@ -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration for the Eye Dropper picker value editor. +/// +public class EyeDropperColorPickerConfiguration { - /// - /// Represents the configuration for the Eye Dropper picker value editor. - /// - public class EyeDropperColorPickerConfiguration - { - [ConfigurationField("showAlpha", "Show alpha", "boolean", Description = "Allow alpha transparency selection.")] - public bool ShowAlpha { get; set; } + [ConfigurationField("showAlpha", "Show alpha", "boolean", Description = "Allow alpha transparency selection.")] + public bool ShowAlpha { get; set; } - [ConfigurationField("showPalette", "Show palette", "boolean", Description = "Show a palette next to the color picker.")] - public bool ShowPalette { get; set; } - } + [ConfigurationField("showPalette", "Show palette", "boolean", + Description = "Show a palette next to the color picker.")] + public bool ShowPalette { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerConfigurationEditor.cs index 49611f09b94d..02428a85a91a 100644 --- a/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerConfigurationEditor.cs @@ -1,51 +1,49 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +internal class EyeDropperColorPickerConfigurationEditor : ConfigurationEditor { - internal class EyeDropperColorPickerConfigurationEditor : ConfigurationEditor + public EyeDropperColorPickerConfigurationEditor(IIOHelper ioHelper, + IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) { - public EyeDropperColorPickerConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) - { - } + } - /// - public override Dictionary ToConfigurationEditor(EyeDropperColorPickerConfiguration? configuration) + /// + public override Dictionary + ToConfigurationEditor(EyeDropperColorPickerConfiguration? configuration) => + new Dictionary { - return new Dictionary - { - { "showAlpha", configuration?.ShowAlpha ?? false }, - { "showPalette", configuration?.ShowPalette ?? false }, - }; - } + {"showAlpha", configuration?.ShowAlpha ?? false}, {"showPalette", configuration?.ShowPalette ?? false} + }; - /// - public override EyeDropperColorPickerConfiguration FromConfigurationEditor(IDictionary? editorValues, EyeDropperColorPickerConfiguration? configuration) - { - var showAlpha = true; - var showPalette = true; + /// + public override EyeDropperColorPickerConfiguration FromConfigurationEditor( + IDictionary? editorValues, EyeDropperColorPickerConfiguration? configuration) + { + var showAlpha = true; + var showPalette = true; - if (editorValues is not null && editorValues.TryGetValue("showAlpha", out var alpha)) + if (editorValues is not null && editorValues.TryGetValue("showAlpha", out var alpha)) + { + Attempt attempt = alpha.TryConvertTo(); + if (attempt.Success) { - var attempt = alpha.TryConvertTo(); - if (attempt.Success) - showAlpha = attempt.Result; + showAlpha = attempt.Result; } + } - if (editorValues is not null && editorValues.TryGetValue("showPalette", out var palette)) + if (editorValues is not null && editorValues.TryGetValue("showPalette", out var palette)) + { + Attempt attempt = palette.TryConvertTo(); + if (attempt.Success) { - var attempt = palette.TryConvertTo(); - if (attempt.Success) - showPalette = attempt.Result; + showPalette = attempt.Result; } - - return new EyeDropperColorPickerConfiguration - { - ShowAlpha = showAlpha, - ShowPalette = showPalette - }; } + + return new EyeDropperColorPickerConfiguration {ShowAlpha = showAlpha, ShowPalette = showPalette}; } } diff --git a/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerPropertyEditor.cs index e19a3803343a..e55fbf0ffe89 100644 --- a/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerPropertyEditor.cs @@ -1,48 +1,45 @@ -using System; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; -using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.PropertyEditors -{ - [DataEditor( - Constants.PropertyEditors.Aliases.ColorPickerEyeDropper, - EditorType.PropertyValue | EditorType.MacroParameter, - "Eye Dropper Color Picker", - "eyedropper", - Icon = "icon-colorpicker", - Group = Constants.PropertyEditors.Groups.Pickers)] - public class EyeDropperColorPickerPropertyEditor : DataEditor - { - private readonly IIOHelper _ioHelper; - private readonly IEditorConfigurationParser _editorConfigurationParser; +namespace Umbraco.Cms.Core.PropertyEditors; - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public EyeDropperColorPickerPropertyEditor( - IDataValueEditorFactory dataValueEditorFactory, - IIOHelper ioHelper, - EditorType type = EditorType.PropertyValue) - : this(dataValueEditorFactory, ioHelper, StaticServiceProvider.Instance.GetRequiredService(), type) - { - } +[DataEditor( + Constants.PropertyEditors.Aliases.ColorPickerEyeDropper, + EditorType.PropertyValue | EditorType.MacroParameter, + "Eye Dropper Color Picker", + "eyedropper", + Icon = "icon-colorpicker", + Group = Constants.PropertyEditors.Groups.Pickers)] +public class EyeDropperColorPickerPropertyEditor : DataEditor +{ + private readonly IEditorConfigurationParser _editorConfigurationParser; + private readonly IIOHelper _ioHelper; - public EyeDropperColorPickerPropertyEditor( - IDataValueEditorFactory dataValueEditorFactory, - IIOHelper ioHelper, - IEditorConfigurationParser editorConfigurationParser, - EditorType type = EditorType.PropertyValue) - : base(dataValueEditorFactory, type) - { - _ioHelper = ioHelper; - _editorConfigurationParser = editorConfigurationParser; - } + // Scheduled for removal in v12 + [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + public EyeDropperColorPickerPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory, + IIOHelper ioHelper, + EditorType type = EditorType.PropertyValue) + : this(dataValueEditorFactory, ioHelper, + StaticServiceProvider.Instance.GetRequiredService(), type) + { + } - /// - protected override IConfigurationEditor CreateConfigurationEditor() => new EyeDropperColorPickerConfigurationEditor(_ioHelper, _editorConfigurationParser); + public EyeDropperColorPickerPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory, + IIOHelper ioHelper, + IEditorConfigurationParser editorConfigurationParser, + EditorType type = EditorType.PropertyValue) + : base(dataValueEditorFactory, type) + { + _ioHelper = ioHelper; + _editorConfigurationParser = editorConfigurationParser; } + + /// + protected override IConfigurationEditor CreateConfigurationEditor() => + new EyeDropperColorPickerConfigurationEditor(_ioHelper, _editorConfigurationParser); } diff --git a/src/Umbraco.Core/PropertyEditors/FileExtensionConfigItem.cs b/src/Umbraco.Core/PropertyEditors/FileExtensionConfigItem.cs index 2b1997459c98..bdb0fbc18de1 100644 --- a/src/Umbraco.Core/PropertyEditors/FileExtensionConfigItem.cs +++ b/src/Umbraco.Core/PropertyEditors/FileExtensionConfigItem.cs @@ -1,14 +1,11 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +[DataContract] +public class FileExtensionConfigItem : IFileExtensionConfigItem { - [DataContract] - public class FileExtensionConfigItem : IFileExtensionConfigItem - { - [DataMember(Name = "id")] - public int Id { get; set; } + [DataMember(Name = "id")] public int Id { get; set; } - [DataMember(Name = "value")] - public string? Value { get; set; } - } + [DataMember(Name = "value")] public string? Value { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/FileUploadConfiguration.cs b/src/Umbraco.Core/PropertyEditors/FileUploadConfiguration.cs index 2953e2a1ed2f..a3a8c57e612e 100644 --- a/src/Umbraco.Core/PropertyEditors/FileUploadConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/FileUploadConfiguration.cs @@ -1,13 +1,10 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.PropertyEditors +/// +/// Represents the configuration for the file upload address value editor. +/// +public class FileUploadConfiguration : IFileExtensionsConfig { - /// - /// Represents the configuration for the file upload address value editor. - /// - public class FileUploadConfiguration : IFileExtensionsConfig - { - [ConfigurationField("fileExtensions", "Accepted file extensions", "multivalues")] - public List FileExtensions { get; set; } = new List(); - } + [ConfigurationField("fileExtensions", "Accepted file extensions", "multivalues")] + public List FileExtensions { get; set; } = new(); } diff --git a/src/Umbraco.Core/PropertyEditors/FileUploadConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/FileUploadConfigurationEditor.cs index e8aa86e5d805..17a65d28e500 100644 --- a/src/Umbraco.Core/PropertyEditors/FileUploadConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/FileUploadConfigurationEditor.cs @@ -1,25 +1,24 @@ -using System; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration editor for the file upload value editor. +/// +public class FileUploadConfigurationEditor : ConfigurationEditor { - /// - /// Represents the configuration editor for the file upload value editor. - /// - public class FileUploadConfigurationEditor : ConfigurationEditor + // Scheduled for removal in v12 + [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + public FileUploadConfigurationEditor(IIOHelper ioHelper) + : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) { - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public FileUploadConfigurationEditor(IIOHelper ioHelper) - : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) - { - } + } - public FileUploadConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) - { - } + public FileUploadConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : + base(ioHelper, editorConfigurationParser) + { } } diff --git a/src/Umbraco.Core/PropertyEditors/GridEditor.cs b/src/Umbraco.Core/PropertyEditors/GridEditor.cs index 0e7b238900d9..8ad8ca4e4c42 100644 --- a/src/Umbraco.Core/PropertyEditors/GridEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/GridEditor.cs @@ -1,69 +1,70 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Configuration.Grid; -namespace Umbraco.Cms.Core.PropertyEditors -{ +namespace Umbraco.Cms.Core.PropertyEditors; - [DataContract] - public class GridEditor : IGridEditorConfig +[DataContract] +public class GridEditor : IGridEditorConfig +{ + public GridEditor() { - public GridEditor() - { - Config = new Dictionary(); - Alias = string.Empty; - } + Config = new Dictionary(); + Alias = string.Empty; + } - [DataMember(Name = "name", IsRequired = true)] - public string? Name { get; set; } + [DataMember(Name = "name", IsRequired = true)] + public string? Name { get; set; } - [DataMember(Name = "nameTemplate")] - public string? NameTemplate { get; set; } + [DataMember(Name = "nameTemplate")] public string? NameTemplate { get; set; } - [DataMember(Name = "alias", IsRequired = true)] - public string Alias { get; set; } + [DataMember(Name = "alias", IsRequired = true)] + public string Alias { get; set; } - [DataMember(Name = "view", IsRequired = true)] - public string? View{ get; set; } + [DataMember(Name = "view", IsRequired = true)] + public string? View { get; set; } - [DataMember(Name = "render")] - public string? Render { get; set; } + [DataMember(Name = "render")] public string? Render { get; set; } - [DataMember(Name = "icon", IsRequired = true)] - public string? Icon { get; set; } + [DataMember(Name = "icon", IsRequired = true)] + public string? Icon { get; set; } - [DataMember(Name = "config")] - public IDictionary Config { get; set; } + [DataMember(Name = "config")] public IDictionary Config { get; set; } - protected bool Equals(GridEditor other) + protected bool Equals(GridEditor other) => string.Equals(Alias, other.Alias); + + /// + /// Determines whether the specified is equal to the current + /// . + /// + /// + /// true if the specified object is equal to the current object; otherwise, false. + /// + /// The object to compare with the current object. + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) { - return string.Equals(Alias, other.Alias); + return false; } - /// - /// Determines whether the specified is equal to the current . - /// - /// - /// true if the specified object is equal to the current object; otherwise, false. - /// - /// The object to compare with the current object. - public override bool Equals(object? obj) + if (ReferenceEquals(this, obj)) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((GridEditor) obj); + return true; } - /// - /// Serves as a hash function for a particular type. - /// - /// - /// A hash code for the current . - /// - public override int GetHashCode() + if (obj.GetType() != GetType()) { - return Alias.GetHashCode(); + return false; } + + return Equals((GridEditor)obj); } + + /// + /// Serves as a hash function for a particular type. + /// + /// + /// A hash code for the current . + /// + public override int GetHashCode() => Alias.GetHashCode(); } diff --git a/src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs index b4d41f8e3394..5829188502a3 100644 --- a/src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs @@ -1,76 +1,82 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents an editor for editing the configuration of editors. +/// +public interface IConfigurationEditor { /// - /// Represents an editor for editing the configuration of editors. + /// Gets the fields. /// - public interface IConfigurationEditor - { - /// - /// Gets the fields. - /// - [DataMember(Name = "fields")] - List Fields { get; } + [DataMember(Name = "fields")] + List Fields { get; } - /// - /// Gets the default configuration. - /// - /// - /// For basic configuration editors, this will be a dictionary of key/values. For advanced editors - /// which inherit from , this will be the dictionary - /// equivalent of an actual configuration object (ie an instance of TConfiguration, obtained - /// via . - /// - [DataMember(Name = "defaultConfig")] - IDictionary DefaultConfiguration { get; } + /// + /// Gets the default configuration. + /// + /// + /// + /// For basic configuration editors, this will be a dictionary of key/values. For advanced editors + /// which inherit from , this will be the dictionary + /// equivalent of an actual configuration object (ie an instance of TConfiguration, obtained + /// via . + /// + /// + [DataMember(Name = "defaultConfig")] + IDictionary DefaultConfiguration { get; } - /// - /// Gets the default configuration object. - /// - /// - /// For basic configuration editors, this will be , ie a - /// dictionary of key/values. For advanced editors which inherit from , - /// this will be an actual configuration object (ie an instance of TConfiguration. - /// - object? DefaultConfigurationObject { get; } + /// + /// Gets the default configuration object. + /// + /// + /// + /// For basic configuration editors, this will be , ie a + /// dictionary of key/values. For advanced editors which inherit from + /// , + /// this will be an actual configuration object (ie an instance of TConfiguration. + /// + /// + object? DefaultConfigurationObject { get; } - /// - /// Determines whether a configuration object is of the type expected by the configuration editor. - /// - bool IsConfiguration(object obj); + /// + /// Determines whether a configuration object is of the type expected by the configuration editor. + /// + bool IsConfiguration(object obj); - // notes - // ToConfigurationEditor returns a dictionary, and FromConfigurationEditor accepts a dictionary. - // this is due to the way our front-end editors work, see DataTypeController.PostSave - // and DataTypeConfigurationFieldDisplayResolver - we are not going to change it now. + // notes + // ToConfigurationEditor returns a dictionary, and FromConfigurationEditor accepts a dictionary. + // this is due to the way our front-end editors work, see DataTypeController.PostSave + // and DataTypeConfigurationFieldDisplayResolver - we are not going to change it now. - /// - /// Converts the serialized database value into the actual configuration object. - /// - /// Converting the configuration object to the serialized database value is - /// achieved by simply serializing the configuration. See . - object FromDatabase(string? configurationJson, IConfigurationEditorJsonSerializer configurationEditorJsonSerializer); + /// + /// Converts the serialized database value into the actual configuration object. + /// + /// + /// Converting the configuration object to the serialized database value is + /// achieved by simply serializing the configuration. See . + /// + object FromDatabase(string? configurationJson, + IConfigurationEditorJsonSerializer configurationEditorJsonSerializer); - /// - /// Converts the values posted by the configuration editor into the actual configuration object. - /// - /// The values posted by the configuration editor. - /// The current configuration object. - object? FromConfigurationEditor(IDictionary? editorValues, object? configuration); + /// + /// Converts the values posted by the configuration editor into the actual configuration object. + /// + /// The values posted by the configuration editor. + /// The current configuration object. + object? FromConfigurationEditor(IDictionary? editorValues, object? configuration); - /// - /// Converts the configuration object to values for the configuration editor. - /// - /// The configuration. - IDictionary ToConfigurationEditor(object? configuration); + /// + /// Converts the configuration object to values for the configuration editor. + /// + /// The configuration. + IDictionary ToConfigurationEditor(object? configuration); - /// - /// Converts the configuration object to values for the value editor. - /// - /// The configuration. - IDictionary? ToValueEditor(object? configuration); - } + /// + /// Converts the configuration object to values for the value editor. + /// + /// The configuration. + IDictionary? ToValueEditor(object? configuration); } diff --git a/src/Umbraco.Core/PropertyEditors/IConfigureValueType.cs b/src/Umbraco.Core/PropertyEditors/IConfigureValueType.cs index 831d5d19fda5..09f26ee7fb18 100644 --- a/src/Umbraco.Core/PropertyEditors/IConfigureValueType.cs +++ b/src/Umbraco.Core/PropertyEditors/IConfigureValueType.cs @@ -1,18 +1,17 @@ using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents a configuration that configures the value type. +/// +/// +/// This is used in to get the value type from the configuration. +/// +public interface IConfigureValueType { /// - /// Represents a configuration that configures the value type. + /// Gets the value type. /// - /// - /// This is used in to get the value type from the configuration. - /// - public interface IConfigureValueType - { - /// - /// Gets the value type. - /// - string ValueType { get; } - } + string ValueType { get; } } diff --git a/src/Umbraco.Core/PropertyEditors/IDataEditor.cs b/src/Umbraco.Core/PropertyEditors/IDataEditor.cs index dba30aaf6046..6f72f29cf3a6 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataEditor.cs @@ -1,75 +1,73 @@ -using System.Collections.Generic; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents a data editor. +/// +/// This is the base interface for parameter and property editors. +public interface IDataEditor : IDiscoverable { /// - /// Represents a data editor. + /// Gets the alias of the editor. /// - /// This is the base interface for parameter and property editors. - public interface IDataEditor : IDiscoverable - { - /// - /// Gets the alias of the editor. - /// - string Alias { get; } + string Alias { get; } - /// - /// Gets the type of the editor. - /// - /// An editor can be a property value editor, or a parameter editor. - EditorType Type { get; } + /// + /// Gets the type of the editor. + /// + /// An editor can be a property value editor, or a parameter editor. + EditorType Type { get; } - /// - /// Gets the name of the editor. - /// - string Name { get; } + /// + /// Gets the name of the editor. + /// + string Name { get; } - /// - /// Gets the icon of the editor. - /// - /// Can be used to display editors when presenting them. - string Icon { get; } + /// + /// Gets the icon of the editor. + /// + /// Can be used to display editors when presenting them. + string Icon { get; } - /// - /// Gets the group of the editor. - /// - /// Can be used to organize editors when presenting them. - string Group { get; } + /// + /// Gets the group of the editor. + /// + /// Can be used to organize editors when presenting them. + string Group { get; } - /// - /// Gets a value indicating whether the editor is deprecated. - /// - /// Deprecated editors are supported but not proposed in the UI. - bool IsDeprecated { get; } + /// + /// Gets a value indicating whether the editor is deprecated. + /// + /// Deprecated editors are supported but not proposed in the UI. + bool IsDeprecated { get; } - /// - /// Gets a value editor. - /// - IDataValueEditor GetValueEditor(); // TODO: should be configured?! + /// + /// Gets the configuration for the value editor. + /// + IDictionary? DefaultConfiguration { get; } - /// - /// Gets a configured value editor. - /// - IDataValueEditor GetValueEditor(object? configuration); + /// + /// Gets the index value factory for the editor. + /// + IPropertyIndexValueFactory PropertyIndexValueFactory { get; } - /// - /// Gets the configuration for the value editor. - /// - IDictionary? DefaultConfiguration { get; } + /// + /// Gets a value editor. + /// + IDataValueEditor GetValueEditor(); // TODO: should be configured?! - /// - /// Gets an editor to edit the value editor configuration. - /// - /// - /// Is expected to throw if the editor does not support being configured, e.g. for most parameter editors. - /// - IConfigurationEditor GetConfigurationEditor(); + /// + /// Gets a configured value editor. + /// + IDataValueEditor GetValueEditor(object? configuration); - /// - /// Gets the index value factory for the editor. - /// - IPropertyIndexValueFactory PropertyIndexValueFactory { get; } - } + /// + /// Gets an editor to edit the value editor configuration. + /// + /// + /// Is expected to throw if the editor does not support being configured, e.g. for most parameter editors. + /// + IConfigurationEditor GetConfigurationEditor(); } diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueEditorFactory.cs b/src/Umbraco.Core/PropertyEditors/IDataValueEditorFactory.cs index 663c7db6d6b0..070230ac5819 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataValueEditorFactory.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataValueEditorFactory.cs @@ -1,11 +1,9 @@ -using System; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +public interface IDataValueEditorFactory { - public interface IDataValueEditorFactory - { - TDataValueEditor Create(params object[] args) - where TDataValueEditor : class, IDataValueEditor; - } + TDataValueEditor Create(params object[] args) + where TDataValueEditor : class, IDataValueEditor; } diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs b/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs index d44d732464ae..21c4ca8f5caf 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs @@ -1,19 +1,17 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Editors; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Resolve references from values +/// +public interface IDataValueReference { /// - /// Resolve references from values + /// Returns any references contained in the value /// - public interface IDataValueReference - { - /// - /// Returns any references contained in the value - /// - /// - /// - IEnumerable GetReferences(object? value); - } + /// + /// + IEnumerable GetReferences(object? value); } diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueReferenceFactory.cs b/src/Umbraco.Core/PropertyEditors/IDataValueReferenceFactory.cs index fd1f2f50d2e9..b57cd1d37ac5 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataValueReferenceFactory.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataValueReferenceFactory.cs @@ -1,18 +1,16 @@ -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +public interface IDataValueReferenceFactory { - public interface IDataValueReferenceFactory - { - /// - /// Gets a value indicating whether the DataValueReference lookup supports a datatype (data editor). - /// - /// - /// A value indicating whether the converter supports a datatype. - bool IsForEditor(IDataEditor? dataEditor); + /// + /// Gets a value indicating whether the DataValueReference lookup supports a datatype (data editor). + /// + /// + /// A value indicating whether the converter supports a datatype. + bool IsForEditor(IDataEditor? dataEditor); - /// - /// - /// - /// - IDataValueReference GetDataValueReference(); - } + /// + /// + /// + IDataValueReference GetDataValueReference(); } diff --git a/src/Umbraco.Core/PropertyEditors/IFileExtensionConfig.cs b/src/Umbraco.Core/PropertyEditors/IFileExtensionConfig.cs index 6e9e9221f629..2ed4ea50850f 100644 --- a/src/Umbraco.Core/PropertyEditors/IFileExtensionConfig.cs +++ b/src/Umbraco.Core/PropertyEditors/IFileExtensionConfig.cs @@ -1,12 +1,9 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.PropertyEditors +/// +/// Marker interface for any editor configuration that supports defining file extensions +/// +public interface IFileExtensionsConfig { - /// - /// Marker interface for any editor configuration that supports defining file extensions - /// - public interface IFileExtensionsConfig - { - List FileExtensions { get; set; } - } + List FileExtensions { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/IFileExtensionConfigItem.cs b/src/Umbraco.Core/PropertyEditors/IFileExtensionConfigItem.cs index d32005fb7f9d..4de2f0ea08ed 100644 --- a/src/Umbraco.Core/PropertyEditors/IFileExtensionConfigItem.cs +++ b/src/Umbraco.Core/PropertyEditors/IFileExtensionConfigItem.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +public interface IFileExtensionConfigItem { - public interface IFileExtensionConfigItem - { - int Id { get; set; } + int Id { get; set; } - string? Value { get; set; } - } + string? Value { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/IIgnoreUserStartNodesConfig.cs b/src/Umbraco.Core/PropertyEditors/IIgnoreUserStartNodesConfig.cs index d6c20b9cdb82..3ec973e7e227 100644 --- a/src/Umbraco.Core/PropertyEditors/IIgnoreUserStartNodesConfig.cs +++ b/src/Umbraco.Core/PropertyEditors/IIgnoreUserStartNodesConfig.cs @@ -1,10 +1,9 @@ -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Marker interface for any editor configuration that supports Ignoring user start nodes +/// +public interface IIgnoreUserStartNodesConfig { - /// - /// Marker interface for any editor configuration that supports Ignoring user start nodes - /// - public interface IIgnoreUserStartNodesConfig - { - bool IgnoreUserStartNodes { get; set; } - } + bool IgnoreUserStartNodes { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/IManifestValueValidator.cs b/src/Umbraco.Core/PropertyEditors/IManifestValueValidator.cs index 28cf26022f16..dba27bbf161c 100644 --- a/src/Umbraco.Core/PropertyEditors/IManifestValueValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/IManifestValueValidator.cs @@ -1,14 +1,13 @@ -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Defines a value validator that can be referenced in a manifest. +/// +/// If the manifest can be configured, then it should expose a Configuration property. +public interface IManifestValueValidator : IValueValidator { /// - /// Defines a value validator that can be referenced in a manifest. + /// Gets the name of the validator. /// - /// If the manifest can be configured, then it should expose a Configuration property. - public interface IManifestValueValidator : IValueValidator - { - /// - /// Gets the name of the validator. - /// - string ValidationName { get; } - } + string ValidationName { get; } } diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyCacheCompression.cs b/src/Umbraco.Core/PropertyEditors/IPropertyCacheCompression.cs index 61f31a85c988..2af36b856f9b 100644 --- a/src/Umbraco.Core/PropertyEditors/IPropertyCacheCompression.cs +++ b/src/Umbraco.Core/PropertyEditors/IPropertyCacheCompression.cs @@ -1,20 +1,19 @@ using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Determines if a property type's value should be compressed in memory +/// +/// +/// +public interface IPropertyCacheCompression { /// - /// Determines if a property type's value should be compressed in memory + /// Whether a property on the content is/should be compressed /// - /// - /// - /// - public interface IPropertyCacheCompression - {/// - /// Whether a property on the content is/should be compressed - /// - /// The content - /// The property to compress or not - /// Whether this content is the published version - bool IsCompressed(IReadOnlyContentBase content, string propertyTypeAlias, bool published); - } + /// The content + /// The property to compress or not + /// Whether this content is the published version + bool IsCompressed(IReadOnlyContentBase content, string propertyTypeAlias, bool published); } diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyCacheCompressionOptions.cs b/src/Umbraco.Core/PropertyEditors/IPropertyCacheCompressionOptions.cs index a63029fc3dc1..1cff2e7552b1 100644 --- a/src/Umbraco.Core/PropertyEditors/IPropertyCacheCompressionOptions.cs +++ b/src/Umbraco.Core/PropertyEditors/IPropertyCacheCompressionOptions.cs @@ -1,16 +1,15 @@ using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +public interface IPropertyCacheCompressionOptions { - public interface IPropertyCacheCompressionOptions - { - /// - /// Whether a property on the content is/should be compressed - /// - /// The content - /// The property to compress or not - /// The datatype of the property to compress or not - /// Whether this content is the published version - bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor, bool published); - } + /// + /// Whether a property on the content is/should be compressed + /// + /// The content + /// The property to compress or not + /// The datatype of the property to compress or not + /// Whether this content is the published version + bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor, bool published); } diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyIndexValueFactory.cs b/src/Umbraco.Core/PropertyEditors/IPropertyIndexValueFactory.cs index 6ac6b46f5040..98ed077cb4cc 100644 --- a/src/Umbraco.Core/PropertyEditors/IPropertyIndexValueFactory.cs +++ b/src/Umbraco.Core/PropertyEditors/IPropertyIndexValueFactory.cs @@ -1,24 +1,27 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents a property index value factory. +/// +public interface IPropertyIndexValueFactory { /// - /// Represents a property index value factory. + /// Gets the index values for a property. /// - public interface IPropertyIndexValueFactory - { - /// - /// Gets the index values for a property. - /// - /// - /// Returns key-value pairs, where keys are indexed field names. By default, that would be the property alias, - /// and there would be only one pair, but some implementations (see for instance the grid one) may return more than - /// one pair, with different indexed field names. - /// And then, values are an enumerable of objects, because each indexed field can in turn have multiple - /// values. By default, there would be only one object: the property value. But some implementations may return - /// more than one value for a given field. - /// - IEnumerable>> GetIndexValues(IProperty property, string? culture, string? segment, bool published); - } + /// + /// + /// Returns key-value pairs, where keys are indexed field names. By default, that would be the property alias, + /// and there would be only one pair, but some implementations (see for instance the grid one) may return more than + /// one pair, with different indexed field names. + /// + /// + /// And then, values are an enumerable of objects, because each indexed field can in turn have multiple + /// values. By default, there would be only one object: the property value. But some implementations may return + /// more than one value for a given field. + /// + /// + IEnumerable>> GetIndexValues(IProperty property, string? culture, + string? segment, bool published); } diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs index 499a691204c9..4d274514e8ea 100644 --- a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs @@ -1,112 +1,135 @@ -using System; -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Provides published content properties conversion service. +/// +/// This is not a simple "value converter" because it really works only for properties. +public interface IPropertyValueConverter : IDiscoverable { /// - /// Provides published content properties conversion service. + /// Gets a value indicating whether the converter supports a property type. /// - /// This is not a simple "value converter" because it really works only for properties. - public interface IPropertyValueConverter : IDiscoverable - { - /// - /// Gets a value indicating whether the converter supports a property type. - /// - /// The property type. - /// A value indicating whether the converter supports a property type. - bool IsConverter(IPublishedPropertyType propertyType); + /// The property type. + /// A value indicating whether the converter supports a property type. + bool IsConverter(IPublishedPropertyType propertyType); - /// - /// Determines whether a value is an actual value, or not a value. - /// - /// - /// Called for Source, Inter and Object levels, until one does not return null. - /// Can return true (is a value), false (is not a value), or null to indicate that it - /// cannot be determined at the specified level. For instance, if source is a string that - /// could contain JSON, the decision could be made on the intermediate value. Or, if it is - /// a picker, it could be made on the object value (the actual picked object). - /// - bool? IsValue(object? value, PropertyValueLevel level); + /// + /// Determines whether a value is an actual value, or not a value. + /// + /// + /// Called for Source, Inter and Object levels, until one does not return null. + /// + /// Can return true (is a value), false (is not a value), or null to indicate that it + /// cannot be determined at the specified level. For instance, if source is a string that + /// could contain JSON, the decision could be made on the intermediate value. Or, if it is + /// a picker, it could be made on the object value (the actual picked object). + /// + /// + bool? IsValue(object? value, PropertyValueLevel level); - /// - /// Gets the type of values returned by the converter. - /// - /// The property type. - /// The CLR type of values returned by the converter. - /// Some of the CLR types may be generated, therefore this method cannot directly return - /// a Type object (which may not exist yet). In which case it needs to return a ModelType instance. - Type GetPropertyValueType(IPublishedPropertyType propertyType); + /// + /// Gets the type of values returned by the converter. + /// + /// The property type. + /// The CLR type of values returned by the converter. + /// + /// Some of the CLR types may be generated, therefore this method cannot directly return + /// a Type object (which may not exist yet). In which case it needs to return a ModelType instance. + /// + Type GetPropertyValueType(IPublishedPropertyType propertyType); - /// - /// Gets the property cache level. - /// - /// The property type. - /// The property cache level. - PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType); + /// + /// Gets the property cache level. + /// + /// The property type. + /// The property cache level. + PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType); - /// - /// Converts a property source value to an intermediate value. - /// - /// The property set owning the property. - /// The property type. - /// The source value. - /// A value indicating whether conversion should take place in preview mode. - /// The result of the conversion. - /// - /// The converter should know how to convert a null source value, meaning that no - /// value has been assigned to the property. The intermediate value can be null. - /// With the XML cache, source values come from the XML cache and therefore are strings. - /// With objects caches, source values would come from the database and therefore be either - /// ints, DateTimes, decimals, or strings. - /// The converter should be prepared to handle both situations. - /// When source values are strings, the converter must handle empty strings, whitespace - /// strings, and xml-whitespace strings appropriately, ie it should know whether to preserve - /// white spaces. - /// - object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview); + /// + /// Converts a property source value to an intermediate value. + /// + /// The property set owning the property. + /// The property type. + /// The source value. + /// A value indicating whether conversion should take place in preview mode. + /// The result of the conversion. + /// + /// + /// The converter should know how to convert a null source value, meaning that no + /// value has been assigned to the property. The intermediate value can be null. + /// + /// With the XML cache, source values come from the XML cache and therefore are strings. + /// + /// With objects caches, source values would come from the database and therefore be either + /// ints, DateTimes, decimals, or strings. + /// + /// The converter should be prepared to handle both situations. + /// + /// When source values are strings, the converter must handle empty strings, whitespace + /// strings, and xml-whitespace strings appropriately, ie it should know whether to preserve + /// white spaces. + /// + /// + object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, + bool preview); - /// - /// Converts a property intermediate value to an Object value. - /// - /// The property set owning the property. - /// The property type. - /// The reference cache level. - /// The intermediate value. - /// A value indicating whether conversion should take place in preview mode. - /// The result of the conversion. - /// - /// The converter should know how to convert a null intermediate value, or any intermediate value - /// indicating that no value has been assigned to the property. It is up to the converter to determine - /// what to return in that case: either null, or the default value... - /// The is passed to the converter so that it can be, in turn, - /// passed to eg a PublishedFragment constructor. It is used by the fragment and the properties to manage - /// the cache levels of property values. It is not meant to be used by the converter. - /// - object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview); + /// + /// Converts a property intermediate value to an Object value. + /// + /// The property set owning the property. + /// The property type. + /// The reference cache level. + /// The intermediate value. + /// A value indicating whether conversion should take place in preview mode. + /// The result of the conversion. + /// + /// + /// The converter should know how to convert a null intermediate value, or any intermediate value + /// indicating that no value has been assigned to the property. It is up to the converter to determine + /// what to return in that case: either null, or the default value... + /// + /// + /// The is passed to the converter so that it can be, in turn, + /// passed to eg a PublishedFragment constructor. It is used by the fragment and the properties to manage + /// the cache levels of property values. It is not meant to be used by the converter. + /// + /// + object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel referenceCacheLevel, object? inter, bool preview); - /// - /// Converts a property intermediate value to an XPath value. - /// - /// The property set owning the property. - /// The property type. - /// The reference cache level. - /// The intermediate value. - /// A value indicating whether conversion should take place in preview mode. - /// The result of the conversion. - /// - /// The converter should know how to convert a null intermediate value, or any intermediate value - /// indicating that no value has been assigned to the property. It is up to the converter to determine - /// what to return in that case: either null, or the default value... - /// If successful, the result should be either null, a string, or an XPathNavigator - /// instance. Whether an xml-whitespace string should be returned as null or literally, is - /// up to the converter. - /// The converter may want to return an XML fragment that represent a part of the content tree, - /// but should pay attention not to create infinite loops that would kill XPath and XSLT. - /// The is passed to the converter so that it can be, in turn, - /// passed to eg a PublishedFragment constructor. It is used by the fragment and the properties to manage - /// the cache levels of property values. It is not meant to be used by the converter. - /// - object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview); - } + /// + /// Converts a property intermediate value to an XPath value. + /// + /// The property set owning the property. + /// The property type. + /// The reference cache level. + /// The intermediate value. + /// A value indicating whether conversion should take place in preview mode. + /// The result of the conversion. + /// + /// + /// The converter should know how to convert a null intermediate value, or any intermediate value + /// indicating that no value has been assigned to the property. It is up to the converter to determine + /// what to return in that case: either null, or the default value... + /// + /// + /// If successful, the result should be either null, a string, or an XPathNavigator + /// instance. Whether an xml-whitespace string should be returned as null or literally, is + /// up to the converter. + /// + /// + /// The converter may want to return an XML fragment that represent a part of the content tree, + /// but should pay attention not to create infinite loops that would kill XPath and XSLT. + /// + /// + /// The is passed to the converter so that it can be, in turn, + /// passed to eg a PublishedFragment constructor. It is used by the fragment and the properties to manage + /// the cache levels of property values. It is not meant to be used by the converter. + /// + /// + object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel referenceCacheLevel, object? inter, bool preview); } diff --git a/src/Umbraco.Core/PropertyEditors/IValueFormatValidator.cs b/src/Umbraco.Core/PropertyEditors/IValueFormatValidator.cs index 9674eaea9895..fc5e691109ae 100644 --- a/src/Umbraco.Core/PropertyEditors/IValueFormatValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/IValueFormatValidator.cs @@ -1,24 +1,22 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Defines a value format validator. +/// +public interface IValueFormatValidator { /// - /// Defines a value format validator. + /// Validates a value. /// - public interface IValueFormatValidator - { - /// - /// Validates a value. - /// - /// The value to validate. - /// The value type. - /// A format definition. - /// Validation results. - /// - /// The is expected to be a valid regular expression. - /// This is used to validate values against the property type validation regular expression. - /// - IEnumerable ValidateFormat(object? value, string valueType, string format); - } + /// The value to validate. + /// The value type. + /// A format definition. + /// Validation results. + /// + /// The is expected to be a valid regular expression. + /// This is used to validate values against the property type validation regular expression. + /// + IEnumerable ValidateFormat(object? value, string valueType, string format); } diff --git a/src/Umbraco.Core/PropertyEditors/IValueRequiredValidator.cs b/src/Umbraco.Core/PropertyEditors/IValueRequiredValidator.cs index 439bfcdc810c..1150ba992b9c 100644 --- a/src/Umbraco.Core/PropertyEditors/IValueRequiredValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/IValueRequiredValidator.cs @@ -1,22 +1,20 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Defines a required value validator. +/// +public interface IValueRequiredValidator { /// - /// Defines a required value validator. + /// Validates a value. /// - public interface IValueRequiredValidator - { - /// - /// Validates a value. - /// - /// The value to validate. - /// The value type. - /// Validation results. - /// - /// This is used to validate values when the property type specifies that a value is required. - /// - IEnumerable ValidateRequired(object? value, string valueType); - } + /// The value to validate. + /// The value type. + /// Validation results. + /// + /// This is used to validate values when the property type specifies that a value is required. + /// + IEnumerable ValidateRequired(object? value, string valueType); } diff --git a/src/Umbraco.Core/PropertyEditors/IValueValidator.cs b/src/Umbraco.Core/PropertyEditors/IValueValidator.cs index b4304fad5933..b9000e817c6a 100644 --- a/src/Umbraco.Core/PropertyEditors/IValueValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/IValueValidator.cs @@ -1,23 +1,24 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Defines a value validator. +/// +public interface IValueValidator { /// - /// Defines a value validator. + /// Validates a value. /// - public interface IValueValidator - { - /// - /// Validates a value. - /// - /// The value to validate. - /// The value type. - /// A datatype configuration. - /// Validation results. - /// - /// The value can be a string, a Json structure (JObject, JArray...)... corresponding to what was posted by an editor. - /// - IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration); - } + /// The value to validate. + /// The value type. + /// A datatype configuration. + /// Validation results. + /// + /// + /// The value can be a string, a Json structure (JObject, JArray...)... corresponding to what was posted by an + /// editor. + /// + /// + IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration); } diff --git a/src/Umbraco.Core/PropertyEditors/IntegerConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/IntegerConfigurationEditor.cs index e7c2114dd2fa..a9b74330fc02 100644 --- a/src/Umbraco.Core/PropertyEditors/IntegerConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/IntegerConfigurationEditor.cs @@ -1,37 +1,36 @@ using Umbraco.Cms.Core.PropertyEditors.Validators; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// A custom pre-value editor class to deal with the legacy way that the pre-value data is stored. +/// +public class IntegerConfigurationEditor : ConfigurationEditor { - /// - /// A custom pre-value editor class to deal with the legacy way that the pre-value data is stored. - /// - public class IntegerConfigurationEditor : ConfigurationEditor + public IntegerConfigurationEditor() { - public IntegerConfigurationEditor() + Fields.Add(new ConfigurationField(new IntegerValidator()) { - Fields.Add(new ConfigurationField(new IntegerValidator()) - { - Description = "Enter the minimum amount of number to be entered", - Key = "min", - View = "number", - Name = "Minimum" - }); + Description = "Enter the minimum amount of number to be entered", + Key = "min", + View = "number", + Name = "Minimum" + }); - Fields.Add(new ConfigurationField(new IntegerValidator()) - { - Description = "Enter the intervals amount between each step of number to be entered", - Key = "step", - View = "number", - Name = "Step Size" - }); + Fields.Add(new ConfigurationField(new IntegerValidator()) + { + Description = "Enter the intervals amount between each step of number to be entered", + Key = "step", + View = "number", + Name = "Step Size" + }); - Fields.Add(new ConfigurationField(new IntegerValidator()) - { - Description = "Enter the maximum amount of number to be entered", - Key = "max", - View = "number", - Name = "Maximum" - }); - } + Fields.Add(new ConfigurationField(new IntegerValidator()) + { + Description = "Enter the maximum amount of number to be entered", + Key = "max", + View = "number", + Name = "Maximum" + }); } } diff --git a/src/Umbraco.Core/PropertyEditors/IntegerPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/IntegerPropertyEditor.cs index f243158db31e..138a7cee1bd6 100644 --- a/src/Umbraco.Core/PropertyEditors/IntegerPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/IntegerPropertyEditor.cs @@ -1,38 +1,33 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors.Validators; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents an integer property and parameter editor. +/// +[DataEditor( + Constants.PropertyEditors.Aliases.Integer, + EditorType.PropertyValue | EditorType.MacroParameter, + "Numeric", + "integer", + ValueType = ValueTypes.Integer)] +public class IntegerPropertyEditor : DataEditor { - /// - /// Represents an integer property and parameter editor. - /// - [DataEditor( - Constants.PropertyEditors.Aliases.Integer, - EditorType.PropertyValue | EditorType.MacroParameter, - "Numeric", - "integer", - ValueType = ValueTypes.Integer)] - public class IntegerPropertyEditor : DataEditor + public IntegerPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory) + : base(dataValueEditorFactory) { - public IntegerPropertyEditor( - IDataValueEditorFactory dataValueEditorFactory) - : base(dataValueEditorFactory) - { } - - /// - protected override IDataValueEditor CreateValueEditor() - { - var editor = base.CreateValueEditor(); - editor.Validators.Add(new IntegerValidator()); // ensure the value is validated - return editor; - } + } - /// - protected override IConfigurationEditor CreateConfigurationEditor() => new IntegerConfigurationEditor(); + /// + protected override IDataValueEditor CreateValueEditor() + { + IDataValueEditor editor = base.CreateValueEditor(); + editor.Validators.Add(new IntegerValidator()); // ensure the value is validated + return editor; } + + /// + protected override IConfigurationEditor CreateConfigurationEditor() => new IntegerConfigurationEditor(); } diff --git a/src/Umbraco.Core/PropertyEditors/LabelConfiguration.cs b/src/Umbraco.Core/PropertyEditors/LabelConfiguration.cs index 28fe05d1515b..167c1743e70b 100644 --- a/src/Umbraco.Core/PropertyEditors/LabelConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/LabelConfiguration.cs @@ -1,11 +1,10 @@ -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration for the label value editor. +/// +public class LabelConfiguration : IConfigureValueType { - /// - /// Represents the configuration for the label value editor. - /// - public class LabelConfiguration : IConfigureValueType - { - [ConfigurationField(Constants.PropertyEditors.ConfigurationKeys.DataValueType, "Value type", "valuetype")] - public string ValueType { get; set; } = ValueTypes.String; - } + [ConfigurationField(Constants.PropertyEditors.ConfigurationKeys.DataValueType, "Value type", "valuetype")] + public string ValueType { get; set; } = ValueTypes.String; } diff --git a/src/Umbraco.Core/PropertyEditors/LabelConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/LabelConfigurationEditor.cs index b2a214f729bd..24a6df1bacf1 100644 --- a/src/Umbraco.Core/PropertyEditors/LabelConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/LabelConfigurationEditor.cs @@ -1,49 +1,50 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration for the label value editor. +/// +public class LabelConfigurationEditor : ConfigurationEditor { - /// - /// Represents the configuration for the label value editor. - /// - public class LabelConfigurationEditor : ConfigurationEditor + // Scheduled for removal in v12 + [Obsolete("Please use constructor that takes and IEditorConfigurationParser instead")] + public LabelConfigurationEditor(IIOHelper ioHelper) + : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) { - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes and IEditorConfigurationParser instead")] - public LabelConfigurationEditor(IIOHelper ioHelper) - : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) - { - } + } - public LabelConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) - { - } + public LabelConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base( + ioHelper, editorConfigurationParser) + { + } - /// - public override LabelConfiguration FromConfigurationEditor(IDictionary? editorValues, LabelConfiguration? configuration) - { - var newConfiguration = new LabelConfiguration(); + /// + public override LabelConfiguration FromConfigurationEditor(IDictionary? editorValues, + LabelConfiguration? configuration) + { + var newConfiguration = new LabelConfiguration(); - // get the value type - // not simply deserializing Json because we want to validate the valueType + // get the value type + // not simply deserializing Json because we want to validate the valueType - if (editorValues is not null && editorValues.TryGetValue(Cms.Core.Constants.PropertyEditors.ConfigurationKeys.DataValueType, out var valueTypeObj) - && valueTypeObj is string stringValue) + if (editorValues is not null && editorValues.TryGetValue( + Constants.PropertyEditors.ConfigurationKeys.DataValueType, + out var valueTypeObj) + && valueTypeObj is string stringValue) + { + if (!string.IsNullOrWhiteSpace(stringValue) && ValueTypes.IsValue(stringValue)) // validate { - if (!string.IsNullOrWhiteSpace(stringValue) && ValueTypes.IsValue(stringValue)) // validate - newConfiguration.ValueType = stringValue; + newConfiguration.ValueType = stringValue; } - - return newConfiguration; } - + return newConfiguration; } } diff --git a/src/Umbraco.Core/PropertyEditors/LabelPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/LabelPropertyEditor.cs index c142b581d00d..a1b448c82951 100644 --- a/src/Umbraco.Core/PropertyEditors/LabelPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/LabelPropertyEditor.cs @@ -1,7 +1,6 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; @@ -10,61 +9,64 @@ using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents a property editor for label properties. +/// +[DataEditor( + Constants.PropertyEditors.Aliases.Label, + "Label", + "readonlyvalue", + Icon = "icon-readonly")] +public class LabelPropertyEditor : DataEditor { + private readonly IEditorConfigurationParser _editorConfigurationParser; + private readonly IIOHelper _ioHelper; + + // Scheduled for removal in v12 + [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + public LabelPropertyEditor(IDataValueEditorFactory dataValueEditorFactory, + IIOHelper ioHelper) + : this(dataValueEditorFactory, ioHelper, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + /// - /// Represents a property editor for label properties. + /// Initializes a new instance of the class. /// - [DataEditor( - Cms.Core.Constants.PropertyEditors.Aliases.Label, - "Label", - "readonlyvalue", - Icon = "icon-readonly")] - public class LabelPropertyEditor : DataEditor + public LabelPropertyEditor(IDataValueEditorFactory dataValueEditorFactory, + IIOHelper ioHelper, + IEditorConfigurationParser editorConfigurationParser) + : base(dataValueEditorFactory) { - private readonly IIOHelper _ioHelper; - private readonly IEditorConfigurationParser _editorConfigurationParser; + _ioHelper = ioHelper; + _editorConfigurationParser = editorConfigurationParser; + } - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public LabelPropertyEditor(IDataValueEditorFactory dataValueEditorFactory, - IIOHelper ioHelper) - : this(dataValueEditorFactory, ioHelper, StaticServiceProvider.Instance.GetRequiredService()) - { - } + /// + protected override IDataValueEditor CreateValueEditor() => + DataValueEditorFactory.Create(Attribute!); - /// - /// Initializes a new instance of the class. - /// - public LabelPropertyEditor(IDataValueEditorFactory dataValueEditorFactory, - IIOHelper ioHelper, - IEditorConfigurationParser editorConfigurationParser) - : base(dataValueEditorFactory) + /// + protected override IConfigurationEditor CreateConfigurationEditor() => + new LabelConfigurationEditor(_ioHelper, _editorConfigurationParser); + + // provides the property value editor + internal class LabelPropertyValueEditor : DataValueEditor + { + public LabelPropertyValueEditor( + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper, + DataEditorAttribute attribute) + : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) { - _ioHelper = ioHelper; - _editorConfigurationParser = editorConfigurationParser; } /// - protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create(Attribute!); - - /// - protected override IConfigurationEditor CreateConfigurationEditor() => new LabelConfigurationEditor(_ioHelper, _editorConfigurationParser); - - // provides the property value editor - internal class LabelPropertyValueEditor : DataValueEditor - { - public LabelPropertyValueEditor( - ILocalizedTextService localizedTextService, - IShortStringHelper shortStringHelper, - IJsonSerializer jsonSerializer, - IIOHelper ioHelper, - DataEditorAttribute attribute) - : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) - { } - - /// - public override bool IsReadOnly => true; - } + public override bool IsReadOnly => true; } } diff --git a/src/Umbraco.Core/PropertyEditors/ListViewConfiguration.cs b/src/Umbraco.Core/PropertyEditors/ListViewConfiguration.cs index 055867e80bdd..9a218af49be0 100644 --- a/src/Umbraco.Core/PropertyEditors/ListViewConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/ListViewConfiguration.cs @@ -1,128 +1,136 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration for the listview value editor. +/// +public class ListViewConfiguration { - /// - /// Represents the configuration for the listview value editor. - /// - public class ListViewConfiguration + public ListViewConfiguration() { - public ListViewConfiguration() - { - // initialize defaults + // initialize defaults - PageSize = 10; - OrderBy = "SortOrder"; - OrderDirection = "asc"; + PageSize = 10; + OrderBy = "SortOrder"; + OrderDirection = "asc"; - BulkActionPermissions = new BulkActionPermissionSettings - { - AllowBulkPublish = true, - AllowBulkUnpublish = true, - AllowBulkCopy = true, - AllowBulkMove = true, - AllowBulkDelete = true - }; - - Layouts = new[] + BulkActionPermissions = new BulkActionPermissionSettings + { + AllowBulkPublish = true, + AllowBulkUnpublish = true, + AllowBulkCopy = true, + AllowBulkMove = true, + AllowBulkDelete = true + }; + + Layouts = new[] + { + new Layout { - new Layout { Name = "List", Icon = "icon-list", IsSystem = 1, Selected = true, Path = "views/propertyeditors/listview/layouts/list/list.html" }, - new Layout { Name = "Grid", Icon = "icon-thumbnails-small", IsSystem = 1, Selected = true, Path = "views/propertyeditors/listview/layouts/grid/grid.html" } - }; - - IncludeProperties = new[] + Name = "List", + Icon = "icon-list", + IsSystem = 1, + Selected = true, + Path = "views/propertyeditors/listview/layouts/list/list.html" + }, + new Layout { - new Property { Alias = "sortOrder", Header = "Sort order", IsSystem = 1 }, - new Property { Alias = "updateDate", Header = "Last edited", IsSystem = 1 }, - new Property { Alias = "owner", Header = "Created by", IsSystem = 1 } - }; - } + Name = "Grid", + Icon = "icon-thumbnails-small", + IsSystem = 1, + Selected = true, + Path = "views/propertyeditors/listview/layouts/grid/grid.html" + } + }; + + IncludeProperties = new[] + { + new Property {Alias = "sortOrder", Header = "Sort order", IsSystem = 1}, + new Property {Alias = "updateDate", Header = "Last edited", IsSystem = 1}, + new Property {Alias = "owner", Header = "Created by", IsSystem = 1} + }; + } - [ConfigurationField("pageSize", "Page Size", "number", Description = "Number of items per page")] - public int PageSize { get; set; } + [ConfigurationField("pageSize", "Page Size", "number", Description = "Number of items per page")] + public int PageSize { get; set; } - [ConfigurationField("orderBy", "Order By", "views/propertyeditors/listview/sortby.prevalues.html", - Description = "The default sort order for the list")] - public string OrderBy { get; set; } + [ConfigurationField("orderBy", "Order By", "views/propertyeditors/listview/sortby.prevalues.html", + Description = "The default sort order for the list")] + public string OrderBy { get; set; } - [ConfigurationField("orderDirection", "Order Direction", "views/propertyeditors/listview/orderDirection.prevalues.html")] - public string OrderDirection { get; set; } + [ConfigurationField("orderDirection", "Order Direction", + "views/propertyeditors/listview/orderDirection.prevalues.html")] + public string OrderDirection { get; set; } - [ConfigurationField("includeProperties", "Columns Displayed", "views/propertyeditors/listview/includeproperties.prevalues.html", - Description = "The properties that will be displayed for each column")] - public Property[] IncludeProperties { get; set; } + [ConfigurationField("includeProperties", "Columns Displayed", + "views/propertyeditors/listview/includeproperties.prevalues.html", + Description = "The properties that will be displayed for each column")] + public Property[] IncludeProperties { get; set; } - [ConfigurationField("layouts", "Layouts", "views/propertyeditors/listview/layouts.prevalues.html")] - public Layout[] Layouts { get; set; } + [ConfigurationField("layouts", "Layouts", "views/propertyeditors/listview/layouts.prevalues.html")] + public Layout[] Layouts { get; set; } - [ConfigurationField("bulkActionPermissions", "Bulk Action Permissions", "views/propertyeditors/listview/bulkActionPermissions.prevalues.html", - Description = "The bulk actions that are allowed from the list view")] - public BulkActionPermissionSettings BulkActionPermissions { get; set; } = new BulkActionPermissionSettings(); // TODO: managing defaults? + [ConfigurationField("bulkActionPermissions", "Bulk Action Permissions", + "views/propertyeditors/listview/bulkActionPermissions.prevalues.html", + Description = "The bulk actions that are allowed from the list view")] + public BulkActionPermissionSettings BulkActionPermissions { get; set; } = new(); // TODO: managing defaults? - [ConfigurationField("icon", "Content app icon", "views/propertyeditors/listview/icon.prevalues.html", Description = "The icon of the listview content app")] - public string? Icon { get; set; } + [ConfigurationField("icon", "Content app icon", "views/propertyeditors/listview/icon.prevalues.html", + Description = "The icon of the listview content app")] + public string? Icon { get; set; } - [ConfigurationField("tabName", "Content app name", "textstring", Description = "The name of the listview content app (default if empty: 'Child Items')")] - public string? TabName { get; set; } + [ConfigurationField("tabName", "Content app name", "textstring", + Description = "The name of the listview content app (default if empty: 'Child Items')")] + public string? TabName { get; set; } - [ConfigurationField("showContentFirst", "Show Content App First", "boolean", Description = "Enable this to show the content app by default instead of the list view app")] - public bool ShowContentFirst { get; set; } + [ConfigurationField("showContentFirst", "Show Content App First", "boolean", + Description = "Enable this to show the content app by default instead of the list view app")] + public bool ShowContentFirst { get; set; } - [ConfigurationField("useInfiniteEditor", "Edit in Infinite Editor", "boolean", Description = "Enable this to use infinite editing to edit the content of the list view")] - public bool UseInfiniteEditor { get; set; } + [ConfigurationField("useInfiniteEditor", "Edit in Infinite Editor", "boolean", + Description = "Enable this to use infinite editing to edit the content of the list view")] + public bool UseInfiniteEditor { get; set; } - [DataContract] - public class Property - { - [DataMember(Name = "alias")] - public string? Alias { get; set; } + [DataContract] + public class Property + { + [DataMember(Name = "alias")] public string? Alias { get; set; } - [DataMember(Name = "header")] - public string? Header { get; set; } + [DataMember(Name = "header")] public string? Header { get; set; } - [DataMember(Name = "nameTemplate")] - public string? Template { get; set; } + [DataMember(Name = "nameTemplate")] public string? Template { get; set; } - [DataMember(Name = "isSystem")] - public int IsSystem { get; set; } // TODO: bool - } + [DataMember(Name = "isSystem")] public int IsSystem { get; set; } // TODO: bool + } - [DataContract] - public class Layout - { - [DataMember(Name = "name")] - public string? Name { get; set; } + [DataContract] + public class Layout + { + [DataMember(Name = "name")] public string? Name { get; set; } - [DataMember(Name = "path")] - public string? Path { get; set; } + [DataMember(Name = "path")] public string? Path { get; set; } - [DataMember(Name = "icon")] - public string? Icon { get; set; } + [DataMember(Name = "icon")] public string? Icon { get; set; } - [DataMember(Name = "isSystem")] - public int IsSystem { get; set; } // TODO: bool + [DataMember(Name = "isSystem")] public int IsSystem { get; set; } // TODO: bool - [DataMember(Name = "selected")] - public bool Selected { get; set; } - } + [DataMember(Name = "selected")] public bool Selected { get; set; } + } - [DataContract] - public class BulkActionPermissionSettings - { - [DataMember(Name = "allowBulkPublish")] - public bool AllowBulkPublish { get; set; } = true; + [DataContract] + public class BulkActionPermissionSettings + { + [DataMember(Name = "allowBulkPublish")] + public bool AllowBulkPublish { get; set; } = true; - [DataMember(Name = "allowBulkUnpublish")] - public bool AllowBulkUnpublish { get; set; } = true; + [DataMember(Name = "allowBulkUnpublish")] + public bool AllowBulkUnpublish { get; set; } = true; - [DataMember(Name = "allowBulkCopy")] - public bool AllowBulkCopy { get; set; } = true; + [DataMember(Name = "allowBulkCopy")] public bool AllowBulkCopy { get; set; } = true; - [DataMember(Name = "allowBulkMove")] - public bool AllowBulkMove { get; set; } = true; + [DataMember(Name = "allowBulkMove")] public bool AllowBulkMove { get; set; } = true; - [DataMember(Name = "allowBulkDelete")] - public bool AllowBulkDelete { get; set; } = true; - } + [DataMember(Name = "allowBulkDelete")] public bool AllowBulkDelete { get; set; } = true; } } diff --git a/src/Umbraco.Core/PropertyEditors/ListViewConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/ListViewConfigurationEditor.cs index d673ce4ee6f0..60b100d275a8 100644 --- a/src/Umbraco.Core/PropertyEditors/ListViewConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ListViewConfigurationEditor.cs @@ -1,28 +1,27 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration editor for the listview value editor. +/// +public class ListViewConfigurationEditor : ConfigurationEditor { - /// - /// Represents the configuration editor for the listview value editor. - /// - public class ListViewConfigurationEditor : ConfigurationEditor + // Scheduled for removal in v12 + [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + public ListViewConfigurationEditor(IIOHelper ioHelper) + : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) { - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public ListViewConfigurationEditor(IIOHelper ioHelper) - : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) - { - } + } - public ListViewConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) - { - } + public ListViewConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base( + ioHelper, editorConfigurationParser) + { } } diff --git a/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollection.cs b/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollection.cs index 81b1c1fba185..e419e1d0bf25 100644 --- a/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollection.cs @@ -1,32 +1,31 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core.Composing; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +public class ManifestValueValidatorCollection : BuilderCollectionBase { - public class ManifestValueValidatorCollection : BuilderCollectionBase + public ManifestValueValidatorCollection(Func> items) : base(items) { - public ManifestValueValidatorCollection(Func> items) : base(items) - { - } + } - public IManifestValueValidator? Create(string name) - { - var v = GetByName(name); + public IManifestValueValidator? Create(string name) + { + IManifestValueValidator v = GetByName(name); - // TODO: what is this exactly? - // we cannot return this instance, need to clone it? - return (IManifestValueValidator?) Activator.CreateInstance(v.GetType()); // ouch - } + // TODO: what is this exactly? + // we cannot return this instance, need to clone it? + return (IManifestValueValidator?)Activator.CreateInstance(v.GetType()); // ouch + } - public IManifestValueValidator GetByName(string name) + public IManifestValueValidator GetByName(string name) + { + IManifestValueValidator v = this.FirstOrDefault(x => x.ValidationName.InvariantEquals(name)); + if (v == null) { - var v = this.FirstOrDefault(x => x.ValidationName.InvariantEquals(name)); - if (v == null) - throw new InvalidOperationException($"Could not find a validator named \"{name}\"."); - return v; + throw new InvalidOperationException($"Could not find a validator named \"{name}\"."); } + + return v; } } diff --git a/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs index 66a967c828ff..80dcfb8c85c6 100644 --- a/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs @@ -1,9 +1,9 @@ using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +public class ManifestValueValidatorCollectionBuilder : SetCollectionBuilderBase { - public class ManifestValueValidatorCollectionBuilder : SetCollectionBuilderBase - { - protected override ManifestValueValidatorCollectionBuilder This => this; - } + protected override ManifestValueValidatorCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/PropertyEditors/MarkdownConfiguration.cs b/src/Umbraco.Core/PropertyEditors/MarkdownConfiguration.cs index 62ddd4c053cb..94ed2104f75c 100644 --- a/src/Umbraco.Core/PropertyEditors/MarkdownConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/MarkdownConfiguration.cs @@ -1,18 +1,19 @@ -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration for the markdown value editor. +/// +public class MarkdownConfiguration { - /// - /// Represents the configuration for the markdown value editor. - /// - public class MarkdownConfiguration - { - [ConfigurationField("preview", "Preview", "boolean", Description = "Display a live preview")] - public bool DisplayLivePreview { get; set; } + [ConfigurationField("preview", "Preview", "boolean", Description = "Display a live preview")] + public bool DisplayLivePreview { get; set; } - [ConfigurationField("defaultValue", "Default value", "textarea", Description = "If value is blank, the editor will show this")] - public string? DefaultValue { get; set; } + [ConfigurationField("defaultValue", "Default value", "textarea", + Description = "If value is blank, the editor will show this")] + public string? DefaultValue { get; set; } - [ConfigurationField("overlaySize", "Overlay Size", "overlaysize", Description = "Select the width of the overlay (link picker).")] - public string? OverlaySize { get; set; } - } + [ConfigurationField("overlaySize", "Overlay Size", "overlaysize", + Description = "Select the width of the overlay (link picker).")] + public string? OverlaySize { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/MarkdownConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/MarkdownConfigurationEditor.cs index 3f9bc612759c..6647cd59ff74 100644 --- a/src/Umbraco.Core/PropertyEditors/MarkdownConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/MarkdownConfigurationEditor.cs @@ -4,15 +4,15 @@ using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration editorfor the markdown value editor. +/// +internal class MarkdownConfigurationEditor : ConfigurationEditor { - /// - /// Represents the configuration editorfor the markdown value editor. - /// - internal class MarkdownConfigurationEditor : ConfigurationEditor + public MarkdownConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base( + ioHelper, editorConfigurationParser) { - public MarkdownConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) - { - } } } diff --git a/src/Umbraco.Core/PropertyEditors/MarkdownPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/MarkdownPropertyEditor.cs index 6db2ac552e58..876cbcb8a82f 100644 --- a/src/Umbraco.Core/PropertyEditors/MarkdownPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/MarkdownPropertyEditor.cs @@ -1,52 +1,52 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents a markdown editor. +/// +[DataEditor( + Constants.PropertyEditors.Aliases.MarkdownEditor, + "Markdown editor", + "markdowneditor", + ValueType = ValueTypes.Text, + Group = Constants.PropertyEditors.Groups.RichContent, + Icon = "icon-code")] +public class MarkdownPropertyEditor : DataEditor { + private readonly IEditorConfigurationParser _editorConfigurationParser; + private readonly IIOHelper _ioHelper; + + // Scheduled for removal in v12 + [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + public MarkdownPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory, + IIOHelper ioHelper) + : this(dataValueEditorFactory, ioHelper, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + /// - /// Represents a markdown editor. + /// Initializes a new instance of the class. /// - [DataEditor( - Constants.PropertyEditors.Aliases.MarkdownEditor, - "Markdown editor", - "markdowneditor", - ValueType = ValueTypes.Text, - Group = Constants.PropertyEditors.Groups.RichContent, - Icon = "icon-code")] - public class MarkdownPropertyEditor : DataEditor + public MarkdownPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory, + IIOHelper ioHelper, + IEditorConfigurationParser editorConfigurationParser) + : base(dataValueEditorFactory) { - private readonly IIOHelper _ioHelper; - private readonly IEditorConfigurationParser _editorConfigurationParser; - - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public MarkdownPropertyEditor( - IDataValueEditorFactory dataValueEditorFactory, - IIOHelper ioHelper) - : this(dataValueEditorFactory, ioHelper, StaticServiceProvider.Instance.GetRequiredService()) - { - } - - /// - /// Initializes a new instance of the class. - /// - public MarkdownPropertyEditor( - IDataValueEditorFactory dataValueEditorFactory, - IIOHelper ioHelper, - IEditorConfigurationParser editorConfigurationParser) - : base(dataValueEditorFactory) - { - _ioHelper = ioHelper; - _editorConfigurationParser = editorConfigurationParser; - } - - /// - protected override IConfigurationEditor CreateConfigurationEditor() => new MarkdownConfigurationEditor(_ioHelper, _editorConfigurationParser); + _ioHelper = ioHelper; + _editorConfigurationParser = editorConfigurationParser; } + + /// + protected override IConfigurationEditor CreateConfigurationEditor() => + new MarkdownConfigurationEditor(_ioHelper, _editorConfigurationParser); } diff --git a/src/Umbraco.Core/PropertyEditors/MediaPicker3Configuration.cs b/src/Umbraco.Core/PropertyEditors/MediaPicker3Configuration.cs index 8b843fdf8595..0874fec2e40a 100644 --- a/src/Umbraco.Core/PropertyEditors/MediaPicker3Configuration.cs +++ b/src/Umbraco.Core/PropertyEditors/MediaPicker3Configuration.cs @@ -1,61 +1,54 @@ using System.Runtime.Serialization; +namespace Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.PropertyEditors +/// +/// Represents the configuration for the media picker value editor. +/// +public class MediaPicker3Configuration : IIgnoreUserStartNodesConfig { - /// - /// Represents the configuration for the media picker value editor. - /// - public class MediaPicker3Configuration : IIgnoreUserStartNodesConfig - { - [ConfigurationField("filter", "Accepted types", "treesourcetypepicker", - Description = "Limit to specific types")] - public string? Filter { get; set; } + [ConfigurationField("filter", "Accepted types", "treesourcetypepicker", + Description = "Limit to specific types")] + public string? Filter { get; set; } - [ConfigurationField("multiple", "Pick multiple items", "boolean", Description = "Outputs a IEnumerable")] - public bool Multiple { get; set; } + [ConfigurationField("multiple", "Pick multiple items", "boolean", Description = "Outputs a IEnumerable")] + public bool Multiple { get; set; } - [ConfigurationField("validationLimit", "Amount", "numberrange", Description = "Set a required range of medias")] - public NumberRange ValidationLimit { get; set; } = new NumberRange(); + [ConfigurationField("validationLimit", "Amount", "numberrange", Description = "Set a required range of medias")] + public NumberRange ValidationLimit { get; set; } = new(); - [DataContract] - public class NumberRange - { - [DataMember(Name = "min")] - public int? Min { get; set; } + [ConfigurationField("startNodeId", "Start node", "mediapicker")] + public Udi? StartNodeId { get; set; } - [DataMember(Name = "max")] - public int? Max { get; set; } - } + [ConfigurationField("enableLocalFocalPoint", "Enable Focal Point", "boolean")] + public bool EnableLocalFocalPoint { get; set; } - [ConfigurationField("startNodeId", "Start node", "mediapicker")] - public Udi? StartNodeId { get; set; } + [ConfigurationField("crops", "Image Crops", "views/propertyeditors/mediapicker3/prevalue/mediapicker3.crops.html", + Description = "Local crops, stored on document")] + public CropConfiguration[]? Crops { get; set; } - [ConfigurationField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, - "Ignore User Start Nodes", "boolean", - Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] - public bool IgnoreUserStartNodes { get; set; } + [ConfigurationField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, + "Ignore User Start Nodes", "boolean", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] + public bool IgnoreUserStartNodes { get; set; } - [ConfigurationField("enableLocalFocalPoint", "Enable Focal Point", "boolean")] - public bool EnableLocalFocalPoint { get; set; } + [DataContract] + public class NumberRange + { + [DataMember(Name = "min")] public int? Min { get; set; } - [ConfigurationField("crops", "Image Crops", "views/propertyeditors/mediapicker3/prevalue/mediapicker3.crops.html", Description = "Local crops, stored on document")] - public CropConfiguration[]? Crops { get; set; } + [DataMember(Name = "max")] public int? Max { get; set; } + } - [DataContract] - public class CropConfiguration - { - [DataMember(Name = "alias")] - public string? Alias { get; set; } + [DataContract] + public class CropConfiguration + { + [DataMember(Name = "alias")] public string? Alias { get; set; } - [DataMember(Name = "label")] - public string? Label { get; set; } + [DataMember(Name = "label")] public string? Label { get; set; } - [DataMember(Name = "width")] - public int Width { get; set; } + [DataMember(Name = "width")] public int Width { get; set; } - [DataMember(Name = "height")] - public int Height { get; set; } - } + [DataMember(Name = "height")] public int Height { get; set; } } } diff --git a/src/Umbraco.Core/PropertyEditors/MediaPicker3ConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/MediaPicker3ConfigurationEditor.cs index c5ab1c403ca0..f325faeb02d8 100644 --- a/src/Umbraco.Core/PropertyEditors/MediaPicker3ConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/MediaPicker3ConfigurationEditor.cs @@ -1,38 +1,36 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration editor for the media picker value editor. +/// +public class MediaPicker3ConfigurationEditor : ConfigurationEditor { + // Scheduled for removal in v12 + [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + public MediaPicker3ConfigurationEditor(IIOHelper ioHelper) + : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) + { + } + /// - /// Represents the configuration editor for the media picker value editor. + /// Initializes a new instance of the class. /// - public class MediaPicker3ConfigurationEditor : ConfigurationEditor + public MediaPicker3ConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : + base(ioHelper, editorConfigurationParser) { - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public MediaPicker3ConfigurationEditor(IIOHelper ioHelper) - : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) - { - } - - /// - /// Initializes a new instance of the class. - /// - public MediaPicker3ConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) - { - // configure fields - // this is not part of ContentPickerConfiguration, - // but is required to configure the UI editor (when editing the configuration) + // configure fields + // this is not part of ContentPickerConfiguration, + // but is required to configure the UI editor (when editing the configuration) - Field(nameof(MediaPicker3Configuration.StartNodeId)) - .Config = new Dictionary { { "idType", "udi" } }; + Field(nameof(MediaPicker3Configuration.StartNodeId)) + .Config = new Dictionary {{"idType", "udi"}}; - Field(nameof(MediaPicker3Configuration.Filter)) - .Config = new Dictionary { { "itemType", "media" } }; - } + Field(nameof(MediaPicker3Configuration.Filter)) + .Config = new Dictionary {{"itemType", "media"}}; } } diff --git a/src/Umbraco.Core/PropertyEditors/MediaPickerConfiguration.cs b/src/Umbraco.Core/PropertyEditors/MediaPickerConfiguration.cs index d18eeac644d1..c0f061b283c0 100644 --- a/src/Umbraco.Core/PropertyEditors/MediaPickerConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/MediaPickerConfiguration.cs @@ -1,25 +1,26 @@ -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration for the media picker value editor. +/// +public class MediaPickerConfiguration : IIgnoreUserStartNodesConfig { - /// - /// Represents the configuration for the media picker value editor. - /// - public class MediaPickerConfiguration : IIgnoreUserStartNodesConfig - { - [ConfigurationField("multiPicker", "Pick multiple items", "boolean")] - public bool Multiple { get; set; } + [ConfigurationField("multiPicker", "Pick multiple items", "boolean")] + public bool Multiple { get; set; } - [ConfigurationField("onlyImages", "Pick only images", "boolean", Description = "Only let the editor choose images from media.")] - public bool OnlyImages { get; set; } + [ConfigurationField("onlyImages", "Pick only images", "boolean", + Description = "Only let the editor choose images from media.")] + public bool OnlyImages { get; set; } - [ConfigurationField("disableFolderSelect", "Disable folder select", "boolean", Description = "Do not allow folders to be picked.")] - public bool DisableFolderSelect { get; set; } + [ConfigurationField("disableFolderSelect", "Disable folder select", "boolean", + Description = "Do not allow folders to be picked.")] + public bool DisableFolderSelect { get; set; } - [ConfigurationField("startNodeId", "Start node", "mediapicker")] - public Udi? StartNodeId { get; set; } + [ConfigurationField("startNodeId", "Start node", "mediapicker")] + public Udi? StartNodeId { get; set; } - [ConfigurationField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, - "Ignore User Start Nodes", "boolean", - Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] - public bool IgnoreUserStartNodes { get; set; } - } + [ConfigurationField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, + "Ignore User Start Nodes", "boolean", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] + public bool IgnoreUserStartNodes { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/MediaPickerConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/MediaPickerConfigurationEditor.cs index a3dbbc04d704..0d66439a78d8 100644 --- a/src/Umbraco.Core/PropertyEditors/MediaPickerConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/MediaPickerConfigurationEditor.cs @@ -1,49 +1,45 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration editor for the media picker value editor. +/// +public class MediaPickerConfigurationEditor : ConfigurationEditor { - /// - /// Represents the configuration editor for the media picker value editor. - /// - public class MediaPickerConfigurationEditor : ConfigurationEditor + // Scheduled for removal in v12 + [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + public MediaPickerConfigurationEditor(IIOHelper ioHelper) + : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) { - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public MediaPickerConfigurationEditor(IIOHelper ioHelper) - : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) - { - } + } - /// - /// Initializes a new instance of the class. - /// - public MediaPickerConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) - { - // configure fields - // this is not part of ContentPickerConfiguration, - // but is required to configure the UI editor (when editing the configuration) - Field(nameof(MediaPickerConfiguration.StartNodeId)) - .Config = new Dictionary { { "idType", "udi" } }; - } + /// + /// Initializes a new instance of the class. + /// + public MediaPickerConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : + base(ioHelper, editorConfigurationParser) => + // configure fields + // this is not part of ContentPickerConfiguration, + // but is required to configure the UI editor (when editing the configuration) + Field(nameof(MediaPickerConfiguration.StartNodeId)) + .Config = new Dictionary {{"idType", "udi"}}; - public override IDictionary ToValueEditor(object? configuration) - { - // get the configuration fields - var d = base.ToValueEditor(configuration); + public override IDictionary ToValueEditor(object? configuration) + { + // get the configuration fields + IDictionary d = base.ToValueEditor(configuration); - // add extra fields - // not part of ContentPickerConfiguration but used to configure the UI editor - d["idType"] = "udi"; + // add extra fields + // not part of ContentPickerConfiguration but used to configure the UI editor + d["idType"] = "udi"; - return d; - } + return d; } } diff --git a/src/Umbraco.Core/PropertyEditors/MediaUrlGeneratorCollection.cs b/src/Umbraco.Core/PropertyEditors/MediaUrlGeneratorCollection.cs index a58203c7b5c5..360ba1b023cb 100644 --- a/src/Umbraco.Core/PropertyEditors/MediaUrlGeneratorCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/MediaUrlGeneratorCollection.cs @@ -1,34 +1,32 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +public class MediaUrlGeneratorCollection : BuilderCollectionBase { - public class MediaUrlGeneratorCollection : BuilderCollectionBase + public MediaUrlGeneratorCollection(Func> items) + : base(items) { - public MediaUrlGeneratorCollection(Func> items) - : base(items) - { } + } - public bool TryGetMediaPath(string? propertyEditorAlias, object? value, out string? mediaPath) + public bool TryGetMediaPath(string? propertyEditorAlias, object? value, out string? mediaPath) + { + // We can't get a media path from a null value + // The value will be null when uploading a brand new image, since we try to get the "old path" which doesn't exist yet + if (value is not null) { - // We can't get a media path from a null value - // The value will be null when uploading a brand new image, since we try to get the "old path" which doesn't exist yet - if (value is not null) + foreach (IMediaUrlGenerator generator in this) { - foreach (IMediaUrlGenerator generator in this) + if (generator.TryGetMediaPath(propertyEditorAlias, value, out var generatorMediaPath)) { - if (generator.TryGetMediaPath(propertyEditorAlias, value, out var generatorMediaPath)) - { - mediaPath = generatorMediaPath; - return true; - } + mediaPath = generatorMediaPath; + return true; } } - - mediaPath = null; - return false; } + + mediaPath = null; + return false; } } diff --git a/src/Umbraco.Core/PropertyEditors/MediaUrlGeneratorCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/MediaUrlGeneratorCollectionBuilder.cs index 57ab93832bcb..2f4cf4b2cda9 100644 --- a/src/Umbraco.Core/PropertyEditors/MediaUrlGeneratorCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/MediaUrlGeneratorCollectionBuilder.cs @@ -1,10 +1,10 @@ using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +public class MediaUrlGeneratorCollectionBuilder : SetCollectionBuilderBase { - public class MediaUrlGeneratorCollectionBuilder : SetCollectionBuilderBase - { - protected override MediaUrlGeneratorCollectionBuilder This => this; - } + protected override MediaUrlGeneratorCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/PropertyEditors/MemberGroupPickerPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/MemberGroupPickerPropertyEditor.cs index cccf0fe2b7ae..090b667bc789 100644 --- a/src/Umbraco.Core/PropertyEditors/MemberGroupPickerPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/MemberGroupPickerPropertyEditor.cs @@ -1,23 +1,17 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; +namespace Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.PropertyEditors +[DataEditor( + Constants.PropertyEditors.Aliases.MemberGroupPicker, + "Member Group Picker", + "membergrouppicker", + ValueType = ValueTypes.Text, + Group = Constants.PropertyEditors.Groups.People, + Icon = Constants.Icons.MemberGroup)] +public class MemberGroupPickerPropertyEditor : DataEditor { - [DataEditor( - Constants.PropertyEditors.Aliases.MemberGroupPicker, - "Member Group Picker", - "membergrouppicker", - ValueType = ValueTypes.Text, - Group = Constants.PropertyEditors.Groups.People, - Icon = Constants.Icons.MemberGroup)] - public class MemberGroupPickerPropertyEditor : DataEditor + public MemberGroupPickerPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory) + : base(dataValueEditorFactory) { - public MemberGroupPickerPropertyEditor( - IDataValueEditorFactory dataValueEditorFactory) - : base(dataValueEditorFactory) - { } } } diff --git a/src/Umbraco.Core/PropertyEditors/MemberPickerConfiguration.cs b/src/Umbraco.Core/PropertyEditors/MemberPickerConfiguration.cs index 6d6fb3a8b7ca..aa3cc122a845 100644 --- a/src/Umbraco.Core/PropertyEditors/MemberPickerConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/MemberPickerConfiguration.cs @@ -1,12 +1,7 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.PropertyEditors +public class MemberPickerConfiguration : ConfigurationEditor { - public class MemberPickerConfiguration : ConfigurationEditor - { - public override IDictionary DefaultConfiguration => new Dictionary - { - { "idType", "udi" } - }; - } + public override IDictionary DefaultConfiguration => + new Dictionary {{"idType", "udi"}}; } diff --git a/src/Umbraco.Core/PropertyEditors/MemberPickerPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/MemberPickerPropertyEditor.cs index d348d6f22e5b..40d9482b2ce4 100644 --- a/src/Umbraco.Core/PropertyEditors/MemberPickerPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/MemberPickerPropertyEditor.cs @@ -1,25 +1,19 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; +namespace Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.PropertyEditors +[DataEditor( + Constants.PropertyEditors.Aliases.MemberPicker, + "Member Picker", + "memberpicker", + ValueType = ValueTypes.String, + Group = Constants.PropertyEditors.Groups.People, + Icon = Constants.Icons.Member)] +public class MemberPickerPropertyEditor : DataEditor { - [DataEditor( - Constants.PropertyEditors.Aliases.MemberPicker, - "Member Picker", - "memberpicker", - ValueType = ValueTypes.String, - Group = Constants.PropertyEditors.Groups.People, - Icon = Constants.Icons.Member)] - public class MemberPickerPropertyEditor : DataEditor + public MemberPickerPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory) + : base(dataValueEditorFactory) { - public MemberPickerPropertyEditor( - IDataValueEditorFactory dataValueEditorFactory) - : base(dataValueEditorFactory) - { } - - protected override IConfigurationEditor CreateConfigurationEditor() => new MemberPickerConfiguration(); } + + protected override IConfigurationEditor CreateConfigurationEditor() => new MemberPickerConfiguration(); } diff --git a/src/Umbraco.Core/PropertyEditors/MissingPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/MissingPropertyEditor.cs index ba1d03e7bb4c..83694b7647ee 100644 --- a/src/Umbraco.Core/PropertyEditors/MissingPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/MissingPropertyEditor.cs @@ -1,43 +1,32 @@ -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents a temporary representation of an editor for cases where a data type is created but not editor is +/// available. +/// +public class MissingPropertyEditor : IDataEditor { - /// - /// Represents a temporary representation of an editor for cases where a data type is created but not editor is available. - /// - public class MissingPropertyEditor : IDataEditor - { - public string Alias => "Umbraco.Missing"; + public string Alias => "Umbraco.Missing"; - public EditorType Type => EditorType.Nothing; + public EditorType Type => EditorType.Nothing; - public string Name => "Missing property editor"; + public string Name => "Missing property editor"; - public string Icon => string.Empty; + public string Icon => string.Empty; - public string Group => string.Empty; + public string Group => string.Empty; - public bool IsDeprecated => false; + public bool IsDeprecated => false; - public IDictionary DefaultConfiguration => throw new NotImplementedException(); + public IDictionary DefaultConfiguration => throw new NotImplementedException(); - public IPropertyIndexValueFactory PropertyIndexValueFactory => throw new NotImplementedException(); + public IPropertyIndexValueFactory PropertyIndexValueFactory => throw new NotImplementedException(); - public IConfigurationEditor GetConfigurationEditor() - { - return new ConfigurationEditor(); - } + public IConfigurationEditor GetConfigurationEditor() => new ConfigurationEditor(); - public IDataValueEditor GetValueEditor() - { - throw new NotImplementedException(); - } + public IDataValueEditor GetValueEditor() => throw new NotImplementedException(); - public IDataValueEditor GetValueEditor(object? configuration) - { - throw new NotImplementedException(); - } - } + public IDataValueEditor GetValueEditor(object? configuration) => throw new NotImplementedException(); } diff --git a/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfiguration.cs b/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfiguration.cs index 2825b5b8afa0..ff8e7f741ab5 100644 --- a/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfiguration.cs @@ -1,28 +1,28 @@ -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration for the multinode picker value editor. +/// +public class MultiNodePickerConfiguration : IIgnoreUserStartNodesConfig { - /// - /// Represents the configuration for the multinode picker value editor. - /// - public class MultiNodePickerConfiguration : IIgnoreUserStartNodesConfig - { - [ConfigurationField("startNode", "Node type", "treesource")] - public MultiNodePickerConfigurationTreeSource? TreeSource { get; set; } + [ConfigurationField("startNode", "Node type", "treesource")] + public MultiNodePickerConfigurationTreeSource? TreeSource { get; set; } - [ConfigurationField("filter", "Allow items of type", "treesourcetypepicker", Description = "Select the applicable types")] - public string? Filter { get; set; } + [ConfigurationField("filter", "Allow items of type", "treesourcetypepicker", + Description = "Select the applicable types")] + public string? Filter { get; set; } - [ConfigurationField("minNumber", "Minimum number of items", "number")] - public int MinNumber { get; set; } + [ConfigurationField("minNumber", "Minimum number of items", "number")] + public int MinNumber { get; set; } - [ConfigurationField("maxNumber", "Maximum number of items", "number")] - public int MaxNumber { get; set; } + [ConfigurationField("maxNumber", "Maximum number of items", "number")] + public int MaxNumber { get; set; } - [ConfigurationField("showOpenButton", "Show open button", "boolean", Description = "Opens the node in a dialog")] - public bool ShowOpen { get; set; } + [ConfigurationField("showOpenButton", "Show open button", "boolean", Description = "Opens the node in a dialog")] + public bool ShowOpen { get; set; } - [ConfigurationField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, - "Ignore User Start Nodes", "boolean", - Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] - public bool IgnoreUserStartNodes { get; set; } - } + [ConfigurationField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, + "Ignore User Start Nodes", "boolean", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] + public bool IgnoreUserStartNodes { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfigurationEditor.cs index aa66be9d39ca..e01c69e3b15e 100644 --- a/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfigurationEditor.cs @@ -1,53 +1,49 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration for the multinode picker value editor. +/// +public class MultiNodePickerConfigurationEditor : ConfigurationEditor { - /// - /// Represents the configuration for the multinode picker value editor. - /// - public class MultiNodePickerConfigurationEditor : ConfigurationEditor + // Scheduled for removal in v12 + [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + public MultiNodePickerConfigurationEditor(IIOHelper ioHelper) + : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) + { + } + + public MultiNodePickerConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) + : base(ioHelper, editorConfigurationParser) => + Field(nameof(MultiNodePickerConfiguration.TreeSource)) + .Config = new Dictionary {{"idType", "udi"}}; + + /// + public override Dictionary ToConfigurationEditor(MultiNodePickerConfiguration? configuration) + { + // sanitize configuration + Dictionary output = base.ToConfigurationEditor(configuration); + + output["multiPicker"] = configuration?.MaxNumber > 1; + + return output; + } + + /// + public override IDictionary ToValueEditor(object? configuration) { - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public MultiNodePickerConfigurationEditor(IIOHelper ioHelper) - : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) - { - } - - public MultiNodePickerConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) - { - Field(nameof(MultiNodePickerConfiguration.TreeSource)) - .Config = new Dictionary { { "idType", "udi" } }; - } - - /// - public override Dictionary ToConfigurationEditor(MultiNodePickerConfiguration? configuration) - { - // sanitize configuration - var output = base.ToConfigurationEditor(configuration); - - output["multiPicker"] = configuration?.MaxNumber > 1; - - return output; - } - - /// - public override IDictionary ToValueEditor(object? configuration) - { - var d = base.ToValueEditor(configuration); - d["multiPicker"] = true; - d["showEditButton"] = false; - d["showPathOnHover"] = false; - d["idType"] = "udi"; - return d; - } + IDictionary d = base.ToValueEditor(configuration); + d["multiPicker"] = true; + d["showEditButton"] = false; + d["showPathOnHover"] = false; + d["idType"] = "udi"; + return d; } } diff --git a/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfigurationTreeSource.cs b/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfigurationTreeSource.cs index bc48bbdd545b..f4a14e6f4fc7 100644 --- a/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfigurationTreeSource.cs +++ b/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfigurationTreeSource.cs @@ -1,20 +1,16 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the 'startNode' value for the +/// +[DataContract] +public class MultiNodePickerConfigurationTreeSource { - /// - /// Represents the 'startNode' value for the - /// - [DataContract] - public class MultiNodePickerConfigurationTreeSource - { - [DataMember(Name = "type")] - public string? ObjectType { get; set; } + [DataMember(Name = "type")] public string? ObjectType { get; set; } - [DataMember(Name = "query")] - public string? StartNodeQuery { get; set; } + [DataMember(Name = "query")] public string? StartNodeQuery { get; set; } - [DataMember(Name = "id")] - public Udi? StartNodeId { get; set; } - } + [DataMember(Name = "id")] public Udi? StartNodeId { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/MultiUrlPickerConfiguration.cs b/src/Umbraco.Core/PropertyEditors/MultiUrlPickerConfiguration.cs index caf933e6ad7b..3212c1decea7 100644 --- a/src/Umbraco.Core/PropertyEditors/MultiUrlPickerConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/MultiUrlPickerConfiguration.cs @@ -1,25 +1,23 @@ -namespace Umbraco.Cms.Core.PropertyEditors -{ +namespace Umbraco.Cms.Core.PropertyEditors; - public class MultiUrlPickerConfiguration : IIgnoreUserStartNodesConfig - { - [ConfigurationField("minNumber", "Minimum number of items", "number")] - public int MinNumber { get; set; } +public class MultiUrlPickerConfiguration : IIgnoreUserStartNodesConfig +{ + [ConfigurationField("minNumber", "Minimum number of items", "number")] + public int MinNumber { get; set; } - [ConfigurationField("maxNumber", "Maximum number of items", "number")] - public int MaxNumber { get; set; } + [ConfigurationField("maxNumber", "Maximum number of items", "number")] + public int MaxNumber { get; set; } - [ConfigurationField("overlaySize", "Overlay Size", "overlaysize", Description = "Select the width of the overlay.")] - public string? OverlaySize { get; set; } + [ConfigurationField("overlaySize", "Overlay Size", "overlaysize", Description = "Select the width of the overlay.")] + public string? OverlaySize { get; set; } - [ConfigurationField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, - "Ignore user start nodes", "boolean", - Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] - public bool IgnoreUserStartNodes { get; set; } + [ConfigurationField("hideAnchor", + "Hide anchor/query string input", "boolean", + Description = "Selecting this hides the anchor/query string input field in the linkpicker overlay.")] + public bool HideAnchor { get; set; } - [ConfigurationField("hideAnchor", - "Hide anchor/query string input", "boolean", - Description = "Selecting this hides the anchor/query string input field in the linkpicker overlay.")] - public bool HideAnchor { get; set; } - } + [ConfigurationField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, + "Ignore user start nodes", "boolean", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] + public bool IgnoreUserStartNodes { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/MultiUrlPickerConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/MultiUrlPickerConfigurationEditor.cs index f5baa18c0415..99112080f630 100644 --- a/src/Umbraco.Core/PropertyEditors/MultiUrlPickerConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/MultiUrlPickerConfigurationEditor.cs @@ -1,26 +1,25 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +public class MultiUrlPickerConfigurationEditor : ConfigurationEditor { - public class MultiUrlPickerConfigurationEditor : ConfigurationEditor - { - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public MultiUrlPickerConfigurationEditor(IIOHelper ioHelper) - : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) + // Scheduled for removal in v12 + [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + public MultiUrlPickerConfigurationEditor(IIOHelper ioHelper) + : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) - { - } + { + } - public MultiUrlPickerConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) - { - } + public MultiUrlPickerConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : + base(ioHelper, editorConfigurationParser) + { } } diff --git a/src/Umbraco.Core/PropertyEditors/MultipleTextStringConfiguration.cs b/src/Umbraco.Core/PropertyEditors/MultipleTextStringConfiguration.cs index 506b3bebc9c3..d897960f564a 100644 --- a/src/Umbraco.Core/PropertyEditors/MultipleTextStringConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/MultipleTextStringConfiguration.cs @@ -1,14 +1,13 @@ -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration for a multiple textstring value editor. +/// +public class MultipleTextStringConfiguration { - /// - /// Represents the configuration for a multiple textstring value editor. - /// - public class MultipleTextStringConfiguration - { - // fields are configured in the editor + // fields are configured in the editor - public int Minimum { get; set; } + public int Minimum { get; set; } - public int Maximum {get; set; } - } + public int Maximum { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/NestedContentConfiguration.cs b/src/Umbraco.Core/PropertyEditors/NestedContentConfiguration.cs index aed6b5cd00f7..283e06521e1c 100644 --- a/src/Umbraco.Core/PropertyEditors/NestedContentConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/NestedContentConfiguration.cs @@ -1,43 +1,42 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.PropertyEditors -{ +namespace Umbraco.Cms.Core.PropertyEditors; - /// - /// Represents the configuration for the nested content value editor. - /// - public class NestedContentConfiguration - { - [ConfigurationField("contentTypes", "Element Types", "views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html", Description = "Select the Element Types to use as models for the items.")] - public ContentType[]? ContentTypes { get; set; } +/// +/// Represents the configuration for the nested content value editor. +/// +public class NestedContentConfiguration +{ + [ConfigurationField("contentTypes", "Element Types", + "views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html", + Description = "Select the Element Types to use as models for the items.")] + public ContentType[]? ContentTypes { get; set; } - [ConfigurationField("minItems", "Min Items", "number", Description = "Minimum number of items allowed.")] - public int? MinItems { get; set; } + [ConfigurationField("minItems", "Min Items", "number", Description = "Minimum number of items allowed.")] + public int? MinItems { get; set; } - [ConfigurationField("maxItems", "Max Items", "number", Description = "Maximum number of items allowed.")] - public int? MaxItems { get; set; } + [ConfigurationField("maxItems", "Max Items", "number", Description = "Maximum number of items allowed.")] + public int? MaxItems { get; set; } - [ConfigurationField("confirmDeletes", "Confirm Deletes", "boolean", Description = "Requires editor confirmation for delete actions.")] - public bool ConfirmDeletes { get; set; } = true; + [ConfigurationField("confirmDeletes", "Confirm Deletes", "boolean", + Description = "Requires editor confirmation for delete actions.")] + public bool ConfirmDeletes { get; set; } = true; - [ConfigurationField("showIcons", "Show Icons", "boolean", Description = "Show the Element Type icons.")] - public bool ShowIcons { get; set; } = true; + [ConfigurationField("showIcons", "Show Icons", "boolean", Description = "Show the Element Type icons.")] + public bool ShowIcons { get; set; } = true; - [ConfigurationField("hideLabel", "Hide Label", "boolean", Description = "Hide the property label and let the item list span the full width of the editor window.")] - public bool HideLabel { get; set; } + [ConfigurationField("hideLabel", "Hide Label", "boolean", + Description = "Hide the property label and let the item list span the full width of the editor window.")] + public bool HideLabel { get; set; } - [DataContract] - public class ContentType - { - [DataMember(Name = "ncAlias")] - public string? Alias { get; set; } + [DataContract] + public class ContentType + { + [DataMember(Name = "ncAlias")] public string? Alias { get; set; } - [DataMember(Name = "ncTabAlias")] - public string? TabAlias { get; set; } + [DataMember(Name = "ncTabAlias")] public string? TabAlias { get; set; } - [DataMember(Name = "nameTemplate")] - public string? Template { get; set; } - } + [DataMember(Name = "nameTemplate")] public string? Template { get; set; } } } diff --git a/src/Umbraco.Core/PropertyEditors/NestedContentConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/NestedContentConfigurationEditor.cs index bab2038d2d85..58e3065567f1 100644 --- a/src/Umbraco.Core/PropertyEditors/NestedContentConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/NestedContentConfigurationEditor.cs @@ -1,28 +1,27 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration editor for the nested content value editor. +/// +public class NestedContentConfigurationEditor : ConfigurationEditor { - /// - /// Represents the configuration editor for the nested content value editor. - /// - public class NestedContentConfigurationEditor : ConfigurationEditor + // Scheduled for removal in v12 + [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + public NestedContentConfigurationEditor(IIOHelper ioHelper) + : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) { - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public NestedContentConfigurationEditor(IIOHelper ioHelper) - : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) - { - } + } - public NestedContentConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) - { - } + public NestedContentConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : + base(ioHelper, editorConfigurationParser) + { } } diff --git a/src/Umbraco.Core/PropertyEditors/NoopPropertyCacheCompressionOptions.cs b/src/Umbraco.Core/PropertyEditors/NoopPropertyCacheCompressionOptions.cs index 7e91d8e3ee2a..54a6e6d2a780 100644 --- a/src/Umbraco.Core/PropertyEditors/NoopPropertyCacheCompressionOptions.cs +++ b/src/Umbraco.Core/PropertyEditors/NoopPropertyCacheCompressionOptions.cs @@ -1,12 +1,13 @@ using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Default implementation for which does not compress any property +/// data +/// +public sealed class NoopPropertyCacheCompressionOptions : IPropertyCacheCompressionOptions { - /// - /// Default implementation for which does not compress any property data - /// - public sealed class NoopPropertyCacheCompressionOptions : IPropertyCacheCompressionOptions - { - public bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor, bool published) => false; - } + public bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor, + bool published) => false; } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditorCollection.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditorCollection.cs index c58c962df4f3..eec435ddf6a5 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditorCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditorCollection.cs @@ -1,25 +1,24 @@ -using System.Linq; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Manifest; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +public class ParameterEditorCollection : BuilderCollectionBase { - public class ParameterEditorCollection : BuilderCollectionBase + public ParameterEditorCollection(DataEditorCollection dataEditors, IManifestParser manifestParser) + : base(() => dataEditors + .Where(x => (x.Type & EditorType.MacroParameter) > 0) + .Union(manifestParser.CombinedManifest.PropertyEditors)) { - public ParameterEditorCollection(DataEditorCollection dataEditors, IManifestParser manifestParser) - : base(() => dataEditors - .Where(x => (x.Type & EditorType.MacroParameter) > 0) - .Union(manifestParser.CombinedManifest.PropertyEditors)) - { } + } - // note: virtual so it can be mocked - public virtual IDataEditor? this[string alias] - => this.SingleOrDefault(x => x.Alias == alias); + // note: virtual so it can be mocked + public virtual IDataEditor? this[string alias] + => this.SingleOrDefault(x => x.Alias == alias); - public virtual bool TryGet(string alias, out IDataEditor? editor) - { - editor = this.FirstOrDefault(x => x.Alias == alias); - return editor != null; - } + public virtual bool TryGet(string alias, out IDataEditor? editor) + { + editor = this.FirstOrDefault(x => x.Alias == alias); + return editor != null; } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/ContentTypeParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/ContentTypeParameterEditor.cs index c7d8067fff4d..5a227348ea01 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/ContentTypeParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/ContentTypeParameterEditor.cs @@ -1,31 +1,24 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; +namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors; -namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors +/// +/// Represents a content type parameter editor. +/// +[DataEditor( + "contentType", + EditorType.MacroParameter, + "Content Type Picker", + "entitypicker")] +public class ContentTypeParameterEditor : DataEditor { /// - /// Represents a content type parameter editor. + /// Initializes a new instance of the class. /// - [DataEditor( - "contentType", - EditorType.MacroParameter, - "Content Type Picker", - "entitypicker")] - public class ContentTypeParameterEditor : DataEditor + public ContentTypeParameterEditor( + IDataValueEditorFactory dataValueEditorFactory) + : base(dataValueEditorFactory) { - /// - /// Initializes a new instance of the class. - /// - public ContentTypeParameterEditor( - IDataValueEditorFactory dataValueEditorFactory) - : base(dataValueEditorFactory) - { - // configure - DefaultConfiguration.Add("multiple", false); - DefaultConfiguration.Add("entityType", "DocumentType"); - } + // configure + DefaultConfiguration.Add("multiple", false); + DefaultConfiguration.Add("entityType", "DocumentType"); } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs index 65056c75ce5b..6da7680540da 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs @@ -4,41 +4,44 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; -namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors +namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors; + +/// +/// Represents a parameter editor of some sort. +/// +[DataEditor( + Constants.PropertyEditors.Aliases.MultiNodeTreePicker, + EditorType.MacroParameter, + "Multiple Content Picker", + "contentpicker")] +public class MultipleContentPickerParameterEditor : DataEditor { /// - /// Represents a parameter editor of some sort. + /// Initializes a new instance of the class. /// - [DataEditor( - Constants.PropertyEditors.Aliases.MultiNodeTreePicker, - EditorType.MacroParameter, - "Multiple Content Picker", - "contentpicker")] - public class MultipleContentPickerParameterEditor : DataEditor + public MultipleContentPickerParameterEditor( + IDataValueEditorFactory dataValueEditorFactory) + : base(dataValueEditorFactory) { - /// - /// Initializes a new instance of the class. - /// - public MultipleContentPickerParameterEditor( - IDataValueEditorFactory dataValueEditorFactory) - : base(dataValueEditorFactory) - { - // configure - DefaultConfiguration.Add("multiPicker", "1"); - DefaultConfiguration.Add("minNumber",0 ); - DefaultConfiguration.Add("maxNumber", 0); - } + // configure + DefaultConfiguration.Add("multiPicker", "1"); + DefaultConfiguration.Add("minNumber", 0); + DefaultConfiguration.Add("maxNumber", 0); + } - protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create(Attribute!); + protected override IDataValueEditor CreateValueEditor() => + DataValueEditorFactory.Create(Attribute!); - internal class MultipleContentPickerParamateterValueEditor : MultiplePickerParamateterValueEditorBase + internal class MultipleContentPickerParamateterValueEditor : MultiplePickerParamateterValueEditorBase + { + public MultipleContentPickerParamateterValueEditor(ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper, + DataEditorAttribute attribute, IEntityService entityService) : base(localizedTextService, shortStringHelper, + jsonSerializer, ioHelper, attribute, entityService) { - public MultipleContentPickerParamateterValueEditor(ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper, DataEditorAttribute attribute, IEntityService entityService) : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute, entityService) - { - } - - public override string UdiEntityType { get; } = Constants.UdiEntityType.Document; - public override UmbracoObjectTypes UmbracoObjectType { get; } = UmbracoObjectTypes.Document; } + + public override string UdiEntityType { get; } = Constants.UdiEntityType.Document; + public override UmbracoObjectTypes UmbracoObjectType { get; } = UmbracoObjectTypes.Document; } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentTypeParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentTypeParameterEditor.cs index 01bae2ada246..7036a4ea73e9 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentTypeParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentTypeParameterEditor.cs @@ -1,25 +1,18 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; +namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors; -namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors +[DataEditor( + "contentTypeMultiple", + EditorType.MacroParameter, + "Multiple Content Type Picker", + "entitypicker")] +public class MultipleContentTypeParameterEditor : DataEditor { - [DataEditor( - "contentTypeMultiple", - EditorType.MacroParameter, - "Multiple Content Type Picker", - "entitypicker")] - public class MultipleContentTypeParameterEditor : DataEditor + public MultipleContentTypeParameterEditor( + IDataValueEditorFactory dataValueEditorFactory) + : base(dataValueEditorFactory) { - public MultipleContentTypeParameterEditor( - IDataValueEditorFactory dataValueEditorFactory) - : base(dataValueEditorFactory) - { - // configure - DefaultConfiguration.Add("multiple", true); - DefaultConfiguration.Add("entityType", "DocumentType"); - } + // configure + DefaultConfiguration.Add("multiple", true); + DefaultConfiguration.Add("entityType", "DocumentType"); } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs index 4a6bab528c07..b0cae56d58ac 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs @@ -1,46 +1,43 @@ -using System; -using System.Collections.Generic; -using System.Reflection.Metadata; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; -namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors +namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors; + +/// +/// Represents a multiple media picker macro parameter editor. +/// +[DataEditor( + Constants.PropertyEditors.Aliases.MultipleMediaPicker, + EditorType.MacroParameter, + "Multiple Media Picker", + "mediapicker", + ValueType = ValueTypes.Text)] +public class MultipleMediaPickerParameterEditor : DataEditor { /// - /// Represents a multiple media picker macro parameter editor. + /// Initializes a new instance of the class. /// - [DataEditor( - Constants.PropertyEditors.Aliases.MultipleMediaPicker, - EditorType.MacroParameter, - "Multiple Media Picker", - "mediapicker", - ValueType = ValueTypes.Text)] - public class MultipleMediaPickerParameterEditor : DataEditor - { - /// - /// Initializes a new instance of the class. - /// - public MultipleMediaPickerParameterEditor( - IDataValueEditorFactory dataValueEditorFactory) - : base(dataValueEditorFactory) - { - DefaultConfiguration.Add("multiPicker", "1"); - } + public MultipleMediaPickerParameterEditor( + IDataValueEditorFactory dataValueEditorFactory) + : base(dataValueEditorFactory) => + DefaultConfiguration.Add("multiPicker", "1"); - protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create(Attribute!); + protected override IDataValueEditor CreateValueEditor() => + DataValueEditorFactory.Create(Attribute!); - internal class MultipleMediaPickerPropertyValueEditor : MultiplePickerParamateterValueEditorBase + internal class MultipleMediaPickerPropertyValueEditor : MultiplePickerParamateterValueEditorBase + { + public MultipleMediaPickerPropertyValueEditor(ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper, + DataEditorAttribute attribute, IEntityService entityService) : base(localizedTextService, shortStringHelper, + jsonSerializer, ioHelper, attribute, entityService) { - public MultipleMediaPickerPropertyValueEditor(ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper, DataEditorAttribute attribute, IEntityService entityService) : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute, entityService) - { - } - - public override string UdiEntityType { get; } = Constants.UdiEntityType.Media; - public override UmbracoObjectTypes UmbracoObjectType { get; } = UmbracoObjectTypes.Media; } + + public override string UdiEntityType { get; } = Constants.UdiEntityType.Media; + public override UmbracoObjectTypes UmbracoObjectType { get; } = UmbracoObjectTypes.Media; } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePickerParamateterValueEditorBase.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePickerParamateterValueEditorBase.cs index 5182c1fbd238..24e8cf9e6ea3 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePickerParamateterValueEditorBase.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePickerParamateterValueEditorBase.cs @@ -1,59 +1,54 @@ -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; -namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors +namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors; + +internal abstract class MultiplePickerParamateterValueEditorBase : DataValueEditor, IDataValueReference { - internal abstract class MultiplePickerParamateterValueEditorBase : DataValueEditor, IDataValueReference + private readonly IEntityService _entityService; + + public MultiplePickerParamateterValueEditorBase( + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper, + DataEditorAttribute attribute, + IEntityService entityService) + : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) => + _entityService = entityService; + + public abstract string UdiEntityType { get; } + public abstract UmbracoObjectTypes UmbracoObjectType { get; } + + public IEnumerable GetReferences(object? value) { - private readonly IEntityService _entityService; - - public MultiplePickerParamateterValueEditorBase( - ILocalizedTextService localizedTextService, - IShortStringHelper shortStringHelper, - IJsonSerializer jsonSerializer, - IIOHelper ioHelper, - DataEditorAttribute attribute, - IEntityService entityService) - : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) + var asString = value is string str ? str : value?.ToString(); + + if (string.IsNullOrEmpty(asString)) { - _entityService = entityService; + yield break; } - public abstract string UdiEntityType { get; } - public abstract UmbracoObjectTypes UmbracoObjectType { get; } - public IEnumerable GetReferences(object? value) + foreach (var udiStr in asString.Split(',')) { - var asString = value is string str ? str : value?.ToString(); - - if (string.IsNullOrEmpty(asString)) + if (UdiParser.TryParse(udiStr, out Udi? udi)) { - yield break; + yield return new UmbracoEntityReference(udi); } - foreach (var udiStr in asString.Split(',')) + // this is needed to support the legacy case when the multiple media picker parameter editor stores ints not udis + if (int.TryParse(udiStr, out var id)) { - if (UdiParser.TryParse(udiStr, out Udi? udi)) - { - yield return new UmbracoEntityReference(udi); - } + Attempt guidAttempt = _entityService.GetKey(id, UmbracoObjectType); + Guid guid = guidAttempt.Success ? guidAttempt.Result : Guid.Empty; - // this is needed to support the legacy case when the multiple media picker parameter editor stores ints not udis - if (int.TryParse(udiStr, out var id)) + if (guid != Guid.Empty) { - Attempt guidAttempt = _entityService.GetKey(id, UmbracoObjectType); - Guid guid = guidAttempt.Success ? guidAttempt.Result : Guid.Empty; - - if (guid != Guid.Empty) - { - yield return new UmbracoEntityReference(new GuidUdi(Constants.UdiEntityType.Media, guid)); - } - + yield return new UmbracoEntityReference(new GuidUdi(Constants.UdiEntityType.Media, guid)); } } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePropertyGroupParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePropertyGroupParameterEditor.cs index d39f792971fc..ec912a2a15bd 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePropertyGroupParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePropertyGroupParameterEditor.cs @@ -1,27 +1,20 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; +namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors; -namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors +[DataEditor( + "tabPickerMultiple", + EditorType.MacroParameter, + "Multiple Tab Picker", + "entitypicker")] +public class MultiplePropertyGroupParameterEditor : DataEditor { - [DataEditor( - "tabPickerMultiple", - EditorType.MacroParameter, - "Multiple Tab Picker", - "entitypicker")] - public class MultiplePropertyGroupParameterEditor : DataEditor + public MultiplePropertyGroupParameterEditor( + IDataValueEditorFactory dataValueEditorFactory) + : base(dataValueEditorFactory) { - public MultiplePropertyGroupParameterEditor( - IDataValueEditorFactory dataValueEditorFactory) - : base(dataValueEditorFactory) - { - // configure - DefaultConfiguration.Add("multiple", true); - DefaultConfiguration.Add("entityType", "PropertyGroup"); - //don't publish the id for a property group, publish its alias, which is actually just its lower cased name - DefaultConfiguration.Add("publishBy", "alias"); - } + // configure + DefaultConfiguration.Add("multiple", true); + DefaultConfiguration.Add("entityType", "PropertyGroup"); + //don't publish the id for a property group, publish its alias, which is actually just its lower cased name + DefaultConfiguration.Add("publishBy", "alias"); } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePropertyTypeParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePropertyTypeParameterEditor.cs index 64e310551b1c..2df36ca8a33f 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePropertyTypeParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePropertyTypeParameterEditor.cs @@ -1,27 +1,20 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; +namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors; -namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors +[DataEditor( + "propertyTypePickerMultiple", + EditorType.MacroParameter, + "Multiple Property Type Picker", + "entitypicker")] +public class MultiplePropertyTypeParameterEditor : DataEditor { - [DataEditor( - "propertyTypePickerMultiple", - EditorType.MacroParameter, - "Multiple Property Type Picker", - "entitypicker")] - public class MultiplePropertyTypeParameterEditor : DataEditor + public MultiplePropertyTypeParameterEditor( + IDataValueEditorFactory dataValueEditorFactory) + : base(dataValueEditorFactory) { - public MultiplePropertyTypeParameterEditor( - IDataValueEditorFactory dataValueEditorFactory) - : base(dataValueEditorFactory) - { - // configure - DefaultConfiguration.Add("multiple", "1"); - DefaultConfiguration.Add("entityType", "PropertyType"); - //don't publish the id for a property type, publish its alias - DefaultConfiguration.Add("publishBy", "alias"); - } + // configure + DefaultConfiguration.Add("multiple", "1"); + DefaultConfiguration.Add("entityType", "PropertyType"); + //don't publish the id for a property type, publish its alias + DefaultConfiguration.Add("publishBy", "alias"); } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/PropertyGroupParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/PropertyGroupParameterEditor.cs index 6441e8cb2463..c134564e5166 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/PropertyGroupParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/PropertyGroupParameterEditor.cs @@ -1,27 +1,20 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; +namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors; -namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors +[DataEditor( + "tabPicker", + EditorType.MacroParameter, + "Tab Picker", + "entitypicker")] +public class PropertyGroupParameterEditor : DataEditor { - [DataEditor( - "tabPicker", - EditorType.MacroParameter, - "Tab Picker", - "entitypicker")] - public class PropertyGroupParameterEditor : DataEditor + public PropertyGroupParameterEditor( + IDataValueEditorFactory dataValueEditorFactory) + : base(dataValueEditorFactory) { - public PropertyGroupParameterEditor( - IDataValueEditorFactory dataValueEditorFactory) - : base(dataValueEditorFactory) - { - // configure - DefaultConfiguration.Add("multiple", "0"); - DefaultConfiguration.Add("entityType", "PropertyGroup"); - //don't publish the id for a property group, publish it's alias (which is actually just it's lower cased name) - DefaultConfiguration.Add("publishBy", "alias"); - } + // configure + DefaultConfiguration.Add("multiple", "0"); + DefaultConfiguration.Add("entityType", "PropertyGroup"); + //don't publish the id for a property group, publish it's alias (which is actually just it's lower cased name) + DefaultConfiguration.Add("publishBy", "alias"); } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/PropertyTypeParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/PropertyTypeParameterEditor.cs index 9e253d4e41a6..858101e2e34b 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/PropertyTypeParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/PropertyTypeParameterEditor.cs @@ -1,27 +1,20 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; +namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors; -namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors +[DataEditor( + "propertyTypePicker", + EditorType.MacroParameter, + "Property Type Picker", + "entitypicker")] +public class PropertyTypeParameterEditor : DataEditor { - [DataEditor( - "propertyTypePicker", - EditorType.MacroParameter, - "Property Type Picker", - "entitypicker")] - public class PropertyTypeParameterEditor : DataEditor + public PropertyTypeParameterEditor( + IDataValueEditorFactory dataValueEditorFactory) + : base(dataValueEditorFactory) { - public PropertyTypeParameterEditor( - IDataValueEditorFactory dataValueEditorFactory) - : base(dataValueEditorFactory) - { - // configure - DefaultConfiguration.Add("multiple", "0"); - DefaultConfiguration.Add("entityType", "PropertyType"); - //don't publish the id for a property type, publish its alias - DefaultConfiguration.Add("publishBy", "alias"); - } + // configure + DefaultConfiguration.Add("multiple", "0"); + DefaultConfiguration.Add("entityType", "PropertyType"); + //don't publish the id for a property type, publish its alias + DefaultConfiguration.Add("publishBy", "alias"); } } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyCacheCompression.cs b/src/Umbraco.Core/PropertyEditors/PropertyCacheCompression.cs index ac275c46e30e..826eb8c1d1aa 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyCacheCompression.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyCacheCompression.cs @@ -1,53 +1,56 @@ using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.PropertyEditors +/// +/// Compresses property data based on config +/// +public class PropertyCacheCompression : IPropertyCacheCompression { + private readonly IPropertyCacheCompressionOptions _compressionOptions; + private readonly IReadOnlyDictionary _contentTypes; - /// - /// Compresses property data based on config - /// - public class PropertyCacheCompression : IPropertyCacheCompression + private readonly ConcurrentDictionary<(int contentTypeId, string propertyAlias, bool published), bool> + _isCompressedCache; + + private readonly PropertyEditorCollection _propertyEditors; + + public PropertyCacheCompression( + IPropertyCacheCompressionOptions compressionOptions, + IReadOnlyDictionary contentTypes, + PropertyEditorCollection propertyEditors, + ConcurrentDictionary<(int, string, bool), bool> compressedStoragePropertyEditorCache) { - private readonly IPropertyCacheCompressionOptions _compressionOptions; - private readonly IReadOnlyDictionary _contentTypes; - private readonly PropertyEditorCollection _propertyEditors; - private readonly ConcurrentDictionary<(int contentTypeId, string propertyAlias, bool published), bool> _isCompressedCache; - - public PropertyCacheCompression( - IPropertyCacheCompressionOptions compressionOptions, - IReadOnlyDictionary contentTypes, - PropertyEditorCollection propertyEditors, - ConcurrentDictionary<(int, string, bool), bool> compressedStoragePropertyEditorCache) - { - _compressionOptions = compressionOptions; - _contentTypes = contentTypes ?? throw new System.ArgumentNullException(nameof(contentTypes)); - _propertyEditors = propertyEditors ?? throw new System.ArgumentNullException(nameof(propertyEditors)); - _isCompressedCache = compressedStoragePropertyEditorCache; - } + _compressionOptions = compressionOptions; + _contentTypes = contentTypes ?? throw new ArgumentNullException(nameof(contentTypes)); + _propertyEditors = propertyEditors ?? throw new ArgumentNullException(nameof(propertyEditors)); + _isCompressedCache = compressedStoragePropertyEditorCache; + } - public bool IsCompressed(IReadOnlyContentBase content, string alias, bool published) + public bool IsCompressed(IReadOnlyContentBase content, string alias, bool published) + { + var compressedStorage = _isCompressedCache.GetOrAdd((content.ContentTypeId, alias, published), x => { - var compressedStorage = _isCompressedCache.GetOrAdd((content.ContentTypeId, alias, published), x => + if (!_contentTypes.TryGetValue(x.contentTypeId, out IContentTypeComposition ct)) { - if (!_contentTypes.TryGetValue(x.contentTypeId, out var ct)) - return false; + return false; + } - var propertyType = ct.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == alias); - if (propertyType == null) - return false; + IPropertyType propertyType = ct.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == alias); + if (propertyType == null) + { + return false; + } - if (!_propertyEditors.TryGet(propertyType.PropertyEditorAlias, out var propertyEditor)) - return false; + if (!_propertyEditors.TryGet(propertyType.PropertyEditorAlias, out IDataEditor propertyEditor)) + { + return false; + } - return _compressionOptions.IsCompressed(content, propertyType, propertyEditor!, published); - }); + return _compressionOptions.IsCompressed(content, propertyType, propertyEditor!, published); + }); - return compressedStorage; - } + return compressedStorage; } } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyCacheLevel.cs b/src/Umbraco.Core/PropertyEditors/PropertyCacheLevel.cs index 9c9400861652..5a23b6b825b4 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyCacheLevel.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyCacheLevel.cs @@ -1,39 +1,40 @@ -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Specifies the level of cache for a property value. +/// +public enum PropertyCacheLevel { /// - /// Specifies the level of cache for a property value. + /// Default value. /// - public enum PropertyCacheLevel - { - /// - /// Default value. - /// - Unknown = 0, + Unknown = 0, - /// - /// Indicates that the property value can be cached at the element level, i.e. it can be - /// cached until the element itself is modified. - /// - Element = 1, + /// + /// Indicates that the property value can be cached at the element level, i.e. it can be + /// cached until the element itself is modified. + /// + Element = 1, - /// - /// Indicates that the property value can be cached at the elements level, i.e. it can - /// be cached until any element is modified. - /// - Elements = 2, + /// + /// Indicates that the property value can be cached at the elements level, i.e. it can + /// be cached until any element is modified. + /// + Elements = 2, - /// - /// Indicates that the property value can be cached at the snapshot level, i.e. it can be - /// cached for the duration of the current snapshot. - /// - /// In most cases, a snapshot is created per request, and therefore this is - /// equivalent to cache the value for the duration of the request. - Snapshot = 3, + /// + /// Indicates that the property value can be cached at the snapshot level, i.e. it can be + /// cached for the duration of the current snapshot. + /// + /// + /// In most cases, a snapshot is created per request, and therefore this is + /// equivalent to cache the value for the duration of the request. + /// + Snapshot = 3, - /// - /// Indicates that the property value cannot be cached and has to be converted each time - /// it is requested. - /// - None = 4 - } + /// + /// Indicates that the property value cannot be cached and has to be converted each time + /// it is requested. + /// + None = 4 } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs index 34f72cf5c075..ff700431d5dd 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs @@ -1,31 +1,31 @@ using System.Diagnostics.CodeAnalysis; -using System.Linq; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Manifest; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +public class PropertyEditorCollection : BuilderCollectionBase { - public class PropertyEditorCollection : BuilderCollectionBase + public PropertyEditorCollection(DataEditorCollection dataEditors, IManifestParser manifestParser) + : base(() => dataEditors + .Where(x => (x.Type & EditorType.PropertyValue) > 0) + .Union(manifestParser.CombinedManifest.PropertyEditors)) { - public PropertyEditorCollection(DataEditorCollection dataEditors, IManifestParser manifestParser) - : base(() => dataEditors - .Where(x => (x.Type & EditorType.PropertyValue) > 0) - .Union(manifestParser.CombinedManifest.PropertyEditors)) - { } + } - public PropertyEditorCollection(DataEditorCollection dataEditors) - : base(() => dataEditors - .Where(x => (x.Type & EditorType.PropertyValue) > 0)) - { } + public PropertyEditorCollection(DataEditorCollection dataEditors) + : base(() => dataEditors + .Where(x => (x.Type & EditorType.PropertyValue) > 0)) + { + } - // note: virtual so it can be mocked - public virtual IDataEditor? this[string? alias] - => this.SingleOrDefault(x => x.Alias == alias); + // note: virtual so it can be mocked + public virtual IDataEditor? this[string? alias] + => this.SingleOrDefault(x => x.Alias == alias); - public virtual bool TryGet(string? alias, [MaybeNullWhen(false)] out IDataEditor editor) - { - editor = this.FirstOrDefault(x => x.Alias == alias); - return editor != null; - } + public virtual bool TryGet(string? alias, [MaybeNullWhen(false)] out IDataEditor editor) + { + editor = this.FirstOrDefault(x => x.Alias == alias); + return editor != null; } } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditorTagsExtensions.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditorTagsExtensions.cs index fa57956cdd55..38906446e144 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditorTagsExtensions.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditorTagsExtensions.cs @@ -1,22 +1,21 @@ using Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Provides extension methods for the interface to manage tags. +/// +public static class PropertyEditorTagsExtensions { /// - /// Provides extension methods for the interface to manage tags. + /// Determines whether an editor supports tags. /// - public static class PropertyEditorTagsExtensions - { - /// - /// Determines whether an editor supports tags. - /// - public static bool IsTagsEditor(this IDataEditor editor) - => editor.GetTagAttribute() != null; + public static bool IsTagsEditor(this IDataEditor editor) + => editor.GetTagAttribute() != null; - /// - /// Gets the tags configuration attribute of an editor. - /// - public static TagsPropertyEditorAttribute? GetTagAttribute(this IDataEditor? editor) - => editor?.GetType().GetCustomAttribute(false); - } + /// + /// Gets the tags configuration attribute of an editor. + /// + public static TagsPropertyEditorAttribute? GetTagAttribute(this IDataEditor? editor) + => editor?.GetType().GetCustomAttribute(false); } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs index 0442ae1b18f8..10a47aea19d4 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs @@ -1,61 +1,63 @@ -using System; -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Provides a default implementation for . +/// +/// +public abstract class PropertyValueConverterBase : IPropertyValueConverter { - /// - /// Provides a default implementation for . - /// - /// - public abstract class PropertyValueConverterBase : IPropertyValueConverter - { - /// - public virtual bool IsConverter(IPublishedPropertyType propertyType) - => false; + /// + public virtual bool IsConverter(IPublishedPropertyType propertyType) + => false; - /// - public virtual bool? IsValue(object? value, PropertyValueLevel level) + /// + public virtual bool? IsValue(object? value, PropertyValueLevel level) + { + switch (level) { - switch (level) - { - case PropertyValueLevel.Source: - // the default implementation uses the old magic null & string comparisons, - // other implementations may be more clever, and/or test the final converted object values - return value != null && (!(value is string stringValue) || !string.IsNullOrWhiteSpace(stringValue)); - case PropertyValueLevel.Inter: - return null; - case PropertyValueLevel.Object: - return null; - default: - throw new NotSupportedException($"Invalid level: {level}."); - } + case PropertyValueLevel.Source: + // the default implementation uses the old magic null & string comparisons, + // other implementations may be more clever, and/or test the final converted object values + return value != null && (!(value is string stringValue) || !string.IsNullOrWhiteSpace(stringValue)); + case PropertyValueLevel.Inter: + return null; + case PropertyValueLevel.Object: + return null; + default: + throw new NotSupportedException($"Invalid level: {level}."); } + } - [Obsolete("This method is not part of the IPropertyValueConverter contract, therefore not used and will be removed in future versions; use IsValue instead.")] - public virtual bool HasValue(IPublishedProperty property, string culture, string segment) - { - var value = property.GetSourceValue(culture, segment); - return value != null && (!(value is string stringValue) || !string.IsNullOrWhiteSpace(stringValue)); - } + /// + public virtual Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(object); - /// - public virtual Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof(object); + /// + public virtual PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Snapshot; - /// - public virtual PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Snapshot; + /// + public virtual object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, + object? source, bool preview) + => source; - /// - public virtual object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) - => source; + /// + public virtual object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + => inter; - /// - public virtual object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) - => inter; + /// + public virtual object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + => inter?.ToString() ?? string.Empty; - /// - public virtual object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) - => inter?.ToString() ?? string.Empty; + [Obsolete( + "This method is not part of the IPropertyValueConverter contract, therefore not used and will be removed in future versions; use IsValue instead.")] + public virtual bool HasValue(IPublishedProperty property, string culture, string segment) + { + var value = property.GetSourceValue(culture, segment); + return value != null && (!(value is string stringValue) || !string.IsNullOrWhiteSpace(stringValue)); } } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollection.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollection.cs index 9214f1048222..5dfb8a275e6a 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollection.cs @@ -1,47 +1,48 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core.Composing; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +public class PropertyValueConverterCollection : BuilderCollectionBase { - public class PropertyValueConverterCollection : BuilderCollectionBase - { - public PropertyValueConverterCollection(Func> items) : base(items) - { - } + private readonly object _locker = new(); + private Dictionary? _defaultConverters; - private readonly object _locker = new object(); - private Dictionary? _defaultConverters; + public PropertyValueConverterCollection(Func> items) : base(items) + { + } - private Dictionary DefaultConverters + private Dictionary DefaultConverters + { + get { - get + lock (_locker) { - lock (_locker) + if (_defaultConverters != null) { - if (_defaultConverters != null) - return _defaultConverters; + return _defaultConverters; + } - _defaultConverters = new Dictionary(); + _defaultConverters = new Dictionary(); - foreach (var converter in this) + foreach (IPropertyValueConverter converter in this) + { + DefaultPropertyValueConverterAttribute attr = converter.GetType() + .GetCustomAttribute(false); + if (attr != null) { - var attr = converter.GetType().GetCustomAttribute(false); - if (attr != null) - _defaultConverters[converter] = attr.DefaultConvertersToShadow; + _defaultConverters[converter] = attr.DefaultConvertersToShadow; } - - return _defaultConverters; } + + return _defaultConverters; } } + } - internal bool IsDefault(IPropertyValueConverter converter) - => DefaultConverters.ContainsKey(converter); + internal bool IsDefault(IPropertyValueConverter converter) + => DefaultConverters.ContainsKey(converter); - internal bool Shadows(IPropertyValueConverter shadowing, IPropertyValueConverter shadowed) - => DefaultConverters.TryGetValue(shadowing, out Type[]? types) && types.Contains(shadowed.GetType()); - } + internal bool Shadows(IPropertyValueConverter shadowing, IPropertyValueConverter shadowed) + => DefaultConverters.TryGetValue(shadowing, out Type[]? types) && types.Contains(shadowed.GetType()); } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollectionBuilder.cs index f7bbca2b02d0..2d1f8ae73648 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollectionBuilder.cs @@ -1,9 +1,9 @@ using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +public class PropertyValueConverterCollectionBuilder : OrderedCollectionBuilderBase< + PropertyValueConverterCollectionBuilder, PropertyValueConverterCollection, IPropertyValueConverter> { - public class PropertyValueConverterCollectionBuilder : OrderedCollectionBuilderBase - { - protected override PropertyValueConverterCollectionBuilder This => this; - } + protected override PropertyValueConverterCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueLevel.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueLevel.cs index 583bf87f3eb9..afdba8434102 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueLevel.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueLevel.cs @@ -1,23 +1,22 @@ -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Indicates the level of a value. +/// +public enum PropertyValueLevel { /// - /// Indicates the level of a value. + /// The source value, i.e. what is in the database. /// - public enum PropertyValueLevel - { - /// - /// The source value, i.e. what is in the database. - /// - Source, + Source, - /// - /// The conversion intermediate value. - /// - Inter, + /// + /// The conversion intermediate value. + /// + Inter, - /// - /// The converted value. - /// - Object - } + /// + /// The converted value. + /// + Object } diff --git a/src/Umbraco.Core/PropertyEditors/RichTextConfiguration.cs b/src/Umbraco.Core/PropertyEditors/RichTextConfiguration.cs index e0bbae88b567..1130e7ebf989 100644 --- a/src/Umbraco.Core/PropertyEditors/RichTextConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/RichTextConfiguration.cs @@ -1,27 +1,27 @@ -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration for the rich text value editor. +/// +public class RichTextConfiguration : IIgnoreUserStartNodesConfig { - /// - /// Represents the configuration for the rich text value editor. - /// - public class RichTextConfiguration : IIgnoreUserStartNodesConfig - { - // TODO: Make these strongly typed, for now this works though - [ConfigurationField("editor", "Editor", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] - public object? Editor { get; set; } + // TODO: Make these strongly typed, for now this works though + [ConfigurationField("editor", "Editor", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] + public object? Editor { get; set; } - [ConfigurationField("overlaySize", "Overlay Size", "overlaysize", Description = "Select the width of the overlay (link picker).")] - public string? OverlaySize { get; set; } + [ConfigurationField("overlaySize", "Overlay Size", "overlaysize", + Description = "Select the width of the overlay (link picker).")] + public string? OverlaySize { get; set; } - [ConfigurationField("hideLabel", "Hide Label", "boolean")] - public bool HideLabel { get; set; } + [ConfigurationField("hideLabel", "Hide Label", "boolean")] + public bool HideLabel { get; set; } - [ConfigurationField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, - "Ignore User Start Nodes", "boolean", - Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] - public bool IgnoreUserStartNodes { get; set; } + [ConfigurationField("mediaParentId", "Image Upload Folder", "mediafolderpicker", + Description = "Choose the upload location of pasted images")] + public GuidUdi? MediaParentId { get; set; } - [ConfigurationField("mediaParentId", "Image Upload Folder", "mediafolderpicker", - Description = "Choose the upload location of pasted images")] - public GuidUdi? MediaParentId { get; set; } - } + [ConfigurationField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, + "Ignore User Start Nodes", "boolean", + Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] + public bool IgnoreUserStartNodes { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/RichTextConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/RichTextConfigurationEditor.cs index a967ec236775..8412aa6b2bd8 100644 --- a/src/Umbraco.Core/PropertyEditors/RichTextConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/RichTextConfigurationEditor.cs @@ -1,28 +1,27 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration editor for the rich text value editor. +/// +public class RichTextConfigurationEditor : ConfigurationEditor { - /// - /// Represents the configuration editor for the rich text value editor. - /// - public class RichTextConfigurationEditor : ConfigurationEditor + // Scheduled for removal in v12 + [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + public RichTextConfigurationEditor(IIOHelper ioHelper) + : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) { - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public RichTextConfigurationEditor(IIOHelper ioHelper) - : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) - { - } + } - public RichTextConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) - { - } + public RichTextConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base( + ioHelper, editorConfigurationParser) + { } } diff --git a/src/Umbraco.Core/PropertyEditors/SliderConfiguration.cs b/src/Umbraco.Core/PropertyEditors/SliderConfiguration.cs index 8d41873a119f..501121d439c7 100644 --- a/src/Umbraco.Core/PropertyEditors/SliderConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/SliderConfiguration.cs @@ -1,26 +1,25 @@ -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration for the slider value editor. +/// +public class SliderConfiguration { - /// - /// Represents the configuration for the slider value editor. - /// - public class SliderConfiguration - { - [ConfigurationField("enableRange", "Enable range", "boolean")] - public bool EnableRange { get; set; } + [ConfigurationField("enableRange", "Enable range", "boolean")] + public bool EnableRange { get; set; } - [ConfigurationField("initVal1", "Initial value", "number")] - public decimal InitialValue { get; set; } + [ConfigurationField("initVal1", "Initial value", "number")] + public decimal InitialValue { get; set; } - [ConfigurationField("initVal2", "Initial value 2", "number", Description = "Used when range is enabled")] - public decimal InitialValue2 { get; set; } + [ConfigurationField("initVal2", "Initial value 2", "number", Description = "Used when range is enabled")] + public decimal InitialValue2 { get; set; } - [ConfigurationField("minVal", "Minimum value", "number")] - public decimal MinimumValue { get; set; } + [ConfigurationField("minVal", "Minimum value", "number")] + public decimal MinimumValue { get; set; } - [ConfigurationField("maxVal", "Maximum value", "number")] - public decimal MaximumValue { get; set; } + [ConfigurationField("maxVal", "Maximum value", "number")] + public decimal MaximumValue { get; set; } - [ConfigurationField("step", "Step increments", "number")] - public decimal StepIncrements { get; set; } - } + [ConfigurationField("step", "Step increments", "number")] + public decimal StepIncrements { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/SliderConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/SliderConfigurationEditor.cs index 6cd9db839917..a740ed1f35eb 100644 --- a/src/Umbraco.Core/PropertyEditors/SliderConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/SliderConfigurationEditor.cs @@ -1,28 +1,27 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration editor for the slider value editor. +/// +public class SliderConfigurationEditor : ConfigurationEditor { - /// - /// Represents the configuration editor for the slider value editor. - /// - public class SliderConfigurationEditor : ConfigurationEditor + // Scheduled for removal in v12 + [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + public SliderConfigurationEditor(IIOHelper ioHelper) + : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) { - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public SliderConfigurationEditor(IIOHelper ioHelper) - : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) - { - } + } - public SliderConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) - { - } + public SliderConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base( + ioHelper, editorConfigurationParser) + { } } diff --git a/src/Umbraco.Core/PropertyEditors/TagConfiguration.cs b/src/Umbraco.Core/PropertyEditors/TagConfiguration.cs index 61fa80472d8b..e06a9d4aab3c 100644 --- a/src/Umbraco.Core/PropertyEditors/TagConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/TagConfiguration.cs @@ -1,21 +1,21 @@ using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration for the tag value editor. +/// +public class TagConfiguration { - /// - /// Represents the configuration for the tag value editor. - /// - public class TagConfiguration - { - [ConfigurationField("group", "Tag group", "requiredfield", - Description = "Define a tag group")] - public string Group { get; set; } = "default"; + [ConfigurationField("group", "Tag group", "requiredfield", + Description = "Define a tag group")] + public string Group { get; set; } = "default"; - [ConfigurationField("storageType", "Storage Type", "views/propertyeditors/tags/tags.prevalues.html", - Description = "Select whether to store the tags in cache as JSON (default) or as CSV. The only benefits of storage as JSON is that you are able to have commas in a tag value")] - public TagsStorageType StorageType { get; set; } = TagsStorageType.Json; + [ConfigurationField("storageType", "Storage Type", "views/propertyeditors/tags/tags.prevalues.html", + Description = + "Select whether to store the tags in cache as JSON (default) or as CSV. The only benefits of storage as JSON is that you are able to have commas in a tag value")] + public TagsStorageType StorageType { get; set; } = TagsStorageType.Json; - // not a field - public char Delimiter { get; set; } - } + // not a field + public char Delimiter { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/TagConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/TagConfigurationEditor.cs index 2f77642e5f2f..c69631f5044d 100644 --- a/src/Umbraco.Core/PropertyEditors/TagConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/TagConfigurationEditor.cs @@ -1,8 +1,6 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; @@ -10,51 +8,58 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration editor for the tag value editor. +/// +public class TagConfigurationEditor : ConfigurationEditor { - /// - /// Represents the configuration editor for the tag value editor. - /// - public class TagConfigurationEditor : ConfigurationEditor + // Scheduled for removal in v12 + [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + public TagConfigurationEditor(ManifestValueValidatorCollection validators, IIOHelper ioHelper, + ILocalizedTextService localizedTextService) + : this(validators, ioHelper, localizedTextService, + StaticServiceProvider.Instance.GetRequiredService()) { - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public TagConfigurationEditor(ManifestValueValidatorCollection validators, IIOHelper ioHelper, ILocalizedTextService localizedTextService) - : this(validators, ioHelper, localizedTextService, StaticServiceProvider.Instance.GetRequiredService()) - { - } + } + + public TagConfigurationEditor(ManifestValueValidatorCollection validators, IIOHelper ioHelper, + ILocalizedTextService localizedTextService, IEditorConfigurationParser editorConfigurationParser) : base( + ioHelper, editorConfigurationParser) + { + Field(nameof(TagConfiguration.Group)).Validators.Add(new RequiredValidator(localizedTextService)); + Field(nameof(TagConfiguration.StorageType)).Validators.Add(new RequiredValidator(localizedTextService)); + } + + public override Dictionary ToConfigurationEditor(TagConfiguration? configuration) + { + Dictionary dictionary = base.ToConfigurationEditor(configuration); - public TagConfigurationEditor(ManifestValueValidatorCollection validators, IIOHelper ioHelper, ILocalizedTextService localizedTextService, IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) + // the front-end editor expects the string value of the storage type + if (!dictionary.TryGetValue("storageType", out var storageType)) { - Field(nameof(TagConfiguration.Group)).Validators.Add(new RequiredValidator(localizedTextService)); - Field(nameof(TagConfiguration.StorageType)).Validators.Add(new RequiredValidator(localizedTextService)); + storageType = TagsStorageType.Json; //default to Json } - public override Dictionary ToConfigurationEditor(TagConfiguration? configuration) - { - var dictionary = base.ToConfigurationEditor(configuration); + dictionary["storageType"] = storageType.ToString()!; - // the front-end editor expects the string value of the storage type - if (!dictionary.TryGetValue("storageType", out var storageType)) - storageType = TagsStorageType.Json; //default to Json - dictionary["storageType"] = storageType.ToString()!; + return dictionary; + } - return dictionary; - } + public override TagConfiguration? FromConfigurationEditor(IDictionary? editorValues, + TagConfiguration? configuration) + { + // the front-end editor returns the string value of the storage type + // pure Json could do with + // [JsonConverter(typeof(StringEnumConverter))] + // but here we're only deserializing to object and it's too late - public override TagConfiguration? FromConfigurationEditor(IDictionary? editorValues, TagConfiguration? configuration) + if (editorValues is not null) { - // the front-end editor returns the string value of the storage type - // pure Json could do with - // [JsonConverter(typeof(StringEnumConverter))] - // but here we're only deserializing to object and it's too late - - if (editorValues is not null) - { - editorValues["storageType"] = Enum.Parse(typeof(TagsStorageType), (string) editorValues["storageType"]!); - } - - return base.FromConfigurationEditor(editorValues, configuration); + editorValues["storageType"] = Enum.Parse(typeof(TagsStorageType), (string)editorValues["storageType"]!); } + + return base.FromConfigurationEditor(editorValues, configuration); } } diff --git a/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs b/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs index c21ea09ac99d..71775fe64a72 100644 --- a/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs +++ b/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs @@ -1,61 +1,58 @@ -using System; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Marks property editors that support tags. +/// +[AttributeUsage(AttributeTargets.Class)] +public class TagsPropertyEditorAttribute : Attribute { /// - /// Marks property editors that support tags. + /// Initializes a new instance of the class. /// - [AttributeUsage(AttributeTargets.Class)] - public class TagsPropertyEditorAttribute : Attribute + public TagsPropertyEditorAttribute(Type tagsConfigurationProvider) + : this() => + TagsConfigurationProviderType = tagsConfigurationProvider ?? + throw new ArgumentNullException(nameof(tagsConfigurationProvider)); + + /// + /// Initializes a new instance of the class. + /// + public TagsPropertyEditorAttribute() { - /// - /// Initializes a new instance of the class. - /// - public TagsPropertyEditorAttribute(Type tagsConfigurationProvider) - : this() - { - TagsConfigurationProviderType = tagsConfigurationProvider ?? throw new ArgumentNullException(nameof(tagsConfigurationProvider)); - } - - /// - /// Initializes a new instance of the class. - /// - public TagsPropertyEditorAttribute() - { - Delimiter = ','; - ReplaceTags = true; - TagGroup = "default"; - StorageType = TagsStorageType.Json; - } - - /// - /// Gets or sets a value indicating how tags are stored. - /// - public TagsStorageType StorageType { get; set; } - - /// - /// Gets or sets the delimited for delimited strings. - /// - /// Default is a comma. Has no meaning when tags are stored as Json. - public char Delimiter { get; set; } - - /// - /// Gets or sets a value indicating whether to replace the tags entirely. - /// - // TODO: what's the usage? - public bool ReplaceTags { get; set; } - - /// - /// Gets or sets the tags group. - /// - /// Default is "default". - public string TagGroup { get; set; } - - /// - /// Gets the type of the dynamic configuration provider. - /// - //TODO: This is not used and should be implemented in a nicer way, see https://github.com/umbraco/Umbraco-CMS/issues/6017#issuecomment-516253562 - public Type? TagsConfigurationProviderType { get; } + Delimiter = ','; + ReplaceTags = true; + TagGroup = "default"; + StorageType = TagsStorageType.Json; } + + /// + /// Gets or sets a value indicating how tags are stored. + /// + public TagsStorageType StorageType { get; set; } + + /// + /// Gets or sets the delimited for delimited strings. + /// + /// Default is a comma. Has no meaning when tags are stored as Json. + public char Delimiter { get; set; } + + /// + /// Gets or sets a value indicating whether to replace the tags entirely. + /// + // TODO: what's the usage? + public bool ReplaceTags { get; set; } + + /// + /// Gets or sets the tags group. + /// + /// Default is "default". + public string TagGroup { get; set; } + + /// + /// Gets the type of the dynamic configuration provider. + /// + //TODO: This is not used and should be implemented in a nicer way, see https://github.com/umbraco/Umbraco-CMS/issues/6017#issuecomment-516253562 + public Type? TagsConfigurationProviderType { get; } } diff --git a/src/Umbraco.Core/PropertyEditors/TextAreaConfiguration.cs b/src/Umbraco.Core/PropertyEditors/TextAreaConfiguration.cs index 86ca35ef6427..0c7982a0122a 100644 --- a/src/Umbraco.Core/PropertyEditors/TextAreaConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/TextAreaConfiguration.cs @@ -1,14 +1,15 @@ -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration for the textarea value editor. +/// +public class TextAreaConfiguration { - /// - /// Represents the configuration for the textarea value editor. - /// - public class TextAreaConfiguration - { - [ConfigurationField("maxChars", "Maximum allowed characters", "number", Description = "If empty - no character limit")] - public int? MaxChars { get; set; } + [ConfigurationField("maxChars", "Maximum allowed characters", "number", + Description = "If empty - no character limit")] + public int? MaxChars { get; set; } - [ConfigurationField("rows", "Number of rows", "number", Description = "If empty - 10 rows would be set as the default value")] - public int? Rows { get; set; } - } + [ConfigurationField("rows", "Number of rows", "number", + Description = "If empty - 10 rows would be set as the default value")] + public int? Rows { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/TextAreaConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/TextAreaConfigurationEditor.cs index 4fa4e7908c71..0cea517df73c 100644 --- a/src/Umbraco.Core/PropertyEditors/TextAreaConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/TextAreaConfigurationEditor.cs @@ -1,28 +1,27 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration editor for the textarea value editor. +/// +public class TextAreaConfigurationEditor : ConfigurationEditor { - /// - /// Represents the configuration editor for the textarea value editor. - /// - public class TextAreaConfigurationEditor : ConfigurationEditor - { - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public TextAreaConfigurationEditor(IIOHelper ioHelper) + // Scheduled for removal in v12 + [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + public TextAreaConfigurationEditor(IIOHelper ioHelper) : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) - { - } + { + } - public TextAreaConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) - { - } + public TextAreaConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base( + ioHelper, editorConfigurationParser) + { } } diff --git a/src/Umbraco.Core/PropertyEditors/TextOnlyValueEditor.cs b/src/Umbraco.Core/PropertyEditors/TextOnlyValueEditor.cs index cb401cf92a0c..6c97d485c0e8 100644 --- a/src/Umbraco.Core/PropertyEditors/TextOnlyValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/TextOnlyValueEditor.cs @@ -1,56 +1,58 @@ -using System; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Custom value editor which ensures that the value stored is just plain text and that +/// no magic json formatting occurs when translating it to and from the database values +/// +public class TextOnlyValueEditor : DataValueEditor { + public TextOnlyValueEditor( + DataEditorAttribute attribute, + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper) + : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) + { + } + /// - /// Custom value editor which ensures that the value stored is just plain text and that - /// no magic json formatting occurs when translating it to and from the database values + /// A method used to format the database value to a value that can be used by the editor /// - public class TextOnlyValueEditor : DataValueEditor + /// + /// + /// + /// + /// + /// The object returned will always be a string and if the database type is not a valid string type an exception is + /// thrown + /// + public override object ToEditor(IProperty property, string? culture = null, string? segment = null) { - public TextOnlyValueEditor( - DataEditorAttribute attribute, - ILocalizedTextService localizedTextService, - IShortStringHelper shortStringHelper, - IJsonSerializer jsonSerializer, - IIOHelper ioHelper) - : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute) - { } + var val = property.GetValue(culture, segment); - /// - /// A method used to format the database value to a value that can be used by the editor - /// - /// - /// - /// - /// - /// - /// The object returned will always be a string and if the database type is not a valid string type an exception is thrown - /// - public override object ToEditor(IProperty property, string? culture = null, string? segment = null) + if (val == null) { - var val = property.GetValue(culture, segment); - - if (val == null) return string.Empty; - - switch (ValueTypes.ToStorageType(ValueType)) - { - case ValueStorageType.Ntext: - case ValueStorageType.Nvarchar: - return val.ToString() ?? string.Empty; - case ValueStorageType.Integer: - case ValueStorageType.Decimal: - case ValueStorageType.Date: - default: - throw new InvalidOperationException("The " + typeof(TextOnlyValueEditor) + " can only be used with string based property editors"); - } + return string.Empty; } + switch (ValueTypes.ToStorageType(ValueType)) + { + case ValueStorageType.Ntext: + case ValueStorageType.Nvarchar: + return val.ToString() ?? string.Empty; + case ValueStorageType.Integer: + case ValueStorageType.Decimal: + case ValueStorageType.Date: + default: + throw new InvalidOperationException("The " + typeof(TextOnlyValueEditor) + + " can only be used with string based property editors"); + } } } diff --git a/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs index 60f4169ce60d..c69fca53957e 100644 --- a/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs @@ -1,58 +1,58 @@ -using System; -using System.Linq; -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Templates; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +[DefaultPropertyValueConverter] +public class TextStringValueConverter : PropertyValueConverterBase { - [DefaultPropertyValueConverter] - public class TextStringValueConverter : PropertyValueConverterBase + private static readonly string[] PropertyTypeAliases = { - public TextStringValueConverter(HtmlLocalLinkParser linkParser, HtmlUrlParser urlParser) - { - _linkParser = linkParser; - _urlParser = urlParser; - } - - private static readonly string[] PropertyTypeAliases = - { - Constants.PropertyEditors.Aliases.TextBox, - Constants.PropertyEditors.Aliases.TextArea - }; - private readonly HtmlLocalLinkParser _linkParser; - private readonly HtmlUrlParser _urlParser; + Constants.PropertyEditors.Aliases.TextBox, Constants.PropertyEditors.Aliases.TextArea + }; - public override bool IsConverter(IPublishedPropertyType propertyType) - => PropertyTypeAliases.Contains(propertyType.EditorAlias); + private readonly HtmlLocalLinkParser _linkParser; + private readonly HtmlUrlParser _urlParser; - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (string); + public TextStringValueConverter(HtmlLocalLinkParser linkParser, HtmlUrlParser urlParser) + { + _linkParser = linkParser; + _urlParser = urlParser; + } - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Snapshot; + public override bool IsConverter(IPublishedPropertyType propertyType) + => PropertyTypeAliases.Contains(propertyType.EditorAlias); - public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) - { - if (source == null) return null; - var sourceString = source.ToString(); + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(string); - // ensures string is parsed for {localLink} and URLs are resolved correctly - sourceString = _linkParser.EnsureInternalLinks(sourceString!, preview); - sourceString = _urlParser.EnsureUrls(sourceString); + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Snapshot; - return sourceString; - } - - public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, + object? source, bool preview) + { + if (source == null) { - // source should come from ConvertSource and be a string (or null) already - return inter ?? string.Empty; + return null; } - public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) - { - // source should come from ConvertSource and be a string (or null) already - return inter; - } + var sourceString = source.ToString(); + + // ensures string is parsed for {localLink} and URLs are resolved correctly + sourceString = _linkParser.EnsureInternalLinks(sourceString!, preview); + sourceString = _urlParser.EnsureUrls(sourceString); + + return sourceString; } + + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => + // source should come from ConvertSource and be a string (or null) already + inter ?? string.Empty; + + public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => + // source should come from ConvertSource and be a string (or null) already + inter; } diff --git a/src/Umbraco.Core/PropertyEditors/TextboxConfiguration.cs b/src/Umbraco.Core/PropertyEditors/TextboxConfiguration.cs index fb56567bc537..363eadd81dc2 100644 --- a/src/Umbraco.Core/PropertyEditors/TextboxConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/TextboxConfiguration.cs @@ -1,11 +1,11 @@ -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration for the textbox value editor. +/// +public class TextboxConfiguration { - /// - /// Represents the configuration for the textbox value editor. - /// - public class TextboxConfiguration - { - [ConfigurationField("maxChars", "Maximum allowed characters", "textstringlimited", Description = "If empty, 512 character limit")] - public int? MaxChars { get; set; } - } + [ConfigurationField("maxChars", "Maximum allowed characters", "textstringlimited", + Description = "If empty, 512 character limit")] + public int? MaxChars { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/TextboxConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/TextboxConfigurationEditor.cs index 81ea1f07b808..8949a2f2471a 100644 --- a/src/Umbraco.Core/PropertyEditors/TextboxConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/TextboxConfigurationEditor.cs @@ -1,28 +1,27 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration editor for the textbox value editor. +/// +public class TextboxConfigurationEditor : ConfigurationEditor { - /// - /// Represents the configuration editor for the textbox value editor. - /// - public class TextboxConfigurationEditor : ConfigurationEditor + // Scheduled for removal in v12 + [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + public TextboxConfigurationEditor(IIOHelper ioHelper) + : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) { - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public TextboxConfigurationEditor(IIOHelper ioHelper) - : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) - { - } + } - public TextboxConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) - { - } + public TextboxConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base( + ioHelper, editorConfigurationParser) + { } } diff --git a/src/Umbraco.Core/PropertyEditors/TrueFalseConfiguration.cs b/src/Umbraco.Core/PropertyEditors/TrueFalseConfiguration.cs index 945e10fd17c1..56db4ce52a58 100644 --- a/src/Umbraco.Core/PropertyEditors/TrueFalseConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/TrueFalseConfiguration.cs @@ -1,20 +1,22 @@ -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration for the boolean value editor. +/// +public class TrueFalseConfiguration { - /// - /// Represents the configuration for the boolean value editor. - /// - public class TrueFalseConfiguration - { - [ConfigurationField("default", "Initial State", "boolean", Description = "The initial state for the toggle, when it is displayed for the first time in the backoffice, eg. for a new content item.")] - public bool Default { get; set; } + [ConfigurationField("default", "Initial State", "boolean", + Description = + "The initial state for the toggle, when it is displayed for the first time in the backoffice, eg. for a new content item.")] + public bool Default { get; set; } - [ConfigurationField("showLabels", "Show toggle labels", "boolean", Description = "Show labels next to toggle button.")] - public bool ShowLabels { get; set; } + [ConfigurationField("showLabels", "Show toggle labels", "boolean", + Description = "Show labels next to toggle button.")] + public bool ShowLabels { get; set; } - [ConfigurationField("labelOn", "Label On", "textstring", Description = "Label text when enabled.")] - public string? LabelOn { get; set; } + [ConfigurationField("labelOn", "Label On", "textstring", Description = "Label text when enabled.")] + public string? LabelOn { get; set; } - [ConfigurationField("labelOff", "Label Off", "textstring", Description = "Label text when disabled.")] - public string? LabelOff { get; set; } - } + [ConfigurationField("labelOff", "Label Off", "textstring", Description = "Label text when disabled.")] + public string? LabelOff { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/TrueFalseConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/TrueFalseConfigurationEditor.cs index d5210edc872e..cfb37697e9b5 100644 --- a/src/Umbraco.Core/PropertyEditors/TrueFalseConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/TrueFalseConfigurationEditor.cs @@ -1,28 +1,27 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the configuration editor for the boolean value editor. +/// +public class TrueFalseConfigurationEditor : ConfigurationEditor { - /// - /// Represents the configuration editor for the boolean value editor. - /// - public class TrueFalseConfigurationEditor : ConfigurationEditor + // Scheduled for removal in v12 + [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + public TrueFalseConfigurationEditor(IIOHelper ioHelper) + : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) { - // Scheduled for removal in v12 - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public TrueFalseConfigurationEditor(IIOHelper ioHelper) - : this(ioHelper, StaticServiceProvider.Instance.GetRequiredService()) - { - } + } - public TrueFalseConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) - { - } + public TrueFalseConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : + base(ioHelper, editorConfigurationParser) + { } } diff --git a/src/Umbraco.Core/PropertyEditors/UnPublishedContentPropertyCacheCompressionOptions.cs b/src/Umbraco.Core/PropertyEditors/UnPublishedContentPropertyCacheCompressionOptions.cs index d8bade11e1c0..bc7cc532d954 100644 --- a/src/Umbraco.Core/PropertyEditors/UnPublishedContentPropertyCacheCompressionOptions.cs +++ b/src/Umbraco.Core/PropertyEditors/UnPublishedContentPropertyCacheCompressionOptions.cs @@ -1,25 +1,28 @@ using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Compress large, non published text properties +/// +public class UnPublishedContentPropertyCacheCompressionOptions : IPropertyCacheCompressionOptions { - /// - /// Compress large, non published text properties - /// - public class UnPublishedContentPropertyCacheCompressionOptions : IPropertyCacheCompressionOptions + public bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor, + bool published) { - public bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor, bool published) + if (!published && propertyType.SupportsPublishing && propertyType.ValueStorageType == ValueStorageType.Ntext) { - if (!published && propertyType.SupportsPublishing && propertyType.ValueStorageType == ValueStorageType.Ntext) - { - //Only compress non published content that supports publishing and the property is text - return true; - } - if (propertyType.ValueStorageType == ValueStorageType.Integer && Constants.PropertyEditors.Aliases.Boolean.Equals(dataEditor.Alias)) - { - //Compress boolean values from int to bool - return true; - } - return false; + //Only compress non published content that supports publishing and the property is text + return true; } + + if (propertyType.ValueStorageType == ValueStorageType.Integer && + Constants.PropertyEditors.Aliases.Boolean.Equals(dataEditor.Alias)) + { + //Compress boolean values from int to bool + return true; + } + + return false; } } diff --git a/src/Umbraco.Core/PropertyEditors/UserPickerConfiguration.cs b/src/Umbraco.Core/PropertyEditors/UserPickerConfiguration.cs index 3e2a48ffd627..d90e75fcfab1 100644 --- a/src/Umbraco.Core/PropertyEditors/UserPickerConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/UserPickerConfiguration.cs @@ -1,13 +1,9 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.PropertyEditors +public class UserPickerConfiguration : ConfigurationEditor { - public class UserPickerConfiguration : ConfigurationEditor + public override IDictionary DefaultConfiguration => new Dictionary { - public override IDictionary DefaultConfiguration => new Dictionary - { - { "entityType", "User" }, - { "multiPicker", "0" } - }; - } + {"entityType", "User"}, {"multiPicker", "0"} + }; } diff --git a/src/Umbraco.Core/PropertyEditors/UserPickerPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/UserPickerPropertyEditor.cs index 17dd8060f5a4..a0e317b69050 100644 --- a/src/Umbraco.Core/PropertyEditors/UserPickerPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/UserPickerPropertyEditor.cs @@ -1,25 +1,19 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; +namespace Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.PropertyEditors +[DataEditor( + Constants.PropertyEditors.Aliases.UserPicker, + "User Picker", + "userpicker", + ValueType = ValueTypes.Integer, + Group = Constants.PropertyEditors.Groups.People, + Icon = Constants.Icons.User)] +public class UserPickerPropertyEditor : DataEditor { - [DataEditor( - Constants.PropertyEditors.Aliases.UserPicker, - "User Picker", - "userpicker", - ValueType = ValueTypes.Integer, - Group = Constants.PropertyEditors.Groups.People, - Icon = Constants.Icons.User)] - public class UserPickerPropertyEditor : DataEditor + public UserPickerPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory) + : base(dataValueEditorFactory) { - public UserPickerPropertyEditor( - IDataValueEditorFactory dataValueEditorFactory) - : base(dataValueEditorFactory) - { } - - protected override IConfigurationEditor CreateConfigurationEditor() => new UserPickerConfiguration(); } + + protected override IConfigurationEditor CreateConfigurationEditor() => new UserPickerConfiguration(); } diff --git a/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorElementTypeValidationResult.cs b/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorElementTypeValidationResult.cs index ac7eb9ff614e..158198a37dcb 100644 --- a/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorElementTypeValidationResult.cs +++ b/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorElementTypeValidationResult.cs @@ -1,41 +1,40 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; -namespace Umbraco.Cms.Core.PropertyEditors.Validation +namespace Umbraco.Cms.Core.PropertyEditors.Validation; + +/// +/// A collection of for an element type within complex editor +/// represented by an Element Type +/// +/// +/// For a more indepth explanation of how server side validation works with the angular app, see this GitHub PR: +/// https://github.com/umbraco/Umbraco-CMS/pull/8339 +/// +public class ComplexEditorElementTypeValidationResult : ValidationResult { + public ComplexEditorElementTypeValidationResult(string elementTypeAlias, Guid blockId) + : base(string.Empty) + { + ElementTypeAlias = elementTypeAlias; + BlockId = blockId; + } + + public IList ValidationResults { get; } = + new List(); + /// - /// A collection of for an element type within complex editor represented by an Element Type + /// The element type alias of the validation result /// /// - /// For a more indepth explanation of how server side validation works with the angular app, see this GitHub PR: - /// https://github.com/umbraco/Umbraco-CMS/pull/8339 + /// This is useful for debugging purposes but it's not actively used in the angular app /// - public class ComplexEditorElementTypeValidationResult : ValidationResult - { - public ComplexEditorElementTypeValidationResult(string elementTypeAlias, Guid blockId) - : base(string.Empty) - { - ElementTypeAlias = elementTypeAlias; - BlockId = blockId; - } - - public IList ValidationResults { get; } = new List(); + public string ElementTypeAlias { get; } - /// - /// The element type alias of the validation result - /// - /// - /// This is useful for debugging purposes but it's not actively used in the angular app - /// - public string ElementTypeAlias { get; } - - /// - /// The Block ID of the validation result - /// - /// - /// This is the GUID id of the content item based on the element type - /// - public Guid BlockId { get; } - } + /// + /// The Block ID of the validation result + /// + /// + /// This is the GUID id of the content item based on the element type + /// + public Guid BlockId { get; } } diff --git a/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorPropertyTypeValidationResult.cs b/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorPropertyTypeValidationResult.cs index 449ef432d8a0..0ad261a2e469 100644 --- a/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorPropertyTypeValidationResult.cs +++ b/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorPropertyTypeValidationResult.cs @@ -1,36 +1,34 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; +using System.ComponentModel.DataAnnotations; -namespace Umbraco.Cms.Core.PropertyEditors.Validation +namespace Umbraco.Cms.Core.PropertyEditors.Validation; + +/// +/// A collection of for a property type within a complex editor represented by an +/// Element Type +/// +/// +/// For a more indepth explanation of how server side validation works with the angular app, see this GitHub PR: +/// https://github.com/umbraco/Umbraco-CMS/pull/8339 +/// +public class ComplexEditorPropertyTypeValidationResult : ValidationResult { - /// - /// A collection of for a property type within a complex editor represented by an Element Type - /// - /// - /// For a more indepth explanation of how server side validation works with the angular app, see this GitHub PR: - /// https://github.com/umbraco/Umbraco-CMS/pull/8339 - /// - public class ComplexEditorPropertyTypeValidationResult : ValidationResult - { - public ComplexEditorPropertyTypeValidationResult(string propertyTypeAlias) - : base(string.Empty) - { - PropertyTypeAlias = propertyTypeAlias; - } + private readonly List _validationResults = new(); - private readonly List _validationResults = new List(); + public ComplexEditorPropertyTypeValidationResult(string propertyTypeAlias) + : base(string.Empty) => + PropertyTypeAlias = propertyTypeAlias; - public void AddValidationResult(ValidationResult validationResult) - { - if (validationResult is ComplexEditorValidationResult && _validationResults.Any(x => x is ComplexEditorValidationResult)) - throw new InvalidOperationException($"Cannot add more than one {typeof(ComplexEditorValidationResult)}"); + public IReadOnlyList ValidationResults => _validationResults; + public string PropertyTypeAlias { get; } - _validationResults.Add(validationResult); + public void AddValidationResult(ValidationResult validationResult) + { + if (validationResult is ComplexEditorValidationResult && + _validationResults.Any(x => x is ComplexEditorValidationResult)) + { + throw new InvalidOperationException($"Cannot add more than one {typeof(ComplexEditorValidationResult)}"); } - public IReadOnlyList ValidationResults => _validationResults; - public string PropertyTypeAlias { get; } + _validationResults.Add(validationResult); } } diff --git a/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorValidationResult.cs b/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorValidationResult.cs index 225963f4612b..0d6ca74c8a33 100644 --- a/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorValidationResult.cs +++ b/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorValidationResult.cs @@ -1,25 +1,24 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; -namespace Umbraco.Cms.Core.PropertyEditors.Validation -{ +namespace Umbraco.Cms.Core.PropertyEditors.Validation; - /// - /// A collection of for a complex editor represented by an Element Type - /// - /// - /// For example, each represents validation results for a row in Nested Content. - /// - /// For a more indepth explanation of how server side validation works with the angular app, see this GitHub PR: - /// https://github.com/umbraco/Umbraco-CMS/pull/8339 - /// - public class ComplexEditorValidationResult : ValidationResult +/// +/// A collection of for a complex editor represented by an +/// Element Type +/// +/// +/// For example, each represents validation results for a row in Nested +/// Content. +/// For a more indepth explanation of how server side validation works with the angular app, see this GitHub PR: +/// https://github.com/umbraco/Umbraco-CMS/pull/8339 +/// +public class ComplexEditorValidationResult : ValidationResult +{ + public ComplexEditorValidationResult() + : base(string.Empty) { - public ComplexEditorValidationResult() - : base(string.Empty) - { - } - - public IList ValidationResults { get; } = new List(); } + + public IList ValidationResults { get; } = + new List(); } diff --git a/src/Umbraco.Core/PropertyEditors/Validators/DateTimeValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/DateTimeValidator.cs index 7c15a418d8ac..0429b96416fd 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/DateTimeValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/DateTimeValidator.cs @@ -1,34 +1,32 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors.Validators +namespace Umbraco.Cms.Core.PropertyEditors.Validators; + +/// +/// Used to validate if the value is a valid date/time +/// +public class DateTimeValidator : IValueValidator { - /// - /// Used to validate if the value is a valid date/time - /// - public class DateTimeValidator : IValueValidator + public IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration) { - public IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration) + //don't validate if empty + if (value == null || value.ToString().IsNullOrWhiteSpace()) { - //don't validate if empty - if (value == null || value.ToString().IsNullOrWhiteSpace()) - { - yield break; - } + yield break; + } - DateTime dt; - if (DateTime.TryParse(value.ToString(), out dt) == false) - { - yield return new ValidationResult(string.Format("The string value {0} cannot be parsed into a DateTime", value), - new[] - { - //we only store a single value for this editor so the 'member' or 'field' - // we'll associate this error with will simply be called 'value' - "value" - }); - } + DateTime dt; + if (DateTime.TryParse(value.ToString(), out dt) == false) + { + yield return new ValidationResult( + string.Format("The string value {0} cannot be parsed into a DateTime", value), + new[] + { + //we only store a single value for this editor so the 'member' or 'field' + // we'll associate this error with will simply be called 'value' + "value" + }); } } } diff --git a/src/Umbraco.Core/PropertyEditors/Validators/DecimalValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/DecimalValidator.cs index 1fb2486e45e3..b17dc9ccb289 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/DecimalValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/DecimalValidator.cs @@ -1,26 +1,28 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors.Validators +namespace Umbraco.Cms.Core.PropertyEditors.Validators; + +/// +/// A validator that validates that the value is a valid decimal +/// +public sealed class DecimalValidator : IManifestValueValidator { - /// - /// A validator that validates that the value is a valid decimal - /// - public sealed class DecimalValidator : IManifestValueValidator - { - /// - public string ValidationName => "Decimal"; + /// + public string ValidationName => "Decimal"; - /// - public IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration) + /// + public IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration) + { + if (value == null || value.ToString() == string.Empty) { - if (value == null || value.ToString() == string.Empty) - yield break; + yield break; + } - var result = value.TryConvertTo(); - if (result.Success == false) - yield return new ValidationResult("The value " + value + " is not a valid decimal", new[] { "value" }); + Attempt result = value.TryConvertTo(); + if (result.Success == false) + { + yield return new ValidationResult("The value " + value + " is not a valid decimal", new[] {"value"}); } } } diff --git a/src/Umbraco.Core/PropertyEditors/Validators/DelimitedValueValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/DelimitedValueValidator.cs index 8e93e5189e8e..29b5a722d06c 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/DelimitedValueValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/DelimitedValueValidator.cs @@ -1,59 +1,57 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Text.RegularExpressions; -namespace Umbraco.Cms.Core.PropertyEditors.Validators +namespace Umbraco.Cms.Core.PropertyEditors.Validators; + +/// +/// A validator that validates a delimited set of values against a common regex +/// +public sealed class DelimitedValueValidator : IManifestValueValidator { /// - /// A validator that validates a delimited set of values against a common regex + /// Gets or sets the configuration, when parsed as . /// - public sealed class DelimitedValueValidator : IManifestValueValidator - { - /// - public string ValidationName => "Delimited"; + public DelimitedValueValidatorConfig? Configuration { get; set; } - /// - /// Gets or sets the configuration, when parsed as . - /// - public DelimitedValueValidatorConfig? Configuration { get; set; } + /// + public string ValidationName => "Delimited"; - /// - public IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration) + /// + public IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration) + { + // TODO: localize these! + if (value != null) { - // TODO: localize these! - if (value != null) - { - var delimiter = Configuration?.Delimiter ?? ","; - var regex = (Configuration?.Pattern != null) ? new Regex(Configuration.Pattern) : null; + var delimiter = Configuration?.Delimiter ?? ","; + Regex regex = Configuration?.Pattern != null ? new Regex(Configuration.Pattern) : null; - var stringVal = value.ToString(); - var split = stringVal!.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries); - for (var i = 0; i < split.Length; i++) + var stringVal = value.ToString(); + var split = stringVal!.Split(new[] {delimiter}, StringSplitOptions.RemoveEmptyEntries); + for (var i = 0; i < split.Length; i++) + { + var s = split[i]; + //next if we have a regex statement validate with that + if (regex != null) { - var s = split[i]; - //next if we have a regex statement validate with that - if (regex != null) + if (regex.IsMatch(s) == false) { - if (regex.IsMatch(s) == false) - { - yield return new ValidationResult("The item at index " + i + " did not match the expression " + regex, - new[] - { - //make the field name called 'value0' where 0 is the index - "value" + i - }); - } + yield return new ValidationResult( + "The item at index " + i + " did not match the expression " + regex, + new[] + { + //make the field name called 'value0' where 0 is the index + "value" + i + }); } } } } } +} - public class DelimitedValueValidatorConfig - { - public string? Delimiter { get; set; } - public string? Pattern { get; set; } - } +public class DelimitedValueValidatorConfig +{ + public string? Delimiter { get; set; } + public string? Pattern { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs index 0db537ede582..ca10db63f1f4 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs @@ -1,28 +1,26 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; -namespace Umbraco.Cms.Core.PropertyEditors.Validators +namespace Umbraco.Cms.Core.PropertyEditors.Validators; + +/// +/// A validator that validates an email address +/// +public sealed class EmailValidator : IManifestValueValidator { - /// - /// A validator that validates an email address - /// - public sealed class EmailValidator : IManifestValueValidator - { - /// - public string ValidationName => "Email"; + /// + public string ValidationName => "Email"; - /// - public IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration) - { - var asString = value == null ? "" : value.ToString(); + /// + public IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration) + { + var asString = value == null ? "" : value.ToString(); - var emailVal = new EmailAddressAttribute(); + var emailVal = new EmailAddressAttribute(); - if (asString != string.Empty && emailVal.IsValid(asString) == false) - { - // TODO: localize these! - yield return new ValidationResult("Email is invalid", new[] { "value" }); - } + if (asString != string.Empty && emailVal.IsValid(asString) == false) + { + // TODO: localize these! + yield return new ValidationResult("Email is invalid", new[] {"value"}); } } } diff --git a/src/Umbraco.Core/PropertyEditors/Validators/IntegerValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/IntegerValidator.cs index 351d0de82d54..fa527eac12f3 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/IntegerValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/IntegerValidator.cs @@ -1,27 +1,25 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors.Validators +namespace Umbraco.Cms.Core.PropertyEditors.Validators; + +/// +/// A validator that validates that the value is a valid integer +/// +public sealed class IntegerValidator : IManifestValueValidator { - /// - /// A validator that validates that the value is a valid integer - /// - public sealed class IntegerValidator : IManifestValueValidator - { - /// - public string ValidationName => "Integer"; + /// + public string ValidationName => "Integer"; - /// - public IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration) + /// + public IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration) + { + if (value != null && value.ToString() != string.Empty) { - if (value != null && value.ToString() != string.Empty) + Attempt result = value.TryConvertTo(); + if (result.Success == false) { - var result = value.TryConvertTo(); - if (result.Success == false) - { - yield return new ValidationResult("The value " + value + " is not a valid integer", new[] { "value" }); - } + yield return new ValidationResult("The value " + value + " is not a valid integer", new[] {"value"}); } } } diff --git a/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs index ead85c30e48f..7c99ebaf0f26 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs @@ -1,84 +1,103 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Text.RegularExpressions; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors.Validators +namespace Umbraco.Cms.Core.PropertyEditors.Validators; + +/// +/// A validator that validates that the value against a regular expression. +/// +public sealed class RegexValidator : IValueFormatValidator, IManifestValueValidator { + private const string ValueIsInvalid = "Value is invalid, it does not match the correct pattern"; + private readonly ILocalizedTextService _textService; + private string _regex; + /// - /// A validator that validates that the value against a regular expression. + /// Initializes a new instance of the class. /// - public sealed class RegexValidator : IValueFormatValidator, IManifestValueValidator + /// + /// Use this constructor when the validator is used as an , + /// and the regular expression is supplied at validation time. This constructor is also used when + /// the validator is used as an and the regular expression + /// is supplied via the method. + /// + public RegexValidator(ILocalizedTextService textService) : this(textService, string.Empty) { - private readonly ILocalizedTextService _textService; - private string _regex; + } - const string ValueIsInvalid = "Value is invalid, it does not match the correct pattern"; + /// + /// Initializes a new instance of the class. + /// + /// + /// Use this constructor when the validator is used as an , + /// and the regular expression must be supplied when the validator is created. + /// + public RegexValidator(ILocalizedTextService textService, string regex) + { + _textService = textService; + _regex = regex; + } - /// - public string ValidationName => "Regex"; + /// + /// Gets or sets the configuration, when parsed as . + /// + public string Configuration + { + get => _regex; + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } - /// - /// Initializes a new instance of the class. - /// - /// Use this constructor when the validator is used as an , - /// and the regular expression is supplied at validation time. This constructor is also used when - /// the validator is used as an and the regular expression - /// is supplied via the method. - public RegexValidator(ILocalizedTextService textService) : this(textService, string.Empty) - { } + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(value)); + } - /// - /// Initializes a new instance of the class. - /// - /// Use this constructor when the validator is used as an , - /// and the regular expression must be supplied when the validator is created. - public RegexValidator(ILocalizedTextService textService, string regex) - { - _textService = textService; - _regex = regex; + _regex = value; } + } - /// - /// Gets or sets the configuration, when parsed as . - /// - public string Configuration - { - get => _regex; - set - { - if (value == null) throw new ArgumentNullException(nameof(value)); - if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(value)); + /// + public string ValidationName => "Regex"; - _regex = value; - } + /// + public IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration) + { + if (_regex == null) + { + throw new InvalidOperationException("The validator has not been configured."); } - /// - public IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration) + return ValidateFormat(value, valueType, _regex); + } + + /// + public IEnumerable ValidateFormat(object? value, string? valueType, string format) + { + if (format == null) { - if (_regex == null) - { - throw new InvalidOperationException("The validator has not been configured."); - } + throw new ArgumentNullException(nameof(format)); + } - return ValidateFormat(value, valueType, _regex); + if (string.IsNullOrWhiteSpace(format)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(format)); } - /// - public IEnumerable ValidateFormat(object? value, string? valueType, string format) + if (value == null || !new Regex(format).IsMatch(value.ToString()!)) { - if (format == null) throw new ArgumentNullException(nameof(format)); - if (string.IsNullOrWhiteSpace(format)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(format)); - if (value == null || !new Regex(format).IsMatch(value.ToString()!)) - { - yield return new ValidationResult(_textService?.Localize("validation", "invalidPattern") ?? ValueIsInvalid, new[] { "value" }); - } + yield return new ValidationResult(_textService?.Localize("validation", "invalidPattern") ?? ValueIsInvalid, + new[] {"value"}); } } } diff --git a/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs index 050ba5a38826..323f1042a067 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs @@ -1,56 +1,52 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors.Validators +namespace Umbraco.Cms.Core.PropertyEditors.Validators; + +/// +/// A validator that validates that the value is not null or empty (if it is a string) +/// +public sealed class RequiredValidator : IValueRequiredValidator, IManifestValueValidator { - /// - /// A validator that validates that the value is not null or empty (if it is a string) - /// - public sealed class RequiredValidator : IValueRequiredValidator, IManifestValueValidator - { - private readonly ILocalizedTextService _textService; - const string ValueCannotBeNull = "Value cannot be null"; - const string ValueCannotBeEmpty = "Value cannot be empty"; - public RequiredValidator(ILocalizedTextService textService) - { - _textService = textService; - } + private const string ValueCannotBeNull = "Value cannot be null"; + private const string ValueCannotBeEmpty = "Value cannot be empty"; + private readonly ILocalizedTextService _textService; - /// - public string ValidationName => "Required"; + public RequiredValidator(ILocalizedTextService textService) => _textService = textService; - /// - public IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration) + /// + public string ValidationName => "Required"; + + /// + public IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration) => + ValidateRequired(value, valueType); + + /// + public IEnumerable ValidateRequired(object? value, string? valueType) + { + if (value == null) { - return ValidateRequired(value, valueType); + yield return new ValidationResult(_textService?.Localize("validation", "invalidNull") ?? ValueCannotBeNull, + new[] {"value"}); + yield break; } - /// - public IEnumerable ValidateRequired(object? value, string? valueType) + if (valueType.InvariantEquals(ValueTypes.Json)) { - if (value == null) + if (value.ToString()?.DetectIsEmptyJson() ?? false) { - yield return new ValidationResult(_textService?.Localize("validation", "invalidNull") ?? ValueCannotBeNull, new[] {"value"}); - yield break; + yield return new ValidationResult( + _textService?.Localize("validation", "invalidEmpty") ?? ValueCannotBeEmpty, new[] {"value"}); } - if (valueType.InvariantEquals(ValueTypes.Json)) - { - if (value.ToString()?.DetectIsEmptyJson() ?? false) - { - - yield return new ValidationResult(_textService?.Localize("validation", "invalidEmpty") ?? ValueCannotBeEmpty, new[] { "value" }); - } - - yield break; - } + yield break; + } - if (value.ToString().IsNullOrWhiteSpace()) - { - yield return new ValidationResult(_textService?.Localize("validation", "invalidEmpty") ?? ValueCannotBeEmpty, new[] { "value" }); - } + if (value.ToString().IsNullOrWhiteSpace()) + { + yield return new ValidationResult( + _textService?.Localize("validation", "invalidEmpty") ?? ValueCannotBeEmpty, new[] {"value"}); } } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs index 2aeee98bf4b4..c5ef520f8d58 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs @@ -1,39 +1,35 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Serialization; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +[DefaultPropertyValueConverter] +public class CheckboxListValueConverter : PropertyValueConverterBase { - [DefaultPropertyValueConverter] - public class CheckboxListValueConverter : PropertyValueConverterBase - { - private readonly IJsonSerializer _jsonSerializer; + private readonly IJsonSerializer _jsonSerializer; - public CheckboxListValueConverter(IJsonSerializer jsonSerializer) - { - _jsonSerializer = jsonSerializer; - } + public CheckboxListValueConverter(IJsonSerializer jsonSerializer) => _jsonSerializer = jsonSerializer; - public override bool IsConverter(IPublishedPropertyType propertyType) - => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.CheckBoxList); + public override bool IsConverter(IPublishedPropertyType propertyType) + => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.CheckBoxList); - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (IEnumerable); + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(IEnumerable); - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Element; + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Element; - public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview) - { - var sourceString = source?.ToString() ?? string.Empty; - - if (string.IsNullOrEmpty(sourceString)) - return Enumerable.Empty(); + public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel cacheLevel, object? source, bool preview) + { + var sourceString = source?.ToString() ?? string.Empty; - return _jsonSerializer.Deserialize(sourceString); + if (string.IsNullOrEmpty(sourceString)) + { + return Enumerable.Empty(); } + + return _jsonSerializer.Deserialize(sourceString); } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs index 126b4516d1ae..bbd496c412ae 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs @@ -1,92 +1,116 @@ -using System; -using System.Collections.Generic; using System.Globalization; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +internal class ContentPickerValueConverter : PropertyValueConverterBase { - internal class ContentPickerValueConverter : PropertyValueConverterBase + private static readonly List PropertiesToExclude = new() { - private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + Constants.Conventions.Content.InternalRedirectId.ToLower(CultureInfo.InvariantCulture), + Constants.Conventions.Content.Redirect.ToLower(CultureInfo.InvariantCulture) + }; - private static readonly List PropertiesToExclude = new List - { - Constants.Conventions.Content.InternalRedirectId.ToLower(CultureInfo.InvariantCulture), - Constants.Conventions.Content.Redirect.ToLower(CultureInfo.InvariantCulture) - }; + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; - public ContentPickerValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor) => _publishedSnapshotAccessor = publishedSnapshotAccessor; + public ContentPickerValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor) => + _publishedSnapshotAccessor = publishedSnapshotAccessor; - public override bool IsConverter(IPublishedPropertyType propertyType) - => propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.ContentPicker); + public override bool IsConverter(IPublishedPropertyType propertyType) + => propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.ContentPicker); - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof(IPublishedContent); + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(IPublishedContent); - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Elements; + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Elements; - public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) + public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, + object? source, bool preview) + { + if (source == null) { - if (source == null) return null; + return null; + } - if(source is not string) - { - var attemptConvertInt = source.TryConvertTo(); - if (attemptConvertInt.Success) - return attemptConvertInt.Result; - } - //Don't attempt to convert to int for UDI - if( source is string strSource - && !string.IsNullOrWhiteSpace(strSource) - && !strSource.StartsWith("umb") - && int.TryParse(strSource, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue)) + if (source is not string) + { + Attempt attemptConvertInt = source.TryConvertTo(); + if (attemptConvertInt.Success) { - return intValue; + return attemptConvertInt.Result; } + } - var attemptConvertUdi = source.TryConvertTo(); - if (attemptConvertUdi.Success) - return attemptConvertUdi.Result; - return null; + //Don't attempt to convert to int for UDI + if (source is string strSource + && !string.IsNullOrWhiteSpace(strSource) + && !strSource.StartsWith("umb") + && int.TryParse(strSource, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue)) + { + return intValue; } - public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + Attempt attemptConvertUdi = source.TryConvertTo(); + if (attemptConvertUdi.Success) { - if (inter == null) - return null; + return attemptConvertUdi.Result; + } - if ((propertyType.Alias != null && PropertiesToExclude.Contains(propertyType.Alias.ToLower(CultureInfo.InvariantCulture))) == false) + return null; + } + + public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + { + if (inter == null) + { + return null; + } + + if ((propertyType.Alias != null && + PropertiesToExclude.Contains(propertyType.Alias.ToLower(CultureInfo.InvariantCulture))) == false) + { + IPublishedContent? content; + IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot(); + if (inter is int id) { - IPublishedContent? content; - var publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot(); - if (inter is int id) + content = publishedSnapshot.Content?.GetById(id); + if (content != null) { - content = publishedSnapshot.Content?.GetById(id); - if (content != null) - return content; + return content; } - else + } + else + { + var udi = inter as GuidUdi; + if (udi is null) { - var udi = inter as GuidUdi; - if (udi is null) - return null; - content = publishedSnapshot.Content?.GetById(udi.Guid); - if (content != null && content.ContentType.ItemType == PublishedItemType.Content) - return content; + return null; } - } - return inter; + content = publishedSnapshot.Content?.GetById(udi.Guid); + if (content != null && content.ContentType.ItemType == PublishedItemType.Content) + { + return content; + } + } } - public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + return inter; + } + + public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + { + if (inter == null) { - if (inter == null) return null; - return inter.ToString(); + return null; } + + return inter.ToString(); } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs index 7182719ee11b..2fb967aea0c1 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs @@ -1,52 +1,56 @@ -using System; -using System.Xml; +using System.Xml; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +[DefaultPropertyValueConverter] +public class DatePickerValueConverter : PropertyValueConverterBase { - [DefaultPropertyValueConverter] - public class DatePickerValueConverter : PropertyValueConverterBase - { - public override bool IsConverter(IPublishedPropertyType propertyType) - => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.DateTime); + public override bool IsConverter(IPublishedPropertyType propertyType) + => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.DateTime); - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (DateTime); + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(DateTime); - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Element; + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, + object? source, bool preview) + { + if (source == null) { - if (source == null) return DateTime.MinValue; - - // in XML a DateTime is: string - format "yyyy-MM-ddTHH:mm:ss" - // Actually, not always sometimes it is formatted in UTC style with 'Z' suffixed on the end but that is due to this bug: - // http://issues.umbraco.org/issue/U4-4145, http://issues.umbraco.org/issue/U4-3894 - // We should just be using TryConvertTo instead. - - if (source is string sourceString) - { - var attempt = sourceString.TryConvertTo(); - return attempt.Success == false ? DateTime.MinValue : attempt.Result; - } - - // in the database a DateTime is: DateTime - // default value is: DateTime.MinValue - return source is DateTime ? source : DateTime.MinValue; + return DateTime.MinValue; } - // default ConvertSourceToObject just returns source ie a DateTime value + // in XML a DateTime is: string - format "yyyy-MM-ddTHH:mm:ss" + // Actually, not always sometimes it is formatted in UTC style with 'Z' suffixed on the end but that is due to this bug: + // http://issues.umbraco.org/issue/U4-4145, http://issues.umbraco.org/issue/U4-3894 + // We should just be using TryConvertTo instead. - public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + if (source is string sourceString) { - // source should come from ConvertSource and be a DateTime already - if (inter is null) - { - return null; - } - return XmlConvert.ToString((DateTime) inter, XmlDateTimeSerializationMode.Unspecified); + Attempt attempt = sourceString.TryConvertTo(); + return attempt.Success == false ? DateTime.MinValue : attempt.Result; } + + // in the database a DateTime is: DateTime + // default value is: DateTime.MinValue + return source is DateTime ? source : DateTime.MinValue; + } + + // default ConvertSourceToObject just returns source ie a DateTime value + + public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + { + // source should come from ConvertSource and be a DateTime already + if (inter is null) + { + return null; + } + + return XmlConvert.ToString((DateTime)inter, XmlDateTimeSerializationMode.Unspecified); } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs index 06eb23bc70c6..5ab8403d1fa9 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs @@ -1,48 +1,50 @@ -using System; -using System.Globalization; +using System.Globalization; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +[DefaultPropertyValueConverter] +public class DecimalValueConverter : PropertyValueConverterBase { - [DefaultPropertyValueConverter] - public class DecimalValueConverter : PropertyValueConverterBase - { - public override bool IsConverter(IPublishedPropertyType propertyType) - => Constants.PropertyEditors.Aliases.Decimal.Equals(propertyType.EditorAlias); + public override bool IsConverter(IPublishedPropertyType propertyType) + => Constants.PropertyEditors.Aliases.Decimal.Equals(propertyType.EditorAlias); - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (decimal); + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(decimal); - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Element; + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, + object? source, bool preview) + { + if (source == null) { - if (source == null) - { - return 0M; - } - - // is it already a decimal? - if(source is decimal) - { - return source; - } - - // is it a double? - if(source is double sourceDouble) - { - return Convert.ToDecimal(sourceDouble); - } - - // is it a string? - if (source is string sourceString) - { - return decimal.TryParse(sourceString, NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out decimal d) ? d : 0M; - } - - // couldn't convert the source value - default to zero return 0M; } + + // is it already a decimal? + if (source is decimal) + { + return source; + } + + // is it a double? + if (source is double sourceDouble) + { + return Convert.ToDecimal(sourceDouble); + } + + // is it a string? + if (source is string sourceString) + { + return decimal.TryParse(sourceString, NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, + CultureInfo.InvariantCulture, out var d) + ? d + : 0M; + } + + // couldn't convert the source value - default to zero + return 0M; } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/EmailAddressValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/EmailAddressValueConverter.cs index ea7a8b2301e9..ec39b724ac5f 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/EmailAddressValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/EmailAddressValueConverter.cs @@ -1,24 +1,20 @@ -using System; -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +[DefaultPropertyValueConverter] +public class EmailAddressValueConverter : PropertyValueConverterBase { - [DefaultPropertyValueConverter] - public class EmailAddressValueConverter : PropertyValueConverterBase - { - public override bool IsConverter(IPublishedPropertyType propertyType) - => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.EmailAddress); + public override bool IsConverter(IPublishedPropertyType propertyType) + => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.EmailAddress); - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (string); + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(string); - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Element; + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Element; - public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview) - { - return source?.ToString() ?? string.Empty; - } - } + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel cacheLevel, object? source, bool preview) => source?.ToString() ?? string.Empty; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/EyeDropperValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/EyeDropperValueConverter.cs index 6ea5aae9bbd9..c0127fbf471c 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/EyeDropperValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/EyeDropperValueConverter.cs @@ -1,22 +1,21 @@ -using System; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +[DefaultPropertyValueConverter] +public class EyeDropperValueConverter : PropertyValueConverterBase { - [DefaultPropertyValueConverter] - public class EyeDropperValueConverter : PropertyValueConverterBase - { - public override bool IsConverter(IPublishedPropertyType propertyType) - => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.ColorPickerEyeDropper); + public override bool IsConverter(IPublishedPropertyType propertyType) + => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.ColorPickerEyeDropper); - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof(string); + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(string); - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Element; + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Element; - public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview) - => source?.ToString() ?? string.Empty; - } + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel cacheLevel, object? source, bool preview) + => source?.ToString() ?? string.Empty; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs index 4bffc5a928ef..7e9af9a6ebfb 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs @@ -1,24 +1,20 @@ -using System; -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +[DefaultPropertyValueConverter] +public class IntegerValueConverter : PropertyValueConverterBase { - [DefaultPropertyValueConverter] - public class IntegerValueConverter : PropertyValueConverterBase - { - public override bool IsConverter(IPublishedPropertyType propertyType) - => Constants.PropertyEditors.Aliases.Integer.Equals(propertyType.EditorAlias); + public override bool IsConverter(IPublishedPropertyType propertyType) + => Constants.PropertyEditors.Aliases.Integer.Equals(propertyType.EditorAlias); - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (int); + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(int); - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Element; + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) - { - return source.TryConvertTo().Result; - } - } + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, + object? source, bool preview) => source.TryConvertTo().Result; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs index 9f2c06cdf90e..12c02a3ce076 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs @@ -1,85 +1,128 @@ -using System; -using System.Globalization; +using System.Globalization; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +/// +/// We need this property converter so that we always force the value of a label to be a string +/// +/// +/// Without a property converter defined for the label type, the value will be converted with +/// the `ConvertUsingDarkMagic` method which will try to parse the value into it's correct type, but this +/// can cause issues if the string is detected as a number and then strips leading zeros. +/// Example: http://issues.umbraco.org/issue/U4-7929 +/// +[DefaultPropertyValueConverter] +public class LabelValueConverter : PropertyValueConverterBase { - /// - /// We need this property converter so that we always force the value of a label to be a string - /// - /// - /// Without a property converter defined for the label type, the value will be converted with - /// the `ConvertUsingDarkMagic` method which will try to parse the value into it's correct type, but this - /// can cause issues if the string is detected as a number and then strips leading zeros. - /// Example: http://issues.umbraco.org/issue/U4-7929 - /// - [DefaultPropertyValueConverter] - public class LabelValueConverter : PropertyValueConverterBase - { - public override bool IsConverter(IPublishedPropertyType propertyType) - => Constants.PropertyEditors.Aliases.Label.Equals(propertyType.EditorAlias); + public override bool IsConverter(IPublishedPropertyType propertyType) + => Constants.PropertyEditors.Aliases.Label.Equals(propertyType.EditorAlias); - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + { + LabelConfiguration valueType = + ConfigurationEditor.ConfigurationAs(propertyType.DataType.Configuration); + switch (valueType?.ValueType) { - var valueType = ConfigurationEditor.ConfigurationAs(propertyType.DataType.Configuration); - switch (valueType?.ValueType) - { - case ValueTypes.DateTime: - case ValueTypes.Date: - return typeof(DateTime); - case ValueTypes.Time: - return typeof(TimeSpan); - case ValueTypes.Decimal: - return typeof(decimal); - case ValueTypes.Integer: - return typeof(int); - case ValueTypes.Bigint: - return typeof(long); - default: // everything else is a string - return typeof(string); - } + case ValueTypes.DateTime: + case ValueTypes.Date: + return typeof(DateTime); + case ValueTypes.Time: + return typeof(TimeSpan); + case ValueTypes.Decimal: + return typeof(decimal); + case ValueTypes.Integer: + return typeof(int); + case ValueTypes.Bigint: + return typeof(long); + default: // everything else is a string + return typeof(string); } + } - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Element; + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, + object? source, bool preview) + { + LabelConfiguration valueType = + ConfigurationEditor.ConfigurationAs(propertyType.DataType.Configuration); + switch (valueType?.ValueType) { - var valueType = ConfigurationEditor.ConfigurationAs(propertyType.DataType.Configuration); - switch (valueType?.ValueType) - { - case ValueTypes.DateTime: - case ValueTypes.Date: - if (source is DateTime sourceDateTime) - return sourceDateTime; - if (source is string sourceDateTimeString) - return DateTime.TryParse(sourceDateTimeString, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt) ? dt : DateTime.MinValue; - return DateTime.MinValue; - case ValueTypes.Time: - if (source is DateTime sourceTime) - return sourceTime.TimeOfDay; - if (source is string sourceTimeString) - return TimeSpan.TryParse(sourceTimeString, CultureInfo.InvariantCulture, out var ts) ? ts : TimeSpan.Zero; - return TimeSpan.Zero; - case ValueTypes.Decimal: - if (source is decimal sourceDecimal) return sourceDecimal; - if (source is string sourceDecimalString) - return decimal.TryParse(sourceDecimalString, NumberStyles.Any, CultureInfo.InvariantCulture, out var d) ? d : 0; - if (source is double sourceDouble) - return Convert.ToDecimal(sourceDouble); - return (decimal)0; - case ValueTypes.Integer: - if (source is int sourceInt) return sourceInt; - if (source is string sourceIntString) - return int.TryParse(sourceIntString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var i) ? i : 0; - return 0; - case ValueTypes.Bigint: - if (source is string sourceLongString) - return long.TryParse(sourceLongString, out var i) ? i : 0; - return (long)0; - default: // everything else is a string - return source?.ToString() ?? string.Empty; - } + case ValueTypes.DateTime: + case ValueTypes.Date: + if (source is DateTime sourceDateTime) + { + return sourceDateTime; + } + + if (source is string sourceDateTimeString) + { + return DateTime.TryParse(sourceDateTimeString, CultureInfo.InvariantCulture, DateTimeStyles.None, + out DateTime dt) + ? dt + : DateTime.MinValue; + } + + return DateTime.MinValue; + case ValueTypes.Time: + if (source is DateTime sourceTime) + { + return sourceTime.TimeOfDay; + } + + if (source is string sourceTimeString) + { + return TimeSpan.TryParse(sourceTimeString, CultureInfo.InvariantCulture, out TimeSpan ts) + ? ts + : TimeSpan.Zero; + } + + return TimeSpan.Zero; + case ValueTypes.Decimal: + if (source is decimal sourceDecimal) + { + return sourceDecimal; + } + + if (source is string sourceDecimalString) + { + return decimal.TryParse(sourceDecimalString, NumberStyles.Any, CultureInfo.InvariantCulture, + out var d) + ? d + : 0; + } + + if (source is double sourceDouble) + { + return Convert.ToDecimal(sourceDouble); + } + + return (decimal)0; + case ValueTypes.Integer: + if (source is int sourceInt) + { + return sourceInt; + } + + if (source is string sourceIntString) + { + return int.TryParse(sourceIntString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var i) + ? i + : 0; + } + + return 0; + case ValueTypes.Bigint: + if (source is string sourceLongString) + { + return long.TryParse(sourceLongString, out var i) ? i : 0; + } + + return (long)0; + default: // everything else is a string + return source?.ToString() ?? string.Empty; } } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs index 2df96fc3101a..9de4792b1adf 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs @@ -1,92 +1,102 @@ -using System; using System.Collections; -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +/// +/// The media picker property value converter. +/// +[DefaultPropertyValueConverter] +public class MediaPickerValueConverter : PropertyValueConverterBase { - /// - /// The media picker property value converter. - /// - [DefaultPropertyValueConverter] - public class MediaPickerValueConverter : PropertyValueConverterBase + // hard-coding "image" here but that's how it works at UI level too + private const string ImageTypeAlias = "image"; + + private readonly IPublishedModelFactory _publishedModelFactory; + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + + public MediaPickerValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor, + IPublishedModelFactory publishedModelFactory) { - // hard-coding "image" here but that's how it works at UI level too - private const string ImageTypeAlias = "image"; + _publishedSnapshotAccessor = publishedSnapshotAccessor ?? + throw new ArgumentNullException(nameof(publishedSnapshotAccessor)); + _publishedModelFactory = publishedModelFactory; + } - private readonly IPublishedModelFactory _publishedModelFactory; - private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + public override bool IsConverter(IPublishedPropertyType propertyType) => + propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.MediaPicker); - public MediaPickerValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor, - IPublishedModelFactory publishedModelFactory) - { - _publishedSnapshotAccessor = publishedSnapshotAccessor ?? - throw new ArgumentNullException(nameof(publishedSnapshotAccessor)); - _publishedModelFactory = publishedModelFactory; - } + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + { + var isMultiple = IsMultipleDataType(propertyType.DataType); + return isMultiple + ? typeof(IEnumerable) + : typeof(IPublishedContent); + } + + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Snapshot; - public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.MediaPicker); + private bool IsMultipleDataType(PublishedDataType dataType) + { + MediaPickerConfiguration config = + ConfigurationEditor.ConfigurationAs(dataType.Configuration); + return config?.Multiple ?? false; + } - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, + object? source, bool preview) + { + if (source == null) { - var isMultiple = IsMultipleDataType(propertyType.DataType); - return isMultiple - ? typeof(IEnumerable) - : typeof(IPublishedContent); + return null; } - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Snapshot; + Udi[] nodeIds = source.ToString()? + .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) + .Select(UdiParser.Parse) + .ToArray(); + return nodeIds; + } - private bool IsMultipleDataType(PublishedDataType dataType) - { - var config = ConfigurationEditor.ConfigurationAs(dataType.Configuration); - return config?.Multiple ?? false; - } + public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel cacheLevel, object? source, bool preview) + { + var isMultiple = IsMultipleDataType(propertyType.DataType); - public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, - object? source, bool preview) - { - if (source == null) return null; + var udis = (Udi[]?)source; + var mediaItems = new List(); - var nodeIds = source.ToString()? - .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) - .Select(UdiParser.Parse) - .ToArray(); - return nodeIds; + if (source == null) + { + return isMultiple ? mediaItems : null; } - public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel cacheLevel, object? source, bool preview) + if (udis?.Any() ?? false) { - var isMultiple = IsMultipleDataType(propertyType.DataType); - - var udis = (Udi[]?)source; - var mediaItems = new List(); - - if (source == null) return isMultiple ? mediaItems : null; - - if (udis?.Any() ?? false) + IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot(); + foreach (Udi udi in udis) { - var publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot(); - foreach (var udi in udis) + var guidUdi = udi as GuidUdi; + if (guidUdi is null) { - var guidUdi = udi as GuidUdi; - if (guidUdi is null) continue; - var item = publishedSnapshot?.Media?.GetById(guidUdi.Guid); - if (item != null) - mediaItems.Add(item); + continue; } - return isMultiple ? mediaItems : FirstOrDefault(mediaItems); + IPublishedContent item = publishedSnapshot?.Media?.GetById(guidUdi.Guid); + if (item != null) + { + mediaItems.Add(item); + } } - return source; + return isMultiple ? mediaItems : FirstOrDefault(mediaItems); } - private object? FirstOrDefault(IList mediaItems) => mediaItems.Count == 0 ? null : mediaItems[0]; + return source; } + + private object? FirstOrDefault(IList mediaItems) => mediaItems.Count == 0 ? null : mediaItems[0]; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberGroupPickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberGroupPickerValueConverter.cs index 2fcaa011fd98..e04dbcc54619 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberGroupPickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberGroupPickerValueConverter.cs @@ -1,24 +1,20 @@ -using System; -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +[DefaultPropertyValueConverter] +public class MemberGroupPickerValueConverter : PropertyValueConverterBase { - [DefaultPropertyValueConverter] - public class MemberGroupPickerValueConverter : PropertyValueConverterBase - { - public override bool IsConverter(IPublishedPropertyType propertyType) - => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.MemberGroupPicker); + public override bool IsConverter(IPublishedPropertyType propertyType) + => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.MemberGroupPicker); - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (string); + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(string); - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Element; + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) - { - return source?.ToString() ?? string.Empty; - } - } + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, + object? source, bool preview) => source?.ToString() ?? string.Empty; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs index 241b968df92d..d41c4ff71e39 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs @@ -1,4 +1,3 @@ -using System; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; @@ -6,91 +5,103 @@ using Umbraco.Cms.Core.Web; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +[DefaultPropertyValueConverter] +public class MemberPickerValueConverter : PropertyValueConverterBase { - [DefaultPropertyValueConverter] - public class MemberPickerValueConverter : PropertyValueConverterBase + private readonly IMemberService _memberService; + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + + public MemberPickerValueConverter( + IMemberService memberService, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IUmbracoContextAccessor umbracoContextAccessor) + { + _memberService = memberService; + _publishedSnapshotAccessor = publishedSnapshotAccessor; + _umbracoContextAccessor = umbracoContextAccessor; + } + + public override bool IsConverter(IPublishedPropertyType propertyType) + => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.MemberPicker); + + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Snapshot; + + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(IPublishedContent); + + public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, + object? source, bool preview) { - private readonly IMemberService _memberService; - private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - - public MemberPickerValueConverter( - IMemberService memberService, - IPublishedSnapshotAccessor publishedSnapshotAccessor, - IUmbracoContextAccessor umbracoContextAccessor) + if (source == null) { - _memberService = memberService; - _publishedSnapshotAccessor = publishedSnapshotAccessor; - _umbracoContextAccessor = umbracoContextAccessor; + return null; } - public override bool IsConverter(IPublishedPropertyType propertyType) - => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.MemberPicker); + Attempt attemptConvertInt = source.TryConvertTo(); + if (attemptConvertInt.Success) + { + return attemptConvertInt.Result; + } - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Snapshot; + Attempt attemptConvertUdi = source.TryConvertTo(); + if (attemptConvertUdi.Success) + { + return attemptConvertUdi.Result; + } - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof(IPublishedContent); + return null; + } - public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) + public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel cacheLevel, object? source, bool preview) + { + if (source == null) { - if (source == null) - return null; - - var attemptConvertInt = source.TryConvertTo(); - if (attemptConvertInt.Success) - return attemptConvertInt.Result; - var attemptConvertUdi = source.TryConvertTo(); - if (attemptConvertUdi.Success) - return attemptConvertUdi.Result; return null; } - public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview) + IPublishedContent? member; + IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot(); + if (source is int id) { - if (source == null) + IMember? m = _memberService.GetById(id); + if (m == null) { return null; } - IPublishedContent? member; - var publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot(); - if (source is int id) + member = publishedSnapshot?.Members?.Get(m); + if (member != null) + { + return member; + } + } + else + { + var sourceUdi = source as GuidUdi; + if (sourceUdi is null) { - IMember? m = _memberService.GetById(id); - if (m == null) - { - return null; - } - member = publishedSnapshot?.Members?.Get(m); - if (member != null) - { - return member; - } + return null; } - else + + IMember? m = _memberService.GetByKey(sourceUdi.Guid); + if (m == null) { - var sourceUdi = source as GuidUdi; - if (sourceUdi is null) - return null; - - IMember? m = _memberService.GetByKey(sourceUdi.Guid); - if (m == null) - { - return null; - } - - member = publishedSnapshot?.Members?.Get(m); - - if (member != null) - { - return member; - } + return null; } - return source; + member = publishedSnapshot?.Members?.Get(m); + + if (member != null) + { + return member; + } } + + return source; } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs index faab6e712ef0..c53c5fe2ad49 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; @@ -9,163 +6,183 @@ using Umbraco.Cms.Core.Web; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters -{ +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; - /// - /// The multi node tree picker property editor value converter. - /// - [DefaultPropertyValueConverter(typeof(MustBeStringValueConverter))] - public class MultiNodeTreePickerValueConverter : PropertyValueConverterBase +/// +/// The multi node tree picker property editor value converter. +/// +[DefaultPropertyValueConverter(typeof(MustBeStringValueConverter))] +public class MultiNodeTreePickerValueConverter : PropertyValueConverterBase +{ + private static readonly List PropertiesToExclude = new() + { + Constants.Conventions.Content.InternalRedirectId.ToLower(CultureInfo.InvariantCulture), + Constants.Conventions.Content.Redirect.ToLower(CultureInfo.InvariantCulture) + }; + + private readonly IMemberService _memberService; + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + + public MultiNodeTreePickerValueConverter( + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IUmbracoContextAccessor umbracoContextAccessor, + IMemberService memberService) { - private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly IMemberService _memberService; + _publishedSnapshotAccessor = publishedSnapshotAccessor ?? + throw new ArgumentNullException(nameof(publishedSnapshotAccessor)); + _umbracoContextAccessor = umbracoContextAccessor; + _memberService = memberService; + } - private static readonly List PropertiesToExclude = new List - { - Constants.Conventions.Content.InternalRedirectId.ToLower(CultureInfo.InvariantCulture), - Constants.Conventions.Content.Redirect.ToLower(CultureInfo.InvariantCulture) - }; - - public MultiNodeTreePickerValueConverter( - IPublishedSnapshotAccessor publishedSnapshotAccessor, - IUmbracoContextAccessor umbracoContextAccessor, - IMemberService memberService) + public override bool IsConverter(IPublishedPropertyType propertyType) => + propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.MultiNodeTreePicker); + + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Snapshot; + + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => IsSingleNodePicker(propertyType) + ? typeof(IPublishedContent) + : typeof(IEnumerable); + + public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, + object? source, bool preview) + { + if (source == null) { - _publishedSnapshotAccessor = publishedSnapshotAccessor ?? throw new ArgumentNullException(nameof(publishedSnapshotAccessor)); - _umbracoContextAccessor = umbracoContextAccessor; - _memberService = memberService; + return null; } - public override bool IsConverter(IPublishedPropertyType propertyType) + if (propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.MultiNodeTreePicker)) { - return propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.MultiNodeTreePicker); + Udi[] nodeIds = source.ToString()? + .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) + .Select(UdiParser.Parse) + .ToArray(); + return nodeIds; } - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Snapshot; - - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => IsSingleNodePicker(propertyType) - ? typeof(IPublishedContent) - : typeof(IEnumerable); + return null; + } - public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) + public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel cacheLevel, object? source, bool preview) + { + if (source == null) { - if (source == null) return null; - - if (propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.MultiNodeTreePicker)) - { - var nodeIds = source.ToString()? - .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) - .Select(UdiParser.Parse) - .ToArray(); - return nodeIds; - } return null; } - public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview) + // TODO: Inject an UmbracoHelper and create a GetUmbracoHelper method based on either injected or singleton + if (_umbracoContextAccessor.TryGetUmbracoContext(out _)) { - if (source == null) + if (propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.MultiNodeTreePicker)) { - return null; - } + var udis = (Udi[])source; + var isSingleNodePicker = IsSingleNodePicker(propertyType); - // TODO: Inject an UmbracoHelper and create a GetUmbracoHelper method based on either injected or singleton - if (_umbracoContextAccessor.TryGetUmbracoContext(out _)) - { - if (propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.MultiNodeTreePicker)) + if ((propertyType.Alias != null && PropertiesToExclude.InvariantContains(propertyType.Alias)) == false) { - var udis = (Udi[])source; - var isSingleNodePicker = IsSingleNodePicker(propertyType); + var multiNodeTreePicker = new List(); - if ((propertyType.Alias != null && PropertiesToExclude.InvariantContains(propertyType.Alias)) == false) + UmbracoObjectTypes objectType = UmbracoObjectTypes.Unknown; + IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot(); + foreach (Udi udi in udis) { - var multiNodeTreePicker = new List(); - - var objectType = UmbracoObjectTypes.Unknown; - var publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot(); - foreach (var udi in udis) + var guidUdi = udi as GuidUdi; + if (guidUdi is null) { - var guidUdi = udi as GuidUdi; - if (guidUdi is null) continue; + continue; + } - IPublishedContent? multiNodeTreePickerItem = null; - switch (udi.EntityType) - { - case Constants.UdiEntityType.Document: - multiNodeTreePickerItem = GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Document, id => publishedSnapshot.Content?.GetById(guidUdi.Guid)); - break; - case Constants.UdiEntityType.Media: - multiNodeTreePickerItem = GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Media, id => publishedSnapshot.Media?.GetById(guidUdi.Guid)); - break; - case Constants.UdiEntityType.Member: - multiNodeTreePickerItem = GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Member, id => + IPublishedContent? multiNodeTreePickerItem = null; + switch (udi.EntityType) + { + case Constants.UdiEntityType.Document: + multiNodeTreePickerItem = GetPublishedContent(udi, ref objectType, + UmbracoObjectTypes.Document, + id => publishedSnapshot.Content?.GetById(guidUdi.Guid)); + break; + case Constants.UdiEntityType.Media: + multiNodeTreePickerItem = GetPublishedContent(udi, ref objectType, + UmbracoObjectTypes.Media, id => publishedSnapshot.Media?.GetById(guidUdi.Guid)); + break; + case Constants.UdiEntityType.Member: + multiNodeTreePickerItem = GetPublishedContent(udi, ref objectType, + UmbracoObjectTypes.Member, id => { IMember? m = _memberService.GetByKey(guidUdi.Guid); if (m == null) { return null; } + IPublishedContent? member = publishedSnapshot?.Members?.Get(m); return member; }); - break; - } + break; + } - if (multiNodeTreePickerItem != null && multiNodeTreePickerItem.ContentType.ItemType != PublishedItemType.Element) + if (multiNodeTreePickerItem != null && + multiNodeTreePickerItem.ContentType.ItemType != PublishedItemType.Element) + { + multiNodeTreePicker.Add(multiNodeTreePickerItem); + if (isSingleNodePicker) { - multiNodeTreePicker.Add(multiNodeTreePickerItem); - if (isSingleNodePicker) - { - break; - } + break; } } + } - if (isSingleNodePicker) - { - return multiNodeTreePicker.FirstOrDefault(); - } - return multiNodeTreePicker; + if (isSingleNodePicker) + { + return multiNodeTreePicker.FirstOrDefault(); } - // return the first nodeId as this is one of the excluded properties that expects a single id - return udis.FirstOrDefault(); + return multiNodeTreePicker; } + + // return the first nodeId as this is one of the excluded properties that expects a single id + return udis.FirstOrDefault(); } - return source; } - /// - /// Attempt to get an IPublishedContent instance based on ID and content type - /// - /// The content node ID - /// The type of content being requested - /// The type of content expected/supported by - /// A function to fetch content of type - /// The requested content, or null if either it does not exist or does not match - private IPublishedContent? GetPublishedContent(T nodeId, ref UmbracoObjectTypes actualType, UmbracoObjectTypes expectedType, Func contentFetcher) + return source; + } + + /// + /// Attempt to get an IPublishedContent instance based on ID and content type + /// + /// The content node ID + /// The type of content being requested + /// The type of content expected/supported by + /// A function to fetch content of type + /// + /// The requested content, or null if either it does not exist or does not match + /// + /// + private IPublishedContent? GetPublishedContent(T nodeId, ref UmbracoObjectTypes actualType, + UmbracoObjectTypes expectedType, Func contentFetcher) + { + // is the actual type supported by the content fetcher? + if (actualType != UmbracoObjectTypes.Unknown && actualType != expectedType) { - // is the actual type supported by the content fetcher? - if (actualType != UmbracoObjectTypes.Unknown && actualType != expectedType) - { - // no, return null - return null; - } + // no, return null + return null; + } - // attempt to get the content - var content = contentFetcher(nodeId); - if (content != null) - { - // if we found the content, assign the expected type to the actual type so we don't have to keep looking for other types of content - actualType = expectedType; - } - return content; + // attempt to get the content + IPublishedContent content = contentFetcher(nodeId); + if (content != null) + { + // if we found the content, assign the expected type to the actual type so we don't have to keep looking for other types of content + actualType = expectedType; } - private static bool IsSingleNodePicker(IPublishedPropertyType propertyType) => propertyType.DataType.ConfigurationAs()?.MaxNumber == 1; + return content; } + + private static bool IsSingleNodePicker(IPublishedPropertyType propertyType) => + propertyType.DataType.ConfigurationAs()?.MaxNumber == 1; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs index b4ce51c07753..1affb1f21d0a 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs @@ -1,81 +1,82 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml; +using System.Xml; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +[DefaultPropertyValueConverter] +public class MultipleTextStringValueConverter : PropertyValueConverterBase { - [DefaultPropertyValueConverter] - public class MultipleTextStringValueConverter : PropertyValueConverterBase - { - public override bool IsConverter(IPublishedPropertyType propertyType) - => Constants.PropertyEditors.Aliases.MultipleTextstring.Equals(propertyType.EditorAlias); + private static readonly string[] NewLineDelimiters = {"\r\n", "\r", "\n"}; - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (IEnumerable); + public override bool IsConverter(IPublishedPropertyType propertyType) + => Constants.PropertyEditors.Aliases.MultipleTextstring.Equals(propertyType.EditorAlias); - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Element; + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(IEnumerable); - private static readonly string[] NewLineDelimiters = { "\r\n", "\r", "\n" }; + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Element; + + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, + object? source, bool preview) + { + // data is (both in database and xml): + // + // + // Strong + // Flexible + // Efficient + // + // - public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) + var sourceString = source?.ToString(); + if (string.IsNullOrWhiteSpace(sourceString)) { - // data is (both in database and xml): - // - // - // Strong - // Flexible - // Efficient - // - // + return Enumerable.Empty(); + } - var sourceString = source?.ToString(); - if (string.IsNullOrWhiteSpace(sourceString)) return Enumerable.Empty(); + //SD: I have no idea why this logic is here, I'm pretty sure we've never saved the multiple txt string + // as xml in the database, it's always been new line delimited. Will ask Stephen about this. + // In the meantime, we'll do this xml check, see if it parses and if not just continue with + // splitting by newline + // + // RS: SD/Stephan Please consider post before deciding to remove + //// https://our.umbraco.com/forum/contributing-to-umbraco-cms/76989-keep-the-xml-values-in-the-multipletextstringvalueconverter + var values = new List(); + var pos = sourceString.IndexOf("", StringComparison.Ordinal); + while (pos >= 0) + { + pos += "".Length; + var npos = sourceString.IndexOf("<", pos, StringComparison.Ordinal); + var value = sourceString.Substring(pos, npos - pos); + values.Add(value); + pos = sourceString.IndexOf("", pos, StringComparison.Ordinal); + } - //SD: I have no idea why this logic is here, I'm pretty sure we've never saved the multiple txt string - // as xml in the database, it's always been new line delimited. Will ask Stephen about this. - // In the meantime, we'll do this xml check, see if it parses and if not just continue with - // splitting by newline - // - // RS: SD/Stephan Please consider post before deciding to remove - //// https://our.umbraco.com/forum/contributing-to-umbraco-cms/76989-keep-the-xml-values-in-the-multipletextstringvalueconverter - var values = new List(); - var pos = sourceString.IndexOf("", StringComparison.Ordinal); - while (pos >= 0) - { - pos += "".Length; - var npos = sourceString.IndexOf("<", pos, StringComparison.Ordinal); - var value = sourceString.Substring(pos, npos - pos); - values.Add(value); - pos = sourceString.IndexOf("", pos, StringComparison.Ordinal); - } + // fall back on normal behaviour + return values.Any() == false + ? sourceString.Split(NewLineDelimiters, StringSplitOptions.None) + : values.ToArray(); + } - // fall back on normal behaviour - return values.Any() == false - ? sourceString.Split(NewLineDelimiters, StringSplitOptions.None) - : values.ToArray(); - } + public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + { + var d = new XmlDocument(); + XmlElement e = d.CreateElement("values"); + d.AppendChild(e); - public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + var values = (IEnumerable?)inter; + if (values is not null) { - var d = new XmlDocument(); - var e = d.CreateElement("values"); - d.AppendChild(e); - - var values = (IEnumerable?) inter; - if (values is not null) + foreach (var value in values) { - foreach (var value in values) - { - var ee = d.CreateElement("value"); - ee.InnerText = value; - e.AppendChild(ee); - } + XmlElement ee = d.CreateElement("value"); + ee.InnerText = value; + e.AppendChild(ee); } - - return d.CreateNavigator(); } + + return d.CreateNavigator(); } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MustBeStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MustBeStringValueConverter.cs index d172e534c4c8..500d5fa2bfa2 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MustBeStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MustBeStringValueConverter.cs @@ -1,39 +1,35 @@ -using System; -using System.Linq; -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +/// +/// Ensures that no matter what is selected in (editor), the value results in a string. +/// +/// +/// +/// For more details see issues http://issues.umbraco.org/issue/U4-3776 (MNTP) +/// and http://issues.umbraco.org/issue/U4-4160 (media picker). +/// +/// +/// The cache level is set to .Content because the string is supposed to depend +/// on the source value only, and not on any other content. It is NOT appropriate +/// to use that converter for values whose .ToString() would depend on other content. +/// +/// +[DefaultPropertyValueConverter] +public class MustBeStringValueConverter : PropertyValueConverterBase { - /// - /// Ensures that no matter what is selected in (editor), the value results in a string. - /// - /// - /// For more details see issues http://issues.umbraco.org/issue/U4-3776 (MNTP) - /// and http://issues.umbraco.org/issue/U4-4160 (media picker). - /// The cache level is set to .Content because the string is supposed to depend - /// on the source value only, and not on any other content. It is NOT appropriate - /// to use that converter for values whose .ToString() would depend on other content. - /// - [DefaultPropertyValueConverter] - public class MustBeStringValueConverter : PropertyValueConverterBase - { - private static readonly string[] Aliases = - { - Constants.PropertyEditors.Aliases.MultiNodeTreePicker - }; + private static readonly string[] Aliases = {Constants.PropertyEditors.Aliases.MultiNodeTreePicker}; - public override bool IsConverter(IPublishedPropertyType propertyType) - => Aliases.Contains(propertyType.EditorAlias); + public override bool IsConverter(IPublishedPropertyType propertyType) + => Aliases.Contains(propertyType.EditorAlias); - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (string); + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(string); - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Element; + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Element; - public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) - { - return source?.ToString(); - } - } + public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, + object? source, bool preview) => source?.ToString(); } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs index 162764fbf5cb..836a9fe574c1 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs @@ -1,29 +1,30 @@ -using System; -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters -{ - [DefaultPropertyValueConverter] - public class RadioButtonListValueConverter : PropertyValueConverterBase - { - public override bool IsConverter(IPublishedPropertyType propertyType) - => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.RadioButtonList); +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (string); +[DefaultPropertyValueConverter] +public class RadioButtonListValueConverter : PropertyValueConverterBase +{ + public override bool IsConverter(IPublishedPropertyType propertyType) + => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.RadioButtonList); - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Element; + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(string); - public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) - { - var attempt = source.TryConvertTo(); + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Element; - if (attempt.Success) - return attempt.Result; + public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, + object? source, bool preview) + { + Attempt attempt = source.TryConvertTo(); - return null; + if (attempt.Success) + { + return attempt.Result; } + + return null; } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs index 1ad867bfd020..c126f29b90b8 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs @@ -1,43 +1,38 @@ -using System; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Strings; -namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +/// +/// Value converter for the RTE so that it always returns IHtmlString so that Html.Raw doesn't have to be used. +/// +[DefaultPropertyValueConverter] +public class SimpleTinyMceValueConverter : PropertyValueConverterBase { - /// - /// Value converter for the RTE so that it always returns IHtmlString so that Html.Raw doesn't have to be used. - /// - [DefaultPropertyValueConverter] - public class SimpleTinyMceValueConverter : PropertyValueConverterBase - { - public override bool IsConverter(IPublishedPropertyType propertyType) - => propertyType.EditorAlias == Constants.PropertyEditors.Aliases.TinyMce; + public override bool IsConverter(IPublishedPropertyType propertyType) + => propertyType.EditorAlias == Constants.PropertyEditors.Aliases.TinyMce; - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof(IHtmlEncodedString); + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(IHtmlEncodedString); - // PropertyCacheLevel.Content is ok here because that converter does not parse {locallink} nor executes macros - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Element; + // PropertyCacheLevel.Content is ok here because that converter does not parse {locallink} nor executes macros + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Element; - public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) - { - // in xml a string is: string - // in the database a string is: string - // default value is: null - return source; - } + public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, + object? source, bool preview) => + // in xml a string is: string + // in the database a string is: string + // default value is: null + source; - public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) - { - // source should come from ConvertSource and be a string (or null) already - return new HtmlEncodedString(inter == null ? string.Empty : (string)inter); - } + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => + // source should come from ConvertSource and be a string (or null) already + new HtmlEncodedString(inter == null ? string.Empty : (string)inter); - public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) - { - // source should come from ConvertSource and be a string (or null) already - return inter; - } - } + public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => + // source should come from ConvertSource and be a string (or null) already + inter; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs index 1da3458dabd5..5364ec4d992a 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs @@ -1,86 +1,79 @@ -using System; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +[DefaultPropertyValueConverter] +public class SliderValueConverter : PropertyValueConverterBase { - [DefaultPropertyValueConverter] - public class SliderValueConverter : PropertyValueConverterBase - { - private readonly IDataTypeService _dataTypeService; + private static readonly ConcurrentDictionary Storages = new(); + private readonly IDataTypeService _dataTypeService; - public SliderValueConverter(IDataTypeService dataTypeService) - { - _dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService)); - } + public SliderValueConverter(IDataTypeService dataTypeService) => _dataTypeService = + dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService)); - public override bool IsConverter(IPublishedPropertyType propertyType) - => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.Slider); + public override bool IsConverter(IPublishedPropertyType propertyType) + => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.Slider); - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => IsRangeDataType(propertyType.DataType.Id) ? typeof (Range) : typeof (decimal); + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => IsRangeDataType(propertyType.DataType.Id) ? typeof(Range) : typeof(decimal); - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Element; + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Element; - public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview) + public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel cacheLevel, object? source, bool preview) + { + if (source == null) { - if (source == null) - return null; - - if (IsRangeDataType(propertyType.DataType.Id)) - { - var rangeRawValues = source.ToString()!.Split(Constants.CharArrays.Comma); - var minimumAttempt = rangeRawValues[0].TryConvertTo(); - var maximumAttempt = rangeRawValues[1].TryConvertTo(); - - if ((minimumAttempt.Success) && (maximumAttempt.Success)) - { - return new Range { Maximum = maximumAttempt.Result, Minimum = minimumAttempt.Result }; - } - } - - var valueAttempt = source.ToString().TryConvertTo(); - if (valueAttempt.Success) - return valueAttempt.Result; - - // Something failed in the conversion of the strings to decimals return null; - } - /// - /// Discovers if the slider is set to range mode. - /// - /// - /// The data type id. - /// - /// - /// The . - /// - private bool IsRangeDataType(int dataTypeId) + if (IsRangeDataType(propertyType.DataType.Id)) { - // GetPreValuesCollectionByDataTypeId is cached at repository level; - // still, the collection is deep-cloned so this is kinda expensive, - // better to cache here + trigger refresh in DataTypeCacheRefresher - // TODO: this is cheap now, remove the caching + var rangeRawValues = source.ToString()!.Split(Constants.CharArrays.Comma); + Attempt minimumAttempt = rangeRawValues[0].TryConvertTo(); + Attempt maximumAttempt = rangeRawValues[1].TryConvertTo(); - return Storages.GetOrAdd(dataTypeId, id => + if (minimumAttempt.Success && maximumAttempt.Success) { - var dataType = _dataTypeService.GetDataType(id); - var configuration = dataType?.ConfigurationAs(); - return configuration?.EnableRange ?? false; - }); + return new Range {Maximum = maximumAttempt.Result, Minimum = minimumAttempt.Result}; + } } - private static readonly ConcurrentDictionary Storages = new ConcurrentDictionary(); - - public static void ClearCaches() + Attempt valueAttempt = source.ToString().TryConvertTo(); + if (valueAttempt.Success) { - Storages.Clear(); + return valueAttempt.Result; } + + // Something failed in the conversion of the strings to decimals + return null; } + + /// + /// Discovers if the slider is set to range mode. + /// + /// + /// The data type id. + /// + /// + /// The . + /// + private bool IsRangeDataType(int dataTypeId) => + // GetPreValuesCollectionByDataTypeId is cached at repository level; + // still, the collection is deep-cloned so this is kinda expensive, + // better to cache here + trigger refresh in DataTypeCacheRefresher + // TODO: this is cheap now, remove the caching + Storages.GetOrAdd(dataTypeId, id => + { + IDataType dataType = _dataTypeService.GetDataType(id); + SliderConfiguration configuration = dataType?.ConfigurationAs(); + return configuration?.EnableRange ?? false; + }); + + public static void ClearCaches() => Storages.Clear(); } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs index da5dfd5416a0..c9c6c416528f 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs @@ -1,82 +1,76 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; +using System.Collections.Concurrent; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +[DefaultPropertyValueConverter] +public class TagsValueConverter : PropertyValueConverterBase { - [DefaultPropertyValueConverter] - public class TagsValueConverter : PropertyValueConverterBase - { - private readonly IDataTypeService _dataTypeService; - private readonly IJsonSerializer _jsonSerializer; + private static readonly ConcurrentDictionary Storages = new(); + private readonly IDataTypeService _dataTypeService; + private readonly IJsonSerializer _jsonSerializer; - public TagsValueConverter(IDataTypeService dataTypeService, IJsonSerializer jsonSerializer) - { - _dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService)); - _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer)); - } + public TagsValueConverter(IDataTypeService dataTypeService, IJsonSerializer jsonSerializer) + { + _dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService)); + _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer)); + } - public override bool IsConverter(IPublishedPropertyType propertyType) - => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.Tags); + public override bool IsConverter(IPublishedPropertyType propertyType) + => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.Tags); - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (IEnumerable); + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(IEnumerable); - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Element; + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Element; - public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) + public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, + object? source, bool preview) + { + if (source == null) { - if (source == null) return Array.Empty(); - - // if Json storage type deserialize and return as string array - if (JsonStorageType(propertyType.DataType.Id)) - { - var array = source.ToString() is not null ? _jsonSerializer.Deserialize(source.ToString()!) : null; - return array ?? Array.Empty(); - } - - // Otherwise assume CSV storage type and return as string array - return source.ToString()?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); + return Array.Empty(); } - public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview) + // if Json storage type deserialize and return as string array + if (JsonStorageType(propertyType.DataType.Id)) { - return (string[]?) source; + var array = source.ToString() is not null + ? _jsonSerializer.Deserialize(source.ToString()!) + : null; + return array ?? Array.Empty(); } - /// - /// Discovers if the tags data type is storing its data in a Json format - /// - /// - /// The data type id. - /// - /// - /// The . - /// - private bool JsonStorageType(int dataTypeId) - { - // GetDataType(id) is cached at repository level; still, there is some - // deep-cloning involved (expensive) - better cache here + trigger - // refresh in DataTypeCacheRefresher - - return Storages.GetOrAdd(dataTypeId, id => - { - var configuration = _dataTypeService.GetDataType(id)?.ConfigurationAs(); - return configuration?.StorageType == TagsStorageType.Json; - }); - } + // Otherwise assume CSV storage type and return as string array + return source.ToString()?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); + } - private static readonly ConcurrentDictionary Storages = new ConcurrentDictionary(); + public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel cacheLevel, object? source, bool preview) => (string[]?)source; - public static void ClearCaches() + /// + /// Discovers if the tags data type is storing its data in a Json format + /// + /// + /// The data type id. + /// + /// + /// The . + /// + private bool JsonStorageType(int dataTypeId) => + // GetDataType(id) is cached at repository level; still, there is some + // deep-cloning involved (expensive) - better cache here + trigger + // refresh in DataTypeCacheRefresher + Storages.GetOrAdd(dataTypeId, id => { - Storages.Clear(); - } - } + TagConfiguration configuration = _dataTypeService.GetDataType(id)?.ConfigurationAs(); + return configuration?.StorageType == TagsStorageType.Json; + }); + + public static void ClearCaches() => Storages.Clear(); } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/UploadPropertyConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/UploadPropertyConverter.cs index a554e7d13417..67b538330b64 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/UploadPropertyConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/UploadPropertyConverter.cs @@ -1,26 +1,22 @@ -using System; -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +/// +/// The upload property value converter. +/// +[DefaultPropertyValueConverter] +public class UploadPropertyConverter : PropertyValueConverterBase { - /// - /// The upload property value converter. - /// - [DefaultPropertyValueConverter] - public class UploadPropertyConverter : PropertyValueConverterBase - { - public override bool IsConverter(IPublishedPropertyType propertyType) - => propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.UploadField); + public override bool IsConverter(IPublishedPropertyType propertyType) + => propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.UploadField); - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (string); + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(string); - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Element; + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Element; - public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview) - { - return source?.ToString() ?? ""; - } - } + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel cacheLevel, object? source, bool preview) => source?.ToString() ?? ""; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs index 6534ce3f14f9..e16ece995b27 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs @@ -1,58 +1,66 @@ -using System; -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters +namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; + +[DefaultPropertyValueConverter] +public class YesNoValueConverter : PropertyValueConverterBase { - [DefaultPropertyValueConverter] - public class YesNoValueConverter : PropertyValueConverterBase - { - public override bool IsConverter(IPublishedPropertyType propertyType) - => propertyType.EditorAlias == Constants.PropertyEditors.Aliases.Boolean; + public override bool IsConverter(IPublishedPropertyType propertyType) + => propertyType.EditorAlias == Constants.PropertyEditors.Aliases.Boolean; - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof (bool); + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(bool); - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Element; + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) - { - // in xml a boolean is: string - // in the database a boolean is: string "1" or "0" or empty - // typically the converter does not need to handle anything else ("true"...) - // however there are cases where the value passed to the converter could be a non-string object, e.g. int, bool + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, + object? source, bool preview) + { + // in xml a boolean is: string + // in the database a boolean is: string "1" or "0" or empty + // typically the converter does not need to handle anything else ("true"...) + // however there are cases where the value passed to the converter could be a non-string object, e.g. int, bool - if (source is string s) + if (source is string s) + { + if (s.Length == 0 || s == "0") { - if (s.Length == 0 || s == "0") - return false; - - if (s == "1") - return true; - - return bool.TryParse(s, out bool result) && result; + return false; } - if (source is int) - return (int)source == 1; - - // this is required for correct true/false handling in nested content elements - if (source is long) - return (long)source == 1; + if (s == "1") + { + return true; + } - if (source is bool) - return (bool)source; + return bool.TryParse(s, out var result) && result; + } - // default value is: false - return false; + if (source is int) + { + return (int)source == 1; } - // default ConvertSourceToObject just returns source ie a boolean value + // this is required for correct true/false handling in nested content elements + if (source is long) + { + return (long)source == 1; + } - public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + if (source is bool) { - // source should come from ConvertSource and be a boolean already - return (bool?)inter ?? false ? "1" : "0"; + return (bool)source; } + + // default value is: false + return false; } + + // default ConvertSourceToObject just returns source ie a boolean value + + public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, + PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => + // source should come from ConvertSource and be a boolean already + (bool?)inter ?? false ? "1" : "0"; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueListConfiguration.cs b/src/Umbraco.Core/PropertyEditors/ValueListConfiguration.cs index 61b8a02f0e23..121c111e2392 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueListConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueListConfiguration.cs @@ -1,24 +1,20 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the ValueList editor configuration. +/// +public class ValueListConfiguration { - /// - /// Represents the ValueList editor configuration. - /// - public class ValueListConfiguration - { - [ConfigurationField("items", "Configure", "multivalues", Description = "Add, remove or sort values for the list.")] - public List Items { get; set; } = new List(); + [ConfigurationField("items", "Configure", "multivalues", Description = "Add, remove or sort values for the list.")] + public List Items { get; set; } = new(); - [DataContract] - public class ValueListItem - { - [DataMember(Name = "id")] - public int Id { get; set; } + [DataContract] + public class ValueListItem + { + [DataMember(Name = "id")] public int Id { get; set; } - [DataMember(Name = "value")] - public string? Value { get; set; } - } + [DataMember(Name = "value")] public string? Value { get; set; } } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueTypes.cs b/src/Umbraco.Core/PropertyEditors/ValueTypes.cs index 3a99a70a1418..07375e875c21 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueTypes.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueTypes.cs @@ -1,113 +1,112 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; +using System.Reflection; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents the types of the edited values. +/// +/// +/// +/// These types are used to determine the storage type, but also for +/// validation. Therefore, they are more detailed than the storage types. +/// +/// +public static class ValueTypes { /// - /// Represents the types of the edited values. + /// Date value. /// - /// - /// These types are used to determine the storage type, but also for - /// validation. Therefore, they are more detailed than the storage types. - /// - public static class ValueTypes + public const string Date = "DATE"; // Date + + /// + /// DateTime value. + /// + public const string DateTime = "DATETIME"; // Date + + /// + /// Decimal value. + /// + public const string Decimal = "DECIMAL"; // Decimal + + /// + /// Integer value. + /// + public const string Integer = "INT"; // Integer + + /// + /// Integer value. + /// + public const string Bigint = "BIGINT"; // String + + /// + /// Json value. + /// + public const string Json = "JSON"; // NText + + /// + /// Text value (maps to text database type). + /// + public const string Text = "TEXT"; // NText + + /// + /// Time value. + /// + public const string Time = "TIME"; // Date + + /// + /// Text value (maps to varchar database type). + /// + public const string String = "STRING"; // NVarchar + + /// + /// Xml value. + /// + public const string Xml = "XML"; // NText + + // the auto, static, set of valid values + private static readonly HashSet Values + = new(typeof(ValueTypes) + .GetFields(BindingFlags.Static | BindingFlags.Public) + .Where(x => x.IsLiteral && !x.IsInitOnly) + .Select(x => (string?)x.GetRawConstantValue())); + + /// + /// Determines whether a string value is a valid ValueTypes value. + /// + public static bool IsValue(string s) + => Values.Contains(s); + + /// + /// Gets the value corresponding to a ValueTypes value. + /// + public static ValueStorageType ToStorageType(string valueType) { - // the auto, static, set of valid values - private static readonly HashSet Values - = new HashSet(typeof(ValueTypes) - .GetFields(BindingFlags.Static | BindingFlags.Public) - .Where(x => x.IsLiteral && !x.IsInitOnly) - .Select(x => (string?) x.GetRawConstantValue())); - - /// - /// Date value. - /// - public const string Date = "DATE"; // Date - - /// - /// DateTime value. - /// - public const string DateTime = "DATETIME"; // Date - - /// - /// Decimal value. - /// - public const string Decimal = "DECIMAL"; // Decimal - - /// - /// Integer value. - /// - public const string Integer = "INT"; // Integer - - /// - /// Integer value. - /// - public const string Bigint = "BIGINT"; // String - - /// - /// Json value. - /// - public const string Json = "JSON"; // NText - - /// - /// Text value (maps to text database type). - /// - public const string Text = "TEXT"; // NText - - /// - /// Time value. - /// - public const string Time = "TIME"; // Date - - /// - /// Text value (maps to varchar database type). - /// - public const string String = "STRING"; // NVarchar - - /// - /// Xml value. - /// - public const string Xml = "XML"; // NText - - /// - /// Determines whether a string value is a valid ValueTypes value. - /// - public static bool IsValue(string s) - => Values.Contains(s); - - /// - /// Gets the value corresponding to a ValueTypes value. - /// - public static ValueStorageType ToStorageType(string valueType) + switch (valueType.ToUpperInvariant()) { - switch (valueType.ToUpperInvariant()) - { - case Integer: - return ValueStorageType.Integer; - - case Decimal: - return ValueStorageType.Decimal; - - case String: - case Bigint: - return ValueStorageType.Nvarchar; - - case Text: - case Json: - case Xml: - return ValueStorageType.Ntext; - - case DateTime: - case Date: - case Time: - return ValueStorageType.Date; - - default: - throw new ArgumentOutOfRangeException(nameof(valueType), $"Value \"{valueType}\" is not a valid ValueTypes."); - } + case Integer: + return ValueStorageType.Integer; + + case Decimal: + return ValueStorageType.Decimal; + + case String: + case Bigint: + return ValueStorageType.Nvarchar; + + case Text: + case Json: + case Xml: + return ValueStorageType.Ntext; + + case DateTime: + case Date: + case Time: + return ValueStorageType.Date; + + default: + throw new ArgumentOutOfRangeException(nameof(valueType), + $"Value \"{valueType}\" is not a valid ValueTypes."); } } } diff --git a/src/Umbraco.Core/PropertyEditors/VoidEditor.cs b/src/Umbraco.Core/PropertyEditors/VoidEditor.cs index 28a0afb6ce63..f272dc49bd7c 100644 --- a/src/Umbraco.Core/PropertyEditors/VoidEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/VoidEditor.cs @@ -1,46 +1,49 @@ -using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings; -namespace Umbraco.Cms.Core.PropertyEditors +namespace Umbraco.Cms.Core.PropertyEditors; + +/// +/// Represents a void editor. +/// +/// +/// Can be used in some places where an editor is needed but no actual +/// editor is available. Not to be used otherwise. Not discovered, and therefore +/// not part of the editors collection. +/// +[HideFromTypeFinder] +public class VoidEditor : DataEditor { /// - /// Represents a void editor. + /// Initializes a new instance of the class. /// - /// Can be used in some places where an editor is needed but no actual - /// editor is available. Not to be used otherwise. Not discovered, and therefore - /// not part of the editors collection. - [HideFromTypeFinder] - public class VoidEditor : DataEditor + /// An optional alias suffix. + /// A logger factory. + /// + /// The default alias of the editor is "Umbraco.Void". When a suffix is provided, + /// it is appended to the alias. Eg if the suffix is "Foo" the alias is "Umbraco.Void.Foo". + /// + public VoidEditor( + string? aliasSuffix, + IDataValueEditorFactory dataValueEditorFactory) + : base(dataValueEditorFactory) { - /// - /// Initializes a new instance of the class. - /// - /// An optional alias suffix. - /// A logger factory. - /// The default alias of the editor is "Umbraco.Void". When a suffix is provided, - /// it is appended to the alias. Eg if the suffix is "Foo" the alias is "Umbraco.Void.Foo". - public VoidEditor( - string? aliasSuffix, - IDataValueEditorFactory dataValueEditorFactory) - : base(dataValueEditorFactory) + Alias = "Umbraco.Void"; + if (string.IsNullOrWhiteSpace(aliasSuffix)) { - Alias = "Umbraco.Void"; - if (string.IsNullOrWhiteSpace(aliasSuffix)) return; - Alias += "." + aliasSuffix; + return; } - /// - /// Initializes a new instance of the class. - /// - /// A logger factory. - /// The alias of the editor is "Umbraco.Void". - public VoidEditor( - IDataValueEditorFactory dataValueEditorFactory) - : this(null, dataValueEditorFactory) - { } + Alias += "." + aliasSuffix; + } + + /// + /// Initializes a new instance of the class. + /// + /// A logger factory. + /// The alias of the editor is "Umbraco.Void". + public VoidEditor( + IDataValueEditorFactory dataValueEditorFactory) + : this(null, dataValueEditorFactory) + { } } diff --git a/src/Umbraco.Core/PublishedCache/DefaultCultureAccessor.cs b/src/Umbraco.Core/PublishedCache/DefaultCultureAccessor.cs index 648041a3a4df..250190db9591 100644 --- a/src/Umbraco.Core/PublishedCache/DefaultCultureAccessor.cs +++ b/src/Umbraco.Core/PublishedCache/DefaultCultureAccessor.cs @@ -2,32 +2,32 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.PublishedCache +namespace Umbraco.Cms.Core.PublishedCache; + +/// +/// Provides the default implementation of . +/// +public class DefaultCultureAccessor : IDefaultCultureAccessor { + private readonly ILocalizationService _localizationService; + private readonly IRuntimeState _runtimeState; + private GlobalSettings _options; + + /// - /// Provides the default implementation of . + /// Initializes a new instance of the class. /// - public class DefaultCultureAccessor : IDefaultCultureAccessor + public DefaultCultureAccessor(ILocalizationService localizationService, IRuntimeState runtimeState, + IOptionsMonitor options) { - private readonly ILocalizationService _localizationService; - private readonly IRuntimeState _runtimeState; - private GlobalSettings _options; - - - /// - /// Initializes a new instance of the class. - /// - public DefaultCultureAccessor(ILocalizationService localizationService, IRuntimeState runtimeState, IOptionsMonitor options) - { - _localizationService = localizationService; - _runtimeState = runtimeState; - _options = options.CurrentValue; - options.OnChange(x => _options = x); - } - - /// - public string DefaultCulture => _runtimeState.Level == RuntimeLevel.Run - ? _localizationService.GetDefaultLanguageIsoCode() ?? "" // fast - : _options.DefaultUILanguage; // default for install and upgrade, when the service is n/a + _localizationService = localizationService; + _runtimeState = runtimeState; + _options = options.CurrentValue; + options.OnChange(x => _options = x); } + + /// + public string DefaultCulture => _runtimeState.Level == RuntimeLevel.Run + ? _localizationService.GetDefaultLanguageIsoCode() ?? "" // fast + : _options.DefaultUILanguage; // default for install and upgrade, when the service is n/a } diff --git a/src/Umbraco.Core/PublishedCache/IDefaultCultureAccessor.cs b/src/Umbraco.Core/PublishedCache/IDefaultCultureAccessor.cs index 58844562a7f7..eb529ac2e82e 100644 --- a/src/Umbraco.Core/PublishedCache/IDefaultCultureAccessor.cs +++ b/src/Umbraco.Core/PublishedCache/IDefaultCultureAccessor.cs @@ -1,16 +1,15 @@ -namespace Umbraco.Cms.Core.PublishedCache +namespace Umbraco.Cms.Core.PublishedCache; + +/// +/// Gives access to the default culture. +/// +public interface IDefaultCultureAccessor { /// - /// Gives access to the default culture. + /// Gets the system default culture. /// - public interface IDefaultCultureAccessor - { - /// - /// Gets the system default culture. - /// - /// - /// Implementations must NOT return a null value. Return an empty string for the invariant culture. - /// - string DefaultCulture { get; } - } + /// + /// Implementations must NOT return a null value. Return an empty string for the invariant culture. + /// + string DefaultCulture { get; } } diff --git a/src/Umbraco.Core/PublishedCache/IDomainCache.cs b/src/Umbraco.Core/PublishedCache/IDomainCache.cs index 0555960dfa7e..6fdb0be995c2 100644 --- a/src/Umbraco.Core/PublishedCache/IDomainCache.cs +++ b/src/Umbraco.Core/PublishedCache/IDomainCache.cs @@ -1,34 +1,33 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Routing; -namespace Umbraco.Cms.Core.PublishedCache +namespace Umbraco.Cms.Core.PublishedCache; + +public interface IDomainCache { - public interface IDomainCache - { - /// - /// Gets all in the current domain cache, including any domains that may be referenced by documents that are no longer published. - /// - /// - /// - IEnumerable GetAll(bool includeWildcards); + /// + /// Gets the system default culture. + /// + string DefaultCulture { get; } - /// - /// Gets all assigned for specified document, even if it is not published. - /// - /// The document identifier. - /// A value indicating whether to consider wildcard domains. - IEnumerable GetAssigned(int documentId, bool includeWildcards = false); + /// + /// Gets all in the current domain cache, including any domains that may be referenced by + /// documents that are no longer published. + /// + /// + /// + IEnumerable GetAll(bool includeWildcards); - /// - /// Determines whether a document has domains. - /// - /// The document identifier. - /// A value indicating whether to consider wildcard domains. - bool HasAssigned(int documentId, bool includeWildcards = false); + /// + /// Gets all assigned for specified document, even if it is not published. + /// + /// The document identifier. + /// A value indicating whether to consider wildcard domains. + IEnumerable GetAssigned(int documentId, bool includeWildcards = false); - /// - /// Gets the system default culture. - /// - string DefaultCulture { get; } - } + /// + /// Determines whether a document has domains. + /// + /// The document identifier. + /// A value indicating whether to consider wildcard domains. + bool HasAssigned(int documentId, bool includeWildcards = false); } diff --git a/src/Umbraco.Core/PublishedCache/IPublishedCache.cs b/src/Umbraco.Core/PublishedCache/IPublishedCache.cs index 5a06d88ee59a..673760908020 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedCache.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedCache.cs @@ -1,245 +1,244 @@ -using System; -using System.Collections.Generic; -using System.Xml.XPath; +using System.Xml.XPath; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Xml; -namespace Umbraco.Cms.Core.PublishedCache +namespace Umbraco.Cms.Core.PublishedCache; + +/// +/// Provides access to cached contents. +/// +public interface IPublishedCache : IXPathNavigable { /// - /// Provides access to cached contents. - /// - public interface IPublishedCache : IXPathNavigable - { - /// - /// Gets a content identified by its unique identifier. - /// - /// A value indicating whether to consider unpublished content. - /// The content unique identifier. - /// The content, or null. - /// The value of overrides defaults. - IPublishedContent? GetById(bool preview, int contentId); - - /// - /// Gets a content identified by its unique identifier. - /// - /// A value indicating whether to consider unpublished content. - /// The content unique identifier. - /// The content, or null. - /// The value of overrides defaults. - IPublishedContent? GetById(bool preview, Guid contentId); - - /// - /// Gets a content identified by its Udi identifier. - /// - /// A value indicating whether to consider unpublished content. - /// The content Udi identifier. - /// The content, or null. - /// The value of overrides defaults. - IPublishedContent? GetById(bool preview, Udi contentId); - - /// - /// Gets a content identified by its unique identifier. - /// - /// The content unique identifier. - /// The content, or null. - /// Considers published or unpublished content depending on defaults. - IPublishedContent? GetById(int contentId); - - /// - /// Gets a content identified by its unique identifier. - /// - /// The content unique identifier. - /// The content, or null. - /// Considers published or unpublished content depending on defaults. - IPublishedContent? GetById(Guid contentId); - - /// - /// Gets a content identified by its unique identifier. - /// - /// The content unique identifier. - /// The content, or null. - /// Considers published or unpublished content depending on defaults. - IPublishedContent? GetById(Udi contentId); - - /// - /// Gets a value indicating whether the cache contains a specified content. - /// - /// A value indicating whether to consider unpublished content. - /// The content unique identifier. - /// A value indicating whether to the cache contains the specified content. - /// The value of overrides defaults. - bool HasById(bool preview, int contentId); - - /// - /// Gets a value indicating whether the cache contains a specified content. - /// - /// The content unique identifier. - /// A value indicating whether to the cache contains the specified content. - /// Considers published or unpublished content depending on defaults. - bool HasById(int contentId); - - /// - /// Gets contents at root. - /// - /// A value indicating whether to consider unpublished content. - /// A culture. - /// The contents. - /// The value of overrides defaults. - IEnumerable GetAtRoot(bool preview, string? culture = null); - - /// - /// Gets contents at root. - /// - /// A culture. - /// The contents. - /// Considers published or unpublished content depending on defaults. - IEnumerable GetAtRoot(string? culture = null); - - /// - /// Gets a content resulting from an XPath query. - /// - /// A value indicating whether to consider unpublished content. - /// The XPath query. - /// Optional XPath variables. - /// The content, or null. - /// The value of overrides defaults. - IPublishedContent? GetSingleByXPath(bool preview, string xpath, params XPathVariable[] vars); - - /// - /// Gets a content resulting from an XPath query. - /// - /// The XPath query. - /// Optional XPath variables. - /// The content, or null. - /// Considers published or unpublished content depending on defaults. - IPublishedContent? GetSingleByXPath(string xpath, params XPathVariable[] vars); - - /// - /// Gets a content resulting from an XPath query. - /// - /// A value indicating whether to consider unpublished content. - /// The XPath query. - /// Optional XPath variables. - /// The content, or null. - /// The value of overrides defaults. - IPublishedContent? GetSingleByXPath(bool preview, XPathExpression xpath, params XPathVariable[] vars); - - /// - /// Gets a content resulting from an XPath query. - /// - /// The XPath query. - /// Optional XPath variables. - /// The content, or null. - /// Considers published or unpublished content depending on defaults. - IPublishedContent? GetSingleByXPath(XPathExpression xpath, params XPathVariable[] vars); - - /// - /// Gets contents resulting from an XPath query. - /// - /// A value indicating whether to consider unpublished content. - /// The XPath query. - /// Optional XPath variables. - /// The contents. - /// The value of overrides defaults. - IEnumerable GetByXPath(bool preview, string xpath, params XPathVariable[] vars); - - /// - /// Gets contents resulting from an XPath query. - /// - /// The XPath query. - /// Optional XPath variables. - /// The contents. - /// Considers published or unpublished content depending on defaults. - IEnumerable GetByXPath(string xpath, params XPathVariable[] vars); - - /// - /// Gets contents resulting from an XPath query. - /// - /// A value indicating whether to consider unpublished content. - /// The XPath query. - /// Optional XPath variables. - /// The contents. - /// The value of overrides defaults. - IEnumerable GetByXPath(bool preview, XPathExpression xpath, params XPathVariable[] vars); - - /// - /// Gets contents resulting from an XPath query. - /// - /// The XPath query. - /// Optional XPath variables. - /// The contents. - /// Considers published or unpublished content depending on defaults. - IEnumerable GetByXPath(XPathExpression xpath, params XPathVariable[] vars); - - /// - /// Creates an XPath navigator that can be used to navigate contents. - /// - /// A value indicating whether to consider unpublished content. - /// The XPath navigator. - /// - /// The value of overrides the context. - /// The navigator is already a safe clone (no need to clone it again). - /// - XPathNavigator CreateNavigator(bool preview); - - /// - /// Creates an XPath navigator that can be used to navigate one node. - /// - /// The node identifier. - /// A value indicating whether to consider unpublished content. - /// The XPath navigator, or null. - /// - /// The value of overrides the context. - /// The navigator is already a safe clone (no need to clone it again). - /// Navigates over the node - and only the node, ie no children. Exists only for backward - /// compatibility + transition reasons, we should obsolete that one as soon as possible. - /// If the node does not exist, returns null. - /// - XPathNavigator? CreateNodeNavigator(int id, bool preview); - - /// - /// Gets a value indicating whether the cache contains published content. - /// - /// A value indicating whether to consider unpublished content. - /// A value indicating whether the cache contains published content. - /// The value of overrides defaults. - bool HasContent(bool preview); - - /// - /// Gets a value indicating whether the cache contains published content. - /// - /// A value indicating whether the cache contains published content. - /// Considers published or unpublished content depending on defaults. - bool HasContent(); - - /// - /// Gets a content type identified by its unique identifier. - /// - /// The content type unique identifier. - /// The content type, or null. - IPublishedContentType? GetContentType(int id); - - /// - /// Gets a content type identified by its alias. - /// - /// The content type alias. - /// The content type, or null. - /// The alias is case-insensitive. - IPublishedContentType? GetContentType(string alias); - - /// - /// Gets contents of a given content type. - /// - /// The content type. - /// The contents. - IEnumerable GetByContentType(IPublishedContentType contentType); - - /// - /// Gets a content type identified by its alias. - /// - /// The content type key. - /// The content type, or null. - IPublishedContentType? GetContentType(Guid key); - } + /// Gets a content identified by its unique identifier. + /// + /// A value indicating whether to consider unpublished content. + /// The content unique identifier. + /// The content, or null. + /// The value of overrides defaults. + IPublishedContent? GetById(bool preview, int contentId); + + /// + /// Gets a content identified by its unique identifier. + /// + /// A value indicating whether to consider unpublished content. + /// The content unique identifier. + /// The content, or null. + /// The value of overrides defaults. + IPublishedContent? GetById(bool preview, Guid contentId); + + /// + /// Gets a content identified by its Udi identifier. + /// + /// A value indicating whether to consider unpublished content. + /// The content Udi identifier. + /// The content, or null. + /// The value of overrides defaults. + IPublishedContent? GetById(bool preview, Udi contentId); + + /// + /// Gets a content identified by its unique identifier. + /// + /// The content unique identifier. + /// The content, or null. + /// Considers published or unpublished content depending on defaults. + IPublishedContent? GetById(int contentId); + + /// + /// Gets a content identified by its unique identifier. + /// + /// The content unique identifier. + /// The content, or null. + /// Considers published or unpublished content depending on defaults. + IPublishedContent? GetById(Guid contentId); + + /// + /// Gets a content identified by its unique identifier. + /// + /// The content unique identifier. + /// The content, or null. + /// Considers published or unpublished content depending on defaults. + IPublishedContent? GetById(Udi contentId); + + /// + /// Gets a value indicating whether the cache contains a specified content. + /// + /// A value indicating whether to consider unpublished content. + /// The content unique identifier. + /// A value indicating whether to the cache contains the specified content. + /// The value of overrides defaults. + bool HasById(bool preview, int contentId); + + /// + /// Gets a value indicating whether the cache contains a specified content. + /// + /// The content unique identifier. + /// A value indicating whether to the cache contains the specified content. + /// Considers published or unpublished content depending on defaults. + bool HasById(int contentId); + + /// + /// Gets contents at root. + /// + /// A value indicating whether to consider unpublished content. + /// A culture. + /// The contents. + /// The value of overrides defaults. + IEnumerable GetAtRoot(bool preview, string? culture = null); + + /// + /// Gets contents at root. + /// + /// A culture. + /// The contents. + /// Considers published or unpublished content depending on defaults. + IEnumerable GetAtRoot(string? culture = null); + + /// + /// Gets a content resulting from an XPath query. + /// + /// A value indicating whether to consider unpublished content. + /// The XPath query. + /// Optional XPath variables. + /// The content, or null. + /// The value of overrides defaults. + IPublishedContent? GetSingleByXPath(bool preview, string xpath, params XPathVariable[] vars); + + /// + /// Gets a content resulting from an XPath query. + /// + /// The XPath query. + /// Optional XPath variables. + /// The content, or null. + /// Considers published or unpublished content depending on defaults. + IPublishedContent? GetSingleByXPath(string xpath, params XPathVariable[] vars); + + /// + /// Gets a content resulting from an XPath query. + /// + /// A value indicating whether to consider unpublished content. + /// The XPath query. + /// Optional XPath variables. + /// The content, or null. + /// The value of overrides defaults. + IPublishedContent? GetSingleByXPath(bool preview, XPathExpression xpath, params XPathVariable[] vars); + + /// + /// Gets a content resulting from an XPath query. + /// + /// The XPath query. + /// Optional XPath variables. + /// The content, or null. + /// Considers published or unpublished content depending on defaults. + IPublishedContent? GetSingleByXPath(XPathExpression xpath, params XPathVariable[] vars); + + /// + /// Gets contents resulting from an XPath query. + /// + /// A value indicating whether to consider unpublished content. + /// The XPath query. + /// Optional XPath variables. + /// The contents. + /// The value of overrides defaults. + IEnumerable GetByXPath(bool preview, string xpath, params XPathVariable[] vars); + + /// + /// Gets contents resulting from an XPath query. + /// + /// The XPath query. + /// Optional XPath variables. + /// The contents. + /// Considers published or unpublished content depending on defaults. + IEnumerable GetByXPath(string xpath, params XPathVariable[] vars); + + /// + /// Gets contents resulting from an XPath query. + /// + /// A value indicating whether to consider unpublished content. + /// The XPath query. + /// Optional XPath variables. + /// The contents. + /// The value of overrides defaults. + IEnumerable GetByXPath(bool preview, XPathExpression xpath, params XPathVariable[] vars); + + /// + /// Gets contents resulting from an XPath query. + /// + /// The XPath query. + /// Optional XPath variables. + /// The contents. + /// Considers published or unpublished content depending on defaults. + IEnumerable GetByXPath(XPathExpression xpath, params XPathVariable[] vars); + + /// + /// Creates an XPath navigator that can be used to navigate contents. + /// + /// A value indicating whether to consider unpublished content. + /// The XPath navigator. + /// + /// The value of overrides the context. + /// The navigator is already a safe clone (no need to clone it again). + /// + XPathNavigator CreateNavigator(bool preview); + + /// + /// Creates an XPath navigator that can be used to navigate one node. + /// + /// The node identifier. + /// A value indicating whether to consider unpublished content. + /// The XPath navigator, or null. + /// + /// The value of overrides the context. + /// The navigator is already a safe clone (no need to clone it again). + /// + /// Navigates over the node - and only the node, ie no children. Exists only for backward + /// compatibility + transition reasons, we should obsolete that one as soon as possible. + /// + /// If the node does not exist, returns null. + /// + XPathNavigator? CreateNodeNavigator(int id, bool preview); + + /// + /// Gets a value indicating whether the cache contains published content. + /// + /// A value indicating whether to consider unpublished content. + /// A value indicating whether the cache contains published content. + /// The value of overrides defaults. + bool HasContent(bool preview); + + /// + /// Gets a value indicating whether the cache contains published content. + /// + /// A value indicating whether the cache contains published content. + /// Considers published or unpublished content depending on defaults. + bool HasContent(); + + /// + /// Gets a content type identified by its unique identifier. + /// + /// The content type unique identifier. + /// The content type, or null. + IPublishedContentType? GetContentType(int id); + + /// + /// Gets a content type identified by its alias. + /// + /// The content type alias. + /// The content type, or null. + /// The alias is case-insensitive. + IPublishedContentType? GetContentType(string alias); + + /// + /// Gets contents of a given content type. + /// + /// The content type. + /// The contents. + IEnumerable GetByContentType(IPublishedContentType contentType); + + /// + /// Gets a content type identified by its alias. + /// + /// The content type key. + /// The content type, or null. + IPublishedContentType? GetContentType(Guid key); } diff --git a/src/Umbraco.Core/PublishedCache/IPublishedContentCache.cs b/src/Umbraco.Core/PublishedCache/IPublishedContentCache.cs index 4621adcb821d..c753751d5018 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedContentCache.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedContentCache.cs @@ -1,61 +1,76 @@ using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.PublishedCache +namespace Umbraco.Cms.Core.PublishedCache; + +public interface IPublishedContentCache : IPublishedCache { - public interface IPublishedContentCache : IPublishedCache - { - /// - /// Gets content identified by a route. - /// - /// A value indicating whether to consider unpublished content. - /// The route - /// A value forcing the HideTopLevelNode setting. - /// The content, or null. - /// - /// A valid route is either a simple path eg /foo/bar/nil or a root node id and a path, eg 123/foo/bar/nil. - /// If is null then the settings value is used. - /// The value of overrides defaults. - /// - IPublishedContent? GetByRoute(bool preview, string route, bool? hideTopLevelNode = null, string? culture = null); + /// + /// Gets content identified by a route. + /// + /// A value indicating whether to consider unpublished content. + /// The route + /// A value forcing the HideTopLevelNode setting. + /// The content, or null. + /// + /// + /// A valid route is either a simple path eg /foo/bar/nil or a root node id and a path, eg + /// 123/foo/bar/nil. + /// + /// + /// If + /// + /// is null then the settings value is used. + /// + /// The value of overrides defaults. + /// + IPublishedContent? GetByRoute(bool preview, string route, bool? hideTopLevelNode = null, string? culture = null); - /// - /// Gets content identified by a route. - /// - /// The route - /// A value forcing the HideTopLevelNode setting. - /// The content, or null. - /// - /// A valid route is either a simple path eg /foo/bar/nil or a root node id and a path, eg 123/foo/bar/nil. - /// If is null then the settings value is used. - /// Considers published or unpublished content depending on defaults. - /// - IPublishedContent? GetByRoute(string route, bool? hideTopLevelNode = null, string? culture = null); + /// + /// Gets content identified by a route. + /// + /// The route + /// A value forcing the HideTopLevelNode setting. + /// The content, or null. + /// + /// + /// A valid route is either a simple path eg /foo/bar/nil or a root node id and a path, eg + /// 123/foo/bar/nil. + /// + /// + /// If + /// + /// is null then the settings value is used. + /// + /// Considers published or unpublished content depending on defaults. + /// + IPublishedContent? GetByRoute(string route, bool? hideTopLevelNode = null, string? culture = null); - /// - /// Gets the route for a content identified by its unique identifier. - /// - /// A value indicating whether to consider unpublished content. - /// The content unique identifier. - /// A special string formatted route path. - /// - /// - /// The resulting string is a special encoded route string that may contain the domain ID - /// for the current route. If a domain is present the string will be prefixed with the domain ID integer, example: {domainId}/route-path-of-item - /// - /// The value of overrides defaults. - /// - string? GetRouteById(bool preview, int contentId, string? culture = null); + /// + /// Gets the route for a content identified by its unique identifier. + /// + /// A value indicating whether to consider unpublished content. + /// The content unique identifier. + /// A special string formatted route path. + /// + /// + /// The resulting string is a special encoded route string that may contain the domain ID + /// for the current route. If a domain is present the string will be prefixed with the domain ID integer, example: + /// {domainId}/route-path-of-item + /// + /// The value of overrides defaults. + /// + string? GetRouteById(bool preview, int contentId, string? culture = null); - /// - /// Gets the route for a content identified by its unique identifier. - /// - /// The content unique identifier. - /// A special string formatted route path. - /// Considers published or unpublished content depending on defaults. - /// - /// The resulting string is a special encoded route string that may contain the domain ID - /// for the current route. If a domain is present the string will be prefixed with the domain ID integer, example: {domainId}/route-path-of-item - /// - string? GetRouteById(int contentId, string? culture = null); - } + /// + /// Gets the route for a content identified by its unique identifier. + /// + /// The content unique identifier. + /// A special string formatted route path. + /// Considers published or unpublished content depending on defaults. + /// + /// The resulting string is a special encoded route string that may contain the domain ID + /// for the current route. If a domain is present the string will be prefixed with the domain ID integer, example: + /// {domainId}/route-path-of-item + /// + string? GetRouteById(int contentId, string? culture = null); } diff --git a/src/Umbraco.Core/PublishedCache/IPublishedMediaCache.cs b/src/Umbraco.Core/PublishedCache/IPublishedMediaCache.cs index 1c10776d11c2..01c72072a712 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedMediaCache.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedMediaCache.cs @@ -1,5 +1,5 @@ -namespace Umbraco.Cms.Core.PublishedCache +namespace Umbraco.Cms.Core.PublishedCache; + +public interface IPublishedMediaCache : IPublishedCache { - public interface IPublishedMediaCache : IPublishedCache - { } } diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshot.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshot.cs index 1f5344df4c3b..72e977326449 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshot.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshot.cs @@ -1,61 +1,67 @@ -using System; -using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Cache; -namespace Umbraco.Cms.Core.PublishedCache +namespace Umbraco.Cms.Core.PublishedCache; + +/// +/// Specifies a published snapshot. +/// +/// +/// A published snapshot is a point-in-time capture of the current state of +/// everything that is "published". +/// +public interface IPublishedSnapshot : IDisposable { /// - /// Specifies a published snapshot. + /// Gets the . + /// + IPublishedContentCache? Content { get; } + + /// + /// Gets the . + /// + IPublishedMediaCache? Media { get; } + + /// + /// Gets the . + /// + IPublishedMemberCache? Members { get; } + + /// + /// Gets the . + /// + IDomainCache? Domains { get; } + + /// + /// Gets the snapshot-level cache. + /// + /// + /// The snapshot-level cache belongs to this snapshot only. + /// + IAppCache? SnapshotCache { get; } + + /// + /// Gets the elements-level cache. + /// + /// + /// + /// The elements-level cache is shared by all snapshots relying on the same elements, + /// ie all snapshots built on top of unchanging content / media / etc. + /// + /// + IAppCache? ElementsCache { get; } + + /// + /// Forces the preview mode. /// - /// A published snapshot is a point-in-time capture of the current state of - /// everything that is "published". - public interface IPublishedSnapshot : IDisposable - { - /// - /// Gets the . - /// - IPublishedContentCache? Content { get; } - - /// - /// Gets the . - /// - IPublishedMediaCache? Media { get; } - - /// - /// Gets the . - /// - IPublishedMemberCache? Members { get; } - - /// - /// Gets the . - /// - IDomainCache? Domains { get; } - - /// - /// Gets the snapshot-level cache. - /// - /// - /// The snapshot-level cache belongs to this snapshot only. - /// - IAppCache? SnapshotCache { get; } - - /// - /// Gets the elements-level cache. - /// - /// - /// The elements-level cache is shared by all snapshots relying on the same elements, - /// ie all snapshots built on top of unchanging content / media / etc. - /// - IAppCache? ElementsCache { get; } - - /// - /// Forces the preview mode. - /// - /// The forced preview mode. - /// A callback to execute when reverting to previous preview. - /// - /// Forcing to false means no preview. Forcing to true means 'full' preview if the snapshot is not already previewing; - /// otherwise the snapshot keeps previewing according to whatever settings it is using already. - /// Stops forcing preview when disposed. - IDisposable ForcedPreview(bool preview, Action? callback = null); - } + /// The forced preview mode. + /// A callback to execute when reverting to previous preview. + /// + /// + /// Forcing to false means no preview. Forcing to true means 'full' preview if the snapshot is not already + /// previewing; + /// otherwise the snapshot keeps previewing according to whatever settings it is using already. + /// + /// Stops forcing preview when disposed. + /// + IDisposable ForcedPreview(bool preview, Action? callback = null); } diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotAccessor.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotAccessor.cs index 3a4b5a24b0a9..0f9cc8fca9ba 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotAccessor.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotAccessor.cs @@ -1,10 +1,10 @@ -namespace Umbraco.Cms.Core.PublishedCache +namespace Umbraco.Cms.Core.PublishedCache; + +/// +/// Provides access to a TryGetPublishedSnapshot bool method that will return true if the "current" +/// is not null. +/// +public interface IPublishedSnapshotAccessor { - /// - /// Provides access to a TryGetPublishedSnapshot bool method that will return true if the "current" is not null. - /// - public interface IPublishedSnapshotAccessor - { - bool TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot); - } + bool TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot); } diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs index 210739c6a29c..f8d158dce92d 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs @@ -1,102 +1,112 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; using Umbraco.Cms.Core.Cache; -namespace Umbraco.Cms.Core.PublishedCache +namespace Umbraco.Cms.Core.PublishedCache; + +/// +/// Creates and manages instances. +/// +public interface IPublishedSnapshotService : IDisposable { + /* Various places (such as Node) want to access the XML content, today as an XmlDocument + * but to migrate to a new cache, they're migrating to an XPathNavigator. Still, they need + * to find out how to get that navigator. + * + * Because a cache such as NuCache is contextual i.e. it has a "snapshot" thing and remains + * consistent over the snapshot, the navigator has to come from the "current" snapshot. + * + * So although everything should be injected... we also need a notion of "the current published + * snapshot". This is provided by the IPublishedSnapshotAccessor. + * + */ /// - /// Creates and manages instances. + /// Creates a published snapshot. /// - public interface IPublishedSnapshotService : IDisposable - { - /* Various places (such as Node) want to access the XML content, today as an XmlDocument - * but to migrate to a new cache, they're migrating to an XPathNavigator. Still, they need - * to find out how to get that navigator. - * - * Because a cache such as NuCache is contextual i.e. it has a "snapshot" thing and remains - * consistent over the snapshot, the navigator has to come from the "current" snapshot. - * - * So although everything should be injected... we also need a notion of "the current published - * snapshot". This is provided by the IPublishedSnapshotAccessor. - * - */ - - /// - /// Creates a published snapshot. - /// - /// A preview token, or null if not previewing. - /// A published snapshot. - /// If is null, the snapshot is not previewing, else it - /// is previewing, and what is or is not visible in preview depends on the content of the token, - /// which is not specified and depends on the actual published snapshot service implementation. - IPublishedSnapshot CreatePublishedSnapshot(string? previewToken); + /// A preview token, or null if not previewing. + /// A published snapshot. + /// + /// If is null, the snapshot is not previewing, else it + /// is previewing, and what is or is not visible in preview depends on the content of the token, + /// which is not specified and depends on the actual published snapshot service implementation. + /// + IPublishedSnapshot CreatePublishedSnapshot(string? previewToken); - /// - /// Rebuilds internal database caches (but does not reload). - /// - /// If not null will process content for the matching content types, if empty will process all content - /// If not null will process content for the matching media types, if empty will process all media - /// If not null will process content for the matching members types, if empty will process all members - /// - /// Forces the snapshot service to rebuild its internal database caches. For instance, some caches - /// may rely on a database table to store pre-serialized version of documents. - /// This does *not* reload the caches. Caches need to be reloaded, for instance via - /// RefreshAllPublishedSnapshot method. - /// - void Rebuild( - IReadOnlyCollection? contentTypeIds = null, - IReadOnlyCollection? mediaTypeIds = null, - IReadOnlyCollection? memberTypeIds = null); + /// + /// Rebuilds internal database caches (but does not reload). + /// + /// + /// If not null will process content for the matching content types, if empty will process all + /// content + /// + /// + /// If not null will process content for the matching media types, if empty will process all + /// media + /// + /// + /// If not null will process content for the matching members types, if empty will process all + /// members + /// + /// + /// + /// Forces the snapshot service to rebuild its internal database caches. For instance, some caches + /// may rely on a database table to store pre-serialized version of documents. + /// + /// + /// This does *not* reload the caches. Caches need to be reloaded, for instance via + /// RefreshAllPublishedSnapshot method. + /// + /// + void Rebuild( + IReadOnlyCollection? contentTypeIds = null, + IReadOnlyCollection? mediaTypeIds = null, + IReadOnlyCollection? memberTypeIds = null); - /* An IPublishedCachesService implementation can rely on transaction-level events to update - * its internal, database-level data, as these events are purely internal. However, it cannot - * rely on cache refreshers CacheUpdated events to update itself, as these events are external - * and the order-of-execution of the handlers cannot be guaranteed, which means that some - * user code may run before Umbraco is finished updating itself. Instead, the cache refreshers - * explicitly notify the service of changes. - * - */ + /* An IPublishedCachesService implementation can rely on transaction-level events to update + * its internal, database-level data, as these events are purely internal. However, it cannot + * rely on cache refreshers CacheUpdated events to update itself, as these events are external + * and the order-of-execution of the handlers cannot be guaranteed, which means that some + * user code may run before Umbraco is finished updating itself. Instead, the cache refreshers + * explicitly notify the service of changes. + * + */ - /// - /// Notifies of content cache refresher changes. - /// - /// The changes. - /// A value indicating whether draft contents have been changed in the cache. - /// A value indicating whether published contents have been changed in the cache. - void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged); + /// + /// Notifies of content cache refresher changes. + /// + /// The changes. + /// A value indicating whether draft contents have been changed in the cache. + /// A value indicating whether published contents have been changed in the cache. + void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged); - /// - /// Notifies of media cache refresher changes. - /// - /// The changes. - /// A value indicating whether medias have been changed in the cache. - void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged); + /// + /// Notifies of media cache refresher changes. + /// + /// The changes. + /// A value indicating whether medias have been changed in the cache. + void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged); - // there is no NotifyChanges for MemberCacheRefresher because we're not caching members. + // there is no NotifyChanges for MemberCacheRefresher because we're not caching members. - /// - /// Notifies of content type refresher changes. - /// - /// The changes. - void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads); + /// + /// Notifies of content type refresher changes. + /// + /// The changes. + void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads); - /// - /// Notifies of data type refresher changes. - /// - /// The changes. - void Notify(DataTypeCacheRefresher.JsonPayload[] payloads); + /// + /// Notifies of data type refresher changes. + /// + /// The changes. + void Notify(DataTypeCacheRefresher.JsonPayload[] payloads); - /// - /// Notifies of domain refresher changes. - /// - /// The changes. - void Notify(DomainCacheRefresher.JsonPayload[] payloads); + /// + /// Notifies of domain refresher changes. + /// + /// The changes. + void Notify(DomainCacheRefresher.JsonPayload[] payloads); - /// - /// Cleans up unused snapshots - /// - Task CollectAsync(); - } + /// + /// Cleans up unused snapshots + /// + Task CollectAsync(); } diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotStatus.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotStatus.cs index 5695f0337776..3991681bd181 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotStatus.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotStatus.cs @@ -1,18 +1,17 @@ -namespace Umbraco.Cms.Core.PublishedCache +namespace Umbraco.Cms.Core.PublishedCache; + +/// +/// Returns the currents status for nucache +/// +public interface IPublishedSnapshotStatus { /// - /// Returns the currents status for nucache + /// Gets the URL used to retreive the status /// - public interface IPublishedSnapshotStatus - { - /// - /// Gets the status report as a string - /// - string GetStatus(); + string StatusUrl { get; } - /// - /// Gets the URL used to retreive the status - /// - string StatusUrl { get; } - } + /// + /// Gets the status report as a string + /// + string GetStatus(); } diff --git a/src/Umbraco.Core/PublishedCache/ITagQuery.cs b/src/Umbraco.Core/PublishedCache/ITagQuery.cs index 9a59cac9d6a1..c823db305f7f 100644 --- a/src/Umbraco.Core/PublishedCache/ITagQuery.cs +++ b/src/Umbraco.Core/PublishedCache/ITagQuery.cs @@ -1,59 +1,58 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.PublishedCache +namespace Umbraco.Cms.Core.PublishedCache; + +public interface ITagQuery { - public interface ITagQuery - { - /// - /// Gets all documents tagged with the specified tag. - /// - IEnumerable GetContentByTag(string tag, string? group = null, string? culture = null); - - /// - /// Gets all documents tagged with any tag in the specified group. - /// - IEnumerable GetContentByTagGroup(string group, string? culture = null); - - /// - /// Gets all media tagged with the specified tag. - /// - IEnumerable GetMediaByTag(string tag, string? group = null, string? culture = null); - - /// - /// Gets all media tagged with any tag in the specified group. - /// - IEnumerable GetMediaByTagGroup(string group, string? culture = null); - - /// - /// Gets all tags. - /// - IEnumerable GetAllTags(string? group = null, string? culture = null); - - /// - /// Gets all document tags. - /// - IEnumerable GetAllContentTags(string? group = null, string? culture = null); - - /// - /// Gets all media tags. - /// - IEnumerable GetAllMediaTags(string? group = null, string? culture = null); - - /// - /// Gets all member tags. - /// - IEnumerable GetAllMemberTags(string? group = null, string? culture = null); - - /// - /// Gets all tags attached to an entity via a property. - /// - IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string? group = null, string? culture = null); - - /// - /// Gets all tags attached to an entity. - /// - IEnumerable GetTagsForEntity(int contentId, string? group = null, string? culture = null); - } + /// + /// Gets all documents tagged with the specified tag. + /// + IEnumerable GetContentByTag(string tag, string? group = null, string? culture = null); + + /// + /// Gets all documents tagged with any tag in the specified group. + /// + IEnumerable GetContentByTagGroup(string group, string? culture = null); + + /// + /// Gets all media tagged with the specified tag. + /// + IEnumerable GetMediaByTag(string tag, string? group = null, string? culture = null); + + /// + /// Gets all media tagged with any tag in the specified group. + /// + IEnumerable GetMediaByTagGroup(string group, string? culture = null); + + /// + /// Gets all tags. + /// + IEnumerable GetAllTags(string? group = null, string? culture = null); + + /// + /// Gets all document tags. + /// + IEnumerable GetAllContentTags(string? group = null, string? culture = null); + + /// + /// Gets all media tags. + /// + IEnumerable GetAllMediaTags(string? group = null, string? culture = null); + + /// + /// Gets all member tags. + /// + IEnumerable GetAllMemberTags(string? group = null, string? culture = null); + + /// + /// Gets all tags attached to an entity via a property. + /// + IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string? group = null, + string? culture = null); + + /// + /// Gets all tags attached to an entity. + /// + IEnumerable GetTagsForEntity(int contentId, string? group = null, string? culture = null); } diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContent.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContent.cs index 557d5469b69b..81bdf5223cb2 100644 --- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContent.cs +++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContent.cs @@ -1,107 +1,107 @@ -using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Linq; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PublishedCache.Internal +namespace Umbraco.Cms.Core.PublishedCache.Internal; + +// TODO: Only used in unit tests, needs to be moved to test project +[EditorBrowsable(EditorBrowsableState.Never)] +public sealed class InternalPublishedContent : IPublishedContent { - // TODO: Only used in unit tests, needs to be moved to test project - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class InternalPublishedContent : IPublishedContent + private Dictionary? _cultures; + + public InternalPublishedContent(IPublishedContentType contentType) { - public InternalPublishedContent(IPublishedContentType contentType) - { - // initialize boring stuff - TemplateId = 0; - WriterId = CreatorId = 0; - CreateDate = UpdateDate = DateTime.Now; - Version = Guid.Empty; - Path = string.Empty; - ContentType = contentType; - Properties = Enumerable.Empty(); - } + // initialize boring stuff + TemplateId = 0; + WriterId = CreatorId = 0; + CreateDate = UpdateDate = DateTime.Now; + Version = Guid.Empty; + Path = string.Empty; + ContentType = contentType; + Properties = Enumerable.Empty(); + } - private Dictionary? _cultures; + public Guid Version { get; set; } - private Dictionary GetCultures() => new Dictionary { { string.Empty, new PublishedCultureInfo(string.Empty, Name, UrlSegment, UpdateDate) } }; + public int ParentId { get; set; } - public int Id { get; set; } + public IEnumerable? ChildIds { get; set; } - public Guid Key { get; set; } + public object? this[string alias] + { + get + { + IPublishedProperty property = GetProperty(alias); + return property == null || property.HasValue() == false ? null : property.GetValue(); + } + } - public int? TemplateId { get; set; } + public int Id { get; set; } - public int SortOrder { get; set; } + public Guid Key { get; set; } - public string? Name { get; set; } + public int? TemplateId { get; set; } - public IReadOnlyDictionary Cultures => _cultures ??= GetCultures(); + public int SortOrder { get; set; } - public string? UrlSegment { get; set; } + public string? Name { get; set; } - public int WriterId { get; set; } + public IReadOnlyDictionary Cultures => _cultures ??= GetCultures(); - public int CreatorId { get; set; } + public string? UrlSegment { get; set; } - public string Path { get; set; } + public int WriterId { get; set; } - public DateTime CreateDate { get; set; } + public int CreatorId { get; set; } - public DateTime UpdateDate { get; set; } + public string Path { get; set; } - public Guid Version { get; set; } + public DateTime CreateDate { get; set; } - public int Level { get; set; } + public DateTime UpdateDate { get; set; } - public PublishedItemType ItemType => PublishedItemType.Content; + public int Level { get; set; } - public bool IsDraft(string? culture = null) => false; + public PublishedItemType ItemType => PublishedItemType.Content; - public bool IsPublished(string? culture = null) => true; + public bool IsDraft(string? culture = null) => false; - public int ParentId { get; set; } + public bool IsPublished(string? culture = null) => true; - public IEnumerable? ChildIds { get; set; } + public IPublishedContent? Parent { get; set; } - public IPublishedContent? Parent { get; set; } + public IEnumerable? Children { get; set; } - public IEnumerable? Children { get; set; } + public IEnumerable? ChildrenForAllCultures => Children; - public IEnumerable? ChildrenForAllCultures => Children; + public IPublishedContentType ContentType { get; set; } - public IPublishedContentType ContentType { get; set; } + public IEnumerable Properties { get; set; } - public IEnumerable Properties { get; set; } + public IPublishedProperty? GetProperty(string alias) => + Properties?.FirstOrDefault(p => p.Alias.InvariantEquals(alias)); - public IPublishedProperty? GetProperty(string alias) => Properties?.FirstOrDefault(p => p.Alias.InvariantEquals(alias)); + private Dictionary GetCultures() => new() + { + {string.Empty, new PublishedCultureInfo(string.Empty, Name, UrlSegment, UpdateDate)} + }; - public IPublishedProperty? GetProperty(string alias, bool recurse) + public IPublishedProperty? GetProperty(string alias, bool recurse) + { + IPublishedProperty? property = GetProperty(alias); + if (recurse == false) { - IPublishedProperty? property = GetProperty(alias); - if (recurse == false) - { - return property; - } - - IPublishedContent? content = this; - while (content != null && (property == null || property.HasValue() == false)) - { - content = content.Parent; - property = content?.GetProperty(alias); - } - return property; } - public object? this[string alias] + IPublishedContent? content = this; + while (content != null && (property == null || property.HasValue() == false)) { - get - { - var property = GetProperty(alias); - return property == null || property.HasValue() == false ? null : property.GetValue(); - } + content = content.Parent; + property = content?.GetProperty(alias); } + + return property; } } diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs index abeb19e4ec77..ef07b5d3b7d4 100644 --- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs +++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs @@ -1,65 +1,72 @@ -using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Linq; +using System.Xml.XPath; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Xml; -namespace Umbraco.Cms.Core.PublishedCache.Internal +namespace Umbraco.Cms.Core.PublishedCache.Internal; + +// TODO: Only used in unit tests, needs to be moved to test project +[EditorBrowsable(EditorBrowsableState.Never)] +public sealed class InternalPublishedContentCache : PublishedCacheBase, IPublishedContentCache, IPublishedMediaCache { - // TODO: Only used in unit tests, needs to be moved to test project - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class InternalPublishedContentCache : PublishedCacheBase, IPublishedContentCache, IPublishedMediaCache - { - private readonly Dictionary _content = new Dictionary(); + private readonly Dictionary _content = new(); - public InternalPublishedContentCache() - : base(false) - { - } + public InternalPublishedContentCache() + : base(false) + { + } - //public void Add(InternalPublishedContent content) => _content[content.Id] = content.CreateModel(Mock.Of()); + public IPublishedContent GetByRoute(bool preview, string route, bool? hideTopLevelNode = null, + string? culture = null) => throw new NotImplementedException(); - public void Clear() => _content.Clear(); + public IPublishedContent GetByRoute(string route, bool? hideTopLevelNode = null, string? culture = null) => + throw new NotImplementedException(); - public IPublishedContent GetByRoute(bool preview, string route, bool? hideTopLevelNode = null, string? culture = null) => throw new NotImplementedException(); + public string GetRouteById(bool preview, int contentId, string? culture = null) => + throw new NotImplementedException(); - public IPublishedContent GetByRoute(string route, bool? hideTopLevelNode = null, string? culture = null) => throw new NotImplementedException(); + public string GetRouteById(int contentId, string? culture = null) => throw new NotImplementedException(); - public string GetRouteById(bool preview, int contentId, string? culture = null) => throw new NotImplementedException(); + public override IPublishedContent? GetById(bool preview, int contentId) => + _content.ContainsKey(contentId) ? _content[contentId] : null; - public string GetRouteById(int contentId, string? culture = null) => throw new NotImplementedException(); + public override IPublishedContent GetById(bool preview, Guid contentId) => throw new NotImplementedException(); - public override IPublishedContent? GetById(bool preview, int contentId) => _content.ContainsKey(contentId) ? _content[contentId] : null; + public override IPublishedContent GetById(bool preview, Udi nodeId) => throw new NotSupportedException(); - public override IPublishedContent GetById(bool preview, Guid contentId) => throw new NotImplementedException(); + public override bool HasById(bool preview, int contentId) => _content.ContainsKey(contentId); - public override IPublishedContent GetById(bool preview, Udi nodeId) => throw new NotSupportedException(); + public override IEnumerable GetAtRoot(bool preview, string? culture = null) => + _content.Values.Where(x => x.Parent == null); - public override bool HasById(bool preview, int contentId) => _content.ContainsKey(contentId); + public override IPublishedContent GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars) => + throw new NotImplementedException(); - public override IEnumerable GetAtRoot(bool preview, string? culture = null) => _content.Values.Where(x => x.Parent == null); + public override IPublishedContent GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) => + throw new NotImplementedException(); - public override IPublishedContent GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars) => throw new NotImplementedException(); + public override IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars) => + throw new NotImplementedException(); - public override IPublishedContent GetSingleByXPath(bool preview, System.Xml.XPath.XPathExpression xpath, XPathVariable[] vars) => throw new NotImplementedException(); + public override IEnumerable + GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) => throw new NotImplementedException(); - public override IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars) => throw new NotImplementedException(); + public override XPathNavigator CreateNavigator(bool preview) => throw new NotImplementedException(); - public override IEnumerable GetByXPath(bool preview, System.Xml.XPath.XPathExpression xpath, XPathVariable[] vars) => throw new NotImplementedException(); + public override XPathNavigator CreateNodeNavigator(int id, bool preview) => throw new NotImplementedException(); - public override System.Xml.XPath.XPathNavigator CreateNavigator(bool preview) => throw new NotImplementedException(); + public override bool HasContent(bool preview) => _content.Count > 0; - public override System.Xml.XPath.XPathNavigator CreateNodeNavigator(int id, bool preview) => throw new NotImplementedException(); + public override IPublishedContentType GetContentType(int id) => throw new NotImplementedException(); - public override bool HasContent(bool preview) => _content.Count > 0; + public override IPublishedContentType GetContentType(string alias) => throw new NotImplementedException(); - public override IPublishedContentType GetContentType(int id) => throw new NotImplementedException(); + public override IPublishedContentType GetContentType(Guid key) => throw new NotImplementedException(); - public override IPublishedContentType GetContentType(string alias) => throw new NotImplementedException(); + public override IEnumerable GetByContentType(IPublishedContentType contentType) => + throw new NotImplementedException(); - public override IPublishedContentType GetContentType(Guid key) => throw new NotImplementedException(); + //public void Add(InternalPublishedContent content) => _content[content.Id] = content.CreateModel(Mock.Of()); - public override IEnumerable GetByContentType(IPublishedContentType contentType) => throw new NotImplementedException(); - } + public void Clear() => _content.Clear(); } diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs index 0e7280d4434b..a741f4b7ff96 100644 --- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs +++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs @@ -1,30 +1,28 @@ using System.ComponentModel; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.PublishedCache.Internal -{ - // TODO: Only used in unit tests, needs to be moved to test project - [EditorBrowsable(EditorBrowsableState.Never)] - public class InternalPublishedProperty : IPublishedProperty - { - public IPublishedPropertyType PropertyType { get; set; } = null!; +namespace Umbraco.Cms.Core.PublishedCache.Internal; - public string Alias { get; set; } = string.Empty; +// TODO: Only used in unit tests, needs to be moved to test project +[EditorBrowsable(EditorBrowsableState.Never)] +public class InternalPublishedProperty : IPublishedProperty +{ + public object? SolidSourceValue { get; set; } - public object? SolidSourceValue { get; set; } + public object? SolidValue { get; set; } - public object? SolidValue { get; set; } + public bool SolidHasValue { get; set; } - public bool SolidHasValue { get; set; } + public object? SolidXPathValue { get; set; } + public IPublishedPropertyType PropertyType { get; set; } = null!; - public object? SolidXPathValue { get; set; } + public string Alias { get; set; } = string.Empty; - public virtual object? GetSourceValue(string? culture = null, string? segment = null) => SolidSourceValue; + public virtual object? GetSourceValue(string? culture = null, string? segment = null) => SolidSourceValue; - public virtual object? GetValue(string? culture = null, string? segment = null) => SolidValue; + public virtual object? GetValue(string? culture = null, string? segment = null) => SolidValue; - public virtual object? GetXPathValue(string? culture = null, string? segment = null) => SolidXPathValue; + public virtual object? GetXPathValue(string? culture = null, string? segment = null) => SolidXPathValue; - public virtual bool HasValue(string? culture = null, string? segment = null) => SolidHasValue; - } + public virtual bool HasValue(string? culture = null, string? segment = null) => SolidHasValue; } diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshot.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshot.cs index 0516edc47b73..56a7da6adc92 100644 --- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshot.cs +++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshot.cs @@ -1,37 +1,35 @@ -using System; using System.ComponentModel; using Umbraco.Cms.Core.Cache; -namespace Umbraco.Cms.Core.PublishedCache.Internal -{ +namespace Umbraco.Cms.Core.PublishedCache.Internal; - // TODO: Only used in unit tests, needs to be moved to test project - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class InternalPublishedSnapshot : IPublishedSnapshot - { - public InternalPublishedContentCache InnerContentCache { get; } = new InternalPublishedContentCache(); - public InternalPublishedContentCache InnerMediaCache { get; } = new InternalPublishedContentCache(); +// TODO: Only used in unit tests, needs to be moved to test project +[EditorBrowsable(EditorBrowsableState.Never)] +public sealed class InternalPublishedSnapshot : IPublishedSnapshot +{ + public InternalPublishedContentCache InnerContentCache { get; } = new(); + public InternalPublishedContentCache InnerMediaCache { get; } = new(); - public IPublishedContentCache Content => InnerContentCache; + public IPublishedContentCache Content => InnerContentCache; - public IPublishedMediaCache Media => InnerMediaCache; + public IPublishedMediaCache Media => InnerMediaCache; - public IPublishedMemberCache? Members => null; + public IPublishedMemberCache? Members => null; - public IDomainCache? Domains => null; + public IDomainCache? Domains => null; - public IDisposable ForcedPreview(bool forcedPreview, Action? callback = null) => throw new NotImplementedException(); + public IDisposable ForcedPreview(bool forcedPreview, Action? callback = null) => + throw new NotImplementedException(); - public void Resync() - { - } + public IAppCache? SnapshotCache => null; - public IAppCache? SnapshotCache => null; + public IAppCache? ElementsCache => null; - public IAppCache? ElementsCache => null; + public void Dispose() + { + } - public void Dispose() - { - } + public void Resync() + { } } diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshotService.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshotService.cs index bbf121b45719..f2194b2aa85e 100644 --- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshotService.cs +++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshotService.cs @@ -1,63 +1,56 @@ -using System.Collections.Generic; using System.ComponentModel; -using System.Threading.Tasks; using Umbraco.Cms.Core.Cache; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PublishedCache.Internal +namespace Umbraco.Cms.Core.PublishedCache.Internal; + +// TODO: Only used in unit tests, needs to be moved to test project +[EditorBrowsable(EditorBrowsableState.Never)] +public class InternalPublishedSnapshotService : IPublishedSnapshotService { - // TODO: Only used in unit tests, needs to be moved to test project - [EditorBrowsable(EditorBrowsableState.Never)] - public class InternalPublishedSnapshotService : IPublishedSnapshotService - { - private InternalPublishedSnapshot? _snapshot; - private InternalPublishedSnapshot? _previewSnapshot; + private InternalPublishedSnapshot? _previewSnapshot; + private InternalPublishedSnapshot? _snapshot; - public Task CollectAsync() => Task.CompletedTask; + public Task CollectAsync() => Task.CompletedTask; - public IPublishedSnapshot CreatePublishedSnapshot(string? previewToken) + public IPublishedSnapshot CreatePublishedSnapshot(string? previewToken) + { + if (previewToken.IsNullOrWhiteSpace()) { - if (previewToken.IsNullOrWhiteSpace()) - { - return _snapshot ??= new InternalPublishedSnapshot(); - } - else - { - return _previewSnapshot ??= new InternalPublishedSnapshot(); - } + return _snapshot ??= new InternalPublishedSnapshot(); } - public void Dispose() - { - _snapshot?.Dispose(); - _previewSnapshot?.Dispose(); - } + return _previewSnapshot ??= new InternalPublishedSnapshot(); + } - public void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged) - { - draftChanged = false; - publishedChanged = false; - } + public void Dispose() + { + _snapshot?.Dispose(); + _previewSnapshot?.Dispose(); + } - public void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged) - { - anythingChanged = false; - } + public void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged) + { + draftChanged = false; + publishedChanged = false; + } - public void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads) - { - } + public void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged) => anythingChanged = false; - public void Notify(DataTypeCacheRefresher.JsonPayload[] payloads) - { - } + public void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads) + { + } - public void Notify(DomainCacheRefresher.JsonPayload[] payloads) - { - } + public void Notify(DataTypeCacheRefresher.JsonPayload[] payloads) + { + } - public void Rebuild(IReadOnlyCollection? contentTypeIds = null, IReadOnlyCollection? mediaTypeIds = null, IReadOnlyCollection? memberTypeIds = null) - { - } + public void Notify(DomainCacheRefresher.JsonPayload[] payloads) + { + } + + public void Rebuild(IReadOnlyCollection? contentTypeIds = null, IReadOnlyCollection? mediaTypeIds = null, + IReadOnlyCollection? memberTypeIds = null) + { } } diff --git a/src/Umbraco.Core/PublishedCache/PublishedCacheBase.cs b/src/Umbraco.Core/PublishedCache/PublishedCacheBase.cs index b374424b8b5e..b610febdc7d7 100644 --- a/src/Umbraco.Core/PublishedCache/PublishedCacheBase.cs +++ b/src/Umbraco.Core/PublishedCache/PublishedCacheBase.cs @@ -1,111 +1,84 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml.XPath; +using System.Xml.XPath; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Xml; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PublishedCache +namespace Umbraco.Cms.Core.PublishedCache; + +public abstract class PublishedCacheBase : IPublishedCache { - public abstract class PublishedCacheBase : IPublishedCache - { - private readonly IVariationContextAccessor? _variationContextAccessor; + private readonly IVariationContextAccessor? _variationContextAccessor; - public PublishedCacheBase(IVariationContextAccessor variationContextAccessor) - { - _variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); + public PublishedCacheBase(IVariationContextAccessor variationContextAccessor) => _variationContextAccessor = + variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); - } - public bool PreviewDefault { get; } + protected PublishedCacheBase(bool previewDefault) => PreviewDefault = previewDefault; - protected PublishedCacheBase(bool previewDefault) - { - PreviewDefault = previewDefault; - } + public bool PreviewDefault { get; } - public abstract IPublishedContent? GetById(bool preview, int contentId); + public abstract IPublishedContent? GetById(bool preview, int contentId); - public IPublishedContent? GetById(int contentId) - => GetById(PreviewDefault, contentId); + public IPublishedContent? GetById(int contentId) + => GetById(PreviewDefault, contentId); - public abstract IPublishedContent? GetById(bool preview, Guid contentId); + public abstract IPublishedContent? GetById(bool preview, Guid contentId); - public IPublishedContent? GetById(Guid contentId) - => GetById(PreviewDefault, contentId); + public IPublishedContent? GetById(Guid contentId) + => GetById(PreviewDefault, contentId); - public abstract IPublishedContent? GetById(bool preview, Udi contentId); + public abstract IPublishedContent? GetById(bool preview, Udi contentId); - public IPublishedContent? GetById(Udi contentId) - => GetById(PreviewDefault, contentId); + public IPublishedContent? GetById(Udi contentId) + => GetById(PreviewDefault, contentId); - public abstract bool HasById(bool preview, int contentId); + public abstract bool HasById(bool preview, int contentId); - public bool HasById(int contentId) - => HasById(PreviewDefault, contentId); + public bool HasById(int contentId) + => HasById(PreviewDefault, contentId); - public abstract IEnumerable GetAtRoot(bool preview, string? culture = null); + public abstract IEnumerable GetAtRoot(bool preview, string? culture = null); - public IEnumerable GetAtRoot(string? culture = null) - { - return GetAtRoot(PreviewDefault, culture); - } + public IEnumerable GetAtRoot(string? culture = null) => GetAtRoot(PreviewDefault, culture); - public abstract IPublishedContent? GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars); + public abstract IPublishedContent? GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars); - public IPublishedContent? GetSingleByXPath(string xpath, XPathVariable[] vars) - { - return GetSingleByXPath(PreviewDefault, xpath, vars); - } + public IPublishedContent? GetSingleByXPath(string xpath, XPathVariable[] vars) => + GetSingleByXPath(PreviewDefault, xpath, vars); - public abstract IPublishedContent? GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars); + public abstract IPublishedContent? GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars); - public IPublishedContent? GetSingleByXPath(XPathExpression xpath, XPathVariable[] vars) - { - return GetSingleByXPath(PreviewDefault, xpath, vars); - } + public IPublishedContent? GetSingleByXPath(XPathExpression xpath, XPathVariable[] vars) => + GetSingleByXPath(PreviewDefault, xpath, vars); - public abstract IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars); + public abstract IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars); - public IEnumerable GetByXPath(string xpath, XPathVariable[] vars) - { - return GetByXPath(PreviewDefault, xpath, vars); - } + public IEnumerable GetByXPath(string xpath, XPathVariable[] vars) => + GetByXPath(PreviewDefault, xpath, vars); - public abstract IEnumerable GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars); + public abstract IEnumerable + GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars); - public IEnumerable GetByXPath(XPathExpression xpath, XPathVariable[] vars) - { - return GetByXPath(PreviewDefault, xpath, vars); - } + public IEnumerable GetByXPath(XPathExpression xpath, XPathVariable[] vars) => + GetByXPath(PreviewDefault, xpath, vars); - public abstract XPathNavigator CreateNavigator(bool preview); + public abstract XPathNavigator CreateNavigator(bool preview); - public XPathNavigator CreateNavigator() - { - return CreateNavigator(PreviewDefault); - } + public XPathNavigator CreateNavigator() => CreateNavigator(PreviewDefault); - public abstract XPathNavigator? CreateNodeNavigator(int id, bool preview); + public abstract XPathNavigator? CreateNodeNavigator(int id, bool preview); - public abstract bool HasContent(bool preview); + public abstract bool HasContent(bool preview); - public bool HasContent() - { - return HasContent(PreviewDefault); - } + public bool HasContent() => HasContent(PreviewDefault); - public abstract IPublishedContentType? GetContentType(int id); - public abstract IPublishedContentType? GetContentType(string alias); - public abstract IPublishedContentType? GetContentType(Guid key); + public abstract IPublishedContentType? GetContentType(int id); + public abstract IPublishedContentType? GetContentType(string alias); + public abstract IPublishedContentType? GetContentType(Guid key); - public virtual IEnumerable GetByContentType(IPublishedContentType contentType) - { - // this is probably not super-efficient, but works - // some cache implementation may want to override it, though - return GetAtRoot() - .SelectMany(x => x.DescendantsOrSelf(_variationContextAccessor!)) - .Where(x => x.ContentType.Id == contentType.Id); - } - } + public virtual IEnumerable GetByContentType(IPublishedContentType contentType) => + // this is probably not super-efficient, but works + // some cache implementation may want to override it, though + GetAtRoot() + .SelectMany(x => x.DescendantsOrSelf(_variationContextAccessor!)) + .Where(x => x.ContentType.Id == contentType.Id); } diff --git a/src/Umbraco.Core/PublishedCache/PublishedElement.cs b/src/Umbraco.Core/PublishedCache/PublishedElement.cs index c67e3b0e40c1..baa3b54b57f2 100644 --- a/src/Umbraco.Core/PublishedCache/PublishedElement.cs +++ b/src/Umbraco.Core/PublishedCache/PublishedElement.cs @@ -1,89 +1,103 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.PublishedCache +namespace Umbraco.Cms.Core.PublishedCache; + +// notes: +// a published element does NOT manage any tree-like elements, neither the +// original NestedContent (from Lee) nor the DetachedPublishedContent POC did. +// +// at the moment we do NOT support models for sets - that would require +// an entirely new models factory + not even sure it makes sense at all since +// sets are created manually todo yes it does! - what does this all mean? +// +public class PublishedElement : IPublishedElement { - // notes: - // a published element does NOT manage any tree-like elements, neither the - // original NestedContent (from Lee) nor the DetachedPublishedContent POC did. - // - // at the moment we do NOT support models for sets - that would require - // an entirely new models factory + not even sure it makes sense at all since - // sets are created manually todo yes it does! - what does this all mean? - // - public class PublishedElement : IPublishedElement + // initializes a new instance of the PublishedElement class + // within the context of a published snapshot service (eg a published content property value) + public PublishedElement(IPublishedContentType contentType, Guid key, Dictionary? values, + bool previewing, + PropertyCacheLevel referenceCacheLevel, IPublishedSnapshotAccessor? publishedSnapshotAccessor) { - // initializes a new instance of the PublishedElement class - // within the context of a published snapshot service (eg a published content property value) - public PublishedElement(IPublishedContentType contentType, Guid key, Dictionary? values, bool previewing, - PropertyCacheLevel referenceCacheLevel, IPublishedSnapshotAccessor? publishedSnapshotAccessor) + if (key == Guid.Empty) { - if (key == Guid.Empty) throw new ArgumentException("Empty guid."); - if (values == null) throw new ArgumentNullException(nameof(values)); - if (referenceCacheLevel != PropertyCacheLevel.None && publishedSnapshotAccessor == null) - throw new ArgumentNullException("A published snapshot accessor is required when referenceCacheLevel != None.", nameof(publishedSnapshotAccessor)); - - ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - Key = key; - - values = GetCaseInsensitiveValueDictionary(values); - - _propertiesArray = contentType - .PropertyTypes? - .Select(propertyType => - { - values.TryGetValue(propertyType.Alias, out var value); - return (IPublishedProperty)new PublishedElementPropertyBase(propertyType, this, previewing, referenceCacheLevel, value, publishedSnapshotAccessor); - }) - .ToArray() - ?? new IPublishedProperty[0]; + throw new ArgumentException("Empty guid."); } - // initializes a new instance of the PublishedElement class - // without any context, so it's purely 'standalone' and should NOT interfere with the published snapshot service - // + using an initial reference cache level of .None ensures that everything will be - // cached at .Content level - and that reference cache level will propagate to all - // properties - public PublishedElement(IPublishedContentType contentType, Guid key, Dictionary values, bool previewing) - : this(contentType, key, values, previewing, PropertyCacheLevel.None, null) - { } + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } - private static Dictionary GetCaseInsensitiveValueDictionary(Dictionary values) + if (referenceCacheLevel != PropertyCacheLevel.None && publishedSnapshotAccessor == null) { - // ensure we ignore case for property aliases - var comparer = values.Comparer; - var ignoreCase = Equals(comparer, StringComparer.OrdinalIgnoreCase) || Equals(comparer, StringComparer.InvariantCultureIgnoreCase) || Equals(comparer, StringComparer.CurrentCultureIgnoreCase); - return ignoreCase ? values : new Dictionary(values, StringComparer.OrdinalIgnoreCase); + throw new ArgumentNullException( + "A published snapshot accessor is required when referenceCacheLevel != None.", + nameof(publishedSnapshotAccessor)); } - #region ContentType + ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + Key = key; + + values = GetCaseInsensitiveValueDictionary(values); + + _propertiesArray = contentType + .PropertyTypes? + .Select(propertyType => + { + values.TryGetValue(propertyType.Alias, out var value); + return (IPublishedProperty)new PublishedElementPropertyBase(propertyType, this, + previewing, referenceCacheLevel, value, publishedSnapshotAccessor); + }) + .ToArray() + ?? new IPublishedProperty[0]; + } - public IPublishedContentType ContentType { get; } + // initializes a new instance of the PublishedElement class + // without any context, so it's purely 'standalone' and should NOT interfere with the published snapshot service + // + using an initial reference cache level of .None ensures that everything will be + // cached at .Content level - and that reference cache level will propagate to all + // properties + public PublishedElement(IPublishedContentType contentType, Guid key, Dictionary values, + bool previewing) + : this(contentType, key, values, previewing, PropertyCacheLevel.None, null) + { + } - #endregion + #region ContentType - #region PublishedElement + public IPublishedContentType ContentType { get; } - public Guid Key { get; } + #endregion - #endregion + #region PublishedElement - #region Properties + public Guid Key { get; } - private readonly IPublishedProperty[] _propertiesArray; + #endregion - public IEnumerable Properties => _propertiesArray; + private static Dictionary GetCaseInsensitiveValueDictionary(Dictionary values) + { + // ensure we ignore case for property aliases + IEqualityComparer comparer = values.Comparer; + var ignoreCase = Equals(comparer, StringComparer.OrdinalIgnoreCase) || + Equals(comparer, StringComparer.InvariantCultureIgnoreCase) || + Equals(comparer, StringComparer.CurrentCultureIgnoreCase); + return ignoreCase ? values : new Dictionary(values, StringComparer.OrdinalIgnoreCase); + } - public IPublishedProperty? GetProperty(string alias) - { - var index = ContentType.GetPropertyIndex(alias); - var property = index < 0 ? null : _propertiesArray?[index]; - return property; - } + #region Properties + + private readonly IPublishedProperty[] _propertiesArray; - #endregion + public IEnumerable Properties => _propertiesArray; + + public IPublishedProperty? GetProperty(string alias) + { + var index = ContentType.GetPropertyIndex(alias); + IPublishedProperty property = index < 0 ? null : _propertiesArray?[index]; + return property; } + + #endregion } diff --git a/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs b/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs index c6fe365be8c5..ce77dfbd36f0 100644 --- a/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs +++ b/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs @@ -1,197 +1,221 @@ -using System; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.PublishedCache +namespace Umbraco.Cms.Core.PublishedCache; + +internal class PublishedElementPropertyBase : PublishedPropertyBase { - internal class PublishedElementPropertyBase : PublishedPropertyBase + // define constant - determines whether to use cache when previewing + // to store eg routes, property converted values, anything - caching + // means faster execution, but uses memory - not sure if we want it + // so making it configurable. + private const bool FullCacheWhenPreviewing = true; + private readonly object _locko = new(); + private readonly IPublishedSnapshotAccessor? _publishedSnapshotAccessor; + private readonly object? _sourceValue; + + protected readonly IPublishedElement Element; + protected readonly bool IsMember; + protected readonly bool IsPreviewing; + private CacheValues? _cacheValues; + + private bool _interInitialized; + private object? _interValue; + private string? _valuesCacheKey; + + public PublishedElementPropertyBase(IPublishedPropertyType propertyType, IPublishedElement element, bool previewing, + PropertyCacheLevel referenceCacheLevel, object? sourceValue = null, + IPublishedSnapshotAccessor? publishedSnapshotAccessor = null) + : base(propertyType, referenceCacheLevel) { - private readonly object _locko = new object(); - private readonly object? _sourceValue; - private readonly IPublishedSnapshotAccessor? _publishedSnapshotAccessor; - - protected readonly IPublishedElement Element; - protected readonly bool IsPreviewing; - protected readonly bool IsMember; - - private bool _interInitialized; - private object? _interValue; - private CacheValues? _cacheValues; - private string? _valuesCacheKey; - - // define constant - determines whether to use cache when previewing - // to store eg routes, property converted values, anything - caching - // means faster execution, but uses memory - not sure if we want it - // so making it configurable. - private const bool FullCacheWhenPreviewing = true; - - public PublishedElementPropertyBase(IPublishedPropertyType propertyType, IPublishedElement element, bool previewing, PropertyCacheLevel referenceCacheLevel, object? sourceValue = null, IPublishedSnapshotAccessor? publishedSnapshotAccessor = null) - : base(propertyType, referenceCacheLevel) + _sourceValue = sourceValue; + _publishedSnapshotAccessor = publishedSnapshotAccessor; + Element = element; + IsPreviewing = previewing; + IsMember = propertyType.ContentType?.ItemType == PublishedItemType.Member; + } + + // used to cache the CacheValues of this property + // ReSharper disable InconsistentlySynchronizedField + internal string ValuesCacheKey => _valuesCacheKey + ?? (_valuesCacheKey = PropertyCacheValues(Element.Key, Alias, IsPreviewing)); + // ReSharper restore InconsistentlySynchronizedField + + public override bool HasValue(string? culture = null, string? segment = null) + { + var hasValue = PropertyType.IsValue(_sourceValue, PropertyValueLevel.Source); + if (hasValue.HasValue) { - _sourceValue = sourceValue; - _publishedSnapshotAccessor = publishedSnapshotAccessor; - Element = element; - IsPreviewing = previewing; - IsMember = propertyType.ContentType?.ItemType == PublishedItemType.Member; + return hasValue.Value; } - public override bool HasValue(string? culture = null, string? segment = null) - { - var hasValue = PropertyType.IsValue(_sourceValue, PropertyValueLevel.Source); - if (hasValue.HasValue) return hasValue.Value; + GetCacheLevels(out PropertyCacheLevel cacheLevel, out PropertyCacheLevel referenceCacheLevel); - GetCacheLevels(out var cacheLevel, out var referenceCacheLevel); + lock (_locko) + { + var value = GetInterValue(); + hasValue = PropertyType.IsValue(value, PropertyValueLevel.Inter); + if (hasValue.HasValue) + { + return hasValue.Value; + } - lock (_locko) + CacheValues cacheValues = GetCacheValues(cacheLevel); + if (!cacheValues.ObjectInitialized) { - var value = GetInterValue(); - hasValue = PropertyType.IsValue(value, PropertyValueLevel.Inter); - if (hasValue.HasValue) return hasValue.Value; - - var cacheValues = GetCacheValues(cacheLevel); - if (!cacheValues.ObjectInitialized) - { - cacheValues.ObjectValue = PropertyType.ConvertInterToObject(Element, referenceCacheLevel, value, IsPreviewing); - cacheValues.ObjectInitialized = true; - } - value = cacheValues.ObjectValue; - return PropertyType.IsValue(value, PropertyValueLevel.Object) ?? false; + cacheValues.ObjectValue = + PropertyType.ConvertInterToObject(Element, referenceCacheLevel, value, IsPreviewing); + cacheValues.ObjectInitialized = true; } + + value = cacheValues.ObjectValue; + return PropertyType.IsValue(value, PropertyValueLevel.Object) ?? false; } + } - // used to cache the CacheValues of this property - // ReSharper disable InconsistentlySynchronizedField - internal string ValuesCacheKey => _valuesCacheKey - ?? (_valuesCacheKey = PropertyCacheValues(Element.Key, Alias, IsPreviewing)); - // ReSharper restore InconsistentlySynchronizedField + public static string PropertyCacheValues(Guid contentUid, string typeAlias, bool previewing) => + "PublishedSnapshot.Property.CacheValues[" + (previewing ? "D:" : "P:") + contentUid + ":" + typeAlias + "]"; - protected class CacheValues + private void GetCacheLevels(out PropertyCacheLevel cacheLevel, out PropertyCacheLevel referenceCacheLevel) + { + // based upon the current reference cache level (ReferenceCacheLevel) and this property + // cache level (PropertyType.CacheLevel), determines both the actual cache level for the + // property, and the new reference cache level. + + // if the property cache level is 'shorter-termed' that the reference + // then use it and it becomes the new reference, else use Content and + // don't change the reference. + // + // examples: + // currently (reference) caching at published snapshot, property specifies + // elements, ok to use element. OTOH, currently caching at elements, + // property specifies snapshot, need to use snapshot. + // + if (PropertyType.CacheLevel > ReferenceCacheLevel || PropertyType.CacheLevel == PropertyCacheLevel.None) { - public bool ObjectInitialized; - public object? ObjectValue; - public bool XPathInitialized; - public object? XPathValue; + cacheLevel = PropertyType.CacheLevel; + referenceCacheLevel = cacheLevel; } - - public static string PropertyCacheValues(Guid contentUid, string typeAlias, bool previewing) => "PublishedSnapshot.Property.CacheValues[" + (previewing ? "D:" : "P:") + contentUid + ":" + typeAlias + "]"; - - private void GetCacheLevels(out PropertyCacheLevel cacheLevel, out PropertyCacheLevel referenceCacheLevel) + else { - // based upon the current reference cache level (ReferenceCacheLevel) and this property - // cache level (PropertyType.CacheLevel), determines both the actual cache level for the - // property, and the new reference cache level. - - // if the property cache level is 'shorter-termed' that the reference - // then use it and it becomes the new reference, else use Content and - // don't change the reference. - // - // examples: - // currently (reference) caching at published snapshot, property specifies - // elements, ok to use element. OTOH, currently caching at elements, - // property specifies snapshot, need to use snapshot. - // - if (PropertyType.CacheLevel > ReferenceCacheLevel || PropertyType.CacheLevel == PropertyCacheLevel.None) - { - cacheLevel = PropertyType.CacheLevel; - referenceCacheLevel = cacheLevel; - } - else - { - cacheLevel = PropertyCacheLevel.Element; - referenceCacheLevel = ReferenceCacheLevel; - } + cacheLevel = PropertyCacheLevel.Element; + referenceCacheLevel = ReferenceCacheLevel; } + } - private IAppCache? GetSnapshotCache() + private IAppCache? GetSnapshotCache() + { + // cache within the snapshot cache, unless previewing, then use the snapshot or + // elements cache (if we don't want to pollute the elements cache with short-lived + // data) depending on settings + // for members, always cache in the snapshot cache - never pollute elements cache + if (_publishedSnapshotAccessor is null) { - // cache within the snapshot cache, unless previewing, then use the snapshot or - // elements cache (if we don't want to pollute the elements cache with short-lived - // data) depending on settings - // for members, always cache in the snapshot cache - never pollute elements cache - if (_publishedSnapshotAccessor is null) - { - return null; - } - - if (!_publishedSnapshotAccessor.TryGetPublishedSnapshot(out var publishedSnapshot)) - { - return null; - } - - return (IsPreviewing == false || FullCacheWhenPreviewing) && IsMember == false - ? publishedSnapshot!.ElementsCache - : publishedSnapshot!.SnapshotCache; + return null; } - private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel) + if (!_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot publishedSnapshot)) { - CacheValues cacheValues; - switch (cacheLevel) - { - case PropertyCacheLevel.None: - // never cache anything - cacheValues = new CacheValues(); - break; - case PropertyCacheLevel.Element: - // cache within the property object itself, ie within the content object - cacheValues = _cacheValues ?? (_cacheValues = new CacheValues()); - break; - case PropertyCacheLevel.Elements: - // cache within the elements cache, depending... - var snapshotCache = GetSnapshotCache(); - cacheValues = (CacheValues?) snapshotCache?.Get(ValuesCacheKey, () => new CacheValues()) ?? new CacheValues(); - break; - case PropertyCacheLevel.Snapshot: - var publishedSnapshot = _publishedSnapshotAccessor?.GetRequiredPublishedSnapshot(); - // cache within the snapshot cache - var facadeCache = publishedSnapshot?.SnapshotCache; - cacheValues = (CacheValues?) facadeCache?.Get(ValuesCacheKey, () => new CacheValues()) ?? new CacheValues(); - break; - default: - throw new InvalidOperationException("Invalid cache level."); - } - return cacheValues; + return null; } - private object? GetInterValue() + return (IsPreviewing == false || FullCacheWhenPreviewing) && IsMember == false + ? publishedSnapshot!.ElementsCache + : publishedSnapshot!.SnapshotCache; + } + + private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel) + { + CacheValues cacheValues; + switch (cacheLevel) { - if (_interInitialized) return _interValue; + case PropertyCacheLevel.None: + // never cache anything + cacheValues = new CacheValues(); + break; + case PropertyCacheLevel.Element: + // cache within the property object itself, ie within the content object + cacheValues = _cacheValues ?? (_cacheValues = new CacheValues()); + break; + case PropertyCacheLevel.Elements: + // cache within the elements cache, depending... + IAppCache snapshotCache = GetSnapshotCache(); + cacheValues = (CacheValues?)snapshotCache?.Get(ValuesCacheKey, () => new CacheValues()) ?? + new CacheValues(); + break; + case PropertyCacheLevel.Snapshot: + IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor?.GetRequiredPublishedSnapshot(); + // cache within the snapshot cache + IAppCache facadeCache = publishedSnapshot?.SnapshotCache; + cacheValues = (CacheValues?)facadeCache?.Get(ValuesCacheKey, () => new CacheValues()) ?? + new CacheValues(); + break; + default: + throw new InvalidOperationException("Invalid cache level."); + } - _interValue = PropertyType.ConvertSourceToInter(Element, _sourceValue, IsPreviewing); - _interInitialized = true; + return cacheValues; + } + + private object? GetInterValue() + { + if (_interInitialized) + { return _interValue; } - public override object? GetSourceValue(string? culture = null, string? segment = null) => _sourceValue; + _interValue = PropertyType.ConvertSourceToInter(Element, _sourceValue, IsPreviewing); + _interInitialized = true; + return _interValue; + } - public override object? GetValue(string? culture = null, string? segment = null) - { - GetCacheLevels(out var cacheLevel, out var referenceCacheLevel); + public override object? GetSourceValue(string? culture = null, string? segment = null) => _sourceValue; - lock (_locko) + public override object? GetValue(string? culture = null, string? segment = null) + { + GetCacheLevels(out PropertyCacheLevel cacheLevel, out PropertyCacheLevel referenceCacheLevel); + + lock (_locko) + { + CacheValues cacheValues = GetCacheValues(cacheLevel); + if (cacheValues.ObjectInitialized) { - var cacheValues = GetCacheValues(cacheLevel); - if (cacheValues.ObjectInitialized) return cacheValues.ObjectValue; - cacheValues.ObjectValue = PropertyType.ConvertInterToObject(Element, referenceCacheLevel, GetInterValue(), IsPreviewing); - cacheValues.ObjectInitialized = true; return cacheValues.ObjectValue; } + + cacheValues.ObjectValue = + PropertyType.ConvertInterToObject(Element, referenceCacheLevel, GetInterValue(), IsPreviewing); + cacheValues.ObjectInitialized = true; + return cacheValues.ObjectValue; } + } - public override object? GetXPathValue(string? culture = null, string? segment = null) - { - GetCacheLevels(out var cacheLevel, out var referenceCacheLevel); + public override object? GetXPathValue(string? culture = null, string? segment = null) + { + GetCacheLevels(out PropertyCacheLevel cacheLevel, out PropertyCacheLevel referenceCacheLevel); - lock (_locko) + lock (_locko) + { + CacheValues cacheValues = GetCacheValues(cacheLevel); + if (cacheValues.XPathInitialized) { - var cacheValues = GetCacheValues(cacheLevel); - if (cacheValues.XPathInitialized) return cacheValues.XPathValue; - cacheValues.XPathValue = PropertyType.ConvertInterToXPath(Element, referenceCacheLevel, GetInterValue(), IsPreviewing); - cacheValues.XPathInitialized = true; return cacheValues.XPathValue; } + + cacheValues.XPathValue = + PropertyType.ConvertInterToXPath(Element, referenceCacheLevel, GetInterValue(), IsPreviewing); + cacheValues.XPathInitialized = true; + return cacheValues.XPathValue; } } + + protected class CacheValues + { + public bool ObjectInitialized; + public object? ObjectValue; + public bool XPathInitialized; + public object? XPathValue; + } } diff --git a/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs b/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs index 7f81d066f2ad..0c793890d3f1 100644 --- a/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs +++ b/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs @@ -1,46 +1,44 @@ -using System; using Umbraco.Cms.Core.Web; -namespace Umbraco.Cms.Core.PublishedCache +namespace Umbraco.Cms.Core.PublishedCache; + +// TODO: This is a mess. This is a circular reference: +// IPublishedSnapshotAccessor -> PublishedSnapshotService -> UmbracoContext -> PublishedSnapshotService -> IPublishedSnapshotAccessor +// Injecting IPublishedSnapshotAccessor into PublishedSnapshotService seems pretty strange +// The underlying reason for this mess is because IPublishedContent is both a service and a model. +// Until that is fixed, IPublishedContent will need to have a IPublishedSnapshotAccessor +public class UmbracoContextPublishedSnapshotAccessor : IPublishedSnapshotAccessor { - // TODO: This is a mess. This is a circular reference: - // IPublishedSnapshotAccessor -> PublishedSnapshotService -> UmbracoContext -> PublishedSnapshotService -> IPublishedSnapshotAccessor - // Injecting IPublishedSnapshotAccessor into PublishedSnapshotService seems pretty strange - // The underlying reason for this mess is because IPublishedContent is both a service and a model. - // Until that is fixed, IPublishedContent will need to have a IPublishedSnapshotAccessor - public class UmbracoContextPublishedSnapshotAccessor : IPublishedSnapshotAccessor - { - private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; - public UmbracoContextPublishedSnapshotAccessor(IUmbracoContextAccessor umbracoContextAccessor) - { - _umbracoContextAccessor = umbracoContextAccessor; - } + public UmbracoContextPublishedSnapshotAccessor(IUmbracoContextAccessor umbracoContextAccessor) => + _umbracoContextAccessor = umbracoContextAccessor; - public IPublishedSnapshot? PublishedSnapshot + public IPublishedSnapshot? PublishedSnapshot + { + get { - get + if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext umbracoContext)) { - if (!_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) - { - return null; - } - return umbracoContext?.PublishedSnapshot; + return null; } - set => throw new NotSupportedException(); // not ok to set + return umbracoContext?.PublishedSnapshot; } - public bool TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) - { - if (!_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) - { - publishedSnapshot = null; - return false; - } - publishedSnapshot = umbracoContext?.PublishedSnapshot; + set => throw new NotSupportedException(); // not ok to set + } - return publishedSnapshot is not null; + public bool TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) + { + if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext umbracoContext)) + { + publishedSnapshot = null; + return false; } + + publishedSnapshot = umbracoContext?.PublishedSnapshot; + + return publishedSnapshot is not null; } } diff --git a/src/Umbraco.Core/ReflectionUtilities.cs b/src/Umbraco.Core/ReflectionUtilities.cs index 982e0835fba3..e7b5bccc63af 100644 --- a/src/Umbraco.Core/ReflectionUtilities.cs +++ b/src/Umbraco.Core/ReflectionUtilities.cs @@ -1,919 +1,1219 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; +using System.Reflection; using System.Reflection.Emit; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Provides utilities to simplify reflection. +/// +/// +/// +/// Readings: +/// * CIL instructions: https://en.wikipedia.org/wiki/List_of_CIL_instructions +/// * ECMA 335: https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf +/// * MSIL programming: http://www.blackbeltcoder.com/Articles/net/msil-programming-part-1 +/// +/// +/// Supports emitting constructors, instance and static methods, instance property getters and +/// setters. Does not support static properties yet. +/// +/// +public static class ReflectionUtilities { + #region Fields + /// - /// Provides utilities to simplify reflection. + /// Emits a field getter. /// - /// - /// Readings: - /// * CIL instructions: https://en.wikipedia.org/wiki/List_of_CIL_instructions - /// * ECMA 335: https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf - /// * MSIL programming: http://www.blackbeltcoder.com/Articles/net/msil-programming-part-1 - /// - /// Supports emitting constructors, instance and static methods, instance property getters and - /// setters. Does not support static properties yet. - /// - public static class ReflectionUtilities - { - #region Fields - - /// - /// Emits a field getter. - /// - /// The declaring type. - /// The field type. - /// The name of the field. - /// - /// A field getter function. - /// - /// fieldName - /// Value can't be empty or consist only of white-space characters. - - /// or - /// Value type does not match field . type. - /// Could not find field .. - public static Func EmitFieldGetter(string fieldName) - { - var field = GetField(fieldName); - return EmitFieldGetter(field); - } - - /// - /// Emits a field setter. - /// - /// The declaring type. - /// The field type. - /// The name of the field. - /// - /// A field setter action. - /// - /// fieldName - /// Value can't be empty or consist only of white-space characters. - - /// or - /// Value type does not match field . type. - /// Could not find field .. - public static Action EmitFieldSetter(string fieldName) - { - var field = GetField(fieldName); - return EmitFieldSetter(field); - } - - /// - /// Emits a field getter and setter. - /// - /// The declaring type. - /// The field type. - /// The name of the field. - /// - /// A field getter and setter functions. - /// - /// fieldName - /// Value can't be empty or consist only of white-space characters. - - /// or - /// Value type does not match field . type. - /// Could not find field .. - public static (Func, Action) EmitFieldGetterAndSetter(string fieldName) - { - var field = GetField(fieldName); - return (EmitFieldGetter(field), EmitFieldSetter(field)); - } - - /// - /// Gets the field. - /// - /// The type of the declaring. - /// The type of the value. - /// Name of the field. - /// - /// fieldName - /// Value can't be empty or consist only of white-space characters. - - /// or - /// Value type does not match field . type. - /// Could not find field .. - private static FieldInfo GetField(string fieldName) - { - if (fieldName == null) throw new ArgumentNullException(nameof(fieldName)); - if (string.IsNullOrWhiteSpace(fieldName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(fieldName)); - - // get the field - var field = typeof(TDeclaring).GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - if (field == null) throw new InvalidOperationException($"Could not find field {typeof(TDeclaring)}.{fieldName}."); - - // validate field type - if (field.FieldType != typeof(TValue)) // strict - throw new ArgumentException($"Value type {typeof(TValue)} does not match field {typeof(TDeclaring)}.{fieldName} type {field.FieldType}."); - - return field; - } - - private static Func EmitFieldGetter(FieldInfo field) - { - // emit - var (dm, ilgen) = CreateIlGenerator(field.DeclaringType?.Module, new [] { typeof(TDeclaring) }, typeof(TValue)); - ilgen.Emit(OpCodes.Ldarg_0); - ilgen.Emit(OpCodes.Ldfld, field); - ilgen.Return(); - - return (Func) (object) dm.CreateDelegate(typeof(Func)); - } - - private static Action EmitFieldSetter(FieldInfo field) - { - // emit - var (dm, ilgen) = CreateIlGenerator(field.DeclaringType?.Module, new [] { typeof(TDeclaring), typeof(TValue) }, typeof(void)); - ilgen.Emit(OpCodes.Ldarg_0); - ilgen.Emit(OpCodes.Ldarg_1); - ilgen.Emit(OpCodes.Stfld, field); - ilgen.Return(); - - return (Action) (object) dm.CreateDelegate(typeof(Action)); - } - - #endregion - - #region Properties - - /// - /// Emits a property getter. - /// - /// The declaring type. - /// The property type. - /// The name of the property. - /// A value indicating whether the property and its getter must exist. - /// - /// A property getter function. If is false, returns null when the property or its getter does not exist. - /// - /// propertyName - /// Value can't be empty or consist only of white-space characters. - - /// or - /// Value type does not match property . type. - /// Could not find property getter for .. - public static Func? EmitPropertyGetter(string propertyName, bool mustExist = true) - { - if (propertyName == null) throw new ArgumentNullException(nameof(propertyName)); - if (string.IsNullOrWhiteSpace(propertyName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyName)); + /// The declaring type. + /// The field type. + /// The name of the field. + /// + /// A field getter function. + /// + /// fieldName + /// + /// Value can't be empty or consist only of white-space characters. - + /// or + /// Value type does not match field . + /// type. + /// + /// + /// Could not find field . + /// . + /// + public static Func EmitFieldGetter(string fieldName) + { + FieldInfo field = GetField(fieldName); + return EmitFieldGetter(field); + } - var property = typeof(TDeclaring).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - - if (property?.GetMethod != null) - return EmitMethod>(property.GetMethod); + /// + /// Emits a field setter. + /// + /// The declaring type. + /// The field type. + /// The name of the field. + /// + /// A field setter action. + /// + /// fieldName + /// + /// Value can't be empty or consist only of white-space characters. - + /// or + /// Value type does not match field . + /// type. + /// + /// + /// Could not find field . + /// . + /// + public static Action EmitFieldSetter(string fieldName) + { + FieldInfo field = GetField(fieldName); + return EmitFieldSetter(field); + } - if (!mustExist) - return default; + /// + /// Emits a field getter and setter. + /// + /// The declaring type. + /// The field type. + /// The name of the field. + /// + /// A field getter and setter functions. + /// + /// fieldName + /// + /// Value can't be empty or consist only of white-space characters. - + /// or + /// Value type does not match field . + /// type. + /// + /// + /// Could not find field . + /// . + /// + public static (Func, Action) EmitFieldGetterAndSetter( + string fieldName) + { + FieldInfo field = GetField(fieldName); + return (EmitFieldGetter(field), EmitFieldSetter(field)); + } + + /// + /// Gets the field. + /// + /// The type of the declaring. + /// The type of the value. + /// Name of the field. + /// + /// fieldName + /// + /// Value can't be empty or consist only of white-space characters. - + /// or + /// Value type does not match field . + /// type. + /// + /// + /// Could not find field . + /// . + /// + private static FieldInfo GetField(string fieldName) + { + if (fieldName == null) + { + throw new ArgumentNullException(nameof(fieldName)); + } - throw new InvalidOperationException($"Could not find getter for {typeof(TDeclaring)}.{propertyName}."); + if (string.IsNullOrWhiteSpace(fieldName)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(fieldName)); } - /// - /// Emits a property setter. - /// - /// The declaring type. - /// The property type. - /// The name of the property. - /// A value indicating whether the property and its setter must exist. - /// - /// A property setter function. If is false, returns null when the property or its setter does not exist. - /// - /// propertyName - /// Value can't be empty or consist only of white-space characters. - - /// or - /// Value type does not match property . type. - /// Could not find property setter for .. - public static Action? EmitPropertySetter(string propertyName, bool mustExist = true) + // get the field + FieldInfo field = typeof(TDeclaring).GetField(fieldName, + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + if (field == null) { - if (propertyName == null) throw new ArgumentNullException(nameof(propertyName)); - if (string.IsNullOrWhiteSpace(propertyName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyName)); + throw new InvalidOperationException($"Could not find field {typeof(TDeclaring)}.{fieldName}."); + } - var property = typeof(TDeclaring).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + // validate field type + if (field.FieldType != typeof(TValue)) // strict + { + throw new ArgumentException( + $"Value type {typeof(TValue)} does not match field {typeof(TDeclaring)}.{fieldName} type {field.FieldType}."); + } - if (property?.SetMethod != null) - return EmitMethod>(property.SetMethod); + return field; + } - if (!mustExist) - return default; + private static Func EmitFieldGetter(FieldInfo field) + { + // emit + (DynamicMethod dm, ILGenerator ilgen) = + CreateIlGenerator(field.DeclaringType?.Module, new[] {typeof(TDeclaring)}, typeof(TValue)); + ilgen.Emit(OpCodes.Ldarg_0); + ilgen.Emit(OpCodes.Ldfld, field); + ilgen.Return(); + + return (Func)dm.CreateDelegate(typeof(Func)); + } - throw new InvalidOperationException($"Could not find setter for {typeof(TDeclaring)}.{propertyName}."); + private static Action EmitFieldSetter(FieldInfo field) + { + // emit + (DynamicMethod dm, ILGenerator ilgen) = CreateIlGenerator(field.DeclaringType?.Module, + new[] {typeof(TDeclaring), typeof(TValue)}, typeof(void)); + ilgen.Emit(OpCodes.Ldarg_0); + ilgen.Emit(OpCodes.Ldarg_1); + ilgen.Emit(OpCodes.Stfld, field); + ilgen.Return(); + + return (Action)dm.CreateDelegate(typeof(Action)); + } + + #endregion + + #region Properties + + /// + /// Emits a property getter. + /// + /// The declaring type. + /// The property type. + /// The name of the property. + /// A value indicating whether the property and its getter must exist. + /// + /// A property getter function. If is false, returns null when the property or its + /// getter does not exist. + /// + /// propertyName + /// + /// Value can't be empty or consist only of white-space characters. - + /// or + /// Value type does not match property . + /// type. + /// + /// + /// Could not find property getter for . + /// . + /// + public static Func? EmitPropertyGetter(string propertyName, + bool mustExist = true) + { + if (propertyName == null) + { + throw new ArgumentNullException(nameof(propertyName)); } - /// - /// Emits a property getter and setter. - /// - /// The declaring type. - /// The property type. - /// The name of the property. - /// A value indicating whether the property and its getter and setter must exist. - /// - /// A property getter and setter functions. If is false, returns null when the property or its getter or setter does not exist. - /// - /// propertyName - /// Value can't be empty or consist only of white-space characters. - - /// or - /// Value type does not match property . type. - /// Could not find property getter and setter for .. - public static (Func, Action) EmitPropertyGetterAndSetter(string propertyName, bool mustExist = true) + if (string.IsNullOrWhiteSpace(propertyName)) { - if (propertyName == null) throw new ArgumentNullException(nameof(propertyName)); - if (string.IsNullOrWhiteSpace(propertyName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyName)); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(propertyName)); + } - var property = typeof(TDeclaring).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + PropertyInfo property = typeof(TDeclaring).GetProperty(propertyName, + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - if (property?.GetMethod != null && property.SetMethod != null) - return ( - EmitMethod>(property.GetMethod), - EmitMethod>(property.SetMethod)); + if (property?.GetMethod != null) + { + return EmitMethod>(property.GetMethod); + } - if (!mustExist) - return default; + if (!mustExist) + { + return default; + } + + throw new InvalidOperationException($"Could not find getter for {typeof(TDeclaring)}.{propertyName}."); + } - throw new InvalidOperationException($"Could not find getter and/or setter for {typeof(TDeclaring)}.{propertyName}."); + /// + /// Emits a property setter. + /// + /// The declaring type. + /// The property type. + /// The name of the property. + /// A value indicating whether the property and its setter must exist. + /// + /// A property setter function. If is false, returns null when the property or its + /// setter does not exist. + /// + /// propertyName + /// + /// Value can't be empty or consist only of white-space characters. - + /// or + /// Value type does not match property . + /// type. + /// + /// + /// Could not find property setter for . + /// . + /// + public static Action? EmitPropertySetter(string propertyName, + bool mustExist = true) + { + if (propertyName == null) + { + throw new ArgumentNullException(nameof(propertyName)); } - /// - /// Emits a property getter. - /// - /// The declaring type. - /// The property type. - /// The property info. - /// A property getter function. - /// Occurs when is null. - /// Occurs when the property has no getter. - /// Occurs when does not match the type of the property. - public static Func EmitPropertyGetter(PropertyInfo propertyInfo) + if (string.IsNullOrWhiteSpace(propertyName)) { - if (propertyInfo == null) throw new ArgumentNullException(nameof(propertyInfo)); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(propertyName)); + } - if (propertyInfo.GetMethod == null) - throw new ArgumentException("Property has no getter.", nameof(propertyInfo)); + PropertyInfo property = typeof(TDeclaring).GetProperty(propertyName, + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - return EmitMethod>(propertyInfo.GetMethod); + if (property?.SetMethod != null) + { + return EmitMethod>(property.SetMethod); } - /// - /// Emits a property setter. - /// - /// The declaring type. - /// The property type. - /// The property info. - /// A property setter function. - /// Occurs when is null. - /// Occurs when the property has no setter. - /// Occurs when does not match the type of the property. - public static Action EmitPropertySetter(PropertyInfo propertyInfo) + if (!mustExist) { - if (propertyInfo == null) throw new ArgumentNullException(nameof(propertyInfo)); + return default; + } - if (propertyInfo.SetMethod == null) - throw new ArgumentException("Property has no setter.", nameof(propertyInfo)); + throw new InvalidOperationException($"Could not find setter for {typeof(TDeclaring)}.{propertyName}."); + } - return EmitMethod>(propertyInfo.SetMethod); + /// + /// Emits a property getter and setter. + /// + /// The declaring type. + /// The property type. + /// The name of the property. + /// A value indicating whether the property and its getter and setter must exist. + /// + /// A property getter and setter functions. If is false, returns null when the + /// property or its getter or setter does not exist. + /// + /// propertyName + /// + /// Value can't be empty or consist only of white-space characters. - + /// or + /// Value type does not match property . + /// type. + /// + /// + /// Could not find property getter and setter for + /// .. + /// + public static (Func, Action) + EmitPropertyGetterAndSetter(string propertyName, bool mustExist = true) + { + if (propertyName == null) + { + throw new ArgumentNullException(nameof(propertyName)); } - /// - /// Emits a property getter and setter. - /// - /// The declaring type. - /// The property type. - /// The property info. - /// A property getter and setter functions. - /// Occurs when is null. - /// Occurs when the property has no getter or no setter. - /// Occurs when does not match the type of the property. - public static (Func, Action) EmitPropertyGetterAndSetter(PropertyInfo propertyInfo) + if (string.IsNullOrWhiteSpace(propertyName)) { - if (propertyInfo == null) throw new ArgumentNullException(nameof(propertyInfo)); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(propertyName)); + } - if (propertyInfo.GetMethod == null || propertyInfo.SetMethod == null) - throw new ArgumentException("Property has no getter and/or no setter.", nameof(propertyInfo)); + PropertyInfo property = typeof(TDeclaring).GetProperty(propertyName, + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + if (property?.GetMethod != null && property.SetMethod != null) + { return ( - EmitMethod>(propertyInfo.GetMethod), - EmitMethod>(propertyInfo.SetMethod)); - } - - /// - /// Emits a property setter. - /// - /// The declaring type. - /// The property type. - /// The property info. - /// A property setter function. - /// Occurs when is null. - /// Occurs when the property has no setter. - /// Occurs when does not match the type of the property. - public static Action EmitPropertySetterUnsafe(PropertyInfo propertyInfo) - { - if (propertyInfo == null) throw new ArgumentNullException(nameof(propertyInfo)); - - if (propertyInfo.SetMethod == null) - throw new ArgumentException("Property has no setter.", nameof(propertyInfo)); - - return EmitMethodUnsafe>(propertyInfo.SetMethod); - } - - #endregion - - #region Constructors - - /// - /// Emits a constructor. - /// - /// A lambda representing the constructor. - /// A value indicating whether the constructor must exist. - /// The optional type of the class to construct. - /// A constructor function. If is false, returns null when the constructor does not exist. - /// - /// When is not specified, it is the type returned by . - /// The constructor arguments are determined by generic arguments. - /// The type returned by does not need to be exactly , - /// when e.g. that type is not known at compile time, but it has to be a parent type (eg an interface, or object). - /// - /// Occurs when the constructor does not exist and is true. - /// Occurs when is not a Func or when - /// is specified and does not match the function's returned type. - public static TLambda? EmitConstructor(bool mustExist = true, Type? declaring = null) - { - var (_, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, true); - - // determine returned / declaring type - if (declaring == null) declaring = lambdaReturned; - else if (!lambdaReturned.IsAssignableFrom(declaring)) - throw new ArgumentException($"Type {lambdaReturned} is not assignable from type {declaring}.", nameof(declaring)); - - // get the constructor infos - var ctor = declaring.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, lambdaParameters, null); - if (ctor == null) - { - if (!mustExist) return default; - throw new InvalidOperationException($"Could not find constructor {declaring}.ctor({string.Join(", ", (IEnumerable) lambdaParameters)})."); - } + EmitMethod>(property.GetMethod), + EmitMethod>(property.SetMethod)); + } + + if (!mustExist) + { + return default; + } + + throw new InvalidOperationException( + $"Could not find getter and/or setter for {typeof(TDeclaring)}.{propertyName}."); + } - // emit - return EmitConstructorSafe(lambdaParameters, lambdaReturned, ctor); + /// + /// Emits a property getter. + /// + /// The declaring type. + /// The property type. + /// The property info. + /// A property getter function. + /// Occurs when is null. + /// Occurs when the property has no getter. + /// Occurs when does not match the type of the property. + public static Func EmitPropertyGetter(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw new ArgumentNullException(nameof(propertyInfo)); } - /// - /// Emits a constructor. - /// - /// A lambda representing the constructor. - /// The constructor info. - /// A constructor function. - /// Occurs when is not a Func or when its generic - /// arguments do not match those of . - /// Occurs when is null. - public static TLambda EmitConstructor(ConstructorInfo ctor) + if (propertyInfo.GetMethod == null) { - if (ctor == null) throw new ArgumentNullException(nameof(ctor)); + throw new ArgumentException("Property has no getter.", nameof(propertyInfo)); + } - var (_, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, true); + return EmitMethod>(propertyInfo.GetMethod); + } - return EmitConstructorSafe(lambdaParameters, lambdaReturned, ctor); + /// + /// Emits a property setter. + /// + /// The declaring type. + /// The property type. + /// The property info. + /// A property setter function. + /// Occurs when is null. + /// Occurs when the property has no setter. + /// Occurs when does not match the type of the property. + public static Action EmitPropertySetter(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw new ArgumentNullException(nameof(propertyInfo)); } - private static TLambda EmitConstructorSafe(Type[] lambdaParameters, Type returned, ConstructorInfo ctor) + if (propertyInfo.SetMethod == null) { - // get type and args - var ctorDeclaring = ctor.DeclaringType; - var ctorParameters = ctor.GetParameters().Select(x => x.ParameterType).ToArray(); + throw new ArgumentException("Property has no setter.", nameof(propertyInfo)); + } - // validate arguments - if (lambdaParameters.Length != ctorParameters.Length) - ThrowInvalidLambda("ctor", ctorDeclaring, ctorParameters); - for (var i = 0; i < lambdaParameters.Length; i++) - if (lambdaParameters[i] != ctorParameters[i]) // note: relax the constraint with IsAssignableFrom? - ThrowInvalidLambda("ctor", ctorDeclaring, ctorParameters); - if (!returned.IsAssignableFrom(ctorDeclaring)) - ThrowInvalidLambda("ctor", ctorDeclaring, ctorParameters); + return EmitMethod>(propertyInfo.SetMethod); + } - // emit - return EmitConstructor(ctorDeclaring, ctorParameters, ctor); - } - - /// - /// Emits a constructor. - /// - /// A lambda representing the constructor. - /// The constructor info. - /// A constructor function. - /// - /// The constructor is emitted in an unsafe way, using the lambda arguments without verifying - /// them at all. This assumes that the calling code is taking care of all verifications, in order - /// to avoid cast errors. - /// - /// Occurs when is not a Func or when its generic - /// arguments do not match those of . - /// Occurs when is null. - public static TLambda EmitConstructorUnsafe(ConstructorInfo ctor) - { - if (ctor == null) throw new ArgumentNullException(nameof(ctor)); - - var (_, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, true); - - // emit - unsafe - use lambda's args and assume they are correct - return EmitConstructor(lambdaReturned, lambdaParameters, ctor); - } - - private static TLambda EmitConstructor(Type? declaring, Type[] lambdaParameters, ConstructorInfo ctor) - { - // gets the method argument types - var ctorParameters = GetParameters(ctor); - - // emit - var (dm, ilgen) = CreateIlGenerator(ctor.DeclaringType?.Module, lambdaParameters, declaring); - EmitLdargs(ilgen, lambdaParameters, ctorParameters); - ilgen.Emit(OpCodes.Newobj, ctor); // ok to just return, it's only objects - ilgen.Return(); - - return (TLambda) (object) dm.CreateDelegate(typeof(TLambda)); - } - - #endregion - - #region Methods - - /// - /// Emits a static method. - /// - /// The declaring type. - /// A lambda representing the method. - /// The name of the method. - /// A value indicating whether the constructor must exist. - /// - /// The method. If is false, returns null when the method does not exist. - /// - /// methodName - /// Value can't be empty or consist only of white-space characters. - - /// or - /// Occurs when does not match the method signature.. - /// Occurs when no proper method with name could be found. - /// - /// The method arguments are determined by generic arguments. - /// - public static TLambda? EmitMethod(string methodName, bool mustExist = true) - { - return EmitMethod(typeof(TDeclaring), methodName, mustExist); - } - - /// - /// Emits a static method. - /// - /// A lambda representing the method. - /// The declaring type. - /// The name of the method. - /// A value indicating whether the constructor must exist. - /// - /// The method. If is false, returns null when the method does not exist. - /// - /// methodName - /// Value can't be empty or consist only of white-space characters. - - /// or - /// Occurs when does not match the method signature.. - /// Occurs when no proper method with name could be found. - /// - /// The method arguments are determined by generic arguments. - /// - public static TLambda? EmitMethod(Type declaring, string methodName, bool mustExist = true) - { - if (methodName == null) throw new ArgumentNullException(nameof(methodName)); - if (string.IsNullOrWhiteSpace(methodName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(methodName)); - - var (lambdaDeclaring, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, out var isFunction); - - // get the method infos - var method = declaring.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, null, lambdaParameters, null); - if (method == null || isFunction && !lambdaReturned.IsAssignableFrom(method.ReturnType)) - { - if (!mustExist) return default; - throw new InvalidOperationException($"Could not find static method {declaring}.{methodName}({string.Join(", ", (IEnumerable) lambdaParameters)})."); - } + /// + /// Emits a property getter and setter. + /// + /// The declaring type. + /// The property type. + /// The property info. + /// A property getter and setter functions. + /// Occurs when is null. + /// Occurs when the property has no getter or no setter. + /// Occurs when does not match the type of the property. + public static (Func, Action) + EmitPropertyGetterAndSetter(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw new ArgumentNullException(nameof(propertyInfo)); + } - // emit - return EmitMethod(lambdaDeclaring, lambdaReturned, lambdaParameters, method); + if (propertyInfo.GetMethod == null || propertyInfo.SetMethod == null) + { + throw new ArgumentException("Property has no getter and/or no setter.", nameof(propertyInfo)); } - /// - /// Emits a method. - /// - /// A lambda representing the method. - /// The method info. - /// The method. - /// Occurs when is null. - /// Occurs when Occurs when does not match the method signature. - public static TLambda EmitMethod(MethodInfo method) + return ( + EmitMethod>(propertyInfo.GetMethod), + EmitMethod>(propertyInfo.SetMethod)); + } + + /// + /// Emits a property setter. + /// + /// The declaring type. + /// The property type. + /// The property info. + /// A property setter function. + /// Occurs when is null. + /// Occurs when the property has no setter. + /// Occurs when does not match the type of the property. + public static Action EmitPropertySetterUnsafe(PropertyInfo propertyInfo) + { + if (propertyInfo == null) { - if (method == null) throw new ArgumentNullException(nameof(method)); + throw new ArgumentNullException(nameof(propertyInfo)); + } - // get type and args - var methodDeclaring = method.DeclaringType; - var methodReturned = method.ReturnType; - var methodParameters = method.GetParameters().Select(x => x.ParameterType).ToArray(); + if (propertyInfo.SetMethod == null) + { + throw new ArgumentException("Property has no setter.", nameof(propertyInfo)); + } - var isStatic = method.IsStatic; - var (lambdaDeclaring, lambdaParameters, lambdaReturned) = AnalyzeLambda(isStatic, out var isFunction); + return EmitMethodUnsafe>(propertyInfo.SetMethod); + } - // if not static, then the first lambda arg must be the method declaring type - if (!isStatic && (methodDeclaring == null || !methodDeclaring.IsAssignableFrom(lambdaDeclaring))) - ThrowInvalidLambda(method.Name, methodReturned, methodParameters); + #endregion - if (methodParameters.Length != lambdaParameters.Length) - ThrowInvalidLambda(method.Name, methodReturned, methodParameters); + #region Constructors - for (var i = 0; i < methodParameters.Length; i++) - if (!methodParameters[i].IsAssignableFrom(lambdaParameters[i])) - ThrowInvalidLambda(method.Name, methodReturned, methodParameters); + /// + /// Emits a constructor. + /// + /// A lambda representing the constructor. + /// A value indicating whether the constructor must exist. + /// The optional type of the class to construct. + /// + /// A constructor function. If is false, returns null when the constructor + /// does not exist. + /// + /// + /// + /// When is not specified, it is the type returned by + /// . + /// + /// The constructor arguments are determined by generic arguments. + /// + /// The type returned by does not need to be exactly , + /// when e.g. that type is not known at compile time, but it has to be a parent type (eg an interface, or + /// object). + /// + /// + /// + /// Occurs when the constructor does not exist and + /// is true. + /// + /// + /// Occurs when is not a Func or when + /// is specified and does not match the function's returned type. + /// + public static TLambda? EmitConstructor(bool mustExist = true, Type? declaring = null) + { + (_, Type[] lambdaParameters, Type lambdaReturned) = AnalyzeLambda(true, true); - // if it's a function then the last lambda arg must match the method returned type - if (isFunction && !lambdaReturned.IsAssignableFrom(methodReturned)) - ThrowInvalidLambda(method.Name, methodReturned, methodParameters); + // determine returned / declaring type + if (declaring == null) + { + declaring = lambdaReturned; + } + else if (!lambdaReturned.IsAssignableFrom(declaring)) + { + throw new ArgumentException($"Type {lambdaReturned} is not assignable from type {declaring}.", + nameof(declaring)); + } - // emit - return EmitMethod(lambdaDeclaring, lambdaReturned, lambdaParameters, method); - } - - /// - /// Emits a method. - /// - /// A lambda representing the method. - /// The method info. - /// The method. - /// Occurs when is null. - /// Occurs when Occurs when does not match the method signature. - public static TLambda EmitMethodUnsafe(MethodInfo method) - { - if (method == null) throw new ArgumentNullException(nameof(method)); - - var isStatic = method.IsStatic; - var (lambdaDeclaring, lambdaParameters, lambdaReturned) = AnalyzeLambda(isStatic, out _); - - // emit - unsafe - use lambda's args and assume they are correct - return EmitMethod(lambdaDeclaring, lambdaReturned, lambdaParameters, method); - } - - /// - /// Emits an instance method. - /// - /// A lambda representing the method. - /// The name of the method. - /// A value indicating whether the constructor must exist. - /// - /// The method. If is false, returns null when the method does not exist. - /// - /// methodName - /// Value can't be empty or consist only of white-space characters. - - /// or - /// Occurs when does not match the method signature.. - /// Occurs when no proper method with name could be found. - /// - /// The method arguments are determined by generic arguments. - /// - public static TLambda? EmitMethod(string methodName, bool mustExist = true) - { - if (methodName == null) throw new ArgumentNullException(nameof(methodName)); - if (string.IsNullOrWhiteSpace(methodName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(methodName)); - - // validate lambda type - var (lambdaDeclaring, lambdaParameters, lambdaReturned) = AnalyzeLambda(false, out var isFunction); - - // get the method infos - var method = lambdaDeclaring?.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, lambdaParameters, null); - if (method == null || isFunction && method.ReturnType != lambdaReturned) + // get the constructor infos + ConstructorInfo ctor = declaring.GetConstructor( + BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, lambdaParameters, null); + if (ctor == null) + { + if (!mustExist) { - if (!mustExist) return default; - throw new InvalidOperationException($"Could not find method {lambdaDeclaring}.{methodName}({string.Join(", ", (IEnumerable) lambdaParameters)})."); + return default; } - // emit - return EmitMethod(lambdaDeclaring, lambdaReturned, lambdaParameters, method); + throw new InvalidOperationException( + $"Could not find constructor {declaring}.ctor({string.Join(", ", (IEnumerable)lambdaParameters)})."); } - // lambdaReturned = the lambda returned type (can be void) - // lambdaArgTypes = the lambda argument types - private static TLambda EmitMethod(Type? lambdaDeclaring, Type lambdaReturned, Type[] lambdaParameters, MethodInfo method) + // emit + return EmitConstructorSafe(lambdaParameters, lambdaReturned, ctor); + } + + /// + /// Emits a constructor. + /// + /// A lambda representing the constructor. + /// The constructor info. + /// A constructor function. + /// + /// Occurs when is not a Func or when its generic + /// arguments do not match those of . + /// + /// Occurs when is null. + public static TLambda EmitConstructor(ConstructorInfo ctor) + { + if (ctor == null) + { + throw new ArgumentNullException(nameof(ctor)); + } + + (_, Type[] lambdaParameters, Type lambdaReturned) = AnalyzeLambda(true, true); + + return EmitConstructorSafe(lambdaParameters, lambdaReturned, ctor); + } + + private static TLambda EmitConstructorSafe(Type[] lambdaParameters, Type returned, ConstructorInfo ctor) + { + // get type and args + Type ctorDeclaring = ctor.DeclaringType; + Type[] ctorParameters = ctor.GetParameters().Select(x => x.ParameterType).ToArray(); + + // validate arguments + if (lambdaParameters.Length != ctorParameters.Length) + { + ThrowInvalidLambda("ctor", ctorDeclaring, ctorParameters); + } + + for (var i = 0; i < lambdaParameters.Length; i++) { - // non-static methods need the declaring type as first arg - var parameters = lambdaParameters; - if (!method.IsStatic) + if (lambdaParameters[i] != ctorParameters[i]) // note: relax the constraint with IsAssignableFrom? { - parameters = new Type[lambdaParameters.Length + 1]; - parameters[0] = lambdaDeclaring ?? method.DeclaringType!; - Array.Copy(lambdaParameters, 0, parameters, 1, lambdaParameters.Length); + ThrowInvalidLambda("ctor", ctorDeclaring, ctorParameters); } + } - // gets the method argument types - var methodArgTypes = GetParameters(method, withDeclaring: !method.IsStatic); + if (!returned.IsAssignableFrom(ctorDeclaring)) + { + ThrowInvalidLambda("ctor", ctorDeclaring, ctorParameters); + } - // emit IL - var (dm, ilgen) = CreateIlGenerator(method.DeclaringType?.Module, parameters, lambdaReturned); - EmitLdargs(ilgen, parameters, methodArgTypes); - ilgen.CallMethod(method); - EmitOutputAdapter(ilgen, lambdaReturned, method.ReturnType); - ilgen.Return(); + // emit + return EmitConstructor(ctorDeclaring, ctorParameters, ctor); + } - // create - return (TLambda) (object) dm.CreateDelegate(typeof(TLambda)); + /// + /// Emits a constructor. + /// + /// A lambda representing the constructor. + /// The constructor info. + /// A constructor function. + /// + /// + /// The constructor is emitted in an unsafe way, using the lambda arguments without verifying + /// them at all. This assumes that the calling code is taking care of all verifications, in order + /// to avoid cast errors. + /// + /// + /// + /// Occurs when is not a Func or when its generic + /// arguments do not match those of . + /// + /// Occurs when is null. + public static TLambda EmitConstructorUnsafe(ConstructorInfo ctor) + { + if (ctor == null) + { + throw new ArgumentNullException(nameof(ctor)); } - #endregion + (_, Type[] lambdaParameters, Type lambdaReturned) = AnalyzeLambda(true, true); + + // emit - unsafe - use lambda's args and assume they are correct + return EmitConstructor(lambdaReturned, lambdaParameters, ctor); + } + + private static TLambda EmitConstructor(Type? declaring, Type[] lambdaParameters, ConstructorInfo ctor) + { + // gets the method argument types + Type[] ctorParameters = GetParameters(ctor); + + // emit + (DynamicMethod dm, ILGenerator ilgen) = + CreateIlGenerator(ctor.DeclaringType?.Module, lambdaParameters, declaring); + EmitLdargs(ilgen, lambdaParameters, ctorParameters); + ilgen.Emit(OpCodes.Newobj, ctor); // ok to just return, it's only objects + ilgen.Return(); + + return (TLambda)(object)dm.CreateDelegate(typeof(TLambda)); + } - #region Utilities + #endregion - // when !isStatic, the first generic argument of the lambda is the declaring type - // hence, when !isStatic, the lambda cannot be a simple Action, as it requires at least one generic argument - // when isFunction, the last generic argument of the lambda is the returned type - // everything in between is parameters - private static (Type? Declaring, Type[] Parameters, Type Returned) AnalyzeLambda(bool isStatic, bool isFunction) + #region Methods + + /// + /// Emits a static method. + /// + /// The declaring type. + /// A lambda representing the method. + /// The name of the method. + /// A value indicating whether the constructor must exist. + /// + /// The method. If is false, returns null when the method does not exist. + /// + /// methodName + /// + /// Value can't be empty or consist only of white-space characters. - + /// or + /// Occurs when does not match the method signature.. + /// + /// + /// Occurs when no proper method with name could + /// be found. + /// + /// + /// The method arguments are determined by generic arguments. + /// + public static TLambda? EmitMethod(string methodName, bool mustExist = true) => + EmitMethod(typeof(TDeclaring), methodName, mustExist); + + /// + /// Emits a static method. + /// + /// A lambda representing the method. + /// The declaring type. + /// The name of the method. + /// A value indicating whether the constructor must exist. + /// + /// The method. If is false, returns null when the method does not exist. + /// + /// methodName + /// + /// Value can't be empty or consist only of white-space characters. - + /// or + /// Occurs when does not match the method signature.. + /// + /// + /// Occurs when no proper method with name could + /// be found. + /// + /// + /// The method arguments are determined by generic arguments. + /// + public static TLambda? EmitMethod(Type declaring, string methodName, bool mustExist = true) + { + if (methodName == null) + { + throw new ArgumentNullException(nameof(methodName)); + } + + if (string.IsNullOrWhiteSpace(methodName)) { - var typeLambda = typeof(TLambda); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(methodName)); + } - var (declaring, parameters, returned) = AnalyzeLambda(isStatic, out var maybeFunction); + (Type lambdaDeclaring, Type[] lambdaParameters, Type lambdaReturned) = + AnalyzeLambda(true, out var isFunction); - if (isFunction) + // get the method infos + MethodInfo method = declaring.GetMethod(methodName, + BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, null, lambdaParameters, null); + if (method == null || (isFunction && !lambdaReturned.IsAssignableFrom(method.ReturnType))) + { + if (!mustExist) { - if (!maybeFunction) - throw new ArgumentException($"Lambda {typeLambda} is an Action, a Func was expected.", nameof(TLambda)); + return default; } - else + + throw new InvalidOperationException( + $"Could not find static method {declaring}.{methodName}({string.Join(", ", (IEnumerable)lambdaParameters)})."); + } + + // emit + return EmitMethod(lambdaDeclaring, lambdaReturned, lambdaParameters, method); + } + + /// + /// Emits a method. + /// + /// A lambda representing the method. + /// The method info. + /// The method. + /// Occurs when is null. + /// + /// Occurs when Occurs when does not match the method + /// signature. + /// + public static TLambda EmitMethod(MethodInfo method) + { + if (method == null) + { + throw new ArgumentNullException(nameof(method)); + } + + // get type and args + Type methodDeclaring = method.DeclaringType; + Type methodReturned = method.ReturnType; + Type[] methodParameters = method.GetParameters().Select(x => x.ParameterType).ToArray(); + + var isStatic = method.IsStatic; + (Type lambdaDeclaring, Type[] lambdaParameters, Type lambdaReturned) = + AnalyzeLambda(isStatic, out var isFunction); + + // if not static, then the first lambda arg must be the method declaring type + if (!isStatic && (methodDeclaring == null || !methodDeclaring.IsAssignableFrom(lambdaDeclaring))) + { + ThrowInvalidLambda(method.Name, methodReturned, methodParameters); + } + + if (methodParameters.Length != lambdaParameters.Length) + { + ThrowInvalidLambda(method.Name, methodReturned, methodParameters); + } + + for (var i = 0; i < methodParameters.Length; i++) + { + if (!methodParameters[i].IsAssignableFrom(lambdaParameters[i])) { - if (maybeFunction) - throw new ArgumentException($"Lambda {typeLambda} is a Func, an Action was expected.", nameof(TLambda)); + ThrowInvalidLambda(method.Name, methodReturned, methodParameters); } + } - return (declaring, parameters, returned); + // if it's a function then the last lambda arg must match the method returned type + if (isFunction && !lambdaReturned.IsAssignableFrom(methodReturned)) + { + ThrowInvalidLambda(method.Name, methodReturned, methodParameters); } - // when !isStatic, the first generic argument of the lambda is the declaring type - // hence, when !isStatic, the lambda cannot be a simple Action, as it requires at least one generic argument - // when isFunction, the last generic argument of the lambda is the returned type - // everything in between is parameters - private static (Type? Declaring, Type[] Parameters, Type Returned) AnalyzeLambda(bool isStatic, out bool isFunction) + // emit + return EmitMethod(lambdaDeclaring, lambdaReturned, lambdaParameters, method); + } + + /// + /// Emits a method. + /// + /// A lambda representing the method. + /// The method info. + /// The method. + /// Occurs when is null. + /// + /// Occurs when Occurs when does not match the method + /// signature. + /// + public static TLambda EmitMethodUnsafe(MethodInfo method) + { + if (method == null) { - isFunction = false; + throw new ArgumentNullException(nameof(method)); + } - var typeLambda = typeof(TLambda); + var isStatic = method.IsStatic; + (Type lambdaDeclaring, Type[] lambdaParameters, Type lambdaReturned) = AnalyzeLambda(isStatic, out _); - var isAction = typeLambda.FullName == "System.Action"; - if (isAction) + // emit - unsafe - use lambda's args and assume they are correct + return EmitMethod(lambdaDeclaring, lambdaReturned, lambdaParameters, method); + } + + /// + /// Emits an instance method. + /// + /// A lambda representing the method. + /// The name of the method. + /// A value indicating whether the constructor must exist. + /// + /// The method. If is false, returns null when the method does not exist. + /// + /// methodName + /// + /// Value can't be empty or consist only of white-space characters. - + /// or + /// Occurs when does not match the method signature.. + /// + /// + /// Occurs when no proper method with name could + /// be found. + /// + /// + /// The method arguments are determined by generic arguments. + /// + public static TLambda? EmitMethod(string methodName, bool mustExist = true) + { + if (methodName == null) + { + throw new ArgumentNullException(nameof(methodName)); + } + + if (string.IsNullOrWhiteSpace(methodName)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(methodName)); + } + + // validate lambda type + (Type lambdaDeclaring, Type[] lambdaParameters, Type lambdaReturned) = + AnalyzeLambda(false, out var isFunction); + + // get the method infos + MethodInfo method = lambdaDeclaring?.GetMethod(methodName, + BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, lambdaParameters, null); + if (method == null || (isFunction && method.ReturnType != lambdaReturned)) + { + if (!mustExist) { - if (!isStatic) - throw new ArgumentException($"Lambda {typeLambda} is an Action and can be used for static methods exclusively.", nameof(TLambda)); + return default; + } - return (null, Array.Empty(), typeof(void)); + throw new InvalidOperationException( + $"Could not find method {lambdaDeclaring}.{methodName}({string.Join(", ", (IEnumerable)lambdaParameters)})."); + } + + // emit + return EmitMethod(lambdaDeclaring, lambdaReturned, lambdaParameters, method); + } + + // lambdaReturned = the lambda returned type (can be void) + // lambdaArgTypes = the lambda argument types + private static TLambda EmitMethod(Type? lambdaDeclaring, Type lambdaReturned, Type[] lambdaParameters, + MethodInfo method) + { + // non-static methods need the declaring type as first arg + Type[] parameters = lambdaParameters; + if (!method.IsStatic) + { + parameters = new Type[lambdaParameters.Length + 1]; + parameters[0] = lambdaDeclaring ?? method.DeclaringType!; + Array.Copy(lambdaParameters, 0, parameters, 1, lambdaParameters.Length); + } + + // gets the method argument types + Type[] methodArgTypes = GetParameters(method, !method.IsStatic); + + // emit IL + (DynamicMethod dm, ILGenerator ilgen) = + CreateIlGenerator(method.DeclaringType?.Module, parameters, lambdaReturned); + EmitLdargs(ilgen, parameters, methodArgTypes); + ilgen.CallMethod(method); + EmitOutputAdapter(ilgen, lambdaReturned, method.ReturnType); + ilgen.Return(); + + // create + return (TLambda)(object)dm.CreateDelegate(typeof(TLambda)); + } + + #endregion + + #region Utilities + + // when !isStatic, the first generic argument of the lambda is the declaring type + // hence, when !isStatic, the lambda cannot be a simple Action, as it requires at least one generic argument + // when isFunction, the last generic argument of the lambda is the returned type + // everything in between is parameters + private static (Type? Declaring, Type[] Parameters, Type Returned) AnalyzeLambda(bool isStatic, + bool isFunction) + { + Type typeLambda = typeof(TLambda); + + (Type declaring, Type[] parameters, Type returned) = AnalyzeLambda(isStatic, out var maybeFunction); + + if (isFunction) + { + if (!maybeFunction) + { + throw new ArgumentException($"Lambda {typeLambda} is an Action, a Func was expected.", nameof(TLambda)); + } + } + else + { + if (maybeFunction) + { + throw new ArgumentException($"Lambda {typeLambda} is a Func, an Action was expected.", nameof(TLambda)); } + } + + return (declaring, parameters, returned); + } + + // when !isStatic, the first generic argument of the lambda is the declaring type + // hence, when !isStatic, the lambda cannot be a simple Action, as it requires at least one generic argument + // when isFunction, the last generic argument of the lambda is the returned type + // everything in between is parameters + private static (Type? Declaring, Type[] Parameters, Type Returned) AnalyzeLambda(bool isStatic, + out bool isFunction) + { + isFunction = false; + + Type typeLambda = typeof(TLambda); - var genericDefinition = typeLambda.IsGenericType ? typeLambda.GetGenericTypeDefinition() : null; - var name = genericDefinition?.FullName; + var isAction = typeLambda.FullName == "System.Action"; + if (isAction) + { + if (!isStatic) + { + throw new ArgumentException( + $"Lambda {typeLambda} is an Action and can be used for static methods exclusively.", + nameof(TLambda)); + } - if (name == null) - throw new ArgumentException($"Lambda {typeLambda} is not a Func nor an Action.", nameof(TLambda)); + return (null, Array.Empty(), typeof(void)); + } - var isActionOf = name.StartsWith("System.Action`"); - isFunction = name.StartsWith("System.Func`"); + Type genericDefinition = typeLambda.IsGenericType ? typeLambda.GetGenericTypeDefinition() : null; + var name = genericDefinition?.FullName; - if (!isActionOf && !isFunction) - throw new ArgumentException($"Lambda {typeLambda} is not a Func nor an Action.", nameof(TLambda)); + if (name == null) + { + throw new ArgumentException($"Lambda {typeLambda} is not a Func nor an Action.", nameof(TLambda)); + } - var genericArgs = typeLambda.GetGenericArguments(); - if (genericArgs.Length == 0) - throw new Exception("Panic: Func<> or Action<> has zero generic arguments."); + var isActionOf = name.StartsWith("System.Action`"); + isFunction = name.StartsWith("System.Func`"); - var i = 0; - var declaring = isStatic ? typeof(void) : genericArgs[i++]; + if (!isActionOf && !isFunction) + { + throw new ArgumentException($"Lambda {typeLambda} is not a Func nor an Action.", nameof(TLambda)); + } - var parameterCount = genericArgs.Length - (isStatic ? 0 : 1) - (isFunction ? 1 : 0); - if (parameterCount < 0) - throw new ArgumentException($"Lambda {typeLambda} is a Func and requires at least two arguments (declaring type and returned type).", nameof(TLambda)); + Type[] genericArgs = typeLambda.GetGenericArguments(); + if (genericArgs.Length == 0) + { + throw new Exception("Panic: Func<> or Action<> has zero generic arguments."); + } - var parameters = new Type[parameterCount]; - for (var j = 0; j < parameterCount; j++) - parameters[j] = genericArgs[i++]; + var i = 0; + Type declaring = isStatic ? typeof(void) : genericArgs[i++]; - var returned = isFunction ? genericArgs[i] : typeof(void); + var parameterCount = genericArgs.Length - (isStatic ? 0 : 1) - (isFunction ? 1 : 0); + if (parameterCount < 0) + { + throw new ArgumentException( + $"Lambda {typeLambda} is a Func and requires at least two arguments (declaring type and returned type).", + nameof(TLambda)); + } - return (declaring, parameters, returned); + var parameters = new Type[parameterCount]; + for (var j = 0; j < parameterCount; j++) + { + parameters[j] = genericArgs[i++]; } - private static (DynamicMethod, ILGenerator) CreateIlGenerator(Module? module, Type[] arguments, Type? returned) + Type returned = isFunction ? genericArgs[i] : typeof(void); + + return (declaring, parameters, returned); + } + + private static (DynamicMethod, ILGenerator) CreateIlGenerator(Module? module, Type[] arguments, Type? returned) + { + if (module == null) { - if (module == null) throw new ArgumentNullException(nameof(module)); - var dm = new DynamicMethod(string.Empty, returned, arguments, module, true); - return (dm, dm.GetILGenerator()); + throw new ArgumentNullException(nameof(module)); } - private static Type[] GetParameters(ConstructorInfo ctor) + var dm = new DynamicMethod(string.Empty, returned, arguments, module, true); + return (dm, dm.GetILGenerator()); + } + + private static Type[] GetParameters(ConstructorInfo ctor) + { + ParameterInfo[] parameters = ctor.GetParameters(); + var types = new Type[parameters.Length]; + var i = 0; + foreach (ParameterInfo parameter in parameters) { - var parameters = ctor.GetParameters(); - var types = new Type[parameters.Length]; - var i = 0; - foreach (var parameter in parameters) - types[i++] = parameter.ParameterType; - return types; + types[i++] = parameter.ParameterType; } - private static Type[] GetParameters(MethodInfo method, bool withDeclaring) + return types; + } + + private static Type[] GetParameters(MethodInfo method, bool withDeclaring) + { + ParameterInfo[] parameters = method.GetParameters(); + var types = new Type[parameters.Length + (withDeclaring ? 1 : 0)]; + var i = 0; + if (withDeclaring) { - var parameters = method.GetParameters(); - var types = new Type[parameters.Length + (withDeclaring ? 1 : 0)]; - var i = 0; - if (withDeclaring) - types[i++] = method.DeclaringType!; - foreach (var parameter in parameters) - types[i++] = parameter.ParameterType; - return types; + types[i++] = method.DeclaringType!; } - // emits args - private static void EmitLdargs(ILGenerator ilgen, Type[] lambdaArgTypes, Type[] methodArgTypes) + foreach (ParameterInfo parameter in parameters) { - var ldargOpCodes = new[] { OpCodes.Ldarg_0, OpCodes.Ldarg_1, OpCodes.Ldarg_2, OpCodes.Ldarg_3 }; + types[i++] = parameter.ParameterType; + } - if (lambdaArgTypes.Length != methodArgTypes.Length) - throw new Exception("Panic: inconsistent number of args."); + return types; + } - for (var i = 0; i < lambdaArgTypes.Length; i++) - { - if (lambdaArgTypes.Length < 5) - ilgen.Emit(ldargOpCodes[i]); - else - ilgen.Emit(OpCodes.Ldarg, i); + // emits args + private static void EmitLdargs(ILGenerator ilgen, Type[] lambdaArgTypes, Type[] methodArgTypes) + { + OpCode[] ldargOpCodes = new[] {OpCodes.Ldarg_0, OpCodes.Ldarg_1, OpCodes.Ldarg_2, OpCodes.Ldarg_3}; + + if (lambdaArgTypes.Length != methodArgTypes.Length) + { + throw new Exception("Panic: inconsistent number of args."); + } - //var local = false; - EmitInputAdapter(ilgen, lambdaArgTypes[i], methodArgTypes[i]/*, ref local*/); + for (var i = 0; i < lambdaArgTypes.Length; i++) + { + if (lambdaArgTypes.Length < 5) + { + ilgen.Emit(ldargOpCodes[i]); } + else + { + ilgen.Emit(OpCodes.Ldarg, i); + } + + //var local = false; + EmitInputAdapter(ilgen, lambdaArgTypes[i], methodArgTypes[i] /*, ref local*/); } + } - // emits adapter opcodes after OpCodes.Ldarg - // inputType is the lambda input type - // methodParamType is the actual type expected by the actual method - // adding code to do inputType -> methodParamType - // valueType -> valueType : not supported ('cos, why?) - // valueType -> !valueType : not supported ('cos, why?) - // !valueType -> valueType : unbox and convert - // !valueType -> !valueType : cast (could throw) - private static void EmitInputAdapter(ILGenerator ilgen, Type inputType, Type methodParamType /*, ref bool local*/) + // emits adapter opcodes after OpCodes.Ldarg + // inputType is the lambda input type + // methodParamType is the actual type expected by the actual method + // adding code to do inputType -> methodParamType + // valueType -> valueType : not supported ('cos, why?) + // valueType -> !valueType : not supported ('cos, why?) + // !valueType -> valueType : unbox and convert + // !valueType -> !valueType : cast (could throw) + private static void EmitInputAdapter(ILGenerator ilgen, Type inputType, Type methodParamType /*, ref bool local*/) + { + if (inputType == methodParamType) { - if (inputType == methodParamType) return; + return; + } - if (methodParamType.IsValueType) + if (methodParamType.IsValueType) + { + if (inputType.IsValueType) { - if (inputType.IsValueType) - { - // both input and parameter are value types - // not supported, use proper input - // (otherwise, would require converting) - throw new NotSupportedException("ValueTypes conversion."); - } + // both input and parameter are value types + // not supported, use proper input + // (otherwise, would require converting) + throw new NotSupportedException("ValueTypes conversion."); + } - // parameter is value type, but input is reference type - // unbox the input to the parameter value type - // this is more or less equivalent to the ToT method below + // parameter is value type, but input is reference type + // unbox the input to the parameter value type + // this is more or less equivalent to the ToT method below - var unbox = ilgen.DefineLabel(); + Label unbox = ilgen.DefineLabel(); - //if (!local) - //{ - // ilgen.DeclareLocal(typeof(object)); // declare local var for st/ld loc_0 - // local = true; - //} + //if (!local) + //{ + // ilgen.DeclareLocal(typeof(object)); // declare local var for st/ld loc_0 + // local = true; + //} - // stack: value + // stack: value - // following code can be replaced with .Dump (and then we don't need the local variable anymore) - //ilgen.Emit(OpCodes.Stloc_0); // pop value into loc.0 - //// stack: - //ilgen.Emit(OpCodes.Ldloc_0); // push loc.0 - //ilgen.Emit(OpCodes.Ldloc_0); // push loc.0 + // following code can be replaced with .Dump (and then we don't need the local variable anymore) + //ilgen.Emit(OpCodes.Stloc_0); // pop value into loc.0 + //// stack: + //ilgen.Emit(OpCodes.Ldloc_0); // push loc.0 + //ilgen.Emit(OpCodes.Ldloc_0); // push loc.0 - ilgen.Emit(OpCodes.Dup); // duplicate top of stack + ilgen.Emit(OpCodes.Dup); // duplicate top of stack - // stack: value ; value + // stack: value ; value - ilgen.Emit(OpCodes.Isinst, methodParamType); // test, pops value, and pushes either a null ref, or an instance of the type + ilgen.Emit(OpCodes.Isinst, + methodParamType); // test, pops value, and pushes either a null ref, or an instance of the type - // stack: inst|null ; value + // stack: inst|null ; value - ilgen.Emit(OpCodes.Ldnull); // push null + ilgen.Emit(OpCodes.Ldnull); // push null - // stack: null ; inst|null ; value + // stack: null ; inst|null ; value - ilgen.Emit(OpCodes.Cgt_Un); // compare what isInst returned to null - pops 2 values, and pushes 1 if greater else 0 + ilgen.Emit(OpCodes + .Cgt_Un); // compare what isInst returned to null - pops 2 values, and pushes 1 if greater else 0 - // stack: 0|1 ; value + // stack: 0|1 ; value - ilgen.Emit(OpCodes.Brtrue_S, unbox); // pops value, branches to unbox if true, ie nonzero + ilgen.Emit(OpCodes.Brtrue_S, unbox); // pops value, branches to unbox if true, ie nonzero - // stack: value + // stack: value - ilgen.Convert(methodParamType); // convert + ilgen.Convert(methodParamType); // convert - // stack: value|converted + // stack: value|converted - ilgen.MarkLabel(unbox); - ilgen.Emit(OpCodes.Unbox_Any, methodParamType); - } - else + ilgen.MarkLabel(unbox); + ilgen.Emit(OpCodes.Unbox_Any, methodParamType); + } + else + { + // parameter is reference type, but input is value type + // not supported, input should always be less constrained + // (otherwise, would require boxing and converting) + if (inputType.IsValueType) { - // parameter is reference type, but input is value type - // not supported, input should always be less constrained - // (otherwise, would require boxing and converting) - if (inputType.IsValueType) - throw new NotSupportedException("ValueType boxing."); - - // both input and parameter are reference types - // cast the input to the parameter type - ilgen.Emit(OpCodes.Castclass, methodParamType); + throw new NotSupportedException("ValueType boxing."); } + + // both input and parameter are reference types + // cast the input to the parameter type + ilgen.Emit(OpCodes.Castclass, methodParamType); } + } - //private static T ToT(object o) - //{ - // return o is T t ? t : (T) System.Convert.ChangeType(o, typeof(T)); - //} + //private static T ToT(object o) + //{ + // return o is T t ? t : (T) System.Convert.ChangeType(o, typeof(T)); + //} - private static MethodInfo? _convertMethod; - private static MethodInfo? _getTypeFromHandle; + private static MethodInfo? _convertMethod; + private static MethodInfo? _getTypeFromHandle; - private static void Convert(this ILGenerator ilgen, Type type) + private static void Convert(this ILGenerator ilgen, Type type) + { + if (_getTypeFromHandle == null) { + _getTypeFromHandle = typeof(Type).GetMethod("GetTypeFromHandle", BindingFlags.Public | BindingFlags.Static, + null, new[] {typeof(RuntimeTypeHandle)}, null); + } - if (_getTypeFromHandle == null) - _getTypeFromHandle = typeof(Type).GetMethod("GetTypeFromHandle", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(RuntimeTypeHandle) }, null); - - if (_convertMethod == null) - _convertMethod = typeof(Convert).GetMethod("ChangeType", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(object), typeof(Type) }, null); - - ilgen.Emit(OpCodes.Ldtoken, type); - ilgen.CallMethod(_getTypeFromHandle); - ilgen.CallMethod(_convertMethod); + if (_convertMethod == null) + { + _convertMethod = typeof(Convert).GetMethod("ChangeType", BindingFlags.Public | BindingFlags.Static, null, + new[] {typeof(object), typeof(Type)}, null); } - // emits adapter code before OpCodes.Ret - // outputType is the lambda output type - // methodReturnedType is the actual type returned by the actual method - // adding code to do methodReturnedType -> outputType - // valueType -> valueType : not supported ('cos, why?) - // valueType -> !valueType : box - // !valueType -> valueType : not supported ('cos, why?) - // !valueType -> !valueType : implicit cast (could throw) - private static void EmitOutputAdapter(ILGenerator ilgen, Type outputType, Type methodReturnedType) + ilgen.Emit(OpCodes.Ldtoken, type); + ilgen.CallMethod(_getTypeFromHandle); + ilgen.CallMethod(_convertMethod); + } + + // emits adapter code before OpCodes.Ret + // outputType is the lambda output type + // methodReturnedType is the actual type returned by the actual method + // adding code to do methodReturnedType -> outputType + // valueType -> valueType : not supported ('cos, why?) + // valueType -> !valueType : box + // !valueType -> valueType : not supported ('cos, why?) + // !valueType -> !valueType : implicit cast (could throw) + private static void EmitOutputAdapter(ILGenerator ilgen, Type outputType, Type methodReturnedType) + { + if (outputType == methodReturnedType) { - if (outputType == methodReturnedType) return; + return; + } - // note: the only important thing to support here, is returning a specific type - // as an object, when emitting the method as a Func<..., object> - anything else - // is pointless really - so we box value types, and ensure that non value types - // can be assigned + // note: the only important thing to support here, is returning a specific type + // as an object, when emitting the method as a Func<..., object> - anything else + // is pointless really - so we box value types, and ensure that non value types + // can be assigned - if (methodReturnedType.IsValueType) - { - if (outputType.IsValueType) - { - // both returned and output are value types - // not supported, use proper output - // (otherwise, would require converting) - throw new NotSupportedException("ValueTypes conversion."); - } - - // returned is value type, but output is reference type - // box the returned value - ilgen.Emit(OpCodes.Box, methodReturnedType); - } - else + if (methodReturnedType.IsValueType) + { + if (outputType.IsValueType) { - // returned is reference type, but output is value type - // not supported, output should always be less constrained - // (otherwise, would require boxing and converting) - if (outputType.IsValueType) - throw new NotSupportedException("ValueType boxing."); - - // both output and returned are reference types - // as long as returned can be assigned to output, good - if (!outputType.IsAssignableFrom(methodReturnedType)) - throw new NotSupportedException("Invalid cast."); + // both returned and output are value types + // not supported, use proper output + // (otherwise, would require converting) + throw new NotSupportedException("ValueTypes conversion."); } - } - private static void ThrowInvalidLambda(string methodName, Type? returned, Type[] args) - { - throw new ArgumentException($"Lambda {typeof(TLambda)} does not match {methodName}({string.Join(", ", (IEnumerable) args)}):{returned}.", nameof(TLambda)); + // returned is value type, but output is reference type + // box the returned value + ilgen.Emit(OpCodes.Box, methodReturnedType); } - - private static void CallMethod(this ILGenerator ilgen, MethodInfo? method) + else { - if (method is not null) + // returned is reference type, but output is value type + // not supported, output should always be less constrained + // (otherwise, would require boxing and converting) + if (outputType.IsValueType) { - var virt = !method.IsStatic && (method.IsVirtual || !method.IsFinal); - ilgen.Emit(virt ? OpCodes.Callvirt : OpCodes.Call, method); + throw new NotSupportedException("ValueType boxing."); + } + + // both output and returned are reference types + // as long as returned can be assigned to output, good + if (!outputType.IsAssignableFrom(methodReturnedType)) + { + throw new NotSupportedException("Invalid cast."); } } + } + + private static void ThrowInvalidLambda(string methodName, Type? returned, Type[] args) => + throw new ArgumentException( + $"Lambda {typeof(TLambda)} does not match {methodName}({string.Join(", ", (IEnumerable)args)}):{returned}.", + nameof(TLambda)); - private static void Return(this ILGenerator ilgen) + private static void CallMethod(this ILGenerator ilgen, MethodInfo? method) + { + if (method is not null) { - ilgen.Emit(OpCodes.Ret); + var virt = !method.IsStatic && (method.IsVirtual || !method.IsFinal); + ilgen.Emit(virt ? OpCodes.Callvirt : OpCodes.Call, method); } - - #endregion } + + private static void Return(this ILGenerator ilgen) => ilgen.Emit(OpCodes.Ret); + + #endregion } diff --git a/src/Umbraco.Core/Routing/AliasUrlProvider.cs b/src/Umbraco.Core/Routing/AliasUrlProvider.cs index 21fb3e9832a2..a70add833cfd 100644 --- a/src/Umbraco.Core/Routing/AliasUrlProvider.cs +++ b/src/Umbraco.Core/Routing/AliasUrlProvider.cs @@ -1,149 +1,166 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Web; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// Provides URLs using the umbracoUrlAlias property. +/// +public class AliasUrlProvider : IUrlProvider { + private readonly IPublishedValueFallback _publishedValueFallback; + private readonly ISiteDomainMapper _siteDomainMapper; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly UriUtility _uriUtility; + private RequestHandlerSettings _requestConfig; + + public AliasUrlProvider(IOptionsMonitor requestConfig, ISiteDomainMapper siteDomainMapper, + UriUtility uriUtility, IPublishedValueFallback publishedValueFallback, + IUmbracoContextAccessor umbracoContextAccessor) + { + _requestConfig = requestConfig.CurrentValue; + _siteDomainMapper = siteDomainMapper; + _uriUtility = uriUtility; + _publishedValueFallback = publishedValueFallback; + _umbracoContextAccessor = umbracoContextAccessor; + + requestConfig.OnChange(x => _requestConfig = x); + } + + // note - at the moment we seem to accept pretty much anything as an alias + // without any form of validation ... could even prob. kill the XPath ... + // ok, this is somewhat experimental and is NOT enabled by default + + #region GetUrl + + /// + public UrlInfo? GetUrl(IPublishedContent content, UrlMode mode, string? culture, Uri current) => + null; // we have nothing to say + + #endregion + + #region GetOtherUrls + /// - /// Provides URLs using the umbracoUrlAlias property. + /// Gets the other URLs of a published content. /// - public class AliasUrlProvider : IUrlProvider + /// The Umbraco context. + /// The published content id. + /// The current absolute URL. + /// The other URLs for the published content. + /// + /// + /// Other URLs are those that GetUrl would not return in the current context, but would be valid + /// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...). + /// + /// + public IEnumerable GetOtherUrls(int id, Uri current) { - private RequestHandlerSettings _requestConfig; - private readonly ISiteDomainMapper _siteDomainMapper; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly UriUtility _uriUtility; - private readonly IPublishedValueFallback _publishedValueFallback; - - public AliasUrlProvider(IOptionsMonitor requestConfig, ISiteDomainMapper siteDomainMapper, UriUtility uriUtility, IPublishedValueFallback publishedValueFallback, IUmbracoContextAccessor umbracoContextAccessor) + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + IPublishedContent node = umbracoContext.Content?.GetById(id); + if (node == null) { - _requestConfig = requestConfig.CurrentValue; - _siteDomainMapper = siteDomainMapper; - _uriUtility = uriUtility; - _publishedValueFallback = publishedValueFallback; - _umbracoContextAccessor = umbracoContextAccessor; - - requestConfig.OnChange(x => _requestConfig = x); + yield break; } - // note - at the moment we seem to accept pretty much anything as an alias - // without any form of validation ... could even prob. kill the XPath ... - // ok, this is somewhat experimental and is NOT enabled by default - - #region GetUrl + if (!node.HasProperty(Constants.Conventions.Content.UrlAlias)) + { + yield break; + } - /// - public UrlInfo? GetUrl(IPublishedContent content, UrlMode mode, string? culture, Uri current) + // look for domains, walking up the tree + IPublishedContent n = node; + IEnumerable domainUris = DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, + _siteDomainMapper, n.Id, current, false); + while (domainUris == null && n != null) // n is null at root { - return null; // we have nothing to say + // move to parent node + n = n.Parent; + domainUris = n == null + ? null + : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, + current, false); } - #endregion - - #region GetOtherUrls - - /// - /// Gets the other URLs of a published content. - /// - /// The Umbraco context. - /// The published content id. - /// The current absolute URL. - /// The other URLs for the published content. - /// - /// Other URLs are those that GetUrl would not return in the current context, but would be valid - /// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...). - /// - public IEnumerable GetOtherUrls(int id, Uri current) + // determine whether the alias property varies + var varies = node.GetProperty(Constants.Conventions.Content.UrlAlias)!.PropertyType.VariesByCulture(); + + if (domainUris == null) { - var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - var node = umbracoContext.Content?.GetById(id); - if (node == null) + // no domain + // if the property is invariant, then URL "/" is ok + // if the property varies, then what are we supposed to do? + // the content finder may work, depending on the 'current' culture, + // but there's no way we can return something meaningful here + if (varies) + { yield break; + } - if (!node.HasProperty(Constants.Conventions.Content.UrlAlias)) + var umbracoUrlName = node.Value(_publishedValueFallback, Constants.Conventions.Content.UrlAlias); + var aliases = umbracoUrlName?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); + + if (aliases == null || aliases.Any() == false) + { yield break; + } - // look for domains, walking up the tree - var n = node; - var domainUris = DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, current, false); - while (domainUris == null && n != null) // n is null at root + foreach (var alias in aliases.Distinct()) { - // move to parent node - n = n.Parent; - domainUris = n == null ? null : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, current, excludeDefault: false); + var path = "/" + alias; + var uri = new Uri(path, UriKind.Relative); + yield return UrlInfo.Url(_uriUtility.UriFromUmbraco(uri, _requestConfig).ToString()); } + } + else + { + // some domains: one URL per domain, which is "/" + foreach (DomainAndUri domainUri in domainUris) + { + // if the property is invariant, get the invariant value, URL is "/" + // if the property varies, get the variant value, URL is "/" + + // but! only if the culture is published, else ignore + if (varies && !node.HasCulture(domainUri.Culture)) + { + continue; + } - // determine whether the alias property varies - var varies = node.GetProperty(Constants.Conventions.Content.UrlAlias)!.PropertyType.VariesByCulture(); + var umbracoUrlName = varies + ? node.Value(_publishedValueFallback, Constants.Conventions.Content.UrlAlias, + domainUri.Culture) + : node.Value(_publishedValueFallback, Constants.Conventions.Content.UrlAlias); - if (domainUris == null) - { - // no domain - // if the property is invariant, then URL "/" is ok - // if the property varies, then what are we supposed to do? - // the content finder may work, depending on the 'current' culture, - // but there's no way we can return something meaningful here - if (varies) - yield break; - - var umbracoUrlName = node.Value(_publishedValueFallback, Constants.Conventions.Content.UrlAlias); var aliases = umbracoUrlName?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); if (aliases == null || aliases.Any() == false) - yield break; + { + continue; + } foreach (var alias in aliases.Distinct()) { var path = "/" + alias; - var uri = new Uri(path, UriKind.Relative); - yield return UrlInfo.Url(_uriUtility.UriFromUmbraco(uri, _requestConfig).ToString()); - } - } - else - { - // some domains: one URL per domain, which is "/" - foreach (var domainUri in domainUris) - { - // if the property is invariant, get the invariant value, URL is "/" - // if the property varies, get the variant value, URL is "/" - - // but! only if the culture is published, else ignore - if (varies && !node.HasCulture(domainUri.Culture)) continue; - - var umbracoUrlName = varies - ? node.Value(_publishedValueFallback,Constants.Conventions.Content.UrlAlias, culture: domainUri.Culture) - : node.Value(_publishedValueFallback, Constants.Conventions.Content.UrlAlias); - - var aliases = umbracoUrlName?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); - - if (aliases == null || aliases.Any() == false) - continue; - - foreach(var alias in aliases.Distinct()) - { - var path = "/" + alias; - var uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)); - yield return UrlInfo.Url(_uriUtility.UriFromUmbraco(uri, _requestConfig).ToString(), domainUri.Culture); - } + var uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)); + yield return UrlInfo.Url(_uriUtility.UriFromUmbraco(uri, _requestConfig).ToString(), + domainUri.Culture); } } } + } - #endregion - - #region Utilities + #endregion - string CombinePaths(string path1, string path2) - { - string path = path1.TrimEnd(Constants.CharArrays.ForwardSlash) + path2; - return path == "/" ? path : path.TrimEnd(Constants.CharArrays.ForwardSlash); - } + #region Utilities - #endregion + private string CombinePaths(string path1, string path2) + { + var path = path1.TrimEnd(Constants.CharArrays.ForwardSlash) + path2; + return path == "/" ? path : path.TrimEnd(Constants.CharArrays.ForwardSlash); } + + #endregion } diff --git a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs index 380d7459edfc..e5bdd68dec00 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs @@ -1,116 +1,118 @@ using System.Globalization; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Web; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// Provides an implementation of that handles page identifiers. +/// +/// +/// Handles /1234 where 1234 is the identified of a document. +/// +public class ContentFinderByIdPath : IContentFinder { + private readonly ILogger _logger; + private readonly IRequestAccessor _requestAccessor; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private WebRoutingSettings _webRoutingSettings; + /// - /// Provides an implementation of that handles page identifiers. + /// Initializes a new instance of the class. /// - /// - /// Handles /1234 where 1234 is the identified of a document. - /// - public class ContentFinderByIdPath : IContentFinder + public ContentFinderByIdPath( + IOptionsMonitor webRoutingSettings, + ILogger logger, + IRequestAccessor requestAccessor, + IUmbracoContextAccessor umbracoContextAccessor) { - private readonly ILogger _logger; - private readonly IRequestAccessor _requestAccessor; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private WebRoutingSettings _webRoutingSettings; - - /// - /// Initializes a new instance of the class. - /// - public ContentFinderByIdPath( - IOptionsMonitor webRoutingSettings, - ILogger logger, - IRequestAccessor requestAccessor, - IUmbracoContextAccessor umbracoContextAccessor) - { - _webRoutingSettings = webRoutingSettings.CurrentValue ?? throw new System.ArgumentNullException(nameof(webRoutingSettings)); - _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); - _requestAccessor = requestAccessor ?? throw new System.ArgumentNullException(nameof(requestAccessor)); - _umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor)); + _webRoutingSettings = webRoutingSettings.CurrentValue ?? + throw new ArgumentNullException(nameof(webRoutingSettings)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _requestAccessor = requestAccessor ?? throw new ArgumentNullException(nameof(requestAccessor)); + _umbracoContextAccessor = + umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + + webRoutingSettings.OnChange(x => _webRoutingSettings = x); + } - webRoutingSettings.OnChange(x => _webRoutingSettings = x); + /// + /// Tries to find and assign an Umbraco document to a PublishedRequest. + /// + /// The PublishedRequest. + /// A value indicating whether an Umbraco document was found and assigned. + public async Task TryFindContent(IPublishedRequestBuilder frequest) + { + if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext umbracoContext)) + { + return false; } - /// - /// Tries to find and assign an Umbraco document to a PublishedRequest. - /// - /// The PublishedRequest. - /// A value indicating whether an Umbraco document was found and assigned. - public async Task TryFindContent(IPublishedRequestBuilder frequest) + if (umbracoContext == null || (umbracoContext != null && umbracoContext.InPreviewMode == false && + _webRoutingSettings.DisableFindContentByIdPath)) { - if(!_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) - { - return false; - } - if (umbracoContext == null || (umbracoContext != null && umbracoContext.InPreviewMode == false && _webRoutingSettings.DisableFindContentByIdPath)) - { - return false; - } + return false; + } - IPublishedContent? node = null; - var path = frequest.AbsolutePathDecoded; + IPublishedContent? node = null; + var path = frequest.AbsolutePathDecoded; - var nodeId = -1; + var nodeId = -1; - // no id if "/" - if (path != "/") + // no id if "/" + if (path != "/") + { + var noSlashPath = path.Substring(1); + + if (int.TryParse(noSlashPath, NumberStyles.Integer, CultureInfo.InvariantCulture, out nodeId) == false) { - var noSlashPath = path.Substring(1); + nodeId = -1; + } - if (int.TryParse(noSlashPath, NumberStyles.Integer, CultureInfo.InvariantCulture, out nodeId) == false) + if (nodeId > 0) + { + if (_logger.IsEnabled(LogLevel.Debug)) { - nodeId = -1; + _logger.LogDebug("Id={NodeId}", nodeId); } - if (nodeId > 0) + node = umbracoContext?.Content?.GetById(nodeId); + + if (node != null) { - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Id={NodeId}", nodeId); - } - node = umbracoContext?.Content?.GetById(nodeId); + var cultureFromQuerystring = _requestAccessor.GetQueryStringValue("culture"); - if (node != null) + // if we have a node, check if we have a culture in the query string + if (!string.IsNullOrEmpty(cultureFromQuerystring)) { - - var cultureFromQuerystring = _requestAccessor.GetQueryStringValue("culture"); - - // if we have a node, check if we have a culture in the query string - if (!string.IsNullOrEmpty(cultureFromQuerystring)) - { - // we're assuming it will match a culture, if an invalid one is passed in, an exception will throw (there is no TryGetCultureInfo method), i think this is ok though - frequest.SetCulture(cultureFromQuerystring); - } - - frequest.SetPublishedContent(node); - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Found node with id={PublishedContentId}", node.Id); - } + // we're assuming it will match a culture, if an invalid one is passed in, an exception will throw (there is no TryGetCultureInfo method), i think this is ok though + frequest.SetCulture(cultureFromQuerystring); } - else + + frequest.SetPublishedContent(node); + if (_logger.IsEnabled(LogLevel.Debug)) { - nodeId = -1; // trigger message below + _logger.LogDebug("Found node with id={PublishedContentId}", node.Id); } } - } - - if (nodeId == -1) - { - if (_logger.IsEnabled(LogLevel.Debug)) + else { - _logger.LogDebug("Not a node id"); + nodeId = -1; // trigger message below } } + } - return node != null; + if (nodeId == -1) + { + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Not a node id"); + } } + + return node != null; } } diff --git a/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs b/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs index 646d091ebb68..e8e595e2c9b6 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs @@ -1,51 +1,51 @@ using System.Globalization; -using System.Threading.Tasks; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Web; -using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// This looks up a document by checking for the umbPageId of a request/query string +/// +/// +/// This is used by library.RenderTemplate and also some of the macro rendering functionality like in +/// macroResultWrapper.aspx +/// +public class ContentFinderByPageIdQuery : IContentFinder { + private readonly IRequestAccessor _requestAccessor; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + /// - /// This looks up a document by checking for the umbPageId of a request/query string + /// Initializes a new instance of the class. /// - /// - /// This is used by library.RenderTemplate and also some of the macro rendering functionality like in - /// macroResultWrapper.aspx - /// - public class ContentFinderByPageIdQuery : IContentFinder + public ContentFinderByPageIdQuery(IRequestAccessor requestAccessor, IUmbracoContextAccessor umbracoContextAccessor) { - private readonly IRequestAccessor _requestAccessor; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; + _requestAccessor = requestAccessor ?? throw new ArgumentNullException(nameof(requestAccessor)); + _umbracoContextAccessor = + umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + } - /// - /// Initializes a new instance of the class. - /// - public ContentFinderByPageIdQuery(IRequestAccessor requestAccessor, IUmbracoContextAccessor umbracoContextAccessor) + /// + public async Task TryFindContent(IPublishedRequestBuilder frequest) + { + if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext umbracoContext)) { - _requestAccessor = requestAccessor ?? throw new System.ArgumentNullException(nameof(requestAccessor)); - _umbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor)); + return false; } - /// - public async Task TryFindContent(IPublishedRequestBuilder frequest) + if (int.TryParse(_requestAccessor.GetRequestValue("umbPageID"), NumberStyles.Integer, + CultureInfo.InvariantCulture, out var pageId)) { - if(!_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) - { - return false; - } - if (int.TryParse(_requestAccessor.GetRequestValue("umbPageID"), NumberStyles.Integer, CultureInfo.InvariantCulture, out int pageId)) - { - IPublishedContent? doc = umbracoContext.Content?.GetById(pageId); + IPublishedContent? doc = umbracoContext.Content?.GetById(pageId); - if (doc != null) - { - frequest.SetPublishedContent(doc); - return true; - } + if (doc != null) + { + frequest.SetPublishedContent(doc); + return true; } - - return false; } + + return false; } } diff --git a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs index a200afec6702..1805b7e0f9fa 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; @@ -7,95 +5,102 @@ using Umbraco.Cms.Core.Web; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// Provides an implementation of that handles page URL rewrites +/// that are stored when moving, saving, or deleting a node. +/// +/// +/// Assigns a permanent redirect notification to the request. +/// +public class ContentFinderByRedirectUrl : IContentFinder { + private readonly ILogger _logger; + private readonly IPublishedUrlProvider _publishedUrlProvider; + private readonly IRedirectUrlService _redirectUrlService; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + + /// + /// Initializes a new instance of the class. + /// + public ContentFinderByRedirectUrl( + IRedirectUrlService redirectUrlService, + ILogger logger, + IPublishedUrlProvider publishedUrlProvider, + IUmbracoContextAccessor umbracoContextAccessor) + { + _redirectUrlService = redirectUrlService; + _logger = logger; + _publishedUrlProvider = publishedUrlProvider; + _umbracoContextAccessor = umbracoContextAccessor; + } + /// - /// Provides an implementation of that handles page URL rewrites - /// that are stored when moving, saving, or deleting a node. + /// Tries to find and assign an Umbraco document to a PublishedRequest. /// + /// The PublishedRequest. + /// A value indicating whether an Umbraco document was found and assigned. /// - /// Assigns a permanent redirect notification to the request. + /// Optionally, can also assign the template or anything else on the document request, although that is not + /// required. /// - public class ContentFinderByRedirectUrl : IContentFinder + public async Task TryFindContent(IPublishedRequestBuilder frequest) { - private readonly IRedirectUrlService _redirectUrlService; - private readonly ILogger _logger; - private readonly IPublishedUrlProvider _publishedUrlProvider; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - - /// - /// Initializes a new instance of the class. - /// - public ContentFinderByRedirectUrl( - IRedirectUrlService redirectUrlService, - ILogger logger, - IPublishedUrlProvider publishedUrlProvider, - IUmbracoContextAccessor umbracoContextAccessor) + if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext umbracoContext)) { - _redirectUrlService = redirectUrlService; - _logger = logger; - _publishedUrlProvider = publishedUrlProvider; - _umbracoContextAccessor = umbracoContextAccessor; + return false; } - /// - /// Tries to find and assign an Umbraco document to a PublishedRequest. - /// - /// The PublishedRequest. - /// A value indicating whether an Umbraco document was found and assigned. - /// Optionally, can also assign the template or anything else on the document request, although that is not required. - public async Task TryFindContent(IPublishedRequestBuilder frequest) - { - if (!_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) - { - return false; - } - - var route = frequest.Domain != null - ? frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.AbsolutePathDecoded) - : frequest.AbsolutePathDecoded; + var route = frequest.Domain != null + ? frequest.Domain.ContentId + + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.AbsolutePathDecoded) + : frequest.AbsolutePathDecoded; - IRedirectUrl? redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(route, frequest.Culture); + IRedirectUrl? redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(route, frequest.Culture); - if (redirectUrl == null) + if (redirectUrl == null) + { + if (_logger.IsEnabled(LogLevel.Debug)) { - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("No match for route: {Route}", route); - } - return false; + _logger.LogDebug("No match for route: {Route}", route); } - IPublishedContent? content = umbracoContext.Content?.GetById(redirectUrl.ContentId); - var url = content == null ? "#" : content.Url(_publishedUrlProvider, redirectUrl.Culture); - if (url.StartsWith("#")) - { - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Route {Route} matches content {ContentId} which has no URL.", route, redirectUrl.ContentId); - } - return false; - } + return false; + } - // Appending any querystring from the incoming request to the redirect URL - url = string.IsNullOrEmpty(frequest.Uri.Query) ? url : url + frequest.Uri.Query; + IPublishedContent? content = umbracoContext.Content?.GetById(redirectUrl.ContentId); + var url = content == null ? "#" : content.Url(_publishedUrlProvider, redirectUrl.Culture); + if (url.StartsWith("#")) + { if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("Route {Route} matches content {ContentId} with URL '{Url}', redirecting.", route, content?.Id, url); + _logger.LogDebug("Route {Route} matches content {ContentId} which has no URL.", route, + redirectUrl.ContentId); } - frequest - .SetRedirectPermanent(url) - - // From: http://stackoverflow.com/a/22468386/5018 - // See http://issues.umbraco.org/issue/U4-8361#comment=67-30532 - // Setting automatic 301 redirects to not be cached because browsers cache these very aggressively which then leads - // to problems if you rename a page back to it's original name or create a new page with the original name - .SetNoCacheHeader(true) - .SetCacheExtensions(new List { "no-store, must-revalidate" }) - .SetHeaders(new Dictionary { { "Pragma", "no-cache" }, { "Expires", "0" } }); + return false; + } - return true; + // Appending any querystring from the incoming request to the redirect URL + url = string.IsNullOrEmpty(frequest.Uri.Query) ? url : url + frequest.Uri.Query; + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Route {Route} matches content {ContentId} with URL '{Url}', redirecting.", route, + content?.Id, url); } + + frequest + .SetRedirectPermanent(url) + + // From: http://stackoverflow.com/a/22468386/5018 + // See http://issues.umbraco.org/issue/U4-8361#comment=67-30532 + // Setting automatic 301 redirects to not be cached because browsers cache these very aggressively which then leads + // to problems if you rename a page back to it's original name or create a new page with the original name + .SetNoCacheHeader(true) + .SetCacheExtensions(new List {"no-store, must-revalidate"}) + .SetHeaders(new Dictionary {{"Pragma", "no-cache"}, {"Expires", "0"}}); + + return true; } } diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrl.cs b/src/Umbraco.Core/Routing/ContentFinderByUrl.cs index e95a0362158d..e408b1853fa1 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrl.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrl.cs @@ -1,98 +1,100 @@ -using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Web; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// Provides an implementation of that handles page nice URLs. +/// +/// +/// Handles /foo/bar where /foo/bar is the nice URL of a document. +/// +public class ContentFinderByUrl : IContentFinder { + private readonly ILogger _logger; + /// - /// Provides an implementation of that handles page nice URLs. + /// Initializes a new instance of the class. /// - /// - /// Handles /foo/bar where /foo/bar is the nice URL of a document. - /// - public class ContentFinderByUrl : IContentFinder + public ContentFinderByUrl(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor) { - private readonly ILogger _logger; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + UmbracoContextAccessor = + umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + } + + /// + /// Gets the + /// + protected IUmbracoContextAccessor UmbracoContextAccessor { get; } - /// - /// Initializes a new instance of the class. - /// - public ContentFinderByUrl(ILogger logger, IUmbracoContextAccessor umbracoContextAccessor) + /// + /// Tries to find and assign an Umbraco document to a PublishedRequest. + /// + /// The PublishedRequest. + /// A value indicating whether an Umbraco document was found and assigned. + public virtual async Task TryFindContent(IPublishedRequestBuilder frequest) + { + if (!UmbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext umbracoContext)) { - _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); - UmbracoContextAccessor = umbracoContextAccessor ?? throw new System.ArgumentNullException(nameof(umbracoContextAccessor)); + return false; } - /// - /// Gets the - /// - protected IUmbracoContextAccessor UmbracoContextAccessor { get; } - - /// - /// Tries to find and assign an Umbraco document to a PublishedRequest. - /// - /// The PublishedRequest. - /// A value indicating whether an Umbraco document was found and assigned. - public virtual async Task TryFindContent(IPublishedRequestBuilder frequest) + string route; + if (frequest.Domain != null) { - if (!UmbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) - { - return false; - } + route = frequest.Domain.ContentId + + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.AbsolutePathDecoded); + } + else + { + route = frequest.AbsolutePathDecoded; + } - string route; - if (frequest.Domain != null) - { - route = frequest.Domain.ContentId + DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, frequest.AbsolutePathDecoded); - } - else - { - route = frequest.AbsolutePathDecoded; - } + IPublishedContent? node = FindContent(frequest, route); + return node != null; + } - IPublishedContent? node = FindContent(frequest, route); - return node != null; + /// + /// Tries to find an Umbraco document for a PublishedRequest and a route. + /// + /// The document node, or null. + protected IPublishedContent? FindContent(IPublishedRequestBuilder docreq, string route) + { + if (!UmbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext umbracoContext)) + { + return null; } - /// - /// Tries to find an Umbraco document for a PublishedRequest and a route. - /// - /// The document node, or null. - protected IPublishedContent? FindContent(IPublishedRequestBuilder docreq, string route) + if (docreq == null) { - if (!UmbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) - { - return null; - } + throw new ArgumentNullException(nameof(docreq)); + } - if (docreq == null) - { - throw new System.ArgumentNullException(nameof(docreq)); - } - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Test route {Route}", route); - } + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Test route {Route}", route); + } - IPublishedContent? node = umbracoContext.Content?.GetByRoute(umbracoContext.InPreviewMode, route, culture: docreq.Culture); - if (node != null) + IPublishedContent? node = + umbracoContext.Content?.GetByRoute(umbracoContext.InPreviewMode, route, culture: docreq.Culture); + if (node != null) + { + docreq.SetPublishedContent(node); + if (_logger.IsEnabled(LogLevel.Debug)) { - docreq.SetPublishedContent(node); - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Got content, id={NodeId}", node.Id); - } + _logger.LogDebug("Got content, id={NodeId}", node.Id); } - else + } + else + { + if (_logger.IsEnabled(LogLevel.Debug)) { - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("No match."); - } + _logger.LogDebug("No match."); } - - return node; } + + return node; } } diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs b/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs index 5a8f6e16fe8a..88b8e0088cfb 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs @@ -1,157 +1,160 @@ -using System; -using System.Linq; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Web; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// Provides an implementation of that handles page aliases. +/// +/// +/// +/// Handles /just/about/anything where /just/about/anything is contained in the +/// umbracoUrlAlias property of a document. +/// +/// The alias is the full path to the document. There can be more than one alias, separated by commas. +/// +public class ContentFinderByUrlAlias : IContentFinder { + private readonly ILogger _logger; + private readonly IPublishedValueFallback _publishedValueFallback; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IVariationContextAccessor _variationContextAccessor; + + /// + /// Initializes a new instance of the class. + /// + public ContentFinderByUrlAlias( + ILogger logger, + IPublishedValueFallback publishedValueFallback, + IVariationContextAccessor variationContextAccessor, + IUmbracoContextAccessor umbracoContextAccessor) + { + _publishedValueFallback = publishedValueFallback; + _variationContextAccessor = variationContextAccessor; + _umbracoContextAccessor = umbracoContextAccessor; + _logger = logger; + } + /// - /// Provides an implementation of that handles page aliases. + /// Tries to find and assign an Umbraco document to a PublishedRequest. /// - /// - /// Handles /just/about/anything where /just/about/anything is contained in the umbracoUrlAlias property of a document. - /// The alias is the full path to the document. There can be more than one alias, separated by commas. - /// - public class ContentFinderByUrlAlias : IContentFinder + /// The PublishedRequest. + /// A value indicating whether an Umbraco document was found and assigned. + public async Task TryFindContent(IPublishedRequestBuilder frequest) { - private readonly IPublishedValueFallback _publishedValueFallback; - private readonly IVariationContextAccessor _variationContextAccessor; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly ILogger _logger; - - /// - /// Initializes a new instance of the class. - /// - public ContentFinderByUrlAlias( - ILogger logger, - IPublishedValueFallback publishedValueFallback, - IVariationContextAccessor variationContextAccessor, - IUmbracoContextAccessor umbracoContextAccessor) + if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext umbracoContext)) { - _publishedValueFallback = publishedValueFallback; - _variationContextAccessor = variationContextAccessor; - _umbracoContextAccessor = umbracoContextAccessor; - _logger = logger; + return false; } - /// - /// Tries to find and assign an Umbraco document to a PublishedRequest. - /// - /// The PublishedRequest. - /// A value indicating whether an Umbraco document was found and assigned. - public async Task TryFindContent(IPublishedRequestBuilder frequest) + IPublishedContent? node = null; + + // no alias if "/" + if (frequest.Uri.AbsolutePath != "/") { - if (!_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) - { - return false; - } - IPublishedContent? node = null; + node = FindContentByAlias( + umbracoContext!.Content, + frequest.Domain != null ? frequest.Domain.ContentId : 0, + frequest.Culture, + frequest.AbsolutePathDecoded); - // no alias if "/" - if (frequest.Uri.AbsolutePath != "/") + if (node != null) { - node = FindContentByAlias( - umbracoContext!.Content, - frequest.Domain != null ? frequest.Domain.ContentId : 0, - frequest.Culture, - frequest.AbsolutePathDecoded); - - if (node != null) + frequest.SetPublishedContent(node); + if (_logger.IsEnabled(LogLevel.Debug)) { - frequest.SetPublishedContent(node); - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Path '{UriAbsolutePath}' is an alias for id={PublishedContentId}", frequest.Uri.AbsolutePath, node.Id); - } + _logger.LogDebug("Path '{UriAbsolutePath}' is an alias for id={PublishedContentId}", + frequest.Uri.AbsolutePath, node.Id); } } - - return node != null; } - private IPublishedContent? FindContentByAlias(IPublishedContentCache? cache, int rootNodeId, string? culture, string alias) + return node != null; + } + + private IPublishedContent? FindContentByAlias(IPublishedContentCache? cache, int rootNodeId, string? culture, + string alias) + { + if (alias == null) { - if (alias == null) - { - throw new ArgumentNullException(nameof(alias)); - } + throw new ArgumentNullException(nameof(alias)); + } - // the alias may be "foo/bar" or "/foo/bar" - // there may be spaces as in "/foo/bar, /foo/nil" - // these should probably be taken care of earlier on + // the alias may be "foo/bar" or "/foo/bar" + // there may be spaces as in "/foo/bar, /foo/nil" + // these should probably be taken care of earlier on - // TODO: can we normalize the values so that they contain no whitespaces, and no leading slashes? - // and then the comparisons in IsMatch can be way faster - and allocate way less strings - const string propertyAlias = Constants.Conventions.Content.UrlAlias; + // TODO: can we normalize the values so that they contain no whitespaces, and no leading slashes? + // and then the comparisons in IsMatch can be way faster - and allocate way less strings + const string propertyAlias = Constants.Conventions.Content.UrlAlias; - var test1 = alias.TrimStart(Constants.CharArrays.ForwardSlash) + ","; - var test2 = ",/" + test1; // test2 is ",/alias," - test1 = "," + test1; // test1 is ",alias," + var test1 = alias.TrimStart(Constants.CharArrays.ForwardSlash) + ","; + var test2 = ",/" + test1; // test2 is ",/alias," + test1 = "," + test1; // test1 is ",alias," - bool IsMatch(IPublishedContent c, string a1, string a2) + bool IsMatch(IPublishedContent c, string a1, string a2) + { + // this basically implements the original XPath query ;-( + // + // "//* [@isDoc and (" + + // "contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',{0},')" + + // " or contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',/{0},')" + + // ")]" + if (!c.HasProperty(propertyAlias)) { - // this basically implements the original XPath query ;-( - // - // "//* [@isDoc and (" + - // "contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',{0},')" + - // " or contains(concat(',',translate(umbracoUrlAlias, ' ', ''),','),',/{0},')" + - // ")]" - if (!c.HasProperty(propertyAlias)) - { - return false; - } - - IPublishedProperty? p = c.GetProperty(propertyAlias); - var varies = p!.PropertyType?.VariesByCulture(); - string? v; - if (varies ?? false) - { - if (!c.HasCulture(culture)) - { - return false; - } - - v = c.Value(_publishedValueFallback, propertyAlias, culture); - } - else - { - v = c.Value(_publishedValueFallback, propertyAlias); - } + return false; + } - if (string.IsNullOrWhiteSpace(v)) + IPublishedProperty? p = c.GetProperty(propertyAlias); + var varies = p!.PropertyType?.VariesByCulture(); + string? v; + if (varies ?? false) + { + if (!c.HasCulture(culture)) { return false; } - v = "," + v.Replace(" ", string.Empty) + ","; - return v.InvariantContains(a1) || v.InvariantContains(a2); + v = c.Value(_publishedValueFallback, propertyAlias, culture); + } + else + { + v = c.Value(_publishedValueFallback, propertyAlias); } - // TODO: even with Linq, what happens below has to be horribly slow - // but the only solution is to entirely refactor URL providers to stop being dynamic - if (rootNodeId > 0) + if (string.IsNullOrWhiteSpace(v)) { - IPublishedContent? rootNode = cache?.GetById(rootNodeId); - return rootNode?.Descendants(_variationContextAccessor).FirstOrDefault(x => IsMatch(x, test1, test2)); + return false; } - if (cache is not null) + v = "," + v.Replace(" ", string.Empty) + ","; + return v.InvariantContains(a1) || v.InvariantContains(a2); + } + + // TODO: even with Linq, what happens below has to be horribly slow + // but the only solution is to entirely refactor URL providers to stop being dynamic + if (rootNodeId > 0) + { + IPublishedContent? rootNode = cache?.GetById(rootNodeId); + return rootNode?.Descendants(_variationContextAccessor).FirstOrDefault(x => IsMatch(x, test1, test2)); + } + + if (cache is not null) + { + foreach (IPublishedContent rootContent in cache.GetAtRoot()) { - foreach (IPublishedContent rootContent in cache.GetAtRoot()) + IPublishedContent? c = rootContent.DescendantsOrSelf(_variationContextAccessor) + .FirstOrDefault(x => IsMatch(x, test1, test2)); + if (c != null) { - IPublishedContent? c = rootContent.DescendantsOrSelf(_variationContextAccessor).FirstOrDefault(x => IsMatch(x, test1, test2)); - if (c != null) - { - return c; - } + return c; } } - - return null; } + + return null; } } diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs b/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs index f05985008653..0d3e7589785f 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs @@ -1,4 +1,3 @@ -using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; @@ -8,111 +7,121 @@ using Umbraco.Cms.Core.Web; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// Provides an implementation of that handles page nice URLs and a template. +/// +/// +/// +/// This finder allows for an odd routing pattern similar to altTemplate, probably only use case is if there is +/// an alternative mime type template and it should be routable by something like "/hello/world/json" where the +/// JSON template is to be used for the "world" page +/// +/// +/// Handles /foo/bar/template where /foo/bar is the nice URL of a document, and template a +/// template alias. +/// +/// If successful, then the template of the document request is also assigned. +/// +public class ContentFinderByUrlAndTemplate : ContentFinderByUrl { + private readonly IContentTypeService _contentTypeService; + private readonly IFileService _fileService; + private readonly ILogger _logger; + private WebRoutingSettings _webRoutingSettings; + + /// + /// Initializes a new instance of the class. + /// + public ContentFinderByUrlAndTemplate( + ILogger logger, + IFileService fileService, + IContentTypeService contentTypeService, + IUmbracoContextAccessor umbracoContextAccessor, + IOptionsMonitor webRoutingSettings) + : base(logger, umbracoContextAccessor) + { + _logger = logger; + _fileService = fileService; + _contentTypeService = contentTypeService; + _webRoutingSettings = webRoutingSettings.CurrentValue; + webRoutingSettings.OnChange(x => _webRoutingSettings = x); + } + /// - /// Provides an implementation of that handles page nice URLs and a template. + /// Tries to find and assign an Umbraco document to a PublishedRequest. /// - /// - /// This finder allows for an odd routing pattern similar to altTemplate, probably only use case is if there is an alternative mime type template and it should be routable by something like "/hello/world/json" where the JSON template is to be used for the "world" page - /// Handles /foo/bar/template where /foo/bar is the nice URL of a document, and template a template alias. - /// If successful, then the template of the document request is also assigned. - /// - public class ContentFinderByUrlAndTemplate : ContentFinderByUrl + /// The PublishedRequest. + /// A value indicating whether an Umbraco document was found and assigned. + /// If successful, also assigns the template. + public override async Task TryFindContent(IPublishedRequestBuilder frequest) { - private readonly ILogger _logger; - private readonly IFileService _fileService; - - private readonly IContentTypeService _contentTypeService; - private WebRoutingSettings _webRoutingSettings; - - /// - /// Initializes a new instance of the class. - /// - public ContentFinderByUrlAndTemplate( - ILogger logger, - IFileService fileService, - IContentTypeService contentTypeService, - IUmbracoContextAccessor umbracoContextAccessor, - IOptionsMonitor webRoutingSettings) - : base(logger, umbracoContextAccessor) + var path = frequest.AbsolutePathDecoded; + + if (frequest.Domain != null) { - _logger = logger; - _fileService = fileService; - _contentTypeService = contentTypeService; - _webRoutingSettings = webRoutingSettings.CurrentValue; - webRoutingSettings.OnChange(x => _webRoutingSettings = x); + path = DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, path); } - /// - /// Tries to find and assign an Umbraco document to a PublishedRequest. - /// - /// The PublishedRequest. - /// A value indicating whether an Umbraco document was found and assigned. - /// If successful, also assigns the template. - public override async Task TryFindContent(IPublishedRequestBuilder frequest) + // no template if "/" + if (path == "/") { - var path = frequest.AbsolutePathDecoded; - - if (frequest.Domain != null) + if (_logger.IsEnabled(LogLevel.Debug)) { - path = DomainUtilities.PathRelativeToDomain(frequest.Domain.Uri, path); + _logger.LogDebug("No template in path '/'"); } - // no template if "/" - if (path == "/") - { - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("No template in path '/'"); - } - return false; - } + return false; + } - // look for template in last position - var pos = path.LastIndexOf('/'); - var templateAlias = path.Substring(pos + 1); - path = pos == 0 ? "/" : path.Substring(0, pos); + // look for template in last position + var pos = path.LastIndexOf('/'); + var templateAlias = path.Substring(pos + 1); + path = pos == 0 ? "/" : path.Substring(0, pos); - ITemplate? template = _fileService.GetTemplate(templateAlias); + ITemplate? template = _fileService.GetTemplate(templateAlias); - if (template == null) - { - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Not a valid template: '{TemplateAlias}'", templateAlias); - } - return false; - } + if (template == null) + { if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("Valid template: '{TemplateAlias}'", templateAlias); + _logger.LogDebug("Not a valid template: '{TemplateAlias}'", templateAlias); } - // look for node corresponding to the rest of the route - var route = frequest.Domain != null ? (frequest.Domain.ContentId + path) : path; - IPublishedContent? node = FindContent(frequest, route); + return false; + } - if (node == null) - { - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Not a valid route to node: '{Route}'", route); - } - return false; - } + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("Valid template: '{TemplateAlias}'", templateAlias); + } - // IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings - if (!node.IsAllowedTemplate(_contentTypeService, _webRoutingSettings, template.Id)) + // look for node corresponding to the rest of the route + var route = frequest.Domain != null ? frequest.Domain.ContentId + path : path; + IPublishedContent? node = FindContent(frequest, route); + + if (node == null) + { + if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogWarning("Alternative template '{TemplateAlias}' is not allowed on node {NodeId}.", template.Alias, node.Id); - frequest.SetPublishedContent(null); // clear - return false; + _logger.LogDebug("Not a valid route to node: '{Route}'", route); } - // got it - frequest.SetTemplate(template); - return true; + return false; } + + // IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings + if (!node.IsAllowedTemplate(_contentTypeService, _webRoutingSettings, template.Id)) + { + _logger.LogWarning("Alternative template '{TemplateAlias}' is not allowed on node {NodeId}.", + template.Alias, node.Id); + frequest.SetPublishedContent(null); // clear + return false; + } + + // got it + frequest.SetTemplate(template); + return true; } } diff --git a/src/Umbraco.Core/Routing/ContentFinderCollection.cs b/src/Umbraco.Core/Routing/ContentFinderCollection.cs index 8965d9d44764..5e705ff81f00 100644 --- a/src/Umbraco.Core/Routing/ContentFinderCollection.cs +++ b/src/Umbraco.Core/Routing/ContentFinderCollection.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +public class ContentFinderCollection : BuilderCollectionBase { - public class ContentFinderCollection : BuilderCollectionBase + public ContentFinderCollection(Func> items) : base(items) { - public ContentFinderCollection(Func> items) : base(items) - { - } } } diff --git a/src/Umbraco.Core/Routing/ContentFinderCollectionBuilder.cs b/src/Umbraco.Core/Routing/ContentFinderCollectionBuilder.cs index d471acf60c0b..e2c58ff2ae7b 100644 --- a/src/Umbraco.Core/Routing/ContentFinderCollectionBuilder.cs +++ b/src/Umbraco.Core/Routing/ContentFinderCollectionBuilder.cs @@ -1,9 +1,9 @@ using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +public class ContentFinderCollectionBuilder : OrderedCollectionBuilderBase { - public class ContentFinderCollectionBuilder : OrderedCollectionBuilderBase - { - protected override ContentFinderCollectionBuilder This => this; - } + protected override ContentFinderCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/Routing/DefaultMediaUrlProvider.cs b/src/Umbraco.Core/Routing/DefaultMediaUrlProvider.cs index 1afda0175c7a..92bfd968dc1a 100644 --- a/src/Umbraco.Core/Routing/DefaultMediaUrlProvider.cs +++ b/src/Umbraco.Core/Routing/DefaultMediaUrlProvider.cs @@ -1,75 +1,79 @@ -using System; -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// Default media URL provider. +/// +public class DefaultMediaUrlProvider : IMediaUrlProvider { - /// - /// Default media URL provider. - /// - public class DefaultMediaUrlProvider : IMediaUrlProvider + private readonly MediaUrlGeneratorCollection _mediaPathGenerators; + private readonly UriUtility _uriUtility; + + public DefaultMediaUrlProvider(MediaUrlGeneratorCollection mediaPathGenerators, UriUtility uriUtility) { - private readonly UriUtility _uriUtility; - private readonly MediaUrlGeneratorCollection _mediaPathGenerators; + _mediaPathGenerators = mediaPathGenerators ?? throw new ArgumentNullException(nameof(mediaPathGenerators)); + _uriUtility = uriUtility; + } - public DefaultMediaUrlProvider(MediaUrlGeneratorCollection mediaPathGenerators, UriUtility uriUtility) - { - _mediaPathGenerators = mediaPathGenerators ?? throw new ArgumentNullException(nameof(mediaPathGenerators)); - _uriUtility = uriUtility; - } + /// + public virtual UrlInfo? GetMediaUrl(IPublishedContent content, + string propertyAlias, UrlMode mode, string? culture, Uri current) + { + IPublishedProperty prop = content.GetProperty(propertyAlias); - /// - public virtual UrlInfo? GetMediaUrl(IPublishedContent content, - string propertyAlias, UrlMode mode, string? culture, Uri current) + // get the raw source value since this is what is used by IDataEditorWithMediaPath for processing + var value = prop?.GetSourceValue(culture); + if (value == null) { - var prop = content.GetProperty(propertyAlias); + return null; + } - // get the raw source value since this is what is used by IDataEditorWithMediaPath for processing - var value = prop?.GetSourceValue(culture); - if (value == null) - { - return null; - } + IPublishedPropertyType propType = prop?.PropertyType; - var propType = prop?.PropertyType; + if (_mediaPathGenerators.TryGetMediaPath(propType?.EditorAlias, value, out var path)) + { + Uri url = AssembleUrl(path!, current, mode); + return UrlInfo.Url(url.ToString(), culture); + } - if (_mediaPathGenerators.TryGetMediaPath(propType?.EditorAlias, value, out var path)) - { - var url = AssembleUrl(path!, current, mode); - return UrlInfo.Url(url.ToString(), culture); - } + return null; + } - return null; + private Uri AssembleUrl(string path, Uri current, UrlMode mode) + { + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentException($"{nameof(path)} cannot be null or whitespace", nameof(path)); } - private Uri AssembleUrl(string path, Uri current, UrlMode mode) + // the stored path is absolute so we just return it as is + if (Uri.IsWellFormedUriString(path, UriKind.Absolute)) { - if (string.IsNullOrWhiteSpace(path)) - throw new ArgumentException($"{nameof(path)} cannot be null or whitespace", nameof(path)); - - // the stored path is absolute so we just return it as is - if (Uri.IsWellFormedUriString(path, UriKind.Absolute)) - return new Uri(path); - - Uri uri; + return new Uri(path); + } - if (current == null) - mode = UrlMode.Relative; // best we can do + Uri uri; - switch (mode) - { - case UrlMode.Absolute: - uri = new Uri(current?.GetLeftPart(UriPartial.Authority) + path); - break; - case UrlMode.Relative: - case UrlMode.Auto: - uri = new Uri(path, UriKind.Relative); - break; - default: - throw new ArgumentOutOfRangeException(nameof(mode)); - } + if (current == null) + { + mode = UrlMode.Relative; // best we can do + } - return _uriUtility.MediaUriFromUmbraco(uri); + switch (mode) + { + case UrlMode.Absolute: + uri = new Uri(current?.GetLeftPart(UriPartial.Authority) + path); + break; + case UrlMode.Relative: + case UrlMode.Auto: + uri = new Uri(path, UriKind.Relative); + break; + default: + throw new ArgumentOutOfRangeException(nameof(mode)); } + + return _uriUtility.MediaUriFromUmbraco(uri); } } diff --git a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs index 25e076434904..09baebf74478 100644 --- a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Globalization; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -11,235 +9,237 @@ using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// Provides urls. +/// +public class DefaultUrlProvider : IUrlProvider { + private readonly ILocalizationService _localizationService; + private readonly ILocalizedTextService? _localizedTextService; + private readonly ILogger _logger; + private readonly ISiteDomainMapper _siteDomainMapper; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly UriUtility _uriUtility; + private RequestHandlerSettings _requestSettings; + + [Obsolete("Use ctor with all parameters")] + public DefaultUrlProvider(IOptionsMonitor requestSettings, + ILogger logger, + ISiteDomainMapper siteDomainMapper, IUmbracoContextAccessor umbracoContextAccessor, UriUtility uriUtility) + : this(requestSettings, logger, siteDomainMapper, umbracoContextAccessor, uriUtility, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + + public DefaultUrlProvider( + IOptionsMonitor requestSettings, + ILogger logger, + ISiteDomainMapper siteDomainMapper, + IUmbracoContextAccessor umbracoContextAccessor, + UriUtility uriUtility, + ILocalizationService localizationService) + { + _requestSettings = requestSettings.CurrentValue; + _logger = logger; + _siteDomainMapper = siteDomainMapper; + _umbracoContextAccessor = umbracoContextAccessor; + _uriUtility = uriUtility; + _localizationService = localizationService; + + requestSettings.OnChange(x => _requestSettings = x); + } + + #region GetOtherUrls + /// - /// Provides urls. + /// Gets the other URLs of a published content. /// - public class DefaultUrlProvider : IUrlProvider + /// The Umbraco context. + /// The published content id. + /// The current absolute URL. + /// The other URLs for the published content. + /// + /// + /// Other URLs are those that GetUrl would not return in the current context, but would be valid + /// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...). + /// + /// + public virtual IEnumerable GetOtherUrls(int id, Uri current) { - private readonly ILocalizationService _localizationService; - private readonly ILocalizedTextService? _localizedTextService; - private readonly ILogger _logger; - private RequestHandlerSettings _requestSettings; - private readonly ISiteDomainMapper _siteDomainMapper; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly UriUtility _uriUtility; - - [Obsolete("Use ctor with all parameters")] - public DefaultUrlProvider(IOptionsMonitor requestSettings, ILogger logger, - ISiteDomainMapper siteDomainMapper, IUmbracoContextAccessor umbracoContextAccessor, UriUtility uriUtility) - : this(requestSettings, logger, siteDomainMapper, umbracoContextAccessor, uriUtility, - StaticServiceProvider.Instance.GetRequiredService()) + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + IPublishedContent? node = umbracoContext.Content?.GetById(id); + if (node == null) { + yield break; } - public DefaultUrlProvider( - IOptionsMonitor requestSettings, - ILogger logger, - ISiteDomainMapper siteDomainMapper, - IUmbracoContextAccessor umbracoContextAccessor, - UriUtility uriUtility, - ILocalizationService localizationService) + // look for domains, walking up the tree + IPublishedContent? n = node; + IEnumerable? domainUris = + DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, + current, false); + while (domainUris == null && n != null) // n is null at root { - _requestSettings = requestSettings.CurrentValue; - _logger = logger; - _siteDomainMapper = siteDomainMapper; - _umbracoContextAccessor = umbracoContextAccessor; - _uriUtility = uriUtility; - _localizationService = localizationService; - - requestSettings.OnChange(x => _requestSettings = x); + n = n.Parent; // move to parent node + domainUris = n == null + ? null + : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, + current); } - #region GetOtherUrls - - /// - /// Gets the other URLs of a published content. - /// - /// The Umbraco context. - /// The published content id. - /// The current absolute URL. - /// The other URLs for the published content. - /// - /// - /// Other URLs are those that GetUrl would not return in the current context, but would be valid - /// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...). - /// - /// - public virtual IEnumerable GetOtherUrls(int id, Uri current) + // no domains = exit + if (domainUris == null) { - IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - IPublishedContent? node = umbracoContext.Content?.GetById(id); - if (node == null) - { - yield break; - } + yield break; + } - // look for domains, walking up the tree - IPublishedContent? n = node; - IEnumerable? domainUris = - DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, - current, false); - while (domainUris == null && n != null) // n is null at root - { - n = n.Parent; // move to parent node - domainUris = n == null - ? null - : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, - current); - } + foreach (DomainAndUri d in domainUris) + { + var culture = d.Culture; - // no domains = exit - if (domainUris == null) + // although we are passing in culture here, if any node in this path is invariant, it ignores the culture anyways so this is ok + var route = umbracoContext.Content?.GetRouteById(id, culture); + if (route == null) { - yield break; + continue; } - foreach (DomainAndUri d in domainUris) - { - var culture = d.Culture; - - // although we are passing in culture here, if any node in this path is invariant, it ignores the culture anyways so this is ok - var route = umbracoContext.Content?.GetRouteById(id, culture); - if (route == null) - { - continue; - } - - // need to strip off the leading ID for the route if it exists (occurs if the route is for a node with a domain assigned) - var pos = route.IndexOf('/'); - var path = pos == 0 ? route : route.Substring(pos); + // need to strip off the leading ID for the route if it exists (occurs if the route is for a node with a domain assigned) + var pos = route.IndexOf('/'); + var path = pos == 0 ? route : route.Substring(pos); - var uri = new Uri(CombinePaths(d.Uri.GetLeftPart(UriPartial.Path), path)); - uri = _uriUtility.UriFromUmbraco(uri, _requestSettings); - yield return UrlInfo.Url(uri.ToString(), culture); - } + var uri = new Uri(CombinePaths(d.Uri.GetLeftPart(UriPartial.Path), path)); + uri = _uriUtility.UriFromUmbraco(uri, _requestSettings); + yield return UrlInfo.Url(uri.ToString(), culture); } + } - #endregion + #endregion - #region GetUrl + #region GetUrl - /// - public virtual UrlInfo? GetUrl(IPublishedContent content, UrlMode mode, string? culture, Uri current) + /// + public virtual UrlInfo? GetUrl(IPublishedContent content, UrlMode mode, string? culture, Uri current) + { + if (!current.IsAbsoluteUri) { - if (!current.IsAbsoluteUri) - { - throw new ArgumentException("Current URL must be absolute.", nameof(current)); - } + throw new ArgumentException("Current URL must be absolute.", nameof(current)); + } - IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - // will not use cache if previewing - var route = umbracoContext.Content?.GetRouteById(content.Id, culture); + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + // will not use cache if previewing + var route = umbracoContext.Content?.GetRouteById(content.Id, culture); - return GetUrlFromRoute(route, umbracoContext, content.Id, current, mode, culture); + return GetUrlFromRoute(route, umbracoContext, content.Id, current, mode, culture); + } + + internal UrlInfo? GetUrlFromRoute( + string? route, + IUmbracoContext umbracoContext, + int id, + Uri current, + UrlMode mode, + string? culture) + { + if (string.IsNullOrWhiteSpace(route)) + { + _logger.LogDebug( + "Couldn't find any page with nodeId={NodeId}. This is most likely caused by the page not being published.", + id); + return null; } - internal UrlInfo? GetUrlFromRoute( - string? route, - IUmbracoContext umbracoContext, - int id, - Uri current, - UrlMode mode, - string? culture) + // extract domainUri and path + // route is / or / + var pos = route.IndexOf('/'); + var path = pos == 0 ? route : route.Substring(pos); + DomainAndUri? domainUri = pos == 0 + ? null + : DomainUtilities.DomainForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, + int.Parse(route.Substring(0, pos), CultureInfo.InvariantCulture), current, culture); + + var defaultCulture = _localizationService.GetDefaultLanguageIsoCode(); + if (domainUri is not null || string.IsNullOrEmpty(culture) || + culture.Equals(defaultCulture, StringComparison.InvariantCultureIgnoreCase)) { - if (string.IsNullOrWhiteSpace(route)) - { - _logger.LogDebug( - "Couldn't find any page with nodeId={NodeId}. This is most likely caused by the page not being published.", - id); - return null; - } + var url = AssembleUrl(domainUri, path, current, mode).ToString(); + return UrlInfo.Url(url, culture); + } - // extract domainUri and path - // route is / or / - var pos = route.IndexOf('/'); - var path = pos == 0 ? route : route.Substring(pos); - DomainAndUri? domainUri = pos == 0 - ? null - : DomainUtilities.DomainForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, int.Parse(route.Substring(0, pos), CultureInfo.InvariantCulture), current, culture); + return null; + } - var defaultCulture = _localizationService.GetDefaultLanguageIsoCode(); - if (domainUri is not null || string.IsNullOrEmpty(culture) || culture.Equals(defaultCulture, StringComparison.InvariantCultureIgnoreCase)) - { - var url = AssembleUrl(domainUri, path, current, mode).ToString(); - return UrlInfo.Url(url, culture); - } + #endregion - return null; - } + #region Utilities - #endregion + private Uri AssembleUrl(DomainAndUri? domainUri, string path, Uri current, UrlMode mode) + { + Uri uri; - #region Utilities + // ignore vdir at that point, UriFromUmbraco will do it - private Uri AssembleUrl(DomainAndUri? domainUri, string path, Uri current, UrlMode mode) + if (domainUri == null) // no domain was found { - Uri uri; - - // ignore vdir at that point, UriFromUmbraco will do it - - if (domainUri == null) // no domain was found + if (current == null) { - if (current == null) - { - mode = UrlMode.Relative; // best we can do - } + mode = UrlMode.Relative; // best we can do + } - switch (mode) - { - case UrlMode.Absolute: - uri = new Uri(current!.GetLeftPart(UriPartial.Authority) + path); - break; - case UrlMode.Relative: - case UrlMode.Auto: - uri = new Uri(path, UriKind.Relative); - break; - default: - throw new ArgumentOutOfRangeException(nameof(mode)); - } + switch (mode) + { + case UrlMode.Absolute: + uri = new Uri(current!.GetLeftPart(UriPartial.Authority) + path); + break; + case UrlMode.Relative: + case UrlMode.Auto: + uri = new Uri(path, UriKind.Relative); + break; + default: + throw new ArgumentOutOfRangeException(nameof(mode)); } - else // a domain was found + } + else // a domain was found + { + if (mode == UrlMode.Auto) { - if (mode == UrlMode.Auto) + //this check is a little tricky, we can't just compare domains + if (current != null && domainUri.Uri.GetLeftPart(UriPartial.Authority) == + current.GetLeftPart(UriPartial.Authority)) { - //this check is a little tricky, we can't just compare domains - if (current != null && domainUri.Uri.GetLeftPart(UriPartial.Authority) == - current.GetLeftPart(UriPartial.Authority)) - { - mode = UrlMode.Relative; - } - else - { - mode = UrlMode.Absolute; - } + mode = UrlMode.Relative; } - - switch (mode) + else { - case UrlMode.Absolute: - uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)); - break; - case UrlMode.Relative: - uri = new Uri(CombinePaths(domainUri.Uri.AbsolutePath, path), UriKind.Relative); - break; - default: - throw new ArgumentOutOfRangeException(nameof(mode)); + mode = UrlMode.Absolute; } } - // UriFromUmbraco will handle vdir - // meaning it will add vdir into domain URLs too! - return _uriUtility.UriFromUmbraco(uri, _requestSettings); + switch (mode) + { + case UrlMode.Absolute: + uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)); + break; + case UrlMode.Relative: + uri = new Uri(CombinePaths(domainUri.Uri.AbsolutePath, path), UriKind.Relative); + break; + default: + throw new ArgumentOutOfRangeException(nameof(mode)); + } } - private string CombinePaths(string path1, string path2) - { - var path = path1.TrimEnd(Constants.CharArrays.ForwardSlash) + path2; - return path == "/" ? path : path.TrimEnd(Constants.CharArrays.ForwardSlash); - } + // UriFromUmbraco will handle vdir + // meaning it will add vdir into domain URLs too! + return _uriUtility.UriFromUmbraco(uri, _requestSettings); + } - #endregion + private string CombinePaths(string path1, string path2) + { + var path = path1.TrimEnd(Constants.CharArrays.ForwardSlash) + path2; + return path == "/" ? path : path.TrimEnd(Constants.CharArrays.ForwardSlash); } + + #endregion } diff --git a/src/Umbraco.Core/Routing/Domain.cs b/src/Umbraco.Core/Routing/Domain.cs index ecefb07e8b40..291d7beed920 100644 --- a/src/Umbraco.Core/Routing/Domain.cs +++ b/src/Umbraco.Core/Routing/Domain.cs @@ -1,63 +1,62 @@ -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// Represents a published snapshot domain. +/// +public class Domain { /// - /// Represents a published snapshot domain. + /// Initializes a new instance of the class. /// - public class Domain + /// The unique identifier of the domain. + /// The name of the domain. + /// The identifier of the content which supports the domain. + /// The culture of the domain. + /// A value indicating whether the domain is a wildcard domain. + public Domain(int id, string name, int contentId, string? culture, bool isWildcard) { - /// - /// Initializes a new instance of the class. - /// - /// The unique identifier of the domain. - /// The name of the domain. - /// The identifier of the content which supports the domain. - /// The culture of the domain. - /// A value indicating whether the domain is a wildcard domain. - public Domain(int id, string name, int contentId, string? culture, bool isWildcard) - { - Id = id; - Name = name; - ContentId = contentId; - Culture = culture; - IsWildcard = isWildcard; - } + Id = id; + Name = name; + ContentId = contentId; + Culture = culture; + IsWildcard = isWildcard; + } - /// - /// Initializes a new instance of the class. - /// - /// An origin domain. - protected Domain(Domain domain) - { - Id = domain.Id; - Name = domain.Name; - ContentId = domain.ContentId; - Culture = domain.Culture; - IsWildcard = domain.IsWildcard; - } + /// + /// Initializes a new instance of the class. + /// + /// An origin domain. + protected Domain(Domain domain) + { + Id = domain.Id; + Name = domain.Name; + ContentId = domain.ContentId; + Culture = domain.Culture; + IsWildcard = domain.IsWildcard; + } - /// - /// Gets the unique identifier of the domain. - /// - public int Id { get; } + /// + /// Gets the unique identifier of the domain. + /// + public int Id { get; } - /// - /// Gets the name of the domain. - /// - public string Name { get; } + /// + /// Gets the name of the domain. + /// + public string Name { get; } - /// - /// Gets the identifier of the content which supports the domain. - /// - public int ContentId { get; } + /// + /// Gets the identifier of the content which supports the domain. + /// + public int ContentId { get; } - /// - /// Gets the culture of the domain. - /// - public string? Culture { get; } + /// + /// Gets the culture of the domain. + /// + public string? Culture { get; } - /// - /// Gets a value indicating whether the domain is a wildcard domain. - /// - public bool IsWildcard { get; } - } + /// + /// Gets a value indicating whether the domain is a wildcard domain. + /// + public bool IsWildcard { get; } } diff --git a/src/Umbraco.Core/Routing/DomainAndUri.cs b/src/Umbraco.Core/Routing/DomainAndUri.cs index 751c4ead5891..31d2eea7ffbd 100644 --- a/src/Umbraco.Core/Routing/DomainAndUri.cs +++ b/src/Umbraco.Core/Routing/DomainAndUri.cs @@ -1,44 +1,46 @@ -using System; -using Umbraco.Extensions; +using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// Represents a published snapshot domain with its normalized uri. +/// +/// +/// +/// In Umbraco it is valid to create domains with name such as example.com, https://www.example.com +/// , example.com/foo/. +/// +/// +/// The normalized uri of a domain begins with a scheme and ends with no slash, eg http://example.com/, +/// https://www.example.com/, http://example.com/foo/. +/// +/// +public class DomainAndUri : Domain { /// - /// Represents a published snapshot domain with its normalized uri. + /// Initializes a new instance of the class. /// - /// - /// In Umbraco it is valid to create domains with name such as example.com, https://www.example.com, example.com/foo/. - /// The normalized uri of a domain begins with a scheme and ends with no slash, eg http://example.com/, https://www.example.com/, http://example.com/foo/. - /// - public class DomainAndUri : Domain + /// The original domain. + /// The context current Uri. + public DomainAndUri(Domain domain, Uri currentUri) + : base(domain) { - /// - /// Initializes a new instance of the class. - /// - /// The original domain. - /// The context current Uri. - public DomainAndUri(Domain domain, Uri currentUri) - : base(domain) + try { - try - { - Uri = DomainUtilities.ParseUriFromDomainName(Name, currentUri); - } - catch (UriFormatException) - { - throw new ArgumentException($"Failed to parse invalid domain: node id={domain.ContentId}, hostname=\"{Name.ToCSharpString()}\"." - + " Hostname should be a valid uri.", nameof(domain)); - } + Uri = DomainUtilities.ParseUriFromDomainName(Name, currentUri); } - - /// - /// Gets the normalized uri of the domain, within the current context. - /// - public Uri Uri { get; } - - public override string ToString() + catch (UriFormatException) { - return $"{{ \"{Name}\", \"{Uri}\" }}"; + throw new ArgumentException( + $"Failed to parse invalid domain: node id={domain.ContentId}, hostname=\"{Name.ToCSharpString()}\"." + + " Hostname should be a valid uri.", nameof(domain)); } } + + /// + /// Gets the normalized uri of the domain, within the current context. + /// + public Uri Uri { get; } + + public override string ToString() => $"{{ \"{Name}\", \"{Uri}\" }}"; } diff --git a/src/Umbraco.Core/Routing/DomainUtilities.cs b/src/Umbraco.Core/Routing/DomainUtilities.cs index 9e762a600e52..96c585e93ce7 100644 --- a/src/Umbraco.Core/Routing/DomainUtilities.cs +++ b/src/Umbraco.Core/Routing/DomainUtilities.cs @@ -1,372 +1,433 @@ -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Web; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// Provides utilities to handle domains. +/// +public static class DomainUtilities { + #region Document Culture + /// - /// Provides utilities to handle domains. + /// Gets the culture assigned to a document by domains, in the context of a current Uri. /// - public static class DomainUtilities + /// The document identifier. + /// The document path. + /// An optional current Uri. + /// An Umbraco context. + /// The site domain helper. + /// The culture assigned to the document by domains. + /// + /// + /// In 1:1 multilingual setup, a document contains several cultures (there is not + /// one document per culture), and domains, withing the context of a current Uri, assign + /// a culture to that document. + /// + /// + public static string? GetCultureFromDomains(int contentId, string contentPath, Uri? current, + IUmbracoContext umbracoContext, ISiteDomainMapper siteDomainMapper) { - #region Document Culture - - /// - /// Gets the culture assigned to a document by domains, in the context of a current Uri. - /// - /// The document identifier. - /// The document path. - /// An optional current Uri. - /// An Umbraco context. - /// The site domain helper. - /// The culture assigned to the document by domains. - /// - /// In 1:1 multilingual setup, a document contains several cultures (there is not - /// one document per culture), and domains, withing the context of a current Uri, assign - /// a culture to that document. - /// - public static string? GetCultureFromDomains(int contentId, string contentPath, Uri? current, IUmbracoContext umbracoContext, ISiteDomainMapper siteDomainMapper) + if (umbracoContext == null) { - if (umbracoContext == null) - throw new InvalidOperationException("A current UmbracoContext is required."); + throw new InvalidOperationException("A current UmbracoContext is required."); + } - if (current == null) - current = umbracoContext.CleanedUmbracoUrl; + if (current == null) + { + current = umbracoContext.CleanedUmbracoUrl; + } - // get the published route, else the preview route - // if both are null then the content does not exist - var route = umbracoContext.Content?.GetRouteById(contentId) ?? - umbracoContext.Content?.GetRouteById(true, contentId); + // get the published route, else the preview route + // if both are null then the content does not exist + var route = umbracoContext.Content?.GetRouteById(contentId) ?? + umbracoContext.Content?.GetRouteById(true, contentId); - if (route == null) - return null; + if (route == null) + { + return null; + } - var pos = route.IndexOf('/'); - var domain = pos == 0 - ? null - : DomainForNode(umbracoContext.Domains, siteDomainMapper, int.Parse(route.Substring(0, pos), CultureInfo.InvariantCulture), current); + var pos = route.IndexOf('/'); + DomainAndUri domain = pos == 0 + ? null + : DomainForNode(umbracoContext.Domains, siteDomainMapper, + int.Parse(route.Substring(0, pos), CultureInfo.InvariantCulture), current); - var rootContentId = domain?.ContentId ?? -1; - var wcDomain = FindWildcardDomainInPath(umbracoContext.Domains?.GetAll(true), contentPath, rootContentId); + var rootContentId = domain?.ContentId ?? -1; + Domain wcDomain = FindWildcardDomainInPath(umbracoContext.Domains?.GetAll(true), contentPath, rootContentId); - if (wcDomain != null) return wcDomain.Culture; - if (domain != null) return domain.Culture; - return umbracoContext.Domains?.DefaultCulture; + if (wcDomain != null) + { + return wcDomain.Culture; } - #endregion - - #region Domain for Document - - /// - /// Finds the domain for the specified node, if any, that best matches a specified uri. - /// - /// A domain cache. - /// The site domain helper. - /// The node identifier. - /// The uri, or null. - /// The culture, or null. - /// The domain and its uri, if any, that best matches the specified uri and culture, else null. - /// - /// If at least a domain is set on the node then the method returns the domain that - /// best matches the specified uri and culture, else it returns null. - /// If culture is null, uses the default culture for the installation instead. Otherwise, - /// will try with the specified culture, else return null. - /// - internal static DomainAndUri? DomainForNode(IDomainCache? domainCache, ISiteDomainMapper siteDomainMapper, int nodeId, Uri current, string? culture = null) + if (domain != null) { - // be safe - if (nodeId <= 0) - return null; + return domain.Culture; + } - // get the domains on that node - var domains = domainCache?.GetAssigned(nodeId).ToArray(); + return umbracoContext.Domains?.DefaultCulture; + } - // none? - if (domains is null || domains.Length == 0) - return null; + #endregion - // else filter - // it could be that none apply (due to culture) - return SelectDomain(domains, current, culture, domainCache?.DefaultCulture, siteDomainMapper.MapDomain); - } + #region Domain for Document - /// - /// Find the domains for the specified node, if any, that match a specified uri. - /// - /// A domain cache. - /// The site domain helper. - /// The node identifier. - /// The uri, or null. - /// A value indicating whether to exclude the current/default domain. True by default. - /// The domains and their uris, that match the specified uri, else null. - /// If at least a domain is set on the node then the method returns the domains that - /// best match the specified uri, else it returns null. - internal static IEnumerable? DomainsForNode(IDomainCache? domainCache, ISiteDomainMapper siteDomainMapper, int nodeId, Uri current, bool excludeDefault = true) + /// + /// Finds the domain for the specified node, if any, that best matches a specified uri. + /// + /// A domain cache. + /// The site domain helper. + /// The node identifier. + /// The uri, or null. + /// The culture, or null. + /// The domain and its uri, if any, that best matches the specified uri and culture, else null. + /// + /// + /// If at least a domain is set on the node then the method returns the domain that + /// best matches the specified uri and culture, else it returns null. + /// + /// + /// If culture is null, uses the default culture for the installation instead. Otherwise, + /// will try with the specified culture, else return null. + /// + /// + internal static DomainAndUri? DomainForNode(IDomainCache? domainCache, ISiteDomainMapper siteDomainMapper, + int nodeId, Uri current, string? culture = null) + { + // be safe + if (nodeId <= 0) { - // be safe - if (nodeId <= 0) - return null; + return null; + } - // get the domains on that node - var domains = domainCache?.GetAssigned(nodeId).ToArray(); + // get the domains on that node + Domain[] domains = domainCache?.GetAssigned(nodeId).ToArray(); - // none? - if (domains is null || domains.Length == 0) - return null; + // none? + if (domains is null || domains.Length == 0) + { + return null; + } - // get the domains and their uris - var domainAndUris = SelectDomains(domains, current).ToArray(); + // else filter + // it could be that none apply (due to culture) + return SelectDomain(domains, current, culture, domainCache?.DefaultCulture, siteDomainMapper.MapDomain); + } - // filter - return siteDomainMapper.MapDomains(domainAndUris, current, excludeDefault, null, domainCache?.DefaultCulture).ToArray(); + /// + /// Find the domains for the specified node, if any, that match a specified uri. + /// + /// A domain cache. + /// The site domain helper. + /// The node identifier. + /// The uri, or null. + /// A value indicating whether to exclude the current/default domain. True by default. + /// The domains and their uris, that match the specified uri, else null. + /// + /// If at least a domain is set on the node then the method returns the domains that + /// best match the specified uri, else it returns null. + /// + internal static IEnumerable? DomainsForNode(IDomainCache? domainCache, + ISiteDomainMapper siteDomainMapper, int nodeId, Uri current, bool excludeDefault = true) + { + // be safe + if (nodeId <= 0) + { + return null; } - #endregion - - #region Selects Domain(s) - - /// - /// Selects the domain that best matches a specified uri and cultures, from a set of domains. - /// - /// The group of domains. - /// An optional uri. - /// An optional culture. - /// An optional default culture. - /// An optional function to filter the list of domains, if more than one applies. - /// The domain and its normalized uri, that best matches the specified uri and cultures. - /// - /// TODO: must document and explain this all - /// If is null, pick the first domain that matches , - /// else the first that matches , else the first one (ordered by id), else null. - /// If is not null, look for domains that would be a base uri of the current uri, - /// If more than one domain matches, then the function is used to pick - /// the right one, unless it is null, in which case the method returns null. - /// The filter, if any, will be called only with a non-empty argument, and _must_ return something. - /// - public static DomainAndUri? SelectDomain(IEnumerable? domains, Uri uri, string? culture = null, string? defaultCulture = null, Func, Uri, string?, string?, DomainAndUri?>? filter = null) - { - // sanitize the list to have proper uris for comparison (scheme, path end with /) - // we need to end with / because example.com/foo cannot match example.com/foobar - // we need to order so example.com/foo matches before example.com/ - var domainsAndUris = domains? - .Where(d => d.IsWildcard == false) - .Select(d => new DomainAndUri(d, uri)) - .OrderByDescending(d => d.Uri.ToString()) - .ToList(); - - // nothing = no magic, return null - if (domainsAndUris is null || domainsAndUris.Count == 0) - return null; - - // sanitize cultures - culture = culture?.NullOrWhiteSpaceAsNull(); - defaultCulture = defaultCulture?.NullOrWhiteSpaceAsNull(); - - if (uri == null) - { - // no uri - will only rely on culture - return GetByCulture(domainsAndUris, culture, defaultCulture); - } + // get the domains on that node + Domain[] domains = domainCache?.GetAssigned(nodeId).ToArray(); - // else we have a uri, - // try to match that uri, else filter + // none? + if (domains is null || domains.Length == 0) + { + return null; + } - // if a culture is specified, then try to get domains for that culture - // (else cultureDomains will be null) - // do NOT specify a default culture, else it would pick those domains - var cultureDomains = SelectByCulture(domainsAndUris, culture, defaultCulture: null); - IReadOnlyCollection considerForBaseDomains = domainsAndUris; - if (cultureDomains != null) - { - if (cultureDomains.Count == 1) // only 1, return - return cultureDomains.First(); + // get the domains and their uris + DomainAndUri[] domainAndUris = SelectDomains(domains, current).ToArray(); - // else restrict to those domains, for base lookup - considerForBaseDomains = cultureDomains; - } + // filter + return siteDomainMapper.MapDomains(domainAndUris, current, excludeDefault, null, domainCache?.DefaultCulture) + .ToArray(); + } - // look for domains that would be the base of the uri - var baseDomains = SelectByBase(considerForBaseDomains, uri, culture); - if (baseDomains.Count > 0) // found, return - return baseDomains.First(); + #endregion - // if nothing works, then try to run the filter to select a domain - // either restricting on cultureDomains, or on all domains - if (filter != null) - { - var domainAndUri = filter(cultureDomains ?? domainsAndUris, uri, culture, defaultCulture); - return domainAndUri; - } + #region Selects Domain(s) + /// + /// Selects the domain that best matches a specified uri and cultures, from a set of domains. + /// + /// The group of domains. + /// An optional uri. + /// An optional culture. + /// An optional default culture. + /// An optional function to filter the list of domains, if more than one applies. + /// The domain and its normalized uri, that best matches the specified uri and cultures. + /// + /// TODO: must document and explain this all + /// + /// If is null, pick the first domain that matches , + /// else the first that matches , else the first one (ordered by id), else null. + /// + /// If is not null, look for domains that would be a base uri of the current uri, + /// + /// If more than one domain matches, then the function is used to pick + /// the right one, unless it is null, in which case the method returns null. + /// + /// The filter, if any, will be called only with a non-empty argument, and _must_ return something. + /// + public static DomainAndUri? SelectDomain(IEnumerable? domains, Uri uri, string? culture = null, + string? defaultCulture = null, + Func, Uri, string?, string?, DomainAndUri?>? filter = null) + { + // sanitize the list to have proper uris for comparison (scheme, path end with /) + // we need to end with / because example.com/foo cannot match example.com/foobar + // we need to order so example.com/foo matches before example.com/ + var domainsAndUris = domains? + .Where(d => d.IsWildcard == false) + .Select(d => new DomainAndUri(d, uri)) + .OrderByDescending(d => d.Uri.ToString()) + .ToList(); + + // nothing = no magic, return null + if (domainsAndUris is null || domainsAndUris.Count == 0) + { return null; } - private static bool IsBaseOf(DomainAndUri domain, Uri uri) - => domain.Uri.EndPathWithSlash().IsBaseOf(uri); + // sanitize cultures + culture = culture?.NullOrWhiteSpaceAsNull(); + defaultCulture = defaultCulture?.NullOrWhiteSpaceAsNull(); - private static bool MatchesCulture(DomainAndUri domain, string? culture) - => culture == null || domain.Culture.InvariantEquals(culture); - - private static IReadOnlyCollection SelectByBase(IReadOnlyCollection domainsAndUris, Uri uri, string? culture) + if (uri == null) { - // look for domains that would be the base of the uri - // ie current is www.example.com/foo/bar, look for domain www.example.com - var currentWithSlash = uri.EndPathWithSlash(); - var baseDomains = domainsAndUris.Where(d => IsBaseOf(d, currentWithSlash) && MatchesCulture(d, culture)).ToList(); - - // if none matches, try again without the port - // ie current is www.example.com:1234/foo/bar, look for domain www.example.com - var currentWithoutPort = currentWithSlash.WithoutPort(); - if (baseDomains.Count == 0) - baseDomains = domainsAndUris.Where(d => IsBaseOf(d, currentWithoutPort)).ToList(); - - return baseDomains; + // no uri - will only rely on culture + return GetByCulture(domainsAndUris, culture, defaultCulture); } - private static IReadOnlyCollection? SelectByCulture(IReadOnlyCollection domainsAndUris, string? culture, string? defaultCulture) - { - // we try our best to match cultures, but may end with a bogus domain + // else we have a uri, + // try to match that uri, else filter - if (culture != null) // try the supplied culture + // if a culture is specified, then try to get domains for that culture + // (else cultureDomains will be null) + // do NOT specify a default culture, else it would pick those domains + IReadOnlyCollection cultureDomains = SelectByCulture(domainsAndUris, culture, null); + IReadOnlyCollection considerForBaseDomains = domainsAndUris; + if (cultureDomains != null) + { + if (cultureDomains.Count == 1) // only 1, return { - var cultureDomains = domainsAndUris.Where(x => x.Culture.InvariantEquals(culture)).ToList(); - if (cultureDomains.Count > 0) return cultureDomains; + return cultureDomains.First(); } - if (defaultCulture != null) // try the defaultCulture culture - { - var cultureDomains = domainsAndUris.Where(x => x.Culture.InvariantEquals(defaultCulture)).ToList(); - if (cultureDomains.Count > 0) return cultureDomains; - } + // else restrict to those domains, for base lookup + considerForBaseDomains = cultureDomains; + } - return null; + // look for domains that would be the base of the uri + IReadOnlyCollection baseDomains = SelectByBase(considerForBaseDomains, uri, culture); + if (baseDomains.Count > 0) // found, return + { + return baseDomains.First(); } - private static DomainAndUri GetByCulture(IReadOnlyCollection domainsAndUris, string? culture, string? defaultCulture) + // if nothing works, then try to run the filter to select a domain + // either restricting on cultureDomains, or on all domains + if (filter != null) { - DomainAndUri? domainAndUri; + DomainAndUri domainAndUri = filter(cultureDomains ?? domainsAndUris, uri, culture, defaultCulture); + return domainAndUri; + } - // we try our best to match cultures, but may end with a bogus domain + return null; + } - if (culture != null) // try the supplied culture - { - domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)); - if (domainAndUri != null) return domainAndUri; - } + private static bool IsBaseOf(DomainAndUri domain, Uri uri) + => domain.Uri.EndPathWithSlash().IsBaseOf(uri); - if (defaultCulture != null) // try the defaultCulture culture - { - domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(defaultCulture)); - if (domainAndUri != null) return domainAndUri; - } + private static bool MatchesCulture(DomainAndUri domain, string? culture) + => culture == null || domain.Culture.InvariantEquals(culture); - return domainsAndUris.First(); // what else? + private static IReadOnlyCollection SelectByBase(IReadOnlyCollection domainsAndUris, + Uri uri, string? culture) + { + // look for domains that would be the base of the uri + // ie current is www.example.com/foo/bar, look for domain www.example.com + Uri currentWithSlash = uri.EndPathWithSlash(); + var baseDomains = domainsAndUris.Where(d => IsBaseOf(d, currentWithSlash) && MatchesCulture(d, culture)) + .ToList(); + + // if none matches, try again without the port + // ie current is www.example.com:1234/foo/bar, look for domain www.example.com + Uri currentWithoutPort = currentWithSlash.WithoutPort(); + if (baseDomains.Count == 0) + { + baseDomains = domainsAndUris.Where(d => IsBaseOf(d, currentWithoutPort)).ToList(); } - /// - /// Selects the domains that match a specified uri, from a set of domains. - /// - /// The domains. - /// The uri, or null. - /// The domains and their normalized uris, that match the specified uri. - internal static IEnumerable SelectDomains(IEnumerable domains, Uri uri) + return baseDomains; + } + + private static IReadOnlyCollection? SelectByCulture(IReadOnlyCollection domainsAndUris, + string? culture, string? defaultCulture) + { + // we try our best to match cultures, but may end with a bogus domain + + if (culture != null) // try the supplied culture { - // TODO: where are we matching ?!!? - return domains - .Where(d => d.IsWildcard == false) - .Select(d => new DomainAndUri(d, uri)) - .OrderByDescending(d => d.Uri.ToString()); + var cultureDomains = domainsAndUris.Where(x => x.Culture.InvariantEquals(culture)).ToList(); + if (cultureDomains.Count > 0) + { + return cultureDomains; + } } - /// - /// Parses a domain name into a URI. - /// - /// The domain name to parse - /// The currently requested URI. If the domain name is relative, the authority of URI will be used. - /// The domain name as a URI - public static Uri ParseUriFromDomainName(string domainName, Uri currentUri) + if (defaultCulture != null) // try the defaultCulture culture { - // turn "/en" into "http://whatever.com/en" so it becomes a parseable uri - var name = domainName.StartsWith("/") && currentUri != null - ? currentUri.GetLeftPart(UriPartial.Authority) + domainName - : domainName; - var scheme = currentUri?.Scheme ?? Uri.UriSchemeHttp; - return new Uri(UriUtilityCore.TrimPathEndSlash(UriUtilityCore.StartWithScheme(name, scheme))); + var cultureDomains = domainsAndUris.Where(x => x.Culture.InvariantEquals(defaultCulture)).ToList(); + if (cultureDomains.Count > 0) + { + return cultureDomains; + } } - #endregion + return null; + } - #region Utilities + private static DomainAndUri GetByCulture(IReadOnlyCollection domainsAndUris, string? culture, + string? defaultCulture) + { + DomainAndUri? domainAndUri; - /// - /// Gets a value indicating whether there is another domain defined down in the path to a node under the current domain's root node. - /// - /// The domains. - /// The path to a node under the current domain's root node eg '-1,1234,5678'. - /// The current domain root node identifier, or null. - /// A value indicating if there is another domain defined down in the path. - /// Looks _under_ rootNodeId but not _at_ rootNodeId. - internal static bool ExistsDomainInPath(IEnumerable domains, string path, int? rootNodeId) - { - return FindDomainInPath(domains, path, rootNodeId) != null; - } + // we try our best to match cultures, but may end with a bogus domain - /// - /// Gets the deepest non-wildcard Domain, if any, from a group of Domains, in a node path. - /// - /// The domains. - /// The node path eg '-1,1234,5678'. - /// The current domain root node identifier, or null. - /// The deepest non-wildcard Domain in the path, or null. - /// Looks _under_ rootNodeId but not _at_ rootNodeId. - internal static Domain? FindDomainInPath(IEnumerable domains, string path, int? rootNodeId) + if (culture != null) // try the supplied culture { - var stopNodeId = rootNodeId ?? -1; - - return path.Split(Constants.CharArrays.Comma) - .Reverse() - .Select(s => int.Parse(s, CultureInfo.InvariantCulture)) - .TakeWhile(id => id != stopNodeId) - .Select(id => domains.FirstOrDefault(d => d.ContentId == id && d.IsWildcard == false)) - .SkipWhile(domain => domain == null) - .FirstOrDefault(); + domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)); + if (domainAndUri != null) + { + return domainAndUri; + } } - /// - /// Gets the deepest wildcard Domain, if any, from a group of Domains, in a node path. - /// - /// The domains. - /// The node path eg '-1,1234,5678'. - /// The current domain root node identifier, or null. - /// The deepest wildcard Domain in the path, or null. - /// Looks _under_ rootNodeId but not _at_ rootNodeId. - public static Domain? FindWildcardDomainInPath(IEnumerable? domains, string path, int? rootNodeId) + if (defaultCulture != null) // try the defaultCulture culture { - var stopNodeId = rootNodeId ?? -1; - - return path.Split(Constants.CharArrays.Comma) - .Reverse() - .Select(s => int.Parse(s, CultureInfo.InvariantCulture)) - .TakeWhile(id => id != stopNodeId) - .Select(id => domains?.FirstOrDefault(d => d.ContentId == id && d.IsWildcard)) - .FirstOrDefault(domain => domain != null); + domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(defaultCulture)); + if (domainAndUri != null) + { + return domainAndUri; + } } - /// - /// Returns the part of a path relative to the uri of a domain. - /// - /// The normalized uri of the domain. - /// The full path of the uri. - /// The path part relative to the uri of the domain. - /// Eg the relative part of /foo/bar/nil to domain example.com/foo is /bar/nil. - public static string PathRelativeToDomain(Uri domainUri, string path) - => path.Substring(domainUri.GetAbsolutePathDecoded().Length).EnsureStartsWith('/'); - - #endregion + return domainsAndUris.First(); // what else? } + + /// + /// Selects the domains that match a specified uri, from a set of domains. + /// + /// The domains. + /// The uri, or null. + /// The domains and their normalized uris, that match the specified uri. + internal static IEnumerable SelectDomains(IEnumerable domains, Uri uri) => + // TODO: where are we matching ?!!? + domains + .Where(d => d.IsWildcard == false) + .Select(d => new DomainAndUri(d, uri)) + .OrderByDescending(d => d.Uri.ToString()); + + /// + /// Parses a domain name into a URI. + /// + /// The domain name to parse + /// + /// The currently requested URI. If the domain name is relative, the authority of URI will be + /// used. + /// + /// The domain name as a URI + public static Uri ParseUriFromDomainName(string domainName, Uri currentUri) + { + // turn "/en" into "http://whatever.com/en" so it becomes a parseable uri + var name = domainName.StartsWith("/") && currentUri != null + ? currentUri.GetLeftPart(UriPartial.Authority) + domainName + : domainName; + var scheme = currentUri?.Scheme ?? Uri.UriSchemeHttp; + return new Uri(UriUtilityCore.TrimPathEndSlash(UriUtilityCore.StartWithScheme(name, scheme))); + } + + #endregion + + #region Utilities + + /// + /// Gets a value indicating whether there is another domain defined down in the path to a node under the current + /// domain's root node. + /// + /// The domains. + /// The path to a node under the current domain's root node eg '-1,1234,5678'. + /// The current domain root node identifier, or null. + /// A value indicating if there is another domain defined down in the path. + /// Looks _under_ rootNodeId but not _at_ rootNodeId. + internal static bool ExistsDomainInPath(IEnumerable domains, string path, int? rootNodeId) => + FindDomainInPath(domains, path, rootNodeId) != null; + + /// + /// Gets the deepest non-wildcard Domain, if any, from a group of Domains, in a node path. + /// + /// The domains. + /// The node path eg '-1,1234,5678'. + /// The current domain root node identifier, or null. + /// The deepest non-wildcard Domain in the path, or null. + /// Looks _under_ rootNodeId but not _at_ rootNodeId. + internal static Domain? FindDomainInPath(IEnumerable domains, string path, int? rootNodeId) + { + var stopNodeId = rootNodeId ?? -1; + + return path.Split(Constants.CharArrays.Comma) + .Reverse() + .Select(s => int.Parse(s, CultureInfo.InvariantCulture)) + .TakeWhile(id => id != stopNodeId) + .Select(id => domains.FirstOrDefault(d => d.ContentId == id && d.IsWildcard == false)) + .SkipWhile(domain => domain == null) + .FirstOrDefault(); + } + + /// + /// Gets the deepest wildcard Domain, if any, from a group of Domains, in a node path. + /// + /// The domains. + /// The node path eg '-1,1234,5678'. + /// The current domain root node identifier, or null. + /// The deepest wildcard Domain in the path, or null. + /// Looks _under_ rootNodeId but not _at_ rootNodeId. + public static Domain? FindWildcardDomainInPath(IEnumerable? domains, string path, int? rootNodeId) + { + var stopNodeId = rootNodeId ?? -1; + + return path.Split(Constants.CharArrays.Comma) + .Reverse() + .Select(s => int.Parse(s, CultureInfo.InvariantCulture)) + .TakeWhile(id => id != stopNodeId) + .Select(id => domains?.FirstOrDefault(d => d.ContentId == id && d.IsWildcard)) + .FirstOrDefault(domain => domain != null); + } + + /// + /// Returns the part of a path relative to the uri of a domain. + /// + /// The normalized uri of the domain. + /// The full path of the uri. + /// The path part relative to the uri of the domain. + /// Eg the relative part of /foo/bar/nil to domain example.com/foo is /bar/nil. + public static string PathRelativeToDomain(Uri domainUri, string path) + => path.Substring(domainUri.GetAbsolutePathDecoded().Length).EnsureStartsWith('/'); + + #endregion } diff --git a/src/Umbraco.Core/Routing/IContentFinder.cs b/src/Umbraco.Core/Routing/IContentFinder.cs index ab160715bbff..3e4304fe709e 100644 --- a/src/Umbraco.Core/Routing/IContentFinder.cs +++ b/src/Umbraco.Core/Routing/IContentFinder.cs @@ -1,18 +1,18 @@ -using System.Threading.Tasks; +namespace Umbraco.Cms.Core.Routing; -namespace Umbraco.Cms.Core.Routing +/// +/// Provides a method to try to find and assign an Umbraco document to a PublishedRequest. +/// +public interface IContentFinder { /// - /// Provides a method to try to find and assign an Umbraco document to a PublishedRequest. + /// Tries to find and assign an Umbraco document to a PublishedRequest. /// - public interface IContentFinder - { - /// - /// Tries to find and assign an Umbraco document to a PublishedRequest. - /// - /// The PublishedRequest. - /// A value indicating whether an Umbraco document was found and assigned. - /// Optionally, can also assign the template or anything else on the document request, although that is not required. - Task TryFindContent(IPublishedRequestBuilder request); - } + /// The PublishedRequest. + /// A value indicating whether an Umbraco document was found and assigned. + /// + /// Optionally, can also assign the template or anything else on the document request, although that is not + /// required. + /// + Task TryFindContent(IPublishedRequestBuilder request); } diff --git a/src/Umbraco.Core/Routing/IContentLastChanceFinder.cs b/src/Umbraco.Core/Routing/IContentLastChanceFinder.cs index 19e5f8024636..ad3959ae42d1 100644 --- a/src/Umbraco.Core/Routing/IContentLastChanceFinder.cs +++ b/src/Umbraco.Core/Routing/IContentLastChanceFinder.cs @@ -1,10 +1,10 @@ -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// Provides a method to try to find and assign an Umbraco document to a PublishedRequest +/// when everything else has failed. +/// +/// Identical to but required in order to differentiate them in ioc. +public interface IContentLastChanceFinder : IContentFinder { - /// - /// Provides a method to try to find and assign an Umbraco document to a PublishedRequest - /// when everything else has failed. - /// - /// Identical to but required in order to differentiate them in ioc. - public interface IContentLastChanceFinder : IContentFinder - { } } diff --git a/src/Umbraco.Core/Routing/IMediaUrlProvider.cs b/src/Umbraco.Core/Routing/IMediaUrlProvider.cs index 4478f60334c3..5faf087110ae 100644 --- a/src/Umbraco.Core/Routing/IMediaUrlProvider.cs +++ b/src/Umbraco.Core/Routing/IMediaUrlProvider.cs @@ -1,30 +1,32 @@ -using System; -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// Provides media URL. +/// +public interface IMediaUrlProvider { /// - /// Provides media URL. + /// Gets the URL of a media item. /// - public interface IMediaUrlProvider - { - /// - /// Gets the URL of a media item. - /// - /// The published content. - /// The property alias to resolve the URL from. - /// The URL mode. - /// The variation language. - /// The current absolute URL. - /// The URL for the media. - /// - /// The URL is absolute or relative depending on mode and on current. - /// If the media is multi-lingual, gets the URL for the specified culture or, - /// when no culture is specified, the current culture. - /// The URL provider can ignore the mode and always return an absolute URL, - /// e.g. a cdn URL provider will most likely always return an absolute URL. - /// If the provider is unable to provide a URL, it returns null. - /// - UrlInfo? GetMediaUrl(IPublishedContent content, string propertyAlias, UrlMode mode, string? culture, Uri current); - } + /// The published content. + /// The property alias to resolve the URL from. + /// The URL mode. + /// The variation language. + /// The current absolute URL. + /// The URL for the media. + /// + /// The URL is absolute or relative depending on mode and on current. + /// + /// If the media is multi-lingual, gets the URL for the specified culture or, + /// when no culture is specified, the current culture. + /// + /// + /// The URL provider can ignore the mode and always return an absolute URL, + /// e.g. a cdn URL provider will most likely always return an absolute URL. + /// + /// If the provider is unable to provide a URL, it returns null. + /// + UrlInfo? GetMediaUrl(IPublishedContent content, string propertyAlias, UrlMode mode, string? culture, Uri current); } diff --git a/src/Umbraco.Core/Routing/IPublishedRequest.cs b/src/Umbraco.Core/Routing/IPublishedRequest.cs index 9f68c618d23a..645de414d702 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequest.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequest.cs @@ -1,99 +1,114 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// The result of Umbraco routing built with the +/// +public interface IPublishedRequest { /// - /// The result of Umbraco routing built with the + /// Gets the cleaned up inbound Uri used for routing. /// - public interface IPublishedRequest - { - /// - /// Gets the cleaned up inbound Uri used for routing. - /// - /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. - Uri Uri { get; } + /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. + Uri Uri { get; } - /// - /// Gets the URI decoded absolute path of the - /// - string AbsolutePathDecoded { get; } + /// + /// Gets the URI decoded absolute path of the + /// + string AbsolutePathDecoded { get; } - /// - /// Gets a value indicating the requested content. - /// - IPublishedContent? PublishedContent { get; } + /// + /// Gets a value indicating the requested content. + /// + IPublishedContent? PublishedContent { get; } - /// - /// Gets a value indicating whether the current published content has been obtained - /// from the initial published content following internal redirections exclusively. - /// - /// Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to - /// apply the internal redirect or not, when content is not the initial content. - bool IsInternalRedirect { get; } + /// + /// Gets a value indicating whether the current published content has been obtained + /// from the initial published content following internal redirections exclusively. + /// + /// + /// Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to + /// apply the internal redirect or not, when content is not the initial content. + /// + bool IsInternalRedirect { get; } - /// - /// Gets the template assigned to the request (if any) - /// - ITemplate? Template { get; } + /// + /// Gets the template assigned to the request (if any) + /// + ITemplate? Template { get; } - /// - /// Gets the content request's domain. - /// - /// Is a DomainAndUri object ie a standard Domain plus the fully qualified uri. For example, - /// the Domain may contain "example.com" whereas the Uri will be fully qualified eg "http://example.com/". - DomainAndUri? Domain { get; } + /// + /// Gets the content request's domain. + /// + /// + /// Is a DomainAndUri object ie a standard Domain plus the fully qualified uri. For example, + /// the Domain may contain "example.com" whereas the Uri will be fully qualified eg + /// "http://example.com/". + /// + DomainAndUri? Domain { get; } - /// - /// Gets the content request's culture. - /// - /// - /// This will get mapped to a CultureInfo eventually but CultureInfo are expensive to create so we want to leave that up to the - /// localization middleware to do. See https://github.com/dotnet/aspnetcore/blob/b795ac3546eb3e2f47a01a64feb3020794ca33bb/src/Middleware/Localization/src/RequestLocalizationMiddleware.cs#L165. - /// - string? Culture { get; } + /// + /// Gets the content request's culture. + /// + /// + /// This will get mapped to a CultureInfo eventually but CultureInfo are expensive to create so we want to leave that + /// up to the + /// localization middleware to do. See + /// https://github.com/dotnet/aspnetcore/blob/b795ac3546eb3e2f47a01a64feb3020794ca33bb/src/Middleware/Localization/src/RequestLocalizationMiddleware.cs#L165. + /// + string? Culture { get; } - /// - /// Gets the url to redirect to, when the content request triggers a redirect. - /// - string? RedirectUrl { get; } + /// + /// Gets the url to redirect to, when the content request triggers a redirect. + /// + string? RedirectUrl { get; } - /// - /// Gets the content request http response status code. - /// - /// Does not actually set the http response status code, only registers that the response - /// should use the specified code. The code will or will not be used, in due time. - int? ResponseStatusCode { get; } + /// + /// Gets the content request http response status code. + /// + /// + /// Does not actually set the http response status code, only registers that the response + /// should use the specified code. The code will or will not be used, in due time. + /// + int? ResponseStatusCode { get; } - /// - /// Gets a list of Extensions to append to the Response.Cache object. - /// - IReadOnlyList? CacheExtensions { get; } + /// + /// Gets a list of Extensions to append to the Response.Cache object. + /// + IReadOnlyList? CacheExtensions { get; } - /// - /// Gets a dictionary of Headers to append to the Response object. - /// - IReadOnlyDictionary? Headers { get; } + /// + /// Gets a dictionary of Headers to append to the Response object. + /// + IReadOnlyDictionary? Headers { get; } - /// - /// Gets a value indicating whether the no-cache value should be added to the Cache-Control header - /// - bool SetNoCacheHeader { get; } + /// + /// Gets a value indicating whether the no-cache value should be added to the Cache-Control header + /// + bool SetNoCacheHeader { get; } - /// - /// Gets a value indicating whether the Umbraco Backoffice should ignore a collision for this request. - /// - /// - /// This is an uncommon API used for edge cases with complex routing and would be used - /// by developers to configure the request to disable collision checks in . - /// This flag is based on previous Umbraco versions but it is not clear how this flag can be set by developers since - /// collission checking only occurs in the back office which is launched by - /// for which events do not execute. - /// More can be read about this setting here: https://github.com/umbraco/Umbraco-CMS/pull/2148, https://issues.umbraco.org/issue/U4-10345 - /// but it's still unclear how this was used. - /// - bool IgnorePublishedContentCollisions { get; } - } + /// + /// Gets a value indicating whether the Umbraco Backoffice should ignore a collision for this request. + /// + /// + /// + /// This is an uncommon API used for edge cases with complex routing and would be used + /// by developers to configure the request to disable collision checks in . + /// + /// + /// This flag is based on previous Umbraco versions but it is not clear how this flag can be set by developers + /// since + /// collission checking only occurs in the back office which is launched by + /// + /// for which events do not execute. + /// + /// + /// More can be read about this setting here: https://github.com/umbraco/Umbraco-CMS/pull/2148, + /// https://issues.umbraco.org/issue/U4-10345 + /// but it's still unclear how this was used. + /// + /// + bool IgnorePublishedContentCollisions { get; } } diff --git a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs index e5a915d682c5..f6cdafee78d1 100644 --- a/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs +++ b/src/Umbraco.Core/Routing/IPublishedRequestBuilder.cs @@ -1,161 +1,175 @@ -using System; -using System.Collections.Generic; using System.Globalization; using System.Net; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// Used by to route inbound requests to Umbraco content +/// +public interface IPublishedRequestBuilder { /// - /// Used by to route inbound requests to Umbraco content - /// - public interface IPublishedRequestBuilder - { - /// - /// Gets the cleaned up inbound Uri used for routing. - /// - /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. - Uri Uri { get; } - - /// - /// Gets the URI decoded absolute path of the - /// - string AbsolutePathDecoded { get; } - - /// - /// Gets the assigned (if any) - /// - DomainAndUri? Domain { get; } - - /// - /// Gets the assigned (if any) - /// - string? Culture { get; } - - /// - /// Gets a value indicating whether the current published content has been obtained - /// from the initial published content following internal redirections exclusively. - /// - /// Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to - /// apply the internal redirect or not, when content is not the initial content. - bool IsInternalRedirect { get; } - - /// - /// Gets the content request http response status code. - /// - int? ResponseStatusCode { get; } - - /// - /// Gets the current assigned (if any) - /// - IPublishedContent? PublishedContent { get; } - - /// - /// Gets the template assigned to the request (if any) - /// - ITemplate? Template { get; } - - /// - /// Builds the - /// - IPublishedRequest Build(); - - /// - /// Sets the domain for the request which also sets the culture - /// - IPublishedRequestBuilder SetDomain(DomainAndUri domain); - - /// - /// Sets the culture for the request - /// - IPublishedRequestBuilder SetCulture(string? culture); - - /// - /// Sets the found for the request - /// - /// Setting the content clears the template and redirect - IPublishedRequestBuilder SetPublishedContent(IPublishedContent? content); - - /// - /// Sets the requested content, following an internal redirect. - /// - /// The requested content. - /// Since this sets the content, it will clear the template - IPublishedRequestBuilder SetInternalRedirect(IPublishedContent content); - - /// - /// Tries to set the template to use to display the requested content. - /// - /// The alias of the template. - /// A value indicating whether a valid template with the specified alias was found. - /// - /// Successfully setting the template does refresh RenderingEngine. - /// If setting the template fails, then the previous template (if any) remains in place. - /// - bool TrySetTemplate(string alias); - - /// - /// Sets the template to use to display the requested content. - /// - /// The template. - /// Setting the template does refresh RenderingEngine. - IPublishedRequestBuilder SetTemplate(ITemplate? template); - - /// - /// Indicates that the content request should trigger a permanent redirect (301). - /// - /// The url to redirect to. - /// Does not actually perform a redirect, only registers that the response should - /// redirect. Redirect will or will not take place in due time. - IPublishedRequestBuilder SetRedirectPermanent(string url); - - /// - /// Indicates that the content request should trigger a redirect, with a specified status code. - /// - /// The url to redirect to. - /// The status code (300-308). - /// Does not actually perform a redirect, only registers that the response should - /// redirect. Redirect will or will not take place in due time. - IPublishedRequestBuilder SetRedirect(string url, int status = (int)HttpStatusCode.Redirect); - - /// - /// Sets the http response status code, along with an optional associated description. - /// - /// The http status code. - /// Does not actually set the http response status code and description, only registers that - /// the response should use the specified code and description. The code and description will or will - /// not be used, in due time. - IPublishedRequestBuilder SetResponseStatus(int code); - - /// - /// Sets the no-cache value to the Cache-Control header - /// - /// True to set the header, false to not set it - IPublishedRequestBuilder SetNoCacheHeader(bool setHeader); - - /// - /// Sets a list of Extensions to append to the Response.Cache object. - /// - IPublishedRequestBuilder SetCacheExtensions(IEnumerable cacheExtensions); - - /// - /// Sets a dictionary of Headers to append to the Response object. - /// - IPublishedRequestBuilder SetHeaders(IReadOnlyDictionary headers); - - /// - /// Can be called to configure the result to ignore URL collisions - /// - /// - /// This is an uncommon API used for edge cases with complex routing and would be used - /// by developers to configure the request to disable collision checks in . - /// This flag is based on previous Umbraco versions but it is not clear how this flag can be set by developers since - /// collission checking only occurs in the back office which is launched by - /// for which events do not execute. - /// More can be read about this setting here: https://github.com/umbraco/Umbraco-CMS/pull/2148, https://issues.umbraco.org/issue/U4-10345 - /// but it's still unclear how this was used. - /// - void IgnorePublishedContentCollisions(); - } + /// Gets the cleaned up inbound Uri used for routing. + /// + /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. + Uri Uri { get; } + + /// + /// Gets the URI decoded absolute path of the + /// + string AbsolutePathDecoded { get; } + + /// + /// Gets the assigned (if any) + /// + DomainAndUri? Domain { get; } + + /// + /// Gets the assigned (if any) + /// + string? Culture { get; } + + /// + /// Gets a value indicating whether the current published content has been obtained + /// from the initial published content following internal redirections exclusively. + /// + /// + /// Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to + /// apply the internal redirect or not, when content is not the initial content. + /// + bool IsInternalRedirect { get; } + + /// + /// Gets the content request http response status code. + /// + int? ResponseStatusCode { get; } + + /// + /// Gets the current assigned (if any) + /// + IPublishedContent? PublishedContent { get; } + + /// + /// Gets the template assigned to the request (if any) + /// + ITemplate? Template { get; } + + /// + /// Builds the + /// + IPublishedRequest Build(); + + /// + /// Sets the domain for the request which also sets the culture + /// + IPublishedRequestBuilder SetDomain(DomainAndUri domain); + + /// + /// Sets the culture for the request + /// + IPublishedRequestBuilder SetCulture(string? culture); + + /// + /// Sets the found for the request + /// + /// Setting the content clears the template and redirect + IPublishedRequestBuilder SetPublishedContent(IPublishedContent? content); + + /// + /// Sets the requested content, following an internal redirect. + /// + /// The requested content. + /// Since this sets the content, it will clear the template + IPublishedRequestBuilder SetInternalRedirect(IPublishedContent content); + + /// + /// Tries to set the template to use to display the requested content. + /// + /// The alias of the template. + /// A value indicating whether a valid template with the specified alias was found. + /// + /// Successfully setting the template does refresh RenderingEngine. + /// If setting the template fails, then the previous template (if any) remains in place. + /// + bool TrySetTemplate(string alias); + + /// + /// Sets the template to use to display the requested content. + /// + /// The template. + /// Setting the template does refresh RenderingEngine. + IPublishedRequestBuilder SetTemplate(ITemplate? template); + + /// + /// Indicates that the content request should trigger a permanent redirect (301). + /// + /// The url to redirect to. + /// + /// Does not actually perform a redirect, only registers that the response should + /// redirect. Redirect will or will not take place in due time. + /// + IPublishedRequestBuilder SetRedirectPermanent(string url); + + /// + /// Indicates that the content request should trigger a redirect, with a specified status code. + /// + /// The url to redirect to. + /// The status code (300-308). + /// + /// Does not actually perform a redirect, only registers that the response should + /// redirect. Redirect will or will not take place in due time. + /// + IPublishedRequestBuilder SetRedirect(string url, int status = (int)HttpStatusCode.Redirect); + + /// + /// Sets the http response status code, along with an optional associated description. + /// + /// The http status code. + /// + /// Does not actually set the http response status code and description, only registers that + /// the response should use the specified code and description. The code and description will or will + /// not be used, in due time. + /// + IPublishedRequestBuilder SetResponseStatus(int code); + + /// + /// Sets the no-cache value to the Cache-Control header + /// + /// True to set the header, false to not set it + IPublishedRequestBuilder SetNoCacheHeader(bool setHeader); + + /// + /// Sets a list of Extensions to append to the Response.Cache object. + /// + IPublishedRequestBuilder SetCacheExtensions(IEnumerable cacheExtensions); + + /// + /// Sets a dictionary of Headers to append to the Response object. + /// + IPublishedRequestBuilder SetHeaders(IReadOnlyDictionary headers); + + /// + /// Can be called to configure the result to ignore URL collisions + /// + /// + /// + /// This is an uncommon API used for edge cases with complex routing and would be used + /// by developers to configure the request to disable collision checks in . + /// + /// + /// This flag is based on previous Umbraco versions but it is not clear how this flag can be set by developers + /// since + /// collission checking only occurs in the back office which is launched by + /// + /// for which events do not execute. + /// + /// + /// More can be read about this setting here: https://github.com/umbraco/Umbraco-CMS/pull/2148, + /// https://issues.umbraco.org/issue/U4-10345 + /// but it's still unclear how this was used. + /// + /// + void IgnorePublishedContentCollisions(); } diff --git a/src/Umbraco.Core/Routing/IPublishedRouter.cs b/src/Umbraco.Core/Routing/IPublishedRouter.cs index a3c041768f40..cf6ac6140657 100644 --- a/src/Umbraco.Core/Routing/IPublishedRouter.cs +++ b/src/Umbraco.Core/Routing/IPublishedRouter.cs @@ -1,52 +1,50 @@ -using System; -using System.Threading.Tasks; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// Routes requests. +/// +public interface IPublishedRouter { /// - /// Routes requests. + /// Creates a published request. /// - public interface IPublishedRouter - { - /// - /// Creates a published request. - /// - /// The current request Uri. - /// A published request builder. - Task CreateRequestAsync(Uri uri); + /// The current request Uri. + /// A published request builder. + Task CreateRequestAsync(Uri uri); - /// - /// Sends a through the routing pipeline and builds a result. - /// - /// The request. - /// The options. - /// The built instance. - Task RouteRequestAsync(IPublishedRequestBuilder request, RouteRequestOptions options); + /// + /// Sends a through the routing pipeline and builds a result. + /// + /// The request. + /// The options. + /// The built instance. + Task RouteRequestAsync(IPublishedRequestBuilder request, RouteRequestOptions options); - /// - /// Updates the request to use the specified item, or NULL - /// - /// The request. - /// - /// - /// A new based on values from the original - /// and with the re-routed values based on the passed in - /// - /// - /// This method is used for 2 cases: - /// - When the rendering content needs to change due to Public Access rules. - /// - When there is nothing to render due to circumstances such as no template files. In this case, NULL is used as the parameter. - /// - /// - /// This method is invoked when the pipeline decides it cannot render - /// the request, for whatever reason, and wants to force it to be re-routed - /// and rendered as if no document were found (404). - /// This occurs if there is no template found and route hijacking was not matched. - /// In that case it's the same as if there was no content which means even if there was - /// content matched we want to run the request through the last chance finders. - /// - /// - Task UpdateRequestAsync(IPublishedRequest request, IPublishedContent? publishedContent); - } + /// + /// Updates the request to use the specified item, or NULL + /// + /// The request. + /// + /// + /// A new based on values from the original + /// and with the re-routed values based on the passed in + /// + /// + /// This method is used for 2 cases: + /// - When the rendering content needs to change due to Public Access rules. + /// - When there is nothing to render due to circumstances such as no template files. In this case, NULL is used as + /// the parameter. + /// + /// + /// This method is invoked when the pipeline decides it cannot render + /// the request, for whatever reason, and wants to force it to be re-routed + /// and rendered as if no document were found (404). + /// This occurs if there is no template found and route hijacking was not matched. + /// In that case it's the same as if there was no content which means even if there was + /// content matched we want to run the request through the last chance finders. + /// + /// + Task UpdateRequestAsync(IPublishedRequest request, IPublishedContent? publishedContent); } diff --git a/src/Umbraco.Core/Routing/IPublishedUrlProvider.cs b/src/Umbraco.Core/Routing/IPublishedUrlProvider.cs index fd52bc78057f..358041e6173a 100644 --- a/src/Umbraco.Core/Routing/IPublishedUrlProvider.cs +++ b/src/Umbraco.Core/Routing/IPublishedUrlProvider.cs @@ -1,104 +1,112 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +public interface IPublishedUrlProvider { - public interface IPublishedUrlProvider - { - /// - /// Gets or sets the provider url mode. - /// - UrlMode Mode { get; set; } + /// + /// Gets or sets the provider url mode. + /// + UrlMode Mode { get; set; } - /// - /// Gets the url of a published content. - /// - /// The published content identifier. - /// The url mode. - /// A culture. - /// The current absolute url. - /// The url for the published content. - string GetUrl(Guid id, UrlMode mode = UrlMode.Default, string? culture = null, Uri? current = null); + /// + /// Gets the url of a published content. + /// + /// The published content identifier. + /// The url mode. + /// A culture. + /// The current absolute url. + /// The url for the published content. + string GetUrl(Guid id, UrlMode mode = UrlMode.Default, string? culture = null, Uri? current = null); - /// - /// Gets the url of a published content. - /// - /// The published content identifier. - /// The url mode. - /// A culture. - /// The current absolute url. - /// The url for the published content. - string GetUrl(int id, UrlMode mode = UrlMode.Default, string? culture = null, Uri? current = null); + /// + /// Gets the url of a published content. + /// + /// The published content identifier. + /// The url mode. + /// A culture. + /// The current absolute url. + /// The url for the published content. + string GetUrl(int id, UrlMode mode = UrlMode.Default, string? culture = null, Uri? current = null); - /// - /// Gets the url of a published content. - /// - /// The published content. - /// The url mode. - /// A culture. - /// The current absolute url. - /// The url for the published content. - /// - /// The url is absolute or relative depending on mode and on current. - /// If the published content is multi-lingual, gets the url for the specified culture or, - /// when no culture is specified, the current culture. - /// If the provider is unable to provide a url, it returns "#". - /// - string GetUrl(IPublishedContent content, UrlMode mode = UrlMode.Default, string? culture = null, Uri? current = null); + /// + /// Gets the url of a published content. + /// + /// The published content. + /// The url mode. + /// A culture. + /// The current absolute url. + /// The url for the published content. + /// + /// The url is absolute or relative depending on mode and on current. + /// + /// If the published content is multi-lingual, gets the url for the specified culture or, + /// when no culture is specified, the current culture. + /// + /// If the provider is unable to provide a url, it returns "#". + /// + string GetUrl(IPublishedContent content, UrlMode mode = UrlMode.Default, string? culture = null, + Uri? current = null); - string GetUrlFromRoute(int id, string? route, string? culture); + string GetUrlFromRoute(int id, string? route, string? culture); - /// - /// Gets the other urls of a published content. - /// - /// The published content id. - /// The other urls for the published content. - /// - /// Other urls are those that GetUrl would not return in the current context, but would be valid - /// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...). - /// The results depend on the current url. - /// - IEnumerable GetOtherUrls(int id); + /// + /// Gets the other urls of a published content. + /// + /// The published content id. + /// The other urls for the published content. + /// + /// + /// Other urls are those that GetUrl would not return in the current context, but would be valid + /// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...). + /// + /// The results depend on the current url. + /// + IEnumerable GetOtherUrls(int id); - /// - /// Gets the other urls of a published content. - /// - /// The published content id. - /// The current absolute url. - /// The other urls for the published content. - /// - /// Other urls are those that GetUrl would not return in the current context, but would be valid - /// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...). - /// - IEnumerable GetOtherUrls(int id, Uri current); + /// + /// Gets the other urls of a published content. + /// + /// The published content id. + /// The current absolute url. + /// The other urls for the published content. + /// + /// + /// Other urls are those that GetUrl would not return in the current context, but would be valid + /// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...). + /// + /// + IEnumerable GetOtherUrls(int id, Uri current); - /// - /// Gets the url of a media item. - /// - /// - /// - /// - /// - /// - /// - string GetMediaUrl(Guid id, UrlMode mode = UrlMode.Default, string? culture = null, string propertyAlias = Constants.Conventions.Media.File, Uri? current = null); + /// + /// Gets the url of a media item. + /// + /// + /// + /// + /// + /// + /// + string GetMediaUrl(Guid id, UrlMode mode = UrlMode.Default, string? culture = null, + string propertyAlias = Constants.Conventions.Media.File, Uri? current = null); - /// - /// Gets the url of a media item. - /// - /// The published content. - /// The property alias to resolve the url from. - /// The url mode. - /// The variation language. - /// The current absolute url. - /// The url for the media. - /// - /// The url is absolute or relative depending on mode and on current. - /// If the media is multi-lingual, gets the url for the specified culture or, - /// when no culture is specified, the current culture. - /// If the provider is unable to provide a url, it returns . - /// - string GetMediaUrl(IPublishedContent? content, UrlMode mode = UrlMode.Default, string? culture = null, string propertyAlias = Constants.Conventions.Media.File, Uri? current = null); - } + /// + /// Gets the url of a media item. + /// + /// The published content. + /// The property alias to resolve the url from. + /// The url mode. + /// The variation language. + /// The current absolute url. + /// The url for the media. + /// + /// The url is absolute or relative depending on mode and on current. + /// + /// If the media is multi-lingual, gets the url for the specified culture or, + /// when no culture is specified, the current culture. + /// + /// If the provider is unable to provide a url, it returns . + /// + string GetMediaUrl(IPublishedContent? content, UrlMode mode = UrlMode.Default, string? culture = null, + string propertyAlias = Constants.Conventions.Media.File, Uri? current = null); } diff --git a/src/Umbraco.Core/Routing/ISiteDomainMapper.cs b/src/Umbraco.Core/Routing/ISiteDomainMapper.cs index e9ca34477cde..d5e81c6eb317 100644 --- a/src/Umbraco.Core/Routing/ISiteDomainMapper.cs +++ b/src/Umbraco.Core/Routing/ISiteDomainMapper.cs @@ -1,45 +1,49 @@ -using System; -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Routing; -namespace Umbraco.Cms.Core.Routing +/// +/// Provides utilities to handle site domains. +/// +public interface ISiteDomainMapper { /// - /// Provides utilities to handle site domains. + /// Filters a list of DomainAndUri to pick one that best matches the current request. /// - public interface ISiteDomainMapper - { - /// - /// Filters a list of DomainAndUri to pick one that best matches the current request. - /// - /// The list of DomainAndUri to filter. - /// The Uri of the current request. - /// A culture. - /// The default culture. - /// The selected DomainAndUri. - /// - /// If the filter is invoked then is _not_ empty and - /// is _not_ null, and could not be - /// matched with anything in . - /// The may be null, but when non-null, it can be used - /// to help pick the best matches. - /// The filter _must_ return something else an exception will be thrown. - /// - DomainAndUri? MapDomain(IReadOnlyCollection domainAndUris, Uri current, string? culture, string? defaultCulture); + /// The list of DomainAndUri to filter. + /// The Uri of the current request. + /// A culture. + /// The default culture. + /// The selected DomainAndUri. + /// + /// + /// If the filter is invoked then is _not_ empty and + /// is _not_ null, and could not be + /// matched with anything in . + /// + /// + /// The may be null, but when non-null, it can be used + /// to help pick the best matches. + /// + /// The filter _must_ return something else an exception will be thrown. + /// + DomainAndUri? MapDomain(IReadOnlyCollection domainAndUris, Uri current, string? culture, + string? defaultCulture); - /// - /// Filters a list of DomainAndUri to pick those that best matches the current request. - /// - /// The list of DomainAndUri to filter. - /// The Uri of the current request. - /// A value indicating whether to exclude the current/default domain. - /// A culture. - /// The default culture. - /// The selected DomainAndUri items. - /// - /// The filter must return something, even empty, else an exception will be thrown. - /// The may be null, but when non-null, it can be used - /// to help pick the best matches. - /// - IEnumerable MapDomains(IReadOnlyCollection domainAndUris, Uri current, bool excludeDefault, string? culture, string? defaultCulture); - } + /// + /// Filters a list of DomainAndUri to pick those that best matches the current request. + /// + /// The list of DomainAndUri to filter. + /// The Uri of the current request. + /// A value indicating whether to exclude the current/default domain. + /// A culture. + /// The default culture. + /// The selected DomainAndUri items. + /// + /// The filter must return something, even empty, else an exception will be thrown. + /// + /// The may be null, but when non-null, it can be used + /// to help pick the best matches. + /// + /// + IEnumerable MapDomains(IReadOnlyCollection domainAndUris, Uri current, + bool excludeDefault, string? culture, string? defaultCulture); } diff --git a/src/Umbraco.Core/Routing/IUrlProvider.cs b/src/Umbraco.Core/Routing/IUrlProvider.cs index 0223b39c1d6d..01954baf7386 100644 --- a/src/Umbraco.Core/Routing/IUrlProvider.cs +++ b/src/Umbraco.Core/Routing/IUrlProvider.cs @@ -1,40 +1,41 @@ -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// Provides URLs. +/// +public interface IUrlProvider { /// - /// Provides URLs. + /// Gets the URL of a published content. /// - public interface IUrlProvider - { - /// - /// Gets the URL of a published content. - /// - /// The published content. - /// The URL mode. - /// A culture. - /// The current absolute URL. - /// The URL for the published content. - /// - /// The URL is absolute or relative depending on mode and on current. - /// If the published content is multi-lingual, gets the URL for the specified culture or, - /// when no culture is specified, the current culture. - /// If the provider is unable to provide a URL, it should return null. - /// - UrlInfo? GetUrl(IPublishedContent content, UrlMode mode, string? culture, Uri current); + /// The published content. + /// The URL mode. + /// A culture. + /// The current absolute URL. + /// The URL for the published content. + /// + /// The URL is absolute or relative depending on mode and on current. + /// + /// If the published content is multi-lingual, gets the URL for the specified culture or, + /// when no culture is specified, the current culture. + /// + /// If the provider is unable to provide a URL, it should return null. + /// + UrlInfo? GetUrl(IPublishedContent content, UrlMode mode, string? culture, Uri current); - /// - /// Gets the other URLs of a published content. - /// - /// The published content id. - /// The current absolute URL. - /// The other URLs for the published content. - /// - /// Other URLs are those that GetUrl would not return in the current context, but would be valid - /// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...). - /// - IEnumerable GetOtherUrls(int id, Uri current); - } + /// + /// Gets the other URLs of a published content. + /// + /// The published content id. + /// The current absolute URL. + /// The other URLs for the published content. + /// + /// + /// Other URLs are those that GetUrl would not return in the current context, but would be valid + /// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...). + /// + /// + IEnumerable GetOtherUrls(int id, Uri current); } diff --git a/src/Umbraco.Core/Routing/MediaUrlProviderCollection.cs b/src/Umbraco.Core/Routing/MediaUrlProviderCollection.cs index 264be41d6076..51be5c934350 100644 --- a/src/Umbraco.Core/Routing/MediaUrlProviderCollection.cs +++ b/src/Umbraco.Core/Routing/MediaUrlProviderCollection.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +public class MediaUrlProviderCollection : BuilderCollectionBase { - public class MediaUrlProviderCollection : BuilderCollectionBase + public MediaUrlProviderCollection(Func> items) : base(items) { - public MediaUrlProviderCollection(Func> items) : base(items) - { - } } } diff --git a/src/Umbraco.Core/Routing/MediaUrlProviderCollectionBuilder.cs b/src/Umbraco.Core/Routing/MediaUrlProviderCollectionBuilder.cs index d778540e3110..366b74a5eb6f 100644 --- a/src/Umbraco.Core/Routing/MediaUrlProviderCollectionBuilder.cs +++ b/src/Umbraco.Core/Routing/MediaUrlProviderCollectionBuilder.cs @@ -1,9 +1,9 @@ using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +public class MediaUrlProviderCollectionBuilder : OrderedCollectionBuilderBase { - public class MediaUrlProviderCollectionBuilder : OrderedCollectionBuilderBase - { - protected override MediaUrlProviderCollectionBuilder This => this; - } + protected override MediaUrlProviderCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/Routing/PublishedRequest.cs b/src/Umbraco.Core/Routing/PublishedRequest.cs index 50328cbfddc2..6e0a07da7895 100644 --- a/src/Umbraco.Core/Routing/PublishedRequest.cs +++ b/src/Umbraco.Core/Routing/PublishedRequest.cs @@ -1,70 +1,69 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Core.Routing -{ +namespace Umbraco.Cms.Core.Routing; - public class PublishedRequest : IPublishedRequest +public class PublishedRequest : IPublishedRequest +{ + /// + /// Initializes a new instance of the class. + /// + public PublishedRequest(Uri uri, string absolutePathDecoded, IPublishedContent? publishedContent, + bool isInternalRedirect, ITemplate? template, DomainAndUri? domain, string? culture, string? redirectUrl, + int? responseStatusCode, IReadOnlyList? cacheExtensions, IReadOnlyDictionary? headers, + bool setNoCacheHeader, bool ignorePublishedContentCollisions) { - /// - /// Initializes a new instance of the class. - /// - public PublishedRequest(Uri uri, string absolutePathDecoded, IPublishedContent? publishedContent, bool isInternalRedirect, ITemplate? template, DomainAndUri? domain, string? culture, string? redirectUrl, int? responseStatusCode, IReadOnlyList? cacheExtensions, IReadOnlyDictionary? headers, bool setNoCacheHeader, bool ignorePublishedContentCollisions) - { - Uri = uri ?? throw new ArgumentNullException(nameof(uri)); - AbsolutePathDecoded = absolutePathDecoded ?? throw new ArgumentNullException(nameof(absolutePathDecoded)); - PublishedContent = publishedContent; - IsInternalRedirect = isInternalRedirect; - Template = template; - Domain = domain; - Culture = culture; - RedirectUrl = redirectUrl; - ResponseStatusCode = responseStatusCode; - CacheExtensions = cacheExtensions; - Headers = headers; - SetNoCacheHeader = setNoCacheHeader; - IgnorePublishedContentCollisions = ignorePublishedContentCollisions; - } + Uri = uri ?? throw new ArgumentNullException(nameof(uri)); + AbsolutePathDecoded = absolutePathDecoded ?? throw new ArgumentNullException(nameof(absolutePathDecoded)); + PublishedContent = publishedContent; + IsInternalRedirect = isInternalRedirect; + Template = template; + Domain = domain; + Culture = culture; + RedirectUrl = redirectUrl; + ResponseStatusCode = responseStatusCode; + CacheExtensions = cacheExtensions; + Headers = headers; + SetNoCacheHeader = setNoCacheHeader; + IgnorePublishedContentCollisions = ignorePublishedContentCollisions; + } - /// - public Uri Uri { get; } + /// + public Uri Uri { get; } - /// - public string AbsolutePathDecoded { get; } + /// + public string AbsolutePathDecoded { get; } - /// - public bool IgnorePublishedContentCollisions { get; } + /// + public bool IgnorePublishedContentCollisions { get; } - /// - public IPublishedContent? PublishedContent { get; } + /// + public IPublishedContent? PublishedContent { get; } - /// - public bool IsInternalRedirect { get; } + /// + public bool IsInternalRedirect { get; } - /// - public ITemplate? Template { get; } + /// + public ITemplate? Template { get; } - /// - public DomainAndUri? Domain { get; } + /// + public DomainAndUri? Domain { get; } - /// - public string? Culture { get; } + /// + public string? Culture { get; } - /// - public string? RedirectUrl { get; } + /// + public string? RedirectUrl { get; } - /// - public int? ResponseStatusCode { get; } + /// + public int? ResponseStatusCode { get; } - /// - public IReadOnlyList? CacheExtensions { get; } + /// + public IReadOnlyList? CacheExtensions { get; } - /// - public IReadOnlyDictionary? Headers { get; } + /// + public IReadOnlyDictionary? Headers { get; } - /// - public bool SetNoCacheHeader { get; } - } + /// + public bool SetNoCacheHeader { get; } } diff --git a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs index 128c81f605f7..180033dd33a8 100644 --- a/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs +++ b/src/Umbraco.Core/Routing/PublishedRequestBuilder.cs @@ -1,204 +1,200 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Net; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +public class PublishedRequestBuilder : IPublishedRequestBuilder { - public class PublishedRequestBuilder : IPublishedRequestBuilder + private readonly IFileService _fileService; + private bool _cacheability; + private IReadOnlyList? _cacheExtensions; + private IReadOnlyDictionary? _headers; + private bool _ignorePublishedContentCollisions; + private IPublishedContent? _publishedContent; + private string? _redirectUrl; + private HttpStatusCode? _responseStatus; + + /// + /// Initializes a new instance of the class. + /// + public PublishedRequestBuilder(Uri uri, IFileService fileService) { - private readonly IFileService _fileService; - private IReadOnlyDictionary? _headers; - private bool _cacheability; - private IReadOnlyList? _cacheExtensions; - private string? _redirectUrl; - private HttpStatusCode? _responseStatus; - private IPublishedContent? _publishedContent; - private bool _ignorePublishedContentCollisions; - - /// - /// Initializes a new instance of the class. - /// - public PublishedRequestBuilder(Uri uri, IFileService fileService) - { - Uri = uri; - AbsolutePathDecoded = uri.GetAbsolutePathDecoded(); - _fileService = fileService; - } + Uri = uri; + AbsolutePathDecoded = uri.GetAbsolutePathDecoded(); + _fileService = fileService; + } - /// - public Uri Uri { get; } + /// + public Uri Uri { get; } - /// - public string AbsolutePathDecoded { get; } + /// + public string AbsolutePathDecoded { get; } - /// - public DomainAndUri? Domain { get; private set; } + /// + public DomainAndUri? Domain { get; private set; } - /// - public string? Culture { get; private set; } + /// + public string? Culture { get; private set; } - /// - public ITemplate? Template { get; private set; } + /// + public ITemplate? Template { get; private set; } - /// - public bool IsInternalRedirect { get; private set; } + /// + public bool IsInternalRedirect { get; private set; } - /// - public int? ResponseStatusCode => _responseStatus.HasValue ? (int?)_responseStatus : null; + /// + public int? ResponseStatusCode => _responseStatus.HasValue ? (int?)_responseStatus : null; - /// - public IPublishedContent? PublishedContent + /// + public IPublishedContent? PublishedContent + { + get => _publishedContent; + private set { - get => _publishedContent; - private set - { - _publishedContent = value; - IsInternalRedirect = false; - Template = null; - } + _publishedContent = value; + IsInternalRedirect = false; + Template = null; } + } - /// - public IPublishedRequest Build() => new PublishedRequest( - Uri, - AbsolutePathDecoded, - PublishedContent, - IsInternalRedirect, - Template, - Domain, - Culture, - _redirectUrl, - _responseStatus.HasValue ? (int?)_responseStatus : null, - _cacheExtensions, - _headers, - _cacheability, - _ignorePublishedContentCollisions); - - /// - public IPublishedRequestBuilder SetNoCacheHeader(bool cacheability) - { - _cacheability = cacheability; - return this; - } + /// + public IPublishedRequest Build() => new PublishedRequest( + Uri, + AbsolutePathDecoded, + PublishedContent, + IsInternalRedirect, + Template, + Domain, + Culture, + _redirectUrl, + _responseStatus.HasValue ? (int?)_responseStatus : null, + _cacheExtensions, + _headers, + _cacheability, + _ignorePublishedContentCollisions); + + /// + public IPublishedRequestBuilder SetNoCacheHeader(bool cacheability) + { + _cacheability = cacheability; + return this; + } - /// - public IPublishedRequestBuilder SetCacheExtensions(IEnumerable cacheExtensions) - { - _cacheExtensions = cacheExtensions.ToList(); - return this; - } + /// + public IPublishedRequestBuilder SetCacheExtensions(IEnumerable cacheExtensions) + { + _cacheExtensions = cacheExtensions.ToList(); + return this; + } - /// - public IPublishedRequestBuilder SetCulture(string? culture) - { - Culture = culture; - return this; - } + /// + public IPublishedRequestBuilder SetCulture(string? culture) + { + Culture = culture; + return this; + } - /// - public IPublishedRequestBuilder SetDomain(DomainAndUri domain) - { - Domain = domain; - SetCulture(domain.Culture); - return this; - } + /// + public IPublishedRequestBuilder SetDomain(DomainAndUri domain) + { + Domain = domain; + SetCulture(domain.Culture); + return this; + } + + /// + public IPublishedRequestBuilder SetHeaders(IReadOnlyDictionary headers) + { + _headers = headers; + return this; + } + + /// + public IPublishedRequestBuilder SetInternalRedirect(IPublishedContent content) + { + // unless a template has been set already by the finder, + // template should be null at that point. - /// - public IPublishedRequestBuilder SetHeaders(IReadOnlyDictionary headers) + // redirecting to self + if (PublishedContent != null && content.Id == PublishedContent.Id) { - _headers = headers; + // no need to set PublishedContent, we're done + IsInternalRedirect = true; return this; } - /// - public IPublishedRequestBuilder SetInternalRedirect(IPublishedContent content) - { - // unless a template has been set already by the finder, - // template should be null at that point. + // else - // redirecting to self - if (PublishedContent != null && content.Id == PublishedContent.Id) - { - // no need to set PublishedContent, we're done - IsInternalRedirect = true; - return this; - } + // set published content - this resets the template, and sets IsInternalRedirect to false + PublishedContent = content; + IsInternalRedirect = true; - // else + return this; + } - // set published content - this resets the template, and sets IsInternalRedirect to false - PublishedContent = content; - IsInternalRedirect = true; + /// + public IPublishedRequestBuilder SetPublishedContent(IPublishedContent? content) + { + PublishedContent = content; + IsInternalRedirect = false; + return this; + } - return this; - } + /// + public IPublishedRequestBuilder SetRedirect(string url, int status = (int)HttpStatusCode.Redirect) + { + _redirectUrl = url; + _responseStatus = (HttpStatusCode)status; + return this; + } - /// - public IPublishedRequestBuilder SetPublishedContent(IPublishedContent? content) - { - PublishedContent = content; - IsInternalRedirect = false; - return this; - } + /// + public IPublishedRequestBuilder SetRedirectPermanent(string url) + { + _redirectUrl = url; + _responseStatus = HttpStatusCode.Moved; + return this; + } - /// - public IPublishedRequestBuilder SetRedirect(string url, int status = (int)HttpStatusCode.Redirect) - { - _redirectUrl = url; - _responseStatus = (HttpStatusCode)status; - return this; - } + /// + public IPublishedRequestBuilder SetResponseStatus(int code) + { + _responseStatus = (HttpStatusCode)code; + return this; + } - /// - public IPublishedRequestBuilder SetRedirectPermanent(string url) - { - _redirectUrl = url; - _responseStatus = HttpStatusCode.Moved; - return this; - } + /// + public IPublishedRequestBuilder SetTemplate(ITemplate? template) + { + Template = template; + return this; + } - /// - public IPublishedRequestBuilder SetResponseStatus(int code) + /// + public bool TrySetTemplate(string alias) + { + if (string.IsNullOrWhiteSpace(alias)) { - _responseStatus = (HttpStatusCode)code; - return this; + Template = null; + return true; } - /// - public IPublishedRequestBuilder SetTemplate(ITemplate? template) - { - Template = template; - return this; - } + // NOTE - can we still get it with whitespaces in it due to old legacy bugs? + alias = alias.Replace(" ", string.Empty); - /// - public bool TrySetTemplate(string alias) + ITemplate? model = _fileService.GetTemplate(alias); + if (model == null) { - if (string.IsNullOrWhiteSpace(alias)) - { - Template = null; - return true; - } - - // NOTE - can we still get it with whitespaces in it due to old legacy bugs? - alias = alias.Replace(" ", string.Empty); - - ITemplate? model = _fileService.GetTemplate(alias); - if (model == null) - { - return false; - } - - Template = model; - return true; + return false; } - /// - public void IgnorePublishedContentCollisions() => _ignorePublishedContentCollisions = true; + Template = model; + return true; } + + /// + public void IgnorePublishedContentCollisions() => _ignorePublishedContentCollisions = true; } diff --git a/src/Umbraco.Core/Routing/PublishedRequestExtensions.cs b/src/Umbraco.Core/Routing/PublishedRequestExtensions.cs index 855bd53bded6..6b9720e4ace2 100644 --- a/src/Umbraco.Core/Routing/PublishedRequestExtensions.cs +++ b/src/Umbraco.Core/Routing/PublishedRequestExtensions.cs @@ -1,97 +1,102 @@ using System.Net; -namespace Umbraco.Cms.Core.Routing -{ +namespace Umbraco.Cms.Core.Routing; - public static class PublishedRequestExtensions +public static class PublishedRequestExtensions +{ + /// + /// Gets the + /// + public static UmbracoRouteResult GetRouteResult(this IPublishedRequest publishedRequest) { - /// - /// Gets the - /// - public static UmbracoRouteResult GetRouteResult(this IPublishedRequest publishedRequest) + if (publishedRequest.IsRedirect()) { - if (publishedRequest.IsRedirect()) - { - return UmbracoRouteResult.Redirect; - } - - if (!publishedRequest.HasPublishedContent()) - { - return UmbracoRouteResult.NotFound; - } - - return UmbracoRouteResult.Success; + return UmbracoRouteResult.Redirect; } - /// - /// Gets a value indicating whether the request was successfully routed - /// - public static bool Success(this IPublishedRequest publishedRequest) - => !publishedRequest.IsRedirect() && publishedRequest.HasPublishedContent(); - - /// - /// Sets the response status to be 404 not found - /// - public static IPublishedRequestBuilder SetIs404(this IPublishedRequestBuilder publishedRequest) + if (!publishedRequest.HasPublishedContent()) { - publishedRequest.SetResponseStatus((int)HttpStatusCode.NotFound); - return publishedRequest; + return UmbracoRouteResult.NotFound; } - /// - /// Gets a value indicating whether the content request has a content. - /// - public static bool HasPublishedContent(this IPublishedRequestBuilder publishedRequest) => publishedRequest.PublishedContent != null; - - /// - /// Gets a value indicating whether the content request has a content. - /// - public static bool HasPublishedContent(this IPublishedRequest publishedRequest) => publishedRequest.PublishedContent != null; - - /// - /// Gets a value indicating whether the content request has a template. - /// - public static bool HasTemplate(this IPublishedRequestBuilder publishedRequest) => publishedRequest.Template != null; - - /// - /// Gets a value indicating whether the content request has a template. - /// - public static bool HasTemplate(this IPublishedRequest publishedRequest) => publishedRequest.Template != null; - - /// - /// Gets the alias of the template to use to display the requested content. - /// - public static string? GetTemplateAlias(this IPublishedRequest publishedRequest) => publishedRequest.Template?.Alias; - - /// - /// Gets a value indicating whether the requested content could not be found. - /// - public static bool Is404(this IPublishedRequest publishedRequest) => publishedRequest.ResponseStatusCode == (int)HttpStatusCode.NotFound; - - /// - /// Gets a value indicating whether the content request triggers a redirect (permanent or not). - /// - public static bool IsRedirect(this IPublishedRequestBuilder publishedRequest) => publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Redirect || publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Moved; - - /// - /// Gets indicating whether the content request triggers a redirect (permanent or not). - /// - public static bool IsRedirect(this IPublishedRequest publishedRequest) => publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Redirect || publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Moved; - - /// - /// Gets a value indicating whether the redirect is permanent. - /// - public static bool IsRedirectPermanent(this IPublishedRequest publishedRequest) => publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Moved; - - /// - /// Gets a value indicating whether the content request has a domain. - /// - public static bool HasDomain(this IPublishedRequestBuilder publishedRequest) => publishedRequest.Domain != null; - - /// - /// Gets a value indicating whether the content request has a domain. - /// - public static bool HasDomain(this IPublishedRequest publishedRequest) => publishedRequest.Domain != null; + return UmbracoRouteResult.Success; + } + + /// + /// Gets a value indicating whether the request was successfully routed + /// + public static bool Success(this IPublishedRequest publishedRequest) + => !publishedRequest.IsRedirect() && publishedRequest.HasPublishedContent(); + /// + /// Sets the response status to be 404 not found + /// + public static IPublishedRequestBuilder SetIs404(this IPublishedRequestBuilder publishedRequest) + { + publishedRequest.SetResponseStatus((int)HttpStatusCode.NotFound); + return publishedRequest; } + + /// + /// Gets a value indicating whether the content request has a content. + /// + public static bool HasPublishedContent(this IPublishedRequestBuilder publishedRequest) => + publishedRequest.PublishedContent != null; + + /// + /// Gets a value indicating whether the content request has a content. + /// + public static bool HasPublishedContent(this IPublishedRequest publishedRequest) => + publishedRequest.PublishedContent != null; + + /// + /// Gets a value indicating whether the content request has a template. + /// + public static bool HasTemplate(this IPublishedRequestBuilder publishedRequest) => publishedRequest.Template != null; + + /// + /// Gets a value indicating whether the content request has a template. + /// + public static bool HasTemplate(this IPublishedRequest publishedRequest) => publishedRequest.Template != null; + + /// + /// Gets the alias of the template to use to display the requested content. + /// + public static string? GetTemplateAlias(this IPublishedRequest publishedRequest) => publishedRequest.Template?.Alias; + + /// + /// Gets a value indicating whether the requested content could not be found. + /// + public static bool Is404(this IPublishedRequest publishedRequest) => + publishedRequest.ResponseStatusCode == (int)HttpStatusCode.NotFound; + + /// + /// Gets a value indicating whether the content request triggers a redirect (permanent or not). + /// + public static bool IsRedirect(this IPublishedRequestBuilder publishedRequest) => + publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Redirect || + publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Moved; + + /// + /// Gets indicating whether the content request triggers a redirect (permanent or not). + /// + public static bool IsRedirect(this IPublishedRequest publishedRequest) => + publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Redirect || + publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Moved; + + /// + /// Gets a value indicating whether the redirect is permanent. + /// + public static bool IsRedirectPermanent(this IPublishedRequest publishedRequest) => + publishedRequest.ResponseStatusCode == (int)HttpStatusCode.Moved; + + /// + /// Gets a value indicating whether the content request has a domain. + /// + public static bool HasDomain(this IPublishedRequestBuilder publishedRequest) => publishedRequest.Domain != null; + + /// + /// Gets a value indicating whether the content request has a domain. + /// + public static bool HasDomain(this IPublishedRequest publishedRequest) => publishedRequest.Domain != null; } diff --git a/src/Umbraco.Core/Routing/PublishedRequestOld.cs b/src/Umbraco.Core/Routing/PublishedRequestOld.cs index 44a75aaccd9d..ccd05db43424 100644 --- a/src/Umbraco.Core/Routing/PublishedRequestOld.cs +++ b/src/Umbraco.Core/Routing/PublishedRequestOld.cs @@ -1,392 +1,415 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Threading; +using System.Globalization; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Web; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +// TODO: Kill this, but we need to port all of it's functionality +public class PublishedRequestOld // : IPublishedRequest { - // TODO: Kill this, but we need to port all of it's functionality - public class PublishedRequestOld // : IPublishedRequest + private readonly IPublishedRouter _publishedRouter; + private readonly WebRoutingSettings _webRoutingSettings; + private CultureInfo? _culture; + private DomainAndUri? _domain; + private bool _is404; + private IPublishedContent? _publishedContent; + + private bool _readonly; // after prepared + + /// + /// Initializes a new instance of the class. + /// + public PublishedRequestOld(IPublishedRouter publishedRouter, IUmbracoContext umbracoContext, + IOptions webRoutingSettings, Uri? uri = null) { - private readonly IPublishedRouter _publishedRouter; - private readonly WebRoutingSettings _webRoutingSettings; - - private bool _readonly; // after prepared - private bool _is404; - private DomainAndUri? _domain; - private CultureInfo? _culture; - private IPublishedContent? _publishedContent; - private IPublishedContent? _initialPublishedContent; // found by finders before 404, redirects, etc - - /// - /// Initializes a new instance of the class. - /// - public PublishedRequestOld(IPublishedRouter publishedRouter, IUmbracoContext umbracoContext, IOptions webRoutingSettings, Uri? uri = null) + UmbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext)); + _publishedRouter = publishedRouter ?? throw new ArgumentNullException(nameof(publishedRouter)); + _webRoutingSettings = webRoutingSettings.Value; + Uri = uri ?? umbracoContext.CleanedUmbracoUrl; + } + + /// + /// Gets the UmbracoContext. + /// + public IUmbracoContext UmbracoContext { get; } + + /// + /// Gets or sets the cleaned up Uri used for routing. + /// + /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. + public Uri Uri { get; } + + public bool CacheabilityNoCache { get; set; } + + ///// + ///// Prepares the request. + ///// + //public void Prepare() + //{ + // _publishedRouter.PrepareRequest(this); + //} + + /// + /// Gets or sets a value indicating whether the Umbraco Backoffice should ignore a collision for this request. + /// + public bool IgnorePublishedContentCollisions { get; set; } + + /// + /// Gets or sets the template model to use to display the requested content. + /// + public ITemplate? Template { get; } + + /// + /// Gets the alias of the template to use to display the requested content. + /// + public string? TemplateAlias => Template?.Alias; + + + /// + /// Gets or sets the content request's domain. + /// + /// + /// Is a DomainAndUri object ie a standard Domain plus the fully qualified uri. For example, + /// the Domain may contain "example.com" whereas the Uri will be fully qualified eg + /// "http://example.com/". + /// + public DomainAndUri? Domain + { + get => _domain; + set { - UmbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext)); - _publishedRouter = publishedRouter ?? throw new ArgumentNullException(nameof(publishedRouter)); - _webRoutingSettings = webRoutingSettings.Value; - Uri = uri ?? umbracoContext.CleanedUmbracoUrl; + EnsureWriteable(); + _domain = value; } + } - /// - /// Gets the UmbracoContext. - /// - public IUmbracoContext UmbracoContext { get; } + /// + /// Gets a value indicating whether the content request has a domain. + /// + public bool HasDomain => Domain != null; - /// - /// Gets or sets the cleaned up Uri used for routing. - /// - /// The cleaned up Uri has no virtual directory, no trailing slash, no .aspx extension, etc. - public Uri Uri { get; } + /// + /// Gets or sets the content request's culture. + /// + public CultureInfo Culture + { + get => _culture ?? Thread.CurrentThread.CurrentCulture; + set + { + EnsureWriteable(); + _culture = value; + } + } - // utility for ensuring it is ok to set some properties - public void EnsureWriteable() + // utility for ensuring it is ok to set some properties + public void EnsureWriteable() + { + if (_readonly) { - if (_readonly) - { - throw new InvalidOperationException("Cannot modify a PublishedRequest once it is read-only."); - } + throw new InvalidOperationException("Cannot modify a PublishedRequest once it is read-only."); } + } - public bool CacheabilityNoCache { get; set; } + //#region Events + + ///// + ///// Triggers before the published content request is prepared. + ///// + ///// When the event triggers, no preparation has been done. It is still possible to + ///// modify the request's Uri property, for example to restore its original, public-facing value + ///// that might have been modified by an in-between equipment such as a load-balancer. + //public static event EventHandler Preparing; + + ///// + ///// Triggers once the published content request has been prepared, but before it is processed. + ///// + ///// When the event triggers, preparation is done ie domain, culture, document, template, + ///// rendering engine, etc. have been setup. It is then possible to change anything, before + ///// the request is actually processed and rendered by Umbraco. + //public static event EventHandler Prepared; + + ///// + ///// Triggers the Preparing event. + ///// + //public void OnPreparing() + //{ + // Preparing?.Invoke(this, EventArgs.Empty); + //} + + ///// + ///// Triggers the Prepared event. + ///// + //public void OnPrepared() + //{ + // Prepared?.Invoke(this, EventArgs.Empty); + + // if (HasPublishedContent == false) + // Is404 = true; // safety + + // _readonly = true; + //} + + //#endregion + + #region PublishedContent + + ///// + ///// Gets or sets the requested content. + ///// + ///// Setting the requested content clears Template. + //public IPublishedContent PublishedContent + //{ + // get { return _publishedContent; } + // set + // { + // EnsureWriteable(); + // _publishedContent = value; + // IsInternalRedirectPublishedContent = false; + // TemplateModel = null; + // } + //} + + /// + /// Sets the requested content, following an internal redirect. + /// + /// The requested content. + /// + /// Depending on UmbracoSettings.InternalRedirectPreservesTemplate, will + /// preserve or reset the template, if any. + /// + public void SetInternalRedirectPublishedContent(IPublishedContent content) + { + //if (content == null) + // throw new ArgumentNullException(nameof(content)); + //EnsureWriteable(); - ///// - ///// Prepares the request. - ///// - //public void Prepare() - //{ - // _publishedRouter.PrepareRequest(this); - //} + //// unless a template has been set already by the finder, + //// template should be null at that point. - /// - /// Gets or sets a value indicating whether the Umbraco Backoffice should ignore a collision for this request. - /// - public bool IgnorePublishedContentCollisions { get; set; } - - //#region Events - - ///// - ///// Triggers before the published content request is prepared. - ///// - ///// When the event triggers, no preparation has been done. It is still possible to - ///// modify the request's Uri property, for example to restore its original, public-facing value - ///// that might have been modified by an in-between equipment such as a load-balancer. - //public static event EventHandler Preparing; - - ///// - ///// Triggers once the published content request has been prepared, but before it is processed. - ///// - ///// When the event triggers, preparation is done ie domain, culture, document, template, - ///// rendering engine, etc. have been setup. It is then possible to change anything, before - ///// the request is actually processed and rendered by Umbraco. - //public static event EventHandler Prepared; - - ///// - ///// Triggers the Preparing event. - ///// - //public void OnPreparing() - //{ - // Preparing?.Invoke(this, EventArgs.Empty); - //} + //// IsInternalRedirect if IsInitial, or already IsInternalRedirect + //var isInternalRedirect = IsInitialPublishedContent || IsInternalRedirectPublishedContent; - ///// - ///// Triggers the Prepared event. - ///// - //public void OnPrepared() + //// redirecting to self + //if (content.Id == PublishedContent.Id) // neither can be null //{ - // Prepared?.Invoke(this, EventArgs.Empty); - - // if (HasPublishedContent == false) - // Is404 = true; // safety - - // _readonly = true; + // // no need to set PublishedContent, we're done + // IsInternalRedirectPublishedContent = isInternalRedirect; + // return; //} - //#endregion + //// else + + //// save + //var template = Template; - #region PublishedContent + //// set published content - this resets the template, and sets IsInternalRedirect to false + //PublishedContent = content; + //IsInternalRedirectPublishedContent = isInternalRedirect; - ///// - ///// Gets or sets the requested content. - ///// - ///// Setting the requested content clears Template. - //public IPublishedContent PublishedContent + //// must restore the template if it's an internal redirect & the config option is set + //if (isInternalRedirect && _webRoutingSettings.InternalRedirectPreservesTemplate) //{ - // get { return _publishedContent; } - // set - // { - // EnsureWriteable(); - // _publishedContent = value; - // IsInternalRedirectPublishedContent = false; - // TemplateModel = null; - // } + // // restore + // TemplateModel = template; //} + } - /// - /// Sets the requested content, following an internal redirect. - /// - /// The requested content. - /// Depending on UmbracoSettings.InternalRedirectPreservesTemplate, will - /// preserve or reset the template, if any. - public void SetInternalRedirectPublishedContent(IPublishedContent content) - { - //if (content == null) - // throw new ArgumentNullException(nameof(content)); - //EnsureWriteable(); - - //// unless a template has been set already by the finder, - //// template should be null at that point. - - //// IsInternalRedirect if IsInitial, or already IsInternalRedirect - //var isInternalRedirect = IsInitialPublishedContent || IsInternalRedirectPublishedContent; - - //// redirecting to self - //if (content.Id == PublishedContent.Id) // neither can be null - //{ - // // no need to set PublishedContent, we're done - // IsInternalRedirectPublishedContent = isInternalRedirect; - // return; - //} - - //// else - - //// save - //var template = Template; - - //// set published content - this resets the template, and sets IsInternalRedirect to false - //PublishedContent = content; - //IsInternalRedirectPublishedContent = isInternalRedirect; - - //// must restore the template if it's an internal redirect & the config option is set - //if (isInternalRedirect && _webRoutingSettings.InternalRedirectPreservesTemplate) - //{ - // // restore - // TemplateModel = template; - //} - } - - /// - /// Gets the initial requested content. - /// - /// The initial requested content is the content that was found by the finders, - /// before anything such as 404, redirect... took place. - public IPublishedContent? InitialPublishedContent => _initialPublishedContent; - - /// - /// Gets value indicating whether the current published content is the initial one. - /// - public bool IsInitialPublishedContent => _initialPublishedContent != null && _initialPublishedContent == _publishedContent; - - /// - /// Indicates that the current PublishedContent is the initial one. - /// - public void SetIsInitialPublishedContent() - { - EnsureWriteable(); - - // note: it can very well be null if the initial content was not found - _initialPublishedContent = _publishedContent; - IsInternalRedirectPublishedContent = false; - } - - /// - /// Gets or sets a value indicating whether the current published content has been obtained - /// from the initial published content following internal redirections exclusively. - /// - /// Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to - /// apply the internal redirect or not, when content is not the initial content. - public bool IsInternalRedirectPublishedContent { get; private set; } - - - #endregion - - /// - /// Gets or sets the template model to use to display the requested content. - /// - public ITemplate? Template { get; } - - /// - /// Gets the alias of the template to use to display the requested content. - /// - public string? TemplateAlias => Template?.Alias; - - - /// - /// Gets or sets the content request's domain. - /// - /// Is a DomainAndUri object ie a standard Domain plus the fully qualified uri. For example, - /// the Domain may contain "example.com" whereas the Uri will be fully qualified eg "http://example.com/". - public DomainAndUri? Domain - { - get { return _domain; } - set - { - EnsureWriteable(); - _domain = value; - } - } + /// + /// Gets the initial requested content. + /// + /// + /// The initial requested content is the content that was found by the finders, + /// before anything such as 404, redirect... took place. + /// + public IPublishedContent? InitialPublishedContent { get; private set; } + + /// + /// Gets value indicating whether the current published content is the initial one. + /// + public bool IsInitialPublishedContent => + InitialPublishedContent != null && InitialPublishedContent == _publishedContent; + + /// + /// Indicates that the current PublishedContent is the initial one. + /// + public void SetIsInitialPublishedContent() + { + EnsureWriteable(); - /// - /// Gets a value indicating whether the content request has a domain. - /// - public bool HasDomain => Domain != null; + // note: it can very well be null if the initial content was not found + InitialPublishedContent = _publishedContent; + IsInternalRedirectPublishedContent = false; + } - /// - /// Gets or sets the content request's culture. - /// - public CultureInfo Culture + /// + /// Gets or sets a value indicating whether the current published content has been obtained + /// from the initial published content following internal redirections exclusively. + /// + /// + /// Used by PublishedContentRequestEngine.FindTemplate() to figure out whether to + /// apply the internal redirect or not, when content is not the initial content. + /// + public bool IsInternalRedirectPublishedContent { get; private set; } + + #endregion + + // note: do we want to have an ordered list of alternate cultures, + // to allow for fallbacks when doing dictionary lookup and such? + + + #region Status + + /// + /// Gets or sets a value indicating whether the requested content could not be found. + /// + /// + /// This is set in the PublishedContentRequestBuilder and can also be used in + /// custom content finders or Prepared event handlers, where we want to allow developers + /// to indicate a request is 404 but not to cancel it. + /// + public bool Is404 + { + get => _is404; + set { - get { return _culture ?? Thread.CurrentThread.CurrentCulture; } - set - { - EnsureWriteable(); - _culture = value; - } + EnsureWriteable(); + _is404 = value; } + } - // note: do we want to have an ordered list of alternate cultures, - // to allow for fallbacks when doing dictionary lookup and such? - - - #region Status + /// + /// Gets a value indicating whether the content request triggers a redirect (permanent or not). + /// + public bool IsRedirect => string.IsNullOrWhiteSpace(RedirectUrl) == false; + + /// + /// Gets or sets a value indicating whether the redirect is permanent. + /// + public bool IsRedirectPermanent { get; private set; } + + /// + /// Gets or sets the URL to redirect to, when the content request triggers a redirect. + /// + public string? RedirectUrl { get; private set; } + + /// + /// Indicates that the content request should trigger a redirect (302). + /// + /// The URL to redirect to. + /// + /// Does not actually perform a redirect, only registers that the response should + /// redirect. Redirect will or will not take place in due time. + /// + public void SetRedirect(string url) + { + EnsureWriteable(); + RedirectUrl = url; + IsRedirectPermanent = false; + } - /// - /// Gets or sets a value indicating whether the requested content could not be found. - /// - /// This is set in the PublishedContentRequestBuilder and can also be used in - /// custom content finders or Prepared event handlers, where we want to allow developers - /// to indicate a request is 404 but not to cancel it. - public bool Is404 - { - get { return _is404; } - set - { - EnsureWriteable(); - _is404 = value; - } - } + /// + /// Indicates that the content request should trigger a permanent redirect (301). + /// + /// The URL to redirect to. + /// + /// Does not actually perform a redirect, only registers that the response should + /// redirect. Redirect will or will not take place in due time. + /// + public void SetRedirectPermanent(string url) + { + EnsureWriteable(); + RedirectUrl = url; + IsRedirectPermanent = true; + } - /// - /// Gets a value indicating whether the content request triggers a redirect (permanent or not). - /// - public bool IsRedirect => string.IsNullOrWhiteSpace(RedirectUrl) == false; - - /// - /// Gets or sets a value indicating whether the redirect is permanent. - /// - public bool IsRedirectPermanent { get; private set; } - - /// - /// Gets or sets the URL to redirect to, when the content request triggers a redirect. - /// - public string? RedirectUrl { get; private set; } - - /// - /// Indicates that the content request should trigger a redirect (302). - /// - /// The URL to redirect to. - /// Does not actually perform a redirect, only registers that the response should - /// redirect. Redirect will or will not take place in due time. - public void SetRedirect(string url) - { - EnsureWriteable(); - RedirectUrl = url; - IsRedirectPermanent = false; - } + /// + /// Indicates that the content request should trigger a redirect, with a specified status code. + /// + /// The URL to redirect to. + /// The status code (300-308). + /// + /// Does not actually perform a redirect, only registers that the response should + /// redirect. Redirect will or will not take place in due time. + /// + public void SetRedirect(string url, int status) + { + EnsureWriteable(); - /// - /// Indicates that the content request should trigger a permanent redirect (301). - /// - /// The URL to redirect to. - /// Does not actually perform a redirect, only registers that the response should - /// redirect. Redirect will or will not take place in due time. - public void SetRedirectPermanent(string url) + if (status < 300 || status > 308) { - EnsureWriteable(); - RedirectUrl = url; - IsRedirectPermanent = true; + throw new ArgumentOutOfRangeException(nameof(status), "Valid redirection status codes 300-308."); } - /// - /// Indicates that the content request should trigger a redirect, with a specified status code. - /// - /// The URL to redirect to. - /// The status code (300-308). - /// Does not actually perform a redirect, only registers that the response should - /// redirect. Redirect will or will not take place in due time. - public void SetRedirect(string url, int status) + RedirectUrl = url; + IsRedirectPermanent = status == 301 || status == 308; + if (status != 301 && status != 302) // default redirect statuses { - EnsureWriteable(); - - if (status < 300 || status > 308) - throw new ArgumentOutOfRangeException(nameof(status), "Valid redirection status codes 300-308."); - - RedirectUrl = url; - IsRedirectPermanent = (status == 301 || status == 308); - if (status != 301 && status != 302) // default redirect statuses - ResponseStatusCode = status; + ResponseStatusCode = status; } + } - /// - /// Gets or sets the content request http response status code. - /// - /// Does not actually set the http response status code, only registers that the response - /// should use the specified code. The code will or will not be used, in due time. - public int ResponseStatusCode { get; private set; } - - /// - /// Gets or sets the content request http response status description. - /// - /// Does not actually set the http response status description, only registers that the response - /// should use the specified description. The description will or will not be used, in due time. - public string? ResponseStatusDescription { get; private set; } - - /// - /// Sets the http response status code, along with an optional associated description. - /// - /// The http status code. - /// The description. - /// Does not actually set the http response status code and description, only registers that - /// the response should use the specified code and description. The code and description will or will - /// not be used, in due time. - public void SetResponseStatus(int code, string? description = null) - { - EnsureWriteable(); + /// + /// Gets or sets the content request http response status code. + /// + /// + /// Does not actually set the http response status code, only registers that the response + /// should use the specified code. The code will or will not be used, in due time. + /// + public int ResponseStatusCode { get; private set; } + + /// + /// Gets or sets the content request http response status description. + /// + /// + /// Does not actually set the http response status description, only registers that the response + /// should use the specified description. The description will or will not be used, in due time. + /// + public string? ResponseStatusDescription { get; private set; } + + /// + /// Sets the http response status code, along with an optional associated description. + /// + /// The http status code. + /// The description. + /// + /// Does not actually set the http response status code and description, only registers that + /// the response should use the specified code and description. The code and description will or will + /// not be used, in due time. + /// + public void SetResponseStatus(int code, string? description = null) + { + EnsureWriteable(); - // .Status is deprecated - // .SubStatusCode is IIS 7+ internal, ignore - ResponseStatusCode = code; - ResponseStatusDescription = description; - } + // .Status is deprecated + // .SubStatusCode is IIS 7+ internal, ignore + ResponseStatusCode = code; + ResponseStatusDescription = description; + } - #endregion + #endregion - #region Response Cache + #region Response Cache - /// - /// Gets or sets the System.Web.HttpCacheability - /// - // Note: we used to set a default value here but that would then be the default - // for ALL requests, we shouldn't overwrite it though if people are using [OutputCache] for example - // see: https://our.umbraco.com/forum/using-umbraco-and-getting-started/79715-output-cache-in-umbraco-752 - //public HttpCacheability Cacheability { get; set; } + /// + /// Gets or sets the System.Web.HttpCacheability + /// + // Note: we used to set a default value here but that would then be the default + // for ALL requests, we shouldn't overwrite it though if people are using [OutputCache] for example + // see: https://our.umbraco.com/forum/using-umbraco-and-getting-started/79715-output-cache-in-umbraco-752 + //public HttpCacheability Cacheability { get; set; } - /// - /// Gets or sets a list of Extensions to append to the Response.Cache object. - /// - public List CacheExtensions { get; set; } = new List(); + /// + /// Gets or sets a list of Extensions to append to the Response.Cache object. + /// + public List CacheExtensions { get; set; } = new(); - /// - /// Gets or sets a dictionary of Headers to append to the Response object. - /// - public Dictionary Headers { get; set; } = new Dictionary(); + /// + /// Gets or sets a dictionary of Headers to append to the Response object. + /// + public Dictionary Headers { get; set; } = new(); - #endregion - } + #endregion } diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs index 119f9980b4c5..14cd36a4c17a 100644 --- a/src/Umbraco.Core/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -1,8 +1,4 @@ -using System; using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; @@ -15,437 +11,452 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; using Umbraco.Extensions; +using LogLevel = Microsoft.Extensions.Logging.LogLevel; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// Provides the default implementation. +/// +public class PublishedRouter : IPublishedRouter { + private readonly ContentFinderCollection _contentFinders; + private readonly IContentLastChanceFinder _contentLastChanceFinder; + private readonly IContentTypeService _contentTypeService; + private readonly IEventAggregator _eventAggregator; + private readonly IFileService _fileService; + private readonly ILogger _logger; + private readonly IProfilingLogger _profilingLogger; + private readonly IPublishedUrlProvider _publishedUrlProvider; + private readonly IPublishedValueFallback _publishedValueFallback; + private readonly IRequestAccessor _requestAccessor; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IVariationContextAccessor _variationContextAccessor; + private WebRoutingSettings _webRoutingSettings; /// - /// Provides the default implementation. + /// Initializes a new instance of the class. /// - public class PublishedRouter : IPublishedRouter + public PublishedRouter( + IOptionsMonitor webRoutingSettings, + ContentFinderCollection contentFinders, + IContentLastChanceFinder contentLastChanceFinder, + IVariationContextAccessor variationContextAccessor, + IProfilingLogger proflog, + ILogger logger, + IPublishedUrlProvider publishedUrlProvider, + IRequestAccessor requestAccessor, + IPublishedValueFallback publishedValueFallback, + IFileService fileService, + IContentTypeService contentTypeService, + IUmbracoContextAccessor umbracoContextAccessor, + IEventAggregator eventAggregator) { - private WebRoutingSettings _webRoutingSettings; - private readonly ContentFinderCollection _contentFinders; - private readonly IContentLastChanceFinder _contentLastChanceFinder; - private readonly IProfilingLogger _profilingLogger; - private readonly IVariationContextAccessor _variationContextAccessor; - private readonly ILogger _logger; - private readonly IPublishedUrlProvider _publishedUrlProvider; - private readonly IRequestAccessor _requestAccessor; - private readonly IPublishedValueFallback _publishedValueFallback; - private readonly IFileService _fileService; - private readonly IContentTypeService _contentTypeService; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly IEventAggregator _eventAggregator; - - /// - /// Initializes a new instance of the class. - /// - public PublishedRouter( - IOptionsMonitor webRoutingSettings, - ContentFinderCollection contentFinders, - IContentLastChanceFinder contentLastChanceFinder, - IVariationContextAccessor variationContextAccessor, - IProfilingLogger proflog, - ILogger logger, - IPublishedUrlProvider publishedUrlProvider, - IRequestAccessor requestAccessor, - IPublishedValueFallback publishedValueFallback, - IFileService fileService, - IContentTypeService contentTypeService, - IUmbracoContextAccessor umbracoContextAccessor, - IEventAggregator eventAggregator) - { - _webRoutingSettings = webRoutingSettings.CurrentValue ?? throw new ArgumentNullException(nameof(webRoutingSettings)); - _contentFinders = contentFinders ?? throw new ArgumentNullException(nameof(contentFinders)); - _contentLastChanceFinder = contentLastChanceFinder ?? throw new ArgumentNullException(nameof(contentLastChanceFinder)); - _profilingLogger = proflog ?? throw new ArgumentNullException(nameof(proflog)); - _variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); - _logger = logger; - _publishedUrlProvider = publishedUrlProvider; - _requestAccessor = requestAccessor; - _publishedValueFallback = publishedValueFallback; - _fileService = fileService; - _contentTypeService = contentTypeService; - _umbracoContextAccessor = umbracoContextAccessor; - _eventAggregator = eventAggregator; - webRoutingSettings.OnChange(x => _webRoutingSettings = x); - } - - /// - public async Task CreateRequestAsync(Uri uri) - { - // trigger the Creating event - at that point the URL can be changed - // this is based on this old task here: https://issues.umbraco.org/issue/U4-7914 which was fulfiled by - // this PR https://github.com/umbraco/Umbraco-CMS/pull/1137 - // It's to do with proxies, quote: - - /* - "Thinking about another solution. - We already have an event, PublishedContentRequest.Prepared, which triggers once the request has been prepared and domain, content, template have been figured out -- but before it renders -- so ppl can change things before rendering. - Wondering whether we could have a event, PublishedContentRequest.Preparing, which would trigger before the request is prepared, and would let ppl change the value of the request's URI (which by default derives from the HttpContext request). - That way, if an in-between equipement changes the URI, you could replace it with the original, public-facing URI before we process the request, meaning you could register your HTTPS domain and it would work. And you would have to supply code for each equipment. Less magic in Core." - */ - - // but now we'll just have one event for creating so if people wish to change the URL here they can but nothing else - var creatingRequest = new CreatingRequestNotification(uri); - await _eventAggregator.PublishAsync(creatingRequest); - - var publishedRequestBuilder = new PublishedRequestBuilder(creatingRequest.Url, _fileService); - return publishedRequestBuilder; - } - - private async Task TryRouteRequest(IPublishedRequestBuilder request) - { - FindDomain(request); - - if (request.IsRedirect()) - { - return request.Build(); - } + _webRoutingSettings = webRoutingSettings.CurrentValue ?? + throw new ArgumentNullException(nameof(webRoutingSettings)); + _contentFinders = contentFinders ?? throw new ArgumentNullException(nameof(contentFinders)); + _contentLastChanceFinder = + contentLastChanceFinder ?? throw new ArgumentNullException(nameof(contentLastChanceFinder)); + _profilingLogger = proflog ?? throw new ArgumentNullException(nameof(proflog)); + _variationContextAccessor = variationContextAccessor ?? + throw new ArgumentNullException(nameof(variationContextAccessor)); + _logger = logger; + _publishedUrlProvider = publishedUrlProvider; + _requestAccessor = requestAccessor; + _publishedValueFallback = publishedValueFallback; + _fileService = fileService; + _contentTypeService = contentTypeService; + _umbracoContextAccessor = umbracoContextAccessor; + _eventAggregator = eventAggregator; + webRoutingSettings.OnChange(x => _webRoutingSettings = x); + } - if (request.HasPublishedContent()) - { - return request.Build(); - } + /// + public async Task CreateRequestAsync(Uri uri) + { + // trigger the Creating event - at that point the URL can be changed + // this is based on this old task here: https://issues.umbraco.org/issue/U4-7914 which was fulfiled by + // this PR https://github.com/umbraco/Umbraco-CMS/pull/1137 + // It's to do with proxies, quote: + + /* + "Thinking about another solution. + We already have an event, PublishedContentRequest.Prepared, which triggers once the request has been prepared and domain, content, template have been figured out -- but before it renders -- so ppl can change things before rendering. + Wondering whether we could have a event, PublishedContentRequest.Preparing, which would trigger before the request is prepared, and would let ppl change the value of the request's URI (which by default derives from the HttpContext request). + That way, if an in-between equipement changes the URI, you could replace it with the original, public-facing URI before we process the request, meaning you could register your HTTPS domain and it would work. And you would have to supply code for each equipment. Less magic in Core." + */ + + // but now we'll just have one event for creating so if people wish to change the URL here they can but nothing else + var creatingRequest = new CreatingRequestNotification(uri); + await _eventAggregator.PublishAsync(creatingRequest); + + var publishedRequestBuilder = new PublishedRequestBuilder(creatingRequest.Url, _fileService); + return publishedRequestBuilder; + } - await FindPublishedContent(request); + /// + public async Task RouteRequestAsync(IPublishedRequestBuilder builder, + RouteRequestOptions options) + { + // outbound routing performs different/simpler logic + if (options.RouteDirection == RouteDirection.Outbound) + { + return await TryRouteRequest(builder); + } - return request.Build(); + // find domain + if (builder.Domain == null) + { + FindDomain(builder); } - private void SetVariationContext(string? culture) + await RouteRequestInternalAsync(builder); + + // complete the PCR and assign the remaining values + return BuildRequest(builder); + } + + /// + public async Task UpdateRequestAsync(IPublishedRequest request, + IPublishedContent? publishedContent) + { + // store the original (if any) + IPublishedContent? content = request.PublishedContent; + + IPublishedRequestBuilder builder = new PublishedRequestBuilder(request.Uri, _fileService); + + // set to the new content (or null if specified) + builder.SetPublishedContent(publishedContent); + + // re-route + await RouteRequestInternalAsync(builder); + + // return if we are redirect + if (builder.IsRedirect()) { - VariationContext? variationContext = _variationContextAccessor.VariationContext; - if (variationContext != null && variationContext.Culture == culture) - { - return; - } + return BuildRequest(builder); + } - _variationContextAccessor.VariationContext = new VariationContext(culture); + // this will occur if publishedContent is null and the last chance finders also don't assign content + if (!builder.HasPublishedContent()) + { + // means the engine could not find a proper document to handle 404 + // restore the saved content so we know it exists + builder.SetPublishedContent(content); } - /// - public async Task RouteRequestAsync(IPublishedRequestBuilder builder, RouteRequestOptions options) + if (!builder.HasDomain()) { - // outbound routing performs different/simpler logic - if (options.RouteDirection == RouteDirection.Outbound) - { - return await TryRouteRequest(builder); - } + FindDomain(builder); + } - // find domain - if (builder.Domain == null) - { - FindDomain(builder); - } + return BuildRequest(builder); + } - await RouteRequestInternalAsync(builder); + private async Task TryRouteRequest(IPublishedRequestBuilder request) + { + FindDomain(request); - // complete the PCR and assign the remaining values - return BuildRequest(builder); + if (request.IsRedirect()) + { + return request.Build(); } - private async Task RouteRequestInternalAsync(IPublishedRequestBuilder builder) + if (request.HasPublishedContent()) { - // if request builder was already flagged to redirect then return - // whoever called us is in charge of actually redirecting - if (builder.IsRedirect()) - { - return; - } - - // set the culture - SetVariationContext(builder.Culture); + return request.Build(); + } - var foundContentByFinders = false; + await FindPublishedContent(request); - // Find the published content if it's not assigned. - // This could be manually assigned with a custom route handler, etc... - // which in turn could call this method - // to setup the rest of the pipeline but we don't want to run the finders since there's one assigned. - if (!builder.HasPublishedContent()) - { - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) - { - _logger.LogDebug("FindPublishedContentAndTemplate: Path={UriAbsolutePath}", builder.Uri.AbsolutePath); - } + return request.Build(); + } - // run the document finders - foundContentByFinders = await FindPublishedContent(builder); - } + private void SetVariationContext(string? culture) + { + VariationContext? variationContext = _variationContextAccessor.VariationContext; + if (variationContext != null && variationContext.Culture == culture) + { + return; + } - // if we are not a redirect - if (!builder.IsRedirect()) - { - // handle not-found, redirects, access... - await HandlePublishedContent(builder); + _variationContextAccessor.VariationContext = new VariationContext(culture); + } - // find a template - FindTemplate(builder, foundContentByFinders); + private async Task RouteRequestInternalAsync(IPublishedRequestBuilder builder) + { + // if request builder was already flagged to redirect then return + // whoever called us is in charge of actually redirecting + if (builder.IsRedirect()) + { + return; + } - // handle umbracoRedirect - FollowExternalRedirect(builder); + // set the culture + SetVariationContext(builder.Culture); - // handle wildcard domains - HandleWildcardDomains(builder); + var foundContentByFinders = false; - // set the culture -- again, 'cos it might have changed due to a finder or wildcard domain - SetVariationContext(builder.Culture); + // Find the published content if it's not assigned. + // This could be manually assigned with a custom route handler, etc... + // which in turn could call this method + // to setup the rest of the pipeline but we don't want to run the finders since there's one assigned. + if (!builder.HasPublishedContent()) + { + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("FindPublishedContentAndTemplate: Path={UriAbsolutePath}", builder.Uri.AbsolutePath); } - // trigger the routing request (used to be called Prepared) event - at that point it is still possible to change about anything - // even though the request might be flagged for redirection - we'll redirect _after_ the event - var routingRequest = new RoutingRequestNotification(builder); - await _eventAggregator.PublishAsync(routingRequest); - - // we don't take care of anything so if the content has changed, it's up to the user - // to find out the appropriate template + // run the document finders + foundContentByFinders = await FindPublishedContent(builder); } - /// - /// This method finalizes/builds the PCR with the values assigned. - /// - /// - /// Returns false if the request was not successfully configured - /// - /// - /// This method logic has been put into it's own method in case developers have created a custom PCR or are assigning their own values - /// but need to finalize it themselves. - /// - internal IPublishedRequest BuildRequest(IPublishedRequestBuilder builder) + // if we are not a redirect + if (!builder.IsRedirect()) { - IPublishedRequest result = builder.Build(); + // handle not-found, redirects, access... + await HandlePublishedContent(builder); - if (!builder.HasPublishedContent()) - { - return result; - } + // find a template + FindTemplate(builder, foundContentByFinders); - // set the culture -- again, 'cos it might have changed in the event handler - SetVariationContext(result.Culture); + // handle umbracoRedirect + FollowExternalRedirect(builder); - return result; + // handle wildcard domains + HandleWildcardDomains(builder); + + // set the culture -- again, 'cos it might have changed due to a finder or wildcard domain + SetVariationContext(builder.Culture); } - /// - public async Task UpdateRequestAsync(IPublishedRequest request, IPublishedContent? publishedContent) - { - // store the original (if any) - IPublishedContent? content = request.PublishedContent; + // trigger the routing request (used to be called Prepared) event - at that point it is still possible to change about anything + // even though the request might be flagged for redirection - we'll redirect _after_ the event + var routingRequest = new RoutingRequestNotification(builder); + await _eventAggregator.PublishAsync(routingRequest); - IPublishedRequestBuilder builder = new PublishedRequestBuilder(request.Uri, _fileService); + // we don't take care of anything so if the content has changed, it's up to the user + // to find out the appropriate template + } - // set to the new content (or null if specified) - builder.SetPublishedContent(publishedContent); + /// + /// This method finalizes/builds the PCR with the values assigned. + /// + /// + /// Returns false if the request was not successfully configured + /// + /// + /// This method logic has been put into it's own method in case developers have created a custom PCR or are assigning + /// their own values + /// but need to finalize it themselves. + /// + internal IPublishedRequest BuildRequest(IPublishedRequestBuilder builder) + { + IPublishedRequest result = builder.Build(); - // re-route - await RouteRequestInternalAsync(builder); + if (!builder.HasPublishedContent()) + { + return result; + } - // return if we are redirect - if (builder.IsRedirect()) - { - return BuildRequest(builder); - } + // set the culture -- again, 'cos it might have changed in the event handler + SetVariationContext(result.Culture); - // this will occur if publishedContent is null and the last chance finders also don't assign content - if (!builder.HasPublishedContent()) - { - // means the engine could not find a proper document to handle 404 - // restore the saved content so we know it exists - builder.SetPublishedContent(content); - } + return result; + } - if (!builder.HasDomain()) - { - FindDomain(builder); - } + /// + /// Finds the site root (if any) matching the http request, and updates the PublishedRequest accordingly. + /// + /// A value indicating whether a domain was found. + internal bool FindDomain(IPublishedRequestBuilder request) + { + const string tracePrefix = "FindDomain: "; - return BuildRequest(builder); + // note - we are not handling schemes nor ports here. + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("{TracePrefix}Uri={RequestUri}", tracePrefix, request.Uri); } - /// - /// Finds the site root (if any) matching the http request, and updates the PublishedRequest accordingly. - /// - /// A value indicating whether a domain was found. - internal bool FindDomain(IPublishedRequestBuilder request) + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + IDomainCache? domainsCache = umbracoContext.PublishedSnapshot.Domains; + var domains = domainsCache?.GetAll(false).ToList(); + + // determines whether a domain corresponds to a published document, since some + // domains may exist but on a document that has been unpublished - as a whole - or + // that is not published for the domain's culture - in which case the domain does + // not apply + bool IsPublishedContentDomain(Domain domain) { - const string tracePrefix = "FindDomain: "; + // just get it from content cache - optimize there, not here + IPublishedContent? domainDocument = umbracoContext.PublishedSnapshot.Content?.GetById(domain.ContentId); - // note - we are not handling schemes nor ports here. - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) + // not published - at all + if (domainDocument == null) { - _logger.LogDebug("{TracePrefix}Uri={RequestUri}", tracePrefix, request.Uri); + return false; } - var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - IDomainCache? domainsCache = umbracoContext.PublishedSnapshot.Domains; - var domains = domainsCache?.GetAll(includeWildcards: false).ToList(); - - // determines whether a domain corresponds to a published document, since some - // domains may exist but on a document that has been unpublished - as a whole - or - // that is not published for the domain's culture - in which case the domain does - // not apply - bool IsPublishedContentDomain(Domain domain) - { - // just get it from content cache - optimize there, not here - IPublishedContent? domainDocument = umbracoContext.PublishedSnapshot.Content?.GetById(domain.ContentId); - - // not published - at all - if (domainDocument == null) - { - return false; - } - // invariant - always published - if (!domainDocument.ContentType.VariesByCulture()) - { - return true; - } - - // variant, ensure that the culture corresponding to the domain's language is published - return domain.Culture is not null && domainDocument.Cultures.ContainsKey(domain.Culture); + // invariant - always published + if (!domainDocument.ContentType.VariesByCulture()) + { + return true; } - domains = domains?.Where(IsPublishedContentDomain).ToList(); + // variant, ensure that the culture corresponding to the domain's language is published + return domain.Culture is not null && domainDocument.Cultures.ContainsKey(domain.Culture); + } - var defaultCulture = domainsCache?.DefaultCulture; + domains = domains?.Where(IsPublishedContentDomain).ToList(); - // try to find a domain matching the current request - DomainAndUri? domainAndUri = DomainUtilities.SelectDomain(domains, request.Uri, defaultCulture: defaultCulture); + var defaultCulture = domainsCache?.DefaultCulture; - // handle domain - always has a contentId and a culture - if (domainAndUri != null) + // try to find a domain matching the current request + DomainAndUri? domainAndUri = DomainUtilities.SelectDomain(domains, request.Uri, defaultCulture: defaultCulture); + + // handle domain - always has a contentId and a culture + if (domainAndUri != null) + { + // matching an existing domain + if (_logger.IsEnabled(LogLevel.Debug)) { - // matching an existing domain - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) - { - _logger.LogDebug("{TracePrefix}Matches domain={Domain}, rootId={RootContentId}, culture={Culture}", tracePrefix, domainAndUri.Name, domainAndUri.ContentId, domainAndUri.Culture); - } - request.SetDomain(domainAndUri); - - // canonical? not implemented at the moment - // if (...) - // { - // _pcr.RedirectUrl = "..."; - // return true; - // } + _logger.LogDebug("{TracePrefix}Matches domain={Domain}, rootId={RootContentId}, culture={Culture}", + tracePrefix, domainAndUri.Name, domainAndUri.ContentId, domainAndUri.Culture); } - else - { - // not matching any existing domain - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) - { - _logger.LogDebug("{TracePrefix}Matches no domain", tracePrefix); - } - request.SetCulture(defaultCulture ?? CultureInfo.CurrentUICulture.Name); - } - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) + request.SetDomain(domainAndUri); + + // canonical? not implemented at the moment + // if (...) + // { + // _pcr.RedirectUrl = "..."; + // return true; + // } + } + else + { + // not matching any existing domain + if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("{TracePrefix}Culture={CultureName}", tracePrefix, request.Culture); + _logger.LogDebug("{TracePrefix}Matches no domain", tracePrefix); } - return request.Domain != null; + request.SetCulture(defaultCulture ?? CultureInfo.CurrentUICulture.Name); } - /// - /// Looks for wildcard domains in the path and updates Culture accordingly. - /// - internal void HandleWildcardDomains(IPublishedRequestBuilder request) + if (_logger.IsEnabled(LogLevel.Debug)) { - const string tracePrefix = "HandleWildcardDomains: "; + _logger.LogDebug("{TracePrefix}Culture={CultureName}", tracePrefix, request.Culture); + } - if (request.PublishedContent == null) - { - return; - } + return request.Domain != null; + } - var nodePath = request.PublishedContent.Path; - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) - { - _logger.LogDebug("{TracePrefix}Path={NodePath}", tracePrefix, nodePath); - } - var rootNodeId = request.Domain != null ? request.Domain.ContentId : (int?)null; - var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - Domain? domain = DomainUtilities.FindWildcardDomainInPath(umbracoContext.PublishedSnapshot.Domains?.GetAll(true), nodePath, rootNodeId); + /// + /// Looks for wildcard domains in the path and updates Culture accordingly. + /// + internal void HandleWildcardDomains(IPublishedRequestBuilder request) + { + const string tracePrefix = "HandleWildcardDomains: "; - // always has a contentId and a culture - if (domain != null) - { - request.SetCulture(domain.Culture); - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) - { - _logger.LogDebug("{TracePrefix}Got domain on node {DomainContentId}, set culture to {CultureName}", tracePrefix, domain.ContentId, request.Culture); - } - } - else - { - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) - { - _logger.LogDebug("{TracePrefix}No match.", tracePrefix); - } - } + if (request.PublishedContent == null) + { + return; + } + + var nodePath = request.PublishedContent.Path; + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("{TracePrefix}Path={NodePath}", tracePrefix, nodePath); } - internal bool FindTemplateRenderingEngineInDirectory(DirectoryInfo directory, string alias, string[] extensions) + var rootNodeId = request.Domain != null ? request.Domain.ContentId : (int?)null; + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + Domain? domain = + DomainUtilities.FindWildcardDomainInPath(umbracoContext.PublishedSnapshot.Domains?.GetAll(true), nodePath, + rootNodeId); + + // always has a contentId and a culture + if (domain != null) { - if (directory == null || directory.Exists == false) + request.SetCulture(domain.Culture); + if (_logger.IsEnabled(LogLevel.Debug)) { - return false; + _logger.LogDebug("{TracePrefix}Got domain on node {DomainContentId}, set culture to {CultureName}", + tracePrefix, domain.ContentId, request.Culture); } - - var pos = alias.IndexOf('/'); - if (pos > 0) + } + else + { + if (_logger.IsEnabled(LogLevel.Debug)) { - // recurse - DirectoryInfo? subdir = directory.GetDirectories(alias.Substring(0, pos)).FirstOrDefault(); - alias = alias.Substring(pos + 1); - return subdir != null && FindTemplateRenderingEngineInDirectory(subdir, alias, extensions); + _logger.LogDebug("{TracePrefix}No match.", tracePrefix); } + } + } - // look here - return directory.GetFiles().Any(f => extensions.Any(e => f.Name.InvariantEquals(alias + e))); + internal bool FindTemplateRenderingEngineInDirectory(DirectoryInfo directory, string alias, string[] extensions) + { + if (directory == null || directory.Exists == false) + { + return false; } - /// - /// Tries to find the document matching the request, by running the IPublishedContentFinder instances. - /// - /// There is no finder collection. - internal async Task FindPublishedContent(IPublishedRequestBuilder request) + var pos = alias.IndexOf('/'); + if (pos > 0) { - const string tracePrefix = "FindPublishedContent: "; + // recurse + DirectoryInfo? subdir = directory.GetDirectories(alias.Substring(0, pos)).FirstOrDefault(); + alias = alias.Substring(pos + 1); + return subdir != null && FindTemplateRenderingEngineInDirectory(subdir, alias, extensions); + } + + // look here + return directory.GetFiles().Any(f => extensions.Any(e => f.Name.InvariantEquals(alias + e))); + } - // look for the document - // the first successful finder, if any, will set this.PublishedContent, and may also set this.Template - // some finders may implement caching - DisposableTimer? profilingScope = null; - try + /// + /// Tries to find the document matching the request, by running the IPublishedContentFinder instances. + /// + /// There is no finder collection. + internal async Task FindPublishedContent(IPublishedRequestBuilder request) + { + const string tracePrefix = "FindPublishedContent: "; + + // look for the document + // the first successful finder, if any, will set this.PublishedContent, and may also set this.Template + // some finders may implement caching + DisposableTimer? profilingScope = null; + try + { + if (_logger.IsEnabled(LogLevel.Debug)) { - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) - { - profilingScope = _profilingLogger.DebugDuration( + profilingScope = _profilingLogger.DebugDuration( $"{tracePrefix}Begin finders", $"{tracePrefix}End finders"); - } + } - // iterate but return on first one that finds it - var found = false; - foreach (var contentFinder in _contentFinders) + // iterate but return on first one that finds it + var found = false; + foreach (IContentFinder contentFinder in _contentFinders) + { + if (_logger.IsEnabled(LogLevel.Debug)) { - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) - { - _logger.LogDebug("Finder {ContentFinderType}", contentFinder.GetType().FullName); - } - found = await contentFinder.TryFindContent(request); - if (found) - { - break; - } + _logger.LogDebug("Finder {ContentFinderType}", contentFinder.GetType().FullName); } - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) + found = await contentFinder.TryFindContent(request); + if (found) { - _logger.LogDebug( + break; + } + } + + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug( "Found? {Found}, Content: {PublishedContentId}, Template: {TemplateAlias}, Domain: {Domain}, Culture: {Culture}, StatusCode: {StatusCode}", found, request.HasPublishedContent() ? request.PublishedContent?.Id : "NULL", @@ -453,393 +464,422 @@ internal async Task FindPublishedContent(IPublishedRequestBuilder request) request.HasDomain() ? request.Domain?.ToString() : "NULL", request.Culture ?? "NULL", request.ResponseStatusCode); - } - - return found; } - finally + + return found; + } + finally + { + profilingScope?.Dispose(); + } + } + + /// + /// Handles the published content (if any). + /// + /// The request builder. + /// + /// Handles "not found", internal redirects ... + /// things that must be handled in one place because they can create loops + /// + private async Task HandlePublishedContent(IPublishedRequestBuilder request) + { + // because these might loop, we have to have some sort of infinite loop detection + int i = 0, j = 0; + const int maxLoop = 8; + do + { + if (_logger.IsEnabled(LogLevel.Debug)) { - profilingScope?.Dispose(); + _logger.LogDebug("HandlePublishedContent: Loop {LoopCounter}", i); } - } - /// - /// Handles the published content (if any). - /// - /// The request builder. - /// - /// Handles "not found", internal redirects ... - /// things that must be handled in one place because they can create loops - /// - private async Task HandlePublishedContent(IPublishedRequestBuilder request) - { - // because these might loop, we have to have some sort of infinite loop detection - int i = 0, j = 0; - const int maxLoop = 8; - do + // handle not found + if (request.PublishedContent == null) { - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) + request.SetIs404(); + if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("HandlePublishedContent: Loop {LoopCounter}", i); + _logger.LogDebug("HandlePublishedContent: No document, try last chance lookup"); } - // handle not found - if (request.PublishedContent == null) + // if it fails then give up, there isn't much more that we can do + if (await _contentLastChanceFinder.TryFindContent(request) == false) { - request.SetIs404(); - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) + if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("HandlePublishedContent: No document, try last chance lookup"); + _logger.LogDebug("HandlePublishedContent: Failed to find a document, give up"); } - // if it fails then give up, there isn't much more that we can do - if (await _contentLastChanceFinder.TryFindContent(request) == false) - { - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) - { - _logger.LogDebug("HandlePublishedContent: Failed to find a document, give up"); - } - break; - } - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) - { - _logger.LogDebug("HandlePublishedContent: Found a document"); - } - } - - // follow internal redirects as long as it's not running out of control ie infinite loop of some sort - j = 0; - while (FollowInternalRedirects(request) && j++ < maxLoop) - { } - - // we're running out of control - if (j == maxLoop) - { break; } - // loop while we don't have page, ie the redirect or access - // got us to nowhere and now we need to run the notFoundLookup again - // as long as it's not running out of control ie infinite loop of some sort - } while (request.PublishedContent == null && i++ < maxLoop); - - if (i == maxLoop || j == maxLoop) - { - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) + if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("HandlePublishedContent: Looks like we are running into an infinite loop, abort"); + _logger.LogDebug("HandlePublishedContent: Found a document"); } - request.SetPublishedContent(null); } - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) + + // follow internal redirects as long as it's not running out of control ie infinite loop of some sort + j = 0; + while (FollowInternalRedirects(request) && j++ < maxLoop) { - _logger.LogDebug("HandlePublishedContent: End"); } - } - /// - /// Follows internal redirections through the umbracoInternalRedirectId document property. - /// - /// The request builder. - /// A value indicating whether redirection took place and led to a new published document. - /// - /// Redirecting to a different site root and/or culture will not pick the new site root nor the new culture. - /// As per legacy, if the redirect does not work, we just ignore it. - /// - private bool FollowInternalRedirects(IPublishedRequestBuilder request) - { - if (request.PublishedContent == null) + // we're running out of control + if (j == maxLoop) { - throw new InvalidOperationException("There is no PublishedContent."); + break; } - // don't try to find a redirect if the property doesn't exist - if (request.PublishedContent.HasProperty(Constants.Conventions.Content.InternalRedirectId) == false) + // loop while we don't have page, ie the redirect or access + // got us to nowhere and now we need to run the notFoundLookup again + // as long as it's not running out of control ie infinite loop of some sort + } while (request.PublishedContent == null && i++ < maxLoop); + + if (i == maxLoop || j == maxLoop) + { + if (_logger.IsEnabled(LogLevel.Debug)) { - return false; + _logger.LogDebug("HandlePublishedContent: Looks like we are running into an infinite loop, abort"); } - var redirect = false; - var valid = false; - IPublishedContent? internalRedirectNode = null; - var internalRedirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.InternalRedirectId, defaultValue: -1); - var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + request.SetPublishedContent(null); + } + + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("HandlePublishedContent: End"); + } + } + + /// + /// Follows internal redirections through the umbracoInternalRedirectId document property. + /// + /// The request builder. + /// A value indicating whether redirection took place and led to a new published document. + /// + /// Redirecting to a different site root and/or culture will not pick the new site root nor the new culture. + /// As per legacy, if the redirect does not work, we just ignore it. + /// + private bool FollowInternalRedirects(IPublishedRequestBuilder request) + { + if (request.PublishedContent == null) + { + throw new InvalidOperationException("There is no PublishedContent."); + } + + // don't try to find a redirect if the property doesn't exist + if (request.PublishedContent.HasProperty(Constants.Conventions.Content.InternalRedirectId) == false) + { + return false; + } + + var redirect = false; + var valid = false; + IPublishedContent? internalRedirectNode = null; + var internalRedirectId = request.PublishedContent.Value(_publishedValueFallback, + Constants.Conventions.Content.InternalRedirectId, defaultValue: -1); + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - if (internalRedirectId > 0) + if (internalRedirectId > 0) + { + // try and get the redirect node from a legacy integer ID + valid = true; + internalRedirectNode = umbracoContext.Content?.GetById(internalRedirectId); + } + else + { + GuidUdi? udiInternalRedirectId = request.PublishedContent.Value(_publishedValueFallback, + Constants.Conventions.Content.InternalRedirectId); + if (udiInternalRedirectId is not null) { - // try and get the redirect node from a legacy integer ID + // try and get the redirect node from a UDI Guid valid = true; - internalRedirectNode = umbracoContext.Content?.GetById(internalRedirectId); - } - else - { - GuidUdi? udiInternalRedirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.InternalRedirectId); - if (udiInternalRedirectId is not null) - { - // try and get the redirect node from a UDI Guid - valid = true; - internalRedirectNode = umbracoContext.Content?.GetById(udiInternalRedirectId.Guid); - } + internalRedirectNode = umbracoContext.Content?.GetById(udiInternalRedirectId.Guid); } + } - if (valid == false) + if (valid == false) + { + // bad redirect - log and display the current page (legacy behavior) + if (_logger.IsEnabled(LogLevel.Debug)) { - // bad redirect - log and display the current page (legacy behavior) - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) - { - _logger.LogDebug( + _logger.LogDebug( "FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: value is not an int nor a GuidUdi.", - request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId)?.GetSourceValue()); - } + request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId) + ?.GetSourceValue()); } + } - if (internalRedirectNode == null) + if (internalRedirectNode == null) + { + if (_logger.IsEnabled(LogLevel.Debug)) { - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) - { - _logger.LogDebug( + _logger.LogDebug( "FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: no such published document.", - request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId)?.GetSourceValue()); - } + request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId) + ?.GetSourceValue()); } - else if (internalRedirectId == request.PublishedContent.Id) + } + else if (internalRedirectId == request.PublishedContent.Id) + { + // redirect to self + if (_logger.IsEnabled(LogLevel.Debug)) { - // redirect to self - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) - { - _logger.LogDebug("FollowInternalRedirects: Redirecting to self, ignore"); - } + _logger.LogDebug("FollowInternalRedirects: Redirecting to self, ignore"); } - else - { - // save since it will be cleared - ITemplate? template = request.Template; + } + else + { + // save since it will be cleared + ITemplate? template = request.Template; - request.SetInternalRedirect(internalRedirectNode); // don't use .PublishedContent here + request.SetInternalRedirect(internalRedirectNode); // don't use .PublishedContent here - // must restore the template if it's an internal redirect & the config option is set - if (request.IsInternalRedirect && _webRoutingSettings.InternalRedirectPreservesTemplate) - { - // restore - request.SetTemplate(template); - } - - redirect = true; - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) - { - _logger.LogDebug("FollowInternalRedirects: Redirecting to id={InternalRedirectId}", internalRedirectId); - } + // must restore the template if it's an internal redirect & the config option is set + if (request.IsInternalRedirect && _webRoutingSettings.InternalRedirectPreservesTemplate) + { + // restore + request.SetTemplate(template); } - return redirect; + redirect = true; + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("FollowInternalRedirects: Redirecting to id={InternalRedirectId}", internalRedirectId); + } } - /// - /// Finds a template for the current node, if any. - /// - /// The request builder. - /// If the content was found by the finders, before anything such as 404, redirect... took place. - private void FindTemplate(IPublishedRequestBuilder request, bool contentFoundByFinders) + return redirect; + } + + /// + /// Finds a template for the current node, if any. + /// + /// The request builder. + /// + /// If the content was found by the finders, before anything such as 404, redirect... + /// took place. + /// + private void FindTemplate(IPublishedRequestBuilder request, bool contentFoundByFinders) + { + // TODO: We've removed the event, might need to re-add? + // NOTE: at the moment there is only 1 way to find a template, and then ppl must + // use the Prepared event to change the template if they wish. Should we also + // implement an ITemplateFinder logic? + if (request.PublishedContent == null) { - // TODO: We've removed the event, might need to re-add? - // NOTE: at the moment there is only 1 way to find a template, and then ppl must - // use the Prepared event to change the template if they wish. Should we also - // implement an ITemplateFinder logic? - if (request.PublishedContent == null) - { - request.SetTemplate(null); - return; - } + request.SetTemplate(null); + return; + } - // read the alternate template alias, from querystring, form, cookie or server vars, - // only if the published content is the initial once, else the alternate template - // does not apply - // + optionally, apply the alternate template on internal redirects - var useAltTemplate = contentFoundByFinders - || (_webRoutingSettings.InternalRedirectPreservesTemplate && request.IsInternalRedirect); + // read the alternate template alias, from querystring, form, cookie or server vars, + // only if the published content is the initial once, else the alternate template + // does not apply + // + optionally, apply the alternate template on internal redirects + var useAltTemplate = contentFoundByFinders + || (_webRoutingSettings.InternalRedirectPreservesTemplate && request.IsInternalRedirect); - var altTemplate = useAltTemplate - ? _requestAccessor.GetRequestValue(Constants.Conventions.Url.AltTemplate) - : null; + var altTemplate = useAltTemplate + ? _requestAccessor.GetRequestValue(Constants.Conventions.Url.AltTemplate) + : null; - if (string.IsNullOrWhiteSpace(altTemplate)) + if (string.IsNullOrWhiteSpace(altTemplate)) + { + // we don't have an alternate template specified. use the current one if there's one already, + // which can happen if a content lookup also set the template (LookupByNiceUrlAndTemplate...), + // else lookup the template id on the document then lookup the template with that id. + if (request.HasTemplate()) { - // we don't have an alternate template specified. use the current one if there's one already, - // which can happen if a content lookup also set the template (LookupByNiceUrlAndTemplate...), - // else lookup the template id on the document then lookup the template with that id. - if (request.HasTemplate()) + if (_logger.IsEnabled(LogLevel.Debug)) { - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) - { - _logger.LogDebug("FindTemplate: Has a template already, and no alternate template."); - } - return; + _logger.LogDebug("FindTemplate: Has a template already, and no alternate template."); } - // TODO: We need to limit altTemplate to only allow templates that are assigned to the current document type! - // if the template isn't assigned to the document type we should log a warning and return 404 - var templateId = request.PublishedContent.TemplateId; - ITemplate? template = GetTemplate(templateId); - request.SetTemplate(template); - if (template != null) - { - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) - { - _logger.LogDebug("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); - } - } - else + return; + } + + // TODO: We need to limit altTemplate to only allow templates that are assigned to the current document type! + // if the template isn't assigned to the document type we should log a warning and return 404 + var templateId = request.PublishedContent.TemplateId; + ITemplate? template = GetTemplate(templateId); + request.SetTemplate(template); + if (template != null) + { + if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogWarning("FindTemplate: Could not find template with id {TemplateId}", templateId); + _logger.LogDebug("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", + template.Id, template.Alias); } } else { - // we have an alternate template specified. lookup the template with that alias - // this means the we override any template that a content lookup might have set - // so /path/to/page/template1?altTemplate=template2 will use template2 + _logger.LogWarning("FindTemplate: Could not find template with id {TemplateId}", templateId); + } + } + else + { + // we have an alternate template specified. lookup the template with that alias + // this means the we override any template that a content lookup might have set + // so /path/to/page/template1?altTemplate=template2 will use template2 - // ignore if the alias does not match - just trace - if (request.HasTemplate()) - { - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) - { - _logger.LogDebug("FindTemplate: Has a template already, but also an alternative template."); - } - } - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) + // ignore if the alias does not match - just trace + if (request.HasTemplate()) + { + if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("FindTemplate: Look for alternative template alias={AltTemplate}", altTemplate); + _logger.LogDebug("FindTemplate: Has a template already, but also an alternative template."); } + } - // IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings - if (request.PublishedContent.IsAllowedTemplate( + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("FindTemplate: Look for alternative template alias={AltTemplate}", altTemplate); + } + + // IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings + if (request.PublishedContent.IsAllowedTemplate( _fileService, _contentTypeService, _webRoutingSettings.DisableAlternativeTemplates, _webRoutingSettings.ValidateAlternativeTemplates, altTemplate)) - { - // allowed, use - ITemplate? template = _fileService.GetTemplate(altTemplate); + { + // allowed, use + ITemplate? template = _fileService.GetTemplate(altTemplate); - if (template != null) - { - request.SetTemplate(template); - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) - { - _logger.LogDebug("FindTemplate: Got alternative template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); - } - } - else + if (template != null) + { + request.SetTemplate(template); + if (_logger.IsEnabled(LogLevel.Debug)) { - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) - { - _logger.LogDebug("FindTemplate: The alternative template with alias={AltTemplate} does not exist, ignoring.", altTemplate); - } + _logger.LogDebug("FindTemplate: Got alternative template id={TemplateId} alias={TemplateAlias}", + template.Id, template.Alias); } } else { - _logger.LogWarning("FindTemplate: Alternative template {TemplateAlias} is not allowed on node {NodeId}, ignoring.", altTemplate, request.PublishedContent.Id); - // no allowed, back to default - var templateId = request.PublishedContent.TemplateId; - ITemplate? template = GetTemplate(templateId); - request.SetTemplate(template); - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) + if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", template?.Id, template?.Alias); + _logger.LogDebug( + "FindTemplate: The alternative template with alias={AltTemplate} does not exist, ignoring.", + altTemplate); } } } - - if (!request.HasTemplate()) + else { - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) + _logger.LogWarning( + "FindTemplate: Alternative template {TemplateAlias} is not allowed on node {NodeId}, ignoring.", + altTemplate, request.PublishedContent.Id); + // no allowed, back to default + var templateId = request.PublishedContent.TemplateId; + ITemplate? template = GetTemplate(templateId); + request.SetTemplate(template); + if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("FindTemplate: No template was found."); + _logger.LogDebug("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", + template?.Id, template?.Alias); } - - // initial idea was: if we're not already 404 and UmbracoSettings.HandleMissingTemplateAs404 is true - // then reset _pcr.Document to null to force a 404. - // - // but: because we want to let MVC hijack routes even though no template is defined, we decide that - // a missing template is OK but the request will then be forwarded to MVC, which will need to take - // care of everything. - // - // so, don't set _pcr.Document to null here } } - private ITemplate? GetTemplate(int? templateId) + if (!request.HasTemplate()) { - if (templateId.HasValue == false || templateId.Value == default) - { - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) - { - _logger.LogDebug("GetTemplateModel: No template."); - } - return null; - } - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) + if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("GetTemplateModel: Get template id={TemplateId}", templateId); + _logger.LogDebug("FindTemplate: No template was found."); } - if (templateId == null) - { - throw new InvalidOperationException("The template is not set, the page cannot render."); - } + // initial idea was: if we're not already 404 and UmbracoSettings.HandleMissingTemplateAs404 is true + // then reset _pcr.Document to null to force a 404. + // + // but: because we want to let MVC hijack routes even though no template is defined, we decide that + // a missing template is OK but the request will then be forwarded to MVC, which will need to take + // care of everything. + // + // so, don't set _pcr.Document to null here + } + } - ITemplate? template = _fileService.GetTemplate(templateId.Value); - if (template == null) - { - throw new InvalidOperationException("The template with Id " + templateId + " does not exist, the page cannot render."); - } - if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) + private ITemplate? GetTemplate(int? templateId) + { + if (templateId.HasValue == false || templateId.Value == default) + { + if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("GetTemplateModel: Got template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); + _logger.LogDebug("GetTemplateModel: No template."); } - return template; + + return null; } - /// - /// Follows external redirection through umbracoRedirect document property. - /// - /// As per legacy, if the redirect does not work, we just ignore it. - private void FollowExternalRedirect(IPublishedRequestBuilder request) + if (_logger.IsEnabled(LogLevel.Debug)) { - if (request.PublishedContent == null) - { - return; - } + _logger.LogDebug("GetTemplateModel: Get template id={TemplateId}", templateId); + } - // don't try to find a redirect if the property doesn't exist - if (request.PublishedContent.HasProperty(Constants.Conventions.Content.Redirect) == false) - { - return; - } + if (templateId == null) + { + throw new InvalidOperationException("The template is not set, the page cannot render."); + } - var redirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.Redirect, defaultValue: -1); - var redirectUrl = "#"; - if (redirectId > 0) - { - redirectUrl = _publishedUrlProvider.GetUrl(redirectId); - } - else - { - // might be a UDI instead of an int Id - GuidUdi? redirectUdi = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.Redirect); - if (redirectUdi is not null) - { - redirectUrl = _publishedUrlProvider.GetUrl(redirectUdi.Guid); - } - } + ITemplate? template = _fileService.GetTemplate(templateId.Value); + if (template == null) + { + throw new InvalidOperationException("The template with Id " + templateId + + " does not exist, the page cannot render."); + } + + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("GetTemplateModel: Got template id={TemplateId} alias={TemplateAlias}", template.Id, + template.Alias); + } + + return template; + } - if (redirectUrl != "#") + /// + /// Follows external redirection through umbracoRedirect document property. + /// + /// As per legacy, if the redirect does not work, we just ignore it. + private void FollowExternalRedirect(IPublishedRequestBuilder request) + { + if (request.PublishedContent == null) + { + return; + } + + // don't try to find a redirect if the property doesn't exist + if (request.PublishedContent.HasProperty(Constants.Conventions.Content.Redirect) == false) + { + return; + } + + var redirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.Redirect, + defaultValue: -1); + var redirectUrl = "#"; + if (redirectId > 0) + { + redirectUrl = _publishedUrlProvider.GetUrl(redirectId); + } + else + { + // might be a UDI instead of an int Id + GuidUdi? redirectUdi = + request.PublishedContent.Value(_publishedValueFallback, + Constants.Conventions.Content.Redirect); + if (redirectUdi is not null) { - request.SetRedirect(redirectUrl); + redirectUrl = _publishedUrlProvider.GetUrl(redirectUdi.Guid); } } + + if (redirectUrl != "#") + { + request.SetRedirect(redirectUrl); + } } } diff --git a/src/Umbraco.Core/Routing/RouteDirection.cs b/src/Umbraco.Core/Routing/RouteDirection.cs index 33dad7b08107..546ea2030a00 100644 --- a/src/Umbraco.Core/Routing/RouteDirection.cs +++ b/src/Umbraco.Core/Routing/RouteDirection.cs @@ -1,18 +1,17 @@ -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// The direction of a route +/// +public enum RouteDirection { /// - /// The direction of a route + /// An inbound route used to map a URL to a content item /// - public enum RouteDirection - { - /// - /// An inbound route used to map a URL to a content item - /// - Inbound = 1, + Inbound = 1, - /// - /// An outbound route used to generate a URL for a content item - /// - Outbound = 2 - } + /// + /// An outbound route used to generate a URL for a content item + /// + Outbound = 2 } diff --git a/src/Umbraco.Core/Routing/RouteRequestOptions.cs b/src/Umbraco.Core/Routing/RouteRequestOptions.cs index 97792ebad38b..960bf4bd3610 100644 --- a/src/Umbraco.Core/Routing/RouteRequestOptions.cs +++ b/src/Umbraco.Core/Routing/RouteRequestOptions.cs @@ -1,29 +1,26 @@ -using System; +namespace Umbraco.Cms.Core.Routing; -namespace Umbraco.Cms.Core.Routing +/// +/// Options for routing an Umbraco request +/// +public struct RouteRequestOptions : IEquatable { /// - /// Options for routing an Umbraco request + /// Initializes a new instance of the struct. /// - public struct RouteRequestOptions : IEquatable - { - /// - /// Initializes a new instance of the struct. - /// - public RouteRequestOptions(RouteDirection direction) => RouteDirection = direction; + public RouteRequestOptions(RouteDirection direction) => RouteDirection = direction; - /// - /// Gets the - /// - public RouteDirection RouteDirection { get; } + /// + /// Gets the + /// + public RouteDirection RouteDirection { get; } - /// - public override bool Equals(object? obj) => obj is RouteRequestOptions options && Equals(options); + /// + public override bool Equals(object? obj) => obj is RouteRequestOptions options && Equals(options); - /// - public bool Equals(RouteRequestOptions other) => RouteDirection == other.RouteDirection; + /// + public bool Equals(RouteRequestOptions other) => RouteDirection == other.RouteDirection; - /// - public override int GetHashCode() => 15391035 + RouteDirection.GetHashCode(); - } + /// + public override int GetHashCode() => 15391035 + RouteDirection.GetHashCode(); } diff --git a/src/Umbraco.Core/Routing/SiteDomainMapper.cs b/src/Umbraco.Core/Routing/SiteDomainMapper.cs index a74d4532e1a7..b317b3b55eba 100644 --- a/src/Umbraco.Core/Routing/SiteDomainMapper.cs +++ b/src/Umbraco.Core/Routing/SiteDomainMapper.cs @@ -1,429 +1,424 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Text.RegularExpressions; -using System.Threading; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// Provides utilities to handle site domains. +/// +public class SiteDomainMapper : ISiteDomainMapper, IDisposable { - /// - /// Provides utilities to handle site domains. - /// - public class SiteDomainMapper : ISiteDomainMapper, IDisposable - { - public void Dispose() => - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(true); + public void Dispose() => + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(true); - protected virtual void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) { - if (!_disposedValue) + if (disposing) { - if (disposing) - { - // This is pretty nasty disposing a static on an instance but it's because this whole class - // is pretty fubar. I'm sure we've fixed this all up in netcore now? We need to remove all statics. - _configLock.Dispose(); - } - - _disposedValue = true; + // This is pretty nasty disposing a static on an instance but it's because this whole class + // is pretty fubar. I'm sure we've fixed this all up in netcore now? We need to remove all statics. + _configLock.Dispose(); } + + _disposedValue = true; } + } - #region Configure + #region Configure - private readonly ReaderWriterLockSlim _configLock = new(); - private Dictionary>? _qualifiedSites; - private bool _disposedValue; + private readonly ReaderWriterLockSlim _configLock = new(); + private Dictionary>? _qualifiedSites; + private bool _disposedValue; - internal Dictionary? Sites { get; private set; } - internal Dictionary>? Bindings { get; private set; } + internal Dictionary? Sites { get; private set; } + internal Dictionary>? Bindings { get; private set; } - // these are for validation - //private const string DomainValidationSource = @"^(\*|((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/[-\w]*)?))$"; - private const string DomainValidationSource = @"^(((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/)?))$"; + // these are for validation + //private const string DomainValidationSource = @"^(\*|((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/[-\w]*)?))$"; + private const string DomainValidationSource = @"^(((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/)?))$"; - private static readonly Regex s_domainValidation = - new(DomainValidationSource, RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex s_domainValidation = + new(DomainValidationSource, RegexOptions.IgnoreCase | RegexOptions.Compiled); - /// - /// Clears the entire configuration. - /// - public void Clear() + /// + /// Clears the entire configuration. + /// + public void Clear() + { + try { - try - { - _configLock.EnterWriteLock(); + _configLock.EnterWriteLock(); - Sites = null; - Bindings = null; - _qualifiedSites = null; - } - finally + Sites = null; + Bindings = null; + _qualifiedSites = null; + } + finally + { + if (_configLock.IsWriteLockHeld) { - if (_configLock.IsWriteLockHeld) - { - _configLock.ExitWriteLock(); - } + _configLock.ExitWriteLock(); } } + } - private IEnumerable ValidateDomains(IEnumerable domains) => - // must use authority format w/optional scheme and port, but no path - // any domain should appear only once - domains.Select(domain => + private IEnumerable ValidateDomains(IEnumerable domains) => + // must use authority format w/optional scheme and port, but no path + // any domain should appear only once + domains.Select(domain => + { + if (!s_domainValidation.IsMatch(domain)) { - if (!s_domainValidation.IsMatch(domain)) - { - throw new ArgumentOutOfRangeException(nameof(domains), $"Invalid domain: \"{domain}\"."); - } + throw new ArgumentOutOfRangeException(nameof(domains), $"Invalid domain: \"{domain}\"."); + } - return domain; - }); + return domain; + }); - /// - /// Adds a site. - /// - /// A key uniquely identifying the site. - /// The site domains. - /// At the moment there is no public way to remove a site. Clear and reconfigure. - public void AddSite(string key, IEnumerable domains) + /// + /// Adds a site. + /// + /// A key uniquely identifying the site. + /// The site domains. + /// At the moment there is no public way to remove a site. Clear and reconfigure. + public void AddSite(string key, IEnumerable domains) + { + try { - try - { - _configLock.EnterWriteLock(); + _configLock.EnterWriteLock(); - Sites = Sites ?? new Dictionary(); - Sites[key] = ValidateDomains(domains).ToArray(); - _qualifiedSites = null; - } - finally + Sites = Sites ?? new Dictionary(); + Sites[key] = ValidateDomains(domains).ToArray(); + _qualifiedSites = null; + } + finally + { + if (_configLock.IsWriteLockHeld) { - if (_configLock.IsWriteLockHeld) - { - _configLock.ExitWriteLock(); - } + _configLock.ExitWriteLock(); } } + } - /// - /// Adds a site. - /// - /// A key uniquely identifying the site. - /// The site domains. - /// At the moment there is no public way to remove a site. Clear and reconfigure. - public void AddSite(string key, params string[] domains) + /// + /// Adds a site. + /// + /// A key uniquely identifying the site. + /// The site domains. + /// At the moment there is no public way to remove a site. Clear and reconfigure. + public void AddSite(string key, params string[] domains) + { + try { - try - { - _configLock.EnterWriteLock(); + _configLock.EnterWriteLock(); - Sites = Sites ?? new Dictionary(); - Sites[key] = ValidateDomains(domains).ToArray(); - _qualifiedSites = null; - } - finally + Sites = Sites ?? new Dictionary(); + Sites[key] = ValidateDomains(domains).ToArray(); + _qualifiedSites = null; + } + finally + { + if (_configLock.IsWriteLockHeld) { - if (_configLock.IsWriteLockHeld) - { - _configLock.ExitWriteLock(); - } + _configLock.ExitWriteLock(); } } + } - /// - /// Removes a site. - /// - /// A key uniquely identifying the site. - internal void RemoveSite(string key) + /// + /// Removes a site. + /// + /// A key uniquely identifying the site. + internal void RemoveSite(string key) + { + try { - try - { - _configLock.EnterWriteLock(); + _configLock.EnterWriteLock(); - if (Sites == null || !Sites.ContainsKey(key)) - { - return; - } + if (Sites == null || !Sites.ContainsKey(key)) + { + return; + } - Sites.Remove(key); - if (Sites.Count == 0) - { - Sites = null; - } + Sites.Remove(key); + if (Sites.Count == 0) + { + Sites = null; + } - if (Bindings != null && Bindings.ContainsKey(key)) + if (Bindings != null && Bindings.ContainsKey(key)) + { + foreach (var b in Bindings[key]) { - foreach (var b in Bindings[key]) - { - Bindings[b].Remove(key); - if (Bindings[b].Count == 0) - { - Bindings.Remove(b); - } - } - - Bindings.Remove(key); - if (Bindings.Count > 0) + Bindings[b].Remove(key); + if (Bindings[b].Count == 0) { - Bindings = null; + Bindings.Remove(b); } } - _qualifiedSites = null; - } - finally - { - if (_configLock.IsWriteLockHeld) + Bindings.Remove(key); + if (Bindings.Count > 0) { - _configLock.ExitWriteLock(); + Bindings = null; } } - } - /// - /// Binds some sites. - /// - /// The keys uniquely identifying the sites to bind. - /// - /// At the moment there is no public way to unbind sites. Clear and reconfigure. - /// If site1 is bound to site2 and site2 is bound to site3 then site1 is bound to site3. - /// - public void BindSites(params string[] keys) + _qualifiedSites = null; + } + finally { - try + if (_configLock.IsWriteLockHeld) { - _configLock.EnterWriteLock(); + _configLock.ExitWriteLock(); + } + } + } - foreach (var key in keys.Where(key => !Sites?.ContainsKey(key) ?? false)) - { - throw new ArgumentException($"Not an existing site key: {key}.", nameof(keys)); - } + /// + /// Binds some sites. + /// + /// The keys uniquely identifying the sites to bind. + /// + /// At the moment there is no public way to unbind sites. Clear and reconfigure. + /// If site1 is bound to site2 and site2 is bound to site3 then site1 is bound to site3. + /// + public void BindSites(params string[] keys) + { + try + { + _configLock.EnterWriteLock(); - Bindings = Bindings ?? new Dictionary>(); + foreach (var key in keys.Where(key => !Sites?.ContainsKey(key) ?? false)) + { + throw new ArgumentException($"Not an existing site key: {key}.", nameof(keys)); + } - var allkeys = Bindings - .Where(kvp => keys.Contains(kvp.Key)) - .SelectMany(kvp => kvp.Value) - .Union(keys) - .ToArray(); + Bindings = Bindings ?? new Dictionary>(); - foreach (var key in allkeys) - { - if (!Bindings.ContainsKey(key)) - { - Bindings[key] = new List(); - } + var allkeys = Bindings + .Where(kvp => keys.Contains(kvp.Key)) + .SelectMany(kvp => kvp.Value) + .Union(keys) + .ToArray(); - var xkey = key; - IEnumerable addKeys = allkeys.Where(k => k != xkey).Except(Bindings[key]); - Bindings[key].AddRange(addKeys); - } - } - finally + foreach (var key in allkeys) { - if (_configLock.IsWriteLockHeld) + if (!Bindings.ContainsKey(key)) { - _configLock.ExitWriteLock(); + Bindings[key] = new List(); } + + var xkey = key; + IEnumerable addKeys = allkeys.Where(k => k != xkey).Except(Bindings[key]); + Bindings[key].AddRange(addKeys); + } + } + finally + { + if (_configLock.IsWriteLockHeld) + { + _configLock.ExitWriteLock(); } } + } - #endregion + #endregion - #region Map domains + #region Map domains - /// - public virtual DomainAndUri? MapDomain(IReadOnlyCollection domainAndUris, Uri current, - string? culture, string? defaultCulture) - { - var currentAuthority = current.GetLeftPart(UriPartial.Authority); - Dictionary? qualifiedSites = GetQualifiedSites(current); + /// + public virtual DomainAndUri? MapDomain(IReadOnlyCollection domainAndUris, Uri current, + string? culture, string? defaultCulture) + { + var currentAuthority = current.GetLeftPart(UriPartial.Authority); + Dictionary? qualifiedSites = GetQualifiedSites(current); - return MapDomain(domainAndUris, qualifiedSites, currentAuthority, culture, defaultCulture); - } + return MapDomain(domainAndUris, qualifiedSites, currentAuthority, culture, defaultCulture); + } - /// - public virtual IEnumerable MapDomains(IReadOnlyCollection domainAndUris, - Uri current, bool excludeDefault, string? culture, string? defaultCulture) - { - // TODO: ignoring cultures entirely? + /// + public virtual IEnumerable MapDomains(IReadOnlyCollection domainAndUris, + Uri current, bool excludeDefault, string? culture, string? defaultCulture) + { + // TODO: ignoring cultures entirely? - var currentAuthority = current.GetLeftPart(UriPartial.Authority); - KeyValuePair[]? candidateSites = null; - IEnumerable ret = domainAndUris; + var currentAuthority = current.GetLeftPart(UriPartial.Authority); + KeyValuePair[]? candidateSites = null; + IEnumerable ret = domainAndUris; - try - { - _configLock.EnterReadLock(); + try + { + _configLock.EnterReadLock(); - Dictionary? qualifiedSites = GetQualifiedSitesInsideLock(current); + Dictionary? qualifiedSites = GetQualifiedSitesInsideLock(current); - if (excludeDefault) + if (excludeDefault) + { + // exclude the current one (avoid producing the absolute equivalent of what GetUrl returns) + Uri hintWithSlash = current.EndPathWithSlash(); + DomainAndUri? hinted = + domainAndUris.FirstOrDefault(d => d.Uri.EndPathWithSlash().IsBaseOf(hintWithSlash)); + if (hinted != null) { - // exclude the current one (avoid producing the absolute equivalent of what GetUrl returns) - Uri hintWithSlash = current.EndPathWithSlash(); - DomainAndUri? hinted = - domainAndUris.FirstOrDefault(d => d.Uri.EndPathWithSlash().IsBaseOf(hintWithSlash)); - if (hinted != null) - { - ret = ret.Where(d => d != hinted); - } - - // exclude the default one (avoid producing a possible duplicate of what GetUrl returns) - // only if the default one cannot be the current one ie if hinted is not null - if (hinted == null && domainAndUris.Any()) - { - // it is illegal to call MapDomain if domainAndUris is empty - // also, domainAndUris should NOT contain current, hence the test on hinted - DomainAndUri? mainDomain = MapDomain(domainAndUris, qualifiedSites, currentAuthority, culture, - defaultCulture); // what GetUrl would get - ret = ret.Where(d => d != mainDomain); - } + ret = ret.Where(d => d != hinted); } - // we do our best, but can't do the impossible - if (qualifiedSites == null) + // exclude the default one (avoid producing a possible duplicate of what GetUrl returns) + // only if the default one cannot be the current one ie if hinted is not null + if (hinted == null && domainAndUris.Any()) { - return ret; + // it is illegal to call MapDomain if domainAndUris is empty + // also, domainAndUris should NOT contain current, hence the test on hinted + DomainAndUri? mainDomain = MapDomain(domainAndUris, qualifiedSites, currentAuthority, culture, + defaultCulture); // what GetUrl would get + ret = ret.Where(d => d != mainDomain); } + } - // find a site that contains the current authority - KeyValuePair currentSite = - qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority)); + // we do our best, but can't do the impossible + if (qualifiedSites == null) + { + return ret; + } - // if current belongs to a site, pick every element from domainAndUris that also belong - // to that site -- or to any site bound to that site + // find a site that contains the current authority + KeyValuePair currentSite = + qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority)); - if (!currentSite.Equals(default(KeyValuePair))) - { - candidateSites = new[] { currentSite }; - if (Bindings != null && Bindings.ContainsKey(currentSite.Key)) - { - IEnumerable> boundSites = - qualifiedSites.Where(site => Bindings[currentSite.Key].Contains(site.Key)); - candidateSites = candidateSites.Union(boundSites).ToArray(); + // if current belongs to a site, pick every element from domainAndUris that also belong + // to that site -- or to any site bound to that site - // .ToArray ensures it is evaluated before the configuration lock is exited - } - } - } - finally + if (!currentSite.Equals(default(KeyValuePair))) { - if (_configLock.IsReadLockHeld) + candidateSites = new[] {currentSite}; + if (Bindings != null && Bindings.ContainsKey(currentSite.Key)) { - _configLock.ExitReadLock(); + IEnumerable> boundSites = + qualifiedSites.Where(site => Bindings[currentSite.Key].Contains(site.Key)); + candidateSites = candidateSites.Union(boundSites).ToArray(); + + // .ToArray ensures it is evaluated before the configuration lock is exited } } - - // if we are able to filter, then filter, else return the whole lot - return candidateSites == null - ? ret - : ret.Where(d => - { - var authority = d.Uri.GetLeftPart(UriPartial.Authority); - return candidateSites.Any(site => site.Value.Contains(authority)); - }); } - - private Dictionary? GetQualifiedSites(Uri current) + finally { - try - { - _configLock.EnterReadLock(); - - return GetQualifiedSitesInsideLock(current); - } - finally + if (_configLock.IsReadLockHeld) { - if (_configLock.IsReadLockHeld) - { - _configLock.ExitReadLock(); - } + _configLock.ExitReadLock(); } } - private Dictionary? GetQualifiedSitesInsideLock(Uri current) - { - // we do our best, but can't do the impossible - if (Sites == null) + // if we are able to filter, then filter, else return the whole lot + return candidateSites == null + ? ret + : ret.Where(d => { - return null; - } + var authority = d.Uri.GetLeftPart(UriPartial.Authority); + return candidateSites.Any(site => site.Value.Contains(authority)); + }); + } + + private Dictionary? GetQualifiedSites(Uri current) + { + try + { + _configLock.EnterReadLock(); - // cached? - if (_qualifiedSites != null && _qualifiedSites.ContainsKey(current.Scheme)) + return GetQualifiedSitesInsideLock(current); + } + finally + { + if (_configLock.IsReadLockHeld) { - return _qualifiedSites[current.Scheme]; + _configLock.ExitReadLock(); } + } + } - _qualifiedSites = _qualifiedSites ?? new Dictionary>(); - - // convert sites into authority sites based upon current scheme - // because some domains in the sites might not have a scheme -- and cache - return _qualifiedSites[current.Scheme] = Sites - .ToDictionary( - kvp => kvp.Key, - kvp => kvp.Value.Select(d => - new Uri(UriUtilityCore.StartWithScheme(d, current.Scheme)) - .GetLeftPart(UriPartial.Authority)) - .ToArray() - ); - - // .ToDictionary will evaluate and create the dictionary immediately - // the new value is .ToArray so it will also be evaluated immediately - // therefore it is safe to return and exit the configuration lock + private Dictionary? GetQualifiedSitesInsideLock(Uri current) + { + // we do our best, but can't do the impossible + if (Sites == null) + { + return null; } - private DomainAndUri? MapDomain(IReadOnlyCollection domainAndUris, - Dictionary? qualifiedSites, string currentAuthority, string? culture, string? defaultCulture) + // cached? + if (_qualifiedSites != null && _qualifiedSites.ContainsKey(current.Scheme)) { - if (domainAndUris == null) - { - throw new ArgumentNullException(nameof(domainAndUris)); - } + return _qualifiedSites[current.Scheme]; + } - if (domainAndUris.Count == 0) - { - throw new ArgumentException("Cannot be empty.", nameof(domainAndUris)); - } + _qualifiedSites = _qualifiedSites ?? new Dictionary>(); + + // convert sites into authority sites based upon current scheme + // because some domains in the sites might not have a scheme -- and cache + return _qualifiedSites[current.Scheme] = Sites + .ToDictionary( + kvp => kvp.Key, + kvp => kvp.Value.Select(d => + new Uri(UriUtilityCore.StartWithScheme(d, current.Scheme)) + .GetLeftPart(UriPartial.Authority)) + .ToArray() + ); + + // .ToDictionary will evaluate and create the dictionary immediately + // the new value is .ToArray so it will also be evaluated immediately + // therefore it is safe to return and exit the configuration lock + } - if (qualifiedSites == null) - { - return domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)) - ?? domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(defaultCulture)) - ?? (culture is null ? domainAndUris.First() : null); - } + private DomainAndUri? MapDomain(IReadOnlyCollection domainAndUris, + Dictionary? qualifiedSites, string currentAuthority, string? culture, string? defaultCulture) + { + if (domainAndUris == null) + { + throw new ArgumentNullException(nameof(domainAndUris)); + } - // find a site that contains the current authority - KeyValuePair currentSite = - qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority)); + if (domainAndUris.Count == 0) + { + throw new ArgumentException("Cannot be empty.", nameof(domainAndUris)); + } - // if current belongs to a site - try to pick the first element - // from domainAndUris that also belongs to that site - DomainAndUri? ret = currentSite.Equals(default(KeyValuePair)) - ? null - : domainAndUris.FirstOrDefault(d => - currentSite.Value.Contains(d.Uri.GetLeftPart(UriPartial.Authority))); + if (qualifiedSites == null) + { + return domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)) + ?? domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(defaultCulture)) + ?? (culture is null ? domainAndUris.First() : null); + } - // no match means that either current does not belong to a site, or the site it belongs to - // does not contain any of domainAndUris. Yet we have to return something. here, it becomes - // a bit arbitrary. + // find a site that contains the current authority + KeyValuePair currentSite = + qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority)); - // look through sites in order and pick the first domainAndUri that belongs to a site - ret = ret ?? qualifiedSites - .Where(site => site.Key != currentSite.Key) - .Select(site => domainAndUris.FirstOrDefault(domainAndUri => - site.Value.Contains(domainAndUri.Uri.GetLeftPart(UriPartial.Authority)))) - .FirstOrDefault(domainAndUri => domainAndUri != null); + // if current belongs to a site - try to pick the first element + // from domainAndUris that also belongs to that site + DomainAndUri? ret = currentSite.Equals(default(KeyValuePair)) + ? null + : domainAndUris.FirstOrDefault(d => + currentSite.Value.Contains(d.Uri.GetLeftPart(UriPartial.Authority))); - // random, really - ret = ret ?? domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)) ?? domainAndUris.First(); + // no match means that either current does not belong to a site, or the site it belongs to + // does not contain any of domainAndUris. Yet we have to return something. here, it becomes + // a bit arbitrary. - return ret; - } + // look through sites in order and pick the first domainAndUri that belongs to a site + ret = ret ?? qualifiedSites + .Where(site => site.Key != currentSite.Key) + .Select(site => domainAndUris.FirstOrDefault(domainAndUri => + site.Value.Contains(domainAndUri.Uri.GetLeftPart(UriPartial.Authority)))) + .FirstOrDefault(domainAndUri => domainAndUri != null); - #endregion + // random, really + ret = ret ?? domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)) ?? domainAndUris.First(); + + return ret; } + + #endregion } diff --git a/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs index 5d298b811aae..af9107048ad4 100644 --- a/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs +++ b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs @@ -1,134 +1,129 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using Microsoft.Extensions.Options; -using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// Utility for checking paths +/// +public class UmbracoRequestPaths { + private readonly string _apiMvcPath; + private readonly string _appPath; + private readonly string _backOfficeMvcPath; + private readonly string _backOfficePath; + private readonly List _defaultUmbPaths; + private readonly string _installPath; + private readonly string _mvcArea; + private readonly string _previewMvcPath; + private readonly string _surfaceMvcPath; + /// - /// Utility for checking paths + /// Initializes a new instance of the class. /// - public class UmbracoRequestPaths + public UmbracoRequestPaths(IOptions globalSettings, IHostingEnvironment hostingEnvironment) { - private readonly string _backOfficePath; - private readonly string _mvcArea; - private readonly string _backOfficeMvcPath; - private readonly string _previewMvcPath; - private readonly string _surfaceMvcPath; - private readonly string _apiMvcPath; - private readonly string _installPath; - private readonly string _appPath; - private readonly List _defaultUmbPaths; - - /// - /// Initializes a new instance of the class. - /// - public UmbracoRequestPaths(IOptions globalSettings, IHostingEnvironment hostingEnvironment) - { - var applicationPath = hostingEnvironment.ApplicationVirtualPath; - _appPath = applicationPath.TrimStart(Constants.CharArrays.ForwardSlash); + var applicationPath = hostingEnvironment.ApplicationVirtualPath; + _appPath = applicationPath.TrimStart(Constants.CharArrays.ForwardSlash); - _backOfficePath = globalSettings.Value.GetBackOfficePath(hostingEnvironment) - .EnsureStartsWith('/').TrimStart(_appPath.EnsureStartsWith('/')).EnsureStartsWith('/'); - - _mvcArea = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment); - _defaultUmbPaths = new List { "/" + _mvcArea, "/" + _mvcArea + "/" }; - _backOfficeMvcPath = "/" + _mvcArea + "/BackOffice/"; - _previewMvcPath = "/" + _mvcArea + "/Preview/"; - _surfaceMvcPath = "/" + _mvcArea + "/Surface/"; - _apiMvcPath = "/" + _mvcArea + "/Api/"; - _installPath = hostingEnvironment.ToAbsolute(Constants.SystemDirectories.Install); - } - - /// - /// Checks if the current uri is a back office request - /// - /// - /// - /// There are some special routes we need to check to properly determine this: - /// - /// - /// These are def back office: - /// /Umbraco/BackOffice = back office - /// /Umbraco/Preview = back office - /// - /// - /// If it's not any of the above then we cannot determine if it's back office or front-end - /// so we can only assume that it is not back office. This will occur if people use an UmbracoApiController for the backoffice - /// but do not inherit from UmbracoAuthorizedApiController and do not use [IsBackOffice] attribute. - /// - /// - /// These are def front-end: - /// /Umbraco/Surface = front-end - /// /Umbraco/Api = front-end - /// But if we've got this far we'll just have to assume it's front-end anyways. - /// - /// - public bool IsBackOfficeRequest(string absPath) - { - var fullUrlPath = absPath.TrimStart(Constants.CharArrays.ForwardSlash); - var urlPath = fullUrlPath.TrimStart(_appPath).EnsureStartsWith('/'); + _backOfficePath = globalSettings.Value.GetBackOfficePath(hostingEnvironment) + .EnsureStartsWith('/').TrimStart(_appPath.EnsureStartsWith('/')).EnsureStartsWith('/'); - // check if this is in the umbraco back office - var isUmbracoPath = urlPath.InvariantStartsWith(_backOfficePath); - - // if not, then def not back office - if (isUmbracoPath == false) - { - return false; - } + _mvcArea = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment); + _defaultUmbPaths = new List {"/" + _mvcArea, "/" + _mvcArea + "/"}; + _backOfficeMvcPath = "/" + _mvcArea + "/BackOffice/"; + _previewMvcPath = "/" + _mvcArea + "/Preview/"; + _surfaceMvcPath = "/" + _mvcArea + "/Surface/"; + _apiMvcPath = "/" + _mvcArea + "/Api/"; + _installPath = hostingEnvironment.ToAbsolute(Constants.SystemDirectories.Install); + } - // if its the normal /umbraco path - if (_defaultUmbPaths.Any(x => urlPath.InvariantEquals(x))) - { - return true; - } + /// + /// Checks if the current uri is a back office request + /// + /// + /// + /// There are some special routes we need to check to properly determine this: + /// + /// + /// These are def back office: + /// /Umbraco/BackOffice = back office + /// /Umbraco/Preview = back office + /// + /// + /// If it's not any of the above then we cannot determine if it's back office or front-end + /// so we can only assume that it is not back office. This will occur if people use an UmbracoApiController for the + /// backoffice + /// but do not inherit from UmbracoAuthorizedApiController and do not use [IsBackOffice] attribute. + /// + /// + /// These are def front-end: + /// /Umbraco/Surface = front-end + /// /Umbraco/Api = front-end + /// But if we've got this far we'll just have to assume it's front-end anyways. + /// + /// + public bool IsBackOfficeRequest(string absPath) + { + var fullUrlPath = absPath.TrimStart(Constants.CharArrays.ForwardSlash); + var urlPath = fullUrlPath.TrimStart(_appPath).EnsureStartsWith('/'); - // check for special back office paths - if (urlPath.InvariantStartsWith(_backOfficeMvcPath) - || urlPath.InvariantStartsWith(_previewMvcPath)) - { - return true; - } + // check if this is in the umbraco back office + var isUmbracoPath = urlPath.InvariantStartsWith(_backOfficePath); - // check for special front-end paths - if (urlPath.InvariantStartsWith(_surfaceMvcPath) - || urlPath.InvariantStartsWith(_apiMvcPath)) - { - return false; - } + // if not, then def not back office + if (isUmbracoPath == false) + { + return false; + } - // if its none of the above, we will have to try to detect if it's a PluginController route, we can detect this by - // checking how many parts the route has, for example, all PluginController routes will be routed like - // Umbraco/MYPLUGINAREA/MYCONTROLLERNAME/{action}/{id} - // so if the path contains at a minimum 3 parts: Umbraco + MYPLUGINAREA + MYCONTROLLERNAME then we will have to assume it is a - // plugin controller for the front-end. - if (urlPath.Split(Constants.CharArrays.ForwardSlash, StringSplitOptions.RemoveEmptyEntries).Length >= 3) - { - return false; - } + // if its the normal /umbraco path + if (_defaultUmbPaths.Any(x => urlPath.InvariantEquals(x))) + { + return true; + } - // if its anything else we can assume it's back office + // check for special back office paths + if (urlPath.InvariantStartsWith(_backOfficeMvcPath) + || urlPath.InvariantStartsWith(_previewMvcPath)) + { return true; } - /// - /// Checks if the current uri is an install request - /// - public bool IsInstallerRequest(string absPath) => absPath.InvariantStartsWith(_installPath); + // check for special front-end paths + if (urlPath.InvariantStartsWith(_surfaceMvcPath) + || urlPath.InvariantStartsWith(_apiMvcPath)) + { + return false; + } - /// - /// Rudimentary check to see if it's not a server side request - /// - public bool IsClientSideRequest(string absPath) + // if its none of the above, we will have to try to detect if it's a PluginController route, we can detect this by + // checking how many parts the route has, for example, all PluginController routes will be routed like + // Umbraco/MYPLUGINAREA/MYCONTROLLERNAME/{action}/{id} + // so if the path contains at a minimum 3 parts: Umbraco + MYPLUGINAREA + MYCONTROLLERNAME then we will have to assume it is a + // plugin controller for the front-end. + if (urlPath.Split(Constants.CharArrays.ForwardSlash, StringSplitOptions.RemoveEmptyEntries).Length >= 3) { - var ext = Path.GetExtension(absPath); - return !ext.IsNullOrWhiteSpace(); + return false; } + + // if its anything else we can assume it's back office + return true; + } + + /// + /// Checks if the current uri is an install request + /// + public bool IsInstallerRequest(string absPath) => absPath.InvariantStartsWith(_installPath); + + /// + /// Rudimentary check to see if it's not a server side request + /// + public bool IsClientSideRequest(string absPath) + { + var ext = Path.GetExtension(absPath); + return !ext.IsNullOrWhiteSpace(); } } diff --git a/src/Umbraco.Core/Routing/UmbracoRouteResult.cs b/src/Umbraco.Core/Routing/UmbracoRouteResult.cs index d41c7ad7c388..5311596ff4d4 100644 --- a/src/Umbraco.Core/Routing/UmbracoRouteResult.cs +++ b/src/Umbraco.Core/Routing/UmbracoRouteResult.cs @@ -1,20 +1,19 @@ -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +public enum UmbracoRouteResult { - public enum UmbracoRouteResult - { - /// - /// Routing was successful and a content item was matched - /// - Success, + /// + /// Routing was successful and a content item was matched + /// + Success, - /// - /// A redirection took place - /// - Redirect, + /// + /// A redirection took place + /// + Redirect, - /// - /// Nothing matched - /// - NotFound - } + /// + /// Nothing matched + /// + NotFound } diff --git a/src/Umbraco.Core/Routing/UriUtility.cs b/src/Umbraco.Core/Routing/UriUtility.cs index b973bdd068b6..0deb57721cad 100644 --- a/src/Umbraco.Core/Routing/UriUtility.cs +++ b/src/Umbraco.Core/Routing/UriUtility.cs @@ -1,201 +1,232 @@ -using System; using System.Text; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +public sealed class UriUtility { - public sealed class UriUtility + private static string? _appPath; + private static string? _appPathPrefix; + + public UriUtility(IHostingEnvironment hostingEnvironment) { - static string? _appPath; - static string? _appPathPrefix; + if (hostingEnvironment is null) + { + throw new ArgumentNullException(nameof(hostingEnvironment)); + } - public UriUtility(IHostingEnvironment hostingEnvironment) + ResetAppDomainAppVirtualPath(hostingEnvironment); + } + + // will be "/" or "/foo" + public string? AppPath => _appPath; + + // will be "" or "/foo" + public string? AppPathPrefix => _appPathPrefix; + + // internal for unit testing only + internal void SetAppDomainAppVirtualPath(string appPath) + { + _appPath = appPath ?? "/"; + _appPathPrefix = _appPath; + if (_appPathPrefix == "/") { - if (hostingEnvironment is null) throw new ArgumentNullException(nameof(hostingEnvironment)); - ResetAppDomainAppVirtualPath(hostingEnvironment); + _appPathPrefix = string.Empty; } + } + + internal void ResetAppDomainAppVirtualPath(IHostingEnvironment hostingEnvironment) => + SetAppDomainAppVirtualPath(hostingEnvironment.ApplicationVirtualPath); + + // adds the virtual directory if any + // see also VirtualPathUtility.ToAbsolute + // TODO: Does this do anything differently than IHostingEnvironment.ToAbsolute? Seems it does less, maybe should be removed? + public string ToAbsolute(string url) + { + //return ResolveUrl(url); + url = url.TrimStart(Constants.CharArrays.Tilde); + return _appPathPrefix + url; + } - // internal for unit testing only - internal void SetAppDomainAppVirtualPath(string appPath) + // strips the virtual directory if any + // see also VirtualPathUtility.ToAppRelative + public string ToAppRelative(string virtualPath) + { + if (_appPathPrefix is not null && virtualPath.InvariantStartsWith(_appPathPrefix) + && (virtualPath.Length == _appPathPrefix.Length || + virtualPath[_appPathPrefix.Length] == '/')) { - _appPath = appPath ?? "/"; - _appPathPrefix = _appPath; - if (_appPathPrefix == "/") - _appPathPrefix = String.Empty; + virtualPath = virtualPath.Substring(_appPathPrefix.Length); } - internal void ResetAppDomainAppVirtualPath(IHostingEnvironment hostingEnvironment) + if (virtualPath.Length == 0) { - SetAppDomainAppVirtualPath(hostingEnvironment.ApplicationVirtualPath); + virtualPath = "/"; } - // will be "/" or "/foo" - public string? AppPath => _appPath; + return virtualPath; + } - // will be "" or "/foo" - public string? AppPathPrefix => _appPathPrefix; + // maps an internal umbraco uri to a public uri + // ie with virtual directory, .aspx if required... + public Uri UriFromUmbraco(Uri uri, RequestHandlerSettings requestConfig) + { + var path = uri.GetSafeAbsolutePath(); - // adds the virtual directory if any - // see also VirtualPathUtility.ToAbsolute - // TODO: Does this do anything differently than IHostingEnvironment.ToAbsolute? Seems it does less, maybe should be removed? - public string ToAbsolute(string url) + if (path != "/" && requestConfig.AddTrailingSlash) { - //return ResolveUrl(url); - url = url.TrimStart(Constants.CharArrays.Tilde); - return _appPathPrefix + url; + path = path.EnsureEndsWith("/"); } - // strips the virtual directory if any - // see also VirtualPathUtility.ToAppRelative - public string ToAppRelative(string virtualPath) - { - if (_appPathPrefix is not null && virtualPath.InvariantStartsWith(_appPathPrefix) - && (virtualPath.Length == _appPathPrefix.Length || virtualPath[_appPathPrefix.Length] == '/')) - { - virtualPath = virtualPath.Substring(_appPathPrefix.Length); - } + path = ToAbsolute(path); - if (virtualPath.Length == 0) - { - virtualPath = "/"; - } + return uri.Rewrite(path); + } - return virtualPath; - } + // maps a media umbraco uri to a public uri + // ie with virtual directory - that is all for media + public Uri MediaUriFromUmbraco(Uri uri) + { + var path = uri.GetSafeAbsolutePath(); + path = ToAbsolute(path); + return uri.Rewrite(path); + } - // maps an internal umbraco uri to a public uri - // ie with virtual directory, .aspx if required... - public Uri UriFromUmbraco(Uri uri, RequestHandlerSettings requestConfig) - { - var path = uri.GetSafeAbsolutePath(); + // maps a public uri to an internal umbraco uri + // ie no virtual directory, no .aspx, lowercase... + public Uri UriToUmbraco(Uri uri) + { + // TODO: This is critical code that executes on every request, we should + // look into if all of this is necessary? not really sure we need ToLower? - if (path != "/" && requestConfig.AddTrailingSlash) - path = path.EnsureEndsWith("/"); + // note: no need to decode uri here because we're returning a uri + // so it will be re-encoded anyway + var path = uri.GetSafeAbsolutePath(); - path = ToAbsolute(path); + path = path.ToLower(); + path = ToAppRelative(path); // strip vdir if any - return uri.Rewrite(path); + if (path != "/") + { + path = path.TrimEnd(Constants.CharArrays.ForwardSlash); } - // maps a media umbraco uri to a public uri - // ie with virtual directory - that is all for media - public Uri MediaUriFromUmbraco(Uri uri) + return uri.Rewrite(path); + } + + #region ResolveUrl + + // http://www.codeproject.com/Articles/53460/ResolveUrl-in-ASP-NET-The-Perfect-Solution + // note + // if browsing http://example.com/sub/page1.aspx then + // ResolveUrl("page2.aspx") returns "/page2.aspx" + // Page.ResolveUrl("page2.aspx") returns "/sub/page2.aspx" (relative...) + // + public string ResolveUrl(string relativeUrl) + { + if (relativeUrl == null) { - var path = uri.GetSafeAbsolutePath(); - path = ToAbsolute(path); - return uri.Rewrite(path); + throw new ArgumentNullException("relativeUrl"); } - // maps a public uri to an internal umbraco uri - // ie no virtual directory, no .aspx, lowercase... - public Uri UriToUmbraco(Uri uri) + if (relativeUrl.Length == 0 || relativeUrl[0] == '/' || relativeUrl[0] == '\\') { - // TODO: This is critical code that executes on every request, we should - // look into if all of this is necessary? not really sure we need ToLower? - - // note: no need to decode uri here because we're returning a uri - // so it will be re-encoded anyway - var path = uri.GetSafeAbsolutePath(); - - path = path.ToLower(); - path = ToAppRelative(path); // strip vdir if any + return relativeUrl; + } - if (path != "/") + var idxOfScheme = relativeUrl.IndexOf(@"://", StringComparison.Ordinal); + if (idxOfScheme != -1) + { + var idxOfQM = relativeUrl.IndexOf('?'); + if (idxOfQM == -1 || idxOfQM > idxOfScheme) { - path = path.TrimEnd(Constants.CharArrays.ForwardSlash); + return relativeUrl; } - - return uri.Rewrite(path); } - #region ResolveUrl - - // http://www.codeproject.com/Articles/53460/ResolveUrl-in-ASP-NET-The-Perfect-Solution - // note - // if browsing http://example.com/sub/page1.aspx then - // ResolveUrl("page2.aspx") returns "/page2.aspx" - // Page.ResolveUrl("page2.aspx") returns "/sub/page2.aspx" (relative...) - // - public string ResolveUrl(string relativeUrl) + var sbUrl = new StringBuilder(); + sbUrl.Append(_appPathPrefix); + if (sbUrl.Length == 0 || sbUrl[sbUrl.Length - 1] != '/') { - if (relativeUrl == null) throw new ArgumentNullException("relativeUrl"); - - if (relativeUrl.Length == 0 || relativeUrl[0] == '/' || relativeUrl[0] == '\\') - return relativeUrl; - - int idxOfScheme = relativeUrl.IndexOf(@"://", StringComparison.Ordinal); - if (idxOfScheme != -1) - { - int idxOfQM = relativeUrl.IndexOf('?'); - if (idxOfQM == -1 || idxOfQM > idxOfScheme) return relativeUrl; - } + sbUrl.Append('/'); + } - StringBuilder sbUrl = new StringBuilder(); - sbUrl.Append(_appPathPrefix); - if (sbUrl.Length == 0 || sbUrl[sbUrl.Length - 1] != '/') sbUrl.Append('/'); + // found question mark already? query string, do not touch! + var foundQM = false; + bool foundSlash; // the latest char was a slash? + if (relativeUrl.Length > 1 + && relativeUrl[0] == '~' + && (relativeUrl[1] == '/' || relativeUrl[1] == '\\')) + { + relativeUrl = relativeUrl.Substring(2); + foundSlash = true; + } + else + { + foundSlash = false; + } - // found question mark already? query string, do not touch! - bool foundQM = false; - bool foundSlash; // the latest char was a slash? - if (relativeUrl.Length > 1 - && relativeUrl[0] == '~' - && (relativeUrl[1] == '/' || relativeUrl[1] == '\\')) - { - relativeUrl = relativeUrl.Substring(2); - foundSlash = true; - } - else foundSlash = false; - foreach (char c in relativeUrl) + foreach (var c in relativeUrl) + { + if (!foundQM) { - if (!foundQM) + if (c == '?') + { + foundQM = true; + } + else { - if (c == '?') foundQM = true; - else + if (c == '/' || c == '\\') { - if (c == '/' || c == '\\') + if (foundSlash) { - if (foundSlash) continue; - else - { - sbUrl.Append('/'); - foundSlash = true; - continue; - } + continue; } - else if (foundSlash) foundSlash = false; + + sbUrl.Append('/'); + foundSlash = true; + continue; + } + + if (foundSlash) + { + foundSlash = false; } } - sbUrl.Append(c); } - return sbUrl.ToString(); + sbUrl.Append(c); } - #endregion - + return sbUrl.ToString(); + } - /// - /// Returns an full URL with the host, port, etc... - /// - /// An absolute path (i.e. starts with a '/' ) - /// - /// - /// - /// Based on http://stackoverflow.com/questions/3681052/get-absolute-url-from-relative-path-refactored-method - /// - internal Uri ToFullUrl(string absolutePath, Uri curentRequestUrl) - { - if (string.IsNullOrEmpty(absolutePath)) - throw new ArgumentNullException(nameof(absolutePath)); + #endregion - if (!absolutePath.StartsWith("/")) - throw new FormatException("The absolutePath specified does not start with a '/'"); - return new Uri(absolutePath, UriKind.Relative).MakeAbsolute(curentRequestUrl); + /// + /// Returns an full URL with the host, port, etc... + /// + /// An absolute path (i.e. starts with a '/' ) + /// + /// + /// + /// Based on http://stackoverflow.com/questions/3681052/get-absolute-url-from-relative-path-refactored-method + /// + internal Uri ToFullUrl(string absolutePath, Uri curentRequestUrl) + { + if (string.IsNullOrEmpty(absolutePath)) + { + throw new ArgumentNullException(nameof(absolutePath)); } + if (!absolutePath.StartsWith("/")) + { + throw new FormatException("The absolutePath specified does not start with a '/'"); + } + return new Uri(absolutePath, UriKind.Relative).MakeAbsolute(curentRequestUrl); } } diff --git a/src/Umbraco.Core/Routing/UrlInfo.cs b/src/Umbraco.Core/Routing/UrlInfo.cs index 3a5c27772522..30ae9cc16ecd 100644 --- a/src/Umbraco.Core/Routing/UrlInfo.cs +++ b/src/Umbraco.Core/Routing/UrlInfo.cs @@ -1,102 +1,117 @@ -using System; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// Represents infos for a URL. +/// +[DataContract(Name = "urlInfo", Namespace = "")] +public class UrlInfo : IEquatable { /// - /// Represents infos for a URL. + /// Initializes a new instance of the class. /// - [DataContract(Name = "urlInfo", Namespace = "")] - public class UrlInfo : IEquatable + public UrlInfo(string text, bool isUrl, string? culture) { + if (string.IsNullOrWhiteSpace(text)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(text)); + } - /// - /// Creates a instance representing a true URL. - /// - public static UrlInfo Url(string text, string? culture = null) => new UrlInfo(text, true, culture); + IsUrl = isUrl; + Text = text; + Culture = culture; + } - /// - /// Creates a instance representing a message. - /// - public static UrlInfo Message(string text, string? culture = null) => new UrlInfo(text, false, culture); + /// + /// Gets the culture. + /// + [DataMember(Name = "culture")] + public string? Culture { get; } - /// - /// Initializes a new instance of the class. - /// - public UrlInfo(string text, bool isUrl, string? culture) - { - if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(text)); - IsUrl = isUrl; - Text = text; - Culture = culture; - } + /// + /// Gets a value indicating whether the URL is a true URL. + /// + /// Otherwise, it is a message. + [DataMember(Name = "isUrl")] + public bool IsUrl { get; } + + /// + /// Gets the text, which is either the URL, or a message. + /// + [DataMember(Name = "text")] + public string Text { get; } - /// - /// Gets the culture. - /// - [DataMember(Name = "culture")] - public string? Culture { get; } - - /// - /// Gets a value indicating whether the URL is a true URL. - /// - /// Otherwise, it is a message. - [DataMember(Name = "isUrl")] - public bool IsUrl { get; } - - /// - /// Gets the text, which is either the URL, or a message. - /// - [DataMember(Name = "text")] - public string Text { get; } - - /// - /// Checks equality - /// - /// - /// - /// - /// Compare both culture and Text as invariant strings since URLs are not case sensitive, nor are culture names within Umbraco - /// - public bool Equals(UrlInfo? other) + /// + /// Checks equality + /// + /// + /// + /// + /// Compare both culture and Text as invariant strings since URLs are not case sensitive, nor are culture names within + /// Umbraco + /// + public bool Equals(UrlInfo? other) + { + if (ReferenceEquals(null, other)) { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return string.Equals(Culture, other.Culture, StringComparison.InvariantCultureIgnoreCase) && IsUrl == other.IsUrl && string.Equals(Text, other.Text, StringComparison.InvariantCultureIgnoreCase); + return false; } - public override bool Equals(object? obj) + if (ReferenceEquals(this, other)) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((UrlInfo) obj); + return true; } - public override int GetHashCode() + return string.Equals(Culture, other.Culture, StringComparison.InvariantCultureIgnoreCase) && + IsUrl == other.IsUrl && string.Equals(Text, other.Text, StringComparison.InvariantCultureIgnoreCase); + } + + /// + /// Creates a instance representing a true URL. + /// + public static UrlInfo Url(string text, string? culture = null) => new(text, true, culture); + + /// + /// Creates a instance representing a message. + /// + public static UrlInfo Message(string text, string? culture = null) => new(text, false, culture); + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) { - unchecked - { - var hashCode = (Culture != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(Culture) : 0); - hashCode = (hashCode * 397) ^ IsUrl.GetHashCode(); - hashCode = (hashCode * 397) ^ (Text != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(Text) : 0); - return hashCode; - } + return false; } - public static bool operator ==(UrlInfo left, UrlInfo right) + if (ReferenceEquals(this, obj)) { - return Equals(left, right); + return true; } - public static bool operator !=(UrlInfo left, UrlInfo right) + if (obj.GetType() != GetType()) { - return !Equals(left, right); + return false; } - public override string ToString() + return Equals((UrlInfo)obj); + } + + public override int GetHashCode() + { + unchecked { - return Text; + var hashCode = Culture != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(Culture) : 0; + hashCode = (hashCode * 397) ^ IsUrl.GetHashCode(); + hashCode = (hashCode * 397) ^ + (Text != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(Text) : 0); + return hashCode; } } + + public static bool operator ==(UrlInfo left, UrlInfo right) => Equals(left, right); + + public static bool operator !=(UrlInfo left, UrlInfo right) => !Equals(left, right); + + public override string ToString() => Text; } diff --git a/src/Umbraco.Core/Routing/UrlProvider.cs b/src/Umbraco.Core/Routing/UrlProvider.cs index 11597159f75d..cf78e63f2836 100644 --- a/src/Umbraco.Core/Routing/UrlProvider.cs +++ b/src/Umbraco.Core/Routing/UrlProvider.cs @@ -1,245 +1,271 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Web; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +/// +/// Provides URLs. +/// +public class UrlProvider : IPublishedUrlProvider { + #region Ctor and configuration + /// - /// Provides URLs. + /// Initializes a new instance of the class with an Umbraco context and a list of URL + /// providers. /// - public class UrlProvider : IPublishedUrlProvider + /// The Umbraco context accessor. + /// Routing settings. + /// The list of URL providers. + /// The list of media URL providers. + /// The current variation accessor. + /// + public UrlProvider(IUmbracoContextAccessor umbracoContextAccessor, IOptions routingSettings, + UrlProviderCollection urlProviders, MediaUrlProviderCollection mediaUrlProviders, + IVariationContextAccessor variationContextAccessor) { - #region Ctor and configuration - - /// - /// Initializes a new instance of the class with an Umbraco context and a list of URL providers. - /// - /// The Umbraco context accessor. - /// Routing settings. - /// The list of URL providers. - /// The list of media URL providers. - /// The current variation accessor. - /// - public UrlProvider(IUmbracoContextAccessor umbracoContextAccessor, IOptions routingSettings, UrlProviderCollection urlProviders, MediaUrlProviderCollection mediaUrlProviders, IVariationContextAccessor variationContextAccessor) - { - _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); - _urlProviders = urlProviders; - _mediaUrlProviders = mediaUrlProviders; - _variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); - Mode = routingSettings.Value.UrlProviderMode; + _umbracoContextAccessor = + umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + _urlProviders = urlProviders; + _mediaUrlProviders = mediaUrlProviders; + _variationContextAccessor = variationContextAccessor ?? + throw new ArgumentNullException(nameof(variationContextAccessor)); + Mode = routingSettings.Value.UrlProviderMode; + } - } + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IEnumerable _urlProviders; + private readonly IEnumerable _mediaUrlProviders; + private readonly IVariationContextAccessor _variationContextAccessor; + + /// + /// Gets or sets the provider URL mode. + /// + public UrlMode Mode { get; set; } - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly IEnumerable _urlProviders; - private readonly IEnumerable _mediaUrlProviders; - private readonly IVariationContextAccessor _variationContextAccessor; + #endregion - /// - /// Gets or sets the provider URL mode. - /// - public UrlMode Mode { get; set; } + #region GetUrl - #endregion + private IPublishedContent? GetDocument(int id) + { + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + return umbracoContext.Content?.GetById(id); + } + + private IPublishedContent? GetDocument(Guid id) + { + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + return umbracoContext.Content?.GetById(id); + } - #region GetUrl + private IPublishedContent? GetMedia(Guid id) + { + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + return umbracoContext.Media?.GetById(id); + } - private IPublishedContent? GetDocument(int id) + /// + /// Gets the URL of a published content. + /// + /// The published content identifier. + /// The URL mode. + /// A culture. + /// The current absolute URL. + /// The URL for the published content. + public string GetUrl(Guid id, UrlMode mode = UrlMode.Default, string? culture = null, Uri? current = null) + => GetUrl(GetDocument(id), mode, culture, current); + + /// + /// Gets the URL of a published content. + /// + /// The published content identifier. + /// The URL mode. + /// A culture. + /// The current absolute URL. + /// The URL for the published content. + public string GetUrl(int id, UrlMode mode = UrlMode.Default, string? culture = null, Uri? current = null) + => GetUrl(GetDocument(id), mode, culture, current); + + /// + /// Gets the URL of a published content. + /// + /// The published content. + /// The URL mode. + /// A culture. + /// The current absolute URL. + /// The URL for the published content. + /// + /// The URL is absolute or relative depending on mode and on current. + /// + /// If the published content is multi-lingual, gets the URL for the specified culture or, + /// when no culture is specified, the current culture. + /// + /// If the provider is unable to provide a URL, it returns "#". + /// + public string GetUrl(IPublishedContent? content, UrlMode mode = UrlMode.Default, string? culture = null, + Uri? current = null) + { + if (content == null || content.ContentType.ItemType == PublishedItemType.Element) { - var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - return umbracoContext.Content?.GetById(id); + return "#"; } - private IPublishedContent? GetDocument(Guid id) + + if (mode == UrlMode.Default) { - var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - return umbracoContext.Content?.GetById(id); + mode = Mode; } - private IPublishedContent? GetMedia(Guid id) + + // this the ONLY place where we deal with default culture - IUrlProvider always receive a culture + // be nice with tests, assume things can be null, ultimately fall back to invariant + // (but only for variant content of course) + if (content.ContentType.VariesByCulture()) { - var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - return umbracoContext.Media?.GetById(id); + if (culture == null) + { + culture = _variationContextAccessor?.VariationContext?.Culture ?? ""; + } } - /// - /// Gets the URL of a published content. - /// - /// The published content identifier. - /// The URL mode. - /// A culture. - /// The current absolute URL. - /// The URL for the published content. - public string GetUrl(Guid id, UrlMode mode = UrlMode.Default, string? culture = null, Uri? current = null) - => GetUrl(GetDocument(id), mode, culture, current); - - /// - /// Gets the URL of a published content. - /// - /// The published content identifier. - /// The URL mode. - /// A culture. - /// The current absolute URL. - /// The URL for the published content. - public string GetUrl(int id, UrlMode mode = UrlMode.Default, string? culture = null, Uri? current = null) - => GetUrl(GetDocument(id), mode, culture, current); - - /// - /// Gets the URL of a published content. - /// - /// The published content. - /// The URL mode. - /// A culture. - /// The current absolute URL. - /// The URL for the published content. - /// - /// The URL is absolute or relative depending on mode and on current. - /// If the published content is multi-lingual, gets the URL for the specified culture or, - /// when no culture is specified, the current culture. - /// If the provider is unable to provide a URL, it returns "#". - /// - public string GetUrl(IPublishedContent? content, UrlMode mode = UrlMode.Default, string? culture = null, Uri? current = null) + if (current == null) { - if (content == null || content.ContentType.ItemType == PublishedItemType.Element) - return "#"; + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + current = umbracoContext.CleanedUmbracoUrl; + } - if (mode == UrlMode.Default) - mode = Mode; - // this the ONLY place where we deal with default culture - IUrlProvider always receive a culture - // be nice with tests, assume things can be null, ultimately fall back to invariant - // (but only for variant content of course) - if (content.ContentType.VariesByCulture()) - { - if (culture == null) - culture = _variationContextAccessor?.VariationContext?.Culture ?? ""; - } + UrlInfo url = _urlProviders.Select(provider => provider.GetUrl(content, mode, culture, current)) + .FirstOrDefault(u => u is not null); + return url?.Text ?? "#"; // legacy wants this + } - if (current == null) - { - var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - current = umbracoContext.CleanedUmbracoUrl; - } + public string GetUrlFromRoute(int id, string? route, string? culture) + { + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + DefaultUrlProvider provider = _urlProviders.OfType().FirstOrDefault(); + var url = provider == null + ? route // what else? + : provider.GetUrlFromRoute(route, umbracoContext, id, umbracoContext.CleanedUmbracoUrl, Mode, culture) + ?.Text; + return url ?? "#"; + } + #endregion - var url = _urlProviders.Select(provider => provider.GetUrl(content, mode, culture, current)) - .FirstOrDefault(u => u is not null); - return url?.Text ?? "#"; // legacy wants this - } + #region GetOtherUrls + + /// + /// Gets the other URLs of a published content. + /// + /// The published content id. + /// The other URLs for the published content. + /// + /// + /// Other URLs are those that GetUrl would not return in the current context, but would be valid + /// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...). + /// + /// The results depend on the current URL. + /// + public IEnumerable GetOtherUrls(int id) + { + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + return GetOtherUrls(id, umbracoContext.CleanedUmbracoUrl); + } + + /// + /// Gets the other URLs of a published content. + /// + /// The published content id. + /// The current absolute URL. + /// The other URLs for the published content. + /// + /// + /// Other URLs are those that GetUrl would not return in the current context, but would be valid + /// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...). + /// + /// + public IEnumerable GetOtherUrls(int id, Uri current) => _urlProviders.SelectMany(provider => + provider.GetOtherUrls(id, current) ?? Enumerable.Empty()); + + #endregion + + #region GetMediaUrl - public string GetUrlFromRoute(int id, string? route, string? culture) + /// + /// Gets the URL of a media item. + /// + /// + /// + /// + /// + /// + /// + public string GetMediaUrl(Guid id, UrlMode mode = UrlMode.Default, string? culture = null, + string propertyAlias = Constants.Conventions.Media.File, Uri? current = null) + => GetMediaUrl(GetMedia(id), mode, culture, propertyAlias, current); + + /// + /// Gets the URL of a media item. + /// + /// The published content. + /// The property alias to resolve the URL from. + /// The URL mode. + /// The variation language. + /// The current absolute URL. + /// The URL for the media. + /// + /// The URL is absolute or relative depending on mode and on current. + /// + /// If the media is multi-lingual, gets the URL for the specified culture or, + /// when no culture is specified, the current culture. + /// + /// If the provider is unable to provide a URL, it returns . + /// + public string GetMediaUrl(IPublishedContent? content, UrlMode mode = UrlMode.Default, string? culture = null, + string propertyAlias = Constants.Conventions.Media.File, Uri? current = null) + { + if (propertyAlias == null) { - var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - var provider = _urlProviders.OfType().FirstOrDefault(); - var url = provider == null - ? route // what else? - : provider.GetUrlFromRoute(route, umbracoContext, id, umbracoContext.CleanedUmbracoUrl, Mode, culture)?.Text; - return url ?? "#"; + throw new ArgumentNullException(nameof(propertyAlias)); } - #endregion - - #region GetOtherUrls - - /// - /// Gets the other URLs of a published content. - /// - /// The published content id. - /// The other URLs for the published content. - /// - /// Other URLs are those that GetUrl would not return in the current context, but would be valid - /// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...). - /// The results depend on the current URL. - /// - public IEnumerable GetOtherUrls(int id) + if (content == null) { - var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - return GetOtherUrls(id, umbracoContext.CleanedUmbracoUrl); + return ""; } - /// - /// Gets the other URLs of a published content. - /// - /// The published content id. - /// The current absolute URL. - /// The other URLs for the published content. - /// - /// Other URLs are those that GetUrl would not return in the current context, but would be valid - /// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...). - /// - public IEnumerable GetOtherUrls(int id, Uri current) + if (mode == UrlMode.Default) { - return _urlProviders.SelectMany(provider => provider.GetOtherUrls(id, current) ?? Enumerable.Empty()); + mode = Mode; } - #endregion - - #region GetMediaUrl - - /// - /// Gets the URL of a media item. - /// - /// - /// - /// - /// - /// - /// - public string GetMediaUrl(Guid id, UrlMode mode = UrlMode.Default, string? culture = null, string propertyAlias = Constants.Conventions.Media.File, Uri? current = null) - => GetMediaUrl( GetMedia(id), mode, culture, propertyAlias, current); - - /// - /// Gets the URL of a media item. - /// - /// The published content. - /// The property alias to resolve the URL from. - /// The URL mode. - /// The variation language. - /// The current absolute URL. - /// The URL for the media. - /// - /// The URL is absolute or relative depending on mode and on current. - /// If the media is multi-lingual, gets the URL for the specified culture or, - /// when no culture is specified, the current culture. - /// If the provider is unable to provide a URL, it returns . - /// - public string GetMediaUrl(IPublishedContent? content, UrlMode mode = UrlMode.Default, string? culture = null, string propertyAlias = Constants.Conventions.Media.File, Uri? current = null) + // this the ONLY place where we deal with default culture - IMediaUrlProvider always receive a culture + // be nice with tests, assume things can be null, ultimately fall back to invariant + // (but only for variant content of course) + if (content.ContentType.VariesByCulture()) { - if (propertyAlias == null) - throw new ArgumentNullException(nameof(propertyAlias)); - - if (content == null) - return ""; - - if (mode == UrlMode.Default) - mode = Mode; - - // this the ONLY place where we deal with default culture - IMediaUrlProvider always receive a culture - // be nice with tests, assume things can be null, ultimately fall back to invariant - // (but only for variant content of course) - if (content.ContentType.VariesByCulture()) + if (culture == null) { - if (culture == null) - culture = _variationContextAccessor?.VariationContext?.Culture ?? ""; - } - - if (current == null) - { - var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - current = umbracoContext.CleanedUmbracoUrl; + culture = _variationContextAccessor?.VariationContext?.Culture ?? ""; } + } + if (current == null) + { + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + current = umbracoContext.CleanedUmbracoUrl; + } - var url = _mediaUrlProviders.Select(provider => - provider.GetMediaUrl(content, propertyAlias, mode, culture, current)) - .FirstOrDefault(u => u is not null); - return url?.Text ?? ""; - } + UrlInfo url = _mediaUrlProviders.Select(provider => + provider.GetMediaUrl(content, propertyAlias, mode, culture, current)) + .FirstOrDefault(u => u is not null); - #endregion + return url?.Text ?? ""; } + + #endregion } diff --git a/src/Umbraco.Core/Routing/UrlProviderCollection.cs b/src/Umbraco.Core/Routing/UrlProviderCollection.cs index c17417c83cad..59744c0737f2 100644 --- a/src/Umbraco.Core/Routing/UrlProviderCollection.cs +++ b/src/Umbraco.Core/Routing/UrlProviderCollection.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +public class UrlProviderCollection : BuilderCollectionBase { - public class UrlProviderCollection : BuilderCollectionBase + public UrlProviderCollection(Func> items) : base(items) { - public UrlProviderCollection(Func> items) : base(items) - { - } } } diff --git a/src/Umbraco.Core/Routing/UrlProviderCollectionBuilder.cs b/src/Umbraco.Core/Routing/UrlProviderCollectionBuilder.cs index ca6f703c8b9f..8ee1caf343c9 100644 --- a/src/Umbraco.Core/Routing/UrlProviderCollectionBuilder.cs +++ b/src/Umbraco.Core/Routing/UrlProviderCollectionBuilder.cs @@ -1,9 +1,9 @@ using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +public class UrlProviderCollectionBuilder : OrderedCollectionBuilderBase { - public class UrlProviderCollectionBuilder : OrderedCollectionBuilderBase - { - protected override UrlProviderCollectionBuilder This => this; - } + protected override UrlProviderCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs index 5f28bee2b704..9c4e81c536dc 100644 --- a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs @@ -6,250 +6,258 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Web; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class UrlProviderExtensions { - public static class UrlProviderExtensions + /// + /// Gets the URLs of the content item. + /// + /// + /// Use when displaying URLs. If errors occur when generating the URLs, they will show in the list. + /// Contains all the URLs that we can figure out (based upon domains, etc). + /// + public static async Task> GetContentUrlsAsync( + this IContent content, + IPublishedRouter publishedRouter, + IUmbracoContext umbracoContext, + ILocalizationService localizationService, + ILocalizedTextService textService, + IContentService contentService, + IVariationContextAccessor variationContextAccessor, + ILogger logger, + UriUtility uriUtility, + IPublishedUrlProvider publishedUrlProvider) { - /// - /// Gets the URLs of the content item. - /// - /// - /// Use when displaying URLs. If errors occur when generating the URLs, they will show in the list. - /// Contains all the URLs that we can figure out (based upon domains, etc). - /// - public static async Task> GetContentUrlsAsync( - this IContent content, - IPublishedRouter publishedRouter, - IUmbracoContext umbracoContext, - ILocalizationService localizationService, - ILocalizedTextService textService, - IContentService contentService, - IVariationContextAccessor variationContextAccessor, - ILogger logger, - UriUtility uriUtility, - IPublishedUrlProvider publishedUrlProvider) + ArgumentNullException.ThrowIfNull(content); + ArgumentNullException.ThrowIfNull(publishedRouter); + ArgumentNullException.ThrowIfNull(umbracoContext); + ArgumentNullException.ThrowIfNull(localizationService); + ArgumentNullException.ThrowIfNull(textService); + ArgumentNullException.ThrowIfNull(contentService); + ArgumentNullException.ThrowIfNull(variationContextAccessor); + ArgumentNullException.ThrowIfNull(logger); + ArgumentNullException.ThrowIfNull(uriUtility); + ArgumentNullException.ThrowIfNull(publishedUrlProvider); + + var result = new List(); + + if (content.Published == false) { - ArgumentNullException.ThrowIfNull(content); - ArgumentNullException.ThrowIfNull(publishedRouter); - ArgumentNullException.ThrowIfNull(umbracoContext); - ArgumentNullException.ThrowIfNull(localizationService); - ArgumentNullException.ThrowIfNull(textService); - ArgumentNullException.ThrowIfNull(contentService); - ArgumentNullException.ThrowIfNull(variationContextAccessor); - ArgumentNullException.ThrowIfNull(logger); - ArgumentNullException.ThrowIfNull(uriUtility); - ArgumentNullException.ThrowIfNull(publishedUrlProvider); - - var result = new List(); - - if (content.Published == false) - { - result.Add(UrlInfo.Message(textService.Localize("content", "itemNotPublished"))); - return result; - } + result.Add(UrlInfo.Message(textService.Localize("content", "itemNotPublished"))); + return result; + } - // build a list of URLs, for the back-office - // which will contain - // - the 'main' URLs, which is what .Url would return, for each culture - // - the 'other' URLs we know (based upon domains, etc) - // - // need to work through each installed culture: - // on invariant nodes, each culture returns the same URL segment but, - // we don't know if the branch to this content is invariant, so we need to ask - // for URLs for all cultures. - // and, not only for those assigned to domains in the branch, because we want - // to show what GetUrl() would return, for every culture. - var urls = new HashSet(); - var cultures = localizationService.GetAllLanguages().Select(x => x.IsoCode).ToList(); - - // get all URLs for all cultures - // in a HashSet, so de-duplicates too - foreach (UrlInfo cultureUrl in await GetContentUrlsByCultureAsync(content, cultures, publishedRouter, umbracoContext, contentService, textService, variationContextAccessor, logger, uriUtility, publishedUrlProvider)) - { - urls.Add(cultureUrl); - } + // build a list of URLs, for the back-office + // which will contain + // - the 'main' URLs, which is what .Url would return, for each culture + // - the 'other' URLs we know (based upon domains, etc) + // + // need to work through each installed culture: + // on invariant nodes, each culture returns the same URL segment but, + // we don't know if the branch to this content is invariant, so we need to ask + // for URLs for all cultures. + // and, not only for those assigned to domains in the branch, because we want + // to show what GetUrl() would return, for every culture. + var urls = new HashSet(); + var cultures = localizationService.GetAllLanguages().Select(x => x.IsoCode).ToList(); + + // get all URLs for all cultures + // in a HashSet, so de-duplicates too + foreach (UrlInfo cultureUrl in await GetContentUrlsByCultureAsync(content, cultures, publishedRouter, + umbracoContext, contentService, textService, variationContextAccessor, logger, uriUtility, + publishedUrlProvider)) + { + urls.Add(cultureUrl); + } - // return the real URLs first, then the messages - foreach (IGrouping urlGroup in urls.GroupBy(x => x.IsUrl).OrderByDescending(x => x.Key)) + // return the real URLs first, then the messages + foreach (IGrouping urlGroup in urls.GroupBy(x => x.IsUrl).OrderByDescending(x => x.Key)) + { + // in some cases there will be the same URL for multiple cultures: + // * The entire branch is invariant + // * If there are less domain/cultures assigned to the branch than the number of cultures/languages installed + if (urlGroup.Key) { - // in some cases there will be the same URL for multiple cultures: - // * The entire branch is invariant - // * If there are less domain/cultures assigned to the branch than the number of cultures/languages installed - if (urlGroup.Key) - { - result.AddRange(urlGroup.DistinctBy(x => x.Text, StringComparer.OrdinalIgnoreCase).OrderBy(x => x.Text).ThenBy(x => x.Culture)); - } - else - { - result.AddRange(urlGroup); - } + result.AddRange(urlGroup.DistinctBy(x => x.Text, StringComparer.OrdinalIgnoreCase).OrderBy(x => x.Text) + .ThenBy(x => x.Culture)); } - - // get the 'other' URLs - ie not what you'd get with GetUrl() but URLs that would route to the document, nevertheless. - // for these 'other' URLs, we don't check whether they are routable, collide, anything - we just report them. - foreach (UrlInfo otherUrl in publishedUrlProvider.GetOtherUrls(content.Id).OrderBy(x => x.Text).ThenBy(x => x.Culture)) + else { - // avoid duplicates - if (urls.Add(otherUrl)) - { - result.Add(otherUrl); - } + result.AddRange(urlGroup); } - - return result; } - /// - /// Tries to return a for each culture for the content while detecting collisions/errors - /// - private static async Task> GetContentUrlsByCultureAsync( - IContent content, - IEnumerable cultures, - IPublishedRouter publishedRouter, - IUmbracoContext umbracoContext, - IContentService contentService, - ILocalizedTextService textService, - IVariationContextAccessor variationContextAccessor, - ILogger logger, - UriUtility uriUtility, - IPublishedUrlProvider publishedUrlProvider) + // get the 'other' URLs - ie not what you'd get with GetUrl() but URLs that would route to the document, nevertheless. + // for these 'other' URLs, we don't check whether they are routable, collide, anything - we just report them. + foreach (UrlInfo otherUrl in publishedUrlProvider.GetOtherUrls(content.Id).OrderBy(x => x.Text) + .ThenBy(x => x.Culture)) { - var result = new List(); - - foreach (var culture in cultures) + // avoid duplicates + if (urls.Add(otherUrl)) { - // if content is variant, and culture is not published, skip - if (content.ContentType.VariesByCulture() && !content.IsCulturePublished(culture)) - { - continue; - } - - // if it's variant and culture is published, or if it's invariant, proceed - string url; - try - { - url = publishedUrlProvider.GetUrl(content.Id, culture: culture); - } - catch (Exception ex) - { - logger.LogError(ex, "GetUrl exception."); - url = "#ex"; - } - - switch (url) - { - // deal with 'could not get the URL' - case "#": - result.Add(HandleCouldNotGetUrl(content, culture, contentService, textService)); - break; - - // deal with exceptions - case "#ex": - result.Add(UrlInfo.Message(textService.Localize("content", "getUrlException"), culture)); - break; - - // got a URL, deal with collisions, add URL - default: - // detect collisions, etc - Attempt hasCollision = await DetectCollisionAsync(logger, content, url, culture, umbracoContext, publishedRouter, textService, variationContextAccessor, uriUtility); - if (hasCollision.Success && hasCollision.Result is not null) - { - result.Add(hasCollision.Result); - } - else - { - result.Add(UrlInfo.Url(url, culture)); - } - - break; - } + result.Add(otherUrl); } - - return result; } - private static UrlInfo HandleCouldNotGetUrl(IContent content, string culture, IContentService contentService, ILocalizedTextService textService) + return result; + } + + /// + /// Tries to return a for each culture for the content while detecting collisions/errors + /// + private static async Task> GetContentUrlsByCultureAsync( + IContent content, + IEnumerable cultures, + IPublishedRouter publishedRouter, + IUmbracoContext umbracoContext, + IContentService contentService, + ILocalizedTextService textService, + IVariationContextAccessor variationContextAccessor, + ILogger logger, + UriUtility uriUtility, + IPublishedUrlProvider publishedUrlProvider) + { + var result = new List(); + + foreach (var culture in cultures) { - // document has a published version yet its URL is "#" => a parent must be - // unpublished, walk up the tree until we find it, and report. - IContent? parent = content; - do + // if content is variant, and culture is not published, skip + if (content.ContentType.VariesByCulture() && !content.IsCulturePublished(culture)) { - parent = parent.ParentId > 0 ? contentService.GetParent(parent) : null; + continue; } - while (parent != null && parent.Published && (!parent.ContentType.VariesByCulture() || parent.IsCulturePublished(culture))); - if (parent == null) + // if it's variant and culture is published, or if it's invariant, proceed + string url; + try + { + url = publishedUrlProvider.GetUrl(content.Id, culture: culture); + } + catch (Exception ex) { - // oops, internal error - return UrlInfo.Message(textService.Localize("content", "parentNotPublishedAnomaly"), culture); + logger.LogError(ex, "GetUrl exception."); + url = "#ex"; } - if (!parent.Published) + switch (url) { - // totally not published - return UrlInfo.Message(textService.Localize("content", "parentNotPublished", new[] { parent.Name }), culture); + // deal with 'could not get the URL' + case "#": + result.Add(HandleCouldNotGetUrl(content, culture, contentService, textService)); + break; + + // deal with exceptions + case "#ex": + result.Add(UrlInfo.Message(textService.Localize("content", "getUrlException"), culture)); + break; + + // got a URL, deal with collisions, add URL + default: + // detect collisions, etc + Attempt hasCollision = await DetectCollisionAsync(logger, content, url, culture, + umbracoContext, publishedRouter, textService, variationContextAccessor, uriUtility); + if (hasCollision.Success && hasCollision.Result is not null) + { + result.Add(hasCollision.Result); + } + else + { + result.Add(UrlInfo.Url(url, culture)); + } + + break; } + } - // culture not published - return UrlInfo.Message(textService.Localize("content", "parentCultureNotPublished", new[] { parent.Name }), culture); + return result; + } + + private static UrlInfo HandleCouldNotGetUrl(IContent content, string culture, IContentService contentService, + ILocalizedTextService textService) + { + // document has a published version yet its URL is "#" => a parent must be + // unpublished, walk up the tree until we find it, and report. + IContent? parent = content; + do + { + parent = parent.ParentId > 0 ? contentService.GetParent(parent) : null; + } while (parent != null && parent.Published && + (!parent.ContentType.VariesByCulture() || parent.IsCulturePublished(culture))); + + if (parent == null) + { + // oops, internal error + return UrlInfo.Message(textService.Localize("content", "parentNotPublishedAnomaly"), culture); } - private static async Task> DetectCollisionAsync( - ILogger logger, - IContent content, - string url, - string culture, - IUmbracoContext umbracoContext, - IPublishedRouter publishedRouter, - ILocalizedTextService textService, - IVariationContextAccessor variationContextAccessor, - UriUtility uriUtility) + if (!parent.Published) { - // test for collisions on the 'main' URL - var uri = new Uri(url.TrimEnd(Constants.CharArrays.ForwardSlash), UriKind.RelativeOrAbsolute); - if (uri.IsAbsoluteUri == false) - { - uri = uri.MakeAbsolute(umbracoContext.CleanedUmbracoUrl); - } + // totally not published + return UrlInfo.Message(textService.Localize("content", "parentNotPublished", new[] {parent.Name}), culture); + } - uri = uriUtility.UriToUmbraco(uri); - IPublishedRequestBuilder builder = await publishedRouter.CreateRequestAsync(uri); - IPublishedRequest pcr = await publishedRouter.RouteRequestAsync(builder, new RouteRequestOptions(RouteDirection.Outbound)); + // culture not published + return UrlInfo.Message(textService.Localize("content", "parentCultureNotPublished", new[] {parent.Name}), + culture); + } - if (!pcr.HasPublishedContent()) - { - const string logMsg = nameof(DetectCollisionAsync) + " did not resolve a content item for original url: {Url}, translated to {TranslatedUrl} and culture: {Culture}"; - logger.LogDebug(logMsg, url, uri, culture); + private static async Task> DetectCollisionAsync( + ILogger logger, + IContent content, + string url, + string culture, + IUmbracoContext umbracoContext, + IPublishedRouter publishedRouter, + ILocalizedTextService textService, + IVariationContextAccessor variationContextAccessor, + UriUtility uriUtility) + { + // test for collisions on the 'main' URL + var uri = new Uri(url.TrimEnd(Constants.CharArrays.ForwardSlash), UriKind.RelativeOrAbsolute); + if (uri.IsAbsoluteUri == false) + { + uri = uri.MakeAbsolute(umbracoContext.CleanedUmbracoUrl); + } - var urlInfo = UrlInfo.Message(textService.Localize("content", "routeErrorCannotRoute"), culture); - return Attempt.Succeed(urlInfo); - } + uri = uriUtility.UriToUmbraco(uri); + IPublishedRequestBuilder builder = await publishedRouter.CreateRequestAsync(uri); + IPublishedRequest pcr = + await publishedRouter.RouteRequestAsync(builder, new RouteRequestOptions(RouteDirection.Outbound)); - if (pcr.IgnorePublishedContentCollisions) - { - return Attempt.Fail(); - } + if (!pcr.HasPublishedContent()) + { + const string logMsg = nameof(DetectCollisionAsync) + + " did not resolve a content item for original url: {Url}, translated to {TranslatedUrl} and culture: {Culture}"; + logger.LogDebug(logMsg, url, uri, culture); + + var urlInfo = UrlInfo.Message(textService.Localize("content", "routeErrorCannotRoute"), culture); + return Attempt.Succeed(urlInfo); + } + + if (pcr.IgnorePublishedContentCollisions) + { + return Attempt.Fail(); + } - if (pcr.PublishedContent?.Id != content.Id) + if (pcr.PublishedContent?.Id != content.Id) + { + IPublishedContent? o = pcr.PublishedContent; + var l = new List(); + while (o != null) { - IPublishedContent? o = pcr.PublishedContent; - var l = new List(); - while (o != null) - { - l.Add(o.Name(variationContextAccessor)!); - o = o.Parent; - } - - l.Reverse(); - var s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent?.Id + ")"; - - var urlInfo = UrlInfo.Message(textService.Localize("content", "routeError", new[] { s }), culture); - return Attempt.Succeed(urlInfo); + l.Add(o.Name(variationContextAccessor)!); + o = o.Parent; } - // no collision - return Attempt.Fail(); + l.Reverse(); + var s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent?.Id + ")"; + + var urlInfo = UrlInfo.Message(textService.Localize("content", "routeError", new[] {s}), culture); + return Attempt.Succeed(urlInfo); } + + // no collision + return Attempt.Fail(); } } diff --git a/src/Umbraco.Core/Routing/WebPath.cs b/src/Umbraco.Core/Routing/WebPath.cs index a4da94ac7988..e95fd8e54de0 100644 --- a/src/Umbraco.Core/Routing/WebPath.cs +++ b/src/Umbraco.Core/Routing/WebPath.cs @@ -1,55 +1,53 @@ -using System; using System.Text; -namespace Umbraco.Cms.Core.Routing +namespace Umbraco.Cms.Core.Routing; + +public class WebPath { - public class WebPath + public const char PathSeparator = '/'; + + public static string Combine(params string[]? paths) { - public const char PathSeparator = '/'; + if (paths == null) + { + throw new ArgumentNullException(nameof(paths)); + } + + if (paths.Length == 0) + { + return string.Empty; + } + + var sb = new StringBuilder(); - public static string Combine(params string[]? paths) + for (var index = 0; index < paths.Length; index++) { - if (paths == null) + var path = paths[index]; + var start = 0; + var count = path.Length; + var isFirst = index == 0; + var isLast = index == paths.Length - 1; + + // don't trim start if it's the first + if (!isFirst && path[0] == PathSeparator) { - throw new ArgumentNullException(nameof(paths)); + start = 1; } - if (paths.Length == 0) + // always trim end + if (path[path.Length - 1] == PathSeparator) { - return string.Empty; + count = path.Length - 1; } - var sb = new StringBuilder(); + sb.Append(path, start, count - start); - for (var index = 0; index < paths.Length; index++) + if (!isLast) { - var path = paths[index]; - var start = 0; - var count = path.Length; - var isFirst = index == 0; - var isLast = index == paths.Length - 1; - - // don't trim start if it's the first - if (!isFirst && path[0] == PathSeparator) - { - start = 1; - } - - // always trim end - if (path[path.Length - 1] == PathSeparator) - { - count = path.Length - 1; - } - - sb.Append(path, start, count - start); - - if (!isLast) - { - sb.Append(PathSeparator); - } + sb.Append(PathSeparator); } - - return sb.ToString(); } + + return sb.ToString(); } } diff --git a/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs b/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs index 6c45e4d96915..c35c1a828aa2 100644 --- a/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs +++ b/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs @@ -5,31 +5,30 @@ using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Notifications; -namespace Umbraco.Cms.Core.Runtime -{ - public class EssentialDirectoryCreator : INotificationHandler - { - private readonly IIOHelper _ioHelper; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly GlobalSettings _globalSettings; +namespace Umbraco.Cms.Core.Runtime; - public EssentialDirectoryCreator(IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, IOptions globalSettings) - { - _ioHelper = ioHelper; - _hostingEnvironment = hostingEnvironment; - _globalSettings = globalSettings.Value; - } +public class EssentialDirectoryCreator : INotificationHandler +{ + private readonly GlobalSettings _globalSettings; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IIOHelper _ioHelper; - public void Handle(UmbracoApplicationStartingNotification notification) - { - // ensure we have some essential directories - // every other component can then initialize safely - _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data)); - _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoMediaPhysicalRootPath)); - _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MvcViews)); - _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.PartialViews)); - _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MacroPartials)); + public EssentialDirectoryCreator(IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, + IOptions globalSettings) + { + _ioHelper = ioHelper; + _hostingEnvironment = hostingEnvironment; + _globalSettings = globalSettings.Value; + } - } + public void Handle(UmbracoApplicationStartingNotification notification) + { + // ensure we have some essential directories + // every other component can then initialize safely + _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data)); + _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoMediaPhysicalRootPath)); + _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MvcViews)); + _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.PartialViews)); + _ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MacroPartials)); } } diff --git a/src/Umbraco.Core/Runtime/IMainDom.cs b/src/Umbraco.Core/Runtime/IMainDom.cs index 65c64857b32d..59278e161c6c 100644 --- a/src/Umbraco.Core/Runtime/IMainDom.cs +++ b/src/Umbraco.Core/Runtime/IMainDom.cs @@ -1,39 +1,39 @@ -using System; using Umbraco.Cms.Core.Hosting; -namespace Umbraco.Cms.Core.Runtime +namespace Umbraco.Cms.Core.Runtime; + +/// +/// Represents the main AppDomain running for a given application. +/// +/// +/// There can be only one "main" AppDomain running for a given application at a time. +/// It is possible to register against the MainDom and be notified when it is released. +/// +public interface IMainDom { /// - /// Represents the main AppDomain running for a given application. + /// Gets a value indicating whether the current domain is the main domain. /// /// - /// There can be only one "main" AppDomain running for a given application at a time. - /// It is possible to register against the MainDom and be notified when it is released. + /// Acquire must be called first else this will always return false /// - public interface IMainDom - { - /// - /// Gets a value indicating whether the current domain is the main domain. - /// - /// - /// Acquire must be called first else this will always return false - /// - bool IsMainDom { get; } + bool IsMainDom { get; } - /// - /// Tries to acquire the MainDom, returns true if successful else false - /// - bool Acquire(IApplicationShutdownRegistry hostingEnvironment); + /// + /// Tries to acquire the MainDom, returns true if successful else false + /// + bool Acquire(IApplicationShutdownRegistry hostingEnvironment); - /// - /// Registers a resource that requires the current AppDomain to be the main domain to function. - /// - /// An action to execute when registering. - /// An action to execute before the AppDomain releases the main domain status. - /// An optional weight (lower goes first). - /// A value indicating whether it was possible to register. - /// If registering is successful, then the action - /// is guaranteed to execute before the AppDomain releases the main domain status. - bool Register(Action? install = null, Action? release = null, int weight = 100); - } + /// + /// Registers a resource that requires the current AppDomain to be the main domain to function. + /// + /// An action to execute when registering. + /// An action to execute before the AppDomain releases the main domain status. + /// An optional weight (lower goes first). + /// A value indicating whether it was possible to register. + /// + /// If registering is successful, then the action + /// is guaranteed to execute before the AppDomain releases the main domain status. + /// + bool Register(Action? install = null, Action? release = null, int weight = 100); } diff --git a/src/Umbraco.Core/Runtime/IMainDomKeyGenerator.cs b/src/Umbraco.Core/Runtime/IMainDomKeyGenerator.cs index 5b8fb819e658..cbfbffac4cb9 100644 --- a/src/Umbraco.Core/Runtime/IMainDomKeyGenerator.cs +++ b/src/Umbraco.Core/Runtime/IMainDomKeyGenerator.cs @@ -1,13 +1,12 @@ -namespace Umbraco.Cms.Core.Runtime +namespace Umbraco.Cms.Core.Runtime; + +/// +/// Defines a class which can generate a distinct key for a MainDom boundary. +/// +public interface IMainDomKeyGenerator { /// - /// Defines a class which can generate a distinct key for a MainDom boundary. + /// Returns a key that signifies a MainDom boundary. /// - public interface IMainDomKeyGenerator - { - /// - /// Returns a key that signifies a MainDom boundary. - /// - string GenerateKey(); - } + string GenerateKey(); } diff --git a/src/Umbraco.Core/Runtime/IMainDomLock.cs b/src/Umbraco.Core/Runtime/IMainDomLock.cs index b0b3394a01ec..d7ae8aba8a59 100644 --- a/src/Umbraco.Core/Runtime/IMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/IMainDomLock.cs @@ -1,29 +1,25 @@ -using System; -using System.Threading.Tasks; +namespace Umbraco.Cms.Core.Runtime; -namespace Umbraco.Cms.Core.Runtime +/// +/// An application-wide distributed lock +/// +/// +/// Disposing releases the lock +/// +public interface IMainDomLock : IDisposable { /// - /// An application-wide distributed lock + /// Acquires an application-wide distributed lock /// - /// - /// Disposing releases the lock - /// - public interface IMainDomLock : IDisposable - { - /// - /// Acquires an application-wide distributed lock - /// - /// - /// - /// An awaitable boolean value which will be false if the elapsed millsecondsTimeout value is exceeded - /// - Task AcquireLockAsync(int millisecondsTimeout); + /// + /// + /// An awaitable boolean value which will be false if the elapsed millsecondsTimeout value is exceeded + /// + Task AcquireLockAsync(int millisecondsTimeout); - /// - /// Wait on a background thread to receive a signal from another AppDomain - /// - /// - Task ListenAsync(); - } + /// + /// Wait on a background thread to receive a signal from another AppDomain + /// + /// + Task ListenAsync(); } diff --git a/src/Umbraco.Core/Runtime/IUmbracoBootPermissionChecker.cs b/src/Umbraco.Core/Runtime/IUmbracoBootPermissionChecker.cs index 48ea6a5a48e7..b5c43cde4acf 100644 --- a/src/Umbraco.Core/Runtime/IUmbracoBootPermissionChecker.cs +++ b/src/Umbraco.Core/Runtime/IUmbracoBootPermissionChecker.cs @@ -1,7 +1,6 @@ -namespace Umbraco.Cms.Core.Runtime +namespace Umbraco.Cms.Core.Runtime; + +public interface IUmbracoBootPermissionChecker { - public interface IUmbracoBootPermissionChecker - { - void ThrowIfNotPermissions(); - } + void ThrowIfNotPermissions(); } diff --git a/src/Umbraco.Core/Runtime/MainDom.cs b/src/Umbraco.Core/Runtime/MainDom.cs index 0198382b2a8d..f3673d77a6ca 100644 --- a/src/Umbraco.Core/Runtime/MainDom.cs +++ b/src/Umbraco.Core/Runtime/MainDom.cs @@ -1,291 +1,293 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Security.Cryptography; -using System.Threading; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Hosting; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Runtime +namespace Umbraco.Cms.Core.Runtime; + +/// +/// Provides the full implementation of . +/// +/// +/// When an AppDomain starts, it tries to acquire the main domain status. +/// When an AppDomain stops (eg the application is restarting) it should release the main domain status. +/// +public class MainDom : IMainDom, IRegisteredObject, IDisposable { + #region Ctor - /// - /// Provides the full implementation of . - /// - /// - /// When an AppDomain starts, it tries to acquire the main domain status. - /// When an AppDomain stops (eg the application is restarting) it should release the main domain status. - /// - public class MainDom : IMainDom, IRegisteredObject, IDisposable + // initializes a new instance of MainDom + public MainDom(ILogger logger, IMainDomLock systemLock) { - #region Vars - - private readonly ILogger _logger; - private IApplicationShutdownRegistry? _hostingEnvironment; - private readonly IMainDomLock _mainDomLock; - - // our own lock for local consistency - private object _locko = new object(); - - private bool _isInitialized; - // indicates whether... - private bool? _isMainDom; // we are the main domain - private volatile bool _signaled; // we have been signaled - - // actions to run before releasing the main domain - private readonly List> _callbacks = new List>(); - - private const int LockTimeoutMilliseconds = 40000; // 40 seconds - - private Task? _listenTask; - private Task? _listenCompleteTask; + _logger = logger; + _mainDomLock = systemLock; + } - #endregion + #endregion - #region Ctor + /// + public bool Acquire(IApplicationShutdownRegistry hostingEnvironment) + { + _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); - // initializes a new instance of MainDom - public MainDom(ILogger logger, IMainDomLock systemLock) + return LazyInitializer.EnsureInitialized(ref _isMainDom, ref _isInitialized, ref _locko, () => { - _logger = logger; - _mainDomLock = systemLock; - } - - #endregion + hostingEnvironment.RegisterObject(this); + return Acquire(); + })!.Value; + } - /// - public bool Acquire(IApplicationShutdownRegistry hostingEnvironment) + /// + /// Registers a resource that requires the current AppDomain to be the main domain to function. + /// + /// An action to execute when registering. + /// An action to execute before the AppDomain releases the main domain status. + /// An optional weight (lower goes first). + /// A value indicating whether it was possible to register. + /// + /// If registering is successful, then the action + /// is guaranteed to execute before the AppDomain releases the main domain status. + /// + public bool Register(Action? install = null, Action? release = null, int weight = 100) + { + lock (_locko) { - _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); - - return LazyInitializer.EnsureInitialized(ref _isMainDom, ref _isInitialized, ref _locko, () => + if (_signaled) { - hostingEnvironment.RegisterObject(this); - return Acquire(); - })!.Value; - } + return false; + } - /// - /// Registers a resource that requires the current AppDomain to be the main domain to function. - /// - /// An action to execute when registering. - /// An action to execute before the AppDomain releases the main domain status. - /// An optional weight (lower goes first). - /// A value indicating whether it was possible to register. - /// If registering is successful, then the action - /// is guaranteed to execute before the AppDomain releases the main domain status. - public bool Register(Action? install = null, Action? release = null, int weight = 100) - { - lock (_locko) + if (_isMainDom.HasValue == false) { - if (_signaled) - { - return false; - } - - if (_isMainDom.HasValue == false) - { - throw new InvalidOperationException("Register called before IsMainDom has been established"); - } - else if (_isMainDom == false) - { - _logger.LogWarning("Register called when MainDom has not been acquired"); - return false; - } + throw new InvalidOperationException("Register called before IsMainDom has been established"); + } - install?.Invoke(); - if (release != null) - { - _callbacks.Add(new KeyValuePair(weight, release)); - } + if (_isMainDom == false) + { + _logger.LogWarning("Register called when MainDom has not been acquired"); + return false; + } - return true; + install?.Invoke(); + if (release != null) + { + _callbacks.Add(new KeyValuePair(weight, release)); } + + return true; } + } - // handles the signal requesting that the main domain is released - private void OnSignal(string source) + /// + /// Gets a value indicating whether the current domain is the main domain. + /// + /// + /// Acquire must be called first else this will always return false + /// + public bool IsMainDom + { + get { - // once signaled, we stop waiting, but then there is the hosting environment - // so we have to make sure that we only enter that method once - - lock (_locko) + if (!_isMainDom.HasValue) { - _logger.LogDebug("Signaled ({Signaled}) ({SignalSource})", _signaled ? "again" : "first", source); - if (_signaled) return; - if (_isMainDom == false) return; // probably not needed - _signaled = true; + throw new InvalidOperationException("IsMainDom has not been established yet"); + } - try - { - _logger.LogInformation("Stopping ({SignalSource})", source); - foreach (var callback in _callbacks.OrderBy(x => x.Key).Select(x => x.Value)) - { - try - { - callback(); // no timeout on callbacks - } - catch (Exception e) - { - _logger.LogError(e, "Error while running callback"); - continue; - } - } + return _isMainDom.Value; + } + } - _logger.LogDebug("Stopped ({SignalSource})", source); - } - finally - { - // in any case... - _isMainDom = false; - _mainDomLock.Dispose(); - _logger.LogInformation("Released ({SignalSource})", source); - } + // IRegisteredObject + void IRegisteredObject.Stop(bool immediate) + { + OnSignal("environment"); // will run once - } - } + // The web app is stopping, need to wind down + Dispose(true); + + _hostingEnvironment?.UnregisterObject(this); + } + + // handles the signal requesting that the main domain is released + private void OnSignal(string source) + { + // once signaled, we stop waiting, but then there is the hosting environment + // so we have to make sure that we only enter that method once - // acquires the main domain - private bool Acquire() + lock (_locko) { - // if signaled, too late to acquire, give up - // the handler is not installed so that would be the hosting environment + _logger.LogDebug("Signaled ({Signaled}) ({SignalSource})", _signaled ? "again" : "first", source); if (_signaled) { - _logger.LogInformation("Cannot acquire MainDom (signaled)."); - return false; + return; } - _logger.LogInformation("Acquiring MainDom."); - - // Get the lock - var acquired = false; - try - { - acquired = _mainDomLock.AcquireLockAsync(LockTimeoutMilliseconds).GetAwaiter().GetResult(); - } - catch (Exception ex) + if (_isMainDom == false) { - _logger.LogError(ex, "Error while acquiring MainDom"); + return; // probably not needed } - if (!acquired) - { - _logger.LogInformation("Cannot acquire MainDom (timeout)."); - - // In previous versions we'd let a TimeoutException be thrown - // and the appdomain would not start. We have the opportunity to allow it to - // start without having MainDom? This would mean that it couldn't write - // to nucache/examine and would only be ok if this was a super short lived appdomain. - // maybe safer to just keep throwing in this case. - - throw new TimeoutException("Cannot acquire MainDom"); - // return false; - } + _signaled = true; try { - // Listen for the signal from another AppDomain coming online to release the lock - _listenTask = _mainDomLock.ListenAsync(); - _listenCompleteTask = _listenTask.ContinueWith(t => + _logger.LogInformation("Stopping ({SignalSource})", source); + foreach (Action callback in _callbacks.OrderBy(x => x.Key).Select(x => x.Value)) { - if (_listenTask.Exception != null) + try { - _logger.LogWarning("Listening task completed with {TaskStatus}, Exception: {Exception}", _listenTask.Status, _listenTask.Exception); + callback(); // no timeout on callbacks } - else + catch (Exception e) { - _logger.LogDebug("Listening task completed with {TaskStatus}", _listenTask.Status); + _logger.LogError(e, "Error while running callback"); } + } - OnSignal("signal"); - }, TaskScheduler.Default); // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html + _logger.LogDebug("Stopped ({SignalSource})", source); } - catch (OperationCanceledException ex) + finally { - // the waiting task could be canceled if this appdomain is naturally shutting down, we'll just swallow this exception - _logger.LogWarning(ex, ex.Message); + // in any case... + _isMainDom = false; + _mainDomLock.Dispose(); + _logger.LogInformation("Released ({SignalSource})", source); } - - _logger.LogInformation("Acquired MainDom."); - return true; } + } - /// - /// Gets a value indicating whether the current domain is the main domain. - /// - /// - /// Acquire must be called first else this will always return false - /// - public bool IsMainDom + // acquires the main domain + private bool Acquire() + { + // if signaled, too late to acquire, give up + // the handler is not installed so that would be the hosting environment + if (_signaled) { - get - { - if (!_isMainDom.HasValue) - { - throw new InvalidOperationException("IsMainDom has not been established yet"); - } - return _isMainDom.Value; - } + _logger.LogInformation("Cannot acquire MainDom (signaled)."); + return false; } - // IRegisteredObject - void IRegisteredObject.Stop(bool immediate) - { - OnSignal("environment"); // will run once + _logger.LogInformation("Acquiring MainDom."); - // The web app is stopping, need to wind down - Dispose(true); - - _hostingEnvironment?.UnregisterObject(this); + // Get the lock + var acquired = false; + try + { + acquired = _mainDomLock.AcquireLockAsync(LockTimeoutMilliseconds).GetAwaiter().GetResult(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while acquiring MainDom"); } - #region IDisposable Support + if (!acquired) + { + _logger.LogInformation("Cannot acquire MainDom (timeout)."); - // This code added to correctly implement the disposable pattern. + // In previous versions we'd let a TimeoutException be thrown + // and the appdomain would not start. We have the opportunity to allow it to + // start without having MainDom? This would mean that it couldn't write + // to nucache/examine and would only be ok if this was a super short lived appdomain. + // maybe safer to just keep throwing in this case. - private bool disposedValue = false; // To detect redundant calls + throw new TimeoutException("Cannot acquire MainDom"); + // return false; + } - protected virtual void Dispose(bool disposing) + try { - if (!disposedValue) + // Listen for the signal from another AppDomain coming online to release the lock + _listenTask = _mainDomLock.ListenAsync(); + _listenCompleteTask = _listenTask.ContinueWith(t => { - if (disposing) + if (_listenTask.Exception != null) + { + _logger.LogWarning("Listening task completed with {TaskStatus}, Exception: {Exception}", + _listenTask.Status, _listenTask.Exception); + } + else { - _mainDomLock.Dispose(); + _logger.LogDebug("Listening task completed with {TaskStatus}", _listenTask.Status); } - disposedValue = true; - } + OnSignal("signal"); + }, TaskScheduler.Default); // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html } - - public void Dispose() + catch (OperationCanceledException ex) { - Dispose(true); + // the waiting task could be canceled if this appdomain is naturally shutting down, we'll just swallow this exception + _logger.LogWarning(ex, ex.Message); } - #endregion + _logger.LogInformation("Acquired MainDom."); + return true; + } + + public static string GetMainDomId(IHostingEnvironment hostingEnvironment) + { + // HostingEnvironment.ApplicationID is null in unit tests, making ReplaceNonAlphanumericChars fail + var appId = hostingEnvironment.ApplicationId?.ReplaceNonAlphanumericChars(string.Empty) ?? string.Empty; + + // combining with the physical path because if running on eg IIS Express, + // two sites could have the same appId even though they are different. + // + // now what could still collide is... two sites, running in two different processes + // and having the same appId, and running on the same app physical path + // + // we *cannot* use the process ID here because when an AppPool restarts it is + // a new process for the same application path + + var appPath = hostingEnvironment.ApplicationPhysicalPath?.ToLowerInvariant() ?? string.Empty; + var hash = (appId + ":::" + appPath).GenerateHash(); + + return hash; + } + + #region Vars + + private readonly ILogger _logger; + private IApplicationShutdownRegistry? _hostingEnvironment; + private readonly IMainDomLock _mainDomLock; + + // our own lock for local consistency + private object _locko = new(); + + private bool _isInitialized; - public static string GetMainDomId(IHostingEnvironment hostingEnvironment) + // indicates whether... + private bool? _isMainDom; // we are the main domain + private volatile bool _signaled; // we have been signaled + + // actions to run before releasing the main domain + private readonly List> _callbacks = new(); + + private const int LockTimeoutMilliseconds = 40000; // 40 seconds + + private Task? _listenTask; + private Task? _listenCompleteTask; + + #endregion + + #region IDisposable Support + + // This code added to correctly implement the disposable pattern. + + private bool disposedValue; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) { - // HostingEnvironment.ApplicationID is null in unit tests, making ReplaceNonAlphanumericChars fail - var appId = hostingEnvironment.ApplicationId?.ReplaceNonAlphanumericChars(string.Empty) ?? string.Empty; - - // combining with the physical path because if running on eg IIS Express, - // two sites could have the same appId even though they are different. - // - // now what could still collide is... two sites, running in two different processes - // and having the same appId, and running on the same app physical path - // - // we *cannot* use the process ID here because when an AppPool restarts it is - // a new process for the same application path - - var appPath = hostingEnvironment.ApplicationPhysicalPath?.ToLowerInvariant() ?? string.Empty; - var hash = (appId + ":::" + appPath).GenerateHash(); - - return hash; + if (disposing) + { + _mainDomLock.Dispose(); + } + + disposedValue = true; } } + + public void Dispose() => Dispose(true); + + #endregion } diff --git a/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs b/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs index 5d2248906ee7..e163c6baa931 100644 --- a/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs +++ b/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs @@ -1,104 +1,100 @@ -using System; using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Hosting; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Runtime +namespace Umbraco.Cms.Core.Runtime; + +/// +/// Uses a system-wide Semaphore and EventWaitHandle to synchronize the current AppDomain +/// +public class MainDomSemaphoreLock : IMainDomLock { - /// - /// Uses a system-wide Semaphore and EventWaitHandle to synchronize the current AppDomain - /// - public class MainDomSemaphoreLock : IMainDomLock - { - private readonly SystemLock _systemLock; + private readonly ILogger _logger; - // event wait handle used to notify current main domain that it should - // release the lock because a new domain wants to be the main domain - private readonly EventWaitHandle _signal; - private readonly ILogger _logger; - private IDisposable? _lockRelease; + // event wait handle used to notify current main domain that it should + // release the lock because a new domain wants to be the main domain + private readonly EventWaitHandle _signal; + private readonly SystemLock _systemLock; + private IDisposable? _lockRelease; - public MainDomSemaphoreLock(ILogger logger, IHostingEnvironment hostingEnvironment) + public MainDomSemaphoreLock(ILogger logger, IHostingEnvironment hostingEnvironment) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - throw new PlatformNotSupportedException("MainDomSemaphoreLock is only supported on Windows."); - } + throw new PlatformNotSupportedException("MainDomSemaphoreLock is only supported on Windows."); + } - var mainDomId = MainDom.GetMainDomId(hostingEnvironment); - var lockName = "UMBRACO-" + mainDomId + "-MAINDOM-LCK"; - _systemLock = new SystemLock(lockName); + var mainDomId = MainDom.GetMainDomId(hostingEnvironment); + var lockName = "UMBRACO-" + mainDomId + "-MAINDOM-LCK"; + _systemLock = new SystemLock(lockName); - var eventName = "UMBRACO-" + mainDomId + "-MAINDOM-EVT"; - _signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName); - _logger = logger; - } + var eventName = "UMBRACO-" + mainDomId + "-MAINDOM-EVT"; + _signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName); + _logger = logger; + } - // WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread - public Task ListenAsync() => _signal.WaitOneAsync(); + // WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread + public Task ListenAsync() => _signal.WaitOneAsync(); - public Task AcquireLockAsync(int millisecondsTimeout) - { - // signal other instances that we want the lock, then wait on the lock, - // which may timeout, and this is accepted - see comments below + public Task AcquireLockAsync(int millisecondsTimeout) + { + // signal other instances that we want the lock, then wait on the lock, + // which may timeout, and this is accepted - see comments below - // signal, then wait for the lock, then make sure the event is - // reset (maybe there was noone listening..) - _signal.Set(); + // signal, then wait for the lock, then make sure the event is + // reset (maybe there was noone listening..) + _signal.Set(); - // if more than 1 instance reach that point, one will get the lock - // and the other one will timeout, which is accepted + // if more than 1 instance reach that point, one will get the lock + // and the other one will timeout, which is accepted - // This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset. - try - { - _lockRelease = _systemLock.Lock(millisecondsTimeout); - return Task.FromResult(true); - } - catch (TimeoutException ex) - { - _logger.LogError(ex.Message); - return Task.FromResult(false); - } - finally - { - // we need to reset the event, because otherwise we would end up - // signaling ourselves and committing suicide immediately. - // only 1 instance can reach that point, but other instances may - // have started and be trying to get the lock - they will timeout, - // which is accepted + // This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset. + try + { + _lockRelease = _systemLock.Lock(millisecondsTimeout); + return Task.FromResult(true); + } + catch (TimeoutException ex) + { + _logger.LogError(ex.Message); + return Task.FromResult(false); + } + finally + { + // we need to reset the event, because otherwise we would end up + // signaling ourselves and committing suicide immediately. + // only 1 instance can reach that point, but other instances may + // have started and be trying to get the lock - they will timeout, + // which is accepted - _signal.Reset(); - } + _signal.Reset(); } + } + + #region IDisposable Support - #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls + private bool disposedValue; // To detect redundant calls - protected virtual void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) { - if (!disposedValue) + if (disposing) { - if (disposing) - { - _lockRelease?.Dispose(); - _signal.Close(); - _signal.Dispose(); - } - - disposedValue = true; + _lockRelease?.Dispose(); + _signal.Close(); + _signal.Dispose(); } - } - // This code added to correctly implement the disposable pattern. - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); + disposedValue = true; } - #endregion } + + // This code added to correctly implement the disposable pattern. + public void Dispose() => + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + + #endregion } diff --git a/src/Umbraco.Core/RuntimeLevel.cs b/src/Umbraco.Core/RuntimeLevel.cs index d6687d4628f4..d36c7df1d831 100644 --- a/src/Umbraco.Core/RuntimeLevel.cs +++ b/src/Umbraco.Core/RuntimeLevel.cs @@ -1,40 +1,39 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Describes the levels in which the runtime can run. +/// +public enum RuntimeLevel { /// - /// Describes the levels in which the runtime can run. + /// The runtime has failed to boot and cannot run. /// - public enum RuntimeLevel - { - /// - /// The runtime has failed to boot and cannot run. - /// - BootFailed = -1, + BootFailed = -1, - /// - /// The level is unknown. - /// - Unknown = 0, + /// + /// The level is unknown. + /// + Unknown = 0, - /// - /// The runtime is booting. - /// - Boot = 1, + /// + /// The runtime is booting. + /// + Boot = 1, - /// - /// The runtime has detected that Umbraco is not installed at all, ie there is - /// no database, and is currently installing Umbraco. - /// - Install = 2, + /// + /// The runtime has detected that Umbraco is not installed at all, ie there is + /// no database, and is currently installing Umbraco. + /// + Install = 2, - /// - /// The runtime has detected an Umbraco install which needed to be upgraded, and - /// is currently upgrading Umbraco. - /// - Upgrade = 3, + /// + /// The runtime has detected an Umbraco install which needed to be upgraded, and + /// is currently upgrading Umbraco. + /// + Upgrade = 3, - /// - /// The runtime has detected an up-to-date Umbraco install and is running. - /// - Run = 100 - } + /// + /// The runtime has detected an up-to-date Umbraco install and is running. + /// + Run = 100 } diff --git a/src/Umbraco.Core/RuntimeLevelReason.cs b/src/Umbraco.Core/RuntimeLevelReason.cs index 94192c83b2c6..3a89bc115d40 100644 --- a/src/Umbraco.Core/RuntimeLevelReason.cs +++ b/src/Umbraco.Core/RuntimeLevelReason.cs @@ -1,78 +1,77 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Describes the reason for the runtime level. +/// +public enum RuntimeLevelReason { /// - /// Describes the reason for the runtime level. + /// The reason is unknown. /// - public enum RuntimeLevelReason - { - /// - /// The reason is unknown. - /// - Unknown, + Unknown, - /// - /// The code version is lower than the version indicated in web.config, and - /// downgrading Umbraco is not supported. - /// - BootFailedCannotDowngrade, + /// + /// The code version is lower than the version indicated in web.config, and + /// downgrading Umbraco is not supported. + /// + BootFailedCannotDowngrade, - /// - /// The runtime cannot connect to the configured database. - /// - BootFailedCannotConnectToDatabase, + /// + /// The runtime cannot connect to the configured database. + /// + BootFailedCannotConnectToDatabase, - /// - /// The runtime can connect to the configured database, but it cannot - /// retrieve the migrations status. - /// - BootFailedCannotCheckUpgradeState, + /// + /// The runtime can connect to the configured database, but it cannot + /// retrieve the migrations status. + /// + BootFailedCannotCheckUpgradeState, - /// - /// An exception was thrown during boot. - /// - BootFailedOnException, + /// + /// An exception was thrown during boot. + /// + BootFailedOnException, - /// - /// Umbraco is not installed at all. - /// - InstallNoVersion, + /// + /// Umbraco is not installed at all. + /// + InstallNoVersion, - /// - /// A version is specified in web.config but the database is not configured. - /// - /// This is a weird state. - InstallNoDatabase, + /// + /// A version is specified in web.config but the database is not configured. + /// + /// This is a weird state. + InstallNoDatabase, - /// - /// A version is specified in web.config and a database is configured, but the - /// database is missing, and installing over a missing database has been enabled. - /// - InstallMissingDatabase, + /// + /// A version is specified in web.config and a database is configured, but the + /// database is missing, and installing over a missing database has been enabled. + /// + InstallMissingDatabase, - /// - /// A version is specified in web.config and a database is configured, but the - /// database is empty, and installing over an empty database has been enabled. - /// - InstallEmptyDatabase, + /// + /// A version is specified in web.config and a database is configured, but the + /// database is empty, and installing over an empty database has been enabled. + /// + InstallEmptyDatabase, - /// - /// Umbraco runs an old version. - /// - UpgradeOldVersion, + /// + /// Umbraco runs an old version. + /// + UpgradeOldVersion, - /// - /// Umbraco runs the current version but some migrations have not run. - /// - UpgradeMigrations, + /// + /// Umbraco runs the current version but some migrations have not run. + /// + UpgradeMigrations, - /// - /// Umbraco runs the current version but some package migrations have not run. - /// - UpgradePackageMigrations, + /// + /// Umbraco runs the current version but some package migrations have not run. + /// + UpgradePackageMigrations, - /// - /// Umbraco is running. - /// - Run - } + /// + /// Umbraco is running. + /// + Run } diff --git a/src/Umbraco.Core/Scoping/ICoreScope.cs b/src/Umbraco.Core/Scoping/ICoreScope.cs index 8bb85ca29d90..ef3cf91c4c42 100644 --- a/src/Umbraco.Core/Scoping/ICoreScope.cs +++ b/src/Umbraco.Core/Scoping/ICoreScope.cs @@ -1,57 +1,56 @@ -using System; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Events; namespace Umbraco.Cms.Core.Scoping; /// -/// Represents a scope. +/// Represents a scope. /// public interface ICoreScope : IDisposable, IInstanceIdentifiable { /// - /// Gets the scope notification publisher + /// Gets the scope notification publisher /// IScopedNotificationPublisher Notifications { get; } /// - /// Gets the repositories cache mode. + /// Gets the repositories cache mode. /// RepositoryCacheMode RepositoryCacheMode { get; } /// - /// Gets the scope isolated cache. + /// Gets the scope isolated cache. /// IsolatedCaches IsolatedCaches { get; } /// - /// Completes the scope. + /// Completes the scope. /// /// A value indicating whether the scope has been successfully completed. /// Can return false if any child scope has not completed. bool Complete(); /// - /// Read-locks some lock objects. + /// Read-locks some lock objects. /// /// Array of lock object identifiers. void ReadLock(params int[] lockIds); /// - /// Write-locks some lock objects. + /// Write-locks some lock objects. /// /// Array of object identifiers. void WriteLock(params int[] lockIds); /// - /// Write-locks some lock objects. + /// Write-locks some lock objects. /// /// The database timeout in milliseconds /// The lock object identifier. void WriteLock(TimeSpan timeout, int lockId); /// - /// Read-locks some lock objects. + /// Read-locks some lock objects. /// /// The database timeout in milliseconds /// The lock object identifier. diff --git a/src/Umbraco.Core/Scoping/ICoreScopeProvider.cs b/src/Umbraco.Core/Scoping/ICoreScopeProvider.cs index d4fe496d38ff..792673453f44 100644 --- a/src/Umbraco.Core/Scoping/ICoreScopeProvider.cs +++ b/src/Umbraco.Core/Scoping/ICoreScopeProvider.cs @@ -2,49 +2,50 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Persistence.Querying; -namespace Umbraco.Cms.Core.Scoping +namespace Umbraco.Cms.Core.Scoping; + +/// +/// Provides scopes. +/// +public interface ICoreScopeProvider { /// - /// Provides scopes. + /// Gets the scope context. /// - public interface ICoreScopeProvider - { - /// - /// Creates an ambient scope. - /// - /// The transaction isolation level. - /// The repositories cache mode. - /// An optional events dispatcher. - /// An optional notification publisher. - /// A value indicating whether to scope the filesystems. - /// A value indicating whether this scope should always be registered in the call context. - /// A value indicating whether this scope is auto-completed. - /// The created ambient scope. - /// - /// The created scope becomes the ambient scope. - /// If an ambient scope already exists, it becomes the parent of the created scope. - /// When the created scope is disposed, the parent scope becomes the ambient scope again. - /// Parameters must be specified on the outermost scope, or must be compatible with the parents. - /// Auto-completed scopes should be used for read-only operations ONLY. Do not use them if you do not - /// understand the associated issues, such as the scope being completed even though an exception is thrown. - /// - ICoreScope CreateCoreScope( - IsolationLevel isolationLevel = IsolationLevel.Unspecified, - RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, - IEventDispatcher? eventDispatcher = null, - IScopedNotificationPublisher? scopedNotificationPublisher = null, - bool? scopeFileSystems = null, - bool callContext = false, - bool autoComplete = false); + IScopeContext? Context { get; } - /// - /// Gets the scope context. - /// - IScopeContext? Context { get; } + /// + /// Creates an ambient scope. + /// + /// The transaction isolation level. + /// The repositories cache mode. + /// An optional events dispatcher. + /// An optional notification publisher. + /// A value indicating whether to scope the filesystems. + /// A value indicating whether this scope should always be registered in the call context. + /// A value indicating whether this scope is auto-completed. + /// The created ambient scope. + /// + /// The created scope becomes the ambient scope. + /// If an ambient scope already exists, it becomes the parent of the created scope. + /// When the created scope is disposed, the parent scope becomes the ambient scope again. + /// Parameters must be specified on the outermost scope, or must be compatible with the parents. + /// + /// Auto-completed scopes should be used for read-only operations ONLY. Do not use them if you do not + /// understand the associated issues, such as the scope being completed even though an exception is thrown. + /// + /// + ICoreScope CreateCoreScope( + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, + IEventDispatcher? eventDispatcher = null, + IScopedNotificationPublisher? scopedNotificationPublisher = null, + bool? scopeFileSystems = null, + bool callContext = false, + bool autoComplete = false); - /// - /// Creates an instance of - /// - IQuery CreateQuery(); - } + /// + /// Creates an instance of + /// + IQuery CreateQuery(); } diff --git a/src/Umbraco.Core/Scoping/IInstanceIdentifiable.cs b/src/Umbraco.Core/Scoping/IInstanceIdentifiable.cs index 9d0bc9ceef5a..1942ecdc43e2 100644 --- a/src/Umbraco.Core/Scoping/IInstanceIdentifiable.cs +++ b/src/Umbraco.Core/Scoping/IInstanceIdentifiable.cs @@ -1,16 +1,14 @@ -using System; +namespace Umbraco.Cms.Core.Scoping; -namespace Umbraco.Cms.Core.Scoping +/// +/// Exposes an instance unique identifier. +/// +public interface IInstanceIdentifiable { /// - /// Exposes an instance unique identifier. + /// Gets the instance unique identifier. /// - public interface IInstanceIdentifiable - { - /// - /// Gets the instance unique identifier. - /// - Guid InstanceId { get; } - int CreatedThreadId { get; } - } + Guid InstanceId { get; } + + int CreatedThreadId { get; } } diff --git a/src/Umbraco.Core/Scoping/IScopeContext.cs b/src/Umbraco.Core/Scoping/IScopeContext.cs index 7f1302911a16..fd33f46e4a18 100644 --- a/src/Umbraco.Core/Scoping/IScopeContext.cs +++ b/src/Umbraco.Core/Scoping/IScopeContext.cs @@ -1,52 +1,53 @@ -using System; +namespace Umbraco.Cms.Core.Scoping; -namespace Umbraco.Cms.Core.Scoping +/// +/// Represents a scope context. +/// +/// +/// A scope context can enlist objects that will be attached to the scope, and available +/// for the duration of the scope. In addition, it can enlist actions, that will run when the +/// scope is exiting, and after the database transaction has been committed. +/// +public interface IScopeContext : IInstanceIdentifiable { /// - /// Represents a scope context. + /// Enlists an action. /// - /// A scope context can enlist objects that will be attached to the scope, and available - /// for the duration of the scope. In addition, it can enlist actions, that will run when the - /// scope is exiting, and after the database transaction has been committed. - public interface IScopeContext : IInstanceIdentifiable - { - /// - /// Enlists an action. - /// - /// The action unique identifier. - /// The action. - /// The optional action priority (default is 100, lower runs first). - /// - /// It is ok to enlist multiple action with the same key but only the first one will run. - /// The action boolean parameter indicates whether the scope completed or not. - /// - void Enlist(string key, Action action, int priority = 100); + /// The action unique identifier. + /// The action. + /// The optional action priority (default is 100, lower runs first). + /// + /// It is ok to enlist multiple action with the same key but only the first one will run. + /// The action boolean parameter indicates whether the scope completed or not. + /// + void Enlist(string key, Action action, int priority = 100); - /// - /// Enlists an object and action. - /// - /// The type of the object. - /// The object unique identifier. - /// A function providing the object. - /// The optional action. - /// The optional action priority (default is 100, lower runs first). - /// The object. - /// - /// On the first time an object is enlisted with a given key, the object is actually - /// created. Next calls just return the existing object. It is ok to enlist multiple objects - /// and action with the same key but only the first one is used, the others are ignored. - /// The action boolean parameter indicates whether the scope completed or not. - /// - T? Enlist(string key, Func creator, Action? action = null, int priority = 100); + /// + /// Enlists an object and action. + /// + /// The type of the object. + /// The object unique identifier. + /// A function providing the object. + /// The optional action. + /// The optional action priority (default is 100, lower runs first). + /// The object. + /// + /// + /// On the first time an object is enlisted with a given key, the object is actually + /// created. Next calls just return the existing object. It is ok to enlist multiple objects + /// and action with the same key but only the first one is used, the others are ignored. + /// + /// The action boolean parameter indicates whether the scope completed or not. + /// + T? Enlist(string key, Func creator, Action? action = null, int priority = 100); - /// - /// Gets an enlisted object. - /// - /// The type of the object. - /// The object unique identifier. - /// The enlisted object, if any, else the default value. - T? GetEnlisted(string key); + /// + /// Gets an enlisted object. + /// + /// The type of the object. + /// The object unique identifier. + /// The enlisted object, if any, else the default value. + T? GetEnlisted(string key); - void ScopeExit(bool completed); - } + void ScopeExit(bool completed); } diff --git a/src/Umbraco.Core/Scoping/RepositoryCacheMode.cs b/src/Umbraco.Core/Scoping/RepositoryCacheMode.cs index 78c50b628f29..de5a6a82aab3 100644 --- a/src/Umbraco.Core/Scoping/RepositoryCacheMode.cs +++ b/src/Umbraco.Core/Scoping/RepositoryCacheMode.cs @@ -1,36 +1,35 @@ -namespace Umbraco.Cms.Core.Scoping +namespace Umbraco.Cms.Core.Scoping; + +/// +/// Specifies the cache mode of repositories. +/// +public enum RepositoryCacheMode { /// - /// Specifies the cache mode of repositories. + /// Unspecified. /// - public enum RepositoryCacheMode - { - /// - /// Unspecified. - /// - Unspecified = 0, + Unspecified = 0, - /// - /// Default, full L2 cache. - /// - Default = 1, + /// + /// Default, full L2 cache. + /// + Default = 1, - /// - /// Scoped cache. - /// - /// - /// Reads from, and writes to, a scope-local cache. - /// Upon scope completion, clears the global L2 cache. - /// - Scoped = 2, + /// + /// Scoped cache. + /// + /// + /// Reads from, and writes to, a scope-local cache. + /// Upon scope completion, clears the global L2 cache. + /// + Scoped = 2, - /// - /// No cache. - /// - /// - /// Bypasses caches entirely. - /// Upon scope completion, clears the global L2 cache. - /// - None = 3 - } + /// + /// No cache. + /// + /// + /// Bypasses caches entirely. + /// Upon scope completion, clears the global L2 cache. + /// + None = 3 } diff --git a/src/Umbraco.Core/Sections/ContentSection.cs b/src/Umbraco.Core/Sections/ContentSection.cs index 828adea2952f..ccb34b000965 100644 --- a/src/Umbraco.Core/Sections/ContentSection.cs +++ b/src/Umbraco.Core/Sections/ContentSection.cs @@ -1,14 +1,13 @@ -namespace Umbraco.Cms.Core.Sections +namespace Umbraco.Cms.Core.Sections; + +/// +/// Defines the back office content section +/// +public class ContentSection : ISection { - /// - /// Defines the back office content section - /// - public class ContentSection : ISection - { - /// - public string Alias => Constants.Applications.Content; + /// + public string Alias => Constants.Applications.Content; - /// - public string Name => "Content"; - } + /// + public string Name => "Content"; } diff --git a/src/Umbraco.Core/Sections/FormsSection.cs b/src/Umbraco.Core/Sections/FormsSection.cs index e0fd1085eed2..2f98a84a5689 100644 --- a/src/Umbraco.Core/Sections/FormsSection.cs +++ b/src/Umbraco.Core/Sections/FormsSection.cs @@ -1,11 +1,10 @@ -namespace Umbraco.Cms.Core.Sections +namespace Umbraco.Cms.Core.Sections; + +/// +/// Defines the back office media section +/// +public class FormsSection : ISection { - /// - /// Defines the back office media section - /// - public class FormsSection : ISection - { - public string Alias => Constants.Applications.Forms; - public string Name => "Forms"; - } + public string Alias => Constants.Applications.Forms; + public string Name => "Forms"; } diff --git a/src/Umbraco.Core/Sections/ISection.cs b/src/Umbraco.Core/Sections/ISection.cs index bbd380f57ea8..f79c3260c6c9 100644 --- a/src/Umbraco.Core/Sections/ISection.cs +++ b/src/Umbraco.Core/Sections/ISection.cs @@ -1,18 +1,17 @@ -namespace Umbraco.Cms.Core.Sections +namespace Umbraco.Cms.Core.Sections; + +/// +/// Defines a back office section. +/// +public interface ISection { /// - /// Defines a back office section. + /// Gets the alias of the section. /// - public interface ISection - { - /// - /// Gets the alias of the section. - /// - string Alias { get; } + string Alias { get; } - /// - /// Gets the name of the section. - /// - string Name { get; } - } + /// + /// Gets the name of the section. + /// + string Name { get; } } diff --git a/src/Umbraco.Core/Sections/MediaSection.cs b/src/Umbraco.Core/Sections/MediaSection.cs index 8732556a2873..038996298922 100644 --- a/src/Umbraco.Core/Sections/MediaSection.cs +++ b/src/Umbraco.Core/Sections/MediaSection.cs @@ -1,11 +1,10 @@ -namespace Umbraco.Cms.Core.Sections +namespace Umbraco.Cms.Core.Sections; + +/// +/// Defines the back office media section +/// +public class MediaSection : ISection { - /// - /// Defines the back office media section - /// - public class MediaSection : ISection - { - public string Alias => Constants.Applications.Media; - public string Name => "Media"; - } + public string Alias => Constants.Applications.Media; + public string Name => "Media"; } diff --git a/src/Umbraco.Core/Sections/MembersSection.cs b/src/Umbraco.Core/Sections/MembersSection.cs index 1edbf1260411..52952ed088cb 100644 --- a/src/Umbraco.Core/Sections/MembersSection.cs +++ b/src/Umbraco.Core/Sections/MembersSection.cs @@ -1,14 +1,13 @@ -namespace Umbraco.Cms.Core.Sections +namespace Umbraco.Cms.Core.Sections; + +/// +/// Defines the back office members section +/// +public class MembersSection : ISection { - /// - /// Defines the back office members section - /// - public class MembersSection : ISection - { - /// - public string Alias => Constants.Applications.Members; + /// + public string Alias => Constants.Applications.Members; - /// - public string Name => "Members"; - } + /// + public string Name => "Members"; } diff --git a/src/Umbraco.Core/Sections/PackagesSection.cs b/src/Umbraco.Core/Sections/PackagesSection.cs index 4852c11397cb..637aac8b21e5 100644 --- a/src/Umbraco.Core/Sections/PackagesSection.cs +++ b/src/Umbraco.Core/Sections/PackagesSection.cs @@ -1,14 +1,13 @@ -namespace Umbraco.Cms.Core.Sections +namespace Umbraco.Cms.Core.Sections; + +/// +/// Defines the back office packages section +/// +public class PackagesSection : ISection { - /// - /// Defines the back office packages section - /// - public class PackagesSection : ISection - { - /// - public string Alias => Constants.Applications.Packages; + /// + public string Alias => Constants.Applications.Packages; - /// - public string Name => "Packages"; - } + /// + public string Name => "Packages"; } diff --git a/src/Umbraco.Core/Sections/SectionCollection.cs b/src/Umbraco.Core/Sections/SectionCollection.cs index 5ff0157d149c..22a7544c8401 100644 --- a/src/Umbraco.Core/Sections/SectionCollection.cs +++ b/src/Umbraco.Core/Sections/SectionCollection.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Sections +namespace Umbraco.Cms.Core.Sections; + +public class SectionCollection : BuilderCollectionBase { - public class SectionCollection : BuilderCollectionBase + public SectionCollection(Func> items) : base(items) { - public SectionCollection(Func> items) : base(items) - { - } } } diff --git a/src/Umbraco.Core/Sections/SectionCollectionBuilder.cs b/src/Umbraco.Core/Sections/SectionCollectionBuilder.cs index 219d6342617b..73c72a307c21 100644 --- a/src/Umbraco.Core/Sections/SectionCollectionBuilder.cs +++ b/src/Umbraco.Core/Sections/SectionCollectionBuilder.cs @@ -1,24 +1,21 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Manifest; -namespace Umbraco.Cms.Core.Sections +namespace Umbraco.Cms.Core.Sections; + +public class + SectionCollectionBuilder : OrderedCollectionBuilderBase { - public class SectionCollectionBuilder : OrderedCollectionBuilderBase - { - protected override SectionCollectionBuilder This => this; + protected override SectionCollectionBuilder This => this; - protected override IEnumerable CreateItems(IServiceProvider factory) - { - // get the manifest parser just-in-time - injecting it in the ctor would mean that - // simply getting the builder in order to configure the collection, would require - // its dependencies too, and that can create cycles or other oddities - var manifestParser = factory.GetRequiredService(); + protected override IEnumerable CreateItems(IServiceProvider factory) + { + // get the manifest parser just-in-time - injecting it in the ctor would mean that + // simply getting the builder in order to configure the collection, would require + // its dependencies too, and that can create cycles or other oddities + IManifestParser manifestParser = factory.GetRequiredService(); - return base.CreateItems(factory).Concat(manifestParser.CombinedManifest.Sections); - } + return base.CreateItems(factory).Concat(manifestParser.CombinedManifest.Sections); } } diff --git a/src/Umbraco.Core/Sections/SettingsSection.cs b/src/Umbraco.Core/Sections/SettingsSection.cs index bc0a43cae128..8b49e796118a 100644 --- a/src/Umbraco.Core/Sections/SettingsSection.cs +++ b/src/Umbraco.Core/Sections/SettingsSection.cs @@ -1,14 +1,13 @@ -namespace Umbraco.Cms.Core.Sections +namespace Umbraco.Cms.Core.Sections; + +/// +/// Defines the back office settings section +/// +public class SettingsSection : ISection { - /// - /// Defines the back office settings section - /// - public class SettingsSection : ISection - { - /// - public string Alias => Constants.Applications.Settings; + /// + public string Alias => Constants.Applications.Settings; - /// - public string Name => "Settings"; - } + /// + public string Name => "Settings"; } diff --git a/src/Umbraco.Core/Sections/TranslationSection.cs b/src/Umbraco.Core/Sections/TranslationSection.cs index d739757e93d1..a403eb02392a 100644 --- a/src/Umbraco.Core/Sections/TranslationSection.cs +++ b/src/Umbraco.Core/Sections/TranslationSection.cs @@ -1,14 +1,13 @@ -namespace Umbraco.Cms.Core.Sections +namespace Umbraco.Cms.Core.Sections; + +/// +/// Defines the back office translation section +/// +public class TranslationSection : ISection { - /// - /// Defines the back office translation section - /// - public class TranslationSection : ISection - { - /// - public string Alias => Constants.Applications.Translation; + /// + public string Alias => Constants.Applications.Translation; - /// - public string Name => "Translation"; - } + /// + public string Name => "Translation"; } diff --git a/src/Umbraco.Core/Sections/UsersSection.cs b/src/Umbraco.Core/Sections/UsersSection.cs index 6969e9be3de4..257fd91c5110 100644 --- a/src/Umbraco.Core/Sections/UsersSection.cs +++ b/src/Umbraco.Core/Sections/UsersSection.cs @@ -1,14 +1,13 @@ -namespace Umbraco.Cms.Core.Sections +namespace Umbraco.Cms.Core.Sections; + +/// +/// Defines the back office users section +/// +public class UsersSection : ISection { - /// - /// Defines the back office users section - /// - public class UsersSection : ISection - { - /// - public string Alias => Constants.Applications.Users; + /// + public string Alias => Constants.Applications.Users; - /// - public string Name => "Users"; - } + /// + public string Name => "Users"; } diff --git a/src/Umbraco.Core/Security/AuthenticationExtensions.cs b/src/Umbraco.Core/Security/AuthenticationExtensions.cs index 187d44b05d6b..e583c2d6c1c1 100644 --- a/src/Umbraco.Core/Security/AuthenticationExtensions.cs +++ b/src/Umbraco.Core/Security/AuthenticationExtensions.cs @@ -4,32 +4,31 @@ using System.Globalization; using System.Security.Claims; using System.Security.Principal; -using System.Threading; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class AuthenticationExtensions { - public static class AuthenticationExtensions + /// + /// Ensures that the thread culture is set based on the back office user's culture + /// + public static void EnsureCulture(this IIdentity identity) { - /// - /// Ensures that the thread culture is set based on the back office user's culture - /// - public static void EnsureCulture(this IIdentity identity) + CultureInfo culture = GetCulture(identity); + if (!(culture is null)) { - var culture = GetCulture(identity); - if (!(culture is null)) - { - Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = culture; - } + Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = culture; } + } - public static CultureInfo? GetCulture(this IIdentity identity) + public static CultureInfo? GetCulture(this IIdentity identity) + { + if (identity is ClaimsIdentity umbIdentity && umbIdentity.VerifyBackOfficeIdentity(out _) && + umbIdentity.IsAuthenticated && umbIdentity.GetCultureString() is not null) { - if (identity is ClaimsIdentity umbIdentity && umbIdentity.VerifyBackOfficeIdentity(out _) && umbIdentity.IsAuthenticated && umbIdentity.GetCultureString() is not null) - { - return CultureInfo.GetCultureInfo(umbIdentity.GetCultureString()!); - } - - return null; + return CultureInfo.GetCultureInfo(umbIdentity.GetCultureString()!); } + + return null; } } diff --git a/src/Umbraco.Core/Security/BackOfficeExternalLoginProviderErrors.cs b/src/Umbraco.Core/Security/BackOfficeExternalLoginProviderErrors.cs index c79fa8742921..de5bc9edfd37 100644 --- a/src/Umbraco.Core/Security/BackOfficeExternalLoginProviderErrors.cs +++ b/src/Umbraco.Core/Security/BackOfficeExternalLoginProviderErrors.cs @@ -1,22 +1,18 @@ -using System.Collections.Generic; -using System.Linq; +namespace Umbraco.Cms.Core.Security; -namespace Umbraco.Cms.Core.Security +public class BackOfficeExternalLoginProviderErrors { - public class BackOfficeExternalLoginProviderErrors + // required for deserialization + public BackOfficeExternalLoginProviderErrors() { - // required for deserialization - public BackOfficeExternalLoginProviderErrors() - { - } - - public BackOfficeExternalLoginProviderErrors(string? authenticationType, IEnumerable errors) - { - AuthenticationType = authenticationType; - Errors = errors ?? Enumerable.Empty(); - } + } - public string? AuthenticationType { get; set; } - public IEnumerable? Errors { get; set; } + public BackOfficeExternalLoginProviderErrors(string? authenticationType, IEnumerable errors) + { + AuthenticationType = authenticationType; + Errors = errors ?? Enumerable.Empty(); } + + public string? AuthenticationType { get; set; } + public IEnumerable? Errors { get; set; } } diff --git a/src/Umbraco.Core/Security/BackOfficeIdentityOptions.cs b/src/Umbraco.Core/Security/BackOfficeIdentityOptions.cs index e4eacaf9d612..913f4c6dde88 100644 --- a/src/Umbraco.Core/Security/BackOfficeIdentityOptions.cs +++ b/src/Umbraco.Core/Security/BackOfficeIdentityOptions.cs @@ -1,11 +1,10 @@ using Microsoft.AspNetCore.Identity; -namespace Umbraco.Cms.Core.Security +namespace Umbraco.Cms.Core.Security; + +/// +/// Identity options specifically for the back office identity implementation +/// +public class BackOfficeIdentityOptions : IdentityOptions { - /// - /// Identity options specifically for the back office identity implementation - /// - public class BackOfficeIdentityOptions : IdentityOptions - { - } } diff --git a/src/Umbraco.Core/Security/BackOfficeUserPasswordCheckerResult.cs b/src/Umbraco.Core/Security/BackOfficeUserPasswordCheckerResult.cs index a59c1fb435b3..25bfa1a71a2a 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserPasswordCheckerResult.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserPasswordCheckerResult.cs @@ -1,12 +1,11 @@ -namespace Umbraco.Cms.Core.Security +namespace Umbraco.Cms.Core.Security; + +/// +/// The result returned from the IBackOfficeUserPasswordChecker +/// +public enum BackOfficeUserPasswordCheckerResult { - /// - /// The result returned from the IBackOfficeUserPasswordChecker - /// - public enum BackOfficeUserPasswordCheckerResult - { - ValidCredentials, - InvalidCredentials, - FallbackToDefaultChecker - } + ValidCredentials, + InvalidCredentials, + FallbackToDefaultChecker } diff --git a/src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs b/src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs index b9912a391180..312771ed42fb 100644 --- a/src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs +++ b/src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs @@ -1,91 +1,100 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using System.Globalization; -using System.Linq; using System.Security.Claims; using System.Security.Principal; using Umbraco.Cms.Core; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class ClaimsPrincipalExtensions { - public static class ClaimsPrincipalExtensions + public static bool IsBackOfficeAuthenticationType(this ClaimsIdentity claimsIdentity) { - - public static bool IsBackOfficeAuthenticationType(this ClaimsIdentity claimsIdentity) + if (claimsIdentity is null) { - if (claimsIdentity is null) - { - return false; - } - - return claimsIdentity.IsAuthenticated && claimsIdentity.AuthenticationType == Constants.Security.BackOfficeAuthenticationType; + return false; } - /// - /// This will return the current back office identity if the IPrincipal is the correct type and authenticated. - /// - /// - /// - public static ClaimsIdentity? GetUmbracoIdentity(this IPrincipal principal) + + return claimsIdentity.IsAuthenticated && + claimsIdentity.AuthenticationType == Constants.Security.BackOfficeAuthenticationType; + } + + /// + /// This will return the current back office identity if the IPrincipal is the correct type and authenticated. + /// + /// + /// + public static ClaimsIdentity? GetUmbracoIdentity(this IPrincipal principal) + { + //If it's already a UmbracoBackOfficeIdentity + if (principal.Identity is ClaimsIdentity claimsIdentity + && claimsIdentity.IsBackOfficeAuthenticationType() + && claimsIdentity.VerifyBackOfficeIdentity(out ClaimsIdentity backOfficeIdentity)) { - //If it's already a UmbracoBackOfficeIdentity - if (principal.Identity is ClaimsIdentity claimsIdentity - && claimsIdentity.IsBackOfficeAuthenticationType() - && claimsIdentity.VerifyBackOfficeIdentity(out var backOfficeIdentity)) - { - return backOfficeIdentity; - } + return backOfficeIdentity; + } - //Check if there's more than one identity assigned and see if it's a UmbracoBackOfficeIdentity and use that - // We can have assigned more identities if it is a preview request. - if (principal is ClaimsPrincipal claimsPrincipal ) + //Check if there's more than one identity assigned and see if it's a UmbracoBackOfficeIdentity and use that + // We can have assigned more identities if it is a preview request. + if (principal is ClaimsPrincipal claimsPrincipal) + { + ClaimsIdentity identity = + claimsPrincipal.Identities.FirstOrDefault(x => x.IsBackOfficeAuthenticationType()); + if (identity is not null) { - var identity = claimsPrincipal.Identities.FirstOrDefault(x => x.IsBackOfficeAuthenticationType()); - if (identity is not null) + claimsIdentity = identity; + if (claimsIdentity.VerifyBackOfficeIdentity(out backOfficeIdentity)) { - claimsIdentity = identity; - if (claimsIdentity.VerifyBackOfficeIdentity(out backOfficeIdentity)) - { - return backOfficeIdentity; - } + return backOfficeIdentity; } } + } - //Otherwise convert to a UmbracoBackOfficeIdentity if it's auth'd - if (principal.Identity is ClaimsIdentity claimsIdentity2 - && claimsIdentity2.VerifyBackOfficeIdentity(out backOfficeIdentity)) - { - return backOfficeIdentity; - } - return null; + //Otherwise convert to a UmbracoBackOfficeIdentity if it's auth'd + if (principal.Identity is ClaimsIdentity claimsIdentity2 + && claimsIdentity2.VerifyBackOfficeIdentity(out backOfficeIdentity)) + { + return backOfficeIdentity; } - /// - /// Returns the remaining seconds on an auth ticket for the user based on the claim applied to the user durnig authentication - /// - /// - /// - public static double GetRemainingAuthSeconds(this IPrincipal user) => user.GetRemainingAuthSeconds(DateTimeOffset.UtcNow); + return null; + } + + /// + /// Returns the remaining seconds on an auth ticket for the user based on the claim applied to the user durnig + /// authentication + /// + /// + /// + public static double GetRemainingAuthSeconds(this IPrincipal user) => + user.GetRemainingAuthSeconds(DateTimeOffset.UtcNow); - /// - /// Returns the remaining seconds on an auth ticket for the user based on the claim applied to the user durnig authentication - /// - /// - /// - /// - public static double GetRemainingAuthSeconds(this IPrincipal user, DateTimeOffset now) + /// + /// Returns the remaining seconds on an auth ticket for the user based on the claim applied to the user durnig + /// authentication + /// + /// + /// + /// + public static double GetRemainingAuthSeconds(this IPrincipal user, DateTimeOffset now) + { + var claimsPrincipal = user as ClaimsPrincipal; + if (claimsPrincipal == null) { - var claimsPrincipal = user as ClaimsPrincipal; - if (claimsPrincipal == null) return 0; + return 0; + } - var ticketExpires = claimsPrincipal.FindFirst(Constants.Security.TicketExpiresClaimType)?.Value; - if (ticketExpires.IsNullOrWhiteSpace()) return 0; + var ticketExpires = claimsPrincipal.FindFirst(Constants.Security.TicketExpiresClaimType)?.Value; + if (ticketExpires.IsNullOrWhiteSpace()) + { + return 0; + } - var utcExpired = DateTimeOffset.Parse(ticketExpires!, null, DateTimeStyles.RoundtripKind); + var utcExpired = DateTimeOffset.Parse(ticketExpires!, null, DateTimeStyles.RoundtripKind); - var secondsRemaining = utcExpired.Subtract(now).TotalSeconds; - return secondsRemaining; - } + var secondsRemaining = utcExpired.Subtract(now).TotalSeconds; + return secondsRemaining; } } diff --git a/src/Umbraco.Core/Security/ContentPermissions.cs b/src/Umbraco.Core/Security/ContentPermissions.cs index 73f9f4ccef49..28e70e8ce2ea 100644 --- a/src/Umbraco.Core/Security/ContentPermissions.cs +++ b/src/Umbraco.Core/Security/ContentPermissions.cs @@ -1,290 +1,356 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; +using System.Globalization; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.Security +namespace Umbraco.Cms.Core.Security; + +/// +/// Checks user access to content +/// +public class ContentPermissions { + public enum ContentAccess + { + Granted, + Denied, + NotFound + } - /// - /// Checks user access to content - /// - public class ContentPermissions + private readonly AppCaches _appCaches; + private readonly IContentService _contentService; + private readonly IEntityService _entityService; + private readonly IUserService _userService; + + public ContentPermissions( + IUserService userService, + IContentService contentService, + IEntityService entityService, + AppCaches appCaches) { - private readonly IUserService _userService; - private readonly IContentService _contentService; - private readonly IEntityService _entityService; - private readonly AppCaches _appCaches; + _userService = userService; + _contentService = contentService; + _entityService = entityService; + _appCaches = appCaches; + } - public enum ContentAccess + public ContentAccess CheckPermissions( + IContent content, + IUser user, + char permissionToCheck) => CheckPermissions(content, user, new[] {permissionToCheck}); + + public ContentAccess CheckPermissions( + IContent? content, + IUser? user, + IReadOnlyList permissionsToCheck) + { + if (user == null) { - Granted, - Denied, - NotFound + throw new ArgumentNullException(nameof(user)); } - public ContentPermissions( - IUserService userService, - IContentService contentService, - IEntityService entityService, - AppCaches appCaches) + if (content == null) { - _userService = userService; - _contentService = contentService; - _entityService = entityService; - _appCaches = appCaches; + return ContentAccess.NotFound; } - public ContentAccess CheckPermissions( - IContent content, - IUser user, - char permissionToCheck) => CheckPermissions(content, user, new[] { permissionToCheck }); + var hasPathAccess = user.HasPathAccess(content, _entityService, _appCaches); - public ContentAccess CheckPermissions( - IContent? content, - IUser? user, - IReadOnlyList permissionsToCheck) + if (hasPathAccess == false) { - if (user == null) throw new ArgumentNullException(nameof(user)); + return ContentAccess.Denied; + } - if (content == null) return ContentAccess.NotFound; + if (permissionsToCheck == null || permissionsToCheck.Count == 0) + { + return ContentAccess.Granted; + } - var hasPathAccess = user.HasPathAccess(content, _entityService, _appCaches); + //get the implicit/inherited permissions for the user for this path + return CheckPermissionsPath(content.Path, user, permissionsToCheck) + ? ContentAccess.Granted + : ContentAccess.Denied; + } - if (hasPathAccess == false) - return ContentAccess.Denied; + public ContentAccess CheckPermissions( + IUmbracoEntity entity, + IUser? user, + char permissionToCheck) => CheckPermissions(entity, user, new[] {permissionToCheck}); - if (permissionsToCheck == null || permissionsToCheck.Count == 0) - return ContentAccess.Granted; + public ContentAccess CheckPermissions( + IUmbracoEntity entity, + IUser? user, + IReadOnlyList permissionsToCheck) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } - //get the implicit/inherited permissions for the user for this path - return CheckPermissionsPath(content.Path, user, permissionsToCheck) - ? ContentAccess.Granted - : ContentAccess.Denied; + if (entity == null) + { + return ContentAccess.NotFound; } - public ContentAccess CheckPermissions( - IUmbracoEntity entity, - IUser? user, - char permissionToCheck) => CheckPermissions(entity, user, new[] { permissionToCheck }); + var hasPathAccess = user.HasContentPathAccess(entity, _entityService, _appCaches); - public ContentAccess CheckPermissions( - IUmbracoEntity entity, - IUser? user, - IReadOnlyList permissionsToCheck) + if (hasPathAccess == false) { - if (user == null) throw new ArgumentNullException(nameof(user)); + return ContentAccess.Denied; + } - if (entity == null) return ContentAccess.NotFound; + if (permissionsToCheck == null || permissionsToCheck.Count == 0) + { + return ContentAccess.Granted; + } - var hasPathAccess = user.HasContentPathAccess(entity, _entityService, _appCaches); + //get the implicit/inherited permissions for the user for this path + return CheckPermissionsPath(entity.Path, user, permissionsToCheck) + ? ContentAccess.Granted + : ContentAccess.Denied; + } - if (hasPathAccess == false) - return ContentAccess.Denied; + /// + /// Checks if the user has access to the specified node and permissions set + /// + /// + /// + /// + /// + /// The item resolved if one was found for the id + /// + /// + public ContentAccess CheckPermissions( + int nodeId, + IUser user, + out IUmbracoEntity? entity, + IReadOnlyList? permissionsToCheck = null) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } - if (permissionsToCheck == null || permissionsToCheck.Count == 0) - return ContentAccess.Granted; + if (permissionsToCheck == null) + { + permissionsToCheck = Array.Empty(); + } - //get the implicit/inherited permissions for the user for this path - return CheckPermissionsPath(entity.Path, user, permissionsToCheck) - ? ContentAccess.Granted - : ContentAccess.Denied; + bool? hasPathAccess = null; + entity = null; + + if (nodeId == Constants.System.Root) + { + hasPathAccess = user.HasContentRootAccess(_entityService, _appCaches); + } + else if (nodeId == Constants.System.RecycleBinContent) + { + hasPathAccess = user.HasContentBinAccess(_entityService, _appCaches); } - /// - /// Checks if the user has access to the specified node and permissions set - /// - /// - /// - /// - /// - /// The item resolved if one was found for the id - /// - /// - public ContentAccess CheckPermissions( - int nodeId, - IUser user, - out IUmbracoEntity? entity, - IReadOnlyList? permissionsToCheck = null) + if (hasPathAccess.HasValue) { - if (user == null) throw new ArgumentNullException(nameof(user)); + return hasPathAccess.Value ? ContentAccess.Granted : ContentAccess.Denied; + } - if (permissionsToCheck == null) - { - permissionsToCheck = Array.Empty(); - } + entity = _entityService.Get(nodeId, UmbracoObjectTypes.Document); + if (entity == null) + { + return ContentAccess.NotFound; + } - bool? hasPathAccess = null; - entity = null; - - if (nodeId == Constants.System.Root) - hasPathAccess = user.HasContentRootAccess(_entityService, _appCaches); - else if (nodeId == Constants.System.RecycleBinContent) - hasPathAccess = user.HasContentBinAccess(_entityService, _appCaches); - - if (hasPathAccess.HasValue) - return hasPathAccess.Value ? ContentAccess.Granted : ContentAccess.Denied; - - entity = _entityService.Get(nodeId, UmbracoObjectTypes.Document); - if (entity == null) return ContentAccess.NotFound; - hasPathAccess = user.HasContentPathAccess(entity, _entityService, _appCaches); - - if (hasPathAccess == false) - return ContentAccess.Denied; - - if (permissionsToCheck == null || permissionsToCheck.Count == 0) - return ContentAccess.Granted; - - //get the implicit/inherited permissions for the user for this path - return CheckPermissionsPath(entity.Path, user, permissionsToCheck) - ? ContentAccess.Granted - : ContentAccess.Denied; - } - - /// - /// Checks if the user has access to the specified node and permissions set - /// - /// - /// - /// - /// - /// - /// The item resolved if one was found for the id - /// - /// - public ContentAccess CheckPermissions( - int nodeId, - IUser? user, - out IContent? contentItem, - IReadOnlyList? permissionsToCheck = null) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - - if (permissionsToCheck == null) - { - permissionsToCheck = Array.Empty(); - } + hasPathAccess = user.HasContentPathAccess(entity, _entityService, _appCaches); - bool? hasPathAccess = null; - contentItem = null; + if (hasPathAccess == false) + { + return ContentAccess.Denied; + } - if (nodeId == Constants.System.Root) - hasPathAccess = user.HasContentRootAccess(_entityService, _appCaches); - else if (nodeId == Constants.System.RecycleBinContent) - hasPathAccess = user.HasContentBinAccess(_entityService, _appCaches); + if (permissionsToCheck == null || permissionsToCheck.Count == 0) + { + return ContentAccess.Granted; + } - if (hasPathAccess.HasValue) - return hasPathAccess.Value ? ContentAccess.Granted : ContentAccess.Denied; + //get the implicit/inherited permissions for the user for this path + return CheckPermissionsPath(entity.Path, user, permissionsToCheck) + ? ContentAccess.Granted + : ContentAccess.Denied; + } - contentItem = _contentService.GetById(nodeId); - if (contentItem == null) return ContentAccess.NotFound; - hasPathAccess = user.HasPathAccess(contentItem, _entityService, _appCaches); + /// + /// Checks if the user has access to the specified node and permissions set + /// + /// + /// + /// + /// + /// + /// The item resolved if one was found for the id + /// + /// + public ContentAccess CheckPermissions( + int nodeId, + IUser? user, + out IContent? contentItem, + IReadOnlyList? permissionsToCheck = null) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } - if (hasPathAccess == false) - return ContentAccess.Denied; + if (permissionsToCheck == null) + { + permissionsToCheck = Array.Empty(); + } - if (permissionsToCheck == null || permissionsToCheck.Count == 0) - return ContentAccess.Granted; + bool? hasPathAccess = null; + contentItem = null; - //get the implicit/inherited permissions for the user for this path - return CheckPermissionsPath(contentItem.Path, user, permissionsToCheck) - ? ContentAccess.Granted - : ContentAccess.Denied; + if (nodeId == Constants.System.Root) + { + hasPathAccess = user.HasContentRootAccess(_entityService, _appCaches); + } + else if (nodeId == Constants.System.RecycleBinContent) + { + hasPathAccess = user.HasContentBinAccess(_entityService, _appCaches); } - private bool CheckPermissionsPath(string? path, IUser user, IReadOnlyList? permissionsToCheck = null) + if (hasPathAccess.HasValue) { - if (permissionsToCheck == null) - { - permissionsToCheck = Array.Empty(); - } + return hasPathAccess.Value ? ContentAccess.Granted : ContentAccess.Denied; + } - //get the implicit/inherited permissions for the user for this path, - //if there is no content item for this id, than just use the id as the path (i.e. -1 or -20) - var permission = _userService.GetPermissionsForPath(user, path); + contentItem = _contentService.GetById(nodeId); + if (contentItem == null) + { + return ContentAccess.NotFound; + } - var allowed = true; - foreach (var p in permissionsToCheck) - { - if (permission == null - || permission.GetAllPermissions().Contains(p.ToString(CultureInfo.InvariantCulture)) == false) - { - allowed = false; - } - } - return allowed; + hasPathAccess = user.HasPathAccess(contentItem, _entityService, _appCaches); + + if (hasPathAccess == false) + { + return ContentAccess.Denied; } - public static bool HasPathAccess(string? path, int[]? startNodeIds, int recycleBinId) + if (permissionsToCheck == null || permissionsToCheck.Count == 0) { - if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(path)); + return ContentAccess.Granted; + } - // check for no access - if (startNodeIds is null || startNodeIds.Length == 0) - return false; + //get the implicit/inherited permissions for the user for this path + return CheckPermissionsPath(contentItem.Path, user, permissionsToCheck) + ? ContentAccess.Granted + : ContentAccess.Denied; + } - // check for root access - if (startNodeIds.Contains(Constants.System.Root)) - return true; + private bool CheckPermissionsPath(string? path, IUser user, IReadOnlyList? permissionsToCheck = null) + { + if (permissionsToCheck == null) + { + permissionsToCheck = Array.Empty(); + } - var formattedPath = string.Concat(",", path, ","); + //get the implicit/inherited permissions for the user for this path, + //if there is no content item for this id, than just use the id as the path (i.e. -1 or -20) + EntityPermissionSet permission = _userService.GetPermissionsForPath(user, path); - // only users with root access have access to the recycle bin, - // if the above check didn't pass then access is denied - if (formattedPath.Contains(string.Concat(",", recycleBinId.ToString(CultureInfo.InvariantCulture), ","))) - return false; + var allowed = true; + foreach (var p in permissionsToCheck) + { + if (permission == null + || permission.GetAllPermissions().Contains(p.ToString(CultureInfo.InvariantCulture)) == false) + { + allowed = false; + } + } - // check for a start node in the path - return startNodeIds.Any(x => formattedPath.Contains(string.Concat(",", x.ToString(CultureInfo.InvariantCulture), ","))); + return allowed; + } + + public static bool HasPathAccess(string? path, int[]? startNodeIds, int recycleBinId) + { + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(path)); } - public static bool IsInBranchOfStartNode(string path, int[]? startNodeIds, string[]? startNodePaths, out bool hasPathAccess) + // check for no access + if (startNodeIds is null || startNodeIds.Length == 0) { - if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(path)); + return false; + } - hasPathAccess = false; + // check for root access + if (startNodeIds.Contains(Constants.System.Root)) + { + return true; + } - // check for no access - if (startNodeIds?.Length == 0) - return false; + var formattedPath = string.Concat(",", path, ","); - // check for root access - if (startNodeIds?.Contains(Constants.System.Root) ?? false) - { - hasPathAccess = true; - return true; - } + // only users with root access have access to the recycle bin, + // if the above check didn't pass then access is denied + if (formattedPath.Contains(string.Concat(",", recycleBinId.ToString(CultureInfo.InvariantCulture), ","))) + { + return false; + } - //is it self? - var self = startNodePaths?.Any(x => x == path) ?? false; - if (self) - { - hasPathAccess = true; - return true; - } + // check for a start node in the path + return startNodeIds.Any(x => + formattedPath.Contains(string.Concat(",", x.ToString(CultureInfo.InvariantCulture), ","))); + } - //is it ancestor? - var ancestor = startNodePaths?.Any(x => x.StartsWith(path)) ?? false; - if (ancestor) - { - //hasPathAccess = false; - return true; - } + public static bool IsInBranchOfStartNode(string path, int[]? startNodeIds, string[]? startNodePaths, + out bool hasPathAccess) + { + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(path)); + } - //is it descendant? - var descendant = startNodePaths?.Any(x => path.StartsWith(x)) ?? false; - if (descendant) - { - hasPathAccess = true; - return true; - } + hasPathAccess = false; + // check for no access + if (startNodeIds?.Length == 0) + { return false; } + + // check for root access + if (startNodeIds?.Contains(Constants.System.Root) ?? false) + { + hasPathAccess = true; + return true; + } + + //is it self? + var self = startNodePaths?.Any(x => x == path) ?? false; + if (self) + { + hasPathAccess = true; + return true; + } + + //is it ancestor? + var ancestor = startNodePaths?.Any(x => x.StartsWith(path)) ?? false; + if (ancestor) + { + //hasPathAccess = false; + return true; + } + + //is it descendant? + var descendant = startNodePaths?.Any(x => path.StartsWith(x)) ?? false; + if (descendant) + { + hasPathAccess = true; + return true; + } + + return false; } } diff --git a/src/Umbraco.Core/Security/ExternalLogin.cs b/src/Umbraco.Core/Security/ExternalLogin.cs index 631fe52b28d2..6eb3defc4509 100644 --- a/src/Umbraco.Core/Security/ExternalLogin.cs +++ b/src/Umbraco.Core/Security/ExternalLogin.cs @@ -1,28 +1,24 @@ -using System; +namespace Umbraco.Cms.Core.Security; -namespace Umbraco.Cms.Core.Security +/// +public class ExternalLogin : IExternalLogin { - - /// - public class ExternalLogin : IExternalLogin + /// + /// Initializes a new instance of the class. + /// + public ExternalLogin(string loginProvider, string providerKey, string? userData = null) { - /// - /// Initializes a new instance of the class. - /// - public ExternalLogin(string loginProvider, string providerKey, string? userData = null) - { - LoginProvider = loginProvider ?? throw new ArgumentNullException(nameof(loginProvider)); - ProviderKey = providerKey ?? throw new ArgumentNullException(nameof(providerKey)); - UserData = userData; - } + LoginProvider = loginProvider ?? throw new ArgumentNullException(nameof(loginProvider)); + ProviderKey = providerKey ?? throw new ArgumentNullException(nameof(providerKey)); + UserData = userData; + } - /// - public string LoginProvider { get; } + /// + public string LoginProvider { get; } - /// - public string ProviderKey { get; } + /// + public string ProviderKey { get; } - /// - public string? UserData { get; } - } + /// + public string? UserData { get; } } diff --git a/src/Umbraco.Core/Security/ExternalLoginToken.cs b/src/Umbraco.Core/Security/ExternalLoginToken.cs index 85089ddba625..df986d176ff5 100644 --- a/src/Umbraco.Core/Security/ExternalLoginToken.cs +++ b/src/Umbraco.Core/Security/ExternalLoginToken.cs @@ -1,27 +1,24 @@ -using System; +namespace Umbraco.Cms.Core.Security; -namespace Umbraco.Cms.Core.Security +/// +public class ExternalLoginToken : IExternalLoginToken { - /// - public class ExternalLoginToken : IExternalLoginToken + /// + /// Initializes a new instance of the class. + /// + public ExternalLoginToken(string loginProvider, string name, string value) { - /// - /// Initializes a new instance of the class. - /// - public ExternalLoginToken(string loginProvider, string name, string value) - { - LoginProvider = loginProvider ?? throw new ArgumentNullException(nameof(loginProvider)); - Name = name ?? throw new ArgumentNullException(nameof(name)); - Value = value ?? throw new ArgumentNullException(nameof(value)); - } + LoginProvider = loginProvider ?? throw new ArgumentNullException(nameof(loginProvider)); + Name = name ?? throw new ArgumentNullException(nameof(name)); + Value = value ?? throw new ArgumentNullException(nameof(value)); + } - /// - public string LoginProvider { get; } + /// + public string LoginProvider { get; } - /// - public string Name { get; } + /// + public string Name { get; } - /// - public string Value { get; } - } + /// + public string Value { get; } } diff --git a/src/Umbraco.Core/Security/IBackofficeSecurity.cs b/src/Umbraco.Core/Security/IBackofficeSecurity.cs index 3b3c956cd61b..18c8f30a4584 100644 --- a/src/Umbraco.Core/Security/IBackofficeSecurity.cs +++ b/src/Umbraco.Core/Security/IBackofficeSecurity.cs @@ -1,44 +1,43 @@ using Umbraco.Cms.Core.Models.Membership; -namespace Umbraco.Cms.Core.Security +namespace Umbraco.Cms.Core.Security; + +public interface IBackOfficeSecurity { - public interface IBackOfficeSecurity - { - /// - /// Gets the current user. - /// - /// The current user that has been authenticated for the request. - /// If authentication hasn't taken place this will be null. - // TODO: This is used a lot but most of it can be refactored to not use this at all since the IUser instance isn't - // needed in most cases. Where an IUser is required this could be an ext method on the ClaimsIdentity/ClaimsPrincipal that passes in - // an IUserService, like HttpContext.User.GetUmbracoUser(_userService); - // This one isn't as easy to remove as the others below. - IUser? CurrentUser { get; } + /// + /// Gets the current user. + /// + /// The current user that has been authenticated for the request. + /// If authentication hasn't taken place this will be null. + // TODO: This is used a lot but most of it can be refactored to not use this at all since the IUser instance isn't + // needed in most cases. Where an IUser is required this could be an ext method on the ClaimsIdentity/ClaimsPrincipal that passes in + // an IUserService, like HttpContext.User.GetUmbracoUser(_userService); + // This one isn't as easy to remove as the others below. + IUser? CurrentUser { get; } - /// - /// Gets the current user's id. - /// - /// The current user's Id that has been authenticated for the request. - /// If authentication hasn't taken place this will be unsuccessful. - // TODO: This should just be an extension method on ClaimsIdentity - Attempt GetUserId(); + /// + /// Gets the current user's id. + /// + /// The current user's Id that has been authenticated for the request. + /// If authentication hasn't taken place this will be unsuccessful. + // TODO: This should just be an extension method on ClaimsIdentity + Attempt GetUserId(); - /// - /// Checks if the specified user as access to the app - /// - /// - /// - /// - /// If authentication hasn't taken place this will be unsuccessful. - // TODO: Should be part of IBackOfficeUserManager - bool UserHasSectionAccess(string section, IUser user); + /// + /// Checks if the specified user as access to the app + /// + /// + /// + /// + /// If authentication hasn't taken place this will be unsuccessful. + // TODO: Should be part of IBackOfficeUserManager + bool UserHasSectionAccess(string section, IUser user); - /// - /// Ensures that a back office user is logged in - /// - /// - /// This does not force authentication, that must be done before calls to this are made. - // TODO: Should be removed, this should not be necessary - bool IsAuthenticated(); - } + /// + /// Ensures that a back office user is logged in + /// + /// + /// This does not force authentication, that must be done before calls to this are made. + // TODO: Should be removed, this should not be necessary + bool IsAuthenticated(); } diff --git a/src/Umbraco.Core/Security/IBackofficeSecurityAccessor.cs b/src/Umbraco.Core/Security/IBackofficeSecurityAccessor.cs index 7ef33ecdc612..11ed86971ee5 100644 --- a/src/Umbraco.Core/Security/IBackofficeSecurityAccessor.cs +++ b/src/Umbraco.Core/Security/IBackofficeSecurityAccessor.cs @@ -1,7 +1,6 @@ -namespace Umbraco.Cms.Core.Security +namespace Umbraco.Cms.Core.Security; + +public interface IBackOfficeSecurityAccessor { - public interface IBackOfficeSecurityAccessor - { - IBackOfficeSecurity? BackOfficeSecurity { get; } - } + IBackOfficeSecurity? BackOfficeSecurity { get; } } diff --git a/src/Umbraco.Core/Security/IExternalLogin.cs b/src/Umbraco.Core/Security/IExternalLogin.cs index 0c09cecfc0b0..225b0390d38e 100644 --- a/src/Umbraco.Core/Security/IExternalLogin.cs +++ b/src/Umbraco.Core/Security/IExternalLogin.cs @@ -1,23 +1,22 @@ -namespace Umbraco.Cms.Core.Security +namespace Umbraco.Cms.Core.Security; + +/// +/// Used to persist external login data for a user +/// +public interface IExternalLogin { /// - /// Used to persist external login data for a user + /// Gets the login provider /// - public interface IExternalLogin - { - /// - /// Gets the login provider - /// - string LoginProvider { get; } + string LoginProvider { get; } - /// - /// Gets the provider key - /// - string ProviderKey { get; } + /// + /// Gets the provider key + /// + string ProviderKey { get; } - /// - /// Gets the user data - /// - string? UserData { get; } - } + /// + /// Gets the user data + /// + string? UserData { get; } } diff --git a/src/Umbraco.Core/Security/IExternalLoginToken.cs b/src/Umbraco.Core/Security/IExternalLoginToken.cs index b3fd4b64b2e8..a5dba5a17e5c 100644 --- a/src/Umbraco.Core/Security/IExternalLoginToken.cs +++ b/src/Umbraco.Core/Security/IExternalLoginToken.cs @@ -1,23 +1,22 @@ -namespace Umbraco.Cms.Core.Security +namespace Umbraco.Cms.Core.Security; + +/// +/// Used to persist an external login token for a user +/// +public interface IExternalLoginToken { /// - /// Used to persist an external login token for a user + /// Gets the login provider /// - public interface IExternalLoginToken - { - /// - /// Gets the login provider - /// - string LoginProvider { get; } + string LoginProvider { get; } - /// - /// Gets the name of the token - /// - string Name { get; } + /// + /// Gets the name of the token + /// + string Name { get; } - /// - /// Gets the value of the token - /// - string Value { get; } - } + /// + /// Gets the value of the token + /// + string Value { get; } } diff --git a/src/Umbraco.Core/Security/IHtmlSanitizer.cs b/src/Umbraco.Core/Security/IHtmlSanitizer.cs index 9bcfe405ddb3..3faf3cfd4d2a 100644 --- a/src/Umbraco.Core/Security/IHtmlSanitizer.cs +++ b/src/Umbraco.Core/Security/IHtmlSanitizer.cs @@ -1,12 +1,11 @@ -namespace Umbraco.Cms.Core.Security +namespace Umbraco.Cms.Core.Security; + +public interface IHtmlSanitizer { - public interface IHtmlSanitizer - { - /// - /// Sanitizes HTML - /// - /// HTML to be sanitized - /// Sanitized HTML - string Sanitize(string html); - } + /// + /// Sanitizes HTML + /// + /// HTML to be sanitized + /// Sanitized HTML + string Sanitize(string html); } diff --git a/src/Umbraco.Core/Security/IIdentityUserLogin.cs b/src/Umbraco.Core/Security/IIdentityUserLogin.cs index c9eb64ceb3eb..51035b724c5c 100644 --- a/src/Umbraco.Core/Security/IIdentityUserLogin.cs +++ b/src/Umbraco.Core/Security/IIdentityUserLogin.cs @@ -1,31 +1,30 @@ using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Security +namespace Umbraco.Cms.Core.Security; + +/// +/// An external login provider linked to a user +/// +/// The PK type for the user +public interface IIdentityUserLogin : IEntity, IRememberBeingDirty { /// - /// An external login provider linked to a user + /// Gets or sets the login provider for the login (i.e. Facebook, Google) /// - /// The PK type for the user - public interface IIdentityUserLogin : IEntity, IRememberBeingDirty - { - /// - /// Gets or sets the login provider for the login (i.e. Facebook, Google) - /// - string LoginProvider { get; set; } + string LoginProvider { get; set; } - /// - /// Gets or sets key representing the login for the provider - /// - string ProviderKey { get; set; } + /// + /// Gets or sets key representing the login for the provider + /// + string ProviderKey { get; set; } - /// - /// Gets or sets user or member key (Guid) for the user/member who owns this login - /// - string UserId { get; set; } // TODO: This should be able to be used by both users and members + /// + /// Gets or sets user or member key (Guid) for the user/member who owns this login + /// + string UserId { get; set; } // TODO: This should be able to be used by both users and members - /// - /// Gets or sets any arbitrary data for the user and external provider - /// - string? UserData { get; set; } - } + /// + /// Gets or sets any arbitrary data for the user and external provider + /// + string? UserData { get; set; } } diff --git a/src/Umbraco.Core/Security/IIdentityUserToken.cs b/src/Umbraco.Core/Security/IIdentityUserToken.cs index 0e7f22d72f64..f2e17a19afe2 100644 --- a/src/Umbraco.Core/Security/IIdentityUserToken.cs +++ b/src/Umbraco.Core/Security/IIdentityUserToken.cs @@ -1,30 +1,29 @@ using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Security +namespace Umbraco.Cms.Core.Security; + +/// +/// An external login provider token +/// +public interface IIdentityUserToken : IEntity { /// - /// An external login provider token + /// Gets or sets user Id for the user who owns this token /// - public interface IIdentityUserToken : IEntity - { - /// - /// Gets or sets user Id for the user who owns this token - /// - string? UserId { get; set; } + string? UserId { get; set; } - /// - /// Gets or sets the login provider for the login (i.e. Facebook, Google) - /// - string LoginProvider { get; set; } + /// + /// Gets or sets the login provider for the login (i.e. Facebook, Google) + /// + string LoginProvider { get; set; } - /// - /// Gets or sets the token name - /// - string Name { get; set; } + /// + /// Gets or sets the token name + /// + string Name { get; set; } - /// - /// Gets or set the token value - /// - string Value { get; set; } - } + /// + /// Gets or set the token value + /// + string Value { get; set; } } diff --git a/src/Umbraco.Core/Security/IPasswordHasher.cs b/src/Umbraco.Core/Security/IPasswordHasher.cs index c0d436048e2f..5f3345ea73e0 100644 --- a/src/Umbraco.Core/Security/IPasswordHasher.cs +++ b/src/Umbraco.Core/Security/IPasswordHasher.cs @@ -1,12 +1,11 @@ -namespace Umbraco.Cms.Core.Security +namespace Umbraco.Cms.Core.Security; + +public interface IPasswordHasher { - public interface IPasswordHasher - { - /// - /// Hashes a password - /// - /// The password. - /// The password hashed. - string HashPassword(string password); - } + /// + /// Hashes a password + /// + /// The password. + /// The password hashed. + string HashPassword(string password); } diff --git a/src/Umbraco.Core/Security/IPublicAccessChecker.cs b/src/Umbraco.Core/Security/IPublicAccessChecker.cs index 6ec9eb7ade61..d830d757f172 100644 --- a/src/Umbraco.Core/Security/IPublicAccessChecker.cs +++ b/src/Umbraco.Core/Security/IPublicAccessChecker.cs @@ -1,9 +1,6 @@ -using System.Threading.Tasks; +namespace Umbraco.Cms.Core.Security; -namespace Umbraco.Cms.Core.Security +public interface IPublicAccessChecker { - public interface IPublicAccessChecker - { - Task HasMemberAccessToContentAsync(int publishedContentId); - } + Task HasMemberAccessToContentAsync(int publishedContentId); } diff --git a/src/Umbraco.Core/Security/ITwoFactorProvider.cs b/src/Umbraco.Core/Security/ITwoFactorProvider.cs index f0da6c314a85..62c664ba4f57 100644 --- a/src/Umbraco.Core/Security/ITwoFactorProvider.cs +++ b/src/Umbraco.Core/Security/ITwoFactorProvider.cs @@ -1,22 +1,15 @@ -using System; -using System.Threading.Tasks; +namespace Umbraco.Cms.Core.Security; -namespace Umbraco.Cms.Core.Security +public interface ITwoFactorProvider { - public interface ITwoFactorProvider - { - string ProviderName { get; } + string ProviderName { get; } - Task GetSetupDataAsync(Guid userOrMemberKey, string secret); - - bool ValidateTwoFactorPIN(string secret, string token); - - /// - /// - /// - /// Called to confirm the setup of two factor on the user. - bool ValidateTwoFactorSetup(string secret, string token); - } + Task GetSetupDataAsync(Guid userOrMemberKey, string secret); + bool ValidateTwoFactorPIN(string secret, string token); + /// + /// + /// Called to confirm the setup of two factor on the user. + bool ValidateTwoFactorSetup(string secret, string token); } diff --git a/src/Umbraco.Core/Security/IdentityAuditEventArgs.cs b/src/Umbraco.Core/Security/IdentityAuditEventArgs.cs index 225d46b26835..dccafafdcdaa 100644 --- a/src/Umbraco.Core/Security/IdentityAuditEventArgs.cs +++ b/src/Umbraco.Core/Security/IdentityAuditEventArgs.cs @@ -1,88 +1,84 @@ -using System; +namespace Umbraco.Cms.Core.Security; -namespace Umbraco.Cms.Core.Security +/// +/// This class is used by events raised from the BackofficeUserManager +/// +public class IdentityAuditEventArgs : EventArgs { - /// - /// This class is used by events raised from the BackofficeUserManager + /// Default constructor /// - public class IdentityAuditEventArgs : EventArgs + /// + /// + /// + /// + /// + public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string performingUser, string comment, + string affectedUser, string affectedUsername) { - /// - /// The action that got triggered from the audit event - /// - public AuditEvent Action { get; private set; } - - /// - /// Current date/time in UTC format - /// - public DateTime DateTimeUtc { get; private set; } - - /// - /// The source IP address of the user performing the action - /// - public string IpAddress { get; private set; } + DateTimeUtc = DateTime.UtcNow; + Action = action; + IpAddress = ipAddress; + Comment = comment; + PerformingUser = performingUser; + AffectedUsername = affectedUsername; + AffectedUser = affectedUser; + } - /// - /// The user affected by the event raised - /// - public string AffectedUser { get; private set; } + public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string performingUser, string comment, + string affectedUsername) + : this(action, ipAddress, performingUser, comment, Constants.Security.SuperUserIdAsString, affectedUsername) + { + } - /// - /// If a user is performing an action on a different user, then this will be set. Otherwise it will be -1 - /// - public string PerformingUser { get; private set; } + /// + /// The action that got triggered from the audit event + /// + public AuditEvent Action { get; } - /// - /// An optional comment about the action being logged - /// - public string Comment { get; private set; } + /// + /// Current date/time in UTC format + /// + public DateTime DateTimeUtc { get; } - /// - /// This property is always empty except in the LoginFailed event for an unknown user trying to login - /// - public string AffectedUsername { get; private set; } + /// + /// The source IP address of the user performing the action + /// + public string IpAddress { get; } + /// + /// The user affected by the event raised + /// + public string AffectedUser { get; } - /// - /// Default constructor - /// - /// - /// - /// - /// - /// - public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string performingUser, string comment, string affectedUser, string affectedUsername) - { - DateTimeUtc = DateTime.UtcNow; - Action = action; - IpAddress = ipAddress; - Comment = comment; - PerformingUser = performingUser; - AffectedUsername = affectedUsername; - AffectedUser = affectedUser; - } + /// + /// If a user is performing an action on a different user, then this will be set. Otherwise it will be -1 + /// + public string PerformingUser { get; } - public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string performingUser, string comment, string affectedUsername) - : this(action, ipAddress, performingUser, comment, Constants.Security.SuperUserIdAsString, affectedUsername) - { - } + /// + /// An optional comment about the action being logged + /// + public string Comment { get; } - } + /// + /// This property is always empty except in the LoginFailed event for an unknown user trying to login + /// + public string AffectedUsername { get; } +} - public enum AuditEvent - { - AccountLocked, - AccountUnlocked, - ForgotPasswordRequested, - ForgotPasswordChangedSuccess, - LoginFailed, - LoginRequiresVerification, - LoginSucces, - LogoutSuccess, - PasswordChanged, - PasswordReset, - ResetAccessFailedCount, - SendingUserInvite - } +public enum AuditEvent +{ + AccountLocked, + AccountUnlocked, + ForgotPasswordRequested, + ForgotPasswordChangedSuccess, + LoginFailed, + LoginRequiresVerification, + LoginSucces, + LogoutSuccess, + PasswordChanged, + PasswordReset, + ResetAccessFailedCount, + SendingUserInvite } diff --git a/src/Umbraco.Core/Security/IdentityUserLogin.cs b/src/Umbraco.Core/Security/IdentityUserLogin.cs index 402660ead994..ca821811cc69 100644 --- a/src/Umbraco.Core/Security/IdentityUserLogin.cs +++ b/src/Umbraco.Core/Security/IdentityUserLogin.cs @@ -1,46 +1,43 @@ -using System; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Security -{ +namespace Umbraco.Cms.Core.Security; +/// +/// Entity type for a user's login (i.e. Facebook, Google) +/// +public class IdentityUserLogin : EntityBase, IIdentityUserLogin +{ /// - /// Entity type for a user's login (i.e. Facebook, Google) + /// Initializes a new instance of the class. /// - public class IdentityUserLogin : EntityBase, IIdentityUserLogin + public IdentityUserLogin(string loginProvider, string providerKey, string userId) { - /// - /// Initializes a new instance of the class. - /// - public IdentityUserLogin(string loginProvider, string providerKey, string userId) - { - LoginProvider = loginProvider; - ProviderKey = providerKey; - UserId = userId; - } + LoginProvider = loginProvider; + ProviderKey = providerKey; + UserId = userId; + } - /// - /// Initializes a new instance of the class. - /// - public IdentityUserLogin(int id, string loginProvider, string providerKey, string userId, DateTime createDate) - { - Id = id; - LoginProvider = loginProvider; - ProviderKey = providerKey; - UserId = userId; - CreateDate = createDate; - } + /// + /// Initializes a new instance of the class. + /// + public IdentityUserLogin(int id, string loginProvider, string providerKey, string userId, DateTime createDate) + { + Id = id; + LoginProvider = loginProvider; + ProviderKey = providerKey; + UserId = userId; + CreateDate = createDate; + } - /// - public string LoginProvider { get; set; } + /// + public string LoginProvider { get; set; } - /// - public string ProviderKey { get; set; } + /// + public string ProviderKey { get; set; } - /// - public string UserId { get; set; } + /// + public string UserId { get; set; } - /// - public string? UserData { get; set; } - } + /// + public string? UserData { get; set; } } diff --git a/src/Umbraco.Core/Security/IdentityUserToken.cs b/src/Umbraco.Core/Security/IdentityUserToken.cs index 014001a3a9cf..b8c41c936bb1 100644 --- a/src/Umbraco.Core/Security/IdentityUserToken.cs +++ b/src/Umbraco.Core/Security/IdentityUserToken.cs @@ -1,44 +1,43 @@ -using System; using Umbraco.Cms.Core.Models.Entities; -namespace Umbraco.Cms.Core.Security +namespace Umbraco.Cms.Core.Security; + +public class IdentityUserToken : EntityBase, IIdentityUserToken { - public class IdentityUserToken : EntityBase, IIdentityUserToken + /// + /// Initializes a new instance of the class. + /// + public IdentityUserToken(string loginProvider, string? name, string? value, string? userId) { - /// - /// Initializes a new instance of the class. - /// - public IdentityUserToken(string loginProvider, string? name, string? value, string? userId) - { - LoginProvider = loginProvider ?? throw new ArgumentNullException(nameof(loginProvider)); - Name = name ?? throw new ArgumentNullException(nameof(name)); - Value = value ?? throw new ArgumentNullException(nameof(value)); - UserId = userId; - } + LoginProvider = loginProvider ?? throw new ArgumentNullException(nameof(loginProvider)); + Name = name ?? throw new ArgumentNullException(nameof(name)); + Value = value ?? throw new ArgumentNullException(nameof(value)); + UserId = userId; + } - /// - /// Initializes a new instance of the class. - /// - public IdentityUserToken(int id, string? loginProvider, string? name, string? value, string userId, DateTime createDate) - { - Id = id; - LoginProvider = loginProvider ?? throw new ArgumentNullException(nameof(loginProvider)); - Name = name ?? throw new ArgumentNullException(nameof(name)); - Value = value ?? throw new ArgumentNullException(nameof(value)); - UserId = userId; - CreateDate = createDate; - } + /// + /// Initializes a new instance of the class. + /// + public IdentityUserToken(int id, string? loginProvider, string? name, string? value, string userId, + DateTime createDate) + { + Id = id; + LoginProvider = loginProvider ?? throw new ArgumentNullException(nameof(loginProvider)); + Name = name ?? throw new ArgumentNullException(nameof(name)); + Value = value ?? throw new ArgumentNullException(nameof(value)); + UserId = userId; + CreateDate = createDate; + } - /// - public string LoginProvider { get; set; } + /// + public string LoginProvider { get; set; } - /// - public string Name { get; set; } + /// + public string Name { get; set; } - /// - public string Value { get; set; } + /// + public string Value { get; set; } - /// - public string? UserId { get; set; } - } + /// + public string? UserId { get; set; } } diff --git a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs index b8c7596b2d0a..fc0d8c354b98 100644 --- a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs +++ b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs @@ -1,239 +1,244 @@ -using System; using System.ComponentModel; using System.Security.Cryptography; using System.Text; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Security +namespace Umbraco.Cms.Core.Security; + +/// +/// Handles password hashing and formatting for legacy hashing algorithms. +/// +/// +/// Should probably be internal. +/// +public class LegacyPasswordSecurity { + // TODO: Remove v11 + // Used for tests + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("We shouldn't be altering our public API to make test code easier, removing v11")] + public string HashPasswordForStorage(string algorithmType, string password) + { + if (string.IsNullOrWhiteSpace(password)) + { + throw new ArgumentException("password cannot be empty", nameof(password)); + } + + string salt; + var hashed = HashNewPassword(algorithmType, password, out salt); + return FormatPasswordForStorage(algorithmType, hashed, salt); + } + + // TODO: Remove v11 + // Used for tests + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("We shouldn't be altering our public API to make test code easier, removing v11")] + public string FormatPasswordForStorage(string algorithmType, string hashedPassword, string salt) + { + if (!SupportHashAlgorithm(algorithmType)) + { + throw new InvalidOperationException($"{algorithmType} is not supported"); + } + + return salt + hashedPassword; + } /// - /// Handles password hashing and formatting for legacy hashing algorithms. + /// Verifies if the password matches the expected hash+salt of the stored password string /// - /// - /// Should probably be internal. - /// - public class LegacyPasswordSecurity + /// The hashing algorithm for the stored password. + /// The password. + /// The value of the password stored in a data store. + /// + public bool VerifyPassword(string algorithm, string password, string dbPassword) { - // TODO: Remove v11 - // Used for tests - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("We shouldn't be altering our public API to make test code easier, removing v11")] - public string HashPasswordForStorage(string algorithmType, string password) - { - if (string.IsNullOrWhiteSpace(password)) - throw new ArgumentException("password cannot be empty", nameof(password)); - - string salt; - var hashed = HashNewPassword(algorithmType, password, out salt); - return FormatPasswordForStorage(algorithmType, hashed, salt); + if (string.IsNullOrWhiteSpace(dbPassword)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(dbPassword)); } - // TODO: Remove v11 - // Used for tests - [EditorBrowsable(EditorBrowsableState.Never)] - [Obsolete("We shouldn't be altering our public API to make test code easier, removing v11")] - public string FormatPasswordForStorage(string algorithmType, string hashedPassword, string salt) + if (dbPassword.StartsWith(Constants.Security.EmptyPasswordPrefix)) { - if (!SupportHashAlgorithm(algorithmType)) - { - throw new InvalidOperationException($"{algorithmType} is not supported"); - } - - return salt + hashedPassword; + return false; } - /// - /// Verifies if the password matches the expected hash+salt of the stored password string - /// - /// The hashing algorithm for the stored password. - /// The password. - /// The value of the password stored in a data store. - /// - public bool VerifyPassword(string algorithm, string password, string dbPassword) + try { - if (string.IsNullOrWhiteSpace(dbPassword)) - { - throw new ArgumentException("Value cannot be null or whitespace.", nameof(dbPassword)); - } - - if (dbPassword.StartsWith(Constants.Security.EmptyPasswordPrefix)) - { - return false; - } - - try - { - var storedHashedPass = ParseStoredHashPassword(algorithm, dbPassword, out var salt); - var hashed = HashPassword(algorithm, password, salt); - return storedHashedPass == hashed; - } - catch (ArgumentOutOfRangeException) - { - //This can happen if the length of the password is wrong and a salt cannot be extracted. - return false; - } + var storedHashedPass = ParseStoredHashPassword(algorithm, dbPassword, out var salt); + var hashed = HashPassword(algorithm, password, salt); + return storedHashedPass == hashed; + } + catch (ArgumentOutOfRangeException) + { + //This can happen if the length of the password is wrong and a salt cannot be extracted. + return false; } + } - /// - /// Verify a legacy hashed password (HMACSHA1) - /// - public bool VerifyLegacyHashedPassword(string password, string dbPassword) + /// + /// Verify a legacy hashed password (HMACSHA1) + /// + public bool VerifyLegacyHashedPassword(string password, string dbPassword) + { + var hashAlgorithm = new HMACSHA1 { - var hashAlgorithm = new HMACSHA1 - { - //the legacy salt was actually the password :( - Key = Encoding.Unicode.GetBytes(password) - }; + //the legacy salt was actually the password :( + Key = Encoding.Unicode.GetBytes(password) + }; - var hashed = Convert.ToBase64String(hashAlgorithm.ComputeHash(Encoding.Unicode.GetBytes(password))); + var hashed = Convert.ToBase64String(hashAlgorithm.ComputeHash(Encoding.Unicode.GetBytes(password))); - return dbPassword == hashed; - } + return dbPassword == hashed; + } - /// - /// Create a new password hash and a new salt - /// - /// The hashing algorithm for the password. - /// - /// - /// - // TODO: Do we need this method? We shouldn't be using this class to create new password hashes for storage - // TODO: Remove v11 - [Obsolete("We shouldn't be altering our public API to make test code easier, removing v11")] - public string HashNewPassword(string algorithm, string newPassword, out string salt) - { - salt = GenerateSalt(); - return HashPassword(algorithm, newPassword, salt); + /// + /// Create a new password hash and a new salt + /// + /// The hashing algorithm for the password. + /// + /// + /// + // TODO: Do we need this method? We shouldn't be using this class to create new password hashes for storage + // TODO: Remove v11 + [Obsolete("We shouldn't be altering our public API to make test code easier, removing v11")] + public string HashNewPassword(string algorithm, string newPassword, out string salt) + { + salt = GenerateSalt(); + return HashPassword(algorithm, newPassword, salt); + } + + /// + /// Parses out the hashed password and the salt from the stored password string value + /// + /// The hashing algorithm for the stored password. + /// + /// returns the salt + /// + public string ParseStoredHashPassword(string algorithm, string storedString, out string salt) + { + if (string.IsNullOrWhiteSpace(storedString)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(storedString)); } - /// - /// Parses out the hashed password and the salt from the stored password string value - /// - /// The hashing algorithm for the stored password. - /// - /// returns the salt - /// - public string ParseStoredHashPassword(string algorithm, string storedString, out string salt) + if (!SupportHashAlgorithm(algorithm)) { - if (string.IsNullOrWhiteSpace(storedString)) - { - throw new ArgumentException("Value cannot be null or whitespace.", nameof(storedString)); - } + throw new InvalidOperationException($"{algorithm} is not supported"); + } - if (!SupportHashAlgorithm(algorithm)) - { - throw new InvalidOperationException($"{algorithm} is not supported"); - } + var saltLen = GenerateSalt(); + salt = storedString.Substring(0, saltLen.Length); + return storedString.Substring(saltLen.Length); + } - var saltLen = GenerateSalt(); - salt = storedString.Substring(0, saltLen.Length); - return storedString.Substring(saltLen.Length); - } + public static string GenerateSalt() + { + var numArray = new byte[16]; + new RNGCryptoServiceProvider().GetBytes(numArray); + return Convert.ToBase64String(numArray); + } - public static string GenerateSalt() + /// + /// Hashes a password with a given salt + /// + /// The hashing algorithm for the password. + /// + /// + /// + private string HashPassword(string algorithmType, string pass, string salt) + { + if (!SupportHashAlgorithm(algorithmType)) { - var numArray = new byte[16]; - new RNGCryptoServiceProvider().GetBytes(numArray); - return Convert.ToBase64String(numArray); + throw new InvalidOperationException($"{algorithmType} is not supported"); } - /// - /// Hashes a password with a given salt - /// - /// The hashing algorithm for the password. - /// - /// - /// - private string HashPassword(string algorithmType, string pass, string salt) + // This is the correct way to implement this (as per the sql membership provider) + + var bytes = Encoding.Unicode.GetBytes(pass); + var saltBytes = Convert.FromBase64String(salt); + byte[] inArray; + + using HashAlgorithm hashAlgorithm = GetHashAlgorithm(algorithmType); + var algorithm = hashAlgorithm as KeyedHashAlgorithm; + if (algorithm != null) { - if (!SupportHashAlgorithm(algorithmType)) + KeyedHashAlgorithm keyedHashAlgorithm = algorithm; + if (keyedHashAlgorithm.Key.Length == saltBytes.Length) { - throw new InvalidOperationException($"{algorithmType} is not supported"); + //if the salt bytes is the required key length for the algorithm, use it as-is + keyedHashAlgorithm.Key = saltBytes; } - - // This is the correct way to implement this (as per the sql membership provider) - - var bytes = Encoding.Unicode.GetBytes(pass); - var saltBytes = Convert.FromBase64String(salt); - byte[] inArray; - - using var hashAlgorithm = GetHashAlgorithm(algorithmType); - var algorithm = hashAlgorithm as KeyedHashAlgorithm; - if (algorithm != null) + else if (keyedHashAlgorithm.Key.Length < saltBytes.Length) { - var keyedHashAlgorithm = algorithm; - if (keyedHashAlgorithm.Key.Length == saltBytes.Length) - { - //if the salt bytes is the required key length for the algorithm, use it as-is - keyedHashAlgorithm.Key = saltBytes; - } - else if (keyedHashAlgorithm.Key.Length < saltBytes.Length) - { - //if the salt bytes is too long for the required key length for the algorithm, reduce it - var numArray2 = new byte[keyedHashAlgorithm.Key.Length]; - Buffer.BlockCopy(saltBytes, 0, numArray2, 0, numArray2.Length); - keyedHashAlgorithm.Key = numArray2; - } - else - { - //if the salt bytes is too short for the required key length for the algorithm, extend it - var numArray2 = new byte[keyedHashAlgorithm.Key.Length]; - var dstOffset = 0; - while (dstOffset < numArray2.Length) - { - var count = Math.Min(saltBytes.Length, numArray2.Length - dstOffset); - Buffer.BlockCopy(saltBytes, 0, numArray2, dstOffset, count); - dstOffset += count; - } - keyedHashAlgorithm.Key = numArray2; - } - inArray = keyedHashAlgorithm.ComputeHash(bytes); + //if the salt bytes is too long for the required key length for the algorithm, reduce it + var numArray2 = new byte[keyedHashAlgorithm.Key.Length]; + Buffer.BlockCopy(saltBytes, 0, numArray2, 0, numArray2.Length); + keyedHashAlgorithm.Key = numArray2; } else { - var buffer = new byte[saltBytes.Length + bytes.Length]; - Buffer.BlockCopy(saltBytes, 0, buffer, 0, saltBytes.Length); - Buffer.BlockCopy(bytes, 0, buffer, saltBytes.Length, bytes.Length); - inArray = hashAlgorithm.ComputeHash(buffer); + //if the salt bytes is too short for the required key length for the algorithm, extend it + var numArray2 = new byte[keyedHashAlgorithm.Key.Length]; + var dstOffset = 0; + while (dstOffset < numArray2.Length) + { + var count = Math.Min(saltBytes.Length, numArray2.Length - dstOffset); + Buffer.BlockCopy(saltBytes, 0, numArray2, dstOffset, count); + dstOffset += count; + } + + keyedHashAlgorithm.Key = numArray2; } - return Convert.ToBase64String(inArray); + inArray = keyedHashAlgorithm.ComputeHash(bytes); } - - /// - /// Return the hash algorithm to use based on the - /// - /// The hashing algorithm name. - /// - /// - private HashAlgorithm GetHashAlgorithm(string algorithm) + else { - if (algorithm.IsNullOrWhiteSpace()) - throw new InvalidOperationException("No hash algorithm type specified"); + var buffer = new byte[saltBytes.Length + bytes.Length]; + Buffer.BlockCopy(saltBytes, 0, buffer, 0, saltBytes.Length); + Buffer.BlockCopy(bytes, 0, buffer, saltBytes.Length, bytes.Length); + inArray = hashAlgorithm.ComputeHash(buffer); + } - var alg = HashAlgorithm.Create(algorithm); - if (alg == null) - throw new InvalidOperationException($"The hash algorithm specified {algorithm} cannot be resolved"); + return Convert.ToBase64String(inArray); + } - return alg; + /// + /// Return the hash algorithm to use based on the + /// + /// The hashing algorithm name. + /// + /// + private HashAlgorithm GetHashAlgorithm(string algorithm) + { + if (algorithm.IsNullOrWhiteSpace()) + { + throw new InvalidOperationException("No hash algorithm type specified"); } - public bool SupportHashAlgorithm(string algorithm) + var alg = HashAlgorithm.Create(algorithm); + if (alg == null) { - // This is for the v6-v8 hashing algorithm - if (algorithm.InvariantEquals(Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName)) - { - return true; - } + throw new InvalidOperationException($"The hash algorithm specified {algorithm} cannot be resolved"); + } - // Default validation value for old machine keys (switched to HMACSHA256 aspnet 4 https://docs.microsoft.com/en-us/aspnet/whitepapers/aspnet4/breaking-changes) - if (algorithm.InvariantEquals("SHA1")) - { - return true; - } + return alg; + } - return false; + public bool SupportHashAlgorithm(string algorithm) + { + // This is for the v6-v8 hashing algorithm + if (algorithm.InvariantEquals(Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName)) + { + return true; } + + // Default validation value for old machine keys (switched to HMACSHA256 aspnet 4 https://docs.microsoft.com/en-us/aspnet/whitepapers/aspnet4/breaking-changes) + if (algorithm.InvariantEquals("SHA1")) + { + return true; + } + + return false; } } diff --git a/src/Umbraco.Core/Security/MediaPermissions.cs b/src/Umbraco.Core/Security/MediaPermissions.cs index d30ab90af2d1..7571b8ae8c9e 100644 --- a/src/Umbraco.Core/Security/MediaPermissions.cs +++ b/src/Umbraco.Core/Security/MediaPermissions.cs @@ -1,77 +1,84 @@ -using System; -using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.Security +namespace Umbraco.Cms.Core.Security; + +/// +/// Checks user access to media +/// +public class MediaPermissions { + public enum MediaAccess + { + Granted, + Denied, + NotFound + } + + private readonly AppCaches _appCaches; + private readonly IEntityService _entityService; + private readonly IMediaService _mediaService; + + public MediaPermissions(IMediaService mediaService, IEntityService entityService, AppCaches appCaches) + { + _mediaService = mediaService; + _entityService = entityService; + _appCaches = appCaches; + } + /// - /// Checks user access to media + /// Performs a permissions check for the user to check if it has access to the node based on + /// start node and/or permissions for the node /// - public class MediaPermissions + /// + /// + /// + /// The content to lookup, if the contentItem is not specified + /// + public MediaAccess CheckPermissions(IUser? user, int nodeId, out IMedia? media) { - private readonly IMediaService _mediaService; - private readonly IEntityService _entityService; - private readonly AppCaches _appCaches; - - public enum MediaAccess + if (user == null) { - Granted, - Denied, - NotFound + throw new ArgumentNullException(nameof(user)); } - public MediaPermissions(IMediaService mediaService, IEntityService entityService, AppCaches appCaches) + media = null; + + if (nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) { - _mediaService = mediaService; - _entityService = entityService; - _appCaches = appCaches; + media = _mediaService.GetById(nodeId); } - /// - /// Performs a permissions check for the user to check if it has access to the node based on - /// start node and/or permissions for the node - /// - /// - /// - /// - /// The content to lookup, if the contentItem is not specified - /// - public MediaAccess CheckPermissions(IUser? user, int nodeId, out IMedia? media) + if (media == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) { - if (user == null) throw new ArgumentNullException(nameof(user)); - - media = null; - - if (nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) - { - media = _mediaService.GetById(nodeId); - } + return MediaAccess.NotFound; + } - if (media == null && nodeId != Constants.System.Root && nodeId != Constants.System.RecycleBinMedia) - { - return MediaAccess.NotFound; - } + var hasPathAccess = nodeId == Constants.System.Root + ? user.HasMediaRootAccess(_entityService, _appCaches) + : nodeId == Constants.System.RecycleBinMedia + ? user.HasMediaBinAccess(_entityService, _appCaches) + : user.HasPathAccess(media, _entityService, _appCaches); - var hasPathAccess = (nodeId == Constants.System.Root) - ? user.HasMediaRootAccess(_entityService, _appCaches) - : (nodeId == Constants.System.RecycleBinMedia) - ? user.HasMediaBinAccess(_entityService, _appCaches) - : user.HasPathAccess(media, _entityService, _appCaches); + return hasPathAccess ? MediaAccess.Granted : MediaAccess.Denied; + } - return hasPathAccess ? MediaAccess.Granted : MediaAccess.Denied; + public MediaAccess CheckPermissions(IMedia? media, IUser? user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); } - public MediaAccess CheckPermissions(IMedia? media, IUser? user) + if (media == null) { - if (user == null) throw new ArgumentNullException(nameof(user)); - - if (media == null) return MediaAccess.NotFound; + return MediaAccess.NotFound; + } - var hasPathAccess = user.HasPathAccess(media, _entityService, _appCaches); + var hasPathAccess = user.HasPathAccess(media, _entityService, _appCaches); - return hasPathAccess ? MediaAccess.Granted : MediaAccess.Denied; - } + return hasPathAccess ? MediaAccess.Granted : MediaAccess.Denied; } } diff --git a/src/Umbraco.Core/Security/NoopHtmlSanitizer.cs b/src/Umbraco.Core/Security/NoopHtmlSanitizer.cs index 2ada23631aa1..5892f786a747 100644 --- a/src/Umbraco.Core/Security/NoopHtmlSanitizer.cs +++ b/src/Umbraco.Core/Security/NoopHtmlSanitizer.cs @@ -1,10 +1,6 @@ -namespace Umbraco.Cms.Core.Security +namespace Umbraco.Cms.Core.Security; + +public class NoopHtmlSanitizer : IHtmlSanitizer { - public class NoopHtmlSanitizer : IHtmlSanitizer - { - public string Sanitize(string html) - { - return html; - } - } + public string Sanitize(string html) => html; } diff --git a/src/Umbraco.Core/Security/PasswordGenerator.cs b/src/Umbraco.Core/Security/PasswordGenerator.cs index 55a6ba1a5108..3a72b66b1bb3 100644 --- a/src/Umbraco.Core/Security/PasswordGenerator.cs +++ b/src/Umbraco.Core/Security/PasswordGenerator.cs @@ -1,161 +1,211 @@ -using System; -using System.Linq; -using System.Security.Cryptography; +using System.Security.Cryptography; using Umbraco.Cms.Core.Configuration; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Security +namespace Umbraco.Cms.Core.Security; + +/// +/// Generates a password +/// +/// +/// This uses logic copied from the old MembershipProvider.GeneratePassword logic +/// +public class PasswordGenerator { - /// - /// Generates a password - /// - /// - /// This uses logic copied from the old MembershipProvider.GeneratePassword logic - /// - public class PasswordGenerator - { - private readonly IPasswordConfiguration _passwordConfiguration; + private readonly IPasswordConfiguration _passwordConfiguration; - public PasswordGenerator(IPasswordConfiguration passwordConfiguration) - { - _passwordConfiguration = passwordConfiguration; - } - public string GeneratePassword() - { - var password = PasswordStore.GeneratePassword( - _passwordConfiguration.RequiredLength, - _passwordConfiguration.GetMinNonAlphaNumericChars()); + public PasswordGenerator(IPasswordConfiguration passwordConfiguration) => + _passwordConfiguration = passwordConfiguration; - var random = new Random(); + public string GeneratePassword() + { + var password = PasswordStore.GeneratePassword( + _passwordConfiguration.RequiredLength, + _passwordConfiguration.GetMinNonAlphaNumericChars()); - var passwordChars = password.ToCharArray(); + var random = new Random(); - if (_passwordConfiguration.RequireDigit && passwordChars.ContainsAny(Enumerable.Range(48, 58).Select(x => (char)x))) - password += Convert.ToChar(random.Next(48, 58)); // 0-9 + var passwordChars = password.ToCharArray(); - if (_passwordConfiguration.RequireLowercase && passwordChars.ContainsAny(Enumerable.Range(97, 123).Select(x => (char)x))) - password += Convert.ToChar(random.Next(97, 123)); // a-z + if (_passwordConfiguration.RequireDigit && + passwordChars.ContainsAny(Enumerable.Range(48, 58).Select(x => (char)x))) + { + password += Convert.ToChar(random.Next(48, 58)); // 0-9 + } - if (_passwordConfiguration.RequireUppercase && passwordChars.ContainsAny(Enumerable.Range(65, 91).Select(x => (char)x))) - password += Convert.ToChar(random.Next(65, 91)); // A-Z + if (_passwordConfiguration.RequireLowercase && + passwordChars.ContainsAny(Enumerable.Range(97, 123).Select(x => (char)x))) + { + password += Convert.ToChar(random.Next(97, 123)); // a-z + } - if (_passwordConfiguration.RequireNonLetterOrDigit && passwordChars.ContainsAny(Enumerable.Range(33, 48).Select(x => (char)x))) - password += Convert.ToChar(random.Next(33, 48)); // symbols !"#$%&'()*+,-./ + if (_passwordConfiguration.RequireUppercase && + passwordChars.ContainsAny(Enumerable.Range(65, 91).Select(x => (char)x))) + { + password += Convert.ToChar(random.Next(65, 91)); // A-Z + } - return password; + if (_passwordConfiguration.RequireNonLetterOrDigit && + passwordChars.ContainsAny(Enumerable.Range(33, 48).Select(x => (char)x))) + { + password += Convert.ToChar(random.Next(33, 48)); // symbols !"#$%&'()*+,-./ } - /// - /// Internal class copied from ASP.NET Framework MembershipProvider - /// - /// - /// See https://stackoverflow.com/a/39855417/694494 + https://github.com/Microsoft/referencesource/blob/master/System.Web/Security/Membership.cs - /// - private static class PasswordStore + return password; + } + + /// + /// Internal class copied from ASP.NET Framework MembershipProvider + /// + /// + /// See https://stackoverflow.com/a/39855417/694494 + + /// https://github.com/Microsoft/referencesource/blob/master/System.Web/Security/Membership.cs + /// + private static class PasswordStore + { + private static readonly char[] Punctuations = "!@#$%^&*()_-+=[{]};:>|./?".ToCharArray(); + private static readonly char[] StartingChars = {'<', '&'}; + + /// Generates a random password of the specified length. + /// A random password of the specified length. + /// + /// The number of characters in the generated password. The length must be between 1 and 128 + /// characters. + /// + /// + /// The minimum number of non-alphanumeric characters (such as @, #, !, %, + /// &, and so on) in the generated password. + /// + /// + /// is less than 1 or greater than 128 -or- + /// is less than 0 or greater than . + /// + public static string GeneratePassword(int length, int numberOfNonAlphanumericCharacters) { - private static readonly char[] Punctuations = "!@#$%^&*()_-+=[{]};:>|./?".ToCharArray(); - private static readonly char[] StartingChars = new char[] { '<', '&' }; - /// Generates a random password of the specified length. - /// A random password of the specified length. - /// The number of characters in the generated password. The length must be between 1 and 128 characters. - /// The minimum number of non-alphanumeric characters (such as @, #, !, %, &, and so on) in the generated password. - /// - /// is less than 1 or greater than 128 -or- is less than 0 or greater than . - public static string GeneratePassword(int length, int numberOfNonAlphanumericCharacters) + if (length < 1 || length > 128) + { + throw new ArgumentException("password length incorrect", nameof(length)); + } + + if (numberOfNonAlphanumericCharacters > length || numberOfNonAlphanumericCharacters < 0) { - if (length < 1 || length > 128) - throw new ArgumentException("password length incorrect", nameof(length)); - if (numberOfNonAlphanumericCharacters > length || numberOfNonAlphanumericCharacters < 0) - throw new ArgumentException("min required non alphanumeric characters incorrect", nameof(numberOfNonAlphanumericCharacters)); - string s; - int matchIndex; - do + throw new ArgumentException("min required non alphanumeric characters incorrect", + nameof(numberOfNonAlphanumericCharacters)); + } + + string s; + int matchIndex; + do + { + var data = new byte[length]; + var chArray = new char[length]; + var num1 = 0; + new RNGCryptoServiceProvider().GetBytes(data); + for (var index = 0; index < length; ++index) { - var data = new byte[length]; - var chArray = new char[length]; - var num1 = 0; - new RNGCryptoServiceProvider().GetBytes(data); - for (var index = 0; index < length; ++index) + var num2 = data[index] % 87; + if (num2 < 10) { - var num2 = (int)data[index] % 87; - if (num2 < 10) - chArray[index] = (char)(48 + num2); - else if (num2 < 36) - chArray[index] = (char)(65 + num2 - 10); - else if (num2 < 62) - { - chArray[index] = (char)(97 + num2 - 36); - } - else - { - chArray[index] = Punctuations[num2 - 62]; - ++num1; - } + chArray[index] = (char)(48 + num2); } - if (num1 < numberOfNonAlphanumericCharacters) + else if (num2 < 36) { - var random = new Random(); - for (var index1 = 0; index1 < numberOfNonAlphanumericCharacters - num1; ++index1) + chArray[index] = (char)(65 + num2 - 10); + } + else if (num2 < 62) + { + chArray[index] = (char)(97 + num2 - 36); + } + else + { + chArray[index] = Punctuations[num2 - 62]; + ++num1; + } + } + + if (num1 < numberOfNonAlphanumericCharacters) + { + var random = new Random(); + for (var index1 = 0; index1 < numberOfNonAlphanumericCharacters - num1; ++index1) + { + int index2; + do { - int index2; - do - { - index2 = random.Next(0, length); - } - while (!char.IsLetterOrDigit(chArray[index2])); - chArray[index2] = Punctuations[random.Next(0, Punctuations.Length)]; - } + index2 = random.Next(0, length); + } while (!char.IsLetterOrDigit(chArray[index2])); + + chArray[index2] = Punctuations[random.Next(0, Punctuations.Length)]; } - s = new string(chArray); } - while (IsDangerousString(s, out matchIndex)); - return s; - } - private static bool IsDangerousString(string s, out int matchIndex) + s = new string(chArray); + } while (IsDangerousString(s, out matchIndex)); + + return s; + } + + private static bool IsDangerousString(string s, out int matchIndex) + { + //bool inComment = false; + matchIndex = 0; + + for (var i = 0;;) { - //bool inComment = false; - matchIndex = 0; + // Look for the start of one of our patterns + var n = s.IndexOfAny(StartingChars, i); - for (var i = 0; ;) + // If not found, the string is safe + if (n < 0) { + return false; + } - // Look for the start of one of our patterns - var n = s.IndexOfAny(StartingChars, i); - - // If not found, the string is safe - if (n < 0) return false; + // If it's the last char, it's safe + if (n == s.Length - 1) + { + return false; + } - // If it's the last char, it's safe - if (n == s.Length - 1) return false; + matchIndex = n; - matchIndex = n; + switch (s[n]) + { + case '<': + // If the < is followed by a letter or '!', it's unsafe (looks like a tag or HTML comment) + if (IsAtoZ(s[n + 1]) || s[n + 1] == '!' || s[n + 1] == '/' || s[n + 1] == '?') + { + return true; + } - switch (s[n]) - { - case '<': - // If the < is followed by a letter or '!', it's unsafe (looks like a tag or HTML comment) - if (IsAtoZ(s[n + 1]) || s[n + 1] == '!' || s[n + 1] == '/' || s[n + 1] == '?') return true; - break; - case '&': - // If the & is followed by a #, it's unsafe (e.g. S) - if (s[n + 1] == '#') return true; - break; - } + break; + case '&': + // If the & is followed by a #, it's unsafe (e.g. S) + if (s[n + 1] == '#') + { + return true; + } - // Continue searching - i = n + 1; + break; } + + // Continue searching + i = n + 1; + } + } + + private static bool IsAtoZ(char c) + { + if (c >= 97 && c <= 122) + { + return true; } - private static bool IsAtoZ(char c) + if (c >= 65) { - if ((int)c >= 97 && (int)c <= 122) - return true; - if ((int)c >= 65) - return (int)c <= 90; - return false; + return c <= 90; } + + return false; } } } diff --git a/src/Umbraco.Core/Security/PublicAccessStatus.cs b/src/Umbraco.Core/Security/PublicAccessStatus.cs index b92c0ff57a5b..fad5ddb196e2 100644 --- a/src/Umbraco.Core/Security/PublicAccessStatus.cs +++ b/src/Umbraco.Core/Security/PublicAccessStatus.cs @@ -1,11 +1,10 @@ -namespace Umbraco.Cms.Core.Security +namespace Umbraco.Cms.Core.Security; + +public enum PublicAccessStatus { - public enum PublicAccessStatus - { - NotLoggedIn, - AccessDenied, - NotApproved, - LockedOut, - AccessAccepted - } + NotLoggedIn, + AccessDenied, + NotApproved, + LockedOut, + AccessAccepted } diff --git a/src/Umbraco.Core/Security/UpdateMemberProfileResult.cs b/src/Umbraco.Core/Security/UpdateMemberProfileResult.cs index b6b6c241e425..2c4407fcc106 100644 --- a/src/Umbraco.Core/Security/UpdateMemberProfileResult.cs +++ b/src/Umbraco.Core/Security/UpdateMemberProfileResult.cs @@ -1,24 +1,18 @@ -namespace Umbraco.Cms.Core.Security +namespace Umbraco.Cms.Core.Security; + +public class UpdateMemberProfileResult { - public class UpdateMemberProfileResult + private UpdateMemberProfileResult() { - private UpdateMemberProfileResult() - { - } - - public UpdateMemberProfileStatus Status { get; private set; } + } - public string? ErrorMessage { get; private set; } + public UpdateMemberProfileStatus Status { get; private set; } - public static UpdateMemberProfileResult Success() - { - return new UpdateMemberProfileResult { Status = UpdateMemberProfileStatus.Success }; - } + public string? ErrorMessage { get; private set; } - public static UpdateMemberProfileResult Error(string message) - { - return new UpdateMemberProfileResult { Status = UpdateMemberProfileStatus.Error, ErrorMessage = message }; - } - } + public static UpdateMemberProfileResult Success() => + new UpdateMemberProfileResult {Status = UpdateMemberProfileStatus.Success}; + public static UpdateMemberProfileResult Error(string message) => + new UpdateMemberProfileResult {Status = UpdateMemberProfileStatus.Error, ErrorMessage = message}; } diff --git a/src/Umbraco.Core/Security/UpdateMemberProfileStatus.cs b/src/Umbraco.Core/Security/UpdateMemberProfileStatus.cs index df805d30969e..81103386a86a 100644 --- a/src/Umbraco.Core/Security/UpdateMemberProfileStatus.cs +++ b/src/Umbraco.Core/Security/UpdateMemberProfileStatus.cs @@ -1,8 +1,7 @@ -namespace Umbraco.Cms.Core.Security +namespace Umbraco.Cms.Core.Security; + +public enum UpdateMemberProfileStatus { - public enum UpdateMemberProfileStatus - { - Success, - Error, - } + Success, + Error } diff --git a/src/Umbraco.Core/Semver/Semver.cs b/src/Umbraco.Core/Semver/Semver.cs index 5a04553f1bef..9af4a1aa40ee 100644 --- a/src/Umbraco.Core/Semver/Semver.cs +++ b/src/Umbraco.Core/Semver/Semver.cs @@ -1,5 +1,4 @@ -using System; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; #if !NETSTANDARD using System.Globalization; using System.Runtime.Serialization; @@ -30,8 +29,8 @@ THE SOFTWARE. namespace Umbraco.Cms.Core.Semver { /// - /// A semantic version implementation. - /// Conforms to v2.0.0 of http://semver.org/ + /// A semantic version implementation. + /// Conforms to v2.0.0 of http://semver.org/ /// #if NETSTANDARD public sealed class SemVersion : IComparable, IComparable @@ -40,8 +39,8 @@ public sealed class SemVersion : IComparable, IComparable public sealed class SemVersion : IComparable, IComparable, ISerializable #endif { - static Regex parseEx = - new Regex(@"^(?\d+)" + + private static Regex parseEx = + new(@"^(?\d+)" + @"(\.(?\d+))?" + @"(\.(?\d+))?" + @"(\-(?
[0-9A-Za-z\-\.]+))?" +
@@ -54,15 +53,19 @@ public sealed class SemVersion : IComparable, IComparable, ISerializ
 
 #if !NETSTANDARD
         /// 
-        /// Initializes a new instance of the  class.
+        ///     Initializes a new instance of the  class.
         /// 
         /// 
         /// 
         /// 
         private SemVersion(SerializationInfo info, StreamingContext context)
         {
-            if (info == null) throw new ArgumentNullException("info");
-            var semVersion = Parse(info.GetString("SemVersion")!);
+            if (info == null)
+            {
+                throw new ArgumentNullException("info");
+            }
+
+            SemVersion semVersion = Parse(info.GetString("SemVersion")!);
             Major = semVersion.Major;
             Minor = semVersion.Minor;
             Patch = semVersion.Patch;
@@ -72,7 +75,7 @@ private SemVersion(SerializationInfo info, StreamingContext context)
 #endif
 
         /// 
-        /// Initializes a new instance of the  class.
+        ///     Initializes a new instance of the  class.
         /// 
         /// The major version.
         /// The minor version.
@@ -81,46 +84,50 @@ private SemVersion(SerializationInfo info, StreamingContext context)
         /// The build eg ("nightly.232").
         public SemVersion(int major, int minor = 0, int patch = 0, string prerelease = "", string build = "")
         {
-            this.Major = major;
-            this.Minor = minor;
-            this.Patch = patch;
+            Major = major;
+            Minor = minor;
+            Patch = patch;
 
-            this.Prerelease = prerelease ?? "";
-            this.Build = build ?? "";
+            Prerelease = prerelease ?? "";
+            Build = build ?? "";
         }
 
         /// 
-        /// Initializes a new instance of the  class.
+        ///     Initializes a new instance of the  class.
         /// 
-        /// The  that is used to initialize
-        /// the Major, Minor, Patch and Build properties.
+        /// 
+        ///     The  that is used to initialize
+        ///     the Major, Minor, Patch and Build properties.
+        /// 
         public SemVersion(Version version)
         {
             if (version == null)
+            {
                 throw new ArgumentNullException("version");
+            }
 
-            this.Major = version.Major;
-            this.Minor = version.Minor;
+            Major = version.Major;
+            Minor = version.Minor;
 
             if (version.Revision >= 0)
             {
-                this.Patch = version.Revision;
+                Patch = version.Revision;
             }
 
-            this.Prerelease = String.Empty;
+            Prerelease = string.Empty;
 
             if (version.Build > 0)
             {
-                this.Build = version.Build.ToString();
+                Build = version.Build.ToString();
             }
             else
             {
-                this.Build = String.Empty;
+                Build = string.Empty;
             }
         }
 
         /// 
-        /// Parses the specified string to a semantic version.
+        ///     Parses the specified string to a semantic version.
         /// 
         /// The version string.
         /// If set to true minor and patch version are required, else they default to 0.
@@ -128,9 +135,11 @@ public SemVersion(Version version)
         /// When a invalid version string is passed.
         public static SemVersion Parse(string version, bool strict = false)
         {
-            var match = parseEx.Match(version);
+            Match match = parseEx.Match(version);
             if (!match.Success)
+            {
                 throw new ArgumentException("Invalid version.", "version");
+            }
 
 #if NETSTANDARD
             var major = int.Parse(match.Groups["major"].Value);
@@ -138,8 +147,8 @@ public static SemVersion Parse(string version, bool strict = false)
             var major = int.Parse(match.Groups["major"].Value, CultureInfo.InvariantCulture);
 #endif
 
-            var minorMatch = match.Groups["minor"];
-            int minor = 0;
+            Group minorMatch = match.Groups["minor"];
+            var minor = 0;
             if (minorMatch.Success)
             {
 #if NETSTANDARD
@@ -153,8 +162,8 @@ public static SemVersion Parse(string version, bool strict = false)
                 throw new InvalidOperationException("Invalid version (no minor version given in strict mode)");
             }
 
-            var patchMatch = match.Groups["patch"];
-            int patch = 0;
+            Group patchMatch = match.Groups["patch"];
+            var patch = 0;
             if (patchMatch.Success)
             {
 #if NETSTANDARD
@@ -175,12 +184,14 @@ public static SemVersion Parse(string version, bool strict = false)
         }
 
         /// 
-        /// Parses the specified string to a semantic version.
+        ///     Parses the specified string to a semantic version.
         /// 
         /// The version string.
-        /// When the method returns, contains a SemVersion instance equivalent
-        /// to the version string passed in, if the version string was valid, or null if the
-        /// version string was not valid.
+        /// 
+        ///     When the method returns, contains a SemVersion instance equivalent
+        ///     to the version string passed in, if the version string was valid, or null if the
+        ///     version string was not valid.
+        /// 
         /// If set to true minor and patch version are required, else they default to 0.
         /// False when a invalid version string is passed, otherwise true.
         public static bool TryParse(string version, out SemVersion? semver, bool strict = false)
@@ -198,7 +209,7 @@ public static bool TryParse(string version, out SemVersion? semver, bool strict
         }
 
         /// 
-        /// Tests the specified versions for equality.
+        ///     Tests the specified versions for equality.
         /// 
         /// The first version.
         /// The second version.
@@ -206,26 +217,34 @@ public static bool TryParse(string version, out SemVersion? semver, bool strict
         public static bool Equals(SemVersion versionA, SemVersion versionB)
         {
             if (ReferenceEquals(versionA, null))
+            {
                 return ReferenceEquals(versionB, null);
+            }
+
             return versionA.Equals(versionB);
         }
 
         /// 
-        /// Compares the specified versions.
+        ///     Compares the specified versions.
         /// 
         /// The version to compare to.
         /// The version to compare against.
-        /// If versionA < versionB < 0, if versionA > versionB > 0,
-        /// if versionA is equal to versionB 0.
+        /// 
+        ///     If versionA < versionB < 0, if versionA > versionB > 0,
+        ///     if versionA is equal to versionB 0.
+        /// 
         public static int Compare(SemVersion versionA, SemVersion versionB)
         {
             if (ReferenceEquals(versionA, null))
+            {
                 return ReferenceEquals(versionB, null) ? 0 : -1;
+            }
+
             return versionA.CompareTo(versionB);
         }
 
         /// 
-        /// Make a copy of the current instance with optional altered fields.
+        ///     Make a copy of the current instance with optional altered fields.
         /// 
         /// The major version.
         /// The minor version.
@@ -234,193 +253,224 @@ public static int Compare(SemVersion versionA, SemVersion versionB)
         /// The build text.
         /// The new version object.
         public SemVersion Change(int? major = null, int? minor = null, int? patch = null,
-            string? prerelease = null, string? build = null)
-        {
-            return new SemVersion(
-                major ?? this.Major,
-                minor ?? this.Minor,
-                patch ?? this.Patch,
-                prerelease ?? this.Prerelease,
-                build ?? this.Build);
-        }
+            string? prerelease = null, string? build = null) =>
+            new(
+                major ?? Major,
+                minor ?? Minor,
+                patch ?? Patch,
+                prerelease ?? Prerelease,
+                build ?? Build);
 
         /// 
-        /// Gets the major version.
+        ///     Gets the major version.
         /// 
         /// 
-        /// The major version.
+        ///     The major version.
         /// 
         public int Major { get; private set; }
 
         /// 
-        /// Gets the minor version.
+        ///     Gets the minor version.
         /// 
         /// 
-        /// The minor version.
+        ///     The minor version.
         /// 
         public int Minor { get; private set; }
 
         /// 
-        /// Gets the patch version.
+        ///     Gets the patch version.
         /// 
         /// 
-        /// The patch version.
+        ///     The patch version.
         /// 
         public int Patch { get; private set; }
 
         /// 
-        /// Gets the pre-release version.
+        ///     Gets the pre-release version.
         /// 
         /// 
-        /// The pre-release version.
+        ///     The pre-release version.
         /// 
         public string Prerelease { get; private set; }
 
         /// 
-        /// Gets the build version.
+        ///     Gets the build version.
         /// 
         /// 
-        /// The build version.
+        ///     The build version.
         /// 
         public string Build { get; private set; }
 
         /// 
-        /// Returns a  that represents this instance.
+        ///     Returns a  that represents this instance.
         /// 
         /// 
-        /// A  that represents this instance.
+        ///     A  that represents this instance.
         /// 
         public override string ToString()
         {
             var version = "" + Major + "." + Minor + "." + Patch;
-            if (!String.IsNullOrEmpty(Prerelease))
+            if (!string.IsNullOrEmpty(Prerelease))
+            {
                 version += "-" + Prerelease;
-            if (!String.IsNullOrEmpty(Build))
+            }
+
+            if (!string.IsNullOrEmpty(Build))
+            {
                 version += "+" + Build;
+            }
+
             return version;
         }
 
         /// 
-        /// Compares the current instance with another object of the same type and returns an integer that indicates
-        /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the
-        /// other object.
+        ///     Compares the current instance with another object of the same type and returns an integer that indicates
+        ///     whether the current instance precedes, follows, or occurs in the same position in the sort order as the
+        ///     other object.
         /// 
         /// An object to compare with this instance.
         /// 
-        /// A value that indicates the relative order of the objects being compared.
-        /// The return value has these meanings: Value Meaning Less than zero
-        ///  This instance precedes  in the sort order.
-        ///  Zero This instance occurs in the same position in the sort order as . i
-        ///  Greater than zero This instance follows  in the sort order.
+        ///     A value that indicates the relative order of the objects being compared.
+        ///     The return value has these meanings: Value Meaning Less than zero
+        ///     This instance precedes  in the sort order.
+        ///     Zero This instance occurs in the same position in the sort order as . i
+        ///     Greater than zero This instance follows  in the sort order.
         /// 
-        public int CompareTo(object? obj)
-        {
-            return CompareTo((SemVersion?)obj);
-        }
+        public int CompareTo(object? obj) => CompareTo((SemVersion?)obj);
 
         /// 
-        /// Compares the current instance with another object of the same type and returns an integer that indicates
-        /// whether the current instance precedes, follows, or occurs in the same position in the sort order as the
-        /// other object.
+        ///     Compares the current instance with another object of the same type and returns an integer that indicates
+        ///     whether the current instance precedes, follows, or occurs in the same position in the sort order as the
+        ///     other object.
         /// 
         /// An object to compare with this instance.
         /// 
-        /// A value that indicates the relative order of the objects being compared.
-        /// The return value has these meanings: Value Meaning Less than zero
-        ///  This instance precedes  in the sort order.
-        ///  Zero This instance occurs in the same position in the sort order as . i
-        ///  Greater than zero This instance follows  in the sort order.
+        ///     A value that indicates the relative order of the objects being compared.
+        ///     The return value has these meanings: Value Meaning Less than zero
+        ///     This instance precedes  in the sort order.
+        ///     Zero This instance occurs in the same position in the sort order as . i
+        ///     Greater than zero This instance follows  in the sort order.
         /// 
         public int CompareTo(SemVersion? other)
         {
             if (ReferenceEquals(other, null))
+            {
                 return 1;
+            }
 
-            var r = this.CompareByPrecedence(other);
+            var r = CompareByPrecedence(other);
             if (r != 0)
+            {
                 return r;
+            }
 
-            r = CompareComponent(this.Build, other.Build);
+            r = CompareComponent(Build, other.Build);
             return r;
         }
 
         /// 
-        /// Compares to semantic versions by precedence. This does the same as a Equals, but ignores the build information.
+        ///     Compares to semantic versions by precedence. This does the same as a Equals, but ignores the build information.
         /// 
         /// The semantic version.
         /// true if the version precedence matches.
-        public bool PrecedenceMatches(SemVersion other)
-        {
-            return CompareByPrecedence(other) == 0;
-        }
+        public bool PrecedenceMatches(SemVersion other) => CompareByPrecedence(other) == 0;
 
         /// 
-        /// Compares to semantic versions by precedence. This does the same as a Equals, but ignores the build information.
+        ///     Compares to semantic versions by precedence. This does the same as a Equals, but ignores the build information.
         /// 
         /// The semantic version.
         /// 
-        /// A value that indicates the relative order of the objects being compared.
-        /// The return value has these meanings: Value Meaning Less than zero
-        ///  This instance precedes  in the version precedence.
-        ///  Zero This instance has the same precedence as . i
-        ///  Greater than zero This instance has creater precedence as .
+        ///     A value that indicates the relative order of the objects being compared.
+        ///     The return value has these meanings: Value Meaning Less than zero
+        ///     This instance precedes  in the version precedence.
+        ///     Zero This instance has the same precedence as . i
+        ///     Greater than zero This instance has creater precedence as .
         /// 
         public int CompareByPrecedence(SemVersion other)
         {
             if (ReferenceEquals(other, null))
+            {
                 return 1;
+            }
 
-            var r = this.Major.CompareTo(other.Major);
-            if (r != 0) return r;
+            var r = Major.CompareTo(other.Major);
+            if (r != 0)
+            {
+                return r;
+            }
 
-            r = this.Minor.CompareTo(other.Minor);
-            if (r != 0) return r;
+            r = Minor.CompareTo(other.Minor);
+            if (r != 0)
+            {
+                return r;
+            }
 
-            r = this.Patch.CompareTo(other.Patch);
-            if (r != 0) return r;
+            r = Patch.CompareTo(other.Patch);
+            if (r != 0)
+            {
+                return r;
+            }
 
-            r = CompareComponent(this.Prerelease, other.Prerelease, true);
+            r = CompareComponent(Prerelease, other.Prerelease, true);
             return r;
         }
 
-        static int CompareComponent(string a, string b, bool lower = false)
+        private static int CompareComponent(string a, string b, bool lower = false)
         {
-            var aEmpty = String.IsNullOrEmpty(a);
-            var bEmpty = String.IsNullOrEmpty(b);
+            var aEmpty = string.IsNullOrEmpty(a);
+            var bEmpty = string.IsNullOrEmpty(b);
             if (aEmpty && bEmpty)
+            {
                 return 0;
+            }
 
             if (aEmpty)
+            {
                 return lower ? 1 : -1;
+            }
+
             if (bEmpty)
+            {
                 return lower ? -1 : 1;
+            }
 
             var aComps = a.Split('.');
             var bComps = b.Split('.');
 
             var minLen = Math.Min(aComps.Length, bComps.Length);
-            for (int i = 0; i < minLen; i++)
+            for (var i = 0; i < minLen; i++)
             {
                 var ac = aComps[i];
                 var bc = bComps[i];
                 int anum, bnum;
-                var isanum = Int32.TryParse(ac, out anum);
-                var isbnum = Int32.TryParse(bc, out bnum);
+                var isanum = int.TryParse(ac, out anum);
+                var isbnum = int.TryParse(bc, out bnum);
                 int r;
                 if (isanum && isbnum)
                 {
                     r = anum.CompareTo(bnum);
-                    if (r != 0) return anum.CompareTo(bnum);
+                    if (r != 0)
+                    {
+                        return anum.CompareTo(bnum);
+                    }
                 }
                 else
                 {
                     if (isanum)
+                    {
                         return -1;
+                    }
+
                     if (isbnum)
+                    {
                         return 1;
-                    r = String.CompareOrdinal(ac, bc);
+                    }
+
+                    r = string.CompareOrdinal(ac, bc);
                     if (r != 0)
+                    {
                         return r;
+                    }
                 }
             }
 
@@ -428,44 +478,48 @@ static int CompareComponent(string a, string b, bool lower = false)
         }
 
         /// 
-        /// Determines whether the specified  is equal to this instance.
+        ///     Determines whether the specified  is equal to this instance.
         /// 
         /// The  to compare with this instance.
         /// 
-        ///   true if the specified  is equal to this instance; otherwise, false.
+        ///     true if the specified  is equal to this instance; otherwise, false.
         /// 
         public override bool Equals(object? obj)
         {
             if (ReferenceEquals(obj, null))
+            {
                 return false;
+            }
 
             if (ReferenceEquals(this, obj))
+            {
                 return true;
+            }
 
             var other = (SemVersion)obj;
 
-            return this.Major == other.Major &&
-                this.Minor == other.Minor &&
-                this.Patch == other.Patch &&
-                string.Equals(this.Prerelease, other.Prerelease, StringComparison.Ordinal) &&
-                string.Equals(this.Build, other.Build, StringComparison.Ordinal);
+            return Major == other.Major &&
+                   Minor == other.Minor &&
+                   Patch == other.Patch &&
+                   string.Equals(Prerelease, other.Prerelease, StringComparison.Ordinal) &&
+                   string.Equals(Build, other.Build, StringComparison.Ordinal);
         }
 
         /// 
-        /// Returns a hash code for this instance.
+        ///     Returns a hash code for this instance.
         /// 
         /// 
-        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+        ///     A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
         /// 
         public override int GetHashCode()
         {
             unchecked
             {
-                int result = this.Major.GetHashCode();
-                result = result * 31 + this.Minor.GetHashCode();
-                result = result * 31 + this.Patch.GetHashCode();
-                result = result * 31 + this.Prerelease.GetHashCode();
-                result = result * 31 + this.Build.GetHashCode();
+                var result = Major.GetHashCode();
+                result = (result * 31) + Minor.GetHashCode();
+                result = (result * 31) + Patch.GetHashCode();
+                result = (result * 31) + Prerelease.GetHashCode();
+                result = (result * 31) + Build.GetHashCode();
                 return result;
             }
         }
@@ -474,85 +528,68 @@ public override int GetHashCode()
         [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
         public void GetObjectData(SerializationInfo info, StreamingContext context)
         {
-            if (info == null) throw new ArgumentNullException("info");
+            if (info == null)
+            {
+                throw new ArgumentNullException("info");
+            }
+
             info.AddValue("SemVersion", ToString());
         }
 #endif
 
         /// 
-        /// Implicit conversion from string to SemVersion.
+        ///     Implicit conversion from string to SemVersion.
         /// 
         /// The semantic version.
         /// The SemVersion object.
-        public static implicit operator SemVersion(string version)
-        {
-            return SemVersion.Parse(version);
-        }
+        public static implicit operator SemVersion(string version) => Parse(version);
 
         /// 
-        /// The override of the equals operator.
+        ///     The override of the equals operator.
         /// 
         /// The left value.
         /// The right value.
         /// If left is equal to right true, else false.
-        public static bool operator ==(SemVersion left, SemVersion right)
-        {
-            return SemVersion.Equals(left, right);
-        }
+        public static bool operator ==(SemVersion left, SemVersion right) => Equals(left, right);
 
         /// 
-        /// The override of the un-equal operator.
+        ///     The override of the un-equal operator.
         /// 
         /// The left value.
         /// The right value.
         /// If left is not equal to right true, else false.
-        public static bool operator !=(SemVersion left, SemVersion right)
-        {
-            return !SemVersion.Equals(left, right);
-        }
+        public static bool operator !=(SemVersion left, SemVersion right) => !Equals(left, right);
 
         /// 
-        /// The override of the greater operator.
+        ///     The override of the greater operator.
         /// 
         /// The left value.
         /// The right value.
         /// If left is greater than right true, else false.
-        public static bool operator >(SemVersion left, SemVersion right)
-        {
-            return SemVersion.Compare(left, right) > 0;
-        }
+        public static bool operator >(SemVersion left, SemVersion right) => Compare(left, right) > 0;
 
         /// 
-        /// The override of the greater than or equal operator.
+        ///     The override of the greater than or equal operator.
         /// 
         /// The left value.
         /// The right value.
         /// If left is greater than or equal to right true, else false.
-        public static bool operator >=(SemVersion left, SemVersion right)
-        {
-            return left == right || left > right;
-        }
+        public static bool operator >=(SemVersion left, SemVersion right) => left == right || left > right;
 
         /// 
-        /// The override of the less operator.
+        ///     The override of the less operator.
         /// 
         /// The left value.
         /// The right value.
         /// If left is less than right true, else false.
-        public static bool operator <(SemVersion left, SemVersion right)
-        {
-            return SemVersion.Compare(left, right) < 0;
-        }
+        public static bool operator <(SemVersion left, SemVersion right) => Compare(left, right) < 0;
 
         /// 
-        /// The override of the less than or equal operator.
+        ///     The override of the less than or equal operator.
         /// 
         /// The left value.
         /// The right value.
         /// If left is less than or equal to right true, else false.
-        public static bool operator <=(SemVersion left, SemVersion right)
-        {
-            return left == right || left < right;
-        }
+        public static bool operator <=(SemVersion left, SemVersion right) => left == right || left < right;
     }
 }
diff --git a/src/Umbraco.Core/Serialization/IConfigurationEditorJsonSerializer.cs b/src/Umbraco.Core/Serialization/IConfigurationEditorJsonSerializer.cs
index dee2e4c5db92..4e9b2606ea80 100644
--- a/src/Umbraco.Core/Serialization/IConfigurationEditorJsonSerializer.cs
+++ b/src/Umbraco.Core/Serialization/IConfigurationEditorJsonSerializer.cs
@@ -1,7 +1,5 @@
-namespace Umbraco.Cms.Core.Serialization
-{
-    public interface IConfigurationEditorJsonSerializer : IJsonSerializer
-    {
+namespace Umbraco.Cms.Core.Serialization;
 
-    }
+public interface IConfigurationEditorJsonSerializer : IJsonSerializer
+{
 }
diff --git a/src/Umbraco.Core/Serialization/IJsonSerializer.cs b/src/Umbraco.Core/Serialization/IJsonSerializer.cs
index 051055b56407..569907b0c847 100644
--- a/src/Umbraco.Core/Serialization/IJsonSerializer.cs
+++ b/src/Umbraco.Core/Serialization/IJsonSerializer.cs
@@ -1,11 +1,10 @@
-namespace Umbraco.Cms.Core.Serialization
+namespace Umbraco.Cms.Core.Serialization;
+
+public interface IJsonSerializer
 {
-    public interface IJsonSerializer
-    {
-        string Serialize(object? input);
+    string Serialize(object? input);
 
-        T? Deserialize(string input);
+    T? Deserialize(string input);
 
-        T? DeserializeSubset(string input, string key);
-    }
+    T? DeserializeSubset(string input, string key);
 }
diff --git a/src/Umbraco.Core/Services/AuditService.cs b/src/Umbraco.Core/Services/AuditService.cs
index f7560afa9338..b06941ac99a0 100644
--- a/src/Umbraco.Core/Services/AuditService.cs
+++ b/src/Umbraco.Core/Services/AuditService.cs
@@ -1,6 +1,3 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
 using Microsoft.Extensions.Logging;
 using Umbraco.Cms.Core.Events;
 using Umbraco.Cms.Core.Models;
@@ -8,231 +5,287 @@
 using Umbraco.Cms.Core.Persistence.Repositories;
 using Umbraco.Cms.Core.Scoping;
 
-namespace Umbraco.Cms.Core.Services.Implement
+namespace Umbraco.Cms.Core.Services.Implement;
+
+public sealed class AuditService : RepositoryService, IAuditService
 {
-    public sealed class AuditService : RepositoryService, IAuditService
+    private readonly IAuditEntryRepository _auditEntryRepository;
+    private readonly IAuditRepository _auditRepository;
+    private readonly Lazy _isAvailable;
+
+    public AuditService(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
+        IEventMessagesFactory eventMessagesFactory,
+        IAuditRepository auditRepository, IAuditEntryRepository auditEntryRepository)
+        : base(provider, loggerFactory, eventMessagesFactory)
+    {
+        _auditRepository = auditRepository;
+        _auditEntryRepository = auditEntryRepository;
+        _isAvailable = new Lazy(DetermineIsAvailable);
+    }
+
+    public void Add(AuditType type, int userId, int objectId, string? entityType, string comment,
+        string? parameters = null)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            _auditRepository.Save(new AuditItem(objectId, type, userId, entityType, comment, parameters));
+            scope.Complete();
+        }
+    }
+
+    public IEnumerable GetLogs(int objectId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            IEnumerable result = _auditRepository.Get(Query().Where(x => x.Id == objectId));
+            scope.Complete();
+            return result;
+        }
+    }
+
+    public IEnumerable GetUserLogs(int userId, AuditType type, DateTime? sinceDate = null)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            IEnumerable result = sinceDate.HasValue == false
+                ? _auditRepository.Get(type, Query().Where(x => x.UserId == userId))
+                : _auditRepository.Get(type,
+                    Query().Where(x => x.UserId == userId && x.CreateDate >= sinceDate.Value));
+            scope.Complete();
+            return result;
+        }
+    }
+
+    public IEnumerable GetLogs(AuditType type, DateTime? sinceDate = null)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            IEnumerable result = sinceDate.HasValue == false
+                ? _auditRepository.Get(type, Query())
+                : _auditRepository.Get(type, Query().Where(x => x.CreateDate >= sinceDate.Value));
+            scope.Complete();
+            return result;
+        }
+    }
+
+    public void CleanLogs(int maximumAgeOfLogsInMinutes)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            _auditRepository.CleanLogs(maximumAgeOfLogsInMinutes);
+            scope.Complete();
+        }
+    }
+
+    /// 
+    ///     Returns paged items in the audit trail for a given entity
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    ///     By default this will always be ordered descending (newest first)
+    /// 
+    /// 
+    ///     Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query
+    ///     or the custom filter
+    ///     so we need to do that here
+    /// 
+    /// 
+    ///     Optional filter to be applied
+    /// 
+    /// 
+    public IEnumerable GetPagedItemsByEntity(int entityId, long pageIndex, int pageSize,
+        out long totalRecords,
+        Direction orderDirection = Direction.Descending,
+        AuditType[]? auditTypeFilter = null,
+        IQuery? customFilter = null)
+    {
+        if (pageIndex < 0)
+        {
+            throw new ArgumentOutOfRangeException(nameof(pageIndex));
+        }
+
+        if (pageSize <= 0)
+        {
+            throw new ArgumentOutOfRangeException(nameof(pageSize));
+        }
+
+        if (entityId == Constants.System.Root || entityId <= 0)
+        {
+            totalRecords = 0;
+            return Enumerable.Empty();
+        }
+
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            IQuery query = Query().Where(x => x.Id == entityId);
+
+            return _auditRepository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderDirection,
+                auditTypeFilter, customFilter);
+        }
+    }
+
+    /// 
+    ///     Returns paged items in the audit trail for a given user
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    ///     By default this will always be ordered descending (newest first)
+    /// 
+    /// 
+    ///     Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query
+    ///     or the custom filter
+    ///     so we need to do that here
+    /// 
+    /// 
+    ///     Optional filter to be applied
+    /// 
+    /// 
+    public IEnumerable GetPagedItemsByUser(int userId, long pageIndex, int pageSize, out long totalRecords,
+        Direction orderDirection = Direction.Descending, AuditType[]? auditTypeFilter = null,
+        IQuery? customFilter = null)
+    {
+        if (pageIndex < 0)
+        {
+            throw new ArgumentOutOfRangeException(nameof(pageIndex));
+        }
+
+        if (pageSize <= 0)
+        {
+            throw new ArgumentOutOfRangeException(nameof(pageSize));
+        }
+
+        if (userId < Constants.Security.SuperUserId)
+        {
+            totalRecords = 0;
+            return Enumerable.Empty();
+        }
+
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            IQuery query = Query().Where(x => x.UserId == userId);
+
+            return _auditRepository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderDirection,
+                auditTypeFilter, customFilter);
+        }
+    }
+
+    /// 
+    public IAuditEntry Write(int performingUserId, string perfomingDetails, string performingIp, DateTime eventDateUtc,
+        int affectedUserId, string? affectedDetails, string eventType, string eventDetails)
     {
-        private readonly Lazy _isAvailable;
-        private readonly IAuditRepository _auditRepository;
-        private readonly IAuditEntryRepository _auditEntryRepository;
-
-        public AuditService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory,
-            IAuditRepository auditRepository, IAuditEntryRepository auditEntryRepository)
-            : base(provider, loggerFactory, eventMessagesFactory)
-        {
-            _auditRepository = auditRepository;
-            _auditEntryRepository = auditEntryRepository;
-            _isAvailable = new Lazy(DetermineIsAvailable);
-        }
-
-        public void Add(AuditType type, int userId, int objectId, string? entityType, string comment, string? parameters = null)
-        {
-            using (var scope = ScopeProvider.CreateCoreScope())
-            {
-                _auditRepository.Save(new AuditItem(objectId, type, userId, entityType, comment, parameters));
-                scope.Complete();
-            }
-        }
-
-        public IEnumerable GetLogs(int objectId)
-        {
-            using (var scope = ScopeProvider.CreateCoreScope())
-            {
-                var result = _auditRepository.Get(Query().Where(x => x.Id == objectId));
-                scope.Complete();
-                return result;
-            }
-        }
-
-        public IEnumerable GetUserLogs(int userId, AuditType type, DateTime? sinceDate = null)
-        {
-            using (var scope = ScopeProvider.CreateCoreScope())
-            {
-                var result = sinceDate.HasValue == false
-                    ? _auditRepository.Get(type, Query().Where(x => x.UserId == userId))
-                    : _auditRepository.Get(type, Query().Where(x => x.UserId == userId && x.CreateDate >= sinceDate.Value));
-                scope.Complete();
-                return result;
-            }
-        }
-
-        public IEnumerable GetLogs(AuditType type, DateTime? sinceDate = null)
-        {
-            using (var scope = ScopeProvider.CreateCoreScope())
-            {
-                var result = sinceDate.HasValue == false
-                    ? _auditRepository.Get(type, Query())
-                    : _auditRepository.Get(type, Query().Where(x => x.CreateDate >= sinceDate.Value));
-                scope.Complete();
-                return result;
-            }
-        }
-
-        public void CleanLogs(int maximumAgeOfLogsInMinutes)
-        {
-            using (var scope = ScopeProvider.CreateCoreScope())
-            {
-                _auditRepository.CleanLogs(maximumAgeOfLogsInMinutes);
-                scope.Complete();
-            }
-        }
-
-        /// 
-        /// Returns paged items in the audit trail for a given entity
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// By default this will always be ordered descending (newest first)
-        /// 
-        /// 
-        /// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
-        /// so we need to do that here
-        /// 
-        /// 
-        /// Optional filter to be applied
-        /// 
-        /// 
-        public IEnumerable GetPagedItemsByEntity(int entityId, long pageIndex, int pageSize, out long totalRecords,
-            Direction orderDirection = Direction.Descending,
-            AuditType[]? auditTypeFilter = null,
-            IQuery? customFilter = null)
-        {
-            if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex));
-            if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize));
-
-            if (entityId == Cms.Core.Constants.System.Root || entityId <= 0)
-            {
-                totalRecords = 0;
-                return Enumerable.Empty();
-            }
-
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                var query = Query().Where(x => x.Id == entityId);
-
-                return _auditRepository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderDirection, auditTypeFilter, customFilter);
-            }
-        }
-
-        /// 
-        /// Returns paged items in the audit trail for a given user
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// By default this will always be ordered descending (newest first)
-        /// 
-        /// 
-        /// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
-        /// so we need to do that here
-        /// 
-        /// 
-        /// Optional filter to be applied
-        /// 
-        /// 
-        public IEnumerable GetPagedItemsByUser(int userId, long pageIndex, int pageSize, out long totalRecords, Direction orderDirection = Direction.Descending, AuditType[]? auditTypeFilter = null, IQuery? customFilter = null)
-        {
-            if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex));
-            if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize));
-
-            if (userId < Cms.Core.Constants.Security.SuperUserId)
-            {
-                totalRecords = 0;
-                return Enumerable.Empty();
-            }
-
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                var query = Query().Where(x => x.UserId == userId);
-
-                return _auditRepository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderDirection, auditTypeFilter, customFilter);
-            }
-        }
-
-        /// 
-        public IAuditEntry Write(int performingUserId, string perfomingDetails, string performingIp, DateTime eventDateUtc, int affectedUserId, string? affectedDetails, string eventType, string eventDetails)
-        {
-            if (performingUserId < 0 && performingUserId != Cms.Core.Constants.Security.SuperUserId) throw new ArgumentOutOfRangeException(nameof(performingUserId));
-            if (string.IsNullOrWhiteSpace(perfomingDetails)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(perfomingDetails));
-            if (string.IsNullOrWhiteSpace(eventType)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(eventType));
-            if (string.IsNullOrWhiteSpace(eventDetails)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(eventDetails));
-
-            //we need to truncate the data else we'll get SQL errors
-            affectedDetails = affectedDetails?.Substring(0, Math.Min(affectedDetails.Length, Constants.Audit.DetailsLength));
-            eventDetails = eventDetails.Substring(0, Math.Min(eventDetails.Length, Constants.Audit.DetailsLength));
-
-            //validate the eventType - must contain a forward slash, no spaces, no special chars
-            var eventTypeParts = eventType.ToCharArray();
-            if (eventTypeParts.Contains('/') == false || eventTypeParts.All(c => char.IsLetterOrDigit(c) || c == '/' || c == '-') == false)
-                throw new ArgumentException(nameof(eventType) + " must contain only alphanumeric characters, hyphens and at least one '/' defining a category");
-            if (eventType.Length > Constants.Audit.EventTypeLength)
-                throw new ArgumentException($"Must be max {Constants.Audit.EventTypeLength} chars.", nameof(eventType));
-            if (performingIp != null && performingIp.Length > Constants.Audit.IpLength)
-                throw new ArgumentException($"Must be max {Constants.Audit.EventTypeLength} chars.", nameof(performingIp));
-
-            var entry = new AuditEntry
-            {
-                PerformingUserId = performingUserId,
-                PerformingDetails = perfomingDetails,
-                PerformingIp = performingIp,
-                EventDateUtc = eventDateUtc,
-                AffectedUserId = affectedUserId,
-                AffectedDetails = affectedDetails,
-                EventType = eventType,
-                EventDetails = eventDetails,
-            };
-
-            if (_isAvailable.Value == false) return entry;
-
-            using (var scope = ScopeProvider.CreateCoreScope())
-            {
-                _auditEntryRepository.Save(entry);
-                scope.Complete();
-            }
+        if (performingUserId < 0 && performingUserId != Constants.Security.SuperUserId)
+        {
+            throw new ArgumentOutOfRangeException(nameof(performingUserId));
+        }
 
+        if (string.IsNullOrWhiteSpace(perfomingDetails))
+        {
+            throw new ArgumentException("Value cannot be null or whitespace.", nameof(perfomingDetails));
+        }
+
+        if (string.IsNullOrWhiteSpace(eventType))
+        {
+            throw new ArgumentException("Value cannot be null or whitespace.", nameof(eventType));
+        }
+
+        if (string.IsNullOrWhiteSpace(eventDetails))
+        {
+            throw new ArgumentException("Value cannot be null or whitespace.", nameof(eventDetails));
+        }
+
+        //we need to truncate the data else we'll get SQL errors
+        affectedDetails =
+            affectedDetails?.Substring(0, Math.Min(affectedDetails.Length, Constants.Audit.DetailsLength));
+        eventDetails = eventDetails.Substring(0, Math.Min(eventDetails.Length, Constants.Audit.DetailsLength));
+
+        //validate the eventType - must contain a forward slash, no spaces, no special chars
+        var eventTypeParts = eventType.ToCharArray();
+        if (eventTypeParts.Contains('/') == false ||
+            eventTypeParts.All(c => char.IsLetterOrDigit(c) || c == '/' || c == '-') == false)
+        {
+            throw new ArgumentException(nameof(eventType) +
+                                        " must contain only alphanumeric characters, hyphens and at least one '/' defining a category");
+        }
+
+        if (eventType.Length > Constants.Audit.EventTypeLength)
+        {
+            throw new ArgumentException($"Must be max {Constants.Audit.EventTypeLength} chars.", nameof(eventType));
+        }
+
+        if (performingIp != null && performingIp.Length > Constants.Audit.IpLength)
+        {
+            throw new ArgumentException($"Must be max {Constants.Audit.EventTypeLength} chars.", nameof(performingIp));
+        }
+
+        var entry = new AuditEntry
+        {
+            PerformingUserId = performingUserId,
+            PerformingDetails = perfomingDetails,
+            PerformingIp = performingIp,
+            EventDateUtc = eventDateUtc,
+            AffectedUserId = affectedUserId,
+            AffectedDetails = affectedDetails,
+            EventType = eventType,
+            EventDetails = eventDetails
+        };
+
+        if (_isAvailable.Value == false)
+        {
             return entry;
         }
 
-        // TODO: Currently used in testing only, not part of the interface, need to add queryable methods to the interface instead
-        internal IEnumerable? GetAll()
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            _auditEntryRepository.Save(entry);
+            scope.Complete();
+        }
+
+        return entry;
+    }
+
+    // TODO: Currently used in testing only, not part of the interface, need to add queryable methods to the interface instead
+    internal IEnumerable? GetAll()
+    {
+        if (_isAvailable.Value == false)
         {
-            if (_isAvailable.Value == false) return Enumerable.Empty();
+            return Enumerable.Empty();
+        }
 
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _auditEntryRepository.GetMany();
-            }
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return _auditEntryRepository.GetMany();
         }
+    }
 
-        // TODO: Currently used in testing only, not part of the interface, need to add queryable methods to the interface instead
-        internal IEnumerable GetPage(long pageIndex, int pageCount, out long records)
+    // TODO: Currently used in testing only, not part of the interface, need to add queryable methods to the interface instead
+    internal IEnumerable GetPage(long pageIndex, int pageCount, out long records)
+    {
+        if (_isAvailable.Value == false)
         {
-            if (_isAvailable.Value == false)
-            {
-                records = 0;
-                return Enumerable.Empty();
-            }
+            records = 0;
+            return Enumerable.Empty();
+        }
 
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _auditEntryRepository.GetPage(pageIndex, pageCount, out records);
-            }
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return _auditEntryRepository.GetPage(pageIndex, pageCount, out records);
         }
+    }
 
-        /// 
-        /// Determines whether the repository is available.
-        /// 
-        private bool DetermineIsAvailable()
+    /// 
+    ///     Determines whether the repository is available.
+    /// 
+    private bool DetermineIsAvailable()
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _auditEntryRepository.IsAvailable();
-            }
+            return _auditEntryRepository.IsAvailable();
         }
     }
 }
diff --git a/src/Umbraco.Core/Services/BasicAuthService.cs b/src/Umbraco.Core/Services/BasicAuthService.cs
index 3021768bfe1d..2fe4d7a2054e 100644
--- a/src/Umbraco.Core/Services/BasicAuthService.cs
+++ b/src/Umbraco.Core/Services/BasicAuthService.cs
@@ -1,48 +1,46 @@
-using System;
 using System.Net;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Options;
 using Umbraco.Cms.Core.Configuration.Models;
 using Umbraco.Cms.Web.Common.DependencyInjection;
 
-namespace Umbraco.Cms.Core.Services.Implement
+namespace Umbraco.Cms.Core.Services.Implement;
+
+public class BasicAuthService : IBasicAuthService
 {
-    public class BasicAuthService : IBasicAuthService
-    {
-        private readonly IIpAddressUtilities _ipAddressUtilities;
-        private BasicAuthSettings _basicAuthSettings;
+    private readonly IIpAddressUtilities _ipAddressUtilities;
+    private BasicAuthSettings _basicAuthSettings;
 
-        // Scheduled for removal in v12
-        [Obsolete("Please use the contructor that takes an IIpadressUtilities instead")]
-        public BasicAuthService(IOptionsMonitor optionsMonitor)
+    // Scheduled for removal in v12
+    [Obsolete("Please use the contructor that takes an IIpadressUtilities instead")]
+    public BasicAuthService(IOptionsMonitor optionsMonitor)
         : this(optionsMonitor, StaticServiceProvider.Instance.GetRequiredService())
-        {
-            _basicAuthSettings = optionsMonitor.CurrentValue;
+    {
+        _basicAuthSettings = optionsMonitor.CurrentValue;
 
-            optionsMonitor.OnChange(basicAuthSettings => _basicAuthSettings = basicAuthSettings);
-        }
+        optionsMonitor.OnChange(basicAuthSettings => _basicAuthSettings = basicAuthSettings);
+    }
 
-        public BasicAuthService(IOptionsMonitor optionsMonitor, IIpAddressUtilities ipAddressUtilities)
-        {
-            _ipAddressUtilities = ipAddressUtilities;
-            _basicAuthSettings = optionsMonitor.CurrentValue;
+    public BasicAuthService(IOptionsMonitor optionsMonitor, IIpAddressUtilities ipAddressUtilities)
+    {
+        _ipAddressUtilities = ipAddressUtilities;
+        _basicAuthSettings = optionsMonitor.CurrentValue;
 
-            optionsMonitor.OnChange(basicAuthSettings => _basicAuthSettings = basicAuthSettings);
-        }
+        optionsMonitor.OnChange(basicAuthSettings => _basicAuthSettings = basicAuthSettings);
+    }
 
-        public bool IsBasicAuthEnabled() => _basicAuthSettings.Enabled;
+    public bool IsBasicAuthEnabled() => _basicAuthSettings.Enabled;
 
-        public bool IsIpAllowListed(IPAddress clientIpAddress)
+    public bool IsIpAllowListed(IPAddress clientIpAddress)
+    {
+        foreach (var allowedIpString in _basicAuthSettings.AllowedIPs)
         {
-            foreach (var allowedIpString in _basicAuthSettings.AllowedIPs)
+            if (_ipAddressUtilities.IsAllowListed(clientIpAddress, allowedIpString))
             {
-                if (_ipAddressUtilities.IsAllowListed(clientIpAddress, allowedIpString))
-                {
-                    return true;
-                }
+                return true;
             }
-
-            return false;
         }
+
+        return false;
     }
 }
diff --git a/src/Umbraco.Core/Services/Changes/ContentTypeChange.cs b/src/Umbraco.Core/Services/Changes/ContentTypeChange.cs
index f82340681892..5561c776d41f 100644
--- a/src/Umbraco.Core/Services/Changes/ContentTypeChange.cs
+++ b/src/Umbraco.Core/Services/Changes/ContentTypeChange.cs
@@ -1,21 +1,17 @@
-using System.Collections.Generic;
-using System.Linq;
-using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services.Changes
+namespace Umbraco.Cms.Core.Services.Changes;
+
+public class ContentTypeChange
+    where TItem : class, IContentTypeComposition
 {
-    public class ContentTypeChange
-        where TItem : class, IContentTypeComposition
+    public ContentTypeChange(TItem item, ContentTypeChangeTypes changeTypes)
     {
-        public ContentTypeChange(TItem item, ContentTypeChangeTypes changeTypes)
-        {
-            Item = item;
-            ChangeTypes = changeTypes;
-        }
-
-        public TItem Item { get; }
-
-        public ContentTypeChangeTypes ChangeTypes { get; set; }
+        Item = item;
+        ChangeTypes = changeTypes;
     }
 
+    public TItem Item { get; }
+
+    public ContentTypeChangeTypes ChangeTypes { get; set; }
 }
diff --git a/src/Umbraco.Core/Services/Changes/ContentTypeChangeExtensions.cs b/src/Umbraco.Core/Services/Changes/ContentTypeChangeExtensions.cs
index 9489e52d42bc..560599a17e21 100644
--- a/src/Umbraco.Core/Services/Changes/ContentTypeChangeExtensions.cs
+++ b/src/Umbraco.Core/Services/Changes/ContentTypeChangeExtensions.cs
@@ -1,33 +1,21 @@
 // Copyright (c) Umbraco.
 // See LICENSE for more details.
 
-using System.Collections.Generic;
-using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Services.Changes;
 
-namespace Umbraco.Extensions
-{
-    public static class ContentTypeChangeExtensions
-    {
+namespace Umbraco.Extensions;
 
-        public static bool HasType(this ContentTypeChangeTypes change, ContentTypeChangeTypes type)
-        {
-            return (change & type) != ContentTypeChangeTypes.None;
-        }
+public static class ContentTypeChangeExtensions
+{
+    public static bool HasType(this ContentTypeChangeTypes change, ContentTypeChangeTypes type) =>
+        (change & type) != ContentTypeChangeTypes.None;
 
-        public static bool HasTypesAll(this ContentTypeChangeTypes change, ContentTypeChangeTypes types)
-        {
-            return (change & types) == types;
-        }
+    public static bool HasTypesAll(this ContentTypeChangeTypes change, ContentTypeChangeTypes types) =>
+        (change & types) == types;
 
-        public static bool HasTypesAny(this ContentTypeChangeTypes change, ContentTypeChangeTypes types)
-        {
-            return (change & types) != ContentTypeChangeTypes.None;
-        }
+    public static bool HasTypesAny(this ContentTypeChangeTypes change, ContentTypeChangeTypes types) =>
+        (change & types) != ContentTypeChangeTypes.None;
 
-        public static bool HasTypesNone(this ContentTypeChangeTypes change, ContentTypeChangeTypes types)
-        {
-            return (change & types) == ContentTypeChangeTypes.None;
-        }
-    }
+    public static bool HasTypesNone(this ContentTypeChangeTypes change, ContentTypeChangeTypes types) =>
+        (change & types) == ContentTypeChangeTypes.None;
 }
diff --git a/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs b/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs
index cd4965dc2b2e..21cce2403296 100644
--- a/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs
+++ b/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs
@@ -1,30 +1,27 @@
-using System;
+namespace Umbraco.Cms.Core.Services.Changes;
 
-namespace Umbraco.Cms.Core.Services.Changes
+[Flags]
+public enum ContentTypeChangeTypes : byte
 {
-    [Flags]
-    public enum ContentTypeChangeTypes : byte
-    {
-        None = 0,
+    None = 0,
 
-        /// 
-        /// Item type has been created, no impact
-        /// 
-        Create = 1,
+    /// 
+    ///     Item type has been created, no impact
+    /// 
+    Create = 1,
 
-        /// 
-        /// Content type changes impact only the Content type being saved
-        /// 
-        RefreshMain = 2,
+    /// 
+    ///     Content type changes impact only the Content type being saved
+    /// 
+    RefreshMain = 2,
 
-        /// 
-        /// Content type changes impacts the content type being saved and others used that are composed of it
-        /// 
-        RefreshOther = 4, // changed, other change
+    /// 
+    ///     Content type changes impacts the content type being saved and others used that are composed of it
+    /// 
+    RefreshOther = 4, // changed, other change
 
-        /// 
-        /// Content type was removed
-        /// 
-        Remove = 8
-    }
+    /// 
+    ///     Content type was removed
+    /// 
+    Remove = 8
 }
diff --git a/src/Umbraco.Core/Services/Changes/DomainChangeTypes.cs b/src/Umbraco.Core/Services/Changes/DomainChangeTypes.cs
index 25bf48e55aaf..28902c7a7eee 100644
--- a/src/Umbraco.Core/Services/Changes/DomainChangeTypes.cs
+++ b/src/Umbraco.Core/Services/Changes/DomainChangeTypes.cs
@@ -1,10 +1,9 @@
-namespace Umbraco.Cms.Core.Services.Changes
+namespace Umbraco.Cms.Core.Services.Changes;
+
+public enum DomainChangeTypes : byte
 {
-    public enum DomainChangeTypes : byte
-    {
-        None = 0,
-        RefreshAll = 1,
-        Refresh = 2,
-        Remove = 3
-    }
+    None = 0,
+    RefreshAll = 1,
+    Refresh = 2,
+    Remove = 3
 }
diff --git a/src/Umbraco.Core/Services/Changes/TreeChange.cs b/src/Umbraco.Core/Services/Changes/TreeChange.cs
index f306a796cccb..97aaf4c4ea26 100644
--- a/src/Umbraco.Core/Services/Changes/TreeChange.cs
+++ b/src/Umbraco.Core/Services/Changes/TreeChange.cs
@@ -1,36 +1,27 @@
-using System.Collections.Generic;
-using System.Linq;
+namespace Umbraco.Cms.Core.Services.Changes;
 
-namespace Umbraco.Cms.Core.Services.Changes
+public class TreeChange
 {
-    public class TreeChange
+    public TreeChange(TItem changedItem, TreeChangeTypes changeTypes)
     {
-        public TreeChange(TItem changedItem, TreeChangeTypes changeTypes)
-        {
-            Item = changedItem;
-            ChangeTypes = changeTypes;
-        }
-
-        public TItem Item { get; }
-        public TreeChangeTypes ChangeTypes { get; }
+        Item = changedItem;
+        ChangeTypes = changeTypes;
+    }
 
-        public EventArgs ToEventArgs()
-        {
-            return new EventArgs(this);
-        }
+    public TItem Item { get; }
+    public TreeChangeTypes ChangeTypes { get; }
 
-        public class EventArgs : System.EventArgs
-        {
-            public EventArgs(IEnumerable> changes)
-            {
-                Changes = changes.ToArray();
-            }
+    public EventArgs ToEventArgs() => new EventArgs(this);
 
-            public EventArgs(TreeChange change)
-                : this(new[] { change })
-            { }
+    public class EventArgs : System.EventArgs
+    {
+        public EventArgs(IEnumerable> changes) => Changes = changes.ToArray();
 
-            public IEnumerable> Changes { get; private set; }
+        public EventArgs(TreeChange change)
+            : this(new[] {change})
+        {
         }
+
+        public IEnumerable> Changes { get; }
     }
 }
diff --git a/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs b/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs
index 5de6ae984726..1e15da177716 100644
--- a/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs
+++ b/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs
@@ -1,36 +1,23 @@
 // Copyright (c) Umbraco.
 // See LICENSE for more details.
 
-using System.Collections.Generic;
 using Umbraco.Cms.Core.Services.Changes;
 
-namespace Umbraco.Extensions
+namespace Umbraco.Extensions;
+
+public static class TreeChangeExtensions
 {
-    public static class TreeChangeExtensions
-    {
-        public static TreeChange.EventArgs ToEventArgs(this IEnumerable> changes)
-        {
-            return new TreeChange.EventArgs(changes);
-        }
+    public static TreeChange.EventArgs ToEventArgs(this IEnumerable> changes) =>
+        new TreeChange.EventArgs(changes);
 
-        public static bool HasType(this TreeChangeTypes change, TreeChangeTypes type)
-        {
-            return (change & type) != TreeChangeTypes.None;
-        }
+    public static bool HasType(this TreeChangeTypes change, TreeChangeTypes type) =>
+        (change & type) != TreeChangeTypes.None;
 
-        public static bool HasTypesAll(this TreeChangeTypes change, TreeChangeTypes types)
-        {
-            return (change & types) == types;
-        }
+    public static bool HasTypesAll(this TreeChangeTypes change, TreeChangeTypes types) => (change & types) == types;
 
-        public static bool HasTypesAny(this TreeChangeTypes change, TreeChangeTypes types)
-        {
-            return (change & types) != TreeChangeTypes.None;
-        }
+    public static bool HasTypesAny(this TreeChangeTypes change, TreeChangeTypes types) =>
+        (change & types) != TreeChangeTypes.None;
 
-        public static bool HasTypesNone(this TreeChangeTypes change, TreeChangeTypes types)
-        {
-            return (change & types) == TreeChangeTypes.None;
-        }
-    }
+    public static bool HasTypesNone(this TreeChangeTypes change, TreeChangeTypes types) =>
+        (change & types) == TreeChangeTypes.None;
 }
diff --git a/src/Umbraco.Core/Services/Changes/TreeChangeTypes.cs b/src/Umbraco.Core/Services/Changes/TreeChangeTypes.cs
index 9ef231ac066c..88dcb85f54b6 100644
--- a/src/Umbraco.Core/Services/Changes/TreeChangeTypes.cs
+++ b/src/Umbraco.Core/Services/Changes/TreeChangeTypes.cs
@@ -1,25 +1,22 @@
-using System;
+namespace Umbraco.Cms.Core.Services.Changes;
 
-namespace Umbraco.Cms.Core.Services.Changes
+[Flags]
+public enum TreeChangeTypes : byte
 {
-    [Flags]
-    public enum TreeChangeTypes : byte
-    {
-        None = 0,
+    None = 0,
 
-        // all items have been refreshed
-        RefreshAll = 1,
+    // all items have been refreshed
+    RefreshAll = 1,
 
-        // an item node has been refreshed
-        // with only local impact
-        RefreshNode = 2,
+    // an item node has been refreshed
+    // with only local impact
+    RefreshNode = 2,
 
-        // an item node has been refreshed
-        // with branch impact
-        RefreshBranch = 4,
+    // an item node has been refreshed
+    // with branch impact
+    RefreshBranch = 4,
 
-        // an item node has been removed
-        // never to return
-        Remove = 8,
-    }
+    // an item node has been removed
+    // never to return
+    Remove = 8
 }
diff --git a/src/Umbraco.Core/Services/ConsentService.cs b/src/Umbraco.Core/Services/ConsentService.cs
index d37e2e4d0f5f..d48e0c9a9a4a 100644
--- a/src/Umbraco.Core/Services/ConsentService.cs
+++ b/src/Umbraco.Core/Services/ConsentService.cs
@@ -1,81 +1,110 @@
-using System;
-using System.Collections.Generic;
 using Microsoft.Extensions.Logging;
 using Umbraco.Cms.Core.Events;
 using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Persistence.Querying;
 using Umbraco.Cms.Core.Persistence.Repositories;
 using Umbraco.Cms.Core.Scoping;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Implements .
+/// 
+internal class ConsentService : RepositoryService, IConsentService
 {
+    private readonly IConsentRepository _consentRepository;
+
     /// 
-    /// Implements .
+    ///     Initializes a new instance of the  class.
     /// 
-    internal class ConsentService : RepositoryService, IConsentService
+    public ConsentService(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
+        IEventMessagesFactory eventMessagesFactory, IConsentRepository consentRepository)
+        : base(provider, loggerFactory, eventMessagesFactory) =>
+        _consentRepository = consentRepository;
+
+    /// 
+    public IConsent RegisterConsent(string source, string context, string action, ConsentState state,
+        string? comment = null)
     {
-        private readonly IConsentRepository _consentRepository;
+        // prevent stupid states
+        var v = 0;
+        if ((state & ConsentState.Pending) > 0)
+        {
+            v++;
+        }
+
+        if ((state & ConsentState.Granted) > 0)
+        {
+            v++;
+        }
+
+        if ((state & ConsentState.Revoked) > 0)
+        {
+            v++;
+        }
 
-        /// 
-        /// Initializes a new instance of the  class.
-        /// 
-        public ConsentService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IConsentRepository consentRepository)
-            : base(provider, loggerFactory, eventMessagesFactory)
+        if (v != 1)
         {
-            _consentRepository = consentRepository;
+            throw new ArgumentException("Invalid state.", nameof(state));
         }
 
-        /// 
-        public IConsent RegisterConsent(string source, string context, string action, ConsentState state, string? comment = null)
+        var consent = new Consent
         {
-            // prevent stupid states
-            var v = 0;
-            if ((state & ConsentState.Pending) > 0) v++;
-            if ((state & ConsentState.Granted) > 0) v++;
-            if ((state & ConsentState.Revoked) > 0) v++;
-            if (v != 1)
-                throw new ArgumentException("Invalid state.", nameof(state));
-
-            var consent = new Consent
+            Current = true,
+            Source = source,
+            Context = context,
+            Action = action,
+            CreateDate = DateTime.Now,
+            State = state,
+            Comment = comment
+        };
+
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            _consentRepository.ClearCurrent(source, context, action);
+            _consentRepository.Save(consent);
+            scope.Complete();
+        }
+
+        return consent;
+    }
+
+    /// 
+    public IEnumerable LookupConsent(string? source = null, string? context = null, string? action = null,
+        bool sourceStartsWith = false, bool contextStartsWith = false, bool actionStartsWith = false,
+        bool includeHistory = false)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            IQuery query = Query();
+
+            if (string.IsNullOrWhiteSpace(source) == false)
             {
-                Current = true,
-                Source = source,
-                Context = context,
-                Action = action,
-                CreateDate = DateTime.Now,
-                State = state,
-                Comment = comment
-            };
-
-            using (var scope = ScopeProvider.CreateCoreScope())
+                query = sourceStartsWith
+                    ? query.Where(x => x.Source!.StartsWith(source))
+                    : query.Where(x => x.Source == source);
+            }
+
+            if (string.IsNullOrWhiteSpace(context) == false)
             {
-                _consentRepository.ClearCurrent(source, context, action);
-                _consentRepository.Save(consent);
-                scope.Complete();
+                query = contextStartsWith
+                    ? query.Where(x => x.Context!.StartsWith(context))
+                    : query.Where(x => x.Context == context);
             }
 
-            return consent;
-        }
+            if (string.IsNullOrWhiteSpace(action) == false)
+            {
+                query = actionStartsWith
+                    ? query.Where(x => x.Action!.StartsWith(action))
+                    : query.Where(x => x.Action == action);
+            }
 
-        /// 
-        public IEnumerable LookupConsent(string? source = null, string? context = null, string? action = null,
-            bool sourceStartsWith = false, bool contextStartsWith = false, bool actionStartsWith = false,
-            bool includeHistory = false)
-        {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
+            if (includeHistory == false)
             {
-                var query = Query();
-
-                if (string.IsNullOrWhiteSpace(source) == false)
-                    query = sourceStartsWith ? query.Where(x => x.Source!.StartsWith(source)) : query.Where(x => x.Source == source);
-                if (string.IsNullOrWhiteSpace(context) == false)
-                    query = contextStartsWith ? query.Where(x => x.Context!.StartsWith(context)) : query.Where(x => x.Context == context);
-                if (string.IsNullOrWhiteSpace(action) == false)
-                    query = actionStartsWith ? query.Where(x => x.Action!.StartsWith(action)) : query.Where(x => x.Action == action);
-                if (includeHistory == false)
-                    query = query.Where(x => x.Current);
-
-                return _consentRepository.Get(query);
+                query = query.Where(x => x.Current);
             }
+
+            return _consentRepository.Get(query);
         }
     }
 }
diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs
index a2fa13a34689..c2823526153f 100644
--- a/src/Umbraco.Core/Services/ContentService.cs
+++ b/src/Umbraco.Core/Services/ContentService.cs
@@ -1,6 +1,3 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
 using Microsoft.Extensions.Logging;
 using Umbraco.Cms.Core.Events;
 using Umbraco.Cms.Core.Exceptions;
@@ -16,1483 +13,1514 @@
 using Umbraco.Cms.Core.Strings;
 using Umbraco.Extensions;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Implements the content service.
+/// 
+public class ContentService : RepositoryService, IContentService
 {
-    /// 
-    ///     Implements the content service.
-    /// 
-    public class ContentService : RepositoryService, IContentService
+    private readonly IAuditRepository _auditRepository;
+    private readonly IContentTypeRepository _contentTypeRepository;
+    private readonly IDocumentBlueprintRepository _documentBlueprintRepository;
+    private readonly IDocumentRepository _documentRepository;
+    private readonly IEntityRepository _entityRepository;
+    private readonly ILanguageRepository _languageRepository;
+    private readonly ILogger _logger;
+    private readonly Lazy _propertyValidationService;
+    private readonly IShortStringHelper _shortStringHelper;
+    private IQuery? _queryNotTrashed;
+
+    #region Constructors
+
+    public ContentService(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
+        IEventMessagesFactory eventMessagesFactory,
+        IDocumentRepository documentRepository, IEntityRepository entityRepository,
+        IAuditRepository auditRepository,
+        IContentTypeRepository contentTypeRepository, IDocumentBlueprintRepository documentBlueprintRepository,
+        ILanguageRepository languageRepository,
+        Lazy propertyValidationService, IShortStringHelper shortStringHelper)
+        : base(provider, loggerFactory, eventMessagesFactory)
     {
-        private readonly IAuditRepository _auditRepository;
-        private readonly IContentTypeRepository _contentTypeRepository;
-        private readonly IDocumentBlueprintRepository _documentBlueprintRepository;
-        private readonly IDocumentRepository _documentRepository;
-        private readonly IEntityRepository _entityRepository;
-        private readonly ILanguageRepository _languageRepository;
-        private readonly ILogger _logger;
-        private readonly Lazy _propertyValidationService;
-        private readonly IShortStringHelper _shortStringHelper;
-        private IQuery? _queryNotTrashed;
+        _documentRepository = documentRepository;
+        _entityRepository = entityRepository;
+        _auditRepository = auditRepository;
+        _contentTypeRepository = contentTypeRepository;
+        _documentBlueprintRepository = documentBlueprintRepository;
+        _languageRepository = languageRepository;
+        _propertyValidationService = propertyValidationService;
+        _shortStringHelper = shortStringHelper;
+        _logger = loggerFactory.CreateLogger();
+    }
 
-        #region Constructors
+    #endregion
 
-        public ContentService(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
-            IEventMessagesFactory eventMessagesFactory,
-            IDocumentRepository documentRepository, IEntityRepository entityRepository,
-            IAuditRepository auditRepository,
-            IContentTypeRepository contentTypeRepository, IDocumentBlueprintRepository documentBlueprintRepository,
-            ILanguageRepository languageRepository,
-            Lazy propertyValidationService, IShortStringHelper shortStringHelper)
-            : base(provider, loggerFactory, eventMessagesFactory)
-        {
-            _documentRepository = documentRepository;
-            _entityRepository = entityRepository;
-            _auditRepository = auditRepository;
-            _contentTypeRepository = contentTypeRepository;
-            _documentBlueprintRepository = documentBlueprintRepository;
-            _languageRepository = languageRepository;
-            _propertyValidationService = propertyValidationService;
-            _shortStringHelper = shortStringHelper;
-            _logger = loggerFactory.CreateLogger();
-        }
+    #region Static queries
 
-        #endregion
+    // lazy-constructed because when the ctor runs, the query factory may not be ready
 
-        #region Static queries
+    private IQuery QueryNotTrashed =>
+        _queryNotTrashed ??= Query().Where(x => x.Trashed == false);
 
-        // lazy-constructed because when the ctor runs, the query factory may not be ready
+    #endregion
 
-        private IQuery QueryNotTrashed =>
-            _queryNotTrashed ??= Query().Where(x => x.Trashed == false);
+    #region Rollback
 
-        #endregion
+    public OperationResult Rollback(int id, int versionId, string culture = "*",
+        int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
 
-        #region Rollback
+        // Get the current copy of the node
+        IContent? content = GetById(id);
 
-        public OperationResult Rollback(int id, int versionId, string culture = "*",
-            int userId = Constants.Security.SuperUserId)
-        {
-            EventMessages evtMsgs = EventMessagesFactory.Get();
+        // Get the version
+        IContent? version = GetVersion(versionId);
 
-            // Get the current copy of the node
-            IContent? content = GetById(id);
+        // Good old null checks
+        if (content == null || version == null || content.Trashed)
+        {
+            return new OperationResult(OperationResultType.FailedCannot, evtMsgs);
+        }
 
-            // Get the version
-            IContent? version = GetVersion(versionId);
+        // Store the result of doing the save of content for the rollback
+        OperationResult rollbackSaveResult;
 
-            // Good old null checks
-            if (content == null || version == null || content.Trashed)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            var rollingBackNotification = new ContentRollingBackNotification(content, evtMsgs);
+            if (scope.Notifications.PublishCancelable(rollingBackNotification))
             {
-                return new OperationResult(OperationResultType.FailedCannot, evtMsgs);
+                scope.Complete();
+                return OperationResult.Cancel(evtMsgs);
             }
 
-            // Store the result of doing the save of content for the rollback
-            OperationResult rollbackSaveResult;
-
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                var rollingBackNotification = new ContentRollingBackNotification(content, evtMsgs);
-                if (scope.Notifications.PublishCancelable(rollingBackNotification))
-                {
-                    scope.Complete();
-                    return OperationResult.Cancel(evtMsgs);
-                }
-
-                // Copy the changes from the version
-                content.CopyFrom(version, culture);
-
-                // Save the content for the rollback
-                rollbackSaveResult = Save(content, userId);
+            // Copy the changes from the version
+            content.CopyFrom(version, culture);
 
-                // Depending on the save result - is what we log & audit along with what we return
-                if (rollbackSaveResult.Success == false)
-                {
-                    // Log the error/warning
-                    _logger.LogError(
-                        "User '{UserId}' was unable to rollback content '{ContentId}' to version '{VersionId}'", userId,
-                        id, versionId);
-                }
-                else
-                {
-                    scope.Notifications.Publish(
-                        new ContentRolledBackNotification(content, evtMsgs).WithStateFrom(rollingBackNotification));
+            // Save the content for the rollback
+            rollbackSaveResult = Save(content, userId);
 
-                    // Logging & Audit message
-                    _logger.LogInformation("User '{UserId}' rolled back content '{ContentId}' to version '{VersionId}'",
-                        userId, id, versionId);
-                    Audit(AuditType.RollBack, userId, id,
-                        $"Content '{content.Name}' was rolled back to version '{versionId}'");
-                }
+            // Depending on the save result - is what we log & audit along with what we return
+            if (rollbackSaveResult.Success == false)
+            {
+                // Log the error/warning
+                _logger.LogError(
+                    "User '{UserId}' was unable to rollback content '{ContentId}' to version '{VersionId}'", userId,
+                    id, versionId);
+            }
+            else
+            {
+                scope.Notifications.Publish(
+                    new ContentRolledBackNotification(content, evtMsgs).WithStateFrom(rollingBackNotification));
 
-                scope.Complete();
+                // Logging & Audit message
+                _logger.LogInformation("User '{UserId}' rolled back content '{ContentId}' to version '{VersionId}'",
+                    userId, id, versionId);
+                Audit(AuditType.RollBack, userId, id,
+                    $"Content '{content.Name}' was rolled back to version '{versionId}'");
             }
 
-            return rollbackSaveResult;
+            scope.Complete();
         }
 
-        #endregion
+        return rollbackSaveResult;
+    }
+
+    #endregion
 
-        #region Count
+    #region Count
 
-        public int CountPublished(string? contentTypeAlias = null)
+    public int CountPublished(string? contentTypeAlias = null)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                return _documentRepository.CountPublished(contentTypeAlias);
-            }
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _documentRepository.CountPublished(contentTypeAlias);
         }
+    }
 
-        public int Count(string? contentTypeAlias = null)
+    public int Count(string? contentTypeAlias = null)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                return _documentRepository.Count(contentTypeAlias);
-            }
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _documentRepository.Count(contentTypeAlias);
         }
+    }
 
-        public int CountChildren(int parentId, string? contentTypeAlias = null)
+    public int CountChildren(int parentId, string? contentTypeAlias = null)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                return _documentRepository.CountChildren(parentId, contentTypeAlias);
-            }
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _documentRepository.CountChildren(parentId, contentTypeAlias);
         }
+    }
 
-        public int CountDescendants(int parentId, string? contentTypeAlias = null)
+    public int CountDescendants(int parentId, string? contentTypeAlias = null)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                return _documentRepository.CountDescendants(parentId, contentTypeAlias);
-            }
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _documentRepository.CountDescendants(parentId, contentTypeAlias);
         }
+    }
 
-        #endregion
+    #endregion
 
-        #region Permissions
+    #region Permissions
 
-        /// 
-        ///     Used to bulk update the permissions set for a content item. This will replace all permissions
-        ///     assigned to an entity with a list of user id & permission pairs.
-        /// 
-        /// 
-        public void SetPermissions(EntityPermissionSet permissionSet)
+    /// 
+    ///     Used to bulk update the permissions set for a content item. This will replace all permissions
+    ///     assigned to an entity with a list of user id & permission pairs.
+    /// 
+    /// 
+    public void SetPermissions(EntityPermissionSet permissionSet)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(Constants.Locks.ContentTree);
-                _documentRepository.ReplaceContentPermissions(permissionSet);
-                scope.Complete();
-            }
+            scope.WriteLock(Constants.Locks.ContentTree);
+            _documentRepository.ReplaceContentPermissions(permissionSet);
+            scope.Complete();
         }
+    }
 
-        /// 
-        ///     Assigns a single permission to the current content item for the specified group ids
-        /// 
-        /// 
-        /// 
-        /// 
-        public void SetPermission(IContent entity, char permission, IEnumerable groupIds)
+    /// 
+    ///     Assigns a single permission to the current content item for the specified group ids
+    /// 
+    /// 
+    /// 
+    /// 
+    public void SetPermission(IContent entity, char permission, IEnumerable groupIds)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(Constants.Locks.ContentTree);
-                _documentRepository.AssignEntityPermission(entity, permission, groupIds);
-                scope.Complete();
-            }
+            scope.WriteLock(Constants.Locks.ContentTree);
+            _documentRepository.AssignEntityPermission(entity, permission, groupIds);
+            scope.Complete();
         }
+    }
 
-        /// 
-        ///     Returns implicit/inherited permissions assigned to the content item for all user groups
-        /// 
-        /// 
-        /// 
-        public EntityPermissionCollection GetPermissions(IContent content)
+    /// 
+    ///     Returns implicit/inherited permissions assigned to the content item for all user groups
+    /// 
+    /// 
+    /// 
+    public EntityPermissionCollection GetPermissions(IContent content)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                return _documentRepository.GetPermissionsForEntity(content.Id);
-            }
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _documentRepository.GetPermissionsForEntity(content.Id);
         }
+    }
 
-        #endregion
+    #endregion
 
-        #region Create
+    #region Create
 
-        /// 
-        ///     Creates an  object using the alias of the 
-        ///     that this Content should based on.
-        /// 
-        /// 
-        ///     Note that using this method will simply return a new IContent without any identity
-        ///     as it has not yet been persisted. It is intended as a shortcut to creating new content objects
-        ///     that does not invoke a save operation against the database.
-        /// 
-        /// Name of the Content object
-        /// Id of Parent for the new Content
-        /// Alias of the 
-        /// Optional id of the user creating the content
-        /// 
-        ///     
-        /// 
-        public IContent Create(string name, Guid parentId, string contentTypeAlias,
-            int userId = Constants.Security.SuperUserId)
-        {
-            // TODO: what about culture?
+    /// 
+    ///     Creates an  object using the alias of the 
+    ///     that this Content should based on.
+    /// 
+    /// 
+    ///     Note that using this method will simply return a new IContent without any identity
+    ///     as it has not yet been persisted. It is intended as a shortcut to creating new content objects
+    ///     that does not invoke a save operation against the database.
+    /// 
+    /// Name of the Content object
+    /// Id of Parent for the new Content
+    /// Alias of the 
+    /// Optional id of the user creating the content
+    /// 
+    ///     
+    /// 
+    public IContent Create(string name, Guid parentId, string contentTypeAlias,
+        int userId = Constants.Security.SuperUserId)
+    {
+        // TODO: what about culture?
 
-            IContent? parent = GetById(parentId);
-            return Create(name, parent, contentTypeAlias, userId);
-        }
+        IContent? parent = GetById(parentId);
+        return Create(name, parent, contentTypeAlias, userId);
+    }
 
-        /// 
-        ///     Creates an  object of a specified content type.
-        /// 
-        /// 
-        ///     This method simply returns a new, non-persisted, IContent without any identity. It
-        ///     is intended as a shortcut to creating new content objects that does not invoke a save
-        ///     operation against the database.
-        /// 
-        /// The name of the content object.
-        /// The identifier of the parent, or -1.
-        /// The alias of the content type.
-        /// The optional id of the user creating the content.
-        /// The content object.
-        public IContent Create(string name, int parentId, string contentTypeAlias,
-            int userId = Constants.Security.SuperUserId)
-        {
-            // TODO: what about culture?
+    /// 
+    ///     Creates an  object of a specified content type.
+    /// 
+    /// 
+    ///     This method simply returns a new, non-persisted, IContent without any identity. It
+    ///     is intended as a shortcut to creating new content objects that does not invoke a save
+    ///     operation against the database.
+    /// 
+    /// The name of the content object.
+    /// The identifier of the parent, or -1.
+    /// The alias of the content type.
+    /// The optional id of the user creating the content.
+    /// The content object.
+    public IContent Create(string name, int parentId, string contentTypeAlias,
+        int userId = Constants.Security.SuperUserId)
+    {
+        // TODO: what about culture?
+
+        IContentType contentType = GetContentType(contentTypeAlias);
+        return Create(name, parentId, contentType, userId);
+    }
 
-            IContentType contentType = GetContentType(contentTypeAlias);
-            return Create(name, parentId, contentType, userId);
+    /// 
+    ///     Creates an  object of a specified content type.
+    /// 
+    /// 
+    ///     This method simply returns a new, non-persisted, IContent without any identity. It
+    ///     is intended as a shortcut to creating new content objects that does not invoke a save
+    ///     operation against the database.
+    /// 
+    /// The name of the content object.
+    /// The identifier of the parent, or -1.
+    /// The content type of the content
+    /// The optional id of the user creating the content.
+    /// The content object.
+    public IContent Create(string name, int parentId, IContentType contentType,
+        int userId = Constants.Security.SuperUserId)
+    {
+        if (contentType is null)
+        {
+            throw new ArgumentException("Content type must be specified", nameof(contentType));
         }
 
-        /// 
-        ///     Creates an  object of a specified content type.
-        /// 
-        /// 
-        ///     This method simply returns a new, non-persisted, IContent without any identity. It
-        ///     is intended as a shortcut to creating new content objects that does not invoke a save
-        ///     operation against the database.
-        /// 
-        /// The name of the content object.
-        /// The identifier of the parent, or -1.
-        /// The content type of the content
-        /// The optional id of the user creating the content.
-        /// The content object.
-        public IContent Create(string name, int parentId, IContentType contentType,
-            int userId = Constants.Security.SuperUserId)
+        IContent? parent = parentId > 0 ? GetById(parentId) : null;
+        if (parentId > 0 && parent is null)
         {
-            if (contentType is null)
-            {
-                throw new ArgumentException("Content type must be specified", nameof(contentType));
-            }
+            throw new ArgumentException("No content with that id.", nameof(parentId));
+        }
 
-            IContent? parent = parentId > 0 ? GetById(parentId) : null;
-            if (parentId > 0 && parent is null)
-            {
-                throw new ArgumentException("No content with that id.", nameof(parentId));
-            }
+        var content = new Content(name, parentId, contentType, userId);
 
-            var content = new Content(name, parentId, contentType, userId);
+        return content;
+    }
 
-            return content;
+    /// 
+    ///     Creates an  object of a specified content type, under a parent.
+    /// 
+    /// 
+    ///     This method simply returns a new, non-persisted, IContent without any identity. It
+    ///     is intended as a shortcut to creating new content objects that does not invoke a save
+    ///     operation against the database.
+    /// 
+    /// The name of the content object.
+    /// The parent content object.
+    /// The alias of the content type.
+    /// The optional id of the user creating the content.
+    /// The content object.
+    public IContent Create(string name, IContent? parent, string contentTypeAlias,
+        int userId = Constants.Security.SuperUserId)
+    {
+        // TODO: what about culture?
+
+        if (parent == null)
+        {
+            throw new ArgumentNullException(nameof(parent));
         }
 
-        /// 
-        ///     Creates an  object of a specified content type, under a parent.
-        /// 
-        /// 
-        ///     This method simply returns a new, non-persisted, IContent without any identity. It
-        ///     is intended as a shortcut to creating new content objects that does not invoke a save
-        ///     operation against the database.
-        /// 
-        /// The name of the content object.
-        /// The parent content object.
-        /// The alias of the content type.
-        /// The optional id of the user creating the content.
-        /// The content object.
-        public IContent Create(string name, IContent? parent, string contentTypeAlias,
-            int userId = Constants.Security.SuperUserId)
+        IContentType contentType = GetContentType(contentTypeAlias);
+        if (contentType == null)
         {
-            // TODO: what about culture?
+            throw new ArgumentException("No content type with that alias.",
+                nameof(contentTypeAlias)); // causes rollback
+        }
 
-            if (parent == null)
-            {
-                throw new ArgumentNullException(nameof(parent));
-            }
+        var content = new Content(name, parent, contentType, userId);
+
+        return content;
+    }
+
+    /// 
+    ///     Creates an  object of a specified content type.
+    /// 
+    /// This method returns a new, persisted, IContent with an identity.
+    /// The name of the content object.
+    /// The identifier of the parent, or -1.
+    /// The alias of the content type.
+    /// The optional id of the user creating the content.
+    /// The content object.
+    public IContent CreateAndSave(string name, int parentId, string contentTypeAlias,
+        int userId = Constants.Security.SuperUserId)
+    {
+        // TODO: what about culture?
 
-            IContentType contentType = GetContentType(contentTypeAlias);
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            // locking the content tree secures content types too
+            scope.WriteLock(Constants.Locks.ContentTree);
+
+            IContentType contentType = GetContentType(contentTypeAlias); // + locks
             if (contentType == null)
             {
                 throw new ArgumentException("No content type with that alias.",
                     nameof(contentTypeAlias)); // causes rollback
             }
 
-            var content = new Content(name, parent, contentType, userId);
-
-            return content;
-        }
-
-        /// 
-        ///     Creates an  object of a specified content type.
-        /// 
-        /// This method returns a new, persisted, IContent with an identity.
-        /// The name of the content object.
-        /// The identifier of the parent, or -1.
-        /// The alias of the content type.
-        /// The optional id of the user creating the content.
-        /// The content object.
-        public IContent CreateAndSave(string name, int parentId, string contentTypeAlias,
-            int userId = Constants.Security.SuperUserId)
-        {
-            // TODO: what about culture?
-
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+            IContent? parent = parentId > 0 ? GetById(parentId) : null; // + locks
+            if (parentId > 0 && parent == null)
             {
-                // locking the content tree secures content types too
-                scope.WriteLock(Constants.Locks.ContentTree);
+                throw new ArgumentException("No content with that id.", nameof(parentId)); // causes rollback
+            }
 
-                IContentType contentType = GetContentType(contentTypeAlias); // + locks
-                if (contentType == null)
-                {
-                    throw new ArgumentException("No content type with that alias.",
-                        nameof(contentTypeAlias)); // causes rollback
-                }
+            Content content = parentId > 0
+                ? new Content(name, parent!, contentType, userId)
+                : new Content(name, parentId, contentType, userId);
 
-                IContent? parent = parentId > 0 ? GetById(parentId) : null; // + locks
-                if (parentId > 0 && parent == null)
-                {
-                    throw new ArgumentException("No content with that id.", nameof(parentId)); // causes rollback
-                }
+            Save(content, userId);
 
-                Content content = parentId > 0
-                    ? new Content(name, parent!, contentType, userId)
-                    : new Content(name, parentId, contentType, userId);
+            return content;
+        }
+    }
 
-                Save(content, userId);
+    /// 
+    ///     Creates an  object of a specified content type, under a parent.
+    /// 
+    /// This method returns a new, persisted, IContent with an identity.
+    /// The name of the content object.
+    /// The parent content object.
+    /// The alias of the content type.
+    /// The optional id of the user creating the content.
+    /// The content object.
+    public IContent CreateAndSave(string name, IContent parent, string contentTypeAlias,
+        int userId = Constants.Security.SuperUserId)
+    {
+        // TODO: what about culture?
 
-                return content;
-            }
+        if (parent == null)
+        {
+            throw new ArgumentNullException(nameof(parent));
         }
 
-        /// 
-        ///     Creates an  object of a specified content type, under a parent.
-        /// 
-        /// This method returns a new, persisted, IContent with an identity.
-        /// The name of the content object.
-        /// The parent content object.
-        /// The alias of the content type.
-        /// The optional id of the user creating the content.
-        /// The content object.
-        public IContent CreateAndSave(string name, IContent parent, string contentTypeAlias,
-            int userId = Constants.Security.SuperUserId)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            // TODO: what about culture?
+            // locking the content tree secures content types too
+            scope.WriteLock(Constants.Locks.ContentTree);
 
-            if (parent == null)
+            IContentType contentType = GetContentType(contentTypeAlias); // + locks
+            if (contentType == null)
             {
-                throw new ArgumentNullException(nameof(parent));
+                throw new ArgumentException("No content type with that alias.",
+                    nameof(contentTypeAlias)); // causes rollback
             }
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                // locking the content tree secures content types too
-                scope.WriteLock(Constants.Locks.ContentTree);
-
-                IContentType contentType = GetContentType(contentTypeAlias); // + locks
-                if (contentType == null)
-                {
-                    throw new ArgumentException("No content type with that alias.",
-                        nameof(contentTypeAlias)); // causes rollback
-                }
-
-                var content = new Content(name, parent, contentType, userId);
+            var content = new Content(name, parent, contentType, userId);
 
-                Save(content, userId);
+            Save(content, userId);
 
-                return content;
-            }
+            return content;
         }
+    }
 
-        #endregion
+    #endregion
 
-        #region Get, Has, Is
+    #region Get, Has, Is
 
-        /// 
-        ///     Gets an  object by Id
-        /// 
-        /// Id of the Content to retrieve
-        /// 
-        ///     
-        /// 
-        public IContent? GetById(int id)
+    /// 
+    ///     Gets an  object by Id
+    /// 
+    /// Id of the Content to retrieve
+    /// 
+    ///     
+    /// 
+    public IContent? GetById(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                return _documentRepository.Get(id);
-            }
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _documentRepository.Get(id);
         }
+    }
 
-        /// 
-        ///     Gets an  object by Id
-        /// 
-        /// Ids of the Content to retrieve
-        /// 
-        ///     
-        /// 
-        public IEnumerable GetByIds(IEnumerable ids)
+    /// 
+    ///     Gets an  object by Id
+    /// 
+    /// Ids of the Content to retrieve
+    /// 
+    ///     
+    /// 
+    public IEnumerable GetByIds(IEnumerable ids)
+    {
+        var idsA = ids.ToArray();
+        if (idsA.Length == 0)
         {
-            var idsA = ids.ToArray();
-            if (idsA.Length == 0)
-            {
-                return Enumerable.Empty();
-            }
-
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                IEnumerable items = _documentRepository.GetMany(idsA);
-                var index = items.ToDictionary(x => x.Id, x => x);
-                return idsA.Select(x => index.TryGetValue(x, out IContent? c) ? c : null).WhereNotNull();
-            }
+            return Enumerable.Empty();
         }
 
-        /// 
-        ///     Gets an  object by its 'UniqueId'
-        /// 
-        /// Guid key of the Content to retrieve
-        /// 
-        ///     
-        /// 
-        public IContent? GetById(Guid key)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                return _documentRepository.Get(key);
-            }
+            scope.ReadLock(Constants.Locks.ContentTree);
+            IEnumerable items = _documentRepository.GetMany(idsA);
+            var index = items.ToDictionary(x => x.Id, x => x);
+            return idsA.Select(x => index.TryGetValue(x, out IContent? c) ? c : null).WhereNotNull();
         }
+    }
 
-        /// 
-        public ContentScheduleCollection GetContentScheduleByContentId(int contentId)
+    /// 
+    ///     Gets an  object by its 'UniqueId'
+    /// 
+    /// Guid key of the Content to retrieve
+    /// 
+    ///     
+    /// 
+    public IContent? GetById(Guid key)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Cms.Core.Constants.Locks.ContentTree);
-                return _documentRepository.GetContentSchedule(contentId);
-            }
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _documentRepository.Get(key);
         }
+    }
 
-        /// 
-        public void PersistContentSchedule(IContent content, ContentScheduleCollection contentSchedule)
+    /// 
+    public ContentScheduleCollection GetContentScheduleByContentId(int contentId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.WriteLock(Cms.Core.Constants.Locks.ContentTree);
-                _documentRepository.PersistContentSchedule(content, contentSchedule);
-            }
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _documentRepository.GetContentSchedule(contentId);
         }
+    }
 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        Attempt IContentServiceBase.Save(IEnumerable contents, int userId) =>
-            Attempt.Succeed(Save(contents, userId));
-
-        /// 
-        ///     Gets  objects by Ids
-        /// 
-        /// Ids of the Content to retrieve
-        /// 
-        ///     
-        /// 
-        public IEnumerable GetByIds(IEnumerable ids)
+    /// 
+    public void PersistContentSchedule(IContent content, ContentScheduleCollection contentSchedule)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            Guid[] idsA = ids.ToArray();
-            if (idsA.Length == 0)
-            {
-                return Enumerable.Empty();
-            }
-
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                IEnumerable? items = _documentRepository.GetMany(idsA);
-
-                if (items is not null)
-                {
-                    var index = items.ToDictionary(x => x.Key, x => x);
+            scope.WriteLock(Constants.Locks.ContentTree);
+            _documentRepository.PersistContentSchedule(content, contentSchedule);
+        }
+    }
 
-                    return idsA.Select(x => index.TryGetValue(x, out IContent? c) ? c : null).WhereNotNull();
-                }
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    Attempt IContentServiceBase.Save(IEnumerable contents, int userId) =>
+        Attempt.Succeed(Save(contents, userId));
 
-                return Enumerable.Empty();
-            }
+    /// 
+    ///     Gets  objects by Ids
+    /// 
+    /// Ids of the Content to retrieve
+    /// 
+    ///     
+    /// 
+    public IEnumerable GetByIds(IEnumerable ids)
+    {
+        Guid[] idsA = ids.ToArray();
+        if (idsA.Length == 0)
+        {
+            return Enumerable.Empty();
         }
 
-        /// 
-        public IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize,
-            out long totalRecords
-            , IQuery? filter = null, Ordering? ordering = null)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            if (pageIndex < 0)
-            {
-                throw new ArgumentOutOfRangeException(nameof(pageIndex));
-            }
+            scope.ReadLock(Constants.Locks.ContentTree);
+            IEnumerable? items = _documentRepository.GetMany(idsA);
 
-            if (pageSize <= 0)
+            if (items is not null)
             {
-                throw new ArgumentOutOfRangeException(nameof(pageSize));
-            }
+                var index = items.ToDictionary(x => x.Key, x => x);
 
-            if (ordering == null)
-            {
-                ordering = Ordering.By("sortOrder");
+                return idsA.Select(x => index.TryGetValue(x, out IContent? c) ? c : null).WhereNotNull();
             }
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                return _documentRepository.GetPage(
-                    Query()?.Where(x => x.ContentTypeId == contentTypeId),
-                    pageIndex, pageSize, out totalRecords, filter, ordering);
-            }
+            return Enumerable.Empty();
         }
+    }
 
-        /// 
-        public IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize,
-            out long totalRecords, IQuery? filter, Ordering? ordering = null)
+    /// 
+    public IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize,
+        out long totalRecords
+        , IQuery? filter = null, Ordering? ordering = null)
+    {
+        if (pageIndex < 0)
         {
-            if (pageIndex < 0)
-            {
-                throw new ArgumentOutOfRangeException(nameof(pageIndex));
-            }
-
-            if (pageSize <= 0)
-            {
-                throw new ArgumentOutOfRangeException(nameof(pageSize));
-            }
-
-            if (ordering == null)
-            {
-                ordering = Ordering.By("sortOrder");
-            }
-
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                return _documentRepository.GetPage(
-                    Query()?.Where(x => contentTypeIds.Contains(x.ContentTypeId)),
-                    pageIndex, pageSize, out totalRecords, filter, ordering);
-            }
+            throw new ArgumentOutOfRangeException(nameof(pageIndex));
         }
 
-        /// 
-        ///     Gets a collection of  objects by Level
-        /// 
-        /// The level to retrieve Content from
-        /// An Enumerable list of  objects
-        /// Contrary to most methods, this method filters out trashed content items.
-        public IEnumerable GetByLevel(int level)
+        if (pageSize <= 0)
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                IQuery? query = Query().Where(x => x.Level == level && x.Trashed == false);
-                return _documentRepository.Get(query);
-            }
+            throw new ArgumentOutOfRangeException(nameof(pageSize));
         }
 
-        /// 
-        ///     Gets a specific version of an  item.
-        /// 
-        /// Id of the version to retrieve
-        /// An  item
-        public IContent? GetVersion(int versionId)
+        if (ordering == null)
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                return _documentRepository.GetVersion(versionId);
-            }
+            ordering = Ordering.By("sortOrder");
         }
 
-        /// 
-        ///     Gets a collection of an  objects versions by Id
-        /// 
-        /// 
-        /// An Enumerable list of  objects
-        public IEnumerable GetVersions(int id)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                return _documentRepository.GetAllVersions(id);
-            }
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _documentRepository.GetPage(
+                Query()?.Where(x => x.ContentTypeId == contentTypeId),
+                pageIndex, pageSize, out totalRecords, filter, ordering);
         }
+    }
 
-        /// 
-        ///     Gets a collection of an  objects versions by Id
-        /// 
-        /// An Enumerable list of  objects
-        public IEnumerable GetVersionsSlim(int id, int skip, int take)
+    /// 
+    public IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize,
+        out long totalRecords, IQuery? filter, Ordering? ordering = null)
+    {
+        if (pageIndex < 0)
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                return _documentRepository.GetAllVersionsSlim(id, skip, take);
-            }
+            throw new ArgumentOutOfRangeException(nameof(pageIndex));
         }
 
-        /// 
-        ///     Gets a list of all version Ids for the given content item ordered so latest is first
-        /// 
-        /// 
-        /// The maximum number of rows to return
-        /// 
-        public IEnumerable GetVersionIds(int id, int maxRows)
+        if (pageSize <= 0)
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _documentRepository.GetVersionIds(id, maxRows);
-            }
+            throw new ArgumentOutOfRangeException(nameof(pageSize));
         }
 
-        /// 
-        ///     Gets a collection of  objects, which are ancestors of the current content.
-        /// 
-        /// Id of the  to retrieve ancestors for
-        /// An Enumerable list of  objects
-        public IEnumerable GetAncestors(int id)
+        if (ordering == null)
         {
-            // intentionally not locking
-            IContent? content = GetById(id);
-            if (content is null)
-            {
-                return Enumerable.Empty();
-            }
-
-            return GetAncestors(content);
+            ordering = Ordering.By("sortOrder");
         }
 
-        /// 
-        ///     Gets a collection of  objects, which are ancestors of the current content.
-        /// 
-        ///  to retrieve ancestors for
-        /// An Enumerable list of  objects
-        public IEnumerable GetAncestors(IContent content)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            //null check otherwise we get exceptions
-            if (content.Path.IsNullOrWhiteSpace())
-            {
-                return Enumerable.Empty();
-            }
-
-            var ids = content.GetAncestorIds()?.ToArray();
-            if (ids?.Any() == false)
-            {
-                return new List();
-            }
-
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                return _documentRepository.GetMany(ids!);
-            }
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _documentRepository.GetPage(
+                Query()?.Where(x => contentTypeIds.Contains(x.ContentTypeId)),
+                pageIndex, pageSize, out totalRecords, filter, ordering);
         }
+    }
 
-        /// 
-        ///     Gets a collection of published  objects by Parent Id
-        /// 
-        /// Id of the Parent to retrieve Children from
-        /// An Enumerable list of published  objects
-        public IEnumerable GetPublishedChildren(int id)
+    /// 
+    ///     Gets a collection of  objects by Level
+    /// 
+    /// The level to retrieve Content from
+    /// An Enumerable list of  objects
+    /// Contrary to most methods, this method filters out trashed content items.
+    public IEnumerable GetByLevel(int level)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                IQuery? query = Query().Where(x => x.ParentId == id && x.Published);
-                return _documentRepository.Get(query).OrderBy(x => x.SortOrder);
-            }
+            scope.ReadLock(Constants.Locks.ContentTree);
+            IQuery? query = Query().Where(x => x.Level == level && x.Trashed == false);
+            return _documentRepository.Get(query);
         }
+    }
 
-        /// 
-        public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren,
-            IQuery? filter = null, Ordering? ordering = null)
+    /// 
+    ///     Gets a specific version of an  item.
+    /// 
+    /// Id of the version to retrieve
+    /// An  item
+    public IContent? GetVersion(int versionId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            if (pageIndex < 0)
-            {
-                throw new ArgumentOutOfRangeException(nameof(pageIndex));
-            }
-
-            if (pageSize <= 0)
-            {
-                throw new ArgumentOutOfRangeException(nameof(pageSize));
-            }
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _documentRepository.GetVersion(versionId);
+        }
+    }
 
-            if (ordering == null)
-            {
-                ordering = Ordering.By("sortOrder");
-            }
+    /// 
+    ///     Gets a collection of an  objects versions by Id
+    /// 
+    /// 
+    /// An Enumerable list of  objects
+    public IEnumerable GetVersions(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _documentRepository.GetAllVersions(id);
+        }
+    }
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
+    /// 
+    ///     Gets a collection of an  objects versions by Id
+    /// 
+    /// An Enumerable list of  objects
+    public IEnumerable GetVersionsSlim(int id, int skip, int take)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _documentRepository.GetAllVersionsSlim(id, skip, take);
+        }
+    }
 
-                IQuery? query = Query()?.Where(x => x.ParentId == id);
-                return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering);
-            }
+    /// 
+    ///     Gets a list of all version Ids for the given content item ordered so latest is first
+    /// 
+    /// 
+    /// The maximum number of rows to return
+    /// 
+    public IEnumerable GetVersionIds(int id, int maxRows)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return _documentRepository.GetVersionIds(id, maxRows);
         }
+    }
 
-        /// 
-        public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren,
-            IQuery? filter = null, Ordering? ordering = null)
+    /// 
+    ///     Gets a collection of  objects, which are ancestors of the current content.
+    /// 
+    /// Id of the  to retrieve ancestors for
+    /// An Enumerable list of  objects
+    public IEnumerable GetAncestors(int id)
+    {
+        // intentionally not locking
+        IContent? content = GetById(id);
+        if (content is null)
         {
-            if (ordering == null)
-            {
-                ordering = Ordering.By("Path");
-            }
+            return Enumerable.Empty();
+        }
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
+        return GetAncestors(content);
+    }
 
-                //if the id is System Root, then just get all
-                if (id != Constants.System.Root)
-                {
-                    TreeEntityPath[] contentPath =
-                        _entityRepository.GetAllPaths(Constants.ObjectTypes.Document, id).ToArray();
-                    if (contentPath.Length == 0)
-                    {
-                        totalChildren = 0;
-                        return Enumerable.Empty();
-                    }
+    /// 
+    ///     Gets a collection of  objects, which are ancestors of the current content.
+    /// 
+    ///  to retrieve ancestors for
+    /// An Enumerable list of  objects
+    public IEnumerable GetAncestors(IContent content)
+    {
+        //null check otherwise we get exceptions
+        if (content.Path.IsNullOrWhiteSpace())
+        {
+            return Enumerable.Empty();
+        }
 
-                    return GetPagedLocked(GetPagedDescendantQuery(contentPath[0].Path), pageIndex, pageSize,
-                        out totalChildren, filter, ordering);
-                }
+        var ids = content.GetAncestorIds()?.ToArray();
+        if (ids?.Any() == false)
+        {
+            return new List();
+        }
 
-                return GetPagedLocked(null, pageIndex, pageSize, out totalChildren, filter, ordering);
-            }
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _documentRepository.GetMany(ids!);
         }
+    }
 
-        private IQuery? GetPagedDescendantQuery(string contentPath)
+    /// 
+    ///     Gets a collection of published  objects by Parent Id
+    /// 
+    /// Id of the Parent to retrieve Children from
+    /// An Enumerable list of published  objects
+    public IEnumerable GetPublishedChildren(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            IQuery? query = Query();
-            if (!contentPath.IsNullOrWhiteSpace())
-            {
-                query?.Where(x => x.Path.SqlStartsWith($"{contentPath},", TextColumnType.NVarchar));
-            }
+            scope.ReadLock(Constants.Locks.ContentTree);
+            IQuery? query = Query().Where(x => x.ParentId == id && x.Published);
+            return _documentRepository.Get(query).OrderBy(x => x.SortOrder);
+        }
+    }
 
-            return query;
+    /// 
+    public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren,
+        IQuery? filter = null, Ordering? ordering = null)
+    {
+        if (pageIndex < 0)
+        {
+            throw new ArgumentOutOfRangeException(nameof(pageIndex));
         }
 
-        private IEnumerable GetPagedLocked(IQuery? query, long pageIndex, int pageSize,
-            out long totalChildren,
-            IQuery? filter, Ordering? ordering)
+        if (pageSize <= 0)
         {
-            if (pageIndex < 0)
-            {
-                throw new ArgumentOutOfRangeException(nameof(pageIndex));
-            }
+            throw new ArgumentOutOfRangeException(nameof(pageSize));
+        }
 
-            if (pageSize <= 0)
-            {
-                throw new ArgumentOutOfRangeException(nameof(pageSize));
-            }
+        if (ordering == null)
+        {
+            ordering = Ordering.By("sortOrder");
+        }
 
-            if (ordering == null)
-            {
-                throw new ArgumentNullException(nameof(ordering));
-            }
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            scope.ReadLock(Constants.Locks.ContentTree);
 
+            IQuery? query = Query()?.Where(x => x.ParentId == id);
             return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering);
         }
+    }
 
-        /// 
-        ///     Gets the parent of the current content as an  item.
-        /// 
-        /// Id of the  to retrieve the parent from
-        /// Parent  object
-        public IContent? GetParent(int id)
+    /// 
+    public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren,
+        IQuery? filter = null, Ordering? ordering = null)
+    {
+        if (ordering == null)
         {
-            // intentionally not locking
-            IContent? content = GetById(id);
-            return GetParent(content);
+            ordering = Ordering.By("Path");
         }
 
-        /// 
-        ///     Gets the parent of the current content as an  item.
-        /// 
-        ///  to retrieve the parent from
-        /// Parent  object
-        public IContent? GetParent(IContent? content)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            if (content?.ParentId == Constants.System.Root || content?.ParentId == Constants.System.RecycleBinContent ||
-                content is null)
+            scope.ReadLock(Constants.Locks.ContentTree);
+
+            //if the id is System Root, then just get all
+            if (id != Constants.System.Root)
             {
-                return null;
+                TreeEntityPath[] contentPath =
+                    _entityRepository.GetAllPaths(Constants.ObjectTypes.Document, id).ToArray();
+                if (contentPath.Length == 0)
+                {
+                    totalChildren = 0;
+                    return Enumerable.Empty();
+                }
+
+                return GetPagedLocked(GetPagedDescendantQuery(contentPath[0].Path), pageIndex, pageSize,
+                    out totalChildren, filter, ordering);
             }
 
-            return GetById(content.ParentId);
+            return GetPagedLocked(null, pageIndex, pageSize, out totalChildren, filter, ordering);
         }
+    }
 
-        /// 
-        ///     Gets a collection of  objects, which reside at the first level / root
-        /// 
-        /// An Enumerable list of  objects
-        public IEnumerable GetRootContent()
+    private IQuery? GetPagedDescendantQuery(string contentPath)
+    {
+        IQuery? query = Query();
+        if (!contentPath.IsNullOrWhiteSpace())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                IQuery query = Query().Where(x => x.ParentId == Constants.System.Root);
-                return _documentRepository.Get(query);
-            }
+            query?.Where(x => x.Path.SqlStartsWith($"{contentPath},", TextColumnType.NVarchar));
         }
 
-        /// 
-        ///     Gets all published content items
-        /// 
-        /// 
-        internal IEnumerable GetAllPublished()
+        return query;
+    }
+
+    private IEnumerable GetPagedLocked(IQuery? query, long pageIndex, int pageSize,
+        out long totalChildren,
+        IQuery? filter, Ordering? ordering)
+    {
+        if (pageIndex < 0)
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                return _documentRepository.Get(QueryNotTrashed);
-            }
+            throw new ArgumentOutOfRangeException(nameof(pageIndex));
         }
 
-        /// 
-        public IEnumerable GetContentForExpiration(DateTime date)
+        if (pageSize <= 0)
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                return _documentRepository.GetContentForExpiration(date);
-            }
+            throw new ArgumentOutOfRangeException(nameof(pageSize));
         }
 
-        /// 
-        public IEnumerable GetContentForRelease(DateTime date)
+        if (ordering == null)
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                return _documentRepository.GetContentForRelease(date);
-            }
+            throw new ArgumentNullException(nameof(ordering));
         }
 
-        /// 
-        ///     Gets a collection of an  objects, which resides in the Recycle Bin
-        /// 
-        /// An Enumerable list of  objects
-        public IEnumerable GetPagedContentInRecycleBin(long pageIndex, int pageSize, out long totalRecords,
-            IQuery? filter = null, Ordering? ordering = null)
+        return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering);
+    }
+
+    /// 
+    ///     Gets the parent of the current content as an  item.
+    /// 
+    /// Id of the  to retrieve the parent from
+    /// Parent  object
+    public IContent? GetParent(int id)
+    {
+        // intentionally not locking
+        IContent? content = GetById(id);
+        return GetParent(content);
+    }
+
+    /// 
+    ///     Gets the parent of the current content as an  item.
+    /// 
+    ///  to retrieve the parent from
+    /// Parent  object
+    public IContent? GetParent(IContent? content)
+    {
+        if (content?.ParentId == Constants.System.Root || content?.ParentId == Constants.System.RecycleBinContent ||
+            content is null)
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                if (ordering == null)
-                {
-                    ordering = Ordering.By("Path");
-                }
+            return null;
+        }
 
-                scope.ReadLock(Constants.Locks.ContentTree);
-                IQuery? query = Query()?
-                    .Where(x => x.Path.StartsWith(Constants.System.RecycleBinContentPathPrefix));
-                return _documentRepository.GetPage(query, pageIndex, pageSize, out totalRecords, filter, ordering);
-            }
+        return GetById(content.ParentId);
+    }
+
+    /// 
+    ///     Gets a collection of  objects, which reside at the first level / root
+    /// 
+    /// An Enumerable list of  objects
+    public IEnumerable GetRootContent()
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            scope.ReadLock(Constants.Locks.ContentTree);
+            IQuery query = Query().Where(x => x.ParentId == Constants.System.Root);
+            return _documentRepository.Get(query);
         }
+    }
 
-        /// 
-        ///     Checks whether an  item has any children
-        /// 
-        /// Id of the 
-        /// True if the content has any children otherwise False
-        public bool HasChildren(int id) => CountChildren(id) > 0;
+    /// 
+    ///     Gets all published content items
+    /// 
+    /// 
+    internal IEnumerable GetAllPublished()
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _documentRepository.Get(QueryNotTrashed);
+        }
+    }
 
-        /// 
-        ///     Checks if the passed in  can be published based on the ancestors publish state.
-        /// 
-        ///  to check if ancestors are published
-        /// True if the Content can be published, otherwise False
-        public bool IsPathPublishable(IContent content)
+    /// 
+    public IEnumerable GetContentForExpiration(DateTime date)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            // fast
-            if (content.ParentId == Constants.System.Root)
-            {
-                return true; // root content is always publishable
-            }
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _documentRepository.GetContentForExpiration(date);
+        }
+    }
 
-            if (content.Trashed)
+    /// 
+    public IEnumerable GetContentForRelease(DateTime date)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _documentRepository.GetContentForRelease(date);
+        }
+    }
+
+    /// 
+    ///     Gets a collection of an  objects, which resides in the Recycle Bin
+    /// 
+    /// An Enumerable list of  objects
+    public IEnumerable GetPagedContentInRecycleBin(long pageIndex, int pageSize, out long totalRecords,
+        IQuery? filter = null, Ordering? ordering = null)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            if (ordering == null)
             {
-                return false; // trashed content is never publishable
+                ordering = Ordering.By("Path");
             }
 
-            // not trashed and has a parent: publishable if the parent is path-published
-            IContent? parent = GetById(content.ParentId);
-            return parent == null || IsPathPublished(parent);
+            scope.ReadLock(Constants.Locks.ContentTree);
+            IQuery? query = Query()?
+                .Where(x => x.Path.StartsWith(Constants.System.RecycleBinContentPathPrefix));
+            return _documentRepository.GetPage(query, pageIndex, pageSize, out totalRecords, filter, ordering);
         }
+    }
+
+    /// 
+    ///     Checks whether an  item has any children
+    /// 
+    /// Id of the 
+    /// True if the content has any children otherwise False
+    public bool HasChildren(int id) => CountChildren(id) > 0;
 
-        public bool IsPathPublished(IContent? content)
+    /// 
+    ///     Checks if the passed in  can be published based on the ancestors publish state.
+    /// 
+    ///  to check if ancestors are published
+    /// True if the Content can be published, otherwise False
+    public bool IsPathPublishable(IContent content)
+    {
+        // fast
+        if (content.ParentId == Constants.System.Root)
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                return _documentRepository.IsPathPublished(content);
-            }
+            return true; // root content is always publishable
         }
 
-        #endregion
+        if (content.Trashed)
+        {
+            return false; // trashed content is never publishable
+        }
 
-        #region Save, Publish, Unpublish
+        // not trashed and has a parent: publishable if the parent is path-published
+        IContent? parent = GetById(content.ParentId);
+        return parent == null || IsPathPublished(parent);
+    }
 
-        /// 
-        public OperationResult Save(IContent content, int? userId = null,
-            ContentScheduleCollection? contentSchedule = null)
+    public bool IsPathPublished(IContent? content)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            PublishedState publishedState = content.PublishedState;
-            if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished)
-            {
-                throw new InvalidOperationException(
-                    $"Cannot save (un)publishing content with name: {content.Name} - and state: {content.PublishedState}, use the dedicated SavePublished method.");
-            }
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _documentRepository.IsPathPublished(content);
+        }
+    }
 
-            if (content.Name != null && content.Name.Length > 255)
-            {
-                throw new InvalidOperationException(
-                    $"Content with the name {content.Name} cannot be more than 255 characters in length.");
-            }
+    #endregion
 
-            EventMessages eventMessages = EventMessagesFactory.Get();
+    #region Save, Publish, Unpublish
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+    /// 
+    public OperationResult Save(IContent content, int? userId = null,
+        ContentScheduleCollection? contentSchedule = null)
+    {
+        PublishedState publishedState = content.PublishedState;
+        if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished)
+        {
+            throw new InvalidOperationException(
+                $"Cannot save (un)publishing content with name: {content.Name} - and state: {content.PublishedState}, use the dedicated SavePublished method.");
+        }
+
+        if (content.Name != null && content.Name.Length > 255)
+        {
+            throw new InvalidOperationException(
+                $"Content with the name {content.Name} cannot be more than 255 characters in length.");
+        }
+
+        EventMessages eventMessages = EventMessagesFactory.Get();
+
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            var savingNotification = new ContentSavingNotification(content, eventMessages);
+            if (scope.Notifications.PublishCancelable(savingNotification))
             {
-                var savingNotification = new ContentSavingNotification(content, eventMessages);
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    scope.Complete();
-                    return OperationResult.Cancel(eventMessages);
-                }
+                scope.Complete();
+                return OperationResult.Cancel(eventMessages);
+            }
 
-                scope.WriteLock(Constants.Locks.ContentTree);
-                userId ??= Constants.Security.SuperUserId;
+            scope.WriteLock(Constants.Locks.ContentTree);
+            userId ??= Constants.Security.SuperUserId;
 
-                if (content.HasIdentity == false)
-                {
-                    content.CreatorId = userId.Value;
-                }
+            if (content.HasIdentity == false)
+            {
+                content.CreatorId = userId.Value;
+            }
 
-                content.WriterId = userId.Value;
+            content.WriterId = userId.Value;
 
-                //track the cultures that have changed
-                List? culturesChanging = content.ContentType.VariesByCulture()
-                    ? content.CultureInfos?.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList()
-                    : null;
-                // TODO: Currently there's no way to change track which variant properties have changed, we only have change
-                // tracking enabled on all values on the Property which doesn't allow us to know which variants have changed.
-                // in this particular case, determining which cultures have changed works with the above with names since it will
-                // have always changed if it's been saved in the back office but that's not really fail safe.
+            //track the cultures that have changed
+            List? culturesChanging = content.ContentType.VariesByCulture()
+                ? content.CultureInfos?.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList()
+                : null;
+            // TODO: Currently there's no way to change track which variant properties have changed, we only have change
+            // tracking enabled on all values on the Property which doesn't allow us to know which variants have changed.
+            // in this particular case, determining which cultures have changed works with the above with names since it will
+            // have always changed if it's been saved in the back office but that's not really fail safe.
 
-                _documentRepository.Save(content);
+            _documentRepository.Save(content);
 
-                if (contentSchedule != null)
-                {
-                    _documentRepository.PersistContentSchedule(content, contentSchedule);
-                }
+            if (contentSchedule != null)
+            {
+                _documentRepository.PersistContentSchedule(content, contentSchedule);
+            }
 
-                scope.Notifications.Publish(
-                    new ContentSavedNotification(content, eventMessages).WithStateFrom(savingNotification));
+            scope.Notifications.Publish(
+                new ContentSavedNotification(content, eventMessages).WithStateFrom(savingNotification));
 
-                // TODO: we had code here to FORCE that this event can never be suppressed. But that just doesn't make a ton of sense?!
-                // I understand that if its suppressed that the caches aren't updated, but that would be expected. If someone
-                // is supressing events then I think it's expected that nothing will happen. They are probably doing it for perf
-                // reasons like bulk import and in those cases we don't want this occuring.
-                scope.Notifications.Publish(
-                    new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshNode, eventMessages));
+            // TODO: we had code here to FORCE that this event can never be suppressed. But that just doesn't make a ton of sense?!
+            // I understand that if its suppressed that the caches aren't updated, but that would be expected. If someone
+            // is supressing events then I think it's expected that nothing will happen. They are probably doing it for perf
+            // reasons like bulk import and in those cases we don't want this occuring.
+            scope.Notifications.Publish(
+                new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshNode, eventMessages));
 
-                if (culturesChanging != null)
-                {
-                    var languages = _languageRepository.GetMany()?
-                        .Where(x => culturesChanging.InvariantContains(x.IsoCode))
-                        .Select(x => x.CultureName);
-                    if (languages is not null)
-                    {
-                        var langs = string.Join(", ", languages);
-                        Audit(AuditType.SaveVariant, userId.Value, content.Id, $"Saved languages: {langs}", langs);
-                    }
-                }
-                else
+            if (culturesChanging != null)
+            {
+                IEnumerable languages = _languageRepository.GetMany()?
+                    .Where(x => culturesChanging.InvariantContains(x.IsoCode))
+                    .Select(x => x.CultureName);
+                if (languages is not null)
                 {
-                    Audit(AuditType.Save, userId.Value, content.Id);
+                    var langs = string.Join(", ", languages);
+                    Audit(AuditType.SaveVariant, userId.Value, content.Id, $"Saved languages: {langs}", langs);
                 }
-
-                scope.Complete();
+            }
+            else
+            {
+                Audit(AuditType.Save, userId.Value, content.Id);
             }
 
-            return OperationResult.Succeed(eventMessages);
+            scope.Complete();
         }
 
-        /// 
-        public OperationResult Save(IEnumerable contents, int userId = Constants.Security.SuperUserId)
+        return OperationResult.Succeed(eventMessages);
+    }
+
+    /// 
+    public OperationResult Save(IEnumerable contents, int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages eventMessages = EventMessagesFactory.Get();
+        IContent[] contentsA = contents.ToArray();
+
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            EventMessages eventMessages = EventMessagesFactory.Get();
-            IContent[] contentsA = contents.ToArray();
+            var savingNotification = new ContentSavingNotification(contentsA, eventMessages);
+            if (scope.Notifications.PublishCancelable(savingNotification))
+            {
+                scope.Complete();
+                return OperationResult.Cancel(eventMessages);
+            }
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            scope.WriteLock(Constants.Locks.ContentTree);
+            foreach (IContent content in contentsA)
             {
-                var savingNotification = new ContentSavingNotification(contentsA, eventMessages);
-                if (scope.Notifications.PublishCancelable(savingNotification))
+                if (content.HasIdentity == false)
                 {
-                    scope.Complete();
-                    return OperationResult.Cancel(eventMessages);
+                    content.CreatorId = userId;
                 }
 
-                scope.WriteLock(Constants.Locks.ContentTree);
-                foreach (IContent content in contentsA)
-                {
-                    if (content.HasIdentity == false)
-                    {
-                        content.CreatorId = userId;
-                    }
+                content.WriterId = userId;
+
+                _documentRepository.Save(content);
+            }
 
-                    content.WriterId = userId;
+            scope.Notifications.Publish(
+                new ContentSavedNotification(contentsA, eventMessages).WithStateFrom(savingNotification));
+            // TODO: See note above about supressing events
+            scope.Notifications.Publish(
+                new ContentTreeChangeNotification(contentsA, TreeChangeTypes.RefreshNode, eventMessages));
 
-                    _documentRepository.Save(content);
-                }
+            Audit(AuditType.Save, userId == -1 ? 0 : userId, Constants.System.Root, "Saved multiple content");
 
-                scope.Notifications.Publish(
-                    new ContentSavedNotification(contentsA, eventMessages).WithStateFrom(savingNotification));
-                // TODO: See note above about supressing events
-                scope.Notifications.Publish(
-                    new ContentTreeChangeNotification(contentsA, TreeChangeTypes.RefreshNode, eventMessages));
+            scope.Complete();
+        }
 
-                Audit(AuditType.Save, userId == -1 ? 0 : userId, Constants.System.Root, "Saved multiple content");
+        return OperationResult.Succeed(eventMessages);
+    }
 
-                scope.Complete();
-            }
+    /// 
+    public PublishResult SaveAndPublish(IContent content, string culture = "*",
+        int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
 
-            return OperationResult.Succeed(eventMessages);
+        PublishedState publishedState = content.PublishedState;
+        if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished)
+        {
+            throw new InvalidOperationException(
+                $"Cannot save-and-publish (un)publishing content, use the dedicated {nameof(CommitDocumentChanges)} method.");
         }
 
-        /// 
-        public PublishResult SaveAndPublish(IContent content, string culture = "*",
-            int userId = Constants.Security.SuperUserId)
+        // cannot accept invariant (null or empty) culture for variant content type
+        // cannot accept a specific culture for invariant content type (but '*' is ok)
+        if (content.ContentType.VariesByCulture())
         {
-            EventMessages evtMsgs = EventMessagesFactory.Get();
-
-            PublishedState publishedState = content.PublishedState;
-            if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished)
+            if (culture.IsNullOrWhiteSpace())
             {
-                throw new InvalidOperationException(
-                    $"Cannot save-and-publish (un)publishing content, use the dedicated {nameof(CommitDocumentChanges)} method.");
+                throw new NotSupportedException("Invariant culture is not supported by variant content types.");
             }
-
-            // cannot accept invariant (null or empty) culture for variant content type
-            // cannot accept a specific culture for invariant content type (but '*' is ok)
-            if (content.ContentType.VariesByCulture())
-            {
-                if (culture.IsNullOrWhiteSpace())
-                {
-                    throw new NotSupportedException("Invariant culture is not supported by variant content types.");
-                }
-            }
-            else
+        }
+        else
+        {
+            if (!culture.IsNullOrWhiteSpace() && culture != "*")
             {
-                if (!culture.IsNullOrWhiteSpace() && culture != "*")
-                {
-                    throw new NotSupportedException(
-                        $"Culture \"{culture}\" is not supported by invariant content types.");
-                }
+                throw new NotSupportedException(
+                    $"Culture \"{culture}\" is not supported by invariant content types.");
             }
+        }
+
+        if (content.Name != null && content.Name.Length > 255)
+        {
+            throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+        }
+
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            scope.WriteLock(Constants.Locks.ContentTree);
+
+            var allLangs = _languageRepository.GetMany().ToList();
 
-            if (content.Name != null && content.Name.Length > 255)
+            var savingNotification = new ContentSavingNotification(content, evtMsgs);
+            if (scope.Notifications.PublishCancelable(savingNotification))
             {
-                throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+                return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
             }
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(Constants.Locks.ContentTree);
+            // if culture is specific, first publish the invariant values, then publish the culture itself.
+            // if culture is '*', then publish them all (including variants)
 
-                var allLangs = _languageRepository.GetMany().ToList();
+            //this will create the correct culture impact even if culture is * or null
+            var impact = CultureImpact.Create(culture, IsDefaultCulture(allLangs, culture), content);
 
-                var savingNotification = new ContentSavingNotification(content, evtMsgs);
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
-                }
+            // publish the culture(s)
+            // we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
+            content.PublishCulture(impact);
 
-                // if culture is specific, first publish the invariant values, then publish the culture itself.
-                // if culture is '*', then publish them all (including variants)
+            PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs,
+                savingNotification.State, userId);
+            scope.Complete();
+            return result;
+        }
+    }
 
-                //this will create the correct culture impact even if culture is * or null
-                var impact = CultureImpact.Create(culture, IsDefaultCulture(allLangs, culture), content);
+    /// 
+    public PublishResult SaveAndPublish(IContent content, string[] cultures,
+        int userId = Constants.Security.SuperUserId)
+    {
+        if (content == null)
+        {
+            throw new ArgumentNullException(nameof(content));
+        }
 
-                // publish the culture(s)
-                // we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
-                content.PublishCulture(impact);
+        if (cultures == null)
+        {
+            throw new ArgumentNullException(nameof(cultures));
+        }
 
-                PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs,
-                    savingNotification.State, userId);
-                scope.Complete();
-                return result;
-            }
+        if (content.Name != null && content.Name.Length > 255)
+        {
+            throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
         }
 
-        /// 
-        public PublishResult SaveAndPublish(IContent content, string[] cultures,
-            int userId = Constants.Security.SuperUserId)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            if (content == null)
-            {
-                throw new ArgumentNullException(nameof(content));
-            }
+            scope.WriteLock(Constants.Locks.ContentTree);
 
-            if (cultures == null)
+            var allLangs = _languageRepository.GetMany().ToList();
+
+            EventMessages evtMsgs = EventMessagesFactory.Get();
+
+            var savingNotification = new ContentSavingNotification(content, evtMsgs);
+            if (scope.Notifications.PublishCancelable(savingNotification))
             {
-                throw new ArgumentNullException(nameof(cultures));
+                return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
             }
 
-            if (content.Name != null && content.Name.Length > 255)
+            var varies = content.ContentType.VariesByCulture();
+
+            if (cultures.Length == 0 && !varies)
             {
-                throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+                //no cultures specified and doesn't vary, so publish it, else nothing to publish
+                return SaveAndPublish(content, userId: userId);
             }
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            if (cultures.Any(x => x == null || x == "*"))
             {
-                scope.WriteLock(Constants.Locks.ContentTree);
+                throw new InvalidOperationException(
+                    "Only valid cultures are allowed to be used in this method, wildcards or nulls are not allowed");
+            }
 
-                var allLangs = _languageRepository.GetMany().ToList();
+            IEnumerable impacts =
+                cultures.Select(x => CultureImpact.Explicit(x, IsDefaultCulture(allLangs, x)));
 
-                EventMessages evtMsgs = EventMessagesFactory.Get();
+            // publish the culture(s)
+            // we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
+            foreach (CultureImpact impact in impacts)
+            {
+                content.PublishCulture(impact);
+            }
 
-                var savingNotification = new ContentSavingNotification(content, evtMsgs);
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
-                }
+            PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs,
+                savingNotification.State, userId);
+            scope.Complete();
+            return result;
+        }
+    }
 
-                var varies = content.ContentType.VariesByCulture();
+    /// 
+    public PublishResult Unpublish(IContent content, string? culture = "*",
+        int userId = Constants.Security.SuperUserId)
+    {
+        if (content == null)
+        {
+            throw new ArgumentNullException(nameof(content));
+        }
 
-                if (cultures.Length == 0 && !varies)
-                {
-                    //no cultures specified and doesn't vary, so publish it, else nothing to publish
-                    return SaveAndPublish(content, userId: userId);
-                }
+        EventMessages evtMsgs = EventMessagesFactory.Get();
 
-                if (cultures.Any(x => x == null || x == "*"))
-                {
-                    throw new InvalidOperationException(
-                        "Only valid cultures are allowed to be used in this method, wildcards or nulls are not allowed");
-                }
+        culture = culture?.NullOrWhiteSpaceAsNull();
 
-                IEnumerable impacts =
-                    cultures.Select(x => CultureImpact.Explicit(x, IsDefaultCulture(allLangs, x)));
-
-                // publish the culture(s)
-                // we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
-                foreach (CultureImpact impact in impacts)
-                {
-                    content.PublishCulture(impact);
-                }
+        PublishedState publishedState = content.PublishedState;
+        if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished)
+        {
+            throw new InvalidOperationException(
+                $"Cannot save-and-publish (un)publishing content, use the dedicated {nameof(CommitDocumentChanges)} method.");
+        }
 
-                PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs,
-                    savingNotification.State, userId);
-                scope.Complete();
-                return result;
+        // cannot accept invariant (null or empty) culture for variant content type
+        // cannot accept a specific culture for invariant content type (but '*' is ok)
+        if (content.ContentType.VariesByCulture())
+        {
+            if (culture == null)
+            {
+                throw new NotSupportedException("Invariant culture is not supported by variant content types.");
             }
         }
-
-        /// 
-        public PublishResult Unpublish(IContent content, string? culture = "*",
-            int userId = Constants.Security.SuperUserId)
+        else
         {
-            if (content == null)
+            if (culture != null && culture != "*")
             {
-                throw new ArgumentNullException(nameof(content));
+                throw new NotSupportedException(
+                    $"Culture \"{culture}\" is not supported by invariant content types.");
             }
+        }
 
-            EventMessages evtMsgs = EventMessagesFactory.Get();
+        // if the content is not published, nothing to do
+        if (!content.Published)
+        {
+            return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content);
+        }
 
-            culture = culture?.NullOrWhiteSpaceAsNull();
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            scope.WriteLock(Constants.Locks.ContentTree);
 
-            PublishedState publishedState = content.PublishedState;
-            if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished)
-            {
-                throw new InvalidOperationException(
-                    $"Cannot save-and-publish (un)publishing content, use the dedicated {nameof(CommitDocumentChanges)} method.");
-            }
+            var allLangs = _languageRepository.GetMany().ToList();
 
-            // cannot accept invariant (null or empty) culture for variant content type
-            // cannot accept a specific culture for invariant content type (but '*' is ok)
-            if (content.ContentType.VariesByCulture())
-            {
-                if (culture == null)
-                {
-                    throw new NotSupportedException("Invariant culture is not supported by variant content types.");
-                }
-            }
-            else
+            var savingNotification = new ContentSavingNotification(content, evtMsgs);
+            if (scope.Notifications.PublishCancelable(savingNotification))
             {
-                if (culture != null && culture != "*")
-                {
-                    throw new NotSupportedException(
-                        $"Culture \"{culture}\" is not supported by invariant content types.");
-                }
+                return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
             }
 
-            // if the content is not published, nothing to do
-            if (!content.Published)
+            // all cultures = unpublish whole
+            if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null))
             {
-                return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content);
-            }
+                // It's important to understand that when the document varies by culture but the "*" is used,
+                // we are just unpublishing the whole document but leaving all of the culture's as-is. This is expected
+                // because we don't want to actually unpublish every culture and then the document, we just want everything
+                // to be non-routable so that when it's re-published all variants were as they were.
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+                content.PublishedState = PublishedState.Unpublishing;
+                PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs,
+                    savingNotification.State, userId);
+                scope.Complete();
+                return result;
+            }
+            else
             {
-                scope.WriteLock(Constants.Locks.ContentTree);
+                // Unpublish the culture, this will change the document state to Publishing! ... which is expected because this will
+                // essentially be re-publishing the document with the requested culture removed.
+                // The call to CommitDocumentChangesInternal will perform all the checks like if this is a mandatory culture or the last culture being unpublished
+                // and will then unpublish the document accordingly.
+                // If the result of this is false it means there was no culture to unpublish (i.e. it was already unpublished or it did not exist)
+                var removed = content.UnpublishCulture(culture);
 
-                var allLangs = _languageRepository.GetMany().ToList();
-
-                var savingNotification = new ContentSavingNotification(content, evtMsgs);
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
-                }
+                //save and publish any changes
+                PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs,
+                    savingNotification.State, userId);
 
-                // all cultures = unpublish whole
-                if (culture == "*" || (!content.ContentType.VariesByCulture() && culture == null))
+                scope.Complete();
+
+                // In one case the result will be PublishStatusType.FailedPublishNothingToPublish which means that no cultures
+                // were specified to be published which will be the case when removed is false. In that case
+                // we want to swap the result type to PublishResultType.SuccessUnpublishAlready (that was the expectation before).
+                if (result.Result == PublishResultType.FailedPublishNothingToPublish && !removed)
                 {
-                    // It's important to understand that when the document varies by culture but the "*" is used,
-                    // we are just unpublishing the whole document but leaving all of the culture's as-is. This is expected
-                    // because we don't want to actually unpublish every culture and then the document, we just want everything
-                    // to be non-routable so that when it's re-published all variants were as they were.
-
-                    content.PublishedState = PublishedState.Unpublishing;
-                    PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs,
-                        savingNotification.State, userId);
-                    scope.Complete();
-                    return result;
+                    return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content);
                 }
-                else
-                {
-                    // Unpublish the culture, this will change the document state to Publishing! ... which is expected because this will
-                    // essentially be re-publishing the document with the requested culture removed.
-                    // The call to CommitDocumentChangesInternal will perform all the checks like if this is a mandatory culture or the last culture being unpublished
-                    // and will then unpublish the document accordingly.
-                    // If the result of this is false it means there was no culture to unpublish (i.e. it was already unpublished or it did not exist)
-                    var removed = content.UnpublishCulture(culture);
-
-                    //save and publish any changes
-                    PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs,
-                        savingNotification.State, userId);
-
-                    scope.Complete();
-
-                    // In one case the result will be PublishStatusType.FailedPublishNothingToPublish which means that no cultures
-                    // were specified to be published which will be the case when removed is false. In that case
-                    // we want to swap the result type to PublishResultType.SuccessUnpublishAlready (that was the expectation before).
-                    if (result.Result == PublishResultType.FailedPublishNothingToPublish && !removed)
-                    {
-                        return new PublishResult(PublishResultType.SuccessUnpublishAlready, evtMsgs, content);
-                    }
 
-                    return result;
-                }
+                return result;
             }
         }
+    }
 
-        /// 
-        ///     Saves a document and publishes/unpublishes any pending publishing changes made to the document.
-        /// 
-        /// 
-        ///     
-        ///         This MUST NOT be called from within this service, this used to be a public API and must only be used outside of
-        ///         this service.
-        ///         Internally in this service, calls must be made to CommitDocumentChangesInternal
-        ///     
-        ///     This is the underlying logic for both publishing and unpublishing any document
-        ///     
-        ///         Pending publishing/unpublishing changes on a document are made with calls to
-        ///          and
-        ///         .
-        ///     
-        ///     
-        ///         When publishing or unpublishing a single culture, or all cultures, use 
-        ///         and . But if the flexibility to both publish and unpublish in a single operation is
-        ///         required
-        ///         then this method needs to be used in combination with 
-        ///         and 
-        ///         on the content itself - this prepares the content, but does not commit anything - and then, invoke
-        ///          to actually commit the changes to the database.
-        ///     
-        ///     The document is *always* saved, even when publishing fails.
-        /// 
-        internal PublishResult CommitDocumentChanges(IContent content,
-            int userId = Constants.Security.SuperUserId)
+    /// 
+    ///     Saves a document and publishes/unpublishes any pending publishing changes made to the document.
+    /// 
+    /// 
+    ///     
+    ///         This MUST NOT be called from within this service, this used to be a public API and must only be used outside of
+    ///         this service.
+    ///         Internally in this service, calls must be made to CommitDocumentChangesInternal
+    ///     
+    ///     This is the underlying logic for both publishing and unpublishing any document
+    ///     
+    ///         Pending publishing/unpublishing changes on a document are made with calls to
+    ///          and
+    ///         .
+    ///     
+    ///     
+    ///         When publishing or unpublishing a single culture, or all cultures, use 
+    ///         and . But if the flexibility to both publish and unpublish in a single operation is
+    ///         required
+    ///         then this method needs to be used in combination with 
+    ///         and 
+    ///         on the content itself - this prepares the content, but does not commit anything - and then, invoke
+    ///          to actually commit the changes to the database.
+    ///     
+    ///     The document is *always* saved, even when publishing fails.
+    /// 
+    internal PublishResult CommitDocumentChanges(IContent content,
+        int userId = Constants.Security.SuperUserId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            EventMessages evtMsgs = EventMessagesFactory.Get();
+
+            scope.WriteLock(Constants.Locks.ContentTree);
+
+            var savingNotification = new ContentSavingNotification(content, evtMsgs);
+            if (scope.Notifications.PublishCancelable(savingNotification))
             {
-                EventMessages evtMsgs = EventMessagesFactory.Get();
+                return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
+            }
 
-                scope.WriteLock(Constants.Locks.ContentTree);
+            var allLangs = _languageRepository.GetMany().ToList();
 
-                var savingNotification = new ContentSavingNotification(content, evtMsgs);
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
-                }
+            PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs,
+                savingNotification.State, userId);
+            scope.Complete();
+            return result;
+        }
+    }
 
-                var allLangs = _languageRepository.GetMany().ToList();
+    /// 
+    ///     Handles a lot of business logic cases for how the document should be persisted
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    ///     
+    ///         Business logic cases such: as unpublishing a mandatory culture, or unpublishing the last culture, checking for
+    ///         pending scheduled publishing, etc... is dealt with in this method.
+    ///         There is quite a lot of cases to take into account along with logic that needs to deal with scheduled
+    ///         saving/publishing, branch saving/publishing, etc...
+    ///     
+    /// 
+    private PublishResult CommitDocumentChangesInternal(ICoreScope scope, IContent content,
+        EventMessages eventMessages, IReadOnlyCollection allLangs,
+        IDictionary? notificationState,
+        int userId = Constants.Security.SuperUserId,
+        bool branchOne = false, bool branchRoot = false)
+    {
+        if (scope == null)
+        {
+            throw new ArgumentNullException(nameof(scope));
+        }
 
-                PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs,
-                    savingNotification.State, userId);
-                scope.Complete();
-                return result;
-            }
+        if (content == null)
+        {
+            throw new ArgumentNullException(nameof(content));
         }
 
-        /// 
-        ///     Handles a lot of business logic cases for how the document should be persisted
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        ///     
-        ///         Business logic cases such: as unpublishing a mandatory culture, or unpublishing the last culture, checking for
-        ///         pending scheduled publishing, etc... is dealt with in this method.
-        ///         There is quite a lot of cases to take into account along with logic that needs to deal with scheduled
-        ///         saving/publishing, branch saving/publishing, etc...
-        ///     
-        /// 
-        private PublishResult CommitDocumentChangesInternal(ICoreScope scope, IContent content,
-            EventMessages eventMessages, IReadOnlyCollection allLangs,
-            IDictionary? notificationState,
-            int userId = Constants.Security.SuperUserId,
-            bool branchOne = false, bool branchRoot = false)
+        if (eventMessages == null)
         {
-            if (scope == null)
-            {
-                throw new ArgumentNullException(nameof(scope));
-            }
+            throw new ArgumentNullException(nameof(eventMessages));
+        }
 
-            if (content == null)
-            {
-                throw new ArgumentNullException(nameof(content));
-            }
+        PublishResult? publishResult = null;
+        PublishResult? unpublishResult = null;
 
-            if (eventMessages == null)
-            {
-                throw new ArgumentNullException(nameof(eventMessages));
-            }
+        // nothing set = republish it all
+        if (content.PublishedState != PublishedState.Publishing &&
+            content.PublishedState != PublishedState.Unpublishing)
+        {
+            content.PublishedState = PublishedState.Publishing;
+        }
+
+        // State here is either Publishing or Unpublishing
+        // Publishing to unpublish a culture may end up unpublishing everything so these flags can be flipped later
+        var publishing = content.PublishedState == PublishedState.Publishing;
+        var unpublishing = content.PublishedState == PublishedState.Unpublishing;
+
+        var variesByCulture = content.ContentType.VariesByCulture();
 
-            PublishResult? publishResult = null;
-            PublishResult? unpublishResult = null;
+        //track cultures that are being published, changed, unpublished
+        IReadOnlyList? culturesPublishing = null;
+        IReadOnlyList? culturesUnpublishing = null;
+        IReadOnlyList? culturesChanging = variesByCulture
+            ? content.CultureInfos?.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList()
+            : null;
 
-            // nothing set = republish it all
-            if (content.PublishedState != PublishedState.Publishing &&
-                content.PublishedState != PublishedState.Unpublishing)
+        var isNew = !content.HasIdentity;
+        TreeChangeTypes changeType = isNew ? TreeChangeTypes.RefreshNode : TreeChangeTypes.RefreshBranch;
+        var previouslyPublished = content.HasIdentity && content.Published;
+
+        //inline method to persist the document with the documentRepository since this logic could be called a couple times below
+        void SaveDocument(IContent c)
+        {
+            // save, always
+            if (c.HasIdentity == false)
             {
-                content.PublishedState = PublishedState.Publishing;
+                c.CreatorId = userId;
             }
 
-            // State here is either Publishing or Unpublishing
-            // Publishing to unpublish a culture may end up unpublishing everything so these flags can be flipped later
-            var publishing = content.PublishedState == PublishedState.Publishing;
-            var unpublishing = content.PublishedState == PublishedState.Unpublishing;
+            c.WriterId = userId;
 
-            var variesByCulture = content.ContentType.VariesByCulture();
+            // saving does NOT change the published version, unless PublishedState is Publishing or Unpublishing
+            _documentRepository.Save(c);
+        }
 
-            //track cultures that are being published, changed, unpublished
-            IReadOnlyList? culturesPublishing = null;
-            IReadOnlyList? culturesUnpublishing = null;
-            IReadOnlyList? culturesChanging = variesByCulture
-                ? content.CultureInfos?.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList()
+        if (publishing)
+        {
+            //determine cultures publishing/unpublishing which will be based on previous calls to content.PublishCulture and ClearPublishInfo
+            culturesUnpublishing = content.GetCulturesUnpublishing();
+            culturesPublishing = variesByCulture
+                ? content.PublishCultureInfos?.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList()
                 : null;
 
-            var isNew = !content.HasIdentity;
-            TreeChangeTypes changeType = isNew ? TreeChangeTypes.RefreshNode : TreeChangeTypes.RefreshBranch;
-            var previouslyPublished = content.HasIdentity && content.Published;
+            // ensure that the document can be published, and publish handling events, business rules, etc
+            publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ !branchOne || branchRoot,
+                culturesPublishing, culturesUnpublishing, eventMessages, allLangs, notificationState);
+            if (publishResult.Success)
+            {
+                // note: StrategyPublish flips the PublishedState to Publishing!
+                publishResult = StrategyPublish(content, culturesPublishing, culturesUnpublishing, eventMessages);
+
+                //check if a culture has been unpublished and if there are no cultures left, and then unpublish document as a whole
+                if (publishResult.Result == PublishResultType.SuccessUnpublishCulture &&
+                    content.PublishCultureInfos?.Count == 0)
+                {
+                    // This is a special case! We are unpublishing the last culture and to persist that we need to re-publish without any cultures
+                    // so the state needs to remain Publishing to do that. However, we then also need to unpublish the document and to do that
+                    // the state needs to be Unpublishing and it cannot be both. This state is used within the documentRepository to know how to
+                    // persist certain things. So before proceeding below, we need to save the Publishing state to publish no cultures, then we can
+                    // mark the document for Unpublishing.
+                    SaveDocument(content);
 
-            //inline method to persist the document with the documentRepository since this logic could be called a couple times below
-            void SaveDocument(IContent c)
+                    //set the flag to unpublish and continue
+                    unpublishing = content.Published; // if not published yet, nothing to do
+                }
+            }
+            else
             {
-                // save, always
-                if (c.HasIdentity == false)
+                // in a branch, just give up
+                if (branchOne && !branchRoot)
                 {
-                    c.CreatorId = userId;
+                    return publishResult;
                 }
 
-                c.WriterId = userId;
+                //check for mandatory culture missing, and then unpublish document as a whole
+                if (publishResult.Result == PublishResultType.FailedPublishMandatoryCultureMissing)
+                {
+                    publishing = false;
+                    unpublishing = content.Published; // if not published yet, nothing to do
+
+                    // we may end up in a state where we won't publish nor unpublish
+                    // keep going, though, as we want to save anyways
+                }
 
-                // saving does NOT change the published version, unless PublishedState is Publishing or Unpublishing
-                _documentRepository.Save(c);
+                // reset published state from temp values (publishing, unpublishing) to original value
+                // (published, unpublished) in order to save the document, unchanged - yes, this is odd,
+                // but: (a) it means we don't reproduce the PublishState logic here and (b) setting the
+                // PublishState to anything other than Publishing or Unpublishing - which is precisely
+                // what we want to do here - throws
+                content.Published = content.Published;
             }
+        }
 
-            if (publishing)
+        if (unpublishing) // won't happen in a branch
+        {
+            IContent? newest = GetById(content.Id); // ensure we have the newest version - in scope
+            if (content.VersionId != newest?.VersionId)
             {
-                //determine cultures publishing/unpublishing which will be based on previous calls to content.PublishCulture and ClearPublishInfo
-                culturesUnpublishing = content.GetCulturesUnpublishing();
-                culturesPublishing = variesByCulture
-                    ? content.PublishCultureInfos?.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList()
-                    : null;
+                return new PublishResult(PublishResultType.FailedPublishConcurrencyViolation, eventMessages,
+                    content);
+            }
 
-                // ensure that the document can be published, and publish handling events, business rules, etc
-                publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ !branchOne || branchRoot,
-                    culturesPublishing, culturesUnpublishing, eventMessages, allLangs, notificationState);
-                if (publishResult.Success)
+            if (content.Published)
+            {
+                // ensure that the document can be unpublished, and unpublish
+                // handling events, business rules, etc
+                // note: StrategyUnpublish flips the PublishedState to Unpublishing!
+                // note: This unpublishes the entire document (not different variants)
+                unpublishResult = StrategyCanUnpublish(scope, content, eventMessages);
+                if (unpublishResult.Success)
                 {
-                    // note: StrategyPublish flips the PublishedState to Publishing!
-                    publishResult = StrategyPublish(content, culturesPublishing, culturesUnpublishing, eventMessages);
-
-                    //check if a culture has been unpublished and if there are no cultures left, and then unpublish document as a whole
-                    if (publishResult.Result == PublishResultType.SuccessUnpublishCulture &&
-                        content.PublishCultureInfos?.Count == 0)
-                    {
-                        // This is a special case! We are unpublishing the last culture and to persist that we need to re-publish without any cultures
-                        // so the state needs to remain Publishing to do that. However, we then also need to unpublish the document and to do that
-                        // the state needs to be Unpublishing and it cannot be both. This state is used within the documentRepository to know how to
-                        // persist certain things. So before proceeding below, we need to save the Publishing state to publish no cultures, then we can
-                        // mark the document for Unpublishing.
-                        SaveDocument(content);
-
-                        //set the flag to unpublish and continue
-                        unpublishing = content.Published; // if not published yet, nothing to do
-                    }
+                    unpublishResult = StrategyUnpublish(content, eventMessages);
                 }
                 else
                 {
-                    // in a branch, just give up
-                    if (branchOne && !branchRoot)
-                    {
-                        return publishResult;
-                    }
-
-                    //check for mandatory culture missing, and then unpublish document as a whole
-                    if (publishResult.Result == PublishResultType.FailedPublishMandatoryCultureMissing)
-                    {
-                        publishing = false;
-                        unpublishing = content.Published; // if not published yet, nothing to do
-
-                        // we may end up in a state where we won't publish nor unpublish
-                        // keep going, though, as we want to save anyways
-                    }
-
                     // reset published state from temp values (publishing, unpublishing) to original value
                     // (published, unpublished) in order to save the document, unchanged - yes, this is odd,
                     // but: (a) it means we don't reproduce the PublishState logic here and (b) setting the
@@ -1501,2167 +1529,2135 @@ void SaveDocument(IContent c)
                     content.Published = content.Published;
                 }
             }
+            else
+            {
+                // already unpublished - optimistic concurrency collision, really,
+                // and I am not sure at all what we should do, better die fast, else
+                // we may end up corrupting the db
+                throw new InvalidOperationException("Concurrency collision.");
+            }
+        }
+
+        //Persist the document
+        SaveDocument(content);
 
-            if (unpublishing) // won't happen in a branch
+        // raise the Saved event, always
+        scope.Notifications.Publish(
+            new ContentSavedNotification(content, eventMessages).WithState(notificationState));
+
+        if (unpublishing) // we have tried to unpublish - won't happen in a branch
+        {
+            if (unpublishResult?.Success ?? false) // and succeeded, trigger events
             {
-                IContent? newest = GetById(content.Id); // ensure we have the newest version - in scope
-                if (content.VersionId != newest?.VersionId)
-                {
-                    return new PublishResult(PublishResultType.FailedPublishConcurrencyViolation, eventMessages,
-                        content);
-                }
+                // events and audit
+                scope.Notifications.Publish(
+                    new ContentUnpublishedNotification(content, eventMessages).WithState(notificationState));
+                scope.Notifications.Publish(new ContentTreeChangeNotification(content,
+                    TreeChangeTypes.RefreshBranch, eventMessages));
 
-                if (content.Published)
+                if (culturesUnpublishing != null)
                 {
-                    // ensure that the document can be unpublished, and unpublish
-                    // handling events, business rules, etc
-                    // note: StrategyUnpublish flips the PublishedState to Unpublishing!
-                    // note: This unpublishes the entire document (not different variants)
-                    unpublishResult = StrategyCanUnpublish(scope, content, eventMessages);
-                    if (unpublishResult.Success)
+                    // This will mean that that we unpublished a mandatory culture or we unpublished the last culture.
+
+                    var langs = string.Join(", ", allLangs
+                        .Where(x => culturesUnpublishing.InvariantContains(x.IsoCode))
+                        .Select(x => x.CultureName));
+                    Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs);
+
+                    if (publishResult == null)
                     {
-                        unpublishResult = StrategyUnpublish(content, eventMessages);
+                        throw new PanicException("publishResult == null - should not happen");
                     }
-                    else
+
+                    switch (publishResult.Result)
                     {
-                        // reset published state from temp values (publishing, unpublishing) to original value
-                        // (published, unpublished) in order to save the document, unchanged - yes, this is odd,
-                        // but: (a) it means we don't reproduce the PublishState logic here and (b) setting the
-                        // PublishState to anything other than Publishing or Unpublishing - which is precisely
-                        // what we want to do here - throws
-                        content.Published = content.Published;
+                        case PublishResultType.FailedPublishMandatoryCultureMissing:
+                            //occurs when a mandatory culture was unpublished (which means we tried publishing the document without a mandatory culture)
+
+                            //log that the whole content item has been unpublished due to mandatory culture unpublished
+                            Audit(AuditType.Unpublish, userId, content.Id,
+                                "Unpublished (mandatory language unpublished)");
+                            return new PublishResult(PublishResultType.SuccessUnpublishMandatoryCulture,
+                                eventMessages, content);
+                        case PublishResultType.SuccessUnpublishCulture:
+                            //occurs when the last culture is unpublished
+
+                            Audit(AuditType.Unpublish, userId, content.Id,
+                                "Unpublished (last language unpublished)");
+                            return new PublishResult(PublishResultType.SuccessUnpublishLastCulture, eventMessages,
+                                content);
                     }
                 }
-                else
-                {
-                    // already unpublished - optimistic concurrency collision, really,
-                    // and I am not sure at all what we should do, better die fast, else
-                    // we may end up corrupting the db
-                    throw new InvalidOperationException("Concurrency collision.");
-                }
-            }
 
-            //Persist the document
-            SaveDocument(content);
+                Audit(AuditType.Unpublish, userId, content.Id);
+                return new PublishResult(PublishResultType.SuccessUnpublish, eventMessages, content);
+            }
 
-            // raise the Saved event, always
-            scope.Notifications.Publish(
-                new ContentSavedNotification(content, eventMessages).WithState(notificationState));
+            // or, failed
+            scope.Notifications.Publish(new ContentTreeChangeNotification(content, changeType, eventMessages));
+            return new PublishResult(PublishResultType.FailedUnpublish, eventMessages, content); // bah
+        }
 
-            if (unpublishing) // we have tried to unpublish - won't happen in a branch
+        if (publishing) // we have tried to publish
+        {
+            if (publishResult?.Success ?? false) // and succeeded, trigger events
             {
-                if (unpublishResult?.Success ?? false) // and succeeded, trigger events
+                if (isNew == false && previouslyPublished == false)
                 {
-                    // events and audit
-                    scope.Notifications.Publish(
-                        new ContentUnpublishedNotification(content, eventMessages).WithState(notificationState));
-                    scope.Notifications.Publish(new ContentTreeChangeNotification(content,
-                        TreeChangeTypes.RefreshBranch, eventMessages));
+                    changeType = TreeChangeTypes.RefreshBranch; // whole branch
+                }
+                else if (isNew == false && previouslyPublished)
+                {
+                    changeType = TreeChangeTypes.RefreshNode; // single node
+                }
 
-                    if (culturesUnpublishing != null)
-                    {
-                        // This will mean that that we unpublished a mandatory culture or we unpublished the last culture.
 
-                        var langs = string.Join(", ", allLangs
-                            .Where(x => culturesUnpublishing.InvariantContains(x.IsoCode))
-                            .Select(x => x.CultureName));
-                        Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs);
+                // invalidate the node/branch
+                if (!branchOne) // for branches, handled by SaveAndPublishBranch
+                {
+                    scope.Notifications.Publish(
+                        new ContentTreeChangeNotification(content, changeType, eventMessages));
+                    scope.Notifications.Publish(
+                        new ContentPublishedNotification(content, eventMessages).WithState(notificationState));
+                }
+
+                // it was not published and now is... descendants that were 'published' (but
+                // had an unpublished ancestor) are 're-published' ie not explicitly published
+                // but back as 'published' nevertheless
+                if (!branchOne && isNew == false && previouslyPublished == false && HasChildren(content.Id))
+                {
+                    IContent[] descendants = GetPublishedDescendantsLocked(content).ToArray();
+                    scope.Notifications.Publish(
+                        new ContentPublishedNotification(descendants, eventMessages).WithState(notificationState));
+                }
 
-                        if (publishResult == null)
+                switch (publishResult.Result)
+                {
+                    case PublishResultType.SuccessPublish:
+                        Audit(AuditType.Publish, userId, content.Id);
+                        break;
+                    case PublishResultType.SuccessPublishCulture:
+                        if (culturesPublishing != null)
                         {
-                            throw new PanicException("publishResult == null - should not happen");
+                            var langs = string.Join(", ", allLangs
+                                .Where(x => culturesPublishing.InvariantContains(x.IsoCode))
+                                .Select(x => x.CultureName));
+                            Audit(AuditType.PublishVariant, userId, content.Id, $"Published languages: {langs}",
+                                langs);
                         }
 
-                        switch (publishResult.Result)
+                        break;
+                    case PublishResultType.SuccessUnpublishCulture:
+                        if (culturesUnpublishing != null)
                         {
-                            case PublishResultType.FailedPublishMandatoryCultureMissing:
-                                //occurs when a mandatory culture was unpublished (which means we tried publishing the document without a mandatory culture)
-
-                                //log that the whole content item has been unpublished due to mandatory culture unpublished
-                                Audit(AuditType.Unpublish, userId, content.Id,
-                                    "Unpublished (mandatory language unpublished)");
-                                return new PublishResult(PublishResultType.SuccessUnpublishMandatoryCulture,
-                                    eventMessages, content);
-                            case PublishResultType.SuccessUnpublishCulture:
-                                //occurs when the last culture is unpublished
-
-                                Audit(AuditType.Unpublish, userId, content.Id,
-                                    "Unpublished (last language unpublished)");
-                                return new PublishResult(PublishResultType.SuccessUnpublishLastCulture, eventMessages,
-                                    content);
+                            var langs = string.Join(", ", allLangs
+                                .Where(x => culturesUnpublishing.InvariantContains(x.IsoCode))
+                                .Select(x => x.CultureName));
+                            Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}",
+                                langs);
                         }
-                    }
 
-                    Audit(AuditType.Unpublish, userId, content.Id);
-                    return new PublishResult(PublishResultType.SuccessUnpublish, eventMessages, content);
+                        break;
                 }
 
-                // or, failed
-                scope.Notifications.Publish(new ContentTreeChangeNotification(content, changeType, eventMessages));
-                return new PublishResult(PublishResultType.FailedUnpublish, eventMessages, content); // bah
+                return publishResult;
+            }
+        }
+
+        // should not happen
+        if (branchOne && !branchRoot)
+        {
+            throw new PanicException("branchOne && !branchRoot - should not happen");
+        }
+
+        //if publishing didn't happen or if it has failed, we still need to log which cultures were saved
+        if (!branchOne && (publishResult == null || !publishResult.Success))
+        {
+            if (culturesChanging != null)
+            {
+                var langs = string.Join(", ", allLangs
+                    .Where(x => culturesChanging.InvariantContains(x.IsoCode))
+                    .Select(x => x.CultureName));
+                Audit(AuditType.SaveVariant, userId, content.Id, $"Saved languages: {langs}", langs);
+            }
+            else
+            {
+                Audit(AuditType.Save, userId, content.Id);
             }
+        }
+
+        // or, failed
+        scope.Notifications.Publish(new ContentTreeChangeNotification(content, changeType, eventMessages));
+        return publishResult!;
+    }
+
+    /// 
+    public IEnumerable PerformScheduledPublish(DateTime date)
+    {
+        var allLangs = new Lazy>(() => _languageRepository.GetMany().ToList());
+        EventMessages evtMsgs = EventMessagesFactory.Get();
+        var results = new List();
+
+        PerformScheduledPublishingRelease(date, results, evtMsgs, allLangs);
+        PerformScheduledPublishingExpiration(date, results, evtMsgs, allLangs);
+
+        return results;
+    }
+
+    private void PerformScheduledPublishingExpiration(DateTime date, List results,
+        EventMessages evtMsgs, Lazy> allLangs)
+    {
+        using ICoreScope scope = ScopeProvider.CreateCoreScope();
+
+        // do a fast read without any locks since this executes often to see if we even need to proceed
+        if (_documentRepository.HasContentForExpiration(date))
+        {
+            // now take a write lock since we'll be updating
+            scope.WriteLock(Constants.Locks.ContentTree);
 
-            if (publishing) // we have tried to publish
+            foreach (IContent d in _documentRepository.GetContentForExpiration(date))
             {
-                if (publishResult?.Success ?? false) // and succeeded, trigger events
+                ContentScheduleCollection contentSchedule = _documentRepository.GetContentSchedule(d.Id);
+                if (d.ContentType.VariesByCulture())
                 {
-                    if (isNew == false && previouslyPublished == false)
-                    {
-                        changeType = TreeChangeTypes.RefreshBranch; // whole branch
-                    }
-                    else if (isNew == false && previouslyPublished)
+                    //find which cultures have pending schedules
+                    var pendingCultures = contentSchedule.GetPending(ContentScheduleAction.Expire, date)
+                        .Select(x => x.Culture)
+                        .Distinct()
+                        .ToList();
+
+                    if (pendingCultures.Count == 0)
                     {
-                        changeType = TreeChangeTypes.RefreshNode; // single node
+                        continue; //shouldn't happen but no point in processing this document if there's nothing there
                     }
 
-
-                    // invalidate the node/branch
-                    if (!branchOne) // for branches, handled by SaveAndPublishBranch
+                    var savingNotification = new ContentSavingNotification(d, evtMsgs);
+                    if (scope.Notifications.PublishCancelable(savingNotification))
                     {
-                        scope.Notifications.Publish(
-                            new ContentTreeChangeNotification(content, changeType, eventMessages));
-                        scope.Notifications.Publish(
-                            new ContentPublishedNotification(content, eventMessages).WithState(notificationState));
+                        results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d));
+                        continue;
                     }
 
-                    // it was not published and now is... descendants that were 'published' (but
-                    // had an unpublished ancestor) are 're-published' ie not explicitly published
-                    // but back as 'published' nevertheless
-                    if (!branchOne && isNew == false && previouslyPublished == false && HasChildren(content.Id))
+                    foreach (var c in pendingCultures)
                     {
-                        IContent[] descendants = GetPublishedDescendantsLocked(content).ToArray();
-                        scope.Notifications.Publish(
-                            new ContentPublishedNotification(descendants, eventMessages).WithState(notificationState));
+                        //Clear this schedule for this culture
+                        contentSchedule.Clear(c, ContentScheduleAction.Expire, date);
+                        //set the culture to be published
+                        d.UnpublishCulture(c);
                     }
 
-                    switch (publishResult.Result)
+                    _documentRepository.PersistContentSchedule(d, contentSchedule);
+                    PublishResult result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value,
+                        savingNotification.State, d.WriterId);
+                    if (result.Success == false)
                     {
-                        case PublishResultType.SuccessPublish:
-                            Audit(AuditType.Publish, userId, content.Id);
-                            break;
-                        case PublishResultType.SuccessPublishCulture:
-                            if (culturesPublishing != null)
-                            {
-                                var langs = string.Join(", ", allLangs
-                                    .Where(x => culturesPublishing.InvariantContains(x.IsoCode))
-                                    .Select(x => x.CultureName));
-                                Audit(AuditType.PublishVariant, userId, content.Id, $"Published languages: {langs}",
-                                    langs);
-                            }
-
-                            break;
-                        case PublishResultType.SuccessUnpublishCulture:
-                            if (culturesUnpublishing != null)
-                            {
-                                var langs = string.Join(", ", allLangs
-                                    .Where(x => culturesUnpublishing.InvariantContains(x.IsoCode))
-                                    .Select(x => x.CultureName));
-                                Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}",
-                                    langs);
-                            }
-
-                            break;
+                        _logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id,
+                            result.Result);
                     }
 
-                    return publishResult;
-                }
-            }
-
-            // should not happen
-            if (branchOne && !branchRoot)
-            {
-                throw new PanicException("branchOne && !branchRoot - should not happen");
-            }
-
-            //if publishing didn't happen or if it has failed, we still need to log which cultures were saved
-            if (!branchOne && (publishResult == null || !publishResult.Success))
-            {
-                if (culturesChanging != null)
-                {
-                    var langs = string.Join(", ", allLangs
-                        .Where(x => culturesChanging.InvariantContains(x.IsoCode))
-                        .Select(x => x.CultureName));
-                    Audit(AuditType.SaveVariant, userId, content.Id, $"Saved languages: {langs}", langs);
+                    results.Add(result);
                 }
                 else
                 {
-                    Audit(AuditType.Save, userId, content.Id);
+                    //Clear this schedule for this culture
+                    contentSchedule.Clear(ContentScheduleAction.Expire, date);
+                    _documentRepository.PersistContentSchedule(d, contentSchedule);
+                    PublishResult result = Unpublish(d, userId: d.WriterId);
+                    if (result.Success == false)
+                    {
+                        _logger.LogError(null, "Failed to unpublish document id={DocumentId}, reason={Reason}.",
+                            d.Id, result.Result);
+                    }
+
+                    results.Add(result);
                 }
             }
 
-            // or, failed
-            scope.Notifications.Publish(new ContentTreeChangeNotification(content, changeType, eventMessages));
-            return publishResult!;
+            _documentRepository.ClearSchedule(date, ContentScheduleAction.Expire);
         }
 
-        /// 
-        public IEnumerable PerformScheduledPublish(DateTime date)
-        {
-            var allLangs = new Lazy>(() => _languageRepository.GetMany().ToList());
-            EventMessages evtMsgs = EventMessagesFactory.Get();
-            var results = new List();
-
-            PerformScheduledPublishingRelease(date, results, evtMsgs, allLangs);
-            PerformScheduledPublishingExpiration(date, results, evtMsgs, allLangs);
+        scope.Complete();
+    }
 
-            return results;
-        }
+    private void PerformScheduledPublishingRelease(DateTime date, List results,
+        EventMessages evtMsgs, Lazy> allLangs)
+    {
+        using ICoreScope scope = ScopeProvider.CreateCoreScope();
 
-        private void PerformScheduledPublishingExpiration(DateTime date, List results,
-            EventMessages evtMsgs, Lazy> allLangs)
+        // do a fast read without any locks since this executes often to see if we even need to proceed
+        if (_documentRepository.HasContentForRelease(date))
         {
-            using ICoreScope scope = ScopeProvider.CreateCoreScope();
+            // now take a write lock since we'll be updating
+            scope.WriteLock(Constants.Locks.ContentTree);
 
-            // do a fast read without any locks since this executes often to see if we even need to proceed
-            if (_documentRepository.HasContentForExpiration(date))
+            foreach (IContent d in _documentRepository.GetContentForRelease(date))
             {
-                // now take a write lock since we'll be updating
-                scope.WriteLock(Constants.Locks.ContentTree);
-
-                foreach (IContent d in _documentRepository.GetContentForExpiration(date))
+                ContentScheduleCollection contentSchedule = _documentRepository.GetContentSchedule(d.Id);
+                if (d.ContentType.VariesByCulture())
                 {
-                    ContentScheduleCollection contentSchedule = _documentRepository.GetContentSchedule(d.Id);
-                    if (d.ContentType.VariesByCulture())
+                    //find which cultures have pending schedules
+                    var pendingCultures = contentSchedule.GetPending(ContentScheduleAction.Release, date)
+                        .Select(x => x.Culture)
+                        .Distinct()
+                        .ToList();
+
+                    if (pendingCultures.Count == 0)
                     {
-                        //find which cultures have pending schedules
-                        var pendingCultures = contentSchedule.GetPending(ContentScheduleAction.Expire, date)
-                            .Select(x => x.Culture)
-                            .Distinct()
-                            .ToList();
+                        continue; //shouldn't happen but no point in processing this document if there's nothing there
+                    }
 
-                        if (pendingCultures.Count == 0)
-                        {
-                            continue; //shouldn't happen but no point in processing this document if there's nothing there
-                        }
+                    var savingNotification = new ContentSavingNotification(d, evtMsgs);
+                    if (scope.Notifications.PublishCancelable(savingNotification))
+                    {
+                        results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d));
+                        continue;
+                    }
+
+                    var publishing = true;
+                    foreach (var culture in pendingCultures)
+                    {
+                        //Clear this schedule for this culture
+                        contentSchedule.Clear(culture, ContentScheduleAction.Release, date);
 
-                        var savingNotification = new ContentSavingNotification(d, evtMsgs);
-                        if (scope.Notifications.PublishCancelable(savingNotification))
+                        if (d.Trashed)
                         {
-                            results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d));
-                            continue;
+                            continue; // won't publish
                         }
 
-                        foreach (var c in pendingCultures)
+                        //publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed
+                        IProperty[]? invalidProperties = null;
+                        var impact = CultureImpact.Explicit(culture, IsDefaultCulture(allLangs.Value, culture));
+                        var tryPublish = d.PublishCulture(impact) &&
+                                         _propertyValidationService.Value.IsPropertyDataValid(d,
+                                             out invalidProperties, impact);
+                        if (invalidProperties != null && invalidProperties.Length > 0)
                         {
-                            //Clear this schedule for this culture
-                            contentSchedule.Clear(c, ContentScheduleAction.Expire, date);
-                            //set the culture to be published
-                            d.UnpublishCulture(c);
+                            _logger.LogWarning(
+                                "Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}",
+                                d.Id, culture, string.Join(",", invalidProperties.Select(x => x.Alias)));
                         }
 
-                        _documentRepository.PersistContentSchedule(d, contentSchedule);
-                        PublishResult result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value,
-                            savingNotification.State, d.WriterId);
-                        if (result.Success == false)
+                        publishing &= tryPublish; //set the culture to be published
+                        if (!publishing)
                         {
-                            _logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id,
-                                result.Result);
                         }
+                    }
 
-                        results.Add(result);
+                    PublishResult result;
+
+                    if (d.Trashed)
+                    {
+                        result = new PublishResult(PublishResultType.FailedPublishIsTrashed, evtMsgs, d);
+                    }
+                    else if (!publishing)
+                    {
+                        result = new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, d);
                     }
                     else
                     {
-                        //Clear this schedule for this culture
-                        contentSchedule.Clear(ContentScheduleAction.Expire, date);
                         _documentRepository.PersistContentSchedule(d, contentSchedule);
-                        PublishResult result = Unpublish(d, userId: d.WriterId);
-                        if (result.Success == false)
-                        {
-                            _logger.LogError(null, "Failed to unpublish document id={DocumentId}, reason={Reason}.",
-                                d.Id, result.Result);
-                        }
-
-                        results.Add(result);
+                        result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value,
+                            savingNotification.State, d.WriterId);
                     }
-                }
-
-                _documentRepository.ClearSchedule(date, ContentScheduleAction.Expire);
-            }
-
-            scope.Complete();
-        }
-
-        private void PerformScheduledPublishingRelease(DateTime date, List results,
-            EventMessages evtMsgs, Lazy> allLangs)
-        {
-            using ICoreScope scope = ScopeProvider.CreateCoreScope();
-
-            // do a fast read without any locks since this executes often to see if we even need to proceed
-            if (_documentRepository.HasContentForRelease(date))
-            {
-                // now take a write lock since we'll be updating
-                scope.WriteLock(Constants.Locks.ContentTree);
 
-                foreach (IContent d in _documentRepository.GetContentForRelease(date))
-                {
-                    ContentScheduleCollection contentSchedule = _documentRepository.GetContentSchedule(d.Id);
-                    if (d.ContentType.VariesByCulture())
+                    if (result.Success == false)
                     {
-                        //find which cultures have pending schedules
-                        var pendingCultures = contentSchedule.GetPending(ContentScheduleAction.Release, date)
-                            .Select(x => x.Culture)
-                            .Distinct()
-                            .ToList();
-
-                        if (pendingCultures.Count == 0)
-                        {
-                            continue; //shouldn't happen but no point in processing this document if there's nothing there
-                        }
-
-                        var savingNotification = new ContentSavingNotification(d, evtMsgs);
-                        if (scope.Notifications.PublishCancelable(savingNotification))
-                        {
-                            results.Add(new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, d));
-                            continue;
-                        }
-
-                        var publishing = true;
-                        foreach (var culture in pendingCultures)
-                        {
-                            //Clear this schedule for this culture
-                            contentSchedule.Clear(culture, ContentScheduleAction.Release, date);
-
-                            if (d.Trashed)
-                            {
-                                continue; // won't publish
-                            }
-
-                            //publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed
-                            IProperty[]? invalidProperties = null;
-                            var impact = CultureImpact.Explicit(culture, IsDefaultCulture(allLangs.Value, culture));
-                            var tryPublish = d.PublishCulture(impact) &&
-                                             _propertyValidationService.Value.IsPropertyDataValid(d,
-                                                 out invalidProperties, impact);
-                            if (invalidProperties != null && invalidProperties.Length > 0)
-                            {
-                                _logger.LogWarning(
-                                    "Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}",
-                                    d.Id, culture, string.Join(",", invalidProperties.Select(x => x.Alias)));
-                            }
-
-                            publishing &= tryPublish; //set the culture to be published
-                            if (!publishing)
-                            {
-                                continue;
-                            }
-                        }
-
-                        PublishResult result;
+                        _logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id,
+                            result.Result);
+                    }
 
-                        if (d.Trashed)
-                        {
-                            result = new PublishResult(PublishResultType.FailedPublishIsTrashed, evtMsgs, d);
-                        }
-                        else if (!publishing)
-                        {
-                            result = new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, d);
-                        }
-                        else
-                        {
-                            _documentRepository.PersistContentSchedule(d, contentSchedule);
-                            result = CommitDocumentChangesInternal(scope, d, evtMsgs, allLangs.Value,
-                                savingNotification.State, d.WriterId);
-                        }
+                    results.Add(result);
+                }
+                else
+                {
+                    //Clear this schedule
+                    contentSchedule.Clear(ContentScheduleAction.Release, date);
 
-                        if (result.Success == false)
-                        {
-                            _logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id,
-                                result.Result);
-                        }
+                    PublishResult? result = null;
 
-                        results.Add(result);
+                    if (d.Trashed)
+                    {
+                        result = new PublishResult(PublishResultType.FailedPublishIsTrashed, evtMsgs, d);
                     }
                     else
                     {
-                        //Clear this schedule
-                        contentSchedule.Clear(ContentScheduleAction.Release, date);
-
-                        PublishResult? result = null;
-
-                        if (d.Trashed)
-                        {
-                            result = new PublishResult(PublishResultType.FailedPublishIsTrashed, evtMsgs, d);
-                        }
-                        else
-                        {
-                            _documentRepository.PersistContentSchedule(d, contentSchedule);
-                            result = SaveAndPublish(d, userId: d.WriterId);
-                        }
-
-                        if (result.Success == false)
-                        {
-                            _logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id,
-                                result.Result);
-                        }
+                        _documentRepository.PersistContentSchedule(d, contentSchedule);
+                        result = SaveAndPublish(d, userId: d.WriterId);
+                    }
 
-                        results.Add(result);
+                    if (result.Success == false)
+                    {
+                        _logger.LogError(null, "Failed to publish document id={DocumentId}, reason={Reason}.", d.Id,
+                            result.Result);
                     }
-                }
 
-                _documentRepository.ClearSchedule(date, ContentScheduleAction.Release);
+                    results.Add(result);
+                }
             }
 
-            scope.Complete();
+            _documentRepository.ClearSchedule(date, ContentScheduleAction.Release);
         }
 
-        // utility 'PublishCultures' func used by SaveAndPublishBranch
-        private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet culturesToPublish,
-            IReadOnlyCollection allLangs)
-        {
-            //TODO: This does not support being able to return invalid property details to bubble up to the UI
-
-            // variant content type - publish specified cultures
-            // invariant content type - publish only the invariant culture
-            if (content.ContentType.VariesByCulture())
-            {
-                return culturesToPublish.All(culture =>
-                {
-                    var impact = CultureImpact.Create(culture, IsDefaultCulture(allLangs, culture), content);
-                    return content.PublishCulture(impact) &&
-                           _propertyValidationService.Value.IsPropertyDataValid(content, out _, impact);
-                });
-            }
+        scope.Complete();
+    }
 
-            return content.PublishCulture(CultureImpact.Invariant)
-                   && _propertyValidationService.Value.IsPropertyDataValid(content, out _, CultureImpact.Invariant);
-        }
+    // utility 'PublishCultures' func used by SaveAndPublishBranch
+    private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet culturesToPublish,
+        IReadOnlyCollection allLangs)
+    {
+        //TODO: This does not support being able to return invalid property details to bubble up to the UI
 
-        // utility 'ShouldPublish' func used by SaveAndPublishBranch
-        private HashSet? SaveAndPublishBranch_ShouldPublish(ref HashSet? cultures, string c,
-            bool published, bool edited, bool isRoot, bool force)
+        // variant content type - publish specified cultures
+        // invariant content type - publish only the invariant culture
+        if (content.ContentType.VariesByCulture())
         {
-            // if published, republish
-            if (published)
+            return culturesToPublish.All(culture =>
             {
-                if (cultures == null)
-                {
-                    cultures = new HashSet(); // empty means 'already published'
-                }
-
-                if (edited)
-                {
-                    cultures.Add(c); //  means 'republish this culture'
-                }
+                var impact = CultureImpact.Create(culture, IsDefaultCulture(allLangs, culture), content);
+                return content.PublishCulture(impact) &&
+                       _propertyValidationService.Value.IsPropertyDataValid(content, out _, impact);
+            });
+        }
 
-                return cultures;
-            }
+        return content.PublishCulture(CultureImpact.Invariant)
+               && _propertyValidationService.Value.IsPropertyDataValid(content, out _, CultureImpact.Invariant);
+    }
 
-            // if not published, publish if force/root else do nothing
-            if (!force && !isRoot)
+    // utility 'ShouldPublish' func used by SaveAndPublishBranch
+    private HashSet? SaveAndPublishBranch_ShouldPublish(ref HashSet? cultures, string c,
+        bool published, bool edited, bool isRoot, bool force)
+    {
+        // if published, republish
+        if (published)
+        {
+            if (cultures == null)
             {
-                return cultures; // null means 'nothing to do'
+                cultures = new HashSet(); // empty means 'already published'
             }
 
-            if (cultures == null)
+            if (edited)
             {
-                cultures = new HashSet();
+                cultures.Add(c); //  means 'republish this culture'
             }
 
-            cultures.Add(c); //  means 'publish this culture'
             return cultures;
         }
 
-        /// 
-        public IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = "*",
-            int userId = Constants.Security.SuperUserId)
+        // if not published, publish if force/root else do nothing
+        if (!force && !isRoot)
         {
-            // note: EditedValue and PublishedValue are objects here, so it is important to .Equals()
-            // and not to == them, else we would be comparing references, and that is a bad thing
+            return cultures; // null means 'nothing to do'
+        }
 
-            // determines whether the document is edited, and thus needs to be published,
-            // for the specified culture (it may be edited for other cultures and that
-            // should not trigger a publish).
+        if (cultures == null)
+        {
+            cultures = new HashSet();
+        }
 
-            // determines cultures to be published
-            // can be: null (content is not impacted), an empty set (content is impacted but already published), or cultures
-            HashSet? ShouldPublish(IContent c)
-            {
-                var isRoot = c.Id == content.Id;
-                HashSet? culturesToPublish = null;
+        cultures.Add(c); //  means 'publish this culture'
+        return cultures;
+    }
 
-                if (!c.ContentType.VariesByCulture()) // invariant content type
-                {
-                    return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot,
-                        force);
-                }
+    /// 
+    public IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = "*",
+        int userId = Constants.Security.SuperUserId)
+    {
+        // note: EditedValue and PublishedValue are objects here, so it is important to .Equals()
+        // and not to == them, else we would be comparing references, and that is a bad thing
 
-                if (culture != "*") // variant content type, specific culture
-                {
-                    return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, culture,
-                        c.IsCulturePublished(culture), c.IsCultureEdited(culture), isRoot, force);
-                }
+        // determines whether the document is edited, and thus needs to be published,
+        // for the specified culture (it may be edited for other cultures and that
+        // should not trigger a publish).
 
-                // variant content type, all cultures
-                if (c.Published)
-                {
-                    // then some (and maybe all) cultures will be 'already published' (unless forcing),
-                    // others will have to 'republish this culture'
-                    foreach (var x in c.AvailableCultures)
-                    {
-                        SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x),
-                            c.IsCultureEdited(x), isRoot, force);
-                    }
+        // determines cultures to be published
+        // can be: null (content is not impacted), an empty set (content is impacted but already published), or cultures
+        HashSet? ShouldPublish(IContent c)
+        {
+            var isRoot = c.Id == content.Id;
+            HashSet? culturesToPublish = null;
+
+            if (!c.ContentType.VariesByCulture()) // invariant content type
+            {
+                return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot,
+                    force);
+            }
 
-                    return culturesToPublish;
+            if (culture != "*") // variant content type, specific culture
+            {
+                return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, culture,
+                    c.IsCulturePublished(culture), c.IsCultureEdited(culture), isRoot, force);
+            }
+
+            // variant content type, all cultures
+            if (c.Published)
+            {
+                // then some (and maybe all) cultures will be 'already published' (unless forcing),
+                // others will have to 'republish this culture'
+                foreach (var x in c.AvailableCultures)
+                {
+                    SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x),
+                        c.IsCultureEdited(x), isRoot, force);
                 }
 
-                // if not published, publish if force/root else do nothing
-                return force || isRoot
-                    ? new HashSet {"*"} // "*" means 'publish all'
-                    : null; // null means 'nothing to do'
+                return culturesToPublish;
             }
 
-            return SaveAndPublishBranch(content, force, ShouldPublish, SaveAndPublishBranch_PublishCultures, userId);
+            // if not published, publish if force/root else do nothing
+            return force || isRoot
+                ? new HashSet {"*"} // "*" means 'publish all'
+                : null; // null means 'nothing to do'
         }
 
-        /// 
-        public IEnumerable SaveAndPublishBranch(IContent content, bool force, string[] cultures,
-            int userId = Constants.Security.SuperUserId)
-        {
-            // note: EditedValue and PublishedValue are objects here, so it is important to .Equals()
-            // and not to == them, else we would be comparing references, and that is a bad thing
+        return SaveAndPublishBranch(content, force, ShouldPublish, SaveAndPublishBranch_PublishCultures, userId);
+    }
 
-            cultures = cultures ?? Array.Empty();
+    /// 
+    public IEnumerable SaveAndPublishBranch(IContent content, bool force, string[] cultures,
+        int userId = Constants.Security.SuperUserId)
+    {
+        // note: EditedValue and PublishedValue are objects here, so it is important to .Equals()
+        // and not to == them, else we would be comparing references, and that is a bad thing
+
+        cultures = cultures ?? Array.Empty();
+
+        // determines cultures to be published
+        // can be: null (content is not impacted), an empty set (content is impacted but already published), or cultures
+        HashSet? ShouldPublish(IContent c)
+        {
+            var isRoot = c.Id == content.Id;
+            HashSet? culturesToPublish = null;
 
-            // determines cultures to be published
-            // can be: null (content is not impacted), an empty set (content is impacted but already published), or cultures
-            HashSet? ShouldPublish(IContent c)
+            if (!c.ContentType.VariesByCulture()) // invariant content type
             {
-                var isRoot = c.Id == content.Id;
-                HashSet? culturesToPublish = null;
+                return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot,
+                    force);
+            }
 
-                if (!c.ContentType.VariesByCulture()) // invariant content type
+            // variant content type, specific cultures
+            if (c.Published)
+            {
+                // then some (and maybe all) cultures will be 'already published' (unless forcing),
+                // others will have to 'republish this culture'
+                foreach (var x in cultures)
                 {
-                    return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot,
-                        force);
+                    SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x),
+                        c.IsCultureEdited(x), isRoot, force);
                 }
 
-                // variant content type, specific cultures
-                if (c.Published)
-                {
-                    // then some (and maybe all) cultures will be 'already published' (unless forcing),
-                    // others will have to 'republish this culture'
-                    foreach (var x in cultures)
-                    {
-                        SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x),
-                            c.IsCultureEdited(x), isRoot, force);
-                    }
+                return culturesToPublish;
+            }
 
-                    return culturesToPublish;
-                }
+            // if not published, publish if force/root else do nothing
+            return force || isRoot
+                ? new HashSet(cultures) // means 'publish specified cultures'
+                : null; // null means 'nothing to do'
+        }
 
-                // if not published, publish if force/root else do nothing
-                return force || isRoot
-                    ? new HashSet(cultures) // means 'publish specified cultures'
-                    : null; // null means 'nothing to do'
-            }
+        return SaveAndPublishBranch(content, force, ShouldPublish, SaveAndPublishBranch_PublishCultures, userId);
+    }
+
+    internal IEnumerable SaveAndPublishBranch(IContent document, bool force,
+        Func?> shouldPublish,
+        Func, IReadOnlyCollection, bool> publishCultures,
+        int userId = Constants.Security.SuperUserId)
+    {
+        if (shouldPublish == null)
+        {
+            throw new ArgumentNullException(nameof(shouldPublish));
+        }
 
-            return SaveAndPublishBranch(content, force, ShouldPublish, SaveAndPublishBranch_PublishCultures, userId);
+        if (publishCultures == null)
+        {
+            throw new ArgumentNullException(nameof(publishCultures));
         }
 
-        internal IEnumerable SaveAndPublishBranch(IContent document, bool force,
-            Func?> shouldPublish,
-            Func, IReadOnlyCollection, bool> publishCultures,
-            int userId = Constants.Security.SuperUserId)
+        EventMessages eventMessages = EventMessagesFactory.Get();
+        var results = new List();
+        var publishedDocuments = new List();
+
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            if (shouldPublish == null)
+            scope.WriteLock(Constants.Locks.ContentTree);
+
+            var allLangs = _languageRepository.GetMany().ToList();
+
+            if (!document.HasIdentity)
             {
-                throw new ArgumentNullException(nameof(shouldPublish));
+                throw new InvalidOperationException("Cannot not branch-publish a new document.");
             }
 
-            if (publishCultures == null)
+            PublishedState publishedState = document.PublishedState;
+            if (publishedState == PublishedState.Publishing)
             {
-                throw new ArgumentNullException(nameof(publishCultures));
+                throw new InvalidOperationException("Cannot mix PublishCulture and SaveAndPublishBranch.");
             }
 
-            EventMessages eventMessages = EventMessagesFactory.Get();
-            var results = new List();
-            var publishedDocuments = new List();
-
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            // deal with the branch root - if it fails, abort
+            PublishResult? result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true,
+                publishedDocuments, eventMessages, userId, allLangs);
+            if (result != null)
             {
-                scope.WriteLock(Constants.Locks.ContentTree);
-
-                var allLangs = _languageRepository.GetMany().ToList();
-
-                if (!document.HasIdentity)
+                results.Add(result);
+                if (!result.Success)
                 {
-                    throw new InvalidOperationException("Cannot not branch-publish a new document.");
+                    return results;
                 }
+            }
 
-                PublishedState publishedState = document.PublishedState;
-                if (publishedState == PublishedState.Publishing)
-                {
-                    throw new InvalidOperationException("Cannot mix PublishCulture and SaveAndPublishBranch.");
-                }
+            // deal with descendants
+            // if one fails, abort its branch
+            var exclude = new HashSet();
 
-                // deal with the branch root - if it fails, abort
-                PublishResult? result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true,
-                    publishedDocuments, eventMessages, userId, allLangs);
-                if (result != null)
+            int count;
+            var page = 0;
+            const int pageSize = 100;
+            do
+            {
+                count = 0;
+                // important to order by Path ASC so make it explicit in case defaults change
+                // ReSharper disable once RedundantArgumentDefaultValue
+                foreach (IContent d in GetPagedDescendants(document.Id, page, pageSize, out _,
+                             ordering: Ordering.By("Path", Direction.Ascending)))
                 {
-                    results.Add(result);
-                    if (!result.Success)
+                    count++;
+
+                    // if parent is excluded, exclude child too
+                    if (exclude.Contains(d.ParentId))
                     {
-                        return results;
+                        exclude.Add(d.Id);
+                        continue;
                     }
-                }
 
-                // deal with descendants
-                // if one fails, abort its branch
-                var exclude = new HashSet();
-
-                int count;
-                var page = 0;
-                const int pageSize = 100;
-                do
-                {
-                    count = 0;
-                    // important to order by Path ASC so make it explicit in case defaults change
-                    // ReSharper disable once RedundantArgumentDefaultValue
-                    foreach (IContent d in GetPagedDescendants(document.Id, page, pageSize, out _,
-                                 ordering: Ordering.By("Path", Direction.Ascending)))
+                    // no need to check path here, parent has to be published here
+                    result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false,
+                        publishedDocuments, eventMessages, userId, allLangs);
+                    if (result != null)
                     {
-                        count++;
-
-                        // if parent is excluded, exclude child too
-                        if (exclude.Contains(d.ParentId))
+                        results.Add(result);
+                        if (result.Success)
                         {
-                            exclude.Add(d.Id);
                             continue;
                         }
+                    }
 
-                        // no need to check path here, parent has to be published here
-                        result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false,
-                            publishedDocuments, eventMessages, userId, allLangs);
-                        if (result != null)
-                        {
-                            results.Add(result);
-                            if (result.Success)
-                            {
-                                continue;
-                            }
-                        }
+                    // if we could not publish the document, cut its branch
+                    exclude.Add(d.Id);
+                }
 
-                        // if we could not publish the document, cut its branch
-                        exclude.Add(d.Id);
-                    }
+                page++;
+            } while (count > 0);
 
-                    page++;
-                } while (count > 0);
+            Audit(AuditType.Publish, userId, document.Id, "Branch published");
 
-                Audit(AuditType.Publish, userId, document.Id, "Branch published");
+            // trigger events for the entire branch
+            // (SaveAndPublishBranchOne does *not* do it)
+            scope.Notifications.Publish(
+                new ContentTreeChangeNotification(document, TreeChangeTypes.RefreshBranch, eventMessages));
+            scope.Notifications.Publish(new ContentPublishedNotification(publishedDocuments, eventMessages));
 
-                // trigger events for the entire branch
-                // (SaveAndPublishBranchOne does *not* do it)
-                scope.Notifications.Publish(
-                    new ContentTreeChangeNotification(document, TreeChangeTypes.RefreshBranch, eventMessages));
-                scope.Notifications.Publish(new ContentPublishedNotification(publishedDocuments, eventMessages));
+            scope.Complete();
+        }
 
-                scope.Complete();
-            }
+        return results;
+    }
 
-            return results;
+    // shouldPublish: a function determining whether the document has changes that need to be published
+    //  note - 'force' is handled by 'editing'
+    // publishValues: a function publishing values (using the appropriate PublishCulture calls)
+    private PublishResult? SaveAndPublishBranchItem(ICoreScope scope, IContent document,
+        Func?> shouldPublish,
+        Func, IReadOnlyCollection, bool> publishCultures,
+        bool isRoot,
+        ICollection publishedDocuments,
+        EventMessages evtMsgs, int userId, IReadOnlyCollection allLangs)
+    {
+        HashSet? culturesToPublish = shouldPublish(document);
+        if (culturesToPublish == null) // null = do not include
+        {
+            return null;
         }
 
-        // shouldPublish: a function determining whether the document has changes that need to be published
-        //  note - 'force' is handled by 'editing'
-        // publishValues: a function publishing values (using the appropriate PublishCulture calls)
-        private PublishResult? SaveAndPublishBranchItem(ICoreScope scope, IContent document,
-            Func?> shouldPublish,
-            Func, IReadOnlyCollection, bool> publishCultures,
-            bool isRoot,
-            ICollection publishedDocuments,
-            EventMessages evtMsgs, int userId, IReadOnlyCollection allLangs)
+        if (culturesToPublish.Count == 0) // empty = already published
         {
-            HashSet? culturesToPublish = shouldPublish(document);
-            if (culturesToPublish == null) // null = do not include
-            {
-                return null;
-            }
+            return new PublishResult(PublishResultType.SuccessPublishAlready, evtMsgs, document);
+        }
 
-            if (culturesToPublish.Count == 0) // empty = already published
-            {
-                return new PublishResult(PublishResultType.SuccessPublishAlready, evtMsgs, document);
-            }
+        var savingNotification = new ContentSavingNotification(document, evtMsgs);
+        if (scope.Notifications.PublishCancelable(savingNotification))
+        {
+            return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, document);
+        }
 
-            var savingNotification = new ContentSavingNotification(document, evtMsgs);
-            if (scope.Notifications.PublishCancelable(savingNotification))
-            {
-                return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, document);
-            }
+        // publish & check if values are valid
+        if (!publishCultures(document, culturesToPublish, allLangs))
+        {
+            //TODO: Based on this callback behavior there is no way to know which properties may have been invalid if this failed, see other results of FailedPublishContentInvalid
+            return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, document);
+        }
 
-            // publish & check if values are valid
-            if (!publishCultures(document, culturesToPublish, allLangs))
-            {
-                //TODO: Based on this callback behavior there is no way to know which properties may have been invalid if this failed, see other results of FailedPublishContentInvalid
-                return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, document);
-            }
+        PublishResult result = CommitDocumentChangesInternal(scope, document, evtMsgs, allLangs,
+            savingNotification.State, userId, true, isRoot);
+        if (result.Success)
+        {
+            publishedDocuments.Add(document);
+        }
 
-            PublishResult result = CommitDocumentChangesInternal(scope, document, evtMsgs, allLangs,
-                savingNotification.State, userId, true, isRoot);
-            if (result.Success)
-            {
-                publishedDocuments.Add(document);
-            }
+        return result;
+    }
 
-            return result;
-        }
+    #endregion
 
-        #endregion
+    #region Delete
 
-        #region Delete
+    /// 
+    public OperationResult Delete(IContent content, int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages eventMessages = EventMessagesFactory.Get();
 
-        /// 
-        public OperationResult Delete(IContent content, int userId = Constants.Security.SuperUserId)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            EventMessages eventMessages = EventMessagesFactory.Get();
+            if (scope.Notifications.PublishCancelable(new ContentDeletingNotification(content, eventMessages)))
+            {
+                scope.Complete();
+                return OperationResult.Cancel(eventMessages);
+            }
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            scope.WriteLock(Constants.Locks.ContentTree);
+
+            // if it's not trashed yet, and published, we should unpublish
+            // but... Unpublishing event makes no sense (not going to cancel?) and no need to save
+            // just raise the event
+            if (content.Trashed == false && content.Published)
             {
-                if (scope.Notifications.PublishCancelable(new ContentDeletingNotification(content, eventMessages)))
-                {
-                    scope.Complete();
-                    return OperationResult.Cancel(eventMessages);
-                }
+                scope.Notifications.Publish(new ContentUnpublishedNotification(content, eventMessages));
+            }
 
-                scope.WriteLock(Constants.Locks.ContentTree);
+            DeleteLocked(scope, content, eventMessages);
 
-                // if it's not trashed yet, and published, we should unpublish
-                // but... Unpublishing event makes no sense (not going to cancel?) and no need to save
-                // just raise the event
-                if (content.Trashed == false && content.Published)
-                {
-                    scope.Notifications.Publish(new ContentUnpublishedNotification(content, eventMessages));
-                }
+            scope.Notifications.Publish(
+                new ContentTreeChangeNotification(content, TreeChangeTypes.Remove, eventMessages));
+            Audit(AuditType.Delete, userId, content.Id);
 
-                DeleteLocked(scope, content, eventMessages);
+            scope.Complete();
+        }
 
-                scope.Notifications.Publish(
-                    new ContentTreeChangeNotification(content, TreeChangeTypes.Remove, eventMessages));
-                Audit(AuditType.Delete, userId, content.Id);
+        return OperationResult.Succeed(eventMessages);
+    }
 
-                scope.Complete();
-            }
+    private void DeleteLocked(ICoreScope scope, IContent content, EventMessages evtMsgs)
+    {
+        void DoDelete(IContent c)
+        {
+            _documentRepository.Delete(c);
+            scope.Notifications.Publish(new ContentDeletedNotification(c, evtMsgs));
 
-            return OperationResult.Succeed(eventMessages);
+            // media files deleted by QueuingEventDispatcher
         }
 
-        private void DeleteLocked(ICoreScope scope, IContent content, EventMessages evtMsgs)
+        const int pageSize = 500;
+        var total = long.MaxValue;
+        while (total > 0)
         {
-            void DoDelete(IContent c)
+            //get descendants - ordered from deepest to shallowest
+            IEnumerable descendants = GetPagedDescendants(content.Id, 0, pageSize, out total,
+                ordering: Ordering.By("Path", Direction.Descending));
+            foreach (IContent c in descendants)
             {
-                _documentRepository.Delete(c);
-                scope.Notifications.Publish(new ContentDeletedNotification(c, evtMsgs));
-
-                // media files deleted by QueuingEventDispatcher
+                DoDelete(c);
             }
+        }
 
-            const int pageSize = 500;
-            var total = long.MaxValue;
-            while (total > 0)
-            {
-                //get descendants - ordered from deepest to shallowest
-                IEnumerable descendants = GetPagedDescendants(content.Id, 0, pageSize, out total,
-                    ordering: Ordering.By("Path", Direction.Descending));
-                foreach (IContent c in descendants)
-                {
-                    DoDelete(c);
-                }
-            }
+        DoDelete(content);
+    }
 
-            DoDelete(content);
-        }
+    //TODO: both DeleteVersions methods below have an issue. Sort of. They do NOT take care of files the way
+    // Delete does - for a good reason: the file may be referenced by other, non-deleted, versions. BUT,
+    // if that's not the case, then the file will never be deleted, because when we delete the content,
+    // the version referencing the file will not be there anymore. SO, we can leak files.
 
-        //TODO: both DeleteVersions methods below have an issue. Sort of. They do NOT take care of files the way
-        // Delete does - for a good reason: the file may be referenced by other, non-deleted, versions. BUT,
-        // if that's not the case, then the file will never be deleted, because when we delete the content,
-        // the version referencing the file will not be there anymore. SO, we can leak files.
+    /// 
+    ///     Permanently deletes versions from an  object prior to a specific date.
+    ///     This method will never delete the latest version of a content item.
+    /// 
+    /// Id of the  object to delete versions from
+    /// Latest version date
+    /// Optional Id of the User deleting versions of a Content object
+    public void DeleteVersions(int id, DateTime versionDate, int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
 
-        /// 
-        ///     Permanently deletes versions from an  object prior to a specific date.
-        ///     This method will never delete the latest version of a content item.
-        /// 
-        /// Id of the  object to delete versions from
-        /// Latest version date
-        /// Optional Id of the User deleting versions of a Content object
-        public void DeleteVersions(int id, DateTime versionDate, int userId = Constants.Security.SuperUserId)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            EventMessages evtMsgs = EventMessagesFactory.Get();
-
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            var deletingVersionsNotification =
+                new ContentDeletingVersionsNotification(id, evtMsgs, dateToRetain: versionDate);
+            if (scope.Notifications.PublishCancelable(deletingVersionsNotification))
             {
-                var deletingVersionsNotification =
-                    new ContentDeletingVersionsNotification(id, evtMsgs, dateToRetain: versionDate);
-                if (scope.Notifications.PublishCancelable(deletingVersionsNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
+                scope.Complete();
+                return;
+            }
 
-                scope.WriteLock(Constants.Locks.ContentTree);
-                _documentRepository.DeleteVersions(id, versionDate);
+            scope.WriteLock(Constants.Locks.ContentTree);
+            _documentRepository.DeleteVersions(id, versionDate);
 
-                scope.Notifications.Publish(
-                    new ContentDeletedVersionsNotification(id, evtMsgs, dateToRetain: versionDate).WithStateFrom(
-                        deletingVersionsNotification));
-                Audit(AuditType.Delete, userId, Constants.System.Root, "Delete (by version date)");
+            scope.Notifications.Publish(
+                new ContentDeletedVersionsNotification(id, evtMsgs, dateToRetain: versionDate).WithStateFrom(
+                    deletingVersionsNotification));
+            Audit(AuditType.Delete, userId, Constants.System.Root, "Delete (by version date)");
 
-                scope.Complete();
-            }
+            scope.Complete();
         }
+    }
 
-        /// 
-        ///     Permanently deletes specific version(s) from an  object.
-        ///     This method will never delete the latest version of a content item.
-        /// 
-        /// Id of the  object to delete a version from
-        /// Id of the version to delete
-        /// Boolean indicating whether to delete versions prior to the versionId
-        /// Optional Id of the User deleting versions of a Content object
-        public void DeleteVersion(int id, int versionId, bool deletePriorVersions,
-            int userId = Constants.Security.SuperUserId)
-        {
-            EventMessages evtMsgs = EventMessagesFactory.Get();
+    /// 
+    ///     Permanently deletes specific version(s) from an  object.
+    ///     This method will never delete the latest version of a content item.
+    /// 
+    /// Id of the  object to delete a version from
+    /// Id of the version to delete
+    /// Boolean indicating whether to delete versions prior to the versionId
+    /// Optional Id of the User deleting versions of a Content object
+    public void DeleteVersion(int id, int versionId, bool deletePriorVersions,
+        int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            var deletingVersionsNotification = new ContentDeletingVersionsNotification(id, evtMsgs, versionId);
+            if (scope.Notifications.PublishCancelable(deletingVersionsNotification))
             {
-                var deletingVersionsNotification = new ContentDeletingVersionsNotification(id, evtMsgs, versionId);
-                if (scope.Notifications.PublishCancelable(deletingVersionsNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
+                scope.Complete();
+                return;
+            }
 
-                if (deletePriorVersions)
-                {
-                    IContent? content = GetVersion(versionId);
-                    DeleteVersions(id, content?.UpdateDate ?? DateTime.Now, userId);
-                }
+            if (deletePriorVersions)
+            {
+                IContent? content = GetVersion(versionId);
+                DeleteVersions(id, content?.UpdateDate ?? DateTime.Now, userId);
+            }
 
-                scope.WriteLock(Constants.Locks.ContentTree);
-                IContent? c = _documentRepository.Get(id);
-                if (c?.VersionId != versionId &&
-                    c?.PublishedVersionId != versionId) // don't delete the current or published version
-                {
-                    _documentRepository.DeleteVersion(versionId);
-                }
+            scope.WriteLock(Constants.Locks.ContentTree);
+            IContent? c = _documentRepository.Get(id);
+            if (c?.VersionId != versionId &&
+                c?.PublishedVersionId != versionId) // don't delete the current or published version
+            {
+                _documentRepository.DeleteVersion(versionId);
+            }
 
-                scope.Notifications.Publish(
-                    new ContentDeletedVersionsNotification(id, evtMsgs, versionId).WithStateFrom(
-                        deletingVersionsNotification));
-                Audit(AuditType.Delete, userId, Constants.System.Root, "Delete (by version)");
+            scope.Notifications.Publish(
+                new ContentDeletedVersionsNotification(id, evtMsgs, versionId).WithStateFrom(
+                    deletingVersionsNotification));
+            Audit(AuditType.Delete, userId, Constants.System.Root, "Delete (by version)");
 
-                scope.Complete();
-            }
+            scope.Complete();
         }
+    }
 
-        #endregion
+    #endregion
 
-        #region Move, RecycleBin
+    #region Move, RecycleBin
 
-        /// 
-        public OperationResult MoveToRecycleBin(IContent content, int userId = Constants.Security.SuperUserId)
+    /// 
+    public OperationResult MoveToRecycleBin(IContent content, int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages eventMessages = EventMessagesFactory.Get();
+        var moves = new List<(IContent, string)>();
+
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            EventMessages eventMessages = EventMessagesFactory.Get();
-            var moves = new List<(IContent, string)>();
+            scope.WriteLock(Constants.Locks.ContentTree);
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(Constants.Locks.ContentTree);
+            var originalPath = content.Path;
+            var moveEventInfo =
+                new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent);
 
-                var originalPath = content.Path;
-                var moveEventInfo =
-                    new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent);
+            var movingToRecycleBinNotification =
+                new ContentMovingToRecycleBinNotification(moveEventInfo, eventMessages);
+            if (scope.Notifications.PublishCancelable(movingToRecycleBinNotification))
+            {
+                scope.Complete();
+                return OperationResult.Cancel(eventMessages); // causes rollback
+            }
 
-                var movingToRecycleBinNotification =
-                    new ContentMovingToRecycleBinNotification(moveEventInfo, eventMessages);
-                if (scope.Notifications.PublishCancelable(movingToRecycleBinNotification))
-                {
-                    scope.Complete();
-                    return OperationResult.Cancel(eventMessages); // causes rollback
-                }
+            // if it's published we may want to force-unpublish it - that would be backward-compatible... but...
+            // making a radical decision here: trashing is equivalent to moving under an unpublished node so
+            // it's NOT unpublishing, only the content is now masked - allowing us to restore it if wanted
+            //if (content.HasPublishedVersion)
+            //{ }
 
-                // if it's published we may want to force-unpublish it - that would be backward-compatible... but...
-                // making a radical decision here: trashing is equivalent to moving under an unpublished node so
-                // it's NOT unpublishing, only the content is now masked - allowing us to restore it if wanted
-                //if (content.HasPublishedVersion)
-                //{ }
+            PerformMoveLocked(content, Constants.System.RecycleBinContent, null, userId, moves, true);
+            scope.Notifications.Publish(
+                new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages));
 
-                PerformMoveLocked(content, Constants.System.RecycleBinContent, null, userId, moves, true);
-                scope.Notifications.Publish(
-                    new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages));
+            MoveEventInfo[] moveInfo = moves
+                .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId))
+                .ToArray();
 
-                MoveEventInfo[] moveInfo = moves
-                    .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId))
-                    .ToArray();
+            scope.Notifications.Publish(
+                new ContentMovedToRecycleBinNotification(moveInfo, eventMessages).WithStateFrom(
+                    movingToRecycleBinNotification));
+            Audit(AuditType.Move, userId, content.Id, "Moved to recycle bin");
 
-                scope.Notifications.Publish(
-                    new ContentMovedToRecycleBinNotification(moveInfo, eventMessages).WithStateFrom(
-                        movingToRecycleBinNotification));
-                Audit(AuditType.Move, userId, content.Id, "Moved to recycle bin");
+            scope.Complete();
+        }
 
-                scope.Complete();
-            }
+        return OperationResult.Succeed(eventMessages);
+    }
 
-            return OperationResult.Succeed(eventMessages);
+    /// 
+    ///     Moves an  object to a new location by changing its parent id.
+    /// 
+    /// 
+    ///     If the  object is already published it will be
+    ///     published after being moved to its new location. Otherwise it'll just
+    ///     be saved with a new parent id.
+    /// 
+    /// The  to move
+    /// Id of the Content's new Parent
+    /// Optional Id of the User moving the Content
+    public void Move(IContent content, int parentId, int userId = Constants.Security.SuperUserId)
+    {
+        // if moving to the recycle bin then use the proper method
+        if (parentId == Constants.System.RecycleBinContent)
+        {
+            MoveToRecycleBin(content, userId);
+            return;
         }
 
-        /// 
-        ///     Moves an  object to a new location by changing its parent id.
-        /// 
-        /// 
-        ///     If the  object is already published it will be
-        ///     published after being moved to its new location. Otherwise it'll just
-        ///     be saved with a new parent id.
-        /// 
-        /// The  to move
-        /// Id of the Content's new Parent
-        /// Optional Id of the User moving the Content
-        public void Move(IContent content, int parentId, int userId = Constants.Security.SuperUserId)
-        {
-            // if moving to the recycle bin then use the proper method
-            if (parentId == Constants.System.RecycleBinContent)
-            {
-                MoveToRecycleBin(content, userId);
-                return;
-            }
+        EventMessages eventMessages = EventMessagesFactory.Get();
 
-            EventMessages eventMessages = EventMessagesFactory.Get();
+        var moves = new List<(IContent, string)>();
 
-            var moves = new List<(IContent, string)>();
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            scope.WriteLock(Constants.Locks.ContentTree);
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            IContent? parent = parentId == Constants.System.Root ? null : GetById(parentId);
+            if (parentId != Constants.System.Root && (parent == null || parent.Trashed))
             {
-                scope.WriteLock(Constants.Locks.ContentTree);
-
-                IContent? parent = parentId == Constants.System.Root ? null : GetById(parentId);
-                if (parentId != Constants.System.Root && (parent == null || parent.Trashed))
-                {
-                    throw new InvalidOperationException("Parent does not exist or is trashed."); // causes rollback
-                }
-
-                var moveEventInfo = new MoveEventInfo(content, content.Path, parentId);
+                throw new InvalidOperationException("Parent does not exist or is trashed."); // causes rollback
+            }
 
-                var movingNotification = new ContentMovingNotification(moveEventInfo, eventMessages);
-                if (scope.Notifications.PublishCancelable(movingNotification))
-                {
-                    scope.Complete();
-                    return; // causes rollback
-                }
+            var moveEventInfo = new MoveEventInfo(content, content.Path, parentId);
 
-                // if content was trashed, and since we're not moving to the recycle bin,
-                // indicate that the trashed status should be changed to false, else just
-                // leave it unchanged
-                var trashed = content.Trashed ? false : (bool?)null;
+            var movingNotification = new ContentMovingNotification(moveEventInfo, eventMessages);
+            if (scope.Notifications.PublishCancelable(movingNotification))
+            {
+                scope.Complete();
+                return; // causes rollback
+            }
 
-                // if the content was trashed under another content, and so has a published version,
-                // it cannot move back as published but has to be unpublished first - that's for the
-                // root content, everything underneath will retain its published status
-                if (content.Trashed && content.Published)
-                {
-                    // however, it had been masked when being trashed, so there's no need for
-                    // any special event here - just change its state
-                    content.PublishedState = PublishedState.Unpublishing;
-                }
+            // if content was trashed, and since we're not moving to the recycle bin,
+            // indicate that the trashed status should be changed to false, else just
+            // leave it unchanged
+            var trashed = content.Trashed ? false : (bool?)null;
 
-                PerformMoveLocked(content, parentId, parent, userId, moves, trashed);
+            // if the content was trashed under another content, and so has a published version,
+            // it cannot move back as published but has to be unpublished first - that's for the
+            // root content, everything underneath will retain its published status
+            if (content.Trashed && content.Published)
+            {
+                // however, it had been masked when being trashed, so there's no need for
+                // any special event here - just change its state
+                content.PublishedState = PublishedState.Unpublishing;
+            }
 
-                scope.Notifications.Publish(
-                    new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages));
+            PerformMoveLocked(content, parentId, parent, userId, moves, trashed);
 
-                MoveEventInfo[] moveInfo = moves //changes
-                    .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId))
-                    .ToArray();
+            scope.Notifications.Publish(
+                new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages));
 
-                scope.Notifications.Publish(
-                    new ContentMovedNotification(moveInfo, eventMessages).WithStateFrom(movingNotification));
+            MoveEventInfo[] moveInfo = moves //changes
+                .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId))
+                .ToArray();
 
-                Audit(AuditType.Move, userId, content.Id);
+            scope.Notifications.Publish(
+                new ContentMovedNotification(moveInfo, eventMessages).WithStateFrom(movingNotification));
 
-                scope.Complete();
-            }
-        }
+            Audit(AuditType.Move, userId, content.Id);
 
-        // MUST be called from within WriteLock
-        // trash indicates whether we are trashing, un-trashing, or not changing anything
-        private void PerformMoveLocked(IContent content, int parentId, IContent? parent, int userId,
-            ICollection<(IContent, string)> moves,
-            bool? trash)
-        {
-            content.WriterId = userId;
-            content.ParentId = parentId;
+            scope.Complete();
+        }
+    }
 
-            // get the level delta (old pos to new pos)
-            // note that recycle bin (id:-20) level is 0!
-            var levelDelta = 1 - content.Level + (parent?.Level ?? 0);
+    // MUST be called from within WriteLock
+    // trash indicates whether we are trashing, un-trashing, or not changing anything
+    private void PerformMoveLocked(IContent content, int parentId, IContent? parent, int userId,
+        ICollection<(IContent, string)> moves,
+        bool? trash)
+    {
+        content.WriterId = userId;
+        content.ParentId = parentId;
 
-            var paths = new Dictionary();
+        // get the level delta (old pos to new pos)
+        // note that recycle bin (id:-20) level is 0!
+        var levelDelta = 1 - content.Level + (parent?.Level ?? 0);
 
-            moves.Add((content, content.Path)); // capture original path
+        var paths = new Dictionary();
 
-            //need to store the original path to lookup descendants based on it below
-            var originalPath = content.Path;
+        moves.Add((content, content.Path)); // capture original path
 
-            // these will be updated by the repo because we changed parentId
-            //content.Path = (parent == null ? "-1" : parent.Path) + "," + content.Id;
-            //content.SortOrder = ((ContentRepository) repository).NextChildSortOrder(parentId);
-            //content.Level += levelDelta;
-            PerformMoveContentLocked(content, userId, trash);
-
-            // if uow is not immediate, content.Path will be updated only when the UOW commits,
-            // and because we want it now, we have to calculate it by ourselves
-            //paths[content.Id] = content.Path;
-            paths[content.Id] =
-                (parent == null
-                    ? parentId == Constants.System.RecycleBinContent ? "-1,-20" : Constants.System.RootString
-                    : parent.Path) + "," + content.Id;
-
-            const int pageSize = 500;
-            IQuery? query = GetPagedDescendantQuery(originalPath);
-            long total;
-            do
-            {
-                // We always page a page 0 because for each page, we are moving the result so the resulting total will be reduced
-                IEnumerable descendants =
-                    GetPagedLocked(query, 0, pageSize, out total, null, Ordering.By("Path"));
+        //need to store the original path to lookup descendants based on it below
+        var originalPath = content.Path;
 
-                foreach (IContent descendant in descendants)
-                {
-                    moves.Add((descendant, descendant.Path)); // capture original path
+        // these will be updated by the repo because we changed parentId
+        //content.Path = (parent == null ? "-1" : parent.Path) + "," + content.Id;
+        //content.SortOrder = ((ContentRepository) repository).NextChildSortOrder(parentId);
+        //content.Level += levelDelta;
+        PerformMoveContentLocked(content, userId, trash);
 
-                    // update path and level since we do not update parentId
-                    descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id;
-                    descendant.Level += levelDelta;
-                    PerformMoveContentLocked(descendant, userId, trash);
-                }
-            } while (total > pageSize);
-        }
+        // if uow is not immediate, content.Path will be updated only when the UOW commits,
+        // and because we want it now, we have to calculate it by ourselves
+        //paths[content.Id] = content.Path;
+        paths[content.Id] =
+            (parent == null
+                ? parentId == Constants.System.RecycleBinContent ? "-1,-20" : Constants.System.RootString
+                : parent.Path) + "," + content.Id;
 
-        private void PerformMoveContentLocked(IContent content, int userId, bool? trash)
+        const int pageSize = 500;
+        IQuery? query = GetPagedDescendantQuery(originalPath);
+        long total;
+        do
         {
-            if (trash.HasValue)
+            // We always page a page 0 because for each page, we are moving the result so the resulting total will be reduced
+            IEnumerable descendants =
+                GetPagedLocked(query, 0, pageSize, out total, null, Ordering.By("Path"));
+
+            foreach (IContent descendant in descendants)
             {
-                ((ContentBase)content).Trashed = trash.Value;
+                moves.Add((descendant, descendant.Path)); // capture original path
+
+                // update path and level since we do not update parentId
+                descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id;
+                descendant.Level += levelDelta;
+                PerformMoveContentLocked(descendant, userId, trash);
             }
+        } while (total > pageSize);
+    }
 
-            content.WriterId = userId;
-            _documentRepository.Save(content);
+    private void PerformMoveContentLocked(IContent content, int userId, bool? trash)
+    {
+        if (trash.HasValue)
+        {
+            ((ContentBase)content).Trashed = trash.Value;
         }
 
-        /// 
-        ///     Empties the Recycle Bin by deleting all  that resides in the bin
-        /// 
-        public OperationResult EmptyRecycleBin(int userId = Constants.Security.SuperUserId)
-        {
-            var deleted = new List();
-            EventMessages eventMessages = EventMessagesFactory.Get();
+        content.WriterId = userId;
+        _documentRepository.Save(content);
+    }
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(Constants.Locks.ContentTree);
+    /// 
+    ///     Empties the Recycle Bin by deleting all  that resides in the bin
+    /// 
+    public OperationResult EmptyRecycleBin(int userId = Constants.Security.SuperUserId)
+    {
+        var deleted = new List();
+        EventMessages eventMessages = EventMessagesFactory.Get();
 
-                // emptying the recycle bin means deleting whatever is in there - do it properly!
-                IQuery? query = Query().Where(x => x.ParentId == Constants.System.RecycleBinContent);
-                IContent[] contents = _documentRepository.Get(query).ToArray();
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            scope.WriteLock(Constants.Locks.ContentTree);
 
-                var emptyingRecycleBinNotification = new ContentEmptyingRecycleBinNotification(contents, eventMessages);
-                if (scope.Notifications.PublishCancelable(emptyingRecycleBinNotification))
-                {
-                    scope.Complete();
-                    return OperationResult.Cancel(eventMessages);
-                }
+            // emptying the recycle bin means deleting whatever is in there - do it properly!
+            IQuery? query = Query().Where(x => x.ParentId == Constants.System.RecycleBinContent);
+            IContent[] contents = _documentRepository.Get(query).ToArray();
+
+            var emptyingRecycleBinNotification = new ContentEmptyingRecycleBinNotification(contents, eventMessages);
+            if (scope.Notifications.PublishCancelable(emptyingRecycleBinNotification))
+            {
+                scope.Complete();
+                return OperationResult.Cancel(eventMessages);
+            }
 
-                if (contents is not null)
+            if (contents is not null)
+            {
+                foreach (IContent content in contents)
                 {
-                    foreach (IContent content in contents)
-                    {
-                        DeleteLocked(scope, content, eventMessages);
-                        deleted.Add(content);
-                    }
+                    DeleteLocked(scope, content, eventMessages);
+                    deleted.Add(content);
                 }
-
-                scope.Notifications.Publish(
-                    new ContentEmptiedRecycleBinNotification(deleted, eventMessages).WithStateFrom(
-                        emptyingRecycleBinNotification));
-                scope.Notifications.Publish(
-                    new ContentTreeChangeNotification(deleted, TreeChangeTypes.Remove, eventMessages));
-                Audit(AuditType.Delete, userId, Constants.System.RecycleBinContent, "Recycle bin emptied");
-
-                scope.Complete();
             }
 
-            return OperationResult.Succeed(eventMessages);
+            scope.Notifications.Publish(
+                new ContentEmptiedRecycleBinNotification(deleted, eventMessages).WithStateFrom(
+                    emptyingRecycleBinNotification));
+            scope.Notifications.Publish(
+                new ContentTreeChangeNotification(deleted, TreeChangeTypes.Remove, eventMessages));
+            Audit(AuditType.Delete, userId, Constants.System.RecycleBinContent, "Recycle bin emptied");
+
+            scope.Complete();
         }
 
-        public bool RecycleBinSmells()
+        return OperationResult.Succeed(eventMessages);
+    }
+
+    public bool RecycleBinSmells()
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                return _documentRepository.RecycleBinSmells();
-            }
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _documentRepository.RecycleBinSmells();
         }
+    }
 
-        #endregion
+    #endregion
 
-        #region Others
+    #region Others
 
-        /// 
-        ///     Copies an  object by creating a new Content object of the same type and copies all data from
-        ///     the current
-        ///     to the new copy which is returned. Recursively copies all children.
-        /// 
-        /// The  to copy
-        /// Id of the Content's new Parent
-        /// Boolean indicating whether the copy should be related to the original
-        /// Optional Id of the User copying the Content
-        /// The newly created  object
-        public IContent? Copy(IContent content, int parentId, bool relateToOriginal,
-            int userId = Constants.Security.SuperUserId) => Copy(content, parentId, relateToOriginal, true, userId);
+    /// 
+    ///     Copies an  object by creating a new Content object of the same type and copies all data from
+    ///     the current
+    ///     to the new copy which is returned. Recursively copies all children.
+    /// 
+    /// The  to copy
+    /// Id of the Content's new Parent
+    /// Boolean indicating whether the copy should be related to the original
+    /// Optional Id of the User copying the Content
+    /// The newly created  object
+    public IContent? Copy(IContent content, int parentId, bool relateToOriginal,
+        int userId = Constants.Security.SuperUserId) => Copy(content, parentId, relateToOriginal, true, userId);
 
-        /// 
-        ///     Copies an  object by creating a new Content object of the same type and copies all data from
-        ///     the current
-        ///     to the new copy which is returned.
-        /// 
-        /// The  to copy
-        /// Id of the Content's new Parent
-        /// Boolean indicating whether the copy should be related to the original
-        /// A value indicating whether to recursively copy children.
-        /// Optional Id of the User copying the Content
-        /// The newly created  object
-        public IContent? Copy(IContent content, int parentId, bool relateToOriginal, bool recursive,
-            int userId = Constants.Security.SuperUserId)
-        {
-            EventMessages eventMessages = EventMessagesFactory.Get();
+    /// 
+    ///     Copies an  object by creating a new Content object of the same type and copies all data from
+    ///     the current
+    ///     to the new copy which is returned.
+    /// 
+    /// The  to copy
+    /// Id of the Content's new Parent
+    /// Boolean indicating whether the copy should be related to the original
+    /// A value indicating whether to recursively copy children.
+    /// Optional Id of the User copying the Content
+    /// The newly created  object
+    public IContent? Copy(IContent content, int parentId, bool relateToOriginal, bool recursive,
+        int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages eventMessages = EventMessagesFactory.Get();
 
-            IContent copy = content.DeepCloneWithResetIdentities();
-            copy.ParentId = parentId;
+        IContent copy = content.DeepCloneWithResetIdentities();
+        copy.ParentId = parentId;
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            if (scope.Notifications.PublishCancelable(
+                    new ContentCopyingNotification(content, copy, parentId, eventMessages)))
             {
-                if (scope.Notifications.PublishCancelable(
-                        new ContentCopyingNotification(content, copy, parentId, eventMessages)))
-                {
-                    scope.Complete();
-                    return null;
-                }
+                scope.Complete();
+                return null;
+            }
 
-                // note - relateToOriginal is not managed here,
-                // it's just part of the Copied event args so the RelateOnCopyHandler knows what to do
-                // meaning that the event has to trigger for every copied content including descendants
+            // note - relateToOriginal is not managed here,
+            // it's just part of the Copied event args so the RelateOnCopyHandler knows what to do
+            // meaning that the event has to trigger for every copied content including descendants
 
-                var copies = new List>();
+            var copies = new List>();
 
-                scope.WriteLock(Constants.Locks.ContentTree);
+            scope.WriteLock(Constants.Locks.ContentTree);
 
-                // a copy is not published (but not really unpublishing either)
-                // update the create author and last edit author
-                if (copy.Published)
-                {
-                    copy.Published = false;
-                }
+            // a copy is not published (but not really unpublishing either)
+            // update the create author and last edit author
+            if (copy.Published)
+            {
+                copy.Published = false;
+            }
 
-                copy.CreatorId = userId;
-                copy.WriterId = userId;
+            copy.CreatorId = userId;
+            copy.WriterId = userId;
 
-                //get the current permissions, if there are any explicit ones they need to be copied
-                EntityPermissionCollection currentPermissions = GetPermissions(content);
-                currentPermissions.RemoveWhere(p => p.IsDefaultPermissions);
+            //get the current permissions, if there are any explicit ones they need to be copied
+            EntityPermissionCollection currentPermissions = GetPermissions(content);
+            currentPermissions.RemoveWhere(p => p.IsDefaultPermissions);
 
-                // save and flush because we need the ID for the recursive Copying events
-                _documentRepository.Save(copy);
+            // save and flush because we need the ID for the recursive Copying events
+            _documentRepository.Save(copy);
 
-                //add permissions
-                if (currentPermissions.Count > 0)
-                {
-                    var permissionSet = new ContentPermissionSet(copy, currentPermissions);
-                    _documentRepository.AddOrUpdatePermissions(permissionSet);
-                }
+            //add permissions
+            if (currentPermissions.Count > 0)
+            {
+                var permissionSet = new ContentPermissionSet(copy, currentPermissions);
+                _documentRepository.AddOrUpdatePermissions(permissionSet);
+            }
 
-                // keep track of copies
-                copies.Add(Tuple.Create(content, copy));
-                var idmap = new Dictionary {[content.Id] = copy.Id};
+            // keep track of copies
+            copies.Add(Tuple.Create(content, copy));
+            var idmap = new Dictionary {[content.Id] = copy.Id};
 
-                if (recursive) // process descendants
+            if (recursive) // process descendants
+            {
+                const int pageSize = 500;
+                var page = 0;
+                var total = long.MaxValue;
+                while (page * pageSize < total)
                 {
-                    const int pageSize = 500;
-                    var page = 0;
-                    var total = long.MaxValue;
-                    while (page * pageSize < total)
+                    IEnumerable descendants =
+                        GetPagedDescendants(content.Id, page++, pageSize, out total);
+                    foreach (IContent descendant in descendants)
                     {
-                        IEnumerable descendants =
-                            GetPagedDescendants(content.Id, page++, pageSize, out total);
-                        foreach (IContent descendant in descendants)
+                        // if parent has not been copied, skip, else gets its copy id
+                        if (idmap.TryGetValue(descendant.ParentId, out parentId) == false)
                         {
-                            // if parent has not been copied, skip, else gets its copy id
-                            if (idmap.TryGetValue(descendant.ParentId, out parentId) == false)
-                            {
-                                continue;
-                            }
-
-                            IContent descendantCopy = descendant.DeepCloneWithResetIdentities();
-                            descendantCopy.ParentId = parentId;
-
-                            if (scope.Notifications.PublishCancelable(
-                                    new ContentCopyingNotification(descendant, descendantCopy, parentId,
-                                        eventMessages)))
-                            {
-                                continue;
-                            }
-
-                            // a copy is not published (but not really unpublishing either)
-                            // update the create author and last edit author
-                            if (descendantCopy.Published)
-                            {
-                                descendantCopy.Published = false;
-                            }
-
-                            descendantCopy.CreatorId = userId;
-                            descendantCopy.WriterId = userId;
-
-                            // save and flush (see above)
-                            _documentRepository.Save(descendantCopy);
-
-                            copies.Add(Tuple.Create(descendant, descendantCopy));
-                            idmap[descendant.Id] = descendantCopy.Id;
+                            continue;
                         }
-                    }
-                }
 
-                // not handling tags here, because
-                // - tags should be handled by the content repository
-                // - a copy is unpublished and therefore has no impact on tags in DB
+                        IContent descendantCopy = descendant.DeepCloneWithResetIdentities();
+                        descendantCopy.ParentId = parentId;
 
-                scope.Notifications.Publish(
-                    new ContentTreeChangeNotification(copy, TreeChangeTypes.RefreshBranch, eventMessages));
-                foreach (Tuple x in copies)
-                {
-                    scope.Notifications.Publish(new ContentCopiedNotification(x.Item1, x.Item2, parentId,
-                        relateToOriginal, eventMessages));
+                        if (scope.Notifications.PublishCancelable(
+                                new ContentCopyingNotification(descendant, descendantCopy, parentId,
+                                    eventMessages)))
+                        {
+                            continue;
+                        }
+
+                        // a copy is not published (but not really unpublishing either)
+                        // update the create author and last edit author
+                        if (descendantCopy.Published)
+                        {
+                            descendantCopy.Published = false;
+                        }
+
+                        descendantCopy.CreatorId = userId;
+                        descendantCopy.WriterId = userId;
+
+                        // save and flush (see above)
+                        _documentRepository.Save(descendantCopy);
+
+                        copies.Add(Tuple.Create(descendant, descendantCopy));
+                        idmap[descendant.Id] = descendantCopy.Id;
+                    }
                 }
+            }
 
-                Audit(AuditType.Copy, userId, content.Id);
+            // not handling tags here, because
+            // - tags should be handled by the content repository
+            // - a copy is unpublished and therefore has no impact on tags in DB
 
-                scope.Complete();
+            scope.Notifications.Publish(
+                new ContentTreeChangeNotification(copy, TreeChangeTypes.RefreshBranch, eventMessages));
+            foreach (Tuple x in copies)
+            {
+                scope.Notifications.Publish(new ContentCopiedNotification(x.Item1, x.Item2, parentId,
+                    relateToOriginal, eventMessages));
             }
 
-            return copy;
+            Audit(AuditType.Copy, userId, content.Id);
+
+            scope.Complete();
+        }
+
+        return copy;
+    }
+
+    /// 
+    ///     Sends an  to Publication, which executes handlers and events for the 'Send to Publication'
+    ///     action.
+    /// 
+    /// The  to send to publication
+    /// Optional Id of the User issuing the send to publication
+    /// True if sending publication was successful otherwise false
+    public bool SendToPublication(IContent? content, int userId = Constants.Security.SuperUserId)
+    {
+        if (content is null)
+        {
+            return false;
         }
 
-        /// 
-        ///     Sends an  to Publication, which executes handlers and events for the 'Send to Publication'
-        ///     action.
-        /// 
-        /// The  to send to publication
-        /// Optional Id of the User issuing the send to publication
-        /// True if sending publication was successful otherwise false
-        public bool SendToPublication(IContent? content, int userId = Constants.Security.SuperUserId)
+        EventMessages evtMsgs = EventMessagesFactory.Get();
+
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            if (content is null)
+            var sendingToPublishNotification = new ContentSendingToPublishNotification(content, evtMsgs);
+            if (scope.Notifications.PublishCancelable(sendingToPublishNotification))
             {
+                scope.Complete();
                 return false;
             }
-            EventMessages evtMsgs = EventMessagesFactory.Get();
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                var sendingToPublishNotification = new ContentSendingToPublishNotification(content, evtMsgs);
-                if (scope.Notifications.PublishCancelable(sendingToPublishNotification))
-                {
-                    scope.Complete();
-                    return false;
-                }
+            //track the cultures changing for auditing
+            var culturesChanging = content.ContentType.VariesByCulture()
+                ? string.Join(",", content.CultureInfos!.Values.Where(x => x.IsDirty()).Select(x => x.Culture))
+                : null;
 
-                //track the cultures changing for auditing
-                var culturesChanging = content.ContentType.VariesByCulture()
-                    ? string.Join(",", content.CultureInfos!.Values.Where(x => x.IsDirty()).Select(x => x.Culture))
-                    : null;
+            // TODO: Currently there's no way to change track which variant properties have changed, we only have change
+            // tracking enabled on all values on the Property which doesn't allow us to know which variants have changed.
+            // in this particular case, determining which cultures have changed works with the above with names since it will
+            // have always changed if it's been saved in the back office but that's not really fail safe.
 
-                // TODO: Currently there's no way to change track which variant properties have changed, we only have change
-                // tracking enabled on all values on the Property which doesn't allow us to know which variants have changed.
-                // in this particular case, determining which cultures have changed works with the above with names since it will
-                // have always changed if it's been saved in the back office but that's not really fail safe.
+            //Save before raising event
+            OperationResult saveResult = Save(content, userId);
 
-                //Save before raising event
-                OperationResult saveResult = Save(content, userId);
+            // always complete (but maybe return a failed status)
+            scope.Complete();
 
-                // always complete (but maybe return a failed status)
-                scope.Complete();
+            if (!saveResult.Success)
+            {
+                return saveResult.Success;
+            }
 
-                if (!saveResult.Success)
-                {
-                    return saveResult.Success;
-                }
+            scope.Notifications.Publish(
+                new ContentSentToPublishNotification(content, evtMsgs).WithStateFrom(sendingToPublishNotification));
 
-                scope.Notifications.Publish(
-                    new ContentSentToPublishNotification(content, evtMsgs).WithStateFrom(sendingToPublishNotification));
+            if (culturesChanging != null)
+            {
+                Audit(AuditType.SendToPublishVariant, userId, content.Id,
+                    $"Send To Publish for cultures: {culturesChanging}", culturesChanging);
+            }
+            else
+            {
+                Audit(AuditType.SendToPublish, content.WriterId, content.Id);
+            }
 
-                if (culturesChanging != null)
-                {
-                    Audit(AuditType.SendToPublishVariant, userId, content.Id,
-                        $"Send To Publish for cultures: {culturesChanging}", culturesChanging);
-                }
-                else
-                {
-                    Audit(AuditType.SendToPublish, content.WriterId, content.Id);
-                }
+            return saveResult.Success;
+        }
+    }
 
-                return saveResult.Success;
-            }
+    /// 
+    ///     Sorts a collection of  objects by updating the SortOrder according
+    ///     to the ordering of items in the passed in .
+    /// 
+    /// 
+    ///     Using this method will ensure that the Published-state is maintained upon sorting
+    ///     so the cache is updated accordingly - as needed.
+    /// 
+    /// 
+    /// 
+    /// Result indicating what action was taken when handling the command.
+    public OperationResult Sort(IEnumerable items, int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
+
+        IContent[] itemsA = items.ToArray();
+        if (itemsA.Length == 0)
+        {
+            return new OperationResult(OperationResultType.NoOperation, evtMsgs);
         }
 
-        /// 
-        ///     Sorts a collection of  objects by updating the SortOrder according
-        ///     to the ordering of items in the passed in .
-        /// 
-        /// 
-        ///     Using this method will ensure that the Published-state is maintained upon sorting
-        ///     so the cache is updated accordingly - as needed.
-        /// 
-        /// 
-        /// 
-        /// Result indicating what action was taken when handling the command.
-        public OperationResult Sort(IEnumerable items, int userId = Constants.Security.SuperUserId)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            EventMessages evtMsgs = EventMessagesFactory.Get();
+            scope.WriteLock(Constants.Locks.ContentTree);
 
-            IContent[] itemsA = items.ToArray();
-            if (itemsA.Length == 0)
-            {
-                return new OperationResult(OperationResultType.NoOperation, evtMsgs);
-            }
+            OperationResult ret = Sort(scope, itemsA, userId, evtMsgs);
+            scope.Complete();
+            return ret;
+        }
+    }
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(Constants.Locks.ContentTree);
+    /// 
+    ///     Sorts a collection of  objects by updating the SortOrder according
+    ///     to the ordering of items identified by the .
+    /// 
+    /// 
+    ///     Using this method will ensure that the Published-state is maintained upon sorting
+    ///     so the cache is updated accordingly - as needed.
+    /// 
+    /// 
+    /// 
+    /// Result indicating what action was taken when handling the command.
+    public OperationResult Sort(IEnumerable? ids, int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
 
-                OperationResult ret = Sort(scope, itemsA, userId, evtMsgs);
-                scope.Complete();
-                return ret;
-            }
+        var idsA = ids?.ToArray();
+        if (idsA is null || idsA.Length == 0)
+        {
+            return new OperationResult(OperationResultType.NoOperation, evtMsgs);
         }
 
-        /// 
-        ///     Sorts a collection of  objects by updating the SortOrder according
-        ///     to the ordering of items identified by the .
-        /// 
-        /// 
-        ///     Using this method will ensure that the Published-state is maintained upon sorting
-        ///     so the cache is updated accordingly - as needed.
-        /// 
-        /// 
-        /// 
-        /// Result indicating what action was taken when handling the command.
-        public OperationResult Sort(IEnumerable? ids, int userId = Constants.Security.SuperUserId)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            EventMessages evtMsgs = EventMessagesFactory.Get();
+            scope.WriteLock(Constants.Locks.ContentTree);
+            IContent[] itemsA = GetByIds(idsA).ToArray();
 
-            var idsA = ids?.ToArray();
-            if (idsA is null || idsA.Length == 0)
-            {
-                return new OperationResult(OperationResultType.NoOperation, evtMsgs);
-            }
+            OperationResult ret = Sort(scope, itemsA, userId, evtMsgs);
+            scope.Complete();
+            return ret;
+        }
+    }
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(Constants.Locks.ContentTree);
-                IContent[] itemsA = GetByIds(idsA).ToArray();
+    private OperationResult Sort(ICoreScope scope, IContent[] itemsA, int userId, EventMessages eventMessages)
+    {
+        var sortingNotification = new ContentSortingNotification(itemsA, eventMessages);
+        var savingNotification = new ContentSavingNotification(itemsA, eventMessages);
 
-                OperationResult ret = Sort(scope, itemsA, userId, evtMsgs);
-                scope.Complete();
-                return ret;
-            }
+        // raise cancelable sorting event
+        if (scope.Notifications.PublishCancelable(sortingNotification))
+        {
+            return OperationResult.Cancel(eventMessages);
         }
 
-        private OperationResult Sort(ICoreScope scope, IContent[] itemsA, int userId, EventMessages eventMessages)
+        // raise cancelable saving event
+        if (scope.Notifications.PublishCancelable(savingNotification))
         {
-            var sortingNotification = new ContentSortingNotification(itemsA, eventMessages);
-            var savingNotification = new ContentSavingNotification(itemsA, eventMessages);
+            return OperationResult.Cancel(eventMessages);
+        }
 
-            // raise cancelable sorting event
-            if (scope.Notifications.PublishCancelable(sortingNotification))
+        var published = new List();
+        var saved = new List();
+        var sortOrder = 0;
+
+        foreach (IContent content in itemsA)
+        {
+            // if the current sort order equals that of the content we don't
+            // need to update it, so just increment the sort order and continue.
+            if (content.SortOrder == sortOrder)
             {
-                return OperationResult.Cancel(eventMessages);
+                sortOrder++;
+                continue;
             }
 
-            // raise cancelable saving event
-            if (scope.Notifications.PublishCancelable(savingNotification))
+            // else update
+            content.SortOrder = sortOrder++;
+            content.WriterId = userId;
+
+            // if it's published, register it, no point running StrategyPublish
+            // since we're not really publishing it and it cannot be cancelled etc
+            if (content.Published)
             {
-                return OperationResult.Cancel(eventMessages);
+                published.Add(content);
             }
 
-            var published = new List();
-            var saved = new List();
-            var sortOrder = 0;
+            // save
+            saved.Add(content);
+            _documentRepository.Save(content);
+        }
 
-            foreach (IContent content in itemsA)
-            {
-                // if the current sort order equals that of the content we don't
-                // need to update it, so just increment the sort order and continue.
-                if (content.SortOrder == sortOrder)
-                {
-                    sortOrder++;
-                    continue;
-                }
+        //first saved, then sorted
+        scope.Notifications.Publish(
+            new ContentSavedNotification(itemsA, eventMessages).WithStateFrom(savingNotification));
+        scope.Notifications.Publish(
+            new ContentSortedNotification(itemsA, eventMessages).WithStateFrom(sortingNotification));
 
-                // else update
-                content.SortOrder = sortOrder++;
-                content.WriterId = userId;
+        scope.Notifications.Publish(
+            new ContentTreeChangeNotification(saved, TreeChangeTypes.RefreshNode, eventMessages));
 
-                // if it's published, register it, no point running StrategyPublish
-                // since we're not really publishing it and it cannot be cancelled etc
-                if (content.Published)
-                {
-                    published.Add(content);
-                }
+        if (published.Any())
+        {
+            scope.Notifications.Publish(new ContentPublishedNotification(published, eventMessages));
+        }
 
-                // save
-                saved.Add(content);
-                _documentRepository.Save(content);
-            }
+        Audit(AuditType.Sort, userId, 0, "Sorting content performed by user");
+        return OperationResult.Succeed(eventMessages);
+    }
 
-            //first saved, then sorted
-            scope.Notifications.Publish(
-                new ContentSavedNotification(itemsA, eventMessages).WithStateFrom(savingNotification));
-            scope.Notifications.Publish(
-                new ContentSortedNotification(itemsA, eventMessages).WithStateFrom(sortingNotification));
+    public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            scope.WriteLock(Constants.Locks.ContentTree);
 
-            scope.Notifications.Publish(
-                new ContentTreeChangeNotification(saved, TreeChangeTypes.RefreshNode, eventMessages));
+            ContentDataIntegrityReport report = _documentRepository.CheckDataIntegrity(options);
 
-            if (published.Any())
+            if (report.FixedIssues.Count > 0)
             {
-                scope.Notifications.Publish(new ContentPublishedNotification(published, eventMessages));
+                //The event args needs a content item so we'll make a fake one with enough properties to not cause a null ref
+                var root = new Content("root", -1, new ContentType(_shortStringHelper, -1)) {Id = -1, Key = Guid.Empty};
+                scope.Notifications.Publish(new ContentTreeChangeNotification(root, TreeChangeTypes.RefreshAll,
+                    EventMessagesFactory.Get()));
             }
 
-            Audit(AuditType.Sort, userId, 0, "Sorting content performed by user");
-            return OperationResult.Succeed(eventMessages);
+            return report;
         }
+    }
+
+    #endregion
+
+    #region Internal Methods
 
-        public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options)
+    /// 
+    ///     Gets a collection of  descendants by the first Parent.
+    /// 
+    ///  item to retrieve Descendants from
+    /// An Enumerable list of  objects
+    internal IEnumerable GetPublishedDescendants(IContent content)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.WriteLock(Constants.Locks.ContentTree);
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return GetPublishedDescendantsLocked(content).ToArray(); // ToArray important in uow!
+        }
+    }
+
+    internal IEnumerable GetPublishedDescendantsLocked(IContent content)
+    {
+        var pathMatch = content.Path + ",";
+        IQuery query = Query()
+            .Where(x => x.Id != content.Id && x.Path.StartsWith(pathMatch) /*&& x.Trashed == false*/);
+        IEnumerable contents = _documentRepository.Get(query);
 
-                ContentDataIntegrityReport report = _documentRepository.CheckDataIntegrity(options);
+        // beware! contents contains all published version below content
+        // including those that are not directly published because below an unpublished content
+        // these must be filtered out here
 
-                if (report.FixedIssues.Count > 0)
+        var parents = new List {content.Id};
+        if (contents is not null)
+        {
+            foreach (IContent c in contents)
+            {
+                if (parents.Contains(c.ParentId))
                 {
-                    //The event args needs a content item so we'll make a fake one with enough properties to not cause a null ref
-                    var root = new Content("root", -1, new ContentType(_shortStringHelper, -1))
-                    {
-                        Id = -1, Key = Guid.Empty
-                    };
-                    scope.Notifications.Publish(new ContentTreeChangeNotification(root, TreeChangeTypes.RefreshAll,
-                        EventMessagesFactory.Get()));
+                    yield return c;
+                    parents.Add(c.Id);
                 }
-
-                return report;
             }
         }
+    }
+
+    #endregion
+
+    #region Private Methods
+
+    private void Audit(AuditType type, int userId, int objectId, string? message = null,
+        string? parameters = null) =>
+        _auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.Document.GetName(), message,
+            parameters));
+
+    private bool IsDefaultCulture(IReadOnlyCollection? langs, string culture) =>
+        langs?.Any(x => x.IsDefault && x.IsoCode.InvariantEquals(culture)) ?? false;
+
+    private bool IsMandatoryCulture(IReadOnlyCollection langs, string culture) =>
+        langs.Any(x => x.IsMandatory && x.IsoCode.InvariantEquals(culture));
+
+    #endregion
+
+    #region Publishing Strategies
 
-        #endregion
+    /// 
+    ///     Ensures that a document can be published
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    private PublishResult StrategyCanPublish(ICoreScope scope, IContent content, bool checkPath,
+        IReadOnlyList? culturesPublishing,
+        IReadOnlyCollection? culturesUnpublishing, EventMessages evtMsgs,
+        IReadOnlyCollection allLangs, IDictionary? notificationState)
+    {
+        // raise Publishing notification
+        if (scope.Notifications.PublishCancelable(
+                new ContentPublishingNotification(content, evtMsgs).WithState(notificationState)))
+        {
+            _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}",
+                content.Name, content.Id, "publishing was cancelled");
+            return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
+        }
+
+        var variesByCulture = content.ContentType.VariesByCulture();
 
-        #region Internal Methods
+        CultureImpact[] impactsToPublish = culturesPublishing == null
+            ? new[] {CultureImpact.Invariant} //if it's null it's invariant
+            : culturesPublishing.Select(x =>
+                CultureImpact.Explicit(x,
+                    allLangs.Any(lang => lang.IsoCode.InvariantEquals(x) && lang.IsMandatory))).ToArray();
+
+        // publish the culture(s)
+        if (!impactsToPublish.All(content.PublishCulture))
+        {
+            return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content);
+        }
 
-        /// 
-        ///     Gets a collection of  descendants by the first Parent.
-        /// 
-        ///  item to retrieve Descendants from
-        /// An Enumerable list of  objects
-        internal IEnumerable GetPublishedDescendants(IContent content)
+        //validate the property values
+        IProperty[]? invalidProperties = null;
+        if (!impactsToPublish.All(x =>
+                _propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties, x)))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+            return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content)
             {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                return GetPublishedDescendantsLocked(content).ToArray(); // ToArray important in uow!
-            }
+                InvalidProperties = invalidProperties
+            };
         }
 
-        internal IEnumerable GetPublishedDescendantsLocked(IContent content)
+        //Check if mandatory languages fails, if this fails it will mean anything that the published flag on the document will
+        // be changed to Unpublished and any culture currently published will not be visible.
+        if (variesByCulture)
         {
-            var pathMatch = content.Path + ",";
-            IQuery query = Query()
-                .Where(x => x.Id != content.Id && x.Path.StartsWith(pathMatch) /*&& x.Trashed == false*/);
-            IEnumerable contents = _documentRepository.Get(query);
+            if (culturesPublishing == null)
+            {
+                throw new InvalidOperationException(
+                    "Internal error, variesByCulture but culturesPublishing is null.");
+            }
+
+            if (content.Published && culturesPublishing.Count == 0 && culturesUnpublishing?.Count == 0)
+            {
+                // no published cultures = cannot be published
+                // This will occur if for example, a culture that is already unpublished is sent to be unpublished again, or vice versa, in that case
+                // there will be nothing to publish/unpublish.
+                return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
+            }
 
-            // beware! contents contains all published version below content
-            // including those that are not directly published because below an unpublished content
-            // these must be filtered out here
 
-            var parents = new List {content.Id};
-            if (contents is not null)
+            // missing mandatory culture = cannot be published
+            IEnumerable mandatoryCultures = allLangs.Where(x => x.IsMandatory).Select(x => x.IsoCode);
+            var mandatoryMissing = mandatoryCultures.Any(x =>
+                !content.PublishedCultures.Contains(x, StringComparer.OrdinalIgnoreCase));
+            if (mandatoryMissing)
             {
-                foreach (IContent c in contents)
-                {
-                    if (parents.Contains(c.ParentId))
-                    {
-                        yield return c;
-                        parents.Add(c.Id);
-                    }
-                }
+                return new PublishResult(PublishResultType.FailedPublishMandatoryCultureMissing, evtMsgs, content);
             }
-        }
 
-        #endregion
+            if (culturesPublishing.Count == 0 && culturesUnpublishing?.Count > 0)
+            {
+                return new PublishResult(PublishResultType.SuccessUnpublishCulture, evtMsgs, content);
+            }
+        }
 
-        #region Private Methods
+        // ensure that the document has published values
+        // either because it is 'publishing' or because it already has a published version
+        if (content.PublishedState != PublishedState.Publishing && content.PublishedVersionId == 0)
+        {
+            _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}",
+                content.Name, content.Id, "document does not have published values");
+            return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
+        }
 
-        private void Audit(AuditType type, int userId, int objectId, string? message = null,
-            string? parameters = null) =>
-            _auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.Document.GetName(), message,
-                parameters));
+        ContentScheduleCollection contentSchedule = _documentRepository.GetContentSchedule(content.Id);
+        //loop over each culture publishing - or string.Empty for invariant
+        foreach (var culture in culturesPublishing ?? new[] {string.Empty})
+        {
+            // ensure that the document status is correct
+            // note: culture will be string.Empty for invariant
+            switch (content.GetStatus(contentSchedule, culture))
+            {
+                case ContentStatus.Expired:
+                    if (!variesByCulture)
+                    {
+                        _logger.LogInformation(
+                            "Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name,
+                            content.Id, "document has expired");
+                    }
+                    else
+                    {
+                        _logger.LogInformation(
+                            "Document {ContentName} (id={ContentId}) culture {Culture} cannot be published: {Reason}",
+                            content.Name, content.Id, culture, "document culture has expired");
+                    }
 
-        private bool IsDefaultCulture(IReadOnlyCollection? langs, string culture) =>
-            langs?.Any(x => x.IsDefault && x.IsoCode.InvariantEquals(culture)) ?? false;
+                    return new PublishResult(
+                        !variesByCulture
+                            ? PublishResultType.FailedPublishHasExpired
+                            : PublishResultType.FailedPublishCultureHasExpired, evtMsgs, content);
 
-        private bool IsMandatoryCulture(IReadOnlyCollection langs, string culture) =>
-            langs.Any(x => x.IsMandatory && x.IsoCode.InvariantEquals(culture));
+                case ContentStatus.AwaitingRelease:
+                    if (!variesByCulture)
+                    {
+                        _logger.LogInformation(
+                            "Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name,
+                            content.Id, "document is awaiting release");
+                    }
+                    else
+                    {
+                        _logger.LogInformation(
+                            "Document {ContentName} (id={ContentId}) culture {Culture} cannot be published: {Reason}",
+                            content.Name, content.Id, culture, "document is culture awaiting release");
+                    }
 
-        #endregion
+                    return new PublishResult(
+                        !variesByCulture
+                            ? PublishResultType.FailedPublishAwaitingRelease
+                            : PublishResultType.FailedPublishCultureAwaitingRelease, evtMsgs, content);
 
-        #region Publishing Strategies
+                case ContentStatus.Trashed:
+                    _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}",
+                        content.Name, content.Id, "document is trashed");
+                    return new PublishResult(PublishResultType.FailedPublishIsTrashed, evtMsgs, content);
+            }
+        }
 
-        /// 
-        ///     Ensures that a document can be published
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        private PublishResult StrategyCanPublish(ICoreScope scope, IContent content, bool checkPath,
-            IReadOnlyList? culturesPublishing,
-            IReadOnlyCollection? culturesUnpublishing, EventMessages evtMsgs,
-            IReadOnlyCollection allLangs, IDictionary? notificationState)
+        if (checkPath)
         {
-            // raise Publishing notification
-            if (scope.Notifications.PublishCancelable(
-                    new ContentPublishingNotification(content, evtMsgs).WithState(notificationState)))
+            // check if the content can be path-published
+            // root content can be published
+            // else check ancestors - we know we are not trashed
+            var pathIsOk = content.ParentId == Constants.System.Root || IsPathPublished(GetParent(content));
+            if (!pathIsOk)
             {
                 _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}",
-                    content.Name, content.Id, "publishing was cancelled");
-                return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
+                    content.Name, content.Id, "parent is not published");
+                return new PublishResult(PublishResultType.FailedPublishPathNotPublished, evtMsgs, content);
             }
+        }
 
-            var variesByCulture = content.ContentType.VariesByCulture();
-
-            CultureImpact[] impactsToPublish = culturesPublishing == null
-                ? new[] {CultureImpact.Invariant} //if it's null it's invariant
-                : culturesPublishing.Select(x =>
-                    CultureImpact.Explicit(x,
-                        allLangs.Any(lang => lang.IsoCode.InvariantEquals(x) && lang.IsMandatory))).ToArray();
+        //If we are both publishing and unpublishing cultures, then return a mixed status
+        if (variesByCulture && culturesPublishing?.Count > 0 && culturesUnpublishing?.Count > 0)
+        {
+            return new PublishResult(PublishResultType.SuccessMixedCulture, evtMsgs, content);
+        }
 
-            // publish the culture(s)
-            if (!impactsToPublish.All(content.PublishCulture))
-            {
-                return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content);
-            }
+        return new PublishResult(evtMsgs, content);
+    }
 
-            //validate the property values
-            IProperty[]? invalidProperties = null;
-            if (!impactsToPublish.All(x =>
-                    _propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties, x)))
-            {
-                return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content)
-                {
-                    InvalidProperties = invalidProperties
-                };
-            }
+    /// 
+    ///     Publishes a document
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    ///     It is assumed that all publishing checks have passed before calling this method like
+    ///     
+    /// 
+    private PublishResult StrategyPublish(IContent content,
+        IReadOnlyCollection? culturesPublishing, IReadOnlyCollection? culturesUnpublishing,
+        EventMessages evtMsgs)
+    {
+        // change state to publishing
+        content.PublishedState = PublishedState.Publishing;
 
-            //Check if mandatory languages fails, if this fails it will mean anything that the published flag on the document will
-            // be changed to Unpublished and any culture currently published will not be visible.
-            if (variesByCulture)
+        //if this is a variant then we need to log which cultures have been published/unpublished and return an appropriate result
+        if (content.ContentType.VariesByCulture())
+        {
+            if (content.Published && culturesUnpublishing?.Count == 0 && culturesPublishing?.Count == 0)
             {
-                if (culturesPublishing == null)
-                {
-                    throw new InvalidOperationException(
-                        "Internal error, variesByCulture but culturesPublishing is null.");
-                }
-
-                if (content.Published && culturesPublishing.Count == 0 && culturesUnpublishing?.Count == 0)
-                {
-                    // no published cultures = cannot be published
-                    // This will occur if for example, a culture that is already unpublished is sent to be unpublished again, or vice versa, in that case
-                    // there will be nothing to publish/unpublish.
-                    return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
-                }
-
-
-                // missing mandatory culture = cannot be published
-                IEnumerable mandatoryCultures = allLangs.Where(x => x.IsMandatory).Select(x => x.IsoCode);
-                var mandatoryMissing = mandatoryCultures.Any(x =>
-                    !content.PublishedCultures.Contains(x, StringComparer.OrdinalIgnoreCase));
-                if (mandatoryMissing)
-                {
-                    return new PublishResult(PublishResultType.FailedPublishMandatoryCultureMissing, evtMsgs, content);
-                }
-
-                if (culturesPublishing.Count == 0 && culturesUnpublishing?.Count > 0)
-                {
-                    return new PublishResult(PublishResultType.SuccessUnpublishCulture, evtMsgs, content);
-                }
+                return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
             }
 
-            // ensure that the document has published values
-            // either because it is 'publishing' or because it already has a published version
-            if (content.PublishedState != PublishedState.Publishing && content.PublishedVersionId == 0)
+            if (culturesUnpublishing?.Count > 0)
             {
-                _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}",
-                    content.Name, content.Id, "document does not have published values");
-                return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
+                _logger.LogInformation(
+                    "Document {ContentName} (id={ContentId}) cultures: {Cultures} have been unpublished.",
+                    content.Name, content.Id, string.Join(",", culturesUnpublishing));
             }
 
-            ContentScheduleCollection contentSchedule = _documentRepository.GetContentSchedule(content.Id);
-            //loop over each culture publishing - or string.Empty for invariant
-            foreach (var culture in culturesPublishing ?? new[] {string.Empty})
+            if (culturesPublishing?.Count > 0)
             {
-                // ensure that the document status is correct
-                // note: culture will be string.Empty for invariant
-                switch (content.GetStatus(contentSchedule, culture))
-                {
-                    case ContentStatus.Expired:
-                        if (!variesByCulture)
-                        {
-                            _logger.LogInformation(
-                                "Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name,
-                                content.Id, "document has expired");
-                        }
-                        else
-                        {
-                            _logger.LogInformation(
-                                "Document {ContentName} (id={ContentId}) culture {Culture} cannot be published: {Reason}",
-                                content.Name, content.Id, culture, "document culture has expired");
-                        }
-
-                        return new PublishResult(
-                            !variesByCulture
-                                ? PublishResultType.FailedPublishHasExpired
-                                : PublishResultType.FailedPublishCultureHasExpired, evtMsgs, content);
-
-                    case ContentStatus.AwaitingRelease:
-                        if (!variesByCulture)
-                        {
-                            _logger.LogInformation(
-                                "Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name,
-                                content.Id, "document is awaiting release");
-                        }
-                        else
-                        {
-                            _logger.LogInformation(
-                                "Document {ContentName} (id={ContentId}) culture {Culture} cannot be published: {Reason}",
-                                content.Name, content.Id, culture, "document is culture awaiting release");
-                        }
-
-                        return new PublishResult(
-                            !variesByCulture
-                                ? PublishResultType.FailedPublishAwaitingRelease
-                                : PublishResultType.FailedPublishCultureAwaitingRelease, evtMsgs, content);
-
-                    case ContentStatus.Trashed:
-                        _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}",
-                            content.Name, content.Id, "document is trashed");
-                        return new PublishResult(PublishResultType.FailedPublishIsTrashed, evtMsgs, content);
-                }
+                _logger.LogInformation(
+                    "Document {ContentName} (id={ContentId}) cultures: {Cultures} have been published.",
+                    content.Name, content.Id, string.Join(",", culturesPublishing));
             }
 
-            if (checkPath)
+            if (culturesUnpublishing?.Count > 0 && culturesPublishing?.Count > 0)
             {
-                // check if the content can be path-published
-                // root content can be published
-                // else check ancestors - we know we are not trashed
-                var pathIsOk = content.ParentId == Constants.System.Root || IsPathPublished(GetParent(content));
-                if (!pathIsOk)
-                {
-                    _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}",
-                        content.Name, content.Id, "parent is not published");
-                    return new PublishResult(PublishResultType.FailedPublishPathNotPublished, evtMsgs, content);
-                }
+                return new PublishResult(PublishResultType.SuccessMixedCulture, evtMsgs, content);
             }
 
-            //If we are both publishing and unpublishing cultures, then return a mixed status
-            if (variesByCulture && culturesPublishing?.Count > 0 && culturesUnpublishing?.Count > 0)
+            if (culturesUnpublishing?.Count > 0 && culturesPublishing?.Count == 0)
             {
-                return new PublishResult(PublishResultType.SuccessMixedCulture, evtMsgs, content);
+                return new PublishResult(PublishResultType.SuccessUnpublishCulture, evtMsgs, content);
             }
 
-            return new PublishResult(evtMsgs, content);
+            return new PublishResult(PublishResultType.SuccessPublishCulture, evtMsgs, content);
         }
 
-        /// 
-        ///     Publishes a document
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        ///     It is assumed that all publishing checks have passed before calling this method like
-        ///     
-        /// 
-        private PublishResult StrategyPublish(IContent content,
-            IReadOnlyCollection? culturesPublishing, IReadOnlyCollection? culturesUnpublishing,
-            EventMessages evtMsgs)
-        {
-            // change state to publishing
-            content.PublishedState = PublishedState.Publishing;
-
-            //if this is a variant then we need to log which cultures have been published/unpublished and return an appropriate result
-            if (content.ContentType.VariesByCulture())
-            {
-                if (content.Published && culturesUnpublishing?.Count == 0 && culturesPublishing?.Count == 0)
-                {
-                    return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
-                }
+        _logger.LogInformation("Document {ContentName} (id={ContentId}) has been published.", content.Name,
+            content.Id);
+        return new PublishResult(evtMsgs, content);
+    }
 
-                if (culturesUnpublishing?.Count > 0)
-                {
-                    _logger.LogInformation(
-                        "Document {ContentName} (id={ContentId}) cultures: {Cultures} have been unpublished.",
-                        content.Name, content.Id, string.Join(",", culturesUnpublishing));
-                }
+    /// 
+    ///     Ensures that a document can be unpublished
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    private PublishResult StrategyCanUnpublish(ICoreScope scope, IContent content, EventMessages evtMsgs)
+    {
+        // raise Unpublishing notification
+        if (scope.Notifications.PublishCancelable(new ContentUnpublishingNotification(content, evtMsgs)))
+        {
+            _logger.LogInformation(
+                "Document {ContentName} (id={ContentId}) cannot be unpublished: unpublishing was cancelled.",
+                content.Name, content.Id);
+            return new PublishResult(PublishResultType.FailedUnpublishCancelledByEvent, evtMsgs, content);
+        }
 
-                if (culturesPublishing?.Count > 0)
-                {
-                    _logger.LogInformation(
-                        "Document {ContentName} (id={ContentId}) cultures: {Cultures} have been published.",
-                        content.Name, content.Id, string.Join(",", culturesPublishing));
-                }
+        return new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content);
+    }
 
-                if (culturesUnpublishing?.Count > 0 && culturesPublishing?.Count > 0)
-                {
-                    return new PublishResult(PublishResultType.SuccessMixedCulture, evtMsgs, content);
-                }
+    /// 
+    ///     Unpublishes a document
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    ///     It is assumed that all unpublishing checks have passed before calling this method like
+    ///     
+    /// 
+    private PublishResult StrategyUnpublish(IContent content, EventMessages evtMsgs)
+    {
+        var attempt = new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content);
 
-                if (culturesUnpublishing?.Count > 0 && culturesPublishing?.Count == 0)
-                {
-                    return new PublishResult(PublishResultType.SuccessUnpublishCulture, evtMsgs, content);
-                }
+        //TODO: What is this check?? we just created this attempt and of course it is Success?!
+        if (attempt.Success == false)
+        {
+            return attempt;
+        }
 
-                return new PublishResult(PublishResultType.SuccessPublishCulture, evtMsgs, content);
-            }
+        // if the document has any release dates set to before now,
+        // they should be removed so they don't interrupt an unpublish
+        // otherwise it would remain released == published
 
-            _logger.LogInformation("Document {ContentName} (id={ContentId}) has been published.", content.Name,
-                content.Id);
-            return new PublishResult(evtMsgs, content);
+        ContentScheduleCollection contentSchedule = _documentRepository.GetContentSchedule(content.Id);
+        IReadOnlyList pastReleases =
+            contentSchedule.GetPending(ContentScheduleAction.Expire, DateTime.Now);
+        foreach (ContentSchedule p in pastReleases)
+        {
+            contentSchedule.Remove(p);
         }
 
-        /// 
-        ///     Ensures that a document can be unpublished
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        private PublishResult StrategyCanUnpublish(ICoreScope scope, IContent content, EventMessages evtMsgs)
+        if (pastReleases.Count > 0)
         {
-            // raise Unpublishing notification
-            if (scope.Notifications.PublishCancelable(new ContentUnpublishingNotification(content, evtMsgs)))
-            {
-                _logger.LogInformation(
-                    "Document {ContentName} (id={ContentId}) cannot be unpublished: unpublishing was cancelled.",
-                    content.Name, content.Id);
-                return new PublishResult(PublishResultType.FailedUnpublishCancelledByEvent, evtMsgs, content);
-            }
-
-            return new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content);
+            _logger.LogInformation(
+                "Document {ContentName} (id={ContentId}) had its release date removed, because it was unpublished.",
+                content.Name, content.Id);
         }
 
-        /// 
-        ///     Unpublishes a document
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        ///     It is assumed that all unpublishing checks have passed before calling this method like
-        ///     
-        /// 
-        private PublishResult StrategyUnpublish(IContent content, EventMessages evtMsgs)
-        {
-            var attempt = new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content);
+        _documentRepository.PersistContentSchedule(content, contentSchedule);
+        // change state to unpublishing
+        content.PublishedState = PublishedState.Unpublishing;
 
-            //TODO: What is this check?? we just created this attempt and of course it is Success?!
-            if (attempt.Success == false)
-            {
-                return attempt;
-            }
+        _logger.LogInformation("Document {ContentName} (id={ContentId}) has been unpublished.", content.Name,
+            content.Id);
+        return attempt;
+    }
 
-            // if the document has any release dates set to before now,
-            // they should be removed so they don't interrupt an unpublish
-            // otherwise it would remain released == published
+    #endregion
 
-            var contentSchedule = _documentRepository.GetContentSchedule(content.Id);
-            var pastReleases = contentSchedule.GetPending(ContentScheduleAction.Expire, DateTime.Now);
-            foreach (var p in pastReleases)
-                contentSchedule.Remove(p);
+    #region Content Types
 
-            if (pastReleases.Count > 0)
-            {
-                _logger.LogInformation(
-                    "Document {ContentName} (id={ContentId}) had its release date removed, because it was unpublished.",
-                    content.Name, content.Id);
-            }
+    /// 
+    ///     Deletes all content of specified type. All children of deleted content is moved to Recycle Bin.
+    /// 
+    /// 
+    ///     This needs extra care and attention as its potentially a dangerous and extensive operation.
+    ///     
+    ///         Deletes content items of the specified type, and only that type. Does *not* handle content types
+    ///         inheritance and compositions, which need to be managed outside of this method.
+    ///     
+    /// 
+    /// Id of the 
+    /// Optional Id of the user issuing the delete operation
+    public void DeleteOfTypes(IEnumerable contentTypeIds, int userId = Constants.Security.SuperUserId)
+    {
+        // TODO: This currently this is called from the ContentTypeService but that needs to change,
+        // if we are deleting a content type, we should just delete the data and do this operation slightly differently.
+        // This method will recursively go lookup every content item, check if any of it's descendants are
+        // of a different type, move them to the recycle bin, then permanently delete the content items.
+        // The main problem with this is that for every content item being deleted, events are raised...
+        // which we need for many things like keeping caches in sync, but we can surely do this MUCH better.
 
-            _documentRepository.PersistContentSchedule(content, contentSchedule);
-            // change state to unpublishing
-            content.PublishedState = PublishedState.Unpublishing;
+        var changes = new List>();
+        var moves = new List<(IContent, string)>();
+        var contentTypeIdsA = contentTypeIds.ToArray();
+        EventMessages eventMessages = EventMessagesFactory.Get();
 
-            _logger.LogInformation("Document {ContentName} (id={ContentId}) has been unpublished.", content.Name,
-                content.Id);
-            return attempt;
-        }
+        // using an immediate uow here because we keep making changes with
+        // PerformMoveLocked and DeleteLocked that must be applied immediately,
+        // no point queuing operations
+        //
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            scope.WriteLock(Constants.Locks.ContentTree);
 
-        #endregion
-
-        #region Content Types
-
-        /// 
-        ///     Deletes all content of specified type. All children of deleted content is moved to Recycle Bin.
-        /// 
-        /// 
-        ///     This needs extra care and attention as its potentially a dangerous and extensive operation.
-        ///     
-        ///         Deletes content items of the specified type, and only that type. Does *not* handle content types
-        ///         inheritance and compositions, which need to be managed outside of this method.
-        ///     
-        /// 
-        /// Id of the 
-        /// Optional Id of the user issuing the delete operation
-        public void DeleteOfTypes(IEnumerable contentTypeIds, int userId = Constants.Security.SuperUserId)
-        {
-            // TODO: This currently this is called from the ContentTypeService but that needs to change,
-            // if we are deleting a content type, we should just delete the data and do this operation slightly differently.
-            // This method will recursively go lookup every content item, check if any of it's descendants are
-            // of a different type, move them to the recycle bin, then permanently delete the content items.
-            // The main problem with this is that for every content item being deleted, events are raised...
-            // which we need for many things like keeping caches in sync, but we can surely do this MUCH better.
-
-            var changes = new List>();
-            var moves = new List<(IContent, string)>();
-            var contentTypeIdsA = contentTypeIds.ToArray();
-            EventMessages eventMessages = EventMessagesFactory.Get();
+            IQuery query = Query().WhereIn(x => x.ContentTypeId, contentTypeIdsA);
+            IContent[] contents = _documentRepository.Get(query).ToArray();
 
-            // using an immediate uow here because we keep making changes with
-            // PerformMoveLocked and DeleteLocked that must be applied immediately,
-            // no point queuing operations
-            //
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            if (contents is null)
             {
-                scope.WriteLock(Constants.Locks.ContentTree);
+                return;
+            }
 
-                IQuery query = Query().WhereIn(x => x.ContentTypeId, contentTypeIdsA);
-                IContent[] contents = _documentRepository.Get(query).ToArray();
+            if (scope.Notifications.PublishCancelable(new ContentDeletingNotification(contents, eventMessages)))
+            {
+                scope.Complete();
+                return;
+            }
 
-                if (contents is null)
+            // order by level, descending, so deepest first - that way, we cannot move
+            // a content of the deleted type, to the recycle bin (and then delete it...)
+            foreach (IContent content in contents.OrderByDescending(x => x.ParentId))
+            {
+                // if it's not trashed yet, and published, we should unpublish
+                // but... Unpublishing event makes no sense (not going to cancel?) and no need to save
+                // just raise the event
+                if (content.Trashed == false && content.Published)
                 {
-                    return;
+                    scope.Notifications.Publish(new ContentUnpublishedNotification(content, eventMessages));
                 }
 
-                if (scope.Notifications.PublishCancelable(new ContentDeletingNotification(contents, eventMessages)))
+                // if current content has children, move them to trash
+                IContent c = content;
+                IQuery childQuery = Query().Where(x => x.ParentId == c.Id);
+                IEnumerable children = _documentRepository.Get(childQuery);
+                foreach (IContent child in children)
                 {
-                    scope.Complete();
-                    return;
+                    // see MoveToRecycleBin
+                    PerformMoveLocked(child, Constants.System.RecycleBinContent, null, userId, moves, true);
+                    changes.Add(new TreeChange(content, TreeChangeTypes.RefreshBranch));
                 }
 
-                // order by level, descending, so deepest first - that way, we cannot move
-                // a content of the deleted type, to the recycle bin (and then delete it...)
-                foreach (IContent content in contents.OrderByDescending(x => x.ParentId))
-                {
-                    // if it's not trashed yet, and published, we should unpublish
-                    // but... Unpublishing event makes no sense (not going to cancel?) and no need to save
-                    // just raise the event
-                    if (content.Trashed == false && content.Published)
-                    {
-                        scope.Notifications.Publish(new ContentUnpublishedNotification(content, eventMessages));
-                    }
+                // delete content
+                // triggers the deleted event (and handles the files)
+                DeleteLocked(scope, content, eventMessages);
+                changes.Add(new TreeChange(content, TreeChangeTypes.Remove));
+            }
 
-                    // if current content has children, move them to trash
-                    IContent c = content;
-                    IQuery childQuery = Query().Where(x => x.ParentId == c.Id);
-                    IEnumerable children = _documentRepository.Get(childQuery);
-                    foreach (IContent child in children)
-                    {
-                        // see MoveToRecycleBin
-                        PerformMoveLocked(child, Constants.System.RecycleBinContent, null, userId, moves, true);
-                        changes.Add(new TreeChange(content, TreeChangeTypes.RefreshBranch));
-                    }
+            MoveEventInfo[] moveInfos = moves
+                .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId))
+                .ToArray();
+            if (moveInfos.Length > 0)
+            {
+                scope.Notifications.Publish(new ContentMovedToRecycleBinNotification(moveInfos, eventMessages));
+            }
 
-                    // delete content
-                    // triggers the deleted event (and handles the files)
-                    DeleteLocked(scope, content, eventMessages);
-                    changes.Add(new TreeChange(content, TreeChangeTypes.Remove));
-                }
+            scope.Notifications.Publish(new ContentTreeChangeNotification(changes, eventMessages));
 
-                MoveEventInfo[] moveInfos = moves
-                    .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId))
-                    .ToArray();
-                if (moveInfos.Length > 0)
-                {
-                    scope.Notifications.Publish(new ContentMovedToRecycleBinNotification(moveInfos, eventMessages));
-                }
+            Audit(AuditType.Delete, userId, Constants.System.Root,
+                $"Delete content of type {string.Join(",", contentTypeIdsA)}");
 
-                scope.Notifications.Publish(new ContentTreeChangeNotification(changes, eventMessages));
+            scope.Complete();
+        }
+    }
 
-                Audit(AuditType.Delete, userId, Constants.System.Root,
-                    $"Delete content of type {string.Join(",", contentTypeIdsA)}");
+    /// 
+    ///     Deletes all content items of specified type. All children of deleted content item is moved to Recycle Bin.
+    /// 
+    /// This needs extra care and attention as its potentially a dangerous and extensive operation
+    /// Id of the 
+    /// Optional id of the user deleting the media
+    public void DeleteOfType(int contentTypeId, int userId = Constants.Security.SuperUserId) =>
+        DeleteOfTypes(new[] {contentTypeId}, userId);
 
-                scope.Complete();
-            }
+    private IContentType GetContentType(ICoreScope scope, string contentTypeAlias)
+    {
+        if (contentTypeAlias == null)
+        {
+            throw new ArgumentNullException(nameof(contentTypeAlias));
         }
 
-        /// 
-        ///     Deletes all content items of specified type. All children of deleted content item is moved to Recycle Bin.
-        /// 
-        /// This needs extra care and attention as its potentially a dangerous and extensive operation
-        /// Id of the 
-        /// Optional id of the user deleting the media
-        public void DeleteOfType(int contentTypeId, int userId = Constants.Security.SuperUserId) =>
-            DeleteOfTypes(new[] {contentTypeId}, userId);
-
-        private IContentType GetContentType(ICoreScope scope, string contentTypeAlias)
+        if (string.IsNullOrWhiteSpace(contentTypeAlias))
         {
-            if (contentTypeAlias == null)
-            {
-                throw new ArgumentNullException(nameof(contentTypeAlias));
-            }
+            throw new ArgumentException("Value can't be empty or consist only of white-space characters.",
+                nameof(contentTypeAlias));
+        }
 
-            if (string.IsNullOrWhiteSpace(contentTypeAlias))
-            {
-                throw new ArgumentException("Value can't be empty or consist only of white-space characters.",
-                    nameof(contentTypeAlias));
-            }
+        scope.ReadLock(Constants.Locks.ContentTypes);
 
-            scope.ReadLock(Constants.Locks.ContentTypes);
+        IQuery query = Query().Where(x => x.Alias == contentTypeAlias);
+        IContentType? contentType = _contentTypeRepository.Get(query).FirstOrDefault();
 
-            IQuery query = Query().Where(x => x.Alias == contentTypeAlias);
-            IContentType? contentType = _contentTypeRepository.Get(query).FirstOrDefault();
+        if (contentType == null)
+        {
+            throw new Exception(
+                $"No ContentType matching the passed in Alias: '{contentTypeAlias}' was found"); // causes rollback
+        }
 
-            if (contentType == null)
-            {
-                throw new Exception(
-                    $"No ContentType matching the passed in Alias: '{contentTypeAlias}' was found"); // causes rollback
-            }
+        return contentType;
+    }
 
-            return contentType;
+    private IContentType GetContentType(string contentTypeAlias)
+    {
+        if (contentTypeAlias == null)
+        {
+            throw new ArgumentNullException(nameof(contentTypeAlias));
         }
 
-        private IContentType GetContentType(string contentTypeAlias)
+        if (string.IsNullOrWhiteSpace(contentTypeAlias))
         {
-            if (contentTypeAlias == null)
-            {
-                throw new ArgumentNullException(nameof(contentTypeAlias));
-            }
-
-            if (string.IsNullOrWhiteSpace(contentTypeAlias))
-            {
-                throw new ArgumentException("Value can't be empty or consist only of white-space characters.",
-                    nameof(contentTypeAlias));
-            }
+            throw new ArgumentException("Value can't be empty or consist only of white-space characters.",
+                nameof(contentTypeAlias));
+        }
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return GetContentType(scope, contentTypeAlias);
-            }
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return GetContentType(scope, contentTypeAlias);
         }
+    }
 
-        #endregion
+    #endregion
 
-        #region Blueprints
+    #region Blueprints
 
-        public IContent? GetBlueprintById(int id)
+    public IContent? GetBlueprintById(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+            scope.ReadLock(Constants.Locks.ContentTree);
+            IContent? blueprint = _documentBlueprintRepository.Get(id);
+            if (blueprint != null)
             {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                IContent? blueprint = _documentBlueprintRepository.Get(id);
-                if (blueprint != null)
-                {
-                    blueprint.Blueprint = true;
-                }
-
-                return blueprint;
+                blueprint.Blueprint = true;
             }
+
+            return blueprint;
         }
+    }
 
-        public IContent? GetBlueprintById(Guid id)
+    public IContent? GetBlueprintById(Guid id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+            scope.ReadLock(Constants.Locks.ContentTree);
+            IContent? blueprint = _documentBlueprintRepository.Get(id);
+            if (blueprint != null)
             {
-                scope.ReadLock(Constants.Locks.ContentTree);
-                IContent? blueprint = _documentBlueprintRepository.Get(id);
-                if (blueprint != null)
-                {
-                    blueprint.Blueprint = true;
-                }
-
-                return blueprint;
+                blueprint.Blueprint = true;
             }
+
+            return blueprint;
         }
+    }
+
+    public void SaveBlueprint(IContent content, int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
 
-        public void SaveBlueprint(IContent content, int userId = Constants.Security.SuperUserId)
+        //always ensure the blueprint is at the root
+        if (content.ParentId != -1)
         {
-            EventMessages evtMsgs = EventMessagesFactory.Get();
+            content.ParentId = -1;
+        }
 
-            //always ensure the blueprint is at the root
-            if (content.ParentId != -1)
-            {
-                content.ParentId = -1;
-            }
+        content.Blueprint = true;
 
-            content.Blueprint = true;
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            scope.WriteLock(Constants.Locks.ContentTree);
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            if (content.HasIdentity == false)
             {
-                scope.WriteLock(Constants.Locks.ContentTree);
-
-                if (content.HasIdentity == false)
-                {
-                    content.CreatorId = userId;
-                }
+                content.CreatorId = userId;
+            }
 
-                content.WriterId = userId;
+            content.WriterId = userId;
 
-                _documentBlueprintRepository.Save(content);
+            _documentBlueprintRepository.Save(content);
 
-                Audit(AuditType.Save, Constants.Security.SuperUserId, content.Id,
-                    $"Saved content template: {content.Name}");
+            Audit(AuditType.Save, Constants.Security.SuperUserId, content.Id,
+                $"Saved content template: {content.Name}");
 
-                scope.Notifications.Publish(new ContentSavedBlueprintNotification(content, evtMsgs));
+            scope.Notifications.Publish(new ContentSavedBlueprintNotification(content, evtMsgs));
 
-                scope.Complete();
-            }
+            scope.Complete();
         }
+    }
 
-        public void DeleteBlueprint(IContent content, int userId = Constants.Security.SuperUserId)
-        {
-            EventMessages evtMsgs = EventMessagesFactory.Get();
+    public void DeleteBlueprint(IContent content, int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(Constants.Locks.ContentTree);
-                _documentBlueprintRepository.Delete(content);
-                scope.Notifications.Publish(new ContentDeletedBlueprintNotification(content, evtMsgs));
-                scope.Complete();
-            }
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            scope.WriteLock(Constants.Locks.ContentTree);
+            _documentBlueprintRepository.Delete(content);
+            scope.Notifications.Publish(new ContentDeletedBlueprintNotification(content, evtMsgs));
+            scope.Complete();
         }
+    }
 
-        private static readonly string?[] ArrayOfOneNullString = {null};
+    private static readonly string?[] ArrayOfOneNullString = {null};
 
-        public IContent CreateContentFromBlueprint(IContent blueprint, string name,
-            int userId = Constants.Security.SuperUserId)
+    public IContent CreateContentFromBlueprint(IContent blueprint, string name,
+        int userId = Constants.Security.SuperUserId)
+    {
+        if (blueprint == null)
         {
-            if (blueprint == null)
-            {
-                throw new ArgumentNullException(nameof(blueprint));
-            }
+            throw new ArgumentNullException(nameof(blueprint));
+        }
 
-            IContentType contentType = GetContentType(blueprint.ContentType.Alias);
-            var content = new Content(name, -1, contentType);
-            content.Path = string.Concat(content.ParentId.ToString(), ",", content.Id);
+        IContentType contentType = GetContentType(blueprint.ContentType.Alias);
+        var content = new Content(name, -1, contentType);
+        content.Path = string.Concat(content.ParentId.ToString(), ",", content.Id);
 
-            content.CreatorId = userId;
-            content.WriterId = userId;
+        content.CreatorId = userId;
+        content.WriterId = userId;
 
-            IEnumerable cultures = ArrayOfOneNullString;
-            if (blueprint.CultureInfos?.Count > 0)
+        IEnumerable cultures = ArrayOfOneNullString;
+        if (blueprint.CultureInfos?.Count > 0)
+        {
+            cultures = blueprint.CultureInfos.Values.Select(x => x.Culture);
+            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
             {
-                cultures = blueprint.CultureInfos.Values.Select(x => x.Culture);
-                using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+                if (blueprint.CultureInfos.TryGetValue(_languageRepository.GetDefaultIsoCode(),
+                        out ContentCultureInfos defaultCulture))
                 {
-                    if (blueprint.CultureInfos.TryGetValue(_languageRepository.GetDefaultIsoCode(),
-                            out ContentCultureInfos defaultCulture))
-                    {
-                        defaultCulture.Name = name;
-                    }
-
-                    scope.Complete();
+                    defaultCulture.Name = name;
                 }
+
+                scope.Complete();
             }
+        }
 
-            DateTime now = DateTime.Now;
-            foreach (var culture in cultures)
+        DateTime now = DateTime.Now;
+        foreach (var culture in cultures)
+        {
+            foreach (IProperty property in blueprint.Properties)
             {
-                foreach (IProperty property in blueprint.Properties)
-                {
-                    var propertyCulture = property.PropertyType.VariesByCulture() ? culture : null;
-                    content.SetValue(property.Alias, property.GetValue(propertyCulture), propertyCulture);
-                }
-
-                if (!string.IsNullOrEmpty(culture))
-                {
-                    content.SetCultureInfo(culture, blueprint.GetCultureName(culture), now);
-                }
+                var propertyCulture = property.PropertyType.VariesByCulture() ? culture : null;
+                content.SetValue(property.Alias, property.GetValue(propertyCulture), propertyCulture);
             }
 
-            return content;
+            if (!string.IsNullOrEmpty(culture))
+            {
+                content.SetCultureInfo(culture, blueprint.GetCultureName(culture), now);
+            }
         }
 
-        public IEnumerable GetBlueprintsForContentTypes(params int[] contentTypeId)
+        return content;
+    }
+
+    public IEnumerable GetBlueprintsForContentTypes(params int[] contentTypeId)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
+            IQuery query = Query();
+            if (contentTypeId.Length > 0)
             {
-                IQuery query = Query();
-                if (contentTypeId.Length > 0)
-                {
-                    query.Where(x => contentTypeId.Contains(x.ContentTypeId));
-                }
-
-                return _documentBlueprintRepository.Get(query).Select(x =>
-                {
-                    x.Blueprint = true;
-                    return x;
-                });
+                query.Where(x => contentTypeId.Contains(x.ContentTypeId));
             }
+
+            return _documentBlueprintRepository.Get(query).Select(x =>
+            {
+                x.Blueprint = true;
+                return x;
+            });
         }
+    }
+
+    public void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds,
+        int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
 
-        public void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds,
-            int userId = Constants.Security.SuperUserId)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            EventMessages evtMsgs = EventMessagesFactory.Get();
+            scope.WriteLock(Constants.Locks.ContentTree);
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            var contentTypeIdsA = contentTypeIds.ToArray();
+            IQuery query = Query();
+            if (contentTypeIdsA.Length > 0)
             {
-                scope.WriteLock(Constants.Locks.ContentTree);
-
-                var contentTypeIdsA = contentTypeIds.ToArray();
-                IQuery query = Query();
-                if (contentTypeIdsA.Length > 0)
-                {
-                    query.Where(x => contentTypeIdsA.Contains(x.ContentTypeId));
-                }
+                query.Where(x => contentTypeIdsA.Contains(x.ContentTypeId));
+            }
 
-                IContent[]? blueprints = _documentBlueprintRepository.Get(query)?.Select(x =>
-                {
-                    x.Blueprint = true;
-                    return x;
-                }).ToArray();
+            IContent[]? blueprints = _documentBlueprintRepository.Get(query)?.Select(x =>
+            {
+                x.Blueprint = true;
+                return x;
+            }).ToArray();
 
-                if (blueprints is not null)
+            if (blueprints is not null)
+            {
+                foreach (IContent blueprint in blueprints)
                 {
-                    foreach (IContent blueprint in blueprints)
-                    {
-                        _documentBlueprintRepository.Delete(blueprint);
-                    }
-
-                    scope.Notifications.Publish(new ContentDeletedBlueprintNotification(blueprints, evtMsgs));
-                    scope.Complete();
+                    _documentBlueprintRepository.Delete(blueprint);
                 }
+
+                scope.Notifications.Publish(new ContentDeletedBlueprintNotification(blueprints, evtMsgs));
+                scope.Complete();
             }
         }
+    }
 
-        public void DeleteBlueprintsOfType(int contentTypeId, int userId = Constants.Security.SuperUserId) =>
-            DeleteBlueprintsOfTypes(new[] {contentTypeId}, userId);
+    public void DeleteBlueprintsOfType(int contentTypeId, int userId = Constants.Security.SuperUserId) =>
+        DeleteBlueprintsOfTypes(new[] {contentTypeId}, userId);
 
-        #endregion
-    }
+    #endregion
 }
diff --git a/src/Umbraco.Core/Services/ContentServiceExtensions.cs b/src/Umbraco.Core/Services/ContentServiceExtensions.cs
index 726c5b4435f8..73893ba61e3c 100644
--- a/src/Umbraco.Core/Services/ContentServiceExtensions.cs
+++ b/src/Umbraco.Core/Services/ContentServiceExtensions.cs
@@ -1,102 +1,110 @@
 // Copyright (c) Umbraco.
 // See LICENSE for more details.
 
-using System;
-using System.Collections.Generic;
-using System.Linq;
 using System.Text.RegularExpressions;
 using Umbraco.Cms.Core;
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Models.Membership;
 using Umbraco.Cms.Core.Services;
 
-namespace Umbraco.Extensions
+namespace Umbraco.Extensions;
+
+/// 
+///     Content service extension methods
+/// 
+public static class ContentServiceExtensions
 {
-    /// 
-    /// Content service extension methods
-    /// 
-    public static class ContentServiceExtensions
+    public static IEnumerable? GetByIds(this IContentService contentService, IEnumerable ids)
     {
-        #region RTE Anchor values
-
-        private static readonly Regex AnchorRegex = new Regex("", RegexOptions.Compiled);
-
-        public static IEnumerable GetAnchorValuesFromRTEs(this IContentService contentService, int id, string? culture = "*")
+        var guids = new List();
+        foreach (Udi udi in ids)
         {
-            var result = new List();
-            var content = contentService.GetById(id);
-
-            if (content is not null)
+            var guidUdi = udi as GuidUdi;
+            if (guidUdi is null)
             {
-                foreach (var contentProperty in content.Properties)
-                {
-                    if (contentProperty.PropertyType.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.TinyMce))
-                    {
-                        var value = contentProperty.GetValue(culture)?.ToString();
-                        if (!string.IsNullOrEmpty(value))
-                        {
-                            result.AddRange(contentService.GetAnchorValuesFromRTEContent(value));
-                        }
-                    }
-                }
+                throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) +
+                                                    " which is required by content");
             }
 
-            return result;
+            guids.Add(guidUdi);
         }
 
+        return contentService.GetByIds(guids.Select(x => x.Guid));
+    }
 
-        public static IEnumerable GetAnchorValuesFromRTEContent(this IContentService contentService, string rteContent)
+    /// 
+    ///     Method to create an IContent object based on the Udi of a parent
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    public static IContent CreateContent(this IContentService contentService, string name, Udi parentId,
+        string contentTypeAlias, int userId = Constants.Security.SuperUserId)
+    {
+        var guidUdi = parentId as GuidUdi;
+        if (guidUdi is null)
         {
-            var result = new List();
-            var matches = AnchorRegex.Matches(rteContent);
-            foreach (Match match in matches)
-            {
-                result.Add(match.Value.Split(Constants.CharArrays.DoubleQuote)[1]);
-            }
-            return result;
+            throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) +
+                                                " which is required by content");
         }
-        #endregion
 
-        public static IEnumerable? GetByIds(this IContentService contentService, IEnumerable ids)
+        IContent parent = contentService.GetById(guidUdi.Guid);
+        return contentService.Create(name, parent, contentTypeAlias, userId);
+    }
+
+    /// 
+    ///     Remove all permissions for this user for all nodes
+    /// 
+    /// 
+    /// 
+    public static void RemoveContentPermissions(this IContentService contentService, int contentId) =>
+        contentService.SetPermissions(new EntityPermissionSet(contentId, new EntityPermissionCollection()));
+
+    #region RTE Anchor values
+
+    private static readonly Regex AnchorRegex = new("", RegexOptions.Compiled);
+
+    public static IEnumerable GetAnchorValuesFromRTEs(this IContentService contentService, int id,
+        string? culture = "*")
+    {
+        var result = new List();
+        IContent content = contentService.GetById(id);
+
+        if (content is not null)
         {
-            var guids = new List();
-            foreach (var udi in ids)
+            foreach (IProperty contentProperty in content.Properties)
             {
-                var guidUdi = udi as GuidUdi;
-                if (guidUdi is null)
-                    throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by content");
-                guids.Add(guidUdi);
+                if (contentProperty.PropertyType.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases
+                        .TinyMce))
+                {
+                    var value = contentProperty.GetValue(culture)?.ToString();
+                    if (!string.IsNullOrEmpty(value))
+                    {
+                        result.AddRange(contentService.GetAnchorValuesFromRTEContent(value));
+                    }
+                }
             }
-
-            return contentService.GetByIds(guids.Select(x => x.Guid));
         }
 
-        /// 
-        /// Method to create an IContent object based on the Udi of a parent
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        public static IContent CreateContent(this IContentService contentService, string name, Udi parentId, string contentTypeAlias, int userId = Constants.Security.SuperUserId)
-        {
-            var guidUdi = parentId as GuidUdi;
-            if (guidUdi is null)
-                throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by content");
-            var parent = contentService.GetById(guidUdi.Guid);
-            return contentService.Create(name, parent, contentTypeAlias, userId);
-        }
+        return result;
+    }
 
-        /// 
-        /// Remove all permissions for this user for all nodes
-        /// 
-        /// 
-        /// 
-        public static void RemoveContentPermissions(this IContentService contentService, int contentId)
+
+    public static IEnumerable GetAnchorValuesFromRTEContent(this IContentService contentService,
+        string rteContent)
+    {
+        var result = new List();
+        MatchCollection matches = AnchorRegex.Matches(rteContent);
+        foreach (Match match in matches)
         {
-            contentService.SetPermissions(new EntityPermissionSet(contentId, new EntityPermissionCollection()));
+            result.Add(match.Value.Split(Constants.CharArrays.DoubleQuote)[1]);
         }
+
+        return result;
     }
+
+    #endregion
 }
diff --git a/src/Umbraco.Core/Services/ContentTypeBaseServiceProvider.cs b/src/Umbraco.Core/Services/ContentTypeBaseServiceProvider.cs
index b493460876fe..2cd0d6f2f9c1 100644
--- a/src/Umbraco.Core/Services/ContentTypeBaseServiceProvider.cs
+++ b/src/Umbraco.Core/Services/ContentTypeBaseServiceProvider.cs
@@ -1,42 +1,50 @@
-using System;
 using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public class ContentTypeBaseServiceProvider : IContentTypeBaseServiceProvider
 {
-    public class ContentTypeBaseServiceProvider : IContentTypeBaseServiceProvider
+    private readonly IContentTypeService _contentTypeService;
+    private readonly IMediaTypeService _mediaTypeService;
+    private readonly IMemberTypeService _memberTypeService;
+
+    public ContentTypeBaseServiceProvider(IContentTypeService contentTypeService, IMediaTypeService mediaTypeService,
+        IMemberTypeService memberTypeService)
     {
-        private readonly IContentTypeService _contentTypeService;
-        private readonly IMediaTypeService _mediaTypeService;
-        private readonly IMemberTypeService _memberTypeService;
+        _contentTypeService = contentTypeService;
+        _mediaTypeService = mediaTypeService;
+        _memberTypeService = memberTypeService;
+    }
 
-        public ContentTypeBaseServiceProvider(IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService)
+    public IContentTypeBaseService For(IContentBase contentBase)
+    {
+        if (contentBase == null)
         {
-            _contentTypeService = contentTypeService;
-            _mediaTypeService = mediaTypeService;
-            _memberTypeService = memberTypeService;
+            throw new ArgumentNullException(nameof(contentBase));
         }
 
-        public IContentTypeBaseService For(IContentBase contentBase)
+        switch (contentBase)
         {
-            if (contentBase == null) throw new ArgumentNullException(nameof(contentBase));
-            switch (contentBase)
-            {
-                case IContent _:
-                    return  _contentTypeService;
-                case IMedia _:
-                    return   _mediaTypeService;
-                case IMember _:
-                    return  _memberTypeService;
-                default:
-                    throw new ArgumentException($"Invalid contentBase type: {contentBase.GetType().FullName}" , nameof(contentBase));
-            }
+            case IContent _:
+                return _contentTypeService;
+            case IMedia _:
+                return _mediaTypeService;
+            case IMember _:
+                return _memberTypeService;
+            default:
+                throw new ArgumentException($"Invalid contentBase type: {contentBase.GetType().FullName}",
+                    nameof(contentBase));
         }
+    }
 
-        // note: this should be a default interface method with C# 8
-        public IContentTypeComposition? GetContentTypeOf(IContentBase contentBase)
+    // note: this should be a default interface method with C# 8
+    public IContentTypeComposition? GetContentTypeOf(IContentBase contentBase)
+    {
+        if (contentBase == null)
         {
-            if (contentBase == null) throw new ArgumentNullException(nameof(contentBase));
-            return For(contentBase).Get(contentBase.ContentTypeId);
+            throw new ArgumentNullException(nameof(contentBase));
         }
+
+        return For(contentBase).Get(contentBase.ContentTypeId);
     }
 }
diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs
index 8f7316d913a8..2a610903532e 100644
--- a/src/Umbraco.Core/Services/ContentTypeService.cs
+++ b/src/Umbraco.Core/Services/ContentTypeService.cs
@@ -1,6 +1,3 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
 using Microsoft.Extensions.Logging;
 using Umbraco.Cms.Core.Events;
 using Umbraco.Cms.Core.Models;
@@ -9,125 +6,125 @@
 using Umbraco.Cms.Core.Scoping;
 using Umbraco.Cms.Core.Services.Changes;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Represents the ContentType Service, which is an easy access to operations involving 
+/// 
+public class ContentTypeService : ContentTypeServiceBase, IContentTypeService
 {
+    public ContentTypeService(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
+        IEventMessagesFactory eventMessagesFactory, IContentService contentService,
+        IContentTypeRepository repository, IAuditRepository auditRepository,
+        IDocumentTypeContainerRepository entityContainerRepository, IEntityRepository entityRepository,
+        IEventAggregator eventAggregator)
+        : base(provider, loggerFactory, eventMessagesFactory, repository, auditRepository, entityContainerRepository,
+            entityRepository, eventAggregator) =>
+        ContentService = contentService;
+
+    // beware! order is important to avoid deadlocks
+    protected override int[] ReadLockIds { get; } = {Constants.Locks.ContentTypes};
+    protected override int[] WriteLockIds { get; } = {Constants.Locks.ContentTree, Constants.Locks.ContentTypes};
+
+    private IContentService ContentService { get; }
+
+    protected override Guid ContainedObjectType => Constants.ObjectTypes.DocumentType;
+
     /// 
-    /// Represents the ContentType Service, which is an easy access to operations involving 
+    ///     Gets all property type aliases across content, media and member types.
     /// 
-    public class ContentTypeService : ContentTypeServiceBase, IContentTypeService
+    /// All property type aliases.
+    /// Beware! Works across content, media and member types.
+    public IEnumerable GetAllPropertyTypeAliases()
     {
-        public ContentTypeService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IContentService contentService,
-            IContentTypeRepository repository, IAuditRepository auditRepository, IDocumentTypeContainerRepository entityContainerRepository, IEntityRepository entityRepository,
-            IEventAggregator eventAggregator)
-            : base(provider, loggerFactory, eventMessagesFactory, repository, auditRepository, entityContainerRepository, entityRepository, eventAggregator)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            ContentService = contentService;
+            // that one is special because it works across content, media and member types
+            scope.ReadLock(Constants.Locks.ContentTypes, Constants.Locks.MediaTypes, Constants.Locks.MemberTypes);
+            return Repository.GetAllPropertyTypeAliases();
         }
+    }
 
-        // beware! order is important to avoid deadlocks
-        protected override int[] ReadLockIds { get; } = { Cms.Core.Constants.Locks.ContentTypes };
-        protected override int[] WriteLockIds { get; } = { Cms.Core.Constants.Locks.ContentTree, Cms.Core.Constants.Locks.ContentTypes };
-
-        private IContentService ContentService { get; }
-
-        protected override Guid ContainedObjectType => Cms.Core.Constants.ObjectTypes.DocumentType;
-
-        #region Notifications
-
-        protected override SavingNotification GetSavingNotification(IContentType item,
-            EventMessages eventMessages) => new ContentTypeSavingNotification(item, eventMessages);
+    /// 
+    ///     Gets all content type aliases across content, media and member types.
+    /// 
+    /// Optional object types guid to restrict to content, and/or media, and/or member types.
+    /// All content type aliases.
+    /// Beware! Works across content, media and member types.
+    public IEnumerable GetAllContentTypeAliases(params Guid[] guids)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            // that one is special because it works across content, media and member types
+            scope.ReadLock(Constants.Locks.ContentTypes, Constants.Locks.MediaTypes, Constants.Locks.MemberTypes);
+            return Repository.GetAllContentTypeAliases(guids);
+        }
+    }
 
-        protected override SavingNotification GetSavingNotification(IEnumerable items,
-            EventMessages eventMessages) => new ContentTypeSavingNotification(items, eventMessages);
+    /// 
+    ///     Gets all content type id for aliases across content, media and member types.
+    /// 
+    /// Aliases to look for.
+    /// All content type ids.
+    /// Beware! Works across content, media and member types.
+    public IEnumerable GetAllContentTypeIds(string[] aliases)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            // that one is special because it works across content, media and member types
+            scope.ReadLock(Constants.Locks.ContentTypes, Constants.Locks.MediaTypes, Constants.Locks.MemberTypes);
+            return Repository.GetAllContentTypeIds(aliases);
+        }
+    }
 
-        protected override SavedNotification GetSavedNotification(IContentType item,
-            EventMessages eventMessages) => new ContentTypeSavedNotification(item, eventMessages);
+    protected override void DeleteItemsOfTypes(IEnumerable typeIds)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            var typeIdsA = typeIds.ToArray();
+            ContentService.DeleteOfTypes(typeIdsA);
+            ContentService.DeleteBlueprintsOfTypes(typeIdsA);
+            scope.Complete();
+        }
+    }
 
-        protected override SavedNotification GetSavedNotification(IEnumerable items,
-            EventMessages eventMessages) => new ContentTypeSavedNotification(items, eventMessages);
+    #region Notifications
 
-        protected override DeletingNotification GetDeletingNotification(IContentType item,
-            EventMessages eventMessages) => new ContentTypeDeletingNotification(item, eventMessages);
+    protected override SavingNotification GetSavingNotification(IContentType item,
+        EventMessages eventMessages) => new ContentTypeSavingNotification(item, eventMessages);
 
-        protected override DeletingNotification GetDeletingNotification(IEnumerable items,
-            EventMessages eventMessages) => new ContentTypeDeletingNotification(items, eventMessages);
+    protected override SavingNotification GetSavingNotification(IEnumerable items,
+        EventMessages eventMessages) => new ContentTypeSavingNotification(items, eventMessages);
 
-        protected override DeletedNotification GetDeletedNotification(IEnumerable items,
-            EventMessages eventMessages) => new ContentTypeDeletedNotification(items, eventMessages);
+    protected override SavedNotification GetSavedNotification(IContentType item,
+        EventMessages eventMessages) => new ContentTypeSavedNotification(item, eventMessages);
 
-        protected override MovingNotification GetMovingNotification(MoveEventInfo moveInfo,
-            EventMessages eventMessages) => new ContentTypeMovingNotification(moveInfo, eventMessages);
+    protected override SavedNotification GetSavedNotification(IEnumerable items,
+        EventMessages eventMessages) => new ContentTypeSavedNotification(items, eventMessages);
 
-        protected override MovedNotification GetMovedNotification(
-            IEnumerable> moveInfo, EventMessages eventMessages) =>
-            new ContentTypeMovedNotification(moveInfo, eventMessages);
+    protected override DeletingNotification GetDeletingNotification(IContentType item,
+        EventMessages eventMessages) => new ContentTypeDeletingNotification(item, eventMessages);
 
-        protected override ContentTypeChangeNotification GetContentTypeChangedNotification(
-            IEnumerable> changes, EventMessages eventMessages) =>
-            new ContentTypeChangedNotification(changes, eventMessages);
+    protected override DeletingNotification GetDeletingNotification(IEnumerable items,
+        EventMessages eventMessages) => new ContentTypeDeletingNotification(items, eventMessages);
 
-        protected override ContentTypeRefreshNotification GetContentTypeRefreshedNotification(
-            IEnumerable> changes, EventMessages eventMessages) =>
-            new ContentTypeRefreshedNotification(changes, eventMessages);
+    protected override DeletedNotification GetDeletedNotification(IEnumerable items,
+        EventMessages eventMessages) => new ContentTypeDeletedNotification(items, eventMessages);
 
-        #endregion
+    protected override MovingNotification GetMovingNotification(MoveEventInfo moveInfo,
+        EventMessages eventMessages) => new ContentTypeMovingNotification(moveInfo, eventMessages);
 
-        protected override void DeleteItemsOfTypes(IEnumerable typeIds)
-        {
-            using (var scope = ScopeProvider.CreateCoreScope())
-            {
-                var typeIdsA = typeIds.ToArray();
-                ContentService.DeleteOfTypes(typeIdsA);
-                ContentService.DeleteBlueprintsOfTypes(typeIdsA);
-                scope.Complete();
-            }
-        }
+    protected override MovedNotification GetMovedNotification(
+        IEnumerable> moveInfo, EventMessages eventMessages) =>
+        new ContentTypeMovedNotification(moveInfo, eventMessages);
 
-        /// 
-        /// Gets all property type aliases across content, media and member types.
-        /// 
-        /// All property type aliases.
-        /// Beware! Works across content, media and member types.
-        public IEnumerable GetAllPropertyTypeAliases()
-        {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                // that one is special because it works across content, media and member types
-                scope.ReadLock(new[] { Constants.Locks.ContentTypes, Constants.Locks.MediaTypes, Constants.Locks.MemberTypes });
-                return Repository.GetAllPropertyTypeAliases();
-            }
-        }
+    protected override ContentTypeChangeNotification GetContentTypeChangedNotification(
+        IEnumerable> changes, EventMessages eventMessages) =>
+        new ContentTypeChangedNotification(changes, eventMessages);
 
-        /// 
-        /// Gets all content type aliases across content, media and member types.
-        /// 
-        /// Optional object types guid to restrict to content, and/or media, and/or member types.
-        /// All content type aliases.
-        /// Beware! Works across content, media and member types.
-        public IEnumerable GetAllContentTypeAliases(params Guid[] guids)
-        {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                // that one is special because it works across content, media and member types
-                scope.ReadLock(new[] { Constants.Locks.ContentTypes, Constants.Locks.MediaTypes, Constants.Locks.MemberTypes });
-                return Repository.GetAllContentTypeAliases(guids);
-            }
-        }
+    protected override ContentTypeRefreshNotification GetContentTypeRefreshedNotification(
+        IEnumerable> changes, EventMessages eventMessages) =>
+        new ContentTypeRefreshedNotification(changes, eventMessages);
 
-        /// 
-        /// Gets all content type id for aliases across content, media and member types.
-        /// 
-        /// Aliases to look for.
-        /// All content type ids.
-        /// Beware! Works across content, media and member types.
-        public IEnumerable GetAllContentTypeIds(string[] aliases)
-        {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                // that one is special because it works across content, media and member types
-                scope.ReadLock(new[] { Constants.Locks.ContentTypes, Constants.Locks.MediaTypes, Constants.Locks.MemberTypes });
-                return Repository.GetAllContentTypeIds(aliases);
-            }
-        }
-    }
+    #endregion
 }
diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBase.cs b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs
index 1e97e02dca6a..5bacf5102e6c 100644
--- a/src/Umbraco.Core/Services/ContentTypeServiceBase.cs
+++ b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs
@@ -2,12 +2,13 @@
 using Umbraco.Cms.Core.Events;
 using Umbraco.Cms.Core.Scoping;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public abstract class ContentTypeServiceBase : RepositoryService
 {
-    public abstract class ContentTypeServiceBase : RepositoryService
+    protected ContentTypeServiceBase(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
+        IEventMessagesFactory eventMessagesFactory)
+        : base(provider, loggerFactory, eventMessagesFactory)
     {
-        protected ContentTypeServiceBase(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory)
-            : base(provider, loggerFactory, eventMessagesFactory)
-        { }
     }
 }
diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
index d97021dce044..5970331b2bf6 100644
--- a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
+++ b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
@@ -5,1080 +5,1177 @@
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Models.Entities;
 using Umbraco.Cms.Core.Notifications;
+using Umbraco.Cms.Core.Persistence.Querying;
 using Umbraco.Cms.Core.Persistence.Repositories;
 using Umbraco.Cms.Core.Scoping;
 using Umbraco.Cms.Core.Services.Changes;
 using Umbraco.Extensions;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public abstract class ContentTypeServiceBase : ContentTypeServiceBase,
+    IContentTypeBaseService
+    where TRepository : IContentTypeRepositoryBase
+    where TItem : class, IContentTypeComposition
 {
-    public abstract class ContentTypeServiceBase : ContentTypeServiceBase, IContentTypeBaseService
-        where TRepository : IContentTypeRepositoryBase
-        where TItem : class, IContentTypeComposition
+    private readonly IAuditRepository _auditRepository;
+    private readonly IEntityContainerRepository _containerRepository;
+    private readonly IEntityRepository _entityRepository;
+    private readonly IEventAggregator _eventAggregator;
+
+    protected ContentTypeServiceBase(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
+        IEventMessagesFactory eventMessagesFactory,
+        TRepository repository, IAuditRepository auditRepository, IEntityContainerRepository containerRepository,
+        IEntityRepository entityRepository,
+        IEventAggregator eventAggregator)
+        : base(provider, loggerFactory, eventMessagesFactory)
+    {
+        Repository = repository;
+        _auditRepository = auditRepository;
+        _containerRepository = containerRepository;
+        _entityRepository = entityRepository;
+        _eventAggregator = eventAggregator;
+    }
+
+    protected TRepository Repository { get; }
+    protected abstract int[] WriteLockIds { get; }
+    protected abstract int[] ReadLockIds { get; }
+
+    #region Move
+
+    public Attempt?> Move(TItem moving, int containerId)
     {
-        private readonly IAuditRepository _auditRepository;
-        private readonly IEntityContainerRepository _containerRepository;
-        private readonly IEntityRepository _entityRepository;
-        private readonly IEventAggregator _eventAggregator;
-
-        protected ContentTypeServiceBase(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory,
-            TRepository repository, IAuditRepository auditRepository, IEntityContainerRepository containerRepository, IEntityRepository entityRepository,
-            IEventAggregator eventAggregator)
-            : base(provider, loggerFactory, eventMessagesFactory)
+        EventMessages eventMessages = EventMessagesFactory.Get();
+
+        var moveInfo = new List>();
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            Repository = repository;
-            _auditRepository = auditRepository;
-            _containerRepository = containerRepository;
-            _entityRepository = entityRepository;
-            _eventAggregator = eventAggregator;
+            var moveEventInfo = new MoveEventInfo(moving, moving.Path, containerId);
+            MovingNotification movingNotification = GetMovingNotification(moveEventInfo, eventMessages);
+            if (scope.Notifications.PublishCancelable(movingNotification))
+            {
+                scope.Complete();
+                return OperationResult.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, eventMessages);
+            }
+
+            scope.WriteLock(WriteLockIds); // also for containers
+
+            try
+            {
+                EntityContainer? container = null;
+                if (containerId > 0)
+                {
+                    container = _containerRepository?.Get(containerId);
+                    if (container == null)
+                    {
+                        throw new DataOperationException(MoveOperationStatusType
+                            .FailedParentNotFound); // causes rollback
+                    }
+                }
+
+                moveInfo.AddRange(Repository.Move(moving, container!));
+                scope.Complete();
+            }
+            catch (DataOperationException ex)
+            {
+                scope.Complete();
+                return OperationResult.Attempt.Fail(ex.Operation, eventMessages);
+            }
+
+            // note: not raising any Changed event here because moving a content type under another container
+            // has no impact on the published content types - would be entirely different if we were to support
+            // moving a content type under another content type.
+            MovedNotification movedNotification = GetMovedNotification(moveInfo, eventMessages);
+            movedNotification.WithStateFrom(movingNotification);
+            scope.Notifications.Publish(movedNotification);
         }
 
-        protected TRepository Repository { get; }
-        protected abstract int[] WriteLockIds { get; }
-        protected abstract int[] ReadLockIds { get; }
+        return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, eventMessages);
+    }
+
+    #endregion
+
+    #region Audit
 
-        #region Notifications
+    private void Audit(AuditType type, int userId, int objectId) =>
+        _auditRepository.Save(new AuditItem(objectId, type, userId,
+            ObjectTypes.GetUmbracoObjectType(ContainedObjectType).GetName()));
 
-        protected abstract SavingNotification GetSavingNotification(TItem item, EventMessages eventMessages);
-        protected abstract SavingNotification GetSavingNotification(IEnumerable items, EventMessages eventMessages);
+    #endregion
 
-        protected abstract SavedNotification GetSavedNotification(TItem item, EventMessages eventMessages);
-        protected abstract SavedNotification GetSavedNotification(IEnumerable items, EventMessages eventMessages);
+    #region Notifications
 
-        protected abstract DeletingNotification GetDeletingNotification(TItem item, EventMessages eventMessages);
-        protected abstract DeletingNotification GetDeletingNotification(IEnumerable items, EventMessages eventMessages);
+    protected abstract SavingNotification GetSavingNotification(TItem item, EventMessages eventMessages);
 
-        protected abstract DeletedNotification GetDeletedNotification(IEnumerable items, EventMessages eventMessages);
+    protected abstract SavingNotification GetSavingNotification(IEnumerable items,
+        EventMessages eventMessages);
 
-        protected abstract MovingNotification GetMovingNotification(MoveEventInfo moveInfo, EventMessages eventMessages);
+    protected abstract SavedNotification GetSavedNotification(TItem item, EventMessages eventMessages);
 
-        protected abstract MovedNotification GetMovedNotification(IEnumerable> moveInfo, EventMessages eventMessages);
+    protected abstract SavedNotification GetSavedNotification(IEnumerable items,
+        EventMessages eventMessages);
 
-        protected abstract ContentTypeChangeNotification GetContentTypeChangedNotification(IEnumerable> changes, EventMessages eventMessages);
+    protected abstract DeletingNotification GetDeletingNotification(TItem item, EventMessages eventMessages);
 
-        // This notification is identical to GetTypeChangeNotification, however it needs to be a different notification type because it's published within the transaction
-        /// The purpose of this notification being published within the transaction is so that listeners can perform database
-        /// operations from within the same transaction and guarantee data consistency so that if anything goes wrong
-        /// the entire transaction can be rolled back. This is used by Nucache.
-        protected abstract ContentTypeRefreshNotification GetContentTypeRefreshedNotification(IEnumerable> changes, EventMessages eventMessages);
+    protected abstract DeletingNotification GetDeletingNotification(IEnumerable items,
+        EventMessages eventMessages);
 
-        #endregion
+    protected abstract DeletedNotification GetDeletedNotification(IEnumerable items,
+        EventMessages eventMessages);
 
-        #region Validation
+    protected abstract MovingNotification GetMovingNotification(MoveEventInfo moveInfo,
+        EventMessages eventMessages);
 
-        public Attempt ValidateComposition(TItem? compo)
+    protected abstract MovedNotification GetMovedNotification(IEnumerable> moveInfo,
+        EventMessages eventMessages);
+
+    protected abstract ContentTypeChangeNotification GetContentTypeChangedNotification(
+        IEnumerable> changes, EventMessages eventMessages);
+
+    // This notification is identical to GetTypeChangeNotification, however it needs to be a different notification type because it's published within the transaction
+    /// The purpose of this notification being published within the transaction is so that listeners can perform database
+    /// operations from within the same transaction and guarantee data consistency so that if anything goes wrong
+    /// the entire transaction can be rolled back. This is used by Nucache.
+    protected abstract ContentTypeRefreshNotification GetContentTypeRefreshedNotification(
+        IEnumerable> changes, EventMessages eventMessages);
+
+    #endregion
+
+    #region Validation
+
+    public Attempt ValidateComposition(TItem? compo)
+    {
+        try
         {
-            try
-            {
-                using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-                {
-                    scope.ReadLock(ReadLockIds);
-                    ValidateLocked(compo!);
-                }
-                return Attempt.Succeed();
-            }
-            catch (InvalidCompositionException ex)
+            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
             {
-                return Attempt.Fail(ex.PropertyTypeAliases, ex);
+                scope.ReadLock(ReadLockIds);
+                ValidateLocked(compo!);
             }
-        }
 
-        protected void ValidateLocked(TItem compositionContentType)
+            return Attempt.Succeed();
+        }
+        catch (InvalidCompositionException ex)
         {
-            // performs business-level validation of the composition
-            // should ensure that it is absolutely safe to save the composition
-
-            // eg maybe a property has been added, with an alias that's OK (no conflict with ancestors)
-            // but that cannot be used (conflict with descendants)
-
-            var allContentTypes = Repository.GetMany(new int[0]).Cast().ToArray();
+            return Attempt.Fail(ex.PropertyTypeAliases, ex);
+        }
+    }
 
-            var compositionAliases = compositionContentType.CompositionAliases();
-            var compositions = allContentTypes.Where(x => compositionAliases.Any(y => x.Alias.Equals(y)));
-            var propertyTypeAliases = compositionContentType.PropertyTypes.Select(x => x.Alias).ToArray();
-            var propertyGroupAliases = compositionContentType.PropertyGroups.ToDictionary(x => x.Alias, x => x.Type, StringComparer.InvariantCultureIgnoreCase);
-            var indirectReferences = allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == compositionContentType.Id));
-            var comparer = new DelegateEqualityComparer((x, y) => x?.Id == y?.Id, x => x.Id);
-            var dependencies = new HashSet(compositions, comparer);
+    protected void ValidateLocked(TItem compositionContentType)
+    {
+        // performs business-level validation of the composition
+        // should ensure that it is absolutely safe to save the composition
+
+        // eg maybe a property has been added, with an alias that's OK (no conflict with ancestors)
+        // but that cannot be used (conflict with descendants)
+
+        IContentTypeComposition[] allContentTypes =
+            Repository.GetMany(new int[0]).Cast().ToArray();
+
+        IEnumerable compositionAliases = compositionContentType.CompositionAliases();
+        IEnumerable compositions =
+            allContentTypes.Where(x => compositionAliases.Any(y => x.Alias.Equals(y)));
+        var propertyTypeAliases = compositionContentType.PropertyTypes.Select(x => x.Alias).ToArray();
+        var propertyGroupAliases = compositionContentType.PropertyGroups.ToDictionary(x => x.Alias, x => x.Type,
+            StringComparer.InvariantCultureIgnoreCase);
+        IEnumerable indirectReferences =
+            allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == compositionContentType.Id));
+        var comparer = new DelegateEqualityComparer((x, y) => x?.Id == y?.Id, x => x.Id);
+        var dependencies = new HashSet(compositions, comparer);
+
+        var stack = new Stack();
+        foreach (IContentTypeComposition indirectReference in indirectReferences)
+        {
+            stack.Push(indirectReference); // push indirect references to a stack, so we can add recursively
+        }
 
-            var stack = new Stack();
-            foreach (var indirectReference in indirectReferences)
-                stack.Push(indirectReference); // push indirect references to a stack, so we can add recursively
+        while (stack.Count > 0)
+        {
+            IContentTypeComposition indirectReference = stack.Pop();
+            dependencies.Add(indirectReference);
 
-            while (stack.Count > 0)
+            // get all compositions for the current indirect reference
+            IEnumerable directReferences = indirectReference.ContentTypeComposition;
+            foreach (IContentTypeComposition directReference in directReferences)
             {
-                var indirectReference = stack.Pop();
-                dependencies.Add(indirectReference);
-
-                // get all compositions for the current indirect reference
-                var directReferences = indirectReference.ContentTypeComposition;
-                foreach (var directReference in directReferences)
+                if (directReference.Id == compositionContentType.Id ||
+                    directReference.Alias.Equals(compositionContentType.Alias))
                 {
-                    if (directReference.Id == compositionContentType.Id || directReference.Alias.Equals(compositionContentType.Alias))
-                        continue;
+                    continue;
+                }
 
-                    dependencies.Add(directReference);
+                dependencies.Add(directReference);
 
-                    // a direct reference has compositions of its own - these also need to be taken into account
-                    var directReferenceGraph = directReference.CompositionAliases();
-                    foreach (var c in allContentTypes.Where(x => directReferenceGraph.Any(y => x.Alias.Equals(y, StringComparison.InvariantCultureIgnoreCase))))
-                        dependencies.Add(c);
+                // a direct reference has compositions of its own - these also need to be taken into account
+                IEnumerable directReferenceGraph = directReference.CompositionAliases();
+                foreach (IContentTypeComposition c in allContentTypes.Where(x =>
+                             directReferenceGraph.Any(y =>
+                                 x.Alias.Equals(y, StringComparison.InvariantCultureIgnoreCase))))
+                {
+                    dependencies.Add(c);
                 }
-
-                // recursive lookup of indirect references
-                foreach (var c in allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == indirectReference.Id)))
-                    stack.Push(c);
             }
 
-            var duplicatePropertyTypeAliases = new List();
-            var invalidPropertyGroupAliases = new List();
-
-            foreach (var dependency in dependencies)
+            // recursive lookup of indirect references
+            foreach (IContentTypeComposition c in allContentTypes.Where(x =>
+                         x.ContentTypeComposition.Any(y => y.Id == indirectReference.Id)))
             {
-                if (dependency.Id == compositionContentType.Id)
-                    continue;
+                stack.Push(c);
+            }
+        }
 
-                var contentTypeDependency = allContentTypes.FirstOrDefault(x => x.Alias.Equals(dependency.Alias, StringComparison.InvariantCultureIgnoreCase));
-                if (contentTypeDependency == null)
-                    continue;
+        var duplicatePropertyTypeAliases = new List();
+        var invalidPropertyGroupAliases = new List();
 
-                duplicatePropertyTypeAliases.AddRange(contentTypeDependency.PropertyTypes.Select(x => x.Alias).Intersect(propertyTypeAliases, StringComparer.InvariantCultureIgnoreCase));
-                invalidPropertyGroupAliases.AddRange(contentTypeDependency.PropertyGroups.Where(x => propertyGroupAliases.TryGetValue(x.Alias, out var type) && type != x.Type).Select(x => x.Alias));
+        foreach (IContentTypeComposition dependency in dependencies)
+        {
+            if (dependency.Id == compositionContentType.Id)
+            {
+                continue;
             }
 
-            if (duplicatePropertyTypeAliases.Count > 0 || invalidPropertyGroupAliases.Count > 0)
-
+            IContentTypeComposition contentTypeDependency = allContentTypes.FirstOrDefault(x =>
+                x.Alias.Equals(dependency.Alias, StringComparison.InvariantCultureIgnoreCase));
+            if (contentTypeDependency == null)
             {
-                throw new InvalidCompositionException(compositionContentType.Alias, null, duplicatePropertyTypeAliases.Distinct().ToArray(), invalidPropertyGroupAliases.Distinct().ToArray());
+                continue;
             }
+
+            duplicatePropertyTypeAliases.AddRange(contentTypeDependency.PropertyTypes.Select(x => x.Alias)
+                .Intersect(propertyTypeAliases, StringComparer.InvariantCultureIgnoreCase));
+            invalidPropertyGroupAliases.AddRange(contentTypeDependency.PropertyGroups.Where(x =>
+                    propertyGroupAliases.TryGetValue(x.Alias, out PropertyGroupType type) && type != x.Type)
+                .Select(x => x.Alias));
         }
 
-        #endregion
+        if (duplicatePropertyTypeAliases.Count > 0 || invalidPropertyGroupAliases.Count > 0)
 
-        #region Composition
+        {
+            throw new InvalidCompositionException(compositionContentType.Alias, null,
+                duplicatePropertyTypeAliases.Distinct().ToArray(), invalidPropertyGroupAliases.Distinct().ToArray());
+        }
+    }
 
-        internal IEnumerable> ComposeContentTypeChanges(params TItem[] contentTypes)
+    #endregion
+
+    #region Composition
+
+    internal IEnumerable> ComposeContentTypeChanges(params TItem[] contentTypes)
+    {
+        // find all content types impacted by the changes,
+        // - content type alias changed
+        // - content type property removed, or alias changed
+        // - content type composition removed (not testing if composition had properties...)
+        // - content type variation changed
+        // - property type variation changed
+        //
+        // because these are the changes that would impact the raw content data
+
+        // note
+        // this is meant to run *after* uow.Commit() so must use WasPropertyDirty() everywhere
+        // instead of IsPropertyDirty() since dirty properties have been reset already
+
+        var changes = new List>();
+
+        foreach (TItem contentType in contentTypes)
         {
-            // find all content types impacted by the changes,
-            // - content type alias changed
-            // - content type property removed, or alias changed
-            // - content type composition removed (not testing if composition had properties...)
-            // - content type variation changed
-            // - property type variation changed
-            //
-            // because these are the changes that would impact the raw content data
-
-            // note
-            // this is meant to run *after* uow.Commit() so must use WasPropertyDirty() everywhere
-            // instead of IsPropertyDirty() since dirty properties have been reset already
-
-            var changes = new List>();
-
-            foreach (var contentType in contentTypes)
+            var dirty = (IRememberBeingDirty)contentType;
+
+            // skip new content types
+            // TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly
+            var isNewContentType = dirty.WasPropertyDirty("Id");
+            if (isNewContentType)
             {
-                var dirty = (IRememberBeingDirty)contentType;
+                AddChange(changes, contentType, ContentTypeChangeTypes.Create);
+                continue;
+            }
 
-                // skip new content types
+            // alias change?
+            var hasAliasChanged = dirty.WasPropertyDirty("Alias");
+
+            // existing property alias change?
+            var hasAnyPropertyChangedAlias = contentType.PropertyTypes.Any(propertyType =>
+            {
+                // skip new properties
                 // TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly
-                var isNewContentType = dirty.WasPropertyDirty("Id");
-                if (isNewContentType)
+                var isNewProperty = propertyType.WasPropertyDirty("Id");
+                if (isNewProperty)
                 {
-                    AddChange(changes, contentType, ContentTypeChangeTypes.Create);
-                    continue;
+                    return false;
                 }
 
                 // alias change?
-                var hasAliasChanged = dirty.WasPropertyDirty("Alias");
-
-                // existing property alias change?
-                var hasAnyPropertyChangedAlias = contentType.PropertyTypes.Any(propertyType =>
-                {
-                    // skip new properties
-                    // TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly
-                    var isNewProperty = propertyType.WasPropertyDirty("Id");
-                    if (isNewProperty) return false;
+                return propertyType.WasPropertyDirty("Alias");
+            });
 
-                    // alias change?
-                    return propertyType.WasPropertyDirty("Alias");
-                });
+            // removed properties?
+            var hasAnyPropertyBeenRemoved = dirty.WasPropertyDirty("HasPropertyTypeBeenRemoved");
 
-                // removed properties?
-                var hasAnyPropertyBeenRemoved = dirty.WasPropertyDirty("HasPropertyTypeBeenRemoved");
+            // removed compositions?
+            var hasAnyCompositionBeenRemoved = dirty.WasPropertyDirty("HasCompositionTypeBeenRemoved");
 
-                // removed compositions?
-                var hasAnyCompositionBeenRemoved = dirty.WasPropertyDirty("HasCompositionTypeBeenRemoved");
+            // variation changed?
+            var hasContentTypeVariationChanged = dirty.WasPropertyDirty("Variations");
 
-                // variation changed?
-                var hasContentTypeVariationChanged = dirty.WasPropertyDirty("Variations");
+            // property variation change?
+            var hasAnyPropertyVariationChanged = contentType.WasPropertyTypeVariationChanged();
 
-                // property variation change?
-                var hasAnyPropertyVariationChanged = contentType.WasPropertyTypeVariationChanged();
+            // main impact on properties?
+            var hasPropertyMainImpact = hasContentTypeVariationChanged || hasAnyPropertyVariationChanged
+                                                                       || hasAnyCompositionBeenRemoved ||
+                                                                       hasAnyPropertyBeenRemoved ||
+                                                                       hasAnyPropertyChangedAlias;
 
-                // main impact on properties?
-                var hasPropertyMainImpact = hasContentTypeVariationChanged || hasAnyPropertyVariationChanged
-                    || hasAnyCompositionBeenRemoved || hasAnyPropertyBeenRemoved || hasAnyPropertyChangedAlias;
-
-                if (hasAliasChanged || hasPropertyMainImpact)
-                {
-                    // add that one, as a main change
-                    AddChange(changes, contentType, ContentTypeChangeTypes.RefreshMain);
+            if (hasAliasChanged || hasPropertyMainImpact)
+            {
+                // add that one, as a main change
+                AddChange(changes, contentType, ContentTypeChangeTypes.RefreshMain);
 
-                    if (hasPropertyMainImpact)
-                        foreach (var c in GetComposedOf(contentType.Id))
-                            AddChange(changes, c, ContentTypeChangeTypes.RefreshMain);
-                }
-                else
+                if (hasPropertyMainImpact)
                 {
-                    // add that one, as an other change
-                    AddChange(changes, contentType, ContentTypeChangeTypes.RefreshOther);
+                    foreach (TItem c in GetComposedOf(contentType.Id))
+                    {
+                        AddChange(changes, c, ContentTypeChangeTypes.RefreshMain);
+                    }
                 }
             }
-
-            return changes;
-        }
-
-        // ensures changes contains no duplicates
-        private static void AddChange(ICollection> changes, TItem contentType, ContentTypeChangeTypes changeTypes)
-        {
-            var change = changes.FirstOrDefault(x => x.Item == contentType);
-            if (change == null)
+            else
             {
-                changes.Add(new ContentTypeChange(contentType, changeTypes));
-                return;
+                // add that one, as an other change
+                AddChange(changes, contentType, ContentTypeChangeTypes.RefreshOther);
             }
-            change.ChangeTypes |= changeTypes;
         }
 
-        #endregion
-
-        #region Get, Has, Is, Count
+        return changes;
+    }
 
-        IContentTypeComposition? IContentTypeBaseService.Get(int id)
+    // ensures changes contains no duplicates
+    private static void AddChange(ICollection> changes, TItem contentType,
+        ContentTypeChangeTypes changeTypes)
+    {
+        ContentTypeChange change = changes.FirstOrDefault(x => x.Item == contentType);
+        if (change == null)
         {
-            return Get(id);
+            changes.Add(new ContentTypeChange(contentType, changeTypes));
+            return;
         }
 
-        public TItem? Get(int id)
+        change.ChangeTypes |= changeTypes;
+    }
+
+    #endregion
+
+    #region Get, Has, Is, Count
+
+    IContentTypeComposition? IContentTypeBaseService.Get(int id) => Get(id);
+
+    public TItem? Get(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(ReadLockIds);
-                return Repository.Get(id);
-            }
+            scope.ReadLock(ReadLockIds);
+            return Repository.Get(id);
         }
+    }
 
-        public TItem? Get(string alias)
+    public TItem? Get(string alias)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(ReadLockIds);
-                return Repository.Get(alias);
-            }
+            scope.ReadLock(ReadLockIds);
+            return Repository.Get(alias);
         }
+    }
 
-        public TItem? Get(Guid id)
+    public TItem? Get(Guid id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(ReadLockIds);
-                return Repository.Get(id);
-            }
+            scope.ReadLock(ReadLockIds);
+            return Repository.Get(id);
         }
+    }
 
-        public IEnumerable GetAll(params int[] ids)
+    public IEnumerable GetAll(params int[] ids)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(ReadLockIds);
-                return Repository.GetMany(ids);
-            }
+            scope.ReadLock(ReadLockIds);
+            return Repository.GetMany(ids);
         }
+    }
 
-        public IEnumerable GetAll(IEnumerable? ids)
+    public IEnumerable GetAll(IEnumerable? ids)
+    {
+        if (ids is null)
         {
-            if (ids is null)
-            {
-                return Enumerable.Empty();
-            }
+            return Enumerable.Empty();
+        }
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
 
-            {
-                scope.ReadLock(ReadLockIds);
-                return Repository.GetMany(ids.ToArray());
-            }
+        {
+            scope.ReadLock(ReadLockIds);
+            return Repository.GetMany(ids.ToArray());
         }
+    }
 
-        public IEnumerable GetChildren(int id)
+    public IEnumerable GetChildren(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(ReadLockIds);
-                var query = Query().Where(x => x.ParentId == id);
-                return Repository.Get(query);
-            }
+            scope.ReadLock(ReadLockIds);
+            IQuery query = Query().Where(x => x.ParentId == id);
+            return Repository.Get(query);
         }
+    }
 
-        public IEnumerable GetChildren(Guid id)
+    public IEnumerable GetChildren(Guid id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+            scope.ReadLock(ReadLockIds);
+            TItem found = Get(id);
+            if (found == null)
             {
-                scope.ReadLock(ReadLockIds);
-                var found = Get(id);
-                if (found == null) return Enumerable.Empty();
-                var query = Query().Where(x => x.ParentId == found.Id);
-                return Repository.Get(query);
+                return Enumerable.Empty();
             }
+
+            IQuery query = Query().Where(x => x.ParentId == found.Id);
+            return Repository.Get(query);
         }
+    }
 
-        public bool HasChildren(int id)
+    public bool HasChildren(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(ReadLockIds);
-                var query = Query().Where(x => x.ParentId == id);
-                var count = Repository.Count(query);
-                return count > 0;
-            }
+            scope.ReadLock(ReadLockIds);
+            IQuery query = Query().Where(x => x.ParentId == id);
+            var count = Repository.Count(query);
+            return count > 0;
         }
+    }
 
-        public bool HasChildren(Guid id)
+    public bool HasChildren(Guid id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+            scope.ReadLock(ReadLockIds);
+            TItem found = Get(id);
+            if (found == null)
             {
-                scope.ReadLock(ReadLockIds);
-                var found = Get(id);
-                if (found == null) return false;
-                var query = Query().Where(x => x.ParentId == found.Id);
-                var count = Repository.Count(query);
-                return count > 0;
+                return false;
             }
+
+            IQuery query = Query().Where(x => x.ParentId == found.Id);
+            var count = Repository.Count(query);
+            return count > 0;
         }
+    }
 
-        /// 
-        /// Given the path of a content item, this will return true if the content item exists underneath a list view content item
-        /// 
-        /// 
-        /// 
-        public bool HasContainerInPath(string contentPath)
+    /// 
+    ///     Given the path of a content item, this will return true if the content item exists underneath a list view content
+    ///     item
+    /// 
+    /// 
+    /// 
+    public bool HasContainerInPath(string contentPath)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                // can use same repo for both content and media
-                return Repository.HasContainerInPath(contentPath);
-            }
+            // can use same repo for both content and media
+            return Repository.HasContainerInPath(contentPath);
         }
+    }
 
-        public bool HasContainerInPath(params int[] ids)
+    public bool HasContainerInPath(params int[] ids)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                // can use same repo for both content and media
-                return Repository.HasContainerInPath(ids);
-            }
+            // can use same repo for both content and media
+            return Repository.HasContainerInPath(ids);
         }
+    }
 
-        public IEnumerable GetDescendants(int id, bool andSelf)
+    public IEnumerable GetDescendants(int id, bool andSelf)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(ReadLockIds);
+            scope.ReadLock(ReadLockIds);
 
-                var descendants = new List();
-                if (andSelf)
+            var descendants = new List();
+            if (andSelf)
+            {
+                TItem self = Repository.Get(id);
+                if (self is not null)
                 {
-                    var self = Repository.Get(id);
-                    if (self is not null)
-                    {
-                        descendants.Add(self);
-                    }
+                    descendants.Add(self);
                 }
-                var ids = new Stack();
-                ids.Push(id);
+            }
 
-                while (ids.Count > 0)
-                {
-                    var i = ids.Pop();
-                    var query = Query().Where(x => x.ParentId == i);
-                    var result = Repository.Get(query).ToArray();
+            var ids = new Stack();
+            ids.Push(id);
+
+            while (ids.Count > 0)
+            {
+                var i = ids.Pop();
+                IQuery query = Query().Where(x => x.ParentId == i);
+                TItem[] result = Repository.Get(query).ToArray();
 
-                    if (result is not null)
+                if (result is not null)
+                {
+                    foreach (TItem c in result)
                     {
-                        foreach (var c in result)
-                        {
-                            descendants.Add(c);
-                            ids.Push(c.Id);
-                        }
+                        descendants.Add(c);
+                        ids.Push(c.Id);
                     }
                 }
-
-                return descendants.ToArray();
             }
+
+            return descendants.ToArray();
         }
+    }
 
-        public IEnumerable GetComposedOf(int id, IEnumerable all)
-        {
-            return all.Where(x => x.ContentTypeComposition.Any(y => y.Id == id));
+    public IEnumerable GetComposedOf(int id, IEnumerable all) =>
+        all.Where(x => x.ContentTypeComposition.Any(y => y.Id == id));
 
-        }
+    public IEnumerable GetComposedOf(int id)
+    {
+        // GetAll is cheap, repository has a full dataset cache policy
+        // TODO: still, because it uses the cache, race conditions!
+        IEnumerable allContentTypes = GetAll(Array.Empty());
+        return GetComposedOf(id, allContentTypes);
+    }
 
-        public IEnumerable GetComposedOf(int id)
+    public int Count()
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            // GetAll is cheap, repository has a full dataset cache policy
-            // TODO: still, because it uses the cache, race conditions!
-            var allContentTypes = GetAll(Array.Empty());
-            return GetComposedOf(id, allContentTypes);
+            scope.ReadLock(ReadLockIds);
+            return Repository.Count(Query());
         }
+    }
 
-        public int Count()
+    public bool HasContentNodes(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(ReadLockIds);
-                return Repository.Count(Query());
-            }
+            scope.ReadLock(ReadLockIds);
+            return Repository.HasContentNodes(id);
         }
+    }
+
+    #endregion
 
-        public bool HasContentNodes(int id)
+    #region Save
+
+    public void Save(TItem? item, int userId = Constants.Security.SuperUserId)
+    {
+        if (item is null)
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(ReadLockIds);
-                return Repository.HasContentNodes(id);
-            }
+            return;
         }
 
-        #endregion
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
 
-        #region Save
-
-        public void Save(TItem? item, int userId = Cms.Core.Constants.Security.SuperUserId)
         {
-            if (item is null)
+            EventMessages eventMessages = EventMessagesFactory.Get();
+            SavingNotification savingNotification = GetSavingNotification(item, eventMessages);
+            if (scope.Notifications.PublishCancelable(savingNotification))
             {
+                scope.Complete();
                 return;
             }
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            if (string.IsNullOrWhiteSpace(item.Name))
+            {
+                throw new ArgumentException("Cannot save item with empty name.");
+            }
 
+            if (item.Name != null && item.Name.Length > 255)
             {
-                EventMessages eventMessages = EventMessagesFactory.Get();
-                SavingNotification savingNotification = GetSavingNotification(item, eventMessages);
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
+                throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+            }
 
-                if (string.IsNullOrWhiteSpace(item.Name))
-                {
-                    throw new ArgumentException("Cannot save item with empty name.");
-                }
+            scope.WriteLock(WriteLockIds);
 
-                if (item.Name != null && item.Name.Length > 255)
-                {
-                    throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
-                }
+            // validate the DAG transform, within the lock
+            ValidateLocked(item); // throws if invalid
 
-                scope.WriteLock(WriteLockIds);
+            item.CreatorId = userId;
+            if (item.Description == string.Empty)
+            {
+                item.Description = null;
+            }
 
-                // validate the DAG transform, within the lock
-                ValidateLocked(item); // throws if invalid
+            Repository.Save(item); // also updates content/media/member items
 
-                item.CreatorId = userId;
-                if (item.Description == string.Empty)
-                {
-                    item.Description = null;
-                }
+            // figure out impacted content types
+            ContentTypeChange[] changes = ComposeContentTypeChanges(item).ToArray();
 
-                Repository.Save(item); // also updates content/media/member items
+            // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info.
+            _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages));
 
-                // figure out impacted content types
-                ContentTypeChange[] changes = ComposeContentTypeChanges(item).ToArray();
+            scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages));
 
-                // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info.
-                _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages));
+            SavedNotification savedNotification = GetSavedNotification(item, eventMessages);
+            savedNotification.WithStateFrom(savingNotification);
+            scope.Notifications.Publish(savedNotification);
 
-                scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages));
+            Audit(AuditType.Save, userId, item.Id);
+            scope.Complete();
+        }
+    }
 
-                SavedNotification savedNotification = GetSavedNotification(item, eventMessages);
-                savedNotification.WithStateFrom(savingNotification);
-                scope.Notifications.Publish(savedNotification);
+    public void Save(IEnumerable items, int userId = Constants.Security.SuperUserId)
+    {
+        TItem[] itemsA = items.ToArray();
 
-                Audit(AuditType.Save, userId, item.Id);
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            EventMessages eventMessages = EventMessagesFactory.Get();
+            SavingNotification savingNotification = GetSavingNotification(itemsA, eventMessages);
+            if (scope.Notifications.PublishCancelable(savingNotification))
+            {
                 scope.Complete();
+                return;
             }
-        }
 
-        public void Save(IEnumerable items, int userId = Cms.Core.Constants.Security.SuperUserId)
-        {
-            TItem[] itemsA = items.ToArray();
+            scope.WriteLock(WriteLockIds);
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            // all-or-nothing, validate them all first
+            foreach (TItem contentType in itemsA)
             {
-                EventMessages eventMessages = EventMessagesFactory.Get();
-                SavingNotification savingNotification = GetSavingNotification(itemsA, eventMessages);
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
-
-                scope.WriteLock(WriteLockIds);
+                ValidateLocked(contentType); // throws if invalid
+            }
 
-                // all-or-nothing, validate them all first
-                foreach (TItem contentType in itemsA)
+            foreach (TItem contentType in itemsA)
+            {
+                contentType.CreatorId = userId;
+                if (contentType.Description == string.Empty)
                 {
-                    ValidateLocked(contentType); // throws if invalid
+                    contentType.Description = null;
                 }
-                foreach (TItem contentType in itemsA)
-                {
-                    contentType.CreatorId = userId;
-                    if (contentType.Description == string.Empty)
-                    {
-                        contentType.Description = null;
-                    }
 
-                    Repository.Save(contentType);
-                }
+                Repository.Save(contentType);
+            }
 
-                // figure out impacted content types
-                ContentTypeChange[] changes = ComposeContentTypeChanges(itemsA).ToArray();
+            // figure out impacted content types
+            ContentTypeChange[] changes = ComposeContentTypeChanges(itemsA).ToArray();
 
-                // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info.
-                _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages)); ;
+            // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info.
+            _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages));
+            ;
 
-                scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages));
+            scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages));
 
-                SavedNotification savedNotification = GetSavedNotification(itemsA, eventMessages);
-                savedNotification.WithStateFrom(savingNotification);
-                scope.Notifications.Publish(savedNotification);
+            SavedNotification savedNotification = GetSavedNotification(itemsA, eventMessages);
+            savedNotification.WithStateFrom(savingNotification);
+            scope.Notifications.Publish(savedNotification);
 
-                Audit(AuditType.Save, userId, -1);
-                scope.Complete();
-            }
+            Audit(AuditType.Save, userId, -1);
+            scope.Complete();
         }
+    }
 
-        #endregion
+    #endregion
 
-        #region Delete
+    #region Delete
 
-        public void Delete(TItem item, int userId = Cms.Core.Constants.Security.SuperUserId)
+    public void Delete(TItem item, int userId = Constants.Security.SuperUserId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            EventMessages eventMessages = EventMessagesFactory.Get();
+            DeletingNotification deletingNotification = GetDeletingNotification(item, eventMessages);
+            if (scope.Notifications.PublishCancelable(deletingNotification))
             {
-                EventMessages eventMessages = EventMessagesFactory.Get();
-                DeletingNotification deletingNotification = GetDeletingNotification(item, eventMessages);
-                if (scope.Notifications.PublishCancelable(deletingNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
-
-                scope.WriteLock(WriteLockIds);
-
-                // all descendants are going to be deleted
-                TItem[] descendantsAndSelf = GetDescendants(item.Id, true)
-                    .ToArray();
-                TItem[] deleted = descendantsAndSelf;
+                scope.Complete();
+                return;
+            }
 
-                // all impacted (through composition) probably lose some properties
-                // don't try to be too clever here, just report them all
-                // do this before anything is deleted
-                TItem[] changed = descendantsAndSelf.SelectMany(xx => GetComposedOf(xx.Id))
-                    .Distinct()
-                    .Except(descendantsAndSelf)
-                    .ToArray();
+            scope.WriteLock(WriteLockIds);
 
-                // delete content
-                DeleteItemsOfTypes(descendantsAndSelf.Select(x => x.Id));
+            // all descendants are going to be deleted
+            TItem[] descendantsAndSelf = GetDescendants(item.Id, true)
+                .ToArray();
+            TItem[] deleted = descendantsAndSelf;
+
+            // all impacted (through composition) probably lose some properties
+            // don't try to be too clever here, just report them all
+            // do this before anything is deleted
+            TItem[] changed = descendantsAndSelf.SelectMany(xx => GetComposedOf(xx.Id))
+                .Distinct()
+                .Except(descendantsAndSelf)
+                .ToArray();
 
-                // Next find all other document types that have a reference to this content type
-                IEnumerable referenceToAllowedContentTypes = GetAll().Where(q => q.AllowedContentTypes?.Any(p=>p.Id.Value==item.Id) ?? false);
-                foreach (TItem reference in referenceToAllowedContentTypes)
-                {
-                    reference.AllowedContentTypes = reference.AllowedContentTypes?.Where(p => p.Id.Value != item.Id);
-                    var changedRef = new List>() { new ContentTypeChange(reference, ContentTypeChangeTypes.RefreshMain) };
-                    // Fire change event
-                    scope.Notifications.Publish(GetContentTypeChangedNotification(changedRef, eventMessages));
-                }
+            // delete content
+            DeleteItemsOfTypes(descendantsAndSelf.Select(x => x.Id));
 
-                // finally delete the content type
-                // - recursively deletes all descendants
-                // - deletes all associated property data
-                //  (contents of any descendant type have been deleted but
-                //   contents of any composed (impacted) type remain but
-                //   need to have their property data cleared)
-                Repository.Delete(item);
+            // Next find all other document types that have a reference to this content type
+            IEnumerable referenceToAllowedContentTypes =
+                GetAll().Where(q => q.AllowedContentTypes?.Any(p => p.Id.Value == item.Id) ?? false);
+            foreach (TItem reference in referenceToAllowedContentTypes)
+            {
+                reference.AllowedContentTypes = reference.AllowedContentTypes?.Where(p => p.Id.Value != item.Id);
+                var changedRef =
+                    new List> {new(reference, ContentTypeChangeTypes.RefreshMain)};
+                // Fire change event
+                scope.Notifications.Publish(GetContentTypeChangedNotification(changedRef, eventMessages));
+            }
 
-                ContentTypeChange[] changes = descendantsAndSelf.Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.Remove))
-                    .Concat(changed.Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther)))
-                    .ToArray();
+            // finally delete the content type
+            // - recursively deletes all descendants
+            // - deletes all associated property data
+            //  (contents of any descendant type have been deleted but
+            //   contents of any composed (impacted) type remain but
+            //   need to have their property data cleared)
+            Repository.Delete(item);
+
+            ContentTypeChange[] changes = descendantsAndSelf
+                .Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.Remove))
+                .Concat(changed.Select(x =>
+                    new ContentTypeChange(x,
+                        ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther)))
+                .ToArray();
 
-                // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info.
-                _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages));
+            // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info.
+            _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages));
 
-                scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages));
+            scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages));
 
-                DeletedNotification deletedNotification = GetDeletedNotification(deleted.DistinctBy(x => x.Id), eventMessages);
-                deletedNotification.WithStateFrom(deletingNotification);
-                scope.Notifications.Publish(deletedNotification);
+            DeletedNotification deletedNotification =
+                GetDeletedNotification(deleted.DistinctBy(x => x.Id), eventMessages);
+            deletedNotification.WithStateFrom(deletingNotification);
+            scope.Notifications.Publish(deletedNotification);
 
-                Audit(AuditType.Delete, userId, item.Id);
-                scope.Complete();
-            }
+            Audit(AuditType.Delete, userId, item.Id);
+            scope.Complete();
         }
+    }
 
-        public void Delete(IEnumerable items, int userId = Cms.Core.Constants.Security.SuperUserId)
-        {
-            TItem[] itemsA = items.ToArray();
+    public void Delete(IEnumerable items, int userId = Constants.Security.SuperUserId)
+    {
+        TItem[] itemsA = items.ToArray();
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            EventMessages eventMessages = EventMessagesFactory.Get();
+            DeletingNotification deletingNotification = GetDeletingNotification(itemsA, eventMessages);
+            if (scope.Notifications.PublishCancelable(deletingNotification))
             {
-                EventMessages eventMessages = EventMessagesFactory.Get();
-                DeletingNotification deletingNotification = GetDeletingNotification(itemsA, eventMessages);
-                if (scope.Notifications.PublishCancelable(deletingNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
+                scope.Complete();
+                return;
+            }
 
-                scope.WriteLock(WriteLockIds);
+            scope.WriteLock(WriteLockIds);
 
-                // all descendants are going to be deleted
-                TItem[] allDescendantsAndSelf = itemsA.SelectMany(xx => GetDescendants(xx.Id, true)).DistinctBy(x => x.Id).ToArray();
-                TItem[] deleted = allDescendantsAndSelf;
+            // all descendants are going to be deleted
+            TItem[] allDescendantsAndSelf =
+                itemsA.SelectMany(xx => GetDescendants(xx.Id, true)).DistinctBy(x => x.Id).ToArray();
+            TItem[] deleted = allDescendantsAndSelf;
 
-                // all impacted (through composition) probably lose some properties
-                // don't try to be too clever here, just report them all
-                // do this before anything is deleted
-                TItem[] changed = allDescendantsAndSelf.SelectMany(x => GetComposedOf(x.Id))
-                    .Distinct()
-                    .Except(allDescendantsAndSelf)
-                    .ToArray();
+            // all impacted (through composition) probably lose some properties
+            // don't try to be too clever here, just report them all
+            // do this before anything is deleted
+            TItem[] changed = allDescendantsAndSelf.SelectMany(x => GetComposedOf(x.Id))
+                .Distinct()
+                .Except(allDescendantsAndSelf)
+                .ToArray();
 
-                // delete content
-                DeleteItemsOfTypes(allDescendantsAndSelf.Select(x => x.Id));
+            // delete content
+            DeleteItemsOfTypes(allDescendantsAndSelf.Select(x => x.Id));
 
-                // finally delete the content types
-                // (see notes in overload)
-                foreach (TItem item in itemsA)
-                {
-                    Repository.Delete(item);
-                }
+            // finally delete the content types
+            // (see notes in overload)
+            foreach (TItem item in itemsA)
+            {
+                Repository.Delete(item);
+            }
 
-                ContentTypeChange[] changes = allDescendantsAndSelf.Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.Remove))
-                    .Concat(changed.Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther)))
-                    .ToArray();
+            ContentTypeChange[] changes = allDescendantsAndSelf
+                .Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.Remove))
+                .Concat(changed.Select(x =>
+                    new ContentTypeChange(x,
+                        ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther)))
+                .ToArray();
 
-                // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info.
-                _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages));
+            // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info.
+            _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages));
 
-                scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages));
+            scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages));
 
-                DeletedNotification deletedNotification = GetDeletedNotification(deleted.DistinctBy(x => x.Id), eventMessages);
-                deletedNotification.WithStateFrom(deletingNotification);
-                scope.Notifications.Publish(deletedNotification);
+            DeletedNotification deletedNotification =
+                GetDeletedNotification(deleted.DistinctBy(x => x.Id), eventMessages);
+            deletedNotification.WithStateFrom(deletingNotification);
+            scope.Notifications.Publish(deletedNotification);
 
-                Audit(AuditType.Delete, userId, -1);
-                scope.Complete();
-            }
+            Audit(AuditType.Delete, userId, -1);
+            scope.Complete();
         }
+    }
 
-        protected abstract void DeleteItemsOfTypes(IEnumerable typeIds);
+    protected abstract void DeleteItemsOfTypes(IEnumerable typeIds);
 
-        #endregion
+    #endregion
 
-        #region Copy
+    #region Copy
 
-        public TItem Copy(TItem original, string alias, string name, int parentId = -1)
+    public TItem Copy(TItem original, string alias, string name, int parentId = -1)
+    {
+        TItem? parent = null;
+        if (parentId > 0)
         {
-            TItem? parent = null;
-            if (parentId > 0)
+            parent = Get(parentId);
+            if (parent == null)
             {
-                parent = Get(parentId);
-                if (parent == null)
-                {
-                    throw new InvalidOperationException("Could not find parent with id " + parentId);
-                }
+                throw new InvalidOperationException("Could not find parent with id " + parentId);
             }
-            return Copy(original, alias, name, parent);
         }
 
-        public TItem Copy(TItem original, string alias, string name, TItem? parent)
-        {
-            if (original == null) throw new ArgumentNullException(nameof(original));
-            if (alias == null) throw new ArgumentNullException(nameof(alias));
-            if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(alias));
-            if (parent != null && parent.HasIdentity == false) throw new InvalidOperationException("Parent must have an identity.");
-
-            // this is illegal
-            //var originalb = (ContentTypeCompositionBase)original;
-            // but we *know* it has to be a ContentTypeCompositionBase anyways
-            var originalb = (ContentTypeCompositionBase) (object) original;
-            var clone = (TItem) (object) originalb.DeepCloneWithResetIdentities(alias);
-
-            clone.Name = name;
-
-            //remove all composition that is not it's current alias
-            var compositionAliases = clone.CompositionAliases().Except(new[] { alias }).ToList();
-            foreach (var a in compositionAliases)
-            {
-                clone.RemoveContentType(a);
-            }
-
-            //if a parent is specified set it's composition and parent
-            if (parent != null)
-            {
-                //add a new parent composition
-                clone.AddContentType(parent);
-                clone.ParentId = parent.Id;
-            }
-            else
-            {
-                //set to root
-                clone.ParentId = -1;
-            }
+        return Copy(original, alias, name, parent);
+    }
 
-            Save(clone);
-            return clone;
+    public TItem Copy(TItem original, string alias, string name, TItem? parent)
+    {
+        if (original == null)
+        {
+            throw new ArgumentNullException(nameof(original));
         }
 
-        public Attempt?> Copy(TItem copying, int containerId)
+        if (alias == null)
         {
-            var evtMsgs = EventMessagesFactory.Get();
+            throw new ArgumentNullException(nameof(alias));
+        }
 
-            TItem copy;
-            using (var scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(WriteLockIds);
+        if (string.IsNullOrWhiteSpace(alias))
+        {
+            throw new ArgumentException("Value can't be empty or consist only of white-space characters.",
+                nameof(alias));
+        }
 
-                try
-                {
-                    if (containerId > 0)
-                    {
-                        var container = _containerRepository?.Get(containerId);
-                        if (container == null)
-                            throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); // causes rollback
-                    }
-                    var alias = Repository.GetUniqueAlias(copying.Alias);
+        if (parent != null && parent.HasIdentity == false)
+        {
+            throw new InvalidOperationException("Parent must have an identity.");
+        }
 
-                    // this is illegal
-                    //var copyingb = (ContentTypeCompositionBase) copying;
-                    // but we *know* it has to be a ContentTypeCompositionBase anyways
-                    var copyingb = (ContentTypeCompositionBase) (object)copying;
-                    copy = (TItem) (object) copyingb.DeepCloneWithResetIdentities(alias);
+        // this is illegal
+        //var originalb = (ContentTypeCompositionBase)original;
+        // but we *know* it has to be a ContentTypeCompositionBase anyways
+        var originalb = (ContentTypeCompositionBase)(object)original;
+        var clone = (TItem)(object)originalb.DeepCloneWithResetIdentities(alias);
 
-                    copy.Name = copy.Name + " (copy)"; // might not be unique
+        clone.Name = name;
 
-                    // if it has a parent, and the parent is a content type, unplug composition
-                    // all other compositions remain in place in the copied content type
-                    if (copy.ParentId > 0)
-                    {
-                        var parent = Repository.Get(copy.ParentId);
-                        if (parent != null)
-                            copy.RemoveContentType(parent.Alias);
-                    }
-
-                    copy.ParentId = containerId;
-                    Repository.Save(copy);
-                    scope.Complete();
-                }
-                catch (DataOperationException ex)
-                {
-                    return OperationResult.Attempt.Fail(ex.Operation, evtMsgs); // causes rollback
-                }
-            }
+        //remove all composition that is not it's current alias
+        var compositionAliases = clone.CompositionAliases().Except(new[] {alias}).ToList();
+        foreach (var a in compositionAliases)
+        {
+            clone.RemoveContentType(a);
+        }
 
-            return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs, copy);
+        //if a parent is specified set it's composition and parent
+        if (parent != null)
+        {
+            //add a new parent composition
+            clone.AddContentType(parent);
+            clone.ParentId = parent.Id;
+        }
+        else
+        {
+            //set to root
+            clone.ParentId = -1;
         }
 
-        #endregion
+        Save(clone);
+        return clone;
+    }
 
-        #region Move
+    public Attempt?> Copy(TItem copying, int containerId)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
 
-        public Attempt?> Move(TItem moving, int containerId)
+        TItem copy;
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            EventMessages eventMessages = EventMessagesFactory.Get();
+            scope.WriteLock(WriteLockIds);
 
-            var moveInfo = new List>();
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            try
             {
-                var moveEventInfo = new MoveEventInfo(moving, moving.Path, containerId);
-                MovingNotification movingNotification = GetMovingNotification(moveEventInfo, eventMessages);
-                if (scope.Notifications.PublishCancelable(movingNotification))
+                if (containerId > 0)
                 {
-                    scope.Complete();
-                    return OperationResult.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, eventMessages);
+                    EntityContainer container = _containerRepository?.Get(containerId);
+                    if (container == null)
+                    {
+                        throw new DataOperationException(MoveOperationStatusType
+                            .FailedParentNotFound); // causes rollback
+                    }
                 }
 
-                scope.WriteLock(WriteLockIds); // also for containers
+                var alias = Repository.GetUniqueAlias(copying.Alias);
+
+                // this is illegal
+                //var copyingb = (ContentTypeCompositionBase) copying;
+                // but we *know* it has to be a ContentTypeCompositionBase anyways
+                var copyingb = (ContentTypeCompositionBase)(object)copying;
+                copy = (TItem)(object)copyingb.DeepCloneWithResetIdentities(alias);
 
-                try
+                copy.Name = copy.Name + " (copy)"; // might not be unique
+
+                // if it has a parent, and the parent is a content type, unplug composition
+                // all other compositions remain in place in the copied content type
+                if (copy.ParentId > 0)
                 {
-                    EntityContainer? container = null;
-                    if (containerId > 0)
+                    TItem parent = Repository.Get(copy.ParentId);
+                    if (parent != null)
                     {
-                        container = _containerRepository?.Get(containerId);
-                        if (container == null)
-                            throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); // causes rollback
+                        copy.RemoveContentType(parent.Alias);
                     }
-                    moveInfo.AddRange(Repository.Move(moving, container!));
-                    scope.Complete();
-                }
-                catch (DataOperationException ex)
-                {
-                    scope.Complete();
-                    return OperationResult.Attempt.Fail(ex.Operation, eventMessages);
                 }
 
-                // note: not raising any Changed event here because moving a content type under another container
-                // has no impact on the published content types - would be entirely different if we were to support
-                // moving a content type under another content type.
-                MovedNotification movedNotification = GetMovedNotification(moveInfo, eventMessages);
-                movedNotification.WithStateFrom(movingNotification);
-                scope.Notifications.Publish(movedNotification);
+                copy.ParentId = containerId;
+                Repository.Save(copy);
+                scope.Complete();
+            }
+            catch (DataOperationException ex)
+            {
+                return OperationResult.Attempt.Fail(ex.Operation,
+                    evtMsgs); // causes rollback
             }
-
-            return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, eventMessages);
         }
 
-        #endregion
-
-        #region Containers
-
-        protected abstract Guid ContainedObjectType { get; }
-
-        protected Guid ContainerObjectType => EntityContainer.GetContainerObjectType(ContainedObjectType);
+        return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs, copy);
+    }
 
-        public Attempt?> CreateContainer(int parentId, Guid key, string name, int userId = Cms.Core.Constants.Security.SuperUserId)
-        {
-            EventMessages eventMessages = EventMessagesFactory.Get();
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(WriteLockIds); // also for containers
+    #endregion
 
-                try
-                {
-                    var container = new EntityContainer(ContainedObjectType)
-                    {
-                        Name = name,
-                        ParentId = parentId,
-                        CreatorId = userId,
-                        Key = key
-                    };
-
-                    var savingNotification = new EntityContainerSavingNotification(container, eventMessages);
-                    if (scope.Notifications.PublishCancelable(savingNotification))
-                    {
-                        scope.Complete();
-                        return OperationResult.Attempt.Cancel(eventMessages, container);
-                    }
+    #region Containers
 
-                    _containerRepository?.Save(container);
-                    scope.Complete();
+    protected abstract Guid ContainedObjectType { get; }
 
-                    var savedNotification = new EntityContainerSavedNotification(container, eventMessages);
-                    savedNotification.WithStateFrom(savingNotification);
-                    scope.Notifications.Publish(savedNotification);
-                    // TODO: Audit trail ?
+    protected Guid ContainerObjectType => EntityContainer.GetContainerObjectType(ContainedObjectType);
 
-                    return OperationResult.Attempt.Succeed(eventMessages, container);
-                }
-                catch (Exception ex)
-                {
-                    scope.Complete();
-                    return OperationResult.Attempt.Fail(OperationResultType.FailedCancelledByEvent, eventMessages, ex);
-                }
-            }
-        }
-
-        public Attempt SaveContainer(EntityContainer container, int userId = Cms.Core.Constants.Security.SuperUserId)
+    public Attempt?> CreateContainer(int parentId, Guid key,
+        string name, int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages eventMessages = EventMessagesFactory.Get();
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            EventMessages eventMessages = EventMessagesFactory.Get();
-
-            Guid containerObjectType = ContainerObjectType;
-            if (container.ContainerObjectType != containerObjectType)
-            {
-                var ex = new InvalidOperationException("Not a container of the proper type.");
-                return OperationResult.Attempt.Fail(eventMessages, ex);
-            }
+            scope.WriteLock(WriteLockIds); // also for containers
 
-            if (container.HasIdentity && container.IsPropertyDirty("ParentId"))
+            try
             {
-                var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead.");
-                return OperationResult.Attempt.Fail(eventMessages, ex);
-            }
+                var container = new EntityContainer(ContainedObjectType)
+                {
+                    Name = name, ParentId = parentId, CreatorId = userId, Key = key
+                };
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
                 var savingNotification = new EntityContainerSavingNotification(container, eventMessages);
                 if (scope.Notifications.PublishCancelable(savingNotification))
                 {
                     scope.Complete();
-                    return OperationResult.Attempt.Cancel(eventMessages);
+                    return OperationResult.Attempt.Cancel(eventMessages, container);
                 }
 
-                scope.WriteLock(WriteLockIds); // also for containers
-
                 _containerRepository?.Save(container);
                 scope.Complete();
 
                 var savedNotification = new EntityContainerSavedNotification(container, eventMessages);
                 savedNotification.WithStateFrom(savingNotification);
                 scope.Notifications.Publish(savedNotification);
+                // TODO: Audit trail ?
+
+                return OperationResult.Attempt.Succeed(eventMessages, container);
+            }
+            catch (Exception ex)
+            {
+                scope.Complete();
+                return OperationResult.Attempt.Fail(
+                    OperationResultType.FailedCancelledByEvent, eventMessages, ex);
             }
+        }
+    }
 
-            // TODO: Audit trail ?
+    public Attempt SaveContainer(EntityContainer container,
+        int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages eventMessages = EventMessagesFactory.Get();
 
-            return OperationResult.Attempt.Succeed(eventMessages);
+        Guid containerObjectType = ContainerObjectType;
+        if (container.ContainerObjectType != containerObjectType)
+        {
+            var ex = new InvalidOperationException("Not a container of the proper type.");
+            return OperationResult.Attempt.Fail(eventMessages, ex);
         }
 
-        public EntityContainer? GetContainer(int containerId)
+        if (container.HasIdentity && container.IsPropertyDirty("ParentId"))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(ReadLockIds); // also for containers
-
-                return _containerRepository.Get(containerId);
-            }
+            var ex = new InvalidOperationException(
+                "Cannot save a container with a modified parent, move the container instead.");
+            return OperationResult.Attempt.Fail(eventMessages, ex);
         }
 
-        public EntityContainer? GetContainer(Guid containerId)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+            var savingNotification = new EntityContainerSavingNotification(container, eventMessages);
+            if (scope.Notifications.PublishCancelable(savingNotification))
             {
-                scope.ReadLock(ReadLockIds); // also for containers
-
-                return _containerRepository.Get(containerId);
+                scope.Complete();
+                return OperationResult.Attempt.Cancel(eventMessages);
             }
-        }
 
-        public IEnumerable GetContainers(int[] containerIds)
-        {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(ReadLockIds); // also for containers
+            scope.WriteLock(WriteLockIds); // also for containers
 
-                return _containerRepository.GetMany(containerIds);
-            }
+            _containerRepository?.Save(container);
+            scope.Complete();
+
+            var savedNotification = new EntityContainerSavedNotification(container, eventMessages);
+            savedNotification.WithStateFrom(savingNotification);
+            scope.Notifications.Publish(savedNotification);
         }
 
-        public IEnumerable GetContainers(TItem item)
+        // TODO: Audit trail ?
+
+        return OperationResult.Attempt.Succeed(eventMessages);
+    }
+
+    public EntityContainer? GetContainer(int containerId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            var ancestorIds = item.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)
-                .Select(x => int.TryParse(x, NumberStyles.Integer, CultureInfo.InvariantCulture, out var asInt) ? asInt : int.MinValue)
-                .Where(x => x != int.MinValue && x != item.Id)
-                .ToArray();
+            scope.ReadLock(ReadLockIds); // also for containers
 
-            return GetContainers(ancestorIds);
+            return _containerRepository.Get(containerId);
         }
+    }
 
-        public IEnumerable GetContainers(string name, int level)
+    public EntityContainer? GetContainer(Guid containerId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(ReadLockIds); // also for containers
+            scope.ReadLock(ReadLockIds); // also for containers
 
-                return _containerRepository.Get(name, level);
-            }
+            return _containerRepository.Get(containerId);
         }
+    }
 
-        public Attempt DeleteContainer(int containerId, int userId = Cms.Core.Constants.Security.SuperUserId)
+    public IEnumerable GetContainers(int[] containerIds)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            EventMessages eventMessages = EventMessagesFactory.Get();
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(WriteLockIds); // also for containers
+            scope.ReadLock(ReadLockIds); // also for containers
 
-                EntityContainer? container = _containerRepository?.Get(containerId);
-                if (container == null)
-                {
-                    return OperationResult.Attempt.NoOperation(eventMessages);
-                }
+            return _containerRepository.GetMany(containerIds);
+        }
+    }
 
-                // 'container' here does not know about its children, so we need
-                // to get it again from the entity repository, as a light entity
-                IEntitySlim? entity = _entityRepository.Get(container.Id);
-                if (entity?.HasChildren ?? false)
-                {
-                    scope.Complete();
-                    return Attempt.Fail(new OperationResult(OperationResultType.FailedCannot, eventMessages));
-                }
+    public IEnumerable GetContainers(TItem item)
+    {
+        var ancestorIds = item.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)
+            .Select(x =>
+                int.TryParse(x, NumberStyles.Integer, CultureInfo.InvariantCulture, out var asInt)
+                    ? asInt
+                    : int.MinValue)
+            .Where(x => x != int.MinValue && x != item.Id)
+            .ToArray();
+
+        return GetContainers(ancestorIds);
+    }
 
-                var deletingNotification = new EntityContainerDeletingNotification(container, eventMessages);
-                if (scope.Notifications.PublishCancelable(deletingNotification))
-                {
-                    scope.Complete();
-                    return Attempt.Fail(new OperationResult(OperationResultType.FailedCancelledByEvent, eventMessages));
-                }
+    public IEnumerable GetContainers(string name, int level)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            scope.ReadLock(ReadLockIds); // also for containers
 
-                _containerRepository?.Delete(container);
-                scope.Complete();
+            return _containerRepository.Get(name, level);
+        }
+    }
 
-                var deletedNotification = new EntityContainerDeletedNotification(container, eventMessages);
-                deletedNotification.WithStateFrom(deletingNotification);
-                scope.Notifications.Publish(deletedNotification);
+    public Attempt DeleteContainer(int containerId, int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages eventMessages = EventMessagesFactory.Get();
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            scope.WriteLock(WriteLockIds); // also for containers
 
-                return OperationResult.Attempt.Succeed(eventMessages);
-                // TODO: Audit trail ?
+            EntityContainer? container = _containerRepository?.Get(containerId);
+            if (container == null)
+            {
+                return OperationResult.Attempt.NoOperation(eventMessages);
             }
-        }
 
-        public Attempt?> RenameContainer(int id, string name, int userId = Cms.Core.Constants.Security.SuperUserId)
-        {
-            EventMessages eventMessages = EventMessagesFactory.Get();
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            // 'container' here does not know about its children, so we need
+            // to get it again from the entity repository, as a light entity
+            IEntitySlim? entity = _entityRepository.Get(container.Id);
+            if (entity?.HasChildren ?? false)
             {
-                scope.WriteLock(WriteLockIds); // also for containers
+                scope.Complete();
+                return Attempt.Fail(new OperationResult(OperationResultType.FailedCannot, eventMessages));
+            }
 
-                try
-                {
-                    EntityContainer? container = _containerRepository?.Get(id);
+            var deletingNotification = new EntityContainerDeletingNotification(container, eventMessages);
+            if (scope.Notifications.PublishCancelable(deletingNotification))
+            {
+                scope.Complete();
+                return Attempt.Fail(new OperationResult(OperationResultType.FailedCancelledByEvent, eventMessages));
+            }
 
-                    //throw if null, this will be caught by the catch and a failed returned
-                    if (container == null)
-                    {
-                        throw new InvalidOperationException("No container found with id " + id);
-                    }
+            _containerRepository?.Delete(container);
+            scope.Complete();
 
-                    container.Name = name;
+            var deletedNotification = new EntityContainerDeletedNotification(container, eventMessages);
+            deletedNotification.WithStateFrom(deletingNotification);
+            scope.Notifications.Publish(deletedNotification);
 
-                    var renamingNotification = new EntityContainerRenamingNotification(container, eventMessages);
-                    if (scope.Notifications.PublishCancelable(renamingNotification))
-                    {
-                        scope.Complete();
-                        return OperationResult.Attempt.Cancel(eventMessages);
-                    }
+            return OperationResult.Attempt.Succeed(eventMessages);
+            // TODO: Audit trail ?
+        }
+    }
 
-                    _containerRepository?.Save(container);
-                    scope.Complete();
+    public Attempt?> RenameContainer(int id, string name,
+        int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages eventMessages = EventMessagesFactory.Get();
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            scope.WriteLock(WriteLockIds); // also for containers
 
-                    var renamedNotification = new EntityContainerRenamedNotification(container, eventMessages);
-                    renamedNotification.WithStateFrom(renamingNotification);
-                    scope.Notifications.Publish(renamedNotification);
+            try
+            {
+                EntityContainer? container = _containerRepository?.Get(id);
 
-                    return OperationResult.Attempt.Succeed(OperationResultType.Success, eventMessages, container);
-                }
-                catch (Exception ex)
+                //throw if null, this will be caught by the catch and a failed returned
+                if (container == null)
                 {
-                    return OperationResult.Attempt.Fail(eventMessages, ex);
+                    throw new InvalidOperationException("No container found with id " + id);
                 }
-            }
-        }
-
-        #endregion
 
-        #region Audit
+                container.Name = name;
 
-        private void Audit(AuditType type, int userId, int objectId)
-        {
-            _auditRepository.Save(new AuditItem(objectId, type, userId,
-                ObjectTypes.GetUmbracoObjectType(ContainedObjectType).GetName()));
-        }
+                var renamingNotification = new EntityContainerRenamingNotification(container, eventMessages);
+                if (scope.Notifications.PublishCancelable(renamingNotification))
+                {
+                    scope.Complete();
+                    return OperationResult.Attempt.Cancel(eventMessages);
+                }
 
-        #endregion
+                _containerRepository?.Save(container);
+                scope.Complete();
 
+                var renamedNotification = new EntityContainerRenamedNotification(container, eventMessages);
+                renamedNotification.WithStateFrom(renamingNotification);
+                scope.Notifications.Publish(renamedNotification);
 
+                return OperationResult.Attempt.Succeed(OperationResultType.Success, eventMessages, container);
+            }
+            catch (Exception ex)
+            {
+                return OperationResult.Attempt.Fail(eventMessages, ex);
+            }
+        }
     }
+
+    #endregion
 }
diff --git a/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs b/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs
index 6c30b24b67e3..cfa1281513cd 100644
--- a/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs
+++ b/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs
@@ -1,188 +1,206 @@
 // Copyright (c) Umbraco.
 // See LICENSE for more details.
 
-using System;
-using System.Collections.Generic;
-using System.Linq;
 using Umbraco.Cms.Core;
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Services;
 
-namespace Umbraco.Extensions
+namespace Umbraco.Extensions;
+
+public static class ContentTypeServiceExtensions
 {
-    public static class ContentTypeServiceExtensions
+    /// 
+    ///     Gets all of the element types (e.g. content types that have been marked as an element type).
+    /// 
+    /// The content type service.
+    /// Returns all the element types.
+    public static IEnumerable GetAllElementTypes(this IContentTypeService contentTypeService)
     {
-        /// 
-        /// Gets all of the element types (e.g. content types that have been marked as an element type).
-        /// 
-        /// The content type service.
-        /// Returns all the element types.
-        public static IEnumerable GetAllElementTypes(this IContentTypeService contentTypeService)
+        if (contentTypeService == null)
         {
-            if (contentTypeService == null)
-            {
-                return Enumerable.Empty();
-            }
+            return Enumerable.Empty();
+        }
 
-            return contentTypeService.GetAll().Where(x => x.IsElement);
+        return contentTypeService.GetAll().Where(x => x.IsElement);
+    }
+
+    /// 
+    ///     Returns the available composite content types for a given content type
+    /// 
+    /// 
+    /// 
+    ///     This is normally an empty list but if additional content type aliases are passed in, any content types containing
+    ///     those aliases will be filtered out
+    ///     along with any content types that have matching property types that are included in the filtered content types
+    /// 
+    /// 
+    /// 
+    /// 
+    ///     This is normally an empty list but if additional property type aliases are passed in, any content types that have
+    ///     these aliases will be filtered out.
+    ///     This is required because in the case of creating/modifying a content type because new property types being added to
+    ///     it are not yet persisted so cannot
+    ///     be looked up via the db, they need to be passed in.
+    /// 
+    /// Whether the composite content types should be applicable for an element type
+    /// 
+    public static ContentTypeAvailableCompositionsResults GetAvailableCompositeContentTypes(
+        this IContentTypeService ctService,
+        IContentTypeComposition? source,
+        IContentTypeComposition[] allContentTypes,
+        string[]? filterContentTypes = null,
+        string[]? filterPropertyTypes = null,
+        bool isElement = false)
+    {
+        filterContentTypes = filterContentTypes == null
+            ? Array.Empty()
+            : filterContentTypes.Where(x => !x.IsNullOrWhiteSpace()).ToArray();
+
+        filterPropertyTypes = filterPropertyTypes == null
+            ? Array.Empty()
+            : filterPropertyTypes.Where(x => !x.IsNullOrWhiteSpace()).ToArray();
+
+        //create the full list of property types to use as the filter
+        //this is the combination of all property type aliases found in the content types passed in for the filter
+        //as well as the specific property types passed in for the filter
+        filterPropertyTypes = allContentTypes
+            .Where(c => filterContentTypes.InvariantContains(c.Alias))
+            .SelectMany(c => c.PropertyTypes)
+            .Select(c => c.Alias)
+            .Union(filterPropertyTypes)
+            .ToArray();
+
+        var sourceId = source?.Id ?? 0;
+
+        // find out if any content type uses this content type
+        IContentTypeComposition[] isUsing =
+            allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == sourceId)).ToArray();
+        if (isUsing.Length > 0)
+        {
+            //if already in use a composition, do not allow any composited types
+            return new ContentTypeAvailableCompositionsResults();
         }
 
-        /// 
-        /// Returns the available composite content types for a given content type
-        /// 
-        /// 
-        /// 
-        /// This is normally an empty list but if additional content type aliases are passed in, any content types containing those aliases will be filtered out
-        /// along with any content types that have matching property types that are included in the filtered content types
-        /// 
-        /// 
-        /// 
-        /// 
-        /// This is normally an empty list but if additional property type aliases are passed in, any content types that have these aliases will be filtered out.
-        /// This is required because in the case of creating/modifying a content type because new property types being added to it are not yet persisted so cannot
-        /// be looked up via the db, they need to be passed in.
-        /// 
-        /// Whether the composite content types should be applicable for an element type
-        /// 
-        public static ContentTypeAvailableCompositionsResults GetAvailableCompositeContentTypes(this IContentTypeService ctService,
-            IContentTypeComposition? source,
-            IContentTypeComposition[] allContentTypes,
-            string[]? filterContentTypes = null,
-            string[]? filterPropertyTypes = null,
-            bool isElement = false)
+        // if it is not used then composition is possible
+        // hashset guarantees uniqueness on Id
+        var list = new HashSet(new DelegateEqualityComparer(
+            (x, y) => x?.Id == y?.Id,
+            x => x.Id));
+
+        // usable types are those that are top-level
+        // do not allow element types to be composed by non-element types as this will break the model generation in ModelsBuilder
+        IContentTypeComposition[] usableContentTypes = allContentTypes
+            .Where(x => x.ContentTypeComposition.Any() == false && (isElement == false || x.IsElement)).ToArray();
+        foreach (IContentTypeComposition x in usableContentTypes)
         {
-            filterContentTypes = filterContentTypes == null
-                ? Array.Empty()
-                : filterContentTypes.Where(x => !x.IsNullOrWhiteSpace()).ToArray();
-
-            filterPropertyTypes = filterPropertyTypes == null
-                ? Array.Empty()
-                : filterPropertyTypes.Where(x => !x.IsNullOrWhiteSpace()).ToArray();
-
-            //create the full list of property types to use as the filter
-            //this is the combination of all property type aliases found in the content types passed in for the filter
-            //as well as the specific property types passed in for the filter
-            filterPropertyTypes = allContentTypes
-                    .Where(c => filterContentTypes.InvariantContains(c.Alias))
-                    .SelectMany(c => c.PropertyTypes)
-                    .Select(c => c.Alias)
-                    .Union(filterPropertyTypes)
-                    .ToArray();
-
-            var sourceId = source?.Id ?? 0;
-
-            // find out if any content type uses this content type
-            var isUsing = allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == sourceId)).ToArray();
-            if (isUsing.Length > 0)
-            {
-                //if already in use a composition, do not allow any composited types
-                return new ContentTypeAvailableCompositionsResults();
-            }
+            list.Add(x);
+        }
+
+        // indirect types are those that we use, directly or indirectly
+        IContentTypeComposition[] indirectContentTypes = GetDirectOrIndirect(source).ToArray();
+        foreach (IContentTypeComposition x in indirectContentTypes)
+        {
+            list.Add(x);
+        }
+
+        //At this point we have a list of content types that 'could' be compositions
 
-            // if it is not used then composition is possible
-            // hashset guarantees uniqueness on Id
-            var list = new HashSet(new DelegateEqualityComparer(
-                (x, y) => x?.Id == y?.Id,
-                x => x.Id));
-
-            // usable types are those that are top-level
-            // do not allow element types to be composed by non-element types as this will break the model generation in ModelsBuilder
-            var usableContentTypes = allContentTypes
-                .Where(x => x.ContentTypeComposition.Any() == false && (isElement == false || x.IsElement)).ToArray();
-            foreach (var x in usableContentTypes)
-                list.Add(x);
-
-            // indirect types are those that we use, directly or indirectly
-            var indirectContentTypes = GetDirectOrIndirect(source).ToArray();
-            foreach (var x in indirectContentTypes)
-                list.Add(x);
-
-            //At this point we have a list of content types that 'could' be compositions
-
-            //now we'll filter this list based on the filters requested
-            var filtered = list
-                .Where(x =>
-                {
-                    //need to filter any content types that are included in this list
-                    return filterContentTypes.Any(c => c.InvariantEquals(x.Alias)) == false;
-                })
-                .Where(x =>
-                {
-                    //need to filter any content types that have matching property aliases that are included in this list
-                    //ensure that we don't return if there's any overlapping property aliases from the filtered ones specified
-                    return filterPropertyTypes.Intersect(
-                        x.PropertyTypes.Select(p => p.Alias),
-                        StringComparer.InvariantCultureIgnoreCase).Any() == false;
-                })
-                .OrderBy(x => x.Name)
-                .ToList();
-
-            //get ancestor ids - we will filter all ancestors
-            var ancestors = GetAncestors(source, allContentTypes);
-            var ancestorIds = ancestors.Select(x => x.Id).ToArray();
-
-            //now we can create our result based on what is still available and the ancestors
-            var result = list
-                //not itself
-                .Where(x => x.Id != sourceId)
-                .OrderBy(x => x.Name)
-                .Select(composition => filtered.Contains(composition)
+        //now we'll filter this list based on the filters requested
+        var filtered = list
+            .Where(x =>
+            {
+                //need to filter any content types that are included in this list
+                return filterContentTypes.Any(c => c.InvariantEquals(x.Alias)) == false;
+            })
+            .Where(x =>
+            {
+                //need to filter any content types that have matching property aliases that are included in this list
+                //ensure that we don't return if there's any overlapping property aliases from the filtered ones specified
+                return filterPropertyTypes.Intersect(
+                    x.PropertyTypes.Select(p => p.Alias),
+                    StringComparer.InvariantCultureIgnoreCase).Any() == false;
+            })
+            .OrderBy(x => x.Name)
+            .ToList();
+
+        //get ancestor ids - we will filter all ancestors
+        IContentTypeComposition[] ancestors = GetAncestors(source, allContentTypes);
+        var ancestorIds = ancestors.Select(x => x.Id).ToArray();
+
+        //now we can create our result based on what is still available and the ancestors
+        var result = list
+            //not itself
+            .Where(x => x.Id != sourceId)
+            .OrderBy(x => x.Name)
+            .Select(composition => filtered.Contains(composition)
                 ? new ContentTypeAvailableCompositionsResult(composition, ancestorIds.Contains(composition.Id) == false)
                 : new ContentTypeAvailableCompositionsResult(composition, false)).ToList();
 
-            return new ContentTypeAvailableCompositionsResults(ancestors, result);
-        }
+        return new ContentTypeAvailableCompositionsResults(ancestors, result);
+    }
 
 
-        private static IContentTypeComposition[] GetAncestors(IContentTypeComposition? ctype, IContentTypeComposition[] allContentTypes)
+    private static IContentTypeComposition[] GetAncestors(IContentTypeComposition? ctype,
+        IContentTypeComposition[] allContentTypes)
+    {
+        if (ctype == null)
         {
-            if (ctype == null) return new IContentTypeComposition[] {};
-            var ancestors = new List();
-            var parentId = ctype.ParentId;
-            while (parentId > 0)
+            return new IContentTypeComposition[] { };
+        }
+
+        var ancestors = new List();
+        var parentId = ctype.ParentId;
+        while (parentId > 0)
+        {
+            IContentTypeComposition parent = allContentTypes.FirstOrDefault(x => x.Id == parentId);
+            if (parent != null)
+            {
+                ancestors.Add(parent);
+                parentId = parent.ParentId;
+            }
+            else
             {
-                var parent = allContentTypes.FirstOrDefault(x => x.Id == parentId);
-                if (parent != null)
-                {
-                    ancestors.Add(parent);
-                    parentId = parent.ParentId;
-                }
-                else
-                {
-                    parentId = -1;
-                }
+                parentId = -1;
             }
-            return ancestors.ToArray();
         }
 
-        /// 
-        /// Get those that we use directly
-        /// 
-        /// 
-        /// 
-        private static IEnumerable GetDirectOrIndirect(IContentTypeComposition? ctype)
+        return ancestors.ToArray();
+    }
+
+    /// 
+    ///     Get those that we use directly
+    /// 
+    /// 
+    /// 
+    private static IEnumerable GetDirectOrIndirect(IContentTypeComposition? ctype)
+    {
+        if (ctype == null)
         {
-            if (ctype == null) return Enumerable.Empty();
+            return Enumerable.Empty();
+        }
 
-            // hashset guarantees uniqueness on Id
-            var all = new HashSet(new DelegateEqualityComparer(
-                (x, y) => x?.Id == y?.Id,
-                x => x.Id));
+        // hashset guarantees uniqueness on Id
+        var all = new HashSet(new DelegateEqualityComparer(
+            (x, y) => x?.Id == y?.Id,
+            x => x.Id));
 
-            var stack = new Stack();
+        var stack = new Stack();
 
-            foreach (var x in ctype.ContentTypeComposition)
-                stack.Push(x);
+        foreach (IContentTypeComposition x in ctype.ContentTypeComposition)
+        {
+            stack.Push(x);
+        }
 
-            while (stack.Count > 0)
+        while (stack.Count > 0)
+        {
+            IContentTypeComposition x = stack.Pop();
+            all.Add(x);
+            foreach (IContentTypeComposition y in x.ContentTypeComposition)
             {
-                var x = stack.Pop();
-                all.Add(x);
-                foreach (var y in x.ContentTypeComposition)
-                    stack.Push(y);
+                stack.Push(y);
             }
-
-            return all;
         }
+
+        return all;
     }
 }
diff --git a/src/Umbraco.Core/Services/ContentVersionService.cs b/src/Umbraco.Core/Services/ContentVersionService.cs
index 9e32bab7622d..79df9bb34843 100644
--- a/src/Umbraco.Core/Services/ContentVersionService.cs
+++ b/src/Umbraco.Core/Services/ContentVersionService.cs
@@ -1,6 +1,3 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
 using Microsoft.Extensions.Logging;
 using Umbraco.Cms.Core.Events;
 using Umbraco.Cms.Core.Models;
@@ -10,191 +7,196 @@
 using Umbraco.Extensions;
 
 // ReSharper disable once CheckNamespace
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+internal class ContentVersionService : IContentVersionService
 {
-    internal class ContentVersionService : IContentVersionService
+    private readonly IAuditRepository _auditRepository;
+    private readonly IContentVersionCleanupPolicy _contentVersionCleanupPolicy;
+    private readonly IDocumentVersionRepository _documentVersionRepository;
+    private readonly IEventMessagesFactory _eventMessagesFactory;
+    private readonly ILanguageRepository _languageRepository;
+    private readonly ILogger _logger;
+    private readonly ICoreScopeProvider _scopeProvider;
+
+    public ContentVersionService(
+        ILogger logger,
+        IDocumentVersionRepository documentVersionRepository,
+        IContentVersionCleanupPolicy contentVersionCleanupPolicy,
+        ICoreScopeProvider scopeProvider,
+        IEventMessagesFactory eventMessagesFactory,
+        IAuditRepository auditRepository,
+        ILanguageRepository languageRepository)
     {
-        private readonly ILogger _logger;
-        private readonly IDocumentVersionRepository _documentVersionRepository;
-        private readonly IContentVersionCleanupPolicy _contentVersionCleanupPolicy;
-        private readonly ICoreScopeProvider _scopeProvider;
-        private readonly IEventMessagesFactory _eventMessagesFactory;
-        private readonly IAuditRepository _auditRepository;
-        private readonly ILanguageRepository _languageRepository;
-
-        public ContentVersionService(
-            ILogger logger,
-            IDocumentVersionRepository documentVersionRepository,
-            IContentVersionCleanupPolicy contentVersionCleanupPolicy,
-            ICoreScopeProvider scopeProvider,
-            IEventMessagesFactory eventMessagesFactory,
-            IAuditRepository auditRepository,
-            ILanguageRepository languageRepository)
+        _logger = logger;
+        _documentVersionRepository = documentVersionRepository;
+        _contentVersionCleanupPolicy = contentVersionCleanupPolicy;
+        _scopeProvider = scopeProvider;
+        _eventMessagesFactory = eventMessagesFactory;
+        _auditRepository = auditRepository;
+        _languageRepository = languageRepository;
+    }
+
+    /// 
+    public IReadOnlyCollection PerformContentVersionCleanup(DateTime asAtDate) =>
+        // Media - ignored
+        // Members - ignored
+        CleanupDocumentVersions(asAtDate);
+
+    /// 
+    public IEnumerable? GetPagedContentVersions(int contentId, long pageIndex, int pageSize,
+        out long totalRecords, string? culture = null)
+    {
+        if (pageIndex < 0)
         {
-            _logger = logger;
-            _documentVersionRepository = documentVersionRepository;
-            _contentVersionCleanupPolicy = contentVersionCleanupPolicy;
-            _scopeProvider = scopeProvider;
-            _eventMessagesFactory = eventMessagesFactory;
-            _auditRepository = auditRepository;
-            _languageRepository = languageRepository;
+            throw new ArgumentOutOfRangeException(nameof(pageIndex));
         }
 
-        /// 
-        public IReadOnlyCollection PerformContentVersionCleanup(DateTime asAtDate)
+        if (pageSize <= 0)
         {
-            // Media - ignored
-            // Members - ignored
-            return CleanupDocumentVersions(asAtDate);
+            throw new ArgumentOutOfRangeException(nameof(pageSize));
         }
 
-        private IReadOnlyCollection CleanupDocumentVersions(DateTime asAtDate)
+        using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true))
         {
-            List versionsToDelete;
-
-            /* Why so many scopes?
-             *
-             * We could just work out the set to delete at SQL infra level which was the original plan, however we agreed that really we should fire
-             * ContentService.DeletingVersions so people can hook & cancel if required.
-             *
-             * On first time run of cleanup on a site with a lot of history there may be a lot of historic ContentVersions to remove e.g. 200K for our.umbraco.com.
-             * If we weren't supporting SQL CE we could do TVP, or use temp tables to bulk delete with joins to our list of version ids to nuke.
-             * (much nicer, we can kill 100k in sub second time-frames).
-             *
-             * However we are supporting SQL CE, so the easiest thing to do is use the Umbraco InGroupsOf helper to create a query with 2K args of version
-             * ids to delete at a time.
-             *
-             * This is already done at the repository level, however if we only had a single scope at service level we're still locking
-             * the ContentVersions table (and other related tables) for a couple of minutes which makes the back office unusable.
-             *
-             * As a quick fix, we can also use InGroupsOf at service level, create a scope per group to give other connections a chance
-             * to grab the locks and execute their queries.
-             *
-             * This makes the back office a tiny bit sluggish during first run but it is usable for loading tree and publishing content.
-             *
-             * There are optimizations we can do, we could add a bulk delete for SqlServerSyntaxProvider which differs in implementation
-             * and fallback to this naive approach only for SQL CE, however we agreed it is not worth the effort as this is a one time pain,
-             * subsequent runs shouldn't have huge numbers of versions to cleanup.
-             *
-             * tl;dr lots of scopes to enable other connections to use the DB whilst we work.
-             */
-            using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                IReadOnlyCollection? allHistoricVersions = _documentVersionRepository.GetDocumentVersionsEligibleForCleanup();
+            var languageId = _languageRepository.GetIdByIsoCode(culture, true);
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _documentVersionRepository.GetPagedItemsByContentId(contentId, pageIndex, pageSize, out totalRecords,
+                languageId);
+        }
+    }
 
-                if (allHistoricVersions is null)
-                {
-                    return Array.Empty();
-                }
-                _logger.LogDebug("Discovered {count} candidate(s) for ContentVersion cleanup", allHistoricVersions.Count);
-                versionsToDelete = new List(allHistoricVersions.Count);
+    /// 
+    public void SetPreventCleanup(int versionId, bool preventCleanup, int userId = -1)
+    {
+        using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            scope.WriteLock(Constants.Locks.ContentTree);
+            _documentVersionRepository.SetPreventCleanup(versionId, preventCleanup);
 
-                IEnumerable filteredContentVersions = _contentVersionCleanupPolicy.Apply(asAtDate, allHistoricVersions);
+            ContentVersionMeta? version = _documentVersionRepository.Get(versionId);
 
-                foreach (ContentVersionMeta version in filteredContentVersions)
-                {
-                    EventMessages messages = _eventMessagesFactory.Get();
+            if (version is null)
+            {
+                return;
+            }
 
-                    if (scope.Notifications.PublishCancelable(new ContentDeletingVersionsNotification(version.ContentId, messages, version.VersionId)))
-                    {
-                        _logger.LogDebug("Delete cancelled for ContentVersion [{versionId}]", version.VersionId);
-                        continue;
-                    }
+            AuditType auditType = preventCleanup
+                ? AuditType.ContentVersionPreventCleanup
+                : AuditType.ContentVersionEnableCleanup;
 
-                    versionsToDelete.Add(version);
-                }
-            }
+            var message = $"set preventCleanup = '{preventCleanup}' for version '{versionId}'";
 
-            if (!versionsToDelete.Any())
+            Audit(auditType, userId, version.ContentId, message, $"{version.VersionDate}");
+        }
+    }
+
+    private IReadOnlyCollection CleanupDocumentVersions(DateTime asAtDate)
+    {
+        List versionsToDelete;
+
+        /* Why so many scopes?
+         *
+         * We could just work out the set to delete at SQL infra level which was the original plan, however we agreed that really we should fire
+         * ContentService.DeletingVersions so people can hook & cancel if required.
+         *
+         * On first time run of cleanup on a site with a lot of history there may be a lot of historic ContentVersions to remove e.g. 200K for our.umbraco.com.
+         * If we weren't supporting SQL CE we could do TVP, or use temp tables to bulk delete with joins to our list of version ids to nuke.
+         * (much nicer, we can kill 100k in sub second time-frames).
+         *
+         * However we are supporting SQL CE, so the easiest thing to do is use the Umbraco InGroupsOf helper to create a query with 2K args of version
+         * ids to delete at a time.
+         *
+         * This is already done at the repository level, however if we only had a single scope at service level we're still locking
+         * the ContentVersions table (and other related tables) for a couple of minutes which makes the back office unusable.
+         *
+         * As a quick fix, we can also use InGroupsOf at service level, create a scope per group to give other connections a chance
+         * to grab the locks and execute their queries.
+         *
+         * This makes the back office a tiny bit sluggish during first run but it is usable for loading tree and publishing content.
+         *
+         * There are optimizations we can do, we could add a bulk delete for SqlServerSyntaxProvider which differs in implementation
+         * and fallback to this naive approach only for SQL CE, however we agreed it is not worth the effort as this is a one time pain,
+         * subsequent runs shouldn't have huge numbers of versions to cleanup.
+         *
+         * tl;dr lots of scopes to enable other connections to use the DB whilst we work.
+         */
+        using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            IReadOnlyCollection? allHistoricVersions =
+                _documentVersionRepository.GetDocumentVersionsEligibleForCleanup();
+
+            if (allHistoricVersions is null)
             {
-                _logger.LogDebug("No remaining ContentVersions for cleanup");
                 return Array.Empty();
             }
 
-            _logger.LogDebug("Removing {count} ContentVersion(s)", versionsToDelete.Count);
+            _logger.LogDebug("Discovered {count} candidate(s) for ContentVersion cleanup", allHistoricVersions.Count);
+            versionsToDelete = new List(allHistoricVersions.Count);
 
-            foreach (IEnumerable group in versionsToDelete.InGroupsOf(Constants.Sql.MaxParameterCount))
-            {
-                using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true))
-                {
-                    scope.WriteLock(Constants.Locks.ContentTree);
-                    var groupEnumerated = group.ToList();
-                    _documentVersionRepository.DeleteVersions(groupEnumerated.Select(x => x.VersionId));
+            IEnumerable filteredContentVersions =
+                _contentVersionCleanupPolicy.Apply(asAtDate, allHistoricVersions);
 
-                    foreach (ContentVersionMeta version in groupEnumerated)
-                    {
-                        EventMessages messages = _eventMessagesFactory.Get();
+            foreach (ContentVersionMeta version in filteredContentVersions)
+            {
+                EventMessages messages = _eventMessagesFactory.Get();
 
-                        scope.Notifications.Publish(new ContentDeletedVersionsNotification(version.ContentId, messages, version.VersionId));
-                    }
+                if (scope.Notifications.PublishCancelable(
+                        new ContentDeletingVersionsNotification(version.ContentId, messages, version.VersionId)))
+                {
+                    _logger.LogDebug("Delete cancelled for ContentVersion [{versionId}]", version.VersionId);
+                    continue;
                 }
-            }
 
-            using (_scopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                Audit(AuditType.Delete, Constants.Security.SuperUserId, -1, $"Removed {versionsToDelete.Count} ContentVersion(s) according to cleanup policy");
+                versionsToDelete.Add(version);
             }
-
-            return versionsToDelete;
         }
 
-        /// 
-        public IEnumerable? GetPagedContentVersions(int contentId, long pageIndex, int pageSize, out long totalRecords, string? culture = null)
+        if (!versionsToDelete.Any())
         {
-            if (pageIndex < 0)
-            {
-                throw new ArgumentOutOfRangeException(nameof(pageIndex));
-            }
-
-            if (pageSize <= 0)
-            {
-                throw new ArgumentOutOfRangeException(nameof(pageSize));
-            }
-
-            using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                var languageId = _languageRepository.GetIdByIsoCode(culture, throwOnNotFound: true);
-                scope.ReadLock(Constants.Locks.ContentTree);
-                return _documentVersionRepository.GetPagedItemsByContentId(contentId, pageIndex, pageSize, out totalRecords, languageId);
-            }
+            _logger.LogDebug("No remaining ContentVersions for cleanup");
+            return Array.Empty();
         }
 
-        /// 
-        public void SetPreventCleanup(int versionId, bool preventCleanup, int userId = -1)
+        _logger.LogDebug("Removing {count} ContentVersion(s)", versionsToDelete.Count);
+
+        foreach (IEnumerable group in versionsToDelete.InGroupsOf(Constants.Sql.MaxParameterCount))
         {
             using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true))
             {
                 scope.WriteLock(Constants.Locks.ContentTree);
-                _documentVersionRepository.SetPreventCleanup(versionId, preventCleanup);
-
-                ContentVersionMeta? version = _documentVersionRepository.Get(versionId);
+                var groupEnumerated = group.ToList();
+                _documentVersionRepository.DeleteVersions(groupEnumerated.Select(x => x.VersionId));
 
-                if (version is null)
+                foreach (ContentVersionMeta version in groupEnumerated)
                 {
-                    return;
-                }
-
-                AuditType auditType = preventCleanup
-                    ? AuditType.ContentVersionPreventCleanup
-                    : AuditType.ContentVersionEnableCleanup;
-
-                var message = $"set preventCleanup = '{preventCleanup}' for version '{versionId}'";
+                    EventMessages messages = _eventMessagesFactory.Get();
 
-                Audit(auditType, userId, version.ContentId, message, $"{version.VersionDate}");
+                    scope.Notifications.Publish(
+                        new ContentDeletedVersionsNotification(version.ContentId, messages, version.VersionId));
+                }
             }
         }
 
-        private void Audit(AuditType type, int userId, int objectId, string? message = null, string? parameters = null)
+        using (_scopeProvider.CreateCoreScope(autoComplete: true))
         {
-            var entry = new AuditItem(
-                objectId,
-                type,
-                userId,
-                UmbracoObjectTypes.Document.GetName(),
-                message,
-                parameters);
-
-            _auditRepository.Save(entry);
+            Audit(AuditType.Delete, Constants.Security.SuperUserId, -1,
+                $"Removed {versionsToDelete.Count} ContentVersion(s) according to cleanup policy");
         }
+
+        return versionsToDelete;
+    }
+
+    private void Audit(AuditType type, int userId, int objectId, string? message = null, string? parameters = null)
+    {
+        var entry = new AuditItem(
+            objectId,
+            type,
+            userId,
+            UmbracoObjectTypes.Document.GetName(),
+            message,
+            parameters);
+
+        _auditRepository.Save(entry);
     }
 }
diff --git a/src/Umbraco.Core/Services/DashboardService.cs b/src/Umbraco.Core/Services/DashboardService.cs
index 203ce64984d0..c1e928e7438b 100644
--- a/src/Umbraco.Core/Services/DashboardService.cs
+++ b/src/Umbraco.Core/Services/DashboardService.cs
@@ -1,145 +1,163 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Umbraco.Cms.Core.Dashboards;
+using Umbraco.Cms.Core.Dashboards;
 using Umbraco.Cms.Core.Models.ContentEditing;
 using Umbraco.Cms.Core.Models.Membership;
 using Umbraco.Extensions;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     A utility class for determine dashboard security
+/// 
+public class DashboardService : IDashboardService
 {
-    /// 
-    /// A utility class for determine dashboard security
-    /// 
-    public class DashboardService : IDashboardService
-    {
-        // TODO: Unit test all this!!! :/
+    private readonly DashboardCollection _dashboardCollection;
 
-        private readonly ISectionService _sectionService;
-        private readonly DashboardCollection _dashboardCollection;
-        private readonly ILocalizedTextService _localizedText;
+    private readonly ILocalizedTextService _localizedText;
+    // TODO: Unit test all this!!! :/
 
-        public DashboardService(ISectionService sectionService, DashboardCollection dashboardCollection, ILocalizedTextService localizedText)
-        {
-            _sectionService = sectionService ?? throw new ArgumentNullException(nameof(sectionService));
-            _dashboardCollection = dashboardCollection ?? throw new ArgumentNullException(nameof(dashboardCollection));
-            _localizedText = localizedText ?? throw new ArgumentNullException(nameof(localizedText));
-        }
+    private readonly ISectionService _sectionService;
 
+    public DashboardService(ISectionService sectionService, DashboardCollection dashboardCollection,
+        ILocalizedTextService localizedText)
+    {
+        _sectionService = sectionService ?? throw new ArgumentNullException(nameof(sectionService));
+        _dashboardCollection = dashboardCollection ?? throw new ArgumentNullException(nameof(dashboardCollection));
+        _localizedText = localizedText ?? throw new ArgumentNullException(nameof(localizedText));
+    }
 
-        /// 
-        public IEnumerable> GetDashboards(string section, IUser? currentUser)
-        {
-            var tabs = new List>();
-            var tabId = 0;
 
-            foreach (var dashboard in _dashboardCollection.Where(x => x.Sections.InvariantContains(section)))
-            {
-                // validate access
-                if (currentUser is null || !CheckUserAccessByRules(currentUser, _sectionService, dashboard.AccessRules))
-                    continue;
+    /// 
+    public IEnumerable> GetDashboards(string section, IUser? currentUser)
+    {
+        var tabs = new List>();
+        var tabId = 0;
 
-                if (dashboard.View?.InvariantEndsWith(".ascx") ?? false)
-                    throw new NotSupportedException("Legacy UserControl (.ascx) dashboards are no longer supported.");
+        foreach (IDashboard dashboard in _dashboardCollection.Where(x => x.Sections.InvariantContains(section)))
+        {
+            // validate access
+            if (currentUser is null || !CheckUserAccessByRules(currentUser, _sectionService, dashboard.AccessRules))
+            {
+                continue;
+            }
 
-                var dashboards = new List { dashboard };
-                tabs.Add(new Tab
-                {
-                    Id = tabId++,
-                    Label = _localizedText.Localize("dashboardTabs", dashboard.Alias),
-                    Alias = dashboard.Alias,
-                    Properties = dashboards
-                });
+            if (dashboard.View?.InvariantEndsWith(".ascx") ?? false)
+            {
+                throw new NotSupportedException("Legacy UserControl (.ascx) dashboards are no longer supported.");
             }
 
-            return tabs;
+            var dashboards = new List {dashboard};
+            tabs.Add(new Tab
+            {
+                Id = tabId++,
+                Label = _localizedText.Localize("dashboardTabs", dashboard.Alias),
+                Alias = dashboard.Alias,
+                Properties = dashboards
+            });
         }
 
-        /// 
-        public IDictionary>> GetDashboards(IUser? currentUser)
+        return tabs;
+    }
+
+    /// 
+    public IDictionary>> GetDashboards(IUser? currentUser) => _sectionService
+        .GetSections().ToDictionary(x => x.Alias, x => GetDashboards(x.Alias, currentUser));
+
+    private bool CheckUserAccessByRules(IUser user, ISectionService sectionService, IEnumerable rules)
+    {
+        if (user.Id == Constants.Security.SuperUserId)
         {
-            return _sectionService.GetSections().ToDictionary(x => x.Alias, x => GetDashboards(x.Alias, currentUser));
+            return true;
         }
 
-        private bool CheckUserAccessByRules(IUser user, ISectionService sectionService, IEnumerable rules)
-        {
-            if (user.Id == Constants.Security.SuperUserId)
-                return true;
+        (IAccessRule[] denyRules, IAccessRule[] grantRules, IAccessRule[] grantBySectionRules) = GroupRules(rules);
 
-            var (denyRules, grantRules, grantBySectionRules) = GroupRules(rules);
+        var hasAccess = true;
+        string[]? assignedUserGroups = null;
 
-            var hasAccess = true;
-            string[]? assignedUserGroups = null;
+        // if there are no grant rules, then access is granted by default, unless denied
+        // otherwise, grant rules determine if access can be granted at all
+        if (grantBySectionRules.Length > 0 || grantRules.Length > 0)
+        {
+            hasAccess = false;
 
-            // if there are no grant rules, then access is granted by default, unless denied
-            // otherwise, grant rules determine if access can be granted at all
-            if (grantBySectionRules.Length > 0 || grantRules.Length > 0)
+            // check if this item has any grant-by-section arguments.
+            // if so check if the user has access to any of the sections approved, if so they will be allowed to see it (so far)
+            if (grantBySectionRules.Length > 0)
             {
-                hasAccess = false;
+                var allowedSections = sectionService.GetAllowedSections(user.Id).Select(x => x.Alias).ToArray();
+                var wantedSections = grantBySectionRules.SelectMany(g =>
+                    g.Value?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) ??
+                    Array.Empty()).ToArray();
 
-                // check if this item has any grant-by-section arguments.
-                // if so check if the user has access to any of the sections approved, if so they will be allowed to see it (so far)
-                if (grantBySectionRules.Length > 0)
+                if (wantedSections.Intersect(allowedSections).Any())
                 {
-                    var allowedSections = sectionService.GetAllowedSections(user.Id).Select(x => x.Alias).ToArray();
-                    var wantedSections = grantBySectionRules.SelectMany(g => g.Value?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty()).ToArray();
-
-                    if (wantedSections.Intersect(allowedSections).Any())
-                        hasAccess = true;
+                    hasAccess = true;
                 }
+            }
 
-                // if not already granted access, check if this item as any grant arguments.
-                // if so check if the user is in one of the user groups approved, if so they will be allowed to see it (so far)
-                if (hasAccess == false && grantRules.Any())
-                {
-                    assignedUserGroups = user.Groups.Select(x => x.Alias).ToArray();
-                    var wantedUserGroups = grantRules.SelectMany(g => g.Value?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty()).ToArray();
+            // if not already granted access, check if this item as any grant arguments.
+            // if so check if the user is in one of the user groups approved, if so they will be allowed to see it (so far)
+            if (hasAccess == false && grantRules.Any())
+            {
+                assignedUserGroups = user.Groups.Select(x => x.Alias).ToArray();
+                var wantedUserGroups = grantRules.SelectMany(g =>
+                    g.Value?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) ??
+                    Array.Empty()).ToArray();
 
-                    if (wantedUserGroups.Intersect(assignedUserGroups).Any())
-                        hasAccess = true;
+                if (wantedUserGroups.Intersect(assignedUserGroups).Any())
+                {
+                    hasAccess = true;
                 }
             }
+        }
 
-            // No need to check denyRules if there aren't any, just return current state
-            if (denyRules.Length == 0)
-                return hasAccess;
-
-            // check if this item has any deny arguments, if so check if the user is in one of the denied user groups, if so they will
-            // be denied to see it no matter what
-            assignedUserGroups = assignedUserGroups ?? user.Groups.Select(x => x.Alias).ToArray();
-            var deniedUserGroups = denyRules.SelectMany(g => g.Value?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty()).ToArray();
-
-            if (deniedUserGroups.Intersect(assignedUserGroups).Any())
-                hasAccess = false;
-
+        // No need to check denyRules if there aren't any, just return current state
+        if (denyRules.Length == 0)
+        {
             return hasAccess;
         }
 
-        private static (IAccessRule[], IAccessRule[], IAccessRule[]) GroupRules(IEnumerable rules)
+        // check if this item has any deny arguments, if so check if the user is in one of the denied user groups, if so they will
+        // be denied to see it no matter what
+        assignedUserGroups = assignedUserGroups ?? user.Groups.Select(x => x.Alias).ToArray();
+        var deniedUserGroups = denyRules.SelectMany(g =>
+                g.Value?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) ??
+                Array.Empty())
+            .ToArray();
+
+        if (deniedUserGroups.Intersect(assignedUserGroups).Any())
         {
-            IAccessRule[]? denyRules = null, grantRules = null, grantBySectionRules = null;
+            hasAccess = false;
+        }
+
+        return hasAccess;
+    }
+
+    private static (IAccessRule[], IAccessRule[], IAccessRule[]) GroupRules(IEnumerable rules)
+    {
+        IAccessRule[]? denyRules = null, grantRules = null, grantBySectionRules = null;
 
-            var groupedRules = rules.GroupBy(x => x.Type);
-            foreach (var group in groupedRules)
+        IEnumerable> groupedRules = rules.GroupBy(x => x.Type);
+        foreach (IGrouping group in groupedRules)
+        {
+            IAccessRule[] a = group.ToArray();
+            switch (group.Key)
             {
-                var a = group.ToArray();
-                switch (group.Key)
-                {
-                    case AccessRuleType.Deny:
-                        denyRules = a;
-                        break;
-                    case AccessRuleType.Grant:
-                        grantRules = a;
-                        break;
-                    case AccessRuleType.GrantBySection:
-                        grantBySectionRules = a;
-                        break;
-                    default:
-                        throw new NotSupportedException($"The '{group.Key.ToString()}'-AccessRuleType is not supported.");
-                }
+                case AccessRuleType.Deny:
+                    denyRules = a;
+                    break;
+                case AccessRuleType.Grant:
+                    grantRules = a;
+                    break;
+                case AccessRuleType.GrantBySection:
+                    grantBySectionRules = a;
+                    break;
+                default:
+                    throw new NotSupportedException($"The '{group.Key.ToString()}'-AccessRuleType is not supported.");
             }
-
-            return (denyRules ?? Array.Empty(), grantRules ?? Array.Empty(), grantBySectionRules ?? Array.Empty());
         }
+
+        return (denyRules ?? Array.Empty(), grantRules ?? Array.Empty(),
+            grantBySectionRules ?? Array.Empty());
     }
 }
diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs
index 5c9d5847edc6..86c30da7a7b8 100644
--- a/src/Umbraco.Core/Services/DataTypeService.cs
+++ b/src/Umbraco.Core/Services/DataTypeService.cs
@@ -1,13 +1,12 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using Umbraco.Cms.Core.Events;
 using Umbraco.Cms.Core.Exceptions;
 using Umbraco.Cms.Core.IO;
 using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models.Entities;
 using Umbraco.Cms.Core.Notifications;
+using Umbraco.Cms.Core.Persistence.Querying;
 using Umbraco.Cms.Core.Persistence.Repositories;
 using Umbraco.Cms.Core.PropertyEditors;
 using Umbraco.Cms.Core.Scoping;
@@ -16,570 +15,597 @@
 using Umbraco.Cms.Web.Common.DependencyInjection;
 using Umbraco.Extensions;
 
-namespace Umbraco.Cms.Core.Services.Implement
+namespace Umbraco.Cms.Core.Services.Implement;
+
+/// 
+///     Represents the DataType Service, which is an easy access to operations involving 
+/// 
+public class DataTypeService : RepositoryService, IDataTypeService
 {
+    private readonly IAuditRepository _auditRepository;
+    private readonly IContentTypeRepository _contentTypeRepository;
+    private readonly IDataTypeContainerRepository _dataTypeContainerRepository;
+    private readonly IDataTypeRepository _dataTypeRepository;
+    private readonly IDataValueEditorFactory _dataValueEditorFactory;
+    private readonly IEditorConfigurationParser _editorConfigurationParser;
+    private readonly IEntityRepository _entityRepository;
+    private readonly IIOHelper _ioHelper;
+    private readonly IJsonSerializer _jsonSerializer;
+    private readonly ILocalizationService _localizationService;
+    private readonly ILocalizedTextService _localizedTextService;
+    private readonly IShortStringHelper _shortStringHelper;
+
+    [Obsolete("Please use constructor that takes an ")]
+    public DataTypeService(
+        IDataValueEditorFactory dataValueEditorFactory,
+        ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory,
+        IDataTypeRepository dataTypeRepository, IDataTypeContainerRepository dataTypeContainerRepository,
+        IAuditRepository auditRepository, IEntityRepository entityRepository,
+        IContentTypeRepository contentTypeRepository,
+        IIOHelper ioHelper, ILocalizedTextService localizedTextService, ILocalizationService localizationService,
+        IShortStringHelper shortStringHelper,
+        IJsonSerializer jsonSerializer)
+        : this(
+            dataValueEditorFactory,
+            provider,
+            loggerFactory,
+            eventMessagesFactory,
+            dataTypeRepository,
+            dataTypeContainerRepository,
+            auditRepository,
+            entityRepository,
+            contentTypeRepository,
+            ioHelper,
+            localizedTextService,
+            localizationService,
+            shortStringHelper,
+            jsonSerializer,
+            StaticServiceProvider.Instance.GetRequiredService())
+    {
+        _dataValueEditorFactory = dataValueEditorFactory;
+        _dataTypeRepository = dataTypeRepository;
+        _dataTypeContainerRepository = dataTypeContainerRepository;
+        _auditRepository = auditRepository;
+        _entityRepository = entityRepository;
+        _contentTypeRepository = contentTypeRepository;
+        _ioHelper = ioHelper;
+        _localizedTextService = localizedTextService;
+        _localizationService = localizationService;
+        _shortStringHelper = shortStringHelper;
+        _jsonSerializer = jsonSerializer;
+    }
+
+    public DataTypeService(
+        IDataValueEditorFactory dataValueEditorFactory,
+        ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory,
+        IDataTypeRepository dataTypeRepository, IDataTypeContainerRepository dataTypeContainerRepository,
+        IAuditRepository auditRepository, IEntityRepository entityRepository,
+        IContentTypeRepository contentTypeRepository,
+        IIOHelper ioHelper, ILocalizedTextService localizedTextService, ILocalizationService localizationService,
+        IShortStringHelper shortStringHelper,
+        IJsonSerializer jsonSerializer,
+        IEditorConfigurationParser editorConfigurationParser)
+        : base(provider, loggerFactory, eventMessagesFactory)
+    {
+        _dataValueEditorFactory = dataValueEditorFactory;
+        _dataTypeRepository = dataTypeRepository;
+        _dataTypeContainerRepository = dataTypeContainerRepository;
+        _auditRepository = auditRepository;
+        _entityRepository = entityRepository;
+        _contentTypeRepository = contentTypeRepository;
+        _ioHelper = ioHelper;
+        _localizedTextService = localizedTextService;
+        _localizationService = localizationService;
+        _shortStringHelper = shortStringHelper;
+        _jsonSerializer = jsonSerializer;
+        _editorConfigurationParser = editorConfigurationParser;
+    }
+
     /// 
-    /// Represents the DataType Service, which is an easy access to operations involving 
+    ///     Gets a  by its Name
     /// 
-    public class DataTypeService : RepositoryService, IDataTypeService
+    /// Name of the 
+    /// 
+    ///     
+    /// 
+    public IDataType? GetDataType(string name)
     {
-        private readonly IDataValueEditorFactory _dataValueEditorFactory;
-        private readonly IDataTypeRepository _dataTypeRepository;
-        private readonly IDataTypeContainerRepository _dataTypeContainerRepository;
-        private readonly IContentTypeRepository _contentTypeRepository;
-        private readonly IAuditRepository _auditRepository;
-        private readonly IEntityRepository _entityRepository;
-        private readonly IIOHelper _ioHelper;
-        private readonly ILocalizedTextService _localizedTextService;
-        private readonly ILocalizationService _localizationService;
-        private readonly IShortStringHelper _shortStringHelper;
-        private readonly IJsonSerializer _jsonSerializer;
-        private readonly IEditorConfigurationParser _editorConfigurationParser;
-
-        [Obsolete("Please use constructor that takes an ")]
-        public DataTypeService(
-            IDataValueEditorFactory dataValueEditorFactory,
-            ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory,
-            IDataTypeRepository dataTypeRepository, IDataTypeContainerRepository dataTypeContainerRepository,
-            IAuditRepository auditRepository, IEntityRepository entityRepository, IContentTypeRepository contentTypeRepository,
-            IIOHelper ioHelper, ILocalizedTextService localizedTextService, ILocalizationService localizationService,
-            IShortStringHelper shortStringHelper,
-            IJsonSerializer jsonSerializer)
-            : this(
-                dataValueEditorFactory,
-                provider,
-                loggerFactory,
-                eventMessagesFactory,
-                dataTypeRepository,
-                dataTypeContainerRepository,
-                auditRepository,
-                entityRepository,
-                contentTypeRepository,
-                ioHelper,
-                localizedTextService,
-                localizationService,
-                shortStringHelper,
-                jsonSerializer,
-                StaticServiceProvider.Instance.GetRequiredService())
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            _dataValueEditorFactory = dataValueEditorFactory;
-            _dataTypeRepository = dataTypeRepository;
-            _dataTypeContainerRepository = dataTypeContainerRepository;
-            _auditRepository = auditRepository;
-            _entityRepository = entityRepository;
-            _contentTypeRepository = contentTypeRepository;
-            _ioHelper = ioHelper;
-            _localizedTextService = localizedTextService;
-            _localizationService = localizationService;
-            _shortStringHelper = shortStringHelper;
-            _jsonSerializer = jsonSerializer;
+            IDataType dataType = _dataTypeRepository.Get(Query().Where(x => x.Name == name))
+                ?.FirstOrDefault();
+            ConvertMissingEditorOfDataTypeToLabel(dataType);
+            return dataType;
         }
+    }
 
-        public DataTypeService(
-            IDataValueEditorFactory dataValueEditorFactory,
-            ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory,
-            IDataTypeRepository dataTypeRepository, IDataTypeContainerRepository dataTypeContainerRepository,
-            IAuditRepository auditRepository, IEntityRepository entityRepository, IContentTypeRepository contentTypeRepository,
-            IIOHelper ioHelper, ILocalizedTextService localizedTextService, ILocalizationService localizationService,
-            IShortStringHelper shortStringHelper,
-            IJsonSerializer jsonSerializer,
-            IEditorConfigurationParser editorConfigurationParser)
-            : base(provider, loggerFactory, eventMessagesFactory)
+    /// 
+    ///     Gets a  by its Id
+    /// 
+    /// Id of the 
+    /// 
+    ///     
+    /// 
+    public IDataType? GetDataType(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            _dataValueEditorFactory = dataValueEditorFactory;
-            _dataTypeRepository = dataTypeRepository;
-            _dataTypeContainerRepository = dataTypeContainerRepository;
-            _auditRepository = auditRepository;
-            _entityRepository = entityRepository;
-            _contentTypeRepository = contentTypeRepository;
-            _ioHelper = ioHelper;
-            _localizedTextService = localizedTextService;
-            _localizationService = localizationService;
-            _shortStringHelper = shortStringHelper;
-            _jsonSerializer = jsonSerializer;
-            _editorConfigurationParser = editorConfigurationParser;
+            IDataType dataType = _dataTypeRepository.Get(id);
+            ConvertMissingEditorOfDataTypeToLabel(dataType);
+            return dataType;
         }
+    }
 
-        #region Containers
-
-        public Attempt?> CreateContainer(int parentId, Guid key, string name, int userId = Cms.Core.Constants.Security.SuperUserId)
+    /// 
+    ///     Gets a  by its unique guid Id
+    /// 
+    /// Unique guid Id of the DataType
+    /// 
+    ///     
+    /// 
+    public IDataType? GetDataType(Guid id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            var evtMsgs = EventMessagesFactory.Get();
-            using (var scope = ScopeProvider.CreateCoreScope())
-            {
-                try
-                {
-                    var container = new EntityContainer(Cms.Core.Constants.ObjectTypes.DataType)
-                    {
-                        Name = name,
-                        ParentId = parentId,
-                        CreatorId = userId,
-                        Key = key
-                    };
-
-                    var savingEntityContainerNotification = new EntityContainerSavingNotification(container, evtMsgs);
-                    if (scope.Notifications.PublishCancelable(savingEntityContainerNotification))
-                    {
-                        scope.Complete();
-                        return OperationResult.Attempt.Cancel(evtMsgs, container);
-                    }
-
-                    _dataTypeContainerRepository.Save(container);
-                    scope.Complete();
-
-                    scope.Notifications.Publish(new EntityContainerSavedNotification(container, evtMsgs).WithStateFrom(savingEntityContainerNotification));
-
-                    // TODO: Audit trail ?
-
-                    return OperationResult.Attempt.Succeed(evtMsgs, container);
-                }
-                catch (Exception ex)
-                {
-                    return OperationResult.Attempt.Fail(evtMsgs, ex);
-                }
-            }
+            IQuery query = Query().Where(x => x.Key == id);
+            IDataType dataType = _dataTypeRepository.Get(query).FirstOrDefault();
+            ConvertMissingEditorOfDataTypeToLabel(dataType);
+            return dataType;
         }
+    }
 
-        public EntityContainer? GetContainer(int containerId)
+    /// 
+    ///     Gets a  by its control Id
+    /// 
+    /// Alias of the property editor
+    /// Collection of  objects with a matching control id
+    public IEnumerable GetByEditorAlias(string propertyEditorAlias)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _dataTypeContainerRepository.Get(containerId);
-            }
+            IQuery query = Query().Where(x => x.EditorAlias == propertyEditorAlias);
+            IEnumerable dataType = _dataTypeRepository.Get(query);
+            ConvertMissingEditorsOfDataTypesToLabels(dataType);
+            return dataType;
         }
+    }
 
-        public EntityContainer? GetContainer(Guid containerId)
+    /// 
+    ///     Gets all  objects or those with the ids passed in
+    /// 
+    /// Optional array of Ids
+    /// An enumerable list of  objects
+    public IEnumerable GetAll(params int[] ids)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _dataTypeContainerRepository.Get(containerId);
-            }
+            IEnumerable dataTypes = _dataTypeRepository.GetMany(ids);
+
+            ConvertMissingEditorsOfDataTypesToLabels(dataTypes);
+            return dataTypes;
         }
+    }
+
+    public Attempt?> Move(IDataType toMove, int parentId)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
+        var moveInfo = new List>();
 
-        public IEnumerable GetContainers(string name, int level)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+            var moveEventInfo = new MoveEventInfo(toMove, toMove.Path, parentId);
+
+            var movingDataTypeNotification = new DataTypeMovingNotification(moveEventInfo, evtMsgs);
+            if (scope.Notifications.PublishCancelable(movingDataTypeNotification))
             {
-                return _dataTypeContainerRepository.Get(name, level);
+                scope.Complete();
+                return OperationResult.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs);
             }
-        }
 
-        public IEnumerable GetContainers(IDataType dataType)
-        {
-            var ancestorIds = dataType.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)
-                .Select(x =>
+            try
+            {
+                EntityContainer? container = null;
+                if (parentId > 0)
                 {
-                    var asInt = x.TryConvertTo();
-                    return asInt.Success ? asInt.Result : int.MinValue;
-                })
-                .Where(x => x != int.MinValue && x != dataType.Id)
-                .ToArray();
+                    container = _dataTypeContainerRepository.Get(parentId);
+                    if (container == null)
+                    {
+                        throw new DataOperationException(MoveOperationStatusType
+                            .FailedParentNotFound); // causes rollback
+                    }
+                }
 
-            return GetContainers(ancestorIds);
-        }
+                moveInfo.AddRange(_dataTypeRepository.Move(toMove, container));
 
-        public IEnumerable GetContainers(int[] containerIds)
-        {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+                scope.Notifications.Publish(
+                    new DataTypeMovedNotification(moveEventInfo, evtMsgs).WithStateFrom(movingDataTypeNotification));
+
+                scope.Complete();
+            }
+            catch (DataOperationException ex)
             {
-                return _dataTypeContainerRepository.GetMany(containerIds);
+                scope.Complete(); // TODO: what are we doing here exactly?
+                return OperationResult.Attempt.Fail(ex.Operation, evtMsgs);
             }
         }
 
-        public Attempt SaveContainer(EntityContainer container, int userId = Cms.Core.Constants.Security.SuperUserId)
+        return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs);
+    }
+
+    /// 
+    ///     Saves an 
+    /// 
+    ///  to save
+    /// Id of the user issuing the save
+    public void Save(IDataType dataType, int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
+        dataType.CreatorId = userId;
+
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            var evtMsgs = EventMessagesFactory.Get();
+            var saveEventArgs = new SaveEventArgs(dataType);
 
-            if (container.ContainedObjectType != Cms.Core.Constants.ObjectTypes.DataType)
+            var savingDataTypeNotification = new DataTypeSavingNotification(dataType, evtMsgs);
+            if (scope.Notifications.PublishCancelable(savingDataTypeNotification))
             {
-                var ex = new InvalidOperationException("Not a " + Cms.Core.Constants.ObjectTypes.DataType + " container.");
-                return OperationResult.Attempt.Fail(evtMsgs, ex);
+                scope.Complete();
+                return;
             }
 
-            if (container.HasIdentity && container.IsPropertyDirty("ParentId"))
+            if (string.IsNullOrWhiteSpace(dataType.Name))
             {
-                var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead.");
-                return OperationResult.Attempt.Fail(evtMsgs, ex);
+                throw new ArgumentException("Cannot save datatype with empty name.");
             }
 
-            using (var scope = ScopeProvider.CreateCoreScope())
+            if (dataType.Name != null && dataType.Name.Length > 255)
             {
-                var savingEntityContainerNotification = new EntityContainerSavingNotification(container, evtMsgs);
-                if (scope.Notifications.PublishCancelable(savingEntityContainerNotification))
-                {
-                    scope.Complete();
-                    return OperationResult.Attempt.Cancel(evtMsgs);
-                }
+                throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+            }
 
-                _dataTypeContainerRepository.Save(container);
+            _dataTypeRepository.Save(dataType);
 
-                scope.Notifications.Publish(new EntityContainerSavedNotification(container, evtMsgs).WithStateFrom(savingEntityContainerNotification));
-                scope.Complete();
-            }
+            scope.Notifications.Publish(
+                new DataTypeSavedNotification(dataType, evtMsgs).WithStateFrom(savingDataTypeNotification));
 
-            // TODO: Audit trail ?
-            return OperationResult.Attempt.Succeed(evtMsgs);
+            Audit(AuditType.Save, userId, dataType.Id);
+            scope.Complete();
         }
+    }
 
-        public Attempt DeleteContainer(int containerId, int userId = Cms.Core.Constants.Security.SuperUserId)
+    /// 
+    ///     Saves a collection of 
+    /// 
+    ///  to save
+    /// Id of the user issuing the save
+    public void Save(IEnumerable dataTypeDefinitions, int userId)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
+        IDataType[] dataTypeDefinitionsA = dataTypeDefinitions.ToArray();
+
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            var evtMsgs = EventMessagesFactory.Get();
-            using (var scope = ScopeProvider.CreateCoreScope())
+            var savingDataTypeNotification = new DataTypeSavingNotification(dataTypeDefinitions, evtMsgs);
+            if (scope.Notifications.PublishCancelable(savingDataTypeNotification))
             {
-                var container = _dataTypeContainerRepository.Get(containerId);
-                if (container == null) return OperationResult.Attempt.NoOperation(evtMsgs);
-
-                // 'container' here does not know about its children, so we need
-                // to get it again from the entity repository, as a light entity
-                var entity = _entityRepository.Get(container.Id);
-                if (entity?.HasChildren ?? false)
-                {
-                    scope.Complete();
-                    return Attempt.Fail(new OperationResult(OperationResultType.FailedCannot, evtMsgs));
-                }
+                scope.Complete();
+                return;
+            }
 
-                var deletingEntityContainerNotification = new EntityContainerDeletingNotification(container, evtMsgs);
-                if (scope.Notifications.PublishCancelable(deletingEntityContainerNotification))
-                {
-                    scope.Complete();
-                    return Attempt.Fail(new OperationResult(OperationResultType.FailedCancelledByEvent, evtMsgs));
-                }
+            foreach (IDataType dataTypeDefinition in dataTypeDefinitionsA)
+            {
+                dataTypeDefinition.CreatorId = userId;
+                _dataTypeRepository.Save(dataTypeDefinition);
+            }
 
-                _dataTypeContainerRepository.Delete(container);
+            scope.Notifications.Publish(
+                new DataTypeSavedNotification(dataTypeDefinitions, evtMsgs).WithStateFrom(savingDataTypeNotification));
 
-                scope.Notifications.Publish(new EntityContainerDeletedNotification(container, evtMsgs).WithStateFrom(deletingEntityContainerNotification));
-                scope.Complete();
-            }
+            Audit(AuditType.Save, userId, -1);
 
-            // TODO: Audit trail ?
-            return OperationResult.Attempt.Succeed(evtMsgs);
+            scope.Complete();
         }
+    }
 
-        public Attempt?> RenameContainer(int id, string name, int userId = Cms.Core.Constants.Security.SuperUserId)
+    /// 
+    ///     Deletes an 
+    /// 
+    /// 
+    ///     Please note that deleting a  will remove
+    ///     all the  data that references this .
+    /// 
+    ///  to delete
+    /// Optional Id of the user issuing the deletion
+    public void Delete(IDataType dataType, int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            var evtMsgs = EventMessagesFactory.Get();
-            using (var scope = ScopeProvider.CreateCoreScope())
+            var deletingDataTypeNotification = new DataTypeDeletingNotification(dataType, evtMsgs);
+            if (scope.Notifications.PublishCancelable(deletingDataTypeNotification))
             {
-                try
-                {
-                    var container = _dataTypeContainerRepository.Get(id);
-
-                    //throw if null, this will be caught by the catch and a failed returned
-                    if (container == null)
-                        throw new InvalidOperationException("No container found with id " + id);
-
-                    container.Name = name;
+                scope.Complete();
+                return;
+            }
 
-                    var renamingEntityContainerNotification = new EntityContainerRenamingNotification(container, evtMsgs);
-                    if (scope.Notifications.PublishCancelable(renamingEntityContainerNotification))
+            // find ContentTypes using this IDataTypeDefinition on a PropertyType, and delete
+            // TODO: media and members?!
+            // TODO: non-group properties?!
+            IQuery query = Query().Where(x => x.DataTypeId == dataType.Id);
+            IEnumerable contentTypes = _contentTypeRepository.GetByQuery(query);
+            foreach (IContentType contentType in contentTypes)
+            {
+                foreach (PropertyGroup propertyGroup in contentType.PropertyGroups)
+                {
+                    var types = propertyGroup.PropertyTypes?.Where(x => x.DataTypeId == dataType.Id).ToList();
+                    if (types is not null)
                     {
-                        scope.Complete();
-                        return OperationResult.Attempt.Cancel(evtMsgs, container);
+                        foreach (IPropertyType propertyType in types)
+                        {
+                            propertyGroup.PropertyTypes?.Remove(propertyType);
+                        }
                     }
+                }
 
-                    _dataTypeContainerRepository.Save(container);
-                    scope.Complete();
-
-                    scope.Notifications.Publish(new EntityContainerRenamedNotification(container, evtMsgs).WithStateFrom(renamingEntityContainerNotification));
+                // so... we are modifying content types here. the service will trigger Deleted event,
+                // which will propagate to DataTypeCacheRefresher which will clear almost every cache
+                // there is to clear... and in addition published snapshot caches will clear themselves too, so
+                // this is probably safe although it looks... weird.
+                //
+                // what IS weird is that a content type is losing a property and we do NOT raise any
+                // content type event... so ppl better listen on the data type events too.
 
-                    return OperationResult.Attempt.Succeed(OperationResultType.Success, evtMsgs, container);
-                }
-                catch (Exception ex)
-                {
-                    return OperationResult.Attempt.Fail(evtMsgs, ex);
-                }
+                _contentTypeRepository.Save(contentType);
             }
-        }
 
-        #endregion
+            _dataTypeRepository.Delete(dataType);
 
-        /// 
-        /// Gets a  by its Name
-        /// 
-        /// Name of the 
-        /// 
-        public IDataType? GetDataType(string name)
-        {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                var dataType = _dataTypeRepository.Get(Query().Where(x => x.Name == name))?.FirstOrDefault();
-                ConvertMissingEditorOfDataTypeToLabel(dataType);
-                return dataType;
-            }
-        }
+            scope.Notifications.Publish(
+                new DataTypeDeletedNotification(dataType, evtMsgs).WithStateFrom(deletingDataTypeNotification));
 
-        /// 
-        /// Gets a  by its Id
-        /// 
-        /// Id of the 
-        /// 
-        public IDataType? GetDataType(int id)
-        {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                var dataType = _dataTypeRepository.Get(id);
-                ConvertMissingEditorOfDataTypeToLabel(dataType);
-                return dataType;
-            }
-        }
+            Audit(AuditType.Delete, userId, dataType.Id);
 
-        /// 
-        /// Gets a  by its unique guid Id
-        /// 
-        /// Unique guid Id of the DataType
-        /// 
-        public IDataType? GetDataType(Guid id)
-        {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                var query = Query().Where(x => x.Key == id);
-                var dataType = _dataTypeRepository.Get(query).FirstOrDefault();
-                ConvertMissingEditorOfDataTypeToLabel(dataType);
-                return dataType;
-            }
+            scope.Complete();
         }
+    }
 
-        /// 
-        /// Gets a  by its control Id
-        /// 
-        /// Alias of the property editor
-        /// Collection of  objects with a matching control id
-        public IEnumerable GetByEditorAlias(string propertyEditorAlias)
+    public IReadOnlyDictionary> GetReferences(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                var query = Query().Where(x => x.EditorAlias == propertyEditorAlias);
-                var dataType = _dataTypeRepository.Get(query);
-                ConvertMissingEditorsOfDataTypesToLabels(dataType);
-                return dataType;
-            }
+            return _dataTypeRepository.FindUsages(id);
         }
+    }
 
-        /// 
-        /// Gets all  objects or those with the ids passed in
-        /// 
-        /// Optional array of Ids
-        /// An enumerable list of  objects
-        public IEnumerable GetAll(params int[] ids)
+    private void ConvertMissingEditorOfDataTypeToLabel(IDataType? dataType)
+    {
+        if (dataType == null)
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                var dataTypes = _dataTypeRepository.GetMany(ids);
-
-                ConvertMissingEditorsOfDataTypesToLabels(dataTypes);
-                return dataTypes;
-            }
+            return;
         }
 
-        private void ConvertMissingEditorOfDataTypeToLabel(IDataType? dataType)
-        {
-            if (dataType == null)
-            {
-                return;
-            }
-
-            ConvertMissingEditorsOfDataTypesToLabels(new[] { dataType });
-        }
+        ConvertMissingEditorsOfDataTypesToLabels(new[] {dataType});
+    }
 
-        private void ConvertMissingEditorsOfDataTypesToLabels(IEnumerable dataTypes)
+    private void ConvertMissingEditorsOfDataTypesToLabels(IEnumerable dataTypes)
+    {
+        // Any data types that don't have an associated editor are created of a specific type.
+        // We convert them to labels to make clear to the user why the data type cannot be used.
+        IEnumerable dataTypesWithMissingEditors = dataTypes
+            .Where(x => x.Editor is MissingPropertyEditor);
+        foreach (IDataType dataType in dataTypesWithMissingEditors)
         {
-            // Any data types that don't have an associated editor are created of a specific type.
-            // We convert them to labels to make clear to the user why the data type cannot be used.
-            var dataTypesWithMissingEditors = dataTypes
-                .Where(x => x.Editor is MissingPropertyEditor);
-            foreach (var dataType in dataTypesWithMissingEditors)
-            {
-                dataType.Editor = new LabelPropertyEditor(_dataValueEditorFactory, _ioHelper, _editorConfigurationParser);
-            }
+            dataType.Editor = new LabelPropertyEditor(_dataValueEditorFactory, _ioHelper, _editorConfigurationParser);
         }
+    }
 
-        public Attempt?> Move(IDataType toMove, int parentId)
-        {
-            var evtMsgs = EventMessagesFactory.Get();
-            var moveInfo = new List>();
+    private void Audit(AuditType type, int userId, int objectId) =>
+        _auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.DataType.GetName()));
+
+    #region Containers
 
-            using (var scope = ScopeProvider.CreateCoreScope())
+    public Attempt?> CreateContainer(int parentId, Guid key,
+        string name, int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            try
             {
-                var moveEventInfo = new MoveEventInfo(toMove, toMove.Path, parentId);
+                var container = new EntityContainer(Constants.ObjectTypes.DataType)
+                {
+                    Name = name, ParentId = parentId, CreatorId = userId, Key = key
+                };
 
-                var movingDataTypeNotification = new DataTypeMovingNotification(moveEventInfo, evtMsgs);
-                if (scope.Notifications.PublishCancelable(movingDataTypeNotification))
+                var savingEntityContainerNotification = new EntityContainerSavingNotification(container, evtMsgs);
+                if (scope.Notifications.PublishCancelable(savingEntityContainerNotification))
                 {
                     scope.Complete();
-                    return OperationResult.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs);
+                    return OperationResult.Attempt.Cancel(evtMsgs, container);
                 }
 
-                try
-                {
-                    EntityContainer? container = null;
-                    if (parentId > 0)
-                    {
-                        container = _dataTypeContainerRepository.Get(parentId);
-                        if (container == null)
-                            throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); // causes rollback
-                    }
-                    moveInfo.AddRange(_dataTypeRepository.Move(toMove, container));
+                _dataTypeContainerRepository.Save(container);
+                scope.Complete();
 
-                    scope.Notifications.Publish(new DataTypeMovedNotification(moveEventInfo, evtMsgs).WithStateFrom(movingDataTypeNotification));
+                scope.Notifications.Publish(
+                    new EntityContainerSavedNotification(container, evtMsgs).WithStateFrom(
+                        savingEntityContainerNotification));
 
-                    scope.Complete();
-                }
-                catch (DataOperationException ex)
-                {
-                    scope.Complete(); // TODO: what are we doing here exactly?
-                    return OperationResult.Attempt.Fail(ex.Operation, evtMsgs);
-                }
+                // TODO: Audit trail ?
+
+                return OperationResult.Attempt.Succeed(evtMsgs, container);
             }
+            catch (Exception ex)
+            {
+                return OperationResult.Attempt.Fail(evtMsgs, ex);
+            }
+        }
+    }
 
-            return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs);
+    public EntityContainer? GetContainer(int containerId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return _dataTypeContainerRepository.Get(containerId);
         }
+    }
 
-        /// 
-        /// Saves an 
-        /// 
-        ///  to save
-        /// Id of the user issuing the save
-        public void Save(IDataType dataType, int userId = Cms.Core.Constants.Security.SuperUserId)
+    public EntityContainer? GetContainer(Guid containerId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            var evtMsgs = EventMessagesFactory.Get();
-            dataType.CreatorId = userId;
+            return _dataTypeContainerRepository.Get(containerId);
+        }
+    }
 
-            using (var scope = ScopeProvider.CreateCoreScope())
+    public IEnumerable GetContainers(string name, int level)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return _dataTypeContainerRepository.Get(name, level);
+        }
+    }
+
+    public IEnumerable GetContainers(IDataType dataType)
+    {
+        var ancestorIds = dataType.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)
+            .Select(x =>
             {
-                var saveEventArgs = new SaveEventArgs(dataType);
+                Attempt asInt = x.TryConvertTo();
+                return asInt.Success ? asInt.Result : int.MinValue;
+            })
+            .Where(x => x != int.MinValue && x != dataType.Id)
+            .ToArray();
 
-                var savingDataTypeNotification = new DataTypeSavingNotification(dataType, evtMsgs);
-                if (scope.Notifications.PublishCancelable(savingDataTypeNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
+        return GetContainers(ancestorIds);
+    }
 
-                if (string.IsNullOrWhiteSpace(dataType.Name))
-                {
-                    throw new ArgumentException("Cannot save datatype with empty name.");
-                }
+    public IEnumerable GetContainers(int[] containerIds)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return _dataTypeContainerRepository.GetMany(containerIds);
+        }
+    }
 
-                if (dataType.Name != null && dataType.Name.Length > 255)
-                {
-                    throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
-                }
+    public Attempt SaveContainer(EntityContainer container,
+        int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
 
-                _dataTypeRepository.Save(dataType);
+        if (container.ContainedObjectType != Constants.ObjectTypes.DataType)
+        {
+            var ex = new InvalidOperationException("Not a " + Constants.ObjectTypes.DataType + " container.");
+            return OperationResult.Attempt.Fail(evtMsgs, ex);
+        }
 
-                scope.Notifications.Publish(new DataTypeSavedNotification(dataType, evtMsgs).WithStateFrom(savingDataTypeNotification));
+        if (container.HasIdentity && container.IsPropertyDirty("ParentId"))
+        {
+            var ex = new InvalidOperationException(
+                "Cannot save a container with a modified parent, move the container instead.");
+            return OperationResult.Attempt.Fail(evtMsgs, ex);
+        }
 
-                Audit(AuditType.Save, userId, dataType.Id);
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            var savingEntityContainerNotification = new EntityContainerSavingNotification(container, evtMsgs);
+            if (scope.Notifications.PublishCancelable(savingEntityContainerNotification))
+            {
                 scope.Complete();
+                return OperationResult.Attempt.Cancel(evtMsgs);
             }
-        }
 
-        /// 
-        /// Saves a collection of 
-        /// 
-        ///  to save
-        /// Id of the user issuing the save
-        public void Save(IEnumerable dataTypeDefinitions, int userId)
-        {
-            var evtMsgs = EventMessagesFactory.Get();
-            var dataTypeDefinitionsA = dataTypeDefinitions.ToArray();
+            _dataTypeContainerRepository.Save(container);
 
-            using (var scope = ScopeProvider.CreateCoreScope())
-            {
-                var savingDataTypeNotification = new DataTypeSavingNotification(dataTypeDefinitions, evtMsgs);
-                if (scope.Notifications.PublishCancelable(savingDataTypeNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
+            scope.Notifications.Publish(
+                new EntityContainerSavedNotification(container, evtMsgs).WithStateFrom(
+                    savingEntityContainerNotification));
+            scope.Complete();
+        }
 
-                foreach (var dataTypeDefinition in dataTypeDefinitionsA)
-                {
-                    dataTypeDefinition.CreatorId = userId;
-                    _dataTypeRepository.Save(dataTypeDefinition);
-                }
+        // TODO: Audit trail ?
+        return OperationResult.Attempt.Succeed(evtMsgs);
+    }
 
-                scope.Notifications.Publish(new DataTypeSavedNotification(dataTypeDefinitions, evtMsgs).WithStateFrom(savingDataTypeNotification));
+    public Attempt DeleteContainer(int containerId, int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            EntityContainer container = _dataTypeContainerRepository.Get(containerId);
+            if (container == null)
+            {
+                return OperationResult.Attempt.NoOperation(evtMsgs);
+            }
 
-                Audit(AuditType.Save, userId, -1);
+            // 'container' here does not know about its children, so we need
+            // to get it again from the entity repository, as a light entity
+            IEntitySlim entity = _entityRepository.Get(container.Id);
+            if (entity?.HasChildren ?? false)
+            {
+                scope.Complete();
+                return Attempt.Fail(new OperationResult(OperationResultType.FailedCannot, evtMsgs));
+            }
 
+            var deletingEntityContainerNotification = new EntityContainerDeletingNotification(container, evtMsgs);
+            if (scope.Notifications.PublishCancelable(deletingEntityContainerNotification))
+            {
                 scope.Complete();
+                return Attempt.Fail(new OperationResult(OperationResultType.FailedCancelledByEvent, evtMsgs));
             }
+
+            _dataTypeContainerRepository.Delete(container);
+
+            scope.Notifications.Publish(
+                new EntityContainerDeletedNotification(container, evtMsgs).WithStateFrom(
+                    deletingEntityContainerNotification));
+            scope.Complete();
         }
 
-        /// 
-        /// Deletes an 
-        /// 
-        /// 
-        /// Please note that deleting a  will remove
-        /// all the  data that references this .
-        /// 
-        ///  to delete
-        /// Optional Id of the user issuing the deletion
-        public void Delete(IDataType dataType, int userId = Cms.Core.Constants.Security.SuperUserId)
+        // TODO: Audit trail ?
+        return OperationResult.Attempt.Succeed(evtMsgs);
+    }
+
+    public Attempt?> RenameContainer(int id, string name,
+        int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            var evtMsgs = EventMessagesFactory.Get();
-            using (var scope = ScopeProvider.CreateCoreScope())
+            try
             {
-                var deletingDataTypeNotification = new DataTypeDeletingNotification(dataType, evtMsgs);
-                if (scope.Notifications.PublishCancelable(deletingDataTypeNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
+                EntityContainer container = _dataTypeContainerRepository.Get(id);
 
-                // find ContentTypes using this IDataTypeDefinition on a PropertyType, and delete
-                // TODO: media and members?!
-                // TODO: non-group properties?!
-                var query = Query().Where(x => x.DataTypeId == dataType.Id);
-                var contentTypes = _contentTypeRepository.GetByQuery(query);
-                foreach (var contentType in contentTypes)
+                //throw if null, this will be caught by the catch and a failed returned
+                if (container == null)
                 {
-                    foreach (var propertyGroup in contentType.PropertyGroups)
-                    {
-                        var types = propertyGroup.PropertyTypes?.Where(x => x.DataTypeId == dataType.Id).ToList();
-                        if (types is not null)
-                        {
-                            foreach (var propertyType in types)
-                            {
-                                propertyGroup.PropertyTypes?.Remove(propertyType);
-                            }
-                        }
-                    }
+                    throw new InvalidOperationException("No container found with id " + id);
+                }
 
-                    // so... we are modifying content types here. the service will trigger Deleted event,
-                    // which will propagate to DataTypeCacheRefresher which will clear almost every cache
-                    // there is to clear... and in addition published snapshot caches will clear themselves too, so
-                    // this is probably safe although it looks... weird.
-                    //
-                    // what IS weird is that a content type is losing a property and we do NOT raise any
-                    // content type event... so ppl better listen on the data type events too.
+                container.Name = name;
 
-                    _contentTypeRepository.Save(contentType);
+                var renamingEntityContainerNotification = new EntityContainerRenamingNotification(container, evtMsgs);
+                if (scope.Notifications.PublishCancelable(renamingEntityContainerNotification))
+                {
+                    scope.Complete();
+                    return OperationResult.Attempt.Cancel(evtMsgs, container);
                 }
 
-                _dataTypeRepository.Delete(dataType);
-
-                scope.Notifications.Publish(new DataTypeDeletedNotification(dataType, evtMsgs).WithStateFrom(deletingDataTypeNotification));
+                _dataTypeContainerRepository.Save(container);
+                scope.Complete();
 
-                Audit(AuditType.Delete, userId, dataType.Id);
+                scope.Notifications.Publish(
+                    new EntityContainerRenamedNotification(container, evtMsgs).WithStateFrom(
+                        renamingEntityContainerNotification));
 
-                scope.Complete();
+                return OperationResult.Attempt.Succeed(OperationResultType.Success, evtMsgs, container);
             }
-        }
-
-        public IReadOnlyDictionary> GetReferences(int id)
-        {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete:true))
+            catch (Exception ex)
             {
-                return _dataTypeRepository.FindUsages(id);
+                return OperationResult.Attempt.Fail(evtMsgs, ex);
             }
         }
-
-        private void Audit(AuditType type, int userId, int objectId)
-        {
-            _auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.DataType)));
-        }
-
     }
+
+    #endregion
 }
diff --git a/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs
index 312b939ec5fd..5c737f665f76 100644
--- a/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs
+++ b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs
@@ -1,21 +1,25 @@
-using System;
+using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.PropertyEditors;
 using Umbraco.Cms.Core.Services;
 
-namespace Umbraco.Extensions
+namespace Umbraco.Extensions;
+
+public static class DateTypeServiceExtensions
 {
-    public static class DateTypeServiceExtensions
+    public static bool IsDataTypeIgnoringUserStartNodes(this IDataTypeService dataTypeService, Guid key)
     {
-        public static bool IsDataTypeIgnoringUserStartNodes(this IDataTypeService dataTypeService, Guid key)
+        if (DataTypeExtensions.IsBuildInDataType(key))
         {
-            if (DataTypeExtensions.IsBuildInDataType(key)) return false; //built in ones can never be ignoring start nodes
-
-            var dataType = dataTypeService.GetDataType(key);
+            return false; //built in ones can never be ignoring start nodes
+        }
 
-            if (dataType != null && dataType.Configuration is IIgnoreUserStartNodesConfig ignoreStartNodesConfig)
-                return ignoreStartNodesConfig.IgnoreUserStartNodes;
+        IDataType dataType = dataTypeService.GetDataType(key);
 
-            return false;
+        if (dataType != null && dataType.Configuration is IIgnoreUserStartNodesConfig ignoreStartNodesConfig)
+        {
+            return ignoreStartNodesConfig.IgnoreUserStartNodes;
         }
+
+        return false;
     }
 }
diff --git a/src/Umbraco.Core/Services/DefaultContentVersionCleanupPolicy.cs b/src/Umbraco.Core/Services/DefaultContentVersionCleanupPolicy.cs
index 810106e0ba3d..dc2cbe3b07e8 100644
--- a/src/Umbraco.Core/Services/DefaultContentVersionCleanupPolicy.cs
+++ b/src/Umbraco.Core/Services/DefaultContentVersionCleanupPolicy.cs
@@ -1,6 +1,3 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
 using Microsoft.Extensions.Options;
 using Umbraco.Cms.Core.Configuration.Models;
 using Umbraco.Cms.Core.Models;
@@ -8,93 +5,91 @@
 using Umbraco.Cms.Core.Scoping;
 using ContentVersionCleanupPolicySettings = Umbraco.Cms.Core.Models.ContentVersionCleanupPolicySettings;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public class DefaultContentVersionCleanupPolicy : IContentVersionCleanupPolicy
 {
-    public class DefaultContentVersionCleanupPolicy : IContentVersionCleanupPolicy
+    private readonly IOptions _contentSettings;
+    private readonly IDocumentVersionRepository _documentVersionRepository;
+    private readonly ICoreScopeProvider _scopeProvider;
+
+    public DefaultContentVersionCleanupPolicy(IOptions contentSettings,
+        ICoreScopeProvider scopeProvider, IDocumentVersionRepository documentVersionRepository)
     {
-        private readonly IOptions _contentSettings;
-        private readonly ICoreScopeProvider _scopeProvider;
-        private readonly IDocumentVersionRepository _documentVersionRepository;
+        _contentSettings = contentSettings ?? throw new ArgumentNullException(nameof(contentSettings));
+        _scopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider));
+        _documentVersionRepository = documentVersionRepository ??
+                                     throw new ArgumentNullException(nameof(documentVersionRepository));
+    }
 
-        public DefaultContentVersionCleanupPolicy(IOptions contentSettings, ICoreScopeProvider scopeProvider, IDocumentVersionRepository documentVersionRepository)
-        {
-            _contentSettings = contentSettings ?? throw new ArgumentNullException(nameof(contentSettings));
-            _scopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider));
-            _documentVersionRepository = documentVersionRepository ?? throw new ArgumentNullException(nameof(documentVersionRepository));
-        }
+    public IEnumerable Apply(DateTime asAtDate, IEnumerable items)
+    {
+        // Note: Not checking global enable flag, that's handled in the scheduled job.
+        // If this method is called and policy is globally disabled someone has chosen to run in code.
 
-        public IEnumerable Apply(DateTime asAtDate, IEnumerable items)
-        {
-            // Note: Not checking global enable flag, that's handled in the scheduled job.
-            // If this method is called and policy is globally disabled someone has chosen to run in code.
+        Configuration.Models.ContentVersionCleanupPolicySettings globalPolicy =
+            _contentSettings.Value.ContentVersionCleanupPolicy;
 
-            var globalPolicy = _contentSettings.Value.ContentVersionCleanupPolicy;
+        var theRest = new List();
 
-            var theRest = new List();
+        using (_scopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            var policyOverrides = _documentVersionRepository.GetCleanupPolicies()?
+                .ToDictionary(x => x.ContentTypeId);
 
-            using(_scopeProvider.CreateCoreScope(autoComplete: true))
+            foreach (ContentVersionMeta version in items)
             {
-                var policyOverrides = _documentVersionRepository.GetCleanupPolicies()?
-                    .ToDictionary(x => x.ContentTypeId);
-
-                foreach (var version in items)
-                {
-                    var age = asAtDate - version.VersionDate;
-
-                    var overrides = GetOverridePolicy(version, policyOverrides);
+                TimeSpan age = asAtDate - version.VersionDate;
 
-                    var keepAll = overrides?.KeepAllVersionsNewerThanDays ?? globalPolicy.KeepAllVersionsNewerThanDays!;
-                    var keepLatest = overrides?.KeepLatestVersionPerDayForDays ?? globalPolicy.KeepLatestVersionPerDayForDays;
-                    var preventCleanup = overrides?.PreventCleanup ?? false;
+                ContentVersionCleanupPolicySettings overrides = GetOverridePolicy(version, policyOverrides);
 
-                    if (preventCleanup)
-                    {
-                        continue;
-                    }
+                var keepAll = overrides?.KeepAllVersionsNewerThanDays ?? globalPolicy.KeepAllVersionsNewerThanDays!;
+                var keepLatest = overrides?.KeepLatestVersionPerDayForDays ??
+                                 globalPolicy.KeepLatestVersionPerDayForDays;
+                var preventCleanup = overrides?.PreventCleanup ?? false;
 
-                    if (age.TotalDays <= keepAll)
-                    {
-                        continue;
-                    }
-
-                    if (age.TotalDays > keepLatest)
-                    {
-
-                        yield return version;
-                        continue;
-                    }
+                if (preventCleanup)
+                {
+                    continue;
+                }
 
-                    theRest.Add(version);
+                if (age.TotalDays <= keepAll)
+                {
+                    continue;
                 }
 
-                var grouped = theRest.GroupBy(x => new
+                if (age.TotalDays > keepLatest)
                 {
-                    x.ContentId,
-                    x.VersionDate.Date
-                });
+                    yield return version;
+                    continue;
+                }
 
-                foreach (var group in grouped)
+                theRest.Add(version);
+            }
+
+            var grouped = theRest.GroupBy(x => new {x.ContentId, x.VersionDate.Date});
+
+            foreach (var group in grouped)
+            {
+                foreach (ContentVersionMeta version in group.OrderByDescending(x => x.VersionId).Skip(1))
                 {
-                    foreach (var version in group.OrderByDescending(x => x.VersionId).Skip(1))
-                    {
-                        yield return version;
-                    }
+                    yield return version;
                 }
             }
         }
+    }
 
-        private ContentVersionCleanupPolicySettings? GetOverridePolicy(
-            ContentVersionMeta version,
-            IDictionary? overrides)
+    private ContentVersionCleanupPolicySettings? GetOverridePolicy(
+        ContentVersionMeta version,
+        IDictionary? overrides)
+    {
+        if (overrides is null)
         {
-            if (overrides is null)
-            {
-                return null;
-            }
+            return null;
+        }
 
-            _ = overrides.TryGetValue(version.ContentTypeId, out var value);
+        _ = overrides.TryGetValue(version.ContentTypeId, out ContentVersionCleanupPolicySettings value);
 
-            return value;
-        }
+        return value;
     }
 }
diff --git a/src/Umbraco.Core/Services/DomainService.cs b/src/Umbraco.Core/Services/DomainService.cs
index b319f0fc42a6..bc24812c1dc1 100644
--- a/src/Umbraco.Core/Services/DomainService.cs
+++ b/src/Umbraco.Core/Services/DomainService.cs
@@ -1,105 +1,104 @@
-using System.Collections.Generic;
-using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging;
 using Umbraco.Cms.Core.Events;
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Notifications;
 using Umbraco.Cms.Core.Persistence.Repositories;
 using Umbraco.Cms.Core.Scoping;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public class DomainService : RepositoryService, IDomainService
 {
-    public class DomainService : RepositoryService, IDomainService
-    {
-        private readonly IDomainRepository _domainRepository;
+    private readonly IDomainRepository _domainRepository;
 
-        public DomainService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory,
-            IDomainRepository domainRepository)
-            : base(provider, loggerFactory, eventMessagesFactory)
-        {
-            _domainRepository = domainRepository;
-        }
+    public DomainService(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
+        IEventMessagesFactory eventMessagesFactory,
+        IDomainRepository domainRepository)
+        : base(provider, loggerFactory, eventMessagesFactory) =>
+        _domainRepository = domainRepository;
 
-        public bool Exists(string domainName)
+    public bool Exists(string domainName)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _domainRepository.Exists(domainName);
-            }
+            return _domainRepository.Exists(domainName);
         }
+    }
 
-        public Attempt Delete(IDomain domain)
-        {
-            EventMessages eventMessages = EventMessagesFactory.Get();
+    public Attempt Delete(IDomain domain)
+    {
+        EventMessages eventMessages = EventMessagesFactory.Get();
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            var deletingNotification = new DomainDeletingNotification(domain, eventMessages);
+            if (scope.Notifications.PublishCancelable(deletingNotification))
             {
-                var deletingNotification = new DomainDeletingNotification(domain, eventMessages);
-                if (scope.Notifications.PublishCancelable(deletingNotification))
-                {
-                    scope.Complete();
-                    return OperationResult.Attempt.Cancel(eventMessages);
-                }
-
-                _domainRepository.Delete(domain);
                 scope.Complete();
-
-                scope.Notifications.Publish(new DomainDeletedNotification(domain, eventMessages).WithStateFrom(deletingNotification));
+                return OperationResult.Attempt.Cancel(eventMessages);
             }
 
-            return OperationResult.Attempt.Succeed(eventMessages);
+            _domainRepository.Delete(domain);
+            scope.Complete();
+
+            scope.Notifications.Publish(
+                new DomainDeletedNotification(domain, eventMessages).WithStateFrom(deletingNotification));
         }
 
-        public IDomain? GetByName(string name)
+        return OperationResult.Attempt.Succeed(eventMessages);
+    }
+
+    public IDomain? GetByName(string name)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _domainRepository.GetByName(name);
-            }
+            return _domainRepository.GetByName(name);
         }
+    }
 
-        public IDomain? GetById(int id)
+    public IDomain? GetById(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _domainRepository.Get(id);
-            }
+            return _domainRepository.Get(id);
         }
+    }
 
-        public IEnumerable GetAll(bool includeWildcards)
+    public IEnumerable GetAll(bool includeWildcards)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _domainRepository.GetAll(includeWildcards);
-            }
+            return _domainRepository.GetAll(includeWildcards);
         }
+    }
 
-        public IEnumerable GetAssignedDomains(int contentId, bool includeWildcards)
+    public IEnumerable GetAssignedDomains(int contentId, bool includeWildcards)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _domainRepository.GetAssignedDomains(contentId, includeWildcards);
-            }
+            return _domainRepository.GetAssignedDomains(contentId, includeWildcards);
         }
+    }
 
-        public Attempt Save(IDomain domainEntity)
-        {
-            EventMessages eventMessages = EventMessagesFactory.Get();
+    public Attempt Save(IDomain domainEntity)
+    {
+        EventMessages eventMessages = EventMessagesFactory.Get();
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            var savingNotification = new DomainSavingNotification(domainEntity, eventMessages);
+            if (scope.Notifications.PublishCancelable(savingNotification))
             {
-                var savingNotification = new DomainSavingNotification(domainEntity, eventMessages);
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    scope.Complete();
-                    return OperationResult.Attempt.Cancel(eventMessages);
-                }
-
-                _domainRepository.Save(domainEntity);
                 scope.Complete();
-                scope.Notifications.Publish(new DomainSavedNotification(domainEntity, eventMessages).WithStateFrom(savingNotification));
+                return OperationResult.Attempt.Cancel(eventMessages);
             }
 
-            return OperationResult.Attempt.Succeed(eventMessages);
+            _domainRepository.Save(domainEntity);
+            scope.Complete();
+            scope.Notifications.Publish(
+                new DomainSavedNotification(domainEntity, eventMessages).WithStateFrom(savingNotification));
         }
+
+        return OperationResult.Attempt.Succeed(eventMessages);
     }
 }
diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs
index 5fa7ed24f739..4368a3f84e62 100644
--- a/src/Umbraco.Core/Services/EntityService.cs
+++ b/src/Umbraco.Core/Services/EntityService.cs
@@ -1,6 +1,3 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
 using System.Linq.Expressions;
 using Microsoft.Extensions.Logging;
 using Umbraco.Cms.Core.Events;
@@ -11,472 +8,500 @@
 using Umbraco.Cms.Core.Persistence.Repositories;
 using Umbraco.Cms.Core.Scoping;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public class EntityService : RepositoryService, IEntityService
 {
-    public class EntityService : RepositoryService, IEntityService
+    private readonly IEntityRepository _entityRepository;
+    private readonly IIdKeyMap _idKeyMap;
+    private readonly Dictionary _objectTypes;
+    private IQuery? _queryRootEntity;
+
+    public EntityService(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
+        IEventMessagesFactory eventMessagesFactory, IIdKeyMap idKeyMap, IEntityRepository entityRepository)
+        : base(provider, loggerFactory, eventMessagesFactory)
     {
-        private readonly IEntityRepository _entityRepository;
-        private readonly Dictionary _objectTypes;
-        private IQuery? _queryRootEntity;
-        private readonly IIdKeyMap _idKeyMap;
-
-        public EntityService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IIdKeyMap idKeyMap, IEntityRepository entityRepository)
-            : base(provider, loggerFactory, eventMessagesFactory)
-        {
-            _idKeyMap = idKeyMap;
-            _entityRepository = entityRepository;
-
-            _objectTypes = new Dictionary
-            {
-                { typeof (IDataType).FullName!, UmbracoObjectTypes.DataType },
-                { typeof (IContent).FullName!, UmbracoObjectTypes.Document },
-                { typeof (IContentType).FullName!, UmbracoObjectTypes.DocumentType },
-                { typeof (IMedia).FullName!, UmbracoObjectTypes.Media },
-                { typeof (IMediaType).FullName!, UmbracoObjectTypes.MediaType },
-                { typeof (IMember).FullName!, UmbracoObjectTypes.Member },
-                { typeof (IMemberType).FullName!, UmbracoObjectTypes.MemberType },
-            };
-        }
+        _idKeyMap = idKeyMap;
+        _entityRepository = entityRepository;
+
+        _objectTypes = new Dictionary
+        {
+            {typeof(IDataType).FullName!, UmbracoObjectTypes.DataType},
+            {typeof(IContent).FullName!, UmbracoObjectTypes.Document},
+            {typeof(IContentType).FullName!, UmbracoObjectTypes.DocumentType},
+            {typeof(IMedia).FullName!, UmbracoObjectTypes.Media},
+            {typeof(IMediaType).FullName!, UmbracoObjectTypes.MediaType},
+            {typeof(IMember).FullName!, UmbracoObjectTypes.Member},
+            {typeof(IMemberType).FullName!, UmbracoObjectTypes.MemberType}
+        };
+    }
 
-        #region Static Queries
+    #region Static Queries
 
-        // lazy-constructed because when the ctor runs, the query factory may not be ready
-        private IQuery QueryRootEntity => _queryRootEntity
-            ?? (_queryRootEntity = Query().Where(x => x.ParentId == -1));
+    // lazy-constructed because when the ctor runs, the query factory may not be ready
+    private IQuery QueryRootEntity => _queryRootEntity
+                                                      ?? (_queryRootEntity = Query()
+                                                          .Where(x => x.ParentId == -1));
 
-        #endregion
+    #endregion
 
-        // gets the object type, throws if not supported
-        private UmbracoObjectTypes GetObjectType(Type ?type)
+    /// 
+    public IEntitySlim? Get(int id)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            if (type?.FullName == null || !_objectTypes.TryGetValue(type.FullName, out var objType))
-                throw new NotSupportedException($"Type \"{type?.FullName ?? ""}\" is not supported here.");
-            return objType;
+            return _entityRepository.Get(id);
         }
+    }
 
-        /// 
-        public IEntitySlim? Get(int id)
+    /// 
+    public IEntitySlim? Get(Guid key)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _entityRepository.Get(id);
-            }
+            return _entityRepository.Get(key);
         }
+    }
 
-        /// 
-        public IEntitySlim? Get(Guid key)
+    /// 
+    public virtual IEntitySlim? Get(int id, UmbracoObjectTypes objectType)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _entityRepository.Get(key);
-            }
+            return _entityRepository.Get(id, objectType.GetGuid());
         }
+    }
 
-        /// 
-        public virtual IEntitySlim? Get(int id, UmbracoObjectTypes objectType)
+    /// 
+    public IEntitySlim? Get(Guid key, UmbracoObjectTypes objectType)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _entityRepository.Get(id, objectType.GetGuid());
-            }
+            return _entityRepository.Get(key, objectType.GetGuid());
         }
+    }
 
-        /// 
-        public IEntitySlim? Get(Guid key, UmbracoObjectTypes objectType)
+    /// 
+    public virtual IEntitySlim? Get(int id)
+        where T : IUmbracoEntity
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _entityRepository.Get(key, objectType.GetGuid());
-            }
+            return _entityRepository.Get(id);
         }
+    }
 
-        /// 
-        public virtual IEntitySlim? Get(int id)
-            where T : IUmbracoEntity
+    /// 
+    public virtual IEntitySlim? Get(Guid key)
+        where T : IUmbracoEntity
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _entityRepository.Get(id);
-            }
+            return _entityRepository.Get(key);
         }
+    }
 
-        /// 
-        public virtual IEntitySlim? Get(Guid key)
-            where T : IUmbracoEntity
+    /// 
+    public bool Exists(int id)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _entityRepository.Get(key);
-            }
+            return _entityRepository.Exists(id);
         }
+    }
 
-        /// 
-        public bool Exists(int id)
+    /// 
+    public bool Exists(Guid key)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _entityRepository.Exists(id);
-            }
+            return _entityRepository.Exists(key);
         }
+    }
 
-        /// 
-        public bool Exists(Guid key)
-        {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _entityRepository.Exists(key);
-            }
-        }
 
+    /// 
+    public virtual IEnumerable GetAll() where T : IUmbracoEntity
+        => GetAll(Array.Empty());
 
-        /// 
-        public virtual IEnumerable GetAll() where T : IUmbracoEntity
-            => GetAll(Array.Empty());
+    /// 
+    public virtual IEnumerable GetAll(params int[] ids)
+        where T : IUmbracoEntity
+    {
+        Type entityType = typeof(T);
+        UmbracoObjectTypes objectType = GetObjectType(entityType);
+        Guid objectTypeId = objectType.GetGuid();
 
-        /// 
-        public virtual IEnumerable GetAll(params int[] ids)
-            where T : IUmbracoEntity
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            var entityType = typeof (T);
-            var objectType = GetObjectType(entityType);
-            var objectTypeId = objectType.GetGuid();
-
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _entityRepository.GetAll(objectTypeId, ids);
-            }
+            return _entityRepository.GetAll(objectTypeId, ids);
         }
+    }
 
-        /// 
-        public virtual IEnumerable GetAll(UmbracoObjectTypes objectType)
-            => GetAll(objectType, Array.Empty());
+    /// 
+    public virtual IEnumerable GetAll(UmbracoObjectTypes objectType)
+        => GetAll(objectType, Array.Empty());
 
-        /// 
-        public virtual IEnumerable GetAll(UmbracoObjectTypes objectType, params int[] ids)
+    /// 
+    public virtual IEnumerable GetAll(UmbracoObjectTypes objectType, params int[] ids)
+    {
+        Type entityType = objectType.GetClrType();
+        if (entityType == null)
         {
-            var entityType = objectType.GetClrType();
-            if (entityType == null)
-                throw new NotSupportedException($"Type \"{objectType}\" is not supported here.");
+            throw new NotSupportedException($"Type \"{objectType}\" is not supported here.");
+        }
 
-            GetObjectType(entityType);
+        GetObjectType(entityType);
 
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _entityRepository.GetAll(objectType.GetGuid(), ids);
-            }
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return _entityRepository.GetAll(objectType.GetGuid(), ids);
         }
+    }
 
-        /// 
-        public virtual IEnumerable GetAll(Guid objectType)
-            => GetAll(objectType, Array.Empty());
+    /// 
+    public virtual IEnumerable GetAll(Guid objectType)
+        => GetAll(objectType, Array.Empty());
 
-        /// 
-        public virtual IEnumerable GetAll(Guid objectType, params int[] ids)
-        {
-            var entityType = ObjectTypes.GetClrType(objectType);
-            GetObjectType(entityType);
+    /// 
+    public virtual IEnumerable GetAll(Guid objectType, params int[] ids)
+    {
+        Type entityType = ObjectTypes.GetClrType(objectType);
+        GetObjectType(entityType);
 
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _entityRepository.GetAll(objectType, ids);
-            }
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return _entityRepository.GetAll(objectType, ids);
         }
+    }
 
-        /// 
-        public virtual IEnumerable GetAll(params Guid[] keys)
-            where T : IUmbracoEntity
-        {
-            var entityType = typeof (T);
-            var objectType = GetObjectType(entityType);
-            var objectTypeId = objectType.GetGuid();
+    /// 
+    public virtual IEnumerable GetAll(params Guid[] keys)
+        where T : IUmbracoEntity
+    {
+        Type entityType = typeof(T);
+        UmbracoObjectTypes objectType = GetObjectType(entityType);
+        Guid objectTypeId = objectType.GetGuid();
 
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _entityRepository.GetAll(objectTypeId, keys);
-            }
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return _entityRepository.GetAll(objectTypeId, keys);
         }
+    }
 
-        /// 
-        public IEnumerable GetAll(UmbracoObjectTypes objectType, Guid[] keys)
-        {
-            var entityType = objectType.GetClrType();
-            GetObjectType(entityType);
+    /// 
+    public IEnumerable GetAll(UmbracoObjectTypes objectType, Guid[] keys)
+    {
+        Type entityType = objectType.GetClrType();
+        GetObjectType(entityType);
 
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _entityRepository.GetAll(objectType.GetGuid(), keys);
-            }
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return _entityRepository.GetAll(objectType.GetGuid(), keys);
         }
+    }
 
-        /// 
-        public virtual IEnumerable GetAll(Guid objectType, params Guid[] keys)
-        {
-            var entityType = ObjectTypes.GetClrType(objectType);
-            GetObjectType(entityType);
+    /// 
+    public virtual IEnumerable GetAll(Guid objectType, params Guid[] keys)
+    {
+        Type entityType = ObjectTypes.GetClrType(objectType);
+        GetObjectType(entityType);
 
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _entityRepository.GetAll(objectType, keys);
-            }
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return _entityRepository.GetAll(objectType, keys);
         }
+    }
 
-        /// 
-        public virtual IEnumerable GetRootEntities(UmbracoObjectTypes objectType)
+    /// 
+    public virtual IEnumerable GetRootEntities(UmbracoObjectTypes objectType)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _entityRepository.GetByQuery(QueryRootEntity, objectType.GetGuid());
-            }
+            return _entityRepository.GetByQuery(QueryRootEntity, objectType.GetGuid());
         }
+    }
 
-        /// 
-        public virtual IEntitySlim? GetParent(int id)
+    /// 
+    public virtual IEntitySlim? GetParent(int id)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
+            IEntitySlim entity = _entityRepository.Get(id);
+            if (entity is null || entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21)
             {
-                var entity = _entityRepository.Get(id);
-                if (entity is null || entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21)
-                    return null;
-                return _entityRepository.Get(entity.ParentId);
+                return null;
             }
+
+            return _entityRepository.Get(entity.ParentId);
         }
+    }
 
-        /// 
-        public virtual IEntitySlim? GetParent(int id, UmbracoObjectTypes objectType)
+    /// 
+    public virtual IEntitySlim? GetParent(int id, UmbracoObjectTypes objectType)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
+            IEntitySlim entity = _entityRepository.Get(id);
+            if (entity is null || entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21)
             {
-                var entity = _entityRepository.Get(id);
-                if (entity is null || entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21)
-                    return null;
-                return _entityRepository.Get(entity.ParentId, objectType.GetGuid());
+                return null;
             }
+
+            return _entityRepository.Get(entity.ParentId, objectType.GetGuid());
         }
+    }
 
-        /// 
-        public virtual IEnumerable GetChildren(int parentId)
+    /// 
+    public virtual IEnumerable GetChildren(int parentId)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                var query = Query().Where(x => x.ParentId == parentId);
-                return _entityRepository.GetByQuery(query);
-            }
+            IQuery query = Query().Where(x => x.ParentId == parentId);
+            return _entityRepository.GetByQuery(query);
         }
+    }
 
-        /// 
-        public virtual IEnumerable GetChildren(int parentId, UmbracoObjectTypes objectType)
+    /// 
+    public virtual IEnumerable GetChildren(int parentId, UmbracoObjectTypes objectType)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                var query = Query().Where(x => x.ParentId == parentId);
-                return _entityRepository.GetByQuery(query, objectType.GetGuid());
-            }
+            IQuery query = Query().Where(x => x.ParentId == parentId);
+            return _entityRepository.GetByQuery(query, objectType.GetGuid());
         }
+    }
 
-        /// 
-        public virtual IEnumerable GetDescendants(int id)
+    /// 
+    public virtual IEnumerable GetDescendants(int id)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                var entity = _entityRepository.Get(id);
-                var pathMatch = entity?.Path + ",";
-                var query = Query().Where(x => x.Path.StartsWith(pathMatch) && x.Id != id);
-                return _entityRepository.GetByQuery(query);
-            }
+            IEntitySlim entity = _entityRepository.Get(id);
+            var pathMatch = entity?.Path + ",";
+            IQuery query = Query()
+                .Where(x => x.Path.StartsWith(pathMatch) && x.Id != id);
+            return _entityRepository.GetByQuery(query);
         }
+    }
 
-        /// 
-        public virtual IEnumerable GetDescendants(int id, UmbracoObjectTypes objectType)
+    /// 
+    public virtual IEnumerable GetDescendants(int id, UmbracoObjectTypes objectType)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
+            IEntitySlim entity = _entityRepository.Get(id);
+            if (entity is null)
             {
-                var entity = _entityRepository.Get(id);
-                if (entity is null)
-                {
-                    return Enumerable.Empty();
-                }
-                var query = Query().Where(x => x.Path.StartsWith(entity.Path) && x.Id != id);
-                return _entityRepository.GetByQuery(query, objectType.GetGuid());
+                return Enumerable.Empty();
             }
+
+            IQuery query = Query()
+                .Where(x => x.Path.StartsWith(entity.Path) && x.Id != id);
+            return _entityRepository.GetByQuery(query, objectType.GetGuid());
         }
+    }
 
-        /// 
-        public IEnumerable GetPagedChildren(int id, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords,
-            IQuery? filter = null, Ordering? ordering = null)
+    /// 
+    public IEnumerable GetPagedChildren(int id, UmbracoObjectTypes objectType, long pageIndex,
+        int pageSize, out long totalRecords,
+        IQuery? filter = null, Ordering? ordering = null)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                var query = Query().Where(x => x.ParentId == id && x.Trashed == false);
+            IQuery query = Query().Where(x => x.ParentId == id && x.Trashed == false);
 
-                return _entityRepository.GetPagedResultsByQuery(query, objectType.GetGuid(), pageIndex, pageSize, out totalRecords, filter, ordering);
-            }
+            return _entityRepository.GetPagedResultsByQuery(query, objectType.GetGuid(), pageIndex, pageSize,
+                out totalRecords, filter, ordering);
         }
+    }
 
-        /// 
-        public IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords,
-            IQuery? filter = null, Ordering? ordering = null)
+    /// 
+    public IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes objectType, long pageIndex,
+        int pageSize, out long totalRecords,
+        IQuery? filter = null, Ordering? ordering = null)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                var objectTypeGuid = objectType.GetGuid();
-                var query = Query();
+            Guid objectTypeGuid = objectType.GetGuid();
+            IQuery query = Query();
 
-                if (id != Cms.Core.Constants.System.Root)
+            if (id != Constants.System.Root)
+            {
+                // lookup the path so we can use it in the prefix query below
+                TreeEntityPath[] paths = _entityRepository.GetAllPaths(objectTypeGuid, id).ToArray();
+                if (paths.Length == 0)
                 {
-                    // lookup the path so we can use it in the prefix query below
-                    var paths = _entityRepository.GetAllPaths(objectTypeGuid, id).ToArray();
-                    if (paths.Length == 0)
-                    {
-                        totalRecords = 0;
-                        return Enumerable.Empty();
-                    }
-                    var path = paths[0].Path;
-                    query.Where(x => x.Path.SqlStartsWith(path + ",", TextColumnType.NVarchar));
+                    totalRecords = 0;
+                    return Enumerable.Empty();
                 }
 
-                return _entityRepository.GetPagedResultsByQuery(query, objectTypeGuid, pageIndex, pageSize, out totalRecords, filter, ordering);
+                var path = paths[0].Path;
+                query.Where(x => x.Path.SqlStartsWith(path + ",", TextColumnType.NVarchar));
             }
+
+            return _entityRepository.GetPagedResultsByQuery(query, objectTypeGuid, pageIndex, pageSize,
+                out totalRecords, filter, ordering);
         }
+    }
+
+    /// 
+    public IEnumerable GetPagedDescendants(IEnumerable ids, UmbracoObjectTypes objectType,
+        long pageIndex, int pageSize, out long totalRecords,
+        IQuery? filter = null, Ordering? ordering = null)
+    {
+        totalRecords = 0;
 
-        /// 
-        public IEnumerable GetPagedDescendants(IEnumerable ids, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords,
-            IQuery? filter = null, Ordering? ordering = null)
+        var idsA = ids.ToArray();
+        if (idsA.Length == 0)
         {
-            totalRecords = 0;
+            return Enumerable.Empty();
+        }
 
-            var idsA = ids.ToArray();
-            if (idsA.Length == 0)
-                return Enumerable.Empty();
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            Guid objectTypeGuid = objectType.GetGuid();
+            IQuery query = Query();
 
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
+            if (idsA.All(x => x != Constants.System.Root))
             {
-                var objectTypeGuid = objectType.GetGuid();
-                var query = Query();
+                TreeEntityPath[] paths = _entityRepository.GetAllPaths(objectTypeGuid, idsA).ToArray();
+                if (paths.Length == 0)
+                {
+                    totalRecords = 0;
+                    return Enumerable.Empty();
+                }
 
-                if (idsA.All(x => x != Cms.Core.Constants.System.Root))
+                var clauses = new List>>();
+                foreach (var id in idsA)
                 {
-                    var paths = _entityRepository.GetAllPaths(objectTypeGuid, idsA).ToArray();
-                    if (paths.Length == 0)
+                    // if the id is root then don't add any clauses
+                    if (id == Constants.System.Root)
                     {
-                        totalRecords = 0;
-                        return Enumerable.Empty();
+                        continue;
                     }
-                    var clauses = new List>>();
-                    foreach (var id in idsA)
-                    {
-                        // if the id is root then don't add any clauses
-                        if (id == Cms.Core.Constants.System.Root) continue;
-
-                        var entityPath = paths.FirstOrDefault(x => x.Id == id);
-                        if (entityPath == null) continue;
 
-                        var path = entityPath.Path;
-                        var qid = id;
-                        clauses.Add(x => x.Path.SqlStartsWith(path + ",", TextColumnType.NVarchar) || x.Path.SqlEndsWith("," + qid, TextColumnType.NVarchar));
+                    TreeEntityPath entityPath = paths.FirstOrDefault(x => x.Id == id);
+                    if (entityPath == null)
+                    {
+                        continue;
                     }
-                    query.WhereAny(clauses);
+
+                    var path = entityPath.Path;
+                    var qid = id;
+                    clauses.Add(x =>
+                        x.Path.SqlStartsWith(path + ",", TextColumnType.NVarchar) ||
+                        x.Path.SqlEndsWith("," + qid, TextColumnType.NVarchar));
                 }
 
-                return _entityRepository.GetPagedResultsByQuery(query, objectTypeGuid, pageIndex, pageSize, out totalRecords, filter, ordering);
+                query.WhereAny(clauses);
             }
-        }
-
-        /// 
-        public IEnumerable GetPagedDescendants(UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords,
-            IQuery? filter = null, Ordering? ordering = null, bool includeTrashed = true)
-        {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                var query = Query();
-                if (includeTrashed == false)
-                    query.Where(x => x.Trashed == false);
 
-                return _entityRepository.GetPagedResultsByQuery(query, objectType.GetGuid(), pageIndex, pageSize, out totalRecords, filter, ordering);
-            }
+            return _entityRepository.GetPagedResultsByQuery(query, objectTypeGuid, pageIndex, pageSize,
+                out totalRecords, filter, ordering);
         }
+    }
 
-        /// 
-        public virtual UmbracoObjectTypes GetObjectType(int id)
+    /// 
+    public IEnumerable GetPagedDescendants(UmbracoObjectTypes objectType, long pageIndex, int pageSize,
+        out long totalRecords,
+        IQuery? filter = null, Ordering? ordering = null, bool includeTrashed = true)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+            IQuery query = Query();
+            if (includeTrashed == false)
             {
-                return _entityRepository.GetObjectType(id);
+                query.Where(x => x.Trashed == false);
             }
-        }
 
-        /// 
-        public virtual UmbracoObjectTypes GetObjectType(Guid key)
-        {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _entityRepository.GetObjectType(key);
-            }
+            return _entityRepository.GetPagedResultsByQuery(query, objectType.GetGuid(), pageIndex, pageSize,
+                out totalRecords, filter, ordering);
         }
+    }
 
-        /// 
-        public virtual UmbracoObjectTypes GetObjectType(IUmbracoEntity entity)
+    /// 
+    public virtual UmbracoObjectTypes GetObjectType(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            return entity is IEntitySlim light
-                ? ObjectTypes.GetUmbracoObjectType(light.NodeObjectType)
-                : GetObjectType(entity.Id);
+            return _entityRepository.GetObjectType(id);
         }
+    }
 
-        /// 
-        public virtual Type? GetEntityType(int id)
+    /// 
+    public virtual UmbracoObjectTypes GetObjectType(Guid key)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            var objectType = GetObjectType(id);
-            return objectType.GetClrType();
+            return _entityRepository.GetObjectType(key);
         }
+    }
 
-        /// 
-        public Attempt GetId(Guid key, UmbracoObjectTypes objectType)
-        {
-            return _idKeyMap.GetIdForKey(key, objectType);
-        }
+    /// 
+    public virtual UmbracoObjectTypes GetObjectType(IUmbracoEntity entity) =>
+        entity is IEntitySlim light
+            ? ObjectTypes.GetUmbracoObjectType(light.NodeObjectType)
+            : GetObjectType(entity.Id);
 
-        /// 
-        public Attempt GetId(Udi udi)
-        {
-            return _idKeyMap.GetIdForUdi(udi);
-        }
+    /// 
+    public virtual Type? GetEntityType(int id)
+    {
+        UmbracoObjectTypes objectType = GetObjectType(id);
+        return objectType.GetClrType();
+    }
 
-        /// 
-        public Attempt GetKey(int id, UmbracoObjectTypes umbracoObjectType)
+    /// 
+    public Attempt GetId(Guid key, UmbracoObjectTypes objectType) => _idKeyMap.GetIdForKey(key, objectType);
+
+    /// 
+    public Attempt GetId(Udi udi) => _idKeyMap.GetIdForUdi(udi);
+
+    /// 
+    public Attempt GetKey(int id, UmbracoObjectTypes umbracoObjectType) =>
+        _idKeyMap.GetKeyForId(id, umbracoObjectType);
+
+    /// 
+    public virtual IEnumerable GetAllPaths(UmbracoObjectTypes objectType, params int[]? ids)
+    {
+        Type entityType = objectType.GetClrType();
+        GetObjectType(entityType);
+
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            return _idKeyMap.GetKeyForId(id, umbracoObjectType);
+            return _entityRepository.GetAllPaths(objectType.GetGuid(), ids);
         }
+    }
 
-        /// 
-        public virtual IEnumerable GetAllPaths(UmbracoObjectTypes objectType, params int[]? ids)
-        {
-            var entityType = objectType.GetClrType();
-            GetObjectType(entityType);
+    /// 
+    public virtual IEnumerable GetAllPaths(UmbracoObjectTypes objectType, params Guid[] keys)
+    {
+        Type entityType = objectType.GetClrType();
+        GetObjectType(entityType);
 
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _entityRepository.GetAllPaths(objectType.GetGuid(), ids);
-            }
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return _entityRepository.GetAllPaths(objectType.GetGuid(), keys);
         }
+    }
 
-        /// 
-        public virtual IEnumerable GetAllPaths(UmbracoObjectTypes objectType, params Guid[] keys)
+    /// 
+    public int ReserveId(Guid key)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            var entityType = objectType.GetClrType();
-            GetObjectType(entityType);
-
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _entityRepository.GetAllPaths(objectType.GetGuid(), keys);
-            }
+            return _entityRepository.ReserveId(key);
         }
+    }
 
-        /// 
-        public int ReserveId(Guid key)
+    // gets the object type, throws if not supported
+    private UmbracoObjectTypes GetObjectType(Type? type)
+    {
+        if (type?.FullName == null || !_objectTypes.TryGetValue(type.FullName, out UmbracoObjectTypes objType))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _entityRepository.ReserveId(key);
-            }
+            throw new NotSupportedException($"Type \"{type?.FullName ?? ""}\" is not supported here.");
         }
+
+        return objType;
     }
 }
diff --git a/src/Umbraco.Core/Services/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/EntityXmlSerializer.cs
index c91f536b3830..9363e96cd1a3 100644
--- a/src/Umbraco.Core/Services/EntityXmlSerializer.cs
+++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs
@@ -1,7 +1,4 @@
-using System;
-using System.Collections.Generic;
 using System.Globalization;
-using System.Linq;
 using System.Net;
 using System.Xml.Linq;
 using Umbraco.Cms.Core.Models;
@@ -11,685 +8,738 @@
 using Umbraco.Cms.Core.Strings;
 using Umbraco.Extensions;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Serializes entities to XML
+/// 
+internal class EntityXmlSerializer : IEntityXmlSerializer
 {
+    private readonly IConfigurationEditorJsonSerializer _configurationEditorJsonSerializer;
+    private readonly IContentService _contentService;
+    private readonly IContentTypeService _contentTypeService;
+    private readonly IDataTypeService _dataTypeService;
+    private readonly ILocalizationService _localizationService;
+    private readonly IMediaService _mediaService;
+    private readonly PropertyEditorCollection _propertyEditors;
+    private readonly IShortStringHelper _shortStringHelper;
+    private readonly UrlSegmentProviderCollection _urlSegmentProviders;
+    private readonly IUserService _userService;
+
+    public EntityXmlSerializer(
+        IContentService contentService,
+        IMediaService mediaService,
+        IDataTypeService dataTypeService,
+        IUserService userService,
+        ILocalizationService localizationService,
+        IContentTypeService contentTypeService,
+        UrlSegmentProviderCollection urlSegmentProviders,
+        IShortStringHelper shortStringHelper,
+        PropertyEditorCollection propertyEditors,
+        IConfigurationEditorJsonSerializer configurationEditorJsonSerializer)
+    {
+        _contentTypeService = contentTypeService;
+        _mediaService = mediaService;
+        _contentService = contentService;
+        _dataTypeService = dataTypeService;
+        _userService = userService;
+        _localizationService = localizationService;
+        _urlSegmentProviders = urlSegmentProviders;
+        _shortStringHelper = shortStringHelper;
+        _propertyEditors = propertyEditors;
+        _configurationEditorJsonSerializer = configurationEditorJsonSerializer;
+    }
+
     /// 
-    /// Serializes entities to XML
+    ///     Exports an IContent item as an XElement.
     /// 
-    internal class EntityXmlSerializer : IEntityXmlSerializer
+    public XElement Serialize(IContent content,
+        bool published,
+        bool withDescendants = false) // TODO: take care of usage! only used for the packager
     {
-        private readonly IContentTypeService _contentTypeService;
-        private readonly IMediaService _mediaService;
-        private readonly IContentService _contentService;
-        private readonly IDataTypeService _dataTypeService;
-        private readonly IUserService _userService;
-        private readonly ILocalizationService _localizationService;
-        private readonly UrlSegmentProviderCollection _urlSegmentProviders;
-        private readonly IShortStringHelper _shortStringHelper;
-        private readonly PropertyEditorCollection _propertyEditors;
-        private readonly IConfigurationEditorJsonSerializer _configurationEditorJsonSerializer;
-
-        public EntityXmlSerializer(
-            IContentService contentService,
-            IMediaService mediaService,
-            IDataTypeService dataTypeService,
-            IUserService userService,
-            ILocalizationService localizationService,
-            IContentTypeService contentTypeService,
-            UrlSegmentProviderCollection urlSegmentProviders,
-            IShortStringHelper shortStringHelper,
-            PropertyEditorCollection propertyEditors,
-            IConfigurationEditorJsonSerializer configurationEditorJsonSerializer)
-        {
-            _contentTypeService = contentTypeService;
-            _mediaService = mediaService;
-            _contentService = contentService;
-            _dataTypeService = dataTypeService;
-            _userService = userService;
-            _localizationService = localizationService;
-            _urlSegmentProviders = urlSegmentProviders;
-            _shortStringHelper = shortStringHelper;
-            _propertyEditors = propertyEditors;
-            _configurationEditorJsonSerializer = configurationEditorJsonSerializer;
-        }
-
-        /// 
-        /// Exports an IContent item as an XElement.
-        /// 
-        public XElement Serialize(IContent content,
-            bool published,
-            bool withDescendants = false) // TODO: take care of usage! only used for the packager
-        {
-            if (content == null) throw new ArgumentNullException(nameof(content));
-
-            var nodeName = content.ContentType.Alias.ToSafeAlias(_shortStringHelper);
-
-            var xml = SerializeContentBase(content, content.GetUrlSegment(_shortStringHelper, _urlSegmentProviders), nodeName, published);
-
-            xml.Add(new XAttribute("nodeType", content.ContentType.Id));
-            xml.Add(new XAttribute("nodeTypeAlias", content.ContentType.Alias));
-
-            xml.Add(new XAttribute("creatorName", content.GetCreatorProfile(_userService)?.Name ?? "??"));
-            //xml.Add(new XAttribute("creatorID", content.CreatorId));
-            xml.Add(new XAttribute("writerName", content.GetWriterProfile(_userService)?.Name ?? "??"));
-            xml.Add(new XAttribute("writerID", content.WriterId));
-
-            xml.Add(new XAttribute("template", content.TemplateId?.ToString(CultureInfo.InvariantCulture) ?? ""));
-
-            xml.Add(new XAttribute("isPublished", content.Published));
-
-            if (withDescendants)
-            {
-                const int pageSize = 500;
-                var page = 0;
-                var total = long.MaxValue;
-                while(page * pageSize < total)
-                {
-                    var children = _contentService.GetPagedChildren(content.Id, page++, pageSize, out total);
-                    SerializeChildren(children, xml, published);
-                }
+        if (content == null)
+        {
+            throw new ArgumentNullException(nameof(content));
+        }
 
-            }
+        var nodeName = content.ContentType.Alias.ToSafeAlias(_shortStringHelper);
 
-            return xml;
-        }
+        XElement xml = SerializeContentBase(content, content.GetUrlSegment(_shortStringHelper, _urlSegmentProviders),
+            nodeName, published);
 
-        /// 
-        /// Exports an IMedia item as an XElement.
-        /// 
-        public XElement Serialize(
-            IMedia media,
-            bool withDescendants = false,
-            Action? onMediaItemSerialized = null)
-        {
-            if (_mediaService == null) throw new ArgumentNullException(nameof(_mediaService));
-            if (_dataTypeService == null) throw new ArgumentNullException(nameof(_dataTypeService));
-            if (_userService == null) throw new ArgumentNullException(nameof(_userService));
-            if (_localizationService == null) throw new ArgumentNullException(nameof(_localizationService));
-            if (media == null) throw new ArgumentNullException(nameof(media));
-            if (_urlSegmentProviders == null) throw new ArgumentNullException(nameof(_urlSegmentProviders));
+        xml.Add(new XAttribute("nodeType", content.ContentType.Id));
+        xml.Add(new XAttribute("nodeTypeAlias", content.ContentType.Alias));
 
-            var nodeName = media.ContentType.Alias.ToSafeAlias(_shortStringHelper);
+        xml.Add(new XAttribute("creatorName", content.GetCreatorProfile(_userService)?.Name ?? "??"));
+        //xml.Add(new XAttribute("creatorID", content.CreatorId));
+        xml.Add(new XAttribute("writerName", content.GetWriterProfile(_userService)?.Name ?? "??"));
+        xml.Add(new XAttribute("writerID", content.WriterId));
 
-            const bool published = false; // always false for media
-            string? urlValue = media.GetUrlSegment(_shortStringHelper, _urlSegmentProviders);
-            XElement xml = SerializeContentBase(media, urlValue, nodeName, published);
+        xml.Add(new XAttribute("template", content.TemplateId?.ToString(CultureInfo.InvariantCulture) ?? ""));
 
+        xml.Add(new XAttribute("isPublished", content.Published));
 
-            xml.Add(new XAttribute("nodeType", media.ContentType.Id));
-            xml.Add(new XAttribute("nodeTypeAlias", media.ContentType.Alias));
+        if (withDescendants)
+        {
+            const int pageSize = 500;
+            var page = 0;
+            var total = long.MaxValue;
+            while (page * pageSize < total)
+            {
+                IEnumerable children =
+                    _contentService.GetPagedChildren(content.Id, page++, pageSize, out total);
+                SerializeChildren(children, xml, published);
+            }
+        }
 
-            //xml.Add(new XAttribute("creatorName", media.GetCreatorProfile(userService).Name));
-            //xml.Add(new XAttribute("creatorID", media.CreatorId));
-            xml.Add(new XAttribute("writerName", media.GetWriterProfile(_userService)?.Name ?? string.Empty));
-            xml.Add(new XAttribute("writerID", media.WriterId));
-            xml.Add(new XAttribute("udi", media.GetUdi()));
+        return xml;
+    }
 
-            //xml.Add(new XAttribute("template", 0)); // no template for media
+    /// 
+    ///     Exports an IMedia item as an XElement.
+    /// 
+    public XElement Serialize(
+        IMedia media,
+        bool withDescendants = false,
+        Action? onMediaItemSerialized = null)
+    {
+        if (_mediaService == null)
+        {
+            throw new ArgumentNullException(nameof(_mediaService));
+        }
 
-            onMediaItemSerialized?.Invoke(media, xml);
+        if (_dataTypeService == null)
+        {
+            throw new ArgumentNullException(nameof(_dataTypeService));
+        }
 
-            if (withDescendants)
-            {
-                const int pageSize = 500;
-                var page = 0;
-                var total = long.MaxValue;
-                while (page * pageSize < total)
-                {
-                    var children = _mediaService.GetPagedChildren(media.Id, page++, pageSize, out total);
-                    SerializeChildren(children, xml, onMediaItemSerialized);
-                }
-            }
+        if (_userService == null)
+        {
+            throw new ArgumentNullException(nameof(_userService));
+        }
 
-            return xml;
+        if (_localizationService == null)
+        {
+            throw new ArgumentNullException(nameof(_localizationService));
         }
 
-        /// 
-        /// Exports an IMember item as an XElement.
-        /// 
-        public XElement Serialize(IMember member)
+        if (media == null)
         {
-            var nodeName = member.ContentType.Alias.ToSafeAlias(_shortStringHelper);
+            throw new ArgumentNullException(nameof(media));
+        }
 
-            const bool published = false; // always false for member
-            var xml = SerializeContentBase(member, "", nodeName, published);
+        if (_urlSegmentProviders == null)
+        {
+            throw new ArgumentNullException(nameof(_urlSegmentProviders));
+        }
 
-            xml.Add(new XAttribute("nodeType", member.ContentType.Id));
-            xml.Add(new XAttribute("nodeTypeAlias", member.ContentType.Alias));
+        var nodeName = media.ContentType.Alias.ToSafeAlias(_shortStringHelper);
 
-            // what about writer/creator/version?
+        const bool published = false; // always false for media
+        var urlValue = media.GetUrlSegment(_shortStringHelper, _urlSegmentProviders);
+        XElement xml = SerializeContentBase(media, urlValue, nodeName, published);
 
-            xml.Add(new XAttribute("loginName", member.Username!));
-            xml.Add(new XAttribute("email", member.Email!));
-            xml.Add(new XAttribute("icon", member.ContentType.Icon!));
 
-            return xml;
-        }
+        xml.Add(new XAttribute("nodeType", media.ContentType.Id));
+        xml.Add(new XAttribute("nodeTypeAlias", media.ContentType.Alias));
 
-        /// 
-        /// Exports a list of Data Types
-        /// 
-        /// List of data types to export
-        ///  containing the xml representation of the IDataTypeDefinition objects
-        public XElement Serialize(IEnumerable dataTypeDefinitions)
+        //xml.Add(new XAttribute("creatorName", media.GetCreatorProfile(userService).Name));
+        //xml.Add(new XAttribute("creatorID", media.CreatorId));
+        xml.Add(new XAttribute("writerName", media.GetWriterProfile(_userService)?.Name ?? string.Empty));
+        xml.Add(new XAttribute("writerID", media.WriterId));
+        xml.Add(new XAttribute("udi", media.GetUdi()));
+
+        //xml.Add(new XAttribute("template", 0)); // no template for media
+
+        onMediaItemSerialized?.Invoke(media, xml);
+
+        if (withDescendants)
         {
-            var container = new XElement("DataTypes");
-            foreach (var dataTypeDefinition in dataTypeDefinitions)
+            const int pageSize = 500;
+            var page = 0;
+            var total = long.MaxValue;
+            while (page * pageSize < total)
             {
-                container.Add(Serialize(dataTypeDefinition));
+                IEnumerable children = _mediaService.GetPagedChildren(media.Id, page++, pageSize, out total);
+                SerializeChildren(children, xml, onMediaItemSerialized);
             }
-            return container;
         }
 
-        public XElement Serialize(IDataType dataType)
-        {
-            var xml = new XElement("DataType");
-            xml.Add(new XAttribute("Name", dataType.Name!));
-            //The 'ID' when exporting is actually the property editor alias (in pre v7 it was the IDataType GUID id)
-            xml.Add(new XAttribute("Id", dataType.EditorAlias));
-            xml.Add(new XAttribute("Definition", dataType.Key));
-            xml.Add(new XAttribute("DatabaseType", dataType.DatabaseType.ToString()));
-            xml.Add(new XAttribute("Configuration", _configurationEditorJsonSerializer.Serialize(dataType.Configuration)));
+        return xml;
+    }
 
-            var folderNames = string.Empty;
-            var folderKeys = string.Empty;
-            if (dataType.Level != 1)
-            {
-                //get URL encoded folder names
-                IOrderedEnumerable folders = _dataTypeService.GetContainers(dataType)
-                    .OrderBy(x => x.Level);
+    /// 
+    ///     Exports an IMember item as an XElement.
+    /// 
+    public XElement Serialize(IMember member)
+    {
+        var nodeName = member.ContentType.Alias.ToSafeAlias(_shortStringHelper);
 
-                folderNames = string.Join("/", folders.Select(x => WebUtility.UrlEncode(x.Name)).ToArray());
-                folderKeys = string.Join("/", folders.Select(x => x.Key).ToArray());
-            }
+        const bool published = false; // always false for member
+        XElement xml = SerializeContentBase(member, "", nodeName, published);
 
-            if (string.IsNullOrWhiteSpace(folderNames) == false)
-            {
-                xml.Add(new XAttribute("Folders", folderNames));
-                xml.Add(new XAttribute("FolderKeys", folderKeys));
-            }
+        xml.Add(new XAttribute("nodeType", member.ContentType.Id));
+        xml.Add(new XAttribute("nodeTypeAlias", member.ContentType.Alias));
 
+        // what about writer/creator/version?
 
-            return xml;
-        }
+        xml.Add(new XAttribute("loginName", member.Username!));
+        xml.Add(new XAttribute("email", member.Email!));
+        xml.Add(new XAttribute("icon", member.ContentType.Icon!));
 
-        /// 
-        /// Exports a list of  items to xml as an 
-        /// 
-        /// List of dictionary items to export
-        /// Optional boolean indicating whether or not to include children
-        ///  containing the xml representation of the IDictionaryItem objects
-        public XElement Serialize(IEnumerable dictionaryItem, bool includeChildren = true)
+        return xml;
+    }
+
+    /// 
+    ///     Exports a list of Data Types
+    /// 
+    /// List of data types to export
+    ///  containing the xml representation of the IDataTypeDefinition objects
+    public XElement Serialize(IEnumerable dataTypeDefinitions)
+    {
+        var container = new XElement("DataTypes");
+        foreach (IDataType dataTypeDefinition in dataTypeDefinitions)
         {
-            var xml = new XElement("DictionaryItems");
-            foreach (var item in dictionaryItem)
-            {
-                xml.Add(Serialize(item, includeChildren));
-            }
-            return xml;
+            container.Add(Serialize(dataTypeDefinition));
         }
 
-        /// 
-        /// Exports a single  item to xml as an 
-        /// 
-        /// Dictionary Item to export
-        /// Optional boolean indicating whether or not to include children
-        ///  containing the xml representation of the IDictionaryItem object
-        public XElement Serialize(IDictionaryItem dictionaryItem, bool includeChildren)
-        {
-            var xml = Serialize(dictionaryItem);
+        return container;
+    }
 
-            if (includeChildren)
-            {
-                var children = _localizationService.GetDictionaryItemChildren(dictionaryItem.Key);
-                if (children is not null)
-                {
-                    foreach (var child in children)
-                    {
-                        xml.Add(Serialize(child, true));
-                    }
-                }
-            }
+    public XElement Serialize(IDataType dataType)
+    {
+        var xml = new XElement("DataType");
+        xml.Add(new XAttribute("Name", dataType.Name!));
+        //The 'ID' when exporting is actually the property editor alias (in pre v7 it was the IDataType GUID id)
+        xml.Add(new XAttribute("Id", dataType.EditorAlias));
+        xml.Add(new XAttribute("Definition", dataType.Key));
+        xml.Add(new XAttribute("DatabaseType", dataType.DatabaseType.ToString()));
+        xml.Add(new XAttribute("Configuration", _configurationEditorJsonSerializer.Serialize(dataType.Configuration)));
+
+        var folderNames = string.Empty;
+        var folderKeys = string.Empty;
+        if (dataType.Level != 1)
+        {
+            //get URL encoded folder names
+            IOrderedEnumerable folders = _dataTypeService.GetContainers(dataType)
+                .OrderBy(x => x.Level);
 
-            return xml;
+            folderNames = string.Join("/", folders.Select(x => WebUtility.UrlEncode(x.Name)).ToArray());
+            folderKeys = string.Join("/", folders.Select(x => x.Key).ToArray());
         }
 
-        private XElement Serialize(IDictionaryItem dictionaryItem)
+        if (string.IsNullOrWhiteSpace(folderNames) == false)
         {
-            var xml = new XElement("DictionaryItem",
-                new XAttribute("Key", dictionaryItem.Key),
-                new XAttribute("Name", dictionaryItem.ItemKey));
+            xml.Add(new XAttribute("Folders", folderNames));
+            xml.Add(new XAttribute("FolderKeys", folderKeys));
+        }
 
-            foreach (IDictionaryTranslation translation in dictionaryItem.Translations!)
-            {
-                xml.Add(new XElement("Value",
-                    new XAttribute("LanguageId", translation.Language!.Id),
-                    new XAttribute("LanguageCultureAlias", translation.Language.IsoCode),
-                    new XCData(translation.Value!)));
-            }
 
-            return xml;
-        }
+        return xml;
+    }
 
-        public XElement Serialize(IStylesheet stylesheet, bool includeProperties)
+    /// 
+    ///     Exports a list of  items to xml as an 
+    /// 
+    /// List of dictionary items to export
+    /// Optional boolean indicating whether or not to include children
+    ///  containing the xml representation of the IDictionaryItem objects
+    public XElement Serialize(IEnumerable dictionaryItem, bool includeChildren = true)
+    {
+        var xml = new XElement("DictionaryItems");
+        foreach (IDictionaryItem item in dictionaryItem)
         {
-            var xml = new XElement("Stylesheet",
-                new XElement("Name", stylesheet.Alias),
-                new XElement("FileName", stylesheet.Path),
-                new XElement("Content", new XCData(stylesheet.Content!)));
+            xml.Add(Serialize(item, includeChildren));
+        }
 
-            if (!includeProperties)
-            {
-                return xml;
-            }
+        return xml;
+    }
 
-            var props = new XElement("Properties");
-            xml.Add(props);
+    /// 
+    ///     Exports a single  item to xml as an 
+    /// 
+    /// Dictionary Item to export
+    /// Optional boolean indicating whether or not to include children
+    ///  containing the xml representation of the IDictionaryItem object
+    public XElement Serialize(IDictionaryItem dictionaryItem, bool includeChildren)
+    {
+        XElement xml = Serialize(dictionaryItem);
 
-            if (stylesheet.Properties is not null)
+        if (includeChildren)
+        {
+            IEnumerable children = _localizationService.GetDictionaryItemChildren(dictionaryItem.Key);
+            if (children is not null)
             {
-                foreach (var prop in stylesheet.Properties)
+                foreach (IDictionaryItem child in children)
                 {
-                    props.Add(new XElement("Property",
-                        new XElement("Name", prop.Name),
-                        new XElement("Alias", prop.Alias),
-                        new XElement("Value", prop.Value)));
+                    xml.Add(Serialize(child, true));
                 }
             }
+        }
+
+        return xml;
+    }
 
+    public XElement Serialize(IStylesheet stylesheet, bool includeProperties)
+    {
+        var xml = new XElement("Stylesheet",
+            new XElement("Name", stylesheet.Alias),
+            new XElement("FileName", stylesheet.Path),
+            new XElement("Content", new XCData(stylesheet.Content!)));
+
+        if (!includeProperties)
+        {
             return xml;
         }
 
-        /// 
-        /// Exports a list of  items to xml as an 
-        /// 
-        /// List of Languages to export
-        ///  containing the xml representation of the ILanguage objects
-        public XElement Serialize(IEnumerable languages)
+        var props = new XElement("Properties");
+        xml.Add(props);
+
+        if (stylesheet.Properties is not null)
         {
-            var xml = new XElement("Languages");
-            foreach (var language in languages)
+            foreach (IStylesheetProperty prop in stylesheet.Properties)
             {
-                xml.Add(Serialize(language));
+                props.Add(new XElement("Property",
+                    new XElement("Name", prop.Name),
+                    new XElement("Alias", prop.Alias),
+                    new XElement("Value", prop.Value)));
             }
-            return xml;
         }
 
-        public XElement Serialize(ILanguage language)
-        {
-            var xml = new XElement("Language",
-                new XAttribute("Id", language.Id),
-                new XAttribute("CultureAlias", language.IsoCode),
-                new XAttribute("FriendlyName", language.CultureName!));
+        return xml;
+    }
 
-            return xml;
+    /// 
+    ///     Exports a list of  items to xml as an 
+    /// 
+    /// List of Languages to export
+    ///  containing the xml representation of the ILanguage objects
+    public XElement Serialize(IEnumerable languages)
+    {
+        var xml = new XElement("Languages");
+        foreach (ILanguage language in languages)
+        {
+            xml.Add(Serialize(language));
         }
 
-        public XElement Serialize(ITemplate template)
-        {
-            var xml = new XElement("Template");
-            xml.Add(new XElement("Name", template.Name));
-            xml.Add(new XElement("Key", template.Key));
-            xml.Add(new XElement("Alias", template.Alias));
-            xml.Add(new XElement("Design", new XCData(template.Content!)));
+        return xml;
+    }
 
-            if (template is Template concreteTemplate && concreteTemplate.MasterTemplateId != null)
-            {
-                if (concreteTemplate.MasterTemplateId.IsValueCreated &&
-                    concreteTemplate.MasterTemplateId.Value != default)
-                {
-                    xml.Add(new XElement("Master", concreteTemplate.MasterTemplateId.ToString()));
-                    xml.Add(new XElement("MasterAlias", concreteTemplate.MasterTemplateAlias));
-                }
-            }
+    public XElement Serialize(ILanguage language)
+    {
+        var xml = new XElement("Language",
+            new XAttribute("Id", language.Id),
+            new XAttribute("CultureAlias", language.IsoCode),
+            new XAttribute("FriendlyName", language.CultureName!));
 
-            return xml;
-        }
+        return xml;
+    }
 
-        /// 
-        /// Exports a list of  items to xml as an 
-        /// 
-        /// 
-        /// 
-        public XElement Serialize(IEnumerable templates)
+    public XElement Serialize(ITemplate template)
+    {
+        var xml = new XElement("Template");
+        xml.Add(new XElement("Name", template.Name));
+        xml.Add(new XElement("Key", template.Key));
+        xml.Add(new XElement("Alias", template.Alias));
+        xml.Add(new XElement("Design", new XCData(template.Content!)));
+
+        if (template is Template concreteTemplate && concreteTemplate.MasterTemplateId != null)
         {
-            var xml = new XElement("Templates");
-            foreach (var item in templates)
+            if (concreteTemplate.MasterTemplateId.IsValueCreated &&
+                concreteTemplate.MasterTemplateId.Value != default)
             {
-                xml.Add(Serialize(item));
+                xml.Add(new XElement("Master", concreteTemplate.MasterTemplateId.ToString()));
+                xml.Add(new XElement("MasterAlias", concreteTemplate.MasterTemplateAlias));
             }
-            return xml;
         }
 
+        return xml;
+    }
+
+    /// 
+    ///     Exports a list of  items to xml as an 
+    /// 
+    /// 
+    /// 
+    public XElement Serialize(IEnumerable templates)
+    {
+        var xml = new XElement("Templates");
+        foreach (ITemplate item in templates)
+        {
+            xml.Add(Serialize(item));
+        }
+
+        return xml;
+    }
+
 
-        public XElement Serialize(IMediaType mediaType)
+    public XElement Serialize(IMediaType mediaType)
+    {
+        var info = new XElement("Info",
+            new XElement("Name", mediaType.Name),
+            new XElement("Alias", mediaType.Alias),
+            new XElement("Key", mediaType.Key),
+            new XElement("Icon", mediaType.Icon),
+            new XElement("Thumbnail", mediaType.Thumbnail),
+            new XElement("Description", mediaType.Description),
+            new XElement("AllowAtRoot", mediaType.AllowedAsRoot.ToString()));
+
+        var masterContentType = mediaType.CompositionAliases().FirstOrDefault();
+        if (masterContentType != null)
         {
-            var info = new XElement("Info",
-                                    new XElement("Name", mediaType.Name),
-                                    new XElement("Alias", mediaType.Alias),
-                                    new XElement("Key", mediaType.Key),
-                                    new XElement("Icon", mediaType.Icon),
-                                    new XElement("Thumbnail", mediaType.Thumbnail),
-                                    new XElement("Description", mediaType.Description),
-                                    new XElement("AllowAtRoot", mediaType.AllowedAsRoot.ToString()));
+            info.Add(new XElement("Master", masterContentType));
+        }
 
-            var masterContentType = mediaType.CompositionAliases().FirstOrDefault();
-            if (masterContentType != null)
+        var structure = new XElement("Structure");
+        if (mediaType.AllowedContentTypes is not null)
+        {
+            foreach (ContentTypeSort allowedType in mediaType.AllowedContentTypes)
             {
-                info.Add(new XElement("Master", masterContentType));
+                structure.Add(new XElement("MediaType", allowedType.Alias));
             }
+        }
 
-            var structure = new XElement("Structure");
-            if (mediaType.AllowedContentTypes is not null)
-            {
-                foreach (var allowedType in mediaType.AllowedContentTypes)
-                {
-                    structure.Add(new XElement("MediaType", allowedType.Alias));
-                }
-            }
+        var genericProperties = new XElement("GenericProperties",
+            SerializePropertyTypes(mediaType.PropertyTypes, mediaType.PropertyGroups)); // actually, all of them
 
-            var genericProperties = new XElement("GenericProperties", SerializePropertyTypes(mediaType.PropertyTypes, mediaType.PropertyGroups)); // actually, all of them
+        var tabs = new XElement("Tabs",
+            SerializePropertyGroups(mediaType.PropertyGroups)); // TODO Rename to PropertyGroups
 
-            var tabs = new XElement("Tabs", SerializePropertyGroups(mediaType.PropertyGroups)); // TODO Rename to PropertyGroups
+        var xml = new XElement("MediaType",
+            info,
+            structure,
+            genericProperties,
+            tabs);
 
-            var xml = new XElement("MediaType",
-                                   info,
-                                   structure,
-                                   genericProperties,
-                                   tabs);
+        return xml;
+    }
 
-            return xml;
+    /// 
+    ///     Exports a list of  items to xml as an 
+    /// 
+    /// Macros to export
+    ///  containing the xml representation of the IMacro objects
+    public XElement Serialize(IEnumerable macros)
+    {
+        var xml = new XElement("Macros");
+        foreach (IMacro item in macros)
+        {
+            xml.Add(Serialize(item));
         }
 
-        /// 
-        /// Exports a list of  items to xml as an 
-        /// 
-        /// Macros to export
-        ///  containing the xml representation of the IMacro objects
-        public XElement Serialize(IEnumerable macros)
+        return xml;
+    }
+
+    public XElement Serialize(IMacro macro)
+    {
+        var xml = new XElement("macro");
+        xml.Add(new XElement("name", macro.Name));
+        xml.Add(new XElement("key", macro.Key));
+        xml.Add(new XElement("alias", macro.Alias));
+        xml.Add(new XElement("macroSource", macro.MacroSource));
+        xml.Add(new XElement("useInEditor", macro.UseInEditor.ToString()));
+        xml.Add(new XElement("dontRender", macro.DontRender.ToString()));
+        xml.Add(new XElement("refreshRate", macro.CacheDuration.ToString(CultureInfo.InvariantCulture)));
+        xml.Add(new XElement("cacheByMember", macro.CacheByMember.ToString()));
+        xml.Add(new XElement("cacheByPage", macro.CacheByPage.ToString()));
+
+        var properties = new XElement("properties");
+        foreach (IMacroProperty property in macro.Properties)
         {
-            var xml = new XElement("Macros");
-            foreach (var item in macros)
-            {
-                xml.Add(Serialize(item));
-            }
-            return xml;
+            properties.Add(new XElement("property",
+                new XAttribute("key", property.Key),
+                new XAttribute("name", property.Name!),
+                new XAttribute("alias", property.Alias),
+                new XAttribute("sortOrder", property.SortOrder),
+                new XAttribute("propertyType", property.EditorAlias)));
         }
 
-        public XElement Serialize(IMacro macro)
-        {
-            var xml = new XElement("macro");
-            xml.Add(new XElement("name", macro.Name));
-            xml.Add(new XElement("key", macro.Key));
-            xml.Add(new XElement("alias", macro.Alias));
-            xml.Add(new XElement("macroSource", macro.MacroSource));
-            xml.Add(new XElement("useInEditor", macro.UseInEditor.ToString()));
-            xml.Add(new XElement("dontRender", macro.DontRender.ToString()));
-            xml.Add(new XElement("refreshRate", macro.CacheDuration.ToString(CultureInfo.InvariantCulture)));
-            xml.Add(new XElement("cacheByMember", macro.CacheByMember.ToString()));
-            xml.Add(new XElement("cacheByPage", macro.CacheByPage.ToString()));
+        xml.Add(properties);
 
-            var properties = new XElement("properties");
-            foreach (var property in macro.Properties)
-            {
-                properties.Add(new XElement("property",
-                    new XAttribute("key", property.Key),
-                    new XAttribute("name", property.Name!),
-                    new XAttribute("alias", property.Alias),
-                    new XAttribute("sortOrder", property.SortOrder),
-                    new XAttribute("propertyType", property.EditorAlias)));
-            }
-            xml.Add(properties);
+        return xml;
+    }
 
-            return xml;
+    public XElement Serialize(IContentType contentType)
+    {
+        var info = new XElement("Info",
+            new XElement("Name", contentType.Name),
+            new XElement("Alias", contentType.Alias),
+            new XElement("Key", contentType.Key),
+            new XElement("Icon", contentType.Icon),
+            new XElement("Thumbnail", contentType.Thumbnail),
+            new XElement("Description", contentType.Description),
+            new XElement("AllowAtRoot", contentType.AllowedAsRoot.ToString()),
+            new XElement("IsListView", contentType.IsContainer.ToString()),
+            new XElement("IsElement", contentType.IsElement.ToString()),
+            new XElement("Variations", contentType.Variations.ToString()));
+
+        IContentTypeComposition masterContentType =
+            contentType.ContentTypeComposition.FirstOrDefault(x => x.Id == contentType.ParentId);
+        if (masterContentType != null)
+        {
+            info.Add(new XElement("Master", masterContentType.Alias));
         }
 
-        public XElement Serialize(IContentType contentType)
+        var compositionsElement = new XElement("Compositions");
+        IEnumerable compositions = contentType.ContentTypeComposition;
+        foreach (IContentTypeComposition composition in compositions)
         {
-            var info = new XElement("Info",
-                                    new XElement("Name", contentType.Name),
-                                    new XElement("Alias", contentType.Alias),
-                                    new XElement("Key", contentType.Key),
-                                    new XElement("Icon", contentType.Icon),
-                                    new XElement("Thumbnail", contentType.Thumbnail),
-                                    new XElement("Description", contentType.Description),
-                                    new XElement("AllowAtRoot", contentType.AllowedAsRoot.ToString()),
-                                    new XElement("IsListView", contentType.IsContainer.ToString()),
-                                    new XElement("IsElement", contentType.IsElement.ToString()),
-                                    new XElement("Variations", contentType.Variations.ToString()));
+            compositionsElement.Add(new XElement("Composition", composition.Alias));
+        }
 
-            var masterContentType = contentType.ContentTypeComposition.FirstOrDefault(x => x.Id == contentType.ParentId);
-            if (masterContentType != null)
-            {
-                info.Add(new XElement("Master", masterContentType.Alias));
-            }
+        info.Add(compositionsElement);
 
-            var compositionsElement = new XElement("Compositions");
-            var compositions = contentType.ContentTypeComposition;
-            foreach (var composition in compositions)
-            {
-                compositionsElement.Add(new XElement("Composition", composition.Alias));
-            }
-            info.Add(compositionsElement);
-
-            var allowedTemplates = new XElement("AllowedTemplates");
-            if (contentType.AllowedTemplates is not null)
+        var allowedTemplates = new XElement("AllowedTemplates");
+        if (contentType.AllowedTemplates is not null)
+        {
+            foreach (ITemplate template in contentType.AllowedTemplates)
             {
-                foreach (var template in contentType.AllowedTemplates)
-                {
-                    allowedTemplates.Add(new XElement("Template", template.Alias));
-                }
+                allowedTemplates.Add(new XElement("Template", template.Alias));
             }
+        }
 
-            info.Add(allowedTemplates);
+        info.Add(allowedTemplates);
 
-            if (contentType.DefaultTemplate != null && contentType.DefaultTemplate.Id != 0)
-            {
-                info.Add(new XElement("DefaultTemplate", contentType.DefaultTemplate.Alias));
-            }
-            else
-            {
-                info.Add(new XElement("DefaultTemplate", ""));
-            }
+        if (contentType.DefaultTemplate != null && contentType.DefaultTemplate.Id != 0)
+        {
+            info.Add(new XElement("DefaultTemplate", contentType.DefaultTemplate.Alias));
+        }
+        else
+        {
+            info.Add(new XElement("DefaultTemplate", ""));
+        }
 
-            var structure = new XElement("Structure");
-            if (contentType.AllowedContentTypes is not null)
+        var structure = new XElement("Structure");
+        if (contentType.AllowedContentTypes is not null)
+        {
+            foreach (ContentTypeSort allowedType in contentType.AllowedContentTypes)
             {
-                foreach (var allowedType in contentType.AllowedContentTypes)
-                {
-                    structure.Add(new XElement("DocumentType", allowedType.Alias));
-                }
+                structure.Add(new XElement("DocumentType", allowedType.Alias));
             }
+        }
 
-            var genericProperties = new XElement("GenericProperties", SerializePropertyTypes(contentType.PropertyTypes, contentType.PropertyGroups)); // actually, all of them
-
-            var tabs = new XElement("Tabs", SerializePropertyGroups(contentType.PropertyGroups)); // TODO Rename to PropertyGroups
-
-            var xml = new XElement("DocumentType",
-                info,
-                structure,
-                genericProperties,
-                tabs);
-
-            if (contentType is IContentTypeWithHistoryCleanup withCleanup && withCleanup.HistoryCleanup is not null)
-            {
-                xml.Add(SerializeCleanupPolicy(withCleanup.HistoryCleanup));
-            }
+        var genericProperties = new XElement("GenericProperties",
+            SerializePropertyTypes(contentType.PropertyTypes, contentType.PropertyGroups)); // actually, all of them
 
-            var folderNames = string.Empty;
-            var folderKeys = string.Empty;
-            //don't add folders if this is a child doc type
-            if (contentType.Level != 1 && masterContentType == null)
-            {
-                //get URL encoded folder names
-                IOrderedEnumerable folders = _contentTypeService.GetContainers(contentType)
-                    .OrderBy(x => x.Level);
+        var tabs = new XElement("Tabs",
+            SerializePropertyGroups(contentType.PropertyGroups)); // TODO Rename to PropertyGroups
 
-                folderNames = string.Join("/", folders.Select(x => WebUtility.UrlEncode(x.Name)).ToArray());
-                folderKeys = string.Join("/", folders.Select(x => x.Key).ToArray());
-            }
+        var xml = new XElement("DocumentType",
+            info,
+            structure,
+            genericProperties,
+            tabs);
 
-            if (string.IsNullOrWhiteSpace(folderNames) == false)
-            {
-                xml.Add(new XAttribute("Folders", folderNames));
-                xml.Add(new XAttribute("FolderKeys", folderKeys));
-            }
+        if (contentType is IContentTypeWithHistoryCleanup withCleanup && withCleanup.HistoryCleanup is not null)
+        {
+            xml.Add(SerializeCleanupPolicy(withCleanup.HistoryCleanup));
+        }
 
+        var folderNames = string.Empty;
+        var folderKeys = string.Empty;
+        //don't add folders if this is a child doc type
+        if (contentType.Level != 1 && masterContentType == null)
+        {
+            //get URL encoded folder names
+            IOrderedEnumerable folders = _contentTypeService.GetContainers(contentType)
+                .OrderBy(x => x.Level);
 
-            return xml;
+            folderNames = string.Join("/", folders.Select(x => WebUtility.UrlEncode(x.Name)).ToArray());
+            folderKeys = string.Join("/", folders.Select(x => x.Key).ToArray());
         }
 
-        private IEnumerable SerializePropertyTypes(IEnumerable propertyTypes, IEnumerable propertyGroups)
+        if (string.IsNullOrWhiteSpace(folderNames) == false)
         {
-            foreach (var propertyType in propertyTypes)
-            {
-                var definition = _dataTypeService.GetDataType(propertyType.DataTypeId);
+            xml.Add(new XAttribute("Folders", folderNames));
+            xml.Add(new XAttribute("FolderKeys", folderKeys));
+        }
 
-                var propertyGroup = propertyType.PropertyGroupId == null // true generic property
-                    ? null
-                    : propertyGroups.FirstOrDefault(x => x.Id == propertyType.PropertyGroupId.Value);
 
-                XElement genericProperty = SerializePropertyType(propertyType, definition, propertyGroup);
-                genericProperty.Add(new XElement("Variations", propertyType.Variations.ToString()));
+        return xml;
+    }
 
-                yield return genericProperty;
-            }
-        }
+    private XElement Serialize(IDictionaryItem dictionaryItem)
+    {
+        var xml = new XElement("DictionaryItem",
+            new XAttribute("Key", dictionaryItem.Key),
+            new XAttribute("Name", dictionaryItem.ItemKey));
 
-        private IEnumerable SerializePropertyGroups(IEnumerable propertyGroups)
+        foreach (IDictionaryTranslation translation in dictionaryItem.Translations!)
         {
-            foreach (var propertyGroup in propertyGroups)
-            {
-                yield return new XElement("Tab", // TODO Rename to PropertyGroup
-                    new XElement("Id", propertyGroup.Id),
-                    new XElement("Key", propertyGroup.Key),
-                    new XElement("Type", propertyGroup.Type.ToString()),
-                    new XElement("Caption", propertyGroup.Name), // TODO Rename to Name (same in PackageDataInstallation)
-                    new XElement("Alias", propertyGroup.Alias),
-                    new XElement("SortOrder", propertyGroup.SortOrder));
-            }
+            xml.Add(new XElement("Value",
+                new XAttribute("LanguageId", translation.Language!.Id),
+                new XAttribute("LanguageCultureAlias", translation.Language.IsoCode),
+                new XCData(translation.Value!)));
         }
 
-        private XElement SerializePropertyType(IPropertyType propertyType, IDataType? definition, PropertyGroup? propertyGroup)
-            => new XElement("GenericProperty",
-                    new XElement("Name", propertyType.Name),
-                    new XElement("Alias", propertyType.Alias),
-                    new XElement("Key", propertyType.Key),
-                    new XElement("Type", propertyType.PropertyEditorAlias),
-                    definition is not null ? new XElement("Definition", definition.Key) : null,
-                    propertyGroup is not null ? new XElement("Tab", propertyGroup.Name, new XAttribute("Alias", propertyGroup.Alias)) : null, // TODO Replace with PropertyGroupAlias
-                    new XElement("SortOrder", propertyType.SortOrder),
-                    new XElement("Mandatory", propertyType.Mandatory.ToString()),
-                    new XElement("LabelOnTop", propertyType.LabelOnTop.ToString()),
-                    propertyType.MandatoryMessage != null ? new XElement("MandatoryMessage", propertyType.MandatoryMessage) : null,
-                    propertyType.ValidationRegExp != null ? new XElement("Validation", propertyType.ValidationRegExp) : null,
-                    propertyType.ValidationRegExpMessage != null ? new XElement("ValidationRegExpMessage", propertyType.ValidationRegExpMessage) : null,
-                    propertyType.Description != null ? new XElement("Description", new XCData(propertyType.Description)) : null);
-
-        private XElement SerializeCleanupPolicy(HistoryCleanup cleanupPolicy)
-        {
-            if (cleanupPolicy == null)
-            {
-                throw new ArgumentNullException(nameof(cleanupPolicy));
-            }
+        return xml;
+    }
 
-            var element = new XElement("HistoryCleanupPolicy",
-                new XAttribute("preventCleanup", cleanupPolicy.PreventCleanup));
+    private IEnumerable SerializePropertyTypes(IEnumerable propertyTypes,
+        IEnumerable propertyGroups)
+    {
+        foreach (IPropertyType propertyType in propertyTypes)
+        {
+            IDataType definition = _dataTypeService.GetDataType(propertyType.DataTypeId);
 
-            if (cleanupPolicy.KeepAllVersionsNewerThanDays.HasValue)
-            {
-                element.Add(new XAttribute("keepAllVersionsNewerThanDays", cleanupPolicy.KeepAllVersionsNewerThanDays));
-            }
+            PropertyGroup propertyGroup = propertyType.PropertyGroupId == null // true generic property
+                ? null
+                : propertyGroups.FirstOrDefault(x => x.Id == propertyType.PropertyGroupId.Value);
 
-            if (cleanupPolicy.KeepLatestVersionPerDayForDays.HasValue)
-            {
-                element.Add(new XAttribute("keepLatestVersionPerDayForDays", cleanupPolicy.KeepLatestVersionPerDayForDays));
-            }
+            XElement genericProperty = SerializePropertyType(propertyType, definition, propertyGroup);
+            genericProperty.Add(new XElement("Variations", propertyType.Variations.ToString()));
 
-            return element;
+            yield return genericProperty;
         }
+    }
 
-        // exports an IContentBase (IContent, IMedia or IMember) as an XElement.
-        private XElement SerializeContentBase(IContentBase contentBase, string? urlValue, string nodeName, bool published)
+    private IEnumerable SerializePropertyGroups(IEnumerable propertyGroups)
+    {
+        foreach (PropertyGroup propertyGroup in propertyGroups)
         {
-            var xml = new XElement(nodeName,
-                new XAttribute("id", contentBase.Id.ToInvariantString()),
-                new XAttribute("key", contentBase.Key),
-                new XAttribute("parentID", (contentBase.Level > 1 ? contentBase.ParentId : -1).ToInvariantString()),
-                new XAttribute("level", contentBase.Level),
-                new XAttribute("creatorID", contentBase.CreatorId.ToInvariantString()),
-                new XAttribute("sortOrder", contentBase.SortOrder),
-                new XAttribute("createDate", contentBase.CreateDate.ToString("s")),
-                new XAttribute("updateDate", contentBase.UpdateDate.ToString("s")),
-                new XAttribute("nodeName", contentBase.Name!),
-                new XAttribute("urlName", urlValue!),
-                new XAttribute("path", contentBase.Path),
-                new XAttribute("isDoc", ""));
+            yield return new XElement("Tab", // TODO Rename to PropertyGroup
+                new XElement("Id", propertyGroup.Id),
+                new XElement("Key", propertyGroup.Key),
+                new XElement("Type", propertyGroup.Type.ToString()),
+                new XElement("Caption", propertyGroup.Name), // TODO Rename to Name (same in PackageDataInstallation)
+                new XElement("Alias", propertyGroup.Alias),
+                new XElement("SortOrder", propertyGroup.SortOrder));
+        }
+    }
 
+    private XElement SerializePropertyType(IPropertyType propertyType, IDataType? definition,
+        PropertyGroup? propertyGroup)
+        => new("GenericProperty",
+            new XElement("Name", propertyType.Name),
+            new XElement("Alias", propertyType.Alias),
+            new XElement("Key", propertyType.Key),
+            new XElement("Type", propertyType.PropertyEditorAlias),
+            definition is not null ? new XElement("Definition", definition.Key) : null,
+            propertyGroup is not null
+                ? new XElement("Tab", propertyGroup.Name, new XAttribute("Alias", propertyGroup.Alias))
+                : null, // TODO Replace with PropertyGroupAlias
+            new XElement("SortOrder", propertyType.SortOrder),
+            new XElement("Mandatory", propertyType.Mandatory.ToString()),
+            new XElement("LabelOnTop", propertyType.LabelOnTop.ToString()),
+            propertyType.MandatoryMessage != null
+                ? new XElement("MandatoryMessage", propertyType.MandatoryMessage)
+                : null,
+            propertyType.ValidationRegExp != null ? new XElement("Validation", propertyType.ValidationRegExp) : null,
+            propertyType.ValidationRegExpMessage != null
+                ? new XElement("ValidationRegExpMessage", propertyType.ValidationRegExpMessage)
+                : null,
+            propertyType.Description != null
+                ? new XElement("Description", new XCData(propertyType.Description))
+                : null);
+
+    private XElement SerializeCleanupPolicy(HistoryCleanup cleanupPolicy)
+    {
+        if (cleanupPolicy == null)
+        {
+            throw new ArgumentNullException(nameof(cleanupPolicy));
+        }
 
-            // Add culture specific node names
-            foreach (var culture in contentBase.AvailableCultures)
-            {
-                xml.Add(new XAttribute("nodeName-" + culture, contentBase.GetCultureName(culture)!));
-            }
+        var element = new XElement("HistoryCleanupPolicy",
+            new XAttribute("preventCleanup", cleanupPolicy.PreventCleanup));
 
-            foreach (var property in contentBase.Properties)
-                xml.Add(SerializeProperty(property, published));
+        if (cleanupPolicy.KeepAllVersionsNewerThanDays.HasValue)
+        {
+            element.Add(new XAttribute("keepAllVersionsNewerThanDays", cleanupPolicy.KeepAllVersionsNewerThanDays));
+        }
 
-            return xml;
+        if (cleanupPolicy.KeepLatestVersionPerDayForDays.HasValue)
+        {
+            element.Add(new XAttribute("keepLatestVersionPerDayForDays", cleanupPolicy.KeepLatestVersionPerDayForDays));
         }
 
-        // exports a property as XElements.
-        private IEnumerable SerializeProperty(IProperty property, bool published)
+        return element;
+    }
+
+    // exports an IContentBase (IContent, IMedia or IMember) as an XElement.
+    private XElement SerializeContentBase(IContentBase contentBase, string? urlValue, string nodeName, bool published)
+    {
+        var xml = new XElement(nodeName,
+            new XAttribute("id", contentBase.Id.ToInvariantString()),
+            new XAttribute("key", contentBase.Key),
+            new XAttribute("parentID", (contentBase.Level > 1 ? contentBase.ParentId : -1).ToInvariantString()),
+            new XAttribute("level", contentBase.Level),
+            new XAttribute("creatorID", contentBase.CreatorId.ToInvariantString()),
+            new XAttribute("sortOrder", contentBase.SortOrder),
+            new XAttribute("createDate", contentBase.CreateDate.ToString("s")),
+            new XAttribute("updateDate", contentBase.UpdateDate.ToString("s")),
+            new XAttribute("nodeName", contentBase.Name!),
+            new XAttribute("urlName", urlValue!),
+            new XAttribute("path", contentBase.Path),
+            new XAttribute("isDoc", ""));
+
+
+        // Add culture specific node names
+        foreach (var culture in contentBase.AvailableCultures)
         {
-            var propertyType = property.PropertyType;
+            xml.Add(new XAttribute("nodeName-" + culture, contentBase.GetCultureName(culture)!));
+        }
 
-            // get the property editor for this property and let it convert it to the xml structure
-            var propertyEditor = _propertyEditors[propertyType.PropertyEditorAlias];
-            return propertyEditor == null
-                ? Array.Empty()
-                : propertyEditor.GetValueEditor().ConvertDbToXml(property, published);
+        foreach (IProperty property in contentBase.Properties)
+        {
+            xml.Add(SerializeProperty(property, published));
         }
 
-        // exports an IContent item descendants.
-        private void SerializeChildren(IEnumerable children, XElement xml, bool published)
+        return xml;
+    }
+
+    // exports a property as XElements.
+    private IEnumerable SerializeProperty(IProperty property, bool published)
+    {
+        IPropertyType propertyType = property.PropertyType;
+
+        // get the property editor for this property and let it convert it to the xml structure
+        IDataEditor propertyEditor = _propertyEditors[propertyType.PropertyEditorAlias];
+        return propertyEditor == null
+            ? Array.Empty()
+            : propertyEditor.GetValueEditor().ConvertDbToXml(property, published);
+    }
+
+    // exports an IContent item descendants.
+    private void SerializeChildren(IEnumerable children, XElement xml, bool published)
+    {
+        foreach (IContent child in children)
         {
-            foreach (var child in children)
+            // add the child xml
+            XElement childXml = Serialize(child, published);
+            xml.Add(childXml);
+
+            const int pageSize = 500;
+            var page = 0;
+            var total = long.MaxValue;
+            while (page * pageSize < total)
             {
-                // add the child xml
-                var childXml = Serialize(child, published);
-                xml.Add(childXml);
-
-                const int pageSize = 500;
-                var page = 0;
-                var total = long.MaxValue;
-                while(page * pageSize < total)
-                {
-                    var grandChildren = _contentService.GetPagedChildren(child.Id, page++, pageSize, out total);
-                    // recurse
-                    SerializeChildren(grandChildren, childXml, published);
-                }
+                IEnumerable grandChildren =
+                    _contentService.GetPagedChildren(child.Id, page++, pageSize, out total);
+                // recurse
+                SerializeChildren(grandChildren, childXml, published);
             }
         }
+    }
 
-        // exports an IMedia item descendants.
-        private void SerializeChildren(IEnumerable children, XElement xml, Action? onMediaItemSerialized)
+    // exports an IMedia item descendants.
+    private void SerializeChildren(IEnumerable children, XElement xml,
+        Action? onMediaItemSerialized)
+    {
+        foreach (IMedia child in children)
         {
-            foreach (var child in children)
-            {
-                // add the child xml
-                var childXml = Serialize(child, onMediaItemSerialized: onMediaItemSerialized);
-                xml.Add(childXml);
-
-                const int pageSize = 500;
-                var page = 0;
-                var total = long.MaxValue;
-                while (page * pageSize < total)
-                {
-                    var grandChildren = _mediaService.GetPagedChildren(child.Id, page++, pageSize, out total);
-                    // recurse
-                    SerializeChildren(grandChildren, childXml, onMediaItemSerialized);
-                }
+            // add the child xml
+            XElement childXml = Serialize(child, onMediaItemSerialized: onMediaItemSerialized);
+            xml.Add(childXml);
+
+            const int pageSize = 500;
+            var page = 0;
+            var total = long.MaxValue;
+            while (page * pageSize < total)
+            {
+                IEnumerable grandChildren =
+                    _mediaService.GetPagedChildren(child.Id, page++, pageSize, out total);
+                // recurse
+                SerializeChildren(grandChildren, childXml, onMediaItemSerialized);
             }
         }
     }
diff --git a/src/Umbraco.Core/Services/ExternalLoginService.cs b/src/Umbraco.Core/Services/ExternalLoginService.cs
index d934e895282e..9baf5dcd374d 100644
--- a/src/Umbraco.Core/Services/ExternalLoginService.cs
+++ b/src/Umbraco.Core/Services/ExternalLoginService.cs
@@ -1,7 +1,3 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using Umbraco.Cms.Core.Events;
@@ -11,110 +7,110 @@
 using Umbraco.Cms.Web.Common.DependencyInjection;
 using Umbraco.Extensions;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public class ExternalLoginService : RepositoryService, IExternalLoginService, IExternalLoginWithKeyService
 {
-    public class ExternalLoginService : RepositoryService, IExternalLoginService, IExternalLoginWithKeyService
-    {
-        private readonly IExternalLoginWithKeyRepository _externalLoginRepository;
+    private readonly IExternalLoginWithKeyRepository _externalLoginRepository;
 
-        public ExternalLoginService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory,
-            IExternalLoginWithKeyRepository externalLoginRepository)
-            : base(provider, loggerFactory, eventMessagesFactory)
-        {
-            _externalLoginRepository = externalLoginRepository;
-        }
+    public ExternalLoginService(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
+        IEventMessagesFactory eventMessagesFactory,
+        IExternalLoginWithKeyRepository externalLoginRepository)
+        : base(provider, loggerFactory, eventMessagesFactory) =>
+        _externalLoginRepository = externalLoginRepository;
 
-        [Obsolete("Use ctor injecting IExternalLoginWithKeyRepository")]
-        public ExternalLoginService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory,
-            IExternalLoginRepository externalLoginRepository)
-            : this(provider, loggerFactory, eventMessagesFactory, StaticServiceProvider.Instance.GetRequiredService())
-        {
-        }
+    [Obsolete("Use ctor injecting IExternalLoginWithKeyRepository")]
+    public ExternalLoginService(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
+        IEventMessagesFactory eventMessagesFactory,
+        IExternalLoginRepository externalLoginRepository)
+        : this(provider, loggerFactory, eventMessagesFactory,
+            StaticServiceProvider.Instance.GetRequiredService())
+    {
+    }
 
-        /// 
-        [Obsolete("Use overload that takes a user/member key (Guid).")]
-        public IEnumerable GetExternalLogins(int userId)
-            => GetExternalLogins(userId.ToGuid());
+    /// 
+    [Obsolete("Use overload that takes a user/member key (Guid).")]
+    public IEnumerable GetExternalLogins(int userId)
+        => GetExternalLogins(userId.ToGuid());
 
-        /// 
-        [Obsolete("Use overload that takes a user/member key (Guid).")]
-        public IEnumerable GetExternalLoginTokens(int userId) =>
-            GetExternalLoginTokens(userId.ToGuid());
+    /// 
+    [Obsolete("Use overload that takes a user/member key (Guid).")]
+    public IEnumerable GetExternalLoginTokens(int userId) =>
+        GetExternalLoginTokens(userId.ToGuid());
 
-        /// 
-        [Obsolete("Use overload that takes a user/member key (Guid).")]
-        public void Save(int userId, IEnumerable logins)
-            => Save(userId.ToGuid(), logins);
+    /// 
+    [Obsolete("Use overload that takes a user/member key (Guid).")]
+    public void Save(int userId, IEnumerable logins)
+        => Save(userId.ToGuid(), logins);
 
-        /// 
-        [Obsolete("Use overload that takes a user/member key (Guid).")]
-        public void Save(int userId, IEnumerable tokens)
-            => Save(userId.ToGuid(), tokens);
+    /// 
+    [Obsolete("Use overload that takes a user/member key (Guid).")]
+    public void Save(int userId, IEnumerable tokens)
+        => Save(userId.ToGuid(), tokens);
 
-        /// 
-        [Obsolete("Use overload that takes a user/member key (Guid).")]
-        public void DeleteUserLogins(int userId)
-            => DeleteUserLogins(userId.ToGuid());
+    /// 
+    [Obsolete("Use overload that takes a user/member key (Guid).")]
+    public void DeleteUserLogins(int userId)
+        => DeleteUserLogins(userId.ToGuid());
 
-        /// 
-        public IEnumerable GetExternalLogins(Guid userOrMemberKey)
+    /// 
+    public IEnumerable Find(string loginProvider, string providerKey)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _externalLoginRepository.Get(Query().Where(x => x.Key == userOrMemberKey))
-                    .ToList();
-            }
+            return _externalLoginRepository.Get(Query()
+                    .Where(x => x.ProviderKey == providerKey && x.LoginProvider == loginProvider))
+                .ToList();
         }
+    }
 
-        /// 
-        public IEnumerable GetExternalLoginTokens(Guid userOrMemberKey)
+    /// 
+    public IEnumerable GetExternalLogins(Guid userOrMemberKey)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _externalLoginRepository.Get(Query().Where(x => x.Key == userOrMemberKey))
-                    .ToList();
-            }
+            return _externalLoginRepository.Get(Query().Where(x => x.Key == userOrMemberKey))
+                .ToList();
         }
+    }
 
-        /// 
-        public IEnumerable Find(string loginProvider, string providerKey)
+    /// 
+    public IEnumerable GetExternalLoginTokens(Guid userOrMemberKey)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _externalLoginRepository.Get(Query()
-                    .Where(x => x.ProviderKey == providerKey && x.LoginProvider == loginProvider))
-                    .ToList();
-            }
+            return _externalLoginRepository.Get(Query().Where(x => x.Key == userOrMemberKey))
+                .ToList();
         }
+    }
 
-        /// 
-        public void Save(Guid userOrMemberKey, IEnumerable logins)
+    /// 
+    public void Save(Guid userOrMemberKey, IEnumerable logins)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (var scope = ScopeProvider.CreateCoreScope())
-            {
-                _externalLoginRepository.Save(userOrMemberKey, logins);
-                scope.Complete();
-            }
+            _externalLoginRepository.Save(userOrMemberKey, logins);
+            scope.Complete();
         }
+    }
 
-        /// 
-        public void Save(Guid userOrMemberKey, IEnumerable tokens)
+    /// 
+    public void Save(Guid userOrMemberKey, IEnumerable tokens)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (var scope = ScopeProvider.CreateCoreScope())
-            {
-                _externalLoginRepository.Save(userOrMemberKey, tokens);
-                scope.Complete();
-            }
+            _externalLoginRepository.Save(userOrMemberKey, tokens);
+            scope.Complete();
         }
+    }
 
-        /// 
-        public void DeleteUserLogins(Guid userOrMemberKey)
+    /// 
+    public void DeleteUserLogins(Guid userOrMemberKey)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (var scope = ScopeProvider.CreateCoreScope())
-            {
-                _externalLoginRepository.DeleteUserLogins(userOrMemberKey);
-                scope.Complete();
-            }
+            _externalLoginRepository.DeleteUserLogins(userOrMemberKey);
+            scope.Complete();
         }
     }
 }
diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs
index 2d901ffacd73..76f96c6529bb 100644
--- a/src/Umbraco.Core/Services/FileService.cs
+++ b/src/Umbraco.Core/Services/FileService.cs
@@ -1,7 +1,3 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
 using System.Text.RegularExpressions;
 using Microsoft.Extensions.FileProviders;
 using Microsoft.Extensions.Logging;
@@ -16,1002 +12,1028 @@
 using Umbraco.Cms.Core.Scoping;
 using Umbraco.Cms.Core.Strings;
 using Umbraco.Extensions;
+using File = System.IO.File;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Represents the File Service, which is an easy access to operations involving  objects like
+///     Scripts, Stylesheets and Templates
+/// 
+public class FileService : RepositoryService, IFileService
 {
-    /// 
-    /// Represents the File Service, which is an easy access to operations involving  objects like Scripts, Stylesheets and Templates
-    /// 
-    public class FileService : RepositoryService, IFileService
-    {
-        private readonly IStylesheetRepository _stylesheetRepository;
-        private readonly IScriptRepository _scriptRepository;
-        private readonly ITemplateRepository _templateRepository;
-        private readonly IPartialViewRepository _partialViewRepository;
-        private readonly IPartialViewMacroRepository _partialViewMacroRepository;
-        private readonly IAuditRepository _auditRepository;
-        private readonly IShortStringHelper _shortStringHelper;
-        private readonly GlobalSettings _globalSettings;
-        private readonly IHostingEnvironment _hostingEnvironment;
-
-        private const string PartialViewHeader = "@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage";
-        private const string PartialViewMacroHeader = "@inherits Umbraco.Cms.Web.Common.Macros.PartialViewMacroPage";
-
-        public FileService(ICoreScopeProvider uowProvider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory,
-            IStylesheetRepository stylesheetRepository, IScriptRepository scriptRepository, ITemplateRepository templateRepository,
-            IPartialViewRepository partialViewRepository, IPartialViewMacroRepository partialViewMacroRepository,
-            IAuditRepository auditRepository, IShortStringHelper shortStringHelper, IOptions globalSettings, IHostingEnvironment hostingEnvironment)
-            : base(uowProvider, loggerFactory, eventMessagesFactory)
-        {
-            _stylesheetRepository = stylesheetRepository;
-            _scriptRepository = scriptRepository;
-            _templateRepository = templateRepository;
-            _partialViewRepository = partialViewRepository;
-            _partialViewMacroRepository = partialViewMacroRepository;
-            _auditRepository = auditRepository;
-            _shortStringHelper = shortStringHelper;
-            _globalSettings = globalSettings.Value;
-            _hostingEnvironment = hostingEnvironment;
-        }
-
-        #region Stylesheets
-
-        /// 
-        public IEnumerable GetStylesheets(params string[] paths)
-        {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _stylesheetRepository.GetMany(paths);
-            }
+    private const string PartialViewHeader = "@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage";
+    private const string PartialViewMacroHeader = "@inherits Umbraco.Cms.Web.Common.Macros.PartialViewMacroPage";
+    private readonly IAuditRepository _auditRepository;
+    private readonly GlobalSettings _globalSettings;
+    private readonly IHostingEnvironment _hostingEnvironment;
+    private readonly IPartialViewMacroRepository _partialViewMacroRepository;
+    private readonly IPartialViewRepository _partialViewRepository;
+    private readonly IScriptRepository _scriptRepository;
+    private readonly IShortStringHelper _shortStringHelper;
+    private readonly IStylesheetRepository _stylesheetRepository;
+    private readonly ITemplateRepository _templateRepository;
+
+    public FileService(ICoreScopeProvider uowProvider, ILoggerFactory loggerFactory,
+        IEventMessagesFactory eventMessagesFactory,
+        IStylesheetRepository stylesheetRepository, IScriptRepository scriptRepository,
+        ITemplateRepository templateRepository,
+        IPartialViewRepository partialViewRepository, IPartialViewMacroRepository partialViewMacroRepository,
+        IAuditRepository auditRepository, IShortStringHelper shortStringHelper, IOptions globalSettings,
+        IHostingEnvironment hostingEnvironment)
+        : base(uowProvider, loggerFactory, eventMessagesFactory)
+    {
+        _stylesheetRepository = stylesheetRepository;
+        _scriptRepository = scriptRepository;
+        _templateRepository = templateRepository;
+        _partialViewRepository = partialViewRepository;
+        _partialViewMacroRepository = partialViewMacroRepository;
+        _auditRepository = auditRepository;
+        _shortStringHelper = shortStringHelper;
+        _globalSettings = globalSettings.Value;
+        _hostingEnvironment = hostingEnvironment;
+    }
+
+    private void Audit(AuditType type, int userId, int objectId, string? entityType) =>
+        _auditRepository.Save(new AuditItem(objectId, type, userId, entityType));
+
+    #region Stylesheets
+
+    /// 
+    public IEnumerable GetStylesheets(params string[] paths)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return _stylesheetRepository.GetMany(paths);
         }
+    }
 
-        /// 
-        public IStylesheet? GetStylesheet(string? path)
+    /// 
+    public IStylesheet? GetStylesheet(string? path)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _stylesheetRepository.Get(path);
-            }
+            return _stylesheetRepository.Get(path);
         }
+    }
 
-        /// 
-        public void SaveStylesheet(IStylesheet? stylesheet, int? userId = null)
+    /// 
+    public void SaveStylesheet(IStylesheet? stylesheet, int? userId = null)
+    {
+        if (stylesheet is null)
         {
-            if (stylesheet is null)
-            {
-                return;
-            }
+            return;
+        }
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
 
+        {
+            EventMessages eventMessages = EventMessagesFactory.Get();
+            var savingNotification = new StylesheetSavingNotification(stylesheet, eventMessages);
+            if (scope.Notifications.PublishCancelable(savingNotification))
             {
-                EventMessages eventMessages = EventMessagesFactory.Get();
-                var savingNotification = new StylesheetSavingNotification(stylesheet, eventMessages);
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
-
-                userId ??= Constants.Security.SuperUserId;
-                _stylesheetRepository.Save(stylesheet);
-                scope.Notifications.Publish(new StylesheetSavedNotification(stylesheet, eventMessages).WithStateFrom(savingNotification));
-                Audit(AuditType.Save, userId.Value, -1, "Stylesheet");
-
                 scope.Complete();
+                return;
             }
+
+            userId ??= Constants.Security.SuperUserId;
+            _stylesheetRepository.Save(stylesheet);
+            scope.Notifications.Publish(
+                new StylesheetSavedNotification(stylesheet, eventMessages).WithStateFrom(savingNotification));
+            Audit(AuditType.Save, userId.Value, -1, "Stylesheet");
+
+            scope.Complete();
         }
+    }
 
-        /// 
-        public void DeleteStylesheet(string path, int? userId)
+    /// 
+    public void DeleteStylesheet(string path, int? userId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            IStylesheet? stylesheet = _stylesheetRepository.Get(path);
+            if (stylesheet == null)
             {
-                IStylesheet? stylesheet = _stylesheetRepository.Get(path);
-                if (stylesheet == null)
-                {
-                    scope.Complete();
-                    return;
-                }
+                scope.Complete();
+                return;
+            }
 
-                EventMessages eventMessages = EventMessagesFactory.Get();
-                var deletingNotification = new StylesheetDeletingNotification(stylesheet, eventMessages);
-                if (scope.Notifications.PublishCancelable(deletingNotification))
-                {
-                    scope.Complete();
-                    return; // causes rollback
-                }
+            EventMessages eventMessages = EventMessagesFactory.Get();
+            var deletingNotification = new StylesheetDeletingNotification(stylesheet, eventMessages);
+            if (scope.Notifications.PublishCancelable(deletingNotification))
+            {
+                scope.Complete();
+                return; // causes rollback
+            }
 
-                userId ??= Constants.Security.SuperUserId;
-                _stylesheetRepository.Delete(stylesheet);
+            userId ??= Constants.Security.SuperUserId;
+            _stylesheetRepository.Delete(stylesheet);
 
-                scope.Notifications.Publish(new StylesheetDeletedNotification(stylesheet, eventMessages).WithStateFrom(deletingNotification));
-                Audit(AuditType.Delete, userId.Value, -1, "Stylesheet");
+            scope.Notifications.Publish(
+                new StylesheetDeletedNotification(stylesheet, eventMessages).WithStateFrom(deletingNotification));
+            Audit(AuditType.Delete, userId.Value, -1, "Stylesheet");
 
-                scope.Complete();
-            }
+            scope.Complete();
         }
+    }
 
-        /// 
-        public void CreateStyleSheetFolder(string folderPath)
+    /// 
+    public void CreateStyleSheetFolder(string folderPath)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                _stylesheetRepository.AddFolder(folderPath);
-                scope.Complete();
-            }
+            _stylesheetRepository.AddFolder(folderPath);
+            scope.Complete();
         }
+    }
 
-        /// 
-        public void DeleteStyleSheetFolder(string folderPath)
+    /// 
+    public void DeleteStyleSheetFolder(string folderPath)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                _stylesheetRepository.DeleteFolder(folderPath);
-                scope.Complete();
-            }
+            _stylesheetRepository.DeleteFolder(folderPath);
+            scope.Complete();
         }
+    }
 
-        /// 
-        public Stream GetStylesheetFileContentStream(string filepath)
+    /// 
+    public Stream GetStylesheetFileContentStream(string filepath)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _stylesheetRepository.GetFileContentStream(filepath);
-            }
+            return _stylesheetRepository.GetFileContentStream(filepath);
         }
+    }
 
-        /// 
-        public void SetStylesheetFileContent(string filepath, Stream content)
+    /// 
+    public void SetStylesheetFileContent(string filepath, Stream content)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                _stylesheetRepository.SetFileContent(filepath, content);
-                scope.Complete();
-            }
+            _stylesheetRepository.SetFileContent(filepath, content);
+            scope.Complete();
         }
+    }
 
-        /// 
-        public long GetStylesheetFileSize(string filepath)
+    /// 
+    public long GetStylesheetFileSize(string filepath)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _stylesheetRepository.GetFileSize(filepath);
-            }
+            return _stylesheetRepository.GetFileSize(filepath);
         }
+    }
 
-        #endregion
+    #endregion
 
-        #region Scripts
+    #region Scripts
 
-        /// 
-        public IEnumerable GetScripts(params string[] names)
+    /// 
+    public IEnumerable GetScripts(params string[] names)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _scriptRepository.GetMany(names);
-            }
+            return _scriptRepository.GetMany(names);
         }
+    }
 
-        /// 
-        public IScript? GetScript(string? name)
+    /// 
+    public IScript? GetScript(string? name)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _scriptRepository.Get(name);
-            }
+            return _scriptRepository.Get(name);
         }
+    }
 
-        /// 
-        public void SaveScript(IScript? script, int? userId)
+    /// 
+    public void SaveScript(IScript? script, int? userId)
+    {
+        if (userId is null)
         {
-            if (userId is null)
-            {
-                userId = Constants.Security.SuperUserId;
-            }
-            if (script is null)
-            {
-                return;
-            }
-
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            userId = Constants.Security.SuperUserId;
+        }
 
-            {
-                EventMessages eventMessages = EventMessagesFactory.Get();
-                var savingNotification = new ScriptSavingNotification(script, eventMessages);
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
+        if (script is null)
+        {
+            return;
+        }
 
-                _scriptRepository.Save(script);
-                scope.Notifications.Publish(new ScriptSavedNotification(script, eventMessages).WithStateFrom(savingNotification));
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
 
-                Audit(AuditType.Save, userId.Value, -1, "Script");
+        {
+            EventMessages eventMessages = EventMessagesFactory.Get();
+            var savingNotification = new ScriptSavingNotification(script, eventMessages);
+            if (scope.Notifications.PublishCancelable(savingNotification))
+            {
                 scope.Complete();
+                return;
             }
+
+            _scriptRepository.Save(script);
+            scope.Notifications.Publish(
+                new ScriptSavedNotification(script, eventMessages).WithStateFrom(savingNotification));
+
+            Audit(AuditType.Save, userId.Value, -1, "Script");
+            scope.Complete();
         }
+    }
 
-        /// 
-        public void DeleteScript(string path, int? userId = null)
+    /// 
+    public void DeleteScript(string path, int? userId = null)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            IScript? script = _scriptRepository.Get(path);
+            if (script == null)
             {
-                IScript? script = _scriptRepository.Get(path);
-                if (script == null)
-                {
-                    scope.Complete();
-                    return;
-                }
-
-                EventMessages eventMessages = EventMessagesFactory.Get();
-                var deletingNotification = new ScriptDeletingNotification(script, eventMessages);
-                if (scope.Notifications.PublishCancelable(deletingNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
-
-                userId ??= Constants.Security.SuperUserId;
-                _scriptRepository.Delete(script);
-                scope.Notifications.Publish(new ScriptDeletedNotification(script, eventMessages).WithStateFrom(deletingNotification));
-
-                Audit(AuditType.Delete, userId.Value, -1, "Script");
                 scope.Complete();
+                return;
             }
-        }
 
-        /// 
-        public void CreateScriptFolder(string folderPath)
-        {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            EventMessages eventMessages = EventMessagesFactory.Get();
+            var deletingNotification = new ScriptDeletingNotification(script, eventMessages);
+            if (scope.Notifications.PublishCancelable(deletingNotification))
             {
-                _scriptRepository.AddFolder(folderPath);
                 scope.Complete();
+                return;
             }
+
+            userId ??= Constants.Security.SuperUserId;
+            _scriptRepository.Delete(script);
+            scope.Notifications.Publish(
+                new ScriptDeletedNotification(script, eventMessages).WithStateFrom(deletingNotification));
+
+            Audit(AuditType.Delete, userId.Value, -1, "Script");
+            scope.Complete();
+        }
+    }
+
+    /// 
+    public void CreateScriptFolder(string folderPath)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            _scriptRepository.AddFolder(folderPath);
+            scope.Complete();
         }
+    }
 
-        /// 
-        public void DeleteScriptFolder(string folderPath)
+    /// 
+    public void DeleteScriptFolder(string folderPath)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                _scriptRepository.DeleteFolder(folderPath);
-                scope.Complete();
-            }
+            _scriptRepository.DeleteFolder(folderPath);
+            scope.Complete();
         }
+    }
 
-        /// 
-        public Stream GetScriptFileContentStream(string filepath)
+    /// 
+    public Stream GetScriptFileContentStream(string filepath)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _scriptRepository.GetFileContentStream(filepath);
-            }
+            return _scriptRepository.GetFileContentStream(filepath);
         }
+    }
 
-        /// 
-        public void SetScriptFileContent(string filepath, Stream content)
+    /// 
+    public void SetScriptFileContent(string filepath, Stream content)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                _scriptRepository.SetFileContent(filepath, content);
-                scope.Complete();
-            }
+            _scriptRepository.SetFileContent(filepath, content);
+            scope.Complete();
         }
+    }
 
-        /// 
-        public long GetScriptFileSize(string filepath)
+    /// 
+    public long GetScriptFileSize(string filepath)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _scriptRepository.GetFileSize(filepath);
-            }
+            return _scriptRepository.GetFileSize(filepath);
         }
+    }
 
-        #endregion
+    #endregion
 
-        #region Templates
+    #region Templates
 
-        /// 
-        /// Creates a template for a content type
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// The template created
-        /// 
-        public Attempt?> CreateTemplateForContentType(string contentTypeAlias, string? contentTypeName, int userId = Constants.Security.SuperUserId)
+    /// 
+    ///     Creates a template for a content type
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    ///     The template created
+    /// 
+    public Attempt?> CreateTemplateForContentType(
+        string contentTypeAlias, string? contentTypeName, int userId = Constants.Security.SuperUserId)
+    {
+        var template = new Template(_shortStringHelper, contentTypeName,
+            //NOTE: We are NOT passing in the content type alias here, we want to use it's name since we don't
+            // want to save template file names as camelCase, the Template ctor will clean the alias as
+            // `alias.ToCleanString(CleanStringType.UnderscoreAlias)` which has been the default.
+            // This fixes: http://issues.umbraco.org/issue/U4-7953
+            contentTypeName);
+
+        EventMessages eventMessages = EventMessagesFactory.Get();
+
+        if (contentTypeAlias != null && contentTypeAlias.Length > 255)
         {
-            var template = new Template(_shortStringHelper, contentTypeName,
-                //NOTE: We are NOT passing in the content type alias here, we want to use it's name since we don't
-                // want to save template file names as camelCase, the Template ctor will clean the alias as
-                // `alias.ToCleanString(CleanStringType.UnderscoreAlias)` which has been the default.
-                // This fixes: http://issues.umbraco.org/issue/U4-7953
-                contentTypeName);
+            throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+        }
 
-            EventMessages eventMessages = EventMessagesFactory.Get();
+        // check that the template hasn't been created on disk before creating the content type
+        // if it exists, set the new template content to the existing file content
+        var content = GetViewContent(contentTypeAlias);
+        if (content != null)
+        {
+            template.Content = content;
+        }
 
-            if (contentTypeAlias != null && contentTypeAlias.Length > 255)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            var savingEvent = new TemplateSavingNotification(template, eventMessages, true, contentTypeAlias!);
+            if (scope.Notifications.PublishCancelable(savingEvent))
             {
-                throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+                scope.Complete();
+                return OperationResult.Attempt.Fail(
+                    OperationResultType.FailedCancelledByEvent, eventMessages, template);
             }
 
-            // check that the template hasn't been created on disk before creating the content type
-            // if it exists, set the new template content to the existing file content
-            string? content = GetViewContent(contentTypeAlias);
-            if (content != null)
-            {
-                template.Content = content;
-            }
+            _templateRepository.Save(template);
+            scope.Notifications.Publish(
+                new TemplateSavedNotification(template, eventMessages).WithStateFrom(savingEvent));
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                var savingEvent = new TemplateSavingNotification(template, eventMessages, true, contentTypeAlias!);
-                if (scope.Notifications.PublishCancelable(savingEvent))
-                {
-                    scope.Complete();
-                    return OperationResult.Attempt.Fail(OperationResultType.FailedCancelledByEvent, eventMessages, template);
-                }
+            Audit(AuditType.Save, userId, template.Id, UmbracoObjectTypes.Template.GetName());
+            scope.Complete();
+        }
 
-                _templateRepository.Save(template);
-                scope.Notifications.Publish(new TemplateSavedNotification(template, eventMessages).WithStateFrom(savingEvent));
+        return OperationResult.Attempt.Succeed(OperationResultType.Success,
+            eventMessages, template);
+    }
 
-                Audit(AuditType.Save, userId, template.Id, ObjectTypes.GetName(UmbracoObjectTypes.Template));
-                scope.Complete();
-            }
+    /// 
+    ///     Create a new template, setting the content if a view exists in the filesystem
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    public ITemplate CreateTemplateWithIdentity(string? name, string? alias, string? content,
+        ITemplate? masterTemplate = null, int userId = Constants.Security.SuperUserId)
+    {
+        if (name == null)
+        {
+            throw new ArgumentNullException(nameof(name));
+        }
 
-            return OperationResult.Attempt.Succeed(OperationResultType.Success, eventMessages, template);
+        if (string.IsNullOrWhiteSpace(name))
+        {
+            throw new ArgumentException("Name cannot be empty or contain only white-space characters", nameof(name));
         }
 
-        /// 
-        /// Create a new template, setting the content if a view exists in the filesystem
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        public ITemplate CreateTemplateWithIdentity(string? name, string? alias, string? content, ITemplate? masterTemplate = null, int userId = Constants.Security.SuperUserId)
+        if (name.Length > 255)
         {
-            if (name == null)
-            {
-                throw new ArgumentNullException(nameof(name));
-            }
+            throw new ArgumentOutOfRangeException(nameof(name), "Name cannot be more than 255 characters in length.");
+        }
 
-            if (string.IsNullOrWhiteSpace(name))
-            {
-                throw new ArgumentException("Name cannot be empty or contain only white-space characters", nameof(name));
-            }
+        // file might already be on disk, if so grab the content to avoid overwriting
+        var template = new Template(_shortStringHelper, name, alias) {Content = GetViewContent(alias) ?? content};
 
-            if (name.Length > 255)
-            {
-                throw new ArgumentOutOfRangeException(nameof(name), "Name cannot be more than 255 characters in length.");
-            }
+        if (masterTemplate != null)
+        {
+            template.SetMasterTemplate(masterTemplate);
+        }
 
-            // file might already be on disk, if so grab the content to avoid overwriting
-            var template = new Template(_shortStringHelper, name, alias)
-            {
-                Content = GetViewContent(alias) ?? content
-            };
+        SaveTemplate(template, userId);
 
-            if (masterTemplate != null)
-            {
-                template.SetMasterTemplate(masterTemplate);
-            }
+        return template;
+    }
 
-            SaveTemplate(template, userId);
+    /// 
+    ///     Gets a list of all  objects
+    /// 
+    /// An enumerable list of  objects
+    public IEnumerable? GetTemplates(params string[] aliases)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return _templateRepository.GetAll(aliases)?.OrderBy(x => x.Name);
+        }
+    }
 
-            return template;
+    /// 
+    ///     Gets a list of all  objects
+    /// 
+    /// An enumerable list of  objects
+    public IEnumerable? GetTemplates(int masterTemplateId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return _templateRepository.GetChildren(masterTemplateId)?.OrderBy(x => x.Name);
         }
+    }
 
-        /// 
-        /// Gets a list of all  objects
-        /// 
-        /// An enumerable list of  objects
-        public IEnumerable? GetTemplates(params string[] aliases)
+    /// 
+    ///     Gets a  object by its alias.
+    /// 
+    /// The alias of the template.
+    /// The  object matching the alias, or null.
+    public ITemplate? GetTemplate(string? alias)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _templateRepository.GetAll(aliases)?.OrderBy(x => x.Name);
-            }
+            return _templateRepository.Get(alias);
         }
+    }
 
-        /// 
-        /// Gets a list of all  objects
-        /// 
-        /// An enumerable list of  objects
-        public IEnumerable? GetTemplates(int masterTemplateId)
+    /// 
+    ///     Gets a  object by its identifier.
+    /// 
+    /// The identifier of the template.
+    /// The  object matching the identifier, or null.
+    public ITemplate? GetTemplate(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _templateRepository.GetChildren(masterTemplateId)?.OrderBy(x => x.Name);
-            }
+            return _templateRepository.Get(id);
         }
+    }
 
-        /// 
-        /// Gets a  object by its alias.
-        /// 
-        /// The alias of the template.
-        /// The  object matching the alias, or null.
-        public ITemplate? GetTemplate(string? alias)
+    /// 
+    ///     Gets a  object by its guid identifier.
+    /// 
+    /// The guid identifier of the template.
+    /// The  object matching the identifier, or null.
+    public ITemplate? GetTemplate(Guid id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _templateRepository.Get(alias);
-            }
+            IQuery? query = Query().Where(x => x.Key == id);
+            return _templateRepository.Get(query)?.SingleOrDefault();
         }
+    }
 
-        /// 
-        /// Gets a  object by its identifier.
-        /// 
-        /// The identifier of the template.
-        /// The  object matching the identifier, or null.
-        public ITemplate? GetTemplate(int id)
+    /// 
+    ///     Gets the template descendants
+    /// 
+    /// 
+    /// 
+    public IEnumerable GetTemplateDescendants(int masterTemplateId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _templateRepository.Get(id);
-            }
+            return _templateRepository.GetDescendants(masterTemplateId);
         }
+    }
 
-        /// 
-        /// Gets a  object by its guid identifier.
-        /// 
-        /// The guid identifier of the template.
-        /// The  object matching the identifier, or null.
-        public ITemplate? GetTemplate(Guid id)
+    /// 
+    ///     Saves a 
+    /// 
+    ///  to save
+    /// 
+    public void SaveTemplate(ITemplate template, int userId = Constants.Security.SuperUserId)
+    {
+        if (template == null)
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                IQuery? query = Query().Where(x => x.Key == id);
-                return _templateRepository.Get(query)?.SingleOrDefault();
-            }
+            throw new ArgumentNullException(nameof(template));
         }
 
-        /// 
-        /// Gets the template descendants
-        /// 
-        /// 
-        /// 
-        public IEnumerable GetTemplateDescendants(int masterTemplateId)
+        if (string.IsNullOrWhiteSpace(template.Name) || template.Name.Length > 255)
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _templateRepository.GetDescendants(masterTemplateId);
-            }
+            throw new InvalidOperationException(
+                "Name cannot be null, empty, contain only white-space characters or be more than 255 characters in length.");
         }
 
-        /// 
-        /// Saves a 
-        /// 
-        ///  to save
-        /// 
-        public void SaveTemplate(ITemplate template, int userId = Constants.Security.SuperUserId)
+
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            if (template == null)
+            EventMessages eventMessages = EventMessagesFactory.Get();
+            var savingNotification = new TemplateSavingNotification(template, eventMessages);
+            if (scope.Notifications.PublishCancelable(savingNotification))
             {
-                throw new ArgumentNullException(nameof(template));
+                scope.Complete();
+                return;
             }
 
-            if (string.IsNullOrWhiteSpace(template.Name) || template.Name.Length > 255)
-            {
-                throw new InvalidOperationException("Name cannot be null, empty, contain only white-space characters or be more than 255 characters in length.");
-            }
+            _templateRepository.Save(template);
+
+            scope.Notifications.Publish(
+                new TemplateSavedNotification(template, eventMessages).WithStateFrom(savingNotification));
 
+            Audit(AuditType.Save, userId, template.Id, UmbracoObjectTypes.Template.GetName());
+            scope.Complete();
+        }
+    }
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+    /// 
+    ///     Saves a collection of  objects
+    /// 
+    /// List of  to save
+    /// Optional id of the user
+    public void SaveTemplate(IEnumerable templates, int userId = Constants.Security.SuperUserId)
+    {
+        ITemplate[] templatesA = templates.ToArray();
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            EventMessages eventMessages = EventMessagesFactory.Get();
+            var savingNotification = new TemplateSavingNotification(templatesA, eventMessages);
+            if (scope.Notifications.PublishCancelable(savingNotification))
             {
-                EventMessages eventMessages = EventMessagesFactory.Get();
-                var savingNotification = new TemplateSavingNotification(template, eventMessages);
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
+                scope.Complete();
+                return;
+            }
 
+            foreach (ITemplate template in templatesA)
+            {
                 _templateRepository.Save(template);
+            }
 
-                scope.Notifications.Publish(new TemplateSavedNotification(template, eventMessages).WithStateFrom(savingNotification));
+            scope.Notifications.Publish(
+                new TemplateSavedNotification(templatesA, eventMessages).WithStateFrom(savingNotification));
 
-                Audit(AuditType.Save, userId, template.Id, UmbracoObjectTypes.Template.GetName());
-                scope.Complete();
-            }
+            Audit(AuditType.Save, userId, -1, UmbracoObjectTypes.Template.GetName());
+            scope.Complete();
         }
+    }
 
-        /// 
-        /// Saves a collection of  objects
-        /// 
-        /// List of  to save
-        /// Optional id of the user
-        public void SaveTemplate(IEnumerable templates, int userId = Constants.Security.SuperUserId)
+    /// 
+    ///     Deletes a template by its alias
+    /// 
+    /// Alias of the  to delete
+    /// 
+    public void DeleteTemplate(string alias, int userId = Constants.Security.SuperUserId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            ITemplate[] templatesA = templates.ToArray();
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            ITemplate? template = _templateRepository.Get(alias);
+            if (template == null)
             {
-                EventMessages eventMessages = EventMessagesFactory.Get();
-                var savingNotification = new TemplateSavingNotification(templatesA, eventMessages);
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
-
-                foreach (ITemplate template in templatesA)
-                {
-                    _templateRepository.Save(template);
-                }
-
-                scope.Notifications.Publish(new TemplateSavedNotification(templatesA, eventMessages).WithStateFrom(savingNotification));
-
-                Audit(AuditType.Save, userId, -1, UmbracoObjectTypes.Template.GetName());
                 scope.Complete();
+                return;
             }
-        }
 
-        /// 
-        /// Deletes a template by its alias
-        /// 
-        /// Alias of the  to delete
-        /// 
-        public void DeleteTemplate(string alias, int userId = Constants.Security.SuperUserId)
-        {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            EventMessages eventMessages = EventMessagesFactory.Get();
+            var deletingNotification = new TemplateDeletingNotification(template, eventMessages);
+            if (scope.Notifications.PublishCancelable(deletingNotification))
             {
-                ITemplate? template = _templateRepository.Get(alias);
-                if (template == null)
-                {
-                    scope.Complete();
-                    return;
-                }
-
-                EventMessages eventMessages = EventMessagesFactory.Get();
-                var deletingNotification = new TemplateDeletingNotification(template, eventMessages);
-                if (scope.Notifications.PublishCancelable(deletingNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
+                scope.Complete();
+                return;
+            }
 
-                _templateRepository.Delete(template);
+            _templateRepository.Delete(template);
 
-                scope.Notifications.Publish(new TemplateDeletedNotification(template, eventMessages).WithStateFrom(deletingNotification));
+            scope.Notifications.Publish(
+                new TemplateDeletedNotification(template, eventMessages).WithStateFrom(deletingNotification));
 
-                Audit(AuditType.Delete, userId, template.Id, ObjectTypes.GetName(UmbracoObjectTypes.Template));
-                scope.Complete();
-            }
+            Audit(AuditType.Delete, userId, template.Id, UmbracoObjectTypes.Template.GetName());
+            scope.Complete();
         }
+    }
 
-        private string? GetViewContent(string? fileName)
+    private string? GetViewContent(string? fileName)
+    {
+        if (fileName.IsNullOrWhiteSpace())
         {
-            if (fileName.IsNullOrWhiteSpace())
-            {
-                throw new ArgumentNullException(nameof(fileName));
-            }
+            throw new ArgumentNullException(nameof(fileName));
+        }
 
-            if (!fileName!.EndsWith(".cshtml"))
-            {
-                fileName = $"{fileName}.cshtml";
-            }
+        if (!fileName!.EndsWith(".cshtml"))
+        {
+            fileName = $"{fileName}.cshtml";
+        }
 
-            Stream fs = _templateRepository.GetFileContentStream(fileName);
+        Stream fs = _templateRepository.GetFileContentStream(fileName);
 
-            using (var view = new StreamReader(fs))
-            {
-                return view.ReadToEnd().Trim();
-            }
+        using (var view = new StreamReader(fs))
+        {
+            return view.ReadToEnd().Trim();
         }
+    }
 
-        /// 
-        public Stream GetTemplateFileContentStream(string filepath)
+    /// 
+    public Stream GetTemplateFileContentStream(string filepath)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _templateRepository.GetFileContentStream(filepath);
-            }
+            return _templateRepository.GetFileContentStream(filepath);
         }
+    }
 
-        /// 
-        public void SetTemplateFileContent(string filepath, Stream content)
+    /// 
+    public void SetTemplateFileContent(string filepath, Stream content)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                _templateRepository.SetFileContent(filepath, content);
-                scope.Complete();
-            }
+            _templateRepository.SetFileContent(filepath, content);
+            scope.Complete();
         }
+    }
 
-        /// 
-        public long GetTemplateFileSize(string filepath)
+    /// 
+    public long GetTemplateFileSize(string filepath)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _templateRepository.GetFileSize(filepath);
-            }
+            return _templateRepository.GetFileSize(filepath);
         }
+    }
 
-        #endregion
+    #endregion
 
-        #region Partial Views
+    #region Partial Views
 
-        public IEnumerable GetPartialViewSnippetNames(params string[] filterNames)
-        {
-            var snippetProvider =
-                new EmbeddedFileProvider(this.GetType().Assembly, "Umbraco.Cms.Core.EmbeddedResources.Snippets");
+    public IEnumerable GetPartialViewSnippetNames(params string[] filterNames)
+    {
+        var snippetProvider =
+            new EmbeddedFileProvider(GetType().Assembly, "Umbraco.Cms.Core.EmbeddedResources.Snippets");
 
-            var files = snippetProvider.GetDirectoryContents(string.Empty)
-                .Where(x => !x.IsDirectory && x.Name.EndsWith(".cshtml"))
-                .Select(x => Path.GetFileNameWithoutExtension(x.Name))
-                .Except(filterNames, StringComparer.InvariantCultureIgnoreCase)
-                .ToArray();
+        var files = snippetProvider.GetDirectoryContents(string.Empty)
+            .Where(x => !x.IsDirectory && x.Name.EndsWith(".cshtml"))
+            .Select(x => Path.GetFileNameWithoutExtension(x.Name))
+            .Except(filterNames, StringComparer.InvariantCultureIgnoreCase)
+            .ToArray();
 
-            //Ensure the ones that are called 'Empty' are at the top
-            var empty = files.Where(x => Path.GetFileName(x)?.InvariantStartsWith("Empty") ?? false)
-                .OrderBy(x => x?.Length)
-                .ToArray();
+        //Ensure the ones that are called 'Empty' are at the top
+        var empty = files.Where(x => Path.GetFileName(x)?.InvariantStartsWith("Empty") ?? false)
+            .OrderBy(x => x?.Length)
+            .ToArray();
 
-            return empty.Union(files.Except(empty)).WhereNotNull();
-        }
+        return empty.Union(files.Except(empty)).WhereNotNull();
+    }
 
-        public void DeletePartialViewFolder(string folderPath)
+    public void DeletePartialViewFolder(string folderPath)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                _partialViewRepository.DeleteFolder(folderPath);
-                scope.Complete();
-            }
+            _partialViewRepository.DeleteFolder(folderPath);
+            scope.Complete();
         }
+    }
 
-        public void DeletePartialViewMacroFolder(string folderPath)
+    public void DeletePartialViewMacroFolder(string folderPath)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                _partialViewMacroRepository.DeleteFolder(folderPath);
-                scope.Complete();
-            }
+            _partialViewMacroRepository.DeleteFolder(folderPath);
+            scope.Complete();
         }
+    }
 
-        public IEnumerable GetPartialViews(params string[] names)
+    public IEnumerable GetPartialViews(params string[] names)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _partialViewRepository.GetMany(names);
-            }
+            return _partialViewRepository.GetMany(names);
         }
+    }
 
-        public IPartialView? GetPartialView(string path)
+    public IPartialView? GetPartialView(string path)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _partialViewRepository.Get(path);
-            }
+            return _partialViewRepository.Get(path);
         }
+    }
 
-        public IPartialView? GetPartialViewMacro(string path)
+    public IPartialView? GetPartialViewMacro(string path)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _partialViewMacroRepository.Get(path);
-            }
+            return _partialViewMacroRepository.Get(path);
         }
+    }
 
-        public Attempt CreatePartialView(IPartialView partialView, string? snippetName = null, int? userId = Constants.Security.SuperUserId) =>
-            CreatePartialViewMacro(partialView, PartialViewType.PartialView, snippetName, userId);
+    public Attempt CreatePartialView(IPartialView partialView, string? snippetName = null,
+        int? userId = Constants.Security.SuperUserId) =>
+        CreatePartialViewMacro(partialView, PartialViewType.PartialView, snippetName, userId);
 
-        public Attempt CreatePartialViewMacro(IPartialView partialView, string? snippetName = null, int? userId = Constants.Security.SuperUserId) =>
-            CreatePartialViewMacro(partialView, PartialViewType.PartialViewMacro, snippetName, userId);
+    public Attempt CreatePartialViewMacro(IPartialView partialView, string? snippetName = null,
+        int? userId = Constants.Security.SuperUserId) =>
+        CreatePartialViewMacro(partialView, PartialViewType.PartialViewMacro, snippetName, userId);
 
-        private Attempt CreatePartialViewMacro(IPartialView partialView, PartialViewType partialViewType, string? snippetName = null, int? userId = Constants.Security.SuperUserId)
+    private Attempt CreatePartialViewMacro(IPartialView partialView, PartialViewType partialViewType,
+        string? snippetName = null, int? userId = Constants.Security.SuperUserId)
+    {
+        string partialViewHeader;
+        switch (partialViewType)
+        {
+            case PartialViewType.PartialView:
+                partialViewHeader = PartialViewHeader;
+                break;
+            case PartialViewType.PartialViewMacro:
+                partialViewHeader = PartialViewMacroHeader;
+                break;
+            default:
+                throw new ArgumentOutOfRangeException(nameof(partialViewType));
+        }
+
+        string? partialViewContent = null;
+        if (snippetName.IsNullOrWhiteSpace() == false)
         {
-            string partialViewHeader;
-            switch (partialViewType)
+            //create the file
+            Attempt snippetPathAttempt = TryGetSnippetPath(snippetName);
+            if (snippetPathAttempt.Success == false)
             {
-                case PartialViewType.PartialView:
-                    partialViewHeader = PartialViewHeader;
-                    break;
-                case PartialViewType.PartialViewMacro:
-                    partialViewHeader = PartialViewMacroHeader;
-                    break;
-                default:
-                    throw new ArgumentOutOfRangeException(nameof(partialViewType));
+                throw new InvalidOperationException("Could not load snippet with name " + snippetName);
             }
 
-            string? partialViewContent = null;
-            if (snippetName.IsNullOrWhiteSpace() == false)
+            using (var snippetFile = new StreamReader(File.OpenRead(snippetPathAttempt.Result!)))
             {
-                //create the file
-                Attempt snippetPathAttempt = TryGetSnippetPath(snippetName);
-                if (snippetPathAttempt.Success == false)
-                {
-                    throw new InvalidOperationException("Could not load snippet with name " + snippetName);
-                }
-
-                using (var snippetFile = new StreamReader(System.IO.File.OpenRead(snippetPathAttempt.Result!)))
-                {
-                    var snippetContent = snippetFile.ReadToEnd().Trim();
-
-                    //strip the @inherits if it's there
-                    snippetContent = StripPartialViewHeader(snippetContent);
+                var snippetContent = snippetFile.ReadToEnd().Trim();
 
-                    //Update Model.Content. to be Model. when used as PartialView
-                    if(partialViewType == PartialViewType.PartialView)
-                    {
-                        snippetContent = snippetContent.Replace("Model.Content.", "Model.");
-                    }
+                //strip the @inherits if it's there
+                snippetContent = StripPartialViewHeader(snippetContent);
 
-                    partialViewContent = $"{partialViewHeader}{Environment.NewLine}{snippetContent}";
+                //Update Model.Content. to be Model. when used as PartialView
+                if (partialViewType == PartialViewType.PartialView)
+                {
+                    snippetContent = snippetContent.Replace("Model.Content.", "Model.");
                 }
+
+                partialViewContent = $"{partialViewHeader}{Environment.NewLine}{snippetContent}";
             }
+        }
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            EventMessages eventMessages = EventMessagesFactory.Get();
+            var creatingNotification = new PartialViewCreatingNotification(partialView, eventMessages);
+            if (scope.Notifications.PublishCancelable(creatingNotification))
             {
-                EventMessages eventMessages = EventMessagesFactory.Get();
-                var creatingNotification = new PartialViewCreatingNotification(partialView, eventMessages);
-                if (scope.Notifications.PublishCancelable(creatingNotification))
-                {
-                    scope.Complete();
-                    return Attempt.Fail();
-                }
+                scope.Complete();
+                return Attempt.Fail();
+            }
 
-                IPartialViewRepository repository = GetPartialViewRepository(partialViewType);
-                if (partialViewContent != null)
-                {
-                    partialView.Content = partialViewContent;
-                }
+            IPartialViewRepository repository = GetPartialViewRepository(partialViewType);
+            if (partialViewContent != null)
+            {
+                partialView.Content = partialViewContent;
+            }
 
-                repository.Save(partialView);
+            repository.Save(partialView);
 
-                scope.Notifications.Publish(new PartialViewCreatedNotification(partialView, eventMessages).WithStateFrom(creatingNotification));
+            scope.Notifications.Publish(
+                new PartialViewCreatedNotification(partialView, eventMessages).WithStateFrom(creatingNotification));
 
-                Audit(AuditType.Save, userId!.Value, -1, partialViewType.ToString());
+            Audit(AuditType.Save, userId!.Value, -1, partialViewType.ToString());
 
-                scope.Complete();
-            }
-
-            return Attempt.Succeed(partialView);
+            scope.Complete();
         }
 
-        public bool DeletePartialView(string path, int? userId = null) =>
-            DeletePartialViewMacro(path, PartialViewType.PartialView, userId);
+        return Attempt.Succeed(partialView);
+    }
+
+    public bool DeletePartialView(string path, int? userId = null) =>
+        DeletePartialViewMacro(path, PartialViewType.PartialView, userId);
 
-        public bool DeletePartialViewMacro(string path, int? userId = null) =>
-            DeletePartialViewMacro(path, PartialViewType.PartialViewMacro, userId);
+    public bool DeletePartialViewMacro(string path, int? userId = null) =>
+        DeletePartialViewMacro(path, PartialViewType.PartialViewMacro, userId);
 
-        private bool DeletePartialViewMacro(string path, PartialViewType partialViewType, int? userId = null)
+    private bool DeletePartialViewMacro(string path, PartialViewType partialViewType, int? userId = null)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            IPartialViewRepository repository = GetPartialViewRepository(partialViewType);
+            IPartialView? partialView = repository.Get(path);
+            if (partialView == null)
             {
+                scope.Complete();
+                return true;
+            }
 
-                IPartialViewRepository repository = GetPartialViewRepository(partialViewType);
-                IPartialView? partialView = repository.Get(path);
-                if (partialView == null)
-                {
-                    scope.Complete();
-                    return true;
-                }
-
-                EventMessages eventMessages = EventMessagesFactory.Get();
-                var deletingNotification = new PartialViewDeletingNotification(partialView, eventMessages);
-                if (scope.Notifications.PublishCancelable(deletingNotification))
-                {
-                    scope.Complete();
-                    return false;
-                }
-
-                userId ??= Constants.Security.SuperUserId;
-                repository.Delete(partialView);
-                scope.Notifications.Publish(new PartialViewDeletedNotification(partialView, eventMessages).WithStateFrom(deletingNotification));
-                Audit(AuditType.Delete, userId.Value, -1, partialViewType.ToString());
-
+            EventMessages eventMessages = EventMessagesFactory.Get();
+            var deletingNotification = new PartialViewDeletingNotification(partialView, eventMessages);
+            if (scope.Notifications.PublishCancelable(deletingNotification))
+            {
                 scope.Complete();
+                return false;
             }
 
-            return true;
+            userId ??= Constants.Security.SuperUserId;
+            repository.Delete(partialView);
+            scope.Notifications.Publish(
+                new PartialViewDeletedNotification(partialView, eventMessages).WithStateFrom(deletingNotification));
+            Audit(AuditType.Delete, userId.Value, -1, partialViewType.ToString());
+
+            scope.Complete();
         }
 
-        public Attempt SavePartialView(IPartialView partialView, int? userId = null) =>
-            SavePartialView(partialView, PartialViewType.PartialView, userId);
+        return true;
+    }
+
+    public Attempt SavePartialView(IPartialView partialView, int? userId = null) =>
+        SavePartialView(partialView, PartialViewType.PartialView, userId);
 
-        public Attempt SavePartialViewMacro(IPartialView partialView, int? userId = null) =>
-            SavePartialView(partialView, PartialViewType.PartialViewMacro, userId);
+    public Attempt SavePartialViewMacro(IPartialView partialView, int? userId = null) =>
+        SavePartialView(partialView, PartialViewType.PartialViewMacro, userId);
 
-        private Attempt SavePartialView(IPartialView partialView, PartialViewType partialViewType, int? userId = null)
+    private Attempt SavePartialView(IPartialView partialView, PartialViewType partialViewType,
+        int? userId = null)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            EventMessages eventMessages = EventMessagesFactory.Get();
+            var savingNotification = new PartialViewSavingNotification(partialView, eventMessages);
+            if (scope.Notifications.PublishCancelable(savingNotification))
             {
-                EventMessages eventMessages = EventMessagesFactory.Get();
-                var savingNotification = new PartialViewSavingNotification(partialView, eventMessages);
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    scope.Complete();
-                    return Attempt.Fail();
-                }
-
-                userId ??= Constants.Security.SuperUserId;
-                IPartialViewRepository repository = GetPartialViewRepository(partialViewType);
-                repository.Save(partialView);
-
-                Audit(AuditType.Save, userId.Value, -1, partialViewType.ToString());
-                scope.Notifications.Publish(new PartialViewSavedNotification(partialView, eventMessages).WithStateFrom(savingNotification));
-
                 scope.Complete();
+                return Attempt.Fail();
             }
 
-            return Attempt.Succeed(partialView);
-        }
+            userId ??= Constants.Security.SuperUserId;
+            IPartialViewRepository repository = GetPartialViewRepository(partialViewType);
+            repository.Save(partialView);
 
-        internal string StripPartialViewHeader(string contents)
-        {
-            var headerMatch = new Regex("^@inherits\\s+?.*$", RegexOptions.Multiline);
-            return headerMatch.Replace(contents, string.Empty);
+            Audit(AuditType.Save, userId.Value, -1, partialViewType.ToString());
+            scope.Notifications.Publish(
+                new PartialViewSavedNotification(partialView, eventMessages).WithStateFrom(savingNotification));
+
+            scope.Complete();
         }
 
-        internal Attempt TryGetSnippetPath(string? fileName)
-        {
-            if (fileName?.EndsWith(".cshtml") == false)
-            {
-                fileName += ".cshtml";
-            }
+        return Attempt.Succeed(partialView);
+    }
+
+    internal string StripPartialViewHeader(string contents)
+    {
+        var headerMatch = new Regex("^@inherits\\s+?.*$", RegexOptions.Multiline);
+        return headerMatch.Replace(contents, string.Empty);
+    }
 
-            var snippetPath = _hostingEnvironment.MapPathContentRoot($"{Constants.SystemDirectories.Umbraco}/PartialViewMacros/Templates/{fileName}");
-            return System.IO.File.Exists(snippetPath)
-                ? Attempt.Succeed(snippetPath)
-                : Attempt.Fail();
+    internal Attempt TryGetSnippetPath(string? fileName)
+    {
+        if (fileName?.EndsWith(".cshtml") == false)
+        {
+            fileName += ".cshtml";
         }
 
-        public void CreatePartialViewFolder(string folderPath)
+        var snippetPath =
+            _hostingEnvironment.MapPathContentRoot(
+                $"{Constants.SystemDirectories.Umbraco}/PartialViewMacros/Templates/{fileName}");
+        return File.Exists(snippetPath)
+            ? Attempt.Succeed(snippetPath)
+            : Attempt.Fail();
+    }
+
+    public void CreatePartialViewFolder(string folderPath)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                _partialViewRepository.AddFolder(folderPath);
-                scope.Complete();
-            }
+            _partialViewRepository.AddFolder(folderPath);
+            scope.Complete();
         }
+    }
 
-        public void CreatePartialViewMacroFolder(string folderPath)
+    public void CreatePartialViewMacroFolder(string folderPath)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                _partialViewMacroRepository.AddFolder(folderPath);
-                scope.Complete();
-            }
+            _partialViewMacroRepository.AddFolder(folderPath);
+            scope.Complete();
         }
+    }
 
-        private IPartialViewRepository GetPartialViewRepository(PartialViewType partialViewType)
+    private IPartialViewRepository GetPartialViewRepository(PartialViewType partialViewType)
+    {
+        switch (partialViewType)
         {
-            switch (partialViewType)
-            {
-                case PartialViewType.PartialView:
-                    return _partialViewRepository;
-                case PartialViewType.PartialViewMacro:
-                    return _partialViewMacroRepository;
-                default:
-                    throw new ArgumentOutOfRangeException(nameof(partialViewType));
-            }
+            case PartialViewType.PartialView:
+                return _partialViewRepository;
+            case PartialViewType.PartialViewMacro:
+                return _partialViewMacroRepository;
+            default:
+                throw new ArgumentOutOfRangeException(nameof(partialViewType));
         }
+    }
 
-        /// 
-        public Stream GetPartialViewFileContentStream(string filepath)
+    /// 
+    public Stream GetPartialViewFileContentStream(string filepath)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _partialViewRepository.GetFileContentStream(filepath);
-            }
+            return _partialViewRepository.GetFileContentStream(filepath);
         }
+    }
 
-        /// 
-        public void SetPartialViewFileContent(string filepath, Stream content)
+    /// 
+    public void SetPartialViewFileContent(string filepath, Stream content)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                _partialViewRepository.SetFileContent(filepath, content);
-                scope.Complete();
-            }
+            _partialViewRepository.SetFileContent(filepath, content);
+            scope.Complete();
         }
+    }
 
-        /// 
-        public long GetPartialViewFileSize(string filepath)
+    /// 
+    public long GetPartialViewFileSize(string filepath)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _partialViewRepository.GetFileSize(filepath);
-            }
+            return _partialViewRepository.GetFileSize(filepath);
         }
+    }
 
-        /// 
-        public Stream GetPartialViewMacroFileContentStream(string filepath)
+    /// 
+    public Stream GetPartialViewMacroFileContentStream(string filepath)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _partialViewMacroRepository.GetFileContentStream(filepath);
-            }
+            return _partialViewMacroRepository.GetFileContentStream(filepath);
         }
+    }
 
-        /// 
-        public void SetPartialViewMacroFileContent(string filepath, Stream content)
+    /// 
+    public void SetPartialViewMacroFileContent(string filepath, Stream content)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                _partialViewMacroRepository.SetFileContent(filepath, content);
-                scope.Complete();
-            }
+            _partialViewMacroRepository.SetFileContent(filepath, content);
+            scope.Complete();
         }
+    }
 
-        /// 
-        public long GetPartialViewMacroFileSize(string filepath)
+    /// 
+    public long GetPartialViewMacroFileSize(string filepath)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _partialViewMacroRepository.GetFileSize(filepath);
-            }
+            return _partialViewMacroRepository.GetFileSize(filepath);
         }
+    }
 
-        #endregion
+    #endregion
 
-        #region Snippets
+    #region Snippets
 
-        public string GetPartialViewSnippetContent(string snippetName) => GetPartialViewMacroSnippetContent(snippetName, PartialViewType.PartialView);
+    public string GetPartialViewSnippetContent(string snippetName) =>
+        GetPartialViewMacroSnippetContent(snippetName, PartialViewType.PartialView);
 
-        public string GetPartialViewMacroSnippetContent(string snippetName) => GetPartialViewMacroSnippetContent(snippetName, PartialViewType.PartialViewMacro);
+    public string GetPartialViewMacroSnippetContent(string snippetName) =>
+        GetPartialViewMacroSnippetContent(snippetName, PartialViewType.PartialViewMacro);
 
-        private string GetPartialViewMacroSnippetContent(string snippetName, PartialViewType partialViewType)
+    private string GetPartialViewMacroSnippetContent(string snippetName, PartialViewType partialViewType)
+    {
+        if (snippetName.IsNullOrWhiteSpace())
         {
-            if (snippetName.IsNullOrWhiteSpace())
-            {
-                throw new ArgumentNullException(nameof(snippetName));
-            }
-
-            string partialViewHeader;
-            switch (partialViewType)
-            {
-                case PartialViewType.PartialView:
-                    partialViewHeader = PartialViewHeader;
-                    break;
-                case PartialViewType.PartialViewMacro:
-                    partialViewHeader = PartialViewMacroHeader;
-                    break;
-                default:
-                    throw new ArgumentOutOfRangeException(nameof(partialViewType));
-            }
+            throw new ArgumentNullException(nameof(snippetName));
+        }
 
-            var snippetProvider =
-                new EmbeddedFileProvider(this.GetType().Assembly, "Umbraco.Cms.Core.EmbeddedResources.Snippets");
+        string partialViewHeader;
+        switch (partialViewType)
+        {
+            case PartialViewType.PartialView:
+                partialViewHeader = PartialViewHeader;
+                break;
+            case PartialViewType.PartialViewMacro:
+                partialViewHeader = PartialViewMacroHeader;
+                break;
+            default:
+                throw new ArgumentOutOfRangeException(nameof(partialViewType));
+        }
 
-            var file = snippetProvider.GetDirectoryContents(string.Empty).FirstOrDefault(x=>x.Exists && x.Name.Equals(snippetName + ".cshtml"));
+        var snippetProvider =
+            new EmbeddedFileProvider(GetType().Assembly, "Umbraco.Cms.Core.EmbeddedResources.Snippets");
 
-            // Try and get the snippet path
-            if (file is null)
-            {
-                throw new InvalidOperationException("Could not load snippet with name " + snippetName);
-            }
+        IFileInfo file = snippetProvider.GetDirectoryContents(string.Empty)
+            .FirstOrDefault(x => x.Exists && x.Name.Equals(snippetName + ".cshtml"));
 
-            using (var snippetFile = new StreamReader(file.CreateReadStream()))
-            {
-                var snippetContent = snippetFile.ReadToEnd().Trim();
+        // Try and get the snippet path
+        if (file is null)
+        {
+            throw new InvalidOperationException("Could not load snippet with name " + snippetName);
+        }
 
-                //strip the @inherits if it's there
-                snippetContent = StripPartialViewHeader(snippetContent);
+        using (var snippetFile = new StreamReader(file.CreateReadStream()))
+        {
+            var snippetContent = snippetFile.ReadToEnd().Trim();
 
-                //Update Model.Content to be Model when used as PartialView
-                if (partialViewType == PartialViewType.PartialView)
-                {
-                    snippetContent = snippetContent
-                        .Replace("Model.Content.", "Model.")
-                        .Replace("(Model.Content)", "(Model)");
-                }
+            //strip the @inherits if it's there
+            snippetContent = StripPartialViewHeader(snippetContent);
 
-                var content = $"{partialViewHeader}{Environment.NewLine}{snippetContent}";
-                return content;
+            //Update Model.Content to be Model when used as PartialView
+            if (partialViewType == PartialViewType.PartialView)
+            {
+                snippetContent = snippetContent
+                    .Replace("Model.Content.", "Model.")
+                    .Replace("(Model.Content)", "(Model)");
             }
-        }
 
-        #endregion
+            var content = $"{partialViewHeader}{Environment.NewLine}{snippetContent}";
+            return content;
+        }
+    }
 
-        private void Audit(AuditType type, int userId, int objectId, string? entityType) => _auditRepository.Save(new AuditItem(objectId, type, userId, entityType));
+    #endregion
 
-        // TODO: Method to change name and/or alias of view template
-    }
+    // TODO: Method to change name and/or alias of view template
 }
diff --git a/src/Umbraco.Core/Services/IAuditService.cs b/src/Umbraco.Core/Services/IAuditService.cs
index df816960a3d2..8023124c74ef 100644
--- a/src/Umbraco.Core/Services/IAuditService.cs
+++ b/src/Umbraco.Core/Services/IAuditService.cs
@@ -1,86 +1,86 @@
-using System;
-using System.Collections.Generic;
-using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Persistence.Querying;
 
-namespace Umbraco.Cms.Core.Services
-{
-    /// 
-    /// Represents a service for handling audit.
-    /// 
-    public interface IAuditService : IService
-    {
-        void Add(AuditType type, int userId, int objectId, string? entityType, string comment, string? parameters = null);
+namespace Umbraco.Cms.Core.Services;
 
-        IEnumerable GetLogs(int objectId);
-        IEnumerable GetUserLogs(int userId, AuditType type, DateTime? sinceDate = null);
-        IEnumerable GetLogs(AuditType type, DateTime? sinceDate = null);
-        void CleanLogs(int maximumAgeOfLogsInMinutes);
+/// 
+///     Represents a service for handling audit.
+/// 
+public interface IAuditService : IService
+{
+    void Add(AuditType type, int userId, int objectId, string? entityType, string comment, string? parameters = null);
 
-        /// 
-        /// Returns paged items in the audit trail for a given entity
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// By default this will always be ordered descending (newest first)
-        /// 
-        /// 
-        /// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
-        /// so we need to do that here
-        /// 
-        /// 
-        /// Optional filter to be applied
-        /// 
-        /// 
-        IEnumerable GetPagedItemsByEntity(int entityId, long pageIndex, int pageSize, out long totalRecords,
-            Direction orderDirection = Direction.Descending,
-            AuditType[]? auditTypeFilter = null,
-            IQuery? customFilter = null);
+    IEnumerable GetLogs(int objectId);
+    IEnumerable GetUserLogs(int userId, AuditType type, DateTime? sinceDate = null);
+    IEnumerable GetLogs(AuditType type, DateTime? sinceDate = null);
+    void CleanLogs(int maximumAgeOfLogsInMinutes);
 
-        /// 
-        /// Returns paged items in the audit trail for a given user
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// By default this will always be ordered descending (newest first)
-        /// 
-        /// 
-        /// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
-        /// so we need to do that here
-        /// 
-        /// 
-        /// Optional filter to be applied
-        /// 
-        /// 
-        IEnumerable GetPagedItemsByUser(int userId, long pageIndex, int pageSize, out long totalRecords,
-            Direction orderDirection = Direction.Descending,
-            AuditType[]? auditTypeFilter = null,
-            IQuery? customFilter = null);
+    /// 
+    ///     Returns paged items in the audit trail for a given entity
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    ///     By default this will always be ordered descending (newest first)
+    /// 
+    /// 
+    ///     Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query
+    ///     or the custom filter
+    ///     so we need to do that here
+    /// 
+    /// 
+    ///     Optional filter to be applied
+    /// 
+    /// 
+    IEnumerable GetPagedItemsByEntity(int entityId, long pageIndex, int pageSize, out long totalRecords,
+        Direction orderDirection = Direction.Descending,
+        AuditType[]? auditTypeFilter = null,
+        IQuery? customFilter = null);
 
-        /// 
-        /// Writes an audit entry for an audited event.
-        /// 
-        /// The identifier of the user triggering the audited event.
-        /// Free-form details about the user triggering the audited event.
-        /// The IP address or the request triggering the audited event.
-        /// The date and time of the audited event.
-        /// The identifier of the user affected by the audited event.
-        /// Free-form details about the entity affected by the audited event.
-        /// 
-        /// The type of the audited event - must contain only alphanumeric chars and hyphens with forward slashes separating categories.
-        /// 
-        /// The eventType will generally be formatted like: {application}/{entity-type}/{category}/{sub-category}
-        /// Example: umbraco/user/sign-in/failed
-        /// 
-        /// 
-        /// Free-form details about the audited event.
-        IAuditEntry Write(int performingUserId, string perfomingDetails, string performingIp, DateTime eventDateUtc, int affectedUserId, string affectedDetails, string eventType, string eventDetails);
+    /// 
+    ///     Returns paged items in the audit trail for a given user
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    ///     By default this will always be ordered descending (newest first)
+    /// 
+    /// 
+    ///     Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query
+    ///     or the custom filter
+    ///     so we need to do that here
+    /// 
+    /// 
+    ///     Optional filter to be applied
+    /// 
+    /// 
+    IEnumerable GetPagedItemsByUser(int userId, long pageIndex, int pageSize, out long totalRecords,
+        Direction orderDirection = Direction.Descending,
+        AuditType[]? auditTypeFilter = null,
+        IQuery? customFilter = null);
 
-    }
+    /// 
+    ///     Writes an audit entry for an audited event.
+    /// 
+    /// The identifier of the user triggering the audited event.
+    /// Free-form details about the user triggering the audited event.
+    /// The IP address or the request triggering the audited event.
+    /// The date and time of the audited event.
+    /// The identifier of the user affected by the audited event.
+    /// Free-form details about the entity affected by the audited event.
+    /// 
+    ///     The type of the audited event - must contain only alphanumeric chars and hyphens with forward slashes separating
+    ///     categories.
+    ///     
+    ///         The eventType will generally be formatted like: {application}/{entity-type}/{category}/{sub-category}
+    ///         Example: umbraco/user/sign-in/failed
+    ///     
+    /// 
+    /// Free-form details about the audited event.
+    IAuditEntry Write(int performingUserId, string perfomingDetails, string performingIp, DateTime eventDateUtc,
+        int affectedUserId, string affectedDetails, string eventType, string eventDetails);
 }
diff --git a/src/Umbraco.Core/Services/IBasicAuthService.cs b/src/Umbraco.Core/Services/IBasicAuthService.cs
index 84173a629aeb..21417a35863c 100644
--- a/src/Umbraco.Core/Services/IBasicAuthService.cs
+++ b/src/Umbraco.Core/Services/IBasicAuthService.cs
@@ -1,10 +1,9 @@
 using System.Net;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface IBasicAuthService
 {
-    public interface IBasicAuthService
-    {
-        bool IsBasicAuthEnabled();
-        bool IsIpAllowListed(IPAddress clientIpAddress);
-    }
+    bool IsBasicAuthEnabled();
+    bool IsIpAllowListed(IPAddress clientIpAddress);
 }
diff --git a/src/Umbraco.Core/Services/ICacheInstructionService.cs b/src/Umbraco.Core/Services/ICacheInstructionService.cs
index c884b8bed87d..0b71bde66d32 100644
--- a/src/Umbraco.Core/Services/ICacheInstructionService.cs
+++ b/src/Umbraco.Core/Services/ICacheInstructionService.cs
@@ -1,53 +1,50 @@
-using System;
-using System.Collections.Generic;
-using System.Threading;
 using Umbraco.Cms.Core.Cache;
 using Umbraco.Cms.Core.Sync;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface ICacheInstructionService
 {
-    public interface ICacheInstructionService
-    {
-        /// 
-        /// Checks to see if a cold boot is required, either because instructions exist and none have been synced or
-        /// because the last recorded synced instruction can't be found in the database.
-        /// 
-        bool IsColdBootRequired(int lastId);
+    /// 
+    ///     Checks to see if a cold boot is required, either because instructions exist and none have been synced or
+    ///     because the last recorded synced instruction can't be found in the database.
+    /// 
+    bool IsColdBootRequired(int lastId);
 
-        /// 
-        /// Checks to see if the number of pending instructions are over the configured limit.
-        /// 
-        bool IsInstructionCountOverLimit(int lastId, int limit, out int count);
+    /// 
+    ///     Checks to see if the number of pending instructions are over the configured limit.
+    /// 
+    bool IsInstructionCountOverLimit(int lastId, int limit, out int count);
 
-        /// 
-        /// Gets the most recent cache instruction record Id.
-        /// 
-        /// 
-        int GetMaxInstructionId();
+    /// 
+    ///     Gets the most recent cache instruction record Id.
+    /// 
+    /// 
+    int GetMaxInstructionId();
 
-        /// 
-        /// Creates a cache instruction record from a set of individual instructions and saves it.
-        /// 
-        void DeliverInstructions(IEnumerable instructions, string localIdentity);
+    /// 
+    ///     Creates a cache instruction record from a set of individual instructions and saves it.
+    /// 
+    void DeliverInstructions(IEnumerable instructions, string localIdentity);
 
-        /// 
-        /// Creates one or more cache instruction records based on the configured batch size from a set of individual instructions and saves them.
-        /// 
-        void DeliverInstructionsInBatches(IEnumerable instructions, string localIdentity);
+    /// 
+    ///     Creates one or more cache instruction records based on the configured batch size from a set of individual
+    ///     instructions and saves them.
+    /// 
+    void DeliverInstructionsInBatches(IEnumerable instructions, string localIdentity);
 
-        /// 
-        /// Processes and then prunes pending database cache instructions.
-        /// 
-        /// Flag indicating if process is shutting now and operations should exit.
-        /// Local identity of the executing AppDomain.
-        /// Date of last prune operation.
-        /// Id of the latest processed instruction
-        ProcessInstructionsResult ProcessInstructions(
-            CacheRefresherCollection cacheRefreshers,
-            ServerRole serverRole,
-            CancellationToken cancellationToken,
-            string localIdentity,
-            DateTime lastPruned,
-            int lastId);
-    }
+    /// 
+    ///     Processes and then prunes pending database cache instructions.
+    /// 
+    /// Flag indicating if process is shutting now and operations should exit.
+    /// Local identity of the executing AppDomain.
+    /// Date of last prune operation.
+    /// Id of the latest processed instruction
+    ProcessInstructionsResult ProcessInstructions(
+        CacheRefresherCollection cacheRefreshers,
+        ServerRole serverRole,
+        CancellationToken cancellationToken,
+        string localIdentity,
+        DateTime lastPruned,
+        int lastId);
 }
diff --git a/src/Umbraco.Core/Services/IConflictingRouteService.cs b/src/Umbraco.Core/Services/IConflictingRouteService.cs
index 04d81d7f8857..fe044362b764 100644
--- a/src/Umbraco.Core/Services/IConflictingRouteService.cs
+++ b/src/Umbraco.Core/Services/IConflictingRouteService.cs
@@ -1,7 +1,6 @@
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface IConflictingRouteService
 {
-    public interface IConflictingRouteService
-    {
-        public bool HasConflictingRoutes(out string controllerName);
-    }
+    public bool HasConflictingRoutes(out string controllerName);
 }
diff --git a/src/Umbraco.Core/Services/IConsentService.cs b/src/Umbraco.Core/Services/IConsentService.cs
index d191caebe298..e63cbbcc6b84 100644
--- a/src/Umbraco.Core/Services/IConsentService.cs
+++ b/src/Umbraco.Core/Services/IConsentService.cs
@@ -1,45 +1,47 @@
-using System.Collections.Generic;
-using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     A service for handling lawful data processing requirements
+/// 
+/// 
+///     
+///         Consent can be given or revoked or changed via the  method, which
+///         creates a new  entity to track the consent. Revoking a consent is performed by
+///         registering a revoked consent.
+///     
+///     A consent can be revoked, by registering a revoked consent, but cannot be deleted.
+///     
+///         Getter methods return the current state of a consent, i.e. the latest 
+///         entity that was created.
+///     
+/// 
+public interface IConsentService : IService
 {
     /// 
-    /// A service for handling lawful data processing requirements
+    ///     Registers consent.
     /// 
-    /// 
-    /// Consent can be given or revoked or changed via the  method, which
-    /// creates a new  entity to track the consent. Revoking a consent is performed by
-    /// registering a revoked consent.
-    /// A consent can be revoked, by registering a revoked consent, but cannot be deleted.
-    /// Getter methods return the current state of a consent, i.e. the latest 
-    /// entity that was created.
-    /// 
-    public interface IConsentService : IService
-    {
-        /// 
-        /// Registers consent.
-        /// 
-        /// The source, i.e. whoever is consenting.
-        /// 
-        /// 
-        /// The state of the consent.
-        /// Additional free text.
-        /// The corresponding consent entity.
-        IConsent RegisterConsent(string source, string context, string action, ConsentState state, string? comment = null);
+    /// The source, i.e. whoever is consenting.
+    /// 
+    /// 
+    /// The state of the consent.
+    /// Additional free text.
+    /// The corresponding consent entity.
+    IConsent RegisterConsent(string source, string context, string action, ConsentState state, string? comment = null);
 
-        /// 
-        /// Retrieves consents.
-        /// 
-        /// The optional source.
-        /// The optional context.
-        /// The optional action.
-        /// Determines whether  is a start pattern.
-        /// Determines whether  is a start pattern.
-        /// Determines whether  is a start pattern.
-        /// Determines whether to include the history of consents.
-        /// Consents matching the parameters.
-        IEnumerable LookupConsent(string? source = null, string? context = null, string? action = null,
-            bool sourceStartsWith = false, bool contextStartsWith = false, bool actionStartsWith = false,
-            bool includeHistory = false);
-    }
+    /// 
+    ///     Retrieves consents.
+    /// 
+    /// The optional source.
+    /// The optional context.
+    /// The optional action.
+    /// Determines whether  is a start pattern.
+    /// Determines whether  is a start pattern.
+    /// Determines whether  is a start pattern.
+    /// Determines whether to include the history of consents.
+    /// Consents matching the parameters.
+    IEnumerable LookupConsent(string? source = null, string? context = null, string? action = null,
+        bool sourceStartsWith = false, bool contextStartsWith = false, bool actionStartsWith = false,
+        bool includeHistory = false);
 }
diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs
index 93d51da7573a..ee9169324b6b 100644
--- a/src/Umbraco.Core/Services/IContentService.cs
+++ b/src/Umbraco.Core/Services/IContentService.cs
@@ -1,544 +1,568 @@
-using System;
-using System.Collections.Generic;
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Models.Membership;
 using Umbraco.Cms.Core.Persistence.Querying;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Defines the ContentService, which is an easy access to operations involving 
+/// 
+public interface IContentService : IContentServiceBase
 {
+    #region Rollback
+
+    /// 
+    ///     Rolls back the content to a specific version.
+    /// 
+    /// The id of the content node.
+    /// The version id to roll back to.
+    /// An optional culture to roll back.
+    /// The identifier of the user who is performing the roll back.
+    /// 
+    ///     When no culture is specified, all cultures are rolled back.
+    /// 
+    OperationResult Rollback(int id, int versionId, string culture = "*", int userId = Constants.Security.SuperUserId);
+
+    #endregion
+
+    #region Blueprints
+
+    /// 
+    ///     Gets a blueprint.
+    /// 
+    IContent? GetBlueprintById(int id);
+
+    /// 
+    ///     Gets a blueprint.
+    /// 
+    IContent? GetBlueprintById(Guid id);
+
+    /// 
+    ///     Gets blueprints for a content type.
+    /// 
+    IEnumerable GetBlueprintsForContentTypes(params int[] documentTypeId);
+
+    /// 
+    ///     Saves a blueprint.
+    /// 
+    void SaveBlueprint(IContent content, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Deletes a blueprint.
+    /// 
+    void DeleteBlueprint(IContent content, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Creates a new content item from a blueprint.
+    /// 
+    IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Deletes blueprints for a content type.
+    /// 
+    void DeleteBlueprintsOfType(int contentTypeId, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Deletes blueprints for content types.
+    /// 
+    void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = Constants.Security.SuperUserId);
+
+    #endregion
+
+    #region Get, Count Documents
+
+    /// 
+    ///     Gets a document.
+    /// 
+    IContent? GetById(int id);
+
+    /// 
+    ///     Gets a document.
+    /// 
+    IContent? GetById(Guid key);
+
+    /// 
+    ///     Gets publish/unpublish schedule for a content node.
+    /// 
+    /// Id of the Content to load schedule for
+    /// 
+    ///     
+    /// 
+    ContentScheduleCollection GetContentScheduleByContentId(int contentId);
+
+    /// 
+    ///     Persists publish/unpublish schedule for a content node.
+    /// 
+    /// 
+    /// 
+    void PersistContentSchedule(IContent content, ContentScheduleCollection contentSchedule);
+
+    /// 
+    ///     Gets documents.
+    /// 
+    IEnumerable GetByIds(IEnumerable ids);
+
+    /// 
+    ///     Gets documents.
+    /// 
+    IEnumerable GetByIds(IEnumerable ids);
+
+    /// 
+    ///     Gets documents at a given level.
+    /// 
+    IEnumerable GetByLevel(int level);
+
+    /// 
+    ///     Gets the parent of a document.
+    /// 
+    IContent? GetParent(int id);
+
     /// 
-    /// Defines the ContentService, which is an easy access to operations involving 
-    /// 
-    public interface IContentService : IContentServiceBase
-    {
-        #region Blueprints
-
-        /// 
-        /// Gets a blueprint.
-        /// 
-        IContent? GetBlueprintById(int id);
-
-        /// 
-        /// Gets a blueprint.
-        /// 
-        IContent? GetBlueprintById(Guid id);
-
-        /// 
-        /// Gets blueprints for a content type.
-        /// 
-        IEnumerable GetBlueprintsForContentTypes(params int[] documentTypeId);
-
-        /// 
-        /// Saves a blueprint.
-        /// 
-        void SaveBlueprint(IContent content, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Deletes a blueprint.
-        /// 
-        void DeleteBlueprint(IContent content, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Creates a new content item from a blueprint.
-        /// 
-        IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Deletes blueprints for a content type.
-        /// 
-        void DeleteBlueprintsOfType(int contentTypeId, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Deletes blueprints for content types.
-        /// 
-        void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = Constants.Security.SuperUserId);
-
-        #endregion
-
-        #region Get, Count Documents
-
-        /// 
-        /// Gets a document.
-        /// 
-        IContent? GetById(int id);
-
-        /// 
-        /// Gets a document.
-        /// 
-        IContent? GetById(Guid key);
-
-        /// 
-        /// Gets publish/unpublish schedule for a content node.
-        /// 
-        /// Id of the Content to load schedule for
-        /// 
-        ContentScheduleCollection GetContentScheduleByContentId(int contentId);
-
-        /// 
-        /// Persists publish/unpublish schedule for a content node.
-        /// 
-        /// 
-        /// 
-        void PersistContentSchedule(IContent content, ContentScheduleCollection contentSchedule);
-
-        /// 
-        /// Gets documents.
-        /// 
-        IEnumerable GetByIds(IEnumerable ids);
-
-        /// 
-        /// Gets documents.
-        /// 
-        IEnumerable GetByIds(IEnumerable ids);
-
-        /// 
-        /// Gets documents at a given level.
-        /// 
-        IEnumerable GetByLevel(int level);
-
-        /// 
-        /// Gets the parent of a document.
-        /// 
-        IContent? GetParent(int id);
-
-        /// 
-        /// Gets the parent of a document.
-        /// 
-        IContent? GetParent(IContent content);
-
-        /// 
-        /// Gets ancestor documents of a document.
-        /// 
-        IEnumerable GetAncestors(int id);
-
-        /// 
-        /// Gets ancestor documents of a document.
-        /// 
-        IEnumerable GetAncestors(IContent content);
-
-        /// 
-        /// Gets all versions of a document.
-        /// 
-        /// Versions are ordered with current first, then most recent first.
-        IEnumerable GetVersions(int id);
-
-        /// 
-        /// Gets all versions of a document.
-        /// 
-        /// Versions are ordered with current first, then most recent first.
-        IEnumerable GetVersionsSlim(int id, int skip, int take);
-
-        /// 
-        /// Gets top versions of a document.
-        /// 
-        /// Versions are ordered with current first, then most recent first.
-        IEnumerable GetVersionIds(int id, int topRows);
-
-        /// 
-        /// Gets a version of a document.
-        /// 
-        IContent? GetVersion(int versionId);
-
-        /// 
-        /// Gets root-level documents.
-        /// 
-        IEnumerable GetRootContent();
-
-        /// 
-        /// Gets documents having an expiration date before (lower than, or equal to) a specified date.
-        /// 
-        /// An Enumerable list of  objects
-        /// 
-        /// The content returned from this method may be culture variant, in which case the resulting  should be queried
-        /// for which culture(s) have been scheduled.
-        /// 
-        IEnumerable GetContentForExpiration(DateTime date);
-
-        /// 
-        /// Gets documents having a release date before (lower than, or equal to) a specified date.
-        /// 
-        /// An Enumerable list of  objects
-        /// 
-        /// The content returned from this method may be culture variant, in which case the resulting  should be queried
-        /// for which culture(s) have been scheduled.
-        /// 
-        IEnumerable GetContentForRelease(DateTime date);
-
-        /// 
-        /// Gets documents in the recycle bin.
-        /// 
-        IEnumerable GetPagedContentInRecycleBin(long pageIndex, int pageSize, out long totalRecords,
-            IQuery? filter = null, Ordering? ordering = null);
-
-        /// 
-        /// Gets child documents of a parent.
-        /// 
-        /// The parent identifier.
-        /// The page number.
-        /// The page size.
-        /// Total number of documents.
-        /// Query filter.
-        /// Ordering infos.
-        IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords,
-            IQuery? filter = null, Ordering? ordering = null);
-
-        /// 
-        /// Gets descendant documents of a given parent.
-        /// 
-        /// The parent identifier.
-        /// The page number.
-        /// The page size.
-        /// Total number of documents.
-        /// Query filter.
-        /// Ordering infos.
-        IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords,
-            IQuery? filter = null, Ordering? ordering = null);
-
-        /// 
-        /// Gets paged documents of a content
-        /// 
-        /// The page number.
-        /// The page number.
-        /// The page size.
-        /// Total number of documents.
-        /// Search text filter.
-        /// Ordering infos.
-        IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords,
-            IQuery filter, Ordering? ordering = null);
-
-        /// 
-        /// Gets paged documents for specified content types
-        /// 
-        /// The page number.
-        /// The page number.
-        /// The page size.
-        /// Total number of documents.
-        /// Search text filter.
-        /// Ordering infos.
-        IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize, out long totalRecords,
-            IQuery? filter, Ordering? ordering = null);
-
-        /// 
-        /// Counts documents of a given document type.
-        /// 
-        int Count(string? documentTypeAlias = null);
-
-        /// 
-        /// Counts published documents of a given document type.
-        /// 
-        int CountPublished(string? documentTypeAlias = null);
-
-        /// 
-        /// Counts child documents of a given parent, of a given document type.
-        /// 
-        int CountChildren(int parentId, string? documentTypeAlias = null);
-
-        /// 
-        /// Counts descendant documents of a given parent, of a given document type.
-        /// 
-        int CountDescendants(int parentId, string? documentTypeAlias = null);
-
-        /// 
-        /// Gets a value indicating whether a document has children.
-        /// 
-        bool HasChildren(int id);
-
-        #endregion
-
-        #region Save, Delete Document
-
-        /// 
-        /// Saves a document.
-        /// 
-        OperationResult Save(IContent content, int? userId = null, ContentScheduleCollection? contentSchedule = null);
-
-        /// 
-        /// Saves documents.
-        /// 
-        // TODO: why only 1 result not 1 per content?!
-        OperationResult Save(IEnumerable contents, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Deletes a document.
-        /// 
-        /// 
-        /// This method will also delete associated media files, child content and possibly associated domains.
-        /// This method entirely clears the content from the database.
-        /// 
-        OperationResult Delete(IContent content, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Deletes all documents of a given document type.
-        /// 
-        /// 
-        /// All non-deleted descendants of the deleted documents are moved to the recycle bin.
-        /// This operation is potentially dangerous and expensive.
-        /// 
-        void DeleteOfType(int documentTypeId, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Deletes all documents of given document types.
-        /// 
-        /// 
-        /// All non-deleted descendants of the deleted documents are moved to the recycle bin.
-        /// This operation is potentially dangerous and expensive.
-        /// 
-        void DeleteOfTypes(IEnumerable contentTypeIds, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Deletes versions of a document prior to a given date.
-        /// 
-        void DeleteVersions(int id, DateTime date, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Deletes a version of a document.
-        /// 
-        void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = Constants.Security.SuperUserId);
-
-        #endregion
-
-        #region Move, Copy, Sort Document
-
-        /// 
-        /// Moves a document under a new parent.
-        /// 
-        void Move(IContent content, int parentId, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Copies a document.
-        /// 
-        /// 
-        /// Recursively copies all children.
-        /// 
-        IContent? Copy(IContent content, int parentId, bool relateToOriginal, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Copies a document.
-        /// 
-        /// 
-        /// Optionally recursively copies all children.
-        /// 
-        IContent? Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Moves a document to the recycle bin.
-        /// 
-        OperationResult MoveToRecycleBin(IContent content, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Empties the Recycle Bin by deleting all  that resides in the bin
-        /// 
-        /// Optional Id of the User emptying the Recycle Bin
-        OperationResult EmptyRecycleBin(int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Returns true if there is any content in the recycle bin
-        /// 
-        bool RecycleBinSmells();
-
-        /// 
-        /// Sorts documents.
-        /// 
-        OperationResult Sort(IEnumerable items, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Sorts documents.
-        /// 
-        OperationResult Sort(IEnumerable? ids, int userId = Constants.Security.SuperUserId);
-
-        #endregion
-
-        #region Publish Document
-
-        /// 
-        /// Saves and publishes a document.
-        /// 
-        /// 
-        /// By default, publishes all variations of the document, but it is possible to specify a culture to be published.
-        /// When a culture is being published, it includes all varying values along with all invariant values.
-        /// The document is *always* saved, even when publishing fails.
-        /// If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor
-        /// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty.
-        /// 
-        /// The document to publish.
-        /// The culture to publish.
-        /// The identifier of the user performing the action.
-        PublishResult SaveAndPublish(IContent content, string culture = "*", int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Saves and publishes a document.
-        /// 
-        /// 
-        /// By default, publishes all variations of the document, but it is possible to specify a culture to be published.
-        /// When a culture is being published, it includes all varying values along with all invariant values.
-        /// The document is *always* saved, even when publishing fails.
-        /// 
-        /// The document to publish.
-        /// The cultures to publish.
-        /// The identifier of the user performing the action.
-        PublishResult SaveAndPublish(IContent content, string[] cultures, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Saves and publishes a document branch.
-        /// 
-        /// The root document.
-        /// A value indicating whether to force-publish documents that are not already published.
-        /// A culture, or "*" for all cultures.
-        /// The identifier of the user performing the operation.
-        /// 
-        /// Unless specified, all cultures are re-published. Otherwise, one culture can be specified. To act on more
-        /// than one culture, see the other overloads of this method.
-        /// The  parameter determines which documents are published. When false,
-        /// only those documents that are already published, are republished. When true, all documents are
-        /// published. The root of the branch is always published, regardless of .
-        /// 
-        IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = "*", int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Saves and publishes a document branch.
-        /// 
-        /// The root document.
-        /// A value indicating whether to force-publish documents that are not already published.
-        /// The cultures to publish.
-        /// The identifier of the user performing the operation.
-        /// 
-        /// The  parameter determines which documents are published. When false,
-        /// only those documents that are already published, are republished. When true, all documents are
-        /// published. The root of the branch is always published, regardless of .
-        /// 
-        IEnumerable SaveAndPublishBranch(IContent content, bool force, string[] cultures, int userId = Constants.Security.SuperUserId);
-
-        ///// 
-        ///// Saves and publishes a document branch.
-        ///// 
-        ///// The root document.
-        ///// A value indicating whether to force-publish documents that are not already published.
-        ///// A function determining cultures to publish.
-        ///// A function publishing cultures.
-        ///// The identifier of the user performing the operation.
-        ///// 
-        ///// The  parameter determines which documents are published. When false,
-        ///// only those documents that are already published, are republished. When true, all documents are
-        ///// published. The root of the branch is always published, regardless of .
-        ///// The  parameter is a function which determines whether a document has
-        ///// changes to publish (else there is no need to publish it). If one wants to publish only a selection of
-        ///// cultures, one may want to check that only properties for these cultures have changed. Otherwise, other
-        ///// cultures may trigger an unwanted republish.
-        ///// The  parameter is a function to execute to publish cultures, on
-        ///// each document. It can publish all, one, or a selection of cultures. It returns a boolean indicating
-        ///// whether the cultures could be published.
-        ///// 
-        //IEnumerable SaveAndPublishBranch(IContent content, bool force,
-        //    Func> shouldPublish,
-        //    Func, bool> publishCultures,
-        //    int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Unpublishes a document.
-        /// 
-        /// 
-        /// By default, unpublishes the document as a whole, but it is possible to specify a culture to be
-        /// unpublished. Depending on whether that culture is mandatory, and other cultures remain published,
-        /// the document as a whole may or may not remain published.
-        /// If the content type is variant, then culture can be either '*' or an actual culture, but neither null nor
-        /// empty. If the content type is invariant, then culture can be either '*' or null or empty.
-        /// 
-        PublishResult Unpublish(IContent content, string culture = "*", int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Gets a value indicating whether a document is path-publishable.
-        /// 
-        /// A document is path-publishable when all its ancestors are published.
-        bool IsPathPublishable(IContent content);
-
-        /// 
-        /// Gets a value indicating whether a document is path-published.
-        /// 
-        /// A document is path-published when all its ancestors, and the document itself, are published.
-        bool IsPathPublished(IContent content);
-
-        /// 
-        /// Saves a document and raises the "sent to publication" events.
-        /// 
-        bool SendToPublication(IContent? content, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Publishes and unpublishes scheduled documents.
-        /// 
-        IEnumerable PerformScheduledPublish(DateTime date);
-
-        #endregion
-
-        #region Permissions
-
-        /// 
-        /// Gets permissions assigned to a document.
-        /// 
-        EntityPermissionCollection GetPermissions(IContent content);
-
-        /// 
-        /// Sets the permission of a document.
-        /// 
-        /// Replaces all permissions with the new set of permissions.
-        void SetPermissions(EntityPermissionSet permissionSet);
-
-        /// 
-        /// Assigns a permission to a document.
-        /// 
-        /// Adds the permission to existing permissions.
-        void SetPermission(IContent entity, char permission, IEnumerable groupIds);
-
-        #endregion
-
-        #region Create
-
-        /// 
-        /// Creates a document.
-        /// 
-        IContent Create(string name, Guid parentId, string documentTypeAlias, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Creates a document.
-        /// 
-        IContent Create(string name, int parentId, string documentTypeAlias, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Creates a document
-        /// 
-        IContent Create(string name, int parentId, IContentType contentType, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Creates a document.
-        /// 
-        IContent Create(string name, IContent? parent, string documentTypeAlias, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Creates and saves a document.
-        /// 
-        IContent CreateAndSave(string name, int parentId, string contentTypeAlias, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Creates and saves a document.
-        /// 
-        IContent CreateAndSave(string name, IContent parent, string contentTypeAlias, int userId = Constants.Security.SuperUserId);
-
-        #endregion
-
-        #region Rollback
-
-        /// 
-        /// Rolls back the content to a specific version.
-        /// 
-        /// The id of the content node.
-        /// The version id to roll back to.
-        /// An optional culture to roll back.
-        /// The identifier of the user who is performing the roll back.
-        /// 
-        /// When no culture is specified, all cultures are rolled back.
-        /// 
-        OperationResult Rollback(int id, int versionId, string culture = "*", int userId = Constants.Security.SuperUserId);
-
-        #endregion
-
-    }
+    ///     Gets the parent of a document.
+    /// 
+    IContent? GetParent(IContent content);
+
+    /// 
+    ///     Gets ancestor documents of a document.
+    /// 
+    IEnumerable GetAncestors(int id);
+
+    /// 
+    ///     Gets ancestor documents of a document.
+    /// 
+    IEnumerable GetAncestors(IContent content);
+
+    /// 
+    ///     Gets all versions of a document.
+    /// 
+    /// Versions are ordered with current first, then most recent first.
+    IEnumerable GetVersions(int id);
+
+    /// 
+    ///     Gets all versions of a document.
+    /// 
+    /// Versions are ordered with current first, then most recent first.
+    IEnumerable GetVersionsSlim(int id, int skip, int take);
+
+    /// 
+    ///     Gets top versions of a document.
+    /// 
+    /// Versions are ordered with current first, then most recent first.
+    IEnumerable GetVersionIds(int id, int topRows);
+
+    /// 
+    ///     Gets a version of a document.
+    /// 
+    IContent? GetVersion(int versionId);
+
+    /// 
+    ///     Gets root-level documents.
+    /// 
+    IEnumerable GetRootContent();
+
+    /// 
+    ///     Gets documents having an expiration date before (lower than, or equal to) a specified date.
+    /// 
+    /// An Enumerable list of  objects
+    /// 
+    ///     The content returned from this method may be culture variant, in which case the resulting
+    ///      should be queried
+    ///     for which culture(s) have been scheduled.
+    /// 
+    IEnumerable GetContentForExpiration(DateTime date);
+
+    /// 
+    ///     Gets documents having a release date before (lower than, or equal to) a specified date.
+    /// 
+    /// An Enumerable list of  objects
+    /// 
+    ///     The content returned from this method may be culture variant, in which case the resulting
+    ///      should be queried
+    ///     for which culture(s) have been scheduled.
+    /// 
+    IEnumerable GetContentForRelease(DateTime date);
+
+    /// 
+    ///     Gets documents in the recycle bin.
+    /// 
+    IEnumerable GetPagedContentInRecycleBin(long pageIndex, int pageSize, out long totalRecords,
+        IQuery? filter = null, Ordering? ordering = null);
+
+    /// 
+    ///     Gets child documents of a parent.
+    /// 
+    /// The parent identifier.
+    /// The page number.
+    /// The page size.
+    /// Total number of documents.
+    /// Query filter.
+    /// Ordering infos.
+    IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords,
+        IQuery? filter = null, Ordering? ordering = null);
+
+    /// 
+    ///     Gets descendant documents of a given parent.
+    /// 
+    /// The parent identifier.
+    /// The page number.
+    /// The page size.
+    /// Total number of documents.
+    /// Query filter.
+    /// Ordering infos.
+    IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords,
+        IQuery? filter = null, Ordering? ordering = null);
+
+    /// 
+    ///     Gets paged documents of a content
+    /// 
+    /// The page number.
+    /// The page number.
+    /// The page size.
+    /// Total number of documents.
+    /// Search text filter.
+    /// Ordering infos.
+    IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords,
+        IQuery filter, Ordering? ordering = null);
+
+    /// 
+    ///     Gets paged documents for specified content types
+    /// 
+    /// The page number.
+    /// The page number.
+    /// The page size.
+    /// Total number of documents.
+    /// Search text filter.
+    /// Ordering infos.
+    IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize, out long totalRecords,
+        IQuery? filter, Ordering? ordering = null);
+
+    /// 
+    ///     Counts documents of a given document type.
+    /// 
+    int Count(string? documentTypeAlias = null);
+
+    /// 
+    ///     Counts published documents of a given document type.
+    /// 
+    int CountPublished(string? documentTypeAlias = null);
+
+    /// 
+    ///     Counts child documents of a given parent, of a given document type.
+    /// 
+    int CountChildren(int parentId, string? documentTypeAlias = null);
+
+    /// 
+    ///     Counts descendant documents of a given parent, of a given document type.
+    /// 
+    int CountDescendants(int parentId, string? documentTypeAlias = null);
+
+    /// 
+    ///     Gets a value indicating whether a document has children.
+    /// 
+    bool HasChildren(int id);
+
+    #endregion
+
+    #region Save, Delete Document
+
+    /// 
+    ///     Saves a document.
+    /// 
+    OperationResult Save(IContent content, int? userId = null, ContentScheduleCollection? contentSchedule = null);
+
+    /// 
+    ///     Saves documents.
+    /// 
+    // TODO: why only 1 result not 1 per content?!
+    OperationResult Save(IEnumerable contents, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Deletes a document.
+    /// 
+    /// 
+    ///     This method will also delete associated media files, child content and possibly associated domains.
+    ///     This method entirely clears the content from the database.
+    /// 
+    OperationResult Delete(IContent content, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Deletes all documents of a given document type.
+    /// 
+    /// 
+    ///     All non-deleted descendants of the deleted documents are moved to the recycle bin.
+    ///     This operation is potentially dangerous and expensive.
+    /// 
+    void DeleteOfType(int documentTypeId, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Deletes all documents of given document types.
+    /// 
+    /// 
+    ///     All non-deleted descendants of the deleted documents are moved to the recycle bin.
+    ///     This operation is potentially dangerous and expensive.
+    /// 
+    void DeleteOfTypes(IEnumerable contentTypeIds, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Deletes versions of a document prior to a given date.
+    /// 
+    void DeleteVersions(int id, DateTime date, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Deletes a version of a document.
+    /// 
+    void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = Constants.Security.SuperUserId);
+
+    #endregion
+
+    #region Move, Copy, Sort Document
+
+    /// 
+    ///     Moves a document under a new parent.
+    /// 
+    void Move(IContent content, int parentId, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Copies a document.
+    /// 
+    /// 
+    ///     Recursively copies all children.
+    /// 
+    IContent? Copy(IContent content, int parentId, bool relateToOriginal, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Copies a document.
+    /// 
+    /// 
+    ///     Optionally recursively copies all children.
+    /// 
+    IContent? Copy(IContent content, int parentId, bool relateToOriginal, bool recursive,
+        int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Moves a document to the recycle bin.
+    /// 
+    OperationResult MoveToRecycleBin(IContent content, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Empties the Recycle Bin by deleting all  that resides in the bin
+    /// 
+    /// Optional Id of the User emptying the Recycle Bin
+    OperationResult EmptyRecycleBin(int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Returns true if there is any content in the recycle bin
+    /// 
+    bool RecycleBinSmells();
+
+    /// 
+    ///     Sorts documents.
+    /// 
+    OperationResult Sort(IEnumerable items, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Sorts documents.
+    /// 
+    OperationResult Sort(IEnumerable? ids, int userId = Constants.Security.SuperUserId);
+
+    #endregion
+
+    #region Publish Document
+
+    /// 
+    ///     Saves and publishes a document.
+    /// 
+    /// 
+    ///     
+    ///         By default, publishes all variations of the document, but it is possible to specify a culture to be
+    ///         published.
+    ///     
+    ///     When a culture is being published, it includes all varying values along with all invariant values.
+    ///     The document is *always* saved, even when publishing fails.
+    ///     
+    ///         If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor
+    ///         'empty'. If the content type is invariant, then culture can be either '*' or null or empty.
+    ///     
+    /// 
+    /// The document to publish.
+    /// The culture to publish.
+    /// The identifier of the user performing the action.
+    PublishResult SaveAndPublish(IContent content, string culture = "*", int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Saves and publishes a document.
+    /// 
+    /// 
+    ///     
+    ///         By default, publishes all variations of the document, but it is possible to specify a culture to be
+    ///         published.
+    ///     
+    ///     When a culture is being published, it includes all varying values along with all invariant values.
+    ///     The document is *always* saved, even when publishing fails.
+    /// 
+    /// The document to publish.
+    /// The cultures to publish.
+    /// The identifier of the user performing the action.
+    PublishResult SaveAndPublish(IContent content, string[] cultures, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Saves and publishes a document branch.
+    /// 
+    /// The root document.
+    /// A value indicating whether to force-publish documents that are not already published.
+    /// A culture, or "*" for all cultures.
+    /// The identifier of the user performing the operation.
+    /// 
+    ///     
+    ///         Unless specified, all cultures are re-published. Otherwise, one culture can be specified. To act on more
+    ///         than one culture, see the other overloads of this method.
+    ///     
+    ///     
+    ///         The  parameter determines which documents are published. When false,
+    ///         only those documents that are already published, are republished. When true, all documents are
+    ///         published. The root of the branch is always published, regardless of .
+    ///     
+    /// 
+    IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = "*",
+        int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Saves and publishes a document branch.
+    /// 
+    /// The root document.
+    /// A value indicating whether to force-publish documents that are not already published.
+    /// The cultures to publish.
+    /// The identifier of the user performing the operation.
+    /// 
+    ///     
+    ///         The  parameter determines which documents are published. When false,
+    ///         only those documents that are already published, are republished. When true, all documents are
+    ///         published. The root of the branch is always published, regardless of .
+    ///     
+    /// 
+    IEnumerable SaveAndPublishBranch(IContent content, bool force, string[] cultures,
+        int userId = Constants.Security.SuperUserId);
+
+    ///// 
+    ///// Saves and publishes a document branch.
+    ///// 
+    ///// The root document.
+    ///// A value indicating whether to force-publish documents that are not already published.
+    ///// A function determining cultures to publish.
+    ///// A function publishing cultures.
+    ///// The identifier of the user performing the operation.
+    ///// 
+    ///// The  parameter determines which documents are published. When false,
+    ///// only those documents that are already published, are republished. When true, all documents are
+    ///// published. The root of the branch is always published, regardless of .
+    ///// The  parameter is a function which determines whether a document has
+    ///// changes to publish (else there is no need to publish it). If one wants to publish only a selection of
+    ///// cultures, one may want to check that only properties for these cultures have changed. Otherwise, other
+    ///// cultures may trigger an unwanted republish.
+    ///// The  parameter is a function to execute to publish cultures, on
+    ///// each document. It can publish all, one, or a selection of cultures. It returns a boolean indicating
+    ///// whether the cultures could be published.
+    ///// 
+    //IEnumerable SaveAndPublishBranch(IContent content, bool force,
+    //    Func> shouldPublish,
+    //    Func, bool> publishCultures,
+    //    int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Unpublishes a document.
+    /// 
+    /// 
+    ///     
+    ///         By default, unpublishes the document as a whole, but it is possible to specify a culture to be
+    ///         unpublished. Depending on whether that culture is mandatory, and other cultures remain published,
+    ///         the document as a whole may or may not remain published.
+    ///     
+    ///     
+    ///         If the content type is variant, then culture can be either '*' or an actual culture, but neither null nor
+    ///         empty. If the content type is invariant, then culture can be either '*' or null or empty.
+    ///     
+    /// 
+    PublishResult Unpublish(IContent content, string culture = "*", int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Gets a value indicating whether a document is path-publishable.
+    /// 
+    /// A document is path-publishable when all its ancestors are published.
+    bool IsPathPublishable(IContent content);
+
+    /// 
+    ///     Gets a value indicating whether a document is path-published.
+    /// 
+    /// A document is path-published when all its ancestors, and the document itself, are published.
+    bool IsPathPublished(IContent content);
+
+    /// 
+    ///     Saves a document and raises the "sent to publication" events.
+    /// 
+    bool SendToPublication(IContent? content, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Publishes and unpublishes scheduled documents.
+    /// 
+    IEnumerable PerformScheduledPublish(DateTime date);
+
+    #endregion
+
+    #region Permissions
+
+    /// 
+    ///     Gets permissions assigned to a document.
+    /// 
+    EntityPermissionCollection GetPermissions(IContent content);
+
+    /// 
+    ///     Sets the permission of a document.
+    /// 
+    /// Replaces all permissions with the new set of permissions.
+    void SetPermissions(EntityPermissionSet permissionSet);
+
+    /// 
+    ///     Assigns a permission to a document.
+    /// 
+    /// Adds the permission to existing permissions.
+    void SetPermission(IContent entity, char permission, IEnumerable groupIds);
+
+    #endregion
+
+    #region Create
+
+    /// 
+    ///     Creates a document.
+    /// 
+    IContent Create(string name, Guid parentId, string documentTypeAlias, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Creates a document.
+    /// 
+    IContent Create(string name, int parentId, string documentTypeAlias, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Creates a document
+    /// 
+    IContent Create(string name, int parentId, IContentType contentType, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Creates a document.
+    /// 
+    IContent Create(string name, IContent? parent, string documentTypeAlias,
+        int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Creates and saves a document.
+    /// 
+    IContent CreateAndSave(string name, int parentId, string contentTypeAlias,
+        int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Creates and saves a document.
+    /// 
+    IContent CreateAndSave(string name, IContent parent, string contentTypeAlias,
+        int userId = Constants.Security.SuperUserId);
+
+    #endregion
 }
diff --git a/src/Umbraco.Core/Services/IContentServiceBase.cs b/src/Umbraco.Core/Services/IContentServiceBase.cs
index 1916fb49c4ac..9031877e4660 100644
--- a/src/Umbraco.Core/Services/IContentServiceBase.cs
+++ b/src/Umbraco.Core/Services/IContentServiceBase.cs
@@ -1,25 +1,22 @@
-using System;
-using System.Collections.Generic;
 using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface IContentServiceBase : IContentServiceBase
+    where TItem : class, IContentBase
 {
-    public interface IContentServiceBase : IContentServiceBase
-        where TItem: class, IContentBase
-    {
-        TItem? GetById(Guid key);
-        Attempt Save(IEnumerable contents, int userId = Constants.Security.SuperUserId);
-    }
+    TItem? GetById(Guid key);
+    Attempt Save(IEnumerable contents, int userId = Constants.Security.SuperUserId);
+}
 
+/// 
+///     Placeholder for sharing logic between the content, media (and member) services
+///     TODO: Start sharing the logic!
+/// 
+public interface IContentServiceBase : IService
+{
     /// 
-    /// Placeholder for sharing logic between the content, media (and member) services
-    /// TODO: Start sharing the logic!
+    ///     Checks/fixes the data integrity of node paths/levels stored in the database
     /// 
-    public interface IContentServiceBase : IService
-    {
-        /// 
-        /// Checks/fixes the data integrity of node paths/levels stored in the database
-        /// 
-        ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options);
-    }
+    ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options);
 }
diff --git a/src/Umbraco.Core/Services/IContentTypeBaseServiceProvider.cs b/src/Umbraco.Core/Services/IContentTypeBaseServiceProvider.cs
index 4b6a78850c3a..be8cef8fd12d 100644
--- a/src/Umbraco.Core/Services/IContentTypeBaseServiceProvider.cs
+++ b/src/Umbraco.Core/Services/IContentTypeBaseServiceProvider.cs
@@ -1,27 +1,30 @@
 using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Provides the  corresponding to an  object.
+/// 
+public interface IContentTypeBaseServiceProvider
 {
     /// 
-    /// Provides the  corresponding to an  object.
+    ///     Gets the content type service base managing types for the specified content base.
     /// 
-    public interface IContentTypeBaseServiceProvider
-    {
-        /// 
-        /// Gets the content type service base managing types for the specified content base.
-        /// 
-        /// 
-        /// If  is an , this returns the
-        /// , and if it's an , this returns
-        /// the , etc.
-        /// Services are returned as  and can be used
-        /// to retrieve the content / media / whatever type as .
-        /// 
-        IContentTypeBaseService For(IContentBase contentBase);
+    /// 
+    ///     
+    ///         If  is an , this returns the
+    ///         , and if it's an , this returns
+    ///         the , etc.
+    ///     
+    ///     
+    ///         Services are returned as  and can be used
+    ///         to retrieve the content / media / whatever type as .
+    ///     
+    /// 
+    IContentTypeBaseService For(IContentBase contentBase);
 
-        /// 
-        /// Gets the content type of an  object.
-        /// 
-        IContentTypeComposition? GetContentTypeOf(IContentBase contentBase);
-    }
+    /// 
+    ///     Gets the content type of an  object.
+    /// 
+    IContentTypeComposition? GetContentTypeOf(IContentBase contentBase);
 }
diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs
index 4b34baa8693a..8e6390a5a776 100644
--- a/src/Umbraco.Core/Services/IContentTypeService.cs
+++ b/src/Umbraco.Core/Services/IContentTypeService.cs
@@ -1,35 +1,32 @@
-using System;
-using System.Collections.Generic;
-using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Manages  objects.
+/// 
+public interface IContentTypeService : IContentTypeBaseService
 {
     /// 
-    /// Manages  objects.
+    ///     Gets all property type aliases.
     /// 
-    public interface IContentTypeService : IContentTypeBaseService
-    {
-        /// 
-        /// Gets all property type aliases.
-        /// 
-        /// 
-        IEnumerable GetAllPropertyTypeAliases();
+    /// 
+    IEnumerable GetAllPropertyTypeAliases();
 
-        /// 
-        /// Gets all content type aliases
-        /// 
-        /// 
-        /// If this list is empty, it will return all content type aliases for media, members and content, otherwise
-        /// it will only return content type aliases for the object types specified
-        /// 
-        /// 
-        IEnumerable GetAllContentTypeAliases(params Guid[] objectTypes);
+    /// 
+    ///     Gets all content type aliases
+    /// 
+    /// 
+    ///     If this list is empty, it will return all content type aliases for media, members and content, otherwise
+    ///     it will only return content type aliases for the object types specified
+    /// 
+    /// 
+    IEnumerable GetAllContentTypeAliases(params Guid[] objectTypes);
 
-        /// 
-        /// Returns all content type Ids for the aliases given
-        /// 
-        /// 
-        /// 
-        IEnumerable GetAllContentTypeIds(string[] aliases);
-    }
+    /// 
+    ///     Returns all content type Ids for the aliases given
+    /// 
+    /// 
+    /// 
+    IEnumerable GetAllContentTypeIds(string[] aliases);
 }
diff --git a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs
index 5614d87bf3e2..907cc689d1fd 100644
--- a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs
+++ b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs
@@ -1,96 +1,99 @@
-using System;
-using System.Collections.Generic;
-using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Provides a common base interface for .
+/// 
+public interface IContentTypeBaseService
 {
     /// 
-    /// Provides a common base interface for .
+    ///     Gets a content type.
+    /// 
+    IContentTypeComposition? Get(int id);
+}
+
+/// 
+///     Provides a common base interface for ,  and
+///     .
+/// 
+/// The type of the item.
+public interface IContentTypeBaseService : IContentTypeBaseService, IService
+    where TItem : IContentTypeComposition
+{
+    /// 
+    ///     Gets a content type.
+    /// 
+    new TItem? Get(int id);
+
+    /// 
+    ///     Gets a content type.
+    /// 
+    TItem? Get(Guid key);
+
+    /// 
+    ///     Gets a content type.
+    /// 
+    TItem? Get(string alias);
+
+    int Count();
+
+    /// 
+    ///     Returns true or false depending on whether content nodes have been created based on the provided content type id.
+    /// 
+    bool HasContentNodes(int id);
+
+    IEnumerable GetAll(params int[] ids);
+    IEnumerable GetAll(IEnumerable? ids);
+
+    IEnumerable GetDescendants(int id, bool andSelf); // parent-child axis
+    IEnumerable GetComposedOf(int id); // composition axis
+
+    IEnumerable GetChildren(int id);
+    IEnumerable GetChildren(Guid id);
+
+    bool HasChildren(int id);
+    bool HasChildren(Guid id);
+
+    void Save(TItem? item, int userId = Constants.Security.SuperUserId);
+    void Save(IEnumerable items, int userId = Constants.Security.SuperUserId);
+    void Delete(TItem item, int userId = Constants.Security.SuperUserId);
+    void Delete(IEnumerable item, int userId = Constants.Security.SuperUserId);
+
+
+    Attempt ValidateComposition(TItem? compo);
+
+    /// 
+    ///     Given the path of a content item, this will return true if the content item exists underneath a list view content
+    ///     item
     /// 
-    public interface IContentTypeBaseService
-    {
-        /// 
-        /// Gets a content type.
-        /// 
-        IContentTypeComposition? Get(int id);
-    }
+    /// 
+    /// 
+    bool HasContainerInPath(string contentPath);
 
     /// 
-    /// Provides a common base interface for ,  and .
+    ///     Gets a value indicating whether there is a list view content item in the path.
     /// 
-    /// The type of the item.
-    public interface IContentTypeBaseService : IContentTypeBaseService, IService
-        where TItem : IContentTypeComposition
-    {
-        /// 
-        /// Gets a content type.
-        /// 
-        new TItem? Get(int id);
-
-        /// 
-        /// Gets a content type.
-        /// 
-        TItem? Get(Guid key);
-
-        /// 
-        /// Gets a content type.
-        /// 
-        TItem? Get(string alias);
-
-        int Count();
-
-        /// 
-        /// Returns true or false depending on whether content nodes have been created based on the provided content type id.
-        /// 
-        bool HasContentNodes(int id);
-
-        IEnumerable GetAll(params int[] ids);
-        IEnumerable GetAll(IEnumerable? ids);
-
-        IEnumerable GetDescendants(int id, bool andSelf); // parent-child axis
-        IEnumerable GetComposedOf(int id); // composition axis
-
-        IEnumerable GetChildren(int id);
-        IEnumerable GetChildren(Guid id);
-
-        bool HasChildren(int id);
-        bool HasChildren(Guid id);
-
-        void Save(TItem? item, int userId = Constants.Security.SuperUserId);
-        void Save(IEnumerable items, int userId = Constants.Security.SuperUserId);
-        void Delete(TItem item, int userId = Constants.Security.SuperUserId);
-        void Delete(IEnumerable item, int userId = Constants.Security.SuperUserId);
-
-
-        Attempt ValidateComposition(TItem? compo);
-
-        /// 
-        /// Given the path of a content item, this will return true if the content item exists underneath a list view content item
-        /// 
-        /// 
-        /// 
-        bool HasContainerInPath(string contentPath);
-
-        /// 
-        /// Gets a value indicating whether there is a list view content item in the path.
-        /// 
-        /// 
-        /// 
-        bool HasContainerInPath(params int[] ids);
-
-        Attempt?> CreateContainer(int parentContainerId, Guid key, string name, int userId = Constants.Security.SuperUserId);
-        Attempt SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId);
-        EntityContainer? GetContainer(int containerId);
-        EntityContainer? GetContainer(Guid containerId);
-        IEnumerable GetContainers(int[] containerIds);
-        IEnumerable GetContainers(TItem contentType);
-        IEnumerable GetContainers(string folderName, int level);
-        Attempt DeleteContainer(int containerId, int userId = Constants.Security.SuperUserId);
-        Attempt?> RenameContainer(int id, string name, int userId = Constants.Security.SuperUserId);
-
-        Attempt?> Move(TItem moving, int containerId);
-        Attempt?> Copy(TItem copying, int containerId);
-        TItem Copy(TItem original, string alias, string name, int parentId = -1);
-        TItem Copy(TItem original, string alias, string name, TItem parent);
-    }
+    /// 
+    /// 
+    bool HasContainerInPath(params int[] ids);
+
+    Attempt?> CreateContainer(int parentContainerId, Guid key,
+        string name, int userId = Constants.Security.SuperUserId);
+
+    Attempt SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId);
+    EntityContainer? GetContainer(int containerId);
+    EntityContainer? GetContainer(Guid containerId);
+    IEnumerable GetContainers(int[] containerIds);
+    IEnumerable GetContainers(TItem contentType);
+    IEnumerable GetContainers(string folderName, int level);
+    Attempt DeleteContainer(int containerId, int userId = Constants.Security.SuperUserId);
+
+    Attempt?> RenameContainer(int id, string name,
+        int userId = Constants.Security.SuperUserId);
+
+    Attempt?> Move(TItem moving, int containerId);
+    Attempt?> Copy(TItem copying, int containerId);
+    TItem Copy(TItem original, string alias, string name, int parentId = -1);
+    TItem Copy(TItem original, string alias, string name, TItem parent);
 }
diff --git a/src/Umbraco.Core/Services/IContentVersionCleanupPolicy.cs b/src/Umbraco.Core/Services/IContentVersionCleanupPolicy.cs
index 86e298830749..d9cbcc0cda5b 100644
--- a/src/Umbraco.Core/Services/IContentVersionCleanupPolicy.cs
+++ b/src/Umbraco.Core/Services/IContentVersionCleanupPolicy.cs
@@ -1,17 +1,14 @@
-using System;
-using System.Collections.Generic;
 using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Used to filter historic content versions for cleanup.
+/// 
+public interface IContentVersionCleanupPolicy
 {
     /// 
-    /// Used to filter historic content versions for cleanup.
+    ///     Filters a set of candidates historic content versions for cleanup according to policy settings.
     /// 
-    public interface IContentVersionCleanupPolicy
-    {
-        /// 
-        /// Filters a set of candidates historic content versions for cleanup according to policy settings.
-        /// 
-        IEnumerable Apply(DateTime asAtDate, IEnumerable items);
-    }
+    IEnumerable Apply(DateTime asAtDate, IEnumerable items);
 }
diff --git a/src/Umbraco.Core/Services/IContentVersionService.cs b/src/Umbraco.Core/Services/IContentVersionService.cs
index d0f203b2ef7b..8d85b9039b1c 100644
--- a/src/Umbraco.Core/Services/IContentVersionService.cs
+++ b/src/Umbraco.Core/Services/IContentVersionService.cs
@@ -1,25 +1,23 @@
-using System;
-using System.Collections.Generic;
 using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface IContentVersionService
 {
-    public interface IContentVersionService
-    {
-        /// 
-        /// Removes historic content versions according to a policy.
-        /// 
-        IReadOnlyCollection PerformContentVersionCleanup(DateTime asAtDate);
+    /// 
+    ///     Removes historic content versions according to a policy.
+    /// 
+    IReadOnlyCollection PerformContentVersionCleanup(DateTime asAtDate);
 
-        /// 
-        /// Gets paginated content versions for given content id paginated.
-        /// 
-        /// Thrown when  is invalid.
-        IEnumerable? GetPagedContentVersions(int contentId, long pageIndex, int pageSize, out long totalRecords, string? culture = null);
+    /// 
+    ///     Gets paginated content versions for given content id paginated.
+    /// 
+    /// Thrown when  is invalid.
+    IEnumerable? GetPagedContentVersions(int contentId, long pageIndex, int pageSize,
+        out long totalRecords, string? culture = null);
 
-        /// 
-        /// Updates preventCleanup value for given content version.
-        /// 
-        void SetPreventCleanup(int versionId, bool preventCleanup, int userId = -1);
-    }
+    /// 
+    ///     Updates preventCleanup value for given content version.
+    /// 
+    void SetPreventCleanup(int versionId, bool preventCleanup, int userId = -1);
 }
diff --git a/src/Umbraco.Core/Services/IDashboardService.cs b/src/Umbraco.Core/Services/IDashboardService.cs
index 70e34106276f..698617be4b4e 100644
--- a/src/Umbraco.Core/Services/IDashboardService.cs
+++ b/src/Umbraco.Core/Services/IDashboardService.cs
@@ -1,27 +1,24 @@
-using System.Collections.Generic;
-using Umbraco.Cms.Core.Dashboards;
+using Umbraco.Cms.Core.Dashboards;
 using Umbraco.Cms.Core.Models.ContentEditing;
 using Umbraco.Cms.Core.Models.Membership;
 
-namespace Umbraco.Cms.Core.Services
-{
-    public interface IDashboardService
-    {
-        /// 
-        /// Gets dashboard for a specific section/application
-        /// For a specific backoffice user
-        /// 
-        /// 
-        /// 
-        /// 
-        IEnumerable> GetDashboards(string section, IUser? currentUser);
+namespace Umbraco.Cms.Core.Services;
 
-        /// 
-        /// Gets all dashboards, organized by section, for a user.
-        /// 
-        /// 
-        /// 
-        IDictionary>> GetDashboards(IUser? currentUser);
+public interface IDashboardService
+{
+    /// 
+    ///     Gets dashboard for a specific section/application
+    ///     For a specific backoffice user
+    /// 
+    /// 
+    /// 
+    /// 
+    IEnumerable> GetDashboards(string section, IUser? currentUser);
 
-    }
+    /// 
+    ///     Gets all dashboards, organized by section, for a user.
+    /// 
+    /// 
+    /// 
+    IDictionary>> GetDashboards(IUser? currentUser);
 }
diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs
index 898b24355eda..4b2ca72d8967 100644
--- a/src/Umbraco.Core/Services/IDataTypeService.cs
+++ b/src/Umbraco.Core/Services/IDataTypeService.cs
@@ -1,92 +1,99 @@
-using System;
-using System.Collections.Generic;
 using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
-{
+namespace Umbraco.Cms.Core.Services;
 
+/// 
+///     Defines the DataType Service, which is an easy access to operations involving 
+/// 
+public interface IDataTypeService : IService
+{
     /// 
-    /// Defines the DataType Service, which is an easy access to operations involving 
+    ///     Returns a dictionary of content type s and the property type aliases that use a
+    ///     
     /// 
-    public interface IDataTypeService : IService
-    {
-        /// 
-        /// Returns a dictionary of content type s and the property type aliases that use a 
-        /// 
-        /// 
-        /// 
-        IReadOnlyDictionary> GetReferences(int id);
+    /// 
+    /// 
+    IReadOnlyDictionary> GetReferences(int id);
+
+    Attempt?> CreateContainer(int parentId, Guid key, string name,
+        int userId = Constants.Security.SuperUserId);
 
-        Attempt?> CreateContainer(int parentId, Guid key, string name, int userId = Constants.Security.SuperUserId);
-        Attempt SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId);
-        EntityContainer? GetContainer(int containerId);
-        EntityContainer? GetContainer(Guid containerId);
-        IEnumerable GetContainers(string folderName, int level);
-        IEnumerable GetContainers(IDataType dataType);
-        IEnumerable GetContainers(int[] containerIds);
-        Attempt DeleteContainer(int containerId, int userId = Constants.Security.SuperUserId);
-        Attempt?> RenameContainer(int id, string name, int userId = Constants.Security.SuperUserId);
+    Attempt SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId);
+    EntityContainer? GetContainer(int containerId);
+    EntityContainer? GetContainer(Guid containerId);
+    IEnumerable GetContainers(string folderName, int level);
+    IEnumerable GetContainers(IDataType dataType);
+    IEnumerable GetContainers(int[] containerIds);
+    Attempt DeleteContainer(int containerId, int userId = Constants.Security.SuperUserId);
 
-        /// 
-        /// Gets a  by its Name
-        /// 
-        /// Name of the 
-        /// 
-        IDataType? GetDataType(string name);
+    Attempt?> RenameContainer(int id, string name,
+        int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Gets a  by its Name
+    /// 
+    /// Name of the 
+    /// 
+    ///     
+    /// 
+    IDataType? GetDataType(string name);
 
-        /// 
-        /// Gets a  by its Id
-        /// 
-        /// Id of the 
-        /// 
-        IDataType? GetDataType(int id);
+    /// 
+    ///     Gets a  by its Id
+    /// 
+    /// Id of the 
+    /// 
+    ///     
+    /// 
+    IDataType? GetDataType(int id);
 
-        /// 
-        /// Gets a  by its unique guid Id
-        /// 
-        /// Unique guid Id of the DataType
-        /// 
-        IDataType? GetDataType(Guid id);
+    /// 
+    ///     Gets a  by its unique guid Id
+    /// 
+    /// Unique guid Id of the DataType
+    /// 
+    ///     
+    /// 
+    IDataType? GetDataType(Guid id);
 
-        /// 
-        /// Gets all  objects or those with the ids passed in
-        /// 
-        /// Optional array of Ids
-        /// An enumerable list of  objects
-        IEnumerable GetAll(params int[] ids);
+    /// 
+    ///     Gets all  objects or those with the ids passed in
+    /// 
+    /// Optional array of Ids
+    /// An enumerable list of  objects
+    IEnumerable GetAll(params int[] ids);
 
-        /// 
-        /// Saves an 
-        /// 
-        ///  to save
-        /// Id of the user issuing the save
-        void Save(IDataType dataType, int userId = Constants.Security.SuperUserId);
+    /// 
+    ///     Saves an 
+    /// 
+    ///  to save
+    /// Id of the user issuing the save
+    void Save(IDataType dataType, int userId = Constants.Security.SuperUserId);
 
-        /// 
-        /// Saves a collection of 
-        /// 
-        ///  to save
-        /// Id of the user issuing the save
-        void Save(IEnumerable dataTypeDefinitions, int userId = Constants.Security.SuperUserId);
+    /// 
+    ///     Saves a collection of 
+    /// 
+    ///  to save
+    /// Id of the user issuing the save
+    void Save(IEnumerable dataTypeDefinitions, int userId = Constants.Security.SuperUserId);
 
-        /// 
-        /// Deletes an 
-        /// 
-        /// 
-        /// Please note that deleting a  will remove
-        /// all the  data that references this .
-        /// 
-        ///  to delete
-        /// Id of the user issuing the deletion
-        void Delete(IDataType dataType, int userId = Constants.Security.SuperUserId);
+    /// 
+    ///     Deletes an 
+    /// 
+    /// 
+    ///     Please note that deleting a  will remove
+    ///     all the  data that references this .
+    /// 
+    ///  to delete
+    /// Id of the user issuing the deletion
+    void Delete(IDataType dataType, int userId = Constants.Security.SuperUserId);
 
-        /// 
-        /// Gets a  by its control Id
-        /// 
-        /// Alias of the property editor
-        /// Collection of  objects with a matching control id
-        IEnumerable GetByEditorAlias(string propertyEditorAlias);
+    /// 
+    ///     Gets a  by its control Id
+    /// 
+    /// Alias of the property editor
+    /// Collection of  objects with a matching control id
+    IEnumerable GetByEditorAlias(string propertyEditorAlias);
 
-        Attempt?> Move(IDataType toMove, int parentId);
-    }
+    Attempt?> Move(IDataType toMove, int parentId);
 }
diff --git a/src/Umbraco.Core/Services/IDomainService.cs b/src/Umbraco.Core/Services/IDomainService.cs
index 952eaecfde43..31e5f6040c6e 100644
--- a/src/Umbraco.Core/Services/IDomainService.cs
+++ b/src/Umbraco.Core/Services/IDomainService.cs
@@ -1,16 +1,14 @@
-using System.Collections.Generic;
-using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface IDomainService : IService
 {
-    public interface IDomainService : IService
-    {
-        bool Exists(string domainName);
-        Attempt Delete(IDomain domain);
-        IDomain? GetByName(string name);
-        IDomain? GetById(int id);
-        IEnumerable GetAll(bool includeWildcards);
-        IEnumerable GetAssignedDomains(int contentId, bool includeWildcards);
-        Attempt Save(IDomain domainEntity);
-    }
+    bool Exists(string domainName);
+    Attempt Delete(IDomain domain);
+    IDomain? GetByName(string name);
+    IDomain? GetById(int id);
+    IEnumerable GetAll(bool includeWildcards);
+    IEnumerable GetAssignedDomains(int contentId, bool includeWildcards);
+    Attempt Save(IDomain domainEntity);
 }
diff --git a/src/Umbraco.Core/Services/IEditorConfigurationParser.cs b/src/Umbraco.Core/Services/IEditorConfigurationParser.cs
index 8dc1210d11c7..83c70c273cad 100644
--- a/src/Umbraco.Core/Services/IEditorConfigurationParser.cs
+++ b/src/Umbraco.Core/Services/IEditorConfigurationParser.cs
@@ -1,11 +1,11 @@
-using System.Collections.Generic;
-using Umbraco.Cms.Core.PropertyEditors;
+using Umbraco.Cms.Core.PropertyEditors;
 
 namespace Umbraco.Cms.Core.Services;
 
 public interface IEditorConfigurationParser
 {
-    TConfiguration? ParseFromConfigurationEditor(IDictionary? editorValues, IEnumerable fields);
+    TConfiguration? ParseFromConfigurationEditor(IDictionary? editorValues,
+        IEnumerable fields);
 
     Dictionary ParseToConfigurationEditor(TConfiguration? configuration);
 }
diff --git a/src/Umbraco.Core/Services/IEntityService.cs b/src/Umbraco.Core/Services/IEntityService.cs
index 66298aba1d00..7ca182a68b11 100644
--- a/src/Umbraco.Core/Services/IEntityService.cs
+++ b/src/Umbraco.Core/Services/IEntityService.cs
@@ -1,252 +1,253 @@
-using System;
-using System.Collections.Generic;
-using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Models.Entities;
 using Umbraco.Cms.Core.Persistence.Querying;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface IEntityService
 {
-    public interface IEntityService
-    {
-        /// 
-        /// Gets an entity.
-        /// 
-        /// The identifier of the entity.
-        IEntitySlim? Get(int id);
-
-        /// 
-        /// Gets an entity.
-        /// 
-        /// The unique key of the entity.
-        IEntitySlim? Get(Guid key);
-
-        /// 
-        /// Gets an entity.
-        /// 
-        /// The identifier of the entity.
-        /// The object type of the entity.
-        IEntitySlim? Get(int id, UmbracoObjectTypes objectType);
-
-        /// 
-        /// Gets an entity.
-        /// 
-        /// The unique key of the entity.
-        /// The object type of the entity.
-        IEntitySlim? Get(Guid key, UmbracoObjectTypes objectType);
-
-        /// 
-        /// Gets an entity.
-        /// 
-        /// The type used to determine the object type of the entity.
-        /// The identifier of the entity.
-        IEntitySlim? Get(int id) where T : IUmbracoEntity;
-
-        /// 
-        /// Gets an entity.
-        /// 
-        /// The type used to determine the object type of the entity.
-        /// The unique key of the entity.
-        IEntitySlim? Get(Guid key) where T : IUmbracoEntity;
-
-        /// 
-        /// Determines whether an entity exists.
-        /// 
-        /// The identifier of the entity.
-        bool Exists(int id);
-
-        /// 
-        /// Determines whether an entity exists.
-        /// 
-        /// The unique key of the entity.
-        bool Exists(Guid key);
-
-        /// 
-        /// Gets entities of a given object type.
-        /// 
-        /// The type used to determine the object type of the entities.
-        IEnumerable GetAll() where T : IUmbracoEntity;
-
-        /// 
-        /// Gets entities of a given object type.
-        /// 
-        /// The type used to determine the object type of the entities.
-        /// The identifiers of the entities.
-        /// If  is empty, returns all entities.
-        IEnumerable GetAll(params int[] ids) where T : IUmbracoEntity;
-
-        /// 
-        /// Gets entities of a given object type.
-        /// 
-        /// The object type of the entities.
-        IEnumerable GetAll(UmbracoObjectTypes objectType);
-
-        /// 
-        /// Gets entities of a given object type.
-        /// 
-        /// The object type of the entities.
-        /// The identifiers of the entities.
-        /// If  is empty, returns all entities.
-        IEnumerable GetAll(UmbracoObjectTypes objectType, params int[] ids);
-
-        /// 
-        /// Gets entities of a given object type.
-        /// 
-        /// The object type of the entities.
-        IEnumerable GetAll(Guid objectType);
-
-        /// 
-        /// Gets entities of a given object type.
-        /// 
-        /// The object type of the entities.
-        /// The identifiers of the entities.
-        /// If  is empty, returns all entities.
-        IEnumerable GetAll(Guid objectType, params int[] ids);
-
-        /// 
-        /// Gets entities of a given object type.
-        /// 
-        /// The type used to determine the object type of the entities.
-        /// The unique identifiers of the entities.
-        /// If  is empty, returns all entities.
-        IEnumerable GetAll(params Guid[] keys) where T : IUmbracoEntity;
-
-        /// 
-        /// Gets entities of a given object type.
-        /// 
-        /// The object type of the entities.
-        /// The unique identifiers of the entities.
-        /// If  is empty, returns all entities.
-        IEnumerable GetAll(UmbracoObjectTypes objectType, Guid[] keys);
-
-        /// 
-        /// Gets entities of a given object type.
-        /// 
-        /// The object type of the entities.
-        /// The unique identifiers of the entities.
-        /// If  is empty, returns all entities.
-        IEnumerable GetAll(Guid objectType, params Guid[] keys);
-
-        /// 
-        /// Gets entities at root.
-        /// 
-        /// The object type of the entities.
-        IEnumerable GetRootEntities(UmbracoObjectTypes objectType);
-
-        /// 
-        /// Gets the parent of an entity.
-        /// 
-        /// The identifier of the entity.
-        IEntitySlim? GetParent(int id);
-
-        /// 
-        /// Gets the parent of an entity.
-        /// 
-        /// The identifier of the entity.
-        /// The object type of the parent.
-        IEntitySlim? GetParent(int id, UmbracoObjectTypes objectType);
-
-        /// 
-        /// Gets the children of an entity.
-        /// 
-        /// The identifier of the entity.
-        IEnumerable GetChildren(int id);
-
-        /// 
-        /// Gets the children of an entity.
-        /// 
-        /// The identifier of the entity.
-        /// The object type of the children.
-        IEnumerable GetChildren(int id, UmbracoObjectTypes objectType);
-
-        /// 
-        /// Gets the descendants of an entity.
-        /// 
-        /// The identifier of the entity.
-        IEnumerable GetDescendants(int id);
-
-        /// 
-        /// Gets the descendants of an entity.
-        /// 
-        /// The identifier of the entity.
-        /// The object type of the descendants.
-        IEnumerable GetDescendants(int id, UmbracoObjectTypes objectType);
-
-        /// 
-        /// Gets children of an entity.
-        /// 
-        IEnumerable GetPagedChildren(int id, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords,
-            IQuery? filter = null, Ordering? ordering = null);
-
-        /// 
-        /// Gets descendants of an entity.
-        /// 
-        IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords,
-            IQuery? filter = null, Ordering? ordering = null);
-
-        /// 
-        /// Gets descendants of entities.
-        /// 
-        IEnumerable GetPagedDescendants(IEnumerable ids, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords,
-            IQuery? filter = null, Ordering? ordering = null);
-
-        // TODO: Do we really need this? why not just pass in -1
-        /// 
-        /// Gets descendants of root.
-        /// 
-        IEnumerable GetPagedDescendants(UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords,
-            IQuery? filter = null, Ordering? ordering = null, bool includeTrashed = true);
-
-        /// 
-        /// Gets the object type of an entity.
-        /// 
-        UmbracoObjectTypes GetObjectType(int id);
-
-        /// 
-        /// Gets the object type of an entity.
-        /// 
-        UmbracoObjectTypes GetObjectType(Guid key);
-
-        /// 
-        /// Gets the object type of an entity.
-        /// 
-        UmbracoObjectTypes GetObjectType(IUmbracoEntity entity);
-
-        /// 
-        /// Gets the CLR type of an entity.
-        /// 
-        Type? GetEntityType(int id);
-
-        /// 
-        /// Gets the integer identifier corresponding to a unique Guid identifier.
-        /// 
-        Attempt GetId(Guid key, UmbracoObjectTypes objectType);
-
-        /// 
-        /// Gets the integer identifier corresponding to a Udi.
-        /// 
-        Attempt GetId(Udi udi);
-
-        /// 
-        /// Gets the unique Guid identifier corresponding to an integer identifier.
-        /// 
-        Attempt GetKey(int id, UmbracoObjectTypes umbracoObjectType);
-
-        /// 
-        /// Gets paths for entities.
-        /// 
-        IEnumerable GetAllPaths(UmbracoObjectTypes objectType, params int[]? ids);
-
-        /// 
-        /// Gets paths for entities.
-        /// 
-        IEnumerable GetAllPaths(UmbracoObjectTypes objectType, params Guid[] keys);
-
-        /// 
-        /// Reserves an identifier for a key.
-        /// 
-        /// They key.
-        /// The identifier.
-        /// When a new content or a media is saved with the key, it will have the reserved identifier.
-        int ReserveId(Guid key);
-    }
+    /// 
+    ///     Gets an entity.
+    /// 
+    /// The identifier of the entity.
+    IEntitySlim? Get(int id);
+
+    /// 
+    ///     Gets an entity.
+    /// 
+    /// The unique key of the entity.
+    IEntitySlim? Get(Guid key);
+
+    /// 
+    ///     Gets an entity.
+    /// 
+    /// The identifier of the entity.
+    /// The object type of the entity.
+    IEntitySlim? Get(int id, UmbracoObjectTypes objectType);
+
+    /// 
+    ///     Gets an entity.
+    /// 
+    /// The unique key of the entity.
+    /// The object type of the entity.
+    IEntitySlim? Get(Guid key, UmbracoObjectTypes objectType);
+
+    /// 
+    ///     Gets an entity.
+    /// 
+    /// The type used to determine the object type of the entity.
+    /// The identifier of the entity.
+    IEntitySlim? Get(int id) where T : IUmbracoEntity;
+
+    /// 
+    ///     Gets an entity.
+    /// 
+    /// The type used to determine the object type of the entity.
+    /// The unique key of the entity.
+    IEntitySlim? Get(Guid key) where T : IUmbracoEntity;
+
+    /// 
+    ///     Determines whether an entity exists.
+    /// 
+    /// The identifier of the entity.
+    bool Exists(int id);
+
+    /// 
+    ///     Determines whether an entity exists.
+    /// 
+    /// The unique key of the entity.
+    bool Exists(Guid key);
+
+    /// 
+    ///     Gets entities of a given object type.
+    /// 
+    /// The type used to determine the object type of the entities.
+    IEnumerable GetAll() where T : IUmbracoEntity;
+
+    /// 
+    ///     Gets entities of a given object type.
+    /// 
+    /// The type used to determine the object type of the entities.
+    /// The identifiers of the entities.
+    /// If  is empty, returns all entities.
+    IEnumerable GetAll(params int[] ids) where T : IUmbracoEntity;
+
+    /// 
+    ///     Gets entities of a given object type.
+    /// 
+    /// The object type of the entities.
+    IEnumerable GetAll(UmbracoObjectTypes objectType);
+
+    /// 
+    ///     Gets entities of a given object type.
+    /// 
+    /// The object type of the entities.
+    /// The identifiers of the entities.
+    /// If  is empty, returns all entities.
+    IEnumerable GetAll(UmbracoObjectTypes objectType, params int[] ids);
+
+    /// 
+    ///     Gets entities of a given object type.
+    /// 
+    /// The object type of the entities.
+    IEnumerable GetAll(Guid objectType);
+
+    /// 
+    ///     Gets entities of a given object type.
+    /// 
+    /// The object type of the entities.
+    /// The identifiers of the entities.
+    /// If  is empty, returns all entities.
+    IEnumerable GetAll(Guid objectType, params int[] ids);
+
+    /// 
+    ///     Gets entities of a given object type.
+    /// 
+    /// The type used to determine the object type of the entities.
+    /// The unique identifiers of the entities.
+    /// If  is empty, returns all entities.
+    IEnumerable GetAll(params Guid[] keys) where T : IUmbracoEntity;
+
+    /// 
+    ///     Gets entities of a given object type.
+    /// 
+    /// The object type of the entities.
+    /// The unique identifiers of the entities.
+    /// If  is empty, returns all entities.
+    IEnumerable GetAll(UmbracoObjectTypes objectType, Guid[] keys);
+
+    /// 
+    ///     Gets entities of a given object type.
+    /// 
+    /// The object type of the entities.
+    /// The unique identifiers of the entities.
+    /// If  is empty, returns all entities.
+    IEnumerable GetAll(Guid objectType, params Guid[] keys);
+
+    /// 
+    ///     Gets entities at root.
+    /// 
+    /// The object type of the entities.
+    IEnumerable GetRootEntities(UmbracoObjectTypes objectType);
+
+    /// 
+    ///     Gets the parent of an entity.
+    /// 
+    /// The identifier of the entity.
+    IEntitySlim? GetParent(int id);
+
+    /// 
+    ///     Gets the parent of an entity.
+    /// 
+    /// The identifier of the entity.
+    /// The object type of the parent.
+    IEntitySlim? GetParent(int id, UmbracoObjectTypes objectType);
+
+    /// 
+    ///     Gets the children of an entity.
+    /// 
+    /// The identifier of the entity.
+    IEnumerable GetChildren(int id);
+
+    /// 
+    ///     Gets the children of an entity.
+    /// 
+    /// The identifier of the entity.
+    /// The object type of the children.
+    IEnumerable GetChildren(int id, UmbracoObjectTypes objectType);
+
+    /// 
+    ///     Gets the descendants of an entity.
+    /// 
+    /// The identifier of the entity.
+    IEnumerable GetDescendants(int id);
+
+    /// 
+    ///     Gets the descendants of an entity.
+    /// 
+    /// The identifier of the entity.
+    /// The object type of the descendants.
+    IEnumerable GetDescendants(int id, UmbracoObjectTypes objectType);
+
+    /// 
+    ///     Gets children of an entity.
+    /// 
+    IEnumerable GetPagedChildren(int id, UmbracoObjectTypes objectType, long pageIndex, int pageSize,
+        out long totalRecords,
+        IQuery? filter = null, Ordering? ordering = null);
+
+    /// 
+    ///     Gets descendants of an entity.
+    /// 
+    IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes objectType, long pageIndex, int pageSize,
+        out long totalRecords,
+        IQuery? filter = null, Ordering? ordering = null);
+
+    /// 
+    ///     Gets descendants of entities.
+    /// 
+    IEnumerable GetPagedDescendants(IEnumerable ids, UmbracoObjectTypes objectType, long pageIndex,
+        int pageSize, out long totalRecords,
+        IQuery? filter = null, Ordering? ordering = null);
+
+    // TODO: Do we really need this? why not just pass in -1
+    /// 
+    ///     Gets descendants of root.
+    /// 
+    IEnumerable GetPagedDescendants(UmbracoObjectTypes objectType, long pageIndex, int pageSize,
+        out long totalRecords,
+        IQuery? filter = null, Ordering? ordering = null, bool includeTrashed = true);
+
+    /// 
+    ///     Gets the object type of an entity.
+    /// 
+    UmbracoObjectTypes GetObjectType(int id);
+
+    /// 
+    ///     Gets the object type of an entity.
+    /// 
+    UmbracoObjectTypes GetObjectType(Guid key);
+
+    /// 
+    ///     Gets the object type of an entity.
+    /// 
+    UmbracoObjectTypes GetObjectType(IUmbracoEntity entity);
+
+    /// 
+    ///     Gets the CLR type of an entity.
+    /// 
+    Type? GetEntityType(int id);
+
+    /// 
+    ///     Gets the integer identifier corresponding to a unique Guid identifier.
+    /// 
+    Attempt GetId(Guid key, UmbracoObjectTypes objectType);
+
+    /// 
+    ///     Gets the integer identifier corresponding to a Udi.
+    /// 
+    Attempt GetId(Udi udi);
+
+    /// 
+    ///     Gets the unique Guid identifier corresponding to an integer identifier.
+    /// 
+    Attempt GetKey(int id, UmbracoObjectTypes umbracoObjectType);
+
+    /// 
+    ///     Gets paths for entities.
+    /// 
+    IEnumerable GetAllPaths(UmbracoObjectTypes objectType, params int[]? ids);
+
+    /// 
+    ///     Gets paths for entities.
+    /// 
+    IEnumerable GetAllPaths(UmbracoObjectTypes objectType, params Guid[] keys);
+
+    /// 
+    ///     Reserves an identifier for a key.
+    /// 
+    /// They key.
+    /// The identifier.
+    /// When a new content or a media is saved with the key, it will have the reserved identifier.
+    int ReserveId(Guid key);
 }
diff --git a/src/Umbraco.Core/Services/IEntityXmlSerializer.cs b/src/Umbraco.Core/Services/IEntityXmlSerializer.cs
index fd68a9dfca66..f102d3522229 100644
--- a/src/Umbraco.Core/Services/IEntityXmlSerializer.cs
+++ b/src/Umbraco.Core/Services/IEntityXmlSerializer.cs
@@ -1,90 +1,87 @@
-using System;
-using System.Collections.Generic;
 using System.Xml.Linq;
 using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Serializes entities to XML
+/// 
+public interface IEntityXmlSerializer
 {
     /// 
-    /// Serializes entities to XML
+    ///     Exports an IContent item as an XElement.
     /// 
-    public interface IEntityXmlSerializer
-    {
-        /// 
-        /// Exports an IContent item as an XElement.
-        /// 
-        XElement Serialize(IContent content,
-                bool published,
-                bool withDescendants = false) // TODO: take care of usage! only used for the packager
-            ;
+    XElement Serialize(IContent content,
+            bool published,
+            bool withDescendants = false) // TODO: take care of usage! only used for the packager
+        ;
 
-        /// 
-        /// Exports an IMedia item as an XElement.
-        /// 
-        XElement Serialize(
-            IMedia media,
-            bool withDescendants = false,
-            Action? onMediaItemSerialized = null);
+    /// 
+    ///     Exports an IMedia item as an XElement.
+    /// 
+    XElement Serialize(
+        IMedia media,
+        bool withDescendants = false,
+        Action? onMediaItemSerialized = null);
 
-        /// 
-        /// Exports an IMember item as an XElement.
-        /// 
-        XElement Serialize(IMember member);
+    /// 
+    ///     Exports an IMember item as an XElement.
+    /// 
+    XElement Serialize(IMember member);
 
-        /// 
-        /// Exports a list of Data Types
-        /// 
-        /// List of data types to export
-        ///  containing the xml representation of the IDataTypeDefinition objects
-        XElement Serialize(IEnumerable dataTypeDefinitions);
+    /// 
+    ///     Exports a list of Data Types
+    /// 
+    /// List of data types to export
+    ///  containing the xml representation of the IDataTypeDefinition objects
+    XElement Serialize(IEnumerable dataTypeDefinitions);
 
-        XElement Serialize(IDataType dataType);
+    XElement Serialize(IDataType dataType);
 
-        /// 
-        /// Exports a list of  items to xml as an 
-        /// 
-        /// List of dictionary items to export
-        /// Optional boolean indicating whether or not to include children
-        ///  containing the xml representation of the IDictionaryItem objects
-        XElement Serialize(IEnumerable dictionaryItem, bool includeChildren = true);
+    /// 
+    ///     Exports a list of  items to xml as an 
+    /// 
+    /// List of dictionary items to export
+    /// Optional boolean indicating whether or not to include children
+    ///  containing the xml representation of the IDictionaryItem objects
+    XElement Serialize(IEnumerable dictionaryItem, bool includeChildren = true);
 
-        /// 
-        /// Exports a single  item to xml as an 
-        /// 
-        /// Dictionary Item to export
-        /// Optional boolean indicating whether or not to include children
-        ///  containing the xml representation of the IDictionaryItem object
-        XElement Serialize(IDictionaryItem dictionaryItem, bool includeChildren);
+    /// 
+    ///     Exports a single  item to xml as an 
+    /// 
+    /// Dictionary Item to export
+    /// Optional boolean indicating whether or not to include children
+    ///  containing the xml representation of the IDictionaryItem object
+    XElement Serialize(IDictionaryItem dictionaryItem, bool includeChildren);
 
-        XElement Serialize(IStylesheet stylesheet, bool includeProperties);
+    XElement Serialize(IStylesheet stylesheet, bool includeProperties);
 
-        /// 
-        /// Exports a list of  items to xml as an 
-        /// 
-        /// List of Languages to export
-        ///  containing the xml representation of the ILanguage objects
-        XElement Serialize(IEnumerable languages);
+    /// 
+    ///     Exports a list of  items to xml as an 
+    /// 
+    /// List of Languages to export
+    ///  containing the xml representation of the ILanguage objects
+    XElement Serialize(IEnumerable languages);
 
-        XElement Serialize(ILanguage language);
-        XElement Serialize(ITemplate template);
+    XElement Serialize(ILanguage language);
+    XElement Serialize(ITemplate template);
 
-        /// 
-        /// Exports a list of  items to xml as an 
-        /// 
-        /// 
-        /// 
-        XElement Serialize(IEnumerable templates);
+    /// 
+    ///     Exports a list of  items to xml as an 
+    /// 
+    /// 
+    /// 
+    XElement Serialize(IEnumerable templates);
 
-        XElement Serialize(IMediaType mediaType);
+    XElement Serialize(IMediaType mediaType);
 
-        /// 
-        /// Exports a list of  items to xml as an 
-        /// 
-        /// Macros to export
-        ///  containing the xml representation of the IMacro objects
-        XElement Serialize(IEnumerable macros);
+    /// 
+    ///     Exports a list of  items to xml as an 
+    /// 
+    /// Macros to export
+    ///  containing the xml representation of the IMacro objects
+    XElement Serialize(IEnumerable macros);
 
-        XElement Serialize(IMacro macro);
-        XElement Serialize(IContentType contentType);
-    }
+    XElement Serialize(IMacro macro);
+    XElement Serialize(IContentType contentType);
 }
diff --git a/src/Umbraco.Core/Services/IExamineIndexCountService.cs b/src/Umbraco.Core/Services/IExamineIndexCountService.cs
index 05c5f7d5548f..cd7f5848ff51 100644
--- a/src/Umbraco.Core/Services/IExamineIndexCountService.cs
+++ b/src/Umbraco.Core/Services/IExamineIndexCountService.cs
@@ -1,7 +1,6 @@
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface IExamineIndexCountService
 {
-    public interface IExamineIndexCountService
-    {
-        public int GetCount();
-    }
+    public int GetCount();
 }
diff --git a/src/Umbraco.Core/Services/IExternalLoginService.cs b/src/Umbraco.Core/Services/IExternalLoginService.cs
index 75f8069f0c2b..ba75d505ffa5 100644
--- a/src/Umbraco.Core/Services/IExternalLoginService.cs
+++ b/src/Umbraco.Core/Services/IExternalLoginService.cs
@@ -1,66 +1,63 @@
-using System;
-using System.Collections.Generic;
 using Umbraco.Cms.Core.Security;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Used to store the external login info
+/// 
+[Obsolete("Use IExternalLoginServiceWithKey. This will be removed in Umbraco 10")]
+public interface IExternalLoginService : IService
 {
     /// 
-    /// Used to store the external login info
+    ///     Returns all user logins assigned
     /// 
-    [Obsolete("Use IExternalLoginServiceWithKey. This will be removed in Umbraco 10")]
-    public interface IExternalLoginService : IService
-    {
-        /// 
-        /// Returns all user logins assigned
-        /// 
-        /// 
-        /// 
-        IEnumerable GetExternalLogins(int userId);
+    /// 
+    /// 
+    IEnumerable GetExternalLogins(int userId);
 
-        /// 
-        /// Returns all user login tokens assigned
-        /// 
-        /// 
-        /// 
-        IEnumerable GetExternalLoginTokens(int userId);
+    /// 
+    ///     Returns all user login tokens assigned
+    /// 
+    /// 
+    /// 
+    IEnumerable GetExternalLoginTokens(int userId);
 
-        /// 
-        /// Returns all logins matching the login info - generally there should only be one but in some cases
-        /// there might be more than one depending on if an administrator has been editing/removing members
-        /// 
-        /// 
-        /// 
-        /// 
-        IEnumerable Find(string loginProvider, string providerKey);
+    /// 
+    ///     Returns all logins matching the login info - generally there should only be one but in some cases
+    ///     there might be more than one depending on if an administrator has been editing/removing members
+    /// 
+    /// 
+    /// 
+    /// 
+    IEnumerable Find(string loginProvider, string providerKey);
 
-        /// 
-        /// Saves the external logins associated with the user
-        /// 
-        /// 
-        /// The user associated with the logins
-        /// 
-        /// 
-        /// 
-        /// This will replace all external login provider information for the user
-        /// 
-        void Save(int userId, IEnumerable logins);
+    /// 
+    ///     Saves the external logins associated with the user
+    /// 
+    /// 
+    ///     The user associated with the logins
+    /// 
+    /// 
+    /// 
+    ///     This will replace all external login provider information for the user
+    /// 
+    void Save(int userId, IEnumerable logins);
 
-        /// 
-        /// Saves the external login tokens associated with the user
-        /// 
-        /// 
-        /// The user associated with the tokens
-        /// 
-        /// 
-        /// 
-        /// This will replace all external login tokens for the user
-        /// 
-        void Save(int userId, IEnumerable tokens);
+    /// 
+    ///     Saves the external login tokens associated with the user
+    /// 
+    /// 
+    ///     The user associated with the tokens
+    /// 
+    /// 
+    /// 
+    ///     This will replace all external login tokens for the user
+    /// 
+    void Save(int userId, IEnumerable tokens);
 
-        /// 
-        /// Deletes all user logins - normally used when a member is deleted
-        /// 
-        /// 
-        void DeleteUserLogins(int userId);
-    }
+    /// 
+    ///     Deletes all user logins - normally used when a member is deleted
+    /// 
+    /// 
+    void DeleteUserLogins(int userId);
 }
diff --git a/src/Umbraco.Core/Services/IExternalLoginWithKeyService.cs b/src/Umbraco.Core/Services/IExternalLoginWithKeyService.cs
index bc31f54f8b26..54f827c899a1 100644
--- a/src/Umbraco.Core/Services/IExternalLoginWithKeyService.cs
+++ b/src/Umbraco.Core/Services/IExternalLoginWithKeyService.cs
@@ -1,54 +1,51 @@
-using System;
-using System.Collections.Generic;
 using Umbraco.Cms.Core.Security;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface IExternalLoginWithKeyService : IService
 {
-    public interface IExternalLoginWithKeyService : IService
-    {
-        /// 
-        /// Returns all user logins assigned
-        /// 
-        IEnumerable GetExternalLogins(Guid userOrMemberKey);
+    /// 
+    ///     Returns all user logins assigned
+    /// 
+    IEnumerable GetExternalLogins(Guid userOrMemberKey);
 
-        /// 
-        /// Returns all user login tokens assigned
-        /// 
-        IEnumerable GetExternalLoginTokens(Guid userOrMemberKey);
+    /// 
+    ///     Returns all user login tokens assigned
+    /// 
+    IEnumerable GetExternalLoginTokens(Guid userOrMemberKey);
 
-        /// 
-        /// Returns all logins matching the login info - generally there should only be one but in some cases
-        /// there might be more than one depending on if an administrator has been editing/removing members
-        /// 
-        IEnumerable Find(string loginProvider, string providerKey);
+    /// 
+    ///     Returns all logins matching the login info - generally there should only be one but in some cases
+    ///     there might be more than one depending on if an administrator has been editing/removing members
+    /// 
+    IEnumerable Find(string loginProvider, string providerKey);
 
-        /// 
-        /// Saves the external logins associated with the user
-        /// 
-        /// 
-        /// The user or member key associated with the logins
-        /// 
-        /// 
-        /// 
-        /// This will replace all external login provider information for the user
-        /// 
-        void Save(Guid userOrMemberKey, IEnumerable logins);
+    /// 
+    ///     Saves the external logins associated with the user
+    /// 
+    /// 
+    ///     The user or member key associated with the logins
+    /// 
+    /// 
+    /// 
+    ///     This will replace all external login provider information for the user
+    /// 
+    void Save(Guid userOrMemberKey, IEnumerable logins);
 
-        /// 
-        /// Saves the external login tokens associated with the user
-        /// 
-        /// 
-        /// The user or member key associated with the logins
-        /// 
-        /// 
-        /// 
-        /// This will replace all external login tokens for the user
-        /// 
-        void Save(Guid userOrMemberKey,IEnumerable tokens);
+    /// 
+    ///     Saves the external login tokens associated with the user
+    /// 
+    /// 
+    ///     The user or member key associated with the logins
+    /// 
+    /// 
+    /// 
+    ///     This will replace all external login tokens for the user
+    /// 
+    void Save(Guid userOrMemberKey, IEnumerable tokens);
 
-        /// 
-        /// Deletes all user logins - normally used when a member is deleted
-        /// 
-        void DeleteUserLogins(Guid userOrMemberKey);
-    }
+    /// 
+    ///     Deletes all user logins - normally used when a member is deleted
+    /// 
+    void DeleteUserLogins(Guid userOrMemberKey);
 }
diff --git a/src/Umbraco.Core/Services/IFileService.cs b/src/Umbraco.Core/Services/IFileService.cs
index 8d68c20c8187..1e8980894dc5 100644
--- a/src/Umbraco.Core/Services/IFileService.cs
+++ b/src/Umbraco.Core/Services/IFileService.cs
@@ -1,307 +1,311 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
 using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Defines the File Service, which is an easy access to operations involving  objects like
+///     Scripts, Stylesheets and Templates
+/// 
+public interface IFileService : IService
 {
+    IEnumerable GetPartialViewSnippetNames(params string[] filterNames);
+    void CreatePartialViewFolder(string folderPath);
+    void CreatePartialViewMacroFolder(string folderPath);
+    void DeletePartialViewFolder(string folderPath);
+    void DeletePartialViewMacroFolder(string folderPath);
+
+    /// 
+    ///     Gets a list of all  objects
+    /// 
+    /// An enumerable list of  objects
+    IEnumerable GetPartialViews(params string[] names);
+
+    IPartialView? GetPartialView(string path);
+    IPartialView? GetPartialViewMacro(string path);
+
+    Attempt CreatePartialView(IPartialView partialView, string? snippetName = null,
+        int? userId = Constants.Security.SuperUserId);
+
+    Attempt CreatePartialViewMacro(IPartialView partialView, string? snippetName = null,
+        int? userId = Constants.Security.SuperUserId);
+
+    bool DeletePartialView(string path, int? userId = null);
+    bool DeletePartialViewMacro(string path, int? userId = null);
+    Attempt SavePartialView(IPartialView partialView, int? userId = null);
+    Attempt SavePartialViewMacro(IPartialView partialView, int? userId = null);
+
+    /// 
+    ///     Gets the content of a partial view as a stream.
+    /// 
+    /// The filesystem path to the partial view.
+    /// The content of the partial view.
+    Stream GetPartialViewFileContentStream(string filepath);
+
+    /// 
+    ///     Sets the content of a partial view.
+    /// 
+    /// The filesystem path to the partial view.
+    /// The content of the partial view.
+    void SetPartialViewFileContent(string filepath, Stream content);
+
+    /// 
+    ///     Gets the size of a partial view.
+    /// 
+    /// The filesystem path to the partial view.
+    /// The size of the partial view.
+    long GetPartialViewFileSize(string filepath);
+
+    /// 
+    ///     Gets the content of a macro partial view as a stream.
+    /// 
+    /// The filesystem path to the macro partial view.
+    /// The content of the macro partial view.
+    Stream GetPartialViewMacroFileContentStream(string filepath);
+
+    /// 
+    ///     Sets the content of a macro partial view.
+    /// 
+    /// The filesystem path to the macro partial view.
+    /// The content of the macro partial view.
+    void SetPartialViewMacroFileContent(string filepath, Stream content);
+
+    /// 
+    ///     Gets the size of a macro partial view.
+    /// 
+    /// The filesystem path to the macro partial view.
+    /// The size of the macro partial view.
+    long GetPartialViewMacroFileSize(string filepath);
+
+    /// 
+    ///     Gets a list of all  objects
+    /// 
+    /// An enumerable list of  objects
+    IEnumerable GetStylesheets(params string[] paths);
+
+    /// 
+    ///     Gets a  object by its name
+    /// 
+    /// Path of the stylesheet incl. extension
+    /// A  object
+    IStylesheet? GetStylesheet(string? path);
+
+    /// 
+    ///     Saves a 
+    /// 
+    ///  to save
+    /// Optional id of the user saving the stylesheet
+    void SaveStylesheet(IStylesheet? stylesheet, int? userId = null);
+
+    /// 
+    ///     Deletes a stylesheet by its name
+    /// 
+    /// Name incl. extension of the Stylesheet to delete
+    /// Optional id of the user deleting the stylesheet
+    void DeleteStylesheet(string path, int? userId = null);
+
+    /// 
+    ///     Creates a folder for style sheets
+    /// 
+    /// 
+    /// 
+    void CreateStyleSheetFolder(string folderPath);
+
+    /// 
+    ///     Deletes a folder for style sheets
+    /// 
+    /// 
+    void DeleteStyleSheetFolder(string folderPath);
+
+    /// 
+    ///     Gets the content of a stylesheet as a stream.
+    /// 
+    /// The filesystem path to the stylesheet.
+    /// The content of the stylesheet.
+    Stream GetStylesheetFileContentStream(string filepath);
+
+    /// 
+    ///     Sets the content of a stylesheet.
+    /// 
+    /// The filesystem path to the stylesheet.
+    /// The content of the stylesheet.
+    void SetStylesheetFileContent(string filepath, Stream content);
+
+    /// 
+    ///     Gets the size of a stylesheet.
+    /// 
+    /// The filesystem path to the stylesheet.
+    /// The size of the stylesheet.
+    long GetStylesheetFileSize(string filepath);
+
+    /// 
+    ///     Gets a list of all  objects
+    /// 
+    /// An enumerable list of  objects
+    IEnumerable GetScripts(params string[] names);
+
+    /// 
+    ///     Gets a  object by its name
+    /// 
+    /// Name of the script incl. extension
+    /// A  object
+    IScript? GetScript(string? name);
+
+    /// 
+    ///     Saves a 
+    /// 
+    ///  to save
+    /// Optional id of the user saving the script
+    void SaveScript(IScript? script, int? userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Deletes a script by its name
+    /// 
+    /// Name incl. extension of the Script to delete
+    /// Optional id of the user deleting the script
+    void DeleteScript(string path, int? userId = null);
+
+    /// 
+    ///     Creates a folder for scripts
+    /// 
+    /// 
+    /// 
+    void CreateScriptFolder(string folderPath);
+
+    /// 
+    ///     Deletes a folder for scripts
+    /// 
+    /// 
+    void DeleteScriptFolder(string folderPath);
+
+    /// 
+    ///     Gets the content of a script file as a stream.
+    /// 
+    /// The filesystem path to the script.
+    /// The content of the script file.
+    Stream GetScriptFileContentStream(string filepath);
+
+    /// 
+    ///     Sets the content of a script file.
+    /// 
+    /// The filesystem path to the script.
+    /// The content of the script file.
+    void SetScriptFileContent(string filepath, Stream content);
+
+    /// 
+    ///     Gets the size of a script file.
+    /// 
+    /// The filesystem path to the script file.
+    /// The size of the script file.
+    long GetScriptFileSize(string filepath);
+
+    /// 
+    ///     Gets a list of all  objects
+    /// 
+    /// An enumerable list of  objects
+    IEnumerable? GetTemplates(params string[] aliases);
+
+    /// 
+    ///     Gets a list of all  objects
+    /// 
+    /// An enumerable list of  objects
+    IEnumerable? GetTemplates(int masterTemplateId);
+
+    /// 
+    ///     Gets a  object by its alias.
+    /// 
+    /// The alias of the template.
+    /// The  object matching the alias, or null.
+    ITemplate? GetTemplate(string? alias);
+
+    /// 
+    ///     Gets a  object by its identifier.
+    /// 
+    /// The identifier of the template.
+    /// The  object matching the identifier, or null.
+    ITemplate? GetTemplate(int id);
+
     /// 
-    /// Defines the File Service, which is an easy access to operations involving  objects like Scripts, Stylesheets and Templates
-    /// 
-    public interface IFileService : IService
-    {
-        IEnumerable GetPartialViewSnippetNames(params string[] filterNames);
-        void CreatePartialViewFolder(string folderPath);
-        void CreatePartialViewMacroFolder(string folderPath);
-        void DeletePartialViewFolder(string folderPath);
-        void DeletePartialViewMacroFolder(string folderPath);
-
-        /// 
-        /// Gets a list of all  objects
-        /// 
-        /// An enumerable list of  objects
-        IEnumerable GetPartialViews(params string[] names);
-
-        IPartialView? GetPartialView(string path);
-        IPartialView? GetPartialViewMacro(string path);
-        Attempt CreatePartialView(IPartialView partialView, string? snippetName = null, int? userId = Constants.Security.SuperUserId);
-        Attempt CreatePartialViewMacro(IPartialView partialView, string? snippetName = null, int? userId = Constants.Security.SuperUserId);
-        bool DeletePartialView(string path, int? userId = null);
-        bool DeletePartialViewMacro(string path, int? userId = null);
-        Attempt SavePartialView(IPartialView partialView, int? userId = null);
-        Attempt SavePartialViewMacro(IPartialView partialView, int? userId = null);
-
-        /// 
-        /// Gets the content of a partial view as a stream.
-        /// 
-        /// The filesystem path to the partial view.
-        /// The content of the partial view.
-        Stream GetPartialViewFileContentStream(string filepath);
-
-        /// 
-        /// Sets the content of a partial view.
-        /// 
-        /// The filesystem path to the partial view.
-        /// The content of the partial view.
-        void SetPartialViewFileContent(string filepath, Stream content);
-
-        /// 
-        /// Gets the size of a partial view.
-        /// 
-        /// The filesystem path to the partial view.
-        /// The size of the partial view.
-        long GetPartialViewFileSize(string filepath);
-
-        /// 
-        /// Gets the content of a macro partial view as a stream.
-        /// 
-        /// The filesystem path to the macro partial view.
-        /// The content of the macro partial view.
-        Stream GetPartialViewMacroFileContentStream(string filepath);
-
-        /// 
-        /// Sets the content of a macro partial view.
-        /// 
-        /// The filesystem path to the macro partial view.
-        /// The content of the macro partial view.
-        void SetPartialViewMacroFileContent(string filepath, Stream content);
-
-        /// 
-        /// Gets the size of a macro partial view.
-        /// 
-        /// The filesystem path to the macro partial view.
-        /// The size of the macro partial view.
-        long GetPartialViewMacroFileSize(string filepath);
-
-        /// 
-        /// Gets a list of all  objects
-        /// 
-        /// An enumerable list of  objects
-        IEnumerable GetStylesheets(params string[] paths);
-
-        /// 
-        /// Gets a  object by its name
-        /// 
-        /// Path of the stylesheet incl. extension
-        /// A  object
-        IStylesheet? GetStylesheet(string? path);
-
-        /// 
-        /// Saves a 
-        /// 
-        ///  to save
-        /// Optional id of the user saving the stylesheet
-        void SaveStylesheet(IStylesheet? stylesheet, int? userId = null);
-
-        /// 
-        /// Deletes a stylesheet by its name
-        /// 
-        /// Name incl. extension of the Stylesheet to delete
-        /// Optional id of the user deleting the stylesheet
-        void DeleteStylesheet(string path, int? userId = null);
-
-        /// 
-        /// Creates a folder for style sheets
-        /// 
-        /// 
-        /// 
-        void CreateStyleSheetFolder(string folderPath);
-
-        /// 
-        /// Deletes a folder for style sheets
-        /// 
-        /// 
-        void DeleteStyleSheetFolder(string folderPath);
-
-        /// 
-        /// Gets the content of a stylesheet as a stream.
-        /// 
-        /// The filesystem path to the stylesheet.
-        /// The content of the stylesheet.
-        Stream GetStylesheetFileContentStream(string filepath);
-
-        /// 
-        /// Sets the content of a stylesheet.
-        /// 
-        /// The filesystem path to the stylesheet.
-        /// The content of the stylesheet.
-        void SetStylesheetFileContent(string filepath, Stream content);
-
-        /// 
-        /// Gets the size of a stylesheet.
-        /// 
-        /// The filesystem path to the stylesheet.
-        /// The size of the stylesheet.
-        long GetStylesheetFileSize(string filepath);
-
-        /// 
-        /// Gets a list of all  objects
-        /// 
-        /// An enumerable list of  objects
-        IEnumerable GetScripts(params string[] names);
-
-        /// 
-        /// Gets a  object by its name
-        /// 
-        /// Name of the script incl. extension
-        /// A  object
-        IScript? GetScript(string? name);
-
-        /// 
-        /// Saves a 
-        /// 
-        ///  to save
-        /// Optional id of the user saving the script
-        void SaveScript(IScript? script, int? userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Deletes a script by its name
-        /// 
-        /// Name incl. extension of the Script to delete
-        /// Optional id of the user deleting the script
-        void DeleteScript(string path, int? userId = null);
-
-        /// 
-        /// Creates a folder for scripts
-        /// 
-        /// 
-        /// 
-        void CreateScriptFolder(string folderPath);
-
-        /// 
-        /// Deletes a folder for scripts
-        /// 
-        /// 
-        void DeleteScriptFolder(string folderPath);
-
-        /// 
-        /// Gets the content of a script file as a stream.
-        /// 
-        /// The filesystem path to the script.
-        /// The content of the script file.
-        Stream GetScriptFileContentStream(string filepath);
-
-        /// 
-        /// Sets the content of a script file.
-        /// 
-        /// The filesystem path to the script.
-        /// The content of the script file.
-        void SetScriptFileContent(string filepath, Stream content);
-
-        /// 
-        /// Gets the size of a script file.
-        /// 
-        /// The filesystem path to the script file.
-        /// The size of the script file.
-        long GetScriptFileSize(string filepath);
-
-        /// 
-        /// Gets a list of all  objects
-        /// 
-        /// An enumerable list of  objects
-        IEnumerable? GetTemplates(params string[] aliases);
-
-        /// 
-        /// Gets a list of all  objects
-        /// 
-        /// An enumerable list of  objects
-        IEnumerable? GetTemplates(int masterTemplateId);
-
-        /// 
-        /// Gets a  object by its alias.
-        /// 
-        /// The alias of the template.
-        /// The  object matching the alias, or null.
-        ITemplate? GetTemplate(string? alias);
-
-        /// 
-        /// Gets a  object by its identifier.
-        /// 
-        /// The identifier of the template.
-        /// The  object matching the identifier, or null.
-        ITemplate? GetTemplate(int id);
-
-        /// 
-        /// Gets a  object by its guid identifier.
-        /// 
-        /// The guid identifier of the template.
-        /// The  object matching the identifier, or null.
-        ITemplate? GetTemplate(Guid id);
-
-        /// 
-        /// Gets the template descendants
-        /// 
-        /// 
-        /// 
-        IEnumerable GetTemplateDescendants(int masterTemplateId);
-
-        /// 
-        /// Saves a 
-        /// 
-        ///  to save
-        /// Optional id of the user saving the template
-        void SaveTemplate(ITemplate template, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Creates a template for a content type
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// The template created
-        /// 
-        Attempt?> CreateTemplateForContentType(string contentTypeAlias, string? contentTypeName, int userId = Constants.Security.SuperUserId);
-
-        ITemplate CreateTemplateWithIdentity(string? name, string? alias, string? content, ITemplate? masterTemplate = null, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Deletes a template by its alias
-        /// 
-        /// Alias of the  to delete
-        /// Optional id of the user deleting the template
-        void DeleteTemplate(string alias, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Saves a collection of  objects
-        /// 
-        /// List of  to save
-        /// Optional id of the user
-        void SaveTemplate(IEnumerable templates, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Gets the content of a template as a stream.
-        /// 
-        /// The filesystem path to the template.
-        /// The content of the template.
-        Stream GetTemplateFileContentStream(string filepath);
-
-        /// 
-        /// Sets the content of a template.
-        /// 
-        /// The filesystem path to the template.
-        /// The content of the template.
-        void SetTemplateFileContent(string filepath, Stream content);
-
-        /// 
-        /// Gets the size of a template.
-        /// 
-        /// The filesystem path to the template.
-        /// The size of the template.
-        long GetTemplateFileSize(string filepath);
-
-        /// 
-        /// Gets the content of a macro partial view snippet as a string
-        /// 
-        /// The name of the snippet
-        /// 
-        string GetPartialViewMacroSnippetContent(string snippetName);
-
-        /// 
-        /// Gets the content of a partial view snippet as a string.
-        /// 
-        /// The name of the snippet
-        /// The content of the partial view.
-        string GetPartialViewSnippetContent(string snippetName);
-    }
+    ///     Gets a  object by its guid identifier.
+    /// 
+    /// The guid identifier of the template.
+    /// The  object matching the identifier, or null.
+    ITemplate? GetTemplate(Guid id);
+
+    /// 
+    ///     Gets the template descendants
+    /// 
+    /// 
+    /// 
+    IEnumerable GetTemplateDescendants(int masterTemplateId);
+
+    /// 
+    ///     Saves a 
+    /// 
+    ///  to save
+    /// Optional id of the user saving the template
+    void SaveTemplate(ITemplate template, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Creates a template for a content type
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    ///     The template created
+    /// 
+    Attempt?> CreateTemplateForContentType(string contentTypeAlias,
+        string? contentTypeName, int userId = Constants.Security.SuperUserId);
+
+    ITemplate CreateTemplateWithIdentity(string? name, string? alias, string? content, ITemplate? masterTemplate = null,
+        int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Deletes a template by its alias
+    /// 
+    /// Alias of the  to delete
+    /// Optional id of the user deleting the template
+    void DeleteTemplate(string alias, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Saves a collection of  objects
+    /// 
+    /// List of  to save
+    /// Optional id of the user
+    void SaveTemplate(IEnumerable templates, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Gets the content of a template as a stream.
+    /// 
+    /// The filesystem path to the template.
+    /// The content of the template.
+    Stream GetTemplateFileContentStream(string filepath);
+
+    /// 
+    ///     Sets the content of a template.
+    /// 
+    /// The filesystem path to the template.
+    /// The content of the template.
+    void SetTemplateFileContent(string filepath, Stream content);
+
+    /// 
+    ///     Gets the size of a template.
+    /// 
+    /// The filesystem path to the template.
+    /// The size of the template.
+    long GetTemplateFileSize(string filepath);
+
+    /// 
+    ///     Gets the content of a macro partial view snippet as a string
+    /// 
+    /// The name of the snippet
+    /// 
+    string GetPartialViewMacroSnippetContent(string snippetName);
+
+    /// 
+    ///     Gets the content of a partial view snippet as a string.
+    /// 
+    /// The name of the snippet
+    /// The content of the partial view.
+    string GetPartialViewSnippetContent(string snippetName);
 }
diff --git a/src/Umbraco.Core/Services/IIconService.cs b/src/Umbraco.Core/Services/IIconService.cs
index 0b215c481cc8..8aff7e8920ea 100644
--- a/src/Umbraco.Core/Services/IIconService.cs
+++ b/src/Umbraco.Core/Services/IIconService.cs
@@ -1,23 +1,19 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
 using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface IIconService
 {
-    public interface IIconService
-    {
-        /// 
-        /// Gets the svg string for the icon name found at the global icons path
-        /// 
-        /// 
-        /// 
-        IconModel? GetIcon(string iconName);
+    /// 
+    ///     Gets the svg string for the icon name found at the global icons path
+    /// 
+    /// 
+    /// 
+    IconModel? GetIcon(string iconName);
 
-        /// 
-        /// Gets a list of all svg icons found at at the global icons path.
-        /// 
-        /// 
-        IReadOnlyDictionary? GetIcons();
-    }
+    /// 
+    ///     Gets a list of all svg icons found at at the global icons path.
+    /// 
+    /// 
+    IReadOnlyDictionary? GetIcons();
 }
diff --git a/src/Umbraco.Core/Services/IIdKeyMap.cs b/src/Umbraco.Core/Services/IIdKeyMap.cs
index 199ee23813d9..8318261fa6e0 100644
--- a/src/Umbraco.Core/Services/IIdKeyMap.cs
+++ b/src/Umbraco.Core/Services/IIdKeyMap.cs
@@ -1,16 +1,14 @@
-using System;
 using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface IIdKeyMap
 {
-    public interface IIdKeyMap
-    {
-        Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType);
-        Attempt GetIdForUdi(Udi udi);
-        Attempt GetUdiForId(int id, UmbracoObjectTypes umbracoObjectType);
-        Attempt GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType);
-        void ClearCache();
-        void ClearCache(int id);
-        void ClearCache(Guid key);
-    }
+    Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType);
+    Attempt GetIdForUdi(Udi udi);
+    Attempt GetUdiForId(int id, UmbracoObjectTypes umbracoObjectType);
+    Attempt GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType);
+    void ClearCache();
+    void ClearCache(int id);
+    void ClearCache(Guid key);
 }
diff --git a/src/Umbraco.Core/Services/IInstallationService.cs b/src/Umbraco.Core/Services/IInstallationService.cs
index 5b1d28cccc34..8c238f83c41c 100644
--- a/src/Umbraco.Core/Services/IInstallationService.cs
+++ b/src/Umbraco.Core/Services/IInstallationService.cs
@@ -1,9 +1,6 @@
-using System.Threading.Tasks;
+namespace Umbraco.Cms.Core.Services;
 
-namespace Umbraco.Cms.Core.Services
+public interface IInstallationService
 {
-    public interface IInstallationService
-    {
-        Task LogInstall(InstallLog installLog);
-    }
+    Task LogInstall(InstallLog installLog);
 }
diff --git a/src/Umbraco.Core/Services/IKeyValueService.cs b/src/Umbraco.Core/Services/IKeyValueService.cs
index 1ebf6e972886..97316911c464 100644
--- a/src/Umbraco.Core/Services/IKeyValueService.cs
+++ b/src/Umbraco.Core/Services/IKeyValueService.cs
@@ -1,45 +1,45 @@
-using System.Collections;
-using System.Collections.Generic;
+namespace Umbraco.Cms.Core.Services;
 
-namespace Umbraco.Cms.Core.Services
+/// 
+///     Manages the simplified key/value store.
+/// 
+public interface IKeyValueService
 {
     /// 
-    /// Manages the simplified key/value store.
+    ///     Gets a value.
     /// 
-    public interface IKeyValueService
-    {
-        /// 
-        /// Gets a value.
-        /// 
-        /// Returns null if no value was found for the key.
-        string? GetValue(string key);
+    /// Returns null if no value was found for the key.
+    string? GetValue(string key);
 
-        /// 
-        /// Returns key/value pairs for all keys with the specified prefix.
-        /// 
-        /// 
-        /// 
-        IReadOnlyDictionary? FindByKeyPrefix(string keyPrefix);
+    /// 
+    ///     Returns key/value pairs for all keys with the specified prefix.
+    /// 
+    /// 
+    /// 
+    IReadOnlyDictionary? FindByKeyPrefix(string keyPrefix);
 
-        /// 
-        /// Sets a value.
-        /// 
-        void SetValue(string key, string value);
+    /// 
+    ///     Sets a value.
+    /// 
+    void SetValue(string key, string value);
 
-        /// 
-        /// Sets a value.
-        /// 
-        /// Sets the value to  if the value is ,
-        /// and returns true; otherwise throws an exception. In other words, ensures that the value has not changed
-        /// before setting it.
-        void SetValue(string key, string originValue, string newValue);
+    /// 
+    ///     Sets a value.
+    /// 
+    /// 
+    ///     Sets the value to  if the value is ,
+    ///     and returns true; otherwise throws an exception. In other words, ensures that the value has not changed
+    ///     before setting it.
+    /// 
+    void SetValue(string key, string originValue, string newValue);
 
-        /// 
-        /// Tries to set a value.
-        /// 
-        /// Sets the value to  if the value is ,
-        /// and returns true; otherwise returns false. In other words, ensures that the value has not changed
-        /// before setting it.
-        bool TrySetValue(string key, string originValue, string newValue);
-    }
+    /// 
+    ///     Tries to set a value.
+    /// 
+    /// 
+    ///     Sets the value to  if the value is ,
+    ///     and returns true; otherwise returns false. In other words, ensures that the value has not changed
+    ///     before setting it.
+    /// 
+    bool TrySetValue(string key, string originValue, string newValue);
 }
diff --git a/src/Umbraco.Core/Services/ILocalizationService.cs b/src/Umbraco.Core/Services/ILocalizationService.cs
index f68214cfc35f..4bd52d16baf7 100644
--- a/src/Umbraco.Core/Services/ILocalizationService.cs
+++ b/src/Umbraco.Core/Services/ILocalizationService.cs
@@ -1,171 +1,178 @@
-using System;
-using System.Collections.Generic;
-using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Defines the Localization Service, which is an easy access to operations involving Languages and Dictionary
+/// 
+public interface ILocalizationService : IService
 {
+    //Possible to-do list:
+    //Import DictionaryItem (?)
+    //RemoveByLanguage (translations)
+    //Add/Set Text (Insert/Update)
+    //Remove Text (in translation)
+
+    /// 
+    ///     Adds or updates a translation for a dictionary item and language
+    /// 
+    /// 
+    /// 
+    /// 
+    void AddOrUpdateDictionaryValue(IDictionaryItem item, ILanguage? language, string value);
+
+    /// 
+    ///     Creates and saves a new dictionary item and assigns a value to all languages if defaultValue is specified.
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    IDictionaryItem CreateDictionaryItemWithIdentity(string key, Guid? parentId, string? defaultValue = null);
+
+    /// 
+    ///     Gets a  by its  id
+    /// 
+    /// Id of the 
+    /// 
+    ///     
+    /// 
+    IDictionaryItem? GetDictionaryItemById(int id);
+
+    /// 
+    ///     Gets a  by its  id
+    /// 
+    /// Id of the 
+    /// 
+    ///     
+    /// 
+    IDictionaryItem? GetDictionaryItemById(Guid id);
+
+    /// 
+    ///     Gets a  by its key
+    /// 
+    /// Key of the 
+    /// 
+    ///     
+    /// 
+    IDictionaryItem? GetDictionaryItemByKey(string key);
+
+    /// 
+    ///     Gets a list of children for a 
+    /// 
+    /// Id of the parent
+    /// An enumerable list of  objects
+    IEnumerable? GetDictionaryItemChildren(Guid parentId);
+
+    /// 
+    ///     Gets a list of descendants for a 
+    /// 
+    /// Id of the parent, null will return all dictionary items
+    /// An enumerable list of  objects
+    IEnumerable GetDictionaryItemDescendants(Guid? parentId);
+
+    /// 
+    ///     Gets the root/top  objects
+    /// 
+    /// An enumerable list of  objects
+    IEnumerable? GetRootDictionaryItems();
+
+    /// 
+    ///     Checks if a  with given key exists
+    /// 
+    /// Key of the 
+    /// True if a  exists, otherwise false
+    bool DictionaryItemExists(string key);
+
+    /// 
+    ///     Saves a  object
+    /// 
+    ///  to save
+    /// Optional id of the user saving the dictionary item
+    void Save(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId);
+
     /// 
-    /// Defines the Localization Service, which is an easy access to operations involving Languages and Dictionary
-    /// 
-    public interface ILocalizationService : IService
-    {
-        //Possible to-do list:
-        //Import DictionaryItem (?)
-        //RemoveByLanguage (translations)
-        //Add/Set Text (Insert/Update)
-        //Remove Text (in translation)
-
-        /// 
-        /// Adds or updates a translation for a dictionary item and language
-        /// 
-        /// 
-        /// 
-        /// 
-        void AddOrUpdateDictionaryValue(IDictionaryItem item, ILanguage? language, string value);
-
-        /// 
-        /// Creates and saves a new dictionary item and assigns a value to all languages if defaultValue is specified.
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        IDictionaryItem CreateDictionaryItemWithIdentity(string key, Guid? parentId, string? defaultValue = null);
-
-        /// 
-        /// Gets a  by its  id
-        /// 
-        /// Id of the 
-        /// 
-        IDictionaryItem? GetDictionaryItemById(int id);
-
-        /// 
-        /// Gets a  by its  id
-        /// 
-        /// Id of the 
-        /// 
-        IDictionaryItem? GetDictionaryItemById(Guid id);
-
-        /// 
-        /// Gets a  by its key
-        /// 
-        /// Key of the 
-        /// 
-        IDictionaryItem? GetDictionaryItemByKey(string key);
-
-        /// 
-        /// Gets a list of children for a 
-        /// 
-        /// Id of the parent
-        /// An enumerable list of  objects
-        IEnumerable? GetDictionaryItemChildren(Guid parentId);
-
-        /// 
-        /// Gets a list of descendants for a 
-        /// 
-        /// Id of the parent, null will return all dictionary items
-        /// An enumerable list of  objects
-        IEnumerable GetDictionaryItemDescendants(Guid? parentId);
-
-        /// 
-        /// Gets the root/top  objects
-        /// 
-        /// An enumerable list of  objects
-        IEnumerable? GetRootDictionaryItems();
-
-        /// 
-        /// Checks if a  with given key exists
-        /// 
-        /// Key of the 
-        /// True if a  exists, otherwise false
-        bool DictionaryItemExists(string key);
-
-        /// 
-        /// Saves a  object
-        /// 
-        ///  to save
-        /// Optional id of the user saving the dictionary item
-        void Save(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Deletes a  object and its related translations
-        /// as well as its children.
-        /// 
-        ///  to delete
-        /// Optional id of the user deleting the dictionary item
-        void Delete(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Gets a  by its id
-        /// 
-        /// Id of the 
-        /// 
-        ILanguage? GetLanguageById(int id);
-
-        /// 
-        /// Gets a  by its iso code
-        /// 
-        /// Iso Code of the language (ie. en-US)
-        /// 
-        ILanguage? GetLanguageByIsoCode(string? isoCode);
-
-        /// 
-        /// Gets a language identifier from its ISO code.
-        /// 
-        /// 
-        /// This can be optimized and bypass all deep cloning.
-        /// 
-        int? GetLanguageIdByIsoCode(string isoCode);
-
-        /// 
-        /// Gets a language ISO code from its identifier.
-        /// 
-        /// 
-        /// This can be optimized and bypass all deep cloning.
-        /// 
-        string? GetLanguageIsoCodeById(int id);
-
-        /// 
-        /// Gets the default language ISO code.
-        /// 
-        /// 
-        /// This can be optimized and bypass all deep cloning.
-        /// 
-        string GetDefaultLanguageIsoCode();
-
-        /// 
-        /// Gets the default language identifier.
-        /// 
-        /// 
-        /// This can be optimized and bypass all deep cloning.
-        /// 
-        int? GetDefaultLanguageId();
-
-        /// 
-        /// Gets all available languages
-        /// 
-        /// An enumerable list of  objects
-        IEnumerable GetAllLanguages();
-
-        /// 
-        /// Saves a  object
-        /// 
-        ///  to save
-        /// Optional id of the user saving the language
-        void Save(ILanguage language, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Deletes a  by removing it and its usages from the db
-        /// 
-        ///  to delete
-        /// Optional id of the user deleting the language
-        void Delete(ILanguage language, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Gets the full dictionary key map.
-        /// 
-        /// The full dictionary key map.
-        Dictionary GetDictionaryItemKeyMap();
-    }
+    ///     Deletes a  object and its related translations
+    ///     as well as its children.
+    /// 
+    ///  to delete
+    /// Optional id of the user deleting the dictionary item
+    void Delete(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Gets a  by its id
+    /// 
+    /// Id of the 
+    /// 
+    ///     
+    /// 
+    ILanguage? GetLanguageById(int id);
+
+    /// 
+    ///     Gets a  by its iso code
+    /// 
+    /// Iso Code of the language (ie. en-US)
+    /// 
+    ///     
+    /// 
+    ILanguage? GetLanguageByIsoCode(string? isoCode);
+
+    /// 
+    ///     Gets a language identifier from its ISO code.
+    /// 
+    /// 
+    ///     This can be optimized and bypass all deep cloning.
+    /// 
+    int? GetLanguageIdByIsoCode(string isoCode);
+
+    /// 
+    ///     Gets a language ISO code from its identifier.
+    /// 
+    /// 
+    ///     This can be optimized and bypass all deep cloning.
+    /// 
+    string? GetLanguageIsoCodeById(int id);
+
+    /// 
+    ///     Gets the default language ISO code.
+    /// 
+    /// 
+    ///     This can be optimized and bypass all deep cloning.
+    /// 
+    string GetDefaultLanguageIsoCode();
+
+    /// 
+    ///     Gets the default language identifier.
+    /// 
+    /// 
+    ///     This can be optimized and bypass all deep cloning.
+    /// 
+    int? GetDefaultLanguageId();
+
+    /// 
+    ///     Gets all available languages
+    /// 
+    /// An enumerable list of  objects
+    IEnumerable GetAllLanguages();
+
+    /// 
+    ///     Saves a  object
+    /// 
+    ///  to save
+    /// Optional id of the user saving the language
+    void Save(ILanguage language, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Deletes a  by removing it and its usages from the db
+    /// 
+    ///  to delete
+    /// Optional id of the user deleting the language
+    void Delete(ILanguage language, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Gets the full dictionary key map.
+    /// 
+    /// The full dictionary key map.
+    Dictionary GetDictionaryItemKeyMap();
 }
diff --git a/src/Umbraco.Core/Services/ILocalizedTextService.cs b/src/Umbraco.Core/Services/ILocalizedTextService.cs
index c49a4e6b2f0b..5b5cdb1fb322 100644
--- a/src/Umbraco.Core/Services/ILocalizedTextService.cs
+++ b/src/Umbraco.Core/Services/ILocalizedTextService.cs
@@ -1,62 +1,62 @@
-using System.Collections.Generic;
-using System.Globalization;
+using System.Globalization;
 
-namespace Umbraco.Cms.Core.Services
-{
-    // TODO: This needs to be merged into one interface in v9, but better yet
-    // the Localize method should just the based on area + alias and we should remove
-    // the one with the 'key' (the concatenated area/alias) to ensure that we never use that again.
+namespace Umbraco.Cms.Core.Services;
+// TODO: This needs to be merged into one interface in v9, but better yet
+// the Localize method should just the based on area + alias and we should remove
+// the one with the 'key' (the concatenated area/alias) to ensure that we never use that again.
 
+/// 
+///     The entry point to localize any key in the text storage source for a given culture
+/// 
+/// 
+///     This class is created to be as simple as possible so that it can be replaced very easily,
+///     all other methods are extension methods that simply call the one underlying method in this class
+/// 
+public interface ILocalizedTextService
+{
     /// 
-    /// The entry point to localize any key in the text storage source for a given culture
+    ///     Localize a key with variables
     /// 
-    /// 
-    /// This class is created to be as simple as possible so that it can be replaced very easily,
-    /// all other methods are extension methods that simply call the one underlying method in this class
-    /// 
-    public interface ILocalizedTextService
-    {
-        /// 
-        /// Localize a key with variables
-        /// 
-        /// 
-        /// 
-        /// 
-        /// This can be null
-        /// 
-        string Localize(string? area, string? alias, CultureInfo? culture, IDictionary? tokens = null);
+    /// 
+    /// 
+    /// 
+    /// This can be null
+    /// 
+    string Localize(string? area, string? alias, CultureInfo? culture, IDictionary? tokens = null);
 
 
-        /// 
-        /// Returns all key/values in storage for the given culture
-        /// 
-        /// 
-        IDictionary> GetAllStoredValuesByAreaAndAlias(CultureInfo culture);
+    /// 
+    ///     Returns all key/values in storage for the given culture
+    /// 
+    /// 
+    IDictionary> GetAllStoredValuesByAreaAndAlias(CultureInfo culture);
 
-        /// 
-        /// Returns all key/values in storage for the given culture
-        /// 
-        /// 
-        IDictionary GetAllStoredValues(CultureInfo culture);
+    /// 
+    ///     Returns all key/values in storage for the given culture
+    /// 
+    /// 
+    IDictionary GetAllStoredValues(CultureInfo culture);
 
-        /// 
-        /// Returns a list of all currently supported cultures
-        /// 
-        /// 
-        IEnumerable GetSupportedCultures();
+    /// 
+    ///     Returns a list of all currently supported cultures
+    /// 
+    /// 
+    IEnumerable GetSupportedCultures();
 
-        /// 
-        /// Tries to resolve a full 4 letter culture from a 2 letter culture name
-        /// 
-        /// 
-        /// The culture to determine if it is only a 2 letter culture, if so we'll try to convert it, otherwise it will just be returned
-        /// 
-        /// 
-        /// 
-        /// TODO: This is just a hack due to the way we store the language files, they should be stored with 4 letters since that
-        /// is what they reference but they are stored with 2, further more our user's languages are stored with 2. So this attempts
-        /// to resolve the full culture if possible.
-        /// 
-        CultureInfo ConvertToSupportedCultureWithRegionCode(CultureInfo currentCulture);
-    }
+    /// 
+    ///     Tries to resolve a full 4 letter culture from a 2 letter culture name
+    /// 
+    /// 
+    ///     The culture to determine if it is only a 2 letter culture, if so we'll try to convert it, otherwise it will just be
+    ///     returned
+    /// 
+    /// 
+    /// 
+    ///     TODO: This is just a hack due to the way we store the language files, they should be stored with 4 letters since
+    ///     that
+    ///     is what they reference but they are stored with 2, further more our user's languages are stored with 2. So this
+    ///     attempts
+    ///     to resolve the full culture if possible.
+    /// 
+    CultureInfo ConvertToSupportedCultureWithRegionCode(CultureInfo currentCulture);
 }
diff --git a/src/Umbraco.Core/Services/IMacroService.cs b/src/Umbraco.Core/Services/IMacroService.cs
index a75547dd6d83..0a1cffad3ebc 100644
--- a/src/Umbraco.Core/Services/IMacroService.cs
+++ b/src/Umbraco.Core/Services/IMacroService.cs
@@ -1,57 +1,53 @@
-using System;
-using System.Collections.Generic;
 using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Defines the MacroService, which is an easy access to operations involving 
+/// 
+public interface IMacroService : IService
 {
     /// 
-    /// Defines the MacroService, which is an easy access to operations involving 
+    ///     Gets an  object by its alias
+    /// 
+    /// Alias to retrieve an  for
+    /// An  object
+    IMacro? GetByAlias(string alias);
+
+    IEnumerable GetAll();
+
+    IEnumerable GetAll(params int[] ids);
+
+    IEnumerable GetAll(params Guid[] ids);
+
+    IMacro? GetById(int id);
+
+    IMacro? GetById(Guid id);
+
+    /// 
+    ///     Deletes an 
+    /// 
+    ///  to delete
+    /// Optional id of the user deleting the macro
+    void Delete(IMacro macro, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Saves an 
     /// 
-    public interface IMacroService : IService
-    {
-
-        /// 
-        /// Gets an  object by its alias
-        /// 
-        /// Alias to retrieve an  for
-        /// An  object
-        IMacro? GetByAlias(string alias);
-
-        IEnumerable GetAll();
-
-        IEnumerable GetAll(params int[] ids);
-
-        IEnumerable GetAll(params Guid[] ids);
-
-        IMacro? GetById(int id);
-
-        IMacro? GetById(Guid id);
-
-        /// 
-        /// Deletes an 
-        /// 
-        ///  to delete
-        /// Optional id of the user deleting the macro
-        void Delete(IMacro macro, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Saves an 
-        /// 
-        ///  to save
-        /// Optional id of the user saving the macro
-        void Save(IMacro macro, int userId = Constants.Security.SuperUserId);
-
-        ///// 
-        ///// Gets a list all available  plugins
-        ///// 
-        ///// An enumerable list of  objects
-        //IEnumerable GetMacroPropertyTypes();
-
-        ///// 
-        ///// Gets an  by its alias
-        ///// 
-        ///// Alias to retrieve an  for
-        ///// An  object
-        //IMacroPropertyType GetMacroPropertyTypeByAlias(string alias);
-    }
+    ///  to save
+    /// Optional id of the user saving the macro
+    void Save(IMacro macro, int userId = Constants.Security.SuperUserId);
+
+    ///// 
+    ///// Gets a list all available  plugins
+    ///// 
+    ///// An enumerable list of  objects
+    //IEnumerable GetMacroPropertyTypes();
+
+    ///// 
+    ///// Gets an  by its alias
+    ///// 
+    ///// Alias to retrieve an  for
+    ///// An  object
+    //IMacroPropertyType GetMacroPropertyTypeByAlias(string alias);
 }
diff --git a/src/Umbraco.Core/Services/IMacroWithAliasService.cs b/src/Umbraco.Core/Services/IMacroWithAliasService.cs
index 6e72777bfa42..508168b877ab 100644
--- a/src/Umbraco.Core/Services/IMacroWithAliasService.cs
+++ b/src/Umbraco.Core/Services/IMacroWithAliasService.cs
@@ -1,17 +1,14 @@
-using System;
-using System.Collections.Generic;
 using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+[Obsolete("This interface will be merged with IMacroService in Umbraco 11")]
+public interface IMacroWithAliasService : IMacroService
 {
-    [Obsolete("This interface will be merged with IMacroService in Umbraco 11")]
-    public interface IMacroWithAliasService : IMacroService
-    {
-        /// 
-        /// Gets a list of available  objects by alias.
-        /// 
-        /// Optional array of aliases to limit the results
-        /// An enumerable list of  objects
-        IEnumerable GetAll(params string[] aliases);
-    }
+    /// 
+    ///     Gets a list of available  objects by alias.
+    /// 
+    /// Optional array of aliases to limit the results
+    /// An enumerable list of  objects
+    IEnumerable GetAll(params string[] aliases);
 }
diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs
index ee6ba02b669b..54bb2007c65d 100644
--- a/src/Umbraco.Core/Services/IMediaService.cs
+++ b/src/Umbraco.Core/Services/IMediaService.cs
@@ -1,363 +1,378 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Persistence.Querying;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Defines the Media Service, which is an easy access to operations involving 
+/// 
+public interface IMediaService : IContentServiceBase
 {
-        /// 
-    /// Defines the Media Service, which is an easy access to operations involving 
-    /// 
-    public interface IMediaService : IContentServiceBase
-    {
-        int CountNotTrashed(string? contentTypeAlias = null);
-        int Count(string? mediaTypeAlias = null);
-        int CountChildren(int parentId, string? mediaTypeAlias = null);
-        int CountDescendants(int parentId, string? mediaTypeAlias = null);
-
-        IEnumerable GetByIds(IEnumerable ids);
-        IEnumerable GetByIds(IEnumerable ids);
-
-        /// 
-        /// Creates an  object using the alias of the 
-        /// that this Media should based on.
-        /// 
-        /// 
-        /// Note that using this method will simply return a new IMedia without any identity
-        /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects
-        /// that does not invoke a save operation against the database.
-        /// 
-        /// Name of the Media object
-        /// Id of Parent for the new Media item
-        /// Alias of the 
-        /// Optional id of the user creating the media item
-        /// 
-        IMedia CreateMedia(string name, Guid parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Creates an  object using the alias of the 
-        /// that this Media should based on.
-        /// 
-        /// 
-        /// Note that using this method will simply return a new IMedia without any identity
-        /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects
-        /// that does not invoke a save operation against the database.
-        /// 
-        /// Name of the Media object
-        /// Id of Parent for the new Media item
-        /// Alias of the 
-        /// Optional id of the user creating the media item
-        /// 
-        IMedia CreateMedia(string? name, int parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Creates an  object using the alias of the 
-        /// that this Media should based on.
-        /// 
-        /// 
-        /// Note that using this method will simply return a new IMedia without any identity
-        /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects
-        /// that does not invoke a save operation against the database.
-        /// 
-        /// Name of the Media object
-        /// Parent  for the new Media item
-        /// Alias of the 
-        /// Optional id of the user creating the media item
-        /// 
-        IMedia CreateMedia(string name, IMedia? parent, string mediaTypeAlias, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Gets an  object by Id
-        /// 
-        /// Id of the Content to retrieve
-        /// 
-        IMedia? GetById(int id);
-
-        /// 
-        /// Gets a collection of  objects by Parent Id
-        /// 
-        /// Id of the Parent to retrieve Children from
-        /// Page number
-        /// Page size
-        /// Total records query would return without paging
-        /// Field to order by
-        /// Direction to order by
-        /// Flag to indicate when ordering by system field
-        /// 
-        /// An Enumerable list of  objects
-        IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords,
-            IQuery? filter = null, Ordering? ordering = null);
-
-        /// 
-        /// Gets a collection of  objects by Parent Id
-        /// 
-        /// Id of the Parent to retrieve Descendants from
-        /// Page number
-        /// Page size
-        /// Total records query would return without paging
-        /// 
-        /// 
-        /// An Enumerable list of  objects
-        IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords,
-            IQuery? filter = null, Ordering? ordering = null);
-
-        /// 
-        /// Gets paged documents of a content
-        /// 
-        /// The page number.
-        /// The page number.
-        /// The page size.
-        /// Total number of documents.
-        /// Search text filter.
-        /// Ordering infos.
-        IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords,
-            IQuery? filter = null, Ordering? ordering = null);
-
-        /// 
-        /// Gets paged documents for specified content types
-        /// 
-        /// The page number.
-        /// The page number.
-        /// The page size.
-        /// Total number of documents.
-        /// Search text filter.
-        /// Ordering infos.
-        IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize, out long totalRecords,
-            IQuery? filter = null, Ordering? ordering = null);
-
-        /// 
-        /// Gets a collection of  objects, which reside at the first level / root
-        /// 
-        /// An Enumerable list of  objects
-        IEnumerable? GetRootMedia();
-
-        /// 
-        /// Gets a collection of an  objects, which resides in the Recycle Bin
-        /// 
-        /// An Enumerable list of  objects
-        IEnumerable GetPagedMediaInRecycleBin(long pageIndex, int pageSize, out long totalRecords,
-            IQuery? filter = null, Ordering? ordering = null);
-
-        /// 
-        /// Moves an  object to a new location
-        /// 
-        /// The  to move
-        /// Id of the Media's new Parent
-        /// Id of the User moving the Media
-        /// True if moving succeeded, otherwise False
-        Attempt Move(IMedia media, int parentId, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Deletes an  object by moving it to the Recycle Bin
-        /// 
-        /// The  to delete
-        /// Id of the User deleting the Media
-        Attempt MoveToRecycleBin(IMedia media, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Empties the Recycle Bin by deleting all  that resides in the bin
-        /// 
-        /// Optional Id of the User emptying the Recycle Bin
-        OperationResult EmptyRecycleBin(int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Returns true if there is any media in the recycle bin
-        /// 
-        bool RecycleBinSmells();
-
-        /// 
-        /// Deletes all media of specified type. All children of deleted media is moved to Recycle Bin.
-        /// 
-        /// This needs extra care and attention as its potentially a dangerous and extensive operation
-        /// Id of the 
-        /// Optional Id of the user deleting Media
-        void DeleteMediaOfType(int mediaTypeId, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Deletes all media of the specified types. All Descendants of deleted media that is not of these types is moved to Recycle Bin.
-        /// 
-        /// This needs extra care and attention as its potentially a dangerous and extensive operation
-        /// Ids of the s
-        /// Optional Id of the user issuing the delete operation
-        void DeleteMediaOfTypes(IEnumerable mediaTypeIds, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Permanently deletes an  object
-        /// 
-        /// 
-        /// Please note that this method will completely remove the Media from the database,
-        /// but current not from the file system.
-        /// 
-        /// The  to delete
-        /// Id of the User deleting the Media
-        Attempt Delete(IMedia media, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Saves a single  object
-        /// 
-        /// The  to save
-        /// Id of the User saving the Media
-        Attempt Save(IMedia media, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Saves a collection of  objects
-        /// 
-        /// Collection of  to save
-        /// Id of the User saving the Media
-        Attempt Save(IEnumerable medias, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Gets an  object by its 'UniqueId'
-        /// 
-        /// Guid key of the Media to retrieve
-        /// 
-        IMedia? GetById(Guid key);
-
-        /// 
-        /// Gets a collection of  objects by Level
-        /// 
-        /// The level to retrieve Media from
-        /// An Enumerable list of  objects
-        IEnumerable? GetByLevel(int level);
-
-        /// 
-        /// Gets a specific version of an  item.
-        /// 
-        /// Id of the version to retrieve
-        /// An  item
-        IMedia? GetVersion(int versionId);
-
-        /// 
-        /// Gets a collection of an  objects versions by Id
-        /// 
-        /// 
-        /// An Enumerable list of  objects
-        IEnumerable GetVersions(int id);
-
-        /// 
-        /// Checks whether an  item has any children
-        /// 
-        /// Id of the 
-        /// True if the media has any children otherwise False
-        bool HasChildren(int id);
-
-        /// 
-        /// Permanently deletes versions from an  object prior to a specific date.
-        /// 
-        /// Id of the  object to delete versions from
-        /// Latest version date
-        /// Optional Id of the User deleting versions of a Content object
-        void DeleteVersions(int id, DateTime versionDate, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Permanently deletes specific version(s) from an  object.
-        /// 
-        /// Id of the  object to delete a version from
-        /// Id of the version to delete
-        /// Boolean indicating whether to delete versions prior to the versionId
-        /// Optional Id of the User deleting versions of a Content object
-        void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Gets an  object from the path stored in the 'umbracoFile' property.
-        /// 
-        /// Path of the media item to retrieve (for example: /media/1024/koala_403x328.jpg)
-        /// 
-        IMedia? GetMediaByPath(string mediaPath);
-
-        /// 
-        /// Gets a collection of  objects, which are ancestors of the current media.
-        /// 
-        /// Id of the  to retrieve ancestors for
-        /// An Enumerable list of  objects
-        IEnumerable GetAncestors(int id);
-
-        /// 
-        /// Gets a collection of  objects, which are ancestors of the current media.
-        /// 
-        ///  to retrieve ancestors for
-        /// An Enumerable list of  objects
-        IEnumerable GetAncestors(IMedia media);
-
-        /// 
-        /// Gets the parent of the current media as an  item.
-        /// 
-        /// Id of the  to retrieve the parent from
-        /// Parent  object
-        IMedia? GetParent(int id);
-
-        /// 
-        /// Gets the parent of the current media as an  item.
-        /// 
-        ///  to retrieve the parent from
-        /// Parent  object
-        IMedia? GetParent(IMedia media);
-
-        /// 
-        /// Sorts a collection of  objects by updating the SortOrder according
-        /// to the ordering of items in the passed in .
-        /// 
-        /// 
-        /// 
-        /// True if sorting succeeded, otherwise False
-        bool Sort(IEnumerable items, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Creates an  object using the alias of the 
-        /// that this Media should based on.
-        /// 
-        /// 
-        /// This method returns an  object that has been persisted to the database
-        /// and therefor has an identity.
-        /// 
-        /// Name of the Media object
-        /// Parent  for the new Media item
-        /// Alias of the 
-        /// Optional id of the user creating the media item
-        /// 
-        IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Creates an  object using the alias of the 
-        /// that this Media should based on.
-        /// 
-        /// 
-        /// This method returns an  object that has been persisted to the database
-        /// and therefor has an identity.
-        /// 
-        /// Name of the Media object
-        /// Id of Parent for the new Media item
-        /// Alias of the 
-        /// Optional id of the user creating the media item
-        /// 
-        IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Gets the content of a media as a stream.
-        /// 
-        /// The filesystem path to the media.
-        /// The content of the media.
-        Stream GetMediaFileContentStream(string filepath);
-
-        /// 
-        /// Sets the content of a media.
-        /// 
-        /// The filesystem path to the media.
-        /// The content of the media.
-        void SetMediaFileContent(string filepath, Stream content);
-
-        /// 
-        /// Deletes a media file.
-        /// 
-        /// The filesystem path to the media.
-        void DeleteMediaFile(string filepath);
-
-        /// 
-        /// Gets the size of a media.
-        /// 
-        /// The filesystem path to the media.
-        /// The size of the media.
-        long GetMediaFileSize(string filepath);
-    }
+    int CountNotTrashed(string? contentTypeAlias = null);
+    int Count(string? mediaTypeAlias = null);
+    int CountChildren(int parentId, string? mediaTypeAlias = null);
+    int CountDescendants(int parentId, string? mediaTypeAlias = null);
+
+    IEnumerable GetByIds(IEnumerable ids);
+    IEnumerable GetByIds(IEnumerable ids);
+
+    /// 
+    ///     Creates an  object using the alias of the 
+    ///     that this Media should based on.
+    /// 
+    /// 
+    ///     Note that using this method will simply return a new IMedia without any identity
+    ///     as it has not yet been persisted. It is intended as a shortcut to creating new media objects
+    ///     that does not invoke a save operation against the database.
+    /// 
+    /// Name of the Media object
+    /// Id of Parent for the new Media item
+    /// Alias of the 
+    /// Optional id of the user creating the media item
+    /// 
+    ///     
+    /// 
+    IMedia CreateMedia(string name, Guid parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Creates an  object using the alias of the 
+    ///     that this Media should based on.
+    /// 
+    /// 
+    ///     Note that using this method will simply return a new IMedia without any identity
+    ///     as it has not yet been persisted. It is intended as a shortcut to creating new media objects
+    ///     that does not invoke a save operation against the database.
+    /// 
+    /// Name of the Media object
+    /// Id of Parent for the new Media item
+    /// Alias of the 
+    /// Optional id of the user creating the media item
+    /// 
+    ///     
+    /// 
+    IMedia CreateMedia(string? name, int parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Creates an  object using the alias of the 
+    ///     that this Media should based on.
+    /// 
+    /// 
+    ///     Note that using this method will simply return a new IMedia without any identity
+    ///     as it has not yet been persisted. It is intended as a shortcut to creating new media objects
+    ///     that does not invoke a save operation against the database.
+    /// 
+    /// Name of the Media object
+    /// Parent  for the new Media item
+    /// Alias of the 
+    /// Optional id of the user creating the media item
+    /// 
+    ///     
+    /// 
+    IMedia CreateMedia(string name, IMedia? parent, string mediaTypeAlias, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Gets an  object by Id
+    /// 
+    /// Id of the Content to retrieve
+    /// 
+    ///     
+    /// 
+    IMedia? GetById(int id);
+
+    /// 
+    ///     Gets a collection of  objects by Parent Id
+    /// 
+    /// Id of the Parent to retrieve Children from
+    /// Page number
+    /// Page size
+    /// Total records query would return without paging
+    /// Field to order by
+    /// Direction to order by
+    /// Flag to indicate when ordering by system field
+    /// 
+    /// An Enumerable list of  objects
+    IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords,
+        IQuery? filter = null, Ordering? ordering = null);
+
+    /// 
+    ///     Gets a collection of  objects by Parent Id
+    /// 
+    /// Id of the Parent to retrieve Descendants from
+    /// Page number
+    /// Page size
+    /// Total records query would return without paging
+    /// 
+    /// 
+    /// An Enumerable list of  objects
+    IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords,
+        IQuery? filter = null, Ordering? ordering = null);
+
+    /// 
+    ///     Gets paged documents of a content
+    /// 
+    /// The page number.
+    /// The page number.
+    /// The page size.
+    /// Total number of documents.
+    /// Search text filter.
+    /// Ordering infos.
+    IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords,
+        IQuery? filter = null, Ordering? ordering = null);
+
+    /// 
+    ///     Gets paged documents for specified content types
+    /// 
+    /// The page number.
+    /// The page number.
+    /// The page size.
+    /// Total number of documents.
+    /// Search text filter.
+    /// Ordering infos.
+    IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize, out long totalRecords,
+        IQuery? filter = null, Ordering? ordering = null);
+
+    /// 
+    ///     Gets a collection of  objects, which reside at the first level / root
+    /// 
+    /// An Enumerable list of  objects
+    IEnumerable? GetRootMedia();
+
+    /// 
+    ///     Gets a collection of an  objects, which resides in the Recycle Bin
+    /// 
+    /// An Enumerable list of  objects
+    IEnumerable GetPagedMediaInRecycleBin(long pageIndex, int pageSize, out long totalRecords,
+        IQuery? filter = null, Ordering? ordering = null);
+
+    /// 
+    ///     Moves an  object to a new location
+    /// 
+    /// The  to move
+    /// Id of the Media's new Parent
+    /// Id of the User moving the Media
+    /// True if moving succeeded, otherwise False
+    Attempt Move(IMedia media, int parentId, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Deletes an  object by moving it to the Recycle Bin
+    /// 
+    /// The  to delete
+    /// Id of the User deleting the Media
+    Attempt MoveToRecycleBin(IMedia media, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Empties the Recycle Bin by deleting all  that resides in the bin
+    /// 
+    /// Optional Id of the User emptying the Recycle Bin
+    OperationResult EmptyRecycleBin(int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Returns true if there is any media in the recycle bin
+    /// 
+    bool RecycleBinSmells();
+
+    /// 
+    ///     Deletes all media of specified type. All children of deleted media is moved to Recycle Bin.
+    /// 
+    /// This needs extra care and attention as its potentially a dangerous and extensive operation
+    /// Id of the 
+    /// Optional Id of the user deleting Media
+    void DeleteMediaOfType(int mediaTypeId, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Deletes all media of the specified types. All Descendants of deleted media that is not of these types is moved to
+    ///     Recycle Bin.
+    /// 
+    /// This needs extra care and attention as its potentially a dangerous and extensive operation
+    /// Ids of the s
+    /// Optional Id of the user issuing the delete operation
+    void DeleteMediaOfTypes(IEnumerable mediaTypeIds, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Permanently deletes an  object
+    /// 
+    /// 
+    ///     Please note that this method will completely remove the Media from the database,
+    ///     but current not from the file system.
+    /// 
+    /// The  to delete
+    /// Id of the User deleting the Media
+    Attempt Delete(IMedia media, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Saves a single  object
+    /// 
+    /// The  to save
+    /// Id of the User saving the Media
+    Attempt Save(IMedia media, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Saves a collection of  objects
+    /// 
+    /// Collection of  to save
+    /// Id of the User saving the Media
+    Attempt Save(IEnumerable medias, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Gets an  object by its 'UniqueId'
+    /// 
+    /// Guid key of the Media to retrieve
+    /// 
+    ///     
+    /// 
+    IMedia? GetById(Guid key);
+
+    /// 
+    ///     Gets a collection of  objects by Level
+    /// 
+    /// The level to retrieve Media from
+    /// An Enumerable list of  objects
+    IEnumerable? GetByLevel(int level);
+
+    /// 
+    ///     Gets a specific version of an  item.
+    /// 
+    /// Id of the version to retrieve
+    /// An  item
+    IMedia? GetVersion(int versionId);
+
+    /// 
+    ///     Gets a collection of an  objects versions by Id
+    /// 
+    /// 
+    /// An Enumerable list of  objects
+    IEnumerable GetVersions(int id);
+
+    /// 
+    ///     Checks whether an  item has any children
+    /// 
+    /// Id of the 
+    /// True if the media has any children otherwise False
+    bool HasChildren(int id);
+
+    /// 
+    ///     Permanently deletes versions from an  object prior to a specific date.
+    /// 
+    /// Id of the  object to delete versions from
+    /// Latest version date
+    /// Optional Id of the User deleting versions of a Content object
+    void DeleteVersions(int id, DateTime versionDate, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Permanently deletes specific version(s) from an  object.
+    /// 
+    /// Id of the  object to delete a version from
+    /// Id of the version to delete
+    /// Boolean indicating whether to delete versions prior to the versionId
+    /// Optional Id of the User deleting versions of a Content object
+    void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Gets an  object from the path stored in the 'umbracoFile' property.
+    /// 
+    /// Path of the media item to retrieve (for example: /media/1024/koala_403x328.jpg)
+    /// 
+    ///     
+    /// 
+    IMedia? GetMediaByPath(string mediaPath);
+
+    /// 
+    ///     Gets a collection of  objects, which are ancestors of the current media.
+    /// 
+    /// Id of the  to retrieve ancestors for
+    /// An Enumerable list of  objects
+    IEnumerable GetAncestors(int id);
+
+    /// 
+    ///     Gets a collection of  objects, which are ancestors of the current media.
+    /// 
+    ///  to retrieve ancestors for
+    /// An Enumerable list of  objects
+    IEnumerable GetAncestors(IMedia media);
+
+    /// 
+    ///     Gets the parent of the current media as an  item.
+    /// 
+    /// Id of the  to retrieve the parent from
+    /// Parent  object
+    IMedia? GetParent(int id);
+
+    /// 
+    ///     Gets the parent of the current media as an  item.
+    /// 
+    ///  to retrieve the parent from
+    /// Parent  object
+    IMedia? GetParent(IMedia media);
+
+    /// 
+    ///     Sorts a collection of  objects by updating the SortOrder according
+    ///     to the ordering of items in the passed in .
+    /// 
+    /// 
+    /// 
+    /// True if sorting succeeded, otherwise False
+    bool Sort(IEnumerable items, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Creates an  object using the alias of the 
+    ///     that this Media should based on.
+    /// 
+    /// 
+    ///     This method returns an  object that has been persisted to the database
+    ///     and therefor has an identity.
+    /// 
+    /// Name of the Media object
+    /// Parent  for the new Media item
+    /// Alias of the 
+    /// Optional id of the user creating the media item
+    /// 
+    ///     
+    /// 
+    IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias,
+        int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Creates an  object using the alias of the 
+    ///     that this Media should based on.
+    /// 
+    /// 
+    ///     This method returns an  object that has been persisted to the database
+    ///     and therefor has an identity.
+    /// 
+    /// Name of the Media object
+    /// Id of Parent for the new Media item
+    /// Alias of the 
+    /// Optional id of the user creating the media item
+    /// 
+    ///     
+    /// 
+    IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias,
+        int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Gets the content of a media as a stream.
+    /// 
+    /// The filesystem path to the media.
+    /// The content of the media.
+    Stream GetMediaFileContentStream(string filepath);
+
+    /// 
+    ///     Sets the content of a media.
+    /// 
+    /// The filesystem path to the media.
+    /// The content of the media.
+    void SetMediaFileContent(string filepath, Stream content);
+
+    /// 
+    ///     Deletes a media file.
+    /// 
+    /// The filesystem path to the media.
+    void DeleteMediaFile(string filepath);
+
+    /// 
+    ///     Gets the size of a media.
+    /// 
+    /// The filesystem path to the media.
+    /// The size of the media.
+    long GetMediaFileSize(string filepath);
 }
diff --git a/src/Umbraco.Core/Services/IMediaTypeService.cs b/src/Umbraco.Core/Services/IMediaTypeService.cs
index e00d86613b6a..d7e730d9b74f 100644
--- a/src/Umbraco.Core/Services/IMediaTypeService.cs
+++ b/src/Umbraco.Core/Services/IMediaTypeService.cs
@@ -1,10 +1,10 @@
 using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Manages  objects.
+/// 
+public interface IMediaTypeService : IContentTypeBaseService
 {
-    /// 
-    /// Manages  objects.
-    /// 
-    public interface IMediaTypeService : IContentTypeBaseService
-    { }
 }
diff --git a/src/Umbraco.Core/Services/IMemberGroupService.cs b/src/Umbraco.Core/Services/IMemberGroupService.cs
index 9b8c4a8d536e..00606b4ea656 100644
--- a/src/Umbraco.Core/Services/IMemberGroupService.cs
+++ b/src/Umbraco.Core/Services/IMemberGroupService.cs
@@ -1,17 +1,14 @@
-using System;
-using System.Collections.Generic;
 using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface IMemberGroupService : IService
 {
-    public interface IMemberGroupService : IService
-    {
-        IEnumerable GetAll();
-        IMemberGroup? GetById(int id);
-        IMemberGroup? GetById(Guid id);
-        IEnumerable GetByIds(IEnumerable ids);
-        IMemberGroup? GetByName(string? name);
-        void Save(IMemberGroup memberGroup);
-        void Delete(IMemberGroup memberGroup);
-    }
+    IEnumerable GetAll();
+    IMemberGroup? GetById(int id);
+    IMemberGroup? GetById(Guid id);
+    IEnumerable GetByIds(IEnumerable ids);
+    IMemberGroup? GetByName(string? name);
+    void Save(IMemberGroup memberGroup);
+    void Delete(IMemberGroup memberGroup);
 }
diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs
index 67010909153a..7f3ac2dcc305 100644
--- a/src/Umbraco.Core/Services/IMemberService.cs
+++ b/src/Umbraco.Core/Services/IMemberService.cs
@@ -1,206 +1,263 @@
-using System;
-using System.Collections.Generic;
-using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Persistence.Querying;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Defines the MemberService, which is an easy access to operations involving (umbraco) members.
+/// 
+public interface IMemberService : IMembershipMemberService
 {
     /// 
-    /// Defines the MemberService, which is an easy access to operations involving (umbraco) members.
-    /// 
-    public interface IMemberService : IMembershipMemberService
-    {
-        /// 
-        /// Gets a list of paged  objects
-        /// 
-        /// An  can be of type  
-        /// Current page index
-        /// Size of the page
-        /// Total number of records found (out)
-        /// Field to order by
-        /// Direction to order by
-        /// 
-        /// Search text filter
-        /// 
-        IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords,
-            string orderBy, Direction orderDirection, string? memberTypeAlias = null, string filter = "");
-
-        /// 
-        /// Gets a list of paged  objects
-        /// 
-        /// An  can be of type  
-        /// Current page index
-        /// Size of the page
-        /// Total number of records found (out)
-        /// Field to order by
-        /// Direction to order by
-        /// Flag to indicate when ordering by system field
-        /// 
-        /// Search text filter
-        /// 
-        IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords,
-            string orderBy, Direction orderDirection, bool orderBySystemField, string? memberTypeAlias, string filter);
-
-        /// 
-        /// Creates an  object without persisting it
-        /// 
-        /// This method is convenient for when you need to add properties to a new Member
-        /// before persisting it in order to limit the amount of times its saved.
-        /// Also note that the returned  will not have an Id until its saved.
-        /// Username of the Member to create
-        /// Email of the Member to create
-        /// Name of the Member to create
-        /// Alias of the MemberType the Member should be based on
-        /// 
-        IMember CreateMember(string username, string email, string name, string memberTypeAlias);
-
-        /// 
-        /// Creates an  object without persisting it
-        /// 
-        /// This method is convenient for when you need to add properties to a new Member
-        /// before persisting it in order to limit the amount of times its saved.
-        /// Also note that the returned  will not have an Id until its saved.
-        /// Username of the Member to create
-        /// Email of the Member to create
-        /// Name of the Member to create
-        /// MemberType the Member should be based on
-        /// 
-        IMember CreateMember(string username, string email, string name, IMemberType memberType);
-
-        /// 
-        /// Creates and persists a Member
-        /// 
-        /// Using this method will persist the Member object before its returned
-        /// meaning that it will have an Id available (unlike the CreateMember method)
-        /// Username of the Member to create
-        /// Email of the Member to create
-        /// Name of the Member to create
-        /// Alias of the MemberType the Member should be based on
-        /// 
-        IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias);
-
-        /// 
-        /// Creates and persists a Member
-        /// 
-        /// Using this method will persist the Member object before its returned
-        /// meaning that it will have an Id available (unlike the CreateMember method)
-        /// Username of the Member to create
-        /// Email of the Member to create
-        /// Name of the Member to create
-        /// MemberType the Member should be based on
-        /// 
-        IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType);
-
-        /// 
-        /// Gets the count of Members by an optional MemberType alias
-        /// 
-        /// If no alias is supplied then the count for all Member will be returned
-        /// Optional alias for the MemberType when counting number of Members
-        ///  with number of Members
-        int Count(string? memberTypeAlias = null);
-
-        /// 
-        /// Checks if a Member with the id exists
-        /// 
-        /// Id of the Member
-        /// True if the Member exists otherwise False
-        bool Exists(int id);
-
-        /// 
-        /// Gets a Member by the unique key
-        /// 
-        /// The guid key corresponds to the unique id in the database
-        /// and the user id in the membership provider.
-        ///  Id
-        /// 
-        IMember? GetByKey(Guid id);
-
-        /// 
-        /// Gets a Member by its integer id
-        /// 
-        ///  Id
-        /// 
-        IMember? GetById(int id);
-
-        /// 
-        /// Gets all Members for the specified MemberType alias
-        /// 
-        /// Alias of the MemberType
-        /// 
-        IEnumerable? GetMembersByMemberType(string memberTypeAlias);
-
-        /// 
-        /// Gets all Members for the MemberType id
-        /// 
-        /// Id of the MemberType
-        /// 
-        IEnumerable? GetMembersByMemberType(int memberTypeId);
-
-        /// 
-        /// Gets all Members within the specified MemberGroup name
-        /// 
-        /// Name of the MemberGroup
-        /// 
-        IEnumerable GetMembersByGroup(string memberGroupName);
-
-        /// 
-        /// Gets all Members with the ids specified
-        /// 
-        /// If no Ids are specified all Members will be retrieved
-        /// Optional list of Member Ids
-        /// 
-        IEnumerable GetAllMembers(params int[] ids);
-
-        /// 
-        /// Delete Members of the specified MemberType id
-        /// 
-        /// Id of the MemberType
-        void DeleteMembersOfType(int memberTypeId);
-
-        /// 
-        /// Finds Members based on their display name
-        /// 
-        /// Display name to match
-        /// Current page index
-        /// Size of the page
-        /// Total number of records found (out)
-        /// The type of match to make as . Default is 
-        /// 
-        IEnumerable FindMembersByDisplayName(string displayNameToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith);
-
-        /// 
-        /// Gets a list of Members based on a property search
-        /// 
-        /// Alias of the PropertyType to search for
-        ///  Value to match
-        /// The type of match to make as . Default is 
-        /// 
-        IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact);
-
-        /// 
-        /// Gets a list of Members based on a property search
-        /// 
-        /// Alias of the PropertyType to search for
-        ///  Value to match
-        /// The type of match to make as . Default is 
-        /// 
-        IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact);
-
-        /// 
-        /// Gets a list of Members based on a property search
-        /// 
-        /// Alias of the PropertyType to search for
-        ///  Value to match
-        /// 
-        IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, bool value);
-
-        /// 
-        /// Gets a list of Members based on a property search
-        /// 
-        /// Alias of the PropertyType to search for
-        ///  Value to match
-        /// The type of match to make as . Default is 
-        /// 
-        IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact);
-    }
+    ///     Gets a list of paged  objects
+    /// 
+    /// An  can be of type  
+    /// Current page index
+    /// Size of the page
+    /// Total number of records found (out)
+    /// Field to order by
+    /// Direction to order by
+    /// 
+    /// Search text filter
+    /// 
+    ///     
+    /// 
+    IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords,
+        string orderBy, Direction orderDirection, string? memberTypeAlias = null, string filter = "");
+
+    /// 
+    ///     Gets a list of paged  objects
+    /// 
+    /// An  can be of type  
+    /// Current page index
+    /// Size of the page
+    /// Total number of records found (out)
+    /// Field to order by
+    /// Direction to order by
+    /// Flag to indicate when ordering by system field
+    /// 
+    /// Search text filter
+    /// 
+    ///     
+    /// 
+    IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords,
+        string orderBy, Direction orderDirection, bool orderBySystemField, string? memberTypeAlias, string filter);
+
+    /// 
+    ///     Creates an  object without persisting it
+    /// 
+    /// 
+    ///     This method is convenient for when you need to add properties to a new Member
+    ///     before persisting it in order to limit the amount of times its saved.
+    ///     Also note that the returned  will not have an Id until its saved.
+    /// 
+    /// Username of the Member to create
+    /// Email of the Member to create
+    /// Name of the Member to create
+    /// Alias of the MemberType the Member should be based on
+    /// 
+    ///     
+    /// 
+    IMember CreateMember(string username, string email, string name, string memberTypeAlias);
+
+    /// 
+    ///     Creates an  object without persisting it
+    /// 
+    /// 
+    ///     This method is convenient for when you need to add properties to a new Member
+    ///     before persisting it in order to limit the amount of times its saved.
+    ///     Also note that the returned  will not have an Id until its saved.
+    /// 
+    /// Username of the Member to create
+    /// Email of the Member to create
+    /// Name of the Member to create
+    /// MemberType the Member should be based on
+    /// 
+    ///     
+    /// 
+    IMember CreateMember(string username, string email, string name, IMemberType memberType);
+
+    /// 
+    ///     Creates and persists a Member
+    /// 
+    /// 
+    ///     Using this method will persist the Member object before its returned
+    ///     meaning that it will have an Id available (unlike the CreateMember method)
+    /// 
+    /// Username of the Member to create
+    /// Email of the Member to create
+    /// Name of the Member to create
+    /// Alias of the MemberType the Member should be based on
+    /// 
+    ///     
+    /// 
+    IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias);
+
+    /// 
+    ///     Creates and persists a Member
+    /// 
+    /// 
+    ///     Using this method will persist the Member object before its returned
+    ///     meaning that it will have an Id available (unlike the CreateMember method)
+    /// 
+    /// Username of the Member to create
+    /// Email of the Member to create
+    /// Name of the Member to create
+    /// MemberType the Member should be based on
+    /// 
+    ///     
+    /// 
+    IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType);
+
+    /// 
+    ///     Gets the count of Members by an optional MemberType alias
+    /// 
+    /// If no alias is supplied then the count for all Member will be returned
+    /// Optional alias for the MemberType when counting number of Members
+    ///  with number of Members
+    int Count(string? memberTypeAlias = null);
+
+    /// 
+    ///     Checks if a Member with the id exists
+    /// 
+    /// Id of the Member
+    /// True if the Member exists otherwise False
+    bool Exists(int id);
+
+    /// 
+    ///     Gets a Member by the unique key
+    /// 
+    /// 
+    ///     The guid key corresponds to the unique id in the database
+    ///     and the user id in the membership provider.
+    /// 
+    ///  Id
+    /// 
+    ///     
+    /// 
+    IMember? GetByKey(Guid id);
+
+    /// 
+    ///     Gets a Member by its integer id
+    /// 
+    ///  Id
+    /// 
+    ///     
+    /// 
+    IMember? GetById(int id);
+
+    /// 
+    ///     Gets all Members for the specified MemberType alias
+    /// 
+    /// Alias of the MemberType
+    /// 
+    ///     
+    /// 
+    IEnumerable? GetMembersByMemberType(string memberTypeAlias);
+
+    /// 
+    ///     Gets all Members for the MemberType id
+    /// 
+    /// Id of the MemberType
+    /// 
+    ///     
+    /// 
+    IEnumerable? GetMembersByMemberType(int memberTypeId);
+
+    /// 
+    ///     Gets all Members within the specified MemberGroup name
+    /// 
+    /// Name of the MemberGroup
+    /// 
+    ///     
+    /// 
+    IEnumerable GetMembersByGroup(string memberGroupName);
+
+    /// 
+    ///     Gets all Members with the ids specified
+    /// 
+    /// If no Ids are specified all Members will be retrieved
+    /// Optional list of Member Ids
+    /// 
+    ///     
+    /// 
+    IEnumerable GetAllMembers(params int[] ids);
+
+    /// 
+    ///     Delete Members of the specified MemberType id
+    /// 
+    /// Id of the MemberType
+    void DeleteMembersOfType(int memberTypeId);
+
+    /// 
+    ///     Finds Members based on their display name
+    /// 
+    /// Display name to match
+    /// Current page index
+    /// Size of the page
+    /// Total number of records found (out)
+    /// 
+    ///     The type of match to make as . Default is
+    ///     
+    /// 
+    /// 
+    ///     
+    /// 
+    IEnumerable FindMembersByDisplayName(string displayNameToMatch, long pageIndex, int pageSize,
+        out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith);
+
+    /// 
+    ///     Gets a list of Members based on a property search
+    /// 
+    /// Alias of the PropertyType to search for
+    ///  Value to match
+    /// 
+    ///     The type of match to make as . Default is
+    ///     
+    /// 
+    /// 
+    ///     
+    /// 
+    IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, string value,
+        StringPropertyMatchType matchType = StringPropertyMatchType.Exact);
+
+    /// 
+    ///     Gets a list of Members based on a property search
+    /// 
+    /// Alias of the PropertyType to search for
+    ///  Value to match
+    /// 
+    ///     The type of match to make as . Default is
+    ///     
+    /// 
+    /// 
+    ///     
+    /// 
+    IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, int value,
+        ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact);
+
+    /// 
+    ///     Gets a list of Members based on a property search
+    /// 
+    /// Alias of the PropertyType to search for
+    ///  Value to match
+    /// 
+    ///     
+    /// 
+    IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, bool value);
+
+    /// 
+    ///     Gets a list of Members based on a property search
+    /// 
+    /// Alias of the PropertyType to search for
+    ///  Value to match
+    /// 
+    ///     The type of match to make as . Default is
+    ///     
+    /// 
+    /// 
+    ///     
+    /// 
+    IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, DateTime value,
+        ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact);
 }
diff --git a/src/Umbraco.Core/Services/IMemberTypeService.cs b/src/Umbraco.Core/Services/IMemberTypeService.cs
index 4a52438d5ef1..f044e4a770e1 100644
--- a/src/Umbraco.Core/Services/IMemberTypeService.cs
+++ b/src/Umbraco.Core/Services/IMemberTypeService.cs
@@ -1,12 +1,11 @@
 using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Manages  objects.
+/// 
+public interface IMemberTypeService : IContentTypeBaseService
 {
-    /// 
-    /// Manages  objects.
-    /// 
-    public interface IMemberTypeService : IContentTypeBaseService
-    {
-        string GetDefault();
-    }
+    string GetDefault();
 }
diff --git a/src/Umbraco.Core/Services/IMembershipMemberService.cs b/src/Umbraco.Core/Services/IMembershipMemberService.cs
index 94dbbf3da99e..74b98dbfea15 100644
--- a/src/Umbraco.Core/Services/IMembershipMemberService.cs
+++ b/src/Umbraco.Core/Services/IMembershipMemberService.cs
@@ -1,171 +1,206 @@
-using System;
-using System.Collections.Generic;
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Models.Membership;
 using Umbraco.Cms.Core.Persistence.Querying;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Defines part of the MemberService, which is specific to methods used by the membership provider.
+/// 
+/// 
+///     Idea is to have this as an isolated interface so that it can be easily 'replaced' in the membership provider
+///     implementation.
+/// 
+public interface IMembershipMemberService : IMembershipMemberService, IMembershipRoleService
+{
+    /// 
+    ///     Creates and persists a new Member
+    /// 
+    /// Username of the Member to create
+    /// Email of the Member to create
+    ///  which the Member should be based on
+    /// 
+    ///     
+    /// 
+    IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType);
+}
+
+/// 
+///     Defines part of the UserService/MemberService, which is specific to methods used by the membership provider.
+///     The generic type is restricted to . The implementation of this interface  uses
+///     either  for the MemberService or  for the UserService.
+/// 
+/// 
+///     Idea is to have this as an isolated interface so that it can be easily 'replaced' in the membership provider
+///     implementation.
+/// 
+public interface IMembershipMemberService : IService
+    where T : class, IMembershipUser
 {
     /// 
-    /// Defines part of the MemberService, which is specific to methods used by the membership provider.
+    ///     Gets the total number of Members or Users based on the count type
     /// 
     /// 
-    /// Idea is to have this as an isolated interface so that it can be easily 'replaced' in the membership provider implementation.
+    ///     The way the Online count is done is the same way that it is done in the MS SqlMembershipProvider - We query for any
+    ///     members
+    ///     that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact
+    ///     science
+    ///     but that is how MS have made theirs so we'll follow that principal.
     /// 
-    public interface IMembershipMemberService : IMembershipMemberService, IMembershipRoleService
-    {
-        /// 
-        /// Creates and persists a new Member
-        /// 
-        /// Username of the Member to create
-        /// Email of the Member to create
-        ///  which the Member should be based on
-        /// 
-        IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType);
-    }
+    ///  to count by
+    ///  with number of Members or Users for passed in type
+    int GetCount(MemberCountType countType);
+
+    /// 
+    ///     Checks if a Member with the username exists
+    /// 
+    /// Username to check
+    /// True if the Member exists otherwise False
+    bool Exists(string username);
+
+    /// 
+    ///     Creates and persists a new 
+    /// 
+    /// An  can be of type  or 
+    /// Username of the  to create
+    /// Email of the  to create
+    /// 
+    ///     This value should be the encoded/encrypted/hashed value for the password that will be
+    ///     stored in the database
+    /// 
+    /// Alias of the Type
+    /// 
+    ///     
+    /// 
+    T CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias);
+
+    /// 
+    ///     Creates and persists a new 
+    /// 
+    /// An  can be of type  or 
+    /// Username of the  to create
+    /// Email of the  to create
+    /// 
+    ///     This value should be the encoded/encrypted/hashed value for the password that will be
+    ///     stored in the database
+    /// 
+    /// Alias of the Type
+    /// IsApproved of the  to create
+    /// 
+    ///     
+    /// 
+    T CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias, bool isApproved);
+
+    /// 
+    ///     Gets an  by its provider key
+    /// 
+    /// An  can be of type  or 
+    /// Id to use for retrieval
+    /// 
+    ///     
+    /// 
+    T? GetByProviderKey(object id);
+
+    /// 
+    ///     Get an  by email
+    /// 
+    /// An  can be of type  or 
+    /// Email to use for retrieval
+    /// 
+    ///     
+    /// 
+    T? GetByEmail(string email);
 
     /// 
-    /// Defines part of the UserService/MemberService, which is specific to methods used by the membership provider.
-    /// The generic type is restricted to . The implementation of this interface  uses
-    /// either  for the MemberService or  for the UserService.
+    ///     Get an  by username
     /// 
+    /// An  can be of type  or 
+    /// Username to use for retrieval
+    /// 
+    ///     
+    /// 
+    T? GetByUsername(string? username);
+
+    /// 
+    ///     Deletes an 
+    /// 
+    /// An  can be of type  or 
+    ///  or  to Delete
+    void Delete(T membershipUser);
+
+    /// 
+    ///     Sets the last login date for the member if they are found by username
+    /// 
+    /// 
+    /// 
     /// 
-    /// Idea is to have this as an isolated interface so that it can be easily 'replaced' in the membership provider implementation.
+    ///     This is a specialized method because whenever a member logs in, the membership provider requires us to set the
+    ///     'online' which requires
+    ///     updating their login date. This operation must be fast and cannot use database locks which is fine if we are only
+    ///     executing a single query
+    ///     for this data since there won't be any other data contention issues.
     /// 
-    public interface IMembershipMemberService : IService
-        where T : class, IMembershipUser
-    {
-        /// 
-        /// Gets the total number of Members or Users based on the count type
-        /// 
-        /// 
-        /// The way the Online count is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members
-        /// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact science
-        /// but that is how MS have made theirs so we'll follow that principal.
-        /// 
-        ///  to count by
-        ///  with number of Members or Users for passed in type
-        int GetCount(MemberCountType countType);
-
-        /// 
-        /// Checks if a Member with the username exists
-        /// 
-        /// Username to check
-        /// True if the Member exists otherwise False
-        bool Exists(string username);
-
-        /// 
-        /// Creates and persists a new 
-        /// 
-        /// An  can be of type  or 
-        /// Username of the  to create
-        /// Email of the  to create
-        /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database
-        /// Alias of the Type
-        /// 
-        T CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias);
-
-        /// 
-        /// Creates and persists a new 
-        /// 
-        /// An  can be of type  or 
-        /// Username of the  to create
-        /// Email of the  to create
-        /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database
-        /// Alias of the Type
-        /// IsApproved of the  to create
-        /// 
-        T CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias, bool isApproved);
-
-        /// 
-        /// Gets an  by its provider key
-        /// 
-        /// An  can be of type  or 
-        /// Id to use for retrieval
-        /// 
-        T? GetByProviderKey(object id);
-
-        /// 
-        /// Get an  by email
-        /// 
-        /// An  can be of type  or 
-        /// Email to use for retrieval
-        /// 
-        T? GetByEmail(string email);
-
-        /// 
-        /// Get an  by username
-        /// 
-        /// An  can be of type  or 
-        /// Username to use for retrieval
-        /// 
-        T? GetByUsername(string? username);
-
-        /// 
-        /// Deletes an 
-        /// 
-        /// An  can be of type  or 
-        ///  or  to Delete
-        void Delete(T membershipUser);
-
-        /// 
-        /// Sets the last login date for the member if they are found by username
-        /// 
-        /// 
-        /// 
-        /// 
-        /// This is a specialized method because whenever a member logs in, the membership provider requires us to set the 'online' which requires
-        /// updating their login date. This operation must be fast and cannot use database locks which is fine if we are only executing a single query
-        /// for this data since there won't be any other data contention issues.
-        /// 
-        void SetLastLogin(string username, DateTime date);
-
-        /// 
-        /// Saves an 
-        /// 
-        /// An  can be of type  or 
-        ///  or  to Save
-        void Save(T entity);
-
-        /// 
-        /// Saves a list of  objects
-        /// 
-        /// An  can be of type  or 
-        ///  to save
-        void Save(IEnumerable entities);
-
-        /// 
-        /// Finds a list of  objects by a partial email string
-        /// 
-        /// An  can be of type  or 
-        /// Partial email string to match
-        /// Current page index
-        /// Size of the page
-        /// Total number of records found (out)
-        /// The type of match to make as . Default is 
-        /// 
-        IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith);
-
-        /// 
-        /// Finds a list of  objects by a partial username
-        /// 
-        /// An  can be of type  or 
-        /// Partial username to match
-        /// Current page index
-        /// Size of the page
-        /// Total number of records found (out)
-        /// The type of match to make as . Default is 
-        /// 
-        IEnumerable FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith);
-
-        /// 
-        /// Gets a list of paged  objects
-        /// 
-        /// An  can be of type  or 
-        /// Current page index
-        /// Size of the page
-        /// Total number of records found (out)
-        /// 
-        IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords);
-    }
+    void SetLastLogin(string username, DateTime date);
+
+    /// 
+    ///     Saves an 
+    /// 
+    /// An  can be of type  or 
+    ///  or  to Save
+    void Save(T entity);
+
+    /// 
+    ///     Saves a list of  objects
+    /// 
+    /// An  can be of type  or 
+    ///  to save
+    void Save(IEnumerable entities);
+
+    /// 
+    ///     Finds a list of  objects by a partial email string
+    /// 
+    /// An  can be of type  or 
+    /// Partial email string to match
+    /// Current page index
+    /// Size of the page
+    /// Total number of records found (out)
+    /// 
+    ///     The type of match to make as . Default is
+    ///     
+    /// 
+    /// 
+    ///     
+    /// 
+    IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, out long totalRecords,
+        StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith);
+
+    /// 
+    ///     Finds a list of  objects by a partial username
+    /// 
+    /// An  can be of type  or 
+    /// Partial username to match
+    /// Current page index
+    /// Size of the page
+    /// Total number of records found (out)
+    /// 
+    ///     The type of match to make as . Default is
+    ///     
+    /// 
+    /// 
+    ///     
+    /// 
+    IEnumerable FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords,
+        StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith);
+
+    /// 
+    ///     Gets a list of paged  objects
+    /// 
+    /// An  can be of type  or 
+    /// Current page index
+    /// Size of the page
+    /// Total number of records found (out)
+    /// 
+    ///     
+    /// 
+    IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords);
 }
diff --git a/src/Umbraco.Core/Services/IMembershipRoleService.cs b/src/Umbraco.Core/Services/IMembershipRoleService.cs
index 70e1d5349148..c335159605b1 100644
--- a/src/Umbraco.Core/Services/IMembershipRoleService.cs
+++ b/src/Umbraco.Core/Services/IMembershipRoleService.cs
@@ -1,52 +1,50 @@
-using System.Collections.Generic;
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Models.Membership;
 using Umbraco.Cms.Core.Persistence.Querying;
 
-namespace Umbraco.Cms.Core.Services
-{
-    public interface IMembershipRoleService
-        where T : class, IMembershipUser
-    {
-        void AddRole(string roleName);
+namespace Umbraco.Cms.Core.Services;
 
-        IEnumerable GetAllRoles();
+public interface IMembershipRoleService
+    where T : class, IMembershipUser
+{
+    void AddRole(string roleName);
 
-        IEnumerable? GetAllRoles(int memberId);
+    IEnumerable GetAllRoles();
 
-        IEnumerable GetAllRoles(string? username);
+    IEnumerable? GetAllRoles(int memberId);
 
-        IEnumerable GetAllRolesIds();
+    IEnumerable GetAllRoles(string? username);
 
-        IEnumerable GetAllRolesIds(int memberId);
+    IEnumerable GetAllRolesIds();
 
-        IEnumerable GetAllRolesIds(string username);
+    IEnumerable GetAllRolesIds(int memberId);
 
-        IEnumerable GetMembersInRole(string roleName);
+    IEnumerable GetAllRolesIds(string username);
 
-        IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith);
+    IEnumerable GetMembersInRole(string roleName);
 
-        bool DeleteRole(string roleName, bool throwIfBeingUsed);
+    IEnumerable FindMembersInRole(string roleName, string usernameToMatch,
+        StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith);
 
-        void AssignRole(string username, string roleName);
+    bool DeleteRole(string roleName, bool throwIfBeingUsed);
 
-        void AssignRoles(string[] usernames, string[] roleNames);
+    void AssignRole(string username, string roleName);
 
-        void DissociateRole(string username, string roleName);
+    void AssignRoles(string[] usernames, string[] roleNames);
 
-        void DissociateRoles(string[] usernames, string[] roleNames);
+    void DissociateRole(string username, string roleName);
 
-        void AssignRole(int memberId, string roleName);
+    void DissociateRoles(string[] usernames, string[] roleNames);
 
-        void AssignRoles(int[] memberIds, string[] roleNames);
+    void AssignRole(int memberId, string roleName);
 
-        void DissociateRole(int memberId, string roleName);
+    void AssignRoles(int[] memberIds, string[] roleNames);
 
-        void DissociateRoles(int[] memberIds, string[] roleNames);
+    void DissociateRole(int memberId, string roleName);
 
-        void ReplaceRoles(string[] usernames, string[] roleNames);
+    void DissociateRoles(int[] memberIds, string[] roleNames);
 
-        void ReplaceRoles(int[] memberIds, string[] roleNames);
+    void ReplaceRoles(string[] usernames, string[] roleNames);
 
-    }
+    void ReplaceRoles(int[] memberIds, string[] roleNames);
 }
diff --git a/src/Umbraco.Core/Services/IMembershipUserService.cs b/src/Umbraco.Core/Services/IMembershipUserService.cs
index a2aca2821e7d..dc57adcfa5bf 100644
--- a/src/Umbraco.Core/Services/IMembershipUserService.cs
+++ b/src/Umbraco.Core/Services/IMembershipUserService.cs
@@ -1,24 +1,27 @@
 using Umbraco.Cms.Core.Models.Membership;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Defines part of the UserService, which is specific to methods used by the membership provider.
+/// 
+/// 
+///     Idea is to have this is an isolated interface so that it can be easily 'replaced' in the membership provider impl.
+/// 
+public interface IMembershipUserService : IMembershipMemberService
 {
     /// 
-    /// Defines part of the UserService, which is specific to methods used by the membership provider.
+    ///     Creates and persists a new User
     /// 
     /// 
-    /// Idea is to have this is an isolated interface so that it can be easily 'replaced' in the membership provider impl.
+    ///     The user will be saved in the database and returned with an Id.
+    ///     This method is convenient when you need to perform operations, which needs the
+    ///     Id of the user once its been created.
     /// 
-    public interface IMembershipUserService : IMembershipMemberService
-    {
-        /// 
-        /// Creates and persists a new User
-        /// 
-        /// The user will be saved in the database and returned with an Id.
-        /// This method is convenient when you need to perform operations, which needs the
-        /// Id of the user once its been created.
-        /// Username of the User to create
-        /// Email of the User to create
-        /// 
-        IUser CreateUserWithIdentity(string username, string email);
-    }
+    /// Username of the User to create
+    /// Email of the User to create
+    /// 
+    ///     
+    /// 
+    IUser CreateUserWithIdentity(string username, string email);
 }
diff --git a/src/Umbraco.Core/Services/IMetricsConsentService.cs b/src/Umbraco.Core/Services/IMetricsConsentService.cs
index e55cfd71d031..f803bb4cf14b 100644
--- a/src/Umbraco.Core/Services/IMetricsConsentService.cs
+++ b/src/Umbraco.Core/Services/IMetricsConsentService.cs
@@ -1,11 +1,10 @@
 using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface IMetricsConsentService
 {
-    public interface IMetricsConsentService
-    {
-        TelemetryLevel GetConsentLevel();
+    TelemetryLevel GetConsentLevel();
 
-        void SetConsentLevel(TelemetryLevel telemetryLevel);
-    }
+    void SetConsentLevel(TelemetryLevel telemetryLevel);
 }
diff --git a/src/Umbraco.Core/Services/INodeCountService.cs b/src/Umbraco.Core/Services/INodeCountService.cs
index 50d91c1512e0..86a1c4b14c12 100644
--- a/src/Umbraco.Core/Services/INodeCountService.cs
+++ b/src/Umbraco.Core/Services/INodeCountService.cs
@@ -1,10 +1,7 @@
-using System;
+namespace Umbraco.Cms.Core.Services;
 
-namespace Umbraco.Cms.Core.Services
+public interface INodeCountService
 {
-    public interface INodeCountService
-    {
-        int GetNodeCount(Guid nodeType);
-        int GetMediaCount();
-    }
+    int GetNodeCount(Guid nodeType);
+    int GetMediaCount();
 }
diff --git a/src/Umbraco.Core/Services/INotificationService.cs b/src/Umbraco.Core/Services/INotificationService.cs
index cf65b1aa67f9..81c21de2166f 100644
--- a/src/Umbraco.Core/Services/INotificationService.cs
+++ b/src/Umbraco.Core/Services/INotificationService.cs
@@ -1,89 +1,88 @@
-using System;
-using System.Collections.Generic;
-using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Models.Entities;
 using Umbraco.Cms.Core.Models.Membership;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface INotificationService : IService
 {
-    public interface INotificationService : IService
-    {
-        /// 
-        /// Sends the notifications for the specified user regarding the specified nodes and action.
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        void SendNotifications(IUser operatingUser, IEnumerable entities, string? action, string? actionName, Uri siteUri,
-                               Func<(IUser user, NotificationEmailSubjectParams subject), string> createSubject,
-                               Func<(IUser user, NotificationEmailBodyParams body, bool isHtml), string> createBody);
+    /// 
+    ///     Sends the notifications for the specified user regarding the specified nodes and action.
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    void SendNotifications(IUser operatingUser, IEnumerable entities, string? action, string? actionName,
+        Uri siteUri,
+        Func<(IUser user, NotificationEmailSubjectParams subject), string> createSubject,
+        Func<(IUser user, NotificationEmailBodyParams body, bool isHtml), string> createBody);
 
-        /// 
-        /// Gets the notifications for the user
-        /// 
-        /// 
-        /// 
-        IEnumerable? GetUserNotifications(IUser user);
+    /// 
+    ///     Gets the notifications for the user
+    /// 
+    /// 
+    /// 
+    IEnumerable? GetUserNotifications(IUser user);
 
-        /// 
-        /// Gets the notifications for the user based on the specified node path
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// Notifications are inherited from the parent so any child node will also have notifications assigned based on it's parent (ancestors)
-        /// 
-        IEnumerable? GetUserNotifications(IUser? user, string path);
+    /// 
+    ///     Gets the notifications for the user based on the specified node path
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    ///     Notifications are inherited from the parent so any child node will also have notifications assigned based on it's
+    ///     parent (ancestors)
+    /// 
+    IEnumerable? GetUserNotifications(IUser? user, string path);
 
-        /// 
-        /// Returns the notifications for an entity
-        /// 
-        /// 
-        /// 
-        IEnumerable? GetEntityNotifications(IEntity entity);
+    /// 
+    ///     Returns the notifications for an entity
+    /// 
+    /// 
+    /// 
+    IEnumerable? GetEntityNotifications(IEntity entity);
 
-        /// 
-        /// Deletes notifications by entity
-        /// 
-        /// 
-        void DeleteNotifications(IEntity entity);
+    /// 
+    ///     Deletes notifications by entity
+    /// 
+    /// 
+    void DeleteNotifications(IEntity entity);
 
-        /// 
-        /// Deletes notifications by user
-        /// 
-        /// 
-        void DeleteNotifications(IUser user);
+    /// 
+    ///     Deletes notifications by user
+    /// 
+    /// 
+    void DeleteNotifications(IUser user);
 
-        /// 
-        /// Delete notifications by user and entity
-        /// 
-        /// 
-        /// 
-        void DeleteNotifications(IUser user, IEntity entity);
+    /// 
+    ///     Delete notifications by user and entity
+    /// 
+    /// 
+    /// 
+    void DeleteNotifications(IUser user, IEntity entity);
 
-        /// 
-        /// Sets the specific notifications for the user and entity
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// This performs a full replace
-        /// 
-        IEnumerable? SetNotifications(IUser? user, IEntity entity, string[] actions);
+    /// 
+    ///     Sets the specific notifications for the user and entity
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    ///     This performs a full replace
+    /// 
+    IEnumerable? SetNotifications(IUser? user, IEntity entity, string[] actions);
 
-        /// 
-        /// Creates a new notification
-        /// 
-        /// 
-        /// 
-        /// The action letter - note: this is a string for future compatibility
-        /// 
-        Notification CreateNotification(IUser user, IEntity entity, string action);
-    }
+    /// 
+    ///     Creates a new notification
+    /// 
+    /// 
+    /// 
+    /// The action letter - note: this is a string for future compatibility
+    /// 
+    Notification CreateNotification(IUser user, IEntity entity, string action);
 }
diff --git a/src/Umbraco.Core/Services/IPackagingService.cs b/src/Umbraco.Core/Services/IPackagingService.cs
index 842989835442..c6433bbd2a9a 100644
--- a/src/Umbraco.Core/Services/IPackagingService.cs
+++ b/src/Umbraco.Core/Services/IPackagingService.cs
@@ -1,63 +1,60 @@
-using System.Collections.Generic;
-using System.IO;
 using System.Xml.Linq;
 using Umbraco.Cms.Core.Models.Packaging;
 using Umbraco.Cms.Core.Packaging;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface IPackagingService : IService
 {
-    public interface IPackagingService : IService
-    {
-        /// 
-        /// Returns a  result from an umbraco package file (zip)
-        /// 
-        /// 
-        /// 
-        CompiledPackage GetCompiledPackageInfo(XDocument packageXml);
-
-        /// 
-        /// Installs the data, entities, objects contained in an umbraco package file (zip)
-        /// 
-        /// 
-        /// 
-        InstallationSummary InstallCompiledPackageData(FileInfo packageXmlFile, int userId = Constants.Security.SuperUserId);
-
-        InstallationSummary InstallCompiledPackageData(XDocument? packageXml, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Returns the advertised installed packages
-        /// 
-        /// 
-        IEnumerable GetAllInstalledPackages();
-
-        InstalledPackage? GetInstalledPackageByName(string packageName);
-
-        /// 
-        /// Returns the created packages
-        /// 
-        /// 
-        IEnumerable GetAllCreatedPackages();
-
-        /// 
-        /// Returns a created package by id
-        /// 
-        /// 
-        /// 
-        PackageDefinition? GetCreatedPackageById(int id);
-
-        void DeleteCreatedPackage(int id, int userId = Constants.Security.SuperUserId);
-
-        /// 
-        /// Persists a package definition to storage
-        /// 
-        /// 
-        bool SaveCreatedPackage(PackageDefinition definition);
-
-        /// 
-        /// Creates the package file and returns it's physical path
-        /// 
-        /// 
-        string ExportCreatedPackage(PackageDefinition definition);
-
-    }
+    /// 
+    ///     Returns a  result from an umbraco package file (zip)
+    /// 
+    /// 
+    /// 
+    CompiledPackage GetCompiledPackageInfo(XDocument packageXml);
+
+    /// 
+    ///     Installs the data, entities, objects contained in an umbraco package file (zip)
+    /// 
+    /// 
+    /// 
+    InstallationSummary
+        InstallCompiledPackageData(FileInfo packageXmlFile, int userId = Constants.Security.SuperUserId);
+
+    InstallationSummary InstallCompiledPackageData(XDocument? packageXml, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Returns the advertised installed packages
+    /// 
+    /// 
+    IEnumerable GetAllInstalledPackages();
+
+    InstalledPackage? GetInstalledPackageByName(string packageName);
+
+    /// 
+    ///     Returns the created packages
+    /// 
+    /// 
+    IEnumerable GetAllCreatedPackages();
+
+    /// 
+    ///     Returns a created package by id
+    /// 
+    /// 
+    /// 
+    PackageDefinition? GetCreatedPackageById(int id);
+
+    void DeleteCreatedPackage(int id, int userId = Constants.Security.SuperUserId);
+
+    /// 
+    ///     Persists a package definition to storage
+    /// 
+    /// 
+    bool SaveCreatedPackage(PackageDefinition definition);
+
+    /// 
+    ///     Creates the package file and returns it's physical path
+    /// 
+    /// 
+    string ExportCreatedPackage(PackageDefinition definition);
 }
diff --git a/src/Umbraco.Core/Services/IPropertyValidationService.cs b/src/Umbraco.Core/Services/IPropertyValidationService.cs
index c2b8824340ae..fd97db652220 100644
--- a/src/Umbraco.Core/Services/IPropertyValidationService.cs
+++ b/src/Umbraco.Core/Services/IPropertyValidationService.cs
@@ -1,39 +1,37 @@
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations;
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.PropertyEditors;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface IPropertyValidationService
 {
-    public interface IPropertyValidationService
-    {
-        /// 
-        /// Validates the content item's properties pass validation rules
-        /// 
-        bool IsPropertyDataValid(IContent content, out IProperty[] invalidProperties, CultureImpact? impact);
+    /// 
+    ///     Validates the content item's properties pass validation rules
+    /// 
+    bool IsPropertyDataValid(IContent content, out IProperty[] invalidProperties, CultureImpact? impact);
 
-        /// 
-        /// Gets a value indicating whether the property has valid values.
-        /// 
-        bool IsPropertyValid(IProperty property, string culture = "*", string segment = "*");
+    /// 
+    ///     Gets a value indicating whether the property has valid values.
+    /// 
+    bool IsPropertyValid(IProperty property, string culture = "*", string segment = "*");
 
-        /// 
-        /// Validates a property value.
-        /// 
-        IEnumerable ValidatePropertyValue(
-            IDataEditor editor,
-            IDataType dataType,
-            object? postedValue,
-            bool isRequired,
-            string? validationRegExp,
-            string? isRequiredMessage,
-            string? validationRegExpMessage);
+    /// 
+    ///     Validates a property value.
+    /// 
+    IEnumerable ValidatePropertyValue(
+        IDataEditor editor,
+        IDataType dataType,
+        object? postedValue,
+        bool isRequired,
+        string? validationRegExp,
+        string? isRequiredMessage,
+        string? validationRegExpMessage);
 
-        /// 
-        /// Validates a property value.
-        /// 
-        IEnumerable ValidatePropertyValue(
-            IPropertyType propertyType,
-            object? postedValue);
-    }
+    /// 
+    ///     Validates a property value.
+    /// 
+    IEnumerable ValidatePropertyValue(
+        IPropertyType propertyType,
+        object? postedValue);
 }
diff --git a/src/Umbraco.Core/Services/IPublicAccessService.cs b/src/Umbraco.Core/Services/IPublicAccessService.cs
index 96d8ca5d1ba4..5088666a04eb 100644
--- a/src/Umbraco.Core/Services/IPublicAccessService.cs
+++ b/src/Umbraco.Core/Services/IPublicAccessService.cs
@@ -1,73 +1,70 @@
-using System.Collections.Generic;
-using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
-{
-    public interface IPublicAccessService : IService
-    {
-
-        /// 
-        /// Gets all defined entries and associated rules
-        /// 
-        /// 
-        IEnumerable GetAll();
+namespace Umbraco.Cms.Core.Services;
 
-        /// 
-        /// Gets the entry defined for the content item's path
-        /// 
-        /// 
-        /// Returns null if no entry is found
-        PublicAccessEntry? GetEntryForContent(IContent content);
+public interface IPublicAccessService : IService
+{
+    /// 
+    ///     Gets all defined entries and associated rules
+    /// 
+    /// 
+    IEnumerable GetAll();
 
-        /// 
-        /// Gets the entry defined for the content item based on a content path
-        /// 
-        /// 
-        /// Returns null if no entry is found
-        PublicAccessEntry? GetEntryForContent(string contentPath);
+    /// 
+    ///     Gets the entry defined for the content item's path
+    /// 
+    /// 
+    /// Returns null if no entry is found
+    PublicAccessEntry? GetEntryForContent(IContent content);
 
-        /// 
-        /// Returns true if the content has an entry for it's path
-        /// 
-        /// 
-        /// 
-        Attempt IsProtected(IContent content);
+    /// 
+    ///     Gets the entry defined for the content item based on a content path
+    /// 
+    /// 
+    /// Returns null if no entry is found
+    PublicAccessEntry? GetEntryForContent(string contentPath);
 
-        /// 
-        /// Returns true if the content has an entry based on a content path
-        /// 
-        /// 
-        /// 
-        Attempt IsProtected(string contentPath);
+    /// 
+    ///     Returns true if the content has an entry for it's path
+    /// 
+    /// 
+    /// 
+    Attempt IsProtected(IContent content);
 
-        /// 
-        /// Adds a rule if the entry doesn't already exist
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        Attempt?> AddRule(IContent content, string ruleType, string ruleValue);
+    /// 
+    ///     Returns true if the content has an entry based on a content path
+    /// 
+    /// 
+    /// 
+    Attempt IsProtected(string contentPath);
 
-        /// 
-        /// Removes a rule
-        /// 
-        /// 
-        /// 
-        /// 
-        Attempt RemoveRule(IContent content, string ruleType, string ruleValue);
+    /// 
+    ///     Adds a rule if the entry doesn't already exist
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    Attempt?> AddRule(IContent content, string ruleType,
+        string ruleValue);
 
-        /// 
-        /// Saves the entry
-        /// 
-        /// 
-        Attempt Save(PublicAccessEntry entry);
+    /// 
+    ///     Removes a rule
+    /// 
+    /// 
+    /// 
+    /// 
+    Attempt RemoveRule(IContent content, string ruleType, string ruleValue);
 
-        /// 
-        /// Deletes the entry and all associated rules
-        /// 
-        /// 
-        Attempt Delete(PublicAccessEntry entry);
+    /// 
+    ///     Saves the entry
+    /// 
+    /// 
+    Attempt Save(PublicAccessEntry entry);
 
-    }
+    /// 
+    ///     Deletes the entry and all associated rules
+    /// 
+    /// 
+    Attempt Delete(PublicAccessEntry entry);
 }
diff --git a/src/Umbraco.Core/Services/IRedirectUrlService.cs b/src/Umbraco.Core/Services/IRedirectUrlService.cs
index 3c061db4660b..5413f82d5867 100644
--- a/src/Umbraco.Core/Services/IRedirectUrlService.cs
+++ b/src/Umbraco.Core/Services/IRedirectUrlService.cs
@@ -1,95 +1,91 @@
-using System;
-using System.Collections.Generic;
-using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+/// 
+public interface IRedirectUrlService : IService
 {
     /// 
-    ///
+    ///     Registers a redirect URL.
     /// 
-    public interface IRedirectUrlService : IService
-    {
-        /// 
-        /// Registers a redirect URL.
-        /// 
-        /// The Umbraco URL route.
-        /// The content unique key.
-        /// The culture.
-        /// Is a proper Umbraco route eg /path/to/foo or 123/path/tofoo.
-        void Register(string url, Guid contentKey, string? culture = null);
+    /// The Umbraco URL route.
+    /// The content unique key.
+    /// The culture.
+    /// Is a proper Umbraco route eg /path/to/foo or 123/path/tofoo.
+    void Register(string url, Guid contentKey, string? culture = null);
 
-        /// 
-        /// Deletes all redirect URLs for a given content.
-        /// 
-        /// The content unique key.
-        void DeleteContentRedirectUrls(Guid contentKey);
+    /// 
+    ///     Deletes all redirect URLs for a given content.
+    /// 
+    /// The content unique key.
+    void DeleteContentRedirectUrls(Guid contentKey);
 
-        /// 
-        /// Deletes a redirect URL.
-        /// 
-        /// The redirect URL to delete.
-        void Delete(IRedirectUrl redirectUrl);
+    /// 
+    ///     Deletes a redirect URL.
+    /// 
+    /// The redirect URL to delete.
+    void Delete(IRedirectUrl redirectUrl);
 
-        /// 
-        /// Deletes a redirect URL.
-        /// 
-        /// The redirect URL identifier.
-        void Delete(Guid id);
+    /// 
+    ///     Deletes a redirect URL.
+    /// 
+    /// The redirect URL identifier.
+    void Delete(Guid id);
 
-        /// 
-        /// Deletes all redirect URLs.
-        /// 
-        void DeleteAll();
+    /// 
+    ///     Deletes all redirect URLs.
+    /// 
+    void DeleteAll();
 
-        /// 
-        /// Gets the most recent redirect URLs corresponding to an Umbraco redirect URL route.
-        /// 
-        /// The Umbraco redirect URL route.
-        /// The most recent redirect URLs corresponding to the route.
-        IRedirectUrl? GetMostRecentRedirectUrl(string url);
+    /// 
+    ///     Gets the most recent redirect URLs corresponding to an Umbraco redirect URL route.
+    /// 
+    /// The Umbraco redirect URL route.
+    /// The most recent redirect URLs corresponding to the route.
+    IRedirectUrl? GetMostRecentRedirectUrl(string url);
 
-        /// 
-        /// Gets the most recent redirect URLs corresponding to an Umbraco redirect URL route.
-        /// 
-        /// The Umbraco redirect URL route.
-        /// The culture of the request.
-        /// The most recent redirect URLs corresponding to the route.
-        IRedirectUrl? GetMostRecentRedirectUrl(string url, string? culture);
+    /// 
+    ///     Gets the most recent redirect URLs corresponding to an Umbraco redirect URL route.
+    /// 
+    /// The Umbraco redirect URL route.
+    /// The culture of the request.
+    /// The most recent redirect URLs corresponding to the route.
+    IRedirectUrl? GetMostRecentRedirectUrl(string url, string? culture);
 
-        /// 
-        /// Gets all redirect URLs for a content item.
-        /// 
-        /// The content unique key.
-        /// All redirect URLs for the content item.
-        IEnumerable GetContentRedirectUrls(Guid contentKey);
+    /// 
+    ///     Gets all redirect URLs for a content item.
+    /// 
+    /// The content unique key.
+    /// All redirect URLs for the content item.
+    IEnumerable GetContentRedirectUrls(Guid contentKey);
 
-        /// 
-        /// Gets all redirect URLs.
-        /// 
-        /// The page index.
-        /// The page size.
-        /// The total count of redirect URLs.
-        /// The redirect URLs.
-        IEnumerable GetAllRedirectUrls(long pageIndex, int pageSize, out long total);
+    /// 
+    ///     Gets all redirect URLs.
+    /// 
+    /// The page index.
+    /// The page size.
+    /// The total count of redirect URLs.
+    /// The redirect URLs.
+    IEnumerable GetAllRedirectUrls(long pageIndex, int pageSize, out long total);
 
-        /// 
-        /// Gets all redirect URLs below a given content item.
-        /// 
-        /// The content unique identifier.
-        /// The page index.
-        /// The page size.
-        /// The total count of redirect URLs.
-        /// The redirect URLs.
-        IEnumerable GetAllRedirectUrls(int rootContentId, long pageIndex, int pageSize, out long total);
+    /// 
+    ///     Gets all redirect URLs below a given content item.
+    /// 
+    /// The content unique identifier.
+    /// The page index.
+    /// The page size.
+    /// The total count of redirect URLs.
+    /// The redirect URLs.
+    IEnumerable GetAllRedirectUrls(int rootContentId, long pageIndex, int pageSize, out long total);
 
-        /// 
-        /// Searches for all redirect URLs that contain a given search term in their URL property.
-        /// 
-        /// The term to search for.
-        /// The page index.
-        /// The page size.
-        /// The total count of redirect URLs.
-        /// The redirect URLs.
-        IEnumerable SearchRedirectUrls(string searchTerm, long pageIndex, int pageSize, out long total);
-    }
+    /// 
+    ///     Searches for all redirect URLs that contain a given search term in their URL property.
+    /// 
+    /// The term to search for.
+    /// The page index.
+    /// The page size.
+    /// The total count of redirect URLs.
+    /// The redirect URLs.
+    IEnumerable SearchRedirectUrls(string searchTerm, long pageIndex, int pageSize, out long total);
 }
diff --git a/src/Umbraco.Core/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs
index a0825611f73f..22782b9bba92 100644
--- a/src/Umbraco.Core/Services/IRelationService.cs
+++ b/src/Umbraco.Core/Services/IRelationService.cs
@@ -1,357 +1,356 @@
-using System;
-using System.Collections.Generic;
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Models.Entities;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface IRelationService : IService
 {
-    public interface IRelationService : IService
-    {
-        /// 
-        /// Gets a  by its Id
-        /// 
-        /// Id of the 
-        /// A  object
-        IRelation? GetById(int id);
-
-        /// 
-        /// Gets a  by its Id
-        /// 
-        /// Id of the 
-        /// A  object
-        IRelationType? GetRelationTypeById(int id);
-
-        /// 
-        /// Gets a  by its Id
-        /// 
-        /// Id of the 
-        /// A  object
-        IRelationType? GetRelationTypeById(Guid id);
-
-        /// 
-        /// Gets a  by its Alias
-        /// 
-        /// Alias of the 
-        /// A  object
-        IRelationType? GetRelationTypeByAlias(string alias);
-
-        /// 
-        /// Gets all  objects
-        /// 
-        /// Optional array of integer ids to return relations for
-        /// An enumerable list of  objects
-        IEnumerable GetAllRelations(params int[] ids);
-
-        /// 
-        /// Gets all  objects by their 
-        /// 
-        ///  to retrieve Relations for
-        /// An enumerable list of  objects
-        IEnumerable? GetAllRelationsByRelationType(IRelationType relationType);
-
-        /// 
-        /// Gets all  objects by their 's Id
-        /// 
-        /// Id of the  to retrieve Relations for
-        /// An enumerable list of  objects
-        IEnumerable? GetAllRelationsByRelationType(int relationTypeId);
-
-        /// 
-        /// Gets all  objects
-        /// 
-        /// Optional array of integer ids to return relationtypes for
-        /// An enumerable list of  objects
-        IEnumerable GetAllRelationTypes(params int[] ids);
-
-        /// 
-        /// Gets a list of  objects by their parent Id
-        /// 
-        /// Id of the parent to retrieve relations for
-        /// An enumerable list of  objects
-        IEnumerable? GetByParentId(int id);
-
-        /// 
-        /// Gets a list of  objects by their parent Id
-        /// 
-        /// Id of the parent to retrieve relations for
-        /// Alias of the type of relation to retrieve
-        /// An enumerable list of  objects
-        IEnumerable? GetByParentId(int id, string relationTypeAlias);
-
-        /// 
-        /// Gets a list of  objects by their parent entity
-        /// 
-        /// Parent Entity to retrieve relations for
-        /// An enumerable list of  objects
-        IEnumerable? GetByParent(IUmbracoEntity parent);
-
-        /// 
-        /// Gets a list of  objects by their parent entity
-        /// 
-        /// Parent Entity to retrieve relations for
-        /// Alias of the type of relation to retrieve
-        /// An enumerable list of  objects
-        IEnumerable GetByParent(IUmbracoEntity parent, string relationTypeAlias);
-
-        /// 
-        /// Gets a list of  objects by their child Id
-        /// 
-        /// Id of the child to retrieve relations for
-        /// An enumerable list of  objects
-        IEnumerable GetByChildId(int id);
-
-        /// 
-        /// Gets a list of  objects by their child Id
-        /// 
-        /// Id of the child to retrieve relations for
-        /// Alias of the type of relation to retrieve
-        /// An enumerable list of  objects
-        IEnumerable GetByChildId(int id, string relationTypeAlias);
-
-        /// 
-        /// Gets a list of  objects by their child Entity
-        /// 
-        /// Child Entity to retrieve relations for
-        /// An enumerable list of  objects
-        IEnumerable GetByChild(IUmbracoEntity child);
-
-        /// 
-        /// Gets a list of  objects by their child Entity
-        /// 
-        /// Child Entity to retrieve relations for
-        /// Alias of the type of relation to retrieve
-        /// An enumerable list of  objects
-        IEnumerable GetByChild(IUmbracoEntity child, string relationTypeAlias);
-
-        /// 
-        /// Gets a list of  objects by their child or parent Id.
-        /// Using this method will get you all relations regards of it being a child or parent relation.
-        /// 
-        /// Id of the child or parent to retrieve relations for
-        /// An enumerable list of  objects
-        IEnumerable GetByParentOrChildId(int id);
-
-        IEnumerable GetByParentOrChildId(int id, string relationTypeAlias);
-
-        /// 
-        /// Gets a relation by the unique combination of parentId, childId and relationType.
-        /// 
-        /// The id of the parent item.
-        /// The id of the child item.
-        /// The RelationType.
-        /// The relation or null
-        IRelation? GetByParentAndChildId(int parentId, int childId, IRelationType relationType);
-
-        /// 
-        /// Gets a list of  objects by the Name of the 
-        /// 
-        /// Name of the  to retrieve Relations for
-        /// An enumerable list of  objects
-        IEnumerable GetByRelationTypeName(string relationTypeName);
-
-        /// 
-        /// Gets a list of  objects by the Alias of the 
-        /// 
-        /// Alias of the  to retrieve Relations for
-        /// An enumerable list of  objects
-        IEnumerable GetByRelationTypeAlias(string relationTypeAlias);
-
-        /// 
-        /// Gets a list of  objects by the Id of the 
-        /// 
-        /// Id of the  to retrieve Relations for
-        /// An enumerable list of  objects
-        IEnumerable? GetByRelationTypeId(int relationTypeId);
-
-        /// 
-        /// Gets a paged result of 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        IEnumerable GetPagedByRelationTypeId(int relationTypeId, long pageIndex, int pageSize, out long totalRecords, Ordering? ordering = null);
-
-        /// 
-        /// Gets the Child object from a Relation as an 
-        /// 
-        /// Relation to retrieve child object from
-        /// An 
-        IUmbracoEntity? GetChildEntityFromRelation(IRelation relation);
-
-        /// 
-        /// Gets the Parent object from a Relation as an 
-        /// 
-        /// Relation to retrieve parent object from
-        /// An 
-        IUmbracoEntity? GetParentEntityFromRelation(IRelation relation);
-
-        /// 
-        /// Gets the Parent and Child objects from a Relation as a "/> with .
-        /// 
-        /// Relation to retrieve parent and child object from
-        /// Returns a Tuple with Parent (item1) and Child (item2)
-        Tuple? GetEntitiesFromRelation(IRelation relation);
-
-        /// 
-        /// Gets the Child objects from a list of Relations as a list of  objects.
-        /// 
-        /// List of relations to retrieve child objects from
-        /// An enumerable list of 
-        IEnumerable GetChildEntitiesFromRelations(IEnumerable relations);
-
-        /// 
-        /// Gets the Parent objects from a list of Relations as a list of  objects.
-        /// 
-        /// List of relations to retrieve parent objects from
-        /// An enumerable list of 
-        IEnumerable GetParentEntitiesFromRelations(IEnumerable relations);
-
-        /// 
-        /// Returns paged parent entities for a related child id
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// An enumerable list of 
-        IEnumerable GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes);
-
-        /// 
-        /// Returns paged child entities for a related parent id
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// An enumerable list of 
-        IEnumerable GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes);
-
-        /// 
-        /// Gets the Parent and Child objects from a list of Relations as a list of  objects.
-        /// 
-        /// List of relations to retrieve parent and child objects from
-        /// An enumerable list of  with 
-        IEnumerable> GetEntitiesFromRelations(IEnumerable relations);
-
-        /// 
-        /// Relates two objects by their entity Ids.
-        /// 
-        /// Id of the parent
-        /// Id of the child
-        /// The type of relation to create
-        /// The created 
-        IRelation Relate(int parentId, int childId, IRelationType relationType);
-
-        /// 
-        /// Relates two objects that are based on the  interface.
-        /// 
-        /// Parent entity
-        /// Child entity
-        /// The type of relation to create
-        /// The created 
-        IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType);
-
-        /// 
-        /// Relates two objects by their entity Ids.
-        /// 
-        /// Id of the parent
-        /// Id of the child
-        /// Alias of the type of relation to create
-        /// The created 
-        IRelation Relate(int parentId, int childId, string relationTypeAlias);
-
-        /// 
-        /// Relates two objects that are based on the  interface.
-        /// 
-        /// Parent entity
-        /// Child entity
-        /// Alias of the type of relation to create
-        /// The created 
-        IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias);
-
-        /// 
-        /// Checks whether any relations exists for the passed in .
-        /// 
-        ///  to check for relations
-        /// Returns True if any relations exists for the given , otherwise False
-        bool HasRelations(IRelationType relationType);
-
-        /// 
-        /// Checks whether any relations exists for the passed in Id.
-        /// 
-        /// Id of an object to check relations for
-        /// Returns True if any relations exists with the given Id, otherwise False
-        bool IsRelated(int id);
-
-        /// 
-        /// Checks whether two items are related
-        /// 
-        /// Id of the Parent relation
-        /// Id of the Child relation
-        /// Returns True if any relations exists with the given Ids, otherwise False
-        bool AreRelated(int parentId, int childId);
-
-        /// 
-        /// Checks whether two items are related
-        /// 
-        /// Parent entity
-        /// Child entity
-        /// Returns True if any relations exist between the entities, otherwise False
-        bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child);
-
-        /// 
-        /// Checks whether two items are related
-        /// 
-        /// Parent entity
-        /// Child entity
-        /// Alias of the type of relation to create
-        /// Returns True if any relations exist between the entities, otherwise False
-        bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias);
-
-        /// 
-        /// Checks whether two items are related
-        /// 
-        /// Id of the Parent relation
-        /// Id of the Child relation
-        /// Alias of the type of relation to create
-        /// Returns True if any relations exist between the entities, otherwise False
-        bool AreRelated(int parentId, int childId, string relationTypeAlias);
-
-        /// 
-        /// Saves a 
-        /// 
-        /// Relation to save
-        void Save(IRelation relation);
-
-        void Save(IEnumerable relations);
-
-        /// 
-        /// Saves a 
-        /// 
-        /// RelationType to Save
-        void Save(IRelationType relationType);
-
-        /// 
-        /// Deletes a 
-        /// 
-        /// Relation to Delete
-        void Delete(IRelation relation);
-
-        /// 
-        /// Deletes a 
-        /// 
-        /// RelationType to Delete
-        void Delete(IRelationType relationType);
-
-        /// 
-        /// Deletes all  objects based on the passed in 
-        /// 
-        ///  to Delete Relations for
-        void DeleteRelationsOfType(IRelationType relationType);
-
-
-
-    }
+    /// 
+    ///     Gets a  by its Id
+    /// 
+    /// Id of the 
+    /// A  object
+    IRelation? GetById(int id);
+
+    /// 
+    ///     Gets a  by its Id
+    /// 
+    /// Id of the 
+    /// A  object
+    IRelationType? GetRelationTypeById(int id);
+
+    /// 
+    ///     Gets a  by its Id
+    /// 
+    /// Id of the 
+    /// A  object
+    IRelationType? GetRelationTypeById(Guid id);
+
+    /// 
+    ///     Gets a  by its Alias
+    /// 
+    /// Alias of the 
+    /// A  object
+    IRelationType? GetRelationTypeByAlias(string alias);
+
+    /// 
+    ///     Gets all  objects
+    /// 
+    /// Optional array of integer ids to return relations for
+    /// An enumerable list of  objects
+    IEnumerable GetAllRelations(params int[] ids);
+
+    /// 
+    ///     Gets all  objects by their 
+    /// 
+    ///  to retrieve Relations for
+    /// An enumerable list of  objects
+    IEnumerable? GetAllRelationsByRelationType(IRelationType relationType);
+
+    /// 
+    ///     Gets all  objects by their 's Id
+    /// 
+    /// Id of the  to retrieve Relations for
+    /// An enumerable list of  objects
+    IEnumerable? GetAllRelationsByRelationType(int relationTypeId);
+
+    /// 
+    ///     Gets all  objects
+    /// 
+    /// Optional array of integer ids to return relationtypes for
+    /// An enumerable list of  objects
+    IEnumerable GetAllRelationTypes(params int[] ids);
+
+    /// 
+    ///     Gets a list of  objects by their parent Id
+    /// 
+    /// Id of the parent to retrieve relations for
+    /// An enumerable list of  objects
+    IEnumerable? GetByParentId(int id);
+
+    /// 
+    ///     Gets a list of  objects by their parent Id
+    /// 
+    /// Id of the parent to retrieve relations for
+    /// Alias of the type of relation to retrieve
+    /// An enumerable list of  objects
+    IEnumerable? GetByParentId(int id, string relationTypeAlias);
+
+    /// 
+    ///     Gets a list of  objects by their parent entity
+    /// 
+    /// Parent Entity to retrieve relations for
+    /// An enumerable list of  objects
+    IEnumerable? GetByParent(IUmbracoEntity parent);
+
+    /// 
+    ///     Gets a list of  objects by their parent entity
+    /// 
+    /// Parent Entity to retrieve relations for
+    /// Alias of the type of relation to retrieve
+    /// An enumerable list of  objects
+    IEnumerable GetByParent(IUmbracoEntity parent, string relationTypeAlias);
+
+    /// 
+    ///     Gets a list of  objects by their child Id
+    /// 
+    /// Id of the child to retrieve relations for
+    /// An enumerable list of  objects
+    IEnumerable GetByChildId(int id);
+
+    /// 
+    ///     Gets a list of  objects by their child Id
+    /// 
+    /// Id of the child to retrieve relations for
+    /// Alias of the type of relation to retrieve
+    /// An enumerable list of  objects
+    IEnumerable GetByChildId(int id, string relationTypeAlias);
+
+    /// 
+    ///     Gets a list of  objects by their child Entity
+    /// 
+    /// Child Entity to retrieve relations for
+    /// An enumerable list of  objects
+    IEnumerable GetByChild(IUmbracoEntity child);
+
+    /// 
+    ///     Gets a list of  objects by their child Entity
+    /// 
+    /// Child Entity to retrieve relations for
+    /// Alias of the type of relation to retrieve
+    /// An enumerable list of  objects
+    IEnumerable GetByChild(IUmbracoEntity child, string relationTypeAlias);
+
+    /// 
+    ///     Gets a list of  objects by their child or parent Id.
+    ///     Using this method will get you all relations regards of it being a child or parent relation.
+    /// 
+    /// Id of the child or parent to retrieve relations for
+    /// An enumerable list of  objects
+    IEnumerable GetByParentOrChildId(int id);
+
+    IEnumerable GetByParentOrChildId(int id, string relationTypeAlias);
+
+    /// 
+    ///     Gets a relation by the unique combination of parentId, childId and relationType.
+    /// 
+    /// The id of the parent item.
+    /// The id of the child item.
+    /// The RelationType.
+    /// The relation or null
+    IRelation? GetByParentAndChildId(int parentId, int childId, IRelationType relationType);
+
+    /// 
+    ///     Gets a list of  objects by the Name of the 
+    /// 
+    /// Name of the  to retrieve Relations for
+    /// An enumerable list of  objects
+    IEnumerable GetByRelationTypeName(string relationTypeName);
+
+    /// 
+    ///     Gets a list of  objects by the Alias of the 
+    /// 
+    /// Alias of the  to retrieve Relations for
+    /// An enumerable list of  objects
+    IEnumerable GetByRelationTypeAlias(string relationTypeAlias);
+
+    /// 
+    ///     Gets a list of  objects by the Id of the 
+    /// 
+    /// Id of the  to retrieve Relations for
+    /// An enumerable list of  objects
+    IEnumerable? GetByRelationTypeId(int relationTypeId);
+
+    /// 
+    ///     Gets a paged result of 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    IEnumerable GetPagedByRelationTypeId(int relationTypeId, long pageIndex, int pageSize,
+        out long totalRecords, Ordering? ordering = null);
+
+    /// 
+    ///     Gets the Child object from a Relation as an 
+    /// 
+    /// Relation to retrieve child object from
+    /// An 
+    IUmbracoEntity? GetChildEntityFromRelation(IRelation relation);
+
+    /// 
+    ///     Gets the Parent object from a Relation as an 
+    /// 
+    /// Relation to retrieve parent object from
+    /// An 
+    IUmbracoEntity? GetParentEntityFromRelation(IRelation relation);
+
+    /// 
+    ///     Gets the Parent and Child objects from a Relation as a "/> with .
+    /// 
+    /// Relation to retrieve parent and child object from
+    /// Returns a Tuple with Parent (item1) and Child (item2)
+    Tuple? GetEntitiesFromRelation(IRelation relation);
+
+    /// 
+    ///     Gets the Child objects from a list of Relations as a list of  objects.
+    /// 
+    /// List of relations to retrieve child objects from
+    /// An enumerable list of 
+    IEnumerable GetChildEntitiesFromRelations(IEnumerable relations);
+
+    /// 
+    ///     Gets the Parent objects from a list of Relations as a list of  objects.
+    /// 
+    /// List of relations to retrieve parent objects from
+    /// An enumerable list of 
+    IEnumerable GetParentEntitiesFromRelations(IEnumerable relations);
+
+    /// 
+    ///     Returns paged parent entities for a related child id
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// An enumerable list of 
+    IEnumerable GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize,
+        out long totalChildren, params UmbracoObjectTypes[] entityTypes);
+
+    /// 
+    ///     Returns paged child entities for a related parent id
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// An enumerable list of 
+    IEnumerable GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize,
+        out long totalChildren, params UmbracoObjectTypes[] entityTypes);
+
+    /// 
+    ///     Gets the Parent and Child objects from a list of Relations as a list of  objects.
+    /// 
+    /// List of relations to retrieve parent and child objects from
+    /// An enumerable list of  with 
+    IEnumerable> GetEntitiesFromRelations(IEnumerable relations);
+
+    /// 
+    ///     Relates two objects by their entity Ids.
+    /// 
+    /// Id of the parent
+    /// Id of the child
+    /// The type of relation to create
+    /// The created 
+    IRelation Relate(int parentId, int childId, IRelationType relationType);
+
+    /// 
+    ///     Relates two objects that are based on the  interface.
+    /// 
+    /// Parent entity
+    /// Child entity
+    /// The type of relation to create
+    /// The created 
+    IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType);
+
+    /// 
+    ///     Relates two objects by their entity Ids.
+    /// 
+    /// Id of the parent
+    /// Id of the child
+    /// Alias of the type of relation to create
+    /// The created 
+    IRelation Relate(int parentId, int childId, string relationTypeAlias);
+
+    /// 
+    ///     Relates two objects that are based on the  interface.
+    /// 
+    /// Parent entity
+    /// Child entity
+    /// Alias of the type of relation to create
+    /// The created 
+    IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias);
+
+    /// 
+    ///     Checks whether any relations exists for the passed in .
+    /// 
+    ///  to check for relations
+    /// 
+    ///     Returns True if any relations exists for the given , otherwise False
+    /// 
+    bool HasRelations(IRelationType relationType);
+
+    /// 
+    ///     Checks whether any relations exists for the passed in Id.
+    /// 
+    /// Id of an object to check relations for
+    /// Returns True if any relations exists with the given Id, otherwise False
+    bool IsRelated(int id);
+
+    /// 
+    ///     Checks whether two items are related
+    /// 
+    /// Id of the Parent relation
+    /// Id of the Child relation
+    /// Returns True if any relations exists with the given Ids, otherwise False
+    bool AreRelated(int parentId, int childId);
+
+    /// 
+    ///     Checks whether two items are related
+    /// 
+    /// Parent entity
+    /// Child entity
+    /// Returns True if any relations exist between the entities, otherwise False
+    bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child);
+
+    /// 
+    ///     Checks whether two items are related
+    /// 
+    /// Parent entity
+    /// Child entity
+    /// Alias of the type of relation to create
+    /// Returns True if any relations exist between the entities, otherwise False
+    bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias);
+
+    /// 
+    ///     Checks whether two items are related
+    /// 
+    /// Id of the Parent relation
+    /// Id of the Child relation
+    /// Alias of the type of relation to create
+    /// Returns True if any relations exist between the entities, otherwise False
+    bool AreRelated(int parentId, int childId, string relationTypeAlias);
+
+    /// 
+    ///     Saves a 
+    /// 
+    /// Relation to save
+    void Save(IRelation relation);
+
+    void Save(IEnumerable relations);
+
+    /// 
+    ///     Saves a 
+    /// 
+    /// RelationType to Save
+    void Save(IRelationType relationType);
+
+    /// 
+    ///     Deletes a 
+    /// 
+    /// Relation to Delete
+    void Delete(IRelation relation);
+
+    /// 
+    ///     Deletes a 
+    /// 
+    /// RelationType to Delete
+    void Delete(IRelationType relationType);
+
+    /// 
+    ///     Deletes all  objects based on the passed in 
+    /// 
+    ///  to Delete Relations for
+    void DeleteRelationsOfType(IRelationType relationType);
 }
diff --git a/src/Umbraco.Core/Services/IRuntime.cs b/src/Umbraco.Core/Services/IRuntime.cs
index caa430ce1f7e..53ac51f5851c 100644
--- a/src/Umbraco.Core/Services/IRuntime.cs
+++ b/src/Umbraco.Core/Services/IRuntime.cs
@@ -1,22 +1,19 @@
-using System.Threading;
-using System.Threading.Tasks;
 using Microsoft.Extensions.Hosting;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Defines the Umbraco runtime.
+/// 
+public interface IRuntime : IHostedService
 {
     /// 
-    /// Defines the Umbraco runtime.
+    ///     Gets the runtime state.
     /// 
-    public interface IRuntime : IHostedService
-    {
-        /// 
-        /// Gets the runtime state.
-        /// 
-        IRuntimeState State { get; }
+    IRuntimeState State { get; }
 
-        /// 
-        /// Stops and Starts the runtime using the original cancellation token.
-        /// 
-        Task RestartAsync();
-    }
+    /// 
+    ///     Stops and Starts the runtime using the original cancellation token.
+    /// 
+    Task RestartAsync();
 }
diff --git a/src/Umbraco.Core/Services/IRuntimeState.cs b/src/Umbraco.Core/Services/IRuntimeState.cs
index 3c765a07480a..a57667101099 100644
--- a/src/Umbraco.Core/Services/IRuntimeState.cs
+++ b/src/Umbraco.Core/Services/IRuntimeState.cs
@@ -1,65 +1,62 @@
-using System;
-using System.Collections.Generic;
 using Umbraco.Cms.Core.Exceptions;
 using Umbraco.Cms.Core.Semver;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Represents the state of the Umbraco runtime.
+/// 
+public interface IRuntimeState
 {
     /// 
-    /// Represents the state of the Umbraco runtime.
+    ///     Gets the version of the executing code.
     /// 
-    public interface IRuntimeState
-    {
-        /// 
-        /// Gets the version of the executing code.
-        /// 
-        Version Version { get; }
+    Version Version { get; }
 
-        /// 
-        /// Gets the version comment of the executing code.
-        /// 
-        string VersionComment { get; }
+    /// 
+    ///     Gets the version comment of the executing code.
+    /// 
+    string VersionComment { get; }
 
-        /// 
-        /// Gets the semantic version of the executing code.
-        /// 
-        SemVersion SemanticVersion { get; }
+    /// 
+    ///     Gets the semantic version of the executing code.
+    /// 
+    SemVersion SemanticVersion { get; }
 
-        /// 
-        /// Gets the runtime level of execution.
-        /// 
-        RuntimeLevel Level { get; }
+    /// 
+    ///     Gets the runtime level of execution.
+    /// 
+    RuntimeLevel Level { get; }
 
-        /// 
-        /// Gets the reason for the runtime level of execution.
-        /// 
-        RuntimeLevelReason Reason { get; }
+    /// 
+    ///     Gets the reason for the runtime level of execution.
+    /// 
+    RuntimeLevelReason Reason { get; }
 
-        /// 
-        /// Gets the current migration state.
-        /// 
-        string? CurrentMigrationState { get; }
+    /// 
+    ///     Gets the current migration state.
+    /// 
+    string? CurrentMigrationState { get; }
 
-        /// 
-        /// Gets the final migration state.
-        /// 
-        string? FinalMigrationState { get; }
+    /// 
+    ///     Gets the final migration state.
+    /// 
+    string? FinalMigrationState { get; }
 
-        /// 
-        /// Gets the exception that caused the boot to fail.
-        /// 
-        BootFailedException? BootFailedException { get; }
+    /// 
+    ///     Gets the exception that caused the boot to fail.
+    /// 
+    BootFailedException? BootFailedException { get; }
 
-        /// 
-        /// Determines the runtime level.
-        /// 
-        void DetermineRuntimeLevel();
+    /// 
+    ///     Returns any state data that was collected during startup
+    /// 
+    IReadOnlyDictionary StartupState { get; }
 
-        void Configure(RuntimeLevel level, RuntimeLevelReason reason, Exception? bootFailedException = null);
+    /// 
+    ///     Determines the runtime level.
+    /// 
+    void DetermineRuntimeLevel();
 
-        /// 
-        /// Returns any state data that was collected during startup
-        /// 
-        IReadOnlyDictionary StartupState { get; }
-    }
+    void Configure(RuntimeLevel level, RuntimeLevelReason reason, Exception? bootFailedException = null);
 }
diff --git a/src/Umbraco.Core/Services/ISectionService.cs b/src/Umbraco.Core/Services/ISectionService.cs
index ded733963b8b..425637908583 100644
--- a/src/Umbraco.Core/Services/ISectionService.cs
+++ b/src/Umbraco.Core/Services/ISectionService.cs
@@ -1,27 +1,25 @@
-using System.Collections.Generic;
-using Umbraco.Cms.Core.Sections;
+using Umbraco.Cms.Core.Sections;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface ISectionService
 {
-    public interface ISectionService
-    {
-        /// 
-        /// The cache storage for all applications
-        /// 
-        IEnumerable GetSections();
+    /// 
+    ///     The cache storage for all applications
+    /// 
+    IEnumerable GetSections();
 
-        /// 
-        /// Get the user group's allowed sections
-        /// 
-        /// 
-        /// 
-        IEnumerable GetAllowedSections(int userId);
+    /// 
+    ///     Get the user group's allowed sections
+    /// 
+    /// 
+    /// 
+    IEnumerable GetAllowedSections(int userId);
 
-        /// 
-        /// Gets the application by its alias.
-        /// 
-        /// The application alias.
-        /// 
-        ISection? GetByAlias(string appAlias);
-    }
+    /// 
+    ///     Gets the application by its alias.
+    /// 
+    /// The application alias.
+    /// 
+    ISection? GetByAlias(string appAlias);
 }
diff --git a/src/Umbraco.Core/Services/IServerRegistrationService.cs b/src/Umbraco.Core/Services/IServerRegistrationService.cs
index e469de9a061f..4a084920792b 100644
--- a/src/Umbraco.Core/Services/IServerRegistrationService.cs
+++ b/src/Umbraco.Core/Services/IServerRegistrationService.cs
@@ -1,57 +1,58 @@
-using System;
-using System.Collections.Generic;
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Sync;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface IServerRegistrationService
 {
-    public interface IServerRegistrationService
-    {
-        /// 
-        /// Touches a server to mark it as active; deactivate stale servers.
-        /// 
-        /// The server URL.
-        /// The time after which a server is considered stale.
-        void TouchServer(string serverAddress, TimeSpan staleTimeout);
+    /// 
+    ///     Touches a server to mark it as active; deactivate stale servers.
+    /// 
+    /// The server URL.
+    /// The time after which a server is considered stale.
+    void TouchServer(string serverAddress, TimeSpan staleTimeout);
 
-        /// 
-        /// Deactivates a server.
-        /// 
-        /// The server unique identity.
-        void DeactiveServer(string serverIdentity);
+    /// 
+    ///     Deactivates a server.
+    /// 
+    /// The server unique identity.
+    void DeactiveServer(string serverIdentity);
 
-        /// 
-        /// Deactivates stale servers.
-        /// 
-        /// The time after which a server is considered stale.
-        void DeactiveStaleServers(TimeSpan staleTimeout);
+    /// 
+    ///     Deactivates stale servers.
+    /// 
+    /// The time after which a server is considered stale.
+    void DeactiveStaleServers(TimeSpan staleTimeout);
 
-        /// 
-        /// Return all active servers.
-        /// 
-        /// A value indicating whether to force-refresh the cache.
-        /// All active servers.
-        /// By default this method will rely on the repository's cache, which is updated each
-        /// time the current server is touched, and the period depends on the configuration. Use the
-        ///  parameter to force a cache refresh and reload active servers
-        /// from the database.
-        IEnumerable? GetActiveServers(bool refresh = false);
+    /// 
+    ///     Return all active servers.
+    /// 
+    /// A value indicating whether to force-refresh the cache.
+    /// All active servers.
+    /// 
+    ///     By default this method will rely on the repository's cache, which is updated each
+    ///     time the current server is touched, and the period depends on the configuration. Use the
+    ///      parameter to force a cache refresh and reload active servers
+    ///     from the database.
+    /// 
+    IEnumerable? GetActiveServers(bool refresh = false);
 
-        /// 
-        /// Return all servers (active and inactive).
-        /// 
-        /// A value indicating whether to force-refresh the cache.
-        /// All servers.
-        /// By default this method will rely on the repository's cache, which is updated each
-        /// time the current server is touched, and the period depends on the configuration. Use the
-        ///  parameter to force a cache refresh and reload all servers
-        /// from the database.
-        IEnumerable GetServers(bool refresh = false);
+    /// 
+    ///     Return all servers (active and inactive).
+    /// 
+    /// A value indicating whether to force-refresh the cache.
+    /// All servers.
+    /// 
+    ///     By default this method will rely on the repository's cache, which is updated each
+    ///     time the current server is touched, and the period depends on the configuration. Use the
+    ///      parameter to force a cache refresh and reload all servers
+    ///     from the database.
+    /// 
+    IEnumerable GetServers(bool refresh = false);
 
-        /// 
-        /// Gets the role of the current server.
-        /// 
-        /// The role of the current server.
-        ServerRole GetCurrentServerRole();
-    }
+    /// 
+    ///     Gets the role of the current server.
+    /// 
+    /// The role of the current server.
+    ServerRole GetCurrentServerRole();
 }
diff --git a/src/Umbraco.Core/Services/IService.cs b/src/Umbraco.Core/Services/IService.cs
index 6ca00a8dbee3..1bf32b04e164 100644
--- a/src/Umbraco.Core/Services/IService.cs
+++ b/src/Umbraco.Core/Services/IService.cs
@@ -1,10 +1,8 @@
-namespace Umbraco.Cms.Core.Services
-{
-    /// 
-    /// Marker interface for services, which is used to store difference services in a list or dictionary
-    /// 
-    public interface IService
-    {
+namespace Umbraco.Cms.Core.Services;
 
-    }
+/// 
+///     Marker interface for services, which is used to store difference services in a list or dictionary
+/// 
+public interface IService
+{
 }
diff --git a/src/Umbraco.Core/Services/ITagService.cs b/src/Umbraco.Core/Services/ITagService.cs
index 70c4ba81b6eb..7a6766a4ce26 100644
--- a/src/Umbraco.Core/Services/ITagService.cs
+++ b/src/Umbraco.Core/Services/ITagService.cs
@@ -1,98 +1,98 @@
-using System;
-using System.Collections.Generic;
-using Umbraco.Cms.Core.Models;
-
-namespace Umbraco.Cms.Core.Services
+using Umbraco.Cms.Core.Models;
+
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Tag service to query for tags in the tags db table. The tags returned are only relevant for published content &
+///     saved media or members
+/// 
+/// 
+///     If there is unpublished content with tags, those tags will not be contained.
+///     This service does not contain methods to query for content, media or members based on tags, those methods will be
+///     added
+///     to the content, media and member services respectively.
+/// 
+public interface ITagService : IService
 {
     /// 
-    /// Tag service to query for tags in the tags db table. The tags returned are only relevant for published content & saved media or members
-    /// 
-    /// 
-    /// If there is unpublished content with tags, those tags will not be contained.
-    ///
-    /// This service does not contain methods to query for content, media or members based on tags, those methods will be added
-    /// to the content, media and member services respectively.
-    /// 
-    public interface ITagService : IService
-    {
-        /// 
-        /// Gets a tagged entity.
-        /// 
-        TaggedEntity? GetTaggedEntityById(int id);
-
-        /// 
-        /// Gets a tagged entity.
-        /// 
-        TaggedEntity? GetTaggedEntityByKey(Guid key);
-
-        /// 
-        /// Gets all documents tagged with any tag in the specified group.
-        /// 
-        IEnumerable GetTaggedContentByTagGroup(string group, string? culture = null);
-
-        /// 
-        /// Gets all documents tagged with the specified tag.
-        /// 
-        IEnumerable GetTaggedContentByTag(string tag, string? group = null, string? culture = null);
-
-        /// 
-        /// Gets all media tagged with any tag in the specified group.
-        /// 
-        IEnumerable GetTaggedMediaByTagGroup(string group, string? culture = null);
-
-        /// 
-        /// Gets all media tagged with the specified tag.
-        /// 
-        IEnumerable GetTaggedMediaByTag(string tag, string? group = null, string? culture = null);
-
-        /// 
-        /// Gets all members tagged with any tag in the specified group.
-        /// 
-        IEnumerable GetTaggedMembersByTagGroup(string group, string? culture = null);
-
-        /// 
-        /// Gets all members tagged with the specified tag.
-        /// 
-        IEnumerable GetTaggedMembersByTag(string tag, string? group = null, string? culture = null);
-
-        /// 
-        /// Gets all tags.
-        /// 
-        IEnumerable GetAllTags(string? group = null, string? culture = null);
-
-        /// 
-        /// Gets all document tags.
-        /// 
-        IEnumerable GetAllContentTags(string? group = null, string? culture = null);
-
-        /// 
-        /// Gets all media tags.
-        /// 
-        IEnumerable GetAllMediaTags(string? group = null, string? culture = null);
-
-        /// 
-        /// Gets all member tags.
-        /// 
-        IEnumerable GetAllMemberTags(string? group = null, string? culture = null);
-
-        /// 
-        /// Gets all tags attached to an entity via a property.
-        /// 
-        IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string? group = null, string? culture = null);
-
-        /// 
-        /// Gets all tags attached to an entity.
-        /// 
-        IEnumerable GetTagsForEntity(int contentId, string? group = null, string? culture = null);
-
-        /// 
-        /// Gets all tags attached to an entity via a property.
-        /// 
-        IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string? group = null, string? culture = null);
-
-        /// 
-        /// Gets all tags attached to an entity.
-        /// 
-        IEnumerable GetTagsForEntity(Guid contentId, string? group = null, string? culture = null);
-    }
+    ///     Gets a tagged entity.
+    /// 
+    TaggedEntity? GetTaggedEntityById(int id);
+
+    /// 
+    ///     Gets a tagged entity.
+    /// 
+    TaggedEntity? GetTaggedEntityByKey(Guid key);
+
+    /// 
+    ///     Gets all documents tagged with any tag in the specified group.
+    /// 
+    IEnumerable GetTaggedContentByTagGroup(string group, string? culture = null);
+
+    /// 
+    ///     Gets all documents tagged with the specified tag.
+    /// 
+    IEnumerable GetTaggedContentByTag(string tag, string? group = null, string? culture = null);
+
+    /// 
+    ///     Gets all media tagged with any tag in the specified group.
+    /// 
+    IEnumerable GetTaggedMediaByTagGroup(string group, string? culture = null);
+
+    /// 
+    ///     Gets all media tagged with the specified tag.
+    /// 
+    IEnumerable GetTaggedMediaByTag(string tag, string? group = null, string? culture = null);
+
+    /// 
+    ///     Gets all members tagged with any tag in the specified group.
+    /// 
+    IEnumerable GetTaggedMembersByTagGroup(string group, string? culture = null);
+
+    /// 
+    ///     Gets all members tagged with the specified tag.
+    /// 
+    IEnumerable GetTaggedMembersByTag(string tag, string? group = null, string? culture = null);
+
+    /// 
+    ///     Gets all tags.
+    /// 
+    IEnumerable GetAllTags(string? group = null, string? culture = null);
+
+    /// 
+    ///     Gets all document tags.
+    /// 
+    IEnumerable GetAllContentTags(string? group = null, string? culture = null);
+
+    /// 
+    ///     Gets all media tags.
+    /// 
+    IEnumerable GetAllMediaTags(string? group = null, string? culture = null);
+
+    /// 
+    ///     Gets all member tags.
+    /// 
+    IEnumerable GetAllMemberTags(string? group = null, string? culture = null);
+
+    /// 
+    ///     Gets all tags attached to an entity via a property.
+    /// 
+    IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string? group = null,
+        string? culture = null);
+
+    /// 
+    ///     Gets all tags attached to an entity.
+    /// 
+    IEnumerable GetTagsForEntity(int contentId, string? group = null, string? culture = null);
+
+    /// 
+    ///     Gets all tags attached to an entity via a property.
+    /// 
+    IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string? group = null,
+        string? culture = null);
+
+    /// 
+    ///     Gets all tags attached to an entity.
+    /// 
+    IEnumerable GetTagsForEntity(Guid contentId, string? group = null, string? culture = null);
 }
diff --git a/src/Umbraco.Core/Services/ITrackedReferencesService.cs b/src/Umbraco.Core/Services/ITrackedReferencesService.cs
index dea99c0f6d1a..0d40d9f29ef3 100644
--- a/src/Umbraco.Core/Services/ITrackedReferencesService.cs
+++ b/src/Umbraco.Core/Services/ITrackedReferencesService.cs
@@ -1,38 +1,49 @@
 using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface ITrackedReferencesService
 {
-    public interface ITrackedReferencesService
-    {
-        /// 
-        /// Gets a paged result of items which are in relation with the current item.
-        /// Basically, shows the items which depend on the current item.
-        /// 
-        /// The identifier of the entity to retrieve relations for.
-        /// The page index.
-        /// The page size.
-        /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true).
-        /// A paged result of  objects.
-        PagedResult GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency);
+    /// 
+    ///     Gets a paged result of items which are in relation with the current item.
+    ///     Basically, shows the items which depend on the current item.
+    /// 
+    /// The identifier of the entity to retrieve relations for.
+    /// The page index.
+    /// The page size.
+    /// 
+    ///     A boolean indicating whether to filter only the RelationTypes which are
+    ///     dependencies (isDependency field is set to true).
+    /// 
+    /// A paged result of  objects.
+    PagedResult GetPagedRelationsForItem(int id, long pageIndex, int pageSize,
+        bool filterMustBeIsDependency);
 
-        /// 
-        /// Gets a paged result of the descending items that have any references, given a parent id.
-        /// 
-        /// The unique identifier of the parent to retrieve descendants for.
-        /// The page index.
-        /// The page size.
-        /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true).
-        /// A paged result of  objects.
-        PagedResult GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency);
+    /// 
+    ///     Gets a paged result of the descending items that have any references, given a parent id.
+    /// 
+    /// The unique identifier of the parent to retrieve descendants for.
+    /// The page index.
+    /// The page size.
+    /// 
+    ///     A boolean indicating whether to filter only the RelationTypes which are
+    ///     dependencies (isDependency field is set to true).
+    /// 
+    /// A paged result of  objects.
+    PagedResult GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize,
+        bool filterMustBeIsDependency);
 
-        /// 
-        /// Gets a paged result of items used in any kind of relation from selected integer ids.
-        /// 
-        /// The identifiers of the entities to check for relations.
-        /// The page index.
-        /// The page size.
-        /// A boolean indicating whether to filter only the RelationTypes which are dependencies (isDependency field is set to true).
-        /// A paged result of  objects.
-        PagedResult GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency);
-    }
+    /// 
+    ///     Gets a paged result of items used in any kind of relation from selected integer ids.
+    /// 
+    /// The identifiers of the entities to check for relations.
+    /// The page index.
+    /// The page size.
+    /// 
+    ///     A boolean indicating whether to filter only the RelationTypes which are
+    ///     dependencies (isDependency field is set to true).
+    /// 
+    /// A paged result of  objects.
+    PagedResult GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize,
+        bool filterMustBeIsDependency);
 }
diff --git a/src/Umbraco.Core/Services/ITreeService.cs b/src/Umbraco.Core/Services/ITreeService.cs
index b67e36e15b7e..8d0a9db7150b 100644
--- a/src/Umbraco.Core/Services/ITreeService.cs
+++ b/src/Umbraco.Core/Services/ITreeService.cs
@@ -1,32 +1,30 @@
-using System.Collections.Generic;
-using Umbraco.Cms.Core.Trees;
+using Umbraco.Cms.Core.Trees;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Represents a service which manages section trees.
+/// 
+public interface ITreeService
 {
     /// 
-    /// Represents a service which manages section trees.
+    ///     Gets a tree.
     /// 
-    public interface ITreeService
-    {
-        /// 
-        /// Gets a tree.
-        /// 
-        /// The tree alias.
-        Tree? GetByAlias(string treeAlias);
+    /// The tree alias.
+    Tree? GetByAlias(string treeAlias);
 
-        /// 
-        /// Gets all trees.
-        /// 
-        IEnumerable GetAll(TreeUse use = TreeUse.Main);
+    /// 
+    ///     Gets all trees.
+    /// 
+    IEnumerable GetAll(TreeUse use = TreeUse.Main);
 
-        /// 
-        /// Gets all trees for a section.
-        /// 
-        IEnumerable GetBySection(string sectionAlias, TreeUse use = TreeUse.Main);
+    /// 
+    ///     Gets all trees for a section.
+    /// 
+    IEnumerable GetBySection(string sectionAlias, TreeUse use = TreeUse.Main);
 
-        /// 
-        /// Gets all trees for a section, grouped.
-        /// 
-        IDictionary> GetBySectionGrouped(string sectionAlias, TreeUse use = TreeUse.Main);
-    }
+    /// 
+    ///     Gets all trees for a section, grouped.
+    /// 
+    IDictionary> GetBySectionGrouped(string sectionAlias, TreeUse use = TreeUse.Main);
 }
diff --git a/src/Umbraco.Core/Services/ITwoFactorLoginService.cs b/src/Umbraco.Core/Services/ITwoFactorLoginService.cs
index 30b221742c96..d0509a9283e0 100644
--- a/src/Umbraco.Core/Services/ITwoFactorLoginService.cs
+++ b/src/Umbraco.Core/Services/ITwoFactorLoginService.cs
@@ -1,69 +1,66 @@
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
 using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Service handling 2FA logins.
+/// 
+public interface ITwoFactorLoginService : IService
 {
     /// 
-    /// Service handling 2FA logins.
+    ///     Deletes all user logins - normally used when a member is deleted.
     /// 
-    public interface ITwoFactorLoginService : IService
-    {
-        /// 
-        /// Deletes all user logins - normally used when a member is deleted.
-        /// 
-        Task DeleteUserLoginsAsync(Guid userOrMemberKey);
+    Task DeleteUserLoginsAsync(Guid userOrMemberKey);
 
-        /// 
-        /// Checks whether 2FA is enabled for the user or member with the specified key.
-        /// 
-        Task IsTwoFactorEnabledAsync(Guid userOrMemberKey);
+    /// 
+    ///     Checks whether 2FA is enabled for the user or member with the specified key.
+    /// 
+    Task IsTwoFactorEnabledAsync(Guid userOrMemberKey);
 
-        /// 
-        /// Gets the secret for user or member and a specific provider.
-        /// 
-        Task GetSecretForUserAndProviderAsync(Guid userOrMemberKey, string providerName);
+    /// 
+    ///     Gets the secret for user or member and a specific provider.
+    /// 
+    Task GetSecretForUserAndProviderAsync(Guid userOrMemberKey, string providerName);
 
-        /// 
-        /// Gets the setup info for a specific user or member and a specific provider.
-        /// 
-        /// 
-        /// The returned type can be anything depending on the setup providers. You will need to cast it to the type handled by the provider.
-        /// 
-        Task GetSetupInfoAsync(Guid userOrMemberKey, string providerName);
+    /// 
+    ///     Gets the setup info for a specific user or member and a specific provider.
+    /// 
+    /// 
+    ///     The returned type can be anything depending on the setup providers. You will need to cast it to the type handled by
+    ///     the provider.
+    /// 
+    Task GetSetupInfoAsync(Guid userOrMemberKey, string providerName);
 
-        /// 
-        /// Gets all registered providers names.
-        /// 
-        IEnumerable GetAllProviderNames();
+    /// 
+    ///     Gets all registered providers names.
+    /// 
+    IEnumerable GetAllProviderNames();
 
-        /// 
-        /// Disables the 2FA provider with the specified provider name for the specified user or member.
-        /// 
-        Task DisableAsync(Guid userOrMemberKey, string providerName);
+    /// 
+    ///     Disables the 2FA provider with the specified provider name for the specified user or member.
+    /// 
+    Task DisableAsync(Guid userOrMemberKey, string providerName);
 
-        /// 
-        /// Validates the setup of the provider using the secret and code.
-        /// 
-        bool ValidateTwoFactorSetup(string providerName, string secret, string code);
+    /// 
+    ///     Validates the setup of the provider using the secret and code.
+    /// 
+    bool ValidateTwoFactorSetup(string providerName, string secret, string code);
 
-        /// 
-        /// Saves the 2FA login information.
-        /// 
-        Task SaveAsync(TwoFactorLogin twoFactorLogin);
+    /// 
+    ///     Saves the 2FA login information.
+    /// 
+    Task SaveAsync(TwoFactorLogin twoFactorLogin);
 
-        /// 
-        /// Gets all the enabled 2FA providers for the user or member with the specified key.
-        /// 
-        Task> GetEnabledTwoFactorProviderNamesAsync(Guid userOrMemberKey);
-    }
+    /// 
+    ///     Gets all the enabled 2FA providers for the user or member with the specified key.
+    /// 
+    Task> GetEnabledTwoFactorProviderNamesAsync(Guid userOrMemberKey);
+}
 
-    [Obsolete("This will be merged into ITwoFactorLoginService in Umbraco 11")]
-    public interface ITwoFactorLoginService2 : ITwoFactorLoginService
-    {
-        Task DisableWithCodeAsync(string providerName, Guid userOrMemberKey, string code);
+[Obsolete("This will be merged into ITwoFactorLoginService in Umbraco 11")]
+public interface ITwoFactorLoginService2 : ITwoFactorLoginService
+{
+    Task DisableWithCodeAsync(string providerName, Guid userOrMemberKey, string code);
 
-        Task ValidateAndSaveAsync(string providerName, Guid userKey, string secret, string code);
-    }
+    Task ValidateAndSaveAsync(string providerName, Guid userKey, string secret, string code);
 }
diff --git a/src/Umbraco.Core/Services/IUpgradeService.cs b/src/Umbraco.Core/Services/IUpgradeService.cs
index 2e0f2a5f17e5..df7e0f285bae 100644
--- a/src/Umbraco.Core/Services/IUpgradeService.cs
+++ b/src/Umbraco.Core/Services/IUpgradeService.cs
@@ -1,10 +1,8 @@
-using System.Threading.Tasks;
-using Umbraco.Cms.Core.Semver;
+using Umbraco.Cms.Core.Semver;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface IUpgradeService
 {
-    public interface IUpgradeService
-    {
-        Task CheckUpgrade(SemVersion version);
-    }
+    Task CheckUpgrade(SemVersion version);
 }
diff --git a/src/Umbraco.Core/Services/IUsageInformationService.cs b/src/Umbraco.Core/Services/IUsageInformationService.cs
index c6b2c6870292..a36072c9679e 100644
--- a/src/Umbraco.Core/Services/IUsageInformationService.cs
+++ b/src/Umbraco.Core/Services/IUsageInformationService.cs
@@ -1,10 +1,8 @@
-using System.Collections.Generic;
-using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface IUsageInformationService
 {
-    public interface IUsageInformationService
-    {
-        IEnumerable? GetDetailed();
-    }
+    IEnumerable? GetDetailed();
 }
diff --git a/src/Umbraco.Core/Services/IUserDataService.cs b/src/Umbraco.Core/Services/IUserDataService.cs
index e63ee3f6973c..dd557739d323 100644
--- a/src/Umbraco.Core/Services/IUserDataService.cs
+++ b/src/Umbraco.Core/Services/IUserDataService.cs
@@ -1,10 +1,8 @@
-using System.Collections.Generic;
-using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public interface IUserDataService
 {
-    public interface IUserDataService
-    {
-        IEnumerable GetUserData();
-    }
+    IEnumerable GetUserData();
 }
diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs
index 9a63fcf0adb1..9711dfc9e2e9 100644
--- a/src/Umbraco.Core/Services/IUserService.cs
+++ b/src/Umbraco.Core/Services/IUserService.cs
@@ -1,256 +1,283 @@
-using System;
-using System.Collections.Generic;
 using Umbraco.Cms.Core.Models.Membership;
 using Umbraco.Cms.Core.Persistence.Querying;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Defines the UserService, which is an easy access to operations involving  and eventually
+///     Users.
+/// 
+public interface IUserService : IMembershipUserService
 {
     /// 
-    /// Defines the UserService, which is an easy access to operations involving  and eventually Users.
+    ///     Creates a database entry for starting a new login session for a user
     /// 
-    public interface IUserService : IMembershipUserService
-    {
-        /// 
-        /// Creates a database entry for starting a new login session for a user
-        /// 
-        /// 
-        /// 
-        /// 
-        Guid CreateLoginSession(int userId, string requestingIpAddress);
+    /// 
+    /// 
+    /// 
+    Guid CreateLoginSession(int userId, string requestingIpAddress);
 
-        /// 
-        /// Validates that a user login session is valid/current and hasn't been closed
-        /// 
-        /// 
-        /// 
-        /// 
-        bool ValidateLoginSession(int userId, Guid sessionId);
+    /// 
+    ///     Validates that a user login session is valid/current and hasn't been closed
+    /// 
+    /// 
+    /// 
+    /// 
+    bool ValidateLoginSession(int userId, Guid sessionId);
 
-        /// 
-        /// Removes the session's validity
-        /// 
-        /// 
-        void ClearLoginSession(Guid sessionId);
+    /// 
+    ///     Removes the session's validity
+    /// 
+    /// 
+    void ClearLoginSession(Guid sessionId);
 
-        /// 
-        /// Removes all valid sessions for the user
-        /// 
-        /// 
-        int ClearLoginSessions(int userId);
+    /// 
+    ///     Removes all valid sessions for the user
+    /// 
+    /// 
+    int ClearLoginSessions(int userId);
 
-        /// 
-        /// This is basically facets of UserStates key = state, value = count
-        /// 
-        IDictionary GetUserStates();
+    /// 
+    ///     This is basically facets of UserStates key = state, value = count
+    /// 
+    IDictionary GetUserStates();
 
-        /// 
-        /// Get paged users
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// A filter to only include user that belong to these user groups
-        /// 
-        /// 
-        /// A filter to only include users that do not belong to these user groups
-        /// 
-        /// 
-        /// 
-        IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords,
-            string orderBy, Direction orderDirection,
-            UserState[]? userState = null,
-            string[]? includeUserGroups = null,
-            string[]? excludeUserGroups = null,
-            IQuery? filter = null);
+    /// 
+    ///     Get paged users
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    ///     A filter to only include user that belong to these user groups
+    /// 
+    /// 
+    ///     A filter to only include users that do not belong to these user groups
+    /// 
+    /// 
+    /// 
+    IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords,
+        string orderBy, Direction orderDirection,
+        UserState[]? userState = null,
+        string[]? includeUserGroups = null,
+        string[]? excludeUserGroups = null,
+        IQuery? filter = null);
 
-        /// 
-        /// Get paged users
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// A filter to only include user that belong to these user groups
-        /// 
-        /// 
-        /// 
-        IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords,
-            string orderBy, Direction orderDirection,
-            UserState[]? userState = null,
-            string[]? userGroups = null,
-            string? filter = null);
+    /// 
+    ///     Get paged users
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    ///     A filter to only include user that belong to these user groups
+    /// 
+    /// 
+    /// 
+    IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords,
+        string orderBy, Direction orderDirection,
+        UserState[]? userState = null,
+        string[]? userGroups = null,
+        string? filter = null);
 
-        /// 
-        /// Deletes or disables a User
-        /// 
-        ///  to delete
-        /// True to permanently delete the user, False to disable the user
-        void Delete(IUser user, bool deletePermanently);
+    /// 
+    ///     Deletes or disables a User
+    /// 
+    ///  to delete
+    /// True to permanently delete the user, False to disable the user
+    void Delete(IUser user, bool deletePermanently);
 
-        /// 
-        /// Gets an IProfile by User Id.
-        /// 
-        /// Id of the User to retrieve
-        /// 
-        IProfile? GetProfileById(int id);
+    /// 
+    ///     Gets an IProfile by User Id.
+    /// 
+    /// Id of the User to retrieve
+    /// 
+    ///     
+    /// 
+    IProfile? GetProfileById(int id);
 
-        /// 
-        /// Gets a profile by username
-        /// 
-        /// Username
-        /// 
-        IProfile? GetProfileByUserName(string username);
+    /// 
+    ///     Gets a profile by username
+    /// 
+    /// Username
+    /// 
+    ///     
+    /// 
+    IProfile? GetProfileByUserName(string username);
 
-        /// 
-        /// Gets a user by Id
-        /// 
-        /// Id of the user to retrieve
-        /// 
-        IUser? GetUserById(int id);
+    /// 
+    ///     Gets a user by Id
+    /// 
+    /// Id of the user to retrieve
+    /// 
+    ///     
+    /// 
+    IUser? GetUserById(int id);
 
-        /// 
-        /// Gets a users by Id
-        /// 
-        /// Ids of the users to retrieve
-        /// 
-        IEnumerable GetUsersById(params int[]? ids);
+    /// 
+    ///     Gets a users by Id
+    /// 
+    /// Ids of the users to retrieve
+    /// 
+    ///     
+    /// 
+    IEnumerable GetUsersById(params int[]? ids);
 
-        /// 
-        /// Removes a specific section from all user groups
-        /// 
-        /// This is useful when an entire section is removed from config
-        /// Alias of the section to remove
-        void DeleteSectionFromAllUserGroups(string sectionAlias);
+    /// 
+    ///     Removes a specific section from all user groups
+    /// 
+    /// This is useful when an entire section is removed from config
+    /// Alias of the section to remove
+    void DeleteSectionFromAllUserGroups(string sectionAlias);
 
-        /// 
-        /// Get explicitly assigned permissions for a user and optional node ids
-        /// 
-        /// If no permissions are found for a particular entity then the user's default permissions will be applied
-        /// User to retrieve permissions for
-        /// Specifying nothing will return all user permissions for all nodes that have explicit permissions defined
-        /// An enumerable list of 
-        /// 
-        /// This will return the default permissions for the user's groups for node ids that don't have explicitly defined permissions
-        /// 
-        EntityPermissionCollection GetPermissions(IUser? user, params int[] nodeIds);
+    /// 
+    ///     Get explicitly assigned permissions for a user and optional node ids
+    /// 
+    /// If no permissions are found for a particular entity then the user's default permissions will be applied
+    /// User to retrieve permissions for
+    /// 
+    ///     Specifying nothing will return all user permissions for all nodes that have explicit permissions
+    ///     defined
+    /// 
+    /// An enumerable list of 
+    /// 
+    ///     This will return the default permissions for the user's groups for node ids that don't have explicitly defined
+    ///     permissions
+    /// 
+    EntityPermissionCollection GetPermissions(IUser? user, params int[] nodeIds);
 
-        /// 
-        /// Get explicitly assigned permissions for groups and optional node Ids
-        /// 
-        /// 
-        /// 
-        ///     Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set
-        /// 
-        /// Specifying nothing will return all permissions for all nodes
-        /// An enumerable list of 
-        EntityPermissionCollection GetPermissions(IUserGroup?[] groups, bool fallbackToDefaultPermissions, params int[] nodeIds);
+    /// 
+    ///     Get explicitly assigned permissions for groups and optional node Ids
+    /// 
+    /// 
+    /// 
+    ///     Flag indicating if we want to include the default group permissions for each result if there are not explicit
+    ///     permissions set
+    /// 
+    /// Specifying nothing will return all permissions for all nodes
+    /// An enumerable list of 
+    EntityPermissionCollection GetPermissions(IUserGroup?[] groups, bool fallbackToDefaultPermissions,
+        params int[] nodeIds);
 
-        /// 
-        /// Gets the implicit/inherited permissions for the user for the given path
-        /// 
-        /// User to check permissions for
-        /// Path to check permissions for
-        EntityPermissionSet GetPermissionsForPath(IUser? user, string? path);
+    /// 
+    ///     Gets the implicit/inherited permissions for the user for the given path
+    /// 
+    /// User to check permissions for
+    /// Path to check permissions for
+    EntityPermissionSet GetPermissionsForPath(IUser? user, string? path);
 
-        /// 
-        /// Gets the permissions for the provided groups and path
-        /// 
-        /// 
-        /// Path to check permissions for
-        /// 
-        ///     Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set
-        /// 
-        EntityPermissionSet GetPermissionsForPath(IUserGroup[] groups, string path, bool fallbackToDefaultPermissions = false);
+    /// 
+    ///     Gets the permissions for the provided groups and path
+    /// 
+    /// 
+    /// Path to check permissions for
+    /// 
+    ///     Flag indicating if we want to include the default group permissions for each result if there are not explicit
+    ///     permissions set
+    /// 
+    EntityPermissionSet GetPermissionsForPath(IUserGroup[] groups, string path,
+        bool fallbackToDefaultPermissions = false);
 
-        /// 
-        /// Replaces the same permission set for a single group to any number of entities
-        /// 
-        /// Id of the group
-        /// 
-        /// Permissions as enumerable list of ,
-        /// if no permissions are specified then all permissions for this node are removed for this group
-        /// 
-        /// Specify the nodes to replace permissions for. If nothing is specified all permissions are removed.
-        /// If no 'entityIds' are specified all permissions will be removed for the specified group.
-        void ReplaceUserGroupPermissions(int groupId, IEnumerable? permissions, params int[] entityIds);
+    /// 
+    ///     Replaces the same permission set for a single group to any number of entities
+    /// 
+    /// Id of the group
+    /// 
+    ///     Permissions as enumerable list of ,
+    ///     if no permissions are specified then all permissions for this node are removed for this group
+    /// 
+    /// 
+    ///     Specify the nodes to replace permissions for. If nothing is specified all permissions are
+    ///     removed.
+    /// 
+    /// If no 'entityIds' are specified all permissions will be removed for the specified group.
+    void ReplaceUserGroupPermissions(int groupId, IEnumerable? permissions, params int[] entityIds);
 
-        /// 
-        /// Assigns the same permission set for a single user group to any number of entities
-        /// 
-        /// Id of the group
-        /// 
-        /// Specify the nodes to replace permissions for
-        void AssignUserGroupPermission(int groupId, char permission, params int[] entityIds);
+    /// 
+    ///     Assigns the same permission set for a single user group to any number of entities
+    /// 
+    /// Id of the group
+    /// 
+    /// Specify the nodes to replace permissions for
+    void AssignUserGroupPermission(int groupId, char permission, params int[] entityIds);
 
-        /// 
-        /// Gets a list of  objects associated with a given group
-        /// 
-        /// Id of group
-        /// 
-        IEnumerable GetAllInGroup(int? groupId);
+    /// 
+    ///     Gets a list of  objects associated with a given group
+    /// 
+    /// Id of group
+    /// 
+    ///     
+    /// 
+    IEnumerable GetAllInGroup(int? groupId);
 
-        /// 
-        /// Gets a list of  objects not associated with a given group
-        /// 
-        /// Id of group
-        /// 
-        IEnumerable GetAllNotInGroup(int groupId);
+    /// 
+    ///     Gets a list of  objects not associated with a given group
+    /// 
+    /// Id of group
+    /// 
+    ///     
+    /// 
+    IEnumerable GetAllNotInGroup(int groupId);
 
-        IEnumerable GetNextUsers(int id, int count);
+    IEnumerable GetNextUsers(int id, int count);
 
-        #region User groups
+    #region User groups
 
-        /// 
-        /// Gets all UserGroups or those specified as parameters
-        /// 
-        /// Optional Ids of UserGroups to retrieve
-        /// An enumerable list of 
-        IEnumerable GetAllUserGroups(params int[] ids);
+    /// 
+    ///     Gets all UserGroups or those specified as parameters
+    /// 
+    /// Optional Ids of UserGroups to retrieve
+    /// An enumerable list of 
+    IEnumerable GetAllUserGroups(params int[] ids);
 
-        /// 
-        /// Gets a UserGroup by its Alias
-        /// 
-        /// Alias of the UserGroup to retrieve
-        /// 
-        IEnumerable GetUserGroupsByAlias(params string[] alias);
+    /// 
+    ///     Gets a UserGroup by its Alias
+    /// 
+    /// Alias of the UserGroup to retrieve
+    /// 
+    ///     
+    /// 
+    IEnumerable GetUserGroupsByAlias(params string[] alias);
 
-        /// 
-        /// Gets a UserGroup by its Alias
-        /// 
-        /// Name of the UserGroup to retrieve
-        /// 
-        IUserGroup? GetUserGroupByAlias(string name);
+    /// 
+    ///     Gets a UserGroup by its Alias
+    /// 
+    /// Name of the UserGroup to retrieve
+    /// 
+    ///     
+    /// 
+    IUserGroup? GetUserGroupByAlias(string name);
 
-        /// 
-        /// Gets a UserGroup by its Id
-        /// 
-        /// Id of the UserGroup to retrieve
-        /// 
-        IUserGroup? GetUserGroupById(int id);
+    /// 
+    ///     Gets a UserGroup by its Id
+    /// 
+    /// Id of the UserGroup to retrieve
+    /// 
+    ///     
+    /// 
+    IUserGroup? GetUserGroupById(int id);
 
-        /// 
-        /// Saves a UserGroup
-        /// 
-        /// UserGroup to save
-        /// 
-        /// If null than no changes are made to the users who are assigned to this group, however if a value is passed in
-        /// than all users will be removed from this group and only these users will be added
-        /// 
-        void Save(IUserGroup userGroup, int[]? userIds = null);
+    /// 
+    ///     Saves a UserGroup
+    /// 
+    /// UserGroup to save
+    /// 
+    ///     If null than no changes are made to the users who are assigned to this group, however if a value is passed in
+    ///     than all users will be removed from this group and only these users will be added
+    /// 
+    void Save(IUserGroup userGroup, int[]? userIds = null);
 
-        /// 
-        /// Deletes a UserGroup
-        /// 
-        /// UserGroup to delete
-        void DeleteUserGroup(IUserGroup userGroup);
+    /// 
+    ///     Deletes a UserGroup
+    /// 
+    /// UserGroup to delete
+    void DeleteUserGroup(IUserGroup userGroup);
 
-        #endregion
-    }
+    #endregion
 }
diff --git a/src/Umbraco.Core/Services/IdKeyMap.cs b/src/Umbraco.Core/Services/IdKeyMap.cs
index 00acb7ad043b..bd5c5e8e5c1f 100644
--- a/src/Umbraco.Core/Services/IdKeyMap.cs
+++ b/src/Umbraco.Core/Services/IdKeyMap.cs
@@ -1,79 +1,77 @@
-using System;
 using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Threading;
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Persistence.Repositories;
 using Umbraco.Cms.Core.Scoping;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public class IdKeyMap : IIdKeyMap, IDisposable
 {
-    public class IdKeyMap : IIdKeyMap,IDisposable
-    {
-        private readonly ICoreScopeProvider _scopeProvider;
-        private readonly IIdKeyMapRepository _idKeyMapRepository;
-        private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
+    private readonly ICoreScopeProvider _scopeProvider;
+    private readonly IIdKeyMapRepository _idKeyMapRepository;
+    private readonly ReaderWriterLockSlim _locker = new();
 
-        private readonly Dictionary> _id2Key = new Dictionary>();
-        private readonly Dictionary> _key2Id = new Dictionary>();
+    private readonly Dictionary> _id2Key = new();
+    private readonly Dictionary> _key2Id = new();
 
-        public IdKeyMap(ICoreScopeProvider scopeProvider, IIdKeyMapRepository idKeyMapRepository)
-        {
-            _scopeProvider = scopeProvider;
-            _idKeyMapRepository = idKeyMapRepository;
-        }
+    public IdKeyMap(ICoreScopeProvider scopeProvider, IIdKeyMapRepository idKeyMapRepository)
+    {
+        _scopeProvider = scopeProvider;
+        _idKeyMapRepository = idKeyMapRepository;
+    }
 
-        // note - for pure read-only we might want to *not* enforce a transaction?
-
-        // notes
-        //
-        // - this class assumes that the id/guid map is unique; that is, if an id and a guid map
-        //   to each other, then the id will never map to another guid, and the guid will never map
-        //   to another id
-        //
-        // - cache is cleared by MediaCacheRefresher, UnpublishedPageCacheRefresher, and other
-        //   refreshers - because id/guid map is unique, we only clear to avoid leaking memory, 'cos
-        //   we don't risk caching obsolete values - and only when actually deleting
-        //
-        // - we do NOT prefetch anything from database
-        //
-        // - NuCache maintains its own id/guid map for content & media items
-        //   it does *not* populate the idk map, because it directly uses its own map
-        //   still, it provides mappers so that the idk map can benefit from them
-        //   which means there will be some double-caching at some point ??
-        //
-        // - when a request comes in:
-        //   if the idkMap already knows about the map, it returns the value
-        //   else it tries the published cache via mappers
-        //   else it hits the database
-
-        private readonly ConcurrentDictionary id2key, Func key2id)> _dictionary
-            = new ConcurrentDictionary id2key, Func key2id)>();
-        private bool _disposedValue;
-
-        public void SetMapper(UmbracoObjectTypes umbracoObjectType, Func id2key, Func key2id)
+    // note - for pure read-only we might want to *not* enforce a transaction?
+
+    // notes
+    //
+    // - this class assumes that the id/guid map is unique; that is, if an id and a guid map
+    //   to each other, then the id will never map to another guid, and the guid will never map
+    //   to another id
+    //
+    // - cache is cleared by MediaCacheRefresher, UnpublishedPageCacheRefresher, and other
+    //   refreshers - because id/guid map is unique, we only clear to avoid leaking memory, 'cos
+    //   we don't risk caching obsolete values - and only when actually deleting
+    //
+    // - we do NOT prefetch anything from database
+    //
+    // - NuCache maintains its own id/guid map for content & media items
+    //   it does *not* populate the idk map, because it directly uses its own map
+    //   still, it provides mappers so that the idk map can benefit from them
+    //   which means there will be some double-caching at some point ??
+    //
+    // - when a request comes in:
+    //   if the idkMap already knows about the map, it returns the value
+    //   else it tries the published cache via mappers
+    //   else it hits the database
+
+    private readonly ConcurrentDictionary id2key, Func key2id)>
+        _dictionary
+            = new();
+
+    private bool _disposedValue;
+
+    public void SetMapper(UmbracoObjectTypes umbracoObjectType, Func id2key, Func key2id) =>
+        _dictionary[umbracoObjectType] = (id2key, key2id);
+
+    internal void Populate(IEnumerable<(int id, Guid key)> pairs, UmbracoObjectTypes umbracoObjectType)
+    {
+        try
         {
-            _dictionary[umbracoObjectType] = (id2key, key2id);
+            _locker.EnterWriteLock();
+            foreach ((int id, Guid key) pair in pairs)
+            {
+                _id2Key[pair.id] = new TypedId(pair.key, umbracoObjectType);
+                _key2Id[pair.key] = new TypedId(pair.id, umbracoObjectType);
+            }
         }
-
-        internal void Populate(IEnumerable<(int id, Guid key)> pairs, UmbracoObjectTypes umbracoObjectType)
+        finally
         {
-            try
+            if (_locker.IsWriteLockHeld)
             {
-                _locker.EnterWriteLock();
-                foreach (var pair in pairs)
-                {
-
-                    _id2Key[pair.id] = new TypedId(pair.key, umbracoObjectType);
-                    _key2Id[pair.key] = new TypedId(pair.id, umbracoObjectType);
-                }
-            }
-            finally
-            {
-                if (_locker.IsWriteLockHeld)
-                    _locker.ExitWriteLock();
+                _locker.ExitWriteLock();
             }
         }
+    }
 
 #if POPULATE_FROM_DATABASE
         private void PopulateLocked()
@@ -85,7 +83,8 @@ private void PopulateLocked()
             {
                 // populate content and media items
                 var types = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media };
-                var values = scope.Database.Query("SELECT id, uniqueId, nodeObjectType FROM umbracoNode WHERE nodeObjectType IN @types", new { types });
+                var values =
+ scope.Database.Query("SELECT id, uniqueId, nodeObjectType FROM umbracoNode WHERE nodeObjectType IN @types", new { types });
                 foreach (var value in values)
                 {
                     var umbracoObjectType = ObjectTypes.GetUmbracoObjectType(value.NodeObjectType);
@@ -135,21 +134,27 @@ private Attempt PopulateAndGetKeyForId(int id, UmbracoObjectTypes umbracoO
         }
 #endif
 
-        public Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType)
-        {
-            bool empty;
+    public Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType)
+    {
+        bool empty;
 
-            try
+        try
+        {
+            _locker.EnterReadLock();
+            if (_key2Id.TryGetValue(key, out TypedId id) && id.UmbracoObjectType == umbracoObjectType)
             {
-                _locker.EnterReadLock();
-                if (_key2Id.TryGetValue(key, out var id) && id.UmbracoObjectType == umbracoObjectType) return Attempt.Succeed(id.Id);
-                empty = _key2Id.Count == 0;
+                return Attempt.Succeed(id.Id);
             }
-            finally
+
+            empty = _key2Id.Count == 0;
+        }
+        finally
+        {
+            if (_locker.IsReadLockHeld)
             {
-                if (_locker.IsReadLockHeld)
-                    _locker.ExitReadLock();
+                _locker.ExitReadLock();
             }
+        }
 
 #if POPULATE_FROM_DATABASE
             // if cache is empty and looking for a document or a media,
@@ -158,77 +163,96 @@ public Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType)
                 return PopulateAndGetIdForKey(key, umbracoObjectType);
 #endif
 
-            // optimize for read speed: reading database outside a lock means that we could read
-            // multiple times, but we don't lock the cache while accessing the database = better
-
-            int? val = null;
+        // optimize for read speed: reading database outside a lock means that we could read
+        // multiple times, but we don't lock the cache while accessing the database = better
 
-            if (_dictionary.TryGetValue(umbracoObjectType, out var mappers))
-                if ((val = mappers.key2id(key)) == default(int)) val = null;
+        int? val = null;
 
-            if (val == null)
+        if (_dictionary.TryGetValue(umbracoObjectType, out (Func id2key, Func key2id) mappers))
+        {
+            if ((val = mappers.key2id(key)) == default(int))
             {
-                using (var scope = _scopeProvider.CreateCoreScope())
-                {
-                    val = _idKeyMapRepository.GetIdForKey(key, umbracoObjectType);
-                    scope.Complete();
-                }
+                val = null;
             }
+        }
 
-            if (val == null) return Attempt.Fail();
-
-            // cache reservations, when something is saved this cache is cleared anyways
-            //if (umbracoObjectType == UmbracoObjectTypes.IdReservation)
-            //    Attempt.Succeed(val.Value);
-
-            try
-            {
-                _locker.EnterWriteLock();
-                _id2Key[val.Value] = new TypedId(key, umbracoObjectType);
-                _key2Id[key] = new TypedId(val.Value, umbracoObjectType);
-            }
-            finally
+        if (val == null)
+        {
+            using (ICoreScope scope = _scopeProvider.CreateCoreScope())
             {
-                if (_locker.IsWriteLockHeld)
-                    _locker.ExitWriteLock();
+                val = _idKeyMapRepository.GetIdForKey(key, umbracoObjectType);
+                scope.Complete();
             }
-
-            return Attempt.Succeed(val.Value);
         }
 
-        public Attempt GetIdForUdi(Udi udi)
+        if (val == null)
         {
-            var guidUdi = udi as GuidUdi;
-            if (guidUdi == null)
-                return Attempt.Fail();
-
-            var umbracoType = UdiEntityTypeHelper.ToUmbracoObjectType(guidUdi.EntityType);
-            return GetIdForKey(guidUdi.Guid, umbracoType);
+            return Attempt.Fail();
         }
 
-        public Attempt GetUdiForId(int id, UmbracoObjectTypes umbracoObjectType)
+        // cache reservations, when something is saved this cache is cleared anyways
+        //if (umbracoObjectType == UmbracoObjectTypes.IdReservation)
+        //    Attempt.Succeed(val.Value);
+
+        try
         {
-            var keyAttempt = GetKeyForId(id, umbracoObjectType);
-            return keyAttempt.Success
-                ? Attempt.Succeed(new GuidUdi(UdiEntityTypeHelper.FromUmbracoObjectType(umbracoObjectType), keyAttempt.Result))
-                : Attempt.Fail();
+            _locker.EnterWriteLock();
+            _id2Key[val.Value] = new TypedId(key, umbracoObjectType);
+            _key2Id[key] = new TypedId(val.Value, umbracoObjectType);
+        }
+        finally
+        {
+            if (_locker.IsWriteLockHeld)
+            {
+                _locker.ExitWriteLock();
+            }
         }
 
-        public Attempt GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType)
+        return Attempt.Succeed(val.Value);
+    }
+
+    public Attempt GetIdForUdi(Udi udi)
+    {
+        var guidUdi = udi as GuidUdi;
+        if (guidUdi == null)
         {
-            bool empty;
+            return Attempt.Fail();
+        }
 
-            try
+        UmbracoObjectTypes umbracoType = UdiEntityTypeHelper.ToUmbracoObjectType(guidUdi.EntityType);
+        return GetIdForKey(guidUdi.Guid, umbracoType);
+    }
+
+    public Attempt GetUdiForId(int id, UmbracoObjectTypes umbracoObjectType)
+    {
+        Attempt keyAttempt = GetKeyForId(id, umbracoObjectType);
+        return keyAttempt.Success
+            ? Attempt.Succeed(new GuidUdi(UdiEntityTypeHelper.FromUmbracoObjectType(umbracoObjectType),
+                keyAttempt.Result))
+            : Attempt.Fail();
+    }
+
+    public Attempt GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType)
+    {
+        bool empty;
+
+        try
+        {
+            _locker.EnterReadLock();
+            if (_id2Key.TryGetValue(id, out TypedId key) && key.UmbracoObjectType == umbracoObjectType)
             {
-                _locker.EnterReadLock();
-                if (_id2Key.TryGetValue(id, out var key) && key.UmbracoObjectType == umbracoObjectType) return Attempt.Succeed(key.Id);
-                empty = _id2Key.Count == 0;
+                return Attempt.Succeed(key.Id);
             }
-            finally
+
+            empty = _id2Key.Count == 0;
+        }
+        finally
+        {
+            if (_locker.IsReadLockHeld)
             {
-                if (_locker.IsReadLockHeld)
-                    _locker.ExitReadLock();
+                _locker.ExitReadLock();
             }
+        }
 
 #if POPULATE_FROM_DATABASE
             // if cache is empty and looking for a document or a media,
@@ -237,133 +261,155 @@ public Attempt GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType)
                 return PopulateAndGetKeyForId(id, umbracoObjectType);
 #endif
 
-            // optimize for read speed: reading database outside a lock means that we could read
-            // multiple times, but we don't lock the cache while accessing the database = better
+        // optimize for read speed: reading database outside a lock means that we could read
+        // multiple times, but we don't lock the cache while accessing the database = better
 
-            Guid? val = null;
+        Guid? val = null;
 
-            if (_dictionary.TryGetValue(umbracoObjectType, out var mappers))
-                if ((val = mappers.id2key(id)) == default(Guid)) val = null;
-
-            if (val == null)
+        if (_dictionary.TryGetValue(umbracoObjectType, out (Func id2key, Func key2id) mappers))
+        {
+            if ((val = mappers.id2key(id)) == default(Guid))
             {
-                using (var scope = _scopeProvider.CreateCoreScope())
-                {
-                    val = _idKeyMapRepository.GetIdForKey(id, umbracoObjectType);
-                    scope.Complete();
-                }
+                val = null;
             }
+        }
 
-            if (val == null) return Attempt.Fail();
-
-            // cache reservations, when something is saved this cache is cleared anyways
-            //if (umbracoObjectType == UmbracoObjectTypes.IdReservation)
-            //    Attempt.Succeed(val.Value);
-
-            try
-            {
-                _locker.EnterWriteLock();
-                _id2Key[id] = new TypedId(val.Value, umbracoObjectType);
-                _key2Id[val.Value] = new TypedId(id, umbracoObjectType);
-            }
-            finally
+        if (val == null)
+        {
+            using (ICoreScope scope = _scopeProvider.CreateCoreScope())
             {
-                if (_locker.IsWriteLockHeld)
-                    _locker.ExitWriteLock();
+                val = _idKeyMapRepository.GetIdForKey(id, umbracoObjectType);
+                scope.Complete();
             }
+        }
 
-            return Attempt.Succeed(val.Value);
+        if (val == null)
+        {
+            return Attempt.Fail();
         }
 
-        // invoked on UnpublishedPageCacheRefresher.RefreshAll
-        // anything else will use the id-specific overloads
-        public void ClearCache()
+        // cache reservations, when something is saved this cache is cleared anyways
+        //if (umbracoObjectType == UmbracoObjectTypes.IdReservation)
+        //    Attempt.Succeed(val.Value);
+
+        try
         {
-            try
-            {
-                _locker.EnterWriteLock();
-                _id2Key.Clear();
-                _key2Id.Clear();
-            }
-            finally
+            _locker.EnterWriteLock();
+            _id2Key[id] = new TypedId(val.Value, umbracoObjectType);
+            _key2Id[val.Value] = new TypedId(id, umbracoObjectType);
+        }
+        finally
+        {
+            if (_locker.IsWriteLockHeld)
             {
-                if (_locker.IsWriteLockHeld)
-                    _locker.ExitWriteLock();
+                _locker.ExitWriteLock();
             }
         }
 
-        public void ClearCache(int id)
+        return Attempt.Succeed(val.Value);
+    }
+
+    // invoked on UnpublishedPageCacheRefresher.RefreshAll
+    // anything else will use the id-specific overloads
+    public void ClearCache()
+    {
+        try
         {
-            try
-            {
-                _locker.EnterWriteLock();
-                if (_id2Key.TryGetValue(id, out var key) == false) return;
-                _id2Key.Remove(id);
-                _key2Id.Remove(key.Id);
-            }
-            finally
+            _locker.EnterWriteLock();
+            _id2Key.Clear();
+            _key2Id.Clear();
+        }
+        finally
+        {
+            if (_locker.IsWriteLockHeld)
             {
-                if (_locker.IsWriteLockHeld)
-                    _locker.ExitWriteLock();
+                _locker.ExitWriteLock();
             }
         }
+    }
 
-        public void ClearCache(Guid key)
+    public void ClearCache(int id)
+    {
+        try
         {
-            try
+            _locker.EnterWriteLock();
+            if (_id2Key.TryGetValue(id, out TypedId key) == false)
             {
-                _locker.EnterWriteLock();
-                if (_key2Id.TryGetValue(key, out var id) == false) return;
-                _id2Key.Remove(id.Id);
-                _key2Id.Remove(key);
+                return;
             }
-            finally
+
+            _id2Key.Remove(id);
+            _key2Id.Remove(key.Id);
+        }
+        finally
+        {
+            if (_locker.IsWriteLockHeld)
             {
-                if (_locker.IsWriteLockHeld)
-                    _locker.ExitWriteLock();
+                _locker.ExitWriteLock();
             }
         }
+    }
 
-        // ReSharper disable ClassNeverInstantiated.Local
-        // ReSharper disable UnusedAutoPropertyAccessor.Local
-        private class TypedIdDto
+    public void ClearCache(Guid key)
+    {
+        try
         {
-            public int Id { get; set; }
-            public Guid UniqueId { get; set; }
-            public Guid NodeObjectType { get; set; }
-        }
-        // ReSharper restore ClassNeverInstantiated.Local
-        // ReSharper restore UnusedAutoPropertyAccessor.Local
+            _locker.EnterWriteLock();
+            if (_key2Id.TryGetValue(key, out TypedId id) == false)
+            {
+                return;
+            }
 
-        private struct TypedId
+            _id2Key.Remove(id.Id);
+            _key2Id.Remove(key);
+        }
+        finally
         {
-            public TypedId(T id, UmbracoObjectTypes umbracoObjectType)
+            if (_locker.IsWriteLockHeld)
             {
-                UmbracoObjectType = umbracoObjectType;
-                Id = id;
+                _locker.ExitWriteLock();
             }
+        }
+    }
 
-            public UmbracoObjectTypes UmbracoObjectType { get; }
+    // ReSharper disable ClassNeverInstantiated.Local
+    // ReSharper disable UnusedAutoPropertyAccessor.Local
+    private class TypedIdDto
+    {
+        public int Id { get; set; }
+        public Guid UniqueId { get; set; }
+        public Guid NodeObjectType { get; set; }
+    }
+    // ReSharper restore ClassNeverInstantiated.Local
+    // ReSharper restore UnusedAutoPropertyAccessor.Local
 
-            public T Id { get; }
+    private struct TypedId
+    {
+        public TypedId(T id, UmbracoObjectTypes umbracoObjectType)
+        {
+            UmbracoObjectType = umbracoObjectType;
+            Id = id;
         }
 
-        protected virtual void Dispose(bool disposing)
+        public UmbracoObjectTypes UmbracoObjectType { get; }
+
+        public T Id { get; }
+    }
+
+    protected virtual void Dispose(bool disposing)
+    {
+        if (!_disposedValue)
         {
-            if (!_disposedValue)
+            if (disposing)
             {
-                if (disposing)
-                {
-                    _locker.Dispose();
-                }
-                _disposedValue = true;
+                _locker.Dispose();
             }
-        }
 
-        public void Dispose()
-        {
-            // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
-            Dispose(disposing: true);
+            _disposedValue = true;
         }
     }
+
+    public void Dispose() =>
+        // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+        Dispose(true);
 }
diff --git a/src/Umbraco.Core/Services/InstallationService.cs b/src/Umbraco.Core/Services/InstallationService.cs
index eb1632be8aef..00bd00aa91e4 100644
--- a/src/Umbraco.Core/Services/InstallationService.cs
+++ b/src/Umbraco.Core/Services/InstallationService.cs
@@ -1,20 +1,14 @@
-using System.Threading.Tasks;
 using Umbraco.Cms.Core.Persistence.Repositories;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public class InstallationService : IInstallationService
 {
-    public class InstallationService : IInstallationService
-    {
-        private readonly IInstallationRepository _installationRepository;
+    private readonly IInstallationRepository _installationRepository;
 
-        public InstallationService(IInstallationRepository installationRepository)
-        {
-            _installationRepository = installationRepository;
-        }
+    public InstallationService(IInstallationRepository installationRepository) =>
+        _installationRepository = installationRepository;
 
-        public async Task LogInstall(InstallLog installLog)
-        {
-            await _installationRepository.SaveInstallLogAsync(installLog);
-        }
-    }
+    public async Task LogInstall(InstallLog installLog) =>
+        await _installationRepository.SaveInstallLogAsync(installLog);
 }
diff --git a/src/Umbraco.Core/Services/KeyValueService.cs b/src/Umbraco.Core/Services/KeyValueService.cs
index 834c0d311659..de080a84cece 100644
--- a/src/Umbraco.Core/Services/KeyValueService.cs
+++ b/src/Umbraco.Core/Services/KeyValueService.cs
@@ -1,97 +1,91 @@
-using System;
-using System.Collections.Generic;
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Persistence.Repositories;
 using Umbraco.Cms.Core.Scoping;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+internal class KeyValueService : IKeyValueService
 {
-    internal class KeyValueService : IKeyValueService
+    private readonly IKeyValueRepository _repository;
+    private readonly ICoreScopeProvider _scopeProvider;
+
+    public KeyValueService(ICoreScopeProvider scopeProvider, IKeyValueRepository repository)
     {
-        private readonly ICoreScopeProvider _scopeProvider;
-        private readonly IKeyValueRepository _repository;
+        _scopeProvider = scopeProvider;
+        _repository = repository;
+    }
 
-        public KeyValueService(ICoreScopeProvider scopeProvider, IKeyValueRepository repository)
+    /// 
+    public string? GetValue(string key)
+    {
+        using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true))
         {
-            _scopeProvider = scopeProvider;
-            _repository = repository;
+            return _repository.Get(key)?.Value;
         }
+    }
 
-        /// 
-        public string? GetValue(string key)
+    /// 
+    public IReadOnlyDictionary? FindByKeyPrefix(string keyPrefix)
+    {
+        using (ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = _scopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _repository.Get(key)?.Value;
-            }
+            return _repository.FindByKeyPrefix(keyPrefix);
         }
+    }
 
-        /// 
-        public IReadOnlyDictionary? FindByKeyPrefix(string keyPrefix)
+    /// 
+    public void SetValue(string key, string value)
+    {
+        using (ICoreScope scope = _scopeProvider.CreateCoreScope())
         {
-            using (var scope = _scopeProvider.CreateCoreScope(autoComplete: true))
+            scope.WriteLock(Constants.Locks.KeyValues);
+
+            IKeyValue keyValue = _repository.Get(key);
+            if (keyValue == null)
             {
-                return _repository.FindByKeyPrefix(keyPrefix);
+                keyValue = new KeyValue {Identifier = key, Value = value, UpdateDate = DateTime.Now};
             }
-        }
-
-        /// 
-        public void SetValue(string key, string value)
-        {
-            using (var scope = _scopeProvider.CreateCoreScope())
+            else
             {
-                scope.WriteLock(Cms.Core.Constants.Locks.KeyValues);
-
-                var keyValue = _repository.Get(key);
-                if (keyValue == null)
-                {
-                    keyValue = new KeyValue
-                    {
-                        Identifier = key,
-                        Value = value,
-                        UpdateDate = DateTime.Now,
-                    };
-                }
-                else
-                {
-                    keyValue.Value = value;
-                    keyValue.UpdateDate = DateTime.Now;
-                }
+                keyValue.Value = value;
+                keyValue.UpdateDate = DateTime.Now;
+            }
 
-                _repository.Save(keyValue);
+            _repository.Save(keyValue);
 
-                scope.Complete();
-            }
+            scope.Complete();
         }
+    }
 
-        /// 
-        public void SetValue(string key, string originValue, string newValue)
+    /// 
+    public void SetValue(string key, string originValue, string newValue)
+    {
+        if (!TrySetValue(key, originValue, newValue))
         {
-            if (!TrySetValue(key, originValue, newValue))
-                throw new InvalidOperationException("Could not set the value.");
+            throw new InvalidOperationException("Could not set the value.");
         }
+    }
 
-        /// 
-        public bool TrySetValue(string key, string originalValue, string newValue)
+    /// 
+    public bool TrySetValue(string key, string originalValue, string newValue)
+    {
+        using (ICoreScope scope = _scopeProvider.CreateCoreScope())
         {
-            using (var scope = _scopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(Cms.Core.Constants.Locks.KeyValues);
-
-                var keyValue = _repository.Get(key);
-                if (keyValue == null || keyValue.Value != originalValue)
-                {
-                    return false;
-                }
-
-                keyValue.Value = newValue;
-                keyValue.UpdateDate = DateTime.Now;
-                _repository.Save(keyValue);
+            scope.WriteLock(Constants.Locks.KeyValues);
 
-                scope.Complete();
+            IKeyValue keyValue = _repository.Get(key);
+            if (keyValue == null || keyValue.Value != originalValue)
+            {
+                return false;
             }
 
-            return true;
+            keyValue.Value = newValue;
+            keyValue.UpdateDate = DateTime.Now;
+            _repository.Save(keyValue);
+
+            scope.Complete();
         }
+
+        return true;
     }
 }
diff --git a/src/Umbraco.Core/Services/LocalizationService.cs b/src/Umbraco.Core/Services/LocalizationService.cs
index ac9d800cb102..2d9603a9094c 100644
--- a/src/Umbraco.Core/Services/LocalizationService.cs
+++ b/src/Umbraco.Core/Services/LocalizationService.cs
@@ -1,501 +1,554 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging;
 using Umbraco.Cms.Core.Events;
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Notifications;
+using Umbraco.Cms.Core.Persistence.Querying;
 using Umbraco.Cms.Core.Persistence.Repositories;
 using Umbraco.Cms.Core.Scoping;
 using Umbraco.Extensions;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Represents the Localization Service, which is an easy access to operations involving  and
+///     
+/// 
+internal class LocalizationService : RepositoryService, ILocalizationService
 {
+    private readonly IAuditRepository _auditRepository;
+    private readonly IDictionaryRepository _dictionaryRepository;
+    private readonly ILanguageRepository _languageRepository;
+
+    public LocalizationService(
+        ICoreScopeProvider provider,
+        ILoggerFactory loggerFactory,
+        IEventMessagesFactory eventMessagesFactory,
+        IDictionaryRepository dictionaryRepository,
+        IAuditRepository auditRepository,
+        ILanguageRepository languageRepository)
+        : base(provider, loggerFactory, eventMessagesFactory)
+    {
+        _dictionaryRepository = dictionaryRepository;
+        _auditRepository = auditRepository;
+        _languageRepository = languageRepository;
+    }
+
     /// 
-    /// Represents the Localization Service, which is an easy access to operations involving  and 
+    ///     Adds or updates a translation for a dictionary item and language
     /// 
-    internal class LocalizationService : RepositoryService, ILocalizationService
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    ///     This does not save the item, that needs to be done explicitly
+    /// 
+    public void AddOrUpdateDictionaryValue(IDictionaryItem item, ILanguage? language, string value)
     {
-        private readonly IDictionaryRepository _dictionaryRepository;
-        private readonly ILanguageRepository _languageRepository;
-        private readonly IAuditRepository _auditRepository;
-
-        public LocalizationService(
-            ICoreScopeProvider provider,
-            ILoggerFactory loggerFactory,
-            IEventMessagesFactory eventMessagesFactory,
-            IDictionaryRepository dictionaryRepository,
-            IAuditRepository auditRepository,
-            ILanguageRepository languageRepository)
-            : base(provider, loggerFactory, eventMessagesFactory)
+        if (item == null)
         {
-            _dictionaryRepository = dictionaryRepository;
-            _auditRepository = auditRepository;
-            _languageRepository = languageRepository;
+            throw new ArgumentNullException(nameof(item));
         }
 
-        /// 
-        /// Adds or updates a translation for a dictionary item and language
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// This does not save the item, that needs to be done explicitly
-        /// 
-        public void AddOrUpdateDictionaryValue(IDictionaryItem item, ILanguage? language, string value)
+        if (language == null)
         {
-            if (item == null) throw new ArgumentNullException(nameof(item));
-            if (language == null) throw new ArgumentNullException(nameof(language));
+            throw new ArgumentNullException(nameof(language));
+        }
 
-            var existing = item.Translations?.FirstOrDefault(x => x.Language?.Id == language.Id);
-            if (existing != null)
+        IDictionaryTranslation existing = item.Translations?.FirstOrDefault(x => x.Language?.Id == language.Id);
+        if (existing != null)
+        {
+            existing.Value = value;
+        }
+        else
+        {
+            if (item.Translations is not null)
             {
-                existing.Value = value;
+                item.Translations = new List(item.Translations)
+                {
+                    new DictionaryTranslation(language, value)
+                };
             }
             else
             {
-                if (item.Translations is not null)
-                {
-                    item.Translations = new List(item.Translations)
-                    {
-                        new DictionaryTranslation(language, value)
-                    };
-                }
-                else
-                {
-                    item.Translations = new List
-                    {
-                        new DictionaryTranslation(language, value)
-                    };
-                }
+                item.Translations = new List {new DictionaryTranslation(language, value)};
             }
         }
+    }
 
-        /// 
-        /// Creates and saves a new dictionary item and assigns a value to all languages if defaultValue is specified.
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        public IDictionaryItem CreateDictionaryItemWithIdentity(string key, Guid? parentId, string? defaultValue = null)
+    /// 
+    ///     Creates and saves a new dictionary item and assigns a value to all languages if defaultValue is specified.
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    public IDictionaryItem CreateDictionaryItemWithIdentity(string key, Guid? parentId, string? defaultValue = null)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (var scope = ScopeProvider.CreateCoreScope())
-            {
-                //validate the parent
+            //validate the parent
 
-                if (parentId.HasValue && parentId.Value != Guid.Empty)
+            if (parentId.HasValue && parentId.Value != Guid.Empty)
+            {
+                IDictionaryItem parent = GetDictionaryItemById(parentId.Value);
+                if (parent == null)
                 {
-                    var parent = GetDictionaryItemById(parentId.Value);
-                    if (parent == null)
-                        throw new ArgumentException($"No parent dictionary item was found with id {parentId.Value}.");
+                    throw new ArgumentException($"No parent dictionary item was found with id {parentId.Value}.");
                 }
+            }
 
-                var item = new DictionaryItem(parentId, key);
+            var item = new DictionaryItem(parentId, key);
 
-                if (defaultValue.IsNullOrWhiteSpace() == false)
-                {
-                    var langs = GetAllLanguages();
-                    var translations = langs.Select(language => new DictionaryTranslation(language, defaultValue!))
-                        .Cast()
-                        .ToList();
+            if (defaultValue.IsNullOrWhiteSpace() == false)
+            {
+                IEnumerable langs = GetAllLanguages();
+                var translations = langs.Select(language => new DictionaryTranslation(language, defaultValue!))
+                    .Cast()
+                    .ToList();
 
-                    item.Translations = translations;
-                }
+                item.Translations = translations;
+            }
 
-                EventMessages eventMessages = EventMessagesFactory.Get();
-                var savingNotification = new DictionaryItemSavingNotification(item, eventMessages);
+            EventMessages eventMessages = EventMessagesFactory.Get();
+            var savingNotification = new DictionaryItemSavingNotification(item, eventMessages);
 
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    scope.Complete();
-                    return item;
-                }
-                _dictionaryRepository.Save(item);
+            if (scope.Notifications.PublishCancelable(savingNotification))
+            {
+                scope.Complete();
+                return item;
+            }
 
-                // ensure the lazy Language callback is assigned
-                EnsureDictionaryItemLanguageCallback(item);
+            _dictionaryRepository.Save(item);
 
-                scope.Notifications.Publish(new DictionaryItemSavedNotification(item, eventMessages).WithStateFrom(savingNotification));
+            // ensure the lazy Language callback is assigned
+            EnsureDictionaryItemLanguageCallback(item);
 
-                scope.Complete();
+            scope.Notifications.Publish(
+                new DictionaryItemSavedNotification(item, eventMessages).WithStateFrom(savingNotification));
 
-                return item;
-            }
+            scope.Complete();
+
+            return item;
         }
+    }
 
-        /// 
-        /// Gets a  by its  id
-        /// 
-        /// Id of the 
-        /// 
-        public IDictionaryItem? GetDictionaryItemById(int id)
+    /// 
+    ///     Gets a  by its  id
+    /// 
+    /// Id of the 
+    /// 
+    ///     
+    /// 
+    public IDictionaryItem? GetDictionaryItemById(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                var item = _dictionaryRepository.Get(id);
-                //ensure the lazy Language callback is assigned
-                EnsureDictionaryItemLanguageCallback(item);
-                return item;
-            }
+            IDictionaryItem item = _dictionaryRepository.Get(id);
+            //ensure the lazy Language callback is assigned
+            EnsureDictionaryItemLanguageCallback(item);
+            return item;
         }
+    }
 
-        /// 
-        /// Gets a  by its  id
-        /// 
-        /// Id of the 
-        /// 
-        public IDictionaryItem? GetDictionaryItemById(Guid id)
+    /// 
+    ///     Gets a  by its  id
+    /// 
+    /// Id of the 
+    /// 
+    ///     
+    /// 
+    public IDictionaryItem? GetDictionaryItemById(Guid id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                var item = _dictionaryRepository.Get(id);
-                //ensure the lazy Language callback is assigned
-                EnsureDictionaryItemLanguageCallback(item);
-                return item;
-            }
+            IDictionaryItem item = _dictionaryRepository.Get(id);
+            //ensure the lazy Language callback is assigned
+            EnsureDictionaryItemLanguageCallback(item);
+            return item;
         }
+    }
 
-        /// 
-        /// Gets a  by its key
-        /// 
-        /// Key of the 
-        /// 
-        public IDictionaryItem? GetDictionaryItemByKey(string key)
+    /// 
+    ///     Gets a  by its key
+    /// 
+    /// Key of the 
+    /// 
+    ///     
+    /// 
+    public IDictionaryItem? GetDictionaryItemByKey(string key)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                var item = _dictionaryRepository.Get(key);
-                //ensure the lazy Language callback is assigned
-                EnsureDictionaryItemLanguageCallback(item);
-                return item;
-            }
+            IDictionaryItem item = _dictionaryRepository.Get(key);
+            //ensure the lazy Language callback is assigned
+            EnsureDictionaryItemLanguageCallback(item);
+            return item;
         }
+    }
 
-        /// 
-        /// Gets a list of children for a 
-        /// 
-        /// Id of the parent
-        /// An enumerable list of  objects
-        public IEnumerable? GetDictionaryItemChildren(Guid parentId)
+    /// 
+    ///     Gets a list of children for a 
+    /// 
+    /// Id of the parent
+    /// An enumerable list of  objects
+    public IEnumerable? GetDictionaryItemChildren(Guid parentId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+            IQuery query = Query().Where(x => x.ParentId == parentId);
+            IDictionaryItem[] items = _dictionaryRepository.Get(query)?.ToArray();
+            if (items is not null)
             {
-                var query = Query().Where(x => x.ParentId == parentId);
-                var items = _dictionaryRepository.Get(query)?.ToArray();
-                if (items is not null)
+                //ensure the lazy Language callback is assigned
+                foreach (IDictionaryItem item in items)
                 {
-                    //ensure the lazy Language callback is assigned
-                    foreach (var item in items)
-                        EnsureDictionaryItemLanguageCallback(item);
+                    EnsureDictionaryItemLanguageCallback(item);
                 }
-
-                return items;
             }
+
+            return items;
         }
+    }
 
-        /// 
-        /// Gets a list of descendants for a 
-        /// 
-        /// Id of the parent, null will return all dictionary items
-        /// An enumerable list of  objects
-        public IEnumerable GetDictionaryItemDescendants(Guid? parentId)
+    /// 
+    ///     Gets a list of descendants for a 
+    /// 
+    /// Id of the parent, null will return all dictionary items
+    /// An enumerable list of  objects
+    public IEnumerable GetDictionaryItemDescendants(Guid? parentId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+            IDictionaryItem[] items = _dictionaryRepository.GetDictionaryItemDescendants(parentId).ToArray();
+            //ensure the lazy Language callback is assigned
+            foreach (IDictionaryItem item in items)
             {
-                var items = _dictionaryRepository.GetDictionaryItemDescendants(parentId).ToArray();
-                //ensure the lazy Language callback is assigned
-                foreach (var item in items)
-                    EnsureDictionaryItemLanguageCallback(item);
-                return items;
+                EnsureDictionaryItemLanguageCallback(item);
             }
+
+            return items;
         }
+    }
 
-        /// 
-        /// Gets the root/top  objects
-        /// 
-        /// An enumerable list of  objects
-        public IEnumerable? GetRootDictionaryItems()
+    /// 
+    ///     Gets the root/top  objects
+    /// 
+    /// An enumerable list of  objects
+    public IEnumerable? GetRootDictionaryItems()
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+            IQuery query = Query().Where(x => x.ParentId == null);
+            IDictionaryItem[] items = _dictionaryRepository.Get(query)?.ToArray();
+            if (items is not null)
             {
-                var query = Query().Where(x => x.ParentId == null);
-                var items = _dictionaryRepository.Get(query)?.ToArray();
-                if (items is not null)
+                //ensure the lazy Language callback is assigned
+                foreach (IDictionaryItem item in items)
                 {
-                    //ensure the lazy Language callback is assigned
-                    foreach (var item in items)
-                        EnsureDictionaryItemLanguageCallback(item);
+                    EnsureDictionaryItemLanguageCallback(item);
                 }
-                return items;
             }
+
+            return items;
         }
+    }
 
-        /// 
-        /// Checks if a  with given key exists
-        /// 
-        /// Key of the 
-        /// True if a  exists, otherwise false
-        public bool DictionaryItemExists(string key)
+    /// 
+    ///     Checks if a  with given key exists
+    /// 
+    /// Key of the 
+    /// True if a  exists, otherwise false
+    public bool DictionaryItemExists(string key)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                var item = _dictionaryRepository.Get(key);
-                return item != null;
-            }
+            IDictionaryItem item = _dictionaryRepository.Get(key);
+            return item != null;
         }
+    }
 
-        /// 
-        /// Saves a  object
-        /// 
-        ///  to save
-        /// Optional id of the user saving the dictionary item
-        public void Save(IDictionaryItem dictionaryItem, int userId = Cms.Core.Constants.Security.SuperUserId)
+    /// 
+    ///     Saves a  object
+    /// 
+    ///  to save
+    /// Optional id of the user saving the dictionary item
+    public void Save(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (var scope = ScopeProvider.CreateCoreScope())
+            EventMessages eventMessages = EventMessagesFactory.Get();
+            var savingNotification = new DictionaryItemSavingNotification(dictionaryItem, eventMessages);
+            if (scope.Notifications.PublishCancelable(savingNotification))
             {
-                EventMessages eventMessages = EventMessagesFactory.Get();
-                var savingNotification = new DictionaryItemSavingNotification(dictionaryItem, eventMessages);
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
+                scope.Complete();
+                return;
+            }
 
-                _dictionaryRepository.Save(dictionaryItem);
+            _dictionaryRepository.Save(dictionaryItem);
 
-                // ensure the lazy Language callback is assigned
-                // ensure the lazy Language callback is assigned
+            // ensure the lazy Language callback is assigned
+            // ensure the lazy Language callback is assigned
 
-                EnsureDictionaryItemLanguageCallback(dictionaryItem);
-                scope.Notifications.Publish(new DictionaryItemSavedNotification(dictionaryItem, eventMessages).WithStateFrom(savingNotification));
+            EnsureDictionaryItemLanguageCallback(dictionaryItem);
+            scope.Notifications.Publish(
+                new DictionaryItemSavedNotification(dictionaryItem, eventMessages).WithStateFrom(savingNotification));
 
-                Audit(AuditType.Save, "Save DictionaryItem", userId, dictionaryItem.Id, "DictionaryItem");
-                scope.Complete();
-            }
+            Audit(AuditType.Save, "Save DictionaryItem", userId, dictionaryItem.Id, "DictionaryItem");
+            scope.Complete();
         }
+    }
 
-        /// 
-        /// Deletes a  object and its related translations
-        /// as well as its children.
-        /// 
-        ///  to delete
-        /// Optional id of the user deleting the dictionary item
-        public void Delete(IDictionaryItem dictionaryItem, int userId = Cms.Core.Constants.Security.SuperUserId)
+    /// 
+    ///     Deletes a  object and its related translations
+    ///     as well as its children.
+    /// 
+    ///  to delete
+    /// Optional id of the user deleting the dictionary item
+    public void Delete(IDictionaryItem dictionaryItem, int userId = Constants.Security.SuperUserId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (var scope = ScopeProvider.CreateCoreScope())
+            EventMessages eventMessages = EventMessagesFactory.Get();
+            var deletingNotification = new DictionaryItemDeletingNotification(dictionaryItem, eventMessages);
+            if (scope.Notifications.PublishCancelable(deletingNotification))
             {
-                EventMessages eventMessages = EventMessagesFactory.Get();
-                var deletingNotification = new DictionaryItemDeletingNotification(dictionaryItem, eventMessages);
-                if (scope.Notifications.PublishCancelable(deletingNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
+                scope.Complete();
+                return;
+            }
 
-                _dictionaryRepository.Delete(dictionaryItem);
-                scope.Notifications.Publish(new DictionaryItemDeletedNotification(dictionaryItem, eventMessages).WithStateFrom(deletingNotification));
+            _dictionaryRepository.Delete(dictionaryItem);
+            scope.Notifications.Publish(
+                new DictionaryItemDeletedNotification(dictionaryItem, eventMessages)
+                    .WithStateFrom(deletingNotification));
 
-                Audit(AuditType.Delete, "Delete DictionaryItem", userId, dictionaryItem.Id, "DictionaryItem");
+            Audit(AuditType.Delete, "Delete DictionaryItem", userId, dictionaryItem.Id, "DictionaryItem");
 
-                scope.Complete();
-            }
+            scope.Complete();
         }
+    }
 
-        /// 
-        /// Gets a  by its id
-        /// 
-        /// Id of the 
-        /// 
-        public ILanguage? GetLanguageById(int id)
+    /// 
+    ///     Gets a  by its id
+    /// 
+    /// Id of the 
+    /// 
+    ///     
+    /// 
+    public ILanguage? GetLanguageById(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _languageRepository.Get(id);
-            }
+            return _languageRepository.Get(id);
         }
+    }
 
-        /// 
-        /// Gets a  by its iso code
-        /// 
-        /// Iso Code of the language (ie. en-US)
-        /// 
-        public ILanguage? GetLanguageByIsoCode(string? isoCode)
+    /// 
+    ///     Gets a  by its iso code
+    /// 
+    /// Iso Code of the language (ie. en-US)
+    /// 
+    ///     
+    /// 
+    public ILanguage? GetLanguageByIsoCode(string? isoCode)
+    {
+        if (isoCode is null)
         {
-            if (isoCode is null)
-            {
-                return null;
-            }
-            
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _languageRepository.GetByIsoCode(isoCode);
-            }
+            return null;
         }
 
-        /// 
-        public int? GetLanguageIdByIsoCode(string isoCode)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _languageRepository.GetIdByIsoCode(isoCode);
-            }
+            return _languageRepository.GetByIsoCode(isoCode);
         }
+    }
 
-        /// 
-        public string? GetLanguageIsoCodeById(int id)
+    /// 
+    public int? GetLanguageIdByIsoCode(string isoCode)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _languageRepository.GetIsoCodeById(id);
-            }
+            return _languageRepository.GetIdByIsoCode(isoCode);
         }
+    }
 
-        /// 
-        public string GetDefaultLanguageIsoCode()
+    /// 
+    public string? GetLanguageIsoCodeById(int id)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _languageRepository.GetDefaultIsoCode();
-            }
+            return _languageRepository.GetIsoCodeById(id);
         }
+    }
 
-        /// 
-        public int? GetDefaultLanguageId()
+    /// 
+    public string GetDefaultLanguageIsoCode()
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _languageRepository.GetDefaultId();
-            }
+            return _languageRepository.GetDefaultIsoCode();
         }
+    }
 
-        /// 
-        /// Gets all available languages
-        /// 
-        /// An enumerable list of  objects
-        public IEnumerable GetAllLanguages()
+    /// 
+    public int? GetDefaultLanguageId()
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _languageRepository.GetMany();
-            }
+            return _languageRepository.GetDefaultId();
+        }
+    }
+
+    /// 
+    ///     Gets all available languages
+    /// 
+    /// An enumerable list of  objects
+    public IEnumerable GetAllLanguages()
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return _languageRepository.GetMany();
         }
+    }
 
-        /// 
-        /// Saves a  object
-        /// 
-        ///  to save
-        /// Optional id of the user saving the language
-        public void Save(ILanguage language, int userId = Cms.Core.Constants.Security.SuperUserId)
+    /// 
+    ///     Saves a  object
+    /// 
+    ///  to save
+    /// Optional id of the user saving the language
+    public void Save(ILanguage language, int userId = Constants.Security.SuperUserId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (var scope = ScopeProvider.CreateCoreScope())
-            {
-                // write-lock languages to guard against race conds when dealing with default language
-                scope.WriteLock(Cms.Core.Constants.Locks.Languages);
+            // write-lock languages to guard against race conds when dealing with default language
+            scope.WriteLock(Constants.Locks.Languages);
 
-                // look for cycles - within write-lock
-                if (language.FallbackLanguageId.HasValue)
+            // look for cycles - within write-lock
+            if (language.FallbackLanguageId.HasValue)
+            {
+                var languages = _languageRepository.GetMany().ToDictionary(x => x.Id, x => x);
+                if (!languages.ContainsKey(language.FallbackLanguageId.Value))
                 {
-                    var languages = _languageRepository.GetMany().ToDictionary(x => x.Id, x => x);
-                    if (!languages.ContainsKey(language.FallbackLanguageId.Value))
-                        throw new InvalidOperationException($"Cannot save language {language.IsoCode} with fallback id={language.FallbackLanguageId.Value} which is not a valid language id.");
-                    if (CreatesCycle(language, languages))
-                        throw new InvalidOperationException($"Cannot save language {language.IsoCode} with fallback {languages[language.FallbackLanguageId.Value].IsoCode} as it would create a fallback cycle.");
+                    throw new InvalidOperationException(
+                        $"Cannot save language {language.IsoCode} with fallback id={language.FallbackLanguageId.Value} which is not a valid language id.");
                 }
 
-                EventMessages eventMessages = EventMessagesFactory.Get();
-                var savingNotification = new LanguageSavingNotification(language, eventMessages);
-                if (scope.Notifications.PublishCancelable(savingNotification))
+                if (CreatesCycle(language, languages))
                 {
-                    scope.Complete();
-                    return;
+                    throw new InvalidOperationException(
+                        $"Cannot save language {language.IsoCode} with fallback {languages[language.FallbackLanguageId.Value].IsoCode} as it would create a fallback cycle.");
                 }
+            }
 
-                _languageRepository.Save(language);
-                scope.Notifications.Publish(new LanguageSavedNotification(language, eventMessages).WithStateFrom(savingNotification));
-
-                Audit(AuditType.Save, "Save Language", userId, language.Id, ObjectTypes.GetName(UmbracoObjectTypes.Language));
-
+            EventMessages eventMessages = EventMessagesFactory.Get();
+            var savingNotification = new LanguageSavingNotification(language, eventMessages);
+            if (scope.Notifications.PublishCancelable(savingNotification))
+            {
                 scope.Complete();
+                return;
             }
-        }
 
-        private bool CreatesCycle(ILanguage language, IDictionary languages)
-        {
-            // a new language is not referenced yet, so cannot be part of a cycle
-            if (!language.HasIdentity) return false;
+            _languageRepository.Save(language);
+            scope.Notifications.Publish(
+                new LanguageSavedNotification(language, eventMessages).WithStateFrom(savingNotification));
 
-            var id = language.FallbackLanguageId;
-            while (true) // assuming languages does not already contains a cycle, this must end
-            {
-                if (!id.HasValue) return false; // no fallback means no cycle
-                if (id.Value == language.Id) return true; // back to language = cycle!
-                id = languages[id.Value].FallbackLanguageId; // else keep chaining
-            }
+            Audit(AuditType.Save, "Save Language", userId, language.Id, UmbracoObjectTypes.Language.GetName());
+
+            scope.Complete();
         }
+    }
 
-        /// 
-        /// Deletes a  by removing it (but not its usages) from the db
-        /// 
-        ///  to delete
-        /// Optional id of the user deleting the language
-        public void Delete(ILanguage language, int userId = Cms.Core.Constants.Security.SuperUserId)
+    /// 
+    ///     Deletes a  by removing it (but not its usages) from the db
+    /// 
+    ///  to delete
+    /// Optional id of the user deleting the language
+    public void Delete(ILanguage language, int userId = Constants.Security.SuperUserId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (var scope = ScopeProvider.CreateCoreScope())
+            // write-lock languages to guard against race conds when dealing with default language
+            scope.WriteLock(Constants.Locks.Languages);
+
+            EventMessages eventMessages = EventMessagesFactory.Get();
+            var deletingLanguageNotification = new LanguageDeletingNotification(language, eventMessages);
+            if (scope.Notifications.PublishCancelable(deletingLanguageNotification))
             {
-                // write-lock languages to guard against race conds when dealing with default language
-                scope.WriteLock(Cms.Core.Constants.Locks.Languages);
+                scope.Complete();
+                return;
+            }
 
-                EventMessages eventMessages = EventMessagesFactory.Get();
-                var deletingLanguageNotification = new LanguageDeletingNotification(language, eventMessages);
-                if (scope.Notifications.PublishCancelable(deletingLanguageNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
+            // NOTE: Other than the fall-back language, there aren't any other constraints in the db, so possible references aren't deleted
+            _languageRepository.Delete(language);
 
-                // NOTE: Other than the fall-back language, there aren't any other constraints in the db, so possible references aren't deleted
-                _languageRepository.Delete(language);
+            scope.Notifications.Publish(
+                new LanguageDeletedNotification(language, eventMessages).WithStateFrom(deletingLanguageNotification));
 
-                scope.Notifications.Publish(new LanguageDeletedNotification(language, eventMessages).WithStateFrom(deletingLanguageNotification));
+            Audit(AuditType.Delete, "Delete Language", userId, language.Id, UmbracoObjectTypes.Language.GetName());
+            scope.Complete();
+        }
+    }
 
-                Audit(AuditType.Delete, "Delete Language", userId, language.Id, ObjectTypes.GetName(UmbracoObjectTypes.Language));
-                scope.Complete();
-            }
+    public Dictionary GetDictionaryItemKeyMap()
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return _dictionaryRepository.GetDictionaryItemKeyMap();
         }
+    }
 
-        private void Audit(AuditType type, string message, int userId, int objectId, string? entityType)
+    private bool CreatesCycle(ILanguage language, IDictionary languages)
+    {
+        // a new language is not referenced yet, so cannot be part of a cycle
+        if (!language.HasIdentity)
         {
-            _auditRepository.Save(new AuditItem(objectId, type, userId, entityType, message));
+            return false;
         }
 
-        /// 
-        /// This is here to take care of a hack - the DictionaryTranslation model contains an ILanguage reference which we don't want but
-        /// we cannot remove it because it would be a large breaking change, so we need to make sure it's resolved lazily. This is because
-        /// if developers have a lot of dictionary items and translations, the caching and cloning size gets much larger because of
-        /// the large object graphs. So now we don't cache or clone the attached ILanguage
-        /// 
-        private void EnsureDictionaryItemLanguageCallback(IDictionaryItem? d)
+        var id = language.FallbackLanguageId;
+        while (true) // assuming languages does not already contains a cycle, this must end
         {
-            var item = d as DictionaryItem;
-            if (item == null) return;
+            if (!id.HasValue)
+            {
+                return false; // no fallback means no cycle
+            }
 
-            item.GetLanguage = GetLanguageById;
-            var translations = item.Translations?.OfType();
-            if (translations is not null)
+            if (id.Value == language.Id)
             {
-                foreach (var trans in translations)
-                    trans.GetLanguage = GetLanguageById;
+                return true; // back to language = cycle!
             }
+
+            id = languages[id.Value].FallbackLanguageId; // else keep chaining
+        }
+    }
+
+    private void Audit(AuditType type, string message, int userId, int objectId, string? entityType) =>
+        _auditRepository.Save(new AuditItem(objectId, type, userId, entityType, message));
+
+    /// 
+    ///     This is here to take care of a hack - the DictionaryTranslation model contains an ILanguage reference which we
+    ///     don't want but
+    ///     we cannot remove it because it would be a large breaking change, so we need to make sure it's resolved lazily. This
+    ///     is because
+    ///     if developers have a lot of dictionary items and translations, the caching and cloning size gets much larger
+    ///     because of
+    ///     the large object graphs. So now we don't cache or clone the attached ILanguage
+    /// 
+    private void EnsureDictionaryItemLanguageCallback(IDictionaryItem? d)
+    {
+        var item = d as DictionaryItem;
+        if (item == null)
+        {
+            return;
         }
 
-        public Dictionary GetDictionaryItemKeyMap()
+        item.GetLanguage = GetLanguageById;
+        IEnumerable translations = item.Translations?.OfType();
+        if (translations is not null)
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+            foreach (DictionaryTranslation trans in translations)
             {
-                return _dictionaryRepository.GetDictionaryItemKeyMap();
+                trans.GetLanguage = GetLanguageById;
             }
         }
     }
diff --git a/src/Umbraco.Core/Services/LocalizedTextService.cs b/src/Umbraco.Core/Services/LocalizedTextService.cs
index 6aa7e8fb2b85..45909fd808f1 100644
--- a/src/Umbraco.Core/Services/LocalizedTextService.cs
+++ b/src/Umbraco.Core/Services/LocalizedTextService.cs
@@ -1,469 +1,518 @@
-using System;
-using System.Collections.Generic;
 using System.Globalization;
-using System.Linq;
 using System.Xml.Linq;
 using System.Xml.XPath;
 using Microsoft.Extensions.Logging;
 
-namespace Umbraco.Cms.Core.Services
-{
-    /// 
-    public class LocalizedTextService : ILocalizedTextService
-    {
-        private readonly ILogger _logger;
-        private readonly Lazy? _fileSources;
-
-        private IDictionary>>> _dictionarySource =>
-            _dictionarySourceLazy.Value;
+namespace Umbraco.Cms.Core.Services;
 
-        private IDictionary>> _noAreaDictionarySource =>
-            _noAreaDictionarySourceLazy.Value;
+/// 
+public class LocalizedTextService : ILocalizedTextService
+{
+    private readonly Lazy>>>>
+        _dictionarySourceLazy;
 
-        private readonly Lazy>>>>
-            _dictionarySourceLazy;
+    private readonly Lazy? _fileSources;
+    private readonly ILogger _logger;
 
-        private readonly Lazy>>> _noAreaDictionarySourceLazy;
+    private readonly Lazy>>> _noAreaDictionarySourceLazy;
 
-        /// 
-        /// Initializes with a file sources instance
-        /// 
-        /// 
-        /// 
-        public LocalizedTextService(Lazy fileSources,
-            ILogger logger)
+    /// 
+    ///     Initializes with a file sources instance
+    /// 
+    /// 
+    /// 
+    public LocalizedTextService(Lazy fileSources,
+        ILogger logger)
+    {
+        if (logger == null)
         {
-            if (logger == null) throw new ArgumentNullException(nameof(logger));
-            _logger = logger;
-            if (fileSources == null) throw new ArgumentNullException(nameof(fileSources));
-            _dictionarySourceLazy =
-                new Lazy>>>>(() =>
-                    FileSourcesToAreaDictionarySources(fileSources.Value));
-            _noAreaDictionarySourceLazy =
-                new Lazy>>>(() =>
-                    FileSourcesToNoAreaDictionarySources(fileSources.Value));
-            _fileSources = fileSources;
+            throw new ArgumentNullException(nameof(logger));
         }
 
-        private IDictionary>> FileSourcesToNoAreaDictionarySources(
-            LocalizedTextServiceFileSources fileSources)
+        _logger = logger;
+        if (fileSources == null)
         {
-            var xmlSources = fileSources.GetXmlSources();
-
-            return XmlSourceToNoAreaDictionary(xmlSources);
+            throw new ArgumentNullException(nameof(fileSources));
         }
 
-        private IDictionary>> XmlSourceToNoAreaDictionary(
-            IDictionary> xmlSources)
-        {
-            var cultureNoAreaDictionary = new Dictionary>>();
-            foreach (var xmlSource in xmlSources)
-            {
-                var noAreaAliasValue =
-                    new Lazy>(() => GetNoAreaStoredTranslations(xmlSources, xmlSource.Key));
-                cultureNoAreaDictionary.Add(xmlSource.Key, noAreaAliasValue);
-            }
-
-            return cultureNoAreaDictionary;
-        }
+        _dictionarySourceLazy =
+            new Lazy>>>>(() =>
+                FileSourcesToAreaDictionarySources(fileSources.Value));
+        _noAreaDictionarySourceLazy =
+            new Lazy>>>(() =>
+                FileSourcesToNoAreaDictionarySources(fileSources.Value));
+        _fileSources = fileSources;
+    }
 
-        private IDictionary>>>
-            FileSourcesToAreaDictionarySources(LocalizedTextServiceFileSources fileSources)
+    /// 
+    ///     Initializes with an XML source
+    /// 
+    /// 
+    /// 
+    public LocalizedTextService(IDictionary> source,
+        ILogger logger)
+    {
+        if (source == null)
         {
-            var xmlSources = fileSources.GetXmlSources();
-            return XmlSourcesToAreaDictionary(xmlSources);
+            throw new ArgumentNullException(nameof(source));
         }
 
-        private IDictionary>>>
-            XmlSourcesToAreaDictionary(IDictionary> xmlSources)
+        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+
+        _dictionarySourceLazy =
+            new Lazy>>>>(() =>
+                XmlSourcesToAreaDictionary(source));
+        _noAreaDictionarySourceLazy =
+            new Lazy>>>(() =>
+                XmlSourceToNoAreaDictionary(source));
+    }
+
+    [Obsolete(
+        "Use other ctor with IDictionary>>> as input parameter.")]
+    public LocalizedTextService(IDictionary>> source,
+        ILogger logger) : this(
+        source.ToDictionary(x => x.Key, x => new Lazy>>(() => x.Value)),
+        logger)
+    {
+    }
+
+    /// 
+    ///     Initializes with a source of a dictionary of culture -> areas -> sub dictionary of keys/values
+    /// 
+    /// 
+    /// 
+    public LocalizedTextService(
+        IDictionary>>> source,
+        ILogger logger)
+    {
+        IDictionary>>> dictionarySource =
+            source ?? throw new ArgumentNullException(nameof(source));
+        _dictionarySourceLazy =
+            new Lazy>>>>(() =>
+                dictionarySource);
+        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+        var cultureNoAreaDictionary = new Dictionary>>();
+        foreach (KeyValuePair>>> cultureDictionary in
+                 dictionarySource)
         {
-            var cultureDictionary =
-                new Dictionary>>>();
-            foreach (var xmlSource in xmlSources)
-            {
-                var areaAliaValue =
-                    new Lazy>>(() =>
-                        GetAreaStoredTranslations(xmlSources, xmlSource.Key));
-                cultureDictionary.Add(xmlSource.Key, areaAliaValue);
-            }
+            Dictionary> areaAliaValue =
+                GetAreaStoredTranslations(source, cultureDictionary.Key);
 
-            return cultureDictionary;
+            cultureNoAreaDictionary.Add(cultureDictionary.Key,
+                new Lazy>(() => GetAliasValues(areaAliaValue)));
         }
 
-        /// 
-        /// Initializes with an XML source
-        /// 
-        /// 
-        /// 
-        public LocalizedTextService(IDictionary> source,
-            ILogger logger)
+        _noAreaDictionarySourceLazy =
+            new Lazy>>>(() => cultureNoAreaDictionary);
+    }
+
+    private IDictionary>>> _dictionarySource =>
+        _dictionarySourceLazy.Value;
+
+    private IDictionary>> _noAreaDictionarySource =>
+        _noAreaDictionarySourceLazy.Value;
+
+    public string Localize(string? area, string? alias, CultureInfo? culture,
+        IDictionary? tokens = null)
+    {
+        if (culture == null)
         {
-            if (source == null) throw new ArgumentNullException(nameof(source));
-            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
-
-            _dictionarySourceLazy =
-                new Lazy>>>>(() =>
-                    XmlSourcesToAreaDictionary(source));
-            _noAreaDictionarySourceLazy =
-                new Lazy>>>(() =>
-                    XmlSourceToNoAreaDictionary(source));
+            throw new ArgumentNullException(nameof(culture));
         }
 
-        [Obsolete("Use other ctor with IDictionary>>> as input parameter.")]
-        public LocalizedTextService(IDictionary>> source,
-            ILogger logger) : this(source.ToDictionary(x=>x.Key, x=> new Lazy>>(() => x.Value)), logger)
+        //This is what the legacy ui service did
+        if (string.IsNullOrEmpty(alias))
         {
-
+            return string.Empty;
         }
-        /// 
-        /// Initializes with a source of a dictionary of culture -> areas -> sub dictionary of keys/values
-        /// 
-        /// 
-        /// 
-        public LocalizedTextService(
-            IDictionary>>> source,
-            ILogger logger)
+
+        // TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode
+        culture = ConvertToSupportedCultureWithRegionCode(culture);
+
+        return GetFromDictionarySource(culture, area, alias, tokens);
+    }
+
+    /// 
+    ///     Returns all key/values in storage for the given culture
+    /// 
+    public IDictionary GetAllStoredValues(CultureInfo culture)
+    {
+        if (culture == null)
         {
-            var dictionarySource = source ?? throw new ArgumentNullException(nameof(source));
-            _dictionarySourceLazy =
-                new Lazy>>>>(() =>
-                    dictionarySource);
-            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
-            var cultureNoAreaDictionary = new Dictionary>>();
-            foreach (var cultureDictionary in dictionarySource)
-            {
-                var areaAliaValue = GetAreaStoredTranslations(source, cultureDictionary.Key);
+            throw new ArgumentNullException(nameof(culture));
+        }
 
-                cultureNoAreaDictionary.Add(cultureDictionary.Key,
-                    new Lazy>(() => GetAliasValues(areaAliaValue)));
-            }
+        // TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode
+        culture = ConvertToSupportedCultureWithRegionCode(culture);
 
-            _noAreaDictionarySourceLazy =
-                new Lazy>>>(() => cultureNoAreaDictionary);
+        if (_dictionarySource.ContainsKey(culture) == false)
+        {
+            _logger.LogWarning(
+                "The culture specified {Culture} was not found in any configured sources for this service",
+                culture);
+            return new Dictionary(0);
         }
 
-        private static Dictionary GetAliasValues(
-            Dictionary> areaAliaValue)
+        IDictionary result = new Dictionary();
+        //convert all areas + keys to a single key with a '/'
+        foreach (KeyValuePair> area in _dictionarySource[culture].Value)
         {
-            var aliasValue = new Dictionary();
-            foreach (var area in areaAliaValue)
+            foreach (KeyValuePair key in area.Value)
             {
-                foreach (var alias in area.Value)
+                var dictionaryKey = string.Format("{0}/{1}", area.Key, key.Key);
+                //i don't think it's possible to have duplicates because we're dealing with a dictionary in the first place, but we'll double check here just in case.
+                if (result.ContainsKey(dictionaryKey) == false)
                 {
-                    if (!aliasValue.ContainsKey(alias.Key))
-                    {
-                        aliasValue.Add(alias.Key, alias.Value);
-                    }
+                    result.Add(dictionaryKey, key.Value);
                 }
             }
+        }
+
+        return result;
+    }
+
+    /// 
+    ///     Returns a list of all currently supported cultures
+    /// 
+    /// 
+    public IEnumerable GetSupportedCultures() => _dictionarySource.Keys;
+
+    /// 
+    ///     Tries to resolve a full 4 letter culture from a 2 letter culture name
+    /// 
+    /// 
+    ///     The culture to determine if it is only a 2 letter culture, if so we'll try to convert it, otherwise it will just be
+    ///     returned
+    /// 
+    /// 
+    /// 
+    ///     TODO: This is just a hack due to the way we store the language files, they should be stored with 4 letters since
+    ///     that
+    ///     is what they reference but they are stored with 2, further more our user's languages are stored with 2. So this
+    ///     attempts
+    ///     to resolve the full culture if possible.
+    ///     This only works when this service is constructed with the LocalizedTextServiceFileSources
+    /// 
+    public CultureInfo ConvertToSupportedCultureWithRegionCode(CultureInfo currentCulture)
+    {
+        if (currentCulture == null)
+        {
+            throw new ArgumentNullException("currentCulture");
+        }
 
-            return aliasValue;
+        if (_fileSources == null)
+        {
+            return currentCulture;
         }
 
-        public string Localize(string key, CultureInfo culture, IDictionary? tokens = null)
+        if (currentCulture.Name.Length > 2)
         {
-            if (culture == null) throw new ArgumentNullException(nameof(culture));
+            return currentCulture;
+        }
 
-            //This is what the legacy ui service did
-            if (string.IsNullOrEmpty(key))
-                return string.Empty;
+        Attempt attempt =
+            _fileSources.Value.TryConvert2LetterCultureTo4Letter(currentCulture.TwoLetterISOLanguageName);
+        return attempt.Success ? attempt.Result! : currentCulture;
+    }
 
-            var keyParts = key.Split(Constants.CharArrays.ForwardSlash, StringSplitOptions.RemoveEmptyEntries);
-            var area = keyParts.Length > 1 ? keyParts[0] : null;
-            var alias = keyParts.Length > 1 ? keyParts[1] : keyParts[0];
-            return Localize(area, alias, culture, tokens);
+    /// 
+    ///     Returns all key/values in storage for the given culture
+    /// 
+    /// 
+    public IDictionary> GetAllStoredValuesByAreaAndAlias(CultureInfo culture)
+    {
+        if (culture == null)
+        {
+            throw new ArgumentNullException("culture");
         }
 
-        public string Localize(string? area, string? alias, CultureInfo? culture,
-            IDictionary? tokens = null)
+        // TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode
+        culture = ConvertToSupportedCultureWithRegionCode(culture);
+
+        if (_dictionarySource.ContainsKey(culture) == false)
         {
-            if (culture == null) throw new ArgumentNullException(nameof(culture));
+            _logger.LogWarning(
+                "The culture specified {Culture} was not found in any configured sources for this service",
+                culture);
+            return new Dictionary>(0);
+        }
 
-            //This is what the legacy ui service did
-            if (string.IsNullOrEmpty(alias))
-                return string.Empty;
+        return _dictionarySource[culture].Value;
+    }
 
-            // TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode
-            culture = ConvertToSupportedCultureWithRegionCode(culture);
+    private IDictionary>> FileSourcesToNoAreaDictionarySources(
+        LocalizedTextServiceFileSources fileSources)
+    {
+        IDictionary> xmlSources = fileSources.GetXmlSources();
 
-            return GetFromDictionarySource(culture, area, alias, tokens);
-        }
+        return XmlSourceToNoAreaDictionary(xmlSources);
+    }
 
-        /// 
-        /// Returns all key/values in storage for the given culture
-        /// 
-        public IDictionary GetAllStoredValues(CultureInfo culture)
+    private IDictionary>> XmlSourceToNoAreaDictionary(
+        IDictionary> xmlSources)
+    {
+        var cultureNoAreaDictionary = new Dictionary>>();
+        foreach (KeyValuePair> xmlSource in xmlSources)
         {
-            if (culture == null) throw new ArgumentNullException(nameof(culture));
-
-            // TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode
-            culture = ConvertToSupportedCultureWithRegionCode(culture);
+            var noAreaAliasValue =
+                new Lazy>(() => GetNoAreaStoredTranslations(xmlSources, xmlSource.Key));
+            cultureNoAreaDictionary.Add(xmlSource.Key, noAreaAliasValue);
+        }
 
-            if (_dictionarySource.ContainsKey(culture) == false)
-            {
-                _logger.LogWarning(
-                    "The culture specified {Culture} was not found in any configured sources for this service",
-                    culture);
-                return new Dictionary(0);
-            }
+        return cultureNoAreaDictionary;
+    }
 
-            IDictionary result = new Dictionary();
-            //convert all areas + keys to a single key with a '/'
-            foreach (var area in _dictionarySource[culture].Value)
-            {
-                foreach (var key in area.Value)
-                {
-                    var dictionaryKey = string.Format("{0}/{1}", area.Key, key.Key);
-                    //i don't think it's possible to have duplicates because we're dealing with a dictionary in the first place, but we'll double check here just in case.
-                    if (result.ContainsKey(dictionaryKey) == false)
-                    {
-                        result.Add(dictionaryKey, key.Value);
-                    }
-                }
-            }
+    private IDictionary>>>
+        FileSourcesToAreaDictionarySources(LocalizedTextServiceFileSources fileSources)
+    {
+        IDictionary> xmlSources = fileSources.GetXmlSources();
+        return XmlSourcesToAreaDictionary(xmlSources);
+    }
 
-            return result;
+    private IDictionary>>>
+        XmlSourcesToAreaDictionary(IDictionary> xmlSources)
+    {
+        var cultureDictionary =
+            new Dictionary>>>();
+        foreach (KeyValuePair> xmlSource in xmlSources)
+        {
+            var areaAliaValue =
+                new Lazy>>(() =>
+                    GetAreaStoredTranslations(xmlSources, xmlSource.Key));
+            cultureDictionary.Add(xmlSource.Key, areaAliaValue);
         }
 
-        private IDictionary> GetAreaStoredTranslations(
-            IDictionary> xmlSource, CultureInfo cult)
+        return cultureDictionary;
+    }
+
+    private static Dictionary GetAliasValues(
+        Dictionary> areaAliaValue)
+    {
+        var aliasValue = new Dictionary();
+        foreach (KeyValuePair> area in areaAliaValue)
         {
-            var overallResult = new Dictionary>(StringComparer.InvariantCulture);
-            var areas = xmlSource[cult].Value.XPathSelectElements("//area");
-            foreach (var area in areas)
+            foreach (KeyValuePair alias in area.Value)
             {
-                var result = new Dictionary(StringComparer.InvariantCulture);
-                var keys = area.XPathSelectElements("./key");
-                foreach (var key in keys)
+                if (!aliasValue.ContainsKey(alias.Key))
                 {
-                    var dictionaryKey =
-                        (string)key.Attribute("alias")!;
-                    //there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files
-                    if (result.ContainsKey(dictionaryKey) == false)
-                        result.Add(dictionaryKey, key.Value);
+                    aliasValue.Add(alias.Key, alias.Value);
                 }
-
-                overallResult.Add(area.Attribute("alias")!.Value, result);
             }
+        }
 
-            //Merge English Dictionary
-            var englishCulture = new CultureInfo("en-US");
-            if (!cult.Equals(englishCulture))
-            {
-                var enUS = xmlSource[englishCulture].Value.XPathSelectElements("//area");
-                foreach (var area in enUS)
-                {
-                    IDictionary
-                        result = new Dictionary(StringComparer.InvariantCulture);
-                    if (overallResult.ContainsKey(area.Attribute("alias")!.Value))
-                    {
-                        result = overallResult[area.Attribute("alias")!.Value];
-                    }
-
-                    var keys = area.XPathSelectElements("./key");
-                    foreach (var key in keys)
-                    {
-                        var dictionaryKey =
-                            (string)key.Attribute("alias")!;
-                        //there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files
-                        if (result.ContainsKey(dictionaryKey) == false)
-                            result.Add(dictionaryKey, key.Value);
-                    }
+        return aliasValue;
+    }
 
-                    if (!overallResult.ContainsKey(area.Attribute("alias")!.Value))
-                    {
-                        overallResult.Add(area.Attribute("alias")!.Value, result);
-                    }
-                }
-            }
+    public string Localize(string key, CultureInfo culture, IDictionary? tokens = null)
+    {
+        if (culture == null)
+        {
+            throw new ArgumentNullException(nameof(culture));
+        }
 
-            return overallResult;
+        //This is what the legacy ui service did
+        if (string.IsNullOrEmpty(key))
+        {
+            return string.Empty;
         }
 
-        private Dictionary GetNoAreaStoredTranslations(
-            IDictionary> xmlSource, CultureInfo cult)
+        var keyParts = key.Split(Constants.CharArrays.ForwardSlash, StringSplitOptions.RemoveEmptyEntries);
+        var area = keyParts.Length > 1 ? keyParts[0] : null;
+        var alias = keyParts.Length > 1 ? keyParts[1] : keyParts[0];
+        return Localize(area, alias, culture, tokens);
+    }
+
+    private IDictionary> GetAreaStoredTranslations(
+        IDictionary> xmlSource, CultureInfo cult)
+    {
+        var overallResult = new Dictionary>(StringComparer.InvariantCulture);
+        IEnumerable areas = xmlSource[cult].Value.XPathSelectElements("//area");
+        foreach (XElement area in areas)
         {
             var result = new Dictionary(StringComparer.InvariantCulture);
-            var keys = xmlSource[cult].Value.XPathSelectElements("//key");
-
-            foreach (var key in keys)
+            IEnumerable keys = area.XPathSelectElements("./key");
+            foreach (XElement key in keys)
             {
                 var dictionaryKey =
                     (string)key.Attribute("alias")!;
                 //there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files
                 if (result.ContainsKey(dictionaryKey) == false)
+                {
                     result.Add(dictionaryKey, key.Value);
+                }
             }
 
-            //Merge English Dictionary
-            var englishCulture = new CultureInfo("en-US");
-            if (!cult.Equals(englishCulture))
+            overallResult.Add(area.Attribute("alias")!.Value, result);
+        }
+
+        //Merge English Dictionary
+        var englishCulture = new CultureInfo("en-US");
+        if (!cult.Equals(englishCulture))
+        {
+            IEnumerable enUS = xmlSource[englishCulture].Value.XPathSelectElements("//area");
+            foreach (XElement area in enUS)
             {
-                var keysEn = xmlSource[englishCulture].Value.XPathSelectElements("//key");
+                IDictionary
+                    result = new Dictionary(StringComparer.InvariantCulture);
+                if (overallResult.ContainsKey(area.Attribute("alias")!.Value))
+                {
+                    result = overallResult[area.Attribute("alias")!.Value];
+                }
 
-                foreach (var key in keys)
+                IEnumerable keys = area.XPathSelectElements("./key");
+                foreach (XElement key in keys)
                 {
                     var dictionaryKey =
                         (string)key.Attribute("alias")!;
                     //there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files
                     if (result.ContainsKey(dictionaryKey) == false)
+                    {
                         result.Add(dictionaryKey, key.Value);
+                    }
                 }
-            }
 
-            return result;
-        }
-
-        private Dictionary> GetAreaStoredTranslations(
-            IDictionary>>> dictionarySource,
-            CultureInfo cult)
-        {
-            var overallResult = new Dictionary>(StringComparer.InvariantCulture);
-            var areaDict = dictionarySource[cult];
-
-            foreach (var area in areaDict.Value)
-            {
-                var result = new Dictionary(StringComparer.InvariantCulture);
-                var keys = area.Value.Keys;
-                foreach (var key in keys)
+                if (!overallResult.ContainsKey(area.Attribute("alias")!.Value))
                 {
-                    //there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files
-                    if (result.ContainsKey(key) == false)
-                        result.Add(key, area.Value[key]);
+                    overallResult.Add(area.Attribute("alias")!.Value, result);
                 }
-
-                overallResult.Add(area.Key, result);
             }
-
-            return overallResult;
-        }
-
-        /// 
-        /// Returns a list of all currently supported cultures
-        /// 
-        /// 
-        public IEnumerable GetSupportedCultures()
-        {
-            return _dictionarySource.Keys;
         }
 
-        /// 
-        /// Tries to resolve a full 4 letter culture from a 2 letter culture name
-        /// 
-        /// 
-        /// The culture to determine if it is only a 2 letter culture, if so we'll try to convert it, otherwise it will just be returned
-        /// 
-        /// 
-        /// 
-        /// TODO: This is just a hack due to the way we store the language files, they should be stored with 4 letters since that
-        /// is what they reference but they are stored with 2, further more our user's languages are stored with 2. So this attempts
-        /// to resolve the full culture if possible.
-        ///
-        /// This only works when this service is constructed with the LocalizedTextServiceFileSources
-        /// 
-        public CultureInfo ConvertToSupportedCultureWithRegionCode(CultureInfo currentCulture)
-        {
-            if (currentCulture == null) throw new ArgumentNullException("currentCulture");
-
-            if (_fileSources == null) return currentCulture;
-            if (currentCulture.Name.Length > 2) return currentCulture;
+        return overallResult;
+    }
 
-            var attempt = _fileSources.Value.TryConvert2LetterCultureTo4Letter(currentCulture.TwoLetterISOLanguageName);
-            return attempt.Success ? attempt.Result! : currentCulture;
-        }
+    private Dictionary GetNoAreaStoredTranslations(
+        IDictionary> xmlSource, CultureInfo cult)
+    {
+        var result = new Dictionary(StringComparer.InvariantCulture);
+        IEnumerable keys = xmlSource[cult].Value.XPathSelectElements("//key");
 
-        private string GetFromDictionarySource(CultureInfo culture, string? area, string key,
-            IDictionary? tokens)
+        foreach (XElement key in keys)
         {
-            if (_dictionarySource.ContainsKey(culture) == false)
+            var dictionaryKey =
+                (string)key.Attribute("alias")!;
+            //there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files
+            if (result.ContainsKey(dictionaryKey) == false)
             {
-                _logger.LogWarning(
-                    "The culture specified {Culture} was not found in any configured sources for this service",
-                    culture);
-                return "[" + key + "]";
+                result.Add(dictionaryKey, key.Value);
             }
+        }
 
+        //Merge English Dictionary
+        var englishCulture = new CultureInfo("en-US");
+        if (!cult.Equals(englishCulture))
+        {
+            IEnumerable keysEn = xmlSource[englishCulture].Value.XPathSelectElements("//key");
 
-            string? found = null;
-            if (string.IsNullOrWhiteSpace(area))
-            {
-                _noAreaDictionarySource[culture].Value.TryGetValue(key, out found);
-            }
-            else
+            foreach (XElement key in keys)
             {
-                if (_dictionarySource[culture].Value.TryGetValue(area, out var areaDictionary))
+                var dictionaryKey =
+                    (string)key.Attribute("alias")!;
+                //there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files
+                if (result.ContainsKey(dictionaryKey) == false)
                 {
-                    areaDictionary.TryGetValue(key, out found);
+                    result.Add(dictionaryKey, key.Value);
                 }
+            }
+        }
 
-                if (found == null)
+        return result;
+    }
+
+    private Dictionary> GetAreaStoredTranslations(
+        IDictionary>>> dictionarySource,
+        CultureInfo cult)
+    {
+        var overallResult = new Dictionary>(StringComparer.InvariantCulture);
+        Lazy>> areaDict = dictionarySource[cult];
+
+        foreach (KeyValuePair> area in areaDict.Value)
+        {
+            var result = new Dictionary(StringComparer.InvariantCulture);
+            ICollection keys = area.Value.Keys;
+            foreach (var key in keys)
+            {
+                //there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files
+                if (result.ContainsKey(key) == false)
                 {
-                    _noAreaDictionarySource[culture].Value.TryGetValue(key, out found);
+                    result.Add(key, area.Value[key]);
                 }
             }
 
+            overallResult.Add(area.Key, result);
+        }
 
-            if (found != null)
-            {
-                return ParseTokens(found, tokens);
-            }
+        return overallResult;
+    }
 
-            //NOTE: Based on how legacy works, the default text does not contain the area, just the key
+    private string GetFromDictionarySource(CultureInfo culture, string? area, string key,
+        IDictionary? tokens)
+    {
+        if (_dictionarySource.ContainsKey(culture) == false)
+        {
+            _logger.LogWarning(
+                "The culture specified {Culture} was not found in any configured sources for this service",
+                culture);
             return "[" + key + "]";
         }
 
-        /// 
-        /// Parses the tokens in the value
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// This is based on how the legacy ui localized text worked, each token was just a sequential value delimited with a % symbol.
-        /// For example: hello %0%, you are %1% !
-        ///
-        /// Since we're going to continue using the same language files for now, the token system needs to remain the same. With our new service
-        /// we support a dictionary which means in the future we can really have any sort of token system.
-        /// Currently though, the token key's will need to be an integer and sequential - though we aren't going to throw exceptions if that is not the case.
-        /// 
-        internal static string ParseTokens(string value, IDictionary? tokens)
+
+        string? found = null;
+        if (string.IsNullOrWhiteSpace(area))
+        {
+            _noAreaDictionarySource[culture].Value.TryGetValue(key, out found);
+        }
+        else
         {
-            if (tokens == null || tokens.Any() == false)
+            if (_dictionarySource[culture].Value.TryGetValue(area, out IDictionary areaDictionary))
             {
-                return value;
+                areaDictionary.TryGetValue(key, out found);
             }
 
-            foreach (var token in tokens)
+            if (found == null)
             {
-                value = value.Replace(string.Concat("%", token.Key, "%"), token.Value);
+                _noAreaDictionarySource[culture].Value.TryGetValue(key, out found);
             }
-
-            return value;
         }
 
-        /// 
-        /// Returns all key/values in storage for the given culture
-        /// 
-        /// 
-        public IDictionary> GetAllStoredValuesByAreaAndAlias(CultureInfo culture)
+
+        if (found != null)
         {
-            if (culture == null)
-            {
-                throw new ArgumentNullException("culture");
-            }
+            return ParseTokens(found, tokens);
+        }
 
-            // TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode
-            culture = ConvertToSupportedCultureWithRegionCode(culture);
+        //NOTE: Based on how legacy works, the default text does not contain the area, just the key
+        return "[" + key + "]";
+    }
 
-            if (_dictionarySource.ContainsKey(culture) == false)
-            {
-                _logger.LogWarning(
-                    "The culture specified {Culture} was not found in any configured sources for this service",
-                    culture);
-                return new Dictionary>(0);
-            }
+    /// 
+    ///     Parses the tokens in the value
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    ///     This is based on how the legacy ui localized text worked, each token was just a sequential value delimited with a %
+    ///     symbol.
+    ///     For example: hello %0%, you are %1% !
+    ///     Since we're going to continue using the same language files for now, the token system needs to remain the same.
+    ///     With our new service
+    ///     we support a dictionary which means in the future we can really have any sort of token system.
+    ///     Currently though, the token key's will need to be an integer and sequential - though we aren't going to throw
+    ///     exceptions if that is not the case.
+    /// 
+    internal static string ParseTokens(string value, IDictionary? tokens)
+    {
+        if (tokens == null || tokens.Any() == false)
+        {
+            return value;
+        }
 
-            return _dictionarySource[culture].Value;
+        foreach (KeyValuePair token in tokens)
+        {
+            value = value.Replace(string.Concat("%", token.Key, "%"), token.Value);
         }
+
+        return value;
     }
 }
diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs
index 8a9559f7bca8..e2a372515d41 100644
--- a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs
+++ b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs
@@ -1,91 +1,102 @@
 // Copyright (c) Umbraco.
 // See LICENSE for more details.
 
-using System;
-using System.Collections.Generic;
 using System.Globalization;
-using System.Linq;
-using System.Threading;
 using Umbraco.Cms.Core.Dictionary;
 using Umbraco.Cms.Core.Services;
 
-namespace Umbraco.Extensions
+namespace Umbraco.Extensions;
+
+/// 
+///     Extension methods for ILocalizedTextService
+/// 
+public static class LocalizedTextServiceExtensions
 {
+    public static string Localize(this ILocalizedTextService manager, string area, T key)
+        where T : Enum =>
+        manager.Localize(area, key.ToString(), Thread.CurrentThread.CurrentUICulture);
+
+    public static string Localize(this ILocalizedTextService manager, string? area, string? alias)
+        => manager.Localize(area, alias, Thread.CurrentThread.CurrentUICulture);
+
+    /// 
+    ///     Localize using the current thread culture
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    public static string Localize(this ILocalizedTextService manager, string? area, string alias, string?[]? tokens)
+        => manager.Localize(area, alias, Thread.CurrentThread.CurrentUICulture, ConvertToDictionaryVars(tokens));
+
     /// 
-    /// Extension methods for ILocalizedTextService
+    ///     Localize a key without any variables
     /// 
-    public static class LocalizedTextServiceExtensions
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    public static string Localize(this ILocalizedTextService manager, string area, string alias, CultureInfo culture,
+        string?[] tokens)
+        => manager.Localize(area, alias, culture, ConvertToDictionaryVars(tokens));
+
+    /// 
+    ///     Convert an array of strings to a dictionary of indices -> values
+    /// 
+    /// 
+    /// 
+    internal static IDictionary? ConvertToDictionaryVars(string?[]? variables)
     {
-         public static string Localize(this ILocalizedTextService manager, string area, T key)
-         where T: System.Enum =>
-             manager.Localize(area, key.ToString(), Thread.CurrentThread.CurrentUICulture);
-
-        public static string Localize(this ILocalizedTextService manager, string? area, string? alias)
-            => manager.Localize(area, alias, Thread.CurrentThread.CurrentUICulture);
-
-        /// 
-        /// Localize using the current thread culture
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        public static string Localize(this ILocalizedTextService manager, string? area, string alias, string?[]? tokens)
-                    => manager.Localize(area, alias, Thread.CurrentThread.CurrentUICulture, ConvertToDictionaryVars(tokens));
-
-        /// 
-        /// Localize a key without any variables
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        public static string Localize(this ILocalizedTextService manager, string area, string alias, CultureInfo culture, string?[] tokens)
-            => manager.Localize(area, alias, culture, ConvertToDictionaryVars(tokens));
-
-         /// 
-         /// Convert an array of strings to a dictionary of indices -> values
-         /// 
-         /// 
-         /// 
-         internal static IDictionary? ConvertToDictionaryVars(string?[]? variables)
-         {
-             if (variables == null) return null;
-             if (variables.Any() == false) return null;
-
-             return variables.Select((s, i) => new { index = i.ToString(CultureInfo.InvariantCulture), value = s })
-                 .ToDictionary(keyvals => keyvals.index, keyvals => keyvals.value);
-         }
-
-         public static string? UmbracoDictionaryTranslate(this ILocalizedTextService manager, ICultureDictionary cultureDictionary, string? text)
-         {
-             if (text == null)
-                 return null;
-
-             if (text.StartsWith("#") == false)
-                 return text;
-
-             text = text.Substring(1);
-             var value = cultureDictionary[text];
-             if (value.IsNullOrWhiteSpace() == false)
-             {
-                 return value;
-             }
-
-             if (text.IndexOf('_') == -1)
-                 return text;
-
-             var areaAndKey = text.Split('_');
-
-             if (areaAndKey.Length < 2)
-                return text;
-
-             value = manager.Localize(areaAndKey[0], areaAndKey[1]);
-             return value.StartsWith("[") ? text : value;
-         }
+        if (variables == null)
+        {
+            return null;
+        }
+
+        if (variables.Any() == false)
+        {
+            return null;
+        }
+
+        return variables.Select((s, i) => new {index = i.ToString(CultureInfo.InvariantCulture), value = s})
+            .ToDictionary(keyvals => keyvals.index, keyvals => keyvals.value);
+    }
+
+    public static string? UmbracoDictionaryTranslate(this ILocalizedTextService manager,
+        ICultureDictionary cultureDictionary, string? text)
+    {
+        if (text == null)
+        {
+            return null;
+        }
+
+        if (text.StartsWith("#") == false)
+        {
+            return text;
+        }
+
+        text = text.Substring(1);
+        var value = cultureDictionary[text];
+        if (value.IsNullOrWhiteSpace() == false)
+        {
+            return value;
+        }
+
+        if (text.IndexOf('_') == -1)
+        {
+            return text;
+        }
+
+        var areaAndKey = text.Split('_');
+
+        if (areaAndKey.Length < 2)
+        {
+            return text;
+        }
 
+        value = manager.Localize(areaAndKey[0], areaAndKey[1]);
+        return value.StartsWith("[") ? text : value;
     }
 }
diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs b/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs
index 41d12f9a4563..6de89f3d5a7b 100644
--- a/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs
+++ b/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs
@@ -1,8 +1,4 @@
-using System;
-using System.Collections.Generic;
 using System.Globalization;
-using System.IO;
-using System.Linq;
 using System.Xml;
 using System.Xml.Linq;
 using Microsoft.Extensions.FileProviders;
@@ -11,290 +7,325 @@
 using Umbraco.Cms.Core.Cache;
 using Umbraco.Extensions;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Exposes the XDocument sources from files for the default localization text service and ensure caching is taken care
+///     of
+/// 
+public class LocalizedTextServiceFileSources
 {
+    private readonly IAppPolicyCache _cache;
+    private readonly IDirectoryContents _directoryContents;
+    private readonly DirectoryInfo? _fileSourceFolder;
+    private readonly ILogger _logger;
+    private readonly IEnumerable? _supplementFileSources;
+
+    // TODO: See other notes in this class, this is purely a hack because we store 2 letter culture file names that contain 4 letter cultures :(
+    private readonly Dictionary _twoLetterCultureConverter = new();
+
+    private readonly Lazy>> _xmlSources;
+
+    [Obsolete("Use ctor with all params. This will be removed in Umbraco 12")]
+    public LocalizedTextServiceFileSources(
+        ILogger logger,
+        AppCaches appCaches,
+        DirectoryInfo fileSourceFolder,
+        IEnumerable supplementFileSources)
+        : this(
+            logger,
+            appCaches,
+            fileSourceFolder,
+            supplementFileSources, new NotFoundDirectoryContents())
+    {
+    }
+
     /// 
-    /// Exposes the XDocument sources from files for the default localization text service and ensure caching is taken care of
+    ///     This is used to configure the file sources with the main file sources shipped with Umbraco and also including
+    ///     supplemental/plugin based
+    ///     localization files. The supplemental files will be loaded in and merged in after the primary files.
+    ///     The supplemental files must be named with the 4 letter culture name with a hyphen such as : en-AU.xml
     /// 
-    public class LocalizedTextServiceFileSources
+    /// 
+    /// 
+    /// 
+    /// 
+    public LocalizedTextServiceFileSources(
+        ILogger logger,
+        AppCaches appCaches,
+        DirectoryInfo fileSourceFolder,
+        IEnumerable supplementFileSources,
+        IDirectoryContents directoryContents
+    )
     {
-        private readonly ILogger _logger;
-        private readonly IDirectoryContents _directoryContents;
-        private readonly IAppPolicyCache _cache;
-        private readonly IEnumerable? _supplementFileSources;
-        private readonly DirectoryInfo? _fileSourceFolder;
-
-        // TODO: See other notes in this class, this is purely a hack because we store 2 letter culture file names that contain 4 letter cultures :(
-        private readonly Dictionary _twoLetterCultureConverter = new Dictionary();
-
-        private readonly Lazy>> _xmlSources;
-
-        [Obsolete("Use ctor with all params. This will be removed in Umbraco 12")]
-        public LocalizedTextServiceFileSources(
-            ILogger logger,
-            AppCaches appCaches,
-            DirectoryInfo fileSourceFolder,
-            IEnumerable supplementFileSources)
-            :this(
-                logger,
-                appCaches,
-                fileSourceFolder,
-                supplementFileSources, new NotFoundDirectoryContents())
+        if (logger == null)
         {
+            throw new ArgumentNullException("logger");
+        }
 
+        if (appCaches == null)
+        {
+            throw new ArgumentNullException("cache");
         }
 
-        /// 
-        /// This is used to configure the file sources with the main file sources shipped with Umbraco and also including supplemental/plugin based
-        /// localization files. The supplemental files will be loaded in and merged in after the primary files.
-        /// The supplemental files must be named with the 4 letter culture name with a hyphen such as : en-AU.xml
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        public LocalizedTextServiceFileSources(
-            ILogger logger,
-            AppCaches appCaches,
-            DirectoryInfo fileSourceFolder,
-            IEnumerable supplementFileSources,
-            IDirectoryContents directoryContents
-            )
+        if (fileSourceFolder == null)
         {
-            if (logger == null) throw new ArgumentNullException("logger");
-            if (appCaches == null) throw new ArgumentNullException("cache");
-            if (fileSourceFolder == null) throw new ArgumentNullException("fileSourceFolder");
-
-            _logger = logger;
-            _directoryContents = directoryContents;
-            _cache = appCaches.RuntimeCache;
-            _fileSourceFolder = fileSourceFolder;
-            _supplementFileSources = supplementFileSources;
-
-            //Create the lazy source for the _xmlSources
-            _xmlSources = new Lazy>>(() =>
-            {
-                var result = new Dictionary>();
+            throw new ArgumentNullException("fileSourceFolder");
+        }
 
+        _logger = logger;
+        _directoryContents = directoryContents;
+        _cache = appCaches.RuntimeCache;
+        _fileSourceFolder = fileSourceFolder;
+        _supplementFileSources = supplementFileSources;
 
-                var files = GetLanguageFiles();
+        //Create the lazy source for the _xmlSources
+        _xmlSources = new Lazy>>(() =>
+        {
+            var result = new Dictionary>();
 
-                if (!files.Any())
-                {
-                    return result;
-                }
 
-                foreach (var fileInfo in files)
+            IEnumerable files = GetLanguageFiles();
+
+            if (!files.Any())
+            {
+                return result;
+            }
+
+            foreach (IFileInfo fileInfo in files)
+            {
+                IFileInfo localCopy = fileInfo;
+                var filename = Path.GetFileNameWithoutExtension(localCopy.Name).Replace("_", "-");
+
+                // TODO: Fix this nonsense... would have to wait until v8 to store the language files with their correct
+                // names instead of storing them as 2 letters but actually having a 4 letter culture. So now, we
+                // need to check if the file is 2 letters, then open it to try to find it's 4 letter culture, then use that
+                // if it's successful. We're going to assume (though it seems assuming in the legacy logic is never a great idea)
+                // that any 4 letter file is named with the actual culture that it is!
+                CultureInfo? culture = null;
+                if (filename.Length == 2)
                 {
-                    var localCopy = fileInfo;
-                    var filename = Path.GetFileNameWithoutExtension(localCopy.Name).Replace("_", "-");
-
-                    // TODO: Fix this nonsense... would have to wait until v8 to store the language files with their correct
-                    // names instead of storing them as 2 letters but actually having a 4 letter culture. So now, we
-                    // need to check if the file is 2 letters, then open it to try to find it's 4 letter culture, then use that
-                    // if it's successful. We're going to assume (though it seems assuming in the legacy logic is never a great idea)
-                    // that any 4 letter file is named with the actual culture that it is!
-                    CultureInfo? culture = null;
-                    if (filename.Length == 2)
+                    //we need to open the file to see if we can read it's 'real' culture, we'll use XmlReader since we don't
+                    //want to load in the entire doc into mem just to read a single value
+                    using (Stream fs = fileInfo.CreateReadStream())
+                    using (var reader = XmlReader.Create(fs))
                     {
-                        //we need to open the file to see if we can read it's 'real' culture, we'll use XmlReader since we don't
-                        //want to load in the entire doc into mem just to read a single value
-                        using (var fs = fileInfo.CreateReadStream())
-                        using (var reader = XmlReader.Create(fs))
+                        if (reader.IsStartElement())
                         {
-                            if (reader.IsStartElement())
+                            if (reader.Name == "language")
                             {
-                                if (reader.Name == "language")
+                                if (reader.MoveToAttribute("culture"))
                                 {
-                                    if (reader.MoveToAttribute("culture"))
+                                    var cultureVal = reader.Value;
+                                    try
                                     {
-                                        var cultureVal = reader.Value;
-                                        try
-                                        {
-                                            culture = CultureInfo.GetCultureInfo(cultureVal);
-                                            //add to the tracked dictionary
-                                            _twoLetterCultureConverter[filename] = culture;
-                                        }
-                                        catch (CultureNotFoundException)
-                                        {
-                                            _logger.LogWarning("The culture {CultureValue} found in the file {CultureFile} is not a valid culture", cultureVal, fileInfo.Name);
-                                            //If the culture in the file is invalid, we'll just hope the file name is a valid culture below, otherwise
-                                            // an exception will be thrown.
-                                        }
+                                        culture = CultureInfo.GetCultureInfo(cultureVal);
+                                        //add to the tracked dictionary
+                                        _twoLetterCultureConverter[filename] = culture;
+                                    }
+                                    catch (CultureNotFoundException)
+                                    {
+                                        _logger.LogWarning(
+                                            "The culture {CultureValue} found in the file {CultureFile} is not a valid culture",
+                                            cultureVal, fileInfo.Name);
+                                        //If the culture in the file is invalid, we'll just hope the file name is a valid culture below, otherwise
+                                        // an exception will be thrown.
                                     }
                                 }
                             }
                         }
                     }
-                    if (culture == null)
+                }
+
+                if (culture == null)
+                {
+                    culture = CultureInfo.GetCultureInfo(filename);
+                }
+
+                //get the lazy value from cache
+                result[culture] = new Lazy(() => _cache.GetCacheItem(
+                    string.Format("{0}-{1}", typeof(LocalizedTextServiceFileSources).Name, culture.Name), () =>
                     {
-                        culture = CultureInfo.GetCultureInfo(filename);
-                    }
+                        XDocument xdoc;
 
-                    //get the lazy value from cache
-                    result[culture] = new Lazy(() => _cache.GetCacheItem(
-                        string.Format("{0}-{1}", typeof(LocalizedTextServiceFileSources).Name, culture.Name), () =>
+                        //load in primary
+                        using (Stream fs = localCopy.CreateReadStream())
                         {
-                            XDocument xdoc;
+                            xdoc = XDocument.Load(fs);
+                        }
 
-                            //load in primary
-                            using (var fs = localCopy.CreateReadStream())
-                            {
-                                xdoc = XDocument.Load(fs);
-                            }
+                        //load in supplementary
+                        MergeSupplementaryFiles(culture, xdoc);
 
-                            //load in supplementary
-                            MergeSupplementaryFiles(culture, xdoc);
+                        return xdoc;
+                    }, isSliding: true, timeout: TimeSpan.FromMinutes(10))!);
+            }
 
-                            return xdoc;
-                        }, isSliding: true, timeout: TimeSpan.FromMinutes(10))!);
-                }
-                return result;
-            });
+            return result;
+        });
+    }
 
+    /// 
+    ///     Constructor
+    /// 
+    public LocalizedTextServiceFileSources(ILogger logger, AppCaches appCaches,
+        DirectoryInfo fileSourceFolder)
+        : this(logger, appCaches, fileSourceFolder, Enumerable.Empty())
+    {
+    }
 
-        }
+    private IEnumerable GetLanguageFiles()
+    {
+        var result = new List();
 
-        private IEnumerable GetLanguageFiles()
+        if (_fileSourceFolder is not null && _fileSourceFolder.Exists)
         {
-            var result = new List();
-
-            if (_fileSourceFolder is not null && _fileSourceFolder.Exists)
-            {
-
-                result.AddRange(
-                    new PhysicalDirectoryContents(_fileSourceFolder.FullName)
+            result.AddRange(
+                new PhysicalDirectoryContents(_fileSourceFolder.FullName)
                     .Where(x => !x.IsDirectory && x.Name.EndsWith(".xml"))
-                );
-            }
+            );
+        }
 
-            if (_directoryContents.Exists)
-            {
-                result.AddRange(
+        if (_directoryContents.Exists)
+        {
+            result.AddRange(
                 _directoryContents
-                        .Where(x => !x.IsDirectory && x.Name.EndsWith(".xml"))
-                );
-            }
-
-            return result;
+                    .Where(x => !x.IsDirectory && x.Name.EndsWith(".xml"))
+            );
         }
 
-        /// 
-        /// Constructor
-        /// 
-        public LocalizedTextServiceFileSources(ILogger logger, AppCaches appCaches, DirectoryInfo fileSourceFolder)
-            : this(logger, appCaches, fileSourceFolder, Enumerable.Empty())
-        { }
-
-        /// 
-        /// returns all xml sources for all culture files found in the folder
-        /// 
-        /// 
-        public IDictionary> GetXmlSources()
+        return result;
+    }
+
+    /// 
+    ///     returns all xml sources for all culture files found in the folder
+    /// 
+    /// 
+    public IDictionary> GetXmlSources() => _xmlSources.Value;
+
+    // TODO: See other notes in this class, this is purely a hack because we store 2 letter culture file names that contain 4 letter cultures :(
+    public Attempt TryConvert2LetterCultureTo4Letter(string twoLetterCulture)
+    {
+        if (twoLetterCulture.Length != 2)
         {
-            return _xmlSources.Value;
+            return Attempt.Fail();
         }
 
-        // TODO: See other notes in this class, this is purely a hack because we store 2 letter culture file names that contain 4 letter cultures :(
-        public Attempt TryConvert2LetterCultureTo4Letter(string twoLetterCulture)
-        {
-            if (twoLetterCulture.Length != 2) return Attempt.Fail();
+        //This needs to be resolved before continuing so that the _twoLetterCultureConverter cache is initialized
+        Dictionary> resolved = _xmlSources.Value;
 
-            //This needs to be resolved before continuing so that the _twoLetterCultureConverter cache is initialized
-            var resolved = _xmlSources.Value;
+        return _twoLetterCultureConverter.ContainsKey(twoLetterCulture)
+            ? Attempt.Succeed(_twoLetterCultureConverter[twoLetterCulture])
+            : Attempt.Fail();
+    }
 
-            return _twoLetterCultureConverter.ContainsKey(twoLetterCulture)
-                ? Attempt.Succeed(_twoLetterCultureConverter[twoLetterCulture])
-                : Attempt.Fail();
+    // TODO: See other notes in this class, this is purely a hack because we store 2 letter culture file names that contain 4 letter cultures :(
+    public Attempt TryConvert4LetterCultureTo2Letter(CultureInfo culture)
+    {
+        if (culture == null)
+        {
+            throw new ArgumentNullException("culture");
         }
 
-        // TODO: See other notes in this class, this is purely a hack because we store 2 letter culture file names that contain 4 letter cultures :(
-        public Attempt TryConvert4LetterCultureTo2Letter(CultureInfo culture)
-        {
-            if (culture == null) throw new ArgumentNullException("culture");
+        //This needs to be resolved before continuing so that the _twoLetterCultureConverter cache is initialized
+        Dictionary> resolved = _xmlSources.Value;
 
-            //This needs to be resolved before continuing so that the _twoLetterCultureConverter cache is initialized
-            var resolved = _xmlSources.Value;
+        return _twoLetterCultureConverter.Values.Contains(culture)
+            ? Attempt.Succeed(culture.Name.Substring(0, 2))
+            : Attempt.Fail();
+    }
 
-            return _twoLetterCultureConverter.Values.Contains(culture)
-                ? Attempt.Succeed(culture.Name.Substring(0, 2))
-                : Attempt.Fail();
+    private void MergeSupplementaryFiles(CultureInfo culture, XDocument xMasterDoc)
+    {
+        if (xMasterDoc.Root == null)
+        {
+            return;
         }
 
-        private void MergeSupplementaryFiles(CultureInfo culture, XDocument xMasterDoc)
+        if (_supplementFileSources != null)
         {
-            if (xMasterDoc.Root == null) return;
-            if (_supplementFileSources != null)
+            //now load in supplementary
+            IEnumerable found = _supplementFileSources.Where(x =>
             {
-                //now load in supplementary
-                var found = _supplementFileSources.Where(x =>
-                {
-                    var extension = Path.GetExtension(x.File.FullName);
-                    var fileCultureName = Path.GetFileNameWithoutExtension(x.File.FullName).Replace("_", "-").Replace(".user", "");
-                    return extension.InvariantEquals(".xml") && (
-                        fileCultureName.InvariantEquals(culture.Name)
-                        || fileCultureName.InvariantEquals(culture.TwoLetterISOLanguageName)
-                    );
-                });
-
-                foreach (var supplementaryFile in found)
+                var extension = Path.GetExtension(x.File.FullName);
+                var fileCultureName = Path.GetFileNameWithoutExtension(x.File.FullName).Replace("_", "-")
+                    .Replace(".user", "");
+                return extension.InvariantEquals(".xml") && (
+                    fileCultureName.InvariantEquals(culture.Name)
+                    || fileCultureName.InvariantEquals(culture.TwoLetterISOLanguageName)
+                );
+            });
+
+            foreach (LocalizedTextServiceSupplementaryFileSource supplementaryFile in found)
+            {
+                using (FileStream fs = supplementaryFile.File.OpenRead())
                 {
-                    using (var fs = supplementaryFile.File.OpenRead())
+                    XDocument xChildDoc;
+                    try
                     {
-                        XDocument xChildDoc;
-                        try
-                        {
-                            xChildDoc = XDocument.Load(fs);
-                        }
-                        catch (Exception ex)
+                        xChildDoc = XDocument.Load(fs);
+                    }
+                    catch (Exception ex)
+                    {
+                        _logger.LogError(ex, "Could not load file into XML {File}", supplementaryFile.File.FullName);
+                        continue;
+                    }
+
+                    if (xChildDoc.Root == null || xChildDoc.Root.Name != "language")
+                    {
+                        continue;
+                    }
+
+                    foreach (XElement xArea in xChildDoc.Root.Elements("area")
+                                 .Where(x => ((string)x.Attribute("alias")!).IsNullOrWhiteSpace() == false))
+                    {
+                        var areaAlias = (string)xArea.Attribute("alias")!;
+
+                        XElement areaFound = xMasterDoc.Root.Elements("area")
+                            .FirstOrDefault(x => (string)x.Attribute("alias")! == areaAlias);
+                        if (areaFound == null)
                         {
-                            _logger.LogError(ex, "Could not load file into XML {File}", supplementaryFile.File.FullName);
-                            continue;
+                            //add the whole thing
+                            xMasterDoc.Root.Add(xArea);
                         }
-
-                        if (xChildDoc.Root == null || xChildDoc.Root.Name != "language") continue;
-                        foreach (var xArea in xChildDoc.Root.Elements("area")
-                            .Where(x => ((string)x.Attribute("alias")!).IsNullOrWhiteSpace() == false))
+                        else
                         {
-                            var areaAlias = (string)xArea.Attribute("alias")!;
-
-                            var areaFound = xMasterDoc.Root.Elements("area").FirstOrDefault(x => ((string)x.Attribute("alias")!) == areaAlias);
-                            if (areaFound == null)
-                            {
-                                //add the whole thing
-                                xMasterDoc.Root.Add(xArea);
-                            }
-                            else
-                            {
-                                MergeChildKeys(xArea, areaFound, supplementaryFile.OverwriteCoreKeys);
-                            }
+                            MergeChildKeys(xArea, areaFound, supplementaryFile.OverwriteCoreKeys);
                         }
                     }
                 }
             }
         }
+    }
 
-        private void MergeChildKeys(XElement source, XElement destination, bool overwrite)
+    private void MergeChildKeys(XElement source, XElement destination, bool overwrite)
+    {
+        if (destination == null)
+        {
+            throw new ArgumentNullException("destination");
+        }
+
+        if (source == null)
         {
-            if (destination == null) throw new ArgumentNullException("destination");
-            if (source == null) throw new ArgumentNullException("source");
+            throw new ArgumentNullException("source");
+        }
 
-            //merge in the child elements
-            foreach (var key in source.Elements("key")
-                .Where(x => ((string)x.Attribute("alias")!).IsNullOrWhiteSpace() == false))
+        //merge in the child elements
+        foreach (XElement key in source.Elements("key")
+                     .Where(x => ((string)x.Attribute("alias")!).IsNullOrWhiteSpace() == false))
+        {
+            var keyAlias = (string)key.Attribute("alias")!;
+            XElement keyFound = destination.Elements("key")
+                .FirstOrDefault(x => (string)x.Attribute("alias")! == keyAlias);
+            if (keyFound == null)
             {
-                var keyAlias = (string)key.Attribute("alias")!;
-                var keyFound = destination.Elements("key").FirstOrDefault(x => ((string)x.Attribute("alias")!) == keyAlias);
-                if (keyFound == null)
-                {
-                    //append, it doesn't exist
-                    destination.Add(key);
-                }
-                else if (overwrite)
-                {
-                    //overwrite
-                    keyFound.Value = key.Value;
-                }
+                //append, it doesn't exist
+                destination.Add(key);
+            }
+            else if (overwrite)
+            {
+                //overwrite
+                keyFound.Value = key.Value;
             }
         }
     }
diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceSupplementaryFileSource.cs b/src/Umbraco.Core/Services/LocalizedTextServiceSupplementaryFileSource.cs
index 7fe5e0e48a99..dff1cffbf5eb 100644
--- a/src/Umbraco.Core/Services/LocalizedTextServiceSupplementaryFileSource.cs
+++ b/src/Umbraco.Core/Services/LocalizedTextServiceSupplementaryFileSource.cs
@@ -1,20 +1,18 @@
-using System;
-using System.IO;
+namespace Umbraco.Cms.Core.Services;
 
-namespace Umbraco.Cms.Core.Services
+public class LocalizedTextServiceSupplementaryFileSource
 {
-    public class LocalizedTextServiceSupplementaryFileSource
+    public LocalizedTextServiceSupplementaryFileSource(FileInfo file, bool overwriteCoreKeys)
     {
-
-        public LocalizedTextServiceSupplementaryFileSource(FileInfo file, bool overwriteCoreKeys)
+        if (file == null)
         {
-            if (file == null) throw new ArgumentNullException("file");
-
-            File = file;
-            OverwriteCoreKeys = overwriteCoreKeys;
+            throw new ArgumentNullException("file");
         }
 
-        public FileInfo File { get; private set; }
-        public bool OverwriteCoreKeys { get; private set; }
+        File = file;
+        OverwriteCoreKeys = overwriteCoreKeys;
     }
+
+    public FileInfo File { get; }
+    public bool OverwriteCoreKeys { get; }
 }
diff --git a/src/Umbraco.Core/Services/MacroService.cs b/src/Umbraco.Core/Services/MacroService.cs
index 6b598921e1b8..0b1c96d4d599 100644
--- a/src/Umbraco.Core/Services/MacroService.cs
+++ b/src/Umbraco.Core/Services/MacroService.cs
@@ -1,179 +1,173 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging;
 using Umbraco.Cms.Core.Events;
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Notifications;
 using Umbraco.Cms.Core.Persistence.Repositories;
 using Umbraco.Cms.Core.Scoping;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Represents the Macro Service, which is an easy access to operations involving 
+/// 
+internal class MacroService : RepositoryService, IMacroWithAliasService
 {
+    private readonly IAuditRepository _auditRepository;
+    private readonly IMacroRepository _macroRepository;
+
+    public MacroService(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
+        IEventMessagesFactory eventMessagesFactory, IMacroRepository macroRepository, IAuditRepository auditRepository)
+        : base(provider, loggerFactory, eventMessagesFactory)
+    {
+        _macroRepository = macroRepository;
+        _auditRepository = auditRepository;
+    }
+
     /// 
-    /// Represents the Macro Service, which is an easy access to operations involving 
+    ///     Gets an  object by its alias
     /// 
-    internal class MacroService : RepositoryService, IMacroWithAliasService
+    /// Alias to retrieve an  for
+    /// An  object
+    public IMacro? GetByAlias(string alias)
     {
-        private readonly IMacroRepository _macroRepository;
-        private readonly IAuditRepository _auditRepository;
-
-        public MacroService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IMacroRepository macroRepository, IAuditRepository auditRepository)
-            : base(provider, loggerFactory, eventMessagesFactory)
+        if (_macroRepository is not IMacroWithAliasRepository macroWithAliasRepository)
         {
-            _macroRepository = macroRepository;
-            _auditRepository = auditRepository;
+            return GetAll().FirstOrDefault(x => x.Alias == alias);
         }
 
-        /// 
-        /// Gets an  object by its alias
-        /// 
-        /// Alias to retrieve an  for
-        /// An  object
-        public IMacro? GetByAlias(string alias)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            if (_macroRepository is not IMacroWithAliasRepository macroWithAliasRepository)
-            {
-                return GetAll().FirstOrDefault(x => x.Alias == alias);
-            }
-
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return macroWithAliasRepository.GetByAlias(alias);
-            }
+            return macroWithAliasRepository.GetByAlias(alias);
         }
+    }
 
-        public IEnumerable GetAll()
+    public IEnumerable GetAll() => GetAll(new int[0]);
+
+    public IEnumerable GetAll(params int[] ids)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            return GetAll(new int[0]);
+            return _macroRepository.GetMany(ids);
         }
+    }
 
-        public IEnumerable GetAll(params int[] ids)
+    public IEnumerable GetAll(params Guid[] ids)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _macroRepository.GetMany(ids);
-            }
+            return _macroRepository.GetMany(ids);
         }
+    }
 
-        public IEnumerable GetAll(params Guid[] ids)
+    public IEnumerable GetAll(params string[] aliases)
+    {
+        if (_macroRepository is not IMacroWithAliasRepository macroWithAliasRepository)
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _macroRepository.GetMany(ids);
-            }
+            var hashset = new HashSet(aliases);
+            return GetAll().Where(x => hashset.Contains(x.Alias));
         }
 
-        public IEnumerable GetAll(params string[] aliases)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            if (_macroRepository is not IMacroWithAliasRepository macroWithAliasRepository)
-            {
-                var hashset = new HashSet(aliases);
-                return GetAll().Where(x => hashset.Contains(x.Alias));
-            }
-
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return macroWithAliasRepository.GetAllByAlias(aliases) ?? Enumerable.Empty();
-            }
+            return macroWithAliasRepository.GetAllByAlias(aliases) ?? Enumerable.Empty();
         }
+    }
 
-        public IMacro? GetById(int id)
+    public IMacro? GetById(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _macroRepository.Get(id);
-            }
+            return _macroRepository.Get(id);
         }
+    }
 
-        public IMacro? GetById(Guid id)
+    public IMacro? GetById(Guid id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _macroRepository.Get(id);
-            }
+            return _macroRepository.Get(id);
         }
+    }
 
-        /// 
-        /// Deletes an 
-        /// 
-        ///  to delete
-        /// Optional id of the user deleting the macro
-        public void Delete(IMacro macro, int userId = Cms.Core.Constants.Security.SuperUserId)
+    /// 
+    ///     Deletes an 
+    /// 
+    ///  to delete
+    /// Optional id of the user deleting the macro
+    public void Delete(IMacro macro, int userId = Constants.Security.SuperUserId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            EventMessages eventMessages = EventMessagesFactory.Get();
+            var deletingNotification = new MacroDeletingNotification(macro, eventMessages);
+            if (scope.Notifications.PublishCancelable(deletingNotification))
             {
-                EventMessages eventMessages = EventMessagesFactory.Get();
-                var deletingNotification = new MacroDeletingNotification(macro, eventMessages);
-                if (scope.Notifications.PublishCancelable(deletingNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
+                scope.Complete();
+                return;
+            }
 
-                _macroRepository.Delete(macro);
+            _macroRepository.Delete(macro);
 
-                scope.Notifications.Publish(new MacroDeletedNotification(macro, eventMessages).WithStateFrom(deletingNotification));
-                Audit(AuditType.Delete, userId, -1);
+            scope.Notifications.Publish(
+                new MacroDeletedNotification(macro, eventMessages).WithStateFrom(deletingNotification));
+            Audit(AuditType.Delete, userId, -1);
 
-                scope.Complete();
-            }
+            scope.Complete();
         }
+    }
 
-        /// 
-        /// Saves an 
-        /// 
-        ///  to save
-        /// Optional Id of the user deleting the macro
-        public void Save(IMacro macro, int userId = Cms.Core.Constants.Security.SuperUserId)
+    /// 
+    ///     Saves an 
+    /// 
+    ///  to save
+    /// Optional Id of the user deleting the macro
+    public void Save(IMacro macro, int userId = Constants.Security.SuperUserId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                EventMessages eventMessages = EventMessagesFactory.Get();
-                var savingNotification = new MacroSavingNotification(macro, eventMessages);
-
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
+            EventMessages eventMessages = EventMessagesFactory.Get();
+            var savingNotification = new MacroSavingNotification(macro, eventMessages);
 
-                if (string.IsNullOrWhiteSpace(macro.Name))
-                {
-                    throw new ArgumentException("Cannot save macro with empty name.");
-                }
+            if (scope.Notifications.PublishCancelable(savingNotification))
+            {
+                scope.Complete();
+                return;
+            }
 
-                _macroRepository.Save(macro);
+            if (string.IsNullOrWhiteSpace(macro.Name))
+            {
+                throw new ArgumentException("Cannot save macro with empty name.");
+            }
 
-                scope.Notifications.Publish(new MacroSavedNotification(macro, eventMessages).WithStateFrom(savingNotification));
-                Audit(AuditType.Save, userId, -1);
+            _macroRepository.Save(macro);
 
-                scope.Complete();
-            }
-        }
+            scope.Notifications.Publish(
+                new MacroSavedNotification(macro, eventMessages).WithStateFrom(savingNotification));
+            Audit(AuditType.Save, userId, -1);
 
-        ///// 
-        ///// Gets a list all available  plugins
-        ///// 
-        ///// An enumerable list of  objects
-        //public IEnumerable GetMacroPropertyTypes()
-        //{
-        //    return MacroPropertyTypeResolver.Current.MacroPropertyTypes;
-        //}
-
-        ///// 
-        ///// Gets an  by its alias
-        ///// 
-        ///// Alias to retrieve an  for
-        ///// An  object
-        //public IMacroPropertyType GetMacroPropertyTypeByAlias(string alias)
-        //{
-        //    return MacroPropertyTypeResolver.Current.MacroPropertyTypes.FirstOrDefault(x => x.Alias == alias);
-        //}
-
-        private void Audit(AuditType type, int userId, int objectId)
-        {
-            _auditRepository.Save(new AuditItem(objectId, type, userId, "Macro"));
+            scope.Complete();
         }
     }
+
+    ///// 
+    ///// Gets a list all available  plugins
+    ///// 
+    ///// An enumerable list of  objects
+    //public IEnumerable GetMacroPropertyTypes()
+    //{
+    //    return MacroPropertyTypeResolver.Current.MacroPropertyTypes;
+    //}
+
+    ///// 
+    ///// Gets an  by its alias
+    ///// 
+    ///// Alias to retrieve an  for
+    ///// An  object
+    //public IMacroPropertyType GetMacroPropertyTypeByAlias(string alias)
+    //{
+    //    return MacroPropertyTypeResolver.Current.MacroPropertyTypes.FirstOrDefault(x => x.Alias == alias);
+    //}
+
+    private void Audit(AuditType type, int userId, int objectId) =>
+        _auditRepository.Save(new AuditItem(objectId, type, userId, "Macro"));
 }
diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs
index 446dae4f396e..b3d34de4727d 100644
--- a/src/Umbraco.Core/Services/MediaService.cs
+++ b/src/Umbraco.Core/Services/MediaService.cs
@@ -1,12 +1,9 @@
-using System;
-using System.Collections.Generic;
 using System.Globalization;
-using System.IO;
-using System.Linq;
 using Microsoft.Extensions.Logging;
 using Umbraco.Cms.Core.Events;
 using Umbraco.Cms.Core.IO;
 using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models.Entities;
 using Umbraco.Cms.Core.Notifications;
 using Umbraco.Cms.Core.Persistence;
 using Umbraco.Cms.Core.Persistence.Querying;
@@ -16,1310 +13,1478 @@
 using Umbraco.Cms.Core.Strings;
 using Umbraco.Extensions;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Represents the Media Service, which is an easy access to operations involving 
+/// 
+public class MediaService : RepositoryService, IMediaService
 {
-    /// 
-    /// Represents the Media Service, which is an easy access to operations involving 
-    /// 
-    public class MediaService : RepositoryService, IMediaService
+    private readonly IAuditRepository _auditRepository;
+    private readonly IEntityRepository _entityRepository;
+
+    private readonly MediaFileManager _mediaFileManager;
+    private readonly IMediaRepository _mediaRepository;
+    private readonly IMediaTypeRepository _mediaTypeRepository;
+    private readonly IShortStringHelper _shortStringHelper;
+
+    #region Constructors
+
+    public MediaService(ICoreScopeProvider provider, MediaFileManager mediaFileManager, ILoggerFactory loggerFactory,
+        IEventMessagesFactory eventMessagesFactory,
+        IMediaRepository mediaRepository, IAuditRepository auditRepository, IMediaTypeRepository mediaTypeRepository,
+        IEntityRepository entityRepository, IShortStringHelper shortStringHelper)
+        : base(provider, loggerFactory, eventMessagesFactory)
     {
-        private readonly IMediaRepository _mediaRepository;
-        private readonly IMediaTypeRepository _mediaTypeRepository;
-        private readonly IAuditRepository _auditRepository;
-        private readonly IEntityRepository _entityRepository;
-        private readonly IShortStringHelper _shortStringHelper;
+        _mediaFileManager = mediaFileManager;
+        _mediaRepository = mediaRepository;
+        _auditRepository = auditRepository;
+        _mediaTypeRepository = mediaTypeRepository;
+        _entityRepository = entityRepository;
+        _shortStringHelper = shortStringHelper;
+    }
 
-        private readonly MediaFileManager _mediaFileManager;
+    #endregion
 
-        #region Constructors
+    #region Private Methods
 
-        public MediaService(ICoreScopeProvider provider, MediaFileManager mediaFileManager, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory,
-            IMediaRepository mediaRepository, IAuditRepository auditRepository, IMediaTypeRepository mediaTypeRepository,
-            IEntityRepository entityRepository, IShortStringHelper shortStringHelper)
-            : base(provider, loggerFactory, eventMessagesFactory)
-        {
-            _mediaFileManager = mediaFileManager;
-            _mediaRepository = mediaRepository;
-            _auditRepository = auditRepository;
-            _mediaTypeRepository = mediaTypeRepository;
-            _entityRepository = entityRepository;
-            _shortStringHelper = shortStringHelper;
-        }
+    private void Audit(AuditType type, int userId, int objectId, string? message = null) =>
+        _auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.Media.GetName(), message));
 
-        #endregion
+    #endregion
 
-        #region Count
+    #region Count
 
-        public int Count(string? mediaTypeAlias = null)
+    public int Count(string? mediaTypeAlias = null)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Cms.Core.Constants.Locks.MediaTree);
-                return _mediaRepository.Count(mediaTypeAlias);
-            }
+            scope.ReadLock(Constants.Locks.MediaTree);
+            return _mediaRepository.Count(mediaTypeAlias);
         }
+    }
 
-        public int CountNotTrashed(string? mediaTypeAlias = null)
+    public int CountNotTrashed(string? mediaTypeAlias = null)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Cms.Core.Constants.Locks.MediaTree);
+            scope.ReadLock(Constants.Locks.MediaTree);
 
-                var mediaTypeId = 0;
-                if (string.IsNullOrWhiteSpace(mediaTypeAlias) == false)
+            var mediaTypeId = 0;
+            if (string.IsNullOrWhiteSpace(mediaTypeAlias) == false)
+            {
+                IMediaType mediaType = _mediaTypeRepository.Get(mediaTypeAlias);
+                if (mediaType == null)
                 {
-                    var mediaType = _mediaTypeRepository.Get(mediaTypeAlias);
-                    if (mediaType == null) return 0;
-                    mediaTypeId = mediaType.Id;
+                    return 0;
                 }
 
-                var query = Query().Where(x => x.Trashed == false);
-                if (mediaTypeId > 0)
-                    query = query.Where(x => x.ContentTypeId == mediaTypeId);
-                return _mediaRepository.Count(query);
+                mediaTypeId = mediaType.Id;
             }
-        }
 
-        public int CountChildren(int parentId, string? mediaTypeAlias = null)
-        {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+            IQuery query = Query().Where(x => x.Trashed == false);
+            if (mediaTypeId > 0)
             {
-                scope.ReadLock(Cms.Core.Constants.Locks.MediaTree);
-                return _mediaRepository.CountChildren(parentId, mediaTypeAlias);
+                query = query.Where(x => x.ContentTypeId == mediaTypeId);
             }
+
+            return _mediaRepository.Count(query);
         }
+    }
 
-        public int CountDescendants(int parentId, string? mediaTypeAlias = null)
+    public int CountChildren(int parentId, string? mediaTypeAlias = null)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Cms.Core.Constants.Locks.MediaTree);
-                return _mediaRepository.CountDescendants(parentId, mediaTypeAlias);
-            }
+            scope.ReadLock(Constants.Locks.MediaTree);
+            return _mediaRepository.CountChildren(parentId, mediaTypeAlias);
         }
+    }
 
-        #endregion
-
-        #region Create
-
-        /// 
-        /// Creates an  object using the alias of the 
-        /// that this Media should based on.
-        /// 
-        /// 
-        /// Note that using this method will simply return a new IMedia without any identity
-        /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects
-        /// that does not invoke a save operation against the database.
-        /// 
-        /// Name of the Media object
-        /// Id of Parent for the new Media item
-        /// Alias of the 
-        /// Optional id of the user creating the media item
-        /// 
-        public IMedia CreateMedia(string name, Guid parentId, string mediaTypeAlias, int userId = Cms.Core.Constants.Security.SuperUserId)
-        {
-            var parent = GetById(parentId);
-            return CreateMedia(name, parent, mediaTypeAlias, userId);
-        }
-
-        /// 
-        /// Creates an  object of a specified media type.
-        /// 
-        /// This method simply returns a new, non-persisted, IMedia without any identity. It
-        /// is intended as a shortcut to creating new media objects that does not invoke a save
-        /// operation against the database.
-        /// 
-        /// The name of the media object.
-        /// The identifier of the parent, or -1.
-        /// The alias of the media type.
-        /// The optional id of the user creating the media.
-        /// The media object.
-        public IMedia CreateMedia(string? name, int parentId, string mediaTypeAlias, int userId = Cms.Core.Constants.Security.SuperUserId)
-        {
-            var mediaType = GetMediaType(mediaTypeAlias);
-            if (mediaType == null)
-                throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias));
-            var parent = parentId > 0 ? GetById(parentId) : null;
-            if (parentId > 0 && parent == null)
-                throw new ArgumentException("No media with that id.", nameof(parentId));
-            if (name != null && name.Length > 255)
-            {
-                throw new InvalidOperationException("Name cannot be more than 255 characters in length."); throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
-            }
+    public int CountDescendants(int parentId, string? mediaTypeAlias = null)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            scope.ReadLock(Constants.Locks.MediaTree);
+            return _mediaRepository.CountDescendants(parentId, mediaTypeAlias);
+        }
+    }
 
-            var media = new Core.Models.Media(name, parentId, mediaType);
-            using (var scope = ScopeProvider.CreateCoreScope())
-            {
-                CreateMedia(scope, media, parent!, userId, false);
-                scope.Complete();
-            }
+    #endregion
 
-            return media;
+    #region Create
+
+    /// 
+    ///     Creates an  object using the alias of the 
+    ///     that this Media should based on.
+    /// 
+    /// 
+    ///     Note that using this method will simply return a new IMedia without any identity
+    ///     as it has not yet been persisted. It is intended as a shortcut to creating new media objects
+    ///     that does not invoke a save operation against the database.
+    /// 
+    /// Name of the Media object
+    /// Id of Parent for the new Media item
+    /// Alias of the 
+    /// Optional id of the user creating the media item
+    /// 
+    ///     
+    /// 
+    public IMedia CreateMedia(string name, Guid parentId, string mediaTypeAlias,
+        int userId = Constants.Security.SuperUserId)
+    {
+        IMedia parent = GetById(parentId);
+        return CreateMedia(name, parent, mediaTypeAlias, userId);
+    }
+
+    /// 
+    ///     Creates an  object of a specified media type.
+    /// 
+    /// 
+    ///     This method simply returns a new, non-persisted, IMedia without any identity. It
+    ///     is intended as a shortcut to creating new media objects that does not invoke a save
+    ///     operation against the database.
+    /// 
+    /// The name of the media object.
+    /// The identifier of the parent, or -1.
+    /// The alias of the media type.
+    /// The optional id of the user creating the media.
+    /// The media object.
+    public IMedia CreateMedia(string? name, int parentId, string mediaTypeAlias,
+        int userId = Constants.Security.SuperUserId)
+    {
+        IMediaType mediaType = GetMediaType(mediaTypeAlias);
+        if (mediaType == null)
+        {
+            throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias));
+        }
+
+        IMedia parent = parentId > 0 ? GetById(parentId) : null;
+        if (parentId > 0 && parent == null)
+        {
+            throw new ArgumentException("No media with that id.", nameof(parentId));
+        }
+
+        if (name != null && name.Length > 255)
+        {
+            throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+            throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+        }
+
+        var media = new Models.Media(name, parentId, mediaType);
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            CreateMedia(scope, media, parent!, userId, false);
+            scope.Complete();
+        }
+
+        return media;
+    }
+
+    /// 
+    ///     Creates an  object of a specified media type, at root.
+    /// 
+    /// 
+    ///     This method simply returns a new, non-persisted, IMedia without any identity. It
+    ///     is intended as a shortcut to creating new media objects that does not invoke a save
+    ///     operation against the database.
+    /// 
+    /// The name of the media object.
+    /// The alias of the media type.
+    /// The optional id of the user creating the media.
+    /// The media object.
+    public IMedia CreateMedia(string name, string mediaTypeAlias, int userId = Constants.Security.SuperUserId)
+    {
+        // not locking since not saving anything
+
+        IMediaType mediaType = GetMediaType(mediaTypeAlias);
+        if (mediaType == null)
+        {
+            throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias));
+        }
+
+        if (name != null && name.Length > 255)
+        {
+            throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+            throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+        }
+
+        var media = new Models.Media(name, -1, mediaType);
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            CreateMedia(scope, media, null, userId, false);
+            scope.Complete();
+        }
+
+        return media;
+    }
+
+    /// 
+    ///     Creates an  object of a specified media type, under a parent.
+    /// 
+    /// 
+    ///     This method simply returns a new, non-persisted, IMedia without any identity. It
+    ///     is intended as a shortcut to creating new media objects that does not invoke a save
+    ///     operation against the database.
+    /// 
+    /// The name of the media object.
+    /// The parent media object.
+    /// The alias of the media type.
+    /// The optional id of the user creating the media.
+    /// The media object.
+    public IMedia CreateMedia(string name, IMedia? parent, string mediaTypeAlias,
+        int userId = Constants.Security.SuperUserId)
+    {
+        if (parent == null)
+        {
+            throw new ArgumentNullException(nameof(parent));
         }
 
-        /// 
-        /// Creates an  object of a specified media type, at root.
-        /// 
-        /// This method simply returns a new, non-persisted, IMedia without any identity. It
-        /// is intended as a shortcut to creating new media objects that does not invoke a save
-        /// operation against the database.
-        /// 
-        /// The name of the media object.
-        /// The alias of the media type.
-        /// The optional id of the user creating the media.
-        /// The media object.
-        public IMedia CreateMedia(string name, string mediaTypeAlias, int userId = Cms.Core.Constants.Security.SuperUserId)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
             // not locking since not saving anything
 
-            var mediaType = GetMediaType(mediaTypeAlias);
+            IMediaType mediaType = GetMediaType(mediaTypeAlias);
             if (mediaType == null)
-                throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias));
-            if (name != null && name.Length > 255)
             {
-                throw new InvalidOperationException("Name cannot be more than 255 characters in length."); throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+                throw new ArgumentException("No media type with that alias.",
+                    nameof(mediaTypeAlias)); // causes rollback
             }
 
-            var media = new Core.Models.Media(name, -1, mediaType);
-            using (var scope = ScopeProvider.CreateCoreScope())
+            if (name != null && name.Length > 255)
             {
-                CreateMedia(scope, media, null, userId, false);
-                scope.Complete();
+                throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+                throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
             }
 
+            var media = new Models.Media(name, parent, mediaType);
+            CreateMedia(scope, media, parent, userId, false);
+
+            scope.Complete();
             return media;
         }
+    }
 
-        /// 
-        /// Creates an  object of a specified media type, under a parent.
-        /// 
-        /// This method simply returns a new, non-persisted, IMedia without any identity. It
-        /// is intended as a shortcut to creating new media objects that does not invoke a save
-        /// operation against the database.
-        /// 
-        /// The name of the media object.
-        /// The parent media object.
-        /// The alias of the media type.
-        /// The optional id of the user creating the media.
-        /// The media object.
-        public IMedia CreateMedia(string name, IMedia? parent, string mediaTypeAlias, int userId = Cms.Core.Constants.Security.SuperUserId)
+    /// 
+    ///     Creates an  object of a specified media type.
+    /// 
+    /// This method returns a new, persisted, IMedia with an identity.
+    /// The name of the media object.
+    /// The identifier of the parent, or -1.
+    /// The alias of the media type.
+    /// The optional id of the user creating the media.
+    /// The media object.
+    public IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias,
+        int userId = Constants.Security.SuperUserId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            if (parent == null) throw new ArgumentNullException(nameof(parent));
+            // locking the media tree secures media types too
+            scope.WriteLock(Constants.Locks.MediaTree);
 
-            using (var scope = ScopeProvider.CreateCoreScope())
+            IMediaType mediaType = GetMediaType(mediaTypeAlias); // + locks
+            if (mediaType == null)
             {
-                // not locking since not saving anything
+                throw new ArgumentException("No media type with that alias.",
+                    nameof(mediaTypeAlias)); // causes rollback
+            }
 
-                var mediaType = GetMediaType(mediaTypeAlias);
-                if (mediaType == null)
-                    throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); // causes rollback
-                if (name != null && name.Length > 255)
-                {
-                    throw new InvalidOperationException("Name cannot be more than 255 characters in length."); throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
-                }
+            IMedia parent = parentId > 0 ? GetById(parentId) : null; // + locks
+            if (parentId > 0 && parent == null)
+            {
+                throw new ArgumentException("No media with that id.", nameof(parentId)); // causes rollback
+            }
 
-                var media = new Core.Models.Media(name, parent, mediaType);
-                CreateMedia(scope, media, parent, userId, false);
+            Models.Media media = parentId > 0
+                ? new Models.Media(name, parent, mediaType)
+                : new Models.Media(name, parentId, mediaType);
+            CreateMedia(scope, media, parent, userId, true);
 
-                scope.Complete();
-                return media;
-            }
+            scope.Complete();
+            return media;
         }
+    }
 
-        /// 
-        /// Creates an  object of a specified media type.
-        /// 
-        /// This method returns a new, persisted, IMedia with an identity.
-        /// The name of the media object.
-        /// The identifier of the parent, or -1.
-        /// The alias of the media type.
-        /// The optional id of the user creating the media.
-        /// The media object.
-        public IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = Cms.Core.Constants.Security.SuperUserId)
+    /// 
+    ///     Creates an  object of a specified media type, under a parent.
+    /// 
+    /// This method returns a new, persisted, IMedia with an identity.
+    /// The name of the media object.
+    /// The parent media object.
+    /// The alias of the media type.
+    /// The optional id of the user creating the media.
+    /// The media object.
+    public IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias,
+        int userId = Constants.Security.SuperUserId)
+    {
+        if (parent == null)
         {
-            using (var scope = ScopeProvider.CreateCoreScope())
-            {
-                // locking the media tree secures media types too
-                scope.WriteLock(Cms.Core.Constants.Locks.MediaTree);
+            throw new ArgumentNullException(nameof(parent));
+        }
 
-                var mediaType = GetMediaType(mediaTypeAlias); // + locks
-                if (mediaType == null)
-                    throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); // causes rollback
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            // locking the media tree secures media types too
+            scope.WriteLock(Constants.Locks.MediaTree);
 
-                var parent = parentId > 0 ? GetById(parentId) : null; // + locks
-                if (parentId > 0 && parent == null)
-                    throw new ArgumentException("No media with that id.", nameof(parentId)); // causes rollback
+            IMediaType mediaType = GetMediaType(mediaTypeAlias); // + locks
+            if (mediaType == null)
+            {
+                throw new ArgumentException("No media type with that alias.",
+                    nameof(mediaTypeAlias)); // causes rollback
+            }
 
-                var media = parentId > 0 ? new Core.Models.Media(name, parent, mediaType) : new Core.Models.Media(name, parentId, mediaType);
-                CreateMedia(scope, media, parent, userId, true);
+            var media = new Models.Media(name, parent, mediaType);
+            CreateMedia(scope, media, parent, userId, true);
 
-                scope.Complete();
-                return media;
-            }
+            scope.Complete();
+            return media;
         }
+    }
 
-        /// 
-        /// Creates an  object of a specified media type, under a parent.
-        /// 
-        /// This method returns a new, persisted, IMedia with an identity.
-        /// The name of the media object.
-        /// The parent media object.
-        /// The alias of the media type.
-        /// The optional id of the user creating the media.
-        /// The media object.
-        public IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = Cms.Core.Constants.Security.SuperUserId)
-        {
-            if (parent == null) throw new ArgumentNullException(nameof(parent));
+    private void CreateMedia(ICoreScope scope, Models.Media media, IMedia? parent, int userId, bool withIdentity)
+    {
+        EventMessages eventMessages = EventMessagesFactory.Get();
 
-            using (var scope = ScopeProvider.CreateCoreScope())
-            {
-                // locking the media tree secures media types too
-                scope.WriteLock(Cms.Core.Constants.Locks.MediaTree);
+        media.CreatorId = userId;
 
-                var mediaType = GetMediaType(mediaTypeAlias); // + locks
-                if (mediaType == null)
-                    throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); // causes rollback
+        if (withIdentity)
+        {
+            var savingNotification = new MediaSavingNotification(media, eventMessages);
+            if (scope.Notifications.PublishCancelable(savingNotification))
+            {
+                return;
+            }
 
-                var media = new Core.Models.Media(name, parent, mediaType);
-                CreateMedia(scope, media, parent, userId, true);
+            _mediaRepository.Save(media);
 
-                scope.Complete();
-                return media;
-            }
+            scope.Notifications.Publish(
+                new MediaSavedNotification(media, eventMessages).WithStateFrom(savingNotification));
+            scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshNode,
+                eventMessages));
         }
 
-        private void CreateMedia(ICoreScope scope, Core.Models.Media media, IMedia? parent, int userId, bool withIdentity)
+        if (withIdentity == false)
         {
-            EventMessages eventMessages = EventMessagesFactory.Get();
-
-            media.CreatorId = userId;
-
-            if (withIdentity)
-            {
-                var savingNotification = new MediaSavingNotification(media, eventMessages);
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    return;
-                }
+            return;
+        }
 
-                _mediaRepository.Save(media);
+        Audit(AuditType.New, media.CreatorId, media.Id, $"Media '{media.Name}' was created with Id {media.Id}");
+    }
 
-                scope.Notifications.Publish(new MediaSavedNotification(media, eventMessages).WithStateFrom(savingNotification));
-                scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshNode, eventMessages));
-            }
+    #endregion
 
-            if (withIdentity == false)
-                return;
+    #region Get, Has, Is
 
-            Audit(AuditType.New, media.CreatorId, media.Id, $"Media '{media.Name}' was created with Id {media.Id}");
+    /// 
+    ///     Gets an  object by Id
+    /// 
+    /// Id of the Media to retrieve
+    /// 
+    ///     
+    /// 
+    public IMedia? GetById(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            scope.ReadLock(Constants.Locks.MediaTree);
+            return _mediaRepository.Get(id);
         }
+    }
 
-        #endregion
+    /// 
+    ///     Gets an  object by Id
+    /// 
+    /// Ids of the Media to retrieve
+    /// 
+    ///     
+    /// 
+    public IEnumerable GetByIds(IEnumerable ids)
+    {
+        var idsA = ids.ToArray();
+        if (idsA.Length == 0)
+        {
+            return Enumerable.Empty();
+        }
 
-        #region Get, Has, Is
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            scope.ReadLock(Constants.Locks.MediaTree);
+            return _mediaRepository.GetMany(idsA);
+        }
+    }
 
-        /// 
-        /// Gets an  object by Id
-        /// 
-        /// Id of the Media to retrieve
-        /// 
-        public IMedia? GetById(int id)
+    /// 
+    ///     Gets an  object by its 'UniqueId'
+    /// 
+    /// Guid key of the Media to retrieve
+    /// 
+    ///     
+    /// 
+    public IMedia? GetById(Guid key)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Cms.Core.Constants.Locks.MediaTree);
-                return _mediaRepository.Get(id);
-            }
+            scope.ReadLock(Constants.Locks.MediaTree);
+            return _mediaRepository.Get(key);
         }
+    }
 
-        /// 
-        /// Gets an  object by Id
-        /// 
-        /// Ids of the Media to retrieve
-        /// 
-        public IEnumerable GetByIds(IEnumerable ids)
+    /// 
+    ///     Gets an  object by Id
+    /// 
+    /// Ids of the Media to retrieve
+    /// 
+    ///     
+    /// 
+    public IEnumerable GetByIds(IEnumerable ids)
+    {
+        Guid[] idsA = ids.ToArray();
+        if (idsA.Length == 0)
         {
-            var idsA = ids.ToArray();
-            if (idsA.Length == 0) return Enumerable.Empty();
+            return Enumerable.Empty();
+        }
 
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Cms.Core.Constants.Locks.MediaTree);
-                return _mediaRepository.GetMany(idsA);
-            }
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            scope.ReadLock(Constants.Locks.MediaTree);
+            return _mediaRepository.GetMany(idsA);
         }
+    }
 
-        /// 
-        /// Gets an  object by its 'UniqueId'
-        /// 
-        /// Guid key of the Media to retrieve
-        /// 
-        public IMedia? GetById(Guid key)
+    /// 
+    public IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords,
+        IQuery? filter = null, Ordering? ordering = null)
+    {
+        if (pageIndex < 0)
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Cms.Core.Constants.Locks.MediaTree);
-                return _mediaRepository.Get(key);
-            }
+            throw new ArgumentOutOfRangeException(nameof(pageIndex));
         }
 
-        /// 
-        /// Gets an  object by Id
-        /// 
-        /// Ids of the Media to retrieve
-        /// 
-        public IEnumerable GetByIds(IEnumerable ids)
+        if (pageSize <= 0)
         {
-            var idsA = ids.ToArray();
-            if (idsA.Length == 0) return Enumerable.Empty();
+            throw new ArgumentOutOfRangeException(nameof(pageSize));
+        }
 
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Cms.Core.Constants.Locks.MediaTree);
-                return _mediaRepository.GetMany(idsA);
-            }
+        if (ordering == null)
+        {
+            ordering = Ordering.By("sortOrder");
         }
 
-        /// 
-        public IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords, IQuery? filter = null, Ordering? ordering = null)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex));
-            if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize));
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _mediaRepository.GetPage(
+                Query()?.Where(x => x.ContentTypeId == contentTypeId),
+                pageIndex, pageSize, out totalRecords, filter, ordering);
+        }
+    }
 
-            if (ordering == null)
-                ordering = Ordering.By("sortOrder");
+    /// 
+    public IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize,
+        out long totalRecords, IQuery? filter = null, Ordering? ordering = null)
+    {
+        if (pageIndex < 0)
+        {
+            throw new ArgumentOutOfRangeException(nameof(pageIndex));
+        }
 
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Cms.Core.Constants.Locks.ContentTree);
-                return _mediaRepository.GetPage(
-                    Query()?.Where(x => x.ContentTypeId == contentTypeId),
-                    pageIndex, pageSize, out totalRecords, filter, ordering);
-            }
+        if (pageSize <= 0)
+        {
+            throw new ArgumentOutOfRangeException(nameof(pageSize));
         }
 
-        /// 
-        public IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize, out long totalRecords, IQuery? filter = null, Ordering? ordering = null)
+        if (ordering == null)
         {
-            if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex));
-            if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize));
+            ordering = Ordering.By("sortOrder");
+        }
 
-            if (ordering == null)
-                ordering = Ordering.By("sortOrder");
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            scope.ReadLock(Constants.Locks.ContentTree);
+            return _mediaRepository.GetPage(
+                Query()?.Where(x => contentTypeIds.Contains(x.ContentTypeId)),
+                pageIndex, pageSize, out totalRecords, filter, ordering);
+        }
+    }
 
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Cms.Core.Constants.Locks.ContentTree);
-                return _mediaRepository.GetPage(
-                    Query()?.Where(x => contentTypeIds.Contains(x.ContentTypeId)),
-                    pageIndex, pageSize, out totalRecords, filter, ordering);
-            }
+    /// 
+    ///     Gets a collection of  objects by Level
+    /// 
+    /// The level to retrieve Media from
+    /// An Enumerable list of  objects
+    /// Contrary to most methods, this method filters out trashed media items.
+    public IEnumerable? GetByLevel(int level)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            scope.ReadLock(Constants.Locks.MediaTree);
+            IQuery query = Query().Where(x => x.Level == level && x.Trashed == false);
+            return _mediaRepository.Get(query);
         }
+    }
 
-        /// 
-        /// Gets a collection of  objects by Level
-        /// 
-        /// The level to retrieve Media from
-        /// An Enumerable list of  objects
-        /// Contrary to most methods, this method filters out trashed media items.
-        public IEnumerable? GetByLevel(int level)
+    /// 
+    ///     Gets a specific version of an  item.
+    /// 
+    /// Id of the version to retrieve
+    /// An  item
+    public IMedia? GetVersion(int versionId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Cms.Core.Constants.Locks.MediaTree);
-                var query = Query().Where(x => x.Level == level && x.Trashed == false);
-                return _mediaRepository.Get(query);
-            }
+            scope.ReadLock(Constants.Locks.MediaTree);
+            return _mediaRepository.GetVersion(versionId);
         }
+    }
 
-        /// 
-        /// Gets a specific version of an  item.
-        /// 
-        /// Id of the version to retrieve
-        /// An  item
-        public IMedia? GetVersion(int versionId)
+    /// 
+    ///     Gets a collection of an  objects versions by Id
+    /// 
+    /// 
+    /// An Enumerable list of  objects
+    public IEnumerable GetVersions(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Cms.Core.Constants.Locks.MediaTree);
-                return _mediaRepository.GetVersion(versionId);
-            }
+            scope.ReadLock(Constants.Locks.MediaTree);
+            return _mediaRepository.GetAllVersions(id);
         }
+    }
 
-        /// 
-        /// Gets a collection of an  objects versions by Id
-        /// 
-        /// 
-        /// An Enumerable list of  objects
-        public IEnumerable GetVersions(int id)
+    /// 
+    ///     Gets a collection of  objects, which are ancestors of the current media.
+    /// 
+    /// Id of the  to retrieve ancestors for
+    /// An Enumerable list of  objects
+    public IEnumerable GetAncestors(int id)
+    {
+        // intentionally not locking
+        IMedia media = GetById(id);
+        return GetAncestors(media);
+    }
+
+    /// 
+    ///     Gets a collection of  objects, which are ancestors of the current media.
+    /// 
+    ///  to retrieve ancestors for
+    /// An Enumerable list of  objects
+    public IEnumerable GetAncestors(IMedia? media)
+    {
+        //null check otherwise we get exceptions
+        if (media is null || media.Path.IsNullOrWhiteSpace())
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Cms.Core.Constants.Locks.MediaTree);
-                return _mediaRepository.GetAllVersions(id);
-            }
+            return Enumerable.Empty();
         }
 
-        /// 
-        /// Gets a collection of  objects, which are ancestors of the current media.
-        /// 
-        /// Id of the  to retrieve ancestors for
-        /// An Enumerable list of  objects
-        public IEnumerable GetAncestors(int id)
+        var rootId = Constants.System.RootString;
+        var ids = media.Path.Split(Constants.CharArrays.Comma)
+            .Where(x => x != rootId && x != media.Id.ToString(CultureInfo.InvariantCulture))
+            .Select(s => int.Parse(s, CultureInfo.InvariantCulture))
+            .ToArray();
+        if (ids.Any() == false)
         {
-            // intentionally not locking
-            var media = GetById(id);
-            return GetAncestors(media);
+            return new List();
         }
 
-        /// 
-        /// Gets a collection of  objects, which are ancestors of the current media.
-        /// 
-        ///  to retrieve ancestors for
-        /// An Enumerable list of  objects
-        public IEnumerable GetAncestors(IMedia? media)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            //null check otherwise we get exceptions
-            if (media is null || media.Path.IsNullOrWhiteSpace()) return Enumerable.Empty();
+            scope.ReadLock(Constants.Locks.MediaTree);
+            return _mediaRepository.GetMany(ids);
+        }
+    }
 
-            var rootId = Cms.Core.Constants.System.RootString;
-            var ids = media.Path.Split(Constants.CharArrays.Comma)
-                .Where(x => x != rootId && x != media.Id.ToString(CultureInfo.InvariantCulture))
-                .Select(s => int.Parse(s, CultureInfo.InvariantCulture))
-                .ToArray();
-            if (ids.Any() == false)
-                return new List();
+    /// 
+    public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren,
+        IQuery? filter = null, Ordering? ordering = null)
+    {
+        if (pageIndex < 0)
+        {
+            throw new ArgumentOutOfRangeException(nameof(pageIndex));
+        }
 
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Cms.Core.Constants.Locks.MediaTree);
-                return _mediaRepository.GetMany(ids);
-            }
+        if (pageSize <= 0)
+        {
+            throw new ArgumentOutOfRangeException(nameof(pageSize));
         }
 
-        /// 
-        public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren,
-            IQuery? filter = null, Ordering? ordering = null)
+        if (ordering == null)
         {
-            if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex));
-            if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize));
+            ordering = Ordering.By("sortOrder");
+        }
 
-            if (ordering == null)
-                ordering = Ordering.By("sortOrder");
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            scope.ReadLock(Constants.Locks.MediaTree);
 
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Cms.Core.Constants.Locks.MediaTree);
+            IQuery query = Query()?.Where(x => x.ParentId == id);
+            return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering);
+        }
+    }
 
-                var query = Query()?.Where(x => x.ParentId == id);
-                return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering);
-            }
+    /// 
+    public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren,
+        IQuery? filter = null, Ordering? ordering = null)
+    {
+        if (ordering == null)
+        {
+            ordering = Ordering.By("Path");
         }
 
-        /// 
-        public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren,
-            IQuery? filter = null, Ordering? ordering = null)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            if (ordering == null)
-                ordering = Ordering.By("Path");
+            scope.ReadLock(Constants.Locks.MediaTree);
 
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+            //if the id is System Root, then just get all
+            if (id != Constants.System.Root)
             {
-                scope.ReadLock(Cms.Core.Constants.Locks.MediaTree);
-
-                //if the id is System Root, then just get all
-                if (id != Cms.Core.Constants.System.Root)
+                TreeEntityPath[] mediaPath = _entityRepository.GetAllPaths(Constants.ObjectTypes.Media, id).ToArray();
+                if (mediaPath.Length == 0)
                 {
-                    var mediaPath = _entityRepository.GetAllPaths(Cms.Core.Constants.ObjectTypes.Media, id).ToArray();
-                    if (mediaPath.Length == 0)
-                    {
-                        totalChildren = 0;
-                        return Enumerable.Empty();
-                    }
-                    return GetPagedLocked(GetPagedDescendantQuery(mediaPath[0].Path), pageIndex, pageSize, out totalChildren, filter, ordering);
+                    totalChildren = 0;
+                    return Enumerable.Empty();
                 }
-                return GetPagedLocked(GetPagedDescendantQuery(null), pageIndex, pageSize, out totalChildren, filter, ordering);
+
+                return GetPagedLocked(GetPagedDescendantQuery(mediaPath[0].Path), pageIndex, pageSize,
+                    out totalChildren, filter, ordering);
             }
+
+            return GetPagedLocked(GetPagedDescendantQuery(null), pageIndex, pageSize, out totalChildren, filter,
+                ordering);
         }
+    }
 
-        private IQuery? GetPagedDescendantQuery(string? mediaPath)
+    private IQuery? GetPagedDescendantQuery(string? mediaPath)
+    {
+        IQuery query = Query();
+        if (!mediaPath.IsNullOrWhiteSpace())
         {
-            var query = Query();
-            if (!mediaPath.IsNullOrWhiteSpace())
-                query?.Where(x => x.Path.SqlStartsWith(mediaPath + ",", TextColumnType.NVarchar));
-            return query;
+            query?.Where(x => x.Path.SqlStartsWith(mediaPath + ",", TextColumnType.NVarchar));
         }
 
-        private IEnumerable GetPagedLocked(IQuery? query, long pageIndex, int pageSize, out long totalChildren,
-            IQuery? filter, Ordering ordering)
+        return query;
+    }
+
+    private IEnumerable GetPagedLocked(IQuery? query, long pageIndex, int pageSize,
+        out long totalChildren,
+        IQuery? filter, Ordering ordering)
+    {
+        if (pageIndex < 0)
         {
-            if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex));
-            if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize));
-            if (ordering == null) throw new ArgumentNullException(nameof(ordering));
+            throw new ArgumentOutOfRangeException(nameof(pageIndex));
+        }
 
-            return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering);
+        if (pageSize <= 0)
+        {
+            throw new ArgumentOutOfRangeException(nameof(pageSize));
         }
 
-        /// 
-        /// Gets the parent of the current media as an  item.
-        /// 
-        /// Id of the  to retrieve the parent from
-        /// Parent  object
-        public IMedia? GetParent(int id)
+        if (ordering == null)
         {
-            // intentionally not locking
-            var media = GetById(id);
-            return GetParent(media);
+            throw new ArgumentNullException(nameof(ordering));
         }
 
-        /// 
-        /// Gets the parent of the current media as an  item.
-        /// 
-        ///  to retrieve the parent from
-        /// Parent  object
-        public IMedia? GetParent(IMedia? media)
+        return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering);
+    }
+
+    /// 
+    ///     Gets the parent of the current media as an  item.
+    /// 
+    /// Id of the  to retrieve the parent from
+    /// Parent  object
+    public IMedia? GetParent(int id)
+    {
+        // intentionally not locking
+        IMedia media = GetById(id);
+        return GetParent(media);
+    }
+
+    /// 
+    ///     Gets the parent of the current media as an  item.
+    /// 
+    ///  to retrieve the parent from
+    /// Parent  object
+    public IMedia? GetParent(IMedia? media)
+    {
+        var parentId = media?.ParentId;
+        if (parentId is null || media?.ParentId == Constants.System.Root ||
+            media?.ParentId == Constants.System.RecycleBinMedia)
         {
-            var parentId = media?.ParentId;
-            if (parentId is null || media?.ParentId == Cms.Core.Constants.System.Root || media?.ParentId == Cms.Core.Constants.System.RecycleBinMedia)
-                return null;
+            return null;
+        }
+
+        return GetById(parentId.Value);
+    }
 
-            return GetById(parentId.Value);
+    /// 
+    ///     Gets a collection of  objects, which reside at the first level / root
+    /// 
+    /// An Enumerable list of  objects
+    public IEnumerable? GetRootMedia()
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            scope.ReadLock(Constants.Locks.MediaTree);
+            IQuery query = Query().Where(x => x.ParentId == Constants.System.Root);
+            return _mediaRepository.Get(query);
         }
+    }
 
-        /// 
-        /// Gets a collection of  objects, which reside at the first level / root
-        /// 
-        /// An Enumerable list of  objects
-        public IEnumerable? GetRootMedia()
+    /// 
+    public IEnumerable GetPagedMediaInRecycleBin(long pageIndex, int pageSize, out long totalRecords,
+        IQuery? filter = null, Ordering? ordering = null)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+            if (ordering == null)
             {
-                scope.ReadLock(Cms.Core.Constants.Locks.MediaTree);
-                var query = Query().Where(x => x.ParentId == Cms.Core.Constants.System.Root);
-                return _mediaRepository.Get(query);
+                ordering = Ordering.By("Path");
             }
+
+            scope.ReadLock(Constants.Locks.MediaTree);
+            IQuery query =
+                Query()?.Where(x => x.Path.StartsWith(Constants.System.RecycleBinMediaPathPrefix));
+            return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalRecords, filter, ordering);
         }
+    }
 
-        /// 
-        public IEnumerable GetPagedMediaInRecycleBin(long pageIndex, int pageSize, out long totalRecords,
-            IQuery? filter = null, Ordering? ordering = null)
+    /// 
+    ///     Checks whether an  item has any children
+    /// 
+    /// Id of the 
+    /// True if the media has any children otherwise False
+    public bool HasChildren(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                if (ordering == null)
-                    ordering = Ordering.By("Path");
+            IQuery query = Query().Where(x => x.ParentId == id);
+            var count = _mediaRepository.Count(query);
+            return count > 0;
+        }
+    }
 
-                scope.ReadLock(Cms.Core.Constants.Locks.MediaTree);
-                var query = Query()?.Where(x => x.Path.StartsWith(Cms.Core.Constants.System.RecycleBinMediaPathPrefix));
-                return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalRecords, filter, ordering);
-            }
+    /// 
+    ///     Gets an  object from the path stored in the 'umbracoFile' property.
+    /// 
+    /// Path of the media item to retrieve (for example: /media/1024/koala_403x328.jpg)
+    /// 
+    ///     
+    /// 
+    public IMedia? GetMediaByPath(string mediaPath)
+    {
+        using (ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return _mediaRepository.GetMediaByPath(mediaPath);
         }
+    }
+
+    #endregion
+
+    #region Save
+
+    /// 
+    ///     Saves a single  object
+    /// 
+    /// The  to save
+    /// Id of the User saving the Media
+    public Attempt Save(IMedia media, int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages eventMessages = EventMessagesFactory.Get();
 
-        /// 
-        /// Checks whether an  item has any children
-        /// 
-        /// Id of the 
-        /// True if the media has any children otherwise False
-        public bool HasChildren(int id)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+            var savingNotification = new MediaSavingNotification(media, eventMessages);
+            if (scope.Notifications.PublishCancelable(savingNotification))
             {
-                var query = Query().Where(x => x.ParentId == id);
-                var count = _mediaRepository.Count(query);
-                return count > 0;
+                scope.Complete();
+                return OperationResult.Attempt.Cancel(eventMessages);
             }
-        }
 
-        /// 
-        /// Gets an  object from the path stored in the 'umbracoFile' property.
-        /// 
-        /// Path of the media item to retrieve (for example: /media/1024/koala_403x328.jpg)
-        /// 
-        public IMedia? GetMediaByPath(string mediaPath)
-        {
-            using (ScopeProvider.CreateCoreScope(autoComplete: true))
+            // poor man's validation?
+            if (string.IsNullOrWhiteSpace(media.Name))
+            {
+                throw new ArgumentException("Media has no name.", nameof(media));
+            }
+
+            if (media.Name != null && media.Name.Length > 255)
+            {
+                throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+                throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
+            }
+
+            scope.WriteLock(Constants.Locks.MediaTree);
+            if (media.HasIdentity == false)
             {
-                return _mediaRepository.GetMediaByPath(mediaPath);
+                media.CreatorId = userId;
             }
-        }
-
-        #endregion
 
-        #region Save
+            _mediaRepository.Save(media);
+            scope.Notifications.Publish(
+                new MediaSavedNotification(media, eventMessages).WithStateFrom(savingNotification));
+            // TODO: See note about suppressing events in content service
+            scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshNode,
+                eventMessages));
+
+            Audit(AuditType.Save, userId, media.Id);
+            scope.Complete();
+        }
 
+        return OperationResult.Attempt.Succeed(eventMessages);
+    }
 
+    /// 
+    ///     Saves a collection of  objects
+    /// 
+    /// Collection of  to save
+    /// Id of the User saving the Media
+    public Attempt Save(IEnumerable medias, int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages messages = EventMessagesFactory.Get();
+        IMedia[] mediasA = medias.ToArray();
 
-        /// 
-        /// Saves a single  object
-        /// 
-        /// The  to save
-        /// Id of the User saving the Media
-        public Attempt Save(IMedia media, int userId = Cms.Core.Constants.Security.SuperUserId)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            EventMessages eventMessages = EventMessagesFactory.Get();
-
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            var savingNotification = new MediaSavingNotification(mediasA, messages);
+            if (scope.Notifications.PublishCancelable(savingNotification))
             {
-                var savingNotification = new MediaSavingNotification(media, eventMessages);
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    scope.Complete();
-                    return OperationResult.Attempt.Cancel(eventMessages);
-                }
-
-                // poor man's validation?
-                if (string.IsNullOrWhiteSpace(media.Name))
-                {
-                    throw new ArgumentException("Media has no name.", nameof(media));
-                }
+                scope.Complete();
+                return OperationResult.Attempt.Cancel(messages);
+            }
 
-                if (media.Name != null && media.Name.Length > 255)
-                {
-                    throw new InvalidOperationException("Name cannot be more than 255 characters in length."); throw new InvalidOperationException("Name cannot be more than 255 characters in length.");
-                }
+            IEnumerable> treeChanges =
+                mediasA.Select(x => new TreeChange(x, TreeChangeTypes.RefreshNode));
 
-                scope.WriteLock(Cms.Core.Constants.Locks.MediaTree);
+            scope.WriteLock(Constants.Locks.MediaTree);
+            foreach (IMedia media in mediasA)
+            {
                 if (media.HasIdentity == false)
                 {
                     media.CreatorId = userId;
                 }
 
                 _mediaRepository.Save(media);
-                scope.Notifications.Publish(new MediaSavedNotification(media, eventMessages).WithStateFrom(savingNotification));
-                // TODO: See note about suppressing events in content service
-                scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshNode, eventMessages));
-
-                Audit(AuditType.Save, userId, media.Id);
-                scope.Complete();
             }
 
-            return OperationResult.Attempt.Succeed(eventMessages);
-        }
-
-        /// 
-        /// Saves a collection of  objects
-        /// 
-        /// Collection of  to save
-        /// Id of the User saving the Media
-        public Attempt Save(IEnumerable medias, int userId = Cms.Core.Constants.Security.SuperUserId)
-        {
-            EventMessages messages = EventMessagesFactory.Get();
-            IMedia[] mediasA = medias.ToArray();
+            scope.Notifications.Publish(
+                new MediaSavedNotification(mediasA, messages).WithStateFrom(savingNotification));
+            // TODO: See note about suppressing events in content service
+            scope.Notifications.Publish(new MediaTreeChangeNotification(treeChanges, messages));
+            Audit(AuditType.Save, userId == -1 ? 0 : userId, Constants.System.Root, "Bulk save media");
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                var savingNotification = new MediaSavingNotification(mediasA, messages);
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    scope.Complete();
-                    return OperationResult.Attempt.Cancel(messages);
-                }
+            scope.Complete();
+        }
 
-                IEnumerable> treeChanges = mediasA.Select(x => new TreeChange(x, TreeChangeTypes.RefreshNode));
+        return OperationResult.Attempt.Succeed(messages);
+    }
 
-                scope.WriteLock(Cms.Core.Constants.Locks.MediaTree);
-                foreach (IMedia media in mediasA)
-                {
-                    if (media.HasIdentity == false)
-                    {
-                        media.CreatorId = userId;
-                    }
+    #endregion
 
-                    _mediaRepository.Save(media);
-                }
+    #region Delete
 
-                scope.Notifications.Publish(new MediaSavedNotification(mediasA, messages).WithStateFrom(savingNotification));
-                // TODO: See note about suppressing events in content service
-                scope.Notifications.Publish(new MediaTreeChangeNotification(treeChanges, messages));
-                Audit(AuditType.Save, userId == -1 ? 0 : userId, Cms.Core.Constants.System.Root, "Bulk save media");
+    /// 
+    ///     Permanently deletes an  object
+    /// 
+    /// The  to delete
+    /// Id of the User deleting the Media
+    public Attempt Delete(IMedia media, int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages messages = EventMessagesFactory.Get();
 
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            if (scope.Notifications.PublishCancelable(new MediaDeletingNotification(media, messages)))
+            {
                 scope.Complete();
+                return OperationResult.Attempt.Cancel(messages);
             }
 
-            return OperationResult.Attempt.Succeed(messages);
-        }
-
-        #endregion
-
-        #region Delete
-
-        /// 
-        /// Permanently deletes an  object
-        /// 
-        /// The  to delete
-        /// Id of the User deleting the Media
-        public Attempt Delete(IMedia media, int userId = Cms.Core.Constants.Security.SuperUserId)
-        {
-            EventMessages messages = EventMessagesFactory.Get();
+            scope.WriteLock(Constants.Locks.MediaTree);
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                if (scope.Notifications.PublishCancelable(new MediaDeletingNotification(media, messages)))
-                {
-                    scope.Complete();
-                    return OperationResult.Attempt.Cancel(messages);
-                }
+            DeleteLocked(scope, media, messages);
 
-                scope.WriteLock(Cms.Core.Constants.Locks.MediaTree);
+            scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.Remove, messages));
+            Audit(AuditType.Delete, userId, media.Id);
 
-                DeleteLocked(scope, media, messages);
+            scope.Complete();
+        }
 
-                scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.Remove, messages));
-                Audit(AuditType.Delete, userId, media.Id);
+        return OperationResult.Attempt.Succeed(messages);
+    }
 
-                scope.Complete();
-            }
+    private void DeleteLocked(ICoreScope scope, IMedia media, EventMessages evtMsgs)
+    {
+        void DoDelete(IMedia c)
+        {
+            _mediaRepository.Delete(c);
+            scope.Notifications.Publish(new MediaDeletedNotification(c, evtMsgs));
 
-            return OperationResult.Attempt.Succeed(messages);
+            // media files deleted by QueuingEventDispatcher
         }
 
-        private void DeleteLocked(ICoreScope scope, IMedia media, EventMessages evtMsgs)
+        const int pageSize = 500;
+        var page = 0;
+        var total = long.MaxValue;
+        while (page * pageSize < total)
         {
-            void DoDelete(IMedia c)
+            //get descendants - ordered from deepest to shallowest
+            IEnumerable descendants = GetPagedDescendants(media.Id, page, pageSize, out total,
+                ordering: Ordering.By("Path", Direction.Descending));
+            foreach (IMedia c in descendants)
             {
-                _mediaRepository.Delete(c);
-                scope.Notifications.Publish(new MediaDeletedNotification(c, evtMsgs));
-
-                // media files deleted by QueuingEventDispatcher
+                DoDelete(c);
             }
+        }
 
-            const int pageSize = 500;
-            var page = 0;
-            var total = long.MaxValue;
-            while (page * pageSize < total)
-            {
-                //get descendants - ordered from deepest to shallowest
-                var descendants = GetPagedDescendants(media.Id, page, pageSize, out total, ordering: Ordering.By("Path", Direction.Descending));
-                foreach (var c in descendants)
-                    DoDelete(c);
-            }
-            DoDelete(media);
+        DoDelete(media);
+    }
+
+    //TODO: both DeleteVersions methods below have an issue. Sort of. They do NOT take care of files the way
+    // Delete does - for a good reason: the file may be referenced by other, non-deleted, versions. BUT,
+    // if that's not the case, then the file will never be deleted, because when we delete the media,
+    // the version referencing the file will not be there anymore. SO, we can leak files.
+
+    /// 
+    ///     Permanently deletes versions from an  object prior to a specific date.
+    ///     This method will never delete the latest version of a media item.
+    /// 
+    /// Id of the  object to delete versions from
+    /// Latest version date
+    /// Optional Id of the User deleting versions of a Media object
+    public void DeleteVersions(int id, DateTime versionDate, int userId = Constants.Security.SuperUserId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            DeleteVersions(scope, true, id, versionDate, userId);
+            scope.Complete();
         }
+    }
 
-        //TODO: both DeleteVersions methods below have an issue. Sort of. They do NOT take care of files the way
-        // Delete does - for a good reason: the file may be referenced by other, non-deleted, versions. BUT,
-        // if that's not the case, then the file will never be deleted, because when we delete the media,
-        // the version referencing the file will not be there anymore. SO, we can leak files.
+    private void DeleteVersions(ICoreScope scope, bool wlock, int id, DateTime versionDate,
+        int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
 
-        /// 
-        /// Permanently deletes versions from an  object prior to a specific date.
-        /// This method will never delete the latest version of a media item.
-        /// 
-        /// Id of the  object to delete versions from
-        /// Latest version date
-        /// Optional Id of the User deleting versions of a Media object
-        public void DeleteVersions(int id, DateTime versionDate, int userId = Cms.Core.Constants.Security.SuperUserId)
+        var deletingVersionsNotification =
+            new MediaDeletingVersionsNotification(id, evtMsgs, dateToRetain: versionDate);
+        if (scope.Notifications.PublishCancelable(deletingVersionsNotification))
         {
-            using (var scope = ScopeProvider.CreateCoreScope())
-            {
-                DeleteVersions(scope, true, id, versionDate, userId);
-                scope.Complete();
-            }
+            return;
         }
 
-        private void DeleteVersions(ICoreScope scope, bool wlock, int id, DateTime versionDate, int userId = Cms.Core.Constants.Security.SuperUserId)
+        if (wlock)
         {
-            var evtMsgs = EventMessagesFactory.Get();
+            scope.WriteLock(Constants.Locks.MediaTree);
+        }
 
-            var deletingVersionsNotification = new MediaDeletingVersionsNotification(id, evtMsgs, dateToRetain: versionDate);
-            if (scope.Notifications.PublishCancelable(deletingVersionsNotification))
-            {
-                return;
-            }
+        _mediaRepository.DeleteVersions(id, versionDate);
 
-            if (wlock)
-                scope.WriteLock(Cms.Core.Constants.Locks.MediaTree);
-            _mediaRepository.DeleteVersions(id, versionDate);
+        scope.Notifications.Publish(
+            new MediaDeletedVersionsNotification(id, evtMsgs, dateToRetain: versionDate).WithStateFrom(
+                deletingVersionsNotification));
+        Audit(AuditType.Delete, userId, Constants.System.Root, "Delete Media by version date");
+    }
 
-            scope.Notifications.Publish(new MediaDeletedVersionsNotification(id, evtMsgs, dateToRetain: versionDate).WithStateFrom(deletingVersionsNotification));
-            Audit(AuditType.Delete, userId, Cms.Core.Constants.System.Root, "Delete Media by version date");
-        }
+    /// 
+    ///     Permanently deletes specific version(s) from an  object.
+    ///     This method will never delete the latest version of a media item.
+    /// 
+    /// Id of the  object to delete a version from
+    /// Id of the version to delete
+    /// Boolean indicating whether to delete versions prior to the versionId
+    /// Optional Id of the User deleting versions of a Media object
+    public void DeleteVersion(int id, int versionId, bool deletePriorVersions,
+        int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
 
-        /// 
-        /// Permanently deletes specific version(s) from an  object.
-        /// This method will never delete the latest version of a media item.
-        /// 
-        /// Id of the  object to delete a version from
-        /// Id of the version to delete
-        /// Boolean indicating whether to delete versions prior to the versionId
-        /// Optional Id of the User deleting versions of a Media object
-        public void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = Cms.Core.Constants.Security.SuperUserId)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            var evtMsgs = EventMessagesFactory.Get();
-
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            var deletingVersionsNotification = new MediaDeletingVersionsNotification(id, evtMsgs, versionId);
+            if (scope.Notifications.PublishCancelable(deletingVersionsNotification))
             {
-                var deletingVersionsNotification = new MediaDeletingVersionsNotification(id, evtMsgs, specificVersion: versionId);
-                if (scope.Notifications.PublishCancelable(deletingVersionsNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
+                scope.Complete();
+                return;
+            }
 
-                if (deletePriorVersions)
-                {
-                    var media = GetVersion(versionId);
-                    if (media is not null)
-                    {
-                        DeleteVersions(scope, true, id, media.UpdateDate, userId);
-                    }
-                }
-                else
+            if (deletePriorVersions)
+            {
+                IMedia media = GetVersion(versionId);
+                if (media is not null)
                 {
-                    scope.WriteLock(Cms.Core.Constants.Locks.MediaTree);
+                    DeleteVersions(scope, true, id, media.UpdateDate, userId);
                 }
+            }
+            else
+            {
+                scope.WriteLock(Constants.Locks.MediaTree);
+            }
 
-                _mediaRepository.DeleteVersion(versionId);
+            _mediaRepository.DeleteVersion(versionId);
 
-                scope.Notifications.Publish(new MediaDeletedVersionsNotification(id, evtMsgs, specificVersion: versionId).WithStateFrom(deletingVersionsNotification));
-                Audit(AuditType.Delete, userId, Cms.Core.Constants.System.Root, "Delete Media by version");
+            scope.Notifications.Publish(
+                new MediaDeletedVersionsNotification(id, evtMsgs, versionId)
+                    .WithStateFrom(deletingVersionsNotification));
+            Audit(AuditType.Delete, userId, Constants.System.Root, "Delete Media by version");
 
-                scope.Complete();
-            }
+            scope.Complete();
         }
+    }
+
+    #endregion
 
-        #endregion
+    #region Move, RecycleBin
 
-        #region Move, RecycleBin
+    /// 
+    ///     Deletes an  object by moving it to the Recycle Bin
+    /// 
+    /// The  to delete
+    /// Id of the User deleting the Media
+    public Attempt MoveToRecycleBin(IMedia media, int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages messages = EventMessagesFactory.Get();
+        var moves = new List<(IMedia, string)>();
 
-        /// 
-        /// Deletes an  object by moving it to the Recycle Bin
-        /// 
-        /// The  to delete
-        /// Id of the User deleting the Media
-        public Attempt MoveToRecycleBin(IMedia media, int userId = Cms.Core.Constants.Security.SuperUserId)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            EventMessages messages = EventMessagesFactory.Get();
-            var moves = new List<(IMedia, string)>();
+            scope.WriteLock(Constants.Locks.MediaTree);
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(Cms.Core.Constants.Locks.MediaTree);
+            // TODO: missing 7.6 "ensure valid path" thing here?
+            // but then should be in PerformMoveLocked on every moved item?
 
-                // TODO: missing 7.6 "ensure valid path" thing here?
-                // but then should be in PerformMoveLocked on every moved item?
+            var originalPath = media.Path;
 
-                var originalPath = media.Path;
+            var moveEventInfo = new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia);
 
-                var moveEventInfo = new MoveEventInfo(media, originalPath, Cms.Core.Constants.System.RecycleBinMedia);
+            var movingToRecycleBinNotification = new MediaMovingToRecycleBinNotification(moveEventInfo, messages);
+            if (scope.Notifications.PublishCancelable(movingToRecycleBinNotification))
+            {
+                scope.Complete();
+                return OperationResult.Attempt.Cancel(messages);
+            }
 
-                var movingToRecycleBinNotification = new MediaMovingToRecycleBinNotification(moveEventInfo, messages);
-                if (scope.Notifications.PublishCancelable(movingToRecycleBinNotification))
-                {
-                    scope.Complete();
-                    return OperationResult.Attempt.Cancel(messages);
-                }
+            PerformMoveLocked(media, Constants.System.RecycleBinMedia, null, userId, moves, true);
 
-                PerformMoveLocked(media, Cms.Core.Constants.System.RecycleBinMedia, null, userId, moves, true);
+            scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshBranch,
+                messages));
+            MoveEventInfo[] moveInfo =
+                moves.Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)).ToArray();
+            scope.Notifications.Publish(
+                new MediaMovedToRecycleBinNotification(moveInfo, messages)
+                    .WithStateFrom(movingToRecycleBinNotification));
+            Audit(AuditType.Move, userId, media.Id, "Move Media to recycle bin");
 
-                scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshBranch, messages));
-                MoveEventInfo[] moveInfo = moves.Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)).ToArray();
-                scope.Notifications.Publish(new MediaMovedToRecycleBinNotification(moveInfo, messages).WithStateFrom(movingToRecycleBinNotification));
-                Audit(AuditType.Move, userId, media.Id, "Move Media to recycle bin");
+            scope.Complete();
+        }
 
-                scope.Complete();
-            }
+        return OperationResult.Attempt.Succeed(messages);
+    }
+
+    /// 
+    ///     Moves an  object to a new location
+    /// 
+    /// The  to move
+    /// Id of the Media's new Parent
+    /// Id of the User moving the Media
+    public Attempt Move(IMedia media, int parentId, int userId = Constants.Security.SuperUserId)
+    {
+        EventMessages messages = EventMessagesFactory.Get();
 
+        // if moving to the recycle bin then use the proper method
+        if (parentId == Constants.System.RecycleBinMedia)
+        {
+            MoveToRecycleBin(media, userId);
             return OperationResult.Attempt.Succeed(messages);
         }
 
-        /// 
-        /// Moves an  object to a new location
-        /// 
-        /// The  to move
-        /// Id of the Media's new Parent
-        /// Id of the User moving the Media
-        public Attempt Move(IMedia media, int parentId, int userId = Cms.Core.Constants.Security.SuperUserId)
+        var moves = new List<(IMedia, string)>();
+
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            EventMessages messages = EventMessagesFactory.Get();
+            scope.WriteLock(Constants.Locks.MediaTree);
 
-            // if moving to the recycle bin then use the proper method
-            if (parentId == Cms.Core.Constants.System.RecycleBinMedia)
+            IMedia? parent = parentId == Constants.System.Root ? null : GetById(parentId);
+            if (parentId != Constants.System.Root && (parent == null || parent.Trashed))
             {
-                MoveToRecycleBin(media, userId);
-                return OperationResult.Attempt.Succeed(messages);
+                throw new InvalidOperationException("Parent does not exist or is trashed."); // causes rollback
             }
 
-            var moves = new List<(IMedia, string)>();
-
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            var moveEventInfo = new MoveEventInfo(media, media.Path, parentId);
+            var movingNotification = new MediaMovingNotification(moveEventInfo, messages);
+            if (scope.Notifications.PublishCancelable(movingNotification))
             {
-                scope.WriteLock(Cms.Core.Constants.Locks.MediaTree);
-
-                IMedia? parent = parentId == Cms.Core.Constants.System.Root ? null : GetById(parentId);
-                if (parentId != Cms.Core.Constants.System.Root && (parent == null || parent.Trashed))
-                {
-                    throw new InvalidOperationException("Parent does not exist or is trashed."); // causes rollback
-                }
-
-                var moveEventInfo = new MoveEventInfo(media, media.Path, parentId);
-                var movingNotification = new MediaMovingNotification(moveEventInfo, messages);
-                if (scope.Notifications.PublishCancelable(movingNotification))
-                {
-                    scope.Complete();
-                    return OperationResult.Attempt.Cancel(messages);
-                }
-
-                // if media was trashed, and since we're not moving to the recycle bin,
-                // indicate that the trashed status should be changed to false, else just
-                // leave it unchanged
-                var trashed = media.Trashed ? false : (bool?)null;
-
-                PerformMoveLocked(media, parentId, parent, userId, moves, trashed);
-                scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshBranch, messages));
-
-                MoveEventInfo[] moveInfo = moves //changes
-                    .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId))
-                    .ToArray();
-                scope.Notifications.Publish(new MediaMovedNotification(moveInfo, messages).WithStateFrom(movingNotification));
-                Audit(AuditType.Move, userId, media.Id);
                 scope.Complete();
+                return OperationResult.Attempt.Cancel(messages);
             }
-            return OperationResult.Attempt.Succeed(messages);
-        }
 
-        // MUST be called from within WriteLock
-        // trash indicates whether we are trashing, un-trashing, or not changing anything
-        private void PerformMoveLocked(IMedia media, int parentId, IMedia? parent, int userId, ICollection<(IMedia, string)> moves, bool? trash)
-        {
-            media.ParentId = parentId;
+            // if media was trashed, and since we're not moving to the recycle bin,
+            // indicate that the trashed status should be changed to false, else just
+            // leave it unchanged
+            var trashed = media.Trashed ? false : (bool?)null;
 
-            // get the level delta (old pos to new pos)
-            // note that recycle bin (id:-20) level is 0!
-            var levelDelta = 1 - media.Level + (parent?.Level ?? 0);
+            PerformMoveLocked(media, parentId, parent, userId, moves, trashed);
+            scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshBranch,
+                messages));
 
-            var paths = new Dictionary();
+            MoveEventInfo[] moveInfo = moves //changes
+                .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId))
+                .ToArray();
+            scope.Notifications.Publish(
+                new MediaMovedNotification(moveInfo, messages).WithStateFrom(movingNotification));
+            Audit(AuditType.Move, userId, media.Id);
+            scope.Complete();
+        }
 
-            moves.Add((media, media.Path)); // capture original path
+        return OperationResult.Attempt.Succeed(messages);
+    }
 
-            //need to store the original path to lookup descendants based on it below
-            var originalPath = media.Path;
+    // MUST be called from within WriteLock
+    // trash indicates whether we are trashing, un-trashing, or not changing anything
+    private void PerformMoveLocked(IMedia media, int parentId, IMedia? parent, int userId,
+        ICollection<(IMedia, string)> moves, bool? trash)
+    {
+        media.ParentId = parentId;
 
-            // these will be updated by the repo because we changed parentId
-            //media.Path = (parent == null ? "-1" : parent.Path) + "," + media.Id;
-            //media.SortOrder = ((MediaRepository) repository).NextChildSortOrder(parentId);
-            //media.Level += levelDelta;
-            PerformMoveMediaLocked(media, userId, trash);
-
-            // if uow is not immediate, content.Path will be updated only when the UOW commits,
-            // and because we want it now, we have to calculate it by ourselves
-            //paths[media.Id] = media.Path;
-            paths[media.Id] = (parent == null ? (parentId == Cms.Core.Constants.System.RecycleBinMedia ? "-1,-21" : Cms.Core.Constants.System.RootString) : parent.Path) + "," + media.Id;
-
-            const int pageSize = 500;
-            var query = GetPagedDescendantQuery(originalPath);
-            long total;
-            do
-            {
-                // We always page a page 0 because for each page, we are moving the result so the resulting total will be reduced
-                var descendants = GetPagedLocked(query, 0, pageSize, out total, null, Ordering.By("Path", Direction.Ascending));
+        // get the level delta (old pos to new pos)
+        // note that recycle bin (id:-20) level is 0!
+        var levelDelta = 1 - media.Level + (parent?.Level ?? 0);
 
-                foreach (var descendant in descendants)
-                {
-                    moves.Add((descendant, descendant.Path)); // capture original path
+        var paths = new Dictionary();
 
-                    // update path and level since we do not update parentId
-                    descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id;
-                    descendant.Level += levelDelta;
-                    PerformMoveMediaLocked(descendant, userId, trash);
-                }
+        moves.Add((media, media.Path)); // capture original path
 
-            } while (total > pageSize);
+        //need to store the original path to lookup descendants based on it below
+        var originalPath = media.Path;
 
-        }
+        // these will be updated by the repo because we changed parentId
+        //media.Path = (parent == null ? "-1" : parent.Path) + "," + media.Id;
+        //media.SortOrder = ((MediaRepository) repository).NextChildSortOrder(parentId);
+        //media.Level += levelDelta;
+        PerformMoveMediaLocked(media, userId, trash);
 
-        private void PerformMoveMediaLocked(IMedia media, int userId, bool? trash)
-        {
-            if (trash.HasValue) ((ContentBase)media).Trashed = trash.Value;
-            _mediaRepository.Save(media);
-        }
+        // if uow is not immediate, content.Path will be updated only when the UOW commits,
+        // and because we want it now, we have to calculate it by ourselves
+        //paths[media.Id] = media.Path;
+        paths[media.Id] =
+            (parent == null
+                ? parentId == Constants.System.RecycleBinMedia ? "-1,-21" : Constants.System.RootString
+                : parent.Path) + "," + media.Id;
 
-        /// 
-        /// Empties the Recycle Bin by deleting all  that resides in the bin
-        /// 
-        /// Optional Id of the User emptying the Recycle Bin
-        public OperationResult EmptyRecycleBin(int userId = Cms.Core.Constants.Security.SuperUserId)
+        const int pageSize = 500;
+        IQuery query = GetPagedDescendantQuery(originalPath);
+        long total;
+        do
         {
-            var deleted = new List();
-            EventMessages messages = EventMessagesFactory.Get(); // TODO: and then?
+            // We always page a page 0 because for each page, we are moving the result so the resulting total will be reduced
+            IEnumerable descendants = GetPagedLocked(query, 0, pageSize, out total, null, Ordering.By("Path"));
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            foreach (IMedia descendant in descendants)
             {
-                scope.WriteLock(Cms.Core.Constants.Locks.MediaTree);
-
-                // emptying the recycle bin means deleting whatever is in there - do it properly!
-                IQuery? query = Query().Where(x => x.ParentId == Cms.Core.Constants.System.RecycleBinMedia);
-                IMedia[] medias = _mediaRepository.Get(query)?.ToArray() ?? Array.Empty();
-
-                var emptyingRecycleBinNotification = new MediaEmptyingRecycleBinNotification(medias, messages);
-                if (scope.Notifications.PublishCancelable(emptyingRecycleBinNotification))
-                {
-                    scope.Complete();
-                    return OperationResult.Cancel(messages);
-                }
+                moves.Add((descendant, descendant.Path)); // capture original path
 
-                foreach (IMedia media in medias)
-                {
-                    DeleteLocked(scope, media, messages);
-                    deleted.Add(media);
-                }
-                scope.Notifications.Publish(new MediaEmptiedRecycleBinNotification(deleted, new EventMessages()).WithStateFrom(emptyingRecycleBinNotification));
-                scope.Notifications.Publish(new MediaTreeChangeNotification(deleted, TreeChangeTypes.Remove, messages));
-                Audit(AuditType.Delete, userId, Cms.Core.Constants.System.RecycleBinMedia, "Empty Media recycle bin");
-                scope.Complete();
+                // update path and level since we do not update parentId
+                descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id;
+                descendant.Level += levelDelta;
+                PerformMoveMediaLocked(descendant, userId, trash);
             }
+        } while (total > pageSize);
+    }
 
-            return OperationResult.Succeed(messages);
-        }
-
-        public bool RecycleBinSmells()
+    private void PerformMoveMediaLocked(IMedia media, int userId, bool? trash)
+    {
+        if (trash.HasValue)
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MediaTree);
-                return _mediaRepository.RecycleBinSmells();
-            }
+            ((ContentBase)media).Trashed = trash.Value;
         }
 
-        #endregion
+        _mediaRepository.Save(media);
+    }
 
-        #region Others
+    /// 
+    ///     Empties the Recycle Bin by deleting all  that resides in the bin
+    /// 
+    /// Optional Id of the User emptying the Recycle Bin
+    public OperationResult EmptyRecycleBin(int userId = Constants.Security.SuperUserId)
+    {
+        var deleted = new List();
+        EventMessages messages = EventMessagesFactory.Get(); // TODO: and then?
 
-        /// 
-        /// Sorts a collection of  objects by updating the SortOrder according
-        /// to the ordering of items in the passed in .
-        /// 
-        /// 
-        /// 
-        /// True if sorting succeeded, otherwise False
-        public bool Sort(IEnumerable items, int userId = Cms.Core.Constants.Security.SuperUserId)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            IMedia[] itemsA = items.ToArray();
-            if (itemsA.Length == 0)
+            scope.WriteLock(Constants.Locks.MediaTree);
+
+            // emptying the recycle bin means deleting whatever is in there - do it properly!
+            IQuery? query = Query().Where(x => x.ParentId == Constants.System.RecycleBinMedia);
+            IMedia[] medias = _mediaRepository.Get(query)?.ToArray() ?? Array.Empty();
+
+            var emptyingRecycleBinNotification = new MediaEmptyingRecycleBinNotification(medias, messages);
+            if (scope.Notifications.PublishCancelable(emptyingRecycleBinNotification))
             {
-                return true;
+                scope.Complete();
+                return OperationResult.Cancel(messages);
             }
 
-            EventMessages messages = EventMessagesFactory.Get();
-
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            foreach (IMedia media in medias)
             {
-                var savingNotification = new MediaSavingNotification(itemsA, messages);
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    scope.Complete();
-                    return false;
-                }
+                DeleteLocked(scope, media, messages);
+                deleted.Add(media);
+            }
 
-                var saved = new List();
+            scope.Notifications.Publish(
+                new MediaEmptiedRecycleBinNotification(deleted, new EventMessages()).WithStateFrom(
+                    emptyingRecycleBinNotification));
+            scope.Notifications.Publish(new MediaTreeChangeNotification(deleted, TreeChangeTypes.Remove, messages));
+            Audit(AuditType.Delete, userId, Constants.System.RecycleBinMedia, "Empty Media recycle bin");
+            scope.Complete();
+        }
 
-                scope.WriteLock(Cms.Core.Constants.Locks.MediaTree);
-                var sortOrder = 0;
+        return OperationResult.Succeed(messages);
+    }
 
-                foreach (IMedia media in itemsA)
-                {
-                    // if the current sort order equals that of the media we don't
-                    // need to update it, so just increment the sort order and continue.
-                    if (media.SortOrder == sortOrder)
-                    {
-                        sortOrder++;
-                        continue;
-                    }
-                    // else update
-                    media.SortOrder = sortOrder++;
-                    // save
-                    saved.Add(media);
-                    _mediaRepository.Save(media);
-                }
+    public bool RecycleBinSmells()
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            scope.ReadLock(Constants.Locks.MediaTree);
+            return _mediaRepository.RecycleBinSmells();
+        }
+    }
 
-                scope.Notifications.Publish(new MediaSavedNotification(itemsA, messages).WithStateFrom(savingNotification));
-                // TODO: See note about suppressing events in content service
-                scope.Notifications.Publish(new MediaTreeChangeNotification(saved, TreeChangeTypes.RefreshNode, messages));
-                Audit(AuditType.Sort, userId, 0);
+    #endregion
 
-                scope.Complete();
-            }
+    #region Others
 
+    /// 
+    ///     Sorts a collection of  objects by updating the SortOrder according
+    ///     to the ordering of items in the passed in .
+    /// 
+    /// 
+    /// 
+    /// True if sorting succeeded, otherwise False
+    public bool Sort(IEnumerable items, int userId = Constants.Security.SuperUserId)
+    {
+        IMedia[] itemsA = items.ToArray();
+        if (itemsA.Length == 0)
+        {
             return true;
-
         }
 
-        public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options)
+        EventMessages messages = EventMessagesFactory.Get();
+
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+            var savingNotification = new MediaSavingNotification(itemsA, messages);
+            if (scope.Notifications.PublishCancelable(savingNotification))
             {
-                scope.WriteLock(Cms.Core.Constants.Locks.MediaTree);
+                scope.Complete();
+                return false;
+            }
 
-                ContentDataIntegrityReport report = _mediaRepository.CheckDataIntegrity(options);
+            var saved = new List();
 
-                if (report.FixedIssues.Count > 0)
+            scope.WriteLock(Constants.Locks.MediaTree);
+            var sortOrder = 0;
+
+            foreach (IMedia media in itemsA)
+            {
+                // if the current sort order equals that of the media we don't
+                // need to update it, so just increment the sort order and continue.
+                if (media.SortOrder == sortOrder)
                 {
-                    //The event args needs a content item so we'll make a fake one with enough properties to not cause a null ref
-                    var root = new Core.Models.Media("root", -1, new MediaType(_shortStringHelper, -1)) { Id = -1, Key = Guid.Empty };
-                    scope.Notifications.Publish(new MediaTreeChangeNotification(root, TreeChangeTypes.RefreshAll, EventMessagesFactory.Get()));
+                    sortOrder++;
+                    continue;
                 }
 
-                return report;
+                // else update
+                media.SortOrder = sortOrder++;
+                // save
+                saved.Add(media);
+                _mediaRepository.Save(media);
             }
-        }
 
-        #endregion
+            scope.Notifications.Publish(new MediaSavedNotification(itemsA, messages).WithStateFrom(savingNotification));
+            // TODO: See note about suppressing events in content service
+            scope.Notifications.Publish(new MediaTreeChangeNotification(saved, TreeChangeTypes.RefreshNode, messages));
+            Audit(AuditType.Sort, userId, 0);
 
-        #region Private Methods
-
-        private void Audit(AuditType type, int userId, int objectId, string? message = null)
-        {
-            _auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.Media), message));
+            scope.Complete();
         }
 
-        #endregion
-
-        #region File Management
+        return true;
+    }
 
-        public Stream GetMediaFileContentStream(string filepath)
+    public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            if (_mediaFileManager.FileSystem.FileExists(filepath) == false)
-            {
-                return Stream.Null;
-            }
+            scope.WriteLock(Constants.Locks.MediaTree);
 
-            try
-            {
-                return _mediaFileManager.FileSystem.OpenFile(filepath);
-            }
-            catch
+            ContentDataIntegrityReport report = _mediaRepository.CheckDataIntegrity(options);
+
+            if (report.FixedIssues.Count > 0)
             {
-                return Stream.Null; // deal with race conds
+                //The event args needs a content item so we'll make a fake one with enough properties to not cause a null ref
+                var root = new Models.Media("root", -1, new MediaType(_shortStringHelper, -1))
+                {
+                    Id = -1, Key = Guid.Empty
+                };
+                scope.Notifications.Publish(new MediaTreeChangeNotification(root, TreeChangeTypes.RefreshAll,
+                    EventMessagesFactory.Get()));
             }
+
+            return report;
         }
+    }
+
+    #endregion
 
-        public void SetMediaFileContent(string filepath, Stream stream)
+    #region File Management
+
+    public Stream GetMediaFileContentStream(string filepath)
+    {
+        if (_mediaFileManager.FileSystem.FileExists(filepath) == false)
         {
-            _mediaFileManager.FileSystem.AddFile(filepath, stream, true);
+            return Stream.Null;
         }
 
-        public void DeleteMediaFile(string filepath)
+        try
         {
-            _mediaFileManager.FileSystem.DeleteFile(filepath);
+            return _mediaFileManager.FileSystem.OpenFile(filepath);
         }
-
-        public long GetMediaFileSize(string filepath)
+        catch
         {
-            return _mediaFileManager.FileSystem.GetSize(filepath);
+            return Stream.Null; // deal with race conds
         }
+    }
 
-        #endregion
+    public void SetMediaFileContent(string filepath, Stream stream) =>
+        _mediaFileManager.FileSystem.AddFile(filepath, stream, true);
 
-        #region Content Types
+    public void DeleteMediaFile(string filepath) => _mediaFileManager.FileSystem.DeleteFile(filepath);
 
-        /// 
-        /// Deletes all media of specified type. All children of deleted media is moved to Recycle Bin.
-        /// 
-        /// 
-        /// This needs extra care and attention as its potentially a dangerous and extensive operation.
-        /// Deletes media items of the specified type, and only that type. Does *not* handle content types
-        /// inheritance and compositions, which need to be managed outside of this method.
-        /// 
-        /// Id of the 
-        /// Optional id of the user deleting the media
-        public void DeleteMediaOfTypes(IEnumerable mediaTypeIds, int userId = Cms.Core.Constants.Security.SuperUserId)
-        {
-            // TODO: This currently this is called from the ContentTypeService but that needs to change,
-            // if we are deleting a content type, we should just delete the data and do this operation slightly differently.
-            // This method will recursively go lookup every content item, check if any of it's descendants are
-            // of a different type, move them to the recycle bin, then permanently delete the content items.
-            // The main problem with this is that for every content item being deleted, events are raised...
-            // which we need for many things like keeping caches in sync, but we can surely do this MUCH better.
+    public long GetMediaFileSize(string filepath) => _mediaFileManager.FileSystem.GetSize(filepath);
 
-            var changes = new List>();
-            var moves = new List<(IMedia, string)>();
-            var mediaTypeIdsA = mediaTypeIds.ToArray();
-            EventMessages messages = EventMessagesFactory.Get();
+    #endregion
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(Cms.Core.Constants.Locks.MediaTree);
+    #region Content Types
+
+    /// 
+    ///     Deletes all media of specified type. All children of deleted media is moved to Recycle Bin.
+    /// 
+    /// 
+    ///     This needs extra care and attention as its potentially a dangerous and extensive operation.
+    ///     
+    ///         Deletes media items of the specified type, and only that type. Does *not* handle content types
+    ///         inheritance and compositions, which need to be managed outside of this method.
+    ///     
+    /// 
+    /// Id of the 
+    /// Optional id of the user deleting the media
+    public void DeleteMediaOfTypes(IEnumerable mediaTypeIds, int userId = Constants.Security.SuperUserId)
+    {
+        // TODO: This currently this is called from the ContentTypeService but that needs to change,
+        // if we are deleting a content type, we should just delete the data and do this operation slightly differently.
+        // This method will recursively go lookup every content item, check if any of it's descendants are
+        // of a different type, move them to the recycle bin, then permanently delete the content items.
+        // The main problem with this is that for every content item being deleted, events are raised...
+        // which we need for many things like keeping caches in sync, but we can surely do this MUCH better.
+
+        var changes = new List>();
+        var moves = new List<(IMedia, string)>();
+        var mediaTypeIdsA = mediaTypeIds.ToArray();
+        EventMessages messages = EventMessagesFactory.Get();
+
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            scope.WriteLock(Constants.Locks.MediaTree);
 
-                IQuery? query = Query().WhereIn(x => x.ContentTypeId, mediaTypeIdsA);
-                IMedia[] medias = _mediaRepository.Get(query)?.ToArray() ?? Array.Empty();
+            IQuery? query = Query().WhereIn(x => x.ContentTypeId, mediaTypeIdsA);
+            IMedia[] medias = _mediaRepository.Get(query)?.ToArray() ?? Array.Empty();
 
-                if (scope.Notifications.PublishCancelable(new MediaDeletingNotification(medias, messages)))
-                {
-                    scope.Complete();
-                    return;
-                }
+            if (scope.Notifications.PublishCancelable(new MediaDeletingNotification(medias, messages)))
+            {
+                scope.Complete();
+                return;
+            }
 
-                // order by level, descending, so deepest first - that way, we cannot move
-                // a media of the deleted type, to the recycle bin (and then delete it...)
-                foreach (IMedia media in medias.OrderByDescending(x => x.ParentId))
+            // order by level, descending, so deepest first - that way, we cannot move
+            // a media of the deleted type, to the recycle bin (and then delete it...)
+            foreach (IMedia media in medias.OrderByDescending(x => x.ParentId))
+            {
+                // if current media has children, move them to trash
+                IMedia m = media;
+                IQuery? childQuery = Query().Where(x => x.Path.StartsWith(m.Path));
+                IEnumerable? children = _mediaRepository.Get(childQuery);
+                if (children is not null)
                 {
-                    // if current media has children, move them to trash
-                    IMedia m = media;
-                    IQuery? childQuery = Query().Where(x => x.Path.StartsWith(m.Path));
-                    IEnumerable? children = _mediaRepository.Get(childQuery);
-                    if (children is not null)
+                    foreach (IMedia child in children.Where(x => mediaTypeIdsA.Contains(x.ContentTypeId) == false))
                     {
-                        foreach (IMedia child in children.Where(x => mediaTypeIdsA.Contains(x.ContentTypeId) == false))
-                        {
-                            // see MoveToRecycleBin
-                            PerformMoveLocked(child, Cms.Core.Constants.System.RecycleBinMedia, null, userId, moves, true);
-                            changes.Add(new TreeChange(media, TreeChangeTypes.RefreshBranch));
-                        }
+                        // see MoveToRecycleBin
+                        PerformMoveLocked(child, Constants.System.RecycleBinMedia, null, userId, moves, true);
+                        changes.Add(new TreeChange(media, TreeChangeTypes.RefreshBranch));
                     }
-
-                    // delete media
-                    // triggers the deleted event (and handles the files)
-                    DeleteLocked(scope, media, messages);
-                    changes.Add(new TreeChange(media, TreeChangeTypes.Remove));
-                }
-
-                MoveEventInfo[] moveInfos = moves.Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId))
-                    .ToArray();
-                if (moveInfos.Length > 0)
-                {
-                    scope.Notifications.Publish(new MediaMovedToRecycleBinNotification(moveInfos, messages));
                 }
-                scope.Notifications.Publish(new MediaTreeChangeNotification(changes, messages));
 
-                Audit(AuditType.Delete, userId, Cms.Core.Constants.System.Root, $"Delete Media of types {string.Join(",", mediaTypeIdsA)}");
+                // delete media
+                // triggers the deleted event (and handles the files)
+                DeleteLocked(scope, media, messages);
+                changes.Add(new TreeChange(media, TreeChangeTypes.Remove));
+            }
 
-                scope.Complete();
+            MoveEventInfo[] moveInfos = moves
+                .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId))
+                .ToArray();
+            if (moveInfos.Length > 0)
+            {
+                scope.Notifications.Publish(new MediaMovedToRecycleBinNotification(moveInfos, messages));
             }
+
+            scope.Notifications.Publish(new MediaTreeChangeNotification(changes, messages));
+
+            Audit(AuditType.Delete, userId, Constants.System.Root,
+                $"Delete Media of types {string.Join(",", mediaTypeIdsA)}");
+
+            scope.Complete();
         }
+    }
+
+    /// 
+    ///     Deletes all media of specified type. All children of deleted media is moved to Recycle Bin.
+    /// 
+    /// This needs extra care and attention as its potentially a dangerous and extensive operation
+    /// Id of the 
+    /// Optional id of the user deleting the media
+    public void DeleteMediaOfType(int mediaTypeId, int userId = Constants.Security.SuperUserId) =>
+        DeleteMediaOfTypes(new[] {mediaTypeId}, userId);
 
-        /// 
-        /// Deletes all media of specified type. All children of deleted media is moved to Recycle Bin.
-        /// 
-        /// This needs extra care and attention as its potentially a dangerous and extensive operation
-        /// Id of the 
-        /// Optional id of the user deleting the media
-        public void DeleteMediaOfType(int mediaTypeId, int userId = Cms.Core.Constants.Security.SuperUserId)
+    private IMediaType GetMediaType(string mediaTypeAlias)
+    {
+        if (mediaTypeAlias == null)
         {
-            DeleteMediaOfTypes(new[] { mediaTypeId }, userId);
+            throw new ArgumentNullException(nameof(mediaTypeAlias));
         }
 
-        private IMediaType GetMediaType(string mediaTypeAlias)
+        if (string.IsNullOrWhiteSpace(mediaTypeAlias))
         {
-            if (mediaTypeAlias == null) throw new ArgumentNullException(nameof(mediaTypeAlias));
-            if (string.IsNullOrWhiteSpace(mediaTypeAlias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(mediaTypeAlias));
-
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.ReadLock(Cms.Core.Constants.Locks.MediaTypes);
+            throw new ArgumentException("Value can't be empty or consist only of white-space characters.",
+                nameof(mediaTypeAlias));
+        }
 
-                var query = Query().Where(x => x.Alias == mediaTypeAlias);
-                var mediaType = _mediaTypeRepository.Get(query)?.FirstOrDefault();
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            scope.ReadLock(Constants.Locks.MediaTypes);
 
-                if (mediaType == null)
-                    throw new InvalidOperationException($"No media type matched the specified alias '{mediaTypeAlias}'.");
+            IQuery query = Query().Where(x => x.Alias == mediaTypeAlias);
+            IMediaType mediaType = _mediaTypeRepository.Get(query)?.FirstOrDefault();
 
-                scope.Complete();
-                return mediaType;
+            if (mediaType == null)
+            {
+                throw new InvalidOperationException($"No media type matched the specified alias '{mediaTypeAlias}'.");
             }
-        }
-
-        #endregion
-
 
+            scope.Complete();
+            return mediaType;
+        }
     }
+
+    #endregion
 }
diff --git a/src/Umbraco.Core/Services/MediaServiceExtensions.cs b/src/Umbraco.Core/Services/MediaServiceExtensions.cs
index 1cf648c35d26..3687ed6a2a09 100644
--- a/src/Umbraco.Core/Services/MediaServiceExtensions.cs
+++ b/src/Umbraco.Core/Services/MediaServiceExtensions.cs
@@ -1,45 +1,51 @@
 // Copyright (c) Umbraco.
 // See LICENSE for more details.
 
-using System;
-using System.Collections.Generic;
-using System.Linq;
 using Umbraco.Cms.Core;
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Services;
 
-namespace Umbraco.Extensions
+namespace Umbraco.Extensions;
+
+/// 
+///     Media service extension methods
+/// 
+/// 
+///     Many of these have to do with UDI lookups but we don't need to add these methods to the service interface since a
+///     UDI is just a GUID
+///     and the services already support GUIDs
+/// 
+public static class MediaServiceExtensions
 {
-    /// 
-    /// Media service extension methods
-    /// 
-    /// 
-    /// Many of these have to do with UDI lookups but we don't need to add these methods to the service interface since a UDI is just a GUID
-    /// and the services already support GUIDs
-    /// 
-    public static class MediaServiceExtensions
+    public static IEnumerable GetByIds(this IMediaService mediaService, IEnumerable ids)
     {
-        public static IEnumerable GetByIds(this IMediaService mediaService, IEnumerable ids)
+        var guids = new List();
+        foreach (Udi udi in ids)
         {
-            var guids = new List();
-            foreach (var udi in ids)
+            var guidUdi = udi as GuidUdi;
+            if (guidUdi is null)
             {
-                var guidUdi = udi as GuidUdi;
-                if (guidUdi is null)
-                    throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by media");
-                guids.Add(guidUdi);
+                throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) +
+                                                    " which is required by media");
             }
 
-            return mediaService.GetByIds(guids.Select(x => x.Guid));
+            guids.Add(guidUdi);
         }
 
-        public static IMedia CreateMedia(this IMediaService mediaService, string name, Udi parentId, string mediaTypeAlias, int userId = 0)
+        return mediaService.GetByIds(guids.Select(x => x.Guid));
+    }
+
+    public static IMedia CreateMedia(this IMediaService mediaService, string name, Udi parentId, string mediaTypeAlias,
+        int userId = 0)
+    {
+        var guidUdi = parentId as GuidUdi;
+        if (guidUdi is null)
         {
-            var guidUdi = parentId as GuidUdi;
-            if (guidUdi is null)
-                throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by media");
-            var parent = mediaService.GetById(guidUdi.Guid);
-            return mediaService.CreateMedia(name, parent, mediaTypeAlias, userId);
+            throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) +
+                                                " which is required by media");
         }
+
+        IMedia parent = mediaService.GetById(guidUdi.Guid);
+        return mediaService.CreateMedia(name, parent, mediaTypeAlias, userId);
     }
 }
diff --git a/src/Umbraco.Core/Services/MediaTypeService.cs b/src/Umbraco.Core/Services/MediaTypeService.cs
index 6873fb4a3970..4375246b165f 100644
--- a/src/Umbraco.Core/Services/MediaTypeService.cs
+++ b/src/Umbraco.Core/Services/MediaTypeService.cs
@@ -1,6 +1,4 @@
-using System;
-using System.Collections.Generic;
-using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging;
 using Umbraco.Cms.Core.Events;
 using Umbraco.Cms.Core.Models;
 using Umbraco.Cms.Core.Notifications;
@@ -8,70 +6,72 @@
 using Umbraco.Cms.Core.Scoping;
 using Umbraco.Cms.Core.Services.Changes;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public class MediaTypeService : ContentTypeServiceBase, IMediaTypeService
 {
-    public class MediaTypeService : ContentTypeServiceBase, IMediaTypeService
-    {
-        public MediaTypeService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IMediaService mediaService,
-            IMediaTypeRepository mediaTypeRepository, IAuditRepository auditRepository, IMediaTypeContainerRepository entityContainerRepository,
-            IEntityRepository entityRepository, IEventAggregator eventAggregator)
-            : base(provider, loggerFactory, eventMessagesFactory, mediaTypeRepository, auditRepository, entityContainerRepository, entityRepository, eventAggregator)
-        {
-            MediaService = mediaService;
-        }
+    public MediaTypeService(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
+        IEventMessagesFactory eventMessagesFactory, IMediaService mediaService,
+        IMediaTypeRepository mediaTypeRepository, IAuditRepository auditRepository,
+        IMediaTypeContainerRepository entityContainerRepository,
+        IEntityRepository entityRepository, IEventAggregator eventAggregator)
+        : base(provider, loggerFactory, eventMessagesFactory, mediaTypeRepository, auditRepository,
+            entityContainerRepository, entityRepository, eventAggregator) =>
+        MediaService = mediaService;
 
-        // beware! order is important to avoid deadlocks
-        protected override int[] ReadLockIds { get; } = { Cms.Core.Constants.Locks.MediaTypes };
-        protected override int[] WriteLockIds { get; } = { Cms.Core.Constants.Locks.MediaTree, Cms.Core.Constants.Locks.MediaTypes };
+    // beware! order is important to avoid deadlocks
+    protected override int[] ReadLockIds { get; } = {Constants.Locks.MediaTypes};
+    protected override int[] WriteLockIds { get; } = {Constants.Locks.MediaTree, Constants.Locks.MediaTypes};
 
-        private IMediaService MediaService { get; }
+    private IMediaService MediaService { get; }
 
-        protected override Guid ContainedObjectType => Cms.Core.Constants.ObjectTypes.MediaType;
+    protected override Guid ContainedObjectType => Constants.ObjectTypes.MediaType;
 
-        #region Notifications
+    protected override void DeleteItemsOfTypes(IEnumerable typeIds)
+    {
+        foreach (var typeId in typeIds)
+        {
+            MediaService.DeleteMediaOfType(typeId);
+        }
+    }
 
-        protected override SavingNotification GetSavingNotification(IMediaType item,
-            EventMessages eventMessages) => new MediaTypeSavingNotification(item, eventMessages);
+    #region Notifications
 
-        protected override SavingNotification GetSavingNotification(IEnumerable items,
-            EventMessages eventMessages) => new MediaTypeSavingNotification(items, eventMessages);
+    protected override SavingNotification GetSavingNotification(IMediaType item,
+        EventMessages eventMessages) => new MediaTypeSavingNotification(item, eventMessages);
 
-        protected override SavedNotification GetSavedNotification(IMediaType item,
-            EventMessages eventMessages) => new MediaTypeSavedNotification(item, eventMessages);
+    protected override SavingNotification GetSavingNotification(IEnumerable items,
+        EventMessages eventMessages) => new MediaTypeSavingNotification(items, eventMessages);
 
-        protected override SavedNotification GetSavedNotification(IEnumerable items,
-            EventMessages eventMessages) => new MediaTypeSavedNotification(items, eventMessages);
+    protected override SavedNotification GetSavedNotification(IMediaType item,
+        EventMessages eventMessages) => new MediaTypeSavedNotification(item, eventMessages);
 
-        protected override DeletingNotification GetDeletingNotification(IMediaType item,
-            EventMessages eventMessages) => new MediaTypeDeletingNotification(item, eventMessages);
+    protected override SavedNotification GetSavedNotification(IEnumerable items,
+        EventMessages eventMessages) => new MediaTypeSavedNotification(items, eventMessages);
 
-        protected override DeletingNotification GetDeletingNotification(IEnumerable items,
-            EventMessages eventMessages) => new MediaTypeDeletingNotification(items, eventMessages);
+    protected override DeletingNotification GetDeletingNotification(IMediaType item,
+        EventMessages eventMessages) => new MediaTypeDeletingNotification(item, eventMessages);
 
-        protected override DeletedNotification GetDeletedNotification(IEnumerable items,
-            EventMessages eventMessages) => new MediaTypeDeletedNotification(items, eventMessages);
+    protected override DeletingNotification GetDeletingNotification(IEnumerable items,
+        EventMessages eventMessages) => new MediaTypeDeletingNotification(items, eventMessages);
 
-        protected override MovingNotification GetMovingNotification(MoveEventInfo moveInfo,
-            EventMessages eventMessages) => new MediaTypeMovingNotification(moveInfo, eventMessages);
+    protected override DeletedNotification GetDeletedNotification(IEnumerable items,
+        EventMessages eventMessages) => new MediaTypeDeletedNotification(items, eventMessages);
 
-        protected override MovedNotification GetMovedNotification(
-            IEnumerable> moveInfo, EventMessages eventMessages) =>
-            new MediaTypeMovedNotification(moveInfo, eventMessages);
+    protected override MovingNotification GetMovingNotification(MoveEventInfo moveInfo,
+        EventMessages eventMessages) => new MediaTypeMovingNotification(moveInfo, eventMessages);
 
-        protected override ContentTypeChangeNotification GetContentTypeChangedNotification(
-            IEnumerable> changes, EventMessages eventMessages) =>
-            new MediaTypeChangedNotification(changes, eventMessages);
+    protected override MovedNotification GetMovedNotification(
+        IEnumerable> moveInfo, EventMessages eventMessages) =>
+        new MediaTypeMovedNotification(moveInfo, eventMessages);
 
-        protected override ContentTypeRefreshNotification GetContentTypeRefreshedNotification(
-            IEnumerable> changes, EventMessages eventMessages) =>
-            new MediaTypeRefreshedNotification(changes, eventMessages);
+    protected override ContentTypeChangeNotification GetContentTypeChangedNotification(
+        IEnumerable> changes, EventMessages eventMessages) =>
+        new MediaTypeChangedNotification(changes, eventMessages);
 
-        #endregion
+    protected override ContentTypeRefreshNotification GetContentTypeRefreshedNotification(
+        IEnumerable> changes, EventMessages eventMessages) =>
+        new MediaTypeRefreshedNotification(changes, eventMessages);
 
-        protected override void DeleteItemsOfTypes(IEnumerable typeIds)
-        {
-            foreach (var typeId in typeIds)
-                MediaService.DeleteMediaOfType(typeId);
-        }
-    }
+    #endregion
 }
diff --git a/src/Umbraco.Core/Services/MemberGroupService.cs b/src/Umbraco.Core/Services/MemberGroupService.cs
index 2290f9d84a82..0cd869feca8f 100644
--- a/src/Umbraco.Core/Services/MemberGroupService.cs
+++ b/src/Umbraco.Core/Services/MemberGroupService.cs
@@ -1,6 +1,3 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
 using Microsoft.Extensions.Logging;
 using Umbraco.Cms.Core.Events;
 using Umbraco.Cms.Core.Models;
@@ -8,105 +5,107 @@
 using Umbraco.Cms.Core.Persistence.Repositories;
 using Umbraco.Cms.Core.Scoping;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+internal class MemberGroupService : RepositoryService, IMemberGroupService
 {
-    internal class MemberGroupService : RepositoryService, IMemberGroupService
-    {
-        private readonly IMemberGroupRepository _memberGroupRepository;
+    private readonly IMemberGroupRepository _memberGroupRepository;
 
-        public MemberGroupService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory,
-            IMemberGroupRepository memberGroupRepository)
-            : base(provider, loggerFactory, eventMessagesFactory) =>
-            _memberGroupRepository = memberGroupRepository;
+    public MemberGroupService(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
+        IEventMessagesFactory eventMessagesFactory,
+        IMemberGroupRepository memberGroupRepository)
+        : base(provider, loggerFactory, eventMessagesFactory) =>
+        _memberGroupRepository = memberGroupRepository;
 
-        public IEnumerable GetAll()
+    public IEnumerable GetAll()
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _memberGroupRepository.GetMany();
-            }
+            return _memberGroupRepository.GetMany();
         }
+    }
 
-        public IEnumerable GetByIds(IEnumerable ids)
+    public IEnumerable GetByIds(IEnumerable ids)
+    {
+        if (ids == null || ids.Any() == false)
         {
-            if (ids == null || ids.Any() == false)
-            {
-                return new IMemberGroup[0];
-            }
+            return new IMemberGroup[0];
+        }
 
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _memberGroupRepository.GetMany(ids.ToArray());
-            }
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return _memberGroupRepository.GetMany(ids.ToArray());
         }
+    }
 
-        public IMemberGroup? GetById(int id)
+    public IMemberGroup? GetById(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _memberGroupRepository.Get(id);
-            }
+            return _memberGroupRepository.Get(id);
         }
+    }
 
-        public IMemberGroup? GetById(Guid id)
+    public IMemberGroup? GetById(Guid id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _memberGroupRepository.Get(id);
-            }
+            return _memberGroupRepository.Get(id);
         }
+    }
 
-        public IMemberGroup? GetByName(string? name)
+    public IMemberGroup? GetByName(string? name)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _memberGroupRepository.GetByName(name);
-            }
+            return _memberGroupRepository.GetByName(name);
         }
+    }
 
-        public void Save(IMemberGroup memberGroup)
+    public void Save(IMemberGroup memberGroup)
+    {
+        if (string.IsNullOrWhiteSpace(memberGroup.Name))
         {
-            if (string.IsNullOrWhiteSpace(memberGroup.Name))
-            {
-                throw new InvalidOperationException("The name of a MemberGroup can not be empty");
-            }
+            throw new InvalidOperationException("The name of a MemberGroup can not be empty");
+        }
 
-            var evtMsgs = EventMessagesFactory.Get();
+        EventMessages evtMsgs = EventMessagesFactory.Get();
 
-            using (var scope = ScopeProvider.CreateCoreScope())
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            var savingNotification = new MemberGroupSavingNotification(memberGroup, evtMsgs);
+            if (scope.Notifications.PublishCancelable(savingNotification))
             {
-                var savingNotification = new MemberGroupSavingNotification(memberGroup, evtMsgs);
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
-
-                _memberGroupRepository.Save(memberGroup);
                 scope.Complete();
-
-                scope.Notifications.Publish(new MemberGroupSavedNotification(memberGroup, evtMsgs).WithStateFrom(savingNotification));
+                return;
             }
+
+            _memberGroupRepository.Save(memberGroup);
+            scope.Complete();
+
+            scope.Notifications.Publish(
+                new MemberGroupSavedNotification(memberGroup, evtMsgs).WithStateFrom(savingNotification));
         }
+    }
 
-        public void Delete(IMemberGroup memberGroup)
-        {
-            var evtMsgs = EventMessagesFactory.Get();
+    public void Delete(IMemberGroup memberGroup)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
 
-            using (var scope = ScopeProvider.CreateCoreScope())
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            var deletingNotification = new MemberGroupDeletingNotification(memberGroup, evtMsgs);
+            if (scope.Notifications.PublishCancelable(deletingNotification))
             {
-                var deletingNotification = new MemberGroupDeletingNotification(memberGroup, evtMsgs);
-                if (scope.Notifications.PublishCancelable(deletingNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
-
-                _memberGroupRepository.Delete(memberGroup);
                 scope.Complete();
-
-                scope.Notifications.Publish(new MemberGroupDeletedNotification(memberGroup, evtMsgs).WithStateFrom(deletingNotification));
+                return;
             }
+
+            _memberGroupRepository.Delete(memberGroup);
+            scope.Complete();
+
+            scope.Notifications.Publish(
+                new MemberGroupDeletedNotification(memberGroup, evtMsgs).WithStateFrom(deletingNotification));
         }
     }
 }
diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs
index 9d1b9edf9d32..a181fa99d1fe 100644
--- a/src/Umbraco.Core/Services/MemberService.cs
+++ b/src/Umbraco.Core/Services/MemberService.cs
@@ -1,6 +1,3 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
 using Microsoft.Extensions.Logging;
 using Umbraco.Cms.Core.Events;
 using Umbraco.Cms.Core.Models;
@@ -12,1225 +9,1389 @@
 using Umbraco.Cms.Core.Scoping;
 using Umbraco.Extensions;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+/// 
+///     Represents the MemberService.
+/// 
+public class MemberService : RepositoryService, IMemberService
 {
+    private readonly IAuditRepository _auditRepository;
+    private readonly IMemberGroupRepository _memberGroupRepository;
+
+    private readonly IMemberGroupService _memberGroupService;
+    private readonly IMemberRepository _memberRepository;
+    private readonly IMemberTypeRepository _memberTypeRepository;
+
+    #region Constructor
+
+    public MemberService(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
+        IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService,
+        IMemberRepository memberRepository, IMemberTypeRepository memberTypeRepository,
+        IMemberGroupRepository memberGroupRepository, IAuditRepository auditRepository)
+        : base(provider, loggerFactory, eventMessagesFactory)
+    {
+        _memberRepository = memberRepository;
+        _memberTypeRepository = memberTypeRepository;
+        _memberGroupRepository = memberGroupRepository;
+        _auditRepository = auditRepository;
+        _memberGroupService = memberGroupService ?? throw new ArgumentNullException(nameof(memberGroupService));
+    }
+
+    #endregion
+
+    #region Private Methods
+
+    private void Audit(AuditType type, int userId, int objectId, string? message = null) =>
+        _auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.Member.GetName(), message));
+
+    #endregion
+
+    #region Count
+
     /// 
-    /// Represents the MemberService.
+    ///     Gets the total number of Members based on the count type
     /// 
-    public class MemberService : RepositoryService, IMemberService
+    /// 
+    ///     The way the Online count is done is the same way that it is done in the MS SqlMembershipProvider - We query for any
+    ///     members
+    ///     that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact
+    ///     science
+    ///     but that is how MS have made theirs so we'll follow that principal.
+    /// 
+    ///  to count by
+    ///  with number of Members for passed in type
+    public int GetCount(MemberCountType countType)
     {
-        private readonly IMemberRepository _memberRepository;
-        private readonly IMemberTypeRepository _memberTypeRepository;
-        private readonly IMemberGroupRepository _memberGroupRepository;
-        private readonly IAuditRepository _auditRepository;
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            scope.ReadLock(Constants.Locks.MemberTree);
 
-        private readonly IMemberGroupService _memberGroupService;
+            IQuery? query;
 
-        #region Constructor
+            switch (countType)
+            {
+                case MemberCountType.All:
+                    query = Query();
+                    break;
+                case MemberCountType.LockedOut:
+                    query = Query()?.Where(x => x.IsLockedOut == true);
+                    break;
+                case MemberCountType.Approved:
+                    query = Query()?.Where(x => x.IsApproved == true);
+                    break;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(countType));
+            }
 
-        public MemberService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService,
-            IMemberRepository memberRepository, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, IAuditRepository auditRepository)
-            : base(provider, loggerFactory, eventMessagesFactory)
-        {
-            _memberRepository = memberRepository;
-            _memberTypeRepository = memberTypeRepository;
-            _memberGroupRepository = memberGroupRepository;
-            _auditRepository = auditRepository;
-            _memberGroupService = memberGroupService ?? throw new ArgumentNullException(nameof(memberGroupService));
+            return _memberRepository.GetCountByQuery(query);
         }
+    }
 
-        #endregion
-
-        #region Count
-
-        /// 
-        /// Gets the total number of Members based on the count type
-        /// 
-        /// 
-        /// The way the Online count is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members
-        /// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact science
-        /// but that is how MS have made theirs so we'll follow that principal.
-        /// 
-        ///  to count by
-        ///  with number of Members for passed in type
-        public int GetCount(MemberCountType countType)
+    /// 
+    ///     Gets the count of Members by an optional MemberType alias
+    /// 
+    /// If no alias is supplied then the count for all Member will be returned
+    /// Optional alias for the MemberType when counting number of Members
+    ///  with number of Members
+    public int Count(string? memberTypeAlias = null)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
+            scope.ReadLock(Constants.Locks.MemberTree);
+            return _memberRepository.Count(memberTypeAlias);
+        }
+    }
 
-                IQuery? query;
+    #endregion
 
-                switch (countType)
-                {
-                    case MemberCountType.All:
-                        query = Query();
-                        break;
-                    case MemberCountType.LockedOut:
-                        query = Query()?.Where(x => x.IsLockedOut == true);
-                        break;
-                    case MemberCountType.Approved:
-                        query = Query()?.Where(x => x.IsApproved == true);
-                        break;
-                    default:
-                        throw new ArgumentOutOfRangeException(nameof(countType));
-                }
+    #region Create
 
-                return _memberRepository.GetCountByQuery(query);
-            }
+    /// 
+    ///     Creates an  object without persisting it
+    /// 
+    /// 
+    ///     This method is convenient for when you need to add properties to a new Member
+    ///     before persisting it in order to limit the amount of times its saved.
+    ///     Also note that the returned  will not have an Id until its saved.
+    /// 
+    /// Username of the Member to create
+    /// Email of the Member to create
+    /// Name of the Member to create
+    /// Alias of the MemberType the Member should be based on
+    /// Thrown when a member type for the given alias isn't found
+    /// 
+    ///     
+    /// 
+    public IMember CreateMember(string username, string email, string name, string memberTypeAlias)
+    {
+        IMemberType memberType = GetMemberType(memberTypeAlias);
+        if (memberType == null)
+        {
+            throw new ArgumentException("No member type with that alias.", nameof(memberTypeAlias));
         }
 
-        /// 
-        /// Gets the count of Members by an optional MemberType alias
-        /// 
-        /// If no alias is supplied then the count for all Member will be returned
-        /// Optional alias for the MemberType when counting number of Members
-        ///  with number of Members
-        public int Count(string? memberTypeAlias = null)
+        var member = new Member(name, email.ToLower().Trim(), username, memberType, 0);
+
+        return member;
+    }
+
+    /// 
+    ///     Creates an  object without persisting it
+    /// 
+    /// 
+    ///     This method is convenient for when you need to add properties to a new Member
+    ///     before persisting it in order to limit the amount of times its saved.
+    ///     Also note that the returned  will not have an Id until its saved.
+    /// 
+    /// Username of the Member to create
+    /// Email of the Member to create
+    /// Name of the Member to create
+    /// MemberType the Member should be based on
+    /// 
+    ///     
+    /// 
+    public IMember CreateMember(string username, string email, string name, IMemberType memberType)
+    {
+        if (memberType == null)
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                return _memberRepository.Count(memberTypeAlias);
-            }
+            throw new ArgumentNullException(nameof(memberType));
         }
 
-        #endregion
-
-        #region Create
-
-        /// 
-        /// Creates an  object without persisting it
-        /// 
-        /// This method is convenient for when you need to add properties to a new Member
-        /// before persisting it in order to limit the amount of times its saved.
-        /// Also note that the returned  will not have an Id until its saved.
-        /// Username of the Member to create
-        /// Email of the Member to create
-        /// Name of the Member to create
-        /// Alias of the MemberType the Member should be based on
-        /// Thrown when a member type for the given alias isn't found
-        /// 
-        public IMember CreateMember(string username, string email, string name, string memberTypeAlias)
+        var member = new Member(name, email.ToLower().Trim(), username, memberType, 0);
+
+        return member;
+    }
+
+    /// 
+    ///     Creates and persists a new 
+    /// 
+    /// An  can be of type  or 
+    /// Username of the  to create
+    /// Email of the  to create
+    /// 
+    ///     This value should be the encoded/encrypted/hashed value for the password that will be
+    ///     stored in the database
+    /// 
+    /// Alias of the Type
+    /// Is the member approved
+    /// 
+    ///     
+    /// 
+    IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue,
+        string memberTypeAlias)
+        => CreateMemberWithIdentity(username, email, username, passwordValue, memberTypeAlias);
+
+    /// 
+    ///     Creates and persists a new 
+    /// 
+    /// An  can be of type  or 
+    /// Username of the  to create
+    /// Email of the  to create
+    /// 
+    ///     This value should be the encoded/encrypted/hashed value for the password that will be
+    ///     stored in the database
+    /// 
+    /// Alias of the Type
+    /// 
+    /// 
+    ///     
+    /// 
+    IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue,
+        string memberTypeAlias, bool isApproved)
+        => CreateMemberWithIdentity(username, email, username, passwordValue, memberTypeAlias, isApproved);
+
+    public IMember CreateMemberWithIdentity(string username, string email, string memberTypeAlias)
+        => CreateMemberWithIdentity(username, email, username, "", memberTypeAlias);
+
+    public IMember CreateMemberWithIdentity(string username, string email, string memberTypeAlias, bool isApproved)
+        => CreateMemberWithIdentity(username, email, username, "", memberTypeAlias, isApproved);
+
+    public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias)
+        => CreateMemberWithIdentity(username, email, name, "", memberTypeAlias);
+
+    public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias,
+        bool isApproved)
+        => CreateMemberWithIdentity(username, email, name, "", memberTypeAlias, isApproved);
+
+    /// 
+    ///     Creates and persists a Member
+    /// 
+    /// 
+    ///     Using this method will persist the Member object before its returned
+    ///     meaning that it will have an Id available (unlike the CreateMember method)
+    /// 
+    /// Username of the Member to create
+    /// Email of the Member to create
+    /// Name of the Member to create
+    /// Alias of the MemberType the Member should be based on
+    /// Optional IsApproved of the Member to create
+    /// 
+    ///     
+    /// 
+    public IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue,
+        string memberTypeAlias, bool isApproved = true)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            IMemberType memberType = GetMemberType(memberTypeAlias);
+            // locking the member tree secures member types too
+            scope.WriteLock(Constants.Locks.MemberTree);
+
+            IMemberType memberType = GetMemberType(scope, memberTypeAlias); // + locks // + locks
             if (memberType == null)
             {
-                throw new ArgumentException("No member type with that alias.", nameof(memberTypeAlias));
+                throw new ArgumentException("No member type with that alias.",
+                    nameof(memberTypeAlias)); // causes rollback // causes rollback
             }
 
-            var member = new Member(name, email.ToLower().Trim(), username, memberType, 0);
+            var member = new Member(name, email.ToLower().Trim(), username, passwordValue, memberType, isApproved, -1);
 
-            return member;
-        }
+            Save(member);
 
-        /// 
-        /// Creates an  object without persisting it
-        /// 
-        /// This method is convenient for when you need to add properties to a new Member
-        /// before persisting it in order to limit the amount of times its saved.
-        /// Also note that the returned  will not have an Id until its saved.
-        /// Username of the Member to create
-        /// Email of the Member to create
-        /// Name of the Member to create
-        /// MemberType the Member should be based on
-        /// 
-        public IMember CreateMember(string username, string email, string name, IMemberType memberType)
-        {
-            if (memberType == null) throw new ArgumentNullException(nameof(memberType));
-
-            var member = new Member(name, email.ToLower().Trim(), username, memberType, 0);
+            scope.Complete();
 
             return member;
         }
+    }
 
-        /// 
-        /// Creates and persists a new 
-        /// 
-        /// An  can be of type  or 
-        /// Username of the  to create
-        /// Email of the  to create
-        /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database
-        /// Alias of the Type
-        /// Is the member approved
-        /// 
-        IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias)
-            => CreateMemberWithIdentity(username, email, username, passwordValue, memberTypeAlias);
-
-        /// 
-        /// Creates and persists a new 
-        /// 
-        /// An  can be of type  or 
-        /// Username of the  to create
-        /// Email of the  to create
-        /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database
-        /// Alias of the Type
-        /// 
-        /// 
-        IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias, bool isApproved)
-            => CreateMemberWithIdentity(username, email, username, passwordValue, memberTypeAlias, isApproved);
-
-        public IMember CreateMemberWithIdentity(string username, string email, string memberTypeAlias)
-            => CreateMemberWithIdentity(username, email, username, "", memberTypeAlias);
-
-        public IMember CreateMemberWithIdentity(string username, string email, string memberTypeAlias, bool isApproved)
-            => CreateMemberWithIdentity(username, email, username, "", memberTypeAlias, isApproved);
-
-        public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias)
-            => CreateMemberWithIdentity(username, email, name, "", memberTypeAlias);
-
-        public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias, bool isApproved)
-            => CreateMemberWithIdentity(username, email, name, "", memberTypeAlias, isApproved);
-
-        /// 
-        /// Creates and persists a Member
-        /// 
-        /// Using this method will persist the Member object before its returned
-        /// meaning that it will have an Id available (unlike the CreateMember method)
-        /// Username of the Member to create
-        /// Email of the Member to create
-        /// Name of the Member to create
-        /// Alias of the MemberType the Member should be based on
-        /// Optional IsApproved of the Member to create
-        /// 
-        public IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, string memberTypeAlias, bool isApproved = true)
-        {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                // locking the member tree secures member types too
-                scope.WriteLock(Constants.Locks.MemberTree);
-
-                IMemberType memberType = GetMemberType(scope, memberTypeAlias); // + locks // + locks
-                if (memberType == null)
-                {
-                    throw new ArgumentException("No member type with that alias.", nameof(memberTypeAlias)); // causes rollback // causes rollback
-                }
-
-                var member = new Member(name, email.ToLower().Trim(), username, passwordValue, memberType, isApproved, -1);
+    public IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType)
+        => CreateMemberWithIdentity(username, email, username, "", memberType);
 
-                Save(member);
+    /// 
+    ///     Creates and persists a Member
+    /// 
+    /// 
+    ///     Using this method will persist the Member object before its returned
+    ///     meaning that it will have an Id available (unlike the CreateMember method)
+    /// 
+    /// Username of the Member to create
+    /// Email of the Member to create
+    /// MemberType the Member should be based on
+    /// 
+    ///     
+    /// 
+    public IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType, bool isApproved)
+        => CreateMemberWithIdentity(username, email, username, "", memberType, isApproved);
+
+    public IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType)
+        => CreateMemberWithIdentity(username, email, name, "", memberType);
 
-                scope.Complete();
+    /// 
+    ///     Creates and persists a Member
+    /// 
+    /// 
+    ///     Using this method will persist the Member object before its returned
+    ///     meaning that it will have an Id available (unlike the CreateMember method)
+    /// 
+    /// Username of the Member to create
+    /// Email of the Member to create
+    /// Name of the Member to create
+    /// MemberType the Member should be based on
+    /// 
+    ///     
+    /// 
+    public IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType,
+        bool isApproved)
+        => CreateMemberWithIdentity(username, email, name, "", memberType, isApproved);
 
-                return member;
-            }
+    /// 
+    ///     Creates and persists a Member
+    /// 
+    /// 
+    ///     Using this method will persist the Member object before its returned
+    ///     meaning that it will have an Id available (unlike the CreateMember method)
+    /// 
+    /// Username of the Member to create
+    /// Email of the Member to create
+    /// Name of the Member to create
+    /// 
+    ///     This value should be the encoded/encrypted/hashed value for the password that will be
+    ///     stored in the database
+    /// 
+    /// MemberType the Member should be based on
+    /// 
+    ///     
+    /// 
+    private IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue,
+        IMemberType memberType, bool isApproved = true)
+    {
+        if (memberType == null)
+        {
+            throw new ArgumentNullException(nameof(memberType));
         }
 
-        public IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType)
-            => CreateMemberWithIdentity(username, email, username, "", memberType);
-
-        /// 
-        /// Creates and persists a Member
-        /// 
-        /// Using this method will persist the Member object before its returned
-        /// meaning that it will have an Id available (unlike the CreateMember method)
-        /// Username of the Member to create
-        /// Email of the Member to create
-        /// MemberType the Member should be based on
-        /// 
-        public IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType, bool isApproved)
-            => CreateMemberWithIdentity(username, email, username, "", memberType, isApproved);
-
-        public IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType)
-            => CreateMemberWithIdentity(username, email, name, "", memberType);
-
-        /// 
-        /// Creates and persists a Member
-        /// 
-        /// Using this method will persist the Member object before its returned
-        /// meaning that it will have an Id available (unlike the CreateMember method)
-        /// Username of the Member to create
-        /// Email of the Member to create
-        /// Name of the Member to create
-        /// MemberType the Member should be based on
-        /// 
-        public IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType, bool isApproved)
-            => CreateMemberWithIdentity(username, email, name, "", memberType, isApproved);
-
-        /// 
-        /// Creates and persists a Member
-        /// 
-        /// Using this method will persist the Member object before its returned
-        /// meaning that it will have an Id available (unlike the CreateMember method)
-        /// Username of the Member to create
-        /// Email of the Member to create
-        /// Name of the Member to create
-        /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database
-        /// MemberType the Member should be based on
-        /// 
-        private IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, IMemberType memberType, bool isApproved = true)
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            if (memberType == null) throw new ArgumentNullException(nameof(memberType));
+            scope.WriteLock(Constants.Locks.MemberTree);
 
-            using (var scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(Constants.Locks.MemberTree);
-
-                // ensure it all still make sense
-                // ensure it all still make sense
-                var vrfy = GetMemberType(scope, memberType.Alias); // + locks
+            // ensure it all still make sense
+            // ensure it all still make sense
+            IMemberType vrfy = GetMemberType(scope, memberType.Alias); // + locks
 
-                if (vrfy == null || vrfy.Id != memberType.Id)
-                {
-                    throw new ArgumentException($"Member type with alias {memberType.Alias} does not exist or is a different member type."); // causes rollback
-                }
+            if (vrfy == null || vrfy.Id != memberType.Id)
+            {
+                throw new ArgumentException(
+                    $"Member type with alias {memberType.Alias} does not exist or is a different member type."); // causes rollback
+            }
 
-                var member = new Member(name, email.ToLower().Trim(), username, passwordValue, memberType, isApproved, -1);
+            var member = new Member(name, email.ToLower().Trim(), username, passwordValue, memberType, isApproved, -1);
 
-                Save(member);
+            Save(member);
 
-                scope.Complete();
+            scope.Complete();
 
-                return member;
-            }
+            return member;
         }
+    }
 
-        #endregion
+    #endregion
 
-        #region Get, Has, Is, Exists...
+    #region Get, Has, Is, Exists...
 
-        /// 
-        /// Gets a Member by its integer id
-        /// 
-        ///  Id
-        /// 
-        public IMember? GetById(int id)
+    /// 
+    ///     Gets a Member by its integer id
+    /// 
+    ///  Id
+    /// 
+    ///     
+    /// 
+    public IMember? GetById(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                return _memberRepository.Get(id);
-            }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            return _memberRepository.Get(id);
         }
+    }
 
-        /// 
-        /// Gets a Member by the unique key
-        /// 
-        /// The guid key corresponds to the unique id in the database
-        /// and the user id in the membership provider.
-        ///  Id
-        /// 
-        public IMember? GetByKey(Guid id)
+    /// 
+    ///     Gets a Member by the unique key
+    /// 
+    /// 
+    ///     The guid key corresponds to the unique id in the database
+    ///     and the user id in the membership provider.
+    /// 
+    ///  Id
+    /// 
+    ///     
+    /// 
+    public IMember? GetByKey(Guid id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                var query = Query().Where(x => x.Key == id);
-                return _memberRepository.Get(query)?.FirstOrDefault();
-            }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            IQuery query = Query().Where(x => x.Key == id);
+            return _memberRepository.Get(query)?.FirstOrDefault();
         }
+    }
 
-        /// 
-        /// Gets a list of paged  objects
-        /// 
-        /// Current page index
-        /// Size of the page
-        /// Total number of records found (out)
-        /// 
-        public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords)
+    /// 
+    ///     Gets a list of paged  objects
+    /// 
+    /// Current page index
+    /// Size of the page
+    /// Total number of records found (out)
+    /// 
+    ///     
+    /// 
+    public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                return _memberRepository.GetPage(null, pageIndex, pageSize, out totalRecords, null, Ordering.By("LoginName"));
-            }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            return _memberRepository.GetPage(null, pageIndex, pageSize, out totalRecords, null,
+                Ordering.By("LoginName"));
         }
+    }
+
+    public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords,
+        string orderBy, Direction orderDirection, string? memberTypeAlias = null, string filter = "") =>
+        GetAll(pageIndex, pageSize, out totalRecords, orderBy, orderDirection, true, memberTypeAlias, filter);
 
-        public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords,
-            string orderBy, Direction orderDirection, string? memberTypeAlias = null, string filter = "")
+    public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords,
+        string orderBy, Direction orderDirection, bool orderBySystemField, string? memberTypeAlias, string filter)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            return GetAll(pageIndex, pageSize, out totalRecords, orderBy, orderDirection, true, memberTypeAlias, filter);
+            scope.ReadLock(Constants.Locks.MemberTree);
+            IQuery query1 = memberTypeAlias == null
+                ? null
+                : Query()?.Where(x => x.ContentTypeAlias == memberTypeAlias);
+            IQuery query2 = filter == null
+                ? null
+                : Query()?.Where(x =>
+                    (x.Name != null && x.Name.Contains(filter)) || x.Username.Contains(filter) ||
+                    x.Email.Contains(filter));
+            return _memberRepository.GetPage(query1, pageIndex, pageSize, out totalRecords, query2,
+                Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField));
         }
+    }
 
-        public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords,
-            string orderBy, Direction orderDirection, bool orderBySystemField, string? memberTypeAlias, string filter)
+    /// 
+    ///     Gets an  by its provider key
+    /// 
+    /// Id to use for retrieval
+    /// 
+    ///     
+    /// 
+    public IMember? GetByProviderKey(object id)
+    {
+        Attempt asGuid = id.TryConvertTo();
+        if (asGuid.Success)
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                var query1 = memberTypeAlias == null ? null : Query()?.Where(x => x.ContentTypeAlias == memberTypeAlias);
-                var query2 = filter == null ? null : Query()?.Where(x => (x.Name != null && x.Name.Contains(filter)) || x.Username.Contains(filter) || x.Email.Contains(filter));
-                return _memberRepository.GetPage(query1, pageIndex, pageSize, out totalRecords, query2, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField));
-            }
+            return GetByKey(asGuid.Result);
         }
 
-        /// 
-        /// Gets an  by its provider key
-        /// 
-        /// Id to use for retrieval
-        /// 
-        public IMember? GetByProviderKey(object id)
+        Attempt asInt = id.TryConvertTo();
+        if (asInt.Success)
         {
-            var asGuid = id.TryConvertTo();
-            if (asGuid.Success)
-                return GetByKey(asGuid.Result);
-
-            var asInt = id.TryConvertTo();
-            if (asInt.Success)
-                return GetById(asInt.Result);
-
-            return null;
+            return GetById(asInt.Result);
         }
 
-        /// 
-        /// Get an  by email
-        /// 
-        /// Email to use for retrieval
-        /// 
-        public IMember? GetByEmail(string email)
+        return null;
+    }
+
+    /// 
+    ///     Get an  by email
+    /// 
+    /// Email to use for retrieval
+    /// 
+    ///     
+    /// 
+    public IMember? GetByEmail(string email)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                var query = Query().Where(x => x.Email.Equals(email));
-                return _memberRepository.Get(query)?.FirstOrDefault();
-            }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            IQuery query = Query().Where(x => x.Email.Equals(email));
+            return _memberRepository.Get(query)?.FirstOrDefault();
         }
+    }
 
-        /// 
-        /// Get an  by username
-        /// 
-        /// Username to use for retrieval
-        /// 
-        public IMember? GetByUsername(string? username)
+    /// 
+    ///     Get an  by username
+    /// 
+    /// Username to use for retrieval
+    /// 
+    ///     
+    /// 
+    public IMember? GetByUsername(string? username)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                return _memberRepository.GetByUsername(username);
-            }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            return _memberRepository.GetByUsername(username);
         }
+    }
 
-        /// 
-        /// Gets all Members for the specified MemberType alias
-        /// 
-        /// Alias of the MemberType
-        /// 
-        public IEnumerable? GetMembersByMemberType(string memberTypeAlias)
+    /// 
+    ///     Gets all Members for the specified MemberType alias
+    /// 
+    /// Alias of the MemberType
+    /// 
+    ///     
+    /// 
+    public IEnumerable? GetMembersByMemberType(string memberTypeAlias)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                var query = Query().Where(x => x.ContentTypeAlias == memberTypeAlias);
-                return _memberRepository.Get(query);
-            }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            IQuery query = Query().Where(x => x.ContentTypeAlias == memberTypeAlias);
+            return _memberRepository.Get(query);
         }
+    }
 
-        /// 
-        /// Gets all Members for the MemberType id
-        /// 
-        /// Id of the MemberType
-        /// 
-        public IEnumerable? GetMembersByMemberType(int memberTypeId)
+    /// 
+    ///     Gets all Members for the MemberType id
+    /// 
+    /// Id of the MemberType
+    /// 
+    ///     
+    /// 
+    public IEnumerable? GetMembersByMemberType(int memberTypeId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                var query = Query().Where(x => x.ContentTypeId == memberTypeId);
-                return _memberRepository.Get(query);
-            }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            IQuery query = Query().Where(x => x.ContentTypeId == memberTypeId);
+            return _memberRepository.Get(query);
         }
+    }
 
-        /// 
-        /// Gets all Members within the specified MemberGroup name
-        /// 
-        /// Name of the MemberGroup
-        /// 
-        public IEnumerable GetMembersByGroup(string memberGroupName)
+    /// 
+    ///     Gets all Members within the specified MemberGroup name
+    /// 
+    /// Name of the MemberGroup
+    /// 
+    ///     
+    /// 
+    public IEnumerable GetMembersByGroup(string memberGroupName)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                return _memberRepository.GetByMemberGroup(memberGroupName);
-            }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            return _memberRepository.GetByMemberGroup(memberGroupName);
         }
+    }
 
-        /// 
-        /// Gets all Members with the ids specified
-        /// 
-        /// If no Ids are specified all Members will be retrieved
-        /// Optional list of Member Ids
-        /// 
-        public IEnumerable GetAllMembers(params int[] ids)
+    /// 
+    ///     Gets all Members with the ids specified
+    /// 
+    /// If no Ids are specified all Members will be retrieved
+    /// Optional list of Member Ids
+    /// 
+    ///     
+    /// 
+    public IEnumerable GetAllMembers(params int[] ids)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                return _memberRepository.GetMany(ids);
-            }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            return _memberRepository.GetMany(ids);
         }
+    }
 
-        /// 
-        /// Finds Members based on their display name
-        /// 
-        /// Display name to match
-        /// Current page index
-        /// Size of the page
-        /// Total number of records found (out)
-        /// The type of match to make as . Default is 
-        /// 
-        public IEnumerable FindMembersByDisplayName(string displayNameToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith)
+    /// 
+    ///     Finds Members based on their display name
+    /// 
+    /// Display name to match
+    /// Current page index
+    /// Size of the page
+    /// Total number of records found (out)
+    /// 
+    ///     The type of match to make as . Default is
+    ///     
+    /// 
+    /// 
+    ///     
+    /// 
+    public IEnumerable FindMembersByDisplayName(string displayNameToMatch, long pageIndex, int pageSize,
+        out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                var query = Query();
-
-                switch (matchType)
-                {
-                    case StringPropertyMatchType.Exact:
-                        query?.Where(member => string.Equals(member.Name, displayNameToMatch));
-                        break;
-                    case StringPropertyMatchType.Contains:
-                        query?.Where(member => member.Name != null && member.Name.Contains(displayNameToMatch));
-                        break;
-                    case StringPropertyMatchType.StartsWith:
-                        query?.Where(member => member.Name != null && member.Name.StartsWith(displayNameToMatch));
-                        break;
-                    case StringPropertyMatchType.EndsWith:
-                        query?.Where(member => member.Name != null && member.Name.EndsWith(displayNameToMatch));
-                        break;
-                    case StringPropertyMatchType.Wildcard:
-                        query?.Where(member => member.Name != null && member.Name.SqlWildcard(displayNameToMatch, TextColumnType.NVarchar));
-                        break;
-                    default:
-                        throw new ArgumentOutOfRangeException(nameof(matchType)); // causes rollback // causes rollback
-                }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            IQuery query = Query();
 
-                return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, null, Ordering.By("Name"));
+            switch (matchType)
+            {
+                case StringPropertyMatchType.Exact:
+                    query?.Where(member => string.Equals(member.Name, displayNameToMatch));
+                    break;
+                case StringPropertyMatchType.Contains:
+                    query?.Where(member => member.Name != null && member.Name.Contains(displayNameToMatch));
+                    break;
+                case StringPropertyMatchType.StartsWith:
+                    query?.Where(member => member.Name != null && member.Name.StartsWith(displayNameToMatch));
+                    break;
+                case StringPropertyMatchType.EndsWith:
+                    query?.Where(member => member.Name != null && member.Name.EndsWith(displayNameToMatch));
+                    break;
+                case StringPropertyMatchType.Wildcard:
+                    query?.Where(member =>
+                        member.Name != null && member.Name.SqlWildcard(displayNameToMatch, TextColumnType.NVarchar));
+                    break;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(matchType)); // causes rollback // causes rollback
             }
+
+            return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, null, Ordering.By("Name"));
         }
+    }
 
-        /// 
-        /// Finds a list of  objects by a partial email string
-        /// 
-        /// Partial email string to match
-        /// Current page index
-        /// Size of the page
-        /// Total number of records found (out)
-        /// The type of match to make as . Default is 
-        /// 
-        public IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith)
+    /// 
+    ///     Finds a list of  objects by a partial email string
+    /// 
+    /// Partial email string to match
+    /// Current page index
+    /// Size of the page
+    /// Total number of records found (out)
+    /// 
+    ///     The type of match to make as . Default is
+    ///     
+    /// 
+    /// 
+    ///     
+    /// 
+    public IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSize,
+        out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                var query = Query();
-
-                switch (matchType)
-                {
-                    case StringPropertyMatchType.Exact:
-                        query?.Where(member => member.Email.Equals(emailStringToMatch));
-                        break;
-                    case StringPropertyMatchType.Contains:
-                        query?.Where(member => member.Email.Contains(emailStringToMatch));
-                        break;
-                    case StringPropertyMatchType.StartsWith:
-                        query?.Where(member => member.Email.StartsWith(emailStringToMatch));
-                        break;
-                    case StringPropertyMatchType.EndsWith:
-                        query?.Where(member => member.Email.EndsWith(emailStringToMatch));
-                        break;
-                    case StringPropertyMatchType.Wildcard:
-                        query?.Where(member => member.Email.SqlWildcard(emailStringToMatch, TextColumnType.NVarchar));
-                        break;
-                    default:
-                        throw new ArgumentOutOfRangeException(nameof(matchType));
-                }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            IQuery query = Query();
 
-                return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, null, Ordering.By("Email"));
+            switch (matchType)
+            {
+                case StringPropertyMatchType.Exact:
+                    query?.Where(member => member.Email.Equals(emailStringToMatch));
+                    break;
+                case StringPropertyMatchType.Contains:
+                    query?.Where(member => member.Email.Contains(emailStringToMatch));
+                    break;
+                case StringPropertyMatchType.StartsWith:
+                    query?.Where(member => member.Email.StartsWith(emailStringToMatch));
+                    break;
+                case StringPropertyMatchType.EndsWith:
+                    query?.Where(member => member.Email.EndsWith(emailStringToMatch));
+                    break;
+                case StringPropertyMatchType.Wildcard:
+                    query?.Where(member => member.Email.SqlWildcard(emailStringToMatch, TextColumnType.NVarchar));
+                    break;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(matchType));
             }
+
+            return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, null, Ordering.By("Email"));
         }
+    }
 
-        /// 
-        /// Finds a list of  objects by a partial username
-        /// 
-        /// Partial username to match
-        /// Current page index
-        /// Size of the page
-        /// Total number of records found (out)
-        /// The type of match to make as . Default is 
-        /// 
-        public IEnumerable FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith)
+    /// 
+    ///     Finds a list of  objects by a partial username
+    /// 
+    /// Partial username to match
+    /// Current page index
+    /// Size of the page
+    /// Total number of records found (out)
+    /// 
+    ///     The type of match to make as . Default is
+    ///     
+    /// 
+    /// 
+    ///     
+    /// 
+    public IEnumerable FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords,
+        StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                var query = Query();
-
-                switch (matchType)
-                {
-                    case StringPropertyMatchType.Exact:
-                        query?.Where(member => member.Username.Equals(login));
-                        break;
-                    case StringPropertyMatchType.Contains:
-                        query?.Where(member => member.Username.Contains(login));
-                        break;
-                    case StringPropertyMatchType.StartsWith:
-                        query?.Where(member => member.Username.StartsWith(login));
-                        break;
-                    case StringPropertyMatchType.EndsWith:
-                        query?.Where(member => member.Username.EndsWith(login));
-                        break;
-                    case StringPropertyMatchType.Wildcard:
-                        query?.Where(member => member.Username.SqlWildcard(login, TextColumnType.NVarchar));
-                        break;
-                    default:
-                        throw new ArgumentOutOfRangeException(nameof(matchType));
-                }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            IQuery query = Query();
 
-                return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, null, Ordering.By("LoginName"));
+            switch (matchType)
+            {
+                case StringPropertyMatchType.Exact:
+                    query?.Where(member => member.Username.Equals(login));
+                    break;
+                case StringPropertyMatchType.Contains:
+                    query?.Where(member => member.Username.Contains(login));
+                    break;
+                case StringPropertyMatchType.StartsWith:
+                    query?.Where(member => member.Username.StartsWith(login));
+                    break;
+                case StringPropertyMatchType.EndsWith:
+                    query?.Where(member => member.Username.EndsWith(login));
+                    break;
+                case StringPropertyMatchType.Wildcard:
+                    query?.Where(member => member.Username.SqlWildcard(login, TextColumnType.NVarchar));
+                    break;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(matchType));
             }
+
+            return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, null,
+                Ordering.By("LoginName"));
         }
+    }
 
-        /// 
-        /// Gets a list of Members based on a property search
-        /// 
-        /// Alias of the PropertyType to search for
-        ///  Value to match
-        /// The type of match to make as . Default is 
-        /// 
-        public IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact)
+    /// 
+    ///     Gets a list of Members based on a property search
+    /// 
+    /// Alias of the PropertyType to search for
+    ///  Value to match
+    /// 
+    ///     The type of match to make as . Default is
+    ///     
+    /// 
+    /// 
+    ///     
+    /// 
+    public IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, string value,
+        StringPropertyMatchType matchType = StringPropertyMatchType.Exact)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                IQuery query;
+            scope.ReadLock(Constants.Locks.MemberTree);
+            IQuery query;
 
-                switch (matchType)
-                {
-                    case StringPropertyMatchType.Exact:
-                        query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && (((Member) x).LongStringPropertyValue!.SqlEquals(value, TextColumnType.NText) || ((Member) x).ShortStringPropertyValue!.SqlEquals(value, TextColumnType.NVarchar)));
-                        break;
-                    case StringPropertyMatchType.Contains:
-                        query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && (((Member) x).LongStringPropertyValue!.SqlContains(value, TextColumnType.NText) || ((Member) x).ShortStringPropertyValue!.SqlContains(value, TextColumnType.NVarchar)));
-                        break;
-                    case StringPropertyMatchType.StartsWith:
-                        query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && (((Member) x).LongStringPropertyValue.SqlStartsWith(value, TextColumnType.NText) || ((Member) x).ShortStringPropertyValue.SqlStartsWith(value, TextColumnType.NVarchar)));
-                        break;
-                    case StringPropertyMatchType.EndsWith:
-                        query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && (((Member) x).LongStringPropertyValue!.SqlEndsWith(value, TextColumnType.NText) || ((Member) x).ShortStringPropertyValue!.SqlEndsWith(value, TextColumnType.NVarchar)));
-                        break;
-                    default:
-                        throw new ArgumentOutOfRangeException(nameof(matchType));
-                }
-
-                return _memberRepository.Get(query);
+            switch (matchType)
+            {
+                case StringPropertyMatchType.Exact:
+                    query = Query().Where(x =>
+                        ((Member)x).PropertyTypeAlias == propertyTypeAlias &&
+                        (((Member)x).LongStringPropertyValue!.SqlEquals(value, TextColumnType.NText) ||
+                         ((Member)x).ShortStringPropertyValue!.SqlEquals(value, TextColumnType.NVarchar)));
+                    break;
+                case StringPropertyMatchType.Contains:
+                    query = Query().Where(x =>
+                        ((Member)x).PropertyTypeAlias == propertyTypeAlias &&
+                        (((Member)x).LongStringPropertyValue!.SqlContains(value, TextColumnType.NText) ||
+                         ((Member)x).ShortStringPropertyValue!.SqlContains(value, TextColumnType.NVarchar)));
+                    break;
+                case StringPropertyMatchType.StartsWith:
+                    query = Query().Where(x =>
+                        ((Member)x).PropertyTypeAlias == propertyTypeAlias &&
+                        (((Member)x).LongStringPropertyValue.SqlStartsWith(value, TextColumnType.NText) ||
+                         ((Member)x).ShortStringPropertyValue.SqlStartsWith(value, TextColumnType.NVarchar)));
+                    break;
+                case StringPropertyMatchType.EndsWith:
+                    query = Query().Where(x =>
+                        ((Member)x).PropertyTypeAlias == propertyTypeAlias &&
+                        (((Member)x).LongStringPropertyValue!.SqlEndsWith(value, TextColumnType.NText) ||
+                         ((Member)x).ShortStringPropertyValue!.SqlEndsWith(value, TextColumnType.NVarchar)));
+                    break;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(matchType));
             }
+
+            return _memberRepository.Get(query);
         }
+    }
 
-        /// 
-        /// Gets a list of Members based on a property search
-        /// 
-        /// Alias of the PropertyType to search for
-        ///  Value to match
-        /// The type of match to make as . Default is 
-        /// 
-        public IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact)
+    /// 
+    ///     Gets a list of Members based on a property search
+    /// 
+    /// Alias of the PropertyType to search for
+    ///  Value to match
+    /// 
+    ///     The type of match to make as . Default is
+    ///     
+    /// 
+    /// 
+    ///     
+    /// 
+    public IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, int value,
+        ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                IQuery query;
-
-                switch (matchType)
-                {
-                    case ValuePropertyMatchType.Exact:
-                        query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).IntegerPropertyValue == value);
-                        break;
-                    case ValuePropertyMatchType.GreaterThan:
-                        query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).IntegerPropertyValue > value);
-                        break;
-                    case ValuePropertyMatchType.LessThan:
-                        query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).IntegerPropertyValue < value);
-                        break;
-                    case ValuePropertyMatchType.GreaterThanOrEqualTo:
-                        query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).IntegerPropertyValue >= value);
-                        break;
-                    case ValuePropertyMatchType.LessThanOrEqualTo:
-                        query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).IntegerPropertyValue <= value);
-                        break;
-                    default:
-                        throw new ArgumentOutOfRangeException(nameof(matchType));
-                }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            IQuery query;
 
-                return _memberRepository.Get(query);
+            switch (matchType)
+            {
+                case ValuePropertyMatchType.Exact:
+                    query = Query().Where(x =>
+                        ((Member)x).PropertyTypeAlias == propertyTypeAlias &&
+                        ((Member)x).IntegerPropertyValue == value);
+                    break;
+                case ValuePropertyMatchType.GreaterThan:
+                    query = Query().Where(x =>
+                        ((Member)x).PropertyTypeAlias == propertyTypeAlias && ((Member)x).IntegerPropertyValue > value);
+                    break;
+                case ValuePropertyMatchType.LessThan:
+                    query = Query().Where(x =>
+                        ((Member)x).PropertyTypeAlias == propertyTypeAlias && ((Member)x).IntegerPropertyValue < value);
+                    break;
+                case ValuePropertyMatchType.GreaterThanOrEqualTo:
+                    query = Query().Where(x =>
+                        ((Member)x).PropertyTypeAlias == propertyTypeAlias &&
+                        ((Member)x).IntegerPropertyValue >= value);
+                    break;
+                case ValuePropertyMatchType.LessThanOrEqualTo:
+                    query = Query().Where(x =>
+                        ((Member)x).PropertyTypeAlias == propertyTypeAlias &&
+                        ((Member)x).IntegerPropertyValue <= value);
+                    break;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(matchType));
             }
+
+            return _memberRepository.Get(query);
         }
+    }
 
-        /// 
-        /// Gets a list of Members based on a property search
-        /// 
-        /// Alias of the PropertyType to search for
-        ///  Value to match
-        /// 
-        public IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, bool value)
+    /// 
+    ///     Gets a list of Members based on a property search
+    /// 
+    /// Alias of the PropertyType to search for
+    ///  Value to match
+    /// 
+    ///     
+    /// 
+    public IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, bool value)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                var query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).BoolPropertyValue == value);
+            scope.ReadLock(Constants.Locks.MemberTree);
+            IQuery query = Query().Where(x =>
+                ((Member)x).PropertyTypeAlias == propertyTypeAlias && ((Member)x).BoolPropertyValue == value);
 
-                return _memberRepository.Get(query);
-            }
+            return _memberRepository.Get(query);
         }
+    }
 
-        /// 
-        /// Gets a list of Members based on a property search
-        /// 
-        /// Alias of the PropertyType to search for
-        ///  Value to match
-        /// The type of match to make as . Default is 
-        /// 
-        public IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact)
+    /// 
+    ///     Gets a list of Members based on a property search
+    /// 
+    /// Alias of the PropertyType to search for
+    ///  Value to match
+    /// 
+    ///     The type of match to make as . Default is
+    ///     
+    /// 
+    /// 
+    ///     
+    /// 
+    public IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, DateTime value,
+        ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                IQuery query;
+            scope.ReadLock(Constants.Locks.MemberTree);
+            IQuery query;
 
-                switch (matchType)
-                {
-                    case ValuePropertyMatchType.Exact:
-                        query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).DateTimePropertyValue == value);
-                        break;
-                    case ValuePropertyMatchType.GreaterThan:
-                        query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).DateTimePropertyValue > value);
-                        break;
-                    case ValuePropertyMatchType.LessThan:
-                        query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).DateTimePropertyValue < value);
-                        break;
-                    case ValuePropertyMatchType.GreaterThanOrEqualTo:
-                        query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).DateTimePropertyValue >= value);
-                        break;
-                    case ValuePropertyMatchType.LessThanOrEqualTo:
-                        query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).DateTimePropertyValue <= value);
-                        break;
-                    default:
-                        throw new ArgumentOutOfRangeException(nameof(matchType)); // causes rollback // causes rollback
-                }
-
-                // TODO: Since this is by property value, we need a GetByPropertyQuery on the repo!
-                return _memberRepository.Get(query);
+            switch (matchType)
+            {
+                case ValuePropertyMatchType.Exact:
+                    query = Query().Where(x =>
+                        ((Member)x).PropertyTypeAlias == propertyTypeAlias &&
+                        ((Member)x).DateTimePropertyValue == value);
+                    break;
+                case ValuePropertyMatchType.GreaterThan:
+                    query = Query().Where(x =>
+                        ((Member)x).PropertyTypeAlias == propertyTypeAlias &&
+                        ((Member)x).DateTimePropertyValue > value);
+                    break;
+                case ValuePropertyMatchType.LessThan:
+                    query = Query().Where(x =>
+                        ((Member)x).PropertyTypeAlias == propertyTypeAlias &&
+                        ((Member)x).DateTimePropertyValue < value);
+                    break;
+                case ValuePropertyMatchType.GreaterThanOrEqualTo:
+                    query = Query().Where(x =>
+                        ((Member)x).PropertyTypeAlias == propertyTypeAlias &&
+                        ((Member)x).DateTimePropertyValue >= value);
+                    break;
+                case ValuePropertyMatchType.LessThanOrEqualTo:
+                    query = Query().Where(x =>
+                        ((Member)x).PropertyTypeAlias == propertyTypeAlias &&
+                        ((Member)x).DateTimePropertyValue <= value);
+                    break;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(matchType)); // causes rollback // causes rollback
             }
+
+            // TODO: Since this is by property value, we need a GetByPropertyQuery on the repo!
+            return _memberRepository.Get(query);
         }
+    }
 
-        /// 
-        /// Checks if a Member with the id exists
-        /// 
-        /// Id of the Member
-        /// True if the Member exists otherwise False
-        public bool Exists(int id)
+    /// 
+    ///     Checks if a Member with the id exists
+    /// 
+    /// Id of the Member
+    /// True if the Member exists otherwise False
+    public bool Exists(int id)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                return _memberRepository.Exists(id);
-            }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            return _memberRepository.Exists(id);
         }
+    }
 
-        /// 
-        /// Checks if a Member with the username exists
-        /// 
-        /// Username to check
-        /// True if the Member exists otherwise False
-        public bool Exists(string username)
+    /// 
+    ///     Checks if a Member with the username exists
+    /// 
+    /// Username to check
+    /// True if the Member exists otherwise False
+    public bool Exists(string username)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                return _memberRepository.Exists(username);
-            }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            return _memberRepository.Exists(username);
         }
+    }
 
-        #endregion
+    #endregion
 
-        #region Save
+    #region Save
 
-        /// 
-        [Obsolete("This is now a NoOp since last login date is no longer an umbraco property, set the date on the IMember directly and Save it instead, scheduled for removal in V11.")]
-        public void SetLastLogin(string username, DateTime date)
-        {
-        }
+    /// 
+    [Obsolete(
+        "This is now a NoOp since last login date is no longer an umbraco property, set the date on the IMember directly and Save it instead, scheduled for removal in V11.")]
+    public void SetLastLogin(string username, DateTime date)
+    {
+    }
 
-        /// 
-        public void Save(IMember member)
-        {
-            // trimming username and email to make sure we have no trailing space
-            member.Username = member.Username.Trim();
-            member.Email = member.Email.Trim();
+    /// 
+    public void Save(IMember member)
+    {
+        // trimming username and email to make sure we have no trailing space
+        member.Username = member.Username.Trim();
+        member.Email = member.Email.Trim();
 
-            var evtMsgs = EventMessagesFactory.Get();
+        EventMessages evtMsgs = EventMessagesFactory.Get();
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            var savingNotification = new MemberSavingNotification(member, evtMsgs);
+            if (scope.Notifications.PublishCancelable(savingNotification))
             {
-                var savingNotification = new MemberSavingNotification(member, evtMsgs);
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
+                scope.Complete();
+                return;
+            }
 
-                if (string.IsNullOrWhiteSpace(member.Name))
-                {
-                    throw new ArgumentException("Cannot save member with empty name.");
-                }
+            if (string.IsNullOrWhiteSpace(member.Name))
+            {
+                throw new ArgumentException("Cannot save member with empty name.");
+            }
 
-                scope.WriteLock(Constants.Locks.MemberTree);
+            scope.WriteLock(Constants.Locks.MemberTree);
 
-                _memberRepository.Save(member);
+            _memberRepository.Save(member);
 
-                scope.Notifications.Publish(new MemberSavedNotification(member, evtMsgs).WithStateFrom(savingNotification));
+            scope.Notifications.Publish(new MemberSavedNotification(member, evtMsgs).WithStateFrom(savingNotification));
 
-                Audit(AuditType.Save, 0, member.Id);
+            Audit(AuditType.Save, 0, member.Id);
 
-                scope.Complete();
-            }
+            scope.Complete();
         }
+    }
 
-        /// 
-        public void Save(IEnumerable members)
-        {
-            var membersA = members.ToArray();
+    /// 
+    public void Save(IEnumerable members)
+    {
+        IMember[] membersA = members.ToArray();
 
-            var evtMsgs = EventMessagesFactory.Get();
+        EventMessages evtMsgs = EventMessagesFactory.Get();
 
-            using (var scope = ScopeProvider.CreateCoreScope())
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            var savingNotification = new MemberSavingNotification(membersA, evtMsgs);
+            if (scope.Notifications.PublishCancelable(savingNotification))
             {
-                var savingNotification = new MemberSavingNotification(membersA, evtMsgs);
-                if (scope.Notifications.PublishCancelable(savingNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
+                scope.Complete();
+                return;
+            }
 
-                scope.WriteLock(Constants.Locks.MemberTree);
+            scope.WriteLock(Constants.Locks.MemberTree);
 
-                foreach (var member in membersA)
-                {
-                    //trimming username and email to make sure we have no trailing space
-                    member.Username = member.Username.Trim();
-                    member.Email = member.Email.Trim();
+            foreach (IMember member in membersA)
+            {
+                //trimming username and email to make sure we have no trailing space
+                member.Username = member.Username.Trim();
+                member.Email = member.Email.Trim();
 
-                    _memberRepository.Save(member);
-                }
+                _memberRepository.Save(member);
+            }
 
-                scope.Notifications.Publish(new MemberSavedNotification(membersA, evtMsgs).WithStateFrom(savingNotification));
+            scope.Notifications.Publish(
+                new MemberSavedNotification(membersA, evtMsgs).WithStateFrom(savingNotification));
 
-                Audit(AuditType.Save, 0, -1, "Save multiple Members");
+            Audit(AuditType.Save, 0, -1, "Save multiple Members");
 
-                scope.Complete();
-            }
+            scope.Complete();
         }
+    }
 
-        #endregion
+    #endregion
 
-        #region Delete
+    #region Delete
 
-        /// 
-        /// Deletes an 
-        /// 
-        ///  to Delete
-        public void Delete(IMember member)
-        {
-            var evtMsgs = EventMessagesFactory.Get();
+    /// 
+    ///     Deletes an 
+    /// 
+    ///  to Delete
+    public void Delete(IMember member)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
 
-            using (var scope = ScopeProvider.CreateCoreScope())
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            var deletingNotification = new MemberDeletingNotification(member, evtMsgs);
+            if (scope.Notifications.PublishCancelable(deletingNotification))
             {
-                var deletingNotification = new MemberDeletingNotification(member, evtMsgs);
-                if (scope.Notifications.PublishCancelable(deletingNotification))
-                {
-                    scope.Complete();
-                    return;
-                }
-
-                scope.WriteLock(Constants.Locks.MemberTree);
-                DeleteLocked(scope, member, evtMsgs, deletingNotification.State);
-
-                Audit(AuditType.Delete, 0, member.Id);
                 scope.Complete();
+                return;
             }
-        }
 
-        private void DeleteLocked(ICoreScope scope, IMember member, EventMessages evtMsgs, IDictionary? notificationState = null)
-        {
-            // a member has no descendants
-            _memberRepository.Delete(member);
-            scope.Notifications.Publish(new MemberDeletedNotification(member, evtMsgs).WithState(notificationState));
+            scope.WriteLock(Constants.Locks.MemberTree);
+            DeleteLocked(scope, member, evtMsgs, deletingNotification.State);
 
-            // media files deleted by QueuingEventDispatcher
+            Audit(AuditType.Delete, 0, member.Id);
+            scope.Complete();
         }
+    }
+
+    private void DeleteLocked(ICoreScope scope, IMember member, EventMessages evtMsgs,
+        IDictionary? notificationState = null)
+    {
+        // a member has no descendants
+        _memberRepository.Delete(member);
+        scope.Notifications.Publish(new MemberDeletedNotification(member, evtMsgs).WithState(notificationState));
+
+        // media files deleted by QueuingEventDispatcher
+    }
 
-        #endregion
+    #endregion
 
-        #region Roles
+    #region Roles
 
-        public void AddRole(string roleName)
+    public void AddRole(string roleName)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (var scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(Constants.Locks.MemberTree);
-                _memberGroupRepository.CreateIfNotExists(roleName);
-                scope.Complete();
-            }
+            scope.WriteLock(Constants.Locks.MemberTree);
+            _memberGroupRepository.CreateIfNotExists(roleName);
+            scope.Complete();
         }
+    }
 
-        /// 
-        /// Returns a list of all member roles
-        /// 
-        /// A list of member roles
-
-        public IEnumerable GetAllRoles()
+    /// 
+    ///     Returns a list of all member roles
+    /// 
+    /// A list of member roles
+    public IEnumerable GetAllRoles()
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                return _memberGroupRepository.GetMany().Distinct();
-            }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            return _memberGroupRepository.GetMany().Distinct();
         }
+    }
 
-        /// 
-        /// Returns a list of all member roles for a given member ID
-        /// 
-        /// 
-        /// A list of member roles
-        public IEnumerable? GetAllRoles(int memberId)
+    /// 
+    ///     Returns a list of all member roles for a given member ID
+    /// 
+    /// 
+    /// A list of member roles
+    public IEnumerable? GetAllRoles(int memberId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                var result = _memberGroupRepository.GetMemberGroupsForMember(memberId);
-                return result.Select(x => x.Name).Distinct();
-            }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            IEnumerable result = _memberGroupRepository.GetMemberGroupsForMember(memberId);
+            return result.Select(x => x.Name).Distinct();
         }
+    }
 
-        public IEnumerable GetAllRoles(string? username)
+    public IEnumerable GetAllRoles(string? username)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                IEnumerable result = _memberGroupRepository.GetMemberGroupsForMember(username);
-                return result.Where(x => x.Name != null).Select(x => x.Name).Distinct()!;
-            }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            IEnumerable result = _memberGroupRepository.GetMemberGroupsForMember(username);
+            return result.Where(x => x.Name != null).Select(x => x.Name).Distinct()!;
         }
+    }
 
-        public IEnumerable GetAllRolesIds()
+    public IEnumerable GetAllRolesIds()
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                return _memberGroupRepository.GetMany().Select(x => x.Id).Distinct();
-            }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            return _memberGroupRepository.GetMany().Select(x => x.Id).Distinct();
         }
+    }
 
-        public IEnumerable GetAllRolesIds(int memberId)
+    public IEnumerable GetAllRolesIds(int memberId)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                IEnumerable result = _memberGroupRepository.GetMemberGroupsForMember(memberId);
-                return result.Select(x => x.Id).Distinct();
-            }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            IEnumerable result = _memberGroupRepository.GetMemberGroupsForMember(memberId);
+            return result.Select(x => x.Id).Distinct();
         }
+    }
 
-        public IEnumerable GetAllRolesIds(string username)
+    public IEnumerable GetAllRolesIds(string username)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                IEnumerable result = _memberGroupRepository.GetMemberGroupsForMember(username);
-                return result.Select(x => x.Id).Distinct();
-            }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            IEnumerable result = _memberGroupRepository.GetMemberGroupsForMember(username);
+            return result.Select(x => x.Id).Distinct();
         }
+    }
 
-        public IEnumerable GetMembersInRole(string roleName)
+    public IEnumerable GetMembersInRole(string roleName)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                return _memberRepository.GetByMemberGroup(roleName);
-            }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            return _memberRepository.GetByMemberGroup(roleName);
         }
+    }
 
-        public IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith)
+    public IEnumerable FindMembersInRole(string roleName, string usernameToMatch,
+        StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(Constants.Locks.MemberTree);
-                return _memberRepository.FindMembersInRole(roleName, usernameToMatch, matchType);
-            }
+            scope.ReadLock(Constants.Locks.MemberTree);
+            return _memberRepository.FindMembersInRole(roleName, usernameToMatch, matchType);
         }
+    }
 
-        public bool DeleteRole(string roleName, bool throwIfBeingUsed)
+    public bool DeleteRole(string roleName, bool throwIfBeingUsed)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(Constants.Locks.MemberTree);
+            scope.WriteLock(Constants.Locks.MemberTree);
 
-                if (throwIfBeingUsed)
+            if (throwIfBeingUsed)
+            {
+                // get members in role
+                IEnumerable membersInRole = _memberRepository.GetByMemberGroup(roleName);
+                if (membersInRole.Any())
                 {
-                    // get members in role
-                    IEnumerable membersInRole = _memberRepository.GetByMemberGroup(roleName);
-                    if (membersInRole.Any())
-                    {
-                        throw new InvalidOperationException("The role " + roleName + " is currently assigned to members");
-                    }
+                    throw new InvalidOperationException("The role " + roleName + " is currently assigned to members");
                 }
+            }
 
-                IQuery query = Query().Where(g => g.Name == roleName);
-                IMemberGroup[]? found = _memberGroupRepository.Get(query)?.ToArray();
+            IQuery query = Query().Where(g => g.Name == roleName);
+            IMemberGroup[]? found = _memberGroupRepository.Get(query)?.ToArray();
 
-                if (found is not null)
+            if (found is not null)
+            {
+                foreach (IMemberGroup memberGroup in found)
                 {
-                    foreach (IMemberGroup memberGroup in found)
-                    {
-                        _memberGroupService.Delete(memberGroup);
-                    }
+                    _memberGroupService.Delete(memberGroup);
                 }
-
-                scope.Complete();
-                return found?.Length > 0;
             }
+
+            scope.Complete();
+            return found?.Length > 0;
         }
+    }
 
-        public void AssignRole(string username, string roleName) => AssignRoles(new[] { username }, new[] { roleName });
+    public void AssignRole(string username, string roleName) => AssignRoles(new[] {username}, new[] {roleName});
 
-        public void AssignRoles(string[] usernames, string[] roleNames)
+    public void AssignRoles(string[] usernames, string[] roleNames)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(Constants.Locks.MemberTree);
-                int[] ids = _memberRepository.GetMemberIds(usernames);
-                _memberGroupRepository.AssignRoles(ids, roleNames);
-                scope.Notifications.Publish(new AssignedMemberRolesNotification(ids, roleNames));
-                scope.Complete();
-            }
+            scope.WriteLock(Constants.Locks.MemberTree);
+            var ids = _memberRepository.GetMemberIds(usernames);
+            _memberGroupRepository.AssignRoles(ids, roleNames);
+            scope.Notifications.Publish(new AssignedMemberRolesNotification(ids, roleNames));
+            scope.Complete();
         }
+    }
 
-        public void DissociateRole(string username, string roleName) => DissociateRoles(new[] { username }, new[] { roleName });
+    public void DissociateRole(string username, string roleName) => DissociateRoles(new[] {username}, new[] {roleName});
 
-        public void DissociateRoles(string[] usernames, string[] roleNames)
+    public void DissociateRoles(string[] usernames, string[] roleNames)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(Constants.Locks.MemberTree);
-                int[] ids = _memberRepository.GetMemberIds(usernames);
-                _memberGroupRepository.DissociateRoles(ids, roleNames);
-                scope.Notifications.Publish(new RemovedMemberRolesNotification(ids, roleNames));
-                scope.Complete();
-            }
+            scope.WriteLock(Constants.Locks.MemberTree);
+            var ids = _memberRepository.GetMemberIds(usernames);
+            _memberGroupRepository.DissociateRoles(ids, roleNames);
+            scope.Notifications.Publish(new RemovedMemberRolesNotification(ids, roleNames));
+            scope.Complete();
         }
+    }
 
-        public void AssignRole(int memberId, string roleName) => AssignRoles(new[] { memberId }, new[] { roleName });
+    public void AssignRole(int memberId, string roleName) => AssignRoles(new[] {memberId}, new[] {roleName});
 
-        public void AssignRoles(int[] memberIds, string[] roleNames)
+    public void AssignRoles(int[] memberIds, string[] roleNames)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(Constants.Locks.MemberTree);
-                _memberGroupRepository.AssignRoles(memberIds, roleNames);
-                scope.Notifications.Publish(new AssignedMemberRolesNotification(memberIds, roleNames));
-                scope.Complete();
-            }
+            scope.WriteLock(Constants.Locks.MemberTree);
+            _memberGroupRepository.AssignRoles(memberIds, roleNames);
+            scope.Notifications.Publish(new AssignedMemberRolesNotification(memberIds, roleNames));
+            scope.Complete();
         }
+    }
 
-        public void DissociateRole(int memberId, string roleName) => DissociateRoles(new[] { memberId }, new[] { roleName });
+    public void DissociateRole(int memberId, string roleName) => DissociateRoles(new[] {memberId}, new[] {roleName});
 
-        public void DissociateRoles(int[] memberIds, string[] roleNames)
+    public void DissociateRoles(int[] memberIds, string[] roleNames)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(Constants.Locks.MemberTree);
-                _memberGroupRepository.DissociateRoles(memberIds, roleNames);
-                scope.Notifications.Publish(new RemovedMemberRolesNotification(memberIds, roleNames));
-                scope.Complete();
-            }
+            scope.WriteLock(Constants.Locks.MemberTree);
+            _memberGroupRepository.DissociateRoles(memberIds, roleNames);
+            scope.Notifications.Publish(new RemovedMemberRolesNotification(memberIds, roleNames));
+            scope.Complete();
         }
+    }
 
-        public void ReplaceRoles(string[] usernames, string[] roleNames)
+    public void ReplaceRoles(string[] usernames, string[] roleNames)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(Constants.Locks.MemberTree);
-                int[] ids = _memberRepository.GetMemberIds(usernames);
-                _memberGroupRepository.ReplaceRoles(ids, roleNames);
-                scope.Notifications.Publish(new AssignedMemberRolesNotification(ids, roleNames));
-                scope.Complete();
-            }
+            scope.WriteLock(Constants.Locks.MemberTree);
+            var ids = _memberRepository.GetMemberIds(usernames);
+            _memberGroupRepository.ReplaceRoles(ids, roleNames);
+            scope.Notifications.Publish(new AssignedMemberRolesNotification(ids, roleNames));
+            scope.Complete();
         }
+    }
 
-        public void ReplaceRoles(int[] memberIds, string[] roleNames)
+    public void ReplaceRoles(int[] memberIds, string[] roleNames)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-            {
-                scope.WriteLock(Constants.Locks.MemberTree);
-                _memberGroupRepository.ReplaceRoles(memberIds, roleNames);
-                scope.Notifications.Publish(new AssignedMemberRolesNotification(memberIds, roleNames));
-                scope.Complete();
-            }
+            scope.WriteLock(Constants.Locks.MemberTree);
+            _memberGroupRepository.ReplaceRoles(memberIds, roleNames);
+            scope.Notifications.Publish(new AssignedMemberRolesNotification(memberIds, roleNames));
+            scope.Complete();
         }
+    }
 
-        #endregion
-
-        #region Private Methods
-
-        private void Audit(AuditType type, int userId, int objectId, string? message = null) => _auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.Member), message));
-
-        #endregion
-
-        #region Membership
+    #endregion
 
+    #region Membership
 
-        /// 
-        /// Exports a member.
-        /// 
-        /// 
-        /// This is internal for now and is used to export a member in the member editor,
-        /// it will raise an event so that auditing logs can be created.
-        /// 
-        public MemberExportModel? ExportMember(Guid key)
+    /// 
+    ///     Exports a member.
+    /// 
+    /// 
+    ///     This is internal for now and is used to export a member in the member editor,
+    ///     it will raise an event so that auditing logs can be created.
+    /// 
+    public MemberExportModel? ExportMember(Guid key)
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
         {
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                IQuery? query = Query().Where(x => x.Key == key);
-                IMember? member = _memberRepository.Get(query)?.FirstOrDefault();
+            IQuery? query = Query().Where(x => x.Key == key);
+            IMember? member = _memberRepository.Get(query)?.FirstOrDefault();
 
-                if (member == null)
-                {
-                    return null;
-                }
-
-                var model = new MemberExportModel
-                {
-                    Id = member.Id,
-                    Key = member.Key,
-                    Name = member.Name,
-                    Username = member.Username,
-                    Email = member.Email,
-                    Groups = GetAllRoles(member.Id)?.ToList(),
-                    ContentTypeAlias = member.ContentTypeAlias,
-                    CreateDate = member.CreateDate,
-                    UpdateDate = member.UpdateDate,
-                    Properties = new List(GetPropertyExportItems(member))
-                };
-
-                scope.Notifications.Publish(new ExportedMemberNotification(member, model));
-
-                return model;
-            }
-        }
-
-        private static IEnumerable GetPropertyExportItems(IMember member)
-        {
             if (member == null)
             {
-                throw new ArgumentNullException(nameof(member));
+                return null;
             }
 
-            var exportProperties = new List();
-
-            foreach (IProperty property in member.Properties)
+            var model = new MemberExportModel
             {
-                var propertyExportModel = new MemberExportProperty
-                {
-                    Id = property.Id,
-                    Alias = property.Alias,
-                    Name = property.PropertyType.Name,
-                    Value = property.GetValue(), // TODO: ignoring variants
-                    CreateDate = property.CreateDate,
-                    UpdateDate = property.UpdateDate
-                };
-                exportProperties.Add(propertyExportModel);
-            }
-
-            return exportProperties;
+                Id = member.Id,
+                Key = member.Key,
+                Name = member.Name,
+                Username = member.Username,
+                Email = member.Email,
+                Groups = GetAllRoles(member.Id)?.ToList(),
+                ContentTypeAlias = member.ContentTypeAlias,
+                CreateDate = member.CreateDate,
+                UpdateDate = member.UpdateDate,
+                Properties = new List(GetPropertyExportItems(member))
+            };
+
+            scope.Notifications.Publish(new ExportedMemberNotification(member, model));
+
+            return model;
         }
+    }
 
-        #endregion
+    private static IEnumerable GetPropertyExportItems(IMember member)
+    {
+        if (member == null)
+        {
+            throw new ArgumentNullException(nameof(member));
+        }
 
-        #region Content Types
+        var exportProperties = new List();
 
-        /// 
-        /// Delete Members of the specified MemberType id
-        /// 
-        /// Id of the MemberType
-        public void DeleteMembersOfType(int memberTypeId)
+        foreach (IProperty property in member.Properties)
         {
-            var evtMsgs = EventMessagesFactory.Get();
-
-            // note: no tree to manage here
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+            var propertyExportModel = new MemberExportProperty
             {
-                scope.WriteLock(Constants.Locks.MemberTree);
+                Id = property.Id,
+                Alias = property.Alias,
+                Name = property.PropertyType.Name,
+                Value = property.GetValue(), // TODO: ignoring variants
+                CreateDate = property.CreateDate,
+                UpdateDate = property.UpdateDate
+            };
+            exportProperties.Add(propertyExportModel);
+        }
 
-                // TODO: What about content that has the contenttype as part of its composition?
-                IQuery? query = Query().Where(x => x.ContentTypeId == memberTypeId);
+        return exportProperties;
+    }
 
-                IMember[]? members = _memberRepository.Get(query)?.ToArray();
+    #endregion
 
-                if (members is null)
-                {
-                    return;
-                }
+    #region Content Types
 
-                if (scope.Notifications.PublishCancelable(new MemberDeletingNotification(members, evtMsgs)))
-                {
-                    scope.Complete();
-                    return;
-                }
+    /// 
+    ///     Delete Members of the specified MemberType id
+    /// 
+    /// Id of the MemberType
+    public void DeleteMembersOfType(int memberTypeId)
+    {
+        EventMessages evtMsgs = EventMessagesFactory.Get();
 
-                foreach (IMember member in members)
-                {
-                    // delete media
-                    // triggers the deleted event (and handles the files)
-                    DeleteLocked(scope, member, evtMsgs);
-                }
+        // note: no tree to manage here
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope())
+        {
+            scope.WriteLock(Constants.Locks.MemberTree);
 
-                scope.Complete();
+            // TODO: What about content that has the contenttype as part of its composition?
+            IQuery? query = Query().Where(x => x.ContentTypeId == memberTypeId);
+
+            IMember[]? members = _memberRepository.Get(query)?.ToArray();
+
+            if (members is null)
+            {
+                return;
             }
-        }
 
-        private IMemberType GetMemberType(ICoreScope scope, string memberTypeAlias)
-        {
-            if (memberTypeAlias == null)
+            if (scope.Notifications.PublishCancelable(new MemberDeletingNotification(members, evtMsgs)))
             {
-                throw new ArgumentNullException(nameof(memberTypeAlias));
+                scope.Complete();
+                return;
             }
 
-            if (string.IsNullOrWhiteSpace(memberTypeAlias))
+            foreach (IMember member in members)
             {
-                throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(memberTypeAlias));
+                // delete media
+                // triggers the deleted event (and handles the files)
+                DeleteLocked(scope, member, evtMsgs);
             }
 
-            scope.ReadLock(Constants.Locks.MemberTypes);
+            scope.Complete();
+        }
+    }
 
-            IMemberType? memberType = _memberTypeRepository.Get(memberTypeAlias);
+    private IMemberType GetMemberType(ICoreScope scope, string memberTypeAlias)
+    {
+        if (memberTypeAlias == null)
+        {
+            throw new ArgumentNullException(nameof(memberTypeAlias));
+        }
 
-            if (memberType == null)
-            {
-                throw new Exception($"No MemberType matching the passed in Alias: '{memberTypeAlias}' was found"); // causes rollback
-            }
+        if (string.IsNullOrWhiteSpace(memberTypeAlias))
+        {
+            throw new ArgumentException("Value can't be empty or consist only of white-space characters.",
+                nameof(memberTypeAlias));
+        }
+
+        scope.ReadLock(Constants.Locks.MemberTypes);
 
-            return memberType;
+        IMemberType? memberType = _memberTypeRepository.Get(memberTypeAlias);
+
+        if (memberType == null)
+        {
+            throw new Exception(
+                $"No MemberType matching the passed in Alias: '{memberTypeAlias}' was found"); // causes rollback
         }
 
-        private IMemberType GetMemberType(string memberTypeAlias)
+        return memberType;
+    }
+
+    private IMemberType GetMemberType(string memberTypeAlias)
+    {
+        if (memberTypeAlias == null)
         {
-            if (memberTypeAlias == null)
-            {
-                throw new ArgumentNullException(nameof(memberTypeAlias));
-            }
+            throw new ArgumentNullException(nameof(memberTypeAlias));
+        }
 
-            if (string.IsNullOrWhiteSpace(memberTypeAlias))
-            {
-                throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(memberTypeAlias));
-            }
+        if (string.IsNullOrWhiteSpace(memberTypeAlias))
+        {
+            throw new ArgumentException("Value can't be empty or consist only of white-space characters.",
+                nameof(memberTypeAlias));
+        }
 
-            using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                return GetMemberType(scope, memberTypeAlias);
-            }
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            return GetMemberType(scope, memberTypeAlias);
         }
-        #endregion
     }
+
+    #endregion
 }
diff --git a/src/Umbraco.Core/Services/MemberTypeService.cs b/src/Umbraco.Core/Services/MemberTypeService.cs
index 1d4298984128..44e3ca6df881 100644
--- a/src/Umbraco.Core/Services/MemberTypeService.cs
+++ b/src/Umbraco.Core/Services/MemberTypeService.cs
@@ -1,4 +1,4 @@
-using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using Umbraco.Cms.Core.Events;
 using Umbraco.Cms.Core.Models;
@@ -9,98 +9,112 @@
 using Umbraco.Cms.Web.Common.DependencyInjection;
 using Umbraco.Extensions;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public class MemberTypeService : ContentTypeServiceBase, IMemberTypeService
 {
-    public class MemberTypeService : ContentTypeServiceBase, IMemberTypeService
+    private readonly IMemberTypeRepository _memberTypeRepository;
+
+    [Obsolete("Please use the constructor taking all parameters. This constructor will be removed in V12.")]
+    public MemberTypeService(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
+        IEventMessagesFactory eventMessagesFactory, IMemberService memberService,
+        IMemberTypeRepository memberTypeRepository, IAuditRepository auditRepository,
+        IEntityRepository entityRepository, IEventAggregator eventAggregator)
+        : this(provider, loggerFactory, eventMessagesFactory, memberService, memberTypeRepository, auditRepository,
+            StaticServiceProvider.Instance.GetRequiredService(), entityRepository,
+            eventAggregator)
     {
-        private readonly IMemberTypeRepository _memberTypeRepository;
+    }
 
-        [Obsolete("Please use the constructor taking all parameters. This constructor will be removed in V12.")]
-        public MemberTypeService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IMemberService memberService,
-            IMemberTypeRepository memberTypeRepository, IAuditRepository auditRepository, IEntityRepository entityRepository, IEventAggregator eventAggregator)
-            : this(provider, loggerFactory, eventMessagesFactory, memberService, memberTypeRepository, auditRepository, StaticServiceProvider.Instance.GetRequiredService(), entityRepository, eventAggregator)
-        {
-        }
+    public MemberTypeService(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
+        IEventMessagesFactory eventMessagesFactory, IMemberService memberService,
+        IMemberTypeRepository memberTypeRepository, IAuditRepository auditRepository,
+        IMemberTypeContainerRepository entityContainerRepository, IEntityRepository entityRepository,
+        IEventAggregator eventAggregator)
+        : base(provider, loggerFactory, eventMessagesFactory, memberTypeRepository, auditRepository,
+            entityContainerRepository, entityRepository, eventAggregator)
+    {
+        MemberService = memberService;
+        _memberTypeRepository = memberTypeRepository;
+    }
 
-        public MemberTypeService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IMemberService memberService,
-            IMemberTypeRepository memberTypeRepository, IAuditRepository auditRepository, IMemberTypeContainerRepository entityContainerRepository, IEntityRepository entityRepository, IEventAggregator eventAggregator)
-            : base(provider, loggerFactory, eventMessagesFactory, memberTypeRepository, auditRepository, entityContainerRepository, entityRepository, eventAggregator)
-        {
-            MemberService = memberService;
-            _memberTypeRepository = memberTypeRepository;
-        }
+    // beware! order is important to avoid deadlocks
+    protected override int[] ReadLockIds { get; } = {Constants.Locks.MemberTypes};
+    protected override int[] WriteLockIds { get; } = {Constants.Locks.MemberTree, Constants.Locks.MemberTypes};
 
-        // beware! order is important to avoid deadlocks
-        protected override int[] ReadLockIds { get; } = { Cms.Core.Constants.Locks.MemberTypes };
-        protected override int[] WriteLockIds { get; } = { Cms.Core.Constants.Locks.MemberTree, Cms.Core.Constants.Locks.MemberTypes };
+    private IMemberService MemberService { get; }
 
-        private IMemberService MemberService { get; }
+    protected override Guid ContainedObjectType => Constants.ObjectTypes.MemberType;
 
-        protected override Guid ContainedObjectType => Cms.Core.Constants.ObjectTypes.MemberType;
+    public string GetDefault()
+    {
+        using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
+        {
+            scope.ReadLock(ReadLockIds);
+
+            using (IEnumerator e = _memberTypeRepository.GetMany(new int[0]).GetEnumerator())
+            {
+                if (e.MoveNext() == false)
+                {
+                    throw new InvalidOperationException("No member types could be resolved");
+                }
 
-        #region Notifications
+                var first = e.Current.Alias;
+                var current = true;
+                while (e.Current.Alias.InvariantEquals("Member") == false && (current = e.MoveNext()))
+                {
+                }
 
-        protected override SavingNotification GetSavingNotification(IMemberType item,
-            EventMessages eventMessages) => new MemberTypeSavingNotification(item, eventMessages);
+                return current ? e.Current.Alias : first;
+            }
+        }
+    }
 
-        protected override SavingNotification GetSavingNotification(IEnumerable items,
-            EventMessages eventMessages) => new MemberTypeSavingNotification(items, eventMessages);
+    protected override void DeleteItemsOfTypes(IEnumerable typeIds)
+    {
+        foreach (var typeId in typeIds)
+        {
+            MemberService.DeleteMembersOfType(typeId);
+        }
+    }
 
-        protected override SavedNotification GetSavedNotification(IMemberType item,
-            EventMessages eventMessages) => new MemberTypeSavedNotification(item, eventMessages);
+    #region Notifications
 
-        protected override SavedNotification GetSavedNotification(IEnumerable items,
-            EventMessages eventMessages) => new MemberTypeSavedNotification(items, eventMessages);
+    protected override SavingNotification GetSavingNotification(IMemberType item,
+        EventMessages eventMessages) => new MemberTypeSavingNotification(item, eventMessages);
 
-        protected override DeletingNotification GetDeletingNotification(IMemberType item,
-            EventMessages eventMessages) => new MemberTypeDeletingNotification(item, eventMessages);
+    protected override SavingNotification GetSavingNotification(IEnumerable items,
+        EventMessages eventMessages) => new MemberTypeSavingNotification(items, eventMessages);
 
-        protected override DeletingNotification GetDeletingNotification(IEnumerable items,
-            EventMessages eventMessages) => new MemberTypeDeletingNotification(items, eventMessages);
+    protected override SavedNotification GetSavedNotification(IMemberType item,
+        EventMessages eventMessages) => new MemberTypeSavedNotification(item, eventMessages);
 
-        protected override DeletedNotification GetDeletedNotification(IEnumerable items,
-            EventMessages eventMessages) => new MemberTypeDeletedNotification(items, eventMessages);
+    protected override SavedNotification GetSavedNotification(IEnumerable items,
+        EventMessages eventMessages) => new MemberTypeSavedNotification(items, eventMessages);
 
-        protected override MovingNotification GetMovingNotification(MoveEventInfo moveInfo,
-            EventMessages eventMessages) => new MemberTypeMovingNotification(moveInfo, eventMessages);
+    protected override DeletingNotification GetDeletingNotification(IMemberType item,
+        EventMessages eventMessages) => new MemberTypeDeletingNotification(item, eventMessages);
 
-        protected override MovedNotification GetMovedNotification(
-            IEnumerable> moveInfo, EventMessages eventMessages) =>
-            new MemberTypeMovedNotification(moveInfo, eventMessages);
+    protected override DeletingNotification GetDeletingNotification(IEnumerable items,
+        EventMessages eventMessages) => new MemberTypeDeletingNotification(items, eventMessages);
 
-        protected override ContentTypeChangeNotification GetContentTypeChangedNotification(
-            IEnumerable> changes, EventMessages eventMessages) =>
-            new MemberTypeChangedNotification(changes, eventMessages);
+    protected override DeletedNotification GetDeletedNotification(IEnumerable items,
+        EventMessages eventMessages) => new MemberTypeDeletedNotification(items, eventMessages);
 
-        protected override ContentTypeRefreshNotification GetContentTypeRefreshedNotification(
-            IEnumerable> changes, EventMessages eventMessages) =>
-            new MemberTypeRefreshedNotification(changes, eventMessages);
+    protected override MovingNotification GetMovingNotification(MoveEventInfo moveInfo,
+        EventMessages eventMessages) => new MemberTypeMovingNotification(moveInfo, eventMessages);
 
-        #endregion
+    protected override MovedNotification GetMovedNotification(
+        IEnumerable> moveInfo, EventMessages eventMessages) =>
+        new MemberTypeMovedNotification(moveInfo, eventMessages);
 
-        protected override void DeleteItemsOfTypes(IEnumerable typeIds)
-        {
-            foreach (var typeId in typeIds)
-                MemberService.DeleteMembersOfType(typeId);
-        }
+    protected override ContentTypeChangeNotification GetContentTypeChangedNotification(
+        IEnumerable> changes, EventMessages eventMessages) =>
+        new MemberTypeChangedNotification(changes, eventMessages);
 
-        public string GetDefault()
-        {
-            using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-            {
-                scope.ReadLock(ReadLockIds);
+    protected override ContentTypeRefreshNotification GetContentTypeRefreshedNotification(
+        IEnumerable> changes, EventMessages eventMessages) =>
+        new MemberTypeRefreshedNotification(changes, eventMessages);
 
-                using (var e = _memberTypeRepository.GetMany(new int[0]).GetEnumerator())
-                {
-                    if (e.MoveNext() == false)
-                        throw new InvalidOperationException("No member types could be resolved");
-                    var first = e.Current.Alias;
-                    var current = true;
-                    while (e.Current.Alias.InvariantEquals("Member") == false && (current = e.MoveNext()))
-                    { }
-                    return current ? e.Current.Alias : first;
-                }
-            }
-        }
-    }
+    #endregion
 }
diff --git a/src/Umbraco.Core/Services/MetricsConsentService.cs b/src/Umbraco.Core/Services/MetricsConsentService.cs
index d494dbcf4b0b..5a0de6508677 100644
--- a/src/Umbraco.Core/Services/MetricsConsentService.cs
+++ b/src/Umbraco.Core/Services/MetricsConsentService.cs
@@ -1,57 +1,58 @@
-using System;
-using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models.Membership;
 using Umbraco.Cms.Core.Security;
 using Umbraco.Cms.Web.Common.DependencyInjection;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public class MetricsConsentService : IMetricsConsentService
 {
-    public class MetricsConsentService : IMetricsConsentService
-    {
-        internal const string Key = "UmbracoAnalyticsLevel";
+    internal const string Key = "UmbracoAnalyticsLevel";
+    private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
 
-        private readonly IKeyValueService _keyValueService;
-        private readonly ILogger _logger;
-        private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
+    private readonly IKeyValueService _keyValueService;
+    private readonly ILogger _logger;
 
-        // Scheduled for removal in V12
-        [Obsolete("Please use the constructor that takes and ILogger and IBackOfficeSecurity instead")]
-        public MetricsConsentService(IKeyValueService keyValueService)
+    // Scheduled for removal in V12
+    [Obsolete("Please use the constructor that takes and ILogger and IBackOfficeSecurity instead")]
+    public MetricsConsentService(IKeyValueService keyValueService)
         : this(
             keyValueService,
             StaticServiceProvider.Instance.GetRequiredService>(),
             StaticServiceProvider.Instance.GetRequiredService())
-        {
-        }
-
-        public MetricsConsentService(
-            IKeyValueService keyValueService,
-            ILogger logger,
-            IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
-        {
-            _keyValueService = keyValueService;
-            _logger = logger;
-            _backOfficeSecurityAccessor = backOfficeSecurityAccessor;
-        }
-
-        public TelemetryLevel GetConsentLevel()
-        {
-            var analyticsLevelString = _keyValueService.GetValue(Key);
+    {
+    }
 
-            if (analyticsLevelString is null || Enum.TryParse(analyticsLevelString, out TelemetryLevel analyticsLevel) is false)
-            {
-                return TelemetryLevel.Basic;
-            }
+    public MetricsConsentService(
+        IKeyValueService keyValueService,
+        ILogger logger,
+        IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
+    {
+        _keyValueService = keyValueService;
+        _logger = logger;
+        _backOfficeSecurityAccessor = backOfficeSecurityAccessor;
+    }
 
-            return analyticsLevel;
-        }
+    public TelemetryLevel GetConsentLevel()
+    {
+        var analyticsLevelString = _keyValueService.GetValue(Key);
 
-        public void SetConsentLevel(TelemetryLevel telemetryLevel)
+        if (analyticsLevelString is null ||
+            Enum.TryParse(analyticsLevelString, out TelemetryLevel analyticsLevel) is false)
         {
-            var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser;
-            _logger.LogInformation("Telemetry level set to {telemetryLevel} by {username}", telemetryLevel, currentUser?.Username);
-            _keyValueService.SetValue(Key, telemetryLevel.ToString());
+            return TelemetryLevel.Basic;
         }
+
+        return analyticsLevel;
+    }
+
+    public void SetConsentLevel(TelemetryLevel telemetryLevel)
+    {
+        IUser currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser;
+        _logger.LogInformation("Telemetry level set to {telemetryLevel} by {username}", telemetryLevel,
+            currentUser?.Username);
+        _keyValueService.SetValue(Key, telemetryLevel.ToString());
     }
 }
diff --git a/src/Umbraco.Core/Services/MoveOperationStatusType.cs b/src/Umbraco.Core/Services/MoveOperationStatusType.cs
index 4de17b2fa5e6..1cfa301ee750 100644
--- a/src/Umbraco.Core/Services/MoveOperationStatusType.cs
+++ b/src/Umbraco.Core/Services/MoveOperationStatusType.cs
@@ -1,32 +1,30 @@
-namespace Umbraco.Cms.Core.Services
-{
+namespace Umbraco.Cms.Core.Services;
 
+/// 
+///     A status type of the result of moving an item
+/// 
+/// 
+///     Anything less than 10 = Success!
+/// 
+public enum MoveOperationStatusType : byte
+{
     /// 
-    /// A status type of the result of moving an item
+    ///     The move was successful.
     /// 
-    /// 
-    /// Anything less than 10 = Success!
-    /// 
-    public enum MoveOperationStatusType : byte
-    {
-        /// 
-        /// The move was successful.
-        /// 
-        Success = 0,
+    Success = 0,
 
-        /// 
-        /// The parent being moved to doesn't exist
-        /// 
-        FailedParentNotFound = 13,
+    /// 
+    ///     The parent being moved to doesn't exist
+    /// 
+    FailedParentNotFound = 13,
 
-        /// 
-        /// The move action has been cancelled by an event handler
-        /// 
-        FailedCancelledByEvent = 14,
+    /// 
+    ///     The move action has been cancelled by an event handler
+    /// 
+    FailedCancelledByEvent = 14,
 
-        /// 
-        /// Trying to move an item to an invalid path (i.e. a child of itself)
-        /// 
-        FailedNotAllowedByPath = 15,
-    }
+    /// 
+    ///     Trying to move an item to an invalid path (i.e. a child of itself)
+    /// 
+    FailedNotAllowedByPath = 15
 }
diff --git a/src/Umbraco.Core/Services/NodeCountService.cs b/src/Umbraco.Core/Services/NodeCountService.cs
index 7298d7f23a4b..9832bf47aeb4 100644
--- a/src/Umbraco.Core/Services/NodeCountService.cs
+++ b/src/Umbraco.Core/Services/NodeCountService.cs
@@ -1,31 +1,29 @@
-using System;
-using Umbraco.Cms.Core.Persistence.Repositories;
+using Umbraco.Cms.Core.Persistence.Repositories;
 using Umbraco.Cms.Core.Scoping;
 using Umbraco.Cms.Core.Services;
 
-namespace Umbraco.Cms.Infrastructure.Services.Implement
+namespace Umbraco.Cms.Infrastructure.Services.Implement;
+
+public class NodeCountService : INodeCountService
 {
-    public class NodeCountService : INodeCountService
-    {
-        private readonly INodeCountRepository _nodeCountRepository;
-        private readonly ICoreScopeProvider _scopeProvider;
+    private readonly INodeCountRepository _nodeCountRepository;
+    private readonly ICoreScopeProvider _scopeProvider;
 
-        public NodeCountService(INodeCountRepository nodeCountRepository, ICoreScopeProvider scopeProvider)
-        {
-            _nodeCountRepository = nodeCountRepository;
-            _scopeProvider = scopeProvider;
-        }
+    public NodeCountService(INodeCountRepository nodeCountRepository, ICoreScopeProvider scopeProvider)
+    {
+        _nodeCountRepository = nodeCountRepository;
+        _scopeProvider = scopeProvider;
+    }
 
-        public int GetNodeCount(Guid nodeType)
-        {
-            using var scope = _scopeProvider.CreateCoreScope(autoComplete: true);
-            return _nodeCountRepository.GetNodeCount(nodeType);
-        }
+    public int GetNodeCount(Guid nodeType)
+    {
+        using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
+        return _nodeCountRepository.GetNodeCount(nodeType);
+    }
 
-        public int GetMediaCount()
-        {
-            using var scope = _scopeProvider.CreateCoreScope(autoComplete: true);
-            return _nodeCountRepository.GetMediaCount();
-        }
+    public int GetMediaCount()
+    {
+        using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
+        return _nodeCountRepository.GetMediaCount();
     }
 }
diff --git a/src/Umbraco.Core/Services/NotificationService.cs b/src/Umbraco.Core/Services/NotificationService.cs
index 39aa6a863da8..6c050832a55f 100644
--- a/src/Umbraco.Core/Services/NotificationService.cs
+++ b/src/Umbraco.Core/Services/NotificationService.cs
@@ -1,10 +1,6 @@
-using System;
 using System.Collections.Concurrent;
-using System.Collections.Generic;
 using System.Globalization;
-using System.Linq;
 using System.Text;
-using System.Threading;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
 using Umbraco.Cms.Core.Configuration.Models;
@@ -18,541 +14,612 @@
 using Umbraco.Cms.Core.Scoping;
 using Umbraco.Extensions;
 
-namespace Umbraco.Cms.Core.Services
+namespace Umbraco.Cms.Core.Services;
+
+public class NotificationService : INotificationService
 {
-    public class NotificationService : INotificationService
+    private readonly IContentService _contentService;
+    private readonly ContentSettings _contentSettings;
+    private readonly IEmailSender _emailSender;
+    private readonly GlobalSettings _globalSettings;
+    private readonly IIOHelper _ioHelper;
+    private readonly ILocalizationService _localizationService;
+    private readonly ILogger _logger;
+    private readonly INotificationsRepository _notificationsRepository;
+    private readonly ICoreScopeProvider _uowProvider;
+    private readonly IUserService _userService;
+
+    public NotificationService(ICoreScopeProvider provider, IUserService userService, IContentService contentService,
+        ILocalizationService localizationService,
+        ILogger logger, IIOHelper ioHelper, INotificationsRepository notificationsRepository,
+        IOptions globalSettings, IOptions contentSettings, IEmailSender emailSender)
     {
-        private readonly ICoreScopeProvider _uowProvider;
-        private readonly IUserService _userService;
-        private readonly IContentService _contentService;
-        private readonly ILocalizationService _localizationService;
-        private readonly INotificationsRepository _notificationsRepository;
-        private readonly GlobalSettings _globalSettings;
-        private readonly ContentSettings _contentSettings;
-        private readonly IEmailSender _emailSender;
-        private readonly ILogger _logger;
-        private readonly IIOHelper _ioHelper;
-
-        public NotificationService(ICoreScopeProvider provider, IUserService userService, IContentService contentService, ILocalizationService localizationService,
-            ILogger logger, IIOHelper ioHelper, INotificationsRepository notificationsRepository, IOptions globalSettings, IOptions contentSettings, IEmailSender emailSender)
-        {
-            _notificationsRepository = notificationsRepository;
-            _globalSettings = globalSettings.Value;
-            _contentSettings = contentSettings.Value;
-            _emailSender = emailSender;
-            _uowProvider = provider ?? throw new ArgumentNullException(nameof(provider));
-            _userService = userService ?? throw new ArgumentNullException(nameof(userService));
-            _contentService = contentService ?? throw new ArgumentNullException(nameof(contentService));
-            _localizationService = localizationService;
-            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
-            _ioHelper = ioHelper;
-        }
+        _notificationsRepository = notificationsRepository;
+        _globalSettings = globalSettings.Value;
+        _contentSettings = contentSettings.Value;
+        _emailSender = emailSender;
+        _uowProvider = provider ?? throw new ArgumentNullException(nameof(provider));
+        _userService = userService ?? throw new ArgumentNullException(nameof(userService));
+        _contentService = contentService ?? throw new ArgumentNullException(nameof(contentService));
+        _localizationService = localizationService;
+        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+        _ioHelper = ioHelper;
+    }
 
-        /// 
-        /// Gets the previous version to the latest version of the content item if there is one
-        /// 
-        /// 
-        /// 
-        private IContentBase? GetPreviousVersion(int contentId)
-        {
-            // Regarding this: http://issues.umbraco.org/issue/U4-5180
-            // we know they are descending from the service so we know that newest is first
-            // we are only selecting the top 2 rows since that is all we need
-            var allVersions = _contentService.GetVersionIds(contentId, 2).ToList();
-            var prevVersionIndex = allVersions.Count > 1 ? 1 : 0;
-            return _contentService.GetVersion(allVersions[prevVersionIndex]);
-        }
+    /// 
+    ///     Sends the notifications for the specified user regarding the specified node and action.
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    public void SendNotifications(IUser operatingUser, IEnumerable entities, string? action,
+        string? actionName, Uri siteUri,
+        Func<(IUser user, NotificationEmailSubjectParams subject), string> createSubject,
+        Func<(IUser user, NotificationEmailBodyParams body, bool isHtml), string> createBody)
+    {
+        var entitiesL = entities.ToList();
 
-        /// 
-        /// Sends the notifications for the specified user regarding the specified node and action.
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        public void SendNotifications(IUser operatingUser, IEnumerable entities, string? action, string? actionName, Uri siteUri,
-            Func<(IUser user, NotificationEmailSubjectParams subject), string> createSubject,
-            Func<(IUser user, NotificationEmailBodyParams body, bool isHtml), string> createBody)
+        //exit if there are no entities
+        if (entitiesL.Count == 0)
         {
-            var entitiesL = entities.ToList();
+            return;
+        }
 
-            //exit if there are no entities
-            if (entitiesL.Count == 0) return;
+        //put all entity's paths into a list with the same indices
+        var paths = entitiesL.Select(x =>
+                x.Path.Split(Constants.CharArrays.Comma).Select(s => int.Parse(s, CultureInfo.InvariantCulture))
+                    .ToArray())
+            .ToArray();
 
-            //put all entity's paths into a list with the same indices
-            var paths = entitiesL.Select(x => x.Path.Split(Constants.CharArrays.Comma).Select(s => int.Parse(s, CultureInfo.InvariantCulture)).ToArray()).ToArray();
+        // lazily get versions
+        var prevVersionDictionary = new Dictionary();
 
-            // lazily get versions
-            var prevVersionDictionary = new Dictionary();
+        // see notes above
+        var id = Constants.Security.SuperUserId;
+        const int pagesz = 400; // load batches of 400 users
+        do
+        {
+            // users are returned ordered by id, notifications are returned ordered by user id
+            var users = _userService.GetNextUsers(id, pagesz).Where(x => x.IsApproved).ToList();
+            var notifications = GetUsersNotifications(users.Select(x => x.Id), action, Enumerable.Empty(),
+                Constants.ObjectTypes.Document)?.ToList();
+            if (notifications is null || notifications.Count == 0)
+            {
+                break;
+            }
 
-            // see notes above
-            var id = Cms.Core.Constants.Security.SuperUserId;
-            const int pagesz = 400; // load batches of 400 users
-            do
+            var i = 0;
+            foreach (IUser user in users)
             {
-                // users are returned ordered by id, notifications are returned ordered by user id
-                var users = _userService.GetNextUsers(id, pagesz).Where(x => x.IsApproved).ToList();
-                var notifications = GetUsersNotifications(users.Select(x => x.Id), action, Enumerable.Empty(), Cms.Core.Constants.ObjectTypes.Document)?.ToList();
-                if (notifications is null || notifications.Count == 0) break;
+                // continue if there's no notification for this user
+                if (notifications[i].UserId != user.Id)
+                {
+                    continue; // next user
+                }
 
-                var i = 0;
-                foreach (var user in users)
+                for (var j = 0; j < entitiesL.Count; j++)
                 {
-                    // continue if there's no notification for this user
-                    if (notifications[i].UserId != user.Id) continue; // next user
+                    IContent content = entitiesL[j];
+                    var path = paths[j];
 
-                    for (var j = 0; j < entitiesL.Count; j++)
+                    // test if the notification applies to the path ie to this entity
+                    if (path.Contains(notifications[i].EntityId) == false)
                     {
-                        var content = entitiesL[j];
-                        var path = paths[j];
-
-                        // test if the notification applies to the path ie to this entity
-                        if (path.Contains(notifications[i].EntityId) == false) continue; // next entity
-
-                        if (prevVersionDictionary.ContainsKey(content.Id) == false)
-                        {
-                            prevVersionDictionary[content.Id] = GetPreviousVersion(content.Id);
-                        }
-
-                        // queue notification
-                        var req = CreateNotificationRequest(operatingUser, user, content, prevVersionDictionary[content.Id], actionName, siteUri, createSubject, createBody);
-                        Enqueue(req);
+                        continue; // next entity
                     }
 
-                    // skip other notifications for this user, essentially this means moving i to the next index of notifications
-                    // for the next user.
-                    do
+                    if (prevVersionDictionary.ContainsKey(content.Id) == false)
                     {
-                        i++;
-                    } while (i < notifications.Count && notifications[i].UserId == user.Id);
+                        prevVersionDictionary[content.Id] = GetPreviousVersion(content.Id);
+                    }
+
+                    // queue notification
+                    NotificationRequest req = CreateNotificationRequest(operatingUser, user, content,
+                        prevVersionDictionary[content.Id], actionName, siteUri, createSubject, createBody);
+                    Enqueue(req);
+                }
+
+                // skip other notifications for this user, essentially this means moving i to the next index of notifications
+                // for the next user.
+                do
+                {
+                    i++;
+                } while (i < notifications.Count && notifications[i].UserId == user.Id);
 
-                    if (i >= notifications.Count) break; // break if no more notifications
+                if (i >= notifications.Count)
+                {
+                    break; // break if no more notifications
                 }
+            }
 
-                // load more users if any
-                id = users.Count == pagesz ? users.Last().Id + 1 : -1;
+            // load more users if any
+            id = users.Count == pagesz ? users.Last().Id + 1 : -1;
+        } while (id > 0);
+    }
 
-            } while (id > 0);
+    /// 
+    ///     Gets the notifications for the user
+    /// 
+    /// 
+    /// 
+    public IEnumerable? GetUserNotifications(IUser user)
+    {
+        using (ICoreScope scope = _uowProvider.CreateCoreScope(autoComplete: true))
+        {
+            return _notificationsRepository.GetUserNotifications(user);
         }
+    }
 
-        private IEnumerable? GetUsersNotifications(IEnumerable userIds, string? action, IEnumerable nodeIds, Guid objectType)
+    /// 
+    ///     Gets the notifications for the user based on the specified node path
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    ///     Notifications are inherited from the parent so any child node will also have notifications assigned based on it's
+    ///     parent (ancestors)
+    /// 
+    public IEnumerable? GetUserNotifications(IUser? user, string path)
+    {
+        if (user is null)
         {
-            using (var scope = _uowProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _notificationsRepository.GetUsersNotifications(userIds, action, nodeIds, objectType);
-            }
+            return null;
         }
-        /// 
-        /// Gets the notifications for the user
-        /// 
-        /// 
-        /// 
-        public IEnumerable? GetUserNotifications(IUser user)
+
+        IEnumerable userNotifications = GetUserNotifications(user);
+        return FilterUserNotificationsByPath(userNotifications, path);
+    }
+
+    /// 
+    ///     Deletes notifications by entity
+    /// 
+    /// 
+    public IEnumerable? GetEntityNotifications(IEntity entity)
+    {
+        using (ICoreScope scope = _uowProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = _uowProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _notificationsRepository.GetUserNotifications(user);
-            }
+            return _notificationsRepository.GetEntityNotifications(entity);
         }
+    }
 
-        /// 
-        /// Gets the notifications for the user based on the specified node path
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// Notifications are inherited from the parent so any child node will also have notifications assigned based on it's parent (ancestors)
-        /// 
-        public IEnumerable? GetUserNotifications(IUser? user, string path)
+    /// 
+    ///     Deletes notifications by entity
+    /// 
+    /// 
+    public void DeleteNotifications(IEntity entity)
+    {
+        using (ICoreScope scope = _uowProvider.CreateCoreScope())
         {
-            if (user is null)
-            {
-                return null;
-            }
+            _notificationsRepository.DeleteNotifications(entity);
+            scope.Complete();
+        }
+    }
 
-            var userNotifications = GetUserNotifications(user);
-            return FilterUserNotificationsByPath(userNotifications, path);
+    /// 
+    ///     Deletes notifications by user
+    /// 
+    /// 
+    public void DeleteNotifications(IUser user)
+    {
+        using (ICoreScope scope = _uowProvider.CreateCoreScope())
+        {
+            _notificationsRepository.DeleteNotifications(user);
+            scope.Complete();
         }
+    }
 
-        /// 
-        /// Filters a userNotifications collection by a path
-        /// 
-        /// 
-        /// 
-        /// 
-        public IEnumerable? FilterUserNotificationsByPath(IEnumerable? userNotifications, string path)
+    /// 
+    ///     Delete notifications by user and entity
+    /// 
+    /// 
+    /// 
+    public void DeleteNotifications(IUser user, IEntity entity)
+    {
+        using (ICoreScope scope = _uowProvider.CreateCoreScope())
         {
-            var pathParts = path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
-            return userNotifications?.Where(r => pathParts.InvariantContains(r.EntityId.ToString(CultureInfo.InvariantCulture))).ToList();
+            _notificationsRepository.DeleteNotifications(user, entity);
+            scope.Complete();
         }
+    }
 
-        /// 
-        /// Deletes notifications by entity
-        /// 
-        /// 
-        public IEnumerable? GetEntityNotifications(IEntity entity)
+    /// 
+    ///     Sets the specific notifications for the user and entity
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    ///     This performs a full replace
+    /// 
+    public IEnumerable? SetNotifications(IUser? user, IEntity entity, string[] actions)
+    {
+        if (user is null)
         {
-            using (var scope = _uowProvider.CreateCoreScope(autoComplete: true))
-            {
-                return _notificationsRepository.GetEntityNotifications(entity);
-            }
+            return null;
         }
 
-        /// 
-        /// Deletes notifications by entity
-        /// 
-        /// 
-        public void DeleteNotifications(IEntity entity)
+        using (ICoreScope scope = _uowProvider.CreateCoreScope())
         {
-            using (var scope = _uowProvider.CreateCoreScope())
-            {
-                _notificationsRepository.DeleteNotifications(entity);
-                scope.Complete();
-            }
+            IEnumerable notifications = _notificationsRepository.SetNotifications(user, entity, actions);
+            scope.Complete();
+            return notifications;
         }
+    }
 
-        /// 
-        /// Deletes notifications by user
-        /// 
-        /// 
-        public void DeleteNotifications(IUser user)
+    /// 
+    ///     Creates a new notification
+    /// 
+    /// 
+    /// 
+    /// The action letter - note: this is a string for future compatibility
+    /// 
+    public Notification CreateNotification(IUser user, IEntity entity, string action)
+    {
+        using (ICoreScope scope = _uowProvider.CreateCoreScope())
         {
-            using (var scope = _uowProvider.CreateCoreScope())
-            {
-                _notificationsRepository.DeleteNotifications(user);
-                scope.Complete();
-            }
+            Notification notification = _notificationsRepository.CreateNotification(user, entity, action);
+            scope.Complete();
+            return notification;
         }
+    }
 
-        /// 
-        /// Delete notifications by user and entity
-        /// 
-        /// 
-        /// 
-        public void DeleteNotifications(IUser user, IEntity entity)
+    /// 
+    ///     Gets the previous version to the latest version of the content item if there is one
+    /// 
+    /// 
+    /// 
+    private IContentBase? GetPreviousVersion(int contentId)
+    {
+        // Regarding this: http://issues.umbraco.org/issue/U4-5180
+        // we know they are descending from the service so we know that newest is first
+        // we are only selecting the top 2 rows since that is all we need
+        var allVersions = _contentService.GetVersionIds(contentId, 2).ToList();
+        var prevVersionIndex = allVersions.Count > 1 ? 1 : 0;
+        return _contentService.GetVersion(allVersions[prevVersionIndex]);
+    }
+
+    private IEnumerable? GetUsersNotifications(IEnumerable userIds, string? action,
+        IEnumerable nodeIds, Guid objectType)
+    {
+        using (ICoreScope scope = _uowProvider.CreateCoreScope(autoComplete: true))
         {
-            using (var scope = _uowProvider.CreateCoreScope())
-            {
-                _notificationsRepository.DeleteNotifications(user, entity);
-                scope.Complete();
-            }
+            return _notificationsRepository.GetUsersNotifications(userIds, action, nodeIds, objectType);
         }
+    }
 
-        /// 
-        /// Sets the specific notifications for the user and entity
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// This performs a full replace
-        /// 
-        public IEnumerable? SetNotifications(IUser? user, IEntity entity, string[] actions)
+    /// 
+    ///     Filters a userNotifications collection by a path
+    /// 
+    /// 
+    /// 
+    /// 
+    public IEnumerable? FilterUserNotificationsByPath(IEnumerable? userNotifications,
+        string path)
+    {
+        var pathParts = path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
+        return userNotifications
+            ?.Where(r => pathParts.InvariantContains(r.EntityId.ToString(CultureInfo.InvariantCulture))).ToList();
+    }
+
+    #region private methods
+
+    /// 
+    ///     Sends the notification
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    /// 
+    ///     The action readable name - currently an action is just a single letter, this is the name
+    ///     associated with the letter
+    /// 
+    /// 
+    /// Callback to create the mail subject
+    /// Callback to create the mail body
+    private NotificationRequest CreateNotificationRequest(IUser performingUser, IUser mailingUser, IContent content,
+        IContentBase? oldDoc,
+        string? actionName,
+        Uri siteUri,
+        Func<(IUser user, NotificationEmailSubjectParams subject), string> createSubject,
+        Func<(IUser user, NotificationEmailBodyParams body, bool isHtml), string> createBody)
+    {
+        if (performingUser == null)
         {
-            if (user is null)
-            {
-                return null;
-            }
+            throw new ArgumentNullException("performingUser");
+        }
 
-            using (var scope = _uowProvider.CreateCoreScope())
-            {
-                var notifications = _notificationsRepository.SetNotifications(user, entity, actions);
-                scope.Complete();
-                return notifications;
-            }
+        if (mailingUser == null)
+        {
+            throw new ArgumentNullException("mailingUser");
         }
 
-        /// 
-        /// Creates a new notification
-        /// 
-        /// 
-        /// 
-        /// The action letter - note: this is a string for future compatibility
-        /// 
-        public Notification CreateNotification(IUser user, IEntity entity, string action)
+        if (content == null)
         {
-            using (var scope = _uowProvider.CreateCoreScope())
-            {
-                var notification = _notificationsRepository.CreateNotification(user, entity, action);
-                scope.Complete();
-                return notification;
-            }
+            throw new ArgumentNullException("content");
+        }
+
+        if (siteUri == null)
+        {
+            throw new ArgumentNullException("siteUri");
+        }
+
+        if (createSubject == null)
+        {
+            throw new ArgumentNullException("createSubject");
         }
 
-        #region private methods
-
-        /// 
-        /// Sends the notification
-        /// 
-        /// 
-        /// 
-        /// 
-        /// 
-        /// The action readable name - currently an action is just a single letter, this is the name associated with the letter 
-        /// 
-        /// Callback to create the mail subject
-        /// Callback to create the mail body
-        private NotificationRequest CreateNotificationRequest(IUser performingUser, IUser mailingUser, IContent content, IContentBase? oldDoc,
-            string? actionName,
-            Uri siteUri,
-            Func<(IUser user, NotificationEmailSubjectParams subject), string> createSubject,
-            Func<(IUser user, NotificationEmailBodyParams body, bool isHtml), string> createBody)
+        if (createBody == null)
         {
-            if (performingUser == null) throw new ArgumentNullException("performingUser");
-            if (mailingUser == null) throw new ArgumentNullException("mailingUser");
-            if (content == null) throw new ArgumentNullException("content");
-            if (siteUri == null) throw new ArgumentNullException("siteUri");
-            if (createSubject == null) throw new ArgumentNullException("createSubject");
-            if (createBody == null) throw new ArgumentNullException("createBody");
+            throw new ArgumentNullException("createBody");
+        }
 
-            // build summary
-            var summary = new StringBuilder();
+        // build summary
+        var summary = new StringBuilder();
 
-            if (content.ContentType.VariesByNothing())
+        if (content.ContentType.VariesByNothing())
+        {
+            if (!_contentSettings.Notifications.DisableHtmlEmail)
             {
-                if (!_contentSettings.Notifications.DisableHtmlEmail)
+                //create the HTML summary for invariant content
+
+                //list all of the property values like we used to
+                summary.Append("");
+                foreach (IProperty p in content.Properties)
                 {
-                    //create the HTML summary for invariant content
+                    // TODO: doesn't take into account variants
+
+                    var newText = p.GetValue() != null ? p.GetValue()?.ToString() : "";
+                    var oldText = newText;
 
-                    //list all of the property values like we used to
-                    summary.Append("
"); - foreach (var p in content.Properties) + // check if something was changed and display the changes otherwise display the fields + if (oldDoc?.Properties.Contains(p.PropertyType.Alias) ?? false) { - // TODO: doesn't take into account variants - - var newText = p.GetValue() != null ? p.GetValue()?.ToString() : ""; - var oldText = newText; - - // check if something was changed and display the changes otherwise display the fields - if (oldDoc?.Properties.Contains(p.PropertyType.Alias) ?? false) - { - var oldProperty = oldDoc.Properties[p.PropertyType.Alias]; - oldText = oldProperty?.GetValue() != null ? oldProperty.GetValue()?.ToString() : ""; - - // replace HTML with char equivalent - ReplaceHtmlSymbols(ref oldText); - ReplaceHtmlSymbols(ref newText); - } - - //show the values - summary.Append(""); - summary.Append(""); - summary.Append(""); - summary.Append(""); + IProperty oldProperty = oldDoc.Properties[p.PropertyType.Alias]; + oldText = oldProperty?.GetValue() != null ? oldProperty.GetValue()?.ToString() : ""; + + // replace HTML with char equivalent + ReplaceHtmlSymbols(ref oldText); + ReplaceHtmlSymbols(ref newText); } - summary.Append("
"); - summary.Append(p.PropertyType.Name); - summary.Append(""); - summary.Append(newText); - summary.Append("
"); + + //show the values + summary.Append(""); + summary.Append( + ""); + summary.Append(p.PropertyType.Name); + summary.Append(""); + summary.Append(""); + summary.Append(newText); + summary.Append(""); + summary.Append(""); } + summary.Append(""); } - else if (content.ContentType.VariesByCulture()) - { - //it's variant, so detect what cultures have changed + } + else if (content.ContentType.VariesByCulture()) + { + //it's variant, so detect what cultures have changed - if (!_contentSettings.Notifications.DisableHtmlEmail) + if (!_contentSettings.Notifications.DisableHtmlEmail) + { + //Create the HTML based summary (ul of culture names) + + IEnumerable culturesChanged = content.CultureInfos?.Values.Where(x => x.WasDirty()) + .Select(x => x.Culture) + .Select(_localizationService.GetLanguageByIsoCode) + .WhereNotNull() + .Select(x => x.CultureName); + summary.Append("
    "); + if (culturesChanged is not null) { - //Create the HTML based summary (ul of culture names) - - var culturesChanged = content.CultureInfos?.Values.Where(x => x.WasDirty()) - .Select(x => x.Culture) - .Select(_localizationService.GetLanguageByIsoCode) - .WhereNotNull() - .Select(x => x.CultureName); - summary.Append("
      "); - if (culturesChanged is not null) + foreach (var culture in culturesChanged) { - foreach (var culture in culturesChanged) - { - summary.Append("
    • "); - summary.Append(culture); - summary.Append("
    • "); - } + summary.Append("
    • "); + summary.Append(culture); + summary.Append("
    • "); } - - summary.Append("
    "); } - else - { - //Create the text based summary (csv of culture names) - - var culturesChanged = string.Join(", ", content.CultureInfos!.Values.Where(x => x.WasDirty()) - .Select(x => x.Culture) - .Select(_localizationService.GetLanguageByIsoCode) - .WhereNotNull() - .Select(x => x.CultureName)); - summary.Append("'"); - summary.Append(culturesChanged); - summary.Append("'"); - } + summary.Append("
"); } else { - //not supported yet... - throw new NotSupportedException(); - } + //Create the text based summary (csv of culture names) - var protocol = _globalSettings.UseHttps ? "https" : "http"; - - var subjectVars = new NotificationEmailSubjectParams( - string.Concat(siteUri.Authority, _ioHelper.ResolveUrl(_globalSettings.UmbracoPath)), - actionName, - content.Name); - - var bodyVars = new NotificationEmailBodyParams( - mailingUser.Name, - actionName, - content.Name, - content.Id.ToString(CultureInfo.InvariantCulture), - string.Format("{2}://{0}/{1}", - string.Concat(siteUri.Authority), - // TODO: RE-enable this so we can have a nice URL - /*umbraco.library.NiceUrl(documentObject.Id))*/ - string.Concat(content.Id, ".aspx"), - protocol), - performingUser.Name, - string.Concat(siteUri.Authority, _ioHelper.ResolveUrl(_globalSettings.UmbracoPath)), - summary.ToString()); - - var fromMail = _contentSettings.Notifications.Email ?? _globalSettings.Smtp?.From; - - var subject = createSubject((mailingUser, subjectVars)); - var body = ""; - var isBodyHtml = false; - - if (_contentSettings.Notifications.DisableHtmlEmail) - { - body = createBody((user: mailingUser, body: bodyVars, false)); + var culturesChanged = string.Join(", ", content.CultureInfos!.Values.Where(x => x.WasDirty()) + .Select(x => x.Culture) + .Select(_localizationService.GetLanguageByIsoCode) + .WhereNotNull() + .Select(x => x.CultureName)); + + summary.Append("'"); + summary.Append(culturesChanged); + summary.Append("'"); } - else - { - isBodyHtml = true; - body = - string.Concat(@" + } + else + { + //not supported yet... + throw new NotSupportedException(); + } + + var protocol = _globalSettings.UseHttps ? "https" : "http"; + + var subjectVars = new NotificationEmailSubjectParams( + string.Concat(siteUri.Authority, _ioHelper.ResolveUrl(_globalSettings.UmbracoPath)), + actionName, + content.Name); + + var bodyVars = new NotificationEmailBodyParams( + mailingUser.Name, + actionName, + content.Name, + content.Id.ToString(CultureInfo.InvariantCulture), + string.Format("{2}://{0}/{1}", + string.Concat(siteUri.Authority), + // TODO: RE-enable this so we can have a nice URL + /*umbraco.library.NiceUrl(documentObject.Id))*/ + string.Concat(content.Id, ".aspx"), + protocol), + performingUser.Name, + string.Concat(siteUri.Authority, _ioHelper.ResolveUrl(_globalSettings.UmbracoPath)), + summary.ToString()); + + var fromMail = _contentSettings.Notifications.Email ?? _globalSettings.Smtp?.From; + + var subject = createSubject((mailingUser, subjectVars)); + var body = ""; + var isBodyHtml = false; + + if (_contentSettings.Notifications.DisableHtmlEmail) + { + body = createBody((user: mailingUser, body: bodyVars, false)); + } + else + { + isBodyHtml = true; + body = + string.Concat(@" ", createBody((user: mailingUser, body: bodyVars, true))); - } - - // nh, issue 30724. Due to hardcoded http strings in resource files, we need to check for https replacements here - // adding the server name to make sure we don't replace external links - if (_globalSettings.UseHttps && string.IsNullOrEmpty(body) == false) - { - var serverName = siteUri.Host; - body = body.Replace( - $"http://{serverName}", - $"https://{serverName}"); - } - - // create the mail message - var mail = new EmailMessage(fromMail, mailingUser.Email, subject, body, isBodyHtml); - - return new NotificationRequest(mail, actionName, mailingUser.Name, mailingUser.Email); } - private string ReplaceLinks(string text, Uri siteUri) + // nh, issue 30724. Due to hardcoded http strings in resource files, we need to check for https replacements here + // adding the server name to make sure we don't replace external links + if (_globalSettings.UseHttps && string.IsNullOrEmpty(body) == false) { - var sb = new StringBuilder(_globalSettings.UseHttps ? "https://" : "http://"); - sb.Append(siteUri.Authority); - sb.Append("/"); - var domain = sb.ToString(); - text = text.Replace("href=\"/", "href=\"" + domain); - text = text.Replace("src=\"/", "src=\"" + domain); - return text; + var serverName = siteUri.Host; + body = body.Replace( + $"http://{serverName}", + $"https://{serverName}"); } - /// - /// Replaces the HTML symbols with the character equivalent. - /// - /// The old string. - private static void ReplaceHtmlSymbols(ref string? oldString) + // create the mail message + var mail = new EmailMessage(fromMail, mailingUser.Email, subject, body, isBodyHtml); + + return new NotificationRequest(mail, actionName, mailingUser.Name, mailingUser.Email); + } + + private string ReplaceLinks(string text, Uri siteUri) + { + var sb = new StringBuilder(_globalSettings.UseHttps ? "https://" : "http://"); + sb.Append(siteUri.Authority); + sb.Append("/"); + var domain = sb.ToString(); + text = text.Replace("href=\"/", "href=\"" + domain); + text = text.Replace("src=\"/", "src=\"" + domain); + return text; + } + + /// + /// Replaces the HTML symbols with the character equivalent. + /// + /// The old string. + private static void ReplaceHtmlSymbols(ref string? oldString) + { + if (oldString.IsNullOrWhiteSpace()) { - if (oldString.IsNullOrWhiteSpace()) return; - oldString = oldString!.Replace(" ", " "); - oldString = oldString.Replace("’", "'"); - oldString = oldString.Replace("&", "&"); - oldString = oldString.Replace("“", "“"); - oldString = oldString.Replace("”", "”"); - oldString = oldString.Replace(""", "\""); + return; } - // manage notifications - // ideally, would need to use IBackgroundTasks - but they are not part of Core! + oldString = oldString!.Replace(" ", " "); + oldString = oldString.Replace("’", "'"); + oldString = oldString.Replace("&", "&"); + oldString = oldString.Replace("“", "“"); + oldString = oldString.Replace("”", "”"); + oldString = oldString.Replace(""", "\""); + } - private static readonly object Locker = new object(); - private static readonly BlockingCollection Queue = new BlockingCollection(); - private static volatile bool _running; + // manage notifications + // ideally, would need to use IBackgroundTasks - but they are not part of Core! - private void Enqueue(NotificationRequest notification) + private static readonly object Locker = new(); + private static readonly BlockingCollection Queue = new(); + private static volatile bool _running; + + private void Enqueue(NotificationRequest notification) + { + Queue.Add(notification); + if (_running) { - Queue.Add(notification); - if (_running) return; - lock (Locker) - { - if (_running) return; - Process(Queue); - _running = true; - } + return; } - private class NotificationRequest + lock (Locker) { - public NotificationRequest(EmailMessage mail, string? action, string? userName, string? email) + if (_running) { - Mail = mail; - Action = action; - UserName = userName; - Email = email; + return; } - public EmailMessage Mail { get; } + Process(Queue); + _running = true; + } + } - public string? Action { get; } + private class NotificationRequest + { + public NotificationRequest(EmailMessage mail, string? action, string? userName, string? email) + { + Mail = mail; + Action = action; + UserName = userName; + Email = email; + } - public string? UserName { get; } + public EmailMessage Mail { get; } - public string? Email { get; } - } + public string? Action { get; } + + public string? UserName { get; } + + public string? Email { get; } + } - private void Process(BlockingCollection notificationRequests) + private void Process(BlockingCollection notificationRequests) => + ThreadPool.QueueUserWorkItem(state => { - ThreadPool.QueueUserWorkItem(state => + _logger.LogDebug("Begin processing notifications."); + while (true) { - _logger.LogDebug("Begin processing notifications."); - while (true) + NotificationRequest? request; + while (notificationRequests.TryTake(out request, 8 * 1000)) // stay on for 8s { - NotificationRequest? request; - while (notificationRequests.TryTake(out request, 8 * 1000)) // stay on for 8s + try { - try - { - _emailSender.SendAsync(request.Mail, Constants.Web.EmailTypes.Notification).GetAwaiter().GetResult(); - _logger.LogDebug("Notification '{Action}' sent to {Username} ({Email})", request.Action, request.UserName, request.Email); - } - catch (Exception ex) - { - _logger.LogError(ex, "An error occurred sending notification"); - } + _emailSender.SendAsync(request.Mail, Constants.Web.EmailTypes.Notification).GetAwaiter() + .GetResult(); + _logger.LogDebug("Notification '{Action}' sent to {Username} ({Email})", request.Action, + request.UserName, request.Email); } - lock (Locker) + catch (Exception ex) { - if (notificationRequests.Count > 0) continue; // last chance - _running = false; // going down - break; + _logger.LogError(ex, "An error occurred sending notification"); } } - _logger.LogDebug("Done processing notifications."); - }); - } + lock (Locker) + { + if (notificationRequests.Count > 0) + { + continue; // last chance + } - #endregion - } + _running = false; // going down + break; + } + } + + _logger.LogDebug("Done processing notifications."); + }); + + #endregion } diff --git a/src/Umbraco.Core/Services/OperationResult.cs b/src/Umbraco.Core/Services/OperationResult.cs index a69dc6ee126b..d170a3b0efb1 100644 --- a/src/Umbraco.Core/Services/OperationResult.cs +++ b/src/Umbraco.Core/Services/OperationResult.cs @@ -1,246 +1,232 @@ -using System; -using Umbraco.Cms.Core.Events; - -namespace Umbraco.Cms.Core.Services +using Umbraco.Cms.Core.Events; + +namespace Umbraco.Cms.Core.Services; +// TODO: no need for Attempt - the operation result SHOULD KNOW if it's a success or a failure! +// but then each WhateverResultType must + +/// +/// Represents the result of a service operation. +/// +/// The type of the result type. +/// +/// Type must be an enumeration, and its +/// underlying type must be byte. Values indicating success should be in the 0-127 +/// range, while values indicating failure should be in the 128-255 range. See +/// for a base implementation. +/// +public class OperationResult + where TResultType : struct { - // TODO: no need for Attempt - the operation result SHOULD KNOW if it's a success or a failure! - // but then each WhateverResultType must - - /// - /// Represents the result of a service operation. - /// - /// The type of the result type. - /// Type must be an enumeration, and its - /// underlying type must be byte. Values indicating success should be in the 0-127 - /// range, while values indicating failure should be in the 128-255 range. See - /// for a base implementation. - public class OperationResult - where TResultType : struct + static OperationResult() { - /// - /// Initializes a new instance of the class. - /// - public OperationResult(TResultType result, EventMessages? eventMessages) + // ensure that TResultType is an enum and the underlying type is byte + // so we can safely cast in Success and test against 128 for failures + Type type = typeof(TResultType); + if (type.IsEnum == false) { - Result = result; - EventMessages = eventMessages; + throw new InvalidOperationException($"Type {type} is not an enum."); } - static OperationResult() + if (Enum.GetUnderlyingType(type) != typeof(byte)) { - // ensure that TResultType is an enum and the underlying type is byte - // so we can safely cast in Success and test against 128 for failures - var type = typeof(TResultType); - if (type.IsEnum == false) - throw new InvalidOperationException($"Type {type} is not an enum."); - if (Enum.GetUnderlyingType(type) != typeof (byte)) - throw new InvalidOperationException($"Enum {type} underlying type is not ."); + throw new InvalidOperationException($"Enum {type} underlying type is not ."); } + } - /// - /// Gets a value indicating whether the operation was successful. - /// - public bool Success => ((byte) (object) Result & 128) == 0; // we *know* it's a byte + /// + /// Initializes a new instance of the class. + /// + public OperationResult(TResultType result, EventMessages? eventMessages) + { + Result = result; + EventMessages = eventMessages; + } - /// - /// Gets the result of the operation. - /// - public TResultType Result { get; } + /// + /// Gets a value indicating whether the operation was successful. + /// + public bool Success => ((byte)(object)Result & 128) == 0; // we *know* it's a byte - /// - /// Gets the event messages produced by the operation. - /// - public EventMessages? EventMessages { get; } + /// + /// Gets the result of the operation. + /// + public TResultType Result { get; } + + /// + /// Gets the event messages produced by the operation. + /// + public EventMessages? EventMessages { get; } +} + +/// +/// +/// Represents the result of a service operation for a given entity. +/// +/// The type of the result type. +/// The type of the entity. +/// +/// Type must be an enumeration, and its +/// underlying type must be byte. Values indicating success should be in the 0-127 +/// range, while values indicating failure should be in the 128-255 range. See +/// for a base implementation. +/// +public class OperationResult : OperationResult + where TResultType : struct +{ + /// + /// + /// Initializes a new instance of the class. + /// + /// The status of the operation. + /// Event messages produced by the operation. + public OperationResult(TResultType result, EventMessages eventMessages) + : base(result, eventMessages) + { } /// /// - /// Represents the result of a service operation for a given entity. + /// Initializes a new instance of the class. + /// + public OperationResult(TResultType result, EventMessages? eventMessages, TEntity? entity) + : base(result, eventMessages) => + Entity = entity; + + /// + /// Gets the entity. + /// + public TEntity? Entity { get; } +} + +/// +/// +/// Represents the default operation result. +/// +public class OperationResult : OperationResult +{ + /// + /// + /// Initializes a new instance of the class with a status and event messages. /// - /// The type of the result type. - /// The type of the entity. - /// Type must be an enumeration, and its - /// underlying type must be byte. Values indicating success should be in the 0-127 - /// range, while values indicating failure should be in the 128-255 range. See - /// for a base implementation. - public class OperationResult : OperationResult - where TResultType : struct + /// The status of the operation. + /// Event messages produced by the operation. + public OperationResult(OperationResultType result, EventMessages eventMessages) + : base(result, eventMessages) { - /// - /// - /// Initializes a new instance of the class. - /// - /// The status of the operation. - /// Event messages produced by the operation. - public OperationResult(TResultType result, EventMessages eventMessages) - : base(result, eventMessages) - { } + } + + public static OperationResult Succeed(EventMessages eventMessages) => + new OperationResult(OperationResultType.Success, eventMessages); - /// + public static OperationResult Cancel(EventMessages eventMessages) => + new OperationResult(OperationResultType.FailedCancelledByEvent, eventMessages); + + // TODO: this exists to support services that still return Attempt + // these services should directly return an OperationResult, and then this static class should be deleted + public static class Attempt + { /// - /// Initializes a new instance of the class. + /// Creates a successful operation attempt. /// - public OperationResult(TResultType result, EventMessages? eventMessages, TEntity? entity) - : base(result, eventMessages) - { - Entity = entity; - } + /// The event messages produced by the operation. + /// A new attempt instance. + public static Attempt Succeed(EventMessages eventMessages) => + Core.Attempt.Succeed(new OperationResult(OperationResultType.Success, eventMessages)); + + public static Attempt?> + Succeed(EventMessages eventMessages) => Core.Attempt.Succeed( + new OperationResult(OperationResultType.Success, eventMessages)); + + public static Attempt?> + Succeed(EventMessages eventMessages, TValue value) => Core.Attempt.Succeed( + new OperationResult(OperationResultType.Success, eventMessages, value)); + + public static Attempt?> Succeed(TStatusType statusType, + EventMessages eventMessages) + where TStatusType : struct => + Core.Attempt.Succeed(new OperationResult(statusType, eventMessages)); + + public static Attempt?> Succeed( + TStatusType statusType, EventMessages eventMessages, TValue value) + where TStatusType : struct => + Core.Attempt.Succeed(new OperationResult(statusType, eventMessages, value)); /// - /// Gets the entity. + /// Creates a successful operation attempt indicating that nothing was done. /// - public TEntity? Entity { get; } - } + /// The event messages produced by the operation. + /// A new attempt instance. + public static Attempt NoOperation(EventMessages eventMessages) => + Core.Attempt.Succeed(new OperationResult(OperationResultType.NoOperation, eventMessages)); - /// - /// - /// Represents the default operation result. - /// - public class OperationResult : OperationResult - { - /// /// - /// Initializes a new instance of the class with a status and event messages. + /// Creates a failed operation attempt indicating that the operation has been cancelled. /// - /// The status of the operation. - /// Event messages produced by the operation. - public OperationResult(OperationResultType result, EventMessages eventMessages) - : base(result, eventMessages) - { } + /// The event messages produced by the operation. + /// A new attempt instance. + public static Attempt Cancel(EventMessages eventMessages) => + Core.Attempt.Fail(new OperationResult(OperationResultType.FailedCancelledByEvent, eventMessages)); - public static OperationResult Succeed(EventMessages eventMessages) - { - return new OperationResult(OperationResultType.Success, eventMessages); - } + public static Attempt?> + Cancel(EventMessages eventMessages) => Core.Attempt.Fail( + new OperationResult(OperationResultType.FailedCancelledByEvent, + eventMessages)); - public static OperationResult Cancel(EventMessages eventMessages) - { - return new OperationResult(OperationResultType.FailedCancelledByEvent, eventMessages); - } + public static Attempt?> + Cancel(EventMessages eventMessages, TValue value) => Core.Attempt.Fail( + new OperationResult(OperationResultType.FailedCancelledByEvent, eventMessages, + value)); - // TODO: this exists to support services that still return Attempt - // these services should directly return an OperationResult, and then this static class should be deleted - public static class Attempt + /// + /// Creates a failed operation attempt indicating that an exception was thrown during the operation. + /// + /// The event messages produced by the operation. + /// The exception that caused the operation to fail. + /// A new attempt instance. + public static Attempt Fail(EventMessages eventMessages, Exception exception) { - /// - /// Creates a successful operation attempt. - /// - /// The event messages produced by the operation. - /// A new attempt instance. - public static Attempt Succeed(EventMessages eventMessages) - { - return Core.Attempt.Succeed(new OperationResult(OperationResultType.Success, eventMessages)); - } - - public static Attempt?> Succeed(EventMessages eventMessages) - { - return Core.Attempt.Succeed(new OperationResult(OperationResultType.Success, eventMessages)); - } - - public static Attempt?> Succeed(EventMessages eventMessages, TValue value) - { - return Core.Attempt.Succeed(new OperationResult(OperationResultType.Success, eventMessages, value)); - } - - public static Attempt?> Succeed(TStatusType statusType, EventMessages eventMessages) - where TStatusType : struct - { - return Core.Attempt.Succeed(new OperationResult(statusType, eventMessages)); - } - - public static Attempt?> Succeed(TStatusType statusType, EventMessages eventMessages, TValue value) - where TStatusType : struct - { - return Core.Attempt.Succeed(new OperationResult(statusType, eventMessages, value)); - } - - /// - /// Creates a successful operation attempt indicating that nothing was done. - /// - /// The event messages produced by the operation. - /// A new attempt instance. - public static Attempt NoOperation(EventMessages eventMessages) - { - return Core.Attempt.Succeed(new OperationResult(OperationResultType.NoOperation, eventMessages)); - } - - /// - /// Creates a failed operation attempt indicating that the operation has been cancelled. - /// - /// The event messages produced by the operation. - /// A new attempt instance. - public static Attempt Cancel(EventMessages eventMessages) - { - return Core.Attempt.Fail(new OperationResult(OperationResultType.FailedCancelledByEvent, eventMessages)); - } - - public static Attempt?> Cancel(EventMessages eventMessages) - { - return Core.Attempt.Fail(new OperationResult(OperationResultType.FailedCancelledByEvent, eventMessages)); - } - - public static Attempt?> Cancel(EventMessages eventMessages, TValue value) - { - return Core.Attempt.Fail(new OperationResult(OperationResultType.FailedCancelledByEvent, eventMessages, value)); - } - - /// - /// Creates a failed operation attempt indicating that an exception was thrown during the operation. - /// - /// The event messages produced by the operation. - /// The exception that caused the operation to fail. - /// A new attempt instance. - public static Attempt Fail(EventMessages eventMessages, Exception exception) - { - eventMessages.Add(new EventMessage("", exception.Message, EventMessageType.Error)); - return Core.Attempt.Fail(new OperationResult(OperationResultType.FailedExceptionThrown, eventMessages), exception); - } - - public static Attempt?> Fail(EventMessages eventMessages, Exception exception) - { - return Core.Attempt.Fail(new OperationResult(OperationResultType.FailedExceptionThrown, eventMessages), exception); - } - - public static Attempt?> Fail(TStatusType statusType, EventMessages eventMessages) - where TStatusType : struct - { - return Core.Attempt.Fail(new OperationResult(statusType, eventMessages)); - } - - public static Attempt?> Fail(TStatusType statusType, EventMessages eventMessages, Exception exception) - where TStatusType : struct - { - return Core.Attempt.Fail(new OperationResult(statusType, eventMessages), exception); - } - - public static Attempt?> Fail(TStatusType statusType, EventMessages eventMessages) - where TStatusType : struct - { - return Core.Attempt.Fail(new OperationResult(statusType, eventMessages)); - } - - public static Attempt?> Fail(TStatusType statusType, EventMessages eventMessages, TValue value) - where TStatusType : struct - { - return Core.Attempt.Fail(new OperationResult(statusType, eventMessages, value)); - } - - public static Attempt?> Fail(TStatusType statusType, EventMessages eventMessages, Exception exception) - where TStatusType : struct - { - return Core.Attempt.Fail(new OperationResult(statusType, eventMessages), exception); - } - - public static Attempt?> Fail(TStatusType statusType, EventMessages eventMessages, TValue value, Exception exception) - where TStatusType : struct - { - return Core.Attempt.Fail(new OperationResult(statusType, eventMessages, value), exception); - } - - public static Attempt?> Cannot(EventMessages eventMessages) - { - return Core.Attempt.Fail(new OperationResult(OperationResultType.FailedCannot, eventMessages)); - } + eventMessages.Add(new EventMessage("", exception.Message, EventMessageType.Error)); + return Core.Attempt.Fail(new OperationResult(OperationResultType.FailedExceptionThrown, eventMessages), + exception); } + + public static Attempt?> + Fail(EventMessages eventMessages, Exception exception) => Core.Attempt.Fail( + new OperationResult(OperationResultType.FailedExceptionThrown, eventMessages), + exception); + + public static Attempt?> Fail(TStatusType statusType, + EventMessages eventMessages) + where TStatusType : struct => + Core.Attempt.Fail(new OperationResult(statusType, eventMessages)); + + public static Attempt?> Fail(TStatusType statusType, + EventMessages eventMessages, Exception exception) + where TStatusType : struct => + Core.Attempt.Fail(new OperationResult(statusType, eventMessages), exception); + + public static Attempt?> Fail(TStatusType statusType, + EventMessages eventMessages) + where TStatusType : struct => + Core.Attempt.Fail(new OperationResult(statusType, eventMessages)); + + public static Attempt?> Fail(TStatusType statusType, + EventMessages eventMessages, TValue value) + where TStatusType : struct => + Core.Attempt.Fail(new OperationResult(statusType, eventMessages, value)); + + public static Attempt?> Fail(TStatusType statusType, + EventMessages eventMessages, Exception exception) + where TStatusType : struct => + Core.Attempt.Fail(new OperationResult(statusType, eventMessages), exception); + + public static Attempt?> Fail(TStatusType statusType, + EventMessages eventMessages, TValue value, Exception exception) + where TStatusType : struct => + Core.Attempt.Fail(new OperationResult(statusType, eventMessages, value), exception); + + public static Attempt?> + Cannot(EventMessages eventMessages) => Core.Attempt.Fail( + new OperationResult(OperationResultType.FailedCannot, eventMessages)); } } diff --git a/src/Umbraco.Core/Services/OperationResultType.cs b/src/Umbraco.Core/Services/OperationResultType.cs index 15b332e43c35..1c3cfa38845f 100644 --- a/src/Umbraco.Core/Services/OperationResultType.cs +++ b/src/Umbraco.Core/Services/OperationResultType.cs @@ -1,45 +1,44 @@ -namespace Umbraco.Cms.Core.Services +namespace Umbraco.Cms.Core.Services; + +/// +/// A value indicating the result of an operation. +/// +public enum OperationResultType : byte { + // all "ResultType" enum's must be byte-based, and declare Failed = 128, and declare + // every failure codes as >128 - see OperationResult and OperationResultType for details. + + /// + /// The operation was successful. + /// + Success = 0, + + /// + /// The operation failed. + /// + /// All values above this value indicate a failure. + Failed = 128, + + /// + /// The operation could not complete because of invalid preconditions (eg creating a reference + /// to an item that does not exist). + /// + FailedCannot = Failed | 2, + + /// + /// The operation has been cancelled by an event handler. + /// + FailedCancelledByEvent = Failed | 4, + /// - /// A value indicating the result of an operation. + /// The operation could not complete due to an exception. /// - public enum OperationResultType : byte - { - // all "ResultType" enum's must be byte-based, and declare Failed = 128, and declare - // every failure codes as >128 - see OperationResult and OperationResultType for details. - - /// - /// The operation was successful. - /// - Success = 0, - - /// - /// The operation failed. - /// - /// All values above this value indicate a failure. - Failed = 128, - - /// - /// The operation could not complete because of invalid preconditions (eg creating a reference - /// to an item that does not exist). - /// - FailedCannot = Failed | 2, - - /// - /// The operation has been cancelled by an event handler. - /// - FailedCancelledByEvent = Failed | 4, - - /// - /// The operation could not complete due to an exception. - /// - FailedExceptionThrown = Failed | 5, - - /// - /// No operation has been executed because it was not needed (eg deleting an item that doesn't exist). - /// - NoOperation = Failed | 6, // TODO: shouldn't it be a success? - - // TODO: In the future, we might need to add more operations statuses, potentially like 'FailedByPermissions', etc... - } + FailedExceptionThrown = Failed | 5, + + /// + /// No operation has been executed because it was not needed (eg deleting an item that doesn't exist). + /// + NoOperation = Failed | 6 // TODO: shouldn't it be a success? + + // TODO: In the future, we might need to add more operations statuses, potentially like 'FailedByPermissions', etc... } diff --git a/src/Umbraco.Core/Services/Ordering.cs b/src/Umbraco.Core/Services/Ordering.cs index 513654428bbe..0a3f5fa911ca 100644 --- a/src/Umbraco.Core/Services/Ordering.cs +++ b/src/Umbraco.Core/Services/Ordering.cs @@ -1,81 +1,94 @@ -using Umbraco.Extensions; +using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services +namespace Umbraco.Cms.Core.Services; + +/// +/// Represents ordering information. +/// +public class Ordering { + private static readonly Ordering DefaultOrdering = new(null); + /// - /// Represents ordering information. + /// Initializes a new instance of the class. /// - public class Ordering + /// The name of the ordering field. + /// The ordering direction. + /// The (ISO) culture to consider when sorting multi-lingual fields. + /// A value indicating whether the ordering field is a custom user property. + /// + /// + /// The can be null, meaning: not sorting. If it is the empty string, it becomes + /// null. + /// + /// + /// The can be the empty string, meaning: invariant. If it is null, it becomes the + /// empty string. + /// + /// + public Ordering(string? orderBy, Direction direction = Direction.Ascending, string? culture = null, + bool isCustomField = false) { - private static readonly Ordering DefaultOrdering = new Ordering(null); - - /// - /// Initializes a new instance of the class. - /// - /// The name of the ordering field. - /// The ordering direction. - /// The (ISO) culture to consider when sorting multi-lingual fields. - /// A value indicating whether the ordering field is a custom user property. - /// - /// The can be null, meaning: not sorting. If it is the empty string, it becomes null. - /// The can be the empty string, meaning: invariant. If it is null, it becomes the empty string. - /// - public Ordering(string? orderBy, Direction direction = Direction.Ascending, string? culture = null, bool isCustomField = false) - { - OrderBy = orderBy.IfNullOrWhiteSpace(null); // empty is null and means, not sorting - Direction = direction; - Culture = culture.IfNullOrWhiteSpace(string.Empty); // empty is "" and means, invariant - IsCustomField = isCustomField; - } + OrderBy = orderBy.IfNullOrWhiteSpace(null); // empty is null and means, not sorting + Direction = direction; + Culture = culture.IfNullOrWhiteSpace(string.Empty); // empty is "" and means, invariant + IsCustomField = isCustomField; + } - /// - /// Creates a new instance of the class. - /// - /// The name of the ordering field. - /// The ordering direction. - /// The (ISO) culture to consider when sorting multi-lingual fields. - /// A value indicating whether the ordering field is a custom user property. - /// - /// The can be null, meaning: not sorting. If it is the empty string, it becomes null. - /// The can be the empty string, meaning: invariant. If it is null, it becomes the empty string. - /// - public static Ordering By(string orderBy, Direction direction = Direction.Ascending, string? culture = null, bool isCustomField = false) - => new Ordering(orderBy, direction, culture, isCustomField); + /// + /// Gets the name of the ordering field. + /// + public string? OrderBy { get; } - /// - /// Gets the default instance. - /// - public static Ordering ByDefault() - => DefaultOrdering; + /// + /// Gets the ordering direction. + /// + public Direction Direction { get; } - /// - /// Gets the name of the ordering field. - /// - public string? OrderBy { get; } + /// + /// Gets (ISO) culture to consider when sorting multi-lingual fields. + /// + public string? Culture { get; } - /// - /// Gets the ordering direction. - /// - public Direction Direction { get; } + /// + /// Gets a value indicating whether the ordering field is a custom user property. + /// + public bool IsCustomField { get; } - /// - /// Gets (ISO) culture to consider when sorting multi-lingual fields. - /// - public string? Culture { get; } + /// + /// Gets a value indicating whether this ordering is the default ordering. + /// + public bool IsEmpty => this == DefaultOrdering || OrderBy == null; - /// - /// Gets a value indicating whether the ordering field is a custom user property. - /// - public bool IsCustomField { get; } + /// + /// Gets a value indicating whether the culture of this ordering is invariant. + /// + public bool IsInvariant => this == DefaultOrdering || Culture == string.Empty; - /// - /// Gets a value indicating whether this ordering is the default ordering. - /// - public bool IsEmpty => this == DefaultOrdering || OrderBy == null; + /// + /// Creates a new instance of the class. + /// + /// The name of the ordering field. + /// The ordering direction. + /// The (ISO) culture to consider when sorting multi-lingual fields. + /// A value indicating whether the ordering field is a custom user property. + /// + /// + /// The can be null, meaning: not sorting. If it is the empty string, it becomes + /// null. + /// + /// + /// The can be the empty string, meaning: invariant. If it is null, it becomes the + /// empty string. + /// + /// + public static Ordering By(string orderBy, Direction direction = Direction.Ascending, string? culture = null, + bool isCustomField = false) + => new(orderBy, direction, culture, isCustomField); - /// - /// Gets a value indicating whether the culture of this ordering is invariant. - /// - public bool IsInvariant => this == DefaultOrdering || Culture == string.Empty; - } + /// + /// Gets the default instance. + /// + public static Ordering ByDefault() + => DefaultOrdering; } diff --git a/src/Umbraco.Core/Services/ProcessInstructionsResult.cs b/src/Umbraco.Core/Services/ProcessInstructionsResult.cs index 9a368dab7e35..460860d7456f 100644 --- a/src/Umbraco.Core/Services/ProcessInstructionsResult.cs +++ b/src/Umbraco.Core/Services/ProcessInstructionsResult.cs @@ -1,26 +1,29 @@ -using System; +namespace Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.Services +/// +/// Defines a result object for the +/// operation. +/// +public class ProcessInstructionsResult { - /// - /// Defines a result object for the operation. - /// - public class ProcessInstructionsResult + private ProcessInstructionsResult() { - private ProcessInstructionsResult() - { - } + } - public int NumberOfInstructionsProcessed { get; private set; } + public int NumberOfInstructionsProcessed { get; private set; } - public int LastId { get; private set; } + public int LastId { get; private set; } - public bool InstructionsWerePruned { get; private set; } + public bool InstructionsWerePruned { get; private set; } - public static ProcessInstructionsResult AsCompleted(int numberOfInstructionsProcessed, int lastId) => - new ProcessInstructionsResult { NumberOfInstructionsProcessed = numberOfInstructionsProcessed, LastId = lastId }; + public static ProcessInstructionsResult AsCompleted(int numberOfInstructionsProcessed, int lastId) => + new() {NumberOfInstructionsProcessed = numberOfInstructionsProcessed, LastId = lastId}; - public static ProcessInstructionsResult AsCompletedAndPruned(int numberOfInstructionsProcessed, int lastId) => - new ProcessInstructionsResult { NumberOfInstructionsProcessed = numberOfInstructionsProcessed, LastId = lastId, InstructionsWerePruned = true }; - }; + public static ProcessInstructionsResult AsCompletedAndPruned(int numberOfInstructionsProcessed, int lastId) => + new() + { + NumberOfInstructionsProcessed = numberOfInstructionsProcessed, + LastId = lastId, + InstructionsWerePruned = true + }; } diff --git a/src/Umbraco.Core/Services/PropertyValidationService.cs b/src/Umbraco.Core/Services/PropertyValidationService.cs index c5a431277633..50c6c3b5bad7 100644 --- a/src/Umbraco.Core/Services/PropertyValidationService.cs +++ b/src/Umbraco.Core/Services/PropertyValidationService.cs @@ -1,198 +1,221 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; +using System.ComponentModel.DataAnnotations; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services +namespace Umbraco.Cms.Core.Services; + +public class PropertyValidationService : IPropertyValidationService { - public class PropertyValidationService : IPropertyValidationService + private readonly IDataTypeService _dataTypeService; + private readonly PropertyEditorCollection _propertyEditors; + private readonly ILocalizedTextService _textService; + private readonly IValueEditorCache _valueEditorCache; + + public PropertyValidationService( + PropertyEditorCollection propertyEditors, + IDataTypeService dataTypeService, + ILocalizedTextService textService, + IValueEditorCache valueEditorCache) + { + _propertyEditors = propertyEditors; + _dataTypeService = dataTypeService; + _textService = textService; + _valueEditorCache = valueEditorCache; + } + + /// + public IEnumerable ValidatePropertyValue( + IPropertyType propertyType, + object? postedValue) { - private readonly PropertyEditorCollection _propertyEditors; - private readonly IDataTypeService _dataTypeService; - private readonly ILocalizedTextService _textService; - private readonly IValueEditorCache _valueEditorCache; - - public PropertyValidationService( - PropertyEditorCollection propertyEditors, - IDataTypeService dataTypeService, - ILocalizedTextService textService, - IValueEditorCache valueEditorCache) + if (propertyType is null) { - _propertyEditors = propertyEditors; - _dataTypeService = dataTypeService; - _textService = textService; - _valueEditorCache = valueEditorCache; + throw new ArgumentNullException(nameof(propertyType)); } - /// - public IEnumerable ValidatePropertyValue( - IPropertyType propertyType, - object? postedValue) + IDataType dataType = _dataTypeService.GetDataType(propertyType.DataTypeId); + if (dataType == null) { - if (propertyType is null) throw new ArgumentNullException(nameof(propertyType)); - var dataType = _dataTypeService.GetDataType(propertyType.DataTypeId); - if (dataType == null) throw new InvalidOperationException("No data type found by id " + propertyType.DataTypeId); - - var editor = _propertyEditors[propertyType.PropertyEditorAlias]; - if (editor == null) throw new InvalidOperationException("No property editor found by alias " + propertyType.PropertyEditorAlias); + throw new InvalidOperationException("No data type found by id " + propertyType.DataTypeId); + } - return ValidatePropertyValue(editor, dataType, postedValue, propertyType.Mandatory, propertyType.ValidationRegExp, propertyType.MandatoryMessage, propertyType.ValidationRegExpMessage); + IDataEditor editor = _propertyEditors[propertyType.PropertyEditorAlias]; + if (editor == null) + { + throw new InvalidOperationException("No property editor found by alias " + + propertyType.PropertyEditorAlias); } - /// - public IEnumerable ValidatePropertyValue( - IDataEditor editor, - IDataType dataType, - object? postedValue, - bool isRequired, - string? validationRegExp, - string? isRequiredMessage, - string? validationRegExpMessage) + return ValidatePropertyValue(editor, dataType, postedValue, propertyType.Mandatory, + propertyType.ValidationRegExp, propertyType.MandatoryMessage, propertyType.ValidationRegExpMessage); + } + + /// + public IEnumerable ValidatePropertyValue( + IDataEditor editor, + IDataType dataType, + object? postedValue, + bool isRequired, + string? validationRegExp, + string? isRequiredMessage, + string? validationRegExpMessage) + { + // Retrieve default messages used for required and regex validatation. We'll replace these + // if set with custom ones if they've been provided for a given property. + var requiredDefaultMessages = new[] + { + _textService.Localize("validation", "invalidNull"), _textService.Localize("validation", "invalidEmpty") + }; + var formatDefaultMessages = new[] {_textService.Localize("validation", "invalidPattern")}; + + IDataValueEditor valueEditor = _valueEditorCache.GetValueEditor(editor, dataType); + foreach (ValidationResult validationResult in valueEditor.Validate(postedValue, isRequired, validationRegExp)) { - // Retrieve default messages used for required and regex validatation. We'll replace these - // if set with custom ones if they've been provided for a given property. - var requiredDefaultMessages = new[] - { - _textService.Localize("validation", "invalidNull"), - _textService.Localize("validation", "invalidEmpty") - }; - var formatDefaultMessages = new[] - { - _textService.Localize("validation", "invalidPattern"), - }; - - IDataValueEditor valueEditor = _valueEditorCache.GetValueEditor(editor, dataType); - foreach (var validationResult in valueEditor.Validate(postedValue, isRequired, validationRegExp)) + // If we've got custom error messages, we'll replace the default ones that will have been applied in the call to Validate(). + if (isRequired && !string.IsNullOrWhiteSpace(isRequiredMessage) && + requiredDefaultMessages.Contains(validationResult.ErrorMessage, StringComparer.OrdinalIgnoreCase)) { - // If we've got custom error messages, we'll replace the default ones that will have been applied in the call to Validate(). - if (isRequired && !string.IsNullOrWhiteSpace(isRequiredMessage) && requiredDefaultMessages.Contains(validationResult.ErrorMessage, StringComparer.OrdinalIgnoreCase)) - { - validationResult.ErrorMessage = isRequiredMessage; - } - if (!string.IsNullOrWhiteSpace(validationRegExp) && !string.IsNullOrWhiteSpace(validationRegExpMessage) && formatDefaultMessages.Contains(validationResult.ErrorMessage, StringComparer.OrdinalIgnoreCase)) - { - validationResult.ErrorMessage = validationRegExpMessage; - } - yield return validationResult; + validationResult.ErrorMessage = isRequiredMessage; } + + if (!string.IsNullOrWhiteSpace(validationRegExp) && !string.IsNullOrWhiteSpace(validationRegExpMessage) && + formatDefaultMessages.Contains(validationResult.ErrorMessage, StringComparer.OrdinalIgnoreCase)) + { + validationResult.ErrorMessage = validationRegExpMessage; + } + + yield return validationResult; } + } - /// - public bool IsPropertyDataValid(IContent content, out IProperty[] invalidProperties, CultureImpact? impact) + /// + public bool IsPropertyDataValid(IContent content, out IProperty[] invalidProperties, CultureImpact? impact) + { + // select invalid properties + invalidProperties = content.Properties.Where(x => { - // select invalid properties - invalidProperties = content.Properties.Where(x => - { - var propertyTypeVaries = x.PropertyType.VariesByCulture(); + var propertyTypeVaries = x.PropertyType.VariesByCulture(); - if (impact is null) - { - return false; - } - // impacts invariant = validate invariant property, invariant culture - if (impact.ImpactsOnlyInvariantCulture) - return !(propertyTypeVaries || IsPropertyValid(x, null)); + if (impact is null) + { + return false; + } - // impacts all = validate property, all cultures (incl. invariant) - if (impact.ImpactsAllCultures) - return !IsPropertyValid(x); + // impacts invariant = validate invariant property, invariant culture + if (impact.ImpactsOnlyInvariantCulture) + { + return !(propertyTypeVaries || IsPropertyValid(x, null)); + } - // impacts explicit culture = validate variant property, explicit culture - if (propertyTypeVaries) - return !IsPropertyValid(x, impact.Culture); + // impacts all = validate property, all cultures (incl. invariant) + if (impact.ImpactsAllCultures) + { + return !IsPropertyValid(x); + } - // and, for explicit culture, we may also have to validate invariant property, invariant culture - // if either - // - it is impacted (default culture), or - // - there is no published version of the content - maybe non-default culture, but no published version + // impacts explicit culture = validate variant property, explicit culture + if (propertyTypeVaries) + { + return !IsPropertyValid(x, impact.Culture); + } - var alsoInvariant = impact.ImpactsAlsoInvariantProperties || !content.Published; - return alsoInvariant && !IsPropertyValid(x, null); + // and, for explicit culture, we may also have to validate invariant property, invariant culture + // if either + // - it is impacted (default culture), or + // - there is no published version of the content - maybe non-default culture, but no published version - }).ToArray(); + var alsoInvariant = impact.ImpactsAlsoInvariantProperties || !content.Published; + return alsoInvariant && !IsPropertyValid(x, null); + }).ToArray(); - return invalidProperties.Length == 0; - } + return invalidProperties.Length == 0; + } - /// - public bool IsPropertyValid(IProperty property, string? culture = "*", string? segment = "*") - { - //NOTE - the pvalue and vvalues logic in here is borrowed directly from the Property.Values setter so if you are wondering what that's all about, look there. - // The underlying Property._pvalue and Property._vvalues are not exposed but we can re-create these values ourselves which is what it's doing. + /// + public bool IsPropertyValid(IProperty property, string? culture = "*", string? segment = "*") + { + //NOTE - the pvalue and vvalues logic in here is borrowed directly from the Property.Values setter so if you are wondering what that's all about, look there. + // The underlying Property._pvalue and Property._vvalues are not exposed but we can re-create these values ourselves which is what it's doing. - culture = culture?.NullOrWhiteSpaceAsNull(); - segment = segment?.NullOrWhiteSpaceAsNull(); + culture = culture?.NullOrWhiteSpaceAsNull(); + segment = segment?.NullOrWhiteSpaceAsNull(); - IPropertyValue? pvalue = null; + IPropertyValue? pvalue = null; - // if validating invariant/neutral, and it is supported, validate - // (including ensuring that the value exists, if mandatory) - if ((culture == null || culture == "*") && (segment == null || segment == "*") && property.PropertyType.SupportsVariation(null, null)) + // if validating invariant/neutral, and it is supported, validate + // (including ensuring that the value exists, if mandatory) + if ((culture == null || culture == "*") && (segment == null || segment == "*") && + property.PropertyType.SupportsVariation(null, null)) + { + // validate pvalue (which is the invariant value) + pvalue = property.Values.FirstOrDefault(x => x.Culture == null && x.Segment == null); + if (!IsValidPropertyValue(property, pvalue?.EditedValue)) { - // validate pvalue (which is the invariant value) - pvalue = property.Values.FirstOrDefault(x => x.Culture == null && x.Segment == null); - if (!IsValidPropertyValue(property, pvalue?.EditedValue)) - return false; + return false; } + } - // if validating only invariant/neutral, we are good - if (culture == null && segment == null) - return true; + // if validating only invariant/neutral, we are good + if (culture == null && segment == null) + { + return true; + } - // if nothing else to validate, we are good - if ((culture == null || culture == "*") && (segment == null || segment == "*") && !property.PropertyType.VariesByCulture()) - return true; + // if nothing else to validate, we are good + if ((culture == null || culture == "*") && (segment == null || segment == "*") && + !property.PropertyType.VariesByCulture()) + { + return true; + } - // for anything else, validate the existing values (including mandatory), - // but we cannot validate mandatory globally (we don't know the possible cultures and segments) + // for anything else, validate the existing values (including mandatory), + // but we cannot validate mandatory globally (we don't know the possible cultures and segments) - // validate vvalues (which are the variant values) + // validate vvalues (which are the variant values) - // if we don't have vvalues (property.Values is empty or only contains pvalue), validate null - if (property.Values.Count == (pvalue == null ? 0 : 1)) - return culture == "*" || IsValidPropertyValue(property, null); + // if we don't have vvalues (property.Values is empty or only contains pvalue), validate null + if (property.Values.Count == (pvalue == null ? 0 : 1)) + { + return culture == "*" || IsValidPropertyValue(property, null); + } - // else validate vvalues (but don't revalidate pvalue) - var pvalues = property.Values.Where(x => - x != pvalue && // don't revalidate pvalue - property.PropertyType.SupportsVariation(x.Culture, x.Segment, true) && // the value variation is ok - (culture == "*" || (x.Culture?.InvariantEquals(culture) ?? false)) && // the culture matches - (segment == "*" || (x.Segment?.InvariantEquals(segment) ?? false))) // the segment matches - .ToList(); + // else validate vvalues (but don't revalidate pvalue) + var pvalues = property.Values.Where(x => + x != pvalue && // don't revalidate pvalue + property.PropertyType.SupportsVariation(x.Culture, x.Segment, true) && // the value variation is ok + (culture == "*" || (x.Culture?.InvariantEquals(culture) ?? false)) && // the culture matches + (segment == "*" || (x.Segment?.InvariantEquals(segment) ?? false))) // the segment matches + .ToList(); - return pvalues.Count == 0 || pvalues.All(x => IsValidPropertyValue(property, x.EditedValue)); - } + return pvalues.Count == 0 || pvalues.All(x => IsValidPropertyValue(property, x.EditedValue)); + } - /// - /// Boolean indicating whether the passed in value is valid - /// - /// - /// - /// True is property value is valid, otherwise false - private bool IsValidPropertyValue(IProperty property, object? value) + /// + /// Boolean indicating whether the passed in value is valid + /// + /// + /// + /// True is property value is valid, otherwise false + private bool IsValidPropertyValue(IProperty property, object? value) => + IsPropertyValueValid(property.PropertyType, value); + + /// + /// Determines whether a value is valid for this property type. + /// + private bool IsPropertyValueValid(IPropertyType propertyType, object? value) + { + IDataEditor editor = _propertyEditors[propertyType.PropertyEditorAlias]; + if (editor == null) { - return IsPropertyValueValid(property.PropertyType, value); + // nothing much we can do validation wise if the property editor has been removed. + // the property will be displayed as a label, so flagging it as invalid would be pointless. + return true; } - /// - /// Determines whether a value is valid for this property type. - /// - private bool IsPropertyValueValid(IPropertyType propertyType, object? value) - { - var editor = _propertyEditors[propertyType.PropertyEditorAlias]; - if (editor == null) - { - // nothing much we can do validation wise if the property editor has been removed. - // the property will be displayed as a label, so flagging it as invalid would be pointless. - return true; - } - var configuration = _dataTypeService.GetDataType(propertyType.DataTypeId)?.Configuration; - var valueEditor = editor.GetValueEditor(configuration); - return !valueEditor.Validate(value, propertyType.Mandatory, propertyType.ValidationRegExp).Any(); - } + var configuration = _dataTypeService.GetDataType(propertyType.DataTypeId)?.Configuration; + IDataValueEditor valueEditor = editor.GetValueEditor(configuration); + return !valueEditor.Validate(value, propertyType.Mandatory, propertyType.ValidationRegExp).Any(); } } diff --git a/src/Umbraco.Core/Services/PublicAccessService.cs b/src/Umbraco.Core/Services/PublicAccessService.cs index b6216e4b5830..93eda7694220 100644 --- a/src/Umbraco.Core/Services/PublicAccessService.cs +++ b/src/Umbraco.Core/Services/PublicAccessService.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; @@ -10,228 +7,242 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services +namespace Umbraco.Cms.Core.Services; + +internal class PublicAccessService : RepositoryService, IPublicAccessService { - internal class PublicAccessService : RepositoryService, IPublicAccessService + private readonly IPublicAccessRepository _publicAccessRepository; + + public PublicAccessService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IPublicAccessRepository publicAccessRepository) + : base(provider, loggerFactory, eventMessagesFactory) => + _publicAccessRepository = publicAccessRepository; + + /// + /// Gets all defined entries and associated rules + /// + /// + public IEnumerable GetAll() { - private readonly IPublicAccessRepository _publicAccessRepository; - - public PublicAccessService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - IPublicAccessRepository publicAccessRepository) - : base(provider, loggerFactory, eventMessagesFactory) + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - _publicAccessRepository = publicAccessRepository; + return _publicAccessRepository.GetMany(); } + } - /// - /// Gets all defined entries and associated rules - /// - /// - public IEnumerable GetAll() - { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _publicAccessRepository.GetMany(); - } - } + /// + /// Gets the entry defined for the content item's path + /// + /// + /// Returns null if no entry is found + public PublicAccessEntry? GetEntryForContent(IContent content) => + GetEntryForContent(content.Path.EnsureEndsWith("," + content.Id)); + + /// + /// Gets the entry defined for the content item based on a content path + /// + /// + /// Returns null if no entry is found + /// + /// NOTE: This method get's called *very* often! This will return the results from cache + /// + public PublicAccessEntry? GetEntryForContent(string contentPath) + { + //Get all ids in the path for the content item and ensure they all + // parse to ints that are not -1. + var ids = contentPath.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) + .Select(x => int.TryParse(x, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val) ? val : -1) + .Where(x => x != -1) + .ToList(); - /// - /// Gets the entry defined for the content item's path - /// - /// - /// Returns null if no entry is found - public PublicAccessEntry? GetEntryForContent(IContent content) - { - return GetEntryForContent(content.Path.EnsureEndsWith("," + content.Id)); - } + //start with the deepest id + ids.Reverse(); - /// - /// Gets the entry defined for the content item based on a content path - /// - /// - /// Returns null if no entry is found - /// - /// NOTE: This method get's called *very* often! This will return the results from cache - /// - public PublicAccessEntry? GetEntryForContent(string contentPath) + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - //Get all ids in the path for the content item and ensure they all - // parse to ints that are not -1. - var ids = contentPath.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) - .Select(x => int.TryParse(x, NumberStyles.Integer, CultureInfo.InvariantCulture, out int val) ? val : -1) - .Where(x => x != -1) - .ToList(); - - //start with the deepest id - ids.Reverse(); - - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + //This will retrieve from cache! + var entries = _publicAccessRepository.GetMany().ToList(); + foreach (var id in ids) { - //This will retrieve from cache! - var entries = _publicAccessRepository.GetMany().ToList(); - foreach (var id in ids) + PublicAccessEntry found = entries.FirstOrDefault(x => x.ProtectedNodeId == id); + if (found != null) { - var found = entries.FirstOrDefault(x => x.ProtectedNodeId == id); - if (found != null) return found; + return found; } } - - return null; } - /// - /// Returns true if the content has an entry for it's path - /// - /// - /// - public Attempt IsProtected(IContent content) - { - var result = GetEntryForContent(content); - return Attempt.If(result != null, result); - } + return null; + } - /// - /// Returns true if the content has an entry based on a content path - /// - /// - /// - public Attempt IsProtected(string contentPath) - { - var result = GetEntryForContent(contentPath); - return Attempt.If(result != null, result); - } + /// + /// Returns true if the content has an entry for it's path + /// + /// + /// + public Attempt IsProtected(IContent content) + { + PublicAccessEntry result = GetEntryForContent(content); + return Attempt.If(result != null, result); + } - /// - /// Adds a rule - /// - /// - /// - /// - /// - public Attempt?> AddRule(IContent content, string ruleType, string ruleValue) + /// + /// Returns true if the content has an entry based on a content path + /// + /// + /// + public Attempt IsProtected(string contentPath) + { + PublicAccessEntry result = GetEntryForContent(contentPath); + return Attempt.If(result != null, result); + } + + /// + /// Adds a rule + /// + /// + /// + /// + /// + public Attempt?> AddRule(IContent content, string ruleType, + string ruleValue) + { + EventMessages evtMsgs = EventMessagesFactory.Get(); + PublicAccessEntry? entry; + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - var evtMsgs = EventMessagesFactory.Get(); - PublicAccessEntry? entry; - using (var scope = ScopeProvider.CreateCoreScope()) + entry = _publicAccessRepository.GetMany().FirstOrDefault(x => x.ProtectedNodeId == content.Id); + if (entry == null) { - entry = _publicAccessRepository.GetMany().FirstOrDefault(x => x.ProtectedNodeId == content.Id); - if (entry == null) - return OperationResult.Attempt.Cannot(evtMsgs); // causes rollback - - var existingRule = entry.Rules.FirstOrDefault(x => x.RuleType == ruleType && x.RuleValue == ruleValue); - if (existingRule == null) - { - entry.AddRule(ruleValue, ruleType); - } - else - { - //If they are both the same already then there's nothing to update, exit - return OperationResult.Attempt.Succeed(evtMsgs, entry); - } - - var savingNotifiation = new PublicAccessEntrySavingNotification(entry, evtMsgs); - if (scope.Notifications.PublishCancelable(savingNotifiation)) - { - scope.Complete(); - return OperationResult.Attempt.Cancel(evtMsgs, entry); - } + return OperationResult.Attempt.Cannot(evtMsgs); // causes rollback + } - _publicAccessRepository.Save(entry); + PublicAccessRule existingRule = + entry.Rules.FirstOrDefault(x => x.RuleType == ruleType && x.RuleValue == ruleValue); + if (existingRule == null) + { + entry.AddRule(ruleValue, ruleType); + } + else + { + //If they are both the same already then there's nothing to update, exit + return OperationResult.Attempt.Succeed(evtMsgs, entry); + } + var savingNotifiation = new PublicAccessEntrySavingNotification(entry, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotifiation)) + { scope.Complete(); - - scope.Notifications.Publish(new PublicAccessEntrySavedNotification(entry, evtMsgs).WithStateFrom(savingNotifiation)); + return OperationResult.Attempt.Cancel(evtMsgs, entry); } - return OperationResult.Attempt.Succeed(evtMsgs, entry); + _publicAccessRepository.Save(entry); + + scope.Complete(); + + scope.Notifications.Publish( + new PublicAccessEntrySavedNotification(entry, evtMsgs).WithStateFrom(savingNotifiation)); } - /// - /// Removes a rule - /// - /// - /// - /// - public Attempt RemoveRule(IContent content, string ruleType, string ruleValue) + return OperationResult.Attempt.Succeed(evtMsgs, entry); + } + + /// + /// Removes a rule + /// + /// + /// + /// + public Attempt RemoveRule(IContent content, string ruleType, string ruleValue) + { + EventMessages evtMsgs = EventMessagesFactory.Get(); + PublicAccessEntry? entry; + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - var evtMsgs = EventMessagesFactory.Get(); - PublicAccessEntry? entry; - using (var scope = ScopeProvider.CreateCoreScope()) + entry = _publicAccessRepository.GetMany().FirstOrDefault(x => x.ProtectedNodeId == content.Id); + if (entry == null) { - entry = _publicAccessRepository.GetMany().FirstOrDefault(x => x.ProtectedNodeId == content.Id); - if (entry == null) return Attempt.Fail(); // causes rollback // causes rollback - - var existingRule = entry.Rules.FirstOrDefault(x => x.RuleType == ruleType && x.RuleValue == ruleValue); - if (existingRule == null) return Attempt.Fail(); // causes rollback // causes rollback + return Attempt.Fail(); // causes rollback // causes rollback + } - entry.RemoveRule(existingRule); + PublicAccessRule existingRule = + entry.Rules.FirstOrDefault(x => x.RuleType == ruleType && x.RuleValue == ruleValue); + if (existingRule == null) + { + return Attempt.Fail(); // causes rollback // causes rollback + } - var savingNotifiation = new PublicAccessEntrySavingNotification(entry, evtMsgs); - if (scope.Notifications.PublishCancelable(savingNotifiation)) - { - scope.Complete(); - return OperationResult.Attempt.Cancel(evtMsgs); - } + entry.RemoveRule(existingRule); - _publicAccessRepository.Save(entry); + var savingNotifiation = new PublicAccessEntrySavingNotification(entry, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotifiation)) + { scope.Complete(); - - scope.Notifications.Publish(new PublicAccessEntrySavedNotification(entry, evtMsgs).WithStateFrom(savingNotifiation)); + return OperationResult.Attempt.Cancel(evtMsgs); } - return OperationResult.Attempt.Succeed(evtMsgs); + _publicAccessRepository.Save(entry); + scope.Complete(); + + scope.Notifications.Publish( + new PublicAccessEntrySavedNotification(entry, evtMsgs).WithStateFrom(savingNotifiation)); } - /// - /// Saves the entry - /// - /// - public Attempt Save(PublicAccessEntry entry) - { - var evtMsgs = EventMessagesFactory.Get(); + return OperationResult.Attempt.Succeed(evtMsgs); + } - using (var scope = ScopeProvider.CreateCoreScope()) - { - var savingNotifiation = new PublicAccessEntrySavingNotification(entry, evtMsgs); - if (scope.Notifications.PublishCancelable(savingNotifiation)) - { - scope.Complete(); - return OperationResult.Attempt.Cancel(evtMsgs); - } + /// + /// Saves the entry + /// + /// + public Attempt Save(PublicAccessEntry entry) + { + EventMessages evtMsgs = EventMessagesFactory.Get(); - _publicAccessRepository.Save(entry); + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + var savingNotifiation = new PublicAccessEntrySavingNotification(entry, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotifiation)) + { scope.Complete(); - - scope.Notifications.Publish(new PublicAccessEntrySavedNotification(entry, evtMsgs).WithStateFrom(savingNotifiation)); + return OperationResult.Attempt.Cancel(evtMsgs); } - return OperationResult.Attempt.Succeed(evtMsgs); + _publicAccessRepository.Save(entry); + scope.Complete(); + + scope.Notifications.Publish( + new PublicAccessEntrySavedNotification(entry, evtMsgs).WithStateFrom(savingNotifiation)); } - /// - /// Deletes the entry and all associated rules - /// - /// - public Attempt Delete(PublicAccessEntry entry) - { - var evtMsgs = EventMessagesFactory.Get(); + return OperationResult.Attempt.Succeed(evtMsgs); + } - using (var scope = ScopeProvider.CreateCoreScope()) - { - var deletingNotification = new PublicAccessEntryDeletingNotification(entry, evtMsgs); - if (scope.Notifications.PublishCancelable(deletingNotification)) - { - scope.Complete(); - return OperationResult.Attempt.Cancel(evtMsgs); - } + /// + /// Deletes the entry and all associated rules + /// + /// + public Attempt Delete(PublicAccessEntry entry) + { + EventMessages evtMsgs = EventMessagesFactory.Get(); - _publicAccessRepository.Delete(entry); + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + var deletingNotification = new PublicAccessEntryDeletingNotification(entry, evtMsgs); + if (scope.Notifications.PublishCancelable(deletingNotification)) + { scope.Complete(); - - scope.Notifications.Publish(new PublicAccessEntryDeletedNotification(entry, evtMsgs).WithStateFrom(deletingNotification)); + return OperationResult.Attempt.Cancel(evtMsgs); } - return OperationResult.Attempt.Succeed(evtMsgs); + _publicAccessRepository.Delete(entry); + scope.Complete(); + + scope.Notifications.Publish( + new PublicAccessEntryDeletedNotification(entry, evtMsgs).WithStateFrom(deletingNotification)); } + + return OperationResult.Attempt.Succeed(evtMsgs); } } diff --git a/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs b/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs index d8a7f201de2c..e3b8efb0c28d 100644 --- a/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs +++ b/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs @@ -1,105 +1,129 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Extension methods for the IPublicAccessService +/// +public static class PublicAccessServiceExtensions { - /// - /// Extension methods for the IPublicAccessService - /// - public static class PublicAccessServiceExtensions + public static bool RenameMemberGroupRoleRules(this IPublicAccessService publicAccessService, string? oldRolename, + string? newRolename) { - public static bool RenameMemberGroupRoleRules(this IPublicAccessService publicAccessService, string? oldRolename, string? newRolename) + var hasChange = false; + if (oldRolename == newRolename) { - var hasChange = false; - if (oldRolename == newRolename) return false; + return false; + } - var allEntries = publicAccessService.GetAll(); + IEnumerable allEntries = publicAccessService.GetAll(); - foreach (var entry in allEntries) + foreach (PublicAccessEntry entry in allEntries) + { + //get rules that match + IEnumerable roleRules = entry.Rules + .Where(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType) + .Where(x => x.RuleValue == oldRolename); + var save = false; + foreach (PublicAccessRule roleRule in roleRules) { - //get rules that match - var roleRules = entry.Rules - .Where(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType) - .Where(x => x.RuleValue == oldRolename); - var save = false; - foreach (var roleRule in roleRules) - { - //a rule is being updated so flag this entry to be saved - roleRule.RuleValue = newRolename ?? String.Empty; - save = true; - } - if (save) - { - hasChange = true; - publicAccessService.Save(entry); - } + //a rule is being updated so flag this entry to be saved + roleRule.RuleValue = newRolename ?? string.Empty; + save = true; } - return hasChange; + if (save) + { + hasChange = true; + publicAccessService.Save(entry); + } } - public static bool HasAccess(this IPublicAccessService publicAccessService, int documentId, IContentService contentService, string username, IEnumerable currentMemberRoles) - { - var content = contentService.GetById(documentId); - if (content == null) return true; - - var entry = publicAccessService.GetEntryForContent(content); - if (entry == null) return true; + return hasChange; + } - return HasAccess(entry, username, currentMemberRoles); + public static bool HasAccess(this IPublicAccessService publicAccessService, int documentId, + IContentService contentService, string username, IEnumerable currentMemberRoles) + { + IContent content = contentService.GetById(documentId); + if (content == null) + { + return true; } - /// - /// Checks if the member with the specified username has access to the path which is also based on the passed in roles for the member - /// - /// - /// - /// - /// A callback to retrieve the roles for this member - /// - public static async Task HasAccessAsync(this IPublicAccessService publicAccessService, string path, string username, Func>> rolesCallback) + PublicAccessEntry entry = publicAccessService.GetEntryForContent(content); + if (entry == null) { - if (rolesCallback == null) throw new ArgumentNullException("roles"); - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value cannot be null or whitespace.", "username"); - if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Value cannot be null or whitespace.", "path"); + return true; + } + + return HasAccess(entry, username, currentMemberRoles); + } - var entry = publicAccessService.GetEntryForContent(path.EnsureEndsWith(path)); - if (entry == null) return true; + /// + /// Checks if the member with the specified username has access to the path which is also based on the passed in roles + /// for the member + /// + /// + /// + /// + /// A callback to retrieve the roles for this member + /// + public static async Task HasAccessAsync(this IPublicAccessService publicAccessService, string path, + string username, Func>> rolesCallback) + { + if (rolesCallback == null) + { + throw new ArgumentNullException("roles"); + } - var roles = await rolesCallback(); + if (string.IsNullOrWhiteSpace(username)) + { + throw new ArgumentException("Value cannot be null or whitespace.", "username"); + } - return HasAccess(entry, username, roles); + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentException("Value cannot be null or whitespace.", "path"); } - private static bool HasAccess(PublicAccessEntry entry, string username, IEnumerable roles) + PublicAccessEntry entry = publicAccessService.GetEntryForContent(path.EnsureEndsWith(path)); + if (entry == null) { - if (entry is null) - { - throw new ArgumentNullException(nameof(entry)); - } + return true; + } - if (string.IsNullOrEmpty(username)) - { - throw new ArgumentException($"'{nameof(username)}' cannot be null or empty.", nameof(username)); - } + IEnumerable roles = await rolesCallback(); - if (roles is null) - { - throw new ArgumentNullException(nameof(roles)); - } + return HasAccess(entry, username, roles); + } + + private static bool HasAccess(PublicAccessEntry entry, string username, IEnumerable roles) + { + if (entry is null) + { + throw new ArgumentNullException(nameof(entry)); + } - return entry.Rules.Any(x => - (x.RuleType == Constants.Conventions.PublicAccess.MemberUsernameRuleType && username.Equals(x.RuleValue, StringComparison.OrdinalIgnoreCase)) - || (x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType && roles.Contains(x.RuleValue)) - ); + if (string.IsNullOrEmpty(username)) + { + throw new ArgumentException($"'{nameof(username)}' cannot be null or empty.", nameof(username)); } + + if (roles is null) + { + throw new ArgumentNullException(nameof(roles)); + } + + return entry.Rules.Any(x => + (x.RuleType == Constants.Conventions.PublicAccess.MemberUsernameRuleType && + username.Equals(x.RuleValue, StringComparison.OrdinalIgnoreCase)) + || (x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType && roles.Contains(x.RuleValue)) + ); } } diff --git a/src/Umbraco.Core/Services/PublishResult.cs b/src/Umbraco.Core/Services/PublishResult.cs index 0ab820e7a65c..4511680b03d0 100644 --- a/src/Umbraco.Core/Services/PublishResult.cs +++ b/src/Umbraco.Core/Services/PublishResult.cs @@ -1,37 +1,36 @@ -using System.Collections.Generic; -using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Services -{ +namespace Umbraco.Cms.Core.Services; +/// +/// Represents the result of publishing a document. +/// +public class PublishResult : OperationResult +{ /// - /// Represents the result of publishing a document. + /// Initializes a new instance of the class. /// - public class PublishResult : OperationResult + public PublishResult(PublishResultType resultType, EventMessages? eventMessages, IContent? content) + : base(resultType, eventMessages, content) { - /// - /// Initializes a new instance of the class. - /// - public PublishResult(PublishResultType resultType, EventMessages? eventMessages, IContent? content) - : base(resultType, eventMessages, content) - { } + } - /// - /// Initializes a new instance of the class. - /// - public PublishResult(EventMessages eventMessages, IContent content) - : base(PublishResultType.SuccessPublish, eventMessages, content) - { } + /// + /// Initializes a new instance of the class. + /// + public PublishResult(EventMessages eventMessages, IContent content) + : base(PublishResultType.SuccessPublish, eventMessages, content) + { + } - /// - /// Gets the document. - /// - public IContent? Content => Entity; + /// + /// Gets the document. + /// + public IContent? Content => Entity; - /// - /// Gets or sets the invalid properties, if the status failed due to validation. - /// - public IEnumerable? InvalidProperties { get; set; } - } + /// + /// Gets or sets the invalid properties, if the status failed due to validation. + /// + public IEnumerable? InvalidProperties { get; set; } } diff --git a/src/Umbraco.Core/Services/PublishResultType.cs b/src/Umbraco.Core/Services/PublishResultType.cs index 43fab58218b9..b4a063ec7aa0 100644 --- a/src/Umbraco.Core/Services/PublishResultType.cs +++ b/src/Umbraco.Core/Services/PublishResultType.cs @@ -1,151 +1,153 @@ -namespace Umbraco.Cms.Core.Services +namespace Umbraco.Cms.Core.Services; + +/// +/// A value indicating the result of publishing or unpublishing a document. +/// +public enum PublishResultType : byte { + // all "ResultType" enum's must be byte-based, and declare Failed = 128, and declare + // every failure codes as >128 - see OperationResult and OperationResultType for details. + + #region Success - Publish + + /// + /// The document was successfully published. + /// + SuccessPublish = 0, + + /// + /// The specified document culture was successfully published. + /// + SuccessPublishCulture = 1, + + /// + /// The document was already published. + /// + SuccessPublishAlready = 2, + + #endregion + + #region Success - Unpublish + + /// + /// The document was successfully unpublished. + /// + SuccessUnpublish = 3, + + /// + /// The document was already unpublished. + /// + SuccessUnpublishAlready = 4, + + /// + /// The specified document culture was unpublished, the document item itself remains published. + /// + SuccessUnpublishCulture = 5, + + /// + /// The specified document culture was unpublished, and was a mandatory culture, therefore the document itself was + /// unpublished. + /// + SuccessUnpublishMandatoryCulture = 6, + + /// + /// The specified document culture was unpublished, and was the last published culture in the document, therefore the + /// document itself was unpublished. + /// + SuccessUnpublishLastCulture = 8, + + #endregion + + #region Success - Mixed + + /// + /// Specified document cultures were successfully published and unpublished (in the same operation). + /// + SuccessMixedCulture = 7, + + #endregion + + #region Failed - Publish + + /// + /// The operation failed. + /// + /// All values above this value indicate a failure. + FailedPublish = 128, + /// - /// A value indicating the result of publishing or unpublishing a document. + /// The document could not be published because its ancestor path is not published. /// - public enum PublishResultType : byte - { - // all "ResultType" enum's must be byte-based, and declare Failed = 128, and declare - // every failure codes as >128 - see OperationResult and OperationResultType for details. - - #region Success - Publish - - /// - /// The document was successfully published. - /// - SuccessPublish = 0, - - /// - /// The specified document culture was successfully published. - /// - SuccessPublishCulture = 1, - - /// - /// The document was already published. - /// - SuccessPublishAlready = 2, - - #endregion - - #region Success - Unpublish - - /// - /// The document was successfully unpublished. - /// - SuccessUnpublish = 3, - - /// - /// The document was already unpublished. - /// - SuccessUnpublishAlready = 4, - - /// - /// The specified document culture was unpublished, the document item itself remains published. - /// - SuccessUnpublishCulture = 5, - - /// - /// The specified document culture was unpublished, and was a mandatory culture, therefore the document itself was unpublished. - /// - SuccessUnpublishMandatoryCulture = 6, - - /// - /// The specified document culture was unpublished, and was the last published culture in the document, therefore the document itself was unpublished. - /// - SuccessUnpublishLastCulture = 8, - - #endregion - - #region Success - Mixed - - /// - /// Specified document cultures were successfully published and unpublished (in the same operation). - /// - SuccessMixedCulture = 7, - - #endregion - - #region Failed - Publish - - /// - /// The operation failed. - /// - /// All values above this value indicate a failure. - FailedPublish = 128, - - /// - /// The document could not be published because its ancestor path is not published. - /// - FailedPublishPathNotPublished = FailedPublish | 1, - - /// - /// The document has expired so we cannot force it to be - /// published again as part of a bulk publish operation. - /// - FailedPublishHasExpired = FailedPublish | 2, - - /// - /// The document is scheduled to be released in the future and therefore we cannot force it to - /// be published during a bulk publish operation. - /// - FailedPublishAwaitingRelease = FailedPublish | 3, - - /// - /// A document culture has expired so we cannot force it to be - /// published again as part of a bulk publish operation. - /// - FailedPublishCultureHasExpired = FailedPublish | 4, - - /// - /// A document culture is scheduled to be released in the future and therefore we cannot force it to - /// be published during a bulk publish operation. - /// - FailedPublishCultureAwaitingRelease = FailedPublish | 5, - - /// - /// The document could not be published because it is in the trash. - /// - FailedPublishIsTrashed = FailedPublish | 6, - - /// - /// The publish action has been cancelled by an event handler. - /// - FailedPublishCancelledByEvent = FailedPublish | 7, - - /// - /// The document could not be published because it contains invalid data (has not passed validation requirements). - /// - FailedPublishContentInvalid = FailedPublish | 8, - - /// - /// The document could not be published because it has no publishing flags or values or if its a variant document, no cultures were specified to be published. - /// - FailedPublishNothingToPublish = FailedPublish | 9, - - /// - /// The document could not be published because some mandatory cultures are missing. - /// - FailedPublishMandatoryCultureMissing = FailedPublish | 10, // in ContentService.SavePublishing - - /// - /// The document could not be published because it has been modified by another user. - /// - FailedPublishConcurrencyViolation = FailedPublish | 11, - - #endregion - - #region Failed - Unpublish - - /// - /// The document could not be unpublished. - /// - FailedUnpublish = FailedPublish | 11, // in ContentService.SavePublishing - - /// - /// The unpublish action has been cancelled by an event handler. - /// - FailedUnpublishCancelledByEvent = FailedPublish | 12, - - #endregion - } + FailedPublishPathNotPublished = FailedPublish | 1, + + /// + /// The document has expired so we cannot force it to be + /// published again as part of a bulk publish operation. + /// + FailedPublishHasExpired = FailedPublish | 2, + + /// + /// The document is scheduled to be released in the future and therefore we cannot force it to + /// be published during a bulk publish operation. + /// + FailedPublishAwaitingRelease = FailedPublish | 3, + + /// + /// A document culture has expired so we cannot force it to be + /// published again as part of a bulk publish operation. + /// + FailedPublishCultureHasExpired = FailedPublish | 4, + + /// + /// A document culture is scheduled to be released in the future and therefore we cannot force it to + /// be published during a bulk publish operation. + /// + FailedPublishCultureAwaitingRelease = FailedPublish | 5, + + /// + /// The document could not be published because it is in the trash. + /// + FailedPublishIsTrashed = FailedPublish | 6, + + /// + /// The publish action has been cancelled by an event handler. + /// + FailedPublishCancelledByEvent = FailedPublish | 7, + + /// + /// The document could not be published because it contains invalid data (has not passed validation requirements). + /// + FailedPublishContentInvalid = FailedPublish | 8, + + /// + /// The document could not be published because it has no publishing flags or values or if its a variant document, no + /// cultures were specified to be published. + /// + FailedPublishNothingToPublish = FailedPublish | 9, + + /// + /// The document could not be published because some mandatory cultures are missing. + /// + FailedPublishMandatoryCultureMissing = FailedPublish | 10, // in ContentService.SavePublishing + + /// + /// The document could not be published because it has been modified by another user. + /// + FailedPublishConcurrencyViolation = FailedPublish | 11, + + #endregion + + #region Failed - Unpublish + + /// + /// The document could not be unpublished. + /// + FailedUnpublish = FailedPublish | 11, // in ContentService.SavePublishing + + /// + /// The unpublish action has been cancelled by an event handler. + /// + FailedUnpublishCancelledByEvent = FailedPublish | 12, + + #endregion } diff --git a/src/Umbraco.Core/Services/RedirectUrlService.cs b/src/Umbraco.Core/Services/RedirectUrlService.cs index 14c3e834bfbb..ff7347974a48 100644 --- a/src/Umbraco.Core/Services/RedirectUrlService.cs +++ b/src/Umbraco.Core/Services/RedirectUrlService.cs @@ -1,122 +1,126 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; -namespace Umbraco.Cms.Core.Services +namespace Umbraco.Cms.Core.Services; + +internal class RedirectUrlService : RepositoryService, IRedirectUrlService { - internal class RedirectUrlService : RepositoryService, IRedirectUrlService - { - private readonly IRedirectUrlRepository _redirectUrlRepository; + private readonly IRedirectUrlRepository _redirectUrlRepository; - public RedirectUrlService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - IRedirectUrlRepository redirectUrlRepository) - : base(provider, loggerFactory, eventMessagesFactory) - { - _redirectUrlRepository = redirectUrlRepository; - } + public RedirectUrlService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IRedirectUrlRepository redirectUrlRepository) + : base(provider, loggerFactory, eventMessagesFactory) => + _redirectUrlRepository = redirectUrlRepository; - public void Register(string url, Guid contentKey, string? culture = null) + public void Register(string url, Guid contentKey, string? culture = null) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - using (var scope = ScopeProvider.CreateCoreScope()) + IRedirectUrl redir = _redirectUrlRepository.Get(url, contentKey, culture); + if (redir != null) + { + redir.CreateDateUtc = DateTime.UtcNow; + } + else { - var redir = _redirectUrlRepository.Get(url, contentKey, culture); - if (redir != null) - redir.CreateDateUtc = DateTime.UtcNow; - else - redir = new RedirectUrl { Key = Guid.NewGuid(), Url = url, ContentKey = contentKey, Culture = culture}; - _redirectUrlRepository.Save(redir); - scope.Complete(); + redir = new RedirectUrl {Key = Guid.NewGuid(), Url = url, ContentKey = contentKey, Culture = culture}; } + + _redirectUrlRepository.Save(redir); + scope.Complete(); } + } - public void Delete(IRedirectUrl redirectUrl) + public void Delete(IRedirectUrl redirectUrl) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - using (var scope = ScopeProvider.CreateCoreScope()) - { - _redirectUrlRepository.Delete(redirectUrl); - scope.Complete(); - } + _redirectUrlRepository.Delete(redirectUrl); + scope.Complete(); } + } - public void Delete(Guid id) + public void Delete(Guid id) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - using (var scope = ScopeProvider.CreateCoreScope()) - { - _redirectUrlRepository.Delete(id); - scope.Complete(); - } + _redirectUrlRepository.Delete(id); + scope.Complete(); } + } - public void DeleteContentRedirectUrls(Guid contentKey) + public void DeleteContentRedirectUrls(Guid contentKey) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - using (var scope = ScopeProvider.CreateCoreScope()) - { - _redirectUrlRepository.DeleteContentUrls(contentKey); - scope.Complete(); - } + _redirectUrlRepository.DeleteContentUrls(contentKey); + scope.Complete(); } + } - public void DeleteAll() + public void DeleteAll() + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - using (var scope = ScopeProvider.CreateCoreScope()) - { - _redirectUrlRepository.DeleteAll(); - scope.Complete(); - } + _redirectUrlRepository.DeleteAll(); + scope.Complete(); } + } - public IRedirectUrl? GetMostRecentRedirectUrl(string url) + public IRedirectUrl? GetMostRecentRedirectUrl(string url) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _redirectUrlRepository.GetMostRecentUrl(url); - } + return _redirectUrlRepository.GetMostRecentUrl(url); } + } - public IEnumerable GetContentRedirectUrls(Guid contentKey) + public IEnumerable GetContentRedirectUrls(Guid contentKey) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _redirectUrlRepository.GetContentUrls(contentKey); - } + return _redirectUrlRepository.GetContentUrls(contentKey); } + } - public IEnumerable GetAllRedirectUrls(long pageIndex, int pageSize, out long total) + public IEnumerable GetAllRedirectUrls(long pageIndex, int pageSize, out long total) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _redirectUrlRepository.GetAllUrls(pageIndex, pageSize, out total); - } + return _redirectUrlRepository.GetAllUrls(pageIndex, pageSize, out total); } + } - public IEnumerable GetAllRedirectUrls(int rootContentId, long pageIndex, int pageSize, out long total) + public IEnumerable GetAllRedirectUrls(int rootContentId, long pageIndex, int pageSize, out long total) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _redirectUrlRepository.GetAllUrls(rootContentId, pageIndex, pageSize, out total); - } + return _redirectUrlRepository.GetAllUrls(rootContentId, pageIndex, pageSize, out total); } + } - public IEnumerable SearchRedirectUrls(string searchTerm, long pageIndex, int pageSize, out long total) + public IEnumerable SearchRedirectUrls(string searchTerm, long pageIndex, int pageSize, out long total) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _redirectUrlRepository.SearchUrls(searchTerm, pageIndex, pageSize, out total); - } + return _redirectUrlRepository.SearchUrls(searchTerm, pageIndex, pageSize, out total); } + } - public IRedirectUrl? GetMostRecentRedirectUrl(string url, string? culture) + public IRedirectUrl? GetMostRecentRedirectUrl(string url, string? culture) + { + if (string.IsNullOrWhiteSpace(culture)) { - if (string.IsNullOrWhiteSpace(culture)) return GetMostRecentRedirectUrl(url); - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _redirectUrlRepository.GetMostRecentUrl(url, culture); - } + return GetMostRecentRedirectUrl(url); + } + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + { + return _redirectUrlRepository.GetMostRecentUrl(url, culture); } } } diff --git a/src/Umbraco.Core/Services/RelationService.cs b/src/Umbraco.Core/Services/RelationService.cs index 966e4ec7df96..a9bb0ea03fc9 100644 --- a/src/Umbraco.Core/Services/RelationService.cs +++ b/src/Umbraco.Core/Services/RelationService.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; @@ -11,605 +8,636 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services +namespace Umbraco.Cms.Core.Services; + +public class RelationService : RepositoryService, IRelationService { - public class RelationService : RepositoryService, IRelationService + private readonly IAuditRepository _auditRepository; + private readonly IEntityService _entityService; + private readonly IRelationRepository _relationRepository; + private readonly IRelationTypeRepository _relationTypeRepository; + + public RelationService(ICoreScopeProvider uowProvider, ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, IEntityService entityService, + IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, + IAuditRepository auditRepository) + : base(uowProvider, loggerFactory, eventMessagesFactory) { - private readonly IEntityService _entityService; - private readonly IRelationRepository _relationRepository; - private readonly IRelationTypeRepository _relationTypeRepository; - private readonly IAuditRepository _auditRepository; + _relationRepository = relationRepository; + _relationTypeRepository = relationTypeRepository; + _auditRepository = auditRepository; + _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); + } - public RelationService(ICoreScopeProvider uowProvider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IEntityService entityService, - IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, IAuditRepository auditRepository) - : base(uowProvider, loggerFactory, eventMessagesFactory) + /// + public IRelation? GetById(int id) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - _relationRepository = relationRepository; - _relationTypeRepository = relationTypeRepository; - _auditRepository = auditRepository; - _entityService = entityService ?? throw new ArgumentNullException(nameof(entityService)); + return _relationRepository.Get(id); } + } - /// - public IRelation? GetById(int id) + /// + public IRelationType? GetRelationTypeById(int id) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _relationRepository.Get(id); - } + return _relationTypeRepository.Get(id); } + } - /// - public IRelationType? GetRelationTypeById(int id) + /// + public IRelationType? GetRelationTypeById(Guid id) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _relationTypeRepository.Get(id); - } + return _relationTypeRepository.Get(id); } + } + + /// + public IRelationType? GetRelationTypeByAlias(string alias) => GetRelationType(alias); - /// - public IRelationType? GetRelationTypeById(Guid id) + /// + public IEnumerable GetAllRelations(params int[] ids) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _relationTypeRepository.Get(id); - } + return _relationRepository.GetMany(ids); } + } - /// - public IRelationType? GetRelationTypeByAlias(string alias) => GetRelationType(alias); + /// + public IEnumerable? GetAllRelationsByRelationType(IRelationType relationType) => + GetAllRelationsByRelationType(relationType.Id); - /// - public IEnumerable GetAllRelations(params int[] ids) + /// + public IEnumerable? GetAllRelationsByRelationType(int relationTypeId) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _relationRepository.GetMany(ids); - } + IQuery query = Query().Where(x => x.RelationTypeId == relationTypeId); + return _relationRepository.Get(query); } + } - /// - public IEnumerable? GetAllRelationsByRelationType(IRelationType relationType) + /// + public IEnumerable GetAllRelationTypes(params int[] ids) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - return GetAllRelationsByRelationType(relationType.Id); + return _relationTypeRepository.GetMany(ids); } + } - /// - public IEnumerable? GetAllRelationsByRelationType(int relationTypeId) - { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - var query = Query().Where(x => x.RelationTypeId == relationTypeId); - return _relationRepository.Get(query); - } - } + /// + public IEnumerable? GetByParentId(int id) => GetByParentId(id, null); - /// - public IEnumerable GetAllRelationTypes(params int[] ids) + /// + public IEnumerable GetByParentId(int id, string? relationTypeAlias) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + if (relationTypeAlias.IsNullOrWhiteSpace()) { - return _relationTypeRepository.GetMany(ids); + IQuery qry1 = Query().Where(x => x.ParentId == id); + return _relationRepository.Get(qry1) ?? Enumerable.Empty(); } - } - /// - public IEnumerable? GetByParentId(int id) => GetByParentId(id, null); - - /// - public IEnumerable GetByParentId(int id, string? relationTypeAlias) - { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + IRelationType relationType = GetRelationType(relationTypeAlias!); + if (relationType == null) { - if (relationTypeAlias.IsNullOrWhiteSpace()) - { - var qry1 = Query().Where(x => x.ParentId == id); - return _relationRepository.Get(qry1) ?? Enumerable.Empty(); - } - - var relationType = GetRelationType(relationTypeAlias!); - if (relationType == null) - return Enumerable.Empty(); - - var qry2 = Query().Where(x => x.ParentId == id && x.RelationTypeId == relationType.Id); - return _relationRepository.Get(qry2) ?? Enumerable.Empty(); + return Enumerable.Empty(); } + + IQuery qry2 = + Query().Where(x => x.ParentId == id && x.RelationTypeId == relationType.Id); + return _relationRepository.Get(qry2) ?? Enumerable.Empty(); } + } - /// - public IEnumerable? GetByParent(IUmbracoEntity parent) => GetByParentId(parent.Id); + /// + public IEnumerable? GetByParent(IUmbracoEntity parent) => GetByParentId(parent.Id); - /// - public IEnumerable GetByParent(IUmbracoEntity parent, string relationTypeAlias) => GetByParentId(parent.Id, relationTypeAlias); + /// + public IEnumerable GetByParent(IUmbracoEntity parent, string relationTypeAlias) => + GetByParentId(parent.Id, relationTypeAlias); - /// - public IEnumerable GetByChildId(int id) => GetByChildId(id, null); + /// + public IEnumerable GetByChildId(int id) => GetByChildId(id, null); - /// - public IEnumerable GetByChildId(int id, string? relationTypeAlias) + /// + public IEnumerable GetByChildId(int id, string? relationTypeAlias) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + if (relationTypeAlias.IsNullOrWhiteSpace()) { - if (relationTypeAlias.IsNullOrWhiteSpace()) - { - var qry1 = Query().Where(x => x.ChildId == id); - return _relationRepository.Get(qry1) ?? Enumerable.Empty(); - } - - var relationType = GetRelationType(relationTypeAlias!); - if (relationType == null) - return Enumerable.Empty(); - - var qry2 = Query().Where(x => x.ChildId == id && x.RelationTypeId == relationType.Id); - return _relationRepository.Get(qry2) ?? Enumerable.Empty(); + IQuery qry1 = Query().Where(x => x.ChildId == id); + return _relationRepository.Get(qry1) ?? Enumerable.Empty(); } - } - - /// - public IEnumerable GetByChild(IUmbracoEntity child) => GetByChildId(child.Id); - - /// - public IEnumerable GetByChild(IUmbracoEntity child, string relationTypeAlias) => GetByChildId(child.Id, relationTypeAlias); - /// - public IEnumerable GetByParentOrChildId(int id) - { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + IRelationType relationType = GetRelationType(relationTypeAlias!); + if (relationType == null) { - var query = Query().Where(x => x.ChildId == id || x.ParentId == id); - return _relationRepository.Get(query); + return Enumerable.Empty(); } + + IQuery qry2 = + Query().Where(x => x.ChildId == id && x.RelationTypeId == relationType.Id); + return _relationRepository.Get(qry2) ?? Enumerable.Empty(); } + } - public IEnumerable GetByParentOrChildId(int id, string relationTypeAlias) - { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - var relationType = GetRelationType(relationTypeAlias); - if (relationType == null) - return Enumerable.Empty(); + /// + public IEnumerable GetByChild(IUmbracoEntity child) => GetByChildId(child.Id); - var query = Query().Where(x => (x.ChildId == id || x.ParentId == id) && x.RelationTypeId == relationType.Id); - return _relationRepository.Get(query); - } - } + /// + public IEnumerable GetByChild(IUmbracoEntity child, string relationTypeAlias) => + GetByChildId(child.Id, relationTypeAlias); - /// - public IRelation? GetByParentAndChildId(int parentId, int childId, IRelationType relationType) + /// + public IEnumerable GetByParentOrChildId(int id) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - var query = Query().Where(x => x.ParentId == parentId && - x.ChildId == childId && - x.RelationTypeId == relationType.Id); - return _relationRepository.Get(query)?.FirstOrDefault(); - } + IQuery query = Query().Where(x => x.ChildId == id || x.ParentId == id); + return _relationRepository.Get(query); } + } - /// - public IEnumerable GetByRelationTypeName(string relationTypeName) + public IEnumerable GetByParentOrChildId(int id, string relationTypeAlias) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - List? relationTypeIds; - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + IRelationType relationType = GetRelationType(relationTypeAlias); + if (relationType == null) { - //This is a silly query - but i guess it's needed in case someone has more than one relation with the same Name (not alias), odd. - var query = Query().Where(x => x.Name == relationTypeName); - var relationTypes = _relationTypeRepository.Get(query); - relationTypeIds = relationTypes?.Select(x => x.Id).ToList(); + return Enumerable.Empty(); } - return relationTypeIds is null || relationTypeIds.Count == 0 - ? Enumerable.Empty() - : GetRelationsByListOfTypeIds(relationTypeIds); + IQuery query = Query().Where(x => + (x.ChildId == id || x.ParentId == id) && x.RelationTypeId == relationType.Id); + return _relationRepository.Get(query); } + } - /// - public IEnumerable GetByRelationTypeAlias(string relationTypeAlias) + /// + public IRelation? GetByParentAndChildId(int parentId, int childId, IRelationType relationType) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - var relationType = GetRelationType(relationTypeAlias); - - return relationType == null - ? Enumerable.Empty() - : GetRelationsByListOfTypeIds(new[] { relationType.Id }); + IQuery query = Query().Where(x => x.ParentId == parentId && + x.ChildId == childId && + x.RelationTypeId == relationType.Id); + return _relationRepository.Get(query)?.FirstOrDefault(); } + } - /// - public IEnumerable? GetByRelationTypeId(int relationTypeId) + /// + public IEnumerable GetByRelationTypeName(string relationTypeName) + { + List? relationTypeIds; + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - var query = Query().Where(x => x.RelationTypeId == relationTypeId); - return _relationRepository.Get(query); - } + //This is a silly query - but i guess it's needed in case someone has more than one relation with the same Name (not alias), odd. + IQuery query = Query().Where(x => x.Name == relationTypeName); + IEnumerable relationTypes = _relationTypeRepository.Get(query); + relationTypeIds = relationTypes?.Select(x => x.Id).ToList(); } - /// - public IEnumerable GetPagedByRelationTypeId(int relationTypeId, long pageIndex, int pageSize, out long totalRecords, Ordering? ordering = null) - { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - var query = Query()?.Where(x => x.RelationTypeId == relationTypeId); - return _relationRepository.GetPagedRelationsByQuery(query, pageIndex, pageSize, out totalRecords, ordering); - } - } + return relationTypeIds is null || relationTypeIds.Count == 0 + ? Enumerable.Empty() + : GetRelationsByListOfTypeIds(relationTypeIds); + } + + /// + public IEnumerable GetByRelationTypeAlias(string relationTypeAlias) + { + IRelationType relationType = GetRelationType(relationTypeAlias); - /// - public IUmbracoEntity? GetChildEntityFromRelation(IRelation relation) + return relationType == null + ? Enumerable.Empty() + : GetRelationsByListOfTypeIds(new[] {relationType.Id}); + } + + /// + public IEnumerable? GetByRelationTypeId(int relationTypeId) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - var objectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType); - return _entityService.Get(relation.ChildId, objectType); + IQuery query = Query().Where(x => x.RelationTypeId == relationTypeId); + return _relationRepository.Get(query); } + } - /// - public IUmbracoEntity? GetParentEntityFromRelation(IRelation relation) + /// + public IEnumerable GetPagedByRelationTypeId(int relationTypeId, long pageIndex, int pageSize, + out long totalRecords, Ordering? ordering = null) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - var objectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType); - return _entityService.Get(relation.ParentId, objectType); + IQuery query = Query()?.Where(x => x.RelationTypeId == relationTypeId); + return _relationRepository.GetPagedRelationsByQuery(query, pageIndex, pageSize, out totalRecords, ordering); } + } - /// - public Tuple? GetEntitiesFromRelation(IRelation relation) - { - var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType); - var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType); + /// + public IUmbracoEntity? GetChildEntityFromRelation(IRelation relation) + { + UmbracoObjectTypes objectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType); + return _entityService.Get(relation.ChildId, objectType); + } - var child = _entityService.Get(relation.ChildId, childObjectType); - var parent = _entityService.Get(relation.ParentId, parentObjectType); + /// + public IUmbracoEntity? GetParentEntityFromRelation(IRelation relation) + { + UmbracoObjectTypes objectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType); + return _entityService.Get(relation.ParentId, objectType); + } - if (parent is null || child is null) - { - return null; - } + /// + public Tuple? GetEntitiesFromRelation(IRelation relation) + { + UmbracoObjectTypes childObjectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType); + UmbracoObjectTypes parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType); - return new Tuple(parent, child); - } + IEntitySlim child = _entityService.Get(relation.ChildId, childObjectType); + IEntitySlim parent = _entityService.Get(relation.ParentId, parentObjectType); - /// - public IEnumerable GetChildEntitiesFromRelations(IEnumerable relations) + if (parent is null || child is null) { - // Trying to avoid full N+1 lookups, so we'll group by the object type and then use the GetAll - // method to lookup batches of entities for each parent object type - - foreach (var groupedRelations in relations.GroupBy(x => ObjectTypes.GetUmbracoObjectType(x.ChildObjectType))) - { - var objectType = groupedRelations.Key; - var ids = groupedRelations.Select(x => x.ChildId).ToArray(); - foreach (var e in _entityService.GetAll(objectType, ids)) - yield return e; - } + return null; } - /// - public IEnumerable GetParentEntitiesFromRelations(IEnumerable relations) - { - // Trying to avoid full N+1 lookups, so we'll group by the object type and then use the GetAll - // method to lookup batches of entities for each parent object type + return new Tuple(parent, child); + } - foreach (var groupedRelations in relations.GroupBy(x => ObjectTypes.GetUmbracoObjectType(x.ParentObjectType))) + /// + public IEnumerable GetChildEntitiesFromRelations(IEnumerable relations) + { + // Trying to avoid full N+1 lookups, so we'll group by the object type and then use the GetAll + // method to lookup batches of entities for each parent object type + + foreach (IGrouping groupedRelations in relations.GroupBy(x => + ObjectTypes.GetUmbracoObjectType(x.ChildObjectType))) + { + UmbracoObjectTypes objectType = groupedRelations.Key; + var ids = groupedRelations.Select(x => x.ChildId).ToArray(); + foreach (IEntitySlim e in _entityService.GetAll(objectType, ids)) { - var objectType = groupedRelations.Key; - var ids = groupedRelations.Select(x => x.ParentId).ToArray(); - foreach (var e in _entityService.GetAll(objectType, ids)) - yield return e; + yield return e; } } + } + + /// + public IEnumerable GetParentEntitiesFromRelations(IEnumerable relations) + { + // Trying to avoid full N+1 lookups, so we'll group by the object type and then use the GetAll + // method to lookup batches of entities for each parent object type - /// - public IEnumerable GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes) + foreach (IGrouping groupedRelations in relations.GroupBy(x => + ObjectTypes.GetUmbracoObjectType(x.ParentObjectType))) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + UmbracoObjectTypes objectType = groupedRelations.Key; + var ids = groupedRelations.Select(x => x.ParentId).ToArray(); + foreach (IEntitySlim e in _entityService.GetAll(objectType, ids)) { - return _relationRepository.GetPagedParentEntitiesByChildId(id, pageIndex, pageSize, out totalChildren, entityTypes.Select(x => x.GetGuid()).ToArray()); + yield return e; } } + } - /// - public IEnumerable GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes) + /// + public IEnumerable GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, + out long totalChildren, params UmbracoObjectTypes[] entityTypes) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _relationRepository.GetPagedChildEntitiesByParentId(id, pageIndex, pageSize, out totalChildren, entityTypes.Select(x => x.GetGuid()).ToArray()); - } + return _relationRepository.GetPagedParentEntitiesByChildId(id, pageIndex, pageSize, out totalChildren, + entityTypes.Select(x => x.GetGuid()).ToArray()); } + } - /// - public IEnumerable> GetEntitiesFromRelations(IEnumerable relations) + /// + public IEnumerable GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize, + out long totalChildren, params UmbracoObjectTypes[] entityTypes) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - //TODO: Argh! N+1 + return _relationRepository.GetPagedChildEntitiesByParentId(id, pageIndex, pageSize, out totalChildren, + entityTypes.Select(x => x.GetGuid()).ToArray()); + } + } - foreach (var relation in relations) - { - var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType); - var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType); + /// + public IEnumerable> GetEntitiesFromRelations(IEnumerable relations) + { + //TODO: Argh! N+1 - var child = _entityService.Get(relation.ChildId, childObjectType); - var parent = _entityService.Get(relation.ParentId, parentObjectType); + foreach (IRelation relation in relations) + { + UmbracoObjectTypes childObjectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType); + UmbracoObjectTypes parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType); - if (parent is not null && child is not null) - { - yield return new Tuple(parent, child); - } + IEntitySlim child = _entityService.Get(relation.ChildId, childObjectType); + IEntitySlim parent = _entityService.Get(relation.ParentId, parentObjectType); + + if (parent is not null && child is not null) + { + yield return new Tuple(parent, child); } } + } - /// - public IRelation Relate(int parentId, int childId, IRelationType relationType) + /// + public IRelation Relate(int parentId, int childId, IRelationType relationType) + { + // Ensure that the RelationType has an identity before using it to relate two entities + if (relationType.HasIdentity == false) { - // Ensure that the RelationType has an identity before using it to relate two entities - if (relationType.HasIdentity == false) - { - Save(relationType); - } + Save(relationType); + } - //TODO: We don't check if this exists first, it will throw some sort of data integrity exception if it already exists, is that ok? + //TODO: We don't check if this exists first, it will throw some sort of data integrity exception if it already exists, is that ok? - var relation = new Relation(parentId, childId, relationType); + var relation = new Relation(parentId, childId, relationType); - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + EventMessages eventMessages = EventMessagesFactory.Get(); + var savingNotification = new RelationSavingNotification(relation, eventMessages); + if (scope.Notifications.PublishCancelable(savingNotification)) { - EventMessages eventMessages = EventMessagesFactory.Get(); - var savingNotification = new RelationSavingNotification(relation, eventMessages); - if (scope.Notifications.PublishCancelable(savingNotification)) - { - scope.Complete(); - return relation; // TODO: returning sth that does not exist here?! - } - - _relationRepository.Save(relation); - scope.Notifications.Publish(new RelationSavedNotification(relation, eventMessages).WithStateFrom(savingNotification)); scope.Complete(); - return relation; + return relation; // TODO: returning sth that does not exist here?! } + + _relationRepository.Save(relation); + scope.Notifications.Publish( + new RelationSavedNotification(relation, eventMessages).WithStateFrom(savingNotification)); + scope.Complete(); + return relation; } + } - /// - public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType) + /// + public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType) => + Relate(parent.Id, child.Id, relationType); + + /// + public IRelation Relate(int parentId, int childId, string relationTypeAlias) + { + IRelationType relationType = GetRelationTypeByAlias(relationTypeAlias); + if (relationType == null || string.IsNullOrEmpty(relationType.Alias)) { - return Relate(parent.Id, child.Id, relationType); + throw new ArgumentNullException( + string.Format("No RelationType with Alias '{0}' exists.", relationTypeAlias)); } - /// - public IRelation Relate(int parentId, int childId, string relationTypeAlias) - { - var relationType = GetRelationTypeByAlias(relationTypeAlias); - if (relationType == null || string.IsNullOrEmpty(relationType.Alias)) - throw new ArgumentNullException(string.Format("No RelationType with Alias '{0}' exists.", relationTypeAlias)); + return Relate(parentId, childId, relationType); + } - return Relate(parentId, childId, relationType); + /// + public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias) + { + IRelationType relationType = GetRelationTypeByAlias(relationTypeAlias); + if (relationType == null || string.IsNullOrEmpty(relationType.Alias)) + { + throw new ArgumentNullException( + string.Format("No RelationType with Alias '{0}' exists.", relationTypeAlias)); } - /// - public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias) - { - var relationType = GetRelationTypeByAlias(relationTypeAlias); - if (relationType == null || string.IsNullOrEmpty(relationType.Alias)) - throw new ArgumentNullException(string.Format("No RelationType with Alias '{0}' exists.", relationTypeAlias)); + return Relate(parent.Id, child.Id, relationType); + } - return Relate(parent.Id, child.Id, relationType); + /// + public bool HasRelations(IRelationType relationType) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + { + IQuery query = Query().Where(x => x.RelationTypeId == relationType.Id); + return _relationRepository.Get(query)?.Any() ?? false; } + } - /// - public bool HasRelations(IRelationType relationType) + /// + public bool IsRelated(int id) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - var query = Query().Where(x => x.RelationTypeId == relationType.Id); - return _relationRepository.Get(query)?.Any() ?? false; - } + IQuery query = Query().Where(x => x.ParentId == id || x.ChildId == id); + return _relationRepository.Get(query)?.Any() ?? false; } + } - /// - public bool IsRelated(int id) + /// + public bool AreRelated(int parentId, int childId) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - var query = Query().Where(x => x.ParentId == id || x.ChildId == id); - return _relationRepository.Get(query)?.Any() ?? false; - } + IQuery query = Query().Where(x => x.ParentId == parentId && x.ChildId == childId); + return _relationRepository.Get(query)?.Any() ?? false; } + } - /// - public bool AreRelated(int parentId, int childId) + /// + public bool AreRelated(int parentId, int childId, string relationTypeAlias) + { + IRelationType relType = GetRelationTypeByAlias(relationTypeAlias); + if (relType == null) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - var query = Query().Where(x => x.ParentId == parentId && x.ChildId == childId); - return _relationRepository.Get(query)?.Any() ?? false; - } + return false; } - /// - public bool AreRelated(int parentId, int childId, string relationTypeAlias) - { - var relType = GetRelationTypeByAlias(relationTypeAlias); - if (relType == null) - return false; + return AreRelated(parentId, childId, relType); + } - return AreRelated(parentId, childId, relType); - } + /// + public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child) => AreRelated(parent.Id, child.Id); + + /// + public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias) => + AreRelated(parent.Id, child.Id, relationTypeAlias); - /// - public bool AreRelated(int parentId, int childId, IRelationType relationType) + /// + public void Save(IRelation relation) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + EventMessages eventMessages = EventMessagesFactory.Get(); + var savingNotification = new RelationSavingNotification(relation, eventMessages); + if (scope.Notifications.PublishCancelable(savingNotification)) { - var query = Query().Where(x => x.ParentId == parentId && x.ChildId == childId && x.RelationTypeId == relationType.Id); - return _relationRepository.Get(query)?.Any() ?? false; + scope.Complete(); + return; } - } - /// - public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child) - { - return AreRelated(parent.Id, child.Id); + _relationRepository.Save(relation); + scope.Complete(); + scope.Notifications.Publish( + new RelationSavedNotification(relation, eventMessages).WithStateFrom(savingNotification)); } + } - /// - public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias) + public void Save(IEnumerable relations) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - return AreRelated(parent.Id, child.Id, relationTypeAlias); - } - + IRelation[] relationsA = relations.ToArray(); - /// - public void Save(IRelation relation) - { - using (var scope = ScopeProvider.CreateCoreScope()) + EventMessages messages = EventMessagesFactory.Get(); + var savingNotification = new RelationSavingNotification(relationsA, messages); + if (scope.Notifications.PublishCancelable(savingNotification)) { - EventMessages eventMessages = EventMessagesFactory.Get(); - var savingNotification = new RelationSavingNotification(relation, eventMessages); - if (scope.Notifications.PublishCancelable(savingNotification)) - { - scope.Complete(); - return; - } - - _relationRepository.Save(relation); scope.Complete(); - scope.Notifications.Publish(new RelationSavedNotification(relation, eventMessages).WithStateFrom(savingNotification)); + return; } + + _relationRepository.Save(relationsA); + scope.Complete(); + scope.Notifications.Publish( + new RelationSavedNotification(relationsA, messages).WithStateFrom(savingNotification)); } + } - public void Save(IEnumerable relations) + /// + public void Save(IRelationType relationType) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + EventMessages eventMessages = EventMessagesFactory.Get(); + var savingNotification = new RelationTypeSavingNotification(relationType, eventMessages); + if (scope.Notifications.PublishCancelable(savingNotification)) { - IRelation[] relationsA = relations.ToArray(); - - EventMessages messages = EventMessagesFactory.Get(); - var savingNotification = new RelationSavingNotification(relationsA, messages); - if (scope.Notifications.PublishCancelable(savingNotification)) - { - scope.Complete(); - return; - } - - _relationRepository.Save(relationsA); scope.Complete(); - scope.Notifications.Publish(new RelationSavedNotification(relationsA, messages).WithStateFrom(savingNotification)); + return; } + + _relationTypeRepository.Save(relationType); + Audit(AuditType.Save, Constants.Security.SuperUserId, relationType.Id, + $"Saved relation type: {relationType.Name}"); + scope.Complete(); + scope.Notifications.Publish( + new RelationTypeSavedNotification(relationType, eventMessages).WithStateFrom(savingNotification)); } + } - /// - public void Save(IRelationType relationType) + /// + public void Delete(IRelation relation) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + EventMessages eventMessages = EventMessagesFactory.Get(); + var deletingNotification = new RelationDeletingNotification(relation, eventMessages); + if (scope.Notifications.PublishCancelable(deletingNotification)) { - EventMessages eventMessages = EventMessagesFactory.Get(); - var savingNotification = new RelationTypeSavingNotification(relationType, eventMessages); - if (scope.Notifications.PublishCancelable(savingNotification)) - { - scope.Complete(); - return; - } - - _relationTypeRepository.Save(relationType); - Audit(AuditType.Save, Cms.Core.Constants.Security.SuperUserId, relationType.Id, $"Saved relation type: {relationType.Name}"); scope.Complete(); - scope.Notifications.Publish(new RelationTypeSavedNotification(relationType, eventMessages).WithStateFrom(savingNotification)); + return; } + + _relationRepository.Delete(relation); + scope.Complete(); + scope.Notifications.Publish( + new RelationDeletedNotification(relation, eventMessages).WithStateFrom(deletingNotification)); } + } - /// - public void Delete(IRelation relation) + /// + public void Delete(IRelationType relationType) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + EventMessages eventMessages = EventMessagesFactory.Get(); + var deletingNotification = new RelationTypeDeletingNotification(relationType, eventMessages); + if (scope.Notifications.PublishCancelable(deletingNotification)) { - EventMessages eventMessages = EventMessagesFactory.Get(); - var deletingNotification = new RelationDeletingNotification(relation, eventMessages); - if (scope.Notifications.PublishCancelable(deletingNotification)) - { - scope.Complete(); - return; - } - - _relationRepository.Delete(relation); scope.Complete(); - scope.Notifications.Publish(new RelationDeletedNotification(relation, eventMessages).WithStateFrom(deletingNotification)); + return; } + + _relationTypeRepository.Delete(relationType); + scope.Complete(); + scope.Notifications.Publish( + new RelationTypeDeletedNotification(relationType, eventMessages).WithStateFrom(deletingNotification)); } + } - /// - public void Delete(IRelationType relationType) + /// + public void DeleteRelationsOfType(IRelationType relationType) + { + var relations = new List(); + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + IQuery? query = Query().Where(x => x.RelationTypeId == relationType.Id); + var allRelations = _relationRepository.Get(query)?.ToList(); + if (allRelations is not null) { - EventMessages eventMessages = EventMessagesFactory.Get(); - var deletingNotification = new RelationTypeDeletingNotification(relationType, eventMessages); - if (scope.Notifications.PublishCancelable(deletingNotification)) - { - scope.Complete(); - return; - } - - _relationTypeRepository.Delete(relationType); - scope.Complete(); - scope.Notifications.Publish(new RelationTypeDeletedNotification(relationType, eventMessages).WithStateFrom(deletingNotification)); + relations.AddRange(allRelations); } - } - /// - public void DeleteRelationsOfType(IRelationType relationType) - { - var relations = new List(); - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + //TODO: N+1, we should be able to do this in a single call + + foreach (IRelation relation in relations) { - IQuery? query = Query().Where(x => x.RelationTypeId == relationType.Id); - var allRelations = _relationRepository.Get(query)?.ToList(); - if (allRelations is not null) - { - relations.AddRange(allRelations); - } + _relationRepository.Delete(relation); + } - //TODO: N+1, we should be able to do this in a single call + scope.Complete(); - foreach (IRelation relation in relations) - { - _relationRepository.Delete(relation); - } + scope.Notifications.Publish(new RelationDeletedNotification(relations, EventMessagesFactory.Get())); + } + } - scope.Complete(); - scope.Notifications.Publish(new RelationDeletedNotification(relations, EventMessagesFactory.Get())); - } + /// + public bool AreRelated(int parentId, int childId, IRelationType relationType) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + { + IQuery query = Query().Where(x => + x.ParentId == parentId && x.ChildId == childId && x.RelationTypeId == relationType.Id); + return _relationRepository.Get(query)?.Any() ?? false; } + } - #region Private Methods + #region Private Methods - private IRelationType? GetRelationType(string relationTypeAlias) + private IRelationType? GetRelationType(string relationTypeAlias) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - var query = Query().Where(x => x.Alias == relationTypeAlias); - return _relationTypeRepository.Get(query)?.FirstOrDefault(); - } + IQuery query = Query().Where(x => x.Alias == relationTypeAlias); + return _relationTypeRepository.Get(query)?.FirstOrDefault(); } + } - private IEnumerable GetRelationsByListOfTypeIds(IEnumerable relationTypeIds) + private IEnumerable GetRelationsByListOfTypeIds(IEnumerable relationTypeIds) + { + var relations = new List(); + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - var relations = new List(); - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + foreach (var relationTypeId in relationTypeIds) { - foreach (var relationTypeId in relationTypeIds) + var id = relationTypeId; + IQuery query = Query().Where(x => x.RelationTypeId == id); + IEnumerable relation = _relationRepository.Get(query); + if (relation is not null) { - var id = relationTypeId; - var query = Query().Where(x => x.RelationTypeId == id); - var relation = _relationRepository.Get(query); - if (relation is not null) - { - relations.AddRange(relation); - } + relations.AddRange(relation); } } - return relations; } - private void Audit(AuditType type, int userId, int objectId, string? message = null) - { - _auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.RelationType), message)); - } - #endregion + return relations; } + + private void Audit(AuditType type, int userId, int objectId, string? message = null) => + _auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.RelationType.GetName(), + message)); + + #endregion } diff --git a/src/Umbraco.Core/Services/RepositoryService.cs b/src/Umbraco.Core/Services/RepositoryService.cs index 85e78672ee61..9afdd2ed6783 100644 --- a/src/Umbraco.Core/Services/RepositoryService.cs +++ b/src/Umbraco.Core/Services/RepositoryService.cs @@ -1,29 +1,28 @@ -using System; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Scoping; -namespace Umbraco.Cms.Core.Services +namespace Umbraco.Cms.Core.Services; + +/// +/// Represents a service that works on top of repositories. +/// +public abstract class RepositoryService : IService { - /// - /// Represents a service that works on top of repositories. - /// - public abstract class RepositoryService : IService + protected RepositoryService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory) { - protected IEventMessagesFactory EventMessagesFactory { get; } + EventMessagesFactory = eventMessagesFactory ?? throw new ArgumentNullException(nameof(eventMessagesFactory)); + ScopeProvider = provider ?? throw new ArgumentNullException(nameof(provider)); + LoggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); + } - protected ICoreScopeProvider ScopeProvider { get; } + protected IEventMessagesFactory EventMessagesFactory { get; } - protected ILoggerFactory LoggerFactory { get; } + protected ICoreScopeProvider ScopeProvider { get; } - protected RepositoryService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory) - { - EventMessagesFactory = eventMessagesFactory ?? throw new ArgumentNullException(nameof(eventMessagesFactory)); - ScopeProvider = provider ?? throw new ArgumentNullException(nameof(provider)); - LoggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); - } + protected ILoggerFactory LoggerFactory { get; } - protected IQuery Query() => ScopeProvider.CreateQuery(); - } + protected IQuery Query() => ScopeProvider.CreateQuery(); } diff --git a/src/Umbraco.Core/Services/SectionService.cs b/src/Umbraco.Core/Services/SectionService.cs index b698579b653e..ffb7914640d7 100644 --- a/src/Umbraco.Core/Services/SectionService.cs +++ b/src/Umbraco.Core/Services/SectionService.cs @@ -1,41 +1,40 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Sections; -namespace Umbraco.Cms.Core.Services +namespace Umbraco.Cms.Core.Services; + +public class SectionService : ISectionService { - public class SectionService : ISectionService - { - private readonly IUserService _userService; - private readonly SectionCollection _sectionCollection; + private readonly SectionCollection _sectionCollection; + private readonly IUserService _userService; - public SectionService( - IUserService userService, - SectionCollection sectionCollection) - { - _userService = userService ?? throw new ArgumentNullException(nameof(userService)); - _sectionCollection = sectionCollection ?? throw new ArgumentNullException(nameof(sectionCollection)); - } + public SectionService( + IUserService userService, + SectionCollection sectionCollection) + { + _userService = userService ?? throw new ArgumentNullException(nameof(userService)); + _sectionCollection = sectionCollection ?? throw new ArgumentNullException(nameof(sectionCollection)); + } - /// - /// The cache storage for all applications - /// - public IEnumerable GetSections() - => _sectionCollection; + /// + /// The cache storage for all applications + /// + public IEnumerable GetSections() + => _sectionCollection; - /// - public IEnumerable GetAllowedSections(int userId) + /// + public IEnumerable GetAllowedSections(int userId) + { + IUser user = _userService.GetUserById(userId); + if (user == null) { - var user = _userService.GetUserById(userId); - if (user == null) - throw new InvalidOperationException("No user found with id " + userId); - - return GetSections().Where(x => user.AllowedSections.Contains(x.Alias)); + throw new InvalidOperationException("No user found with id " + userId); } - /// - public ISection? GetByAlias(string appAlias) - => GetSections().FirstOrDefault(t => t.Alias.Equals(appAlias, StringComparison.OrdinalIgnoreCase)); + return GetSections().Where(x => user.AllowedSections.Contains(x.Alias)); } + + /// + public ISection? GetByAlias(string appAlias) + => GetSections().FirstOrDefault(t => t.Alias.Equals(appAlias, StringComparison.OrdinalIgnoreCase)); } diff --git a/src/Umbraco.Core/Services/ServerRegistrationService.cs b/src/Umbraco.Core/Services/ServerRegistrationService.cs index c92977aab0c7..825cc646a6f2 100644 --- a/src/Umbraco.Core/Services/ServerRegistrationService.cs +++ b/src/Umbraco.Core/Services/ServerRegistrationService.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Hosting; @@ -10,163 +7,176 @@ using Umbraco.Cms.Core.Sync; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services.Implement; + +/// +/// Manages server registrations in the database. +/// +public sealed class ServerRegistrationService : RepositoryService, IServerRegistrationService { + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IServerRegistrationRepository _serverRegistrationRepository; + + private ServerRole _currentServerRole = ServerRole.Unknown; + /// - /// Manages server registrations in the database. + /// Initializes a new instance of the class. /// - public sealed class ServerRegistrationService : RepositoryService, IServerRegistrationService + public ServerRegistrationService( + ICoreScopeProvider scopeProvider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IServerRegistrationRepository serverRegistrationRepository, + IHostingEnvironment hostingEnvironment) + : base(scopeProvider, loggerFactory, eventMessagesFactory) { - private readonly IServerRegistrationRepository _serverRegistrationRepository; - private readonly IHostingEnvironment _hostingEnvironment; - - private ServerRole _currentServerRole = ServerRole.Unknown; - - /// - /// Initializes a new instance of the class. - /// - public ServerRegistrationService( - ICoreScopeProvider scopeProvider, - ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, - IServerRegistrationRepository serverRegistrationRepository, - IHostingEnvironment hostingEnvironment) - : base(scopeProvider, loggerFactory, eventMessagesFactory) - { - _serverRegistrationRepository = serverRegistrationRepository; - _hostingEnvironment = hostingEnvironment; - } + _serverRegistrationRepository = serverRegistrationRepository; + _hostingEnvironment = hostingEnvironment; + } - /// - /// Touches a server to mark it as active; deactivate stale servers. - /// - /// The server URL. - /// The time after which a server is considered stale. - public void TouchServer(string serverAddress, TimeSpan staleTimeout) + /// + /// Touches a server to mark it as active; deactivate stale servers. + /// + /// The server URL. + /// The time after which a server is considered stale. + public void TouchServer(string serverAddress, TimeSpan staleTimeout) + { + var serverIdentity = GetCurrentServerIdentity(); + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - var serverIdentity = GetCurrentServerIdentity(); - using (var scope = ScopeProvider.CreateCoreScope()) - { - scope.WriteLock(Cms.Core.Constants.Locks.Servers); + scope.WriteLock(Constants.Locks.Servers); - _serverRegistrationRepository.ClearCache(); // ensure we have up-to-date cache + _serverRegistrationRepository.ClearCache(); // ensure we have up-to-date cache - var regs = _serverRegistrationRepository.GetMany()?.ToArray(); - var hasSchedulingPublisher = regs?.Any(x => ((ServerRegistration) x).IsSchedulingPublisher); - var server = regs?.FirstOrDefault(x => x.ServerIdentity?.InvariantEquals(serverIdentity) ?? false); + IServerRegistration[] regs = _serverRegistrationRepository.GetMany()?.ToArray(); + var hasSchedulingPublisher = regs?.Any(x => ((ServerRegistration)x).IsSchedulingPublisher); + IServerRegistration server = + regs?.FirstOrDefault(x => x.ServerIdentity?.InvariantEquals(serverIdentity) ?? false); - if (server == null) - { - server = new ServerRegistration(serverAddress, serverIdentity, DateTime.Now); - } - else - { - server.ServerAddress = serverAddress; // should not really change but it might! - server.UpdateDate = DateTime.Now; - } + if (server == null) + { + server = new ServerRegistration(serverAddress, serverIdentity, DateTime.Now); + } + else + { + server.ServerAddress = serverAddress; // should not really change but it might! + server.UpdateDate = DateTime.Now; + } - server.IsActive = true; - if (hasSchedulingPublisher == false) - server.IsSchedulingPublisher = true; + server.IsActive = true; + if (hasSchedulingPublisher == false) + { + server.IsSchedulingPublisher = true; + } - _serverRegistrationRepository.Save(server); - _serverRegistrationRepository.DeactiveStaleServers(staleTimeout); // triggers a cache reload + _serverRegistrationRepository.Save(server); + _serverRegistrationRepository.DeactiveStaleServers(staleTimeout); // triggers a cache reload - // reload - cheap, cached + // reload - cheap, cached - regs = _serverRegistrationRepository.GetMany()?.ToArray(); + regs = _serverRegistrationRepository.GetMany()?.ToArray(); - // default role is single server, but if registrations contain more - // than one active server, then role is scheduling publisher or subscriber - _currentServerRole = regs?.Count(x => x.IsActive) > 1 - ? (server.IsSchedulingPublisher ? ServerRole.SchedulingPublisher : ServerRole.Subscriber) - : ServerRole.Single; + // default role is single server, but if registrations contain more + // than one active server, then role is scheduling publisher or subscriber + _currentServerRole = regs?.Count(x => x.IsActive) > 1 + ? server.IsSchedulingPublisher ? ServerRole.SchedulingPublisher : ServerRole.Subscriber + : ServerRole.Single; - scope.Complete(); - } + scope.Complete(); } + } - /// - /// Deactivates a server. - /// - /// The server unique identity. - public void DeactiveServer(string serverIdentity) + /// + /// Deactivates a server. + /// + /// The server unique identity. + public void DeactiveServer(string serverIdentity) + { + // because the repository caches "all" and has queries disabled... + + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - // because the repository caches "all" and has queries disabled... + scope.WriteLock(Constants.Locks.Servers); - using (var scope = ScopeProvider.CreateCoreScope()) - { - scope.WriteLock(Cms.Core.Constants.Locks.Servers); + _serverRegistrationRepository + .ClearCache(); // ensure we have up-to-date cache // ensure we have up-to-date cache - _serverRegistrationRepository.ClearCache(); // ensure we have up-to-date cache // ensure we have up-to-date cache + IServerRegistration server = _serverRegistrationRepository.GetMany() + ?.FirstOrDefault(x => x.ServerIdentity?.InvariantEquals(serverIdentity) ?? false); + if (server == null) + { + return; + } - var server = _serverRegistrationRepository.GetMany()?.FirstOrDefault(x => x.ServerIdentity?.InvariantEquals(serverIdentity) ?? false); - if (server == null) return; - server.IsActive = server.IsSchedulingPublisher = false; - _serverRegistrationRepository.Save(server); // will trigger a cache reload // will trigger a cache reload + server.IsActive = server.IsSchedulingPublisher = false; + _serverRegistrationRepository.Save(server); // will trigger a cache reload // will trigger a cache reload - scope.Complete(); - } + scope.Complete(); } + } - /// - /// Deactivates stale servers. - /// - /// The time after which a server is considered stale. - public void DeactiveStaleServers(TimeSpan staleTimeout) + /// + /// Deactivates stale servers. + /// + /// The time after which a server is considered stale. + public void DeactiveStaleServers(TimeSpan staleTimeout) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - using (var scope = ScopeProvider.CreateCoreScope()) - { - scope.WriteLock(Cms.Core.Constants.Locks.Servers); - _serverRegistrationRepository.DeactiveStaleServers(staleTimeout); - scope.Complete(); - } + scope.WriteLock(Constants.Locks.Servers); + _serverRegistrationRepository.DeactiveStaleServers(staleTimeout); + scope.Complete(); } + } + + /// + /// Return all active servers. + /// + /// A value indicating whether to force-refresh the cache. + /// All active servers. + /// + /// By default this method will rely on the repository's cache, which is updated each + /// time the current server is touched, and the period depends on the configuration. Use the + /// parameter to force a cache refresh and reload active servers + /// from the database. + /// + public IEnumerable? GetActiveServers(bool refresh = false) => + GetServers(refresh).Where(x => x.IsActive); - /// - /// Return all active servers. - /// - /// A value indicating whether to force-refresh the cache. - /// All active servers. - /// By default this method will rely on the repository's cache, which is updated each - /// time the current server is touched, and the period depends on the configuration. Use the - /// parameter to force a cache refresh and reload active servers - /// from the database. - public IEnumerable? GetActiveServers(bool refresh = false) => GetServers(refresh).Where(x => x.IsActive); - - /// - /// Return all servers (active and inactive). - /// - /// A value indicating whether to force-refresh the cache. - /// All servers. - /// By default this method will rely on the repository's cache, which is updated each - /// time the current server is touched, and the period depends on the configuration. Use the - /// parameter to force a cache refresh and reload all servers - /// from the database. - public IEnumerable GetServers(bool refresh = false) + /// + /// Return all servers (active and inactive). + /// + /// A value indicating whether to force-refresh the cache. + /// All servers. + /// + /// By default this method will rely on the repository's cache, which is updated each + /// time the current server is touched, and the period depends on the configuration. Use the + /// parameter to force a cache refresh and reload all servers + /// from the database. + /// + public IEnumerable GetServers(bool refresh = false) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + scope.ReadLock(Constants.Locks.Servers); + if (refresh) { - scope.ReadLock(Cms.Core.Constants.Locks.Servers); - if (refresh) - { - _serverRegistrationRepository.ClearCache(); - } - - return _serverRegistrationRepository.GetMany().ToArray(); // fast, cached // fast, cached + _serverRegistrationRepository.ClearCache(); } - } - /// - /// Gets the role of the current server. - /// - /// The role of the current server. - public ServerRole GetCurrentServerRole() => _currentServerRole; - - /// - /// Gets the local server identity. - /// - private string GetCurrentServerIdentity() => Environment.MachineName // eg DOMAIN\SERVER - + "/" + _hostingEnvironment.ApplicationId; // eg /LM/S3SVC/11/ROOT; + return _serverRegistrationRepository.GetMany().ToArray(); // fast, cached // fast, cached + } } + + /// + /// Gets the role of the current server. + /// + /// The role of the current server. + public ServerRole GetCurrentServerRole() => _currentServerRole; + + /// + /// Gets the local server identity. + /// + private string GetCurrentServerIdentity() => Environment.MachineName // eg DOMAIN\SERVER + + "/" + _hostingEnvironment.ApplicationId; // eg /LM/S3SVC/11/ROOT; } diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index 20774bd7a2ca..1e79cb5e9a5b 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -1,275 +1,287 @@ -using System; +namespace Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.Services +/// +/// Represents the Umbraco Service context, which provides access to all services. +/// +public class ServiceContext { + private readonly Lazy? _auditService; + private readonly Lazy? _consentService; + private readonly Lazy? _contentService; + private readonly Lazy? _contentTypeBaseServiceProvider; + private readonly Lazy? _contentTypeService; + private readonly Lazy? _dataTypeService; + private readonly Lazy? _domainService; + private readonly Lazy? _entityService; + private readonly Lazy? _externalLoginService; + private readonly Lazy? _fileService; + private readonly Lazy? _keyValueService; + private readonly Lazy? _localizationService; + private readonly Lazy? _localizedTextService; + private readonly Lazy? _macroService; + private readonly Lazy? _mediaService; + private readonly Lazy? _mediaTypeService; + private readonly Lazy? _memberGroupService; + private readonly Lazy? _memberService; + private readonly Lazy? _memberTypeService; + private readonly Lazy? _notificationService; + private readonly Lazy? _packagingService; + private readonly Lazy? _publicAccessService; + private readonly Lazy? _redirectUrlService; + private readonly Lazy? _relationService; + private readonly Lazy? _serverRegistrationService; + private readonly Lazy? _tagService; + private readonly Lazy? _userService; + /// - /// Represents the Umbraco Service context, which provides access to all services. + /// Initializes a new instance of the class with lazy services. /// - public class ServiceContext + public ServiceContext(Lazy? publicAccessService, Lazy? domainService, + Lazy? auditService, Lazy? localizedTextService, + Lazy? tagService, Lazy? contentService, Lazy? userService, + Lazy? memberService, Lazy? mediaService, + Lazy? contentTypeService, Lazy? mediaTypeService, + Lazy? dataTypeService, Lazy? fileService, + Lazy? localizationService, Lazy? packagingService, + Lazy? serverRegistrationService, Lazy? entityService, + Lazy? relationService, Lazy? macroService, + Lazy? memberTypeService, Lazy? memberGroupService, + Lazy? notificationService, Lazy? externalLoginService, + Lazy? redirectUrlService, Lazy? consentService, + Lazy? keyValueService, Lazy? contentTypeBaseServiceProvider) { - private readonly Lazy? _publicAccessService; - private readonly Lazy? _domainService; - private readonly Lazy? _auditService; - private readonly Lazy? _localizedTextService; - private readonly Lazy? _tagService; - private readonly Lazy? _contentService; - private readonly Lazy? _userService; - private readonly Lazy? _memberService; - private readonly Lazy? _mediaService; - private readonly Lazy? _contentTypeService; - private readonly Lazy? _mediaTypeService; - private readonly Lazy? _dataTypeService; - private readonly Lazy? _fileService; - private readonly Lazy? _localizationService; - private readonly Lazy? _packagingService; - private readonly Lazy? _serverRegistrationService; - private readonly Lazy? _entityService; - private readonly Lazy? _relationService; - private readonly Lazy? _macroService; - private readonly Lazy? _memberTypeService; - private readonly Lazy? _memberGroupService; - private readonly Lazy? _notificationService; - private readonly Lazy? _externalLoginService; - private readonly Lazy? _redirectUrlService; - private readonly Lazy? _consentService; - private readonly Lazy? _keyValueService; - private readonly Lazy? _contentTypeBaseServiceProvider; - - /// - /// Initializes a new instance of the class with lazy services. - /// - public ServiceContext(Lazy? publicAccessService, Lazy? domainService, Lazy? auditService, Lazy? localizedTextService, Lazy? tagService, Lazy? contentService, Lazy? userService, Lazy? memberService, Lazy? mediaService, Lazy? contentTypeService, Lazy? mediaTypeService, Lazy? dataTypeService, Lazy? fileService, Lazy? localizationService, Lazy? packagingService, Lazy? serverRegistrationService, Lazy? entityService, Lazy? relationService, Lazy? macroService, Lazy? memberTypeService, Lazy? memberGroupService, Lazy? notificationService, Lazy? externalLoginService, Lazy? redirectUrlService, Lazy? consentService, Lazy? keyValueService, Lazy? contentTypeBaseServiceProvider) - { - _publicAccessService = publicAccessService; - _domainService = domainService; - _auditService = auditService; - _localizedTextService = localizedTextService; - _tagService = tagService; - _contentService = contentService; - _userService = userService; - _memberService = memberService; - _mediaService = mediaService; - _contentTypeService = contentTypeService; - _mediaTypeService = mediaTypeService; - _dataTypeService = dataTypeService; - _fileService = fileService; - _localizationService = localizationService; - _packagingService = packagingService; - _serverRegistrationService = serverRegistrationService; - _entityService = entityService; - _relationService = relationService; - _macroService = macroService; - _memberTypeService = memberTypeService; - _memberGroupService = memberGroupService; - _notificationService = notificationService; - _externalLoginService = externalLoginService; - _redirectUrlService = redirectUrlService; - _consentService = consentService; - _keyValueService = keyValueService; - _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; - } + _publicAccessService = publicAccessService; + _domainService = domainService; + _auditService = auditService; + _localizedTextService = localizedTextService; + _tagService = tagService; + _contentService = contentService; + _userService = userService; + _memberService = memberService; + _mediaService = mediaService; + _contentTypeService = contentTypeService; + _mediaTypeService = mediaTypeService; + _dataTypeService = dataTypeService; + _fileService = fileService; + _localizationService = localizationService; + _packagingService = packagingService; + _serverRegistrationService = serverRegistrationService; + _entityService = entityService; + _relationService = relationService; + _macroService = macroService; + _memberTypeService = memberTypeService; + _memberGroupService = memberGroupService; + _notificationService = notificationService; + _externalLoginService = externalLoginService; + _redirectUrlService = redirectUrlService; + _consentService = consentService; + _keyValueService = keyValueService; + _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; + } - /// - /// Creates a partial service context with only some services (for tests). - /// - /// - /// Using a true constructor for this confuses DI containers. - /// - public static ServiceContext CreatePartial( - IContentService? contentService = null, - IMediaService? mediaService = null, - IContentTypeService? contentTypeService = null, - IMediaTypeService? mediaTypeService = null, - IDataTypeService? dataTypeService = null, - IFileService? fileService = null, - ILocalizationService? localizationService = null, - IPackagingService? packagingService = null, - IEntityService? entityService = null, - IRelationService? relationService = null, - IMemberGroupService? memberGroupService = null, - IMemberTypeService? memberTypeService = null, - IMemberService? memberService = null, - IUserService? userService = null, - ITagService? tagService = null, - INotificationService? notificationService = null, - ILocalizedTextService? localizedTextService = null, - IAuditService? auditService = null, - IDomainService? domainService = null, - IMacroService? macroService = null, - IPublicAccessService? publicAccessService = null, - IExternalLoginService? externalLoginService = null, - IServerRegistrationService? serverRegistrationService = null, - IRedirectUrlService? redirectUrlService = null, - IConsentService? consentService = null, - IKeyValueService? keyValueService = null, - IContentTypeBaseServiceProvider? contentTypeBaseServiceProvider = null) - { - Lazy? Lazy(T? service) => service == null ? null : new Lazy(() => service); + /// + /// Gets the + /// + public IPublicAccessService? PublicAccessService => _publicAccessService?.Value; - return new ServiceContext( - Lazy(publicAccessService), - Lazy(domainService), - Lazy(auditService), - Lazy(localizedTextService), - Lazy(tagService), - Lazy(contentService), - Lazy(userService), - Lazy(memberService), - Lazy(mediaService), - Lazy(contentTypeService), - Lazy(mediaTypeService), - Lazy(dataTypeService), - Lazy(fileService), - Lazy(localizationService), - Lazy(packagingService), - Lazy(serverRegistrationService), - Lazy(entityService), - Lazy(relationService), - Lazy(macroService), - Lazy(memberTypeService), - Lazy(memberGroupService), - Lazy(notificationService), - Lazy(externalLoginService), - Lazy(redirectUrlService), - Lazy(consentService), - Lazy(keyValueService), - Lazy(contentTypeBaseServiceProvider) - ); - } + /// + /// Gets the + /// + public IDomainService? DomainService => _domainService?.Value; - /// - /// Gets the - /// - public IPublicAccessService? PublicAccessService => _publicAccessService?.Value; + /// + /// Gets the + /// + public IAuditService? AuditService => _auditService?.Value; - /// - /// Gets the - /// - public IDomainService? DomainService => _domainService?.Value; + /// + /// Gets the + /// + public ILocalizedTextService? TextService => _localizedTextService?.Value; - /// - /// Gets the - /// - public IAuditService? AuditService => _auditService?.Value; + /// + /// Gets the + /// + public INotificationService? NotificationService => _notificationService?.Value; - /// - /// Gets the - /// - public ILocalizedTextService? TextService => _localizedTextService?.Value; + /// + /// Gets the + /// + public IServerRegistrationService? ServerRegistrationService => _serverRegistrationService?.Value; - /// - /// Gets the - /// - public INotificationService? NotificationService => _notificationService?.Value; + /// + /// Gets the + /// + public ITagService? TagService => _tagService?.Value; - /// - /// Gets the - /// - public IServerRegistrationService? ServerRegistrationService => _serverRegistrationService?.Value; + /// + /// Gets the + /// + public IMacroService? MacroService => _macroService?.Value; - /// - /// Gets the - /// - public ITagService? TagService => _tagService?.Value; + /// + /// Gets the + /// + public IEntityService? EntityService => _entityService?.Value; - /// - /// Gets the - /// - public IMacroService? MacroService => _macroService?.Value; + /// + /// Gets the + /// + public IRelationService? RelationService => _relationService?.Value; - /// - /// Gets the - /// - public IEntityService? EntityService => _entityService?.Value; + /// + /// Gets the + /// + public IContentService? ContentService => _contentService?.Value; - /// - /// Gets the - /// - public IRelationService? RelationService => _relationService?.Value; + /// + /// Gets the + /// + public IContentTypeService? ContentTypeService => _contentTypeService?.Value; - /// - /// Gets the - /// - public IContentService? ContentService => _contentService?.Value; + /// + /// Gets the + /// + public IMediaTypeService? MediaTypeService => _mediaTypeService?.Value; - /// - /// Gets the - /// - public IContentTypeService? ContentTypeService => _contentTypeService?.Value; + /// + /// Gets the + /// + public IDataTypeService? DataTypeService => _dataTypeService?.Value; - /// - /// Gets the - /// - public IMediaTypeService? MediaTypeService => _mediaTypeService?.Value; + /// + /// Gets the + /// + public IFileService? FileService => _fileService?.Value; - /// - /// Gets the - /// - public IDataTypeService? DataTypeService => _dataTypeService?.Value; + /// + /// Gets the + /// + public ILocalizationService? LocalizationService => _localizationService?.Value; - /// - /// Gets the - /// - public IFileService? FileService => _fileService?.Value; + /// + /// Gets the + /// + public IMediaService? MediaService => _mediaService?.Value; - /// - /// Gets the - /// - public ILocalizationService? LocalizationService => _localizationService?.Value; + /// + /// Gets the + /// + public IPackagingService? PackagingService => _packagingService?.Value; - /// - /// Gets the - /// - public IMediaService? MediaService => _mediaService?.Value; + /// + /// Gets the + /// + public IUserService? UserService => _userService?.Value; - /// - /// Gets the - /// - public IPackagingService? PackagingService => _packagingService?.Value; + /// + /// Gets the + /// + public IMemberService? MemberService => _memberService?.Value; - /// - /// Gets the - /// - public IUserService? UserService => _userService?.Value; + /// + /// Gets the MemberTypeService + /// + public IMemberTypeService? MemberTypeService => _memberTypeService?.Value; - /// - /// Gets the - /// - public IMemberService? MemberService => _memberService?.Value; + /// + /// Gets the MemberGroupService + /// + public IMemberGroupService? MemberGroupService => _memberGroupService?.Value; - /// - /// Gets the MemberTypeService - /// - public IMemberTypeService? MemberTypeService => _memberTypeService?.Value; + /// + /// Gets the ExternalLoginService. + /// + public IExternalLoginService? ExternalLoginService => _externalLoginService?.Value; - /// - /// Gets the MemberGroupService - /// - public IMemberGroupService? MemberGroupService => _memberGroupService?.Value; + /// + /// Gets the RedirectUrlService. + /// + public IRedirectUrlService? RedirectUrlService => _redirectUrlService?.Value; - /// - /// Gets the ExternalLoginService. - /// - public IExternalLoginService? ExternalLoginService => _externalLoginService?.Value; + /// + /// Gets the ConsentService. + /// + public IConsentService? ConsentService => _consentService?.Value; - /// - /// Gets the RedirectUrlService. - /// - public IRedirectUrlService? RedirectUrlService => _redirectUrlService?.Value; + /// + /// Gets the KeyValueService. + /// + public IKeyValueService? KeyValueService => _keyValueService?.Value; - /// - /// Gets the ConsentService. - /// - public IConsentService? ConsentService => _consentService?.Value; + /// + /// Gets the ContentTypeServiceBaseFactory. + /// + public IContentTypeBaseServiceProvider? ContentTypeBaseServices => _contentTypeBaseServiceProvider?.Value; - /// - /// Gets the KeyValueService. - /// - public IKeyValueService? KeyValueService => _keyValueService?.Value; + /// + /// Creates a partial service context with only some services (for tests). + /// + /// + /// Using a true constructor for this confuses DI containers. + /// + public static ServiceContext CreatePartial( + IContentService? contentService = null, + IMediaService? mediaService = null, + IContentTypeService? contentTypeService = null, + IMediaTypeService? mediaTypeService = null, + IDataTypeService? dataTypeService = null, + IFileService? fileService = null, + ILocalizationService? localizationService = null, + IPackagingService? packagingService = null, + IEntityService? entityService = null, + IRelationService? relationService = null, + IMemberGroupService? memberGroupService = null, + IMemberTypeService? memberTypeService = null, + IMemberService? memberService = null, + IUserService? userService = null, + ITagService? tagService = null, + INotificationService? notificationService = null, + ILocalizedTextService? localizedTextService = null, + IAuditService? auditService = null, + IDomainService? domainService = null, + IMacroService? macroService = null, + IPublicAccessService? publicAccessService = null, + IExternalLoginService? externalLoginService = null, + IServerRegistrationService? serverRegistrationService = null, + IRedirectUrlService? redirectUrlService = null, + IConsentService? consentService = null, + IKeyValueService? keyValueService = null, + IContentTypeBaseServiceProvider? contentTypeBaseServiceProvider = null) + { + Lazy? Lazy(T? service) + { + return service == null ? null : new Lazy(() => service); + } - /// - /// Gets the ContentTypeServiceBaseFactory. - /// - public IContentTypeBaseServiceProvider? ContentTypeBaseServices => _contentTypeBaseServiceProvider?.Value; + return new ServiceContext( + Lazy(publicAccessService), + Lazy(domainService), + Lazy(auditService), + Lazy(localizedTextService), + Lazy(tagService), + Lazy(contentService), + Lazy(userService), + Lazy(memberService), + Lazy(mediaService), + Lazy(contentTypeService), + Lazy(mediaTypeService), + Lazy(dataTypeService), + Lazy(fileService), + Lazy(localizationService), + Lazy(packagingService), + Lazy(serverRegistrationService), + Lazy(entityService), + Lazy(relationService), + Lazy(macroService), + Lazy(memberTypeService), + Lazy(memberGroupService), + Lazy(notificationService), + Lazy(externalLoginService), + Lazy(redirectUrlService), + Lazy(consentService), + Lazy(keyValueService), + Lazy(contentTypeBaseServiceProvider) + ); } } diff --git a/src/Umbraco.Core/Services/TagService.cs b/src/Umbraco.Core/Services/TagService.cs index 65e4a32f9ed9..f25614419e2b 100644 --- a/src/Umbraco.Core/Services/TagService.cs +++ b/src/Umbraco.Core/Services/TagService.cs @@ -1,172 +1,171 @@ -using System; -using System.Collections.Generic; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; -namespace Umbraco.Cms.Core.Services +namespace Umbraco.Cms.Core.Services; + +/// +/// Tag service to query for tags in the tags db table. The tags returned are only relevant for published content & +/// saved media or members +/// +/// +/// If there is unpublished content with tags, those tags will not be contained +/// +public class TagService : RepositoryService, ITagService { - /// - /// Tag service to query for tags in the tags db table. The tags returned are only relevant for published content & saved media or members - /// - /// - /// If there is unpublished content with tags, those tags will not be contained - /// - public class TagService : RepositoryService, ITagService - { - private readonly ITagRepository _tagRepository; + private readonly ITagRepository _tagRepository; - public TagService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - ITagRepository tagRepository) - : base(provider, loggerFactory, eventMessagesFactory) - { - _tagRepository = tagRepository; - } + public TagService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + ITagRepository tagRepository) + : base(provider, loggerFactory, eventMessagesFactory) => + _tagRepository = tagRepository; - /// - public TaggedEntity? GetTaggedEntityById(int id) + /// + public TaggedEntity? GetTaggedEntityById(int id) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _tagRepository.GetTaggedEntityById(id); - } + return _tagRepository.GetTaggedEntityById(id); } + } - /// - public TaggedEntity? GetTaggedEntityByKey(Guid key) + /// + public TaggedEntity? GetTaggedEntityByKey(Guid key) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _tagRepository.GetTaggedEntityByKey(key); - } + return _tagRepository.GetTaggedEntityByKey(key); } + } - /// - public IEnumerable GetTaggedContentByTagGroup(string group, string? culture = null) + /// + public IEnumerable GetTaggedContentByTagGroup(string group, string? culture = null) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _tagRepository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Content, group, culture); - } + return _tagRepository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Content, group, culture); } + } - /// - public IEnumerable GetTaggedContentByTag(string tag, string? group = null, string? culture = null) + /// + public IEnumerable GetTaggedContentByTag(string tag, string? group = null, string? culture = null) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _tagRepository.GetTaggedEntitiesByTag(TaggableObjectTypes.Content, tag, group, culture); - } + return _tagRepository.GetTaggedEntitiesByTag(TaggableObjectTypes.Content, tag, group, culture); } + } - /// - public IEnumerable GetTaggedMediaByTagGroup(string group, string? culture = null) + /// + public IEnumerable GetTaggedMediaByTagGroup(string group, string? culture = null) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _tagRepository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Media, group, culture); - } + return _tagRepository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Media, group, culture); } + } - /// - public IEnumerable GetTaggedMediaByTag(string tag, string? group = null, string? culture = null) + /// + public IEnumerable GetTaggedMediaByTag(string tag, string? group = null, string? culture = null) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _tagRepository.GetTaggedEntitiesByTag(TaggableObjectTypes.Media, tag, group, culture); - } + return _tagRepository.GetTaggedEntitiesByTag(TaggableObjectTypes.Media, tag, group, culture); } + } - /// - public IEnumerable GetTaggedMembersByTagGroup(string group, string? culture = null) + /// + public IEnumerable GetTaggedMembersByTagGroup(string group, string? culture = null) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _tagRepository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Member, group, culture); - } + return _tagRepository.GetTaggedEntitiesByTagGroup(TaggableObjectTypes.Member, group, culture); } + } - /// - public IEnumerable GetTaggedMembersByTag(string tag, string? group = null, string? culture = null) + /// + public IEnumerable GetTaggedMembersByTag(string tag, string? group = null, string? culture = null) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _tagRepository.GetTaggedEntitiesByTag(TaggableObjectTypes.Member, tag, group, culture); - } + return _tagRepository.GetTaggedEntitiesByTag(TaggableObjectTypes.Member, tag, group, culture); } + } - /// - public IEnumerable GetAllTags(string? group = null, string? culture = null) + /// + public IEnumerable GetAllTags(string? group = null, string? culture = null) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _tagRepository.GetTagsForEntityType(TaggableObjectTypes.All, group, culture); - } + return _tagRepository.GetTagsForEntityType(TaggableObjectTypes.All, group, culture); } + } - /// - public IEnumerable GetAllContentTags(string? group = null, string? culture = null) + /// + public IEnumerable GetAllContentTags(string? group = null, string? culture = null) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _tagRepository.GetTagsForEntityType(TaggableObjectTypes.Content, group, culture); - } + return _tagRepository.GetTagsForEntityType(TaggableObjectTypes.Content, group, culture); } + } - /// - public IEnumerable GetAllMediaTags(string? group = null, string? culture = null) + /// + public IEnumerable GetAllMediaTags(string? group = null, string? culture = null) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _tagRepository.GetTagsForEntityType(TaggableObjectTypes.Media, group, culture); - } + return _tagRepository.GetTagsForEntityType(TaggableObjectTypes.Media, group, culture); } + } - /// - public IEnumerable GetAllMemberTags(string? group = null, string? culture = null) + /// + public IEnumerable GetAllMemberTags(string? group = null, string? culture = null) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _tagRepository.GetTagsForEntityType(TaggableObjectTypes.Member, group, culture); - } + return _tagRepository.GetTagsForEntityType(TaggableObjectTypes.Member, group, culture); } + } - /// - public IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string? group = null, string? culture = null) + /// + public IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string? group = null, + string? culture = null) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _tagRepository.GetTagsForProperty(contentId, propertyTypeAlias, group, culture); - } + return _tagRepository.GetTagsForProperty(contentId, propertyTypeAlias, group, culture); } + } - /// - public IEnumerable GetTagsForEntity(int contentId, string? group = null, string? culture = null) + /// + public IEnumerable GetTagsForEntity(int contentId, string? group = null, string? culture = null) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _tagRepository.GetTagsForEntity(contentId, group, culture); - } + return _tagRepository.GetTagsForEntity(contentId, group, culture); } + } - /// - public IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string? group = null, string? culture = null) + /// + public IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string? group = null, + string? culture = null) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _tagRepository.GetTagsForProperty(contentId, propertyTypeAlias, group, culture); - } + return _tagRepository.GetTagsForProperty(contentId, propertyTypeAlias, group, culture); } + } - /// - public IEnumerable GetTagsForEntity(Guid contentId, string? group = null, string? culture = null) + /// + public IEnumerable GetTagsForEntity(Guid contentId, string? group = null, string? culture = null) + { + using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _tagRepository.GetTagsForEntity(contentId, group, culture); - } + return _tagRepository.GetTagsForEntity(contentId, group, culture); } } } diff --git a/src/Umbraco.Core/Services/TrackedReferencesService.cs b/src/Umbraco.Core/Services/TrackedReferencesService.cs index ab5a09ce8ba8..3a6f585ed329 100644 --- a/src/Umbraco.Core/Services/TrackedReferencesService.cs +++ b/src/Umbraco.Core/Services/TrackedReferencesService.cs @@ -2,58 +2,63 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; -namespace Umbraco.Cms.Core.Services +namespace Umbraco.Cms.Core.Services; + +public class TrackedReferencesService : ITrackedReferencesService { - public class TrackedReferencesService : ITrackedReferencesService + private readonly IEntityService _entityService; + private readonly ICoreScopeProvider _scopeProvider; + private readonly ITrackedReferencesRepository _trackedReferencesRepository; + + public TrackedReferencesService(ITrackedReferencesRepository trackedReferencesRepository, + ICoreScopeProvider scopeProvider, IEntityService entityService) + { + _trackedReferencesRepository = trackedReferencesRepository; + _scopeProvider = scopeProvider; + _entityService = entityService; + } + + /// + /// Gets a paged result of items which are in relation with the current item. + /// Basically, shows the items which depend on the current item. + /// + public PagedResult GetPagedRelationsForItem(int id, long pageIndex, int pageSize, + bool filterMustBeIsDependency) + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); + IEnumerable items = _trackedReferencesRepository.GetPagedRelationsForItem(id, pageIndex, pageSize, + filterMustBeIsDependency, out var totalItems); + + return new PagedResult(totalItems, pageIndex + 1, pageSize) {Items = items}; + } + + /// + /// Gets a paged result of items used in any kind of relation from selected integer ids. + /// + public PagedResult GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, + bool filterMustBeIsDependency) { - private readonly ITrackedReferencesRepository _trackedReferencesRepository; - private readonly ICoreScopeProvider _scopeProvider; - private readonly IEntityService _entityService; - - public TrackedReferencesService(ITrackedReferencesRepository trackedReferencesRepository, ICoreScopeProvider scopeProvider, IEntityService entityService) - { - _trackedReferencesRepository = trackedReferencesRepository; - _scopeProvider = scopeProvider; - _entityService = entityService; - } - - /// - /// Gets a paged result of items which are in relation with the current item. - /// Basically, shows the items which depend on the current item. - /// - public PagedResult GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency) - { - using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); - var items = _trackedReferencesRepository.GetPagedRelationsForItem(id, pageIndex, pageSize, filterMustBeIsDependency, out var totalItems); - - return new PagedResult(totalItems, pageIndex + 1, pageSize) { Items = items }; - } - - /// - /// Gets a paged result of items used in any kind of relation from selected integer ids. - /// - public PagedResult GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency) - { - using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); - var items = _trackedReferencesRepository.GetPagedItemsWithRelations(ids, pageIndex, pageSize, filterMustBeIsDependency, out var totalItems); - - return new PagedResult(totalItems, pageIndex + 1, pageSize) { Items = items }; - } - - /// - /// Gets a paged result of the descending items that have any references, given a parent id. - /// - public PagedResult GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency) - { - using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); - - var items = _trackedReferencesRepository.GetPagedDescendantsInReferences( - parentId, - pageIndex, - pageSize, - filterMustBeIsDependency, - out var totalItems); - return new PagedResult(totalItems, pageIndex + 1, pageSize) { Items = items }; - } + using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); + IEnumerable items = _trackedReferencesRepository.GetPagedItemsWithRelations(ids, pageIndex, + pageSize, filterMustBeIsDependency, out var totalItems); + + return new PagedResult(totalItems, pageIndex + 1, pageSize) {Items = items}; + } + + /// + /// Gets a paged result of the descending items that have any references, given a parent id. + /// + public PagedResult GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, + bool filterMustBeIsDependency) + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); + + IEnumerable items = _trackedReferencesRepository.GetPagedDescendantsInReferences( + parentId, + pageIndex, + pageSize, + filterMustBeIsDependency, + out var totalItems); + return new PagedResult(totalItems, pageIndex + 1, pageSize) {Items = items}; } } diff --git a/src/Umbraco.Core/Services/TreeService.cs b/src/Umbraco.Core/Services/TreeService.cs index f325712d77ce..39d1960fb692 100644 --- a/src/Umbraco.Core/Services/TreeService.cs +++ b/src/Umbraco.Core/Services/TreeService.cs @@ -1,45 +1,39 @@ -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.Trees; +using Umbraco.Cms.Core.Trees; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services +namespace Umbraco.Cms.Core.Services; + +/// +/// Implements . +/// +public class TreeService : ITreeService { + private readonly TreeCollection _treeCollection; + /// - /// Implements . + /// Initializes a new instance of the class. /// - public class TreeService : ITreeService - { - private readonly TreeCollection _treeCollection; - - /// - /// Initializes a new instance of the class. - /// - /// - public TreeService(TreeCollection treeCollection) - { - _treeCollection = treeCollection; - } + /// + public TreeService(TreeCollection treeCollection) => _treeCollection = treeCollection; - /// - public Tree? GetByAlias(string treeAlias) => _treeCollection.FirstOrDefault(x => x.TreeAlias == treeAlias); + /// + public Tree? GetByAlias(string treeAlias) => _treeCollection.FirstOrDefault(x => x.TreeAlias == treeAlias); - /// - public IEnumerable GetAll(TreeUse use = TreeUse.Main) - // use HasFlagAny: if use is Main|Dialog, we want to return Main *and* Dialog trees - => _treeCollection.Where(x => x.TreeUse.HasFlagAny(use)); + /// + public IEnumerable GetAll(TreeUse use = TreeUse.Main) + // use HasFlagAny: if use is Main|Dialog, we want to return Main *and* Dialog trees + => _treeCollection.Where(x => x.TreeUse.HasFlagAny(use)); - /// - public IEnumerable GetBySection(string sectionAlias, TreeUse use = TreeUse.Main) - // use HasFlagAny: if use is Main|Dialog, we want to return Main *and* Dialog trees - => _treeCollection.Where(x => x.SectionAlias.InvariantEquals(sectionAlias) && x.TreeUse.HasFlagAny(use)).OrderBy(x => x.SortOrder).ToList(); + /// + public IEnumerable GetBySection(string sectionAlias, TreeUse use = TreeUse.Main) + // use HasFlagAny: if use is Main|Dialog, we want to return Main *and* Dialog trees + => _treeCollection.Where(x => x.SectionAlias.InvariantEquals(sectionAlias) && x.TreeUse.HasFlagAny(use)) + .OrderBy(x => x.SortOrder).ToList(); - /// - public IDictionary> GetBySectionGrouped(string sectionAlias, TreeUse use = TreeUse.Main) - { - return GetBySection(sectionAlias, use).GroupBy(x => x.TreeGroup).ToDictionary( - x => x.Key ?? "", - x => (IEnumerable) x.ToArray()); - } - } + /// + public IDictionary> + GetBySectionGrouped(string sectionAlias, TreeUse use = TreeUse.Main) => + GetBySection(sectionAlias, use).GroupBy(x => x.TreeGroup).ToDictionary( + x => x.Key ?? "", + x => (IEnumerable)x.ToArray()); } diff --git a/src/Umbraco.Core/Services/TwoFactorLoginService.cs b/src/Umbraco.Core/Services/TwoFactorLoginService.cs index 7a4feb91fbdc..b3c90234d7b2 100644 --- a/src/Umbraco.Core/Services/TwoFactorLoginService.cs +++ b/src/Umbraco.Core/Services/TwoFactorLoginService.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -12,216 +8,208 @@ using Umbraco.Cms.Core.Security; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.Services +namespace Umbraco.Cms.Core.Services; + +/// +public class TwoFactorLoginService : ITwoFactorLoginService2 { - /// - public class TwoFactorLoginService : ITwoFactorLoginService2 + private readonly IOptions _backOfficeIdentityOptions; + private readonly IOptions _identityOptions; + private readonly ILogger _logger; + private readonly ICoreScopeProvider _scopeProvider; + private readonly ITwoFactorLoginRepository _twoFactorLoginRepository; + private readonly IDictionary _twoFactorSetupGenerators; + + /// + /// Initializes a new instance of the class. + /// + public TwoFactorLoginService( + ITwoFactorLoginRepository twoFactorLoginRepository, + ICoreScopeProvider scopeProvider, + IEnumerable twoFactorSetupGenerators, + IOptions identityOptions, + IOptions backOfficeIdentityOptions, + ILogger logger) { - private readonly ITwoFactorLoginRepository _twoFactorLoginRepository; - private readonly ICoreScopeProvider _scopeProvider; - private readonly IOptions _identityOptions; - private readonly IOptions _backOfficeIdentityOptions; - private readonly IDictionary _twoFactorSetupGenerators; - private readonly ILogger _logger; - - /// - /// Initializes a new instance of the class. - /// - public TwoFactorLoginService( - ITwoFactorLoginRepository twoFactorLoginRepository, - ICoreScopeProvider scopeProvider, - IEnumerable twoFactorSetupGenerators, - IOptions identityOptions, - IOptions backOfficeIdentityOptions, - ILogger logger) - { - _twoFactorLoginRepository = twoFactorLoginRepository; - _scopeProvider = scopeProvider; - _identityOptions = identityOptions; - _backOfficeIdentityOptions = backOfficeIdentityOptions; - _logger = logger; - _twoFactorSetupGenerators = twoFactorSetupGenerators.ToDictionary(x =>x.ProviderName); - } + _twoFactorLoginRepository = twoFactorLoginRepository; + _scopeProvider = scopeProvider; + _identityOptions = identityOptions; + _backOfficeIdentityOptions = backOfficeIdentityOptions; + _logger = logger; + _twoFactorSetupGenerators = twoFactorSetupGenerators.ToDictionary(x => x.ProviderName); + } - [Obsolete("Use ctor with all params - This will be removed in v11")] - public TwoFactorLoginService( - ITwoFactorLoginRepository twoFactorLoginRepository, - ICoreScopeProvider scopeProvider, - IEnumerable twoFactorSetupGenerators, - IOptions identityOptions, - IOptions backOfficeIdentityOptions) + [Obsolete("Use ctor with all params - This will be removed in v11")] + public TwoFactorLoginService( + ITwoFactorLoginRepository twoFactorLoginRepository, + ICoreScopeProvider scopeProvider, + IEnumerable twoFactorSetupGenerators, + IOptions identityOptions, + IOptions backOfficeIdentityOptions) : this(twoFactorLoginRepository, scopeProvider, twoFactorSetupGenerators, identityOptions, backOfficeIdentityOptions, StaticServiceProvider.Instance.GetRequiredService>()) - { + { + } - } + /// + public async Task DeleteUserLoginsAsync(Guid userOrMemberKey) + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); + await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey); + } - /// - public async Task DeleteUserLoginsAsync(Guid userOrMemberKey) - { - using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); - await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey); - } + /// + public async Task> GetEnabledTwoFactorProviderNamesAsync(Guid userOrMemberKey) => + await GetEnabledProviderNamesAsync(userOrMemberKey); + + public async Task DisableWithCodeAsync(string providerName, Guid userOrMemberKey, string code) + { + var secret = await GetSecretForUserAndProviderAsync(userOrMemberKey, providerName); - /// - public async Task> GetEnabledTwoFactorProviderNamesAsync(Guid userOrMemberKey) + if (!_twoFactorSetupGenerators.TryGetValue(providerName, out ITwoFactorProvider? generator)) { - return await GetEnabledProviderNamesAsync(userOrMemberKey); + throw new InvalidOperationException($"No ITwoFactorSetupGenerator found for provider: {providerName}"); } - public async Task DisableWithCodeAsync(string providerName, Guid userOrMemberKey, string code) - { - var secret = await GetSecretForUserAndProviderAsync(userOrMemberKey, providerName); + var isValid = secret is not null && generator.ValidateTwoFactorPIN(secret, code); - if (!_twoFactorSetupGenerators.TryGetValue(providerName, out ITwoFactorProvider? generator)) - { - throw new InvalidOperationException($"No ITwoFactorSetupGenerator found for provider: {providerName}"); - } + if (!isValid) + { + return false; + } - var isValid = secret is not null && generator.ValidateTwoFactorPIN(secret, code); + return await DisableAsync(userOrMemberKey, providerName); + } - if (!isValid) + public async Task ValidateAndSaveAsync(string providerName, Guid userOrMemberKey, string secret, string code) + { + try + { + var isValid = ValidateTwoFactorSetup(providerName, secret, code); + if (isValid == false) { return false; } - return await DisableAsync(userOrMemberKey, providerName); - } - - public async Task ValidateAndSaveAsync(string providerName, Guid userOrMemberKey, string secret, string code) - { - - try + var twoFactorLogin = new TwoFactorLogin { - var isValid = ValidateTwoFactorSetup(providerName, secret, code); - if (isValid == false) - { - return false; - } - - var twoFactorLogin = new TwoFactorLogin() - { - Confirmed = true, - Secret = secret, - UserOrMemberKey = userOrMemberKey, - ProviderName = providerName - }; - - await SaveAsync(twoFactorLogin); - - return true; - } - catch (Exception ex) - { - _logger.LogError(ex, "Could not log in with the provided one-time-password"); - } + Confirmed = true, Secret = secret, UserOrMemberKey = userOrMemberKey, ProviderName = providerName + }; - return false; - } + await SaveAsync(twoFactorLogin); - private async Task> GetEnabledProviderNamesAsync(Guid userOrMemberKey) + return true; + } + catch (Exception ex) { - using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); - var providersOnUser = (await _twoFactorLoginRepository.GetByUserOrMemberKeyAsync(userOrMemberKey)) - .Select(x => x.ProviderName).ToArray(); - - return providersOnUser.Where(IsKnownProviderName)!; + _logger.LogError(ex, "Could not log in with the provided one-time-password"); } - /// - /// The provider needs to be registered as either a member provider or backoffice provider to show up. - /// - private bool IsKnownProviderName(string? providerName) - { - if (providerName is null) - { - return false; - } - if (_identityOptions.Value.Tokens.ProviderMap.ContainsKey(providerName)) - { - return true; - } + return false; + } - if (_backOfficeIdentityOptions.Value.Tokens.ProviderMap.ContainsKey(providerName)) - { - return true; - } + /// + public async Task IsTwoFactorEnabledAsync(Guid userOrMemberKey) => + (await GetEnabledProviderNamesAsync(userOrMemberKey)).Any(); - return false; - } + /// + public async Task GetSecretForUserAndProviderAsync(Guid userOrMemberKey, string providerName) + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); + return (await _twoFactorLoginRepository.GetByUserOrMemberKeyAsync(userOrMemberKey)) + .FirstOrDefault(x => x.ProviderName == providerName)?.Secret; + } - /// - public async Task IsTwoFactorEnabledAsync(Guid userOrMemberKey) + /// + public async Task GetSetupInfoAsync(Guid userOrMemberKey, string providerName) + { + var secret = await GetSecretForUserAndProviderAsync(userOrMemberKey, providerName); + + // Dont allow to generate a new secrets if user already has one + if (!string.IsNullOrEmpty(secret)) { - return (await GetEnabledProviderNamesAsync(userOrMemberKey)).Any(); + return default; } - /// - public async Task GetSecretForUserAndProviderAsync(Guid userOrMemberKey, string providerName) + secret = GenerateSecret(); + + if (!_twoFactorSetupGenerators.TryGetValue(providerName, out ITwoFactorProvider? generator)) { - using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); - return (await _twoFactorLoginRepository.GetByUserOrMemberKeyAsync(userOrMemberKey)).FirstOrDefault(x => x.ProviderName == providerName)?.Secret; + throw new InvalidOperationException($"No ITwoFactorSetupGenerator found for provider: {providerName}"); } - /// - public async Task GetSetupInfoAsync(Guid userOrMemberKey, string providerName) + return await generator.GetSetupDataAsync(userOrMemberKey, secret); + } + + /// + public IEnumerable GetAllProviderNames() => _twoFactorSetupGenerators.Keys; + + /// + public async Task DisableAsync(Guid userOrMemberKey, string providerName) + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); + return await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey, providerName); + } + + /// + public bool ValidateTwoFactorSetup(string providerName, string secret, string code) + { + if (!_twoFactorSetupGenerators.TryGetValue(providerName, out ITwoFactorProvider? generator)) { - var secret = await GetSecretForUserAndProviderAsync(userOrMemberKey, providerName); + throw new InvalidOperationException($"No ITwoFactorSetupGenerator found for provider: {providerName}"); + } - // Dont allow to generate a new secrets if user already has one - if (!string.IsNullOrEmpty(secret)) - { - return default; - } + return generator.ValidateTwoFactorSetup(secret, code); + } - secret = GenerateSecret(); + /// + public Task SaveAsync(TwoFactorLogin twoFactorLogin) + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); + _twoFactorLoginRepository.Save(twoFactorLogin); - if (!_twoFactorSetupGenerators.TryGetValue(providerName, out ITwoFactorProvider? generator)) - { - throw new InvalidOperationException($"No ITwoFactorSetupGenerator found for provider: {providerName}"); - } + return Task.CompletedTask; + } - return await generator.GetSetupDataAsync(userOrMemberKey, secret); - } + private async Task> GetEnabledProviderNamesAsync(Guid userOrMemberKey) + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); + var providersOnUser = (await _twoFactorLoginRepository.GetByUserOrMemberKeyAsync(userOrMemberKey)) + .Select(x => x.ProviderName).ToArray(); - /// - public IEnumerable GetAllProviderNames() => _twoFactorSetupGenerators.Keys; + return providersOnUser.Where(IsKnownProviderName)!; + } - /// - public async Task DisableAsync(Guid userOrMemberKey, string providerName) + /// + /// The provider needs to be registered as either a member provider or backoffice provider to show up. + /// + private bool IsKnownProviderName(string? providerName) + { + if (providerName is null) { - using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); - return await _twoFactorLoginRepository.DeleteUserLoginsAsync(userOrMemberKey, providerName); + return false; } - /// - public bool ValidateTwoFactorSetup(string providerName, string secret, string code) + if (_identityOptions.Value.Tokens.ProviderMap.ContainsKey(providerName)) { - if (!_twoFactorSetupGenerators.TryGetValue(providerName, out ITwoFactorProvider? generator)) - { - throw new InvalidOperationException($"No ITwoFactorSetupGenerator found for provider: {providerName}"); - } - - return generator.ValidateTwoFactorSetup(secret, code); + return true; } - /// - public Task SaveAsync(TwoFactorLogin twoFactorLogin) + if (_backOfficeIdentityOptions.Value.Tokens.ProviderMap.ContainsKey(providerName)) { - using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); - _twoFactorLoginRepository.Save(twoFactorLogin); - - return Task.CompletedTask; + return true; } - /// - /// Generates a new random unique secret. - /// - /// The random secret - protected virtual string GenerateSecret() => Guid.NewGuid().ToString(); + return false; } + + /// + /// Generates a new random unique secret. + /// + /// The random secret + protected virtual string GenerateSecret() => Guid.NewGuid().ToString(); } diff --git a/src/Umbraco.Core/Services/UpgradeService.cs b/src/Umbraco.Core/Services/UpgradeService.cs index e2003f837082..7a5269d2bf67 100644 --- a/src/Umbraco.Core/Services/UpgradeService.cs +++ b/src/Umbraco.Core/Services/UpgradeService.cs @@ -1,21 +1,15 @@ -using System.Threading.Tasks; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Semver; -namespace Umbraco.Cms.Core.Services +namespace Umbraco.Cms.Core.Services; + +public class UpgradeService : IUpgradeService { - public class UpgradeService : IUpgradeService - { - private readonly IUpgradeCheckRepository _upgradeCheckRepository; + private readonly IUpgradeCheckRepository _upgradeCheckRepository; - public UpgradeService(IUpgradeCheckRepository upgradeCheckRepository) - { - _upgradeCheckRepository = upgradeCheckRepository; - } + public UpgradeService(IUpgradeCheckRepository upgradeCheckRepository) => + _upgradeCheckRepository = upgradeCheckRepository; - public async Task CheckUpgrade(SemVersion version) - { - return await _upgradeCheckRepository.CheckUpgradeAsync(version); - } - } + public async Task CheckUpgrade(SemVersion version) => + await _upgradeCheckRepository.CheckUpgradeAsync(version); } diff --git a/src/Umbraco.Core/Services/UserDataService.cs b/src/Umbraco.Core/Services/UserDataService.cs index a3c6bd11b466..3a60a6b9081f 100644 --- a/src/Umbraco.Core/Services/UserDataService.cs +++ b/src/Umbraco.Core/Services/UserDataService.cs @@ -1,51 +1,46 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; +using System.Diagnostics; using System.Runtime.InteropServices; -using System.Threading; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Models; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services +namespace Umbraco.Cms.Core.Services; + +[Obsolete("Use the IUserDataService interface instead")] +public class UserDataService : IUserDataService { - [Obsolete("Use the IUserDataService interface instead")] - public class UserDataService : IUserDataService - { - private readonly IUmbracoVersion _version; - private readonly ILocalizationService _localizationService; + private readonly ILocalizationService _localizationService; + private readonly IUmbracoVersion _version; - public UserDataService(IUmbracoVersion version, ILocalizationService localizationService) - { - _version = version; - _localizationService = localizationService; - } + public UserDataService(IUmbracoVersion version, ILocalizationService localizationService) + { + _version = version; + _localizationService = localizationService; + } - public IEnumerable GetUserData() => - new List - { - new("Server OS", RuntimeInformation.OSDescription), - new("Server Framework", RuntimeInformation.FrameworkDescription), - new("Default Language", _localizationService.GetDefaultLanguageIsoCode()), - new("Umbraco Version", _version.SemanticVersion.ToSemanticStringWithoutBuild()), - new("Current Culture", Thread.CurrentThread.CurrentCulture.ToString()), - new("Current UI Culture", Thread.CurrentThread.CurrentUICulture.ToString()), - new("Current Webserver", GetCurrentWebServer()) - }; - - private string GetCurrentWebServer() => IsRunningInProcessIIS() ? "IIS" : "Kestrel"; - - public bool IsRunningInProcessIIS() + public IEnumerable GetUserData() => + new List { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return false; - } + new("Server OS", RuntimeInformation.OSDescription), + new("Server Framework", RuntimeInformation.FrameworkDescription), + new("Default Language", _localizationService.GetDefaultLanguageIsoCode()), + new("Umbraco Version", _version.SemanticVersion.ToSemanticStringWithoutBuild()), + new("Current Culture", Thread.CurrentThread.CurrentCulture.ToString()), + new("Current UI Culture", Thread.CurrentThread.CurrentUICulture.ToString()), + new("Current Webserver", GetCurrentWebServer()) + }; + + private string GetCurrentWebServer() => IsRunningInProcessIIS() ? "IIS" : "Kestrel"; - string processName = Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().ProcessName); - return (processName.Contains("w3wp") || processName.Contains("iisexpress")); + public bool IsRunningInProcessIIS() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return false; } + + var processName = Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().ProcessName); + return processName.Contains("w3wp") || processName.Contains("iisexpress"); } } diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index f0b5cc6a327f..cf16621273ff 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; using System.Data.Common; using System.Globalization; -using System.Linq; using System.Linq.Expressions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -16,1159 +13,1312 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services +namespace Umbraco.Cms.Core.Services; + +/// +/// Represents the UserService, which is an easy access to operations involving , +/// and eventually Backoffice Users. +/// +internal class UserService : RepositoryService, IUserService { + private readonly GlobalSettings _globalSettings; + private readonly ILogger _logger; + private readonly IRuntimeState _runtimeState; + private readonly IUserGroupRepository _userGroupRepository; + private readonly IUserRepository _userRepository; + + public UserService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, IRuntimeState runtimeState, + IUserRepository userRepository, IUserGroupRepository userGroupRepository, + IOptions globalSettings) + : base(provider, loggerFactory, eventMessagesFactory) + { + _runtimeState = runtimeState; + _userRepository = userRepository; + _userGroupRepository = userGroupRepository; + _globalSettings = globalSettings.Value; + _logger = loggerFactory.CreateLogger(); + } + + private bool IsUpgrading => + _runtimeState.Level == RuntimeLevel.Install || _runtimeState.Level == RuntimeLevel.Upgrade; + + #region Implementation of IMembershipUserService + /// - /// Represents the UserService, which is an easy access to operations involving , and eventually Backoffice Users. + /// Checks if a User with the username exists /// - internal class UserService : RepositoryService, IUserService + /// Username to check + /// True if the User exists otherwise False + public bool Exists(string username) { - private readonly IRuntimeState _runtimeState; - private readonly IUserRepository _userRepository; - private readonly IUserGroupRepository _userGroupRepository; - private readonly GlobalSettings _globalSettings; - private readonly ILogger _logger; - - public UserService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IRuntimeState runtimeState, - IUserRepository userRepository, IUserGroupRepository userGroupRepository, IOptions globalSettings) - : base(provider, loggerFactory, eventMessagesFactory) + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - _runtimeState = runtimeState; - _userRepository = userRepository; - _userGroupRepository = userGroupRepository; - _globalSettings = globalSettings.Value; - _logger = loggerFactory.CreateLogger(); + return _userRepository.ExistsByUserName(username); } + } - private bool IsUpgrading => _runtimeState.Level == RuntimeLevel.Install || _runtimeState.Level == RuntimeLevel.Upgrade; - - #region Implementation of IMembershipUserService + /// + /// Creates a new User + /// + /// The user will be saved in the database and returned with an Id + /// Username of the user to create + /// Email of the user to create + /// + /// + /// + public IUser CreateUserWithIdentity(string username, string email) => + CreateUserWithIdentity(username, email, string.Empty); - /// - /// Checks if a User with the username exists - /// - /// Username to check - /// True if the User exists otherwise False - public bool Exists(string username) - { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _userRepository.ExistsByUserName(username); - } - } + /// + /// Creates and persists a new + /// + /// Username of the to create + /// Email of the to create + /// + /// This value should be the encoded/encrypted/hashed value for the password that will be + /// stored in the database + /// + /// Not used for users + /// + /// + /// + IUser IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, + string memberTypeAlias) => CreateUserWithIdentity(username, email, passwordValue); - /// - /// Creates a new User - /// - /// The user will be saved in the database and returned with an Id - /// Username of the user to create - /// Email of the user to create - /// - public IUser CreateUserWithIdentity(string username, string email) - { - return CreateUserWithIdentity(username, email, string.Empty); - } + /// + /// Creates and persists a new + /// + /// Username of the to create + /// Email of the to create + /// + /// This value should be the encoded/encrypted/hashed value for the password that will be + /// stored in the database + /// + /// Alias of the Type + /// Is the member approved + /// + /// + /// + IUser IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, + string memberTypeAlias, bool isApproved) => CreateUserWithIdentity(username, email, passwordValue, isApproved); - /// - /// Creates and persists a new - /// - /// Username of the to create - /// Email of the to create - /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database - /// Not used for users - /// - IUser IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias) + /// + /// Creates and persists a Member + /// + /// + /// Using this method will persist the Member object before its returned + /// meaning that it will have an Id available (unlike the CreateMember method) + /// + /// Username of the Member to create + /// Email of the Member to create + /// + /// This value should be the encoded/encrypted/hashed value for the password that will be + /// stored in the database + /// + /// Is the user approved + /// + /// + /// + private IUser CreateUserWithIdentity(string username, string email, string passwordValue, bool isApproved = true) + { + if (username == null) { - return CreateUserWithIdentity(username, email, passwordValue); + throw new ArgumentNullException(nameof(username)); } - /// - /// Creates and persists a new - /// - /// Username of the to create - /// Email of the to create - /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database - /// Alias of the Type - /// Is the member approved - /// - IUser IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias, bool isApproved) + if (string.IsNullOrWhiteSpace(username)) { - return CreateUserWithIdentity(username, email, passwordValue, isApproved); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(username)); } - /// - /// Creates and persists a Member - /// - /// Using this method will persist the Member object before its returned - /// meaning that it will have an Id available (unlike the CreateMember method) - /// Username of the Member to create - /// Email of the Member to create - /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database - /// Is the user approved - /// - private IUser CreateUserWithIdentity(string username, string email, string passwordValue, bool isApproved = true) - { - if (username == null) throw new ArgumentNullException(nameof(username)); - if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(username)); - - var evtMsgs = EventMessagesFactory.Get(); + EventMessages evtMsgs = EventMessagesFactory.Get(); - // TODO: PUT lock here!! + // TODO: PUT lock here!! - User user; - using (var scope = ScopeProvider.CreateCoreScope()) + User user; + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + var loginExists = _userRepository.ExistsByLogin(username); + if (loginExists) { - var loginExists = _userRepository.ExistsByLogin(username); - if (loginExists) - throw new ArgumentException("Login already exists"); // causes rollback - - user = new User(_globalSettings) - { - Email = email, - Language = _globalSettings.DefaultUILanguage, - Name = username, - RawPasswordValue = passwordValue, - Username = username, - IsLockedOut = false, - IsApproved = isApproved - }; - - var savingNotification = new UserSavingNotification(user, evtMsgs); - if (scope.Notifications.PublishCancelable(savingNotification)) - { - scope.Complete(); - return user; - } - - _userRepository.Save(user); + throw new ArgumentException("Login already exists"); // causes rollback + } - scope.Notifications.Publish(new UserSavedNotification(user, evtMsgs).WithStateFrom(savingNotification)); + user = new User(_globalSettings) + { + Email = email, + Language = _globalSettings.DefaultUILanguage, + Name = username, + RawPasswordValue = passwordValue, + Username = username, + IsLockedOut = false, + IsApproved = isApproved + }; + + var savingNotification = new UserSavingNotification(user, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotification)) + { scope.Complete(); + return user; } - return user; + _userRepository.Save(user); + + scope.Notifications.Publish(new UserSavedNotification(user, evtMsgs).WithStateFrom(savingNotification)); + scope.Complete(); } - /// - /// Gets a User by its integer id - /// - /// Id - /// - public IUser? GetById(int id) + return user; + } + + /// + /// Gets a User by its integer id + /// + /// Id + /// + /// + /// + public IUser? GetById(int id) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _userRepository.Get(id); - } + return _userRepository.Get(id); } + } - /// - /// Gets an by its provider key - /// - /// Id to use for retrieval - /// - public IUser? GetByProviderKey(object id) + /// + /// Gets an by its provider key + /// + /// Id to use for retrieval + /// + /// + /// + public IUser? GetByProviderKey(object id) + { + Attempt asInt = id.TryConvertTo(); + return asInt.Success ? GetById(asInt.Result) : null; + } + + /// + /// Get an by email + /// + /// Email to use for retrieval + /// + /// + /// + public IUser? GetByEmail(string email) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - var asInt = id.TryConvertTo(); - return asInt.Success ? GetById(asInt.Result) : null; + IQuery query = Query().Where(x => x.Email.Equals(email)); + return _userRepository.Get(query)?.FirstOrDefault(); } + } - /// - /// Get an by email - /// - /// Email to use for retrieval - /// - public IUser? GetByEmail(string email) + /// + /// Get an by username + /// + /// Username to use for retrieval + /// + /// + /// + public IUser? GetByUsername(string? username) + { + if (username is null) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - var query = Query().Where(x => x.Email.Equals(email)); - return _userRepository.Get(query)?.FirstOrDefault(); - } + return null; } - /// - /// Get an by username - /// - /// Username to use for retrieval - /// - public IUser? GetByUsername(string? username) + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - if (username is null) + try { - return null; + return _userRepository.GetByUsername(username, true); } - - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + catch (DbException) { - try + // TODO: refactor users/upgrade + // currently kinda accepting anything on upgrade, but that won't deal with all cases + // so we need to do it differently, see the custom UmbracoPocoDataBuilder which should + // be better BUT requires that the app restarts after the upgrade! + if (IsUpgrading) { - return _userRepository.GetByUsername(username, includeSecurityData: true); + //NOTE: this will not be cached + return _userRepository.GetByUsername(username, false); } - catch (DbException) - { - // TODO: refactor users/upgrade - // currently kinda accepting anything on upgrade, but that won't deal with all cases - // so we need to do it differently, see the custom UmbracoPocoDataBuilder which should - // be better BUT requires that the app restarts after the upgrade! - if (IsUpgrading) - { - //NOTE: this will not be cached - return _userRepository.GetByUsername(username, includeSecurityData: false); - } - throw; - } + throw; } } + } - /// - /// Disables an - /// - /// to disable - public void Delete(IUser membershipUser) - { - //disable - membershipUser.IsApproved = false; - - Save(membershipUser); - } - - /// - /// Deletes or disables a User - /// - /// to delete - /// True to permanently delete the user, False to disable the user - public void Delete(IUser user, bool deletePermanently) - { - if (deletePermanently == false) - { - Delete(user); - } - else - { - var evtMsgs = EventMessagesFactory.Get(); - - using (var scope = ScopeProvider.CreateCoreScope()) - { - var deletingNotification = new UserDeletingNotification(user, evtMsgs); - if (scope.Notifications.PublishCancelable(deletingNotification)) - { - scope.Complete(); - return; - } - - _userRepository.Delete(user); + /// + /// Disables an + /// + /// to disable + public void Delete(IUser membershipUser) + { + //disable + membershipUser.IsApproved = false; - scope.Notifications.Publish(new UserDeletedNotification(user, evtMsgs).WithStateFrom(deletingNotification)); - scope.Complete(); - } - } - } + Save(membershipUser); + } - // explicit implementation because we don't need it now but due to the way that the members membership provider is put together - // this method must exist in this service as an implementation (legacy) - void IMembershipMemberService.SetLastLogin(string username, DateTime date) + /// + /// Deletes or disables a User + /// + /// to delete + /// True to permanently delete the user, False to disable the user + public void Delete(IUser user, bool deletePermanently) + { + if (deletePermanently == false) { - _logger.LogWarning("This method is not implemented. Using membership providers users is not advised, use ASP.NET Identity instead. See issue #9224 for more information."); + Delete(user); } - - /// - /// Saves an - /// - /// to Save - public void Save(IUser entity) + else { - var evtMsgs = EventMessagesFactory.Get(); + EventMessages evtMsgs = EventMessagesFactory.Get(); - using (var scope = ScopeProvider.CreateCoreScope()) + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - var savingNotification = new UserSavingNotification(entity, evtMsgs); - if (scope.Notifications.PublishCancelable(savingNotification)) + var deletingNotification = new UserDeletingNotification(user, evtMsgs); + if (scope.Notifications.PublishCancelable(deletingNotification)) { scope.Complete(); return; } - if (string.IsNullOrWhiteSpace(entity.Username)) - throw new ArgumentException("Empty username.", nameof(entity)); - - if (string.IsNullOrWhiteSpace(entity.Name)) - throw new ArgumentException("Empty name.", nameof(entity)); - - try - { - _userRepository.Save(entity); - scope.Notifications.Publish(new UserSavedNotification(entity, evtMsgs).WithStateFrom(savingNotification)); + _userRepository.Delete(user); - scope.Complete(); - } - catch (DbException ex) - { - // if we are upgrading and an exception occurs, log and swallow it - if (IsUpgrading == false) throw; - - _logger.LogWarning(ex, "An error occurred attempting to save a user instance during upgrade, normally this warning can be ignored"); - - // we don't want the uow to rollback its scope! - scope.Complete(); - } + scope.Notifications.Publish( + new UserDeletedNotification(user, evtMsgs).WithStateFrom(deletingNotification)); + scope.Complete(); } } + } - /// - /// Saves a list of objects - /// - /// to save - public void Save(IEnumerable entities) - { - var evtMsgs = EventMessagesFactory.Get(); + // explicit implementation because we don't need it now but due to the way that the members membership provider is put together + // this method must exist in this service as an implementation (legacy) + void IMembershipMemberService.SetLastLogin(string username, DateTime date) => _logger.LogWarning( + "This method is not implemented. Using membership providers users is not advised, use ASP.NET Identity instead. See issue #9224 for more information."); - var entitiesA = entities.ToArray(); + /// + /// Saves an + /// + /// to Save + public void Save(IUser entity) + { + EventMessages evtMsgs = EventMessagesFactory.Get(); - using (var scope = ScopeProvider.CreateCoreScope()) + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + var savingNotification = new UserSavingNotification(entity, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotification)) { - var savingNotification = new UserSavingNotification(entitiesA, evtMsgs); - if (scope.Notifications.PublishCancelable(savingNotification)) - { - scope.Complete(); - return; - } + scope.Complete(); + return; + } - foreach (var user in entitiesA) - { - if (string.IsNullOrWhiteSpace(user.Username)) - throw new ArgumentException("Empty username.", nameof(entities)); + if (string.IsNullOrWhiteSpace(entity.Username)) + { + throw new ArgumentException("Empty username.", nameof(entity)); + } - if (string.IsNullOrWhiteSpace(user.Name)) - throw new ArgumentException("Empty name.", nameof(entities)); + if (string.IsNullOrWhiteSpace(entity.Name)) + { + throw new ArgumentException("Empty name.", nameof(entity)); + } - _userRepository.Save(user); + try + { + _userRepository.Save(entity); + scope.Notifications.Publish( + new UserSavedNotification(entity, evtMsgs).WithStateFrom(savingNotification)); + scope.Complete(); + } + catch (DbException ex) + { + // if we are upgrading and an exception occurs, log and swallow it + if (IsUpgrading == false) + { + throw; } - scope.Notifications.Publish(new UserSavedNotification(entitiesA, evtMsgs).WithStateFrom(savingNotification)); + _logger.LogWarning(ex, + "An error occurred attempting to save a user instance during upgrade, normally this warning can be ignored"); - //commit the whole lot in one go + // we don't want the uow to rollback its scope! scope.Complete(); } } + } - /// - /// This is just the default user group that the membership provider will use - /// - /// - public string GetDefaultMemberType() - { - return Cms.Core.Constants.Security.WriterGroupAlias; - } + /// + /// Saves a list of objects + /// + /// to save + public void Save(IEnumerable entities) + { + EventMessages evtMsgs = EventMessagesFactory.Get(); - /// - /// Finds a list of objects by a partial email string - /// - /// Partial email string to match - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// The type of match to make as . Default is - /// - public IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + IUser[] entitiesA = entities.ToArray(); + + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + var savingNotification = new UserSavingNotification(entitiesA, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotification)) { - var query = Query(); - - switch (matchType) - { - case StringPropertyMatchType.Exact: - query?.Where(member => member.Email.Equals(emailStringToMatch)); - break; - case StringPropertyMatchType.Contains: - query?.Where(member => member.Email.Contains(emailStringToMatch)); - break; - case StringPropertyMatchType.StartsWith: - query?.Where(member => member.Email.StartsWith(emailStringToMatch)); - break; - case StringPropertyMatchType.EndsWith: - query?.Where(member => member.Email.EndsWith(emailStringToMatch)); - break; - case StringPropertyMatchType.Wildcard: - query?.Where(member => member.Email.SqlWildcard(emailStringToMatch, TextColumnType.NVarchar)); - break; - default: - throw new ArgumentOutOfRangeException(nameof(matchType)); - } - - return _userRepository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, dto => dto.Email); + scope.Complete(); + return; } - } - /// - /// Finds a list of objects by a partial username - /// - /// Partial username to match - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// The type of match to make as . Default is - /// - public IEnumerable FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + foreach (IUser user in entitiesA) { - var query = Query(); - - switch (matchType) + if (string.IsNullOrWhiteSpace(user.Username)) { - case StringPropertyMatchType.Exact: - query?.Where(member => member.Username.Equals(login)); - break; - case StringPropertyMatchType.Contains: - query?.Where(member => member.Username.Contains(login)); - break; - case StringPropertyMatchType.StartsWith: - query?.Where(member => member.Username.StartsWith(login)); - break; - case StringPropertyMatchType.EndsWith: - query?.Where(member => member.Username.EndsWith(login)); - break; - case StringPropertyMatchType.Wildcard: - query?.Where(member => member.Email.SqlWildcard(login, TextColumnType.NVarchar)); - break; - default: - throw new ArgumentOutOfRangeException(nameof(matchType)); + throw new ArgumentException("Empty username.", nameof(entities)); } - return _userRepository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, dto => dto.Username); - } - } - - /// - /// Gets the total number of Users based on the count type - /// - /// - /// The way the Online count is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members - /// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact science - /// but that is how MS have made theirs so we'll follow that principal. - /// - /// to count by - /// with number of Users for passed in type - public int GetCount(MemberCountType countType) - { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - IQuery? query; - - switch (countType) + if (string.IsNullOrWhiteSpace(user.Name)) { - case MemberCountType.All: - query = Query(); - break; - case MemberCountType.LockedOut: - query = Query()?.Where(x => x.IsLockedOut); - break; - case MemberCountType.Approved: - query = Query()?.Where(x => x.IsApproved); - break; - default: - throw new ArgumentOutOfRangeException(nameof(countType)); + throw new ArgumentException("Empty name.", nameof(entities)); } - return _userRepository.GetCountByQuery(query); + _userRepository.Save(user); } + + scope.Notifications.Publish( + new UserSavedNotification(entitiesA, evtMsgs).WithStateFrom(savingNotification)); + + //commit the whole lot in one go + scope.Complete(); } + } + + /// + /// This is just the default user group that the membership provider will use + /// + /// + public string GetDefaultMemberType() => Constants.Security.WriterGroupAlias; - public Guid CreateLoginSession(int userId, string requestingIpAddress) + /// + /// Finds a list of objects by a partial email string + /// + /// Partial email string to match + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// + /// The type of match to make as . Default is + /// + /// + /// + /// + /// + public IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, + out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope()) + IQuery query = Query(); + + switch (matchType) { - var session = _userRepository.CreateLoginSession(userId, requestingIpAddress); - scope.Complete(); - return session; + case StringPropertyMatchType.Exact: + query?.Where(member => member.Email.Equals(emailStringToMatch)); + break; + case StringPropertyMatchType.Contains: + query?.Where(member => member.Email.Contains(emailStringToMatch)); + break; + case StringPropertyMatchType.StartsWith: + query?.Where(member => member.Email.StartsWith(emailStringToMatch)); + break; + case StringPropertyMatchType.EndsWith: + query?.Where(member => member.Email.EndsWith(emailStringToMatch)); + break; + case StringPropertyMatchType.Wildcard: + query?.Where(member => member.Email.SqlWildcard(emailStringToMatch, TextColumnType.NVarchar)); + break; + default: + throw new ArgumentOutOfRangeException(nameof(matchType)); } + + return _userRepository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, + dto => dto.Email); } + } - public int ClearLoginSessions(int userId) + /// + /// Finds a list of objects by a partial username + /// + /// Partial username to match + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// + /// The type of match to make as . Default is + /// + /// + /// + /// + /// + public IEnumerable FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, + StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope()) + IQuery query = Query(); + + switch (matchType) { - var count = _userRepository.ClearLoginSessions(userId); - scope.Complete(); - return count; + case StringPropertyMatchType.Exact: + query?.Where(member => member.Username.Equals(login)); + break; + case StringPropertyMatchType.Contains: + query?.Where(member => member.Username.Contains(login)); + break; + case StringPropertyMatchType.StartsWith: + query?.Where(member => member.Username.StartsWith(login)); + break; + case StringPropertyMatchType.EndsWith: + query?.Where(member => member.Username.EndsWith(login)); + break; + case StringPropertyMatchType.Wildcard: + query?.Where(member => member.Email.SqlWildcard(login, TextColumnType.NVarchar)); + break; + default: + throw new ArgumentOutOfRangeException(nameof(matchType)); } + + return _userRepository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, + dto => dto.Username); } + } - public void ClearLoginSession(Guid sessionId) + /// + /// Gets the total number of Users based on the count type + /// + /// + /// The way the Online count is done is the same way that it is done in the MS SqlMembershipProvider - We query for any + /// members + /// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact + /// science + /// but that is how MS have made theirs so we'll follow that principal. + /// + /// to count by + /// with number of Users for passed in type + public int GetCount(MemberCountType countType) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope()) + IQuery? query; + + switch (countType) { - _userRepository.ClearLoginSession(sessionId); - scope.Complete(); + case MemberCountType.All: + query = Query(); + break; + case MemberCountType.LockedOut: + query = Query()?.Where(x => x.IsLockedOut); + break; + case MemberCountType.Approved: + query = Query()?.Where(x => x.IsApproved); + break; + default: + throw new ArgumentOutOfRangeException(nameof(countType)); } + + return _userRepository.GetCountByQuery(query); } + } - public bool ValidateLoginSession(int userId, Guid sessionId) + public Guid CreateLoginSession(int userId, string requestingIpAddress) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { - var result = _userRepository.ValidateLoginSession(userId, sessionId); - scope.Complete(); - return result; - } + Guid session = _userRepository.CreateLoginSession(userId, requestingIpAddress); + scope.Complete(); + return session; } + } - public IDictionary GetUserStates() + public int ClearLoginSessions(int userId) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _userRepository.GetUserStates(); - } + var count = _userRepository.ClearLoginSessions(userId); + scope.Complete(); + return count; } + } - public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, UserState[]? userState = null, string[]? userGroups = null, string? filter = null) + public void ClearLoginSession(Guid sessionId) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - IQuery? filterQuery = null; - if (filter.IsNullOrWhiteSpace() == false) - { - filterQuery = Query()?.Where(x => (x.Name != null && x.Name.Contains(filter!)) || x.Username.Contains(filter!)); - } + _userRepository.ClearLoginSession(sessionId); + scope.Complete(); + } + } - return GetAll(pageIndex, pageSize, out totalRecords, orderBy, orderDirection, userState, userGroups, null, filterQuery); + public bool ValidateLoginSession(int userId, Guid sessionId) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + var result = _userRepository.ValidateLoginSession(userId, sessionId); + scope.Complete(); + return result; } + } - public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, UserState[]? userState = null, string[]? includeUserGroups = null, string[]? excludeUserGroups = null, IQuery? filter = null) + public IDictionary GetUserStates() + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - Expression> sort; - switch (orderBy.ToUpperInvariant()) - { - case "USERNAME": - sort = member => member.Username; - break; - case "LANGUAGE": - sort = member => member.Language; - break; - case "NAME": - sort = member => member.Name; - break; - case "EMAIL": - sort = member => member.Email; - break; - case "ID": - sort = member => member.Id; - break; - case "CREATEDATE": - sort = member => member.CreateDate; - break; - case "UPDATEDATE": - sort = member => member.UpdateDate; - break; - case "ISAPPROVED": - sort = member => member.IsApproved; - break; - case "ISLOCKEDOUT": - sort = member => member.IsLockedOut; - break; - case "LASTLOGINDATE": - sort = member => member.LastLoginDate; - break; - default: - throw new IndexOutOfRangeException("The orderBy parameter " + orderBy + " is not valid"); - } + return _userRepository.GetUserStates(); + } + } - return _userRepository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, sort, orderDirection, includeUserGroups, excludeUserGroups, userState, filter); - } + public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, string orderBy, + Direction orderDirection, UserState[]? userState = null, string[]? userGroups = null, string? filter = null) + { + IQuery? filterQuery = null; + if (filter.IsNullOrWhiteSpace() == false) + { + filterQuery = Query()?.Where(x => + (x.Name != null && x.Name.Contains(filter!)) || x.Username.Contains(filter!)); } - /// - /// Gets a list of paged objects - /// - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// - public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords) + return GetAll(pageIndex, pageSize, out totalRecords, orderBy, orderDirection, userState, userGroups, null, + filterQuery); + } + + public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, string orderBy, + Direction orderDirection, UserState[]? userState = null, string[]? includeUserGroups = null, + string[]? excludeUserGroups = null, IQuery? filter = null) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + Expression> sort; + switch (orderBy.ToUpperInvariant()) { - return _userRepository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, member => member.Name); + case "USERNAME": + sort = member => member.Username; + break; + case "LANGUAGE": + sort = member => member.Language; + break; + case "NAME": + sort = member => member.Name; + break; + case "EMAIL": + sort = member => member.Email; + break; + case "ID": + sort = member => member.Id; + break; + case "CREATEDATE": + sort = member => member.CreateDate; + break; + case "UPDATEDATE": + sort = member => member.UpdateDate; + break; + case "ISAPPROVED": + sort = member => member.IsApproved; + break; + case "ISLOCKEDOUT": + sort = member => member.IsLockedOut; + break; + case "LASTLOGINDATE": + sort = member => member.LastLoginDate; + break; + default: + throw new IndexOutOfRangeException("The orderBy parameter " + orderBy + " is not valid"); } + + return _userRepository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, sort, + orderDirection, includeUserGroups, excludeUserGroups, userState, filter); + } + } + + /// + /// Gets a list of paged objects + /// + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// + /// + /// + public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + { + return _userRepository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, + member => member.Name); } + } - public IEnumerable GetNextUsers(int id, int count) + public IEnumerable GetNextUsers(int id, int count) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _userRepository.GetNextUsers(id, count); - } + return _userRepository.GetNextUsers(id, count); } + } - /// - /// Gets a list of objects associated with a given group - /// - /// Id of group - /// - public IEnumerable GetAllInGroup(int? groupId) + /// + /// Gets a list of objects associated with a given group + /// + /// Id of group + /// + /// + /// + public IEnumerable GetAllInGroup(int? groupId) + { + if (groupId is null) { - if (groupId is null) - { - return Array.Empty(); - } + return Array.Empty(); + } - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _userRepository.GetAllInGroup(groupId.Value); - } + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + { + return _userRepository.GetAllInGroup(groupId.Value); } + } - /// - /// Gets a list of objects not associated with a given group - /// - /// Id of group - /// - public IEnumerable GetAllNotInGroup(int groupId) + /// + /// Gets a list of objects not associated with a given group + /// + /// Id of group + /// + /// + /// + public IEnumerable GetAllNotInGroup(int groupId) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - using (var scope = ScopeProvider.CreateCoreScope()) - { - return _userRepository.GetAllNotInGroup(groupId); - } + return _userRepository.GetAllNotInGroup(groupId); } + } - #endregion + #endregion - #region Implementation of IUserService + #region Implementation of IUserService - /// - /// Gets an IProfile by User Id. - /// - /// Id of the User to retrieve - /// - public IProfile? GetProfileById(int id) + /// + /// Gets an IProfile by User Id. + /// + /// Id of the User to retrieve + /// + /// + /// + public IProfile? GetProfileById(int id) + { + //This is called a TON. Go get the full user from cache which should already be IProfile + IUser fullUser = GetUserById(id); + if (fullUser == null) { - //This is called a TON. Go get the full user from cache which should already be IProfile - var fullUser = GetUserById(id); - if (fullUser == null) return null; - var asProfile = fullUser as IProfile; - return asProfile ?? new UserProfile(fullUser.Id, fullUser.Name); + return null; } - /// - /// Gets a profile by username - /// - /// Username - /// - public IProfile? GetProfileByUserName(string username) + var asProfile = fullUser as IProfile; + return asProfile ?? new UserProfile(fullUser.Id, fullUser.Name); + } + + /// + /// Gets a profile by username + /// + /// Username + /// + /// + /// + public IProfile? GetProfileByUserName(string username) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _userRepository.GetProfile(username); - } + return _userRepository.GetProfile(username); } + } - /// - /// Gets a user by Id - /// - /// Id of the user to retrieve - /// - public IUser? GetUserById(int id) + /// + /// Gets a user by Id + /// + /// Id of the user to retrieve + /// + /// + /// + public IUser? GetUserById(int id) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + try + { + return _userRepository.Get(id); + } + catch (DbException) { - try + // TODO: refactor users/upgrade + // currently kinda accepting anything on upgrade, but that won't deal with all cases + // so we need to do it differently, see the custom UmbracoPocoDataBuilder which should + // be better BUT requires that the app restarts after the upgrade! + if (IsUpgrading) { - return _userRepository.Get(id); + //NOTE: this will not be cached + return _userRepository.Get(id, false); } - catch (DbException) - { - // TODO: refactor users/upgrade - // currently kinda accepting anything on upgrade, but that won't deal with all cases - // so we need to do it differently, see the custom UmbracoPocoDataBuilder which should - // be better BUT requires that the app restarts after the upgrade! - if (IsUpgrading) - { - //NOTE: this will not be cached - return _userRepository.Get(id, includeSecurityData: false); - } - throw; - } + throw; } } + } - public IEnumerable GetUsersById(params int[]? ids) + public IEnumerable GetUsersById(params int[]? ids) + { + if (ids?.Length <= 0) { - if (ids?.Length <= 0) return Enumerable.Empty(); - - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _userRepository.GetMany(ids); - } + return Enumerable.Empty(); } - /// - /// Replaces the same permission set for a single group to any number of entities - /// - /// If no 'entityIds' are specified all permissions will be removed for the specified group. - /// Id of the group - /// Permissions as enumerable list of If nothing is specified all permissions are removed. - /// Specify the nodes to replace permissions for. - public void ReplaceUserGroupPermissions(int groupId, IEnumerable? permissions, params int[] entityIds) + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - if (entityIds.Length == 0) - return; - - var evtMsgs = EventMessagesFactory.Get(); - - using (var scope = ScopeProvider.CreateCoreScope()) - { - _userGroupRepository.ReplaceGroupPermissions(groupId, permissions, entityIds); - scope.Complete(); - - var assigned = permissions?.Select(p => p.ToString(CultureInfo.InvariantCulture)).ToArray(); - if (assigned is not null) - { - var entityPermissions = entityIds.Select(x => new EntityPermission(groupId, x, assigned)).ToArray(); - scope.Notifications.Publish(new AssignedUserGroupPermissionsNotification(entityPermissions, evtMsgs)); - } - } + return _userRepository.GetMany(ids); } + } - /// - /// Assigns the same permission set for a single user group to any number of entities - /// - /// Id of the user group - /// - /// Specify the nodes to replace permissions for - public void AssignUserGroupPermission(int groupId, char permission, params int[] entityIds) + /// + /// Replaces the same permission set for a single group to any number of entities + /// + /// If no 'entityIds' are specified all permissions will be removed for the specified group. + /// Id of the group + /// + /// Permissions as enumerable list of If nothing is specified all permissions + /// are removed. + /// + /// Specify the nodes to replace permissions for. + public void ReplaceUserGroupPermissions(int groupId, IEnumerable? permissions, params int[] entityIds) + { + if (entityIds.Length == 0) { - if (entityIds.Length == 0) - return; + return; + } - var evtMsgs = EventMessagesFactory.Get(); + EventMessages evtMsgs = EventMessagesFactory.Get(); - using (var scope = ScopeProvider.CreateCoreScope()) - { - _userGroupRepository.AssignGroupPermission(groupId, permission, entityIds); - scope.Complete(); + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + _userGroupRepository.ReplaceGroupPermissions(groupId, permissions, entityIds); + scope.Complete(); - var assigned = new[] { permission.ToString(CultureInfo.InvariantCulture) }; - var entityPermissions = entityIds.Select(x => new EntityPermission(groupId, x, assigned)).ToArray(); + var assigned = permissions?.Select(p => p.ToString(CultureInfo.InvariantCulture)).ToArray(); + if (assigned is not null) + { + EntityPermission[] entityPermissions = + entityIds.Select(x => new EntityPermission(groupId, x, assigned)).ToArray(); scope.Notifications.Publish(new AssignedUserGroupPermissionsNotification(entityPermissions, evtMsgs)); } } + } - /// - /// Gets all UserGroups or those specified as parameters - /// - /// Optional Ids of UserGroups to retrieve - /// An enumerable list of - public IEnumerable GetAllUserGroups(params int[] ids) + /// + /// Assigns the same permission set for a single user group to any number of entities + /// + /// Id of the user group + /// + /// Specify the nodes to replace permissions for + public void AssignUserGroupPermission(int groupId, char permission, params int[] entityIds) + { + if (entityIds.Length == 0) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _userGroupRepository.GetMany(ids).OrderBy(x => x.Name); - } + return; } - public IEnumerable GetUserGroupsByAlias(params string[] aliases) + EventMessages evtMsgs = EventMessagesFactory.Get(); + + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - if (aliases.Length == 0) return Enumerable.Empty(); + _userGroupRepository.AssignGroupPermission(groupId, permission, entityIds); + scope.Complete(); - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - var query = Query().Where(x => aliases.SqlIn(x.Alias)); - var contents = _userGroupRepository.Get(query); - return contents?.WhereNotNull().ToArray() ?? Enumerable.Empty(); - } + var assigned = new[] {permission.ToString(CultureInfo.InvariantCulture)}; + EntityPermission[] entityPermissions = + entityIds.Select(x => new EntityPermission(groupId, x, assigned)).ToArray(); + scope.Notifications.Publish(new AssignedUserGroupPermissionsNotification(entityPermissions, evtMsgs)); } + } - /// - /// Gets a UserGroup by its Alias - /// - /// Alias of the UserGroup to retrieve - /// - public IUserGroup? GetUserGroupByAlias(string alias) + /// + /// Gets all UserGroups or those specified as parameters + /// + /// Optional Ids of UserGroups to retrieve + /// An enumerable list of + public IEnumerable GetAllUserGroups(params int[] ids) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentException("Value cannot be null or whitespace.", "alias"); - - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - var query = Query().Where(x => x.Alias == alias); - var contents = _userGroupRepository.Get(query); - return contents?.FirstOrDefault(); - } + return _userGroupRepository.GetMany(ids).OrderBy(x => x.Name); } + } - /// - /// Gets a UserGroup by its Id - /// - /// Id of the UserGroup to retrieve - /// - public IUserGroup? GetUserGroupById(int id) + public IEnumerable GetUserGroupsByAlias(params string[] aliases) + { + if (aliases.Length == 0) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _userGroupRepository.Get(id); - } + return Enumerable.Empty(); } - /// - /// Saves a UserGroup - /// - /// UserGroup to save - /// - /// If null than no changes are made to the users who are assigned to this group, however if a value is passed in - /// than all users will be removed from this group and only these users will be added - /// - /// Default is True otherwise set to False to not raise events - public void Save(IUserGroup userGroup, int[]? userIds = null) + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - var evtMsgs = EventMessagesFactory.Get(); - - using (var scope = ScopeProvider.CreateCoreScope()) - { - // we need to figure out which users have been added / removed, for audit purposes - var empty = new IUser[0]; - var addedUsers = empty; - var removedUsers = empty; + IQuery query = Query().Where(x => aliases.SqlIn(x.Alias)); + IEnumerable contents = _userGroupRepository.Get(query); + return contents?.WhereNotNull().ToArray() ?? Enumerable.Empty(); + } + } - if (userIds != null) - { - var groupUsers = userGroup.HasIdentity ? _userRepository.GetAllInGroup(userGroup.Id).ToArray() : empty; - var xGroupUsers = groupUsers.ToDictionary(x => x.Id, x => x); - var groupIds = groupUsers.Select(x => x.Id).ToArray(); - var addedUserIds = userIds.Except(groupIds); + /// + /// Gets a UserGroup by its Alias + /// + /// Alias of the UserGroup to retrieve + /// + /// + /// + public IUserGroup? GetUserGroupByAlias(string alias) + { + if (string.IsNullOrWhiteSpace(alias)) + { + throw new ArgumentException("Value cannot be null or whitespace.", "alias"); + } - addedUsers = addedUserIds.Count() > 0 ? _userRepository.GetMany(addedUserIds.ToArray()).Where(x => x.Id != 0).ToArray() : new IUser[] { }; - removedUsers = groupIds.Except(userIds).Select(x => xGroupUsers[x]).Where(x => x.Id != 0).ToArray(); - } + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + { + IQuery query = Query().Where(x => x.Alias == alias); + IEnumerable contents = _userGroupRepository.Get(query); + return contents?.FirstOrDefault(); + } + } - var userGroupWithUsers = new UserGroupWithUsers(userGroup, addedUsers, removedUsers); + /// + /// Gets a UserGroup by its Id + /// + /// Id of the UserGroup to retrieve + /// + /// + /// + public IUserGroup? GetUserGroupById(int id) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + { + return _userGroupRepository.Get(id); + } + } - // this is the default/expected notification for the IUserGroup entity being saved - var savingNotification = new UserGroupSavingNotification(userGroup, evtMsgs); - if (scope.Notifications.PublishCancelable(savingNotification)) - { - scope.Complete(); - return; - } + /// + /// Saves a UserGroup + /// + /// UserGroup to save + /// + /// If null than no changes are made to the users who are assigned to this group, however if a value is passed in + /// than all users will be removed from this group and only these users will be added + /// + /// Default is + /// True + /// otherwise set to + /// False + /// to not raise events + /// + public void Save(IUserGroup userGroup, int[]? userIds = null) + { + EventMessages evtMsgs = EventMessagesFactory.Get(); - // this is an additional notification for special auditing - var savingUserGroupWithUsersNotification = new UserGroupWithUsersSavingNotification(userGroupWithUsers, evtMsgs); - if (scope.Notifications.PublishCancelable(savingUserGroupWithUsersNotification)) - { - scope.Complete(); - return; - } + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + // we need to figure out which users have been added / removed, for audit purposes + var empty = new IUser[0]; + IUser[] addedUsers = empty; + IUser[] removedUsers = empty; - _userGroupRepository.AddOrUpdateGroupWithUsers(userGroup, userIds); + if (userIds != null) + { + IUser[] groupUsers = + userGroup.HasIdentity ? _userRepository.GetAllInGroup(userGroup.Id).ToArray() : empty; + var xGroupUsers = groupUsers.ToDictionary(x => x.Id, x => x); + var groupIds = groupUsers.Select(x => x.Id).ToArray(); + IEnumerable addedUserIds = userIds.Except(groupIds); + + addedUsers = addedUserIds.Count() > 0 + ? _userRepository.GetMany(addedUserIds.ToArray()).Where(x => x.Id != 0).ToArray() + : new IUser[] { }; + removedUsers = groupIds.Except(userIds).Select(x => xGroupUsers[x]).Where(x => x.Id != 0).ToArray(); + } - scope.Notifications.Publish(new UserGroupSavedNotification(userGroup, evtMsgs).WithStateFrom(savingNotification)); - scope.Notifications.Publish(new UserGroupWithUsersSavedNotification(userGroupWithUsers, evtMsgs).WithStateFrom(savingUserGroupWithUsersNotification)); + var userGroupWithUsers = new UserGroupWithUsers(userGroup, addedUsers, removedUsers); + // this is the default/expected notification for the IUserGroup entity being saved + var savingNotification = new UserGroupSavingNotification(userGroup, evtMsgs); + if (scope.Notifications.PublishCancelable(savingNotification)) + { scope.Complete(); + return; } - } - - /// - /// Deletes a UserGroup - /// - /// UserGroup to delete - public void DeleteUserGroup(IUserGroup userGroup) - { - var evtMsgs = EventMessagesFactory.Get(); - using (var scope = ScopeProvider.CreateCoreScope()) + // this is an additional notification for special auditing + var savingUserGroupWithUsersNotification = + new UserGroupWithUsersSavingNotification(userGroupWithUsers, evtMsgs); + if (scope.Notifications.PublishCancelable(savingUserGroupWithUsersNotification)) { - var deletingNotification = new UserGroupDeletingNotification(userGroup, evtMsgs); - if (scope.Notifications.PublishCancelable(deletingNotification)) - { - scope.Complete(); - return; - } + scope.Complete(); + return; + } - _userGroupRepository.Delete(userGroup); + _userGroupRepository.AddOrUpdateGroupWithUsers(userGroup, userIds); - scope.Notifications.Publish(new UserGroupDeletedNotification(userGroup, evtMsgs).WithStateFrom(deletingNotification)); + scope.Notifications.Publish( + new UserGroupSavedNotification(userGroup, evtMsgs).WithStateFrom(savingNotification)); + scope.Notifications.Publish( + new UserGroupWithUsersSavedNotification(userGroupWithUsers, evtMsgs).WithStateFrom( + savingUserGroupWithUsersNotification)); - scope.Complete(); - } + scope.Complete(); } + } - /// - /// Removes a specific section from all users - /// - /// This is useful when an entire section is removed from config - /// Alias of the section to remove - public void DeleteSectionFromAllUserGroups(string sectionAlias) + /// + /// Deletes a UserGroup + /// + /// UserGroup to delete + public void DeleteUserGroup(IUserGroup userGroup) + { + EventMessages evtMsgs = EventMessagesFactory.Get(); + + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - using (var scope = ScopeProvider.CreateCoreScope()) + var deletingNotification = new UserGroupDeletingNotification(userGroup, evtMsgs); + if (scope.Notifications.PublishCancelable(deletingNotification)) { - var assignedGroups = _userGroupRepository.GetGroupsAssignedToSection(sectionAlias); - foreach (var group in assignedGroups) - { - //now remove the section for each user and commit - //now remove the section for each user and commit - group.RemoveAllowedSection(sectionAlias); - _userGroupRepository.Save(group); - } - scope.Complete(); + return; } + + _userGroupRepository.Delete(userGroup); + + scope.Notifications.Publish( + new UserGroupDeletedNotification(userGroup, evtMsgs).WithStateFrom(deletingNotification)); + + scope.Complete(); } + } - /// - /// Get explicitly assigned permissions for a user and optional node ids - /// - /// User to retrieve permissions for - /// Specifying nothing will return all permissions for all nodes - /// An enumerable list of - public EntityPermissionCollection GetPermissions(IUser? user, params int[] nodeIds) + /// + /// Removes a specific section from all users + /// + /// This is useful when an entire section is removed from config + /// Alias of the section to remove + public void DeleteSectionFromAllUserGroups(string sectionAlias) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + IEnumerable assignedGroups = _userGroupRepository.GetGroupsAssignedToSection(sectionAlias); + foreach (IUserGroup group in assignedGroups) { - return _userGroupRepository.GetPermissions(user?.Groups.ToArray(), true, nodeIds); + //now remove the section for each user and commit + //now remove the section for each user and commit + group.RemoveAllowedSection(sectionAlias); + _userGroupRepository.Save(group); } + + scope.Complete(); } + } - /// - /// Get explicitly assigned permissions for a group and optional node Ids - /// - /// Groups to retrieve permissions for - /// - /// Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set - /// - /// Specifying nothing will return all permissions for all nodes - /// An enumerable list of - private IEnumerable GetPermissions(IReadOnlyUserGroup[] groups, bool fallbackToDefaultPermissions, params int[] nodeIds) + /// + /// Get explicitly assigned permissions for a user and optional node ids + /// + /// User to retrieve permissions for + /// Specifying nothing will return all permissions for all nodes + /// An enumerable list of + public EntityPermissionCollection GetPermissions(IUser? user, params int[] nodeIds) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - if (groups == null) throw new ArgumentNullException(nameof(groups)); + return _userGroupRepository.GetPermissions(user?.Groups.ToArray(), true, nodeIds); + } + } - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _userGroupRepository.GetPermissions(groups, fallbackToDefaultPermissions, nodeIds); - } + /// + /// Get explicitly assigned permissions for a group and optional node Ids + /// + /// Groups to retrieve permissions for + /// + /// Flag indicating if we want to include the default group permissions for each result if there are not explicit + /// permissions set + /// + /// Specifying nothing will return all permissions for all nodes + /// An enumerable list of + private IEnumerable GetPermissions(IReadOnlyUserGroup[] groups, bool fallbackToDefaultPermissions, + params int[] nodeIds) + { + if (groups == null) + { + throw new ArgumentNullException(nameof(groups)); } - /// - /// Get explicitly assigned permissions for a group and optional node Ids - /// - /// - /// - /// Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set - /// - /// Specifying nothing will return all permissions for all nodes - /// An enumerable list of - public EntityPermissionCollection GetPermissions(IUserGroup?[] groups, bool fallbackToDefaultPermissions, params int[] nodeIds) + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - if (groups == null) throw new ArgumentNullException(nameof(groups)); - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _userGroupRepository.GetPermissions(groups.WhereNotNull().Select(x => x.ToReadOnlyGroup()).ToArray(), fallbackToDefaultPermissions, nodeIds); - } + return _userGroupRepository.GetPermissions(groups, fallbackToDefaultPermissions, nodeIds); } - /// - /// Gets the implicit/inherited permissions for the user for the given path - /// - /// User to check permissions for - /// Path to check permissions for - public EntityPermissionSet GetPermissionsForPath(IUser? user, string? path) + } + + /// + /// Get explicitly assigned permissions for a group and optional node Ids + /// + /// + /// + /// Flag indicating if we want to include the default group permissions for each result if there are not explicit + /// permissions set + /// + /// Specifying nothing will return all permissions for all nodes + /// An enumerable list of + public EntityPermissionCollection GetPermissions(IUserGroup?[] groups, bool fallbackToDefaultPermissions, + params int[] nodeIds) + { + if (groups == null) { - var nodeIds = path?.GetIdsFromPathReversed(); + throw new ArgumentNullException(nameof(groups)); + } - if (nodeIds is null || nodeIds.Length == 0 || user is null) - return EntityPermissionSet.Empty(); + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + { + return _userGroupRepository.GetPermissions(groups.WhereNotNull().Select(x => x.ToReadOnlyGroup()).ToArray(), + fallbackToDefaultPermissions, nodeIds); + } + } - //collect all permissions structures for all nodes for all groups belonging to the user - var groupPermissions = GetPermissionsForPath(user.Groups.ToArray(), nodeIds, fallbackToDefaultPermissions: true).ToArray(); + /// + /// Gets the implicit/inherited permissions for the user for the given path + /// + /// User to check permissions for + /// Path to check permissions for + public EntityPermissionSet GetPermissionsForPath(IUser? user, string? path) + { + var nodeIds = path?.GetIdsFromPathReversed(); - return CalculatePermissionsForPathForUser(groupPermissions, nodeIds); + if (nodeIds is null || nodeIds.Length == 0 || user is null) + { + return EntityPermissionSet.Empty(); } - /// - /// Gets the permissions for the provided group and path - /// - /// - /// Path to check permissions for - /// - /// Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set - /// - /// String indicating permissions for provided user and path - public EntityPermissionSet GetPermissionsForPath(IUserGroup[] groups, string path, bool fallbackToDefaultPermissions = false) - { - var nodeIds = path.GetIdsFromPathReversed(); + //collect all permissions structures for all nodes for all groups belonging to the user + EntityPermission[] groupPermissions = GetPermissionsForPath(user.Groups.ToArray(), nodeIds, true).ToArray(); - if (nodeIds.Length == 0) - return EntityPermissionSet.Empty(); + return CalculatePermissionsForPathForUser(groupPermissions, nodeIds); + } - //collect all permissions structures for all nodes for all groups - var groupPermissions = GetPermissionsForPath(groups.Select(x => x.ToReadOnlyGroup()).ToArray(), nodeIds, fallbackToDefaultPermissions: true).ToArray(); + /// + /// Gets the permissions for the provided group and path + /// + /// + /// Path to check permissions for + /// + /// Flag indicating if we want to include the default group permissions for each result if there are not explicit + /// permissions set + /// + /// String indicating permissions for provided user and path + public EntityPermissionSet GetPermissionsForPath(IUserGroup[] groups, string path, + bool fallbackToDefaultPermissions = false) + { + var nodeIds = path.GetIdsFromPathReversed(); - return CalculatePermissionsForPathForUser(groupPermissions, nodeIds); + if (nodeIds.Length == 0) + { + return EntityPermissionSet.Empty(); } - private EntityPermissionCollection GetPermissionsForPath(IReadOnlyUserGroup[] groups, int[] pathIds, bool fallbackToDefaultPermissions = false) + //collect all permissions structures for all nodes for all groups + EntityPermission[] groupPermissions = + GetPermissionsForPath(groups.Select(x => x.ToReadOnlyGroup()).ToArray(), nodeIds, true).ToArray(); + + return CalculatePermissionsForPathForUser(groupPermissions, nodeIds); + } + + private EntityPermissionCollection GetPermissionsForPath(IReadOnlyUserGroup[] groups, int[] pathIds, + bool fallbackToDefaultPermissions = false) + { + if (pathIds.Length == 0) { - if (pathIds.Length == 0) - return new EntityPermissionCollection(Enumerable.Empty()); + return new EntityPermissionCollection(Enumerable.Empty()); + } - //get permissions for all nodes in the path by group - var permissions = GetPermissions(groups, fallbackToDefaultPermissions, pathIds) + //get permissions for all nodes in the path by group + IEnumerable> permissions = + GetPermissions(groups, fallbackToDefaultPermissions, pathIds) .GroupBy(x => x.UserGroupId); - return new EntityPermissionCollection( - permissions.Select(x => GetPermissionsForPathForGroup(x, pathIds, fallbackToDefaultPermissions)).Where(x => x is not null)!); - } + return new EntityPermissionCollection( + permissions.Select(x => GetPermissionsForPathForGroup(x, pathIds, fallbackToDefaultPermissions)) + .Where(x => x is not null)!); + } - /// - /// This performs the calculations for inherited nodes based on this http://issues.umbraco.org/issue/U4-10075#comment=67-40085 - /// - /// - /// - /// - internal static EntityPermissionSet CalculatePermissionsForPathForUser( - EntityPermission[] groupPermissions, - int[] pathIds) + /// + /// This performs the calculations for inherited nodes based on this + /// http://issues.umbraco.org/issue/U4-10075#comment=67-40085 + /// + /// + /// + /// + internal static EntityPermissionSet CalculatePermissionsForPathForUser( + EntityPermission[] groupPermissions, + int[] pathIds) + { + // not sure this will ever happen, it shouldn't since this should return defaults, but maybe those are empty? + if (groupPermissions.Length == 0 || pathIds.Length == 0) { - // not sure this will ever happen, it shouldn't since this should return defaults, but maybe those are empty? - if (groupPermissions.Length == 0 || pathIds.Length == 0) - return EntityPermissionSet.Empty(); + return EntityPermissionSet.Empty(); + } + + //The actual entity id being looked at (deepest part of the path) + var entityId = pathIds[0]; - //The actual entity id being looked at (deepest part of the path) - var entityId = pathIds[0]; + var resultPermissions = new EntityPermissionCollection(); - var resultPermissions = new EntityPermissionCollection(); + //create a grouped by dictionary of another grouped by dictionary + var permissionsByGroup = groupPermissions + .GroupBy(x => x.UserGroupId) + .ToDictionary( + x => x.Key, + x => x.GroupBy(a => a.EntityId).ToDictionary(a => a.Key, a => a.ToArray())); - //create a grouped by dictionary of another grouped by dictionary - var permissionsByGroup = groupPermissions - .GroupBy(x => x.UserGroupId) - .ToDictionary( - x => x.Key, - x => x.GroupBy(a => a.EntityId).ToDictionary(a => a.Key, a => a.ToArray())); + //iterate through each group + foreach (KeyValuePair> byGroup in permissionsByGroup) + { + var added = false; - //iterate through each group - foreach (var byGroup in permissionsByGroup) + //iterate deepest to shallowest + foreach (var pathId in pathIds) { - var added = false; + EntityPermission[]? permissionsForNodeAndGroup; + if (byGroup.Value.TryGetValue(pathId, out permissionsForNodeAndGroup) == false) + { + continue; + } - //iterate deepest to shallowest - foreach (var pathId in pathIds) + //In theory there will only be one EntityPermission in this group + // but there's nothing stopping the logic of this method + // from having more so we deal with it here + foreach (EntityPermission entityPermission in permissionsForNodeAndGroup) { - EntityPermission[]? permissionsForNodeAndGroup; - if (byGroup.Value.TryGetValue(pathId, out permissionsForNodeAndGroup) == false) - continue; - - //In theory there will only be one EntityPermission in this group - // but there's nothing stopping the logic of this method - // from having more so we deal with it here - foreach (var entityPermission in permissionsForNodeAndGroup) + if (entityPermission.IsDefaultPermissions == false) { - if (entityPermission.IsDefaultPermissions == false) - { - //explicit permission found so we'll append it and move on, the collection is a hashset anyways - //so only supports adding one element per groupid/contentid - resultPermissions.Add(entityPermission); - added = true; - break; - } - } - - //if the permission has been added for this group and this branch then we can exit this loop - if (added) + //explicit permission found so we'll append it and move on, the collection is a hashset anyways + //so only supports adding one element per groupid/contentid + resultPermissions.Add(entityPermission); + added = true; break; + } } - if (added == false && byGroup.Value.Count > 0) + //if the permission has been added for this group and this branch then we can exit this loop + if (added) { - //if there was no explicit permissions assigned in this branch for this group, then we will - //add the group's default permissions - resultPermissions.Add(byGroup.Value[entityId][0]); + break; } - } - var permissionSet = new EntityPermissionSet(entityId, resultPermissions); - return permissionSet; + if (added == false && byGroup.Value.Count > 0) + { + //if there was no explicit permissions assigned in this branch for this group, then we will + //add the group's default permissions + resultPermissions.Add(byGroup.Value[entityId][0]); + } } - /// - /// Returns the resulting permission set for a group for the path based on all permissions provided for the branch - /// - /// - /// The collective set of permissions provided to calculate the resulting permissions set for the path - /// based on a single group - /// - /// Must be ordered deepest to shallowest (right to left) - /// - /// Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set - /// - /// - internal static EntityPermission? GetPermissionsForPathForGroup( - IEnumerable pathPermissions, - int[] pathIds, - bool fallbackToDefaultPermissions = false) - { - //get permissions for all nodes in the path - var permissionsByEntityId = pathPermissions.ToDictionary(x => x.EntityId, x => x); + var permissionSet = new EntityPermissionSet(entityId, resultPermissions); + return permissionSet; + } - //then the permissions assigned to the path will be the 'deepest' node found that has permissions - foreach (var id in pathIds) + /// + /// Returns the resulting permission set for a group for the path based on all permissions provided for the branch + /// + /// + /// The collective set of permissions provided to calculate the resulting permissions set for the path + /// based on a single group + /// + /// Must be ordered deepest to shallowest (right to left) + /// + /// Flag indicating if we want to include the default group permissions for each result if there are not explicit + /// permissions set + /// + /// + internal static EntityPermission? GetPermissionsForPathForGroup( + IEnumerable pathPermissions, + int[] pathIds, + bool fallbackToDefaultPermissions = false) + { + //get permissions for all nodes in the path + var permissionsByEntityId = pathPermissions.ToDictionary(x => x.EntityId, x => x); + + //then the permissions assigned to the path will be the 'deepest' node found that has permissions + foreach (var id in pathIds) + { + EntityPermission? permission; + if (permissionsByEntityId.TryGetValue(id, out permission)) { - EntityPermission? permission; - if (permissionsByEntityId.TryGetValue(id, out permission)) + //don't return the default permissions if that is the one assigned here (we'll do that below if nothing was found) + if (permission.IsDefaultPermissions == false) { - //don't return the default permissions if that is the one assigned here (we'll do that below if nothing was found) - if (permission.IsDefaultPermissions == false) - return permission; + return permission; } } - - //if we've made it here it means that no implicit/inherited permissions were found so we return the defaults if that is specified - if (fallbackToDefaultPermissions == false) - return null; - - return permissionsByEntityId[pathIds[0]]; } - /// - /// Checks in a set of permissions associated with a user for those related to a given nodeId - /// - /// The set of permissions - /// The node Id - /// The permissions to return - /// True if permissions for the given path are found - public static bool TryGetAssignedPermissionsForNode(IList permissions, - int nodeId, - out string assignedPermissions) + //if we've made it here it means that no implicit/inherited permissions were found so we return the defaults if that is specified + if (fallbackToDefaultPermissions == false) { - if (permissions.Any(x => x.EntityId == nodeId)) - { - var found = permissions.First(x => x.EntityId == nodeId); - var assignedPermissionsArray = found.AssignedPermissions.ToList(); + return null; + } - // Working with permissions assigned directly to a user AND to their groups, so maybe several per node - // and we need to get the most permissive set - foreach (var permission in permissions.Where(x => x.EntityId == nodeId).Skip(1)) - { - AddAdditionalPermissions(assignedPermissionsArray, permission.AssignedPermissions); - } + return permissionsByEntityId[pathIds[0]]; + } + + /// + /// Checks in a set of permissions associated with a user for those related to a given nodeId + /// + /// The set of permissions + /// The node Id + /// The permissions to return + /// True if permissions for the given path are found + public static bool TryGetAssignedPermissionsForNode(IList permissions, + int nodeId, + out string assignedPermissions) + { + if (permissions.Any(x => x.EntityId == nodeId)) + { + EntityPermission found = permissions.First(x => x.EntityId == nodeId); + var assignedPermissionsArray = found.AssignedPermissions.ToList(); - assignedPermissions = string.Join("", assignedPermissionsArray); - return true; + // Working with permissions assigned directly to a user AND to their groups, so maybe several per node + // and we need to get the most permissive set + foreach (EntityPermission permission in permissions.Where(x => x.EntityId == nodeId).Skip(1)) + { + AddAdditionalPermissions(assignedPermissionsArray, permission.AssignedPermissions); } - assignedPermissions = string.Empty; - return false; + assignedPermissions = string.Join("", assignedPermissionsArray); + return true; } - private static void AddAdditionalPermissions(List assignedPermissions, string[] additionalPermissions) - { - var permissionsToAdd = additionalPermissions - .Where(x => assignedPermissions.Contains(x) == false); - assignedPermissions.AddRange(permissionsToAdd); - } + assignedPermissions = string.Empty; + return false; + } - #endregion + private static void AddAdditionalPermissions(List assignedPermissions, string[] additionalPermissions) + { + IEnumerable permissionsToAdd = additionalPermissions + .Where(x => assignedPermissions.Contains(x) == false); + assignedPermissions.AddRange(permissionsToAdd); } + + #endregion } diff --git a/src/Umbraco.Core/Services/UserServiceExtensions.cs b/src/Umbraco.Core/Services/UserServiceExtensions.cs index 86e823f8bc69..544b22077743 100644 --- a/src/Umbraco.Core/Services/UserServiceExtensions.cs +++ b/src/Umbraco.Core/Services/UserServiceExtensions.cs @@ -1,94 +1,92 @@ -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Services; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class UserServiceExtensions { - public static class UserServiceExtensions + public static EntityPermission? GetPermissions(this IUserService userService, IUser? user, string path) { - public static EntityPermission? GetPermissions(this IUserService userService, IUser? user, string path) + var ids = path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) + .Select(x => + int.TryParse(x, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value) + ? Attempt.Succeed(value) + : Attempt.Fail()) + .Where(x => x.Success) + .Select(x => x.Result) + .ToArray(); + if (ids.Length == 0) { - var ids = path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) - .Select(x => int.TryParse(x, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value) ? Attempt.Succeed(value) : Attempt.Fail()) - .Where(x => x.Success) - .Select(x=>x.Result) - .ToArray(); - if (ids.Length == 0) throw new InvalidOperationException("The path: " + path + " could not be parsed into an array of integers or the path was empty"); - - return userService.GetPermissions(user, ids[ids.Length - 1]).FirstOrDefault(); + throw new InvalidOperationException("The path: " + path + + " could not be parsed into an array of integers or the path was empty"); } - /// - /// Get explicitly assigned permissions for a group and optional node Ids - /// - /// - /// - /// - /// Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set - /// - /// Specifying nothing will return all permissions for all nodes - /// An enumerable list of - public static EntityPermissionCollection GetPermissions(this IUserService service, IUserGroup? group, bool fallbackToDefaultPermissions, params int[] nodeIds) - { - return service.GetPermissions(new[] {group}, fallbackToDefaultPermissions, nodeIds); - } + return userService.GetPermissions(user, ids[ids.Length - 1]).FirstOrDefault(); + } - /// - /// Gets the permissions for the provided group and path - /// - /// - /// - /// Path to check permissions for - /// - /// Flag indicating if we want to include the default group permissions for each result if there are not explicit permissions set - /// - public static EntityPermissionSet GetPermissionsForPath(this IUserService service, IUserGroup group, string path, bool fallbackToDefaultPermissions = false) - { - return service.GetPermissionsForPath(new[] { group }, path, fallbackToDefaultPermissions); - } + /// + /// Get explicitly assigned permissions for a group and optional node Ids + /// + /// + /// + /// + /// Flag indicating if we want to include the default group permissions for each result if there are not explicit + /// permissions set + /// + /// Specifying nothing will return all permissions for all nodes + /// An enumerable list of + public static EntityPermissionCollection GetPermissions(this IUserService service, IUserGroup? group, + bool fallbackToDefaultPermissions, params int[] nodeIds) => + service.GetPermissions(new[] {group}, fallbackToDefaultPermissions, nodeIds); - /// - /// Remove all permissions for this user group for all nodes specified - /// - /// - /// - /// - public static void RemoveUserGroupPermissions(this IUserService userService, int groupId, params int[] entityIds) - { - userService.ReplaceUserGroupPermissions(groupId, null, entityIds); - } - - /// - /// Remove all permissions for this user group for all nodes - /// - /// - /// - public static void RemoveUserGroupPermissions(this IUserService userService, int groupId) - { - userService.ReplaceUserGroupPermissions(groupId, null); - } + /// + /// Gets the permissions for the provided group and path + /// + /// + /// + /// Path to check permissions for + /// + /// Flag indicating if we want to include the default group permissions for each result if there are not explicit + /// permissions set + /// + public static EntityPermissionSet GetPermissionsForPath(this IUserService service, IUserGroup group, string path, + bool fallbackToDefaultPermissions = false) => + service.GetPermissionsForPath(new[] {group}, path, fallbackToDefaultPermissions); + /// + /// Remove all permissions for this user group for all nodes specified + /// + /// + /// + /// + public static void RemoveUserGroupPermissions(this IUserService userService, int groupId, params int[] entityIds) => + userService.ReplaceUserGroupPermissions(groupId, null, entityIds); - public static IEnumerable GetProfilesById(this IUserService userService, params int[] ids) - { - var fullUsers = userService.GetUsersById(ids); + /// + /// Remove all permissions for this user group for all nodes + /// + /// + /// + public static void RemoveUserGroupPermissions(this IUserService userService, int groupId) => + userService.ReplaceUserGroupPermissions(groupId, null); - return fullUsers.Select(user => - { - var asProfile = user as IProfile; - return asProfile ?? new UserProfile(user.Id, user.Name); - }); - } + public static IEnumerable GetProfilesById(this IUserService userService, params int[] ids) + { + IEnumerable fullUsers = userService.GetUsersById(ids); - public static IUser? GetByKey(this IUserService userService, Guid key) + return fullUsers.Select(user => { - int id = BitConverter.ToInt32(key.ToByteArray(), 0); - return userService.GetUserById(id); - } + var asProfile = user as IProfile; + return asProfile ?? new UserProfile(user.Id, user.Name); + }); + } + + public static IUser? GetByKey(this IUserService userService, Guid key) + { + var id = BitConverter.ToInt32(key.ToByteArray(), 0); + return userService.GetUserById(id); } } diff --git a/src/Umbraco.Core/Settable.cs b/src/Umbraco.Core/Settable.cs index 07f53c208072..f3a92d140451 100644 --- a/src/Umbraco.Core/Settable.cs +++ b/src/Umbraco.Core/Settable.cs @@ -1,95 +1,94 @@ -using System; +namespace Umbraco.Cms.Core; -namespace Umbraco.Cms.Core +/// +/// Represents a value that can be assigned a value. +/// +/// The type of the value +public class Settable { + private T? _value; + /// - /// Represents a value that can be assigned a value. + /// Gets a value indicating whether a value has been assigned to this instance. /// - /// The type of the value - public class Settable - { - private T? _value; + public bool HasValue { get; private set; } - /// - /// Assigns a value to this instance. - /// - /// The value. - public void Set(T? value) + /// + /// Gets the value assigned to this instance. + /// + /// An exception is thrown if the HasValue property is false. + /// No value has been assigned to this instance. + public T? Value + { + get { - if (value is not null) + if (HasValue == false) { - HasValue = true; + throw new InvalidOperationException("The HasValue property is false."); } - _value = value; - } - /// - /// Assigns a value to this instance by copying the value - /// of another instance, if the other instance has a value. - /// - /// The other instance. - public void Set(Settable other) - { - // set only if has value else don't change anything - if (other.HasValue) Set(other.Value); + return _value; } + } - /// - /// Clears the value. - /// - public void Clear() + /// + /// Assigns a value to this instance. + /// + /// The value. + public void Set(T? value) + { + if (value is not null) { - HasValue = false; - _value = default (T); + HasValue = true; } - /// - /// Gets a value indicating whether a value has been assigned to this instance. - /// - public bool HasValue { get; private set; } + _value = value; + } - /// - /// Gets the value assigned to this instance. - /// - /// An exception is thrown if the HasValue property is false. - /// No value has been assigned to this instance. - public T? Value + /// + /// Assigns a value to this instance by copying the value + /// of another instance, if the other instance has a value. + /// + /// The other instance. + public void Set(Settable other) + { + // set only if has value else don't change anything + if (other.HasValue) { - get - { - if (HasValue == false) - throw new InvalidOperationException("The HasValue property is false."); - return _value; - } + Set(other.Value); } + } - /// - /// Gets the value assigned to this instance, if a value has been assigned, - /// otherwise the default value of . - /// - /// The value assigned to this instance, if a value has been assigned, - /// else the default value of . - public T? ValueOrDefault() - { - return HasValue ? _value : default(T); - } + /// + /// Clears the value. + /// + public void Clear() + { + HasValue = false; + _value = default; + } - /// - /// Gets the value assigned to this instance, if a value has been assigned, - /// otherwise a specified default value. - /// - /// The default value. - /// The value assigned to this instance, if a value has been assigned, - /// else . - public T? ValueOrDefault(T defaultValue) - { - return HasValue ? _value : defaultValue; - } + /// + /// Gets the value assigned to this instance, if a value has been assigned, + /// otherwise the default value of . + /// + /// + /// The value assigned to this instance, if a value has been assigned, + /// else the default value of . + /// + public T? ValueOrDefault() => HasValue ? _value : default; - /// - public override string? ToString() - { - return HasValue ? _value?.ToString() : "void"; - } - } + /// + /// Gets the value assigned to this instance, if a value has been assigned, + /// otherwise a specified default value. + /// + /// The default value. + /// + /// The value assigned to this instance, if a value has been assigned, + /// else . + /// + public T? ValueOrDefault(T defaultValue) => HasValue ? _value : defaultValue; + + /// + public override string? ToString() => HasValue ? _value?.ToString() : "void"; } diff --git a/src/Umbraco.Core/SimpleMainDom.cs b/src/Umbraco.Core/SimpleMainDom.cs index 3f4bd1ce7cae..4d76e3a9eb30 100644 --- a/src/Umbraco.Core/SimpleMainDom.cs +++ b/src/Umbraco.Core/SimpleMainDom.cs @@ -1,80 +1,92 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Runtime; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Provides a simple implementation of . +/// +public class SimpleMainDom : IMainDom, IDisposable { - /// - /// Provides a simple implementation of . - /// - public class SimpleMainDom : IMainDom, IDisposable + private readonly List> _callbacks = new(); + private readonly object _locko = new(); + private bool _disposedValue; + private bool _isStopping; + + public void Dispose() { - private readonly object _locko = new object(); - private readonly List> _callbacks = new List>(); - private bool _isStopping; - private bool _disposedValue; + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(true); + GC.SuppressFinalize(this); + } - /// - public bool IsMainDom { get; private set; } = true; + /// + public bool IsMainDom { get; private set; } = true; - // always acquire - public bool Acquire(IApplicationShutdownRegistry hostingEnvironment) => true; + // always acquire + public bool Acquire(IApplicationShutdownRegistry hostingEnvironment) => true; - /// - public bool Register(Action? install, Action? release, int weight = 100) + /// + public bool Register(Action? install, Action? release, int weight = 100) + { + lock (_locko) { - lock (_locko) + if (_isStopping) { - if (_isStopping) return false; - install?.Invoke(); - if (release != null) - _callbacks.Add(new KeyValuePair(weight, release)); - return true; + return false; } - } - public void Stop() - { - lock (_locko) + install?.Invoke(); + if (release != null) { - if (_isStopping) return; - if (IsMainDom == false) return; // probably not needed - _isStopping = true; + _callbacks.Add(new KeyValuePair(weight, release)); } - try + return true; + } + } + + public void Stop() + { + lock (_locko) + { + if (_isStopping) { - foreach (var callback in _callbacks.OrderBy(x => x.Key).Select(x => x.Value)) - { - callback(); // no timeout on callbacks - } + return; } - finally + + if (IsMainDom == false) { - // in any case... - IsMainDom = false; + return; // probably not needed } + + _isStopping = true; } - protected virtual void Dispose(bool disposing) + try { - if (!_disposedValue) + foreach (Action callback in _callbacks.OrderBy(x => x.Key).Select(x => x.Value)) { - if (disposing) - { - Stop(); - } - _disposedValue = true; + callback(); // no timeout on callbacks } } + finally + { + // in any case... + IsMainDom = false; + } + } - public void Dispose() + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); + if (disposing) + { + Stop(); + } + + _disposedValue = true; } } } diff --git a/src/Umbraco.Core/StaticApplicationLogging.cs b/src/Umbraco.Core/StaticApplicationLogging.cs index f0d01d40735e..cf30cf3bd468 100644 --- a/src/Umbraco.Core/StaticApplicationLogging.cs +++ b/src/Umbraco.Core/StaticApplicationLogging.cs @@ -1,19 +1,18 @@ -using System; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public static class StaticApplicationLogging { - public static class StaticApplicationLogging - { - private static ILoggerFactory? s_loggerFactory; + private static ILoggerFactory? s_loggerFactory; - public static void Initialize(ILoggerFactory loggerFactory) => s_loggerFactory = loggerFactory; + public static ILogger Logger => CreateLogger(); - public static ILogger Logger => CreateLogger(); + public static void Initialize(ILoggerFactory loggerFactory) => s_loggerFactory = loggerFactory; - public static ILogger CreateLogger() => s_loggerFactory?.CreateLogger() ?? NullLoggerFactory.Instance.CreateLogger(); + public static ILogger CreateLogger() => + s_loggerFactory?.CreateLogger() ?? NullLoggerFactory.Instance.CreateLogger(); - public static ILogger CreateLogger(Type type) => s_loggerFactory?.CreateLogger(type) ?? NullLogger.Instance; - } + public static ILogger CreateLogger(Type type) => s_loggerFactory?.CreateLogger(type) ?? NullLogger.Instance; } diff --git a/src/Umbraco.Core/StringUdi.cs b/src/Umbraco.Core/StringUdi.cs index 3435c81780f9..e27acc61d73f 100644 --- a/src/Umbraco.Core/StringUdi.cs +++ b/src/Umbraco.Core/StringUdi.cs @@ -1,64 +1,50 @@ -using System; -using System.ComponentModel; -using System.Linq; +using System.ComponentModel; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Represents a string-based entity identifier. +/// +[TypeConverter(typeof(UdiTypeConverter))] +public class StringUdi : Udi { /// - /// Represents a string-based entity identifier. + /// Initializes a new instance of the StringUdi class with an entity type and a string id. /// - [TypeConverter(typeof(UdiTypeConverter))] - public class StringUdi : Udi - { - /// - /// The string part of the identifier. - /// - public string Id { get; private set; } - - /// - /// Initializes a new instance of the StringUdi class with an entity type and a string id. - /// - /// The entity type part of the udi. - /// The string id part of the udi. - public StringUdi(string entityType, string id) - : base(entityType, "umb://" + entityType + "/" + EscapeUriString(id)) - { - Id = id; - } + /// The entity type part of the udi. + /// The string id part of the udi. + public StringUdi(string entityType, string id) + : base(entityType, "umb://" + entityType + "/" + EscapeUriString(id)) => + Id = id; - /// - /// Initializes a new instance of the StringUdi class with a uri value. - /// - /// The uri value of the udi. - public StringUdi(Uri uriValue) - : base(uriValue) - { - Id = Uri.UnescapeDataString(uriValue.AbsolutePath.TrimStart(Constants.CharArrays.ForwardSlash)); - } - - private static string EscapeUriString(string s) - { - // Uri.EscapeUriString preserves / but also [ and ] which is bad - // Uri.EscapeDataString does not preserve / which is bad + /// + /// Initializes a new instance of the StringUdi class with a uri value. + /// + /// The uri value of the udi. + public StringUdi(Uri uriValue) + : base(uriValue) => + Id = Uri.UnescapeDataString(uriValue.AbsolutePath.TrimStart(Constants.CharArrays.ForwardSlash)); - // reserved = : / ? # [ ] @ ! $ & ' ( ) * + , ; = - // unreserved = alpha digit - . _ ~ + /// + /// The string part of the identifier. + /// + public string Id { get; } - // we want to preserve the / and the unreserved - // so... - return string.Join("/", s.Split(Constants.CharArrays.ForwardSlash).Select(Uri.EscapeDataString)); - } + /// + public override bool IsRoot => Id == string.Empty; - /// - public override bool IsRoot - { - get { return Id == string.Empty; } - } + private static string EscapeUriString(string s) => + // Uri.EscapeUriString preserves / but also [ and ] which is bad + // Uri.EscapeDataString does not preserve / which is bad + // reserved = : / ? # [ ] @ ! $ & ' ( ) * + , ; = + // unreserved = alpha digit - . _ ~ + // we want to preserve the / and the unreserved + // so... + string.Join("/", s.Split(Constants.CharArrays.ForwardSlash).Select(Uri.EscapeDataString)); - public StringUdi EnsureClosed() - { - EnsureNotRoot(); - return this; - } + public StringUdi EnsureClosed() + { + EnsureNotRoot(); + return this; } } diff --git a/src/Umbraco.Core/Strings/CleanStringType.cs b/src/Umbraco.Core/Strings/CleanStringType.cs index 771e834d35db..83dd2e383de5 100644 --- a/src/Umbraco.Core/Strings/CleanStringType.cs +++ b/src/Umbraco.Core/Strings/CleanStringType.cs @@ -1,124 +1,123 @@ -using System; - -namespace Umbraco.Cms.Core.Strings +namespace Umbraco.Cms.Core.Strings; + +/// +/// Specifies the type of a clean string. +/// +/// +/// Specifies its casing, and its encoding. +/// +[Flags] +public enum CleanStringType { + // note: you have 32 bits at your disposal + // 0xffffffff + + // no value + + /// + /// No value. + /// + None = 0x00, + + + // casing values + + /// + /// Flag mask for casing. + /// + CaseMask = PascalCase | CamelCase | Unchanged | LowerCase | UpperCase | UmbracoCase, + + /// + /// Pascal casing eg "PascalCase". + /// + PascalCase = 0x01, + + /// + /// Camel casing eg "camelCase". + /// + CamelCase = 0x02, + + /// + /// Unchanged casing eg "UncHanGed". + /// + Unchanged = 0x04, + + /// + /// Lower casing eg "lowercase". + /// + LowerCase = 0x08, + /// - /// Specifies the type of a clean string. + /// Upper casing eg "UPPERCASE". + /// + UpperCase = 0x10, + + /// + /// Umbraco "safe alias" case. /// /// - /// Specifies its casing, and its encoding. + /// Uppercases the first char of each term except for the first + /// char of the string, everything else including the first char of the + /// string is unchanged. /// - [Flags] - public enum CleanStringType - { - // note: you have 32 bits at your disposal - // 0xffffffff - - // no value - - /// - /// No value. - /// - None = 0x00, - - - // casing values - - /// - /// Flag mask for casing. - /// - CaseMask = PascalCase | CamelCase | Unchanged | LowerCase | UpperCase | UmbracoCase, - - /// - /// Pascal casing eg "PascalCase". - /// - PascalCase = 0x01, - - /// - /// Camel casing eg "camelCase". - /// - CamelCase = 0x02, - - /// - /// Unchanged casing eg "UncHanGed". - /// - Unchanged = 0x04, - - /// - /// Lower casing eg "lowercase". - /// - LowerCase = 0x08, - - /// - /// Upper casing eg "UPPERCASE". - /// - UpperCase = 0x10, - - /// - /// Umbraco "safe alias" case. - /// - /// Uppercases the first char of each term except for the first - /// char of the string, everything else including the first char of the - /// string is unchanged. - UmbracoCase = 0x20, - - - // encoding values - - /// - /// Flag mask for encoding. - /// - CodeMask = Utf8 | Ascii | TryAscii, - - // Unicode encoding is obsolete, use Utf8 - //Unicode = 0x0100, - - /// - /// Utf8 encoding. - /// - Utf8 = 0x0200, - - /// - /// Ascii encoding. - /// - Ascii = 0x0400, - - /// - /// Ascii encoding, if possible. - /// - TryAscii = 0x0800, - - // role values - - /// - /// Flag mask for role. - /// - RoleMask = UrlSegment | Alias | UnderscoreAlias | FileName | ConvertCase, - - /// - /// Url role. - /// - UrlSegment = 0x010000, - - /// - /// Alias role. - /// - Alias = 0x020000, - - /// - /// FileName role. - /// - FileName = 0x040000, - - /// - /// ConvertCase role. - /// - ConvertCase = 0x080000, - - /// - /// UnderscoreAlias role. - /// - /// This is Alias + leading underscore. - UnderscoreAlias = 0x100000 - } + UmbracoCase = 0x20, + + + // encoding values + + /// + /// Flag mask for encoding. + /// + CodeMask = Utf8 | Ascii | TryAscii, + + // Unicode encoding is obsolete, use Utf8 + //Unicode = 0x0100, + + /// + /// Utf8 encoding. + /// + Utf8 = 0x0200, + + /// + /// Ascii encoding. + /// + Ascii = 0x0400, + + /// + /// Ascii encoding, if possible. + /// + TryAscii = 0x0800, + + // role values + + /// + /// Flag mask for role. + /// + RoleMask = UrlSegment | Alias | UnderscoreAlias | FileName | ConvertCase, + + /// + /// Url role. + /// + UrlSegment = 0x010000, + + /// + /// Alias role. + /// + Alias = 0x020000, + + /// + /// FileName role. + /// + FileName = 0x040000, + + /// + /// ConvertCase role. + /// + ConvertCase = 0x080000, + + /// + /// UnderscoreAlias role. + /// + /// This is Alias + leading underscore. + UnderscoreAlias = 0x100000 } diff --git a/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs b/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs index a95a3edfc29c..1ee31db1f3d4 100644 --- a/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs +++ b/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs @@ -1,63 +1,67 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Strings.Css +namespace Umbraco.Cms.Core.Strings.Css; + +public class StylesheetHelper { - public class StylesheetHelper + private const string RuleRegexFormat = + @"/\*\*\s*umb_name:\s*(?{0}?)\s*\*/\s*(?[^,{{]*?)\s*{{\s*(?.*?)\s*}}"; + + public static IEnumerable ParseRules(string? input) { - private const string RuleRegexFormat = @"/\*\*\s*umb_name:\s*(?{0}?)\s*\*/\s*(?[^,{{]*?)\s*{{\s*(?.*?)\s*}}"; + var rules = new List(); + var ruleRegex = new Regex(string.Format(RuleRegexFormat, @"[^\*\r\n]*"), + RegexOptions.IgnoreCase | RegexOptions.Singleline); - public static IEnumerable ParseRules(string? input) + if (input is not null) { - var rules = new List(); - var ruleRegex = new Regex(string.Format(RuleRegexFormat, @"[^\*\r\n]*"), RegexOptions.IgnoreCase | RegexOptions.Singleline); + var contents = input; + MatchCollection ruleMatches = ruleRegex.Matches(contents); - if (input is not null) + foreach (Match match in ruleMatches) { - var contents = input; - var ruleMatches = ruleRegex.Matches(contents); + var name = match.Groups["Name"].Value; - foreach (Match match in ruleMatches) + //If this name already exists, only use the first one + if (rules.Any(x => x.Name == name)) { - var name = match.Groups["Name"].Value; - - //If this name already exists, only use the first one - if (rules.Any(x => x.Name == name)) continue; - - rules.Add(new StylesheetRule - { - Name = match.Groups["Name"].Value, - Selector = match.Groups["Selector"].Value, - // Only match first selector when chained together - Styles = string.Join(Environment.NewLine, match.Groups["Styles"].Value.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None).Select(x => x.Trim()).ToArray()) - }); + continue; } - } - - return rules; + rules.Add(new StylesheetRule + { + Name = match.Groups["Name"].Value, + Selector = match.Groups["Selector"].Value, + // Only match first selector when chained together + Styles = string.Join(Environment.NewLine, + match.Groups["Styles"].Value.Split(new[] {"\r\n", "\n"}, StringSplitOptions.None) + .Select(x => x.Trim()).ToArray()) + }); + } } - public static string? ReplaceRule(string? input, string oldRuleName, StylesheetRule? rule) - { - var contents = input; - if (contents is not null) - { - var ruleRegex = new Regex(string.Format(RuleRegexFormat, oldRuleName.EscapeRegexSpecialCharacters()), RegexOptions.IgnoreCase | RegexOptions.Singleline); - contents = ruleRegex.Replace(contents, rule != null ? rule.ToString() : ""); - } - return contents; - } + return rules; + } - public static string AppendRule(string? input, StylesheetRule rule) + public static string? ReplaceRule(string? input, string oldRuleName, StylesheetRule? rule) + { + var contents = input; + if (contents is not null) { - var contents = input; - contents += Environment.NewLine + Environment.NewLine + rule; - return contents; + var ruleRegex = new Regex(string.Format(RuleRegexFormat, oldRuleName.EscapeRegexSpecialCharacters()), + RegexOptions.IgnoreCase | RegexOptions.Singleline); + contents = ruleRegex.Replace(contents, rule != null ? rule.ToString() : ""); } + + return contents; + } + + public static string AppendRule(string? input, StylesheetRule rule) + { + var contents = input; + contents += Environment.NewLine + Environment.NewLine + rule; + return contents; } } diff --git a/src/Umbraco.Core/Strings/Css/StylesheetRule.cs b/src/Umbraco.Core/Strings/Css/StylesheetRule.cs index 06a888c81221..d22ac19f2ae6 100644 --- a/src/Umbraco.Core/Strings/Css/StylesheetRule.cs +++ b/src/Umbraco.Core/Strings/Css/StylesheetRule.cs @@ -1,41 +1,42 @@ -using System; -using System.Text; +using System.Text; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Strings.Css +namespace Umbraco.Cms.Core.Strings.Css; + +public class StylesheetRule { - public class StylesheetRule - { - public string Name { get; set; } = null!; + public string Name { get; set; } = null!; - public string Selector { get; set; } = null!; + public string Selector { get; set; } = null!; - public string Styles { get; set; } = null!; + public string Styles { get; set; } = null!; - public override string ToString() + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append("/**"); + sb.AppendFormat("umb_name:{0}", Name); + sb.Append("*/"); + sb.Append(Environment.NewLine); + sb.Append(Selector); + sb.Append(" {"); + sb.Append(Environment.NewLine); + // append nicely formatted style rules + // - using tabs because the back office code editor uses tabs + if (Styles.IsNullOrWhiteSpace() == false) { - var sb = new StringBuilder(); - sb.Append("/**"); - sb.AppendFormat("umb_name:{0}", Name); - sb.Append("*/"); - sb.Append(Environment.NewLine); - sb.Append(Selector); - sb.Append(" {"); - sb.Append(Environment.NewLine); - // append nicely formatted style rules - // - using tabs because the back office code editor uses tabs - if (Styles.IsNullOrWhiteSpace() == false) + // since we already have a string builder in play here, we'll append to it the "hard" way + // instead of using string interpolation (for increased performance) + foreach (var style in + Styles?.Split(Constants.CharArrays.Semicolon, StringSplitOptions.RemoveEmptyEntries) ?? + Array.Empty()) { - // since we already have a string builder in play here, we'll append to it the "hard" way - // instead of using string interpolation (for increased performance) - foreach (var style in Styles?.Split(Constants.CharArrays.Semicolon, StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty()) - { - sb.Append("\t").Append(style.StripNewLines().Trim()).Append(";").Append(Environment.NewLine); - } + sb.Append("\t").Append(style.StripNewLines().Trim()).Append(";").Append(Environment.NewLine); } - sb.Append("}"); - - return sb.ToString(); } + + sb.Append("}"); + + return sb.ToString(); } } diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs index d4d781c9bcda..da729f0c0a70 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs @@ -1,629 +1,673 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; -using System.IO; -using System.Linq; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Strings +namespace Umbraco.Cms.Core.Strings; + +/// +/// New default implementation of string functions for short strings such as aliases or URL segments. +/// +/// +/// Not optimized to work on large bodies of text. +/// Meant to replace LegacyShortStringHelper where/when backward compatibility is not an issue. +/// NOTE: pre-filters run _before_ the string is re-encoded. +/// +public class DefaultShortStringHelper : IShortStringHelper { + #region SplitPascalCasing + /// - /// New default implementation of string functions for short strings such as aliases or URL segments. + /// Splits a Pascal-cased string into a phrase separated by a separator. /// - /// - /// Not optimized to work on large bodies of text. - /// Meant to replace LegacyShortStringHelper where/when backward compatibility is not an issue. - /// NOTE: pre-filters run _before_ the string is re-encoded. - /// - public class DefaultShortStringHelper : IShortStringHelper + /// The text to split. + /// The separator, which defaults to a whitespace. + /// The split text. + /// Supports Utf8 and Ascii strings, not Unicode strings. + // NOTE does not support surrogates pairs at the moment + public virtual string SplitPascalCasing(string text, char separator) { - #region Ctor, consts and vars + // be safe + if (text == null) + { + throw new ArgumentNullException(nameof(text)); + } + + var input = text.ToCharArray(); + var output = new char[input.Length * 2]; + var opos = 0; + var a = input.Length > 0 ? input[0] : char.MinValue; + var upos = char.IsUpper(a) ? 1 : 0; - public DefaultShortStringHelper(IOptions settings) + for (var i = 1; i < input.Length; i++) { - _config = new DefaultShortStringHelperConfig().WithDefault(settings.Value); + var c = input[i]; + if (char.IsUpper(c)) + { + output[opos++] = a; + if (upos == 0) + { + if (opos > 0) + { + output[opos++] = separator; + } + + upos = i + 1; + } + } + else + { + if (upos > 0) + { + if (upos < i && opos > 0) + { + output[opos++] = separator; + } + + upos = 0; + } + + output[opos++] = a; + } + + a = c; } - // clones the config so it cannot be changed at runtime - public DefaultShortStringHelper(DefaultShortStringHelperConfig config) + if (a != char.MinValue) { - _config = config.Clone(); + output[opos++] = a; } - // see notes for CleanAsciiString - //// beware! the order is quite important here! - //const string ValidStringCharactersSource = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - //readonly static char[] ValidStringCharacters; + return new string(output, 0, opos); + } + + #endregion + + #region Ctor, consts and vars + + public DefaultShortStringHelper(IOptions settings) => + _config = new DefaultShortStringHelperConfig().WithDefault(settings.Value); - private readonly DefaultShortStringHelperConfig _config; + // clones the config so it cannot be changed at runtime + public DefaultShortStringHelper(DefaultShortStringHelperConfig config) => _config = config.Clone(); - // see notes for CleanAsciiString - //static DefaultShortStringHelper() - //{ - // ValidStringCharacters = ValidStringCharactersSource.ToCharArray(); - //} + // see notes for CleanAsciiString + //// beware! the order is quite important here! + //const string ValidStringCharactersSource = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + //readonly static char[] ValidStringCharacters; - #endregion + private readonly DefaultShortStringHelperConfig _config; - #region Filters + // see notes for CleanAsciiString + //static DefaultShortStringHelper() + //{ + // ValidStringCharacters = ValidStringCharactersSource.ToCharArray(); + //} - // ok to be static here because it's not configurable in any way - private static readonly char[] InvalidFileNameChars = - Path.GetInvalidFileNameChars() + #endregion + + #region Filters + + // ok to be static here because it's not configurable in any way + private static readonly char[] InvalidFileNameChars = + Path.GetInvalidFileNameChars() .Union("!*'();:@&=+$,/?%#[]-~{}\"<>\\^`| ".ToCharArray()) .Distinct() .ToArray(); - public static bool IsValidFileNameChar(char c) - { - return InvalidFileNameChars.Contains(c) == false; - } + public static bool IsValidFileNameChar(char c) => InvalidFileNameChars.Contains(c) == false; - #endregion + #endregion - #region IShortStringHelper CleanFor... + #region IShortStringHelper CleanFor... - /// - /// Cleans a string to produce a string that can safely be used in an alias. - /// - /// The text to filter. - /// The safe alias. - /// - /// The string will be cleaned in the context of the default culture. - /// Safe aliases are Ascii only. - /// - public virtual string CleanStringForSafeAlias(string text) - { - return CleanStringForSafeAlias(text, _config.DefaultCulture); - } + /// + /// Cleans a string to produce a string that can safely be used in an alias. + /// + /// The text to filter. + /// The safe alias. + /// + /// The string will be cleaned in the context of the default culture. + /// Safe aliases are Ascii only. + /// + public virtual string CleanStringForSafeAlias(string text) => CleanStringForSafeAlias(text, _config.DefaultCulture); - /// - /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an alias. - /// - /// The text to filter. - /// The culture. - /// The safe alias. - /// - /// Safe aliases are Ascii only. - /// - public virtual string CleanStringForSafeAlias(string text, string culture) - { - return CleanString(text, CleanStringType.Alias, culture); - } + /// + /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an alias. + /// + /// The text to filter. + /// The culture. + /// The safe alias. + /// + /// Safe aliases are Ascii only. + /// + public virtual string CleanStringForSafeAlias(string text, string culture) => + CleanString(text, CleanStringType.Alias, culture); - /// - /// Cleans a string to produce a string that can safely be used in an URL segment. - /// - /// The text to filter. - /// The safe URL segment. - /// - /// The string will be cleaned in the context of the default culture. - /// Url segments are Ascii only (no accents...). - /// - public virtual string CleanStringForUrlSegment(string text) + /// + /// Cleans a string to produce a string that can safely be used in an URL segment. + /// + /// The text to filter. + /// The safe URL segment. + /// + /// The string will be cleaned in the context of the default culture. + /// Url segments are Ascii only (no accents...). + /// + public virtual string CleanStringForUrlSegment(string text) => + CleanStringForUrlSegment(text, _config.DefaultCulture); + + /// + /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an URL + /// segment. + /// + /// The text to filter. + /// The culture. + /// The safe URL segment. + /// + /// Url segments are Ascii only (no accents...). + /// + public virtual string CleanStringForUrlSegment(string text, string? culture) => + CleanString(text, CleanStringType.UrlSegment, culture); + + /// + /// Cleans a string, in the context of the default culture, to produce a string that can safely be used as a filename, + /// both internally (on disk) and externally (as a URL). + /// + /// The text to filter. + /// The safe filename. + /// + /// Legacy says this was used to "overcome an issue when Umbraco is used in IE in an intranet environment" but + /// that issue is not documented. + /// + public virtual string CleanStringForSafeFileName(string text) => + CleanStringForSafeFileName(text, _config.DefaultCulture); + + /// + /// Cleans a string to produce a string that can safely be used as a filename, + /// both internally (on disk) and externally (as a URL). + /// + /// The text to filter. + /// The culture. + /// The safe filename. + public virtual string CleanStringForSafeFileName(string text, string culture) + { + if (string.IsNullOrWhiteSpace(text)) { - return CleanStringForUrlSegment(text, _config.DefaultCulture); + return string.Empty; } - /// - /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an URL segment. - /// - /// The text to filter. - /// The culture. - /// The safe URL segment. - /// - /// Url segments are Ascii only (no accents...). - /// - public virtual string CleanStringForUrlSegment(string text, string? culture) + culture = culture ?? ""; + text = text.ReplaceMany(Path.GetInvalidFileNameChars(), '-'); + + var name = Path.GetFileNameWithoutExtension(text); + var ext = Path.GetExtension(text); // includes the dot, empty if no extension + + Debug.Assert(name != null, "name != null"); + if (name.Length > 0) { - return CleanString(text, CleanStringType.UrlSegment, culture); + name = CleanString(name, CleanStringType.FileName, culture); } - /// - /// Cleans a string, in the context of the default culture, to produce a string that can safely be used as a filename, - /// both internally (on disk) and externally (as a URL). - /// - /// The text to filter. - /// The safe filename. - /// Legacy says this was used to "overcome an issue when Umbraco is used in IE in an intranet environment" but that issue is not documented. - public virtual string CleanStringForSafeFileName(string text) + Debug.Assert(ext != null, "ext != null"); + if (ext.Length > 0) { - return CleanStringForSafeFileName(text, _config.DefaultCulture); + ext = CleanString(ext.Substring(1), CleanStringType.FileName, culture); } - /// - /// Cleans a string to produce a string that can safely be used as a filename, - /// both internally (on disk) and externally (as a URL). - /// - /// The text to filter. - /// The culture. - /// The safe filename. - public virtual string CleanStringForSafeFileName(string text, string culture) - { - if (string.IsNullOrWhiteSpace(text)) - return string.Empty; + return ext.Length > 0 ? name + "." + ext : name; + } - culture = culture ?? ""; - text = text.ReplaceMany(Path.GetInvalidFileNameChars(), '-'); + #endregion - var name = Path.GetFileNameWithoutExtension(text); - var ext = Path.GetExtension(text); // includes the dot, empty if no extension + #region CleanString - Debug.Assert(name != null, "name != null"); - if (name.Length > 0) - name = CleanString(name, CleanStringType.FileName, culture); - Debug.Assert(ext != null, "ext != null"); - if (ext.Length > 0) - ext = CleanString(ext.Substring(1), CleanStringType.FileName, culture); + // MS rules & guidelines: + // - Do capitalize both characters of two-character acronyms, except the first word of a camel-cased identifier. + // eg "DBRate" (pascal) or "ioHelper" (camel) - "SpecialDBRate" (pascal) or "specialIOHelper" (camel) + // - Do capitalize only the first character of acronyms with three or more characters, except the first word of a camel-cased identifier. + // eg "XmlWriter (pascal) or "htmlReader" (camel) - "SpecialXmlWriter" (pascal) or "specialHtmlReader" (camel) + // - Do not capitalize any of the characters of any acronyms, whatever their length, at the beginning of a camel-cased identifier. + // eg "xmlWriter" or "dbWriter" (camel) + // + // Our additional stuff: + // - Leading digits are removed. + // - Many consecutive separators are folded into one unique separator. - return ext.Length > 0 ? (name + "." + ext) : name; - } + private const byte StateBreak = 1; + private const byte StateUp = 2; + private const byte StateWord = 3; + private const byte StateAcronym = 4; + + /// + /// Cleans a string. + /// + /// The text to clean. + /// + /// A flag indicating the target casing and encoding of the string. By default, + /// strings are cleaned up to camelCase and Ascii. + /// + /// The clean string. + /// The string is cleaned in the context of the default culture. + public string CleanString(string text, CleanStringType stringType) => + CleanString(text, stringType, _config.DefaultCulture, null); + + /// + /// Cleans a string, using a specified separator. + /// + /// The text to clean. + /// + /// A flag indicating the target casing and encoding of the string. By default, + /// strings are cleaned up to camelCase and Ascii. + /// + /// The separator. + /// The clean string. + /// The string is cleaned in the context of the default culture. + public string CleanString(string text, CleanStringType stringType, char separator) => + CleanString(text, stringType, _config.DefaultCulture, separator); + + /// + /// Cleans a string in the context of a specified culture. + /// + /// The text to clean. + /// + /// A flag indicating the target casing and encoding of the string. By default, + /// strings are cleaned up to camelCase and Ascii. + /// + /// The culture. + /// The clean string. + public string CleanString(string text, CleanStringType stringType, string? culture) => + CleanString(text, stringType, culture, null); - #endregion - - #region CleanString - - // MS rules & guidelines: - // - Do capitalize both characters of two-character acronyms, except the first word of a camel-cased identifier. - // eg "DBRate" (pascal) or "ioHelper" (camel) - "SpecialDBRate" (pascal) or "specialIOHelper" (camel) - // - Do capitalize only the first character of acronyms with three or more characters, except the first word of a camel-cased identifier. - // eg "XmlWriter (pascal) or "htmlReader" (camel) - "SpecialXmlWriter" (pascal) or "specialHtmlReader" (camel) - // - Do not capitalize any of the characters of any acronyms, whatever their length, at the beginning of a camel-cased identifier. - // eg "xmlWriter" or "dbWriter" (camel) - // - // Our additional stuff: - // - Leading digits are removed. - // - Many consecutive separators are folded into one unique separator. - - private const byte StateBreak = 1; - private const byte StateUp = 2; - private const byte StateWord = 3; - private const byte StateAcronym = 4; - - /// - /// Cleans a string. - /// - /// The text to clean. - /// A flag indicating the target casing and encoding of the string. By default, - /// strings are cleaned up to camelCase and Ascii. - /// The clean string. - /// The string is cleaned in the context of the default culture. - public string CleanString(string text, CleanStringType stringType) + /// + /// Cleans a string in the context of a specified culture, using a specified separator. + /// + /// The text to clean. + /// + /// A flag indicating the target casing and encoding of the string. By default, + /// strings are cleaned up to camelCase and Ascii. + /// + /// The separator. + /// The culture. + /// The clean string. + public string CleanString(string text, CleanStringType stringType, char separator, string culture) => + CleanString(text, stringType, culture, separator); + + protected virtual string CleanString(string text, CleanStringType stringType, string? culture, char? separator) + { + // be safe + if (text == null) { - return CleanString(text, stringType, _config.DefaultCulture, null); + throw new ArgumentNullException(nameof(text)); } - /// - /// Cleans a string, using a specified separator. - /// - /// The text to clean. - /// A flag indicating the target casing and encoding of the string. By default, - /// strings are cleaned up to camelCase and Ascii. - /// The separator. - /// The clean string. - /// The string is cleaned in the context of the default culture. - public string CleanString(string text, CleanStringType stringType, char separator) + culture = culture ?? ""; + + // get config + DefaultShortStringHelperConfig.Config config = _config.For(stringType, culture); + stringType = config.StringTypeExtend(stringType); + + // apply defaults + if ((stringType & CleanStringType.CaseMask) == CleanStringType.None) { - return CleanString(text, stringType, _config.DefaultCulture, separator); + stringType |= CleanStringType.CamelCase; } - /// - /// Cleans a string in the context of a specified culture. - /// - /// The text to clean. - /// A flag indicating the target casing and encoding of the string. By default, - /// strings are cleaned up to camelCase and Ascii. - /// The culture. - /// The clean string. - public string CleanString(string text, CleanStringType stringType, string? culture) + if ((stringType & CleanStringType.CodeMask) == CleanStringType.None) { - return CleanString(text, stringType, culture, null); + stringType |= CleanStringType.Ascii; } - /// - /// Cleans a string in the context of a specified culture, using a specified separator. - /// - /// The text to clean. - /// A flag indicating the target casing and encoding of the string. By default, - /// strings are cleaned up to camelCase and Ascii. - /// The separator. - /// The culture. - /// The clean string. - public string CleanString(string text, CleanStringType stringType, char separator, string culture) + // use configured unless specified + separator = separator ?? config.Separator; + + // apply pre-filter + if (config.PreFilter != null) { - return CleanString(text, stringType, culture, separator); + text = config.PreFilter(text); } - protected virtual string CleanString(string text, CleanStringType stringType, string? culture, char? separator) + // apply replacements + //if (config.Replacements != null) + // text = ReplaceMany(text, config.Replacements); + + // recode + CleanStringType codeType = stringType & CleanStringType.CodeMask; + switch (codeType) { - // be safe - if (text == null) throw new ArgumentNullException(nameof(text)); - culture = culture ?? ""; - - // get config - var config = _config.For(stringType, culture); - stringType = config.StringTypeExtend(stringType); - - // apply defaults - if ((stringType & CleanStringType.CaseMask) == CleanStringType.None) - stringType |= CleanStringType.CamelCase; - if ((stringType & CleanStringType.CodeMask) == CleanStringType.None) - stringType |= CleanStringType.Ascii; - - // use configured unless specified - separator = separator ?? config.Separator; - - // apply pre-filter - if (config.PreFilter != null) - text = config.PreFilter(text); - - // apply replacements - //if (config.Replacements != null) - // text = ReplaceMany(text, config.Replacements); - - // recode - var codeType = stringType & CleanStringType.CodeMask; - switch (codeType) - { - case CleanStringType.Ascii: - text = Utf8ToAsciiConverter.ToAsciiString(text); - break; - case CleanStringType.TryAscii: - const char ESC = (char) 27; - var ctext = Utf8ToAsciiConverter.ToAsciiString(text, ESC); - if (ctext.Contains(ESC) == false) text = ctext; - break; - default: - text = RemoveSurrogatePairs(text); - break; - } + case CleanStringType.Ascii: + text = Utf8ToAsciiConverter.ToAsciiString(text); + break; + case CleanStringType.TryAscii: + const char ESC = (char)27; + var ctext = Utf8ToAsciiConverter.ToAsciiString(text, ESC); + if (ctext.Contains(ESC) == false) + { + text = ctext; + } - // clean - text = CleanCodeString(text, stringType, separator.Value, culture, config); + break; + default: + text = RemoveSurrogatePairs(text); + break; + } - // apply post-filter - if (config.PostFilter != null) - text = config.PostFilter(text); + // clean + text = CleanCodeString(text, stringType, separator.Value, culture, config); - return text; + // apply post-filter + if (config.PostFilter != null) + { + text = config.PostFilter(text); } - private static string RemoveSurrogatePairs(string text) - { - var input = text.ToCharArray(); - var output = new char[input.Length]; - var opos = 0; + return text; + } - for (var ipos = 0; ipos < input.Length; ipos++) + private static string RemoveSurrogatePairs(string text) + { + var input = text.ToCharArray(); + var output = new char[input.Length]; + var opos = 0; + + for (var ipos = 0; ipos < input.Length; ipos++) + { + var c = input[ipos]; + if (char.IsSurrogate(c)) // ignore high surrogate { - var c = input[ipos]; - if (char.IsSurrogate(c)) // ignore high surrogate - { - ipos++; // and skip low surrogate - output[opos++] = '?'; - } - else - { - output[opos++] = c; - } + ipos++; // and skip low surrogate + output[opos++] = '?'; + } + else + { + output[opos++] = c; } - - return new string(output, 0, opos); } - // here was a subtle, ascii-optimized version of the cleaning code, and I was - // very proud of it until benchmarking showed it was an order of magnitude slower - // that the utf8 version. Micro-optimizing sometimes isn't such a good idea. + return new string(output, 0, opos); + } + + // here was a subtle, ascii-optimized version of the cleaning code, and I was + // very proud of it until benchmarking showed it was an order of magnitude slower + // that the utf8 version. Micro-optimizing sometimes isn't such a good idea. - // note: does NOT support surrogate pairs in text - internal string CleanCodeString(string text, CleanStringType caseType, char separator, string culture, DefaultShortStringHelperConfig.Config config) - { - int opos = 0, ipos = 0; - var state = StateBreak; + // note: does NOT support surrogate pairs in text + internal string CleanCodeString(string text, CleanStringType caseType, char separator, string culture, + DefaultShortStringHelperConfig.Config config) + { + int opos = 0, ipos = 0; + var state = StateBreak; - culture = culture ?? ""; - caseType &= CleanStringType.CaseMask; + culture = culture ?? ""; + caseType &= CleanStringType.CaseMask; - // if we apply global ToUpper or ToLower to text here - // then we cannot break words on uppercase chars - var input = text; + // if we apply global ToUpper or ToLower to text here + // then we cannot break words on uppercase chars + var input = text; - // it's faster to use an array than a StringBuilder - var ilen = input.Length; - var output = new char[ilen * 2]; // twice the length should be OK in all cases + // it's faster to use an array than a StringBuilder + var ilen = input.Length; + var output = new char[ilen * 2]; // twice the length should be OK in all cases - for (var i = 0; i < ilen; i++) + for (var i = 0; i < ilen; i++) + { + var c = input[i]; + // leading as long as StateBreak and ipos still zero + var leading = state == StateBreak && ipos == 0; + var isTerm = config.IsTerm(c, leading); + + //var isDigit = char.IsDigit(c); + var isUpper = char.IsUpper(c); // false for digits, symbols... + //var isLower = char.IsLower(c); // false for digits, symbols... + + // what should I do with surrogates? - E.g emojis like 🎈 + // no idea, really, so they are not supported at the moment and we just continue + var isPair = char.IsSurrogate(c); + if (isPair) { - var c = input[i]; - // leading as long as StateBreak and ipos still zero - var leading = state == StateBreak && ipos == 0; - var isTerm = config.IsTerm(c, leading); - - //var isDigit = char.IsDigit(c); - var isUpper = char.IsUpper(c); // false for digits, symbols... - //var isLower = char.IsLower(c); // false for digits, symbols... - - // what should I do with surrogates? - E.g emojis like 🎈 - // no idea, really, so they are not supported at the moment and we just continue - var isPair = char.IsSurrogate(c); - if (isPair) - { - continue; - } + continue; + } - switch (state) - { - // within a break - case StateBreak: - // begin a new term if char is a term char, - // and ( pos > 0 or it's also a valid leading char ) - if (isTerm) + switch (state) + { + // within a break + case StateBreak: + // begin a new term if char is a term char, + // and ( pos > 0 or it's also a valid leading char ) + if (isTerm) + { + ipos = i; + if (opos > 0 && separator != char.MinValue) { - ipos = i; - if (opos > 0 && separator != char.MinValue) - output[opos++] = separator; - state = isUpper ? StateUp : StateWord; + output[opos++] = separator; } - break; - // within a term / word - case StateWord: - // end a term if char is not a term char, - // or ( it's uppercase and we break terms on uppercase) - if (isTerm == false || (config.BreakTermsOnUpper && isUpper)) - { - CopyTerm(input, ipos, output, ref opos, i - ipos, caseType, culture, false); - ipos = i; - state = isTerm ? StateUp : StateBreak; - if (state != StateBreak && separator != char.MinValue) - output[opos++] = separator; - } - break; + state = isUpper ? StateUp : StateWord; + } + + break; - // within a term / acronym - case StateAcronym: - // end an acronym if char is not a term char, - // or if it's not uppercase / config - if (isTerm == false || (config.CutAcronymOnNonUpper && isUpper == false)) + // within a term / word + case StateWord: + // end a term if char is not a term char, + // or ( it's uppercase and we break terms on uppercase) + if (isTerm == false || (config.BreakTermsOnUpper && isUpper)) + { + CopyTerm(input, ipos, output, ref opos, i - ipos, caseType, culture, false); + ipos = i; + state = isTerm ? StateUp : StateBreak; + if (state != StateBreak && separator != char.MinValue) { - // whether it's part of the acronym depends on whether we're greedy - if (isTerm && config.GreedyAcronyms == false) - i -= 1; // handle that char again, in another state - not part of the acronym - if (i - ipos > 1) // single-char can't be an acronym - { - CopyTerm(input, ipos, output, ref opos, i - ipos, caseType, culture, true); - ipos = i; - state = isTerm ? StateWord : StateBreak; - if (state != StateBreak && separator != char.MinValue) - output[opos++] = separator; - } - else if (isTerm) - { - state = StateWord; - } + output[opos++] = separator; } - else if (isUpper == false) // isTerm == true + } + + break; + + // within a term / acronym + case StateAcronym: + // end an acronym if char is not a term char, + // or if it's not uppercase / config + if (isTerm == false || (config.CutAcronymOnNonUpper && isUpper == false)) + { + // whether it's part of the acronym depends on whether we're greedy + if (isTerm && config.GreedyAcronyms == false) { - // it's a term char and we don't cut... - // keep moving forward as a word - state = StateWord; + i -= 1; // handle that char again, in another state - not part of the acronym } - break; - // within a term / uppercase = could be a word or an acronym - case StateUp: - if (isTerm) + if (i - ipos > 1) // single-char can't be an acronym { - // add that char to the term and pick word or acronym - state = isUpper ? StateAcronym : StateWord; + CopyTerm(input, ipos, output, ref opos, i - ipos, caseType, culture, true); + ipos = i; + state = isTerm ? StateWord : StateBreak; + if (state != StateBreak && separator != char.MinValue) + { + output[opos++] = separator; + } } - else + else if (isTerm) { - // single char, copy then break - CopyTerm(input, ipos, output, ref opos, 1, caseType, culture, false); - state = StateBreak; + state = StateWord; } - break; - - default: - throw new Exception("Invalid state."); - } - } - - switch (state) - { - case StateBreak: - break; + } + else if (isUpper == false) // isTerm == true + { + // it's a term char and we don't cut... + // keep moving forward as a word + state = StateWord; + } - case StateWord: - CopyTerm(input, ipos, output, ref opos, input.Length - ipos, caseType, culture, false); break; - case StateAcronym: + // within a term / uppercase = could be a word or an acronym case StateUp: - CopyTerm(input, ipos, output, ref opos, input.Length - ipos, caseType, culture, true); + if (isTerm) + { + // add that char to the term and pick word or acronym + state = isUpper ? StateAcronym : StateWord; + } + else + { + // single char, copy then break + CopyTerm(input, ipos, output, ref opos, 1, caseType, culture, false); + state = StateBreak; + } + break; default: throw new Exception("Invalid state."); } - - return new string(output, 0, opos); } - // note: supports surrogate pairs in input string - internal void CopyTerm(string input, int ipos, char[] output, ref int opos, int len, - CleanStringType caseType, string culture, bool isAcronym) + switch (state) { - var term = input.Substring(ipos, len); - var cultureInfo = string.IsNullOrEmpty(culture) ? CultureInfo.InvariantCulture : CultureInfo.GetCultureInfo(culture); + case StateBreak: + break; - if (isAcronym) - { - if ((caseType == CleanStringType.CamelCase && len <= 2 && opos > 0) || - (caseType == CleanStringType.PascalCase && len <= 2) || - (caseType == CleanStringType.UmbracoCase)) - caseType = CleanStringType.Unchanged; - } + case StateWord: + CopyTerm(input, ipos, output, ref opos, input.Length - ipos, caseType, culture, false); + break; - // note: MSDN seems to imply that ToUpper or ToLower preserve the length - // of the string, but that this behavior is not guaranteed and could change. + case StateAcronym: + case StateUp: + CopyTerm(input, ipos, output, ref opos, input.Length - ipos, caseType, culture, true); + break; - char c; - int i; - string s; - switch (caseType) - { - //case CleanStringType.LowerCase: - //case CleanStringType.UpperCase: - case CleanStringType.Unchanged: - term.CopyTo(0, output, opos, len); - opos += len; - break; + default: + throw new Exception("Invalid state."); + } - case CleanStringType.LowerCase: - term = term.ToLower(cultureInfo); - term.CopyTo(0, output, opos, term.Length); - opos += term.Length; - break; + return new string(output, 0, opos); + } - case CleanStringType.UpperCase: - term = term.ToUpper(cultureInfo); - term.CopyTo(0, output, opos, term.Length); - opos += term.Length; - break; + // note: supports surrogate pairs in input string + internal void CopyTerm(string input, int ipos, char[] output, ref int opos, int len, + CleanStringType caseType, string culture, bool isAcronym) + { + var term = input.Substring(ipos, len); + CultureInfo cultureInfo = string.IsNullOrEmpty(culture) + ? CultureInfo.InvariantCulture + : CultureInfo.GetCultureInfo(culture); - case CleanStringType.CamelCase: - c = term[0]; - i = 1; - if (char.IsSurrogate(c)) - { - s = term.Substring(ipos, 2); - s = opos == 0 ? s.ToLower(cultureInfo) : s.ToUpper(cultureInfo); - s.CopyTo(0, output, opos, s.Length); - opos += s.Length; - i++; // surrogate pair len is 2 - } - else - { - output[opos] = opos++ == 0 ? char.ToLower(c, cultureInfo) : char.ToUpper(c, cultureInfo); - } - if (len > i) - { - term = term.Substring(i).ToLower(cultureInfo); - term.CopyTo(0, output, opos, term.Length); - opos += term.Length; - } - break; + if (isAcronym) + { + if ((caseType == CleanStringType.CamelCase && len <= 2 && opos > 0) || + (caseType == CleanStringType.PascalCase && len <= 2) || + caseType == CleanStringType.UmbracoCase) + { + caseType = CleanStringType.Unchanged; + } + } - case CleanStringType.PascalCase: - c = term[0]; - i = 1; - if (char.IsSurrogate(c)) - { - s = term.Substring(ipos, 2); - s = s.ToUpper(cultureInfo); - s.CopyTo(0, output, opos, s.Length); - opos += s.Length; - i++; // surrogate pair len is 2 - } - else - { - output[opos++] = char.ToUpper(c, cultureInfo); - } - if (len > i) - { - term = term.Substring(i).ToLower(cultureInfo); - term.CopyTo(0, output, opos, term.Length); - opos += term.Length; - } - break; + // note: MSDN seems to imply that ToUpper or ToLower preserve the length + // of the string, but that this behavior is not guaranteed and could change. - case CleanStringType.UmbracoCase: - c = term[0]; - i = 1; - if (char.IsSurrogate(c)) - { - s = term.Substring(ipos, 2); - s = opos == 0 ? s : s.ToUpper(cultureInfo); - s.CopyTo(0, output, opos, s.Length); - opos += s.Length; - i++; // surrogate pair len is 2 - } - else - { - output[opos] = opos++ == 0 ? c : char.ToUpper(c, cultureInfo); - } - if (len > i) - { - term = term.Substring(i); - term.CopyTo(0, output, opos, term.Length); - opos += term.Length; - } - break; + char c; + int i; + string s; + switch (caseType) + { + //case CleanStringType.LowerCase: + //case CleanStringType.UpperCase: + case CleanStringType.Unchanged: + term.CopyTo(0, output, opos, len); + opos += len; + break; + + case CleanStringType.LowerCase: + term = term.ToLower(cultureInfo); + term.CopyTo(0, output, opos, term.Length); + opos += term.Length; + break; + + case CleanStringType.UpperCase: + term = term.ToUpper(cultureInfo); + term.CopyTo(0, output, opos, term.Length); + opos += term.Length; + break; + + case CleanStringType.CamelCase: + c = term[0]; + i = 1; + if (char.IsSurrogate(c)) + { + s = term.Substring(ipos, 2); + s = opos == 0 ? s.ToLower(cultureInfo) : s.ToUpper(cultureInfo); + s.CopyTo(0, output, opos, s.Length); + opos += s.Length; + i++; // surrogate pair len is 2 + } + else + { + output[opos] = opos++ == 0 ? char.ToLower(c, cultureInfo) : char.ToUpper(c, cultureInfo); + } - default: - throw new ArgumentOutOfRangeException(nameof(caseType)); - } - } + if (len > i) + { + term = term.Substring(i).ToLower(cultureInfo); + term.CopyTo(0, output, opos, term.Length); + opos += term.Length; + } - #endregion + break; - #region SplitPascalCasing + case CleanStringType.PascalCase: + c = term[0]; + i = 1; + if (char.IsSurrogate(c)) + { + s = term.Substring(ipos, 2); + s = s.ToUpper(cultureInfo); + s.CopyTo(0, output, opos, s.Length); + opos += s.Length; + i++; // surrogate pair len is 2 + } + else + { + output[opos++] = char.ToUpper(c, cultureInfo); + } - /// - /// Splits a Pascal-cased string into a phrase separated by a separator. - /// - /// The text to split. - /// The separator, which defaults to a whitespace. - /// The split text. - /// Supports Utf8 and Ascii strings, not Unicode strings. - // NOTE does not support surrogates pairs at the moment - public virtual string SplitPascalCasing(string text, char separator) - { - // be safe - if (text == null) - throw new ArgumentNullException(nameof(text)); + if (len > i) + { + term = term.Substring(i).ToLower(cultureInfo); + term.CopyTo(0, output, opos, term.Length); + opos += term.Length; + } - var input = text.ToCharArray(); - var output = new char[input.Length * 2]; - var opos = 0; - var a = input.Length > 0 ? input[0] : char.MinValue; - var upos = char.IsUpper(a) ? 1 : 0; + break; - for (var i = 1; i < input.Length; i++) - { - var c = input[i]; - if (char.IsUpper(c)) + case CleanStringType.UmbracoCase: + c = term[0]; + i = 1; + if (char.IsSurrogate(c)) { - output[opos++] = a; - if (upos == 0) - { - if (opos > 0) - output[opos++] = separator; - upos = i + 1; - } + s = term.Substring(ipos, 2); + s = opos == 0 ? s : s.ToUpper(cultureInfo); + s.CopyTo(0, output, opos, s.Length); + opos += s.Length; + i++; // surrogate pair len is 2 } else { - if (upos > 0) - { - if (upos < i && opos > 0) - output[opos++] = separator; - upos = 0; - } - output[opos++] = a; + output[opos] = opos++ == 0 ? c : char.ToUpper(c, cultureInfo); } - a = c; - } - if (a != char.MinValue) - output[opos++] = a; - return new string(output, 0, opos); - } - #endregion + if (len > i) + { + term = term.Substring(i); + term.CopyTo(0, output, opos, term.Length); + opos += term.Length; + } + + break; + + default: + throw new ArgumentOutOfRangeException(nameof(caseType)); + } } + + #endregion } diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs index 4f2d20215524..7ac3c3b726d2 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs @@ -1,227 +1,240 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Configuration.UmbracoSettings; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Strings +namespace Umbraco.Cms.Core.Strings; + +public class DefaultShortStringHelperConfig { - public class DefaultShortStringHelperConfig - { - private readonly Dictionary> _configs = new Dictionary>(); + private readonly Dictionary> _configs = new(); + + public string DefaultCulture { get; set; } = ""; // invariant + + public Dictionary? UrlReplaceCharacters { get; set; } - public DefaultShortStringHelperConfig Clone() + public DefaultShortStringHelperConfig Clone() + { + var config = new DefaultShortStringHelperConfig { - var config = new DefaultShortStringHelperConfig - { - DefaultCulture = DefaultCulture, - UrlReplaceCharacters = UrlReplaceCharacters - }; + DefaultCulture = DefaultCulture, UrlReplaceCharacters = UrlReplaceCharacters + }; - foreach (var kvp1 in _configs) + foreach (KeyValuePair> kvp1 in _configs) + { + Dictionary c = config._configs[kvp1.Key] = + new Dictionary(); + foreach (KeyValuePair kvp2 in _configs[kvp1.Key]) { - var c = config._configs[kvp1.Key] = new Dictionary(); - foreach (var kvp2 in _configs[kvp1.Key]) - c[kvp2.Key] = kvp2.Value.Clone(); + c[kvp2.Key] = kvp2.Value.Clone(); } - - return config; } - public string DefaultCulture { get; set; } = ""; // invariant + return config; + } + + public DefaultShortStringHelperConfig WithConfig(Config config) => + WithConfig(DefaultCulture, CleanStringType.RoleMask, config); - public Dictionary? UrlReplaceCharacters { get; set; } + public DefaultShortStringHelperConfig WithConfig(CleanStringType stringRole, Config config) => + WithConfig(DefaultCulture, stringRole, config); - public DefaultShortStringHelperConfig WithConfig(Config config) + public DefaultShortStringHelperConfig WithConfig(string culture, CleanStringType stringRole, Config config) + { + if (config == null) { - return WithConfig(DefaultCulture, CleanStringType.RoleMask, config); + throw new ArgumentNullException(nameof(config)); } - public DefaultShortStringHelperConfig WithConfig(CleanStringType stringRole, Config config) + culture = culture ?? ""; + + if (_configs.ContainsKey(culture) == false) { - return WithConfig(DefaultCulture, stringRole, config); + _configs[culture] = new Dictionary(); } - public DefaultShortStringHelperConfig WithConfig(string culture, CleanStringType stringRole, Config config) - { - if (config == null) throw new ArgumentNullException(nameof(config)); + _configs[culture][stringRole] = config; + return this; + } - culture = culture ?? ""; + /// + /// Sets the default configuration. + /// + /// The short string helper. + public DefaultShortStringHelperConfig WithDefault(RequestHandlerSettings requestHandlerSettings) + { + IEnumerable charCollection = requestHandlerSettings.GetCharReplacements(); - if (_configs.ContainsKey(culture) == false) - _configs[culture] = new Dictionary(); - _configs[culture][stringRole] = config; - return this; - } + UrlReplaceCharacters = charCollection + .Where(x => string.IsNullOrEmpty(x.Char) == false) + .ToDictionary(x => x.Char, x => x.Replacement); - /// - /// Sets the default configuration. - /// - /// The short string helper. - public DefaultShortStringHelperConfig WithDefault(RequestHandlerSettings requestHandlerSettings) + CleanStringType urlSegmentConvertTo = CleanStringType.Utf8; + if (requestHandlerSettings.ShouldConvertUrlsToAscii) { - IEnumerable charCollection = requestHandlerSettings.GetCharReplacements(); - - UrlReplaceCharacters = charCollection - .Where(x => string.IsNullOrEmpty(x.Char) == false) - .ToDictionary(x => x.Char, x => x.Replacement); - - var urlSegmentConvertTo = CleanStringType.Utf8; - if (requestHandlerSettings.ShouldConvertUrlsToAscii) - urlSegmentConvertTo = CleanStringType.Ascii; - if (requestHandlerSettings.ShouldTryConvertUrlsToAscii) - urlSegmentConvertTo = CleanStringType.TryAscii; + urlSegmentConvertTo = CleanStringType.Ascii; + } - return WithConfig(CleanStringType.UrlSegment, new Config - { - PreFilter = ApplyUrlReplaceCharacters, - PostFilter = x => CutMaxLength(x, 240), - IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore - StringType = urlSegmentConvertTo | CleanStringType.LowerCase, - BreakTermsOnUpper = false, - Separator = '-' - }).WithConfig(CleanStringType.FileName, new Config - { - PreFilter = ApplyUrlReplaceCharacters, - IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore - StringType = CleanStringType.Utf8 | CleanStringType.LowerCase, - BreakTermsOnUpper = false, - Separator = '-' - }).WithConfig(CleanStringType.Alias, new Config - { - PreFilter = ApplyUrlReplaceCharacters, - IsTerm = (c, leading) => leading - ? char.IsLetter(c) // only letters - : (char.IsLetterOrDigit(c) || c == '_'), // letter, digit or underscore - StringType = CleanStringType.Ascii | CleanStringType.UmbracoCase, - BreakTermsOnUpper = false - }).WithConfig(CleanStringType.UnderscoreAlias, new Config - { - PreFilter = ApplyUrlReplaceCharacters, - IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore - StringType = CleanStringType.Ascii | CleanStringType.UmbracoCase, - BreakTermsOnUpper = false - }).WithConfig(CleanStringType.ConvertCase, new Config - { - PreFilter = null, - IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore - StringType = CleanStringType.Ascii, - BreakTermsOnUpper = true - }); + if (requestHandlerSettings.ShouldTryConvertUrlsToAscii) + { + urlSegmentConvertTo = CleanStringType.TryAscii; } - // internal: we don't want ppl to retrieve a config and modify it - // (the helper uses a private clone to prevent modifications) - internal Config For(CleanStringType stringType, string culture) + return WithConfig(CleanStringType.UrlSegment, new Config + { + PreFilter = ApplyUrlReplaceCharacters, + PostFilter = x => CutMaxLength(x, 240), + IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore + StringType = urlSegmentConvertTo | CleanStringType.LowerCase, + BreakTermsOnUpper = false, + Separator = '-' + }).WithConfig(CleanStringType.FileName, new Config + { + PreFilter = ApplyUrlReplaceCharacters, + IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore + StringType = CleanStringType.Utf8 | CleanStringType.LowerCase, + BreakTermsOnUpper = false, + Separator = '-' + }).WithConfig(CleanStringType.Alias, new Config + { + PreFilter = ApplyUrlReplaceCharacters, + IsTerm = (c, leading) => leading + ? char.IsLetter(c) // only letters + : char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore + StringType = CleanStringType.Ascii | CleanStringType.UmbracoCase, + BreakTermsOnUpper = false + }).WithConfig(CleanStringType.UnderscoreAlias, new Config + { + PreFilter = ApplyUrlReplaceCharacters, + IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore + StringType = CleanStringType.Ascii | CleanStringType.UmbracoCase, + BreakTermsOnUpper = false + }).WithConfig(CleanStringType.ConvertCase, new Config { - culture = culture ?? ""; - stringType = stringType & CleanStringType.RoleMask; + PreFilter = null, + IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore + StringType = CleanStringType.Ascii, + BreakTermsOnUpper = true + }); + } + + // internal: we don't want ppl to retrieve a config and modify it + // (the helper uses a private clone to prevent modifications) + internal Config For(CleanStringType stringType, string culture) + { + culture = culture ?? ""; + stringType = stringType & CleanStringType.RoleMask; - Dictionary config; - if (_configs.ContainsKey(culture)) + Dictionary config; + if (_configs.ContainsKey(culture)) + { + config = _configs[culture]; + if (config.ContainsKey(stringType)) // have we got a config for _that_ role? { - config = _configs[culture]; - if (config.ContainsKey(stringType)) // have we got a config for _that_ role? - return config[stringType]; - if (config.ContainsKey(CleanStringType.RoleMask)) // have we got a generic config for _all_ roles? - return config[CleanStringType.RoleMask]; + return config[stringType]; } - else if (_configs.ContainsKey(DefaultCulture)) + + if (config.ContainsKey(CleanStringType.RoleMask)) // have we got a generic config for _all_ roles? { - config = _configs[DefaultCulture]; - if (config.ContainsKey(stringType)) // have we got a config for _that_ role? - return config[stringType]; - if (config.ContainsKey(CleanStringType.RoleMask)) // have we got a generic config for _all_ roles? - return config[CleanStringType.RoleMask]; + return config[CleanStringType.RoleMask]; } - - return Config.NotConfigured; } - - public sealed class Config + else if (_configs.ContainsKey(DefaultCulture)) { - public Config() + config = _configs[DefaultCulture]; + if (config.ContainsKey(stringType)) // have we got a config for _that_ role? { - StringType = CleanStringType.Utf8 | CleanStringType.Unchanged; - PreFilter = null; - PostFilter = null; - IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c); - BreakTermsOnUpper = false; - CutAcronymOnNonUpper = false; - GreedyAcronyms = false; - Separator = char.MinValue; + return config[stringType]; } - public Config Clone() + if (config.ContainsKey(CleanStringType.RoleMask)) // have we got a generic config for _all_ roles? { - return new Config - { - PreFilter = PreFilter, - PostFilter = PostFilter, - IsTerm = IsTerm, - StringType = StringType, - BreakTermsOnUpper = BreakTermsOnUpper, - CutAcronymOnNonUpper = CutAcronymOnNonUpper, - GreedyAcronyms = GreedyAcronyms, - Separator = Separator - }; + return config[CleanStringType.RoleMask]; } + } - public Func? PreFilter { get; set; } - public Func? PostFilter { get; set; } - public Func IsTerm { get; set; } + return Config.NotConfigured; + } - public CleanStringType StringType { get; set; } + /// + /// Returns a new string in which characters have been replaced according to the Umbraco settings UrlReplaceCharacters. + /// + /// The string to filter. + /// The filtered string. + public string ApplyUrlReplaceCharacters(string s) => + UrlReplaceCharacters == null ? s : s.ReplaceMany(UrlReplaceCharacters); - // indicate whether an uppercase within a term eg "fooBar" is to break - // into a new term, or to be considered as part of the current term - public bool BreakTermsOnUpper { get; set; } + public static string CutMaxLength(string text, int length) => + text.Length <= length ? text : text.Substring(0, length); - // indicate whether a non-uppercase within an acronym eg "FOOBar" is to cut - // the acronym (at "B" or "a" depending on GreedyAcronyms) or to give - // up the acronym and treat the term as a word - public bool CutAcronymOnNonUpper { get; set; } + public sealed class Config + { + internal static readonly Config NotConfigured = new(); - // indicates whether acronyms parsing is greedy ie whether "FOObar" is - // "FOO" + "bar" (greedy) or "FO" + "Obar" (non-greedy) - public bool GreedyAcronyms { get; set; } + public Config() + { + StringType = CleanStringType.Utf8 | CleanStringType.Unchanged; + PreFilter = null; + PostFilter = null; + IsTerm = (c, leading) => leading ? char.IsLetter(c) : char.IsLetterOrDigit(c); + BreakTermsOnUpper = false; + CutAcronymOnNonUpper = false; + GreedyAcronyms = false; + Separator = char.MinValue; + } - // the separator char - // but then how can we tell we don't want any? - public char Separator { get; set; } + public Func? PreFilter { get; set; } + public Func? PostFilter { get; set; } + public Func IsTerm { get; set; } - // extends the config - public CleanStringType StringTypeExtend(CleanStringType stringType) - { - var st = StringType; - foreach (var mask in new[] { CleanStringType.CaseMask, CleanStringType.CodeMask }) - { - var a = stringType & mask; - if (a == 0) continue; + public CleanStringType StringType { get; set; } - st = st & ~mask; // clear what we have - st = st | a; // set the new value - } - return st; - } + // indicate whether an uppercase within a term eg "fooBar" is to break + // into a new term, or to be considered as part of the current term + public bool BreakTermsOnUpper { get; set; } - internal static readonly Config NotConfigured = new Config(); - } + // indicate whether a non-uppercase within an acronym eg "FOOBar" is to cut + // the acronym (at "B" or "a" depending on GreedyAcronyms) or to give + // up the acronym and treat the term as a word + public bool CutAcronymOnNonUpper { get; set; } - /// - /// Returns a new string in which characters have been replaced according to the Umbraco settings UrlReplaceCharacters. - /// - /// The string to filter. - /// The filtered string. - public string ApplyUrlReplaceCharacters(string s) - { - return UrlReplaceCharacters == null ? s : s.ReplaceMany(UrlReplaceCharacters); - } + // indicates whether acronyms parsing is greedy ie whether "FOObar" is + // "FOO" + "bar" (greedy) or "FO" + "Obar" (non-greedy) + public bool GreedyAcronyms { get; set; } + + // the separator char + // but then how can we tell we don't want any? + public char Separator { get; set; } - public static string CutMaxLength(string text, int length) + public Config Clone() => + new Config + { + PreFilter = PreFilter, + PostFilter = PostFilter, + IsTerm = IsTerm, + StringType = StringType, + BreakTermsOnUpper = BreakTermsOnUpper, + CutAcronymOnNonUpper = CutAcronymOnNonUpper, + GreedyAcronyms = GreedyAcronyms, + Separator = Separator + }; + + // extends the config + public CleanStringType StringTypeExtend(CleanStringType stringType) { - return text.Length <= length ? text : text.Substring(0, length); + CleanStringType st = StringType; + foreach (CleanStringType mask in new[] {CleanStringType.CaseMask, CleanStringType.CodeMask}) + { + CleanStringType a = stringType & mask; + if (a == 0) + { + continue; + } + + st = st & ~mask; // clear what we have + st = st | a; // set the new value + } + + return st; } } } diff --git a/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs b/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs index 22b8a40c0e2e..d78b0497b6dd 100644 --- a/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs +++ b/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs @@ -1,45 +1,43 @@ using Umbraco.Cms.Core.Models; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Strings +namespace Umbraco.Cms.Core.Strings; + +/// +/// Default implementation of IUrlSegmentProvider. +/// +public class DefaultUrlSegmentProvider : IUrlSegmentProvider { + private readonly IShortStringHelper _shortStringHelper; + + public DefaultUrlSegmentProvider(IShortStringHelper shortStringHelper) => _shortStringHelper = shortStringHelper; + /// - /// Default implementation of IUrlSegmentProvider. + /// Gets the URL segment for a specified content and culture. /// - public class DefaultUrlSegmentProvider : IUrlSegmentProvider - { - private readonly IShortStringHelper _shortStringHelper; + /// The content. + /// The culture. + /// The URL segment. + public string? GetUrlSegment(IContentBase content, string? culture = null) => + GetUrlSegmentSource(content, culture)?.ToUrlSegment(_shortStringHelper, culture); - public DefaultUrlSegmentProvider(IShortStringHelper shortStringHelper) + private static string? GetUrlSegmentSource(IContentBase content, string? culture) + { + string? source = null; + if (content.HasProperty(Constants.Conventions.Content.UrlName)) { - _shortStringHelper = shortStringHelper; + source = (content.GetValue(Constants.Conventions.Content.UrlName, culture) ?? string.Empty).Trim(); } - /// - /// Gets the URL segment for a specified content and culture. - /// - /// The content. - /// The culture. - /// The URL segment. - public string? GetUrlSegment(IContentBase content, string? culture = null) + if (string.IsNullOrWhiteSpace(source)) { - return GetUrlSegmentSource(content, culture)?.ToUrlSegment(_shortStringHelper, culture); + // If the name of a node has been updated, but it has not been published, the url should use the published name, not the current node name + // If this node has never been published (GetPublishName is null), use the unpublished name + source = content is IContent document && document.Edited && document.GetPublishName(culture) != null + ? document.GetPublishName(culture) + : content.GetCultureName(culture); } - private static string? GetUrlSegmentSource(IContentBase content, string? culture) - { - string? source = null; - if (content.HasProperty(Constants.Conventions.Content.UrlName)) - source = (content.GetValue(Constants.Conventions.Content.UrlName, culture) ?? string.Empty).Trim(); - if (string.IsNullOrWhiteSpace(source)) - { - // If the name of a node has been updated, but it has not been published, the url should use the published name, not the current node name - // If this node has never been published (GetPublishName is null), use the unpublished name - source = (content is IContent document) && document.Edited && document.GetPublishName(culture) != null - ? document.GetPublishName(culture) - : content.GetCultureName(culture); - } - return source; - } + return source; } } diff --git a/src/Umbraco.Core/Strings/Diff.cs b/src/Umbraco.Core/Strings/Diff.cs index 8d7ef9feaa37..eed1437f5c33 100644 --- a/src/Umbraco.Core/Strings/Diff.cs +++ b/src/Umbraco.Core/Strings/Diff.cs @@ -1,507 +1,537 @@ -using System; -using System.Collections; +using System.Collections; using System.Text.RegularExpressions; -namespace Umbraco.Cms.Core.Strings +namespace Umbraco.Cms.Core.Strings; + +/// +/// This Class implements the Difference Algorithm published in +/// "An O(ND) Difference Algorithm and its Variations" by Eugene Myers +/// Algorithmica Vol. 1 No. 2, 1986, p 251. +/// The algorithm itself is comparing 2 arrays of numbers so when comparing 2 text documents +/// each line is converted into a (hash) number. See DiffText(). +/// diff.cs: A port of the algorithm to C# +/// Copyright (c) by Matthias Hertel, http://www.mathertel.de +/// This work is licensed under a BSD style license. See http://www.mathertel.de/License.aspx +/// +internal class Diff { /// - /// This Class implements the Difference Algorithm published in - /// "An O(ND) Difference Algorithm and its Variations" by Eugene Myers - /// Algorithmica Vol. 1 No. 2, 1986, p 251. - /// - /// The algorithm itself is comparing 2 arrays of numbers so when comparing 2 text documents - /// each line is converted into a (hash) number. See DiffText(). - /// - /// diff.cs: A port of the algorithm to C# - /// Copyright (c) by Matthias Hertel, http://www.mathertel.de - /// This work is licensed under a BSD style license. See http://www.mathertel.de/License.aspx + /// Find the difference in 2 texts, comparing by text lines. /// - internal class Diff + /// A-version of the text (usually the old one) + /// B-version of the text (usually the new one) + /// Returns a array of Items that describe the differences. + public static Item[] DiffText(string textA, string textB) => + DiffText(textA, textB, false, false, false); // DiffText + + /// + /// Find the difference in 2 texts, comparing by text lines. + /// This method uses the DiffInt internally by 1st converting the string into char codes + /// then uses the diff int method + /// + /// A-version of the text (usually the old one) + /// B-version of the text (usually the new one) + /// Returns a array of Items that describe the differences. + public static Item[] DiffText1(string textA, string textB) => + DiffInt(DiffCharCodes(textA, false), DiffCharCodes(textB, false)); + + + /// + /// Find the difference in 2 text documents, comparing by text lines. + /// The algorithm itself is comparing 2 arrays of numbers so when comparing 2 text documents + /// each line is converted into a (hash) number. This hash-value is computed by storing all + /// text lines into a common Hashtable so i can find duplicates in there, and generating a + /// new number each time a new text line is inserted. + /// + /// A-version of the text (usually the old one) + /// B-version of the text (usually the new one) + /// + /// When set to true, all leading and trailing whitespace characters are stripped out before the + /// comparison is done. + /// + /// + /// When set to true, all whitespace characters are converted to a single space character before + /// the comparison is done. + /// + /// + /// When set to true, all characters are converted to their lowercase equivalence before the + /// comparison is done. + /// + /// Returns a array of Items that describe the differences. + public static Item[] DiffText(string textA, string textB, bool trimSpace, bool ignoreSpace, bool ignoreCase) { - /// Data on one input file being compared. - /// - internal class DiffData - { + // prepare the input-text and convert to comparable numbers. + var h = new Hashtable(textA.Length + textB.Length); - /// Number of elements (lines). - internal int Length; + // The A-Version of the data (original data) to be compared. + var dataA = new DiffData(DiffCodes(textA, h, trimSpace, ignoreSpace, ignoreCase)); - /// Buffer of numbers that will be compared. - internal int[] Data; + // The B-Version of the data (modified data) to be compared. + var dataB = new DiffData(DiffCodes(textB, h, trimSpace, ignoreSpace, ignoreCase)); - /// - /// Array of booleans that flag for modified data. - /// This is the result of the diff. - /// This means deletedA in the first Data or inserted in the second Data. - /// - internal bool[] Modified; + h = null; // free up Hashtable memory (maybe) - /// - /// Initialize the Diff-Data buffer. - /// - /// reference to the buffer - internal DiffData(int[] initData) - { - Data = initData; - Length = initData.Length; - Modified = new bool[Length + 2]; - } // DiffData + var max = dataA.Length + dataB.Length + 1; + // vector for the (0,0) to (x,y) search + var downVector = new int[(2 * max) + 2]; + // vector for the (u,v) to (N,M) search + var upVector = new int[(2 * max) + 2]; - } // class DiffData + Lcs(dataA, 0, dataA.Length, dataB, 0, dataB.Length, downVector, upVector); - /// details of one difference. - public struct Item - { - /// Start Line number in Data A. - public int StartA; - /// Start Line number in Data B. - public int StartB; + Optimize(dataA); + Optimize(dataB); + return CreateDiffs(dataA, dataB); + } // DiffText - /// Number of changes in Data A. - public int DeletedA; - /// Number of changes in Data B. - public int InsertedB; - } // Item - /// - /// Shortest Middle Snake Return Data - /// - private struct Smsrd + /// + /// Diffs the char codes. + /// + /// A text. + /// if set to true [ignore case]. + /// + private static int[] DiffCharCodes(string aText, bool ignoreCase) + { + if (ignoreCase) { - internal int X, Y; - // internal int u, v; // 2002.09.20: no need for 2 points + aText = aText.ToUpperInvariant(); } - /// - /// Find the difference in 2 texts, comparing by text lines. - /// - /// A-version of the text (usually the old one) - /// B-version of the text (usually the new one) - /// Returns a array of Items that describe the differences. - public static Item[] DiffText(string textA, string textB) - { - return (DiffText(textA, textB, false, false, false)); - } // DiffText + var codes = new int[aText.Length]; - /// - /// Find the difference in 2 texts, comparing by text lines. - /// This method uses the DiffInt internally by 1st converting the string into char codes - /// then uses the diff int method - /// - /// A-version of the text (usually the old one) - /// B-version of the text (usually the new one) - /// Returns a array of Items that describe the differences. - public static Item[] DiffText1(string textA, string textB) + for (var n = 0; n < aText.Length; n++) { - return DiffInt(DiffCharCodes(textA, false), DiffCharCodes(textB, false)); + codes[n] = aText[n]; } + return codes; + } // DiffCharCodes - /// - /// Find the difference in 2 text documents, comparing by text lines. - /// The algorithm itself is comparing 2 arrays of numbers so when comparing 2 text documents - /// each line is converted into a (hash) number. This hash-value is computed by storing all - /// text lines into a common Hashtable so i can find duplicates in there, and generating a - /// new number each time a new text line is inserted. - /// - /// A-version of the text (usually the old one) - /// B-version of the text (usually the new one) - /// When set to true, all leading and trailing whitespace characters are stripped out before the comparison is done. - /// When set to true, all whitespace characters are converted to a single space character before the comparison is done. - /// When set to true, all characters are converted to their lowercase equivalence before the comparison is done. - /// Returns a array of Items that describe the differences. - public static Item[] DiffText(string textA, string textB, bool trimSpace, bool ignoreSpace, bool ignoreCase) + /// + /// If a sequence of modified lines starts with a line that contains the same content + /// as the line that appends the changes, the difference sequence is modified so that the + /// appended line and not the starting line is marked as modified. + /// This leads to more readable diff sequences when comparing text files. + /// + /// A Diff data buffer containing the identified changes. + private static void Optimize(DiffData data) + { + var startPos = 0; + while (startPos < data.Length) { - // prepare the input-text and convert to comparable numbers. - var h = new Hashtable(textA.Length + textB.Length); + while (startPos < data.Length && data.Modified[startPos] == false) + { + startPos++; + } - // The A-Version of the data (original data) to be compared. - var dataA = new DiffData(DiffCodes(textA, h, trimSpace, ignoreSpace, ignoreCase)); + var endPos = startPos; + while (endPos < data.Length && data.Modified[endPos]) + { + endPos++; + } - // The B-Version of the data (modified data) to be compared. - var dataB = new DiffData(DiffCodes(textB, h, trimSpace, ignoreSpace, ignoreCase)); + if (endPos < data.Length && data.Data[startPos] == data.Data[endPos]) + { + data.Modified[startPos] = false; + data.Modified[endPos] = true; + } + else + { + startPos = endPos; + } // if + } // while + } // Optimize - h = null; // free up Hashtable memory (maybe) - var max = dataA.Length + dataB.Length + 1; - // vector for the (0,0) to (x,y) search - var downVector = new int[2 * max + 2]; - // vector for the (u,v) to (N,M) search - var upVector = new int[2 * max + 2]; + /// + /// Find the difference in 2 arrays of integers. + /// + /// A-version of the numbers (usually the old one) + /// B-version of the numbers (usually the new one) + /// Returns a array of Items that describe the differences. + public static Item[] DiffInt(int[] arrayA, int[] arrayB) + { + // The A-Version of the data (original data) to be compared. + var dataA = new DiffData(arrayA); - Lcs(dataA, 0, dataA.Length, dataB, 0, dataB.Length, downVector, upVector); + // The B-Version of the data (modified data) to be compared. + var dataB = new DiffData(arrayB); - Optimize(dataA); - Optimize(dataB); - return CreateDiffs(dataA, dataB); - } // DiffText + var max = dataA.Length + dataB.Length + 1; + // vector for the (0,0) to (x,y) search + var downVector = new int[(2 * max) + 2]; + // vector for the (u,v) to (N,M) search + var upVector = new int[(2 * max) + 2]; + Lcs(dataA, 0, dataA.Length, dataB, 0, dataB.Length, downVector, upVector); + return CreateDiffs(dataA, dataB); + } // Diff - /// - /// Diffs the char codes. - /// - /// A text. - /// if set to true [ignore case]. - /// - private static int[] DiffCharCodes(string aText, bool ignoreCase) - { - if (ignoreCase) - aText = aText.ToUpperInvariant(); - var codes = new int[aText.Length]; + /// + /// This function converts all text lines of the text into unique numbers for every unique text line + /// so further work can work only with simple numbers. + /// + /// the input text + /// This extern initialized Hashtable is used for storing all ever used text lines. + /// ignore leading and trailing space characters + /// + /// + /// a array of integers. + private static int[] DiffCodes(string aText, IDictionary h, bool trimSpace, bool ignoreSpace, bool ignoreCase) + { + // get all codes of the text + var lastUsedCode = h.Count; - for (int n = 0; n < aText.Length; n++) - codes[n] = (int)aText[n]; + // strip off all cr, only use lf as text line separator. + aText = aText.Replace("\r", ""); + var lines = aText.Split(Constants.CharArrays.LineFeed); - return (codes); - } // DiffCharCodes + var codes = new int[lines.Length]; - /// - /// If a sequence of modified lines starts with a line that contains the same content - /// as the line that appends the changes, the difference sequence is modified so that the - /// appended line and not the starting line is marked as modified. - /// This leads to more readable diff sequences when comparing text files. - /// - /// A Diff data buffer containing the identified changes. - private static void Optimize(DiffData data) + for (var i = 0; i < lines.Length; ++i) { - var startPos = 0; - while (startPos < data.Length) + var s = lines[i]; + if (trimSpace) { - while ((startPos < data.Length) && (data.Modified[startPos] == false)) - startPos++; - int endPos = startPos; - while ((endPos < data.Length) && (data.Modified[endPos] == true)) - endPos++; + s = s.Trim(); + } - if ((endPos < data.Length) && (data.Data[startPos] == data.Data[endPos])) - { - data.Modified[startPos] = false; - data.Modified[endPos] = true; - } - else - { - startPos = endPos; - } // if - } // while - } // Optimize + if (ignoreSpace) + { + s = Regex.Replace(s, "\\s+", " "); // TODO: optimization: faster blank removal. + } + if (ignoreCase) + { + s = s.ToLower(); + } - /// - /// Find the difference in 2 arrays of integers. - /// - /// A-version of the numbers (usually the old one) - /// B-version of the numbers (usually the new one) - /// Returns a array of Items that describe the differences. - public static Item[] DiffInt(int[] arrayA, int[] arrayB) - { - // The A-Version of the data (original data) to be compared. - var dataA = new DiffData(arrayA); + var aCode = h[s]; + if (aCode == null) + { + lastUsedCode++; + h[s] = lastUsedCode; + codes[i] = lastUsedCode; + } + else + { + codes[i] = (int)aCode; + } // if + } // for - // The B-Version of the data (modified data) to be compared. - var dataB = new DiffData(arrayB); + return codes; + } // DiffCodes - var max = dataA.Length + dataB.Length + 1; - // vector for the (0,0) to (x,y) search - var downVector = new int[2 * max + 2]; - // vector for the (u,v) to (N,M) search - var upVector = new int[2 * max + 2]; - Lcs(dataA, 0, dataA.Length, dataB, 0, dataB.Length, downVector, upVector); - return CreateDiffs(dataA, dataB); - } // Diff + /// + /// This is the algorithm to find the Shortest Middle Snake (SMS). + /// + /// sequence A + /// lower bound of the actual range in DataA + /// upper bound of the actual range in DataA (exclusive) + /// sequence B + /// lower bound of the actual range in DataB + /// upper bound of the actual range in DataB (exclusive) + /// a vector for the (0,0) to (x,y) search. Passed as a parameter for speed reasons. + /// a vector for the (u,v) to (N,M) search. Passed as a parameter for speed reasons. + /// a MiddleSnakeData record containing x,y and u,v + private static Smsrd Sms(DiffData dataA, int lowerA, int upperA, DiffData dataB, int lowerB, int upperB, + int[] downVector, int[] upVector) + { + var max = dataA.Length + dataB.Length + 1; + var downK = lowerA - lowerB; // the k-line to start the forward search + var upK = upperA - upperB; // the k-line to start the reverse search - /// - /// This function converts all text lines of the text into unique numbers for every unique text line - /// so further work can work only with simple numbers. - /// - /// the input text - /// This extern initialized Hashtable is used for storing all ever used text lines. - /// ignore leading and trailing space characters - /// - /// - /// a array of integers. - private static int[] DiffCodes(string aText, IDictionary h, bool trimSpace, bool ignoreSpace, bool ignoreCase) - { - // get all codes of the text - var lastUsedCode = h.Count; + var delta = upperA - lowerA - (upperB - lowerB); + var oddDelta = (delta & 1) != 0; - // strip off all cr, only use lf as text line separator. - aText = aText.Replace("\r", ""); - var lines = aText.Split(Constants.CharArrays.LineFeed); + // The vectors in the publication accepts negative indexes. the vectors implemented here are 0-based + // and are access using a specific offset: UpOffset UpVector and DownOffset for DownVektor + var downOffset = max - downK; + var upOffset = max - upK; - var codes = new int[lines.Length]; + var maxD = ((upperA - lowerA + upperB - lowerB) / 2) + 1; - for (int i = 0; i < lines.Length; ++i) - { - string s = lines[i]; - if (trimSpace) - s = s.Trim(); + // Debug.Write(2, "SMS", String.Format("Search the box: A[{0}-{1}] to B[{2}-{3}]", LowerA, UpperA, LowerB, UpperB)); - if (ignoreSpace) - { - s = Regex.Replace(s, "\\s+", " "); // TODO: optimization: faster blank removal. - } + // init vectors + downVector[downOffset + downK + 1] = lowerA; + upVector[upOffset + upK - 1] = upperA; - if (ignoreCase) - s = s.ToLower(); + for (var d = 0; d <= maxD; d++) + { + // Extend the forward path. + Smsrd ret; + for (var k = downK - d; k <= downK + d; k += 2) + { + // Debug.Write(0, "SMS", "extend forward path " + k.ToString()); - object? aCode = h[s]; - if (aCode == null) + // find the only or better starting point + int x, y; + if (k == downK - d) { - lastUsedCode++; - h[s] = lastUsedCode; - codes[i] = lastUsedCode; + x = downVector[downOffset + k + 1]; // down } else { - codes[i] = (int)aCode; - } // if - } // for - return (codes); - } // DiffCodes - - - /// - /// This is the algorithm to find the Shortest Middle Snake (SMS). - /// - /// sequence A - /// lower bound of the actual range in DataA - /// upper bound of the actual range in DataA (exclusive) - /// sequence B - /// lower bound of the actual range in DataB - /// upper bound of the actual range in DataB (exclusive) - /// a vector for the (0,0) to (x,y) search. Passed as a parameter for speed reasons. - /// a vector for the (u,v) to (N,M) search. Passed as a parameter for speed reasons. - /// a MiddleSnakeData record containing x,y and u,v - private static Smsrd Sms(DiffData dataA, int lowerA, int upperA, DiffData dataB, int lowerB, int upperB, int[] downVector, int[] upVector) - { - int max = dataA.Length + dataB.Length + 1; - - int downK = lowerA - lowerB; // the k-line to start the forward search - int upK = upperA - upperB; // the k-line to start the reverse search - - int delta = (upperA - lowerA) - (upperB - lowerB); - bool oddDelta = (delta & 1) != 0; - - // The vectors in the publication accepts negative indexes. the vectors implemented here are 0-based - // and are access using a specific offset: UpOffset UpVector and DownOffset for DownVektor - int downOffset = max - downK; - int upOffset = max - upK; - - int maxD = ((upperA - lowerA + upperB - lowerB) / 2) + 1; - - // Debug.Write(2, "SMS", String.Format("Search the box: A[{0}-{1}] to B[{2}-{3}]", LowerA, UpperA, LowerB, UpperB)); - - // init vectors - downVector[downOffset + downK + 1] = lowerA; - upVector[upOffset + upK - 1] = upperA; - - for (int d = 0; d <= maxD; d++) - { - - // Extend the forward path. - Smsrd ret; - for (int k = downK - d; k <= downK + d; k += 2) - { - // Debug.Write(0, "SMS", "extend forward path " + k.ToString()); - - // find the only or better starting point - int x, y; - if (k == downK - d) + x = downVector[downOffset + k - 1] + 1; // a step to the right + if (k < downK + d && downVector[downOffset + k + 1] >= x) { x = downVector[downOffset + k + 1]; // down } - else - { - x = downVector[downOffset + k - 1] + 1; // a step to the right - if ((k < downK + d) && (downVector[downOffset + k + 1] >= x)) - x = downVector[downOffset + k + 1]; // down - } - y = x - k; + } - // find the end of the furthest reaching forward D-path in diagonal k. - while ((x < upperA) && (y < upperB) && (dataA.Data[x] == dataB.Data[y])) - { - x++; y++; - } - downVector[downOffset + k] = x; + y = x - k; + + // find the end of the furthest reaching forward D-path in diagonal k. + while (x < upperA && y < upperB && dataA.Data[x] == dataB.Data[y]) + { + x++; + y++; + } + + downVector[downOffset + k] = x; - // overlap ? - if (oddDelta && (upK - d < k) && (k < upK + d)) + // overlap ? + if (oddDelta && upK - d < k && k < upK + d) + { + if (upVector[upOffset + k] <= downVector[downOffset + k]) { - if (upVector[upOffset + k] <= downVector[downOffset + k]) - { - ret.X = downVector[downOffset + k]; - ret.Y = downVector[downOffset + k] - k; - // ret.u = UpVector[UpOffset + k]; // 2002.09.20: no need for 2 points - // ret.v = UpVector[UpOffset + k] - k; - return (ret); - } // if + ret.X = downVector[downOffset + k]; + ret.Y = downVector[downOffset + k] - k; + // ret.u = UpVector[UpOffset + k]; // 2002.09.20: no need for 2 points + // ret.v = UpVector[UpOffset + k] - k; + return ret; } // if + } // if + } // for k - } // for k + // Extend the reverse path. + for (var k = upK - d; k <= upK + d; k += 2) + { + // Debug.Write(0, "SMS", "extend reverse path " + k.ToString()); - // Extend the reverse path. - for (int k = upK - d; k <= upK + d; k += 2) + // find the only or better starting point + int x, y; + if (k == upK + d) { - // Debug.Write(0, "SMS", "extend reverse path " + k.ToString()); - - // find the only or better starting point - int x, y; - if (k == upK + d) + x = upVector[upOffset + k - 1]; // up + } + else + { + x = upVector[upOffset + k + 1] - 1; // left + if (k > upK - d && upVector[upOffset + k - 1] < x) { x = upVector[upOffset + k - 1]; // up } - else - { - x = upVector[upOffset + k + 1] - 1; // left - if ((k > upK - d) && (upVector[upOffset + k - 1] < x)) - x = upVector[upOffset + k - 1]; // up - } // if - y = x - k; + } // if - while ((x > lowerA) && (y > lowerB) && (dataA.Data[x - 1] == dataB.Data[y - 1])) - { - x--; y--; // diagonal - } - upVector[upOffset + k] = x; + y = x - k; - // overlap ? - if (!oddDelta && (downK - d <= k) && (k <= downK + d)) + while (x > lowerA && y > lowerB && dataA.Data[x - 1] == dataB.Data[y - 1]) + { + x--; + y--; // diagonal + } + + upVector[upOffset + k] = x; + + // overlap ? + if (!oddDelta && downK - d <= k && k <= downK + d) + { + if (upVector[upOffset + k] <= downVector[downOffset + k]) { - if (upVector[upOffset + k] <= downVector[downOffset + k]) - { - ret.X = downVector[downOffset + k]; - ret.Y = downVector[downOffset + k] - k; - // ret.u = UpVector[UpOffset + k]; // 2002.09.20: no need for 2 points - // ret.v = UpVector[UpOffset + k] - k; - return (ret); - } // if + ret.X = downVector[downOffset + k]; + ret.Y = downVector[downOffset + k] - k; + // ret.u = UpVector[UpOffset + k]; // 2002.09.20: no need for 2 points + // ret.v = UpVector[UpOffset + k] - k; + return ret; } // if + } // if + } // for k + } // for D - } // for k + throw new ApplicationException("the algorithm should never come here."); + } // SMS - } // for D - throw new ApplicationException("the algorithm should never come here."); - } // SMS + /// + /// This is the divide-and-conquer implementation of the longest common-subsequence (LCS) + /// algorithm. + /// The published algorithm passes recursively parts of the A and B sequences. + /// To avoid copying these arrays the lower and upper bounds are passed while the sequences stay constant. + /// + /// sequence A + /// lower bound of the actual range in DataA + /// upper bound of the actual range in DataA (exclusive) + /// sequence B + /// lower bound of the actual range in DataB + /// upper bound of the actual range in DataB (exclusive) + /// a vector for the (0,0) to (x,y) search. Passed as a parameter for speed reasons. + /// a vector for the (u,v) to (N,M) search. Passed as a parameter for speed reasons. + private static void Lcs(DiffData dataA, int lowerA, int upperA, DiffData dataB, int lowerB, int upperB, + int[] downVector, int[] upVector) + { + // Debug.Write(2, "LCS", String.Format("Analyze the box: A[{0}-{1}] to B[{2}-{3}]", LowerA, UpperA, LowerB, UpperB)); + // Fast walk through equal lines at the start + while (lowerA < upperA && lowerB < upperB && dataA.Data[lowerA] == dataB.Data[lowerB]) + { + lowerA++; + lowerB++; + } - /// - /// This is the divide-and-conquer implementation of the longest common-subsequence (LCS) - /// algorithm. - /// The published algorithm passes recursively parts of the A and B sequences. - /// To avoid copying these arrays the lower and upper bounds are passed while the sequences stay constant. - /// - /// sequence A - /// lower bound of the actual range in DataA - /// upper bound of the actual range in DataA (exclusive) - /// sequence B - /// lower bound of the actual range in DataB - /// upper bound of the actual range in DataB (exclusive) - /// a vector for the (0,0) to (x,y) search. Passed as a parameter for speed reasons. - /// a vector for the (u,v) to (N,M) search. Passed as a parameter for speed reasons. - private static void Lcs(DiffData dataA, int lowerA, int upperA, DiffData dataB, int lowerB, int upperB, int[] downVector, int[] upVector) + // Fast walk through equal lines at the end + while (lowerA < upperA && lowerB < upperB && dataA.Data[upperA - 1] == dataB.Data[upperB - 1]) { - // Debug.Write(2, "LCS", String.Format("Analyze the box: A[{0}-{1}] to B[{2}-{3}]", LowerA, UpperA, LowerB, UpperB)); + --upperA; + --upperB; + } - // Fast walk through equal lines at the start - while (lowerA < upperA && lowerB < upperB && dataA.Data[lowerA] == dataB.Data[lowerB]) + if (lowerA == upperA) + { + // mark as inserted lines. + while (lowerB < upperB) { - lowerA++; lowerB++; + dataB.Modified[lowerB++] = true; } - - // Fast walk through equal lines at the end - while (lowerA < upperA && lowerB < upperB && dataA.Data[upperA - 1] == dataB.Data[upperB - 1]) + } + else if (lowerB == upperB) + { + // mark as deleted lines. + while (lowerA < upperA) { - --upperA; --upperB; + dataA.Modified[lowerA++] = true; } + } + else + { + // Find the middle snake and length of an optimal path for A and B + Smsrd smsrd = Sms(dataA, lowerA, upperA, dataB, lowerB, upperB, downVector, upVector); + // Debug.Write(2, "MiddleSnakeData", String.Format("{0},{1}", smsrd.x, smsrd.y)); + + // The path is from LowerX to (x,y) and (x,y) to UpperX + Lcs(dataA, lowerA, smsrd.X, dataB, lowerB, smsrd.Y, downVector, upVector); + Lcs(dataA, smsrd.X, upperA, dataB, smsrd.Y, upperB, downVector, + upVector); // 2002.09.20: no need for 2 points + } + } // LCS() - if (lowerA == upperA) - { - // mark as inserted lines. - while (lowerB < upperB) - dataB.Modified[lowerB++] = true; - } - else if (lowerB == upperB) - { - // mark as deleted lines. - while (lowerA < upperA) - dataA.Modified[lowerA++] = true; + /// + /// Scan the tables of which lines are inserted and deleted, + /// producing an edit script in forward order. + /// + /// dynamic array + private static Item[] CreateDiffs(DiffData dataA, DiffData dataB) + { + var a = new ArrayList(); + Item aItem; + Item[] result; + var lineA = 0; + var lineB = 0; + while (lineA < dataA.Length || lineB < dataB.Length) + { + if (lineA < dataA.Length && !dataA.Modified[lineA] + && lineB < dataB.Length && !dataB.Modified[lineB]) + { + // equal lines + lineA++; + lineB++; } else { - // Find the middle snake and length of an optimal path for A and B - Smsrd smsrd = Sms(dataA, lowerA, upperA, dataB, lowerB, upperB, downVector, upVector); - // Debug.Write(2, "MiddleSnakeData", String.Format("{0},{1}", smsrd.x, smsrd.y)); + // maybe deleted and/or inserted lines + var startA = lineA; + var startB = lineB; - // The path is from LowerX to (x,y) and (x,y) to UpperX - Lcs(dataA, lowerA, smsrd.X, dataB, lowerB, smsrd.Y, downVector, upVector); - Lcs(dataA, smsrd.X, upperA, dataB, smsrd.Y, upperB, downVector, upVector); // 2002.09.20: no need for 2 points - } - } // LCS() - - - /// Scan the tables of which lines are inserted and deleted, - /// producing an edit script in forward order. - /// - /// dynamic array - private static Item[] CreateDiffs(DiffData dataA, DiffData dataB) - { - ArrayList a = new ArrayList(); - Item aItem; - Item[] result; - - int lineA = 0; - int lineB = 0; - while (lineA < dataA.Length || lineB < dataB.Length) - { - if ((lineA < dataA.Length) && (!dataA.Modified[lineA]) - && (lineB < dataB.Length) && (!dataB.Modified[lineB])) + while (lineA < dataA.Length && (lineB >= dataB.Length || dataA.Modified[lineA])) + // while (LineA < DataA.Length && DataA.modified[LineA]) { - // equal lines lineA++; - lineB++; + } + while (lineB < dataB.Length && (lineA >= dataA.Length || dataB.Modified[lineB])) + // while (LineB < DataB.Length && DataB.modified[LineB]) + { + lineB++; } - else + + if (startA < lineA || startB < lineB) { - // maybe deleted and/or inserted lines - int startA = lineA; - int startB = lineB; + // store a new difference-item + aItem = new Item(); + aItem.StartA = startA; + aItem.StartB = startB; + aItem.DeletedA = lineA - startA; + aItem.InsertedB = lineB - startB; + a.Add(aItem); + } // if + } // if + } // while - while (lineA < dataA.Length && (lineB >= dataB.Length || dataA.Modified[lineA])) - // while (LineA < DataA.Length && DataA.modified[LineA]) - lineA++; + result = new Item[a.Count]; + a.CopyTo(result); - while (lineB < dataB.Length && (lineA >= dataA.Length || dataB.Modified[lineB])) - // while (LineB < DataB.Length && DataB.modified[LineB]) - lineB++; + return result; + } - if ((startA < lineA) || (startB < lineB)) - { - // store a new difference-item - aItem = new Item(); - aItem.StartA = startA; - aItem.StartB = startB; - aItem.DeletedA = lineA - startA; - aItem.InsertedB = lineB - startB; - a.Add(aItem); - } // if - } // if - } // while + /// + /// Data on one input file being compared. + /// + internal class DiffData + { + /// Buffer of numbers that will be compared. + internal int[] Data; - result = new Item[a.Count]; - a.CopyTo(result); + /// Number of elements (lines). + internal int Length; - return (result); - } + /// + /// Array of booleans that flag for modified data. + /// This is the result of the diff. + /// This means deletedA in the first Data or inserted in the second Data. + /// + internal bool[] Modified; + + /// + /// Initialize the Diff-Data buffer. + /// + /// reference to the buffer + internal DiffData(int[] initData) + { + Data = initData; + Length = initData.Length; + Modified = new bool[Length + 2]; + } // DiffData + } // class DiffData + + /// details of one difference. + public struct Item + { + /// Start Line number in Data A. + public int StartA; + + /// Start Line number in Data B. + public int StartB; - } // class Diff + /// Number of changes in Data A. + public int DeletedA; + /// Number of changes in Data B. + public int InsertedB; + } // Item -} + /// + /// Shortest Middle Snake Return Data + /// + private struct Smsrd + { + internal int X, Y; + // internal int u, v; // 2002.09.20: no need for 2 points + } +} // class Diff diff --git a/src/Umbraco.Core/Strings/HtmlEncodedString.cs b/src/Umbraco.Core/Strings/HtmlEncodedString.cs index 16941cef484b..4477d5436cb7 100644 --- a/src/Umbraco.Core/Strings/HtmlEncodedString.cs +++ b/src/Umbraco.Core/Strings/HtmlEncodedString.cs @@ -1,33 +1,21 @@ -namespace Umbraco.Cms.Core.Strings -{ - /// - /// Represents an HTML-encoded string that should not be encoded again. - /// - public class HtmlEncodedString : IHtmlEncodedString - { - - private string _htmlString; +namespace Umbraco.Cms.Core.Strings; - /// Initializes a new instance of the class. - /// An HTML-encoded string that should not be encoded again. - public HtmlEncodedString(string value) - { - this._htmlString = value; - } +/// +/// Represents an HTML-encoded string that should not be encoded again. +/// +public class HtmlEncodedString : IHtmlEncodedString +{ + private readonly string _htmlString; - /// Returns an HTML-encoded string. - /// An HTML-encoded string. - public string ToHtmlString() - { - return this._htmlString; - } + /// Initializes a new instance of the class. + /// An HTML-encoded string that should not be encoded again. + public HtmlEncodedString(string value) => _htmlString = value; - /// Returns a string that represents the current object. - /// A string that represents the current object. - public override string ToString() - { - return this._htmlString; - } + /// Returns an HTML-encoded string. + /// An HTML-encoded string. + public string ToHtmlString() => _htmlString; - } + /// Returns a string that represents the current object. + /// A string that represents the current object. + public override string ToString() => _htmlString; } diff --git a/src/Umbraco.Core/Strings/IHtmlEncodedString.cs b/src/Umbraco.Core/Strings/IHtmlEncodedString.cs index b7c0c27d2da3..bf94f834ad66 100644 --- a/src/Umbraco.Core/Strings/IHtmlEncodedString.cs +++ b/src/Umbraco.Core/Strings/IHtmlEncodedString.cs @@ -1,14 +1,13 @@ -namespace Umbraco.Cms.Core.Strings +namespace Umbraco.Cms.Core.Strings; + +/// +/// Represents an HTML-encoded string that should not be encoded again. +/// +public interface IHtmlEncodedString { /// - /// Represents an HTML-encoded string that should not be encoded again. + /// Returns an HTML-encoded string. /// - public interface IHtmlEncodedString - { - /// - /// Returns an HTML-encoded string. - /// - /// An HTML-encoded string. - string? ToHtmlString(); - } + /// An HTML-encoded string. + string? ToHtmlString(); } diff --git a/src/Umbraco.Core/Strings/IShortStringHelper.cs b/src/Umbraco.Core/Strings/IShortStringHelper.cs index a436758d9acb..b14cf36ef562 100644 --- a/src/Umbraco.Core/Strings/IShortStringHelper.cs +++ b/src/Umbraco.Core/Strings/IShortStringHelper.cs @@ -1,114 +1,129 @@ -namespace Umbraco.Cms.Core.Strings +namespace Umbraco.Cms.Core.Strings; + +/// +/// Provides string functions for short strings such as aliases or URL segments. +/// +/// Not necessarily optimized to work on large bodies of text. +public interface IShortStringHelper { /// - /// Provides string functions for short strings such as aliases or URL segments. + /// Cleans a string to produce a string that can safely be used in an alias. /// - /// Not necessarily optimized to work on large bodies of text. - public interface IShortStringHelper - { - /// - /// Cleans a string to produce a string that can safely be used in an alias. - /// - /// The text to filter. - /// The safe alias. - /// - /// The string will be cleaned in the context of the IShortStringHelper default culture. - /// A safe alias is [a-z][a-zA-Z0-9_]* although legacy will also accept '-', and '_' at the beginning. - /// - string CleanStringForSafeAlias(string text); + /// The text to filter. + /// The safe alias. + /// + /// The string will be cleaned in the context of the IShortStringHelper default culture. + /// A safe alias is [a-z][a-zA-Z0-9_]* although legacy will also accept '-', and '_' at the beginning. + /// + string CleanStringForSafeAlias(string text); - /// - /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an alias. - /// - /// The text to filter. - /// The culture. - /// The safe alias. - string CleanStringForSafeAlias(string text, string culture); + /// + /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an alias. + /// + /// The text to filter. + /// The culture. + /// The safe alias. + string CleanStringForSafeAlias(string text, string culture); - /// - /// Cleans a string to produce a string that can safely be used in an URL segment. - /// - /// The text to filter. - /// The safe URL segment. - /// The string will be cleaned in the context of the IShortStringHelper default culture. - string CleanStringForUrlSegment(string text); + /// + /// Cleans a string to produce a string that can safely be used in an URL segment. + /// + /// The text to filter. + /// The safe URL segment. + /// The string will be cleaned in the context of the IShortStringHelper default culture. + string CleanStringForUrlSegment(string text); - /// - /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an URL segment. - /// - /// The text to filter. - /// The culture. - /// The safe URL segment. - string CleanStringForUrlSegment(string text, string? culture); + /// + /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an URL + /// segment. + /// + /// The text to filter. + /// The culture. + /// The safe URL segment. + string CleanStringForUrlSegment(string text, string? culture); - /// - /// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a filename, - /// both internally (on disk) and externally (as a URL). - /// - /// The text to filter. - /// The safe filename. - /// Legacy says this was used to "overcome an issue when Umbraco is used in IE in an intranet environment" but that issue is not documented. - string CleanStringForSafeFileName(string text); + /// + /// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a + /// filename, + /// both internally (on disk) and externally (as a URL). + /// + /// The text to filter. + /// The safe filename. + /// + /// Legacy says this was used to "overcome an issue when Umbraco is used in IE in an intranet environment" but + /// that issue is not documented. + /// + string CleanStringForSafeFileName(string text); - /// - /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used as a filename, - /// both internally (on disk) and externally (as a URL). - /// - /// The text to filter. - /// The culture. - /// The safe filename. - /// Legacy says this was used to "overcome an issue when Umbraco is used in IE in an intranet environment" but that issue is not documented. - string CleanStringForSafeFileName(string text, string culture); + /// + /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used as a filename, + /// both internally (on disk) and externally (as a URL). + /// + /// The text to filter. + /// The culture. + /// The safe filename. + /// + /// Legacy says this was used to "overcome an issue when Umbraco is used in IE in an intranet environment" but + /// that issue is not documented. + /// + string CleanStringForSafeFileName(string text, string culture); - /// - /// Splits a pascal-cased string by inserting a separator in between each term. - /// - /// The text to split. - /// The separator. - /// The split string. - /// Supports Utf8 and Ascii strings, not Unicode strings. - string SplitPascalCasing(string text, char separator); + /// + /// Splits a pascal-cased string by inserting a separator in between each term. + /// + /// The text to split. + /// The separator. + /// The split string. + /// Supports Utf8 and Ascii strings, not Unicode strings. + string SplitPascalCasing(string text, char separator); - /// - /// Cleans a string. - /// - /// The text to clean. - /// A flag indicating the target casing and encoding of the string. By default, - /// strings are cleaned up to camelCase and Ascii. - /// The clean string. - /// The string is cleaned in the context of the IShortStringHelper default culture. - string CleanString(string text, CleanStringType stringType); + /// + /// Cleans a string. + /// + /// The text to clean. + /// + /// A flag indicating the target casing and encoding of the string. By default, + /// strings are cleaned up to camelCase and Ascii. + /// + /// The clean string. + /// The string is cleaned in the context of the IShortStringHelper default culture. + string CleanString(string text, CleanStringType stringType); - /// - /// Cleans a string, using a specified separator. - /// - /// The text to clean. - /// A flag indicating the target casing and encoding of the string. By default, - /// strings are cleaned up to camelCase and Ascii. - /// The separator. - /// The clean string. - /// The string is cleaned in the context of the IShortStringHelper default culture. - string CleanString(string text, CleanStringType stringType, char separator); + /// + /// Cleans a string, using a specified separator. + /// + /// The text to clean. + /// + /// A flag indicating the target casing and encoding of the string. By default, + /// strings are cleaned up to camelCase and Ascii. + /// + /// The separator. + /// The clean string. + /// The string is cleaned in the context of the IShortStringHelper default culture. + string CleanString(string text, CleanStringType stringType, char separator); - /// - /// Cleans a string in the context of a specified culture. - /// - /// The text to clean. - /// A flag indicating the target casing and encoding of the string. By default, - /// strings are cleaned up to camelCase and Ascii. - /// The culture. - /// The clean string. - string CleanString(string text, CleanStringType stringType, string culture); + /// + /// Cleans a string in the context of a specified culture. + /// + /// The text to clean. + /// + /// A flag indicating the target casing and encoding of the string. By default, + /// strings are cleaned up to camelCase and Ascii. + /// + /// The culture. + /// The clean string. + string CleanString(string text, CleanStringType stringType, string culture); - /// - /// Cleans a string in the context of a specified culture, using a specified separator. - /// - /// The text to clean. - /// A flag indicating the target casing and encoding of the string. By default, - /// strings are cleaned up to camelCase and Ascii. - /// The separator. - /// The culture. - /// The clean string. - string CleanString(string text, CleanStringType stringType, char separator, string culture); - } + /// + /// Cleans a string in the context of a specified culture, using a specified separator. + /// + /// The text to clean. + /// + /// A flag indicating the target casing and encoding of the string. By default, + /// strings are cleaned up to camelCase and Ascii. + /// + /// The separator. + /// The culture. + /// The clean string. + string CleanString(string text, CleanStringType stringType, char separator, string culture); } diff --git a/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs b/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs index 74d147173f08..41e5d1f8250c 100644 --- a/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs +++ b/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs @@ -1,26 +1,27 @@ using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Strings +namespace Umbraco.Cms.Core.Strings; + +/// +/// Provides URL segments for content. +/// +/// Url segments should comply with IETF RFCs regarding content, encoding, etc. +public interface IUrlSegmentProvider { /// - /// Provides URL segments for content. + /// Gets the URL segment for a specified content and culture. /// - /// Url segments should comply with IETF RFCs regarding content, encoding, etc. - public interface IUrlSegmentProvider - { - /// - /// Gets the URL segment for a specified content and culture. - /// - /// The content. - /// The culture. - /// The URL segment. - /// This is for when Umbraco is capable of managing more than one URL - /// per content, in 1-to-1 multilingual configurations. Then there would be one - /// URL per culture. - string? GetUrlSegment(IContentBase content, string? culture = null); + /// The content. + /// The culture. + /// The URL segment. + /// + /// This is for when Umbraco is capable of managing more than one URL + /// per content, in 1-to-1 multilingual configurations. Then there would be one + /// URL per culture. + /// + string? GetUrlSegment(IContentBase content, string? culture = null); - // TODO: For the 301 tracking, we need to add another extended interface to this so that - // the RedirectTrackingEventHandler can ask the IUrlSegmentProvider if the URL is changing. - // Currently the way it works is very hacky, see notes in: RedirectTrackingEventHandler.ContentService_Publishing - } + // TODO: For the 301 tracking, we need to add another extended interface to this so that + // the RedirectTrackingEventHandler can ask the IUrlSegmentProvider if the URL is changing. + // Currently the way it works is very hacky, see notes in: RedirectTrackingEventHandler.ContentService_Publishing } diff --git a/src/Umbraco.Core/Strings/PathUtility.cs b/src/Umbraco.Core/Strings/PathUtility.cs index bc88fa8bcaae..cab7127a6ef9 100644 --- a/src/Umbraco.Core/Strings/PathUtility.cs +++ b/src/Umbraco.Core/Strings/PathUtility.cs @@ -1,22 +1,29 @@ -namespace Umbraco.Cms.Core.Strings +namespace Umbraco.Cms.Core.Strings; + +public static class PathUtility { - public static class PathUtility + /// + /// Ensures that a path has `~/` as prefix + /// + /// + /// + public static string EnsurePathIsApplicationRootPrefixed(string path) { - - /// - /// Ensures that a path has `~/` as prefix - /// - /// - /// - public static string EnsurePathIsApplicationRootPrefixed(string path) + if (path.StartsWith("~/")) { - if (path.StartsWith("~/")) - return path; - if (path.StartsWith("/") == false && path.StartsWith("\\") == false) - path = string.Format("/{0}", path); - if (path.StartsWith("~") == false) - path = string.Format("~{0}", path); return path; } + + if (path.StartsWith("/") == false && path.StartsWith("\\") == false) + { + path = string.Format("/{0}", path); + } + + if (path.StartsWith("~") == false) + { + path = string.Format("~{0}", path); + } + + return path; } } diff --git a/src/Umbraco.Core/Strings/UrlSegmentProviderCollection.cs b/src/Umbraco.Core/Strings/UrlSegmentProviderCollection.cs index 551efc475a56..1e842fefd278 100644 --- a/src/Umbraco.Core/Strings/UrlSegmentProviderCollection.cs +++ b/src/Umbraco.Core/Strings/UrlSegmentProviderCollection.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Strings +namespace Umbraco.Cms.Core.Strings; + +public class UrlSegmentProviderCollection : BuilderCollectionBase { - public class UrlSegmentProviderCollection : BuilderCollectionBase + public UrlSegmentProviderCollection(Func> items) : base(items) { - public UrlSegmentProviderCollection(Func> items) : base(items) - { - } } } diff --git a/src/Umbraco.Core/Strings/UrlSegmentProviderCollectionBuilder.cs b/src/Umbraco.Core/Strings/UrlSegmentProviderCollectionBuilder.cs index 60504734f653..3cb8b9569238 100644 --- a/src/Umbraco.Core/Strings/UrlSegmentProviderCollectionBuilder.cs +++ b/src/Umbraco.Core/Strings/UrlSegmentProviderCollectionBuilder.cs @@ -1,9 +1,9 @@ using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Strings +namespace Umbraco.Cms.Core.Strings; + +public class UrlSegmentProviderCollectionBuilder : OrderedCollectionBuilderBase { - public class UrlSegmentProviderCollectionBuilder : OrderedCollectionBuilderBase - { - protected override UrlSegmentProviderCollectionBuilder This => this; - } + protected override UrlSegmentProviderCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs b/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs index 3f492a7b87ad..2a80e638658d 100644 --- a/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs +++ b/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs @@ -1,3627 +1,3629 @@ -using System; - -namespace Umbraco.Cms.Core.Strings +namespace Umbraco.Cms.Core.Strings; + +/// +/// Provides methods to convert Utf8 text to Ascii. +/// +/// +/// Tries to match characters such as accented eg "é" to Ascii equivalent eg "e". +/// Converts all "whitespace" characters to a single whitespace. +/// Removes all non-Utf8 (unicode) characters, so in fact it can sort-of "convert" Unicode to Ascii. +/// Replaces symbols with '?'. +/// +public static class Utf8ToAsciiConverter { /// - /// Provides methods to convert Utf8 text to Ascii. + /// Converts an Utf8 string into an Ascii string. /// - /// - /// Tries to match characters such as accented eg "é" to Ascii equivalent eg "e". - /// Converts all "whitespace" characters to a single whitespace. - /// Removes all non-Utf8 (unicode) characters, so in fact it can sort-of "convert" Unicode to Ascii. - /// Replaces symbols with '?'. - /// - public static class Utf8ToAsciiConverter + /// The text to convert. + /// The character to use to replace characters that cannot properly be converted. + /// The converted text. + public static string ToAsciiString(string text, char fail = '?') { - /// - /// Converts an Utf8 string into an Ascii string. - /// - /// The text to convert. - /// The character to use to replace characters that cannot properly be converted. - /// The converted text. - public static string ToAsciiString(string text, char fail = '?') - { - var input = text.ToCharArray(); + var input = text.ToCharArray(); - // this is faster although it uses more memory - // but... we should be filtering short strings only... + // this is faster although it uses more memory + // but... we should be filtering short strings only... - var output = new char[input.Length * 3]; // *3 because of things such as OE - var len = ToAscii(input, output, fail); - return new string(output, 0, len); + var output = new char[input.Length * 3]; // *3 because of things such as OE + var len = ToAscii(input, output, fail); + return new string(output, 0, len); - //var output = new StringBuilder(input.Length + 16); // default is 16, start with at least input length + little extra - //ToAscii(input, output); - //return output.ToString(); - } + //var output = new StringBuilder(input.Length + 16); // default is 16, start with at least input length + little extra + //ToAscii(input, output); + //return output.ToString(); + } + + /// + /// Converts an Utf8 string into an array of Ascii characters. + /// + /// The text to convert. + /// The character to use to replace characters that cannot properly be converted. + /// The converted text. + public static char[] ToAsciiCharArray(string text, char fail = '?') + { + var input = text.ToCharArray(); + + // this is faster although it uses more memory + // but... we should be filtering short strings only... + + var output = new char[input.Length * 3]; // *3 because of things such as OE + var len = ToAscii(input, output, fail); + var array = new char[len]; + Array.Copy(output, array, len); + return array; + + //var temp = new StringBuilder(input.Length + 16); // default is 16, start with at least input length + little extra + //ToAscii(input, temp); + //var output = new char[temp.Length]; + //temp.CopyTo(0, output, 0, temp.Length); + //return output; + } + + /// + /// Converts an array of Utf8 characters into an array of Ascii characters. + /// + /// The input array. + /// The output array. + /// The character to use to replace characters that cannot properly be converted. + /// The number of characters in the output array. + /// The caller must ensure that the output array is big enough. + /// The output array is not big enough. + private static int ToAscii(char[] input, char[] output, char fail = '?') + { + var opos = 0; - /// - /// Converts an Utf8 string into an array of Ascii characters. - /// - /// The text to convert. - /// The character to use to replace characters that cannot properly be converted. - /// The converted text. - public static char[] ToAsciiCharArray(string text, char fail = '?') + for (var ipos = 0; ipos < input.Length; ipos++) { - var input = text.ToCharArray(); - - // this is faster although it uses more memory - // but... we should be filtering short strings only... - - var output = new char[input.Length * 3]; // *3 because of things such as OE - var len = ToAscii(input, output, fail); - var array = new char[len]; - Array.Copy(output, array, len); - return array; - - //var temp = new StringBuilder(input.Length + 16); // default is 16, start with at least input length + little extra - //ToAscii(input, temp); - //var output = new char[temp.Length]; - //temp.CopyTo(0, output, 0, temp.Length); - //return output; + if (char.IsSurrogate(input[ipos])) // ignore high surrogate + { + ipos++; // and skip low surrogate + output[opos++] = fail; + } + else + { + ToAscii(input, ipos, output, ref opos, fail); + } } - /// - /// Converts an array of Utf8 characters into an array of Ascii characters. - /// - /// The input array. - /// The output array. - /// The character to use to replace characters that cannot properly be converted. - /// The number of characters in the output array. - /// The caller must ensure that the output array is big enough. - /// The output array is not big enough. - private static int ToAscii(char[] input, char[] output, char fail = '?') - { - var opos = 0; + return opos; + } - for (var ipos = 0; ipos < input.Length; ipos++) - if (char.IsSurrogate(input[ipos])) // ignore high surrogate - { - ipos++; // and skip low surrogate - output[opos++] = fail; - } - else - ToAscii(input, ipos, output, ref opos, fail); + //private static void ToAscii(char[] input, StringBuilder output) + //{ + // var chars = new char[5]; + + // for (var ipos = 0; ipos < input.Length; ipos++) + // { + // var opos = 0; + // if (char.IsSurrogate(input[ipos])) + // ipos++; + // else + // { + // ToAscii(input, ipos, chars, ref opos); + // output.Append(chars, 0, opos); + // } + // } + //} - return opos; - } + /// + /// Converts the character at position in input array of Utf8 characters + /// + /// and writes the converted value to output array of Ascii characters at position + /// , + /// and increments that position accordingly. + /// + /// The input array. + /// The input position. + /// The output array. + /// The output position. + /// The character to use to replace characters that cannot properly be converted. + /// + /// Adapted from various sources on the 'net including Lucene.Net.Analysis.ASCIIFoldingFilter. + /// Input should contain Utf8 characters exclusively and NOT Unicode. + /// Removes controls, normalizes whitespaces, replaces symbols by '?'. + /// + private static void ToAscii(char[] input, int ipos, char[] output, ref int opos, char fail = '?') + { + var c = input[ipos]; + + if (char.IsControl(c)) + { + // Control characters are non-printing and formatting characters, such as ACK, BEL, CR, FF, LF, and VT. + // The Unicode standard assigns the following code points to control characters: from \U0000 to \U001F, + // \U007F, and from \U0080 to \U009F. According to the Unicode standard, these values are to be + // interpreted as control characters unless their use is otherwise defined by an application. Valid + // control characters are members of the UnicodeCategory.Control category. - //private static void ToAscii(char[] input, StringBuilder output) + // we don't want them + } + //else if (char.IsSeparator(c)) //{ - // var chars = new char[5]; - - // for (var ipos = 0; ipos < input.Length; ipos++) - // { - // var opos = 0; - // if (char.IsSurrogate(input[ipos])) - // ipos++; - // else - // { - // ToAscii(input, ipos, chars, ref opos); - // output.Append(chars, 0, opos); - // } - // } + // // The Unicode standard recognizes three subcategories of separators: + // // - Space separators (the UnicodeCategory.SpaceSeparator category), which includes characters such as \u0020. + // // - Line separators (the UnicodeCategory.LineSeparator category), which includes \u2028. + // // - Paragraph separators (the UnicodeCategory.ParagraphSeparator category), which includes \u2029. + // // + // // Note: The Unicode standard classifies the characters \u000A (LF), \u000C (FF), and \u000A (CR) as control + // // characters (members of the UnicodeCategory.Control category), not as separator characters. + + // // better do it via WhiteSpace //} - - /// - /// Converts the character at position in input array of Utf8 characters - /// and writes the converted value to output array of Ascii characters at position , - /// and increments that position accordingly. - /// - /// The input array. - /// The input position. - /// The output array. - /// The output position. - /// The character to use to replace characters that cannot properly be converted. - /// - /// Adapted from various sources on the 'net including Lucene.Net.Analysis.ASCIIFoldingFilter. - /// Input should contain Utf8 characters exclusively and NOT Unicode. - /// Removes controls, normalizes whitespaces, replaces symbols by '?'. - /// - private static void ToAscii(char[] input, int ipos, char[] output, ref int opos, char fail = '?') + else if (char.IsWhiteSpace(c)) { - var c = input[ipos]; - - if (char.IsControl(c)) + // White space characters are the following Unicode characters: + // - Members of the SpaceSeparator category, which includes the characters SPACE (U+0020), + // OGHAM SPACE MARK (U+1680), MONGOLIAN VOWEL SEPARATOR (U+180E), EN QUAD (U+2000), EM QUAD (U+2001), + // EN SPACE (U+2002), EM SPACE (U+2003), THREE-PER-EM SPACE (U+2004), FOUR-PER-EM SPACE (U+2005), + // SIX-PER-EM SPACE (U+2006), FIGURE SPACE (U+2007), PUNCTUATION SPACE (U+2008), THIN SPACE (U+2009), + // HAIR SPACE (U+200A), NARROW NO-BREAK SPACE (U+202F), MEDIUM MATHEMATICAL SPACE (U+205F), + // and IDEOGRAPHIC SPACE (U+3000). + // - Members of the LineSeparator category, which consists solely of the LINE SEPARATOR character (U+2028). + // - Members of the ParagraphSeparator category, which consists solely of the PARAGRAPH SEPARATOR character (U+2029). + // - The characters CHARACTER TABULATION (U+0009), LINE FEED (U+000A), LINE TABULATION (U+000B), + // FORM FEED (U+000C), CARRIAGE RETURN (U+000D), NEXT LINE (U+0085), and NO-BREAK SPACE (U+00A0). + + // make it a whitespace + output[opos++] = ' '; + } + else if (c < '\u0080') + { + // safe + output[opos++] = c; + } + else + { + switch (c) { - // Control characters are non-printing and formatting characters, such as ACK, BEL, CR, FF, LF, and VT. - // The Unicode standard assigns the following code points to control characters: from \U0000 to \U001F, - // \U007F, and from \U0080 to \U009F. According to the Unicode standard, these values are to be - // interpreted as control characters unless their use is otherwise defined by an application. Valid - // control characters are members of the UnicodeCategory.Control category. + case '\u00C0': + // À [LATIN CAPITAL LETTER A WITH GRAVE] + case '\u00C1': + // � [LATIN CAPITAL LETTER A WITH ACUTE] + case '\u00C2': + //  [LATIN CAPITAL LETTER A WITH CIRCUMFLEX] + case '\u00C3': + // à [LATIN CAPITAL LETTER A WITH TILDE] + case '\u00C4': + // Ä [LATIN CAPITAL LETTER A WITH DIAERESIS] + case '\u00C5': + // Ã… [LATIN CAPITAL LETTER A WITH RING ABOVE] + case '\u0100': + // Ä€ [LATIN CAPITAL LETTER A WITH MACRON] + case '\u0102': + // Ä‚ [LATIN CAPITAL LETTER A WITH BREVE] + case '\u0104': + // Ä„ [LATIN CAPITAL LETTER A WITH OGONEK] + case '\u018F': + // � http://en.wikipedia.org/wiki/Schwa [LATIN CAPITAL LETTER SCHWA] + case '\u01CD': + // � [LATIN CAPITAL LETTER A WITH CARON] + case '\u01DE': + // Çž [LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON] + case '\u01E0': + // Ç  [LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON] + case '\u01FA': + // Ǻ [LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE] + case '\u0200': + // È€ [LATIN CAPITAL LETTER A WITH DOUBLE GRAVE] + case '\u0202': + // È‚ [LATIN CAPITAL LETTER A WITH INVERTED BREVE] + case '\u0226': + // Ȧ [LATIN CAPITAL LETTER A WITH DOT ABOVE] + case '\u023A': + // Ⱥ [LATIN CAPITAL LETTER A WITH STROKE] + case '\u1D00': + // á´€ [LATIN LETTER SMALL CAPITAL A] + case '\u1E00': + // Ḁ [LATIN CAPITAL LETTER A WITH RING BELOW] + case '\u1EA0': + // Ạ [LATIN CAPITAL LETTER A WITH DOT BELOW] + case '\u1EA2': + // Ả [LATIN CAPITAL LETTER A WITH HOOK ABOVE] + case '\u1EA4': + // Ấ [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE] + case '\u1EA6': + // Ầ [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE] + case '\u1EA8': + // Ẩ [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE] + case '\u1EAA': + // Ẫ [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE] + case '\u1EAC': + // Ậ [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW] + case '\u1EAE': + // Ắ [LATIN CAPITAL LETTER A WITH BREVE AND ACUTE] + case '\u1EB0': + // Ằ [LATIN CAPITAL LETTER A WITH BREVE AND GRAVE] + case '\u1EB2': + // Ẳ [LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE] + case '\u1EB4': + // Ẵ [LATIN CAPITAL LETTER A WITH BREVE AND TILDE] + case '\u1EB6': + // Ặ [LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW] + case '\u24B6': + // â’¶ [CIRCLED LATIN CAPITAL LETTER A] + case '\uFF21': // A [FULLWIDTH LATIN CAPITAL LETTER A] + output[opos++] = 'A'; + break; - // we don't want them - } - //else if (char.IsSeparator(c)) - //{ - // // The Unicode standard recognizes three subcategories of separators: - // // - Space separators (the UnicodeCategory.SpaceSeparator category), which includes characters such as \u0020. - // // - Line separators (the UnicodeCategory.LineSeparator category), which includes \u2028. - // // - Paragraph separators (the UnicodeCategory.ParagraphSeparator category), which includes \u2029. - // // - // // Note: The Unicode standard classifies the characters \u000A (LF), \u000C (FF), and \u000A (CR) as control - // // characters (members of the UnicodeCategory.Control category), not as separator characters. - - // // better do it via WhiteSpace - //} - else if (char.IsWhiteSpace(c)) - { - // White space characters are the following Unicode characters: - // - Members of the SpaceSeparator category, which includes the characters SPACE (U+0020), - // OGHAM SPACE MARK (U+1680), MONGOLIAN VOWEL SEPARATOR (U+180E), EN QUAD (U+2000), EM QUAD (U+2001), - // EN SPACE (U+2002), EM SPACE (U+2003), THREE-PER-EM SPACE (U+2004), FOUR-PER-EM SPACE (U+2005), - // SIX-PER-EM SPACE (U+2006), FIGURE SPACE (U+2007), PUNCTUATION SPACE (U+2008), THIN SPACE (U+2009), - // HAIR SPACE (U+200A), NARROW NO-BREAK SPACE (U+202F), MEDIUM MATHEMATICAL SPACE (U+205F), - // and IDEOGRAPHIC SPACE (U+3000). - // - Members of the LineSeparator category, which consists solely of the LINE SEPARATOR character (U+2028). - // - Members of the ParagraphSeparator category, which consists solely of the PARAGRAPH SEPARATOR character (U+2029). - // - The characters CHARACTER TABULATION (U+0009), LINE FEED (U+000A), LINE TABULATION (U+000B), - // FORM FEED (U+000C), CARRIAGE RETURN (U+000D), NEXT LINE (U+0085), and NO-BREAK SPACE (U+00A0). - - // make it a whitespace - output[opos++] = ' '; - } - else if (c < '\u0080') - { - // safe - output[opos++] = c; - } - else - { - switch (c) - { - - case '\u00C0': - // À [LATIN CAPITAL LETTER A WITH GRAVE] - case '\u00C1': - // � [LATIN CAPITAL LETTER A WITH ACUTE] - case '\u00C2': - //  [LATIN CAPITAL LETTER A WITH CIRCUMFLEX] - case '\u00C3': - // à [LATIN CAPITAL LETTER A WITH TILDE] - case '\u00C4': - // Ä [LATIN CAPITAL LETTER A WITH DIAERESIS] - case '\u00C5': - // Ã… [LATIN CAPITAL LETTER A WITH RING ABOVE] - case '\u0100': - // Ä€ [LATIN CAPITAL LETTER A WITH MACRON] - case '\u0102': - // Ä‚ [LATIN CAPITAL LETTER A WITH BREVE] - case '\u0104': - // Ä„ [LATIN CAPITAL LETTER A WITH OGONEK] - case '\u018F': - // � http://en.wikipedia.org/wiki/Schwa [LATIN CAPITAL LETTER SCHWA] - case '\u01CD': - // � [LATIN CAPITAL LETTER A WITH CARON] - case '\u01DE': - // Çž [LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON] - case '\u01E0': - // Ç  [LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON] - case '\u01FA': - // Ǻ [LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE] - case '\u0200': - // È€ [LATIN CAPITAL LETTER A WITH DOUBLE GRAVE] - case '\u0202': - // È‚ [LATIN CAPITAL LETTER A WITH INVERTED BREVE] - case '\u0226': - // Ȧ [LATIN CAPITAL LETTER A WITH DOT ABOVE] - case '\u023A': - // Ⱥ [LATIN CAPITAL LETTER A WITH STROKE] - case '\u1D00': - // á´€ [LATIN LETTER SMALL CAPITAL A] - case '\u1E00': - // Ḁ [LATIN CAPITAL LETTER A WITH RING BELOW] - case '\u1EA0': - // Ạ [LATIN CAPITAL LETTER A WITH DOT BELOW] - case '\u1EA2': - // Ả [LATIN CAPITAL LETTER A WITH HOOK ABOVE] - case '\u1EA4': - // Ấ [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE] - case '\u1EA6': - // Ầ [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE] - case '\u1EA8': - // Ẩ [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE] - case '\u1EAA': - // Ẫ [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE] - case '\u1EAC': - // Ậ [LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW] - case '\u1EAE': - // Ắ [LATIN CAPITAL LETTER A WITH BREVE AND ACUTE] - case '\u1EB0': - // Ằ [LATIN CAPITAL LETTER A WITH BREVE AND GRAVE] - case '\u1EB2': - // Ẳ [LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE] - case '\u1EB4': - // Ẵ [LATIN CAPITAL LETTER A WITH BREVE AND TILDE] - case '\u1EB6': - // Ặ [LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW] - case '\u24B6': - // â’¶ [CIRCLED LATIN CAPITAL LETTER A] - case '\uFF21': // A [FULLWIDTH LATIN CAPITAL LETTER A] - output[opos++] = 'A'; - break; - - case '\u00E0': - // à [LATIN SMALL LETTER A WITH GRAVE] - case '\u00E1': - // á [LATIN SMALL LETTER A WITH ACUTE] - case '\u00E2': - // â [LATIN SMALL LETTER A WITH CIRCUMFLEX] - case '\u00E3': - // ã [LATIN SMALL LETTER A WITH TILDE] - case '\u00E4': - // ä [LATIN SMALL LETTER A WITH DIAERESIS] - case '\u00E5': - // Ã¥ [LATIN SMALL LETTER A WITH RING ABOVE] - case '\u0101': - // � [LATIN SMALL LETTER A WITH MACRON] - case '\u0103': - // ă [LATIN SMALL LETTER A WITH BREVE] - case '\u0105': - // Ä… [LATIN SMALL LETTER A WITH OGONEK] - case '\u01CE': - // ÇŽ [LATIN SMALL LETTER A WITH CARON] - case '\u01DF': - // ÇŸ [LATIN SMALL LETTER A WITH DIAERESIS AND MACRON] - case '\u01E1': - // Ç¡ [LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON] - case '\u01FB': - // Ç» [LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE] - case '\u0201': - // � [LATIN SMALL LETTER A WITH DOUBLE GRAVE] - case '\u0203': - // ȃ [LATIN SMALL LETTER A WITH INVERTED BREVE] - case '\u0227': - // ȧ [LATIN SMALL LETTER A WITH DOT ABOVE] - case '\u0250': - // � [LATIN SMALL LETTER TURNED A] - case '\u0259': - // É™ [LATIN SMALL LETTER SCHWA] - case '\u025A': - // Éš [LATIN SMALL LETTER SCHWA WITH HOOK] - case '\u1D8F': - // � [LATIN SMALL LETTER A WITH RETROFLEX HOOK] - case '\u1D95': - // ᶕ [LATIN SMALL LETTER SCHWA WITH RETROFLEX HOOK] - case '\u1E01': - // ạ [LATIN SMALL LETTER A WITH RING BELOW] - case '\u1E9A': - // ả [LATIN SMALL LETTER A WITH RIGHT HALF RING] - case '\u1EA1': - // ạ [LATIN SMALL LETTER A WITH DOT BELOW] - case '\u1EA3': - // ả [LATIN SMALL LETTER A WITH HOOK ABOVE] - case '\u1EA5': - // ấ [LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE] - case '\u1EA7': - // ầ [LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE] - case '\u1EA9': - // ẩ [LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE] - case '\u1EAB': - // ẫ [LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE] - case '\u1EAD': - // ậ [LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW] - case '\u1EAF': - // ắ [LATIN SMALL LETTER A WITH BREVE AND ACUTE] - case '\u1EB1': - // ằ [LATIN SMALL LETTER A WITH BREVE AND GRAVE] - case '\u1EB3': - // ẳ [LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE] - case '\u1EB5': - // ẵ [LATIN SMALL LETTER A WITH BREVE AND TILDE] - case '\u1EB7': - // ặ [LATIN SMALL LETTER A WITH BREVE AND DOT BELOW] - case '\u2090': - // � [LATIN SUBSCRIPT SMALL LETTER A] - case '\u2094': - // �? [LATIN SUBSCRIPT SMALL LETTER SCHWA] - case '\u24D0': - // � [CIRCLED LATIN SMALL LETTER A] - case '\u2C65': - // â±¥ [LATIN SMALL LETTER A WITH STROKE] - case '\u2C6F': - // Ɐ [LATIN CAPITAL LETTER TURNED A] - case '\uFF41': // � [FULLWIDTH LATIN SMALL LETTER A] - output[opos++] = 'a'; - break; - - case '\uA732': // Ꜳ [LATIN CAPITAL LETTER AA] - output[opos++] = 'A'; - output[opos++] = 'A'; - break; - - case '\u00C6': - // Æ [LATIN CAPITAL LETTER AE] - case '\u01E2': - // Ç¢ [LATIN CAPITAL LETTER AE WITH MACRON] - case '\u01FC': - // Ǽ [LATIN CAPITAL LETTER AE WITH ACUTE] - case '\u1D01': // á´� [LATIN LETTER SMALL CAPITAL AE] - output[opos++] = 'A'; - output[opos++] = 'E'; - break; - - case '\uA734': // Ꜵ [LATIN CAPITAL LETTER AO] - output[opos++] = 'A'; - output[opos++] = 'O'; - break; - - case '\uA736': // Ꜷ [LATIN CAPITAL LETTER AU] - output[opos++] = 'A'; - output[opos++] = 'U'; - break; - - case '\uA738': - // Ꜹ [LATIN CAPITAL LETTER AV] - case '\uA73A': // Ꜻ [LATIN CAPITAL LETTER AV WITH HORIZONTAL BAR] - output[opos++] = 'A'; - output[opos++] = 'V'; - break; - - case '\uA73C': // Ꜽ [LATIN CAPITAL LETTER AY] - output[opos++] = 'A'; - output[opos++] = 'Y'; - break; - - case '\u249C': // â’œ [PARENTHESIZED LATIN SMALL LETTER A] - output[opos++] = '('; - output[opos++] = 'a'; - output[opos++] = ')'; - break; - - case '\uA733': // ꜳ [LATIN SMALL LETTER AA] - output[opos++] = 'a'; - output[opos++] = 'a'; - break; - - case '\u00E6': - // æ [LATIN SMALL LETTER AE] - case '\u01E3': - // Ç£ [LATIN SMALL LETTER AE WITH MACRON] - case '\u01FD': - // ǽ [LATIN SMALL LETTER AE WITH ACUTE] - case '\u1D02': // á´‚ [LATIN SMALL LETTER TURNED AE] - output[opos++] = 'a'; - output[opos++] = 'e'; - break; - - case '\uA735': // ꜵ [LATIN SMALL LETTER AO] - output[opos++] = 'a'; - output[opos++] = 'o'; - break; - - case '\uA737': // ꜷ [LATIN SMALL LETTER AU] - output[opos++] = 'a'; - output[opos++] = 'u'; - break; - - case '\uA739': - // ꜹ [LATIN SMALL LETTER AV] - case '\uA73B': // ꜻ [LATIN SMALL LETTER AV WITH HORIZONTAL BAR] - output[opos++] = 'a'; - output[opos++] = 'v'; - break; - - case '\uA73D': // ꜽ [LATIN SMALL LETTER AY] - output[opos++] = 'a'; - output[opos++] = 'y'; - break; - - case '\u0181': - // � [LATIN CAPITAL LETTER B WITH HOOK] - case '\u0182': - // Æ‚ [LATIN CAPITAL LETTER B WITH TOPBAR] - case '\u0243': - // Ƀ [LATIN CAPITAL LETTER B WITH STROKE] - case '\u0299': - // Ê™ [LATIN LETTER SMALL CAPITAL B] - case '\u1D03': - // á´ƒ [LATIN LETTER SMALL CAPITAL BARRED B] - case '\u1E02': - // Ḃ [LATIN CAPITAL LETTER B WITH DOT ABOVE] - case '\u1E04': - // Ḅ [LATIN CAPITAL LETTER B WITH DOT BELOW] - case '\u1E06': - // Ḇ [LATIN CAPITAL LETTER B WITH LINE BELOW] - case '\u24B7': - // â’· [CIRCLED LATIN CAPITAL LETTER B] - case '\uFF22': // ï¼¢ [FULLWIDTH LATIN CAPITAL LETTER B] - output[opos++] = 'B'; - break; - - case '\u0180': - // Æ€ [LATIN SMALL LETTER B WITH STROKE] - case '\u0183': - // ƃ [LATIN SMALL LETTER B WITH TOPBAR] - case '\u0253': - // É“ [LATIN SMALL LETTER B WITH HOOK] - case '\u1D6C': - // ᵬ [LATIN SMALL LETTER B WITH MIDDLE TILDE] - case '\u1D80': - // ᶀ [LATIN SMALL LETTER B WITH PALATAL HOOK] - case '\u1E03': - // ḃ [LATIN SMALL LETTER B WITH DOT ABOVE] - case '\u1E05': - // ḅ [LATIN SMALL LETTER B WITH DOT BELOW] - case '\u1E07': - // ḇ [LATIN SMALL LETTER B WITH LINE BELOW] - case '\u24D1': - // â“‘ [CIRCLED LATIN SMALL LETTER B] - case '\uFF42': // b [FULLWIDTH LATIN SMALL LETTER B] - output[opos++] = 'b'; - break; - - case '\u249D': // â’� [PARENTHESIZED LATIN SMALL LETTER B] - output[opos++] = '('; - output[opos++] = 'b'; - output[opos++] = ')'; - break; - - case '\u00C7': - // Ç [LATIN CAPITAL LETTER C WITH CEDILLA] - case '\u0106': - // Ć [LATIN CAPITAL LETTER C WITH ACUTE] - case '\u0108': - // Ĉ [LATIN CAPITAL LETTER C WITH CIRCUMFLEX] - case '\u010A': - // ÄŠ [LATIN CAPITAL LETTER C WITH DOT ABOVE] - case '\u010C': - // ÄŒ [LATIN CAPITAL LETTER C WITH CARON] - case '\u0187': - // Ƈ [LATIN CAPITAL LETTER C WITH HOOK] - case '\u023B': - // È» [LATIN CAPITAL LETTER C WITH STROKE] - case '\u0297': - // Ê— [LATIN LETTER STRETCHED C] - case '\u1D04': - // á´„ [LATIN LETTER SMALL CAPITAL C] - case '\u1E08': - // Ḉ [LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE] - case '\u24B8': - // â’¸ [CIRCLED LATIN CAPITAL LETTER C] - case '\uFF23': // ï¼£ [FULLWIDTH LATIN CAPITAL LETTER C] - output[opos++] = 'C'; - break; - - case '\u00E7': - // ç [LATIN SMALL LETTER C WITH CEDILLA] - case '\u0107': - // ć [LATIN SMALL LETTER C WITH ACUTE] - case '\u0109': - // ĉ [LATIN SMALL LETTER C WITH CIRCUMFLEX] - case '\u010B': - // Ä‹ [LATIN SMALL LETTER C WITH DOT ABOVE] - case '\u010D': - // � [LATIN SMALL LETTER C WITH CARON] - case '\u0188': - // ƈ [LATIN SMALL LETTER C WITH HOOK] - case '\u023C': - // ȼ [LATIN SMALL LETTER C WITH STROKE] - case '\u0255': - // É• [LATIN SMALL LETTER C WITH CURL] - case '\u1E09': - // ḉ [LATIN SMALL LETTER C WITH CEDILLA AND ACUTE] - case '\u2184': - // ↄ [LATIN SMALL LETTER REVERSED C] - case '\u24D2': - // â“’ [CIRCLED LATIN SMALL LETTER C] - case '\uA73E': - // Ꜿ [LATIN CAPITAL LETTER REVERSED C WITH DOT] - case '\uA73F': - // ꜿ [LATIN SMALL LETTER REVERSED C WITH DOT] - case '\uFF43': // c [FULLWIDTH LATIN SMALL LETTER C] - output[opos++] = 'c'; - break; - - case '\u249E': // â’ž [PARENTHESIZED LATIN SMALL LETTER C] - output[opos++] = '('; - output[opos++] = 'c'; - output[opos++] = ')'; - break; - - case '\u00D0': - // � [LATIN CAPITAL LETTER ETH] - case '\u010E': - // ÄŽ [LATIN CAPITAL LETTER D WITH CARON] - case '\u0110': - // � [LATIN CAPITAL LETTER D WITH STROKE] - case '\u0189': - // Ɖ [LATIN CAPITAL LETTER AFRICAN D] - case '\u018A': - // ÆŠ [LATIN CAPITAL LETTER D WITH HOOK] - case '\u018B': - // Æ‹ [LATIN CAPITAL LETTER D WITH TOPBAR] - case '\u1D05': - // á´… [LATIN LETTER SMALL CAPITAL D] - case '\u1D06': - // á´† [LATIN LETTER SMALL CAPITAL ETH] - case '\u1E0A': - // Ḋ [LATIN CAPITAL LETTER D WITH DOT ABOVE] - case '\u1E0C': - // Ḍ [LATIN CAPITAL LETTER D WITH DOT BELOW] - case '\u1E0E': - // Ḏ [LATIN CAPITAL LETTER D WITH LINE BELOW] - case '\u1E10': - // � [LATIN CAPITAL LETTER D WITH CEDILLA] - case '\u1E12': - // Ḓ [LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW] - case '\u24B9': - // â’¹ [CIRCLED LATIN CAPITAL LETTER D] - case '\uA779': - // � [LATIN CAPITAL LETTER INSULAR D] - case '\uFF24': // D [FULLWIDTH LATIN CAPITAL LETTER D] - output[opos++] = 'D'; - break; - - case '\u00F0': - // ð [LATIN SMALL LETTER ETH] - case '\u010F': - // � [LATIN SMALL LETTER D WITH CARON] - case '\u0111': - // Ä‘ [LATIN SMALL LETTER D WITH STROKE] - case '\u018C': - // ÆŒ [LATIN SMALL LETTER D WITH TOPBAR] - case '\u0221': - // È¡ [LATIN SMALL LETTER D WITH CURL] - case '\u0256': - // É– [LATIN SMALL LETTER D WITH TAIL] - case '\u0257': - // É— [LATIN SMALL LETTER D WITH HOOK] - case '\u1D6D': - // áµ­ [LATIN SMALL LETTER D WITH MIDDLE TILDE] - case '\u1D81': - // � [LATIN SMALL LETTER D WITH PALATAL HOOK] - case '\u1D91': - // ᶑ [LATIN SMALL LETTER D WITH HOOK AND TAIL] - case '\u1E0B': - // ḋ [LATIN SMALL LETTER D WITH DOT ABOVE] - case '\u1E0D': - // � [LATIN SMALL LETTER D WITH DOT BELOW] - case '\u1E0F': - // � [LATIN SMALL LETTER D WITH LINE BELOW] - case '\u1E11': - // ḑ [LATIN SMALL LETTER D WITH CEDILLA] - case '\u1E13': - // ḓ [LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW] - case '\u24D3': - // â““ [CIRCLED LATIN SMALL LETTER D] - case '\uA77A': - // � [LATIN SMALL LETTER INSULAR D] - case '\uFF44': // d [FULLWIDTH LATIN SMALL LETTER D] - output[opos++] = 'd'; - break; - - case '\u01C4': - // Ç„ [LATIN CAPITAL LETTER DZ WITH CARON] - case '\u01F1': // DZ [LATIN CAPITAL LETTER DZ] - output[opos++] = 'D'; - output[opos++] = 'Z'; - break; - - case '\u01C5': - // Ç… [LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON] - case '\u01F2': // Dz [LATIN CAPITAL LETTER D WITH SMALL LETTER Z] - output[opos++] = 'D'; - output[opos++] = 'z'; - break; - - case '\u249F': // â’Ÿ [PARENTHESIZED LATIN SMALL LETTER D] - output[opos++] = '('; - output[opos++] = 'd'; - output[opos++] = ')'; - break; - - case '\u0238': // ȸ [LATIN SMALL LETTER DB DIGRAPH] - output[opos++] = 'd'; - output[opos++] = 'b'; - break; - - case '\u01C6': - // dž [LATIN SMALL LETTER DZ WITH CARON] - case '\u01F3': - // dz [LATIN SMALL LETTER DZ] - case '\u02A3': - // Ê£ [LATIN SMALL LETTER DZ DIGRAPH] - case '\u02A5': // Ê¥ [LATIN SMALL LETTER DZ DIGRAPH WITH CURL] - output[opos++] = 'd'; - output[opos++] = 'z'; - break; - - case '\u00C8': - // È [LATIN CAPITAL LETTER E WITH GRAVE] - case '\u00C9': - // É [LATIN CAPITAL LETTER E WITH ACUTE] - case '\u00CA': - // Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX] - case '\u00CB': - // Ë [LATIN CAPITAL LETTER E WITH DIAERESIS] - case '\u0112': - // Ä’ [LATIN CAPITAL LETTER E WITH MACRON] - case '\u0114': - // �? [LATIN CAPITAL LETTER E WITH BREVE] - case '\u0116': - // Ä– [LATIN CAPITAL LETTER E WITH DOT ABOVE] - case '\u0118': - // Ę [LATIN CAPITAL LETTER E WITH OGONEK] - case '\u011A': - // Äš [LATIN CAPITAL LETTER E WITH CARON] - case '\u018E': - // ÆŽ [LATIN CAPITAL LETTER REVERSED E] - case '\u0190': - // � [LATIN CAPITAL LETTER OPEN E] - case '\u0204': - // È„ [LATIN CAPITAL LETTER E WITH DOUBLE GRAVE] - case '\u0206': - // Ȇ [LATIN CAPITAL LETTER E WITH INVERTED BREVE] - case '\u0228': - // Ȩ [LATIN CAPITAL LETTER E WITH CEDILLA] - case '\u0246': - // Ɇ [LATIN CAPITAL LETTER E WITH STROKE] - case '\u1D07': - // á´‡ [LATIN LETTER SMALL CAPITAL E] - case '\u1E14': - // �? [LATIN CAPITAL LETTER E WITH MACRON AND GRAVE] - case '\u1E16': - // Ḗ [LATIN CAPITAL LETTER E WITH MACRON AND ACUTE] - case '\u1E18': - // Ḙ [LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW] - case '\u1E1A': - // Ḛ [LATIN CAPITAL LETTER E WITH TILDE BELOW] - case '\u1E1C': - // Ḝ [LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE] - case '\u1EB8': - // Ẹ [LATIN CAPITAL LETTER E WITH DOT BELOW] - case '\u1EBA': - // Ẻ [LATIN CAPITAL LETTER E WITH HOOK ABOVE] - case '\u1EBC': - // Ẽ [LATIN CAPITAL LETTER E WITH TILDE] - case '\u1EBE': - // Ế [LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE] - case '\u1EC0': - // Ề [LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE] - case '\u1EC2': - // Ể [LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE] - case '\u1EC4': - // Ễ [LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE] - case '\u1EC6': - // Ệ [LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW] - case '\u24BA': - // â’º [CIRCLED LATIN CAPITAL LETTER E] - case '\u2C7B': - // â±» [LATIN LETTER SMALL CAPITAL TURNED E] - case '\uFF25': // ï¼¥ [FULLWIDTH LATIN CAPITAL LETTER E] - output[opos++] = 'E'; - break; - - case '\u00E8': - // è [LATIN SMALL LETTER E WITH GRAVE] - case '\u00E9': - // é [LATIN SMALL LETTER E WITH ACUTE] - case '\u00EA': - // ê [LATIN SMALL LETTER E WITH CIRCUMFLEX] - case '\u00EB': - // ë [LATIN SMALL LETTER E WITH DIAERESIS] - case '\u0113': - // Ä“ [LATIN SMALL LETTER E WITH MACRON] - case '\u0115': - // Ä• [LATIN SMALL LETTER E WITH BREVE] - case '\u0117': - // Ä— [LATIN SMALL LETTER E WITH DOT ABOVE] - case '\u0119': - // Ä™ [LATIN SMALL LETTER E WITH OGONEK] - case '\u011B': - // Ä› [LATIN SMALL LETTER E WITH CARON] - case '\u01DD': - // � [LATIN SMALL LETTER TURNED E] - case '\u0205': - // È… [LATIN SMALL LETTER E WITH DOUBLE GRAVE] - case '\u0207': - // ȇ [LATIN SMALL LETTER E WITH INVERTED BREVE] - case '\u0229': - // È© [LATIN SMALL LETTER E WITH CEDILLA] - case '\u0247': - // ɇ [LATIN SMALL LETTER E WITH STROKE] - case '\u0258': - // ɘ [LATIN SMALL LETTER REVERSED E] - case '\u025B': - // É› [LATIN SMALL LETTER OPEN E] - case '\u025C': - // Éœ [LATIN SMALL LETTER REVERSED OPEN E] - case '\u025D': - // � [LATIN SMALL LETTER REVERSED OPEN E WITH HOOK] - case '\u025E': - // Éž [LATIN SMALL LETTER CLOSED REVERSED OPEN E] - case '\u029A': - // Êš [LATIN SMALL LETTER CLOSED OPEN E] - case '\u1D08': - // á´ˆ [LATIN SMALL LETTER TURNED OPEN E] - case '\u1D92': - // ᶒ [LATIN SMALL LETTER E WITH RETROFLEX HOOK] - case '\u1D93': - // ᶓ [LATIN SMALL LETTER OPEN E WITH RETROFLEX HOOK] - case '\u1D94': - // �? [LATIN SMALL LETTER REVERSED OPEN E WITH RETROFLEX HOOK] - case '\u1E15': - // ḕ [LATIN SMALL LETTER E WITH MACRON AND GRAVE] - case '\u1E17': - // ḗ [LATIN SMALL LETTER E WITH MACRON AND ACUTE] - case '\u1E19': - // ḙ [LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW] - case '\u1E1B': - // ḛ [LATIN SMALL LETTER E WITH TILDE BELOW] - case '\u1E1D': - // � [LATIN SMALL LETTER E WITH CEDILLA AND BREVE] - case '\u1EB9': - // ẹ [LATIN SMALL LETTER E WITH DOT BELOW] - case '\u1EBB': - // ẻ [LATIN SMALL LETTER E WITH HOOK ABOVE] - case '\u1EBD': - // ẽ [LATIN SMALL LETTER E WITH TILDE] - case '\u1EBF': - // ế [LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE] - case '\u1EC1': - // � [LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE] - case '\u1EC3': - // ể [LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE] - case '\u1EC5': - // á»… [LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE] - case '\u1EC7': - // ệ [LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW] - case '\u2091': - // â‚‘ [LATIN SUBSCRIPT SMALL LETTER E] - case '\u24D4': - // �? [CIRCLED LATIN SMALL LETTER E] - case '\u2C78': - // ⱸ [LATIN SMALL LETTER E WITH NOTCH] - case '\uFF45': // ï½… [FULLWIDTH LATIN SMALL LETTER E] - output[opos++] = 'e'; - break; - - case '\u24A0': // â’  [PARENTHESIZED LATIN SMALL LETTER E] - output[opos++] = '('; - output[opos++] = 'e'; - output[opos++] = ')'; - break; - - case '\u0191': - // Æ‘ [LATIN CAPITAL LETTER F WITH HOOK] - case '\u1E1E': - // Ḟ [LATIN CAPITAL LETTER F WITH DOT ABOVE] - case '\u24BB': - // â’» [CIRCLED LATIN CAPITAL LETTER F] - case '\uA730': - // ꜰ [LATIN LETTER SMALL CAPITAL F] - case '\uA77B': - // � [LATIN CAPITAL LETTER INSULAR F] - case '\uA7FB': - // ꟻ [LATIN EPIGRAPHIC LETTER REVERSED F] - case '\uFF26': // F [FULLWIDTH LATIN CAPITAL LETTER F] - output[opos++] = 'F'; - break; - - case '\u0192': - // Æ’ [LATIN SMALL LETTER F WITH HOOK] - case '\u1D6E': - // áµ® [LATIN SMALL LETTER F WITH MIDDLE TILDE] - case '\u1D82': - // ᶂ [LATIN SMALL LETTER F WITH PALATAL HOOK] - case '\u1E1F': - // ḟ [LATIN SMALL LETTER F WITH DOT ABOVE] - case '\u1E9B': - // ẛ [LATIN SMALL LETTER LONG S WITH DOT ABOVE] - case '\u24D5': - // â“• [CIRCLED LATIN SMALL LETTER F] - case '\uA77C': - // � [LATIN SMALL LETTER INSULAR F] - case '\uFF46': // f [FULLWIDTH LATIN SMALL LETTER F] - output[opos++] = 'f'; - break; - - case '\u24A1': // â’¡ [PARENTHESIZED LATIN SMALL LETTER F] - output[opos++] = '('; - output[opos++] = 'f'; - output[opos++] = ')'; - break; - - case '\uFB00': // ff [LATIN SMALL LIGATURE FF] - output[opos++] = 'f'; - output[opos++] = 'f'; - break; - - case '\uFB03': // ffi [LATIN SMALL LIGATURE FFI] - output[opos++] = 'f'; - output[opos++] = 'f'; - output[opos++] = 'i'; - break; - - case '\uFB04': // ffl [LATIN SMALL LIGATURE FFL] - output[opos++] = 'f'; - output[opos++] = 'f'; - output[opos++] = 'l'; - break; - - case '\uFB01': // � [LATIN SMALL LIGATURE FI] - output[opos++] = 'f'; - output[opos++] = 'i'; - break; - - case '\uFB02': // fl [LATIN SMALL LIGATURE FL] - output[opos++] = 'f'; - output[opos++] = 'l'; - break; - - case '\u011C': - // Äœ [LATIN CAPITAL LETTER G WITH CIRCUMFLEX] - case '\u011E': - // Äž [LATIN CAPITAL LETTER G WITH BREVE] - case '\u0120': - // Ä  [LATIN CAPITAL LETTER G WITH DOT ABOVE] - case '\u0122': - // Ä¢ [LATIN CAPITAL LETTER G WITH CEDILLA] - case '\u0193': - // Æ“ [LATIN CAPITAL LETTER G WITH HOOK] - case '\u01E4': - // Ǥ [LATIN CAPITAL LETTER G WITH STROKE] - case '\u01E5': - // Ç¥ [LATIN SMALL LETTER G WITH STROKE] - case '\u01E6': - // Ǧ [LATIN CAPITAL LETTER G WITH CARON] - case '\u01E7': - // ǧ [LATIN SMALL LETTER G WITH CARON] - case '\u01F4': - // Ç´ [LATIN CAPITAL LETTER G WITH ACUTE] - case '\u0262': - // É¢ [LATIN LETTER SMALL CAPITAL G] - case '\u029B': - // Ê› [LATIN LETTER SMALL CAPITAL G WITH HOOK] - case '\u1E20': - // Ḡ [LATIN CAPITAL LETTER G WITH MACRON] - case '\u24BC': - // â’¼ [CIRCLED LATIN CAPITAL LETTER G] - case '\uA77D': - // � [LATIN CAPITAL LETTER INSULAR G] - case '\uA77E': - // � [LATIN CAPITAL LETTER TURNED INSULAR G] - case '\uFF27': // G [FULLWIDTH LATIN CAPITAL LETTER G] - output[opos++] = 'G'; - break; - - case '\u011D': - // � [LATIN SMALL LETTER G WITH CIRCUMFLEX] - case '\u011F': - // ÄŸ [LATIN SMALL LETTER G WITH BREVE] - case '\u0121': - // Ä¡ [LATIN SMALL LETTER G WITH DOT ABOVE] - case '\u0123': - // Ä£ [LATIN SMALL LETTER G WITH CEDILLA] - case '\u01F5': - // ǵ [LATIN SMALL LETTER G WITH ACUTE] - case '\u0260': - // É  [LATIN SMALL LETTER G WITH HOOK] - case '\u0261': - // É¡ [LATIN SMALL LETTER SCRIPT G] - case '\u1D77': - // áµ· [LATIN SMALL LETTER TURNED G] - case '\u1D79': - // áµ¹ [LATIN SMALL LETTER INSULAR G] - case '\u1D83': - // ᶃ [LATIN SMALL LETTER G WITH PALATAL HOOK] - case '\u1E21': - // ḡ [LATIN SMALL LETTER G WITH MACRON] - case '\u24D6': - // â“– [CIRCLED LATIN SMALL LETTER G] - case '\uA77F': - // � [LATIN SMALL LETTER TURNED INSULAR G] - case '\uFF47': // g [FULLWIDTH LATIN SMALL LETTER G] - output[opos++] = 'g'; - break; - - case '\u24A2': // â’¢ [PARENTHESIZED LATIN SMALL LETTER G] - output[opos++] = '('; - output[opos++] = 'g'; - output[opos++] = ')'; - break; - - case '\u0124': - // Ĥ [LATIN CAPITAL LETTER H WITH CIRCUMFLEX] - case '\u0126': - // Ħ [LATIN CAPITAL LETTER H WITH STROKE] - case '\u021E': - // Èž [LATIN CAPITAL LETTER H WITH CARON] - case '\u029C': - // Êœ [LATIN LETTER SMALL CAPITAL H] - case '\u1E22': - // Ḣ [LATIN CAPITAL LETTER H WITH DOT ABOVE] - case '\u1E24': - // Ḥ [LATIN CAPITAL LETTER H WITH DOT BELOW] - case '\u1E26': - // Ḧ [LATIN CAPITAL LETTER H WITH DIAERESIS] - case '\u1E28': - // Ḩ [LATIN CAPITAL LETTER H WITH CEDILLA] - case '\u1E2A': - // Ḫ [LATIN CAPITAL LETTER H WITH BREVE BELOW] - case '\u24BD': - // â’½ [CIRCLED LATIN CAPITAL LETTER H] - case '\u2C67': - // Ⱨ [LATIN CAPITAL LETTER H WITH DESCENDER] - case '\u2C75': - // â±µ [LATIN CAPITAL LETTER HALF H] - case '\uFF28': // H [FULLWIDTH LATIN CAPITAL LETTER H] - output[opos++] = 'H'; - break; - - case '\u0125': - // Ä¥ [LATIN SMALL LETTER H WITH CIRCUMFLEX] - case '\u0127': - // ħ [LATIN SMALL LETTER H WITH STROKE] - case '\u021F': - // ÈŸ [LATIN SMALL LETTER H WITH CARON] - case '\u0265': - // É¥ [LATIN SMALL LETTER TURNED H] - case '\u0266': - // ɦ [LATIN SMALL LETTER H WITH HOOK] - case '\u02AE': - // Ê® [LATIN SMALL LETTER TURNED H WITH FISHHOOK] - case '\u02AF': - // ʯ [LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL] - case '\u1E23': - // ḣ [LATIN SMALL LETTER H WITH DOT ABOVE] - case '\u1E25': - // ḥ [LATIN SMALL LETTER H WITH DOT BELOW] - case '\u1E27': - // ḧ [LATIN SMALL LETTER H WITH DIAERESIS] - case '\u1E29': - // ḩ [LATIN SMALL LETTER H WITH CEDILLA] - case '\u1E2B': - // ḫ [LATIN SMALL LETTER H WITH BREVE BELOW] - case '\u1E96': - // ẖ [LATIN SMALL LETTER H WITH LINE BELOW] - case '\u24D7': - // â“— [CIRCLED LATIN SMALL LETTER H] - case '\u2C68': - // ⱨ [LATIN SMALL LETTER H WITH DESCENDER] - case '\u2C76': - // ⱶ [LATIN SMALL LETTER HALF H] - case '\uFF48': // h [FULLWIDTH LATIN SMALL LETTER H] - output[opos++] = 'h'; - break; - - case '\u01F6': // Ƕ http://en.wikipedia.org/wiki/Hwair [LATIN CAPITAL LETTER HWAIR] - output[opos++] = 'H'; - output[opos++] = 'V'; - break; - - case '\u24A3': // â’£ [PARENTHESIZED LATIN SMALL LETTER H] - output[opos++] = '('; - output[opos++] = 'h'; - output[opos++] = ')'; - break; - - case '\u0195': // Æ• [LATIN SMALL LETTER HV] - output[opos++] = 'h'; - output[opos++] = 'v'; - break; - - case '\u00CC': - // ÃŒ [LATIN CAPITAL LETTER I WITH GRAVE] - case '\u00CD': - // � [LATIN CAPITAL LETTER I WITH ACUTE] - case '\u00CE': - // ÃŽ [LATIN CAPITAL LETTER I WITH CIRCUMFLEX] - case '\u00CF': - // � [LATIN CAPITAL LETTER I WITH DIAERESIS] - case '\u0128': - // Ĩ [LATIN CAPITAL LETTER I WITH TILDE] - case '\u012A': - // Ī [LATIN CAPITAL LETTER I WITH MACRON] - case '\u012C': - // Ĭ [LATIN CAPITAL LETTER I WITH BREVE] - case '\u012E': - // Ä® [LATIN CAPITAL LETTER I WITH OGONEK] - case '\u0130': - // Ä° [LATIN CAPITAL LETTER I WITH DOT ABOVE] - case '\u0196': - // Æ– [LATIN CAPITAL LETTER IOTA] - case '\u0197': - // Æ— [LATIN CAPITAL LETTER I WITH STROKE] - case '\u01CF': - // � [LATIN CAPITAL LETTER I WITH CARON] - case '\u0208': - // Ȉ [LATIN CAPITAL LETTER I WITH DOUBLE GRAVE] - case '\u020A': - // ÈŠ [LATIN CAPITAL LETTER I WITH INVERTED BREVE] - case '\u026A': - // ɪ [LATIN LETTER SMALL CAPITAL I] - case '\u1D7B': - // áµ» [LATIN SMALL CAPITAL LETTER I WITH STROKE] - case '\u1E2C': - // Ḭ [LATIN CAPITAL LETTER I WITH TILDE BELOW] - case '\u1E2E': - // Ḯ [LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE] - case '\u1EC8': - // Ỉ [LATIN CAPITAL LETTER I WITH HOOK ABOVE] - case '\u1ECA': - // Ị [LATIN CAPITAL LETTER I WITH DOT BELOW] - case '\u24BE': - // â’¾ [CIRCLED LATIN CAPITAL LETTER I] - case '\uA7FE': - // ꟾ [LATIN EPIGRAPHIC LETTER I LONGA] - case '\uFF29': // I [FULLWIDTH LATIN CAPITAL LETTER I] - output[opos++] = 'I'; - break; - - case '\u00EC': - // ì [LATIN SMALL LETTER I WITH GRAVE] - case '\u00ED': - // í [LATIN SMALL LETTER I WITH ACUTE] - case '\u00EE': - // î [LATIN SMALL LETTER I WITH CIRCUMFLEX] - case '\u00EF': - // ï [LATIN SMALL LETTER I WITH DIAERESIS] - case '\u0129': - // Ä© [LATIN SMALL LETTER I WITH TILDE] - case '\u012B': - // Ä« [LATIN SMALL LETTER I WITH MACRON] - case '\u012D': - // Ä­ [LATIN SMALL LETTER I WITH BREVE] - case '\u012F': - // į [LATIN SMALL LETTER I WITH OGONEK] - case '\u0131': - // ı [LATIN SMALL LETTER DOTLESS I] - case '\u01D0': - // � [LATIN SMALL LETTER I WITH CARON] - case '\u0209': - // ȉ [LATIN SMALL LETTER I WITH DOUBLE GRAVE] - case '\u020B': - // È‹ [LATIN SMALL LETTER I WITH INVERTED BREVE] - case '\u0268': - // ɨ [LATIN SMALL LETTER I WITH STROKE] - case '\u1D09': - // á´‰ [LATIN SMALL LETTER TURNED I] - case '\u1D62': - // áµ¢ [LATIN SUBSCRIPT SMALL LETTER I] - case '\u1D7C': - // áµ¼ [LATIN SMALL LETTER IOTA WITH STROKE] - case '\u1D96': - // ᶖ [LATIN SMALL LETTER I WITH RETROFLEX HOOK] - case '\u1E2D': - // ḭ [LATIN SMALL LETTER I WITH TILDE BELOW] - case '\u1E2F': - // ḯ [LATIN SMALL LETTER I WITH DIAERESIS AND ACUTE] - case '\u1EC9': - // ỉ [LATIN SMALL LETTER I WITH HOOK ABOVE] - case '\u1ECB': - // ị [LATIN SMALL LETTER I WITH DOT BELOW] - case '\u2071': - // � [SUPERSCRIPT LATIN SMALL LETTER I] - case '\u24D8': - // ⓘ [CIRCLED LATIN SMALL LETTER I] - case '\uFF49': // i [FULLWIDTH LATIN SMALL LETTER I] - output[opos++] = 'i'; - break; - - case '\u0132': // IJ [LATIN CAPITAL LIGATURE IJ] - output[opos++] = 'I'; - output[opos++] = 'J'; - break; - - case '\u24A4': // â’¤ [PARENTHESIZED LATIN SMALL LETTER I] - output[opos++] = '('; - output[opos++] = 'i'; - output[opos++] = ')'; - break; - - case '\u0133': // ij [LATIN SMALL LIGATURE IJ] - output[opos++] = 'i'; - output[opos++] = 'j'; - break; - - case '\u0134': - // Ä´ [LATIN CAPITAL LETTER J WITH CIRCUMFLEX] - case '\u0248': - // Ɉ [LATIN CAPITAL LETTER J WITH STROKE] - case '\u1D0A': - // á´Š [LATIN LETTER SMALL CAPITAL J] - case '\u24BF': - // â’¿ [CIRCLED LATIN CAPITAL LETTER J] - case '\uFF2A': // J [FULLWIDTH LATIN CAPITAL LETTER J] - output[opos++] = 'J'; - break; - - case '\u0135': - // ĵ [LATIN SMALL LETTER J WITH CIRCUMFLEX] - case '\u01F0': - // Ç° [LATIN SMALL LETTER J WITH CARON] - case '\u0237': - // È· [LATIN SMALL LETTER DOTLESS J] - case '\u0249': - // ɉ [LATIN SMALL LETTER J WITH STROKE] - case '\u025F': - // ÉŸ [LATIN SMALL LETTER DOTLESS J WITH STROKE] - case '\u0284': - // Ê„ [LATIN SMALL LETTER DOTLESS J WITH STROKE AND HOOK] - case '\u029D': - // � [LATIN SMALL LETTER J WITH CROSSED-TAIL] - case '\u24D9': - // â“™ [CIRCLED LATIN SMALL LETTER J] - case '\u2C7C': - // â±¼ [LATIN SUBSCRIPT SMALL LETTER J] - case '\uFF4A': // j [FULLWIDTH LATIN SMALL LETTER J] - output[opos++] = 'j'; - break; - - case '\u24A5': // â’¥ [PARENTHESIZED LATIN SMALL LETTER J] - output[opos++] = '('; - output[opos++] = 'j'; - output[opos++] = ')'; - break; - - case '\u0136': - // Ķ [LATIN CAPITAL LETTER K WITH CEDILLA] - case '\u0198': - // Ƙ [LATIN CAPITAL LETTER K WITH HOOK] - case '\u01E8': - // Ǩ [LATIN CAPITAL LETTER K WITH CARON] - case '\u1D0B': - // á´‹ [LATIN LETTER SMALL CAPITAL K] - case '\u1E30': - // Ḱ [LATIN CAPITAL LETTER K WITH ACUTE] - case '\u1E32': - // Ḳ [LATIN CAPITAL LETTER K WITH DOT BELOW] - case '\u1E34': - // Ḵ [LATIN CAPITAL LETTER K WITH LINE BELOW] - case '\u24C0': - // â“€ [CIRCLED LATIN CAPITAL LETTER K] - case '\u2C69': - // Ⱪ [LATIN CAPITAL LETTER K WITH DESCENDER] - case '\uA740': - // � [LATIN CAPITAL LETTER K WITH STROKE] - case '\uA742': - // � [LATIN CAPITAL LETTER K WITH DIAGONAL STROKE] - case '\uA744': - // � [LATIN CAPITAL LETTER K WITH STROKE AND DIAGONAL STROKE] - case '\uFF2B': // K [FULLWIDTH LATIN CAPITAL LETTER K] - output[opos++] = 'K'; - break; - - case '\u0137': - // Ä· [LATIN SMALL LETTER K WITH CEDILLA] - case '\u0199': - // Æ™ [LATIN SMALL LETTER K WITH HOOK] - case '\u01E9': - // Ç© [LATIN SMALL LETTER K WITH CARON] - case '\u029E': - // Êž [LATIN SMALL LETTER TURNED K] - case '\u1D84': - // ᶄ [LATIN SMALL LETTER K WITH PALATAL HOOK] - case '\u1E31': - // ḱ [LATIN SMALL LETTER K WITH ACUTE] - case '\u1E33': - // ḳ [LATIN SMALL LETTER K WITH DOT BELOW] - case '\u1E35': - // ḵ [LATIN SMALL LETTER K WITH LINE BELOW] - case '\u24DA': - // â“š [CIRCLED LATIN SMALL LETTER K] - case '\u2C6A': - // ⱪ [LATIN SMALL LETTER K WITH DESCENDER] - case '\uA741': - // � [LATIN SMALL LETTER K WITH STROKE] - case '\uA743': - // � [LATIN SMALL LETTER K WITH DIAGONAL STROKE] - case '\uA745': - // � [LATIN SMALL LETTER K WITH STROKE AND DIAGONAL STROKE] - case '\uFF4B': // k [FULLWIDTH LATIN SMALL LETTER K] - output[opos++] = 'k'; - break; - - case '\u24A6': // â’¦ [PARENTHESIZED LATIN SMALL LETTER K] - output[opos++] = '('; - output[opos++] = 'k'; - output[opos++] = ')'; - break; - - case '\u0139': - // Ĺ [LATIN CAPITAL LETTER L WITH ACUTE] - case '\u013B': - // Ä» [LATIN CAPITAL LETTER L WITH CEDILLA] - case '\u013D': - // Ľ [LATIN CAPITAL LETTER L WITH CARON] - case '\u013F': - // Ä¿ [LATIN CAPITAL LETTER L WITH MIDDLE DOT] - case '\u0141': - // � [LATIN CAPITAL LETTER L WITH STROKE] - case '\u023D': - // Ƚ [LATIN CAPITAL LETTER L WITH BAR] - case '\u029F': - // ÊŸ [LATIN LETTER SMALL CAPITAL L] - case '\u1D0C': - // á´Œ [LATIN LETTER SMALL CAPITAL L WITH STROKE] - case '\u1E36': - // Ḷ [LATIN CAPITAL LETTER L WITH DOT BELOW] - case '\u1E38': - // Ḹ [LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON] - case '\u1E3A': - // Ḻ [LATIN CAPITAL LETTER L WITH LINE BELOW] - case '\u1E3C': - // Ḽ [LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW] - case '\u24C1': - // � [CIRCLED LATIN CAPITAL LETTER L] - case '\u2C60': - // â±  [LATIN CAPITAL LETTER L WITH DOUBLE BAR] - case '\u2C62': - // â±¢ [LATIN CAPITAL LETTER L WITH MIDDLE TILDE] - case '\uA746': - // � [LATIN CAPITAL LETTER BROKEN L] - case '\uA748': - // � [LATIN CAPITAL LETTER L WITH HIGH STROKE] - case '\uA780': - // Ꞁ [LATIN CAPITAL LETTER TURNED L] - case '\uFF2C': // L [FULLWIDTH LATIN CAPITAL LETTER L] - output[opos++] = 'L'; - break; - - case '\u013A': - // ĺ [LATIN SMALL LETTER L WITH ACUTE] - case '\u013C': - // ļ [LATIN SMALL LETTER L WITH CEDILLA] - case '\u013E': - // ľ [LATIN SMALL LETTER L WITH CARON] - case '\u0140': - // Å€ [LATIN SMALL LETTER L WITH MIDDLE DOT] - case '\u0142': - // Å‚ [LATIN SMALL LETTER L WITH STROKE] - case '\u019A': - // Æš [LATIN SMALL LETTER L WITH BAR] - case '\u0234': - // È´ [LATIN SMALL LETTER L WITH CURL] - case '\u026B': - // É« [LATIN SMALL LETTER L WITH MIDDLE TILDE] - case '\u026C': - // ɬ [LATIN SMALL LETTER L WITH BELT] - case '\u026D': - // É­ [LATIN SMALL LETTER L WITH RETROFLEX HOOK] - case '\u1D85': - // ᶅ [LATIN SMALL LETTER L WITH PALATAL HOOK] - case '\u1E37': - // ḷ [LATIN SMALL LETTER L WITH DOT BELOW] - case '\u1E39': - // ḹ [LATIN SMALL LETTER L WITH DOT BELOW AND MACRON] - case '\u1E3B': - // ḻ [LATIN SMALL LETTER L WITH LINE BELOW] - case '\u1E3D': - // ḽ [LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW] - case '\u24DB': - // â“› [CIRCLED LATIN SMALL LETTER L] - case '\u2C61': - // ⱡ [LATIN SMALL LETTER L WITH DOUBLE BAR] - case '\uA747': - // � [LATIN SMALL LETTER BROKEN L] - case '\uA749': - // � [LATIN SMALL LETTER L WITH HIGH STROKE] - case '\uA781': - // � [LATIN SMALL LETTER TURNED L] - case '\uFF4C': // l [FULLWIDTH LATIN SMALL LETTER L] - output[opos++] = 'l'; - break; - - case '\u01C7': // LJ [LATIN CAPITAL LETTER LJ] - output[opos++] = 'L'; - output[opos++] = 'J'; - break; - - case '\u1EFA': // Ỻ [LATIN CAPITAL LETTER MIDDLE-WELSH LL] - output[opos++] = 'L'; - output[opos++] = 'L'; - break; - - case '\u01C8': // Lj [LATIN CAPITAL LETTER L WITH SMALL LETTER J] - output[opos++] = 'L'; - output[opos++] = 'j'; - break; - - case '\u24A7': // â’§ [PARENTHESIZED LATIN SMALL LETTER L] - output[opos++] = '('; - output[opos++] = 'l'; - output[opos++] = ')'; - break; - - case '\u01C9': // lj [LATIN SMALL LETTER LJ] - output[opos++] = 'l'; - output[opos++] = 'j'; - break; - - case '\u1EFB': // á»» [LATIN SMALL LETTER MIDDLE-WELSH LL] - output[opos++] = 'l'; - output[opos++] = 'l'; - break; - - case '\u02AA': // ʪ [LATIN SMALL LETTER LS DIGRAPH] - output[opos++] = 'l'; - output[opos++] = 's'; - break; - - case '\u02AB': // Ê« [LATIN SMALL LETTER LZ DIGRAPH] - output[opos++] = 'l'; - output[opos++] = 'z'; - break; - - case '\u019C': - // Æœ [LATIN CAPITAL LETTER TURNED M] - case '\u1D0D': - // á´� [LATIN LETTER SMALL CAPITAL M] - case '\u1E3E': - // Ḿ [LATIN CAPITAL LETTER M WITH ACUTE] - case '\u1E40': - // á¹€ [LATIN CAPITAL LETTER M WITH DOT ABOVE] - case '\u1E42': - // Ṃ [LATIN CAPITAL LETTER M WITH DOT BELOW] - case '\u24C2': - // â“‚ [CIRCLED LATIN CAPITAL LETTER M] - case '\u2C6E': - // â±® [LATIN CAPITAL LETTER M WITH HOOK] - case '\uA7FD': - // ꟽ [LATIN EPIGRAPHIC LETTER INVERTED M] - case '\uA7FF': - // ꟿ [LATIN EPIGRAPHIC LETTER ARCHAIC M] - case '\uFF2D': // ï¼­ [FULLWIDTH LATIN CAPITAL LETTER M] - output[opos++] = 'M'; - break; - - case '\u026F': - // ɯ [LATIN SMALL LETTER TURNED M] - case '\u0270': - // É° [LATIN SMALL LETTER TURNED M WITH LONG LEG] - case '\u0271': - // ɱ [LATIN SMALL LETTER M WITH HOOK] - case '\u1D6F': - // ᵯ [LATIN SMALL LETTER M WITH MIDDLE TILDE] - case '\u1D86': - // ᶆ [LATIN SMALL LETTER M WITH PALATAL HOOK] - case '\u1E3F': - // ḿ [LATIN SMALL LETTER M WITH ACUTE] - case '\u1E41': - // � [LATIN SMALL LETTER M WITH DOT ABOVE] - case '\u1E43': - // ṃ [LATIN SMALL LETTER M WITH DOT BELOW] - case '\u24DC': - // â“œ [CIRCLED LATIN SMALL LETTER M] - case '\uFF4D': // � [FULLWIDTH LATIN SMALL LETTER M] - output[opos++] = 'm'; - break; - - case '\u24A8': // â’¨ [PARENTHESIZED LATIN SMALL LETTER M] - output[opos++] = '('; - output[opos++] = 'm'; - output[opos++] = ')'; - break; - - case '\u00D1': - // Ñ [LATIN CAPITAL LETTER N WITH TILDE] - case '\u0143': - // Ã…Æ’ [LATIN CAPITAL LETTER N WITH ACUTE] - case '\u0145': - // Å… [LATIN CAPITAL LETTER N WITH CEDILLA] - case '\u0147': - // Ň [LATIN CAPITAL LETTER N WITH CARON] - case '\u014A': - // Ã…Å  http://en.wikipedia.org/wiki/Eng_(letter) [LATIN CAPITAL LETTER ENG] - case '\u019D': - // � [LATIN CAPITAL LETTER N WITH LEFT HOOK] - case '\u01F8': - // Ǹ [LATIN CAPITAL LETTER N WITH GRAVE] - case '\u0220': - // È  [LATIN CAPITAL LETTER N WITH LONG RIGHT LEG] - case '\u0274': - // É´ [LATIN LETTER SMALL CAPITAL N] - case '\u1D0E': - // á´Ž [LATIN LETTER SMALL CAPITAL REVERSED N] - case '\u1E44': - // Ṅ [LATIN CAPITAL LETTER N WITH DOT ABOVE] - case '\u1E46': - // Ṇ [LATIN CAPITAL LETTER N WITH DOT BELOW] - case '\u1E48': - // Ṉ [LATIN CAPITAL LETTER N WITH LINE BELOW] - case '\u1E4A': - // Ṋ [LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW] - case '\u24C3': - // Ⓝ [CIRCLED LATIN CAPITAL LETTER N] - case '\uFF2E': // ï¼® [FULLWIDTH LATIN CAPITAL LETTER N] - output[opos++] = 'N'; - break; - - case '\u00F1': - // ñ [LATIN SMALL LETTER N WITH TILDE] - case '\u0144': - // Å„ [LATIN SMALL LETTER N WITH ACUTE] - case '\u0146': - // ņ [LATIN SMALL LETTER N WITH CEDILLA] - case '\u0148': - // ň [LATIN SMALL LETTER N WITH CARON] - case '\u0149': - // ʼn [LATIN SMALL LETTER N PRECEDED BY APOSTROPHE] - case '\u014B': - // Å‹ http://en.wikipedia.org/wiki/Eng_(letter) [LATIN SMALL LETTER ENG] - case '\u019E': - // Æž [LATIN SMALL LETTER N WITH LONG RIGHT LEG] - case '\u01F9': - // ǹ [LATIN SMALL LETTER N WITH GRAVE] - case '\u0235': - // ȵ [LATIN SMALL LETTER N WITH CURL] - case '\u0272': - // ɲ [LATIN SMALL LETTER N WITH LEFT HOOK] - case '\u0273': - // ɳ [LATIN SMALL LETTER N WITH RETROFLEX HOOK] - case '\u1D70': - // áµ° [LATIN SMALL LETTER N WITH MIDDLE TILDE] - case '\u1D87': - // ᶇ [LATIN SMALL LETTER N WITH PALATAL HOOK] - case '\u1E45': - // á¹… [LATIN SMALL LETTER N WITH DOT ABOVE] - case '\u1E47': - // ṇ [LATIN SMALL LETTER N WITH DOT BELOW] - case '\u1E49': - // ṉ [LATIN SMALL LETTER N WITH LINE BELOW] - case '\u1E4B': - // ṋ [LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW] - case '\u207F': - // � [SUPERSCRIPT LATIN SMALL LETTER N] - case '\u24DD': - // � [CIRCLED LATIN SMALL LETTER N] - case '\uFF4E': // n [FULLWIDTH LATIN SMALL LETTER N] - output[opos++] = 'n'; - break; - - case '\u01CA': // ÇŠ [LATIN CAPITAL LETTER NJ] - output[opos++] = 'N'; - output[opos++] = 'J'; - break; - - case '\u01CB': // Ç‹ [LATIN CAPITAL LETTER N WITH SMALL LETTER J] - output[opos++] = 'N'; - output[opos++] = 'j'; - break; - - case '\u24A9': // â’© [PARENTHESIZED LATIN SMALL LETTER N] - output[opos++] = '('; - output[opos++] = 'n'; - output[opos++] = ')'; - break; - - case '\u01CC': // ÇŒ [LATIN SMALL LETTER NJ] - output[opos++] = 'n'; - output[opos++] = 'j'; - break; - - case '\u00D2': - // Ã’ [LATIN CAPITAL LETTER O WITH GRAVE] - case '\u00D3': - // Ó [LATIN CAPITAL LETTER O WITH ACUTE] - case '\u00D4': - // �? [LATIN CAPITAL LETTER O WITH CIRCUMFLEX] - case '\u00D5': - // Õ [LATIN CAPITAL LETTER O WITH TILDE] - case '\u00D6': - // Ö [LATIN CAPITAL LETTER O WITH DIAERESIS] - case '\u00D8': - // Ø [LATIN CAPITAL LETTER O WITH STROKE] - case '\u014C': - // Ã…Å’ [LATIN CAPITAL LETTER O WITH MACRON] - case '\u014E': - // ÅŽ [LATIN CAPITAL LETTER O WITH BREVE] - case '\u0150': - // � [LATIN CAPITAL LETTER O WITH DOUBLE ACUTE] - case '\u0186': - // Ɔ [LATIN CAPITAL LETTER OPEN O] - case '\u019F': - // ÆŸ [LATIN CAPITAL LETTER O WITH MIDDLE TILDE] - case '\u01A0': - // Æ  [LATIN CAPITAL LETTER O WITH HORN] - case '\u01D1': - // Ç‘ [LATIN CAPITAL LETTER O WITH CARON] - case '\u01EA': - // Ǫ [LATIN CAPITAL LETTER O WITH OGONEK] - case '\u01EC': - // Ǭ [LATIN CAPITAL LETTER O WITH OGONEK AND MACRON] - case '\u01FE': - // Ǿ [LATIN CAPITAL LETTER O WITH STROKE AND ACUTE] - case '\u020C': - // ÈŒ [LATIN CAPITAL LETTER O WITH DOUBLE GRAVE] - case '\u020E': - // ÈŽ [LATIN CAPITAL LETTER O WITH INVERTED BREVE] - case '\u022A': - // Ȫ [LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON] - case '\u022C': - // Ȭ [LATIN CAPITAL LETTER O WITH TILDE AND MACRON] - case '\u022E': - // È® [LATIN CAPITAL LETTER O WITH DOT ABOVE] - case '\u0230': - // È° [LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON] - case '\u1D0F': - // á´� [LATIN LETTER SMALL CAPITAL O] - case '\u1D10': - // á´� [LATIN LETTER SMALL CAPITAL OPEN O] - case '\u1E4C': - // Ṍ [LATIN CAPITAL LETTER O WITH TILDE AND ACUTE] - case '\u1E4E': - // Ṏ [LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS] - case '\u1E50': - // � [LATIN CAPITAL LETTER O WITH MACRON AND GRAVE] - case '\u1E52': - // á¹’ [LATIN CAPITAL LETTER O WITH MACRON AND ACUTE] - case '\u1ECC': - // Ọ [LATIN CAPITAL LETTER O WITH DOT BELOW] - case '\u1ECE': - // Ỏ [LATIN CAPITAL LETTER O WITH HOOK ABOVE] - case '\u1ED0': - // � [LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE] - case '\u1ED2': - // á»’ [LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE] - case '\u1ED4': - // �? [LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE] - case '\u1ED6': - // á»– [LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE] - case '\u1ED8': - // Ộ [LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW] - case '\u1EDA': - // Ớ [LATIN CAPITAL LETTER O WITH HORN AND ACUTE] - case '\u1EDC': - // Ờ [LATIN CAPITAL LETTER O WITH HORN AND GRAVE] - case '\u1EDE': - // Ở [LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE] - case '\u1EE0': - // á»  [LATIN CAPITAL LETTER O WITH HORN AND TILDE] - case '\u1EE2': - // Ợ [LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW] - case '\u24C4': - // â“„ [CIRCLED LATIN CAPITAL LETTER O] - case '\uA74A': - // � [LATIN CAPITAL LETTER O WITH LONG STROKE OVERLAY] - case '\uA74C': - // � [LATIN CAPITAL LETTER O WITH LOOP] - case '\uFF2F': // O [FULLWIDTH LATIN CAPITAL LETTER O] - output[opos++] = 'O'; - break; - - case '\u00F2': - // ò [LATIN SMALL LETTER O WITH GRAVE] - case '\u00F3': - // ó [LATIN SMALL LETTER O WITH ACUTE] - case '\u00F4': - // ô [LATIN SMALL LETTER O WITH CIRCUMFLEX] - case '\u00F5': - // õ [LATIN SMALL LETTER O WITH TILDE] - case '\u00F6': - // ö [LATIN SMALL LETTER O WITH DIAERESIS] - case '\u00F8': - // ø [LATIN SMALL LETTER O WITH STROKE] - case '\u014D': - // � [LATIN SMALL LETTER O WITH MACRON] - case '\u014F': - // � [LATIN SMALL LETTER O WITH BREVE] - case '\u0151': - // Å‘ [LATIN SMALL LETTER O WITH DOUBLE ACUTE] - case '\u01A1': - // Æ¡ [LATIN SMALL LETTER O WITH HORN] - case '\u01D2': - // Ç’ [LATIN SMALL LETTER O WITH CARON] - case '\u01EB': - // Ç« [LATIN SMALL LETTER O WITH OGONEK] - case '\u01ED': - // Ç­ [LATIN SMALL LETTER O WITH OGONEK AND MACRON] - case '\u01FF': - // Ç¿ [LATIN SMALL LETTER O WITH STROKE AND ACUTE] - case '\u020D': - // � [LATIN SMALL LETTER O WITH DOUBLE GRAVE] - case '\u020F': - // � [LATIN SMALL LETTER O WITH INVERTED BREVE] - case '\u022B': - // È« [LATIN SMALL LETTER O WITH DIAERESIS AND MACRON] - case '\u022D': - // È­ [LATIN SMALL LETTER O WITH TILDE AND MACRON] - case '\u022F': - // ȯ [LATIN SMALL LETTER O WITH DOT ABOVE] - case '\u0231': - // ȱ [LATIN SMALL LETTER O WITH DOT ABOVE AND MACRON] - case '\u0254': - // �? [LATIN SMALL LETTER OPEN O] - case '\u0275': - // ɵ [LATIN SMALL LETTER BARRED O] - case '\u1D16': - // á´– [LATIN SMALL LETTER TOP HALF O] - case '\u1D17': - // á´— [LATIN SMALL LETTER BOTTOM HALF O] - case '\u1D97': - // ᶗ [LATIN SMALL LETTER OPEN O WITH RETROFLEX HOOK] - case '\u1E4D': - // � [LATIN SMALL LETTER O WITH TILDE AND ACUTE] - case '\u1E4F': - // � [LATIN SMALL LETTER O WITH TILDE AND DIAERESIS] - case '\u1E51': - // ṑ [LATIN SMALL LETTER O WITH MACRON AND GRAVE] - case '\u1E53': - // ṓ [LATIN SMALL LETTER O WITH MACRON AND ACUTE] - case '\u1ECD': - // � [LATIN SMALL LETTER O WITH DOT BELOW] - case '\u1ECF': - // � [LATIN SMALL LETTER O WITH HOOK ABOVE] - case '\u1ED1': - // ố [LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE] - case '\u1ED3': - // ồ [LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE] - case '\u1ED5': - // ổ [LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE] - case '\u1ED7': - // á»— [LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE] - case '\u1ED9': - // á»™ [LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW] - case '\u1EDB': - // á»› [LATIN SMALL LETTER O WITH HORN AND ACUTE] - case '\u1EDD': - // � [LATIN SMALL LETTER O WITH HORN AND GRAVE] - case '\u1EDF': - // ở [LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE] - case '\u1EE1': - // ỡ [LATIN SMALL LETTER O WITH HORN AND TILDE] - case '\u1EE3': - // ợ [LATIN SMALL LETTER O WITH HORN AND DOT BELOW] - case '\u2092': - // â‚’ [LATIN SUBSCRIPT SMALL LETTER O] - case '\u24DE': - // â“ž [CIRCLED LATIN SMALL LETTER O] - case '\u2C7A': - // ⱺ [LATIN SMALL LETTER O WITH LOW RING INSIDE] - case '\uA74B': - // � [LATIN SMALL LETTER O WITH LONG STROKE OVERLAY] - case '\uA74D': - // � [LATIN SMALL LETTER O WITH LOOP] - case '\uFF4F': // � [FULLWIDTH LATIN SMALL LETTER O] - output[opos++] = 'o'; - break; - - case '\u0152': - // Å’ [LATIN CAPITAL LIGATURE OE] - case '\u0276': // ɶ [LATIN LETTER SMALL CAPITAL OE] - output[opos++] = 'O'; - output[opos++] = 'E'; - break; - - case '\uA74E': // � [LATIN CAPITAL LETTER OO] - output[opos++] = 'O'; - output[opos++] = 'O'; - break; - - case '\u0222': - // È¢ http://en.wikipedia.org/wiki/OU [LATIN CAPITAL LETTER OU] - case '\u1D15': // á´• [LATIN LETTER SMALL CAPITAL OU] - output[opos++] = 'O'; - output[opos++] = 'U'; - break; - - case '\u24AA': // â’ª [PARENTHESIZED LATIN SMALL LETTER O] - output[opos++] = '('; - output[opos++] = 'o'; - output[opos++] = ')'; - break; - - case '\u0153': - // Å“ [LATIN SMALL LIGATURE OE] - case '\u1D14': // á´�? [LATIN SMALL LETTER TURNED OE] - output[opos++] = 'o'; - output[opos++] = 'e'; - break; - - case '\uA74F': // � [LATIN SMALL LETTER OO] - output[opos++] = 'o'; - output[opos++] = 'o'; - break; - - case '\u0223': // È£ http://en.wikipedia.org/wiki/OU [LATIN SMALL LETTER OU] - output[opos++] = 'o'; - output[opos++] = 'u'; - break; - - case '\u01A4': - // Ƥ [LATIN CAPITAL LETTER P WITH HOOK] - case '\u1D18': - // á´˜ [LATIN LETTER SMALL CAPITAL P] - case '\u1E54': - // �? [LATIN CAPITAL LETTER P WITH ACUTE] - case '\u1E56': - // á¹– [LATIN CAPITAL LETTER P WITH DOT ABOVE] - case '\u24C5': - // â“… [CIRCLED LATIN CAPITAL LETTER P] - case '\u2C63': - // â±£ [LATIN CAPITAL LETTER P WITH STROKE] - case '\uA750': - // � [LATIN CAPITAL LETTER P WITH STROKE THROUGH DESCENDER] - case '\uA752': - // � [LATIN CAPITAL LETTER P WITH FLOURISH] - case '\uA754': - // �? [LATIN CAPITAL LETTER P WITH SQUIRREL TAIL] - case '\uFF30': // ï¼° [FULLWIDTH LATIN CAPITAL LETTER P] - output[opos++] = 'P'; - break; - - case '\u01A5': - // Æ¥ [LATIN SMALL LETTER P WITH HOOK] - case '\u1D71': - // áµ± [LATIN SMALL LETTER P WITH MIDDLE TILDE] - case '\u1D7D': - // áµ½ [LATIN SMALL LETTER P WITH STROKE] - case '\u1D88': - // ᶈ [LATIN SMALL LETTER P WITH PALATAL HOOK] - case '\u1E55': - // ṕ [LATIN SMALL LETTER P WITH ACUTE] - case '\u1E57': - // á¹— [LATIN SMALL LETTER P WITH DOT ABOVE] - case '\u24DF': - // â“Ÿ [CIRCLED LATIN SMALL LETTER P] - case '\uA751': - // � [LATIN SMALL LETTER P WITH STROKE THROUGH DESCENDER] - case '\uA753': - // � [LATIN SMALL LETTER P WITH FLOURISH] - case '\uA755': - // � [LATIN SMALL LETTER P WITH SQUIRREL TAIL] - case '\uA7FC': - // ꟼ [LATIN EPIGRAPHIC LETTER REVERSED P] - case '\uFF50': // � [FULLWIDTH LATIN SMALL LETTER P] - output[opos++] = 'p'; - break; - - case '\u24AB': // â’« [PARENTHESIZED LATIN SMALL LETTER P] - output[opos++] = '('; - output[opos++] = 'p'; - output[opos++] = ')'; - break; - - case '\u024A': - // ÉŠ [LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL] - case '\u24C6': - // Ⓠ [CIRCLED LATIN CAPITAL LETTER Q] - case '\uA756': - // � [LATIN CAPITAL LETTER Q WITH STROKE THROUGH DESCENDER] - case '\uA758': - // � [LATIN CAPITAL LETTER Q WITH DIAGONAL STROKE] - case '\uFF31': // ï¼± [FULLWIDTH LATIN CAPITAL LETTER Q] - output[opos++] = 'Q'; - break; - - case '\u0138': - // ĸ http://en.wikipedia.org/wiki/Kra_(letter) [LATIN SMALL LETTER KRA] - case '\u024B': - // É‹ [LATIN SMALL LETTER Q WITH HOOK TAIL] - case '\u02A0': - // Ê  [LATIN SMALL LETTER Q WITH HOOK] - case '\u24E0': - // â“  [CIRCLED LATIN SMALL LETTER Q] - case '\uA757': - // � [LATIN SMALL LETTER Q WITH STROKE THROUGH DESCENDER] - case '\uA759': - // � [LATIN SMALL LETTER Q WITH DIAGONAL STROKE] - case '\uFF51': // q [FULLWIDTH LATIN SMALL LETTER Q] - output[opos++] = 'q'; - break; - - case '\u24AC': // â’¬ [PARENTHESIZED LATIN SMALL LETTER Q] - output[opos++] = '('; - output[opos++] = 'q'; - output[opos++] = ')'; - break; - - case '\u0239': // ȹ [LATIN SMALL LETTER QP DIGRAPH] - output[opos++] = 'q'; - output[opos++] = 'p'; - break; - - case '\u0154': - // �? [LATIN CAPITAL LETTER R WITH ACUTE] - case '\u0156': - // Å– [LATIN CAPITAL LETTER R WITH CEDILLA] - case '\u0158': - // Ã…Ëœ [LATIN CAPITAL LETTER R WITH CARON] - case '\u0210': - // È’ [LATIN CAPITAL LETTER R WITH DOUBLE GRAVE] - case '\u0212': - // È’ [LATIN CAPITAL LETTER R WITH INVERTED BREVE] - case '\u024C': - // ÉŒ [LATIN CAPITAL LETTER R WITH STROKE] - case '\u0280': - // Ê€ [LATIN LETTER SMALL CAPITAL R] - case '\u0281': - // � [LATIN LETTER SMALL CAPITAL INVERTED R] - case '\u1D19': - // á´™ [LATIN LETTER SMALL CAPITAL REVERSED R] - case '\u1D1A': - // á´š [LATIN LETTER SMALL CAPITAL TURNED R] - case '\u1E58': - // Ṙ [LATIN CAPITAL LETTER R WITH DOT ABOVE] - case '\u1E5A': - // Ṛ [LATIN CAPITAL LETTER R WITH DOT BELOW] - case '\u1E5C': - // Ṝ [LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON] - case '\u1E5E': - // Ṟ [LATIN CAPITAL LETTER R WITH LINE BELOW] - case '\u24C7': - // Ⓡ [CIRCLED LATIN CAPITAL LETTER R] - case '\u2C64': - // Ɽ [LATIN CAPITAL LETTER R WITH TAIL] - case '\uA75A': - // � [LATIN CAPITAL LETTER R ROTUNDA] - case '\uA782': - // êž‚ [LATIN CAPITAL LETTER INSULAR R] - case '\uFF32': // ï¼² [FULLWIDTH LATIN CAPITAL LETTER R] - output[opos++] = 'R'; - break; - - case '\u0155': - // Å• [LATIN SMALL LETTER R WITH ACUTE] - case '\u0157': - // Å— [LATIN SMALL LETTER R WITH CEDILLA] - case '\u0159': - // Ã…â„¢ [LATIN SMALL LETTER R WITH CARON] - case '\u0211': - // È‘ [LATIN SMALL LETTER R WITH DOUBLE GRAVE] - case '\u0213': - // È“ [LATIN SMALL LETTER R WITH INVERTED BREVE] - case '\u024D': - // � [LATIN SMALL LETTER R WITH STROKE] - case '\u027C': - // ɼ [LATIN SMALL LETTER R WITH LONG LEG] - case '\u027D': - // ɽ [LATIN SMALL LETTER R WITH TAIL] - case '\u027E': - // ɾ [LATIN SMALL LETTER R WITH FISHHOOK] - case '\u027F': - // É¿ [LATIN SMALL LETTER REVERSED R WITH FISHHOOK] - case '\u1D63': - // áµ£ [LATIN SUBSCRIPT SMALL LETTER R] - case '\u1D72': - // áµ² [LATIN SMALL LETTER R WITH MIDDLE TILDE] - case '\u1D73': - // áµ³ [LATIN SMALL LETTER R WITH FISHHOOK AND MIDDLE TILDE] - case '\u1D89': - // ᶉ [LATIN SMALL LETTER R WITH PALATAL HOOK] - case '\u1E59': - // á¹™ [LATIN SMALL LETTER R WITH DOT ABOVE] - case '\u1E5B': - // á¹› [LATIN SMALL LETTER R WITH DOT BELOW] - case '\u1E5D': - // � [LATIN SMALL LETTER R WITH DOT BELOW AND MACRON] - case '\u1E5F': - // ṟ [LATIN SMALL LETTER R WITH LINE BELOW] - case '\u24E1': - // â“¡ [CIRCLED LATIN SMALL LETTER R] - case '\uA75B': - // � [LATIN SMALL LETTER R ROTUNDA] - case '\uA783': - // ꞃ [LATIN SMALL LETTER INSULAR R] - case '\uFF52': // ï½’ [FULLWIDTH LATIN SMALL LETTER R] - output[opos++] = 'r'; - break; - - case '\u24AD': // â’­ [PARENTHESIZED LATIN SMALL LETTER R] - output[opos++] = '('; - output[opos++] = 'r'; - output[opos++] = ')'; - break; - - case '\u015A': - // Ã…Å¡ [LATIN CAPITAL LETTER S WITH ACUTE] - case '\u015C': - // Ã…Å“ [LATIN CAPITAL LETTER S WITH CIRCUMFLEX] - case '\u015E': - // Åž [LATIN CAPITAL LETTER S WITH CEDILLA] - case '\u0160': - // Å  [LATIN CAPITAL LETTER S WITH CARON] - case '\u0218': - // Ș [LATIN CAPITAL LETTER S WITH COMMA BELOW] - case '\u1E60': - // á¹  [LATIN CAPITAL LETTER S WITH DOT ABOVE] - case '\u1E62': - // á¹¢ [LATIN CAPITAL LETTER S WITH DOT BELOW] - case '\u1E64': - // Ṥ [LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE] - case '\u1E66': - // Ṧ [LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE] - case '\u1E68': - // Ṩ [LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE] - case '\u24C8': - // Ⓢ [CIRCLED LATIN CAPITAL LETTER S] - case '\uA731': - // ꜱ [LATIN LETTER SMALL CAPITAL S] - case '\uA785': - // êž… [LATIN SMALL LETTER INSULAR S] - case '\uFF33': // ï¼³ [FULLWIDTH LATIN CAPITAL LETTER S] - output[opos++] = 'S'; - break; - - case '\u015B': - // Å› [LATIN SMALL LETTER S WITH ACUTE] - case '\u015D': - // � [LATIN SMALL LETTER S WITH CIRCUMFLEX] - case '\u015F': - // ÅŸ [LATIN SMALL LETTER S WITH CEDILLA] - case '\u0161': - // Å¡ [LATIN SMALL LETTER S WITH CARON] - case '\u017F': - // Å¿ http://en.wikipedia.org/wiki/Long_S [LATIN SMALL LETTER LONG S] - case '\u0219': - // È™ [LATIN SMALL LETTER S WITH COMMA BELOW] - case '\u023F': - // È¿ [LATIN SMALL LETTER S WITH SWASH TAIL] - case '\u0282': - // Ê‚ [LATIN SMALL LETTER S WITH HOOK] - case '\u1D74': - // áµ´ [LATIN SMALL LETTER S WITH MIDDLE TILDE] - case '\u1D8A': - // ᶊ [LATIN SMALL LETTER S WITH PALATAL HOOK] - case '\u1E61': - // ṡ [LATIN SMALL LETTER S WITH DOT ABOVE] - case '\u1E63': - // á¹£ [LATIN SMALL LETTER S WITH DOT BELOW] - case '\u1E65': - // á¹¥ [LATIN SMALL LETTER S WITH ACUTE AND DOT ABOVE] - case '\u1E67': - // ṧ [LATIN SMALL LETTER S WITH CARON AND DOT ABOVE] - case '\u1E69': - // ṩ [LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE] - case '\u1E9C': - // ẜ [LATIN SMALL LETTER LONG S WITH DIAGONAL STROKE] - case '\u1E9D': - // � [LATIN SMALL LETTER LONG S WITH HIGH STROKE] - case '\u24E2': - // â“¢ [CIRCLED LATIN SMALL LETTER S] - case '\uA784': - // êž„ [LATIN CAPITAL LETTER INSULAR S] - case '\uFF53': // s [FULLWIDTH LATIN SMALL LETTER S] - output[opos++] = 's'; - break; - - case '\u1E9E': // ẞ [LATIN CAPITAL LETTER SHARP S] - output[opos++] = 'S'; - output[opos++] = 'S'; - break; - - case '\u24AE': // â’® [PARENTHESIZED LATIN SMALL LETTER S] - output[opos++] = '('; - output[opos++] = 's'; - output[opos++] = ')'; - break; - - case '\u00DF': // ß [LATIN SMALL LETTER SHARP S] - output[opos++] = 's'; - output[opos++] = 's'; - break; - - case '\uFB06': // st [LATIN SMALL LIGATURE ST] - output[opos++] = 's'; - output[opos++] = 't'; - break; - - case '\u0162': - // Å¢ [LATIN CAPITAL LETTER T WITH CEDILLA] - case '\u0164': - // Ť [LATIN CAPITAL LETTER T WITH CARON] - case '\u0166': - // Ŧ [LATIN CAPITAL LETTER T WITH STROKE] - case '\u01AC': - // Ƭ [LATIN CAPITAL LETTER T WITH HOOK] - case '\u01AE': - // Æ® [LATIN CAPITAL LETTER T WITH RETROFLEX HOOK] - case '\u021A': - // Èš [LATIN CAPITAL LETTER T WITH COMMA BELOW] - case '\u023E': - // Ⱦ [LATIN CAPITAL LETTER T WITH DIAGONAL STROKE] - case '\u1D1B': - // á´› [LATIN LETTER SMALL CAPITAL T] - case '\u1E6A': - // Ṫ [LATIN CAPITAL LETTER T WITH DOT ABOVE] - case '\u1E6C': - // Ṭ [LATIN CAPITAL LETTER T WITH DOT BELOW] - case '\u1E6E': - // á¹® [LATIN CAPITAL LETTER T WITH LINE BELOW] - case '\u1E70': - // á¹° [LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW] - case '\u24C9': - // Ⓣ [CIRCLED LATIN CAPITAL LETTER T] - case '\uA786': - // Ꞇ [LATIN CAPITAL LETTER INSULAR T] - case '\uFF34': // ï¼´ [FULLWIDTH LATIN CAPITAL LETTER T] - output[opos++] = 'T'; - break; - - case '\u0163': - // Å£ [LATIN SMALL LETTER T WITH CEDILLA] - case '\u0165': - // Ã…Â¥ [LATIN SMALL LETTER T WITH CARON] - case '\u0167': - // ŧ [LATIN SMALL LETTER T WITH STROKE] - case '\u01AB': - // Æ« [LATIN SMALL LETTER T WITH PALATAL HOOK] - case '\u01AD': - // Æ­ [LATIN SMALL LETTER T WITH HOOK] - case '\u021B': - // È› [LATIN SMALL LETTER T WITH COMMA BELOW] - case '\u0236': - // ȶ [LATIN SMALL LETTER T WITH CURL] - case '\u0287': - // ʇ [LATIN SMALL LETTER TURNED T] - case '\u0288': - // ʈ [LATIN SMALL LETTER T WITH RETROFLEX HOOK] - case '\u1D75': - // áµµ [LATIN SMALL LETTER T WITH MIDDLE TILDE] - case '\u1E6B': - // ṫ [LATIN SMALL LETTER T WITH DOT ABOVE] - case '\u1E6D': - // á¹­ [LATIN SMALL LETTER T WITH DOT BELOW] - case '\u1E6F': - // ṯ [LATIN SMALL LETTER T WITH LINE BELOW] - case '\u1E71': - // á¹± [LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW] - case '\u1E97': - // ẗ [LATIN SMALL LETTER T WITH DIAERESIS] - case '\u24E3': - // â“£ [CIRCLED LATIN SMALL LETTER T] - case '\u2C66': - // ⱦ [LATIN SMALL LETTER T WITH DIAGONAL STROKE] - case '\uFF54': // �? [FULLWIDTH LATIN SMALL LETTER T] - output[opos++] = 't'; - break; - - case '\u00DE': - // Þ [LATIN CAPITAL LETTER THORN] - case '\uA766': // � [LATIN CAPITAL LETTER THORN WITH STROKE THROUGH DESCENDER] - output[opos++] = 'T'; - output[opos++] = 'H'; - break; - - case '\uA728': // Ꜩ [LATIN CAPITAL LETTER TZ] - output[opos++] = 'T'; - output[opos++] = 'Z'; - break; - - case '\u24AF': // â’¯ [PARENTHESIZED LATIN SMALL LETTER T] - output[opos++] = '('; - output[opos++] = 't'; - output[opos++] = ')'; - break; - - case '\u02A8': // ʨ [LATIN SMALL LETTER TC DIGRAPH WITH CURL] - output[opos++] = 't'; - output[opos++] = 'c'; - break; - - case '\u00FE': - // þ [LATIN SMALL LETTER THORN] - case '\u1D7A': - // ᵺ [LATIN SMALL LETTER TH WITH STRIKETHROUGH] - case '\uA767': // � [LATIN SMALL LETTER THORN WITH STROKE THROUGH DESCENDER] - output[opos++] = 't'; - output[opos++] = 'h'; - break; - - case '\u02A6': // ʦ [LATIN SMALL LETTER TS DIGRAPH] - output[opos++] = 't'; - output[opos++] = 's'; - break; - - case '\uA729': // ꜩ [LATIN SMALL LETTER TZ] - output[opos++] = 't'; - output[opos++] = 'z'; - break; - - case '\u00D9': - // Ù [LATIN CAPITAL LETTER U WITH GRAVE] - case '\u00DA': - // Ú [LATIN CAPITAL LETTER U WITH ACUTE] - case '\u00DB': - // Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX] - case '\u00DC': - // Ãœ [LATIN CAPITAL LETTER U WITH DIAERESIS] - case '\u0168': - // Ũ [LATIN CAPITAL LETTER U WITH TILDE] - case '\u016A': - // Ū [LATIN CAPITAL LETTER U WITH MACRON] - case '\u016C': - // Ŭ [LATIN CAPITAL LETTER U WITH BREVE] - case '\u016E': - // Å® [LATIN CAPITAL LETTER U WITH RING ABOVE] - case '\u0170': - // Å° [LATIN CAPITAL LETTER U WITH DOUBLE ACUTE] - case '\u0172': - // Ų [LATIN CAPITAL LETTER U WITH OGONEK] - case '\u01AF': - // Ư [LATIN CAPITAL LETTER U WITH HORN] - case '\u01D3': - // Ç“ [LATIN CAPITAL LETTER U WITH CARON] - case '\u01D5': - // Ç• [LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON] - case '\u01D7': - // Ç— [LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE] - case '\u01D9': - // Ç™ [LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON] - case '\u01DB': - // Ç› [LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE] - case '\u0214': - // �? [LATIN CAPITAL LETTER U WITH DOUBLE GRAVE] - case '\u0216': - // È– [LATIN CAPITAL LETTER U WITH INVERTED BREVE] - case '\u0244': - // É„ [LATIN CAPITAL LETTER U BAR] - case '\u1D1C': - // á´œ [LATIN LETTER SMALL CAPITAL U] - case '\u1D7E': - // áµ¾ [LATIN SMALL CAPITAL LETTER U WITH STROKE] - case '\u1E72': - // á¹² [LATIN CAPITAL LETTER U WITH DIAERESIS BELOW] - case '\u1E74': - // á¹´ [LATIN CAPITAL LETTER U WITH TILDE BELOW] - case '\u1E76': - // Ṷ [LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW] - case '\u1E78': - // Ṹ [LATIN CAPITAL LETTER U WITH TILDE AND ACUTE] - case '\u1E7A': - // Ṻ [LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS] - case '\u1EE4': - // Ụ [LATIN CAPITAL LETTER U WITH DOT BELOW] - case '\u1EE6': - // Ủ [LATIN CAPITAL LETTER U WITH HOOK ABOVE] - case '\u1EE8': - // Ứ [LATIN CAPITAL LETTER U WITH HORN AND ACUTE] - case '\u1EEA': - // Ừ [LATIN CAPITAL LETTER U WITH HORN AND GRAVE] - case '\u1EEC': - // Ử [LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE] - case '\u1EEE': - // á»® [LATIN CAPITAL LETTER U WITH HORN AND TILDE] - case '\u1EF0': - // á»° [LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW] - case '\u24CA': - // â“Š [CIRCLED LATIN CAPITAL LETTER U] - case '\uFF35': // ï¼µ [FULLWIDTH LATIN CAPITAL LETTER U] - output[opos++] = 'U'; - break; - - case '\u00F9': - // ù [LATIN SMALL LETTER U WITH GRAVE] - case '\u00FA': - // ú [LATIN SMALL LETTER U WITH ACUTE] - case '\u00FB': - // û [LATIN SMALL LETTER U WITH CIRCUMFLEX] - case '\u00FC': - // ü [LATIN SMALL LETTER U WITH DIAERESIS] - case '\u0169': - // Å© [LATIN SMALL LETTER U WITH TILDE] - case '\u016B': - // Å« [LATIN SMALL LETTER U WITH MACRON] - case '\u016D': - // Å­ [LATIN SMALL LETTER U WITH BREVE] - case '\u016F': - // ů [LATIN SMALL LETTER U WITH RING ABOVE] - case '\u0171': - // ű [LATIN SMALL LETTER U WITH DOUBLE ACUTE] - case '\u0173': - // ų [LATIN SMALL LETTER U WITH OGONEK] - case '\u01B0': - // Æ° [LATIN SMALL LETTER U WITH HORN] - case '\u01D4': - // �? [LATIN SMALL LETTER U WITH CARON] - case '\u01D6': - // Ç– [LATIN SMALL LETTER U WITH DIAERESIS AND MACRON] - case '\u01D8': - // ǘ [LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE] - case '\u01DA': - // Çš [LATIN SMALL LETTER U WITH DIAERESIS AND CARON] - case '\u01DC': - // Çœ [LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE] - case '\u0215': - // È• [LATIN SMALL LETTER U WITH DOUBLE GRAVE] - case '\u0217': - // È— [LATIN SMALL LETTER U WITH INVERTED BREVE] - case '\u0289': - // ʉ [LATIN SMALL LETTER U BAR] - case '\u1D64': - // ᵤ [LATIN SUBSCRIPT SMALL LETTER U] - case '\u1D99': - // ᶙ [LATIN SMALL LETTER U WITH RETROFLEX HOOK] - case '\u1E73': - // á¹³ [LATIN SMALL LETTER U WITH DIAERESIS BELOW] - case '\u1E75': - // á¹µ [LATIN SMALL LETTER U WITH TILDE BELOW] - case '\u1E77': - // á¹· [LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW] - case '\u1E79': - // á¹¹ [LATIN SMALL LETTER U WITH TILDE AND ACUTE] - case '\u1E7B': - // á¹» [LATIN SMALL LETTER U WITH MACRON AND DIAERESIS] - case '\u1EE5': - // ụ [LATIN SMALL LETTER U WITH DOT BELOW] - case '\u1EE7': - // ủ [LATIN SMALL LETTER U WITH HOOK ABOVE] - case '\u1EE9': - // ứ [LATIN SMALL LETTER U WITH HORN AND ACUTE] - case '\u1EEB': - // ừ [LATIN SMALL LETTER U WITH HORN AND GRAVE] - case '\u1EED': - // á»­ [LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE] - case '\u1EEF': - // ữ [LATIN SMALL LETTER U WITH HORN AND TILDE] - case '\u1EF1': - // á»± [LATIN SMALL LETTER U WITH HORN AND DOT BELOW] - case '\u24E4': - // ⓤ [CIRCLED LATIN SMALL LETTER U] - case '\uFF55': // u [FULLWIDTH LATIN SMALL LETTER U] - output[opos++] = 'u'; - break; - - case '\u24B0': // â’° [PARENTHESIZED LATIN SMALL LETTER U] - output[opos++] = '('; - output[opos++] = 'u'; - output[opos++] = ')'; - break; - - case '\u1D6B': // ᵫ [LATIN SMALL LETTER UE] - output[opos++] = 'u'; - output[opos++] = 'e'; - break; - - case '\u01B2': - // Ʋ [LATIN CAPITAL LETTER V WITH HOOK] - case '\u0245': - // É… [LATIN CAPITAL LETTER TURNED V] - case '\u1D20': - // á´  [LATIN LETTER SMALL CAPITAL V] - case '\u1E7C': - // á¹¼ [LATIN CAPITAL LETTER V WITH TILDE] - case '\u1E7E': - // á¹¾ [LATIN CAPITAL LETTER V WITH DOT BELOW] - case '\u1EFC': - // Ỽ [LATIN CAPITAL LETTER MIDDLE-WELSH V] - case '\u24CB': - // â“‹ [CIRCLED LATIN CAPITAL LETTER V] - case '\uA75E': - // � [LATIN CAPITAL LETTER V WITH DIAGONAL STROKE] - case '\uA768': - // � [LATIN CAPITAL LETTER VEND] - case '\uFF36': // V [FULLWIDTH LATIN CAPITAL LETTER V] - output[opos++] = 'V'; - break; - - case '\u028B': - // Ê‹ [LATIN SMALL LETTER V WITH HOOK] - case '\u028C': - // ÊŒ [LATIN SMALL LETTER TURNED V] - case '\u1D65': - // áµ¥ [LATIN SUBSCRIPT SMALL LETTER V] - case '\u1D8C': - // ᶌ [LATIN SMALL LETTER V WITH PALATAL HOOK] - case '\u1E7D': - // á¹½ [LATIN SMALL LETTER V WITH TILDE] - case '\u1E7F': - // ṿ [LATIN SMALL LETTER V WITH DOT BELOW] - case '\u24E5': - // â“¥ [CIRCLED LATIN SMALL LETTER V] - case '\u2C71': - // â±± [LATIN SMALL LETTER V WITH RIGHT HOOK] - case '\u2C74': - // â±´ [LATIN SMALL LETTER V WITH CURL] - case '\uA75F': - // � [LATIN SMALL LETTER V WITH DIAGONAL STROKE] - case '\uFF56': // ï½– [FULLWIDTH LATIN SMALL LETTER V] - output[opos++] = 'v'; - break; - - case '\uA760': // � [LATIN CAPITAL LETTER VY] - output[opos++] = 'V'; - output[opos++] = 'Y'; - break; - - case '\u24B1': // â’± [PARENTHESIZED LATIN SMALL LETTER V] - output[opos++] = '('; - output[opos++] = 'v'; - output[opos++] = ')'; - break; - - case '\uA761': // � [LATIN SMALL LETTER VY] - output[opos++] = 'v'; - output[opos++] = 'y'; - break; - - case '\u0174': - // Å´ [LATIN CAPITAL LETTER W WITH CIRCUMFLEX] - case '\u01F7': - // Ç· http://en.wikipedia.org/wiki/Wynn [LATIN CAPITAL LETTER WYNN] - case '\u1D21': - // á´¡ [LATIN LETTER SMALL CAPITAL W] - case '\u1E80': - // Ẁ [LATIN CAPITAL LETTER W WITH GRAVE] - case '\u1E82': - // Ẃ [LATIN CAPITAL LETTER W WITH ACUTE] - case '\u1E84': - // Ẅ [LATIN CAPITAL LETTER W WITH DIAERESIS] - case '\u1E86': - // Ẇ [LATIN CAPITAL LETTER W WITH DOT ABOVE] - case '\u1E88': - // Ẉ [LATIN CAPITAL LETTER W WITH DOT BELOW] - case '\u24CC': - // â“Œ [CIRCLED LATIN CAPITAL LETTER W] - case '\u2C72': - // â±² [LATIN CAPITAL LETTER W WITH HOOK] - case '\uFF37': // ï¼· [FULLWIDTH LATIN CAPITAL LETTER W] - output[opos++] = 'W'; - break; - - case '\u0175': - // ŵ [LATIN SMALL LETTER W WITH CIRCUMFLEX] - case '\u01BF': - // Æ¿ http://en.wikipedia.org/wiki/Wynn [LATIN LETTER WYNN] - case '\u028D': - // � [LATIN SMALL LETTER TURNED W] - case '\u1E81': - // � [LATIN SMALL LETTER W WITH GRAVE] - case '\u1E83': - // ẃ [LATIN SMALL LETTER W WITH ACUTE] - case '\u1E85': - // ẅ [LATIN SMALL LETTER W WITH DIAERESIS] - case '\u1E87': - // ẇ [LATIN SMALL LETTER W WITH DOT ABOVE] - case '\u1E89': - // ẉ [LATIN SMALL LETTER W WITH DOT BELOW] - case '\u1E98': - // ẘ [LATIN SMALL LETTER W WITH RING ABOVE] - case '\u24E6': - // ⓦ [CIRCLED LATIN SMALL LETTER W] - case '\u2C73': - // â±³ [LATIN SMALL LETTER W WITH HOOK] - case '\uFF57': // ï½— [FULLWIDTH LATIN SMALL LETTER W] - output[opos++] = 'w'; - break; - - case '\u24B2': // â’² [PARENTHESIZED LATIN SMALL LETTER W] - output[opos++] = '('; - output[opos++] = 'w'; - output[opos++] = ')'; - break; - - case '\u1E8A': - // Ẋ [LATIN CAPITAL LETTER X WITH DOT ABOVE] - case '\u1E8C': - // Ẍ [LATIN CAPITAL LETTER X WITH DIAERESIS] - case '\u24CD': - // � [CIRCLED LATIN CAPITAL LETTER X] - case '\uFF38': // X [FULLWIDTH LATIN CAPITAL LETTER X] - output[opos++] = 'X'; - break; - - case '\u1D8D': - // � [LATIN SMALL LETTER X WITH PALATAL HOOK] - case '\u1E8B': - // ẋ [LATIN SMALL LETTER X WITH DOT ABOVE] - case '\u1E8D': - // � [LATIN SMALL LETTER X WITH DIAERESIS] - case '\u2093': - // â‚“ [LATIN SUBSCRIPT SMALL LETTER X] - case '\u24E7': - // ⓧ [CIRCLED LATIN SMALL LETTER X] - case '\uFF58': // x [FULLWIDTH LATIN SMALL LETTER X] - output[opos++] = 'x'; - break; - - case '\u24B3': // â’³ [PARENTHESIZED LATIN SMALL LETTER X] - output[opos++] = '('; - output[opos++] = 'x'; - output[opos++] = ')'; - break; - - case '\u00DD': - // � [LATIN CAPITAL LETTER Y WITH ACUTE] - case '\u0176': - // Ŷ [LATIN CAPITAL LETTER Y WITH CIRCUMFLEX] - case '\u0178': - // Ÿ [LATIN CAPITAL LETTER Y WITH DIAERESIS] - case '\u01B3': - // Ƴ [LATIN CAPITAL LETTER Y WITH HOOK] - case '\u0232': - // Ȳ [LATIN CAPITAL LETTER Y WITH MACRON] - case '\u024E': - // ÉŽ [LATIN CAPITAL LETTER Y WITH STROKE] - case '\u028F': - // � [LATIN LETTER SMALL CAPITAL Y] - case '\u1E8E': - // Ẏ [LATIN CAPITAL LETTER Y WITH DOT ABOVE] - case '\u1EF2': - // Ỳ [LATIN CAPITAL LETTER Y WITH GRAVE] - case '\u1EF4': - // á»´ [LATIN CAPITAL LETTER Y WITH DOT BELOW] - case '\u1EF6': - // Ỷ [LATIN CAPITAL LETTER Y WITH HOOK ABOVE] - case '\u1EF8': - // Ỹ [LATIN CAPITAL LETTER Y WITH TILDE] - case '\u1EFE': - // Ỿ [LATIN CAPITAL LETTER Y WITH LOOP] - case '\u24CE': - // â“Ž [CIRCLED LATIN CAPITAL LETTER Y] - case '\uFF39': // ï¼¹ [FULLWIDTH LATIN CAPITAL LETTER Y] - output[opos++] = 'Y'; - break; - - case '\u00FD': - // ý [LATIN SMALL LETTER Y WITH ACUTE] - case '\u00FF': - // ÿ [LATIN SMALL LETTER Y WITH DIAERESIS] - case '\u0177': - // Å· [LATIN SMALL LETTER Y WITH CIRCUMFLEX] - case '\u01B4': - // Æ´ [LATIN SMALL LETTER Y WITH HOOK] - case '\u0233': - // ȳ [LATIN SMALL LETTER Y WITH MACRON] - case '\u024F': - // � [LATIN SMALL LETTER Y WITH STROKE] - case '\u028E': - // ÊŽ [LATIN SMALL LETTER TURNED Y] - case '\u1E8F': - // � [LATIN SMALL LETTER Y WITH DOT ABOVE] - case '\u1E99': - // ẙ [LATIN SMALL LETTER Y WITH RING ABOVE] - case '\u1EF3': - // ỳ [LATIN SMALL LETTER Y WITH GRAVE] - case '\u1EF5': - // ỵ [LATIN SMALL LETTER Y WITH DOT BELOW] - case '\u1EF7': - // á»· [LATIN SMALL LETTER Y WITH HOOK ABOVE] - case '\u1EF9': - // ỹ [LATIN SMALL LETTER Y WITH TILDE] - case '\u1EFF': - // ỿ [LATIN SMALL LETTER Y WITH LOOP] - case '\u24E8': - // ⓨ [CIRCLED LATIN SMALL LETTER Y] - case '\uFF59': // ï½™ [FULLWIDTH LATIN SMALL LETTER Y] - output[opos++] = 'y'; - break; - - case '\u24B4': // â’´ [PARENTHESIZED LATIN SMALL LETTER Y] - output[opos++] = '('; - output[opos++] = 'y'; - output[opos++] = ')'; - break; - - case '\u0179': - // Ź [LATIN CAPITAL LETTER Z WITH ACUTE] - case '\u017B': - // Å» [LATIN CAPITAL LETTER Z WITH DOT ABOVE] - case '\u017D': - // Ž [LATIN CAPITAL LETTER Z WITH CARON] - case '\u01B5': - // Ƶ [LATIN CAPITAL LETTER Z WITH STROKE] - case '\u021C': - // Èœ http://en.wikipedia.org/wiki/Yogh [LATIN CAPITAL LETTER YOGH] - case '\u0224': - // Ȥ [LATIN CAPITAL LETTER Z WITH HOOK] - case '\u1D22': - // á´¢ [LATIN LETTER SMALL CAPITAL Z] - case '\u1E90': - // � [LATIN CAPITAL LETTER Z WITH CIRCUMFLEX] - case '\u1E92': - // Ẓ [LATIN CAPITAL LETTER Z WITH DOT BELOW] - case '\u1E94': - // �? [LATIN CAPITAL LETTER Z WITH LINE BELOW] - case '\u24CF': - // � [CIRCLED LATIN CAPITAL LETTER Z] - case '\u2C6B': - // Ⱬ [LATIN CAPITAL LETTER Z WITH DESCENDER] - case '\uA762': - // � [LATIN CAPITAL LETTER VISIGOTHIC Z] - case '\uFF3A': // Z [FULLWIDTH LATIN CAPITAL LETTER Z] - output[opos++] = 'Z'; - break; - - case '\u017A': - // ź [LATIN SMALL LETTER Z WITH ACUTE] - case '\u017C': - // ż [LATIN SMALL LETTER Z WITH DOT ABOVE] - case '\u017E': - // ž [LATIN SMALL LETTER Z WITH CARON] - case '\u01B6': - // ƶ [LATIN SMALL LETTER Z WITH STROKE] - case '\u021D': - // � http://en.wikipedia.org/wiki/Yogh [LATIN SMALL LETTER YOGH] - case '\u0225': - // È¥ [LATIN SMALL LETTER Z WITH HOOK] - case '\u0240': - // É€ [LATIN SMALL LETTER Z WITH SWASH TAIL] - case '\u0290': - // � [LATIN SMALL LETTER Z WITH RETROFLEX HOOK] - case '\u0291': - // Ê‘ [LATIN SMALL LETTER Z WITH CURL] - case '\u1D76': - // ᵶ [LATIN SMALL LETTER Z WITH MIDDLE TILDE] - case '\u1D8E': - // ᶎ [LATIN SMALL LETTER Z WITH PALATAL HOOK] - case '\u1E91': - // ẑ [LATIN SMALL LETTER Z WITH CIRCUMFLEX] - case '\u1E93': - // ẓ [LATIN SMALL LETTER Z WITH DOT BELOW] - case '\u1E95': - // ẕ [LATIN SMALL LETTER Z WITH LINE BELOW] - case '\u24E9': - // â“© [CIRCLED LATIN SMALL LETTER Z] - case '\u2C6C': - // ⱬ [LATIN SMALL LETTER Z WITH DESCENDER] - case '\uA763': - // � [LATIN SMALL LETTER VISIGOTHIC Z] - case '\uFF5A': // z [FULLWIDTH LATIN SMALL LETTER Z] - output[opos++] = 'z'; - break; - - case '\u24B5': // â’µ [PARENTHESIZED LATIN SMALL LETTER Z] - output[opos++] = '('; - output[opos++] = 'z'; - output[opos++] = ')'; - break; - - case '\u2070': - // � [SUPERSCRIPT ZERO] - case '\u2080': - // â‚€ [SUBSCRIPT ZERO] - case '\u24EA': - // ⓪ [CIRCLED DIGIT ZERO] - case '\u24FF': - // â“¿ [NEGATIVE CIRCLED DIGIT ZERO] - case '\uFF10': // � [FULLWIDTH DIGIT ZERO] - output[opos++] = '0'; - break; - - case '\u00B9': - // ¹ [SUPERSCRIPT ONE] - case '\u2081': - // � [SUBSCRIPT ONE] - case '\u2460': - // â‘  [CIRCLED DIGIT ONE] - case '\u24F5': - // ⓵ [DOUBLE CIRCLED DIGIT ONE] - case '\u2776': - // � [DINGBAT NEGATIVE CIRCLED DIGIT ONE] - case '\u2780': - // ➀ [DINGBAT CIRCLED SANS-SERIF DIGIT ONE] - case '\u278A': - // ➊ [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ONE] - case '\uFF11': // 1 [FULLWIDTH DIGIT ONE] - output[opos++] = '1'; - break; - - case '\u2488': // â’ˆ [DIGIT ONE FULL STOP] - output[opos++] = '1'; - output[opos++] = '.'; - break; - - case '\u2474': // â‘´ [PARENTHESIZED DIGIT ONE] - output[opos++] = '('; - output[opos++] = '1'; - output[opos++] = ')'; - break; - - case '\u00B2': - // ² [SUPERSCRIPT TWO] - case '\u2082': - // â‚‚ [SUBSCRIPT TWO] - case '\u2461': - // â‘¡ [CIRCLED DIGIT TWO] - case '\u24F6': - // ⓶ [DOUBLE CIRCLED DIGIT TWO] - case '\u2777': - // � [DINGBAT NEGATIVE CIRCLED DIGIT TWO] - case '\u2781': - // � [DINGBAT CIRCLED SANS-SERIF DIGIT TWO] - case '\u278B': - // âž‹ [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT TWO] - case '\uFF12': // ï¼’ [FULLWIDTH DIGIT TWO] - output[opos++] = '2'; - break; - - case '\u2489': // â’‰ [DIGIT TWO FULL STOP] - output[opos++] = '2'; - output[opos++] = '.'; - break; - - case '\u2475': // ⑵ [PARENTHESIZED DIGIT TWO] - output[opos++] = '('; - output[opos++] = '2'; - output[opos++] = ')'; - break; - - case '\u00B3': - // ³ [SUPERSCRIPT THREE] - case '\u2083': - // ₃ [SUBSCRIPT THREE] - case '\u2462': - // â‘¢ [CIRCLED DIGIT THREE] - case '\u24F7': - // â“· [DOUBLE CIRCLED DIGIT THREE] - case '\u2778': - // � [DINGBAT NEGATIVE CIRCLED DIGIT THREE] - case '\u2782': - // âž‚ [DINGBAT CIRCLED SANS-SERIF DIGIT THREE] - case '\u278C': - // ➌ [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT THREE] - case '\uFF13': // 3 [FULLWIDTH DIGIT THREE] - output[opos++] = '3'; - break; - - case '\u248A': // â’Š [DIGIT THREE FULL STOP] - output[opos++] = '3'; - output[opos++] = '.'; - break; - - case '\u2476': // ⑶ [PARENTHESIZED DIGIT THREE] - output[opos++] = '('; - output[opos++] = '3'; - output[opos++] = ')'; - break; - - case '\u2074': - // � [SUPERSCRIPT FOUR] - case '\u2084': - // â‚„ [SUBSCRIPT FOUR] - case '\u2463': - // â‘£ [CIRCLED DIGIT FOUR] - case '\u24F8': - // ⓸ [DOUBLE CIRCLED DIGIT FOUR] - case '\u2779': - // � [DINGBAT NEGATIVE CIRCLED DIGIT FOUR] - case '\u2783': - // ➃ [DINGBAT CIRCLED SANS-SERIF DIGIT FOUR] - case '\u278D': - // � [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FOUR] - case '\uFF14': // �? [FULLWIDTH DIGIT FOUR] - output[opos++] = '4'; - break; - - case '\u248B': // â’‹ [DIGIT FOUR FULL STOP] - output[opos++] = '4'; - output[opos++] = '.'; - break; - - case '\u2477': // â‘· [PARENTHESIZED DIGIT FOUR] - output[opos++] = '('; - output[opos++] = '4'; - output[opos++] = ')'; - break; - - case '\u2075': - // � [SUPERSCRIPT FIVE] - case '\u2085': - // â‚… [SUBSCRIPT FIVE] - case '\u2464': - // ⑤ [CIRCLED DIGIT FIVE] - case '\u24F9': - // ⓹ [DOUBLE CIRCLED DIGIT FIVE] - case '\u277A': - // � [DINGBAT NEGATIVE CIRCLED DIGIT FIVE] - case '\u2784': - // âž„ [DINGBAT CIRCLED SANS-SERIF DIGIT FIVE] - case '\u278E': - // ➎ [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FIVE] - case '\uFF15': // 5 [FULLWIDTH DIGIT FIVE] - output[opos++] = '5'; - break; - - case '\u248C': // â’Œ [DIGIT FIVE FULL STOP] - output[opos++] = '5'; - output[opos++] = '.'; - break; - - case '\u2478': // ⑸ [PARENTHESIZED DIGIT FIVE] - output[opos++] = '('; - output[opos++] = '5'; - output[opos++] = ')'; - break; - - case '\u2076': - // � [SUPERSCRIPT SIX] - case '\u2086': - // ₆ [SUBSCRIPT SIX] - case '\u2465': - // â‘¥ [CIRCLED DIGIT SIX] - case '\u24FA': - // ⓺ [DOUBLE CIRCLED DIGIT SIX] - case '\u277B': - // � [DINGBAT NEGATIVE CIRCLED DIGIT SIX] - case '\u2785': - // âž… [DINGBAT CIRCLED SANS-SERIF DIGIT SIX] - case '\u278F': - // � [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SIX] - case '\uFF16': // ï¼– [FULLWIDTH DIGIT SIX] - output[opos++] = '6'; - break; - - case '\u248D': // â’� [DIGIT SIX FULL STOP] - output[opos++] = '6'; - output[opos++] = '.'; - break; - - case '\u2479': // ⑹ [PARENTHESIZED DIGIT SIX] - output[opos++] = '('; - output[opos++] = '6'; - output[opos++] = ')'; - break; - - case '\u2077': - // � [SUPERSCRIPT SEVEN] - case '\u2087': - // ₇ [SUBSCRIPT SEVEN] - case '\u2466': - // ⑦ [CIRCLED DIGIT SEVEN] - case '\u24FB': - // â“» [DOUBLE CIRCLED DIGIT SEVEN] - case '\u277C': - // � [DINGBAT NEGATIVE CIRCLED DIGIT SEVEN] - case '\u2786': - // ➆ [DINGBAT CIRCLED SANS-SERIF DIGIT SEVEN] - case '\u2790': - // � [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SEVEN] - case '\uFF17': // ï¼— [FULLWIDTH DIGIT SEVEN] - output[opos++] = '7'; - break; - - case '\u248E': // â’Ž [DIGIT SEVEN FULL STOP] - output[opos++] = '7'; - output[opos++] = '.'; - break; - - case '\u247A': // ⑺ [PARENTHESIZED DIGIT SEVEN] - output[opos++] = '('; - output[opos++] = '7'; - output[opos++] = ')'; - break; - - case '\u2078': - // � [SUPERSCRIPT EIGHT] - case '\u2088': - // ₈ [SUBSCRIPT EIGHT] - case '\u2467': - // ⑧ [CIRCLED DIGIT EIGHT] - case '\u24FC': - // ⓼ [DOUBLE CIRCLED DIGIT EIGHT] - case '\u277D': - // � [DINGBAT NEGATIVE CIRCLED DIGIT EIGHT] - case '\u2787': - // ➇ [DINGBAT CIRCLED SANS-SERIF DIGIT EIGHT] - case '\u2791': - // âž‘ [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT EIGHT] - case '\uFF18': // 8 [FULLWIDTH DIGIT EIGHT] - output[opos++] = '8'; - break; - - case '\u248F': // â’� [DIGIT EIGHT FULL STOP] - output[opos++] = '8'; - output[opos++] = '.'; - break; - - case '\u247B': // â‘» [PARENTHESIZED DIGIT EIGHT] - output[opos++] = '('; - output[opos++] = '8'; - output[opos++] = ')'; - break; - - case '\u2079': - // � [SUPERSCRIPT NINE] - case '\u2089': - // ₉ [SUBSCRIPT NINE] - case '\u2468': - // ⑨ [CIRCLED DIGIT NINE] - case '\u24FD': - // ⓽ [DOUBLE CIRCLED DIGIT NINE] - case '\u277E': - // � [DINGBAT NEGATIVE CIRCLED DIGIT NINE] - case '\u2788': - // ➈ [DINGBAT CIRCLED SANS-SERIF DIGIT NINE] - case '\u2792': - // âž’ [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT NINE] - case '\uFF19': // ï¼™ [FULLWIDTH DIGIT NINE] - output[opos++] = '9'; - break; - - case '\u2490': // â’� [DIGIT NINE FULL STOP] - output[opos++] = '9'; - output[opos++] = '.'; - break; - - case '\u247C': // ⑼ [PARENTHESIZED DIGIT NINE] - output[opos++] = '('; - output[opos++] = '9'; - output[opos++] = ')'; - break; - - case '\u2469': - // â‘© [CIRCLED NUMBER TEN] - case '\u24FE': - // ⓾ [DOUBLE CIRCLED NUMBER TEN] - case '\u277F': - // � [DINGBAT NEGATIVE CIRCLED NUMBER TEN] - case '\u2789': - // ➉ [DINGBAT CIRCLED SANS-SERIF NUMBER TEN] - case '\u2793': // âž“ [DINGBAT NEGATIVE CIRCLED SANS-SERIF NUMBER TEN] - output[opos++] = '1'; - output[opos++] = '0'; - break; - - case '\u2491': // â’‘ [NUMBER TEN FULL STOP] - output[opos++] = '1'; - output[opos++] = '0'; - output[opos++] = '.'; - break; - - case '\u247D': // ⑽ [PARENTHESIZED NUMBER TEN] - output[opos++] = '('; - output[opos++] = '1'; - output[opos++] = '0'; - output[opos++] = ')'; - break; - - case '\u246A': - // ⑪ [CIRCLED NUMBER ELEVEN] - case '\u24EB': // â“« [NEGATIVE CIRCLED NUMBER ELEVEN] - output[opos++] = '1'; - output[opos++] = '1'; - break; - - case '\u2492': // â’’ [NUMBER ELEVEN FULL STOP] - output[opos++] = '1'; - output[opos++] = '1'; - output[opos++] = '.'; - break; - - case '\u247E': // ⑾ [PARENTHESIZED NUMBER ELEVEN] - output[opos++] = '('; - output[opos++] = '1'; - output[opos++] = '1'; - output[opos++] = ')'; - break; - - case '\u246B': - // â‘« [CIRCLED NUMBER TWELVE] - case '\u24EC': // ⓬ [NEGATIVE CIRCLED NUMBER TWELVE] - output[opos++] = '1'; - output[opos++] = '2'; - break; - - case '\u2493': // â’“ [NUMBER TWELVE FULL STOP] - output[opos++] = '1'; - output[opos++] = '2'; - output[opos++] = '.'; - break; - - case '\u247F': // â‘¿ [PARENTHESIZED NUMBER TWELVE] - output[opos++] = '('; - output[opos++] = '1'; - output[opos++] = '2'; - output[opos++] = ')'; - break; - - case '\u246C': - // ⑬ [CIRCLED NUMBER THIRTEEN] - case '\u24ED': // â“­ [NEGATIVE CIRCLED NUMBER THIRTEEN] - output[opos++] = '1'; - output[opos++] = '3'; - break; - - case '\u2494': // â’�? [NUMBER THIRTEEN FULL STOP] - output[opos++] = '1'; - output[opos++] = '3'; - output[opos++] = '.'; - break; - - case '\u2480': // â’€ [PARENTHESIZED NUMBER THIRTEEN] - output[opos++] = '('; - output[opos++] = '1'; - output[opos++] = '3'; - output[opos++] = ')'; - break; - - case '\u246D': - // â‘­ [CIRCLED NUMBER FOURTEEN] - case '\u24EE': // â“® [NEGATIVE CIRCLED NUMBER FOURTEEN] - output[opos++] = '1'; - output[opos++] = '4'; - break; - - case '\u2495': // â’• [NUMBER FOURTEEN FULL STOP] - output[opos++] = '1'; - output[opos++] = '4'; - output[opos++] = '.'; - break; - - case '\u2481': // â’� [PARENTHESIZED NUMBER FOURTEEN] - output[opos++] = '('; - output[opos++] = '1'; - output[opos++] = '4'; - output[opos++] = ')'; - break; - - case '\u246E': - // â‘® [CIRCLED NUMBER FIFTEEN] - case '\u24EF': // ⓯ [NEGATIVE CIRCLED NUMBER FIFTEEN] - output[opos++] = '1'; - output[opos++] = '5'; - break; - - case '\u2496': // â’– [NUMBER FIFTEEN FULL STOP] - output[opos++] = '1'; - output[opos++] = '5'; - output[opos++] = '.'; - break; - - case '\u2482': // â’‚ [PARENTHESIZED NUMBER FIFTEEN] - output[opos++] = '('; - output[opos++] = '1'; - output[opos++] = '5'; - output[opos++] = ')'; - break; - - case '\u246F': - // ⑯ [CIRCLED NUMBER SIXTEEN] - case '\u24F0': // â“° [NEGATIVE CIRCLED NUMBER SIXTEEN] - output[opos++] = '1'; - output[opos++] = '6'; - break; - - case '\u2497': // â’— [NUMBER SIXTEEN FULL STOP] - output[opos++] = '1'; - output[opos++] = '6'; - output[opos++] = '.'; - break; - - case '\u2483': // â’ƒ [PARENTHESIZED NUMBER SIXTEEN] - output[opos++] = '('; - output[opos++] = '1'; - output[opos++] = '6'; - output[opos++] = ')'; - break; - - case '\u2470': - // â‘° [CIRCLED NUMBER SEVENTEEN] - case '\u24F1': // ⓱ [NEGATIVE CIRCLED NUMBER SEVENTEEN] - output[opos++] = '1'; - output[opos++] = '7'; - break; - - case '\u2498': // â’˜ [NUMBER SEVENTEEN FULL STOP] - output[opos++] = '1'; - output[opos++] = '7'; - output[opos++] = '.'; - break; - - case '\u2484': // â’„ [PARENTHESIZED NUMBER SEVENTEEN] - output[opos++] = '('; - output[opos++] = '1'; - output[opos++] = '7'; - output[opos++] = ')'; - break; - - case '\u2471': - // ⑱ [CIRCLED NUMBER EIGHTEEN] - case '\u24F2': // ⓲ [NEGATIVE CIRCLED NUMBER EIGHTEEN] - output[opos++] = '1'; - output[opos++] = '8'; - break; - - case '\u2499': // â’™ [NUMBER EIGHTEEN FULL STOP] - output[opos++] = '1'; - output[opos++] = '8'; - output[opos++] = '.'; - break; - - case '\u2485': // â’… [PARENTHESIZED NUMBER EIGHTEEN] - output[opos++] = '('; - output[opos++] = '1'; - output[opos++] = '8'; - output[opos++] = ')'; - break; - - case '\u2472': - // ⑲ [CIRCLED NUMBER NINETEEN] - case '\u24F3': // ⓳ [NEGATIVE CIRCLED NUMBER NINETEEN] - output[opos++] = '1'; - output[opos++] = '9'; - break; - - case '\u249A': // â’š [NUMBER NINETEEN FULL STOP] - output[opos++] = '1'; - output[opos++] = '9'; - output[opos++] = '.'; - break; - - case '\u2486': // â’† [PARENTHESIZED NUMBER NINETEEN] - output[opos++] = '('; - output[opos++] = '1'; - output[opos++] = '9'; - output[opos++] = ')'; - break; - - case '\u2473': - // ⑳ [CIRCLED NUMBER TWENTY] - case '\u24F4': // â“´ [NEGATIVE CIRCLED NUMBER TWENTY] - output[opos++] = '2'; - output[opos++] = '0'; - break; - - case '\u249B': // â’› [NUMBER TWENTY FULL STOP] - output[opos++] = '2'; - output[opos++] = '0'; - output[opos++] = '.'; - break; - - case '\u2487': // â’‡ [PARENTHESIZED NUMBER TWENTY] - output[opos++] = '('; - output[opos++] = '2'; - output[opos++] = '0'; - output[opos++] = ')'; - break; - - case '\u00AB': - // « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK] - case '\u00BB': - // » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK] - case '\u201C': - // “ [LEFT DOUBLE QUOTATION MARK] - case '\u201D': - // � [RIGHT DOUBLE QUOTATION MARK] - case '\u201E': - // „ [DOUBLE LOW-9 QUOTATION MARK] - case '\u2033': - // ″ [DOUBLE PRIME] - case '\u2036': - // ‶ [REVERSED DOUBLE PRIME] - case '\u275D': - // � [HEAVY DOUBLE TURNED COMMA QUOTATION MARK ORNAMENT] - case '\u275E': - // � [HEAVY DOUBLE COMMA QUOTATION MARK ORNAMENT] - case '\u276E': - // � [HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT] - case '\u276F': - // � [HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT] - case '\uFF02': // " [FULLWIDTH QUOTATION MARK] - output[opos++] = '"'; - break; - - case '\u2018': - // ‘ [LEFT SINGLE QUOTATION MARK] - case '\u2019': - // ’ [RIGHT SINGLE QUOTATION MARK] - case '\u201A': - // ‚ [SINGLE LOW-9 QUOTATION MARK] - case '\u201B': - // ‛ [SINGLE HIGH-REVERSED-9 QUOTATION MARK] - case '\u2032': - // ′ [PRIME] - case '\u2035': - // ‵ [REVERSED PRIME] - case '\u2039': - // ‹ [SINGLE LEFT-POINTING ANGLE QUOTATION MARK] - case '\u203A': - // › [SINGLE RIGHT-POINTING ANGLE QUOTATION MARK] - case '\u275B': - // � [HEAVY SINGLE TURNED COMMA QUOTATION MARK ORNAMENT] - case '\u275C': - // � [HEAVY SINGLE COMMA QUOTATION MARK ORNAMENT] - case '\uFF07': // ' [FULLWIDTH APOSTROPHE] - output[opos++] = '\''; - break; - - case '\u2010': - // � [HYPHEN] - case '\u2011': - // ‑ [NON-BREAKING HYPHEN] - case '\u2012': - // ‒ [FIGURE DASH] - case '\u2013': - // – [EN DASH] - case '\u2014': - // �? [EM DASH] - case '\u207B': - // � [SUPERSCRIPT MINUS] - case '\u208B': - // â‚‹ [SUBSCRIPT MINUS] - case '\uFF0D': // � [FULLWIDTH HYPHEN-MINUS] - output[opos++] = '-'; - break; - - case '\u2045': - // � [LEFT SQUARE BRACKET WITH QUILL] - case '\u2772': - // � [LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT] - case '\uFF3B': // ï¼» [FULLWIDTH LEFT SQUARE BRACKET] - output[opos++] = '['; - break; - - case '\u2046': - // � [RIGHT SQUARE BRACKET WITH QUILL] - case '\u2773': - // � [LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT] - case '\uFF3D': // ï¼½ [FULLWIDTH RIGHT SQUARE BRACKET] - output[opos++] = ']'; - break; - - case '\u207D': - // � [SUPERSCRIPT LEFT PARENTHESIS] - case '\u208D': - // � [SUBSCRIPT LEFT PARENTHESIS] - case '\u2768': - // � [MEDIUM LEFT PARENTHESIS ORNAMENT] - case '\u276A': - // � [MEDIUM FLATTENED LEFT PARENTHESIS ORNAMENT] - case '\uFF08': // ( [FULLWIDTH LEFT PARENTHESIS] - output[opos++] = '('; - break; - - case '\u2E28': // ⸨ [LEFT DOUBLE PARENTHESIS] - output[opos++] = '('; - output[opos++] = '('; - break; - - case '\u207E': - // � [SUPERSCRIPT RIGHT PARENTHESIS] - case '\u208E': - // â‚Ž [SUBSCRIPT RIGHT PARENTHESIS] - case '\u2769': - // � [MEDIUM RIGHT PARENTHESIS ORNAMENT] - case '\u276B': - // � [MEDIUM FLATTENED RIGHT PARENTHESIS ORNAMENT] - case '\uFF09': // ) [FULLWIDTH RIGHT PARENTHESIS] - output[opos++] = ')'; - break; - - case '\u2E29': // ⸩ [RIGHT DOUBLE PARENTHESIS] - output[opos++] = ')'; - output[opos++] = ')'; - break; - - case '\u276C': - // � [MEDIUM LEFT-POINTING ANGLE BRACKET ORNAMENT] - case '\u2770': - // � [HEAVY LEFT-POINTING ANGLE BRACKET ORNAMENT] - case '\uFF1C': // < [FULLWIDTH LESS-THAN SIGN] - output[opos++] = '<'; - break; - - case '\u276D': - // � [MEDIUM RIGHT-POINTING ANGLE BRACKET ORNAMENT] - case '\u2771': - // � [HEAVY RIGHT-POINTING ANGLE BRACKET ORNAMENT] - case '\uFF1E': // > [FULLWIDTH GREATER-THAN SIGN] - output[opos++] = '>'; - break; - - case '\u2774': - // � [MEDIUM LEFT CURLY BRACKET ORNAMENT] - case '\uFF5B': // ï½› [FULLWIDTH LEFT CURLY BRACKET] - output[opos++] = '{'; - break; - - case '\u2775': - // � [MEDIUM RIGHT CURLY BRACKET ORNAMENT] - case '\uFF5D': // � [FULLWIDTH RIGHT CURLY BRACKET] - output[opos++] = '}'; - break; - - case '\u207A': - // � [SUPERSCRIPT PLUS SIGN] - case '\u208A': - // â‚Š [SUBSCRIPT PLUS SIGN] - case '\uFF0B': // + [FULLWIDTH PLUS SIGN] - output[opos++] = '+'; - break; - - case '\u207C': - // � [SUPERSCRIPT EQUALS SIGN] - case '\u208C': - // â‚Œ [SUBSCRIPT EQUALS SIGN] - case '\uFF1D': // � [FULLWIDTH EQUALS SIGN] - output[opos++] = '='; - break; - - case '\uFF01': // � [FULLWIDTH EXCLAMATION MARK] - output[opos++] = '!'; - break; - - case '\u203C': // ‼ [DOUBLE EXCLAMATION MARK] - output[opos++] = '!'; - output[opos++] = '!'; - break; - - case '\u2049': // � [EXCLAMATION QUESTION MARK] - output[opos++] = '!'; - output[opos++] = '?'; - break; - - case '\uFF03': // # [FULLWIDTH NUMBER SIGN] - output[opos++] = '#'; - break; - - case '\uFF04': // $ [FULLWIDTH DOLLAR SIGN] - output[opos++] = '$'; - break; - - case '\u2052': - // � [COMMERCIAL MINUS SIGN] - case '\uFF05': // ï¼… [FULLWIDTH PERCENT SIGN] - output[opos++] = '%'; - break; - - case '\uFF06': // & [FULLWIDTH AMPERSAND] - output[opos++] = '&'; - break; - - case '\u204E': - // � [LOW ASTERISK] - case '\uFF0A': // * [FULLWIDTH ASTERISK] - output[opos++] = '*'; - break; - - case '\uFF0C': // , [FULLWIDTH COMMA] - output[opos++] = ','; - break; - - case '\uFF0E': // . [FULLWIDTH FULL STOP] - output[opos++] = '.'; - break; - - case '\u2044': - // � [FRACTION SLASH] - case '\uFF0F': // � [FULLWIDTH SOLIDUS] - output[opos++] = '/'; - break; - - case '\uFF1A': // : [FULLWIDTH COLON] - output[opos++] = ':'; - break; - - case '\u204F': - // � [REVERSED SEMICOLON] - case '\uFF1B': // ï¼› [FULLWIDTH SEMICOLON] - output[opos++] = ';'; - break; - - case '\uFF1F': // ? [FULLWIDTH QUESTION MARK] - output[opos++] = '?'; - break; - - case '\u2047': // � [DOUBLE QUESTION MARK] - output[opos++] = '?'; - output[opos++] = '?'; - break; - - case '\u2048': // � [QUESTION EXCLAMATION MARK] - output[opos++] = '?'; - output[opos++] = '!'; - break; - - case '\uFF20': // ï¼  [FULLWIDTH COMMERCIAL AT] - output[opos++] = '@'; - break; - - case '\uFF3C': // ï¼¼ [FULLWIDTH REVERSE SOLIDUS] - output[opos++] = '\\'; - break; - - case '\u2038': - // ‸ [CARET] - case '\uFF3E': // ï¼¾ [FULLWIDTH CIRCUMFLEX ACCENT] - output[opos++] = '^'; - break; - - case '\uFF3F': // _ [FULLWIDTH LOW LINE] - output[opos++] = '_'; - break; - - case '\u2053': - // � [SWUNG DASH] - case '\uFF5E': // ~ [FULLWIDTH TILDE] - output[opos++] = '~'; - break; - - // BEGIN CUSTOM TRANSLITERATION OF CYRILIC CHARS - - #region Cyrillic chars - - // russian uppercase "А Б В Г Д Е Ё Ж З И Й К Л М Н О П Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я" - // russian lowercase "а б в г д е ё ж з и й к л м н о п р с т у ф х ц ч ш щ ъ ы ь э ю я" - - // notes - // read http://www.vesic.org/english/blog/c-sharp/transliteration-easy-way-microsoft-transliteration-utility/ - // should we look into MS Transliteration Utility (http://msdn.microsoft.com/en-US/goglobal/bb688104.aspx) - // also UnicodeSharpFork https://bitbucket.org/DimaStefantsov/unidecodesharpfork - // also Transliterator http://transliterator.codeplex.com/ - // - // in any case it would be good to generate all those "case" statements instead of writing them by hand - // time for a T4 template? - // also we should support extensibility so ppl can register more cases in external code - - // TODO: transliterates Анастасия as Anastasiya, and not Anastasia - // Ольга --> Ol'ga, Татьяна --> Tat'yana -- that's bad (?) - // Note: should ä (German umlaut) become a or ae ? - - case '\u0410': // А - output[opos++] = 'A'; - break; - case '\u0430': // а - output[opos++] = 'a'; - break; - case '\u0411': // Б - output[opos++] = 'B'; - break; - case '\u0431': // б - output[opos++] = 'b'; - break; - case '\u0412': // В - output[opos++] = 'V'; - break; - case '\u0432': // в - output[opos++] = 'v'; - break; - case '\u0413': // Г - output[opos++] = 'G'; - break; - case '\u0433': // г - output[opos++] = 'g'; - break; - case '\u0414': // Д - output[opos++] = 'D'; - break; - case '\u0434': // д - output[opos++] = 'd'; - break; - case '\u0415': // Е - output[opos++] = 'E'; - break; - case '\u0435': // е - output[opos++] = 'e'; - break; - case '\u0401': // Ё - output[opos++] = 'E'; // alt. Yo - break; - case '\u0451': // ё - output[opos++] = 'e'; // alt. yo - break; - case '\u0416': // Ж - output[opos++] = 'Z'; - output[opos++] = 'h'; - break; - case '\u0436': // ж - output[opos++] = 'z'; - output[opos++] = 'h'; - break; - case '\u0417': // З - output[opos++] = 'Z'; - break; - case '\u0437': // з - output[opos++] = 'z'; - break; - case '\u0418': // И - output[opos++] = 'I'; - break; - case '\u0438': // и - output[opos++] = 'i'; - break; - case '\u0419': // Й - output[opos++] = 'I'; // alt. Y, J - break; - case '\u0439': // й - output[opos++] = 'i'; // alt. y, j - break; - case '\u041A': // К - output[opos++] = 'K'; - break; - case '\u043A': // к - output[opos++] = 'k'; - break; - case '\u041B': // Л - output[opos++] = 'L'; - break; - case '\u043B': // л - output[opos++] = 'l'; - break; - case '\u041C': // М - output[opos++] = 'M'; - break; - case '\u043C': // м - output[opos++] = 'm'; - break; - case '\u041D': // Н - output[opos++] = 'N'; - break; - case '\u043D': // н - output[opos++] = 'n'; - break; - case '\u041E': // О - output[opos++] = 'O'; - break; - case '\u043E': // о - output[opos++] = 'o'; - break; - case '\u041F': // П - output[opos++] = 'P'; - break; - case '\u043F': // п - output[opos++] = 'p'; - break; - case '\u0420': // Р - output[opos++] = 'R'; - break; - case '\u0440': // р - output[opos++] = 'r'; - break; - case '\u0421': // С - output[opos++] = 'S'; - break; - case '\u0441': // с - output[opos++] = 's'; - break; - case '\u0422': // Т - output[opos++] = 'T'; - break; - case '\u0442': // т - output[opos++] = 't'; - break; - case '\u0423': // У - output[opos++] = 'U'; - break; - case '\u0443': // у - output[opos++] = 'u'; - break; - case '\u0424': // Ф - output[opos++] = 'F'; - break; - case '\u0444': // ф - output[opos++] = 'f'; - break; - case '\u0425': // Х - output[opos++] = 'K'; // alt. X - output[opos++] = 'h'; - break; - case '\u0445': // х - output[opos++] = 'k'; // alt. x - output[opos++] = 'h'; - break; - case '\u0426': // Ц - output[opos++] = 'F'; - break; - case '\u0446': // ц - output[opos++] = 'f'; - break; - case '\u0427': // Ч - output[opos++] = 'C'; // alt. Ts, C - output[opos++] = 'h'; - break; - case '\u0447': // ч - output[opos++] = 'c'; // alt. ts, c - output[opos++] = 'h'; - break; - case '\u0428': // Ш - output[opos++] = 'S'; // alt. Ch, S - output[opos++] = 'h'; - break; - case '\u0448': // ш - output[opos++] = 's'; // alt. ch, s - output[opos++] = 'h'; - break; - case '\u0429': // Щ - output[opos++] = 'S'; // alt. Shch, Sc - output[opos++] = 'h'; - break; - case '\u0449': // щ - output[opos++] = 's'; // alt. shch, sc - output[opos++] = 'h'; - break; - case '\u042A': // Ъ - output[opos++] = '"'; // " - break; - case '\u044A': // ъ - output[opos++] = '"'; // " - break; - case '\u042B': // Ы - output[opos++] = 'Y'; - break; - case '\u044B': // ы - output[opos++] = 'y'; - break; - case '\u042C': // Ь - output[opos++] = '\''; // ' - break; - case '\u044C': // ь - output[opos++] = '\''; // ' - break; - case '\u042D': // Э - output[opos++] = 'E'; - break; - case '\u044D': // э - output[opos++] = 'e'; - break; - case '\u042E': // Ю - output[opos++] = 'Y'; // alt. Ju - output[opos++] = 'u'; - break; - case '\u044E': // ю - output[opos++] = 'y'; // alt. ju - output[opos++] = 'u'; - break; - case '\u042F': // Я - output[opos++] = 'Y'; // alt. Ja - output[opos++] = 'a'; - break; - case '\u044F': // я - output[opos++] = 'y'; // alt. ja - output[opos++] = 'a'; - break; - - #endregion - - // BEGIN EXTRA - /* - case '£': - output[opos++] = 'G'; - output[opos++] = 'B'; - output[opos++] = 'P'; + case '\u00E0': + // à [LATIN SMALL LETTER A WITH GRAVE] + case '\u00E1': + // á [LATIN SMALL LETTER A WITH ACUTE] + case '\u00E2': + // â [LATIN SMALL LETTER A WITH CIRCUMFLEX] + case '\u00E3': + // ã [LATIN SMALL LETTER A WITH TILDE] + case '\u00E4': + // ä [LATIN SMALL LETTER A WITH DIAERESIS] + case '\u00E5': + // Ã¥ [LATIN SMALL LETTER A WITH RING ABOVE] + case '\u0101': + // � [LATIN SMALL LETTER A WITH MACRON] + case '\u0103': + // ă [LATIN SMALL LETTER A WITH BREVE] + case '\u0105': + // Ä… [LATIN SMALL LETTER A WITH OGONEK] + case '\u01CE': + // ÇŽ [LATIN SMALL LETTER A WITH CARON] + case '\u01DF': + // ÇŸ [LATIN SMALL LETTER A WITH DIAERESIS AND MACRON] + case '\u01E1': + // Ç¡ [LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON] + case '\u01FB': + // Ç» [LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE] + case '\u0201': + // � [LATIN SMALL LETTER A WITH DOUBLE GRAVE] + case '\u0203': + // ȃ [LATIN SMALL LETTER A WITH INVERTED BREVE] + case '\u0227': + // ȧ [LATIN SMALL LETTER A WITH DOT ABOVE] + case '\u0250': + // � [LATIN SMALL LETTER TURNED A] + case '\u0259': + // É™ [LATIN SMALL LETTER SCHWA] + case '\u025A': + // Éš [LATIN SMALL LETTER SCHWA WITH HOOK] + case '\u1D8F': + // � [LATIN SMALL LETTER A WITH RETROFLEX HOOK] + case '\u1D95': + // ᶕ [LATIN SMALL LETTER SCHWA WITH RETROFLEX HOOK] + case '\u1E01': + // ạ [LATIN SMALL LETTER A WITH RING BELOW] + case '\u1E9A': + // ả [LATIN SMALL LETTER A WITH RIGHT HALF RING] + case '\u1EA1': + // ạ [LATIN SMALL LETTER A WITH DOT BELOW] + case '\u1EA3': + // ả [LATIN SMALL LETTER A WITH HOOK ABOVE] + case '\u1EA5': + // ấ [LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE] + case '\u1EA7': + // ầ [LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE] + case '\u1EA9': + // ẩ [LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE] + case '\u1EAB': + // ẫ [LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE] + case '\u1EAD': + // ậ [LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW] + case '\u1EAF': + // ắ [LATIN SMALL LETTER A WITH BREVE AND ACUTE] + case '\u1EB1': + // ằ [LATIN SMALL LETTER A WITH BREVE AND GRAVE] + case '\u1EB3': + // ẳ [LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE] + case '\u1EB5': + // ẵ [LATIN SMALL LETTER A WITH BREVE AND TILDE] + case '\u1EB7': + // ặ [LATIN SMALL LETTER A WITH BREVE AND DOT BELOW] + case '\u2090': + // � [LATIN SUBSCRIPT SMALL LETTER A] + case '\u2094': + // �? [LATIN SUBSCRIPT SMALL LETTER SCHWA] + case '\u24D0': + // � [CIRCLED LATIN SMALL LETTER A] + case '\u2C65': + // â±¥ [LATIN SMALL LETTER A WITH STROKE] + case '\u2C6F': + // Ɐ [LATIN CAPITAL LETTER TURNED A] + case '\uFF41': // � [FULLWIDTH LATIN SMALL LETTER A] + output[opos++] = 'a'; + break; + + case '\uA732': // Ꜳ [LATIN CAPITAL LETTER AA] + output[opos++] = 'A'; + output[opos++] = 'A'; break; - case '€': + case '\u00C6': + // Æ [LATIN CAPITAL LETTER AE] + case '\u01E2': + // Ç¢ [LATIN CAPITAL LETTER AE WITH MACRON] + case '\u01FC': + // Ǽ [LATIN CAPITAL LETTER AE WITH ACUTE] + case '\u1D01': // á´� [LATIN LETTER SMALL CAPITAL AE] + output[opos++] = 'A'; output[opos++] = 'E'; + break; + + case '\uA734': // Ꜵ [LATIN CAPITAL LETTER AO] + output[opos++] = 'A'; + output[opos++] = 'O'; + break; + + case '\uA736': // Ꜷ [LATIN CAPITAL LETTER AU] + output[opos++] = 'A'; output[opos++] = 'U'; - output[opos++] = 'R'; break; - case '©': + case '\uA738': + // Ꜹ [LATIN CAPITAL LETTER AV] + case '\uA73A': // Ꜻ [LATIN CAPITAL LETTER AV WITH HORIZONTAL BAR] + output[opos++] = 'A'; + output[opos++] = 'V'; + break; + + case '\uA73C': // Ꜽ [LATIN CAPITAL LETTER AY] + output[opos++] = 'A'; + output[opos++] = 'Y'; + break; + + case '\u249C': // â’œ [PARENTHESIZED LATIN SMALL LETTER A] + output[opos++] = '('; + output[opos++] = 'a'; + output[opos++] = ')'; + break; + + case '\uA733': // ꜳ [LATIN SMALL LETTER AA] + output[opos++] = 'a'; + output[opos++] = 'a'; + break; + + case '\u00E6': + // æ [LATIN SMALL LETTER AE] + case '\u01E3': + // Ç£ [LATIN SMALL LETTER AE WITH MACRON] + case '\u01FD': + // ǽ [LATIN SMALL LETTER AE WITH ACUTE] + case '\u1D02': // á´‚ [LATIN SMALL LETTER TURNED AE] + output[opos++] = 'a'; + output[opos++] = 'e'; + break; + + case '\uA735': // ꜵ [LATIN SMALL LETTER AO] + output[opos++] = 'a'; + output[opos++] = 'o'; + break; + + case '\uA737': // ꜷ [LATIN SMALL LETTER AU] + output[opos++] = 'a'; + output[opos++] = 'u'; + break; + + case '\uA739': + // ꜹ [LATIN SMALL LETTER AV] + case '\uA73B': // ꜻ [LATIN SMALL LETTER AV WITH HORIZONTAL BAR] + output[opos++] = 'a'; + output[opos++] = 'v'; + break; + + case '\uA73D': // ꜽ [LATIN SMALL LETTER AY] + output[opos++] = 'a'; + output[opos++] = 'y'; + break; + + case '\u0181': + // � [LATIN CAPITAL LETTER B WITH HOOK] + case '\u0182': + // Æ‚ [LATIN CAPITAL LETTER B WITH TOPBAR] + case '\u0243': + // Ƀ [LATIN CAPITAL LETTER B WITH STROKE] + case '\u0299': + // Ê™ [LATIN LETTER SMALL CAPITAL B] + case '\u1D03': + // á´ƒ [LATIN LETTER SMALL CAPITAL BARRED B] + case '\u1E02': + // Ḃ [LATIN CAPITAL LETTER B WITH DOT ABOVE] + case '\u1E04': + // Ḅ [LATIN CAPITAL LETTER B WITH DOT BELOW] + case '\u1E06': + // Ḇ [LATIN CAPITAL LETTER B WITH LINE BELOW] + case '\u24B7': + // â’· [CIRCLED LATIN CAPITAL LETTER B] + case '\uFF22': // ï¼¢ [FULLWIDTH LATIN CAPITAL LETTER B] + output[opos++] = 'B'; + break; + + case '\u0180': + // Æ€ [LATIN SMALL LETTER B WITH STROKE] + case '\u0183': + // ƃ [LATIN SMALL LETTER B WITH TOPBAR] + case '\u0253': + // É“ [LATIN SMALL LETTER B WITH HOOK] + case '\u1D6C': + // ᵬ [LATIN SMALL LETTER B WITH MIDDLE TILDE] + case '\u1D80': + // ᶀ [LATIN SMALL LETTER B WITH PALATAL HOOK] + case '\u1E03': + // ḃ [LATIN SMALL LETTER B WITH DOT ABOVE] + case '\u1E05': + // ḅ [LATIN SMALL LETTER B WITH DOT BELOW] + case '\u1E07': + // ḇ [LATIN SMALL LETTER B WITH LINE BELOW] + case '\u24D1': + // â“‘ [CIRCLED LATIN SMALL LETTER B] + case '\uFF42': // b [FULLWIDTH LATIN SMALL LETTER B] + output[opos++] = 'b'; + break; + + case '\u249D': // â’� [PARENTHESIZED LATIN SMALL LETTER B] output[opos++] = '('; + output[opos++] = 'b'; + output[opos++] = ')'; + break; + + case '\u00C7': + // Ç [LATIN CAPITAL LETTER C WITH CEDILLA] + case '\u0106': + // Ć [LATIN CAPITAL LETTER C WITH ACUTE] + case '\u0108': + // Ĉ [LATIN CAPITAL LETTER C WITH CIRCUMFLEX] + case '\u010A': + // ÄŠ [LATIN CAPITAL LETTER C WITH DOT ABOVE] + case '\u010C': + // ÄŒ [LATIN CAPITAL LETTER C WITH CARON] + case '\u0187': + // Ƈ [LATIN CAPITAL LETTER C WITH HOOK] + case '\u023B': + // È» [LATIN CAPITAL LETTER C WITH STROKE] + case '\u0297': + // Ê— [LATIN LETTER STRETCHED C] + case '\u1D04': + // á´„ [LATIN LETTER SMALL CAPITAL C] + case '\u1E08': + // Ḉ [LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE] + case '\u24B8': + // â’¸ [CIRCLED LATIN CAPITAL LETTER C] + case '\uFF23': // ï¼£ [FULLWIDTH LATIN CAPITAL LETTER C] output[opos++] = 'C'; + break; + + case '\u00E7': + // ç [LATIN SMALL LETTER C WITH CEDILLA] + case '\u0107': + // ć [LATIN SMALL LETTER C WITH ACUTE] + case '\u0109': + // ĉ [LATIN SMALL LETTER C WITH CIRCUMFLEX] + case '\u010B': + // Ä‹ [LATIN SMALL LETTER C WITH DOT ABOVE] + case '\u010D': + // � [LATIN SMALL LETTER C WITH CARON] + case '\u0188': + // ƈ [LATIN SMALL LETTER C WITH HOOK] + case '\u023C': + // ȼ [LATIN SMALL LETTER C WITH STROKE] + case '\u0255': + // É• [LATIN SMALL LETTER C WITH CURL] + case '\u1E09': + // ḉ [LATIN SMALL LETTER C WITH CEDILLA AND ACUTE] + case '\u2184': + // ↄ [LATIN SMALL LETTER REVERSED C] + case '\u24D2': + // â“’ [CIRCLED LATIN SMALL LETTER C] + case '\uA73E': + // Ꜿ [LATIN CAPITAL LETTER REVERSED C WITH DOT] + case '\uA73F': + // ꜿ [LATIN SMALL LETTER REVERSED C WITH DOT] + case '\uFF43': // c [FULLWIDTH LATIN SMALL LETTER C] + output[opos++] = 'c'; + break; + + case '\u249E': // â’ž [PARENTHESIZED LATIN SMALL LETTER C] + output[opos++] = '('; + output[opos++] = 'c'; output[opos++] = ')'; break; - */ - default: - //if (ToMoreAscii(input, ipos, output, ref opos)) - // break; - //if (!char.IsLetterOrDigit(c)) // that would not catch eg 汉 unfortunately - // output[opos++] = '?'; - //else - // output[opos++] = c; + case '\u00D0': + // � [LATIN CAPITAL LETTER ETH] + case '\u010E': + // ÄŽ [LATIN CAPITAL LETTER D WITH CARON] + case '\u0110': + // � [LATIN CAPITAL LETTER D WITH STROKE] + case '\u0189': + // Ɖ [LATIN CAPITAL LETTER AFRICAN D] + case '\u018A': + // ÆŠ [LATIN CAPITAL LETTER D WITH HOOK] + case '\u018B': + // Æ‹ [LATIN CAPITAL LETTER D WITH TOPBAR] + case '\u1D05': + // á´… [LATIN LETTER SMALL CAPITAL D] + case '\u1D06': + // á´† [LATIN LETTER SMALL CAPITAL ETH] + case '\u1E0A': + // Ḋ [LATIN CAPITAL LETTER D WITH DOT ABOVE] + case '\u1E0C': + // Ḍ [LATIN CAPITAL LETTER D WITH DOT BELOW] + case '\u1E0E': + // Ḏ [LATIN CAPITAL LETTER D WITH LINE BELOW] + case '\u1E10': + // � [LATIN CAPITAL LETTER D WITH CEDILLA] + case '\u1E12': + // Ḓ [LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW] + case '\u24B9': + // â’¹ [CIRCLED LATIN CAPITAL LETTER D] + case '\uA779': + // � [LATIN CAPITAL LETTER INSULAR D] + case '\uFF24': // D [FULLWIDTH LATIN CAPITAL LETTER D] + output[opos++] = 'D'; + break; - // strict ASCII - output[opos++] = fail; + case '\u00F0': + // ð [LATIN SMALL LETTER ETH] + case '\u010F': + // � [LATIN SMALL LETTER D WITH CARON] + case '\u0111': + // Ä‘ [LATIN SMALL LETTER D WITH STROKE] + case '\u018C': + // ÆŒ [LATIN SMALL LETTER D WITH TOPBAR] + case '\u0221': + // È¡ [LATIN SMALL LETTER D WITH CURL] + case '\u0256': + // É– [LATIN SMALL LETTER D WITH TAIL] + case '\u0257': + // É— [LATIN SMALL LETTER D WITH HOOK] + case '\u1D6D': + // áµ­ [LATIN SMALL LETTER D WITH MIDDLE TILDE] + case '\u1D81': + // � [LATIN SMALL LETTER D WITH PALATAL HOOK] + case '\u1D91': + // ᶑ [LATIN SMALL LETTER D WITH HOOK AND TAIL] + case '\u1E0B': + // ḋ [LATIN SMALL LETTER D WITH DOT ABOVE] + case '\u1E0D': + // � [LATIN SMALL LETTER D WITH DOT BELOW] + case '\u1E0F': + // � [LATIN SMALL LETTER D WITH LINE BELOW] + case '\u1E11': + // ḑ [LATIN SMALL LETTER D WITH CEDILLA] + case '\u1E13': + // ḓ [LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW] + case '\u24D3': + // â““ [CIRCLED LATIN SMALL LETTER D] + case '\uA77A': + // � [LATIN SMALL LETTER INSULAR D] + case '\uFF44': // d [FULLWIDTH LATIN SMALL LETTER D] + output[opos++] = 'd'; + break; - break; - } - } - } + case '\u01C4': + // Ç„ [LATIN CAPITAL LETTER DZ WITH CARON] + case '\u01F1': // DZ [LATIN CAPITAL LETTER DZ] + output[opos++] = 'D'; + output[opos++] = 'Z'; + break; - //private static bool ToMoreAscii(char[] input, int ipos, char[] output, ref int opos) - //{ - // var c = input[ipos]; - - // switch (c) - // { - // case '£': - // output[opos++] = 'G'; - // output[opos++] = 'B'; - // output[opos++] = 'P'; - // break; - - // case '€': - // output[opos++] = 'E'; - // output[opos++] = 'U'; - // output[opos++] = 'R'; - // break; - - // case '©': - // output[opos++] = '('; - // output[opos++] = 'C'; - // output[opos++] = ')'; - // break; - - // default: - // return false; - // } - - // return true; - //} - } + case '\u01C5': + // Ç… [LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON] + case '\u01F2': // Dz [LATIN CAPITAL LETTER D WITH SMALL LETTER Z] + output[opos++] = 'D'; + output[opos++] = 'z'; + break; + + case '\u249F': // â’Ÿ [PARENTHESIZED LATIN SMALL LETTER D] + output[opos++] = '('; + output[opos++] = 'd'; + output[opos++] = ')'; + break; + + case '\u0238': // ȸ [LATIN SMALL LETTER DB DIGRAPH] + output[opos++] = 'd'; + output[opos++] = 'b'; + break; + + case '\u01C6': + // dž [LATIN SMALL LETTER DZ WITH CARON] + case '\u01F3': + // dz [LATIN SMALL LETTER DZ] + case '\u02A3': + // Ê£ [LATIN SMALL LETTER DZ DIGRAPH] + case '\u02A5': // Ê¥ [LATIN SMALL LETTER DZ DIGRAPH WITH CURL] + output[opos++] = 'd'; + output[opos++] = 'z'; + break; + + case '\u00C8': + // È [LATIN CAPITAL LETTER E WITH GRAVE] + case '\u00C9': + // É [LATIN CAPITAL LETTER E WITH ACUTE] + case '\u00CA': + // Ê [LATIN CAPITAL LETTER E WITH CIRCUMFLEX] + case '\u00CB': + // Ë [LATIN CAPITAL LETTER E WITH DIAERESIS] + case '\u0112': + // Ä’ [LATIN CAPITAL LETTER E WITH MACRON] + case '\u0114': + // �? [LATIN CAPITAL LETTER E WITH BREVE] + case '\u0116': + // Ä– [LATIN CAPITAL LETTER E WITH DOT ABOVE] + case '\u0118': + // Ę [LATIN CAPITAL LETTER E WITH OGONEK] + case '\u011A': + // Äš [LATIN CAPITAL LETTER E WITH CARON] + case '\u018E': + // ÆŽ [LATIN CAPITAL LETTER REVERSED E] + case '\u0190': + // � [LATIN CAPITAL LETTER OPEN E] + case '\u0204': + // È„ [LATIN CAPITAL LETTER E WITH DOUBLE GRAVE] + case '\u0206': + // Ȇ [LATIN CAPITAL LETTER E WITH INVERTED BREVE] + case '\u0228': + // Ȩ [LATIN CAPITAL LETTER E WITH CEDILLA] + case '\u0246': + // Ɇ [LATIN CAPITAL LETTER E WITH STROKE] + case '\u1D07': + // á´‡ [LATIN LETTER SMALL CAPITAL E] + case '\u1E14': + // �? [LATIN CAPITAL LETTER E WITH MACRON AND GRAVE] + case '\u1E16': + // Ḗ [LATIN CAPITAL LETTER E WITH MACRON AND ACUTE] + case '\u1E18': + // Ḙ [LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW] + case '\u1E1A': + // Ḛ [LATIN CAPITAL LETTER E WITH TILDE BELOW] + case '\u1E1C': + // Ḝ [LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE] + case '\u1EB8': + // Ẹ [LATIN CAPITAL LETTER E WITH DOT BELOW] + case '\u1EBA': + // Ẻ [LATIN CAPITAL LETTER E WITH HOOK ABOVE] + case '\u1EBC': + // Ẽ [LATIN CAPITAL LETTER E WITH TILDE] + case '\u1EBE': + // Ế [LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE] + case '\u1EC0': + // Ề [LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE] + case '\u1EC2': + // Ể [LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE] + case '\u1EC4': + // Ễ [LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE] + case '\u1EC6': + // Ệ [LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW] + case '\u24BA': + // â’º [CIRCLED LATIN CAPITAL LETTER E] + case '\u2C7B': + // â±» [LATIN LETTER SMALL CAPITAL TURNED E] + case '\uFF25': // ï¼¥ [FULLWIDTH LATIN CAPITAL LETTER E] + output[opos++] = 'E'; + break; + + case '\u00E8': + // è [LATIN SMALL LETTER E WITH GRAVE] + case '\u00E9': + // é [LATIN SMALL LETTER E WITH ACUTE] + case '\u00EA': + // ê [LATIN SMALL LETTER E WITH CIRCUMFLEX] + case '\u00EB': + // ë [LATIN SMALL LETTER E WITH DIAERESIS] + case '\u0113': + // Ä“ [LATIN SMALL LETTER E WITH MACRON] + case '\u0115': + // Ä• [LATIN SMALL LETTER E WITH BREVE] + case '\u0117': + // Ä— [LATIN SMALL LETTER E WITH DOT ABOVE] + case '\u0119': + // Ä™ [LATIN SMALL LETTER E WITH OGONEK] + case '\u011B': + // Ä› [LATIN SMALL LETTER E WITH CARON] + case '\u01DD': + // � [LATIN SMALL LETTER TURNED E] + case '\u0205': + // È… [LATIN SMALL LETTER E WITH DOUBLE GRAVE] + case '\u0207': + // ȇ [LATIN SMALL LETTER E WITH INVERTED BREVE] + case '\u0229': + // È© [LATIN SMALL LETTER E WITH CEDILLA] + case '\u0247': + // ɇ [LATIN SMALL LETTER E WITH STROKE] + case '\u0258': + // ɘ [LATIN SMALL LETTER REVERSED E] + case '\u025B': + // É› [LATIN SMALL LETTER OPEN E] + case '\u025C': + // Éœ [LATIN SMALL LETTER REVERSED OPEN E] + case '\u025D': + // � [LATIN SMALL LETTER REVERSED OPEN E WITH HOOK] + case '\u025E': + // Éž [LATIN SMALL LETTER CLOSED REVERSED OPEN E] + case '\u029A': + // Êš [LATIN SMALL LETTER CLOSED OPEN E] + case '\u1D08': + // á´ˆ [LATIN SMALL LETTER TURNED OPEN E] + case '\u1D92': + // ᶒ [LATIN SMALL LETTER E WITH RETROFLEX HOOK] + case '\u1D93': + // ᶓ [LATIN SMALL LETTER OPEN E WITH RETROFLEX HOOK] + case '\u1D94': + // �? [LATIN SMALL LETTER REVERSED OPEN E WITH RETROFLEX HOOK] + case '\u1E15': + // ḕ [LATIN SMALL LETTER E WITH MACRON AND GRAVE] + case '\u1E17': + // ḗ [LATIN SMALL LETTER E WITH MACRON AND ACUTE] + case '\u1E19': + // ḙ [LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW] + case '\u1E1B': + // ḛ [LATIN SMALL LETTER E WITH TILDE BELOW] + case '\u1E1D': + // � [LATIN SMALL LETTER E WITH CEDILLA AND BREVE] + case '\u1EB9': + // ẹ [LATIN SMALL LETTER E WITH DOT BELOW] + case '\u1EBB': + // ẻ [LATIN SMALL LETTER E WITH HOOK ABOVE] + case '\u1EBD': + // ẽ [LATIN SMALL LETTER E WITH TILDE] + case '\u1EBF': + // ế [LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE] + case '\u1EC1': + // � [LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE] + case '\u1EC3': + // ể [LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE] + case '\u1EC5': + // á»… [LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE] + case '\u1EC7': + // ệ [LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW] + case '\u2091': + // â‚‘ [LATIN SUBSCRIPT SMALL LETTER E] + case '\u24D4': + // �? [CIRCLED LATIN SMALL LETTER E] + case '\u2C78': + // ⱸ [LATIN SMALL LETTER E WITH NOTCH] + case '\uFF45': // ï½… [FULLWIDTH LATIN SMALL LETTER E] + output[opos++] = 'e'; + break; + + case '\u24A0': // â’  [PARENTHESIZED LATIN SMALL LETTER E] + output[opos++] = '('; + output[opos++] = 'e'; + output[opos++] = ')'; + break; + + case '\u0191': + // Æ‘ [LATIN CAPITAL LETTER F WITH HOOK] + case '\u1E1E': + // Ḟ [LATIN CAPITAL LETTER F WITH DOT ABOVE] + case '\u24BB': + // â’» [CIRCLED LATIN CAPITAL LETTER F] + case '\uA730': + // ꜰ [LATIN LETTER SMALL CAPITAL F] + case '\uA77B': + // � [LATIN CAPITAL LETTER INSULAR F] + case '\uA7FB': + // ꟻ [LATIN EPIGRAPHIC LETTER REVERSED F] + case '\uFF26': // F [FULLWIDTH LATIN CAPITAL LETTER F] + output[opos++] = 'F'; + break; + + case '\u0192': + // Æ’ [LATIN SMALL LETTER F WITH HOOK] + case '\u1D6E': + // áµ® [LATIN SMALL LETTER F WITH MIDDLE TILDE] + case '\u1D82': + // ᶂ [LATIN SMALL LETTER F WITH PALATAL HOOK] + case '\u1E1F': + // ḟ [LATIN SMALL LETTER F WITH DOT ABOVE] + case '\u1E9B': + // ẛ [LATIN SMALL LETTER LONG S WITH DOT ABOVE] + case '\u24D5': + // â“• [CIRCLED LATIN SMALL LETTER F] + case '\uA77C': + // � [LATIN SMALL LETTER INSULAR F] + case '\uFF46': // f [FULLWIDTH LATIN SMALL LETTER F] + output[opos++] = 'f'; + break; + + case '\u24A1': // â’¡ [PARENTHESIZED LATIN SMALL LETTER F] + output[opos++] = '('; + output[opos++] = 'f'; + output[opos++] = ')'; + break; + + case '\uFB00': // ff [LATIN SMALL LIGATURE FF] + output[opos++] = 'f'; + output[opos++] = 'f'; + break; + + case '\uFB03': // ffi [LATIN SMALL LIGATURE FFI] + output[opos++] = 'f'; + output[opos++] = 'f'; + output[opos++] = 'i'; + break; + + case '\uFB04': // ffl [LATIN SMALL LIGATURE FFL] + output[opos++] = 'f'; + output[opos++] = 'f'; + output[opos++] = 'l'; + break; + + case '\uFB01': // � [LATIN SMALL LIGATURE FI] + output[opos++] = 'f'; + output[opos++] = 'i'; + break; + + case '\uFB02': // fl [LATIN SMALL LIGATURE FL] + output[opos++] = 'f'; + output[opos++] = 'l'; + break; + + case '\u011C': + // Äœ [LATIN CAPITAL LETTER G WITH CIRCUMFLEX] + case '\u011E': + // Äž [LATIN CAPITAL LETTER G WITH BREVE] + case '\u0120': + // Ä  [LATIN CAPITAL LETTER G WITH DOT ABOVE] + case '\u0122': + // Ä¢ [LATIN CAPITAL LETTER G WITH CEDILLA] + case '\u0193': + // Æ“ [LATIN CAPITAL LETTER G WITH HOOK] + case '\u01E4': + // Ǥ [LATIN CAPITAL LETTER G WITH STROKE] + case '\u01E5': + // Ç¥ [LATIN SMALL LETTER G WITH STROKE] + case '\u01E6': + // Ǧ [LATIN CAPITAL LETTER G WITH CARON] + case '\u01E7': + // ǧ [LATIN SMALL LETTER G WITH CARON] + case '\u01F4': + // Ç´ [LATIN CAPITAL LETTER G WITH ACUTE] + case '\u0262': + // É¢ [LATIN LETTER SMALL CAPITAL G] + case '\u029B': + // Ê› [LATIN LETTER SMALL CAPITAL G WITH HOOK] + case '\u1E20': + // Ḡ [LATIN CAPITAL LETTER G WITH MACRON] + case '\u24BC': + // â’¼ [CIRCLED LATIN CAPITAL LETTER G] + case '\uA77D': + // � [LATIN CAPITAL LETTER INSULAR G] + case '\uA77E': + // � [LATIN CAPITAL LETTER TURNED INSULAR G] + case '\uFF27': // G [FULLWIDTH LATIN CAPITAL LETTER G] + output[opos++] = 'G'; + break; + + case '\u011D': + // � [LATIN SMALL LETTER G WITH CIRCUMFLEX] + case '\u011F': + // ÄŸ [LATIN SMALL LETTER G WITH BREVE] + case '\u0121': + // Ä¡ [LATIN SMALL LETTER G WITH DOT ABOVE] + case '\u0123': + // Ä£ [LATIN SMALL LETTER G WITH CEDILLA] + case '\u01F5': + // ǵ [LATIN SMALL LETTER G WITH ACUTE] + case '\u0260': + // É  [LATIN SMALL LETTER G WITH HOOK] + case '\u0261': + // É¡ [LATIN SMALL LETTER SCRIPT G] + case '\u1D77': + // áµ· [LATIN SMALL LETTER TURNED G] + case '\u1D79': + // áµ¹ [LATIN SMALL LETTER INSULAR G] + case '\u1D83': + // ᶃ [LATIN SMALL LETTER G WITH PALATAL HOOK] + case '\u1E21': + // ḡ [LATIN SMALL LETTER G WITH MACRON] + case '\u24D6': + // â“– [CIRCLED LATIN SMALL LETTER G] + case '\uA77F': + // � [LATIN SMALL LETTER TURNED INSULAR G] + case '\uFF47': // g [FULLWIDTH LATIN SMALL LETTER G] + output[opos++] = 'g'; + break; + + case '\u24A2': // â’¢ [PARENTHESIZED LATIN SMALL LETTER G] + output[opos++] = '('; + output[opos++] = 'g'; + output[opos++] = ')'; + break; + + case '\u0124': + // Ĥ [LATIN CAPITAL LETTER H WITH CIRCUMFLEX] + case '\u0126': + // Ħ [LATIN CAPITAL LETTER H WITH STROKE] + case '\u021E': + // Èž [LATIN CAPITAL LETTER H WITH CARON] + case '\u029C': + // Êœ [LATIN LETTER SMALL CAPITAL H] + case '\u1E22': + // Ḣ [LATIN CAPITAL LETTER H WITH DOT ABOVE] + case '\u1E24': + // Ḥ [LATIN CAPITAL LETTER H WITH DOT BELOW] + case '\u1E26': + // Ḧ [LATIN CAPITAL LETTER H WITH DIAERESIS] + case '\u1E28': + // Ḩ [LATIN CAPITAL LETTER H WITH CEDILLA] + case '\u1E2A': + // Ḫ [LATIN CAPITAL LETTER H WITH BREVE BELOW] + case '\u24BD': + // â’½ [CIRCLED LATIN CAPITAL LETTER H] + case '\u2C67': + // Ⱨ [LATIN CAPITAL LETTER H WITH DESCENDER] + case '\u2C75': + // â±µ [LATIN CAPITAL LETTER HALF H] + case '\uFF28': // H [FULLWIDTH LATIN CAPITAL LETTER H] + output[opos++] = 'H'; + break; + + case '\u0125': + // Ä¥ [LATIN SMALL LETTER H WITH CIRCUMFLEX] + case '\u0127': + // ħ [LATIN SMALL LETTER H WITH STROKE] + case '\u021F': + // ÈŸ [LATIN SMALL LETTER H WITH CARON] + case '\u0265': + // É¥ [LATIN SMALL LETTER TURNED H] + case '\u0266': + // ɦ [LATIN SMALL LETTER H WITH HOOK] + case '\u02AE': + // Ê® [LATIN SMALL LETTER TURNED H WITH FISHHOOK] + case '\u02AF': + // ʯ [LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL] + case '\u1E23': + // ḣ [LATIN SMALL LETTER H WITH DOT ABOVE] + case '\u1E25': + // ḥ [LATIN SMALL LETTER H WITH DOT BELOW] + case '\u1E27': + // ḧ [LATIN SMALL LETTER H WITH DIAERESIS] + case '\u1E29': + // ḩ [LATIN SMALL LETTER H WITH CEDILLA] + case '\u1E2B': + // ḫ [LATIN SMALL LETTER H WITH BREVE BELOW] + case '\u1E96': + // ẖ [LATIN SMALL LETTER H WITH LINE BELOW] + case '\u24D7': + // â“— [CIRCLED LATIN SMALL LETTER H] + case '\u2C68': + // ⱨ [LATIN SMALL LETTER H WITH DESCENDER] + case '\u2C76': + // ⱶ [LATIN SMALL LETTER HALF H] + case '\uFF48': // h [FULLWIDTH LATIN SMALL LETTER H] + output[opos++] = 'h'; + break; + + case '\u01F6': // Ƕ http://en.wikipedia.org/wiki/Hwair [LATIN CAPITAL LETTER HWAIR] + output[opos++] = 'H'; + output[opos++] = 'V'; + break; + + case '\u24A3': // â’£ [PARENTHESIZED LATIN SMALL LETTER H] + output[opos++] = '('; + output[opos++] = 'h'; + output[opos++] = ')'; + break; + + case '\u0195': // Æ• [LATIN SMALL LETTER HV] + output[opos++] = 'h'; + output[opos++] = 'v'; + break; + + case '\u00CC': + // ÃŒ [LATIN CAPITAL LETTER I WITH GRAVE] + case '\u00CD': + // � [LATIN CAPITAL LETTER I WITH ACUTE] + case '\u00CE': + // ÃŽ [LATIN CAPITAL LETTER I WITH CIRCUMFLEX] + case '\u00CF': + // � [LATIN CAPITAL LETTER I WITH DIAERESIS] + case '\u0128': + // Ĩ [LATIN CAPITAL LETTER I WITH TILDE] + case '\u012A': + // Ī [LATIN CAPITAL LETTER I WITH MACRON] + case '\u012C': + // Ĭ [LATIN CAPITAL LETTER I WITH BREVE] + case '\u012E': + // Ä® [LATIN CAPITAL LETTER I WITH OGONEK] + case '\u0130': + // Ä° [LATIN CAPITAL LETTER I WITH DOT ABOVE] + case '\u0196': + // Æ– [LATIN CAPITAL LETTER IOTA] + case '\u0197': + // Æ— [LATIN CAPITAL LETTER I WITH STROKE] + case '\u01CF': + // � [LATIN CAPITAL LETTER I WITH CARON] + case '\u0208': + // Ȉ [LATIN CAPITAL LETTER I WITH DOUBLE GRAVE] + case '\u020A': + // ÈŠ [LATIN CAPITAL LETTER I WITH INVERTED BREVE] + case '\u026A': + // ɪ [LATIN LETTER SMALL CAPITAL I] + case '\u1D7B': + // áµ» [LATIN SMALL CAPITAL LETTER I WITH STROKE] + case '\u1E2C': + // Ḭ [LATIN CAPITAL LETTER I WITH TILDE BELOW] + case '\u1E2E': + // Ḯ [LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE] + case '\u1EC8': + // Ỉ [LATIN CAPITAL LETTER I WITH HOOK ABOVE] + case '\u1ECA': + // Ị [LATIN CAPITAL LETTER I WITH DOT BELOW] + case '\u24BE': + // â’¾ [CIRCLED LATIN CAPITAL LETTER I] + case '\uA7FE': + // ꟾ [LATIN EPIGRAPHIC LETTER I LONGA] + case '\uFF29': // I [FULLWIDTH LATIN CAPITAL LETTER I] + output[opos++] = 'I'; + break; + + case '\u00EC': + // ì [LATIN SMALL LETTER I WITH GRAVE] + case '\u00ED': + // í [LATIN SMALL LETTER I WITH ACUTE] + case '\u00EE': + // î [LATIN SMALL LETTER I WITH CIRCUMFLEX] + case '\u00EF': + // ï [LATIN SMALL LETTER I WITH DIAERESIS] + case '\u0129': + // Ä© [LATIN SMALL LETTER I WITH TILDE] + case '\u012B': + // Ä« [LATIN SMALL LETTER I WITH MACRON] + case '\u012D': + // Ä­ [LATIN SMALL LETTER I WITH BREVE] + case '\u012F': + // į [LATIN SMALL LETTER I WITH OGONEK] + case '\u0131': + // ı [LATIN SMALL LETTER DOTLESS I] + case '\u01D0': + // � [LATIN SMALL LETTER I WITH CARON] + case '\u0209': + // ȉ [LATIN SMALL LETTER I WITH DOUBLE GRAVE] + case '\u020B': + // È‹ [LATIN SMALL LETTER I WITH INVERTED BREVE] + case '\u0268': + // ɨ [LATIN SMALL LETTER I WITH STROKE] + case '\u1D09': + // á´‰ [LATIN SMALL LETTER TURNED I] + case '\u1D62': + // áµ¢ [LATIN SUBSCRIPT SMALL LETTER I] + case '\u1D7C': + // áµ¼ [LATIN SMALL LETTER IOTA WITH STROKE] + case '\u1D96': + // ᶖ [LATIN SMALL LETTER I WITH RETROFLEX HOOK] + case '\u1E2D': + // ḭ [LATIN SMALL LETTER I WITH TILDE BELOW] + case '\u1E2F': + // ḯ [LATIN SMALL LETTER I WITH DIAERESIS AND ACUTE] + case '\u1EC9': + // ỉ [LATIN SMALL LETTER I WITH HOOK ABOVE] + case '\u1ECB': + // ị [LATIN SMALL LETTER I WITH DOT BELOW] + case '\u2071': + // � [SUPERSCRIPT LATIN SMALL LETTER I] + case '\u24D8': + // ⓘ [CIRCLED LATIN SMALL LETTER I] + case '\uFF49': // i [FULLWIDTH LATIN SMALL LETTER I] + output[opos++] = 'i'; + break; + + case '\u0132': // IJ [LATIN CAPITAL LIGATURE IJ] + output[opos++] = 'I'; + output[opos++] = 'J'; + break; + + case '\u24A4': // â’¤ [PARENTHESIZED LATIN SMALL LETTER I] + output[opos++] = '('; + output[opos++] = 'i'; + output[opos++] = ')'; + break; + + case '\u0133': // ij [LATIN SMALL LIGATURE IJ] + output[opos++] = 'i'; + output[opos++] = 'j'; + break; + + case '\u0134': + // Ä´ [LATIN CAPITAL LETTER J WITH CIRCUMFLEX] + case '\u0248': + // Ɉ [LATIN CAPITAL LETTER J WITH STROKE] + case '\u1D0A': + // á´Š [LATIN LETTER SMALL CAPITAL J] + case '\u24BF': + // â’¿ [CIRCLED LATIN CAPITAL LETTER J] + case '\uFF2A': // J [FULLWIDTH LATIN CAPITAL LETTER J] + output[opos++] = 'J'; + break; + + case '\u0135': + // ĵ [LATIN SMALL LETTER J WITH CIRCUMFLEX] + case '\u01F0': + // Ç° [LATIN SMALL LETTER J WITH CARON] + case '\u0237': + // È· [LATIN SMALL LETTER DOTLESS J] + case '\u0249': + // ɉ [LATIN SMALL LETTER J WITH STROKE] + case '\u025F': + // ÉŸ [LATIN SMALL LETTER DOTLESS J WITH STROKE] + case '\u0284': + // Ê„ [LATIN SMALL LETTER DOTLESS J WITH STROKE AND HOOK] + case '\u029D': + // � [LATIN SMALL LETTER J WITH CROSSED-TAIL] + case '\u24D9': + // â“™ [CIRCLED LATIN SMALL LETTER J] + case '\u2C7C': + // â±¼ [LATIN SUBSCRIPT SMALL LETTER J] + case '\uFF4A': // j [FULLWIDTH LATIN SMALL LETTER J] + output[opos++] = 'j'; + break; + + case '\u24A5': // â’¥ [PARENTHESIZED LATIN SMALL LETTER J] + output[opos++] = '('; + output[opos++] = 'j'; + output[opos++] = ')'; + break; + + case '\u0136': + // Ķ [LATIN CAPITAL LETTER K WITH CEDILLA] + case '\u0198': + // Ƙ [LATIN CAPITAL LETTER K WITH HOOK] + case '\u01E8': + // Ǩ [LATIN CAPITAL LETTER K WITH CARON] + case '\u1D0B': + // á´‹ [LATIN LETTER SMALL CAPITAL K] + case '\u1E30': + // Ḱ [LATIN CAPITAL LETTER K WITH ACUTE] + case '\u1E32': + // Ḳ [LATIN CAPITAL LETTER K WITH DOT BELOW] + case '\u1E34': + // Ḵ [LATIN CAPITAL LETTER K WITH LINE BELOW] + case '\u24C0': + // â“€ [CIRCLED LATIN CAPITAL LETTER K] + case '\u2C69': + // Ⱪ [LATIN CAPITAL LETTER K WITH DESCENDER] + case '\uA740': + // � [LATIN CAPITAL LETTER K WITH STROKE] + case '\uA742': + // � [LATIN CAPITAL LETTER K WITH DIAGONAL STROKE] + case '\uA744': + // � [LATIN CAPITAL LETTER K WITH STROKE AND DIAGONAL STROKE] + case '\uFF2B': // K [FULLWIDTH LATIN CAPITAL LETTER K] + output[opos++] = 'K'; + break; + + case '\u0137': + // Ä· [LATIN SMALL LETTER K WITH CEDILLA] + case '\u0199': + // Æ™ [LATIN SMALL LETTER K WITH HOOK] + case '\u01E9': + // Ç© [LATIN SMALL LETTER K WITH CARON] + case '\u029E': + // Êž [LATIN SMALL LETTER TURNED K] + case '\u1D84': + // ᶄ [LATIN SMALL LETTER K WITH PALATAL HOOK] + case '\u1E31': + // ḱ [LATIN SMALL LETTER K WITH ACUTE] + case '\u1E33': + // ḳ [LATIN SMALL LETTER K WITH DOT BELOW] + case '\u1E35': + // ḵ [LATIN SMALL LETTER K WITH LINE BELOW] + case '\u24DA': + // â“š [CIRCLED LATIN SMALL LETTER K] + case '\u2C6A': + // ⱪ [LATIN SMALL LETTER K WITH DESCENDER] + case '\uA741': + // � [LATIN SMALL LETTER K WITH STROKE] + case '\uA743': + // � [LATIN SMALL LETTER K WITH DIAGONAL STROKE] + case '\uA745': + // � [LATIN SMALL LETTER K WITH STROKE AND DIAGONAL STROKE] + case '\uFF4B': // k [FULLWIDTH LATIN SMALL LETTER K] + output[opos++] = 'k'; + break; + + case '\u24A6': // â’¦ [PARENTHESIZED LATIN SMALL LETTER K] + output[opos++] = '('; + output[opos++] = 'k'; + output[opos++] = ')'; + break; + + case '\u0139': + // Ĺ [LATIN CAPITAL LETTER L WITH ACUTE] + case '\u013B': + // Ä» [LATIN CAPITAL LETTER L WITH CEDILLA] + case '\u013D': + // Ľ [LATIN CAPITAL LETTER L WITH CARON] + case '\u013F': + // Ä¿ [LATIN CAPITAL LETTER L WITH MIDDLE DOT] + case '\u0141': + // � [LATIN CAPITAL LETTER L WITH STROKE] + case '\u023D': + // Ƚ [LATIN CAPITAL LETTER L WITH BAR] + case '\u029F': + // ÊŸ [LATIN LETTER SMALL CAPITAL L] + case '\u1D0C': + // á´Œ [LATIN LETTER SMALL CAPITAL L WITH STROKE] + case '\u1E36': + // Ḷ [LATIN CAPITAL LETTER L WITH DOT BELOW] + case '\u1E38': + // Ḹ [LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON] + case '\u1E3A': + // Ḻ [LATIN CAPITAL LETTER L WITH LINE BELOW] + case '\u1E3C': + // Ḽ [LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW] + case '\u24C1': + // � [CIRCLED LATIN CAPITAL LETTER L] + case '\u2C60': + // â±  [LATIN CAPITAL LETTER L WITH DOUBLE BAR] + case '\u2C62': + // â±¢ [LATIN CAPITAL LETTER L WITH MIDDLE TILDE] + case '\uA746': + // � [LATIN CAPITAL LETTER BROKEN L] + case '\uA748': + // � [LATIN CAPITAL LETTER L WITH HIGH STROKE] + case '\uA780': + // Ꞁ [LATIN CAPITAL LETTER TURNED L] + case '\uFF2C': // L [FULLWIDTH LATIN CAPITAL LETTER L] + output[opos++] = 'L'; + break; + + case '\u013A': + // ĺ [LATIN SMALL LETTER L WITH ACUTE] + case '\u013C': + // ļ [LATIN SMALL LETTER L WITH CEDILLA] + case '\u013E': + // ľ [LATIN SMALL LETTER L WITH CARON] + case '\u0140': + // Å€ [LATIN SMALL LETTER L WITH MIDDLE DOT] + case '\u0142': + // Å‚ [LATIN SMALL LETTER L WITH STROKE] + case '\u019A': + // Æš [LATIN SMALL LETTER L WITH BAR] + case '\u0234': + // È´ [LATIN SMALL LETTER L WITH CURL] + case '\u026B': + // É« [LATIN SMALL LETTER L WITH MIDDLE TILDE] + case '\u026C': + // ɬ [LATIN SMALL LETTER L WITH BELT] + case '\u026D': + // É­ [LATIN SMALL LETTER L WITH RETROFLEX HOOK] + case '\u1D85': + // ᶅ [LATIN SMALL LETTER L WITH PALATAL HOOK] + case '\u1E37': + // ḷ [LATIN SMALL LETTER L WITH DOT BELOW] + case '\u1E39': + // ḹ [LATIN SMALL LETTER L WITH DOT BELOW AND MACRON] + case '\u1E3B': + // ḻ [LATIN SMALL LETTER L WITH LINE BELOW] + case '\u1E3D': + // ḽ [LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW] + case '\u24DB': + // â“› [CIRCLED LATIN SMALL LETTER L] + case '\u2C61': + // ⱡ [LATIN SMALL LETTER L WITH DOUBLE BAR] + case '\uA747': + // � [LATIN SMALL LETTER BROKEN L] + case '\uA749': + // � [LATIN SMALL LETTER L WITH HIGH STROKE] + case '\uA781': + // � [LATIN SMALL LETTER TURNED L] + case '\uFF4C': // l [FULLWIDTH LATIN SMALL LETTER L] + output[opos++] = 'l'; + break; + + case '\u01C7': // LJ [LATIN CAPITAL LETTER LJ] + output[opos++] = 'L'; + output[opos++] = 'J'; + break; + + case '\u1EFA': // Ỻ [LATIN CAPITAL LETTER MIDDLE-WELSH LL] + output[opos++] = 'L'; + output[opos++] = 'L'; + break; + + case '\u01C8': // Lj [LATIN CAPITAL LETTER L WITH SMALL LETTER J] + output[opos++] = 'L'; + output[opos++] = 'j'; + break; + + case '\u24A7': // â’§ [PARENTHESIZED LATIN SMALL LETTER L] + output[opos++] = '('; + output[opos++] = 'l'; + output[opos++] = ')'; + break; + + case '\u01C9': // lj [LATIN SMALL LETTER LJ] + output[opos++] = 'l'; + output[opos++] = 'j'; + break; + + case '\u1EFB': // á»» [LATIN SMALL LETTER MIDDLE-WELSH LL] + output[opos++] = 'l'; + output[opos++] = 'l'; + break; + + case '\u02AA': // ʪ [LATIN SMALL LETTER LS DIGRAPH] + output[opos++] = 'l'; + output[opos++] = 's'; + break; + + case '\u02AB': // Ê« [LATIN SMALL LETTER LZ DIGRAPH] + output[opos++] = 'l'; + output[opos++] = 'z'; + break; + + case '\u019C': + // Æœ [LATIN CAPITAL LETTER TURNED M] + case '\u1D0D': + // á´� [LATIN LETTER SMALL CAPITAL M] + case '\u1E3E': + // Ḿ [LATIN CAPITAL LETTER M WITH ACUTE] + case '\u1E40': + // á¹€ [LATIN CAPITAL LETTER M WITH DOT ABOVE] + case '\u1E42': + // Ṃ [LATIN CAPITAL LETTER M WITH DOT BELOW] + case '\u24C2': + // â“‚ [CIRCLED LATIN CAPITAL LETTER M] + case '\u2C6E': + // â±® [LATIN CAPITAL LETTER M WITH HOOK] + case '\uA7FD': + // ꟽ [LATIN EPIGRAPHIC LETTER INVERTED M] + case '\uA7FF': + // ꟿ [LATIN EPIGRAPHIC LETTER ARCHAIC M] + case '\uFF2D': // ï¼­ [FULLWIDTH LATIN CAPITAL LETTER M] + output[opos++] = 'M'; + break; + + case '\u026F': + // ɯ [LATIN SMALL LETTER TURNED M] + case '\u0270': + // É° [LATIN SMALL LETTER TURNED M WITH LONG LEG] + case '\u0271': + // ɱ [LATIN SMALL LETTER M WITH HOOK] + case '\u1D6F': + // ᵯ [LATIN SMALL LETTER M WITH MIDDLE TILDE] + case '\u1D86': + // ᶆ [LATIN SMALL LETTER M WITH PALATAL HOOK] + case '\u1E3F': + // ḿ [LATIN SMALL LETTER M WITH ACUTE] + case '\u1E41': + // � [LATIN SMALL LETTER M WITH DOT ABOVE] + case '\u1E43': + // ṃ [LATIN SMALL LETTER M WITH DOT BELOW] + case '\u24DC': + // â“œ [CIRCLED LATIN SMALL LETTER M] + case '\uFF4D': // � [FULLWIDTH LATIN SMALL LETTER M] + output[opos++] = 'm'; + break; + + case '\u24A8': // â’¨ [PARENTHESIZED LATIN SMALL LETTER M] + output[opos++] = '('; + output[opos++] = 'm'; + output[opos++] = ')'; + break; + + case '\u00D1': + // Ñ [LATIN CAPITAL LETTER N WITH TILDE] + case '\u0143': + // Ã…Æ’ [LATIN CAPITAL LETTER N WITH ACUTE] + case '\u0145': + // Å… [LATIN CAPITAL LETTER N WITH CEDILLA] + case '\u0147': + // Ň [LATIN CAPITAL LETTER N WITH CARON] + case '\u014A': + // Ã…Å  http://en.wikipedia.org/wiki/Eng_(letter) [LATIN CAPITAL LETTER ENG] + case '\u019D': + // � [LATIN CAPITAL LETTER N WITH LEFT HOOK] + case '\u01F8': + // Ǹ [LATIN CAPITAL LETTER N WITH GRAVE] + case '\u0220': + // È  [LATIN CAPITAL LETTER N WITH LONG RIGHT LEG] + case '\u0274': + // É´ [LATIN LETTER SMALL CAPITAL N] + case '\u1D0E': + // á´Ž [LATIN LETTER SMALL CAPITAL REVERSED N] + case '\u1E44': + // Ṅ [LATIN CAPITAL LETTER N WITH DOT ABOVE] + case '\u1E46': + // Ṇ [LATIN CAPITAL LETTER N WITH DOT BELOW] + case '\u1E48': + // Ṉ [LATIN CAPITAL LETTER N WITH LINE BELOW] + case '\u1E4A': + // Ṋ [LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW] + case '\u24C3': + // Ⓝ [CIRCLED LATIN CAPITAL LETTER N] + case '\uFF2E': // ï¼® [FULLWIDTH LATIN CAPITAL LETTER N] + output[opos++] = 'N'; + break; + + case '\u00F1': + // ñ [LATIN SMALL LETTER N WITH TILDE] + case '\u0144': + // Å„ [LATIN SMALL LETTER N WITH ACUTE] + case '\u0146': + // ņ [LATIN SMALL LETTER N WITH CEDILLA] + case '\u0148': + // ň [LATIN SMALL LETTER N WITH CARON] + case '\u0149': + // ʼn [LATIN SMALL LETTER N PRECEDED BY APOSTROPHE] + case '\u014B': + // Å‹ http://en.wikipedia.org/wiki/Eng_(letter) [LATIN SMALL LETTER ENG] + case '\u019E': + // Æž [LATIN SMALL LETTER N WITH LONG RIGHT LEG] + case '\u01F9': + // ǹ [LATIN SMALL LETTER N WITH GRAVE] + case '\u0235': + // ȵ [LATIN SMALL LETTER N WITH CURL] + case '\u0272': + // ɲ [LATIN SMALL LETTER N WITH LEFT HOOK] + case '\u0273': + // ɳ [LATIN SMALL LETTER N WITH RETROFLEX HOOK] + case '\u1D70': + // áµ° [LATIN SMALL LETTER N WITH MIDDLE TILDE] + case '\u1D87': + // ᶇ [LATIN SMALL LETTER N WITH PALATAL HOOK] + case '\u1E45': + // á¹… [LATIN SMALL LETTER N WITH DOT ABOVE] + case '\u1E47': + // ṇ [LATIN SMALL LETTER N WITH DOT BELOW] + case '\u1E49': + // ṉ [LATIN SMALL LETTER N WITH LINE BELOW] + case '\u1E4B': + // ṋ [LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW] + case '\u207F': + // � [SUPERSCRIPT LATIN SMALL LETTER N] + case '\u24DD': + // � [CIRCLED LATIN SMALL LETTER N] + case '\uFF4E': // n [FULLWIDTH LATIN SMALL LETTER N] + output[opos++] = 'n'; + break; + + case '\u01CA': // ÇŠ [LATIN CAPITAL LETTER NJ] + output[opos++] = 'N'; + output[opos++] = 'J'; + break; + + case '\u01CB': // Ç‹ [LATIN CAPITAL LETTER N WITH SMALL LETTER J] + output[opos++] = 'N'; + output[opos++] = 'j'; + break; + + case '\u24A9': // â’© [PARENTHESIZED LATIN SMALL LETTER N] + output[opos++] = '('; + output[opos++] = 'n'; + output[opos++] = ')'; + break; + + case '\u01CC': // ÇŒ [LATIN SMALL LETTER NJ] + output[opos++] = 'n'; + output[opos++] = 'j'; + break; + + case '\u00D2': + // Ã’ [LATIN CAPITAL LETTER O WITH GRAVE] + case '\u00D3': + // Ó [LATIN CAPITAL LETTER O WITH ACUTE] + case '\u00D4': + // �? [LATIN CAPITAL LETTER O WITH CIRCUMFLEX] + case '\u00D5': + // Õ [LATIN CAPITAL LETTER O WITH TILDE] + case '\u00D6': + // Ö [LATIN CAPITAL LETTER O WITH DIAERESIS] + case '\u00D8': + // Ø [LATIN CAPITAL LETTER O WITH STROKE] + case '\u014C': + // Ã…Å’ [LATIN CAPITAL LETTER O WITH MACRON] + case '\u014E': + // ÅŽ [LATIN CAPITAL LETTER O WITH BREVE] + case '\u0150': + // � [LATIN CAPITAL LETTER O WITH DOUBLE ACUTE] + case '\u0186': + // Ɔ [LATIN CAPITAL LETTER OPEN O] + case '\u019F': + // ÆŸ [LATIN CAPITAL LETTER O WITH MIDDLE TILDE] + case '\u01A0': + // Æ  [LATIN CAPITAL LETTER O WITH HORN] + case '\u01D1': + // Ç‘ [LATIN CAPITAL LETTER O WITH CARON] + case '\u01EA': + // Ǫ [LATIN CAPITAL LETTER O WITH OGONEK] + case '\u01EC': + // Ǭ [LATIN CAPITAL LETTER O WITH OGONEK AND MACRON] + case '\u01FE': + // Ǿ [LATIN CAPITAL LETTER O WITH STROKE AND ACUTE] + case '\u020C': + // ÈŒ [LATIN CAPITAL LETTER O WITH DOUBLE GRAVE] + case '\u020E': + // ÈŽ [LATIN CAPITAL LETTER O WITH INVERTED BREVE] + case '\u022A': + // Ȫ [LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON] + case '\u022C': + // Ȭ [LATIN CAPITAL LETTER O WITH TILDE AND MACRON] + case '\u022E': + // È® [LATIN CAPITAL LETTER O WITH DOT ABOVE] + case '\u0230': + // È° [LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON] + case '\u1D0F': + // á´� [LATIN LETTER SMALL CAPITAL O] + case '\u1D10': + // á´� [LATIN LETTER SMALL CAPITAL OPEN O] + case '\u1E4C': + // Ṍ [LATIN CAPITAL LETTER O WITH TILDE AND ACUTE] + case '\u1E4E': + // Ṏ [LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS] + case '\u1E50': + // � [LATIN CAPITAL LETTER O WITH MACRON AND GRAVE] + case '\u1E52': + // á¹’ [LATIN CAPITAL LETTER O WITH MACRON AND ACUTE] + case '\u1ECC': + // Ọ [LATIN CAPITAL LETTER O WITH DOT BELOW] + case '\u1ECE': + // Ỏ [LATIN CAPITAL LETTER O WITH HOOK ABOVE] + case '\u1ED0': + // � [LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE] + case '\u1ED2': + // á»’ [LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE] + case '\u1ED4': + // �? [LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE] + case '\u1ED6': + // á»– [LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE] + case '\u1ED8': + // Ộ [LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW] + case '\u1EDA': + // Ớ [LATIN CAPITAL LETTER O WITH HORN AND ACUTE] + case '\u1EDC': + // Ờ [LATIN CAPITAL LETTER O WITH HORN AND GRAVE] + case '\u1EDE': + // Ở [LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE] + case '\u1EE0': + // á»  [LATIN CAPITAL LETTER O WITH HORN AND TILDE] + case '\u1EE2': + // Ợ [LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW] + case '\u24C4': + // â“„ [CIRCLED LATIN CAPITAL LETTER O] + case '\uA74A': + // � [LATIN CAPITAL LETTER O WITH LONG STROKE OVERLAY] + case '\uA74C': + // � [LATIN CAPITAL LETTER O WITH LOOP] + case '\uFF2F': // O [FULLWIDTH LATIN CAPITAL LETTER O] + output[opos++] = 'O'; + break; + + case '\u00F2': + // ò [LATIN SMALL LETTER O WITH GRAVE] + case '\u00F3': + // ó [LATIN SMALL LETTER O WITH ACUTE] + case '\u00F4': + // ô [LATIN SMALL LETTER O WITH CIRCUMFLEX] + case '\u00F5': + // õ [LATIN SMALL LETTER O WITH TILDE] + case '\u00F6': + // ö [LATIN SMALL LETTER O WITH DIAERESIS] + case '\u00F8': + // ø [LATIN SMALL LETTER O WITH STROKE] + case '\u014D': + // � [LATIN SMALL LETTER O WITH MACRON] + case '\u014F': + // � [LATIN SMALL LETTER O WITH BREVE] + case '\u0151': + // Å‘ [LATIN SMALL LETTER O WITH DOUBLE ACUTE] + case '\u01A1': + // Æ¡ [LATIN SMALL LETTER O WITH HORN] + case '\u01D2': + // Ç’ [LATIN SMALL LETTER O WITH CARON] + case '\u01EB': + // Ç« [LATIN SMALL LETTER O WITH OGONEK] + case '\u01ED': + // Ç­ [LATIN SMALL LETTER O WITH OGONEK AND MACRON] + case '\u01FF': + // Ç¿ [LATIN SMALL LETTER O WITH STROKE AND ACUTE] + case '\u020D': + // � [LATIN SMALL LETTER O WITH DOUBLE GRAVE] + case '\u020F': + // � [LATIN SMALL LETTER O WITH INVERTED BREVE] + case '\u022B': + // È« [LATIN SMALL LETTER O WITH DIAERESIS AND MACRON] + case '\u022D': + // È­ [LATIN SMALL LETTER O WITH TILDE AND MACRON] + case '\u022F': + // ȯ [LATIN SMALL LETTER O WITH DOT ABOVE] + case '\u0231': + // ȱ [LATIN SMALL LETTER O WITH DOT ABOVE AND MACRON] + case '\u0254': + // �? [LATIN SMALL LETTER OPEN O] + case '\u0275': + // ɵ [LATIN SMALL LETTER BARRED O] + case '\u1D16': + // á´– [LATIN SMALL LETTER TOP HALF O] + case '\u1D17': + // á´— [LATIN SMALL LETTER BOTTOM HALF O] + case '\u1D97': + // ᶗ [LATIN SMALL LETTER OPEN O WITH RETROFLEX HOOK] + case '\u1E4D': + // � [LATIN SMALL LETTER O WITH TILDE AND ACUTE] + case '\u1E4F': + // � [LATIN SMALL LETTER O WITH TILDE AND DIAERESIS] + case '\u1E51': + // ṑ [LATIN SMALL LETTER O WITH MACRON AND GRAVE] + case '\u1E53': + // ṓ [LATIN SMALL LETTER O WITH MACRON AND ACUTE] + case '\u1ECD': + // � [LATIN SMALL LETTER O WITH DOT BELOW] + case '\u1ECF': + // � [LATIN SMALL LETTER O WITH HOOK ABOVE] + case '\u1ED1': + // ố [LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE] + case '\u1ED3': + // ồ [LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE] + case '\u1ED5': + // ổ [LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE] + case '\u1ED7': + // á»— [LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE] + case '\u1ED9': + // á»™ [LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW] + case '\u1EDB': + // á»› [LATIN SMALL LETTER O WITH HORN AND ACUTE] + case '\u1EDD': + // � [LATIN SMALL LETTER O WITH HORN AND GRAVE] + case '\u1EDF': + // ở [LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE] + case '\u1EE1': + // ỡ [LATIN SMALL LETTER O WITH HORN AND TILDE] + case '\u1EE3': + // ợ [LATIN SMALL LETTER O WITH HORN AND DOT BELOW] + case '\u2092': + // â‚’ [LATIN SUBSCRIPT SMALL LETTER O] + case '\u24DE': + // â“ž [CIRCLED LATIN SMALL LETTER O] + case '\u2C7A': + // ⱺ [LATIN SMALL LETTER O WITH LOW RING INSIDE] + case '\uA74B': + // � [LATIN SMALL LETTER O WITH LONG STROKE OVERLAY] + case '\uA74D': + // � [LATIN SMALL LETTER O WITH LOOP] + case '\uFF4F': // � [FULLWIDTH LATIN SMALL LETTER O] + output[opos++] = 'o'; + break; + + case '\u0152': + // Å’ [LATIN CAPITAL LIGATURE OE] + case '\u0276': // ɶ [LATIN LETTER SMALL CAPITAL OE] + output[opos++] = 'O'; + output[opos++] = 'E'; + break; + + case '\uA74E': // � [LATIN CAPITAL LETTER OO] + output[opos++] = 'O'; + output[opos++] = 'O'; + break; + + case '\u0222': + // È¢ http://en.wikipedia.org/wiki/OU [LATIN CAPITAL LETTER OU] + case '\u1D15': // á´• [LATIN LETTER SMALL CAPITAL OU] + output[opos++] = 'O'; + output[opos++] = 'U'; + break; + + case '\u24AA': // â’ª [PARENTHESIZED LATIN SMALL LETTER O] + output[opos++] = '('; + output[opos++] = 'o'; + output[opos++] = ')'; + break; + + case '\u0153': + // Å“ [LATIN SMALL LIGATURE OE] + case '\u1D14': // á´�? [LATIN SMALL LETTER TURNED OE] + output[opos++] = 'o'; + output[opos++] = 'e'; + break; + + case '\uA74F': // � [LATIN SMALL LETTER OO] + output[opos++] = 'o'; + output[opos++] = 'o'; + break; + + case '\u0223': // È£ http://en.wikipedia.org/wiki/OU [LATIN SMALL LETTER OU] + output[opos++] = 'o'; + output[opos++] = 'u'; + break; + + case '\u01A4': + // Ƥ [LATIN CAPITAL LETTER P WITH HOOK] + case '\u1D18': + // á´˜ [LATIN LETTER SMALL CAPITAL P] + case '\u1E54': + // �? [LATIN CAPITAL LETTER P WITH ACUTE] + case '\u1E56': + // á¹– [LATIN CAPITAL LETTER P WITH DOT ABOVE] + case '\u24C5': + // â“… [CIRCLED LATIN CAPITAL LETTER P] + case '\u2C63': + // â±£ [LATIN CAPITAL LETTER P WITH STROKE] + case '\uA750': + // � [LATIN CAPITAL LETTER P WITH STROKE THROUGH DESCENDER] + case '\uA752': + // � [LATIN CAPITAL LETTER P WITH FLOURISH] + case '\uA754': + // �? [LATIN CAPITAL LETTER P WITH SQUIRREL TAIL] + case '\uFF30': // ï¼° [FULLWIDTH LATIN CAPITAL LETTER P] + output[opos++] = 'P'; + break; + + case '\u01A5': + // Æ¥ [LATIN SMALL LETTER P WITH HOOK] + case '\u1D71': + // áµ± [LATIN SMALL LETTER P WITH MIDDLE TILDE] + case '\u1D7D': + // áµ½ [LATIN SMALL LETTER P WITH STROKE] + case '\u1D88': + // ᶈ [LATIN SMALL LETTER P WITH PALATAL HOOK] + case '\u1E55': + // ṕ [LATIN SMALL LETTER P WITH ACUTE] + case '\u1E57': + // á¹— [LATIN SMALL LETTER P WITH DOT ABOVE] + case '\u24DF': + // â“Ÿ [CIRCLED LATIN SMALL LETTER P] + case '\uA751': + // � [LATIN SMALL LETTER P WITH STROKE THROUGH DESCENDER] + case '\uA753': + // � [LATIN SMALL LETTER P WITH FLOURISH] + case '\uA755': + // � [LATIN SMALL LETTER P WITH SQUIRREL TAIL] + case '\uA7FC': + // ꟼ [LATIN EPIGRAPHIC LETTER REVERSED P] + case '\uFF50': // � [FULLWIDTH LATIN SMALL LETTER P] + output[opos++] = 'p'; + break; + + case '\u24AB': // â’« [PARENTHESIZED LATIN SMALL LETTER P] + output[opos++] = '('; + output[opos++] = 'p'; + output[opos++] = ')'; + break; + + case '\u024A': + // ÉŠ [LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL] + case '\u24C6': + // Ⓠ [CIRCLED LATIN CAPITAL LETTER Q] + case '\uA756': + // � [LATIN CAPITAL LETTER Q WITH STROKE THROUGH DESCENDER] + case '\uA758': + // � [LATIN CAPITAL LETTER Q WITH DIAGONAL STROKE] + case '\uFF31': // ï¼± [FULLWIDTH LATIN CAPITAL LETTER Q] + output[opos++] = 'Q'; + break; + + case '\u0138': + // ĸ http://en.wikipedia.org/wiki/Kra_(letter) [LATIN SMALL LETTER KRA] + case '\u024B': + // É‹ [LATIN SMALL LETTER Q WITH HOOK TAIL] + case '\u02A0': + // Ê  [LATIN SMALL LETTER Q WITH HOOK] + case '\u24E0': + // â“  [CIRCLED LATIN SMALL LETTER Q] + case '\uA757': + // � [LATIN SMALL LETTER Q WITH STROKE THROUGH DESCENDER] + case '\uA759': + // � [LATIN SMALL LETTER Q WITH DIAGONAL STROKE] + case '\uFF51': // q [FULLWIDTH LATIN SMALL LETTER Q] + output[opos++] = 'q'; + break; + + case '\u24AC': // â’¬ [PARENTHESIZED LATIN SMALL LETTER Q] + output[opos++] = '('; + output[opos++] = 'q'; + output[opos++] = ')'; + break; + + case '\u0239': // ȹ [LATIN SMALL LETTER QP DIGRAPH] + output[opos++] = 'q'; + output[opos++] = 'p'; + break; + + case '\u0154': + // �? [LATIN CAPITAL LETTER R WITH ACUTE] + case '\u0156': + // Å– [LATIN CAPITAL LETTER R WITH CEDILLA] + case '\u0158': + // Ã…Ëœ [LATIN CAPITAL LETTER R WITH CARON] + case '\u0210': + // È’ [LATIN CAPITAL LETTER R WITH DOUBLE GRAVE] + case '\u0212': + // È’ [LATIN CAPITAL LETTER R WITH INVERTED BREVE] + case '\u024C': + // ÉŒ [LATIN CAPITAL LETTER R WITH STROKE] + case '\u0280': + // Ê€ [LATIN LETTER SMALL CAPITAL R] + case '\u0281': + // � [LATIN LETTER SMALL CAPITAL INVERTED R] + case '\u1D19': + // á´™ [LATIN LETTER SMALL CAPITAL REVERSED R] + case '\u1D1A': + // á´š [LATIN LETTER SMALL CAPITAL TURNED R] + case '\u1E58': + // Ṙ [LATIN CAPITAL LETTER R WITH DOT ABOVE] + case '\u1E5A': + // Ṛ [LATIN CAPITAL LETTER R WITH DOT BELOW] + case '\u1E5C': + // Ṝ [LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON] + case '\u1E5E': + // Ṟ [LATIN CAPITAL LETTER R WITH LINE BELOW] + case '\u24C7': + // Ⓡ [CIRCLED LATIN CAPITAL LETTER R] + case '\u2C64': + // Ɽ [LATIN CAPITAL LETTER R WITH TAIL] + case '\uA75A': + // � [LATIN CAPITAL LETTER R ROTUNDA] + case '\uA782': + // êž‚ [LATIN CAPITAL LETTER INSULAR R] + case '\uFF32': // ï¼² [FULLWIDTH LATIN CAPITAL LETTER R] + output[opos++] = 'R'; + break; + + case '\u0155': + // Å• [LATIN SMALL LETTER R WITH ACUTE] + case '\u0157': + // Å— [LATIN SMALL LETTER R WITH CEDILLA] + case '\u0159': + // Ã…â„¢ [LATIN SMALL LETTER R WITH CARON] + case '\u0211': + // È‘ [LATIN SMALL LETTER R WITH DOUBLE GRAVE] + case '\u0213': + // È“ [LATIN SMALL LETTER R WITH INVERTED BREVE] + case '\u024D': + // � [LATIN SMALL LETTER R WITH STROKE] + case '\u027C': + // ɼ [LATIN SMALL LETTER R WITH LONG LEG] + case '\u027D': + // ɽ [LATIN SMALL LETTER R WITH TAIL] + case '\u027E': + // ɾ [LATIN SMALL LETTER R WITH FISHHOOK] + case '\u027F': + // É¿ [LATIN SMALL LETTER REVERSED R WITH FISHHOOK] + case '\u1D63': + // áµ£ [LATIN SUBSCRIPT SMALL LETTER R] + case '\u1D72': + // áµ² [LATIN SMALL LETTER R WITH MIDDLE TILDE] + case '\u1D73': + // áµ³ [LATIN SMALL LETTER R WITH FISHHOOK AND MIDDLE TILDE] + case '\u1D89': + // ᶉ [LATIN SMALL LETTER R WITH PALATAL HOOK] + case '\u1E59': + // á¹™ [LATIN SMALL LETTER R WITH DOT ABOVE] + case '\u1E5B': + // á¹› [LATIN SMALL LETTER R WITH DOT BELOW] + case '\u1E5D': + // � [LATIN SMALL LETTER R WITH DOT BELOW AND MACRON] + case '\u1E5F': + // ṟ [LATIN SMALL LETTER R WITH LINE BELOW] + case '\u24E1': + // â“¡ [CIRCLED LATIN SMALL LETTER R] + case '\uA75B': + // � [LATIN SMALL LETTER R ROTUNDA] + case '\uA783': + // ꞃ [LATIN SMALL LETTER INSULAR R] + case '\uFF52': // ï½’ [FULLWIDTH LATIN SMALL LETTER R] + output[opos++] = 'r'; + break; + + case '\u24AD': // â’­ [PARENTHESIZED LATIN SMALL LETTER R] + output[opos++] = '('; + output[opos++] = 'r'; + output[opos++] = ')'; + break; + + case '\u015A': + // Ã…Å¡ [LATIN CAPITAL LETTER S WITH ACUTE] + case '\u015C': + // Ã…Å“ [LATIN CAPITAL LETTER S WITH CIRCUMFLEX] + case '\u015E': + // Åž [LATIN CAPITAL LETTER S WITH CEDILLA] + case '\u0160': + // Å  [LATIN CAPITAL LETTER S WITH CARON] + case '\u0218': + // Ș [LATIN CAPITAL LETTER S WITH COMMA BELOW] + case '\u1E60': + // á¹  [LATIN CAPITAL LETTER S WITH DOT ABOVE] + case '\u1E62': + // á¹¢ [LATIN CAPITAL LETTER S WITH DOT BELOW] + case '\u1E64': + // Ṥ [LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE] + case '\u1E66': + // Ṧ [LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE] + case '\u1E68': + // Ṩ [LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE] + case '\u24C8': + // Ⓢ [CIRCLED LATIN CAPITAL LETTER S] + case '\uA731': + // ꜱ [LATIN LETTER SMALL CAPITAL S] + case '\uA785': + // êž… [LATIN SMALL LETTER INSULAR S] + case '\uFF33': // ï¼³ [FULLWIDTH LATIN CAPITAL LETTER S] + output[opos++] = 'S'; + break; + + case '\u015B': + // Å› [LATIN SMALL LETTER S WITH ACUTE] + case '\u015D': + // � [LATIN SMALL LETTER S WITH CIRCUMFLEX] + case '\u015F': + // ÅŸ [LATIN SMALL LETTER S WITH CEDILLA] + case '\u0161': + // Å¡ [LATIN SMALL LETTER S WITH CARON] + case '\u017F': + // Å¿ http://en.wikipedia.org/wiki/Long_S [LATIN SMALL LETTER LONG S] + case '\u0219': + // È™ [LATIN SMALL LETTER S WITH COMMA BELOW] + case '\u023F': + // È¿ [LATIN SMALL LETTER S WITH SWASH TAIL] + case '\u0282': + // Ê‚ [LATIN SMALL LETTER S WITH HOOK] + case '\u1D74': + // áµ´ [LATIN SMALL LETTER S WITH MIDDLE TILDE] + case '\u1D8A': + // ᶊ [LATIN SMALL LETTER S WITH PALATAL HOOK] + case '\u1E61': + // ṡ [LATIN SMALL LETTER S WITH DOT ABOVE] + case '\u1E63': + // á¹£ [LATIN SMALL LETTER S WITH DOT BELOW] + case '\u1E65': + // á¹¥ [LATIN SMALL LETTER S WITH ACUTE AND DOT ABOVE] + case '\u1E67': + // ṧ [LATIN SMALL LETTER S WITH CARON AND DOT ABOVE] + case '\u1E69': + // ṩ [LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE] + case '\u1E9C': + // ẜ [LATIN SMALL LETTER LONG S WITH DIAGONAL STROKE] + case '\u1E9D': + // � [LATIN SMALL LETTER LONG S WITH HIGH STROKE] + case '\u24E2': + // â“¢ [CIRCLED LATIN SMALL LETTER S] + case '\uA784': + // êž„ [LATIN CAPITAL LETTER INSULAR S] + case '\uFF53': // s [FULLWIDTH LATIN SMALL LETTER S] + output[opos++] = 's'; + break; + + case '\u1E9E': // ẞ [LATIN CAPITAL LETTER SHARP S] + output[opos++] = 'S'; + output[opos++] = 'S'; + break; + + case '\u24AE': // â’® [PARENTHESIZED LATIN SMALL LETTER S] + output[opos++] = '('; + output[opos++] = 's'; + output[opos++] = ')'; + break; + + case '\u00DF': // ß [LATIN SMALL LETTER SHARP S] + output[opos++] = 's'; + output[opos++] = 's'; + break; + + case '\uFB06': // st [LATIN SMALL LIGATURE ST] + output[opos++] = 's'; + output[opos++] = 't'; + break; + + case '\u0162': + // Å¢ [LATIN CAPITAL LETTER T WITH CEDILLA] + case '\u0164': + // Ť [LATIN CAPITAL LETTER T WITH CARON] + case '\u0166': + // Ŧ [LATIN CAPITAL LETTER T WITH STROKE] + case '\u01AC': + // Ƭ [LATIN CAPITAL LETTER T WITH HOOK] + case '\u01AE': + // Æ® [LATIN CAPITAL LETTER T WITH RETROFLEX HOOK] + case '\u021A': + // Èš [LATIN CAPITAL LETTER T WITH COMMA BELOW] + case '\u023E': + // Ⱦ [LATIN CAPITAL LETTER T WITH DIAGONAL STROKE] + case '\u1D1B': + // á´› [LATIN LETTER SMALL CAPITAL T] + case '\u1E6A': + // Ṫ [LATIN CAPITAL LETTER T WITH DOT ABOVE] + case '\u1E6C': + // Ṭ [LATIN CAPITAL LETTER T WITH DOT BELOW] + case '\u1E6E': + // á¹® [LATIN CAPITAL LETTER T WITH LINE BELOW] + case '\u1E70': + // á¹° [LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW] + case '\u24C9': + // Ⓣ [CIRCLED LATIN CAPITAL LETTER T] + case '\uA786': + // Ꞇ [LATIN CAPITAL LETTER INSULAR T] + case '\uFF34': // ï¼´ [FULLWIDTH LATIN CAPITAL LETTER T] + output[opos++] = 'T'; + break; + + case '\u0163': + // Å£ [LATIN SMALL LETTER T WITH CEDILLA] + case '\u0165': + // Ã…Â¥ [LATIN SMALL LETTER T WITH CARON] + case '\u0167': + // ŧ [LATIN SMALL LETTER T WITH STROKE] + case '\u01AB': + // Æ« [LATIN SMALL LETTER T WITH PALATAL HOOK] + case '\u01AD': + // Æ­ [LATIN SMALL LETTER T WITH HOOK] + case '\u021B': + // È› [LATIN SMALL LETTER T WITH COMMA BELOW] + case '\u0236': + // ȶ [LATIN SMALL LETTER T WITH CURL] + case '\u0287': + // ʇ [LATIN SMALL LETTER TURNED T] + case '\u0288': + // ʈ [LATIN SMALL LETTER T WITH RETROFLEX HOOK] + case '\u1D75': + // áµµ [LATIN SMALL LETTER T WITH MIDDLE TILDE] + case '\u1E6B': + // ṫ [LATIN SMALL LETTER T WITH DOT ABOVE] + case '\u1E6D': + // á¹­ [LATIN SMALL LETTER T WITH DOT BELOW] + case '\u1E6F': + // ṯ [LATIN SMALL LETTER T WITH LINE BELOW] + case '\u1E71': + // á¹± [LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW] + case '\u1E97': + // ẗ [LATIN SMALL LETTER T WITH DIAERESIS] + case '\u24E3': + // â“£ [CIRCLED LATIN SMALL LETTER T] + case '\u2C66': + // ⱦ [LATIN SMALL LETTER T WITH DIAGONAL STROKE] + case '\uFF54': // �? [FULLWIDTH LATIN SMALL LETTER T] + output[opos++] = 't'; + break; + + case '\u00DE': + // Þ [LATIN CAPITAL LETTER THORN] + case '\uA766': // � [LATIN CAPITAL LETTER THORN WITH STROKE THROUGH DESCENDER] + output[opos++] = 'T'; + output[opos++] = 'H'; + break; + + case '\uA728': // Ꜩ [LATIN CAPITAL LETTER TZ] + output[opos++] = 'T'; + output[opos++] = 'Z'; + break; + + case '\u24AF': // â’¯ [PARENTHESIZED LATIN SMALL LETTER T] + output[opos++] = '('; + output[opos++] = 't'; + output[opos++] = ')'; + break; + + case '\u02A8': // ʨ [LATIN SMALL LETTER TC DIGRAPH WITH CURL] + output[opos++] = 't'; + output[opos++] = 'c'; + break; + + case '\u00FE': + // þ [LATIN SMALL LETTER THORN] + case '\u1D7A': + // ᵺ [LATIN SMALL LETTER TH WITH STRIKETHROUGH] + case '\uA767': // � [LATIN SMALL LETTER THORN WITH STROKE THROUGH DESCENDER] + output[opos++] = 't'; + output[opos++] = 'h'; + break; + + case '\u02A6': // ʦ [LATIN SMALL LETTER TS DIGRAPH] + output[opos++] = 't'; + output[opos++] = 's'; + break; + + case '\uA729': // ꜩ [LATIN SMALL LETTER TZ] + output[opos++] = 't'; + output[opos++] = 'z'; + break; + + case '\u00D9': + // Ù [LATIN CAPITAL LETTER U WITH GRAVE] + case '\u00DA': + // Ú [LATIN CAPITAL LETTER U WITH ACUTE] + case '\u00DB': + // Û [LATIN CAPITAL LETTER U WITH CIRCUMFLEX] + case '\u00DC': + // Ãœ [LATIN CAPITAL LETTER U WITH DIAERESIS] + case '\u0168': + // Ũ [LATIN CAPITAL LETTER U WITH TILDE] + case '\u016A': + // Ū [LATIN CAPITAL LETTER U WITH MACRON] + case '\u016C': + // Ŭ [LATIN CAPITAL LETTER U WITH BREVE] + case '\u016E': + // Å® [LATIN CAPITAL LETTER U WITH RING ABOVE] + case '\u0170': + // Å° [LATIN CAPITAL LETTER U WITH DOUBLE ACUTE] + case '\u0172': + // Ų [LATIN CAPITAL LETTER U WITH OGONEK] + case '\u01AF': + // Ư [LATIN CAPITAL LETTER U WITH HORN] + case '\u01D3': + // Ç“ [LATIN CAPITAL LETTER U WITH CARON] + case '\u01D5': + // Ç• [LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON] + case '\u01D7': + // Ç— [LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE] + case '\u01D9': + // Ç™ [LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON] + case '\u01DB': + // Ç› [LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE] + case '\u0214': + // �? [LATIN CAPITAL LETTER U WITH DOUBLE GRAVE] + case '\u0216': + // È– [LATIN CAPITAL LETTER U WITH INVERTED BREVE] + case '\u0244': + // É„ [LATIN CAPITAL LETTER U BAR] + case '\u1D1C': + // á´œ [LATIN LETTER SMALL CAPITAL U] + case '\u1D7E': + // áµ¾ [LATIN SMALL CAPITAL LETTER U WITH STROKE] + case '\u1E72': + // á¹² [LATIN CAPITAL LETTER U WITH DIAERESIS BELOW] + case '\u1E74': + // á¹´ [LATIN CAPITAL LETTER U WITH TILDE BELOW] + case '\u1E76': + // Ṷ [LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW] + case '\u1E78': + // Ṹ [LATIN CAPITAL LETTER U WITH TILDE AND ACUTE] + case '\u1E7A': + // Ṻ [LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS] + case '\u1EE4': + // Ụ [LATIN CAPITAL LETTER U WITH DOT BELOW] + case '\u1EE6': + // Ủ [LATIN CAPITAL LETTER U WITH HOOK ABOVE] + case '\u1EE8': + // Ứ [LATIN CAPITAL LETTER U WITH HORN AND ACUTE] + case '\u1EEA': + // Ừ [LATIN CAPITAL LETTER U WITH HORN AND GRAVE] + case '\u1EEC': + // Ử [LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE] + case '\u1EEE': + // á»® [LATIN CAPITAL LETTER U WITH HORN AND TILDE] + case '\u1EF0': + // á»° [LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW] + case '\u24CA': + // â“Š [CIRCLED LATIN CAPITAL LETTER U] + case '\uFF35': // ï¼µ [FULLWIDTH LATIN CAPITAL LETTER U] + output[opos++] = 'U'; + break; + + case '\u00F9': + // ù [LATIN SMALL LETTER U WITH GRAVE] + case '\u00FA': + // ú [LATIN SMALL LETTER U WITH ACUTE] + case '\u00FB': + // û [LATIN SMALL LETTER U WITH CIRCUMFLEX] + case '\u00FC': + // ü [LATIN SMALL LETTER U WITH DIAERESIS] + case '\u0169': + // Å© [LATIN SMALL LETTER U WITH TILDE] + case '\u016B': + // Å« [LATIN SMALL LETTER U WITH MACRON] + case '\u016D': + // Å­ [LATIN SMALL LETTER U WITH BREVE] + case '\u016F': + // ů [LATIN SMALL LETTER U WITH RING ABOVE] + case '\u0171': + // ű [LATIN SMALL LETTER U WITH DOUBLE ACUTE] + case '\u0173': + // ų [LATIN SMALL LETTER U WITH OGONEK] + case '\u01B0': + // Æ° [LATIN SMALL LETTER U WITH HORN] + case '\u01D4': + // �? [LATIN SMALL LETTER U WITH CARON] + case '\u01D6': + // Ç– [LATIN SMALL LETTER U WITH DIAERESIS AND MACRON] + case '\u01D8': + // ǘ [LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE] + case '\u01DA': + // Çš [LATIN SMALL LETTER U WITH DIAERESIS AND CARON] + case '\u01DC': + // Çœ [LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE] + case '\u0215': + // È• [LATIN SMALL LETTER U WITH DOUBLE GRAVE] + case '\u0217': + // È— [LATIN SMALL LETTER U WITH INVERTED BREVE] + case '\u0289': + // ʉ [LATIN SMALL LETTER U BAR] + case '\u1D64': + // ᵤ [LATIN SUBSCRIPT SMALL LETTER U] + case '\u1D99': + // ᶙ [LATIN SMALL LETTER U WITH RETROFLEX HOOK] + case '\u1E73': + // á¹³ [LATIN SMALL LETTER U WITH DIAERESIS BELOW] + case '\u1E75': + // á¹µ [LATIN SMALL LETTER U WITH TILDE BELOW] + case '\u1E77': + // á¹· [LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW] + case '\u1E79': + // á¹¹ [LATIN SMALL LETTER U WITH TILDE AND ACUTE] + case '\u1E7B': + // á¹» [LATIN SMALL LETTER U WITH MACRON AND DIAERESIS] + case '\u1EE5': + // ụ [LATIN SMALL LETTER U WITH DOT BELOW] + case '\u1EE7': + // ủ [LATIN SMALL LETTER U WITH HOOK ABOVE] + case '\u1EE9': + // ứ [LATIN SMALL LETTER U WITH HORN AND ACUTE] + case '\u1EEB': + // ừ [LATIN SMALL LETTER U WITH HORN AND GRAVE] + case '\u1EED': + // á»­ [LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE] + case '\u1EEF': + // ữ [LATIN SMALL LETTER U WITH HORN AND TILDE] + case '\u1EF1': + // á»± [LATIN SMALL LETTER U WITH HORN AND DOT BELOW] + case '\u24E4': + // ⓤ [CIRCLED LATIN SMALL LETTER U] + case '\uFF55': // u [FULLWIDTH LATIN SMALL LETTER U] + output[opos++] = 'u'; + break; + + case '\u24B0': // â’° [PARENTHESIZED LATIN SMALL LETTER U] + output[opos++] = '('; + output[opos++] = 'u'; + output[opos++] = ')'; + break; + + case '\u1D6B': // ᵫ [LATIN SMALL LETTER UE] + output[opos++] = 'u'; + output[opos++] = 'e'; + break; + + case '\u01B2': + // Ʋ [LATIN CAPITAL LETTER V WITH HOOK] + case '\u0245': + // É… [LATIN CAPITAL LETTER TURNED V] + case '\u1D20': + // á´  [LATIN LETTER SMALL CAPITAL V] + case '\u1E7C': + // á¹¼ [LATIN CAPITAL LETTER V WITH TILDE] + case '\u1E7E': + // á¹¾ [LATIN CAPITAL LETTER V WITH DOT BELOW] + case '\u1EFC': + // Ỽ [LATIN CAPITAL LETTER MIDDLE-WELSH V] + case '\u24CB': + // â“‹ [CIRCLED LATIN CAPITAL LETTER V] + case '\uA75E': + // � [LATIN CAPITAL LETTER V WITH DIAGONAL STROKE] + case '\uA768': + // � [LATIN CAPITAL LETTER VEND] + case '\uFF36': // V [FULLWIDTH LATIN CAPITAL LETTER V] + output[opos++] = 'V'; + break; + + case '\u028B': + // Ê‹ [LATIN SMALL LETTER V WITH HOOK] + case '\u028C': + // ÊŒ [LATIN SMALL LETTER TURNED V] + case '\u1D65': + // áµ¥ [LATIN SUBSCRIPT SMALL LETTER V] + case '\u1D8C': + // ᶌ [LATIN SMALL LETTER V WITH PALATAL HOOK] + case '\u1E7D': + // á¹½ [LATIN SMALL LETTER V WITH TILDE] + case '\u1E7F': + // ṿ [LATIN SMALL LETTER V WITH DOT BELOW] + case '\u24E5': + // â“¥ [CIRCLED LATIN SMALL LETTER V] + case '\u2C71': + // â±± [LATIN SMALL LETTER V WITH RIGHT HOOK] + case '\u2C74': + // â±´ [LATIN SMALL LETTER V WITH CURL] + case '\uA75F': + // � [LATIN SMALL LETTER V WITH DIAGONAL STROKE] + case '\uFF56': // ï½– [FULLWIDTH LATIN SMALL LETTER V] + output[opos++] = 'v'; + break; + + case '\uA760': // � [LATIN CAPITAL LETTER VY] + output[opos++] = 'V'; + output[opos++] = 'Y'; + break; + + case '\u24B1': // â’± [PARENTHESIZED LATIN SMALL LETTER V] + output[opos++] = '('; + output[opos++] = 'v'; + output[opos++] = ')'; + break; + + case '\uA761': // � [LATIN SMALL LETTER VY] + output[opos++] = 'v'; + output[opos++] = 'y'; + break; + + case '\u0174': + // Å´ [LATIN CAPITAL LETTER W WITH CIRCUMFLEX] + case '\u01F7': + // Ç· http://en.wikipedia.org/wiki/Wynn [LATIN CAPITAL LETTER WYNN] + case '\u1D21': + // á´¡ [LATIN LETTER SMALL CAPITAL W] + case '\u1E80': + // Ẁ [LATIN CAPITAL LETTER W WITH GRAVE] + case '\u1E82': + // Ẃ [LATIN CAPITAL LETTER W WITH ACUTE] + case '\u1E84': + // Ẅ [LATIN CAPITAL LETTER W WITH DIAERESIS] + case '\u1E86': + // Ẇ [LATIN CAPITAL LETTER W WITH DOT ABOVE] + case '\u1E88': + // Ẉ [LATIN CAPITAL LETTER W WITH DOT BELOW] + case '\u24CC': + // â“Œ [CIRCLED LATIN CAPITAL LETTER W] + case '\u2C72': + // â±² [LATIN CAPITAL LETTER W WITH HOOK] + case '\uFF37': // ï¼· [FULLWIDTH LATIN CAPITAL LETTER W] + output[opos++] = 'W'; + break; + + case '\u0175': + // ŵ [LATIN SMALL LETTER W WITH CIRCUMFLEX] + case '\u01BF': + // Æ¿ http://en.wikipedia.org/wiki/Wynn [LATIN LETTER WYNN] + case '\u028D': + // � [LATIN SMALL LETTER TURNED W] + case '\u1E81': + // � [LATIN SMALL LETTER W WITH GRAVE] + case '\u1E83': + // ẃ [LATIN SMALL LETTER W WITH ACUTE] + case '\u1E85': + // ẅ [LATIN SMALL LETTER W WITH DIAERESIS] + case '\u1E87': + // ẇ [LATIN SMALL LETTER W WITH DOT ABOVE] + case '\u1E89': + // ẉ [LATIN SMALL LETTER W WITH DOT BELOW] + case '\u1E98': + // ẘ [LATIN SMALL LETTER W WITH RING ABOVE] + case '\u24E6': + // ⓦ [CIRCLED LATIN SMALL LETTER W] + case '\u2C73': + // â±³ [LATIN SMALL LETTER W WITH HOOK] + case '\uFF57': // ï½— [FULLWIDTH LATIN SMALL LETTER W] + output[opos++] = 'w'; + break; + + case '\u24B2': // â’² [PARENTHESIZED LATIN SMALL LETTER W] + output[opos++] = '('; + output[opos++] = 'w'; + output[opos++] = ')'; + break; + + case '\u1E8A': + // Ẋ [LATIN CAPITAL LETTER X WITH DOT ABOVE] + case '\u1E8C': + // Ẍ [LATIN CAPITAL LETTER X WITH DIAERESIS] + case '\u24CD': + // � [CIRCLED LATIN CAPITAL LETTER X] + case '\uFF38': // X [FULLWIDTH LATIN CAPITAL LETTER X] + output[opos++] = 'X'; + break; + + case '\u1D8D': + // � [LATIN SMALL LETTER X WITH PALATAL HOOK] + case '\u1E8B': + // ẋ [LATIN SMALL LETTER X WITH DOT ABOVE] + case '\u1E8D': + // � [LATIN SMALL LETTER X WITH DIAERESIS] + case '\u2093': + // â‚“ [LATIN SUBSCRIPT SMALL LETTER X] + case '\u24E7': + // ⓧ [CIRCLED LATIN SMALL LETTER X] + case '\uFF58': // x [FULLWIDTH LATIN SMALL LETTER X] + output[opos++] = 'x'; + break; + + case '\u24B3': // â’³ [PARENTHESIZED LATIN SMALL LETTER X] + output[opos++] = '('; + output[opos++] = 'x'; + output[opos++] = ')'; + break; + + case '\u00DD': + // � [LATIN CAPITAL LETTER Y WITH ACUTE] + case '\u0176': + // Ŷ [LATIN CAPITAL LETTER Y WITH CIRCUMFLEX] + case '\u0178': + // Ÿ [LATIN CAPITAL LETTER Y WITH DIAERESIS] + case '\u01B3': + // Ƴ [LATIN CAPITAL LETTER Y WITH HOOK] + case '\u0232': + // Ȳ [LATIN CAPITAL LETTER Y WITH MACRON] + case '\u024E': + // ÉŽ [LATIN CAPITAL LETTER Y WITH STROKE] + case '\u028F': + // � [LATIN LETTER SMALL CAPITAL Y] + case '\u1E8E': + // Ẏ [LATIN CAPITAL LETTER Y WITH DOT ABOVE] + case '\u1EF2': + // Ỳ [LATIN CAPITAL LETTER Y WITH GRAVE] + case '\u1EF4': + // á»´ [LATIN CAPITAL LETTER Y WITH DOT BELOW] + case '\u1EF6': + // Ỷ [LATIN CAPITAL LETTER Y WITH HOOK ABOVE] + case '\u1EF8': + // Ỹ [LATIN CAPITAL LETTER Y WITH TILDE] + case '\u1EFE': + // Ỿ [LATIN CAPITAL LETTER Y WITH LOOP] + case '\u24CE': + // â“Ž [CIRCLED LATIN CAPITAL LETTER Y] + case '\uFF39': // ï¼¹ [FULLWIDTH LATIN CAPITAL LETTER Y] + output[opos++] = 'Y'; + break; + + case '\u00FD': + // ý [LATIN SMALL LETTER Y WITH ACUTE] + case '\u00FF': + // ÿ [LATIN SMALL LETTER Y WITH DIAERESIS] + case '\u0177': + // Å· [LATIN SMALL LETTER Y WITH CIRCUMFLEX] + case '\u01B4': + // Æ´ [LATIN SMALL LETTER Y WITH HOOK] + case '\u0233': + // ȳ [LATIN SMALL LETTER Y WITH MACRON] + case '\u024F': + // � [LATIN SMALL LETTER Y WITH STROKE] + case '\u028E': + // ÊŽ [LATIN SMALL LETTER TURNED Y] + case '\u1E8F': + // � [LATIN SMALL LETTER Y WITH DOT ABOVE] + case '\u1E99': + // ẙ [LATIN SMALL LETTER Y WITH RING ABOVE] + case '\u1EF3': + // ỳ [LATIN SMALL LETTER Y WITH GRAVE] + case '\u1EF5': + // ỵ [LATIN SMALL LETTER Y WITH DOT BELOW] + case '\u1EF7': + // á»· [LATIN SMALL LETTER Y WITH HOOK ABOVE] + case '\u1EF9': + // ỹ [LATIN SMALL LETTER Y WITH TILDE] + case '\u1EFF': + // ỿ [LATIN SMALL LETTER Y WITH LOOP] + case '\u24E8': + // ⓨ [CIRCLED LATIN SMALL LETTER Y] + case '\uFF59': // ï½™ [FULLWIDTH LATIN SMALL LETTER Y] + output[opos++] = 'y'; + break; + + case '\u24B4': // â’´ [PARENTHESIZED LATIN SMALL LETTER Y] + output[opos++] = '('; + output[opos++] = 'y'; + output[opos++] = ')'; + break; + + case '\u0179': + // Ź [LATIN CAPITAL LETTER Z WITH ACUTE] + case '\u017B': + // Å» [LATIN CAPITAL LETTER Z WITH DOT ABOVE] + case '\u017D': + // Ž [LATIN CAPITAL LETTER Z WITH CARON] + case '\u01B5': + // Ƶ [LATIN CAPITAL LETTER Z WITH STROKE] + case '\u021C': + // Èœ http://en.wikipedia.org/wiki/Yogh [LATIN CAPITAL LETTER YOGH] + case '\u0224': + // Ȥ [LATIN CAPITAL LETTER Z WITH HOOK] + case '\u1D22': + // á´¢ [LATIN LETTER SMALL CAPITAL Z] + case '\u1E90': + // � [LATIN CAPITAL LETTER Z WITH CIRCUMFLEX] + case '\u1E92': + // Ẓ [LATIN CAPITAL LETTER Z WITH DOT BELOW] + case '\u1E94': + // �? [LATIN CAPITAL LETTER Z WITH LINE BELOW] + case '\u24CF': + // � [CIRCLED LATIN CAPITAL LETTER Z] + case '\u2C6B': + // Ⱬ [LATIN CAPITAL LETTER Z WITH DESCENDER] + case '\uA762': + // � [LATIN CAPITAL LETTER VISIGOTHIC Z] + case '\uFF3A': // Z [FULLWIDTH LATIN CAPITAL LETTER Z] + output[opos++] = 'Z'; + break; + + case '\u017A': + // ź [LATIN SMALL LETTER Z WITH ACUTE] + case '\u017C': + // ż [LATIN SMALL LETTER Z WITH DOT ABOVE] + case '\u017E': + // ž [LATIN SMALL LETTER Z WITH CARON] + case '\u01B6': + // ƶ [LATIN SMALL LETTER Z WITH STROKE] + case '\u021D': + // � http://en.wikipedia.org/wiki/Yogh [LATIN SMALL LETTER YOGH] + case '\u0225': + // È¥ [LATIN SMALL LETTER Z WITH HOOK] + case '\u0240': + // É€ [LATIN SMALL LETTER Z WITH SWASH TAIL] + case '\u0290': + // � [LATIN SMALL LETTER Z WITH RETROFLEX HOOK] + case '\u0291': + // Ê‘ [LATIN SMALL LETTER Z WITH CURL] + case '\u1D76': + // ᵶ [LATIN SMALL LETTER Z WITH MIDDLE TILDE] + case '\u1D8E': + // ᶎ [LATIN SMALL LETTER Z WITH PALATAL HOOK] + case '\u1E91': + // ẑ [LATIN SMALL LETTER Z WITH CIRCUMFLEX] + case '\u1E93': + // ẓ [LATIN SMALL LETTER Z WITH DOT BELOW] + case '\u1E95': + // ẕ [LATIN SMALL LETTER Z WITH LINE BELOW] + case '\u24E9': + // â“© [CIRCLED LATIN SMALL LETTER Z] + case '\u2C6C': + // ⱬ [LATIN SMALL LETTER Z WITH DESCENDER] + case '\uA763': + // � [LATIN SMALL LETTER VISIGOTHIC Z] + case '\uFF5A': // z [FULLWIDTH LATIN SMALL LETTER Z] + output[opos++] = 'z'; + break; + + case '\u24B5': // â’µ [PARENTHESIZED LATIN SMALL LETTER Z] + output[opos++] = '('; + output[opos++] = 'z'; + output[opos++] = ')'; + break; + + case '\u2070': + // � [SUPERSCRIPT ZERO] + case '\u2080': + // â‚€ [SUBSCRIPT ZERO] + case '\u24EA': + // ⓪ [CIRCLED DIGIT ZERO] + case '\u24FF': + // â“¿ [NEGATIVE CIRCLED DIGIT ZERO] + case '\uFF10': // � [FULLWIDTH DIGIT ZERO] + output[opos++] = '0'; + break; + + case '\u00B9': + // ¹ [SUPERSCRIPT ONE] + case '\u2081': + // � [SUBSCRIPT ONE] + case '\u2460': + // â‘  [CIRCLED DIGIT ONE] + case '\u24F5': + // ⓵ [DOUBLE CIRCLED DIGIT ONE] + case '\u2776': + // � [DINGBAT NEGATIVE CIRCLED DIGIT ONE] + case '\u2780': + // ➀ [DINGBAT CIRCLED SANS-SERIF DIGIT ONE] + case '\u278A': + // ➊ [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ONE] + case '\uFF11': // 1 [FULLWIDTH DIGIT ONE] + output[opos++] = '1'; + break; + + case '\u2488': // â’ˆ [DIGIT ONE FULL STOP] + output[opos++] = '1'; + output[opos++] = '.'; + break; + + case '\u2474': // â‘´ [PARENTHESIZED DIGIT ONE] + output[opos++] = '('; + output[opos++] = '1'; + output[opos++] = ')'; + break; + + case '\u00B2': + // ² [SUPERSCRIPT TWO] + case '\u2082': + // â‚‚ [SUBSCRIPT TWO] + case '\u2461': + // â‘¡ [CIRCLED DIGIT TWO] + case '\u24F6': + // ⓶ [DOUBLE CIRCLED DIGIT TWO] + case '\u2777': + // � [DINGBAT NEGATIVE CIRCLED DIGIT TWO] + case '\u2781': + // � [DINGBAT CIRCLED SANS-SERIF DIGIT TWO] + case '\u278B': + // âž‹ [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT TWO] + case '\uFF12': // ï¼’ [FULLWIDTH DIGIT TWO] + output[opos++] = '2'; + break; + + case '\u2489': // â’‰ [DIGIT TWO FULL STOP] + output[opos++] = '2'; + output[opos++] = '.'; + break; + + case '\u2475': // ⑵ [PARENTHESIZED DIGIT TWO] + output[opos++] = '('; + output[opos++] = '2'; + output[opos++] = ')'; + break; + + case '\u00B3': + // ³ [SUPERSCRIPT THREE] + case '\u2083': + // ₃ [SUBSCRIPT THREE] + case '\u2462': + // â‘¢ [CIRCLED DIGIT THREE] + case '\u24F7': + // â“· [DOUBLE CIRCLED DIGIT THREE] + case '\u2778': + // � [DINGBAT NEGATIVE CIRCLED DIGIT THREE] + case '\u2782': + // âž‚ [DINGBAT CIRCLED SANS-SERIF DIGIT THREE] + case '\u278C': + // ➌ [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT THREE] + case '\uFF13': // 3 [FULLWIDTH DIGIT THREE] + output[opos++] = '3'; + break; + + case '\u248A': // â’Š [DIGIT THREE FULL STOP] + output[opos++] = '3'; + output[opos++] = '.'; + break; + + case '\u2476': // ⑶ [PARENTHESIZED DIGIT THREE] + output[opos++] = '('; + output[opos++] = '3'; + output[opos++] = ')'; + break; + + case '\u2074': + // � [SUPERSCRIPT FOUR] + case '\u2084': + // â‚„ [SUBSCRIPT FOUR] + case '\u2463': + // â‘£ [CIRCLED DIGIT FOUR] + case '\u24F8': + // ⓸ [DOUBLE CIRCLED DIGIT FOUR] + case '\u2779': + // � [DINGBAT NEGATIVE CIRCLED DIGIT FOUR] + case '\u2783': + // ➃ [DINGBAT CIRCLED SANS-SERIF DIGIT FOUR] + case '\u278D': + // � [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FOUR] + case '\uFF14': // �? [FULLWIDTH DIGIT FOUR] + output[opos++] = '4'; + break; + + case '\u248B': // â’‹ [DIGIT FOUR FULL STOP] + output[opos++] = '4'; + output[opos++] = '.'; + break; + + case '\u2477': // â‘· [PARENTHESIZED DIGIT FOUR] + output[opos++] = '('; + output[opos++] = '4'; + output[opos++] = ')'; + break; + + case '\u2075': + // � [SUPERSCRIPT FIVE] + case '\u2085': + // â‚… [SUBSCRIPT FIVE] + case '\u2464': + // ⑤ [CIRCLED DIGIT FIVE] + case '\u24F9': + // ⓹ [DOUBLE CIRCLED DIGIT FIVE] + case '\u277A': + // � [DINGBAT NEGATIVE CIRCLED DIGIT FIVE] + case '\u2784': + // âž„ [DINGBAT CIRCLED SANS-SERIF DIGIT FIVE] + case '\u278E': + // ➎ [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FIVE] + case '\uFF15': // 5 [FULLWIDTH DIGIT FIVE] + output[opos++] = '5'; + break; + + case '\u248C': // â’Œ [DIGIT FIVE FULL STOP] + output[opos++] = '5'; + output[opos++] = '.'; + break; + + case '\u2478': // ⑸ [PARENTHESIZED DIGIT FIVE] + output[opos++] = '('; + output[opos++] = '5'; + output[opos++] = ')'; + break; + + case '\u2076': + // � [SUPERSCRIPT SIX] + case '\u2086': + // ₆ [SUBSCRIPT SIX] + case '\u2465': + // â‘¥ [CIRCLED DIGIT SIX] + case '\u24FA': + // ⓺ [DOUBLE CIRCLED DIGIT SIX] + case '\u277B': + // � [DINGBAT NEGATIVE CIRCLED DIGIT SIX] + case '\u2785': + // âž… [DINGBAT CIRCLED SANS-SERIF DIGIT SIX] + case '\u278F': + // � [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SIX] + case '\uFF16': // ï¼– [FULLWIDTH DIGIT SIX] + output[opos++] = '6'; + break; + + case '\u248D': // â’� [DIGIT SIX FULL STOP] + output[opos++] = '6'; + output[opos++] = '.'; + break; + + case '\u2479': // ⑹ [PARENTHESIZED DIGIT SIX] + output[opos++] = '('; + output[opos++] = '6'; + output[opos++] = ')'; + break; + + case '\u2077': + // � [SUPERSCRIPT SEVEN] + case '\u2087': + // ₇ [SUBSCRIPT SEVEN] + case '\u2466': + // ⑦ [CIRCLED DIGIT SEVEN] + case '\u24FB': + // â“» [DOUBLE CIRCLED DIGIT SEVEN] + case '\u277C': + // � [DINGBAT NEGATIVE CIRCLED DIGIT SEVEN] + case '\u2786': + // ➆ [DINGBAT CIRCLED SANS-SERIF DIGIT SEVEN] + case '\u2790': + // � [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SEVEN] + case '\uFF17': // ï¼— [FULLWIDTH DIGIT SEVEN] + output[opos++] = '7'; + break; + + case '\u248E': // â’Ž [DIGIT SEVEN FULL STOP] + output[opos++] = '7'; + output[opos++] = '.'; + break; + + case '\u247A': // ⑺ [PARENTHESIZED DIGIT SEVEN] + output[opos++] = '('; + output[opos++] = '7'; + output[opos++] = ')'; + break; + + case '\u2078': + // � [SUPERSCRIPT EIGHT] + case '\u2088': + // ₈ [SUBSCRIPT EIGHT] + case '\u2467': + // ⑧ [CIRCLED DIGIT EIGHT] + case '\u24FC': + // ⓼ [DOUBLE CIRCLED DIGIT EIGHT] + case '\u277D': + // � [DINGBAT NEGATIVE CIRCLED DIGIT EIGHT] + case '\u2787': + // ➇ [DINGBAT CIRCLED SANS-SERIF DIGIT EIGHT] + case '\u2791': + // âž‘ [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT EIGHT] + case '\uFF18': // 8 [FULLWIDTH DIGIT EIGHT] + output[opos++] = '8'; + break; + + case '\u248F': // â’� [DIGIT EIGHT FULL STOP] + output[opos++] = '8'; + output[opos++] = '.'; + break; + + case '\u247B': // â‘» [PARENTHESIZED DIGIT EIGHT] + output[opos++] = '('; + output[opos++] = '8'; + output[opos++] = ')'; + break; + + case '\u2079': + // � [SUPERSCRIPT NINE] + case '\u2089': + // ₉ [SUBSCRIPT NINE] + case '\u2468': + // ⑨ [CIRCLED DIGIT NINE] + case '\u24FD': + // ⓽ [DOUBLE CIRCLED DIGIT NINE] + case '\u277E': + // � [DINGBAT NEGATIVE CIRCLED DIGIT NINE] + case '\u2788': + // ➈ [DINGBAT CIRCLED SANS-SERIF DIGIT NINE] + case '\u2792': + // âž’ [DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT NINE] + case '\uFF19': // ï¼™ [FULLWIDTH DIGIT NINE] + output[opos++] = '9'; + break; + + case '\u2490': // â’� [DIGIT NINE FULL STOP] + output[opos++] = '9'; + output[opos++] = '.'; + break; + + case '\u247C': // ⑼ [PARENTHESIZED DIGIT NINE] + output[opos++] = '('; + output[opos++] = '9'; + output[opos++] = ')'; + break; + + case '\u2469': + // â‘© [CIRCLED NUMBER TEN] + case '\u24FE': + // ⓾ [DOUBLE CIRCLED NUMBER TEN] + case '\u277F': + // � [DINGBAT NEGATIVE CIRCLED NUMBER TEN] + case '\u2789': + // ➉ [DINGBAT CIRCLED SANS-SERIF NUMBER TEN] + case '\u2793': // âž“ [DINGBAT NEGATIVE CIRCLED SANS-SERIF NUMBER TEN] + output[opos++] = '1'; + output[opos++] = '0'; + break; + + case '\u2491': // â’‘ [NUMBER TEN FULL STOP] + output[opos++] = '1'; + output[opos++] = '0'; + output[opos++] = '.'; + break; + + case '\u247D': // ⑽ [PARENTHESIZED NUMBER TEN] + output[opos++] = '('; + output[opos++] = '1'; + output[opos++] = '0'; + output[opos++] = ')'; + break; + + case '\u246A': + // ⑪ [CIRCLED NUMBER ELEVEN] + case '\u24EB': // â“« [NEGATIVE CIRCLED NUMBER ELEVEN] + output[opos++] = '1'; + output[opos++] = '1'; + break; + + case '\u2492': // â’’ [NUMBER ELEVEN FULL STOP] + output[opos++] = '1'; + output[opos++] = '1'; + output[opos++] = '.'; + break; + + case '\u247E': // ⑾ [PARENTHESIZED NUMBER ELEVEN] + output[opos++] = '('; + output[opos++] = '1'; + output[opos++] = '1'; + output[opos++] = ')'; + break; + + case '\u246B': + // â‘« [CIRCLED NUMBER TWELVE] + case '\u24EC': // ⓬ [NEGATIVE CIRCLED NUMBER TWELVE] + output[opos++] = '1'; + output[opos++] = '2'; + break; + + case '\u2493': // â’“ [NUMBER TWELVE FULL STOP] + output[opos++] = '1'; + output[opos++] = '2'; + output[opos++] = '.'; + break; + + case '\u247F': // â‘¿ [PARENTHESIZED NUMBER TWELVE] + output[opos++] = '('; + output[opos++] = '1'; + output[opos++] = '2'; + output[opos++] = ')'; + break; + + case '\u246C': + // ⑬ [CIRCLED NUMBER THIRTEEN] + case '\u24ED': // â“­ [NEGATIVE CIRCLED NUMBER THIRTEEN] + output[opos++] = '1'; + output[opos++] = '3'; + break; + + case '\u2494': // â’�? [NUMBER THIRTEEN FULL STOP] + output[opos++] = '1'; + output[opos++] = '3'; + output[opos++] = '.'; + break; + + case '\u2480': // â’€ [PARENTHESIZED NUMBER THIRTEEN] + output[opos++] = '('; + output[opos++] = '1'; + output[opos++] = '3'; + output[opos++] = ')'; + break; + + case '\u246D': + // â‘­ [CIRCLED NUMBER FOURTEEN] + case '\u24EE': // â“® [NEGATIVE CIRCLED NUMBER FOURTEEN] + output[opos++] = '1'; + output[opos++] = '4'; + break; + + case '\u2495': // â’• [NUMBER FOURTEEN FULL STOP] + output[opos++] = '1'; + output[opos++] = '4'; + output[opos++] = '.'; + break; + + case '\u2481': // â’� [PARENTHESIZED NUMBER FOURTEEN] + output[opos++] = '('; + output[opos++] = '1'; + output[opos++] = '4'; + output[opos++] = ')'; + break; + + case '\u246E': + // â‘® [CIRCLED NUMBER FIFTEEN] + case '\u24EF': // ⓯ [NEGATIVE CIRCLED NUMBER FIFTEEN] + output[opos++] = '1'; + output[opos++] = '5'; + break; + + case '\u2496': // â’– [NUMBER FIFTEEN FULL STOP] + output[opos++] = '1'; + output[opos++] = '5'; + output[opos++] = '.'; + break; + + case '\u2482': // â’‚ [PARENTHESIZED NUMBER FIFTEEN] + output[opos++] = '('; + output[opos++] = '1'; + output[opos++] = '5'; + output[opos++] = ')'; + break; + + case '\u246F': + // ⑯ [CIRCLED NUMBER SIXTEEN] + case '\u24F0': // â“° [NEGATIVE CIRCLED NUMBER SIXTEEN] + output[opos++] = '1'; + output[opos++] = '6'; + break; + + case '\u2497': // â’— [NUMBER SIXTEEN FULL STOP] + output[opos++] = '1'; + output[opos++] = '6'; + output[opos++] = '.'; + break; + + case '\u2483': // â’ƒ [PARENTHESIZED NUMBER SIXTEEN] + output[opos++] = '('; + output[opos++] = '1'; + output[opos++] = '6'; + output[opos++] = ')'; + break; + + case '\u2470': + // â‘° [CIRCLED NUMBER SEVENTEEN] + case '\u24F1': // ⓱ [NEGATIVE CIRCLED NUMBER SEVENTEEN] + output[opos++] = '1'; + output[opos++] = '7'; + break; + + case '\u2498': // â’˜ [NUMBER SEVENTEEN FULL STOP] + output[opos++] = '1'; + output[opos++] = '7'; + output[opos++] = '.'; + break; + + case '\u2484': // â’„ [PARENTHESIZED NUMBER SEVENTEEN] + output[opos++] = '('; + output[opos++] = '1'; + output[opos++] = '7'; + output[opos++] = ')'; + break; + + case '\u2471': + // ⑱ [CIRCLED NUMBER EIGHTEEN] + case '\u24F2': // ⓲ [NEGATIVE CIRCLED NUMBER EIGHTEEN] + output[opos++] = '1'; + output[opos++] = '8'; + break; + + case '\u2499': // â’™ [NUMBER EIGHTEEN FULL STOP] + output[opos++] = '1'; + output[opos++] = '8'; + output[opos++] = '.'; + break; + + case '\u2485': // â’… [PARENTHESIZED NUMBER EIGHTEEN] + output[opos++] = '('; + output[opos++] = '1'; + output[opos++] = '8'; + output[opos++] = ')'; + break; + + case '\u2472': + // ⑲ [CIRCLED NUMBER NINETEEN] + case '\u24F3': // ⓳ [NEGATIVE CIRCLED NUMBER NINETEEN] + output[opos++] = '1'; + output[opos++] = '9'; + break; + + case '\u249A': // â’š [NUMBER NINETEEN FULL STOP] + output[opos++] = '1'; + output[opos++] = '9'; + output[opos++] = '.'; + break; + + case '\u2486': // â’† [PARENTHESIZED NUMBER NINETEEN] + output[opos++] = '('; + output[opos++] = '1'; + output[opos++] = '9'; + output[opos++] = ')'; + break; + + case '\u2473': + // ⑳ [CIRCLED NUMBER TWENTY] + case '\u24F4': // â“´ [NEGATIVE CIRCLED NUMBER TWENTY] + output[opos++] = '2'; + output[opos++] = '0'; + break; + + case '\u249B': // â’› [NUMBER TWENTY FULL STOP] + output[opos++] = '2'; + output[opos++] = '0'; + output[opos++] = '.'; + break; + + case '\u2487': // â’‡ [PARENTHESIZED NUMBER TWENTY] + output[opos++] = '('; + output[opos++] = '2'; + output[opos++] = '0'; + output[opos++] = ')'; + break; + + case '\u00AB': + // « [LEFT-POINTING DOUBLE ANGLE QUOTATION MARK] + case '\u00BB': + // » [RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK] + case '\u201C': + // “ [LEFT DOUBLE QUOTATION MARK] + case '\u201D': + // � [RIGHT DOUBLE QUOTATION MARK] + case '\u201E': + // „ [DOUBLE LOW-9 QUOTATION MARK] + case '\u2033': + // ″ [DOUBLE PRIME] + case '\u2036': + // ‶ [REVERSED DOUBLE PRIME] + case '\u275D': + // � [HEAVY DOUBLE TURNED COMMA QUOTATION MARK ORNAMENT] + case '\u275E': + // � [HEAVY DOUBLE COMMA QUOTATION MARK ORNAMENT] + case '\u276E': + // � [HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT] + case '\u276F': + // � [HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT] + case '\uFF02': // " [FULLWIDTH QUOTATION MARK] + output[opos++] = '"'; + break; + + case '\u2018': + // ‘ [LEFT SINGLE QUOTATION MARK] + case '\u2019': + // ’ [RIGHT SINGLE QUOTATION MARK] + case '\u201A': + // ‚ [SINGLE LOW-9 QUOTATION MARK] + case '\u201B': + // ‛ [SINGLE HIGH-REVERSED-9 QUOTATION MARK] + case '\u2032': + // ′ [PRIME] + case '\u2035': + // ‵ [REVERSED PRIME] + case '\u2039': + // ‹ [SINGLE LEFT-POINTING ANGLE QUOTATION MARK] + case '\u203A': + // › [SINGLE RIGHT-POINTING ANGLE QUOTATION MARK] + case '\u275B': + // � [HEAVY SINGLE TURNED COMMA QUOTATION MARK ORNAMENT] + case '\u275C': + // � [HEAVY SINGLE COMMA QUOTATION MARK ORNAMENT] + case '\uFF07': // ' [FULLWIDTH APOSTROPHE] + output[opos++] = '\''; + break; + + case '\u2010': + // � [HYPHEN] + case '\u2011': + // ‑ [NON-BREAKING HYPHEN] + case '\u2012': + // ‒ [FIGURE DASH] + case '\u2013': + // – [EN DASH] + case '\u2014': + // �? [EM DASH] + case '\u207B': + // � [SUPERSCRIPT MINUS] + case '\u208B': + // â‚‹ [SUBSCRIPT MINUS] + case '\uFF0D': // � [FULLWIDTH HYPHEN-MINUS] + output[opos++] = '-'; + break; + + case '\u2045': + // � [LEFT SQUARE BRACKET WITH QUILL] + case '\u2772': + // � [LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT] + case '\uFF3B': // ï¼» [FULLWIDTH LEFT SQUARE BRACKET] + output[opos++] = '['; + break; + + case '\u2046': + // � [RIGHT SQUARE BRACKET WITH QUILL] + case '\u2773': + // � [LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT] + case '\uFF3D': // ï¼½ [FULLWIDTH RIGHT SQUARE BRACKET] + output[opos++] = ']'; + break; + + case '\u207D': + // � [SUPERSCRIPT LEFT PARENTHESIS] + case '\u208D': + // � [SUBSCRIPT LEFT PARENTHESIS] + case '\u2768': + // � [MEDIUM LEFT PARENTHESIS ORNAMENT] + case '\u276A': + // � [MEDIUM FLATTENED LEFT PARENTHESIS ORNAMENT] + case '\uFF08': // ( [FULLWIDTH LEFT PARENTHESIS] + output[opos++] = '('; + break; + + case '\u2E28': // ⸨ [LEFT DOUBLE PARENTHESIS] + output[opos++] = '('; + output[opos++] = '('; + break; + + case '\u207E': + // � [SUPERSCRIPT RIGHT PARENTHESIS] + case '\u208E': + // â‚Ž [SUBSCRIPT RIGHT PARENTHESIS] + case '\u2769': + // � [MEDIUM RIGHT PARENTHESIS ORNAMENT] + case '\u276B': + // � [MEDIUM FLATTENED RIGHT PARENTHESIS ORNAMENT] + case '\uFF09': // ) [FULLWIDTH RIGHT PARENTHESIS] + output[opos++] = ')'; + break; + + case '\u2E29': // ⸩ [RIGHT DOUBLE PARENTHESIS] + output[opos++] = ')'; + output[opos++] = ')'; + break; + + case '\u276C': + // � [MEDIUM LEFT-POINTING ANGLE BRACKET ORNAMENT] + case '\u2770': + // � [HEAVY LEFT-POINTING ANGLE BRACKET ORNAMENT] + case '\uFF1C': // < [FULLWIDTH LESS-THAN SIGN] + output[opos++] = '<'; + break; + + case '\u276D': + // � [MEDIUM RIGHT-POINTING ANGLE BRACKET ORNAMENT] + case '\u2771': + // � [HEAVY RIGHT-POINTING ANGLE BRACKET ORNAMENT] + case '\uFF1E': // > [FULLWIDTH GREATER-THAN SIGN] + output[opos++] = '>'; + break; + + case '\u2774': + // � [MEDIUM LEFT CURLY BRACKET ORNAMENT] + case '\uFF5B': // ï½› [FULLWIDTH LEFT CURLY BRACKET] + output[opos++] = '{'; + break; + + case '\u2775': + // � [MEDIUM RIGHT CURLY BRACKET ORNAMENT] + case '\uFF5D': // � [FULLWIDTH RIGHT CURLY BRACKET] + output[opos++] = '}'; + break; + + case '\u207A': + // � [SUPERSCRIPT PLUS SIGN] + case '\u208A': + // â‚Š [SUBSCRIPT PLUS SIGN] + case '\uFF0B': // + [FULLWIDTH PLUS SIGN] + output[opos++] = '+'; + break; + + case '\u207C': + // � [SUPERSCRIPT EQUALS SIGN] + case '\u208C': + // â‚Œ [SUBSCRIPT EQUALS SIGN] + case '\uFF1D': // � [FULLWIDTH EQUALS SIGN] + output[opos++] = '='; + break; + + case '\uFF01': // � [FULLWIDTH EXCLAMATION MARK] + output[opos++] = '!'; + break; + + case '\u203C': // ‼ [DOUBLE EXCLAMATION MARK] + output[opos++] = '!'; + output[opos++] = '!'; + break; + + case '\u2049': // � [EXCLAMATION QUESTION MARK] + output[opos++] = '!'; + output[opos++] = '?'; + break; + + case '\uFF03': // # [FULLWIDTH NUMBER SIGN] + output[opos++] = '#'; + break; + + case '\uFF04': // $ [FULLWIDTH DOLLAR SIGN] + output[opos++] = '$'; + break; + + case '\u2052': + // � [COMMERCIAL MINUS SIGN] + case '\uFF05': // ï¼… [FULLWIDTH PERCENT SIGN] + output[opos++] = '%'; + break; + + case '\uFF06': // & [FULLWIDTH AMPERSAND] + output[opos++] = '&'; + break; + + case '\u204E': + // � [LOW ASTERISK] + case '\uFF0A': // * [FULLWIDTH ASTERISK] + output[opos++] = '*'; + break; + + case '\uFF0C': // , [FULLWIDTH COMMA] + output[opos++] = ','; + break; + + case '\uFF0E': // . [FULLWIDTH FULL STOP] + output[opos++] = '.'; + break; + + case '\u2044': + // � [FRACTION SLASH] + case '\uFF0F': // � [FULLWIDTH SOLIDUS] + output[opos++] = '/'; + break; + + case '\uFF1A': // : [FULLWIDTH COLON] + output[opos++] = ':'; + break; + + case '\u204F': + // � [REVERSED SEMICOLON] + case '\uFF1B': // ï¼› [FULLWIDTH SEMICOLON] + output[opos++] = ';'; + break; + + case '\uFF1F': // ? [FULLWIDTH QUESTION MARK] + output[opos++] = '?'; + break; + + case '\u2047': // � [DOUBLE QUESTION MARK] + output[opos++] = '?'; + output[opos++] = '?'; + break; + + case '\u2048': // � [QUESTION EXCLAMATION MARK] + output[opos++] = '?'; + output[opos++] = '!'; + break; + + case '\uFF20': // ï¼  [FULLWIDTH COMMERCIAL AT] + output[opos++] = '@'; + break; + + case '\uFF3C': // ï¼¼ [FULLWIDTH REVERSE SOLIDUS] + output[opos++] = '\\'; + break; + + case '\u2038': + // ‸ [CARET] + case '\uFF3E': // ï¼¾ [FULLWIDTH CIRCUMFLEX ACCENT] + output[opos++] = '^'; + break; + + case '\uFF3F': // _ [FULLWIDTH LOW LINE] + output[opos++] = '_'; + break; + + case '\u2053': + // � [SWUNG DASH] + case '\uFF5E': // ~ [FULLWIDTH TILDE] + output[opos++] = '~'; + break; + + // BEGIN CUSTOM TRANSLITERATION OF CYRILIC CHARS + + #region Cyrillic chars + + // russian uppercase "А Б В Г Д Е Ё Ж З И Й К Л М Н О П Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я" + // russian lowercase "а б в г д е ё ж з и й к л м н о п р с т у ф х ц ч ш щ ъ ы ь э ю я" + + // notes + // read http://www.vesic.org/english/blog/c-sharp/transliteration-easy-way-microsoft-transliteration-utility/ + // should we look into MS Transliteration Utility (http://msdn.microsoft.com/en-US/goglobal/bb688104.aspx) + // also UnicodeSharpFork https://bitbucket.org/DimaStefantsov/unidecodesharpfork + // also Transliterator http://transliterator.codeplex.com/ + // + // in any case it would be good to generate all those "case" statements instead of writing them by hand + // time for a T4 template? + // also we should support extensibility so ppl can register more cases in external code + + // TODO: transliterates Анастасия as Anastasiya, and not Anastasia + // Ольга --> Ol'ga, Татьяна --> Tat'yana -- that's bad (?) + // Note: should ä (German umlaut) become a or ae ? + + case '\u0410': // А + output[opos++] = 'A'; + break; + case '\u0430': // а + output[opos++] = 'a'; + break; + case '\u0411': // Б + output[opos++] = 'B'; + break; + case '\u0431': // б + output[opos++] = 'b'; + break; + case '\u0412': // В + output[opos++] = 'V'; + break; + case '\u0432': // в + output[opos++] = 'v'; + break; + case '\u0413': // Г + output[opos++] = 'G'; + break; + case '\u0433': // г + output[opos++] = 'g'; + break; + case '\u0414': // Д + output[opos++] = 'D'; + break; + case '\u0434': // д + output[opos++] = 'd'; + break; + case '\u0415': // Е + output[opos++] = 'E'; + break; + case '\u0435': // е + output[opos++] = 'e'; + break; + case '\u0401': // Ё + output[opos++] = 'E'; // alt. Yo + break; + case '\u0451': // ё + output[opos++] = 'e'; // alt. yo + break; + case '\u0416': // Ж + output[opos++] = 'Z'; + output[opos++] = 'h'; + break; + case '\u0436': // ж + output[opos++] = 'z'; + output[opos++] = 'h'; + break; + case '\u0417': // З + output[opos++] = 'Z'; + break; + case '\u0437': // з + output[opos++] = 'z'; + break; + case '\u0418': // И + output[opos++] = 'I'; + break; + case '\u0438': // и + output[opos++] = 'i'; + break; + case '\u0419': // Й + output[opos++] = 'I'; // alt. Y, J + break; + case '\u0439': // й + output[opos++] = 'i'; // alt. y, j + break; + case '\u041A': // К + output[opos++] = 'K'; + break; + case '\u043A': // к + output[opos++] = 'k'; + break; + case '\u041B': // Л + output[opos++] = 'L'; + break; + case '\u043B': // л + output[opos++] = 'l'; + break; + case '\u041C': // М + output[opos++] = 'M'; + break; + case '\u043C': // м + output[opos++] = 'm'; + break; + case '\u041D': // Н + output[opos++] = 'N'; + break; + case '\u043D': // н + output[opos++] = 'n'; + break; + case '\u041E': // О + output[opos++] = 'O'; + break; + case '\u043E': // о + output[opos++] = 'o'; + break; + case '\u041F': // П + output[opos++] = 'P'; + break; + case '\u043F': // п + output[opos++] = 'p'; + break; + case '\u0420': // Р + output[opos++] = 'R'; + break; + case '\u0440': // р + output[opos++] = 'r'; + break; + case '\u0421': // С + output[opos++] = 'S'; + break; + case '\u0441': // с + output[opos++] = 's'; + break; + case '\u0422': // Т + output[opos++] = 'T'; + break; + case '\u0442': // т + output[opos++] = 't'; + break; + case '\u0423': // У + output[opos++] = 'U'; + break; + case '\u0443': // у + output[opos++] = 'u'; + break; + case '\u0424': // Ф + output[opos++] = 'F'; + break; + case '\u0444': // ф + output[opos++] = 'f'; + break; + case '\u0425': // Х + output[opos++] = 'K'; // alt. X + output[opos++] = 'h'; + break; + case '\u0445': // х + output[opos++] = 'k'; // alt. x + output[opos++] = 'h'; + break; + case '\u0426': // Ц + output[opos++] = 'F'; + break; + case '\u0446': // ц + output[opos++] = 'f'; + break; + case '\u0427': // Ч + output[opos++] = 'C'; // alt. Ts, C + output[opos++] = 'h'; + break; + case '\u0447': // ч + output[opos++] = 'c'; // alt. ts, c + output[opos++] = 'h'; + break; + case '\u0428': // Ш + output[opos++] = 'S'; // alt. Ch, S + output[opos++] = 'h'; + break; + case '\u0448': // ш + output[opos++] = 's'; // alt. ch, s + output[opos++] = 'h'; + break; + case '\u0429': // Щ + output[opos++] = 'S'; // alt. Shch, Sc + output[opos++] = 'h'; + break; + case '\u0449': // щ + output[opos++] = 's'; // alt. shch, sc + output[opos++] = 'h'; + break; + case '\u042A': // Ъ + output[opos++] = '"'; // " + break; + case '\u044A': // ъ + output[opos++] = '"'; // " + break; + case '\u042B': // Ы + output[opos++] = 'Y'; + break; + case '\u044B': // ы + output[opos++] = 'y'; + break; + case '\u042C': // Ь + output[opos++] = '\''; // ' + break; + case '\u044C': // ь + output[opos++] = '\''; // ' + break; + case '\u042D': // Э + output[opos++] = 'E'; + break; + case '\u044D': // э + output[opos++] = 'e'; + break; + case '\u042E': // Ю + output[opos++] = 'Y'; // alt. Ju + output[opos++] = 'u'; + break; + case '\u044E': // ю + output[opos++] = 'y'; // alt. ju + output[opos++] = 'u'; + break; + case '\u042F': // Я + output[opos++] = 'Y'; // alt. Ja + output[opos++] = 'a'; + break; + case '\u044F': // я + output[opos++] = 'y'; // alt. ja + output[opos++] = 'a'; + break; + + #endregion + + // BEGIN EXTRA + /* + case '£': + output[opos++] = 'G'; + output[opos++] = 'B'; + output[opos++] = 'P'; + break; + + case '€': + output[opos++] = 'E'; + output[opos++] = 'U'; + output[opos++] = 'R'; + break; + + case '©': + output[opos++] = '('; + output[opos++] = 'C'; + output[opos++] = ')'; + break; + */ + default: + //if (ToMoreAscii(input, ipos, output, ref opos)) + // break; + + //if (!char.IsLetterOrDigit(c)) // that would not catch eg 汉 unfortunately + // output[opos++] = '?'; + //else + // output[opos++] = c; + + // strict ASCII + output[opos++] = fail; + + break; + } + } + } + + //private static bool ToMoreAscii(char[] input, int ipos, char[] output, ref int opos) + //{ + // var c = input[ipos]; + + // switch (c) + // { + // case '£': + // output[opos++] = 'G'; + // output[opos++] = 'B'; + // output[opos++] = 'P'; + // break; + + // case '€': + // output[opos++] = 'E'; + // output[opos++] = 'U'; + // output[opos++] = 'R'; + // break; + + // case '©': + // output[opos++] = '('; + // output[opos++] = 'C'; + // output[opos++] = ')'; + // break; + + // default: + // return false; + // } + + // return true; + //} } diff --git a/src/Umbraco.Core/Sync/ElectedServerRoleAccessor.cs b/src/Umbraco.Core/Sync/ElectedServerRoleAccessor.cs index 340de80c96f6..09c904b7bcff 100644 --- a/src/Umbraco.Core/Sync/ElectedServerRoleAccessor.cs +++ b/src/Umbraco.Core/Sync/ElectedServerRoleAccessor.cs @@ -1,29 +1,30 @@ -using System; using Umbraco.Cms.Core.Services; -namespace Umbraco.Cms.Core.Sync +namespace Umbraco.Cms.Core.Sync; + +/// +/// Gets the current server's based on active servers registered with +/// +/// +/// +/// This is the default service which determines a server's role by using a master election process. +/// The scheduling publisher election process doesn't occur until just after startup so this election process doesn't +/// really affect the primary startup phase. +/// +public sealed class ElectedServerRoleAccessor : IServerRoleAccessor { + private readonly IServerRegistrationService _registrationService; + /// - /// Gets the current server's based on active servers registered with + /// Initializes a new instance of the class. /// - /// - /// This is the default service which determines a server's role by using a master election process. - /// The scheduling publisher election process doesn't occur until just after startup so this election process doesn't really affect the primary startup phase. - /// - public sealed class ElectedServerRoleAccessor : IServerRoleAccessor - { - private readonly IServerRegistrationService _registrationService; - - /// - /// Initializes a new instance of the class. - /// - /// The registration service. - /// Some options. - public ElectedServerRoleAccessor(IServerRegistrationService registrationService) => _registrationService = registrationService ?? throw new ArgumentNullException(nameof(registrationService)); + /// The registration service. + /// Some options. + public ElectedServerRoleAccessor(IServerRegistrationService registrationService) => _registrationService = + registrationService ?? throw new ArgumentNullException(nameof(registrationService)); - /// - /// Gets the role of the current server in the application environment. - /// - public ServerRole CurrentServerRole => _registrationService.GetCurrentServerRole(); - } + /// + /// Gets the role of the current server in the application environment. + /// + public ServerRole CurrentServerRole => _registrationService.GetCurrentServerRole(); } diff --git a/src/Umbraco.Core/Sync/IServerAddress.cs b/src/Umbraco.Core/Sync/IServerAddress.cs index 4de7490d8fe3..cc9da01db052 100644 --- a/src/Umbraco.Core/Sync/IServerAddress.cs +++ b/src/Umbraco.Core/Sync/IServerAddress.cs @@ -1,15 +1,14 @@ -namespace Umbraco.Cms.Core.Sync +namespace Umbraco.Cms.Core.Sync; + +/// +/// Provides the address of a server. +/// +public interface IServerAddress { /// - /// Provides the address of a server. + /// Gets the server address. /// - public interface IServerAddress - { - /// - /// Gets the server address. - /// - string? ServerAddress { get; } + string? ServerAddress { get; } - // TODO: Should probably add things like port, protocol, server name, app id - } + // TODO: Should probably add things like port, protocol, server name, app id } diff --git a/src/Umbraco.Core/Sync/IServerMessenger.cs b/src/Umbraco.Core/Sync/IServerMessenger.cs index e58cfe9bc019..49cd397e2d79 100644 --- a/src/Umbraco.Core/Sync/IServerMessenger.cs +++ b/src/Umbraco.Core/Sync/IServerMessenger.cs @@ -1,83 +1,81 @@ -using System; using Umbraco.Cms.Core.Cache; -namespace Umbraco.Cms.Core.Sync +namespace Umbraco.Cms.Core.Sync; + +/// +/// Transmits distributed cache notifications for all servers of a load balanced environment. +/// +/// Also ensures that the notification is processed on the local environment. +public interface IServerMessenger { /// - /// Transmits distributed cache notifications for all servers of a load balanced environment. + /// Called to synchronize a server with queued notifications /// - /// Also ensures that the notification is processed on the local environment. - public interface IServerMessenger - { - /// - /// Called to synchronize a server with queued notifications - /// - void Sync(); + void Sync(); - /// - /// Called to send/commit the queued messages created with the Perform methods - /// - void SendMessages(); + /// + /// Called to send/commit the queued messages created with the Perform methods + /// + void SendMessages(); - /// - /// Notifies the distributed cache, for a specified . - /// - /// The ICacheRefresher. - /// The notification content. - void QueueRefresh(ICacheRefresher refresher, TPayload[] payload); + /// + /// Notifies the distributed cache, for a specified . + /// + /// The ICacheRefresher. + /// The notification content. + void QueueRefresh(ICacheRefresher refresher, TPayload[] payload); - /// - /// Notifies the distributed cache of specified item invalidation, for a specified . - /// - /// The type of the invalidated items. - /// The ICacheRefresher. - /// A function returning the unique identifier of items. - /// The invalidated items. - void QueueRefresh(ICacheRefresher refresher, Func getNumericId, params T[] instances); + /// + /// Notifies the distributed cache of specified item invalidation, for a specified . + /// + /// The type of the invalidated items. + /// The ICacheRefresher. + /// A function returning the unique identifier of items. + /// The invalidated items. + void QueueRefresh(ICacheRefresher refresher, Func getNumericId, params T[] instances); - /// - /// Notifies the distributed cache of specified item invalidation, for a specified . - /// - /// The type of the invalidated items. - /// The ICacheRefresher. - /// A function returning the unique identifier of items. - /// The invalidated items. - void QueueRefresh(ICacheRefresher refresher, Func getGuidId, params T[] instances); + /// + /// Notifies the distributed cache of specified item invalidation, for a specified . + /// + /// The type of the invalidated items. + /// The ICacheRefresher. + /// A function returning the unique identifier of items. + /// The invalidated items. + void QueueRefresh(ICacheRefresher refresher, Func getGuidId, params T[] instances); - /// - /// Notifies all servers of specified items removal, for a specified . - /// - /// The type of the removed items. - /// The ICacheRefresher. - /// A function returning the unique identifier of items. - /// The removed items. - void QueueRemove(ICacheRefresher refresher, Func getNumericId, params T[] instances); + /// + /// Notifies all servers of specified items removal, for a specified . + /// + /// The type of the removed items. + /// The ICacheRefresher. + /// A function returning the unique identifier of items. + /// The removed items. + void QueueRemove(ICacheRefresher refresher, Func getNumericId, params T[] instances); - /// - /// Notifies all servers of specified items removal, for a specified . - /// - /// The ICacheRefresher. - /// The unique identifiers of the removed items. - void QueueRemove(ICacheRefresher refresher, params int[] numericIds); + /// + /// Notifies all servers of specified items removal, for a specified . + /// + /// The ICacheRefresher. + /// The unique identifiers of the removed items. + void QueueRemove(ICacheRefresher refresher, params int[] numericIds); - /// - /// Notifies all servers of specified items invalidation, for a specified . - /// - /// The ICacheRefresher. - /// The unique identifiers of the invalidated items. - void QueueRefresh(ICacheRefresher refresher, params int[] numericIds); + /// + /// Notifies all servers of specified items invalidation, for a specified . + /// + /// The ICacheRefresher. + /// The unique identifiers of the invalidated items. + void QueueRefresh(ICacheRefresher refresher, params int[] numericIds); - /// - /// Notifies all servers of specified items invalidation, for a specified . - /// - /// The ICacheRefresher. - /// The unique identifiers of the invalidated items. - void QueueRefresh(ICacheRefresher refresher, params Guid[] guidIds); + /// + /// Notifies all servers of specified items invalidation, for a specified . + /// + /// The ICacheRefresher. + /// The unique identifiers of the invalidated items. + void QueueRefresh(ICacheRefresher refresher, params Guid[] guidIds); - /// - /// Notifies all servers of a global invalidation for a specified . - /// - /// The ICacheRefresher. - void QueueRefreshAll(ICacheRefresher refresher); - } + /// + /// Notifies all servers of a global invalidation for a specified . + /// + /// The ICacheRefresher. + void QueueRefreshAll(ICacheRefresher refresher); } diff --git a/src/Umbraco.Core/Sync/IServerRoleAccessor.cs b/src/Umbraco.Core/Sync/IServerRoleAccessor.cs index 1ebd59b26dcf..aed70b0f50fc 100644 --- a/src/Umbraco.Core/Sync/IServerRoleAccessor.cs +++ b/src/Umbraco.Core/Sync/IServerRoleAccessor.cs @@ -1,13 +1,12 @@ -namespace Umbraco.Cms.Core.Sync +namespace Umbraco.Cms.Core.Sync; + +/// +/// Gets the current server's +/// +public interface IServerRoleAccessor { /// - /// Gets the current server's + /// Gets the role of the current server in the application environment. /// - public interface IServerRoleAccessor - { - /// - /// Gets the role of the current server in the application environment. - /// - ServerRole CurrentServerRole { get; } - } + ServerRole CurrentServerRole { get; } } diff --git a/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs b/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs index 0c616a4e6873..842a3ff424f3 100644 --- a/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs +++ b/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs @@ -1,14 +1,13 @@ -namespace Umbraco.Cms.Core.Sync +namespace Umbraco.Cms.Core.Sync; + +/// +/// Retrieve the for the application during startup +/// +public interface ISyncBootStateAccessor { /// - /// Retrieve the for the application during startup + /// Get the /// - public interface ISyncBootStateAccessor - { - /// - /// Get the - /// - /// - SyncBootState GetSyncBootState(); - } + /// + SyncBootState GetSyncBootState(); } diff --git a/src/Umbraco.Core/Sync/MessageType.cs b/src/Umbraco.Core/Sync/MessageType.cs index 51644286322d..ab1a54c873cc 100644 --- a/src/Umbraco.Core/Sync/MessageType.cs +++ b/src/Umbraco.Core/Sync/MessageType.cs @@ -1,16 +1,15 @@ -namespace Umbraco.Cms.Core.Sync +namespace Umbraco.Cms.Core.Sync; + +/// +/// The message type to be used for syncing across servers. +/// +public enum MessageType { - /// - /// The message type to be used for syncing across servers. - /// - public enum MessageType - { - RefreshAll, - RefreshById, - RefreshByJson, - RemoveById, - RefreshByInstance, - RemoveByInstance, - RefreshByPayload - } + RefreshAll, + RefreshById, + RefreshByJson, + RemoveById, + RefreshByInstance, + RemoveByInstance, + RefreshByPayload } diff --git a/src/Umbraco.Core/Sync/NonRuntimeLevelBootStateAccessor.cs b/src/Umbraco.Core/Sync/NonRuntimeLevelBootStateAccessor.cs index 0dcfa471db16..0df9a645c825 100644 --- a/src/Umbraco.Core/Sync/NonRuntimeLevelBootStateAccessor.cs +++ b/src/Umbraco.Core/Sync/NonRuntimeLevelBootStateAccessor.cs @@ -1,10 +1,9 @@ -namespace Umbraco.Cms.Core.Sync +namespace Umbraco.Cms.Core.Sync; + +/// +/// Boot state implementation for when umbraco is not in the run state +/// +public sealed class NonRuntimeLevelBootStateAccessor : ISyncBootStateAccessor { - /// - /// Boot state implementation for when umbraco is not in the run state - /// - public sealed class NonRuntimeLevelBootStateAccessor : ISyncBootStateAccessor - { - public SyncBootState GetSyncBootState() => SyncBootState.Unknown; - } + public SyncBootState GetSyncBootState() => SyncBootState.Unknown; } diff --git a/src/Umbraco.Core/Sync/RefreshInstruction.cs b/src/Umbraco.Core/Sync/RefreshInstruction.cs index b8609410ab6c..1ac0e7479edd 100644 --- a/src/Umbraco.Core/Sync/RefreshInstruction.cs +++ b/src/Umbraco.Core/Sync/RefreshInstruction.cs @@ -1,217 +1,222 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Serialization; -namespace Umbraco.Cms.Core.Sync +namespace Umbraco.Cms.Core.Sync; + +[Serializable] +public class RefreshInstruction { - [Serializable] - public class RefreshInstruction + // NOTE + // that class should be refactored + // but at the moment it is exposed in CacheRefresher webservice + // so for the time being we keep it as-is for backward compatibility reasons + + // need this public, parameter-less constructor so the web service messenger + // can de-serialize the instructions it receives + + /// + /// Initializes a new instance of the class. + /// + /// + /// Need this public, parameter-less constructor so the web service messenger can de-serialize the instructions it + /// receives. + /// + public RefreshInstruction() => + + // Set default - this value is not used for reading after it's been deserialized, it's only used for persisting the instruction to the db + JsonIdCount = 1; + + /// + /// Initializes a new instance of the class. + /// + /// + /// Need this public one so it can be de-serialized - used by the Json thing + /// otherwise, should use GetInstructions(...) + /// + public RefreshInstruction(Guid refresherId, RefreshMethodType refreshType, Guid guidId, int intId, string jsonIds, + string jsonPayload) + : this() { - // NOTE - // that class should be refactored - // but at the moment it is exposed in CacheRefresher webservice - // so for the time being we keep it as-is for backward compatibility reasons - - // need this public, parameter-less constructor so the web service messenger - // can de-serialize the instructions it receives - - /// - /// Initializes a new instance of the class. - /// - /// - /// Need this public, parameter-less constructor so the web service messenger can de-serialize the instructions it receives. - /// - public RefreshInstruction() => - - // Set default - this value is not used for reading after it's been deserialized, it's only used for persisting the instruction to the db - JsonIdCount = 1; - - /// - /// Initializes a new instance of the class. - /// - /// - /// Need this public one so it can be de-serialized - used by the Json thing - /// otherwise, should use GetInstructions(...) - /// - public RefreshInstruction(Guid refresherId, RefreshMethodType refreshType, Guid guidId, int intId, string jsonIds, string jsonPayload) - : this() - { - RefresherId = refresherId; - RefreshType = refreshType; - GuidId = guidId; - IntId = intId; - JsonIds = jsonIds; - JsonPayload = jsonPayload; - } + RefresherId = refresherId; + RefreshType = refreshType; + GuidId = guidId; + IntId = intId; + JsonIds = jsonIds; + JsonPayload = jsonPayload; + } + + private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshType) + : this() + { + RefresherId = refresher.RefresherUniqueId; + RefreshType = refreshType; + } - private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshType) - : this() + private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshType, Guid guidId) + : this(refresher, refreshType) => GuidId = guidId; + + private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshType, int intId) + : this(refresher, refreshType) => IntId = intId; + + /// + /// A private constructor to create a new instance + /// + /// + /// When the refresh method is we know how many Ids are being refreshed + /// so we know the instruction + /// count which will be taken into account when we store this count in the database. + /// + private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshType, string? json, int idCount = 1) + : this(refresher, refreshType) + { + JsonIdCount = idCount; + + if (refreshType == RefreshMethodType.RefreshByJson) { - RefresherId = refresher.RefresherUniqueId; - RefreshType = refreshType; + JsonPayload = json; } - - private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshType, Guid guidId) - : this(refresher, refreshType) => GuidId = guidId; - - private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshType, int intId) - : this(refresher, refreshType) => IntId = intId; - - /// - /// A private constructor to create a new instance - /// - /// - /// When the refresh method is we know how many Ids are being refreshed so we know the instruction - /// count which will be taken into account when we store this count in the database. - /// - private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshType, string? json, int idCount = 1) - : this(refresher, refreshType) + else { - JsonIdCount = idCount; - - if (refreshType == RefreshMethodType.RefreshByJson) - { - JsonPayload = json; - } - else - { - JsonIds = json; - } + JsonIds = json; } + } - public static IEnumerable GetInstructions( - ICacheRefresher refresher, - IJsonSerializer jsonSerializer, - MessageType messageType, - IEnumerable? ids, - Type? idType, - string? json) + /// + /// Gets or sets the refresh action type. + /// + public RefreshMethodType RefreshType { get; set; } + + /// + /// Gets or sets the refresher unique identifier. + /// + public Guid RefresherId { get; set; } + + /// + /// Gets or sets the Guid data value. + /// + public Guid GuidId { get; set; } + + /// + /// Gets or sets the int data value. + /// + public int IntId { get; set; } + + /// + /// Gets or sets the ids data value. + /// + public string? JsonIds { get; set; } + + /// + /// Gets or sets the number of Ids contained in the JsonIds json value. + /// + /// + /// This is used to determine the instruction count per row. + /// + public int JsonIdCount { get; set; } + + /// + /// Gets or sets the payload data value. + /// + public string? JsonPayload { get; set; } + + public static IEnumerable GetInstructions( + ICacheRefresher refresher, + IJsonSerializer jsonSerializer, + MessageType messageType, + IEnumerable? ids, + Type? idType, + string? json) + { + switch (messageType) { - switch (messageType) - { - case MessageType.RefreshAll: - return new[] { new RefreshInstruction(refresher, RefreshMethodType.RefreshAll) }; - - case MessageType.RefreshByJson: - return new[] { new RefreshInstruction(refresher, RefreshMethodType.RefreshByJson, json) }; - - case MessageType.RefreshById: - if (idType == null) + case MessageType.RefreshAll: + return new[] {new RefreshInstruction(refresher, RefreshMethodType.RefreshAll)}; + + case MessageType.RefreshByJson: + return new[] {new RefreshInstruction(refresher, RefreshMethodType.RefreshByJson, json)}; + + case MessageType.RefreshById: + if (idType == null) + { + throw new InvalidOperationException("Cannot refresh by id if idType is null."); + } + + if (idType == typeof(int)) + { + // Bulk of ints is supported + var intIds = ids?.Cast().ToArray(); + return new[] { - throw new InvalidOperationException("Cannot refresh by id if idType is null."); - } - - if (idType == typeof(int)) - { - // Bulk of ints is supported - var intIds = ids?.Cast().ToArray(); - return new[] { new RefreshInstruction(refresher, RefreshMethodType.RefreshByIds, jsonSerializer.Serialize(intIds), intIds?.Length ?? 0) }; - } + new RefreshInstruction(refresher, RefreshMethodType.RefreshByIds, + jsonSerializer.Serialize(intIds), intIds?.Length ?? 0) + }; + } + + // Else must be guids, bulk of guids is not supported, so iterate. + return ids?.Select(x => new RefreshInstruction(refresher, RefreshMethodType.RefreshByGuid, (Guid)x)) ?? + Enumerable.Empty(); + + case MessageType.RemoveById: + if (idType == null) + { + throw new InvalidOperationException("Cannot remove by id if idType is null."); + } + + // Must be ints, bulk-remove is not supported, so iterate. + return ids?.Select(x => new RefreshInstruction(refresher, RefreshMethodType.RemoveById, (int)x)) ?? + Enumerable.Empty(); + //return new[] { new RefreshInstruction(refresher, RefreshMethodType.RemoveByIds, JsonConvert.SerializeObject(ids.Cast().ToArray())) }; + + default: + //case MessageType.RefreshByInstance: + //case MessageType.RemoveByInstance: + throw new ArgumentOutOfRangeException("messageType"); + } + } - // Else must be guids, bulk of guids is not supported, so iterate. - return ids?.Select(x => new RefreshInstruction(refresher, RefreshMethodType.RefreshByGuid, (Guid) x)) ?? Enumerable.Empty(); + protected bool Equals(RefreshInstruction other) => + RefreshType == other.RefreshType + && RefresherId.Equals(other.RefresherId) + && GuidId.Equals(other.GuidId) + && IntId == other.IntId + && string.Equals(JsonIds, other.JsonIds) + && string.Equals(JsonPayload, other.JsonPayload); - case MessageType.RemoveById: - if (idType == null) - { - throw new InvalidOperationException("Cannot remove by id if idType is null."); - } - - // Must be ints, bulk-remove is not supported, so iterate. - return ids?.Select(x => new RefreshInstruction(refresher, RefreshMethodType.RemoveById, (int) x)) ?? Enumerable.Empty(); - //return new[] { new RefreshInstruction(refresher, RefreshMethodType.RemoveByIds, JsonConvert.SerializeObject(ids.Cast().ToArray())) }; - - default: - //case MessageType.RefreshByInstance: - //case MessageType.RemoveByInstance: - throw new ArgumentOutOfRangeException("messageType"); - } + public override bool Equals(object? other) + { + if (other is null) + { + return false; } - /// - /// Gets or sets the refresh action type. - /// - public RefreshMethodType RefreshType { get; set; } - - /// - /// Gets or sets the refresher unique identifier. - /// - public Guid RefresherId { get; set; } - - /// - /// Gets or sets the Guid data value. - /// - public Guid GuidId { get; set; } - - /// - /// Gets or sets the int data value. - /// - public int IntId { get; set; } - - /// - /// Gets or sets the ids data value. - /// - public string? JsonIds { get; set; } - - /// - /// Gets or sets the number of Ids contained in the JsonIds json value. - /// - /// - /// This is used to determine the instruction count per row. - /// - public int JsonIdCount { get; set; } - - /// - /// Gets or sets the payload data value. - /// - public string? JsonPayload { get; set; } - - protected bool Equals(RefreshInstruction other) => - RefreshType == other.RefreshType - && RefresherId.Equals(other.RefresherId) - && GuidId.Equals(other.GuidId) - && IntId == other.IntId - && string.Equals(JsonIds, other.JsonIds) - && string.Equals(JsonPayload, other.JsonPayload); - - public override bool Equals(object? other) + if (ReferenceEquals(this, other)) { - if (other is null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - if (other.GetType() != GetType()) - { - return false; - } - - return Equals((RefreshInstruction) other); + return true; } - public override int GetHashCode() + if (other.GetType() != GetType()) { - unchecked - { - var hashCode = (int) RefreshType; - hashCode = (hashCode*397) ^ RefresherId.GetHashCode(); - hashCode = (hashCode*397) ^ GuidId.GetHashCode(); - hashCode = (hashCode*397) ^ IntId; - hashCode = (hashCode*397) ^ (JsonIds != null ? JsonIds.GetHashCode() : 0); - hashCode = (hashCode*397) ^ (JsonPayload != null ? JsonPayload.GetHashCode() : 0); - return hashCode; - } + return false; } - public static bool operator ==(RefreshInstruction left, RefreshInstruction right) => Equals(left, right); + return Equals((RefreshInstruction)other); + } - public static bool operator !=(RefreshInstruction left, RefreshInstruction right) => Equals(left, right) == false; + public override int GetHashCode() + { + unchecked + { + var hashCode = (int)RefreshType; + hashCode = (hashCode * 397) ^ RefresherId.GetHashCode(); + hashCode = (hashCode * 397) ^ GuidId.GetHashCode(); + hashCode = (hashCode * 397) ^ IntId; + hashCode = (hashCode * 397) ^ (JsonIds != null ? JsonIds.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (JsonPayload != null ? JsonPayload.GetHashCode() : 0); + return hashCode; + } } + + public static bool operator ==(RefreshInstruction left, RefreshInstruction right) => Equals(left, right); + + public static bool operator !=(RefreshInstruction left, RefreshInstruction right) => Equals(left, right) == false; } diff --git a/src/Umbraco.Core/Sync/RefreshMethodType.cs b/src/Umbraco.Core/Sync/RefreshMethodType.cs index bf72423c1f7f..78d1880951e3 100644 --- a/src/Umbraco.Core/Sync/RefreshMethodType.cs +++ b/src/Umbraco.Core/Sync/RefreshMethodType.cs @@ -1,44 +1,41 @@ -using System; +namespace Umbraco.Cms.Core.Sync; -namespace Umbraco.Cms.Core.Sync +/// +/// Describes refresh action type. +/// +[Serializable] +public enum RefreshMethodType { - /// - /// Describes refresh action type. - /// - [Serializable] - public enum RefreshMethodType - { - // NOTE - // that enum should get merged somehow with MessageType and renamed somehow - // but at the moment it is exposed in CacheRefresher webservice through RefreshInstruction - // so for the time being we keep it as-is for backward compatibility reasons + // NOTE + // that enum should get merged somehow with MessageType and renamed somehow + // but at the moment it is exposed in CacheRefresher webservice through RefreshInstruction + // so for the time being we keep it as-is for backward compatibility reasons - RefreshAll, - RefreshByGuid, - RefreshById, - RefreshByIds, - RefreshByJson, - RemoveById, + RefreshAll, + RefreshByGuid, + RefreshById, + RefreshByIds, + RefreshByJson, + RemoveById - // would adding values break backward compatibility? - //RemoveByIds + // would adding values break backward compatibility? + //RemoveByIds - // these are MessageType values - // note that AnythingByInstance are local messages and cannot be distributed - /* - RefreshAll, - RefreshById, - RefreshByJson, - RemoveById, - RefreshByInstance, - RemoveByInstance - */ + // these are MessageType values + // note that AnythingByInstance are local messages and cannot be distributed + /* + RefreshAll, + RefreshById, + RefreshByJson, + RemoveById, + RefreshByInstance, + RemoveByInstance + */ - // NOTE - // in the future we want - // RefreshAll - // RefreshById / ByInstance (support enumeration of int or guid) - // RemoveById / ByInstance (support enumeration of int or guid) - // Notify (for everything JSON) - } + // NOTE + // in the future we want + // RefreshAll + // RefreshById / ByInstance (support enumeration of int or guid) + // RemoveById / ByInstance (support enumeration of int or guid) + // Notify (for everything JSON) } diff --git a/src/Umbraco.Core/Sync/ServerRole.cs b/src/Umbraco.Core/Sync/ServerRole.cs index 9bfd4469b38c..b1c2b7c444a8 100644 --- a/src/Umbraco.Core/Sync/ServerRole.cs +++ b/src/Umbraco.Core/Sync/ServerRole.cs @@ -1,28 +1,27 @@ -namespace Umbraco.Cms.Core.Sync +namespace Umbraco.Cms.Core.Sync; + +/// +/// The role of a server in an application environment. +/// +public enum ServerRole : byte { /// - /// The role of a server in an application environment. + /// The server role is unknown. /// - public enum ServerRole : byte - { - /// - /// The server role is unknown. - /// - Unknown = 0, + Unknown = 0, - /// - /// The server is the single server of a single-server environment. - /// - Single = 1, + /// + /// The server is the single server of a single-server environment. + /// + Single = 1, - /// - /// In a multi-servers environment, the server is a Subscriber server. - /// - Subscriber = 2, + /// + /// In a multi-servers environment, the server is a Subscriber server. + /// + Subscriber = 2, - /// - /// In a multi-servers environment, the server is the Scheduling Publisher. - /// - SchedulingPublisher = 3 - } + /// + /// In a multi-servers environment, the server is the Scheduling Publisher. + /// + SchedulingPublisher = 3 } diff --git a/src/Umbraco.Core/Sync/SingleServerRoleAccessor.cs b/src/Umbraco.Core/Sync/SingleServerRoleAccessor.cs index 2f4e85c5b1ff..f03f27d9e7bc 100644 --- a/src/Umbraco.Core/Sync/SingleServerRoleAccessor.cs +++ b/src/Umbraco.Core/Sync/SingleServerRoleAccessor.cs @@ -1,15 +1,17 @@ -namespace Umbraco.Cms.Core.Sync +namespace Umbraco.Cms.Core.Sync; + +/// +/// Can be used when Umbraco is definitely not operating in a Load Balanced scenario to micro-optimize some startup +/// performance +/// +/// +/// The micro optimization is specifically to avoid a DB query just after the app starts up to determine the +/// +/// which by default is done with scheduling publisher election by a database query. The master election process +/// doesn't occur until just after startup +/// so this micro optimization doesn't really affect the primary startup phase. +/// +public class SingleServerRoleAccessor : IServerRoleAccessor { - /// - /// Can be used when Umbraco is definitely not operating in a Load Balanced scenario to micro-optimize some startup performance - /// - /// - /// The micro optimization is specifically to avoid a DB query just after the app starts up to determine the - /// which by default is done with scheduling publisher election by a database query. The master election process doesn't occur until just after startup - /// so this micro optimization doesn't really affect the primary startup phase. - /// - public class SingleServerRoleAccessor : IServerRoleAccessor - { - public ServerRole CurrentServerRole => ServerRole.Single; - } + public ServerRole CurrentServerRole => ServerRole.Single; } diff --git a/src/Umbraco.Core/Sync/SyncBootState.cs b/src/Umbraco.Core/Sync/SyncBootState.cs index 670930de3171..307a358e23dc 100644 --- a/src/Umbraco.Core/Sync/SyncBootState.cs +++ b/src/Umbraco.Core/Sync/SyncBootState.cs @@ -1,20 +1,19 @@ -namespace Umbraco.Cms.Core.Sync +namespace Umbraco.Cms.Core.Sync; + +public enum SyncBootState { - public enum SyncBootState - { - /// - /// Unknown state. Treat as WarmBoot - /// - Unknown = 0, + /// + /// Unknown state. Treat as WarmBoot + /// + Unknown = 0, - /// - /// Cold boot. No Sync state - /// - ColdBoot = 1, + /// + /// Cold boot. No Sync state + /// + ColdBoot = 1, - /// - /// Warm boot. Sync state present - /// - WarmBoot = 2 - } + /// + /// Warm boot. Sync state present + /// + WarmBoot = 2 } diff --git a/src/Umbraco.Core/SystemLock.cs b/src/Umbraco.Core/SystemLock.cs index d39d6ecbcef9..587c6565cb82 100644 --- a/src/Umbraco.Core/SystemLock.cs +++ b/src/Umbraco.Core/SystemLock.cs @@ -1,195 +1,189 @@ -using System; -using System.Runtime.ConstrainedExecution; -using System.Threading; -using System.Threading.Tasks; - -namespace Umbraco.Cms.Core +using System.Runtime.ConstrainedExecution; + +namespace Umbraco.Cms.Core; + +// https://devblogs.microsoft.com/pfxteam/building-async-coordination-primitives-part-6-asynclock/ +// +// notes: +// - this is NOT a reader/writer lock +// - this is NOT a recursive lock +// +// using a named Semaphore here and not a Mutex because mutexes have thread +// affinity which does not work with async situations +// +// it is important that managed code properly release the Semaphore before +// going down else it will maintain the lock - however note that when the +// whole process (w3wp.exe) goes down and all handles to the Semaphore have +// been closed, the Semaphore system object is destroyed - so in any case +// an iisreset should clean up everything +// +public class SystemLock { - // https://devblogs.microsoft.com/pfxteam/building-async-coordination-primitives-part-6-asynclock/ - // - // notes: - // - this is NOT a reader/writer lock - // - this is NOT a recursive lock - // - // using a named Semaphore here and not a Mutex because mutexes have thread - // affinity which does not work with async situations - // - // it is important that managed code properly release the Semaphore before - // going down else it will maintain the lock - however note that when the - // whole process (w3wp.exe) goes down and all handles to the Semaphore have - // been closed, the Semaphore system object is destroyed - so in any case - // an iisreset should clean up everything - // - public class SystemLock + private readonly IDisposable? _releaser; + private readonly Task? _releaserTask; + private readonly SemaphoreSlim? _semaphore; + private readonly Semaphore? _semaphore2; + + public SystemLock() + : this(null) { - private readonly SemaphoreSlim? _semaphore; - private readonly Semaphore? _semaphore2; - private readonly IDisposable? _releaser; - private readonly Task? _releaserTask; + } - public SystemLock() - : this(null) - { } + public SystemLock(string? name) + { + // WaitOne() waits until count > 0 then decrements count + // Release() increments count + // initial count: the initial count value + // maximum count: the max value of count, and then Release() throws - public SystemLock(string? name) + if (string.IsNullOrWhiteSpace(name)) { - // WaitOne() waits until count > 0 then decrements count - // Release() increments count - // initial count: the initial count value - // maximum count: the max value of count, and then Release() throws - - if (string.IsNullOrWhiteSpace(name)) - { - // anonymous semaphore - // use one unique releaser, that will not release the semaphore when finalized - // because the semaphore is destroyed anyway if the app goes down - - _semaphore = new SemaphoreSlim(1, 1); // create a local (to the app domain) semaphore - _releaser = new SemaphoreSlimReleaser(_semaphore); - _releaserTask = Task.FromResult(_releaser); - } - else - { - // named semaphore - // use dedicated releasers, that will release the semaphore when finalized - // because the semaphore is system-wide and we cannot leak counts + // anonymous semaphore + // use one unique releaser, that will not release the semaphore when finalized + // because the semaphore is destroyed anyway if the app goes down - _semaphore2 = new Semaphore(1, 1, name); // create a system-wide named semaphore - } + _semaphore = new SemaphoreSlim(1, 1); // create a local (to the app domain) semaphore + _releaser = new SemaphoreSlimReleaser(_semaphore); + _releaserTask = Task.FromResult(_releaser); } - - private IDisposable? CreateReleaser() + else { - // for anonymous semaphore, use the unique releaser, else create a new one - return _semaphore != null - ? _releaser // (IDisposable)new SemaphoreSlimReleaser(_semaphore) - : new NamedSemaphoreReleaser(_semaphore2); + // named semaphore + // use dedicated releasers, that will release the semaphore when finalized + // because the semaphore is system-wide and we cannot leak counts + + _semaphore2 = new Semaphore(1, 1, name); // create a system-wide named semaphore } + } + + private IDisposable? CreateReleaser() => + // for anonymous semaphore, use the unique releaser, else create a new one + _semaphore != null + ? _releaser // (IDisposable)new SemaphoreSlimReleaser(_semaphore) + : new NamedSemaphoreReleaser(_semaphore2); - public IDisposable? Lock() + public IDisposable? Lock() + { + if (_semaphore != null) { - if (_semaphore != null) - _semaphore.Wait(); - else - _semaphore2?.WaitOne(); - return _releaser ?? CreateReleaser(); // anonymous vs named + _semaphore.Wait(); } - - public IDisposable? Lock(int millisecondsTimeout) + else { - var entered = _semaphore != null - ? _semaphore.Wait(millisecondsTimeout) - : _semaphore2?.WaitOne(millisecondsTimeout); - if (entered == false) - throw new TimeoutException("Failed to enter the lock within timeout."); - return _releaser ?? CreateReleaser(); // anonymous vs named + _semaphore2?.WaitOne(); } - // note - before making those classes some structs, read - // about "impure methods" and mutating readonly structs... + return _releaser ?? CreateReleaser(); // anonymous vs named + } - private class NamedSemaphoreReleaser : CriticalFinalizerObject, IDisposable + public IDisposable? Lock(int millisecondsTimeout) + { + var entered = _semaphore != null + ? _semaphore.Wait(millisecondsTimeout) + : _semaphore2?.WaitOne(millisecondsTimeout); + if (entered == false) { - private readonly Semaphore? _semaphore; + throw new TimeoutException("Failed to enter the lock within timeout."); + } - internal NamedSemaphoreReleaser(Semaphore? semaphore) - { - _semaphore = semaphore; - } + return _releaser ?? CreateReleaser(); // anonymous vs named + } - #region IDisposable Support + // note - before making those classes some structs, read + // about "impure methods" and mutating readonly structs... - // This code added to correctly implement the disposable pattern. + private class NamedSemaphoreReleaser : CriticalFinalizerObject, IDisposable + { + private readonly Semaphore? _semaphore; - private bool disposedValue = false; // To detect redundant calls + internal NamedSemaphoreReleaser(Semaphore? semaphore) => _semaphore = semaphore; - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); // finalize will not run - } + #region IDisposable Support - private void Dispose(bool disposing) - { - if (!disposedValue) - { - try - { - _semaphore?.Release(); - } - finally - { - try - { - _semaphore?.Dispose(); - } - catch { } - } - disposedValue = true; - } - } + // This code added to correctly implement the disposable pattern. - // we WANT to release the semaphore because it's a system object, ie a critical - // non-managed resource - and if it is not released then noone else can acquire - // the lock - so we inherit from CriticalFinalizerObject which means that the - // finalizer "should" run in all situations - there is always a chance that it - // does not run and the semaphore remains "acquired" but then chances are the - // whole process (w3wp.exe...) is going down, at which point the semaphore will - // be destroyed by Windows. + private bool disposedValue; // To detect redundant calls - // however, the semaphore is a managed object, and so when the finalizer runs it - // might have been finalized already, and then we get a, ObjectDisposedException - // in the finalizer - which is bad. - - // in order to prevent this we do two things - // - use a GCHandler to ensure the semaphore is still there when the finalizer - // runs, so we can actually release it - // - wrap the finalizer code in a try...catch to make sure it never throws + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); // finalize will not run + } - ~NamedSemaphoreReleaser() + private void Dispose(bool disposing) + { + if (!disposedValue) { try { - Dispose(false); + _semaphore?.Release(); } - catch + finally { - // we do NOT want the finalizer to throw - never ever + try + { + _semaphore?.Dispose(); + } + catch { } } + + disposedValue = true; } + } - #endregion + // we WANT to release the semaphore because it's a system object, ie a critical + // non-managed resource - and if it is not released then noone else can acquire + // the lock - so we inherit from CriticalFinalizerObject which means that the + // finalizer "should" run in all situations - there is always a chance that it + // does not run and the semaphore remains "acquired" but then chances are the + // whole process (w3wp.exe...) is going down, at which point the semaphore will + // be destroyed by Windows. - } + // however, the semaphore is a managed object, and so when the finalizer runs it + // might have been finalized already, and then we get a, ObjectDisposedException + // in the finalizer - which is bad. - private class SemaphoreSlimReleaser : IDisposable - { - private readonly SemaphoreSlim _semaphore; + // in order to prevent this we do two things + // - use a GCHandler to ensure the semaphore is still there when the finalizer + // runs, so we can actually release it + // - wrap the finalizer code in a try...catch to make sure it never throws - internal SemaphoreSlimReleaser(SemaphoreSlim semaphore) + ~NamedSemaphoreReleaser() + { + try { - _semaphore = semaphore; + Dispose(false); } - - public void Dispose() + catch { - Dispose(true); - GC.SuppressFinalize(this); + // we do NOT want the finalizer to throw - never ever } + } - private void Dispose(bool disposing) - { - if (disposing) - { - // normal - _semaphore.Release(); - } - } + #endregion + } + + private class SemaphoreSlimReleaser : IDisposable + { + private readonly SemaphoreSlim _semaphore; - ~SemaphoreSlimReleaser() + internal SemaphoreSlimReleaser(SemaphoreSlim semaphore) => _semaphore = semaphore; + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposing) { - Dispose(false); + // normal + _semaphore.Release(); } } + + ~SemaphoreSlimReleaser() => Dispose(false); } } diff --git a/src/Umbraco.Core/Telemetry/ISiteIdentifierService.cs b/src/Umbraco.Core/Telemetry/ISiteIdentifierService.cs index 7fd0ee5a85ff..bd41914010f4 100644 --- a/src/Umbraco.Core/Telemetry/ISiteIdentifierService.cs +++ b/src/Umbraco.Core/Telemetry/ISiteIdentifierService.cs @@ -1,31 +1,27 @@ -using System; +namespace Umbraco.Cms.Core.Telemetry; -namespace Umbraco.Cms.Core.Telemetry +/// +/// Used to get and create the site identifier +/// +public interface ISiteIdentifierService { /// - /// Used to get and create the site identifier + /// Tries to get the site identifier /// - public interface ISiteIdentifierService - { + /// True if success. + bool TryGetSiteIdentifier(out Guid siteIdentifier); - /// - /// Tries to get the site identifier - /// - /// True if success. - bool TryGetSiteIdentifier(out Guid siteIdentifier); - - /// - /// Creates the site identifier and writes it to config. - /// - /// asd. - /// True if success. - bool TryCreateSiteIdentifier(out Guid createdGuid); + /// + /// Creates the site identifier and writes it to config. + /// + /// asd. + /// True if success. + bool TryCreateSiteIdentifier(out Guid createdGuid); - /// - /// Tries to get the site identifier or otherwise create it if it doesn't exist. - /// - /// The out parameter for the existing or create site identifier. - /// True if success. - bool TryGetOrCreateSiteIdentifier(out Guid siteIdentifier); - } + /// + /// Tries to get the site identifier or otherwise create it if it doesn't exist. + /// + /// The out parameter for the existing or create site identifier. + /// True if success. + bool TryGetOrCreateSiteIdentifier(out Guid siteIdentifier); } diff --git a/src/Umbraco.Core/Telemetry/ITelemetryService.cs b/src/Umbraco.Core/Telemetry/ITelemetryService.cs index bb832bfd7e57..23b0d154a48e 100644 --- a/src/Umbraco.Core/Telemetry/ITelemetryService.cs +++ b/src/Umbraco.Core/Telemetry/ITelemetryService.cs @@ -1,15 +1,14 @@ using Umbraco.Cms.Core.Telemetry.Models; -namespace Umbraco.Cms.Core.Telemetry +namespace Umbraco.Cms.Core.Telemetry; + +/// +/// Service which gathers the data for telemetry reporting +/// +public interface ITelemetryService { /// - /// Service which gathers the data for telemetry reporting + /// Try and get the /// - public interface ITelemetryService - { - /// - /// Try and get the - /// - bool TryGetTelemetryReportData(out TelemetryReportData? telemetryReportData); - } + bool TryGetTelemetryReportData(out TelemetryReportData? telemetryReportData); } diff --git a/src/Umbraco.Core/Telemetry/Models/PackageTelemetry.cs b/src/Umbraco.Core/Telemetry/Models/PackageTelemetry.cs index 53dc6d1a6e12..53c07766e833 100644 --- a/src/Umbraco.Core/Telemetry/Models/PackageTelemetry.cs +++ b/src/Umbraco.Core/Telemetry/Models/PackageTelemetry.cs @@ -1,26 +1,25 @@ using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Telemetry.Models +namespace Umbraco.Cms.Core.Telemetry.Models; + +/// +/// Serializable class containing information about an installed package. +/// +[DataContract(Name = "packageTelemetry")] +public class PackageTelemetry { /// - /// Serializable class containing information about an installed package. + /// Gets or sets the name of the installed package. /// - [DataContract(Name = "packageTelemetry")] - public class PackageTelemetry - { - /// - /// Gets or sets the name of the installed package. - /// - [DataMember(Name = "name")] - public string? Name { get; set; } + [DataMember(Name = "name")] + public string? Name { get; set; } - /// - /// Gets or sets the version of the installed package. - /// - /// - /// This may be an empty string if no version is specified, or if package telemetry has been restricted. - /// - [DataMember(Name = "version")] - public string? Version { get; set; } - } + /// + /// Gets or sets the version of the installed package. + /// + /// + /// This may be an empty string if no version is specified, or if package telemetry has been restricted. + /// + [DataMember(Name = "version")] + public string? Version { get; set; } } diff --git a/src/Umbraco.Core/Telemetry/Models/TelemetryReportData.cs b/src/Umbraco.Core/Telemetry/Models/TelemetryReportData.cs index ea6ff63f9199..fb6a4a990213 100644 --- a/src/Umbraco.Core/Telemetry/Models/TelemetryReportData.cs +++ b/src/Umbraco.Core/Telemetry/Models/TelemetryReportData.cs @@ -1,38 +1,34 @@ -using System; -using System.Collections.Generic; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core.Telemetry.Models +namespace Umbraco.Cms.Core.Telemetry.Models; + +/// +/// Serializable class containing telemetry information. +/// +[DataContract] +public class TelemetryReportData { /// - /// Serializable class containing telemetry information. + /// Gets or sets a random GUID to prevent an instance posting multiple times pr. day. /// - [DataContract] - public class TelemetryReportData - { - /// - /// Gets or sets a random GUID to prevent an instance posting multiple times pr. day. - /// - [DataMember(Name = "id")] - public Guid Id { get; set; } + [DataMember(Name = "id")] + public Guid Id { get; set; } - /// - /// Gets or sets the Umbraco CMS version. - /// - [DataMember(Name = "version")] - public string? Version { get; set; } + /// + /// Gets or sets the Umbraco CMS version. + /// + [DataMember(Name = "version")] + public string? Version { get; set; } - /// - /// Gets or sets an enumerable containing information about packages. - /// - /// - /// Contains only the name and version of the packages, unless no version is specified. - /// - [DataMember(Name = "packages")] - public IEnumerable? Packages { get; set; } + /// + /// Gets or sets an enumerable containing information about packages. + /// + /// + /// Contains only the name and version of the packages, unless no version is specified. + /// + [DataMember(Name = "packages")] + public IEnumerable? Packages { get; set; } - [DataMember(Name = "detailed")] - public IEnumerable? Detailed { get; set; } - } + [DataMember(Name = "detailed")] public IEnumerable? Detailed { get; set; } } diff --git a/src/Umbraco.Core/Telemetry/SiteIdentifierService.cs b/src/Umbraco.Core/Telemetry/SiteIdentifierService.cs index b6e40665c16e..a7b5882ecc73 100644 --- a/src/Umbraco.Core/Telemetry/SiteIdentifierService.cs +++ b/src/Umbraco.Core/Telemetry/SiteIdentifierService.cs @@ -1,81 +1,79 @@ -using System; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; -namespace Umbraco.Cms.Core.Telemetry +namespace Umbraco.Cms.Core.Telemetry; + +/// +internal class SiteIdentifierService : ISiteIdentifierService { - /// - internal class SiteIdentifierService : ISiteIdentifierService + private readonly IConfigManipulator _configManipulator; + private readonly ILogger _logger; + private GlobalSettings _globalSettings; + + public SiteIdentifierService( + IOptionsMonitor optionsMonitor, + IConfigManipulator configManipulator, + ILogger logger) { - private GlobalSettings _globalSettings; - private readonly IConfigManipulator _configManipulator; - private readonly ILogger _logger; + _globalSettings = optionsMonitor.CurrentValue; + optionsMonitor.OnChange(globalSettings => _globalSettings = globalSettings); + _configManipulator = configManipulator; + _logger = logger; + } - public SiteIdentifierService( - IOptionsMonitor optionsMonitor, - IConfigManipulator configManipulator, - ILogger logger) + /// + public bool TryGetSiteIdentifier(out Guid siteIdentifier) + { + // Parse telemetry string as a GUID & verify its a GUID and not some random string + // since users may have messed with or decided to empty the app setting or put in something random + if (Guid.TryParse(_globalSettings.Id, out Guid parsedTelemetryId) is false + || parsedTelemetryId == Guid.Empty) { - _globalSettings = optionsMonitor.CurrentValue; - optionsMonitor.OnChange(globalSettings => _globalSettings = globalSettings); - _configManipulator = configManipulator; - _logger = logger; + siteIdentifier = Guid.Empty; + return false; } - /// - public bool TryGetSiteIdentifier(out Guid siteIdentifier) - { - // Parse telemetry string as a GUID & verify its a GUID and not some random string - // since users may have messed with or decided to empty the app setting or put in something random - if (Guid.TryParse(_globalSettings.Id, out var parsedTelemetryId) is false - || parsedTelemetryId == Guid.Empty) - { - siteIdentifier = Guid.Empty; - return false; - } + siteIdentifier = parsedTelemetryId; + return true; + } - siteIdentifier = parsedTelemetryId; + /// + public bool TryGetOrCreateSiteIdentifier(out Guid siteIdentifier) + { + if (TryGetSiteIdentifier(out Guid existingId)) + { + siteIdentifier = existingId; return true; } - /// - public bool TryGetOrCreateSiteIdentifier(out Guid siteIdentifier) + if (TryCreateSiteIdentifier(out Guid createdId)) { - if (TryGetSiteIdentifier(out Guid existingId)) - { - siteIdentifier = existingId; - return true; - } - - if (TryCreateSiteIdentifier(out Guid createdId)) - { - siteIdentifier = createdId; - return true; - } - - siteIdentifier = Guid.Empty; - return false; + siteIdentifier = createdId; + return true; } - /// - public bool TryCreateSiteIdentifier(out Guid createdGuid) - { - createdGuid = Guid.NewGuid(); + siteIdentifier = Guid.Empty; + return false; + } - try - { - _configManipulator.SetGlobalId(createdGuid.ToString()); - } - catch (Exception ex) - { - _logger.LogError(ex, "Couldn't update config files with a telemetry site identifier"); - createdGuid = Guid.Empty; - return false; - } + /// + public bool TryCreateSiteIdentifier(out Guid createdGuid) + { + createdGuid = Guid.NewGuid(); - return true; + try + { + _configManipulator.SetGlobalId(createdGuid.ToString()); } + catch (Exception ex) + { + _logger.LogError(ex, "Couldn't update config files with a telemetry site identifier"); + createdGuid = Guid.Empty; + return false; + } + + return true; } } diff --git a/src/Umbraco.Core/Telemetry/TelemetryService.cs b/src/Umbraco.Core/Telemetry/TelemetryService.cs index bcc6076d24fc..f3255c63bdd1 100644 --- a/src/Umbraco.Core/Telemetry/TelemetryService.cs +++ b/src/Umbraco.Core/Telemetry/TelemetryService.cs @@ -1,8 +1,6 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Manifest; using Umbraco.Cms.Core.Models; @@ -10,88 +8,86 @@ using Umbraco.Cms.Core.Telemetry.Models; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Telemetry +namespace Umbraco.Cms.Core.Telemetry; + +/// +internal class TelemetryService : ITelemetryService { - /// - internal class TelemetryService : ITelemetryService + private readonly IManifestParser _manifestParser; + private readonly IMetricsConsentService _metricsConsentService; + private readonly ISiteIdentifierService _siteIdentifierService; + private readonly IUmbracoVersion _umbracoVersion; + private readonly IUsageInformationService _usageInformationService; + + /// + /// Initializes a new instance of the class. + /// + public TelemetryService( + IManifestParser manifestParser, + IUmbracoVersion umbracoVersion, + ISiteIdentifierService siteIdentifierService, + IUsageInformationService usageInformationService, + IMetricsConsentService metricsConsentService) { - private readonly IManifestParser _manifestParser; - private readonly IUmbracoVersion _umbracoVersion; - private readonly ISiteIdentifierService _siteIdentifierService; - private readonly IUsageInformationService _usageInformationService; - private readonly IMetricsConsentService _metricsConsentService; + _manifestParser = manifestParser; + _umbracoVersion = umbracoVersion; + _siteIdentifierService = siteIdentifierService; + _usageInformationService = usageInformationService; + _metricsConsentService = metricsConsentService; + } - /// - /// Initializes a new instance of the class. - /// - public TelemetryService( - IManifestParser manifestParser, - IUmbracoVersion umbracoVersion, - ISiteIdentifierService siteIdentifierService, - IUsageInformationService usageInformationService, - IMetricsConsentService metricsConsentService) + /// + public bool TryGetTelemetryReportData(out TelemetryReportData? telemetryReportData) + { + if (_siteIdentifierService.TryGetOrCreateSiteIdentifier(out Guid telemetryId) is false) { - _manifestParser = manifestParser; - _umbracoVersion = umbracoVersion; - _siteIdentifierService = siteIdentifierService; - _usageInformationService = usageInformationService; - _metricsConsentService = metricsConsentService; + telemetryReportData = null; + return false; } - /// - public bool TryGetTelemetryReportData(out TelemetryReportData? telemetryReportData) + telemetryReportData = new TelemetryReportData { - if (_siteIdentifierService.TryGetOrCreateSiteIdentifier(out Guid telemetryId) is false) - { - telemetryReportData = null; - return false; - } + Id = telemetryId, + Version = GetVersion(), + Packages = GetPackageTelemetry(), + Detailed = _usageInformationService.GetDetailed() + }; + return true; + } - telemetryReportData = new TelemetryReportData - { - Id = telemetryId, - Version = GetVersion(), - Packages = GetPackageTelemetry(), - Detailed = _usageInformationService.GetDetailed(), - }; - return true; + private string? GetVersion() + { + if (_metricsConsentService.GetConsentLevel() == TelemetryLevel.Minimal) + { + return null; } - private string? GetVersion() - { - if (_metricsConsentService.GetConsentLevel() == TelemetryLevel.Minimal) - { - return null; - } + return _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild(); + } - return _umbracoVersion.SemanticVersion.ToSemanticStringWithoutBuild(); + private IEnumerable? GetPackageTelemetry() + { + if (_metricsConsentService.GetConsentLevel() == TelemetryLevel.Minimal) + { + return null; } - private IEnumerable? GetPackageTelemetry() + List packages = new(); + IEnumerable manifests = _manifestParser.GetManifests(); + + foreach (PackageManifest manifest in manifests) { - if (_metricsConsentService.GetConsentLevel() == TelemetryLevel.Minimal) + if (manifest.AllowPackageTelemetry is false) { - return null; + continue; } - List packages = new(); - IEnumerable manifests = _manifestParser.GetManifests(); - - foreach (PackageManifest manifest in manifests) + packages.Add(new PackageTelemetry { - if (manifest.AllowPackageTelemetry is false) - { - continue; - } - - packages.Add(new PackageTelemetry - { - Name = manifest.PackageName, - Version = manifest.Version ?? string.Empty, - }); - } - - return packages; + Name = manifest.PackageName, Version = manifest.Version ?? string.Empty + }); } + + return packages; } } diff --git a/src/Umbraco.Core/Templates/HtmlImageSourceParser.cs b/src/Umbraco.Core/Templates/HtmlImageSourceParser.cs index 46ac9fb6e7ac..62c3ceeca913 100644 --- a/src/Umbraco.Core/Templates/HtmlImageSourceParser.cs +++ b/src/Umbraco.Core/Templates/HtmlImageSourceParser.cs @@ -1,95 +1,95 @@ -using System; -using System.Collections.Generic; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using Umbraco.Cms.Core.Routing; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Templates -{ +namespace Umbraco.Cms.Core.Templates; - public sealed class HtmlImageSourceParser - { - public HtmlImageSourceParser(Func getMediaUrl) - { - this._getMediaUrl = getMediaUrl; - } +public sealed class HtmlImageSourceParser +{ + private static readonly Regex ResolveImgPattern = new( + @"(]*src="")([^""\?]*)((?:\?[^""]*)?""[^>]*data-udi="")([^""]*)(""[^>]*>)", + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - private readonly IPublishedUrlProvider? _publishedUrlProvider; + private static readonly Regex DataUdiAttributeRegex = new( + @"data-udi=\\?(?:""|')(?umb://[A-z0-9\-]+/[A-z0-9]+)\\?(?:""|')", + RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); - public HtmlImageSourceParser(IPublishedUrlProvider publishedUrlProvider) - { - _publishedUrlProvider = publishedUrlProvider; - } + private readonly IPublishedUrlProvider? _publishedUrlProvider; - private static readonly Regex ResolveImgPattern = new Regex(@"(]*src="")([^""\?]*)((?:\?[^""]*)?""[^>]*data-udi="")([^""]*)(""[^>]*>)", - RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + private Func? _getMediaUrl; - private static readonly Regex DataUdiAttributeRegex = new Regex(@"data-udi=\\?(?:""|')(?umb://[A-z0-9\-]+/[A-z0-9]+)\\?(?:""|')", - RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); + public HtmlImageSourceParser(Func getMediaUrl) => _getMediaUrl = getMediaUrl; - private Func? _getMediaUrl; + public HtmlImageSourceParser(IPublishedUrlProvider publishedUrlProvider) => + _publishedUrlProvider = publishedUrlProvider; - /// - /// Parses out media UDIs from an html string based on 'data-udi' html attributes - /// - /// - /// - public IEnumerable FindUdisFromDataAttributes(string text) + /// + /// Parses out media UDIs from an html string based on 'data-udi' html attributes + /// + /// + /// + public IEnumerable FindUdisFromDataAttributes(string text) + { + MatchCollection matches = DataUdiAttributeRegex.Matches(text); + if (matches.Count == 0) { - var matches = DataUdiAttributeRegex.Matches(text); - if (matches.Count == 0) - yield break; + yield break; + } - foreach (Match match in matches) + foreach (Match match in matches) + { + if (match.Groups.Count == 2 && UdiParser.TryParse(match.Groups[1].Value, out Udi udi)) { - if (match.Groups.Count == 2 && UdiParser.TryParse(match.Groups[1].Value, out var udi)) - yield return udi; + yield return udi; } } + } - /// - /// Parses the string looking for Umbraco image tags and updates them to their up-to-date image sources. - /// - /// - /// - /// Umbraco image tags are identified by their data-udi attributes - public string EnsureImageSources(string text) + /// + /// Parses the string looking for Umbraco image tags and updates them to their up-to-date image sources. + /// + /// + /// + /// Umbraco image tags are identified by their data-udi attributes + public string EnsureImageSources(string text) + { + if (_getMediaUrl == null) { - if(_getMediaUrl == null) - _getMediaUrl = (guid) => _publishedUrlProvider?.GetMediaUrl(guid); + _getMediaUrl = guid => _publishedUrlProvider?.GetMediaUrl(guid); + } - return ResolveImgPattern.Replace(text, match => + return ResolveImgPattern.Replace(text, match => + { + // match groups: + // - 1 = from the beginning of the image tag until src attribute value begins + // - 2 = the src attribute value excluding the querystring (if present) + // - 3 = anything after group 2 and before the data-udi attribute value begins + // - 4 = the data-udi attribute value + // - 5 = anything after group 4 until the image tag is closed + var udi = match.Groups[4].Value; + if (udi.IsNullOrWhiteSpace() || UdiParser.TryParse(udi, out GuidUdi guidUdi) == false) { - // match groups: - // - 1 = from the beginning of the image tag until src attribute value begins - // - 2 = the src attribute value excluding the querystring (if present) - // - 3 = anything after group 2 and before the data-udi attribute value begins - // - 4 = the data-udi attribute value - // - 5 = anything after group 4 until the image tag is closed - var udi = match.Groups[4].Value; - if (udi.IsNullOrWhiteSpace() ||UdiParser.TryParse(udi, out var guidUdi) == false) - { - return match.Value; - } - var mediaUrl = _getMediaUrl(guidUdi.Guid); - if (mediaUrl == null) - { - // image does not exist - we could choose to remove the image entirely here (return empty string), - // but that would leave the editors completely in the dark as to why the image doesn't show - return match.Value; - } + return match.Value; + } - return $"{match.Groups[1].Value}{mediaUrl}{match.Groups[3].Value}{udi}{match.Groups[5].Value}"; - }); - } + var mediaUrl = _getMediaUrl(guidUdi.Guid); + if (mediaUrl == null) + { + // image does not exist - we could choose to remove the image entirely here (return empty string), + // but that would leave the editors completely in the dark as to why the image doesn't show + return match.Value; + } - /// - /// Removes media URLs from <img> tags where a data-udi attribute is present - /// - /// - /// - public string RemoveImageSources(string text) - // see comment in ResolveMediaFromTextString for group reference - => ResolveImgPattern.Replace(text, "$1$3$4$5"); + return $"{match.Groups[1].Value}{mediaUrl}{match.Groups[3].Value}{udi}{match.Groups[5].Value}"; + }); } + + /// + /// Removes media URLs from <img> tags where a data-udi attribute is present + /// + /// + /// + public string RemoveImageSources(string text) + // see comment in ResolveMediaFromTextString for group reference + => ResolveImgPattern.Replace(text, "$1$3$4$5"); } diff --git a/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs b/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs index 4317f05cc97b..c0ccd6686488 100644 --- a/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs +++ b/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs @@ -1,127 +1,134 @@ -using System; -using System.Collections.Generic; using System.Globalization; using System.Text.RegularExpressions; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Web; -using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Templates +namespace Umbraco.Cms.Core.Templates; + +/// +/// Utility class used to parse internal links +/// +public sealed class HtmlLocalLinkParser { - /// - /// Utility class used to parse internal links - /// - public sealed class HtmlLocalLinkParser - { + internal static readonly Regex LocalLinkPattern = new( + @"href=""[/]?(?:\{|\%7B)localLink:([a-zA-Z0-9-://]+)(?:\}|\%7D)", + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - internal static readonly Regex LocalLinkPattern = new Regex(@"href=""[/]?(?:\{|\%7B)localLink:([a-zA-Z0-9-://]+)(?:\}|\%7D)", - RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + private readonly IPublishedUrlProvider _publishedUrlProvider; - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly IPublishedUrlProvider _publishedUrlProvider; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; - public HtmlLocalLinkParser(IUmbracoContextAccessor umbracoContextAccessor, IPublishedUrlProvider publishedUrlProvider) - { - _umbracoContextAccessor = umbracoContextAccessor; - _publishedUrlProvider = publishedUrlProvider; - } + public HtmlLocalLinkParser(IUmbracoContextAccessor umbracoContextAccessor, + IPublishedUrlProvider publishedUrlProvider) + { + _umbracoContextAccessor = umbracoContextAccessor; + _publishedUrlProvider = publishedUrlProvider; + } - public IEnumerable FindUdisFromLocalLinks(string text) + public IEnumerable FindUdisFromLocalLinks(string text) + { + foreach ((var intId, GuidUdi? udi, var tagValue) in FindLocalLinkIds(text)) { - foreach ((int? intId, GuidUdi? udi, string tagValue) in FindLocalLinkIds(text)) + if (udi is not null) { - if (udi is not null) - yield return udi; // In v8, we only care abuot UDIs + yield return udi; // In v8, we only care abuot UDIs } } + } - /// - /// Parses the string looking for the {localLink} syntax and updates them to their correct links. - /// - /// - /// - /// - public string EnsureInternalLinks(string text, bool preview) + /// + /// Parses the string looking for the {localLink} syntax and updates them to their correct links. + /// + /// + /// + /// + public string EnsureInternalLinks(string text, bool preview) + { + if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext umbracoContext)) { - if (!_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) - { - throw new InvalidOperationException("Could not parse internal links, there is no current UmbracoContext"); - } + throw new InvalidOperationException("Could not parse internal links, there is no current UmbracoContext"); + } - if (!preview) - { - return EnsureInternalLinks(text); - } + if (!preview) + { + return EnsureInternalLinks(text); + } - using (umbracoContext!.ForcedPreview(preview)) // force for URL provider - { - return EnsureInternalLinks(text); - } + using (umbracoContext!.ForcedPreview(preview)) // force for URL provider + { + return EnsureInternalLinks(text); } + } - /// - /// Parses the string looking for the {localLink} syntax and updates them to their correct links. - /// - /// - /// - /// - public string EnsureInternalLinks(string text) + /// + /// Parses the string looking for the {localLink} syntax and updates them to their correct links. + /// + /// + /// + /// + public string EnsureInternalLinks(string text) + { + if (!_umbracoContextAccessor.TryGetUmbracoContext(out _)) { - if (!_umbracoContextAccessor.TryGetUmbracoContext(out _)) - { - throw new InvalidOperationException("Could not parse internal links, there is no current UmbracoContext"); - } + throw new InvalidOperationException("Could not parse internal links, there is no current UmbracoContext"); + } - foreach((int? intId, GuidUdi? udi, string tagValue) in FindLocalLinkIds(text)) + foreach ((var intId, GuidUdi? udi, var tagValue) in FindLocalLinkIds(text)) + { + if (udi is not null) { - if (udi is not null) + var newLink = "#"; + if (udi?.EntityType == Constants.UdiEntityType.Document) { - var newLink = "#"; - if (udi?.EntityType == Constants.UdiEntityType.Document) - newLink = _publishedUrlProvider.GetUrl(udi.Guid); - else if (udi?.EntityType == Constants.UdiEntityType.Media) - newLink = _publishedUrlProvider.GetMediaUrl(udi.Guid); - - if (newLink == null) - newLink = "#"; - - text = text.Replace(tagValue, "href=\"" + newLink); + newLink = _publishedUrlProvider.GetUrl(udi.Guid); } - else if (intId.HasValue) + else if (udi?.EntityType == Constants.UdiEntityType.Media) { - var newLink = _publishedUrlProvider.GetUrl(intId.Value); - text = text.Replace(tagValue, "href=\"" + newLink); + newLink = _publishedUrlProvider.GetMediaUrl(udi.Guid); + } + + if (newLink == null) + { + newLink = "#"; } - } - return text; + text = text.Replace(tagValue, "href=\"" + newLink); + } + else if (intId.HasValue) + { + var newLink = _publishedUrlProvider.GetUrl(intId.Value); + text = text.Replace(tagValue, "href=\"" + newLink); + } } - private IEnumerable<(int? intId, GuidUdi? udi, string tagValue)> FindLocalLinkIds(string text) + return text; + } + + private IEnumerable<(int? intId, GuidUdi? udi, string tagValue)> FindLocalLinkIds(string text) + { + // Parse internal links + MatchCollection tags = LocalLinkPattern.Matches(text); + foreach (Match tag in tags) { - // Parse internal links - var tags = LocalLinkPattern.Matches(text); - foreach (Match tag in tags) + if (tag.Groups.Count > 0) { - if (tag.Groups.Count > 0) - { - var id = tag.Groups[1].Value; //.Remove(tag.Groups[1].Value.Length - 1, 1); + var id = tag.Groups[1].Value; //.Remove(tag.Groups[1].Value.Length - 1, 1); - //The id could be an int or a UDI - if (UdiParser.TryParse(id, out var udi)) + //The id could be an int or a UDI + if (UdiParser.TryParse(id, out Udi udi)) + { + var guidUdi = udi as GuidUdi; + if (guidUdi is not null) { - var guidUdi = udi as GuidUdi; - if (guidUdi is not null) - yield return (null, guidUdi, tag.Value); + yield return (null, guidUdi, tag.Value); } + } - if (int.TryParse(id, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intId)) - { - yield return (intId, null, tag.Value); - } + if (int.TryParse(id, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intId)) + { + yield return (intId, null, tag.Value); } } - } } } diff --git a/src/Umbraco.Core/Templates/HtmlUrlParser.cs b/src/Umbraco.Core/Templates/HtmlUrlParser.cs index 39c82f00ab43..ddc03d9a6e3b 100644 --- a/src/Umbraco.Core/Templates/HtmlUrlParser.cs +++ b/src/Umbraco.Core/Templates/HtmlUrlParser.cs @@ -5,66 +5,77 @@ using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Logging; -namespace Umbraco.Cms.Core.Templates +namespace Umbraco.Cms.Core.Templates; + +public sealed class HtmlUrlParser { - public sealed class HtmlUrlParser + private static readonly Regex ResolveUrlPattern = new( + "(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + + private readonly IIOHelper _ioHelper; + private readonly ILogger _logger; + private readonly IProfilingLogger _profilingLogger; + private ContentSettings _contentSettings; + + public HtmlUrlParser(IOptionsMonitor contentSettings, ILogger logger, + IProfilingLogger profilingLogger, IIOHelper ioHelper) { - private ContentSettings _contentSettings; - private readonly ILogger _logger; - private readonly IIOHelper _ioHelper; - private readonly IProfilingLogger _profilingLogger; + _contentSettings = contentSettings.CurrentValue; + _logger = logger; + _ioHelper = ioHelper; + _profilingLogger = profilingLogger; - private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", - RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + contentSettings.OnChange(x => _contentSettings = x); + } - public HtmlUrlParser(IOptionsMonitor contentSettings, ILogger logger, IProfilingLogger profilingLogger, IIOHelper ioHelper) + /// + /// The RegEx matches any HTML attribute values that start with a tilde (~), those that match are passed to ResolveUrl + /// to replace the tilde with the application path. + /// + /// + /// + /// + /// When used with a Virtual-Directory set-up, this would resolve all URLs correctly. + /// The recommendation is that the "ResolveUrlsFromTextString" option (in umbracoSettings.config) is set to false for + /// non-Virtual-Directory installs. + /// + public string EnsureUrls(string text) + { + if (_contentSettings.ResolveUrlsFromTextString == false) { - _contentSettings = contentSettings.CurrentValue; - _logger = logger; - _ioHelper = ioHelper; - _profilingLogger = profilingLogger; - - contentSettings.OnChange(x => _contentSettings = x); + return text; } - /// - /// The RegEx matches any HTML attribute values that start with a tilde (~), those that match are passed to ResolveUrl to replace the tilde with the application path. - /// - /// - /// - /// - /// When used with a Virtual-Directory set-up, this would resolve all URLs correctly. - /// The recommendation is that the "ResolveUrlsFromTextString" option (in umbracoSettings.config) is set to false for non-Virtual-Directory installs. - /// - public string EnsureUrls(string text) + using (DisposableTimer timer = _profilingLogger.DebugDuration(typeof(IOHelper), + "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete")) { - if (_contentSettings.ResolveUrlsFromTextString == false) - return text; - - using (var timer = _profilingLogger.DebugDuration(typeof(IOHelper), "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete")) + // find all relative URLs (ie. URLs that contain ~) + MatchCollection tags = ResolveUrlPattern.Matches(text); + _logger.LogDebug("After regex: {Duration} matched: {TagsCount}", timer?.Stopwatch.ElapsedMilliseconds, + tags.Count); + foreach (Match tag in tags) { - // find all relative URLs (ie. URLs that contain ~) - var tags = ResolveUrlPattern.Matches(text); - _logger.LogDebug("After regex: {Duration} matched: {TagsCount}", timer?.Stopwatch.ElapsedMilliseconds, tags.Count); - foreach (Match tag in tags) + var url = ""; + if (tag.Groups[1].Success) { - var url = ""; - if (tag.Groups[1].Success) - url = tag.Groups[1].Value; + url = tag.Groups[1].Value; + } - // The richtext editor inserts a slash in front of the URL. That's why we need this little fix - // if (url.StartsWith("/")) - // text = text.Replace(url, ResolveUrl(url.Substring(1))); - // else - if (string.IsNullOrEmpty(url) == false) - { - var resolvedUrl = (url.Substring(0, 1) == "/") ? _ioHelper.ResolveUrl(url.Substring(1)) : _ioHelper.ResolveUrl(url); - text = text.Replace(url, resolvedUrl); - } + // The richtext editor inserts a slash in front of the URL. That's why we need this little fix + // if (url.StartsWith("/")) + // text = text.Replace(url, ResolveUrl(url.Substring(1))); + // else + if (string.IsNullOrEmpty(url) == false) + { + var resolvedUrl = url.Substring(0, 1) == "/" + ? _ioHelper.ResolveUrl(url.Substring(1)) + : _ioHelper.ResolveUrl(url); + text = text.Replace(url, resolvedUrl); } } - - return text; } + + return text; } } diff --git a/src/Umbraco.Core/Templates/ITemplateRenderer.cs b/src/Umbraco.Core/Templates/ITemplateRenderer.cs index f6e6435a8a8d..17d16168ec99 100644 --- a/src/Umbraco.Core/Templates/ITemplateRenderer.cs +++ b/src/Umbraco.Core/Templates/ITemplateRenderer.cs @@ -1,13 +1,9 @@ -using System.IO; -using System.Threading.Tasks; +namespace Umbraco.Cms.Core.Templates; -namespace Umbraco.Cms.Core.Templates +/// +/// This is used purely for the RenderTemplate functionality in Umbraco +/// +public interface ITemplateRenderer { - /// - /// This is used purely for the RenderTemplate functionality in Umbraco - /// - public interface ITemplateRenderer - { - Task RenderAsync(int pageId, int? altTemplateId, StringWriter writer); - } + Task RenderAsync(int pageId, int? altTemplateId, StringWriter writer); } diff --git a/src/Umbraco.Core/Templates/IUmbracoComponentRenderer.cs b/src/Umbraco.Core/Templates/IUmbracoComponentRenderer.cs index 1239f2287702..4d930cb72d19 100644 --- a/src/Umbraco.Core/Templates/IUmbracoComponentRenderer.cs +++ b/src/Umbraco.Core/Templates/IUmbracoComponentRenderer.cs @@ -1,57 +1,54 @@ -using System.Collections.Generic; -using System.Threading.Tasks; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Strings; -namespace Umbraco.Cms.Core.Templates +namespace Umbraco.Cms.Core.Templates; + +/// +/// Methods used to render umbraco components as HTML in templates +/// +public interface IUmbracoComponentRenderer { /// - /// Methods used to render umbraco components as HTML in templates + /// Renders the template for the specified pageId and an optional altTemplateId /// - public interface IUmbracoComponentRenderer - { - /// - /// Renders the template for the specified pageId and an optional altTemplateId - /// - /// The content id - /// If not specified, will use the template assigned to the node - Task RenderTemplateAsync(int contentId, int? altTemplateId = null); - - /// - /// Renders the macro with the specified alias. - /// - /// The content id - /// The alias. - Task RenderMacroAsync(int contentId, string alias); + /// The content id + /// If not specified, will use the template assigned to the node + Task RenderTemplateAsync(int contentId, int? altTemplateId = null); - /// - /// Renders the macro with the specified alias, passing in the specified parameters. - /// - /// The content id - /// The alias. - /// The parameters. - Task RenderMacroAsync(int contentId, string alias, object parameters); + /// + /// Renders the macro with the specified alias. + /// + /// The content id + /// The alias. + Task RenderMacroAsync(int contentId, string alias); - /// - /// Renders the macro with the specified alias, passing in the specified parameters. - /// - /// The content id - /// The alias. - /// The parameters. - Task RenderMacroAsync(int contentId, string alias, IDictionary? parameters); + /// + /// Renders the macro with the specified alias, passing in the specified parameters. + /// + /// The content id + /// The alias. + /// The parameters. + Task RenderMacroAsync(int contentId, string alias, object parameters); - /// - /// Renders the macro with the specified alias, passing in the specified parameters. - /// - /// An IPublishedContent to use for the context for the macro rendering - /// The alias. - /// The parameters. - /// A raw HTML string of the macro output - /// - /// Currently only used when the node is unpublished and unable to get the contentId item from the - /// content cache as its unpublished. This deals with taking in a preview/draft version of the content node - /// - Task RenderMacroForContent(IPublishedContent content, string alias, IDictionary? parameters); + /// + /// Renders the macro with the specified alias, passing in the specified parameters. + /// + /// The content id + /// The alias. + /// The parameters. + Task RenderMacroAsync(int contentId, string alias, IDictionary? parameters); - } + /// + /// Renders the macro with the specified alias, passing in the specified parameters. + /// + /// An IPublishedContent to use for the context for the macro rendering + /// The alias. + /// The parameters. + /// A raw HTML string of the macro output + /// + /// Currently only used when the node is unpublished and unable to get the contentId item from the + /// content cache as its unpublished. This deals with taking in a preview/draft version of the content node + /// + Task RenderMacroForContent(IPublishedContent content, string alias, + IDictionary? parameters); } diff --git a/src/Umbraco.Core/Templates/UmbracoComponentRenderer.cs b/src/Umbraco.Core/Templates/UmbracoComponentRenderer.cs index 407f85ad600e..3d7926878e00 100644 --- a/src/Umbraco.Core/Templates/UmbracoComponentRenderer.cs +++ b/src/Umbraco.Core/Templates/UmbracoComponentRenderer.cs @@ -1,112 +1,112 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Net; -using System.Threading.Tasks; using Umbraco.Cms.Core.Macros; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Core.Web; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Templates +namespace Umbraco.Cms.Core.Templates; + +/// +/// Methods used to render umbraco components as HTML in templates +/// +/// +/// Used by UmbracoHelper +/// +public class UmbracoComponentRenderer : IUmbracoComponentRenderer { + private readonly IMacroRenderer _macroRenderer; + private readonly ITemplateRenderer _templateRenderer; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; /// - /// Methods used to render umbraco components as HTML in templates + /// Initializes a new instance of the class. /// - /// - /// Used by UmbracoHelper - /// - public class UmbracoComponentRenderer : IUmbracoComponentRenderer + public UmbracoComponentRenderer(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, + ITemplateRenderer templateRenderer) { - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly IMacroRenderer _macroRenderer; - private readonly ITemplateRenderer _templateRenderer; - - /// - /// Initializes a new instance of the class. - /// - public UmbracoComponentRenderer(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, ITemplateRenderer templateRenderer) - { - _umbracoContextAccessor = umbracoContextAccessor; - _macroRenderer = macroRenderer; - _templateRenderer = templateRenderer ?? throw new ArgumentNullException(nameof(templateRenderer)); - } + _umbracoContextAccessor = umbracoContextAccessor; + _macroRenderer = macroRenderer; + _templateRenderer = templateRenderer ?? throw new ArgumentNullException(nameof(templateRenderer)); + } - /// - public async Task RenderTemplateAsync(int contentId, int? altTemplateId = null) + /// + public async Task RenderTemplateAsync(int contentId, int? altTemplateId = null) + { + using (var sw = new StringWriter()) { - using (var sw = new StringWriter()) + try + { + await _templateRenderer.RenderAsync(contentId, altTemplateId, sw); + } + catch (Exception ex) { - try - { - await _templateRenderer.RenderAsync(contentId, altTemplateId, sw); - } - catch (Exception ex) - { - sw.Write("", contentId, ex); - } - - return new HtmlEncodedString(sw.ToString()); + sw.Write("", contentId, ex); } + + return new HtmlEncodedString(sw.ToString()); } + } - /// - public async Task RenderMacroAsync(int contentId, string alias) => await RenderMacroAsync(contentId, alias, new { }); + /// + public async Task RenderMacroAsync(int contentId, string alias) => + await RenderMacroAsync(contentId, alias, new { }); - /// - public async Task RenderMacroAsync(int contentId, string alias, object parameters) => await RenderMacroAsync(contentId, alias, parameters.ToDictionary()); + /// + public async Task RenderMacroAsync(int contentId, string alias, object parameters) => + await RenderMacroAsync(contentId, alias, parameters.ToDictionary()); - /// - public async Task RenderMacroAsync(int contentId, string alias, IDictionary? parameters) + /// + public async Task RenderMacroAsync(int contentId, string alias, + IDictionary? parameters) + { + if (contentId == default) { - if (contentId == default) - { - throw new ArgumentException("Invalid content id " + contentId); - } - var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - var content = umbracoContext.Content?.GetById(contentId); + throw new ArgumentException("Invalid content id " + contentId); + } - if (content == null) - { - throw new InvalidOperationException("Cannot render a macro, no content found by id " + contentId); - } + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + IPublishedContent content = umbracoContext.Content?.GetById(contentId); - return await RenderMacroAsync(content, alias, parameters); + if (content == null) + { + throw new InvalidOperationException("Cannot render a macro, no content found by id " + contentId); } - /// - public async Task RenderMacroForContent(IPublishedContent content, string alias, IDictionary? parameters) - { - if(content == null) - { - throw new InvalidOperationException("Cannot render a macro, IPublishedContent is null"); - } + return await RenderMacroAsync(content, alias, parameters); + } - return await RenderMacroAsync(content, alias, parameters); + /// + public async Task RenderMacroForContent(IPublishedContent content, string alias, + IDictionary? parameters) + { + if (content == null) + { + throw new InvalidOperationException("Cannot render a macro, IPublishedContent is null"); } - /// - /// Renders the macro with the specified alias, passing in the specified parameters. - /// - private async Task RenderMacroAsync(IPublishedContent content, string alias, IDictionary? parameters) + return await RenderMacroAsync(content, alias, parameters); + } + + /// + /// Renders the macro with the specified alias, passing in the specified parameters. + /// + private async Task RenderMacroAsync(IPublishedContent content, string alias, + IDictionary? parameters) + { + if (content == null) { - if (content == null) - { - throw new ArgumentNullException(nameof(content)); - } + throw new ArgumentNullException(nameof(content)); + } - // TODO: We are doing at ToLower here because for some insane reason the UpdateMacroModel method looks for a lower case match. the whole macro concept needs to be rewritten. - // NOTE: the value could have HTML encoded values, so we need to deal with that - var macroProps = parameters?.ToDictionary( - x => x.Key.ToLowerInvariant(), - i => (i.Value is string) ? WebUtility.HtmlDecode(i.Value.ToString()) : i.Value); + // TODO: We are doing at ToLower here because for some insane reason the UpdateMacroModel method looks for a lower case match. the whole macro concept needs to be rewritten. + // NOTE: the value could have HTML encoded values, so we need to deal with that + var macroProps = parameters?.ToDictionary( + x => x.Key.ToLowerInvariant(), + i => i.Value is string ? WebUtility.HtmlDecode(i.Value.ToString()) : i.Value); - var html = (await _macroRenderer.RenderAsync(alias, content, macroProps)).Text; + var html = (await _macroRenderer.RenderAsync(alias, content, macroProps)).Text; - return new HtmlEncodedString(html!); - } + return new HtmlEncodedString(html!); } } diff --git a/src/Umbraco.Core/Tour/BackOfficeTourFilter.cs b/src/Umbraco.Core/Tour/BackOfficeTourFilter.cs index 3fba765f83dd..3bb0edb1c3a0 100644 --- a/src/Umbraco.Core/Tour/BackOfficeTourFilter.cs +++ b/src/Umbraco.Core/Tour/BackOfficeTourFilter.cs @@ -1,63 +1,65 @@ using System.Text.RegularExpressions; -namespace Umbraco.Cms.Core.Tour +namespace Umbraco.Cms.Core.Tour; + +/// +/// Represents a back-office tour filter. +/// +public class BackOfficeTourFilter { /// - /// Represents a back-office tour filter. + /// Initializes a new instance of the class. /// - public class BackOfficeTourFilter + /// Value to filter out tours by a plugin, can be null + /// Value to filter out a tour file, can be null + /// Value to filter out a tour alias, can be null + /// + /// Depending on what is null will depend on how the filter is applied. + /// If pluginName is not NULL and it's matched then we check if tourFileName is not NULL and it's matched then we check + /// tour alias is not NULL and then match it, + /// if any steps is NULL then the filters upstream are applied. + /// Example, pluginName = "hello", tourFileName="stuff", tourAlias=NULL = we will filter out the tour file "stuff" from + /// the plugin "hello" but not from other plugins if the same file name exists. + /// Example, tourAlias="test.*" = we will filter out all tour aliases that start with the word "test" regardless of the + /// plugin or file name + /// + public BackOfficeTourFilter(Regex? pluginName, Regex? tourFileName, Regex? tourAlias) { - /// - /// Initializes a new instance of the class. - /// - /// Value to filter out tours by a plugin, can be null - /// Value to filter out a tour file, can be null - /// Value to filter out a tour alias, can be null - /// - /// Depending on what is null will depend on how the filter is applied. - /// If pluginName is not NULL and it's matched then we check if tourFileName is not NULL and it's matched then we check tour alias is not NULL and then match it, - /// if any steps is NULL then the filters upstream are applied. - /// Example, pluginName = "hello", tourFileName="stuff", tourAlias=NULL = we will filter out the tour file "stuff" from the plugin "hello" but not from other plugins if the same file name exists. - /// Example, tourAlias="test.*" = we will filter out all tour aliases that start with the word "test" regardless of the plugin or file name - /// - public BackOfficeTourFilter(Regex? pluginName, Regex? tourFileName, Regex? tourAlias) - { - PluginName = pluginName; - TourFileName = tourFileName; - TourAlias = tourAlias; - } - - /// - /// Gets the plugin name filtering regex. - /// - public Regex? PluginName { get; } - - /// - /// Gets the tour filename filtering regex. - /// - public Regex? TourFileName { get; } - - /// - /// Gets the tour alias filtering regex. - /// - public Regex? TourAlias { get; } - - /// - /// Creates a filter to filter on the plugin name. - /// - public static BackOfficeTourFilter FilterPlugin(Regex pluginName) - => new BackOfficeTourFilter(pluginName, null, null); - - /// - /// Creates a filter to filter on the tour filename. - /// - public static BackOfficeTourFilter FilterFile(Regex tourFileName) - => new BackOfficeTourFilter(null, tourFileName, null); - - /// - /// Creates a filter to filter on the tour alias. - /// - public static BackOfficeTourFilter FilterAlias(Regex tourAlias) - => new BackOfficeTourFilter(null, null, tourAlias); + PluginName = pluginName; + TourFileName = tourFileName; + TourAlias = tourAlias; } + + /// + /// Gets the plugin name filtering regex. + /// + public Regex? PluginName { get; } + + /// + /// Gets the tour filename filtering regex. + /// + public Regex? TourFileName { get; } + + /// + /// Gets the tour alias filtering regex. + /// + public Regex? TourAlias { get; } + + /// + /// Creates a filter to filter on the plugin name. + /// + public static BackOfficeTourFilter FilterPlugin(Regex pluginName) + => new(pluginName, null, null); + + /// + /// Creates a filter to filter on the tour filename. + /// + public static BackOfficeTourFilter FilterFile(Regex tourFileName) + => new(null, tourFileName, null); + + /// + /// Creates a filter to filter on the tour alias. + /// + public static BackOfficeTourFilter FilterAlias(Regex tourAlias) + => new(null, null, tourAlias); } diff --git a/src/Umbraco.Core/Tour/TourFilterCollection.cs b/src/Umbraco.Core/Tour/TourFilterCollection.cs index 2864abbcede7..0803222dfac4 100644 --- a/src/Umbraco.Core/Tour/TourFilterCollection.cs +++ b/src/Umbraco.Core/Tour/TourFilterCollection.cs @@ -1,16 +1,13 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Tour +namespace Umbraco.Cms.Core.Tour; + +/// +/// Represents a collection of items. +/// +public class TourFilterCollection : BuilderCollectionBase { - /// - /// Represents a collection of items. - /// - public class TourFilterCollection : BuilderCollectionBase + public TourFilterCollection(Func> items) : base(items) { - public TourFilterCollection(Func> items) : base(items) - { - } } } diff --git a/src/Umbraco.Core/Tour/TourFilterCollectionBuilder.cs b/src/Umbraco.Core/Tour/TourFilterCollectionBuilder.cs index 61f10cc96d9d..437c20a2dfec 100644 --- a/src/Umbraco.Core/Tour/TourFilterCollectionBuilder.cs +++ b/src/Umbraco.Core/Tour/TourFilterCollectionBuilder.cs @@ -1,73 +1,57 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using Umbraco.Cms.Core.Composing; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Tour +namespace Umbraco.Cms.Core.Tour; + +/// +/// Builds a collection of items. +/// +public class TourFilterCollectionBuilder : CollectionBuilderBase { - /// - /// Builds a collection of items. - /// - public class TourFilterCollectionBuilder : CollectionBuilderBase - { - private readonly HashSet _instances = new HashSet(); + private readonly HashSet _instances = new(); - /// - protected override IEnumerable CreateItems(IServiceProvider factory) - { - return base.CreateItems(factory).Concat(_instances); - } + /// + protected override IEnumerable CreateItems(IServiceProvider factory) => + base.CreateItems(factory).Concat(_instances); - /// - /// Adds a filter instance. - /// - public void AddFilter(BackOfficeTourFilter filter) - { - _instances.Add(filter); - } + /// + /// Adds a filter instance. + /// + public void AddFilter(BackOfficeTourFilter filter) => _instances.Add(filter); - /// - /// Removes a filter instance. - /// - public void RemoveFilter(BackOfficeTourFilter filter) - { - _instances.Remove(filter); - } + /// + /// Removes a filter instance. + /// + public void RemoveFilter(BackOfficeTourFilter filter) => _instances.Remove(filter); - /// - /// Removes all filter instances. - /// - public void RemoveAllFilters() - { - _instances.Clear(); - } + /// + /// Removes all filter instances. + /// + public void RemoveAllFilters() => _instances.Clear(); - /// - /// Removes filters matching a condition. - /// - public void RemoveFilter(Func predicate) - { - _instances.RemoveWhere(new Predicate(predicate)); - } + /// + /// Removes filters matching a condition. + /// + public void RemoveFilter(Func predicate) => + _instances.RemoveWhere(new Predicate(predicate)); - /// - /// Creates and adds a filter instance filtering by plugin name. - /// - public void AddFilterByPlugin(string pluginName) - { - pluginName = pluginName.EnsureStartsWith("^").EnsureEndsWith("$"); - _instances.Add(BackOfficeTourFilter.FilterPlugin(new Regex(pluginName, RegexOptions.IgnoreCase))); - } + /// + /// Creates and adds a filter instance filtering by plugin name. + /// + public void AddFilterByPlugin(string pluginName) + { + pluginName = pluginName.EnsureStartsWith("^").EnsureEndsWith("$"); + _instances.Add(BackOfficeTourFilter.FilterPlugin(new Regex(pluginName, RegexOptions.IgnoreCase))); + } - /// - /// Creates and adds a filter instance filtering by tour filename. - /// - public void AddFilterByFile(string filename) - { - filename = filename.EnsureStartsWith("^").EnsureEndsWith("$"); - _instances.Add(BackOfficeTourFilter.FilterFile(new Regex(filename, RegexOptions.IgnoreCase))); - } + /// + /// Creates and adds a filter instance filtering by tour filename. + /// + public void AddFilterByFile(string filename) + { + filename = filename.EnsureStartsWith("^").EnsureEndsWith("$"); + _instances.Add(BackOfficeTourFilter.FilterFile(new Regex(filename, RegexOptions.IgnoreCase))); } } diff --git a/src/Umbraco.Core/Trees/ActionUrlMethod.cs b/src/Umbraco.Core/Trees/ActionUrlMethod.cs index fcf455c6ad42..7f31c5964735 100644 --- a/src/Umbraco.Core/Trees/ActionUrlMethod.cs +++ b/src/Umbraco.Core/Trees/ActionUrlMethod.cs @@ -1,11 +1,10 @@ -namespace Umbraco.Cms.Core.Trees +namespace Umbraco.Cms.Core.Trees; + +/// +/// Specifies the action to take for a menu item when a URL is specified +/// +public enum ActionUrlMethod { - /// - /// Specifies the action to take for a menu item when a URL is specified - /// - public enum ActionUrlMethod - { - Dialog, - BlankWindow - } + Dialog, + BlankWindow } diff --git a/src/Umbraco.Core/Trees/CoreTreeAttribute.cs b/src/Umbraco.Core/Trees/CoreTreeAttribute.cs index eedad5b600de..0d787ee80523 100644 --- a/src/Umbraco.Core/Trees/CoreTreeAttribute.cs +++ b/src/Umbraco.Core/Trees/CoreTreeAttribute.cs @@ -1,14 +1,12 @@ -using System; +namespace Umbraco.Cms.Core.Trees; -namespace Umbraco.Cms.Core.Trees +/// +/// Indicates that a tree is a core tree and should not be treated as a plugin tree. +/// +/// +/// This ensures that umbraco will look in the umbraco folders for views for this tree. +/// +[AttributeUsage(AttributeTargets.Class)] +public class CoreTreeAttribute : Attribute { - /// - /// Indicates that a tree is a core tree and should not be treated as a plugin tree. - /// - /// - /// This ensures that umbraco will look in the umbraco folders for views for this tree. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class CoreTreeAttribute : Attribute - { } } diff --git a/src/Umbraco.Core/Trees/IMenuItemCollectionFactory.cs b/src/Umbraco.Core/Trees/IMenuItemCollectionFactory.cs index fca82ca18b60..bba1bfc2dce8 100644 --- a/src/Umbraco.Core/Trees/IMenuItemCollectionFactory.cs +++ b/src/Umbraco.Core/Trees/IMenuItemCollectionFactory.cs @@ -1,15 +1,13 @@ -namespace Umbraco.Cms.Core.Trees -{ +namespace Umbraco.Cms.Core.Trees; +/// +/// Represents a factory to create . +/// +public interface IMenuItemCollectionFactory +{ /// - /// Represents a factory to create . + /// Creates an empty . /// - public interface IMenuItemCollectionFactory - { - /// - /// Creates an empty . - /// - /// An empty . - MenuItemCollection Create(); - } + /// An empty . + MenuItemCollection Create(); } diff --git a/src/Umbraco.Core/Trees/ISearchableTree.cs b/src/Umbraco.Core/Trees/ISearchableTree.cs index dd61ba0cdb72..42883d0f8749 100644 --- a/src/Umbraco.Core/Trees/ISearchableTree.cs +++ b/src/Umbraco.Core/Trees/ISearchableTree.cs @@ -1,28 +1,25 @@ -using System.Collections.Generic; -using System.Threading.Tasks; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Models.ContentEditing; -namespace Umbraco.Cms.Core.Trees +namespace Umbraco.Cms.Core.Trees; + +public interface ISearchableTree : IDiscoverable { - public interface ISearchableTree : IDiscoverable - { - /// - /// The alias of the tree that the belongs to - /// - string TreeAlias { get; } + /// + /// The alias of the tree that the belongs to + /// + string TreeAlias { get; } - /// - /// Searches for results based on the entity type - /// - /// - /// - /// - /// - /// - /// A starting point for the search, generally a node id, but for members this is a member type alias - /// - /// - Task SearchAsync(string query, int pageSize, long pageIndex, string? searchFrom = null); - } + /// + /// Searches for results based on the entity type + /// + /// + /// + /// + /// + /// + /// A starting point for the search, generally a node id, but for members this is a member type alias + /// + /// + Task SearchAsync(string query, int pageSize, long pageIndex, string? searchFrom = null); } diff --git a/src/Umbraco.Core/Trees/ITree.cs b/src/Umbraco.Core/Trees/ITree.cs index 106b3eef3750..ae2511b1541a 100644 --- a/src/Umbraco.Core/Trees/ITree.cs +++ b/src/Umbraco.Core/Trees/ITree.cs @@ -1,44 +1,43 @@ -namespace Umbraco.Cms.Core.Trees +namespace Umbraco.Cms.Core.Trees; + +// TODO: we don't really use this, it is nice to have the treecontroller, attribute and ApplicationTree streamlined to implement this but it's not used +// leave as internal for now, maybe we'll use in the future, means we could pass around ITree +// TODO: We should make this a thing, a tree should just be an interface *not* a controller +public interface ITree { - // TODO: we don't really use this, it is nice to have the treecontroller, attribute and ApplicationTree streamlined to implement this but it's not used - // leave as internal for now, maybe we'll use in the future, means we could pass around ITree - // TODO: We should make this a thing, a tree should just be an interface *not* a controller - public interface ITree - { - /// - /// Gets or sets the sort order. - /// - /// The sort order. - int SortOrder { get; } + /// + /// Gets or sets the sort order. + /// + /// The sort order. + int SortOrder { get; } - /// - /// Gets the section alias. - /// - string SectionAlias { get; } + /// + /// Gets the section alias. + /// + string SectionAlias { get; } - /// - /// Gets the tree group. - /// - string? TreeGroup { get; } + /// + /// Gets the tree group. + /// + string? TreeGroup { get; } - /// - /// Gets the tree alias. - /// - string TreeAlias { get; } + /// + /// Gets the tree alias. + /// + string TreeAlias { get; } - /// - /// Gets or sets the tree title (fallback if the tree alias isn't localized) - /// - string? TreeTitle { get; } + /// + /// Gets or sets the tree title (fallback if the tree alias isn't localized) + /// + string? TreeTitle { get; } - /// - /// Gets the tree use. - /// - TreeUse TreeUse { get; } + /// + /// Gets the tree use. + /// + TreeUse TreeUse { get; } - /// - /// Flag to define if this tree is a single node tree (will never contain child nodes, full screen app) - /// - bool IsSingleNodeTree { get; } - } + /// + /// Flag to define if this tree is a single node tree (will never contain child nodes, full screen app) + /// + bool IsSingleNodeTree { get; } } diff --git a/src/Umbraco.Core/Trees/MenuItemCollection.cs b/src/Umbraco.Core/Trees/MenuItemCollection.cs index 66bdba55d41a..ea1c6f196b61 100644 --- a/src/Umbraco.Core/Trees/MenuItemCollection.cs +++ b/src/Umbraco.Core/Trees/MenuItemCollection.cs @@ -1,44 +1,33 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Actions; using Umbraco.Cms.Core.Models.Trees; -namespace Umbraco.Cms.Core.Trees -{ - /// - /// A menu item collection for a given tree node - /// - [DataContract(Name = "menuItems", Namespace = "")] - public class MenuItemCollection - { - private readonly MenuItemList _menuItems; +namespace Umbraco.Cms.Core.Trees; - public MenuItemCollection(ActionCollection actionCollection) - { - _menuItems = new MenuItemList(actionCollection); - } +/// +/// A menu item collection for a given tree node +/// +[DataContract(Name = "menuItems", Namespace = "")] +public class MenuItemCollection +{ + public MenuItemCollection(ActionCollection actionCollection) => Items = new MenuItemList(actionCollection); - public MenuItemCollection(ActionCollection actionCollection, IEnumerable items) - { - _menuItems = new MenuItemList(actionCollection, items); - } + public MenuItemCollection(ActionCollection actionCollection, IEnumerable items) => + Items = new MenuItemList(actionCollection, items); - /// - /// Sets the default menu item alias to be shown when the menu is launched - this is optional and if not set then the menu will just be shown normally. - /// - [DataMember(Name = "defaultAlias")] - public string? DefaultMenuAlias { get; set; } + /// + /// Sets the default menu item alias to be shown when the menu is launched - this is optional and if not set then the + /// menu will just be shown normally. + /// + [DataMember(Name = "defaultAlias")] + public string? DefaultMenuAlias { get; set; } - /// - /// The list of menu items - /// - /// - /// We require this so the json serialization works correctly - /// - [DataMember(Name = "menuItems")] - public MenuItemList Items - { - get { return _menuItems; } - } - } + /// + /// The list of menu items + /// + /// + /// We require this so the json serialization works correctly + /// + [DataMember(Name = "menuItems")] + public MenuItemList Items { get; } } diff --git a/src/Umbraco.Core/Trees/MenuItemCollectionFactory.cs b/src/Umbraco.Core/Trees/MenuItemCollectionFactory.cs index 112b8b624026..da24b0d933f7 100644 --- a/src/Umbraco.Core/Trees/MenuItemCollectionFactory.cs +++ b/src/Umbraco.Core/Trees/MenuItemCollectionFactory.cs @@ -1,20 +1,12 @@ using Umbraco.Cms.Core.Actions; -namespace Umbraco.Cms.Core.Trees -{ - public class MenuItemCollectionFactory: IMenuItemCollectionFactory - { - private readonly ActionCollection _actionCollection; +namespace Umbraco.Cms.Core.Trees; - public MenuItemCollectionFactory(ActionCollection actionCollection) - { - _actionCollection = actionCollection; - } +public class MenuItemCollectionFactory : IMenuItemCollectionFactory +{ + private readonly ActionCollection _actionCollection; - public MenuItemCollection Create() - { - return new MenuItemCollection(_actionCollection); - } + public MenuItemCollectionFactory(ActionCollection actionCollection) => _actionCollection = actionCollection; - } + public MenuItemCollection Create() => new MenuItemCollection(_actionCollection); } diff --git a/src/Umbraco.Core/Trees/MenuItemList.cs b/src/Umbraco.Core/Trees/MenuItemList.cs index b3fe4206022e..ade35f632bb1 100644 --- a/src/Umbraco.Core/Trees/MenuItemList.cs +++ b/src/Umbraco.Core/Trees/MenuItemList.cs @@ -1,72 +1,67 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Collections.Generic; -using System.Threading; using Umbraco.Cms.Core.Actions; using Umbraco.Cms.Core.Models.Trees; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Trees +namespace Umbraco.Cms.Core.Trees; + +/// +/// A custom menu list +/// +/// +/// NOTE: We need a sub collection to the MenuItemCollection object due to how json serialization works. +/// +public class MenuItemList : List { + private readonly ActionCollection _actionCollection; + + public MenuItemList(ActionCollection actionCollection) => _actionCollection = actionCollection; + + public MenuItemList(ActionCollection actionCollection, IEnumerable items) + : base(items) => + _actionCollection = actionCollection; + /// - /// A custom menu list + /// Adds a menu item with a dictionary which is merged to the AdditionalData bag /// - /// - /// NOTE: We need a sub collection to the MenuItemCollection object due to how json serialization works. - /// - public class MenuItemList : List + /// + /// + /// The used to localize the action name based on its alias + /// Whether or not this action opens a dialog + public MenuItem? Add(ILocalizedTextService textService, bool hasSeparator = false, bool opensDialog = false) + where T : IAction { - private readonly ActionCollection _actionCollection; - - public MenuItemList(ActionCollection actionCollection) + MenuItem item = CreateMenuItem(textService, hasSeparator, opensDialog); + if (item != null) { - _actionCollection = actionCollection; + Add(item); + return item; } - public MenuItemList(ActionCollection actionCollection, IEnumerable items) - : base(items) - { - _actionCollection = actionCollection; - } + return null; + } - /// - /// Adds a menu item with a dictionary which is merged to the AdditionalData bag - /// - /// - /// - /// The used to localize the action name based on its alias - /// Whether or not this action opens a dialog - public MenuItem? Add(ILocalizedTextService textService, bool hasSeparator = false, bool opensDialog = false) - where T : IAction + private MenuItem? CreateMenuItem(ILocalizedTextService textService, bool hasSeparator = false, + bool opensDialog = false) + where T : IAction + { + T item = _actionCollection.GetAction(); + if (item == null) { - var item = CreateMenuItem(textService, hasSeparator, opensDialog); - if (item != null) - { - Add(item); - return item; - } return null; } - private MenuItem? CreateMenuItem(ILocalizedTextService textService, bool hasSeparator = false, bool opensDialog = false) - where T : IAction - { - var item = _actionCollection.GetAction(); - if (item == null) return null; - - var values = textService.GetAllStoredValues(Thread.CurrentThread.CurrentUICulture); - values.TryGetValue($"visuallyHiddenTexts/{item.Alias}Description", out var textDescription); + IDictionary values = textService.GetAllStoredValues(Thread.CurrentThread.CurrentUICulture); + values.TryGetValue($"visuallyHiddenTexts/{item.Alias}Description", out var textDescription); - var menuItem = new MenuItem(item, textService.Localize($"actions", item.Alias)) - { - SeparatorBefore = hasSeparator, - OpensDialog = opensDialog, - TextDescription = textDescription, - }; + var menuItem = new MenuItem(item, textService.Localize("actions", item.Alias)) + { + SeparatorBefore = hasSeparator, OpensDialog = opensDialog, TextDescription = textDescription + }; - return menuItem; - } + return menuItem; } } diff --git a/src/Umbraco.Core/Trees/SearchableApplicationTree.cs b/src/Umbraco.Core/Trees/SearchableApplicationTree.cs index 33104cb8c7e4..aa8f31112a4a 100644 --- a/src/Umbraco.Core/Trees/SearchableApplicationTree.cs +++ b/src/Umbraco.Core/Trees/SearchableApplicationTree.cs @@ -1,22 +1,22 @@ -namespace Umbraco.Cms.Core.Trees +namespace Umbraco.Cms.Core.Trees; + +public class SearchableApplicationTree { - public class SearchableApplicationTree + public SearchableApplicationTree(string appAlias, string treeAlias, int sortOrder, string formatterService, + string formatterMethod, ISearchableTree searchableTree) { - public SearchableApplicationTree(string appAlias, string treeAlias, int sortOrder, string formatterService, string formatterMethod, ISearchableTree searchableTree) - { - AppAlias = appAlias; - TreeAlias = treeAlias; - SortOrder = sortOrder; - FormatterService = formatterService; - FormatterMethod = formatterMethod; - SearchableTree = searchableTree; - } - - public string AppAlias { get; } - public string TreeAlias { get; } - public int SortOrder { get; } - public string FormatterService { get; } - public string FormatterMethod { get; } - public ISearchableTree SearchableTree { get; } + AppAlias = appAlias; + TreeAlias = treeAlias; + SortOrder = sortOrder; + FormatterService = formatterService; + FormatterMethod = formatterMethod; + SearchableTree = searchableTree; } + + public string AppAlias { get; } + public string TreeAlias { get; } + public int SortOrder { get; } + public string FormatterService { get; } + public string FormatterMethod { get; } + public ISearchableTree SearchableTree { get; } } diff --git a/src/Umbraco.Core/Trees/SearchableTreeAttribute.cs b/src/Umbraco.Core/Trees/SearchableTreeAttribute.cs index ca5cfef02a26..a21096b1f496 100644 --- a/src/Umbraco.Core/Trees/SearchableTreeAttribute.cs +++ b/src/Umbraco.Core/Trees/SearchableTreeAttribute.cs @@ -1,53 +1,63 @@ -using System; +namespace Umbraco.Cms.Core.Trees; -namespace Umbraco.Cms.Core.Trees +[AttributeUsage(AttributeTargets.Class)] +public sealed class SearchableTreeAttribute : Attribute { - [AttributeUsage(AttributeTargets.Class)] - public sealed class SearchableTreeAttribute : Attribute + public const int DefaultSortOrder = 1000; + + /// + /// This constructor will assume that the method name equals `format(searchResult, appAlias, treeAlias)`. + /// + /// Name of the service. + public SearchableTreeAttribute(string serviceName) + : this(serviceName, string.Empty) { - public const int DefaultSortOrder = 1000; - - public string ServiceName { get; } - - public string MethodName { get; } - - public int SortOrder { get; } - - /// - /// This constructor will assume that the method name equals `format(searchResult, appAlias, treeAlias)`. - /// - /// Name of the service. - public SearchableTreeAttribute(string serviceName) - : this(serviceName, string.Empty) - { } - - /// - /// This constructor defines both the Angular service and method name to use. - /// - /// Name of the service. - /// Name of the method. - public SearchableTreeAttribute(string serviceName, string methodName) - : this(serviceName, methodName, DefaultSortOrder) - { } - - /// - /// This constructor defines both the Angular service and method name to use and explicitly defines a sort order for the results - /// - /// Name of the service. - /// Name of the method. - /// The sort order. - /// serviceName - /// or - /// methodName - /// Value can't be empty or consist only of white-space characters. - serviceName - public SearchableTreeAttribute(string serviceName, string methodName, int sortOrder) + } + + /// + /// This constructor defines both the Angular service and method name to use. + /// + /// Name of the service. + /// Name of the method. + public SearchableTreeAttribute(string serviceName, string methodName) + : this(serviceName, methodName, DefaultSortOrder) + { + } + + /// + /// This constructor defines both the Angular service and method name to use and explicitly defines a sort order for + /// the results + /// + /// Name of the service. + /// Name of the method. + /// The sort order. + /// + /// serviceName + /// or + /// methodName + /// + /// Value can't be empty or consist only of white-space characters. - serviceName + public SearchableTreeAttribute(string serviceName, string methodName, int sortOrder) + { + if (serviceName == null) { - if (serviceName == null) throw new ArgumentNullException(nameof(serviceName)); - if (string.IsNullOrWhiteSpace(serviceName)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(serviceName)); + throw new ArgumentNullException(nameof(serviceName)); + } - ServiceName = serviceName; - MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName)); - SortOrder = sortOrder; + if (string.IsNullOrWhiteSpace(serviceName)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(serviceName)); } + + ServiceName = serviceName; + MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName)); + SortOrder = sortOrder; } + + public string ServiceName { get; } + + public string MethodName { get; } + + public int SortOrder { get; } } diff --git a/src/Umbraco.Core/Trees/SearchableTreeCollection.cs b/src/Umbraco.Core/Trees/SearchableTreeCollection.cs index ff42b5e8c381..e72548bc9c02 100644 --- a/src/Umbraco.Core/Trees/SearchableTreeCollection.cs +++ b/src/Umbraco.Core/Trees/SearchableTreeCollection.cs @@ -1,50 +1,46 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Trees +namespace Umbraco.Cms.Core.Trees; + +public class SearchableTreeCollection : BuilderCollectionBase { - public class SearchableTreeCollection : BuilderCollectionBase - { - private readonly Dictionary _dictionary; + private readonly Dictionary _dictionary; - public SearchableTreeCollection(Func> items, ITreeService treeService) - : base(items) - { - _dictionary = CreateDictionary(treeService); - } + public SearchableTreeCollection(Func> items, ITreeService treeService) + : base(items) => + _dictionary = CreateDictionary(treeService); - private Dictionary CreateDictionary(ITreeService treeService) + public IReadOnlyDictionary SearchableApplicationTrees => _dictionary; + + public SearchableApplicationTree this[string key] => _dictionary[key]; + + private Dictionary CreateDictionary(ITreeService treeService) + { + Tree[] appTrees = treeService.GetAll() + .OrderBy(x => x.SortOrder) + .ToArray(); + var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); + ISearchableTree[] searchableTrees = this.ToArray(); + foreach (Tree appTree in appTrees) { - var appTrees = treeService.GetAll() - .OrderBy(x => x.SortOrder) - .ToArray(); - var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); - var searchableTrees = this.ToArray(); - foreach (var appTree in appTrees) + ISearchableTree found = searchableTrees.FirstOrDefault(x => x.TreeAlias.InvariantEquals(appTree.TreeAlias)); + if (found != null) { - var found = searchableTrees.FirstOrDefault(x => x.TreeAlias.InvariantEquals(appTree.TreeAlias)); - if (found != null) - { - var searchableTreeAttribute = found.GetType().GetCustomAttribute(false); - dictionary[found.TreeAlias] = new SearchableApplicationTree( - appTree.SectionAlias, - appTree.TreeAlias, - searchableTreeAttribute?.SortOrder ?? SearchableTreeAttribute.DefaultSortOrder, - searchableTreeAttribute?.ServiceName ?? string.Empty, - searchableTreeAttribute?.MethodName ?? string.Empty, - found - ); - } + SearchableTreeAttribute searchableTreeAttribute = + found.GetType().GetCustomAttribute(false); + dictionary[found.TreeAlias] = new SearchableApplicationTree( + appTree.SectionAlias, + appTree.TreeAlias, + searchableTreeAttribute?.SortOrder ?? SearchableTreeAttribute.DefaultSortOrder, + searchableTreeAttribute?.ServiceName ?? string.Empty, + searchableTreeAttribute?.MethodName ?? string.Empty, + found + ); } - return dictionary; } - public IReadOnlyDictionary SearchableApplicationTrees => _dictionary; - - public SearchableApplicationTree this[string key] => _dictionary[key]; + return dictionary; } } diff --git a/src/Umbraco.Core/Trees/SearchableTreeCollectionBuilder.cs b/src/Umbraco.Core/Trees/SearchableTreeCollectionBuilder.cs index dca2839558fb..54133fbeacf5 100644 --- a/src/Umbraco.Core/Trees/SearchableTreeCollectionBuilder.cs +++ b/src/Umbraco.Core/Trees/SearchableTreeCollectionBuilder.cs @@ -1,13 +1,13 @@ using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Trees +namespace Umbraco.Cms.Core.Trees; + +public class SearchableTreeCollectionBuilder : LazyCollectionBuilderBase { - public class SearchableTreeCollectionBuilder : LazyCollectionBuilderBase - { - protected override SearchableTreeCollectionBuilder This => this; + protected override SearchableTreeCollectionBuilder This => this; - //per request because generally an instance of ISearchableTree is a controller - protected override ServiceLifetime CollectionLifetime => ServiceLifetime.Scoped; - } + //per request because generally an instance of ISearchableTree is a controller + protected override ServiceLifetime CollectionLifetime => ServiceLifetime.Scoped; } diff --git a/src/Umbraco.Core/Trees/Tree.cs b/src/Umbraco.Core/Trees/Tree.cs index f229dd801921..711e54ef45b5 100644 --- a/src/Umbraco.Core/Trees/Tree.cs +++ b/src/Umbraco.Core/Trees/Tree.cs @@ -1,74 +1,75 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using System.Diagnostics; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Trees +namespace Umbraco.Cms.Core.Trees; + +[DebuggerDisplay("Tree - {SectionAlias}/{TreeAlias}")] +public class Tree : ITree { - [DebuggerDisplay("Tree - {SectionAlias}/{TreeAlias}")] - public class Tree : ITree + public Tree(int sortOrder, string applicationAlias, string? group, string alias, string? title, TreeUse use, + Type treeControllerType, bool isSingleNodeTree) { - public Tree(int sortOrder, string applicationAlias, string? group, string alias, string? title, TreeUse use, Type treeControllerType, bool isSingleNodeTree) - { - SortOrder = sortOrder; - SectionAlias = applicationAlias ?? throw new ArgumentNullException(nameof(applicationAlias)); - TreeGroup = group; - TreeAlias = alias ?? throw new ArgumentNullException(nameof(alias)); - TreeTitle = title; - TreeUse = use; - TreeControllerType = treeControllerType ?? throw new ArgumentNullException(nameof(treeControllerType)); - IsSingleNodeTree = isSingleNodeTree; - } + SortOrder = sortOrder; + SectionAlias = applicationAlias ?? throw new ArgumentNullException(nameof(applicationAlias)); + TreeGroup = group; + TreeAlias = alias ?? throw new ArgumentNullException(nameof(alias)); + TreeTitle = title; + TreeUse = use; + TreeControllerType = treeControllerType ?? throw new ArgumentNullException(nameof(treeControllerType)); + IsSingleNodeTree = isSingleNodeTree; + } - /// - public int SortOrder { get; set; } + /// + /// Gets the tree controller type. + /// + public Type TreeControllerType { get; } - /// - public string SectionAlias { get; set; } + /// + public int SortOrder { get; set; } - /// - public string? TreeGroup { get; } + /// + public string SectionAlias { get; set; } - /// - public string TreeAlias { get; } + /// + public string? TreeGroup { get; } - /// - public string? TreeTitle { get; set; } + /// + public string TreeAlias { get; } - /// - public TreeUse TreeUse { get; set; } + /// + public string? TreeTitle { get; set; } - /// - public bool IsSingleNodeTree { get; } + /// + public TreeUse TreeUse { get; set; } - /// - /// Gets the tree controller type. - /// - public Type TreeControllerType { get; } + /// + public bool IsSingleNodeTree { get; } - public static string? GetRootNodeDisplayName(ITree tree, ILocalizedTextService textService) - { - var label = $"[{tree.TreeAlias}]"; + public static string? GetRootNodeDisplayName(ITree tree, ILocalizedTextService textService) + { + var label = $"[{tree.TreeAlias}]"; - // try to look up a the localized tree header matching the tree alias - var localizedLabel = textService.Localize("treeHeader", tree.TreeAlias); + // try to look up a the localized tree header matching the tree alias + var localizedLabel = textService.Localize("treeHeader", tree.TreeAlias); - // if the localizedLabel returns [alias] then return the title if it's defined - if (localizedLabel != null && localizedLabel.Equals(label, StringComparison.InvariantCultureIgnoreCase)) - { - if (string.IsNullOrEmpty(tree.TreeTitle) == false) - label = tree.TreeTitle; - } - else + // if the localizedLabel returns [alias] then return the title if it's defined + if (localizedLabel != null && localizedLabel.Equals(label, StringComparison.InvariantCultureIgnoreCase)) + { + if (string.IsNullOrEmpty(tree.TreeTitle) == false) { - // the localizedLabel translated into something that's not just [alias], so use the translation - label = localizedLabel; + label = tree.TreeTitle; } - - return label; } + else + { + // the localizedLabel translated into something that's not just [alias], so use the translation + label = localizedLabel; + } + + return label; } } diff --git a/src/Umbraco.Core/Trees/TreeCollection.cs b/src/Umbraco.Core/Trees/TreeCollection.cs index 59fa99819c00..0328f9001bf6 100644 --- a/src/Umbraco.Core/Trees/TreeCollection.cs +++ b/src/Umbraco.Core/Trees/TreeCollection.cs @@ -1,17 +1,13 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.Trees +namespace Umbraco.Cms.Core.Trees; + +/// +/// Represents the collection of section trees. +/// +public class TreeCollection : BuilderCollectionBase { - /// - /// Represents the collection of section trees. - /// - public class TreeCollection : BuilderCollectionBase + public TreeCollection(Func> items) : base(items) { - - public TreeCollection(Func> items) : base(items) - { - } } } diff --git a/src/Umbraco.Core/Trees/TreeNode.cs b/src/Umbraco.Core/Trees/TreeNode.cs index 3c166c9fdd24..bd9913616bff 100644 --- a/src/Umbraco.Core/Trees/TreeNode.cs +++ b/src/Umbraco.Core/Trees/TreeNode.cs @@ -1,128 +1,128 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Trees +namespace Umbraco.Cms.Core.Trees; + +/// +/// Represents a model in the tree +/// +/// +/// TreeNode is sealed to prevent developers from adding additional json data to the response +/// +[DataContract(Name = "node", Namespace = "")] +public class TreeNode : EntityBasic { /// - /// Represents a model in the tree + /// Internal constructor, to create a tree node use the CreateTreeNode methods of the TreeApiController. /// - /// - /// TreeNode is sealed to prevent developers from adding additional json data to the response - /// - [DataContract(Name = "node", Namespace = "")] - public class TreeNode : EntityBasic + /// + /// The parent id for the current node + /// + /// + public TreeNode(string nodeId, string? parentId, string? getChildNodesUrl, string? menuUrl) { - /// - /// Internal constructor, to create a tree node use the CreateTreeNode methods of the TreeApiController. - /// - /// - /// The parent id for the current node - /// - /// - public TreeNode(string nodeId, string? parentId, string? getChildNodesUrl, string? menuUrl) + if (nodeId == null) { - if (nodeId == null) throw new ArgumentNullException(nameof(nodeId)); - if (string.IsNullOrWhiteSpace(nodeId)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(nodeId)); - - Id = nodeId; - ParentId = parentId; - ChildNodesUrl = getChildNodesUrl; - MenuUrl = menuUrl; - CssClasses = new List(); - //default - Icon = "icon-folder-close"; - Path = "-1"; + throw new ArgumentNullException(nameof(nodeId)); } - [DataMember(Name = "parentId", IsRequired = true)] - public new object? ParentId { get; set; } - - /// - /// A flag to set whether or not this node has children - /// - [DataMember(Name = "hasChildren")] - public bool HasChildren { get; set; } - - /// - /// The tree nodetype which refers to the type of node rendered in the tree - /// - [DataMember(Name = "nodeType")] - public string? NodeType { get; set; } - - /// - /// Optional: The Route path for the editor for this node - /// - /// - /// If this is not set, then the route path will be automatically determined by: {section}/edit/{id} - /// - [DataMember(Name = "routePath")] - public string? RoutePath { get; set; } - - /// - /// The JSON URL to load the nodes children - /// - [DataMember(Name = "childNodesUrl")] - public string? ChildNodesUrl { get; set; } - - /// - /// The JSON URL to load the menu from - /// - [DataMember(Name = "menuUrl")] - public string? MenuUrl { get; set; } - - /// - /// Returns true if the icon represents a CSS class instead of a file path - /// - [DataMember(Name = "iconIsClass")] - public bool IconIsClass + if (string.IsNullOrWhiteSpace(nodeId)) { - get - { - if (Icon.IsNullOrWhiteSpace()) - { - return true; - } + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(nodeId)); + } - if (Icon!.StartsWith("..")) - return false; + Id = nodeId; + ParentId = parentId; + ChildNodesUrl = getChildNodesUrl; + MenuUrl = menuUrl; + CssClasses = new List(); + //default + Icon = "icon-folder-close"; + Path = "-1"; + } + [DataMember(Name = "parentId", IsRequired = true)] + public new object? ParentId { get; set; } - //if it starts with a '.' or doesn't contain a '.' at all then it is a class - return Icon.StartsWith(".") || Icon.Contains(".") == false; - } - } + /// + /// A flag to set whether or not this node has children + /// + [DataMember(Name = "hasChildren")] + public bool HasChildren { get; set; } - /// - /// Returns the icon file path if the icon is not a class, otherwise returns an empty string - /// - [DataMember(Name = "iconFilePath")] - public string IconFilePath + /// + /// The tree nodetype which refers to the type of node rendered in the tree + /// + [DataMember(Name = "nodeType")] + public string? NodeType { get; set; } + + /// + /// Optional: The Route path for the editor for this node + /// + /// + /// If this is not set, then the route path will be automatically determined by: {section}/edit/{id} + /// + [DataMember(Name = "routePath")] + public string? RoutePath { get; set; } + + /// + /// The JSON URL to load the nodes children + /// + [DataMember(Name = "childNodesUrl")] + public string? ChildNodesUrl { get; set; } + + /// + /// The JSON URL to load the menu from + /// + [DataMember(Name = "menuUrl")] + public string? MenuUrl { get; set; } + + /// + /// Returns true if the icon represents a CSS class instead of a file path + /// + [DataMember(Name = "iconIsClass")] + public bool IconIsClass + { + get { - get + if (Icon.IsNullOrWhiteSpace()) { - return string.Empty; - - //TODO Figure out how to do this, without the model has to know a bout services and config. - // - // if (IconIsClass) - // return string.Empty; - // - // //absolute path with or without tilde - // if (Icon.StartsWith("~") || Icon.StartsWith("/")) - // return IOHelper.ResolveUrl("~" + Icon.TrimStart(Constants.CharArrays.Tilde)); - // - // //legacy icon path - // return string.Format("{0}images/umbraco/{1}", Current.Configs.Global().Path.EnsureEndsWith("/"), Icon); + return true; } - } - /// - /// A list of additional/custom css classes to assign to the node - /// - [DataMember(Name = "cssClasses")] - public IList CssClasses { get; private set; } + if (Icon!.StartsWith("..")) + { + return false; + } + + + //if it starts with a '.' or doesn't contain a '.' at all then it is a class + return Icon.StartsWith(".") || Icon.Contains(".") == false; + } } + + /// + /// Returns the icon file path if the icon is not a class, otherwise returns an empty string + /// + [DataMember(Name = "iconFilePath")] + public string IconFilePath => string.Empty; + + //TODO Figure out how to do this, without the model has to know a bout services and config. + // + // if (IconIsClass) + // return string.Empty; + // + // //absolute path with or without tilde + // if (Icon.StartsWith("~") || Icon.StartsWith("/")) + // return IOHelper.ResolveUrl("~" + Icon.TrimStart(Constants.CharArrays.Tilde)); + // + // //legacy icon path + // return string.Format("{0}images/umbraco/{1}", Current.Configs.Global().Path.EnsureEndsWith("/"), Icon); + /// + /// A list of additional/custom css classes to assign to the node + /// + [DataMember(Name = "cssClasses")] + public IList CssClasses { get; private set; } } diff --git a/src/Umbraco.Core/Trees/TreeNodeCollection.cs b/src/Umbraco.Core/Trees/TreeNodeCollection.cs index 545b6881aaa6..dcd7c67908eb 100644 --- a/src/Umbraco.Core/Trees/TreeNodeCollection.cs +++ b/src/Umbraco.Core/Trees/TreeNodeCollection.cs @@ -1,20 +1,18 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; -namespace Umbraco.Cms.Core.Trees +namespace Umbraco.Cms.Core.Trees; + +[CollectionDataContract(Name = "nodes", Namespace = "")] +public sealed class TreeNodeCollection : List { - [CollectionDataContract(Name = "nodes", Namespace = "")] - public sealed class TreeNodeCollection : List + public TreeNodeCollection() { - public static TreeNodeCollection Empty => new TreeNodeCollection(); - - public TreeNodeCollection() - { - } + } - public TreeNodeCollection(IEnumerable nodes) - : base(nodes) - { - } + public TreeNodeCollection(IEnumerable nodes) + : base(nodes) + { } + + public static TreeNodeCollection Empty => new(); } diff --git a/src/Umbraco.Core/Trees/TreeNodeExtensions.cs b/src/Umbraco.Core/Trees/TreeNodeExtensions.cs index 9e887f68ec85..1de63ebdf47a 100644 --- a/src/Umbraco.Core/Trees/TreeNodeExtensions.cs +++ b/src/Umbraco.Core/Trees/TreeNodeExtensions.cs @@ -3,80 +3,77 @@ using Umbraco.Cms.Core.Trees; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +public static class TreeNodeExtensions { - public static class TreeNodeExtensions - { - internal const string LegacyJsCallbackKey = "jsClickCallback"; + internal const string LegacyJsCallbackKey = "jsClickCallback"; - /// - /// Legacy tree node's assign a JS method callback for when an item is clicked, this method facilitates that. - /// - /// - /// - internal static void AssignLegacyJsCallback(this TreeNode treeNode, string jsCallback) - { - treeNode.AdditionalData[LegacyJsCallbackKey] = jsCallback; - } + /// + /// Legacy tree node's assign a JS method callback for when an item is clicked, this method facilitates that. + /// + /// + /// + internal static void AssignLegacyJsCallback(this TreeNode treeNode, string jsCallback) => + treeNode.AdditionalData[LegacyJsCallbackKey] = jsCallback; - /// - /// Sets the node style to show that it is a container type - /// - /// - public static void SetContainerStyle(this TreeNode treeNode) + /// + /// Sets the node style to show that it is a container type + /// + /// + public static void SetContainerStyle(this TreeNode treeNode) + { + if (treeNode.CssClasses.Contains("is-container") == false) { - if (treeNode.CssClasses.Contains("is-container") == false) - { - treeNode.CssClasses.Add("is-container"); - } + treeNode.CssClasses.Add("is-container"); } + } - /// - /// Sets the node style to show that it is currently protected publicly - /// - /// - public static void SetProtectedStyle(this TreeNode treeNode) + /// + /// Sets the node style to show that it is currently protected publicly + /// + /// + public static void SetProtectedStyle(this TreeNode treeNode) + { + if (treeNode.CssClasses.Contains("protected") == false) { - if (treeNode.CssClasses.Contains("protected") == false) - { - treeNode.CssClasses.Add("protected"); - } + treeNode.CssClasses.Add("protected"); } + } - /// - /// Sets the node style to show that it is currently locked / non-deletable - /// - /// - public static void SetLockedStyle(this TreeNode treeNode) + /// + /// Sets the node style to show that it is currently locked / non-deletable + /// + /// + public static void SetLockedStyle(this TreeNode treeNode) + { + if (treeNode.CssClasses.Contains("locked") == false) { - if (treeNode.CssClasses.Contains("locked") == false) - { - treeNode.CssClasses.Add("locked"); - } + treeNode.CssClasses.Add("locked"); } + } - /// - /// Sets the node style to show that it is has unpublished versions (but is currently published) - /// - /// - public static void SetHasPendingVersionStyle(this TreeNode treeNode) + /// + /// Sets the node style to show that it is has unpublished versions (but is currently published) + /// + /// + public static void SetHasPendingVersionStyle(this TreeNode treeNode) + { + if (treeNode.CssClasses.Contains("has-unpublished-version") == false) { - if (treeNode.CssClasses.Contains("has-unpublished-version") == false) - { - treeNode.CssClasses.Add("has-unpublished-version"); - } + treeNode.CssClasses.Add("has-unpublished-version"); } + } - /// - /// Sets the node style to show that it is not published - /// - /// - public static void SetNotPublishedStyle(this TreeNode treeNode) + /// + /// Sets the node style to show that it is not published + /// + /// + public static void SetNotPublishedStyle(this TreeNode treeNode) + { + if (treeNode.CssClasses.Contains("not-published") == false) { - if (treeNode.CssClasses.Contains("not-published") == false) - { - treeNode.CssClasses.Add("not-published"); - } + treeNode.CssClasses.Add("not-published"); } } } diff --git a/src/Umbraco.Core/Trees/TreeUse.cs b/src/Umbraco.Core/Trees/TreeUse.cs index 55be24d54db9..65f8729c3840 100644 --- a/src/Umbraco.Core/Trees/TreeUse.cs +++ b/src/Umbraco.Core/Trees/TreeUse.cs @@ -1,26 +1,23 @@ -using System; +namespace Umbraco.Cms.Core.Trees; -namespace Umbraco.Cms.Core.Trees +/// +/// Defines tree uses. +/// +[Flags] +public enum TreeUse { /// - /// Defines tree uses. + /// The tree is not used. /// - [Flags] - public enum TreeUse - { - /// - /// The tree is not used. - /// - None = 0, + None = 0, - /// - /// The tree is used as a main (section) tree. - /// - Main = 1, + /// + /// The tree is used as a main (section) tree. + /// + Main = 1, - /// - /// The tree is used as a dialog. - /// - Dialog = 2, - } + /// + /// The tree is used as a dialog. + /// + Dialog = 2 } diff --git a/src/Umbraco.Core/Udi.cs b/src/Umbraco.Core/Udi.cs index 2e141e2e6686..857c95b99f4f 100644 --- a/src/Umbraco.Core/Udi.cs +++ b/src/Umbraco.Core/Udi.cs @@ -1,171 +1,187 @@ -using System; using System.ComponentModel; -using System.Linq; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Represents an entity identifier. +/// +/// An Udi can be fully qualified or "closed" eg umb://document/{guid} or "open" eg umb://document. +[TypeConverter(typeof(UdiTypeConverter))] +public abstract class Udi : IComparable { + /// + /// Initializes a new instance of the Udi class. + /// + /// The entity type part of the identifier. + /// The string value of the identifier. + protected Udi(string entityType, string stringValue) + { + EntityType = entityType; + UriValue = new Uri(stringValue); + } /// - /// Represents an entity identifier. + /// Initializes a new instance of the Udi class. /// - /// An Udi can be fully qualified or "closed" eg umb://document/{guid} or "open" eg umb://document. - [TypeConverter(typeof(UdiTypeConverter))] - public abstract class Udi : IComparable + /// The uri value of the identifier. + protected Udi(Uri uriValue) { - public Uri UriValue { get; } - - /// - /// Initializes a new instance of the Udi class. - /// - /// The entity type part of the identifier. - /// The string value of the identifier. - protected Udi(string entityType, string stringValue) - { - EntityType = entityType; - UriValue = new Uri(stringValue); - } + EntityType = uriValue.Host; + UriValue = uriValue; + } - /// - /// Initializes a new instance of the Udi class. - /// - /// The uri value of the identifier. - protected Udi(Uri uriValue) - { - EntityType = uriValue.Host; - UriValue = uriValue; - } + public Uri UriValue { get; } + /// + /// Gets the entity type part of the identifier. + /// + public string EntityType { get; } - /// - /// Gets the entity type part of the identifier. - /// - public string EntityType { get; private set; } + /// + /// Gets a value indicating whether this Udi is a root Udi. + /// + /// A root Udi points to the "root of all things" for a given entity type, e.g. the content tree root. + public abstract bool IsRoot { get; } - public int CompareTo(Udi? other) - { - return string.Compare(UriValue.ToString(), other?.UriValue.ToString(), StringComparison.OrdinalIgnoreCase); - } + public int CompareTo(Udi? other) => string.Compare(UriValue.ToString(), other?.UriValue.ToString(), + StringComparison.OrdinalIgnoreCase); - public override string ToString() - { - // UriValue is created in the ctor and is never null - // use AbsoluteUri here and not ToString else it's not encoded! - return UriValue.AbsoluteUri; - } + public override string ToString() => + // UriValue is created in the ctor and is never null + // use AbsoluteUri here and not ToString else it's not encoded! + UriValue.AbsoluteUri; + /// + /// Creates a root Udi for an entity type. + /// + /// The entity type. + /// The root Udi for the entity type. + public static Udi Create(string entityType) => UdiParser.GetRootUdi(entityType); - /// - /// Creates a root Udi for an entity type. - /// - /// The entity type. - /// The root Udi for the entity type. - public static Udi Create(string entityType) + /// + /// Creates a string Udi. + /// + /// The entity type. + /// The identifier. + /// The string Udi for the entity type and identifier. + public static Udi Create(string entityType, string id) + { + if (UdiParser.UdiTypes.TryGetValue(entityType, out UdiType udiType) == false) { - return UdiParser.GetRootUdi(entityType); + throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType), "entityType"); } - /// - /// Creates a string Udi. - /// - /// The entity type. - /// The identifier. - /// The string Udi for the entity type and identifier. - public static Udi Create(string entityType, string id) + if (string.IsNullOrWhiteSpace(id)) { - if (UdiParser.UdiTypes.TryGetValue(entityType, out var udiType) == false) - throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType), "entityType"); - - if (string.IsNullOrWhiteSpace(id)) - throw new ArgumentException("Value cannot be null or whitespace.", "id"); - if (udiType != UdiType.StringUdi) - throw new InvalidOperationException(string.Format("Entity type \"{0}\" does not have string udis.", entityType)); - - return new StringUdi(entityType, id); + throw new ArgumentException("Value cannot be null or whitespace.", "id"); } - /// - /// Creates a Guid Udi. - /// - /// The entity type. - /// The identifier. - /// The Guid Udi for the entity type and identifier. - public static Udi Create(string? entityType, Guid id) + if (udiType != UdiType.StringUdi) { - if (entityType is null || UdiParser.UdiTypes.TryGetValue(entityType, out var udiType) == false) - throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType), "entityType"); + throw new InvalidOperationException(string.Format("Entity type \"{0}\" does not have string udis.", + entityType)); + } + + return new StringUdi(entityType, id); + } - if (udiType != UdiType.GuidUdi) - throw new InvalidOperationException(string.Format("Entity type \"{0}\" does not have guid udis.", entityType)); - if (id == default(Guid)) - throw new ArgumentException("Cannot be an empty guid.", "id"); + /// + /// Creates a Guid Udi. + /// + /// The entity type. + /// The identifier. + /// The Guid Udi for the entity type and identifier. + public static Udi Create(string? entityType, Guid id) + { + if (entityType is null || UdiParser.UdiTypes.TryGetValue(entityType, out UdiType udiType) == false) + { + throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType), "entityType"); + } - return new GuidUdi(entityType, id); + if (udiType != UdiType.GuidUdi) + { + throw new InvalidOperationException(string.Format("Entity type \"{0}\" does not have guid udis.", + entityType)); } - public static Udi Create(Uri uri) + if (id == default) { - // if it's a know type go fast and use ctors - // else fallback to parsing the string (and guess the type) + throw new ArgumentException("Cannot be an empty guid.", "id"); + } - if (UdiParser.UdiTypes.TryGetValue(uri.Host, out var udiType) == false) - throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", uri.Host), "uri"); + return new GuidUdi(entityType, id); + } - if (udiType == UdiType.GuidUdi) - return new GuidUdi(uri); - if (udiType == UdiType.StringUdi) - return new StringUdi(uri); + public static Udi Create(Uri uri) + { + // if it's a know type go fast and use ctors + // else fallback to parsing the string (and guess the type) - throw new ArgumentException(string.Format("Uri \"{0}\" is not a valid udi.", uri)); + if (UdiParser.UdiTypes.TryGetValue(uri.Host, out UdiType udiType) == false) + { + throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", uri.Host), "uri"); } - public void EnsureType(params string[] validTypes) + if (udiType == UdiType.GuidUdi) { - if (validTypes.Contains(EntityType) == false) - throw new Exception(string.Format("Unexpected entity type \"{0}\".", EntityType)); + return new GuidUdi(uri); } - /// - /// Gets a value indicating whether this Udi is a root Udi. - /// - /// A root Udi points to the "root of all things" for a given entity type, e.g. the content tree root. - public abstract bool IsRoot { get; } - - /// - /// Ensures that this Udi is not a root Udi. - /// - /// This Udi. - /// When this Udi is a Root Udi. - public Udi EnsureNotRoot() + if (udiType == UdiType.StringUdi) { - if (IsRoot) throw new Exception("Root Udi."); - return this; + return new StringUdi(uri); } - public override bool Equals(object? obj) + throw new ArgumentException(string.Format("Uri \"{0}\" is not a valid udi.", uri)); + } + + public void EnsureType(params string[] validTypes) + { + if (validTypes.Contains(EntityType) == false) { - var other = obj as Udi; - return other is not null && GetType() == other.GetType() && UriValue == other.UriValue; + throw new Exception(string.Format("Unexpected entity type \"{0}\".", EntityType)); } + } - public override int GetHashCode() + /// + /// Ensures that this Udi is not a root Udi. + /// + /// This Udi. + /// When this Udi is a Root Udi. + public Udi EnsureNotRoot() + { + if (IsRoot) { - return UriValue.GetHashCode(); + throw new Exception("Root Udi."); } - public static bool operator ==(Udi? udi1, Udi? udi2) + return this; + } + + public override bool Equals(object? obj) + { + var other = obj as Udi; + return other is not null && GetType() == other.GetType() && UriValue == other.UriValue; + } + + public override int GetHashCode() => UriValue.GetHashCode(); + + public static bool operator ==(Udi? udi1, Udi? udi2) + { + if (ReferenceEquals(udi1, udi2)) { - if (ReferenceEquals(udi1, udi2)) return true; - if ((object?)udi1 == null || (object?)udi2 == null) return false; - return udi1.Equals(udi2); + return true; } - public static bool operator !=(Udi? udi1, Udi? udi2) + if ((object?)udi1 == null || (object?)udi2 == null) { - return (udi1 == udi2) == false; + return false; } - + return udi1.Equals(udi2); } + + public static bool operator !=(Udi? udi1, Udi? udi2) => udi1 == udi2 == false; } diff --git a/src/Umbraco.Core/UdiDefinitionAttribute.cs b/src/Umbraco.Core/UdiDefinitionAttribute.cs index 9139ef418834..b9e7c865fdbc 100644 --- a/src/Umbraco.Core/UdiDefinitionAttribute.cs +++ b/src/Umbraco.Core/UdiDefinitionAttribute.cs @@ -1,20 +1,25 @@ -using System; +namespace Umbraco.Cms.Core; -namespace Umbraco.Cms.Core +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] +public sealed class UdiDefinitionAttribute : Attribute { - [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - public sealed class UdiDefinitionAttribute : Attribute + public UdiDefinitionAttribute(string entityType, UdiType udiType) { - public UdiDefinitionAttribute(string entityType, UdiType udiType) + if (string.IsNullOrWhiteSpace(entityType)) { - if (string.IsNullOrWhiteSpace(entityType)) throw new ArgumentNullException("entityType"); - if (udiType != UdiType.GuidUdi && udiType != UdiType.StringUdi) throw new ArgumentException("Invalid value.", "udiType"); - EntityType = entityType; - UdiType = udiType; + throw new ArgumentNullException("entityType"); } - public string EntityType { get; private set; } + if (udiType != UdiType.GuidUdi && udiType != UdiType.StringUdi) + { + throw new ArgumentException("Invalid value.", "udiType"); + } - public UdiType UdiType { get; private set; } + EntityType = entityType; + UdiType = udiType; } + + public string EntityType { get; } + + public UdiType UdiType { get; } } diff --git a/src/Umbraco.Core/UdiEntityTypeHelper.cs b/src/Umbraco.Core/UdiEntityTypeHelper.cs index 781c084785de..f0e8774cf89b 100644 --- a/src/Umbraco.Core/UdiEntityTypeHelper.cs +++ b/src/Umbraco.Core/UdiEntityTypeHelper.cs @@ -1,102 +1,98 @@ -using System; using Umbraco.Cms.Core.Models; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public static class UdiEntityTypeHelper { - public static class UdiEntityTypeHelper + public static string FromUmbracoObjectType(UmbracoObjectTypes umbracoObjectType) { - - - public static string FromUmbracoObjectType(UmbracoObjectTypes umbracoObjectType) + switch (umbracoObjectType) { - switch (umbracoObjectType) - { - case UmbracoObjectTypes.Document: - return Constants.UdiEntityType.Document; - case UmbracoObjectTypes.DocumentBlueprint: - return Constants.UdiEntityType.DocumentBlueprint; - case UmbracoObjectTypes.Media: - return Constants.UdiEntityType.Media; - case UmbracoObjectTypes.Member: - return Constants.UdiEntityType.Member; - case UmbracoObjectTypes.Template: - return Constants.UdiEntityType.Template; - case UmbracoObjectTypes.DocumentType: - return Constants.UdiEntityType.DocumentType; - case UmbracoObjectTypes.DocumentTypeContainer: - return Constants.UdiEntityType.DocumentTypeContainer; - case UmbracoObjectTypes.MediaType: - return Constants.UdiEntityType.MediaType; - case UmbracoObjectTypes.MediaTypeContainer: - return Constants.UdiEntityType.MediaTypeContainer; - case UmbracoObjectTypes.DataType: - return Constants.UdiEntityType.DataType; - case UmbracoObjectTypes.DataTypeContainer: - return Constants.UdiEntityType.DataTypeContainer; - case UmbracoObjectTypes.MemberType: - return Constants.UdiEntityType.MemberType; - case UmbracoObjectTypes.MemberGroup: - return Constants.UdiEntityType.MemberGroup; - case UmbracoObjectTypes.RelationType: - return Constants.UdiEntityType.RelationType; - case UmbracoObjectTypes.FormsForm: - return Constants.UdiEntityType.FormsForm; - case UmbracoObjectTypes.FormsPreValue: - return Constants.UdiEntityType.FormsPreValue; - case UmbracoObjectTypes.FormsDataSource: - return Constants.UdiEntityType.FormsDataSource; - case UmbracoObjectTypes.Language: - return Constants.UdiEntityType.Language; - } - - throw new NotSupportedException( - $"UmbracoObjectType \"{umbracoObjectType}\" does not have a matching EntityType."); + case UmbracoObjectTypes.Document: + return Constants.UdiEntityType.Document; + case UmbracoObjectTypes.DocumentBlueprint: + return Constants.UdiEntityType.DocumentBlueprint; + case UmbracoObjectTypes.Media: + return Constants.UdiEntityType.Media; + case UmbracoObjectTypes.Member: + return Constants.UdiEntityType.Member; + case UmbracoObjectTypes.Template: + return Constants.UdiEntityType.Template; + case UmbracoObjectTypes.DocumentType: + return Constants.UdiEntityType.DocumentType; + case UmbracoObjectTypes.DocumentTypeContainer: + return Constants.UdiEntityType.DocumentTypeContainer; + case UmbracoObjectTypes.MediaType: + return Constants.UdiEntityType.MediaType; + case UmbracoObjectTypes.MediaTypeContainer: + return Constants.UdiEntityType.MediaTypeContainer; + case UmbracoObjectTypes.DataType: + return Constants.UdiEntityType.DataType; + case UmbracoObjectTypes.DataTypeContainer: + return Constants.UdiEntityType.DataTypeContainer; + case UmbracoObjectTypes.MemberType: + return Constants.UdiEntityType.MemberType; + case UmbracoObjectTypes.MemberGroup: + return Constants.UdiEntityType.MemberGroup; + case UmbracoObjectTypes.RelationType: + return Constants.UdiEntityType.RelationType; + case UmbracoObjectTypes.FormsForm: + return Constants.UdiEntityType.FormsForm; + case UmbracoObjectTypes.FormsPreValue: + return Constants.UdiEntityType.FormsPreValue; + case UmbracoObjectTypes.FormsDataSource: + return Constants.UdiEntityType.FormsDataSource; + case UmbracoObjectTypes.Language: + return Constants.UdiEntityType.Language; } - public static UmbracoObjectTypes ToUmbracoObjectType(string entityType) - { - switch (entityType) - { - case Constants.UdiEntityType.Document: - return UmbracoObjectTypes.Document; - case Constants.UdiEntityType.DocumentBlueprint: - return UmbracoObjectTypes.DocumentBlueprint; - case Constants.UdiEntityType.Media: - return UmbracoObjectTypes.Media; - case Constants.UdiEntityType.Member: - return UmbracoObjectTypes.Member; - case Constants.UdiEntityType.Template: - return UmbracoObjectTypes.Template; - case Constants.UdiEntityType.DocumentType: - return UmbracoObjectTypes.DocumentType; - case Constants.UdiEntityType.DocumentTypeContainer: - return UmbracoObjectTypes.DocumentTypeContainer; - case Constants.UdiEntityType.MediaType: - return UmbracoObjectTypes.MediaType; - case Constants.UdiEntityType.MediaTypeContainer: - return UmbracoObjectTypes.MediaTypeContainer; - case Constants.UdiEntityType.DataType: - return UmbracoObjectTypes.DataType; - case Constants.UdiEntityType.DataTypeContainer: - return UmbracoObjectTypes.DataTypeContainer; - case Constants.UdiEntityType.MemberType: - return UmbracoObjectTypes.MemberType; - case Constants.UdiEntityType.MemberGroup: - return UmbracoObjectTypes.MemberGroup; - case Constants.UdiEntityType.RelationType: - return UmbracoObjectTypes.RelationType; - case Constants.UdiEntityType.FormsForm: - return UmbracoObjectTypes.FormsForm; - case Constants.UdiEntityType.FormsPreValue: - return UmbracoObjectTypes.FormsPreValue; - case Constants.UdiEntityType.FormsDataSource: - return UmbracoObjectTypes.FormsDataSource; - case Constants.UdiEntityType.Language: - return UmbracoObjectTypes.Language; - } + throw new NotSupportedException( + $"UmbracoObjectType \"{umbracoObjectType}\" does not have a matching EntityType."); + } - throw new NotSupportedException( - $"EntityType \"{entityType}\" does not have a matching UmbracoObjectType."); + public static UmbracoObjectTypes ToUmbracoObjectType(string entityType) + { + switch (entityType) + { + case Constants.UdiEntityType.Document: + return UmbracoObjectTypes.Document; + case Constants.UdiEntityType.DocumentBlueprint: + return UmbracoObjectTypes.DocumentBlueprint; + case Constants.UdiEntityType.Media: + return UmbracoObjectTypes.Media; + case Constants.UdiEntityType.Member: + return UmbracoObjectTypes.Member; + case Constants.UdiEntityType.Template: + return UmbracoObjectTypes.Template; + case Constants.UdiEntityType.DocumentType: + return UmbracoObjectTypes.DocumentType; + case Constants.UdiEntityType.DocumentTypeContainer: + return UmbracoObjectTypes.DocumentTypeContainer; + case Constants.UdiEntityType.MediaType: + return UmbracoObjectTypes.MediaType; + case Constants.UdiEntityType.MediaTypeContainer: + return UmbracoObjectTypes.MediaTypeContainer; + case Constants.UdiEntityType.DataType: + return UmbracoObjectTypes.DataType; + case Constants.UdiEntityType.DataTypeContainer: + return UmbracoObjectTypes.DataTypeContainer; + case Constants.UdiEntityType.MemberType: + return UmbracoObjectTypes.MemberType; + case Constants.UdiEntityType.MemberGroup: + return UmbracoObjectTypes.MemberGroup; + case Constants.UdiEntityType.RelationType: + return UmbracoObjectTypes.RelationType; + case Constants.UdiEntityType.FormsForm: + return UmbracoObjectTypes.FormsForm; + case Constants.UdiEntityType.FormsPreValue: + return UmbracoObjectTypes.FormsPreValue; + case Constants.UdiEntityType.FormsDataSource: + return UmbracoObjectTypes.FormsDataSource; + case Constants.UdiEntityType.Language: + return UmbracoObjectTypes.Language; } + + throw new NotSupportedException( + $"EntityType \"{entityType}\" does not have a matching UmbracoObjectType."); } } diff --git a/src/Umbraco.Core/UdiParser.cs b/src/Umbraco.Core/UdiParser.cs index 907880db1357..966fdbe44ce0 100644 --- a/src/Umbraco.Core/UdiParser.cs +++ b/src/Umbraco.Core/UdiParser.cs @@ -1,222 +1,235 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; +using System.Collections.Concurrent; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public sealed class UdiParser { - public sealed class UdiParser + private static readonly ConcurrentDictionary RootUdis = new(); + + static UdiParser() => + // initialize with known (built-in) Udi types + // we will add scanned types later on + UdiTypes = new ConcurrentDictionary(GetKnownUdiTypes()); + + internal static ConcurrentDictionary UdiTypes { get; private set; } + + /// + /// Internal API for tests to resets all udi types back to only the known udi types. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static void ResetUdiTypes() => UdiTypes = new ConcurrentDictionary(GetKnownUdiTypes()); + + /// + /// Converts the string representation of an entity identifier into the equivalent Udi instance. + /// + /// The string to convert. + /// An Udi instance that contains the value that was parsed. + public static Udi Parse(string s) { - private static readonly ConcurrentDictionary RootUdis = new ConcurrentDictionary(); - internal static ConcurrentDictionary UdiTypes { get; private set; } + ParseInternal(s, false, false, out Udi udi); + return udi!; + } - static UdiParser() - { - // initialize with known (built-in) Udi types - // we will add scanned types later on - UdiTypes = new ConcurrentDictionary(GetKnownUdiTypes()); - } + /// + /// Converts the string representation of an entity identifier into the equivalent Udi instance. + /// + /// The string to convert. + /// A value indicating whether to only deal with known types. + /// An Udi instance that contains the value that was parsed. + /// + /// + /// If is true, and the string could not be parsed because + /// the entity type was not known, the method succeeds but sets udito an + /// value. + /// + /// + /// If is true, assemblies are not scanned for types, + /// and therefore only builtin types may be known. Unless scanning already took place. + /// + /// + public static Udi Parse(string s, bool knownTypes) + { + ParseInternal(s, false, knownTypes, out Udi udi); + return udi!; + } - /// - /// Internal API for tests to resets all udi types back to only the known udi types. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public static void ResetUdiTypes() + /// + /// Converts the string representation of an entity identifier into the equivalent Udi instance. + /// + /// The string to convert. + /// An Udi instance that contains the value that was parsed. + /// A boolean value indicating whether the string could be parsed. + public static bool TryParse(string s, [MaybeNullWhen(false)] out Udi udi) => ParseInternal(s, true, false, out udi); + + /// + /// Converts the string representation of an entity identifier into the equivalent Udi instance. + /// + /// The string to convert. + /// An Udi instance that contains the value that was parsed. + /// A boolean value indicating whether the string could be parsed. + public static bool TryParse(string? s, [MaybeNullWhen(false)] out T udi) + where T : Udi? + { + var result = ParseInternal(s, true, false, out Udi parsed); + if (result && parsed is T) { - UdiTypes = new ConcurrentDictionary(GetKnownUdiTypes()); + udi = (T)parsed; + return true; } - /// - /// Converts the string representation of an entity identifier into the equivalent Udi instance. - /// - /// The string to convert. - /// An Udi instance that contains the value that was parsed. - public static Udi Parse(string s) - { - ParseInternal(s, false, false, out var udi); - return udi!; - } + udi = null; + return false; + } - /// - /// Converts the string representation of an entity identifier into the equivalent Udi instance. - /// - /// The string to convert. - /// A value indicating whether to only deal with known types. - /// An Udi instance that contains the value that was parsed. - /// - /// If is true, and the string could not be parsed because - /// the entity type was not known, the method succeeds but sets udito an - /// value. - /// If is true, assemblies are not scanned for types, - /// and therefore only builtin types may be known. Unless scanning already took place. - /// - public static Udi Parse(string s, bool knownTypes) + /// + /// Converts the string representation of an entity identifier into the equivalent Udi instance. + /// + /// The string to convert. + /// A value indicating whether to only deal with known types. + /// An Udi instance that contains the value that was parsed. + /// A boolean value indicating whether the string could be parsed. + /// + /// + /// If is true, and the string could not be parsed because + /// the entity type was not known, the method returns false but still sets udi + /// to an value. + /// + /// + /// If is true, assemblies are not scanned for types, + /// and therefore only builtin types may be known. Unless scanning already took place. + /// + /// + public static bool TryParse(string? s, bool knownTypes, [MaybeNullWhen(false)] out Udi udi) => + ParseInternal(s, true, knownTypes, out udi); + + private static bool ParseInternal(string? s, bool tryParse, bool knownTypes, [MaybeNullWhen(false)] out Udi udi) + { + udi = null; + if (Uri.IsWellFormedUriString(s, UriKind.Absolute) == false + || Uri.TryCreate(s, UriKind.Absolute, out Uri uri) == false) { - ParseInternal(s, false, knownTypes, out var udi); - return udi!; - } + if (tryParse) + { + return false; + } - /// - /// Converts the string representation of an entity identifier into the equivalent Udi instance. - /// - /// The string to convert. - /// An Udi instance that contains the value that was parsed. - /// A boolean value indicating whether the string could be parsed. - public static bool TryParse(string s, [MaybeNullWhen(returnValue: false)] out Udi udi) - { - return ParseInternal(s, true, false, out udi); + throw new FormatException(string.Format("String \"{0}\" is not a valid udi.", s)); } - /// - /// Converts the string representation of an entity identifier into the equivalent Udi instance. - /// - /// The string to convert. - /// An Udi instance that contains the value that was parsed. - /// A boolean value indicating whether the string could be parsed. - public static bool TryParse(string? s, [MaybeNullWhen(returnValue: false)] out T udi) - where T : Udi? + var entityType = uri.Host; + if (UdiTypes.TryGetValue(entityType, out UdiType udiType) == false) { - var result = ParseInternal(s, true, false, out var parsed); - if (result && parsed is T) + if (knownTypes) { - udi = (T)parsed; - return true; + // not knowing the type is not an error + // just return the unknown type udi + udi = UnknownTypeUdi.Instance; + return false; } - udi = null; - return false; - } + if (tryParse) + { + return false; + } - /// - /// Converts the string representation of an entity identifier into the equivalent Udi instance. - /// - /// The string to convert. - /// A value indicating whether to only deal with known types. - /// An Udi instance that contains the value that was parsed. - /// A boolean value indicating whether the string could be parsed. - /// - /// If is true, and the string could not be parsed because - /// the entity type was not known, the method returns false but still sets udi - /// to an value. - /// If is true, assemblies are not scanned for types, - /// and therefore only builtin types may be known. Unless scanning already took place. - /// - public static bool TryParse(string? s, bool knownTypes, [MaybeNullWhen(returnValue: false)] out Udi udi) - { - return ParseInternal(s, true, knownTypes, out udi); + throw new FormatException(string.Format("Unknown entity type \"{0}\".", entityType)); } - private static bool ParseInternal(string? s, bool tryParse, bool knownTypes,[MaybeNullWhen(returnValue: false)] out Udi udi) + var path = uri.AbsolutePath.TrimStart('/'); + + if (udiType == UdiType.GuidUdi) { - udi = null; - if (Uri.IsWellFormedUriString(s, UriKind.Absolute) == false - || Uri.TryCreate(s, UriKind.Absolute, out var uri) == false) + if (path == string.Empty) { - if (tryParse) return false; - throw new FormatException(string.Format("String \"{0}\" is not a valid udi.", s)); + udi = GetRootUdi(uri.Host); + return true; } - var entityType = uri.Host; - if (UdiTypes.TryGetValue(entityType, out var udiType) == false) + if (Guid.TryParse(path, out Guid guid) == false) { - if (knownTypes) + if (tryParse) { - // not knowing the type is not an error - // just return the unknown type udi - udi = UnknownTypeUdi.Instance; return false; } - if (tryParse) return false; - throw new FormatException(string.Format("Unknown entity type \"{0}\".", entityType)); - } - var path = uri.AbsolutePath.TrimStart('/'); - - if (udiType == UdiType.GuidUdi) - { - if (path == string.Empty) - { - udi = GetRootUdi(uri.Host); - return true; - } - if (Guid.TryParse(path, out var guid) == false) - { - if (tryParse) return false; - throw new FormatException(string.Format("String \"{0}\" is not a valid udi.", s)); - } - udi = new GuidUdi(uri.Host, guid); - return true; + throw new FormatException(string.Format("String \"{0}\" is not a valid udi.", s)); } - if (udiType == UdiType.StringUdi) - { - udi = path == string.Empty ? GetRootUdi(uri.Host) : new StringUdi(uri.Host, Uri.UnescapeDataString(path)); - return true; - } + udi = new GuidUdi(uri.Host, guid); + return true; + } - if (tryParse) return false; - throw new InvalidOperationException(string.Format("Invalid udi type \"{0}\".", udiType)); + if (udiType == UdiType.StringUdi) + { + udi = path == string.Empty ? GetRootUdi(uri.Host) : new StringUdi(uri.Host, Uri.UnescapeDataString(path)); + return true; } - internal static Udi GetRootUdi(string entityType) + if (tryParse) { - return RootUdis.GetOrAdd(entityType, x => - { - if (UdiTypes.TryGetValue(x, out var udiType) == false) - throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType)); - return udiType == UdiType.StringUdi - ? (Udi)new StringUdi(entityType, string.Empty) - : new GuidUdi(entityType, Guid.Empty); - }); + return false; } + throw new InvalidOperationException(string.Format("Invalid udi type \"{0}\".", udiType)); + } + + internal static Udi GetRootUdi(string entityType) => + RootUdis.GetOrAdd(entityType, x => + { + if (UdiTypes.TryGetValue(x, out UdiType udiType) == false) + { + throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType)); + } + + return udiType == UdiType.StringUdi + ? new StringUdi(entityType, string.Empty) + : new GuidUdi(entityType, Guid.Empty); + }); - /// - /// Registers a custom entity type. - /// - /// - /// - public static void RegisterUdiType(string entityType, UdiType udiType) => UdiTypes.TryAdd(entityType, udiType); + /// + /// Registers a custom entity type. + /// + /// + /// + public static void RegisterUdiType(string entityType, UdiType udiType) => UdiTypes.TryAdd(entityType, udiType); - public static Dictionary GetKnownUdiTypes() => - new Dictionary - { - { Constants.UdiEntityType.Unknown, UdiType.Unknown }, - - { Constants.UdiEntityType.AnyGuid, UdiType.GuidUdi }, - { Constants.UdiEntityType.Element, UdiType.GuidUdi }, - { Constants.UdiEntityType.Document, UdiType.GuidUdi }, - { Constants.UdiEntityType.DocumentBlueprint, UdiType.GuidUdi }, - { Constants.UdiEntityType.Media, UdiType.GuidUdi }, - { Constants.UdiEntityType.Member, UdiType.GuidUdi }, - { Constants.UdiEntityType.DictionaryItem, UdiType.GuidUdi }, - { Constants.UdiEntityType.Macro, UdiType.GuidUdi }, - { Constants.UdiEntityType.Template, UdiType.GuidUdi }, - { Constants.UdiEntityType.DocumentType, UdiType.GuidUdi }, - { Constants.UdiEntityType.DocumentTypeContainer, UdiType.GuidUdi }, - { Constants.UdiEntityType.DocumentTypeBluePrints, UdiType.GuidUdi }, - { Constants.UdiEntityType.MediaType, UdiType.GuidUdi }, - { Constants.UdiEntityType.MediaTypeContainer, UdiType.GuidUdi }, - { Constants.UdiEntityType.DataType, UdiType.GuidUdi }, - { Constants.UdiEntityType.DataTypeContainer, UdiType.GuidUdi }, - { Constants.UdiEntityType.MemberType, UdiType.GuidUdi }, - { Constants.UdiEntityType.MemberGroup, UdiType.GuidUdi }, - { Constants.UdiEntityType.RelationType, UdiType.GuidUdi }, - { Constants.UdiEntityType.FormsForm, UdiType.GuidUdi }, - { Constants.UdiEntityType.FormsPreValue, UdiType.GuidUdi }, - { Constants.UdiEntityType.FormsDataSource, UdiType.GuidUdi }, - - { Constants.UdiEntityType.AnyString, UdiType.StringUdi }, - { Constants.UdiEntityType.Language, UdiType.StringUdi }, - { Constants.UdiEntityType.MacroScript, UdiType.StringUdi }, - { Constants.UdiEntityType.MediaFile, UdiType.StringUdi }, - { Constants.UdiEntityType.TemplateFile, UdiType.StringUdi }, - { Constants.UdiEntityType.Script, UdiType.StringUdi }, - { Constants.UdiEntityType.PartialView, UdiType.StringUdi }, - { Constants.UdiEntityType.PartialViewMacro, UdiType.StringUdi }, - { Constants.UdiEntityType.Stylesheet, UdiType.StringUdi } - }; - } + public static Dictionary GetKnownUdiTypes() => + new() + { + {Constants.UdiEntityType.Unknown, UdiType.Unknown}, + {Constants.UdiEntityType.AnyGuid, UdiType.GuidUdi}, + {Constants.UdiEntityType.Element, UdiType.GuidUdi}, + {Constants.UdiEntityType.Document, UdiType.GuidUdi}, + {Constants.UdiEntityType.DocumentBlueprint, UdiType.GuidUdi}, + {Constants.UdiEntityType.Media, UdiType.GuidUdi}, + {Constants.UdiEntityType.Member, UdiType.GuidUdi}, + {Constants.UdiEntityType.DictionaryItem, UdiType.GuidUdi}, + {Constants.UdiEntityType.Macro, UdiType.GuidUdi}, + {Constants.UdiEntityType.Template, UdiType.GuidUdi}, + {Constants.UdiEntityType.DocumentType, UdiType.GuidUdi}, + {Constants.UdiEntityType.DocumentTypeContainer, UdiType.GuidUdi}, + {Constants.UdiEntityType.DocumentTypeBluePrints, UdiType.GuidUdi}, + {Constants.UdiEntityType.MediaType, UdiType.GuidUdi}, + {Constants.UdiEntityType.MediaTypeContainer, UdiType.GuidUdi}, + {Constants.UdiEntityType.DataType, UdiType.GuidUdi}, + {Constants.UdiEntityType.DataTypeContainer, UdiType.GuidUdi}, + {Constants.UdiEntityType.MemberType, UdiType.GuidUdi}, + {Constants.UdiEntityType.MemberGroup, UdiType.GuidUdi}, + {Constants.UdiEntityType.RelationType, UdiType.GuidUdi}, + {Constants.UdiEntityType.FormsForm, UdiType.GuidUdi}, + {Constants.UdiEntityType.FormsPreValue, UdiType.GuidUdi}, + {Constants.UdiEntityType.FormsDataSource, UdiType.GuidUdi}, + {Constants.UdiEntityType.AnyString, UdiType.StringUdi}, + {Constants.UdiEntityType.Language, UdiType.StringUdi}, + {Constants.UdiEntityType.MacroScript, UdiType.StringUdi}, + {Constants.UdiEntityType.MediaFile, UdiType.StringUdi}, + {Constants.UdiEntityType.TemplateFile, UdiType.StringUdi}, + {Constants.UdiEntityType.Script, UdiType.StringUdi}, + {Constants.UdiEntityType.PartialView, UdiType.StringUdi}, + {Constants.UdiEntityType.PartialViewMacro, UdiType.StringUdi}, + {Constants.UdiEntityType.Stylesheet, UdiType.StringUdi} + }; } diff --git a/src/Umbraco.Core/UdiParserServiceConnectors.cs b/src/Umbraco.Core/UdiParserServiceConnectors.cs index 320cc9a90193..f6dfd2df7570 100644 --- a/src/Umbraco.Core/UdiParserServiceConnectors.cs +++ b/src/Umbraco.Core/UdiParserServiceConnectors.cs @@ -1,82 +1,99 @@ -using System; -using System.Collections.Generic; -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Deploy; using Umbraco.Extensions; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public static class UdiParserServiceConnectors { - public static class UdiParserServiceConnectors - { - // notes - see U4-10409 - // if this class is used during application pre-start it cannot scans the assemblies, - // this is addressed by lazily-scanning, with the following caveats: - // - parsing a root udi still requires a scan and therefore still breaks - // - parsing an invalid udi ("umb://should-be-guid/") corrupts KnowUdiTypes + // notes - see U4-10409 + // if this class is used during application pre-start it cannot scans the assemblies, + // this is addressed by lazily-scanning, with the following caveats: + // - parsing a root udi still requires a scan and therefore still breaks + // - parsing an invalid udi ("umb://should-be-guid/") corrupts KnowUdiTypes - private static volatile bool _scanned; - private static readonly object ScanLocker = new object(); + private static volatile bool _scanned; + private static readonly object ScanLocker = new(); - /// - /// Scan for deploy in assemblies for known UDI types. - /// - /// - public static void ScanDeployServiceConnectorsForUdiTypes(TypeLoader typeLoader) + /// + /// Scan for deploy in assemblies for known UDI types. + /// + /// + public static void ScanDeployServiceConnectorsForUdiTypes(TypeLoader typeLoader) + { + if (typeLoader is null) { - if (typeLoader is null) - throw new ArgumentNullException(nameof(typeLoader)); + throw new ArgumentNullException(nameof(typeLoader)); + } - if (_scanned) return; + if (_scanned) + { + return; + } - lock (ScanLocker) + lock (ScanLocker) + { + // Scan for unknown UDI types + // there is no way we can get the "registered" service connectors, as registration + // happens in Deploy, not in Core, and the Udi class belongs to Core - therefore, we + // just pick every service connectors - just making sure that not two of them + // would register the same entity type, with different udi types (would not make + // much sense anyways) + IEnumerable connectors = typeLoader.GetTypes(); + var result = new Dictionary(); + foreach (Type connector in connectors) { - // Scan for unknown UDI types - // there is no way we can get the "registered" service connectors, as registration - // happens in Deploy, not in Core, and the Udi class belongs to Core - therefore, we - // just pick every service connectors - just making sure that not two of them - // would register the same entity type, with different udi types (would not make - // much sense anyways) - var connectors = typeLoader.GetTypes(); - var result = new Dictionary(); - foreach (var connector in connectors) + IEnumerable + attrs = connector.GetCustomAttributes(false); + foreach (UdiDefinitionAttribute attr in attrs) { - var attrs = connector.GetCustomAttributes(false); - foreach (var attr in attrs) + if (result.TryGetValue(attr.EntityType, out UdiType udiType) && udiType != attr.UdiType) { - if (result.TryGetValue(attr.EntityType, out var udiType) && udiType != attr.UdiType) - throw new Exception(string.Format("Entity type \"{0}\" is declared by more than one IServiceConnector, with different UdiTypes.", attr.EntityType)); - result[attr.EntityType] = attr.UdiType; + throw new Exception(string.Format( + "Entity type \"{0}\" is declared by more than one IServiceConnector, with different UdiTypes.", + attr.EntityType)); } - } - // merge these into the known list - foreach (var item in result) - UdiParser.RegisterUdiType(item.Key, item.Value); + result[attr.EntityType] = attr.UdiType; + } + } - _scanned = true; + // merge these into the known list + foreach (KeyValuePair item in result) + { + UdiParser.RegisterUdiType(item.Key, item.Value); } + + _scanned = true; } + } - /// - /// Registers a single to add it's UDI type. - /// - /// - public static void RegisterServiceConnector() - where T: IServiceConnector + /// + /// Registers a single to add it's UDI type. + /// + /// + public static void RegisterServiceConnector() + where T : IServiceConnector + { + var result = new Dictionary(); + Type connector = typeof(T); + IEnumerable attrs = connector.GetCustomAttributes(false); + foreach (UdiDefinitionAttribute attr in attrs) { - var result = new Dictionary(); - var connector = typeof(T); - var attrs = connector.GetCustomAttributes(false); - foreach (var attr in attrs) + if (result.TryGetValue(attr.EntityType, out UdiType udiType) && udiType != attr.UdiType) { - if (result.TryGetValue(attr.EntityType, out var udiType) && udiType != attr.UdiType) - throw new Exception(string.Format("Entity type \"{0}\" is declared by more than one IServiceConnector, with different UdiTypes.", attr.EntityType)); - result[attr.EntityType] = attr.UdiType; + throw new Exception(string.Format( + "Entity type \"{0}\" is declared by more than one IServiceConnector, with different UdiTypes.", + attr.EntityType)); } - // merge these into the known list - foreach (var item in result) - UdiParser.RegisterUdiType(item.Key, item.Value); + result[attr.EntityType] = attr.UdiType; + } + + // merge these into the known list + foreach (KeyValuePair item in result) + { + UdiParser.RegisterUdiType(item.Key, item.Value); } } } diff --git a/src/Umbraco.Core/UdiRange.cs b/src/Umbraco.Core/UdiRange.cs index ca5b07bf36db..263b39acc615 100644 --- a/src/Umbraco.Core/UdiRange.cs +++ b/src/Umbraco.Core/UdiRange.cs @@ -1,103 +1,98 @@ -using System; +namespace Umbraco.Cms.Core; -namespace Umbraco.Cms.Core +/// +/// Represents a range. +/// +/// +/// +/// A Udi range is composed of a which represents the base of the range, +/// plus a selector that can be "." (the Udi), ".*" (the Udi and its children), ".**" (the udi and +/// its descendants, "*" (the children of the Udi), and "**" (the descendants of the Udi). +/// +/// The Udi here can be a closed entity, or an open entity. +/// +public class UdiRange { + private readonly Uri _uriValue; + /// - /// Represents a range. + /// Initializes a new instance of the class with a and an optional + /// selector. /// - /// - /// A Udi range is composed of a which represents the base of the range, - /// plus a selector that can be "." (the Udi), ".*" (the Udi and its children), ".**" (the udi and - /// its descendants, "*" (the children of the Udi), and "**" (the descendants of the Udi). - /// The Udi here can be a closed entity, or an open entity. - public class UdiRange + /// A . + /// An optional selector. + public UdiRange(Udi udi, string selector = Constants.DeploySelector.This) { - private readonly Uri _uriValue; - - /// - /// Initializes a new instance of the class with a and an optional selector. - /// - /// A . - /// An optional selector. - public UdiRange(Udi udi, string selector = Constants.DeploySelector.This) + Udi = udi; + switch (selector) { - Udi = udi; - switch (selector) - { - case Constants.DeploySelector.This: - Selector = selector; - _uriValue = udi.UriValue; - break; - case Constants.DeploySelector.ChildrenOfThis: - case Constants.DeploySelector.DescendantsOfThis: - case Constants.DeploySelector.ThisAndChildren: - case Constants.DeploySelector.ThisAndDescendants: - Selector = selector; - _uriValue = new Uri(Udi + "?" + selector); - break; - default: - throw new ArgumentException(string.Format("Invalid selector \"{0}\".", selector)); - } + case Constants.DeploySelector.This: + Selector = selector; + _uriValue = udi.UriValue; + break; + case Constants.DeploySelector.ChildrenOfThis: + case Constants.DeploySelector.DescendantsOfThis: + case Constants.DeploySelector.ThisAndChildren: + case Constants.DeploySelector.ThisAndDescendants: + Selector = selector; + _uriValue = new Uri(Udi + "?" + selector); + break; + default: + throw new ArgumentException(string.Format("Invalid selector \"{0}\".", selector)); } + } + + /// + /// Gets the for this range. + /// + public Udi Udi { get; } - /// - /// Gets the for this range. - /// - public Udi Udi { get; private set; } + /// + /// Gets or sets the selector for this range. + /// + public string Selector { get; } - /// - /// Gets or sets the selector for this range. - /// - public string Selector { get; private set; } + /// + /// Gets the entity type of the for this range. + /// + public string EntityType => Udi.EntityType; - /// - /// Gets the entity type of the for this range. - /// - public string EntityType - { - get { return Udi.EntityType; } - } + public static UdiRange Parse(string s) + { + Uri? uri; - public static UdiRange Parse(string s) + if (Uri.IsWellFormedUriString(s, UriKind.Absolute) == false + || Uri.TryCreate(s, UriKind.Absolute, out uri) == false) { - Uri? uri; + //if (tryParse) return false; + throw new FormatException(string.Format("String \"{0}\" is not a valid udi range.", s)); + } - if (Uri.IsWellFormedUriString(s, UriKind.Absolute) == false - || Uri.TryCreate(s, UriKind.Absolute, out uri) == false) - { - //if (tryParse) return false; - throw new FormatException(string.Format("String \"{0}\" is not a valid udi range.", s)); - } + Uri udiUri = uri.Query == string.Empty ? uri : new UriBuilder(uri) {Query = string.Empty}.Uri; + return new UdiRange(Udi.Create(udiUri), uri.Query.TrimStart(Constants.CharArrays.QuestionMark)); + } - var udiUri = uri.Query == string.Empty ? uri : new UriBuilder(uri) { Query = string.Empty }.Uri; - return new UdiRange(Udi.Create(udiUri), uri.Query.TrimStart(Constants.CharArrays.QuestionMark)); - } + public override string ToString() => _uriValue.ToString(); - public override string ToString() - { - return _uriValue.ToString(); - } + public override bool Equals(object? obj) => + obj is UdiRange other && GetType() == other.GetType() && _uriValue == other._uriValue; - public override bool Equals(object? obj) - { - return obj is UdiRange other && GetType() == other.GetType() && _uriValue == other._uriValue; - } + public override int GetHashCode() => _uriValue.GetHashCode(); - public override int GetHashCode() + public static bool operator ==(UdiRange range1, UdiRange range2) + { + if (ReferenceEquals(range1, range2)) { - return _uriValue.GetHashCode(); + return true; } - public static bool operator ==(UdiRange range1, UdiRange range2) + if ((object)range1 == null || (object)range2 == null) { - if (ReferenceEquals(range1, range2)) return true; - if ((object)range1 == null || (object)range2 == null) return false; - return range1.Equals(range2); + return false; } - public static bool operator !=(UdiRange range1, UdiRange range2) - { - return !(range1 == range2); - } + return range1.Equals(range2); } + + public static bool operator !=(UdiRange range1, UdiRange range2) => !(range1 == range2); } diff --git a/src/Umbraco.Core/UdiType.cs b/src/Umbraco.Core/UdiType.cs index 572c36de952f..d71463a55402 100644 --- a/src/Umbraco.Core/UdiType.cs +++ b/src/Umbraco.Core/UdiType.cs @@ -1,12 +1,11 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Defines Udi types. +/// +public enum UdiType { - /// - /// Defines Udi types. - /// - public enum UdiType - { - Unknown, - GuidUdi, - StringUdi - } + Unknown, + GuidUdi, + StringUdi } diff --git a/src/Umbraco.Core/UdiTypeConverter.cs b/src/Umbraco.Core/UdiTypeConverter.cs index c443b1817b11..a1d7e3c7345f 100644 --- a/src/Umbraco.Core/UdiTypeConverter.cs +++ b/src/Umbraco.Core/UdiTypeConverter.cs @@ -1,37 +1,37 @@ -using System; -using System.ComponentModel; +using System.ComponentModel; using System.Globalization; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// A custom type converter for UDI +/// +/// +/// Primarily this is used so that WebApi can auto-bind a string parameter to a UDI instance +/// +internal class UdiTypeConverter : TypeConverter { - /// - /// A custom type converter for UDI - /// - /// - /// Primarily this is used so that WebApi can auto-bind a string parameter to a UDI instance - /// - internal class UdiTypeConverter : TypeConverter + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { - public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) + if (sourceType == typeof(string)) { - if (sourceType == typeof(string)) - { - return true; - } - return base.CanConvertFrom(context, sourceType); + return true; } - public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + return base.CanConvertFrom(context, sourceType); + } + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + if (value is string) { - if (value is string) + Udi? udi; + if (UdiParser.TryParse((string)value, out udi)) { - Udi? udi; - if (UdiParser.TryParse((string)value, out udi)) - { - return udi; - } + return udi; } - return base.ConvertFrom(context, culture, value); } + + return base.ConvertFrom(context, culture, value); } } diff --git a/src/Umbraco.Core/UmbracoApiControllerTypeCollection.cs b/src/Umbraco.Core/UmbracoApiControllerTypeCollection.cs index 66ad608881b1..4643558e1365 100644 --- a/src/Umbraco.Core/UmbracoApiControllerTypeCollection.cs +++ b/src/Umbraco.Core/UmbracoApiControllerTypeCollection.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public class UmbracoApiControllerTypeCollection : BuilderCollectionBase { - public class UmbracoApiControllerTypeCollection : BuilderCollectionBase + public UmbracoApiControllerTypeCollection(Func> items) : base(items) { - public UmbracoApiControllerTypeCollection(Func> items) : base(items) - { - } } } diff --git a/src/Umbraco.Core/UmbracoContextReference.cs b/src/Umbraco.Core/UmbracoContextReference.cs index 89959c3b3266..0a7c764d4d1b 100644 --- a/src/Umbraco.Core/UmbracoContextReference.cs +++ b/src/Umbraco.Core/UmbracoContextReference.cs @@ -1,61 +1,62 @@ -using System; using Umbraco.Cms.Core.Web; -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +/// +/// Represents a reference to an instance. +/// +/// +/// +/// A reference points to an and it may own it (when it +/// is a root reference) or just reference it. A reference must be disposed after it has +/// been used. Disposing does nothing if the reference is not a root reference. Otherwise, +/// it disposes the and clears the +/// . +/// +/// +public class UmbracoContextReference : IDisposable { + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private bool _disposedValue; + /// - /// Represents a reference to an instance. + /// Initializes a new instance of the class. /// - /// - /// A reference points to an and it may own it (when it - /// is a root reference) or just reference it. A reference must be disposed after it has - /// been used. Disposing does nothing if the reference is not a root reference. Otherwise, - /// it disposes the and clears the - /// . - /// - public class UmbracoContextReference : IDisposable + public UmbracoContextReference(IUmbracoContext umbracoContext, bool isRoot, + IUmbracoContextAccessor umbracoContextAccessor) { - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private bool _disposedValue; + IsRoot = isRoot; - /// - /// Initializes a new instance of the class. - /// - public UmbracoContextReference(IUmbracoContext umbracoContext, bool isRoot, IUmbracoContextAccessor umbracoContextAccessor) - { - IsRoot = isRoot; + UmbracoContext = umbracoContext; + _umbracoContextAccessor = umbracoContextAccessor; + } - UmbracoContext = umbracoContext; - _umbracoContextAccessor = umbracoContextAccessor; - } + /// + /// Gets the . + /// + public IUmbracoContext UmbracoContext { get; } - /// - /// Gets the . - /// - public IUmbracoContext UmbracoContext { get; } + /// + /// Gets a value indicating whether the reference is a root reference. + /// + public bool IsRoot { get; } - /// - /// Gets a value indicating whether the reference is a root reference. - /// - public bool IsRoot { get; } + public void Dispose() => Dispose(true); - protected virtual void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) { - if (!_disposedValue) + if (disposing) { - if (disposing) + if (IsRoot) { - if (IsRoot) - { - UmbracoContext.Dispose(); - _umbracoContextAccessor.Clear(); - } + UmbracoContext.Dispose(); + _umbracoContextAccessor.Clear(); } - - _disposedValue = true; } - } - public void Dispose() => Dispose(disposing: true); + _disposedValue = true; + } } } diff --git a/src/Umbraco.Core/UnknownTypeUdi.cs b/src/Umbraco.Core/UnknownTypeUdi.cs index 4131eae05338..5cee5e3f47ba 100644 --- a/src/Umbraco.Core/UnknownTypeUdi.cs +++ b/src/Umbraco.Core/UnknownTypeUdi.cs @@ -1,16 +1,13 @@ -namespace Umbraco.Cms.Core -{ - public class UnknownTypeUdi : Udi - { - private UnknownTypeUdi() - : base("unknown", "umb://unknown/") - { } +namespace Umbraco.Cms.Core; - public static readonly UnknownTypeUdi Instance = new UnknownTypeUdi(); +public class UnknownTypeUdi : Udi +{ + public static readonly UnknownTypeUdi Instance = new(); - public override bool IsRoot - { - get { return false; } - } + private UnknownTypeUdi() + : base("unknown", "umb://unknown/") + { } + + public override bool IsRoot => false; } diff --git a/src/Umbraco.Core/UpgradeResult.cs b/src/Umbraco.Core/UpgradeResult.cs index 25431a59836b..aadd83ffd07c 100644 --- a/src/Umbraco.Core/UpgradeResult.cs +++ b/src/Umbraco.Core/UpgradeResult.cs @@ -1,16 +1,15 @@ -namespace Umbraco.Cms.Core +namespace Umbraco.Cms.Core; + +public class UpgradeResult { - public class UpgradeResult + public UpgradeResult(string upgradeType, string comment, string upgradeUrl) { - public string UpgradeType { get; } - public string Comment { get; } - public string UpgradeUrl { get; } - - public UpgradeResult(string upgradeType, string comment, string upgradeUrl) - { - UpgradeType = upgradeType; - Comment = comment; - UpgradeUrl = upgradeUrl; - } + UpgradeType = upgradeType; + Comment = comment; + UpgradeUrl = upgradeUrl; } + + public string UpgradeType { get; } + public string Comment { get; } + public string UpgradeUrl { get; } } diff --git a/src/Umbraco.Core/UriUtilityCore.cs b/src/Umbraco.Core/UriUtilityCore.cs index 68b6234a0fb2..ebb837b1fb1a 100644 --- a/src/Umbraco.Core/UriUtilityCore.cs +++ b/src/Umbraco.Core/UriUtilityCore.cs @@ -1,59 +1,51 @@ -using System; -using Umbraco.Extensions; +using Umbraco.Extensions; -namespace Umbraco.Cms.Core -{ - public static class UriUtilityCore - { +namespace Umbraco.Cms.Core; - #region Uri string utilities +public static class UriUtilityCore +{ + #region Uri string utilities - public static bool HasScheme(string uri) - { - return uri.IndexOf("://") > 0; - } + public static bool HasScheme(string uri) => uri.IndexOf("://") > 0; - public static string StartWithScheme(string uri) - { - return StartWithScheme(uri, null); - } + public static string StartWithScheme(string uri) => StartWithScheme(uri, null); - public static string StartWithScheme(string uri, string? scheme) - { - return HasScheme(uri) ? uri : String.Format("{0}://{1}", scheme ?? Uri.UriSchemeHttp, uri); - } + public static string StartWithScheme(string uri, string? scheme) => + HasScheme(uri) ? uri : string.Format("{0}://{1}", scheme ?? Uri.UriSchemeHttp, uri); - public static string EndPathWithSlash(string uri) - { - var pos1 = Math.Max(0, uri.IndexOf('?')); - var pos2 = Math.Max(0, uri.IndexOf('#')); - var pos = Math.Min(pos1, pos2); - - var path = pos > 0 ? uri.Substring(0, pos) : uri; - path = path.EnsureEndsWith('/'); + public static string EndPathWithSlash(string uri) + { + var pos1 = Math.Max(0, uri.IndexOf('?')); + var pos2 = Math.Max(0, uri.IndexOf('#')); + var pos = Math.Min(pos1, pos2); - if (pos > 0) - path += uri.Substring(pos); + var path = pos > 0 ? uri.Substring(0, pos) : uri; + path = path.EnsureEndsWith('/'); - return path; + if (pos > 0) + { + path += uri.Substring(pos); } - public static string TrimPathEndSlash(string uri) - { - var pos1 = Math.Max(0, uri.IndexOf('?')); - var pos2 = Math.Max(0, uri.IndexOf('#')); - var pos = Math.Min(pos1, pos2); + return path; + } - var path = pos > 0 ? uri.Substring(0, pos) : uri; - path = path.TrimEnd(Constants.CharArrays.ForwardSlash); + public static string TrimPathEndSlash(string uri) + { + var pos1 = Math.Max(0, uri.IndexOf('?')); + var pos2 = Math.Max(0, uri.IndexOf('#')); + var pos = Math.Min(pos1, pos2); - if (pos > 0) - path += uri.Substring(pos); + var path = pos > 0 ? uri.Substring(0, pos) : uri; + path = path.TrimEnd(Constants.CharArrays.ForwardSlash); - return path; + if (pos > 0) + { + path += uri.Substring(pos); } - #endregion - + return path; } + + #endregion } diff --git a/src/Umbraco.Core/Web/CookieManagerExtensions.cs b/src/Umbraco.Core/Web/CookieManagerExtensions.cs index 75014000bb96..6a39e0cdc653 100644 --- a/src/Umbraco.Core/Web/CookieManagerExtensions.cs +++ b/src/Umbraco.Core/Web/CookieManagerExtensions.cs @@ -4,15 +4,10 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Web; -namespace Umbraco.Extensions -{ - public static class CookieManagerExtensions - { - public static string? GetPreviewCookieValue(this ICookieManager cookieManager) - { - return cookieManager.GetCookieValue(Constants.Web.PreviewCookieName); - } - - } +namespace Umbraco.Extensions; +public static class CookieManagerExtensions +{ + public static string? GetPreviewCookieValue(this ICookieManager cookieManager) => + cookieManager.GetCookieValue(Constants.Web.PreviewCookieName); } diff --git a/src/Umbraco.Core/Web/HybridUmbracoContextAccessor.cs b/src/Umbraco.Core/Web/HybridUmbracoContextAccessor.cs index 94710429f079..509a746b3085 100644 --- a/src/Umbraco.Core/Web/HybridUmbracoContextAccessor.cs +++ b/src/Umbraco.Core/Web/HybridUmbracoContextAccessor.cs @@ -1,39 +1,39 @@ using System.Diagnostics.CodeAnalysis; using Umbraco.Cms.Core.Cache; -namespace Umbraco.Cms.Core.Web +namespace Umbraco.Cms.Core.Web; + +/// +/// Implements a hybrid . +/// +public class HybridUmbracoContextAccessor : HybridAccessorBase, IUmbracoContextAccessor { /// - /// Implements a hybrid . + /// Initializes a new instance of the class. /// - public class HybridUmbracoContextAccessor : HybridAccessorBase, IUmbracoContextAccessor + public HybridUmbracoContextAccessor(IRequestCache requestCache) + : base(requestCache) { - /// - /// Initializes a new instance of the class. - /// - public HybridUmbracoContextAccessor(IRequestCache requestCache) - : base(requestCache) - { } + } - /// - /// Tries to get the object. - /// - public bool TryGetUmbracoContext([MaybeNullWhen(false)] out IUmbracoContext umbracoContext) - { - umbracoContext = Value; + /// + /// Tries to get the object. + /// + public bool TryGetUmbracoContext([MaybeNullWhen(false)] out IUmbracoContext umbracoContext) + { + umbracoContext = Value; - return umbracoContext is not null; - } + return umbracoContext is not null; + } - /// - /// Clears the current object. - /// - public void Clear() => Value = null; + /// + /// Clears the current object. + /// + public void Clear() => Value = null; - /// - /// Sets the object. - /// - /// - public void Set(IUmbracoContext umbracoContext) => Value = umbracoContext; - } + /// + /// Sets the object. + /// + /// + public void Set(IUmbracoContext umbracoContext) => Value = umbracoContext; } diff --git a/src/Umbraco.Core/Web/ICookieManager.cs b/src/Umbraco.Core/Web/ICookieManager.cs index 730b78a705f6..93426e708b37 100644 --- a/src/Umbraco.Core/Web/ICookieManager.cs +++ b/src/Umbraco.Core/Web/ICookieManager.cs @@ -1,12 +1,9 @@ -namespace Umbraco.Cms.Core.Web -{ - - public interface ICookieManager - { - void ExpireCookie(string cookieName); - string? GetCookieValue(string cookieName); - void SetCookieValue(string cookieName, string value); - bool HasCookie(string cookieName); - } +namespace Umbraco.Cms.Core.Web; +public interface ICookieManager +{ + void ExpireCookie(string cookieName); + string? GetCookieValue(string cookieName); + void SetCookieValue(string cookieName, string value); + bool HasCookie(string cookieName); } diff --git a/src/Umbraco.Core/Web/IRequestAccessor.cs b/src/Umbraco.Core/Web/IRequestAccessor.cs index 9fb4e99d5c75..a72ec5bc72e4 100644 --- a/src/Umbraco.Core/Web/IRequestAccessor.cs +++ b/src/Umbraco.Core/Web/IRequestAccessor.cs @@ -1,22 +1,19 @@ -using System; +namespace Umbraco.Cms.Core.Web; -namespace Umbraco.Cms.Core.Web +public interface IRequestAccessor { - public interface IRequestAccessor - { - /// - /// Returns the request/form/querystring value for the given name - /// - string GetRequestValue(string name); + /// + /// Returns the request/form/querystring value for the given name + /// + string GetRequestValue(string name); - /// - /// Returns the query string value for the given name - /// - string GetQueryStringValue(string name); + /// + /// Returns the query string value for the given name + /// + string GetQueryStringValue(string name); - /// - /// Returns the current request uri - /// - Uri? GetRequestUrl(); - } + /// + /// Returns the current request uri + /// + Uri? GetRequestUrl(); } diff --git a/src/Umbraco.Core/Web/ISessionManager.cs b/src/Umbraco.Core/Web/ISessionManager.cs index 3ba691e22208..a37bebcfa780 100644 --- a/src/Umbraco.Core/Web/ISessionManager.cs +++ b/src/Umbraco.Core/Web/ISessionManager.cs @@ -1,11 +1,10 @@ -namespace Umbraco.Cms.Core.Web +namespace Umbraco.Cms.Core.Web; + +public interface ISessionManager { - public interface ISessionManager - { - string? GetSessionValue(string key); + string? GetSessionValue(string key); - void SetSessionValue(string key, string value); + void SetSessionValue(string key, string value); - void ClearSessionValue(string key); - } + void ClearSessionValue(string key); } diff --git a/src/Umbraco.Core/Web/IUmbracoContext.cs b/src/Umbraco.Core/Web/IUmbracoContext.cs index 7cfa3876c0e7..62473947e37d 100644 --- a/src/Umbraco.Core/Web/IUmbracoContext.cs +++ b/src/Umbraco.Core/Web/IUmbracoContext.cs @@ -1,74 +1,73 @@ -using System; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Routing; -namespace Umbraco.Cms.Core.Web +namespace Umbraco.Cms.Core.Web; + +public interface IUmbracoContext : IDisposable { - public interface IUmbracoContext : IDisposable - { - /// - /// Gets the DateTime this instance was created. - /// - /// - /// Used internally for performance calculations, the ObjectCreated DateTime is set as soon as this - /// object is instantiated which in the web site is created during the BeginRequest phase. - /// We can then determine complete rendering time from that. - /// - DateTime ObjectCreated { get; } + /// + /// Gets the DateTime this instance was created. + /// + /// + /// Used internally for performance calculations, the ObjectCreated DateTime is set as soon as this + /// object is instantiated which in the web site is created during the BeginRequest phase. + /// We can then determine complete rendering time from that. + /// + DateTime ObjectCreated { get; } - /// - /// Gets the uri that is handled by ASP.NET after server-side rewriting took place. - /// - Uri OriginalRequestUrl { get; } + /// + /// Gets the uri that is handled by ASP.NET after server-side rewriting took place. + /// + Uri OriginalRequestUrl { get; } - /// - /// Gets the cleaned up url that is handled by Umbraco. - /// - /// That is, lowercase, no trailing slash after path, no .aspx... - Uri CleanedUmbracoUrl { get; } + /// + /// Gets the cleaned up url that is handled by Umbraco. + /// + /// That is, lowercase, no trailing slash after path, no .aspx... + Uri CleanedUmbracoUrl { get; } - /// - /// Gets the published snapshot. - /// - IPublishedSnapshot PublishedSnapshot { get; } + /// + /// Gets the published snapshot. + /// + IPublishedSnapshot PublishedSnapshot { get; } - /// - /// Gets the published content cache. - /// - IPublishedContentCache? Content { get; } + /// + /// Gets the published content cache. + /// + IPublishedContentCache? Content { get; } - /// - /// Gets the published media cache. - /// - IPublishedMediaCache? Media { get; } + /// + /// Gets the published media cache. + /// + IPublishedMediaCache? Media { get; } - /// - /// Gets the domains cache. - /// - IDomainCache? Domains { get; } + /// + /// Gets the domains cache. + /// + IDomainCache? Domains { get; } - /// - /// Gets or sets the PublishedRequest object - /// - //// TODO: Can we refactor this so it's not a settable thing required for routing? - //// The only nicer way would be to have a RouteRequest method directly on IUmbracoContext but that means adding another dep to the ctx/factory. - IPublishedRequest? PublishedRequest { get; set; } + /// + /// Gets or sets the PublishedRequest object + /// + //// TODO: Can we refactor this so it's not a settable thing required for routing? + //// The only nicer way would be to have a RouteRequest method directly on IUmbracoContext but that means adding another dep to the ctx/factory. + IPublishedRequest? PublishedRequest { get; set; } - /// - /// Gets a value indicating whether the request has debugging enabled - /// - /// true if this instance is debug; otherwise, false. - bool IsDebug { get; } + /// + /// Gets a value indicating whether the request has debugging enabled + /// + /// true if this instance is debug; otherwise, false. + bool IsDebug { get; } - /// - /// Gets a value indicating whether the current user is in a preview mode and browsing the site (ie. not in the admin UI) - /// - bool InPreviewMode { get; } + /// + /// Gets a value indicating whether the current user is in a preview mode and browsing the site (ie. not in the admin + /// UI) + /// + bool InPreviewMode { get; } - /// - /// Forces the context into preview - /// - /// A instance to be disposed to exit the preview context - IDisposable ForcedPreview(bool preview); - } + /// + /// Forces the context into preview + /// + /// A instance to be disposed to exit the preview context + IDisposable ForcedPreview(bool preview); } diff --git a/src/Umbraco.Core/Web/IUmbracoContextAccessor.cs b/src/Umbraco.Core/Web/IUmbracoContextAccessor.cs index d8e6793f8932..25943d432662 100644 --- a/src/Umbraco.Core/Web/IUmbracoContextAccessor.cs +++ b/src/Umbraco.Core/Web/IUmbracoContextAccessor.cs @@ -1,16 +1,16 @@ using System.Diagnostics.CodeAnalysis; -namespace Umbraco.Cms.Core.Web +namespace Umbraco.Cms.Core.Web; + +/// +/// Provides access to a TryGetUmbracoContext bool method that will return true if the "current" +/// is not null. +/// Provides a Clear() method that will clear the current object. +/// Provides a Set() method that til set the current object. +/// +public interface IUmbracoContextAccessor { - /// - /// Provides access to a TryGetUmbracoContext bool method that will return true if the "current" is not null. - /// Provides a Clear() method that will clear the current object. - /// Provides a Set() method that til set the current object. - /// - public interface IUmbracoContextAccessor - { - bool TryGetUmbracoContext([MaybeNullWhen(false)] out IUmbracoContext umbracoContext); - void Clear(); - void Set(IUmbracoContext umbracoContext); - } + bool TryGetUmbracoContext([MaybeNullWhen(false)] out IUmbracoContext umbracoContext); + void Clear(); + void Set(IUmbracoContext umbracoContext); } diff --git a/src/Umbraco.Core/Web/IUmbracoContextFactory.cs b/src/Umbraco.Core/Web/IUmbracoContextFactory.cs index 68ebcf8b2bd6..10c473f72930 100644 --- a/src/Umbraco.Core/Web/IUmbracoContextFactory.cs +++ b/src/Umbraco.Core/Web/IUmbracoContextFactory.cs @@ -1,30 +1,32 @@ - -namespace Umbraco.Cms.Core.Web +namespace Umbraco.Cms.Core.Web; + +/// +/// Creates and manages instances. +/// +public interface IUmbracoContextFactory { /// - /// Creates and manages instances. + /// Ensures that a current exists. /// - public interface IUmbracoContextFactory - { - /// - /// Ensures that a current exists. - /// - /// - /// If an is already registered in the - /// , returns a non-root reference to it. - /// Otherwise, create a new instance, registers it, and return a root reference - /// to it. - /// If is null, the factory tries to use - /// if it exists. Otherwise, it uses a dummy - /// . - /// - /// - /// using (var contextReference = contextFactory.EnsureUmbracoContext()) - /// { - /// var umbracoContext = contextReference.UmbracoContext; - /// // use umbracoContext... - /// } - /// - UmbracoContextReference EnsureUmbracoContext(); - } + /// + /// + /// If an is already registered in the + /// , returns a non-root reference to it. + /// Otherwise, create a new instance, registers it, and return a root reference + /// to it. + /// + /// + /// If is null, the factory tries to use + /// if it exists. Otherwise, it uses a dummy + /// . + /// + /// + /// + /// using (var contextReference = contextFactory.EnsureUmbracoContext()) + /// { + /// var umbracoContext = contextReference.UmbracoContext; + /// // use umbracoContext... + /// } + /// + UmbracoContextReference EnsureUmbracoContext(); } diff --git a/src/Umbraco.Core/Web/Mvc/PluginControllerMetadata.cs b/src/Umbraco.Core/Web/Mvc/PluginControllerMetadata.cs index efc162a9a3b4..ca533e1832ac 100644 --- a/src/Umbraco.Core/Web/Mvc/PluginControllerMetadata.cs +++ b/src/Umbraco.Core/Web/Mvc/PluginControllerMetadata.cs @@ -1,21 +1,18 @@ -using System; +namespace Umbraco.Cms.Core.Web.Mvc; -namespace Umbraco.Cms.Core.Web.Mvc +/// +/// Represents some metadata about the controller +/// +public class PluginControllerMetadata { + public Type ControllerType { get; set; } = null!; + public string? ControllerName { get; set; } + public string? ControllerNamespace { get; set; } + public string? AreaName { get; set; } + /// - /// Represents some metadata about the controller + /// This is determined by another attribute [IsBackOffice] which slightly modifies the route path + /// allowing us to determine if it is indeed a back office request or not /// - public class PluginControllerMetadata - { - public Type ControllerType { get; set; } = null!; - public string? ControllerName { get; set; } - public string? ControllerNamespace { get; set; } - public string? AreaName { get; set; } - - /// - /// This is determined by another attribute [IsBackOffice] which slightly modifies the route path - /// allowing us to determine if it is indeed a back office request or not - /// - public bool IsBackOffice { get; set; } - } + public bool IsBackOffice { get; set; } } diff --git a/src/Umbraco.Core/WebAssets/AssetFile.cs b/src/Umbraco.Core/WebAssets/AssetFile.cs index c10a423a9946..a450d550ad5d 100644 --- a/src/Umbraco.Core/WebAssets/AssetFile.cs +++ b/src/Umbraco.Core/WebAssets/AssetFile.cs @@ -1,23 +1,19 @@ using System.Diagnostics; -namespace Umbraco.Cms.Core.WebAssets +namespace Umbraco.Cms.Core.WebAssets; + +/// +/// Represents a dependency file +/// +[DebuggerDisplay("Type: {DependencyType}, File: {FilePath}")] +public class AssetFile : IAssetFile { - /// - /// Represents a dependency file - /// - [DebuggerDisplay("Type: {DependencyType}, File: {FilePath}")] - public class AssetFile : IAssetFile - { - #region IAssetFile Members + public AssetFile(AssetType type) => DependencyType = type; - public string? FilePath { get; set; } - public AssetType DependencyType { get; } + #region IAssetFile Members - #endregion + public string? FilePath { get; set; } + public AssetType DependencyType { get; } - public AssetFile(AssetType type) - { - DependencyType = type; - } - } + #endregion } diff --git a/src/Umbraco.Core/WebAssets/AssetType.cs b/src/Umbraco.Core/WebAssets/AssetType.cs index f40a592588e0..dc3e55e8b96b 100644 --- a/src/Umbraco.Core/WebAssets/AssetType.cs +++ b/src/Umbraco.Core/WebAssets/AssetType.cs @@ -1,8 +1,7 @@ -namespace Umbraco.Cms.Core.WebAssets +namespace Umbraco.Cms.Core.WebAssets; + +public enum AssetType { - public enum AssetType - { - Javascript, - Css - } + Javascript, + Css } diff --git a/src/Umbraco.Core/WebAssets/BundlingOptions.cs b/src/Umbraco.Core/WebAssets/BundlingOptions.cs index 64b9e72e17d2..5ec28d785678 100644 --- a/src/Umbraco.Core/WebAssets/BundlingOptions.cs +++ b/src/Umbraco.Core/WebAssets/BundlingOptions.cs @@ -1,44 +1,43 @@ -using System; +namespace Umbraco.Cms.Core.WebAssets; -namespace Umbraco.Cms.Core.WebAssets +public struct BundlingOptions : IEquatable { - public struct BundlingOptions : IEquatable + public static BundlingOptions OptimizedAndComposite => new(true); + public static BundlingOptions OptimizedNotComposite => new(true, false); + public static BundlingOptions NotOptimizedNotComposite => new(false, false); + public static BundlingOptions NotOptimizedAndComposite => new(false); + + public BundlingOptions(bool optimizeOutput = true, bool enabledCompositeFiles = true) + { + OptimizeOutput = optimizeOutput; + EnabledCompositeFiles = enabledCompositeFiles; + } + + /// + /// If true, the files in the bundle will be minified + /// + public bool OptimizeOutput { get; } + + /// + /// If true, the files in the bundle will be combined, if false the files + /// will be served as individual files. + /// + public bool EnabledCompositeFiles { get; } + + public override bool Equals(object? obj) => obj is BundlingOptions options && Equals(options); + + public bool Equals(BundlingOptions other) => OptimizeOutput == other.OptimizeOutput && + EnabledCompositeFiles == other.EnabledCompositeFiles; + + public override int GetHashCode() { - public static BundlingOptions OptimizedAndComposite => new BundlingOptions(true, true); - public static BundlingOptions OptimizedNotComposite => new BundlingOptions(true, false); - public static BundlingOptions NotOptimizedNotComposite => new BundlingOptions(false, false); - public static BundlingOptions NotOptimizedAndComposite => new BundlingOptions(false, true); - - public BundlingOptions(bool optimizeOutput = true, bool enabledCompositeFiles = true) - { - OptimizeOutput = optimizeOutput; - EnabledCompositeFiles = enabledCompositeFiles; - } - - /// - /// If true, the files in the bundle will be minified - /// - public bool OptimizeOutput { get; } - - /// - /// If true, the files in the bundle will be combined, if false the files - /// will be served as individual files. - /// - public bool EnabledCompositeFiles { get; } - - public override bool Equals(object? obj) => obj is BundlingOptions options && Equals(options); - public bool Equals(BundlingOptions other) => OptimizeOutput == other.OptimizeOutput && EnabledCompositeFiles == other.EnabledCompositeFiles; - - public override int GetHashCode() - { - int hashCode = 2130304063; - hashCode = hashCode * -1521134295 + OptimizeOutput.GetHashCode(); - hashCode = hashCode * -1521134295 + EnabledCompositeFiles.GetHashCode(); - return hashCode; - } - - public static bool operator ==(BundlingOptions left, BundlingOptions right) => left.Equals(right); - - public static bool operator !=(BundlingOptions left, BundlingOptions right) => !(left == right); + var hashCode = 2130304063; + hashCode = (hashCode * -1521134295) + OptimizeOutput.GetHashCode(); + hashCode = (hashCode * -1521134295) + EnabledCompositeFiles.GetHashCode(); + return hashCode; } + + public static bool operator ==(BundlingOptions left, BundlingOptions right) => left.Equals(right); + + public static bool operator !=(BundlingOptions left, BundlingOptions right) => !(left == right); } diff --git a/src/Umbraco.Core/WebAssets/CssFile.cs b/src/Umbraco.Core/WebAssets/CssFile.cs index 101ff22763d5..c0c73df77e44 100644 --- a/src/Umbraco.Core/WebAssets/CssFile.cs +++ b/src/Umbraco.Core/WebAssets/CssFile.cs @@ -1,14 +1,11 @@ -namespace Umbraco.Cms.Core.WebAssets +namespace Umbraco.Cms.Core.WebAssets; + +/// +/// Represents a CSS asset file +/// +public class CssFile : AssetFile { - /// - /// Represents a CSS asset file - /// - public class CssFile : AssetFile - { - public CssFile(string filePath) - : base(AssetType.Css) - { - FilePath = filePath; - } - } + public CssFile(string filePath) + : base(AssetType.Css) => + FilePath = filePath; } diff --git a/src/Umbraco.Core/WebAssets/CustomBackOfficeAssetsCollection.cs b/src/Umbraco.Core/WebAssets/CustomBackOfficeAssetsCollection.cs index 2595afe40e42..204a9ddf3764 100644 --- a/src/Umbraco.Core/WebAssets/CustomBackOfficeAssetsCollection.cs +++ b/src/Umbraco.Core/WebAssets/CustomBackOfficeAssetsCollection.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.WebAssets +namespace Umbraco.Cms.Core.WebAssets; + +public class CustomBackOfficeAssetsCollection : BuilderCollectionBase { - public class CustomBackOfficeAssetsCollection : BuilderCollectionBase + public CustomBackOfficeAssetsCollection(Func> items) : base(items) { - public CustomBackOfficeAssetsCollection(Func> items) : base(items) - { - } } } diff --git a/src/Umbraco.Core/WebAssets/CustomBackOfficeAssetsCollectionBuilder.cs b/src/Umbraco.Core/WebAssets/CustomBackOfficeAssetsCollectionBuilder.cs index df84bf013d1e..f45ac4e86bc8 100644 --- a/src/Umbraco.Core/WebAssets/CustomBackOfficeAssetsCollectionBuilder.cs +++ b/src/Umbraco.Core/WebAssets/CustomBackOfficeAssetsCollectionBuilder.cs @@ -1,9 +1,9 @@ using Umbraco.Cms.Core.Composing; -namespace Umbraco.Cms.Core.WebAssets +namespace Umbraco.Cms.Core.WebAssets; + +public class CustomBackOfficeAssetsCollectionBuilder : OrderedCollectionBuilderBase< + CustomBackOfficeAssetsCollectionBuilder, CustomBackOfficeAssetsCollection, IAssetFile> { - public class CustomBackOfficeAssetsCollectionBuilder : OrderedCollectionBuilderBase - { - protected override CustomBackOfficeAssetsCollectionBuilder This => this; - } + protected override CustomBackOfficeAssetsCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/WebAssets/IAssetFile.cs b/src/Umbraco.Core/WebAssets/IAssetFile.cs index dd66afe4a712..d0e2c339a085 100644 --- a/src/Umbraco.Core/WebAssets/IAssetFile.cs +++ b/src/Umbraco.Core/WebAssets/IAssetFile.cs @@ -1,8 +1,7 @@ -namespace Umbraco.Cms.Core.WebAssets +namespace Umbraco.Cms.Core.WebAssets; + +public interface IAssetFile { - public interface IAssetFile - { - string? FilePath { get; set; } - AssetType DependencyType { get; } - } + string? FilePath { get; set; } + AssetType DependencyType { get; } } diff --git a/src/Umbraco.Core/WebAssets/IRuntimeMinifier.cs b/src/Umbraco.Core/WebAssets/IRuntimeMinifier.cs index baf9549562e0..c6116e122fcc 100644 --- a/src/Umbraco.Core/WebAssets/IRuntimeMinifier.cs +++ b/src/Umbraco.Core/WebAssets/IRuntimeMinifier.cs @@ -1,105 +1,101 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; +namespace Umbraco.Cms.Core.WebAssets; -namespace Umbraco.Cms.Core.WebAssets +/// +/// Used for bundling and minifying web assets at runtime +/// +public interface IRuntimeMinifier { /// - /// Used for bundling and minifying web assets at runtime + /// Returns the cache buster value /// - public interface IRuntimeMinifier - { - /// - /// Returns the cache buster value - /// - string CacheBuster { get; } + string CacheBuster { get; } - /// - /// Creates a css bundle - /// - /// - /// - /// - /// All files must be absolute paths, relative paths will throw - /// - /// - /// Thrown if any of the paths specified are not absolute - /// - void CreateCssBundle(string bundleName, BundlingOptions bundleOptions, params string[]? filePaths); + /// + /// Creates a css bundle + /// + /// + /// + /// + /// All files must be absolute paths, relative paths will throw + /// + /// + /// Thrown if any of the paths specified are not absolute + /// + void CreateCssBundle(string bundleName, BundlingOptions bundleOptions, params string[]? filePaths); - /// - /// Renders the html link tag for the bundle - /// - /// - /// - /// An html encoded string - /// - Task RenderCssHereAsync(string bundleName); + /// + /// Renders the html link tag for the bundle + /// + /// + /// + /// An html encoded string + /// + Task RenderCssHereAsync(string bundleName); - /// - /// Creates a JS bundle - /// - /// - /// - /// - /// - /// All files must be absolute paths, relative paths will throw - /// - /// - /// Thrown if any of the paths specified are not absolute - /// - void CreateJsBundle(string bundleName, BundlingOptions bundleOptions, params string[]? filePaths); + /// + /// Creates a JS bundle + /// + /// + /// + /// + /// + /// All files must be absolute paths, relative paths will throw + /// + /// + /// Thrown if any of the paths specified are not absolute + /// + void CreateJsBundle(string bundleName, BundlingOptions bundleOptions, params string[]? filePaths); - /// - /// Renders the html script tag for the bundle - /// - /// - /// - /// An html encoded string - /// - Task RenderJsHereAsync(string bundleName); + /// + /// Renders the html script tag for the bundle + /// + /// + /// + /// An html encoded string + /// + Task RenderJsHereAsync(string bundleName); - /// - /// Returns the asset paths for the JS bundle name - /// - /// - /// - /// If debug mode is enabled this will return all asset paths (not bundled), else it will return a bundle URL - /// - Task> GetJsAssetPathsAsync(string bundleName); + /// + /// Returns the asset paths for the JS bundle name + /// + /// + /// + /// If debug mode is enabled this will return all asset paths (not bundled), else it will return a bundle URL + /// + Task> GetJsAssetPathsAsync(string bundleName); - /// - /// Returns the asset paths for the css bundle name - /// - /// - /// - /// If debug mode is enabled this will return all asset paths (not bundled), else it will return a bundle URL - /// - Task> GetCssAssetPathsAsync(string bundleName); + /// + /// Returns the asset paths for the css bundle name + /// + /// + /// + /// If debug mode is enabled this will return all asset paths (not bundled), else it will return a bundle URL + /// + Task> GetCssAssetPathsAsync(string bundleName); - /// - /// Minify the file content, of a given type - /// - /// - /// - /// - Task MinifyAsync(string? fileContent, AssetType assetType); + /// + /// Minify the file content, of a given type + /// + /// + /// + /// + Task MinifyAsync(string? fileContent, AssetType assetType); - /// - /// Ensures that all runtime minifications are refreshed on next request. E.g. Clearing cache. - /// - /// - /// - /// No longer necessary, invalidation occurs automatically if any of the following occur. - /// - /// - /// Your sites assembly information version changes. - /// Umbraco.Cms.Core assembly information version changes. - /// RuntimeMinificationSettings Version string changes. - /// - /// for further details. - /// - [Obsolete("Invalidation is handled automatically. Scheduled for removal V11.")] - void Reset(); - } + /// + /// Ensures that all runtime minifications are refreshed on next request. E.g. Clearing cache. + /// + /// + /// + /// No longer necessary, invalidation occurs automatically if any of the following occur. + /// + /// + /// Your sites assembly information version changes. + /// Umbraco.Cms.Core assembly information version changes. + /// RuntimeMinificationSettings Version string changes. + /// + /// for further + /// details. + /// + [Obsolete("Invalidation is handled automatically. Scheduled for removal V11.")] + void Reset(); } diff --git a/src/Umbraco.Core/WebAssets/JavascriptFile.cs b/src/Umbraco.Core/WebAssets/JavascriptFile.cs index 2dccbf2a072d..4b38556f728f 100644 --- a/src/Umbraco.Core/WebAssets/JavascriptFile.cs +++ b/src/Umbraco.Core/WebAssets/JavascriptFile.cs @@ -1,14 +1,11 @@ -namespace Umbraco.Cms.Core.WebAssets +namespace Umbraco.Cms.Core.WebAssets; + +/// +/// Represents a JS asset file +/// +public class JavaScriptFile : AssetFile { - /// - /// Represents a JS asset file - /// - public class JavaScriptFile : AssetFile - { - public JavaScriptFile(string filePath) - : base(AssetType.Javascript) - { - FilePath = filePath; - } - } + public JavaScriptFile(string filePath) + : base(AssetType.Javascript) => + FilePath = filePath; } diff --git a/src/Umbraco.Core/Xml/DynamicContext.cs b/src/Umbraco.Core/Xml/DynamicContext.cs index 7547b7cc31d9..48bdb198260b 100644 --- a/src/Umbraco.Core/Xml/DynamicContext.cs +++ b/src/Umbraco.Core/Xml/DynamicContext.cs @@ -1,309 +1,306 @@ -using System; -using System.Collections.Generic; +using System.Globalization; using System.Xml; using System.Xml.XPath; using System.Xml.Xsl; // source: mvpxml.codeplex.com -namespace Umbraco.Cms.Core.Xml +namespace Umbraco.Cms.Core.Xml; + +/// +/// Provides the evaluation context for fast execution and custom +/// variables resolution. +/// +/// +/// This class is responsible for resolving variables during dynamic expression execution. +/// Discussed in http://weblogs.asp.net/cazzu/archive/2003/10/07/30888.aspx +/// Author: Daniel Cazzulino, blog +/// +public class DynamicContext : XsltContext { + #region Private vars + + private readonly IDictionary _variables = + new Dictionary(); + + #endregion Private + + #region Public Members + /// - /// Provides the evaluation context for fast execution and custom - /// variables resolution. + /// Shortcut method that compiles an expression using an empty navigator. /// - /// - /// This class is responsible for resolving variables during dynamic expression execution. - /// Discussed in http://weblogs.asp.net/cazzu/archive/2003/10/07/30888.aspx - /// Author: Daniel Cazzulino, blog - /// - public class DynamicContext : XsltContext - { - #region Private vars + /// The expression to compile + /// A compiled . + public static XPathExpression? Compile(string xpath) => new XmlDocument().CreateNavigator()?.Compile(xpath); - readonly IDictionary _variables = - new Dictionary(); + #endregion Public Members - #endregion Private + #region Internal DynamicVariable class - #region Constructors & Initialization + /// + /// Represents a variable during dynamic expression execution. + /// + internal class DynamicVariable : IXsltContextVariable + { + private readonly object _value; - /// - /// Initializes a new instance of the class. - /// - public DynamicContext() - : base(new NameTable()) - { - } + #region Public Members - /// - /// Initializes a new instance of the - /// class with the specified . - /// - /// The NameTable to use. - public DynamicContext(NameTable table) - : base(table) - { - } + public string Name { get; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// A previously filled context with the namespaces to use. - public DynamicContext(XmlNamespaceManager context) - : this(context, new NameTable()) + /// The name of the variable. + /// The value of the variable. + public DynamicVariable(string name, object value) { - } + Name = name; + _value = value; - /// - /// Initializes a new instance of the class. - /// - /// A previously filled context with the namespaces to use. - /// The NameTable to use. - public DynamicContext(XmlNamespaceManager context, NameTable table) - : base(table) - { - object xml = table.Add(XmlNamespaces.Xml); - object xmlns = table.Add(XmlNamespaces.XmlNs); - - if (context == null) return; - - foreach (string prefix in context) + if (value is string) + { + _type = XPathResultType.String; + } + else if (value is bool) + { + _type = XPathResultType.Boolean; + } + else if (value is XPathNavigator) + { + _type = XPathResultType.Navigator; + } + else if (value is XPathNodeIterator) { - var uri = context.LookupNamespace(prefix); - // Use fast object reference comparison to omit forbidden namespace declarations. - if (Equals(uri, xml) || Equals(uri, xmlns)) - continue; - if (uri == null) - continue; - base.AddNamespace(prefix, uri); + _type = XPathResultType.NodeSet; + } + else + { + // Try to convert to double (native XPath numeric type) + if (value is double) + { + _type = XPathResultType.Number; + } + else + { + if (value is IConvertible) + { + try + { + _value = Convert.ToDouble(value); + // We succeeded, so it's a number. + _type = XPathResultType.Number; + } + catch (FormatException) + { + _type = XPathResultType.Any; + } + catch (OverflowException) + { + _type = XPathResultType.Any; + } + } + else + { + _type = XPathResultType.Any; + } + } } } - #endregion Constructors & Initialization + #endregion Public Members - #region Common Overrides + #region IXsltContextVariable Implementation - /// - /// Implementation equal to . - /// - public override int CompareDocument(string baseUri, string nextbaseUri) - { - return String.Compare(baseUri, nextbaseUri, false, System.Globalization.CultureInfo.InvariantCulture); - } + XPathResultType IXsltContextVariable.VariableType => _type; - /// - /// Same as . - /// - public override string? LookupNamespace(string prefix) - { - var key = NameTable?.Get(prefix); - return key == null ? null : base.LookupNamespace(key); - } + private readonly XPathResultType _type; - /// - /// Same as . - /// - public override string? LookupPrefix(string uri) - { - var key = NameTable?.Get(uri); - return key == null ? null : base.LookupPrefix(key); - } + object IXsltContextVariable.Evaluate(XsltContext context) => _value; - /// - /// Same as . - /// - public override bool PreserveWhitespace(XPathNavigator node) - { - return true; - } + bool IXsltContextVariable.IsLocal => false; - /// - /// Same as . - /// - public override bool Whitespace - { - get { return true; } - } + bool IXsltContextVariable.IsParam => false; - #endregion Common Overrides + #endregion IXsltContextVariable Implementation + } - #region Public Members + #endregion Internal DynamicVariable class - /// - /// Shortcut method that compiles an expression using an empty navigator. - /// - /// The expression to compile - /// A compiled . - public static XPathExpression? Compile(string xpath) - { - return new XmlDocument().CreateNavigator()?.Compile(xpath); - } + #region Constructors & Initialization - #endregion Public Members + /// + /// Initializes a new instance of the class. + /// + public DynamicContext() + : base(new NameTable()) + { + } - #region Variable Handling Code + /// + /// Initializes a new instance of the + /// class with the specified . + /// + /// The NameTable to use. + public DynamicContext(NameTable table) + : base(table) + { + } - /// - /// Adds the variable to the dynamic evaluation context. - /// - /// The name of the variable to add to the context. - /// The value of the variable to add to the context. - /// - /// Value type conversion for XPath evaluation is as follows: - /// - /// - /// CLR Type - /// XPath type - /// - /// - /// System.String - /// XPathResultType.String - /// - /// - /// System.Double (or types that can be converted to) - /// XPathResultType.Number - /// - /// - /// System.Boolean - /// XPathResultType.Boolean - /// - /// - /// System.Xml.XPath.XPathNavigator - /// XPathResultType.Navigator - /// - /// - /// System.Xml.XPath.XPathNodeIterator - /// XPathResultType.NodeSet - /// - /// - /// Others - /// XPathResultType.Any - /// - /// - /// See the topic "Compile, Select, Evaluate, and Matches with - /// XPath and XPathExpressions" in MSDN documentation for additional information. - /// - /// The is null. - public void AddVariable(string name, object value) - { - if (value == null) throw new ArgumentNullException("value"); - _variables[name] = new DynamicVariable(name, value); - } + /// + /// Initializes a new instance of the class. + /// + /// A previously filled context with the namespaces to use. + public DynamicContext(XmlNamespaceManager context) + : this(context, new NameTable()) + { + } - /// - /// See . Not used in our implementation. - /// - public override IXsltContextFunction ResolveFunction(string prefix, string name, XPathResultType[] argTypes) => throw new NotImplementedException(); + /// + /// Initializes a new instance of the class. + /// + /// A previously filled context with the namespaces to use. + /// The NameTable to use. + public DynamicContext(XmlNamespaceManager context, NameTable table) + : base(table) + { + object xml = table.Add(XmlNamespaces.Xml); + object xmlns = table.Add(XmlNamespaces.XmlNs); - /// - /// Resolves the dynamic variables added to the context. See . - /// - public override IXsltContextVariable ResolveVariable(string prefix, string name) + if (context == null) { - IXsltContextVariable var; - _variables.TryGetValue(name, out var!); - return var!; + return; } - #endregion Variable Handling Code - - #region Internal DynamicVariable class - - /// - /// Represents a variable during dynamic expression execution. - /// - internal class DynamicVariable : IXsltContextVariable + foreach (string prefix in context) { - readonly string _name; - readonly object _value; + var uri = context.LookupNamespace(prefix); + // Use fast object reference comparison to omit forbidden namespace declarations. + if (Equals(uri, xml) || Equals(uri, xmlns)) + { + continue; + } - #region Public Members + if (uri == null) + { + continue; + } - public string Name { get { return _name; } } + base.AddNamespace(prefix, uri); + } + } - /// - /// Initializes a new instance of the class. - /// - /// The name of the variable. - /// The value of the variable. - public DynamicVariable(string name, object value) - { + #endregion Constructors & Initialization - _name = name; - _value = value; - - if (value is string) - _type = XPathResultType.String; - else if (value is bool) - _type = XPathResultType.Boolean; - else if (value is XPathNavigator) - _type = XPathResultType.Navigator; - else if (value is XPathNodeIterator) - _type = XPathResultType.NodeSet; - else - { - // Try to convert to double (native XPath numeric type) - if (value is double) - { - _type = XPathResultType.Number; - } - else - { - if (value is IConvertible) - { - try - { - _value = Convert.ToDouble(value); - // We succeeded, so it's a number. - _type = XPathResultType.Number; - } - catch (FormatException) - { - _type = XPathResultType.Any; - } - catch (OverflowException) - { - _type = XPathResultType.Any; - } - } - else - { - _type = XPathResultType.Any; - } - } - } - } + #region Common Overrides - #endregion Public Members + /// + /// Implementation equal to . + /// + public override int CompareDocument(string baseUri, string nextbaseUri) => + string.Compare(baseUri, nextbaseUri, false, CultureInfo.InvariantCulture); - #region IXsltContextVariable Implementation + /// + /// Same as . + /// + public override string? LookupNamespace(string prefix) + { + var key = NameTable?.Get(prefix); + return key == null ? null : base.LookupNamespace(key); + } - XPathResultType IXsltContextVariable.VariableType - { - get { return _type; } - } + /// + /// Same as . + /// + public override string? LookupPrefix(string uri) + { + var key = NameTable?.Get(uri); + return key == null ? null : base.LookupPrefix(key); + } - readonly XPathResultType _type; + /// + /// Same as . + /// + public override bool PreserveWhitespace(XPathNavigator node) => true; - object IXsltContextVariable.Evaluate(XsltContext context) - { - return _value; - } + /// + /// Same as . + /// + public override bool Whitespace => true; - bool IXsltContextVariable.IsLocal - { - get { return false; } - } + #endregion Common Overrides - bool IXsltContextVariable.IsParam - { - get { return false; } - } + #region Variable Handling Code - #endregion IXsltContextVariable Implementation + /// + /// Adds the variable to the dynamic evaluation context. + /// + /// The name of the variable to add to the context. + /// The value of the variable to add to the context. + /// + /// Value type conversion for XPath evaluation is as follows: + /// + /// + /// CLR Type + /// XPath type + /// + /// + /// System.String + /// XPathResultType.String + /// + /// + /// System.Double (or types that can be converted to) + /// XPathResultType.Number + /// + /// + /// System.Boolean + /// XPathResultType.Boolean + /// + /// + /// System.Xml.XPath.XPathNavigator + /// XPathResultType.Navigator + /// + /// + /// System.Xml.XPath.XPathNodeIterator + /// XPathResultType.NodeSet + /// + /// + /// Others + /// XPathResultType.Any + /// + /// + /// + /// See the topic "Compile, Select, Evaluate, and Matches with + /// XPath and XPathExpressions" in MSDN documentation for additional information. + /// + /// + /// The is null. + public void AddVariable(string name, object value) + { + if (value == null) + { + throw new ArgumentNullException("value"); } - #endregion Internal DynamicVariable class + _variables[name] = new DynamicVariable(name, value); + } + + /// + /// See . Not used in our implementation. + /// + public override IXsltContextFunction ResolveFunction(string prefix, string name, XPathResultType[] argTypes) => + throw new NotImplementedException(); + + /// + /// Resolves the dynamic variables added to the context. See . + /// + public override IXsltContextVariable ResolveVariable(string prefix, string name) + { + IXsltContextVariable var; + _variables.TryGetValue(name, out var!); + return var!; } + + #endregion Variable Handling Code } diff --git a/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs b/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs index 4285f9c97f58..777e3689d757 100644 --- a/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs +++ b/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs @@ -1,122 +1,136 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Umbraco.Extensions; +using System.Globalization; -namespace Umbraco.Cms.Core.Xml +namespace Umbraco.Cms.Core.Xml; + +/// +/// This is used to parse our customize Umbraco XPath expressions (i.e. that include special tokens like $site) into +/// a real XPath statement +/// +public class UmbracoXPathPathSyntaxParser { /// - /// This is used to parse our customize Umbraco XPath expressions (i.e. that include special tokens like $site) into - /// a real XPath statement + /// Parses custom umbraco xpath expression /// - public class UmbracoXPathPathSyntaxParser + /// The Xpath expression + /// + /// The current node id context of executing the query - null if there is no current node, in which case + /// some of the parameters like $current, $parent, $site will be disabled + /// + /// The callback to create the nodeId path, given a node Id + /// The callback to return whether a published node exists based on Id + /// + public static string ParseXPathQuery( + string xpathExpression, + int? nodeContextId, + Func?> getPath, + Func publishedContentExists) { - /// - /// Parses custom umbraco xpath expression - /// - /// The Xpath expression - /// - /// The current node id context of executing the query - null if there is no current node, in which case - /// some of the parameters like $current, $parent, $site will be disabled - /// - /// The callback to create the nodeId path, given a node Id - /// The callback to return whether a published node exists based on Id - /// - public static string ParseXPathQuery( - string xpathExpression, - int? nodeContextId, - Func?> getPath, - Func publishedContentExists) + // TODO: This should probably support some of the old syntax and token replacements, currently + // it does not, there is a ticket raised here about it: http://issues.umbraco.org/issue/U4-6364 + // previous tokens were: "$currentPage", "$ancestorOrSelf", "$parentPage" and I believe they were + // allowed 'inline', not just at the beginning... whether or not we want to support that is up + // for discussion. + + if (xpathExpression == null) { + throw new ArgumentNullException(nameof(xpathExpression)); + } - // TODO: This should probably support some of the old syntax and token replacements, currently - // it does not, there is a ticket raised here about it: http://issues.umbraco.org/issue/U4-6364 - // previous tokens were: "$currentPage", "$ancestorOrSelf", "$parentPage" and I believe they were - // allowed 'inline', not just at the beginning... whether or not we want to support that is up - // for discussion. + if (string.IsNullOrWhiteSpace(xpathExpression)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(xpathExpression)); + } - if (xpathExpression == null) throw new ArgumentNullException(nameof(xpathExpression)); - if (string.IsNullOrWhiteSpace(xpathExpression)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(xpathExpression)); - if (getPath == null) throw new ArgumentNullException(nameof(getPath)); - if (publishedContentExists == null) throw new ArgumentNullException(nameof(publishedContentExists)); + if (getPath == null) + { + throw new ArgumentNullException(nameof(getPath)); + } - //no need to parse it - if (xpathExpression.StartsWith("$") == false) - return xpathExpression; + if (publishedContentExists == null) + { + throw new ArgumentNullException(nameof(publishedContentExists)); + } - //get nearest published item - Func?, int> getClosestPublishedAncestor = path => + //no need to parse it + if (xpathExpression.StartsWith("$") == false) + { + return xpathExpression; + } + + //get nearest published item + Func?, int> getClosestPublishedAncestor = path => + { + if (path is not null) { - if (path is not null) + foreach (var i in path) { - foreach (var i in path) + int idAsInt; + if (int.TryParse(i, NumberStyles.Integer, CultureInfo.InvariantCulture, out idAsInt)) { - int idAsInt; - if (int.TryParse(i, NumberStyles.Integer, CultureInfo.InvariantCulture, out idAsInt)) + var exists = publishedContentExists(int.Parse(i, CultureInfo.InvariantCulture)); + if (exists) { - var exists = publishedContentExists(int.Parse(i, CultureInfo.InvariantCulture)); - if (exists) - return idAsInt; + return idAsInt; } } } + } - return -1; - }; + return -1; + }; - const string rootXpath = "id({0})"; + const string rootXpath = "id({0})"; - //parseable items: - var vars = new Dictionary>(); + //parseable items: + var vars = new Dictionary>(); - //These parameters must have a node id context - if (nodeContextId.HasValue) + //These parameters must have a node id context + if (nodeContextId.HasValue) + { + vars.Add("$current", q => { - vars.Add("$current", q => - { - var closestPublishedAncestorId = getClosestPublishedAncestor(getPath(nodeContextId.Value)); - return q.Replace("$current", string.Format(rootXpath, closestPublishedAncestorId)); - }); + var closestPublishedAncestorId = getClosestPublishedAncestor(getPath(nodeContextId.Value)); + return q.Replace("$current", string.Format(rootXpath, closestPublishedAncestorId)); + }); - vars.Add("$parent", q => + vars.Add("$parent", q => + { + //remove the first item in the array if its the current node + //this happens when current is published, but we are looking for its parent specifically + var path = getPath(nodeContextId.Value)?.ToArray(); + if (path?[0] == nodeContextId.ToString()) { - //remove the first item in the array if its the current node - //this happens when current is published, but we are looking for its parent specifically - var path = getPath(nodeContextId.Value)?.ToArray(); - if (path?[0] == nodeContextId.ToString()) - { - path = path?.Skip(1).ToArray(); - } + path = path?.Skip(1).ToArray(); + } - var closestPublishedAncestorId = getClosestPublishedAncestor(path); - return q.Replace("$parent", string.Format(rootXpath, closestPublishedAncestorId)); - }); + var closestPublishedAncestorId = getClosestPublishedAncestor(path); + return q.Replace("$parent", string.Format(rootXpath, closestPublishedAncestorId)); + }); - vars.Add("$site", q => - { - var closestPublishedAncestorId = getClosestPublishedAncestor(getPath(nodeContextId.Value)); - return q.Replace("$site", string.Format(rootXpath, closestPublishedAncestorId) + "/ancestor-or-self::*[@level = 1]"); - }); - } + vars.Add("$site", q => + { + var closestPublishedAncestorId = getClosestPublishedAncestor(getPath(nodeContextId.Value)); + return q.Replace("$site", + string.Format(rootXpath, closestPublishedAncestorId) + "/ancestor-or-self::*[@level = 1]"); + }); + } - // TODO: This used to just replace $root with string.Empty BUT, that would never work - // the root is always "/root . Need to confirm with Per why this was string.Empty before! - vars.Add("$root", q => q.Replace("$root", "/root")); + // TODO: This used to just replace $root with string.Empty BUT, that would never work + // the root is always "/root . Need to confirm with Per why this was string.Empty before! + vars.Add("$root", q => q.Replace("$root", "/root")); - foreach (var varible in vars) + foreach (KeyValuePair> varible in vars) + { + if (xpathExpression.StartsWith(varible.Key)) { - if (xpathExpression.StartsWith(varible.Key)) - { - xpathExpression = varible.Value(xpathExpression); - break; - } + xpathExpression = varible.Value(xpathExpression); + break; } - - return xpathExpression; } + return xpathExpression; } } diff --git a/src/Umbraco.Core/Xml/XPath/INavigableContent.cs b/src/Umbraco.Core/Xml/XPath/INavigableContent.cs index c1a4e6c3e464..b26b11514688 100644 --- a/src/Umbraco.Core/Xml/XPath/INavigableContent.cs +++ b/src/Umbraco.Core/Xml/XPath/INavigableContent.cs @@ -1,59 +1,62 @@ -using System.Collections.Generic; +namespace Umbraco.Cms.Core.Xml.XPath; -namespace Umbraco.Cms.Core.Xml.XPath +/// +/// Represents a content that can be navigated via XPath. +/// +public interface INavigableContent { /// - /// Represents a content that can be navigated via XPath. + /// Gets the unique identifier of the navigable content. /// - public interface INavigableContent - { - /// - /// Gets the unique identifier of the navigable content. - /// - /// The root node identifier should be -1. - int Id { get; } + /// The root node identifier should be -1. + int Id { get; } - /// - /// Gets the unique identifier of parent of the navigable content. - /// - /// The top-level content parent identifiers should be -1 ie the identifier - /// of the root node, whose parent identifier should in turn be -1. - int ParentId { get; } + /// + /// Gets the unique identifier of parent of the navigable content. + /// + /// + /// The top-level content parent identifiers should be -1 ie the identifier + /// of the root node, whose parent identifier should in turn be -1. + /// + int ParentId { get; } - /// - /// Gets the type of the navigable content. - /// - INavigableContentType Type { get; } + /// + /// Gets the type of the navigable content. + /// + INavigableContentType Type { get; } - /// - /// Gets the unique identifiers of the children of the navigable content. - /// - IList? ChildIds { get; } + /// + /// Gets the unique identifiers of the children of the navigable content. + /// + IList? ChildIds { get; } - /// - /// Gets the value of a field of the navigable content for XPath navigation use. - /// - /// The field index. - /// The value of the field for XPath navigation use. - /// - /// Fields are attributes or elements depending on their relative index value compared - /// to source.LastAttributeIndex. - /// For attributes, the value must be a string. - /// For elements, the value should an XPathNavigator instance if the field is xml - /// and has content (is not empty), null to indicate that the element is empty, or a string - /// which can be empty, whitespace... depending on what the data type wants to expose. - /// - object? Value(int index); + /// + /// Gets the value of a field of the navigable content for XPath navigation use. + /// + /// The field index. + /// The value of the field for XPath navigation use. + /// + /// + /// Fields are attributes or elements depending on their relative index value compared + /// to source.LastAttributeIndex. + /// + /// For attributes, the value must be a string. + /// + /// For elements, the value should an XPathNavigator instance if the field is xml + /// and has content (is not empty), null to indicate that the element is empty, or a string + /// which can be empty, whitespace... depending on what the data type wants to expose. + /// + /// + object? Value(int index); - // TODO: implement the following one + // TODO: implement the following one - ///// - ///// Gets the value of a field of the navigable content, for a specified language. - ///// - ///// The field index. - ///// The language key. - ///// The value of the field for the specified language. - ///// ... - //object Value(int index, string languageKey); - } + ///// + ///// Gets the value of a field of the navigable content, for a specified language. + ///// + ///// The field index. + ///// The language key. + ///// The value of the field for the specified language. + ///// ... + //object Value(int index, string languageKey); } diff --git a/src/Umbraco.Core/Xml/XPath/INavigableContentType.cs b/src/Umbraco.Core/Xml/XPath/INavigableContentType.cs index 2e214d5e9a3d..6a8edfb0d6f7 100644 --- a/src/Umbraco.Core/Xml/XPath/INavigableContentType.cs +++ b/src/Umbraco.Core/Xml/XPath/INavigableContentType.cs @@ -1,19 +1,18 @@ -namespace Umbraco.Cms.Core.Xml.XPath +namespace Umbraco.Cms.Core.Xml.XPath; + +/// +/// Represents the type of a content that can be navigated via XPath. +/// +public interface INavigableContentType { /// - /// Represents the type of a content that can be navigated via XPath. + /// Gets the name of the content type. /// - public interface INavigableContentType - { - /// - /// Gets the name of the content type. - /// - string? Name { get; } + string? Name { get; } - /// - /// Gets the field types of the content type. - /// - /// This includes the attributes and the properties. - INavigableFieldType[] FieldTypes { get; } - } + /// + /// Gets the field types of the content type. + /// + /// This includes the attributes and the properties. + INavigableFieldType[] FieldTypes { get; } } diff --git a/src/Umbraco.Core/Xml/XPath/INavigableFieldType.cs b/src/Umbraco.Core/Xml/XPath/INavigableFieldType.cs index 0b66cc0626d7..9d5445999949 100644 --- a/src/Umbraco.Core/Xml/XPath/INavigableFieldType.cs +++ b/src/Umbraco.Core/Xml/XPath/INavigableFieldType.cs @@ -1,23 +1,22 @@ -using System; +namespace Umbraco.Cms.Core.Xml.XPath; -namespace Umbraco.Cms.Core.Xml.XPath +/// +/// Represents the type of a field of a content that can be navigated via XPath. +/// +/// A field can be an attribute or a property. +public interface INavigableFieldType { /// - /// Represents the type of a field of a content that can be navigated via XPath. + /// Gets the name of the field type. /// - /// A field can be an attribute or a property. - public interface INavigableFieldType - { - /// - /// Gets the name of the field type. - /// - string Name { get; } + string Name { get; } - /// - /// Gets a method to convert the field value to a string. - /// - /// This is for built-in properties, ie attributes. User-defined properties have their - /// own way to convert their value for XPath. - Func? XmlStringConverter { get; } - } + /// + /// Gets a method to convert the field value to a string. + /// + /// + /// This is for built-in properties, ie attributes. User-defined properties have their + /// own way to convert their value for XPath. + /// + Func? XmlStringConverter { get; } } diff --git a/src/Umbraco.Core/Xml/XPath/INavigableSource.cs b/src/Umbraco.Core/Xml/XPath/INavigableSource.cs index 76b43b618c9d..67ab52ae8e60 100644 --- a/src/Umbraco.Core/Xml/XPath/INavigableSource.cs +++ b/src/Umbraco.Core/Xml/XPath/INavigableSource.cs @@ -1,29 +1,30 @@ -namespace Umbraco.Cms.Core.Xml.XPath +namespace Umbraco.Cms.Core.Xml.XPath; + +/// +/// Represents a source of content that can be navigated via XPath. +/// +public interface INavigableSource { /// - /// Represents a source of content that can be navigated via XPath. + /// Gets the index of the last attribute in the fields collections. /// - public interface INavigableSource - { - /// - /// Gets a content identified by its unique identifier. - /// - /// The unique identifier. - /// The content identified by the unique identifier, or null. - /// When id is -1 (root content) implementations should return null. - INavigableContent? Get(int id); + int LastAttributeIndex { get; } - /// - /// Gets the index of the last attribute in the fields collections. - /// - int LastAttributeIndex { get; } + /// + /// Gets the content at the root of the source. + /// + /// + /// That content should have unique identifier -1 and should not be gettable, + /// ie Get(-1) should return null. Its ParentId should be -1. It should provide + /// values for the attribute fields. + /// + INavigableContent Root { get; } - /// - /// Gets the content at the root of the source. - /// - /// That content should have unique identifier -1 and should not be gettable, - /// ie Get(-1) should return null. Its ParentId should be -1. It should provide - /// values for the attribute fields. - INavigableContent Root { get; } - } + /// + /// Gets a content identified by its unique identifier. + /// + /// The unique identifier. + /// The content identified by the unique identifier, or null. + /// When id is -1 (root content) implementations should return null. + INavigableContent? Get(int id); } diff --git a/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs b/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs index 2e2819066bf5..186d80e0bf93 100644 --- a/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs +++ b/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs @@ -1,1048 +1,1100 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; +using System.Diagnostics; using System.Xml; using System.Xml.XPath; -namespace Umbraco.Cms.Core.Xml.XPath +namespace Umbraco.Cms.Core.Xml.XPath; + +/// +/// Provides a cursor model for navigating {macro /} as if it were XML. +/// +public class MacroNavigator : XPathNavigator { + private readonly MacroRoot _macro; + private readonly XmlNameTable _nameTable; + /// - /// Provides a cursor model for navigating {macro /} as if it were XML. + /// Gets a value indicating whether the current node is an empty element without an end element tag. /// - public class MacroNavigator : XPathNavigator + public override bool IsEmptyElement { - private readonly XmlNameTable _nameTable; - private readonly MacroRoot _macro; - private State _state; - - #region Constructor - - /// - /// Initializes a new instance of the class with macro parameters. - /// - /// The macro parameters. - public MacroNavigator(IEnumerable parameters) - : this(new MacroRoot(parameters), new NameTable(), new State()) - { } - - /// - /// Initializes a new instance of the class with a macro node, - /// a name table and a state. - /// - /// The macro node. - /// The name table. - /// The state. - /// Privately used for cloning a navigator. - private MacroNavigator(MacroRoot macro, XmlNameTable nameTable, State state) - { - _macro = macro; - _nameTable = nameTable; - _state = state; - } - - #endregion - - #region Diagnostics - - // diagnostics code will not be compiled nor called into Release configuration. - // in Debug configuration, uncomment lines in Debug() to write to console or to log. - // - // much of this code is duplicated in each navigator due to conditional compilation - -#if DEBUG - private const string Tabs = " "; - private int _tabs; - private readonly int _uid = GetUid(); - private static int _uidg; - private static readonly object Uidl = new object(); - private static int GetUid() - { - lock (Uidl) - { - return _uidg++; - } - } -#endif - - [Conditional("DEBUG")] - void DebugEnter(string name) - { -#if DEBUG - Debug(""); - DebugState(":"); - Debug(name); - _tabs = Math.Min(Tabs.Length, _tabs + 2); -#endif - } - - [Conditional("DEBUG")] - void DebugCreate(MacroNavigator nav) - { -#if DEBUG - Debug("Create: [MacroNavigator::{0}]", nav._uid); -#endif - } - - [Conditional("DEBUG")] - private void DebugReturn() - { -#if DEBUG -// ReSharper disable IntroduceOptionalParameters.Local - DebugReturn("(void)"); -// ReSharper restore IntroduceOptionalParameters.Local -#endif - } - - [Conditional("DEBUG")] - private void DebugReturn(bool value) + get { -#if DEBUG - DebugReturn(value ? "true" : "false"); -#endif - } - - [Conditional("DEBUG")] - void DebugReturn(string format, params object[] args) - { -#if DEBUG - Debug("=> " + format, args); - if (_tabs > 0) _tabs -= 2; -#endif - } + DebugEnter("IsEmptyElement"); + bool isEmpty; - [Conditional("DEBUG")] - void DebugState(string s = " =>") - { -#if DEBUG - string position; - - switch (_state.Position) + switch (InternalState.Position) { case StatePosition.Macro: - position = "At macro."; + isEmpty = _macro.Parameters.Length == 0; break; case StatePosition.Parameter: - position = string.Format("At parameter '{0}'.", - _macro.Parameters[_state.ParameterIndex].Name); - break; - case StatePosition.ParameterAttribute: - position = string.Format("At parameter attribute '{0}/{1}'.", - _macro.Parameters[_state.ParameterIndex].Name, - _macro.Parameters[_state.ParameterIndex].Attributes?[_state.ParameterAttributeIndex].Key); + MacroParameter parameter = _macro.Parameters[InternalState.ParameterIndex]; + XPathNavigator nav = parameter.NavigatorValue; + if (parameter.WrapNavigatorInNodes || nav != null) + { + isEmpty = false; + } + else + { + var s = _macro.Parameters[InternalState.ParameterIndex].StringValue; + isEmpty = s == null; + } + break; case StatePosition.ParameterNavigator: - position = string.Format("In parameter '{0}{1}' navigator.", - _macro.Parameters[_state.ParameterIndex].Name, - _macro.Parameters[_state.ParameterIndex].WrapNavigatorInNodes ? "/nodes" : ""); + isEmpty = InternalState.ParameterNavigator?.IsEmptyElement ?? true; break; case StatePosition.ParameterNodes: - position = string.Format("At parameter '{0}/nodes'.", - _macro.Parameters[_state.ParameterIndex].Name); + isEmpty = _macro.Parameters[InternalState.ParameterIndex].NavigatorValue == null; break; + case StatePosition.ParameterAttribute: case StatePosition.ParameterText: - position = string.Format("In parameter '{0}' text.", - _macro.Parameters[_state.ParameterIndex].Name); - break; case StatePosition.Root: - position = "At root."; - break; + throw new InvalidOperationException("Not an element."); default: throw new InvalidOperationException("Invalid position."); } - Debug("State{0} {1}", s, position); -#endif - } - -#if DEBUG - void Debug(string format, params object[] args) - { - // remove comments to write - - format = "[" + _uid.ToString("00000") + "] " + Tabs.Substring(0, _tabs) + format; -#pragma warning disable 168 - var msg = string.Format(format, args); // unused if not writing, hence #pragma -#pragma warning restore 168 - } -#endif - - #endregion - - #region Macro - - private class MacroRoot - { - public MacroRoot(IEnumerable parameters) - { - Parameters = parameters == null ? new MacroParameter[] {} : parameters.ToArray(); - } - - public MacroParameter[] Parameters { get; private set; } - } - - public class MacroParameter - { - // note: assuming we're not thinking about supporting - // XPathIterator in parameters - enough nonsense! - - public MacroParameter(string name, string value) - { - Name = name; - StringValue = value; - } - - public MacroParameter(string name, XPathNavigator navigator, - int maxNavigatorDepth = int.MaxValue, - bool wrapNavigatorInNodes = false, - IEnumerable>? attributes = null) - { - Name = name; - MaxNavigatorDepth = maxNavigatorDepth; - WrapNavigatorInNodes = wrapNavigatorInNodes; - if (attributes != null) - { - var a = attributes.ToArray(); - if (a.Length > 0) - Attributes = a; - } - NavigatorValue = navigator; // should not be empty - } - - public string Name { get; private set; } - public string? StringValue { get; private set; } - public XPathNavigator? NavigatorValue { get; private set; } - public int MaxNavigatorDepth { get; private set; } - public bool WrapNavigatorInNodes { get; private set; } - public KeyValuePair[]? Attributes { get; private set; } - } - - #endregion - - /// - /// Creates a new XPathNavigator positioned at the same node as this XPathNavigator. - /// - /// A new XPathNavigator positioned at the same node as this XPathNavigator. - public override XPathNavigator Clone() - { - DebugEnter("Clone"); - var nav = new MacroNavigator(_macro, _nameTable, _state.Clone()); - DebugCreate(nav); - DebugReturn("[XPathNavigator]"); - return nav; - } - - /// - /// Gets a value indicating whether the current node is an empty element without an end element tag. - /// - public override bool IsEmptyElement - { - get - { - DebugEnter("IsEmptyElement"); - bool isEmpty; - - switch (_state.Position) - { - case StatePosition.Macro: - isEmpty = _macro.Parameters.Length == 0; - break; - case StatePosition.Parameter: - var parameter = _macro.Parameters[_state.ParameterIndex]; - var nav = parameter.NavigatorValue; - if (parameter.WrapNavigatorInNodes || nav != null) - { - isEmpty = false; - } - else - { - var s = _macro.Parameters[_state.ParameterIndex].StringValue; - isEmpty = s == null; - } - break; - case StatePosition.ParameterNavigator: - isEmpty = _state.ParameterNavigator?.IsEmptyElement ?? true; - break; - case StatePosition.ParameterNodes: - isEmpty = _macro.Parameters[_state.ParameterIndex].NavigatorValue == null; - break; - case StatePosition.ParameterAttribute: - case StatePosition.ParameterText: - case StatePosition.Root: - throw new InvalidOperationException("Not an element."); - default: - throw new InvalidOperationException("Invalid position."); - } - - DebugReturn(isEmpty); - return isEmpty; - } + DebugReturn(isEmpty); + return isEmpty; } + } - /// - /// Determines whether the current XPathNavigator is at the same position as the specified XPathNavigator. - /// - /// The XPathNavigator to compare to this XPathNavigator. - /// true if the two XPathNavigator objects have the same position; otherwise, false. - public override bool IsSamePosition(XPathNavigator nav) + /// + /// Gets the qualified name of the current node. + /// + public override string Name + { + get { - DebugEnter("IsSamePosition"); - bool isSame; + DebugEnter("Name"); + string name; - switch (_state.Position) + switch (InternalState.Position) { - case StatePosition.ParameterNavigator: case StatePosition.Macro: + name = "macro"; + break; case StatePosition.Parameter: + name = _macro.Parameters[InternalState.ParameterIndex].Name; + break; case StatePosition.ParameterAttribute: + name = _macro.Parameters[InternalState.ParameterIndex] + .Attributes?[InternalState.ParameterAttributeIndex].Key ?? string.Empty; + break; + case StatePosition.ParameterNavigator: + name = InternalState.ParameterNavigator?.Name ?? string.Empty; + break; case StatePosition.ParameterNodes: + name = "nodes"; + break; case StatePosition.ParameterText: case StatePosition.Root: - var other = nav as MacroNavigator; - isSame = other != null && other._macro == _macro && _state.IsSamePosition(other._state); + name = string.Empty; break; default: throw new InvalidOperationException("Invalid position."); } - DebugReturn(isSame); - return isSame; - } - - /// - /// Gets the qualified name of the current node. - /// - public override string Name - { - get - { - DebugEnter("Name"); - string name; - - switch (_state.Position) - { - case StatePosition.Macro: - name = "macro"; - break; - case StatePosition.Parameter: - name = _macro.Parameters[_state.ParameterIndex].Name; - break; - case StatePosition.ParameterAttribute: - name = _macro.Parameters[_state.ParameterIndex].Attributes?[_state.ParameterAttributeIndex].Key ?? string.Empty; - break; - case StatePosition.ParameterNavigator: - name = _state.ParameterNavigator?.Name ?? string.Empty; - break; - case StatePosition.ParameterNodes: - name = "nodes"; - break; - case StatePosition.ParameterText: - case StatePosition.Root: - name = string.Empty; - break; - default: - throw new InvalidOperationException("Invalid position."); - } - - DebugReturn("\"{0}\"", name); - return name; - } + DebugReturn("\"{0}\"", name); + return name; } + } - /// - /// Gets the Name of the current node without any namespace prefix. - /// - public override string LocalName + /// + /// Gets the Name of the current node without any namespace prefix. + /// + public override string LocalName + { + get { - get - { - DebugEnter("LocalName"); - var name = Name; - DebugReturn("\"{0}\"", name); - return name; - } + DebugEnter("LocalName"); + var name = Name; + DebugReturn("\"{0}\"", name); + return name; } + } - /// - /// Moves the XPathNavigator to the same position as the specified XPathNavigator. - /// - /// The XPathNavigator positioned on the node that you want to move to. - /// Returns true if the XPathNavigator is successful moving to the same position as the specified XPathNavigator; - /// otherwise, false. If false, the position of the XPathNavigator is unchanged. - public override bool MoveTo(XPathNavigator nav) - { - DebugEnter("MoveTo"); - - var other = nav as MacroNavigator; - var succ = false; + /// + /// Gets the base URI for the current node. + /// + public override string BaseURI => string.Empty; - if (other != null && other._macro == _macro) - { - _state = other._state.Clone(); - DebugState(); - succ = true; - } + /// + /// Gets the XmlNameTable of the XPathNavigator. + /// + public override XmlNameTable NameTable => _nameTable; - DebugReturn(succ); - return succ; - } + /// + /// Gets the namespace URI of the current node. + /// + public override string NamespaceURI => string.Empty; - /// - /// Moves the XPathNavigator to the first attribute of the current node. - /// - /// Returns true if the XPathNavigator is successful moving to the first attribute of the current node; - /// otherwise, false. If false, the position of the XPathNavigator is unchanged. - public override bool MoveToFirstAttribute() + /// + /// Gets the XPathNodeType of the current node. + /// + public override XPathNodeType NodeType + { + get { - DebugEnter("MoveToFirstAttribute"); - bool succ; + DebugEnter("NodeType"); + XPathNodeType type; - switch (_state.Position) + switch (InternalState.Position) { - case StatePosition.ParameterNavigator: - succ = _state.ParameterNavigator?.MoveToFirstAttribute() ?? false; - break; + case StatePosition.Macro: case StatePosition.Parameter: - if (_macro.Parameters[_state.ParameterIndex].Attributes != null) - { - _state.Position = StatePosition.ParameterAttribute; - _state.ParameterAttributeIndex = 0; - succ = true; - DebugState(); - } - else succ = false; + case StatePosition.ParameterNodes: + type = XPathNodeType.Element; + break; + case StatePosition.ParameterNavigator: + type = InternalState.ParameterNavigator?.NodeType ?? XPathNodeType.Root; break; case StatePosition.ParameterAttribute: - case StatePosition.ParameterNodes: - case StatePosition.Macro: + type = XPathNodeType.Attribute; + break; case StatePosition.ParameterText: + type = XPathNodeType.Text; + break; case StatePosition.Root: - succ = false; + type = XPathNodeType.Root; break; default: throw new InvalidOperationException("Invalid position."); } - DebugReturn(succ); - return succ; + DebugReturn("\'{0}\'", type); + return type; } + } + + /// + /// Gets the namespace prefix associated with the current node. + /// + public override string Prefix => string.Empty; - /// - /// Moves the XPathNavigator to the first child node of the current node. - /// - /// Returns true if the XPathNavigator is successful moving to the first child node of the current node; - /// otherwise, false. If false, the position of the XPathNavigator is unchanged. - public override bool MoveToFirstChild() + /// + /// Gets the string value of the item. + /// + /// + /// Does not fully behave as per the specs, as we report empty value on root and macro elements, and we start + /// reporting values only on parameter elements. This is because, otherwise, we would might dump the whole database + /// and it probably does not make sense at Umbraco level. + /// + public override string Value + { + get { - DebugEnter("MoveToFirstChild"); - bool succ; + DebugEnter("Value"); + string value; - switch (_state.Position) + XPathNavigator? nav; + switch (InternalState.Position) { - case StatePosition.Macro: - if (_macro.Parameters.Length == 0) - { - succ = false; - } - else - { - _state.ParameterIndex = 0; - _state.Position = StatePosition.Parameter; - succ = true; - } - break; case StatePosition.Parameter: - var parameter = _macro.Parameters[_state.ParameterIndex]; - var nav = parameter.NavigatorValue; - if (parameter.WrapNavigatorInNodes) - { - _state.Position = StatePosition.ParameterNodes; - DebugState(); - succ = true; - } - else if (nav != null) + nav = _macro.Parameters[InternalState.ParameterIndex].NavigatorValue; + if (nav != null) { nav = nav.Clone(); // never use the raw parameter's navigator nav.MoveToFirstChild(); - _state.ParameterNavigator = nav; - _state.ParameterNavigatorDepth = 0; - _state.Position = StatePosition.ParameterNavigator; - DebugState(); - succ = true; + value = nav.Value; } else { - var s = _macro.Parameters[_state.ParameterIndex].StringValue; - if (s != null) - { - _state.Position = StatePosition.ParameterText; - DebugState(); - succ = true; - } - else succ = false; + var s = _macro.Parameters[InternalState.ParameterIndex].StringValue; + value = s ?? string.Empty; } + + break; + case StatePosition.ParameterAttribute: + value = _macro.Parameters[InternalState.ParameterIndex] + .Attributes?[InternalState.ParameterAttributeIndex].Value ?? string.Empty; break; case StatePosition.ParameterNavigator: - if (_state.ParameterNavigatorDepth == _macro.Parameters[_state.ParameterIndex].MaxNavigatorDepth) + value = InternalState.ParameterNavigator?.Value ?? string.Empty; + break; + case StatePosition.ParameterNodes: + nav = _macro.Parameters[InternalState.ParameterIndex].NavigatorValue; + if (nav == null) { - succ = false; + value = string.Empty; } else { - // move to first doc child => increment depth, else (property child) do nothing - succ = _state.ParameterNavigator?.MoveToFirstChild() ?? false; - if (succ && IsDoc(_state.ParameterNavigator)) - { - ++_state.ParameterNavigatorDepth; - DebugState(); - } - } - break; - case StatePosition.ParameterNodes: - if (_macro.Parameters[_state.ParameterIndex].NavigatorValue != null) - { - // never use the raw parameter's navigator - _state.ParameterNavigator = _macro.Parameters[_state.ParameterIndex].NavigatorValue?.Clone(); - _state.Position = StatePosition.ParameterNavigator; - succ = true; - DebugState(); + nav = nav.Clone(); // never use the raw parameter's navigator + nav.MoveToFirstChild(); + value = nav.Value; } - else succ = false; + break; - case StatePosition.ParameterAttribute: case StatePosition.ParameterText: - succ = false; + value = _macro.Parameters[InternalState.ParameterIndex].StringValue ?? string.Empty; break; + case StatePosition.Macro: case StatePosition.Root: - _state.Position = StatePosition.Macro; - DebugState(); - succ = true; + value = string.Empty; break; default: throw new InvalidOperationException("Invalid position."); } - DebugReturn(succ); - return succ; + DebugReturn("\"{0}\"", value); + return value; } + } - /// - /// Moves the XPathNavigator to the first namespace node that matches the XPathNamespaceScope specified. - /// - /// An XPathNamespaceScope value describing the namespace scope. - /// Returns true if the XPathNavigator is successful moving to the first namespace node; - /// otherwise, false. If false, the position of the XPathNavigator is unchanged. - public override bool MoveToFirstNamespace(XPathNamespaceScope namespaceScope) - { - DebugEnter("MoveToFirstNamespace"); - DebugReturn(false); - return false; - } + /// + /// Creates a new XPathNavigator positioned at the same node as this XPathNavigator. + /// + /// A new XPathNavigator positioned at the same node as this XPathNavigator. + public override XPathNavigator Clone() + { + DebugEnter("Clone"); + var nav = new MacroNavigator(_macro, _nameTable, InternalState.Clone()); + DebugCreate(nav); + DebugReturn("[XPathNavigator]"); + return nav; + } + + /// + /// Determines whether the current XPathNavigator is at the same position as the specified XPathNavigator. + /// + /// The XPathNavigator to compare to this XPathNavigator. + /// true if the two XPathNavigator objects have the same position; otherwise, false. + public override bool IsSamePosition(XPathNavigator nav) + { + DebugEnter("IsSamePosition"); + bool isSame; - /// - /// Moves the XPathNavigator to the next namespace node matching the XPathNamespaceScope specified. - /// - /// An XPathNamespaceScope value describing the namespace scope. - /// Returns true if the XPathNavigator is successful moving to the next namespace node; - /// otherwise, false. If false, the position of the XPathNavigator is unchanged. - public override bool MoveToNextNamespace(XPathNamespaceScope namespaceScope) + switch (InternalState.Position) { - DebugEnter("MoveToNextNamespace"); - DebugReturn(false); - return false; + case StatePosition.ParameterNavigator: + case StatePosition.Macro: + case StatePosition.Parameter: + case StatePosition.ParameterAttribute: + case StatePosition.ParameterNodes: + case StatePosition.ParameterText: + case StatePosition.Root: + var other = nav as MacroNavigator; + isSame = other != null && other._macro == _macro && InternalState.IsSamePosition(other.InternalState); + break; + default: + throw new InvalidOperationException("Invalid position."); } - /// - /// Moves to the node that has an attribute of type ID whose value matches the specified String. - /// - /// A String representing the ID value of the node to which you want to move. - /// true if the XPathNavigator is successful moving; otherwise, false. - /// If false, the position of the navigator is unchanged. - public override bool MoveToId(string id) + DebugReturn(isSame); + return isSame; + } + + /// + /// Moves the XPathNavigator to the same position as the specified XPathNavigator. + /// + /// The XPathNavigator positioned on the node that you want to move to. + /// + /// Returns true if the XPathNavigator is successful moving to the same position as the specified XPathNavigator; + /// otherwise, false. If false, the position of the XPathNavigator is unchanged. + /// + public override bool MoveTo(XPathNavigator nav) + { + DebugEnter("MoveTo"); + + var other = nav as MacroNavigator; + var succ = false; + + if (other != null && other._macro == _macro) { - DebugEnter("MoveToId"); - // impossible to implement since parameters can contain duplicate fragments of the - // main xml and therefore there can be duplicate unique node identifiers. - DebugReturn("NotImplementedException"); - throw new NotImplementedException(); + InternalState = other.InternalState.Clone(); + DebugState(); + succ = true; } - /// - /// Moves the XPathNavigator to the next sibling node of the current node. - /// - /// true if the XPathNavigator is successful moving to the next sibling node; - /// otherwise, false if there are no more siblings or if the XPathNavigator is currently - /// positioned on an attribute node. If false, the position of the XPathNavigator is unchanged. - public override bool MoveToNext() - { - DebugEnter("MoveToNext"); - bool succ; + DebugReturn(succ); + return succ; + } - switch (_state.Position) - { - case StatePosition.Parameter: - if (_state.ParameterIndex == _macro.Parameters.Length - 1) - { - succ = false; - } - else - { - ++_state.ParameterIndex; - DebugState(); - succ = true; - } - break; - case StatePosition.ParameterNavigator: - var wasDoc = IsDoc(_state.ParameterNavigator); - succ = _state.ParameterNavigator?.MoveToNext() ?? false; - if (succ && !wasDoc && IsDoc(_state.ParameterNavigator)) - { - // move to first doc child => increment depth, else (another property child) do nothing - if (_state.ParameterNavigatorDepth == _macro.Parameters[_state.ParameterIndex].MaxNavigatorDepth) - { - _state.ParameterNavigator?.MoveToPrevious(); - succ = false; - } - else - { - ++_state.ParameterNavigatorDepth; - DebugState(); - } - } - break; - case StatePosition.ParameterNodes: - case StatePosition.ParameterAttribute: - case StatePosition.ParameterText: - case StatePosition.Macro: - case StatePosition.Root: + /// + /// Moves the XPathNavigator to the first attribute of the current node. + /// + /// + /// Returns true if the XPathNavigator is successful moving to the first attribute of the current node; + /// otherwise, false. If false, the position of the XPathNavigator is unchanged. + /// + public override bool MoveToFirstAttribute() + { + DebugEnter("MoveToFirstAttribute"); + bool succ; + + switch (InternalState.Position) + { + case StatePosition.ParameterNavigator: + succ = InternalState.ParameterNavigator?.MoveToFirstAttribute() ?? false; + break; + case StatePosition.Parameter: + if (_macro.Parameters[InternalState.ParameterIndex].Attributes != null) + { + InternalState.Position = StatePosition.ParameterAttribute; + InternalState.ParameterAttributeIndex = 0; + succ = true; + DebugState(); + } + else + { succ = false; - break; - default: - throw new InvalidOperationException("Invalid position."); - } + } - DebugReturn(succ); - return succ; + break; + case StatePosition.ParameterAttribute: + case StatePosition.ParameterNodes: + case StatePosition.Macro: + case StatePosition.ParameterText: + case StatePosition.Root: + succ = false; + break; + default: + throw new InvalidOperationException("Invalid position."); } - /// - /// Moves the XPathNavigator to the previous sibling node of the current node. - /// - /// Returns true if the XPathNavigator is successful moving to the previous sibling node; - /// otherwise, false if there is no previous sibling node or if the XPathNavigator is currently - /// positioned on an attribute node. If false, the position of the XPathNavigator is unchanged. - public override bool MoveToPrevious() - { - DebugEnter("MoveToPrevious"); - bool succ; + DebugReturn(succ); + return succ; + } - switch (_state.Position) - { - case StatePosition.Parameter: - if (_state.ParameterIndex == -1) + /// + /// Moves the XPathNavigator to the first child node of the current node. + /// + /// + /// Returns true if the XPathNavigator is successful moving to the first child node of the current node; + /// otherwise, false. If false, the position of the XPathNavigator is unchanged. + /// + public override bool MoveToFirstChild() + { + DebugEnter("MoveToFirstChild"); + bool succ; + + switch (InternalState.Position) + { + case StatePosition.Macro: + if (_macro.Parameters.Length == 0) + { + succ = false; + } + else + { + InternalState.ParameterIndex = 0; + InternalState.Position = StatePosition.Parameter; + succ = true; + } + + break; + case StatePosition.Parameter: + MacroParameter parameter = _macro.Parameters[InternalState.ParameterIndex]; + XPathNavigator nav = parameter.NavigatorValue; + if (parameter.WrapNavigatorInNodes) + { + InternalState.Position = StatePosition.ParameterNodes; + DebugState(); + succ = true; + } + else if (nav != null) + { + nav = nav.Clone(); // never use the raw parameter's navigator + nav.MoveToFirstChild(); + InternalState.ParameterNavigator = nav; + InternalState.ParameterNavigatorDepth = 0; + InternalState.Position = StatePosition.ParameterNavigator; + DebugState(); + succ = true; + } + else + { + var s = _macro.Parameters[InternalState.ParameterIndex].StringValue; + if (s != null) { - succ = false; + InternalState.Position = StatePosition.ParameterText; + DebugState(); + succ = true; } else { - --_state.ParameterIndex; - DebugState(); - succ = true; + succ = false; } - break; - case StatePosition.ParameterNavigator: - var wasDoc = IsDoc(_state.ParameterNavigator); - succ = _state.ParameterNavigator?.MoveToPrevious() ?? false; - if (succ && wasDoc && !IsDoc(_state.ParameterNavigator)) + } + + break; + case StatePosition.ParameterNavigator: + if (InternalState.ParameterNavigatorDepth == + _macro.Parameters[InternalState.ParameterIndex].MaxNavigatorDepth) + { + succ = false; + } + else + { + // move to first doc child => increment depth, else (property child) do nothing + succ = InternalState.ParameterNavigator?.MoveToFirstChild() ?? false; + if (succ && IsDoc(InternalState.ParameterNavigator)) { - // move from doc child back to property child => decrement depth - --_state.ParameterNavigatorDepth; + ++InternalState.ParameterNavigatorDepth; DebugState(); } - break; - case StatePosition.ParameterAttribute: - case StatePosition.ParameterNodes: - case StatePosition.ParameterText: - case StatePosition.Macro: - case StatePosition.Root: + } + + break; + case StatePosition.ParameterNodes: + if (_macro.Parameters[InternalState.ParameterIndex].NavigatorValue != null) + { + // never use the raw parameter's navigator + InternalState.ParameterNavigator = + _macro.Parameters[InternalState.ParameterIndex].NavigatorValue?.Clone(); + InternalState.Position = StatePosition.ParameterNavigator; + succ = true; + DebugState(); + } + else + { succ = false; - break; - default: - throw new InvalidOperationException("Invalid position."); - } + } - DebugReturn(succ); - return succ; + break; + case StatePosition.ParameterAttribute: + case StatePosition.ParameterText: + succ = false; + break; + case StatePosition.Root: + InternalState.Position = StatePosition.Macro; + DebugState(); + succ = true; + break; + default: + throw new InvalidOperationException("Invalid position."); } - /// - /// Moves the XPathNavigator to the next attribute. - /// - /// Returns true if the XPathNavigator is successful moving to the next attribute; - /// false if there are no more attributes. If false, the position of the XPathNavigator is unchanged. - public override bool MoveToNextAttribute() + DebugReturn(succ); + return succ; + } + + /// + /// Moves the XPathNavigator to the first namespace node that matches the XPathNamespaceScope specified. + /// + /// An XPathNamespaceScope value describing the namespace scope. + /// + /// Returns true if the XPathNavigator is successful moving to the first namespace node; + /// otherwise, false. If false, the position of the XPathNavigator is unchanged. + /// + public override bool MoveToFirstNamespace(XPathNamespaceScope namespaceScope) + { + DebugEnter("MoveToFirstNamespace"); + DebugReturn(false); + return false; + } + + /// + /// Moves the XPathNavigator to the next namespace node matching the XPathNamespaceScope specified. + /// + /// An XPathNamespaceScope value describing the namespace scope. + /// + /// Returns true if the XPathNavigator is successful moving to the next namespace node; + /// otherwise, false. If false, the position of the XPathNavigator is unchanged. + /// + public override bool MoveToNextNamespace(XPathNamespaceScope namespaceScope) + { + DebugEnter("MoveToNextNamespace"); + DebugReturn(false); + return false; + } + + /// + /// Moves to the node that has an attribute of type ID whose value matches the specified String. + /// + /// A String representing the ID value of the node to which you want to move. + /// + /// true if the XPathNavigator is successful moving; otherwise, false. + /// If false, the position of the navigator is unchanged. + /// + public override bool MoveToId(string id) + { + DebugEnter("MoveToId"); + // impossible to implement since parameters can contain duplicate fragments of the + // main xml and therefore there can be duplicate unique node identifiers. + DebugReturn("NotImplementedException"); + throw new NotImplementedException(); + } + + /// + /// Moves the XPathNavigator to the next sibling node of the current node. + /// + /// + /// true if the XPathNavigator is successful moving to the next sibling node; + /// otherwise, false if there are no more siblings or if the XPathNavigator is currently + /// positioned on an attribute node. If false, the position of the XPathNavigator is unchanged. + /// + public override bool MoveToNext() + { + DebugEnter("MoveToNext"); + bool succ; + + switch (InternalState.Position) { - DebugEnter("MoveToNextAttribute"); - bool succ; + case StatePosition.Parameter: + if (InternalState.ParameterIndex == _macro.Parameters.Length - 1) + { + succ = false; + } + else + { + ++InternalState.ParameterIndex; + DebugState(); + succ = true; + } - switch (_state.Position) - { - case StatePosition.ParameterNavigator: - succ = _state.ParameterNavigator?.MoveToNextAttribute() ?? false; - break; - case StatePosition.ParameterAttribute: - if (_state.ParameterAttributeIndex == _macro.Parameters[_state.ParameterIndex].Attributes?.Length - 1) + break; + case StatePosition.ParameterNavigator: + var wasDoc = IsDoc(InternalState.ParameterNavigator); + succ = InternalState.ParameterNavigator?.MoveToNext() ?? false; + if (succ && !wasDoc && IsDoc(InternalState.ParameterNavigator)) + { + // move to first doc child => increment depth, else (another property child) do nothing + if (InternalState.ParameterNavigatorDepth == + _macro.Parameters[InternalState.ParameterIndex].MaxNavigatorDepth) + { + InternalState.ParameterNavigator?.MoveToPrevious(); succ = false; + } else { - ++_state.ParameterAttributeIndex; + ++InternalState.ParameterNavigatorDepth; DebugState(); - succ = true; } - break; - case StatePosition.Parameter: - case StatePosition.ParameterNodes: - case StatePosition.ParameterText: - case StatePosition.Macro: - case StatePosition.Root: - succ = false; - break; - default: - throw new InvalidOperationException("Invalid position."); - } + } - DebugReturn(succ); - return succ; + break; + case StatePosition.ParameterNodes: + case StatePosition.ParameterAttribute: + case StatePosition.ParameterText: + case StatePosition.Macro: + case StatePosition.Root: + succ = false; + break; + default: + throw new InvalidOperationException("Invalid position."); } - /// - /// Moves the XPathNavigator to the parent node of the current node. - /// - /// Returns true if the XPathNavigator is successful moving to the parent node of the current node; - /// otherwise, false. If false, the position of the XPathNavigator is unchanged. - public override bool MoveToParent() - { - DebugEnter("MoveToParent"); - bool succ; + DebugReturn(succ); + return succ; + } - switch (_state.Position) - { - case StatePosition.Macro: - _state.Position = StatePosition.Root; - DebugState(); - succ = true; - break; - case StatePosition.Parameter: - _state.Position = StatePosition.Macro; + /// + /// Moves the XPathNavigator to the previous sibling node of the current node. + /// + /// + /// Returns true if the XPathNavigator is successful moving to the previous sibling node; + /// otherwise, false if there is no previous sibling node or if the XPathNavigator is currently + /// positioned on an attribute node. If false, the position of the XPathNavigator is unchanged. + /// + public override bool MoveToPrevious() + { + DebugEnter("MoveToPrevious"); + bool succ; + + switch (InternalState.Position) + { + case StatePosition.Parameter: + if (InternalState.ParameterIndex == -1) + { + succ = false; + } + else + { + --InternalState.ParameterIndex; DebugState(); succ = true; - break; - case StatePosition.ParameterAttribute: - case StatePosition.ParameterNodes: - _state.Position = StatePosition.Parameter; + } + + break; + case StatePosition.ParameterNavigator: + var wasDoc = IsDoc(InternalState.ParameterNavigator); + succ = InternalState.ParameterNavigator?.MoveToPrevious() ?? false; + if (succ && wasDoc && !IsDoc(InternalState.ParameterNavigator)) + { + // move from doc child back to property child => decrement depth + --InternalState.ParameterNavigatorDepth; DebugState(); - succ = true; - break; - case StatePosition.ParameterNavigator: - var wasDoc = IsDoc(_state.ParameterNavigator); - succ = _state.ParameterNavigator?.MoveToParent() ?? false; - if (succ) - { - // move from doc child => decrement depth - if (wasDoc && --_state.ParameterNavigatorDepth == 0) - { - _state.Position = StatePosition.Parameter; - _state.ParameterNavigator = null; - DebugState(); - } - } - break; - case StatePosition.ParameterText: - _state.Position = StatePosition.Parameter; + } + + break; + case StatePosition.ParameterAttribute: + case StatePosition.ParameterNodes: + case StatePosition.ParameterText: + case StatePosition.Macro: + case StatePosition.Root: + succ = false; + break; + default: + throw new InvalidOperationException("Invalid position."); + } + + DebugReturn(succ); + return succ; + } + + /// + /// Moves the XPathNavigator to the next attribute. + /// + /// + /// Returns true if the XPathNavigator is successful moving to the next attribute; + /// false if there are no more attributes. If false, the position of the XPathNavigator is unchanged. + /// + public override bool MoveToNextAttribute() + { + DebugEnter("MoveToNextAttribute"); + bool succ; + + switch (InternalState.Position) + { + case StatePosition.ParameterNavigator: + succ = InternalState.ParameterNavigator?.MoveToNextAttribute() ?? false; + break; + case StatePosition.ParameterAttribute: + if (InternalState.ParameterAttributeIndex == + _macro.Parameters[InternalState.ParameterIndex].Attributes?.Length - 1) + { + succ = false; + } + else + { + ++InternalState.ParameterAttributeIndex; DebugState(); succ = true; - break; - case StatePosition.Root: - succ = false; - break; - default: - throw new InvalidOperationException("Invalid position."); - } + } - DebugReturn(succ); - return succ; + break; + case StatePosition.Parameter: + case StatePosition.ParameterNodes: + case StatePosition.ParameterText: + case StatePosition.Macro: + case StatePosition.Root: + succ = false; + break; + default: + throw new InvalidOperationException("Invalid position."); } - /// - /// Moves the XPathNavigator to the root node that the current node belongs to. - /// - public override void MoveToRoot() + DebugReturn(succ); + return succ; + } + + /// + /// Moves the XPathNavigator to the parent node of the current node. + /// + /// + /// Returns true if the XPathNavigator is successful moving to the parent node of the current node; + /// otherwise, false. If false, the position of the XPathNavigator is unchanged. + /// + public override bool MoveToParent() + { + DebugEnter("MoveToParent"); + bool succ; + + switch (InternalState.Position) { - DebugEnter("MoveToRoot"); + case StatePosition.Macro: + InternalState.Position = StatePosition.Root; + DebugState(); + succ = true; + break; + case StatePosition.Parameter: + InternalState.Position = StatePosition.Macro; + DebugState(); + succ = true; + break; + case StatePosition.ParameterAttribute: + case StatePosition.ParameterNodes: + InternalState.Position = StatePosition.Parameter; + DebugState(); + succ = true; + break; + case StatePosition.ParameterNavigator: + var wasDoc = IsDoc(InternalState.ParameterNavigator); + succ = InternalState.ParameterNavigator?.MoveToParent() ?? false; + if (succ) + { + // move from doc child => decrement depth + if (wasDoc && --InternalState.ParameterNavigatorDepth == 0) + { + InternalState.Position = StatePosition.Parameter; + InternalState.ParameterNavigator = null; + DebugState(); + } + } - switch (_state.Position) - { - case StatePosition.ParameterNavigator: - _state.ParameterNavigator = null; - _state.ParameterNavigatorDepth = -1; - break; - case StatePosition.Parameter: - case StatePosition.ParameterText: - _state.ParameterIndex = -1; - break; - case StatePosition.ParameterAttribute: - case StatePosition.ParameterNodes: - case StatePosition.Macro: - case StatePosition.Root: - break; - default: - throw new InvalidOperationException("Invalid position."); - } + break; + case StatePosition.ParameterText: + InternalState.Position = StatePosition.Parameter; + DebugState(); + succ = true; + break; + case StatePosition.Root: + succ = false; + break; + default: + throw new InvalidOperationException("Invalid position."); + } - _state.Position = StatePosition.Root; - DebugState(); + DebugReturn(succ); + return succ; + } + + /// + /// Moves the XPathNavigator to the root node that the current node belongs to. + /// + public override void MoveToRoot() + { + DebugEnter("MoveToRoot"); - DebugReturn(); + switch (InternalState.Position) + { + case StatePosition.ParameterNavigator: + InternalState.ParameterNavigator = null; + InternalState.ParameterNavigatorDepth = -1; + break; + case StatePosition.Parameter: + case StatePosition.ParameterText: + InternalState.ParameterIndex = -1; + break; + case StatePosition.ParameterAttribute: + case StatePosition.ParameterNodes: + case StatePosition.Macro: + case StatePosition.Root: + break; + default: + throw new InvalidOperationException("Invalid position."); } - /// - /// Gets the base URI for the current node. - /// - public override string BaseURI + InternalState.Position = StatePosition.Root; + DebugState(); + + DebugReturn(); + } + + private static bool IsDoc(XPathNavigator? nav) + { + if (nav is null) { - get { return string.Empty; } + return false; } - /// - /// Gets the XmlNameTable of the XPathNavigator. - /// - public override XmlNameTable NameTable + if (nav.NodeType != XPathNodeType.Element) { - get { return _nameTable; } + return false; } - /// - /// Gets the namespace URI of the current node. - /// - public override string NamespaceURI + XPathNavigator clone = nav.Clone(); + if (!clone.MoveToFirstAttribute()) { - get { return string.Empty; } + return false; } - /// - /// Gets the XPathNodeType of the current node. - /// - public override XPathNodeType NodeType + do { - get + if (clone.Name == "isDoc") { - DebugEnter("NodeType"); - XPathNodeType type; + return true; + } + } while (clone.MoveToNextAttribute()); - switch (_state.Position) - { - case StatePosition.Macro: - case StatePosition.Parameter: - case StatePosition.ParameterNodes: - type = XPathNodeType.Element; - break; - case StatePosition.ParameterNavigator: - type = _state.ParameterNavigator?.NodeType ?? XPathNodeType.Root; - break; - case StatePosition.ParameterAttribute: - type = XPathNodeType.Attribute; - break; - case StatePosition.ParameterText: - type = XPathNodeType.Text; - break; - case StatePosition.Root: - type = XPathNodeType.Root; - break; - default: - throw new InvalidOperationException("Invalid position."); - } + return false; + } - DebugReturn("\'{0}\'", type); - return type; - } + #region Constructor + + /// + /// Initializes a new instance of the class with macro parameters. + /// + /// The macro parameters. + public MacroNavigator(IEnumerable parameters) + : this(new MacroRoot(parameters), new NameTable(), new State()) + { + } + + /// + /// Initializes a new instance of the class with a macro node, + /// a name table and a state. + /// + /// The macro node. + /// The name table. + /// The state. + /// Privately used for cloning a navigator. + private MacroNavigator(MacroRoot macro, XmlNameTable nameTable, State state) + { + _macro = macro; + _nameTable = nameTable; + InternalState = state; + } + + #endregion + + #region Diagnostics + + // diagnostics code will not be compiled nor called into Release configuration. + // in Debug configuration, uncomment lines in Debug() to write to console or to log. + // + // much of this code is duplicated in each navigator due to conditional compilation + +#if DEBUG + private const string Tabs = " "; + private int _tabs; + private readonly int _uid = GetUid(); + private static int _uidg; + private static readonly object Uidl = new(); + private static int GetUid() + { + lock (Uidl) + { + return _uidg++; } + } +#endif + + [Conditional("DEBUG")] + private void DebugEnter(string name) + { +#if DEBUG + Debug(""); + DebugState(":"); + Debug(name); + _tabs = Math.Min(Tabs.Length, _tabs + 2); +#endif + } + + [Conditional("DEBUG")] + private void DebugCreate(MacroNavigator nav) + { +#if DEBUG + Debug("Create: [MacroNavigator::{0}]", nav._uid); +#endif + } - /// - /// Gets the namespace prefix associated with the current node. - /// - public override string Prefix + [Conditional("DEBUG")] + private void DebugReturn() + { +#if DEBUG +// ReSharper disable IntroduceOptionalParameters.Local + DebugReturn("(void)"); +// ReSharper restore IntroduceOptionalParameters.Local +#endif + } + + [Conditional("DEBUG")] + private void DebugReturn(bool value) + { +#if DEBUG + DebugReturn(value ? "true" : "false"); +#endif + } + + [Conditional("DEBUG")] + private void DebugReturn(string format, params object[] args) + { +#if DEBUG + Debug("=> " + format, args); + if (_tabs > 0) { - get { return string.Empty; } + _tabs -= 2; } +#endif + } - /// - /// Gets the string value of the item. - /// - /// Does not fully behave as per the specs, as we report empty value on root and macro elements, and we start - /// reporting values only on parameter elements. This is because, otherwise, we would might dump the whole database - /// and it probably does not make sense at Umbraco level. - public override string Value + [Conditional("DEBUG")] + private void DebugState(string s = " =>") + { +#if DEBUG + string position; + + switch (InternalState.Position) { - get - { - DebugEnter("Value"); - string value; + case StatePosition.Macro: + position = "At macro."; + break; + case StatePosition.Parameter: + position = string.Format("At parameter '{0}'.", + _macro.Parameters[InternalState.ParameterIndex].Name); + break; + case StatePosition.ParameterAttribute: + position = string.Format("At parameter attribute '{0}/{1}'.", + _macro.Parameters[InternalState.ParameterIndex].Name, + _macro.Parameters[InternalState.ParameterIndex].Attributes?[InternalState.ParameterAttributeIndex] + .Key); + break; + case StatePosition.ParameterNavigator: + position = string.Format("In parameter '{0}{1}' navigator.", + _macro.Parameters[InternalState.ParameterIndex].Name, + _macro.Parameters[InternalState.ParameterIndex].WrapNavigatorInNodes ? "/nodes" : ""); + break; + case StatePosition.ParameterNodes: + position = string.Format("At parameter '{0}/nodes'.", + _macro.Parameters[InternalState.ParameterIndex].Name); + break; + case StatePosition.ParameterText: + position = string.Format("In parameter '{0}' text.", + _macro.Parameters[InternalState.ParameterIndex].Name); + break; + case StatePosition.Root: + position = "At root."; + break; + default: + throw new InvalidOperationException("Invalid position."); + } - XPathNavigator? nav; - switch (_state.Position) - { - case StatePosition.Parameter: - nav = _macro.Parameters[_state.ParameterIndex].NavigatorValue; - if (nav != null) - { - nav = nav.Clone(); // never use the raw parameter's navigator - nav.MoveToFirstChild(); - value = nav.Value; - } - else - { - var s = _macro.Parameters[_state.ParameterIndex].StringValue; - value = s ?? string.Empty; - } - break; - case StatePosition.ParameterAttribute: - value = _macro.Parameters[_state.ParameterIndex].Attributes?[_state.ParameterAttributeIndex].Value ?? string.Empty; - break; - case StatePosition.ParameterNavigator: - value = _state.ParameterNavigator?.Value ?? string.Empty; - break; - case StatePosition.ParameterNodes: - nav = _macro.Parameters[_state.ParameterIndex].NavigatorValue; - if (nav == null) - value = string.Empty; - else - { - nav = nav.Clone(); // never use the raw parameter's navigator - nav.MoveToFirstChild(); - value = nav.Value; - } - break; - case StatePosition.ParameterText: - value = _macro.Parameters[_state.ParameterIndex].StringValue ?? string.Empty; - break; - case StatePosition.Macro: - case StatePosition.Root: - value = string.Empty; - break; - default: - throw new InvalidOperationException("Invalid position."); - } + Debug("State{0} {1}", s, position); +#endif + } - DebugReturn("\"{0}\"", value); - return value; - } +#if DEBUG + private void Debug(string format, params object[] args) + { + // remove comments to write + + format = "[" + _uid.ToString("00000") + "] " + Tabs.Substring(0, _tabs) + format; +#pragma warning disable 168 + var msg = string.Format(format, args); // unused if not writing, hence #pragma +#pragma warning restore 168 + } +#endif + + #endregion + + #region Macro + + private class MacroRoot + { + public MacroRoot(IEnumerable parameters) => + Parameters = parameters == null ? new MacroParameter[] { } : parameters.ToArray(); + + public MacroParameter[] Parameters { get; } + } + + public class MacroParameter + { + // note: assuming we're not thinking about supporting + // XPathIterator in parameters - enough nonsense! + + public MacroParameter(string name, string value) + { + Name = name; + StringValue = value; } - private static bool IsDoc(XPathNavigator? nav) + public MacroParameter(string name, XPathNavigator navigator, + int maxNavigatorDepth = int.MaxValue, + bool wrapNavigatorInNodes = false, + IEnumerable>? attributes = null) { - if (nav is null) + Name = name; + MaxNavigatorDepth = maxNavigatorDepth; + WrapNavigatorInNodes = wrapNavigatorInNodes; + if (attributes != null) { - return false; + KeyValuePair[] a = attributes.ToArray(); + if (a.Length > 0) + { + Attributes = a; + } } - if (nav.NodeType != XPathNodeType.Element) - return false; - - var clone = nav.Clone(); - if (!clone.MoveToFirstAttribute()) - return false; - do - { - if (clone.Name == "isDoc") - return true; - } while (clone.MoveToNextAttribute()); - return false; + NavigatorValue = navigator; // should not be empty } - #region State management + public string Name { get; } + public string? StringValue { get; } + public XPathNavigator? NavigatorValue { get; } + public int MaxNavigatorDepth { get; } + public bool WrapNavigatorInNodes { get; } + public KeyValuePair[]? Attributes { get; } + } - // the possible state positions - internal enum StatePosition - { - Root, - Macro, - Parameter, - ParameterAttribute, - ParameterText, - ParameterNodes, - ParameterNavigator - }; - - // gets the state - // for unit tests only - internal State InternalState { get { return _state; } } - - // represents the XPathNavigator state - internal class State - { - public StatePosition Position { get; set; } + #endregion - // initialize a new state - private State(StatePosition position) - { - Position = position; - ParameterIndex = 0; - ParameterNavigatorDepth = 0; - ParameterAttributeIndex = 0; - } + #region State management - // initialize a new state - // used for creating the very first state - public State() - : this(StatePosition.Root) - { } + // the possible state positions + internal enum StatePosition + { + Root, + Macro, + Parameter, + ParameterAttribute, + ParameterText, + ParameterNodes, + ParameterNavigator + } - // initialize a clone state - private State(State other) - { - Position = other.Position; + // gets the state + // for unit tests only + internal State InternalState { get; private set; } + + // represents the XPathNavigator state + internal class State + { + // initialize a new state + private State(StatePosition position) + { + Position = position; + ParameterIndex = 0; + ParameterNavigatorDepth = 0; + ParameterAttributeIndex = 0; + } + + // initialize a new state + // used for creating the very first state + public State() + : this(StatePosition.Root) + { + } - ParameterIndex = other.ParameterIndex; + // initialize a clone state + private State(State other) + { + Position = other.Position; - if (Position == StatePosition.ParameterNavigator) - { - ParameterNavigator = other.ParameterNavigator?.Clone(); - ParameterNavigatorDepth = other.ParameterNavigatorDepth; - ParameterAttributeIndex = other.ParameterAttributeIndex; - } - } + ParameterIndex = other.ParameterIndex; - public State Clone() + if (Position == StatePosition.ParameterNavigator) { - return new State(this); + ParameterNavigator = other.ParameterNavigator?.Clone(); + ParameterNavigatorDepth = other.ParameterNavigatorDepth; + ParameterAttributeIndex = other.ParameterAttributeIndex; } + } + + public StatePosition Position { get; set; } + + // the index of the current element + public int ParameterIndex { get; set; } - // the index of the current element - public int ParameterIndex { get; set; } + // the current depth within the element navigator + public int ParameterNavigatorDepth { get; set; } - // the current depth within the element navigator - public int ParameterNavigatorDepth { get; set; } + // the index of the current element's attribute + public int ParameterAttributeIndex { get; set; } - // the index of the current element's attribute - public int ParameterAttributeIndex { get; set; } + // gets or sets the element navigator + public XPathNavigator? ParameterNavigator { get; set; } - // gets or sets the element navigator - public XPathNavigator? ParameterNavigator { get; set; } + public State Clone() => new State(this); - // gets a value indicating whether this state is at the same position as another one. - public bool IsSamePosition(State other) + // gets a value indicating whether this state is at the same position as another one. + public bool IsSamePosition(State other) + { + if (other.ParameterNavigator is null || ParameterNavigator is null) { - if (other.ParameterNavigator is null || ParameterNavigator is null) - { - return false; - } - return other.Position == Position - && (Position != StatePosition.ParameterNavigator || other.ParameterNavigator.IsSamePosition(ParameterNavigator)) - && other.ParameterIndex == ParameterIndex - && other.ParameterAttributeIndex == ParameterAttributeIndex; + return false; } - } - #endregion + return other.Position == Position + && (Position != StatePosition.ParameterNavigator || + other.ParameterNavigator.IsSamePosition(ParameterNavigator)) + && other.ParameterIndex == ParameterIndex + && other.ParameterAttributeIndex == ParameterAttributeIndex; + } } + + #endregion } diff --git a/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs b/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs index a575ee86f8f1..0d067bf16e3b 100644 --- a/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs +++ b/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs @@ -11,134 +11,136 @@ #undef DEBUGNAVIGATOR #endif -using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using System.Linq; using System.Xml; using System.Xml.XPath; -namespace Umbraco.Cms.Core.Xml.XPath +namespace Umbraco.Cms.Core.Xml.XPath; + +/// +/// Provides a cursor model for navigating Umbraco data as if it were XML. +/// +public class NavigableNavigator : XPathNavigator { + // "The XmlNameTable stores atomized strings of any local name, namespace URI, + // and prefix used by the XPathNavigator. This means that when the same Name is + // returned multiple times (like "book"), the same String object is returned for + // that Name. This makes it possible to write efficient code that does object + // comparisons on these strings, instead of expensive string comparisons." + // + // "When an element or attribute name occurs multiple times in an XML document, + // it is stored only once in the NameTable. The names are stored as common + // language runtime (CLR) object types. This enables you to do object comparisons + // on these strings rather than a more expensive string comparison. These + // string objects are referred to as atomized strings." + // + // But... "Any instance members are not guaranteed to be thread safe." + // + // see http://msdn.microsoft.com/en-us/library/aa735772%28v=vs.71%29.aspx + // see http://www.hanselman.com/blog/XmlAndTheNametable.aspx + // see http://blogs.msdn.com/b/mfussell/archive/2004/04/30/123673.aspx + // + // "Additionally, all LocalName, NameSpaceUri and Prefix strings must be added to + // a NameTable, given by the NameTable property. When the LocalName, NamespaceURI, + // and Prefix properties are returned, the string returned should come from the + // NameTable. Comparisons between names are done by object comparisons rather + // than by string comparisons, which are significantly slower."" + // + // So what shall we do? Well, here we have no namespace, no prefix, and all + // local names come from cached instances of INavigableContentType or + // INavigableFieldType and are already unique. So... create a one nametable + // because we need one, and share it amongst all clones. + + private readonly XmlNameTable _nameTable; + private readonly INavigableSource _source; + private readonly int _lastAttributeIndex; // last index of attributes in the fields collection + private readonly int _maxDepth; + + #region Constructor + + ///// + ///// Initializes a new instance of the class with a content source. + ///// + ///// The content source. + ///// The maximum depth. + //private NavigableNavigator(INavigableSource source, int maxDepth) + //{ + // _source = source; + // _lastAttributeIndex = source.LastAttributeIndex; + // _maxDepth = maxDepth; + //} + /// - /// Provides a cursor model for navigating Umbraco data as if it were XML. + /// Initializes a new instance of the class with a content source, + /// and an optional root content. /// - public class NavigableNavigator : XPathNavigator + /// The content source. + /// The root content identifier. + /// The maximum depth. + /// When no root content is supplied then the root of the source is used. + public NavigableNavigator(INavigableSource source, int rootId = 0, int maxDepth = int.MaxValue) + //: this(source, maxDepth) { - // "The XmlNameTable stores atomized strings of any local name, namespace URI, - // and prefix used by the XPathNavigator. This means that when the same Name is - // returned multiple times (like "book"), the same String object is returned for - // that Name. This makes it possible to write efficient code that does object - // comparisons on these strings, instead of expensive string comparisons." - // - // "When an element or attribute name occurs multiple times in an XML document, - // it is stored only once in the NameTable. The names are stored as common - // language runtime (CLR) object types. This enables you to do object comparisons - // on these strings rather than a more expensive string comparison. These - // string objects are referred to as atomized strings." - // - // But... "Any instance members are not guaranteed to be thread safe." - // - // see http://msdn.microsoft.com/en-us/library/aa735772%28v=vs.71%29.aspx - // see http://www.hanselman.com/blog/XmlAndTheNametable.aspx - // see http://blogs.msdn.com/b/mfussell/archive/2004/04/30/123673.aspx - // - // "Additionally, all LocalName, NameSpaceUri and Prefix strings must be added to - // a NameTable, given by the NameTable property. When the LocalName, NamespaceURI, - // and Prefix properties are returned, the string returned should come from the - // NameTable. Comparisons between names are done by object comparisons rather - // than by string comparisons, which are significantly slower."" - // - // So what shall we do? Well, here we have no namespace, no prefix, and all - // local names come from cached instances of INavigableContentType or - // INavigableFieldType and are already unique. So... create a one nametable - // because we need one, and share it amongst all clones. - - private readonly XmlNameTable _nameTable; - private readonly INavigableSource _source; - private readonly int _lastAttributeIndex; // last index of attributes in the fields collection - private State _state; - private readonly int _maxDepth; - - #region Constructor - - ///// - ///// Initializes a new instance of the class with a content source. - ///// - ///// The content source. - ///// The maximum depth. - //private NavigableNavigator(INavigableSource source, int maxDepth) - //{ - // _source = source; - // _lastAttributeIndex = source.LastAttributeIndex; - // _maxDepth = maxDepth; - //} - - /// - /// Initializes a new instance of the class with a content source, - /// and an optional root content. - /// - /// The content source. - /// The root content identifier. - /// The maximum depth. - /// When no root content is supplied then the root of the source is used. - public NavigableNavigator(INavigableSource source, int rootId = 0, int maxDepth = int.MaxValue) - //: this(source, maxDepth) + _source = source; + _lastAttributeIndex = source.LastAttributeIndex; + _maxDepth = maxDepth; + + _nameTable = new NameTable(); + _lastAttributeIndex = source.LastAttributeIndex; + INavigableContent content = rootId <= 0 ? source.Root : source.Get(rootId); + if (content == null) { - _source = source; - _lastAttributeIndex = source.LastAttributeIndex; - _maxDepth = maxDepth; - - _nameTable = new NameTable(); - _lastAttributeIndex = source.LastAttributeIndex; - var content = rootId <= 0 ? source.Root : source.Get(rootId); - if (content == null) - throw new ArgumentException("Not the identifier of a content within the source.", nameof(rootId)); - _state = new State(content, null, null, 0, StatePosition.Root); - - _contents = new ConcurrentDictionary(); + throw new ArgumentException("Not the identifier of a content within the source.", nameof(rootId)); } - ///// - ///// Initializes a new instance of the class with a content source, a name table and a state. - ///// - ///// The content source. - ///// The name table. - ///// The state. - ///// The maximum depth. - ///// Privately used for cloning a navigator. - //private NavigableNavigator(INavigableSource source, XmlNameTable nameTable, State state, int maxDepth) - // : this(source, rootId: 0, maxDepth: maxDepth) - //{ - // _nameTable = nameTable; - // _state = state; - //} - - /// - /// Initializes a new instance of the class as a clone. - /// - /// The cloned navigator. - /// The clone state. - /// The clone maximum depth. - /// Privately used for cloning a navigator. - private NavigableNavigator(NavigableNavigator orig, State? state = null, int maxDepth = -1) - : this(orig._source, rootId: 0, maxDepth: orig._maxDepth) - { - _nameTable = orig._nameTable; + InternalState = new State(content, null, null, 0, StatePosition.Root); + + _contents = new ConcurrentDictionary(); + } + + ///// + ///// Initializes a new instance of the class with a content source, a name table and a state. + ///// + ///// The content source. + ///// The name table. + ///// The state. + ///// The maximum depth. + ///// Privately used for cloning a navigator. + //private NavigableNavigator(INavigableSource source, XmlNameTable nameTable, State state, int maxDepth) + // : this(source, rootId: 0, maxDepth: maxDepth) + //{ + // _nameTable = nameTable; + // _state = state; + //} - _state = state ?? orig._state.Clone(); - if (state != null && maxDepth < 0) - throw new ArgumentException("Both state and maxDepth are required."); - _maxDepth = maxDepth < 0 ? orig._maxDepth : maxDepth; + /// + /// Initializes a new instance of the class as a clone. + /// + /// The cloned navigator. + /// The clone state. + /// The clone maximum depth. + /// Privately used for cloning a navigator. + private NavigableNavigator(NavigableNavigator orig, State? state = null, int maxDepth = -1) + : this(orig._source, 0, orig._maxDepth) + { + _nameTable = orig._nameTable; - _contents = orig._contents; + InternalState = state ?? orig.InternalState.Clone(); + if (state != null && maxDepth < 0) + { + throw new ArgumentException("Both state and maxDepth are required."); } - #endregion + _maxDepth = maxDepth < 0 ? orig._maxDepth : maxDepth; + + _contents = orig._contents; + } + + #endregion - #region Diagnostics + #region Diagnostics #if DEBUGNAVIGATOR private const string Tabs = " "; @@ -155,60 +157,60 @@ private static int GetUid() } #endif - // About conditional methods: marking a method with the [Conditional] attribute ensures - // that no calls to the method will be generated by the compiler. However, the method - // does exist. Wrapping the method body with #if/endif ensures that no IL is generated - // and so it's only an empty method. + // About conditional methods: marking a method with the [Conditional] attribute ensures + // that no calls to the method will be generated by the compiler. However, the method + // does exist. Wrapping the method body with #if/endif ensures that no IL is generated + // and so it's only an empty method. - [Conditional("DEBUGNAVIGATOR")] - void DebugEnter(string name) - { + [Conditional("DEBUGNAVIGATOR")] + private void DebugEnter(string name) + { #if DEBUGNAVIGATOR Debug(""); DebugState(":"); Debug(name); _tabs = Math.Min(Tabs.Length, _tabs + 2); #endif - } + } - [Conditional("DEBUGNAVIGATOR")] - void DebugCreate(NavigableNavigator nav) - { + [Conditional("DEBUGNAVIGATOR")] + private void DebugCreate(NavigableNavigator nav) + { #if DEBUGNAVIGATOR Debug("Create: [NavigableNavigator::{0}]", nav._uid); #endif - } + } - [Conditional("DEBUGNAVIGATOR")] - private void DebugReturn() - { + [Conditional("DEBUGNAVIGATOR")] + private void DebugReturn() + { #if DEBUGNAVIGATOR // ReSharper disable IntroduceOptionalParameters.Local DebugReturn("(void)"); // ReSharper restore IntroduceOptionalParameters.Local #endif - } + } - [Conditional("DEBUGNAVIGATOR")] - private void DebugReturn(bool value) - { + [Conditional("DEBUGNAVIGATOR")] + private void DebugReturn(bool value) + { #if DEBUGNAVIGATOR DebugReturn(value ? "true" : "false"); #endif - } + } - [Conditional("DEBUGNAVIGATOR")] - void DebugReturn(string format, params object[] args) - { + [Conditional("DEBUGNAVIGATOR")] + private void DebugReturn(string format, params object[] args) + { #if DEBUGNAVIGATOR Debug("=> " + format, args); if (_tabs > 0) _tabs -= 2; #endif - } + } - [Conditional("DEBUGNAVIGATOR")] - void DebugState(string s = " =>") - { + [Conditional("DEBUGNAVIGATOR")] + private void DebugState(string s = " =>") + { #if DEBUGNAVIGATOR string position; @@ -245,7 +247,7 @@ void DebugState(string s = " =>") Debug("State{0} {1}", s, position); #endif - } + } #if DEBUGNAVIGATOR void Debug(string format, params object[] args) @@ -257,980 +259,1036 @@ void Debug(string format, params object[] args) } #endif - #endregion + #endregion - #region Source management + #region Source management - private readonly ConcurrentDictionary _contents; + private readonly ConcurrentDictionary _contents; - private INavigableContent? SourceGet(int id) - { - // original version, would keep creating INavigableContent objects - //return _source.Get(id); + private INavigableContent? SourceGet(int id) => + // original version, would keep creating INavigableContent objects + //return _source.Get(id); + // improved version, uses a cache, shared with clones + _contents.GetOrAdd(id, x => _source.Get(x)); - // improved version, uses a cache, shared with clones - return _contents.GetOrAdd(id, x => _source.Get(x)); - } + #endregion - #endregion + /// + /// Gets the underlying content object. + /// + public override object? UnderlyingObject => InternalState.Content; - /// - /// Gets the underlying content object. - /// - public override object? UnderlyingObject => _state.Content; + /// + /// Creates a new XPathNavigator positioned at the same node as this XPathNavigator. + /// + /// A new XPathNavigator positioned at the same node as this XPathNavigator. + public override XPathNavigator Clone() + { + DebugEnter("Clone"); + var nav = new NavigableNavigator(this); + DebugCreate(nav); + DebugReturn("[XPathNavigator]"); + return nav; + } - /// - /// Creates a new XPathNavigator positioned at the same node as this XPathNavigator. - /// - /// A new XPathNavigator positioned at the same node as this XPathNavigator. - public override XPathNavigator Clone() + /// + /// Creates a new XPathNavigator using the same source but positioned at a new root. + /// + /// A new XPathNavigator using the same source and positioned at a new root. + /// The new root can be above this navigator's root. + public XPathNavigator CloneWithNewRoot(string id, int maxDepth = int.MaxValue) + { + int i; + if (int.TryParse(id, NumberStyles.Integer, CultureInfo.InvariantCulture, out i) == false) { - DebugEnter("Clone"); - var nav = new NavigableNavigator(this); - DebugCreate(nav); - DebugReturn("[XPathNavigator]"); - return nav; + throw new ArgumentException("Not a valid identifier.", nameof(id)); } - /// - /// Creates a new XPathNavigator using the same source but positioned at a new root. - /// - /// A new XPathNavigator using the same source and positioned at a new root. - /// The new root can be above this navigator's root. - public XPathNavigator CloneWithNewRoot(string id, int maxDepth = int.MaxValue) - { - int i; - if (int.TryParse(id, NumberStyles.Integer, CultureInfo.InvariantCulture, out i) == false) - throw new ArgumentException("Not a valid identifier.", nameof(id)); - return CloneWithNewRoot(id); - } + return CloneWithNewRoot(id); + } - /// - /// Creates a new XPathNavigator using the same source but positioned at a new root. - /// - /// A new XPathNavigator using the same source and positioned at a new root. - /// The new root can be above this navigator's root. - public XPathNavigator? CloneWithNewRoot(int id, int maxDepth = int.MaxValue) - { - DebugEnter("CloneWithNewRoot"); + /// + /// Creates a new XPathNavigator using the same source but positioned at a new root. + /// + /// A new XPathNavigator using the same source and positioned at a new root. + /// The new root can be above this navigator's root. + public XPathNavigator? CloneWithNewRoot(int id, int maxDepth = int.MaxValue) + { + DebugEnter("CloneWithNewRoot"); - State? state = null; + State? state = null; - if (id <= 0) - { - state = new State(_source.Root, null, null, 0, StatePosition.Root); - } - else + if (id <= 0) + { + state = new State(_source.Root, null, null, 0, StatePosition.Root); + } + else + { + INavigableContent content = SourceGet(id); + if (content != null) { - var content = SourceGet(id); - if (content != null) - { - state = new State(content, null, null, 0, StatePosition.Root); - } + state = new State(content, null, null, 0, StatePosition.Root); } + } - NavigableNavigator? clone = null; - - if (state != null) - { - clone = new NavigableNavigator(this, state, maxDepth); - DebugCreate(clone); - DebugReturn("[XPathNavigator]"); - } - else - { - DebugReturn("[null]"); - } + NavigableNavigator? clone = null; - return clone; + if (state != null) + { + clone = new NavigableNavigator(this, state, maxDepth); + DebugCreate(clone); + DebugReturn("[XPathNavigator]"); } - - /// - /// Gets a value indicating whether the current node is an empty element without an end element tag. - /// - public override bool IsEmptyElement + else { - get - { - DebugEnter("IsEmptyElement"); - bool isEmpty; - - switch (_state.Position) - { - case StatePosition.Element: - // must go through source because of preview/published ie there may be - // ids but corresponding to preview elements that we don't see here - var hasContentChild = _state.GetContentChildIds(_maxDepth).Any(x => SourceGet(x) != null); - isEmpty = (hasContentChild == false) // no content child - && _state.FieldsCount - 1 == _lastAttributeIndex; // no property element child - break; - case StatePosition.PropertyElement: - // value should be - // - an XPathNavigator over a non-empty XML fragment - // - a non-Xml-whitespace string - // - null - isEmpty = _state.Content?.Value(_state.FieldIndex) == null; - break; - case StatePosition.PropertyXml: - isEmpty = _state.XmlFragmentNavigator?.IsEmptyElement ?? true; - break; - case StatePosition.Attribute: - case StatePosition.PropertyText: - case StatePosition.Root: - throw new InvalidOperationException("Not an element."); - default: - throw new InvalidOperationException("Invalid position."); - } - - DebugReturn(isEmpty); - return isEmpty; - } + DebugReturn("[null]"); } - /// - /// Determines whether the current XPathNavigator is at the same position as the specified XPathNavigator. - /// - /// The XPathNavigator to compare to this XPathNavigator. - /// true if the two XPathNavigator objects have the same position; otherwise, false. - public override bool IsSamePosition(XPathNavigator nav) + return clone; + } + + /// + /// Gets a value indicating whether the current node is an empty element without an end element tag. + /// + public override bool IsEmptyElement + { + get { - DebugEnter("IsSamePosition"); - bool isSame; + DebugEnter("IsEmptyElement"); + bool isEmpty; - switch (_state.Position) + switch (InternalState.Position) { + case StatePosition.Element: + // must go through source because of preview/published ie there may be + // ids but corresponding to preview elements that we don't see here + var hasContentChild = InternalState.GetContentChildIds(_maxDepth).Any(x => SourceGet(x) != null); + isEmpty = hasContentChild == false // no content child + && InternalState.FieldsCount - 1 == _lastAttributeIndex; // no property element child + break; + case StatePosition.PropertyElement: + // value should be + // - an XPathNavigator over a non-empty XML fragment + // - a non-Xml-whitespace string + // - null + isEmpty = InternalState.Content?.Value(InternalState.FieldIndex) == null; + break; case StatePosition.PropertyXml: - isSame = _state.XmlFragmentNavigator?.IsSamePosition(nav) ?? false; + isEmpty = InternalState.XmlFragmentNavigator?.IsEmptyElement ?? true; break; case StatePosition.Attribute: - case StatePosition.Element: - case StatePosition.PropertyElement: case StatePosition.PropertyText: case StatePosition.Root: - var other = nav as NavigableNavigator; - isSame = other != null && other._source == _source && _state.IsSamePosition(other._state); - break; + throw new InvalidOperationException("Not an element."); default: throw new InvalidOperationException("Invalid position."); } - DebugReturn(isSame); - return isSame; + DebugReturn(isEmpty); + return isEmpty; } + } - /// - /// Gets the qualified name of the current node. - /// - public override string Name - { - get - { - DebugEnter("Name"); - string name; - - switch (_state.Position) - { - case StatePosition.PropertyXml: - name = _state.XmlFragmentNavigator?.Name ?? string.Empty; - break; - case StatePosition.Attribute: - case StatePosition.PropertyElement: - name = _state.FieldIndex == -1 ? "id" : _state.CurrentFieldType?.Name ?? string.Empty; - break; - case StatePosition.Element: - name = _state.Content?.Type.Name ?? string.Empty; - break; - case StatePosition.PropertyText: - name = string.Empty; - break; - case StatePosition.Root: - name = string.Empty; - break; - default: - throw new InvalidOperationException("Invalid position."); - } - - DebugReturn("\"{0}\"", name); - return name; - } - } + /// + /// Determines whether the current XPathNavigator is at the same position as the specified XPathNavigator. + /// + /// The XPathNavigator to compare to this XPathNavigator. + /// true if the two XPathNavigator objects have the same position; otherwise, false. + public override bool IsSamePosition(XPathNavigator nav) + { + DebugEnter("IsSamePosition"); + bool isSame; - /// - /// Gets the Name of the current node without any namespace prefix. - /// - public override string LocalName + switch (InternalState.Position) { - get - { - DebugEnter("LocalName"); - var name = Name; - DebugReturn("\"{0}\"", name); - return name; - } + case StatePosition.PropertyXml: + isSame = InternalState.XmlFragmentNavigator?.IsSamePosition(nav) ?? false; + break; + case StatePosition.Attribute: + case StatePosition.Element: + case StatePosition.PropertyElement: + case StatePosition.PropertyText: + case StatePosition.Root: + var other = nav as NavigableNavigator; + isSame = other != null && other._source == _source && InternalState.IsSamePosition(other.InternalState); + break; + default: + throw new InvalidOperationException("Invalid position."); } - /// - /// Moves the XPathNavigator to the same position as the specified XPathNavigator. - /// - /// The XPathNavigator positioned on the node that you want to move to. - /// Returns true if the XPathNavigator is successful moving to the same position as the specified XPathNavigator; - /// otherwise, false. If false, the position of the XPathNavigator is unchanged. - public override bool MoveTo(XPathNavigator nav) - { - DebugEnter("MoveTo"); - - var other = nav as NavigableNavigator; - var succ = false; - - if (other != null && other._source == _source) - { - _state = other._state.Clone(); - DebugState(); - succ = true; - } - - DebugReturn(succ); - return succ; - } + DebugReturn(isSame); + return isSame; + } - /// - /// Moves the XPathNavigator to the first attribute of the current node. - /// - /// Returns true if the XPathNavigator is successful moving to the first attribute of the current node; - /// otherwise, false. If false, the position of the XPathNavigator is unchanged. - public override bool MoveToFirstAttribute() + /// + /// Gets the qualified name of the current node. + /// + public override string Name + { + get { - DebugEnter("MoveToFirstAttribute"); - bool succ; + DebugEnter("Name"); + string name; - switch (_state.Position) + switch (InternalState.Position) { case StatePosition.PropertyXml: - succ = _state.XmlFragmentNavigator?.MoveToFirstAttribute() ?? false; - break; - case StatePosition.Element: - _state.FieldIndex = -1; - _state.Position = StatePosition.Attribute; - DebugState(); - succ = true; + name = InternalState.XmlFragmentNavigator?.Name ?? string.Empty; break; case StatePosition.Attribute: case StatePosition.PropertyElement: + name = InternalState.FieldIndex == -1 ? "id" : InternalState.CurrentFieldType?.Name ?? string.Empty; + break; + case StatePosition.Element: + name = InternalState.Content?.Type.Name ?? string.Empty; + break; case StatePosition.PropertyText: + name = string.Empty; + break; case StatePosition.Root: - succ = false; + name = string.Empty; break; default: throw new InvalidOperationException("Invalid position."); } - DebugReturn(succ); - return succ; + DebugReturn("\"{0}\"", name); + return name; } + } - /// - /// Moves the XPathNavigator to the first child node of the current node. - /// - /// Returns true if the XPathNavigator is successful moving to the first child node of the current node; - /// otherwise, false. If false, the position of the XPathNavigator is unchanged. - public override bool MoveToFirstChild() + /// + /// Gets the Name of the current node without any namespace prefix. + /// + public override string LocalName + { + get { - DebugEnter("MoveToFirstChild"); - bool succ; + DebugEnter("LocalName"); + var name = Name; + DebugReturn("\"{0}\"", name); + return name; + } + } - switch (_state.Position) - { - case StatePosition.PropertyXml: - succ = _state.XmlFragmentNavigator?.MoveToFirstChild() ?? false; - break; - case StatePosition.Attribute: - case StatePosition.PropertyText: - succ = false; - break; - case StatePosition.Element: - var firstPropertyIndex = _lastAttributeIndex + 1; - if (_state.FieldsCount > firstPropertyIndex) - { - _state.Position = StatePosition.PropertyElement; - _state.FieldIndex = firstPropertyIndex; - DebugState(); - succ = true; - } - else succ = MoveToFirstChildElement(); - break; - case StatePosition.PropertyElement: - succ = MoveToFirstChildProperty(); - break; - case StatePosition.Root: - _state.Position = StatePosition.Element; - DebugState(); - succ = true; - break; - default: - throw new InvalidOperationException("Invalid position."); - } + /// + /// Moves the XPathNavigator to the same position as the specified XPathNavigator. + /// + /// The XPathNavigator positioned on the node that you want to move to. + /// + /// Returns true if the XPathNavigator is successful moving to the same position as the specified XPathNavigator; + /// otherwise, false. If false, the position of the XPathNavigator is unchanged. + /// + public override bool MoveTo(XPathNavigator nav) + { + DebugEnter("MoveTo"); + + var other = nav as NavigableNavigator; + var succ = false; - DebugReturn(succ); - return succ; + if (other != null && other._source == _source) + { + InternalState = other.InternalState.Clone(); + DebugState(); + succ = true; } - private bool MoveToFirstChildElement() + DebugReturn(succ); + return succ; + } + + /// + /// Moves the XPathNavigator to the first attribute of the current node. + /// + /// + /// Returns true if the XPathNavigator is successful moving to the first attribute of the current node; + /// otherwise, false. If false, the position of the XPathNavigator is unchanged. + /// + public override bool MoveToFirstAttribute() + { + DebugEnter("MoveToFirstAttribute"); + bool succ; + + switch (InternalState.Position) { - var children = _state.GetContentChildIds(_maxDepth); + case StatePosition.PropertyXml: + succ = InternalState.XmlFragmentNavigator?.MoveToFirstAttribute() ?? false; + break; + case StatePosition.Element: + InternalState.FieldIndex = -1; + InternalState.Position = StatePosition.Attribute; + DebugState(); + succ = true; + break; + case StatePosition.Attribute: + case StatePosition.PropertyElement: + case StatePosition.PropertyText: + case StatePosition.Root: + succ = false; + break; + default: + throw new InvalidOperationException("Invalid position."); + } - if (children.Count > 0) - { - // children may contain IDs that does not correspond to some content in source - // because children contains all child IDs including unpublished children - and - // then if we're not previewing, the source will return null. - var child = children.Select(id => SourceGet(id)).FirstOrDefault(c => c != null); - if (child != null) + DebugReturn(succ); + return succ; + } + + /// + /// Moves the XPathNavigator to the first child node of the current node. + /// + /// + /// Returns true if the XPathNavigator is successful moving to the first child node of the current node; + /// otherwise, false. If false, the position of the XPathNavigator is unchanged. + /// + public override bool MoveToFirstChild() + { + DebugEnter("MoveToFirstChild"); + bool succ; + + switch (InternalState.Position) + { + case StatePosition.PropertyXml: + succ = InternalState.XmlFragmentNavigator?.MoveToFirstChild() ?? false; + break; + case StatePosition.Attribute: + case StatePosition.PropertyText: + succ = false; + break; + case StatePosition.Element: + var firstPropertyIndex = _lastAttributeIndex + 1; + if (InternalState.FieldsCount > firstPropertyIndex) { - _state.Position = StatePosition.Element; - _state.FieldIndex = -1; - _state = new State(child, _state, children, 0, StatePosition.Element); + InternalState.Position = StatePosition.PropertyElement; + InternalState.FieldIndex = firstPropertyIndex; DebugState(); - return true; + succ = true; + } + else + { + succ = MoveToFirstChildElement(); } - } - return false; + break; + case StatePosition.PropertyElement: + succ = MoveToFirstChildProperty(); + break; + case StatePosition.Root: + InternalState.Position = StatePosition.Element; + DebugState(); + succ = true; + break; + default: + throw new InvalidOperationException("Invalid position."); } - private bool MoveToFirstChildProperty() - { - var valueForXPath = _state.Content?.Value(_state.FieldIndex); + DebugReturn(succ); + return succ; + } - // value should be - // - an XPathNavigator over a non-empty XML fragment - // - a non-Xml-whitespace string - // - null + private bool MoveToFirstChildElement() + { + IList children = InternalState.GetContentChildIds(_maxDepth); - var nav = valueForXPath as XPathNavigator; - if (nav != null) + if (children.Count > 0) + { + // children may contain IDs that does not correspond to some content in source + // because children contains all child IDs including unpublished children - and + // then if we're not previewing, the source will return null. + INavigableContent child = children.Select(id => SourceGet(id)).FirstOrDefault(c => c != null); + if (child != null) { - nav = nav.Clone(); // never use the one we got - nav.MoveToFirstChild(); - _state.XmlFragmentNavigator = nav; - _state.Position = StatePosition.PropertyXml; + InternalState.Position = StatePosition.Element; + InternalState.FieldIndex = -1; + InternalState = new State(child, InternalState, children, 0, StatePosition.Element); DebugState(); return true; } + } - if (valueForXPath == null) - return false; + return false; + } - if (valueForXPath is string) - { - _state.Position = StatePosition.PropertyText; - DebugState(); - return true; - } + private bool MoveToFirstChildProperty() + { + var valueForXPath = InternalState.Content?.Value(InternalState.FieldIndex); + + // value should be + // - an XPathNavigator over a non-empty XML fragment + // - a non-Xml-whitespace string + // - null - throw new InvalidOperationException("XPathValue must be an XPathNavigator or a string."); + var nav = valueForXPath as XPathNavigator; + if (nav != null) + { + nav = nav.Clone(); // never use the one we got + nav.MoveToFirstChild(); + InternalState.XmlFragmentNavigator = nav; + InternalState.Position = StatePosition.PropertyXml; + DebugState(); + return true; } - /// - /// Moves the XPathNavigator to the first namespace node that matches the XPathNamespaceScope specified. - /// - /// An XPathNamespaceScope value describing the namespace scope. - /// Returns true if the XPathNavigator is successful moving to the first namespace node; - /// otherwise, false. If false, the position of the XPathNavigator is unchanged. - public override bool MoveToFirstNamespace(XPathNamespaceScope namespaceScope) + if (valueForXPath == null) { - DebugEnter("MoveToFirstNamespace"); - DebugReturn(false); return false; } - /// - /// Moves the XPathNavigator to the next namespace node matching the XPathNamespaceScope specified. - /// - /// An XPathNamespaceScope value describing the namespace scope. - /// Returns true if the XPathNavigator is successful moving to the next namespace node; - /// otherwise, false. If false, the position of the XPathNavigator is unchanged. - public override bool MoveToNextNamespace(XPathNamespaceScope namespaceScope) + if (valueForXPath is string) { - DebugEnter("MoveToNextNamespace"); - DebugReturn(false); - return false; + InternalState.Position = StatePosition.PropertyText; + DebugState(); + return true; + } + + throw new InvalidOperationException("XPathValue must be an XPathNavigator or a string."); + } + + /// + /// Moves the XPathNavigator to the first namespace node that matches the XPathNamespaceScope specified. + /// + /// An XPathNamespaceScope value describing the namespace scope. + /// + /// Returns true if the XPathNavigator is successful moving to the first namespace node; + /// otherwise, false. If false, the position of the XPathNavigator is unchanged. + /// + public override bool MoveToFirstNamespace(XPathNamespaceScope namespaceScope) + { + DebugEnter("MoveToFirstNamespace"); + DebugReturn(false); + return false; + } + + /// + /// Moves the XPathNavigator to the next namespace node matching the XPathNamespaceScope specified. + /// + /// An XPathNamespaceScope value describing the namespace scope. + /// + /// Returns true if the XPathNavigator is successful moving to the next namespace node; + /// otherwise, false. If false, the position of the XPathNavigator is unchanged. + /// + public override bool MoveToNextNamespace(XPathNamespaceScope namespaceScope) + { + DebugEnter("MoveToNextNamespace"); + DebugReturn(false); + return false; + } + + /// + /// Moves to the node that has an attribute of type ID whose value matches the specified String. + /// + /// A String representing the ID value of the node to which you want to move. + /// + /// true if the XPathNavigator is successful moving; otherwise, false. + /// If false, the position of the navigator is unchanged. + /// + public override bool MoveToId(string id) + { + DebugEnter("MoveToId"); + var succ = false; + + // don't look into fragments, just look for element identifiers + // not sure we actually need to implement it... think of it as + // as exercise of style, always better than throwing NotImplemented. + + // navigator may be rooted below source root + // find the navigator root id + State state = InternalState; + while (state.Parent != null) // root state has no parent + { + state = state.Parent; } - /// - /// Moves to the node that has an attribute of type ID whose value matches the specified String. - /// - /// A String representing the ID value of the node to which you want to move. - /// true if the XPathNavigator is successful moving; otherwise, false. - /// If false, the position of the navigator is unchanged. - public override bool MoveToId(string id) + var navRootId = state.Content?.Id; + + int contentId; + if (int.TryParse(id, NumberStyles.Integer, CultureInfo.InvariantCulture, out contentId)) { - DebugEnter("MoveToId"); - var succ = false; - - // don't look into fragments, just look for element identifiers - // not sure we actually need to implement it... think of it as - // as exercise of style, always better than throwing NotImplemented. - - // navigator may be rooted below source root - // find the navigator root id - var state = _state; - while (state.Parent != null) // root state has no parent - state = state.Parent; - var navRootId = state.Content?.Id; - - int contentId; - if (int.TryParse(id, NumberStyles.Integer, CultureInfo.InvariantCulture, out contentId)) + if (contentId == navRootId) { - if (contentId == navRootId) - { - _state = new State(state.Content, null, null, 0, StatePosition.Element); - succ = true; - } - else + InternalState = new State(state.Content, null, null, 0, StatePosition.Element); + succ = true; + } + else + { + INavigableContent content = SourceGet(contentId); + if (content != null) { - var content = SourceGet(contentId); - if (content != null) + // walk up to the navigator's root - or the source's root + var s = new Stack(); + while (content != null && content.ParentId != navRootId) { - // walk up to the navigator's root - or the source's root - var s = new Stack(); - while (content != null && content.ParentId != navRootId) - { - s.Push(content); - content = SourceGet(content.ParentId); - } + s.Push(content); + content = SourceGet(content.ParentId); + } - if (content != null && s.Count < _maxDepth) + if (content != null && s.Count < _maxDepth) + { + InternalState = new State(state.Content, null, null, 0, StatePosition.Element); + while (content != null) { - _state = new State(state.Content, null, null, 0, StatePosition.Element); - while (content != null) - { - _state = new State(content, _state, _state.Content?.ChildIds, _state.Content?.ChildIds?.IndexOf(content.Id) ?? -1, StatePosition.Element); - content = s.Count == 0 ? null : s.Pop(); - } - DebugState(); - succ = true; + InternalState = new State(content, InternalState, InternalState.Content?.ChildIds, + InternalState.Content?.ChildIds?.IndexOf(content.Id) ?? -1, StatePosition.Element); + content = s.Count == 0 ? null : s.Pop(); } + + DebugState(); + succ = true; } } } - - DebugReturn(succ); - return succ; } - /// - /// Moves the XPathNavigator to the next sibling node of the current node. - /// - /// true if the XPathNavigator is successful moving to the next sibling node; - /// otherwise, false if there are no more siblings or if the XPathNavigator is currently - /// positioned on an attribute node. If false, the position of the XPathNavigator is unchanged. - public override bool MoveToNext() - { - DebugEnter("MoveToNext"); - bool succ; + DebugReturn(succ); + return succ; + } - switch (_state.Position) - { - case StatePosition.PropertyXml: - succ = _state.XmlFragmentNavigator?.MoveToNext() ?? false; - break; - case StatePosition.Element: - succ = false; - while (_state.Siblings != null && _state.SiblingIndex < _state.Siblings.Count - 1) - { - // Siblings may contain IDs that does not correspond to some content in source - // because children contains all child IDs including unpublished children - and - // then if we're not previewing, the source will return null. - var node = SourceGet(_state.Siblings[++_state.SiblingIndex]); - if (node == null) continue; + /// + /// Moves the XPathNavigator to the next sibling node of the current node. + /// + /// + /// true if the XPathNavigator is successful moving to the next sibling node; + /// otherwise, false if there are no more siblings or if the XPathNavigator is currently + /// positioned on an attribute node. If false, the position of the XPathNavigator is unchanged. + /// + public override bool MoveToNext() + { + DebugEnter("MoveToNext"); + bool succ; - _state.Content = node; - DebugState(); - succ = true; - break; - } - break; - case StatePosition.PropertyElement: - if (_state.FieldIndex == _state.FieldsCount - 1) + switch (InternalState.Position) + { + case StatePosition.PropertyXml: + succ = InternalState.XmlFragmentNavigator?.MoveToNext() ?? false; + break; + case StatePosition.Element: + succ = false; + while (InternalState.Siblings != null && InternalState.SiblingIndex < InternalState.Siblings.Count - 1) + { + // Siblings may contain IDs that does not correspond to some content in source + // because children contains all child IDs including unpublished children - and + // then if we're not previewing, the source will return null. + INavigableContent node = SourceGet(InternalState.Siblings[++InternalState.SiblingIndex]); + if (node == null) { - // after property elements may come some children elements - // if successful, will push a new state - succ = MoveToFirstChildElement(); + continue; } - else - { - ++_state.FieldIndex; - DebugState(); - succ = true; - } - break; - case StatePosition.PropertyText: - case StatePosition.Attribute: - case StatePosition.Root: - succ = false; + + InternalState.Content = node; + DebugState(); + succ = true; break; - default: - throw new InvalidOperationException("Invalid position."); - } + } + + break; + case StatePosition.PropertyElement: + if (InternalState.FieldIndex == InternalState.FieldsCount - 1) + { + // after property elements may come some children elements + // if successful, will push a new state + succ = MoveToFirstChildElement(); + } + else + { + ++InternalState.FieldIndex; + DebugState(); + succ = true; + } - DebugReturn(succ); - return succ; + break; + case StatePosition.PropertyText: + case StatePosition.Attribute: + case StatePosition.Root: + succ = false; + break; + default: + throw new InvalidOperationException("Invalid position."); } - /// - /// Moves the XPathNavigator to the previous sibling node of the current node. - /// - /// Returns true if the XPathNavigator is successful moving to the previous sibling node; - /// otherwise, false if there is no previous sibling node or if the XPathNavigator is currently - /// positioned on an attribute node. If false, the position of the XPathNavigator is unchanged. - public override bool MoveToPrevious() - { - DebugEnter("MoveToPrevious"); - bool succ; + DebugReturn(succ); + return succ; + } - switch (_state.Position) - { - case StatePosition.PropertyXml: - succ = _state.XmlFragmentNavigator?.MoveToPrevious() ?? false; - break; - case StatePosition.Element: - succ = false; - while (_state.Siblings != null && _state.SiblingIndex > 0) - { - // children may contain IDs that does not correspond to some content in source - // because children contains all child IDs including unpublished children - and - // then if we're not previewing, the source will return null. - var content = SourceGet(_state.Siblings[--_state.SiblingIndex]); - if (content == null) continue; + /// + /// Moves the XPathNavigator to the previous sibling node of the current node. + /// + /// + /// Returns true if the XPathNavigator is successful moving to the previous sibling node; + /// otherwise, false if there is no previous sibling node or if the XPathNavigator is currently + /// positioned on an attribute node. If false, the position of the XPathNavigator is unchanged. + /// + public override bool MoveToPrevious() + { + DebugEnter("MoveToPrevious"); + bool succ; - _state.Content = content; - DebugState(); - succ = true; - break; - } - if (succ == false && _state.SiblingIndex == 0 && _state.FieldsCount - 1 > _lastAttributeIndex) + switch (InternalState.Position) + { + case StatePosition.PropertyXml: + succ = InternalState.XmlFragmentNavigator?.MoveToPrevious() ?? false; + break; + case StatePosition.Element: + succ = false; + while (InternalState.Siblings != null && InternalState.SiblingIndex > 0) + { + // children may contain IDs that does not correspond to some content in source + // because children contains all child IDs including unpublished children - and + // then if we're not previewing, the source will return null. + INavigableContent content = SourceGet(InternalState.Siblings[--InternalState.SiblingIndex]); + if (content == null) { - // before children elements may come some property elements - if (MoveToParentElement()) // pops the state - { - _state.FieldIndex = _state.FieldsCount - 1; - DebugState(); - succ = true; - } + continue; } + + InternalState.Content = content; + DebugState(); + succ = true; break; - case StatePosition.PropertyElement: - succ = false; - if (_state.FieldIndex > _lastAttributeIndex) + } + + if (succ == false && InternalState.SiblingIndex == 0 && + InternalState.FieldsCount - 1 > _lastAttributeIndex) + { + // before children elements may come some property elements + if (MoveToParentElement()) // pops the state { - --_state.FieldIndex; + InternalState.FieldIndex = InternalState.FieldsCount - 1; DebugState(); succ = true; } - break; - case StatePosition.Attribute: - case StatePosition.PropertyText: - case StatePosition.Root: + } + + break; + case StatePosition.PropertyElement: + succ = false; + if (InternalState.FieldIndex > _lastAttributeIndex) + { + --InternalState.FieldIndex; + DebugState(); + succ = true; + } + + break; + case StatePosition.Attribute: + case StatePosition.PropertyText: + case StatePosition.Root: + succ = false; + break; + default: + throw new InvalidOperationException("Invalid position."); + } + + DebugReturn(succ); + return succ; + } + + /// + /// Moves the XPathNavigator to the next attribute. + /// + /// + /// Returns true if the XPathNavigator is successful moving to the next attribute; + /// false if there are no more attributes. If false, the position of the XPathNavigator is unchanged. + /// + public override bool MoveToNextAttribute() + { + DebugEnter("MoveToNextAttribute"); + bool succ; + + switch (InternalState.Position) + { + case StatePosition.PropertyXml: + succ = InternalState.XmlFragmentNavigator?.MoveToNextAttribute() ?? false; + break; + case StatePosition.Attribute: + if (InternalState.FieldIndex == _lastAttributeIndex) + { succ = false; - break; - default: - throw new InvalidOperationException("Invalid position."); - } + } + else + { + ++InternalState.FieldIndex; + DebugState(); + succ = true; + } + + break; + case StatePosition.Element: + case StatePosition.PropertyElement: + case StatePosition.PropertyText: + case StatePosition.Root: + succ = false; + break; + default: + throw new InvalidOperationException("Invalid position."); + } + + DebugReturn(succ); + return succ; + } + + /// + /// Moves the XPathNavigator to the parent node of the current node. + /// + /// + /// Returns true if the XPathNavigator is successful moving to the parent node of the current node; + /// otherwise, false. If false, the position of the XPathNavigator is unchanged. + /// + public override bool MoveToParent() + { + DebugEnter("MoveToParent"); + bool succ; + + switch (InternalState.Position) + { + case StatePosition.Attribute: + case StatePosition.PropertyElement: + InternalState.Position = StatePosition.Element; + InternalState.FieldIndex = -1; + DebugState(); + succ = true; + break; + case StatePosition.Element: + succ = MoveToParentElement(); + if (succ == false) + { + InternalState.Position = StatePosition.Root; + succ = true; + } + + break; + case StatePosition.PropertyText: + InternalState.Position = StatePosition.PropertyElement; + DebugState(); + succ = true; + break; + case StatePosition.PropertyXml: + if (InternalState.XmlFragmentNavigator?.MoveToParent() == false) + { + throw new InvalidOperationException("Could not move to parent in fragment."); + } - DebugReturn(succ); - return succ; + if (InternalState.XmlFragmentNavigator?.NodeType == XPathNodeType.Root) + { + InternalState.XmlFragmentNavigator = null; + InternalState.Position = StatePosition.PropertyElement; + DebugState(); + } + + succ = true; + break; + case StatePosition.Root: + succ = false; + break; + default: + throw new InvalidOperationException("Invalid position."); } - /// - /// Moves the XPathNavigator to the next attribute. - /// - /// Returns true if the XPathNavigator is successful moving to the next attribute; - /// false if there are no more attributes. If false, the position of the XPathNavigator is unchanged. - public override bool MoveToNextAttribute() + DebugReturn(succ); + return succ; + } + + private bool MoveToParentElement() + { + State p = InternalState.Parent; + if (p != null) { - DebugEnter("MoveToNextAttribute"); - bool succ; + InternalState = p; + DebugState(); + return true; + } - switch (_state.Position) + return false; + } + + /// + /// Moves the XPathNavigator to the root node that the current node belongs to. + /// + public override void MoveToRoot() + { + DebugEnter("MoveToRoot"); + + while (InternalState.Parent != null) + { + InternalState = InternalState.Parent; + } + + DebugState(); + + DebugReturn(); + } + + /// + /// Gets the base URI for the current node. + /// + public override string BaseURI => string.Empty; + + /// + /// Gets the XmlNameTable of the XPathNavigator. + /// + public override XmlNameTable NameTable => _nameTable; + + /// + /// Gets the namespace URI of the current node. + /// + public override string NamespaceURI => string.Empty; + + /// + /// Gets the XPathNodeType of the current node. + /// + public override XPathNodeType NodeType + { + get + { + DebugEnter("NodeType"); + XPathNodeType type; + + switch (InternalState.Position) { case StatePosition.PropertyXml: - succ = _state.XmlFragmentNavigator?.MoveToNextAttribute() ?? false; + type = InternalState.XmlFragmentNavigator?.NodeType ?? XPathNodeType.Root; break; case StatePosition.Attribute: - if (_state.FieldIndex == _lastAttributeIndex) - succ = false; - else - { - ++_state.FieldIndex; - DebugState(); - succ = true; - } + type = XPathNodeType.Attribute; break; case StatePosition.Element: case StatePosition.PropertyElement: + type = XPathNodeType.Element; + break; case StatePosition.PropertyText: + type = XPathNodeType.Text; + break; case StatePosition.Root: - succ = false; + type = XPathNodeType.Root; break; default: throw new InvalidOperationException("Invalid position."); } - DebugReturn(succ); - return succ; + DebugReturn("\'{0}\'", type); + return type; } + } + + /// + /// Gets the namespace prefix associated with the current node. + /// + public override string Prefix => string.Empty; - /// - /// Moves the XPathNavigator to the parent node of the current node. - /// - /// Returns true if the XPathNavigator is successful moving to the parent node of the current node; - /// otherwise, false. If false, the position of the XPathNavigator is unchanged. - public override bool MoveToParent() + /// + /// Gets the string value of the item. + /// + /// + /// Does not fully behave as per the specs, as we report empty value on content elements, and we start + /// reporting values only on property elements. This is because, otherwise, we would dump the whole database + /// and it probably does not make sense at Umbraco level. + /// + public override string Value + { + get { - DebugEnter("MoveToParent"); - bool succ; + DebugEnter("Value"); + string value; - switch (_state.Position) + switch (InternalState.Position) { + case StatePosition.PropertyXml: + value = InternalState.XmlFragmentNavigator?.Value ?? string.Empty; + break; case StatePosition.Attribute: + case StatePosition.PropertyText: case StatePosition.PropertyElement: - _state.Position = StatePosition.Element; - _state.FieldIndex = -1; - DebugState(); - succ = true; - break; - case StatePosition.Element: - succ = MoveToParentElement(); - if (succ == false) + if (InternalState.FieldIndex == -1) { - _state.Position = StatePosition.Root; - succ = true; + value = InternalState.Content?.Id.ToString(CultureInfo.InvariantCulture) ?? string.Empty; } - break; - case StatePosition.PropertyText: - _state.Position = StatePosition.PropertyElement; - DebugState(); - succ = true; - break; - case StatePosition.PropertyXml: - if (_state.XmlFragmentNavigator?.MoveToParent() == false) - throw new InvalidOperationException("Could not move to parent in fragment."); - if (_state.XmlFragmentNavigator?.NodeType == XPathNodeType.Root) + else { - _state.XmlFragmentNavigator = null; - _state.Position = StatePosition.PropertyElement; - DebugState(); + var valueForXPath = InternalState.Content?.Value(InternalState.FieldIndex); + + // value should be + // - an XPathNavigator over a non-empty XML fragment + // - a non-Xml-whitespace string + // - null + + var nav = valueForXPath as XPathNavigator; + var s = valueForXPath as string; + if (valueForXPath == null) + { + value = string.Empty; + } + else if (nav != null) + { + nav = nav.Clone(); // never use the one we got + value = nav.Value; + } + else if (s != null) + { + value = s; + } + else + { + throw new InvalidOperationException("XPathValue must be an XPathNavigator or a string."); + } } - succ = true; + break; + case StatePosition.Element: case StatePosition.Root: - succ = false; + value = string.Empty; break; default: throw new InvalidOperationException("Invalid position."); } - DebugReturn(succ); - return succ; + DebugReturn("\"{0}\"", value); + return value; } + } - private bool MoveToParentElement() - { - var p = _state.Parent; - if (p != null) - { - _state = p; - DebugState(); - return true; - } + #region State management - return false; - } + // the possible state positions + public enum StatePosition + { + Root, + Element, + Attribute, + PropertyElement, + PropertyText, + PropertyXml + } - /// - /// Moves the XPathNavigator to the root node that the current node belongs to. - /// - public override void MoveToRoot() - { - DebugEnter("MoveToRoot"); + // gets the state + // for unit tests only + public State InternalState { get; private set; } - while (_state.Parent != null) - _state = _state.Parent; - DebugState(); + // represents the XPathNavigator state + public class State + { + private static readonly int[] NoChildIds = new int[0]; - DebugReturn(); - } + // the current content + private INavigableContent? _content; - /// - /// Gets the base URI for the current node. - /// - public override string BaseURI => string.Empty; - - /// - /// Gets the XmlNameTable of the XPathNavigator. - /// - public override XmlNameTable NameTable => _nameTable; - - /// - /// Gets the namespace URI of the current node. - /// - public override string NamespaceURI => string.Empty; - - /// - /// Gets the XPathNodeType of the current node. - /// - public override XPathNodeType NodeType + // initialize a new state + private State(StatePosition position) { - get - { - DebugEnter("NodeType"); - XPathNodeType type; - - switch (_state.Position) - { - case StatePosition.PropertyXml: - type = _state.XmlFragmentNavigator?.NodeType ?? XPathNodeType.Root; - break; - case StatePosition.Attribute: - type = XPathNodeType.Attribute; - break; - case StatePosition.Element: - case StatePosition.PropertyElement: - type = XPathNodeType.Element; - break; - case StatePosition.PropertyText: - type = XPathNodeType.Text; - break; - case StatePosition.Root: - type = XPathNodeType.Root; - break; - default: - throw new InvalidOperationException("Invalid position."); - } - - DebugReturn("\'{0}\'", type); - return type; - } + Position = position; + FieldIndex = -1; } - /// - /// Gets the namespace prefix associated with the current node. - /// - public override string Prefix => string.Empty; - - /// - /// Gets the string value of the item. - /// - /// Does not fully behave as per the specs, as we report empty value on content elements, and we start - /// reporting values only on property elements. This is because, otherwise, we would dump the whole database - /// and it probably does not make sense at Umbraco level. - public override string Value + // initialize a new state + // used for creating the very first state + // and also when moving to a child element + public State(INavigableContent? content, State? parent, IList? siblings, int siblingIndex, + StatePosition position) + : this(position) { - get - { - DebugEnter("Value"); - string value; - - switch (_state.Position) - { - case StatePosition.PropertyXml: - value = _state.XmlFragmentNavigator?.Value ?? string.Empty; - break; - case StatePosition.Attribute: - case StatePosition.PropertyText: - case StatePosition.PropertyElement: - if (_state.FieldIndex == -1) - { - value = _state.Content?.Id.ToString(CultureInfo.InvariantCulture) ?? string.Empty; - } - else - { - var valueForXPath = _state.Content?.Value(_state.FieldIndex); - - // value should be - // - an XPathNavigator over a non-empty XML fragment - // - a non-Xml-whitespace string - // - null - - var nav = valueForXPath as XPathNavigator; - var s = valueForXPath as string; - if (valueForXPath == null) - { - value = string.Empty; - } - else if (nav != null) - { - nav = nav.Clone(); // never use the one we got - value = nav.Value; - } - else if (s != null) - { - value = s; - } - else - { - throw new InvalidOperationException("XPathValue must be an XPathNavigator or a string."); - } - } - break; - case StatePosition.Element: - case StatePosition.Root: - value = string.Empty; - break; - default: - throw new InvalidOperationException("Invalid position."); - } - - DebugReturn("\"{0}\"", value); - return value; - } + Content = content; + Parent = parent; + Depth = parent?.Depth + 1 ?? 0; + Siblings = siblings; + SiblingIndex = siblingIndex; } - #region State management - - // the possible state positions - public enum StatePosition - { - Root, - Element, - Attribute, - PropertyElement, - PropertyText, - PropertyXml - }; - - // gets the state - // for unit tests only - public State InternalState => _state; - - // represents the XPathNavigator state - public class State + // initialize a clone state + private State(State other, bool recurse = false) { - public StatePosition Position { get; set; } + Position = other.Position; - // initialize a new state - private State(StatePosition position) - { - Position = position; - FieldIndex = -1; - } + _content = other._content; + SiblingIndex = other.SiblingIndex; + Siblings = other.Siblings; + FieldsCount = other.FieldsCount; + FieldIndex = other.FieldIndex; + Depth = other.Depth; - // initialize a new state - // used for creating the very first state - // and also when moving to a child element - public State(INavigableContent? content, State? parent, IList? siblings, int siblingIndex, StatePosition position) - : this(position) + if (Position == StatePosition.PropertyXml) { - Content = content; - Parent = parent; - Depth = parent?.Depth + 1 ?? 0; - Siblings = siblings; - SiblingIndex = siblingIndex; + XmlFragmentNavigator = other.XmlFragmentNavigator?.Clone(); } - // initialize a clone state - private State(State other, bool recurse = false) - { - Position = other.Position; - - _content = other._content; - SiblingIndex = other.SiblingIndex; - Siblings = other.Siblings; - FieldsCount = other.FieldsCount; - FieldIndex = other.FieldIndex; - Depth = other.Depth; - - if (Position == StatePosition.PropertyXml) - XmlFragmentNavigator = other.XmlFragmentNavigator?.Clone(); - - // NielsK did - //Parent = other.Parent; - // but that creates corrupted stacks of states when cloning - // because clones share the parents : have to clone the whole - // stack of states. Avoid recursion. + // NielsK did + //Parent = other.Parent; + // but that creates corrupted stacks of states when cloning + // because clones share the parents : have to clone the whole + // stack of states. Avoid recursion. - if (recurse) return; - - var clone = this; - while (other.Parent != null) - { - clone.Parent = new State(other.Parent, true); - clone = clone.Parent; - other = other.Parent; - } + if (recurse) + { + return; } - public State Clone() + State clone = this; + while (other.Parent != null) { - return new State(this); + clone.Parent = new State(other.Parent, true); + clone = clone.Parent; + other = other.Parent; } + } - // the parent state - public State? Parent { get; private set; } + public StatePosition Position { get; set; } - // the depth - public int Depth { get; } + // the parent state + public State? Parent { get; private set; } - // the current content - private INavigableContent? _content; + // the depth + public int Depth { get; } - // the current content - public INavigableContent? Content + // the current content + public INavigableContent? Content + { + get => _content; + set { - get - { - return _content; - } - set - { - FieldsCount = value?.Type.FieldTypes.Length ?? 0; - _content = value; - } + FieldsCount = value?.Type.FieldTypes.Length ?? 0; + _content = value; } + } - private static readonly int[] NoChildIds = new int[0]; + // the index of the current content within Siblings + public int SiblingIndex { get; set; } - // the current content child ids - public IList GetContentChildIds(int maxDepth) - { - return Depth < maxDepth && _content?.ChildIds != null ? _content.ChildIds : NoChildIds; - } + // the list of content identifiers for all children of the current content's parent + public IList? Siblings { get; } - // the index of the current content within Siblings - public int SiblingIndex { get; set; } + // the number of fields of the current content + // properties include attributes and properties + public int FieldsCount { get; private set; } - // the list of content identifiers for all children of the current content's parent - public IList? Siblings { get; } + // the index of the current field + // index -1 means special attribute "id" + public int FieldIndex { get; set; } - // the number of fields of the current content - // properties include attributes and properties - public int FieldsCount { get; private set; } + // the current field type + // beware, no check on the index + public INavigableFieldType? CurrentFieldType => Content?.Type.FieldTypes[FieldIndex]; - // the index of the current field - // index -1 means special attribute "id" - public int FieldIndex { get; set; } + // gets or sets the xml fragment navigator + public XPathNavigator? XmlFragmentNavigator { get; set; } - // the current field type - // beware, no check on the index - public INavigableFieldType? CurrentFieldType => Content?.Type.FieldTypes[FieldIndex]; + public State Clone() => new State(this); - // gets or sets the xml fragment navigator - public XPathNavigator? XmlFragmentNavigator { get; set; } + // the current content child ids + public IList GetContentChildIds(int maxDepth) => + Depth < maxDepth && _content?.ChildIds != null ? _content.ChildIds : NoChildIds; - // gets a value indicating whether this state is at the same position as another one. - public bool IsSamePosition(State other) + // gets a value indicating whether this state is at the same position as another one. + public bool IsSamePosition(State other) + { + if (other.XmlFragmentNavigator is null || XmlFragmentNavigator is null) { - if (other.XmlFragmentNavigator is null || XmlFragmentNavigator is null) - { - return false; - } - return other.Position == Position - && (Position != StatePosition.PropertyXml || other.XmlFragmentNavigator.IsSamePosition(XmlFragmentNavigator)) - && other.Content == Content - && other.FieldIndex == FieldIndex; + return false; } - } - #endregion + return other.Position == Position + && (Position != StatePosition.PropertyXml || + other.XmlFragmentNavigator.IsSamePosition(XmlFragmentNavigator)) + && other.Content == Content + && other.FieldIndex == FieldIndex; + } } + + #endregion } diff --git a/src/Umbraco.Core/Xml/XPath/RenamedRootNavigator.cs b/src/Umbraco.Core/Xml/XPath/RenamedRootNavigator.cs index 364560ebee23..a2fc9e6b5285 100644 --- a/src/Umbraco.Core/Xml/XPath/RenamedRootNavigator.cs +++ b/src/Umbraco.Core/Xml/XPath/RenamedRootNavigator.cs @@ -1,119 +1,90 @@ using System.Xml; using System.Xml.XPath; -namespace Umbraco.Cms.Core.Xml.XPath +namespace Umbraco.Cms.Core.Xml.XPath; + +public class RenamedRootNavigator : XPathNavigator { - public class RenamedRootNavigator : XPathNavigator + private readonly XPathNavigator _navigator; + private readonly string _rootName; + + public RenamedRootNavigator(XPathNavigator navigator, string rootName) { - private readonly XPathNavigator _navigator; - private readonly string _rootName; + _navigator = navigator; + _rootName = rootName; + } - public RenamedRootNavigator(XPathNavigator navigator, string rootName) - { - _navigator = navigator; - _rootName = rootName; - } + public override string BaseURI => _navigator.BaseURI; - public override string BaseURI => _navigator.BaseURI; + public override bool IsEmptyElement => _navigator.IsEmptyElement; - public override XPathNavigator Clone() + public override string LocalName + { + get { - return new RenamedRootNavigator(_navigator.Clone(), _rootName); - } + // local name without prefix - public override bool IsEmptyElement => _navigator.IsEmptyElement; + XPathNavigator nav = _navigator.Clone(); + if (nav.MoveToParent() && nav.MoveToParent()) + { + return _navigator.LocalName; + } - public override bool IsSamePosition(XPathNavigator other) - { - return _navigator.IsSamePosition(other); + return _rootName; } + } - public override string LocalName + public override string Name + { + get { - get - { - // local name without prefix + // qualified name with prefix - var nav = _navigator.Clone(); - if (nav.MoveToParent() && nav.MoveToParent()) - return _navigator.LocalName; - return _rootName; + XPathNavigator nav = _navigator.Clone(); + if (nav.MoveToParent() && nav.MoveToParent()) + { + return _navigator.Name; } - } - public override bool MoveTo(XPathNavigator other) - { - return _navigator.MoveTo(other); + var name = _navigator.Name; + var pos = name.IndexOf(':'); + return pos < 0 ? _rootName : name.Substring(0, pos + 1) + _rootName; } + } - public override bool MoveToFirstAttribute() - { - return _navigator.MoveToFirstAttribute(); - } + public override XmlNameTable NameTable => _navigator.NameTable; - public override bool MoveToFirstChild() - { - return _navigator.MoveToFirstChild(); - } + public override string NamespaceURI => _navigator.NamespaceURI; - public override bool MoveToFirstNamespace(XPathNamespaceScope namespaceScope) - { - return _navigator.MoveToFirstNamespace(namespaceScope); - } + public override XPathNodeType NodeType => _navigator.NodeType; - public override bool MoveToId(string id) - { - return _navigator.MoveToId(id); - } + public override string Prefix => _navigator.Prefix; - public override bool MoveToNext() - { - return _navigator.MoveToNext(); - } + public override string Value => _navigator.Value; - public override bool MoveToNextAttribute() - { - return _navigator.MoveToNextAttribute(); - } + public override XPathNavigator Clone() => new RenamedRootNavigator(_navigator.Clone(), _rootName); - public override bool MoveToNextNamespace(XPathNamespaceScope namespaceScope) - { - return _navigator.MoveToNextNamespace(namespaceScope); - } + public override bool IsSamePosition(XPathNavigator other) => _navigator.IsSamePosition(other); - public override bool MoveToParent() - { - return _navigator.MoveToParent(); - } + public override bool MoveTo(XPathNavigator other) => _navigator.MoveTo(other); - public override bool MoveToPrevious() - { - return _navigator.MoveToPrevious(); - } + public override bool MoveToFirstAttribute() => _navigator.MoveToFirstAttribute(); - public override string Name - { - get - { - // qualified name with prefix - - var nav = _navigator.Clone(); - if (nav.MoveToParent() && nav.MoveToParent()) - return _navigator.Name; - var name = _navigator.Name; - var pos = name.IndexOf(':'); - return pos < 0 ? _rootName : (name.Substring(0, pos + 1) + _rootName); - } - } + public override bool MoveToFirstChild() => _navigator.MoveToFirstChild(); - public override XmlNameTable NameTable => _navigator.NameTable; + public override bool MoveToFirstNamespace(XPathNamespaceScope namespaceScope) => + _navigator.MoveToFirstNamespace(namespaceScope); - public override string NamespaceURI => _navigator.NamespaceURI; + public override bool MoveToId(string id) => _navigator.MoveToId(id); - public override XPathNodeType NodeType => _navigator.NodeType; + public override bool MoveToNext() => _navigator.MoveToNext(); - public override string Prefix => _navigator.Prefix; + public override bool MoveToNextAttribute() => _navigator.MoveToNextAttribute(); - public override string Value => _navigator.Value; - } + public override bool MoveToNextNamespace(XPathNamespaceScope namespaceScope) => + _navigator.MoveToNextNamespace(namespaceScope); + + public override bool MoveToParent() => _navigator.MoveToParent(); + + public override bool MoveToPrevious() => _navigator.MoveToPrevious(); } diff --git a/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs b/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs index 8006d26da6d2..5ca8d98a4482 100644 --- a/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs +++ b/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs @@ -4,58 +4,69 @@ using System.Xml.XPath; using Umbraco.Cms.Core.Xml; -namespace Umbraco.Extensions +namespace Umbraco.Extensions; + +/// +/// Provides extensions to XPathNavigator. +/// +public static class XPathNavigatorExtensions { /// - /// Provides extensions to XPathNavigator. + /// Selects a node set, using the specified XPath expression. + /// + /// A source XPathNavigator. + /// An XPath expression. + /// A set of XPathVariables. + /// An iterator over the nodes matching the specified expression. + public static XPathNodeIterator Select(this XPathNavigator navigator, string expression, + params XPathVariable[] variables) + { + if (variables == null || variables.Length == 0 || variables[0] == null) + { + return navigator.Select(expression); + } + + // Reflector shows that the standard XPathNavigator.Compile method just does + // return XPathExpression.Compile(xpath); + // only difference is, XPathNavigator.Compile is virtual so it could be overridden + // by a class inheriting from XPathNavigator... there does not seem to be any + // doing it in the Framework, though... so we'll assume it's much cleaner to use + // the static compile: + var compiled = XPathExpression.Compile(expression); + + var context = new DynamicContext(); + foreach (XPathVariable variable in variables) + { + context.AddVariable(variable.Name, variable.Value); + } + + compiled.SetContext(context); + return navigator.Select(compiled); + } + + /// + /// Selects a node set, using the specified XPath expression. /// - public static class XPathNavigatorExtensions + /// A source XPathNavigator. + /// An XPath expression. + /// A set of XPathVariables. + /// An iterator over the nodes matching the specified expression. + public static XPathNodeIterator Select(this XPathNavigator navigator, XPathExpression expression, + params XPathVariable[] variables) { - /// - /// Selects a node set, using the specified XPath expression. - /// - /// A source XPathNavigator. - /// An XPath expression. - /// A set of XPathVariables. - /// An iterator over the nodes matching the specified expression. - public static XPathNodeIterator Select(this XPathNavigator navigator, string expression, params XPathVariable[] variables) + if (variables == null || variables.Length == 0 || variables[0] == null) { - if (variables == null || variables.Length == 0 || variables[0] == null) - return navigator.Select(expression); - - // Reflector shows that the standard XPathNavigator.Compile method just does - // return XPathExpression.Compile(xpath); - // only difference is, XPathNavigator.Compile is virtual so it could be overridden - // by a class inheriting from XPathNavigator... there does not seem to be any - // doing it in the Framework, though... so we'll assume it's much cleaner to use - // the static compile: - var compiled = XPathExpression.Compile(expression); - - var context = new DynamicContext(); - foreach (var variable in variables) - context.AddVariable(variable.Name, variable.Value); - compiled.SetContext(context); - return navigator.Select(compiled); + return navigator.Select(expression); } - /// - /// Selects a node set, using the specified XPath expression. - /// - /// A source XPathNavigator. - /// An XPath expression. - /// A set of XPathVariables. - /// An iterator over the nodes matching the specified expression. - public static XPathNodeIterator Select(this XPathNavigator navigator, XPathExpression expression, params XPathVariable[] variables) + XPathExpression compiled = expression.Clone(); // clone for thread-safety + var context = new DynamicContext(); + foreach (XPathVariable variable in variables) { - if (variables == null || variables.Length == 0 || variables[0] == null) - return navigator.Select(expression); - - var compiled = expression.Clone(); // clone for thread-safety - var context = new DynamicContext(); - foreach (var variable in variables) - context.AddVariable(variable.Name, variable.Value); - compiled.SetContext(context); - return navigator.Select(compiled); + context.AddVariable(variable.Name, variable.Value); } + + compiled.SetContext(context); + return navigator.Select(compiled); } } diff --git a/src/Umbraco.Core/Xml/XPathVariable.cs b/src/Umbraco.Core/Xml/XPathVariable.cs index 9bfed8e98d17..e370237d2f20 100644 --- a/src/Umbraco.Core/Xml/XPathVariable.cs +++ b/src/Umbraco.Core/Xml/XPathVariable.cs @@ -1,32 +1,31 @@ // source: mvpxml.codeplex.com -namespace Umbraco.Cms.Core.Xml +namespace Umbraco.Cms.Core.Xml; + +/// +/// Represents a variable in an XPath query. +/// +/// The name must be foo in the constructor and $foo in the XPath query. +public class XPathVariable { /// - /// Represents a variable in an XPath query. + /// Initializes a new instance of the class with a name and a value. /// - /// The name must be foo in the constructor and $foo in the XPath query. - public class XPathVariable + /// + /// + public XPathVariable(string name, string value) { - /// - /// Gets or sets the name of the variable. - /// - public string Name { get; private set; } + Name = name; + Value = value; + } - /// - /// Gets or sets the value of the variable. - /// - public string Value { get; private set; } + /// + /// Gets or sets the name of the variable. + /// + public string Name { get; } - /// - /// Initializes a new instance of the class with a name and a value. - /// - /// - /// - public XPathVariable(string name, string value) - { - Name = name; - Value = value; - } - } + /// + /// Gets or sets the value of the variable. + /// + public string Value { get; } } diff --git a/src/Umbraco.Core/Xml/XmlHelper.cs b/src/Umbraco.Core/Xml/XmlHelper.cs index 4de056e22393..856950e91a6e 100644 --- a/src/Umbraco.Core/Xml/XmlHelper.cs +++ b/src/Umbraco.Core/Xml/XmlHelper.cs @@ -1,392 +1,524 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using System.Xml; using System.Xml.XPath; using Umbraco.Cms.Core.Hosting; -namespace Umbraco.Cms.Core.Xml +namespace Umbraco.Cms.Core.Xml; + +/// +/// The XmlHelper class contains general helper methods for working with xml in umbraco. +/// +public class XmlHelper { /// - /// The XmlHelper class contains general helper methods for working with xml in umbraco. + /// Creates or sets an attribute on the XmlNode if an Attributes collection is available /// - public class XmlHelper + /// + /// + /// + /// + public static void SetAttribute(XmlDocument xml, XmlNode n, string name, string value) { - /// - /// Creates or sets an attribute on the XmlNode if an Attributes collection is available - /// - /// - /// - /// - /// - public static void SetAttribute(XmlDocument xml, XmlNode n, string name, string value) - { - if (xml == null) throw new ArgumentNullException(nameof(xml)); - if (n == null) throw new ArgumentNullException(nameof(n)); - if (name == null) throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); - - if (n.Attributes == null) - { - return; - } - if (n.Attributes[name] == null) - { - var a = xml.CreateAttribute(name); - a.Value = value; - n.Attributes.Append(a); - } - else - { - n.Attributes[name]!.Value = value; - } + if (xml == null) + { + throw new ArgumentNullException(nameof(xml)); } - /// - /// Gets a value indicating whether a specified string contains only xml whitespace characters. - /// - /// The string. - /// true if the string contains only xml whitespace characters. - /// As per XML 1.1 specs, space, \t, \r and \n. - public static bool IsXmlWhitespace(string s) + if (n == null) { - // as per xml 1.1 specs - anything else is significant whitespace - s = s.Trim(Constants.CharArrays.XmlWhitespaceChars); - return s.Length == 0; + throw new ArgumentNullException(nameof(n)); } - /// - /// Creates a new XPathDocument from an xml string. - /// - /// The xml string. - /// An XPathDocument created from the xml string. - public static XPathDocument CreateXPathDocument(string xml) + if (name == null) { - return new XPathDocument(new XmlTextReader(new StringReader(xml))); + throw new ArgumentNullException(nameof(name)); } - /// - /// Tries to create a new XPathDocument from an xml string. - /// - /// The xml string. - /// The XPath document. - /// A value indicating whether it has been possible to create the document. - public static bool TryCreateXPathDocument(string xml, out XPathDocument? doc) + if (string.IsNullOrWhiteSpace(name)) { - try - { - doc = CreateXPathDocument(xml); - return true; - } - catch (Exception) - { - doc = null; - return false; - } + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(name)); } - /// - /// Tries to create a new XPathDocument from a property value. - /// - /// The value of the property. - /// The XPath document. - /// A value indicating whether it has been possible to create the document. - /// The value can be anything... Performance-wise, this is bad. - public static bool TryCreateXPathDocumentFromPropertyValue(object value, out XPathDocument? doc) + if (n.Attributes == null) { - // DynamicNode.ConvertPropertyValueByDataType first cleans the value by calling - // XmlHelper.StripDashesInElementOrAttributeName - this is because the XML is - // to be returned as a DynamicXml and element names such as "value-item" are - // invalid and must be converted to "valueitem". But we don't have that sort of - // problem here - and we don't need to bother with dashes nor dots, etc. + return; + } - doc = null; - var xml = value as string; - if (xml == null) return false; // no a string - if (CouldItBeXml(xml) == false) return false; // string does not look like it's xml - if (IsXmlWhitespace(xml)) return false; // string is whitespace, xml-wise - if (TryCreateXPathDocument(xml, out doc) == false) return false; // string can't be parsed into xml - - var nav = doc!.CreateNavigator(); - if (nav.MoveToFirstChild()) - { - //SD: This used to do this but the razor macros and the entire razor macros section is gone, it was all legacy, it seems this method isn't even - // used apart from for tests so don't think this matters. In any case, we no longer check for this! + if (n.Attributes[name] == null) + { + XmlAttribute a = xml.CreateAttribute(name); + a.Value = value; + n.Attributes.Append(a); + } + else + { + n.Attributes[name]!.Value = value; + } + } - //var name = nav.LocalName; // must not match an excluded tag - //if (UmbracoConfig.For.UmbracoSettings().Scripting.NotDynamicXmlDocumentElements.All(x => x.Element.InvariantEquals(name) == false)) return true; + /// + /// Gets a value indicating whether a specified string contains only xml whitespace characters. + /// + /// The string. + /// true if the string contains only xml whitespace characters. + /// As per XML 1.1 specs, space, \t, \r and \n. + public static bool IsXmlWhitespace(string s) + { + // as per xml 1.1 specs - anything else is significant whitespace + s = s.Trim(Constants.CharArrays.XmlWhitespaceChars); + return s.Length == 0; + } - return true; - } + /// + /// Creates a new XPathDocument from an xml string. + /// + /// The xml string. + /// An XPathDocument created from the xml string. + public static XPathDocument CreateXPathDocument(string xml) => + new XPathDocument(new XmlTextReader(new StringReader(xml))); + /// + /// Tries to create a new XPathDocument from an xml string. + /// + /// The xml string. + /// The XPath document. + /// A value indicating whether it has been possible to create the document. + public static bool TryCreateXPathDocument(string xml, out XPathDocument? doc) + { + try + { + doc = CreateXPathDocument(xml); + return true; + } + catch (Exception) + { doc = null; return false; } + } + /// + /// Tries to create a new XPathDocument from a property value. + /// + /// The value of the property. + /// The XPath document. + /// A value indicating whether it has been possible to create the document. + /// The value can be anything... Performance-wise, this is bad. + public static bool TryCreateXPathDocumentFromPropertyValue(object value, out XPathDocument? doc) + { + // DynamicNode.ConvertPropertyValueByDataType first cleans the value by calling + // XmlHelper.StripDashesInElementOrAttributeName - this is because the XML is + // to be returned as a DynamicXml and element names such as "value-item" are + // invalid and must be converted to "valueitem". But we don't have that sort of + // problem here - and we don't need to bother with dashes nor dots, etc. + + doc = null; + var xml = value as string; + if (xml == null) + { + return false; // no a string + } + + if (CouldItBeXml(xml) == false) + { + return false; // string does not look like it's xml + } + + if (IsXmlWhitespace(xml)) + { + return false; // string is whitespace, xml-wise + } + + if (TryCreateXPathDocument(xml, out doc) == false) + { + return false; // string can't be parsed into xml + } - /// - /// Sorts the children of a parentNode. - /// - /// The parent node. - /// An XPath expression to select children of to sort. - /// A function returning the value to order the nodes by. - public static void SortNodes( - XmlNode parentNode, - string childNodesXPath, - Func orderBy) + XPathNavigator nav = doc!.CreateNavigator(); + if (nav.MoveToFirstChild()) { - var sortedChildNodes = parentNode.SelectNodes(childNodesXPath)?.Cast() - .OrderBy(orderBy) - .ToArray(); + //SD: This used to do this but the razor macros and the entire razor macros section is gone, it was all legacy, it seems this method isn't even + // used apart from for tests so don't think this matters. In any case, we no longer check for this! - // append child nodes to last position, in sort-order - // so all child nodes will go after the property nodes - if (sortedChildNodes is not null) + //var name = nav.LocalName; // must not match an excluded tag + //if (UmbracoConfig.For.UmbracoSettings().Scripting.NotDynamicXmlDocumentElements.All(x => x.Element.InvariantEquals(name) == false)) return true; + + return true; + } + + doc = null; + return false; + } + + + /// + /// Sorts the children of a parentNode. + /// + /// The parent node. + /// An XPath expression to select children of to sort. + /// A function returning the value to order the nodes by. + public static void SortNodes( + XmlNode parentNode, + string childNodesXPath, + Func orderBy) + { + XmlNode[] sortedChildNodes = parentNode.SelectNodes(childNodesXPath)?.Cast() + .OrderBy(orderBy) + .ToArray(); + + // append child nodes to last position, in sort-order + // so all child nodes will go after the property nodes + if (sortedChildNodes is not null) + { + foreach (XmlNode node in sortedChildNodes) { - foreach (var node in sortedChildNodes) - parentNode.AppendChild(node); // moves the node to the last position + parentNode.AppendChild(node); // moves the node to the last position } } + } - /// - /// Sorts a single child node of a parentNode. - /// - /// The parent node. - /// An XPath expression to select children of to sort. - /// The child node to sort. - /// A function returning the value to order the nodes by. - /// A value indicating whether sorting was needed. - /// Assuming all nodes but are sorted, this will move the node to - /// the right position without moving all the nodes (as SortNodes would do) - should improve perfs. - public static bool SortNode( - XmlNode parentNode, - string childNodesXPath, - XmlNode node, - Func orderBy) - { - var nodeSortOrder = orderBy(node); - var childNodesAndOrder = parentNode.SelectNodes(childNodesXPath)?.Cast() - .Select(x => Tuple.Create(x, orderBy(x))).ToArray(); - - // only one node = node is in the right place already, obviously - if (childNodesAndOrder is null || childNodesAndOrder.Length == 1) return false; - - // find the first node with a sortOrder > node.sortOrder - var i = 0; - while (i < childNodesAndOrder.Length && childNodesAndOrder[i].Item2 <= nodeSortOrder) - i++; - - // if one was found - if (i < childNodesAndOrder.Length) + /// + /// Sorts a single child node of a parentNode. + /// + /// The parent node. + /// An XPath expression to select children of to sort. + /// The child node to sort. + /// A function returning the value to order the nodes by. + /// A value indicating whether sorting was needed. + /// + /// Assuming all nodes but are sorted, this will move the node to + /// the right position without moving all the nodes (as SortNodes would do) - should improve perfs. + /// + public static bool SortNode( + XmlNode parentNode, + string childNodesXPath, + XmlNode node, + Func orderBy) + { + var nodeSortOrder = orderBy(node); + Tuple[] childNodesAndOrder = parentNode.SelectNodes(childNodesXPath)?.Cast() + .Select(x => Tuple.Create(x, orderBy(x))).ToArray(); + + // only one node = node is in the right place already, obviously + if (childNodesAndOrder is null || childNodesAndOrder.Length == 1) + { + return false; + } + + // find the first node with a sortOrder > node.sortOrder + var i = 0; + while (i < childNodesAndOrder.Length && childNodesAndOrder[i].Item2 <= nodeSortOrder) + { + i++; + } + + // if one was found + if (i < childNodesAndOrder.Length) + { + // and node is just before, we're done already + // else we need to move it right before the node that was found + if (i == 0 || childNodesAndOrder[i - 1].Item1 != node) { - // and node is just before, we're done already - // else we need to move it right before the node that was found - if (i == 0 || childNodesAndOrder[i - 1].Item1 != node) - { - parentNode.InsertBefore(node, childNodesAndOrder[i].Item1); - return true; - } + parentNode.InsertBefore(node, childNodesAndOrder[i].Item1); + return true; } - else // i == childNodesAndOrder.Length && childNodesAndOrder.Length > 1 + } + else // i == childNodesAndOrder.Length && childNodesAndOrder.Length > 1 + { + // and node is the last one, we're done already + // else we need to append it as the last one + // (and i > 1, see above) + if (childNodesAndOrder[i - 1].Item1 != node) { - // and node is the last one, we're done already - // else we need to append it as the last one - // (and i > 1, see above) - if (childNodesAndOrder[i - 1].Item1 != node) - { - parentNode.AppendChild(node); - return true; - } + parentNode.AppendChild(node); + return true; } - return false; } + return false; + } + - /// - /// Opens a file as a XmlDocument. - /// - /// The relative file path. ie. /config/umbraco.config - /// - /// Returns a XmlDocument class - public static XmlDocument OpenAsXmlDocument(string filePath, IHostingEnvironment hostingEnvironment) + /// + /// Opens a file as a XmlDocument. + /// + /// The relative file path. ie. /config/umbraco.config + /// + /// Returns a XmlDocument class + public static XmlDocument OpenAsXmlDocument(string filePath, IHostingEnvironment hostingEnvironment) + { + using (var reader = + new XmlTextReader(hostingEnvironment.MapPathContentRoot(filePath)) + { + WhitespaceHandling = WhitespaceHandling.All + }) { - using (var reader = new XmlTextReader(hostingEnvironment.MapPathContentRoot(filePath)) {WhitespaceHandling = WhitespaceHandling.All}) - { - var xmlDoc = new XmlDocument(); - //Load the file into the XmlDocument - xmlDoc.Load(reader); + var xmlDoc = new XmlDocument(); + //Load the file into the XmlDocument + xmlDoc.Load(reader); - return xmlDoc; - } + return xmlDoc; + } + } + + /// + /// creates a XmlAttribute with the specified name and value + /// + /// The xmldocument. + /// The name of the attribute. + /// The value of the attribute. + /// a XmlAttribute + public static XmlAttribute AddAttribute(XmlDocument xd, string name, string value) + { + if (xd == null) + { + throw new ArgumentNullException(nameof(xd)); } - /// - /// creates a XmlAttribute with the specified name and value - /// - /// The xmldocument. - /// The name of the attribute. - /// The value of the attribute. - /// a XmlAttribute - public static XmlAttribute AddAttribute(XmlDocument xd, string name, string value) - { - if (xd == null) throw new ArgumentNullException(nameof(xd)); - if (name == null) throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); - - var temp = xd.CreateAttribute(name); - temp.Value = value; - return temp; - } - - /// - /// Creates a text XmlNode with the specified name and value - /// - /// The xmldocument. - /// The node name. - /// The node value. - /// a XmlNode - public static XmlNode AddTextNode(XmlDocument xd, string name, string value) - { - if (xd == null) throw new ArgumentNullException(nameof(xd)); - if (name == null) throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); - - var temp = xd.CreateNode(XmlNodeType.Element, name, ""); - temp.AppendChild(xd.CreateTextNode(value)); - return temp; - } - - /// - /// Sets or Creates a text XmlNode with the specified name and value - /// - /// The xmldocument. - /// The node to set or create the child text node on - /// The node name. - /// The node value. - /// a XmlNode - public static XmlNode SetTextNode(XmlDocument xd, XmlNode parent, string name, string value) - { - if (xd == null) throw new ArgumentNullException(nameof(xd)); - if (parent == null) throw new ArgumentNullException(nameof(parent)); - if (name == null) throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); - - var child = parent.SelectSingleNode(name); - if (child != null) - { - child.InnerText = value; - return child; - } - return AddTextNode(xd, name, value); - } - - /// - /// Sets or creates an Xml node from its inner Xml. - /// - /// The xmldocument. - /// The node to set or create the child text node on - /// The node name. - /// The node inner Xml. - /// a XmlNode - public static XmlNode SetInnerXmlNode(XmlDocument xd, XmlNode parent, string name, string value) - { - if (xd == null) throw new ArgumentNullException(nameof(xd)); - if (parent == null) throw new ArgumentNullException(nameof(parent)); - if (name == null) throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); - - var child = parent.SelectSingleNode(name) ?? xd.CreateNode(XmlNodeType.Element, name, ""); - child.InnerXml = value; + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(name)); + } + + XmlAttribute temp = xd.CreateAttribute(name); + temp.Value = value; + return temp; + } + + /// + /// Creates a text XmlNode with the specified name and value + /// + /// The xmldocument. + /// The node name. + /// The node value. + /// a XmlNode + public static XmlNode AddTextNode(XmlDocument xd, string name, string value) + { + if (xd == null) + { + throw new ArgumentNullException(nameof(xd)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(name)); + } + + XmlNode temp = xd.CreateNode(XmlNodeType.Element, name, ""); + temp.AppendChild(xd.CreateTextNode(value)); + return temp; + } + + /// + /// Sets or Creates a text XmlNode with the specified name and value + /// + /// The xmldocument. + /// The node to set or create the child text node on + /// The node name. + /// The node value. + /// a XmlNode + public static XmlNode SetTextNode(XmlDocument xd, XmlNode parent, string name, string value) + { + if (xd == null) + { + throw new ArgumentNullException(nameof(xd)); + } + + if (parent == null) + { + throw new ArgumentNullException(nameof(parent)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(name)); + } + + XmlNode child = parent.SelectSingleNode(name); + if (child != null) + { + child.InnerText = value; return child; } - /// - /// Creates a cdata XmlNode with the specified name and value - /// - /// The xmldocument. - /// The node name. - /// The node value. - /// A XmlNode - public static XmlNode AddCDataNode(XmlDocument xd, string name, string value) - { - if (xd == null) throw new ArgumentNullException(nameof(xd)); - if (name == null) throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); - - var temp = xd.CreateNode(XmlNodeType.Element, name, ""); - temp.AppendChild(xd.CreateCDataSection(value)); - return temp; - } - - /// - /// Sets or Creates a cdata XmlNode with the specified name and value - /// - /// The xmldocument. - /// The node to set or create the child text node on - /// The node name. - /// The node value. - /// a XmlNode - public static XmlNode SetCDataNode(XmlDocument xd, XmlNode parent, string name, string value) - { - if (xd == null) throw new ArgumentNullException(nameof(xd)); - if (parent == null) throw new ArgumentNullException(nameof(parent)); - if (name == null) throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name)); - - var child = parent.SelectSingleNode(name); - if (child != null) - { - child.InnerXml = ""; - return child; - } - return AddCDataNode(xd, name, value); + return AddTextNode(xd, name, value); + } + + /// + /// Sets or creates an Xml node from its inner Xml. + /// + /// The xmldocument. + /// The node to set or create the child text node on + /// The node name. + /// The node inner Xml. + /// a XmlNode + public static XmlNode SetInnerXmlNode(XmlDocument xd, XmlNode parent, string name, string value) + { + if (xd == null) + { + throw new ArgumentNullException(nameof(xd)); } - /// - /// Gets the value of a XmlNode - /// - /// The XmlNode. - /// the value as a string - public static string GetNodeValue(XmlNode n) + if (parent == null) { - var value = string.Empty; - if (n == null || n.FirstChild == null) - return value; - value = n.FirstChild.Value ?? n.InnerXml; - return value.Replace("", "", "]]>"); + throw new ArgumentNullException(nameof(parent)); } - /// - /// Determines whether the specified string appears to be XML. - /// - /// The XML string. - /// - /// true if the specified string appears to be XML; otherwise, false. - /// - public static bool CouldItBeXml(string? xml) + if (name == null) { - if (string.IsNullOrEmpty(xml)) return false; + throw new ArgumentNullException(nameof(name)); + } - xml = xml.Trim(); - return xml.StartsWith("<") && xml.EndsWith(">") && xml.Contains('/'); + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(name)); } - /// - /// Return a dictionary of attributes found for a string based tag - /// - /// - /// - public static Dictionary GetAttributesFromElement(string tag) + XmlNode child = parent.SelectSingleNode(name) ?? xd.CreateNode(XmlNodeType.Element, name, ""); + child.InnerXml = value; + return child; + } + + /// + /// Creates a cdata XmlNode with the specified name and value + /// + /// The xmldocument. + /// The node name. + /// The node value. + /// A XmlNode + public static XmlNode AddCDataNode(XmlDocument xd, string name, string value) + { + if (xd == null) { - var m = - Regex.Matches(tag, "(?\\S*)=\"(?[^\"]*)\"", - RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - // fix for issue 14862: return lowercase attributes for case insensitive matching - var d = m.Cast().ToDictionary(attributeSet => attributeSet.Groups["attributeName"].Value.ToString().ToLower(), attributeSet => attributeSet.Groups["attributeValue"].Value.ToString()); - return d; + throw new ArgumentNullException(nameof(xd)); } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(name)); + } + + XmlNode temp = xd.CreateNode(XmlNodeType.Element, name, ""); + temp.AppendChild(xd.CreateCDataSection(value)); + return temp; + } + + /// + /// Sets or Creates a cdata XmlNode with the specified name and value + /// + /// The xmldocument. + /// The node to set or create the child text node on + /// The node name. + /// The node value. + /// a XmlNode + public static XmlNode SetCDataNode(XmlDocument xd, XmlNode parent, string name, string value) + { + if (xd == null) + { + throw new ArgumentNullException(nameof(xd)); + } + + if (parent == null) + { + throw new ArgumentNullException(nameof(parent)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + nameof(name)); + } + + XmlNode child = parent.SelectSingleNode(name); + if (child != null) + { + child.InnerXml = ""; + return child; + } + + return AddCDataNode(xd, name, value); + } + + /// + /// Gets the value of a XmlNode + /// + /// The XmlNode. + /// the value as a string + public static string GetNodeValue(XmlNode n) + { + var value = string.Empty; + if (n == null || n.FirstChild == null) + { + return value; + } + + value = n.FirstChild.Value ?? n.InnerXml; + return value.Replace("", "", "]]>"); + } + + /// + /// Determines whether the specified string appears to be XML. + /// + /// The XML string. + /// + /// true if the specified string appears to be XML; otherwise, false. + /// + public static bool CouldItBeXml(string? xml) + { + if (string.IsNullOrEmpty(xml)) + { + return false; + } + + xml = xml.Trim(); + return xml.StartsWith("<") && xml.EndsWith(">") && xml.Contains('/'); + } + + /// + /// Return a dictionary of attributes found for a string based tag + /// + /// + /// + public static Dictionary GetAttributesFromElement(string tag) + { + MatchCollection m = + Regex.Matches(tag, "(?\\S*)=\"(?[^\"]*)\"", + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + // fix for issue 14862: return lowercase attributes for case insensitive matching + var d = m.ToDictionary(attributeSet => attributeSet.Groups["attributeName"].Value.ToString().ToLower(), + attributeSet => attributeSet.Groups["attributeValue"].Value.ToString()); + return d; } } diff --git a/src/Umbraco.Core/Xml/XmlNamespaces.cs b/src/Umbraco.Core/Xml/XmlNamespaces.cs index 1721de253f11..a1fd06952184 100644 --- a/src/Umbraco.Core/Xml/XmlNamespaces.cs +++ b/src/Umbraco.Core/Xml/XmlNamespaces.cs @@ -1,41 +1,40 @@ // source: mvpxml.codeplex.com -namespace Umbraco.Cms.Core.Xml +namespace Umbraco.Cms.Core.Xml; + +/// +/// Provides public constants for wellknown XML namespaces. +/// +/// Author: Daniel Cazzulino, blog +public static class XmlNamespaces { /// - /// Provides public constants for wellknown XML namespaces. + /// The public XML 1.0 namespace. /// - /// Author: Daniel Cazzulino, blog - public static class XmlNamespaces - { - /// - /// The public XML 1.0 namespace. - /// - /// See http://www.w3.org/TR/2004/REC-xml-20040204/ - public const string Xml = "http://www.w3.org/XML/1998/namespace"; + /// See http://www.w3.org/TR/2004/REC-xml-20040204/ + public const string Xml = "http://www.w3.org/XML/1998/namespace"; - /// - /// Public Xml Namespaces specification namespace. - /// - /// See http://www.w3.org/TR/REC-xml-names/ - public const string XmlNs = "http://www.w3.org/2000/xmlns/"; + /// + /// Public Xml Namespaces specification namespace. + /// + /// See http://www.w3.org/TR/REC-xml-names/ + public const string XmlNs = "http://www.w3.org/2000/xmlns/"; - /// - /// Public Xml Namespaces prefix. - /// - /// See http://www.w3.org/TR/REC-xml-names/ - public const string XmlNsPrefix = "xmlns"; + /// + /// Public Xml Namespaces prefix. + /// + /// See http://www.w3.org/TR/REC-xml-names/ + public const string XmlNsPrefix = "xmlns"; - /// - /// XML Schema instance namespace. - /// - /// See http://www.w3.org/TR/xmlschema-1/ - public const string Xsi = "http://www.w3.org/2001/XMLSchema-instance"; + /// + /// XML Schema instance namespace. + /// + /// See http://www.w3.org/TR/xmlschema-1/ + public const string Xsi = "http://www.w3.org/2001/XMLSchema-instance"; - /// - /// XML 1.0 Schema namespace. - /// - /// See http://www.w3.org/TR/xmlschema-1/ - public const string Xsd = "http://www.w3.org/2001/XMLSchema"; - } + /// + /// XML 1.0 Schema namespace. + /// + /// See http://www.w3.org/TR/xmlschema-1/ + public const string Xsd = "http://www.w3.org/2001/XMLSchema"; } diff --git a/src/Umbraco.Core/Xml/XmlNodeListFactory.cs b/src/Umbraco.Core/Xml/XmlNodeListFactory.cs index 29797fc59a87..9d3fca8c24a9 100644 --- a/src/Umbraco.Core/Xml/XmlNodeListFactory.cs +++ b/src/Umbraco.Core/Xml/XmlNodeListFactory.cs @@ -1,178 +1,169 @@ -using System; -using System.Collections.Generic; +using System.Collections; using System.Xml; using System.Xml.XPath; // source: mvpxml.codeplex.com -namespace Umbraco.Cms.Core.Xml +namespace Umbraco.Cms.Core.Xml; + +public class XmlNodeListFactory { - public class XmlNodeListFactory + private XmlNodeListFactory() { } + + #region Public members + + /// + /// Creates an instance of a that allows + /// enumerating elements in the iterator. + /// + /// + /// The result of a previous node selection + /// through an query. + /// + /// An initialized list ready to be enumerated. + /// + /// The underlying XML store used to issue the query must be + /// an object inheriting , such as + /// . + /// + public static XmlNodeList CreateNodeList(XPathNodeIterator? iterator) => new XmlNodeListIterator(iterator); + + #endregion Public members + + #region XmlNodeListIterator + + private class XmlNodeListIterator : XmlNodeList { - private XmlNodeListFactory() { } + private readonly XPathNodeIterator? _iterator; + private readonly IList _nodes = new List(); - #region Public members + public XmlNodeListIterator(XPathNodeIterator? iterator) => _iterator = iterator?.Clone(); - /// - /// Creates an instance of a that allows - /// enumerating elements in the iterator. - /// - /// The result of a previous node selection - /// through an query. - /// An initialized list ready to be enumerated. - /// The underlying XML store used to issue the query must be - /// an object inheriting , such as - /// . - public static XmlNodeList CreateNodeList(XPathNodeIterator? iterator) + public override int Count { - return new XmlNodeListIterator(iterator); + get + { + if (!Done) + { + ReadToEnd(); + } + + return _nodes.Count; + } } - #endregion Public members + /// + /// Flags that the iterator has been consumed. + /// + private bool Done { get; set; } - #region XmlNodeListIterator + /// + /// Current count of nodes in the iterator (read so far). + /// + private int CurrentPosition => _nodes.Count; - private class XmlNodeListIterator : XmlNodeList - { - readonly XPathNodeIterator? _iterator; - readonly IList _nodes = new List(); + public override IEnumerator GetEnumerator() => new XmlNodeListEnumerator(this); - public XmlNodeListIterator(XPathNodeIterator? iterator) + public override XmlNode? Item(int index) + { + if (index >= _nodes.Count) { - _iterator = iterator?.Clone(); + ReadTo(index); } - public override System.Collections.IEnumerator GetEnumerator() + // Compatible behavior with .NET + if (index >= _nodes.Count || index < 0) { - return new XmlNodeListEnumerator(this); + return null; } - public override XmlNode? Item(int index) - { + return _nodes[index]; + } - if (index >= _nodes.Count) - ReadTo(index); - // Compatible behavior with .NET - if (index >= _nodes.Count || index < 0) - return null; - return _nodes[index]; - } - public override int Count + /// + /// Reads the entire iterator. + /// + private void ReadToEnd() + { + while (_iterator is not null && _iterator.MoveNext()) { - get + var node = _iterator.Current as IHasXmlNode; + // Check IHasXmlNode interface. + if (node == null) { - if (!_done) ReadToEnd(); - return _nodes.Count; + throw new ArgumentException("IHasXmlNode is missing."); } + + _nodes.Add(node.GetNode()); } + Done = true; + } - /// - /// Reads the entire iterator. - /// - private void ReadToEnd() + /// + /// Reads up to the specified index, or until the + /// iterator is consumed. + /// + private void ReadTo(int to) + { + while (_nodes.Count <= to) { - while (_iterator is not null && _iterator.MoveNext()) + if (_iterator is not null && _iterator.MoveNext()) { var node = _iterator.Current as IHasXmlNode; // Check IHasXmlNode interface. if (node == null) + { throw new ArgumentException("IHasXmlNode is missing."); + } + _nodes.Add(node.GetNode()); } - _done = true; - } - - /// - /// Reads up to the specified index, or until the - /// iterator is consumed. - /// - private void ReadTo(int to) - { - while (_nodes.Count <= to) + else { - if (_iterator is not null && _iterator.MoveNext()) - { - var node = _iterator.Current as IHasXmlNode; - // Check IHasXmlNode interface. - if (node == null) - throw new ArgumentException("IHasXmlNode is missing."); - _nodes.Add(node.GetNode()); - } - else - { - _done = true; - return; - } + Done = true; + return; } } + } - /// - /// Flags that the iterator has been consumed. - /// - private bool Done - { - get { return _done; } - } - - bool _done; - - /// - /// Current count of nodes in the iterator (read so far). - /// - private int CurrentPosition - { - get { return _nodes.Count; } - } - - #region XmlNodeListEnumerator - - private class XmlNodeListEnumerator : System.Collections.IEnumerator - { - readonly XmlNodeListIterator _iterator; - int _position = -1; - - public XmlNodeListEnumerator(XmlNodeListIterator iterator) - { - _iterator = iterator; - } + #region XmlNodeListEnumerator - #region IEnumerator Members + private class XmlNodeListEnumerator : IEnumerator + { + private readonly XmlNodeListIterator _iterator; + private int _position = -1; - void System.Collections.IEnumerator.Reset() - { - _position = -1; - } + public XmlNodeListEnumerator(XmlNodeListIterator iterator) => _iterator = iterator; + #region IEnumerator Members - bool System.Collections.IEnumerator.MoveNext() - { - _position++; - _iterator.ReadTo(_position); + void IEnumerator.Reset() => _position = -1; - // If we reached the end and our index is still - // bigger, there are no more items. - if (_iterator.Done && _position >= _iterator.CurrentPosition) - return false; - return true; - } + bool IEnumerator.MoveNext() + { + _position++; + _iterator.ReadTo(_position); - object? System.Collections.IEnumerator.Current + // If we reached the end and our index is still + // bigger, there are no more items. + if (_iterator.Done && _position >= _iterator.CurrentPosition) { - get - { - return _iterator[_position]; - } + return false; } - #endregion + return true; } - #endregion XmlNodeListEnumerator + object? IEnumerator.Current => _iterator[_position]; + + #endregion } - #endregion XmlNodeListIterator + #endregion XmlNodeListEnumerator } + + #endregion XmlNodeListIterator } From 869c48b190966dda7855c97879fe0f0ae9a68e5e Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle Date: Tue, 17 May 2022 15:07:01 +0200 Subject: [PATCH 002/117] Start manual cleanup after dotnet format --- src/Umbraco.Core/Actions/ActionBrowse.cs | 2 +- .../Actions/ActionCollectionBuilder.cs | 2 +- src/Umbraco.Core/Actions/ActionCopy.cs | 2 +- .../ActionCreateBlueprintFromContent.cs | 2 +- src/Umbraco.Core/Actions/ActionDelete.cs | 10 +- src/Umbraco.Core/Actions/ActionMove.cs | 2 +- src/Umbraco.Core/Actions/ActionNew.cs | 2 +- src/Umbraco.Core/Actions/ActionNotify.cs | 2 +- src/Umbraco.Core/Actions/ActionProtect.cs | 2 +- src/Umbraco.Core/Actions/ActionPublish.cs | 2 +- src/Umbraco.Core/Actions/ActionRestore.cs | 2 +- src/Umbraco.Core/Actions/ActionRights.cs | 2 +- src/Umbraco.Core/Actions/ActionRollback.cs | 2 +- src/Umbraco.Core/Actions/ActionSort.cs | 2 +- src/Umbraco.Core/Actions/ActionToPublish.cs | 2 +- src/Umbraco.Core/Actions/ActionUnpublish.cs | 2 +- src/Umbraco.Core/Actions/ActionUpdate.cs | 2 +- src/Umbraco.Core/Actions/IAction.cs | 2 +- src/Umbraco.Core/Attempt.cs | 3 +- src/Umbraco.Core/AttemptOfTResult.cs | 22 +- src/Umbraco.Core/AttemptOfTResultTStatus.cs | 32 +- src/Umbraco.Core/Cache/AppCacheExtensions.cs | 12 +- src/Umbraco.Core/Cache/AppCaches.cs | 17 +- .../Cache/AppPolicedCacheDictionary.cs | 31 +- .../Cache/ApplicationCacheRefresher.cs | 15 +- src/Umbraco.Core/Cache/CacheKeys.cs | 2 +- src/Umbraco.Core/Cache/CacheRefresherBase.cs | 10 +- .../Cache/CacheRefresherCollection.cs | 3 +- .../Cache/CacheRefresherCollectionBuilder.cs | 2 +- .../Cache/DataTypeCacheRefresher.cs | 7 +- src/Umbraco.Core/Cache/DeepCloneAppCache.cs | 62 +- src/Umbraco.Core/Cache/DictionaryAppCache.cs | 4 +- .../Cache/DictionaryCacheRefresher.cs | 15 +- src/Umbraco.Core/Cache/DistributedCache.cs | 32 +- .../Cache/FastDictionaryAppCache.cs | 20 +- .../Cache/FastDictionaryAppCacheBase.cs | 23 +- src/Umbraco.Core/Cache/IAppCache.cs | 2 +- src/Umbraco.Core/Cache/IAppPolicyCache.cs | 2 +- src/Umbraco.Core/Cache/ICacheRefresher.cs | 8 +- .../ICacheRefresherNotificationFactory.cs | 6 +- src/Umbraco.Core/Cache/IJsonCacheRefresher.cs | 2 +- .../Cache/IPayloadCacheRefresher.cs | 2 +- .../Cache/IRepositoryCachePolicy.cs | 2 +- src/Umbraco.Core/Cache/IRequestCache.cs | 1 + src/Umbraco.Core/Cache/IValueEditorCache.cs | 3 +- src/Umbraco.Core/Cache/IsolatedCaches.cs | 2 +- .../Cache/JsonCacheRefresherBase.cs | 3 - src/Umbraco.Core/Cache/MacroCacheRefresher.cs | 4 +- src/Umbraco.Core/Cache/MediaCacheRefresher.cs | 15 +- .../CodeAnnotations/FriendlyNameAttribute.cs | 2 +- .../Collections/CompositeIntStringKey.cs | 16 +- .../Collections/CompositeNStringNStringKey.cs | 14 +- .../Collections/CompositeStringStringKey.cs | 14 +- .../Collections/CompositeTypeTypeKey.cs | 14 +- .../Collections/ConcurrentHashSet.cs | 95 ++- .../Collections/DeepCloneableList.cs | 15 +- .../EventClearingObservableCollection.cs | 8 +- .../Collections/ListCloneBehavior.cs | 4 +- .../Composing/CollectionBuilderBase.cs | 116 ++-- .../Composing/ComponentCollection.cs | 21 +- .../Composing/ComponentCollectionBuilder.cs | 8 +- .../Composing/ComponentComposer.cs | 2 +- .../Composing/ComposeAfterAttribute.cs | 2 +- .../Composing/ComposeBeforeAttribute.cs | 2 +- src/Umbraco.Core/Composing/ComposerGraph.cs | 134 ++-- .../Composing/CompositionExtensions.cs | 6 +- .../DefaultUmbracoAssemblyProvider.cs | 8 +- .../Composing/DisableAttribute.cs | 2 +- .../Composing/DisableComposerAttribute.cs | 2 +- src/Umbraco.Core/Composing/EnableAttribute.cs | 2 +- .../Composing/EnableComposerAttribute.cs | 2 +- .../Composing/HideFromTypeFinderAttribute.cs | 2 +- .../Composing/IAssemblyProvider.cs | 2 +- .../Composing/IBuilderCollection.cs | 2 +- .../Composing/ICollectionBuilder.cs | 2 +- src/Umbraco.Core/Composing/IComponent.cs | 2 +- src/Umbraco.Core/Composing/IComposer.cs | 2 +- src/Umbraco.Core/Composing/IDiscoverable.cs | 2 +- src/Umbraco.Core/Composing/IRuntimeHash.cs | 2 +- src/Umbraco.Core/Composing/LazyResolve.cs | 2 +- .../ContentSettingsExtensions.cs | 2 +- .../HealthCheckSettingsExtensions.cs | 5 +- .../Configuration/GlobalSettingsExtensions.cs | 10 +- .../Configuration/Grid/GridConfig.cs | 14 +- .../Configuration/Grid/GridEditorsConfig.cs | 26 +- .../Configuration/Grid/IGridConfig.cs | 2 +- .../Configuration/Grid/IGridEditorConfig.cs | 8 +- .../Configuration/Grid/IGridEditorsConfig.cs | 2 +- .../Configuration/IConfigManipulator.cs | 6 +- .../Configuration/ICronTabParser.cs | 2 +- .../IMemberPasswordConfiguration.cs | 2 +- .../Configuration/ITypeFinderSettings.cs | 2 +- .../IUmbracoConfigurationSection.cs | 2 +- .../IUserPasswordConfiguration.cs | 2 +- .../Configuration/LocalTempStorage.cs | 2 +- .../Configuration/Models/BasicAuthSettings.cs | 1 - .../Models/ContentImagingSettings.cs | 8 +- .../HealthChecksNotificationSettings.cs | 2 +- .../Models/InstallDefaultDataSettings.cs | 2 +- .../Models/LuceneDirectoryFactory.cs | 2 +- .../Validation/ContentSettingsValidator.cs | 3 +- .../IImagingAutoFillUploadField.cs | 2 +- .../IPasswordConfigurationSection.cs | 2 +- .../UmbracoSettings/ITypeFinderConfig.cs | 2 +- src/Umbraco.Core/Constants-Applications.cs | 2 +- src/Umbraco.Core/Constants-Audit.cs | 2 +- src/Umbraco.Core/Constants-CharArrays.cs | 53 +- src/Umbraco.Core/Constants-Composing.cs | 4 +- src/Umbraco.Core/Constants-Conventions.cs | 5 +- src/Umbraco.Core/Constants-DataTypes.cs | 33 +- src/Umbraco.Core/Constants-DeploySelector.cs | 2 +- src/Umbraco.Core/Constants-ModelsBuilder.cs | 2 +- src/Umbraco.Core/Constants-ObjectTypes.cs | 3 +- .../Constants-PackageRepository.cs | 2 +- .../Constants-PropertyTypeGroups.cs | 2 +- src/Umbraco.Core/Constants-Security.cs | 1 - src/Umbraco.Core/Constants-Sql.cs | 2 +- src/Umbraco.Core/Constants-SqlTemplates.cs | 2 +- src/Umbraco.Core/Constants-System.cs | 2 +- .../Constants-SystemDirectories.cs | 3 +- src/Umbraco.Core/Constants-Telemetry.cs | 2 +- src/Umbraco.Core/Constants-UdiEntityType.cs | 7 +- .../ContentAppFactoryCollection.cs | 26 +- .../ContentEditorContentAppFactory.cs | 20 +- .../ContentInfoContentAppFactory.cs | 8 +- .../ContentTypeDesignContentAppFactory.cs | 2 +- .../ContentTypeListViewContentAppFactory.cs | 2 +- ...ContentTypePermissionsContentAppFactory.cs | 2 +- .../ContentTypeTemplatesContentAppFactory.cs | 2 +- .../DictionaryContentAppFactory.cs | 2 +- .../ContentApps/ListViewContentAppFactory.cs | 95 +-- src/Umbraco.Core/ConventionsHelper.cs | 8 +- .../CustomBooleanTypeConverter.cs | 3 +- src/Umbraco.Core/Dashboards/AccessRule.cs | 2 +- src/Umbraco.Core/Dashboards/AccessRuleType.cs | 4 +- .../Dashboards/AnalyticsDashboard.cs | 4 +- .../Dashboards/ContentDashboard.cs | 2 +- .../Dashboards/DashboardCollection.cs | 3 +- .../Dashboards/DashboardCollectionBuilder.cs | 3 +- src/Umbraco.Core/Dashboards/DashboardSlim.cs | 2 +- .../Dashboards/ExamineDashboard.cs | 4 +- src/Umbraco.Core/Dashboards/FormsDashboard.cs | 4 +- .../Dashboards/HealthCheckDashboard.cs | 4 +- src/Umbraco.Core/Dashboards/IAccessRule.cs | 2 +- src/Umbraco.Core/Dashboards/IDashboard.cs | 3 +- src/Umbraco.Core/Dashboards/IDashboardSlim.cs | 2 +- src/Umbraco.Core/Dashboards/MediaDashboard.cs | 4 +- .../DefaultEventMessagesFactory.cs | 11 +- src/Umbraco.Core/DelegateEqualityComparer.cs | 2 +- .../DependencyInjection/IUmbracoBuilder.cs | 8 +- src/Umbraco.Core/Deploy/ArtifactBase.cs | 18 +- src/Umbraco.Core/Deploy/ArtifactDependency.cs | 2 +- .../Deploy/ArtifactDependencyCollection.cs | 6 +- .../Deploy/ArtifactDependencyMode.cs | 4 +- .../Deploy/ArtifactDeployState.cs | 2 +- .../ArtifactDeployStateOfTArtifactTEntity.cs | 2 +- src/Umbraco.Core/Deploy/ArtifactSignature.cs | 2 +- src/Umbraco.Core/Deploy/Difference.cs | 4 +- src/Umbraco.Core/Deploy/Direction.cs | 4 +- src/Umbraco.Core/Deploy/IArtifact.cs | 3 +- src/Umbraco.Core/Deploy/IArtifactSignature.cs | 2 +- src/Umbraco.Core/Deploy/IDeployContext.cs | 7 +- src/Umbraco.Core/Deploy/IFileSource.cs | 4 +- src/Umbraco.Core/Deploy/IFileType.cs | 3 +- .../Deploy/IFileTypeCollection.cs | 2 +- src/Umbraco.Core/Deploy/IImageSourceParser.cs | 2 +- src/Umbraco.Core/Deploy/ILocalLinkParser.cs | 2 +- src/Umbraco.Core/Deploy/IMacroParser.cs | 2 +- src/Umbraco.Core/Deploy/IServiceConnector.cs | 2 +- .../IUniqueIdentifyingServiceConnector.cs | 2 +- src/Umbraco.Core/Deploy/IValueConnector.cs | 5 +- .../Dictionary/ICultureDictionary.cs | 12 +- .../Dictionary/ICultureDictionaryFactory.cs | 2 +- src/Umbraco.Core/Direction.cs | 4 +- .../DistributedLocking/DistributedLockType.cs | 2 +- .../Editors/BackOfficePreviewModel.cs | 4 +- .../Editors/EditorValidatorCollection.cs | 3 +- .../EditorValidatorCollectionBuilder.cs | 2 +- .../Editors/EditorValidatorOfT.cs | 2 +- src/Umbraco.Core/Editors/IEditorValidator.cs | 3 +- src/Umbraco.Core/Enum.cs | 5 +- .../CancellableEnumerableObjectEventArgs.cs | 8 +- .../Events/CancellableEventArgs.cs | 27 +- .../Events/CancellableObjectEventArgs.cs | 5 +- ...ancellableObjectEventArgsOfTEventObject.cs | 19 +- .../Events/ContentCacheEventArgs.cs | 2 +- src/Umbraco.Core/Events/CopyEventArgs.cs | 6 +- src/Umbraco.Core/Events/DeleteEventArgs.cs | 34 +- .../Events/EventAggregator.Notifications.cs | 8 +- src/Umbraco.Core/Events/EventAggregator.cs | 48 +- src/Umbraco.Core/Events/EventDefinition.cs | 23 +- .../Events/EventDefinitionBase.cs | 12 +- .../Events/EventDefinitionFilter.cs | 4 +- src/Umbraco.Core/Events/EventExtensions.cs | 8 +- src/Umbraco.Core/Events/EventMessage.cs | 4 +- src/Umbraco.Core/Events/EventMessageType.cs | 4 +- src/Umbraco.Core/Events/EventMessages.cs | 2 +- src/Umbraco.Core/Events/EventNameExtractor.cs | 18 +- .../Events/EventNameExtractorError.cs | 4 +- .../Events/EventNameExtractorResult.cs | 3 +- .../Events/ExportedMemberEventArgs.cs | 3 +- .../Events/IDeletingMediaFilesEventArgs.cs | 2 +- src/Umbraco.Core/Events/IEventDefinition.cs | 4 +- src/Umbraco.Core/Events/IEventDispatcher.cs | 8 +- .../Events/IEventMessagesAccessor.cs | 2 +- .../Events/IEventMessagesFactory.cs | 2 +- .../Events/INotificationAsyncHandler.cs | 2 +- .../Events/MacroErrorEventArgs.cs | 2 +- .../Exceptions/AuthorizationException.cs | 2 +- .../Exceptions/BootFailedException.cs | 5 +- .../Exceptions/ConfigurationException.cs | 2 +- .../Exceptions/DataOperationException.cs | 2 +- .../Exceptions/InvalidCompositionException.cs | 70 ++- src/Umbraco.Core/ExpressionHelper.cs | 44 +- .../Extensions/AssemblyExtensions.cs | 11 +- .../Extensions/ClaimsIdentityExtensions.cs | 22 +- .../Extensions/ContentExtensions.cs | 125 ++-- .../Extensions/ContentVariationExtensions.cs | 8 +- .../Extensions/CoreCacheHelperExtensions.cs | 2 +- .../Extensions/DataTableExtensions.cs | 22 +- .../Extensions/DateTimeExtensions.cs | 4 +- .../Extensions/DecimalExtensions.cs | 2 +- .../Extensions/DelegateExtensions.cs | 28 +- .../Extensions/DictionaryExtensions.cs | 38 +- src/Umbraco.Core/Extensions/EnumExtensions.cs | 2 +- .../Extensions/EnumerableExtensions.cs | 53 +- .../Extensions/ExpressionExtensions.cs | 2 +- .../Extensions/HostEnvironmentExtensions.cs | 8 +- src/Umbraco.Core/Extensions/IfExtensions.cs | 8 +- src/Umbraco.Core/Extensions/IntExtensions.cs | 2 +- .../Extensions/KeyValuePairExtensions.cs | 2 +- src/Umbraco.Core/GuidUdi.cs | 8 +- src/Umbraco.Core/GuidUtils.cs | 18 +- .../Handlers/AuditNotificationsHandler.cs | 41 +- src/Umbraco.Core/HashCodeCombiner.cs | 12 +- src/Umbraco.Core/HashCodeHelper.cs | 5 +- src/Umbraco.Core/HashGenerator.cs | 33 +- .../HealthChecks/AcceptableConfiguration.cs | 3 +- .../Checks/AbstractSettingsCheck.cs | 26 +- .../Checks/Configuration/MacroErrorsCheck.cs | 13 +- .../Checks/Data/DatabaseIntegrityCheck.cs | 79 +-- .../LiveEnvironment/CompilationDebugCheck.cs | 7 +- .../FolderAndFilePermissionsCheck.cs | 17 +- .../Checks/Security/BaseHttpHeaderCheck.cs | 29 +- .../Checks/Security/ExcessiveHeadersCheck.cs | 18 +- .../Checks/Security/HttpsCheck.cs | 49 +- .../ConfigurationServiceResult.cs | 3 +- src/Umbraco.Core/HealthChecks/HealthCheck.cs | 14 +- .../HealthChecks/HealthCheckAction.cs | 7 +- .../HealthChecks/HealthCheckAttribute.cs | 3 +- .../HealthChecks/HealthCheckCollection.cs | 3 +- .../HealthChecks/HealthCheckGroup.cs | 8 +- .../HealthCheckNotificationMethodAttribute.cs | 2 +- ...HealthCheckNotificationMethodCollection.cs | 4 +- ...heckNotificationMethodCollectionBuilder.cs | 2 +- .../HealthCheckNotificationVerbosity.cs | 4 +- .../HealthChecks/HealthCheckResults.cs | 33 +- .../HealthChecks/HealthCheckStatus.cs | 2 +- .../HeathCheckCollectionBuilder.cs | 2 +- .../EmailNotificationMethod.cs | 9 +- .../IHealthCheckNotificationMethod.cs | 2 +- .../IMarkdownToHtmlConverter.cs | 2 +- src/Umbraco.Core/HexEncoder.cs | 4 +- .../Hosting/IApplicationShutdownRegistry.cs | 1 + src/Umbraco.Core/HybridAccessorBase.cs | 44 +- src/Umbraco.Core/ICompletable.cs | 2 +- src/Umbraco.Core/IO/CleanFolderResult.cs | 8 +- .../IO/CleanFolderResultStatus.cs | 4 +- src/Umbraco.Core/IO/FileSystemExtensions.cs | 8 +- src/Umbraco.Core/IO/FileSystems.cs | 162 +++-- src/Umbraco.Core/IO/IFileSystem.cs | 6 +- src/Umbraco.Core/IO/IOHelper.cs | 15 +- src/Umbraco.Core/IO/IOHelperLinux.cs | 8 +- src/Umbraco.Core/IO/IOHelperOSX.cs | 8 +- src/Umbraco.Core/IO/IOHelperWindows.cs | 10 +- src/Umbraco.Core/IO/IViewHelper.cs | 4 + src/Umbraco.Core/IO/MediaFileManager.cs | 15 +- .../CombinedGuidsMediaPathScheme.cs | 1 - .../Install/FilePermissionTest.cs | 4 +- src/Umbraco.Core/Install/InstallException.cs | 2 +- .../Install/InstallStatusTracker.cs | 40 +- .../InstallSteps/FilePermissionsStep.cs | 4 +- .../Install/Models/DatabaseModel.cs | 18 +- .../Install/Models/InstallInstructions.cs | 8 +- .../Models/InstallProgressResultModel.cs | 14 +- .../Install/Models/InstallSetup.cs | 8 +- .../Install/Models/InstallSetupResult.cs | 2 +- .../Install/Models/InstallSetupStep.cs | 19 +- .../Models/InstallSetupStepAttribute.cs | 16 +- .../Install/Models/InstallTrackingItem.cs | 9 +- .../Install/Models/InstallationType.cs | 4 +- src/Umbraco.Core/InstallLog.cs | 26 +- src/Umbraco.Core/LambdaExpressionCacheKey.cs | 26 +- src/Umbraco.Core/Logging/DisposableTimer.cs | 29 +- src/Umbraco.Core/Logging/IProfiler.cs | 2 +- src/Umbraco.Core/Logging/IProfilerHtml.cs | 2 +- .../Logging/LogHttpRequestExtension.cs | 2 +- src/Umbraco.Core/Logging/LogLevel.cs | 4 +- src/Umbraco.Core/Logging/LogProfiler.cs | 9 +- .../Logging/LoggingConfiguration.cs | 2 +- .../Logging/LoggingTaskExtension.cs | 7 +- src/Umbraco.Core/Macros/IMacroRenderer.cs | 5 +- src/Umbraco.Core/Macros/MacroContent.cs | 8 +- .../Macros/MacroErrorBehaviour.cs | 4 +- src/Umbraco.Core/Macros/MacroModel.cs | 4 +- src/Umbraco.Core/Macros/MacroPropertyModel.cs | 2 +- src/Umbraco.Core/Mail/ISmsSender.cs | 1 - src/Umbraco.Core/Manifest/BundleOptions.cs | 2 +- src/Umbraco.Core/Manifest/IManifestFilter.cs | 2 +- src/Umbraco.Core/Manifest/ManifestAssets.cs | 3 +- .../Manifest/ManifestContentAppDefinition.cs | 5 +- .../Manifest/ManifestContentAppFactory.cs | 26 +- .../Manifest/ManifestDashboard.cs | 11 +- .../Manifest/ManifestFilterCollection.cs | 3 +- .../ManifestFilterCollectionBuilder.cs | 2 +- src/Umbraco.Core/Manifest/ManifestSection.cs | 8 +- src/Umbraco.Core/Mapping/IMapDefinition.cs | 4 +- src/Umbraco.Core/Mapping/IUmbracoMapper.cs | 11 +- .../Mapping/MapDefinitionCollection.cs | 3 +- .../Mapping/MapDefinitionCollectionBuilder.cs | 2 +- src/Umbraco.Core/Mapping/MapperContext.cs | 4 +- .../Media/EmbedProviders/DailyMotion.cs | 13 +- .../EmbedProvidersCollection.cs | 3 +- .../EmbedProvidersCollectionBuilder.cs | 2 +- .../Media/EmbedProviders/Flickr.cs | 12 +- .../Media/EmbedProviders/GettyImages.cs | 13 +- .../Media/EmbedProviders/Giphy.cs | 9 +- src/Umbraco.Core/Media/EmbedProviders/Hulu.cs | 9 +- .../Media/EmbedProviders/Issuu.cs | 13 +- .../Media/EmbedProviders/Kickstarter.cs | 9 +- .../Media/EmbedProviders/LottieFiles.cs | 14 +- .../Media/Exif/ExifBitConverter.cs | 22 +- src/Umbraco.Core/Media/Exif/ExifEnums.cs | 68 +-- src/Umbraco.Core/Media/Exif/ExifExceptions.cs | 2 +- .../Media/Exif/ExifExtendedProperty.cs | 126 ++-- .../Media/Exif/ExifFileTypeDescriptor.cs | 4 +- .../Media/Exif/ExifInterOperability.cs | 18 +- src/Umbraco.Core/Media/Exif/ExifProperty.cs | 273 +++++---- .../Media/Exif/ExifPropertyFactory.cs | 324 ++++++---- src/Umbraco.Core/Media/Exif/ExifTag.cs | 5 +- src/Umbraco.Core/Media/Exif/ExifTagFactory.cs | 2 +- src/Umbraco.Core/Media/Exif/IFD.cs | 4 +- .../Media/Exif/ImageFileDirectory.cs | 2 +- .../Media/Exif/ImageFileDirectoryEntry.cs | 23 +- .../Media/Exif/ImageFileFormat.cs | 4 +- src/Umbraco.Core/Media/Exif/JFIFEnums.cs | 6 +- .../Media/Exif/JFIFExtendedProperty.cs | 20 +- src/Umbraco.Core/Media/Exif/JPEGExceptions.cs | 46 +- src/Umbraco.Core/Media/Exif/JPEGMarker.cs | 4 +- src/Umbraco.Core/Media/IEmbedProvider.cs | 2 +- .../Media/TypeDetector/JpegDetector.cs | 2 +- src/Umbraco.Core/Models/AuditEntry.cs | 2 +- src/Umbraco.Core/Models/AuditItem.cs | 2 +- src/Umbraco.Core/Models/AuditType.cs | 4 +- src/Umbraco.Core/Models/BackOfficeTour.cs | 29 +- src/Umbraco.Core/Models/BackOfficeTourFile.cs | 5 +- src/Umbraco.Core/Models/BackOfficeTourStep.cs | 26 +- .../Models/Blocks/BlockListItem.cs | 2 +- .../Models/Blocks/BlockListModel.cs | 16 +- .../Blocks/ContentAndSettingsReference.cs | 14 +- .../Models/Blocks/IBlockReference.cs | 2 +- src/Umbraco.Core/Models/Consent.cs | 3 +- src/Umbraco.Core/Models/ConsentExtensions.cs | 2 +- src/Umbraco.Core/Models/ConsentState.cs | 8 +- src/Umbraco.Core/Models/Content.cs | 62 +- src/Umbraco.Core/Models/ContentBase.cs | 85 +-- .../Models/ContentBaseExtensions.cs | 8 +- .../Models/ContentCultureInfos.cs | 5 +- .../Models/ContentCultureInfosCollection.cs | 7 +- .../Models/ContentDataIntegrityReport.cs | 10 +- .../Models/ContentDataIntegrityReportEntry.cs | 3 +- .../ContentDataIntegrityReportOptions.cs | 2 +- .../AssignedContentPermissions.cs | 2 +- .../AssignedUserGroupPermissions.cs | 2 +- .../Models/ContentEditing/AuditLog.cs | 29 +- .../ContentEditing/BackOfficeNotification.cs | 11 +- .../Models/ContentEditing/CodeFileDisplay.cs | 6 +- .../Models/ContentEditing/ContentApp.cs | 2 +- .../Models/ContentEditing/ContentAppBadge.cs | 2 +- .../ContentEditing/ContentAppBadgeType.cs | 12 +- .../Models/ContentEditing/ContentBaseSave.cs | 10 +- .../ContentDomainsAndCulture.cs | 8 +- .../Models/ContentEditing/ContentItemBasic.cs | 30 +- .../ContentEditing/ContentItemDisplay.cs | 38 +- .../ContentEditing/ContentItemDisplayBase.cs | 2 +- .../Models/ContentEditing/ContentItemSave.cs | 10 +- .../ContentEditing/ContentPropertyBasic.cs | 5 +- .../ContentPropertyCollectionDto.cs | 2 +- .../ContentEditing/ContentPropertyDisplay.cs | 20 +- .../ContentEditing/ContentPropertyDto.cs | 2 +- .../ContentEditing/ContentRedirectUrl.cs | 20 +- .../ContentEditing/ContentSaveAction.cs | 4 +- .../ContentEditing/ContentSavedState.cs | 4 +- .../Models/ContentEditing/ContentSortOrder.cs | 2 +- .../Models/ContentEditing/ContentTypeBasic.cs | 12 +- .../ContentTypeCompositionDisplay.cs | 24 +- .../Models/ContentEditing/ContentTypeSave.cs | 36 +- .../ContentEditing/ContentVariantSave.cs | 11 +- .../ContentEditing/ContentVariationDisplay.cs | 24 +- .../CreatedDocumentTypeCollectionResult.cs | 8 +- .../Models/ContentEditing/DataTypeBasic.cs | 2 +- .../DataTypeConfigurationFieldDisplay.cs | 2 +- .../DataTypeConfigurationFieldSave.cs | 2 +- .../Models/ContentEditing/DataTypeDisplay.cs | 5 +- .../ContentEditing/DataTypeReferences.cs | 11 +- .../Models/ContentEditing/DataTypeSave.cs | 2 +- .../DictionaryOverviewDisplay.cs | 2 +- .../DictionaryOverviewTranslationDisplay.cs | 2 +- .../Models/ContentEditing/DictionarySave.cs | 2 +- .../DictionaryTranslationDisplay.cs | 2 +- .../ContentEditing/DocumentTypeDisplay.cs | 14 +- .../Models/ContentEditing/DocumentTypeSave.cs | 4 +- .../Models/ContentEditing/DomainDisplay.cs | 14 +- .../Models/ContentEditing/DomainSave.cs | 14 +- .../Models/ContentEditing/EditorNavigation.cs | 17 +- .../Models/ContentEditing/EntityBasic.cs | 5 +- .../ContentEditing/EntitySearchResults.cs | 4 +- .../GetAvailableCompositionsFilter.cs | 2 +- .../ContentEditing/IContentAppFactory.cs | 3 +- .../ContentEditing/IContentProperties.cs | 2 +- .../Models/ContentEditing/IContentSave.cs | 2 +- .../Models/ContentEditing/IErrorModel.cs | 2 +- .../ContentEditing/IHaveUploadedFiles.cs | 2 +- .../ContentEditing/INotificationModel.cs | 2 +- .../Models/ContentEditing/ITabbedContent.cs | 2 +- .../Models/ContentEditing/Language.cs | 14 +- .../Models/ContentEditing/LinkDisplay.cs | 26 +- .../ListViewAwareContentItemDisplayBase.cs | 2 +- .../Models/ContentEditing/MacroDisplay.cs | 2 +- .../Models/ContentEditing/MacroParameter.cs | 8 +- .../ContentEditing/MacroParameterDisplay.cs | 2 +- .../Models/ContentEditing/MediaItemDisplay.cs | 11 +- .../Models/ContentEditing/MediaItemSave.cs | 2 +- .../Models/ContentRepositoryExtensions.cs | 23 +- src/Umbraco.Core/Models/ContentSchedule.cs | 5 +- .../Models/ContentScheduleAction.cs | 4 +- .../Models/ContentScheduleCollection.cs | 39 +- src/Umbraco.Core/Models/ContentStatus.cs | 17 +- .../Models/ContentTagsExtensions.cs | 31 +- src/Umbraco.Core/Models/ContentType.cs | 11 +- .../ContentTypeAvailableCompositionsResult.cs | 3 +- ...ContentTypeAvailableCompositionsResults.cs | 6 +- src/Umbraco.Core/Models/ContentTypeBase.cs | 156 ++--- .../Models/ContentTypeBaseExtensions.cs | 6 +- .../Models/ContentTypeCompositionBase.cs | 48 +- .../Models/ContentTypeImportModel.cs | 14 +- src/Umbraco.Core/Models/ContentTypeSort.cs | 17 +- src/Umbraco.Core/Models/ContentVariation.cs | 4 +- src/Umbraco.Core/Models/ContentVersionMeta.cs | 13 +- src/Umbraco.Core/Models/CultureImpact.cs | 36 +- src/Umbraco.Core/Models/DataType.cs | 13 +- src/Umbraco.Core/Models/DataTypeExtensions.cs | 4 +- src/Umbraco.Core/Models/DeepCloneHelper.cs | 84 +-- src/Umbraco.Core/Models/DictionaryItem.cs | 14 +- .../Models/DictionaryItemExtensions.cs | 6 +- .../Models/DictionaryTranslation.cs | 19 +- .../Models/DoNotCloneAttribute.cs | 2 +- .../Models/Editors/ContentPropertyData.cs | 2 +- .../Models/Editors/ContentPropertyFile.cs | 2 +- src/Umbraco.Core/Models/Email/EmailMessage.cs | 14 +- .../Models/Entities/BeingDirty.cs | 2 +- .../Models/Entities/ContentEntitySlim.cs | 2 +- .../Models/Entities/DocumentEntitySlim.cs | 2 +- .../Models/Entities/EntityBase.cs | 29 +- .../Models/Entities/EntityExtensions.cs | 2 +- .../Models/Entities/EntitySlim.cs | 19 +- .../Models/Entities/ICanBeDirty.cs | 6 +- .../Models/Entities/IContentEntitySlim.cs | 2 +- .../Models/Entities/IDocumentEntitySlim.cs | 2 +- src/Umbraco.Core/Models/Entities/IEntity.cs | 2 +- .../Models/Entities/IEntitySlim.cs | 2 +- .../Models/Entities/IHaveAdditionalData.cs | 2 +- .../Models/Entities/IMediaEntitySlim.cs | 2 +- .../Models/Entities/IMemberEntitySlim.cs | 2 +- .../Models/Entities/IRememberBeingDirty.cs | 2 +- .../Models/Entities/ITreeEntity.cs | 2 +- .../Models/Entities/IUmbracoEntity.cs | 2 +- .../Models/Entities/IValueObject.cs | 2 +- .../Models/Entities/MediaEntitySlim.cs | 2 +- src/Umbraco.Core/Models/EntityContainer.cs | 11 +- src/Umbraco.Core/Models/File.cs | 39 +- src/Umbraco.Core/Models/Folder.cs | 2 +- .../Models/HaveAdditionalDataExtensions.cs | 5 +- src/Umbraco.Core/Models/IAuditEntry.cs | 2 +- src/Umbraco.Core/Models/IAuditItem.cs | 2 +- src/Umbraco.Core/Models/IConsent.cs | 2 +- src/Umbraco.Core/Models/IContentBase.cs | 5 +- .../Models/IContentTypeComposition.cs | 2 +- src/Umbraco.Core/Models/IDataType.cs | 2 +- src/Umbraco.Core/Models/IDataValueEditor.cs | 2 +- src/Umbraco.Core/Models/IDeepCloneable.cs | 2 +- src/Umbraco.Core/Models/IDictionaryItem.cs | 2 +- .../Models/IDictionaryTranslation.cs | 2 +- src/Umbraco.Core/Models/IDomain.cs | 5 +- src/Umbraco.Core/Models/IFile.cs | 2 +- src/Umbraco.Core/Models/IKeyValue.cs | 2 +- src/Umbraco.Core/Models/ILanguage.cs | 2 +- src/Umbraco.Core/Models/ILogViewerQuery.cs | 3 +- src/Umbraco.Core/Models/IMacro.cs | 2 +- src/Umbraco.Core/Models/IMacroProperty.cs | 8 +- src/Umbraco.Core/Models/IMedia.cs | 2 +- src/Umbraco.Core/Models/IMediaType.cs | 2 +- src/Umbraco.Core/Models/IMember.cs | 2 +- src/Umbraco.Core/Models/IMemberGroup.cs | 2 +- src/Umbraco.Core/Models/IMemberType.cs | 2 +- src/Umbraco.Core/Models/IMigrationEntry.cs | 3 +- src/Umbraco.Core/Models/IPartialView.cs | 2 +- src/Umbraco.Core/Models/IProperty.cs | 1 + .../Models/IPropertyCollection.cs | 4 +- src/Umbraco.Core/Models/IRedirectUrl.cs | 2 +- src/Umbraco.Core/Models/IRelation.cs | 8 +- src/Umbraco.Core/Models/IRelationType.cs | 2 +- src/Umbraco.Core/Models/IScript.cs | 2 +- src/Umbraco.Core/Models/ISimpleContentType.cs | 4 +- src/Umbraco.Core/Models/IStylesheet.cs | 2 +- .../Models/IStylesheetProperty.cs | 4 +- src/Umbraco.Core/Models/ITag.cs | 2 +- src/Umbraco.Core/Models/ITemplate.cs | 4 +- src/Umbraco.Core/Models/ITwoFactorLogin.cs | 4 +- src/Umbraco.Core/Models/IconModel.cs | 1 + src/Umbraco.Core/Models/ImageCropAnchor.cs | 4 +- src/Umbraco.Core/Models/ImageCropMode.cs | 2 +- .../Models/ImageUrlGenerationOptions.cs | 2 +- src/Umbraco.Core/Models/KeyValue.cs | 4 +- src/Umbraco.Core/Models/Language.cs | 2 +- src/Umbraco.Core/Models/Link.cs | 6 +- src/Umbraco.Core/Models/LinkType.cs | 4 +- src/Umbraco.Core/Models/LogViewerQuery.cs | 2 +- src/Umbraco.Core/Models/Macro.cs | 82 ++- src/Umbraco.Core/Models/MacroProperty.cs | 11 +- .../Models/MacroPropertyCollection.cs | 5 +- .../Models/Mapping/AuditMapDefinition.cs | 2 +- .../Models/Mapping/CommonMapper.cs | 15 +- .../Mapping/ContentPropertyBasicMapper.cs | 19 +- .../Mapping/ContentPropertyDisplayMapper.cs | 24 +- .../Mapping/ContentPropertyDtoMapper.cs | 23 +- .../Mapping/ContentPropertyMapDefinition.cs | 33 +- .../Models/Mapping/ContentSavedStateMapper.cs | 8 +- .../Mapping/ContentTypeMapDefinition.cs | 323 +++++----- .../Models/Mapping/ContentVariantMapper.cs | 18 +- .../Models/Mapping/DataTypeMapDefinition.cs | 33 +- .../Models/Mapping/DictionaryMapDefinition.cs | 45 +- .../Models/Mapping/LanguageMapDefinition.cs | 12 +- .../Models/Mapping/MacroMapDefinition.cs | 11 +- .../Models/Mapping/MapperContextExtensions.cs | 2 +- .../Models/Membership/ContentPermissionSet.cs | 10 +- .../Models/Membership/EntityPermission.cs | 3 +- .../Membership/EntityPermissionCollection.cs | 12 +- .../Models/Membership/EntityPermissionSet.cs | 3 +- .../Models/Membership/IMembershipUser.cs | 11 +- .../Models/Membership/IProfile.cs | 3 +- .../Models/Membership/IReadOnlyUserGroup.cs | 6 +- src/Umbraco.Core/Models/Membership/IUser.cs | 8 +- .../Models/Membership/IUserGroup.cs | 3 +- .../Models/Packaging/CompiledPackage.cs | 15 + .../Packaging/CompiledPackageContentBase.cs | 4 +- .../Models/Packaging/InstallWarnings.cs | 2 + .../Models/PublishedContent/Fallback.cs | 30 +- .../PublishedContent/IPublishedContent.cs | 20 +- .../IPublishedContentTypeFactory.cs | 16 +- .../PublishedContent/IPublishedElement.cs | 2 +- .../IPublishedModelFactory.cs | 2 +- .../PublishedContent/IPublishedProperty.cs | 2 +- .../IPublishedPropertyType.cs | 8 +- .../IPublishedValueFallback.cs | 36 +- .../IVariationContextAccessor.cs | 2 +- .../PublishedContent/IndexedArrayItem.cs | 3 +- .../Models/TemplateQuery/ContentTypeModel.cs | 2 +- .../Models/Trees/ActionMenuItem.cs | 12 +- .../Models/Trees/CreateChildEntity.cs | 2 +- src/Umbraco.Core/Models/Trees/ExportMember.cs | 5 +- .../ApplicationCacheRefresherNotification.cs | 6 +- .../AssignedMemberRolesNotification.cs | 3 +- ...ssignedUserGroupPermissionsNotification.cs | 4 +- .../CacheRefresherNotification.cs | 1 + .../CancelableEnumerableObjectNotification.cs | 7 +- .../CancelableObjectNotification.cs | 6 +- .../ContentCacheRefresherNotification.cs | 6 +- .../ContentDeletedBlueprintNotification.cs | 7 +- .../ContentDeletedNotification.cs | 3 +- .../ContentDeletedVersionsNotification.cs | 10 +- .../ContentDeletingNotification.cs | 6 +- .../ContentDeletingVersionsNotification.cs | 5 +- .../ContentEmptiedRecycleBinNotification.cs | 3 +- .../ContentEmptyingRecycleBinNotification.cs | 3 +- .../Notifications/ContentMovedNotification.cs | 7 +- .../ContentMovedToRecycleBinNotification.cs | 8 +- .../ContentMovingNotification.cs | 7 +- .../ContentMovingToRecycleBinNotification.cs | 8 +- .../ContentNotificationExtensions.cs | 25 +- .../ContentPublishedNotification.cs | 6 +- .../ContentPublishingNotification.cs | 6 +- .../ContentRefreshNotification.cs | 3 +- .../ContentRolledBackNotification.cs | 3 +- .../ContentRollingBackNotification.cs | 3 +- .../ContentSavedBlueprintNotification.cs | 3 +- .../Notifications/ContentSavedNotification.cs | 6 +- .../ContentSavingNotification.cs | 6 +- .../ContentSendingToPublishNotification.cs | 3 +- .../ContentSentToPublishNotification.cs | 3 +- .../ContentSortedNotification.cs | 3 +- .../ContentSortingNotification.cs | 3 +- .../ContentTreeChangeNotification.cs | 18 +- .../ContentTypeCacheRefresherNotification.cs | 6 +- .../ContentTypeChangeNotification.cs | 7 +- .../ContentTypeChangedNotification.cs | 8 +- .../ContentTypeDeletedNotification.cs | 7 +- .../ContentTypeDeletingNotification.cs | 7 +- .../ContentTypeMovedNotification.cs | 7 +- .../ContentTypeMovingNotification.cs | 8 +- .../ContentTypeRefreshNotification.cs | 7 +- .../ContentTypeRefreshedNotification.cs | 3 +- .../ContentTypeSavedNotification.cs | 7 +- .../ContentTypeSavingNotification.cs | 7 +- .../ContentUnpublishedNotification.cs | 6 +- .../ContentUnpublishingNotification.cs | 7 +- .../Notifications/CopiedNotification.cs | 8 +- .../Notifications/CopyingNotification.cs | 6 +- .../Notifications/CreatedNotification.cs | 6 +- .../Notifications/CreatingNotification.cs | 6 +- .../DataTypeCacheRefresherNotification.cs | 6 +- .../DataTypeDeletedNotification.cs | 3 +- .../DataTypeDeletingNotification.cs | 3 +- .../DataTypeMovedNotification.cs | 3 +- .../DataTypeMovingNotification.cs | 3 +- .../DataTypeSavedNotification.cs | 6 +- .../DataTypeSavingNotification.cs | 6 +- .../Notifications/DeletedNotification.cs | 6 +- .../DeletedVersionsNotification.cs | 3 +- .../DeletedVersionsNotificationBase.cs | 11 +- .../Notifications/DeletingNotification.cs | 6 +- .../DictionaryCacheRefresherNotification.cs | 6 +- .../DictionaryItemDeletedNotification.cs | 3 +- .../DictionaryItemDeletingNotification.cs | 6 +- .../DictionaryItemSavedNotification.cs | 7 +- .../DictionaryItemSavingNotification.cs | 7 +- .../DomainCacheRefresherNotification.cs | 6 +- .../DomainDeletedNotification.cs | 3 +- .../DomainDeletingNotification.cs | 6 +- .../Notifications/DomainSavedNotification.cs | 6 +- .../Notifications/DomainSavingNotification.cs | 6 +- .../EmptiedRecycleBinNotification.cs | 3 +- .../EmptyingRecycleBinNotification.cs | 3 +- .../EntityContainerDeletedNotification.cs | 3 +- .../EntityContainerDeletingNotification.cs | 3 +- .../EntityContainerRenamedNotification.cs | 3 +- .../EntityContainerRenamingNotification.cs | 3 +- .../EntityContainerSavedNotification.cs | 3 +- .../EntityContainerSavingNotification.cs | 3 +- .../EntityRefreshNotification.cs | 6 +- .../EnumerableObjectNotification.cs | 6 +- .../Notifications/INotification.cs | 2 +- .../LanguageCacheRefresherNotification.cs | 6 +- .../LanguageDeletedNotification.cs | 3 +- .../LanguageDeletingNotification.cs | 6 +- .../LanguageSavedNotification.cs | 6 +- .../LanguageSavingNotification.cs | 6 +- .../MacroCacheRefresherNotification.cs | 6 +- .../Notifications/MacroDeletedNotification.cs | 3 +- .../MacroDeletingNotification.cs | 6 +- .../Notifications/MacroSavedNotification.cs | 6 +- .../Notifications/MacroSavingNotification.cs | 6 +- .../MediaCacheRefresherNotification.cs | 6 +- .../Notifications/MediaDeletedNotification.cs | 3 +- .../MediaDeletedVersionsNotification.cs | 5 +- .../MediaDeletingNotification.cs | 6 +- .../MediaDeletingVersionsNotification.cs | 5 +- .../MediaEmptiedRecycleBinNotification.cs | 3 +- .../MediaEmptyingRecycleBinNotification.cs | 3 +- .../Notifications/MediaMovedNotification.cs | 7 +- .../MediaMovedToRecycleBinNotification.cs | 7 +- .../Notifications/MediaMovingNotification.cs | 7 +- .../MediaMovingToRecycleBinNotification.cs | 8 +- .../Notifications/MediaRefreshNotification.cs | 3 +- .../Notifications/MediaSavedNotification.cs | 6 +- .../Notifications/MediaSavingNotification.cs | 6 +- .../Packaging/CompiledPackageXmlParser.cs | 8 +- .../Packaging/ConflictingPackageData.cs | 8 +- .../Packaging/IPackageDefinitionRepository.cs | 4 +- .../Packaging/InstallationSummary.cs | 25 +- .../Packaging/InstalledPackage.cs | 4 +- .../InstalledPackageMigrationPlans.cs | 2 +- .../Persistence/Constants-DatabaseSchema.cs | 2 +- .../Persistence/Constants-Locks.cs | 4 +- .../Persistence/IQueryRepository.cs | 2 +- .../Persistence/IReadRepository.cs | 2 +- .../Persistence/IReadWriteQueryRepository.cs | 2 +- src/Umbraco.Core/Persistence/IRepository.cs | 2 +- .../Persistence/IWriteRepository.cs | 2 +- .../Persistence/Querying/IQuery.cs | 2 +- .../Repositories/IAuditEntryRepository.cs | 2 +- .../Repositories/IAuditRepository.cs | 6 +- .../Repositories/IConsentRepository.cs | 2 +- .../Repositories/IContentRepository.cs | 11 +- .../IContentTypeCommonRepository.cs | 3 +- .../Repositories/IContentTypeRepository.cs | 2 +- .../IContentTypeRepositoryBase.cs | 4 +- .../IDataTypeContainerRepository.cs | 2 +- .../Repositories/IDataTypeRepository.cs | 2 +- .../Repositories/IDictionaryRepository.cs | 5 +- .../IDocumentBlueprintRepository.cs | 2 +- .../Repositories/IDocumentRepository.cs | 1 + .../IDocumentTypeContainerRepository.cs | 2 +- .../Repositories/IDomainRepository.cs | 5 +- .../IEntityContainerRepository.cs | 2 +- .../Repositories/IEntityRepository.cs | 19 +- .../Repositories/IIdKeyMapRepository.cs | 3 +- .../Repositories/IInstallationRepository.cs | 2 +- .../Repositories/ILanguageRepository.cs | 2 +- .../Repositories/ILogViewerQueryRepository.cs | 2 +- .../Repositories/IMacroRepository.cs | 4 +- .../Repositories/IMediaRepository.cs | 3 +- .../IMediaTypeContainerRepository.cs | 2 +- .../Repositories/IMediaTypeRepository.cs | 2 +- .../Repositories/IMemberTypeRepository.cs | 2 +- .../Repositories/INodeCountRepository.cs | 3 +- .../Repositories/INotificationsRepository.cs | 10 +- .../IPartialViewMacroRepository.cs | 2 +- .../Repositories/IPublicAccessRepository.cs | 2 +- .../Repositories/IRedirectUrlRepository.cs | 2 +- .../Repositories/IRelationTypeRepository.cs | 2 +- .../IServerRegistrationRepository.cs | 2 +- .../Repositories/ITagRepository.cs | 17 +- .../Repositories/ITwoFactorLoginRepository.cs | 1 + .../Repositories/IUpgradeCheckRepository.cs | 2 +- .../Repositories/IUserGroupRepository.cs | 5 +- .../Repositories/IUserRepository.cs | 11 +- .../Repositories/InstallationRepository.cs | 3 +- .../PropertyEditors/BlockListConfiguration.cs | 29 +- .../ColorPickerConfiguration.cs | 10 +- .../PropertyEditors/ConfigurationEditor.cs | 63 +- .../ConfigurationEditorOfTConfiguration.cs | 106 ++-- .../PropertyEditors/ConfigurationField.cs | 4 +- .../ConfigurationFieldAttribute.cs | 14 +- .../ContentPickerConfiguration.cs | 8 +- .../ContentPickerConfigurationEditor.cs | 9 +- .../ContentPickerPropertyEditor.cs | 7 +- .../PropertyEditors/DataEditor.cs | 42 +- .../PropertyEditors/DataEditorAttribute.cs | 14 +- .../PropertyEditors/DataEditorCollection.cs | 3 +- .../DataEditorCollectionBuilder.cs | 2 +- .../PropertyEditors/DataValueEditor.cs | 33 +- .../PropertyEditors/DataValueEditorFactory.cs | 2 +- .../DataValueReferenceFactoryCollection.cs | 14 +- ...aValueReferenceFactoryCollectionBuilder.cs | 2 +- .../PropertyEditors/DateTimeConfiguration.cs | 14 +- .../DateTimeConfigurationEditor.cs | 11 +- .../PropertyEditors/DateValueEditor.cs | 4 +- .../DecimalConfigurationEditor.cs | 8 +- .../PropertyEditors/DecimalPropertyEditor.cs | 2 +- .../DefaultPropertyIndexValueFactory.cs | 5 +- .../DefaultPropertyValueConverterAttribute.cs | 2 +- .../DropDownFlexibleConfiguration.cs | 7 +- .../PropertyEditors/EditorType.cs | 4 +- .../EmailAddressConfiguration.cs | 5 +- .../EmailAddressConfigurationEditor.cs | 6 +- .../EyeDropperColorPickerConfiguration.cs | 5 +- ...yeDropperColorPickerConfigurationEditor.cs | 10 +- .../EyeDropperColorPickerPropertyEditor.cs | 5 +- .../FileExtensionConfigItem.cs | 8 +- .../FileUploadConfiguration.cs | 2 +- .../FileUploadConfigurationEditor.cs | 6 +- .../PropertyEditors/GridEditor.cs | 15 +- .../PropertyEditors/IConfigurationEditor.cs | 5 +- .../PropertyEditors/IConfigureValueType.cs | 2 +- .../IDataValueEditorFactory.cs | 2 +- .../PropertyEditors/IDataValueReference.cs | 2 +- .../IDataValueReferenceFactory.cs | 2 +- .../PropertyEditors/IFileExtensionConfig.cs | 2 +- .../IFileExtensionConfigItem.cs | 2 +- .../IIgnoreUserStartNodesConfig.cs | 2 +- .../IManifestValueValidator.cs | 2 +- .../IPropertyIndexValueFactory.cs | 5 +- .../IPropertyValueConverter.cs | 11 +- .../PropertyEditors/IValueFormatValidator.cs | 2 +- .../IValueRequiredValidator.cs | 2 +- .../PropertyEditors/IValueValidator.cs | 2 +- .../IntegerConfigurationEditor.cs | 8 +- .../PropertyEditors/IntegerPropertyEditor.cs | 2 +- .../PropertyEditors/LabelConfiguration.cs | 2 +- .../LabelConfigurationEditor.cs | 12 +- .../PropertyEditors/LabelPropertyEditor.cs | 11 +- .../PropertyEditors/ListViewConfiguration.cs | 81 ++- .../ListViewConfigurationEditor.cs | 5 +- .../ManifestValueValidatorCollection.cs | 5 +- .../PropertyEditors/MarkdownConfiguration.cs | 9 +- .../MarkdownConfigurationEditor.cs | 5 +- .../PropertyEditors/MarkdownPropertyEditor.cs | 5 +- .../MediaPicker3Configuration.cs | 32 +- .../MediaPicker3ConfigurationEditor.cs | 11 +- .../MediaPickerConfiguration.cs | 5 +- .../MediaPickerConfigurationEditor.cs | 9 +- .../ContentTypeParameterEditor.cs | 2 +- ...omplexEditorElementTypeValidationResult.cs | 2 +- ...mplexEditorPropertyTypeValidationResult.cs | 3 +- .../ComplexEditorValidationResult.cs | 2 +- .../Validators/DateTimeValidator.cs | 11 +- .../Validators/DecimalValidator.cs | 4 +- .../Validators/DelimitedValueValidator.cs | 13 +- .../Validators/EmailValidator.cs | 6 +- .../Validators/IntegerValidator.cs | 4 +- .../CheckboxListValueConverter.cs | 5 +- .../ContentPickerValueConverter.cs | 17 +- .../DatePickerValueConverter.cs | 15 +- .../ValueConverters/DecimalValueConverter.cs | 8 +- .../EmailAddressValueConverter.cs | 11 +- .../ValueConverters/IntegerValueConverter.cs | 11 +- .../ValueConverters/LabelValueConverter.cs | 17 +- .../MediaPickerValueConverter.cs | 35 +- .../PublishedCache/DefaultCultureAccessor.cs | 8 +- .../PublishedCache/IDefaultCultureAccessor.cs | 2 +- .../PublishedCache/IDomainCache.cs | 2 +- .../PublishedCache/IPublishedCache.cs | 2 +- .../PublishedCache/IPublishedContentCache.cs | 6 +- .../PublishedCache/IPublishedMediaCache.cs | 2 +- .../PublishedCache/IPublishedSnapshot.cs | 2 +- .../IPublishedSnapshotStatus.cs | 2 +- src/Umbraco.Core/PublishedCache/ITagQuery.cs | 5 +- .../Internal/InternalPublishedContent.cs | 20 +- .../Internal/InternalPublishedContentCache.cs | 6 +- .../Internal/InternalPublishedProperty.cs | 1 + .../Internal/InternalPublishedSnapshot.cs | 5 +- src/Umbraco.Core/Routing/AliasUrlProvider.cs | 25 +- .../Routing/ContentFinderByIdPath.cs | 17 +- .../Routing/ContentFinderByPageIdQuery.cs | 13 +- .../Routing/ContentFinderByRedirectUrl.cs | 22 +- .../Routing/ContentFinderByUrl.cs | 10 +- .../Routing/ContentFinderByUrlAlias.cs | 19 +- .../Routing/ContentFinderByUrlAndTemplate.cs | 20 +- .../Routing/ContentFinderCollection.cs | 3 +- .../Routing/ContentFinderCollectionBuilder.cs | 2 +- .../Routing/DefaultMediaUrlProvider.cs | 12 +- .../Routing/DefaultUrlProvider.cs | 40 +- src/Umbraco.Core/Routing/DomainAndUri.cs | 5 +- src/Umbraco.Core/Routing/DomainUtilities.cs | 326 +++++----- src/Umbraco.Core/Routing/IMediaUrlProvider.cs | 2 +- .../Routing/IPublishedUrlProvider.cs | 11 +- src/Umbraco.Core/Routing/IUrlProvider.cs | 2 +- src/Umbraco.Core/Runtime/IMainDomLock.cs | 2 +- src/Umbraco.Core/Runtime/MainDom.cs | 14 +- .../Runtime/MainDomSemaphoreLock.cs | 2 +- src/Umbraco.Core/Scoping/IScopeContext.cs | 2 +- src/Umbraco.Core/Sections/ContentSection.cs | 2 +- src/Umbraco.Core/Sections/FormsSection.cs | 3 +- src/Umbraco.Core/Sections/ISection.cs | 2 +- src/Umbraco.Core/Sections/MediaSection.cs | 3 +- .../Security/AuthenticationExtensions.cs | 2 +- .../BackOfficeExternalLoginProviderErrors.cs | 3 +- .../BackOfficeUserPasswordCheckerResult.cs | 2 +- .../Security/ClaimsPrincipalExtensions.cs | 15 +- .../Security/ContentPermissions.cs | 100 +-- .../Security/ITwoFactorProvider.cs | 2 +- .../Security/IdentityAuditEventArgs.cs | 43 +- .../Security/LegacyPasswordSecurity.cs | 65 +- src/Umbraco.Core/Security/MediaPermissions.cs | 10 +- .../IConfigurationEditorJsonSerializer.cs | 2 +- .../Serialization/IJsonSerializer.cs | 2 +- src/Umbraco.Core/Services/AuditService.cs | 13 +- .../Services/Changes/ContentTypeChange.cs | 2 +- .../Changes/ContentTypeChangeExtensions.cs | 2 +- .../Changes/ContentTypeChangeTypes.cs | 4 +- .../Services/Changes/DomainChangeTypes.cs | 4 +- src/Umbraco.Core/Services/ConsentService.cs | 17 +- src/Umbraco.Core/Services/ContentService.cs | 575 +++++++++--------- .../Services/ContentServiceExtensions.cs | 22 +- .../ContentTypeBaseServiceProvider.cs | 6 +- .../Services/ContentTypeService.cs | 31 +- ...peServiceBaseOfTRepositoryTItemTService.cs | 14 +- .../Services/ContentTypeServiceExtensions.cs | 31 +- .../Services/ContentVersionService.cs | 10 +- src/Umbraco.Core/Services/DashboardService.cs | 67 +- src/Umbraco.Core/Services/DataTypeService.cs | 165 ++--- .../Services/DateTypeServiceExtensions.cs | 4 +- .../DefaultContentVersionCleanupPolicy.cs | 15 +- src/Umbraco.Core/Services/DomainService.cs | 6 +- src/Umbraco.Core/Services/EntityService.cs | 107 ++-- .../Services/EntityXmlSerializer.cs | 141 +++-- src/Umbraco.Core/Services/FileService.cs | 144 +++-- src/Umbraco.Core/Services/IAuditService.cs | 28 +- .../Services/IBasicAuthService.cs | 1 + src/Umbraco.Core/Services/IConsentService.cs | 11 +- src/Umbraco.Core/Services/IContentService.cs | 37 +- .../Services/IContentServiceBase.cs | 1 + .../Services/IContentTypeService.cs | 2 +- .../Services/IContentTypeServiceBase.cs | 25 +- .../Services/IDashboardService.cs | 2 +- src/Umbraco.Core/Services/IDataTypeService.cs | 12 +- src/Umbraco.Core/Services/IDomainService.cs | 8 +- .../Services/IEditorConfigurationParser.cs | 5 +- src/Umbraco.Core/Services/IEntityService.cs | 56 +- .../Services/IEntityXmlSerializer.cs | 9 +- .../Services/IExamineIndexCountService.cs | 2 +- src/Umbraco.Core/Services/IFileService.cs | 23 +- src/Umbraco.Core/Services/IIdKeyMap.cs | 6 + .../Services/IInstallationService.cs | 2 +- .../Services/IIpAddressUtilities.cs | 2 +- .../Services/ILocalizationService.cs | 16 +- .../Services/ILocalizedTextService.cs | 4 +- src/Umbraco.Core/Services/IMacroService.cs | 4 +- src/Umbraco.Core/Services/IMediaService.cs | 37 +- .../Services/IMediaTypeService.cs | 2 +- .../Services/IMemberGroupService.cs | 6 + src/Umbraco.Core/Services/IMemberService.cs | 41 +- .../Services/IMemberTypeService.cs | 2 +- .../Services/IMembershipUserService.cs | 2 +- .../Services/IMetricsConsentService.cs | 2 +- .../Services/INodeCountService.cs | 3 +- .../Services/INotificationService.cs | 8 +- .../Services/IPropertyValidationService.cs | 2 +- .../Services/IPublicAccessService.cs | 5 +- .../Services/IRedirectUrlService.cs | 2 +- src/Umbraco.Core/Services/ISectionService.cs | 2 +- src/Umbraco.Core/Services/IService.cs | 2 +- src/Umbraco.Core/Services/ITagService.cs | 8 +- src/Umbraco.Core/Services/ITreeService.cs | 2 +- src/Umbraco.Core/Services/IUpgradeService.cs | 2 +- .../Services/IUsageInformationService.cs | 2 +- src/Umbraco.Core/Services/IUserDataService.cs | 2 +- src/Umbraco.Core/Services/IdKeyMap.cs | 99 ++- src/Umbraco.Core/Services/KeyValueService.cs | 6 +- .../Services/LocalizationService.cs | 51 +- .../Services/LocalizedTextService.cs | 218 +++---- .../LocalizedTextServiceExtensions.cs | 53 +- .../LocalizedTextServiceFileSources.cs | 107 ++-- ...lizedTextServiceSupplementaryFileSource.cs | 10 +- src/Umbraco.Core/Services/MacroService.cs | 25 +- src/Umbraco.Core/Services/MediaService.cs | 428 +++++++------ src/Umbraco.Core/Strings/CleanStringType.cs | 8 +- .../Strings/DefaultUrlSegmentProvider.cs | 2 +- src/Umbraco.Core/Strings/Diff.cs | 127 ++-- .../Strings/IShortStringHelper.cs | 2 +- .../Strings/IUrlSegmentProvider.cs | 2 +- .../Sync/ISyncBootStateAccessor.cs | 2 +- .../Templates/HtmlImageSourceParser.cs | 5 +- .../Templates/HtmlLocalLinkParser.cs | 13 +- src/Umbraco.Core/Templates/HtmlUrlParser.cs | 18 +- src/Umbraco.Core/Tour/BackOfficeTourFilter.cs | 2 +- src/Umbraco.Core/Trees/ActionUrlMethod.cs | 4 +- src/Umbraco.Core/Trees/CoreTreeAttribute.cs | 2 +- src/Umbraco.Core/Trees/ITree.cs | 2 +- .../Web/CookieManagerExtensions.cs | 2 +- src/Umbraco.Core/Web/ICookieManager.cs | 3 + .../Web/IUmbracoContextAccessor.cs | 6 +- .../Web/IUmbracoContextFactory.cs | 7 +- src/Umbraco.Core/WebAssets/AssetFile.cs | 3 +- src/Umbraco.Core/WebAssets/AssetType.cs | 4 +- src/Umbraco.Core/WebAssets/BundlingOptions.cs | 17 +- src/Umbraco.Core/WebAssets/CssFile.cs | 2 +- .../CustomBackOfficeAssetsCollection.cs | 3 +- src/Umbraco.Core/WebAssets/IAssetFile.cs | 3 +- src/Umbraco.Core/WebAssets/JavascriptFile.cs | 2 +- .../Xml/XPath/INavigableContent.cs | 4 +- .../Xml/XPath/INavigableContentType.cs | 2 +- .../Xml/XPath/INavigableFieldType.cs | 2 +- .../Xml/XPath/INavigableSource.cs | 2 +- src/Umbraco.Core/Xml/XPath/MacroNavigator.cs | 47 +- 956 files changed, 6845 insertions(+), 5774 deletions(-) diff --git a/src/Umbraco.Core/Actions/ActionBrowse.cs b/src/Umbraco.Core/Actions/ActionBrowse.cs index 1686875eb370..2620888a30e6 100644 --- a/src/Umbraco.Core/Actions/ActionBrowse.cs +++ b/src/Umbraco.Core/Actions/ActionBrowse.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Cms.Core.Actions; diff --git a/src/Umbraco.Core/Actions/ActionCollectionBuilder.cs b/src/Umbraco.Core/Actions/ActionCollectionBuilder.cs index 3c7cbd6a1e25..aac1556234e8 100644 --- a/src/Umbraco.Core/Actions/ActionCollectionBuilder.cs +++ b/src/Umbraco.Core/Actions/ActionCollectionBuilder.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Composing; diff --git a/src/Umbraco.Core/Actions/ActionCopy.cs b/src/Umbraco.Core/Actions/ActionCopy.cs index 880d4847c243..02bb17166f17 100644 --- a/src/Umbraco.Core/Actions/ActionCopy.cs +++ b/src/Umbraco.Core/Actions/ActionCopy.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Cms.Core.Actions; diff --git a/src/Umbraco.Core/Actions/ActionCreateBlueprintFromContent.cs b/src/Umbraco.Core/Actions/ActionCreateBlueprintFromContent.cs index 99716ab61726..85490b42f818 100644 --- a/src/Umbraco.Core/Actions/ActionCreateBlueprintFromContent.cs +++ b/src/Umbraco.Core/Actions/ActionCreateBlueprintFromContent.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Cms.Core.Actions; diff --git a/src/Umbraco.Core/Actions/ActionDelete.cs b/src/Umbraco.Core/Actions/ActionDelete.cs index f64ecd32bfeb..3159ab859064 100644 --- a/src/Umbraco.Core/Actions/ActionDelete.cs +++ b/src/Umbraco.Core/Actions/ActionDelete.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Cms.Core.Actions; @@ -9,14 +9,14 @@ namespace Umbraco.Cms.Core.Actions; public class ActionDelete : IAction { /// - /// The unique action alias + /// The unique action letter /// - private const string ActionAlias = "delete"; + public const char ActionLetter = 'D'; /// - /// The unique action letter + /// The unique action alias /// - public const char ActionLetter = 'D'; + private const string ActionAlias = "delete"; /// public char Letter => ActionLetter; diff --git a/src/Umbraco.Core/Actions/ActionMove.cs b/src/Umbraco.Core/Actions/ActionMove.cs index 76a99b479154..a40d03d096f9 100644 --- a/src/Umbraco.Core/Actions/ActionMove.cs +++ b/src/Umbraco.Core/Actions/ActionMove.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Cms.Core.Actions; diff --git a/src/Umbraco.Core/Actions/ActionNew.cs b/src/Umbraco.Core/Actions/ActionNew.cs index 05f8e4c4c4f7..31056632ed5c 100644 --- a/src/Umbraco.Core/Actions/ActionNew.cs +++ b/src/Umbraco.Core/Actions/ActionNew.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Cms.Core.Actions; diff --git a/src/Umbraco.Core/Actions/ActionNotify.cs b/src/Umbraco.Core/Actions/ActionNotify.cs index 2f13d9ce365e..9d1975b852b8 100644 --- a/src/Umbraco.Core/Actions/ActionNotify.cs +++ b/src/Umbraco.Core/Actions/ActionNotify.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Cms.Core.Actions; diff --git a/src/Umbraco.Core/Actions/ActionProtect.cs b/src/Umbraco.Core/Actions/ActionProtect.cs index c99fa7b5917c..0a5ac8ace8a3 100644 --- a/src/Umbraco.Core/Actions/ActionProtect.cs +++ b/src/Umbraco.Core/Actions/ActionProtect.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Cms.Core.Actions; diff --git a/src/Umbraco.Core/Actions/ActionPublish.cs b/src/Umbraco.Core/Actions/ActionPublish.cs index 39eff9f1716b..e07b0935bc97 100644 --- a/src/Umbraco.Core/Actions/ActionPublish.cs +++ b/src/Umbraco.Core/Actions/ActionPublish.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Cms.Core.Actions; diff --git a/src/Umbraco.Core/Actions/ActionRestore.cs b/src/Umbraco.Core/Actions/ActionRestore.cs index 8c5aa8428ead..79e00f446478 100644 --- a/src/Umbraco.Core/Actions/ActionRestore.cs +++ b/src/Umbraco.Core/Actions/ActionRestore.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Cms.Core.Actions; diff --git a/src/Umbraco.Core/Actions/ActionRights.cs b/src/Umbraco.Core/Actions/ActionRights.cs index bcf353b59603..08afe7e2db4f 100644 --- a/src/Umbraco.Core/Actions/ActionRights.cs +++ b/src/Umbraco.Core/Actions/ActionRights.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Cms.Core.Actions; diff --git a/src/Umbraco.Core/Actions/ActionRollback.cs b/src/Umbraco.Core/Actions/ActionRollback.cs index 2d0dadf7a602..5aada555d3d6 100644 --- a/src/Umbraco.Core/Actions/ActionRollback.cs +++ b/src/Umbraco.Core/Actions/ActionRollback.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Cms.Core.Actions; diff --git a/src/Umbraco.Core/Actions/ActionSort.cs b/src/Umbraco.Core/Actions/ActionSort.cs index 35420501435d..b77a44c7307c 100644 --- a/src/Umbraco.Core/Actions/ActionSort.cs +++ b/src/Umbraco.Core/Actions/ActionSort.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Cms.Core.Actions; diff --git a/src/Umbraco.Core/Actions/ActionToPublish.cs b/src/Umbraco.Core/Actions/ActionToPublish.cs index 86ba32997a5f..bf15ee4e3ba4 100644 --- a/src/Umbraco.Core/Actions/ActionToPublish.cs +++ b/src/Umbraco.Core/Actions/ActionToPublish.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Cms.Core.Actions; diff --git a/src/Umbraco.Core/Actions/ActionUnpublish.cs b/src/Umbraco.Core/Actions/ActionUnpublish.cs index 1388a98bffe6..c8a83f002e0d 100644 --- a/src/Umbraco.Core/Actions/ActionUnpublish.cs +++ b/src/Umbraco.Core/Actions/ActionUnpublish.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Cms.Core.Actions; diff --git a/src/Umbraco.Core/Actions/ActionUpdate.cs b/src/Umbraco.Core/Actions/ActionUpdate.cs index b6684fb0d5f9..4af2410cc445 100644 --- a/src/Umbraco.Core/Actions/ActionUpdate.cs +++ b/src/Umbraco.Core/Actions/ActionUpdate.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Cms.Core.Actions; diff --git a/src/Umbraco.Core/Actions/IAction.cs b/src/Umbraco.Core/Actions/IAction.cs index 995f68603b58..f57e697a2e62 100644 --- a/src/Umbraco.Core/Actions/IAction.cs +++ b/src/Umbraco.Core/Actions/IAction.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Composing; diff --git a/src/Umbraco.Core/Attempt.cs b/src/Umbraco.Core/Attempt.cs index 87231a27b870..cf6df0d01094 100644 --- a/src/Umbraco.Core/Attempt.cs +++ b/src/Umbraco.Core/Attempt.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; /// /// Provides ways to create attempts. @@ -64,7 +64,6 @@ public static Attempt FailWithStatus(TStatus public static Attempt Fail(TResult result, Exception exception) => Attempt.Fail(result, exception); - /// /// Creates a failed attempt with a result, an exception and a status. /// diff --git a/src/Umbraco.Core/AttemptOfTResult.cs b/src/Umbraco.Core/AttemptOfTResult.cs index de5ad7bb38e9..2969755d9450 100644 --- a/src/Umbraco.Core/AttemptOfTResult.cs +++ b/src/Umbraco.Core/AttemptOfTResult.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; /// /// Represents the result of an operation attempt. @@ -7,6 +7,9 @@ [Serializable] public struct Attempt { + // optimize, use a singleton failed attempt + private static readonly Attempt Failed = new(false, default, null); + // private - use Succeed() or Fail() methods to create attempts private Attempt(bool success, TResult? result, Exception? exception) { @@ -30,6 +33,13 @@ private Attempt(bool success, TResult? result, Exception? exception) /// public TResult? Result { get; } + /// + /// Implicitly operator to check if the attempt was successful without having to access the 'success' property + /// + /// + /// + public static implicit operator bool(Attempt a) => a.Success; + /// /// Gets the attempt result, if successful, else a default value. /// @@ -43,9 +53,6 @@ public TResult ResultOr(TResult value) return value; } - // optimize, use a singleton failed attempt - private static readonly Attempt Failed = new(false, default, null); - /// /// Creates a successful attempt. /// @@ -101,11 +108,4 @@ public TResult ResultOr(TResult value) /// The result of the attempt. /// The attempt. public static Attempt If(bool condition, TResult? result) => new(condition, result, null); - - /// - /// Implicitly operator to check if the attempt was successful without having to access the 'success' property - /// - /// - /// - public static implicit operator bool(Attempt a) => a.Success; } diff --git a/src/Umbraco.Core/AttemptOfTResultTStatus.cs b/src/Umbraco.Core/AttemptOfTResultTStatus.cs index 7a12c9d9958a..e88465b3ad3a 100644 --- a/src/Umbraco.Core/AttemptOfTResultTStatus.cs +++ b/src/Umbraco.Core/AttemptOfTResultTStatus.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; /// /// Represents the result of an operation attempt. @@ -8,6 +8,15 @@ [Serializable] public struct Attempt { + // private - use Succeed() or Fail() methods to create attempts + private Attempt(bool success, TResult result, TStatus status, Exception? exception) + { + Success = success; + Result = result; + Status = status; + Exception = exception; + } + /// /// Gets a value indicating whether this was successful. /// @@ -28,14 +37,12 @@ public struct Attempt /// public TStatus Status { get; } - // private - use Succeed() or Fail() methods to create attempts - private Attempt(bool success, TResult result, TStatus status, Exception? exception) - { - Success = success; - Result = result; - Status = status; - Exception = exception; - } + /// + /// Implicitly operator to check if the attempt was successful without having to access the 'success' property + /// + /// + /// + public static implicit operator bool(Attempt a) => a.Success; /// /// Creates a successful attempt. @@ -111,11 +118,4 @@ public static Attempt Fail(TStatus status, TResult result, Exc public static Attempt If(bool condition, TStatus succStatus, TStatus failStatus, TResult result) => new Attempt(condition, result, condition ? succStatus : failStatus, null); - - /// - /// Implicitly operator to check if the attempt was successful without having to access the 'success' property - /// - /// - /// - public static implicit operator bool(Attempt a) => a.Success; } diff --git a/src/Umbraco.Core/Cache/AppCacheExtensions.cs b/src/Umbraco.Core/Cache/AppCacheExtensions.cs index 52441a635baa..0f1f242ed01f 100644 --- a/src/Umbraco.Core/Cache/AppCacheExtensions.cs +++ b/src/Umbraco.Core/Cache/AppCacheExtensions.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Cache; namespace Umbraco.Extensions; @@ -7,7 +7,8 @@ namespace Umbraco.Extensions; /// public static class AppCacheExtensions { - public static T? GetCacheItem(this IAppPolicyCache provider, + public static T? GetCacheItem( + this IAppPolicyCache provider, string cacheKey, Func getCacheItem, TimeSpan? timeout, @@ -18,7 +19,8 @@ public static class AppCacheExtensions return result == null ? default : result.TryConvertTo().Result; } - public static void InsertCacheItem(this IAppPolicyCache provider, + public static void InsertCacheItem( + this IAppPolicyCache provider, string cacheKey, Func getCacheItem, TimeSpan? timeout = null, @@ -28,13 +30,13 @@ public static void InsertCacheItem(this IAppPolicyCache provider, public static IEnumerable GetCacheItemsByKeySearch(this IAppCache provider, string keyStartsWith) { - IEnumerable result = provider.SearchByKey(keyStartsWith); + IEnumerable result = provider.SearchByKey(keyStartsWith); return result.Select(x => x.TryConvertTo().Result); } public static IEnumerable GetCacheItemsByKeyExpression(this IAppCache provider, string regexString) { - IEnumerable result = provider.SearchByRegex(regexString); + IEnumerable result = provider.SearchByRegex(regexString); return result.Select(x => x.TryConvertTo().Result); } diff --git a/src/Umbraco.Core/Cache/AppCaches.cs b/src/Umbraco.Core/Cache/AppCaches.cs index d53f92650a3e..faca2e14f4f0 100644 --- a/src/Umbraco.Core/Cache/AppCaches.cs +++ b/src/Umbraco.Core/Cache/AppCaches.cs @@ -1,4 +1,4 @@ -using Umbraco.Extensions; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.Cache; @@ -29,8 +29,7 @@ public AppCaches( /// When used by repositories, all cache policies apply, but the underlying caches do not cache anything. /// Used by tests. /// - public static AppCaches Disabled { get; } = new(NoAppCache.Instance, NoAppCache.Instance, - new IsolatedCaches(_ => NoAppCache.Instance)); + public static AppCaches Disabled { get; } = new(NoAppCache.Instance, NoAppCache.Instance, new IsolatedCaches(_ => NoAppCache.Instance)); /// /// Gets the special no-cache instance. @@ -39,8 +38,7 @@ public AppCaches( /// When used by repositories, all cache policies are bypassed. /// Used by repositories that do no cache. /// - public static AppCaches NoCache { get; } = new(NoAppCache.Instance, NoAppCache.Instance, - new IsolatedCaches(_ => NoAppCache.Instance)); + public static AppCaches NoCache { get; } = new(NoAppCache.Instance, NoAppCache.Instance, new IsolatedCaches(_ => NoAppCache.Instance)); /// /// Gets the per-request cache. @@ -71,16 +69,17 @@ public AppCaches( /// public IsolatedCaches IsolatedCaches { get; } - public void Dispose() => - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(true); - public static AppCaches Create(IRequestCache requestCache) => new( new DeepCloneAppCache(new ObjectCacheAppCache()), requestCache, new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); + public void Dispose() => + + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(true); + protected virtual void Dispose(bool disposing) { if (!_disposedValue) diff --git a/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs b/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs index edfc8e7b28b2..1cf3b1461e0f 100644 --- a/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs +++ b/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Cache; @@ -25,6 +25,7 @@ public abstract class AppPolicedCacheDictionary : IDisposable protected AppPolicedCacheDictionary(Func cacheFactory) => _cacheFactory = cacheFactory; public void Dispose() => + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(true); @@ -34,14 +35,6 @@ public void Dispose() => public IAppPolicyCache GetOrCreate(TKey key) => _caches.GetOrAdd(key, k => _cacheFactory(k)); - /// - /// Tries to get a cache. - /// - protected Attempt Get(TKey key) - => _caches.TryGetValue(key, out IAppPolicyCache cache) - ? Attempt.Succeed(cache) - : Attempt.Fail(); - /// /// Removes a cache. /// @@ -53,22 +46,30 @@ public IAppPolicyCache GetOrCreate(TKey key) public void RemoveAll() => _caches.Clear(); /// - /// Clears a cache. + /// Clears all caches. /// - protected void ClearCache(TKey key) + public void ClearAllCaches() { - if (_caches.TryGetValue(key, out IAppPolicyCache cache)) + foreach (IAppPolicyCache cache in _caches.Values) { cache.Clear(); } } /// - /// Clears all caches. + /// Tries to get a cache. /// - public void ClearAllCaches() + protected Attempt Get(TKey key) + => _caches.TryGetValue(key, out IAppPolicyCache? cache) + ? Attempt.Succeed(cache) + : Attempt.Fail(); + + /// + /// Clears a cache. + /// + protected void ClearCache(TKey key) { - foreach (IAppPolicyCache cache in _caches.Values) + if (_caches.TryGetValue(key, out IAppPolicyCache? cache)) { cache.Clear(); } diff --git a/src/Umbraco.Core/Cache/ApplicationCacheRefresher.cs b/src/Umbraco.Core/Cache/ApplicationCacheRefresher.cs index 230e254d80b9..11ddd8a18349 100644 --- a/src/Umbraco.Core/Cache/ApplicationCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/ApplicationCacheRefresher.cs @@ -5,24 +5,17 @@ namespace Umbraco.Cms.Core.Cache; public sealed class ApplicationCacheRefresher : CacheRefresherBase { - public ApplicationCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, - ICacheRefresherNotificationFactory factory) + public static readonly Guid UniqueId = Guid.Parse("B15F34A1-BC1D-4F8B-8369-3222728AB4C8"); + + public ApplicationCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) : base(appCaches, eventAggregator, factory) { } - #region Define - - public static readonly Guid UniqueId = Guid.Parse("B15F34A1-BC1D-4F8B-8369-3222728AB4C8"); - public override Guid RefresherUniqueId => UniqueId; public override string Name => "Application Cache Refresher"; - #endregion - - #region Refresher - public override void RefreshAll() { AppCaches.RuntimeCache.Clear(CacheKeys.ApplicationsCacheKey); @@ -40,6 +33,4 @@ public override void Remove(int id) AppCaches.RuntimeCache.Clear(CacheKeys.ApplicationsCacheKey); base.Remove(id); } - - #endregion } diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs index a73c81eaa863..04ae44a64743 100644 --- a/src/Umbraco.Core/Cache/CacheKeys.cs +++ b/src/Umbraco.Core/Cache/CacheKeys.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Cache; +namespace Umbraco.Cms.Core.Cache; /// /// Constants storing cache keys used in caching diff --git a/src/Umbraco.Core/Cache/CacheRefresherBase.cs b/src/Umbraco.Core/Cache/CacheRefresherBase.cs index e93f7cba6cd8..849d42309a41 100644 --- a/src/Umbraco.Core/Cache/CacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/CacheRefresherBase.cs @@ -8,7 +8,6 @@ namespace Umbraco.Cms.Core.Cache; /// /// A base class for cache refreshers that handles events. /// -/// The actual cache refresher type. /// The actual cache refresher type is used for strongly typed events. public abstract class CacheRefresherBase : ICacheRefresher where TNotification : CacheRefresherNotification @@ -16,9 +15,7 @@ public abstract class CacheRefresherBase : ICacheRefresher /// /// Initializes a new instance of the . /// - /// A cache helper. - protected CacheRefresherBase(AppCaches appCaches, IEventAggregator eventAggregator, - ICacheRefresherNotificationFactory factory) + protected CacheRefresherBase(AppCaches appCaches, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) { AppCaches = appCaches; EventAggregator = eventAggregator; @@ -38,7 +35,7 @@ protected CacheRefresherBase(AppCaches appCaches, IEventAggregator eventAggregat public abstract string Name { get; } /// - /// Gets the for + /// Gets the for /// protected ICacheRefresherNotificationFactory NotificationFactory { get; } @@ -50,6 +47,7 @@ protected CacheRefresherBase(AppCaches appCaches, IEventAggregator eventAggregat /// Refreshes all entities. /// public virtual void RefreshAll() => + // NOTE: We pass in string.Empty here because if we pass in NULL this causes problems with // the underlying ActivatorUtilities.CreateInstance which doesn't seem to support passing in // null to an 'object' parameter and we end up with "A suitable constructor for type 'ZYZ' could not be located." @@ -100,8 +98,6 @@ protected void ClearAllIsolatedCacheByEntityType() /// /// Raises the CacheUpdated event. /// - /// The event sender. - /// The event arguments. protected void OnCacheUpdated(CacheRefresherNotification notification) => EventAggregator.Publish(notification); #endregion diff --git a/src/Umbraco.Core/Cache/CacheRefresherCollection.cs b/src/Umbraco.Core/Cache/CacheRefresherCollection.cs index 2c22339f18ca..301f6bbdaf1b 100644 --- a/src/Umbraco.Core/Cache/CacheRefresherCollection.cs +++ b/src/Umbraco.Core/Cache/CacheRefresherCollection.cs @@ -4,7 +4,8 @@ namespace Umbraco.Cms.Core.Cache; public class CacheRefresherCollection : BuilderCollectionBase { - public CacheRefresherCollection(Func> items) : base(items) + public CacheRefresherCollection(Func> items) + : base(items) { } diff --git a/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs b/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs index 0ab9542ea833..79b44ab53d14 100644 --- a/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs +++ b/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Cache; diff --git a/src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs b/src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs index 0770e3ac4e1e..057c98dcf5c7 100644 --- a/src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs @@ -67,10 +67,9 @@ public JsonPayload(int id, Guid key, bool removed) public override void Refresh(JsonPayload[] payloads) { - //we need to clear the ContentType runtime cache since that is what caches the + // we need to clear the ContentType runtime cache since that is what caches the // db data type to store the value against and anytime a datatype changes, this also might change // we basically need to clear all sorts of runtime caches here because so many things depend upon a data type - ClearAllIsolatedCacheByEntityType(); ClearAllIsolatedCacheByEntityType(); ClearAllIsolatedCacheByEntityType(); @@ -78,7 +77,7 @@ public override void Refresh(JsonPayload[] payloads) ClearAllIsolatedCacheByEntityType(); ClearAllIsolatedCacheByEntityType(); - Attempt dataTypeCache = AppCaches.IsolatedCaches.Get(); + Attempt dataTypeCache = AppCaches.IsolatedCaches.Get(); foreach (JsonPayload payload in payloads) { @@ -95,7 +94,6 @@ public override void Refresh(JsonPayload[] payloads) SliderValueConverter.ClearCaches(); // refresh the models and cache - _publishedModelFactory.WithSafeLiveFactoryReset(() => _publishedSnapshotService.Notify(payloads)); @@ -104,7 +102,6 @@ public override void Refresh(JsonPayload[] payloads) // these events should never trigger // everything should be PAYLOAD/JSON - public override void RefreshAll() => throw new NotSupportedException(); public override void Refresh(int id) => throw new NotSupportedException(); diff --git a/src/Umbraco.Core/Cache/DeepCloneAppCache.cs b/src/Umbraco.Core/Cache/DeepCloneAppCache.cs index 701b3ef29ce7..1bc38f529a18 100644 --- a/src/Umbraco.Core/Cache/DeepCloneAppCache.cs +++ b/src/Umbraco.Core/Cache/DeepCloneAppCache.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Extensions; @@ -45,9 +45,10 @@ public DeepCloneAppCache(IAppPolicyCache innerCache) { var cached = InnerCache.Get(key, () => { - Lazy result = SafeLazy.GetSafeLazy(factory); + Lazy result = SafeLazy.GetSafeLazy(factory); var value = result .Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache + // do not store null values (backward compat), clone / reset to go into the cache return value == null ? null : CheckCloneableAndTracksChanges(value); }); @@ -65,35 +66,45 @@ public DeepCloneAppCache(IAppPolicyCache innerCache) .Select(CheckCloneableAndTracksChanges); /// - public object? Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, - string[]? dependentFiles = null) + public object? Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, string[]? dependentFiles = null) { - var cached = InnerCache.Get(key, () => + var cached = InnerCache.Get( + key, + () => { - Lazy result = SafeLazy.GetSafeLazy(factory); + Lazy result = SafeLazy.GetSafeLazy(factory); var value = result .Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache + // do not store null values (backward compat), clone / reset to go into the cache return value == null ? null : CheckCloneableAndTracksChanges(value); // clone / reset to go into the cache - }, timeout, isSliding, dependentFiles); + }, + timeout, + isSliding, + dependentFiles); // clone / reset to go into the cache return CheckCloneableAndTracksChanges(cached); } /// - public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, - string[]? dependentFiles = null) => - InnerCache.Insert(key, () => + public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, string[]? dependentFiles = null) => + InnerCache.Insert( + key, + () => { - Lazy result = SafeLazy.GetSafeLazy(factory); + Lazy result = SafeLazy.GetSafeLazy(factory); var value = result .Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache + // do not store null values (backward compat), clone / reset to go into the cache return value == null ? null : CheckCloneableAndTracksChanges(value); - }, timeout, isSliding, dependentFiles); + }, + timeout, + isSliding, + dependentFiles); /// public void Clear() => InnerCache.Clear(); @@ -117,9 +128,23 @@ public void Insert(string key, Func factory, TimeSpan? timeout = null, public void ClearByRegex(string regex) => InnerCache.ClearByRegex(regex); public void Dispose() => + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(true); + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + InnerCache.DisposeIfDisposable(); + } + + _disposedValue = true; + } + } + private static object? CheckCloneableAndTracksChanges(object? input) { if (input is IDeepCloneable cloneable) @@ -136,17 +161,4 @@ public void Dispose() => return input; } - - protected virtual void Dispose(bool disposing) - { - if (!_disposedValue) - { - if (disposing) - { - InnerCache.DisposeIfDisposable(); - } - - _disposedValue = true; - } - } } diff --git a/src/Umbraco.Core/Cache/DictionaryAppCache.cs b/src/Umbraco.Core/Cache/DictionaryAppCache.cs index 5f7740b8416c..5bf584830967 100644 --- a/src/Umbraco.Core/Cache/DictionaryAppCache.cs +++ b/src/Umbraco.Core/Cache/DictionaryAppCache.cs @@ -34,7 +34,7 @@ public class DictionaryAppCache : IRequestCache public virtual IEnumerable SearchByKey(string keyStartsWith) { var items = new List(); - foreach (var (key, value) in _items) + foreach ((string key, object? value) in _items) { if (key.InvariantStartsWith(keyStartsWith)) { @@ -50,7 +50,7 @@ public class DictionaryAppCache : IRequestCache { var compiled = new Regex(regex, RegexOptions.Compiled); var items = new List(); - foreach (var (key, value) in _items) + foreach ((string key, object? value) in _items) { if (compiled.IsMatch(key)) { diff --git a/src/Umbraco.Core/Cache/DictionaryCacheRefresher.cs b/src/Umbraco.Core/Cache/DictionaryCacheRefresher.cs index dec535448a02..c10640986caa 100644 --- a/src/Umbraco.Core/Cache/DictionaryCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/DictionaryCacheRefresher.cs @@ -6,24 +6,17 @@ namespace Umbraco.Cms.Core.Cache; public sealed class DictionaryCacheRefresher : CacheRefresherBase { - public DictionaryCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, - ICacheRefresherNotificationFactory factory) + public static readonly Guid UniqueId = Guid.Parse("D1D7E227-F817-4816-BFE9-6C39B6152884"); + + public DictionaryCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) : base(appCaches, eventAggregator, factory) { } - #region Define - - public static readonly Guid UniqueId = Guid.Parse("D1D7E227-F817-4816-BFE9-6C39B6152884"); - public override Guid RefresherUniqueId => UniqueId; public override string Name => "Dictionary Cache Refresher"; - #endregion - - #region Refresher - public override void Refresh(int id) { ClearAllIsolatedCacheByEntityType(); @@ -35,6 +28,4 @@ public override void Remove(int id) ClearAllIsolatedCacheByEntityType(); base.Remove(id); } - - #endregion } diff --git a/src/Umbraco.Core/Cache/DistributedCache.cs b/src/Umbraco.Core/Cache/DistributedCache.cs index 712ab72344b3..0adb0ea37083 100644 --- a/src/Umbraco.Core/Cache/DistributedCache.cs +++ b/src/Umbraco.Core/Cache/DistributedCache.cs @@ -26,18 +26,6 @@ public DistributedCache(IServerMessenger serverMessenger, CacheRefresherCollecti _cacheRefreshers = cacheRefreshers; } - // helper method to get an ICacheRefresher by its unique identifier - private ICacheRefresher GetRefresherById(Guid refresherGuid) - { - ICacheRefresher? refresher = _cacheRefreshers[refresherGuid]; - if (refresher == null) - { - throw new InvalidOperationException($"No cache refresher found with id {refresherGuid}"); - } - - return refresher; - } - #region Core notification methods /// @@ -63,6 +51,18 @@ public void Refresh(Guid refresherGuid, Func getNumericId, params T[] instances); } + // helper method to get an ICacheRefresher by its unique identifier + private ICacheRefresher GetRefresherById(Guid refresherGuid) + { + ICacheRefresher? refresher = _cacheRefreshers[refresherGuid]; + if (refresher == null) + { + throw new InvalidOperationException($"No cache refresher found with id {refresherGuid}"); + } + + return refresher; + } + /// /// Notifies the distributed cache of a specified item invalidation, for a specified . /// @@ -130,15 +130,15 @@ public void RefreshByPayload(Guid refresherGuid, IEnumerable ///// ///// The unique identifier of the ICacheRefresher. ///// The notification content. - //internal void Notify(Guid refresherId, object payload) - //{ + // internal void Notify(Guid refresherId, object payload) + // { // if (refresherId == Guid.Empty || payload == null) return; - // _serverMessenger.Notify( + // _serverMessenger.Notify( // Current.ServerRegistrar.Registrations, // GetRefresherById(refresherId), // json); - //} + // } /// /// Notifies the distributed cache of a global invalidation for a specified . diff --git a/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs b/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs index a19410ee1a92..6476c76f96f3 100644 --- a/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs +++ b/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs @@ -21,14 +21,14 @@ public class FastDictionaryAppCache : IAppCache /// public object? Get(string cacheKey) { - _items.TryGetValue(cacheKey, out Lazy result); // else null - return result == null ? null : SafeLazy.GetSafeLazyValue(result!); // return exceptions as null + _items.TryGetValue(cacheKey, out Lazy? result); // else null + return result == null ? null : SafeLazy.GetSafeLazyValue(result); // return exceptions as null } /// public object? Get(string cacheKey, Func getCacheItem) { - Lazy result = _items.GetOrAdd(cacheKey, k => SafeLazy.GetSafeLazy(getCacheItem)); + Lazy? result = _items.GetOrAdd(cacheKey, k => SafeLazy.GetSafeLazy(getCacheItem)); var value = result.Value; // will not throw (safe lazy) if (!(value is SafeLazy.ExceptionHolder eh)) @@ -39,7 +39,6 @@ public class FastDictionaryAppCache : IAppCache // and... it's in the cache anyway - so contrary to other cache providers, // which would trick with GetSafeLazyValue, we need to remove by ourselves, // in order NOT to cache exceptions - _items.TryRemove(cacheKey, out result); eh.Exception.Throw(); // throw once! return null; // never reached @@ -69,7 +68,7 @@ public class FastDictionaryAppCache : IAppCache public void Clear(string key) => _items.TryRemove(key, out _); /// - public void ClearOfType(Type type) + public void ClearOfType(Type? type) { if (type == null) { @@ -78,7 +77,7 @@ public void ClearOfType(Type type) var isInterface = type.IsInterface; - foreach (KeyValuePair> kvp in _items + foreach (KeyValuePair> kvp in _items .Where(x => { // entry.Value is Lazy and not null, its value may be null @@ -101,7 +100,7 @@ public void ClearOfType() Type typeOfT = typeof(T); var isInterface = typeOfT.IsInterface; - foreach (KeyValuePair> kvp in _items + foreach (KeyValuePair> kvp in _items .Where(x => { // entry.Value is Lazy and not null, its value may be null @@ -125,7 +124,7 @@ public void ClearOfType(Func predicate) Type typeOfT = typeof(T); var isInterface = typeOfT.IsInterface; - foreach (KeyValuePair> kvp in _items + foreach (KeyValuePair> kvp in _items .Where(x => { // entry.Value is Lazy and not null, its value may be null @@ -141,6 +140,7 @@ public void ClearOfType(Func predicate) // if T is an interface remove anything that implements that interface // otherwise remove exact types (not inherited types) return (isInterface ? value is T : value.GetType() == typeOfT) + // run predicate on the 'public key' part only, ie without prefix && predicate(x.Key, (T)value); })) @@ -152,7 +152,7 @@ public void ClearOfType(Func predicate) /// public void ClearByKey(string keyStartsWith) { - foreach (KeyValuePair> ikvp in _items + foreach (KeyValuePair> ikvp in _items .Where(kvp => kvp.Key.InvariantStartsWith(keyStartsWith))) { _items.TryRemove(ikvp.Key, out _); @@ -163,7 +163,7 @@ public void ClearByKey(string keyStartsWith) public void ClearByRegex(string regex) { var compiled = new Regex(regex, RegexOptions.Compiled); - foreach (KeyValuePair> ikvp in _items + foreach (KeyValuePair> ikvp in _items .Where(kvp => compiled.IsMatch(kvp.Key))) { _items.TryRemove(ikvp.Key, out _); diff --git a/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs b/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs index 3db980d12a73..fe7acb614f22 100644 --- a/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs +++ b/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs @@ -43,7 +43,7 @@ public virtual IEnumerable SearchByKey(string keyStartsWith) { EnterReadLock(); entries = GetDictionaryEntries() - .Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith)) + .Where(x => ((string)x.Key)[plen..].InvariantStartsWith(keyStartsWith)) .ToArray(); // evaluate while locked } finally @@ -67,7 +67,7 @@ public virtual IEnumerable SearchByKey(string keyStartsWith) { EnterReadLock(); entries = GetDictionaryEntries() - .Where(x => compiled.IsMatch(((string)x.Key).Substring(plen))) + .Where(x => compiled.IsMatch(((string)x.Key)[plen..])) .ToArray(); // evaluate while locked } finally @@ -113,7 +113,7 @@ public virtual void Clear(string key) } /// - public virtual void ClearOfType(Type type) + public virtual void ClearOfType(Type? type) { if (type == null) { @@ -205,8 +205,9 @@ public virtual void ClearOfType(Func predicate) // if T is an interface remove anything that implements that interface // otherwise remove exact types (not inherited types) return (isInterface ? value is T : value.GetType() == typeOfT) + // run predicate on the 'public key' part only, ie without prefix - && predicate(((string)x.Key).Substring(plen), (T)value); + && predicate(((string)x.Key)[plen..], (T)value); })) { RemoveEntry((string)entry.Key); @@ -226,7 +227,7 @@ public virtual void ClearByKey(string keyStartsWith) { EnterWriteLock(); foreach (KeyValuePair entry in GetDictionaryEntries() - .Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith)) + .Where(x => ((string)x.Key)[plen..].InvariantStartsWith(keyStartsWith)) .ToArray()) { RemoveEntry((string)entry.Key); @@ -247,7 +248,7 @@ public virtual void ClearByRegex(string regex) { EnterWriteLock(); foreach (KeyValuePair entry in GetDictionaryEntries() - .Where(x => compiled.IsMatch(((string)x.Key).Substring(plen))) + .Where(x => compiled.IsMatch(((string)x.Key)[plen..])) .ToArray()) { RemoveEntry((string)entry.Key); @@ -267,16 +268,20 @@ public virtual void ClearByRegex(string regex) // these *must* be called from within the appropriate locks // and use the full prefixed cache keys protected abstract IEnumerable> GetDictionaryEntries(); + protected abstract void RemoveEntry(string key); + protected abstract object? GetEntry(string key); // read-write lock the underlying cache - //protected abstract IDisposable ReadLock { get; } - //protected abstract IDisposable WriteLock { get; } - + // protected abstract IDisposable ReadLock { get; } + // protected abstract IDisposable WriteLock { get; } protected abstract void EnterReadLock(); + protected abstract void ExitReadLock(); + protected abstract void EnterWriteLock(); + protected abstract void ExitWriteLock(); protected string GetCacheKey(string key) => $"{CacheItemPrefix}-{key}"; diff --git a/src/Umbraco.Core/Cache/IAppCache.cs b/src/Umbraco.Core/Cache/IAppCache.cs index c223015c7a7f..187ff6fc1134 100644 --- a/src/Umbraco.Core/Cache/IAppCache.cs +++ b/src/Umbraco.Core/Cache/IAppCache.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Cache; +namespace Umbraco.Cms.Core.Cache; /// /// Defines an application cache. diff --git a/src/Umbraco.Core/Cache/IAppPolicyCache.cs b/src/Umbraco.Core/Cache/IAppPolicyCache.cs index 930685563489..1d0044c057bc 100644 --- a/src/Umbraco.Core/Cache/IAppPolicyCache.cs +++ b/src/Umbraco.Core/Cache/IAppPolicyCache.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Cache; +namespace Umbraco.Cms.Core.Cache; /// /// Defines an application cache that support cache policies. diff --git a/src/Umbraco.Core/Cache/ICacheRefresher.cs b/src/Umbraco.Core/Cache/ICacheRefresher.cs index ccd5de268144..dba0cd3b3fa4 100644 --- a/src/Umbraco.Core/Cache/ICacheRefresher.cs +++ b/src/Umbraco.Core/Cache/ICacheRefresher.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Cache; @@ -8,10 +8,15 @@ namespace Umbraco.Cms.Core.Cache; public interface ICacheRefresher : IDiscoverable { Guid RefresherUniqueId { get; } + string Name { get; } + void RefreshAll(); + void Refresh(int id); + void Remove(int id); + void Refresh(Guid id); } @@ -27,5 +32,6 @@ public interface ICacheRefresher : IDiscoverable public interface ICacheRefresher : ICacheRefresher { void Refresh(T instance); + void Remove(T instance); } diff --git a/src/Umbraco.Core/Cache/ICacheRefresherNotificationFactory.cs b/src/Umbraco.Core/Cache/ICacheRefresherNotificationFactory.cs index 15a92ae24649..35eb7a279c4c 100644 --- a/src/Umbraco.Core/Cache/ICacheRefresherNotificationFactory.cs +++ b/src/Umbraco.Core/Cache/ICacheRefresherNotificationFactory.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Cache; @@ -9,9 +9,9 @@ namespace Umbraco.Cms.Core.Cache; public interface ICacheRefresherNotificationFactory { /// - /// Creates a + /// Creates a /// - /// The to create + /// The to create TNotification Create(object msgObject, MessageType type) where TNotification : CacheRefresherNotification; } diff --git a/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs b/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs index 120414cfd5bf..d01bf617fd61 100644 --- a/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/IJsonCacheRefresher.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Cache; +namespace Umbraco.Cms.Core.Cache; /// /// A cache refresher that supports refreshing or removing cache based on a custom Json payload diff --git a/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs b/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs index 150ce18a10bf..426481ea0a08 100644 --- a/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/IPayloadCacheRefresher.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Cache; +namespace Umbraco.Cms.Core.Cache; /// /// A cache refresher that supports refreshing cache based on a custom payload diff --git a/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs index e4efb3316e00..4352f9be31df 100644 --- a/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Cache; diff --git a/src/Umbraco.Core/Cache/IRequestCache.cs b/src/Umbraco.Core/Cache/IRequestCache.cs index 50b55abd93fa..f88bc3bb249c 100644 --- a/src/Umbraco.Core/Cache/IRequestCache.cs +++ b/src/Umbraco.Core/Cache/IRequestCache.cs @@ -8,5 +8,6 @@ public interface IRequestCache : IAppCache, IEnumerable dataTypeIds); } diff --git a/src/Umbraco.Core/Cache/IsolatedCaches.cs b/src/Umbraco.Core/Cache/IsolatedCaches.cs index 670c2e15c243..31dc6fe09573 100644 --- a/src/Umbraco.Core/Cache/IsolatedCaches.cs +++ b/src/Umbraco.Core/Cache/IsolatedCaches.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Cache; +namespace Umbraco.Cms.Core.Cache; /// /// Represents a dictionary of for types. diff --git a/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs index 7bef322b416f..b22cff56d207 100644 --- a/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs @@ -8,7 +8,6 @@ namespace Umbraco.Cms.Core.Cache; /// /// A base class for "json" cache refreshers. /// -/// The actual cache refresher type. /// The actual cache refresher type is used for strongly typed events. public abstract class JsonCacheRefresherBase : CacheRefresherBase, IJsonCacheRefresher @@ -17,7 +16,6 @@ public abstract class JsonCacheRefresherBase : Cach /// /// Initializes a new instance of the . /// - /// A cache helper. protected JsonCacheRefresherBase( AppCaches appCaches, IJsonSerializer jsonSerializer, @@ -44,7 +42,6 @@ public virtual void Refresh(string json) => /// The deserialized object payload. public TJsonPayload[]? Deserialize(string json) => JsonSerializer.Deserialize(json); - public string Serialize(params TJsonPayload[] jsonPayloads) => JsonSerializer.Serialize(jsonPayloads); #endregion diff --git a/src/Umbraco.Core/Cache/MacroCacheRefresher.cs b/src/Umbraco.Core/Cache/MacroCacheRefresher.cs index c76624034b8f..6d984237fd7e 100644 --- a/src/Umbraco.Core/Cache/MacroCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MacroCacheRefresher.cs @@ -61,7 +61,7 @@ public override void RefreshAll() public override void Refresh(string json) { - JsonPayload[] payloads = Deserialize(json); + JsonPayload[]? payloads = Deserialize(json); if (payloads is not null) { @@ -99,7 +99,7 @@ internal static string[] GetAllMacroCacheKeys() => new[] { CacheKeys.MacroContentCacheKey, // macro render cache - CacheKeys.MacroFromAliasCacheKey // lookup macro by alias + CacheKeys.MacroFromAliasCacheKey, // lookup macro by alias }; internal static string[] GetCacheKeysForAlias(string alias) => diff --git a/src/Umbraco.Core/Cache/MediaCacheRefresher.cs b/src/Umbraco.Core/Cache/MediaCacheRefresher.cs index 8aba64ceb2d9..5ba07f1ae570 100644 --- a/src/Umbraco.Core/Cache/MediaCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MediaCacheRefresher.cs @@ -16,8 +16,12 @@ public sealed class private readonly IIdKeyMap _idKeyMap; private readonly IPublishedSnapshotService _publishedSnapshotService; - public MediaCacheRefresher(AppCaches appCaches, IJsonSerializer serializer, - IPublishedSnapshotService publishedSnapshotService, IIdKeyMap idKeyMap, IEventAggregator eventAggregator, + public MediaCacheRefresher( + AppCaches appCaches, + IJsonSerializer serializer, + IPublishedSnapshotService publishedSnapshotService, + IIdKeyMap idKeyMap, + IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) : base(appCaches, serializer, eventAggregator, factory) { @@ -43,7 +47,9 @@ public JsonPayload(int id, Guid? key, TreeChangeTypes changeTypes) } public int Id { get; } + public Guid? Key { get; } + public TreeChangeTypes ChangeTypes { get; } } @@ -61,7 +67,7 @@ public JsonPayload(int id, Guid? key, TreeChangeTypes changeTypes) #region Refresher - public override void Refresh(JsonPayload[] payloads) + public override void Refresh(JsonPayload[]? payloads) { if (payloads == null) { @@ -75,7 +81,7 @@ public override void Refresh(JsonPayload[] payloads) AppCaches.ClearPartialViewCache(); AppCaches.RuntimeCache.ClearByKey(CacheKeys.MediaRecycleBinCacheKey); - Attempt mediaCache = AppCaches.IsolatedCaches.Get(); + Attempt mediaCache = AppCaches.IsolatedCaches.Get(); foreach (JsonPayload payload in payloads) { @@ -109,7 +115,6 @@ public override void Refresh(JsonPayload[] payloads) // these events should never trigger // everything should be JSON - public override void RefreshAll() => throw new NotSupportedException(); public override void Refresh(int id) => throw new NotSupportedException(); diff --git a/src/Umbraco.Core/CodeAnnotations/FriendlyNameAttribute.cs b/src/Umbraco.Core/CodeAnnotations/FriendlyNameAttribute.cs index 1c1cc793e4f2..12e95f1e042f 100644 --- a/src/Umbraco.Core/CodeAnnotations/FriendlyNameAttribute.cs +++ b/src/Umbraco.Core/CodeAnnotations/FriendlyNameAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.CodeAnnotations; +namespace Umbraco.Cms.Core.CodeAnnotations; /// /// Attribute to add a Friendly Name string with an UmbracoObjectType enum value diff --git a/src/Umbraco.Core/Collections/CompositeIntStringKey.cs b/src/Umbraco.Core/Collections/CompositeIntStringKey.cs index 08efb4d70bb4..abbde4f3f05b 100644 --- a/src/Umbraco.Core/Collections/CompositeIntStringKey.cs +++ b/src/Umbraco.Core/Collections/CompositeIntStringKey.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Collections; +namespace Umbraco.Cms.Core.Collections; /// /// Represents a composite key of (int, string) for fast dictionaries. @@ -16,7 +16,7 @@ public struct CompositeIntStringKey : IEquatable /// /// Initializes a new instance of the struct. /// - public CompositeIntStringKey(int? key1, string key2) + public CompositeIntStringKey(int? key1, string? key2) { if (key1 < 0) { @@ -27,6 +27,12 @@ public CompositeIntStringKey(int? key1, string key2) _key2 = key2?.ToLowerInvariant() ?? "NULL"; } + public static bool operator ==(CompositeIntStringKey key1, CompositeIntStringKey key2) + => key1._key2 == key2._key2 && key1._key1 == key2._key1; + + public static bool operator !=(CompositeIntStringKey key1, CompositeIntStringKey key2) + => key1._key2 != key2._key2 || key1._key1 != key2._key1; + public bool Equals(CompositeIntStringKey other) => _key2 == other._key2 && _key1 == other._key1; @@ -35,10 +41,4 @@ public override bool Equals(object? obj) public override int GetHashCode() => (_key2.GetHashCode() * 31) + _key1; - - public static bool operator ==(CompositeIntStringKey key1, CompositeIntStringKey key2) - => key1._key2 == key2._key2 && key1._key1 == key2._key1; - - public static bool operator !=(CompositeIntStringKey key1, CompositeIntStringKey key2) - => key1._key2 != key2._key2 || key1._key1 != key2._key1; } diff --git a/src/Umbraco.Core/Collections/CompositeNStringNStringKey.cs b/src/Umbraco.Core/Collections/CompositeNStringNStringKey.cs index 70d17207f880..0b3ec1aa929b 100644 --- a/src/Umbraco.Core/Collections/CompositeNStringNStringKey.cs +++ b/src/Umbraco.Core/Collections/CompositeNStringNStringKey.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Collections; +namespace Umbraco.Cms.Core.Collections; /// /// Represents a composite key of (string, string) for fast dictionaries. @@ -21,6 +21,12 @@ public CompositeNStringNStringKey(string? key1, string? key2) _key2 = key2?.ToLowerInvariant() ?? "NULL"; } + public static bool operator ==(CompositeNStringNStringKey key1, CompositeNStringNStringKey key2) + => key1._key2 == key2._key2 && key1._key1 == key2._key1; + + public static bool operator !=(CompositeNStringNStringKey key1, CompositeNStringNStringKey key2) + => key1._key2 != key2._key2 || key1._key1 != key2._key1; + public bool Equals(CompositeNStringNStringKey other) => _key2 == other._key2 && _key1 == other._key1; @@ -29,10 +35,4 @@ public override bool Equals(object? obj) public override int GetHashCode() => (_key2.GetHashCode() * 31) + _key1.GetHashCode(); - - public static bool operator ==(CompositeNStringNStringKey key1, CompositeNStringNStringKey key2) - => key1._key2 == key2._key2 && key1._key1 == key2._key1; - - public static bool operator !=(CompositeNStringNStringKey key1, CompositeNStringNStringKey key2) - => key1._key2 != key2._key2 || key1._key1 != key2._key1; } diff --git a/src/Umbraco.Core/Collections/CompositeStringStringKey.cs b/src/Umbraco.Core/Collections/CompositeStringStringKey.cs index 333192ac8751..6fd25f6c1212 100644 --- a/src/Umbraco.Core/Collections/CompositeStringStringKey.cs +++ b/src/Umbraco.Core/Collections/CompositeStringStringKey.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Collections; +namespace Umbraco.Cms.Core.Collections; /// /// Represents a composite key of (string, string) for fast dictionaries. @@ -21,6 +21,12 @@ public CompositeStringStringKey(string? key1, string? key2) _key2 = key2?.ToLowerInvariant() ?? throw new ArgumentNullException(nameof(key2)); } + public static bool operator ==(CompositeStringStringKey key1, CompositeStringStringKey key2) + => key1._key2 == key2._key2 && key1._key1 == key2._key1; + + public static bool operator !=(CompositeStringStringKey key1, CompositeStringStringKey key2) + => key1._key2 != key2._key2 || key1._key1 != key2._key1; + public bool Equals(CompositeStringStringKey other) => _key2 == other._key2 && _key1 == other._key1; @@ -29,10 +35,4 @@ public override bool Equals(object? obj) public override int GetHashCode() => (_key2.GetHashCode() * 31) + _key1.GetHashCode(); - - public static bool operator ==(CompositeStringStringKey key1, CompositeStringStringKey key2) - => key1._key2 == key2._key2 && key1._key1 == key2._key1; - - public static bool operator !=(CompositeStringStringKey key1, CompositeStringStringKey key2) - => key1._key2 != key2._key2 || key1._key1 != key2._key1; } diff --git a/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs b/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs index 820f1bb191a7..ea9a5a496f42 100644 --- a/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs +++ b/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Collections; +namespace Umbraco.Cms.Core.Collections; /// /// Represents a composite key of (Type, Type) for fast dictionaries. @@ -25,6 +25,12 @@ public CompositeTypeTypeKey(Type type1, Type type2) /// public Type Type2 { get; } + public static bool operator ==(CompositeTypeTypeKey key1, CompositeTypeTypeKey key2) => + key1.Type1 == key2.Type1 && key1.Type2 == key2.Type2; + + public static bool operator !=(CompositeTypeTypeKey key1, CompositeTypeTypeKey key2) => + key1.Type1 != key2.Type1 || key1.Type2 != key2.Type2; + /// public bool Equals(CompositeTypeTypeKey other) => Type1 == other.Type1 && Type2 == other.Type2; @@ -35,12 +41,6 @@ public override bool Equals(object? obj) return Type1 == other.Type1 && Type2 == other.Type2; } - public static bool operator ==(CompositeTypeTypeKey key1, CompositeTypeTypeKey key2) => - key1.Type1 == key2.Type1 && key1.Type2 == key2.Type2; - - public static bool operator !=(CompositeTypeTypeKey key1, CompositeTypeTypeKey key2) => - key1.Type1 != key2.Type1 || key1.Type2 != key2.Type2; - /// public override int GetHashCode() { diff --git a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs index 8a53f826cb0a..a79c61a17364 100644 --- a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs +++ b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; namespace Umbraco.Cms.Core.Collections; @@ -14,6 +14,32 @@ public class ConcurrentHashSet : ICollection private readonly HashSet _innerSet = new(); private readonly ReaderWriterLockSlim _instanceLocker = new(LockRecursionPolicy.NoRecursion); + /// + /// Gets the number of elements contained in the . + /// + /// + /// The number of elements contained in the . + /// + /// 2 + public int Count + { + get + { + try + { + _instanceLocker.EnterReadLock(); + return _innerSet.Count; + } + finally + { + if (_instanceLocker.IsReadLockHeld) + { + _instanceLocker.ExitReadLock(); + } + } + } + } + /// /// Returns an enumerator that iterates through the collection. /// @@ -62,33 +88,6 @@ public bool Remove(T item) } } - - /// - /// Gets the number of elements contained in the . - /// - /// - /// The number of elements contained in the . - /// - /// 2 - public int Count - { - get - { - try - { - _instanceLocker.EnterReadLock(); - return _innerSet.Count; - } - finally - { - if (_instanceLocker.IsReadLockHeld) - { - _instanceLocker.ExitReadLock(); - } - } - } - } - /// /// Gets a value indicating whether the is read-only. /// @@ -211,7 +210,7 @@ public bool TryAdd(T item) { _instanceLocker.EnterWriteLock(); - //double check + // double check if (_innerSet.Contains(item)) { return false; @@ -229,25 +228,6 @@ public bool TryAdd(T item) } } - private HashSet GetThreadSafeClone() - { - HashSet? clone = null; - try - { - _instanceLocker.EnterReadLock(); - clone = new HashSet(_innerSet, _innerSet.Comparer); - } - finally - { - if (_instanceLocker.IsReadLockHeld) - { - _instanceLocker.ExitReadLock(); - } - } - - return clone; - } - /// /// Copies the elements of the to an , /// starting at a particular index. @@ -275,4 +255,23 @@ public void CopyTo(Array array, int index) HashSet clone = GetThreadSafeClone(); Array.Copy(clone.ToArray(), 0, array, index, clone.Count); } + + private HashSet GetThreadSafeClone() + { + HashSet? clone = null; + try + { + _instanceLocker.EnterReadLock(); + clone = new HashSet(_innerSet, _innerSet.Comparer); + } + finally + { + if (_instanceLocker.IsReadLockHeld) + { + _instanceLocker.ExitReadLock(); + } + } + + return clone; + } } diff --git a/src/Umbraco.Core/Collections/DeepCloneableList.cs b/src/Umbraco.Core/Collections/DeepCloneableList.cs index cff34be3b864..301795281c66 100644 --- a/src/Umbraco.Core/Collections/DeepCloneableList.cs +++ b/src/Umbraco.Core/Collections/DeepCloneableList.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; @@ -14,7 +14,8 @@ public class DeepCloneableList : List, IDeepCloneable, IRememberBeingDirty public DeepCloneableList(ListCloneBehavior listCloneBehavior) => _listCloneBehavior = listCloneBehavior; - public DeepCloneableList(IEnumerable collection, ListCloneBehavior listCloneBehavior) : base(collection) => + public DeepCloneableList(IEnumerable collection, ListCloneBehavior listCloneBehavior) + : base(collection) => _listCloneBehavior = listCloneBehavior; /// @@ -26,6 +27,8 @@ public DeepCloneableList(IEnumerable collection) { } + public event PropertyChangedEventHandler? PropertyChanged; // noop + /// /// Creates a new list and adds each element as a deep cloned element if it is of type IDeepCloneable /// @@ -35,7 +38,7 @@ public object DeepClone() switch (_listCloneBehavior) { case ListCloneBehavior.CloneOnce: - //we are cloning once, so create a new list in none mode + // we are cloning once, so create a new list in none mode // and deep clone all items into it var newList = new DeepCloneableList(ListCloneBehavior.None); foreach (T item in this) @@ -52,10 +55,10 @@ public object DeepClone() return newList; case ListCloneBehavior.None: - //we are in none mode, so just return a new list with the same items + // we are in none mode, so just return a new list with the same items return new DeepCloneableList(this, ListCloneBehavior.None); case ListCloneBehavior.Always: - //always clone to new list + // always clone to new list var newList2 = new DeepCloneableList(ListCloneBehavior.Always); foreach (T item in this) { @@ -130,7 +133,5 @@ public void ResetDirtyProperties(bool rememberDirty) /// Always return an empty enumerable, the list has no properties that can be dirty. public IEnumerable GetWereDirtyProperties() => Enumerable.Empty(); - public event PropertyChangedEventHandler? PropertyChanged; // noop - #endregion } diff --git a/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs b/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs index 26e50675fe10..579716456bfb 100644 --- a/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs +++ b/src/Umbraco.Core/Collections/EventClearingObservableCollection.cs @@ -1,4 +1,4 @@ -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; using System.Collections.Specialized; namespace Umbraco.Cms.Core.Collections; @@ -19,11 +19,13 @@ public EventClearingObservableCollection() { } - public EventClearingObservableCollection(List list) : base(list) + public EventClearingObservableCollection(List list) + : base(list) { } - public EventClearingObservableCollection(IEnumerable collection) : base(collection) + public EventClearingObservableCollection(IEnumerable collection) + : base(collection) { } diff --git a/src/Umbraco.Core/Collections/ListCloneBehavior.cs b/src/Umbraco.Core/Collections/ListCloneBehavior.cs index 1588d56773eb..4fc9edf3ae03 100644 --- a/src/Umbraco.Core/Collections/ListCloneBehavior.cs +++ b/src/Umbraco.Core/Collections/ListCloneBehavior.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Collections; +namespace Umbraco.Cms.Core.Collections; public enum ListCloneBehavior { @@ -15,5 +15,5 @@ public enum ListCloneBehavior /// /// When set, DeepClone will always clone all items /// - Always + Always, } diff --git a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs index 32f3d33052ab..8b1c33a6100f 100644 --- a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs @@ -50,6 +50,34 @@ public virtual TCollection CreateCollection(IServiceProvider factory) /// public IEnumerable GetTypes() => _types; + /// + /// Gets a value indicating whether the collection contains a type. + /// + /// The type to look for. + /// A value indicating whether the collection contains the type. + /// + /// Some builder implementations may use this to expose a public Has{T}() method, + /// when it makes sense. Probably does not make sense for lazy builders, for example. + /// + public virtual bool Has() + where T : TItem => + _types.Contains(typeof(T)); + + /// + /// Gets a value indicating whether the collection contains a type. + /// + /// The type to look for. + /// A value indicating whether the collection contains the type. + /// + /// Some builder implementations may use this to expose a public Has{T}() method, + /// when it makes sense. Probably does not make sense for lazy builders, for example. + /// + public virtual bool Has(Type type) + { + EnsureType(type, "find"); + return _types.Contains(type); + } + /// /// Configures the internal list of types. /// @@ -77,36 +105,6 @@ protected void Configure(Action> action) /// Used by implementations to add types to the internal list, sort the list, etc. protected virtual IEnumerable GetRegisteringTypes(IEnumerable types) => types; - private void RegisterTypes(IServiceCollection services) - { - lock (_locker) - { - if (_registeredTypes != null) - { - return; - } - - Type[] types = GetRegisteringTypes(_types).ToArray(); - - // ensure they are safe - foreach (Type type in types) - { - EnsureType(type, "register"); - } - - // register them - ensuring that each item is registered with the same lifetime as the collection. - // NOTE: Previously each one was not registered with the same lifetime which would mean that if there - // was a dependency on an individual item, it would resolve a brand new transient instance which isn't what - // we would expect to happen. The same item should be resolved from the container as the collection. - foreach (Type type in types) - { - services.Add(new ServiceDescriptor(type, type, CollectionLifetime)); - } - - _registeredTypes = types; - } - } - /// /// Creates the collection items. /// @@ -130,9 +128,6 @@ protected virtual IEnumerable CreateItems(IServiceProvider factory) protected virtual TItem CreateItem(IServiceProvider factory, Type itemType) => (TItem)factory.GetRequiredService(itemType); - // used to resolve a Func> parameter - private Func> CreateItemsFactory(IServiceProvider factory) => () => CreateItems(factory); - protected Type EnsureType(Type type, string action) { if (typeof(TItem).IsAssignableFrom(type) == false) @@ -144,31 +139,36 @@ protected Type EnsureType(Type type, string action) return type; } - /// - /// Gets a value indicating whether the collection contains a type. - /// - /// The type to look for. - /// A value indicating whether the collection contains the type. - /// - /// Some builder implementations may use this to expose a public Has{T}() method, - /// when it makes sense. Probably does not make sense for lazy builders, for example. - /// - public virtual bool Has() - where T : TItem => - _types.Contains(typeof(T)); - - /// - /// Gets a value indicating whether the collection contains a type. - /// - /// The type to look for. - /// A value indicating whether the collection contains the type. - /// - /// Some builder implementations may use this to expose a public Has{T}() method, - /// when it makes sense. Probably does not make sense for lazy builders, for example. - /// - public virtual bool Has(Type type) + private void RegisterTypes(IServiceCollection services) { - EnsureType(type, "find"); - return _types.Contains(type); + lock (_locker) + { + if (_registeredTypes != null) + { + return; + } + + Type[] types = GetRegisteringTypes(_types).ToArray(); + + // ensure they are safe + foreach (Type type in types) + { + EnsureType(type, "register"); + } + + // register them - ensuring that each item is registered with the same lifetime as the collection. + // NOTE: Previously each one was not registered with the same lifetime which would mean that if there + // was a dependency on an individual item, it would resolve a brand new transient instance which isn't what + // we would expect to happen. The same item should be resolved from the container as the collection. + foreach (Type type in types) + { + services.Add(new ServiceDescriptor(type, type, CollectionLifetime)); + } + + _registeredTypes = types; + } } + + // used to resolve a Func> parameter + private Func> CreateItemsFactory(IServiceProvider factory) => () => CreateItems(factory); } diff --git a/src/Umbraco.Core/Composing/ComponentCollection.cs b/src/Umbraco.Core/Composing/ComponentCollection.cs index 1b73e88a4d1a..d64de626d026 100644 --- a/src/Umbraco.Core/Composing/ComponentCollection.cs +++ b/src/Umbraco.Core/Composing/ComponentCollection.cs @@ -14,8 +14,7 @@ public class ComponentCollection : BuilderCollectionBase private readonly IProfilingLogger _profilingLogger; - public ComponentCollection(Func> items, IProfilingLogger profilingLogger, - ILogger logger) + public ComponentCollection(Func> items, IProfilingLogger profilingLogger, ILogger logger) : base(items) { _profilingLogger = profilingLogger; @@ -30,8 +29,10 @@ public void Initialize() foreach (IComponent component in this) { Type componentType = component.GetType(); - using (_profilingLogger.DebugDuration($"Initializing {componentType.FullName}.", - $"Initialized {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) + using (_profilingLogger.DebugDuration( + $"Initializing {componentType.FullName}.", + $"Initialized {componentType.FullName}.", + thresholdMilliseconds: LogThresholdMilliseconds)) { component.Initialize(); } @@ -44,11 +45,14 @@ public void Terminate() using (_profilingLogger.DebugDuration( $"Terminating. (log components when >{LogThresholdMilliseconds}ms)", "Terminated.")) { - foreach (IComponent component in this.Reverse()) // terminate components in reverse order + // terminate components in reverse order + foreach (IComponent component in this.Reverse()) { Type componentType = component.GetType(); - using (_profilingLogger.DebugDuration($"Terminating {componentType.FullName}.", - $"Terminated {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) + using (_profilingLogger.DebugDuration( + $"Terminating {componentType.FullName}.", + $"Terminated {componentType.FullName}.", + thresholdMilliseconds: LogThresholdMilliseconds)) { try { @@ -57,8 +61,7 @@ public void Terminate() } catch (Exception ex) { - _logger.LogError(ex, "Error while terminating component {ComponentType}.", - componentType.FullName); + _logger.LogError(ex, "Error while terminating component {ComponentType}.", componentType.FullName); } } } diff --git a/src/Umbraco.Core/Composing/ComponentCollectionBuilder.cs b/src/Umbraco.Core/Composing/ComponentCollectionBuilder.cs index c179312f51f9..b77dfde819a1 100644 --- a/src/Umbraco.Core/Composing/ComponentCollectionBuilder.cs +++ b/src/Umbraco.Core/Composing/ComponentCollectionBuilder.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Logging; namespace Umbraco.Cms.Core.Composing; @@ -29,8 +29,10 @@ protected override IComponent CreateItem(IServiceProvider factory, Type itemType { IProfilingLogger logger = factory.GetRequiredService(); - using (logger.DebugDuration($"Creating {itemType.FullName}.", - $"Created {itemType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds)) + using (logger.DebugDuration( + $"Creating {itemType.FullName}.", + $"Created {itemType.FullName}.", + thresholdMilliseconds: LogThresholdMilliseconds)) { return base.CreateItem(factory, itemType); } diff --git a/src/Umbraco.Core/Composing/ComponentComposer.cs b/src/Umbraco.Core/Composing/ComponentComposer.cs index a0bc86ccef96..8cb073728146 100644 --- a/src/Umbraco.Core/Composing/ComponentComposer.cs +++ b/src/Umbraco.Core/Composing/ComponentComposer.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; namespace Umbraco.Cms.Core.Composing; diff --git a/src/Umbraco.Core/Composing/ComposeAfterAttribute.cs b/src/Umbraco.Core/Composing/ComposeAfterAttribute.cs index 4b7979193411..bd3567595d83 100644 --- a/src/Umbraco.Core/Composing/ComposeAfterAttribute.cs +++ b/src/Umbraco.Core/Composing/ComposeAfterAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Composing; +namespace Umbraco.Cms.Core.Composing; /// /// Indicates that a composer requires another composer. diff --git a/src/Umbraco.Core/Composing/ComposeBeforeAttribute.cs b/src/Umbraco.Core/Composing/ComposeBeforeAttribute.cs index 1f060558dd66..c41f1e50742e 100644 --- a/src/Umbraco.Core/Composing/ComposeBeforeAttribute.cs +++ b/src/Umbraco.Core/Composing/ComposeBeforeAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Composing; +namespace Umbraco.Cms.Core.Composing; /// /// Indicates that a component is required by another composer. diff --git a/src/Umbraco.Core/Composing/ComposerGraph.cs b/src/Umbraco.Core/Composing/ComposerGraph.cs index 701150bf6500..b675180e3950 100644 --- a/src/Umbraco.Core/Composing/ComposerGraph.cs +++ b/src/Umbraco.Core/Composing/ComposerGraph.cs @@ -5,6 +5,7 @@ using Umbraco.Cms.Core.DependencyInjection; namespace Umbraco.Cms.Core.Composing; + // note: this class is NOT thread-safe in any way /// @@ -36,8 +37,7 @@ internal class ComposerGraph /// or /// logger /// - public ComposerGraph(IUmbracoBuilder builder, IEnumerable composerTypes, - IEnumerable enableDisableAttributes, ILogger logger) + public ComposerGraph(IUmbracoBuilder builder, IEnumerable composerTypes, IEnumerable enableDisableAttributes, ILogger logger) { _builder = builder ?? throw new ArgumentNullException(nameof(builder)); _composerTypes = composerTypes ?? throw new ArgumentNullException(nameof(composerTypes)); @@ -62,17 +62,70 @@ public void Compose() } } + internal static string GetComposersReport(Dictionary?> requirements) + { + var text = new StringBuilder(); + text.AppendLine("Composers & Dependencies:"); + text.AppendLine(" < compose before"); + text.AppendLine(" > compose after"); + text.AppendLine(" : implements"); + text.AppendLine(" = depends"); + text.AppendLine(); + + bool HasReq(IEnumerable types, Type type) + { + return types.Any(x => type.IsAssignableFrom(x) && !x.IsInterface); + } + + foreach (KeyValuePair?> kvp in requirements) + { + Type type = kvp.Key; + + text.AppendLine(type.FullName); + foreach (ComposeAfterAttribute attribute in type.GetCustomAttributes()) + { + var weak = !(attribute.RequiredType.IsInterface ? attribute.Weak == false : attribute.Weak != true); + text.AppendLine(" > " + attribute.RequiredType + + (weak ? " (weak" : " (strong") + + (HasReq(requirements.Keys, attribute.RequiredType) ? ", found" : ", missing") + ")"); + } + + foreach (ComposeBeforeAttribute attribute in type.GetCustomAttributes()) + { + text.AppendLine(" < " + attribute.RequiringType); + } + + foreach (Type i in type.GetInterfaces()) + { + text.AppendLine(" : " + i.FullName); + } + + if (kvp.Value != null) + { + foreach (Type t in kvp.Value) + { + text.AppendLine(" = " + t); + } + } + + text.AppendLine(); + } + + text.AppendLine("/"); + text.AppendLine(); + return text.ToString(); + } + internal IEnumerable PrepareComposerTypes() { - Dictionary> requirements = GetRequirements(); + Dictionary?> requirements = GetRequirements(); // only for debugging, this is verbose - //_logger.Debug(GetComposersReport(requirements)); - + // _logger.Debug(GetComposersReport(requirements)); IEnumerable sortedComposerTypes = SortComposers(requirements); // bit verbose but should help for troubleshooting - //var text = "Ordered Composers: " + Environment.NewLine + string.Join(Environment.NewLine, sortedComposerTypes) + Environment.NewLine; + // var text = "Ordered Composers: " + Environment.NewLine + string.Join(Environment.NewLine, sortedComposerTypes) + Environment.NewLine; _logger.LogDebug("Ordered Composers: {SortedComposerTypes}", sortedComposerTypes); return sortedComposerTypes; @@ -86,8 +139,7 @@ internal IEnumerable PrepareComposerTypes() // enable or disable composers EnableDisableComposers(_enableDisableAttributes, composerTypeList); - void GatherInterfaces(Type type, Func getTypeInAttribute, HashSet iset, - List set2) + static void GatherInterfaces(Type type, Func getTypeInAttribute, HashSet iset, List set2) where TAttribute : Attribute { foreach (TAttribute attribute in type.GetCustomAttributes()) @@ -168,60 +220,6 @@ internal IEnumerable SortComposers(Dictionary?> requireme return sortedComposerTypes; } - internal static string GetComposersReport(Dictionary?> requirements) - { - var text = new StringBuilder(); - text.AppendLine("Composers & Dependencies:"); - text.AppendLine(" < compose before"); - text.AppendLine(" > compose after"); - text.AppendLine(" : implements"); - text.AppendLine(" = depends"); - text.AppendLine(); - - bool HasReq(IEnumerable types, Type type) - { - return types.Any(x => type.IsAssignableFrom(x) && !x.IsInterface); - } - - foreach (KeyValuePair> kvp in requirements) - { - Type type = kvp.Key; - - text.AppendLine(type.FullName); - foreach (ComposeAfterAttribute attribute in type.GetCustomAttributes()) - { - var weak = !(attribute.RequiredType.IsInterface ? attribute.Weak == false : attribute.Weak != true); - text.AppendLine(" > " + attribute.RequiredType + - (weak ? " (weak" : " (strong") + - (HasReq(requirements.Keys, attribute.RequiredType) ? ", found" : ", missing") + ")"); - } - - foreach (ComposeBeforeAttribute attribute in type.GetCustomAttributes()) - { - text.AppendLine(" < " + attribute.RequiringType); - } - - foreach (Type i in type.GetInterfaces()) - { - text.AppendLine(" : " + i.FullName); - } - - if (kvp.Value != null) - { - foreach (Type t in kvp.Value) - { - text.AppendLine(" = " + t); - } - } - - text.AppendLine(); - } - - text.AppendLine("/"); - text.AppendLine(); - return text.ToString(); - } - private static void EnableDisableComposers(IEnumerable enableDisableAttributes, ICollection types) { var enabled = new Dictionary(); @@ -234,10 +232,9 @@ private static void EnableDisableComposers(IEnumerable enableDisableA // what happens in case of conflicting remote declarations is unspecified. more // precisely, the last declaration to be processed wins, but the order of the // declarations depends on the type finder and is unspecified. - void UpdateEnableInfo(Type composerType, int weight2, Dictionary enabled2, bool value) { - if (enabled.TryGetValue(composerType, out EnableInfo enableInfo) == false) + if (enabled.TryGetValue(composerType, out EnableInfo? enableInfo) == false) { enableInfo = enabled2[composerType] = new EnableInfo(); } @@ -287,8 +284,7 @@ void UpdateEnableInfo(Type composerType, int weight2, Dictionary types, - IDictionary?> requirements, bool throwOnMissing = true) + private static void GatherRequirementsFromAfterAttribute(Type type, ICollection types, IDictionary?> requirements, bool throwOnMissing = true) { // get 'require' attributes // these attributes are *not* inherited because we want to "custom-inherit" for interfaces only @@ -325,6 +321,7 @@ private static void GatherRequirementsFromAfterAttribute(Type type, ICollection< $"Broken composer dependency: {type.FullName} -> {attr.RequiredType.FullName}."); } } + // requiring a class = require that the composer is enabled // unless weak, and then requires it if it is enabled else @@ -347,8 +344,7 @@ private static void GatherRequirementsFromAfterAttribute(Type type, ICollection< } } - private static void GatherRequirementsFromBeforeAttribute(Type type, ICollection types, - IDictionary?> requirements) + private static void GatherRequirementsFromBeforeAttribute(Type type, ICollection types, IDictionary?> requirements) { // get 'required' attributes // these attributes are *not* inherited because we want to "custom-inherit" for interfaces only @@ -379,6 +375,7 @@ private static void GatherRequirementsFromBeforeAttribute(Type type, ICollection requirements[implem]!.Add(type); } } + // required by a class else { @@ -414,6 +411,7 @@ private static IEnumerable InstantiateComposers(IEnumerable typ private class EnableInfo { public bool Enabled { get; set; } + public int Weight { get; set; } = -1; } } diff --git a/src/Umbraco.Core/Composing/CompositionExtensions.cs b/src/Umbraco.Core/Composing/CompositionExtensions.cs index 7c93eaf99454..2906070e4f1e 100644 --- a/src/Umbraco.Core/Composing/CompositionExtensions.cs +++ b/src/Umbraco.Core/Composing/CompositionExtensions.cs @@ -10,7 +10,8 @@ public static class CompositionExtensions /// /// The builder. /// A function creating a published snapshot service. - public static IUmbracoBuilder SetPublishedSnapshotService(this IUmbracoBuilder builder, + public static IUmbracoBuilder SetPublishedSnapshotService( + this IUmbracoBuilder builder, Func factory) { builder.Services.AddUnique(factory); @@ -34,7 +35,8 @@ public static IUmbracoBuilder SetPublishedSnapshotService(this IUmbracoBuilde /// /// The builder. /// A published snapshot service. - public static IUmbracoBuilder SetPublishedSnapshotService(this IUmbracoBuilder builder, + public static IUmbracoBuilder SetPublishedSnapshotService( + this IUmbracoBuilder builder, IPublishedSnapshotService service) { builder.Services.AddUnique(service); diff --git a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs index 5188c095a6c2..0f1d0cc571b0 100644 --- a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs +++ b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs @@ -34,7 +34,6 @@ public DefaultUmbracoAssemblyProvider( // that will still only resolve Assemblies that are already loaded but it would also make it possible to // query dynamically generated assemblies once they are added. It would also provide the ability to probe // assembly locations that are not in the same place as the entry point assemblies. - public IEnumerable Assemblies { get @@ -50,8 +49,11 @@ public IEnumerable Assemblies additionalTargetAssemblies = additionalTargetAssemblies.Concat(_additionalTargetAssemblies); } - var finder = new FindAssembliesWithReferencesTo(new[] {_entryPointAssembly}, - additionalTargetAssemblies.ToArray(), true, _loggerFactory); + var finder = new FindAssembliesWithReferencesTo( + new[] { _entryPointAssembly }, + additionalTargetAssemblies.ToArray(), + true, + _loggerFactory); _discovered = finder.Find().ToList(); return _discovered; diff --git a/src/Umbraco.Core/Composing/DisableAttribute.cs b/src/Umbraco.Core/Composing/DisableAttribute.cs index 9823542f685c..09d638188da2 100644 --- a/src/Umbraco.Core/Composing/DisableAttribute.cs +++ b/src/Umbraco.Core/Composing/DisableAttribute.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; namespace Umbraco.Cms.Core.Composing; diff --git a/src/Umbraco.Core/Composing/DisableComposerAttribute.cs b/src/Umbraco.Core/Composing/DisableComposerAttribute.cs index 7ab79612ee38..2c85d45b46e1 100644 --- a/src/Umbraco.Core/Composing/DisableComposerAttribute.cs +++ b/src/Umbraco.Core/Composing/DisableComposerAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Composing; +namespace Umbraco.Cms.Core.Composing; /// /// Indicates that a composer should be disabled. diff --git a/src/Umbraco.Core/Composing/EnableAttribute.cs b/src/Umbraco.Core/Composing/EnableAttribute.cs index 35b02f44c777..7ca33d50b7da 100644 --- a/src/Umbraco.Core/Composing/EnableAttribute.cs +++ b/src/Umbraco.Core/Composing/EnableAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Composing; +namespace Umbraco.Cms.Core.Composing; /// /// Indicates that a composer should be enabled. diff --git a/src/Umbraco.Core/Composing/EnableComposerAttribute.cs b/src/Umbraco.Core/Composing/EnableComposerAttribute.cs index 3ad71fd3f277..b1a0f53bcd11 100644 --- a/src/Umbraco.Core/Composing/EnableComposerAttribute.cs +++ b/src/Umbraco.Core/Composing/EnableComposerAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Composing; +namespace Umbraco.Cms.Core.Composing; /// /// Indicates that a composer should be enabled. diff --git a/src/Umbraco.Core/Composing/HideFromTypeFinderAttribute.cs b/src/Umbraco.Core/Composing/HideFromTypeFinderAttribute.cs index e4ac2eaaa122..4478deb5ac85 100644 --- a/src/Umbraco.Core/Composing/HideFromTypeFinderAttribute.cs +++ b/src/Umbraco.Core/Composing/HideFromTypeFinderAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Composing; +namespace Umbraco.Cms.Core.Composing; /// /// Notifies the TypeFinder that it should ignore the class marked with this attribute. diff --git a/src/Umbraco.Core/Composing/IAssemblyProvider.cs b/src/Umbraco.Core/Composing/IAssemblyProvider.cs index dc6c6c33b792..4148c9ee4788 100644 --- a/src/Umbraco.Core/Composing/IAssemblyProvider.cs +++ b/src/Umbraco.Core/Composing/IAssemblyProvider.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; namespace Umbraco.Cms.Core.Composing; diff --git a/src/Umbraco.Core/Composing/IBuilderCollection.cs b/src/Umbraco.Core/Composing/IBuilderCollection.cs index 2a0e6d0742b0..56036997bc98 100644 --- a/src/Umbraco.Core/Composing/IBuilderCollection.cs +++ b/src/Umbraco.Core/Composing/IBuilderCollection.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Composing; +namespace Umbraco.Cms.Core.Composing; /// /// Represents a builder collection, ie an immutable enumeration of items. diff --git a/src/Umbraco.Core/Composing/ICollectionBuilder.cs b/src/Umbraco.Core/Composing/ICollectionBuilder.cs index aa448c87b502..da25a548e72f 100644 --- a/src/Umbraco.Core/Composing/ICollectionBuilder.cs +++ b/src/Umbraco.Core/Composing/ICollectionBuilder.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; namespace Umbraco.Cms.Core.Composing; diff --git a/src/Umbraco.Core/Composing/IComponent.cs b/src/Umbraco.Core/Composing/IComponent.cs index c5079e6e26c2..d5655f8a1f81 100644 --- a/src/Umbraco.Core/Composing/IComponent.cs +++ b/src/Umbraco.Core/Composing/IComponent.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Composing; +namespace Umbraco.Cms.Core.Composing; /// /// Represents a component. diff --git a/src/Umbraco.Core/Composing/IComposer.cs b/src/Umbraco.Core/Composing/IComposer.cs index 3d9bf2f73319..7d0a85931434 100644 --- a/src/Umbraco.Core/Composing/IComposer.cs +++ b/src/Umbraco.Core/Composing/IComposer.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; namespace Umbraco.Cms.Core.Composing; diff --git a/src/Umbraco.Core/Composing/IDiscoverable.cs b/src/Umbraco.Core/Composing/IDiscoverable.cs index f0e6077d8be4..848c70ddab7c 100644 --- a/src/Umbraco.Core/Composing/IDiscoverable.cs +++ b/src/Umbraco.Core/Composing/IDiscoverable.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Composing; +namespace Umbraco.Cms.Core.Composing; public interface IDiscoverable { diff --git a/src/Umbraco.Core/Composing/IRuntimeHash.cs b/src/Umbraco.Core/Composing/IRuntimeHash.cs index 5cdc0193235f..d641c9053803 100644 --- a/src/Umbraco.Core/Composing/IRuntimeHash.cs +++ b/src/Umbraco.Core/Composing/IRuntimeHash.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Composing; +namespace Umbraco.Cms.Core.Composing; /// /// Used to create a hash value of the current runtime diff --git a/src/Umbraco.Core/Composing/LazyResolve.cs b/src/Umbraco.Core/Composing/LazyResolve.cs index 1c243187c05a..723d9afe2e35 100644 --- a/src/Umbraco.Core/Composing/LazyResolve.cs +++ b/src/Umbraco.Core/Composing/LazyResolve.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; namespace Umbraco.Cms.Core.Composing; diff --git a/src/Umbraco.Core/Configuration/ContentSettingsExtensions.cs b/src/Umbraco.Core/Configuration/ContentSettingsExtensions.cs index 7f89677000ed..315cee462780 100644 --- a/src/Umbraco.Core/Configuration/ContentSettingsExtensions.cs +++ b/src/Umbraco.Core/Configuration/ContentSettingsExtensions.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Configuration.Models; namespace Umbraco.Extensions; diff --git a/src/Umbraco.Core/Configuration/Extensions/HealthCheckSettingsExtensions.cs b/src/Umbraco.Core/Configuration/Extensions/HealthCheckSettingsExtensions.cs index aaf326dc6696..bbf8c67db519 100644 --- a/src/Umbraco.Core/Configuration/Extensions/HealthCheckSettingsExtensions.cs +++ b/src/Umbraco.Core/Configuration/Extensions/HealthCheckSettingsExtensions.cs @@ -1,12 +1,11 @@ -using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; namespace Umbraco.Extensions; public static class HealthCheckSettingsExtensions { - public static TimeSpan GetNotificationDelay(this HealthChecksSettings settings, ICronTabParser cronTabParser, - DateTime now, TimeSpan defaultDelay) + public static TimeSpan GetNotificationDelay(this HealthChecksSettings settings, ICronTabParser cronTabParser, DateTime now, TimeSpan defaultDelay) { // If first run time not set, start with just small delay after application start. var firstRunTime = settings.Notification.FirstRunTime; diff --git a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs index 7f12ad43fdb3..2f49bfd146cd 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; @@ -50,7 +50,8 @@ public static string GetUmbracoMvcArea(this GlobalSettings globalSettings, IHost return _mvcArea; } - internal static string GetUmbracoMvcAreaNoCache(this GlobalSettings globalSettings, + internal static string GetUmbracoMvcAreaNoCache( + this GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) { var path = string.IsNullOrEmpty(globalSettings.UmbracoPath) @@ -62,9 +63,10 @@ internal static string GetUmbracoMvcAreaNoCache(this GlobalSettings globalSettin throw new InvalidOperationException("Cannot create an MVC Area path without the umbracoPath specified"); } - if (path.StartsWith(hostingEnvironment.ApplicationVirtualPath)) // beware of TrimStart, see U4-2518 + // beware of TrimStart, see U4-2518 + if (path.StartsWith(hostingEnvironment.ApplicationVirtualPath)) { - path = path.Substring(hostingEnvironment.ApplicationVirtualPath.Length); + path = path[hostingEnvironment.ApplicationVirtualPath.Length..]; } return path.TrimStart(Constants.CharArrays.Tilde).TrimStart(Constants.CharArrays.ForwardSlash).Replace('/', '-') diff --git a/src/Umbraco.Core/Configuration/Grid/GridConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridConfig.cs index 12cefa01342e..44c9c37dfd8a 100644 --- a/src/Umbraco.Core/Configuration/Grid/GridConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/GridConfig.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Manifest; @@ -8,10 +8,14 @@ namespace Umbraco.Cms.Core.Configuration.Grid; public class GridConfig : IGridConfig { - public GridConfig(AppCaches appCaches, IManifestParser manifestParser, IJsonSerializer jsonSerializer, - IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory) => EditorsConfig = - new GridEditorsConfig(appCaches, hostingEnvironment, manifestParser, jsonSerializer, - loggerFactory.CreateLogger()); + public GridConfig( + AppCaches appCaches, + IManifestParser manifestParser, + IJsonSerializer jsonSerializer, + IHostingEnvironment hostingEnvironment, + ILoggerFactory loggerFactory) + => EditorsConfig = + new GridEditorsConfig(appCaches, hostingEnvironment, manifestParser, jsonSerializer, loggerFactory.CreateLogger()); public IGridEditorsConfig EditorsConfig { get; } } diff --git a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs index de8c1e8de103..11ae329192fb 100644 --- a/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/GridEditorsConfig.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using System.Text; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Cache; @@ -19,8 +19,12 @@ internal class GridEditorsConfig : IGridEditorsConfig private readonly ILogger _logger; private readonly IManifestParser _manifestParser; - public GridEditorsConfig(AppCaches appCaches, IHostingEnvironment hostingEnvironment, - IManifestParser manifestParser, IJsonSerializer jsonSerializer, ILogger logger) + public GridEditorsConfig( + AppCaches appCaches, + IHostingEnvironment hostingEnvironment, + IManifestParser manifestParser, + IJsonSerializer jsonSerializer, + ILogger logger) { _appCaches = appCaches; _hostingEnvironment = hostingEnvironment; @@ -49,15 +53,18 @@ List GetResult() } catch (Exception ex) { - _logger.LogError(ex, + _logger.LogError( + ex, "Could not parse the contents of grid.editors.config.js into a JSON array '{Json}", sourceString); } } - else // Read default from embedded file + + // Read default from embedded file + else { Assembly assembly = GetType().Assembly; - Stream resourceStream = assembly.GetManifestResourceStream( + Stream? resourceStream = assembly.GetManifestResourceStream( "Umbraco.Cms.Core.EmbeddedResources.Grid.grid.editors.config.js"); if (resourceStream is not null) @@ -80,11 +87,10 @@ List GetResult() return editors; } - //cache the result if debugging is disabled - List result = _hostingEnvironment.IsDebugMode + // cache the result if debugging is disabled + List? result = _hostingEnvironment.IsDebugMode ? GetResult() - : _appCaches.RuntimeCache.GetCacheItem(typeof(GridEditorsConfig) + ".Editors", GetResult, - TimeSpan.FromMinutes(10)); + : _appCaches.RuntimeCache.GetCacheItem(typeof(GridEditorsConfig) + ".Editors", GetResult, TimeSpan.FromMinutes(10)); return result!; } diff --git a/src/Umbraco.Core/Configuration/Grid/IGridConfig.cs b/src/Umbraco.Core/Configuration/Grid/IGridConfig.cs index cbbddcacf9f7..4dd11ee1fc1c 100644 --- a/src/Umbraco.Core/Configuration/Grid/IGridConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/IGridConfig.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Configuration.Grid; +namespace Umbraco.Cms.Core.Configuration.Grid; public interface IGridConfig { diff --git a/src/Umbraco.Core/Configuration/Grid/IGridEditorConfig.cs b/src/Umbraco.Core/Configuration/Grid/IGridEditorConfig.cs index 6b411a459087..5103e7a328f7 100644 --- a/src/Umbraco.Core/Configuration/Grid/IGridEditorConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/IGridEditorConfig.cs @@ -1,12 +1,18 @@ -namespace Umbraco.Cms.Core.Configuration.Grid; +namespace Umbraco.Cms.Core.Configuration.Grid; public interface IGridEditorConfig { string? Name { get; } + string? NameTemplate { get; } + string Alias { get; } + string? View { get; } + string? Render { get; } + string? Icon { get; } + IDictionary Config { get; } } diff --git a/src/Umbraco.Core/Configuration/Grid/IGridEditorsConfig.cs b/src/Umbraco.Core/Configuration/Grid/IGridEditorsConfig.cs index f8855dd76588..e0d8c8f8d412 100644 --- a/src/Umbraco.Core/Configuration/Grid/IGridEditorsConfig.cs +++ b/src/Umbraco.Core/Configuration/Grid/IGridEditorsConfig.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Configuration.Grid; +namespace Umbraco.Cms.Core.Configuration.Grid; public interface IGridEditorsConfig { diff --git a/src/Umbraco.Core/Configuration/IConfigManipulator.cs b/src/Umbraco.Core/Configuration/IConfigManipulator.cs index 24ce06960f0b..18ce8a5eca37 100644 --- a/src/Umbraco.Core/Configuration/IConfigManipulator.cs +++ b/src/Umbraco.Core/Configuration/IConfigManipulator.cs @@ -1,10 +1,14 @@ -namespace Umbraco.Cms.Core.Configuration; +namespace Umbraco.Cms.Core.Configuration; public interface IConfigManipulator { void RemoveConnectionString(); + void SaveConnectionString(string connectionString, string? providerName); + void SaveConfigValue(string itemPath, object value); + void SaveDisableRedirectUrlTracking(bool disable); + void SetGlobalId(string id); } diff --git a/src/Umbraco.Core/Configuration/ICronTabParser.cs b/src/Umbraco.Core/Configuration/ICronTabParser.cs index 72b3ab0c07a1..bd3808ecd182 100644 --- a/src/Umbraco.Core/Configuration/ICronTabParser.cs +++ b/src/Umbraco.Core/Configuration/ICronTabParser.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Cms.Core.Configuration; diff --git a/src/Umbraco.Core/Configuration/IMemberPasswordConfiguration.cs b/src/Umbraco.Core/Configuration/IMemberPasswordConfiguration.cs index 2cedb3d569ef..451cf51bc309 100644 --- a/src/Umbraco.Core/Configuration/IMemberPasswordConfiguration.cs +++ b/src/Umbraco.Core/Configuration/IMemberPasswordConfiguration.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Configuration; +namespace Umbraco.Cms.Core.Configuration; /// /// The password configuration for members diff --git a/src/Umbraco.Core/Configuration/ITypeFinderSettings.cs b/src/Umbraco.Core/Configuration/ITypeFinderSettings.cs index c6ff218debec..1680e6fdfbf9 100644 --- a/src/Umbraco.Core/Configuration/ITypeFinderSettings.cs +++ b/src/Umbraco.Core/Configuration/ITypeFinderSettings.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Configuration; +namespace Umbraco.Cms.Core.Configuration; public interface ITypeFinderSettings { diff --git a/src/Umbraco.Core/Configuration/IUmbracoConfigurationSection.cs b/src/Umbraco.Core/Configuration/IUmbracoConfigurationSection.cs index 32f3acefd943..5547639b11ce 100644 --- a/src/Umbraco.Core/Configuration/IUmbracoConfigurationSection.cs +++ b/src/Umbraco.Core/Configuration/IUmbracoConfigurationSection.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Configuration; +namespace Umbraco.Cms.Core.Configuration; /// /// Represents an Umbraco configuration section which can be used to pass to UmbracoConfiguration.For{T} diff --git a/src/Umbraco.Core/Configuration/IUserPasswordConfiguration.cs b/src/Umbraco.Core/Configuration/IUserPasswordConfiguration.cs index 476d81a66406..c4f86232d309 100644 --- a/src/Umbraco.Core/Configuration/IUserPasswordConfiguration.cs +++ b/src/Umbraco.Core/Configuration/IUserPasswordConfiguration.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Configuration; +namespace Umbraco.Cms.Core.Configuration; /// /// The password configuration for back office users diff --git a/src/Umbraco.Core/Configuration/LocalTempStorage.cs b/src/Umbraco.Core/Configuration/LocalTempStorage.cs index 8f9deba96e86..8be409fc2b8a 100644 --- a/src/Umbraco.Core/Configuration/LocalTempStorage.cs +++ b/src/Umbraco.Core/Configuration/LocalTempStorage.cs @@ -4,5 +4,5 @@ public enum LocalTempStorage { Unknown = 0, Default, - EnvironmentTemp + EnvironmentTemp, } diff --git a/src/Umbraco.Core/Configuration/Models/BasicAuthSettings.cs b/src/Umbraco.Core/Configuration/Models/BasicAuthSettings.cs index 591bbcef2900..fd6fcfdb27b0 100644 --- a/src/Umbraco.Core/Configuration/Models/BasicAuthSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/BasicAuthSettings.cs @@ -19,6 +19,5 @@ public class BasicAuthSettings [DefaultValue(StaticEnabled)] public bool Enabled { get; set; } = StaticEnabled; - public string[] AllowedIPs { get; set; } = Array.Empty(); } diff --git a/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs index bf77c3645d8f..4634f6efb9c6 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentImagingSettings.cs @@ -12,7 +12,7 @@ public class ContentImagingSettings { internal const string StaticImageFileTypes = "jpeg,jpg,gif,bmp,png,tiff,tif,webp"; - private static readonly ImagingAutoFillUploadField[] s_defaultImagingAutoFillUploadField = + private static readonly ImagingAutoFillUploadField[] DefaultImagingAutoFillUploadField = { new() { @@ -20,8 +20,8 @@ public class ContentImagingSettings WidthFieldAlias = Constants.Conventions.Media.Width, HeightFieldAlias = Constants.Conventions.Media.Height, ExtensionFieldAlias = Constants.Conventions.Media.Extension, - LengthFieldAlias = Constants.Conventions.Media.Bytes - } + LengthFieldAlias = Constants.Conventions.Media.Bytes, + }, }; /// @@ -33,5 +33,5 @@ public class ContentImagingSettings /// /// Gets or sets a value for the imaging autofill following media file upload fields. /// - public ImagingAutoFillUploadField[] AutoFillImageProperties { get; set; } = s_defaultImagingAutoFillUploadField; + public ImagingAutoFillUploadField[] AutoFillImageProperties { get; set; } = DefaultImagingAutoFillUploadField; } diff --git a/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationSettings.cs b/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationSettings.cs index 7ae1378bd4e4..6e81c48c7cdf 100644 --- a/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/HealthChecksNotificationSettings.cs @@ -11,7 +11,7 @@ namespace Umbraco.Cms.Core.Configuration.Models; public class HealthChecksNotificationSettings { internal const bool StaticEnabled = false; - internal const string StaticPeriod = "1.00:00:00"; //TimeSpan.FromHours(24); + internal const string StaticPeriod = "1.00:00:00"; // TimeSpan.FromHours(24); /// /// Gets or sets a value indicating whether health check notifications are enabled. diff --git a/src/Umbraco.Core/Configuration/Models/InstallDefaultDataSettings.cs b/src/Umbraco.Core/Configuration/Models/InstallDefaultDataSettings.cs index f0cfee550b9d..25789b397bdb 100644 --- a/src/Umbraco.Core/Configuration/Models/InstallDefaultDataSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/InstallDefaultDataSettings.cs @@ -26,7 +26,7 @@ public enum InstallDefaultDataOption /// /// Install all default data. /// - All + All, } /// diff --git a/src/Umbraco.Core/Configuration/Models/LuceneDirectoryFactory.cs b/src/Umbraco.Core/Configuration/Models/LuceneDirectoryFactory.cs index 901fa1af1e00..3b0e974c089b 100644 --- a/src/Umbraco.Core/Configuration/Models/LuceneDirectoryFactory.cs +++ b/src/Umbraco.Core/Configuration/Models/LuceneDirectoryFactory.cs @@ -19,5 +19,5 @@ public enum LuceneDirectoryFactory /// /// The index will operate only in the processes %temp% directory location /// - TempFileSystemDirectoryFactory + TempFileSystemDirectoryFactory, } diff --git a/src/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidator.cs b/src/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidator.cs index 60527bf7b8c0..079801460038 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidator.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/ContentSettingsValidator.cs @@ -27,7 +27,8 @@ public ValidateOptionsResult Validate(string name, ContentSettings options) } private bool ValidateError404Collection(IEnumerable values, out string message) => - ValidateCollection($"{Constants.Configuration.ConfigContent}:{nameof(ContentSettings.Error404Collection)}", + ValidateCollection( + $"{Constants.Configuration.ConfigContent}:{nameof(ContentSettings.Error404Collection)}", values, "Culture and one and only one of ContentId, ContentKey and ContentXPath must be specified for each entry", out message); diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IImagingAutoFillUploadField.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IImagingAutoFillUploadField.cs index 2484675aee66..f6431dd77a0e 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IImagingAutoFillUploadField.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IImagingAutoFillUploadField.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Configuration.UmbracoSettings; +namespace Umbraco.Cms.Core.Configuration.UmbracoSettings; public interface IImagingAutoFillUploadField { diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IPasswordConfigurationSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IPasswordConfigurationSection.cs index 356858db070d..7a309d6fe34d 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/IPasswordConfigurationSection.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IPasswordConfigurationSection.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Configuration.UmbracoSettings; +namespace Umbraco.Cms.Core.Configuration.UmbracoSettings; public interface IPasswordConfigurationSection : IUmbracoConfigurationSection { diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ITypeFinderConfig.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ITypeFinderConfig.cs index 6eeff38495ff..1dfde6414f8b 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ITypeFinderConfig.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ITypeFinderConfig.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Configuration.UmbracoSettings; +namespace Umbraco.Cms.Core.Configuration.UmbracoSettings; public interface ITypeFinderConfig { diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index a53ca068b494..dc36715585e6 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; public static partial class Constants { diff --git a/src/Umbraco.Core/Constants-Audit.cs b/src/Umbraco.Core/Constants-Audit.cs index 54c51c95ffd9..f795a259749b 100644 --- a/src/Umbraco.Core/Constants-Audit.cs +++ b/src/Umbraco.Core/Constants-Audit.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; public static partial class Constants { diff --git a/src/Umbraco.Core/Constants-CharArrays.cs b/src/Umbraco.Core/Constants-CharArrays.cs index 43a64cfc91b7..832cac00e694 100644 --- a/src/Umbraco.Core/Constants-CharArrays.cs +++ b/src/Umbraco.Core/Constants-CharArrays.cs @@ -10,129 +10,126 @@ public static class CharArrays /// /// Char array containing only / /// - public static readonly char[] ForwardSlash = {'/'}; + public static readonly char[] ForwardSlash = { '/' }; /// /// Char array containing only \ /// - public static readonly char[] Backslash = {'\\'}; + public static readonly char[] Backslash = { '\\' }; /// /// Char array containing only ' /// - public static readonly char[] SingleQuote = {'\''}; + public static readonly char[] SingleQuote = { '\'' }; /// /// Char array containing only " /// - public static readonly char[] DoubleQuote = {'\"'}; - + public static readonly char[] DoubleQuote = { '\"' }; /// /// Char array containing ' " /// - public static readonly char[] DoubleQuoteSingleQuote = {'\"', '\''}; + public static readonly char[] DoubleQuoteSingleQuote = { '\"', '\'' }; /// /// Char array containing only _ /// - public static readonly char[] Underscore = {'_'}; + public static readonly char[] Underscore = { '_' }; /// /// Char array containing \n \r /// - public static readonly char[] LineFeedCarriageReturn = {'\n', '\r'}; - + public static readonly char[] LineFeedCarriageReturn = { '\n', '\r' }; /// /// Char array containing \n /// - public static readonly char[] LineFeed = {'\n'}; + public static readonly char[] LineFeed = { '\n' }; /// /// Char array containing only , /// - public static readonly char[] Comma = {','}; + public static readonly char[] Comma = { ',' }; /// /// Char array containing only & /// - public static readonly char[] Ampersand = {'&'}; + public static readonly char[] Ampersand = { '&' }; /// /// Char array containing only \0 /// - public static readonly char[] NullTerminator = {'\0'}; + public static readonly char[] NullTerminator = { '\0' }; /// /// Char array containing only . /// - public static readonly char[] Period = {'.'}; + public static readonly char[] Period = { '.' }; /// /// Char array containing only ~ /// - public static readonly char[] Tilde = {'~'}; + public static readonly char[] Tilde = { '~' }; /// /// Char array containing ~ / /// - public static readonly char[] TildeForwardSlash = {'~', '/'}; - + public static readonly char[] TildeForwardSlash = { '~', '/' }; /// /// Char array containing ~ / \ /// - public static readonly char[] TildeForwardSlashBackSlash = {'~', '/', '\\'}; + public static readonly char[] TildeForwardSlashBackSlash = { '~', '/', '\\' }; /// /// Char array containing only ? /// - public static readonly char[] QuestionMark = {'?'}; + public static readonly char[] QuestionMark = { '?' }; /// /// Char array containing ? & /// - public static readonly char[] QuestionMarkAmpersand = {'?', '&'}; + public static readonly char[] QuestionMarkAmpersand = { '?', '&' }; /// /// Char array containing XML 1.1 whitespace chars /// - public static readonly char[] XmlWhitespaceChars = {' ', '\t', '\r', '\n'}; + public static readonly char[] XmlWhitespaceChars = { ' ', '\t', '\r', '\n' }; /// /// Char array containing only the Space char /// - public static readonly char[] Space = {' '}; + public static readonly char[] Space = { ' ' }; /// /// Char array containing only ; /// - public static readonly char[] Semicolon = {';'}; + public static readonly char[] Semicolon = { ';' }; /// /// Char array containing a comma and a space /// - public static readonly char[] CommaSpace = {',', ' '}; + public static readonly char[] CommaSpace = { ',', ' ' }; /// /// Char array containing _ - /// - public static readonly char[] UnderscoreDash = {'_', '-'}; + public static readonly char[] UnderscoreDash = { '_', '-' }; /// /// Char array containing = /// - public static readonly char[] EqualsChar = {'='}; + public static readonly char[] EqualsChar = { '=' }; /// /// Char array containing > /// - public static readonly char[] GreaterThan = {'>'}; + public static readonly char[] GreaterThan = { '>' }; /// /// Char array containing | /// - public static readonly char[] VerticalTab = {'|'}; + public static readonly char[] VerticalTab = { '|' }; } } diff --git a/src/Umbraco.Core/Constants-Composing.cs b/src/Umbraco.Core/Constants-Composing.cs index ff95a3d04b7c..e55c32d01a90 100644 --- a/src/Umbraco.Core/Constants-Composing.cs +++ b/src/Umbraco.Core/Constants-Composing.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; /// /// Defines constants. @@ -13,7 +13,7 @@ public static class Composing public static readonly string[] UmbracoCoreAssemblyNames = { "Umbraco.Core", "Umbraco.Infrastructure", "Umbraco.PublishedCache.NuCache", "Umbraco.Examine.Lucene", - "Umbraco.Web.Common", "Umbraco.Web.BackOffice", "Umbraco.Web.Website" + "Umbraco.Web.Common", "Umbraco.Web.BackOffice", "Umbraco.Web.Website", }; } } diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index 216ccfc778d4..7b221e143578 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -28,7 +28,6 @@ public static class PublicAccess public const string MemberRoleRuleType = "MemberRole"; } - public static class DataTypes { public const string ListViewPrefix = "List View - "; @@ -345,9 +344,9 @@ public static class RelationTypes /// Developers should not manually use these relation types since they will all be cleared whenever an entity /// (content, media or member) is saved since they are auto-populated based on property values. /// - public static string[] AutomaticRelationTypes { get; } = {RelatedMediaAlias, RelatedDocumentAlias}; + public static string[] AutomaticRelationTypes { get; } = { RelatedMediaAlias, RelatedDocumentAlias }; - //TODO: return a list of built in types so we can use that to prevent deletion in the uI + // TODO: return a list of built in types so we can use that to prevent deletion in the uI } } } diff --git a/src/Umbraco.Core/Constants-DataTypes.cs b/src/Umbraco.Core/Constants-DataTypes.cs index b1c8e240cbdd..a3e2dbc4c5dc 100644 --- a/src/Umbraco.Core/Constants-DataTypes.cs +++ b/src/Umbraco.Core/Constants-DataTypes.cs @@ -4,10 +4,9 @@ public static partial class Constants { public static class DataTypes { - //NOTE: unfortunately due to backwards compat we can't move/rename these, with the addition of the GUID - //constants, it would make more sense to have these suffixed with "ID" or in a Subclass called "INT", for - //now all we can do is make a subclass called Guids to put the GUID IDs. - + // NOTE: unfortunately due to backwards compat we can't move/rename these, with the addition of the GUID + // constants, it would make more sense to have these suffixed with "ID" or in a Subclass called "INT", for + // now all we can do is make a subclass called Guids to put the GUID IDs. public const int LabelString = System.DefaultLabelDataTypeId; public const int LabelInt = -91; public const int LabelBigint = -93; @@ -50,25 +49,21 @@ public static class Guids /// public const string ContentPicker = "FD1E0DA5-5606-4862-B679-5D0CF3A52A59"; - /// /// Guid for Member Picker as string /// public const string MemberPicker = "1EA2E01F-EBD8-4CE1-8D71-6B1149E63548"; - /// /// Guid for Media Picker as string /// public const string MediaPicker = "135D60E0-64D9-49ED-AB08-893C9BA44AE5"; - /// /// Guid for Multiple Media Picker as string /// public const string MultipleMediaPicker = "9DBBCBBB-2327-434A-B355-AF1B84E5010A"; - /// /// Guid for Media Picker v3 as string /// @@ -79,55 +74,46 @@ public static class Guids /// public const string MediaPicker3Multiple = "1B661F40-2242-4B44-B9CB-3990EE2B13C0"; - /// /// Guid for Media Picker v3 single-image as string /// public const string MediaPicker3SingleImage = "AD9F0CF2-BDA2-45D5-9EA1-A63CFC873FD3"; - /// /// Guid for Media Picker v3 multi-image as string /// public const string MediaPicker3MultipleImages = "0E63D883-B62B-4799-88C3-157F82E83ECC"; - /// /// Guid for Related Links as string /// public const string RelatedLinks = "B4E3535A-1753-47E2-8568-602CF8CFEE6F"; - /// /// Guid for Member as string /// public const string Member = "d59be02f-1df9-4228-aa1e-01917d806cda"; - /// /// Guid for Image Cropper as string /// public const string ImageCropper = "1df9f033-e6d4-451f-b8d2-e0cbc50a836f"; - /// /// Guid for Tags as string /// public const string Tags = "b6b73142-b9c1-4bf8-a16d-e1c23320b549"; - /// /// Guid for List View - Content as string /// public const string ListViewContent = "C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4"; - /// /// Guid for List View - Media as string /// public const string ListViewMedia = "3A0156C4-3B8C-4803-BDC1-6871FAA83FFF"; - /// /// Guid for List View - Members as string /// @@ -138,73 +124,61 @@ public static class Guids /// public const string DatePickerWithTime = "e4d66c0f-b935-4200-81f0-025f7256b89a"; - /// /// Guid for Approved Color as string /// public const string ApprovedColor = "0225af17-b302-49cb-9176-b9f35cab9c17"; - /// /// Guid for Dropdown multiple as string /// public const string DropdownMultiple = "f38f0ac7-1d27-439c-9f3f-089cd8825a53"; - /// /// Guid for Radiobox as string /// public const string Radiobox = "bb5f57c9-ce2b-4bb9-b697-4caca783a805"; - /// /// Guid for Date Picker as string /// public const string DatePicker = "5046194e-4237-453c-a547-15db3a07c4e1"; - /// /// Guid for Dropdown as string /// public const string Dropdown = "0b6a45e7-44ba-430d-9da5-4e46060b9e03"; - /// /// Guid for Checkbox list as string /// public const string CheckboxList = "fbaf13a8-4036-41f2-93a3-974f678c312a"; - /// /// Guid for Checkbox as string /// public const string Checkbox = "92897bc6-a5f3-4ffe-ae27-f2e7e33dda49"; - /// /// Guid for Numeric as string /// public const string Numeric = "2e6d3631-066e-44b8-aec4-96f09099b2b5"; - /// /// Guid for Richtext editor as string /// public const string RichtextEditor = "ca90c950-0aff-4e72-b976-a30b1ac57dad"; - /// /// Guid for Textstring as string /// public const string Textstring = "0cc0eba1-9960-42c9-bf9b-60e150b429ae"; - /// /// Guid for Textarea as string /// public const string Textarea = "c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3"; - /// /// Guid for Upload as string /// @@ -230,7 +204,6 @@ public static class Guids /// public const string UploadVectorGraphics = "215cb418-2153-4429-9aef-8c0f0041191b"; - /// /// Guid for Label as string /// diff --git a/src/Umbraco.Core/Constants-DeploySelector.cs b/src/Umbraco.Core/Constants-DeploySelector.cs index cb6bcc99f839..0f552e8a8290 100644 --- a/src/Umbraco.Core/Constants-DeploySelector.cs +++ b/src/Umbraco.Core/Constants-DeploySelector.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; public static partial class Constants { diff --git a/src/Umbraco.Core/Constants-ModelsBuilder.cs b/src/Umbraco.Core/Constants-ModelsBuilder.cs index 19e237754c81..63b852a6002b 100644 --- a/src/Umbraco.Core/Constants-ModelsBuilder.cs +++ b/src/Umbraco.Core/Constants-ModelsBuilder.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; /// /// Defines constants. diff --git a/src/Umbraco.Core/Constants-ObjectTypes.cs b/src/Umbraco.Core/Constants-ObjectTypes.cs index e1f18b601e55..049a536690ff 100644 --- a/src/Umbraco.Core/Constants-ObjectTypes.cs +++ b/src/Umbraco.Core/Constants-ObjectTypes.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; public static partial class Constants { @@ -67,7 +67,6 @@ public static class ObjectTypes public static class Strings { // ReSharper disable MemberHidesStaticFromOuterClass - public const string DataTypeContainer = "521231E3-8B37-469C-9F9D-51AFC91FEB7B"; public const string DocumentTypeContainer = "2F7A2769-6B0B-4468-90DD-AF42D64F7F16"; diff --git a/src/Umbraco.Core/Constants-PackageRepository.cs b/src/Umbraco.Core/Constants-PackageRepository.cs index 51b31ec81c51..96746adb4940 100644 --- a/src/Umbraco.Core/Constants-PackageRepository.cs +++ b/src/Umbraco.Core/Constants-PackageRepository.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; public static partial class Constants { diff --git a/src/Umbraco.Core/Constants-PropertyTypeGroups.cs b/src/Umbraco.Core/Constants-PropertyTypeGroups.cs index f30bafe6fe57..a713b279b138 100644 --- a/src/Umbraco.Core/Constants-PropertyTypeGroups.cs +++ b/src/Umbraco.Core/Constants-PropertyTypeGroups.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; public static partial class Constants { diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index 226778aeee6a..26e26804ae50 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -42,7 +42,6 @@ public static class Security public const string DefaultMemberTypeAlias = "Member"; - /// /// The prefix used for external identity providers for their authentication type /// diff --git a/src/Umbraco.Core/Constants-Sql.cs b/src/Umbraco.Core/Constants-Sql.cs index 500584152444..f89368046564 100644 --- a/src/Umbraco.Core/Constants-Sql.cs +++ b/src/Umbraco.Core/Constants-Sql.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; public static partial class Constants { diff --git a/src/Umbraco.Core/Constants-SqlTemplates.cs b/src/Umbraco.Core/Constants-SqlTemplates.cs index 2ffdbab19c4d..549dae5bd62f 100644 --- a/src/Umbraco.Core/Constants-SqlTemplates.cs +++ b/src/Umbraco.Core/Constants-SqlTemplates.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; public static partial class Constants { diff --git a/src/Umbraco.Core/Constants-System.cs b/src/Umbraco.Core/Constants-System.cs index db5c485e7b77..43de01995bde 100644 --- a/src/Umbraco.Core/Constants-System.cs +++ b/src/Umbraco.Core/Constants-System.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; public static partial class Constants { diff --git a/src/Umbraco.Core/Constants-SystemDirectories.cs b/src/Umbraco.Core/Constants-SystemDirectories.cs index c941affd6abf..85375390ac0a 100644 --- a/src/Umbraco.Core/Constants-SystemDirectories.cs +++ b/src/Umbraco.Core/Constants-SystemDirectories.cs @@ -62,6 +62,7 @@ public static class SystemDirectories /// public const string LogFiles = Umbraco + "/Logs"; - [Obsolete("Use PluginIcons instead")] public static string AppPluginIcons => "/Backoffice/Icons"; + [Obsolete("Use PluginIcons instead")] + public static string AppPluginIcons => "/Backoffice/Icons"; } } diff --git a/src/Umbraco.Core/Constants-Telemetry.cs b/src/Umbraco.Core/Constants-Telemetry.cs index 493a2ffbe33e..5f3783a77436 100644 --- a/src/Umbraco.Core/Constants-Telemetry.cs +++ b/src/Umbraco.Core/Constants-Telemetry.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; public static partial class Constants { diff --git a/src/Umbraco.Core/Constants-UdiEntityType.cs b/src/Umbraco.Core/Constants-UdiEntityType.cs index 390d33b31a1e..f65c29051614 100644 --- a/src/Umbraco.Core/Constants-UdiEntityType.cs +++ b/src/Umbraco.Core/Constants-UdiEntityType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; public static partial class Constants { @@ -15,12 +15,9 @@ public static class UdiEntityType // this is validated by UdiTests.ValidateUdiEntityType // also, this is used exclusively in Udi static ctor, only once, so there is no // need to keep it around in a field nor to make it readonly - - public const string Unknown = "unknown"; // guid entity types - public const string AnyGuid = "any-guid"; // that one is for tests public const string Element = "element"; @@ -50,13 +47,11 @@ public static class UdiEntityType public const string RelationType = "relation-type"; // forms - public const string FormsForm = "forms-form"; public const string FormsPreValue = "forms-prevalue"; public const string FormsDataSource = "forms-datasource"; // string entity types - public const string AnyString = "any-string"; // that one is for tests public const string Language = "language"; diff --git a/src/Umbraco.Core/ContentApps/ContentAppFactoryCollection.cs b/src/Umbraco.Core/ContentApps/ContentAppFactoryCollection.cs index fe89b40813c1..09a3e410fde5 100644 --- a/src/Umbraco.Core/ContentApps/ContentAppFactoryCollection.cs +++ b/src/Umbraco.Core/ContentApps/ContentAppFactoryCollection.cs @@ -12,22 +12,16 @@ public class ContentAppFactoryCollection : BuilderCollectionBase _logger; - public ContentAppFactoryCollection(Func> items, - ILogger logger, IBackOfficeSecurityAccessor backOfficeSecurityAccessor) + public ContentAppFactoryCollection( + Func> items, + ILogger logger, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor) : base(items) { _logger = logger; _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } - private IEnumerable GetCurrentUserGroups() - { - IUser currentUser = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser; - return currentUser == null - ? Enumerable.Empty() - : currentUser.Groups; - } - public IEnumerable GetContentAppsFor(object o, IEnumerable? userGroups = null) { IEnumerable roles = GetCurrentUserGroups(); @@ -43,7 +37,7 @@ public IEnumerable GetContentAppsFor(object o, IEnumerable())).Add(app.Alias); + (dups ??= new List()).Add(app.Alias); } else { @@ -56,10 +50,18 @@ public IEnumerable GetContentAppsFor(object o, IEnumerable GetCurrentUserGroups() + { + IUser? currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; + return currentUser == null + ? Enumerable.Empty() + : currentUser.Groups; + } } diff --git a/src/Umbraco.Core/ContentApps/ContentEditorContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ContentEditorContentAppFactory.cs index be6e384c5c59..ac8b3a206107 100644 --- a/src/Umbraco.Core/ContentApps/ContentEditorContentAppFactory.cs +++ b/src/Umbraco.Core/ContentApps/ContentEditorContentAppFactory.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Membership; @@ -18,34 +18,34 @@ public class ContentEditorContentAppFactory : IContentAppFactory switch (o) { case IContent content when content.Properties.Count > 0: - return _contentApp ?? (_contentApp = new ContentApp + return _contentApp ??= new ContentApp { Alias = "umbContent", Name = "Content", Icon = Constants.Icons.Content, View = "views/content/apps/content/content.html", - Weight = Weight - }); + Weight = Weight, + }; case IMedia media when !media.ContentType.IsContainer || media.Properties.Count > 0: - return _mediaApp ?? (_mediaApp = new ContentApp + return _mediaApp ??= new ContentApp { Alias = "umbContent", Name = "Content", Icon = Constants.Icons.Content, View = "views/media/apps/content/content.html", - Weight = Weight - }); + Weight = Weight, + }; case IMember _: - return _memberApp ?? (_memberApp = new ContentApp + return _memberApp ??= new ContentApp { Alias = "umbContent", Name = "Content", Icon = Constants.Icons.Content, View = "views/member/apps/content/content.html", - Weight = Weight - }); + Weight = Weight, + }; default: return null; diff --git a/src/Umbraco.Core/ContentApps/ContentInfoContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ContentInfoContentAppFactory.cs index 13a3b38021bd..1e318e380ee6 100644 --- a/src/Umbraco.Core/ContentApps/ContentInfoContentAppFactory.cs +++ b/src/Umbraco.Core/ContentApps/ContentInfoContentAppFactory.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Membership; @@ -24,7 +24,7 @@ public class ContentInfoContentAppFactory : IContentAppFactory Name = "Info", Icon = "icon-info", View = "views/content/apps/info/info.html", - Weight = Weight + Weight = Weight, }; case IMedia _: @@ -34,7 +34,7 @@ public class ContentInfoContentAppFactory : IContentAppFactory Name = "Info", Icon = "icon-info", View = "views/media/apps/info/info.html", - Weight = Weight + Weight = Weight, }; case IMember _: return _memberApp ??= new ContentApp @@ -43,7 +43,7 @@ public class ContentInfoContentAppFactory : IContentAppFactory Name = "Info", Icon = "icon-info", View = "views/member/apps/info/info.html", - Weight = Weight + Weight = Weight, }; default: diff --git a/src/Umbraco.Core/ContentApps/ContentTypeDesignContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ContentTypeDesignContentAppFactory.cs index 3011fbb7dfe6..5e4f6a7a888a 100644 --- a/src/Umbraco.Core/ContentApps/ContentTypeDesignContentAppFactory.cs +++ b/src/Umbraco.Core/ContentApps/ContentTypeDesignContentAppFactory.cs @@ -21,7 +21,7 @@ public class ContentTypeDesignContentAppFactory : IContentAppFactory Name = "Design", Icon = "icon-document-dashed-line", View = "views/documentTypes/views/design/design.html", - Weight = Weight + Weight = Weight, }; default: return null; diff --git a/src/Umbraco.Core/ContentApps/ContentTypeListViewContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ContentTypeListViewContentAppFactory.cs index b1a2a8e5c9cd..8aed04050f6f 100644 --- a/src/Umbraco.Core/ContentApps/ContentTypeListViewContentAppFactory.cs +++ b/src/Umbraco.Core/ContentApps/ContentTypeListViewContentAppFactory.cs @@ -21,7 +21,7 @@ public class ContentTypeListViewContentAppFactory : IContentAppFactory Name = "List view", Icon = "icon-list", View = "views/documentTypes/views/listview/listview.html", - Weight = Weight + Weight = Weight, }; default: return null; diff --git a/src/Umbraco.Core/ContentApps/ContentTypePermissionsContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ContentTypePermissionsContentAppFactory.cs index b2436388bff4..b585a7db4d08 100644 --- a/src/Umbraco.Core/ContentApps/ContentTypePermissionsContentAppFactory.cs +++ b/src/Umbraco.Core/ContentApps/ContentTypePermissionsContentAppFactory.cs @@ -21,7 +21,7 @@ public class ContentTypePermissionsContentAppFactory : IContentAppFactory Name = "Permissions", Icon = "icon-keychain", View = "views/documentTypes/views/permissions/permissions.html", - Weight = Weight + Weight = Weight, }; default: return null; diff --git a/src/Umbraco.Core/ContentApps/ContentTypeTemplatesContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ContentTypeTemplatesContentAppFactory.cs index 7a28fba5b64a..712e1e7c1e0f 100644 --- a/src/Umbraco.Core/ContentApps/ContentTypeTemplatesContentAppFactory.cs +++ b/src/Umbraco.Core/ContentApps/ContentTypeTemplatesContentAppFactory.cs @@ -21,7 +21,7 @@ public class ContentTypeTemplatesContentAppFactory : IContentAppFactory Name = "Templates", Icon = "icon-layout", View = "views/documentTypes/views/templates/templates.html", - Weight = Weight + Weight = Weight, }; default: return null; diff --git a/src/Umbraco.Core/ContentApps/DictionaryContentAppFactory.cs b/src/Umbraco.Core/ContentApps/DictionaryContentAppFactory.cs index fc2226036b10..21bfcfcef024 100644 --- a/src/Umbraco.Core/ContentApps/DictionaryContentAppFactory.cs +++ b/src/Umbraco.Core/ContentApps/DictionaryContentAppFactory.cs @@ -21,7 +21,7 @@ internal class DictionaryContentAppFactory : IContentAppFactory Name = "Content", Icon = "icon-document", View = "views/dictionary/views/content/content.html", - Weight = Weight + Weight = Weight, }; default: return null; diff --git a/src/Umbraco.Core/ContentApps/ListViewContentAppFactory.cs b/src/Umbraco.Core/ContentApps/ListViewContentAppFactory.cs index dc76f391bf61..466c9d7a3baf 100644 --- a/src/Umbraco.Core/ContentApps/ListViewContentAppFactory.cs +++ b/src/Umbraco.Core/ContentApps/ListViewContentAppFactory.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.PropertyEditors; @@ -21,38 +21,11 @@ public ListViewContentAppFactory(IDataTypeService dataTypeService, PropertyEdito _propertyEditors = propertyEditors; } - public ContentApp? GetContentAppFor(object o, IEnumerable userGroups) - { - string contentTypeAlias, entityType; - int dtdId; - - switch (o) - { - case IContent content when !content.ContentType.IsContainer: - return null; - case IContent content: - contentTypeAlias = content.ContentType.Alias; - entityType = "content"; - dtdId = Constants.DataTypes.DefaultContentListView; - break; - case IMedia media when !media.ContentType.IsContainer && - media.ContentType.Alias != Constants.Conventions.MediaTypes.Folder: - return null; - case IMedia media: - contentTypeAlias = media.ContentType.Alias; - entityType = "media"; - dtdId = Constants.DataTypes.DefaultMediaListView; - break; - default: - return null; - } - - return CreateContentApp(_dataTypeService, _propertyEditors, entityType, contentTypeAlias, dtdId); - } - - public static ContentApp CreateContentApp(IDataTypeService dataTypeService, + public static ContentApp CreateContentApp( + IDataTypeService dataTypeService, PropertyEditorCollection propertyEditors, - string entityType, string contentTypeAlias, + string entityType, + string contentTypeAlias, int defaultListViewDataType) { if (dataTypeService == null) @@ -86,14 +59,14 @@ public static ContentApp CreateContentApp(IDataTypeService dataTypeService, Name = "Child items", Icon = "icon-list", View = "views/content/apps/listview/listview.html", - Weight = Weight + Weight = Weight, }; var customDtdName = Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias; - //first try to get the custom one if there is one - IDataType dt = dataTypeService.GetDataType(customDtdName) - ?? dataTypeService.GetDataType(defaultListViewDataType); + // first try to get the custom one if there is one + IDataType? dt = dataTypeService.GetDataType(customDtdName) + ?? dataTypeService.GetDataType(defaultListViewDataType); if (dt == null) { @@ -101,7 +74,7 @@ public static ContentApp CreateContentApp(IDataTypeService dataTypeService, "No list view data type was found for this document type, ensure that the default list view data types exists and/or that your custom list view data type exists"); } - IDataEditor editor = propertyEditors[dt.EditorAlias]; + IDataEditor? editor = propertyEditors[dt.EditorAlias]; if (editor == null) { throw new NullReferenceException("The property editor with alias " + dt.EditorAlias + " does not exist"); @@ -109,24 +82,25 @@ public static ContentApp CreateContentApp(IDataTypeService dataTypeService, IDictionary listViewConfig = editor.GetConfigurationEditor().ToConfigurationEditor(dt.Configuration); - //add the entity type to the config + + // add the entity type to the config listViewConfig["entityType"] = entityType; - //Override Tab Label if tabName is provided + // Override Tab Label if tabName is provided if (listViewConfig.ContainsKey("tabName")) { var configTabName = listViewConfig["tabName"]; - if (configTabName != null && string.IsNullOrWhiteSpace(configTabName.ToString()) == false) + if (string.IsNullOrWhiteSpace(configTabName.ToString()) == false) { contentApp.Name = configTabName.ToString(); } } - //Override Icon if icon is provided + // Override Icon if icon is provided if (listViewConfig.ContainsKey("icon")) { var configIcon = listViewConfig["icon"]; - if (configIcon != null && string.IsNullOrWhiteSpace(configIcon.ToString()) == false) + if (string.IsNullOrWhiteSpace(configIcon.ToString()) == false) { contentApp.Icon = configIcon.ToString(); } @@ -139,20 +113,49 @@ public static ContentApp CreateContentApp(IDataTypeService dataTypeService, contentApp.Weight = ContentEditorContentAppFactory.Weight + 1; } - //This is the view model used for the list view app + // This is the view model used for the list view app contentApp.ViewModel = new List { new() { Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}containerView", - Label = "", + Label = string.Empty, Value = null, View = editor.GetValueEditor().View, HideLabel = true, - Config = listViewConfig - } + Config = listViewConfig, + }, }; return contentApp; } + + public ContentApp? GetContentAppFor(object o, IEnumerable userGroups) + { + string contentTypeAlias, entityType; + int dtdId; + + switch (o) + { + case IContent content when !content.ContentType.IsContainer: + return null; + case IContent content: + contentTypeAlias = content.ContentType.Alias; + entityType = "content"; + dtdId = Constants.DataTypes.DefaultContentListView; + break; + case IMedia media when !media.ContentType.IsContainer && + media.ContentType.Alias != Constants.Conventions.MediaTypes.Folder: + return null; + case IMedia media: + contentTypeAlias = media.ContentType.Alias; + entityType = "media"; + dtdId = Constants.DataTypes.DefaultMediaListView; + break; + default: + return null; + } + + return CreateContentApp(_dataTypeService, _propertyEditors, entityType, contentTypeAlias, dtdId); + } } diff --git a/src/Umbraco.Core/ConventionsHelper.cs b/src/Umbraco.Core/ConventionsHelper.cs index c388d7dff005..7d79338142d3 100644 --- a/src/Umbraco.Core/ConventionsHelper.cs +++ b/src/Umbraco.Core/ConventionsHelper.cs @@ -9,12 +9,14 @@ public static Dictionary GetStandardPropertyTypeStubs(ISho new() { { - Constants.Conventions.Member.Comments, new PropertyType( + Constants.Conventions.Member.Comments, + new PropertyType( shortStringHelper, Constants.PropertyEditors.Aliases.TextArea, ValueStorageType.Ntext, true, - Constants.Conventions.Member.Comments) {Name = Constants.Conventions.Member.CommentsLabel} - } + Constants.Conventions.Member.Comments) + { Name = Constants.Conventions.Member.CommentsLabel } + }, }; } diff --git a/src/Umbraco.Core/CustomBooleanTypeConverter.cs b/src/Umbraco.Core/CustomBooleanTypeConverter.cs index c8f896933211..bacfec7ef9aa 100644 --- a/src/Umbraco.Core/CustomBooleanTypeConverter.cs +++ b/src/Umbraco.Core/CustomBooleanTypeConverter.cs @@ -20,9 +20,8 @@ public override bool CanConvertFrom(ITypeDescriptorContext? context, Type source public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) { - if (value is string) + if (value is string str) { - var str = (string)value; if (str == null || str.Length == 0 || str == "0") { return false; diff --git a/src/Umbraco.Core/Dashboards/AccessRule.cs b/src/Umbraco.Core/Dashboards/AccessRule.cs index a728c9561cbc..eb7383f60169 100644 --- a/src/Umbraco.Core/Dashboards/AccessRule.cs +++ b/src/Umbraco.Core/Dashboards/AccessRule.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Dashboards; +namespace Umbraco.Cms.Core.Dashboards; /// /// Implements . diff --git a/src/Umbraco.Core/Dashboards/AccessRuleType.cs b/src/Umbraco.Core/Dashboards/AccessRuleType.cs index 479d7f6be63c..63d92fc38ac8 100644 --- a/src/Umbraco.Core/Dashboards/AccessRuleType.cs +++ b/src/Umbraco.Core/Dashboards/AccessRuleType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Dashboards; +namespace Umbraco.Cms.Core.Dashboards; /// /// Defines dashboard access rules type. @@ -23,5 +23,5 @@ public enum AccessRuleType /// /// Grant access to the dashboard if user has access to the specified section. /// - GrantBySection + GrantBySection, } diff --git a/src/Umbraco.Core/Dashboards/AnalyticsDashboard.cs b/src/Umbraco.Core/Dashboards/AnalyticsDashboard.cs index a409d128bed8..07688832f66a 100644 --- a/src/Umbraco.Core/Dashboards/AnalyticsDashboard.cs +++ b/src/Umbraco.Core/Dashboards/AnalyticsDashboard.cs @@ -1,10 +1,10 @@ -namespace Umbraco.Cms.Core.Dashboards; +namespace Umbraco.Cms.Core.Dashboards; public class AnalyticsDashboard : IDashboard { public string Alias => "settingsAnalytics"; - public string[] Sections => new[] {"settings"}; + public string[] Sections => new[] { "settings" }; public string View => "views/dashboard/settings/analytics.html"; diff --git a/src/Umbraco.Core/Dashboards/ContentDashboard.cs b/src/Umbraco.Core/Dashboards/ContentDashboard.cs index 6664bb924fb5..ff3a0031b37e 100644 --- a/src/Umbraco.Core/Dashboards/ContentDashboard.cs +++ b/src/Umbraco.Core/Dashboards/ContentDashboard.cs @@ -7,7 +7,7 @@ public class ContentDashboard : IDashboard { public string Alias => "contentIntro"; - public string[] Sections => new[] {"content"}; + public string[] Sections => new[] { "content" }; public string View => "views/dashboard/default/startupdashboardintro.html"; diff --git a/src/Umbraco.Core/Dashboards/DashboardCollection.cs b/src/Umbraco.Core/Dashboards/DashboardCollection.cs index 68b8b683c7c9..ebcf79fc7f1b 100644 --- a/src/Umbraco.Core/Dashboards/DashboardCollection.cs +++ b/src/Umbraco.Core/Dashboards/DashboardCollection.cs @@ -4,7 +4,8 @@ namespace Umbraco.Cms.Core.Dashboards; public class DashboardCollection : BuilderCollectionBase { - public DashboardCollection(Func> items) : base(items) + public DashboardCollection(Func> items) + : base(items) { } } diff --git a/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs b/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs index bd5e52102788..56efe47f6b58 100644 --- a/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs +++ b/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs @@ -23,7 +23,8 @@ protected override IEnumerable CreateItems(IServiceProvider factory) return dashboardSections; } - private IEnumerable Merge(IEnumerable dashboardsFromCode, + private IEnumerable Merge( + IEnumerable dashboardsFromCode, IReadOnlyList dashboardFromManifest) => dashboardsFromCode.Concat(dashboardFromManifest) .Where(x => !string.IsNullOrEmpty(x.Alias)) diff --git a/src/Umbraco.Core/Dashboards/DashboardSlim.cs b/src/Umbraco.Core/Dashboards/DashboardSlim.cs index 010382b5dc98..a79392c0d05d 100644 --- a/src/Umbraco.Core/Dashboards/DashboardSlim.cs +++ b/src/Umbraco.Core/Dashboards/DashboardSlim.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Dashboards; diff --git a/src/Umbraco.Core/Dashboards/ExamineDashboard.cs b/src/Umbraco.Core/Dashboards/ExamineDashboard.cs index 52828a12132d..ddd048c99e04 100644 --- a/src/Umbraco.Core/Dashboards/ExamineDashboard.cs +++ b/src/Umbraco.Core/Dashboards/ExamineDashboard.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Dashboards; @@ -7,7 +7,7 @@ public class ExamineDashboard : IDashboard { public string Alias => "settingsExamine"; - public string[] Sections => new[] {"settings"}; + public string[] Sections => new[] { "settings" }; public string View => "views/dashboard/settings/examinemanagement.html"; diff --git a/src/Umbraco.Core/Dashboards/FormsDashboard.cs b/src/Umbraco.Core/Dashboards/FormsDashboard.cs index ba1888d62d22..414655348468 100644 --- a/src/Umbraco.Core/Dashboards/FormsDashboard.cs +++ b/src/Umbraco.Core/Dashboards/FormsDashboard.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Dashboards; @@ -7,7 +7,7 @@ public class FormsDashboard : IDashboard { public string Alias => "formsInstall"; - public string[] Sections => new[] {Constants.Applications.Forms}; + public string[] Sections => new[] { Constants.Applications.Forms }; public string View => "views/dashboard/forms/formsdashboardintro.html"; diff --git a/src/Umbraco.Core/Dashboards/HealthCheckDashboard.cs b/src/Umbraco.Core/Dashboards/HealthCheckDashboard.cs index 658a9a89a0af..85c20534502e 100644 --- a/src/Umbraco.Core/Dashboards/HealthCheckDashboard.cs +++ b/src/Umbraco.Core/Dashboards/HealthCheckDashboard.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Dashboards; @@ -7,7 +7,7 @@ public class HealthCheckDashboard : IDashboard { public string Alias => "settingsHealthCheck"; - public string[] Sections => new[] {"settings"}; + public string[] Sections => new[] { "settings" }; public string View => "views/dashboard/settings/healthcheck.html"; diff --git a/src/Umbraco.Core/Dashboards/IAccessRule.cs b/src/Umbraco.Core/Dashboards/IAccessRule.cs index b77ffbcf762d..fcd78ebc9b9a 100644 --- a/src/Umbraco.Core/Dashboards/IAccessRule.cs +++ b/src/Umbraco.Core/Dashboards/IAccessRule.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Dashboards; +namespace Umbraco.Cms.Core.Dashboards; /// /// Represents an access rule. diff --git a/src/Umbraco.Core/Dashboards/IDashboard.cs b/src/Umbraco.Core/Dashboards/IDashboard.cs index 74ceb78a6949..96e29d05393a 100644 --- a/src/Umbraco.Core/Dashboards/IDashboard.cs +++ b/src/Umbraco.Core/Dashboards/IDashboard.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Dashboards; @@ -24,7 +24,6 @@ public interface IDashboard : IDashboardSlim [DataMember(Name = "sections")] string[] Sections { get; } - /// /// Gets the access rule determining the visibility of the dashboard. /// diff --git a/src/Umbraco.Core/Dashboards/IDashboardSlim.cs b/src/Umbraco.Core/Dashboards/IDashboardSlim.cs index d1c479e1a447..c3907b1af4f5 100644 --- a/src/Umbraco.Core/Dashboards/IDashboardSlim.cs +++ b/src/Umbraco.Core/Dashboards/IDashboardSlim.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Dashboards; diff --git a/src/Umbraco.Core/Dashboards/MediaDashboard.cs b/src/Umbraco.Core/Dashboards/MediaDashboard.cs index c8d6d35d560b..47e45c4270aa 100644 --- a/src/Umbraco.Core/Dashboards/MediaDashboard.cs +++ b/src/Umbraco.Core/Dashboards/MediaDashboard.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Dashboards; @@ -7,7 +7,7 @@ public class MediaDashboard : IDashboard { public string Alias => "mediaFolderBrowser"; - public string[] Sections => new[] {"media"}; + public string[] Sections => new[] { "media" }; public string View => "views/dashboard/media/mediafolderbrowser.html"; diff --git a/src/Umbraco.Core/DefaultEventMessagesFactory.cs b/src/Umbraco.Core/DefaultEventMessagesFactory.cs index 7ede3f6a7543..9648e76fcabf 100644 --- a/src/Umbraco.Core/DefaultEventMessagesFactory.cs +++ b/src/Umbraco.Core/DefaultEventMessagesFactory.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Events; namespace Umbraco.Cms.Core; @@ -8,17 +8,12 @@ public class DefaultEventMessagesFactory : IEventMessagesFactory public DefaultEventMessagesFactory(IEventMessagesAccessor eventMessagesAccessor) { - if (eventMessagesAccessor == null) - { - throw new ArgumentNullException(nameof(eventMessagesAccessor)); - } - - _eventMessagesAccessor = eventMessagesAccessor; + _eventMessagesAccessor = eventMessagesAccessor ?? throw new ArgumentNullException(nameof(eventMessagesAccessor)); } public EventMessages Get() { - EventMessages eventMessages = _eventMessagesAccessor.EventMessages; + EventMessages? eventMessages = _eventMessagesAccessor.EventMessages; if (eventMessages == null) { _eventMessagesAccessor.EventMessages = eventMessages = new EventMessages(); diff --git a/src/Umbraco.Core/DelegateEqualityComparer.cs b/src/Umbraco.Core/DelegateEqualityComparer.cs index 7ad004a2e685..8a442e8f8547 100644 --- a/src/Umbraco.Core/DelegateEqualityComparer.cs +++ b/src/Umbraco.Core/DelegateEqualityComparer.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; /// /// A custom equality comparer that excepts a delegate to do the comparison operation diff --git a/src/Umbraco.Core/DependencyInjection/IUmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/IUmbracoBuilder.cs index dc040a766442..8850bece117b 100644 --- a/src/Umbraco.Core/DependencyInjection/IUmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/IUmbracoBuilder.cs @@ -11,7 +11,9 @@ namespace Umbraco.Cms.Core.DependencyInjection; public interface IUmbracoBuilder { IServiceCollection Services { get; } + IConfiguration Config { get; } + TypeLoader TypeLoader { get; } /// @@ -31,7 +33,11 @@ public interface IUmbracoBuilder IHostingEnvironment? BuilderHostingEnvironment { get; } IProfiler Profiler { get; } + AppCaches AppCaches { get; } - TBuilder? WithCollectionBuilder() where TBuilder : ICollectionBuilder; + + TBuilder? WithCollectionBuilder() + where TBuilder : ICollectionBuilder; + void Build(); } diff --git a/src/Umbraco.Core/Deploy/ArtifactBase.cs b/src/Umbraco.Core/Deploy/ArtifactBase.cs index a2e9e49dfde4..bfbd6279e7d4 100644 --- a/src/Umbraco.Core/Deploy/ArtifactBase.cs +++ b/src/Umbraco.Core/Deploy/ArtifactBase.cs @@ -23,16 +23,18 @@ protected ArtifactBase(TUdi udi, IEnumerable? dependencies = public string? Alias { get; set; } - protected abstract string GetChecksum(); - - #region Abstract implementation of IArtifactSignature - Udi IArtifactSignature.Udi => Udi; public TUdi Udi { get; set; } public string Checksum => _checksum.Value; + public IEnumerable? Dependencies + { + get => _dependencies; + set => _dependencies = value?.OrderBy(x => x.Udi); + } + /// /// Prevents the property from being serialized. /// @@ -45,11 +47,5 @@ protected ArtifactBase(TUdi udi, IEnumerable? dependencies = /// public bool ShouldSerializeChecksum() => false; - public IEnumerable? Dependencies - { - get => _dependencies; - set => _dependencies = value?.OrderBy(x => x.Udi); - } - - #endregion + protected abstract string GetChecksum(); } diff --git a/src/Umbraco.Core/Deploy/ArtifactDependency.cs b/src/Umbraco.Core/Deploy/ArtifactDependency.cs index 92e27c87bdeb..07ba917dc2ef 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDependency.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDependency.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Deploy; +namespace Umbraco.Cms.Core.Deploy; /// /// Represents an artifact dependency. diff --git a/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs b/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs index c879695b48f0..1be524c86f5e 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDependencyCollection.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; namespace Umbraco.Cms.Core.Deploy; @@ -10,6 +10,8 @@ public class ArtifactDependencyCollection : ICollection { private readonly Dictionary _dependencies = new(); + public int Count => _dependencies.Count; + public IEnumerator GetEnumerator() => _dependencies.Values.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -38,7 +40,5 @@ public bool Contains(ArtifactDependency item) => public bool Remove(ArtifactDependency item) => throw new NotSupportedException(); - public int Count => _dependencies.Count; - public bool IsReadOnly => false; } diff --git a/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs b/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs index be3c27c565e5..b997b9c75970 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDependencyMode.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Deploy; +namespace Umbraco.Cms.Core.Deploy; /// /// Indicates the mode of the dependency. @@ -13,5 +13,5 @@ public enum ArtifactDependencyMode /// /// The dependency must exist. /// - Exist + Exist, } diff --git a/src/Umbraco.Core/Deploy/ArtifactDeployState.cs b/src/Umbraco.Core/Deploy/ArtifactDeployState.cs index e9274f38af2e..cee34706a397 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDeployState.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDeployState.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Deploy; +namespace Umbraco.Cms.Core.Deploy; /// /// Represent the state of an artifact being deployed. diff --git a/src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs b/src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs index 090ccc588a33..335f6e79d5ac 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDeployStateOfTArtifactTEntity.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Deploy; +namespace Umbraco.Cms.Core.Deploy; /// /// Represent the state of an artifact being deployed. diff --git a/src/Umbraco.Core/Deploy/ArtifactSignature.cs b/src/Umbraco.Core/Deploy/ArtifactSignature.cs index 89813866c99d..3dccddba2935 100644 --- a/src/Umbraco.Core/Deploy/ArtifactSignature.cs +++ b/src/Umbraco.Core/Deploy/ArtifactSignature.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Deploy; +namespace Umbraco.Cms.Core.Deploy; public sealed class ArtifactSignature : IArtifactSignature { diff --git a/src/Umbraco.Core/Deploy/Difference.cs b/src/Umbraco.Core/Deploy/Difference.cs index dc049d99bce4..d704642a9f9d 100644 --- a/src/Umbraco.Core/Deploy/Difference.cs +++ b/src/Umbraco.Core/Deploy/Difference.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Deploy; +namespace Umbraco.Cms.Core.Deploy; public class Difference { @@ -10,7 +10,9 @@ public Difference(string title, string? text = null, string? category = null) } public string Title { get; set; } + public string? Text { get; set; } + public string? Category { get; set; } public override string ToString() diff --git a/src/Umbraco.Core/Deploy/Direction.cs b/src/Umbraco.Core/Deploy/Direction.cs index 5f1e5b7a048a..30439380f222 100644 --- a/src/Umbraco.Core/Deploy/Direction.cs +++ b/src/Umbraco.Core/Deploy/Direction.cs @@ -1,7 +1,7 @@ -namespace Umbraco.Cms.Core.Deploy; +namespace Umbraco.Cms.Core.Deploy; public enum Direction { ToArtifact, - FromArtifact + FromArtifact, } diff --git a/src/Umbraco.Core/Deploy/IArtifact.cs b/src/Umbraco.Core/Deploy/IArtifact.cs index a3500c12c63c..faea983dee8a 100644 --- a/src/Umbraco.Core/Deploy/IArtifact.cs +++ b/src/Umbraco.Core/Deploy/IArtifact.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Deploy; +namespace Umbraco.Cms.Core.Deploy; /// /// Represents an artifact ie an object that can be transfered between environments. @@ -6,5 +6,6 @@ public interface IArtifact : IArtifactSignature { string Name { get; } + string? Alias { get; } } diff --git a/src/Umbraco.Core/Deploy/IArtifactSignature.cs b/src/Umbraco.Core/Deploy/IArtifactSignature.cs index 14e6e6ca6d3a..40b2395eed8f 100644 --- a/src/Umbraco.Core/Deploy/IArtifactSignature.cs +++ b/src/Umbraco.Core/Deploy/IArtifactSignature.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Deploy; +namespace Umbraco.Cms.Core.Deploy; /// /// Represents the signature of an artifact. diff --git a/src/Umbraco.Core/Deploy/IDeployContext.cs b/src/Umbraco.Core/Deploy/IDeployContext.cs index eb4443485175..bdc8fd8d61d4 100644 --- a/src/Umbraco.Core/Deploy/IDeployContext.cs +++ b/src/Umbraco.Core/Deploy/IDeployContext.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Deploy; +namespace Umbraco.Cms.Core.Deploy; /// /// Represents a deployment context. @@ -34,10 +34,11 @@ public interface IDeployContext /// The type of the item. /// The key of the item. /// The item with the specified key and type, if any, else null. - T? Item(string key) where T : class; + T? Item(string key) + where T : class; ///// ///// Gets the global deployment cancellation token. ///// - //CancellationToken CancellationToken { get; } + // CancellationToken CancellationToken { get; } } diff --git a/src/Umbraco.Core/Deploy/IFileSource.cs b/src/Umbraco.Core/Deploy/IFileSource.cs index 5b9d1999910a..ed169b9df531 100644 --- a/src/Umbraco.Core/Deploy/IFileSource.cs +++ b/src/Umbraco.Core/Deploy/IFileSource.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Deploy; +namespace Umbraco.Cms.Core.Deploy; /// /// Represents a file source, ie a mean for a target environment involved in a @@ -81,5 +81,5 @@ public interface IFileSource ///// ///// A file entity identifier. ///// A byte array containing the file content. - //byte[] GetFileBytes(StringUdi Udi); + // byte[] GetFileBytes(StringUdi Udi); } diff --git a/src/Umbraco.Core/Deploy/IFileType.cs b/src/Umbraco.Core/Deploy/IFileType.cs index 3b16afb75031..466c87a3edba 100644 --- a/src/Umbraco.Core/Deploy/IFileType.cs +++ b/src/Umbraco.Core/Deploy/IFileType.cs @@ -1,8 +1,9 @@ -namespace Umbraco.Cms.Core.Deploy; +namespace Umbraco.Cms.Core.Deploy; public interface IFileType { bool CanSetPhysical { get; } + Stream GetStream(StringUdi udi); Task GetStreamAsync(StringUdi udi, CancellationToken token); diff --git a/src/Umbraco.Core/Deploy/IFileTypeCollection.cs b/src/Umbraco.Core/Deploy/IFileTypeCollection.cs index 3bede6bd0502..2ae2bb4bb919 100644 --- a/src/Umbraco.Core/Deploy/IFileTypeCollection.cs +++ b/src/Umbraco.Core/Deploy/IFileTypeCollection.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Deploy; +namespace Umbraco.Cms.Core.Deploy; public interface IFileTypeCollection { diff --git a/src/Umbraco.Core/Deploy/IImageSourceParser.cs b/src/Umbraco.Core/Deploy/IImageSourceParser.cs index ea3ad6d56a2c..7b9e3f5e9618 100644 --- a/src/Umbraco.Core/Deploy/IImageSourceParser.cs +++ b/src/Umbraco.Core/Deploy/IImageSourceParser.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Deploy; +namespace Umbraco.Cms.Core.Deploy; /// /// Provides methods to parse image tag sources in property values. diff --git a/src/Umbraco.Core/Deploy/ILocalLinkParser.cs b/src/Umbraco.Core/Deploy/ILocalLinkParser.cs index 6083602c15d5..7ec3fff0fa3a 100644 --- a/src/Umbraco.Core/Deploy/ILocalLinkParser.cs +++ b/src/Umbraco.Core/Deploy/ILocalLinkParser.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Deploy; +namespace Umbraco.Cms.Core.Deploy; /// /// Provides methods to parse local link tags in property values. diff --git a/src/Umbraco.Core/Deploy/IMacroParser.cs b/src/Umbraco.Core/Deploy/IMacroParser.cs index 0aaaa09c2cee..1945b2bdb3c6 100644 --- a/src/Umbraco.Core/Deploy/IMacroParser.cs +++ b/src/Umbraco.Core/Deploy/IMacroParser.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Deploy; +namespace Umbraco.Cms.Core.Deploy; public interface IMacroParser { diff --git a/src/Umbraco.Core/Deploy/IServiceConnector.cs b/src/Umbraco.Core/Deploy/IServiceConnector.cs index 90b1f570e9f3..f6cd7c80024f 100644 --- a/src/Umbraco.Core/Deploy/IServiceConnector.cs +++ b/src/Umbraco.Core/Deploy/IServiceConnector.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Deploy; diff --git a/src/Umbraco.Core/Deploy/IUniqueIdentifyingServiceConnector.cs b/src/Umbraco.Core/Deploy/IUniqueIdentifyingServiceConnector.cs index e7844642be96..c68906bbbf1d 100644 --- a/src/Umbraco.Core/Deploy/IUniqueIdentifyingServiceConnector.cs +++ b/src/Umbraco.Core/Deploy/IUniqueIdentifyingServiceConnector.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Deploy; +namespace Umbraco.Cms.Core.Deploy; /// /// Provides a method to retrieve an artifact's unique identifier. diff --git a/src/Umbraco.Core/Deploy/IValueConnector.cs b/src/Umbraco.Core/Deploy/IValueConnector.cs index b10d9bab0489..f2a776c7ca3a 100644 --- a/src/Umbraco.Core/Deploy/IValueConnector.cs +++ b/src/Umbraco.Core/Deploy/IValueConnector.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Deploy; @@ -30,8 +30,7 @@ public interface IValueConnector /// Gets the content property value corresponding to a deploy property value. /// /// The deploy property value. - /// - /// The value property type< + /// The value property type /// The current content property value. /// The content property value. object? FromArtifact(string? value, IPropertyType propertyType, object? currentValue); diff --git a/src/Umbraco.Core/Dictionary/ICultureDictionary.cs b/src/Umbraco.Core/Dictionary/ICultureDictionary.cs index 3b9a6b7b6fc2..380f7ee28719 100644 --- a/src/Umbraco.Core/Dictionary/ICultureDictionary.cs +++ b/src/Umbraco.Core/Dictionary/ICultureDictionary.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; namespace Umbraco.Cms.Core.Dictionary; @@ -7,6 +7,11 @@ namespace Umbraco.Cms.Core.Dictionary; /// public interface ICultureDictionary { + /// + /// Returns the current culture + /// + CultureInfo Culture { get; } + /// /// Returns the dictionary value based on the key supplied /// @@ -14,11 +19,6 @@ public interface ICultureDictionary /// string? this[string key] { get; } - /// - /// Returns the current culture - /// - CultureInfo Culture { get; } - /// /// Returns the child dictionary entries for a given key /// diff --git a/src/Umbraco.Core/Dictionary/ICultureDictionaryFactory.cs b/src/Umbraco.Core/Dictionary/ICultureDictionaryFactory.cs index f01b6efbc347..6cb2642b1539 100644 --- a/src/Umbraco.Core/Dictionary/ICultureDictionaryFactory.cs +++ b/src/Umbraco.Core/Dictionary/ICultureDictionaryFactory.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Dictionary; +namespace Umbraco.Cms.Core.Dictionary; public interface ICultureDictionaryFactory { diff --git a/src/Umbraco.Core/Direction.cs b/src/Umbraco.Core/Direction.cs index 941d96d9bc93..874a00a4ac87 100644 --- a/src/Umbraco.Core/Direction.cs +++ b/src/Umbraco.Core/Direction.cs @@ -1,7 +1,7 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; public enum Direction { Ascending = 0, - Descending = 1 + Descending = 1, } diff --git a/src/Umbraco.Core/DistributedLocking/DistributedLockType.cs b/src/Umbraco.Core/DistributedLocking/DistributedLockType.cs index 9b6030702d87..8ae47fce08f5 100644 --- a/src/Umbraco.Core/DistributedLocking/DistributedLockType.cs +++ b/src/Umbraco.Core/DistributedLocking/DistributedLockType.cs @@ -6,5 +6,5 @@ namespace Umbraco.Cms.Core.DistributedLocking; public enum DistributedLockType { ReadLock, - WriteLock + WriteLock, } diff --git a/src/Umbraco.Core/Editors/BackOfficePreviewModel.cs b/src/Umbraco.Core/Editors/BackOfficePreviewModel.cs index 8a66e890a28b..6ab0b76e3307 100644 --- a/src/Umbraco.Core/Editors/BackOfficePreviewModel.cs +++ b/src/Umbraco.Core/Editors/BackOfficePreviewModel.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Features; +using Umbraco.Cms.Core.Features; using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Editors; @@ -14,6 +14,8 @@ public BackOfficePreviewModel(UmbracoFeatures features, IEnumerable l } public IEnumerable Languages { get; } + public bool DisableDevicePreview => _features.Disabled.DisableDevicePreview; + public string? PreviewExtendedHeaderView => _features.Enabled.PreviewExtendedView; } diff --git a/src/Umbraco.Core/Editors/EditorValidatorCollection.cs b/src/Umbraco.Core/Editors/EditorValidatorCollection.cs index f63526cc8b31..a1c46cdb57e5 100644 --- a/src/Umbraco.Core/Editors/EditorValidatorCollection.cs +++ b/src/Umbraco.Core/Editors/EditorValidatorCollection.cs @@ -4,7 +4,8 @@ namespace Umbraco.Cms.Core.Editors; public class EditorValidatorCollection : BuilderCollectionBase { - public EditorValidatorCollection(Func> items) : base(items) + public EditorValidatorCollection(Func> items) + : base(items) { } } diff --git a/src/Umbraco.Core/Editors/EditorValidatorCollectionBuilder.cs b/src/Umbraco.Core/Editors/EditorValidatorCollectionBuilder.cs index a128453bf62c..b7b5269ee7d0 100644 --- a/src/Umbraco.Core/Editors/EditorValidatorCollectionBuilder.cs +++ b/src/Umbraco.Core/Editors/EditorValidatorCollectionBuilder.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Editors; diff --git a/src/Umbraco.Core/Editors/EditorValidatorOfT.cs b/src/Umbraco.Core/Editors/EditorValidatorOfT.cs index 6b698075d15a..3e2b8995192b 100644 --- a/src/Umbraco.Core/Editors/EditorValidatorOfT.cs +++ b/src/Umbraco.Core/Editors/EditorValidatorOfT.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Umbraco.Cms.Core.Editors; diff --git a/src/Umbraco.Core/Editors/IEditorValidator.cs b/src/Umbraco.Core/Editors/IEditorValidator.cs index 6ddb4ef14035..2f6bc9f110e5 100644 --- a/src/Umbraco.Core/Editors/IEditorValidator.cs +++ b/src/Umbraco.Core/Editors/IEditorValidator.cs @@ -1,7 +1,8 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Editors; + // note - about IEditorValidator // // interface: IEditorValidator diff --git a/src/Umbraco.Core/Enum.cs b/src/Umbraco.Core/Enum.cs index cbc54d602bea..6084dfe9719e 100644 --- a/src/Umbraco.Core/Enum.cs +++ b/src/Umbraco.Core/Enum.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; /// /// Provides utility methods for handling enumerations. @@ -60,7 +60,8 @@ public static T Parse(string value, bool ignoreCase = false) return parsed; } - throw new ArgumentException($"Value \"{value}\"is not a valid {typeof(T).Name} enumeration value.", + throw new ArgumentException( + $"Value \"{value}\"is not a valid {typeof(T).Name} enumeration value.", nameof(value)); } diff --git a/src/Umbraco.Core/Events/CancellableEnumerableObjectEventArgs.cs b/src/Umbraco.Core/Events/CancellableEnumerableObjectEventArgs.cs index 5989444714dd..22c7ef4c7e22 100644 --- a/src/Umbraco.Core/Events/CancellableEnumerableObjectEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableEnumerableObjectEventArgs.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; /// /// Represents event data, for events that support cancellation, and expose impacted objects. @@ -7,14 +7,12 @@ public class CancellableEnumerableObjectEventArgs : CancellableObjectEventArgs>, IEquatable> { - public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel, - EventMessages messages, IDictionary additionalData) + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) : base(eventObject, canCancel, messages, additionalData) { } - public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel, - EventMessages eventMessages) + public CancellableEnumerableObjectEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) : base(eventObject, canCancel, eventMessages) { } diff --git a/src/Umbraco.Core/Events/CancellableEventArgs.cs b/src/Umbraco.Core/Events/CancellableEventArgs.cs index e3f3418ae713..7768da05f5cd 100644 --- a/src/Umbraco.Core/Events/CancellableEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableEventArgs.cs @@ -1,4 +1,4 @@ -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; namespace Umbraco.Cms.Core.Events; @@ -7,8 +7,7 @@ namespace Umbraco.Cms.Core.Events; /// public class CancellableEventArgs : EventArgs, IEquatable { - private static readonly ReadOnlyDictionary EmptyAdditionalData = - new(new Dictionary()); + private static readonly ReadOnlyDictionary EmptyAdditionalData = new(new Dictionary()); private bool _cancel; private IDictionary? _eventState; @@ -22,20 +21,16 @@ public CancellableEventArgs(bool canCancel, EventMessages messages, IDictionary< public CancellableEventArgs(bool canCancel, EventMessages eventMessages) { - if (eventMessages == null) - { - throw new ArgumentNullException("eventMessages"); - } - CanCancel = canCancel; - Messages = eventMessages; + Messages = eventMessages ?? throw new ArgumentNullException("eventMessages"); AdditionalData = EmptyAdditionalData; } public CancellableEventArgs(bool canCancel) { CanCancel = canCancel; - //create a standalone messages + + // create a standalone messages Messages = new EventMessages(); AdditionalData = EmptyAdditionalData; } @@ -69,6 +64,7 @@ public bool Cancel return _cancel; } + set { if (CanCancel == false) @@ -103,10 +99,14 @@ public bool Cancel /// public IDictionary EventState { - get => _eventState ?? (_eventState = new Dictionary()); + get => _eventState ??= new Dictionary(); set => _eventState = value; } + public static bool operator ==(CancellableEventArgs? left, CancellableEventArgs? right) => Equals(left, right); + + public static bool operator !=(CancellableEventArgs left, CancellableEventArgs right) => Equals(left, right) == false; + public bool Equals(CancellableEventArgs? other) { if (ReferenceEquals(null, other)) @@ -154,9 +154,4 @@ public override bool Equals(object? obj) } public override int GetHashCode() => AdditionalData != null ? AdditionalData.GetHashCode() : 0; - - public static bool operator ==(CancellableEventArgs? left, CancellableEventArgs? right) => Equals(left, right); - - public static bool operator !=(CancellableEventArgs left, CancellableEventArgs right) => - Equals(left, right) == false; } diff --git a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs index df0702cd7d19..26aa61b67a9e 100644 --- a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs +++ b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; /// /// Provides a base class for classes representing event data, for events that support cancellation, and expose an @@ -6,8 +6,7 @@ /// public abstract class CancellableObjectEventArgs : CancellableEventArgs { - protected CancellableObjectEventArgs(object? eventObject, bool canCancel, EventMessages messages, - IDictionary additionalData) + protected CancellableObjectEventArgs(object? eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) : base(canCancel, messages, additionalData) => EventObject = eventObject; diff --git a/src/Umbraco.Core/Events/CancellableObjectEventArgsOfTEventObject.cs b/src/Umbraco.Core/Events/CancellableObjectEventArgsOfTEventObject.cs index 20175b101302..5d9865c253ae 100644 --- a/src/Umbraco.Core/Events/CancellableObjectEventArgsOfTEventObject.cs +++ b/src/Umbraco.Core/Events/CancellableObjectEventArgsOfTEventObject.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; /// /// Represent event data, for events that support cancellation, and expose an impacted object. @@ -7,8 +7,7 @@ public class CancellableObjectEventArgs : CancellableObjectEventArgs, IEquatable> { - public CancellableObjectEventArgs(TEventObject? eventObject, bool canCancel, EventMessages messages, - IDictionary additionalData) + public CancellableObjectEventArgs(TEventObject? eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) : base(eventObject, canCancel, messages, additionalData) { } @@ -45,6 +44,14 @@ public CancellableObjectEventArgs(TEventObject? eventObject) set => base.EventObject = value; } + public static bool operator ==( + CancellableObjectEventArgs left, + CancellableObjectEventArgs right) => Equals(left, right); + + public static bool operator !=( + CancellableObjectEventArgs left, + CancellableObjectEventArgs right) => !Equals(left, right); + public bool Equals(CancellableObjectEventArgs? other) { if (other is null) @@ -92,10 +99,4 @@ public override int GetHashCode() return base.GetHashCode() * 397; } } - - public static bool operator ==(CancellableObjectEventArgs left, - CancellableObjectEventArgs right) => Equals(left, right); - - public static bool operator !=(CancellableObjectEventArgs left, - CancellableObjectEventArgs right) => !Equals(left, right); } diff --git a/src/Umbraco.Core/Events/ContentCacheEventArgs.cs b/src/Umbraco.Core/Events/ContentCacheEventArgs.cs index 5509ddd694be..732e6f2452b4 100644 --- a/src/Umbraco.Core/Events/ContentCacheEventArgs.cs +++ b/src/Umbraco.Core/Events/ContentCacheEventArgs.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; namespace Umbraco.Cms.Core.Events; diff --git a/src/Umbraco.Core/Events/CopyEventArgs.cs b/src/Umbraco.Core/Events/CopyEventArgs.cs index c5f480357046..bead8213b618 100644 --- a/src/Umbraco.Core/Events/CopyEventArgs.cs +++ b/src/Umbraco.Core/Events/CopyEventArgs.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; public class CopyEventArgs : CancellableObjectEventArgs, IEquatable> { @@ -41,6 +41,8 @@ public CopyEventArgs(TEntity eventObject, TEntity copy, bool canCancel, int pare public bool RelateToOriginal { get; set; } + public static bool operator ==(CopyEventArgs left, CopyEventArgs right) => Equals(left, right); + public bool Equals(CopyEventArgs? other) { if (ReferenceEquals(null, other)) @@ -93,7 +95,5 @@ public override int GetHashCode() } } - public static bool operator ==(CopyEventArgs left, CopyEventArgs right) => Equals(left, right); - public static bool operator !=(CopyEventArgs left, CopyEventArgs right) => !Equals(left, right); } diff --git a/src/Umbraco.Core/Events/DeleteEventArgs.cs b/src/Umbraco.Core/Events/DeleteEventArgs.cs index a820227c6049..3ca366834f7e 100644 --- a/src/Umbraco.Core/Events/DeleteEventArgs.cs +++ b/src/Umbraco.Core/Events/DeleteEventArgs.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; [SupersedeEvent(typeof(SaveEventArgs<>))] [SupersedeEvent(typeof(PublishEventArgs<>))] @@ -13,15 +13,17 @@ public class DeleteEventArgs : CancellableEnumerableObjectEventArgs /// /// - public DeleteEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) : - base(eventObject, canCancel, eventMessages) => MediaFilesToDelete = new List(); + public DeleteEventArgs(IEnumerable eventObject, bool canCancel, EventMessages eventMessages) + : base(eventObject, canCancel, eventMessages) => MediaFilesToDelete = new List(); /// /// Constructor accepting multiple entities that are used in the delete operation /// /// /// - public DeleteEventArgs(IEnumerable eventObject, EventMessages eventMessages) : base(eventObject, + public DeleteEventArgs(IEnumerable eventObject, EventMessages eventMessages) + : base( + eventObject, eventMessages) => MediaFilesToDelete = new List(); /// @@ -30,7 +32,7 @@ public DeleteEventArgs(IEnumerable eventObject, EventMessages eventMess /// /// public DeleteEventArgs(TEntity eventObject, EventMessages eventMessages) - : base(new List {eventObject}, eventMessages) => + : base(new List { eventObject }, eventMessages) => MediaFilesToDelete = new List(); /// @@ -40,7 +42,7 @@ public DeleteEventArgs(TEntity eventObject, EventMessages eventMessages) /// /// public DeleteEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) - : base(new List {eventObject}, canCancel, eventMessages) => + : base(new List { eventObject }, canCancel, eventMessages) => MediaFilesToDelete = new List(); /// @@ -48,14 +50,16 @@ public DeleteEventArgs(TEntity eventObject, bool canCancel, EventMessages eventM /// /// /// - public DeleteEventArgs(IEnumerable eventObject, bool canCancel) : base(eventObject, canCancel) => + public DeleteEventArgs(IEnumerable eventObject, bool canCancel) + : base(eventObject, canCancel) => MediaFilesToDelete = new List(); /// /// Constructor accepting multiple entities that are used in the delete operation /// /// - public DeleteEventArgs(IEnumerable eventObject) : base(eventObject) => + public DeleteEventArgs(IEnumerable eventObject) + : base(eventObject) => MediaFilesToDelete = new List(); /// @@ -63,7 +67,7 @@ public DeleteEventArgs(IEnumerable eventObject) : base(eventObject) => /// /// public DeleteEventArgs(TEntity eventObject) - : base(new List {eventObject}) => + : base(new List { eventObject }) => MediaFilesToDelete = new List(); /// @@ -72,7 +76,7 @@ public DeleteEventArgs(TEntity eventObject) /// /// public DeleteEventArgs(TEntity eventObject, bool canCancel) - : base(new List {eventObject}, canCancel) => + : base(new List { eventObject }, canCancel) => MediaFilesToDelete = new List(); /// @@ -89,6 +93,9 @@ public IEnumerable DeletedEntities /// public List MediaFilesToDelete { get; } + public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) => + Equals(left, right); + public bool Equals(DeleteEventArgs? other) { if (ReferenceEquals(null, other)) @@ -132,9 +139,6 @@ public override int GetHashCode() } } - public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) => - Equals(left, right); - public static bool operator !=(DeleteEventArgs left, DeleteEventArgs right) => !Equals(left, right); } @@ -156,6 +160,8 @@ public DeleteEventArgs(int id, bool canCancel) /// public int Id { get; } + public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) => Equals(left, right); + public bool Equals(DeleteEventArgs? other) { if (ReferenceEquals(null, other)) @@ -199,7 +205,5 @@ public override int GetHashCode() } } - public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right) => Equals(left, right); - public static bool operator !=(DeleteEventArgs left, DeleteEventArgs right) => !Equals(left, right); } diff --git a/src/Umbraco.Core/Events/EventAggregator.Notifications.cs b/src/Umbraco.Core/Events/EventAggregator.Notifications.cs index d0a7d9957001..d298f5bbec60 100644 --- a/src/Umbraco.Core/Events/EventAggregator.Notifications.cs +++ b/src/Umbraco.Core/Events/EventAggregator.Notifications.cs @@ -12,15 +12,15 @@ namespace Umbraco.Cms.Core.Events; /// public partial class EventAggregator : IEventAggregator { - private static readonly ConcurrentDictionary s_notificationAsyncHandlers + private static readonly ConcurrentDictionary NotificationAsyncHandlers = new(); - private static readonly ConcurrentDictionary s_notificationHandlers = new(); + private static readonly ConcurrentDictionary NotificationHandlers = new(); private Task PublishNotificationAsync(INotification notification, CancellationToken cancellationToken = default) { Type notificationType = notification.GetType(); - NotificationAsyncHandlerWrapper asyncHandler = s_notificationAsyncHandlers.GetOrAdd( + NotificationAsyncHandlerWrapper asyncHandler = NotificationAsyncHandlers.GetOrAdd( notificationType, t => { @@ -37,7 +37,7 @@ private Task PublishNotificationAsync(INotification notification, CancellationTo private void PublishNotification(INotification notification) { Type notificationType = notification.GetType(); - NotificationHandlerWrapper? asyncHandler = s_notificationHandlers.GetOrAdd( + NotificationHandlerWrapper? asyncHandler = NotificationHandlers.GetOrAdd( notificationType, t => { diff --git a/src/Umbraco.Core/Events/EventAggregator.cs b/src/Umbraco.Core/Events/EventAggregator.cs index 82c9b49dfe01..277b24eb061e 100644 --- a/src/Umbraco.Core/Events/EventAggregator.cs +++ b/src/Umbraco.Core/Events/EventAggregator.cs @@ -13,6 +13,30 @@ namespace Umbraco.Cms.Core.Events; /// An instance of type . public delegate object ServiceFactory(Type serviceType); +/// +/// Extensions for . +/// +public static class ServiceFactoryExtensions +{ + /// + /// Gets an instance of . + /// + /// The type to return. + /// The service factory. + /// The new instance. + public static T GetInstance(this ServiceFactory factory) + => (T)factory(typeof(T)); + + /// + /// Gets a collection of instances of . + /// + /// The collection item type to return. + /// The service factory. + /// The new instance collection. + public static IEnumerable GetInstances(this ServiceFactory factory) + => (IEnumerable)factory(typeof(IEnumerable)); +} + /// public partial class EventAggregator : IEventAggregator { @@ -86,27 +110,3 @@ public async Task PublishCancelableAsync(TCancela return notification.Cancel; } } - -/// -/// Extensions for . -/// -public static class ServiceFactoryExtensions -{ - /// - /// Gets an instance of . - /// - /// The type to return. - /// The service factory. - /// The new instance. - public static T GetInstance(this ServiceFactory factory) - => (T)factory(typeof(T)); - - /// - /// Gets a collection of instances of . - /// - /// The collection item type to return. - /// The service factory. - /// The new instance collection. - public static IEnumerable GetInstances(this ServiceFactory factory) - => (IEnumerable)factory(typeof(IEnumerable)); -} diff --git a/src/Umbraco.Core/Events/EventDefinition.cs b/src/Umbraco.Core/Events/EventDefinition.cs index ec39fc31673e..3f7cd382ed1c 100644 --- a/src/Umbraco.Core/Events/EventDefinition.cs +++ b/src/Umbraco.Core/Events/EventDefinition.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; public class EventDefinition : EventDefinitionBase { @@ -16,10 +16,7 @@ public EventDefinition(EventHandler trackedEvent, object sender, EventArgs args, public override void RaiseEvent() { - if (_trackedEvent != null) - { - _trackedEvent(_sender, _args); - } + _trackedEvent?.Invoke(_sender, _args); } } @@ -29,8 +26,7 @@ public class EventDefinition : EventDefinitionBase private readonly object _sender; private readonly EventHandler _trackedEvent; - public EventDefinition(EventHandler trackedEvent, object sender, TEventArgs args, - string? eventName = null) + public EventDefinition(EventHandler trackedEvent, object sender, TEventArgs args, string? eventName = null) : base(sender, args, eventName) { _trackedEvent = trackedEvent; @@ -40,10 +36,7 @@ public EventDefinition(EventHandler trackedEvent, object sender, TEv public override void RaiseEvent() { - if (_trackedEvent != null) - { - _trackedEvent(_sender, _args); - } + _trackedEvent?.Invoke(_sender, _args); } } @@ -53,8 +46,7 @@ public class EventDefinition : EventDefinitionBase private readonly TSender _sender; private readonly TypedEventHandler _trackedEvent; - public EventDefinition(TypedEventHandler trackedEvent, TSender sender, TEventArgs args, - string? eventName = null) + public EventDefinition(TypedEventHandler trackedEvent, TSender sender, TEventArgs args, string? eventName = null) : base(sender, args, eventName) { _trackedEvent = trackedEvent; @@ -64,9 +56,6 @@ public EventDefinition(TypedEventHandler trackedEvent, TSen public override void RaiseEvent() { - if (_trackedEvent != null) - { - _trackedEvent(_sender, _args); - } + _trackedEvent?.Invoke(_sender, _args); } } diff --git a/src/Umbraco.Core/Events/EventDefinitionBase.cs b/src/Umbraco.Core/Events/EventDefinitionBase.cs index fd6466294639..8ac84c470d1f 100644 --- a/src/Umbraco.Core/Events/EventDefinitionBase.cs +++ b/src/Umbraco.Core/Events/EventDefinitionBase.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Events; @@ -14,7 +14,7 @@ protected EventDefinitionBase(object? sender, object? args, string? eventName = if (EventName.IsNullOrWhiteSpace()) { // don't match "Ing" suffixed names - Attempt findResult = + Attempt findResult = EventNameExtractor.FindEvent(sender, args, EventNameExtractor.MatchIngNames); if (findResult.Success == false) @@ -29,6 +29,8 @@ protected EventDefinitionBase(object? sender, object? args, string? eventName = } } + public object Sender { get; } + public bool Equals(EventDefinitionBase? other) { if (ReferenceEquals(null, other)) @@ -44,10 +46,12 @@ public bool Equals(EventDefinitionBase? other) return Args.Equals(other.Args) && string.Equals(EventName, other.EventName) && Sender.Equals(other.Sender); } - public object Sender { get; } public object Args { get; } + public string? EventName { get; } + public static bool operator ==(EventDefinitionBase left, EventDefinitionBase right) => Equals(left, right); + public abstract void RaiseEvent(); public override bool Equals(object? obj) @@ -85,7 +89,5 @@ public override int GetHashCode() } } - public static bool operator ==(EventDefinitionBase left, EventDefinitionBase right) => Equals(left, right); - public static bool operator !=(EventDefinitionBase left, EventDefinitionBase right) => Equals(left, right) == false; } diff --git a/src/Umbraco.Core/Events/EventDefinitionFilter.cs b/src/Umbraco.Core/Events/EventDefinitionFilter.cs index 7c007164ab45..4872b23e8b84 100644 --- a/src/Umbraco.Core/Events/EventDefinitionFilter.cs +++ b/src/Umbraco.Core/Events/EventDefinitionFilter.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; /// /// The filter used in the GetEvents method which determines @@ -19,5 +19,5 @@ public enum EventDefinitionFilter /// /// Deduplicates events and only returns the last duplicate instance tracked /// - LastIn + LastIn, } diff --git a/src/Umbraco.Core/Events/EventExtensions.cs b/src/Umbraco.Core/Events/EventExtensions.cs index b1a3af404517..6d9fd8103b2c 100644 --- a/src/Umbraco.Core/Events/EventExtensions.cs +++ b/src/Umbraco.Core/Events/EventExtensions.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; /// /// Extension methods for cancellable event operations @@ -18,8 +18,7 @@ public static class EventExtensions /// The event data. /// A value indicating whether the cancelable event should be cancelled /// A cancelable event is raised by a component when it is about to perform an action that can be canceled. - public static bool IsRaisedEventCancelled(this TypedEventHandler eventHandler, - TArgs args, TSender sender) + public static bool IsRaisedEventCancelled(this TypedEventHandler eventHandler, TArgs args, TSender sender) where TArgs : CancellableEventArgs { if (eventHandler == null) @@ -39,8 +38,7 @@ public static bool IsRaisedEventCancelled(this TypedEventHandler /// The event handler. /// The event source. /// The event data. - public static void RaiseEvent(this TypedEventHandler eventHandler, TArgs args, - TSender sender) + public static void RaiseEvent(this TypedEventHandler eventHandler, TArgs args, TSender sender) where TArgs : EventArgs { if (eventHandler == null) diff --git a/src/Umbraco.Core/Events/EventMessage.cs b/src/Umbraco.Core/Events/EventMessage.cs index 02fb1ee370a3..8ba2c98bf804 100644 --- a/src/Umbraco.Core/Events/EventMessage.cs +++ b/src/Umbraco.Core/Events/EventMessage.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; /// /// An event message @@ -16,7 +16,9 @@ public EventMessage(string category, string message, EventMessageType messageTyp } public string Category { get; } + public string Message { get; } + public EventMessageType MessageType { get; } /// diff --git a/src/Umbraco.Core/Events/EventMessageType.cs b/src/Umbraco.Core/Events/EventMessageType.cs index a4b64d8d1787..a3c6ebf2f95b 100644 --- a/src/Umbraco.Core/Events/EventMessageType.cs +++ b/src/Umbraco.Core/Events/EventMessageType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; /// /// The type of event message @@ -9,5 +9,5 @@ public enum EventMessageType Info = 1, Error = 2, Success = 3, - Warning = 4 + Warning = 4, } diff --git a/src/Umbraco.Core/Events/EventMessages.cs b/src/Umbraco.Core/Events/EventMessages.cs index 7f3fa4cd8b8c..68d19f27fd15 100644 --- a/src/Umbraco.Core/Events/EventMessages.cs +++ b/src/Umbraco.Core/Events/EventMessages.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; /// /// Event messages collection diff --git a/src/Umbraco.Core/Events/EventNameExtractor.cs b/src/Umbraco.Core/Events/EventNameExtractor.cs index b6393d6a9fa2..16f772dcb231 100644 --- a/src/Umbraco.Core/Events/EventNameExtractor.cs +++ b/src/Umbraco.Core/Events/EventNameExtractor.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Reflection; using System.Text.RegularExpressions; @@ -45,8 +45,7 @@ public class EventNameExtractor /// /// null if not found or an ambiguous match /// - public static Attempt FindEvent(Type senderType, Type argsType, - Func exclude) + public static Attempt FindEvent(Type senderType, Type argsType, Func exclude) { var events = FindEvents(senderType, argsType, exclude); @@ -59,7 +58,7 @@ public class EventNameExtractor return Attempt.Succeed(new EventNameExtractorResult(events[0])); default: - //there's more than one left so it's ambiguous! + // there's more than one left so it's ambiguous! return Attempt.Fail(new EventNameExtractorResult(EventNameExtractorError.Ambiguous)); } } @@ -72,11 +71,13 @@ public static string[] FindEvents(Type senderType, Type argsType, Func x.EventHandlerType?.IsGenericType ?? false) .Select(x => new EventInfoArgs(x, x.EventHandlerType!.GetGenericArguments())) - //we are only looking for event handlers that have more than one generic argument + + // we are only looking for event handlers that have more than one generic argument .Where(x => { if (x.GenericArgs.Length == 1) @@ -84,7 +85,7 @@ public static string[] FindEvents(Type senderType, Type argsType, Func) && x.GenericArgs.Length == 2) { @@ -103,7 +104,7 @@ public static string[] FindEvents(Type senderType, Type argsType, Func) && x.GenericArgs.Length == 2 && x.GenericArgs[1] == tuple.Item2) @@ -177,6 +178,7 @@ public EventInfoArgs(EventInfo eventInfo, Type[] genericArgs) } public EventInfo EventInfo { get; } + public Type[] GenericArgs { get; } } } diff --git a/src/Umbraco.Core/Events/EventNameExtractorError.cs b/src/Umbraco.Core/Events/EventNameExtractorError.cs index 3e9029c98a99..a2fd01f1c1df 100644 --- a/src/Umbraco.Core/Events/EventNameExtractorError.cs +++ b/src/Umbraco.Core/Events/EventNameExtractorError.cs @@ -1,7 +1,7 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; public enum EventNameExtractorError { NoneFound, - Ambiguous + Ambiguous, } diff --git a/src/Umbraco.Core/Events/EventNameExtractorResult.cs b/src/Umbraco.Core/Events/EventNameExtractorResult.cs index 5e7107a39311..fb847a02821a 100644 --- a/src/Umbraco.Core/Events/EventNameExtractorResult.cs +++ b/src/Umbraco.Core/Events/EventNameExtractorResult.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; public class EventNameExtractorResult { @@ -7,5 +7,6 @@ public class EventNameExtractorResult public EventNameExtractorResult(EventNameExtractorError? error) => Error = error; public EventNameExtractorError? Error { get; } + public string? Name { get; } } diff --git a/src/Umbraco.Core/Events/ExportedMemberEventArgs.cs b/src/Umbraco.Core/Events/ExportedMemberEventArgs.cs index 6d208ffee6f2..06b7ff81f4bc 100644 --- a/src/Umbraco.Core/Events/ExportedMemberEventArgs.cs +++ b/src/Umbraco.Core/Events/ExportedMemberEventArgs.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; namespace Umbraco.Cms.Core.Events; @@ -12,5 +12,6 @@ public ExportedMemberEventArgs(IMember member, MemberExportModel exported) } public IMember Member { get; } + public MemberExportModel Exported { get; } } diff --git a/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs b/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs index 2be700f27fa1..4aaeeac29de8 100644 --- a/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs +++ b/src/Umbraco.Core/Events/IDeletingMediaFilesEventArgs.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; public interface IDeletingMediaFilesEventArgs { diff --git a/src/Umbraco.Core/Events/IEventDefinition.cs b/src/Umbraco.Core/Events/IEventDefinition.cs index 15e8117b107a..d10b931548f6 100644 --- a/src/Umbraco.Core/Events/IEventDefinition.cs +++ b/src/Umbraco.Core/Events/IEventDefinition.cs @@ -1,9 +1,11 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; public interface IEventDefinition { object Sender { get; } + object Args { get; } + string? EventName { get; } void RaiseEvent(); diff --git a/src/Umbraco.Core/Events/IEventDispatcher.cs b/src/Umbraco.Core/Events/IEventDispatcher.cs index 3c9bb0a7b502..9d15a74c0241 100644 --- a/src/Umbraco.Core/Events/IEventDispatcher.cs +++ b/src/Umbraco.Core/Events/IEventDispatcher.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; /// /// Dispatches events from within a scope. @@ -51,8 +51,7 @@ bool DispatchCancelable(EventHandler eventHandler, object sender, /// The optional name of the event. /// A value indicating whether the cancelable event was cancelled. /// See general remarks on the interface. - bool DispatchCancelable(TypedEventHandler eventHandler, TSender sender, TArgs args, - string? name = null) + bool DispatchCancelable(TypedEventHandler eventHandler, TSender sender, TArgs args, string? name = null) where TArgs : CancellableEventArgs; /// @@ -83,8 +82,7 @@ bool DispatchCancelable(TypedEventHandler eventH /// The event data. /// The optional name of the event. /// See general remarks on the interface. - void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, - string? name = null); + void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, string? name = null); /// /// Notifies the dispatcher that the scope is exiting. diff --git a/src/Umbraco.Core/Events/IEventMessagesAccessor.cs b/src/Umbraco.Core/Events/IEventMessagesAccessor.cs index 0592643d17ac..e88ba73deedf 100644 --- a/src/Umbraco.Core/Events/IEventMessagesAccessor.cs +++ b/src/Umbraco.Core/Events/IEventMessagesAccessor.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; public interface IEventMessagesAccessor { diff --git a/src/Umbraco.Core/Events/IEventMessagesFactory.cs b/src/Umbraco.Core/Events/IEventMessagesFactory.cs index df69a7f14208..9ade74d20a5b 100644 --- a/src/Umbraco.Core/Events/IEventMessagesFactory.cs +++ b/src/Umbraco.Core/Events/IEventMessagesFactory.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; /// /// Event messages factory diff --git a/src/Umbraco.Core/Events/INotificationAsyncHandler.cs b/src/Umbraco.Core/Events/INotificationAsyncHandler.cs index be9371949e46..25a46ed250d5 100644 --- a/src/Umbraco.Core/Events/INotificationAsyncHandler.cs +++ b/src/Umbraco.Core/Events/INotificationAsyncHandler.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Notifications; diff --git a/src/Umbraco.Core/Events/MacroErrorEventArgs.cs b/src/Umbraco.Core/Events/MacroErrorEventArgs.cs index fb51b9e63cbf..876f7b99eb62 100644 --- a/src/Umbraco.Core/Events/MacroErrorEventArgs.cs +++ b/src/Umbraco.Core/Events/MacroErrorEventArgs.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Macros; +using Umbraco.Cms.Core.Macros; namespace Umbraco.Cms.Core.Events; diff --git a/src/Umbraco.Core/Exceptions/AuthorizationException.cs b/src/Umbraco.Core/Exceptions/AuthorizationException.cs index ba4e40d1901d..fd55a94b7b56 100644 --- a/src/Umbraco.Core/Exceptions/AuthorizationException.cs +++ b/src/Umbraco.Core/Exceptions/AuthorizationException.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Exceptions; diff --git a/src/Umbraco.Core/Exceptions/BootFailedException.cs b/src/Umbraco.Core/Exceptions/BootFailedException.cs index 55ec0970c0c3..5ade44a68fe5 100644 --- a/src/Umbraco.Core/Exceptions/BootFailedException.cs +++ b/src/Umbraco.Core/Exceptions/BootFailedException.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using System.Text; namespace Umbraco.Cms.Core.Exceptions; @@ -76,8 +76,7 @@ public static void Rethrow(BootFailedException? bootFailedException) // see https://stackoverflow.com/questions/57383 // would that be the correct way to do it? - //ExceptionDispatchInfo.Capture(bootFailedException).Throw(); - + // ExceptionDispatchInfo.Capture(bootFailedException).Throw(); Exception? e = bootFailedException; var m = new StringBuilder(); m.Append(DefaultMessage); diff --git a/src/Umbraco.Core/Exceptions/ConfigurationException.cs b/src/Umbraco.Core/Exceptions/ConfigurationException.cs index 4f227b9d94f2..89d8bfc01d5f 100644 --- a/src/Umbraco.Core/Exceptions/ConfigurationException.cs +++ b/src/Umbraco.Core/Exceptions/ConfigurationException.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Exceptions; diff --git a/src/Umbraco.Core/Exceptions/DataOperationException.cs b/src/Umbraco.Core/Exceptions/DataOperationException.cs index 85c7559fd67a..9acc6ded3890 100644 --- a/src/Umbraco.Core/Exceptions/DataOperationException.cs +++ b/src/Umbraco.Core/Exceptions/DataOperationException.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Exceptions; diff --git a/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs b/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs index 4593fabc7cf6..9bc51d7b6e63 100644 --- a/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs +++ b/src/Umbraco.Core/Exceptions/InvalidCompositionException.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using System.Text; using Umbraco.Extensions; @@ -34,8 +34,7 @@ public InvalidCompositionException(string contentTypeAlias, string[] propertyTyp /// The content type alias. /// The added composition alias. /// The property type aliases. - public InvalidCompositionException(string contentTypeAlias, string? addedCompositionAlias, - string[] propertyTypeAliases) + public InvalidCompositionException(string contentTypeAlias, string? addedCompositionAlias, string[] propertyTypeAliases) : this(contentTypeAlias, addedCompositionAlias, propertyTypeAliases, new string[0]) { } @@ -47,8 +46,7 @@ public InvalidCompositionException(string contentTypeAlias, string? addedComposi /// The added composition alias. /// The property type aliases. /// The property group aliases. - public InvalidCompositionException(string contentTypeAlias, string? addedCompositionAlias, - string[] propertyTypeAliases, string[] propertyGroupAliases) + public InvalidCompositionException(string contentTypeAlias, string? addedCompositionAlias, string[] propertyTypeAliases, string[] propertyGroupAliases) : this(FormatMessage(contentTypeAlias, addedCompositionAlias, propertyTypeAliases, propertyGroupAliases)) { ContentTypeAlias = contentTypeAlias; @@ -131,8 +129,35 @@ protected InvalidCompositionException(SerializationInfo info, StreamingContext c /// public string[]? PropertyGroupAliases { get; } - private static string FormatMessage(string contentTypeAlias, string? addedCompositionAlias, - string[] propertyTypeAliases, string[] propertyGroupAliases) + /// + /// When overridden in a derived class, sets the with + /// information about the exception. + /// + /// + /// The that holds the serialized object + /// data about the exception being thrown. + /// + /// + /// The that contains contextual + /// information about the source or destination. + /// + /// info + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + throw new ArgumentNullException(nameof(info)); + } + + info.AddValue(nameof(ContentTypeAlias), ContentTypeAlias); + info.AddValue(nameof(AddedCompositionAlias), AddedCompositionAlias); + info.AddValue(nameof(PropertyTypeAliases), PropertyTypeAliases); + info.AddValue(nameof(PropertyGroupAliases), PropertyGroupAliases); + + base.GetObjectData(info, context); + } + + private static string FormatMessage(string contentTypeAlias, string? addedCompositionAlias, string[] propertyTypeAliases, string[] propertyGroupAliases) { var sb = new StringBuilder(); @@ -144,7 +169,8 @@ private static string FormatMessage(string contentTypeAlias, string? addedCompos { sb.AppendFormat( "Content type with alias '{0}' was added as a composition to content type with alias '{1}', but there was a conflict.", - addedCompositionAlias, contentTypeAlias); + addedCompositionAlias, + contentTypeAlias); } if (propertyTypeAliases.Length > 0) @@ -163,32 +189,4 @@ private static string FormatMessage(string contentTypeAlias, string? addedCompos return sb.ToString(); } - - /// - /// When overridden in a derived class, sets the with - /// information about the exception. - /// - /// - /// The that holds the serialized object - /// data about the exception being thrown. - /// - /// - /// The that contains contextual - /// information about the source or destination. - /// - /// info - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - - info.AddValue(nameof(ContentTypeAlias), ContentTypeAlias); - info.AddValue(nameof(AddedCompositionAlias), AddedCompositionAlias); - info.AddValue(nameof(PropertyTypeAliases), PropertyTypeAliases); - info.AddValue(nameof(PropertyGroupAliases), PropertyGroupAliases); - - base.GetObjectData(info, context); - } } diff --git a/src/Umbraco.Core/ExpressionHelper.cs b/src/Umbraco.Core/ExpressionHelper.cs index 35af2e7a4a31..79e03d7b9395 100644 --- a/src/Umbraco.Core/ExpressionHelper.cs +++ b/src/Umbraco.Core/ExpressionHelper.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Linq.Expressions; using System.Reflection; using Umbraco.Cms.Core.Persistence; @@ -22,7 +22,8 @@ public static class ExpressionHelper /// The property lambda. /// /// - public static PropertyInfo GetPropertyInfo(this TSource source, + public static PropertyInfo GetPropertyInfo( + this TSource source, Expression> propertyLambda) => GetPropertyInfo(propertyLambda); /// @@ -48,11 +49,9 @@ public static PropertyInfo { // The expression might be for some boxing, e.g. representing a value type like HiveId as an object // in which case the expression will be Convert(x.MyProperty) - var unary = propertyLambda.Body as UnaryExpression; - if (unary != null) + if (propertyLambda.Body is UnaryExpression unary) { - var boxedMember = unary.Operand as MemberExpression; - if (boxedMember == null) + if (unary.Operand is not MemberExpression boxedMember) { throw new ArgumentException( "The type of property could not be inferred, try specifying the type parameters explicitly. This can happen if you have tried to access PropertyInfo where the property's return type is a value type, but the expression is trying to convert it to an object"); @@ -68,7 +67,6 @@ public static PropertyInfo } } - var propInfo = member!.Member as PropertyInfo; if (propInfo == null) { @@ -148,10 +146,9 @@ void Throw() return null; } - var body = fromExpression.Body as MethodCallExpression; - if (body == null) + if (fromExpression.Body is not MethodCallExpression body) { - return new Dictionary(); + return new Dictionary(); } var rVal = new Dictionary(); @@ -187,7 +184,7 @@ void Throw() } var body = fromExpression.Body as MethodCallExpression; - return body != null ? body.Method : null; + return body?.Method; } /// @@ -204,7 +201,7 @@ void Throw() } var body = fromExpression.Body as MethodCallExpression; - return body != null ? body.Method : null; + return body?.Method; } /// @@ -227,14 +224,14 @@ void Throw() case ExpressionType.Convert: case ExpressionType.ConvertChecked: var ue = fromExpression.Body as UnaryExpression; - me = (ue != null ? ue.Operand : null) as MethodCallExpression; + me = ue?.Operand as MethodCallExpression; break; default: me = fromExpression.Body as MethodCallExpression; break; } - return me != null ? me.Method : null; + return me?.Method; } /// @@ -281,14 +278,14 @@ void Throw() case ExpressionType.Convert: case ExpressionType.ConvertChecked: var ue = fromExpression.Body as UnaryExpression; - me = (ue != null ? ue.Operand : null) as MemberExpression; + me = ue?.Operand as MemberExpression; break; default: me = fromExpression.Body as MemberExpression; break; } - return me != null ? me.Member : null; + return me?.Member; } /// @@ -328,14 +325,14 @@ public static bool IsMethodSignatureEqualTo(this MethodInfo left, MethodInfo rig for (var i = 0; i < leftParams.Length; i++) { - //if they are delegate parameters, then assume they match as they could be anything + // if they are delegate parameters, then assume they match as they could be anything if (typeof(Delegate).IsAssignableFrom(leftParams[i].ParameterType) && typeof(Delegate).IsAssignableFrom(rightParams[i].ParameterType)) { continue; } - //if they are not delegates, then compare the types + // if they are not delegates, then compare the types if (leftParams[i].ParameterType != rightParams[i].ParameterType) { return false; @@ -379,7 +376,6 @@ public static MethodInfo GetStaticMethodInfo(Delegate fromMethodGroup) throw new ArgumentNullException("fromMethodGroup"); } - return fromMethodGroup.Method; } @@ -390,16 +386,15 @@ public static MethodInfo GetStaticMethodInfo(Delegate fromMethodGroup) ///// The unhandled item. ///// ///// - //public static string FormatUnhandledItem(T unhandledItem) where T : class - //{ + // public static string FormatUnhandledItem(T unhandledItem) where T : class + // { // if (unhandledItem == null) throw new ArgumentNullException("unhandledItem"); - - // var itemAsExpression = unhandledItem as Expression; + // var itemAsExpression = unhandledItem as Expression; // return itemAsExpression != null // ? FormattingExpressionTreeVisitor.Format(itemAsExpression) // : unhandledItem.ToString(); - //} + // } /// /// Determines whether the specified expression is a method. @@ -409,7 +404,6 @@ public static MethodInfo GetStaticMethodInfo(Delegate fromMethodGroup) /// public static bool IsMethod(Expression expression) => expression is MethodCallExpression; - /// /// Determines whether the specified expression is a member. /// diff --git a/src/Umbraco.Core/Extensions/AssemblyExtensions.cs b/src/Umbraco.Core/Extensions/AssemblyExtensions.cs index 87c0c1478c0e..45ae9ceafeb1 100644 --- a/src/Umbraco.Core/Extensions/AssemblyExtensions.cs +++ b/src/Umbraco.Core/Extensions/AssemblyExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.Reflection; @@ -7,7 +7,7 @@ namespace Umbraco.Extensions; public static class AssemblyExtensions { - private static string _rootDir = ""; + private static string _rootDir = string.Empty; /// /// Utility method that returns the path to the root of the application, by getting the path to where the assembly @@ -34,7 +34,7 @@ public static string GetRootDirectorySafe(this Assembly executingAssembly) } _rootDir = baseDirectory.Contains("bin") - ? baseDirectory.Substring(0, baseDirectory.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase) - 1) + ? baseDirectory[..(baseDirectory.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase) - 1)] : baseDirectory; return _rootDir; @@ -69,7 +69,7 @@ public static bool IsAppCodeAssembly(this Assembly assembly) } catch (FileNotFoundException) { - //this will occur if it cannot load the assembly + // this will occur if it cannot load the assembly return false; } } @@ -83,7 +83,8 @@ public static bool IsAppCodeAssembly(this Assembly assembly) /// /// public static bool IsGlobalAsaxAssembly(this Assembly assembly) => - //only way I can figure out how to test is by the name + + // only way I can figure out how to test is by the name assembly.FullName!.StartsWith("App_global.asax"); /// diff --git a/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs b/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs index 93694124711b..a604b3e0173e 100644 --- a/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs +++ b/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.Diagnostics.CodeAnalysis; @@ -20,12 +20,13 @@ public static class ClaimsIdentityExtensions /// public static IEnumerable RequiredBackOfficeClaimTypes => new[] { - ClaimTypes.NameIdentifier, //id - ClaimTypes.Name, //username + ClaimTypes.NameIdentifier, // id + ClaimTypes.Name, // username ClaimTypes.GivenName, + // Constants.Security.StartContentNodeIdClaimType, These seem to be able to be null... // Constants.Security.StartMediaNodeIdClaimType, - ClaimTypes.Locality, Constants.Security.SecurityStampClaimType + ClaimTypes.Locality, Constants.Security.SecurityStampClaimType, }; public static T? GetUserId(this IIdentity identity) @@ -125,7 +126,8 @@ public static class ClaimsIdentityExtensions /// /// Verified identity wrapped in a ClaimsIdentity with BackOfficeAuthentication type /// True if ClaimsIdentity - public static bool VerifyBackOfficeIdentity(this ClaimsIdentity identity, + public static bool VerifyBackOfficeIdentity( + this ClaimsIdentity identity, [MaybeNullWhen(false)] out ClaimsIdentity verifiedIdentity) { if (identity is null) @@ -164,11 +166,9 @@ public static bool VerifyBackOfficeIdentity(this ClaimsIdentity identity, /// Security stamp /// Allowed apps /// Roles - public static void AddRequiredClaims(this ClaimsIdentity identity, string userId, string username, - string realName, IEnumerable? startContentNodes, IEnumerable? startMediaNodes, string culture, - string securityStamp, IEnumerable allowedApps, IEnumerable roles) + public static void AddRequiredClaims(this ClaimsIdentity identity, string userId, string username, string realName, IEnumerable? startContentNodes, IEnumerable? startMediaNodes, string culture, string securityStamp, IEnumerable allowedApps, IEnumerable roles) { - //This is the id that 'identity' uses to check for the user id + // This is the id that 'identity' uses to check for the user id if (identity.HasClaim(x => x.Type == ClaimTypes.NameIdentifier) == false) { identity.AddClaim(new Claim( @@ -256,8 +256,7 @@ public static void AddRequiredClaims(this ClaimsIdentity identity, string userId } // Add each app as a separate claim - if (identity.HasClaim(x => x.Type == Constants.Security.AllowedApplicationsClaimType) == false && - allowedApps != null) + if (identity.HasClaim(x => x.Type == Constants.Security.AllowedApplicationsClaimType) == false && allowedApps != null) { foreach (var application in allowedApps) { @@ -375,7 +374,6 @@ public static string[] GetAllowedApplications(this ClaimsIdentity identity) => i public static string[] GetRoles(this ClaimsIdentity identity) => identity .FindAll(x => x.Type == ClaimsIdentity.DefaultRoleClaimType).Select(role => role.Value).ToArray(); - /// /// Adds or updates and existing claim. /// diff --git a/src/Umbraco.Core/Extensions/ContentExtensions.cs b/src/Umbraco.Core/Extensions/ContentExtensions.cs index 747fcfc74192..df0e58d8781f 100644 --- a/src/Umbraco.Core/Extensions/ContentExtensions.cs +++ b/src/Umbraco.Core/Extensions/ContentExtensions.cs @@ -54,7 +54,6 @@ public static bool TryGetMediaPath( public static bool WasAnyUserPropertyDirty(this IContentBase entity) => entity.Properties.Any(x => x.WasDirty()); - public static bool IsMoving(this IContentBase entity) { // Check if this entity is being moved as a descendant as part of a bulk moving operations. @@ -68,7 +67,6 @@ public static bool IsMoving(this IContentBase entity) return isMoving; } - /// /// Removes characters that are not valid XML characters from all entity properties /// of type string. See: http://stackoverflow.com/a/961504/5018 @@ -98,6 +96,15 @@ public static void SanitizeEntityPropertiesForXmlStorage(this IContentBase entit } } + /// + /// Returns all properties based on the editorAlias + /// + /// + /// + /// + public static IEnumerable GetPropertiesByEditor(this IContentBase content, string editorAlias) + => content.Properties.Where(x => x.PropertyType?.PropertyEditorAlias == editorAlias); + /// /// Checks if the IContentBase has children /// @@ -122,17 +129,6 @@ public static void SanitizeEntityPropertiesForXmlStorage(this IContentBase entit return false; } - - /// - /// Returns all properties based on the editorAlias - /// - /// - /// - /// - public static IEnumerable GetPropertiesByEditor(this IContentBase content, string editorAlias) - => content.Properties.Where(x => x.PropertyType?.PropertyEditorAlias == editorAlias); - - /// /// Gets the for the Creator of this content item. /// @@ -151,7 +147,6 @@ public static IEnumerable GetPropertiesByEditor(this IContentBase con public static IProfile? GetWriterProfile(this IMedia content, IUserService userService) => userService.GetProfileById(content.WriterId); - #region User/Profile methods /// @@ -162,7 +157,6 @@ public static IEnumerable GetPropertiesByEditor(this IContentBase con #endregion - /// /// Returns properties that do not belong to a group /// @@ -181,13 +175,13 @@ public static IEnumerable GetNonGroupedProperties(this IContentBase c /// public static IEnumerable GetPropertiesForGroup(this IContentBase content, PropertyGroup propertyGroup) => - //get the properties for the current tab + + // get the properties for the current tab content.Properties .Where(property => propertyGroup.PropertyTypes is not null && propertyGroup.PropertyTypes .Select(propertyType => propertyType.Id) .Contains(property.PropertyTypeId)); - #region Dirty public static IEnumerable GetDirtyUserProperties(this IContentBase entity) => @@ -195,7 +189,6 @@ public static IEnumerable GetDirtyUserProperties(this IContentBase entit #endregion - /// /// Creates the full xml representation for the object and all of it's descendants /// @@ -214,7 +207,6 @@ public static XElement ToDeepXml(this IContent content, IEntityXmlSerializer ser public static XElement ToXml(this IContent content, IEntityXmlSerializer serializer) => serializer.Serialize(content, false); - /// /// Creates the xml representation for the object /// @@ -231,14 +223,12 @@ public static XElement ToXml(this IContent content, IEntityXmlSerializer seriali /// Xml representation of the passed in public static XElement ToXml(this IMember member, IEntityXmlSerializer serializer) => serializer.Serialize(member); - #region IContent /// /// Gets the current status of the Content /// - public static ContentStatus GetStatus(this IContent content, ContentScheduleCollection contentSchedule, - string? culture = null) + public static ContentStatus GetStatus(this IContent content, ContentScheduleCollection contentSchedule, string? culture = null) { if (content.Trashed) { @@ -287,7 +277,6 @@ public static ContentStatus GetStatus(this IContent content, ContentScheduleColl #endregion - #region SetValue for setting file contents /// @@ -318,8 +307,47 @@ public static void SetValue( filename = filename.ToLower(); - SetUploadFile(content, mediaFileManager, mediaUrlGenerators, contentTypeBaseServiceProvider, propertyTypeAlias, - filename, filestream, culture, segment); + SetUploadFile(content, mediaFileManager, mediaUrlGenerators, contentTypeBaseServiceProvider, propertyTypeAlias, filename, filestream, culture, segment); + } + + /// + /// Stores a file. + /// + /// A content item. + /// The property alias. + /// The name of the file. + /// A stream containing the file data. + /// The original file path, if any. + /// The path to the file, relative to the media filesystem. + /// + /// + /// Does NOT set the property value, so one should probably store the file and then do + /// something alike: property.Value = MediaHelper.FileSystem.GetUrl(filepath). + /// + /// + /// The original file path is used, in the old media file path scheme, to try and reuse + /// the "folder number" that was assigned to the previous file referenced by the property, + /// if any. + /// + /// + public static string StoreFile( + this IContentBase content, + MediaFileManager mediaFileManager, + IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, + string propertyTypeAlias, + string filename, + Stream filestream, + string filepath) + { + IContentTypeComposition? contentType = contentTypeBaseServiceProvider.GetContentTypeOf(content); + IPropertyType? propertyType = contentType? + .CompositionPropertyTypes.FirstOrDefault(x => x.Alias?.InvariantEquals(propertyTypeAlias) ?? false); + if (propertyType == null) + { + throw new ArgumentException("Invalid property type alias " + propertyTypeAlias + "."); + } + + return mediaFileManager.StoreFile(content, propertyType, filename, filestream, filepath); } private static void SetUploadFile( @@ -353,17 +381,19 @@ private static void SetUploadFile( } // gets or creates a property for a content item. - private static IProperty GetProperty(IContentBase content, - IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, string propertyTypeAlias) + private static IProperty GetProperty( + IContentBase content, + IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, + string propertyTypeAlias) { - IProperty property = content.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); + IProperty? property = content.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); if (property != null) { return property; } - IContentTypeComposition contentType = contentTypeBaseServiceProvider.GetContentTypeOf(content); - IPropertyType propertyType = contentType?.CompositionPropertyTypes + IContentTypeComposition? contentType = contentTypeBaseServiceProvider.GetContentTypeOf(content); + IPropertyType? propertyType = contentType?.CompositionPropertyTypes .FirstOrDefault(x => x.Alias?.InvariantEquals(propertyTypeAlias) ?? false); if (propertyType == null) { @@ -375,40 +405,5 @@ private static IProperty GetProperty(IContentBase content, return property; } - /// - /// Stores a file. - /// - /// A content item. - /// The property alias. - /// The name of the file. - /// A stream containing the file data. - /// The original file path, if any. - /// The path to the file, relative to the media filesystem. - /// - /// - /// Does NOT set the property value, so one should probably store the file and then do - /// something alike: property.Value = MediaHelper.FileSystem.GetUrl(filepath). - /// - /// - /// The original file path is used, in the old media file path scheme, to try and reuse - /// the "folder number" that was assigned to the previous file referenced by the property, - /// if any. - /// - /// - public static string StoreFile(this IContentBase content, MediaFileManager mediaFileManager, - IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, string propertyTypeAlias, string filename, - Stream filestream, string filepath) - { - IContentTypeComposition contentType = contentTypeBaseServiceProvider.GetContentTypeOf(content); - IPropertyType propertyType = contentType? - .CompositionPropertyTypes.FirstOrDefault(x => x.Alias?.InvariantEquals(propertyTypeAlias) ?? false); - if (propertyType == null) - { - throw new ArgumentException("Invalid property type alias " + propertyTypeAlias + "."); - } - - return mediaFileManager.StoreFile(content, propertyType, filename, filestream, filepath); - } - #endregion } diff --git a/src/Umbraco.Core/Extensions/ContentVariationExtensions.cs b/src/Umbraco.Core/Extensions/ContentVariationExtensions.cs index 1b4d897029bd..2654bf0e1e06 100644 --- a/src/Umbraco.Core/Extensions/ContentVariationExtensions.cs +++ b/src/Umbraco.Core/Extensions/ContentVariationExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Models; @@ -275,8 +275,7 @@ public static void SetVariesBy(this IPropertyType propertyType, ContentVariation /// /// This method does not support setting the variation to nothing. /// - public static ContentVariation SetFlag(this ContentVariation variations, ContentVariation variation, - bool value = true) => + public static ContentVariation SetFlag(this ContentVariation variations, ContentVariation variation, bool value = true) => value ? variations | variation // Set flag using bitwise logical OR : variations & @@ -315,8 +314,7 @@ public static ContentVariation SetFlag(this ContentVariation variations, Content /// /// Both and can be "*" to indicate "all of them". /// - public static bool ValidateVariation(this ContentVariation variation, string? culture, string? segment, bool exact, - bool wildcards, bool throwIfInvalid) + public static bool ValidateVariation(this ContentVariation variation, string? culture, string? segment, bool exact, bool wildcards, bool throwIfInvalid) { culture = culture?.NullOrWhiteSpaceAsNull(); segment = segment?.NullOrWhiteSpaceAsNull(); diff --git a/src/Umbraco.Core/Extensions/CoreCacheHelperExtensions.cs b/src/Umbraco.Core/Extensions/CoreCacheHelperExtensions.cs index 2bf5470c7f09..1af0b8c47a21 100644 --- a/src/Umbraco.Core/Extensions/CoreCacheHelperExtensions.cs +++ b/src/Umbraco.Core/Extensions/CoreCacheHelperExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Cache; diff --git a/src/Umbraco.Core/Extensions/DataTableExtensions.cs b/src/Umbraco.Core/Extensions/DataTableExtensions.cs index 6cf9e0892a49..10fa51deaff4 100644 --- a/src/Umbraco.Core/Extensions/DataTableExtensions.cs +++ b/src/Umbraco.Core/Extensions/DataTableExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.Data; @@ -29,19 +29,19 @@ public static DataTable GenerateDataTable( { var dt = new DataTable(tableAlias); - //get all row data - Tuple>, IEnumerable>>[] tableData = + // get all row data + Tuple>, IEnumerable>>[] tableData = rowData().ToArray(); - //get all headers + // get all headers IDictionary propertyHeaders = GetPropertyHeaders(tableAlias, getHeaders); foreach (KeyValuePair h in propertyHeaders) { dt.Columns.Add(new DataColumn(h.Value)); } - //add row data - foreach (Tuple>, IEnumerable>> r in + // add row data + foreach (Tuple>, IEnumerable>> r in tableData) { dt.PopulateRow( @@ -79,10 +79,10 @@ public static void AddRowData( IEnumerable> userVals) => rowData.Add(new Tuple>, IEnumerable>>( standardVals, - userVals - )); + userVals)); - private static IDictionary GetPropertyHeaders(string alias, + private static IDictionary GetPropertyHeaders( + string alias, Func>> getHeaders) { IEnumerable> headers = getHeaders(alias); @@ -97,12 +97,12 @@ private static void PopulateRow( IEnumerable> userPropertyVals) { DataRow dr = dt.NewRow(); - foreach (KeyValuePair r in standardVals) + foreach (KeyValuePair r in standardVals) { dr[r.Key] = r.Value; } - foreach (KeyValuePair p in userPropertyVals.Where(p => p.Value != null)) + foreach (KeyValuePair p in userPropertyVals.Where(p => p.Value != null)) { dr[aliasesToNames[p.Key]] = p.Value; } diff --git a/src/Umbraco.Core/Extensions/DateTimeExtensions.cs b/src/Umbraco.Core/Extensions/DateTimeExtensions.cs index 864e74539e67..35c9f600e558 100644 --- a/src/Umbraco.Core/Extensions/DateTimeExtensions.cs +++ b/src/Umbraco.Core/Extensions/DateTimeExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.Globalization; @@ -14,7 +14,7 @@ public enum DateTruncate Day, Hour, Minute, - Second + Second, } /// diff --git a/src/Umbraco.Core/Extensions/DecimalExtensions.cs b/src/Umbraco.Core/Extensions/DecimalExtensions.cs index 9f0bc95de3bf..6e70544d0ee7 100644 --- a/src/Umbraco.Core/Extensions/DecimalExtensions.cs +++ b/src/Umbraco.Core/Extensions/DecimalExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Extensions; diff --git a/src/Umbraco.Core/Extensions/DelegateExtensions.cs b/src/Umbraco.Core/Extensions/DelegateExtensions.cs index e623d495b60d..621ef46438ff 100644 --- a/src/Umbraco.Core/Extensions/DelegateExtensions.cs +++ b/src/Umbraco.Core/Extensions/DelegateExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.Diagnostics; @@ -8,8 +8,7 @@ namespace Umbraco.Extensions; public static class DelegateExtensions { - public static Attempt RetryUntilSuccessOrTimeout(this Func> task, TimeSpan timeout, - TimeSpan pause) + public static Attempt RetryUntilSuccessOrTimeout(this Func> task, TimeSpan timeout, TimeSpan pause) { if (pause.TotalMilliseconds < 0) { @@ -19,17 +18,20 @@ public static class DelegateExtensions var stopwatch = Stopwatch.StartNew(); do { - Attempt result = task(); - if (result.Success) { return result; } + Attempt result = task(); + if (result.Success) + { + return result; + } Thread.Sleep((int)pause.TotalMilliseconds); - } while (stopwatch.Elapsed < timeout); + } + while (stopwatch.Elapsed < timeout); return Attempt.Fail(); } - public static Attempt RetryUntilSuccessOrMaxAttempts(this Func> task, int totalAttempts, - TimeSpan pause) + public static Attempt RetryUntilSuccessOrMaxAttempts(this Func> task, int totalAttempts, TimeSpan pause) { if (pause.TotalMilliseconds < 0) { @@ -40,11 +42,15 @@ public static class DelegateExtensions do { attempts++; - Attempt result = task(attempts); - if (result.Success) { return result; } + Attempt result = task(attempts); + if (result.Success) + { + return result; + } Thread.Sleep((int)pause.TotalMilliseconds); - } while (attempts < totalAttempts); + } + while (attempts < totalAttempts); return Attempt.Fail(); } diff --git a/src/Umbraco.Core/Extensions/DictionaryExtensions.cs b/src/Umbraco.Core/Extensions/DictionaryExtensions.cs index dc6f7ac7db09..3bbd3bdcb996 100644 --- a/src/Umbraco.Core/Extensions/DictionaryExtensions.cs +++ b/src/Umbraco.Core/Extensions/DictionaryExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.Collections; @@ -49,19 +49,18 @@ public static TVal GetOrCreate(this IDictionary dict, TK /// http://stackoverflow.com/questions/12240219/is-there-a-way-to-use-concurrentdictionary-tryupdate-with-a-lambda-expression /// If there is an item in the dictionary with the key, it will keep trying to update it until it can /// - public static bool TryUpdate(this ConcurrentDictionary dict, TKey key, - Func updateFactory) + public static bool TryUpdate(this ConcurrentDictionary dict, TKey key, Func updateFactory) where TKey : notnull { - TValue? curValue; - while (dict.TryGetValue(key, out curValue)) + while (dict.TryGetValue(key, out TValue? curValue)) { if (dict.TryUpdate(key, updateFactory(curValue), curValue)) { return true; } - //if we're looping either the key was removed by another thread, or another thread - //changed the value, so we start again. + + // if we're looping either the key was removed by another thread, or another thread + // changed the value, so we start again. } return false; @@ -81,18 +80,16 @@ public static bool TryUpdate(this ConcurrentDictionary - public static bool TryUpdateOptimisitic(this ConcurrentDictionary dict, TKey key, - Func updateFactory) + public static bool TryUpdateOptimisitic(this ConcurrentDictionary dict, TKey key, Func updateFactory) where TKey : notnull { - TValue? curValue; - if (!dict.TryGetValue(key, out curValue)) + if (!dict.TryGetValue(key, out TValue? curValue)) { return false; } dict.TryUpdate(key, updateFactory(curValue), curValue); - return true; //note we return true whether we succeed or not, see explanation below. + return true; // note we return true whether we succeed or not, see explanation below. } /// @@ -123,8 +120,10 @@ public static IDictionary ConvertTo(this IDi /// /// /// - public static IDictionary ConvertTo(this IDictionary d, - Func keyConverter, Func valConverter) + public static IDictionary ConvertTo( + this IDictionary d, + Func keyConverter, + Func valConverter) where TKeyOut : notnull { var result = new Dictionary(); @@ -152,7 +151,6 @@ public static NameValueCollection ToNameValueCollection(this IDictionary /// Merges all key/values from the sources dictionaries into the destination dictionary /// @@ -167,8 +165,7 @@ public static NameValueCollection ToNameValueCollection(this IDictionary /// The other dictionaries to merge values from - public static void MergeLeft(this T destination, IEnumerable> sources, - bool overwrite = false) + public static void MergeLeft(this T destination, IEnumerable> sources, bool overwrite = false) where T : IDictionary { foreach (KeyValuePair p in sources.SelectMany(src => src) @@ -194,7 +191,7 @@ public static void MergeLeft(this T destination, IEnumerableThe other dictionary to merge values from public static void MergeLeft(this T destination, IDictionary source, bool overwrite = false) where T : IDictionary => - destination.MergeLeft(new[] {source}, overwrite); + destination.MergeLeft(new[] { source }, overwrite); /// /// Returns the value of the key value based on the key, if the key is not found, a null value is returned @@ -265,14 +262,13 @@ public static string ToQueryString(this IDictionary d) { if (!d.Any()) { - return ""; + return string.Empty; } var builder = new StringBuilder(); foreach (KeyValuePair i in d) { - builder.Append(string.Format("{0}={1}&", WebUtility.UrlEncode(i.Key), - i.Value == null ? string.Empty : WebUtility.UrlEncode(i.Value.ToString()))); + builder.Append(string.Format("{0}={1}&", WebUtility.UrlEncode(i.Key), i.Value == null ? string.Empty : WebUtility.UrlEncode(i.Value.ToString()))); } return builder.ToString().TrimEnd(Constants.CharArrays.Ampersand); diff --git a/src/Umbraco.Core/Extensions/EnumExtensions.cs b/src/Umbraco.Core/Extensions/EnumExtensions.cs index 5b762bd2e802..3aa124d2f379 100644 --- a/src/Umbraco.Core/Extensions/EnumExtensions.cs +++ b/src/Umbraco.Core/Extensions/EnumExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Extensions; diff --git a/src/Umbraco.Core/Extensions/EnumerableExtensions.cs b/src/Umbraco.Core/Extensions/EnumerableExtensions.cs index 8d8944c44ef2..f4a7f92e0fa1 100644 --- a/src/Umbraco.Core/Extensions/EnumerableExtensions.cs +++ b/src/Umbraco.Core/Extensions/EnumerableExtensions.cs @@ -12,6 +12,18 @@ public static class EnumerableExtensions { public static bool IsCollectionEmpty(this IReadOnlyCollection? list) => list == null || list.Count == 0; + /// + /// Wraps this object instance into an IEnumerable{T} consisting of a single item. + /// + /// Type of the object. + /// The instance that will be wrapped. + /// An IEnumerable{T} consisting of a single item. + public static IEnumerable Yield(this T item) + { + // see EnumeratorBenchmarks - this is faster, and allocates less, than returning an array + yield return item; + } + internal static bool HasDuplicates(this IEnumerable items, bool includeNull) { var hs = new HashSet(); @@ -26,19 +38,6 @@ internal static bool HasDuplicates(this IEnumerable items, bool includeNul return false; } - - /// - /// Wraps this object instance into an IEnumerable{T} consisting of a single item. - /// - /// Type of the object. - /// The instance that will be wrapped. - /// An IEnumerable{T} consisting of a single item. - public static IEnumerable Yield(this T item) - { - // see EnumeratorBenchmarks - this is faster, and allocates less, than returning an array - yield return item; - } - public static IEnumerable> InGroupsOf(this IEnumerable? source, int groupSize) { if (source == null) @@ -51,9 +50,7 @@ public static IEnumerable> InGroupsOf(this IEnumerable? sou throw new ArgumentException("Must be greater than zero.", "groupSize"); } - // following code derived from MoreLinq and does not allocate bazillions of tuples - T[]? temp = null; var count = 0; @@ -81,15 +78,18 @@ public static IEnumerable> InGroupsOf(this IEnumerable? sou } } - public static IEnumerable SelectByGroups(this IEnumerable source, + public static IEnumerable SelectByGroups( + this IEnumerable source, Func, IEnumerable> selector, int groupSize) { // don't want to use a SelectMany(x => x) here - isn't this better? // ReSharper disable once LoopCanBeConvertedToQuery foreach (IEnumerable resultGroup in source.InGroupsOf(groupSize).Select(selector)) - foreach (TResult result in resultGroup) { - yield return result; + foreach (TResult result in resultGroup) + { + yield return result; + } } } @@ -113,7 +113,8 @@ public static IEnumerable Range(Func factory, int count) /// The items. /// The action. /// The type - public static void IfNotNull(this IEnumerable items, Action action) where TItem : class + public static void IfNotNull(this IEnumerable items, Action action) + where TItem : class { if (items != null) { @@ -124,7 +125,6 @@ public static void IfNotNull(this IEnumerable items, Action } } - /// /// Returns true if all items in the other collection exist in this collection /// @@ -237,10 +237,13 @@ public static IEnumerable SelectRecursive( /// The coll. /// /// - public static IEnumerable WhereNotNull(this IEnumerable coll) where T : class => + public static IEnumerable WhereNotNull(this IEnumerable coll) + where T : class + => coll.Where(x => x != null)!; - public static IEnumerable ForAllThatAre(this IEnumerable sequence, + public static IEnumerable ForAllThatAre( + this IEnumerable sequence, Action projection) where TActual : class => sequence.Select( @@ -352,7 +355,8 @@ public static bool UnsortedSequenceEqual(this IEnumerable? source, IEnumer /// /// /// - public static IEnumerable Transform(this IEnumerable source, + public static IEnumerable Transform( + this IEnumerable source, Func, IEnumerable> transform) => transform(source); /// @@ -384,7 +388,8 @@ public static IEnumerable SkipLast(this IEnumerable source) } } - public static IOrderedEnumerable OrderBy(this IEnumerable source, + public static IOrderedEnumerable OrderBy( + this IEnumerable source, Func keySelector, Direction sortOrder) => sortOrder == Direction.Ascending ? source.OrderBy(keySelector) : source.OrderByDescending(keySelector); diff --git a/src/Umbraco.Core/Extensions/ExpressionExtensions.cs b/src/Umbraco.Core/Extensions/ExpressionExtensions.cs index 9d152ad5832c..12476c95068c 100644 --- a/src/Umbraco.Core/Extensions/ExpressionExtensions.cs +++ b/src/Umbraco.Core/Extensions/ExpressionExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.Linq.Expressions; diff --git a/src/Umbraco.Core/Extensions/HostEnvironmentExtensions.cs b/src/Umbraco.Core/Extensions/HostEnvironmentExtensions.cs index 4176eaf987ef..a1135164de0e 100644 --- a/src/Umbraco.Core/Extensions/HostEnvironmentExtensions.cs +++ b/src/Umbraco.Core/Extensions/HostEnvironmentExtensions.cs @@ -8,7 +8,7 @@ namespace Umbraco.Cms.Core.Extensions; /// public static class HostEnvironmentExtensions { - private static string? s_temporaryApplicationId; + private static string? temporaryApplicationId; /// /// Maps a virtual path to a physical path to the application's content root. @@ -41,11 +41,11 @@ public static string MapPathContentRoot(this IHostEnvironment hostEnvironment, s /// public static string GetTemporaryApplicationId(this IHostEnvironment hostEnvironment) { - if (s_temporaryApplicationId != null) + if (temporaryApplicationId != null) { - return s_temporaryApplicationId; + return temporaryApplicationId; } - return s_temporaryApplicationId = hostEnvironment.ContentRootPath.GenerateHash(); + return temporaryApplicationId = hostEnvironment.ContentRootPath.GenerateHash(); } } diff --git a/src/Umbraco.Core/Extensions/IfExtensions.cs b/src/Umbraco.Core/Extensions/IfExtensions.cs index d65157654808..1ab908b44526 100644 --- a/src/Umbraco.Core/Extensions/IfExtensions.cs +++ b/src/Umbraco.Core/Extensions/IfExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Extensions; @@ -12,7 +12,8 @@ public static class IfExtensions /// The item. /// The action. /// The type - public static void IfNotNull(this TItem item, Action action) where TItem : class + public static void IfNotNull(this TItem item, Action action) + where TItem : class { if (item != null) { @@ -40,8 +41,7 @@ public static void IfTrue(this bool predicate, Action action) /// The action. /// The default value. /// - public static TResult? IfNotNull(this TItem? item, Func action, - TResult? defaultValue = default) + public static TResult? IfNotNull(this TItem? item, Func action, TResult? defaultValue = default) where TItem : class => item != null ? action(item) : defaultValue; diff --git a/src/Umbraco.Core/Extensions/IntExtensions.cs b/src/Umbraco.Core/Extensions/IntExtensions.cs index 5265587a140e..d347993dd07e 100644 --- a/src/Umbraco.Core/Extensions/IntExtensions.cs +++ b/src/Umbraco.Core/Extensions/IntExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Extensions; diff --git a/src/Umbraco.Core/Extensions/KeyValuePairExtensions.cs b/src/Umbraco.Core/Extensions/KeyValuePairExtensions.cs index 5a612595fead..7189c4cc15ef 100644 --- a/src/Umbraco.Core/Extensions/KeyValuePairExtensions.cs +++ b/src/Umbraco.Core/Extensions/KeyValuePairExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Extensions; diff --git a/src/Umbraco.Core/GuidUdi.cs b/src/Umbraco.Core/GuidUdi.cs index 318753b2eaa9..e8280bceb60c 100644 --- a/src/Umbraco.Core/GuidUdi.cs +++ b/src/Umbraco.Core/GuidUdi.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; namespace Umbraco.Cms.Core; @@ -24,8 +24,7 @@ public GuidUdi(string entityType, Guid guid) public GuidUdi(Uri uriValue) : base(uriValue) { - Guid guid; - if (Guid.TryParse(uriValue.AbsolutePath.TrimStart(Constants.CharArrays.ForwardSlash), out guid) == false) + if (Guid.TryParse(uriValue.AbsolutePath.TrimStart(Constants.CharArrays.ForwardSlash), out Guid guid) == false) { throw new FormatException("URI \"" + uriValue + "\" is not a GUID entity ID."); } @@ -43,8 +42,7 @@ public GuidUdi(Uri uriValue) public override bool Equals(object? obj) { - var other = obj as GuidUdi; - if (other is null) + if (obj is not GuidUdi other) { return false; } diff --git a/src/Umbraco.Core/GuidUtils.cs b/src/Umbraco.Core/GuidUtils.cs index a4285d2e4940..290f36cdcf2c 100644 --- a/src/Umbraco.Core/GuidUtils.cs +++ b/src/Umbraco.Core/GuidUtils.cs @@ -1,4 +1,4 @@ -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Umbraco.Cms.Core; @@ -11,7 +11,7 @@ public static class GuidUtils private static readonly char[] Base32Table = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', - 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5' + 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', }; /// @@ -141,10 +141,14 @@ public static string ToBase32String(Guid guid, int length = 26) [StructLayout(LayoutKind.Explicit)] private struct DecomposedGuid { - [FieldOffset(00)] public readonly Guid Value; - [FieldOffset(00)] public long Hi; - [FieldOffset(08)] public long Lo; - - public DecomposedGuid(Guid value) : this() => Value = value; + [FieldOffset(00)] + public readonly Guid Value; + [FieldOffset(00)] + public long Hi; + [FieldOffset(08)] + public long Lo; + + public DecomposedGuid(Guid value) + : this() => Value = value; } } diff --git a/src/Umbraco.Core/Handlers/AuditNotificationsHandler.cs b/src/Umbraco.Core/Handlers/AuditNotificationsHandler.cs index 2c5cef193625..ebd888506bbb 100644 --- a/src/Umbraco.Core/Handlers/AuditNotificationsHandler.cs +++ b/src/Umbraco.Core/Handlers/AuditNotificationsHandler.cs @@ -54,14 +54,21 @@ private IUser CurrentPerformingUser { get { - IUser identity = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; - IUser user = identity == null ? null : _userService.GetUserById(Convert.ToInt32(identity.Id)); + IUser? identity = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; + IUser? user = identity == null ? null : _userService.GetUserById(Convert.ToInt32(identity.Id)); return user ?? UnknownUser(_globalSettings); } } private string PerformingIp => _ipResolver.GetCurrentRequestIpAddress(); + public static IUser UnknownUser(GlobalSettings globalSettings) => new User(globalSettings) + { + Id = Constants.Security.UnknownUserId, + Name = Constants.Security.UnknownUserName, + Email = string.Empty, + }; + public void Handle(AssignedMemberRolesNotification notification) { IUser performingUser = CurrentPerformingUser; @@ -69,12 +76,16 @@ public void Handle(AssignedMemberRolesNotification notification) var members = _memberService.GetAllMembers(notification.MemberIds).ToDictionary(x => x.Id, x => x); foreach (var id in notification.MemberIds) { - members.TryGetValue(id, out IMember member); - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + members.TryGetValue(id, out IMember? member); + _auditService.Write( + performingUser.Id, + $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.UtcNow, - -1, $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}", - "umbraco/member/roles/assigned", $"roles modified, assigned {roles}"); + -1, + $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}", + "umbraco/member/roles/assigned", + $"roles modified, assigned {roles}"); } } @@ -84,9 +95,9 @@ public void Handle(AssignedUserGroupPermissionsNotification notification) IEnumerable perms = notification.EntityPermissions; foreach (EntityPermission perm in perms) { - IUserGroup group = _userService.GetUserGroupById(perm.UserGroupId); + IUserGroup? group = _userService.GetUserGroupById(perm.UserGroupId); var assigned = string.Join(", ", perm.AssignedPermissions ?? Array.Empty()); - IEntitySlim entity = _entityService.Get(perm.EntityId); + IEntitySlim? entity = _entityService.Get(perm.EntityId); _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, @@ -146,7 +157,7 @@ public void Handle(RemovedMemberRolesNotification notification) var members = _memberService.GetAllMembers(notification.MemberIds).ToDictionary(x => x.Id, x => x); foreach (var id in notification.MemberIds) { - members.TryGetValue(id, out IMember member); + members.TryGetValue(id, out IMember? member); _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.UtcNow, @@ -208,7 +219,6 @@ public void Handle(UserGroupWithUsersSavedNotification notification) "umbraco/user-group/save", $"{sb}"); // now audit the users that have changed - foreach (IUser user in groupWithUser.RemovedUsers) { _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", @@ -248,18 +258,13 @@ public void Handle(UserSavedNotification notification) DateTime.UtcNow, affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", "umbraco/user/save", - $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}{(groups == null ? "" : "; groups assigned: " + groups)}"); + $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}{(groups == null ? string.Empty : "; groups assigned: " + groups)}"); } } - public static IUser UnknownUser(GlobalSettings globalSettings) => new User(globalSettings) - { - Id = Constants.Security.UnknownUserId, Name = Constants.Security.UnknownUserName, Email = "" - }; - private string FormatEmail(IMember? member) => - member == null ? string.Empty : member.Email.IsNullOrWhiteSpace() ? "" : $"<{member.Email}>"; + member == null ? string.Empty : member.Email.IsNullOrWhiteSpace() ? string.Empty : $"<{member.Email}>"; private string FormatEmail(IUser user) => - user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? "" : $"<{user.Email}>"; + user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? string.Empty : $"<{user.Email}>"; } diff --git a/src/Umbraco.Core/HashCodeCombiner.cs b/src/Umbraco.Core/HashCodeCombiner.cs index a92f0fde36f6..3506d335b887 100644 --- a/src/Umbraco.Core/HashCodeCombiner.cs +++ b/src/Umbraco.Core/HashCodeCombiner.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; namespace Umbraco.Cms.Core; @@ -41,7 +41,7 @@ public void AddCaseInsensitiveString(string s) public void AddFileSystemItem(FileSystemInfo f) { - //if it doesn't exist, don't proceed. + // if it doesn't exist, don't proceed. if (!f.Exists) { return; @@ -51,15 +51,13 @@ public void AddFileSystemItem(FileSystemInfo f) AddDateTime(f.CreationTimeUtc); AddDateTime(f.LastWriteTimeUtc); - //check if it is a file or folder - var fileInfo = f as FileInfo; - if (fileInfo != null) + // check if it is a file or folder + if (f is FileInfo fileInfo) { AddInt(fileInfo.Length.GetHashCode()); } - var dirInfo = f as DirectoryInfo; - if (dirInfo != null) + if (f is DirectoryInfo dirInfo) { foreach (FileInfo d in dirInfo.GetFiles()) { diff --git a/src/Umbraco.Core/HashCodeHelper.cs b/src/Umbraco.Core/HashCodeHelper.cs index 369686de6418..ecf209c53221 100644 --- a/src/Umbraco.Core/HashCodeHelper.cs +++ b/src/Umbraco.Core/HashCodeHelper.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; /// /// Borrowed from http://stackoverflow.com/a/2575444/694494 @@ -23,8 +23,7 @@ public static int GetHashCode(T1 arg1, T2 arg2, T3 arg3) } } - public static int GetHashCode(T1 arg1, T2 arg2, T3 arg3, - T4 arg4) + public static int GetHashCode(T1 arg1, T2 arg2, T3 arg3, T4 arg4) { unchecked { diff --git a/src/Umbraco.Core/HashGenerator.cs b/src/Umbraco.Core/HashGenerator.cs index 2f66c1340904..cad3d4b6b8fe 100644 --- a/src/Umbraco.Core/HashGenerator.cs +++ b/src/Umbraco.Core/HashGenerator.cs @@ -36,13 +36,12 @@ public void AddString(string s) public void AddCaseInsensitiveString(string s) { - //I've tried to no allocate a new string with this which can be done if we use the CompareInfo.GetSortKey method which will create a new - //byte array that we can use to write to the output, however this also allocates new objects so i really don't think the performance - //would be much different. In any case, I'll leave this here for reference. We could write the bytes out based on the sort key, - //this is how we could deal with case insensitivity without allocating another string - //for reference see: https://stackoverflow.com/a/10452967/694494 - //we could go a step further and s.Normalize() but we're not really dealing with crazy unicode with this class so far. - + // I've tried to no allocate a new string with this which can be done if we use the CompareInfo.GetSortKey method which will create a new + // byte array that we can use to write to the output, however this also allocates new objects so i really don't think the performance + // would be much different. In any case, I'll leave this here for reference. We could write the bytes out based on the sort key, + // this is how we could deal with case insensitivity without allocating another string + // for reference see: https://stackoverflow.com/a/10452967/694494 + // we could go a step further and s.Normalize() but we're not really dealing with crazy unicode with this class so far. if (s != null) { _writer.Write(s.ToUpperInvariant()); @@ -51,7 +50,7 @@ public void AddCaseInsensitiveString(string s) public void AddFileSystemItem(FileSystemInfo f) { - //if it doesn't exist, don't proceed. + // if it doesn't exist, don't proceed. if (f.Exists == false) { return; @@ -61,7 +60,7 @@ public void AddFileSystemItem(FileSystemInfo f) AddDateTime(f.CreationTimeUtc); AddDateTime(f.LastWriteTimeUtc); - //check if it is a file or folder + // check if it is a file or folder if (f is FileInfo fileInfo) { AddLong(fileInfo.Length); @@ -91,8 +90,7 @@ public void AddFileSystemItem(FileSystemInfo f) /// public string GenerateHash() { - //flush,close,dispose the writer,then create a new one since it's possible to keep adding after GenerateHash is called. - + // flush,close,dispose the writer,then create a new one since it's possible to keep adding after GenerateHash is called. _writer.Flush(); _writer.Close(); _writer.Dispose(); @@ -100,7 +98,7 @@ public string GenerateHash() var hashType = CryptoConfig.AllowOnlyFipsAlgorithms ? "SHA1" : "MD5"; - //create an instance of the correct hashing provider based on the type passed in + // create an instance of the correct hashing provider based on the type passed in var hasher = HashAlgorithm.Create(hashType); if (hasher == null) { @@ -110,20 +108,21 @@ public string GenerateHash() using (hasher) { var buffer = _ms.GetBuffer(); - //get the hashed values created by our selected provider + + // get the hashed values created by our selected provider var hashedByteArray = hasher.ComputeHash(buffer); - //create a StringBuilder object + // create a StringBuilder object var stringBuilder = new StringBuilder(); - //loop to each byte + // loop to each byte foreach (var b in hashedByteArray) { - //append it to our StringBuilder + // append it to our StringBuilder stringBuilder.Append(b.ToString("x2")); } - //return the hashed value + // return the hashed value return stringBuilder.ToString(); } } diff --git a/src/Umbraco.Core/HealthChecks/AcceptableConfiguration.cs b/src/Umbraco.Core/HealthChecks/AcceptableConfiguration.cs index 1768a3917eeb..42420b895432 100644 --- a/src/Umbraco.Core/HealthChecks/AcceptableConfiguration.cs +++ b/src/Umbraco.Core/HealthChecks/AcceptableConfiguration.cs @@ -1,7 +1,8 @@ -namespace Umbraco.Cms.Core.HealthChecks; +namespace Umbraco.Cms.Core.HealthChecks; public class AcceptableConfiguration { public string? Value { get; set; } + public bool IsRecommended { get; set; } } diff --git a/src/Umbraco.Core/HealthChecks/Checks/AbstractSettingsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/AbstractSettingsCheck.cs index 8898b1e30fc5..4dddf34270f5 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/AbstractSettingsCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/AbstractSettingsCheck.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Services; @@ -16,17 +16,17 @@ public abstract class AbstractSettingsCheck : HealthCheck /// protected AbstractSettingsCheck(ILocalizedTextService textService) => LocalizedTextService = textService; - /// - /// Gets the localized text service. - /// - protected ILocalizedTextService LocalizedTextService { get; } - /// /// Gets key within the JSON to check, in the colon-delimited format /// https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1 /// public abstract string ItemPath { get; } + /// + /// Gets the localized text service. + /// + protected ILocalizedTextService LocalizedTextService { get; } + /// /// Gets a link to an external resource with more information. /// @@ -50,8 +50,7 @@ public abstract class AbstractSettingsCheck : HealthCheck /// /// Gets the message for when the check has succeeded. /// - public virtual string CheckSuccessMessage => LocalizedTextService.Localize("healthcheck", "checkSuccessMessage", - new[] {CurrentValue, Values.First(v => v.IsRecommended).Value, ItemPath}); + public virtual string CheckSuccessMessage => LocalizedTextService.Localize("healthcheck", "checkSuccessMessage", new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, ItemPath }); /// /// Gets the message for when the check has failed. @@ -59,11 +58,9 @@ public abstract class AbstractSettingsCheck : HealthCheck public virtual string CheckErrorMessage => ValueComparisonType == ValueComparisonType.ShouldEqual ? LocalizedTextService.Localize( - "healthcheck", "checkErrorMessageDifferentExpectedValue", - new[] {CurrentValue, Values.First(v => v.IsRecommended).Value, ItemPath}) + "healthcheck", "checkErrorMessageDifferentExpectedValue", new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, ItemPath }) : LocalizedTextService.Localize( - "healthcheck", "checkErrorMessageUnexpectedValue", - new[] {CurrentValue, Values.First(v => v.IsRecommended).Value, ItemPath}); + "healthcheck", "checkErrorMessageUnexpectedValue", new[] { CurrentValue, Values.First(v => v.IsRecommended).Value, ItemPath }); /// public override Task> GetStatus() @@ -77,13 +74,14 @@ public override Task> GetStatus() || (ValueComparisonType == ValueComparisonType.ShouldNotEqual && valueFound == false)) { return Task.FromResult( - new HealthCheckStatus(successMessage) {ResultType = StatusResultType.Success}.Yield()); + new HealthCheckStatus(successMessage) { ResultType = StatusResultType.Success }.Yield()); } var resultMessage = string.Format(CheckErrorMessage, ItemPath, Values, CurrentValue); var healthCheckStatus = new HealthCheckStatus(resultMessage) { - ResultType = StatusResultType.Error, ReadMoreLink = ReadMoreLink + ResultType = StatusResultType.Error, + ReadMoreLink = ReadMoreLink, }; return Task.FromResult(healthCheckStatus.Yield()); diff --git a/src/Umbraco.Core/HealthChecks/Checks/Configuration/MacroErrorsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Configuration/MacroErrorsCheck.cs index 19a0e196afc8..a212a69a3eb9 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Configuration/MacroErrorsCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Configuration/MacroErrorsCheck.cs @@ -15,8 +15,7 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Configuration; [HealthCheck( "D0F7599E-9B2A-4D9E-9883-81C7EDC5616F", "Macro errors", - Description = - "Checks to make sure macro errors are not set to throw a YSOD (yellow screen of death), which would prevent certain or all pages from loading completely.", + Description = "Checks to make sure macro errors are not set to throw a YSOD (yellow screen of death), which would prevent certain or all pages from loading completely.", Group = "Configuration")] public class MacroErrorsCheck : AbstractSettingsCheck { @@ -53,8 +52,8 @@ public override IEnumerable Values { var values = new List { - new() {IsRecommended = true, Value = MacroErrorBehaviour.Inline.ToString()}, - new() {IsRecommended = false, Value = MacroErrorBehaviour.Silent.ToString()} + new() { IsRecommended = true, Value = MacroErrorBehaviour.Inline.ToString() }, + new() { IsRecommended = false, Value = MacroErrorBehaviour.Silent.ToString() }, }; return values; @@ -69,14 +68,12 @@ public override IEnumerable Values /// public override string CheckSuccessMessage => _textService.Localize( - "healthcheck", "macroErrorModeCheckSuccessMessage", - new[] {CurrentValue, Values.First(v => v.IsRecommended).Value}); + "healthcheck", "macroErrorModeCheckSuccessMessage", new[] { CurrentValue, Values.First(v => v.IsRecommended).Value }); /// /// Gets the message for when the check has failed. /// public override string CheckErrorMessage => _textService.Localize( - "healthcheck", "macroErrorModeCheckErrorMessage", - new[] {CurrentValue, Values.First(v => v.IsRecommended).Value}); + "healthcheck", "macroErrorModeCheckErrorMessage", new[] { CurrentValue, Values.First(v => v.IsRecommended).Value }); } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Data/DatabaseIntegrityCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Data/DatabaseIntegrityCheck.cs index 786a90ecf676..91fe2efb2555 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Data/DatabaseIntegrityCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Data/DatabaseIntegrityCheck.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.Text; @@ -39,39 +39,20 @@ public DatabaseIntegrityCheck( /// Get the status for this health check /// public override Task> GetStatus() => - Task.FromResult((IEnumerable)new[] {CheckDocuments(false), CheckMedia(false)}); + Task.FromResult((IEnumerable)new[] { CheckDocuments(false), CheckMedia(false) }); - private HealthCheckStatus CheckMedia(bool fix) => - CheckPaths( - SSsFixMediaPaths, - SFixMediaPathsTitle, - Constants.UdiEntityType.Media, - fix, - () => _mediaService.CheckDataIntegrity(new ContentDataIntegrityReportOptions {FixIssues = fix})); - - private HealthCheckStatus CheckDocuments(bool fix) => - CheckPaths( - SFixContentPaths, - SFixContentPathsTitle, - Constants.UdiEntityType.Document, - fix, - () => _contentService.CheckDataIntegrity(new ContentDataIntegrityReportOptions {FixIssues = fix})); - - private HealthCheckStatus CheckPaths(string actionAlias, string actionName, string entityType, bool detailedReport, - Func doCheck) + /// + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) { - ContentDataIntegrityReport report = doCheck(); - - var actions = new List(); - if (!report.Ok) + switch (action.Alias) { - actions.Add(new HealthCheckAction(actionAlias, Id) {Name = actionName}); + case SFixContentPaths: + return CheckDocuments(true); + case SSsFixMediaPaths: + return CheckMedia(true); + default: + throw new InvalidOperationException("Action not supported"); } - - return new HealthCheckStatus(GetReport(report, entityType, detailedReport)) - { - ResultType = report.Ok ? StatusResultType.Success : StatusResultType.Error, Actions = actions - }; } private static string GetReport(ContentDataIntegrityReport report, string entityType, bool detailed) @@ -111,17 +92,37 @@ issueGroup in report.DetectedIssues.GroupBy(x => x.Value.IssueType)) return sb.ToString(); } - /// - public override HealthCheckStatus ExecuteAction(HealthCheckAction action) + private HealthCheckStatus CheckMedia(bool fix) => + CheckPaths( + SSsFixMediaPaths, + SFixMediaPathsTitle, + Constants.UdiEntityType.Media, + fix, + () => _mediaService.CheckDataIntegrity(new ContentDataIntegrityReportOptions { FixIssues = fix })); + + private HealthCheckStatus CheckDocuments(bool fix) => + CheckPaths( + SFixContentPaths, + SFixContentPathsTitle, + Constants.UdiEntityType.Document, + fix, + () => _contentService.CheckDataIntegrity(new ContentDataIntegrityReportOptions { FixIssues = fix })); + + private HealthCheckStatus CheckPaths(string actionAlias, string actionName, string entityType, bool detailedReport, + Func doCheck) { - switch (action.Alias) + ContentDataIntegrityReport report = doCheck(); + + var actions = new List(); + if (!report.Ok) { - case SFixContentPaths: - return CheckDocuments(true); - case SSsFixMediaPaths: - return CheckMedia(true); - default: - throw new InvalidOperationException("Action not supported"); + actions.Add(new HealthCheckAction(actionAlias, Id) { Name = actionName }); } + + return new HealthCheckStatus(GetReport(report, entityType, detailedReport)) + { + ResultType = report.Ok ? StatusResultType.Success : StatusResultType.Error, + Actions = actions, + }; } } diff --git a/src/Umbraco.Core/HealthChecks/Checks/LiveEnvironment/CompilationDebugCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/LiveEnvironment/CompilationDebugCheck.cs index f849e6e6daed..ee4d9fe78863 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/LiveEnvironment/CompilationDebugCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/LiveEnvironment/CompilationDebugCheck.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.Options; @@ -14,8 +14,7 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.LiveEnvironment; [HealthCheck( "61214FF3-FC57-4B31-B5CF-1D095C977D6D", "Debug Compilation Mode", - Description = - "Leaving debug compilation mode enabled can severely slow down a website and take up more memory on the server.", + Description = "Leaving debug compilation mode enabled can severely slow down a website and take up more memory on the server.", Group = "Live Environment")] public class CompilationDebugCheck : AbstractSettingsCheck { @@ -41,7 +40,7 @@ public CompilationDebugCheck(ILocalizedTextService textService, IOptionsMonitor< /// public override IEnumerable Values => new List { - new() {IsRecommended = true, Value = bool.FalseString.ToLower()} + new() { IsRecommended = true, Value = bool.FalseString.ToLower() }, }; /// diff --git a/src/Umbraco.Core/HealthChecks/Checks/Permissions/FolderAndFilePermissionsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Permissions/FolderAndFilePermissionsCheck.cs index c517db8c4db5..13a45c169c60 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Permissions/FolderAndFilePermissionsCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Permissions/FolderAndFilePermissionsCheck.cs @@ -44,10 +44,16 @@ public override Task> GetStatus() { ResultType = x.Value.Any() ? StatusResultType.Error : StatusResultType.Success, ReadMoreLink = GetReadMoreLink(x), - Description = GetErrorDescription(x) + Description = GetErrorDescription(x), })); } + /// + /// Executes the action and returns it's status + /// + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => + throw new InvalidOperationException("FolderAndFilePermissionsCheck has no executable actions"); + private string? GetErrorDescription(KeyValuePair> status) { if (!status.Value.Any()) @@ -87,13 +93,8 @@ private string GetMessage(KeyValuePair> return Constants.HealthChecks.DocumentationLinks.FolderAndFilePermissionsCheck.FileWritingForPackages; case FilePermissionTest.MediaFolderCreation: return Constants.HealthChecks.DocumentationLinks.FolderAndFilePermissionsCheck.MediaFolderCreation; - default: return null; + default: + return null; } } - - /// - /// Executes the action and returns it's status - /// - public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => - throw new InvalidOperationException("FolderAndFilePermissionsCheck has no executable actions"); } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs index 70736a7f4325..f27c786b84ff 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs @@ -13,7 +13,7 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security; /// public abstract class BaseHttpHeaderCheck : HealthCheck { - private static HttpClient? s_httpClient; + private static HttpClient? httpClient; private readonly string _header; private readonly IHostingEnvironment _hostingEnvironment; private readonly string _localizedTextPrefix; @@ -26,7 +26,8 @@ protected BaseHttpHeaderCheck( string header, string value, string localizedTextPrefix, - bool metaTagOptionAvailable) : this(hostingEnvironment, textService, header, localizedTextPrefix, + bool metaTagOptionAvailable) + : this(hostingEnvironment, textService, header, localizedTextPrefix, metaTagOptionAvailable) { } @@ -51,13 +52,13 @@ protected BaseHttpHeaderCheck( [Obsolete("Save ILocalizedTextService in a field on the super class instead of using this")] protected ILocalizedTextService LocalizedTextService { get; } - private static HttpClient HttpClient => s_httpClient ??= new HttpClient(); - /// /// Gets a link to an external read more page. /// protected abstract string ReadMoreLink { get; } + private static HttpClient HttpClient => httpClient ??= new HttpClient(); + /// /// Get the status for this health check /// @@ -101,17 +102,25 @@ protected async Task CheckForHeader() } catch (Exception ex) { - message = LocalizedTextService.Localize("healthcheck", "healthCheckInvalidUrl", new[] {url, ex.Message}); + message = LocalizedTextService.Localize("healthcheck", "healthCheckInvalidUrl", new[] { url, ex.Message }); } return new HealthCheckStatus(message) { ResultType = success ? StatusResultType.Success : StatusResultType.Error, - ReadMoreLink = success ? null : ReadMoreLink + ReadMoreLink = success ? null : ReadMoreLink, }; } + private static Dictionary ParseMetaTags(string html) + { + var regex = new Regex(" m.Groups[1].Value, m => m.Groups[2].Value); + } + private bool HasMatchingHeader(IEnumerable headerKeys) => headerKeys.Contains(_header, StringComparer.InvariantCultureIgnoreCase); @@ -132,12 +141,4 @@ private async Task DoMetaTagsContainKeyForHeader(HttpResponseMessage respo } } } - - private static Dictionary ParseMetaTags(string html) - { - var regex = new Regex(" m.Groups[1].Value, m => m.Groups[2].Value); - } } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs index 1f26a88c59c5..e211d7c25793 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/ExcessiveHeadersCheck.cs @@ -13,12 +13,11 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security; [HealthCheck( "92ABBAA2-0586-4089-8AE2-9A843439D577", "Excessive Headers", - Description = - "Checks to see if your site is revealing information in its headers that gives away unnecessary details about the technology used to build and host it.", + Description = "Checks to see if your site is revealing information in its headers that gives away unnecessary details about the technology used to build and host it.", Group = "Security")] public class ExcessiveHeadersCheck : HealthCheck { - private static HttpClient? s_httpClient; + private static HttpClient? httpClient; private readonly IHostingEnvironment _hostingEnvironment; private readonly ILocalizedTextService _textService; @@ -31,7 +30,7 @@ public ExcessiveHeadersCheck(ILocalizedTextService textService, IHostingEnvironm _hostingEnvironment = hostingEnvironment; } - private static HttpClient HttpClient => s_httpClient ??= new HttpClient(); + private static HttpClient HttpClient => httpClient ??= new HttpClient(); /// /// Get the status for this health check @@ -59,11 +58,11 @@ private async Task CheckForHeaders() IEnumerable allHeaders = response.Headers.Select(x => x.Key); var headersToCheckFor = - new List {"Server", "X-Powered-By", "X-AspNet-Version", "X-AspNetMvc-Version"}; + new List { "Server", "X-Powered-By", "X-AspNet-Version", "X-AspNetMvc-Version" }; // Ignore if server header is present and it's set to cloudflare if (allHeaders.InvariantContains("Server") && - response.Headers.TryGetValues("Server", out IEnumerable serverHeaders) && + response.Headers.TryGetValues("Server", out IEnumerable? serverHeaders) && (serverHeaders.FirstOrDefault()?.InvariantEquals("cloudflare") ?? false)) { headersToCheckFor.Remove("Server"); @@ -75,12 +74,11 @@ private async Task CheckForHeaders() success = headersFound.Any() == false; message = success ? _textService.Localize("healthcheck", "excessiveHeadersNotFound") - : _textService.Localize("healthcheck", "excessiveHeadersFound", - new[] {string.Join(", ", headersFound)}); + : _textService.Localize("healthcheck", "excessiveHeadersFound", new[] { string.Join(", ", headersFound) }); } catch (Exception ex) { - message = _textService.Localize("healthcheck", "healthCheckInvalidUrl", new[] {url, ex.Message}); + message = _textService.Localize("healthcheck", "healthCheckInvalidUrl", new[] { url, ex.Message }); } return @@ -89,7 +87,7 @@ private async Task CheckForHeaders() ResultType = success ? StatusResultType.Success : StatusResultType.Warning, ReadMoreLink = success ? null - : Constants.HealthChecks.DocumentationLinks.Security.ExcessiveHeadersCheck + : Constants.HealthChecks.DocumentationLinks.Security.ExcessiveHeadersCheck, }; } } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs index 7f1cdb85db1a..c9d1a69aec27 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs @@ -18,15 +18,14 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security; [HealthCheck( "EB66BB3B-1BCD-4314-9531-9DA2C1D6D9A7", "HTTPS Configuration", - Description = - "Checks if your site is configured to work over HTTPS and if the Umbraco related configuration for that is correct.", + Description = "Checks if your site is configured to work over HTTPS and if the Umbraco related configuration for that is correct.", Group = "Security")] public class HttpsCheck : HealthCheck { private const int NumberOfDaysForExpiryWarning = 14; private const string HttpPropertyKeyCertificateDaysToExpiry = "CertificateDaysToExpiry"; - private static HttpClient? s_httpClient; + private static HttpClient? httpClient; private readonly IOptionsMonitor _globalSettings; private readonly IHostingEnvironment _hostingEnvironment; @@ -48,9 +47,9 @@ public HttpsCheck( _hostingEnvironment = hostingEnvironment; } - private static HttpClient HttpClient => s_httpClient ??= new HttpClient(new HttpClientHandler + private static HttpClient HttpClient => httpClient ??= new HttpClient(new HttpClientHandler { - ServerCertificateCustomValidationCallback = ServerCertificateCustomValidation + ServerCertificateCustomValidationCallback = ServerCertificateCustomValidation, }); /// @@ -65,8 +64,11 @@ public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => throw new InvalidOperationException( "HttpsCheck action requested is either not executable or does not exist"); - private static bool ServerCertificateCustomValidation(HttpRequestMessage requestMessage, - X509Certificate2? certificate, X509Chain? chain, SslPolicyErrors sslErrors) + private static bool ServerCertificateCustomValidation( + HttpRequestMessage requestMessage, + X509Certificate2? certificate, + X509Chain? chain, + SslPolicyErrors sslErrors) { if (certificate is not null) { @@ -83,7 +85,7 @@ private async Task CheckForValidCertificate() StatusResultType result; // Attempt to access the site over HTTPS to see if it HTTPS is supported and a valid certificate has been configured - var urlBuilder = new UriBuilder(_hostingEnvironment.ApplicationMainUrl) {Scheme = Uri.UriSchemeHttps}; + var urlBuilder = new UriBuilder(_hostingEnvironment.ApplicationMainUrl) { Scheme = Uri.UriSchemeHttps }; Uri url = urlBuilder.Uri; var request = new HttpRequestMessage(HttpMethod.Head, url); @@ -96,8 +98,9 @@ private async Task CheckForValidCertificate() { // Got a valid response, check now if the certificate is expiring within the specified amount of days int? daysToExpiry = 0; - if (request.Properties.TryGetValue(HttpPropertyKeyCertificateDaysToExpiry, - out var certificateDaysToExpiry)) + if (request.Properties.TryGetValue( + HttpPropertyKeyCertificateDaysToExpiry, + out var certificateDaysToExpiry)) { daysToExpiry = (int?)certificateDaysToExpiry; } @@ -110,8 +113,7 @@ private async Task CheckForValidCertificate() else if (daysToExpiry < NumberOfDaysForExpiryWarning) { result = StatusResultType.Warning; - message = _textService.Localize("healthcheck", "httpsCheckExpiringCertificate", - new[] {daysToExpiry.ToString()}); + message = _textService.Localize("healthcheck", "httpsCheckExpiringCertificate", new[] { daysToExpiry.ToString() }); } else { @@ -122,8 +124,7 @@ private async Task CheckForValidCertificate() else { result = StatusResultType.Error; - message = _textService.Localize("healthcheck", "healthCheckInvalidUrl", - new[] {url.AbsoluteUri, response.ReasonPhrase}); + message = _textService.Localize("healthcheck", "healthCheckInvalidUrl", new[] { url.AbsoluteUri, response.ReasonPhrase }); } } catch (Exception ex) @@ -131,14 +132,12 @@ private async Task CheckForValidCertificate() if (ex is WebException exception) { message = exception.Status == WebExceptionStatus.TrustFailure - ? _textService.Localize("healthcheck", "httpsCheckInvalidCertificate", new[] {exception.Message}) - : _textService.Localize("healthcheck", "healthCheckInvalidUrl", - new[] {url.AbsoluteUri, exception.Message}); + ? _textService.Localize("healthcheck", "httpsCheckInvalidCertificate", new[] { exception.Message }) + : _textService.Localize("healthcheck", "healthCheckInvalidUrl", new[] { url.AbsoluteUri, exception.Message }); } else { - message = _textService.Localize("healthcheck", "healthCheckInvalidUrl", - new[] {url.AbsoluteUri, ex.Message}); + message = _textService.Localize("healthcheck", "healthCheckInvalidUrl", new[] { url.AbsoluteUri, ex.Message }); } result = StatusResultType.Error; @@ -149,7 +148,7 @@ private async Task CheckForValidCertificate() ResultType = result, ReadMoreLink = result == StatusResultType.Success ? null - : Constants.HealthChecks.DocumentationLinks.Security.HttpsCheck.CheckIfCurrentSchemeIsHttps + : Constants.HealthChecks.DocumentationLinks.Security.HttpsCheck.CheckIfCurrentSchemeIsHttps, }; } @@ -159,13 +158,12 @@ private Task CheckIfCurrentSchemeIsHttps() var success = uri.Scheme == Uri.UriSchemeHttps; return Task.FromResult( - new HealthCheckStatus(_textService.Localize("healthcheck", "httpsCheckIsCurrentSchemeHttps", - new[] {success ? string.Empty : "not"})) + new HealthCheckStatus(_textService.Localize("healthcheck", "httpsCheckIsCurrentSchemeHttps", new[] { success ? string.Empty : "not" })) { ResultType = success ? StatusResultType.Success : StatusResultType.Error, ReadMoreLink = success ? null - : Constants.HealthChecks.DocumentationLinks.Security.HttpsCheck.CheckIfCurrentSchemeIsHttps + : Constants.HealthChecks.DocumentationLinks.Security.HttpsCheck.CheckIfCurrentSchemeIsHttps, }); } @@ -183,8 +181,7 @@ private Task CheckHttpsConfigurationSetting() } else { - resultMessage = _textService.Localize("healthcheck", "httpsCheckConfigurationCheckResult", - new[] {httpsSettingEnabled.ToString(), httpsSettingEnabled ? string.Empty : "not"}); + resultMessage = _textService.Localize("healthcheck", "httpsCheckConfigurationCheckResult", new[] { httpsSettingEnabled.ToString(), httpsSettingEnabled ? string.Empty : "not" }); resultType = httpsSettingEnabled ? StatusResultType.Success : StatusResultType.Error; } @@ -193,7 +190,7 @@ private Task CheckHttpsConfigurationSetting() ResultType = resultType, ReadMoreLink = resultType == StatusResultType.Success ? null - : Constants.HealthChecks.DocumentationLinks.Security.HttpsCheck.CheckHttpsConfigurationSetting + : Constants.HealthChecks.DocumentationLinks.Security.HttpsCheck.CheckHttpsConfigurationSetting, }); } } diff --git a/src/Umbraco.Core/HealthChecks/ConfigurationServiceResult.cs b/src/Umbraco.Core/HealthChecks/ConfigurationServiceResult.cs index e2c9df2eeb63..564bcc59a557 100644 --- a/src/Umbraco.Core/HealthChecks/ConfigurationServiceResult.cs +++ b/src/Umbraco.Core/HealthChecks/ConfigurationServiceResult.cs @@ -1,7 +1,8 @@ -namespace Umbraco.Cms.Core.HealthChecks; +namespace Umbraco.Cms.Core.HealthChecks; public class ConfigurationServiceResult { public bool Success { get; set; } + public string? Result { get; set; } } diff --git a/src/Umbraco.Core/HealthChecks/HealthCheck.cs b/src/Umbraco.Core/HealthChecks/HealthCheck.cs index ad1382825777..06a1bd27f3b0 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheck.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheck.cs @@ -13,7 +13,7 @@ public abstract class HealthCheck : IDiscoverable protected HealthCheck() { Type thisType = GetType(); - HealthCheckAttribute meta = thisType.GetCustomAttribute(false); + HealthCheckAttribute? meta = thisType.GetCustomAttribute(false); if (meta == null) { throw new InvalidOperationException( @@ -26,13 +26,17 @@ protected HealthCheck() Id = meta.Id; } - [DataMember(Name = "id")] public Guid Id { get; private set; } + [DataMember(Name = "id")] + public Guid Id { get; private set; } - [DataMember(Name = "name")] public string Name { get; private set; } + [DataMember(Name = "name")] + public string Name { get; private set; } - [DataMember(Name = "description")] public string? Description { get; private set; } + [DataMember(Name = "description")] + public string? Description { get; private set; } - [DataMember(Name = "group")] public string? Group { get; private set; } + [DataMember(Name = "group")] + public string? Group { get; private set; } /// /// Get the status for this health check diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckAction.cs b/src/Umbraco.Core/HealthChecks/HealthCheckAction.cs index 5fa189d42e33..7593a54cc2cd 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckAction.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckAction.cs @@ -8,12 +8,15 @@ public class HealthCheckAction /// /// The name of the action - this is used to name the fix button /// - [DataMember(Name = "name")] private string? _name; + [DataMember(Name = "name")] + private string? _name; /// /// Empty ctor used for serialization /// - public HealthCheckAction() { } + public HealthCheckAction() + { + } /// /// Default ctor diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckAttribute.cs b/src/Umbraco.Core/HealthChecks/HealthCheckAttribute.cs index a78be543ecfb..718a689caff6 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckAttribute.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.HealthChecks; +namespace Umbraco.Cms.Core.HealthChecks; /// /// Metadata attribute for Health checks @@ -13,6 +13,7 @@ public HealthCheckAttribute(string id, string name) } public string Name { get; } + public string? Description { get; set; } public string? Group { get; set; } diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckCollection.cs b/src/Umbraco.Core/HealthChecks/HealthCheckCollection.cs index 7f3874ed764c..c2c47c194863 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckCollection.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckCollection.cs @@ -4,7 +4,8 @@ namespace Umbraco.Cms.Core.HealthChecks; public class HealthCheckCollection : BuilderCollectionBase { - public HealthCheckCollection(Func> items) : base(items) + public HealthCheckCollection(Func> items) + : base(items) { } } diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckGroup.cs b/src/Umbraco.Core/HealthChecks/HealthCheckGroup.cs index c0cc713eca5a..ae67c192f5d0 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckGroup.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckGroup.cs @@ -1,11 +1,13 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.HealthChecks; [DataContract(Name = "healthCheckGroup", Namespace = "")] public class HealthCheckGroup { - [DataMember(Name = "name")] public string? Name { get; set; } + [DataMember(Name = "name")] + public string? Name { get; set; } - [DataMember(Name = "checks")] public List? Checks { get; set; } + [DataMember(Name = "checks")] + public List? Checks { get; set; } } diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodAttribute.cs b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodAttribute.cs index 3e5cef461a8e..128e6dabbe37 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodAttribute.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.HealthChecks; +namespace Umbraco.Cms.Core.HealthChecks; /// /// Metadata attribute for health check notification methods diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollection.cs b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollection.cs index c31a652a7b98..1d681690db66 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollection.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollection.cs @@ -5,8 +5,8 @@ namespace Umbraco.Cms.Core.HealthChecks; public class HealthCheckNotificationMethodCollection : BuilderCollectionBase { - public HealthCheckNotificationMethodCollection(Func> items) : - base(items) + public HealthCheckNotificationMethodCollection(Func> items) + : base(items) { } } diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollectionBuilder.cs b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollectionBuilder.cs index 3b7cf6699f95..375ddc7e2e6b 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollectionBuilder.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationMethodCollectionBuilder.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.HealthChecks.NotificationMethods; namespace Umbraco.Cms.Core.HealthChecks; diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckNotificationVerbosity.cs b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationVerbosity.cs index 86914d91d717..1e7ea9053217 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckNotificationVerbosity.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckNotificationVerbosity.cs @@ -1,7 +1,7 @@ -namespace Umbraco.Cms.Core.HealthChecks; +namespace Umbraco.Cms.Core.HealthChecks; public enum HealthCheckNotificationVerbosity { Summary, - Detailed + Detailed, } diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs b/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs index 85e736cea943..afeb8ba9fa3b 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckResults.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; using Microsoft.Extensions.Logging; using Umbraco.Extensions; @@ -14,11 +14,10 @@ private HealthCheckResults(Dictionary> re AllChecksSuccessful = allChecksSuccessful; } - private static ILogger Logger => StaticApplicationLogging.Logger; // TODO: inject - - internal Dictionary> ResultsAsDictionary { get; } + private static ILogger Logger => StaticApplicationLogging.Logger; // TODO: inject + public static async Task Create(IEnumerable checks) { Dictionary> results = await checks.ToDictionaryAsync( @@ -33,7 +32,7 @@ public static async Task Create(IEnumerable che { Logger.LogError(ex, "Error running scheduled health check: {HealthCheckName}", t.Name); var message = $"Health check failed with exception: {ex.Message}. See logs for details."; - return new List {new(message) {ResultType = StatusResultType.Error}}; + return new List { new(message) { ResultType = StatusResultType.Error } }; } }); @@ -74,8 +73,10 @@ public void LogResults() foreach (HealthCheckStatus checkResult in checkResults) { Logger.LogInformation( - "Result for {HealthCheckName}: {HealthCheckResult}, Message: '{HealthCheckMessage}'", checkName, - checkResult.ResultType, checkResult.Message); + "Result for {HealthCheckName}: {HealthCheckResult}, Message: '{HealthCheckMessage}'", + checkName, + checkResult.ResultType, + checkResult.Message); } } } @@ -100,13 +101,11 @@ public string ResultsAsMarkDown(HealthCheckNotificationVerbosity verbosity) if (checkIsSuccess) { - sb.AppendFormat("{0}Checks for '{1}' all completed successfully.{2}", newItem, checkName, - Environment.NewLine); + sb.AppendFormat("{0}Checks for '{1}' all completed successfully.{2}", newItem, checkName, Environment.NewLine); } else { - sb.AppendFormat("{0}Checks for '{1}' completed with errors.{2}", newItem, checkName, - Environment.NewLine); + sb.AppendFormat("{0}Checks for '{1}' completed with errors.{2}", newItem, checkName, Environment.NewLine); } foreach (HealthCheckStatus checkResult in checkResults) @@ -127,12 +126,6 @@ public string ResultsAsMarkDown(HealthCheckNotificationVerbosity verbosity) return sb.ToString(); } - private string SimpleHtmlToMarkDown(string html) => - html.Replace("", "**") - .Replace("", "**") - .Replace("", "*") - .Replace("", "*"); - public Dictionary>? GetResultsForStatus(StatusResultType resultType) { switch (resultType) @@ -166,4 +159,10 @@ private string SimpleHtmlToMarkDown(string html) => return null; } + + private string SimpleHtmlToMarkDown(string html) => + html.Replace("", "**") + .Replace("", "**") + .Replace("", "*") + .Replace("", "*"); } diff --git a/src/Umbraco.Core/HealthChecks/HealthCheckStatus.cs b/src/Umbraco.Core/HealthChecks/HealthCheckStatus.cs index 45141e3d1798..7f04e5154159 100644 --- a/src/Umbraco.Core/HealthChecks/HealthCheckStatus.cs +++ b/src/Umbraco.Core/HealthChecks/HealthCheckStatus.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.HealthChecks; diff --git a/src/Umbraco.Core/HealthChecks/HeathCheckCollectionBuilder.cs b/src/Umbraco.Core/HealthChecks/HeathCheckCollectionBuilder.cs index 2a80af720d12..1c026248c80b 100644 --- a/src/Umbraco.Core/HealthChecks/HeathCheckCollectionBuilder.cs +++ b/src/Umbraco.Core/HealthChecks/HeathCheckCollectionBuilder.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.HealthChecks; diff --git a/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs b/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs index 6ece967657ca..022531c1eccf 100644 --- a/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs +++ b/src/Umbraco.Core/HealthChecks/NotificationMethods/EmailNotificationMethod.cs @@ -58,19 +58,20 @@ public override async Task SendAsync(HealthCheckResults results) return; } - var message = _textService?.Localize("healthcheck", "scheduledHealthCheckEmailBody", + var message = _textService?.Localize( + "healthcheck", + "scheduledHealthCheckEmailBody", new[] { DateTime.Now.ToShortDateString(), DateTime.Now.ToShortTimeString(), - _markdownToHtmlConverter?.ToHtml(results, Verbosity) + _markdownToHtmlConverter?.ToHtml(results, Verbosity), }); // Include the umbraco Application URL host in the message subject so that // you can identify the site that these results are for. var host = _hostingEnvironment?.ApplicationMainUrl?.ToString(); - var subject = _textService?.Localize("healthcheck", "scheduledHealthCheckEmailSubject", new[] {host}); - + var subject = _textService?.Localize("healthcheck", "scheduledHealthCheckEmailSubject", new[] { host }); EmailMessage mailMessage = CreateMailMessage(subject, message); Task? task = _emailSender?.SendAsync(mailMessage, Constants.Web.EmailTypes.HealthCheck); diff --git a/src/Umbraco.Core/HealthChecks/NotificationMethods/IHealthCheckNotificationMethod.cs b/src/Umbraco.Core/HealthChecks/NotificationMethods/IHealthCheckNotificationMethod.cs index 6b95bf90fe03..aa343ef608d0 100644 --- a/src/Umbraco.Core/HealthChecks/NotificationMethods/IHealthCheckNotificationMethod.cs +++ b/src/Umbraco.Core/HealthChecks/NotificationMethods/IHealthCheckNotificationMethod.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.HealthChecks.NotificationMethods; diff --git a/src/Umbraco.Core/HealthChecks/NotificationMethods/IMarkdownToHtmlConverter.cs b/src/Umbraco.Core/HealthChecks/NotificationMethods/IMarkdownToHtmlConverter.cs index 7cbd54cac2a8..87b6a6dbff67 100644 --- a/src/Umbraco.Core/HealthChecks/NotificationMethods/IMarkdownToHtmlConverter.cs +++ b/src/Umbraco.Core/HealthChecks/NotificationMethods/IMarkdownToHtmlConverter.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.HealthChecks.NotificationMethods; +namespace Umbraco.Cms.Core.HealthChecks.NotificationMethods; public interface IMarkdownToHtmlConverter { diff --git a/src/Umbraco.Core/HexEncoder.cs b/src/Umbraco.Core/HexEncoder.cs index 3b6e7c854cdf..b95376646b25 100644 --- a/src/Umbraco.Core/HexEncoder.cs +++ b/src/Umbraco.Core/HexEncoder.cs @@ -1,4 +1,4 @@ -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; namespace Umbraco.Cms.Core; @@ -10,7 +10,7 @@ public static class HexEncoder // LUT's that provide the hexadecimal representation of each possible byte value. private static readonly char[] HexLutBase = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', }; // The base LUT arranged in 16x each item order. 0 * 16, 1 * 16, .... F * 16 diff --git a/src/Umbraco.Core/Hosting/IApplicationShutdownRegistry.cs b/src/Umbraco.Core/Hosting/IApplicationShutdownRegistry.cs index a642a9ab00b2..84b275714b03 100644 --- a/src/Umbraco.Core/Hosting/IApplicationShutdownRegistry.cs +++ b/src/Umbraco.Core/Hosting/IApplicationShutdownRegistry.cs @@ -3,5 +3,6 @@ namespace Umbraco.Cms.Core.Hosting; public interface IApplicationShutdownRegistry { void RegisterObject(IRegisteredObject registeredObject); + void UnregisterObject(IRegisteredObject registeredObject); } diff --git a/src/Umbraco.Core/HybridAccessorBase.cs b/src/Umbraco.Core/HybridAccessorBase.cs index dc8db452a8c7..fdee8e4ec5dc 100644 --- a/src/Umbraco.Core/HybridAccessorBase.cs +++ b/src/Umbraco.Core/HybridAccessorBase.cs @@ -16,7 +16,7 @@ namespace Umbraco.Cms.Core; public abstract class HybridAccessorBase where T : class { - private static readonly AsyncLocal s_ambientContext = new(); + private static readonly AsyncLocal AmbientContext = new(); private readonly IRequestCache _requestCache; private string? _itemKey; @@ -26,27 +26,6 @@ protected HybridAccessorBase(IRequestCache requestCache) protected string ItemKey => _itemKey ??= GetType().FullName!; - // read - // http://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html - // http://stackoverflow.com/questions/14176028/why-does-logicalcallcontext-not-work-with-async - // http://stackoverflow.com/questions/854976/will-values-in-my-threadstatic-variables-still-be-there-when-cycled-via-threadpo - // https://msdn.microsoft.com/en-us/library/dd642243.aspx?f=255&MSPPError=-2147217396 ThreadLocal - // http://stackoverflow.com/questions/29001266/cleaning-up-callcontext-in-tpl clearing call context - // - // anything that is ThreadStatic will stay with the thread and NOT flow in async threads - // the only thing that flows is the logical call context (safe in 4.5+) - - // no! - //[ThreadStatic] - //private static T _value; - - // yes! flows with async! - private T? NonContextValue - { - get => s_ambientContext.Value ?? default; - set => s_ambientContext.Value = value; - } - protected T? Value { get @@ -75,4 +54,25 @@ protected T? Value } } } + + // read + // http://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html + // http://stackoverflow.com/questions/14176028/why-does-logicalcallcontext-not-work-with-async + // http://stackoverflow.com/questions/854976/will-values-in-my-threadstatic-variables-still-be-there-when-cycled-via-threadpo + // https://msdn.microsoft.com/en-us/library/dd642243.aspx?f=255&MSPPError=-2147217396 ThreadLocal + // http://stackoverflow.com/questions/29001266/cleaning-up-callcontext-in-tpl clearing call context + // + // anything that is ThreadStatic will stay with the thread and NOT flow in async threads + // the only thing that flows is the logical call context (safe in 4.5+) + + // no! + // [ThreadStatic] + // private static T _value; + + // yes! flows with async! + private T? NonContextValue + { + get => AmbientContext.Value ?? default; + set => AmbientContext.Value = value; + } } diff --git a/src/Umbraco.Core/ICompletable.cs b/src/Umbraco.Core/ICompletable.cs index 53eebe667f1f..b13000de22b1 100644 --- a/src/Umbraco.Core/ICompletable.cs +++ b/src/Umbraco.Core/ICompletable.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; public interface ICompletable : IDisposable { diff --git a/src/Umbraco.Core/IO/CleanFolderResult.cs b/src/Umbraco.Core/IO/CleanFolderResult.cs index 2cba7d0d4d65..76d1767eabd2 100644 --- a/src/Umbraco.Core/IO/CleanFolderResult.cs +++ b/src/Umbraco.Core/IO/CleanFolderResult.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.IO; +namespace Umbraco.Cms.Core.IO; public class CleanFolderResult { @@ -10,13 +10,13 @@ private CleanFolderResult() public IReadOnlyCollection? Errors { get; private set; } - public static CleanFolderResult Success() => new CleanFolderResult {Status = CleanFolderResultStatus.Success}; + public static CleanFolderResult Success() => new CleanFolderResult { Status = CleanFolderResultStatus.Success }; public static CleanFolderResult FailedAsDoesNotExist() => - new CleanFolderResult {Status = CleanFolderResultStatus.FailedAsDoesNotExist}; + new CleanFolderResult { Status = CleanFolderResultStatus.FailedAsDoesNotExist }; public static CleanFolderResult FailedWithErrors(List errors) => - new CleanFolderResult {Status = CleanFolderResultStatus.FailedWithException, Errors = errors.AsReadOnly()}; + new CleanFolderResult { Status = CleanFolderResultStatus.FailedWithException, Errors = errors.AsReadOnly() }; public class Error { diff --git a/src/Umbraco.Core/IO/CleanFolderResultStatus.cs b/src/Umbraco.Core/IO/CleanFolderResultStatus.cs index 2d394bc52a91..73d32982aa78 100644 --- a/src/Umbraco.Core/IO/CleanFolderResultStatus.cs +++ b/src/Umbraco.Core/IO/CleanFolderResultStatus.cs @@ -1,8 +1,8 @@ -namespace Umbraco.Cms.Core.IO; +namespace Umbraco.Cms.Core.IO; public enum CleanFolderResultStatus { Success, FailedAsDoesNotExist, - FailedWithException + FailedWithException, } diff --git a/src/Umbraco.Core/IO/FileSystemExtensions.cs b/src/Umbraco.Core/IO/FileSystemExtensions.cs index d758c34ed23c..44bc1ac2ad5b 100644 --- a/src/Umbraco.Core/IO/FileSystemExtensions.cs +++ b/src/Umbraco.Core/IO/FileSystemExtensions.cs @@ -32,8 +32,7 @@ public static string GetStreamHash(this Stream fileStream) /// Attempts to open the file at filePath up to maxRetries times, /// with a thread sleep time of sleepPerRetryInMilliseconds between retries. /// - public static FileStream OpenReadWithRetry(this FileInfo file, int maxRetries = 5, - int sleepPerRetryInMilliseconds = 50) + public static FileStream OpenReadWithRetry(this FileInfo file, int maxRetries = 5, int sleepPerRetryInMilliseconds = 50) { var retries = maxRetries; @@ -95,13 +94,14 @@ public static void CreateFolder(this IFileSystem fs, string folderPath) /// /// true if the was successfully created; otherwise, false. /// - public static bool TryCreateFileProvider(this IFileSystem fileSystem, + public static bool TryCreateFileProvider( + this IFileSystem fileSystem, [MaybeNullWhen(false)] out IFileProvider fileProvider) { fileProvider = fileSystem switch { IFileProviderFactory fileProviderFactory => fileProviderFactory.Create(), - _ => null + _ => null, }; return fileProvider != null; diff --git a/src/Umbraco.Core/IO/FileSystems.cs b/src/Umbraco.Core/IO/FileSystems.cs index 81d3d35e3b39..c1bf9f183fd9 100644 --- a/src/Umbraco.Core/IO/FileSystems.cs +++ b/src/Umbraco.Core/IO/FileSystems.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; @@ -22,7 +22,6 @@ public sealed class FileSystems private readonly List _shadowWrappers = new(); private readonly GlobalSettings _globalSettings; - // wrappers for shadow support private ShadowWrapper? _macroPartialFileSystem; private ShadowWrapper? _mvcViewsFileSystem; @@ -61,13 +60,15 @@ internal FileSystems( IFileSystem partialViewsFileSystem, IFileSystem stylesheetFileSystem, IFileSystem scriptsFileSystem, - IFileSystem mvcViewFileSystem) : this(loggerFactory, ioHelper, globalSettings, hostingEnvironment) + IFileSystem mvcViewFileSystem) + : this(loggerFactory, ioHelper, globalSettings, hostingEnvironment) { _macroPartialFileSystem = CreateShadowWrapperInternal(macroPartialFileSystem, "macro-partials"); _partialViewsFileSystem = CreateShadowWrapperInternal(partialViewsFileSystem, "partials"); _stylesheetsFileSystem = CreateShadowWrapperInternal(stylesheetFileSystem, "css"); _scriptsFileSystem = CreateShadowWrapperInternal(scriptsFileSystem, "scripts"); _mvcViewsFileSystem = CreateShadowWrapperInternal(mvcViewFileSystem, "view"); + // Set initialized to true so the filesystems doesn't get overwritten. _wkfsInitialized = true; } @@ -198,14 +199,53 @@ public void SetStylesheetFilesystem(IFileSystem fileSystem) "Can't register the stylesheet filesystem, " + "this is most likely caused by using a PhysicalFileSystem with an incorrect " + "rootPath/rootUrl. RootPath must be \\wwwroot\\css" - + " and rootUrl must be /css", exception); + + " and rootUrl must be /css", + exception); } _stylesheetsFileSystem = CreateShadowWrapperInternal(fileSystem, "css"); } - private void EnsureWellKnownFileSystems() => LazyInitializer.EnsureInitialized(ref _wkfsObject, - ref _wkfsInitialized, ref _wkfsLock, CreateWellKnownFileSystems); + #endregion + + #region Shadow + + // note + // shadowing is thread-safe, but entering and exiting shadow mode is not, and there is only one + // global shadow for the entire application, so great care should be taken to ensure that the + // application is *not* doing anything else when using a shadow. + + /// + /// Shadows the filesystem, should never be used outside the Scope class. + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public ICompletable Shadow() + { + if (Volatile.Read(ref _wkfsInitialized) == false) + { + EnsureWellKnownFileSystems(); + } + + var id = ShadowWrapper.CreateShadowId(_hostingEnvironment); + return new ShadowFileSystems(this, id); // will invoke BeginShadow and EndShadow + } + + /// + /// Creates a shadow wrapper for a filesystem, should never be used outside UmbracoBuilder or testing + /// + /// + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public IFileSystem CreateShadowWrapper(IFileSystem filesystem, string shadowPath) => + CreateShadowWrapperInternal(filesystem, shadowPath); + + private void EnsureWellKnownFileSystems() => LazyInitializer.EnsureInitialized( + ref _wkfsObject, + ref _wkfsInitialized, + ref _wkfsLock, + CreateWellKnownFileSystems); // need to return something to LazyInitializer.EnsureInitialized // but it does not really matter what we return - here, null @@ -213,37 +253,77 @@ private void EnsureWellKnownFileSystems() => LazyInitializer.EnsureInitialized(r { ILogger logger = _loggerFactory.CreateLogger(); - //TODO this is fucked, why do PhysicalFileSystem has a root url? Mvc views cannot be accessed by url! - var macroPartialFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, + // TODO this is fucked, why do PhysicalFileSystem has a root url? Mvc views cannot be accessed by url! + var macroPartialFileSystem = new PhysicalFileSystem( + _ioHelper, + _hostingEnvironment, + logger, _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MacroPartials), _hostingEnvironment.ToAbsolute(Constants.SystemDirectories.MacroPartials)); - var partialViewsFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, + var partialViewsFileSystem = new PhysicalFileSystem( + _ioHelper, + _hostingEnvironment, + logger, _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.PartialViews), _hostingEnvironment.ToAbsolute(Constants.SystemDirectories.PartialViews)); - var scriptsFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, + var scriptsFileSystem = new PhysicalFileSystem( + _ioHelper, + _hostingEnvironment, + logger, _hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoScriptsPath), _hostingEnvironment.ToAbsolute(_globalSettings.UmbracoScriptsPath)); - var mvcViewsFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, + var mvcViewsFileSystem = new PhysicalFileSystem( + _ioHelper, + _hostingEnvironment, + logger, _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MvcViews), _hostingEnvironment.ToAbsolute(Constants.SystemDirectories.MvcViews)); - _macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, _ioHelper, _hostingEnvironment, - _loggerFactory, "macro-partials", IsScoped); - _partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, _ioHelper, _hostingEnvironment, - _loggerFactory, "partials", IsScoped); - _scriptsFileSystem = new ShadowWrapper(scriptsFileSystem, _ioHelper, _hostingEnvironment, _loggerFactory, - "scripts", IsScoped); - _mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, _ioHelper, _hostingEnvironment, _loggerFactory, - "views", IsScoped); + _macroPartialFileSystem = new ShadowWrapper( + macroPartialFileSystem, + _ioHelper, + _hostingEnvironment, + _loggerFactory, + "macro-partials", + IsScoped); + _partialViewsFileSystem = new ShadowWrapper( + partialViewsFileSystem, + _ioHelper, + _hostingEnvironment, + _loggerFactory, + "partials", + IsScoped); + _scriptsFileSystem = new ShadowWrapper( + scriptsFileSystem, + _ioHelper, + _hostingEnvironment, + _loggerFactory, + "scripts", + IsScoped); + _mvcViewsFileSystem = new ShadowWrapper( + mvcViewsFileSystem, + _ioHelper, + _hostingEnvironment, + _loggerFactory, + "views", + IsScoped); if (_stylesheetsFileSystem == null) { - var stylesheetsFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, + var stylesheetsFileSystem = new PhysicalFileSystem( + _ioHelper, + _hostingEnvironment, + logger, _hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoCssPath), _hostingEnvironment.ToAbsolute(_globalSettings.UmbracoCssPath)); - _stylesheetsFileSystem = new ShadowWrapper(stylesheetsFileSystem, _ioHelper, _hostingEnvironment, - _loggerFactory, "css", IsScoped); + _stylesheetsFileSystem = new ShadowWrapper( + stylesheetsFileSystem, + _ioHelper, + _hostingEnvironment, + _loggerFactory, + "css", + IsScoped); _shadowWrappers.Add(_stylesheetsFileSystem); } @@ -257,31 +337,6 @@ private void EnsureWellKnownFileSystems() => LazyInitializer.EnsureInitialized(r return null; } - #endregion - - #region Shadow - - // note - // shadowing is thread-safe, but entering and exiting shadow mode is not, and there is only one - // global shadow for the entire application, so great care should be taken to ensure that the - // application is *not* doing anything else when using a shadow. - - /// - /// Shadows the filesystem, should never be used outside the Scope class. - /// - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public ICompletable Shadow() - { - if (Volatile.Read(ref _wkfsInitialized) == false) - { - EnsureWellKnownFileSystems(); - } - - var id = ShadowWrapper.CreateShadowId(_hostingEnvironment); - return new ShadowFileSystems(this, id); // will invoke BeginShadow and EndShadow - } - internal void BeginShadow(string id) { lock (_shadowLocker) @@ -345,22 +400,11 @@ internal void EndShadow(string id, bool completed) } } - /// - /// Creates a shadow wrapper for a filesystem, should never be used outside UmbracoBuilder or testing - /// - /// - /// - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IFileSystem CreateShadowWrapper(IFileSystem filesystem, string shadowPath) => - CreateShadowWrapperInternal(filesystem, shadowPath); - private ShadowWrapper CreateShadowWrapperInternal(IFileSystem filesystem, string shadowPath) { lock (_shadowLocker) { - var wrapper = new ShadowWrapper(filesystem, _ioHelper, _hostingEnvironment, _loggerFactory, shadowPath, - () => IsScoped?.Invoke()); + var wrapper = new ShadowWrapper(filesystem, _ioHelper, _hostingEnvironment, _loggerFactory, shadowPath, () => IsScoped?.Invoke()); if (_shadowCurrentId != null) { wrapper.Shadow(_shadowCurrentId); diff --git a/src/Umbraco.Core/IO/IFileSystem.cs b/src/Umbraco.Core/IO/IFileSystem.cs index aa208afb3df6..da9dd0b9bba4 100644 --- a/src/Umbraco.Core/IO/IFileSystem.cs +++ b/src/Umbraco.Core/IO/IFileSystem.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.IO; +namespace Umbraco.Cms.Core.IO; /// /// Provides methods allowing the manipulation of files. @@ -170,8 +170,8 @@ public interface IFileSystem // TODO: implement these // - //void CreateDirectory(string path); + // void CreateDirectory(string path); // //// move or rename, directory or file - //void Move(string source, string target); + // void Move(string source, string target); } diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index f6fc9a58a7e3..cffd2780daf7 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -14,9 +14,9 @@ public IOHelper(IHostingEnvironment hostingEnvironment) => _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); // static compiled regex for faster performance - //private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + // private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - //helper to try and match the old path to a new virtual one + // helper to try and match the old path to a new virtual one public string FindFile(string virtualPath) { var retval = virtualPath; @@ -80,14 +80,13 @@ public string MapPath(string path) return retval; } - /// /// Verifies that the current filepath matches a directory where the user is allowed to edit a file. /// /// The filepath to validate. /// The valid directory. /// A value indicating whether the filepath is valid. - public bool VerifyEditPath(string filePath, string validDir) => VerifyEditPath(filePath, new[] {validDir}); + public bool VerifyEditPath(string filePath, string validDir) => VerifyEditPath(filePath, new[] { validDir }); /// /// Verifies that the current filepath matches one of several directories where the user is allowed to edit a file. @@ -105,7 +104,6 @@ public bool VerifyEditPath(string filePath, IEnumerable validDirs) // TODO: what's below is dirty, there are too many ways to get the root dir, etc. // not going to fix everything today - var mappedRoot = MapPath(_hostingEnvironment.ApplicationVirtualPath); if (!PathStartsWith(filePath, mappedRoot)) { @@ -115,8 +113,7 @@ public bool VerifyEditPath(string filePath, IEnumerable validDirs) // yes we can (see above) //// don't trust what we get, it may contain relative segments - //filePath = Path.GetFullPath(filePath); - + // filePath = Path.GetFullPath(filePath); foreach (var dir in validDirs) { var validDir = dir; @@ -167,7 +164,7 @@ public string GetRelativePath(string path) if (path.IsFullPath()) { var rootDirectory = MapPath("~"); - var relativePath = PathStartsWith(path, rootDirectory) ? path.Substring(rootDirectory.Length) : path; + var relativePath = PathStartsWith(path, rootDirectory) ? path[rootDirectory.Length..] : path; path = relativePath; } @@ -182,7 +179,7 @@ public DirectoryInfo[] GetTempFolders() { var tempFolderPaths = new[] { - _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempFileUploads) + _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempFileUploads), }; foreach (var tempFolderPath in tempFolderPaths) diff --git a/src/Umbraco.Core/IO/IOHelperLinux.cs b/src/Umbraco.Core/IO/IOHelperLinux.cs index 4d050271b47b..7d936895a1f3 100644 --- a/src/Umbraco.Core/IO/IOHelperLinux.cs +++ b/src/Umbraco.Core/IO/IOHelperLinux.cs @@ -1,10 +1,11 @@ -using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Hosting; namespace Umbraco.Cms.Core.IO; public class IOHelperLinux : IOHelper { - public IOHelperLinux(IHostingEnvironment hostingEnvironment) : base(hostingEnvironment) + public IOHelperLinux(IHostingEnvironment hostingEnvironment) + : base(hostingEnvironment) { } @@ -14,10 +15,9 @@ public override bool PathStartsWith(string path, string root, params char[] sepa { // either it is identical to root, // or it is root + separator + anything - if (separators == null || separators.Length == 0) { - separators = new[] {Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar}; + separators = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; } if (!path.StartsWith(root, StringComparison.Ordinal)) diff --git a/src/Umbraco.Core/IO/IOHelperOSX.cs b/src/Umbraco.Core/IO/IOHelperOSX.cs index b443f7e444b4..8b8ed2093959 100644 --- a/src/Umbraco.Core/IO/IOHelperOSX.cs +++ b/src/Umbraco.Core/IO/IOHelperOSX.cs @@ -1,10 +1,11 @@ -using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Hosting; namespace Umbraco.Cms.Core.IO; public class IOHelperOSX : IOHelper { - public IOHelperOSX(IHostingEnvironment hostingEnvironment) : base(hostingEnvironment) + public IOHelperOSX(IHostingEnvironment hostingEnvironment) + : base(hostingEnvironment) { } @@ -14,10 +15,9 @@ public override bool PathStartsWith(string path, string root, params char[] sepa { // either it is identical to root, // or it is root + separator + anything - if (separators == null || separators.Length == 0) { - separators = new[] {Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar}; + separators = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; } if (!path.StartsWith(root, StringComparison.OrdinalIgnoreCase)) diff --git a/src/Umbraco.Core/IO/IOHelperWindows.cs b/src/Umbraco.Core/IO/IOHelperWindows.cs index ab7498d4e42d..9dfec76f3603 100644 --- a/src/Umbraco.Core/IO/IOHelperWindows.cs +++ b/src/Umbraco.Core/IO/IOHelperWindows.cs @@ -1,17 +1,17 @@ -using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Hosting; namespace Umbraco.Cms.Core.IO; public class IOHelperWindows : IOHelper { - public IOHelperWindows(IHostingEnvironment hostingEnvironment) : base(hostingEnvironment) + public IOHelperWindows(IHostingEnvironment hostingEnvironment) + : base(hostingEnvironment) { } public override bool IsPathFullyQualified(string path) { // TODO: This implementation is taken from the .NET Standard 2.1 implementation. We should switch to using Path.IsPathFullyQualified once we are on .NET Standard 2.1 - if (path.Length < 2) { // It isn't fixed, it must be relative. There is no way to specify a fixed @@ -32,6 +32,7 @@ public override bool IsPathFullyQualified(string path) return path.Length >= 3 && path[1] == Path.VolumeSeparatorChar && (path[2] == Path.DirectorySeparatorChar || path[2] == Path.AltDirectorySeparatorChar) + // To match old behavior we'll check the drive character for validity as the path is technically // not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream. && ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')); @@ -41,10 +42,9 @@ public override bool PathStartsWith(string path, string root, params char[] sepa { // either it is identical to root, // or it is root + separator + anything - if (separators == null || separators.Length == 0) { - separators = new[] {Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar}; + separators = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; } if (!path.StartsWith(root, StringComparison.OrdinalIgnoreCase)) diff --git a/src/Umbraco.Core/IO/IViewHelper.cs b/src/Umbraco.Core/IO/IViewHelper.cs index ef28e934faeb..f84a1ba25655 100644 --- a/src/Umbraco.Core/IO/IViewHelper.cs +++ b/src/Umbraco.Core/IO/IViewHelper.cs @@ -5,8 +5,12 @@ namespace Umbraco.Cms.Core.IO; public interface IViewHelper { bool ViewExists(ITemplate t); + string GetFileContents(ITemplate t); + string CreateView(ITemplate t, bool overWrite = false); + string? UpdateViewFile(ITemplate t, string? currentAlias = null); + string ViewPath(string alias); } diff --git a/src/Umbraco.Core/IO/MediaFileManager.cs b/src/Umbraco.Core/IO/MediaFileManager.cs index be457982cc4d..c222c017448a 100644 --- a/src/Umbraco.Core/IO/MediaFileManager.cs +++ b/src/Umbraco.Core/IO/MediaFileManager.cs @@ -57,7 +57,7 @@ public void DeleteMediaFiles(IEnumerable files) files = files.Distinct(); // kinda try to keep things under control - var options = new ParallelOptions {MaxDegreeOfParallelism = 20}; + var options = new ParallelOptions { MaxDegreeOfParallelism = 20 }; Parallel.ForEach(files, options, file => { @@ -122,6 +122,8 @@ public string GetMediaPath(string? filename, Guid cuid, Guid puid) /// The file path if a file was found /// /// + /// + /// /// public Stream GetFile( IContentBase content, @@ -161,8 +163,7 @@ public Stream GetFile( /// file. /// /// - public string StoreFile(IContentBase content, IPropertyType? propertyType, string filename, Stream filestream, - string? oldpath) + public string StoreFile(IContentBase content, IPropertyType? propertyType, string filename, Stream filestream, string? oldpath) { if (content == null) { @@ -181,7 +182,8 @@ public string StoreFile(IContentBase content, IPropertyType? propertyType, strin if (string.IsNullOrWhiteSpace(filename)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(filename)); } @@ -193,7 +195,7 @@ public string StoreFile(IContentBase content, IPropertyType? propertyType, strin // clear the old file, if any if (string.IsNullOrWhiteSpace(oldpath) == false) { - FileSystem.DeleteFile(oldpath!); + FileSystem.DeleteFile(oldpath); } // get the filepath, store the data @@ -228,7 +230,8 @@ public string StoreFile(IContentBase content, IPropertyType? propertyType, strin if (string.IsNullOrWhiteSpace(sourcepath)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(sourcepath)); } diff --git a/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs b/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs index 1cca8eba6a10..b73d29df6036 100644 --- a/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs +++ b/src/Umbraco.Core/IO/MediaPathSchemes/CombinedGuidsMediaPathScheme.cs @@ -13,7 +13,6 @@ public string GetFilePath(MediaFileManager fileManager, Guid itemGuid, Guid prop { // assumes that cuid and puid keys can be trusted - and that a single property type // for a single content cannot store two different files with the same name - Guid combinedGuid = GuidUtils.Combine(itemGuid, propertyGuid); var directory = HexEncoder.Encode( diff --git a/src/Umbraco.Core/Install/FilePermissionTest.cs b/src/Umbraco.Core/Install/FilePermissionTest.cs index d7c4b527ba60..21c6d4f0c7da 100644 --- a/src/Umbraco.Core/Install/FilePermissionTest.cs +++ b/src/Umbraco.Core/Install/FilePermissionTest.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Cms.Core.Install; +namespace Umbraco.Cms.Core.Install; public enum FilePermissionTest { FolderCreation, FileWritingForPackages, FileWriting, - MediaFolderCreation + MediaFolderCreation, } diff --git a/src/Umbraco.Core/Install/InstallException.cs b/src/Umbraco.Core/Install/InstallException.cs index fcb878c67724..69e28db92caa 100644 --- a/src/Umbraco.Core/Install/InstallException.cs +++ b/src/Umbraco.Core/Install/InstallException.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Install; diff --git a/src/Umbraco.Core/Install/InstallStatusTracker.cs b/src/Umbraco.Core/Install/InstallStatusTracker.cs index fa45db6b9c09..5403ded3aeb8 100644 --- a/src/Umbraco.Core/Install/InstallStatusTracker.cs +++ b/src/Umbraco.Core/Install/InstallStatusTracker.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Collections; +using Umbraco.Cms.Core.Collections; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Install.Models; using Umbraco.Cms.Core.Serialization; @@ -21,6 +21,15 @@ public InstallStatusTracker(IHostingEnvironment hostingEnvironment, IJsonSeriali _jsonSerializer = jsonSerializer; } + public static IEnumerable GetStatus() => + new List(_steps).OrderBy(x => x.ServerOrder); + + public void Reset() + { + _steps = new ConcurrentHashSet(); + ClearFiles(); + } + private string GetFile(Guid installId) { var file = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempData.EnsureEndsWith('/') + @@ -31,12 +40,6 @@ private string GetFile(Guid installId) return file; } - public void Reset() - { - _steps = new ConcurrentHashSet(); - ClearFiles(); - } - public void ClearFiles() { var dir = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempData.EnsureEndsWith('/') + @@ -57,11 +60,11 @@ public void ClearFiles() public IEnumerable InitializeFromFile(Guid installId) { - //check if we have our persisted file and read it + // check if we have our persisted file and read it var file = GetFile(installId); if (File.Exists(file)) { - IEnumerable deserialized = + IEnumerable? deserialized = _jsonSerializer.Deserialize>( File.ReadAllText(file)); if (deserialized is not null) @@ -83,14 +86,14 @@ public IEnumerable InitializeFromFile(Guid installId) public IEnumerable Initialize(Guid installId, IEnumerable steps) { - //if there are no steps in memory + // if there are no steps in memory if (_steps.Count == 0) { - //check if we have our persisted file and read it + // check if we have our persisted file and read it var file = GetFile(installId); if (File.Exists(file)) { - IEnumerable deserialized = + IEnumerable? deserialized = _jsonSerializer.Deserialize>( File.ReadAllText(file)); if (deserialized is not null) @@ -105,13 +108,13 @@ public IEnumerable Initialize(Guid installId, IEnumerable x.ServerOrder)) { _steps.Add(new InstallTrackingItem(step.Name, step.ServerOrder)); } - //save the file + // save the file var serialized = _jsonSerializer.Serialize(new List(_steps)); Directory.CreateDirectory(Path.GetDirectoryName(file)!); File.WriteAllText(file, serialized); @@ -119,13 +122,13 @@ public IEnumerable Initialize(Guid installId, IEnumerable(_steps)); Directory.CreateDirectory(Path.GetDirectoryName(file)!); File.WriteAllText(file, serialized); @@ -145,12 +148,9 @@ public void SetComplete(Guid installId, string name, IDictionary trackingItem.IsComplete = true; - //save the file + // save the file var file = GetFile(installId); var serialized = _jsonSerializer.Serialize(new List(_steps)); File.WriteAllText(file, serialized); } - - public static IEnumerable GetStatus() => - new List(_steps).OrderBy(x => x.ServerOrder); } diff --git a/src/Umbraco.Core/Install/InstallSteps/FilePermissionsStep.cs b/src/Umbraco.Core/Install/InstallSteps/FilePermissionsStep.cs index ff42ac472988..40f54bab333f 100644 --- a/src/Umbraco.Core/Install/InstallSteps/FilePermissionsStep.cs +++ b/src/Umbraco.Core/Install/InstallSteps/FilePermissionsStep.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Install.Models; @@ -44,7 +44,7 @@ public FilePermissionsStep( report.ToDictionary(x => _localizedTextService.Localize("permissions", x.Key), x => x.Value); if (permissionsOk == false) { - throw new InstallException("Permission check failed", "permissionsreport", new {errors = translatedErrors}); + throw new InstallException("Permission check failed", "permissionsreport", new { errors = translatedErrors }); } return Task.FromResult(null); diff --git a/src/Umbraco.Core/Install/Models/DatabaseModel.cs b/src/Umbraco.Core/Install/Models/DatabaseModel.cs index 40310e4a984b..eb892d9ceea3 100644 --- a/src/Umbraco.Core/Install/Models/DatabaseModel.cs +++ b/src/Umbraco.Core/Install/Models/DatabaseModel.cs @@ -8,17 +8,23 @@ public class DatabaseModel [DataMember(Name = "databaseProviderMetadataId")] public Guid DatabaseProviderMetadataId { get; set; } - [DataMember(Name = "providerName")] public string? ProviderName { get; set; } + [DataMember(Name = "providerName")] + public string? ProviderName { get; set; } - [DataMember(Name = "server")] public string Server { get; set; } = null!; + [DataMember(Name = "server")] + public string Server { get; set; } = null!; - [DataMember(Name = "databaseName")] public string DatabaseName { get; set; } = null!; + [DataMember(Name = "databaseName")] + public string DatabaseName { get; set; } = null!; - [DataMember(Name = "login")] public string Login { get; set; } = null!; + [DataMember(Name = "login")] + public string Login { get; set; } = null!; - [DataMember(Name = "password")] public string Password { get; set; } = null!; + [DataMember(Name = "password")] + public string Password { get; set; } = null!; - [DataMember(Name = "integratedAuth")] public bool IntegratedAuth { get; set; } + [DataMember(Name = "integratedAuth")] + public bool IntegratedAuth { get; set; } [DataMember(Name = "connectionString")] public string? ConnectionString { get; set; } diff --git a/src/Umbraco.Core/Install/Models/InstallInstructions.cs b/src/Umbraco.Core/Install/Models/InstallInstructions.cs index 2c99a1ef917a..c86307d9b0d7 100644 --- a/src/Umbraco.Core/Install/Models/InstallInstructions.cs +++ b/src/Umbraco.Core/Install/Models/InstallInstructions.cs @@ -1,11 +1,13 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Install.Models; [DataContract(Name = "installInstructions", Namespace = "")] public class InstallInstructions { - [DataMember(Name = "instructions")] public IDictionary? Instructions { get; set; } + [DataMember(Name = "instructions")] + public IDictionary? Instructions { get; set; } - [DataMember(Name = "installId")] public Guid InstallId { get; set; } + [DataMember(Name = "installId")] + public Guid InstallId { get; set; } } diff --git a/src/Umbraco.Core/Install/Models/InstallProgressResultModel.cs b/src/Umbraco.Core/Install/Models/InstallProgressResultModel.cs index 178797ec5c29..650c7469986e 100644 --- a/src/Umbraco.Core/Install/Models/InstallProgressResultModel.cs +++ b/src/Umbraco.Core/Install/Models/InstallProgressResultModel.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Install.Models; @@ -8,8 +8,7 @@ namespace Umbraco.Cms.Core.Install.Models; [DataContract(Name = "result", Namespace = "")] public class InstallProgressResultModel { - public InstallProgressResultModel(bool processComplete, string stepCompleted, string nextStep, string? view = null, - object? viewModel = null) + public InstallProgressResultModel(bool processComplete, string stepCompleted, string nextStep, string? view = null, object? viewModel = null) { ProcessComplete = processComplete; StepCompleted = stepCompleted; @@ -25,11 +24,14 @@ public InstallProgressResultModel(bool processComplete, string stepCompleted, st [DataMember(Name = "view")] public string? View { get; private set; } - [DataMember(Name = "complete")] public bool ProcessComplete { get; set; } + [DataMember(Name = "complete")] + public bool ProcessComplete { get; set; } - [DataMember(Name = "stepCompleted")] public string StepCompleted { get; set; } + [DataMember(Name = "stepCompleted")] + public string StepCompleted { get; set; } - [DataMember(Name = "nextStep")] public string NextStep { get; set; } + [DataMember(Name = "nextStep")] + public string NextStep { get; set; } /// /// The view model to return to the UI if this step is returning a view (optional) diff --git a/src/Umbraco.Core/Install/Models/InstallSetup.cs b/src/Umbraco.Core/Install/Models/InstallSetup.cs index 824b993ca04c..2a1e3ce9f7fa 100644 --- a/src/Umbraco.Core/Install/Models/InstallSetup.cs +++ b/src/Umbraco.Core/Install/Models/InstallSetup.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Install.Models; @@ -14,7 +14,9 @@ public InstallSetup() InstallId = Guid.NewGuid(); } - [DataMember(Name = "installId")] public Guid InstallId { get; private set; } + [DataMember(Name = "installId")] + public Guid InstallId { get; private set; } - [DataMember(Name = "steps")] public IEnumerable Steps { get; set; } + [DataMember(Name = "steps")] + public IEnumerable Steps { get; set; } } diff --git a/src/Umbraco.Core/Install/Models/InstallSetupResult.cs b/src/Umbraco.Core/Install/Models/InstallSetupResult.cs index 6072c64714b9..3849a09d7501 100644 --- a/src/Umbraco.Core/Install/Models/InstallSetupResult.cs +++ b/src/Umbraco.Core/Install/Models/InstallSetupResult.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Install.Models; +namespace Umbraco.Cms.Core.Install.Models; /// /// The object returned from each installation step diff --git a/src/Umbraco.Core/Install/Models/InstallSetupStep.cs b/src/Umbraco.Core/Install/Models/InstallSetupStep.cs index 3b855abc7f40..a9d24447c680 100644 --- a/src/Umbraco.Core/Install/Models/InstallSetupStep.cs +++ b/src/Umbraco.Core/Install/Models/InstallSetupStep.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Install.Models; @@ -34,7 +34,7 @@ public abstract class InstallSetupStep { protected InstallSetupStep() { - InstallSetupStepAttribute att = GetType().GetCustomAttribute(false); + InstallSetupStepAttribute? att = GetType().GetCustomAttribute(false); if (att == null) { throw new InvalidOperationException("Each step must be attributed"); @@ -48,9 +48,11 @@ protected InstallSetupStep() PerformsAppRestart = att.PerformsAppRestart; } - [DataMember(Name = "name")] public string Name { get; private set; } + [DataMember(Name = "name")] + public string Name { get; private set; } - [DataMember(Name = "view")] public virtual string View { get; private set; } + [DataMember(Name = "view")] + public virtual string View { get; private set; } /// /// The view model used to render the view, by default is null but can be populated @@ -58,11 +60,14 @@ protected InstallSetupStep() [DataMember(Name = "model")] public virtual object? ViewModel { get; private set; } - [DataMember(Name = "description")] public string Description { get; private set; } + [DataMember(Name = "description")] + public string Description { get; private set; } - [IgnoreDataMember] public InstallationType InstallTypeTarget { get; } + [IgnoreDataMember] + public InstallationType InstallTypeTarget { get; } - [IgnoreDataMember] public bool PerformsAppRestart { get; } + [IgnoreDataMember] + public bool PerformsAppRestart { get; } /// /// Defines what order this step needs to execute on the server side since the diff --git a/src/Umbraco.Core/Install/Models/InstallSetupStepAttribute.cs b/src/Umbraco.Core/Install/Models/InstallSetupStepAttribute.cs index ea6ef727cd0f..c6d0657d3366 100644 --- a/src/Umbraco.Core/Install/Models/InstallSetupStepAttribute.cs +++ b/src/Umbraco.Core/Install/Models/InstallSetupStepAttribute.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.Install.Models; +namespace Umbraco.Cms.Core.Install.Models; public sealed class InstallSetupStepAttribute : Attribute { - public InstallSetupStepAttribute(InstallationType installTypeTarget, string name, string view, int serverOrder, - string description) + public InstallSetupStepAttribute(InstallationType installTypeTarget, string name, string view, int serverOrder, string description) { InstallTypeTarget = installTypeTarget; Name = name; @@ -11,12 +10,11 @@ public InstallSetupStepAttribute(InstallationType installTypeTarget, string name ServerOrder = serverOrder; Description = description; - //default + // default PerformsAppRestart = false; } - public InstallSetupStepAttribute(InstallationType installTypeTarget, string name, int serverOrder, - string description) + public InstallSetupStepAttribute(InstallationType installTypeTarget, string name, int serverOrder, string description) { InstallTypeTarget = installTypeTarget; Name = name; @@ -24,14 +22,18 @@ public InstallSetupStepAttribute(InstallationType installTypeTarget, string name ServerOrder = serverOrder; Description = description; - //default + // default PerformsAppRestart = false; } public InstallationType InstallTypeTarget { get; } + public string Name { get; } + public string View { get; } + public int ServerOrder { get; } + public string Description { get; } /// diff --git a/src/Umbraco.Core/Install/Models/InstallTrackingItem.cs b/src/Umbraco.Core/Install/Models/InstallTrackingItem.cs index b12f1aabd5ac..74170857b5e0 100644 --- a/src/Umbraco.Core/Install/Models/InstallTrackingItem.cs +++ b/src/Umbraco.Core/Install/Models/InstallTrackingItem.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Install.Models; +namespace Umbraco.Cms.Core.Install.Models; public class InstallTrackingItem { @@ -10,11 +10,12 @@ public InstallTrackingItem(string name, int serverOrder) } public string Name { get; set; } + public int ServerOrder { get; set; } + public bool IsComplete { get; set; } - public IDictionary AdditionalData { get; set; } - protected bool Equals(InstallTrackingItem other) => string.Equals(Name, other.Name); + public IDictionary AdditionalData { get; set; } public override bool Equals(object? obj) { @@ -36,5 +37,7 @@ public override bool Equals(object? obj) return Equals((InstallTrackingItem)obj); } + protected bool Equals(InstallTrackingItem other) => string.Equals(Name, other.Name); + public override int GetHashCode() => Name.GetHashCode(); } diff --git a/src/Umbraco.Core/Install/Models/InstallationType.cs b/src/Umbraco.Core/Install/Models/InstallationType.cs index 8fb11d761159..b2b6a428fa46 100644 --- a/src/Umbraco.Core/Install/Models/InstallationType.cs +++ b/src/Umbraco.Core/Install/Models/InstallationType.cs @@ -1,8 +1,8 @@ -namespace Umbraco.Cms.Core.Install.Models; +namespace Umbraco.Cms.Core.Install.Models; [Flags] public enum InstallationType { NewInstall = 1 << 0, // 1 - Upgrade = 1 << 1 // 2 + Upgrade = 1 << 1, // 2 } diff --git a/src/Umbraco.Core/InstallLog.cs b/src/Umbraco.Core/InstallLog.cs index 2cf2a1a59f04..d0bec2097fd5 100644 --- a/src/Umbraco.Core/InstallLog.cs +++ b/src/Umbraco.Core/InstallLog.cs @@ -1,9 +1,19 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; public class InstallLog { - public InstallLog(Guid installId, bool isUpgrade, bool installCompleted, DateTime timestamp, int versionMajor, - int versionMinor, int versionPatch, string versionComment, string error, string? userAgent, string dbProvider) + public InstallLog( + Guid installId, + bool isUpgrade, + bool installCompleted, + DateTime timestamp, + int versionMajor, + int versionMinor, + int versionPatch, + string versionComment, + string error, + string? userAgent, + string dbProvider) { InstallId = installId; IsUpgrade = isUpgrade; @@ -19,14 +29,24 @@ public InstallLog(Guid installId, bool isUpgrade, bool installCompleted, DateTim } public Guid InstallId { get; } + public bool IsUpgrade { get; set; } + public bool InstallCompleted { get; set; } + public DateTime Timestamp { get; set; } + public int VersionMajor { get; } + public int VersionMinor { get; } + public int VersionPatch { get; } + public string VersionComment { get; } + public string Error { get; } + public string? UserAgent { get; } + public string DbProvider { get; set; } } diff --git a/src/Umbraco.Core/LambdaExpressionCacheKey.cs b/src/Umbraco.Core/LambdaExpressionCacheKey.cs index 397732350fcf..31ebcf688f5c 100644 --- a/src/Umbraco.Core/LambdaExpressionCacheKey.cs +++ b/src/Umbraco.Core/LambdaExpressionCacheKey.cs @@ -1,4 +1,4 @@ -using System.Linq.Expressions; +using System.Linq.Expressions; namespace Umbraco.Cms.Core; @@ -8,6 +8,11 @@ namespace Umbraco.Cms.Core; /// public struct LambdaExpressionCacheKey { + /// + /// The argument type names of the + /// + public readonly HashSet ArgTypes; + public LambdaExpressionCacheKey(string returnType, string expression, params string[] argTypes) { ReturnType = returnType; @@ -24,11 +29,6 @@ public LambdaExpressionCacheKey(LambdaExpression obj) _toString = null; } - /// - /// The argument type names of the - /// - public readonly HashSet ArgTypes; - /// /// The return type of the /// @@ -42,21 +42,19 @@ public LambdaExpressionCacheKey(LambdaExpression obj) private string? _toString; /// - /// Returns a that represents this instance. + /// Returns a that represents this instance. /// /// - /// A that represents this instance. + /// A that represents this instance. /// - public override string ToString() => _toString ?? - (_toString = string.Concat(string.Join("|", ArgTypes), ",", ReturnType, ",", - ExpressionAsString)); + public override string ToString() => _toString ??= string.Concat(string.Join("|", ArgTypes), ",", ReturnType, ",", ExpressionAsString); /// - /// Determines whether the specified is equal to this instance. + /// Determines whether the specified is equal to this instance. /// - /// The to compare with this instance. + /// The to compare with this instance. /// - /// true if the specified is equal to this instance; otherwise, false. + /// true if the specified is equal to this instance; otherwise, false. /// public override bool Equals(object? obj) { diff --git a/src/Umbraco.Core/Logging/DisposableTimer.cs b/src/Umbraco.Core/Logging/DisposableTimer.cs index 6c690b86f237..e97fbd34fbb8 100644 --- a/src/Umbraco.Core/Logging/DisposableTimer.cs +++ b/src/Umbraco.Core/Logging/DisposableTimer.cs @@ -44,7 +44,7 @@ internal DisposableTimer( _endMessageArgs = endMessageArgs; _failMessageArgs = failMessageArgs; _thresholdMilliseconds = thresholdMilliseconds < 0 ? 0 : thresholdMilliseconds; - _timingId = Guid.NewGuid().ToString("N").Substring(0, 7); // keep it short-ish + _timingId = Guid.NewGuid().ToString("N")[..7]; // keep it short-ish if (thresholdMilliseconds == 0) { @@ -85,7 +85,6 @@ internal DisposableTimer( // else aren't logging the start message, this is output to the profiler but not the log, // we just want the log to contain the result if it's more than the minimum ms threshold. - _profilerStep = profiler?.Step(loggerType, startMessage); } @@ -122,8 +121,7 @@ protected override void DisposeResources() { if (_failMessageArgs is null) { - _logger.LogError(_failException, "{FailMessage} ({Duration}ms) [Timing {TimingId}]", _failMessage, - Stopwatch.ElapsedMilliseconds, _timingId); + _logger.LogError(_failException, "{FailMessage} ({Duration}ms) [Timing {TimingId}]", _failMessage, Stopwatch.ElapsedMilliseconds, _timingId); } else { @@ -141,14 +139,17 @@ protected override void DisposeResources() case LogLevel.Debug: if (_endMessageArgs == null) { - _logger.LogDebug("{EndMessage} ({Duration}ms) [Timing {TimingId}]", _endMessage, - Stopwatch.ElapsedMilliseconds, _timingId); + _logger.LogDebug( + "{EndMessage} ({Duration}ms) [Timing {TimingId}]", + _endMessage, + Stopwatch.ElapsedMilliseconds, + _timingId); } else { var args = new object[_endMessageArgs.Length + 2]; _endMessageArgs.CopyTo(args, 0); - args[args.Length - 1] = Stopwatch.ElapsedMilliseconds; + args[^1] = Stopwatch.ElapsedMilliseconds; args[args.Length] = _timingId; _logger.LogDebug(_endMessage + " ({Duration}ms) [Timing {TimingId}]", args); } @@ -157,8 +158,11 @@ protected override void DisposeResources() case LogLevel.Information: if (_endMessageArgs == null) { - _logger.LogInformation("{EndMessage} ({Duration}ms) [Timing {TimingId}]", _endMessage, - Stopwatch.ElapsedMilliseconds, _timingId); + _logger.LogInformation( + "{EndMessage} ({Duration}ms) [Timing {TimingId}]", + _endMessage, + Stopwatch.ElapsedMilliseconds, + _timingId); } else { @@ -170,9 +174,10 @@ protected override void DisposeResources() } break; - // filtered in the ctor - //default: - // throw new Exception(); + + // filtered in the ctor + // default: + // throw new Exception(); } } } diff --git a/src/Umbraco.Core/Logging/IProfiler.cs b/src/Umbraco.Core/Logging/IProfiler.cs index 42a17014245a..ab580d6aaead 100644 --- a/src/Umbraco.Core/Logging/IProfiler.cs +++ b/src/Umbraco.Core/Logging/IProfiler.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Logging; +namespace Umbraco.Cms.Core.Logging; /// /// Defines the profiling service. diff --git a/src/Umbraco.Core/Logging/IProfilerHtml.cs b/src/Umbraco.Core/Logging/IProfilerHtml.cs index 0f7496187354..806ee54e7a6d 100644 --- a/src/Umbraco.Core/Logging/IProfilerHtml.cs +++ b/src/Umbraco.Core/Logging/IProfilerHtml.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Logging; +namespace Umbraco.Cms.Core.Logging; /// /// Used to render a profiler in a web page diff --git a/src/Umbraco.Core/Logging/LogHttpRequestExtension.cs b/src/Umbraco.Core/Logging/LogHttpRequestExtension.cs index 302145946265..2981dd598708 100644 --- a/src/Umbraco.Core/Logging/LogHttpRequestExtension.cs +++ b/src/Umbraco.Core/Logging/LogHttpRequestExtension.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Cache; namespace Umbraco.Extensions; diff --git a/src/Umbraco.Core/Logging/LogLevel.cs b/src/Umbraco.Core/Logging/LogLevel.cs index 45d4713b4638..b7271ecf043a 100644 --- a/src/Umbraco.Core/Logging/LogLevel.cs +++ b/src/Umbraco.Core/Logging/LogLevel.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Logging; +namespace Umbraco.Cms.Core.Logging; /// /// Specifies the level of a log event. @@ -10,5 +10,5 @@ public enum LogLevel Information, Warning, Error, - Fatal + Fatal, } diff --git a/src/Umbraco.Core/Logging/LogProfiler.cs b/src/Umbraco.Core/Logging/LogProfiler.cs index 83b3248f0a40..0504a2a1ae00 100644 --- a/src/Umbraco.Core/Logging/LogProfiler.cs +++ b/src/Umbraco.Core/Logging/LogProfiler.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using Microsoft.Extensions.Logging; namespace Umbraco.Cms.Core.Logging; @@ -40,12 +40,7 @@ private class LightDisposableTimer : DisposableObjectSlim protected internal LightDisposableTimer(Action callback) { - if (callback == null) - { - throw new ArgumentNullException(nameof(callback)); - } - - _callback = callback; + _callback = callback ?? throw new ArgumentNullException(nameof(callback)); } protected override void DisposeResources() diff --git a/src/Umbraco.Core/Logging/LoggingConfiguration.cs b/src/Umbraco.Core/Logging/LoggingConfiguration.cs index 7def44988e7f..d2a24d24a95a 100644 --- a/src/Umbraco.Core/Logging/LoggingConfiguration.cs +++ b/src/Umbraco.Core/Logging/LoggingConfiguration.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Logging; +namespace Umbraco.Cms.Core.Logging; public class LoggingConfiguration : ILoggingConfiguration { diff --git a/src/Umbraco.Core/Logging/LoggingTaskExtension.cs b/src/Umbraco.Core/Logging/LoggingTaskExtension.cs index 855d9f8f6026..950e9bb8f46d 100644 --- a/src/Umbraco.Core/Logging/LoggingTaskExtension.cs +++ b/src/Umbraco.Core/Logging/LoggingTaskExtension.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Logging; +namespace Umbraco.Cms.Core.Logging; internal static class LoggingTaskExtension { @@ -15,6 +15,7 @@ public static Task LogErrors(this Task task, Action logMethod t => LogErrorsInner(t, logMethod), CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, + // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html TaskScheduler.Default); @@ -30,6 +31,7 @@ public static Task LogErrors(this Task task, Action logMethod public static Task LogErrorsWaitable(this Task task, Action logMethod) => task.ContinueWith( t => LogErrorsInner(t, logMethod), + // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html TaskScheduler.Default); @@ -37,7 +39,8 @@ private static void LogErrorsInner(Task task, Action logActio { if (task.Exception != null) { - logAction("Aggregate Exception with " + task.Exception.InnerExceptions.Count + " inner exceptions: ", + logAction( + "Aggregate Exception with " + task.Exception.InnerExceptions.Count + " inner exceptions: ", task.Exception); foreach (Exception innerException in task.Exception.InnerExceptions) { diff --git a/src/Umbraco.Core/Macros/IMacroRenderer.cs b/src/Umbraco.Core/Macros/IMacroRenderer.cs index 9a982b7eefe3..f1e7d8c38320 100644 --- a/src/Umbraco.Core/Macros/IMacroRenderer.cs +++ b/src/Umbraco.Core/Macros/IMacroRenderer.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Cms.Core.Macros; @@ -7,6 +7,5 @@ namespace Umbraco.Cms.Core.Macros; /// public interface IMacroRenderer { - Task RenderAsync(string macroAlias, IPublishedContent? content, - IDictionary? macroParams); + Task RenderAsync(string macroAlias, IPublishedContent? content, IDictionary? macroParams); } diff --git a/src/Umbraco.Core/Macros/MacroContent.cs b/src/Umbraco.Core/Macros/MacroContent.cs index e230277370ec..c36c63016804 100644 --- a/src/Umbraco.Core/Macros/MacroContent.cs +++ b/src/Umbraco.Core/Macros/MacroContent.cs @@ -1,8 +1,11 @@ -namespace Umbraco.Cms.Core.Macros; +namespace Umbraco.Cms.Core.Macros; // represents the content of a macro public class MacroContent { + // gets an empty macro content + public static MacroContent Empty { get; } = new(); + // gets or sets the text content public string? Text { get; set; } @@ -11,7 +14,4 @@ public class MacroContent // a value indicating whether the content is empty public bool IsEmpty => Text is null; - - // gets an empty macro content - public static MacroContent Empty { get; } = new(); } diff --git a/src/Umbraco.Core/Macros/MacroErrorBehaviour.cs b/src/Umbraco.Core/Macros/MacroErrorBehaviour.cs index 4d0d4535a792..49a53f11b05c 100644 --- a/src/Umbraco.Core/Macros/MacroErrorBehaviour.cs +++ b/src/Umbraco.Core/Macros/MacroErrorBehaviour.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Macros; +namespace Umbraco.Cms.Core.Macros; public enum MacroErrorBehaviour { @@ -24,5 +24,5 @@ public enum MacroErrorBehaviour /// Silently eat the error and display the custom content reported in /// the error event args /// - Content + Content, } diff --git a/src/Umbraco.Core/Macros/MacroModel.cs b/src/Umbraco.Core/Macros/MacroModel.cs index 224fb32e2f97..12649bf91c74 100644 --- a/src/Umbraco.Core/Macros/MacroModel.cs +++ b/src/Umbraco.Core/Macros/MacroModel.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Macros; @@ -8,7 +8,7 @@ public MacroModel() { } - public MacroModel(IMacro macro) + public MacroModel(IMacro? macro) { if (macro == null) { diff --git a/src/Umbraco.Core/Macros/MacroPropertyModel.cs b/src/Umbraco.Core/Macros/MacroPropertyModel.cs index f5bfb1e535f9..c1022c35613e 100644 --- a/src/Umbraco.Core/Macros/MacroPropertyModel.cs +++ b/src/Umbraco.Core/Macros/MacroPropertyModel.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Macros; +namespace Umbraco.Cms.Core.Macros; public class MacroPropertyModel { diff --git a/src/Umbraco.Core/Mail/ISmsSender.cs b/src/Umbraco.Core/Mail/ISmsSender.cs index 985cb2c79b94..3c09bdc7e632 100644 --- a/src/Umbraco.Core/Mail/ISmsSender.cs +++ b/src/Umbraco.Core/Mail/ISmsSender.cs @@ -6,6 +6,5 @@ namespace Umbraco.Cms.Core.Mail; public interface ISmsSender { // borrowed from https://github.com/dotnet/AspNetCore.Docs/blob/master/aspnetcore/common/samples/WebApplication1/Services/ISmsSender.cs#L8 - Task SendSmsAsync(string number, string message); } diff --git a/src/Umbraco.Core/Manifest/BundleOptions.cs b/src/Umbraco.Core/Manifest/BundleOptions.cs index 6866fd23b2cc..fe04c205d9ff 100644 --- a/src/Umbraco.Core/Manifest/BundleOptions.cs +++ b/src/Umbraco.Core/Manifest/BundleOptions.cs @@ -21,5 +21,5 @@ public enum BundleOptions /// /// The packages assets will be processed as it's own separate bundle. (in debug, files will not be processed) /// - Independent = 2 + Independent = 2, } diff --git a/src/Umbraco.Core/Manifest/IManifestFilter.cs b/src/Umbraco.Core/Manifest/IManifestFilter.cs index 075ca1ce284d..d2998a083920 100644 --- a/src/Umbraco.Core/Manifest/IManifestFilter.cs +++ b/src/Umbraco.Core/Manifest/IManifestFilter.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Manifest; +namespace Umbraco.Cms.Core.Manifest; /// /// Provides filtering for package manifests. diff --git a/src/Umbraco.Core/Manifest/ManifestAssets.cs b/src/Umbraco.Core/Manifest/ManifestAssets.cs index 1dbed2e8105c..2bd84a1bddf4 100644 --- a/src/Umbraco.Core/Manifest/ManifestAssets.cs +++ b/src/Umbraco.Core/Manifest/ManifestAssets.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Manifest; +namespace Umbraco.Cms.Core.Manifest; public class ManifestAssets { @@ -9,5 +9,6 @@ public ManifestAssets(string? packageName, IReadOnlyList assets) } public string PackageName { get; } + public IReadOnlyList Assets { get; } } diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs index 1eec4577f14c..5bfc2a740ef3 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs @@ -1,6 +1,7 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Manifest; + // contentApps: [ // { // name: 'App Name', // required @@ -25,7 +26,7 @@ namespace Umbraco.Cms.Core.Manifest; [DataContract(Name = "appdef", Namespace = "")] public class ManifestContentAppDefinition { - private string? _view; + private readonly string? _view; /// /// Gets or sets the name of the content app. diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs b/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs index e37ed66234ac..122ecc1cb7d5 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppFactory.cs @@ -6,6 +6,7 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Core.Manifest; + // contentApps: [ // { // name: 'App Name', // required @@ -68,14 +69,14 @@ public ManifestContentAppFactory(ManifestContentAppDefinition definition, IIOHel break; case IDictionaryItem _: partA = "dictionary"; - partB = "*"; //Not really a different type for dictionary items + partB = "*"; // Not really a different type for dictionary items break; default: return null; } - ShowRule[] rules = _showRules ?? (_showRules = ShowRule.Parse(_definition.Show).ToArray()); + ShowRule[] rules = _showRules ??= ShowRule.Parse(_definition.Show).ToArray(); var userGroupsList = userGroups.ToList(); var okRole = false; @@ -116,7 +117,9 @@ public ManifestContentAppFactory(ManifestContentAppDefinition definition, IIOHel break; } } - else // it is a type rule + + // it is a type rule + else { // if type has been ok-ed already, skip the rule if (okType) @@ -161,22 +164,21 @@ public ManifestContentAppFactory(ManifestContentAppDefinition definition, IIOHel Name = _definition.Name, Icon = _definition.Icon, View = _ioHelper.ResolveRelativeOrVirtualUrl(_definition.View), - Weight = _definition.Weight + Weight = _definition.Weight, }; } private class ShowRule { - private static readonly Regex ShowRegex = new("^([+-])?([a-z]+)/([a-z0-9_]+|\\*)$", + private static readonly Regex ShowRegex = new( + "^([+-])?([a-z]+)/([a-z0-9_]+|\\*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); public bool Show { get; private set; } + public string? PartA { get; private set; } - public string? PartB { get; private set; } - public bool Matches(string? partA, string? partB) => - (PartA == "*" || (PartA?.InvariantEquals(partA) ?? false)) && - (PartB == "*" || (PartB?.InvariantEquals(partB) ?? false)); + public string? PartB { get; private set; } public static IEnumerable Parse(string[] rules) { @@ -192,9 +194,13 @@ public static IEnumerable Parse(string[] rules) { Show = match.Groups[1].Value != "-", PartA = match.Groups[2].Value, - PartB = match.Groups[3].Value + PartB = match.Groups[3].Value, }; } } + + public bool Matches(string? partA, string? partB) => + (PartA == "*" || (PartA?.InvariantEquals(partA) ?? false)) && + (PartB == "*" || (PartB?.InvariantEquals(partB) ?? false)); } } diff --git a/src/Umbraco.Core/Manifest/ManifestDashboard.cs b/src/Umbraco.Core/Manifest/ManifestDashboard.cs index 05e3eb8e9474..75cdf24ebe21 100644 --- a/src/Umbraco.Core/Manifest/ManifestDashboard.cs +++ b/src/Umbraco.Core/Manifest/ManifestDashboard.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Dashboards; namespace Umbraco.Cms.Core.Manifest; @@ -6,7 +6,8 @@ namespace Umbraco.Cms.Core.Manifest; [DataContract] public class ManifestDashboard : IDashboard { - [DataMember(Name = "weight")] public int Weight { get; set; } = 100; + [DataMember(Name = "weight")] + public int Weight { get; set; } = 100; [DataMember(Name = "alias", IsRequired = true)] public string Alias { get; set; } = null!; @@ -14,7 +15,9 @@ public class ManifestDashboard : IDashboard [DataMember(Name = "view", IsRequired = true)] public string View { get; set; } = null!; - [DataMember(Name = "sections")] public string[] Sections { get; set; } = Array.Empty(); + [DataMember(Name = "sections")] + public string[] Sections { get; set; } = Array.Empty(); - [DataMember(Name = "access")] public IAccessRule[] AccessRules { get; set; } = Array.Empty(); + [DataMember(Name = "access")] + public IAccessRule[] AccessRules { get; set; } = Array.Empty(); } diff --git a/src/Umbraco.Core/Manifest/ManifestFilterCollection.cs b/src/Umbraco.Core/Manifest/ManifestFilterCollection.cs index 2976077f90ed..a1d5cac0c1b0 100644 --- a/src/Umbraco.Core/Manifest/ManifestFilterCollection.cs +++ b/src/Umbraco.Core/Manifest/ManifestFilterCollection.cs @@ -7,7 +7,8 @@ namespace Umbraco.Cms.Core.Manifest; /// public class ManifestFilterCollection : BuilderCollectionBase { - public ManifestFilterCollection(Func> items) : base(items) + public ManifestFilterCollection(Func> items) + : base(items) { } diff --git a/src/Umbraco.Core/Manifest/ManifestFilterCollectionBuilder.cs b/src/Umbraco.Core/Manifest/ManifestFilterCollectionBuilder.cs index 9527fc31c299..5f012d10c9db 100644 --- a/src/Umbraco.Core/Manifest/ManifestFilterCollectionBuilder.cs +++ b/src/Umbraco.Core/Manifest/ManifestFilterCollectionBuilder.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Manifest; diff --git a/src/Umbraco.Core/Manifest/ManifestSection.cs b/src/Umbraco.Core/Manifest/ManifestSection.cs index b391ec89cadb..c7671c91e25e 100644 --- a/src/Umbraco.Core/Manifest/ManifestSection.cs +++ b/src/Umbraco.Core/Manifest/ManifestSection.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Sections; namespace Umbraco.Cms.Core.Manifest; @@ -6,7 +6,9 @@ namespace Umbraco.Cms.Core.Manifest; [DataContract(Name = "section", Namespace = "")] public class ManifestSection : ISection { - [DataMember(Name = "alias")] public string Alias { get; set; } = string.Empty; + [DataMember(Name = "alias")] + public string Alias { get; set; } = string.Empty; - [DataMember(Name = "name")] public string Name { get; set; } = string.Empty; + [DataMember(Name = "name")] + public string Name { get; set; } = string.Empty; } diff --git a/src/Umbraco.Core/Mapping/IMapDefinition.cs b/src/Umbraco.Core/Mapping/IMapDefinition.cs index 4e9c3849c17f..db836fa3b821 100644 --- a/src/Umbraco.Core/Mapping/IMapDefinition.cs +++ b/src/Umbraco.Core/Mapping/IMapDefinition.cs @@ -1,7 +1,7 @@ -namespace Umbraco.Cms.Core.Mapping; +namespace Umbraco.Cms.Core.Mapping; /// -/// Defines maps for . +/// Defines maps for . /// public interface IMapDefinition { diff --git a/src/Umbraco.Core/Mapping/IUmbracoMapper.cs b/src/Umbraco.Core/Mapping/IUmbracoMapper.cs index b1010a2d1c18..5cbee7164c3d 100644 --- a/src/Umbraco.Core/Mapping/IUmbracoMapper.cs +++ b/src/Umbraco.Core/Mapping/IUmbracoMapper.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Mapping; +namespace Umbraco.Cms.Core.Mapping; public interface IUmbracoMapper { @@ -32,7 +32,8 @@ public interface IUmbracoMapper /// The target type. /// A constructor method. /// A mapping method. - void Define(Func ctor, + void Define( + Func ctor, Action map); /// @@ -139,7 +140,8 @@ void Define(Func ctor, /// The source objects. /// A mapper context preparation method. /// A list containing the target objects. - List MapEnumerable(IEnumerable source, + List MapEnumerable( + IEnumerable source, Action f); /// @@ -150,6 +152,7 @@ List MapEnumerable(IEnumerableThe source objects. /// A mapper context. /// A list containing the target objects. - List MapEnumerable(IEnumerable source, + List MapEnumerable( + IEnumerable source, MapperContext context); } diff --git a/src/Umbraco.Core/Mapping/MapDefinitionCollection.cs b/src/Umbraco.Core/Mapping/MapDefinitionCollection.cs index 5f6e7baf115b..db35a3ffac0c 100644 --- a/src/Umbraco.Core/Mapping/MapDefinitionCollection.cs +++ b/src/Umbraco.Core/Mapping/MapDefinitionCollection.cs @@ -4,7 +4,8 @@ namespace Umbraco.Cms.Core.Mapping; public class MapDefinitionCollection : BuilderCollectionBase { - public MapDefinitionCollection(Func> items) : base(items) + public MapDefinitionCollection(Func> items) + : base(items) { } } diff --git a/src/Umbraco.Core/Mapping/MapDefinitionCollectionBuilder.cs b/src/Umbraco.Core/Mapping/MapDefinitionCollectionBuilder.cs index 906ee0c9368a..84df4cd45bef 100644 --- a/src/Umbraco.Core/Mapping/MapDefinitionCollectionBuilder.cs +++ b/src/Umbraco.Core/Mapping/MapDefinitionCollectionBuilder.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Mapping; diff --git a/src/Umbraco.Core/Mapping/MapperContext.cs b/src/Umbraco.Core/Mapping/MapperContext.cs index b0f177af86ad..ef8663beeb82 100644 --- a/src/Umbraco.Core/Mapping/MapperContext.cs +++ b/src/Umbraco.Core/Mapping/MapperContext.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Mapping; +namespace Umbraco.Cms.Core.Mapping; /// /// Represents a mapper context. @@ -21,7 +21,7 @@ public class MapperContext /// /// Gets the context items. /// - public IDictionary Items => _items ?? (_items = new Dictionary()); + public IDictionary Items => _items ??= new Dictionary(); #region Map diff --git a/src/Umbraco.Core/Media/EmbedProviders/DailyMotion.cs b/src/Umbraco.Core/Media/EmbedProviders/DailyMotion.cs index 79a1faed5bc4..e3cba152c5ee 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/DailyMotion.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/DailyMotion.cs @@ -6,24 +6,25 @@ namespace Umbraco.Cms.Core.Media.EmbedProviders; // TODO (V10): change base class to OEmbedProviderBase public class DailyMotion : EmbedProviderBase { - public DailyMotion(IJsonSerializer jsonSerializer) : base(jsonSerializer) + public DailyMotion(IJsonSerializer jsonSerializer) + : base(jsonSerializer) { } public override string ApiEndpoint => "https://www.dailymotion.com/services/oembed"; - public override string[] UrlSchemeRegex => new[] {@"dailymotion.com/video/.*"}; + public override string[] UrlSchemeRegex => new[] { @"dailymotion.com/video/.*" }; public override Dictionary RequestParams => new() { - //ApiUrl/?format=xml - {"format", "xml"} + // ApiUrl/?format=xml + { "format", "xml" }, }; public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); + var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = this.GetXmlResponse(requestUrl); return GetXmlProperty(xmlDocument, "/oembed/html"); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/EmbedProvidersCollection.cs b/src/Umbraco.Core/Media/EmbedProviders/EmbedProvidersCollection.cs index 5d517a5e091e..655d68b87881 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/EmbedProvidersCollection.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/EmbedProvidersCollection.cs @@ -4,7 +4,8 @@ namespace Umbraco.Cms.Core.Media.EmbedProviders; public class EmbedProvidersCollection : BuilderCollectionBase { - public EmbedProvidersCollection(Func> items) : base(items) + public EmbedProvidersCollection(Func> items) + : base(items) { } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/EmbedProvidersCollectionBuilder.cs b/src/Umbraco.Core/Media/EmbedProviders/EmbedProvidersCollectionBuilder.cs index a4856557c86b..411bf7af6f84 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/EmbedProvidersCollectionBuilder.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/EmbedProvidersCollectionBuilder.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Media.EmbedProviders; diff --git a/src/Umbraco.Core/Media/EmbedProviders/Flickr.cs b/src/Umbraco.Core/Media/EmbedProviders/Flickr.cs index 4d773a79d478..2de4f7536567 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Flickr.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Flickr.cs @@ -7,27 +7,27 @@ namespace Umbraco.Cms.Core.Media.EmbedProviders; // TODO(V10) : change base class to OEmbedProviderBase public class Flickr : EmbedProviderBase { - public Flickr(IJsonSerializer jsonSerializer) : base(jsonSerializer) + public Flickr(IJsonSerializer jsonSerializer) + : base(jsonSerializer) { } public override string ApiEndpoint => "http://www.flickr.com/services/oembed/"; - public override string[] UrlSchemeRegex => new[] {@"flickr.com\/photos\/*", @"flic.kr\/p\/*"}; + public override string[] UrlSchemeRegex => new[] { @"flickr.com\/photos\/*", @"flic.kr\/p\/*" }; public override Dictionary RequestParams => new(); public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); + var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = this.GetXmlResponse(requestUrl); var imageUrl = GetXmlProperty(xmlDocument, "/oembed/url"); var imageWidth = GetXmlProperty(xmlDocument, "/oembed/width"); var imageHeight = GetXmlProperty(xmlDocument, "/oembed/height"); var imageTitle = GetXmlProperty(xmlDocument, "/oembed/title"); - return string.Format("\"{3}\"", imageUrl, imageWidth, - imageHeight, WebUtility.HtmlEncode(imageTitle)); + return string.Format("\"{3}\"", imageUrl, imageWidth, imageHeight, WebUtility.HtmlEncode(imageTitle)); } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/GettyImages.cs b/src/Umbraco.Core/Media/EmbedProviders/GettyImages.cs index 45fa425a4528..b8a69d2ea817 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/GettyImages.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/GettyImages.cs @@ -5,22 +5,23 @@ namespace Umbraco.Cms.Core.Media.EmbedProviders; // TODO(V10) : change base class to OEmbedProviderBase public class GettyImages : EmbedProviderBase { - public GettyImages(IJsonSerializer jsonSerializer) : base(jsonSerializer) + public GettyImages(IJsonSerializer jsonSerializer) + : base(jsonSerializer) { } public override string ApiEndpoint => "http://embed.gettyimages.com/oembed"; - //http://gty.im/74917285 - //http://www.gettyimages.com/detail/74917285 - public override string[] UrlSchemeRegex => new[] {@"gty\.im/*", @"gettyimages.com\/detail\/*"}; + // http://gty.im/74917285 + // http://www.gettyimages.com/detail/74917285 + public override string[] UrlSchemeRegex => new[] { @"gty\.im/*", @"gettyimages.com\/detail\/*" }; public override Dictionary RequestParams => new(); public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse oembed = base.GetJsonResponse(requestUrl); + var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse? oembed = this.GetJsonResponse(requestUrl); return oembed?.GetHtml(); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Giphy.cs b/src/Umbraco.Core/Media/EmbedProviders/Giphy.cs index 84dbbd302f70..445687a5a347 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Giphy.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Giphy.cs @@ -8,20 +8,21 @@ namespace Umbraco.Cms.Core.Media.EmbedProviders; /// TODO(V10) : change base class to OEmbedProviderBase public class Giphy : EmbedProviderBase { - public Giphy(IJsonSerializer jsonSerializer) : base(jsonSerializer) + public Giphy(IJsonSerializer jsonSerializer) + : base(jsonSerializer) { } public override string ApiEndpoint => "https://giphy.com/services/oembed?url="; - public override string[] UrlSchemeRegex => new[] {@"giphy\.com/*", @"gph\.is/*"}; + public override string[] UrlSchemeRegex => new[] { @"giphy\.com/*", @"gph\.is/*" }; public override Dictionary RequestParams => new(); public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse oembed = base.GetJsonResponse(requestUrl); + var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse? oembed = this.GetJsonResponse(requestUrl); return oembed?.GetHtml(); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Hulu.cs b/src/Umbraco.Core/Media/EmbedProviders/Hulu.cs index b621d46baf15..2aa2c27b1cbe 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Hulu.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Hulu.cs @@ -5,20 +5,21 @@ namespace Umbraco.Cms.Core.Media.EmbedProviders; // TODO(V10) : change base class to OEmbedProviderBase public class Hulu : EmbedProviderBase { - public Hulu(IJsonSerializer jsonSerializer) : base(jsonSerializer) + public Hulu(IJsonSerializer jsonSerializer) + : base(jsonSerializer) { } public override string ApiEndpoint => "http://www.hulu.com/api/oembed.json"; - public override string[] UrlSchemeRegex => new[] {@"hulu.com/watch/.*"}; + public override string[] UrlSchemeRegex => new[] { @"hulu.com/watch/.*" }; public override Dictionary RequestParams => new(); public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse oembed = base.GetJsonResponse(requestUrl); + var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse? oembed = this.GetJsonResponse(requestUrl); return oembed?.GetHtml(); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs b/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs index b3f3401b5362..0ca8cf68f32c 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs @@ -6,24 +6,25 @@ namespace Umbraco.Cms.Core.Media.EmbedProviders; // TODO(V10) : change base class to OEmbedProviderBase public class Issuu : EmbedProviderBase { - public Issuu(IJsonSerializer jsonSerializer) : base(jsonSerializer) + public Issuu(IJsonSerializer jsonSerializer) + : base(jsonSerializer) { } public override string ApiEndpoint => "https://issuu.com/oembed"; - public override string[] UrlSchemeRegex => new[] {@"issuu.com/.*/docs/.*"}; + public override string[] UrlSchemeRegex => new[] { @"issuu.com/.*/docs/.*" }; public override Dictionary RequestParams => new() { - //ApiUrl/?format=xml - {"format", "xml"} + // ApiUrl/?format=xml + { "format", "xml" }, }; public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); + var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = this.GetXmlResponse(requestUrl); return GetXmlProperty(xmlDocument, "/oembed/html"); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Kickstarter.cs b/src/Umbraco.Core/Media/EmbedProviders/Kickstarter.cs index 64faccf97ce1..8cbeae5e6405 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Kickstarter.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Kickstarter.cs @@ -5,20 +5,21 @@ namespace Umbraco.Cms.Core.Media.EmbedProviders; // TODO(V10) : change base class to OEmbedProviderBase public class Kickstarter : EmbedProviderBase { - public Kickstarter(IJsonSerializer jsonSerializer) : base(jsonSerializer) + public Kickstarter(IJsonSerializer jsonSerializer) + : base(jsonSerializer) { } public override string ApiEndpoint => "http://www.kickstarter.com/services/oembed"; - public override string[] UrlSchemeRegex => new[] {@"kickstarter\.com/projects/*"}; + public override string[] UrlSchemeRegex => new[] { @"kickstarter\.com/projects/*" }; public override Dictionary RequestParams => new(); public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse oembed = base.GetJsonResponse(requestUrl); + var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse? oembed = this.GetJsonResponse(requestUrl); return oembed?.GetHtml(); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/LottieFiles.cs b/src/Umbraco.Core/Media/EmbedProviders/LottieFiles.cs index 5113fcda90ed..95330a646781 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/LottieFiles.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/LottieFiles.cs @@ -8,22 +8,24 @@ namespace Umbraco.Cms.Core.Media.EmbedProviders; /// public class LottieFiles : OEmbedProviderBase { - public LottieFiles(IJsonSerializer jsonSerializer) : base(jsonSerializer) + public LottieFiles(IJsonSerializer jsonSerializer) + : base(jsonSerializer) { } public override string ApiEndpoint => "https://embed.lottiefiles.com/oembed"; - public override string[] UrlSchemeRegex => new[] {@"lottiefiles\.com/*"}; + public override string[] UrlSchemeRegex => new[] { @"lottiefiles\.com/*" }; public override Dictionary RequestParams => new(); public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse? oembed = base.GetJsonResponse(requestUrl); + var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse? oembed = this.GetJsonResponse(requestUrl); var html = oembed?.GetHtml(); - //LottieFiles doesn't seem to support maxwidth and maxheight via oembed + + // LottieFiles doesn't seem to support maxwidth and maxheight via oembed // this is therefore a hack... with regexes.. is that ok? HtmlAgility etc etc // otherwise it always defaults to 300... if (html is null) @@ -38,7 +40,7 @@ public LottieFiles(IJsonSerializer jsonSerializer) : base(jsonSerializer) } else { - //if set to 0, let's default to 100% as an easter egg + // if set to 0, let's default to 100% as an easter egg html = Regex.Replace(html, "width=\"([0-9]{1,4})\"", "width=\"100%\""); html = Regex.Replace(html, "height=\"([0-9]{1,4})\"", "height=\"100%\""); } diff --git a/src/Umbraco.Core/Media/Exif/ExifBitConverter.cs b/src/Umbraco.Core/Media/Exif/ExifBitConverter.cs index ed46cbb4888d..e8dc7c4eb993 100644 --- a/src/Umbraco.Core/Media/Exif/ExifBitConverter.cs +++ b/src/Umbraco.Core/Media/Exif/ExifBitConverter.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; using System.Text; namespace Umbraco.Cms.Core.Media.Exif; @@ -71,7 +71,8 @@ public static DateTime ToDateTime(byte[] data, bool hastime) // yyyy:MM:dd HH:mm:ss // This is the expected format though some cameras // can use single digits. See Issue 21. - return new DateTime(int.Parse(parts[0], CultureInfo.InvariantCulture), + return new DateTime( + int.Parse(parts[0], CultureInfo.InvariantCulture), int.Parse(parts[1], CultureInfo.InvariantCulture), int.Parse(parts[2], CultureInfo.InvariantCulture), int.Parse(parts[3], CultureInfo.InvariantCulture), @@ -82,7 +83,8 @@ public static DateTime ToDateTime(byte[] data, bool hastime) if (!hastime && parts.Length == 3) { // yyyy:MM:dd - return new DateTime(int.Parse(parts[0], CultureInfo.InvariantCulture), + return new DateTime( + int.Parse(parts[0], CultureInfo.InvariantCulture), int.Parse(parts[1], CultureInfo.InvariantCulture), int.Parse(parts[2], CultureInfo.InvariantCulture)); } @@ -117,7 +119,8 @@ public static MathEx.UFraction32 ToURational(byte[] data, ByteOrder frombyteorde var den = new byte[4]; Array.Copy(data, 0, num, 0, 4); Array.Copy(data, 4, den, 0, 4); - return new MathEx.UFraction32(ToUInt32(num, 0, frombyteorder, SystemByteOrder), + return new MathEx.UFraction32( + ToUInt32(num, 0, frombyteorder, SystemByteOrder), ToUInt32(den, 0, frombyteorder, SystemByteOrder)); } @@ -134,7 +137,8 @@ public static MathEx.Fraction32 ToSRational(byte[] data, ByteOrder frombyteorder var den = new byte[4]; Array.Copy(data, 0, num, 0, 4); Array.Copy(data, 4, den, 0, 4); - return new MathEx.Fraction32(ToInt32(num, 0, frombyteorder, SystemByteOrder), + return new MathEx.Fraction32( + ToInt32(num, 0, frombyteorder, SystemByteOrder), ToInt32(den, 0, frombyteorder, SystemByteOrder)); } @@ -206,7 +210,8 @@ public static MathEx.UFraction32[] ToURationalArray(byte[] data, int count, Byte var den = new byte[4]; Array.Copy(data, i * 8, num, 0, 4); Array.Copy(data, (i * 8) + 4, den, 0, 4); - numbers[i].Set(ToUInt32(num, 0, frombyteorder, SystemByteOrder), + numbers[i].Set( + ToUInt32(num, 0, frombyteorder, SystemByteOrder), ToUInt32(den, 0, frombyteorder, SystemByteOrder)); } @@ -227,7 +232,8 @@ public static MathEx.Fraction32[] ToSRationalArray(byte[] data, int count, ByteO var den = new byte[4]; Array.Copy(data, i * 8, num, 0, 4); Array.Copy(data, (i * 8) + 4, den, 0, 4); - numbers[i].Set(ToInt32(num, 0, frombyteorder, SystemByteOrder), + numbers[i].Set( + ToInt32(num, 0, frombyteorder, SystemByteOrder), ToInt32(den, 0, frombyteorder, SystemByteOrder)); } @@ -257,7 +263,7 @@ public static byte[] GetBytes(string value, bool addnull, Encoding encoding) /// public static byte[] GetBytes(DateTime value, bool hastime) { - var str = ""; + var str = string.Empty; if (hastime) { str = value.ToString("yyyy:MM:dd HH:mm:ss", CultureInfo.InvariantCulture); diff --git a/src/Umbraco.Core/Media/Exif/ExifEnums.cs b/src/Umbraco.Core/Media/Exif/ExifEnums.cs index cb79c0d7dc2e..5e27b8dd2418 100644 --- a/src/Umbraco.Core/Media/Exif/ExifEnums.cs +++ b/src/Umbraco.Core/Media/Exif/ExifEnums.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Media.Exif; +namespace Umbraco.Cms.Core.Media.Exif; internal enum Compression : ushort { @@ -8,7 +8,7 @@ internal enum Compression : ushort Group4Fax = 4, LZW = 5, JPEG = 6, - PackBits = 32773 + PackBits = 32773, } internal enum PhotometricInterpretation : ushort @@ -20,7 +20,7 @@ internal enum PhotometricInterpretation : ushort TransparencyMask = 4, CMYK = 5, YCbCr = 6, - CIELab = 8 + CIELab = 8, } internal enum Orientation : ushort @@ -32,31 +32,31 @@ internal enum Orientation : ushort RotatedLeftAndMirroredVertically = 5, RotatedRight = 6, RotatedLeft = 7, - RotatedRightAndMirroredVertically = 8 + RotatedRightAndMirroredVertically = 8, } internal enum PlanarConfiguration : ushort { ChunkyFormat = 1, - PlanarFormat = 2 + PlanarFormat = 2, } internal enum YCbCrPositioning : ushort { Centered = 1, - CoSited = 2 + CoSited = 2, } internal enum ResolutionUnit : ushort { Inches = 2, - Centimeters = 3 + Centimeters = 3, } internal enum ColorSpace : ushort { - sRGB = 1, - Uncalibrated = 0xfff + SRGB = 1, + Uncalibrated = 0xfff, } internal enum ExposureProgram : ushort @@ -85,7 +85,7 @@ internal enum ExposureProgram : ushort /// /// For landscape photos with the background in focus. /// - Landscape = 8 + Landscape = 8, } internal enum MeteringMode : ushort @@ -97,7 +97,7 @@ internal enum MeteringMode : ushort MultiSpot = 4, Pattern = 5, Partial = 6, - Other = 255 + Other = 255, } internal enum LightSource : ushort @@ -138,7 +138,7 @@ internal enum LightSource : ushort D75 = 22, D50 = 23, ISOStudioTungsten = 24, - OtherLightSource = 255 + OtherLightSource = 255, } [Flags] @@ -151,7 +151,7 @@ internal enum Flash : ushort CompulsoryFlashMode = 8, AutoMode = 16, NoFlashFunction = 32, - RedEyeReductionMode = 64 + RedEyeReductionMode = 64, } internal enum SensingMethod : ushort @@ -162,36 +162,36 @@ internal enum SensingMethod : ushort ThreeChipColorAreaSensor = 4, ColorSequentialAreaSensor = 5, TriLinearSensor = 7, - ColorSequentialLinearSensor = 8 + ColorSequentialLinearSensor = 8, } internal enum FileSource : byte // UNDEFINED { - DSC = 3 + DSC = 3, } internal enum SceneType : byte // UNDEFINED { - DirectlyPhotographedImage = 1 + DirectlyPhotographedImage = 1, } internal enum CustomRendered : ushort { NormalProcess = 0, - CustomProcess = 1 + CustomProcess = 1, } internal enum ExposureMode : ushort { Auto = 0, Manual = 1, - AutoBracket = 2 + AutoBracket = 2, } internal enum WhiteBalance : ushort { Auto = 0, - Manual = 1 + Manual = 1, } internal enum SceneCaptureType : ushort @@ -199,7 +199,7 @@ internal enum SceneCaptureType : ushort Standard = 0, Landscape = 1, Portrait = 2, - NightScene = 3 + NightScene = 3, } internal enum GainControl : ushort @@ -208,28 +208,28 @@ internal enum GainControl : ushort LowGainUp = 1, HighGainUp = 2, LowGainDown = 3, - HighGainDown = 4 + HighGainDown = 4, } internal enum Contrast : ushort { Normal = 0, Soft = 1, - Hard = 2 + Hard = 2, } internal enum Saturation : ushort { Normal = 0, Low = 1, - High = 2 + High = 2, } internal enum Sharpness : ushort { Normal = 0, Soft = 1, - Hard = 2 + Hard = 2, } internal enum SubjectDistanceRange : ushort @@ -237,61 +237,61 @@ internal enum SubjectDistanceRange : ushort Unknown = 0, Macro = 1, CloseView = 2, - DistantView = 3 + DistantView = 3, } internal enum GPSLatitudeRef : byte // ASCII { North = 78, // 'N' - South = 83 // 'S' + South = 83, // 'S' } internal enum GPSLongitudeRef : byte // ASCII { West = 87, // 'W' - East = 69 // 'E' + East = 69, // 'E' } internal enum GPSAltitudeRef : byte { AboveSeaLevel = 0, - BelowSeaLevel = 1 + BelowSeaLevel = 1, } internal enum GPSStatus : byte // ASCII { MeasurementInProgress = 65, // 'A' - MeasurementInteroperability = 86 // 'V' + MeasurementInteroperability = 86, // 'V' } internal enum GPSMeasureMode : byte // ASCII { TwoDimensional = 50, // '2' - ThreeDimensional = 51 // '3' + ThreeDimensional = 51, // '3' } internal enum GPSSpeedRef : byte // ASCII { KilometersPerHour = 75, // 'K' MilesPerHour = 77, // 'M' - Knots = 78 // 'N' + Knots = 78, // 'N' } internal enum GPSDirectionRef : byte // ASCII { TrueDirection = 84, // 'T' - MagneticDirection = 77 // 'M' + MagneticDirection = 77, // 'M' } internal enum GPSDistanceRef : byte // ASCII { Kilometers = 75, // 'K' Miles = 77, // 'M' - Knots = 78 // 'N' + Knots = 78, // 'N' } internal enum GPSDifferential : ushort { MeasurementWithoutDifferentialCorrection = 0, - DifferentialCorrectionApplied = 1 + DifferentialCorrectionApplied = 1, } diff --git a/src/Umbraco.Core/Media/Exif/ExifExceptions.cs b/src/Umbraco.Core/Media/Exif/ExifExceptions.cs index 03cc024ad27d..bd4426e15af5 100644 --- a/src/Umbraco.Core/Media/Exif/ExifExceptions.cs +++ b/src/Umbraco.Core/Media/Exif/ExifExceptions.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Media.Exif; diff --git a/src/Umbraco.Core/Media/Exif/ExifExtendedProperty.cs b/src/Umbraco.Core/Media/Exif/ExifExtendedProperty.cs index 186c5442fe4d..ffa31f0cc159 100644 --- a/src/Umbraco.Core/Media/Exif/ExifExtendedProperty.cs +++ b/src/Umbraco.Core/Media/Exif/ExifExtendedProperty.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; namespace Umbraco.Cms.Core.Media.Exif; @@ -23,18 +23,18 @@ public ExifEnumProperty(ExifTag tag, T value) { } - protected override object _Value - { - get => Value; - set => Value = (T)value; - } - public new T Value { get => mValue; set => mValue = value; } + protected override object _Value + { + get => Value; + set => Value = (T)value; + } + public bool IsBitField => mIsBitField; public override ExifInterOperability Interoperability @@ -49,7 +49,7 @@ public override ExifInterOperability Interoperability if (type == typeof(FileSource) || type == typeof(SceneType)) { // UNDEFINED - return new ExifInterOperability(tagid, 7, 1, new[] {(byte)(object)mValue}); + return new ExifInterOperability(tagid, 7, 1, new[] { (byte)(object)mValue }); } if (type == typeof(GPSLatitudeRef) || type == typeof(GPSLongitudeRef) || @@ -58,21 +58,23 @@ public override ExifInterOperability Interoperability type == typeof(GPSDistanceRef)) { // ASCII - return new ExifInterOperability(tagid, 2, 2, new byte[] {(byte)(object)mValue, 0}); + return new ExifInterOperability(tagid, 2, 2, new byte[] { (byte)(object)mValue, 0 }); } if (basetype == typeof(byte)) { // BYTE - return new ExifInterOperability(tagid, 1, 1, new[] {(byte)(object)mValue}); + return new ExifInterOperability(tagid, 1, 1, new[] { (byte)(object)mValue }); } if (basetype == typeof(ushort)) { // SHORT - return new ExifInterOperability(tagid, 3, 1, - BitConverterEx.GetBytes((ushort)(object)mValue, BitConverterEx.SystemByteOrder, - BitConverterEx.SystemByteOrder)); + return new ExifInterOperability( + tagid, + 3, + 1, + BitConverterEx.GetBytes((ushort)(object)mValue, BitConverterEx.SystemByteOrder, BitConverterEx.SystemByteOrder)); } throw new InvalidOperationException( @@ -99,25 +101,25 @@ public ExifEncodedString(ExifTag tag, string value, Encoding encoding) Encoding = encoding; } - protected override object _Value - { - get => Value; - set => Value = (string)value; - } - public new string Value { get => mValue; set => mValue = value; } + protected override object _Value + { + get => Value; + set => Value = (string)value; + } + public Encoding Encoding { get; set; } public override ExifInterOperability Interoperability { get { - var enc = ""; + var enc = string.Empty; if (Encoding == null) { enc = "\0\0\0\0\0\0\0\0"; @@ -165,18 +167,18 @@ public ExifDateTime(ExifTag tag, DateTime value) : base(tag) => mValue = value; - protected override object _Value - { - get => Value; - set => Value = (DateTime)value; - } - public new DateTime Value { get => mValue; set => mValue = value; } + protected override object _Value + { + get => Value; + set => Value = (DateTime)value; + } + public override ExifInterOperability Interoperability => new(ExifTagFactory.GetTagID(mTag), 2, 20, ExifBitConverter.GetBytes(mValue, true)); @@ -198,7 +200,7 @@ public ExifVersion(ExifTag tag, string value) { if (value.Length > 4) { - mValue = value.Substring(0, 4); + mValue = value[..4]; } else if (value.Length < 4) { @@ -210,16 +212,16 @@ public ExifVersion(ExifTag tag, string value) } } - protected override object _Value + public new string Value { - get => Value; - set => Value = (string)value; + get => mValue; + set => mValue = value[..4]; } - public new string Value + protected override object _Value { - get => mValue; - set => mValue = value.Substring(0, 4); + get => Value; + set => Value = (string)value; } public override ExifInterOperability Interoperability @@ -258,14 +260,8 @@ public ExifPointSubjectArea(ExifTag tag, ushort[] value) } public ExifPointSubjectArea(ExifTag tag, ushort x, ushort y) - : base(tag, new[] {x, y}) - { - } - - protected new ushort[] Value + : base(tag, new[] { x, y }) { - get => mValue; - set => mValue = value; } public ushort X @@ -274,6 +270,12 @@ public ushort X set => mValue[0] = value; } + protected new ushort[] Value + { + get => mValue; + set => mValue = value; + } + public ushort Y { get => mValue[1]; @@ -301,7 +303,7 @@ public ExifCircularSubjectArea(ExifTag tag, ushort[] value) } public ExifCircularSubjectArea(ExifTag tag, ushort x, ushort y, ushort d) - : base(tag, new[] {x, y, d}) + : base(tag, new[] { x, y, d }) { } @@ -332,7 +334,7 @@ public ExifRectangularSubjectArea(ExifTag tag, ushort[] value) } public ExifRectangularSubjectArea(ExifTag tag, ushort x, ushort y, ushort w, ushort h) - : base(tag, new[] {x, y, w, h}) + : base(tag, new[] { x, y, w, h }) { } @@ -367,14 +369,8 @@ public GPSLatitudeLongitude(ExifTag tag, MathEx.UFraction32[] value) } public GPSLatitudeLongitude(ExifTag tag, float d, float m, float s) - : base(tag, new[] {new(d), new MathEx.UFraction32(m), new MathEx.UFraction32(s)}) - { - } - - protected new MathEx.UFraction32[] Value + : base(tag, new[] { new(d), new MathEx.UFraction32(m), new MathEx.UFraction32(s) }) { - get => mValue; - set => mValue = value; } public MathEx.UFraction32 Degrees @@ -383,6 +379,12 @@ public MathEx.UFraction32 Degrees set => mValue[0] = value; } + protected new MathEx.UFraction32[] Value + { + get => mValue; + set => mValue = value; + } + public MathEx.UFraction32 Minutes { get => mValue[1]; @@ -414,22 +416,22 @@ public GPSTimeStamp(ExifTag tag, MathEx.UFraction32[] value) } public GPSTimeStamp(ExifTag tag, float h, float m, float s) - : base(tag, new[] {new(h), new MathEx.UFraction32(m), new MathEx.UFraction32(s)}) + : base(tag, new[] { new(h), new MathEx.UFraction32(m), new MathEx.UFraction32(s) }) { } - protected new MathEx.UFraction32[] Value - { - get => mValue; - set => mValue = value; - } - public MathEx.UFraction32 Hour { get => mValue[0]; set => mValue[0] = value; } + protected new MathEx.UFraction32[] Value + { + get => mValue; + set => mValue = value; + } + public MathEx.UFraction32 Minute { get => mValue[1]; @@ -458,18 +460,18 @@ public WindowsByteString(ExifTag tag, string value) : base(tag) => mValue = value; - protected override object _Value - { - get => Value; - set => Value = (string)value; - } - public new string Value { get => mValue; set => mValue = value; } + protected override object _Value + { + get => Value; + set => Value = (string)value; + } + public override ExifInterOperability Interoperability { get diff --git a/src/Umbraco.Core/Media/Exif/ExifFileTypeDescriptor.cs b/src/Umbraco.Core/Media/Exif/ExifFileTypeDescriptor.cs index 5bc4d44efbc9..a07ade99632f 100644 --- a/src/Umbraco.Core/Media/Exif/ExifFileTypeDescriptor.cs +++ b/src/Umbraco.Core/Media/Exif/ExifFileTypeDescriptor.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; namespace Umbraco.Cms.Core.Media.Exif; @@ -78,7 +78,7 @@ internal sealed class ExifPropertyDescriptor : PropertyDescriptor private readonly object originalValue; public ExifPropertyDescriptor(ExifProperty property) - : base(property.Name, new Attribute[] {new BrowsableAttribute(true)}) + : base(property.Name, new Attribute[] { new BrowsableAttribute(true) }) { linkedProperty = property; originalValue = property.Value; diff --git a/src/Umbraco.Core/Media/Exif/ExifInterOperability.cs b/src/Umbraco.Core/Media/Exif/ExifInterOperability.cs index 55a893b224f4..e4e3669eb77a 100644 --- a/src/Umbraco.Core/Media/Exif/ExifInterOperability.cs +++ b/src/Umbraco.Core/Media/Exif/ExifInterOperability.cs @@ -1,10 +1,18 @@ -namespace Umbraco.Cms.Core.Media.Exif; +namespace Umbraco.Cms.Core.Media.Exif; /// /// Represents interoperability data for an exif tag in the platform byte order. /// internal struct ExifInterOperability { + public ExifInterOperability(ushort tagid, ushort typeid, uint count, byte[] data) + { + TagID = tagid; + TypeID = typeid; + Count = count; + Data = data; + } + /// /// Gets the tag ID defined in the Exif standard. /// @@ -45,12 +53,4 @@ internal struct ExifInterOperability /// public override string ToString() => string.Format("Tag: {0}, Type: {1}, Count: {2}, Data Length: {3}", TagID, TypeID, Count, Data.Length); - - public ExifInterOperability(ushort tagid, ushort typeid, uint count, byte[] data) - { - TagID = tagid; - TypeID = typeid; - Count = count; - Data = data; - } } diff --git a/src/Umbraco.Core/Media/Exif/ExifProperty.cs b/src/Umbraco.Core/Media/Exif/ExifProperty.cs index e31c2ce73272..6d742bb3ba6a 100644 --- a/src/Umbraco.Core/Media/Exif/ExifProperty.cs +++ b/src/Umbraco.Core/Media/Exif/ExifProperty.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; namespace Umbraco.Cms.Core.Media.Exif; @@ -44,8 +44,6 @@ public string Name set => mName = value; } - protected abstract object _Value { get; set; } - /// /// Gets or sets the value of this property. /// @@ -55,6 +53,8 @@ public object Value set => _Value = value; } + protected abstract object _Value { get; set; } + /// /// Gets interoperability data for this property. /// @@ -72,20 +72,20 @@ public ExifByte(ExifTag tag, byte value) : base(tag) => mValue = value; - protected override object _Value - { - get => Value; - set => Value = Convert.ToByte(value); - } - public new byte Value { get => mValue; set => mValue = value; } + protected override object _Value + { + get => Value; + set => Value = Convert.ToByte(value); + } + public override ExifInterOperability Interoperability => - new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 1, 1, new[] {mValue}); + new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 1, 1, new[] { mValue }); public static implicit operator byte(ExifByte obj) => obj.mValue; @@ -103,18 +103,18 @@ public ExifByteArray(ExifTag tag, byte[] value) : base(tag) => mValue = value; - protected override object _Value - { - get => Value; - set => Value = (byte[])value; - } - public new byte[] Value { get => mValue; set => mValue = value; } + protected override object _Value + { + get => Value; + set => Value = (byte[])value; + } + public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 1, (uint)mValue.Length, mValue); @@ -150,22 +150,26 @@ public ExifAscii(ExifTag tag, string value, Encoding encoding) Encoding = encoding; } - protected override object _Value - { - get => Value; - set => Value = (string)value; - } - public new string Value { get => mValue; set => mValue = value; } + protected override object _Value + { + get => Value; + set => Value = (string)value; + } + public Encoding Encoding { get; } - public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 2, - (uint)mValue.Length + 1, ExifBitConverter.GetBytes(mValue, true, Encoding)); + public override ExifInterOperability Interoperability => + new ExifInterOperability( + ExifTagFactory.GetTagID(mTag), + 2, + (uint)mValue.Length + 1, + ExifBitConverter.GetBytes(mValue, true, Encoding)); public static implicit operator string(ExifAscii obj) => obj.mValue; @@ -183,20 +187,24 @@ public ExifUShort(ExifTag tag, ushort value) : base(tag) => mValue = value; - protected override object _Value - { - get => Value; - set => Value = Convert.ToUInt16(value); - } - public new ushort Value { get => mValue; set => mValue = value; } - public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 3, - 1, BitConverterEx.GetBytes(mValue, BitConverterEx.SystemByteOrder, BitConverterEx.SystemByteOrder)); + protected override object _Value + { + get => Value; + set => Value = Convert.ToUInt16(value); + } + + public override ExifInterOperability Interoperability => + new ExifInterOperability( + ExifTagFactory.GetTagID(mTag), + 3, + 1, + BitConverterEx.GetBytes(mValue, BitConverterEx.SystemByteOrder, BitConverterEx.SystemByteOrder)); public static implicit operator ushort(ExifUShort obj) => obj.mValue; @@ -215,20 +223,24 @@ public ExifUShortArray(ExifTag tag, ushort[] value) : base(tag) => mValue = value; - protected override object _Value - { - get => Value; - set => Value = (ushort[])value; - } - public new ushort[] Value { get => mValue; set => mValue = value; } - public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 3, - (uint)mValue.Length, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); + protected override object _Value + { + get => Value; + set => Value = (ushort[])value; + } + + public override ExifInterOperability Interoperability => + new ExifInterOperability( + ExifTagFactory.GetTagID(mTag), + 3, + (uint)mValue.Length, + ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); public static implicit operator ushort[](ExifUShortArray obj) => obj.mValue; @@ -259,20 +271,24 @@ public ExifUInt(ExifTag tag, uint value) : base(tag) => mValue = value; - protected override object _Value - { - get => Value; - set => Value = Convert.ToUInt32(value); - } - public new uint Value { get => mValue; set => mValue = value; } - public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 4, - 1, BitConverterEx.GetBytes(mValue, BitConverterEx.SystemByteOrder, BitConverterEx.SystemByteOrder)); + protected override object _Value + { + get => Value; + set => Value = Convert.ToUInt32(value); + } + + public override ExifInterOperability Interoperability => + new ExifInterOperability( + ExifTagFactory.GetTagID(mTag), + 4, + 1, + BitConverterEx.GetBytes(mValue, BitConverterEx.SystemByteOrder, BitConverterEx.SystemByteOrder)); public static implicit operator uint(ExifUInt obj) => obj.mValue; @@ -291,20 +307,19 @@ public ExifUIntArray(ExifTag tag, uint[] value) : base(tag) => mValue = value; - protected override object _Value - { - get => Value; - set => Value = (uint[])value; - } - public new uint[] Value { get => mValue; set => mValue = value; } - public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 3, - (uint)mValue.Length, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); + protected override object _Value + { + get => Value; + set => Value = (uint[])value; + } + + public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 3, (uint)mValue.Length, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); public static implicit operator uint[](ExifUIntArray obj) => obj.mValue; @@ -340,27 +355,32 @@ public ExifURational(ExifTag tag, MathEx.UFraction32 value) : base(tag) => mValue = value; + public new MathEx.UFraction32 Value + { + get => mValue; + set => mValue = value; + } + protected override object _Value { get => Value; set => Value = (MathEx.UFraction32)value; } - public new MathEx.UFraction32 Value - { - get => mValue; - set => mValue = value; - } + public override ExifInterOperability Interoperability => + new ExifInterOperability( + ExifTagFactory.GetTagID(mTag), + 5, + 1, + ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); - public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 5, - 1, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); + public static explicit operator float(ExifURational obj) => (float)obj.mValue; public override string ToString() => mValue.ToString(); - public float ToFloat() => (float)mValue; - public static explicit operator float(ExifURational obj) => (float)obj.mValue; + public float ToFloat() => (float)mValue; - public uint[] ToArray() => new[] {mValue.Numerator, mValue.Denominator}; + public uint[] ToArray() => new[] { mValue.Numerator, mValue.Denominator }; } /// @@ -375,20 +395,24 @@ public ExifURationalArray(ExifTag tag, MathEx.UFraction32[] value) : base(tag) => mValue = value; - protected override object _Value - { - get => Value; - set => Value = (MathEx.UFraction32[])value; - } - public new MathEx.UFraction32[] Value { get => mValue; set => mValue = value; } - public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 5, - (uint)mValue.Length, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); + protected override object _Value + { + get => Value; + set => Value = (MathEx.UFraction32[])value; + } + + public override ExifInterOperability Interoperability => + new ExifInterOperability( + ExifTagFactory.GetTagID(mTag), + 5, + (uint)mValue.Length, + ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); public static explicit operator float[](ExifURationalArray obj) { @@ -428,18 +452,18 @@ public ExifUndefined(ExifTag tag, byte[] value) : base(tag) => mValue = value; - protected override object _Value - { - get => Value; - set => Value = (byte[])value; - } - public new byte[] Value { get => mValue; set => mValue = value; } + protected override object _Value + { + get => Value; + set => Value = (byte[])value; + } + public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 7, (uint)mValue.Length, mValue); @@ -472,24 +496,28 @@ public ExifSInt(ExifTag tag, int value) : base(tag) => mValue = value; - protected override object _Value - { - get => Value; - set => Value = Convert.ToInt32(value); - } - public new int Value { get => mValue; set => mValue = value; } - public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 9, - 1, BitConverterEx.GetBytes(mValue, BitConverterEx.SystemByteOrder, BitConverterEx.SystemByteOrder)); + protected override object _Value + { + get => Value; + set => Value = Convert.ToInt32(value); + } - public override string ToString() => mValue.ToString(); + public override ExifInterOperability Interoperability => + new ExifInterOperability( + ExifTagFactory.GetTagID(mTag), + 9, + 1, + BitConverterEx.GetBytes(mValue, BitConverterEx.SystemByteOrder, BitConverterEx.SystemByteOrder)); public static implicit operator int(ExifSInt obj) => obj.mValue; + + public override string ToString() => mValue.ToString(); } /// @@ -504,20 +532,26 @@ public ExifSIntArray(ExifTag tag, int[] value) : base(tag) => mValue = value; + public new int[] Value + { + get => mValue; + set => mValue = value; + } + protected override object _Value { get => Value; set => Value = (int[])value; } - public new int[] Value - { - get => mValue; - set => mValue = value; - } + public override ExifInterOperability Interoperability => + new ExifInterOperability( + ExifTagFactory.GetTagID(mTag), + 9, + (uint)mValue.Length, + ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); - public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 9, - (uint)mValue.Length, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); + public static implicit operator int[](ExifSIntArray obj) => obj.mValue; public override string ToString() { @@ -533,8 +567,6 @@ public override string ToString() sb.Append(']'); return sb.ToString(); } - - public static implicit operator int[](ExifSIntArray obj) => obj.mValue; } /// @@ -553,27 +585,32 @@ public ExifSRational(ExifTag tag, MathEx.Fraction32 value) : base(tag) => mValue = value; + public new MathEx.Fraction32 Value + { + get => mValue; + set => mValue = value; + } + protected override object _Value { get => Value; set => Value = (MathEx.Fraction32)value; } - public new MathEx.Fraction32 Value - { - get => mValue; - set => mValue = value; - } + public override ExifInterOperability Interoperability => + new ExifInterOperability( + ExifTagFactory.GetTagID(mTag), + 10, + 1, + ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); - public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 10, - 1, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); + public static explicit operator float(ExifSRational obj) => (float)obj.mValue; public override string ToString() => mValue.ToString(); - public float ToFloat() => (float)mValue; - public static explicit operator float(ExifSRational obj) => (float)obj.mValue; + public float ToFloat() => (float)mValue; - public int[] ToArray() => new[] {mValue.Numerator, mValue.Denominator}; + public int[] ToArray() => new[] { mValue.Numerator, mValue.Denominator }; } /// @@ -588,20 +625,24 @@ public ExifSRationalArray(ExifTag tag, MathEx.Fraction32[] value) : base(tag) => mValue = value; - protected override object _Value - { - get => Value; - set => Value = (MathEx.Fraction32[])value; - } - public new MathEx.Fraction32[] Value { get => mValue; set => mValue = value; } - public override ExifInterOperability Interoperability => new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 10, - (uint)mValue.Length, ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); + protected override object _Value + { + get => Value; + set => Value = (MathEx.Fraction32[])value; + } + + public override ExifInterOperability Interoperability => + new ExifInterOperability( + ExifTagFactory.GetTagID(mTag), + 10, + (uint)mValue.Length, + ExifBitConverter.GetBytes(mValue, BitConverterEx.SystemByteOrder)); public static explicit operator float[](ExifSRationalArray obj) { diff --git a/src/Umbraco.Core/Media/Exif/ExifPropertyFactory.cs b/src/Umbraco.Core/Media/Exif/ExifPropertyFactory.cs index 39edcaee7af3..4290dcaf7cd8 100644 --- a/src/Umbraco.Core/Media/Exif/ExifPropertyFactory.cs +++ b/src/Umbraco.Core/Media/Exif/ExifPropertyFactory.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; namespace Umbraco.Cms.Core.Media.Exif; @@ -20,50 +20,61 @@ internal static class ExifPropertyFactory /// IFD section containing this property. /// The encoding to be used for text metadata when the source encoding is unknown. /// an ExifProperty initialized from the interoperability parameters. - public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value, - BitConverterEx.ByteOrder byteOrder, IFD ifd, Encoding encoding) + public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value, BitConverterEx.ByteOrder byteOrder, IFD ifd, Encoding encoding) { var conv = new BitConverterEx(byteOrder, BitConverterEx.SystemByteOrder); + // Find the exif tag corresponding to given tag id ExifTag etag = ExifTagFactory.GetExifTag(ifd, tag); if (ifd == IFD.Zeroth) { - if (tag == 0x103) // Compression + // Compression + if (tag == 0x103) { return new ExifEnumProperty(ExifTag.Compression, (Compression)conv.ToUInt16(value, 0)); } - if (tag == 0x106) // PhotometricInterpretation + // PhotometricInterpretation + if (tag == 0x106) { - return new ExifEnumProperty(ExifTag.PhotometricInterpretation, + return new ExifEnumProperty( + ExifTag.PhotometricInterpretation, (PhotometricInterpretation)conv.ToUInt16(value, 0)); } - if (tag == 0x112) // Orientation + // Orientation + if (tag == 0x112) { return new ExifEnumProperty(ExifTag.Orientation, (Orientation)conv.ToUInt16(value, 0)); } - if (tag == 0x11c) // PlanarConfiguration + // PlanarConfiguration + if (tag == 0x11c) { - return new ExifEnumProperty(ExifTag.PlanarConfiguration, + return new ExifEnumProperty( + ExifTag.PlanarConfiguration, (PlanarConfiguration)conv.ToUInt16(value, 0)); } - if (tag == 0x213) // YCbCrPositioning + // YCbCrPositioning + if (tag == 0x213) { - return new ExifEnumProperty(ExifTag.YCbCrPositioning, + return new ExifEnumProperty( + ExifTag.YCbCrPositioning, (YCbCrPositioning)conv.ToUInt16(value, 0)); } - if (tag == 0x128) // ResolutionUnit + // ResolutionUnit + if (tag == 0x128) { - return new ExifEnumProperty(ExifTag.ResolutionUnit, + return new ExifEnumProperty( + ExifTag.ResolutionUnit, (ResolutionUnit)conv.ToUInt16(value, 0)); } - if (tag == 0x132) // DateTime + // DateTime + if (tag == 0x132) { return new ExifDateTime(ExifTag.DateTime, ExifBitConverter.ToDateTime(value)); } @@ -71,28 +82,33 @@ public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value if (tag == 0x9c9b || tag == 0x9c9c || // Windows tags tag == 0x9c9d || tag == 0x9c9e || tag == 0x9c9f) { - return new WindowsByteString(etag, + return new WindowsByteString( + etag, Encoding.Unicode.GetString(value).TrimEnd(Constants.CharArrays.NullTerminator)); } } else if (ifd == IFD.EXIF) { - if (tag == 0x9000) // ExifVersion + // ExifVersion + if (tag == 0x9000) { return new ExifVersion(ExifTag.ExifVersion, ExifBitConverter.ToAscii(value, Encoding.ASCII)); } - if (tag == 0xa000) // FlashpixVersion + // FlashpixVersion + if (tag == 0xa000) { return new ExifVersion(ExifTag.FlashpixVersion, ExifBitConverter.ToAscii(value, Encoding.ASCII)); } - if (tag == 0xa001) // ColorSpace + // ColorSpace + if (tag == 0xa001) { return new ExifEnumProperty(ExifTag.ColorSpace, (ColorSpace)conv.ToUInt16(value, 0)); } - if (tag == 0x9286) // UserComment + // UserComment + if (tag == 0x9286) { // Default to ASCII Encoding enc = Encoding.ASCII; @@ -129,301 +145,376 @@ public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value return new ExifEncodedString(ExifTag.UserComment, val, enc); } - if (tag == 0x9003) // DateTimeOriginal + // DateTimeOriginal + if (tag == 0x9003) { return new ExifDateTime(ExifTag.DateTimeOriginal, ExifBitConverter.ToDateTime(value)); } - if (tag == 0x9004) // DateTimeDigitized + // DateTimeDigitized + if (tag == 0x9004) { return new ExifDateTime(ExifTag.DateTimeDigitized, ExifBitConverter.ToDateTime(value)); } - if (tag == 0x8822) // ExposureProgram + // ExposureProgram + if (tag == 0x8822) { - return new ExifEnumProperty(ExifTag.ExposureProgram, + return new ExifEnumProperty( + ExifTag.ExposureProgram, (ExposureProgram)conv.ToUInt16(value, 0)); } - if (tag == 0x9207) // MeteringMode + // MeteringMode + if (tag == 0x9207) { return new ExifEnumProperty(ExifTag.MeteringMode, (MeteringMode)conv.ToUInt16(value, 0)); } - if (tag == 0x9208) // LightSource + // LightSource + if (tag == 0x9208) { return new ExifEnumProperty(ExifTag.LightSource, (LightSource)conv.ToUInt16(value, 0)); } - if (tag == 0x9209) // Flash + // Flash + if (tag == 0x9209) { return new ExifEnumProperty(ExifTag.Flash, (Flash)conv.ToUInt16(value, 0), true); } - if (tag == 0x9214) // SubjectArea + // SubjectArea + if (tag == 0x9214) { if (count == 3) { - return new ExifCircularSubjectArea(ExifTag.SubjectArea, + return new ExifCircularSubjectArea( + ExifTag.SubjectArea, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); } if (count == 4) { - return new ExifRectangularSubjectArea(ExifTag.SubjectArea, + return new ExifRectangularSubjectArea( + ExifTag.SubjectArea, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); } - return new ExifPointSubjectArea(ExifTag.SubjectArea, + return new ExifPointSubjectArea( + ExifTag.SubjectArea, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); } - if (tag == 0xa210) // FocalPlaneResolutionUnit + // FocalPlaneResolutionUnit + if (tag == 0xa210) { - return new ExifEnumProperty(ExifTag.FocalPlaneResolutionUnit, - (ResolutionUnit)conv.ToUInt16(value, 0), true); + return new ExifEnumProperty( + ExifTag.FocalPlaneResolutionUnit, + (ResolutionUnit)conv.ToUInt16(value, 0), + true); } - if (tag == 0xa214) // SubjectLocation + // SubjectLocation + if (tag == 0xa214) { - return new ExifPointSubjectArea(ExifTag.SubjectLocation, + return new ExifPointSubjectArea( + ExifTag.SubjectLocation, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); } - if (tag == 0xa217) // SensingMethod + // SensingMethod + if (tag == 0xa217) { - return new ExifEnumProperty(ExifTag.SensingMethod, - (SensingMethod)conv.ToUInt16(value, 0), true); + return new ExifEnumProperty( + ExifTag.SensingMethod, + (SensingMethod)conv.ToUInt16(value, 0), + true); } - if (tag == 0xa300) // FileSource + // FileSource + if (tag == 0xa300) { return new ExifEnumProperty(ExifTag.FileSource, (FileSource)conv.ToUInt16(value, 0), true); } - if (tag == 0xa301) // SceneType + // SceneType + if (tag == 0xa301) { return new ExifEnumProperty(ExifTag.SceneType, (SceneType)conv.ToUInt16(value, 0), true); } - if (tag == 0xa401) // CustomRendered + // CustomRendered + if (tag == 0xa401) { - return new ExifEnumProperty(ExifTag.CustomRendered, - (CustomRendered)conv.ToUInt16(value, 0), true); + return new ExifEnumProperty( + ExifTag.CustomRendered, + (CustomRendered)conv.ToUInt16(value, 0), + true); } - if (tag == 0xa402) // ExposureMode + // ExposureMode + if (tag == 0xa402) { - return new ExifEnumProperty(ExifTag.ExposureMode, (ExposureMode)conv.ToUInt16(value, 0), - true); + return new ExifEnumProperty(ExifTag.ExposureMode, (ExposureMode)conv.ToUInt16(value, 0), true); } - if (tag == 0xa403) // WhiteBalance + // WhiteBalance + if (tag == 0xa403) { - return new ExifEnumProperty(ExifTag.WhiteBalance, (WhiteBalance)conv.ToUInt16(value, 0), - true); + return new ExifEnumProperty(ExifTag.WhiteBalance, (WhiteBalance)conv.ToUInt16(value, 0), true); } - if (tag == 0xa406) // SceneCaptureType + // SceneCaptureType + if (tag == 0xa406) { - return new ExifEnumProperty(ExifTag.SceneCaptureType, - (SceneCaptureType)conv.ToUInt16(value, 0), true); + return new ExifEnumProperty( + ExifTag.SceneCaptureType, + (SceneCaptureType)conv.ToUInt16(value, 0), + true); } - if (tag == 0xa407) // GainControl + // GainControl + if (tag == 0xa407) { - return new ExifEnumProperty(ExifTag.GainControl, (GainControl)conv.ToUInt16(value, 0), - true); + return new ExifEnumProperty(ExifTag.GainControl, (GainControl)conv.ToUInt16(value, 0), true); } - if (tag == 0xa408) // Contrast + // Contrast + if (tag == 0xa408) { return new ExifEnumProperty(ExifTag.Contrast, (Contrast)conv.ToUInt16(value, 0), true); } - if (tag == 0xa409) // Saturation + // Saturation + if (tag == 0xa409) { return new ExifEnumProperty(ExifTag.Saturation, (Saturation)conv.ToUInt16(value, 0), true); } - if (tag == 0xa40a) // Sharpness + // Sharpness + if (tag == 0xa40a) { return new ExifEnumProperty(ExifTag.Sharpness, (Sharpness)conv.ToUInt16(value, 0), true); } - if (tag == 0xa40c) // SubjectDistanceRange + // SubjectDistanceRange + if (tag == 0xa40c) { - return new ExifEnumProperty(ExifTag.SubjectDistanceRange, - (SubjectDistanceRange)conv.ToUInt16(value, 0), true); + return new ExifEnumProperty( + ExifTag.SubjectDistanceRange, + (SubjectDistanceRange)conv.ToUInt16(value, 0), + true); } } else if (ifd == IFD.GPS) { - if (tag == 0) // GPSVersionID + // GPSVersionID + if (tag == 0) { return new ExifVersion(ExifTag.GPSVersionID, ExifBitConverter.ToString(value)); } - if (tag == 1) // GPSLatitudeRef + // GPSLatitudeRef + if (tag == 1) { return new ExifEnumProperty(ExifTag.GPSLatitudeRef, (GPSLatitudeRef)value[0]); } - if (tag == 2) // GPSLatitude + // GPSLatitude + if (tag == 2) { - return new GPSLatitudeLongitude(ExifTag.GPSLatitude, + return new GPSLatitudeLongitude( + ExifTag.GPSLatitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); } - if (tag == 3) // GPSLongitudeRef + // GPSLongitudeRef + if (tag == 3) { return new ExifEnumProperty(ExifTag.GPSLongitudeRef, (GPSLongitudeRef)value[0]); } - if (tag == 4) // GPSLongitude + // GPSLongitude + if (tag == 4) { - return new GPSLatitudeLongitude(ExifTag.GPSLongitude, + return new GPSLatitudeLongitude( + ExifTag.GPSLongitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); } - if (tag == 5) // GPSAltitudeRef + // GPSAltitudeRef + if (tag == 5) { return new ExifEnumProperty(ExifTag.GPSAltitudeRef, (GPSAltitudeRef)value[0]); } - if (tag == 7) // GPSTimeStamp + // GPSTimeStamp + if (tag == 7) { - return new GPSTimeStamp(ExifTag.GPSTimeStamp, + return new GPSTimeStamp( + ExifTag.GPSTimeStamp, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); } - if (tag == 9) // GPSStatus + // GPSStatus + if (tag == 9) { return new ExifEnumProperty(ExifTag.GPSStatus, (GPSStatus)value[0]); } - if (tag == 10) // GPSMeasureMode + // GPSMeasureMode + if (tag == 10) { return new ExifEnumProperty(ExifTag.GPSMeasureMode, (GPSMeasureMode)value[0]); } - if (tag == 12) // GPSSpeedRef + // GPSSpeedRef + if (tag == 12) { return new ExifEnumProperty(ExifTag.GPSSpeedRef, (GPSSpeedRef)value[0]); } - if (tag == 14) // GPSTrackRef + // GPSTrackRef + if (tag == 14) { return new ExifEnumProperty(ExifTag.GPSTrackRef, (GPSDirectionRef)value[0]); } - if (tag == 16) // GPSImgDirectionRef + // GPSImgDirectionRef + if (tag == 16) { return new ExifEnumProperty(ExifTag.GPSImgDirectionRef, (GPSDirectionRef)value[0]); } - if (tag == 19) // GPSDestLatitudeRef + // GPSDestLatitudeRef + if (tag == 19) { return new ExifEnumProperty(ExifTag.GPSDestLatitudeRef, (GPSLatitudeRef)value[0]); } - if (tag == 20) // GPSDestLatitude + // GPSDestLatitude + if (tag == 20) { - return new GPSLatitudeLongitude(ExifTag.GPSDestLatitude, + return new GPSLatitudeLongitude( + ExifTag.GPSDestLatitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); } - if (tag == 21) // GPSDestLongitudeRef + // GPSDestLongitudeRef + if (tag == 21) { return new ExifEnumProperty(ExifTag.GPSDestLongitudeRef, (GPSLongitudeRef)value[0]); } - if (tag == 22) // GPSDestLongitude + // GPSDestLongitude + if (tag == 22) { - return new GPSLatitudeLongitude(ExifTag.GPSDestLongitude, + return new GPSLatitudeLongitude( + ExifTag.GPSDestLongitude, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); } - if (tag == 23) // GPSDestBearingRef + // GPSDestBearingRef + if (tag == 23) { return new ExifEnumProperty(ExifTag.GPSDestBearingRef, (GPSDirectionRef)value[0]); } - if (tag == 25) // GPSDestDistanceRef + // GPSDestDistanceRef + if (tag == 25) { return new ExifEnumProperty(ExifTag.GPSDestDistanceRef, (GPSDistanceRef)value[0]); } - if (tag == 29) // GPSDate + // GPSDate + if (tag == 29) { return new ExifDateTime(ExifTag.GPSDateStamp, ExifBitConverter.ToDateTime(value, false)); } - if (tag == 30) // GPSDifferential + // GPSDifferential + if (tag == 30) { - return new ExifEnumProperty(ExifTag.GPSDifferential, + return new ExifEnumProperty( + ExifTag.GPSDifferential, (GPSDifferential)conv.ToUInt16(value, 0)); } } else if (ifd == IFD.Interop) { - if (tag == 1) // InteroperabilityIndex + // InteroperabilityIndex + if (tag == 1) { - return new ExifAscii(ExifTag.InteroperabilityIndex, ExifBitConverter.ToAscii(value, Encoding.ASCII), - Encoding.ASCII); + return new ExifAscii(ExifTag.InteroperabilityIndex, ExifBitConverter.ToAscii(value, Encoding.ASCII), Encoding.ASCII); } - if (tag == 2) // InteroperabilityVersion + // InteroperabilityVersion + if (tag == 2) { - return new ExifVersion(ExifTag.InteroperabilityVersion, + return new ExifVersion( + ExifTag.InteroperabilityVersion, ExifBitConverter.ToAscii(value, Encoding.ASCII)); } } else if (ifd == IFD.First) { - if (tag == 0x103) // Compression + // Compression + if (tag == 0x103) { - return new ExifEnumProperty(ExifTag.ThumbnailCompression, + return new ExifEnumProperty( + ExifTag.ThumbnailCompression, (Compression)conv.ToUInt16(value, 0)); } - if (tag == 0x106) // PhotometricInterpretation + // PhotometricInterpretation + if (tag == 0x106) { - return new ExifEnumProperty(ExifTag.ThumbnailPhotometricInterpretation, + return new ExifEnumProperty( + ExifTag.ThumbnailPhotometricInterpretation, (PhotometricInterpretation)conv.ToUInt16(value, 0)); } - if (tag == 0x112) // Orientation + // Orientation + if (tag == 0x112) { - return new ExifEnumProperty(ExifTag.ThumbnailOrientation, + return new ExifEnumProperty( + ExifTag.ThumbnailOrientation, (Orientation)conv.ToUInt16(value, 0)); } - if (tag == 0x11c) // PlanarConfiguration + // PlanarConfiguration + if (tag == 0x11c) { - return new ExifEnumProperty(ExifTag.ThumbnailPlanarConfiguration, + return new ExifEnumProperty( + ExifTag.ThumbnailPlanarConfiguration, (PlanarConfiguration)conv.ToUInt16(value, 0)); } - if (tag == 0x213) // YCbCrPositioning + // YCbCrPositioning + if (tag == 0x213) { - return new ExifEnumProperty(ExifTag.ThumbnailYCbCrPositioning, + return new ExifEnumProperty( + ExifTag.ThumbnailYCbCrPositioning, (YCbCrPositioning)conv.ToUInt16(value, 0)); } - if (tag == 0x128) // ResolutionUnit + // ResolutionUnit + if (tag == 0x128) { - return new ExifEnumProperty(ExifTag.ThumbnailResolutionUnit, + return new ExifEnumProperty( + ExifTag.ThumbnailResolutionUnit, (ResolutionUnit)conv.ToUInt16(value, 0)); } - if (tag == 0x132) // DateTime + // DateTime + if (tag == 0x132) { return new ExifDateTime(ExifTag.ThumbnailDateTime, ExifBitConverter.ToDateTime(value)); } } - if (type == 1) // 1 = BYTE An 8-bit unsigned integer. + // 1 = BYTE An 8-bit unsigned integer. + if (type == 1) { if (count == 1) { @@ -433,12 +524,14 @@ public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value return new ExifByteArray(etag, value); } - if (type == 2) // 2 = ASCII An 8-bit byte containing one 7-bit ASCII code. + // 2 = ASCII An 8-bit byte containing one 7-bit ASCII code. + if (type == 2) { return new ExifAscii(etag, ExifBitConverter.ToAscii(value, encoding), encoding); } - if (type == 3) // 3 = SHORT A 16-bit (2-byte) unsigned integer. + // 3 = SHORT A 16-bit (2-byte) unsigned integer. + if (type == 3) { if (count == 1) { @@ -448,7 +541,8 @@ public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value return new ExifUShortArray(etag, ExifBitConverter.ToUShortArray(value, (int)count, byteOrder)); } - if (type == 4) // 4 = LONG A 32-bit (4-byte) unsigned integer. + // 4 = LONG A 32-bit (4-byte) unsigned integer. + if (type == 4) { if (count == 1) { @@ -458,7 +552,8 @@ public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value return new ExifUIntArray(etag, ExifBitConverter.ToUIntArray(value, (int)count, byteOrder)); } - if (type == 5) // 5 = RATIONAL Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator. + // 5 = RATIONAL Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator. + if (type == 5) { if (count == 1) { @@ -468,12 +563,14 @@ public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value return new ExifURationalArray(etag, ExifBitConverter.ToURationalArray(value, (int)count, byteOrder)); } - if (type == 7) // 7 = UNDEFINED An 8-bit byte that can take any value depending on the field definition. + // 7 = UNDEFINED An 8-bit byte that can take any value depending on the field definition. + if (type == 7) { return new ExifUndefined(etag, value); } - if (type == 9) // 9 = SLONG A 32-bit (4-byte) signed integer (2's complement notation). + // 9 = SLONG A 32-bit (4-byte) signed integer (2's complement notation). + if (type == 9) { if (count == 1) { @@ -483,7 +580,8 @@ public static ExifProperty Get(ushort tag, ushort type, uint count, byte[] value return new ExifSIntArray(etag, ExifBitConverter.ToSIntArray(value, (int)count, byteOrder)); } - if (type == 10) // 10 = SRATIONAL Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator. + // 10 = SRATIONAL Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator. + if (type == 10) { if (count == 1) { diff --git a/src/Umbraco.Core/Media/Exif/ExifTag.cs b/src/Umbraco.Core/Media/Exif/ExifTag.cs index 854100405857..0ffd754836aa 100644 --- a/src/Umbraco.Core/Media/Exif/ExifTag.cs +++ b/src/Umbraco.Core/Media/Exif/ExifTag.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Media.Exif; +namespace Umbraco.Cms.Core.Media.Exif; /// /// Represents the tags associated with exif fields. @@ -246,6 +246,7 @@ internal enum ExifTag // **************************** // JFIF Tags // **************************** + /// /// Represents the JFIF version. /// @@ -305,5 +306,5 @@ internal enum ExifTag /// JFIF thumbnail. The thumbnail will be either a JPEG, /// a 256 color palette bitmap, or a 24-bit RGB bitmap. /// - JFXXThumbnail = IFD.JFXX + 202 + JFXXThumbnail = IFD.JFXX + 202, } diff --git a/src/Umbraco.Core/Media/Exif/ExifTagFactory.cs b/src/Umbraco.Core/Media/Exif/ExifTagFactory.cs index cf054dea4b45..726da925aa24 100644 --- a/src/Umbraco.Core/Media/Exif/ExifTagFactory.cs +++ b/src/Umbraco.Core/Media/Exif/ExifTagFactory.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Media.Exif; +namespace Umbraco.Cms.Core.Media.Exif; internal static class ExifTagFactory { diff --git a/src/Umbraco.Core/Media/Exif/IFD.cs b/src/Umbraco.Core/Media/Exif/IFD.cs index 56ab84579be2..cda3cdcb69b7 100644 --- a/src/Umbraco.Core/Media/Exif/IFD.cs +++ b/src/Umbraco.Core/Media/Exif/IFD.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Media.Exif; +namespace Umbraco.Cms.Core.Media.Exif; /// /// Represents the IFD section containing tags. @@ -13,5 +13,5 @@ internal enum IFD First = 500000, MakerNote = 600000, JFIF = 700000, - JFXX = 800000 + JFXX = 800000, } diff --git a/src/Umbraco.Core/Media/Exif/ImageFileDirectory.cs b/src/Umbraco.Core/Media/Exif/ImageFileDirectory.cs index 35a0538ab090..299e7619f950 100644 --- a/src/Umbraco.Core/Media/Exif/ImageFileDirectory.cs +++ b/src/Umbraco.Core/Media/Exif/ImageFileDirectory.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Media.Exif; +namespace Umbraco.Cms.Core.Media.Exif; /// /// Represents an image file directory. diff --git a/src/Umbraco.Core/Media/Exif/ImageFileDirectoryEntry.cs b/src/Umbraco.Core/Media/Exif/ImageFileDirectoryEntry.cs index da8a8bf77ace..a3863b6a6990 100644 --- a/src/Umbraco.Core/Media/Exif/ImageFileDirectoryEntry.cs +++ b/src/Umbraco.Core/Media/Exif/ImageFileDirectoryEntry.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Media.Exif; +namespace Umbraco.Cms.Core.Media.Exif; /// /// Represents an entry in the image file directory. @@ -97,37 +97,44 @@ public static ImageFileDirectoryEntry FromBytes(byte[] data, uint offset, BitCon /// Type identifier. private static uint GetBaseLength(ushort type) { - if (type == 1 || type == 6) // BYTE and SBYTE + // BYTE and SBYTE + if (type == 1 || type == 6) { return 1; } - if (type == 2 || type == 7) // ASCII and UNDEFINED + // ASCII and UNDEFINED + if (type == 2 || type == 7) { return 1; } - if (type == 3 || type == 8) // SHORT and SSHORT + // SHORT and SSHORT + if (type == 3 || type == 8) { return 2; } - if (type == 4 || type == 9) // LONG and SLONG + // LONG and SLONG + if (type == 4 || type == 9) { return 4; } - if (type == 5 || type == 10) // RATIONAL (2xLONG) and SRATIONAL (2xSLONG) + // RATIONAL (2xLONG) and SRATIONAL (2xSLONG) + if (type == 5 || type == 10) { return 8; } - if (type == 11) // FLOAT + // FLOAT + if (type == 11) { return 4; } - if (type == 12) // DOUBLE + // DOUBLE + if (type == 12) { return 8; } diff --git a/src/Umbraco.Core/Media/Exif/ImageFileFormat.cs b/src/Umbraco.Core/Media/Exif/ImageFileFormat.cs index 0797e78d54fe..fe30c713b255 100644 --- a/src/Umbraco.Core/Media/Exif/ImageFileFormat.cs +++ b/src/Umbraco.Core/Media/Exif/ImageFileFormat.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Media.Exif; +namespace Umbraco.Cms.Core.Media.Exif; /// /// Represents the format of the . @@ -23,5 +23,5 @@ internal enum ImageFileFormat /// /// The file is a SVG File. /// - SVG + SVG, } diff --git a/src/Umbraco.Core/Media/Exif/JFIFEnums.cs b/src/Umbraco.Core/Media/Exif/JFIFEnums.cs index a9d112e3ea77..438d7bf3d4d6 100644 --- a/src/Umbraco.Core/Media/Exif/JFIFEnums.cs +++ b/src/Umbraco.Core/Media/Exif/JFIFEnums.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Media.Exif; +namespace Umbraco.Cms.Core.Media.Exif; /// /// Represents the units for the X and Y densities @@ -19,7 +19,7 @@ internal enum JFIFDensityUnit : byte /// /// XDensity and YDensity are dots per cm. /// - DotsPerCm = 2 + DotsPerCm = 2, } /// @@ -40,5 +40,5 @@ internal enum JFIFExtension : byte /// /// Thumbnail stored using 3 bytes/pixel (24-bit) RGB values. /// - Thumbnail24BitRGB = 0x13 + Thumbnail24BitRGB = 0x13, } diff --git a/src/Umbraco.Core/Media/Exif/JFIFExtendedProperty.cs b/src/Umbraco.Core/Media/Exif/JFIFExtendedProperty.cs index eb8d2ba493a9..71ea89228d90 100644 --- a/src/Umbraco.Core/Media/Exif/JFIFExtendedProperty.cs +++ b/src/Umbraco.Core/Media/Exif/JFIFExtendedProperty.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Media.Exif; +namespace Umbraco.Cms.Core.Media.Exif; /// /// Represents the JFIF version as a 16 bit unsigned integer. (EXIF Specification: SHORT) @@ -34,26 +34,25 @@ public JFIFThumbnailProperty(ExifTag tag, JFIFThumbnail value) : base(tag) => mValue = value; - protected override object _Value - { - get => Value; - set => Value = (JFIFThumbnail)value; - } - public new JFIFThumbnail Value { get => mValue; set => mValue = value; } + protected override object _Value + { + get => Value; + set => Value = (JFIFThumbnail)value; + } + public override ExifInterOperability Interoperability { get { if (mValue.Format == JFIFThumbnail.ImageFormat.BMP24Bit) { - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 1, (uint)mValue.PixelData.Length, - mValue.PixelData); + return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 1, (uint)mValue.PixelData.Length, mValue.PixelData); } if (mValue.Format == JFIFThumbnail.ImageFormat.BMPPalette) @@ -66,8 +65,7 @@ public override ExifInterOperability Interoperability if (mValue.Format == JFIFThumbnail.ImageFormat.JPEG) { - return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 1, (uint)mValue.PixelData.Length, - mValue.PixelData); + return new ExifInterOperability(ExifTagFactory.GetTagID(mTag), 1, (uint)mValue.PixelData.Length, mValue.PixelData); } throw new InvalidOperationException("Unknown thumbnail type."); diff --git a/src/Umbraco.Core/Media/Exif/JPEGExceptions.cs b/src/Umbraco.Core/Media/Exif/JPEGExceptions.cs index a0ba1035f0ee..c44d6d1db0db 100644 --- a/src/Umbraco.Core/Media/Exif/JPEGExceptions.cs +++ b/src/Umbraco.Core/Media/Exif/JPEGExceptions.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Media.Exif; @@ -111,44 +111,44 @@ protected NotValidTIFFileException(SerializationInfo info, StreamingContext cont } /// -/// The exception that is thrown when the format of the TIFF header could not be understood. +/// The exception that is thrown when the length of a section exceeds 64 kB. /// /// [Serializable] -internal class NotValidTIFFHeader : Exception +public class SectionExceeds64KBException : Exception { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public NotValidTIFFHeader() - : base("Not a valid TIFF header.") + public SectionExceeds64KBException() + : base("Section length exceeds 64 kB.") { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The message that describes the error. - public NotValidTIFFHeader(string message) + public SectionExceeds64KBException(string message) : base(message) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The error message that explains the reason for the exception. /// /// The exception that is the cause of the current exception, or a null reference ( /// in Visual Basic) if no inner exception is specified. /// - public NotValidTIFFHeader(string message, Exception innerException) + public SectionExceeds64KBException(string message, Exception innerException) : base(message, innerException) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The that holds the serialized object @@ -158,51 +158,51 @@ public NotValidTIFFHeader(string message, Exception innerException) /// The that contains contextual /// information about the source or destination. /// - protected NotValidTIFFHeader(SerializationInfo info, StreamingContext context) + protected SectionExceeds64KBException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// -/// The exception that is thrown when the length of a section exceeds 64 kB. +/// The exception that is thrown when the format of the TIFF header could not be understood. /// /// [Serializable] -public class SectionExceeds64KBException : Exception +internal class NotValidTIFFHeader : Exception { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public SectionExceeds64KBException() - : base("Section length exceeds 64 kB.") + public NotValidTIFFHeader() + : base("Not a valid TIFF header.") { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The message that describes the error. - public SectionExceeds64KBException(string message) + public NotValidTIFFHeader(string message) : base(message) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The error message that explains the reason for the exception. /// /// The exception that is the cause of the current exception, or a null reference ( /// in Visual Basic) if no inner exception is specified. /// - public SectionExceeds64KBException(string message, Exception innerException) + public NotValidTIFFHeader(string message, Exception innerException) : base(message, innerException) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The that holds the serialized object @@ -212,7 +212,7 @@ public SectionExceeds64KBException(string message, Exception innerException) /// The that contains contextual /// information about the source or destination. /// - protected SectionExceeds64KBException(SerializationInfo info, StreamingContext context) + protected NotValidTIFFHeader(SerializationInfo info, StreamingContext context) : base(info, context) { } diff --git a/src/Umbraco.Core/Media/Exif/JPEGMarker.cs b/src/Umbraco.Core/Media/Exif/JPEGMarker.cs index 5235b101ece9..3912d87e822d 100644 --- a/src/Umbraco.Core/Media/Exif/JPEGMarker.cs +++ b/src/Umbraco.Core/Media/Exif/JPEGMarker.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Media.Exif; +namespace Umbraco.Cms.Core.Media.Exif; /// /// Represents a JPEG marker byte. @@ -91,5 +91,5 @@ internal enum JPEGMarker : byte COM = 0xfe, // Temporary - TEM = 0x01 + TEM = 0x01, } diff --git a/src/Umbraco.Core/Media/IEmbedProvider.cs b/src/Umbraco.Core/Media/IEmbedProvider.cs index ec2a9b7dfe89..6760243ce6f1 100644 --- a/src/Umbraco.Core/Media/IEmbedProvider.cs +++ b/src/Umbraco.Core/Media/IEmbedProvider.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Media; +namespace Umbraco.Cms.Core.Media; public interface IEmbedProvider { diff --git a/src/Umbraco.Core/Media/TypeDetector/JpegDetector.cs b/src/Umbraco.Core/Media/TypeDetector/JpegDetector.cs index 7edb69609f0f..e89d8e159d3a 100644 --- a/src/Umbraco.Core/Media/TypeDetector/JpegDetector.cs +++ b/src/Umbraco.Core/Media/TypeDetector/JpegDetector.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Media.TypeDetector; +namespace Umbraco.Cms.Core.Media.TypeDetector; public class JpegDetector : RasterizedTypeDetector { diff --git a/src/Umbraco.Core/Models/AuditEntry.cs b/src/Umbraco.Core/Models/AuditEntry.cs index 6d15e9ef316b..9d1b4dfcef04 100644 --- a/src/Umbraco.Core/Models/AuditEntry.cs +++ b/src/Umbraco.Core/Models/AuditEntry.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/AuditItem.cs b/src/Umbraco.Core/Models/AuditItem.cs index 0afdc5a0500f..995875f8c61f 100644 --- a/src/Umbraco.Core/Models/AuditItem.cs +++ b/src/Umbraco.Core/Models/AuditItem.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/AuditType.cs b/src/Umbraco.Core/Models/AuditType.cs index c421ffd7ea5a..6a3e528273bf 100644 --- a/src/Umbraco.Core/Models/AuditType.cs +++ b/src/Umbraco.Core/Models/AuditType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; /// /// Defines audit types. @@ -123,5 +123,5 @@ public enum AuditType /// /// Content version preventCleanup set to false /// - ContentVersionEnableCleanup + ContentVersionEnableCleanup, } diff --git a/src/Umbraco.Core/Models/BackOfficeTour.cs b/src/Umbraco.Core/Models/BackOfficeTour.cs index 43770e6ad4c3..a7a9d3a5c3e4 100644 --- a/src/Umbraco.Core/Models/BackOfficeTour.cs +++ b/src/Umbraco.Core/Models/BackOfficeTour.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; @@ -10,24 +10,33 @@ public class BackOfficeTour { public BackOfficeTour() => RequiredSections = new List(); - [DataMember(Name = "name")] public string? Name { get; set; } + [DataMember(Name = "name")] + public string? Name { get; set; } - [DataMember(Name = "alias")] public string Alias { get; set; } = null!; + [DataMember(Name = "alias")] + public string Alias { get; set; } = null!; - [DataMember(Name = "group")] public string? Group { get; set; } + [DataMember(Name = "group")] + public string? Group { get; set; } - [DataMember(Name = "groupOrder")] public int GroupOrder { get; set; } + [DataMember(Name = "groupOrder")] + public int GroupOrder { get; set; } - [DataMember(Name = "hidden")] public bool Hidden { get; set; } + [DataMember(Name = "hidden")] + public bool Hidden { get; set; } - [DataMember(Name = "allowDisable")] public bool AllowDisable { get; set; } + [DataMember(Name = "allowDisable")] + public bool AllowDisable { get; set; } [DataMember(Name = "requiredSections")] public List RequiredSections { get; set; } - [DataMember(Name = "steps")] public BackOfficeTourStep[]? Steps { get; set; } + [DataMember(Name = "steps")] + public BackOfficeTourStep[]? Steps { get; set; } - [DataMember(Name = "culture")] public string? Culture { get; set; } + [DataMember(Name = "culture")] + public string? Culture { get; set; } - [DataMember(Name = "contentType")] public string? ContentType { get; set; } + [DataMember(Name = "contentType")] + public string? ContentType { get; set; } } diff --git a/src/Umbraco.Core/Models/BackOfficeTourFile.cs b/src/Umbraco.Core/Models/BackOfficeTourFile.cs index e73946f5af8f..bc0a5cea3bc5 100644 --- a/src/Umbraco.Core/Models/BackOfficeTourFile.cs +++ b/src/Umbraco.Core/Models/BackOfficeTourFile.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; @@ -25,5 +25,6 @@ public class BackOfficeTourFile [DataMember(Name = "pluginName")] public string? PluginName { get; set; } - [DataMember(Name = "tours")] public IEnumerable Tours { get; set; } + [DataMember(Name = "tours")] + public IEnumerable Tours { get; set; } } diff --git a/src/Umbraco.Core/Models/BackOfficeTourStep.cs b/src/Umbraco.Core/Models/BackOfficeTourStep.cs index 377bc84b6ac5..296dcf8bc400 100644 --- a/src/Umbraco.Core/Models/BackOfficeTourStep.cs +++ b/src/Umbraco.Core/Models/BackOfficeTourStep.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; @@ -8,24 +8,32 @@ namespace Umbraco.Cms.Core.Models; [DataContract(Name = "step", Namespace = "")] public class BackOfficeTourStep { - [DataMember(Name = "title")] public string? Title { get; set; } + [DataMember(Name = "title")] + public string? Title { get; set; } - [DataMember(Name = "content")] public string? Content { get; set; } + [DataMember(Name = "content")] + public string? Content { get; set; } - [DataMember(Name = "type")] public string? Type { get; set; } + [DataMember(Name = "type")] + public string? Type { get; set; } - [DataMember(Name = "element")] public string? Element { get; set; } + [DataMember(Name = "element")] + public string? Element { get; set; } [DataMember(Name = "elementPreventClick")] public bool ElementPreventClick { get; set; } - [DataMember(Name = "backdropOpacity")] public float? BackdropOpacity { get; set; } + [DataMember(Name = "backdropOpacity")] + public float? BackdropOpacity { get; set; } - [DataMember(Name = "event")] public string? Event { get; set; } + [DataMember(Name = "event")] + public string? Event { get; set; } - [DataMember(Name = "view")] public string? View { get; set; } + [DataMember(Name = "view")] + public string? View { get; set; } - [DataMember(Name = "eventElement")] public string? EventElement { get; set; } + [DataMember(Name = "eventElement")] + public string? EventElement { get; set; } [DataMember(Name = "customProperties")] public object? CustomProperties { get; set; } diff --git a/src/Umbraco.Core/Models/Blocks/BlockListItem.cs b/src/Umbraco.Core/Models/Blocks/BlockListItem.cs index d159c490b231..ee158f9bd8c9 100644 --- a/src/Umbraco.Core/Models/Blocks/BlockListItem.cs +++ b/src/Umbraco.Core/Models/Blocks/BlockListItem.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Cms.Core.Models.Blocks; diff --git a/src/Umbraco.Core/Models/Blocks/BlockListModel.cs b/src/Umbraco.Core/Models/Blocks/BlockListModel.cs index 359c269164ce..79afb67d40d2 100644 --- a/src/Umbraco.Core/Models/Blocks/BlockListModel.cs +++ b/src/Umbraco.Core/Models/Blocks/BlockListModel.cs @@ -1,4 +1,4 @@ -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.Blocks; @@ -11,19 +11,19 @@ namespace Umbraco.Cms.Core.Models.Blocks; public class BlockListModel : ReadOnlyCollection { /// - /// Prevents a default instance of the class from being created. + /// Initializes a new instance of the class. /// - private BlockListModel() - : this(new List()) + /// The list to wrap. + public BlockListModel(IList list) + : base(list) { } /// - /// Initializes a new instance of the class. + /// Prevents a default instance of the class from being created. /// - /// The list to wrap. - public BlockListModel(IList list) - : base(list) + private BlockListModel() + : this(new List()) { } diff --git a/src/Umbraco.Core/Models/Blocks/ContentAndSettingsReference.cs b/src/Umbraco.Core/Models/Blocks/ContentAndSettingsReference.cs index 0112e2875e21..96a81641fa5c 100644 --- a/src/Umbraco.Core/Models/Blocks/ContentAndSettingsReference.cs +++ b/src/Umbraco.Core/Models/Blocks/ContentAndSettingsReference.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Blocks; +namespace Umbraco.Cms.Core.Models.Blocks; public struct ContentAndSettingsReference : IEquatable { @@ -12,19 +12,21 @@ public ContentAndSettingsReference(Udi? contentUdi, Udi? settingsUdi) public Udi? SettingsUdi { get; } + public static bool operator ==(ContentAndSettingsReference left, ContentAndSettingsReference right) => + left.Equals(right); + public override bool Equals(object? obj) => obj is ContentAndSettingsReference reference && Equals(reference); public bool Equals(ContentAndSettingsReference other) => other != null - && EqualityComparer.Default.Equals(ContentUdi, + && EqualityComparer.Default.Equals( + ContentUdi, other.ContentUdi) - && EqualityComparer.Default.Equals(SettingsUdi, + && EqualityComparer.Default.Equals( + SettingsUdi, other.SettingsUdi); public override int GetHashCode() => (ContentUdi, SettingsUdi).GetHashCode(); - public static bool operator ==(ContentAndSettingsReference left, ContentAndSettingsReference right) => - left.Equals(right); - public static bool operator !=(ContentAndSettingsReference left, ContentAndSettingsReference right) => !(left == right); } diff --git a/src/Umbraco.Core/Models/Blocks/IBlockReference.cs b/src/Umbraco.Core/Models/Blocks/IBlockReference.cs index cdd1c3958430..44533407d243 100644 --- a/src/Umbraco.Core/Models/Blocks/IBlockReference.cs +++ b/src/Umbraco.Core/Models/Blocks/IBlockReference.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Blocks; +namespace Umbraco.Cms.Core.Models.Blocks; /// /// Represents a data item reference for a Block Editor implementation. diff --git a/src/Umbraco.Core/Models/Consent.cs b/src/Umbraco.Core/Models/Consent.cs index 6a1f3f4c8acc..e71f040ba8e9 100644 --- a/src/Umbraco.Core/Models/Consent.cs +++ b/src/Umbraco.Core/Models/Consent.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; @@ -78,6 +78,7 @@ public string? Action public ConsentState State { get => _state; + // note: we probably should validate the state here, but since the // enum is [Flags] with many combinations, this could be expensive set => SetPropertyValueAndDetectChanges(value, ref _state, nameof(State)); diff --git a/src/Umbraco.Core/Models/ConsentExtensions.cs b/src/Umbraco.Core/Models/ConsentExtensions.cs index 1e869eadeefc..1dc6cadde803 100644 --- a/src/Umbraco.Core/Models/ConsentExtensions.cs +++ b/src/Umbraco.Core/Models/ConsentExtensions.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Extensions; diff --git a/src/Umbraco.Core/Models/ConsentState.cs b/src/Umbraco.Core/Models/ConsentState.cs index 368ddb9d8ff5..8a6846b28c42 100644 --- a/src/Umbraco.Core/Models/ConsentState.cs +++ b/src/Umbraco.Core/Models/ConsentState.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; /// /// Represents the state of a consent. @@ -8,8 +8,8 @@ public enum ConsentState // : int { // note - this is a [Flags] enumeration // on can create detailed flags such as: - //GrantedOptIn = Granted | 0x0001 - //GrandedByForce = Granted | 0x0002 + // GrantedOptIn = Granted | 0x0001 + // GrandedByForce = Granted | 0x0002 // // 16 situations for each Pending/Granted/Revoked should be ok @@ -31,5 +31,5 @@ public enum ConsentState // : int /// /// Consent has been revoked. /// - Revoked = 0x40000 + Revoked = 0x40000, } diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index abbdcd35345b..4e251a323ec8 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -52,8 +52,7 @@ public Content(string name, IContent parent, IContentType contentType, int userI /// ContentType for the current Content object /// Collection of properties /// An optional culture. - public Content(string name, IContent parent, IContentType contentType, PropertyCollection properties, - string? culture = null) + public Content(string name, IContent parent, IContentType contentType, PropertyCollection properties, string? culture = null) : base(name, parent, contentType, properties, culture) { if (contentType == null) @@ -100,8 +99,7 @@ public Content(string name, int parentId, IContentType contentType, int userId, /// ContentType for the current Content object /// Collection of properties /// An optional culture. - public Content(string? name, int parentId, IContentType? contentType, PropertyCollection properties, - string? culture = null) + public Content(string? name, int parentId, IContentType? contentType, PropertyCollection properties, string? culture = null) : base(name, parentId, contentType, properties, culture) { if (contentType == null) @@ -170,7 +168,8 @@ public PublishedState PublishedState } } - [IgnoreDataMember] public bool Edited { get; set; } + [IgnoreDataMember] + public bool Edited { get; set; } /// [IgnoreDataMember] @@ -202,6 +201,7 @@ public IEnumerable? EditedCultures /// public bool IsCulturePublished(string culture) + // just check _publishInfos // a non-available culture could not become published anyways => !culture.IsNullOrWhiteSpace() && _publishInfos != null && _publishInfos.ContainsKey(culture); @@ -227,6 +227,7 @@ public ContentCultureInfosCollection? PublishCultureInfos _publishInfos.CollectionChanged += PublishNamesCollectionChanged; return _publishInfos; } + set { if (_publishInfos != null) @@ -284,9 +285,11 @@ public ContentCultureInfosCollection? PublishCultureInfos return _publishInfos.TryGetValue(culture, out ContentCultureInfos infos) ? infos.Date : null; } - [IgnoreDataMember] public int PublishedVersionId { get; set; } + [IgnoreDataMember] + public int PublishedVersionId { get; set; } - [DataMember] public bool Blueprint { get; set; } + [DataMember] + public bool Blueprint { get; set; } public override void ResetWereDirtyProperties() { @@ -306,20 +309,17 @@ public override void ResetDirtyProperties(bool rememberDirty) _currentPublishCultureChanges.addedCultures == null || _currentPublishCultureChanges.addedCultures.Count == 0 ? null - : new HashSet(_currentPublishCultureChanges.addedCultures, - StringComparer.InvariantCultureIgnoreCase); + : new HashSet(_currentPublishCultureChanges.addedCultures, StringComparer.InvariantCultureIgnoreCase); _previousPublishCultureChanges.removedCultures = _currentPublishCultureChanges.removedCultures == null || _currentPublishCultureChanges.removedCultures.Count == 0 ? null - : new HashSet(_currentPublishCultureChanges.removedCultures, - StringComparer.InvariantCultureIgnoreCase); + : new HashSet(_currentPublishCultureChanges.removedCultures, StringComparer.InvariantCultureIgnoreCase); _previousPublishCultureChanges.updatedCultures = _currentPublishCultureChanges.updatedCultures == null || _currentPublishCultureChanges.updatedCultures.Count == 0 ? null - : new HashSet(_currentPublishCultureChanges.updatedCultures, - StringComparer.InvariantCultureIgnoreCase); + : new HashSet(_currentPublishCultureChanges.updatedCultures, StringComparer.InvariantCultureIgnoreCase); } else { @@ -350,7 +350,7 @@ public override void ResetDirtyProperties(bool rememberDirty) /// Overridden to check special keys. public override bool IsPropertyDirty(string propertyName) { - //Special check here since we want to check if the request is for changed cultures + // Special check here since we want to check if the request is for changed cultures if (propertyName.StartsWith(ChangeTrackingPrefix.PublishedCulture)) { var culture = propertyName.TrimStart(ChangeTrackingPrefix.PublishedCulture); @@ -376,7 +376,7 @@ public override bool IsPropertyDirty(string propertyName) /// Overridden to check special keys. public override bool WasPropertyDirty(string propertyName) { - //Special check here since we want to check if the request is for changed cultures + // Special check here since we want to check if the request is for changed cultures if (propertyName.StartsWith(ChangeTrackingPrefix.PublishedCulture)) { var culture = propertyName.TrimStart(ChangeTrackingPrefix.PublishedCulture); @@ -424,13 +424,13 @@ private void PublishNamesCollectionChanged(object? sender, NotifyCollectionChang { OnPropertyChanged(nameof(PublishCultureInfos)); - //we don't need to handle other actions, only add/remove, however we could implement Replace and track updated cultures in _updatedCultures too - //which would allows us to continue doing WasCulturePublished, but don't think we need it anymore + // we don't need to handle other actions, only add/remove, however we could implement Replace and track updated cultures in _updatedCultures too + // which would allows us to continue doing WasCulturePublished, but don't think we need it anymore switch (e.Action) { case NotifyCollectionChangedAction.Add: { - ContentCultureInfos cultureInfo = e.NewItems?.Cast().First(); + ContentCultureInfos? cultureInfo = e.NewItems?.Cast().First(); if (_currentPublishCultureChanges.addedCultures == null) { _currentPublishCultureChanges.addedCultures = @@ -452,10 +452,11 @@ private void PublishNamesCollectionChanged(object? sender, NotifyCollectionChang break; } + case NotifyCollectionChangedAction.Remove: { - //remove listening for changes - ContentCultureInfos cultureInfo = e.OldItems?.Cast().First(); + // Remove listening for changes + ContentCultureInfos? cultureInfo = e.OldItems?.Cast().First(); if (_currentPublishCultureChanges.removedCultures == null) { _currentPublishCultureChanges.removedCultures = @@ -471,10 +472,11 @@ private void PublishNamesCollectionChanged(object? sender, NotifyCollectionChang break; } + case NotifyCollectionChangedAction.Replace: { - //replace occurs when an Update occurs - ContentCultureInfos cultureInfo = e.NewItems?.Cast().First(); + // Replace occurs when an Update occurs + ContentCultureInfos? cultureInfo = e.NewItems?.Cast().First(); if (_currentPublishCultureChanges.updatedCultures == null) { _currentPublishCultureChanges.updatedCultures = @@ -527,18 +529,20 @@ protected override void PerformDeepClone(object clone) var clonedContent = (Content)clone; - //fixme - need to reset change tracking bits + // fixme - need to reset change tracking bits - //if culture infos exist then deal with event bindings + // if culture infos exist then deal with event bindings if (clonedContent._publishInfos != null) { - clonedContent._publishInfos.ClearCollectionChangedEvents(); //clear this event handler if any - clonedContent._publishInfos = - (ContentCultureInfosCollection?)_publishInfos?.DeepClone(); //manually deep clone + // Clear this event handler if any + clonedContent._publishInfos.ClearCollectionChangedEvents(); + + // Manually deep clone + clonedContent._publishInfos = (ContentCultureInfosCollection?)_publishInfos?.DeepClone(); if (clonedContent._publishInfos is not null) { - clonedContent._publishInfos.CollectionChanged += - clonedContent.PublishNamesCollectionChanged; //re-assign correct event handler + // Re-assign correct event handler + clonedContent._publishInfos.CollectionChanged += clonedContent.PublishNamesCollectionChanged; } } diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index cc0a2c43a1da..65ec36042b9c 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -22,8 +22,7 @@ public abstract class ContentBase : TreeEntityBase, IContentBase /// /// Initializes a new instance of the class. /// - protected ContentBase(string? name, int parentId, IContentTypeComposition? contentType, - IPropertyCollection properties, string? culture = null) + protected ContentBase(string? name, int parentId, IContentTypeComposition? contentType, IPropertyCollection properties, string? culture = null) : this(name, contentType, properties, culture) { if (parentId == 0) @@ -37,8 +36,7 @@ protected ContentBase(string? name, int parentId, IContentTypeComposition? conte /// /// Initializes a new instance of the class. /// - protected ContentBase(string? name, IContentBase? parent, IContentTypeComposition contentType, - IPropertyCollection properties, string? culture = null) + protected ContentBase(string? name, IContentBase? parent, IContentTypeComposition contentType, IPropertyCollection properties, string? culture = null) : this(name, contentType, properties, culture) { if (parent == null) @@ -49,8 +47,7 @@ protected ContentBase(string? name, IContentBase? parent, IContentTypeCompositio SetParent(parent); } - private ContentBase(string? name, IContentTypeComposition? contentType, IPropertyCollection properties, - string? culture = null) + private ContentBase(string? name, IContentTypeComposition? contentType, IPropertyCollection properties, string? culture = null) { ContentType = contentType?.ToSimple() ?? throw new ArgumentNullException(nameof(contentType)); @@ -64,15 +61,16 @@ private ContentBase(string? name, IContentTypeComposition? contentType, IPropert _properties = properties ?? throw new ArgumentNullException(nameof(properties)); _properties.EnsurePropertyTypes(contentType.CompositionPropertyTypes); - //track all property types on this content type, these can never change during the lifetime of this single instance - //there is no real extra memory overhead of doing this since these property types are already cached on this object via the - //properties already. + // track all property types on this content type, these can never change during the lifetime of this single instance + // there is no real extra memory overhead of doing this since these property types are already cached on this object via the + // properties already. AllPropertyTypes = new List(contentType.CompositionPropertyTypes); } internal IReadOnlyList AllPropertyTypes { get; } - [IgnoreDataMember] public ISimpleContentType ContentType { get; private set; } + [IgnoreDataMember] + public ISimpleContentType ContentType { get; private set; } /// /// Id of the user who wrote/updated this entity @@ -84,7 +82,8 @@ public int WriterId set => SetPropertyValueAndDetectChanges(value, ref _writerId, nameof(WriterId)); } - [IgnoreDataMember] public int VersionId { get; set; } + [IgnoreDataMember] + public int VersionId { get; set; } /// /// Integer Id of the default ContentType @@ -94,8 +93,8 @@ public int ContentTypeId { get { - //There will be cases where this has not been updated to reflect the true content type ID. - //This will occur when inserting new content. + // There will be cases where this has not been updated to reflect the true content type ID. + // This will occur when inserting new content. if (_contentTypeId == 0 && ContentType != null) { _contentTypeId = ContentType.Id; @@ -148,29 +147,29 @@ protected override void PerformDeepClone(object clone) var clonedContent = (ContentBase)clone; - //need to manually clone this since it's not settable + // Need to manually clone this since it's not settable clonedContent.ContentType = ContentType; - //if culture infos exist then deal with event bindings + // If culture infos exist then deal with event bindings if (clonedContent._cultureInfos != null) { - clonedContent._cultureInfos.ClearCollectionChangedEvents(); //clear this event handler if any + clonedContent._cultureInfos.ClearCollectionChangedEvents(); // clear this event handler if any clonedContent._cultureInfos = - (ContentCultureInfosCollection?)_cultureInfos?.DeepClone(); //manually deep clone + (ContentCultureInfosCollection?)_cultureInfos?.DeepClone(); // manually deep clone if (clonedContent._cultureInfos is not null) { clonedContent._cultureInfos.CollectionChanged += - clonedContent.CultureInfosCollectionChanged; //re-assign correct event handler + clonedContent.CultureInfosCollectionChanged; // re-assign correct event handler } } - //if properties exist then deal with event bindings + // if properties exist then deal with event bindings if (clonedContent._properties != null) { - clonedContent._properties.ClearCollectionChangedEvents(); //clear this event handler if any - clonedContent._properties = (IPropertyCollection)_properties.DeepClone(); //manually deep clone + clonedContent._properties.ClearCollectionChangedEvents(); // clear this event handler if any + clonedContent._properties = (IPropertyCollection)_properties.DeepClone(); // manually deep clone clonedContent._properties.CollectionChanged += - clonedContent.PropertiesChanged; //re-assign correct event handler + clonedContent.PropertiesChanged; // re-assign correct event handler } clonedContent._currentCultureChanges.updatedCultures = null; @@ -233,6 +232,7 @@ public ContentCultureInfosCollection? CultureInfos _cultureInfos.CollectionChanged += CultureInfosCollectionChanged; return _cultureInfos; } + set { if (_cultureInfos != null) @@ -328,8 +328,7 @@ private void ClearCultureInfo(string culture) if (string.IsNullOrWhiteSpace(culture)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", - nameof(culture)); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(culture)); } if (_cultureInfos == null) @@ -355,7 +354,7 @@ private void CultureInfosCollectionChanged(object? sender, NotifyCollectionChang { case NotifyCollectionChangedAction.Add: { - ContentCultureInfos cultureInfo = e.NewItems?.Cast().First(); + ContentCultureInfos? cultureInfo = e.NewItems?.Cast().First(); if (_currentCultureChanges.addedCultures == null) { _currentCultureChanges.addedCultures = @@ -377,10 +376,11 @@ private void CultureInfosCollectionChanged(object? sender, NotifyCollectionChang break; } + case NotifyCollectionChangedAction.Remove: { - //remove listening for changes - ContentCultureInfos cultureInfo = e.OldItems?.Cast().First(); + // Remove listening for changes + ContentCultureInfos? cultureInfo = e.OldItems?.Cast().First(); if (_currentCultureChanges.removedCultures == null) { _currentCultureChanges.removedCultures = @@ -398,8 +398,8 @@ private void CultureInfosCollectionChanged(object? sender, NotifyCollectionChang } case NotifyCollectionChangedAction.Replace: { - //replace occurs when an Update occurs - ContentCultureInfos cultureInfo = e.NewItems?.Cast().First(); + // Replace occurs when an Update occurs + ContentCultureInfos? cultureInfo = e.NewItems?.Cast().First(); if (_currentCultureChanges.updatedCultures == null) { _currentCultureChanges.updatedCultures = @@ -425,17 +425,15 @@ public bool HasProperty(string propertyTypeAlias) => Properties.Contains(propertyTypeAlias); /// - public object? GetValue(string propertyTypeAlias, string? culture = null, string? segment = null, - bool published = false) => - Properties.TryGetValue(propertyTypeAlias, out IProperty property) + public object? GetValue(string propertyTypeAlias, string? culture = null, string? segment = null, bool published = false) => + Properties.TryGetValue(propertyTypeAlias, out IProperty? property) ? property?.GetValue(culture, segment, published) : null; /// - public TValue? GetValue(string propertyTypeAlias, string? culture = null, string? segment = null, - bool published = false) + public TValue? GetValue(string propertyTypeAlias, string? culture = null, string? segment = null, bool published = false) { - if (!Properties.TryGetValue(propertyTypeAlias, out IProperty property)) + if (!Properties.TryGetValue(propertyTypeAlias, out IProperty? property)) { return default; } @@ -449,7 +447,7 @@ public bool HasProperty(string propertyTypeAlias) /// public void SetValue(string propertyTypeAlias, object? value, string? culture = null, string? segment = null) { - if (!Properties.TryGetValue(propertyTypeAlias, out IProperty property)) + if (!Properties.TryGetValue(propertyTypeAlias, out IProperty? property)) { throw new InvalidOperationException( $"No PropertyType exists with the supplied alias \"{propertyTypeAlias}\"."); @@ -457,7 +455,7 @@ public void SetValue(string propertyTypeAlias, object? value, string? culture = property?.SetValue(value, culture, segment); - //bump the culture to be flagged for updating + // bump the culture to be flagged for updating this.TouchCulture(culture); } @@ -484,17 +482,20 @@ public override void ResetDirtyProperties(bool rememberDirty) _previousCultureChanges.addedCultures = _currentCultureChanges.addedCultures == null || _currentCultureChanges.addedCultures.Count == 0 ? null - : new HashSet(_currentCultureChanges.addedCultures, + : new HashSet( + _currentCultureChanges.addedCultures, StringComparer.InvariantCultureIgnoreCase); _previousCultureChanges.removedCultures = _currentCultureChanges.removedCultures == null || _currentCultureChanges.removedCultures.Count == 0 ? null - : new HashSet(_currentCultureChanges.removedCultures, + : new HashSet( + _currentCultureChanges.removedCultures, StringComparer.InvariantCultureIgnoreCase); _previousCultureChanges.updatedCultures = _currentCultureChanges.updatedCultures == null || _currentCultureChanges.updatedCultures.Count == 0 ? null - : new HashSet(_currentCultureChanges.updatedCultures, + : new HashSet( + _currentCultureChanges.updatedCultures, StringComparer.InvariantCultureIgnoreCase); } else @@ -553,7 +554,7 @@ public override bool IsPropertyDirty(string propertyName) return true; } - //Special check here since we want to check if the request is for changed cultures + // Special check here since we want to check if the request is for changed cultures if (propertyName.StartsWith(ChangeTrackingPrefix.AddedCulture)) { var culture = propertyName.TrimStart(ChangeTrackingPrefix.AddedCulture); @@ -584,7 +585,7 @@ public override bool WasPropertyDirty(string propertyName) return true; } - //Special check here since we want to check if the request is for changed cultures + // Special check here since we want to check if the request is for changed cultures if (propertyName.StartsWith(ChangeTrackingPrefix.AddedCulture)) { var culture = propertyName.TrimStart(ChangeTrackingPrefix.AddedCulture); diff --git a/src/Umbraco.Core/Models/ContentBaseExtensions.cs b/src/Umbraco.Core/Models/ContentBaseExtensions.cs index 40642161aa9e..7ea4884359aa 100644 --- a/src/Umbraco.Core/Models/ContentBaseExtensions.cs +++ b/src/Umbraco.Core/Models/ContentBaseExtensions.cs @@ -8,7 +8,7 @@ namespace Umbraco.Extensions; /// public static class ContentBaseExtensions { - private static DefaultUrlSegmentProvider? s_defaultUrlSegmentProvider; + private static DefaultUrlSegmentProvider? defaultUrlSegmentProvider; /// /// Gets the URL segment for a specified content and culture. @@ -34,12 +34,12 @@ public static class ContentBaseExtensions var url = urlSegmentProviders.Select(p => p.GetUrlSegment(content, culture)).FirstOrDefault(u => u != null); if (url == null) { - if (s_defaultUrlSegmentProvider == null) + if (defaultUrlSegmentProvider == null) { - s_defaultUrlSegmentProvider = new DefaultUrlSegmentProvider(shortStringHelper); + defaultUrlSegmentProvider = new DefaultUrlSegmentProvider(shortStringHelper); } - url = s_defaultUrlSegmentProvider.GetUrlSegment(content, culture); // be safe + url = defaultUrlSegmentProvider.GetUrlSegment(content, culture); // be safe } return url; diff --git a/src/Umbraco.Core/Models/ContentCultureInfos.cs b/src/Umbraco.Core/Models/ContentCultureInfos.cs index eb6776dde2b2..8975c1fc586f 100644 --- a/src/Umbraco.Core/Models/ContentCultureInfos.cs +++ b/src/Umbraco.Core/Models/ContentCultureInfos.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; @@ -22,7 +22,8 @@ public ContentCultureInfos(string culture) if (string.IsNullOrWhiteSpace(culture)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(culture)); } diff --git a/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs b/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs index 74df246a3485..cf8a2f03288a 100644 --- a/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs +++ b/src/Umbraco.Core/Models/ContentCultureInfosCollection.cs @@ -1,4 +1,4 @@ -using System.Collections.Specialized; +using System.Collections.Specialized; using Umbraco.Cms.Core.Collections; namespace Umbraco.Cms.Core.Models; @@ -43,7 +43,8 @@ public void AddOrUpdate(string culture, string? name, DateTime date) if (string.IsNullOrWhiteSpace(culture)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(culture)); } @@ -58,7 +59,7 @@ public void AddOrUpdate(string culture, string? name, DateTime date) } else { - Add(new ContentCultureInfos(culture) {Name = name, Date = date}); + Add(new ContentCultureInfos(culture) { Name = name, Date = date }); } } } diff --git a/src/Umbraco.Core/Models/ContentDataIntegrityReport.cs b/src/Umbraco.Core/Models/ContentDataIntegrityReport.cs index 7940c0430765..f4ad0b0dfcef 100644 --- a/src/Umbraco.Core/Models/ContentDataIntegrityReport.cs +++ b/src/Umbraco.Core/Models/ContentDataIntegrityReport.cs @@ -1,7 +1,10 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; public class ContentDataIntegrityReport { + public ContentDataIntegrityReport(IReadOnlyDictionary detectedIssues) => + DetectedIssues = detectedIssues; + public enum IssueType { /// @@ -27,12 +30,9 @@ public enum IssueType /// /// The item's path does not have it's parent Id as the 2nd last entry /// - InvalidPathByParentId + InvalidPathByParentId, } - public ContentDataIntegrityReport(IReadOnlyDictionary detectedIssues) => - DetectedIssues = detectedIssues; - public bool Ok => DetectedIssues.Count == 0 || DetectedIssues.Count == DetectedIssues.Values.Count(x => x.Fixed); public IReadOnlyDictionary DetectedIssues { get; } diff --git a/src/Umbraco.Core/Models/ContentDataIntegrityReportEntry.cs b/src/Umbraco.Core/Models/ContentDataIntegrityReportEntry.cs index ed80ff0ea063..fe0ebce5492d 100644 --- a/src/Umbraco.Core/Models/ContentDataIntegrityReportEntry.cs +++ b/src/Umbraco.Core/Models/ContentDataIntegrityReportEntry.cs @@ -1,9 +1,10 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; public class ContentDataIntegrityReportEntry { public ContentDataIntegrityReportEntry(ContentDataIntegrityReport.IssueType issueType) => IssueType = issueType; public ContentDataIntegrityReport.IssueType IssueType { get; } + public bool Fixed { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentDataIntegrityReportOptions.cs b/src/Umbraco.Core/Models/ContentDataIntegrityReportOptions.cs index df67854bae23..40657069ffcb 100644 --- a/src/Umbraco.Core/Models/ContentDataIntegrityReportOptions.cs +++ b/src/Umbraco.Core/Models/ContentDataIntegrityReportOptions.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; public class ContentDataIntegrityReportOptions { diff --git a/src/Umbraco.Core/Models/ContentEditing/AssignedContentPermissions.cs b/src/Umbraco.Core/Models/ContentEditing/AssignedContentPermissions.cs index fe5c57071a80..18229d2124e9 100644 --- a/src/Umbraco.Core/Models/ContentEditing/AssignedContentPermissions.cs +++ b/src/Umbraco.Core/Models/ContentEditing/AssignedContentPermissions.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/AssignedUserGroupPermissions.cs b/src/Umbraco.Core/Models/ContentEditing/AssignedUserGroupPermissions.cs index 287e8eb484d5..867784d19df2 100644 --- a/src/Umbraco.Core/Models/ContentEditing/AssignedUserGroupPermissions.cs +++ b/src/Umbraco.Core/Models/ContentEditing/AssignedUserGroupPermissions.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/AuditLog.cs b/src/Umbraco.Core/Models/ContentEditing/AuditLog.cs index 3de1348560dc..e7b744bd5907 100644 --- a/src/Umbraco.Core/Models/ContentEditing/AuditLog.cs +++ b/src/Umbraco.Core/Models/ContentEditing/AuditLog.cs @@ -1,25 +1,34 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "auditLog", Namespace = "")] public class AuditLog { - [DataMember(Name = "userId")] public int UserId { get; set; } + [DataMember(Name = "userId")] + public int UserId { get; set; } - [DataMember(Name = "userName")] public string? UserName { get; set; } + [DataMember(Name = "userName")] + public string? UserName { get; set; } - [DataMember(Name = "userAvatars")] public string[]? UserAvatars { get; set; } + [DataMember(Name = "userAvatars")] + public string[]? UserAvatars { get; set; } - [DataMember(Name = "nodeId")] public int NodeId { get; set; } + [DataMember(Name = "nodeId")] + public int NodeId { get; set; } - [DataMember(Name = "timestamp")] public DateTime Timestamp { get; set; } + [DataMember(Name = "timestamp")] + public DateTime Timestamp { get; set; } - [DataMember(Name = "logType")] public string? LogType { get; set; } + [DataMember(Name = "logType")] + public string? LogType { get; set; } - [DataMember(Name = "entityType")] public string? EntityType { get; set; } + [DataMember(Name = "entityType")] + public string? EntityType { get; set; } - [DataMember(Name = "comment")] public string? Comment { get; set; } + [DataMember(Name = "comment")] + public string? Comment { get; set; } - [DataMember(Name = "parameters")] public string? Parameters { get; set; } + [DataMember(Name = "parameters")] + public string? Parameters { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/BackOfficeNotification.cs b/src/Umbraco.Core/Models/ContentEditing/BackOfficeNotification.cs index f89517c9eee8..1cf1e60e2552 100644 --- a/src/Umbraco.Core/Models/ContentEditing/BackOfficeNotification.cs +++ b/src/Umbraco.Core/Models/ContentEditing/BackOfficeNotification.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -16,9 +16,12 @@ public BackOfficeNotification(string header, string message, NotificationStyle n NotificationType = notificationType; } - [DataMember(Name = "header")] public string? Header { get; set; } + [DataMember(Name = "header")] + public string? Header { get; set; } - [DataMember(Name = "message")] public string? Message { get; set; } + [DataMember(Name = "message")] + public string? Message { get; set; } - [DataMember(Name = "type")] public NotificationStyle NotificationType { get; set; } + [DataMember(Name = "type")] + public NotificationStyle NotificationType { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/CodeFileDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/CodeFileDisplay.cs index adc83d985cad..b172fccb5a1f 100644 --- a/src/Umbraco.Core/Models/ContentEditing/CodeFileDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/CodeFileDisplay.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Extensions; @@ -58,13 +58,13 @@ public IEnumerable Validate(ValidationContext validationContex { yield return new ValidationResult( "The file name cannot contain illegal characters", - new[] {"Name"}); + new[] { "Name" }); } else if (System.IO.Path.GetFileNameWithoutExtension(Name).IsNullOrWhiteSpace()) { yield return new ValidationResult( "The file name cannot be empty", - new[] {"Name"}); + new[] { "Name" }); } } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentApp.cs b/src/Umbraco.Core/Models/ContentEditing/ContentApp.cs index 054edb747205..02c32adc540c 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentApp.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentApp.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentAppBadge.cs b/src/Umbraco.Core/Models/ContentEditing/ContentAppBadge.cs index b3afc3181335..7a50073d1717 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentAppBadge.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentAppBadge.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentAppBadgeType.cs b/src/Umbraco.Core/Models/ContentEditing/ContentAppBadgeType.cs index e41c37ee8202..718b36db3358 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentAppBadgeType.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentAppBadgeType.cs @@ -1,6 +1,7 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; + // TODO: This was marked with `[StringEnumConverter]` to inform the serializer // to serialize the values to string instead of INT (which is the default) // so we need to either invent our own attribute and make the implementation aware of it @@ -12,9 +13,12 @@ namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "contentAppBadgeType")] public enum ContentAppBadgeType { - [EnumMember(Value = "default")] Default = 0, + [EnumMember(Value = "default")] + Default = 0, - [EnumMember(Value = "warning")] Warning = 1, + [EnumMember(Value = "warning")] + Warning = 1, - [EnumMember(Value = "alert")] Alert = 2 + [EnumMember(Value = "alert")] + Alert = 2, } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentBaseSave.cs b/src/Umbraco.Core/Models/ContentEditing/ContentBaseSave.cs index 6205f0846afd..241cde46b46f 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentBaseSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentBaseSave.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Editors; @@ -27,14 +27,16 @@ public override IEnumerable Properties set => base.Properties = value; } - [IgnoreDataMember] public List UploadedFiles { get; } + [IgnoreDataMember] + public List UploadedFiles { get; } + + // These need explicit implementation because we are using internal models - //These need explicit implementation because we are using internal models /// [IgnoreDataMember] TPersisted IContentSave.PersistedContent { get; set; } = default!; - //Non explicit internal getter so we don't need to explicitly cast in our own code + // Non explicit internal getter so we don't need to explicitly cast in our own code [IgnoreDataMember] public TPersisted PersistedContent { diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentDomainsAndCulture.cs b/src/Umbraco.Core/Models/ContentEditing/ContentDomainsAndCulture.cs index 835f22027224..ca24b08567b2 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentDomainsAndCulture.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentDomainsAndCulture.cs @@ -1,11 +1,13 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "ContentDomainsAndCulture")] public class ContentDomainsAndCulture { - [DataMember(Name = "domains")] public IEnumerable? Domains { get; set; } + [DataMember(Name = "domains")] + public IEnumerable? Domains { get; set; } - [DataMember(Name = "language")] public string? Language { get; set; } + [DataMember(Name = "language")] + public string? Language { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentItemBasic.cs b/src/Umbraco.Core/Models/ContentEditing/ContentItemBasic.cs index d3c0d305c198..fd277308f74f 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentItemBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentItemBasic.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -9,9 +9,11 @@ namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "content", Namespace = "")] public class ContentItemBasic : EntityBasic { - [DataMember(Name = "updateDate")] public DateTime UpdateDate { get; set; } + [DataMember(Name = "updateDate")] + public DateTime UpdateDate { get; set; } - [DataMember(Name = "createDate")] public DateTime CreateDate { get; set; } + [DataMember(Name = "createDate")] + public DateTime CreateDate { get; set; } /// /// Boolean indicating if this item is published or not based on it's @@ -25,9 +27,11 @@ public class ContentItemBasic : EntityBasic [DataMember(Name = "edited")] public bool Edited { get; set; } - [DataMember(Name = "owner")] public UserProfile? Owner { get; set; } + [DataMember(Name = "owner")] + public UserProfile? Owner { get; set; } - [DataMember(Name = "updater")] public UserProfile? Updater { get; set; } + [DataMember(Name = "updater")] + public UserProfile? Updater { get; set; } public int ContentTypeId { get; set; } @@ -35,7 +39,8 @@ public class ContentItemBasic : EntityBasic [Required(AllowEmptyStrings = false)] public string ContentTypeAlias { get; set; } = null!; - [DataMember(Name = "sortOrder")] public int SortOrder { get; set; } + [DataMember(Name = "sortOrder")] + public int SortOrder { get; set; } /// /// The saved/published state of an item @@ -46,9 +51,8 @@ public class ContentItemBasic : EntityBasic [DataMember(Name = "state")] public ContentSavedState? State { get; set; } - [DataMember(Name = "variesByCulture")] public bool VariesByCulture { get; set; } - - protected bool Equals(ContentItemBasic other) => Id == other.Id; + [DataMember(Name = "variesByCulture")] + public bool VariesByCulture { get; set; } public override bool Equals(object? obj) { @@ -62,10 +66,11 @@ public override bool Equals(object? obj) return true; } - var other = obj as ContentItemBasic; - return other != null && Equals(other); + return obj is ContentItemBasic other && Equals(other); } + protected bool Equals(ContentItemBasic other) => Id == other.Id; + public override int GetHashCode() { if (Id is not null) @@ -87,7 +92,8 @@ public class ContentItemBasic : ContentItemBasic, IContentProperties private IEnumerable _properties; public ContentItemBasic() => - //ensure its not null + + // ensure its not null _properties = Enumerable.Empty(); [DataMember(Name = "properties")] diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs index 36e11eb5419a..8ee8dcb00a22 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplay.cs @@ -19,7 +19,7 @@ public class ContentItemDisplayWithSchedule : ContentItemDisplay : INotificationModel, - IErrorModel //ListViewAwareContentItemDisplayBase + IErrorModel // ListViewAwareContentItemDisplayBase where TVariant : ContentVariantDisplay { public ContentItemDisplay() @@ -39,7 +39,8 @@ public ContentItemDisplay() [ReadOnly(true)] public Udi? Udi { get; set; } - [DataMember(Name = "icon")] public string? Icon { get; set; } + [DataMember(Name = "icon")] + public string? Icon { get; set; } [DataMember(Name = "trashed")] [ReadOnly(true)] @@ -71,9 +72,11 @@ public ContentItemDisplay() [DataMember(Name = "variants")] public IEnumerable? Variants { get; set; } - [DataMember(Name = "owner")] public UserProfile? Owner { get; set; } + [DataMember(Name = "owner")] + public UserProfile? Owner { get; set; } - [DataMember(Name = "updater")] public UserProfile? Updater { get; set; } + [DataMember(Name = "updater")] + public UserProfile? Updater { get; set; } /// /// The name of the content type @@ -109,15 +112,18 @@ public ContentItemDisplay() [DataMember(Name = "treeNodeUrl")] public string? TreeNodeUrl { get; set; } - [DataMember(Name = "contentTypeId")] public int? ContentTypeId { get; set; } + [DataMember(Name = "contentTypeId")] + public int? ContentTypeId { get; set; } - [DataMember(Name = "contentTypeKey")] public Guid ContentTypeKey { get; set; } + [DataMember(Name = "contentTypeKey")] + public Guid ContentTypeKey { get; set; } [DataMember(Name = "contentTypeAlias", IsRequired = true)] [Required(AllowEmptyStrings = false)] public string ContentTypeAlias { get; set; } = null!; - [DataMember(Name = "sortOrder")] public int SortOrder { get; set; } + [DataMember(Name = "sortOrder")] + public int SortOrder { get; set; } /// /// This is the last updated date for the entire content object regardless of variants @@ -128,16 +134,20 @@ public ContentItemDisplay() [DataMember(Name = "updateDate")] public DateTime UpdateDate { get; set; } - [DataMember(Name = "template")] public string? TemplateAlias { get; set; } + [DataMember(Name = "template")] + public string? TemplateAlias { get; set; } - [DataMember(Name = "templateId")] public int TemplateId { get; set; } + [DataMember(Name = "templateId")] + public int TemplateId { get; set; } [DataMember(Name = "allowedTemplates")] public IDictionary? AllowedTemplates { get; set; } - [DataMember(Name = "documentType")] public ContentTypeBasic? DocumentType { get; set; } + [DataMember(Name = "documentType")] + public ContentTypeBasic? DocumentType { get; set; } - [DataMember(Name = "urls")] public UrlInfo[]? Urls { get; set; } + [DataMember(Name = "urls")] + public UrlInfo[]? Urls { get; set; } /// /// Determines whether previewing is allowed for this node @@ -158,9 +168,11 @@ public ContentItemDisplay() [DataMember(Name = "allowedActions")] public IEnumerable? AllowedActions { get; set; } - [DataMember(Name = "isBlueprint")] public bool IsBlueprint { get; set; } + [DataMember(Name = "isBlueprint")] + public bool IsBlueprint { get; set; } - [DataMember(Name = "apps")] public IEnumerable ContentApps { get; set; } + [DataMember(Name = "apps")] + public IEnumerable ContentApps { get; set; } /// /// The real persisted content object - used during inbound model binding diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplayBase.cs b/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplayBase.cs index 909e58f550a7..1adf69371b54 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplayBase.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentItemDisplayBase.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentItemSave.cs b/src/Umbraco.Core/Models/ContentEditing/ContentItemSave.cs index deb807a598c8..400436421bf3 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentItemSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentItemSave.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Editors; @@ -43,14 +43,16 @@ public ContentItemSave() [Required] public ContentSaveAction Action { get; set; } - [IgnoreDataMember] public List UploadedFiles { get; } + [IgnoreDataMember] + public List UploadedFiles { get; } + + // These need explicit implementation because we are using internal models - //These need explicit implementation because we are using internal models /// [IgnoreDataMember] IContent IContentSave.PersistedContent { get; set; } = null!; - //Non explicit internal getter so we don't need to explicitly cast in our own code + // Non explicit internal getter so we don't need to explicitly cast in our own code [IgnoreDataMember] public IContent PersistedContent { diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyBasic.cs b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyBasic.cs index 27fe09f877dc..c4a3d7791b5c 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyBasic.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Cms.Core.PropertyEditors; @@ -25,7 +25,8 @@ public class ContentPropertyBasic [ReadOnly(true)] public Guid DataTypeKey { get; set; } - [DataMember(Name = "value")] public object? Value { get; set; } + [DataMember(Name = "value")] + public object? Value { get; set; } [DataMember(Name = "alias", IsRequired = true)] [Required(AllowEmptyStrings = false)] diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyCollectionDto.cs b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyCollectionDto.cs index a66c9ba4fb1c..35423f19a875 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyCollectionDto.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyCollectionDto.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.ContentEditing; +namespace Umbraco.Cms.Core.Models.ContentEditing; /// /// Used to map property values when saving content/media/members diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDisplay.cs index 923d7c2cd385..ca8c2f1fc2af 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDisplay.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -19,19 +19,25 @@ public ContentPropertyDisplay() [Required] public string? Label { get; set; } - [DataMember(Name = "description")] public string? Description { get; set; } + [DataMember(Name = "description")] + public string? Description { get; set; } [DataMember(Name = "view", IsRequired = true)] [Required(AllowEmptyStrings = false)] public string? View { get; set; } - [DataMember(Name = "config")] public IDictionary? Config { get; set; } + [DataMember(Name = "config")] + public IDictionary? Config { get; set; } - [DataMember(Name = "hideLabel")] public bool HideLabel { get; set; } + [DataMember(Name = "hideLabel")] + public bool HideLabel { get; set; } - [DataMember(Name = "labelOnTop")] public bool? LabelOnTop { get; set; } + [DataMember(Name = "labelOnTop")] + public bool? LabelOnTop { get; set; } - [DataMember(Name = "validation")] public PropertyTypeValidation Validation { get; set; } + [DataMember(Name = "validation")] + public PropertyTypeValidation Validation { get; set; } - [DataMember(Name = "readonly")] public bool Readonly { get; set; } + [DataMember(Name = "readonly")] + public bool Readonly { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDto.cs b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDto.cs index c2972ce8b800..b0045bb0381c 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDto.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentPropertyDto.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.ContentEditing; +namespace Umbraco.Cms.Core.Models.ContentEditing; /// /// Represents a content property from the database diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentRedirectUrl.cs b/src/Umbraco.Core/Models/ContentEditing/ContentRedirectUrl.cs index 8b7c7263616e..62bf29ce82c8 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentRedirectUrl.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentRedirectUrl.cs @@ -1,19 +1,25 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "contentRedirectUrl", Namespace = "")] public class ContentRedirectUrl { - [DataMember(Name = "redirectId")] public Guid RedirectId { get; set; } + [DataMember(Name = "redirectId")] + public Guid RedirectId { get; set; } - [DataMember(Name = "originalUrl")] public string? OriginalUrl { get; set; } + [DataMember(Name = "originalUrl")] + public string? OriginalUrl { get; set; } - [DataMember(Name = "destinationUrl")] public string? DestinationUrl { get; set; } + [DataMember(Name = "destinationUrl")] + public string? DestinationUrl { get; set; } - [DataMember(Name = "createDateUtc")] public DateTime CreateDateUtc { get; set; } + [DataMember(Name = "createDateUtc")] + public DateTime CreateDateUtc { get; set; } - [DataMember(Name = "contentId")] public int ContentId { get; set; } + [DataMember(Name = "contentId")] + public int ContentId { get; set; } - [DataMember(Name = "culture")] public string? Culture { get; set; } + [DataMember(Name = "culture")] + public string? Culture { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentSaveAction.cs b/src/Umbraco.Core/Models/ContentEditing/ContentSaveAction.cs index 064e6385f396..889b03db6d67 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentSaveAction.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentSaveAction.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.ContentEditing; +namespace Umbraco.Cms.Core.Models.ContentEditing; /// /// The action associated with saving a content item @@ -65,5 +65,5 @@ public enum ContentSaveAction /// Creates and publishes the content item including all descendants regardless of whether they have a published /// version or not /// - PublishWithDescendantsForceNew = 11 + PublishWithDescendantsForceNew = 11, } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentSavedState.cs b/src/Umbraco.Core/Models/ContentEditing/ContentSavedState.cs index bf1958d77b05..163514193485 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentSavedState.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentSavedState.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.ContentEditing; +namespace Umbraco.Cms.Core.Models.ContentEditing; /// /// The saved state of a content item @@ -23,5 +23,5 @@ public enum ContentSavedState /// /// The item is published and there are pending changes /// - PublishedPendingChanges = 4 + PublishedPendingChanges = 4, } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentSortOrder.cs b/src/Umbraco.Core/Models/ContentEditing/ContentSortOrder.cs index f17d457486e1..17d751760d7a 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentSortOrder.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentSortOrder.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentTypeBasic.cs b/src/Umbraco.Core/Models/ContentEditing/ContentTypeBasic.cs index 7b4015946c16..90dd6ce5c903 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentTypeBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentTypeBasic.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Extensions; @@ -36,9 +36,11 @@ public ContentTypeBasic() [ReadOnly(true)] public DateTime CreateDate { get; set; } - [DataMember(Name = "description")] public string? Description { get; set; } + [DataMember(Name = "description")] + public string? Description { get; set; } - [DataMember(Name = "thumbnail")] public string? Thumbnail { get; set; } + [DataMember(Name = "thumbnail")] + public string? Thumbnail { get; set; } /// /// Returns true if the icon represents a CSS class instead of a file path @@ -54,7 +56,7 @@ public bool IconIsClass return true; } - //if it starts with a '.' or doesn't contain a '.' at all then it is a class + // if it starts with a '.' or doesn't contain a '.' at all then it is a class return (Icon?.StartsWith(".") ?? false) || Icon?.Contains(".") == false; } } @@ -80,7 +82,7 @@ public bool ThumbnailIsClass return true; } - //if it starts with a '.' or doesn't contain a '.' at all then it is a class + // if it starts with a '.' or doesn't contain a '.' at all then it is a class return (Thumbnail?.StartsWith(".") ?? false) || Thumbnail?.Contains(".") == false; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentTypeCompositionDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/ContentTypeCompositionDisplay.cs index 29d23c05656b..030923d29134 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentTypeCompositionDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentTypeCompositionDisplay.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -7,31 +7,31 @@ public abstract class ContentTypeCompositionDisplay : ContentTypeBasic, INotific { protected ContentTypeCompositionDisplay() { - //initialize collections so at least their never null + // initialize collections so at least their never null AllowedContentTypes = new List(); CompositeContentTypes = new List(); Notifications = new List(); } - //name, alias, icon, thumb, desc, inherited from basic - + // name, alias, icon, thumb, desc, inherited from basic [DataMember(Name = "listViewEditorName")] [ReadOnly(true)] public string? ListViewEditorName { get; set; } - //Allowed child types + // Allowed child types [DataMember(Name = "allowedContentTypes")] public IEnumerable? AllowedContentTypes { get; set; } - //Compositions + // Compositions [DataMember(Name = "compositeContentTypes")] public IEnumerable CompositeContentTypes { get; set; } - //Locked compositions + // Locked compositions [DataMember(Name = "lockedCompositeContentTypes")] public IEnumerable? LockedCompositeContentTypes { get; set; } - [DataMember(Name = "allowAsRoot")] public bool AllowAsRoot { get; set; } + [DataMember(Name = "allowAsRoot")] + public bool AllowAsRoot { get; set; } /// /// This is used for validation of a content item. @@ -59,9 +59,11 @@ public abstract class ContentTypeCompositionDisplay : Cont where TPropertyTypeDisplay : PropertyTypeDisplay { protected ContentTypeCompositionDisplay() => - //initialize collections so at least their never null + + // initialize collections so at least their never null Groups = new List>(); - //Tabs - [DataMember(Name = "groups")] public IEnumerable> Groups { get; set; } + // Tabs + [DataMember(Name = "groups")] + public IEnumerable> Groups { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentTypeSave.cs b/src/Umbraco.Core/Models/ContentEditing/ContentTypeSave.cs index c58a74fd23ef..d6ad7c7ba227 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentTypeSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentTypeSave.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Extensions; @@ -16,17 +16,19 @@ protected ContentTypeSave() CompositeContentTypes = new List(); } - //Compositions + // Compositions [DataMember(Name = "compositeContentTypes")] public IEnumerable CompositeContentTypes { get; set; } - [DataMember(Name = "allowAsRoot")] public bool AllowAsRoot { get; set; } + [DataMember(Name = "allowAsRoot")] + public bool AllowAsRoot { get; set; } - //Allowed child types + // Allowed child types [DataMember(Name = "allowedContentTypes")] public IEnumerable AllowedContentTypes { get; set; } - [DataMember(Name = "historyCleanup")] public HistoryCleanupViewModel? HistoryCleanup { get; set; } + [DataMember(Name = "historyCleanup")] + public HistoryCleanupViewModel? HistoryCleanup { get; set; } /// /// Custom validation @@ -37,8 +39,9 @@ public virtual IEnumerable Validate(ValidationContext validati { if (CompositeContentTypes.Any(x => x.IsNullOrWhiteSpace())) { - yield return new ValidationResult("Composite Content Type value cannot be null", - new[] {"CompositeContentTypes"}); + yield return new ValidationResult( + "Composite Content Type value cannot be null", + new[] { "CompositeContentTypes" }); } } } @@ -65,8 +68,9 @@ public abstract class ContentTypeSave : ContentTypeSave [DataMember(Name = "allowSegmentVariant")] public bool AllowSegmentVariant { get; set; } - //Tabs - [DataMember(Name = "groups")] public IEnumerable> Groups { get; set; } + // Tabs + [DataMember(Name = "groups")] + public IEnumerable> Groups { get; set; } /// /// Custom validation @@ -87,16 +91,17 @@ public override IEnumerable Validate(ValidationContext validat yield return new ValidationResult("Duplicate aliases are not allowed: " + duplicateGroupAlias.Key, new[] { // TODO: We don't display the alias yet, so add the validation message to the name - $"Groups[{lastGroupIndex}].Name" + $"Groups[{lastGroupIndex}].Name", }); } - foreach (IGrouping<(string, string Name), PropertyGroupBasic> duplicateGroupName in Groups + foreach (IGrouping<(string?, string? Name), PropertyGroupBasic> duplicateGroupName in Groups .GroupBy(x => (x.GetParentAlias(), x.Name)).Where(x => x.Count() > 1)) { var lastGroupIndex = Groups.IndexOf(duplicateGroupName.Last()); - yield return new ValidationResult("Duplicate names are not allowed", - new[] {$"Groups[{lastGroupIndex}].Name"}); + yield return new ValidationResult( + "Duplicate names are not allowed", + new[] { $"Groups[{lastGroupIndex}].Name" }); } foreach (IGrouping duplicatePropertyAlias in Groups.SelectMany(x => x.Properties) @@ -107,8 +112,9 @@ public override IEnumerable Validate(ValidationContext validat var lastPropertyIndex = propertyGroup.Properties.IndexOf(lastProperty); var propertyGroupIndex = Groups.IndexOf(propertyGroup); - yield return new ValidationResult("Duplicate property aliases not allowed: " + duplicatePropertyAlias.Key, - new[] {$"Groups[{propertyGroupIndex}].Properties[{lastPropertyIndex}].Alias"}); + yield return new ValidationResult( + "Duplicate property aliases not allowed: " + duplicatePropertyAlias.Key, + new[] { $"Groups[{propertyGroupIndex}].Properties[{lastPropertyIndex}].Alias" }); } } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentVariantSave.cs b/src/Umbraco.Core/Models/ContentEditing/ContentVariantSave.cs index da82ebf9b788..ed9568590f08 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentVariantSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentVariantSave.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Validation; @@ -45,9 +45,11 @@ public class ContentVariantSave : IContentProperties [DataMember(Name = "publish")] public bool Publish { get; set; } - [DataMember(Name = "expireDate")] public DateTime? ExpireDate { get; set; } + [DataMember(Name = "expireDate")] + public DateTime? ExpireDate { get; set; } - [DataMember(Name = "releaseDate")] public DateTime? ReleaseDate { get; set; } + [DataMember(Name = "releaseDate")] + public DateTime? ReleaseDate { get; set; } /// /// The property DTO object is used to gather all required property data including data type information etc... for use @@ -62,5 +64,6 @@ public class ContentVariantSave : IContentProperties [IgnoreDataMember] public ContentPropertyCollectionDto? PropertyCollectionDto { get; set; } - [DataMember(Name = "properties")] public IEnumerable Properties { get; set; } + [DataMember(Name = "properties")] + public IEnumerable Properties { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentVariationDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/ContentVariationDisplay.cs index 886bb5299fbd..3004e29fadc8 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentVariationDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentVariationDisplay.cs @@ -19,7 +19,8 @@ public ContentVariantDisplay() [DataMember(Name = "name", IsRequired = true)] public string? Name { get; set; } - [DataMember(Name = "displayName")] public string? DisplayName { get; set; } + [DataMember(Name = "displayName")] + public string? DisplayName { get; set; } /// /// The language/culture assigned to this content variation @@ -30,15 +31,20 @@ public ContentVariantDisplay() [DataMember(Name = "language")] public Language? Language { get; set; } - [DataMember(Name = "segment")] public string? Segment { get; set; } + [DataMember(Name = "segment")] + public string? Segment { get; set; } - [DataMember(Name = "state")] public ContentSavedState State { get; set; } + [DataMember(Name = "state")] + public ContentSavedState State { get; set; } - [DataMember(Name = "updateDate")] public DateTime UpdateDate { get; set; } + [DataMember(Name = "updateDate")] + public DateTime UpdateDate { get; set; } - [DataMember(Name = "createDate")] public DateTime CreateDate { get; set; } + [DataMember(Name = "createDate")] + public DateTime CreateDate { get; set; } - [DataMember(Name = "publishDate")] public DateTime? PublishDate { get; set; } + [DataMember(Name = "publishDate")] + public DateTime? PublishDate { get; set; } /// /// Internal property used for tests to get all properties from all tabs @@ -67,7 +73,9 @@ public ContentVariantDisplay() public class ContentVariantScheduleDisplay : ContentVariantDisplay { - [DataMember(Name = "releaseDate")] public DateTime? ReleaseDate { get; set; } + [DataMember(Name = "releaseDate")] + public DateTime? ReleaseDate { get; set; } - [DataMember(Name = "expireDate")] public DateTime? ExpireDate { get; set; } + [DataMember(Name = "expireDate")] + public DateTime? ExpireDate { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/CreatedDocumentTypeCollectionResult.cs b/src/Umbraco.Core/Models/ContentEditing/CreatedDocumentTypeCollectionResult.cs index 94aa0aaac632..a6f99ab58636 100644 --- a/src/Umbraco.Core/Models/ContentEditing/CreatedDocumentTypeCollectionResult.cs +++ b/src/Umbraco.Core/Models/ContentEditing/CreatedDocumentTypeCollectionResult.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -8,7 +8,9 @@ namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "contentTypeCollection", Namespace = "")] public class CreatedContentTypeCollectionResult { - [DataMember(Name = "collectionId")] public int CollectionId { get; set; } + [DataMember(Name = "collectionId")] + public int CollectionId { get; set; } - [DataMember(Name = "containerId")] public int ContainerId { get; set; } + [DataMember(Name = "containerId")] + public int ContainerId { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/DataTypeBasic.cs b/src/Umbraco.Core/Models/ContentEditing/DataTypeBasic.cs index d282212c948c..7bb0d427eadc 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DataTypeBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DataTypeBasic.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/DataTypeConfigurationFieldDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DataTypeConfigurationFieldDisplay.cs index f00fd76f3c2f..a324bb4bcea2 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DataTypeConfigurationFieldDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DataTypeConfigurationFieldDisplay.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/DataTypeConfigurationFieldSave.cs b/src/Umbraco.Core/Models/ContentEditing/DataTypeConfigurationFieldSave.cs index bd1a8c49e3e3..514f9b86187b 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DataTypeConfigurationFieldSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DataTypeConfigurationFieldSave.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/DataTypeDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DataTypeDisplay.cs index 5cc25ddc2471..7f3c93d12653 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DataTypeDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DataTypeDisplay.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -21,7 +21,8 @@ public class DataTypeDisplay : DataTypeBasic, INotificationModel [DataMember(Name = "availableEditors")] public IEnumerable? AvailableEditors { get; set; } - [DataMember(Name = "preValues")] public IEnumerable? PreValues { get; set; } + [DataMember(Name = "preValues")] + public IEnumerable? PreValues { get; set; } /// /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. diff --git a/src/Umbraco.Core/Models/ContentEditing/DataTypeReferences.cs b/src/Umbraco.Core/Models/ContentEditing/DataTypeReferences.cs index 8a3159e46611..47711fc0a30a 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DataTypeReferences.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DataTypeReferences.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -17,14 +17,17 @@ public class DataTypeReferences [DataContract(Name = "contentType", Namespace = "")] public class ContentTypeReferences : EntityBasic { - [DataMember(Name = "properties")] public object? Properties { get; set; } + [DataMember(Name = "properties")] + public object? Properties { get; set; } [DataContract(Name = "property", Namespace = "")] public class PropertyTypeReferences { - [DataMember(Name = "name")] public string? Name { get; set; } + [DataMember(Name = "name")] + public string? Name { get; set; } - [DataMember(Name = "alias")] public string? Alias { get; set; } + [DataMember(Name = "alias")] + public string? Alias { get; set; } } } } diff --git a/src/Umbraco.Core/Models/ContentEditing/DataTypeSave.cs b/src/Umbraco.Core/Models/ContentEditing/DataTypeSave.cs index d664b5227914..8968fb0795e2 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DataTypeSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DataTypeSave.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Cms.Core.PropertyEditors; diff --git a/src/Umbraco.Core/Models/ContentEditing/DictionaryOverviewDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DictionaryOverviewDisplay.cs index 2a8d842c39a4..15aab0c7ef62 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DictionaryOverviewDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DictionaryOverviewDisplay.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/DictionaryOverviewTranslationDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DictionaryOverviewTranslationDisplay.cs index 87b42da70754..9e534820fa4a 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DictionaryOverviewTranslationDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DictionaryOverviewTranslationDisplay.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/DictionarySave.cs b/src/Umbraco.Core/Models/ContentEditing/DictionarySave.cs index cc6647ff9de9..85585c45ba16 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DictionarySave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DictionarySave.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/DictionaryTranslationDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DictionaryTranslationDisplay.cs index 6ca31d1d4d16..afd36b6acc84 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DictionaryTranslationDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DictionaryTranslationDisplay.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs index 1a4672827059..3c292a7e6a73 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DocumentTypeDisplay.cs @@ -6,16 +6,18 @@ namespace Umbraco.Cms.Core.Models.ContentEditing; public class DocumentTypeDisplay : ContentTypeCompositionDisplay { public DocumentTypeDisplay() => - //initialize collections so at least their never null + + // initialize collections so at least their never null AllowedTemplates = new List(); - //name, alias, icon, thumb, desc, inherited from the content type + // name, alias, icon, thumb, desc, inherited from the content type // Templates [DataMember(Name = "allowedTemplates")] public IEnumerable AllowedTemplates { get; set; } - [DataMember(Name = "defaultTemplate")] public EntityBasic? DefaultTemplate { get; set; } + [DataMember(Name = "defaultTemplate")] + public EntityBasic? DefaultTemplate { get; set; } [DataMember(Name = "allowCultureVariant")] public bool AllowCultureVariant { get; set; } @@ -23,7 +25,9 @@ public DocumentTypeDisplay() => [DataMember(Name = "allowSegmentVariant")] public bool AllowSegmentVariant { get; set; } - [DataMember(Name = "apps")] public IEnumerable? ContentApps { get; set; } + [DataMember(Name = "apps")] + public IEnumerable? ContentApps { get; set; } - [DataMember(Name = "historyCleanup")] public HistoryCleanupViewModel? HistoryCleanup { get; set; } + [DataMember(Name = "historyCleanup")] + public HistoryCleanupViewModel? HistoryCleanup { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/DocumentTypeSave.cs b/src/Umbraco.Core/Models/ContentEditing/DocumentTypeSave.cs index d8ebf6985261..af13e88f9b14 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DocumentTypeSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DocumentTypeSave.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Extensions; @@ -31,7 +31,7 @@ public override IEnumerable Validate(ValidationContext validat { if (AllowedTemplates?.Any(x => x.IsNullOrWhiteSpace()) ?? false) { - yield return new ValidationResult("Template value cannot be null", new[] {"AllowedTemplates"}); + yield return new ValidationResult("Template value cannot be null", new[] { "AllowedTemplates" }); } foreach (ValidationResult v in base.Validate(validationContext)) diff --git a/src/Umbraco.Core/Models/ContentEditing/DomainDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/DomainDisplay.cs index 1147a8cdaa17..7a6a58443858 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DomainDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DomainDisplay.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -11,11 +11,15 @@ public DomainDisplay(string name, int lang) Lang = lang; } - [DataMember(Name = "name")] public string Name { get; } + [DataMember(Name = "name")] + public string Name { get; } - [DataMember(Name = "lang")] public int Lang { get; } + [DataMember(Name = "lang")] + public int Lang { get; } - [DataMember(Name = "duplicate")] public bool Duplicate { get; set; } + [DataMember(Name = "duplicate")] + public bool Duplicate { get; set; } - [DataMember(Name = "other")] public string? Other { get; set; } + [DataMember(Name = "other")] + public string? Other { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/DomainSave.cs b/src/Umbraco.Core/Models/ContentEditing/DomainSave.cs index 84ea5ebd819d..391616b8dcd5 100644 --- a/src/Umbraco.Core/Models/ContentEditing/DomainSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/DomainSave.cs @@ -1,15 +1,19 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "DomainSave")] public class DomainSave { - [DataMember(Name = "valid")] public bool Valid { get; set; } + [DataMember(Name = "valid")] + public bool Valid { get; set; } - [DataMember(Name = "nodeId")] public int NodeId { get; set; } + [DataMember(Name = "nodeId")] + public int NodeId { get; set; } - [DataMember(Name = "language")] public int Language { get; set; } + [DataMember(Name = "language")] + public int Language { get; set; } - [DataMember(Name = "domains")] public DomainDisplay[]? Domains { get; set; } + [DataMember(Name = "domains")] + public DomainDisplay[]? Domains { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/EditorNavigation.cs b/src/Umbraco.Core/Models/ContentEditing/EditorNavigation.cs index 9500265e11d6..0920e45f2970 100644 --- a/src/Umbraco.Core/Models/ContentEditing/EditorNavigation.cs +++ b/src/Umbraco.Core/Models/ContentEditing/EditorNavigation.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -8,13 +8,18 @@ namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "user", Namespace = "")] public class EditorNavigation { - [DataMember(Name = "name")] public string? Name { get; set; } + [DataMember(Name = "name")] + public string? Name { get; set; } - [DataMember(Name = "alias")] public string? Alias { get; set; } + [DataMember(Name = "alias")] + public string? Alias { get; set; } - [DataMember(Name = "icon")] public string? Icon { get; set; } + [DataMember(Name = "icon")] + public string? Icon { get; set; } - [DataMember(Name = "view")] public string? View { get; set; } + [DataMember(Name = "view")] + public string? View { get; set; } - [DataMember(Name = "active")] public bool Active { get; set; } + [DataMember(Name = "active")] + public bool Active { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/EntityBasic.cs b/src/Umbraco.Core/Models/ContentEditing/EntityBasic.cs index 86fcebe70604..36a837e8e88c 100644 --- a/src/Umbraco.Core/Models/ContentEditing/EntityBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/EntityBasic.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Validation; @@ -27,7 +27,8 @@ public EntityBasic() [ReadOnly(true)] public Udi? Udi { get; set; } - [DataMember(Name = "icon")] public string? Icon { get; set; } + [DataMember(Name = "icon")] + public string? Icon { get; set; } [DataMember(Name = "trashed")] [ReadOnly(true)] diff --git a/src/Umbraco.Core/Models/ContentEditing/EntitySearchResults.cs b/src/Umbraco.Core/Models/ContentEditing/EntitySearchResults.cs index 728707a22f8b..f345d881b6ba 100644 --- a/src/Umbraco.Core/Models/ContentEditing/EntitySearchResults.cs +++ b/src/Umbraco.Core/Models/ContentEditing/EntitySearchResults.cs @@ -14,8 +14,10 @@ public EntitySearchResults(IEnumerable results, long totalF TotalResults = totalFound; } - [DataMember(Name = "totalResults")] public long TotalResults { get; set; } + [DataMember(Name = "totalResults")] + public long TotalResults { get; set; } public IEnumerator GetEnumerator() => _results.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_results).GetEnumerator(); } diff --git a/src/Umbraco.Core/Models/ContentEditing/GetAvailableCompositionsFilter.cs b/src/Umbraco.Core/Models/ContentEditing/GetAvailableCompositionsFilter.cs index 29419533a8d9..c3f49d5b7d1c 100644 --- a/src/Umbraco.Core/Models/ContentEditing/GetAvailableCompositionsFilter.cs +++ b/src/Umbraco.Core/Models/ContentEditing/GetAvailableCompositionsFilter.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.ContentEditing; +namespace Umbraco.Cms.Core.Models.ContentEditing; public class GetAvailableCompositionsFilter { diff --git a/src/Umbraco.Core/Models/ContentEditing/IContentAppFactory.cs b/src/Umbraco.Core/Models/ContentEditing/IContentAppFactory.cs index 15fa3fd99e0f..e0216f66f867 100644 --- a/src/Umbraco.Core/Models/ContentEditing/IContentAppFactory.cs +++ b/src/Umbraco.Core/Models/ContentEditing/IContentAppFactory.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Models.Membership; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -11,6 +11,7 @@ public interface IContentAppFactory /// Gets the content app for an object. /// /// The source object. + /// The user groups /// The content app for the object, or null. /// /// diff --git a/src/Umbraco.Core/Models/ContentEditing/IContentProperties.cs b/src/Umbraco.Core/Models/ContentEditing/IContentProperties.cs index af7fbaea41ca..3520c078b19d 100644 --- a/src/Umbraco.Core/Models/ContentEditing/IContentProperties.cs +++ b/src/Umbraco.Core/Models/ContentEditing/IContentProperties.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.ContentEditing; +namespace Umbraco.Cms.Core.Models.ContentEditing; public interface IContentProperties where T : ContentPropertyBasic diff --git a/src/Umbraco.Core/Models/ContentEditing/IContentSave.cs b/src/Umbraco.Core/Models/ContentEditing/IContentSave.cs index 30503a6cc27c..effccf95faa6 100644 --- a/src/Umbraco.Core/Models/ContentEditing/IContentSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/IContentSave.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.ContentEditing; +namespace Umbraco.Cms.Core.Models.ContentEditing; /// /// An interface exposes the shared parts of content, media, members that we use during model binding in order to share diff --git a/src/Umbraco.Core/Models/ContentEditing/IErrorModel.cs b/src/Umbraco.Core/Models/ContentEditing/IErrorModel.cs index 3884f20860f1..9607146eda43 100644 --- a/src/Umbraco.Core/Models/ContentEditing/IErrorModel.cs +++ b/src/Umbraco.Core/Models/ContentEditing/IErrorModel.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.ContentEditing; +namespace Umbraco.Cms.Core.Models.ContentEditing; public interface IErrorModel { diff --git a/src/Umbraco.Core/Models/ContentEditing/IHaveUploadedFiles.cs b/src/Umbraco.Core/Models/ContentEditing/IHaveUploadedFiles.cs index 2bd451a649d3..7e467ff124c9 100644 --- a/src/Umbraco.Core/Models/ContentEditing/IHaveUploadedFiles.cs +++ b/src/Umbraco.Core/Models/ContentEditing/IHaveUploadedFiles.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Editors; +using Umbraco.Cms.Core.Models.Editors; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/INotificationModel.cs b/src/Umbraco.Core/Models/ContentEditing/INotificationModel.cs index 50e0363798e3..15b75a82cf82 100644 --- a/src/Umbraco.Core/Models/ContentEditing/INotificationModel.cs +++ b/src/Umbraco.Core/Models/ContentEditing/INotificationModel.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/ITabbedContent.cs b/src/Umbraco.Core/Models/ContentEditing/ITabbedContent.cs index 4188c89058ac..13f7375c3d5a 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ITabbedContent.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ITabbedContent.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.ContentEditing; +namespace Umbraco.Cms.Core.Models.ContentEditing; public interface ITabbedContent where T : ContentPropertyBasic diff --git a/src/Umbraco.Core/Models/ContentEditing/Language.cs b/src/Umbraco.Core/Models/ContentEditing/Language.cs index 8395fe948203..15e63eabedc7 100644 --- a/src/Umbraco.Core/Models/ContentEditing/Language.cs +++ b/src/Umbraco.Core/Models/ContentEditing/Language.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -6,17 +6,21 @@ namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "language", Namespace = "")] public class Language { - [DataMember(Name = "id")] public int Id { get; set; } + [DataMember(Name = "id")] + public int Id { get; set; } [DataMember(Name = "culture", IsRequired = true)] [Required(AllowEmptyStrings = false)] public string IsoCode { get; set; } = null!; - [DataMember(Name = "name")] public string? Name { get; set; } + [DataMember(Name = "name")] + public string? Name { get; set; } - [DataMember(Name = "isDefault")] public bool IsDefault { get; set; } + [DataMember(Name = "isDefault")] + public bool IsDefault { get; set; } - [DataMember(Name = "isMandatory")] public bool IsMandatory { get; set; } + [DataMember(Name = "isMandatory")] + public bool IsMandatory { get; set; } [DataMember(Name = "fallbackLanguageId")] public int? FallbackLanguageId { get; set; } diff --git a/src/Umbraco.Core/Models/ContentEditing/LinkDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/LinkDisplay.cs index e36e75fa0abb..9b7bde570d70 100644 --- a/src/Umbraco.Core/Models/ContentEditing/LinkDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/LinkDisplay.cs @@ -1,23 +1,31 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "link", Namespace = "")] public class LinkDisplay { - [DataMember(Name = "icon")] public string? Icon { get; set; } + [DataMember(Name = "icon")] + public string? Icon { get; set; } - [DataMember(Name = "name")] public string? Name { get; set; } + [DataMember(Name = "name")] + public string? Name { get; set; } - [DataMember(Name = "published")] public bool Published { get; set; } + [DataMember(Name = "published")] + public bool Published { get; set; } - [DataMember(Name = "queryString")] public string? QueryString { get; set; } + [DataMember(Name = "queryString")] + public string? QueryString { get; set; } - [DataMember(Name = "target")] public string? Target { get; set; } + [DataMember(Name = "target")] + public string? Target { get; set; } - [DataMember(Name = "trashed")] public bool Trashed { get; set; } + [DataMember(Name = "trashed")] + public bool Trashed { get; set; } - [DataMember(Name = "udi")] public GuidUdi? Udi { get; set; } + [DataMember(Name = "udi")] + public GuidUdi? Udi { get; set; } - [DataMember(Name = "url")] public string? Url { get; set; } + [DataMember(Name = "url")] + public string? Url { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/ListViewAwareContentItemDisplayBase.cs b/src/Umbraco.Core/Models/ContentEditing/ListViewAwareContentItemDisplayBase.cs index 57bedd7f28bf..1add8da7d851 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ListViewAwareContentItemDisplayBase.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ListViewAwareContentItemDisplayBase.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/MacroDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/MacroDisplay.cs index 9daaa3640709..9919004a5036 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MacroDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MacroDisplay.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/MacroParameter.cs b/src/Umbraco.Core/Models/ContentEditing/MacroParameter.cs index dca97e962cd7..3db1cd5820c5 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MacroParameter.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MacroParameter.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -13,9 +13,11 @@ public class MacroParameter [Required] public string Alias { get; set; } = null!; - [DataMember(Name = "name")] public string? Name { get; set; } + [DataMember(Name = "name")] + public string? Name { get; set; } - [DataMember(Name = "sortOrder")] public int SortOrder { get; set; } + [DataMember(Name = "sortOrder")] + public int SortOrder { get; set; } /// /// The editor view to render for this parameter diff --git a/src/Umbraco.Core/Models/ContentEditing/MacroParameterDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/MacroParameterDisplay.cs index e24fd5a53db4..3a532fcc12d4 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MacroParameterDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MacroParameterDisplay.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/MediaItemDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/MediaItemDisplay.cs index e913eb926bb8..784e5510fb58 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MediaItemDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MediaItemDisplay.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -10,9 +10,12 @@ public class MediaItemDisplay : ListViewAwareContentItemDisplayBase ContentApps = new List(); - [DataMember(Name = "contentType")] public ContentTypeBasic? ContentType { get; set; } + [DataMember(Name = "contentType")] + public ContentTypeBasic? ContentType { get; set; } - [DataMember(Name = "mediaLink")] public string? MediaLink { get; set; } + [DataMember(Name = "mediaLink")] + public string? MediaLink { get; set; } - [DataMember(Name = "apps")] public IEnumerable ContentApps { get; set; } + [DataMember(Name = "apps")] + public IEnumerable ContentApps { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MediaItemSave.cs b/src/Umbraco.Core/Models/ContentEditing/MediaItemSave.cs index 52fdb575d83c..7bac43b25dcd 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MediaItemSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MediaItemSave.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs index 1a55a7219bc2..2f449f8ad920 100644 --- a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs +++ b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs @@ -16,7 +16,8 @@ public static void SetCultureInfo(this IContentBase content, string? culture, st if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(name)); } @@ -27,7 +28,8 @@ public static void SetCultureInfo(this IContentBase content, string? culture, st if (string.IsNullOrWhiteSpace(culture)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(culture)); } @@ -89,7 +91,6 @@ public static void AdjustDates(this IContent content, DateTime date, bool publis } } - if (!publishing) { return; @@ -134,7 +135,7 @@ public static void AdjustDates(this IContent content, DateTime date, bool publis return Array.Empty(); } - IEnumerable culturesUnpublishing = content.CultureInfos?.Values + IEnumerable? culturesUnpublishing = content.CultureInfos?.Values .Where(x => content.IsPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + x.Culture)) .Select(x => x.Culture); @@ -212,7 +213,6 @@ public static void CopyFrom(this IContent content, IContent other, string? cultu } // copy names, too - if (culture == "*") { content.CultureInfos?.Clear(); @@ -246,7 +246,8 @@ public static void SetPublishInfo(this IContent content, string? culture, string if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(name)); } @@ -257,7 +258,8 @@ public static void SetPublishInfo(this IContent content, string? culture, string if (string.IsNullOrWhiteSpace(culture)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(culture)); } @@ -273,7 +275,8 @@ public static void SetCultureEdited(this IContent content, IEnumerable? } else { - var editedCultures = new HashSet(cultures.Where(x => !x.IsNullOrWhiteSpace())!, + var editedCultures = new HashSet( + cultures.Where(x => !x.IsNullOrWhiteSpace())!, StringComparer.OrdinalIgnoreCase); content.EditedCultures = editedCultures.Count > 0 ? editedCultures : null; } @@ -324,6 +327,7 @@ public static bool PublishCulture(this IContent content, CultureImpact? impact) { return false; } + // PublishName set by repository - nothing to do here } else if (impact.ImpactsExplicitCulture) @@ -418,7 +422,8 @@ public static bool ClearPublishInfo(this IContent content, string? culture) if (string.IsNullOrWhiteSpace(culture)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(culture)); } diff --git a/src/Umbraco.Core/Models/ContentSchedule.cs b/src/Umbraco.Core/Models/ContentSchedule.cs index 589a051f7656..18d254a9aabe 100644 --- a/src/Umbraco.Core/Models/ContentSchedule.cs +++ b/src/Umbraco.Core/Models/ContentSchedule.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Models; @@ -65,7 +65,10 @@ public override bool Equals(object? obj) => obj is ContentSchedule other && Equals(other); public bool Equals(ContentSchedule other) => + // don't compare Ids, two ContentSchedule are equal if they are for the same change // for the same culture, on the same date - and the collection deals w/duplicates Culture.InvariantEquals(other.Culture) && Date == other.Date && Action == other.Action; + + public override int GetHashCode() => throw new NotImplementedException(); } diff --git a/src/Umbraco.Core/Models/ContentScheduleAction.cs b/src/Umbraco.Core/Models/ContentScheduleAction.cs index ab6452c64ade..d6a50b994b7a 100644 --- a/src/Umbraco.Core/Models/ContentScheduleAction.cs +++ b/src/Umbraco.Core/Models/ContentScheduleAction.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; /// /// Defines scheduled actions for documents. @@ -13,5 +13,5 @@ public enum ContentScheduleAction /// /// Expire the document. /// - Expire + Expire, } diff --git a/src/Umbraco.Core/Models/ContentScheduleCollection.cs b/src/Umbraco.Core/Models/ContentScheduleCollection.cs index 4f333bd3a31f..4fb90779de5c 100644 --- a/src/Umbraco.Core/Models/ContentScheduleCollection.cs +++ b/src/Umbraco.Core/Models/ContentScheduleCollection.cs @@ -4,10 +4,12 @@ namespace Umbraco.Cms.Core.Models; public class ContentScheduleCollection : INotifyCollectionChanged, IDeepCloneable, IEquatable { - //underlying storage for the collection backed by a sorted list so that the schedule is always in order of date and that duplicate dates per culture are not allowed + // underlying storage for the collection backed by a sorted list so that the schedule is always in order of date and that duplicate dates per culture are not allowed private readonly Dictionary> _schedule = new(StringComparer.InvariantCultureIgnoreCase); + public event NotifyCollectionChangedEventHandler? CollectionChanged; + /// /// Returns all schedules registered /// @@ -49,7 +51,7 @@ public bool Equals(ContentScheduleCollection? other) foreach ((var culture, SortedList thisList) in thisSched) { // if culture is missing, or actions differ, false - if (!thatSched.TryGetValue(culture, out SortedList thatList) || + if (!thatSched.TryGetValue(culture, out SortedList? thatList) || !thatList.SequenceEqual(thisList)) { return false; @@ -59,22 +61,25 @@ public bool Equals(ContentScheduleCollection? other) return true; } - public event NotifyCollectionChangedEventHandler? CollectionChanged; + public static ContentScheduleCollection CreateWithEntry(DateTime? release, DateTime? expire) + { + var schedule = new ContentScheduleCollection(); + schedule.Add(string.Empty, release, expire); + return schedule; + } /// /// Clears all event handlers /// public void ClearCollectionChangedEvents() => CollectionChanged = null; - private void OnCollectionChanged(NotifyCollectionChangedEventArgs args) => CollectionChanged?.Invoke(this, args); - /// /// Add an existing schedule /// /// public void Add(ContentSchedule schedule) { - if (!_schedule.TryGetValue(schedule.Culture, out SortedList changes)) + if (!_schedule.TryGetValue(schedule.Culture, out SortedList? changes)) { changes = new SortedList(); _schedule[schedule.Culture] = changes; @@ -86,6 +91,8 @@ public void Add(ContentSchedule schedule) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, schedule)); } + private void OnCollectionChanged(NotifyCollectionChangedEventArgs args) => CollectionChanged?.Invoke(this, args); + /// /// Adds a new schedule for invariant content /// @@ -118,8 +125,7 @@ public bool Add(string? culture, DateTime? releaseDate, DateTime? expireDate) } // TODO: Do we allow passing in a release or expiry date that is before now? - - if (!_schedule.TryGetValue(culture, out SortedList changes)) + if (!_schedule.TryGetValue(culture, out SortedList? changes)) { changes = new SortedList(); _schedule[culture] = changes; @@ -127,7 +133,6 @@ public bool Add(string? culture, DateTime? releaseDate, DateTime? expireDate) // TODO: Below will throw if there are duplicate dates added, should validate/return bool? // but the bool won't indicate which date was in error, maybe have 2 diff methods to schedule start/end? - if (releaseDate.HasValue) { var entry = new ContentSchedule(culture, releaseDate.Value, ContentScheduleAction.Release); @@ -151,7 +156,7 @@ public bool Add(string? culture, DateTime? releaseDate, DateTime? expireDate) /// public void Remove(ContentSchedule change) { - if (_schedule.TryGetValue(change.Culture, out SortedList s)) + if (_schedule.TryGetValue(change.Culture, out SortedList? s)) { var removed = s.Remove(change.Date); if (removed) @@ -181,7 +186,7 @@ public void Clear(ContentScheduleAction action, DateTime? changeDate = null) => /// If specified, will clear all entries with dates less than or equal to the value public void Clear(string? culture, ContentScheduleAction action, DateTime? date = null) { - if (culture is null || !_schedule.TryGetValue(culture, out SortedList schedules)) + if (culture is null || !_schedule.TryGetValue(culture, out SortedList? schedules)) { return; } @@ -231,7 +236,7 @@ public IEnumerable GetSchedule(ContentScheduleAction? action = /// public IEnumerable GetSchedule(string? culture, ContentScheduleAction? action = null) { - if (culture is not null && _schedule.TryGetValue(culture, out SortedList changes)) + if (culture is not null && _schedule.TryGetValue(culture, out SortedList? changes)) { return action == null ? changes.Values : changes.Values.Where(x => x.Action == action.Value); } @@ -242,17 +247,15 @@ public IEnumerable GetSchedule(string? culture, ContentSchedule public override bool Equals(object? obj) => obj is ContentScheduleCollection other && Equals(other); - public static ContentScheduleCollection CreateWithEntry(DateTime? release, DateTime? expire) + public static ContentScheduleCollection CreateWithEntry(string culture, DateTime? release, DateTime? expire) { var schedule = new ContentScheduleCollection(); - schedule.Add(string.Empty, release, expire); + schedule.Add(culture, release, expire); return schedule; } - public static ContentScheduleCollection CreateWithEntry(string culture, DateTime? release, DateTime? expire) + public override int GetHashCode() { - var schedule = new ContentScheduleCollection(); - schedule.Add(culture, release, expire); - return schedule; + throw new NotImplementedException(); } } diff --git a/src/Umbraco.Core/Models/ContentStatus.cs b/src/Umbraco.Core/Models/ContentStatus.cs index 8d59dc9f9da4..1fd1eeaa8aea 100644 --- a/src/Umbraco.Core/Models/ContentStatus.cs +++ b/src/Umbraco.Core/Models/ContentStatus.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; @@ -15,25 +15,30 @@ public enum ContentStatus /// /// The document is not trashed, and not published. /// - [EnumMember] Unpublished, + [EnumMember] + Unpublished, /// /// The document is published. /// - [EnumMember] Published, + [EnumMember] + Published, /// /// The document is not trashed, not published, after being unpublished by a scheduled action. /// - [EnumMember] Expired, + [EnumMember] + Expired, /// /// The document is trashed. /// - [EnumMember] Trashed, + [EnumMember] + Trashed, /// /// The document is not trashed, not published, and pending publication by a scheduled action. /// - [EnumMember] AwaitingRelease + [EnumMember] + AwaitingRelease, } diff --git a/src/Umbraco.Core/Models/ContentTagsExtensions.cs b/src/Umbraco.Core/Models/ContentTagsExtensions.cs index d0f76de4f9f1..1d52300460bd 100644 --- a/src/Umbraco.Core/Models/ContentTagsExtensions.cs +++ b/src/Umbraco.Core/Models/ContentTagsExtensions.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; @@ -20,9 +20,17 @@ public static class ContentTagsExtensions /// A value indicating whether to merge the tags with existing tags instead of replacing them. /// A culture, for multi-lingual properties. /// - public static void AssignTags(this IContentBase content, PropertyEditorCollection propertyEditors, - IDataTypeService dataTypeService, IJsonSerializer serializer, string propertyTypeAlias, - IEnumerable tags, bool merge = false, string? culture = null) => content + /// + public static void AssignTags( + this IContentBase content, + PropertyEditorCollection propertyEditors, + IDataTypeService dataTypeService, + IJsonSerializer serializer, + string propertyTypeAlias, + IEnumerable tags, + bool merge = false, + string? culture = null) => + content .GetTagProperty(propertyTypeAlias) .AssignTags(propertyEditors, dataTypeService, serializer, tags, merge, culture); @@ -35,9 +43,16 @@ public static void AssignTags(this IContentBase content, PropertyEditorCollectio /// The tags. /// A culture, for multi-lingual properties. /// - public static void RemoveTags(this IContentBase content, PropertyEditorCollection propertyEditors, - IDataTypeService dataTypeService, IJsonSerializer serializer, string propertyTypeAlias, - IEnumerable tags, string? culture = null) => content.GetTagProperty(propertyTypeAlias) + /// + public static void RemoveTags( + this IContentBase content, + PropertyEditorCollection propertyEditors, + IDataTypeService dataTypeService, + IJsonSerializer serializer, + string propertyTypeAlias, + IEnumerable tags, + string? culture = null) => + content.GetTagProperty(propertyTypeAlias) .RemoveTags(propertyEditors, dataTypeService, serializer, tags, culture); // gets and validates the property @@ -48,7 +63,7 @@ private static IProperty GetTagProperty(this IContentBase content, string proper throw new ArgumentNullException(nameof(content)); } - IProperty property = content.Properties[propertyTypeAlias]; + IProperty? property = content.Properties[propertyTypeAlias]; if (property != null) { return property; diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index 2f84336c83f8..f4fe617a8355 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -30,19 +30,21 @@ public class ContentType : ContentTypeCompositionBase, IContentTypeWithHistoryCl /// /// Only use this for creating ContentTypes at the root (with ParentId -1). /// - public ContentType(IShortStringHelper shortStringHelper, int parentId) : base(shortStringHelper, parentId) + /// + public ContentType(IShortStringHelper shortStringHelper, int parentId) + : base(shortStringHelper, parentId) { _allowedTemplates = new List(); HistoryCleanup = new HistoryCleanup(); } - /// /// Constuctor for creating a ContentType with the parent as an inherited type. /// /// Use this to ensure inheritance from parent. /// /// + /// public ContentType(IShortStringHelper shortStringHelper, IContentType parent, string alias) : base(shortStringHelper, parent, alias) { @@ -53,9 +55,6 @@ public ContentType(IShortStringHelper shortStringHelper, IContentType parent, st /// public override bool SupportsPublishing => SupportsPublishingConst; - /// - public override ISimpleContentType ToSimple() => new SimpleContentType(this); - /// /// Gets or sets the alias of the default Template. /// TODO: This should be ignored from cloning!!!!!!!!!!!!!! @@ -66,6 +65,8 @@ public ContentType(IShortStringHelper shortStringHelper, IContentType parent, st public ITemplate? DefaultTemplate => AllowedTemplates?.FirstOrDefault(x => x != null && x.Id == DefaultTemplateId); + /// + public override ISimpleContentType ToSimple() => new SimpleContentType(this); [DataMember] public int DefaultTemplateId diff --git a/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResult.cs b/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResult.cs index 44bc7884adcb..c4ab790dfe6c 100644 --- a/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResult.cs +++ b/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResult.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; /// /// Used when determining available compositions for a given content type @@ -12,5 +12,6 @@ public ContentTypeAvailableCompositionsResult(IContentTypeComposition compositio } public IContentTypeComposition Composition { get; } + public bool Allowed { get; } } diff --git a/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResults.cs b/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResults.cs index eaac950c2daa..4dc268faf368 100644 --- a/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResults.cs +++ b/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResults.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; /// /// Used when determining available compositions for a given content type @@ -11,7 +11,8 @@ public ContentTypeAvailableCompositionsResults() Results = Enumerable.Empty(); } - public ContentTypeAvailableCompositionsResults(IEnumerable ancestors, + public ContentTypeAvailableCompositionsResults( + IEnumerable ancestors, IEnumerable results) { Ancestors = ancestors; @@ -19,5 +20,6 @@ public ContentTypeAvailableCompositionsResults(IEnumerable Ancestors { get; } + public IEnumerable Results { get; } } diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 34aa4097a305..6131e1b6809d 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -15,7 +15,7 @@ namespace Umbraco.Cms.Core.Models; [DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] public abstract class ContentTypeBase : TreeEntityBase, IContentTypeBase { - //Custom comparer for enumerable + // Custom comparer for enumerable private static readonly DelegateEqualityComparer> ContentTypeSortComparer = new( (sorts, enumerable) => sorts.UnsortedSequenceEqual(enumerable), @@ -101,6 +101,19 @@ protected ContentTypeBase(IShortStringHelper shortStringHelper, IContentTypeBase /// public abstract bool SupportsPublishing { get; } + /// + /// The Alias of the ContentType + /// + [DataMember] + public virtual string Alias + { + get => _alias; + set => SetPropertyValueAndDetectChanges( + value.ToCleanString(_shortStringHelper, CleanStringType.Alias | CleanStringType.UmbracoCase), + ref _alias!, + nameof(Alias)); + } + /// /// A boolean flag indicating if a property type has been removed from this instance. /// @@ -123,24 +136,12 @@ private set /// PropertyTypes that are not part of a PropertyGroup /// [IgnoreDataMember] + // TODO: should we mark this as EditorBrowsable hidden since it really isn't ever used? internal PropertyTypeCollection PropertyTypeCollection { get; private set; } public abstract ISimpleContentType ToSimple(); - /// - /// The Alias of the ContentType - /// - [DataMember] - public virtual string Alias - { - get => _alias; - set => SetPropertyValueAndDetectChanges( - value.ToCleanString(_shortStringHelper, CleanStringType.Alias | CleanStringType.UmbracoCase), - ref _alias!, - nameof(Alias)); - } - /// /// Description for the ContentType /// @@ -209,8 +210,7 @@ public bool IsElement public IEnumerable? AllowedContentTypes { get => _allowedContentTypes; - set => SetPropertyValueAndDetectChanges(value, ref _allowedContentTypes, nameof(AllowedContentTypes), - ContentTypeSortComparer); + set => SetPropertyValueAndDetectChanges(value, ref _allowedContentTypes, nameof(AllowedContentTypes), ContentTypeSortComparer); } /// @@ -222,20 +222,6 @@ public virtual ContentVariation Variations set => SetPropertyValueAndDetectChanges(value, ref _variations, nameof(Variations)); } - /// - public bool SupportsVariation(string culture, string segment, bool wildcards = false) => - // exact validation: cannot accept a 'null' culture if the property type varies - // by culture, and likewise for segment - // wildcard validation: can accept a '*' culture or segment - Variations.ValidateVariation(culture, segment, true, wildcards, false); - - /// - public bool SupportsPropertyVariation(string culture, string segment, bool wildcards = false) => - // non-exact validation: can accept a 'null' culture if the property type varies - // by culture, and likewise for segment - // wildcard validation: can accept a '*' culture or segment - Variations.ValidateVariation(culture, segment, false, true, false); - /// /// /// A PropertyGroup corresponds to a Tab in the UI @@ -250,11 +236,28 @@ public PropertyGroupCollection PropertyGroups { _propertyGroups = value; _propertyGroups.CollectionChanged += PropertyGroupsChanged; - PropertyGroupsChanged(_propertyGroups, + PropertyGroupsChanged( + _propertyGroups, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } + /// + public bool SupportsVariation(string culture, string segment, bool wildcards = false) => + + // exact validation: cannot accept a 'null' culture if the property type varies + // by culture, and likewise for segment + // wildcard validation: can accept a '*' culture or segment + Variations.ValidateVariation(culture, segment, true, wildcards, false); + + /// + public bool SupportsPropertyVariation(string culture, string segment, bool wildcards = false) => + + // non-exact validation: can accept a 'null' culture if the property type varies + // by culture, and likewise for segment + // wildcard validation: can accept a '*' culture or segment + Variations.ValidateVariation(culture, segment, false, true, false); + /// [IgnoreDataMember] [DoNotClone] @@ -275,7 +278,8 @@ public IEnumerable NoGroupPropertyTypes PropertyTypeCollection = new PropertyTypeCollection(SupportsPublishing, value); PropertyTypeCollection.CollectionChanged += PropertyTypesChanged; - PropertyTypesChanged(PropertyTypeCollection, + PropertyTypesChanged( + PropertyTypeCollection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } @@ -291,8 +295,7 @@ public IEnumerable NoGroupPropertyTypes public abstract bool AddPropertyGroup(string alias, string name); /// - public abstract bool AddPropertyType(IPropertyType propertyType, string propertyGroupAlias, - string? propertyGroupName = null); + public abstract bool AddPropertyType(IPropertyType propertyType, string propertyGroupAlias, string? propertyGroupName = null); /// /// Adds a PropertyType, which does not belong to a PropertyGroup. @@ -323,7 +326,7 @@ public bool AddPropertyType(IPropertyType propertyType) public bool MovePropertyType(string propertyTypeAlias, string propertyGroupAlias) { // get property, ensure it exists - IPropertyType propertyType = PropertyTypes.FirstOrDefault(x => x.Alias == propertyTypeAlias); + IPropertyType? propertyType = PropertyTypes.FirstOrDefault(x => x.Alias == propertyTypeAlias); if (propertyType == null) { return false; @@ -343,8 +346,7 @@ public bool MovePropertyType(string propertyTypeAlias, string propertyGroupAlias } // get old group - PropertyGroup oldPropertyGroup = PropertyGroups.FirstOrDefault(x => - x.PropertyTypes?.Any(y => y.Alias == propertyTypeAlias) ?? false); + PropertyGroup? oldPropertyGroup = PropertyGroups.FirstOrDefault(x => x.PropertyTypes?.Any(y => y.Alias == propertyTypeAlias) ?? false); // set new group propertyType.PropertyGroupId = @@ -363,7 +365,7 @@ public bool MovePropertyType(string propertyTypeAlias, string propertyGroupAlias /// Alias of the to remove public void RemovePropertyType(string alias) { - //check through each property group to see if we can remove the property type by alias from it + // check through each property group to see if we can remove the property type by alias from it foreach (PropertyGroup propertyGroup in PropertyGroups) { if (propertyGroup.PropertyTypes?.RemoveItem(alias) ?? false) @@ -378,7 +380,7 @@ public void RemovePropertyType(string alias) } } - //check through each local property type collection (not assigned to a tab) + // check through each local property type collection (not assigned to a tab) if (PropertyTypeCollection.RemoveItem(alias)) { if (!HasPropertyTypeBeenRemoved) @@ -445,7 +447,7 @@ public override void ResetDirtyProperties() { base.ResetDirtyProperties(); - //loop through each property group to reset the property types + // loop through each property group to reset the property types var propertiesReset = new List(); foreach (PropertyGroup propertyGroup in PropertyGroups) @@ -461,31 +463,54 @@ public override void ResetDirtyProperties() } } - //then loop through our property type collection since some might not exist on a property group - //but don't re-reset ones we've already done. + // then loop through our property type collection since some might not exist on a property group + // but don't re-reset ones we've already done. foreach (IPropertyType propertyType in PropertyTypes.Where(x => propertiesReset.Contains(x.Id) == false)) { propertyType.ResetDirtyProperties(); } } + public ContentTypeBase DeepCloneWithResetIdentities(string alias) + { + var clone = (ContentTypeBase)DeepClone(); + clone.Alias = alias; + clone.Key = Guid.Empty; + foreach (PropertyGroup propertyGroup in clone.PropertyGroups) + { + propertyGroup.ResetIdentity(); + propertyGroup.ResetDirtyProperties(false); + } + + foreach (IPropertyType propertyType in clone.PropertyTypes) + { + propertyType.ResetIdentity(); + propertyType.ResetDirtyProperties(false); + } + + clone.ResetIdentity(); + clone.ResetDirtyProperties(false); + return clone; + } + protected void PropertyGroupsChanged(object? sender, NotifyCollectionChangedEventArgs e) => OnPropertyChanged(nameof(PropertyGroups)); protected void PropertyTypesChanged(object? sender, NotifyCollectionChangedEventArgs e) => - //enable this to detect duplicate property aliases. We do want this, however making this change in a - //patch release might be a little dangerous + + // enable this to detect duplicate property aliases. We do want this, however making this change in a + // patch release might be a little dangerous ////detect if there are any duplicate aliases - this cannot be allowed - //if (e.Action == NotifyCollectionChangedAction.Add + // if (e.Action == NotifyCollectionChangedAction.Add // || e.Action == NotifyCollectionChangedAction.Replace) - //{ + // { // var allAliases = _noGroupPropertyTypes.Concat(PropertyGroups.SelectMany(x => x.PropertyTypes)).Select(x => x.Alias); // if (allAliases.HasDuplicates(false)) // { // var newAliases = string.Join(", ", e.NewItems.Cast().Select(x => x.Alias)); // throw new InvalidOperationException($"Other property types already exist with the aliases: {newAliases}"); // } - //} + // } OnPropertyChanged(nameof(PropertyTypes)); protected override void PerformDeepClone(object clone) @@ -496,45 +521,22 @@ protected override void PerformDeepClone(object clone) if (clonedEntity.PropertyTypeCollection != null) { - //need to manually wire up the event handlers for the property type collections - we've ensured + // need to manually wire up the event handlers for the property type collections - we've ensured // its ignored from the auto-clone process because its return values are unions, not raw and // we end up with duplicates, see: http://issues.umbraco.org/issue/U4-4842 - - clonedEntity.PropertyTypeCollection.ClearCollectionChangedEvents(); //clear this event handler if any + clonedEntity.PropertyTypeCollection.ClearCollectionChangedEvents(); // clear this event handler if any clonedEntity.PropertyTypeCollection = - (PropertyTypeCollection)PropertyTypeCollection.DeepClone(); //manually deep clone + (PropertyTypeCollection)PropertyTypeCollection.DeepClone(); // manually deep clone clonedEntity.PropertyTypeCollection.CollectionChanged += - clonedEntity.PropertyTypesChanged; //re-assign correct event handler + clonedEntity.PropertyTypesChanged; // re-assign correct event handler } if (clonedEntity._propertyGroups != null) { - clonedEntity._propertyGroups.ClearCollectionChangedEvents(); //clear this event handler if any - clonedEntity._propertyGroups = (PropertyGroupCollection)_propertyGroups.DeepClone(); //manually deep clone + clonedEntity._propertyGroups.ClearCollectionChangedEvents(); // clear this event handler if any + clonedEntity._propertyGroups = (PropertyGroupCollection)_propertyGroups.DeepClone(); // manually deep clone clonedEntity._propertyGroups.CollectionChanged += - clonedEntity.PropertyGroupsChanged; //re-assign correct event handler + clonedEntity.PropertyGroupsChanged; // re-assign correct event handler } } - - public ContentTypeBase DeepCloneWithResetIdentities(string alias) - { - var clone = (ContentTypeBase)DeepClone(); - clone.Alias = alias; - clone.Key = Guid.Empty; - foreach (PropertyGroup propertyGroup in clone.PropertyGroups) - { - propertyGroup.ResetIdentity(); - propertyGroup.ResetDirtyProperties(false); - } - - foreach (IPropertyType propertyType in clone.PropertyTypes) - { - propertyType.ResetIdentity(); - propertyType.ResetDirtyProperties(false); - } - - clone.ResetIdentity(); - clone.ResetDirtyProperties(false); - return clone; - } } diff --git a/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs b/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs index 765e79b4fd58..12e0e5a13823 100644 --- a/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs +++ b/src/Umbraco.Core/Models/ContentTypeBaseExtensions.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Extensions; @@ -44,8 +44,10 @@ public static bool WasPropertyTypeVariationChanged(this IContentTypeBase content /// Used to check if any property type was changed between variant/invariant /// /// + /// /// - internal static bool WasPropertyTypeVariationChanged(this IContentTypeBase contentType, + internal static bool WasPropertyTypeVariationChanged( + this IContentTypeBase contentType, out IReadOnlyCollection aliases) { var a = new List(); diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs index a86284ff39d2..b7b9af6231c4 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -24,8 +24,7 @@ protected ContentTypeCompositionBase(IShortStringHelper shortStringHelper, ICont { } - protected ContentTypeCompositionBase(IShortStringHelper shortStringHelper, IContentTypeComposition parent, - string alias) + protected ContentTypeCompositionBase(IShortStringHelper shortStringHelper, IContentTypeComposition parent, string alias) : base(shortStringHelper, parent, alias) => AddContentType(parent); @@ -57,7 +56,6 @@ public IEnumerable CompositionPropertyGroups // it would be nice to cache the resulting enumerable, but alas we cannot, otherwise // any change to compositions are ignored and that breaks many things - and tracking // changes to refresh the cache would be expensive. - void AcquireProperty(IPropertyType propertyType) { propertyType.Variations &= Variations; @@ -91,7 +89,6 @@ public IEnumerable CompositionPropertyTypes // so that we can change their variation according to this content type variations. // // see note in CompositionPropertyGroups for comments on caching the resulting enumerable - IPropertyType AcquireProperty(IPropertyType propertyType) { propertyType = (IPropertyType)propertyType.DeepClone(); @@ -143,8 +140,7 @@ public bool AddContentType(IContentTypeComposition? contentType) if (conflictingPropertyTypeAliases.Any()) { - throw new InvalidCompositionException(Alias, contentType.Alias, - conflictingPropertyTypeAliases.ToArray()); + throw new InvalidCompositionException(Alias, contentType.Alias, conflictingPropertyTypeAliases.ToArray()); } _contentTypeComposition.Add(contentType); @@ -166,9 +162,10 @@ public bool RemoveContentType(string alias) { if (ContentTypeCompositionExists(alias)) { - IContentTypeComposition contentTypeComposition = - ContentTypeComposition.FirstOrDefault(x => x.Alias == alias); - if (contentTypeComposition == null) // You can't remove a composition from another composition + IContentTypeComposition? contentTypeComposition = ContentTypeComposition.FirstOrDefault(x => x.Alias == alias); + + // You can't remove a composition from another composition + if (contentTypeComposition == null) { return false; } @@ -221,8 +218,7 @@ public bool ContentTypeCompositionExists(string alias) public override bool AddPropertyGroup(string alias, string name) => AddAndReturnPropertyGroup(alias, name) != null; /// - public override bool AddPropertyType(IPropertyType propertyType, string propertyGroupAlias, - string? propertyGroupName = null) + public override bool AddPropertyType(IPropertyType propertyType, string propertyGroupAlias, string? propertyGroupName = null) { // ensure no duplicate alias - over all composition properties if (PropertyTypeExists(propertyType.Alias)) @@ -278,6 +274,18 @@ public IEnumerable CompositionIds() .Select(x => x.Id) .Union(ContentTypeComposition.SelectMany(x => x.CompositionIds())); + protected override void PerformDeepClone(object clone) + { + base.PerformDeepClone(clone); + + var clonedEntity = (ContentTypeCompositionBase)clone; + + // need to manually assign since this is an internal field and will not be automatically mapped + clonedEntity._removedContentTypeKeyTracker = new List(); + clonedEntity._contentTypeComposition = + ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList(); + } + private IEnumerable GetRawComposedPropertyTypes(bool start = true) { IEnumerable propertyTypes = ContentTypeComposition @@ -301,16 +309,16 @@ private IEnumerable GetRawComposedPropertyTypes(bool start = true } // Add new group - var group = new PropertyGroup(SupportsPublishing) {Alias = alias, Name = name}; + var group = new PropertyGroup(SupportsPublishing) { Alias = alias, Name = name }; // check if it is inherited - there might be more than 1 but we want the 1st, to // reuse its sort order - if there are more than 1 and they have different sort // orders... there isn't much we can do anyways - PropertyGroup inheritGroup = CompositionPropertyGroups.FirstOrDefault(x => x.Alias == alias); + PropertyGroup? inheritGroup = CompositionPropertyGroups.FirstOrDefault(x => x.Alias == alias); if (inheritGroup == null) { // no, just local, set sort order - PropertyGroup lastGroup = PropertyGroups.LastOrDefault(); + PropertyGroup? lastGroup = PropertyGroups.LastOrDefault(); if (lastGroup != null) { group.SortOrder = lastGroup.SortOrder + 1; @@ -327,16 +335,4 @@ private IEnumerable GetRawComposedPropertyTypes(bool start = true return group; } - - protected override void PerformDeepClone(object clone) - { - base.PerformDeepClone(clone); - - var clonedEntity = (ContentTypeCompositionBase)clone; - - // need to manually assign since this is an internal field and will not be automatically mapped - clonedEntity._removedContentTypeKeyTracker = new List(); - clonedEntity._contentTypeComposition = - ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList(); - } } diff --git a/src/Umbraco.Core/Models/ContentTypeImportModel.cs b/src/Umbraco.Core/Models/ContentTypeImportModel.cs index de11555b7725..5de62fcffa68 100644 --- a/src/Umbraco.Core/Models/ContentTypeImportModel.cs +++ b/src/Umbraco.Core/Models/ContentTypeImportModel.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.ContentEditing; namespace Umbraco.Cms.Core.Models; @@ -6,11 +6,15 @@ namespace Umbraco.Cms.Core.Models; [DataContract(Name = "contentTypeImportModel")] public class ContentTypeImportModel : INotificationModel { - [DataMember(Name = "alias")] public string? Alias { get; set; } + [DataMember(Name = "alias")] + public string? Alias { get; set; } - [DataMember(Name = "name")] public string? Name { get; set; } + [DataMember(Name = "name")] + public string? Name { get; set; } - [DataMember(Name = "tempFileName")] public string? TempFileName { get; set; } + [DataMember(Name = "tempFileName")] + public string? TempFileName { get; set; } - [DataMember(Name = "notifications")] public List Notifications { get; } = new(); + [DataMember(Name = "notifications")] + public List Notifications { get; } = new(); } diff --git a/src/Umbraco.Core/Models/ContentTypeSort.cs b/src/Umbraco.Core/Models/ContentTypeSort.cs index ecbf62e195c0..e10d650cac8d 100644 --- a/src/Umbraco.Core/Models/ContentTypeSort.cs +++ b/src/Umbraco.Core/Models/ContentTypeSort.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; @@ -8,7 +8,9 @@ namespace Umbraco.Cms.Core.Models; public class ContentTypeSort : IValueObject, IDeepCloneable { // this parameterless ctor should never be used BUT is required by AutoMapper in EntityMapperProfile - public ContentTypeSort() { } + public ContentTypeSort() + { + } /// /// Initializes a new instance of the class. @@ -41,7 +43,6 @@ public ContentTypeSort(Lazy id, int sortOrder, string alias) /// public string Alias { get; set; } = string.Empty; - public object DeepClone() { var clone = (ContentTypeSort)MemberwiseClone(); @@ -50,9 +51,6 @@ public object DeepClone() return clone; } - protected bool Equals(ContentTypeSort other) => - Id.Value.Equals(other.Id.Value) && string.Equals(Alias, other.Alias); - public override bool Equals(object? obj) { if (ReferenceEquals(null, obj)) @@ -73,12 +71,15 @@ public override bool Equals(object? obj) return Equals((ContentTypeSort)obj); } + protected bool Equals(ContentTypeSort other) => + Id.Value.Equals(other.Id.Value) && string.Equals(Alias, other.Alias); + public override int GetHashCode() { unchecked { - //The hash code will just be the alias if one is assigned, otherwise it will be the hash code of the Id. - //In some cases the alias can be null of the non lazy ctor is used, in that case, the lazy Id will already have a value created. + // The hash code will just be the alias if one is assigned, otherwise it will be the hash code of the Id. + // In some cases the alias can be null of the non lazy ctor is used, in that case, the lazy Id will already have a value created. return Alias != null ? Alias.GetHashCode() : Id.Value.GetHashCode() * 397; } } diff --git a/src/Umbraco.Core/Models/ContentVariation.cs b/src/Umbraco.Core/Models/ContentVariation.cs index 939f3d10d338..019da0eee01e 100644 --- a/src/Umbraco.Core/Models/ContentVariation.cs +++ b/src/Umbraco.Core/Models/ContentVariation.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; /// /// Indicates how values can vary. @@ -32,5 +32,5 @@ public enum ContentVariation : byte /// /// Values vary by culture and segment. /// - CultureAndSegment = Culture | Segment + CultureAndSegment = Culture | Segment, } diff --git a/src/Umbraco.Core/Models/ContentVersionMeta.cs b/src/Umbraco.Core/Models/ContentVersionMeta.cs index 94b93abf744e..cf95257716ce 100644 --- a/src/Umbraco.Core/Models/ContentVersionMeta.cs +++ b/src/Umbraco.Core/Models/ContentVersionMeta.cs @@ -1,8 +1,10 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; public class ContentVersionMeta { - public ContentVersionMeta() { } + public ContentVersionMeta() + { + } public ContentVersionMeta( int versionId, @@ -28,14 +30,21 @@ public ContentVersionMeta( } public int ContentId { get; } + public int ContentTypeId { get; } + public int VersionId { get; } + public int UserId { get; } public DateTime VersionDate { get; } + public bool CurrentPublishedVersion { get; } + public bool CurrentDraftVersion { get; } + public bool PreventCleanup { get; } + public string? Username { get; } public override string ToString() => $"ContentVersionMeta(versionId: {VersionId}, versionDate: {VersionDate:s}"; diff --git a/src/Umbraco.Core/Models/CultureImpact.cs b/src/Umbraco.Core/Models/CultureImpact.cs index 56b3ba02dc1e..348c25125030 100644 --- a/src/Umbraco.Core/Models/CultureImpact.cs +++ b/src/Umbraco.Core/Models/CultureImpact.cs @@ -1,4 +1,4 @@ -using Umbraco.Extensions; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.Models; @@ -13,15 +13,6 @@ namespace Umbraco.Cms.Core.Models; /// public sealed class CultureImpact { - [Flags] - public enum Behavior : byte - { - AllCultures = 1, - InvariantCulture = 2, - ExplicitCulture = 4, - InvariantProperties = 8 - } - /// /// Initializes a new instance of the class. /// @@ -44,6 +35,15 @@ private CultureImpact(string? culture, bool isDefault = false) ImpactsOnlyDefaultCulture = isDefault; } + [Flags] + public enum Behavior : byte + { + AllCultures = 1, + InvariantCulture = 2, + ExplicitCulture = 4, + InvariantProperties = 8, + } + /// /// Gets the impact of 'all' cultures (including the invariant culture). /// @@ -108,7 +108,7 @@ public Behavior CultureBehavior { get { - //null can only be invariant + // null can only be invariant if (Culture == null) { return Behavior.InvariantCulture | Behavior.InvariantProperties; @@ -120,10 +120,10 @@ public Behavior CultureBehavior return Behavior.AllCultures | Behavior.InvariantProperties; } - //else it's explicit + // else it's explicit Behavior result = Behavior.ExplicitCulture; - //if the explicit culture is the default, then the behavior is also InvariantProperties + // if the explicit culture is the default, then the behavior is also InvariantProperties if (ImpactsOnlyDefaultCulture) { result |= Behavior.InvariantProperties; @@ -161,10 +161,12 @@ public Behavior CultureBehavior } var cultureForInvariantErrors = savingCultures.Any(x => x.InvariantEquals(defaultCulture)) - //the default culture is being flagged for saving so use it + + // the default culture is being flagged for saving so use it ? defaultCulture - //If the content has no published version, we need to affiliate validation with the first variant being saved. - //If the content has a published version we will not affiliate the validation with any culture (null) + + // If the content has no published version, we need to affiliate validation with the first variant being saved. + // If the content has a published version we will not affiliate the validation with any culture (null) : !content.Published ? savingCultures[0] : null; @@ -210,7 +212,7 @@ public static CultureImpact Explicit(string? culture, bool isDefault) public static CultureImpact? Create(string culture, bool isDefault, IContent content) { // throws if not successful - TryCreate(culture, isDefault, content.ContentType.Variations, true, out CultureImpact impact); + TryCreate(culture, isDefault, content.ContentType.Variations, true, out CultureImpact? impact); return impact; } diff --git a/src/Umbraco.Core/Models/DataType.cs b/src/Umbraco.Core/Models/DataType.cs index 9709612cb651..c4b65be4e56d 100644 --- a/src/Umbraco.Core/Models/DataType.cs +++ b/src/Umbraco.Core/Models/DataType.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Serialization; @@ -88,7 +88,6 @@ public object? Configuration // if we know we have a configuration (which may be null), return it // if we don't have an editor, then we have no configuration, return null // else, use the editor to get the configuration object - if (_hasConfiguration) { return _configuration; @@ -111,6 +110,7 @@ public object? Configuration return _configuration; } + set { if (value == null) @@ -122,7 +122,8 @@ public object? Configuration // configurations are kinda non-mutable, mainly because detecting changes would be a pain if (_configuration == value) // reference comparison { - throw new ArgumentException("Configurations are kinda non-mutable. Do not reassign the same object.", + throw new ArgumentException( + "Configurations are kinda non-mutable. Do not reassign the same object.", nameof(value)); } @@ -142,7 +143,8 @@ public object? Configuration // extract database type from dictionary, if appropriate if (value is IDictionary dictionaryConfiguration - && dictionaryConfiguration.TryGetValue(Constants.PropertyEditors.ConfigurationKeys.DataValueType, + && dictionaryConfiguration.TryGetValue( + Constants.PropertyEditors.ConfigurationKeys.DataValueType, out var valueTypeObject) && valueTypeObject is string valueTypeString && ValueTypes.IsValue(valueTypeString)) @@ -191,7 +193,6 @@ public void SetLazyConfiguration(string? configurationJson) { // note: in both cases, make sure we capture what we need - we don't want // to capture a reference to this full, potentially heavy, DataType instance. - if (_hasConfiguration) { // if configuration has already been de-serialized, return @@ -202,7 +203,7 @@ public void SetLazyConfiguration(string? configurationJson) { // else, create a Lazy de-serializer var capturedConfiguration = _configurationJson; - IDataEditor capturedEditor = _editor; + IDataEditor? capturedEditor = _editor; return new Lazy(() => { try diff --git a/src/Umbraco.Core/Models/DataTypeExtensions.cs b/src/Umbraco.Core/Models/DataTypeExtensions.cs index 2f9f4ebd744e..791f7b248b42 100644 --- a/src/Umbraco.Core/Models/DataTypeExtensions.cs +++ b/src/Umbraco.Core/Models/DataTypeExtensions.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; namespace Umbraco.Extensions; @@ -43,7 +43,7 @@ public static class DataTypeExtensions Constants.DataTypes.Guids.LabelDateTimeGuid, Constants.DataTypes.Guids.LabelBigIntGuid, Constants.DataTypes.Guids.LabelTimeGuid, - Constants.DataTypes.Guids.LabelDateTimeGuid + Constants.DataTypes.Guids.LabelDateTimeGuid, }; /// diff --git a/src/Umbraco.Core/Models/DeepCloneHelper.cs b/src/Umbraco.Core/Models/DeepCloneHelper.cs index b6bf03b558ab..840351fed486 100644 --- a/src/Umbraco.Core/Models/DeepCloneHelper.cs +++ b/src/Umbraco.Core/Models/DeepCloneHelper.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using System.Collections.Concurrent; using System.Reflection; using Umbraco.Cms.Core.Composing; @@ -29,28 +29,31 @@ public static void DeepCloneRefProperties(IDeepCloneable input, IDeepCloneable o throw new InvalidOperationException("Both the input and output types must be the same"); } - //get the property metadata from cache so we only have to figure this out once per type + // get the property metadata from cache so we only have to figure this out once per type ClonePropertyInfo[] refProperties = PropCache.GetOrAdd(inputType, type => inputType.GetProperties() .Select(propertyInfo => { if ( - //is not attributed with the ignore clone attribute + + // is not attributed with the ignore clone attribute propertyInfo.GetCustomAttribute() != null - //reference type but not string + + // reference type but not string || propertyInfo.PropertyType.IsValueType || propertyInfo.PropertyType == typeof(string) - //settable + + // settable || propertyInfo.CanWrite == false - //non-indexed + + // non-indexed || propertyInfo.GetIndexParameters().Any()) { return null; } - if (TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType)) { - return new ClonePropertyInfo(propertyInfo) {IsDeepCloneable = true}; + return new ClonePropertyInfo(propertyInfo) { IsDeepCloneable = true }; } if (TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType) @@ -63,43 +66,43 @@ public static void DeepCloneRefProperties(IDeepCloneable input, IDeepCloneable o || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IReadOnlyCollection<>))) { - //if it is a IEnumerable<>, IReadOnlyCollection, IList or ICollection<> we'll use a List<> since it implements them all + // if it is a IEnumerable<>, IReadOnlyCollection, IList or ICollection<> we'll use a List<> since it implements them all Type genericType = typeof(List<>).MakeGenericType(propertyInfo.PropertyType.GetGenericArguments()); - return new ClonePropertyInfo(propertyInfo) {GenericListType = genericType}; + return new ClonePropertyInfo(propertyInfo) { GenericListType = genericType }; } if (propertyInfo.PropertyType.IsArray || (propertyInfo.PropertyType.IsInterface && propertyInfo.PropertyType.IsGenericType == false)) { - //if its an array, we'll create a list to work with first and then convert to array later - //otherwise if its just a regular derivative of IEnumerable, we can use a list too - return new ClonePropertyInfo(propertyInfo) {GenericListType = typeof(List)}; + // if its an array, we'll create a list to work with first and then convert to array later + // otherwise if its just a regular derivative of IEnumerable, we can use a list too + return new ClonePropertyInfo(propertyInfo) { GenericListType = typeof(List) }; } - //skip instead of trying to create instance of abstract or interface + // skip instead of trying to create instance of abstract or interface if (propertyInfo.PropertyType.IsAbstract || propertyInfo.PropertyType.IsInterface) { return null; } - //its a custom IEnumerable, we'll try to create it + // its a custom IEnumerable, we'll try to create it try { var custom = Activator.CreateInstance(propertyInfo.PropertyType); - //if it's an IList we can work with it, otherwise we cannot - var newList = custom as IList; - if (newList == null) + + // if it's an IList we can work with it, otherwise we cannot + if (custom is not IList) { return null; } - return new ClonePropertyInfo(propertyInfo) {GenericListType = propertyInfo.PropertyType}; + return new ClonePropertyInfo(propertyInfo) { GenericListType = propertyInfo.PropertyType }; } catch (Exception) { - //could not create this type so we'll skip it + // could not create this type so we'll skip it return null; } } @@ -114,12 +117,12 @@ public static void DeepCloneRefProperties(IDeepCloneable input, IDeepCloneable o { if (clonePropertyInfo.IsDeepCloneable) { - //this ref property is also deep cloneable so clone it + // this ref property is also deep cloneable so clone it var result = (IDeepCloneable?)clonePropertyInfo.PropertyInfo.GetValue(input, null); if (result != null) { - //set the cloned value to the property + // set the cloned value to the property clonePropertyInfo.PropertyInfo.SetValue(output, result.DeepClone(), null); } } @@ -131,36 +134,35 @@ public static void DeepCloneRefProperties(IDeepCloneable input, IDeepCloneable o continue; } - IList newList = clonePropertyInfo.GenericListType is not null + IList? newList = clonePropertyInfo.GenericListType is not null ? (IList?)Activator.CreateInstance(clonePropertyInfo.GenericListType) : null; var isUsableType = true; - //now clone each item + // now clone each item foreach (var o in enumerable) { - //first check if the item is deep cloneable and copy that way - var dc = o as IDeepCloneable; - if (dc != null) + // first check if the item is deep cloneable and copy that way + if (o is IDeepCloneable dc) { newList?.Add(dc.DeepClone()); } else if (o is string || o.GetType().IsValueType) { - //check if the item is a value type or a string, then we can just use it + // check if the item is a value type or a string, then we can just use it newList?.Add(o); } else { - //this will occur if the item is not a string or value type or IDeepCloneable, in this case we cannot + // this will occur if the item is not a string or value type or IDeepCloneable, in this case we cannot // clone each element, we'll need to skip this property, people will have to manually clone this list isUsableType = false; break; } } - //if this was not usable, skip this property + // if this was not usable, skip this property if (isUsableType == false) { continue; @@ -168,8 +170,9 @@ public static void DeepCloneRefProperties(IDeepCloneable input, IDeepCloneable o if (clonePropertyInfo.PropertyInfo.PropertyType.IsArray) { - //need to convert to array - var arr = (object?[]?)Activator.CreateInstance(clonePropertyInfo.PropertyInfo.PropertyType, + // need to convert to array + var arr = (object?[]?)Activator.CreateInstance( + clonePropertyInfo.PropertyInfo.PropertyType, newList?.Count ?? 0); for (var i = 0; i < newList?.Count; i++) { @@ -179,12 +182,12 @@ public static void DeepCloneRefProperties(IDeepCloneable input, IDeepCloneable o } } - //set the cloned collection + // set the cloned collection clonePropertyInfo.PropertyInfo.SetValue(output, arr, null); } else { - //set the cloned collection + // set the cloned collection clonePropertyInfo.PropertyInfo.SetValue(output, newList, null); } } @@ -196,19 +199,18 @@ public static void DeepCloneRefProperties(IDeepCloneable input, IDeepCloneable o /// private struct ClonePropertyInfo { - public ClonePropertyInfo(PropertyInfo propertyInfo) : this() + public ClonePropertyInfo(PropertyInfo propertyInfo) + : this() { - if (propertyInfo == null) - { - throw new ArgumentNullException("propertyInfo"); - } - - PropertyInfo = propertyInfo; + PropertyInfo = propertyInfo ?? throw new ArgumentNullException("propertyInfo"); } public PropertyInfo PropertyInfo { get; } + public bool IsDeepCloneable { get; set; } + public Type? GenericListType { get; set; } + public bool IsList => GenericListType != null; } } diff --git a/src/Umbraco.Core/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs index efa285e45b73..7473cef60f13 100644 --- a/src/Umbraco.Core/Models/DictionaryItem.cs +++ b/src/Umbraco.Core/Models/DictionaryItem.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Extensions; @@ -11,7 +11,7 @@ namespace Umbraco.Cms.Core.Models; [DataContract(IsReference = true)] public class DictionaryItem : EntityBase, IDictionaryItem { - //Custom comparer for enumerable + // Custom comparer for enumerable private static readonly DelegateEqualityComparer> DictionaryTranslationComparer = new( @@ -65,9 +65,10 @@ public IEnumerable Translations get => _translations; set { - IDictionaryTranslation[] asArray = value?.ToArray(); - //ensure the language callback is set on each translation - if (GetLanguage != null && asArray is not null) + IDictionaryTranslation[] asArray = value.ToArray(); + + // ensure the language callback is set on each translation + if (GetLanguage != null) { foreach (DictionaryTranslation translation in asArray.OfType()) { @@ -75,8 +76,7 @@ public IEnumerable Translations } } - SetPropertyValueAndDetectChanges(asArray, ref _translations!, nameof(Translations), - DictionaryTranslationComparer); + SetPropertyValueAndDetectChanges(asArray, ref _translations!, nameof(Translations), DictionaryTranslationComparer); } } } diff --git a/src/Umbraco.Core/Models/DictionaryItemExtensions.cs b/src/Umbraco.Core/Models/DictionaryItemExtensions.cs index 1fb6500e2069..3e6c05120174 100644 --- a/src/Umbraco.Core/Models/DictionaryItemExtensions.cs +++ b/src/Umbraco.Core/Models/DictionaryItemExtensions.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Extensions; @@ -12,7 +12,7 @@ public static class DictionaryItemExtensions /// public static string? GetTranslatedValue(this IDictionaryItem d, int languageId) { - IDictionaryTranslation trans = d.Translations?.FirstOrDefault(x => x.LanguageId == languageId); + IDictionaryTranslation? trans = d.Translations.FirstOrDefault(x => x.LanguageId == languageId); return trans == null ? string.Empty : trans.Value; } @@ -23,7 +23,7 @@ public static class DictionaryItemExtensions /// public static string? GetDefaultValue(this IDictionaryItem d) { - IDictionaryTranslation defaultTranslation = d.Translations?.FirstOrDefault(x => x.Language?.Id == 1); + IDictionaryTranslation? defaultTranslation = d.Translations.FirstOrDefault(x => x.Language?.Id == 1); return defaultTranslation == null ? string.Empty : defaultTranslation.Value; } } diff --git a/src/Umbraco.Core/Models/DictionaryTranslation.cs b/src/Umbraco.Core/Models/DictionaryTranslation.cs index 6a7dacc22593..5d44768388c3 100644 --- a/src/Umbraco.Core/Models/DictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/DictionaryTranslation.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; @@ -12,29 +12,19 @@ public class DictionaryTranslation : EntityBase, IDictionaryTranslation { private ILanguage? _language; - //note: this will be memberwise cloned + // note: this will be memberwise cloned private string _value; public DictionaryTranslation(ILanguage language, string value) { - if (language == null) - { - throw new ArgumentNullException("language"); - } - - _language = language; + _language = language ?? throw new ArgumentNullException("language"); LanguageId = _language.Id; _value = value; } public DictionaryTranslation(ILanguage language, string value, Guid uniqueId) { - if (language == null) - { - throw new ArgumentNullException("language"); - } - - _language = language; + _language = language ?? throw new ArgumentNullException("language"); LanguageId = _language.Id; _value = value; Key = uniqueId; @@ -85,6 +75,7 @@ public ILanguage? Language return _language; } + set { SetPropertyValueAndDetectChanges(value, ref _language, nameof(Language)); diff --git a/src/Umbraco.Core/Models/DoNotCloneAttribute.cs b/src/Umbraco.Core/Models/DoNotCloneAttribute.cs index ea25f055eea5..1fb0b3cd4b46 100644 --- a/src/Umbraco.Core/Models/DoNotCloneAttribute.cs +++ b/src/Umbraco.Core/Models/DoNotCloneAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; /// /// Used to attribute properties that have a setter and are a reference type diff --git a/src/Umbraco.Core/Models/Editors/ContentPropertyData.cs b/src/Umbraco.Core/Models/Editors/ContentPropertyData.cs index d396a1dd7517..ac19eef0c8c7 100644 --- a/src/Umbraco.Core/Models/Editors/ContentPropertyData.cs +++ b/src/Umbraco.Core/Models/Editors/ContentPropertyData.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Editors; +namespace Umbraco.Cms.Core.Models.Editors; /// /// Represents data that has been submitted to be saved for a content property diff --git a/src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs b/src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs index a2b74fff736e..9bb098697c90 100644 --- a/src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs +++ b/src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Editors; +namespace Umbraco.Cms.Core.Models.Editors; /// /// Represents an uploaded file for a property. diff --git a/src/Umbraco.Core/Models/Email/EmailMessage.cs b/src/Umbraco.Core/Models/Email/EmailMessage.cs index 5df9084144e3..141928541767 100644 --- a/src/Umbraco.Core/Models/Email/EmailMessage.cs +++ b/src/Umbraco.Core/Models/Email/EmailMessage.cs @@ -3,12 +3,20 @@ namespace Umbraco.Cms.Core.Models.Email; public class EmailMessage { public EmailMessage(string? from, string? to, string? subject, string? body, bool isBodyHtml) - : this(from, new[] {to}, null, null, null, subject, body, isBodyHtml, null) + : this(from, new[] { to }, null, null, null, subject, body, isBodyHtml, null) { } - public EmailMessage(string? from, string?[] to, string[]? cc, string[]? bcc, string[]? replyTo, string? subject, - string? body, bool isBodyHtml, IEnumerable? attachments) + public EmailMessage( + string? from, + string?[] to, + string[]? cc, + string[]? bcc, + string[]? replyTo, + string? subject, + string? body, + bool isBodyHtml, + IEnumerable? attachments) { ArgumentIsNotNullOrEmpty(to, nameof(to)); ArgumentIsNotNullOrEmpty(subject, nameof(subject)); diff --git a/src/Umbraco.Core/Models/Entities/BeingDirty.cs b/src/Umbraco.Core/Models/Entities/BeingDirty.cs index 0f40d9368c7c..faf302b1a668 100644 --- a/src/Umbraco.Core/Models/Entities/BeingDirty.cs +++ b/src/Umbraco.Core/Models/Entities/BeingDirty.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Entities; +namespace Umbraco.Cms.Core.Models.Entities; /// /// Provides a concrete implementation of . diff --git a/src/Umbraco.Core/Models/Entities/ContentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/ContentEntitySlim.cs index 9db19aedff7f..3b9d139ba75a 100644 --- a/src/Umbraco.Core/Models/Entities/ContentEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/ContentEntitySlim.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Entities; +namespace Umbraco.Cms.Core.Models.Entities; /// /// Implements . diff --git a/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs index 94f21d9bb164..a5c0ca23c990 100644 --- a/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/DocumentEntitySlim.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Entities; +namespace Umbraco.Cms.Core.Models.Entities; /// /// Implements . diff --git a/src/Umbraco.Core/Models/Entities/EntityBase.cs b/src/Umbraco.Core/Models/Entities/EntityBase.cs index f57dd31933aa..df60d97a1e21 100644 --- a/src/Umbraco.Core/Models/Entities/EntityBase.cs +++ b/src/Umbraco.Core/Models/Entities/EntityBase.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.Entities; @@ -90,6 +90,17 @@ public virtual bool Equals(EntityBase? other) => public override bool Equals(object? obj) => obj != null && (ReferenceEquals(this, obj) || SameIdentityAs(obj as EntityBase)); + public override int GetHashCode() + { + unchecked + { + var hashCode = HasIdentity.GetHashCode(); + hashCode = (hashCode * 397) ^ Id; + hashCode = (hashCode * 397) ^ GetType().GetHashCode(); + return hashCode; + } + } + private bool SameIdentityAs(EntityBase? other) { if (other == null) @@ -100,7 +111,6 @@ private bool SameIdentityAs(EntityBase? other) // same identity if // - same object (reference equals) // - or same CLR type, both have identities, and they are identical - if (ReferenceEquals(this, other)) { return true; @@ -109,17 +119,6 @@ private bool SameIdentityAs(EntityBase? other) return GetType() == other.GetType() && HasIdentity && other.HasIdentity && Id == other.Id; } - public override int GetHashCode() - { - unchecked - { - var hashCode = HasIdentity.GetHashCode(); - hashCode = (hashCode * 397) ^ Id; - hashCode = (hashCode * 397) ^ GetType().GetHashCode(); - return hashCode; - } - } - public object DeepClone() { // memberwise-clone (ie shallow clone) the entity @@ -130,7 +129,7 @@ public object DeepClone() clone.InstanceId = Guid.NewGuid(); #endif - //disable change tracking while we deep clone IDeepCloneable properties + // disable change tracking while we deep clone IDeepCloneable properties clone.DisableChangeTracking(); // deep clone ref properties that are IDeepCloneable @@ -141,7 +140,7 @@ public object DeepClone() // clear changes (ensures the clone has its own dictionaries) clone.ResetDirtyProperties(false); - //re-enable change tracking + // re-enable change tracking clone.EnableChangeTracking(); return clone; diff --git a/src/Umbraco.Core/Models/Entities/EntityExtensions.cs b/src/Umbraco.Core/Models/Entities/EntityExtensions.cs index 0c63e0786713..53801875aebe 100644 --- a/src/Umbraco.Core/Models/Entities/EntityExtensions.cs +++ b/src/Umbraco.Core/Models/Entities/EntityExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Models.Entities; diff --git a/src/Umbraco.Core/Models/Entities/EntitySlim.cs b/src/Umbraco.Core/Models/Entities/EntitySlim.cs index 59cb3214e3ca..91acaea3fd0f 100644 --- a/src/Umbraco.Core/Models/Entities/EntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/EntitySlim.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.Entities; @@ -20,7 +20,7 @@ public class EntitySlim : IEntitySlim /// /// Gets an entity representing "root". /// - public static readonly IEntitySlim Root = new EntitySlim {Path = "-1", Name = "root", HasChildren = true}; + public static readonly IEntitySlim Root = new EntitySlim { Path = "-1", Name = "root", HasChildren = true }; private IDictionary? _additionalData; @@ -50,7 +50,6 @@ public class EntitySlim : IEntitySlim [DataMember] public bool HasIdentity => Id != 0; - // implement ITreeEntity /// @@ -65,14 +64,14 @@ public class EntitySlim : IEntitySlim [DataMember] public int ParentId { get; set; } - /// - public void SetParent(ITreeEntity? parent) => - throw new InvalidOperationException("This property won't be implemented."); - /// [DataMember] public int Level { get; set; } + /// + public void SetParent(ITreeEntity? parent) => + throw new InvalidOperationException("This property won't be implemented."); + /// [DataMember] public string Path { get; set; } = string.Empty; @@ -85,19 +84,17 @@ public void SetParent(ITreeEntity? parent) => [DataMember] public bool Trashed { get; set; } - // implement IUmbracoEntity /// [DataMember] public IDictionary? AdditionalData => - _additionalData ?? (_additionalData = new Dictionary()); +_additionalData ??= new Dictionary(); /// [IgnoreDataMember] public bool HasAdditionalData => _additionalData != null; - // implement IEntitySlim /// @@ -112,7 +109,6 @@ public void SetParent(ITreeEntity? parent) => [DataMember] public virtual bool IsContainer { get; set; } - #region IDeepCloneable /// @@ -130,7 +126,6 @@ public void ResetIdentity() // IEntitySlim does *not* track changes, but since it indirectly implements IUmbracoEntity, // and therefore IRememberBeingDirty, we have to have those methods - which all throw. - public bool IsDirty() => throw new InvalidOperationException("This method won't be implemented."); public bool IsPropertyDirty(string propName) => diff --git a/src/Umbraco.Core/Models/Entities/ICanBeDirty.cs b/src/Umbraco.Core/Models/Entities/ICanBeDirty.cs index 669305c3c957..23d50d54d918 100644 --- a/src/Umbraco.Core/Models/Entities/ICanBeDirty.cs +++ b/src/Umbraco.Core/Models/Entities/ICanBeDirty.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; namespace Umbraco.Cms.Core.Models.Entities; @@ -7,6 +7,8 @@ namespace Umbraco.Cms.Core.Models.Entities; /// public interface ICanBeDirty { + event PropertyChangedEventHandler PropertyChanged; + /// /// Determines whether the current entity is dirty. /// @@ -36,6 +38,4 @@ public interface ICanBeDirty /// Enables change tracking. /// void EnableChangeTracking(); - - event PropertyChangedEventHandler PropertyChanged; } diff --git a/src/Umbraco.Core/Models/Entities/IContentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IContentEntitySlim.cs index 72ad85c35e3d..78ddf9bd8269 100644 --- a/src/Umbraco.Core/Models/Entities/IContentEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/IContentEntitySlim.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Entities; +namespace Umbraco.Cms.Core.Models.Entities; /// /// Represents a lightweight content entity, managed by the entity service. diff --git a/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs index 6772454c3acf..75e16476c25d 100644 --- a/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/IDocumentEntitySlim.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Entities; +namespace Umbraco.Cms.Core.Models.Entities; /// /// Represents a lightweight document entity, managed by the entity service. diff --git a/src/Umbraco.Core/Models/Entities/IEntity.cs b/src/Umbraco.Core/Models/Entities/IEntity.cs index 2118736b0163..859975adfb9d 100644 --- a/src/Umbraco.Core/Models/Entities/IEntity.cs +++ b/src/Umbraco.Core/Models/Entities/IEntity.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Entities; +namespace Umbraco.Cms.Core.Models.Entities; /// /// Defines an entity. diff --git a/src/Umbraco.Core/Models/Entities/IEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IEntitySlim.cs index 67aefdc55705..120d417d1ad0 100644 --- a/src/Umbraco.Core/Models/Entities/IEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/IEntitySlim.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Entities; +namespace Umbraco.Cms.Core.Models.Entities; /// /// Represents a lightweight entity, managed by the entity service. diff --git a/src/Umbraco.Core/Models/Entities/IHaveAdditionalData.cs b/src/Umbraco.Core/Models/Entities/IHaveAdditionalData.cs index 06ee6c675a59..a2ac3a247ac3 100644 --- a/src/Umbraco.Core/Models/Entities/IHaveAdditionalData.cs +++ b/src/Umbraco.Core/Models/Entities/IHaveAdditionalData.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Entities; +namespace Umbraco.Cms.Core.Models.Entities; /// /// Provides support for additional data. diff --git a/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs index beb27ba6642a..019a6f1f7b77 100644 --- a/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/IMediaEntitySlim.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Entities; +namespace Umbraco.Cms.Core.Models.Entities; /// /// Represents a lightweight media entity, managed by the entity service. diff --git a/src/Umbraco.Core/Models/Entities/IMemberEntitySlim.cs b/src/Umbraco.Core/Models/Entities/IMemberEntitySlim.cs index b397997d6733..0ded5370355f 100644 --- a/src/Umbraco.Core/Models/Entities/IMemberEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/IMemberEntitySlim.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Entities; +namespace Umbraco.Cms.Core.Models.Entities; public interface IMemberEntitySlim : IContentEntitySlim { diff --git a/src/Umbraco.Core/Models/Entities/IRememberBeingDirty.cs b/src/Umbraco.Core/Models/Entities/IRememberBeingDirty.cs index da9a4a2d2b15..85c1c472b5cd 100644 --- a/src/Umbraco.Core/Models/Entities/IRememberBeingDirty.cs +++ b/src/Umbraco.Core/Models/Entities/IRememberBeingDirty.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Entities; +namespace Umbraco.Cms.Core.Models.Entities; /// /// Defines an entity that tracks property changes and can be dirty, and remembers diff --git a/src/Umbraco.Core/Models/Entities/ITreeEntity.cs b/src/Umbraco.Core/Models/Entities/ITreeEntity.cs index e6f2a5c4418c..b66368e425ca 100644 --- a/src/Umbraco.Core/Models/Entities/ITreeEntity.cs +++ b/src/Umbraco.Core/Models/Entities/ITreeEntity.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Entities; +namespace Umbraco.Cms.Core.Models.Entities; /// /// Defines an entity that belongs to a tree. diff --git a/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs b/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs index a1a54fa84415..3d8c89c7c4b0 100644 --- a/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs +++ b/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Entities; +namespace Umbraco.Cms.Core.Models.Entities; /// /// Represents an entity that can be managed by the entity service. diff --git a/src/Umbraco.Core/Models/Entities/IValueObject.cs b/src/Umbraco.Core/Models/Entities/IValueObject.cs index 8f1371ff07bc..f101f531fa13 100644 --- a/src/Umbraco.Core/Models/Entities/IValueObject.cs +++ b/src/Umbraco.Core/Models/Entities/IValueObject.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Entities; +namespace Umbraco.Cms.Core.Models.Entities; /// /// Marker interface for value object, eg. objects without diff --git a/src/Umbraco.Core/Models/Entities/MediaEntitySlim.cs b/src/Umbraco.Core/Models/Entities/MediaEntitySlim.cs index f2b72dae6511..fb73e2332dd1 100644 --- a/src/Umbraco.Core/Models/Entities/MediaEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/MediaEntitySlim.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Entities; +namespace Umbraco.Cms.Core.Models.Entities; /// /// Implements . diff --git a/src/Umbraco.Core/Models/EntityContainer.cs b/src/Umbraco.Core/Models/EntityContainer.cs index 76f5733a3899..762297af079f 100644 --- a/src/Umbraco.Core/Models/EntityContainer.cs +++ b/src/Umbraco.Core/Models/EntityContainer.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; @@ -9,9 +9,9 @@ public sealed class EntityContainer : TreeEntityBase, IUmbracoEntity { private static readonly Dictionary ObjectTypeMap = new() { - {Constants.ObjectTypes.DataType, Constants.ObjectTypes.DataTypeContainer}, - {Constants.ObjectTypes.DocumentType, Constants.ObjectTypes.DocumentTypeContainer}, - {Constants.ObjectTypes.MediaType, Constants.ObjectTypes.MediaTypeContainer} + { Constants.ObjectTypes.DataType, Constants.ObjectTypes.DataTypeContainer }, + { Constants.ObjectTypes.DocumentType, Constants.ObjectTypes.DocumentTypeContainer }, + { Constants.ObjectTypes.MediaType, Constants.ObjectTypes.MediaTypeContainer }, }; /// @@ -35,8 +35,7 @@ public EntityContainer(Guid containedObjectType) /// /// Initializes a new instance of an class. /// - public EntityContainer(int id, Guid uniqueId, int parentId, string path, int level, int sortOrder, - Guid containedObjectType, string? name, int userId) + public EntityContainer(int id, Guid uniqueId, int parentId, string path, int level, int sortOrder, Guid containedObjectType, string? name, int userId) : this(containedObjectType) { Id = id; diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index 2b596352347c..e77dc4e07aff 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; @@ -34,7 +34,7 @@ protected File(string path, Func? getFileContent = null) /// Gets or sets the Name of the File including extension /// [DataMember] - public virtual string Name => _name ?? (_name = System.IO.Path.GetFileName(Path)); + public virtual string Name => _name ??= System.IO.Path.GetFileName(Path); /// /// Gets or sets the Alias of the File, which is the name without the extension @@ -53,7 +53,7 @@ public virtual string Alias } var lastIndexOf = name.LastIndexOf(".", StringComparison.InvariantCultureIgnoreCase); - _alias = name.Substring(0, lastIndexOf); + _alias = name[..lastIndexOf]; } return _alias; @@ -69,7 +69,7 @@ public virtual string Path get => _path; set { - //reset + // reset _alias = null; _name = null; @@ -82,11 +82,6 @@ public virtual string Path /// public string OriginalPath { get; private set; } - /// - /// Called to re-set the OriginalPath to the Path - /// - public void ResetOriginalPath() => OriginalPath = _path; - /// /// Gets or sets the Content of a File /// @@ -108,27 +103,28 @@ public virtual string? Content _content = GetFileContent(this); } - return _content ?? (_content = string.Empty); + return _content ??= string.Empty; } set => SetPropertyValueAndDetectChanges( value ?? string.Empty, // cannot set to null - ref _content, nameof(Content)); + ref _content, + nameof(Content)); } + /// + /// Called to re-set the OriginalPath to the Path + /// + public void ResetOriginalPath() => OriginalPath = _path; + /// /// Gets or sets the file's virtual path (i.e. the file path relative to the root of the website) /// public string? VirtualPath { get; set; } - private static string SanitizePath(string path) => - path - .Replace('\\', System.IO.Path.DirectorySeparatorChar) - .Replace('/', System.IO.Path.DirectorySeparatorChar); - - //Don't strip the start - this was a bug fixed in 7.3, see ScriptRepositoryTests.PathTests - //.TrimStart(System.IO.Path.DirectorySeparatorChar) - //.TrimStart('/'); + // Don't strip the start - this was a bug fixed in 7.3, see ScriptRepositoryTests.PathTests + // .TrimStart(System.IO.Path.DirectorySeparatorChar) + // .TrimStart('/'); // this exists so that class that manage name and alias differently, eg Template, // can implement their own cloning - (though really, not sure it's even needed) protected virtual void DeepCloneNameAndAlias(File clone) @@ -138,6 +134,11 @@ protected virtual void DeepCloneNameAndAlias(File clone) clone._alias = Alias; } + private static string SanitizePath(string path) => + path + .Replace('\\', System.IO.Path.DirectorySeparatorChar) + .Replace('/', System.IO.Path.DirectorySeparatorChar); + protected override void PerformDeepClone(object clone) { base.PerformDeepClone(clone); diff --git a/src/Umbraco.Core/Models/Folder.cs b/src/Umbraco.Core/Models/Folder.cs index 7f6b5f7f6785..60e636ca6e50 100644 --- a/src/Umbraco.Core/Models/Folder.cs +++ b/src/Umbraco.Core/Models/Folder.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/HaveAdditionalDataExtensions.cs b/src/Umbraco.Core/Models/HaveAdditionalDataExtensions.cs index a94bb9525bbd..79db47414a2b 100644 --- a/src/Umbraco.Core/Models/HaveAdditionalDataExtensions.cs +++ b/src/Umbraco.Core/Models/HaveAdditionalDataExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Models.Entities; @@ -10,8 +10,7 @@ public static class HaveAdditionalDataExtensions /// /// Gets additional data. /// - public static object? GetAdditionalDataValueIgnoreCase(this IHaveAdditionalData entity, string key, - object? defaultValue) + public static object? GetAdditionalDataValueIgnoreCase(this IHaveAdditionalData entity, string key, object? defaultValue) { if (!entity.HasAdditionalData) { diff --git a/src/Umbraco.Core/Models/IAuditEntry.cs b/src/Umbraco.Core/Models/IAuditEntry.cs index 7bb40ac875a7..3a1b412ce0da 100644 --- a/src/Umbraco.Core/Models/IAuditEntry.cs +++ b/src/Umbraco.Core/Models/IAuditEntry.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/IAuditItem.cs b/src/Umbraco.Core/Models/IAuditItem.cs index 5989289f4b0d..dbf4fe01e881 100644 --- a/src/Umbraco.Core/Models/IAuditItem.cs +++ b/src/Umbraco.Core/Models/IAuditItem.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/IConsent.cs b/src/Umbraco.Core/Models/IConsent.cs index f070a9988f35..bae029428301 100644 --- a/src/Umbraco.Core/Models/IConsent.cs +++ b/src/Umbraco.Core/Models/IConsent.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/IContentBase.cs b/src/Umbraco.Core/Models/IContentBase.cs index e22444e3872c..5f4ccf524487 100644 --- a/src/Umbraco.Core/Models/IContentBase.cs +++ b/src/Umbraco.Core/Models/IContentBase.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; @@ -131,8 +131,7 @@ public interface IContentBase : IUmbracoEntity, IRememberBeingDirty /// Gets the typed value of a Property /// /// Values 'null' and 'empty' are equivalent for culture and segment. - TValue? GetValue(string propertyTypeAlias, string? culture = null, string? segment = null, - bool published = false); + TValue? GetValue(string propertyTypeAlias, string? culture = null, string? segment = null, bool published = false); /// /// Sets the (edited) value of a Property diff --git a/src/Umbraco.Core/Models/IContentTypeComposition.cs b/src/Umbraco.Core/Models/IContentTypeComposition.cs index d9f1fe4e6973..650328548e3e 100644 --- a/src/Umbraco.Core/Models/IContentTypeComposition.cs +++ b/src/Umbraco.Core/Models/IContentTypeComposition.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; /// /// Defines the Composition of a ContentType diff --git a/src/Umbraco.Core/Models/IDataType.cs b/src/Umbraco.Core/Models/IDataType.cs index 91f8d2457c79..6f0002c77946 100644 --- a/src/Umbraco.Core/Models/IDataType.cs +++ b/src/Umbraco.Core/Models/IDataType.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/IDataValueEditor.cs b/src/Umbraco.Core/Models/IDataValueEditor.cs index 5ea762b62088..73b700e411de 100644 --- a/src/Umbraco.Core/Models/IDataValueEditor.cs +++ b/src/Umbraco.Core/Models/IDataValueEditor.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Xml.Linq; using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.PropertyEditors; diff --git a/src/Umbraco.Core/Models/IDeepCloneable.cs b/src/Umbraco.Core/Models/IDeepCloneable.cs index 122dd8116a22..171c2a1f4ef3 100644 --- a/src/Umbraco.Core/Models/IDeepCloneable.cs +++ b/src/Umbraco.Core/Models/IDeepCloneable.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; /// /// Provides a mean to deep-clone an object. diff --git a/src/Umbraco.Core/Models/IDictionaryItem.cs b/src/Umbraco.Core/Models/IDictionaryItem.cs index 3656af8f01eb..e47502199b2e 100644 --- a/src/Umbraco.Core/Models/IDictionaryItem.cs +++ b/src/Umbraco.Core/Models/IDictionaryItem.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/IDictionaryTranslation.cs b/src/Umbraco.Core/Models/IDictionaryTranslation.cs index 54d91881c12f..37579151bc42 100644 --- a/src/Umbraco.Core/Models/IDictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/IDictionaryTranslation.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/IDomain.cs b/src/Umbraco.Core/Models/IDomain.cs index 03449190f07d..2d4845c9a6f8 100644 --- a/src/Umbraco.Core/Models/IDomain.cs +++ b/src/Umbraco.Core/Models/IDomain.cs @@ -1,12 +1,15 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; public interface IDomain : IEntity, IRememberBeingDirty { int? LanguageId { get; set; } + string DomainName { get; set; } + int? RootContentId { get; set; } + bool IsWildcard { get; } /// diff --git a/src/Umbraco.Core/Models/IFile.cs b/src/Umbraco.Core/Models/IFile.cs index 431307a612bf..ed52997c84ea 100644 --- a/src/Umbraco.Core/Models/IFile.cs +++ b/src/Umbraco.Core/Models/IFile.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/IKeyValue.cs b/src/Umbraco.Core/Models/IKeyValue.cs index 211690964450..b893aabf356e 100644 --- a/src/Umbraco.Core/Models/IKeyValue.cs +++ b/src/Umbraco.Core/Models/IKeyValue.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/ILanguage.cs b/src/Umbraco.Core/Models/ILanguage.cs index 70663de05683..5f48bc363eb3 100644 --- a/src/Umbraco.Core/Models/ILanguage.cs +++ b/src/Umbraco.Core/Models/ILanguage.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; diff --git a/src/Umbraco.Core/Models/ILogViewerQuery.cs b/src/Umbraco.Core/Models/ILogViewerQuery.cs index 836be53bbbcf..372fddc3d02c 100644 --- a/src/Umbraco.Core/Models/ILogViewerQuery.cs +++ b/src/Umbraco.Core/Models/ILogViewerQuery.cs @@ -1,9 +1,10 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; public interface ILogViewerQuery : IEntity { string? Name { get; set; } + string? Query { get; set; } } diff --git a/src/Umbraco.Core/Models/IMacro.cs b/src/Umbraco.Core/Models/IMacro.cs index a3e071b24b46..bc979804a753 100644 --- a/src/Umbraco.Core/Models/IMacro.cs +++ b/src/Umbraco.Core/Models/IMacro.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/IMacroProperty.cs b/src/Umbraco.Core/Models/IMacroProperty.cs index 5300e17ae80f..e1b27b64839c 100644 --- a/src/Umbraco.Core/Models/IMacroProperty.cs +++ b/src/Umbraco.Core/Models/IMacroProperty.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; @@ -8,9 +8,11 @@ namespace Umbraco.Cms.Core.Models; /// public interface IMacroProperty : IValueObject, IDeepCloneable, IRememberBeingDirty { - [DataMember] int Id { get; set; } + [DataMember] + int Id { get; set; } - [DataMember] Guid Key { get; set; } + [DataMember] + Guid Key { get; set; } /// /// Gets or sets the Alias of the Property diff --git a/src/Umbraco.Core/Models/IMedia.cs b/src/Umbraco.Core/Models/IMedia.cs index 597b7f44add4..08f206f664e9 100644 --- a/src/Umbraco.Core/Models/IMedia.cs +++ b/src/Umbraco.Core/Models/IMedia.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; public interface IMedia : IContentBase { diff --git a/src/Umbraco.Core/Models/IMediaType.cs b/src/Umbraco.Core/Models/IMediaType.cs index 11658099dfd0..0be980ae6219 100644 --- a/src/Umbraco.Core/Models/IMediaType.cs +++ b/src/Umbraco.Core/Models/IMediaType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; /// /// Defines a ContentType, which Media is based on diff --git a/src/Umbraco.Core/Models/IMember.cs b/src/Umbraco.Core/Models/IMember.cs index ac7e3f08ae9e..6085b84f01b7 100644 --- a/src/Umbraco.Core/Models/IMember.cs +++ b/src/Umbraco.Core/Models/IMember.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; diff --git a/src/Umbraco.Core/Models/IMemberGroup.cs b/src/Umbraco.Core/Models/IMemberGroup.cs index 5c3de74e5930..904d60cf8c65 100644 --- a/src/Umbraco.Core/Models/IMemberGroup.cs +++ b/src/Umbraco.Core/Models/IMemberGroup.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/IMemberType.cs b/src/Umbraco.Core/Models/IMemberType.cs index bd698ec58e78..993e956df986 100644 --- a/src/Umbraco.Core/Models/IMemberType.cs +++ b/src/Umbraco.Core/Models/IMemberType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; /// /// Defines a MemberType, which Member is based on diff --git a/src/Umbraco.Core/Models/IMigrationEntry.cs b/src/Umbraco.Core/Models/IMigrationEntry.cs index 2f864b750bfb..392eb17097c8 100644 --- a/src/Umbraco.Core/Models/IMigrationEntry.cs +++ b/src/Umbraco.Core/Models/IMigrationEntry.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Semver; namespace Umbraco.Cms.Core.Models; @@ -6,5 +6,6 @@ namespace Umbraco.Cms.Core.Models; public interface IMigrationEntry : IEntity, IRememberBeingDirty { string? MigrationName { get; set; } + SemVersion? Version { get; set; } } diff --git a/src/Umbraco.Core/Models/IPartialView.cs b/src/Umbraco.Core/Models/IPartialView.cs index 613bd77391a1..a19cc65c7a78 100644 --- a/src/Umbraco.Core/Models/IPartialView.cs +++ b/src/Umbraco.Core/Models/IPartialView.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; public interface IPartialView : IFile { diff --git a/src/Umbraco.Core/Models/IProperty.cs b/src/Umbraco.Core/Models/IProperty.cs index a5f5abfdf67e..54f1e8581fc4 100644 --- a/src/Umbraco.Core/Models/IProperty.cs +++ b/src/Umbraco.Core/Models/IProperty.cs @@ -34,5 +34,6 @@ public interface IProperty : IEntity, IRememberBeingDirty void SetValue(object? value, string? culture = null, string? segment = null); void PublishValues(string? culture = "*", string segment = "*"); + void UnpublishValues(string? culture = "*", string segment = "*"); } diff --git a/src/Umbraco.Core/Models/IPropertyCollection.cs b/src/Umbraco.Core/Models/IPropertyCollection.cs index 78893e4a9bac..535756fad86f 100644 --- a/src/Umbraco.Core/Models/IPropertyCollection.cs +++ b/src/Umbraco.Core/Models/IPropertyCollection.cs @@ -5,6 +5,8 @@ namespace Umbraco.Cms.Core.Models; public interface IPropertyCollection : IEnumerable, IDeepCloneable, INotifyCollectionChanged { + int Count { get; } + /// /// Gets the property with the specified alias. /// @@ -15,8 +17,8 @@ public interface IPropertyCollection : IEnumerable, IDeepCloneable, I /// IProperty? this[int index] { get; } - int Count { get; } bool TryGetValue(string propertyTypeAlias, [MaybeNullWhen(false)] out IProperty property); + bool Contains(string key); /// diff --git a/src/Umbraco.Core/Models/IRedirectUrl.cs b/src/Umbraco.Core/Models/IRedirectUrl.cs index 23fa75ecca47..cbd12eb0b88d 100644 --- a/src/Umbraco.Core/Models/IRedirectUrl.cs +++ b/src/Umbraco.Core/Models/IRedirectUrl.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/IRelation.cs b/src/Umbraco.Core/Models/IRelation.cs index 16cba492e747..468a51b89706 100644 --- a/src/Umbraco.Core/Models/IRelation.cs +++ b/src/Umbraco.Core/Models/IRelation.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; @@ -11,7 +11,8 @@ public interface IRelation : IEntity, IRememberBeingDirty [DataMember] int ParentId { get; set; } - [DataMember] Guid ParentObjectType { get; set; } + [DataMember] + Guid ParentObjectType { get; set; } /// /// Gets or sets the Child Id of the Relation (Destination) @@ -19,7 +20,8 @@ public interface IRelation : IEntity, IRememberBeingDirty [DataMember] int ChildId { get; set; } - [DataMember] Guid ChildObjectType { get; set; } + [DataMember] + Guid ChildObjectType { get; set; } /// /// Gets or sets the for the Relation diff --git a/src/Umbraco.Core/Models/IRelationType.cs b/src/Umbraco.Core/Models/IRelationType.cs index 262b8835f858..7675a1c49e65 100644 --- a/src/Umbraco.Core/Models/IRelationType.cs +++ b/src/Umbraco.Core/Models/IRelationType.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/IScript.cs b/src/Umbraco.Core/Models/IScript.cs index b12bc131b123..f52bdc02864a 100644 --- a/src/Umbraco.Core/Models/IScript.cs +++ b/src/Umbraco.Core/Models/IScript.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; public interface IScript : IFile { diff --git a/src/Umbraco.Core/Models/ISimpleContentType.cs b/src/Umbraco.Core/Models/ISimpleContentType.cs index af7a0a1506d6..8246b50ca01b 100644 --- a/src/Umbraco.Core/Models/ISimpleContentType.cs +++ b/src/Umbraco.Core/Models/ISimpleContentType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; /// /// Represents a simplified view of a content type. @@ -6,7 +6,9 @@ public interface ISimpleContentType { int Id { get; } + Guid Key { get; } + string? Name { get; } /// diff --git a/src/Umbraco.Core/Models/IStylesheet.cs b/src/Umbraco.Core/Models/IStylesheet.cs index f1024f7154f2..fbe9a1652b71 100644 --- a/src/Umbraco.Core/Models/IStylesheet.cs +++ b/src/Umbraco.Core/Models/IStylesheet.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; public interface IStylesheet : IFile { diff --git a/src/Umbraco.Core/Models/IStylesheetProperty.cs b/src/Umbraco.Core/Models/IStylesheetProperty.cs index 12aad52a8ccc..c2bb81060da7 100644 --- a/src/Umbraco.Core/Models/IStylesheetProperty.cs +++ b/src/Umbraco.Core/Models/IStylesheetProperty.cs @@ -1,10 +1,12 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; public interface IStylesheetProperty : IRememberBeingDirty { string Alias { get; set; } + string Name { get; } + string Value { get; set; } } diff --git a/src/Umbraco.Core/Models/ITag.cs b/src/Umbraco.Core/Models/ITag.cs index 621b8ef9d70e..9824ee5ed2e7 100644 --- a/src/Umbraco.Core/Models/ITag.cs +++ b/src/Umbraco.Core/Models/ITag.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/ITemplate.cs b/src/Umbraco.Core/Models/ITemplate.cs index 17972ab5a0b9..321fff2831e3 100644 --- a/src/Umbraco.Core/Models/ITemplate.cs +++ b/src/Umbraco.Core/Models/ITemplate.cs @@ -1,11 +1,9 @@ -using Umbraco.Cms.Core.Models.Entities; - namespace Umbraco.Cms.Core.Models; /// /// Defines a Template File (Mvc View) /// -public interface ITemplate : IFile, IRememberBeingDirty, ICanBeDirty +public interface ITemplate : IFile { /// /// Gets the Name of the File including extension diff --git a/src/Umbraco.Core/Models/ITwoFactorLogin.cs b/src/Umbraco.Core/Models/ITwoFactorLogin.cs index ee68d48ca2c7..3840dcb1746c 100644 --- a/src/Umbraco.Core/Models/ITwoFactorLogin.cs +++ b/src/Umbraco.Core/Models/ITwoFactorLogin.cs @@ -1,10 +1,12 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; public interface ITwoFactorLogin : IEntity, IRememberBeingDirty { string ProviderName { get; } + string Secret { get; } + Guid UserOrMemberKey { get; } } diff --git a/src/Umbraco.Core/Models/IconModel.cs b/src/Umbraco.Core/Models/IconModel.cs index efefb13d6b23..8fd9005ac3ce 100644 --- a/src/Umbraco.Core/Models/IconModel.cs +++ b/src/Umbraco.Core/Models/IconModel.cs @@ -3,5 +3,6 @@ namespace Umbraco.Cms.Core.Models; public class IconModel { public string Name { get; set; } = null!; + public string SvgString { get; set; } = null!; } diff --git a/src/Umbraco.Core/Models/ImageCropAnchor.cs b/src/Umbraco.Core/Models/ImageCropAnchor.cs index f809a8e6c21d..68544289c605 100644 --- a/src/Umbraco.Core/Models/ImageCropAnchor.cs +++ b/src/Umbraco.Core/Models/ImageCropAnchor.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; public enum ImageCropAnchor { @@ -10,5 +10,5 @@ public enum ImageCropAnchor TopLeft, TopRight, BottomLeft, - BottomRight + BottomRight, } diff --git a/src/Umbraco.Core/Models/ImageCropMode.cs b/src/Umbraco.Core/Models/ImageCropMode.cs index 9589b5557218..3ce2f4bfb94b 100644 --- a/src/Umbraco.Core/Models/ImageCropMode.cs +++ b/src/Umbraco.Core/Models/ImageCropMode.cs @@ -37,5 +37,5 @@ public enum ImageCropMode /// Resizes the image until the shortest side reaches the set given dimension. This will maintain the aspect ratio of /// the original image. Upscaling is disabled in this mode and the original image will be returned if attempted. /// - Min + Min, } diff --git a/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs b/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs index e5c56c65d240..9fd00ac2abfd 100644 --- a/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs +++ b/src/Umbraco.Core/Models/ImageUrlGenerationOptions.cs @@ -44,7 +44,7 @@ public bool Equals(ImageUrlGenerationOptions? other) public override int GetHashCode() { - var hash = new HashCode(); + var hash = default(HashCode); hash.Add(ImageUrl); hash.Add(Width); diff --git a/src/Umbraco.Core/Models/KeyValue.cs b/src/Umbraco.Core/Models/KeyValue.cs index 3aed1d0cf5f9..bf5b26dbee58 100644 --- a/src/Umbraco.Core/Models/KeyValue.cs +++ b/src/Umbraco.Core/Models/KeyValue.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; @@ -8,7 +8,7 @@ namespace Umbraco.Cms.Core.Models; /// [Serializable] [DataContract(IsReference = true)] -public class KeyValue : EntityBase, IKeyValue, IEntity +public class KeyValue : EntityBase, IKeyValue { private string _identifier = null!; private string? _value; diff --git a/src/Umbraco.Core/Models/Language.cs b/src/Umbraco.Core/Models/Language.cs index 9c7ea289e43f..9299665755c3 100644 --- a/src/Umbraco.Core/Models/Language.cs +++ b/src/Umbraco.Core/Models/Language.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; using System.Runtime.Serialization; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models.Entities; diff --git a/src/Umbraco.Core/Models/Link.cs b/src/Umbraco.Core/Models/Link.cs index 9a83d340d7dd..7047b54555ff 100644 --- a/src/Umbraco.Core/Models/Link.cs +++ b/src/Umbraco.Core/Models/Link.cs @@ -1,10 +1,14 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; public class Link { public string? Name { get; set; } + public string? Target { get; set; } + public LinkType Type { get; set; } + public Udi? Udi { get; set; } + public string? Url { get; set; } } diff --git a/src/Umbraco.Core/Models/LinkType.cs b/src/Umbraco.Core/Models/LinkType.cs index 7d60d9b67fb1..500380504374 100644 --- a/src/Umbraco.Core/Models/LinkType.cs +++ b/src/Umbraco.Core/Models/LinkType.cs @@ -1,8 +1,8 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; public enum LinkType { Content, Media, - External + External, } diff --git a/src/Umbraco.Core/Models/LogViewerQuery.cs b/src/Umbraco.Core/Models/LogViewerQuery.cs index 615350f6e533..5941763e24db 100644 --- a/src/Umbraco.Core/Models/LogViewerQuery.cs +++ b/src/Umbraco.Core/Models/LogViewerQuery.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/Macro.cs b/src/Umbraco.Core/Models/Macro.cs index 3c73709662de..ea03750e32fb 100644 --- a/src/Umbraco.Core/Models/Macro.cs +++ b/src/Umbraco.Core/Models/Macro.cs @@ -1,4 +1,4 @@ -using System.Collections.Specialized; +using System.Collections.Specialized; using System.ComponentModel; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; @@ -51,8 +51,19 @@ public Macro(IShortStringHelper shortStringHelper) /// /// /// - public Macro(IShortStringHelper shortStringHelper, int id, Guid key, bool useInEditor, int cacheDuration, - string alias, string? name, bool cacheByPage, bool cacheByMember, bool dontRender, string macroSource) + /// + public Macro( + IShortStringHelper shortStringHelper, + int id, + Guid key, + bool useInEditor, + int cacheDuration, + string alias, + string? name, + bool cacheByPage, + bool cacheByMember, + bool dontRender, + string macroSource) : this(shortStringHelper) { Id = id; @@ -78,7 +89,11 @@ public Macro(IShortStringHelper shortStringHelper, int id, Guid key, bool useInE /// /// /// - public Macro(IShortStringHelper shortStringHelper, string alias, string? name, + /// + public Macro( + IShortStringHelper shortStringHelper, + string alias, + string? name, string macroSource, bool cacheByPage = false, bool cacheByMember = false, @@ -97,6 +112,19 @@ public Macro(IShortStringHelper shortStringHelper, string alias, string? name, MacroSource = macroSource; } + /// + /// Gets or sets the alias of the Macro + /// + [DataMember] + public string Alias + { + get => _alias; + set => SetPropertyValueAndDetectChanges( + value.ToCleanString(_shortStringHelper, CleanStringType.Alias), + ref _alias!, + nameof(Alias)); + } + /// /// Used internally to check if we need to add a section in the repository to the db /// @@ -120,17 +148,6 @@ public override void ResetDirtyProperties(bool rememberDirty) } } - /// - /// Gets or sets the alias of the Macro - /// - [DataMember] - public string Alias - { - get => _alias; - set => SetPropertyValueAndDetectChanges(value.ToCleanString(_shortStringHelper, CleanStringType.Alias), - ref _alias!, nameof(Alias)); - } - /// /// Gets or sets the name of the Macro /// @@ -207,13 +224,27 @@ public string MacroSource [DataMember] public MacroPropertyCollection Properties { get; private set; } + protected override void PerformDeepClone(object clone) + { + base.PerformDeepClone(clone); + + var clonedEntity = (Macro)clone; + + clonedEntity._addedProperties = new List(); + clonedEntity._removedProperties = new List(); + clonedEntity.Properties = (MacroPropertyCollection)Properties.DeepClone(); + + // re-assign the event handler + clonedEntity.Properties.CollectionChanged += clonedEntity.PropertiesChanged; + } + private void PropertiesChanged(object? sender, NotifyCollectionChangedEventArgs e) { OnPropertyChanged(nameof(Properties)); if (e.Action == NotifyCollectionChangedAction.Add) { - //listen for changes + // listen for changes MacroProperty? prop = e.NewItems?.Cast().First(); if (prop is not null) { @@ -223,15 +254,15 @@ private void PropertiesChanged(object? sender, NotifyCollectionChangedEventArgs if (_addedProperties.Contains(alias) == false) { - //add to the added props + // add to the added props _addedProperties.Add(alias); } } } else if (e.Action == NotifyCollectionChangedAction.Remove) { - //remove listening for changes - MacroProperty prop = e.OldItems?.Cast().First(); + // remove listening for changes + MacroProperty? prop = e.OldItems?.Cast().First(); if (prop is not null) { prop.PropertyChanged -= PropertyDataChanged; @@ -253,17 +284,4 @@ private void PropertiesChanged(object? sender, NotifyCollectionChangedEventArgs /// private void PropertyDataChanged(object? sender, PropertyChangedEventArgs e) => OnPropertyChanged(nameof(Properties)); - - protected override void PerformDeepClone(object clone) - { - base.PerformDeepClone(clone); - - var clonedEntity = (Macro)clone; - - clonedEntity._addedProperties = new List(); - clonedEntity._removedProperties = new List(); - clonedEntity.Properties = (MacroPropertyCollection)Properties.DeepClone(); - //re-assign the event handler - clonedEntity.Properties.CollectionChanged += clonedEntity.PropertiesChanged; - } } diff --git a/src/Umbraco.Core/Models/MacroProperty.cs b/src/Umbraco.Core/Models/MacroProperty.cs index 2164de658cb0..2a6f041fc0f2 100644 --- a/src/Umbraco.Core/Models/MacroProperty.cs +++ b/src/Umbraco.Core/Models/MacroProperty.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; @@ -126,17 +126,16 @@ public string EditorAlias public object DeepClone() { - //Memberwise clone on MacroProperty will work since it doesn't have any deep elements + // Memberwise clone on MacroProperty will work since it doesn't have any deep elements // for any sub class this will work for standard properties as well that aren't complex object's themselves. var clone = (MacroProperty)MemberwiseClone(); - //Automatically deep clone ref properties that are IDeepCloneable + + // Automatically deep clone ref properties that are IDeepCloneable DeepCloneHelper.DeepCloneRefProperties(this, clone); clone.ResetDirtyProperties(false); return clone; } - protected bool Equals(MacroProperty other) => string.Equals(_alias, other._alias) && _id == other._id; - public override bool Equals(object? obj) { if (ReferenceEquals(null, obj)) @@ -157,6 +156,8 @@ public override bool Equals(object? obj) return Equals((MacroProperty)obj); } + protected bool Equals(MacroProperty other) => string.Equals(_alias, other._alias) && _id == other._id; + public override int GetHashCode() { unchecked diff --git a/src/Umbraco.Core/Models/MacroPropertyCollection.cs b/src/Umbraco.Core/Models/MacroPropertyCollection.cs index b763c1fd418d..c31cc8cbc113 100644 --- a/src/Umbraco.Core/Models/MacroPropertyCollection.cs +++ b/src/Umbraco.Core/Models/MacroPropertyCollection.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Collections; +using Umbraco.Cms.Core.Collections; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Models; @@ -34,8 +34,7 @@ public object DeepClone() /// The existing property alias /// /// - public void UpdateProperty(string currentAlias, string? name = null, int? sortOrder = null, - string? editorAlias = null, string? newAlias = null) + public void UpdateProperty(string currentAlias, string? name = null, int? sortOrder = null, string? editorAlias = null, string? newAlias = null) { IMacroProperty prop = this[currentAlias]; if (prop == null) diff --git a/src/Umbraco.Core/Models/Mapping/AuditMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/AuditMapDefinition.cs index 7485d9ac45bf..02095596e779 100644 --- a/src/Umbraco.Core/Models/Mapping/AuditMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/AuditMapDefinition.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; namespace Umbraco.Cms.Core.Models.Mapping; diff --git a/src/Umbraco.Core/Models/Mapping/CommonMapper.cs b/src/Umbraco.Core/Models/Mapping/CommonMapper.cs index e925cb7820f8..017ac1eb22fc 100644 --- a/src/Umbraco.Core/Models/Mapping/CommonMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/CommonMapper.cs @@ -16,8 +16,11 @@ public class CommonMapper private readonly ILocalizedTextService _localizedTextService; private readonly IUserService _userService; - public CommonMapper(IUserService userService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, - ContentAppFactoryCollection contentAppDefinitions, ILocalizedTextService localizedTextService) + public CommonMapper( + IUserService userService, + IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, + ContentAppFactoryCollection contentAppDefinitions, + ILocalizedTextService localizedTextService) { _userService = userService; _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; @@ -27,20 +30,20 @@ public CommonMapper(IUserService userService, IContentTypeBaseServiceProvider co public UserProfile? GetOwner(IContentBase source, MapperContext context) { - IProfile profile = source.GetCreatorProfile(_userService); + IProfile? profile = source.GetCreatorProfile(_userService); return profile == null ? null : context.Map(profile); } public UserProfile? GetCreator(IContent source, MapperContext context) { - IProfile profile = source.GetWriterProfile(_userService); + IProfile? profile = source.GetWriterProfile(_userService); return profile == null ? null : context.Map(profile); } public ContentTypeBasic? GetContentType(IContentBase source, MapperContext context) { - IContentTypeComposition contentType = _contentTypeBaseServiceProvider.GetContentTypeOf(source); - ContentTypeBasic contentTypeBasic = context.Map(contentType); + IContentTypeComposition? contentType = _contentTypeBaseServiceProvider.GetContentTypeOf(source); + ContentTypeBasic? contentTypeBasic = context.Map(contentType); return contentTypeBasic; } diff --git a/src/Umbraco.Core/Models/Mapping/ContentPropertyBasicMapper.cs b/src/Umbraco.Core/Models/Mapping/ContentPropertyBasicMapper.cs index d5461fec07b6..d3502cf887d5 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentPropertyBasicMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentPropertyBasicMapper.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.PropertyEditors; @@ -17,8 +17,11 @@ internal class ContentPropertyBasicMapper private readonly ILogger> _logger; private readonly PropertyEditorCollection _propertyEditors; - public ContentPropertyBasicMapper(IDataTypeService dataTypeService, IEntityService entityService, - ILogger> logger, PropertyEditorCollection propertyEditors) + public ContentPropertyBasicMapper( + IDataTypeService dataTypeService, + IEntityService entityService, + ILogger> logger, + PropertyEditorCollection propertyEditors) { _logger = logger; _propertyEditors = propertyEditors; @@ -34,9 +37,7 @@ public ContentPropertyBasicMapper(IDataTypeService dataTypeService, IEntityServi /// public virtual void Map(IProperty property, TDestination dest, MapperContext context) { - IDataEditor editor = property.PropertyType is not null - ? _propertyEditors[property.PropertyType.PropertyEditorAlias] - : null; + IDataEditor? editor = property.PropertyType is not null ? _propertyEditors[property.PropertyType.PropertyEditorAlias] : null; if (editor == null) { _logger.LogError( @@ -68,17 +69,17 @@ public virtual void Map(IProperty property, TDestination dest, MapperContext con return; } - //Get the culture from the context which will be set during the mapping operation for each property + // Get the culture from the context which will be set during the mapping operation for each property var culture = context.GetCulture(); - //a culture needs to be in the context for a property type that can vary + // a culture needs to be in the context for a property type that can vary if (culture == null && property.PropertyType.VariesByCulture()) { throw new InvalidOperationException( $"No culture found in mapping operation when one is required for the culture variant property type {property.PropertyType.Alias}"); } - //set the culture to null if it's an invariant property type + // set the culture to null if it's an invariant property type culture = !property.PropertyType.VariesByCulture() ? null : culture; dest.Culture = culture; diff --git a/src/Umbraco.Core/Models/Mapping/ContentPropertyDisplayMapper.cs b/src/Umbraco.Core/Models/Mapping/ContentPropertyDisplayMapper.cs index 468c98f889a5..eb6c6d92e006 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentPropertyDisplayMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentPropertyDisplayMapper.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.Logging; @@ -19,8 +19,12 @@ internal class ContentPropertyDisplayMapper : ContentPropertyBasicMapper logger, + public ContentPropertyDisplayMapper( + ICultureDictionary cultureDictionary, + IDataTypeService dataTypeService, + IEntityService entityService, + ILocalizedTextService textService, + ILogger logger, PropertyEditorCollection propertyEditors) : base(dataTypeService, entityService, logger, propertyEditors) { @@ -43,16 +47,16 @@ public override void Map(IProperty originalProp, ContentPropertyDisplay dest, Ma // - does it make any sense to use a IDataValueEditor without configuring it? // configure the editor for display with configuration - IDataValueEditor valEditor = dest.PropertyEditor?.GetValueEditor(config); + IDataValueEditor? valEditor = dest.PropertyEditor?.GetValueEditor(config); - //set the display properties after mapping + // set the display properties after mapping dest.Alias = originalProp.Alias; dest.Description = originalProp.PropertyType?.Description; dest.Label = originalProp.PropertyType?.Name; dest.HideLabel = valEditor?.HideLabel ?? false; dest.LabelOnTop = originalProp.PropertyType?.LabelOnTop; - //add the validation information + // add the validation information dest.Validation.Mandatory = originalProp.PropertyType?.Mandatory ?? false; dest.Validation.MandatoryMessage = originalProp.PropertyType?.MandatoryMessage; dest.Validation.Pattern = originalProp.PropertyType?.ValidationRegExp; @@ -60,19 +64,19 @@ public override void Map(IProperty originalProp, ContentPropertyDisplay dest, Ma if (dest.PropertyEditor == null) { - //display.Config = PreValueCollection.AsDictionary(preVals); - //if there is no property editor it means that it is a legacy data type + // display.Config = PreValueCollection.AsDictionary(preVals); + // if there is no property editor it means that it is a legacy data type // we cannot support editing with that so we'll just render the readonly value view. dest.View = "views/propertyeditors/readonlyvalue/readonlyvalue.html"; } else { - //let the property editor format the pre-values + // let the property editor format the pre-values dest.Config = dest.PropertyEditor.GetConfigurationEditor().ToValueEditor(config); dest.View = valEditor?.View; } - //Translate + // Translate dest.Label = _textService.UmbracoDictionaryTranslate(_cultureDictionary, dest.Label); dest.Description = _textService.UmbracoDictionaryTranslate(_cultureDictionary, dest.Description); } diff --git a/src/Umbraco.Core/Models/Mapping/ContentPropertyDtoMapper.cs b/src/Umbraco.Core/Models/Mapping/ContentPropertyDtoMapper.cs index 6f645ab7d80b..5836317b5c33 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentPropertyDtoMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentPropertyDtoMapper.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.PropertyEditors; @@ -11,8 +11,7 @@ namespace Umbraco.Cms.Core.Models.Mapping; /// internal class ContentPropertyDtoMapper : ContentPropertyBasicMapper { - public ContentPropertyDtoMapper(IDataTypeService dataTypeService, IEntityService entityService, - ILogger logger, PropertyEditorCollection propertyEditors) + public ContentPropertyDtoMapper(IDataTypeService dataTypeService, IEntityService entityService, ILogger logger, PropertyEditorCollection propertyEditors) : base(dataTypeService, entityService, logger, propertyEditors) { } @@ -21,15 +20,13 @@ public override void Map(IProperty property, ContentPropertyDto dest, MapperCont { base.Map(property, dest, context); - dest.IsRequired = property.PropertyType?.Mandatory; - dest.IsRequiredMessage = property.PropertyType?.MandatoryMessage; - dest.ValidationRegExp = property.PropertyType?.ValidationRegExp; - dest.ValidationRegExpMessage = property.PropertyType?.ValidationRegExpMessage; - dest.Description = property.PropertyType?.Description; - dest.Label = property.PropertyType?.Name; - dest.DataType = property.PropertyType is null - ? null - : DataTypeService.GetDataType(property.PropertyType.DataTypeId); - dest.LabelOnTop = property.PropertyType?.LabelOnTop; + dest.IsRequired = property.PropertyType.Mandatory; + dest.IsRequiredMessage = property.PropertyType.MandatoryMessage; + dest.ValidationRegExp = property.PropertyType.ValidationRegExp; + dest.ValidationRegExpMessage = property.PropertyType.ValidationRegExpMessage; + dest.Description = property.PropertyType.Description; + dest.Label = property.PropertyType.Name; + dest.DataType = DataTypeService.GetDataType(property.PropertyType.DataTypeId); + dest.LabelOnTop = property.PropertyType.LabelOnTop; } } diff --git a/src/Umbraco.Core/Models/Mapping/ContentPropertyMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/ContentPropertyMapDefinition.cs index 6df8943ae7de..1e27389ebf8e 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentPropertyMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentPropertyMapDefinition.cs @@ -18,17 +18,31 @@ public class ContentPropertyMapDefinition : IMapDefinition private readonly ContentPropertyDisplayMapper _contentPropertyDisplayMapper; private readonly ContentPropertyDtoMapper _contentPropertyDtoConverter; - public ContentPropertyMapDefinition(ICultureDictionary cultureDictionary, IDataTypeService dataTypeService, - IEntityService entityService, ILocalizedTextService textService, ILoggerFactory loggerFactory, + public ContentPropertyMapDefinition( + ICultureDictionary cultureDictionary, + IDataTypeService dataTypeService, + IEntityService entityService, + ILocalizedTextService textService, + ILoggerFactory loggerFactory, PropertyEditorCollection propertyEditors) { - _contentPropertyBasicConverter = new ContentPropertyBasicMapper(dataTypeService, - entityService, loggerFactory.CreateLogger>(), + _contentPropertyBasicConverter = new ContentPropertyBasicMapper( + dataTypeService, + entityService, + loggerFactory.CreateLogger>(), + propertyEditors); + _contentPropertyDtoConverter = new ContentPropertyDtoMapper( + dataTypeService, + entityService, + loggerFactory.CreateLogger(), + propertyEditors); + _contentPropertyDisplayMapper = new ContentPropertyDisplayMapper( + cultureDictionary, + dataTypeService, + entityService, + textService, + loggerFactory.CreateLogger(), propertyEditors); - _contentPropertyDtoConverter = new ContentPropertyDtoMapper(dataTypeService, entityService, - loggerFactory.CreateLogger(), propertyEditors); - _contentPropertyDisplayMapper = new ContentPropertyDisplayMapper(cultureDictionary, dataTypeService, - entityService, textService, loggerFactory.CreateLogger(), propertyEditors); } public void DefineMaps(IUmbracoMapper mapper) @@ -52,14 +66,17 @@ private void Map(PropertyGroup source, Tab target, Mappe } private void Map(IProperty source, ContentPropertyBasic target, MapperContext context) => + // assume this is mapping everything and no MapAll is required _contentPropertyBasicConverter.Map(source, target, context); private void Map(IProperty source, ContentPropertyDto target, MapperContext context) => + // assume this is mapping everything and no MapAll is required _contentPropertyDtoConverter.Map(source, target, context); private void Map(IProperty source, ContentPropertyDisplay target, MapperContext context) => + // assume this is mapping everything and no MapAll is required _contentPropertyDisplayMapper.Map(source, target, context); } diff --git a/src/Umbraco.Core/Models/Mapping/ContentSavedStateMapper.cs b/src/Umbraco.Core/Models/Mapping/ContentSavedStateMapper.cs index bf9cfcad50cb..ba06dae711e9 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentSavedStateMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentSavedStateMapper.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Extensions; @@ -31,10 +31,10 @@ public ContentSavedState Map(IContent source, MapperContext context) if (source.ContentType.VariesByCulture()) { - //Get the culture from the context which will be set during the mapping operation for each variant + // Get the culture from the context which will be set during the mapping operation for each variant var culture = context.GetCulture(); - //a culture needs to be in the context for a variant content item + // a culture needs to be in the context for a variant content item if (culture == null) { throw new InvalidOperationException( @@ -44,7 +44,7 @@ public ContentSavedState Map(IContent source, MapperContext context) publishedState = source.PublishedState == PublishedState - .Unpublished //if the entire document is unpublished, then flag every variant as unpublished + .Unpublished // if the entire document is unpublished, then flag every variant as unpublished ? PublishedState.Unpublished : source.IsCulturePublished(culture) ? PublishedState.Published diff --git a/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs index 1404986e8d17..31c2e86b5b67 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentTypeMapDefinition.cs @@ -34,26 +34,48 @@ public class ContentTypeMapDefinition : IMapDefinition private readonly IShortStringHelper _shortStringHelper; private ContentSettings _contentSettings; - [Obsolete("Use ctor with all params injected")] - public ContentTypeMapDefinition(CommonMapper commonMapper, PropertyEditorCollection propertyEditors, - IDataTypeService dataTypeService, IFileService fileService, - IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, + public ContentTypeMapDefinition( + CommonMapper commonMapper, + PropertyEditorCollection propertyEditors, + IDataTypeService dataTypeService, + IFileService fileService, + IContentTypeService contentTypeService, + IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, - ILoggerFactory loggerFactory, IShortStringHelper shortStringHelper, IOptions globalSettings, + ILoggerFactory loggerFactory, + IShortStringHelper shortStringHelper, + IOptions globalSettings, IHostingEnvironment hostingEnvironment) - : this(commonMapper, propertyEditors, dataTypeService, fileService, contentTypeService, mediaTypeService, - memberTypeService, loggerFactory, shortStringHelper, globalSettings, hostingEnvironment, + : this( + commonMapper, + propertyEditors, + dataTypeService, + fileService, + contentTypeService, + mediaTypeService, + memberTypeService, + loggerFactory, + shortStringHelper, + globalSettings, + hostingEnvironment, StaticServiceProvider.Instance.GetRequiredService>()) { } - public ContentTypeMapDefinition(CommonMapper commonMapper, PropertyEditorCollection propertyEditors, - IDataTypeService dataTypeService, IFileService fileService, - IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, + public ContentTypeMapDefinition( + CommonMapper commonMapper, + PropertyEditorCollection propertyEditors, + IDataTypeService dataTypeService, + IFileService fileService, + IContentTypeService contentTypeService, + IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, - ILoggerFactory loggerFactory, IShortStringHelper shortStringHelper, IOptions globalSettings, - IHostingEnvironment hostingEnvironment, IOptionsMonitor contentSettings) + ILoggerFactory loggerFactory, + IShortStringHelper shortStringHelper, + IOptions globalSettings, + IHostingEnvironment hostingEnvironment, + IOptionsMonitor contentSettings) { _commonMapper = commonMapper; _propertyEditors = propertyEditors; @@ -72,6 +94,32 @@ public ContentTypeMapDefinition(CommonMapper commonMapper, PropertyEditorCollect contentSettings.OnChange(x => _contentSettings = x); } + public static Udi? MapContentTypeUdi(IContentTypeComposition source) + { + if (source == null) + { + return null; + } + + string udiType; + switch (source) + { + case IMemberType _: + udiType = Constants.UdiEntityType.MemberType; + break; + case IMediaType _: + udiType = Constants.UdiEntityType.MediaType; + break; + case IContentType _: + udiType = Constants.UdiEntityType.DocumentType; + break; + default: + throw new PanicException($"Source is of type {source.GetType()} which isn't supported here"); + } + + return Udi.Create(udiType, source.Key); + } + public void DefineMaps(IUmbracoMapper mapper) { mapper.Define( @@ -95,7 +143,8 @@ public void DefineMaps(IUmbracoMapper mapper) } return new PropertyType(_shortStringHelper, dataType, source.Alias); - }, Map); + }, + Map); // TODO: isPublishing in ctor? mapper.Define, PropertyGroup>( @@ -122,28 +171,6 @@ public void DefineMaps(IUmbracoMapper mapper) (source, context) => new MemberPropertyTypeDisplay(), Map); } - // no MapAll - take care - private void Map(DocumentTypeSave source, IContentType target, MapperContext context) - { - MapSaveToTypeBase(source, target, context); - MapComposition(source, target, alias => _contentTypeService.Get(alias)); - - if (target is IContentTypeWithHistoryCleanup targetWithHistoryCleanup) - { - MapHistoryCleanup(source, targetWithHistoryCleanup); - } - - target.AllowedTemplates = source.AllowedTemplates? - .Where(x => x != null) - .Select(_fileService.GetTemplate) - .WhereNotNull() - .ToArray(); - - target.SetDefaultTemplate(source.DefaultTemplate == null - ? null - : _fileService.GetTemplate(source.DefaultTemplate)); - } - private static void MapHistoryCleanup(DocumentTypeSave source, IContentTypeWithHistoryCleanup target) { // If source history cleanup is null we don't have to map all properties @@ -172,6 +199,76 @@ private static void MapHistoryCleanup(DocumentTypeSave source, IContentTypeWithH } } + // Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate + // Umbraco.Code.MapAll -SupportsPublishing -Key -PropertyEditorAlias -ValueStorageType -Variations + private static void Map(PropertyTypeBasic source, IPropertyType target, MapperContext context) + { + target.Name = source.Label; + target.DataTypeId = source.DataTypeId; + target.DataTypeKey = source.DataTypeKey; + target.Mandatory = source.Validation?.Mandatory ?? false; + target.MandatoryMessage = source.Validation?.MandatoryMessage; + target.ValidationRegExp = source.Validation?.Pattern; + target.ValidationRegExpMessage = source.Validation?.PatternMessage; + target.SetVariesBy(ContentVariation.Culture, source.AllowCultureVariant); + target.SetVariesBy(ContentVariation.Segment, source.AllowSegmentVariant); + + if (source.Id > 0) + { + target.Id = source.Id; + } + + if (source.GroupId > 0) + { + if (target.PropertyGroupId?.Value != source.GroupId) + { + target.PropertyGroupId = new Lazy(() => source.GroupId, false); + } + } + + target.Alias = source.Alias; + target.Description = source.Description; + target.SortOrder = source.SortOrder; + target.LabelOnTop = source.LabelOnTop; + } + + // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate -Key -PropertyTypes + private static void Map(PropertyGroupBasic source, PropertyGroup target, MapperContext context) + { + if (source.Id > 0) + { + target.Id = source.Id; + } + + target.Key = source.Key; + target.Type = source.Type; + target.Name = source.Name; + target.Alias = source.Alias; + target.SortOrder = source.SortOrder; + } + + // no MapAll - take care + private void Map(DocumentTypeSave source, IContentType target, MapperContext context) + { + MapSaveToTypeBase(source, target, context); + MapComposition(source, target, alias => _contentTypeService.Get(alias)); + + if (target is IContentTypeWithHistoryCleanup targetWithHistoryCleanup) + { + MapHistoryCleanup(source, targetWithHistoryCleanup); + } + + target.AllowedTemplates = source.AllowedTemplates? + .Where(x => x != null) + .Select(_fileService.GetTemplate) + .WhereNotNull() + .ToArray(); + + target.SetDefaultTemplate(source.DefaultTemplate == null + ? null + : _fileService.GetTemplate(source.DefaultTemplate)); + } + // no MapAll - take care private void Map(MediaTypeSave source, IMediaType target, MapperContext context) { @@ -219,7 +316,7 @@ private void Map(IContentType source, DocumentTypeDisplay target, MapperContext _contentSettings.ContentVersionCleanupPolicy.KeepAllVersionsNewerThanDays, GlobalKeepLatestVersionPerDayForDays = _contentSettings.ContentVersionCleanupPolicy.KeepLatestVersionPerDayForDays, - GlobalEnableCleanup = _contentSettings.ContentVersionCleanupPolicy.EnableCleanup + GlobalEnableCleanup = _contentSettings.ContentVersionCleanupPolicy.EnableCleanup, }; } @@ -227,7 +324,7 @@ private void Map(IContentType source, DocumentTypeDisplay target, MapperContext target.AllowSegmentVariant = source.VariesBySegment(); target.ContentApps = _commonMapper.GetContentAppsForEntity(source); - //sync templates + // sync templates if (source.AllowedTemplates is not null) { target.AllowedTemplates = @@ -239,7 +336,7 @@ private void Map(IContentType source, DocumentTypeDisplay target, MapperContext target.DefaultTemplate = context.Map(source.DefaultTemplate); } - //default listview + // default listview target.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Content"; if (string.IsNullOrEmpty(source.Alias)) @@ -259,7 +356,7 @@ private void Map(IMediaType source, MediaTypeDisplay target, MapperContext conte { MapTypeToDisplayBase(source, target); - //default listview + // default listview target.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Media"; target.IsSystemMediaType = source.IsSystemMediaType(); @@ -280,7 +377,7 @@ private void Map(IMemberType source, MemberTypeDisplay target, MapperContext con { MapTypeToDisplayBase(source, target); - //map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData + // map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData foreach (IPropertyType propertyType in source.PropertyTypes) { IPropertyType localCopy = propertyType; @@ -340,48 +437,18 @@ private void Map(IMediaType source, ContentTypeBasic target, MapperContext conte private void Map(IMemberType source, ContentTypeBasic target, MapperContext context) => Map(source, target, Constants.UdiEntityType.MemberType); - // Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate - // Umbraco.Code.MapAll -SupportsPublishing -Key -PropertyEditorAlias -ValueStorageType -Variations - private static void Map(PropertyTypeBasic source, IPropertyType target, MapperContext context) - { - target.Name = source.Label; - target.DataTypeId = source.DataTypeId; - target.DataTypeKey = source.DataTypeKey; - target.Mandatory = source.Validation?.Mandatory ?? false; - target.MandatoryMessage = source.Validation?.MandatoryMessage; - target.ValidationRegExp = source.Validation?.Pattern; - target.ValidationRegExpMessage = source.Validation?.PatternMessage; - target.SetVariesBy(ContentVariation.Culture, source.AllowCultureVariant); - target.SetVariesBy(ContentVariation.Segment, source.AllowSegmentVariant); - - if (source.Id > 0) - { - target.Id = source.Id; - } - - if (source.GroupId > 0) - { - if (target.PropertyGroupId?.Value != source.GroupId) - { - target.PropertyGroupId = new Lazy(() => source.GroupId, false); - } - } - - target.Alias = source.Alias; - target.Description = source.Description; - target.SortOrder = source.SortOrder; - target.LabelOnTop = source.LabelOnTop; - } - // no MapAll - take care private void Map(DocumentTypeSave source, DocumentTypeDisplay target, MapperContext context) { - MapTypeToDisplayBase(source, - target, context); + MapTypeToDisplayBase( + source, + target, + context); - //sync templates + // sync templates IEnumerable destAllowedTemplateAliases = target.AllowedTemplates.Select(x => x.Alias); - //if the dest is set and it's the same as the source, then don't change + + // if the dest is set and it's the same as the source, then don't change if (source.AllowedTemplates is not null && destAllowedTemplateAliases.SequenceEqual(source.AllowedTemplates) == false) { @@ -400,7 +467,7 @@ private void Map(DocumentTypeSave source, DocumentTypeDisplay target, MapperCont if (source.DefaultTemplate.IsNullOrWhiteSpace() == false) { - //if the dest is set and it's the same as the source, then don't change + // if the dest is set and it's the same as the source, then don't change if (target.DefaultTemplate == null || source.DefaultTemplate != target.DefaultTemplate.Alias) { ITemplate? template = _fileService.GetTemplate(source.DefaultTemplate); @@ -415,8 +482,10 @@ private void Map(DocumentTypeSave source, DocumentTypeDisplay target, MapperCont // no MapAll - take care private void Map(MediaTypeSave source, MediaTypeDisplay target, MapperContext context) => - MapTypeToDisplayBase(source, - target, context); + MapTypeToDisplayBase( + source, + target, + context); // no MapAll - take care private void Map(MemberTypeSave source, MemberTypeDisplay target, MapperContext context) => @@ -424,24 +493,7 @@ private void Map(MemberTypeSave source, MemberTypeDisplay target, MapperContext source, target, context); // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate -Key -PropertyTypes - private static void Map(PropertyGroupBasic source, PropertyGroup target, - MapperContext context) - { - if (source.Id > 0) - { - target.Id = source.Id; - } - - target.Key = source.Key; - target.Type = source.Type; - target.Name = source.Name; - target.Alias = source.Alias; - target.SortOrder = source.SortOrder; - } - - // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate -Key -PropertyTypes - private static void Map(PropertyGroupBasic source, PropertyGroup target, - MapperContext context) + private static void Map(PropertyGroupBasic source, PropertyGroup target, MapperContext context) { if (source.Id > 0) { @@ -456,8 +508,10 @@ private static void Map(PropertyGroupBasic source, Prop } // Umbraco.Code.MapAll -ContentTypeId -ParentTabContentTypes -ParentTabContentTypeNames - private static void Map(PropertyGroupBasic source, - PropertyGroupDisplay target, MapperContext context) + private static void Map( + PropertyGroupBasic source, + PropertyGroupDisplay target, + MapperContext context) { target.Inherited = source.Inherited; if (source.Id > 0) @@ -475,8 +529,10 @@ private static void Map(PropertyGroupBasic source, } // Umbraco.Code.MapAll -ContentTypeId -ParentTabContentTypes -ParentTabContentTypeNames - private static void Map(PropertyGroupBasic source, - PropertyGroupDisplay target, MapperContext context) + private static void Map( + PropertyGroupBasic source, + PropertyGroupDisplay target, + MapperContext context) { target.Inherited = source.Inherited; if (source.Id > 0) @@ -535,8 +591,10 @@ private static void Map(MemberPropertyTypeBasic source, MemberPropertyTypeDispla // Umbraco.Code.MapAll -CreatorId -Level -SortOrder -Variations // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate // Umbraco.Code.MapAll -ContentTypeComposition (done by AfterMapSaveToType) - private static void MapSaveToTypeBase(TSource source, - IContentTypeComposition target, MapperContext context) + private static void MapSaveToTypeBase( + TSource source, + IContentTypeComposition target, + MapperContext context) where TSource : ContentTypeSave where TSourcePropertyType : PropertyTypeBasic { @@ -570,7 +628,6 @@ private static void MapSaveToTypeBase(TSource sour target.AllowedContentTypes = source.AllowedContentTypes.Select((t, i) => new ContentTypeSort(t, i)); } - if (!(target is IMemberType)) { target.SetVariesBy(ContentVariation.Culture, source.AllowCultureVariant); @@ -703,8 +760,11 @@ private void MapTypeToDisplayBase(IContentTypeComp { MapTypeToDisplayBase(source, target); - var groupsMapper = new PropertyTypeGroupMapper(_propertyEditors, _dataTypeService, - _shortStringHelper, _loggerFactory.CreateLogger>()); + var groupsMapper = new PropertyTypeGroupMapper( + _propertyEditors, + _dataTypeService, + _shortStringHelper, + _loggerFactory.CreateLogger>()); target.Groups = groupsMapper.Map(source); } @@ -737,8 +797,10 @@ private void MapTypeToDisplayBase(ContentTypeSave source, ContentTypeComposition } // no MapAll - relies on the non-generic method - private void MapTypeToDisplayBase(TSource source, - TTarget target, MapperContext context) + private void MapTypeToDisplayBase( + TSource source, + TTarget target, + MapperContext context) where TSource : ContentTypeSave where TSourcePropertyType : PropertyTypeBasic where TTarget : ContentTypeCompositionDisplay @@ -769,6 +831,7 @@ private IEnumerable MapLockedCompositions(IContentTypeComposition source var aliases = new List(); IEnumerable? ancestorIds = parent.Path?.Split(Constants.CharArrays.Comma) .Select(s => int.Parse(s, CultureInfo.InvariantCulture)); + // loop through all content types and return ordered aliases of ancestors IContentType[] allContentTypes = _contentTypeService.GetAll().ToArray(); if (ancestorIds is not null) @@ -783,38 +846,13 @@ private IEnumerable MapLockedCompositions(IContentTypeComposition source } } - return aliases.OrderBy(x => x); } - public static Udi? MapContentTypeUdi(IContentTypeComposition source) - { - if (source == null) - { - return null; - } - - string udiType; - switch (source) - { - case IMemberType _: - udiType = Constants.UdiEntityType.MemberType; - break; - case IMediaType _: - udiType = Constants.UdiEntityType.MediaType; - break; - case IContentType _: - udiType = Constants.UdiEntityType.DocumentType; - break; - default: - throw new PanicException($"Source is of type {source.GetType()} which isn't supported here"); - } - - return Udi.Create(udiType, source.Key); - } - - private static PropertyGroup? MapSaveGroup(PropertyGroupBasic sourceGroup, - IEnumerable destOrigGroups, MapperContext context) + private static PropertyGroup? MapSaveGroup( + PropertyGroupBasic sourceGroup, + IEnumerable destOrigGroups, + MapperContext context) where TPropertyType : PropertyTypeBasic { PropertyGroup? destGroup; @@ -840,8 +878,10 @@ private IEnumerable MapLockedCompositions(IContentTypeComposition source return destGroup; } - private static IPropertyType? MapSaveProperty(PropertyTypeBasic sourceProperty, - IEnumerable destOrigProperties, MapperContext context) + private static IPropertyType? MapSaveProperty( + PropertyTypeBasic sourceProperty, + IEnumerable destOrigProperties, + MapperContext context) { IPropertyType? destProperty; if (sourceProperty.Id > 0) @@ -892,8 +932,7 @@ private static void EnsureUniqueAliases(IEnumerable groups) } } - private static void MapComposition(ContentTypeSave source, IContentTypeComposition target, - Func getContentType) + private static void MapComposition(ContentTypeSave source, IContentTypeComposition target, Func getContentType) { var current = target.CompositionAliases().ToArray(); IEnumerable proposed = source.CompositeContentTypes; diff --git a/src/Umbraco.Core/Models/Mapping/ContentVariantMapper.cs b/src/Umbraco.Core/Models/Mapping/ContentVariantMapper.cs index 95bef6a170e9..3346ec6ef5dc 100644 --- a/src/Umbraco.Core/Models/Mapping/ContentVariantMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/ContentVariantMapper.cs @@ -27,7 +27,7 @@ public ContentVariantMapper(ILocalizationService localizationService, ILocalized if (!variesByCulture && !variesBySegment) { // this is invariant so just map the IContent instance to ContentVariationDisplay - TVariant variantDisplay = context.Map(source); + TVariant? variantDisplay = context.Map(source); if (variantDisplay is not null) { variants.Add(variantDisplay); @@ -44,7 +44,7 @@ public ContentVariantMapper(ILocalizationService localizationService, ILocalized else if (variesBySegment && !variesByCulture) { // Segment only - IEnumerable segments = GetSegments(source); + IEnumerable segments = GetSegments(source); variants = segments? .Select(segment => CreateVariantDisplay(context, source, null, segment)) .WhereNotNull() @@ -72,8 +72,10 @@ public ContentVariantMapper(ILocalizationService localizationService, ILocalized return SortVariants(variants); } + private static bool IsDefaultSegment(ContentVariantDisplay variant) => variant.Segment == null; - private IList? SortVariants(IList? variants) where TVariant : ContentVariantDisplay + private IList? SortVariants(IList? variants) + where TVariant : ContentVariantDisplay { if (variants == null || variants.Count <= 1) { @@ -89,8 +91,6 @@ public ContentVariantMapper(ILocalizationService localizationService, ILocalized .ToList(); } - private static bool IsDefaultSegment(ContentVariantDisplay variant) => variant.Segment == null; - private static bool IsDefaultLanguage(ContentVariantDisplay variant) => variant.Language == null || variant.Language.IsDefault; @@ -117,7 +117,7 @@ private static bool IsDefaultLanguage(ContentVariantDisplay variant) => { // The default segment (null) is always there, // even when there is no property data at all yet - var segments = new List {null}; + var segments = new List { null }; // Add actual segments based on the property values segments.AddRange(content.Properties.SelectMany(p => p.Values.Select(v => v.Segment))); @@ -126,13 +126,13 @@ private static bool IsDefaultLanguage(ContentVariantDisplay variant) => return segments.Distinct(); } - private TVariant? CreateVariantDisplay(MapperContext context, IContent content, - ContentEditing.Language? language, string? segment) where TVariant : ContentVariantDisplay + private TVariant? CreateVariantDisplay(MapperContext context, IContent content, ContentEditing.Language? language, string? segment) + where TVariant : ContentVariantDisplay { context.SetCulture(language?.IsoCode); context.SetSegment(segment); - TVariant variantDisplay = context.Map(content); + TVariant? variantDisplay = context.Map(content); if (variantDisplay is null) { diff --git a/src/Umbraco.Core/Models/Mapping/DataTypeMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/DataTypeMapDefinition.cs index fc0b0507d59a..de2a773257b5 100644 --- a/src/Umbraco.Core/Models/Mapping/DataTypeMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/DataTypeMapDefinition.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Mapping; @@ -14,7 +14,7 @@ public class DataTypeMapDefinition : IMapDefinition private static readonly int[] SystemIds = { Constants.DataTypes.DefaultContentListView, Constants.DataTypes.DefaultMediaListView, - Constants.DataTypes.DefaultMembersListView + Constants.DataTypes.DefaultMembersListView, }; private readonly ContentSettings _contentSettings; @@ -22,8 +22,7 @@ public class DataTypeMapDefinition : IMapDefinition private readonly PropertyEditorCollection _propertyEditors; private readonly IConfigurationEditorJsonSerializer _serializer; - public DataTypeMapDefinition(PropertyEditorCollection propertyEditors, ILogger logger, - IOptions contentSettings, IConfigurationEditorJsonSerializer serializer) + public DataTypeMapDefinition(PropertyEditorCollection propertyEditors, ILogger logger, IOptions contentSettings, IConfigurationEditorJsonSerializer serializer) { _propertyEditors = propertyEditors; _logger = logger; @@ -42,7 +41,8 @@ public void DefineMaps(IUmbracoMapper mapper) mapper.Define>(MapPreValues); mapper.Define( (source, context) => - new DataType(_propertyEditors[source.EditorAlias], _serializer) {CreateDate = DateTime.Now}, Map); + new DataType(_propertyEditors[source.EditorAlias], _serializer) { CreateDate = DateTime.Now }, + Map); mapper.Define>(MapPreValues); } @@ -87,12 +87,12 @@ private void Map(IDataType source, DataTypeBasic target, MapperContext context) target.Trashed = source.Trashed; target.Udi = Udi.Create(Constants.UdiEntityType.DataType, source.Key); - if (!_propertyEditors.TryGet(source.EditorAlias, out IDataEditor editor)) + if (!_propertyEditors.TryGet(source.EditorAlias, out IDataEditor? editor)) { return; } - target.Alias = editor!.Alias; + target.Alias = editor.Alias; target.Group = editor.Group; target.Icon = editor.Icon; } @@ -112,12 +112,12 @@ private void Map(IDataType source, DataTypeDisplay target, MapperContext context target.Trashed = source.Trashed; target.Udi = Udi.Create(Constants.UdiEntityType.DataType, source.Key); - if (!_propertyEditors.TryGet(source.EditorAlias, out IDataEditor editor)) + if (!_propertyEditors.TryGet(source.EditorAlias, out IDataEditor? editor)) { return; } - target.Alias = editor!.Alias; + target.Alias = editor.Alias; target.Group = editor.Group; target.Icon = editor.Icon; } @@ -147,15 +147,14 @@ private IEnumerable MapPreValues(IDataType da // in v7 it was apparently fine to have an empty .EditorAlias here, in which case we would map onto // an empty fields list, which made no sense since there would be nothing to map to - and besides, // a datatype without an editor alias is a serious issue - v8 wants an editor here - if (string.IsNullOrWhiteSpace(dataType.EditorAlias) || - !_propertyEditors.TryGet(dataType.EditorAlias, out IDataEditor editor)) + !_propertyEditors.TryGet(dataType.EditorAlias, out IDataEditor? editor)) { throw new InvalidOperationException( $"Could not find a property editor with alias \"{dataType.EditorAlias}\"."); } - IConfigurationEditor configurationEditor = editor!.GetConfigurationEditor(); + IConfigurationEditor configurationEditor = editor.GetConfigurationEditor(); var fields = context .MapEnumerable(configurationEditor.Fields) .WhereNotNull().ToList(); @@ -167,8 +166,7 @@ private IEnumerable MapPreValues(IDataType da return fields; } - private void MapConfigurationFields(IDataType? dataType, List fields, - IDictionary? configuration) + private void MapConfigurationFields(IDataType? dataType, List fields, IDictionary? configuration) { if (fields == null) { @@ -183,7 +181,7 @@ private void MapConfigurationFields(IDataType? dataType, List MapPreValues(IDataEditor // get the configuration editor, // get the configuration fields and map to UI, // get the configuration default values and map to UI - IConfigurationEditor configurationEditor = source.GetConfigurationEditor(); var fields = diff --git a/src/Umbraco.Core/Models/Mapping/DictionaryMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/DictionaryMapDefinition.cs index 2e6714ead4f2..cab595e00fa1 100644 --- a/src/Umbraco.Core/Models/Mapping/DictionaryMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/DictionaryMapDefinition.cs @@ -27,7 +27,8 @@ public void DefineMaps(IUmbracoMapper mapper) { mapper.Define((source, context) => new EntityBasic(), Map); mapper.Define((source, context) => new DictionaryDisplay(), Map); - mapper.Define((source, context) => new DictionaryOverviewDisplay(), + mapper.Define( + (source, context) => new DictionaryOverviewDisplay(), Map); } @@ -40,6 +41,22 @@ private static void Map(IDictionaryItem source, EntityBasic target, MapperContex target.Name = source.ItemKey; } + private static void GetParentId(Guid parentId, ILocalizationService localizationService, List ids) + { + IDictionaryItem? dictionary = localizationService.GetDictionaryItemById(parentId); + if (dictionary == null) + { + return; + } + + ids.Add(dictionary.Id); + + if (dictionary.ParentId.HasValue) + { + GetParentId(dictionary.ParentId.Value, localizationService, ids); + } + } + // Umbraco.Code.MapAll -Icon -Trashed -Alias private void Map(IDictionaryItem source, DictionaryDisplay target, MapperContext context) { @@ -57,7 +74,7 @@ private void Map(IDictionaryItem source, DictionaryDisplay target, MapperContext // TODO: check if there is a better way if (source.ParentId.HasValue) { - var ids = new List {-1}; + var ids = new List { -1 }; var parentIds = new List(); GetParentId(source.ParentId.Value, _localizationService, parentIds); parentIds.Reverse(); @@ -74,14 +91,14 @@ private void Map(IDictionaryItem source, DictionaryDisplay target, MapperContext foreach (ILanguage lang in _localizationService.GetAllLanguages()) { var langId = lang.Id; - IDictionaryTranslation translation = source.Translations?.FirstOrDefault(x => x.LanguageId == langId); + IDictionaryTranslation? translation = source.Translations?.FirstOrDefault(x => x.LanguageId == langId); target.Translations.Add(new DictionaryTranslationDisplay { IsoCode = lang.IsoCode, DisplayName = lang.CultureName, Translation = translation?.Value ?? string.Empty, - LanguageId = lang.Id + LanguageId = lang.Id, }); } } @@ -96,30 +113,14 @@ private void Map(IDictionaryItem source, DictionaryOverviewDisplay target, Mappe foreach (ILanguage lang in _localizationService.GetAllLanguages()) { var langId = lang.Id; - IDictionaryTranslation translation = source.Translations?.FirstOrDefault(x => x.LanguageId == langId); + IDictionaryTranslation? translation = source.Translations?.FirstOrDefault(x => x.LanguageId == langId); target.Translations.Add( new DictionaryOverviewTranslationDisplay { DisplayName = lang.CultureName, - HasTranslation = translation != null && string.IsNullOrEmpty(translation.Value) == false + HasTranslation = translation != null && string.IsNullOrEmpty(translation.Value) == false, }); } } - - private static void GetParentId(Guid parentId, ILocalizationService localizationService, List ids) - { - IDictionaryItem dictionary = localizationService.GetDictionaryItemById(parentId); - if (dictionary == null) - { - return; - } - - ids.Add(dictionary.Id); - - if (dictionary.ParentId.HasValue) - { - GetParentId(dictionary.ParentId.Value, localizationService, ids); - } - } } diff --git a/src/Umbraco.Core/Models/Mapping/LanguageMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/LanguageMapDefinition.cs index 7c17eb3477cb..81096889c8f6 100644 --- a/src/Umbraco.Core/Models/Mapping/LanguageMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/LanguageMapDefinition.cs @@ -1,6 +1,5 @@ -using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; -using Umbraco.Extensions; namespace Umbraco.Cms.Core.Models.Mapping; @@ -35,8 +34,7 @@ private static void Map(ILanguage source, ContentEditing.Language target, Mapper target.FallbackLanguageId = source.FallbackLanguageId; } - private static void Map(IEnumerable source, IEnumerable target, - MapperContext context) + private static void Map(IEnumerable source, IEnumerable target, MapperContext context) { if (target == null) { @@ -50,14 +48,14 @@ private static void Map(IEnumerable source, IEnumerable temp = context.MapEnumerable(source); - //Put the default language first in the list & then sort rest by a-z - ContentEditing.Language defaultLang = temp.SingleOrDefault(x => x!.IsDefault); + // Put the default language first in the list & then sort rest by a-z + ContentEditing.Language? defaultLang = temp.SingleOrDefault(x => x.IsDefault); // insert default lang first, then remaining language a-z if (defaultLang is not null) { list.Add(defaultLang); - list.AddRange(temp.Where(x => x != defaultLang).OrderBy(x => x!.Name).WhereNotNull()); + list.AddRange(temp.Where(x => x != defaultLang).OrderBy(x => x.Name)); } } } diff --git a/src/Umbraco.Core/Models/Mapping/MacroMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/MacroMapDefinition.cs index 7f036603c054..a042497013e9 100644 --- a/src/Umbraco.Core/Models/Mapping/MacroMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/MacroMapDefinition.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.PropertyEditors; @@ -64,12 +64,12 @@ private void Map(IMacroProperty source, MacroParameter target, MapperContext con target.Name = source.Name; target.SortOrder = source.SortOrder; - //map the view and the config + // map the view and the config // we need to show the deprecated ones for backwards compatibility - IDataEditor paramEditor = _parameterEditors[source.EditorAlias]; // TODO: include/filter deprecated?! + IDataEditor? paramEditor = _parameterEditors[source.EditorAlias]; // TODO: include/filter deprecated?! if (paramEditor == null) { - //we'll just map this to a text box + // we'll just map this to a text box paramEditor = _parameterEditors[Constants.PropertyEditors.Aliases.TextBox]; _logger.LogWarning( "Could not resolve a parameter editor with alias {PropertyEditorAlias}, a textbox will be rendered in it's place", @@ -83,8 +83,7 @@ private void Map(IMacroProperty source, MacroParameter target, MapperContext con // after ToValueEditor - important to use DefaultConfigurationObject here, because depending // on editors, ToValueEditor expects the actual strongly typed configuration - not the // dictionary thing returned by DefaultConfiguration - - IConfigurationEditor configurationEditor = paramEditor?.GetConfigurationEditor(); + IConfigurationEditor? configurationEditor = paramEditor?.GetConfigurationEditor(); target.Configuration = configurationEditor?.ToValueEditor(configurationEditor.DefaultConfigurationObject); } } diff --git a/src/Umbraco.Core/Models/Mapping/MapperContextExtensions.cs b/src/Umbraco.Core/Models/Mapping/MapperContextExtensions.cs index 89b95b1129f1..70d4826ab612 100644 --- a/src/Umbraco.Core/Models/Mapping/MapperContextExtensions.cs +++ b/src/Umbraco.Core/Models/Mapping/MapperContextExtensions.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Mapping; namespace Umbraco.Extensions; diff --git a/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs b/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs index b0e294c2876a..613a873d7a4b 100644 --- a/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs +++ b/src/Umbraco.Core/Models/Membership/ContentPermissionSet.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models.Membership; @@ -19,8 +19,6 @@ public ContentPermissionSet(IContent content, EntityPermissionCollection permiss public override int EntityId => _content.Id; - #region Explicit implementation of IAggregateRoot - int IEntity.Id { get => EntityId; @@ -29,11 +27,11 @@ int IEntity.Id bool IEntity.HasIdentity => EntityId > 0; + Guid IEntity.Key { get; set; } + void IEntity.ResetIdentity() => throw new InvalidOperationException($"Resetting identity on {nameof(ContentPermissionSet)} is invalid"); - Guid IEntity.Key { get; set; } - DateTime IEntity.CreateDate { get; set; } DateTime IEntity.UpdateDate { get; set; } @@ -41,6 +39,4 @@ void IEntity.ResetIdentity() => DateTime? IEntity.DeleteDate { get; set; } object IDeepCloneable.DeepClone() => throw new NotImplementedException(); - - #endregion } diff --git a/src/Umbraco.Core/Models/Membership/EntityPermission.cs b/src/Umbraco.Core/Models/Membership/EntityPermission.cs index b900cff5afac..58e84f27f907 100644 --- a/src/Umbraco.Core/Models/Membership/EntityPermission.cs +++ b/src/Umbraco.Core/Models/Membership/EntityPermission.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Membership; +namespace Umbraco.Cms.Core.Models.Membership; /// /// Represents an entity permission (defined on the user group and derived to retrieve permissions for a given user) @@ -22,6 +22,7 @@ public EntityPermission(int groupId, int entityId, string[] assignedPermissions, } public int EntityId { get; } + public int UserGroupId { get; } /// diff --git a/src/Umbraco.Core/Models/Membership/EntityPermissionCollection.cs b/src/Umbraco.Core/Models/Membership/EntityPermissionCollection.cs index 8d96fd7163e3..727f7964f771 100644 --- a/src/Umbraco.Core/Models/Membership/EntityPermissionCollection.cs +++ b/src/Umbraco.Core/Models/Membership/EntityPermissionCollection.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Membership; +namespace Umbraco.Cms.Core.Models.Membership; /// /// A of @@ -13,7 +13,8 @@ public EntityPermissionCollection() { } - public EntityPermissionCollection(IEnumerable collection) : base(collection) + public EntityPermissionCollection(IEnumerable collection) + : base(collection) { } @@ -31,8 +32,7 @@ public IEnumerable GetAllPermissions(int entityId) _aggregateNodePermissions = new Dictionary(); } - string[]? entityPermissions; - if (_aggregateNodePermissions.TryGetValue(entityId, out entityPermissions) == false) + if (_aggregateNodePermissions.TryGetValue(entityId, out string[]? entityPermissions) == false) { entityPermissions = this.Where(x => x.EntityId == entityId).SelectMany(x => x.AssignedPermissions) .Distinct().ToArray(); @@ -50,6 +50,6 @@ public IEnumerable GetAllPermissions(int entityId) /// This value is only calculated once /// public IEnumerable GetAllPermissions() => - _aggregatePermissions ?? (_aggregatePermissions = - this.SelectMany(x => x.AssignedPermissions).Distinct().ToArray()); +_aggregatePermissions ??= + this.SelectMany(x => x.AssignedPermissions).Distinct().ToArray(); } diff --git a/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs b/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs index 3b16bb679f74..0ae0dbf33573 100644 --- a/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs +++ b/src/Umbraco.Core/Models/Membership/EntityPermissionSet.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Membership; +namespace Umbraco.Cms.Core.Models.Membership; /// /// Represents an entity -> user group & permission key value pair collection @@ -30,7 +30,6 @@ public EntityPermissionSet(int entityId, EntityPermissionCollection permissionsS /// public static EntityPermissionSet Empty() => EmptyInstance.Value; - /// /// Returns the aggregate permissions in the permission set /// diff --git a/src/Umbraco.Core/Models/Membership/IMembershipUser.cs b/src/Umbraco.Core/Models/Membership/IMembershipUser.cs index 50c06b169a7d..704158a1af8e 100644 --- a/src/Umbraco.Core/Models/Membership/IMembershipUser.cs +++ b/src/Umbraco.Core/Models/Membership/IMembershipUser.cs @@ -8,7 +8,9 @@ namespace Umbraco.Cms.Core.Models.Membership; public interface IMembershipUser : IEntity { string Username { get; set; } + string Email { get; set; } + DateTime? EmailConfirmedDate { get; set; } /// @@ -22,10 +24,15 @@ public interface IMembershipUser : IEntity string? PasswordConfiguration { get; set; } string? Comments { get; set; } + bool IsApproved { get; set; } + bool IsLockedOut { get; set; } + DateTime? LastLoginDate { get; set; } + DateTime? LastPasswordChangeDate { get; set; } + DateTime? LastLockoutDate { get; set; } /// @@ -43,6 +50,6 @@ public interface IMembershipUser : IEntity /// string? SecurityStamp { get; set; } - //object ProfileId { get; set; } - //IEnumerable Groups { get; set; } + // object ProfileId { get; set; } + // IEnumerable Groups { get; set; } } diff --git a/src/Umbraco.Core/Models/Membership/IProfile.cs b/src/Umbraco.Core/Models/Membership/IProfile.cs index 77d2255d9b59..f30bfd122524 100644 --- a/src/Umbraco.Core/Models/Membership/IProfile.cs +++ b/src/Umbraco.Core/Models/Membership/IProfile.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Membership; +namespace Umbraco.Cms.Core.Models.Membership; /// /// Defines the User Profile interface @@ -6,5 +6,6 @@ public interface IProfile { int Id { get; } + string? Name { get; } } diff --git a/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs b/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs index 046a88ae70b3..2096ec3d67d7 100644 --- a/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/IReadOnlyUserGroup.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Membership; +namespace Umbraco.Cms.Core.Models.Membership; /// /// A readonly user group providing basic information @@ -6,9 +6,13 @@ public interface IReadOnlyUserGroup { string? Name { get; } + string? Icon { get; } + int Id { get; } + int? StartContentId { get; } + int? StartMediaId { get; } /// diff --git a/src/Umbraco.Core/Models/Membership/IUser.cs b/src/Umbraco.Core/Models/Membership/IUser.cs index acc5774f8e16..6fc409a0c02b 100644 --- a/src/Umbraco.Core/Models/Membership/IUser.cs +++ b/src/Umbraco.Core/Models/Membership/IUser.cs @@ -6,14 +6,18 @@ namespace Umbraco.Cms.Core.Models.Membership; /// Defines the interface for a /// /// Will be left internal until a proper Membership implementation is part of the roadmap -public interface IUser : IMembershipUser, IRememberBeingDirty, ICanBeDirty +public interface IUser : IMembershipUser, IRememberBeingDirty { UserState UserState { get; } string? Name { get; set; } + int SessionTimeout { get; set; } + int[]? StartContentIds { get; set; } + int[]? StartMediaIds { get; set; } + string? Language { get; set; } DateTime? InvitedDate { get; set; } @@ -41,6 +45,8 @@ public interface IUser : IMembershipUser, IRememberBeingDirty, ICanBeDirty string? TourData { get; set; } void RemoveGroup(string group); + void ClearGroups(); + void AddGroup(IReadOnlyUserGroup group); } diff --git a/src/Umbraco.Core/Models/Membership/IUserGroup.cs b/src/Umbraco.Core/Models/Membership/IUserGroup.cs index 5e6bff9c68fb..71ef6a7a123d 100644 --- a/src/Umbraco.Core/Models/Membership/IUserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/IUserGroup.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models.Membership; @@ -7,6 +7,7 @@ public interface IUserGroup : IEntity, IRememberBeingDirty string Alias { get; set; } int? StartContentId { get; set; } + int? StartMediaId { get; set; } /// diff --git a/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs b/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs index c30d435c297e..6119d2cea170 100644 --- a/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs +++ b/src/Umbraco.Core/Models/Packaging/CompiledPackage.cs @@ -8,19 +8,34 @@ namespace Umbraco.Cms.Core.Models.Packaging; public class CompiledPackage { public FileInfo? PackageFile { get; set; } + public string Name { get; set; } = null!; + public InstallWarnings Warnings { get; set; } = new(); + public IEnumerable Macros { get; set; } = null!; // TODO: make strongly typed + public IEnumerable MacroPartialViews { get; set; } = null!; // TODO: make strongly typed + public IEnumerable Templates { get; set; } = null!; // TODO: make strongly typed + public IEnumerable Stylesheets { get; set; } = null!; // TODO: make strongly typed + public IEnumerable Scripts { get; set; } = null!; // TODO: make strongly typed + public IEnumerable PartialViews { get; set; } = null!; // TODO: make strongly typed + public IEnumerable DataTypes { get; set; } = null!; // TODO: make strongly typed + public IEnumerable Languages { get; set; } = null!; // TODO: make strongly typed + public IEnumerable DictionaryItems { get; set; } = null!; // TODO: make strongly typed + public IEnumerable DocumentTypes { get; set; } = null!; // TODO: make strongly typed + public IEnumerable MediaTypes { get; set; } = null!; // TODO: make strongly typed + public IEnumerable Documents { get; set; } = null!; + public IEnumerable Media { get; set; } = null!; } diff --git a/src/Umbraco.Core/Models/Packaging/CompiledPackageContentBase.cs b/src/Umbraco.Core/Models/Packaging/CompiledPackageContentBase.cs index 2066d27932c0..794262406a69 100644 --- a/src/Umbraco.Core/Models/Packaging/CompiledPackageContentBase.cs +++ b/src/Umbraco.Core/Models/Packaging/CompiledPackageContentBase.cs @@ -8,7 +8,7 @@ namespace Umbraco.Cms.Core.Models.Packaging; /// public class CompiledPackageContentBase { - public string? ImportMode { get; set; } //this is never used + public string? ImportMode { get; set; } // this is never used /// /// The serialized version of the content @@ -16,5 +16,5 @@ public class CompiledPackageContentBase public XElement XmlData { get; set; } = null!; public static CompiledPackageContentBase Create(XElement xml) => - new() {XmlData = xml, ImportMode = xml.AttributeValue("importMode")}; + new() { XmlData = xml, ImportMode = xml.AttributeValue("importMode") }; } diff --git a/src/Umbraco.Core/Models/Packaging/InstallWarnings.cs b/src/Umbraco.Core/Models/Packaging/InstallWarnings.cs index 8f7c6646375a..d1154f1b30af 100644 --- a/src/Umbraco.Core/Models/Packaging/InstallWarnings.cs +++ b/src/Umbraco.Core/Models/Packaging/InstallWarnings.cs @@ -4,6 +4,8 @@ public class InstallWarnings { // TODO: Shouldn't we detect other conflicting entities too ? public IEnumerable? ConflictingMacros { get; set; } = Enumerable.Empty(); + public IEnumerable? ConflictingTemplates { get; set; } = Enumerable.Empty(); + public IEnumerable? ConflictingStylesheets { get; set; } = Enumerable.Empty(); } diff --git a/src/Umbraco.Core/Models/PublishedContent/Fallback.cs b/src/Umbraco.Core/Models/PublishedContent/Fallback.cs index e8f057c6afc6..2c665f1710c0 100644 --- a/src/Umbraco.Core/Models/PublishedContent/Fallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/Fallback.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; namespace Umbraco.Cms.Core.Models.PublishedContent; @@ -7,6 +7,11 @@ namespace Umbraco.Cms.Core.Models.PublishedContent; /// public struct Fallback : IEnumerable { + /// + /// Do not fallback. + /// + public const int None = 0; + private readonly int[] _values; /// @@ -20,40 +25,35 @@ public struct Fallback : IEnumerable /// public static Fallback To(params int[] values) => new(values); - /// - /// Do not fallback. - /// - public const int None = 0; - /// /// Fallback to default value. /// public const int DefaultValue = 1; /// - /// Gets the fallback to default value policy. + /// Fallback to other languages. /// - public static Fallback ToDefaultValue => new(new[] {DefaultValue}); + public const int Language = 2; /// - /// Fallback to other languages. + /// Fallback to tree ancestors. /// - public const int Language = 2; + public const int Ancestors = 3; /// - /// Gets the fallback to language policy. + /// Gets the fallback to default value policy. /// - public static Fallback ToLanguage => new(new[] {Language}); + public static Fallback ToDefaultValue => new(new[] { DefaultValue }); /// - /// Fallback to tree ancestors. + /// Gets the fallback to language policy. /// - public const int Ancestors = 3; + public static Fallback ToLanguage => new(new[] { Language }); /// /// Gets the fallback to tree ancestors policy. /// - public static Fallback ToAncestors => new(new[] {Ancestors}); + public static Fallback ToAncestors => new(new[] { Ancestors }); /// public IEnumerator GetEnumerator() => ((IEnumerable)_values ?? Array.Empty()).GetEnumerator(); diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index 727b9afa8455..94db74078c6f 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -98,6 +98,16 @@ public interface IPublishedContent : IPublishedElement /// PublishedItemType ItemType { get; } + #endregion + + #region Tree + + /// + /// Gets the parent of the content item. + /// + /// The parent of root content is null. + IPublishedContent? Parent { get; } + /// /// Gets a value indicating whether the content is draft. /// @@ -134,16 +144,6 @@ public interface IPublishedContent : IPublishedElement /// bool IsPublished(string? culture = null); - #endregion - - #region Tree - - /// - /// Gets the parent of the content item. - /// - /// The parent of root content is null. - IPublishedContent? Parent { get; } - /// /// Gets the children of the content item that are available for the current culture. /// diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs index a4dc8bc4290f..09e9a00389a3 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentTypeFactory.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent; +namespace Umbraco.Cms.Core.Models.PublishedContent; /// /// Creates published content types. @@ -28,8 +28,11 @@ public interface IPublishedContentTypeFactory /// The datatype identifier. /// The variations. /// Is used by constructor to create special property types. - IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, string propertyTypeAlias, - int dataTypeId, ContentVariation variations); + IPublishedPropertyType CreatePropertyType( + IPublishedContentType contentType, + string propertyTypeAlias, + int dataTypeId, + ContentVariation variations); /// /// Creates a core (non-user) published property type. @@ -39,8 +42,11 @@ IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, str /// The datatype identifier. /// The variations. /// Is used by constructor to create special property types. - IPublishedPropertyType CreateCorePropertyType(IPublishedContentType contentType, string propertyTypeAlias, - int dataTypeId, ContentVariation variations); + IPublishedPropertyType CreateCorePropertyType( + IPublishedContentType contentType, + string propertyTypeAlias, + int dataTypeId, + ContentVariation variations); /// /// Gets a published datatype. diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs index 4744adaaceb4..a198064137dc 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedElement.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent; +namespace Umbraco.Cms.Core.Models.PublishedContent; /// /// Represents a published element. diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs index 754ea527c436..03485f0b6c35 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedModelFactory.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; namespace Umbraco.Cms.Core.Models.PublishedContent; diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs index d3dd63594d55..b030f145fd2c 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent; +namespace Umbraco.Cms.Core.Models.PublishedContent; /// /// Represents a property of an IPublishedElement. diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs index db92323a55a7..3caaee9a3776 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Cms.Core.Models.PublishedContent; @@ -95,8 +95,7 @@ public interface IPublishedPropertyType /// The intermediate value. /// A value indicating whether content should be considered draft. /// The object value. - object? ConvertInterToObject(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, - bool preview); + object? ConvertInterToObject(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview); /// /// Converts the intermediate value into the XPath value. @@ -109,6 +108,5 @@ public interface IPublishedPropertyType /// /// The XPath value can be either a string or an XPathNavigator. /// - object? ConvertInterToXPath(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, - bool preview); + object? ConvertInterToXPath(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview); } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs index ca6d63089481..729f7dd6bc31 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedValueFallback.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent; +namespace Umbraco.Cms.Core.Models.PublishedContent; /// /// Provides a fallback strategy for getting values. @@ -31,8 +31,7 @@ public interface IPublishedValueFallback /// the web project. /// /// - bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, - object? defaultValue, out object? value); + bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, object? defaultValue, out object? value); /// /// Tries to get a fallback value for a property. @@ -56,8 +55,7 @@ bool TryGetValue(IPublishedProperty property, string? culture, string? segment, /// get property.Value() or property.Value{T}() to trigger fallback. /// /// - bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, - T? defaultValue, out T? value); + bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, T? defaultValue, out T? value); /// /// Tries to get a fallback value for a published element property. @@ -77,8 +75,7 @@ bool TryGetValue(IPublishedProperty property, string? culture, string? segmen /// /// It can only fallback at element level (no recurse). /// - bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, Fallback fallback, - object? defaultValue, out object? value); + bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, Fallback fallback, object? defaultValue, out object? value); /// /// Tries to get a fallback value for a published element property. @@ -99,8 +96,7 @@ bool TryGetValue(IPublishedElement content, string alias, string? culture, strin /// /// It can only fallback at element level (no recurse). /// - bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, Fallback fallback, - T? defaultValue, out T? value); + bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, Fallback fallback, T? defaultValue, out T? value); /// /// Tries to get a fallback value for a published content property. @@ -128,8 +124,15 @@ bool TryGetValue(IPublishedElement content, string alias, string? culture, st /// converter's interpretation of "no value". /// /// - bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, Fallback fallback, - object? defaultValue, out object? value, out IPublishedProperty? noValueProperty); + bool TryGetValue( + IPublishedContent content, + string alias, + string? culture, + string? segment, + Fallback fallback, + object? defaultValue, + out object? value, + out IPublishedProperty? noValueProperty); /// /// Tries to get a fallback value for a published content property. @@ -158,6 +161,13 @@ bool TryGetValue(IPublishedContent content, string alias, string? culture, strin /// converter's interpretation of "no value". /// /// - bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, Fallback fallback, - T defaultValue, out T? value, out IPublishedProperty? noValueProperty); + bool TryGetValue( + IPublishedContent content, + string alias, + string? culture, + string? segment, + Fallback fallback, + T defaultValue, + out T? value, + out IPublishedProperty? noValueProperty); } diff --git a/src/Umbraco.Core/Models/PublishedContent/IVariationContextAccessor.cs b/src/Umbraco.Core/Models/PublishedContent/IVariationContextAccessor.cs index d0fb6ffe342b..a20820d9543f 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IVariationContextAccessor.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IVariationContextAccessor.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent; +namespace Umbraco.Cms.Core.Models.PublishedContent; /// /// Gives access to the current . diff --git a/src/Umbraco.Core/Models/PublishedContent/IndexedArrayItem.cs b/src/Umbraco.Core/Models/PublishedContent/IndexedArrayItem.cs index b31588fb7d37..fe7fe2a47480 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IndexedArrayItem.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IndexedArrayItem.cs @@ -1,4 +1,4 @@ -using System.Net; +using System.Net; using Umbraco.Cms.Core.Strings; namespace Umbraco.Cms.Core.Models.PublishedContent; @@ -85,7 +85,6 @@ public IHtmlEncodedString IsFirst(string valueIfTrue, string valueIfFalse) => // TODO: This method should be removed or moved to an extension method on HtmlHelper. public bool IsNotFirst() => IsFirst() == false; - /// /// If this item is not the first, the HTML encoded will be returned; otherwise, /// . diff --git a/src/Umbraco.Core/Models/TemplateQuery/ContentTypeModel.cs b/src/Umbraco.Core/Models/TemplateQuery/ContentTypeModel.cs index c3e05df2a352..c94cd67b8a4e 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/ContentTypeModel.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/ContentTypeModel.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.TemplateQuery; +namespace Umbraco.Cms.Core.Models.TemplateQuery; public class ContentTypeModel { diff --git a/src/Umbraco.Core/Models/Trees/ActionMenuItem.cs b/src/Umbraco.Core/Models/Trees/ActionMenuItem.cs index 4eb552abd1ab..87fe72a0fbae 100644 --- a/src/Umbraco.Core/Models/Trees/ActionMenuItem.cs +++ b/src/Umbraco.Core/Models/Trees/ActionMenuItem.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Models.Trees; @@ -19,9 +19,11 @@ namespace Umbraco.Cms.Core.Models.Trees; /// public abstract class ActionMenuItem : MenuItem { - protected ActionMenuItem(string alias, string name) : base(alias, name) => Initialize(); + protected ActionMenuItem(string alias, string name) + : base(alias, name) => Initialize(); - protected ActionMenuItem(string alias, ILocalizedTextService textService) : base(alias, textService) => + protected ActionMenuItem(string alias, ILocalizedTextService textService) + : base(alias, textService) => Initialize(); /// @@ -36,10 +38,10 @@ protected ActionMenuItem(string alias, ILocalizedTextService textService) : base private void Initialize() { - //add the current type to the metadata + // add the current type to the metadata if (AngularServiceMethodName.IsNullOrWhiteSpace()) { - //if no method name is supplied we will assume that the menu action is the type name of the current menu class + // if no method name is supplied we will assume that the menu action is the type name of the current menu class ExecuteJsMethod($"{AngularServiceName}.{GetType().Name}"); } else diff --git a/src/Umbraco.Core/Models/Trees/CreateChildEntity.cs b/src/Umbraco.Core/Models/Trees/CreateChildEntity.cs index c858c16f8a74..41c8c6f0deeb 100644 --- a/src/Umbraco.Core/Models/Trees/CreateChildEntity.cs +++ b/src/Umbraco.Core/Models/Trees/CreateChildEntity.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Actions; +using Umbraco.Cms.Core.Actions; using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Core.Models.Trees; diff --git a/src/Umbraco.Core/Models/Trees/ExportMember.cs b/src/Umbraco.Core/Models/Trees/ExportMember.cs index 4c401782a553..3f11ef4b0548 100644 --- a/src/Umbraco.Core/Models/Trees/ExportMember.cs +++ b/src/Umbraco.Core/Models/Trees/ExportMember.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Core.Models.Trees; @@ -7,7 +7,8 @@ namespace Umbraco.Cms.Core.Models.Trees; /// public sealed class ExportMember : ActionMenuItem { - public ExportMember(ILocalizedTextService textService) : base("export", textService) => Icon = "download-alt"; + public ExportMember(ILocalizedTextService textService) + : base("export", textService) => Icon = "download-alt"; public override string AngularServiceName => "umbracoMenuActions"; } diff --git a/src/Umbraco.Core/Notifications/ApplicationCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/ApplicationCacheRefresherNotification.cs index 57487ff4d456..cd0b1326f463 100644 --- a/src/Umbraco.Core/Notifications/ApplicationCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/ApplicationCacheRefresherNotification.cs @@ -1,10 +1,12 @@ -using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Notifications; public class ApplicationCacheRefresherNotification : CacheRefresherNotification { - public ApplicationCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + public ApplicationCacheRefresherNotification(object messageObject, MessageType messageType) + : base( + messageObject, messageType) { } diff --git a/src/Umbraco.Core/Notifications/AssignedMemberRolesNotification.cs b/src/Umbraco.Core/Notifications/AssignedMemberRolesNotification.cs index 7a25295e9e83..23438827fdd6 100644 --- a/src/Umbraco.Core/Notifications/AssignedMemberRolesNotification.cs +++ b/src/Umbraco.Core/Notifications/AssignedMemberRolesNotification.cs @@ -2,7 +2,8 @@ namespace Umbraco.Cms.Core.Notifications; public class AssignedMemberRolesNotification : MemberRolesNotification { - public AssignedMemberRolesNotification(int[] memberIds, string[] roles) : base(memberIds, roles) + public AssignedMemberRolesNotification(int[] memberIds, string[] roles) + : base(memberIds, roles) { } } diff --git a/src/Umbraco.Core/Notifications/AssignedUserGroupPermissionsNotification.cs b/src/Umbraco.Core/Notifications/AssignedUserGroupPermissionsNotification.cs index ef0a28d3866e..347f1934bc3f 100644 --- a/src/Umbraco.Core/Notifications/AssignedUserGroupPermissionsNotification.cs +++ b/src/Umbraco.Core/Notifications/AssignedUserGroupPermissionsNotification.cs @@ -5,8 +5,8 @@ namespace Umbraco.Cms.Core.Notifications; public class AssignedUserGroupPermissionsNotification : EnumerableObjectNotification { - public AssignedUserGroupPermissionsNotification(IEnumerable target, EventMessages messages) : - base(target, messages) + public AssignedUserGroupPermissionsNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/CacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/CacheRefresherNotification.cs index e07e016888fd..637c05dfb099 100644 --- a/src/Umbraco.Core/Notifications/CacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/CacheRefresherNotification.cs @@ -14,5 +14,6 @@ public CacheRefresherNotification(object messageObject, MessageType messageType) } public object MessageObject { get; } + public MessageType MessageType { get; } } diff --git a/src/Umbraco.Core/Notifications/CancelableEnumerableObjectNotification.cs b/src/Umbraco.Core/Notifications/CancelableEnumerableObjectNotification.cs index 49e845f386bf..1f51e68409fe 100644 --- a/src/Umbraco.Core/Notifications/CancelableEnumerableObjectNotification.cs +++ b/src/Umbraco.Core/Notifications/CancelableEnumerableObjectNotification.cs @@ -7,11 +7,14 @@ namespace Umbraco.Cms.Core.Notifications; public abstract class CancelableEnumerableObjectNotification : CancelableObjectNotification> { - protected CancelableEnumerableObjectNotification(T target, EventMessages messages) : base(new[] {target}, messages) + protected CancelableEnumerableObjectNotification(T target, EventMessages messages) + : base(new[] { target }, messages) { } - protected CancelableEnumerableObjectNotification(IEnumerable target, EventMessages messages) : base(target, + protected CancelableEnumerableObjectNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/CancelableObjectNotification.cs b/src/Umbraco.Core/Notifications/CancelableObjectNotification.cs index d333f0cc3c77..be15626eb035 100644 --- a/src/Umbraco.Core/Notifications/CancelableObjectNotification.cs +++ b/src/Umbraco.Core/Notifications/CancelableObjectNotification.cs @@ -5,9 +5,11 @@ namespace Umbraco.Cms.Core.Notifications; -public abstract class CancelableObjectNotification : ObjectNotification, ICancelableNotification where T : class +public abstract class CancelableObjectNotification : ObjectNotification, ICancelableNotification + where T : class { - protected CancelableObjectNotification(T target, EventMessages messages) : base(target, messages) + protected CancelableObjectNotification(T target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/ContentCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/ContentCacheRefresherNotification.cs index 1f5eab40f4e7..67a43b5ac264 100644 --- a/src/Umbraco.Core/Notifications/ContentCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentCacheRefresherNotification.cs @@ -1,10 +1,12 @@ -using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Notifications; public class ContentCacheRefresherNotification : CacheRefresherNotification { - public ContentCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + public ContentCacheRefresherNotification(object messageObject, MessageType messageType) + : base( + messageObject, messageType) { } diff --git a/src/Umbraco.Core/Notifications/ContentDeletedBlueprintNotification.cs b/src/Umbraco.Core/Notifications/ContentDeletedBlueprintNotification.cs index 6f36b2c2a52c..884fcf493b79 100644 --- a/src/Umbraco.Core/Notifications/ContentDeletedBlueprintNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentDeletedBlueprintNotification.cs @@ -8,11 +8,14 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentDeletedBlueprintNotification : EnumerableObjectNotification { - public ContentDeletedBlueprintNotification(IContent target, EventMessages messages) : base(target, messages) + public ContentDeletedBlueprintNotification(IContent target, EventMessages messages) + : base(target, messages) { } - public ContentDeletedBlueprintNotification(IEnumerable target, EventMessages messages) : base(target, + public ContentDeletedBlueprintNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/ContentDeletedNotification.cs b/src/Umbraco.Core/Notifications/ContentDeletedNotification.cs index 1a517a5c778d..c68a07b1f02e 100644 --- a/src/Umbraco.Core/Notifications/ContentDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentDeletedNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentDeletedNotification : DeletedNotification { - public ContentDeletedNotification(IContent target, EventMessages messages) : base(target, messages) + public ContentDeletedNotification(IContent target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/ContentDeletedVersionsNotification.cs b/src/Umbraco.Core/Notifications/ContentDeletedVersionsNotification.cs index 80e94eb9338f..5e2b646008b7 100644 --- a/src/Umbraco.Core/Notifications/ContentDeletedVersionsNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentDeletedVersionsNotification.cs @@ -8,9 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentDeletedVersionsNotification : DeletedVersionsNotification { - public ContentDeletedVersionsNotification(int id, EventMessages messages, int specificVersion = default, - bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, - deletePriorVersions, dateToRetain) + public ContentDeletedVersionsNotification( + int id, + EventMessages messages, + int specificVersion = default, + bool deletePriorVersions = false, + DateTime dateToRetain = default) + : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) { } } diff --git a/src/Umbraco.Core/Notifications/ContentDeletingNotification.cs b/src/Umbraco.Core/Notifications/ContentDeletingNotification.cs index 7af4ac42eac6..de4176a01b5d 100644 --- a/src/Umbraco.Core/Notifications/ContentDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentDeletingNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentDeletingNotification : DeletingNotification { - public ContentDeletingNotification(IContent target, EventMessages messages) : base(target, messages) + public ContentDeletingNotification(IContent target, EventMessages messages) + : base(target, messages) { } - public ContentDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public ContentDeletingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/ContentDeletingVersionsNotification.cs b/src/Umbraco.Core/Notifications/ContentDeletingVersionsNotification.cs index f97d1dd82349..5d173bcc0cf1 100644 --- a/src/Umbraco.Core/Notifications/ContentDeletingVersionsNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentDeletingVersionsNotification.cs @@ -8,9 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentDeletingVersionsNotification : DeletingVersionsNotification { - public ContentDeletingVersionsNotification(int id, EventMessages messages, int specificVersion = default, - bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, - deletePriorVersions, dateToRetain) + public ContentDeletingVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) + : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) { } } diff --git a/src/Umbraco.Core/Notifications/ContentEmptiedRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/ContentEmptiedRecycleBinNotification.cs index 96b9e4f32ff1..9a1637dda95d 100644 --- a/src/Umbraco.Core/Notifications/ContentEmptiedRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentEmptiedRecycleBinNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentEmptiedRecycleBinNotification : EmptiedRecycleBinNotification { - public ContentEmptiedRecycleBinNotification(IEnumerable deletedEntities, EventMessages messages) : base( + public ContentEmptiedRecycleBinNotification(IEnumerable deletedEntities, EventMessages messages) + : base( deletedEntities, messages) { } diff --git a/src/Umbraco.Core/Notifications/ContentEmptyingRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/ContentEmptyingRecycleBinNotification.cs index 0aae4245c1c5..f55d1166ce5f 100644 --- a/src/Umbraco.Core/Notifications/ContentEmptyingRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentEmptyingRecycleBinNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentEmptyingRecycleBinNotification : EmptyingRecycleBinNotification { - public ContentEmptyingRecycleBinNotification(IEnumerable? deletedEntities, EventMessages messages) : base( + public ContentEmptyingRecycleBinNotification(IEnumerable? deletedEntities, EventMessages messages) + : base( deletedEntities, messages) { } diff --git a/src/Umbraco.Core/Notifications/ContentMovedNotification.cs b/src/Umbraco.Core/Notifications/ContentMovedNotification.cs index 067c022ad052..50bd24876d3d 100644 --- a/src/Umbraco.Core/Notifications/ContentMovedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentMovedNotification.cs @@ -8,11 +8,14 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentMovedNotification : MovedNotification { - public ContentMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + public ContentMovedNotification(MoveEventInfo target, EventMessages messages) + : base(target, messages) { } - public ContentMovedNotification(IEnumerable> target, EventMessages messages) : base(target, + public ContentMovedNotification(IEnumerable> target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/ContentMovedToRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/ContentMovedToRecycleBinNotification.cs index dba95a2044b3..bf5415d9d1ed 100644 --- a/src/Umbraco.Core/Notifications/ContentMovedToRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentMovedToRecycleBinNotification.cs @@ -8,13 +8,15 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentMovedToRecycleBinNotification : MovedToRecycleBinNotification { - public ContentMovedToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(target, + public ContentMovedToRecycleBinNotification(MoveEventInfo target, EventMessages messages) + : base( + target, messages) { } - public ContentMovedToRecycleBinNotification(IEnumerable> target, EventMessages messages) : - base(target, messages) + public ContentMovedToRecycleBinNotification(IEnumerable> target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/ContentMovingNotification.cs b/src/Umbraco.Core/Notifications/ContentMovingNotification.cs index 533a6eee4141..eddc7a13f786 100644 --- a/src/Umbraco.Core/Notifications/ContentMovingNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentMovingNotification.cs @@ -8,11 +8,14 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentMovingNotification : MovingNotification { - public ContentMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + public ContentMovingNotification(MoveEventInfo target, EventMessages messages) + : base(target, messages) { } - public ContentMovingNotification(IEnumerable> target, EventMessages messages) : base(target, + public ContentMovingNotification(IEnumerable> target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/ContentMovingToRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/ContentMovingToRecycleBinNotification.cs index c43c02f103bc..5a691c648716 100644 --- a/src/Umbraco.Core/Notifications/ContentMovingToRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentMovingToRecycleBinNotification.cs @@ -8,13 +8,15 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentMovingToRecycleBinNotification : MovingToRecycleBinNotification { - public ContentMovingToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(target, + public ContentMovingToRecycleBinNotification(MoveEventInfo target, EventMessages messages) + : base( + target, messages) { } - public ContentMovingToRecycleBinNotification(IEnumerable> target, EventMessages messages) : - base(target, messages) + public ContentMovingToRecycleBinNotification(IEnumerable> target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/ContentNotificationExtensions.cs b/src/Umbraco.Core/Notifications/ContentNotificationExtensions.cs index 490aab65e4c7..a7449f24cc0b 100644 --- a/src/Umbraco.Core/Notifications/ContentNotificationExtensions.cs +++ b/src/Umbraco.Core/Notifications/ContentNotificationExtensions.cs @@ -25,52 +25,45 @@ public static bool HasSavedCulture(this SavedNotification notification, T /// /// Determines whether a culture is being published, during a Publishing notification /// - public static bool IsPublishingCulture(this ContentPublishingNotification notification, IContent content, - string culture) + public static bool IsPublishingCulture(this ContentPublishingNotification notification, IContent content, string culture) => IsPublishingCulture(content, culture); /// /// Determines whether a culture is being unpublished, during an Publishing notification /// - public static bool IsUnpublishingCulture(this ContentPublishingNotification notification, IContent content, - string culture) + public static bool IsUnpublishingCulture(this ContentPublishingNotification notification, IContent content, string culture) => IsUnpublishingCulture(content, culture); /// /// Determines whether a culture is being unpublished, during a Unpublishing notification /// - public static bool IsUnpublishingCulture(this ContentUnpublishingNotification notification, IContent content, - string culture) - => IsUnpublishingCulture(content, culture); + public static bool IsUnpublishingCulture(this ContentUnpublishingNotification notification, IContent content, string culture) => IsUnpublishingCulture(content, culture); /// /// Determines whether a culture has been published, during a Published notification /// - public static bool HasPublishedCulture(this ContentPublishedNotification notification, IContent content, - string culture) + public static bool HasPublishedCulture(this ContentPublishedNotification notification, IContent content, string culture) => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.ChangedCulture + culture); /// /// Determines whether a culture has been unpublished, during a Published notification /// - public static bool HasUnpublishedCulture(this ContentPublishedNotification notification, IContent content, - string culture) + public static bool HasUnpublishedCulture(this ContentPublishedNotification notification, IContent content, string culture) => HasUnpublishedCulture(content, culture); /// /// Determines whether a culture has been unpublished, during an Unpublished notification /// - public static bool HasUnpublishedCulture(this ContentUnpublishedNotification notification, IContent content, - string culture) + public static bool HasUnpublishedCulture(this ContentUnpublishedNotification notification, IContent content, string culture) => HasUnpublishedCulture(content, culture); - private static bool IsUnpublishingCulture(IContent content, string culture) - => content.IsPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + culture); - public static bool IsPublishingCulture(IContent content, string culture) => (content.PublishCultureInfos?.TryGetValue(culture, out ContentCultureInfos cultureInfo) ?? false) && cultureInfo.IsDirty(); + private static bool IsUnpublishingCulture(IContent content, string culture) + => content.IsPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + culture); + public static bool HasUnpublishedCulture(IContent content, string culture) => content.WasPropertyDirty(ContentBase.ChangeTrackingPrefix.UnpublishedCulture + culture); } diff --git a/src/Umbraco.Core/Notifications/ContentPublishedNotification.cs b/src/Umbraco.Core/Notifications/ContentPublishedNotification.cs index 4b256170fb45..0400155d3cb1 100644 --- a/src/Umbraco.Core/Notifications/ContentPublishedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentPublishedNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentPublishedNotification : EnumerableObjectNotification { - public ContentPublishedNotification(IContent target, EventMessages messages) : base(target, messages) + public ContentPublishedNotification(IContent target, EventMessages messages) + : base(target, messages) { } - public ContentPublishedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public ContentPublishedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/ContentPublishingNotification.cs b/src/Umbraco.Core/Notifications/ContentPublishingNotification.cs index 8b1abcef3b4b..c9a11100899c 100644 --- a/src/Umbraco.Core/Notifications/ContentPublishingNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentPublishingNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentPublishingNotification : CancelableEnumerableObjectNotification { - public ContentPublishingNotification(IContent target, EventMessages messages) : base(target, messages) + public ContentPublishingNotification(IContent target, EventMessages messages) + : base(target, messages) { } - public ContentPublishingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public ContentPublishingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/ContentRefreshNotification.cs b/src/Umbraco.Core/Notifications/ContentRefreshNotification.cs index 42f85d6da330..f2d18fbba15e 100644 --- a/src/Umbraco.Core/Notifications/ContentRefreshNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentRefreshNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; [EditorBrowsable(EditorBrowsableState.Never)] public class ContentRefreshNotification : EntityRefreshNotification { - public ContentRefreshNotification(IContent target, EventMessages messages) : base(target, messages) + public ContentRefreshNotification(IContent target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/ContentRolledBackNotification.cs b/src/Umbraco.Core/Notifications/ContentRolledBackNotification.cs index 08bce7a92cc8..50b89e10b80e 100644 --- a/src/Umbraco.Core/Notifications/ContentRolledBackNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentRolledBackNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentRolledBackNotification : RolledBackNotification { - public ContentRolledBackNotification(IContent target, EventMessages messages) : base(target, messages) + public ContentRolledBackNotification(IContent target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/ContentRollingBackNotification.cs b/src/Umbraco.Core/Notifications/ContentRollingBackNotification.cs index e459d88bd2f7..29b864853cc2 100644 --- a/src/Umbraco.Core/Notifications/ContentRollingBackNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentRollingBackNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentRollingBackNotification : RollingBackNotification { - public ContentRollingBackNotification(IContent target, EventMessages messages) : base(target, messages) + public ContentRollingBackNotification(IContent target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/ContentSavedBlueprintNotification.cs b/src/Umbraco.Core/Notifications/ContentSavedBlueprintNotification.cs index 85785f4e8220..d06f364ed2dd 100644 --- a/src/Umbraco.Core/Notifications/ContentSavedBlueprintNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentSavedBlueprintNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentSavedBlueprintNotification : ObjectNotification { - public ContentSavedBlueprintNotification(IContent target, EventMessages messages) : base(target, messages) + public ContentSavedBlueprintNotification(IContent target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/ContentSavedNotification.cs b/src/Umbraco.Core/Notifications/ContentSavedNotification.cs index 2bd476bd8af8..2d3253117d46 100644 --- a/src/Umbraco.Core/Notifications/ContentSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentSavedNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentSavedNotification : SavedNotification { - public ContentSavedNotification(IContent target, EventMessages messages) : base(target, messages) + public ContentSavedNotification(IContent target, EventMessages messages) + : base(target, messages) { } - public ContentSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public ContentSavedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/ContentSavingNotification.cs b/src/Umbraco.Core/Notifications/ContentSavingNotification.cs index 92a6d4384fb1..4a57a10f29f1 100644 --- a/src/Umbraco.Core/Notifications/ContentSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentSavingNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentSavingNotification : SavingNotification { - public ContentSavingNotification(IContent target, EventMessages messages) : base(target, messages) + public ContentSavingNotification(IContent target, EventMessages messages) + : base(target, messages) { } - public ContentSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public ContentSavingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/ContentSendingToPublishNotification.cs b/src/Umbraco.Core/Notifications/ContentSendingToPublishNotification.cs index 3e2364501700..7d5ee26130c2 100644 --- a/src/Umbraco.Core/Notifications/ContentSendingToPublishNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentSendingToPublishNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentSendingToPublishNotification : CancelableObjectNotification { - public ContentSendingToPublishNotification(IContent target, EventMessages messages) : base(target, messages) + public ContentSendingToPublishNotification(IContent target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/ContentSentToPublishNotification.cs b/src/Umbraco.Core/Notifications/ContentSentToPublishNotification.cs index 9ebbb926e047..e10b9930e3f5 100644 --- a/src/Umbraco.Core/Notifications/ContentSentToPublishNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentSentToPublishNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentSentToPublishNotification : ObjectNotification { - public ContentSentToPublishNotification(IContent target, EventMessages messages) : base(target, messages) + public ContentSentToPublishNotification(IContent target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/ContentSortedNotification.cs b/src/Umbraco.Core/Notifications/ContentSortedNotification.cs index e0e46dbcfc58..8f0d6304ffc2 100644 --- a/src/Umbraco.Core/Notifications/ContentSortedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentSortedNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentSortedNotification : SortedNotification { - public ContentSortedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public ContentSortedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/ContentSortingNotification.cs b/src/Umbraco.Core/Notifications/ContentSortingNotification.cs index 0077e5945f34..bc3e94a4643f 100644 --- a/src/Umbraco.Core/Notifications/ContentSortingNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentSortingNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentSortingNotification : SortingNotification { - public ContentSortingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public ContentSortingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/ContentTreeChangeNotification.cs b/src/Umbraco.Core/Notifications/ContentTreeChangeNotification.cs index 0dce6b079795..df5aab16c733 100644 --- a/src/Umbraco.Core/Notifications/ContentTreeChangeNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTreeChangeNotification.cs @@ -6,24 +6,30 @@ namespace Umbraco.Cms.Core.Notifications; public class ContentTreeChangeNotification : TreeChangeNotification { - public ContentTreeChangeNotification(TreeChange target, EventMessages messages) : base(target, messages) + public ContentTreeChangeNotification(TreeChange target, EventMessages messages) + : base(target, messages) { } - public ContentTreeChangeNotification(IEnumerable> target, EventMessages messages) : base( + public ContentTreeChangeNotification(IEnumerable> target, EventMessages messages) + : base( target, messages) { } - public ContentTreeChangeNotification(IEnumerable target, + public ContentTreeChangeNotification( + IEnumerable target, TreeChangeTypes changeTypes, - EventMessages messages) : base(target.Select(x => new TreeChange(x, changeTypes)), messages) + EventMessages messages) + : base(target.Select(x => new TreeChange(x, changeTypes)), messages) { } - public ContentTreeChangeNotification(IContent target, + public ContentTreeChangeNotification( + IContent target, TreeChangeTypes changeTypes, - EventMessages messages) : base(new TreeChange(target, changeTypes), messages) + EventMessages messages) + : base(new TreeChange(target, changeTypes), messages) { } } diff --git a/src/Umbraco.Core/Notifications/ContentTypeCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeCacheRefresherNotification.cs index ae5d904a153e..d4ced3496d58 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeCacheRefresherNotification.cs @@ -1,10 +1,12 @@ -using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Notifications; public class ContentTypeCacheRefresherNotification : CacheRefresherNotification { - public ContentTypeCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + public ContentTypeCacheRefresherNotification(object messageObject, MessageType messageType) + : base( + messageObject, messageType) { } diff --git a/src/Umbraco.Core/Notifications/ContentTypeChangeNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeChangeNotification.cs index 28dd96e6fec7..606a6fb34e32 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeChangeNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeChangeNotification.cs @@ -7,12 +7,15 @@ namespace Umbraco.Cms.Core.Notifications; public abstract class ContentTypeChangeNotification : EnumerableObjectNotification> where T : class, IContentTypeComposition { - protected ContentTypeChangeNotification(ContentTypeChange target, EventMessages messages) : base(target, + protected ContentTypeChangeNotification(ContentTypeChange target, EventMessages messages) + : base( + target, messages) { } - protected ContentTypeChangeNotification(IEnumerable> target, EventMessages messages) : base( + protected ContentTypeChangeNotification(IEnumerable> target, EventMessages messages) + : base( target, messages) { } diff --git a/src/Umbraco.Core/Notifications/ContentTypeChangedNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeChangedNotification.cs index 52ac4f087ce5..0456ebc9cf97 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeChangedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeChangedNotification.cs @@ -6,13 +6,15 @@ namespace Umbraco.Cms.Core.Notifications; public class ContentTypeChangedNotification : ContentTypeChangeNotification { - public ContentTypeChangedNotification(ContentTypeChange target, EventMessages messages) : base(target, + public ContentTypeChangedNotification(ContentTypeChange target, EventMessages messages) + : base( + target, messages) { } - public ContentTypeChangedNotification(IEnumerable> target, EventMessages messages) : - base(target, messages) + public ContentTypeChangedNotification(IEnumerable> target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/ContentTypeDeletedNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeDeletedNotification.cs index 32f2a6f41ef2..92092d1a57ab 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeDeletedNotification.cs @@ -5,11 +5,14 @@ namespace Umbraco.Cms.Core.Notifications; public class ContentTypeDeletedNotification : DeletedNotification { - public ContentTypeDeletedNotification(IContentType target, EventMessages messages) : base(target, messages) + public ContentTypeDeletedNotification(IContentType target, EventMessages messages) + : base(target, messages) { } - public ContentTypeDeletedNotification(IEnumerable target, EventMessages messages) : base(target, + public ContentTypeDeletedNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/ContentTypeDeletingNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeDeletingNotification.cs index 27ed7a92b343..0313ffcc17d3 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeDeletingNotification.cs @@ -5,11 +5,14 @@ namespace Umbraco.Cms.Core.Notifications; public class ContentTypeDeletingNotification : DeletingNotification { - public ContentTypeDeletingNotification(IContentType target, EventMessages messages) : base(target, messages) + public ContentTypeDeletingNotification(IContentType target, EventMessages messages) + : base(target, messages) { } - public ContentTypeDeletingNotification(IEnumerable target, EventMessages messages) : base(target, + public ContentTypeDeletingNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/ContentTypeMovedNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeMovedNotification.cs index f3d57321ce17..4fab7a67ac76 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeMovedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeMovedNotification.cs @@ -5,12 +5,15 @@ namespace Umbraco.Cms.Core.Notifications; public class ContentTypeMovedNotification : MovedNotification { - public ContentTypeMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, + public ContentTypeMovedNotification(MoveEventInfo target, EventMessages messages) + : base( + target, messages) { } - public ContentTypeMovedNotification(IEnumerable> target, EventMessages messages) : base( + public ContentTypeMovedNotification(IEnumerable> target, EventMessages messages) + : base( target, messages) { } diff --git a/src/Umbraco.Core/Notifications/ContentTypeMovingNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeMovingNotification.cs index 2a55e619a202..210dcf43f29e 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeMovingNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeMovingNotification.cs @@ -5,13 +5,15 @@ namespace Umbraco.Cms.Core.Notifications; public class ContentTypeMovingNotification : MovingNotification { - public ContentTypeMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, + public ContentTypeMovingNotification(MoveEventInfo target, EventMessages messages) + : base( + target, messages) { } - public ContentTypeMovingNotification(IEnumerable> target, EventMessages messages) : - base(target, messages) + public ContentTypeMovingNotification(IEnumerable> target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/ContentTypeRefreshNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeRefreshNotification.cs index b522be928076..108e72aecc00 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeRefreshNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeRefreshNotification.cs @@ -7,12 +7,15 @@ namespace Umbraco.Cms.Core.Notifications; public abstract class ContentTypeRefreshNotification : ContentTypeChangeNotification where T : class, IContentTypeComposition { - protected ContentTypeRefreshNotification(ContentTypeChange target, EventMessages messages) : base(target, + protected ContentTypeRefreshNotification(ContentTypeChange target, EventMessages messages) + : base( + target, messages) { } - protected ContentTypeRefreshNotification(IEnumerable> target, EventMessages messages) : base( + protected ContentTypeRefreshNotification(IEnumerable> target, EventMessages messages) + : base( target, messages) { } diff --git a/src/Umbraco.Core/Notifications/ContentTypeRefreshedNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeRefreshedNotification.cs index 629f8843dedd..d30cab14ca91 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeRefreshedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeRefreshedNotification.cs @@ -9,7 +9,8 @@ namespace Umbraco.Cms.Core.Notifications; [EditorBrowsable(EditorBrowsableState.Never)] public class ContentTypeRefreshedNotification : ContentTypeRefreshNotification { - public ContentTypeRefreshedNotification(ContentTypeChange target, EventMessages messages) : base( + public ContentTypeRefreshedNotification(ContentTypeChange target, EventMessages messages) + : base( target, messages) { } diff --git a/src/Umbraco.Core/Notifications/ContentTypeSavedNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeSavedNotification.cs index 3d5c794a533e..0cbe82dadedd 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeSavedNotification.cs @@ -5,11 +5,14 @@ namespace Umbraco.Cms.Core.Notifications; public class ContentTypeSavedNotification : SavedNotification { - public ContentTypeSavedNotification(IContentType target, EventMessages messages) : base(target, messages) + public ContentTypeSavedNotification(IContentType target, EventMessages messages) + : base(target, messages) { } - public ContentTypeSavedNotification(IEnumerable target, EventMessages messages) : base(target, + public ContentTypeSavedNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/ContentTypeSavingNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeSavingNotification.cs index fc58c30fe2fa..b94a2770cfbb 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeSavingNotification.cs @@ -5,11 +5,14 @@ namespace Umbraco.Cms.Core.Notifications; public class ContentTypeSavingNotification : SavingNotification { - public ContentTypeSavingNotification(IContentType target, EventMessages messages) : base(target, messages) + public ContentTypeSavingNotification(IContentType target, EventMessages messages) + : base(target, messages) { } - public ContentTypeSavingNotification(IEnumerable target, EventMessages messages) : base(target, + public ContentTypeSavingNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/ContentUnpublishedNotification.cs b/src/Umbraco.Core/Notifications/ContentUnpublishedNotification.cs index 548a3f108226..2677ef5a08f5 100644 --- a/src/Umbraco.Core/Notifications/ContentUnpublishedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentUnpublishedNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentUnpublishedNotification : EnumerableObjectNotification { - public ContentUnpublishedNotification(IContent target, EventMessages messages) : base(target, messages) + public ContentUnpublishedNotification(IContent target, EventMessages messages) + : base(target, messages) { } - public ContentUnpublishedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public ContentUnpublishedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/ContentUnpublishingNotification.cs b/src/Umbraco.Core/Notifications/ContentUnpublishingNotification.cs index b05a32b7b19c..aa26e61e41d9 100644 --- a/src/Umbraco.Core/Notifications/ContentUnpublishingNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentUnpublishingNotification.cs @@ -8,11 +8,14 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentUnpublishingNotification : CancelableEnumerableObjectNotification { - public ContentUnpublishingNotification(IContent target, EventMessages messages) : base(target, messages) + public ContentUnpublishingNotification(IContent target, EventMessages messages) + : base(target, messages) { } - public ContentUnpublishingNotification(IEnumerable target, EventMessages messages) : base(target, + public ContentUnpublishingNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/CopiedNotification.cs b/src/Umbraco.Core/Notifications/CopiedNotification.cs index 5142c700fb8a..13b9cf25badf 100644 --- a/src/Umbraco.Core/Notifications/CopiedNotification.cs +++ b/src/Umbraco.Core/Notifications/CopiedNotification.cs @@ -5,10 +5,11 @@ namespace Umbraco.Cms.Core.Notifications; -public abstract class CopiedNotification : ObjectNotification where T : class +public abstract class CopiedNotification : ObjectNotification + where T : class { - protected CopiedNotification(T original, T copy, int parentId, bool relateToOriginal, EventMessages messages) : - base(original, messages) + protected CopiedNotification(T original, T copy, int parentId, bool relateToOriginal, EventMessages messages) + : base(original, messages) { Copy = copy; ParentId = parentId; @@ -20,5 +21,6 @@ protected CopiedNotification(T original, T copy, int parentId, bool relateToOrig public T Copy { get; } public int ParentId { get; } + public bool RelateToOriginal { get; } } diff --git a/src/Umbraco.Core/Notifications/CopyingNotification.cs b/src/Umbraco.Core/Notifications/CopyingNotification.cs index 9aa2ac237410..0992f9708b07 100644 --- a/src/Umbraco.Core/Notifications/CopyingNotification.cs +++ b/src/Umbraco.Core/Notifications/CopyingNotification.cs @@ -5,9 +5,11 @@ namespace Umbraco.Cms.Core.Notifications; -public abstract class CopyingNotification : CancelableObjectNotification where T : class +public abstract class CopyingNotification : CancelableObjectNotification + where T : class { - protected CopyingNotification(T original, T copy, int parentId, EventMessages messages) : base(original, messages) + protected CopyingNotification(T original, T copy, int parentId, EventMessages messages) + : base(original, messages) { Copy = copy; ParentId = parentId; diff --git a/src/Umbraco.Core/Notifications/CreatedNotification.cs b/src/Umbraco.Core/Notifications/CreatedNotification.cs index 0c5bf0bd601c..8667e4bdcc68 100644 --- a/src/Umbraco.Core/Notifications/CreatedNotification.cs +++ b/src/Umbraco.Core/Notifications/CreatedNotification.cs @@ -5,9 +5,11 @@ namespace Umbraco.Cms.Core.Notifications; -public abstract class CreatedNotification : ObjectNotification where T : class +public abstract class CreatedNotification : ObjectNotification + where T : class { - protected CreatedNotification(T target, EventMessages messages) : base(target, messages) + protected CreatedNotification(T target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/CreatingNotification.cs b/src/Umbraco.Core/Notifications/CreatingNotification.cs index c9c2b28f2c08..f76a3d883990 100644 --- a/src/Umbraco.Core/Notifications/CreatingNotification.cs +++ b/src/Umbraco.Core/Notifications/CreatingNotification.cs @@ -5,9 +5,11 @@ namespace Umbraco.Cms.Core.Notifications; -public abstract class CreatingNotification : CancelableObjectNotification where T : class +public abstract class CreatingNotification : CancelableObjectNotification + where T : class { - protected CreatingNotification(T target, EventMessages messages) : base(target, messages) + protected CreatingNotification(T target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/DataTypeCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/DataTypeCacheRefresherNotification.cs index 34743bf0c18b..ed3bd26d1477 100644 --- a/src/Umbraco.Core/Notifications/DataTypeCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/DataTypeCacheRefresherNotification.cs @@ -1,10 +1,12 @@ -using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Notifications; public class DataTypeCacheRefresherNotification : CacheRefresherNotification { - public DataTypeCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + public DataTypeCacheRefresherNotification(object messageObject, MessageType messageType) + : base( + messageObject, messageType) { } diff --git a/src/Umbraco.Core/Notifications/DataTypeDeletedNotification.cs b/src/Umbraco.Core/Notifications/DataTypeDeletedNotification.cs index 87039bf32928..839fa002302b 100644 --- a/src/Umbraco.Core/Notifications/DataTypeDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/DataTypeDeletedNotification.cs @@ -5,7 +5,8 @@ namespace Umbraco.Cms.Core.Notifications; public class DataTypeDeletedNotification : DeletedNotification { - public DataTypeDeletedNotification(IDataType target, EventMessages messages) : base(target, messages) + public DataTypeDeletedNotification(IDataType target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/DataTypeDeletingNotification.cs b/src/Umbraco.Core/Notifications/DataTypeDeletingNotification.cs index ffaa6a1c9392..70035a52376f 100644 --- a/src/Umbraco.Core/Notifications/DataTypeDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/DataTypeDeletingNotification.cs @@ -5,7 +5,8 @@ namespace Umbraco.Cms.Core.Notifications; public class DataTypeDeletingNotification : DeletingNotification { - public DataTypeDeletingNotification(IDataType target, EventMessages messages) : base(target, messages) + public DataTypeDeletingNotification(IDataType target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/DataTypeMovedNotification.cs b/src/Umbraco.Core/Notifications/DataTypeMovedNotification.cs index d37a19261a60..27065b86191f 100644 --- a/src/Umbraco.Core/Notifications/DataTypeMovedNotification.cs +++ b/src/Umbraco.Core/Notifications/DataTypeMovedNotification.cs @@ -5,7 +5,8 @@ namespace Umbraco.Cms.Core.Notifications; public class DataTypeMovedNotification : MovedNotification { - public DataTypeMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + public DataTypeMovedNotification(MoveEventInfo target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/DataTypeMovingNotification.cs b/src/Umbraco.Core/Notifications/DataTypeMovingNotification.cs index 58ee94ee8b74..1a54f14622c7 100644 --- a/src/Umbraco.Core/Notifications/DataTypeMovingNotification.cs +++ b/src/Umbraco.Core/Notifications/DataTypeMovingNotification.cs @@ -5,7 +5,8 @@ namespace Umbraco.Cms.Core.Notifications; public class DataTypeMovingNotification : MovingNotification { - public DataTypeMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + public DataTypeMovingNotification(MoveEventInfo target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/DataTypeSavedNotification.cs b/src/Umbraco.Core/Notifications/DataTypeSavedNotification.cs index df8ea82c7b5c..ca23336ce1b4 100644 --- a/src/Umbraco.Core/Notifications/DataTypeSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/DataTypeSavedNotification.cs @@ -5,11 +5,13 @@ namespace Umbraco.Cms.Core.Notifications; public class DataTypeSavedNotification : SavedNotification { - public DataTypeSavedNotification(IDataType target, EventMessages messages) : base(target, messages) + public DataTypeSavedNotification(IDataType target, EventMessages messages) + : base(target, messages) { } - public DataTypeSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public DataTypeSavedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/DataTypeSavingNotification.cs b/src/Umbraco.Core/Notifications/DataTypeSavingNotification.cs index b0ac80601db8..8099431da6de 100644 --- a/src/Umbraco.Core/Notifications/DataTypeSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/DataTypeSavingNotification.cs @@ -5,11 +5,13 @@ namespace Umbraco.Cms.Core.Notifications; public class DataTypeSavingNotification : SavingNotification { - public DataTypeSavingNotification(IDataType target, EventMessages messages) : base(target, messages) + public DataTypeSavingNotification(IDataType target, EventMessages messages) + : base(target, messages) { } - public DataTypeSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public DataTypeSavingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/DeletedNotification.cs b/src/Umbraco.Core/Notifications/DeletedNotification.cs index b6313e3a38bc..69af0581af0b 100644 --- a/src/Umbraco.Core/Notifications/DeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/DeletedNotification.cs @@ -7,11 +7,13 @@ namespace Umbraco.Cms.Core.Notifications; public abstract class DeletedNotification : EnumerableObjectNotification { - protected DeletedNotification(T target, EventMessages messages) : base(target, messages) + protected DeletedNotification(T target, EventMessages messages) + : base(target, messages) { } - protected DeletedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + protected DeletedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/DeletedVersionsNotification.cs b/src/Umbraco.Core/Notifications/DeletedVersionsNotification.cs index d84008aaf130..b709799afdb1 100644 --- a/src/Umbraco.Core/Notifications/DeletedVersionsNotification.cs +++ b/src/Umbraco.Core/Notifications/DeletedVersionsNotification.cs @@ -5,7 +5,8 @@ namespace Umbraco.Cms.Core.Notifications; -public abstract class DeletedVersionsNotification : DeletedVersionsNotificationBase where T : class +public abstract class DeletedVersionsNotification : DeletedVersionsNotificationBase + where T : class { protected DeletedVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) diff --git a/src/Umbraco.Core/Notifications/DeletedVersionsNotificationBase.cs b/src/Umbraco.Core/Notifications/DeletedVersionsNotificationBase.cs index 2e399e5720fa..a68593de8053 100644 --- a/src/Umbraco.Core/Notifications/DeletedVersionsNotificationBase.cs +++ b/src/Umbraco.Core/Notifications/DeletedVersionsNotificationBase.cs @@ -5,10 +5,15 @@ namespace Umbraco.Cms.Core.Notifications; -public abstract class DeletedVersionsNotificationBase : StatefulNotification where T : class +public abstract class DeletedVersionsNotificationBase : StatefulNotification + where T : class { - protected DeletedVersionsNotificationBase(int id, EventMessages messages, int specificVersion = default, - bool deletePriorVersions = false, DateTime dateToRetain = default) + protected DeletedVersionsNotificationBase( + int id, + EventMessages messages, + int specificVersion = default, + bool deletePriorVersions = false, + DateTime dateToRetain = default) { Id = id; Messages = messages; diff --git a/src/Umbraco.Core/Notifications/DeletingNotification.cs b/src/Umbraco.Core/Notifications/DeletingNotification.cs index 56bcb8d66d4e..ab630468dd7d 100644 --- a/src/Umbraco.Core/Notifications/DeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/DeletingNotification.cs @@ -7,11 +7,13 @@ namespace Umbraco.Cms.Core.Notifications; public abstract class DeletingNotification : CancelableEnumerableObjectNotification { - protected DeletingNotification(T target, EventMessages messages) : base(target, messages) + protected DeletingNotification(T target, EventMessages messages) + : base(target, messages) { } - protected DeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + protected DeletingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/DictionaryCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/DictionaryCacheRefresherNotification.cs index b9fed23cb981..11b23cc5fdbe 100644 --- a/src/Umbraco.Core/Notifications/DictionaryCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryCacheRefresherNotification.cs @@ -1,10 +1,12 @@ -using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Notifications; public class DictionaryCacheRefresherNotification : CacheRefresherNotification { - public DictionaryCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + public DictionaryCacheRefresherNotification(object messageObject, MessageType messageType) + : base( + messageObject, messageType) { } diff --git a/src/Umbraco.Core/Notifications/DictionaryItemDeletedNotification.cs b/src/Umbraco.Core/Notifications/DictionaryItemDeletedNotification.cs index db1d8dbe29f4..c62f6d3f7d82 100644 --- a/src/Umbraco.Core/Notifications/DictionaryItemDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryItemDeletedNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public class DictionaryItemDeletedNotification : DeletedNotification { - public DictionaryItemDeletedNotification(IDictionaryItem target, EventMessages messages) : base(target, messages) + public DictionaryItemDeletedNotification(IDictionaryItem target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/DictionaryItemDeletingNotification.cs b/src/Umbraco.Core/Notifications/DictionaryItemDeletingNotification.cs index bd6d6ccd1a83..4f66c33b455f 100644 --- a/src/Umbraco.Core/Notifications/DictionaryItemDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryItemDeletingNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public class DictionaryItemDeletingNotification : DeletingNotification { - public DictionaryItemDeletingNotification(IDictionaryItem target, EventMessages messages) : base(target, messages) + public DictionaryItemDeletingNotification(IDictionaryItem target, EventMessages messages) + : base(target, messages) { } - public DictionaryItemDeletingNotification(IEnumerable target, EventMessages messages) : base( + public DictionaryItemDeletingNotification(IEnumerable target, EventMessages messages) + : base( target, messages) { } diff --git a/src/Umbraco.Core/Notifications/DictionaryItemSavedNotification.cs b/src/Umbraco.Core/Notifications/DictionaryItemSavedNotification.cs index e3b935955dac..e8423507aa82 100644 --- a/src/Umbraco.Core/Notifications/DictionaryItemSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryItemSavedNotification.cs @@ -8,11 +8,14 @@ namespace Umbraco.Cms.Core.Notifications; public class DictionaryItemSavedNotification : SavedNotification { - public DictionaryItemSavedNotification(IDictionaryItem target, EventMessages messages) : base(target, messages) + public DictionaryItemSavedNotification(IDictionaryItem target, EventMessages messages) + : base(target, messages) { } - public DictionaryItemSavedNotification(IEnumerable target, EventMessages messages) : base(target, + public DictionaryItemSavedNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/DictionaryItemSavingNotification.cs b/src/Umbraco.Core/Notifications/DictionaryItemSavingNotification.cs index e45627afc07b..b2e6646de545 100644 --- a/src/Umbraco.Core/Notifications/DictionaryItemSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryItemSavingNotification.cs @@ -8,11 +8,14 @@ namespace Umbraco.Cms.Core.Notifications; public class DictionaryItemSavingNotification : SavingNotification { - public DictionaryItemSavingNotification(IDictionaryItem target, EventMessages messages) : base(target, messages) + public DictionaryItemSavingNotification(IDictionaryItem target, EventMessages messages) + : base(target, messages) { } - public DictionaryItemSavingNotification(IEnumerable target, EventMessages messages) : base(target, + public DictionaryItemSavingNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/DomainCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/DomainCacheRefresherNotification.cs index 58f262dd5211..5a9ecb6a184f 100644 --- a/src/Umbraco.Core/Notifications/DomainCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/DomainCacheRefresherNotification.cs @@ -1,10 +1,12 @@ -using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Notifications; public class DomainCacheRefresherNotification : CacheRefresherNotification { - public DomainCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + public DomainCacheRefresherNotification(object messageObject, MessageType messageType) + : base( + messageObject, messageType) { } diff --git a/src/Umbraco.Core/Notifications/DomainDeletedNotification.cs b/src/Umbraco.Core/Notifications/DomainDeletedNotification.cs index 7bfd897ead79..c569afc7b4b9 100644 --- a/src/Umbraco.Core/Notifications/DomainDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/DomainDeletedNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public class DomainDeletedNotification : DeletedNotification { - public DomainDeletedNotification(IDomain target, EventMessages messages) : base(target, messages) + public DomainDeletedNotification(IDomain target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/DomainDeletingNotification.cs b/src/Umbraco.Core/Notifications/DomainDeletingNotification.cs index c69d88d90d0d..afeb3fa67c99 100644 --- a/src/Umbraco.Core/Notifications/DomainDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/DomainDeletingNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public class DomainDeletingNotification : DeletingNotification { - public DomainDeletingNotification(IDomain target, EventMessages messages) : base(target, messages) + public DomainDeletingNotification(IDomain target, EventMessages messages) + : base(target, messages) { } - public DomainDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public DomainDeletingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/DomainSavedNotification.cs b/src/Umbraco.Core/Notifications/DomainSavedNotification.cs index edd36338358c..75c93e15b702 100644 --- a/src/Umbraco.Core/Notifications/DomainSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/DomainSavedNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public class DomainSavedNotification : SavedNotification { - public DomainSavedNotification(IDomain target, EventMessages messages) : base(target, messages) + public DomainSavedNotification(IDomain target, EventMessages messages) + : base(target, messages) { } - public DomainSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public DomainSavedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/DomainSavingNotification.cs b/src/Umbraco.Core/Notifications/DomainSavingNotification.cs index 33072d1ff504..673ed92c7205 100644 --- a/src/Umbraco.Core/Notifications/DomainSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/DomainSavingNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public class DomainSavingNotification : SavingNotification { - public DomainSavingNotification(IDomain target, EventMessages messages) : base(target, messages) + public DomainSavingNotification(IDomain target, EventMessages messages) + : base(target, messages) { } - public DomainSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public DomainSavingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/EmptiedRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/EmptiedRecycleBinNotification.cs index fb5e55cb5629..8e648ac14d94 100644 --- a/src/Umbraco.Core/Notifications/EmptiedRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/EmptiedRecycleBinNotification.cs @@ -5,7 +5,8 @@ namespace Umbraco.Cms.Core.Notifications; -public abstract class EmptiedRecycleBinNotification : StatefulNotification where T : class +public abstract class EmptiedRecycleBinNotification : StatefulNotification + where T : class { protected EmptiedRecycleBinNotification(IEnumerable deletedEntities, EventMessages messages) { diff --git a/src/Umbraco.Core/Notifications/EmptyingRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/EmptyingRecycleBinNotification.cs index 8e52204cee00..570181941506 100644 --- a/src/Umbraco.Core/Notifications/EmptyingRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/EmptyingRecycleBinNotification.cs @@ -5,7 +5,8 @@ namespace Umbraco.Cms.Core.Notifications; -public abstract class EmptyingRecycleBinNotification : StatefulNotification, ICancelableNotification where T : class +public abstract class EmptyingRecycleBinNotification : StatefulNotification, ICancelableNotification + where T : class { protected EmptyingRecycleBinNotification(IEnumerable? deletedEntities, EventMessages messages) { diff --git a/src/Umbraco.Core/Notifications/EntityContainerDeletedNotification.cs b/src/Umbraco.Core/Notifications/EntityContainerDeletedNotification.cs index 55d612bdc012..5074aa3893c7 100644 --- a/src/Umbraco.Core/Notifications/EntityContainerDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/EntityContainerDeletedNotification.cs @@ -5,7 +5,8 @@ namespace Umbraco.Cms.Core.Notifications; public class EntityContainerDeletedNotification : DeletedNotification { - public EntityContainerDeletedNotification(EntityContainer target, EventMessages messages) : base(target, messages) + public EntityContainerDeletedNotification(EntityContainer target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/EntityContainerDeletingNotification.cs b/src/Umbraco.Core/Notifications/EntityContainerDeletingNotification.cs index 6f9846ebaf1d..4d22d7715a8b 100644 --- a/src/Umbraco.Core/Notifications/EntityContainerDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/EntityContainerDeletingNotification.cs @@ -5,7 +5,8 @@ namespace Umbraco.Cms.Core.Notifications; public class EntityContainerDeletingNotification : DeletingNotification { - public EntityContainerDeletingNotification(EntityContainer target, EventMessages messages) : base(target, messages) + public EntityContainerDeletingNotification(EntityContainer target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/EntityContainerRenamedNotification.cs b/src/Umbraco.Core/Notifications/EntityContainerRenamedNotification.cs index d3c10b07396a..11e7100b9167 100644 --- a/src/Umbraco.Core/Notifications/EntityContainerRenamedNotification.cs +++ b/src/Umbraco.Core/Notifications/EntityContainerRenamedNotification.cs @@ -5,7 +5,8 @@ namespace Umbraco.Cms.Core.Notifications; public class EntityContainerRenamedNotification : RenamedNotification { - public EntityContainerRenamedNotification(EntityContainer target, EventMessages messages) : base(target, messages) + public EntityContainerRenamedNotification(EntityContainer target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/EntityContainerRenamingNotification.cs b/src/Umbraco.Core/Notifications/EntityContainerRenamingNotification.cs index bfea4b6f8bb8..9e1b795d9f12 100644 --- a/src/Umbraco.Core/Notifications/EntityContainerRenamingNotification.cs +++ b/src/Umbraco.Core/Notifications/EntityContainerRenamingNotification.cs @@ -5,7 +5,8 @@ namespace Umbraco.Cms.Core.Notifications; public class EntityContainerRenamingNotification : RenamingNotification { - public EntityContainerRenamingNotification(EntityContainer target, EventMessages messages) : base(target, messages) + public EntityContainerRenamingNotification(EntityContainer target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/EntityContainerSavedNotification.cs b/src/Umbraco.Core/Notifications/EntityContainerSavedNotification.cs index cd73440b83be..4fa344683473 100644 --- a/src/Umbraco.Core/Notifications/EntityContainerSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/EntityContainerSavedNotification.cs @@ -5,7 +5,8 @@ namespace Umbraco.Cms.Core.Notifications; public class EntityContainerSavedNotification : SavedNotification { - public EntityContainerSavedNotification(EntityContainer target, EventMessages messages) : base(target, messages) + public EntityContainerSavedNotification(EntityContainer target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/EntityContainerSavingNotification.cs b/src/Umbraco.Core/Notifications/EntityContainerSavingNotification.cs index 8994476bdef5..6c5455e76287 100644 --- a/src/Umbraco.Core/Notifications/EntityContainerSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/EntityContainerSavingNotification.cs @@ -5,7 +5,8 @@ namespace Umbraco.Cms.Core.Notifications; public class EntityContainerSavingNotification : SavingNotification { - public EntityContainerSavingNotification(EntityContainer target, EventMessages messages) : base(target, messages) + public EntityContainerSavingNotification(EntityContainer target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/EntityRefreshNotification.cs b/src/Umbraco.Core/Notifications/EntityRefreshNotification.cs index 1dd4fc122a4c..4a5aaa4216c9 100644 --- a/src/Umbraco.Core/Notifications/EntityRefreshNotification.cs +++ b/src/Umbraco.Core/Notifications/EntityRefreshNotification.cs @@ -3,9 +3,11 @@ namespace Umbraco.Cms.Core.Notifications; -public class EntityRefreshNotification : ObjectNotification where T : class, IContentBase +public class EntityRefreshNotification : ObjectNotification + where T : class, IContentBase { - public EntityRefreshNotification(T target, EventMessages messages) : base(target, messages) + public EntityRefreshNotification(T target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/EnumerableObjectNotification.cs b/src/Umbraco.Core/Notifications/EnumerableObjectNotification.cs index 4cb28de7c460..3989e34b4b97 100644 --- a/src/Umbraco.Core/Notifications/EnumerableObjectNotification.cs +++ b/src/Umbraco.Core/Notifications/EnumerableObjectNotification.cs @@ -7,11 +7,13 @@ namespace Umbraco.Cms.Core.Notifications; public abstract class EnumerableObjectNotification : ObjectNotification> { - protected EnumerableObjectNotification(T target, EventMessages messages) : base(new[] {target}, messages) + protected EnumerableObjectNotification(T target, EventMessages messages) + : base(new[] { target }, messages) { } - protected EnumerableObjectNotification(IEnumerable target, EventMessages messages) : base(target, messages) + protected EnumerableObjectNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/INotification.cs b/src/Umbraco.Core/Notifications/INotification.cs index 3d106b37d796..fc73fba39bfb 100644 --- a/src/Umbraco.Core/Notifications/INotification.cs +++ b/src/Umbraco.Core/Notifications/INotification.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Cms.Core.Notifications; diff --git a/src/Umbraco.Core/Notifications/LanguageCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/LanguageCacheRefresherNotification.cs index cd4c7f8a79db..b07277a3fa83 100644 --- a/src/Umbraco.Core/Notifications/LanguageCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/LanguageCacheRefresherNotification.cs @@ -1,10 +1,12 @@ -using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Notifications; public class LanguageCacheRefresherNotification : CacheRefresherNotification { - public LanguageCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + public LanguageCacheRefresherNotification(object messageObject, MessageType messageType) + : base( + messageObject, messageType) { } diff --git a/src/Umbraco.Core/Notifications/LanguageDeletedNotification.cs b/src/Umbraco.Core/Notifications/LanguageDeletedNotification.cs index dd6b299ed76d..9f435775aa92 100644 --- a/src/Umbraco.Core/Notifications/LanguageDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/LanguageDeletedNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public class LanguageDeletedNotification : DeletedNotification { - public LanguageDeletedNotification(ILanguage target, EventMessages messages) : base(target, messages) + public LanguageDeletedNotification(ILanguage target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/LanguageDeletingNotification.cs b/src/Umbraco.Core/Notifications/LanguageDeletingNotification.cs index 16b731d5c6b0..1fdff6538f0c 100644 --- a/src/Umbraco.Core/Notifications/LanguageDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/LanguageDeletingNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public class LanguageDeletingNotification : DeletingNotification { - public LanguageDeletingNotification(ILanguage target, EventMessages messages) : base(target, messages) + public LanguageDeletingNotification(ILanguage target, EventMessages messages) + : base(target, messages) { } - public LanguageDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public LanguageDeletingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/LanguageSavedNotification.cs b/src/Umbraco.Core/Notifications/LanguageSavedNotification.cs index 0c7184b2d38d..b3e58e9b83c6 100644 --- a/src/Umbraco.Core/Notifications/LanguageSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/LanguageSavedNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public class LanguageSavedNotification : SavedNotification { - public LanguageSavedNotification(ILanguage target, EventMessages messages) : base(target, messages) + public LanguageSavedNotification(ILanguage target, EventMessages messages) + : base(target, messages) { } - public LanguageSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public LanguageSavedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/LanguageSavingNotification.cs b/src/Umbraco.Core/Notifications/LanguageSavingNotification.cs index fc46aa80694d..adbba95ad484 100644 --- a/src/Umbraco.Core/Notifications/LanguageSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/LanguageSavingNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public class LanguageSavingNotification : SavingNotification { - public LanguageSavingNotification(ILanguage target, EventMessages messages) : base(target, messages) + public LanguageSavingNotification(ILanguage target, EventMessages messages) + : base(target, messages) { } - public LanguageSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public LanguageSavingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MacroCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/MacroCacheRefresherNotification.cs index b7fab71d846f..c4e580113914 100644 --- a/src/Umbraco.Core/Notifications/MacroCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/MacroCacheRefresherNotification.cs @@ -1,10 +1,12 @@ -using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Notifications; public class MacroCacheRefresherNotification : CacheRefresherNotification { - public MacroCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + public MacroCacheRefresherNotification(object messageObject, MessageType messageType) + : base( + messageObject, messageType) { } diff --git a/src/Umbraco.Core/Notifications/MacroDeletedNotification.cs b/src/Umbraco.Core/Notifications/MacroDeletedNotification.cs index c46c40657277..b42779415a07 100644 --- a/src/Umbraco.Core/Notifications/MacroDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/MacroDeletedNotification.cs @@ -5,7 +5,8 @@ namespace Umbraco.Cms.Core.Notifications; public class MacroDeletedNotification : DeletedNotification { - public MacroDeletedNotification(IMacro target, EventMessages messages) : base(target, messages) + public MacroDeletedNotification(IMacro target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MacroDeletingNotification.cs b/src/Umbraco.Core/Notifications/MacroDeletingNotification.cs index f318b8aa3aea..8d262cb8aa79 100644 --- a/src/Umbraco.Core/Notifications/MacroDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/MacroDeletingNotification.cs @@ -5,11 +5,13 @@ namespace Umbraco.Cms.Core.Notifications; public class MacroDeletingNotification : DeletingNotification { - public MacroDeletingNotification(IMacro target, EventMessages messages) : base(target, messages) + public MacroDeletingNotification(IMacro target, EventMessages messages) + : base(target, messages) { } - public MacroDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public MacroDeletingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MacroSavedNotification.cs b/src/Umbraco.Core/Notifications/MacroSavedNotification.cs index 3471e32d2f56..145ac6eb3da2 100644 --- a/src/Umbraco.Core/Notifications/MacroSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/MacroSavedNotification.cs @@ -5,11 +5,13 @@ namespace Umbraco.Cms.Core.Notifications; public class MacroSavedNotification : SavedNotification { - public MacroSavedNotification(IMacro target, EventMessages messages) : base(target, messages) + public MacroSavedNotification(IMacro target, EventMessages messages) + : base(target, messages) { } - public MacroSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public MacroSavedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MacroSavingNotification.cs b/src/Umbraco.Core/Notifications/MacroSavingNotification.cs index 3ee1c6426296..5786b76d813c 100644 --- a/src/Umbraco.Core/Notifications/MacroSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/MacroSavingNotification.cs @@ -5,11 +5,13 @@ namespace Umbraco.Cms.Core.Notifications; public class MacroSavingNotification : SavingNotification { - public MacroSavingNotification(IMacro target, EventMessages messages) : base(target, messages) + public MacroSavingNotification(IMacro target, EventMessages messages) + : base(target, messages) { } - public MacroSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public MacroSavingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MediaCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/MediaCacheRefresherNotification.cs index 683da92047d1..877ad75617e8 100644 --- a/src/Umbraco.Core/Notifications/MediaCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaCacheRefresherNotification.cs @@ -1,10 +1,12 @@ -using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Notifications; public class MediaCacheRefresherNotification : CacheRefresherNotification { - public MediaCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + public MediaCacheRefresherNotification(object messageObject, MessageType messageType) + : base( + messageObject, messageType) { } diff --git a/src/Umbraco.Core/Notifications/MediaDeletedNotification.cs b/src/Umbraco.Core/Notifications/MediaDeletedNotification.cs index a95c31834f2c..643f907ab815 100644 --- a/src/Umbraco.Core/Notifications/MediaDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaDeletedNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class MediaDeletedNotification : DeletedNotification { - public MediaDeletedNotification(IMedia target, EventMessages messages) : base(target, messages) + public MediaDeletedNotification(IMedia target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MediaDeletedVersionsNotification.cs b/src/Umbraco.Core/Notifications/MediaDeletedVersionsNotification.cs index 9d46eed0580f..b8520e5274f3 100644 --- a/src/Umbraco.Core/Notifications/MediaDeletedVersionsNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaDeletedVersionsNotification.cs @@ -8,9 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class MediaDeletedVersionsNotification : DeletedVersionsNotification { - public MediaDeletedVersionsNotification(int id, EventMessages messages, int specificVersion = default, - bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, - deletePriorVersions, dateToRetain) + public MediaDeletedVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) + : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) { } } diff --git a/src/Umbraco.Core/Notifications/MediaDeletingNotification.cs b/src/Umbraco.Core/Notifications/MediaDeletingNotification.cs index 574f7ba5391b..8973b9861fa9 100644 --- a/src/Umbraco.Core/Notifications/MediaDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaDeletingNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class MediaDeletingNotification : DeletingNotification { - public MediaDeletingNotification(IMedia target, EventMessages messages) : base(target, messages) + public MediaDeletingNotification(IMedia target, EventMessages messages) + : base(target, messages) { } - public MediaDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public MediaDeletingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MediaDeletingVersionsNotification.cs b/src/Umbraco.Core/Notifications/MediaDeletingVersionsNotification.cs index e186ca540c30..0d7ff01ca334 100644 --- a/src/Umbraco.Core/Notifications/MediaDeletingVersionsNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaDeletingVersionsNotification.cs @@ -8,9 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class MediaDeletingVersionsNotification : DeletingVersionsNotification { - public MediaDeletingVersionsNotification(int id, EventMessages messages, int specificVersion = default, - bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, - deletePriorVersions, dateToRetain) + public MediaDeletingVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) + : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) { } } diff --git a/src/Umbraco.Core/Notifications/MediaEmptiedRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/MediaEmptiedRecycleBinNotification.cs index 8ead4f4ad1c7..e0c58c8653bb 100644 --- a/src/Umbraco.Core/Notifications/MediaEmptiedRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaEmptiedRecycleBinNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class MediaEmptiedRecycleBinNotification : EmptiedRecycleBinNotification { - public MediaEmptiedRecycleBinNotification(IEnumerable deletedEntities, EventMessages messages) : base( + public MediaEmptiedRecycleBinNotification(IEnumerable deletedEntities, EventMessages messages) + : base( deletedEntities, messages) { } diff --git a/src/Umbraco.Core/Notifications/MediaEmptyingRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/MediaEmptyingRecycleBinNotification.cs index f9f064b54f0b..43a3971bdb42 100644 --- a/src/Umbraco.Core/Notifications/MediaEmptyingRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaEmptyingRecycleBinNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class MediaEmptyingRecycleBinNotification : EmptyingRecycleBinNotification { - public MediaEmptyingRecycleBinNotification(IEnumerable deletedEntities, EventMessages messages) : base( + public MediaEmptyingRecycleBinNotification(IEnumerable deletedEntities, EventMessages messages) + : base( deletedEntities, messages) { } diff --git a/src/Umbraco.Core/Notifications/MediaMovedNotification.cs b/src/Umbraco.Core/Notifications/MediaMovedNotification.cs index cd75bc474bb4..07ded8c61459 100644 --- a/src/Umbraco.Core/Notifications/MediaMovedNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaMovedNotification.cs @@ -8,11 +8,14 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class MediaMovedNotification : MovedNotification { - public MediaMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + public MediaMovedNotification(MoveEventInfo target, EventMessages messages) + : base(target, messages) { } - public MediaMovedNotification(IEnumerable> target, EventMessages messages) : base(target, + public MediaMovedNotification(IEnumerable> target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/MediaMovedToRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/MediaMovedToRecycleBinNotification.cs index 3dadf540cbfc..b072c1282b23 100644 --- a/src/Umbraco.Core/Notifications/MediaMovedToRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaMovedToRecycleBinNotification.cs @@ -8,12 +8,15 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class MediaMovedToRecycleBinNotification : MovedToRecycleBinNotification { - public MediaMovedToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(target, + public MediaMovedToRecycleBinNotification(MoveEventInfo target, EventMessages messages) + : base( + target, messages) { } - public MediaMovedToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base( + public MediaMovedToRecycleBinNotification(IEnumerable> target, EventMessages messages) + : base( target, messages) { } diff --git a/src/Umbraco.Core/Notifications/MediaMovingNotification.cs b/src/Umbraco.Core/Notifications/MediaMovingNotification.cs index ba22d93f00ab..6ea117d636af 100644 --- a/src/Umbraco.Core/Notifications/MediaMovingNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaMovingNotification.cs @@ -8,11 +8,14 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class MediaMovingNotification : MovingNotification { - public MediaMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + public MediaMovingNotification(MoveEventInfo target, EventMessages messages) + : base(target, messages) { } - public MediaMovingNotification(IEnumerable> target, EventMessages messages) : base(target, + public MediaMovingNotification(IEnumerable> target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/MediaMovingToRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/MediaMovingToRecycleBinNotification.cs index 58b4522aba5f..c236262530df 100644 --- a/src/Umbraco.Core/Notifications/MediaMovingToRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaMovingToRecycleBinNotification.cs @@ -8,13 +8,15 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class MediaMovingToRecycleBinNotification : MovingToRecycleBinNotification { - public MediaMovingToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(target, + public MediaMovingToRecycleBinNotification(MoveEventInfo target, EventMessages messages) + : base( + target, messages) { } - public MediaMovingToRecycleBinNotification(IEnumerable> target, EventMessages messages) : - base(target, messages) + public MediaMovingToRecycleBinNotification(IEnumerable> target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MediaRefreshNotification.cs b/src/Umbraco.Core/Notifications/MediaRefreshNotification.cs index 7edb1e4b6493..bd4cb3efdaf3 100644 --- a/src/Umbraco.Core/Notifications/MediaRefreshNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaRefreshNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; [EditorBrowsable(EditorBrowsableState.Never)] public class MediaRefreshNotification : EntityRefreshNotification { - public MediaRefreshNotification(IMedia target, EventMessages messages) : base(target, messages) + public MediaRefreshNotification(IMedia target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MediaSavedNotification.cs b/src/Umbraco.Core/Notifications/MediaSavedNotification.cs index a1e4e0419494..bf9f50752181 100644 --- a/src/Umbraco.Core/Notifications/MediaSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaSavedNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class MediaSavedNotification : SavedNotification { - public MediaSavedNotification(IMedia target, EventMessages messages) : base(target, messages) + public MediaSavedNotification(IMedia target, EventMessages messages) + : base(target, messages) { } - public MediaSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public MediaSavedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MediaSavingNotification.cs b/src/Umbraco.Core/Notifications/MediaSavingNotification.cs index 9e3edd488746..d902de6ba74b 100644 --- a/src/Umbraco.Core/Notifications/MediaSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaSavingNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class MediaSavingNotification : SavingNotification { - public MediaSavingNotification(IMedia target, EventMessages messages) : base(target, messages) + public MediaSavingNotification(IMedia target, EventMessages messages) + : base(target, messages) { } - public MediaSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public MediaSavingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs b/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs index 932f919c4bb2..fdb76f4bc2ee 100644 --- a/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs +++ b/src/Umbraco.Core/Packaging/CompiledPackageXmlParser.cs @@ -30,13 +30,13 @@ public CompiledPackage ToCompiledPackage(XDocument xml) throw new FormatException("The xml document is invalid"); } - XElement info = xml.Root.Element("info"); + XElement? info = xml.Root.Element("info"); if (info == null) { throw new FormatException("The xml document is invalid"); } - XElement package = info.Element("package"); + XElement? package = info.Element("package"); if (package == null) { throw new FormatException("The xml document is invalid"); @@ -64,7 +64,7 @@ public CompiledPackage ToCompiledPackage(XDocument xml) xml.Root.Element("Documents")?.Elements("DocumentSet")?.Select(CompiledPackageContentBase.Create) ?? Enumerable.Empty(), Media = xml.Root.Element("MediaItems")?.Elements()?.Select(CompiledPackageContentBase.Create) ?? - Enumerable.Empty() + Enumerable.Empty(), }; def.Warnings = GetInstallWarnings(def); @@ -78,7 +78,7 @@ private InstallWarnings GetInstallWarnings(CompiledPackage package) { ConflictingMacros = _conflictingPackageData.FindConflictingMacros(package.Macros), ConflictingTemplates = _conflictingPackageData.FindConflictingTemplates(package.Templates), - ConflictingStylesheets = _conflictingPackageData.FindConflictingStylesheets(package.Stylesheets) + ConflictingStylesheets = _conflictingPackageData.FindConflictingStylesheets(package.Stylesheets), }; return installWarnings; diff --git a/src/Umbraco.Core/Packaging/ConflictingPackageData.cs b/src/Umbraco.Core/Packaging/ConflictingPackageData.cs index aa437c0d66f9..d71eada6184a 100644 --- a/src/Umbraco.Core/Packaging/ConflictingPackageData.cs +++ b/src/Umbraco.Core/Packaging/ConflictingPackageData.cs @@ -1,4 +1,4 @@ -using System.Xml.Linq; +using System.Xml.Linq; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; @@ -20,7 +20,7 @@ public ConflictingPackageData(IMacroService macroService, IFileService fileServi stylesheetNodes? .Select(n => { - XElement xElement = n.Element("Name") ?? n.Element("name"); + XElement? xElement = n.Element("Name") ?? n.Element("name"); if (xElement == null) { throw new FormatException("Missing \"Name\" element"); @@ -34,7 +34,7 @@ public ConflictingPackageData(IMacroService macroService, IFileService fileServi templateNodes? .Select(n => { - XElement xElement = n.Element("Alias") ?? n.Element("alias"); + XElement? xElement = n.Element("Alias") ?? n.Element("alias"); if (xElement == null) { throw new FormatException("missing a \"Alias\" element"); @@ -48,7 +48,7 @@ public ConflictingPackageData(IMacroService macroService, IFileService fileServi macroNodes? .Select(n => { - XElement xElement = n.Element("alias") ?? n.Element("Alias"); + XElement? xElement = n.Element("alias") ?? n.Element("Alias"); if (xElement == null) { throw new FormatException("missing a \"alias\" element in alias element"); diff --git a/src/Umbraco.Core/Packaging/IPackageDefinitionRepository.cs b/src/Umbraco.Core/Packaging/IPackageDefinitionRepository.cs index 6f57bee885f6..b66f4884afe8 100644 --- a/src/Umbraco.Core/Packaging/IPackageDefinitionRepository.cs +++ b/src/Umbraco.Core/Packaging/IPackageDefinitionRepository.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Packaging; +namespace Umbraco.Cms.Core.Packaging; /// /// Defines methods for persisting package definitions to storage @@ -6,7 +6,9 @@ public interface IPackageDefinitionRepository { IEnumerable GetAll(); + PackageDefinition? GetById(int id); + void Delete(int id); /// diff --git a/src/Umbraco.Core/Packaging/InstallationSummary.cs b/src/Umbraco.Core/Packaging/InstallationSummary.cs index 9f154bb8ab40..d72ede149469 100644 --- a/src/Umbraco.Core/Packaging/InstallationSummary.cs +++ b/src/Umbraco.Core/Packaging/InstallationSummary.cs @@ -17,26 +17,38 @@ public InstallationSummary(string packageName) public InstallWarnings Warnings { get; set; } = new(); public IEnumerable DataTypesInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable LanguagesInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable DictionaryItemsInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable MacrosInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable MacroPartialViewsInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable TemplatesInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable DocumentTypesInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable MediaTypesInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable StylesheetsInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable ScriptsInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable PartialViewsInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable ContentInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable MediaInstalled { get; set; } = Enumerable.Empty(); + public IEnumerable EntityContainersInstalled { get; set; } = Enumerable.Empty(); public override string ToString() { var sb = new StringBuilder(); - void WriteConflicts(IEnumerable? source, Func selector, string message, - bool appendLine = true) + void WriteConflicts(IEnumerable? source, Func selector, string message, bool appendLine = true) { var result = source?.Select(selector).ToList(); if (result?.Count > 0) @@ -62,12 +74,9 @@ void WriteCount(string message, IEnumerable source, bool appendLine = true } } - WriteConflicts(Warnings?.ConflictingMacros, x => x?.Alias, - "Conflicting macros found, they will be overwritten: "); - WriteConflicts(Warnings?.ConflictingTemplates, x => x.Alias, - "Conflicting templates found, they will be overwritten: "); - WriteConflicts(Warnings?.ConflictingStylesheets, x => x?.Alias, - "Conflicting stylesheets found, they will be overwritten: "); + WriteConflicts(Warnings?.ConflictingMacros, x => x?.Alias, "Conflicting macros found, they will be overwritten: "); + WriteConflicts(Warnings?.ConflictingTemplates, x => x.Alias, "Conflicting templates found, they will be overwritten: "); + WriteConflicts(Warnings?.ConflictingStylesheets, x => x?.Alias, "Conflicting stylesheets found, they will be overwritten: "); WriteCount("Data types installed: ", DataTypesInstalled); WriteCount("Languages installed: ", LanguagesInstalled); WriteCount("Dictionary items installed: ", DictionaryItemsInstalled); diff --git a/src/Umbraco.Core/Packaging/InstalledPackage.cs b/src/Umbraco.Core/Packaging/InstalledPackage.cs index 00b8d2feca75..3f3cc24a2ad9 100644 --- a/src/Umbraco.Core/Packaging/InstalledPackage.cs +++ b/src/Umbraco.Core/Packaging/InstalledPackage.cs @@ -11,8 +11,8 @@ public class InstalledPackage public string? PackageName { get; set; } // TODO: Version? Icon? Other metadata? This would need to come from querying the package on Our - - [DataMember(Name = "packageView")] public string? PackageView { get; set; } + [DataMember(Name = "packageView")] + public string? PackageView { get; set; } [DataMember(Name = "plans")] public IEnumerable PackageMigrationPlans { get; set; } = diff --git a/src/Umbraco.Core/Packaging/InstalledPackageMigrationPlans.cs b/src/Umbraco.Core/Packaging/InstalledPackageMigrationPlans.cs index a59b075f493c..50cafd1d208f 100644 --- a/src/Umbraco.Core/Packaging/InstalledPackageMigrationPlans.cs +++ b/src/Umbraco.Core/Packaging/InstalledPackageMigrationPlans.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Packaging; diff --git a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs index 89eb1e2aa67d..6ca78967b3b0 100644 --- a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs +++ b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs @@ -6,7 +6,7 @@ public static partial class Constants { public static class DatabaseSchema { - //TODO: Why aren't all table names with the same prefix? + // TODO: Why aren't all table names with the same prefix? public const string TableNamePrefix = "umbraco"; public static class Tables diff --git a/src/Umbraco.Core/Persistence/Constants-Locks.cs b/src/Umbraco.Core/Persistence/Constants-Locks.cs index 67e8564dbc69..e97f16a66303 100644 --- a/src/Umbraco.Core/Persistence/Constants-Locks.cs +++ b/src/Umbraco.Core/Persistence/Constants-Locks.cs @@ -1,10 +1,10 @@ -// ReSharper disable once CheckNamespace +// ReSharper disable once CheckNamespace using Umbraco.Cms.Core.Runtime; namespace Umbraco.Cms.Core; -static partial class Constants +public static partial class Constants { /// /// Defines lock objects. diff --git a/src/Umbraco.Core/Persistence/IQueryRepository.cs b/src/Umbraco.Core/Persistence/IQueryRepository.cs index bc3b612e7fef..e0e507abc1de 100644 --- a/src/Umbraco.Core/Persistence/IQueryRepository.cs +++ b/src/Umbraco.Core/Persistence/IQueryRepository.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Persistence.Querying; +using Umbraco.Cms.Core.Persistence.Querying; namespace Umbraco.Cms.Core.Persistence; diff --git a/src/Umbraco.Core/Persistence/IReadRepository.cs b/src/Umbraco.Core/Persistence/IReadRepository.cs index 3b2d37500224..6503019988c7 100644 --- a/src/Umbraco.Core/Persistence/IReadRepository.cs +++ b/src/Umbraco.Core/Persistence/IReadRepository.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Persistence; +namespace Umbraco.Cms.Core.Persistence; /// /// Defines the base implementation of a reading repository. diff --git a/src/Umbraco.Core/Persistence/IReadWriteQueryRepository.cs b/src/Umbraco.Core/Persistence/IReadWriteQueryRepository.cs index 148831352e9e..40eb92bef68b 100644 --- a/src/Umbraco.Core/Persistence/IReadWriteQueryRepository.cs +++ b/src/Umbraco.Core/Persistence/IReadWriteQueryRepository.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Persistence; +namespace Umbraco.Cms.Core.Persistence; /// /// Defines the base implementation of a reading, writing and querying repository. diff --git a/src/Umbraco.Core/Persistence/IRepository.cs b/src/Umbraco.Core/Persistence/IRepository.cs index c99bcf6f30e7..2629e14c0445 100644 --- a/src/Umbraco.Core/Persistence/IRepository.cs +++ b/src/Umbraco.Core/Persistence/IRepository.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Persistence; +namespace Umbraco.Cms.Core.Persistence; /// /// Defines the base implementation of a repository. diff --git a/src/Umbraco.Core/Persistence/IWriteRepository.cs b/src/Umbraco.Core/Persistence/IWriteRepository.cs index b44ee112a7a4..26e1548bc6b7 100644 --- a/src/Umbraco.Core/Persistence/IWriteRepository.cs +++ b/src/Umbraco.Core/Persistence/IWriteRepository.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Persistence; +namespace Umbraco.Cms.Core.Persistence; /// /// Defines the base implementation of a writing repository. diff --git a/src/Umbraco.Core/Persistence/Querying/IQuery.cs b/src/Umbraco.Core/Persistence/Querying/IQuery.cs index a53aeccbfa21..8803d69fc048 100644 --- a/src/Umbraco.Core/Persistence/Querying/IQuery.cs +++ b/src/Umbraco.Core/Persistence/Querying/IQuery.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using System.Linq.Expressions; namespace Umbraco.Cms.Core.Persistence.Querying; diff --git a/src/Umbraco.Core/Persistence/Repositories/IAuditEntryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IAuditEntryRepository.cs index bb9958b5a333..ade100f0d226 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IAuditEntryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IAuditEntryRepository.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories; diff --git a/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs index 57aa15a176a1..acceefef5db6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IAuditRepository.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; namespace Umbraco.Cms.Core.Persistence.Repositories; @@ -29,7 +29,9 @@ public interface IAuditRepository : IReadRepository, IWriteRepo /// IEnumerable GetPagedResultsByQuery( IQuery query, - long pageIndex, int pageSize, out long totalRecords, + long pageIndex, + int pageSize, + out long totalRecords, Direction orderDirection, AuditType[]? auditTypeFilter, IQuery? customFilter); diff --git a/src/Umbraco.Core/Persistence/Repositories/IConsentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IConsentRepository.cs index eb710d2e6ab2..7fcdb9d2d9c6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IConsentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IConsentRepository.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories; diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs index 2aa55b5232d5..1172512228d8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Services; @@ -73,8 +73,13 @@ public interface IContentRepository : IReadWriteQueryRepository /// Gets paged content items. /// /// Here, can be null but cannot. - IEnumerable GetPage(IQuery? query, long pageIndex, int pageSize, out long totalRecords, - IQuery? filter, Ordering? ordering); + IEnumerable GetPage( + IQuery? query, + long pageIndex, + int pageSize, + out long totalRecords, + IQuery? filter, + Ordering? ordering); ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentTypeCommonRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentTypeCommonRepository.cs index b37701d27cf6..5b122d860d15 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IContentTypeCommonRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IContentTypeCommonRepository.cs @@ -1,6 +1,7 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories; + // TODO // this should be IContentTypeRepository, and what is IContentTypeRepository at the moment should // become IDocumentTypeRepository - but since these interfaces are public, that would be breaking diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepository.cs index 819d462ccc5d..77adda58606b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepository.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; namespace Umbraco.Cms.Core.Persistence.Repositories; diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs index 95f352891609..e90c70e89da7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Events; @@ -10,6 +10,7 @@ public interface IContentTypeRepositoryBase : IReadWriteQueryRepository> Move(TItem moving, EntityContainer container); /// @@ -20,7 +21,6 @@ public interface IContentTypeRepositoryBase : IReadWriteQueryRepositoryUnique across all content, media and member types. string GetUniqueAlias(string alias); - /// /// Gets a value indicating whether there is a list view content item in the path. /// diff --git a/src/Umbraco.Core/Persistence/Repositories/IDataTypeContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDataTypeContainerRepository.cs index aeb8c3bf9a57..69caeb8038c7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDataTypeContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDataTypeContainerRepository.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Persistence.Repositories; +namespace Umbraco.Cms.Core.Persistence.Repositories; public interface IDataTypeContainerRepository : IEntityContainerRepository { diff --git a/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs index 923737358006..060d2f2e1d19 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDataTypeRepository.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories; diff --git a/src/Umbraco.Core/Persistence/Repositories/IDictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDictionaryRepository.cs index d9f535c3d27a..db2347e925d0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDictionaryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDictionaryRepository.cs @@ -1,11 +1,14 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories; public interface IDictionaryRepository : IReadWriteQueryRepository { IDictionaryItem? Get(Guid uniqueId); + IDictionaryItem? Get(string key); + IEnumerable GetDictionaryItemDescendants(Guid? parentId); + Dictionary GetDictionaryItemKeyMap(); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IDocumentBlueprintRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentBlueprintRepository.cs index deab54cee07c..12857f05881c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDocumentBlueprintRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDocumentBlueprintRepository.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Persistence.Repositories; +namespace Umbraco.Cms.Core.Persistence.Repositories; public interface IDocumentBlueprintRepository : IDocumentRepository { diff --git a/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs index 3d5c77576bb9..15312ccbf2dd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDocumentRepository.cs @@ -29,6 +29,7 @@ public interface IDocumentRepository : IContentRepository, IReadR void ClearSchedule(DateTime date, ContentScheduleAction action); bool HasContentForExpiration(DateTime date); + bool HasContentForRelease(DateTime date); /// diff --git a/src/Umbraco.Core/Persistence/Repositories/IDocumentTypeContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentTypeContainerRepository.cs index 8cd5318f9677..ed604ec16567 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDocumentTypeContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDocumentTypeContainerRepository.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Persistence.Repositories; +namespace Umbraco.Cms.Core.Persistence.Repositories; public interface IDocumentTypeContainerRepository : IEntityContainerRepository { diff --git a/src/Umbraco.Core/Persistence/Repositories/IDomainRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDomainRepository.cs index 21aaf74c1c2d..18b2ef1f8e89 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDomainRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDomainRepository.cs @@ -1,11 +1,14 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories; public interface IDomainRepository : IReadWriteQueryRepository { IDomain? GetByName(string domainName); + bool Exists(string domainName); + IEnumerable GetAll(bool includeWildcards); + IEnumerable GetAssignedDomains(int contentId, bool includeWildcards); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs index 4649f41f033f..3e2ae8c7b566 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories; diff --git a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs index fba04ec7fcfb..ff7c8f12d972 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs @@ -8,11 +8,15 @@ namespace Umbraco.Cms.Core.Persistence.Repositories; public interface IEntityRepository : IRepository { IEntitySlim? Get(int id); + IEntitySlim? Get(Guid key); + IEntitySlim? Get(int id, Guid objectTypeId); + IEntitySlim? Get(Guid key, Guid objectTypeId); IEnumerable GetAll(Guid objectType, params int[] ids); + IEnumerable GetAll(Guid objectType, params Guid[] keys); /// @@ -31,13 +35,17 @@ public interface IEntityRepository : IRepository IEnumerable GetByQuery(IQuery query, Guid objectType); UmbracoObjectTypes GetObjectType(int id); + UmbracoObjectTypes GetObjectType(Guid key); + int ReserveId(Guid key); IEnumerable GetAllPaths(Guid objectType, params int[]? ids); + IEnumerable GetAllPaths(Guid objectType, params Guid[] keys); bool Exists(int id); + bool Exists(Guid key); /// @@ -51,7 +59,12 @@ public interface IEntityRepository : IRepository /// /// /// - IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectType, long pageIndex, - int pageSize, out long totalRecords, - IQuery? filter, Ordering? ordering); + IEnumerable GetPagedResultsByQuery( + IQuery query, + Guid objectType, + long pageIndex, + int pageSize, + out long totalRecords, + IQuery? filter, + Ordering? ordering); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IIdKeyMapRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IIdKeyMapRepository.cs index 0a501941b35a..6520644a7f70 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IIdKeyMapRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IIdKeyMapRepository.cs @@ -1,9 +1,10 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories; public interface IIdKeyMapRepository { int? GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType); + Guid? GetIdForKey(int id, UmbracoObjectTypes umbracoObjectType); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IInstallationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IInstallationRepository.cs index 405029662285..f12bd612fc67 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IInstallationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IInstallationRepository.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Persistence.Repositories; +namespace Umbraco.Cms.Core.Persistence.Repositories; public interface IInstallationRepository { diff --git a/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs index b0aaef8b0b48..e7fff03bd7cb 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ILanguageRepository.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories; diff --git a/src/Umbraco.Core/Persistence/Repositories/ILogViewerQueryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ILogViewerQueryRepository.cs index c5f21d2b7ae8..0d1da11c9d27 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ILogViewerQueryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ILogViewerQueryRepository.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories; diff --git a/src/Umbraco.Core/Persistence/Repositories/IMacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMacroRepository.cs index 8c6e21b2ae2b..9d2fe0ecbfa2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMacroRepository.cs @@ -1,8 +1,8 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories; public interface IMacroRepository : IReadWriteQueryRepository, IReadRepository { - //IEnumerable GetAll(params string[] aliases); + // IEnumerable GetAll(params string[] aliases); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IMediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMediaRepository.cs index c0372f021ec1..d51f031071f1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMediaRepository.cs @@ -1,9 +1,10 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories; public interface IMediaRepository : IContentRepository, IReadRepository { IMedia? GetMediaByPath(string mediaPath); + bool RecycleBinSmells(); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IMediaTypeContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMediaTypeContainerRepository.cs index c8470922a512..fe8c798915f9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMediaTypeContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMediaTypeContainerRepository.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Persistence.Repositories; +namespace Umbraco.Cms.Core.Persistence.Repositories; public interface IMediaTypeContainerRepository : IEntityContainerRepository { diff --git a/src/Umbraco.Core/Persistence/Repositories/IMediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMediaTypeRepository.cs index 41e7dd0e2dc1..ac06431ee836 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMediaTypeRepository.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories; diff --git a/src/Umbraco.Core/Persistence/Repositories/IMemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMemberTypeRepository.cs index 8a276f667c7d..f9cd35534ace 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMemberTypeRepository.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories; diff --git a/src/Umbraco.Core/Persistence/Repositories/INodeCountRepository.cs b/src/Umbraco.Core/Persistence/Repositories/INodeCountRepository.cs index 72dcd1a13b27..5f93a912fcbd 100644 --- a/src/Umbraco.Core/Persistence/Repositories/INodeCountRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/INodeCountRepository.cs @@ -1,7 +1,8 @@ -namespace Umbraco.Cms.Core.Persistence.Repositories; +namespace Umbraco.Cms.Core.Persistence.Repositories; public interface INodeCountRepository { int GetNodeCount(Guid nodeType); + int GetMediaCount(); } diff --git a/src/Umbraco.Core/Persistence/Repositories/INotificationsRepository.cs b/src/Umbraco.Core/Persistence/Repositories/INotificationsRepository.cs index 1028f4ab84e8..5a3f63f8cba4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/INotificationsRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/INotificationsRepository.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; @@ -7,14 +7,18 @@ namespace Umbraco.Cms.Core.Persistence.Repositories; public interface INotificationsRepository : IRepository { Notification CreateNotification(IUser user, IEntity entity, string action); + int DeleteNotifications(IUser user); + int DeleteNotifications(IEntity entity); + int DeleteNotifications(IUser user, IEntity entity); + IEnumerable? GetEntityNotifications(IEntity entity); + IEnumerable? GetUserNotifications(IUser user); - IEnumerable? GetUsersNotifications(IEnumerable userIds, string? action, IEnumerable nodeIds, - Guid objectType); + IEnumerable? GetUsersNotifications(IEnumerable userIds, string? action, IEnumerable nodeIds, Guid objectType); IEnumerable SetNotifications(IUser user, IEntity entity, string[] actions); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IPartialViewMacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IPartialViewMacroRepository.cs index 50e56f2cee2e..ba6d24c2d899 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IPartialViewMacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IPartialViewMacroRepository.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Persistence.Repositories; +namespace Umbraco.Cms.Core.Persistence.Repositories; // this only exists to differentiate with IPartialViewRepository in IoC // without resorting to constants, names, whatever - and IPartialViewRepository diff --git a/src/Umbraco.Core/Persistence/Repositories/IPublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IPublicAccessRepository.cs index a2fbd7c67c89..84ef0e92f59e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IPublicAccessRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IPublicAccessRepository.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories; diff --git a/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs index 73871778e9f2..b6393dfcc030 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRedirectUrlRepository.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories; diff --git a/src/Umbraco.Core/Persistence/Repositories/IRelationTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRelationTypeRepository.cs index a5674b97886e..19929ee83f9c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRelationTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRelationTypeRepository.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories; diff --git a/src/Umbraco.Core/Persistence/Repositories/IServerRegistrationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IServerRegistrationRepository.cs index 4ef292a827dd..5593dec09a5f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IServerRegistrationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IServerRegistrationRepository.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories; diff --git a/src/Umbraco.Core/Persistence/Repositories/ITagRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITagRepository.cs index 0b90bb838ab6..35c134adb3a8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ITagRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ITagRepository.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Persistence.Repositories; @@ -62,20 +62,17 @@ public interface ITagRepository : IReadWriteQueryRepository TaggedEntity? GetTaggedEntityById(int id); /// Gets all entities of a type, tagged with any tag in the specified group. - IEnumerable GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string group, - string? culture = null); + IEnumerable GetTaggedEntitiesByTagGroup(TaggableObjectTypes objectType, string group, string? culture = null); /// /// Gets all entities of a type, tagged with the specified tag. /// - IEnumerable GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, string? group = null, - string? culture = null); + IEnumerable GetTaggedEntitiesByTag(TaggableObjectTypes objectType, string tag, string? group = null, string? culture = null); /// /// Gets all tags for an entity type. /// - IEnumerable GetTagsForEntityType(TaggableObjectTypes objectType, string? group = null, - string? culture = null); + IEnumerable GetTagsForEntityType(TaggableObjectTypes objectType, string? group = null, string? culture = null); /// /// Gets all tags attached to an entity. @@ -90,14 +87,12 @@ IEnumerable GetTagsForEntityType(TaggableObjectTypes objectType, string? g /// /// Gets all tags attached to an entity via a property. /// - IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string? group = null, - string? culture = null); + IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string? group = null, string? culture = null); /// /// Gets all tags attached to an entity via a property. /// - IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string? group = null, - string? culture = null); + IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string? group = null, string? culture = null); #endregion } diff --git a/src/Umbraco.Core/Persistence/Repositories/ITwoFactorLoginRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITwoFactorLoginRepository.cs index a732c9e3a9a6..31a279eb62a6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ITwoFactorLoginRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ITwoFactorLoginRepository.cs @@ -5,6 +5,7 @@ namespace Umbraco.Cms.Core.Persistence.Repositories; public interface ITwoFactorLoginRepository : IReadRepository, IWriteRepository { Task DeleteUserLoginsAsync(Guid userOrMemberKey); + Task DeleteUserLoginsAsync(Guid userOrMemberKey, string providerName); Task> GetByUserOrMemberKeyAsync(Guid userOrMemberKey); diff --git a/src/Umbraco.Core/Persistence/Repositories/IUpgradeCheckRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IUpgradeCheckRepository.cs index e509bcfc6738..7a0d8b6f7460 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IUpgradeCheckRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IUpgradeCheckRepository.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Semver; +using Umbraco.Cms.Core.Semver; namespace Umbraco.Cms.Core.Persistence.Repositories; diff --git a/src/Umbraco.Core/Persistence/Repositories/IUserGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IUserGroupRepository.cs index 5d245d7eda37..0959019af2af 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IUserGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IUserGroupRepository.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Models.Membership; namespace Umbraco.Cms.Core.Persistence.Repositories; @@ -40,8 +40,7 @@ public interface IUserGroupRepository : IReadWriteQueryRepository /// Array of entity Ids, if empty will return permissions for the group for all entities - EntityPermissionCollection GetPermissions(IReadOnlyUserGroup[]? groups, bool fallbackToDefaultPermissions, - params int[] nodeIds); + EntityPermissionCollection GetPermissions(IReadOnlyUserGroup[]? groups, bool fallbackToDefaultPermissions, params int[] nodeIds); /// /// Replaces the same permission set for a single group to any number of entities diff --git a/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs index 5067436eec6d..3eb6a684ef03 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs @@ -1,4 +1,4 @@ -using System.Linq.Expressions; +using System.Linq.Expressions; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Persistence.Querying; @@ -28,11 +28,10 @@ public interface IUserRepository : IReadWriteQueryRepository /// bool ExistsByUserName(string username); - /// /// Checks if a user with the login exists /// - /// + /// /// bool ExistsByLogin(string login); @@ -96,13 +95,19 @@ IEnumerable GetPagedResultsByQuery(IQuery? query, long pageIndex, IUser? Get(int? id, bool includeSecurityData); IProfile? GetProfile(string username); + IProfile? GetProfile(int id); + IDictionary GetUserStates(); Guid CreateLoginSession(int? userId, string requestingIpAddress, bool cleanStaleSessions = true); + bool ValidateLoginSession(int userId, Guid sessionId); + int ClearLoginSessions(int userId); + int ClearLoginSessions(TimeSpan timespan); + void ClearLoginSession(Guid sessionId); IEnumerable GetNextUsers(int id, int count); diff --git a/src/Umbraco.Core/Persistence/Repositories/InstallationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/InstallationRepository.cs index 51b2f6bc8c33..c30015a7a01f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/InstallationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/InstallationRepository.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; using Umbraco.Cms.Core.Serialization; namespace Umbraco.Cms.Core.Persistence.Repositories; @@ -24,6 +24,7 @@ public async Task SaveInstallLogAsync(InstallLog installLog) await _httpClient.PostAsync(RestApiInstallUrl, content); } + // this occurs if the server for Our is down or cannot be reached catch (HttpRequestException) { diff --git a/src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs b/src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs index 5c54f582f619..f3427b23d0df 100644 --- a/src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.PropertyEditors; @@ -31,11 +31,14 @@ public class BlockListConfiguration [DataContract] public class BlockConfiguration { - [DataMember(Name = "backgroundColor")] public string? BackgroundColor { get; set; } + [DataMember(Name = "backgroundColor")] + public string? BackgroundColor { get; set; } - [DataMember(Name = "iconColor")] public string? IconColor { get; set; } + [DataMember(Name = "iconColor")] + public string? IconColor { get; set; } - [DataMember(Name = "thumbnail")] public string? Thumbnail { get; set; } + [DataMember(Name = "thumbnail")] + public string? Thumbnail { get; set; } [DataMember(Name = "contentElementTypeKey")] public Guid ContentElementTypeKey { get; set; } @@ -43,13 +46,17 @@ public class BlockConfiguration [DataMember(Name = "settingsElementTypeKey")] public Guid? SettingsElementTypeKey { get; set; } - [DataMember(Name = "view")] public string? View { get; set; } + [DataMember(Name = "view")] + public string? View { get; set; } - [DataMember(Name = "stylesheet")] public string? Stylesheet { get; set; } + [DataMember(Name = "stylesheet")] + public string? Stylesheet { get; set; } - [DataMember(Name = "label")] public string? Label { get; set; } + [DataMember(Name = "label")] + public string? Label { get; set; } - [DataMember(Name = "editorSize")] public string? EditorSize { get; set; } + [DataMember(Name = "editorSize")] + public string? EditorSize { get; set; } [DataMember(Name = "forceHideContentEditorInOverlay")] public bool ForceHideContentEditorInOverlay { get; set; } @@ -58,8 +65,10 @@ public class BlockConfiguration [DataContract] public class NumberRange { - [DataMember(Name = "min")] public int? Min { get; set; } + [DataMember(Name = "min")] + public int? Min { get; set; } - [DataMember(Name = "max")] public int? Max { get; set; } + [DataMember(Name = "max")] + public int? Max { get; set; } } } diff --git a/src/Umbraco.Core/PropertyEditors/ColorPickerConfiguration.cs b/src/Umbraco.Core/PropertyEditors/ColorPickerConfiguration.cs index 3c3e648d1084..02fc30d68b0f 100644 --- a/src/Umbraco.Core/PropertyEditors/ColorPickerConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/ColorPickerConfiguration.cs @@ -1,12 +1,14 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Represents the configuration for the color picker value editor. /// public class ColorPickerConfiguration : ValueListConfiguration { - [ConfigurationField("useLabel", "Include labels?", "boolean", - Description = - "Stores colors as a Json object containing both the color hex string and label, rather than just the hex string.")] + [ConfigurationField( + "useLabel", + "Include labels?", + "boolean", + Description = "Stores colors as a Json object containing both the color hex string and label, rather than just the hex string.")] public bool UseLabel { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs index 549ccb111d21..25aeb93418e4 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Serialization; namespace Umbraco.Cms.Core.PropertyEditors; @@ -46,12 +46,39 @@ public virtual IDictionary DefaultConfiguration /// public virtual object? DefaultConfigurationObject => DefaultConfiguration; + /// + /// Converts a configuration object into a serialized database value. + /// + public static string? ToDatabase( + object? configuration, + IConfigurationEditorJsonSerializer configurationEditorJsonSerializer) + => configuration == null ? null : configurationEditorJsonSerializer.Serialize(configuration); + + /// + /// Gets the configuration as a typed object. + /// + public static TConfiguration? ConfigurationAs(object? obj) + { + if (obj == null) + { + return default; + } + + if (obj is TConfiguration configuration) + { + return configuration; + } + + throw new InvalidCastException( + $"Cannot cast configuration of type {obj.GetType().Name} to {typeof(TConfiguration).Name}."); + } + /// public virtual bool IsConfiguration(object obj) => obj is IDictionary; - /// - public virtual object FromDatabase(string? configurationJson, + public virtual object FromDatabase( + string? configurationJson, IConfigurationEditorJsonSerializer configurationEditorJsonSerializer) => string.IsNullOrWhiteSpace(configurationJson) ? new Dictionary() @@ -63,7 +90,6 @@ public virtual object FromDatabase(string? configurationJson, // by default, return the posted dictionary // but only keep entries that have a non-null/empty value // rest will fall back to default during ToConfigurationEditor() - var keys = editorValues?.Where(x => x.Value == null || (x.Value is string stringValue && string.IsNullOrWhiteSpace(stringValue))) .Select(x => x.Key).ToList(); @@ -85,7 +111,6 @@ public virtual IDictionary ToConfigurationEditor(object? configu // editors that do not override ToEditor/FromEditor have their configuration // as a dictionary of and, by default, we merge their default // configuration with their current configuration - if (configuration == null) { configuration = new Dictionary(); @@ -100,7 +125,7 @@ public virtual IDictionary ToConfigurationEditor(object? configu // clone the default configuration, and apply the current configuration values var d = new Dictionary(DefaultConfiguration); - foreach (var (key, value) in c) + foreach ((string key, object value) in c) { d[key] = value; } @@ -121,30 +146,4 @@ public virtual IDictionary ToValueEditor(object? configuration) /// protected ConfigurationField Field(string name) => Fields.First(x => x.PropertyName == name); - - /// - /// Gets the configuration as a typed object. - /// - public static TConfiguration? ConfigurationAs(object? obj) - { - if (obj == null) - { - return default; - } - - if (obj is TConfiguration configuration) - { - return configuration; - } - - throw new InvalidCastException( - $"Cannot cast configuration of type {obj.GetType().Name} to {typeof(TConfiguration).Name}."); - } - - /// - /// Converts a configuration object into a serialized database value. - /// - public static string? ToDatabase(object? configuration, - IConfigurationEditorJsonSerializer configurationEditorJsonSerializer) - => configuration == null ? null : configurationEditorJsonSerializer.Serialize(configuration); } diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs index 9f6e36ce7802..6d64bc2d1911 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs @@ -41,6 +41,58 @@ protected ConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser edi /// public override object DefaultConfigurationObject => new TConfiguration(); + /// + public override bool IsConfiguration(object obj) + => obj is TConfiguration; + + /// + public override object FromDatabase( + string? configuration, + IConfigurationEditorJsonSerializer configurationEditorJsonSerializer) + { + try + { + if (string.IsNullOrWhiteSpace(configuration)) + { + return new TConfiguration(); + } + + return configurationEditorJsonSerializer.Deserialize(configuration)!; + } + catch (Exception e) + { + throw new InvalidOperationException( + $"Failed to parse configuration \"{configuration}\" as \"{typeof(TConfiguration).Name}\" (see inner exception).", + e); + } + } + + /// + public sealed override object? FromConfigurationEditor( + IDictionary? editorValues, + object? configuration) => FromConfigurationEditor(editorValues, (TConfiguration?)configuration); + + /// + /// Converts the configuration posted by the editor. + /// + /// The configuration object posted by the editor. + /// The current configuration object. + public virtual TConfiguration? FromConfigurationEditor( + IDictionary? editorValues, + TConfiguration? configuration) => + _editorConfigurationParser.ParseFromConfigurationEditor(editorValues, Fields); + + /// + public sealed override IDictionary ToConfigurationEditor(object? configuration) => + ToConfigurationEditor((TConfiguration?)configuration); + + /// + /// Converts configuration values to values for the editor. + /// + /// The configuration. + public virtual Dictionary ToConfigurationEditor(TConfiguration? configuration) => + _editorConfigurationParser.ParseToConfigurationEditor(configuration); + /// /// Discovers fields from configuration properties marked with the field attribute. /// @@ -51,7 +103,7 @@ private static List DiscoverFields(IIOHelper ioHelper) foreach (PropertyInfo property in properties) { - ConfigurationFieldAttribute attribute = property.GetCustomAttribute(false); + ConfigurationFieldAttribute? attribute = property.GetCustomAttribute(false); if (attribute == null) { continue; @@ -60,6 +112,7 @@ private static List DiscoverFields(IIOHelper ioHelper) ConfigurationField field; var attributeView = ioHelper.ResolveRelativeOrVirtualUrl(attribute.View); + // if the field does not have its own type, use the base type if (attribute.Type == null) { @@ -72,7 +125,7 @@ private static List DiscoverFields(IIOHelper ioHelper) PropertyType = property.PropertyType, Description = attribute.Description, HideLabel = attribute.HideLabel, - View = attributeView + View = attributeView, }; fields.Add(field); @@ -131,53 +184,4 @@ private static List DiscoverFields(IIOHelper ioHelper) return fields; } - - /// - public override bool IsConfiguration(object obj) - => obj is TConfiguration; - - /// - public override object FromDatabase(string? configuration, - IConfigurationEditorJsonSerializer configurationEditorJsonSerializer) - { - try - { - if (string.IsNullOrWhiteSpace(configuration)) - { - return new TConfiguration(); - } - - return configurationEditorJsonSerializer.Deserialize(configuration)!; - } - catch (Exception e) - { - throw new InvalidOperationException( - $"Failed to parse configuration \"{configuration}\" as \"{typeof(TConfiguration).Name}\" (see inner exception).", - e); - } - } - - /// - public sealed override object? FromConfigurationEditor(IDictionary? editorValues, - object? configuration) => FromConfigurationEditor(editorValues, (TConfiguration?)configuration); - - /// - /// Converts the configuration posted by the editor. - /// - /// The configuration object posted by the editor. - /// The current configuration object. - public virtual TConfiguration? FromConfigurationEditor(IDictionary? editorValues, - TConfiguration? configuration) => - _editorConfigurationParser.ParseFromConfigurationEditor(editorValues, Fields); - - /// - public sealed override IDictionary ToConfigurationEditor(object? configuration) => - ToConfigurationEditor((TConfiguration?)configuration); - - /// - /// Converts configuration values to values for the editor. - /// - /// The configuration. - public virtual Dictionary ToConfigurationEditor(TConfiguration? configuration) => - _editorConfigurationParser.ParseToConfigurationEditor(configuration); } diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationField.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationField.cs index ea08b87c41cf..40bd0c0ca9c6 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationField.cs +++ b/src/Umbraco.Core/PropertyEditors/ConfigurationField.cs @@ -9,7 +9,7 @@ namespace Umbraco.Cms.Core.PropertyEditors; [DataContract] public class ConfigurationField { - private string? _view; + private readonly string? _view; /// /// Initializes a new instance of the class. @@ -36,7 +36,7 @@ private ConfigurationField(List validators) Config = new Dictionary(); // fill details from attribute, if any - ConfigurationFieldAttribute attribute = GetType().GetCustomAttribute(false); + ConfigurationFieldAttribute? attribute = GetType().GetCustomAttribute(false); if (attribute is null) { return; diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationFieldAttribute.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationFieldAttribute.cs index c607c23c9ddf..c504a790be33 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationFieldAttribute.cs +++ b/src/Umbraco.Core/PropertyEditors/ConfigurationFieldAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Marks a ConfigurationEditor property as a configuration field, and a class as a configuration field type. @@ -43,7 +43,8 @@ public ConfigurationFieldAttribute(string key, string name, string view) if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(name)); } @@ -54,7 +55,8 @@ public ConfigurationFieldAttribute(string key, string name, string view) if (string.IsNullOrWhiteSpace(view)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(view)); } @@ -81,7 +83,8 @@ public ConfigurationFieldAttribute(string name, string view) if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(name)); } @@ -92,7 +95,8 @@ public ConfigurationFieldAttribute(string name, string view) if (string.IsNullOrWhiteSpace(view)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(view)); } diff --git a/src/Umbraco.Core/PropertyEditors/ContentPickerConfiguration.cs b/src/Umbraco.Core/PropertyEditors/ContentPickerConfiguration.cs index 0280b4686e0d..8cbaecdbdbf5 100644 --- a/src/Umbraco.Core/PropertyEditors/ContentPickerConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/ContentPickerConfiguration.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; public class ContentPickerConfiguration : IIgnoreUserStartNodesConfig { @@ -8,8 +8,10 @@ public class ContentPickerConfiguration : IIgnoreUserStartNodesConfig [ConfigurationField("startNodeId", "Start node", "treepicker")] // + config in configuration editor ctor public Udi? StartNodeId { get; set; } - [ConfigurationField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, - "Ignore User Start Nodes", "boolean", + [ConfigurationField( + Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, + "Ignore User Start Nodes", + "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] public bool IgnoreUserStartNodes { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/ContentPickerConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/ContentPickerConfigurationEditor.cs index c6de028ada2c..3bffa4ad6103 100644 --- a/src/Umbraco.Core/PropertyEditors/ContentPickerConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ContentPickerConfigurationEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.IO; @@ -8,13 +8,14 @@ namespace Umbraco.Cms.Core.PropertyEditors; internal class ContentPickerConfigurationEditor : ConfigurationEditor { - public ContentPickerConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : - base(ioHelper, editorConfigurationParser) => + public ContentPickerConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) + : base(ioHelper, editorConfigurationParser) => + // configure fields // this is not part of ContentPickerConfiguration, // but is required to configure the UI editor (when editing the configuration) Field(nameof(ContentPickerConfiguration.StartNodeId)) - .Config = new Dictionary {{"idType", "udi"}}; + .Config = new Dictionary { { "idType", "udi" } }; public override IDictionary ToValueEditor(object? configuration) { diff --git a/src/Umbraco.Core/PropertyEditors/ContentPickerPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/ContentPickerPropertyEditor.cs index ad0c0710ddb4..7ef5407c4f28 100644 --- a/src/Umbraco.Core/PropertyEditors/ContentPickerPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ContentPickerPropertyEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; @@ -32,8 +32,7 @@ public class ContentPickerPropertyEditor : DataEditor public ContentPickerPropertyEditor( IDataValueEditorFactory dataValueEditorFactory, IIOHelper ioHelper) - : this(dataValueEditorFactory, ioHelper, - StaticServiceProvider.Instance.GetRequiredService()) + : this(dataValueEditorFactory, ioHelper, StaticServiceProvider.Instance.GetRequiredService()) { } @@ -74,7 +73,7 @@ public IEnumerable GetReferences(object? value) yield break; } - if (UdiParser.TryParse(asString, out Udi udi)) + if (UdiParser.TryParse(asString, out Udi? udi)) { yield return new UmbracoEntityReference(udi); } diff --git a/src/Umbraco.Core/PropertyEditors/DataEditor.cs b/src/Umbraco.Core/PropertyEditors/DataEditor.cs index 327a4ae90445..115b6a237122 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditor.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Runtime.Serialization; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Models; @@ -50,13 +50,6 @@ public DataEditor(IDataValueEditorFactory dataValueEditorFactory, EditorType typ IsDeprecated = Attribute.IsDeprecated; } - /// - /// Gets the editor attribute. - /// - protected DataEditorAttribute? Attribute { get; } - - protected IDataValueEditorFactory DataValueEditorFactory { get; } - /// /// Gets or sets an explicit value editor. /// @@ -64,6 +57,13 @@ public DataEditor(IDataValueEditorFactory dataValueEditorFactory, EditorType typ [DataMember(Name = "editor")] public IDataValueEditor? ExplicitValueEditor { get; set; } + /// + /// Gets the editor attribute. + /// + protected DataEditorAttribute? Attribute { get; } + + protected IDataValueEditorFactory DataValueEditorFactory { get; } + /// /// Gets or sets an explicit configuration editor. /// @@ -95,6 +95,18 @@ public DataEditor(IDataValueEditorFactory dataValueEditorFactory, EditorType typ [IgnoreDataMember] public bool IsDeprecated { get; } + /// + [DataMember(Name = "defaultConfig")] + public IDictionary DefaultConfiguration + { + // for property value editors, get the ConfigurationEditor.DefaultConfiguration + // else fallback to a default, empty dictionary + get => _defaultConfiguration ?? ((Type & EditorType.PropertyValue) > 0 + ? GetConfigurationEditor().DefaultConfiguration + : _defaultConfiguration = new Dictionary()); + set => _defaultConfiguration = value; + } + /// /// /// @@ -167,19 +179,6 @@ public virtual IDataValueEditor GetValueEditor(object? configuration) /// public IConfigurationEditor GetConfigurationEditor() => ExplicitConfigurationEditor ?? CreateConfigurationEditor(); - /// - [DataMember(Name = "defaultConfig")] - public IDictionary DefaultConfiguration - { - // for property value editors, get the ConfigurationEditor.DefaultConfiguration - // else fallback to a default, empty dictionary - - get => _defaultConfiguration ?? ((Type & EditorType.PropertyValue) > 0 - ? GetConfigurationEditor().DefaultConfiguration - : _defaultConfiguration = new Dictionary()); - set => _defaultConfiguration = value; - } - /// public virtual IPropertyIndexValueFactory PropertyIndexValueFactory => new DefaultPropertyIndexValueFactory(); @@ -203,6 +202,7 @@ protected virtual IDataValueEditor CreateValueEditor() protected virtual IConfigurationEditor CreateConfigurationEditor() { var editor = new ConfigurationEditor(); + // pass the default configuration if this is not a property value editor if ((Type & EditorType.PropertyValue) == 0 && _defaultConfiguration is not null) { diff --git a/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs b/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs index 69705ef76da3..ce15c66a8043 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Marks a class that represents a data editor. @@ -66,7 +66,8 @@ public DataEditorAttribute(string alias, EditorType type, string name, string vi if (string.IsNullOrWhiteSpace(alias)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(alias)); } @@ -82,7 +83,8 @@ public DataEditorAttribute(string alias, EditorType type, string name, string vi if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(name)); } @@ -93,7 +95,8 @@ public DataEditorAttribute(string alias, EditorType type, string name, string vi if (string.IsNullOrWhiteSpace(view)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(view)); } @@ -139,7 +142,8 @@ public string ValueType if (string.IsNullOrWhiteSpace(value)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(value)); } diff --git a/src/Umbraco.Core/PropertyEditors/DataEditorCollection.cs b/src/Umbraco.Core/PropertyEditors/DataEditorCollection.cs index 8079064e847e..40daf7ec7c34 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditorCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditorCollection.cs @@ -4,7 +4,8 @@ namespace Umbraco.Cms.Core.PropertyEditors; public class DataEditorCollection : BuilderCollectionBase { - public DataEditorCollection(Func> items) : base(items) + public DataEditorCollection(Func> items) + : base(items) { } } diff --git a/src/Umbraco.Core/PropertyEditors/DataEditorCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/DataEditorCollectionBuilder.cs index 3b2b2e570678..162c2cc5866c 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditorCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditorCollectionBuilder.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.PropertyEditors; diff --git a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs index 37301856d759..c75844bfa247 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs @@ -108,17 +108,25 @@ public DataValueEditor( [DataMember(Name = "valueType")] public string ValueType { get; set; } + /// + /// A collection of validators for the pre value editor + /// + [DataMember(Name = "validation")] + public List Validators { get; private set; } = new(); + /// public IEnumerable Validate(object? value, bool required, string? format) { List? results = null; var r = Validators.SelectMany(v => v.Validate(value, ValueType, Configuration)).ToList(); - if (r.Any()) { results = r; } + if (r.Any()) + { + results = r; + } // mandatory and regex validators cannot be part of valueEditor.Validators because they // depend on values that are not part of the configuration, .Mandatory and .ValidationRegEx, // so they have to be explicitly invoked here. - if (required) { r = RequiredValidator.ValidateRequired(value, ValueType).ToList(); @@ -155,12 +163,6 @@ public IEnumerable Validate(object? value, bool required, stri return results ?? Enumerable.Empty(); } - /// - /// A collection of validators for the pre value editor - /// - [DataMember(Name = "validation")] - public List Validators { get; private set; } = new(); - /// /// If this is true than the editor will be displayed full width without a label /// @@ -190,12 +192,11 @@ public IEnumerable Validate(object? value, bool required, stri /// public virtual object? FromEditor(ContentPropertyData editorValue, object? currentValue) { - Attempt result = TryConvertValueToCrlType(editorValue.Value); + Attempt result = TryConvertValueToCrlType(editorValue.Value); if (result.Success == false) { StaticApplicationLogging.Logger.LogWarning( - "The value {EditorValue} cannot be converted to the type {StorageTypeValue}", editorValue.Value, - ValueTypes.ToStorageType(ValueType)); + "The value {EditorValue} cannot be converted to the type {StorageTypeValue}", editorValue.Value, ValueTypes.ToStorageType(ValueType)); return null; } @@ -233,7 +234,7 @@ public IEnumerable Validate(object? value, bool required, stri { try { - var json = _jsonSerializer?.Deserialize(stringValue!); + dynamic? json = _jsonSerializer?.Deserialize(stringValue!); return json; } catch @@ -319,7 +320,7 @@ public IEnumerable ConvertDbToXml(IProperty property, bool published) /// public XNode ConvertDbToXml(IPropertyType propertyType, object? value) { - //check for null or empty value, we don't want to return CDATA if that is the case + // check for null or empty value, we don't want to return CDATA if that is the case if (value == null || value.ToString().IsNullOrWhiteSpace()) { return new XText(ConvertDbToString(propertyType, value)); @@ -333,7 +334,7 @@ public XNode ConvertDbToXml(IPropertyType propertyType, object? value) return new XText(ConvertDbToString(propertyType, value)); case ValueStorageType.Nvarchar: case ValueStorageType.Ntext: - //put text in cdata + // put text in cdata return new XCData(ConvertDbToString(propertyType, value)); default: throw new ArgumentOutOfRangeException(); @@ -359,7 +360,7 @@ public virtual string ConvertDbToString(IPropertyType propertyType, object? valu case ValueStorageType.Decimal: return value.ToXmlString(value.GetType()); case ValueStorageType.Date: - //treat dates differently, output the format as xml format + // treat dates differently, output the format as xml format Attempt date = value.TryConvertTo(); if (date.Success == false || date.Result == null) { @@ -420,7 +421,7 @@ public virtual string ConvertDbToString(IPropertyType propertyType, object? valu // If parsing is successful, we need to return as an int, we're only dealing with long's here because of JSON.NET, // we actually don't support long values and if we return a long value, it will get set as a 'long' on the Property.Value (object) and then // when we compare the values for dirty tracking we'll be comparing an int -> long and they will not match. - Attempt result = value.TryConvertTo(valueType); + Attempt result = value.TryConvertTo(valueType); return result.Success && result.Result != null ? Attempt.Succeed((int)(long)result.Result) diff --git a/src/Umbraco.Core/PropertyEditors/DataValueEditorFactory.cs b/src/Umbraco.Core/PropertyEditors/DataValueEditorFactory.cs index d697a0fe0c86..86b771bcaad4 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueEditorFactory.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueEditorFactory.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors; diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs index 7ff6bf446646..24d6f17eb03b 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs @@ -6,33 +6,33 @@ namespace Umbraco.Cms.Core.PropertyEditors; public class DataValueReferenceFactoryCollection : BuilderCollectionBase { - public DataValueReferenceFactoryCollection(Func> items) : base(items) + public DataValueReferenceFactoryCollection(Func> items) + : base(items) { } // TODO: We could further reduce circular dependencies with PropertyEditorCollection by not having IDataValueReference implemented // by property editors and instead just use the already built in IDataValueReferenceFactory and/or refactor that into a more normal collection - - public IEnumerable GetAllReferences(IPropertyCollection properties, + public IEnumerable GetAllReferences( + IPropertyCollection properties, PropertyEditorCollection propertyEditors) { var trackedRelations = new HashSet(); foreach (IProperty p in properties) { - if (!propertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out IDataEditor editor)) + if (!propertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out IDataEditor? editor)) { continue; } - //TODO: We will need to change this once we support tracking via variants/segments + // TODO: We will need to change this once we support tracking via variants/segments // for now, we are tracking values from ALL variants - foreach (IPropertyValue propertyVal in p.Values) { var val = propertyVal.EditedValue; - IDataValueEditor valueEditor = editor?.GetValueEditor(); + IDataValueEditor? valueEditor = editor?.GetValueEditor(); if (valueEditor is IDataValueReference reference) { IEnumerable refs = reference.GetReferences(val); diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs index e889b4893318..d1b5027f8a5b 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.PropertyEditors; diff --git a/src/Umbraco.Core/PropertyEditors/DateTimeConfiguration.cs b/src/Umbraco.Core/PropertyEditors/DateTimeConfiguration.cs index d149324253fb..27c144516032 100644 --- a/src/Umbraco.Core/PropertyEditors/DateTimeConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/DateTimeConfiguration.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Represents the configuration for the datetime value editor. @@ -6,15 +6,17 @@ public class DateTimeConfiguration { public DateTimeConfiguration() => + // different default values Format = "YYYY-MM-DD HH:mm:ss"; - [ConfigurationField("format", "Date format", "textstring", - Description = "If left empty then the format is YYYY-MM-DD. (see momentjs.com for supported formats)")] + [ConfigurationField("format", "Date format", "textstring", Description = "If left empty then the format is YYYY-MM-DD. (see momentjs.com for supported formats)")] public string Format { get; set; } - [ConfigurationField("offsetTime", "Offset time", "boolean", - Description = - "When enabled the time displayed will be offset with the server's timezone, this is useful for scenarios like scheduled publishing when an editor is in a different timezone than the hosted server")] + [ConfigurationField( + "offsetTime", + "Offset time", + "boolean", + Description = "When enabled the time displayed will be offset with the server's timezone, this is useful for scenarios like scheduled publishing when an editor is in a different timezone than the hosted server")] public bool OffsetTime { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/DateTimeConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/DateTimeConfigurationEditor.cs index 460170e19dc9..d97f7e2c6dbc 100644 --- a/src/Umbraco.Core/PropertyEditors/DateTimeConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DateTimeConfigurationEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; @@ -16,12 +16,15 @@ public class DateTimeConfigurationEditor : ConfigurationEditor()) { } - public DateTimeConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base( + public DateTimeConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) + : base( ioHelper, editorConfigurationParser) { } @@ -32,7 +35,7 @@ public override IDictionary ToValueEditor(object? configuration) var format = d["format"].ToString()!; - d["pickTime"] = format.ContainsAny(new[] {"H", "m", "s"}); + d["pickTime"] = format.ContainsAny(new[] { "H", "m", "s" }); return d; } diff --git a/src/Umbraco.Core/PropertyEditors/DateValueEditor.cs b/src/Umbraco.Core/PropertyEditors/DateValueEditor.cs index 177aca5b746e..25cb2c42ed22 100644 --- a/src/Umbraco.Core/PropertyEditors/DateValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DateValueEditor.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors.Validators; using Umbraco.Cms.Core.Serialization; @@ -31,7 +31,7 @@ public override object ToEditor(IProperty property, string? culture = null, stri return string.Empty; } - //Dates will be formatted as yyyy-MM-dd + // Dates will be formatted as yyyy-MM-dd return date.Result.Value.ToString("yyyy-MM-dd"); } } diff --git a/src/Umbraco.Core/PropertyEditors/DecimalConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/DecimalConfigurationEditor.cs index 41958a181053..1b4a094ca205 100644 --- a/src/Umbraco.Core/PropertyEditors/DecimalConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DecimalConfigurationEditor.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.PropertyEditors.Validators; +using Umbraco.Cms.Core.PropertyEditors.Validators; namespace Umbraco.Cms.Core.PropertyEditors; @@ -14,7 +14,7 @@ public DecimalConfigurationEditor() Description = "Enter the minimum amount of number to be entered", Key = "min", View = "decimal", - Name = "Minimum" + Name = "Minimum", }); Fields.Add(new ConfigurationField(new DecimalValidator()) @@ -22,7 +22,7 @@ public DecimalConfigurationEditor() Description = "Enter the intervals amount between each step of number to be entered", Key = "step", View = "decimal", - Name = "Step Size" + Name = "Step Size", }); Fields.Add(new ConfigurationField(new DecimalValidator()) @@ -30,7 +30,7 @@ public DecimalConfigurationEditor() Description = "Enter the maximum amount of number to be entered", Key = "max", View = "decimal", - Name = "Maximum" + Name = "Maximum", }); } } diff --git a/src/Umbraco.Core/PropertyEditors/DecimalPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/DecimalPropertyEditor.cs index 50d7e3c03880..5dc4a3ea5be7 100644 --- a/src/Umbraco.Core/PropertyEditors/DecimalPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DecimalPropertyEditor.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors.Validators; namespace Umbraco.Cms.Core.PropertyEditors; diff --git a/src/Umbraco.Core/PropertyEditors/DefaultPropertyIndexValueFactory.cs b/src/Umbraco.Core/PropertyEditors/DefaultPropertyIndexValueFactory.cs index c9b149222ea6..705ab034fcc2 100644 --- a/src/Umbraco.Core/PropertyEditors/DefaultPropertyIndexValueFactory.cs +++ b/src/Umbraco.Core/PropertyEditors/DefaultPropertyIndexValueFactory.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors; @@ -10,8 +10,7 @@ namespace Umbraco.Cms.Core.PropertyEditors; public class DefaultPropertyIndexValueFactory : IPropertyIndexValueFactory { /// - public IEnumerable>> GetIndexValues(IProperty property, string? culture, - string? segment, bool published) + public IEnumerable>> GetIndexValues(IProperty property, string? culture, string? segment, bool published) { yield return new KeyValuePair>( property.Alias, diff --git a/src/Umbraco.Core/PropertyEditors/DefaultPropertyValueConverterAttribute.cs b/src/Umbraco.Core/PropertyEditors/DefaultPropertyValueConverterAttribute.cs index 61b4e29f4b2b..b74d9903cf91 100644 --- a/src/Umbraco.Core/PropertyEditors/DefaultPropertyValueConverterAttribute.cs +++ b/src/Umbraco.Core/PropertyEditors/DefaultPropertyValueConverterAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Indicates that this is a default property value converter (shipped with Umbraco) diff --git a/src/Umbraco.Core/PropertyEditors/DropDownFlexibleConfiguration.cs b/src/Umbraco.Core/PropertyEditors/DropDownFlexibleConfiguration.cs index 2f9d7ae283cc..c0132d574dcf 100644 --- a/src/Umbraco.Core/PropertyEditors/DropDownFlexibleConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/DropDownFlexibleConfiguration.cs @@ -1,8 +1,11 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; public class DropDownFlexibleConfiguration : ValueListConfiguration { - [ConfigurationField("multiple", "Enable multiple choice", "boolean", + [ConfigurationField( + "multiple", + "Enable multiple choice", + "boolean", Description = "When checked, the dropdown will be a select multiple / combo box style dropdown.")] public bool Multiple { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/EditorType.cs b/src/Umbraco.Core/PropertyEditors/EditorType.cs index 19346f0748a8..15469e1e5126 100644 --- a/src/Umbraco.Core/PropertyEditors/EditorType.cs +++ b/src/Umbraco.Core/PropertyEditors/EditorType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Represents the type of an editor. @@ -19,5 +19,5 @@ public enum EditorType /// /// Macro parameter editor. /// - MacroParameter = 2 + MacroParameter = 2, } diff --git a/src/Umbraco.Core/PropertyEditors/EmailAddressConfiguration.cs b/src/Umbraco.Core/PropertyEditors/EmailAddressConfiguration.cs index 92f7fda58b09..cf3452c1149b 100644 --- a/src/Umbraco.Core/PropertyEditors/EmailAddressConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/EmailAddressConfiguration.cs @@ -1,12 +1,11 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Represents the configuration for the email address value editor. /// public class EmailAddressConfiguration { - [ConfigurationField("IsRequired", "Required?", "hidden", - Description = "Deprecated; Make this required by selecting mandatory when adding to the document type")] + [ConfigurationField("IsRequired", "Required?", "hidden", Description = "Deprecated; Make this required by selecting mandatory when adding to the document type")] [Obsolete("No longer used, use `Mandatory` for the property instead. Will be removed in the next major version")] public bool IsRequired { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/EmailAddressConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/EmailAddressConfigurationEditor.cs index d089df1c932a..2eb507519524 100644 --- a/src/Umbraco.Core/PropertyEditors/EmailAddressConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/EmailAddressConfigurationEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; @@ -20,8 +20,8 @@ public EmailAddressConfigurationEditor(IIOHelper ioHelper) { } - public EmailAddressConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : - base(ioHelper, editorConfigurationParser) + public EmailAddressConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) + : base(ioHelper, editorConfigurationParser) { } } diff --git a/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerConfiguration.cs b/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerConfiguration.cs index dfa91de3b398..e9c8255a193a 100644 --- a/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerConfiguration.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Represents the configuration for the Eye Dropper picker value editor. @@ -8,7 +8,6 @@ public class EyeDropperColorPickerConfiguration [ConfigurationField("showAlpha", "Show alpha", "boolean", Description = "Allow alpha transparency selection.")] public bool ShowAlpha { get; set; } - [ConfigurationField("showPalette", "Show palette", "boolean", - Description = "Show a palette next to the color picker.")] + [ConfigurationField("showPalette", "Show palette", "boolean", Description = "Show a palette next to the color picker.")] public bool ShowPalette { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerConfigurationEditor.cs index 02428a85a91a..2df1a5c11c73 100644 --- a/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerConfigurationEditor.cs @@ -6,8 +6,10 @@ namespace Umbraco.Cms.Core.PropertyEditors; internal class EyeDropperColorPickerConfigurationEditor : ConfigurationEditor { - public EyeDropperColorPickerConfigurationEditor(IIOHelper ioHelper, - IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) + public EyeDropperColorPickerConfigurationEditor( + IIOHelper ioHelper, + IEditorConfigurationParser editorConfigurationParser) + : base(ioHelper, editorConfigurationParser) { } @@ -16,7 +18,7 @@ public override Dictionary ToConfigurationEditor(EyeDropperColorPickerConfiguration? configuration) => new Dictionary { - {"showAlpha", configuration?.ShowAlpha ?? false}, {"showPalette", configuration?.ShowPalette ?? false} + { "showAlpha", configuration?.ShowAlpha ?? false }, { "showPalette", configuration?.ShowPalette ?? false }, }; /// @@ -44,6 +46,6 @@ public override EyeDropperColorPickerConfiguration FromConfigurationEditor( } } - return new EyeDropperColorPickerConfiguration {ShowAlpha = showAlpha, ShowPalette = showPalette}; + return new EyeDropperColorPickerConfiguration { ShowAlpha = showAlpha, ShowPalette = showPalette }; } } diff --git a/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerPropertyEditor.cs index e55fbf0ffe89..076ede0ce5f5 100644 --- a/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerPropertyEditor.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.DependencyInjection; @@ -23,8 +23,7 @@ public EyeDropperColorPickerPropertyEditor( IDataValueEditorFactory dataValueEditorFactory, IIOHelper ioHelper, EditorType type = EditorType.PropertyValue) - : this(dataValueEditorFactory, ioHelper, - StaticServiceProvider.Instance.GetRequiredService(), type) + : this(dataValueEditorFactory, ioHelper, StaticServiceProvider.Instance.GetRequiredService(), type) { } diff --git a/src/Umbraco.Core/PropertyEditors/FileExtensionConfigItem.cs b/src/Umbraco.Core/PropertyEditors/FileExtensionConfigItem.cs index bdb0fbc18de1..4444466c034f 100644 --- a/src/Umbraco.Core/PropertyEditors/FileExtensionConfigItem.cs +++ b/src/Umbraco.Core/PropertyEditors/FileExtensionConfigItem.cs @@ -1,11 +1,13 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.PropertyEditors; [DataContract] public class FileExtensionConfigItem : IFileExtensionConfigItem { - [DataMember(Name = "id")] public int Id { get; set; } + [DataMember(Name = "id")] + public int Id { get; set; } - [DataMember(Name = "value")] public string? Value { get; set; } + [DataMember(Name = "value")] + public string? Value { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/FileUploadConfiguration.cs b/src/Umbraco.Core/PropertyEditors/FileUploadConfiguration.cs index a3a8c57e612e..289f649b009a 100644 --- a/src/Umbraco.Core/PropertyEditors/FileUploadConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/FileUploadConfiguration.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Represents the configuration for the file upload address value editor. diff --git a/src/Umbraco.Core/PropertyEditors/FileUploadConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/FileUploadConfigurationEditor.cs index 17a65d28e500..732e2d795a37 100644 --- a/src/Umbraco.Core/PropertyEditors/FileUploadConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/FileUploadConfigurationEditor.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.DependencyInjection; @@ -17,8 +17,8 @@ public FileUploadConfigurationEditor(IIOHelper ioHelper) { } - public FileUploadConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : - base(ioHelper, editorConfigurationParser) + public FileUploadConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) + : base(ioHelper, editorConfigurationParser) { } } diff --git a/src/Umbraco.Core/PropertyEditors/GridEditor.cs b/src/Umbraco.Core/PropertyEditors/GridEditor.cs index 8ad8ca4e4c42..d661fa9704f0 100644 --- a/src/Umbraco.Core/PropertyEditors/GridEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/GridEditor.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Configuration.Grid; namespace Umbraco.Cms.Core.PropertyEditors; @@ -15,7 +15,8 @@ public GridEditor() [DataMember(Name = "name", IsRequired = true)] public string? Name { get; set; } - [DataMember(Name = "nameTemplate")] public string? NameTemplate { get; set; } + [DataMember(Name = "nameTemplate")] + public string? NameTemplate { get; set; } [DataMember(Name = "alias", IsRequired = true)] public string Alias { get; set; } @@ -23,14 +24,14 @@ public GridEditor() [DataMember(Name = "view", IsRequired = true)] public string? View { get; set; } - [DataMember(Name = "render")] public string? Render { get; set; } + [DataMember(Name = "render")] + public string? Render { get; set; } [DataMember(Name = "icon", IsRequired = true)] public string? Icon { get; set; } - [DataMember(Name = "config")] public IDictionary Config { get; set; } - - protected bool Equals(GridEditor other) => string.Equals(Alias, other.Alias); + [DataMember(Name = "config")] + public IDictionary Config { get; set; } /// /// Determines whether the specified is equal to the current @@ -60,6 +61,8 @@ public override bool Equals(object? obj) return Equals((GridEditor)obj); } + protected bool Equals(GridEditor other) => string.Equals(Alias, other.Alias); + /// /// Serves as a hash function for a particular type. /// diff --git a/src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs index 5829188502a3..d61dcd0e980d 100644 --- a/src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/IConfigurationEditor.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Serialization; namespace Umbraco.Cms.Core.PropertyEditors; @@ -58,7 +58,8 @@ public interface IConfigurationEditor /// Converting the configuration object to the serialized database value is /// achieved by simply serializing the configuration. See . /// - object FromDatabase(string? configurationJson, + object FromDatabase( + string? configurationJson, IConfigurationEditorJsonSerializer configurationEditorJsonSerializer); /// diff --git a/src/Umbraco.Core/PropertyEditors/IConfigureValueType.cs b/src/Umbraco.Core/PropertyEditors/IConfigureValueType.cs index 09f26ee7fb18..47768838d630 100644 --- a/src/Umbraco.Core/PropertyEditors/IConfigureValueType.cs +++ b/src/Umbraco.Core/PropertyEditors/IConfigureValueType.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.PropertyEditors; diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueEditorFactory.cs b/src/Umbraco.Core/PropertyEditors/IDataValueEditorFactory.cs index 070230ac5819..a2f84cd71ccd 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataValueEditorFactory.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataValueEditorFactory.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.PropertyEditors; diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs b/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs index 21c4ca8f5caf..39d7d7e1309a 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Editors; namespace Umbraco.Cms.Core.PropertyEditors; diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueReferenceFactory.cs b/src/Umbraco.Core/PropertyEditors/IDataValueReferenceFactory.cs index b57cd1d37ac5..8c768c295f24 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataValueReferenceFactory.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataValueReferenceFactory.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; public interface IDataValueReferenceFactory { diff --git a/src/Umbraco.Core/PropertyEditors/IFileExtensionConfig.cs b/src/Umbraco.Core/PropertyEditors/IFileExtensionConfig.cs index 2ed4ea50850f..611954395670 100644 --- a/src/Umbraco.Core/PropertyEditors/IFileExtensionConfig.cs +++ b/src/Umbraco.Core/PropertyEditors/IFileExtensionConfig.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Marker interface for any editor configuration that supports defining file extensions diff --git a/src/Umbraco.Core/PropertyEditors/IFileExtensionConfigItem.cs b/src/Umbraco.Core/PropertyEditors/IFileExtensionConfigItem.cs index 4de2f0ea08ed..fa2e8fa5f63a 100644 --- a/src/Umbraco.Core/PropertyEditors/IFileExtensionConfigItem.cs +++ b/src/Umbraco.Core/PropertyEditors/IFileExtensionConfigItem.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; public interface IFileExtensionConfigItem { diff --git a/src/Umbraco.Core/PropertyEditors/IIgnoreUserStartNodesConfig.cs b/src/Umbraco.Core/PropertyEditors/IIgnoreUserStartNodesConfig.cs index 3ec973e7e227..7e6b0c441040 100644 --- a/src/Umbraco.Core/PropertyEditors/IIgnoreUserStartNodesConfig.cs +++ b/src/Umbraco.Core/PropertyEditors/IIgnoreUserStartNodesConfig.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Marker interface for any editor configuration that supports Ignoring user start nodes diff --git a/src/Umbraco.Core/PropertyEditors/IManifestValueValidator.cs b/src/Umbraco.Core/PropertyEditors/IManifestValueValidator.cs index dba27bbf161c..31078649a534 100644 --- a/src/Umbraco.Core/PropertyEditors/IManifestValueValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/IManifestValueValidator.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Defines a value validator that can be referenced in a manifest. diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyIndexValueFactory.cs b/src/Umbraco.Core/PropertyEditors/IPropertyIndexValueFactory.cs index 98ed077cb4cc..fd607f405430 100644 --- a/src/Umbraco.Core/PropertyEditors/IPropertyIndexValueFactory.cs +++ b/src/Umbraco.Core/PropertyEditors/IPropertyIndexValueFactory.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.PropertyEditors; @@ -22,6 +22,5 @@ public interface IPropertyIndexValueFactory /// more than one value for a given field. /// /// - IEnumerable>> GetIndexValues(IProperty property, string? culture, - string? segment, bool published); + IEnumerable>> GetIndexValues(IProperty property, string? culture, string? segment, bool published); } diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs index 4d274514e8ea..37d6b8247573 100644 --- a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Cms.Core.PropertyEditors; @@ -73,8 +73,7 @@ public interface IPropertyValueConverter : IDiscoverable /// white spaces. /// /// - object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, - bool preview); + object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview); /// /// Converts a property intermediate value to an Object value. @@ -97,8 +96,7 @@ public interface IPropertyValueConverter : IDiscoverable /// the cache levels of property values. It is not meant to be used by the converter. /// /// - object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel referenceCacheLevel, object? inter, bool preview); + object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview); /// /// Converts a property intermediate value to an XPath value. @@ -130,6 +128,5 @@ public interface IPropertyValueConverter : IDiscoverable /// the cache levels of property values. It is not meant to be used by the converter. /// /// - object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel referenceCacheLevel, object? inter, bool preview); + object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview); } diff --git a/src/Umbraco.Core/PropertyEditors/IValueFormatValidator.cs b/src/Umbraco.Core/PropertyEditors/IValueFormatValidator.cs index fc5e691109ae..60705123296c 100644 --- a/src/Umbraco.Core/PropertyEditors/IValueFormatValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/IValueFormatValidator.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Umbraco.Cms.Core.PropertyEditors; diff --git a/src/Umbraco.Core/PropertyEditors/IValueRequiredValidator.cs b/src/Umbraco.Core/PropertyEditors/IValueRequiredValidator.cs index 1150ba992b9c..3bbc34843120 100644 --- a/src/Umbraco.Core/PropertyEditors/IValueRequiredValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/IValueRequiredValidator.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Umbraco.Cms.Core.PropertyEditors; diff --git a/src/Umbraco.Core/PropertyEditors/IValueValidator.cs b/src/Umbraco.Core/PropertyEditors/IValueValidator.cs index b9000e817c6a..7d26f8a96cba 100644 --- a/src/Umbraco.Core/PropertyEditors/IValueValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/IValueValidator.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Umbraco.Cms.Core.PropertyEditors; diff --git a/src/Umbraco.Core/PropertyEditors/IntegerConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/IntegerConfigurationEditor.cs index a9b74330fc02..e5d01900c641 100644 --- a/src/Umbraco.Core/PropertyEditors/IntegerConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/IntegerConfigurationEditor.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.PropertyEditors.Validators; +using Umbraco.Cms.Core.PropertyEditors.Validators; namespace Umbraco.Cms.Core.PropertyEditors; @@ -14,7 +14,7 @@ public IntegerConfigurationEditor() Description = "Enter the minimum amount of number to be entered", Key = "min", View = "number", - Name = "Minimum" + Name = "Minimum", }); Fields.Add(new ConfigurationField(new IntegerValidator()) @@ -22,7 +22,7 @@ public IntegerConfigurationEditor() Description = "Enter the intervals amount between each step of number to be entered", Key = "step", View = "number", - Name = "Step Size" + Name = "Step Size", }); Fields.Add(new ConfigurationField(new IntegerValidator()) @@ -30,7 +30,7 @@ public IntegerConfigurationEditor() Description = "Enter the maximum amount of number to be entered", Key = "max", View = "number", - Name = "Maximum" + Name = "Maximum", }); } } diff --git a/src/Umbraco.Core/PropertyEditors/IntegerPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/IntegerPropertyEditor.cs index 138a7cee1bd6..be95623b5623 100644 --- a/src/Umbraco.Core/PropertyEditors/IntegerPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/IntegerPropertyEditor.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors.Validators; namespace Umbraco.Cms.Core.PropertyEditors; diff --git a/src/Umbraco.Core/PropertyEditors/LabelConfiguration.cs b/src/Umbraco.Core/PropertyEditors/LabelConfiguration.cs index 167c1743e70b..f023b86a7880 100644 --- a/src/Umbraco.Core/PropertyEditors/LabelConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/LabelConfiguration.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Represents the configuration for the label value editor. diff --git a/src/Umbraco.Core/PropertyEditors/LabelConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/LabelConfigurationEditor.cs index 24a6df1bacf1..47cda5dcb8df 100644 --- a/src/Umbraco.Core/PropertyEditors/LabelConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/LabelConfigurationEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; @@ -20,26 +20,28 @@ public LabelConfigurationEditor(IIOHelper ioHelper) { } - public LabelConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base( + public LabelConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) + : base( ioHelper, editorConfigurationParser) { } /// - public override LabelConfiguration FromConfigurationEditor(IDictionary? editorValues, + public override LabelConfiguration FromConfigurationEditor( + IDictionary? editorValues, LabelConfiguration? configuration) { var newConfiguration = new LabelConfiguration(); // get the value type // not simply deserializing Json because we want to validate the valueType - if (editorValues is not null && editorValues.TryGetValue( Constants.PropertyEditors.ConfigurationKeys.DataValueType, out var valueTypeObj) && valueTypeObj is string stringValue) { - if (!string.IsNullOrWhiteSpace(stringValue) && ValueTypes.IsValue(stringValue)) // validate + // validate + if (!string.IsNullOrWhiteSpace(stringValue) && ValueTypes.IsValue(stringValue)) { newConfiguration.ValueType = stringValue; } diff --git a/src/Umbraco.Core/PropertyEditors/LabelPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/LabelPropertyEditor.cs index a1b448c82951..d9fd8694e90f 100644 --- a/src/Umbraco.Core/PropertyEditors/LabelPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/LabelPropertyEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; @@ -26,17 +26,18 @@ public class LabelPropertyEditor : DataEditor // Scheduled for removal in v12 [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public LabelPropertyEditor(IDataValueEditorFactory dataValueEditorFactory, + public LabelPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory, IIOHelper ioHelper) - : this(dataValueEditorFactory, ioHelper, - StaticServiceProvider.Instance.GetRequiredService()) + : this(dataValueEditorFactory, ioHelper, StaticServiceProvider.Instance.GetRequiredService()) { } /// /// Initializes a new instance of the class. /// - public LabelPropertyEditor(IDataValueEditorFactory dataValueEditorFactory, + public LabelPropertyEditor( + IDataValueEditorFactory dataValueEditorFactory, IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base(dataValueEditorFactory) diff --git a/src/Umbraco.Core/PropertyEditors/ListViewConfiguration.cs b/src/Umbraco.Core/PropertyEditors/ListViewConfiguration.cs index 9a218af49be0..13f423a328ca 100644 --- a/src/Umbraco.Core/PropertyEditors/ListViewConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/ListViewConfiguration.cs @@ -10,7 +10,6 @@ public class ListViewConfiguration public ListViewConfiguration() { // initialize defaults - PageSize = 10; OrderBy = "SortOrder"; OrderDirection = "asc"; @@ -21,7 +20,7 @@ public ListViewConfiguration() AllowBulkUnpublish = true, AllowBulkCopy = true, AllowBulkMove = true, - AllowBulkDelete = true + AllowBulkDelete = true, }; Layouts = new[] @@ -32,7 +31,7 @@ public ListViewConfiguration() Icon = "icon-list", IsSystem = 1, Selected = true, - Path = "views/propertyeditors/listview/layouts/list/list.html" + Path = "views/propertyeditors/listview/layouts/list/list.html", }, new Layout { @@ -40,30 +39,30 @@ public ListViewConfiguration() Icon = "icon-thumbnails-small", IsSystem = 1, Selected = true, - Path = "views/propertyeditors/listview/layouts/grid/grid.html" - } + Path = "views/propertyeditors/listview/layouts/grid/grid.html", + }, }; IncludeProperties = new[] { - new Property {Alias = "sortOrder", Header = "Sort order", IsSystem = 1}, - new Property {Alias = "updateDate", Header = "Last edited", IsSystem = 1}, - new Property {Alias = "owner", Header = "Created by", IsSystem = 1} + new Property { Alias = "sortOrder", Header = "Sort order", IsSystem = 1 }, + new Property { Alias = "updateDate", Header = "Last edited", IsSystem = 1 }, + new Property { Alias = "owner", Header = "Created by", IsSystem = 1 }, }; } [ConfigurationField("pageSize", "Page Size", "number", Description = "Number of items per page")] public int PageSize { get; set; } - [ConfigurationField("orderBy", "Order By", "views/propertyeditors/listview/sortby.prevalues.html", - Description = "The default sort order for the list")] + [ConfigurationField("orderBy", "Order By", "views/propertyeditors/listview/sortby.prevalues.html", Description = "The default sort order for the list")] public string OrderBy { get; set; } - [ConfigurationField("orderDirection", "Order Direction", - "views/propertyeditors/listview/orderDirection.prevalues.html")] + [ConfigurationField("orderDirection", "Order Direction", "views/propertyeditors/listview/orderDirection.prevalues.html")] public string OrderDirection { get; set; } - [ConfigurationField("includeProperties", "Columns Displayed", + [ConfigurationField( + "includeProperties", + "Columns Displayed", "views/propertyeditors/listview/includeproperties.prevalues.html", Description = "The properties that will be displayed for each column")] public Property[] IncludeProperties { get; set; } @@ -71,51 +70,66 @@ public ListViewConfiguration() [ConfigurationField("layouts", "Layouts", "views/propertyeditors/listview/layouts.prevalues.html")] public Layout[] Layouts { get; set; } - [ConfigurationField("bulkActionPermissions", "Bulk Action Permissions", + [ConfigurationField( + "bulkActionPermissions", + "Bulk Action Permissions", "views/propertyeditors/listview/bulkActionPermissions.prevalues.html", Description = "The bulk actions that are allowed from the list view")] public BulkActionPermissionSettings BulkActionPermissions { get; set; } = new(); // TODO: managing defaults? - [ConfigurationField("icon", "Content app icon", "views/propertyeditors/listview/icon.prevalues.html", - Description = "The icon of the listview content app")] + [ConfigurationField("icon", "Content app icon", "views/propertyeditors/listview/icon.prevalues.html", Description = "The icon of the listview content app")] public string? Icon { get; set; } - [ConfigurationField("tabName", "Content app name", "textstring", - Description = "The name of the listview content app (default if empty: 'Child Items')")] + [ConfigurationField("tabName", "Content app name", "textstring", Description = "The name of the listview content app (default if empty: 'Child Items')")] public string? TabName { get; set; } - [ConfigurationField("showContentFirst", "Show Content App First", "boolean", + [ConfigurationField( + "showContentFirst", + "Show Content App First", + "boolean", Description = "Enable this to show the content app by default instead of the list view app")] public bool ShowContentFirst { get; set; } - [ConfigurationField("useInfiniteEditor", "Edit in Infinite Editor", "boolean", + [ConfigurationField( + "useInfiniteEditor", + "Edit in Infinite Editor", + "boolean", Description = "Enable this to use infinite editing to edit the content of the list view")] public bool UseInfiniteEditor { get; set; } [DataContract] public class Property { - [DataMember(Name = "alias")] public string? Alias { get; set; } + [DataMember(Name = "alias")] + public string? Alias { get; set; } - [DataMember(Name = "header")] public string? Header { get; set; } + [DataMember(Name = "header")] + public string? Header { get; set; } - [DataMember(Name = "nameTemplate")] public string? Template { get; set; } + [DataMember(Name = "nameTemplate")] + public string? Template { get; set; } - [DataMember(Name = "isSystem")] public int IsSystem { get; set; } // TODO: bool + [DataMember(Name = "isSystem")] + public int IsSystem { get; set; } // TODO: bool } [DataContract] public class Layout { - [DataMember(Name = "name")] public string? Name { get; set; } + [DataMember(Name = "name")] + public string? Name { get; set; } - [DataMember(Name = "path")] public string? Path { get; set; } + [DataMember(Name = "path")] + public string? Path { get; set; } - [DataMember(Name = "icon")] public string? Icon { get; set; } + [DataMember(Name = "icon")] + public string? Icon { get; set; } - [DataMember(Name = "isSystem")] public int IsSystem { get; set; } // TODO: bool + [DataMember(Name = "isSystem")] + public int IsSystem { get; set; } // TODO: bool - [DataMember(Name = "selected")] public bool Selected { get; set; } + [DataMember(Name = "selected")] + public bool Selected { get; set; } } [DataContract] @@ -127,10 +141,13 @@ public class BulkActionPermissionSettings [DataMember(Name = "allowBulkUnpublish")] public bool AllowBulkUnpublish { get; set; } = true; - [DataMember(Name = "allowBulkCopy")] public bool AllowBulkCopy { get; set; } = true; + [DataMember(Name = "allowBulkCopy")] + public bool AllowBulkCopy { get; set; } = true; - [DataMember(Name = "allowBulkMove")] public bool AllowBulkMove { get; set; } = true; + [DataMember(Name = "allowBulkMove")] + public bool AllowBulkMove { get; set; } = true; - [DataMember(Name = "allowBulkDelete")] public bool AllowBulkDelete { get; set; } = true; + [DataMember(Name = "allowBulkDelete")] + public bool AllowBulkDelete { get; set; } = true; } } diff --git a/src/Umbraco.Core/PropertyEditors/ListViewConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/ListViewConfigurationEditor.cs index 60b100d275a8..5c6bc0bc355c 100644 --- a/src/Umbraco.Core/PropertyEditors/ListViewConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ListViewConfigurationEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; @@ -20,7 +20,8 @@ public ListViewConfigurationEditor(IIOHelper ioHelper) { } - public ListViewConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base( + public ListViewConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) + : base( ioHelper, editorConfigurationParser) { } diff --git a/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollection.cs b/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollection.cs index e419e1d0bf25..f2a08076b9a4 100644 --- a/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollection.cs @@ -5,7 +5,8 @@ namespace Umbraco.Cms.Core.PropertyEditors; public class ManifestValueValidatorCollection : BuilderCollectionBase { - public ManifestValueValidatorCollection(Func> items) : base(items) + public ManifestValueValidatorCollection(Func> items) + : base(items) { } @@ -20,7 +21,7 @@ public ManifestValueValidatorCollection(Func x.ValidationName.InvariantEquals(name)); + IManifestValueValidator? v = this.FirstOrDefault(x => x.ValidationName.InvariantEquals(name)); if (v == null) { throw new InvalidOperationException($"Could not find a validator named \"{name}\"."); diff --git a/src/Umbraco.Core/PropertyEditors/MarkdownConfiguration.cs b/src/Umbraco.Core/PropertyEditors/MarkdownConfiguration.cs index 94ed2104f75c..b11ef08f3041 100644 --- a/src/Umbraco.Core/PropertyEditors/MarkdownConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/MarkdownConfiguration.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Represents the configuration for the markdown value editor. @@ -8,12 +8,9 @@ public class MarkdownConfiguration [ConfigurationField("preview", "Preview", "boolean", Description = "Display a live preview")] public bool DisplayLivePreview { get; set; } - [ConfigurationField("defaultValue", "Default value", "textarea", - Description = "If value is blank, the editor will show this")] + [ConfigurationField("defaultValue", "Default value", "textarea", Description = "If value is blank, the editor will show this")] public string? DefaultValue { get; set; } - - [ConfigurationField("overlaySize", "Overlay Size", "overlaysize", - Description = "Select the width of the overlay (link picker).")] + [ConfigurationField("overlaySize", "Overlay Size", "overlaysize", Description = "Select the width of the overlay (link picker).")] public string? OverlaySize { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/MarkdownConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/MarkdownConfigurationEditor.cs index 6647cd59ff74..6b626ec01f5e 100644 --- a/src/Umbraco.Core/PropertyEditors/MarkdownConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/MarkdownConfigurationEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.IO; @@ -11,7 +11,8 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// internal class MarkdownConfigurationEditor : ConfigurationEditor { - public MarkdownConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base( + public MarkdownConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) + : base( ioHelper, editorConfigurationParser) { } diff --git a/src/Umbraco.Core/PropertyEditors/MarkdownPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/MarkdownPropertyEditor.cs index 876cbcb8a82f..3cabd3a306f2 100644 --- a/src/Umbraco.Core/PropertyEditors/MarkdownPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/MarkdownPropertyEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; @@ -28,8 +28,7 @@ public class MarkdownPropertyEditor : DataEditor public MarkdownPropertyEditor( IDataValueEditorFactory dataValueEditorFactory, IIOHelper ioHelper) - : this(dataValueEditorFactory, ioHelper, - StaticServiceProvider.Instance.GetRequiredService()) + : this(dataValueEditorFactory, ioHelper, StaticServiceProvider.Instance.GetRequiredService()) { } diff --git a/src/Umbraco.Core/PropertyEditors/MediaPicker3Configuration.cs b/src/Umbraco.Core/PropertyEditors/MediaPicker3Configuration.cs index 0874fec2e40a..11ed4d1afd06 100644 --- a/src/Umbraco.Core/PropertyEditors/MediaPicker3Configuration.cs +++ b/src/Umbraco.Core/PropertyEditors/MediaPicker3Configuration.cs @@ -7,8 +7,7 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// public class MediaPicker3Configuration : IIgnoreUserStartNodesConfig { - [ConfigurationField("filter", "Accepted types", "treesourcetypepicker", - Description = "Limit to specific types")] + [ConfigurationField("filter", "Accepted types", "treesourcetypepicker", Description = "Limit to specific types")] public string? Filter { get; set; } [ConfigurationField("multiple", "Pick multiple items", "boolean", Description = "Outputs a IEnumerable")] @@ -23,32 +22,43 @@ public class MediaPicker3Configuration : IIgnoreUserStartNodesConfig [ConfigurationField("enableLocalFocalPoint", "Enable Focal Point", "boolean")] public bool EnableLocalFocalPoint { get; set; } - [ConfigurationField("crops", "Image Crops", "views/propertyeditors/mediapicker3/prevalue/mediapicker3.crops.html", + [ConfigurationField( + "crops", + "Image Crops", + "views/propertyeditors/mediapicker3/prevalue/mediapicker3.crops.html", Description = "Local crops, stored on document")] public CropConfiguration[]? Crops { get; set; } - [ConfigurationField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, - "Ignore User Start Nodes", "boolean", + [ConfigurationField( + Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, + "Ignore User Start Nodes", + "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] public bool IgnoreUserStartNodes { get; set; } [DataContract] public class NumberRange { - [DataMember(Name = "min")] public int? Min { get; set; } + [DataMember(Name = "min")] + public int? Min { get; set; } - [DataMember(Name = "max")] public int? Max { get; set; } + [DataMember(Name = "max")] + public int? Max { get; set; } } [DataContract] public class CropConfiguration { - [DataMember(Name = "alias")] public string? Alias { get; set; } + [DataMember(Name = "alias")] + public string? Alias { get; set; } - [DataMember(Name = "label")] public string? Label { get; set; } + [DataMember(Name = "label")] + public string? Label { get; set; } - [DataMember(Name = "width")] public int Width { get; set; } + [DataMember(Name = "width")] + public int Width { get; set; } - [DataMember(Name = "height")] public int Height { get; set; } + [DataMember(Name = "height")] + public int Height { get; set; } } } diff --git a/src/Umbraco.Core/PropertyEditors/MediaPicker3ConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/MediaPicker3ConfigurationEditor.cs index f325faeb02d8..9ccf64a6f01e 100644 --- a/src/Umbraco.Core/PropertyEditors/MediaPicker3ConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/MediaPicker3ConfigurationEditor.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Web.Common.DependencyInjection; @@ -20,17 +20,16 @@ public MediaPicker3ConfigurationEditor(IIOHelper ioHelper) /// /// Initializes a new instance of the class. /// - public MediaPicker3ConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : - base(ioHelper, editorConfigurationParser) + public MediaPicker3ConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) + : base(ioHelper, editorConfigurationParser) { // configure fields // this is not part of ContentPickerConfiguration, // but is required to configure the UI editor (when editing the configuration) - Field(nameof(MediaPicker3Configuration.StartNodeId)) - .Config = new Dictionary {{"idType", "udi"}}; + .Config = new Dictionary { { "idType", "udi" } }; Field(nameof(MediaPicker3Configuration.Filter)) - .Config = new Dictionary {{"itemType", "media"}}; + .Config = new Dictionary { { "itemType", "media" } }; } } diff --git a/src/Umbraco.Core/PropertyEditors/MediaPickerConfiguration.cs b/src/Umbraco.Core/PropertyEditors/MediaPickerConfiguration.cs index c0f061b283c0..8d6d99671641 100644 --- a/src/Umbraco.Core/PropertyEditors/MediaPickerConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/MediaPickerConfiguration.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Represents the configuration for the media picker value editor. @@ -19,7 +19,8 @@ public class MediaPickerConfiguration : IIgnoreUserStartNodesConfig [ConfigurationField("startNodeId", "Start node", "mediapicker")] public Udi? StartNodeId { get; set; } - [ConfigurationField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, + [ConfigurationField( + Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, "Ignore User Start Nodes", "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] public bool IgnoreUserStartNodes { get; set; } diff --git a/src/Umbraco.Core/PropertyEditors/MediaPickerConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/MediaPickerConfigurationEditor.cs index 0d66439a78d8..62e9eac43998 100644 --- a/src/Umbraco.Core/PropertyEditors/MediaPickerConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/MediaPickerConfigurationEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; @@ -23,13 +23,14 @@ public MediaPickerConfigurationEditor(IIOHelper ioHelper) /// /// Initializes a new instance of the class. /// - public MediaPickerConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : - base(ioHelper, editorConfigurationParser) => + public MediaPickerConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) + : base(ioHelper, editorConfigurationParser) => + // configure fields // this is not part of ContentPickerConfiguration, // but is required to configure the UI editor (when editing the configuration) Field(nameof(MediaPickerConfiguration.StartNodeId)) - .Config = new Dictionary {{"idType", "udi"}}; + .Config = new Dictionary { { "idType", "udi" } }; public override IDictionary ToValueEditor(object? configuration) { diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/ContentTypeParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/ContentTypeParameterEditor.cs index 5a227348ea01..25bcc38d7ed9 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/ContentTypeParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/ContentTypeParameterEditor.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors; +namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors; /// /// Represents a content type parameter editor. diff --git a/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorElementTypeValidationResult.cs b/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorElementTypeValidationResult.cs index 158198a37dcb..1332b0b03cd0 100644 --- a/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorElementTypeValidationResult.cs +++ b/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorElementTypeValidationResult.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Umbraco.Cms.Core.PropertyEditors.Validation; diff --git a/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorPropertyTypeValidationResult.cs b/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorPropertyTypeValidationResult.cs index 0ad261a2e469..06749c765aaa 100644 --- a/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorPropertyTypeValidationResult.cs +++ b/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorPropertyTypeValidationResult.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Umbraco.Cms.Core.PropertyEditors.Validation; @@ -19,6 +19,7 @@ public ComplexEditorPropertyTypeValidationResult(string propertyTypeAlias) PropertyTypeAlias = propertyTypeAlias; public IReadOnlyList ValidationResults => _validationResults; + public string PropertyTypeAlias { get; } public void AddValidationResult(ValidationResult validationResult) diff --git a/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorValidationResult.cs b/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorValidationResult.cs index 0d6ca74c8a33..6ea03ae60fd5 100644 --- a/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorValidationResult.cs +++ b/src/Umbraco.Core/PropertyEditors/Validation/ComplexEditorValidationResult.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Umbraco.Cms.Core.PropertyEditors.Validation; diff --git a/src/Umbraco.Core/PropertyEditors/Validators/DateTimeValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/DateTimeValidator.cs index 0429b96416fd..530935d276c2 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/DateTimeValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/DateTimeValidator.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors.Validators; @@ -10,22 +10,21 @@ public class DateTimeValidator : IValueValidator { public IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration) { - //don't validate if empty + // don't validate if empty if (value == null || value.ToString().IsNullOrWhiteSpace()) { yield break; } - DateTime dt; - if (DateTime.TryParse(value.ToString(), out dt) == false) + if (DateTime.TryParse(value.ToString(), out DateTime dt) == false) { yield return new ValidationResult( string.Format("The string value {0} cannot be parsed into a DateTime", value), new[] { - //we only store a single value for this editor so the 'member' or 'field' + // we only store a single value for this editor so the 'member' or 'field' // we'll associate this error with will simply be called 'value' - "value" + "value", }); } } diff --git a/src/Umbraco.Core/PropertyEditors/Validators/DecimalValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/DecimalValidator.cs index b17dc9ccb289..cc00b4614e90 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/DecimalValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/DecimalValidator.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors.Validators; @@ -22,7 +22,7 @@ public IEnumerable Validate(object? value, string? valueType, Attempt result = value.TryConvertTo(); if (result.Success == false) { - yield return new ValidationResult("The value " + value + " is not a valid decimal", new[] {"value"}); + yield return new ValidationResult("The value " + value + " is not a valid decimal", new[] { "value" }); } } } diff --git a/src/Umbraco.Core/PropertyEditors/Validators/DelimitedValueValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/DelimitedValueValidator.cs index 29b5a722d06c..73907a4266f6 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/DelimitedValueValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/DelimitedValueValidator.cs @@ -16,7 +16,6 @@ public sealed class DelimitedValueValidator : IManifestValueValidator /// public string ValidationName => "Delimited"; - /// public IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration) { @@ -24,14 +23,15 @@ public IEnumerable Validate(object? value, string? valueType, if (value != null) { var delimiter = Configuration?.Delimiter ?? ","; - Regex regex = Configuration?.Pattern != null ? new Regex(Configuration.Pattern) : null; + Regex? regex = Configuration?.Pattern != null ? new Regex(Configuration.Pattern) : null; var stringVal = value.ToString(); - var split = stringVal!.Split(new[] {delimiter}, StringSplitOptions.RemoveEmptyEntries); + var split = stringVal!.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries); for (var i = 0; i < split.Length; i++) { var s = split[i]; - //next if we have a regex statement validate with that + + // next if we have a regex statement validate with that if (regex != null) { if (regex.IsMatch(s) == false) @@ -40,8 +40,8 @@ public IEnumerable Validate(object? value, string? valueType, "The item at index " + i + " did not match the expression " + regex, new[] { - //make the field name called 'value0' where 0 is the index - "value" + i + // make the field name called 'value0' where 0 is the index + "value" + i, }); } } @@ -53,5 +53,6 @@ public IEnumerable Validate(object? value, string? valueType, public class DelimitedValueValidatorConfig { public string? Delimiter { get; set; } + public string? Pattern { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs index ca10db63f1f4..8b984dc533d7 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/EmailValidator.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Umbraco.Cms.Core.PropertyEditors.Validators; @@ -13,14 +13,14 @@ public sealed class EmailValidator : IManifestValueValidator /// public IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration) { - var asString = value == null ? "" : value.ToString(); + var asString = value == null ? string.Empty : value.ToString(); var emailVal = new EmailAddressAttribute(); if (asString != string.Empty && emailVal.IsValid(asString) == false) { // TODO: localize these! - yield return new ValidationResult("Email is invalid", new[] {"value"}); + yield return new ValidationResult("Email is invalid", new[] { "value" }); } } } diff --git a/src/Umbraco.Core/PropertyEditors/Validators/IntegerValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/IntegerValidator.cs index fa527eac12f3..2123d213f62f 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/IntegerValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/IntegerValidator.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors.Validators; @@ -19,7 +19,7 @@ public IEnumerable Validate(object? value, string? valueType, Attempt result = value.TryConvertTo(); if (result.Success == false) { - yield return new ValidationResult("The value " + value + " is not a valid integer", new[] {"value"}); + yield return new ValidationResult("The value " + value + " is not a valid integer", new[] { "value" }); } } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs index c5ef520f8d58..2e5c17fe7ee9 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/CheckboxListValueConverter.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Serialization; using Umbraco.Extensions; @@ -20,8 +20,7 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel cacheLevel, object? source, bool preview) + public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview) { var sourceString = source?.ToString() ?? string.Empty; diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs index bbd496c412ae..eded7b732951 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs @@ -10,7 +10,7 @@ internal class ContentPickerValueConverter : PropertyValueConverterBase private static readonly List PropertiesToExclude = new() { Constants.Conventions.Content.InternalRedirectId.ToLower(CultureInfo.InvariantCulture), - Constants.Conventions.Content.Redirect.ToLower(CultureInfo.InvariantCulture) + Constants.Conventions.Content.Redirect.ToLower(CultureInfo.InvariantCulture), }; private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; @@ -27,15 +27,13 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Elements; - public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, - object? source, bool preview) + public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) { if (source == null) { return null; } - if (source is not string) { Attempt attemptConvertInt = source.TryConvertTo(); @@ -45,7 +43,7 @@ public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType } } - //Don't attempt to convert to int for UDI + // Don't attempt to convert to int for UDI if (source is string strSource && !string.IsNullOrWhiteSpace(strSource) && !strSource.StartsWith("umb") @@ -63,8 +61,7 @@ public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType return null; } - public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) { if (inter == null) { @@ -86,8 +83,7 @@ public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType } else { - var udi = inter as GuidUdi; - if (udi is null) + if (inter is not GuidUdi udi) { return null; } @@ -103,8 +99,7 @@ public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType return inter; } - public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) { if (inter == null) { diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs index 2fb967aea0c1..79419469640f 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs @@ -1,4 +1,4 @@ -using System.Xml; +using System.Xml; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Extensions; @@ -16,8 +16,7 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, - object? source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) { if (source == null) { @@ -28,7 +27,6 @@ public override object ConvertSourceToIntermediate(IPublishedElement owner, IPub // Actually, not always sometimes it is formatted in UTC style with 'Z' suffixed on the end but that is due to this bug: // http://issues.umbraco.org/issue/U4-4145, http://issues.umbraco.org/issue/U4-3894 // We should just be using TryConvertTo instead. - if (source is string sourceString) { Attempt attempt = sourceString.TryConvertTo(); @@ -41,9 +39,12 @@ public override object ConvertSourceToIntermediate(IPublishedElement owner, IPub } // default ConvertSourceToObject just returns source ie a DateTime value - - public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + public override object? ConvertIntermediateToXPath( + IPublishedElement owner, + IPublishedPropertyType propertyType, + PropertyCacheLevel referenceCacheLevel, + object? inter, + bool preview) { // source should come from ConvertSource and be a DateTime already if (inter is null) diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs index 5ab8403d1fa9..5a7f0a4adc42 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/DecimalValueConverter.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; @@ -15,8 +15,7 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, - object? source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) { if (source == null) { @@ -38,8 +37,7 @@ public override object ConvertSourceToIntermediate(IPublishedElement owner, IPub // is it a string? if (source is string sourceString) { - return decimal.TryParse(sourceString, NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, - CultureInfo.InvariantCulture, out var d) + return decimal.TryParse(sourceString, NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out var d) ? d : 0M; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/EmailAddressValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/EmailAddressValueConverter.cs index ec39b724ac5f..97074b66a338 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/EmailAddressValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/EmailAddressValueConverter.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; @@ -15,6 +15,11 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel cacheLevel, object? source, bool preview) => source?.ToString() ?? string.Empty; + public override object ConvertIntermediateToObject( + IPublishedElement owner, + IPublishedPropertyType propertyType, + PropertyCacheLevel cacheLevel, + object? source, + bool preview) => + source?.ToString() ?? string.Empty; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs index 7e9af9a6ebfb..a4df25df886b 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; @@ -15,6 +15,11 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, - object? source, bool preview) => source.TryConvertTo().Result; + public override object ConvertSourceToIntermediate( + IPublishedElement owner, + IPublishedPropertyType propertyType, + object? source, + bool preview) => + source.TryConvertTo() + .Result; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs index 12c02a3ce076..81f163745a03 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/LabelValueConverter.cs @@ -20,7 +20,7 @@ public override bool IsConverter(IPublishedPropertyType propertyType) public override Type GetPropertyValueType(IPublishedPropertyType propertyType) { - LabelConfiguration valueType = + LabelConfiguration? valueType = ConfigurationEditor.ConfigurationAs(propertyType.DataType.Configuration); switch (valueType?.ValueType) { @@ -43,10 +43,9 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, - object? source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) { - LabelConfiguration valueType = + LabelConfiguration? valueType = ConfigurationEditor.ConfigurationAs(propertyType.DataType.Configuration); switch (valueType?.ValueType) { @@ -59,8 +58,7 @@ public override object ConvertSourceToIntermediate(IPublishedElement owner, IPub if (source is string sourceDateTimeString) { - return DateTime.TryParse(sourceDateTimeString, CultureInfo.InvariantCulture, DateTimeStyles.None, - out DateTime dt) + return DateTime.TryParse(sourceDateTimeString, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dt) ? dt : DateTime.MinValue; } @@ -88,8 +86,7 @@ public override object ConvertSourceToIntermediate(IPublishedElement owner, IPub if (source is string sourceDecimalString) { - return decimal.TryParse(sourceDecimalString, NumberStyles.Any, CultureInfo.InvariantCulture, - out var d) + return decimal.TryParse(sourceDecimalString, NumberStyles.Any, CultureInfo.InvariantCulture, out var d) ? d : 0; } @@ -99,7 +96,7 @@ public override object ConvertSourceToIntermediate(IPublishedElement owner, IPub return Convert.ToDecimal(sourceDouble); } - return (decimal)0; + return 0M; case ValueTypes.Integer: if (source is int sourceInt) { @@ -120,7 +117,7 @@ public override object ConvertSourceToIntermediate(IPublishedElement owner, IPub return long.TryParse(sourceLongString, out var i) ? i : 0; } - return (long)0; + return 0L; default: // everything else is a string return source?.ToString() ?? string.Empty; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs index 9de4792b1adf..06269ef8e8a0 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs @@ -17,7 +17,8 @@ public class MediaPickerValueConverter : PropertyValueConverterBase private readonly IPublishedModelFactory _publishedModelFactory; private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; - public MediaPickerValueConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor, + public MediaPickerValueConverter( + IPublishedSnapshotAccessor publishedSnapshotAccessor, IPublishedModelFactory publishedModelFactory) { _publishedSnapshotAccessor = publishedSnapshotAccessor ?? @@ -39,30 +40,33 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot; - private bool IsMultipleDataType(PublishedDataType dataType) - { - MediaPickerConfiguration config = - ConfigurationEditor.ConfigurationAs(dataType.Configuration); - return config?.Multiple ?? false; - } - - public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, - object? source, bool preview) + public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) { if (source == null) { return null; } - Udi[] nodeIds = source.ToString()? + Udi[]? nodeIds = source.ToString()? .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) .Select(UdiParser.Parse) .ToArray(); return nodeIds; } - public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel cacheLevel, object? source, bool preview) + private bool IsMultipleDataType(PublishedDataType dataType) + { + MediaPickerConfiguration? config = + ConfigurationEditor.ConfigurationAs(dataType.Configuration); + return config?.Multiple ?? false; + } + + public override object? ConvertIntermediateToObject( + IPublishedElement owner, + IPublishedPropertyType propertyType, + PropertyCacheLevel cacheLevel, + object? source, + bool preview) { var isMultiple = IsMultipleDataType(propertyType.DataType); @@ -79,13 +83,12 @@ private bool IsMultipleDataType(PublishedDataType dataType) IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot(); foreach (Udi udi in udis) { - var guidUdi = udi as GuidUdi; - if (guidUdi is null) + if (udi is not GuidUdi guidUdi) { continue; } - IPublishedContent item = publishedSnapshot?.Media?.GetById(guidUdi.Guid); + IPublishedContent? item = publishedSnapshot?.Media?.GetById(guidUdi.Guid); if (item != null) { mediaItems.Add(item); diff --git a/src/Umbraco.Core/PublishedCache/DefaultCultureAccessor.cs b/src/Umbraco.Core/PublishedCache/DefaultCultureAccessor.cs index 250190db9591..4068bc4477f9 100644 --- a/src/Umbraco.Core/PublishedCache/DefaultCultureAccessor.cs +++ b/src/Umbraco.Core/PublishedCache/DefaultCultureAccessor.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Services; @@ -13,12 +13,10 @@ public class DefaultCultureAccessor : IDefaultCultureAccessor private readonly IRuntimeState _runtimeState; private GlobalSettings _options; - /// /// Initializes a new instance of the class. /// - public DefaultCultureAccessor(ILocalizationService localizationService, IRuntimeState runtimeState, - IOptionsMonitor options) + public DefaultCultureAccessor(ILocalizationService localizationService, IRuntimeState runtimeState, IOptionsMonitor options) { _localizationService = localizationService; _runtimeState = runtimeState; @@ -28,6 +26,6 @@ public DefaultCultureAccessor(ILocalizationService localizationService, IRuntime /// public string DefaultCulture => _runtimeState.Level == RuntimeLevel.Run - ? _localizationService.GetDefaultLanguageIsoCode() ?? "" // fast + ? _localizationService.GetDefaultLanguageIsoCode() ?? string.Empty // fast : _options.DefaultUILanguage; // default for install and upgrade, when the service is n/a } diff --git a/src/Umbraco.Core/PublishedCache/IDefaultCultureAccessor.cs b/src/Umbraco.Core/PublishedCache/IDefaultCultureAccessor.cs index eb529ac2e82e..583daca2f315 100644 --- a/src/Umbraco.Core/PublishedCache/IDefaultCultureAccessor.cs +++ b/src/Umbraco.Core/PublishedCache/IDefaultCultureAccessor.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PublishedCache; +namespace Umbraco.Cms.Core.PublishedCache; /// /// Gives access to the default culture. diff --git a/src/Umbraco.Core/PublishedCache/IDomainCache.cs b/src/Umbraco.Core/PublishedCache/IDomainCache.cs index 6fdb0be995c2..41443ef1f6f6 100644 --- a/src/Umbraco.Core/PublishedCache/IDomainCache.cs +++ b/src/Umbraco.Core/PublishedCache/IDomainCache.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Routing; namespace Umbraco.Cms.Core.PublishedCache; diff --git a/src/Umbraco.Core/PublishedCache/IPublishedCache.cs b/src/Umbraco.Core/PublishedCache/IPublishedCache.cs index 673760908020..0ee2ca38ed4b 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedCache.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedCache.cs @@ -1,4 +1,4 @@ -using System.Xml.XPath; +using System.Xml.XPath; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Xml; diff --git a/src/Umbraco.Core/PublishedCache/IPublishedContentCache.cs b/src/Umbraco.Core/PublishedCache/IPublishedContentCache.cs index c753751d5018..7526226302cf 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedContentCache.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedContentCache.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Cms.Core.PublishedCache; @@ -10,6 +10,7 @@ public interface IPublishedContentCache : IPublishedCache /// A value indicating whether to consider unpublished content. /// The route /// A value forcing the HideTopLevelNode setting. + /// the culture /// The content, or null. /// /// @@ -30,6 +31,7 @@ public interface IPublishedContentCache : IPublishedCache /// /// The route /// A value forcing the HideTopLevelNode setting. + /// The culture /// The content, or null. /// /// @@ -50,6 +52,7 @@ public interface IPublishedContentCache : IPublishedCache /// /// A value indicating whether to consider unpublished content. /// The content unique identifier. + /// The culture /// A special string formatted route path. /// /// @@ -65,6 +68,7 @@ public interface IPublishedContentCache : IPublishedCache /// Gets the route for a content identified by its unique identifier. /// /// The content unique identifier. + /// The culture /// A special string formatted route path. /// Considers published or unpublished content depending on defaults. /// diff --git a/src/Umbraco.Core/PublishedCache/IPublishedMediaCache.cs b/src/Umbraco.Core/PublishedCache/IPublishedMediaCache.cs index 01c72072a712..b0fd46748ed4 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedMediaCache.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedMediaCache.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PublishedCache; +namespace Umbraco.Cms.Core.PublishedCache; public interface IPublishedMediaCache : IPublishedCache { diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshot.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshot.cs index 72e977326449..43e629170199 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshot.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshot.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Cache; namespace Umbraco.Cms.Core.PublishedCache; diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotStatus.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotStatus.cs index 3991681bd181..1eb09c8144f4 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotStatus.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotStatus.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PublishedCache; +namespace Umbraco.Cms.Core.PublishedCache; /// /// Returns the currents status for nucache diff --git a/src/Umbraco.Core/PublishedCache/ITagQuery.cs b/src/Umbraco.Core/PublishedCache/ITagQuery.cs index c823db305f7f..2deaf7510843 100644 --- a/src/Umbraco.Core/PublishedCache/ITagQuery.cs +++ b/src/Umbraco.Core/PublishedCache/ITagQuery.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Cms.Core.PublishedCache; @@ -48,8 +48,7 @@ public interface ITagQuery /// /// Gets all tags attached to an entity via a property. /// - IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string? group = null, - string? culture = null); + IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string? group = null, string? culture = null); /// /// Gets all tags attached to an entity. diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContent.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContent.cs index 81bdf5223cb2..0659e835a331 100644 --- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContent.cs +++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContent.cs @@ -28,17 +28,17 @@ public InternalPublishedContent(IPublishedContentType contentType) public IEnumerable? ChildIds { get; set; } + public int Id { get; set; } + public object? this[string alias] { get { - IPublishedProperty property = GetProperty(alias); + IPublishedProperty? property = GetProperty(alias); return property == null || property.HasValue() == false ? null : property.GetValue(); } } - public int Id { get; set; } - public Guid Key { get; set; } public int? TemplateId { get; set; } @@ -65,12 +65,12 @@ public object? this[string alias] public PublishedItemType ItemType => PublishedItemType.Content; + public IPublishedContent? Parent { get; set; } + public bool IsDraft(string? culture = null) => false; public bool IsPublished(string? culture = null) => true; - public IPublishedContent? Parent { get; set; } - public IEnumerable? Children { get; set; } public IEnumerable? ChildrenForAllCultures => Children; @@ -82,11 +82,6 @@ public object? this[string alias] public IPublishedProperty? GetProperty(string alias) => Properties?.FirstOrDefault(p => p.Alias.InvariantEquals(alias)); - private Dictionary GetCultures() => new() - { - {string.Empty, new PublishedCultureInfo(string.Empty, Name, UrlSegment, UpdateDate)} - }; - public IPublishedProperty? GetProperty(string alias, bool recurse) { IPublishedProperty? property = GetProperty(alias); @@ -104,4 +99,9 @@ public object? this[string alias] return property; } + + private Dictionary GetCultures() => new() + { + { string.Empty, new PublishedCultureInfo(string.Empty, Name, UrlSegment, UpdateDate) }, + }; } diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs index ef07b5d3b7d4..e4e9010f5b94 100644 --- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs +++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs @@ -16,8 +16,7 @@ public InternalPublishedContentCache() { } - public IPublishedContent GetByRoute(bool preview, string route, bool? hideTopLevelNode = null, - string? culture = null) => throw new NotImplementedException(); + public IPublishedContent GetByRoute(bool preview, string route, bool? hideTopLevelNode = null, string? culture = null) => throw new NotImplementedException(); public IPublishedContent GetByRoute(string route, bool? hideTopLevelNode = null, string? culture = null) => throw new NotImplementedException(); @@ -66,7 +65,6 @@ public override IEnumerable public override IEnumerable GetByContentType(IPublishedContentType contentType) => throw new NotImplementedException(); - //public void Add(InternalPublishedContent content) => _content[content.Id] = content.CreateModel(Mock.Of()); - + // public void Add(InternalPublishedContent content) => _content[content.Id] = content.CreateModel(Mock.Of()); public void Clear() => _content.Clear(); } diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs index a741f4b7ff96..d9437e6b8c2b 100644 --- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs +++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs @@ -14,6 +14,7 @@ public class InternalPublishedProperty : IPublishedProperty public bool SolidHasValue { get; set; } public object? SolidXPathValue { get; set; } + public IPublishedPropertyType PropertyType { get; set; } = null!; public string Alias { get; set; } = string.Empty; diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshot.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshot.cs index 56a7da6adc92..015962b5aafd 100644 --- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshot.cs +++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshot.cs @@ -8,6 +8,7 @@ namespace Umbraco.Cms.Core.PublishedCache.Internal; public sealed class InternalPublishedSnapshot : IPublishedSnapshot { public InternalPublishedContentCache InnerContentCache { get; } = new(); + public InternalPublishedContentCache InnerMediaCache { get; } = new(); public IPublishedContentCache Content => InnerContentCache; @@ -18,11 +19,11 @@ public sealed class InternalPublishedSnapshot : IPublishedSnapshot public IDomainCache? Domains => null; + public IAppCache? SnapshotCache => null; + public IDisposable ForcedPreview(bool forcedPreview, Action? callback = null) => throw new NotImplementedException(); - public IAppCache? SnapshotCache => null; - public IAppCache? ElementsCache => null; public void Dispose() diff --git a/src/Umbraco.Core/Routing/AliasUrlProvider.cs b/src/Umbraco.Core/Routing/AliasUrlProvider.cs index a70add833cfd..e053a23703f0 100644 --- a/src/Umbraco.Core/Routing/AliasUrlProvider.cs +++ b/src/Umbraco.Core/Routing/AliasUrlProvider.cs @@ -17,8 +17,11 @@ public class AliasUrlProvider : IUrlProvider private readonly UriUtility _uriUtility; private RequestHandlerSettings _requestConfig; - public AliasUrlProvider(IOptionsMonitor requestConfig, ISiteDomainMapper siteDomainMapper, - UriUtility uriUtility, IPublishedValueFallback publishedValueFallback, + public AliasUrlProvider( + IOptionsMonitor requestConfig, + ISiteDomainMapper siteDomainMapper, + UriUtility uriUtility, + IPublishedValueFallback publishedValueFallback, IUmbracoContextAccessor umbracoContextAccessor) { _requestConfig = requestConfig.CurrentValue; @@ -33,7 +36,6 @@ public AliasUrlProvider(IOptionsMonitor requestConfig, I // note - at the moment we seem to accept pretty much anything as an alias // without any form of validation ... could even prob. kill the XPath ... // ok, this is somewhat experimental and is NOT enabled by default - #region GetUrl /// @@ -47,7 +49,6 @@ public AliasUrlProvider(IOptionsMonitor requestConfig, I /// /// Gets the other URLs of a published content. /// - /// The Umbraco context. /// The published content id. /// The current absolute URL. /// The other URLs for the published content. @@ -60,7 +61,7 @@ public AliasUrlProvider(IOptionsMonitor requestConfig, I public IEnumerable GetOtherUrls(int id, Uri current) { IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - IPublishedContent node = umbracoContext.Content?.GetById(id); + IPublishedContent? node = umbracoContext.Content?.GetById(id); if (node == null) { yield break; @@ -72,17 +73,15 @@ public IEnumerable GetOtherUrls(int id, Uri current) } // look for domains, walking up the tree - IPublishedContent n = node; - IEnumerable domainUris = DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, - _siteDomainMapper, n.Id, current, false); + IPublishedContent? n = node; + IEnumerable? domainUris = DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, current, false); while (domainUris == null && n != null) // n is null at root { // move to parent node n = n.Parent; domainUris = n == null ? null - : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, - current, false); + : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, current, false); } // determine whether the alias property varies @@ -130,8 +129,7 @@ public IEnumerable GetOtherUrls(int id, Uri current) } var umbracoUrlName = varies - ? node.Value(_publishedValueFallback, Constants.Conventions.Content.UrlAlias, - domainUri.Culture) + ? node.Value(_publishedValueFallback, Constants.Conventions.Content.UrlAlias, domainUri.Culture) : node.Value(_publishedValueFallback, Constants.Conventions.Content.UrlAlias); var aliases = umbracoUrlName?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); @@ -145,7 +143,8 @@ public IEnumerable GetOtherUrls(int id, Uri current) { var path = "/" + alias; var uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)); - yield return UrlInfo.Url(_uriUtility.UriFromUmbraco(uri, _requestConfig).ToString(), + yield return UrlInfo.Url( + _uriUtility.UriFromUmbraco(uri, _requestConfig).ToString(), domainUri.Culture); } } diff --git a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs index e5bdd68dec00..c54f111ffd4d 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs @@ -44,17 +44,16 @@ public ContentFinderByIdPath( /// /// The PublishedRequest. /// A value indicating whether an Umbraco document was found and assigned. - public async Task TryFindContent(IPublishedRequestBuilder frequest) + public Task TryFindContent(IPublishedRequestBuilder frequest) { - if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext umbracoContext)) + if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext)) { - return false; + return Task.FromResult(false); } - if (umbracoContext == null || (umbracoContext != null && umbracoContext.InPreviewMode == false && - _webRoutingSettings.DisableFindContentByIdPath)) + if (umbracoContext.InPreviewMode == false && _webRoutingSettings.DisableFindContentByIdPath) { - return false; + return Task.FromResult(false); } IPublishedContent? node = null; @@ -65,7 +64,7 @@ public async Task TryFindContent(IPublishedRequestBuilder frequest) // no id if "/" if (path != "/") { - var noSlashPath = path.Substring(1); + var noSlashPath = path[1..]; if (int.TryParse(noSlashPath, NumberStyles.Integer, CultureInfo.InvariantCulture, out nodeId) == false) { @@ -79,7 +78,7 @@ public async Task TryFindContent(IPublishedRequestBuilder frequest) _logger.LogDebug("Id={NodeId}", nodeId); } - node = umbracoContext?.Content?.GetById(nodeId); + node = umbracoContext.Content?.GetById(nodeId); if (node != null) { @@ -113,6 +112,6 @@ public async Task TryFindContent(IPublishedRequestBuilder frequest) } } - return node != null; + return Task.FromResult(node != null); } } diff --git a/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs b/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs index e8e595e2c9b6..772155177744 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByPageIdQuery.cs @@ -27,25 +27,24 @@ public ContentFinderByPageIdQuery(IRequestAccessor requestAccessor, IUmbracoCont } /// - public async Task TryFindContent(IPublishedRequestBuilder frequest) + public Task TryFindContent(IPublishedRequestBuilder frequest) { - if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext umbracoContext)) + if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext)) { - return false; + return Task.FromResult(false); } - if (int.TryParse(_requestAccessor.GetRequestValue("umbPageID"), NumberStyles.Integer, - CultureInfo.InvariantCulture, out var pageId)) + if (int.TryParse(_requestAccessor.GetRequestValue("umbPageID"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var pageId)) { IPublishedContent? doc = umbracoContext.Content?.GetById(pageId); if (doc != null) { frequest.SetPublishedContent(doc); - return true; + return Task.FromResult(true); } } - return false; + return Task.FromResult(false); } } diff --git a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs index 1805b7e0f9fa..99103d8cb6b3 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByRedirectUrl.cs @@ -45,11 +45,11 @@ public ContentFinderByRedirectUrl( /// Optionally, can also assign the template or anything else on the document request, although that is not /// required. /// - public async Task TryFindContent(IPublishedRequestBuilder frequest) + public Task TryFindContent(IPublishedRequestBuilder frequest) { - if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext umbracoContext)) + if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext)) { - return false; + return Task.FromResult(false); } var route = frequest.Domain != null @@ -66,7 +66,7 @@ public async Task TryFindContent(IPublishedRequestBuilder frequest) _logger.LogDebug("No match for route: {Route}", route); } - return false; + return Task.FromResult(false); } IPublishedContent? content = umbracoContext.Content?.GetById(redirectUrl.ContentId); @@ -75,19 +75,17 @@ public async Task TryFindContent(IPublishedRequestBuilder frequest) { if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("Route {Route} matches content {ContentId} which has no URL.", route, - redirectUrl.ContentId); + _logger.LogDebug("Route {Route} matches content {ContentId} which has no URL.", route, redirectUrl.ContentId); } - return false; + return Task.FromResult(false); } // Appending any querystring from the incoming request to the redirect URL url = string.IsNullOrEmpty(frequest.Uri.Query) ? url : url + frequest.Uri.Query; if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("Route {Route} matches content {ContentId} with URL '{Url}', redirecting.", route, - content?.Id, url); + _logger.LogDebug("Route {Route} matches content {ContentId} with URL '{Url}', redirecting.", route, content?.Id, url); } frequest @@ -98,9 +96,9 @@ public async Task TryFindContent(IPublishedRequestBuilder frequest) // Setting automatic 301 redirects to not be cached because browsers cache these very aggressively which then leads // to problems if you rename a page back to it's original name or create a new page with the original name .SetNoCacheHeader(true) - .SetCacheExtensions(new List {"no-store, must-revalidate"}) - .SetHeaders(new Dictionary {{"Pragma", "no-cache"}, {"Expires", "0"}}); + .SetCacheExtensions(new List { "no-store, must-revalidate" }) + .SetHeaders(new Dictionary { { "Pragma", "no-cache" }, { "Expires", "0" } }); - return true; + return Task.FromResult(true); } } diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrl.cs b/src/Umbraco.Core/Routing/ContentFinderByUrl.cs index e408b1853fa1..d2b2a564a70b 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrl.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrl.cs @@ -34,11 +34,11 @@ public ContentFinderByUrl(ILogger logger, IUmbracoContextAcc /// /// The PublishedRequest. /// A value indicating whether an Umbraco document was found and assigned. - public virtual async Task TryFindContent(IPublishedRequestBuilder frequest) + public virtual Task TryFindContent(IPublishedRequestBuilder frequest) { - if (!UmbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext umbracoContext)) + if (!UmbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? _)) { - return false; + return Task.FromResult(false); } string route; @@ -53,7 +53,7 @@ public virtual async Task TryFindContent(IPublishedRequestBuilder frequest } IPublishedContent? node = FindContent(frequest, route); - return node != null; + return Task.FromResult(node != null); } /// @@ -62,7 +62,7 @@ public virtual async Task TryFindContent(IPublishedRequestBuilder frequest /// The document node, or null. protected IPublishedContent? FindContent(IPublishedRequestBuilder docreq, string route) { - if (!UmbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext umbracoContext)) + if (!UmbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext)) { return null; } diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs b/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs index 88b8e0088cfb..3a04c2cb5bad 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs @@ -43,11 +43,11 @@ public ContentFinderByUrlAlias( /// /// The PublishedRequest. /// A value indicating whether an Umbraco document was found and assigned. - public async Task TryFindContent(IPublishedRequestBuilder frequest) + public Task TryFindContent(IPublishedRequestBuilder frequest) { - if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext umbracoContext)) + if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext)) { - return false; + return Task.FromResult(false); } IPublishedContent? node = null; @@ -56,7 +56,7 @@ public async Task TryFindContent(IPublishedRequestBuilder frequest) if (frequest.Uri.AbsolutePath != "/") { node = FindContentByAlias( - umbracoContext!.Content, + umbracoContext.Content, frequest.Domain != null ? frequest.Domain.ContentId : 0, frequest.Culture, frequest.AbsolutePathDecoded); @@ -66,17 +66,16 @@ public async Task TryFindContent(IPublishedRequestBuilder frequest) frequest.SetPublishedContent(node); if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("Path '{UriAbsolutePath}' is an alias for id={PublishedContentId}", - frequest.Uri.AbsolutePath, node.Id); + _logger.LogDebug( + "Path '{UriAbsolutePath}' is an alias for id={PublishedContentId}", frequest.Uri.AbsolutePath, node.Id); } } } - return node != null; + return Task.FromResult(node != null); } - private IPublishedContent? FindContentByAlias(IPublishedContentCache? cache, int rootNodeId, string? culture, - string alias) + private IPublishedContent? FindContentByAlias(IPublishedContentCache? cache, int rootNodeId, string? culture, string alias) { if (alias == null) { @@ -109,7 +108,7 @@ bool IsMatch(IPublishedContent c, string a1, string a2) } IPublishedProperty? p = c.GetProperty(propertyAlias); - var varies = p!.PropertyType?.VariesByCulture(); + var varies = p?.PropertyType?.VariesByCulture(); string? v; if (varies ?? false) { diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs b/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs index 0d3e7589785f..200a7d71438e 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs @@ -55,7 +55,7 @@ public ContentFinderByUrlAndTemplate( /// The PublishedRequest. /// A value indicating whether an Umbraco document was found and assigned. /// If successful, also assigns the template. - public override async Task TryFindContent(IPublishedRequestBuilder frequest) + public override Task TryFindContent(IPublishedRequestBuilder frequest) { var path = frequest.AbsolutePathDecoded; @@ -72,13 +72,13 @@ public override async Task TryFindContent(IPublishedRequestBuilder freques _logger.LogDebug("No template in path '/'"); } - return false; + return Task.FromResult(false); } // look for template in last position var pos = path.LastIndexOf('/'); - var templateAlias = path.Substring(pos + 1); - path = pos == 0 ? "/" : path.Substring(0, pos); + var templateAlias = path[(pos + 1)..]; + path = pos == 0 ? "/" : path[..pos]; ITemplate? template = _fileService.GetTemplate(templateAlias); @@ -89,7 +89,7 @@ public override async Task TryFindContent(IPublishedRequestBuilder freques _logger.LogDebug("Not a valid template: '{TemplateAlias}'", templateAlias); } - return false; + return Task.FromResult(false); } if (_logger.IsEnabled(LogLevel.Debug)) @@ -108,20 +108,20 @@ public override async Task TryFindContent(IPublishedRequestBuilder freques _logger.LogDebug("Not a valid route to node: '{Route}'", route); } - return false; + return Task.FromResult(false); } // IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings if (!node.IsAllowedTemplate(_contentTypeService, _webRoutingSettings, template.Id)) { - _logger.LogWarning("Alternative template '{TemplateAlias}' is not allowed on node {NodeId}.", - template.Alias, node.Id); + _logger.LogWarning( + "Alternative template '{TemplateAlias}' is not allowed on node {NodeId}.", template.Alias, node.Id); frequest.SetPublishedContent(null); // clear - return false; + return Task.FromResult(false); } // got it frequest.SetTemplate(template); - return true; + return Task.FromResult(true); } } diff --git a/src/Umbraco.Core/Routing/ContentFinderCollection.cs b/src/Umbraco.Core/Routing/ContentFinderCollection.cs index 5e705ff81f00..cc3b711d9882 100644 --- a/src/Umbraco.Core/Routing/ContentFinderCollection.cs +++ b/src/Umbraco.Core/Routing/ContentFinderCollection.cs @@ -4,7 +4,8 @@ namespace Umbraco.Cms.Core.Routing; public class ContentFinderCollection : BuilderCollectionBase { - public ContentFinderCollection(Func> items) : base(items) + public ContentFinderCollection(Func> items) + : base(items) { } } diff --git a/src/Umbraco.Core/Routing/ContentFinderCollectionBuilder.cs b/src/Umbraco.Core/Routing/ContentFinderCollectionBuilder.cs index e2c58ff2ae7b..f82b0720fa85 100644 --- a/src/Umbraco.Core/Routing/ContentFinderCollectionBuilder.cs +++ b/src/Umbraco.Core/Routing/ContentFinderCollectionBuilder.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Routing; diff --git a/src/Umbraco.Core/Routing/DefaultMediaUrlProvider.cs b/src/Umbraco.Core/Routing/DefaultMediaUrlProvider.cs index 92bfd968dc1a..d1c79783f043 100644 --- a/src/Umbraco.Core/Routing/DefaultMediaUrlProvider.cs +++ b/src/Umbraco.Core/Routing/DefaultMediaUrlProvider.cs @@ -18,10 +18,14 @@ public DefaultMediaUrlProvider(MediaUrlGeneratorCollection mediaPathGenerators, } /// - public virtual UrlInfo? GetMediaUrl(IPublishedContent content, - string propertyAlias, UrlMode mode, string? culture, Uri current) + public virtual UrlInfo? GetMediaUrl( + IPublishedContent content, + string propertyAlias, + UrlMode mode, + string? culture, + Uri current) { - IPublishedProperty prop = content.GetProperty(propertyAlias); + IPublishedProperty? prop = content.GetProperty(propertyAlias); // get the raw source value since this is what is used by IDataEditorWithMediaPath for processing var value = prop?.GetSourceValue(culture); @@ -30,7 +34,7 @@ public DefaultMediaUrlProvider(MediaUrlGeneratorCollection mediaPathGenerators, return null; } - IPublishedPropertyType propType = prop?.PropertyType; + IPublishedPropertyType? propType = prop?.PropertyType; if (_mediaPathGenerators.TryGetMediaPath(propType?.EditorAlias, value, out var path)) { diff --git a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs index 09baebf74478..18dcbeda2953 100644 --- a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs @@ -25,10 +25,18 @@ public class DefaultUrlProvider : IUrlProvider private RequestHandlerSettings _requestSettings; [Obsolete("Use ctor with all parameters")] - public DefaultUrlProvider(IOptionsMonitor requestSettings, + public DefaultUrlProvider( + IOptionsMonitor requestSettings, ILogger logger, - ISiteDomainMapper siteDomainMapper, IUmbracoContextAccessor umbracoContextAccessor, UriUtility uriUtility) - : this(requestSettings, logger, siteDomainMapper, umbracoContextAccessor, uriUtility, + ISiteDomainMapper siteDomainMapper, + IUmbracoContextAccessor umbracoContextAccessor, + UriUtility uriUtility) + : this( + requestSettings, + logger, + siteDomainMapper, + umbracoContextAccessor, + uriUtility, StaticServiceProvider.Instance.GetRequiredService()) { } @@ -78,15 +86,15 @@ public virtual IEnumerable GetOtherUrls(int id, Uri current) // look for domains, walking up the tree IPublishedContent? n = node; IEnumerable? domainUris = - DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, - current, false); - while (domainUris == null && n != null) // n is null at root + DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, current, false); + + // n is null at root + while (domainUris == null && n != null) { n = n.Parent; // move to parent node domainUris = n == null ? null - : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, - current); + : DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, current); } // no domains = exit @@ -108,7 +116,7 @@ public virtual IEnumerable GetOtherUrls(int id, Uri current) // need to strip off the leading ID for the route if it exists (occurs if the route is for a node with a domain assigned) var pos = route.IndexOf('/'); - var path = pos == 0 ? route : route.Substring(pos); + var path = pos == 0 ? route : route[pos..]; var uri = new Uri(CombinePaths(d.Uri.GetLeftPart(UriPartial.Path), path)); uri = _uriUtility.UriFromUmbraco(uri, _requestSettings); @@ -129,6 +137,7 @@ public virtual IEnumerable GetOtherUrls(int id, Uri current) } IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + // will not use cache if previewing var route = umbracoContext.Content?.GetRouteById(content.Id, culture); @@ -154,11 +163,15 @@ public virtual IEnumerable GetOtherUrls(int id, Uri current) // extract domainUri and path // route is / or / var pos = route.IndexOf('/'); - var path = pos == 0 ? route : route.Substring(pos); + var path = pos == 0 ? route : route[pos..]; DomainAndUri? domainUri = pos == 0 ? null - : DomainUtilities.DomainForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, - int.Parse(route.Substring(0, pos), CultureInfo.InvariantCulture), current, culture); + : DomainUtilities.DomainForNode( + umbracoContext.PublishedSnapshot.Domains, + _siteDomainMapper, + int.Parse(route[..pos], CultureInfo.InvariantCulture), + current, + culture); var defaultCulture = _localizationService.GetDefaultLanguageIsoCode(); if (domainUri is not null || string.IsNullOrEmpty(culture) || @@ -180,7 +193,6 @@ private Uri AssembleUrl(DomainAndUri? domainUri, string path, Uri current, UrlMo Uri uri; // ignore vdir at that point, UriFromUmbraco will do it - if (domainUri == null) // no domain was found { if (current == null) @@ -205,7 +217,7 @@ private Uri AssembleUrl(DomainAndUri? domainUri, string path, Uri current, UrlMo { if (mode == UrlMode.Auto) { - //this check is a little tricky, we can't just compare domains + // this check is a little tricky, we can't just compare domains if (current != null && domainUri.Uri.GetLeftPart(UriPartial.Authority) == current.GetLeftPart(UriPartial.Authority)) { diff --git a/src/Umbraco.Core/Routing/DomainAndUri.cs b/src/Umbraco.Core/Routing/DomainAndUri.cs index 31d2eea7ffbd..c5f9497d77b2 100644 --- a/src/Umbraco.Core/Routing/DomainAndUri.cs +++ b/src/Umbraco.Core/Routing/DomainAndUri.cs @@ -1,4 +1,4 @@ -using Umbraco.Extensions; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.Routing; @@ -33,7 +33,8 @@ public DomainAndUri(Domain domain, Uri currentUri) { throw new ArgumentException( $"Failed to parse invalid domain: node id={domain.ContentId}, hostname=\"{Name.ToCSharpString()}\"." - + " Hostname should be a valid uri.", nameof(domain)); + + " Hostname should be a valid uri.", + nameof(domain)); } } diff --git a/src/Umbraco.Core/Routing/DomainUtilities.cs b/src/Umbraco.Core/Routing/DomainUtilities.cs index 96c585e93ce7..42e945f47968 100644 --- a/src/Umbraco.Core/Routing/DomainUtilities.cs +++ b/src/Umbraco.Core/Routing/DomainUtilities.cs @@ -28,8 +28,12 @@ public static class DomainUtilities /// a culture to that document. /// /// - public static string? GetCultureFromDomains(int contentId, string contentPath, Uri? current, - IUmbracoContext umbracoContext, ISiteDomainMapper siteDomainMapper) + public static string? GetCultureFromDomains( + int contentId, + string contentPath, + Uri? current, + IUmbracoContext umbracoContext, + ISiteDomainMapper siteDomainMapper) { if (umbracoContext == null) { @@ -52,13 +56,12 @@ public static class DomainUtilities } var pos = route.IndexOf('/'); - DomainAndUri domain = pos == 0 + DomainAndUri? domain = pos == 0 ? null - : DomainForNode(umbracoContext.Domains, siteDomainMapper, - int.Parse(route.Substring(0, pos), CultureInfo.InvariantCulture), current); + : DomainForNode(umbracoContext.Domains, siteDomainMapper, int.Parse(route[..pos], CultureInfo.InvariantCulture), current); var rootContentId = domain?.ContentId ?? -1; - Domain wcDomain = FindWildcardDomainInPath(umbracoContext.Domains?.GetAll(true), contentPath, rootContentId); + Domain? wcDomain = FindWildcardDomainInPath(umbracoContext.Domains?.GetAll(true), contentPath, rootContentId); if (wcDomain != null) { @@ -75,91 +78,6 @@ public static class DomainUtilities #endregion - #region Domain for Document - - /// - /// Finds the domain for the specified node, if any, that best matches a specified uri. - /// - /// A domain cache. - /// The site domain helper. - /// The node identifier. - /// The uri, or null. - /// The culture, or null. - /// The domain and its uri, if any, that best matches the specified uri and culture, else null. - /// - /// - /// If at least a domain is set on the node then the method returns the domain that - /// best matches the specified uri and culture, else it returns null. - /// - /// - /// If culture is null, uses the default culture for the installation instead. Otherwise, - /// will try with the specified culture, else return null. - /// - /// - internal static DomainAndUri? DomainForNode(IDomainCache? domainCache, ISiteDomainMapper siteDomainMapper, - int nodeId, Uri current, string? culture = null) - { - // be safe - if (nodeId <= 0) - { - return null; - } - - // get the domains on that node - Domain[] domains = domainCache?.GetAssigned(nodeId).ToArray(); - - // none? - if (domains is null || domains.Length == 0) - { - return null; - } - - // else filter - // it could be that none apply (due to culture) - return SelectDomain(domains, current, culture, domainCache?.DefaultCulture, siteDomainMapper.MapDomain); - } - - /// - /// Find the domains for the specified node, if any, that match a specified uri. - /// - /// A domain cache. - /// The site domain helper. - /// The node identifier. - /// The uri, or null. - /// A value indicating whether to exclude the current/default domain. True by default. - /// The domains and their uris, that match the specified uri, else null. - /// - /// If at least a domain is set on the node then the method returns the domains that - /// best match the specified uri, else it returns null. - /// - internal static IEnumerable? DomainsForNode(IDomainCache? domainCache, - ISiteDomainMapper siteDomainMapper, int nodeId, Uri current, bool excludeDefault = true) - { - // be safe - if (nodeId <= 0) - { - return null; - } - - // get the domains on that node - Domain[] domains = domainCache?.GetAssigned(nodeId).ToArray(); - - // none? - if (domains is null || domains.Length == 0) - { - return null; - } - - // get the domains and their uris - DomainAndUri[] domainAndUris = SelectDomains(domains, current).ToArray(); - - // filter - return siteDomainMapper.MapDomains(domainAndUris, current, excludeDefault, null, domainCache?.DefaultCulture) - .ToArray(); - } - - #endregion - #region Selects Domain(s) /// @@ -184,7 +102,10 @@ public static class DomainUtilities /// /// The filter, if any, will be called only with a non-empty argument, and _must_ return something. /// - public static DomainAndUri? SelectDomain(IEnumerable? domains, Uri uri, string? culture = null, + public static DomainAndUri? SelectDomain( + IEnumerable? domains, + Uri uri, + string? culture = null, string? defaultCulture = null, Func, Uri, string?, string?, DomainAndUri?>? filter = null) { @@ -219,7 +140,7 @@ public static class DomainUtilities // if a culture is specified, then try to get domains for that culture // (else cultureDomains will be null) // do NOT specify a default culture, else it would pick those domains - IReadOnlyCollection cultureDomains = SelectByCulture(domainsAndUris, culture, null); + IReadOnlyCollection? cultureDomains = SelectByCulture(domainsAndUris, culture, null); IReadOnlyCollection considerForBaseDomains = domainsAndUris; if (cultureDomains != null) { @@ -243,21 +164,169 @@ public static class DomainUtilities // either restricting on cultureDomains, or on all domains if (filter != null) { - DomainAndUri domainAndUri = filter(cultureDomains ?? domainsAndUris, uri, culture, defaultCulture); + DomainAndUri? domainAndUri = filter(cultureDomains ?? domainsAndUris, uri, culture, defaultCulture); return domainAndUri; } return null; } + /// + /// Parses a domain name into a URI. + /// + /// The domain name to parse + /// + /// The currently requested URI. If the domain name is relative, the authority of URI will be + /// used. + /// + /// The domain name as a URI + public static Uri ParseUriFromDomainName(string domainName, Uri currentUri) + { + // turn "/en" into "http://whatever.com/en" so it becomes a parseable uri + var name = domainName.StartsWith("/") && currentUri != null + ? currentUri.GetLeftPart(UriPartial.Authority) + domainName + : domainName; + var scheme = currentUri?.Scheme ?? Uri.UriSchemeHttp; + return new Uri(UriUtilityCore.TrimPathEndSlash(UriUtilityCore.StartWithScheme(name, scheme))); + } + + /// + /// Gets the deepest wildcard Domain, if any, from a group of Domains, in a node path. + /// + /// The domains. + /// The node path eg '-1,1234,5678'. + /// The current domain root node identifier, or null. + /// The deepest wildcard Domain in the path, or null. + /// Looks _under_ rootNodeId but not _at_ rootNodeId. + public static Domain? FindWildcardDomainInPath(IEnumerable? domains, string path, int? rootNodeId) + { + var stopNodeId = rootNodeId ?? -1; + + return path.Split(Constants.CharArrays.Comma) + .Reverse() + .Select(s => int.Parse(s, CultureInfo.InvariantCulture)) + .TakeWhile(id => id != stopNodeId) + .Select(id => domains?.FirstOrDefault(d => d.ContentId == id && d.IsWildcard)) + .FirstOrDefault(domain => domain != null); + } + + #endregion + + #region Domain for Document + + /// + /// Finds the domain for the specified node, if any, that best matches a specified uri. + /// + /// A domain cache. + /// The site domain helper. + /// The node identifier. + /// The uri, or null. + /// The culture, or null. + /// The domain and its uri, if any, that best matches the specified uri and culture, else null. + /// + /// + /// If at least a domain is set on the node then the method returns the domain that + /// best matches the specified uri and culture, else it returns null. + /// + /// + /// If culture is null, uses the default culture for the installation instead. Otherwise, + /// will try with the specified culture, else return null. + /// + /// + internal static DomainAndUri? DomainForNode( + IDomainCache? domainCache, + ISiteDomainMapper siteDomainMapper, + int nodeId, + Uri current, + string? culture = null) + { + // be safe + if (nodeId <= 0) + { + return null; + } + + // get the domains on that node + Domain[]? domains = domainCache?.GetAssigned(nodeId).ToArray(); + + // none? + if (domains is null || domains.Length == 0) + { + return null; + } + + // else filter + // it could be that none apply (due to culture) + return SelectDomain(domains, current, culture, domainCache?.DefaultCulture, siteDomainMapper.MapDomain); + } + + /// + /// Find the domains for the specified node, if any, that match a specified uri. + /// + /// A domain cache. + /// The site domain helper. + /// The node identifier. + /// The uri, or null. + /// A value indicating whether to exclude the current/default domain. True by default. + /// The domains and their uris, that match the specified uri, else null. + /// + /// If at least a domain is set on the node then the method returns the domains that + /// best match the specified uri, else it returns null. + /// + internal static IEnumerable? DomainsForNode( + IDomainCache? domainCache, + ISiteDomainMapper siteDomainMapper, + int nodeId, + Uri current, + bool excludeDefault = true) + { + // be safe + if (nodeId <= 0) + { + return null; + } + + // get the domains on that node + Domain[]? domains = domainCache?.GetAssigned(nodeId).ToArray(); + + // none? + if (domains is null || domains.Length == 0) + { + return null; + } + + // get the domains and their uris + DomainAndUri[] domainAndUris = SelectDomains(domains, current).ToArray(); + + // filter + return siteDomainMapper.MapDomains(domainAndUris, current, excludeDefault, null, domainCache?.DefaultCulture) + .ToArray(); + } + + /// + /// Selects the domains that match a specified uri, from a set of domains. + /// + /// The domains. + /// The uri, or null. + /// The domains and their normalized uris, that match the specified uri. + internal static IEnumerable SelectDomains(IEnumerable domains, Uri uri) => + + // TODO: where are we matching ?!!? + domains + .Where(d => d.IsWildcard == false) + .Select(d => new DomainAndUri(d, uri)) + .OrderByDescending(d => d.Uri.ToString()); + private static bool IsBaseOf(DomainAndUri domain, Uri uri) => domain.Uri.EndPathWithSlash().IsBaseOf(uri); private static bool MatchesCulture(DomainAndUri domain, string? culture) => culture == null || domain.Culture.InvariantEquals(culture); - private static IReadOnlyCollection SelectByBase(IReadOnlyCollection domainsAndUris, - Uri uri, string? culture) + private static IReadOnlyCollection SelectByBase( + IReadOnlyCollection domainsAndUris, + Uri uri, + string? culture) { // look for domains that would be the base of the uri // ie current is www.example.com/foo/bar, look for domain www.example.com @@ -276,11 +345,12 @@ private static IReadOnlyCollection SelectByBase(IReadOnlyCollectio return baseDomains; } - private static IReadOnlyCollection? SelectByCulture(IReadOnlyCollection domainsAndUris, - string? culture, string? defaultCulture) + private static IReadOnlyCollection? SelectByCulture( + IReadOnlyCollection domainsAndUris, + string? culture, + string? defaultCulture) { // we try our best to match cultures, but may end with a bogus domain - if (culture != null) // try the supplied culture { var cultureDomains = domainsAndUris.Where(x => x.Culture.InvariantEquals(culture)).ToList(); @@ -302,13 +372,11 @@ private static IReadOnlyCollection SelectByBase(IReadOnlyCollectio return null; } - private static DomainAndUri GetByCulture(IReadOnlyCollection domainsAndUris, string? culture, - string? defaultCulture) + private static DomainAndUri GetByCulture(IReadOnlyCollection domainsAndUris, string? culture, string? defaultCulture) { DomainAndUri? domainAndUri; // we try our best to match cultures, but may end with a bogus domain - if (culture != null) // try the supplied culture { domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)); @@ -330,38 +398,6 @@ private static DomainAndUri GetByCulture(IReadOnlyCollection domai return domainsAndUris.First(); // what else? } - /// - /// Selects the domains that match a specified uri, from a set of domains. - /// - /// The domains. - /// The uri, or null. - /// The domains and their normalized uris, that match the specified uri. - internal static IEnumerable SelectDomains(IEnumerable domains, Uri uri) => - // TODO: where are we matching ?!!? - domains - .Where(d => d.IsWildcard == false) - .Select(d => new DomainAndUri(d, uri)) - .OrderByDescending(d => d.Uri.ToString()); - - /// - /// Parses a domain name into a URI. - /// - /// The domain name to parse - /// - /// The currently requested URI. If the domain name is relative, the authority of URI will be - /// used. - /// - /// The domain name as a URI - public static Uri ParseUriFromDomainName(string domainName, Uri currentUri) - { - // turn "/en" into "http://whatever.com/en" so it becomes a parseable uri - var name = domainName.StartsWith("/") && currentUri != null - ? currentUri.GetLeftPart(UriPartial.Authority) + domainName - : domainName; - var scheme = currentUri?.Scheme ?? Uri.UriSchemeHttp; - return new Uri(UriUtilityCore.TrimPathEndSlash(UriUtilityCore.StartWithScheme(name, scheme))); - } - #endregion #region Utilities @@ -399,26 +435,6 @@ internal static bool ExistsDomainInPath(IEnumerable domains, string path .FirstOrDefault(); } - /// - /// Gets the deepest wildcard Domain, if any, from a group of Domains, in a node path. - /// - /// The domains. - /// The node path eg '-1,1234,5678'. - /// The current domain root node identifier, or null. - /// The deepest wildcard Domain in the path, or null. - /// Looks _under_ rootNodeId but not _at_ rootNodeId. - public static Domain? FindWildcardDomainInPath(IEnumerable? domains, string path, int? rootNodeId) - { - var stopNodeId = rootNodeId ?? -1; - - return path.Split(Constants.CharArrays.Comma) - .Reverse() - .Select(s => int.Parse(s, CultureInfo.InvariantCulture)) - .TakeWhile(id => id != stopNodeId) - .Select(id => domains?.FirstOrDefault(d => d.ContentId == id && d.IsWildcard)) - .FirstOrDefault(domain => domain != null); - } - /// /// Returns the part of a path relative to the uri of a domain. /// @@ -427,7 +443,7 @@ internal static bool ExistsDomainInPath(IEnumerable domains, string path /// The path part relative to the uri of the domain. /// Eg the relative part of /foo/bar/nil to domain example.com/foo is /bar/nil. public static string PathRelativeToDomain(Uri domainUri, string path) - => path.Substring(domainUri.GetAbsolutePathDecoded().Length).EnsureStartsWith('/'); + => path[domainUri.GetAbsolutePathDecoded().Length..].EnsureStartsWith('/'); #endregion } diff --git a/src/Umbraco.Core/Routing/IMediaUrlProvider.cs b/src/Umbraco.Core/Routing/IMediaUrlProvider.cs index 5faf087110ae..9d944efff77e 100644 --- a/src/Umbraco.Core/Routing/IMediaUrlProvider.cs +++ b/src/Umbraco.Core/Routing/IMediaUrlProvider.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Cms.Core.Routing; diff --git a/src/Umbraco.Core/Routing/IPublishedUrlProvider.cs b/src/Umbraco.Core/Routing/IPublishedUrlProvider.cs index 358041e6173a..598ad1b53591 100644 --- a/src/Umbraco.Core/Routing/IPublishedUrlProvider.cs +++ b/src/Umbraco.Core/Routing/IPublishedUrlProvider.cs @@ -45,8 +45,7 @@ public interface IPublishedUrlProvider /// /// If the provider is unable to provide a url, it returns "#". /// - string GetUrl(IPublishedContent content, UrlMode mode = UrlMode.Default, string? culture = null, - Uri? current = null); + string GetUrl(IPublishedContent content, UrlMode mode = UrlMode.Default, string? culture = null, Uri? current = null); string GetUrlFromRoute(int id, string? route, string? culture); @@ -87,8 +86,7 @@ string GetUrl(IPublishedContent content, UrlMode mode = UrlMode.Default, string? /// /// /// - string GetMediaUrl(Guid id, UrlMode mode = UrlMode.Default, string? culture = null, - string propertyAlias = Constants.Conventions.Media.File, Uri? current = null); + string GetMediaUrl(Guid id, UrlMode mode = UrlMode.Default, string? culture = null, string propertyAlias = Constants.Conventions.Media.File, Uri? current = null); /// /// Gets the url of a media item. @@ -105,8 +103,7 @@ string GetMediaUrl(Guid id, UrlMode mode = UrlMode.Default, string? culture = nu /// If the media is multi-lingual, gets the url for the specified culture or, /// when no culture is specified, the current culture. /// - /// If the provider is unable to provide a url, it returns . + /// If the provider is unable to provide a url, it returns . /// - string GetMediaUrl(IPublishedContent? content, UrlMode mode = UrlMode.Default, string? culture = null, - string propertyAlias = Constants.Conventions.Media.File, Uri? current = null); + string GetMediaUrl(IPublishedContent? content, UrlMode mode = UrlMode.Default, string? culture = null, string propertyAlias = Constants.Conventions.Media.File, Uri? current = null); } diff --git a/src/Umbraco.Core/Routing/IUrlProvider.cs b/src/Umbraco.Core/Routing/IUrlProvider.cs index 01954baf7386..38f28b37644a 100644 --- a/src/Umbraco.Core/Routing/IUrlProvider.cs +++ b/src/Umbraco.Core/Routing/IUrlProvider.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Cms.Core.Routing; diff --git a/src/Umbraco.Core/Runtime/IMainDomLock.cs b/src/Umbraco.Core/Runtime/IMainDomLock.cs index d7ae8aba8a59..7e58fa6533b2 100644 --- a/src/Umbraco.Core/Runtime/IMainDomLock.cs +++ b/src/Umbraco.Core/Runtime/IMainDomLock.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Runtime; +namespace Umbraco.Cms.Core.Runtime; /// /// An application-wide distributed lock diff --git a/src/Umbraco.Core/Runtime/MainDom.cs b/src/Umbraco.Core/Runtime/MainDom.cs index f3673d77a6ca..d90042b0d410 100644 --- a/src/Umbraco.Core/Runtime/MainDom.cs +++ b/src/Umbraco.Core/Runtime/MainDom.cs @@ -113,7 +113,6 @@ private void OnSignal(string source) { // once signaled, we stop waiting, but then there is the hosting environment // so we have to make sure that we only enter that method once - lock (_locko) { _logger.LogDebug("Signaled ({Signaled}) ({SignalSource})", _signaled ? "again" : "first", source); @@ -189,8 +188,8 @@ private bool Acquire() // start without having MainDom? This would mean that it couldn't write // to nucache/examine and would only be ok if this was a super short lived appdomain. // maybe safer to just keep throwing in this case. - throw new TimeoutException("Cannot acquire MainDom"); + // return false; } @@ -198,12 +197,12 @@ private bool Acquire() { // Listen for the signal from another AppDomain coming online to release the lock _listenTask = _mainDomLock.ListenAsync(); - _listenCompleteTask = _listenTask.ContinueWith(t => + _listenCompleteTask = _listenTask.ContinueWith( + t => { if (_listenTask.Exception != null) { - _logger.LogWarning("Listening task completed with {TaskStatus}, Exception: {Exception}", - _listenTask.Status, _listenTask.Exception); + _logger.LogWarning("Listening task completed with {TaskStatus}, Exception: {Exception}", _listenTask.Status, _listenTask.Exception); } else { @@ -211,7 +210,8 @@ private bool Acquire() } OnSignal("signal"); - }, TaskScheduler.Default); // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html + }, + TaskScheduler.Default); // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html } catch (OperationCanceledException ex) { @@ -236,7 +236,6 @@ public static string GetMainDomId(IHostingEnvironment hostingEnvironment) // // we *cannot* use the process ID here because when an AppPool restarts it is // a new process for the same application path - var appPath = hostingEnvironment.ApplicationPhysicalPath?.ToLowerInvariant() ?? string.Empty; var hash = (appId + ":::" + appPath).GenerateHash(); @@ -271,7 +270,6 @@ public static string GetMainDomId(IHostingEnvironment hostingEnvironment) #region IDisposable Support // This code added to correctly implement the disposable pattern. - private bool disposedValue; // To detect redundant calls protected virtual void Dispose(bool disposing) diff --git a/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs b/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs index e163c6baa931..cdfd7b9305fa 100644 --- a/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs +++ b/src/Umbraco.Core/Runtime/MainDomSemaphoreLock.cs @@ -67,7 +67,6 @@ public Task AcquireLockAsync(int millisecondsTimeout) // only 1 instance can reach that point, but other instances may // have started and be trying to get the lock - they will timeout, // which is accepted - _signal.Reset(); } } @@ -93,6 +92,7 @@ protected virtual void Dispose(bool disposing) // This code added to correctly implement the disposable pattern. public void Dispose() => + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(true); diff --git a/src/Umbraco.Core/Scoping/IScopeContext.cs b/src/Umbraco.Core/Scoping/IScopeContext.cs index fd33f46e4a18..26f17b31b03e 100644 --- a/src/Umbraco.Core/Scoping/IScopeContext.cs +++ b/src/Umbraco.Core/Scoping/IScopeContext.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Scoping; +namespace Umbraco.Cms.Core.Scoping; /// /// Represents a scope context. diff --git a/src/Umbraco.Core/Sections/ContentSection.cs b/src/Umbraco.Core/Sections/ContentSection.cs index ccb34b000965..f8d46747b1ee 100644 --- a/src/Umbraco.Core/Sections/ContentSection.cs +++ b/src/Umbraco.Core/Sections/ContentSection.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Sections; +namespace Umbraco.Cms.Core.Sections; /// /// Defines the back office content section diff --git a/src/Umbraco.Core/Sections/FormsSection.cs b/src/Umbraco.Core/Sections/FormsSection.cs index 2f98a84a5689..3ac36e47326e 100644 --- a/src/Umbraco.Core/Sections/FormsSection.cs +++ b/src/Umbraco.Core/Sections/FormsSection.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Sections; +namespace Umbraco.Cms.Core.Sections; /// /// Defines the back office media section @@ -6,5 +6,6 @@ public class FormsSection : ISection { public string Alias => Constants.Applications.Forms; + public string Name => "Forms"; } diff --git a/src/Umbraco.Core/Sections/ISection.cs b/src/Umbraco.Core/Sections/ISection.cs index f79c3260c6c9..565955dfe9ac 100644 --- a/src/Umbraco.Core/Sections/ISection.cs +++ b/src/Umbraco.Core/Sections/ISection.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Sections; +namespace Umbraco.Cms.Core.Sections; /// /// Defines a back office section. diff --git a/src/Umbraco.Core/Sections/MediaSection.cs b/src/Umbraco.Core/Sections/MediaSection.cs index 038996298922..f5fd0a79b78e 100644 --- a/src/Umbraco.Core/Sections/MediaSection.cs +++ b/src/Umbraco.Core/Sections/MediaSection.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Sections; +namespace Umbraco.Cms.Core.Sections; /// /// Defines the back office media section @@ -6,5 +6,6 @@ public class MediaSection : ISection { public string Alias => Constants.Applications.Media; + public string Name => "Media"; } diff --git a/src/Umbraco.Core/Security/AuthenticationExtensions.cs b/src/Umbraco.Core/Security/AuthenticationExtensions.cs index e583c2d6c1c1..2b8294e8dbc2 100644 --- a/src/Umbraco.Core/Security/AuthenticationExtensions.cs +++ b/src/Umbraco.Core/Security/AuthenticationExtensions.cs @@ -14,7 +14,7 @@ public static class AuthenticationExtensions /// public static void EnsureCulture(this IIdentity identity) { - CultureInfo culture = GetCulture(identity); + CultureInfo? culture = GetCulture(identity); if (!(culture is null)) { Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = culture; diff --git a/src/Umbraco.Core/Security/BackOfficeExternalLoginProviderErrors.cs b/src/Umbraco.Core/Security/BackOfficeExternalLoginProviderErrors.cs index de5bc9edfd37..cece444588c6 100644 --- a/src/Umbraco.Core/Security/BackOfficeExternalLoginProviderErrors.cs +++ b/src/Umbraco.Core/Security/BackOfficeExternalLoginProviderErrors.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Security; +namespace Umbraco.Cms.Core.Security; public class BackOfficeExternalLoginProviderErrors { @@ -14,5 +14,6 @@ public BackOfficeExternalLoginProviderErrors(string? authenticationType, IEnumer } public string? AuthenticationType { get; set; } + public IEnumerable? Errors { get; set; } } diff --git a/src/Umbraco.Core/Security/BackOfficeUserPasswordCheckerResult.cs b/src/Umbraco.Core/Security/BackOfficeUserPasswordCheckerResult.cs index 25bfa1a71a2a..5466642a1406 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserPasswordCheckerResult.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserPasswordCheckerResult.cs @@ -7,5 +7,5 @@ public enum BackOfficeUserPasswordCheckerResult { ValidCredentials, InvalidCredentials, - FallbackToDefaultChecker + FallbackToDefaultChecker, } diff --git a/src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs b/src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs index 312771ed42fb..4cb9e20dac42 100644 --- a/src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs +++ b/src/Umbraco.Core/Security/ClaimsPrincipalExtensions.cs @@ -10,7 +10,7 @@ namespace Umbraco.Extensions; public static class ClaimsPrincipalExtensions { - public static bool IsBackOfficeAuthenticationType(this ClaimsIdentity claimsIdentity) + public static bool IsBackOfficeAuthenticationType(this ClaimsIdentity? claimsIdentity) { if (claimsIdentity is null) { @@ -28,19 +28,19 @@ public static bool IsBackOfficeAuthenticationType(this ClaimsIdentity claimsIden /// public static ClaimsIdentity? GetUmbracoIdentity(this IPrincipal principal) { - //If it's already a UmbracoBackOfficeIdentity + // If it's already a UmbracoBackOfficeIdentity if (principal.Identity is ClaimsIdentity claimsIdentity && claimsIdentity.IsBackOfficeAuthenticationType() - && claimsIdentity.VerifyBackOfficeIdentity(out ClaimsIdentity backOfficeIdentity)) + && claimsIdentity.VerifyBackOfficeIdentity(out ClaimsIdentity? backOfficeIdentity)) { return backOfficeIdentity; } - //Check if there's more than one identity assigned and see if it's a UmbracoBackOfficeIdentity and use that + // Check if there's more than one identity assigned and see if it's a UmbracoBackOfficeIdentity and use that // We can have assigned more identities if it is a preview request. if (principal is ClaimsPrincipal claimsPrincipal) { - ClaimsIdentity identity = + ClaimsIdentity? identity = claimsPrincipal.Identities.FirstOrDefault(x => x.IsBackOfficeAuthenticationType()); if (identity is not null) { @@ -52,7 +52,7 @@ public static bool IsBackOfficeAuthenticationType(this ClaimsIdentity claimsIden } } - //Otherwise convert to a UmbracoBackOfficeIdentity if it's auth'd + // Otherwise convert to a UmbracoBackOfficeIdentity if it's auth'd if (principal.Identity is ClaimsIdentity claimsIdentity2 && claimsIdentity2.VerifyBackOfficeIdentity(out backOfficeIdentity)) { @@ -80,8 +80,7 @@ public static double GetRemainingAuthSeconds(this IPrincipal user) => /// public static double GetRemainingAuthSeconds(this IPrincipal user, DateTimeOffset now) { - var claimsPrincipal = user as ClaimsPrincipal; - if (claimsPrincipal == null) + if (user is not ClaimsPrincipal claimsPrincipal) { return 0; } diff --git a/src/Umbraco.Core/Security/ContentPermissions.cs b/src/Umbraco.Core/Security/ContentPermissions.cs index 28e70e8ce2ea..db27d100c6b9 100644 --- a/src/Umbraco.Core/Security/ContentPermissions.cs +++ b/src/Umbraco.Core/Security/ContentPermissions.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; @@ -12,14 +12,15 @@ namespace Umbraco.Cms.Core.Security; /// public class ContentPermissions { + private readonly AppCaches _appCaches; + public enum ContentAccess { Granted, Denied, - NotFound + NotFound, } - private readonly AppCaches _appCaches; private readonly IContentService _contentService; private readonly IEntityService _entityService; private readonly IUserService _userService; @@ -36,10 +37,43 @@ public ContentPermissions( _appCaches = appCaches; } + public static bool HasPathAccess(string? path, int[]? startNodeIds, int recycleBinId) + { + if (string.IsNullOrWhiteSpace(path)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(path)); + } + + // check for no access + if (startNodeIds is null || startNodeIds.Length == 0) + { + return false; + } + + // check for root access + if (startNodeIds.Contains(Constants.System.Root)) + { + return true; + } + + var formattedPath = string.Concat(",", path, ","); + + // only users with root access have access to the recycle bin, + // if the above check didn't pass then access is denied + if (formattedPath.Contains(string.Concat(",", recycleBinId.ToString(CultureInfo.InvariantCulture), ","))) + { + return false; + } + + // check for a start node in the path + return startNodeIds.Any(x => + formattedPath.Contains(string.Concat(",", x.ToString(CultureInfo.InvariantCulture), ","))); + } + public ContentAccess CheckPermissions( IContent content, IUser user, - char permissionToCheck) => CheckPermissions(content, user, new[] {permissionToCheck}); + char permissionToCheck) => CheckPermissions(content, user, new[] { permissionToCheck }); public ContentAccess CheckPermissions( IContent? content, @@ -68,7 +102,7 @@ public ContentAccess CheckPermissions( return ContentAccess.Granted; } - //get the implicit/inherited permissions for the user for this path + // get the implicit/inherited permissions for the user for this path return CheckPermissionsPath(content.Path, user, permissionsToCheck) ? ContentAccess.Granted : ContentAccess.Denied; @@ -77,7 +111,7 @@ public ContentAccess CheckPermissions( public ContentAccess CheckPermissions( IUmbracoEntity entity, IUser? user, - char permissionToCheck) => CheckPermissions(entity, user, new[] {permissionToCheck}); + char permissionToCheck) => CheckPermissions(entity, user, new[] { permissionToCheck }); public ContentAccess CheckPermissions( IUmbracoEntity entity, @@ -106,7 +140,7 @@ public ContentAccess CheckPermissions( return ContentAccess.Granted; } - //get the implicit/inherited permissions for the user for this path + // get the implicit/inherited permissions for the user for this path return CheckPermissionsPath(entity.Path, user, permissionsToCheck) ? ContentAccess.Granted : ContentAccess.Denied; @@ -173,7 +207,7 @@ public ContentAccess CheckPermissions( return ContentAccess.Granted; } - //get the implicit/inherited permissions for the user for this path + // get the implicit/inherited permissions for the user for this path return CheckPermissionsPath(entity.Path, user, permissionsToCheck) ? ContentAccess.Granted : ContentAccess.Denied; @@ -241,7 +275,7 @@ public ContentAccess CheckPermissions( return ContentAccess.Granted; } - //get the implicit/inherited permissions for the user for this path + // get the implicit/inherited permissions for the user for this path return CheckPermissionsPath(contentItem.Path, user, permissionsToCheck) ? ContentAccess.Granted : ContentAccess.Denied; @@ -254,8 +288,8 @@ private bool CheckPermissionsPath(string? path, IUser user, IReadOnlyList? permissionsToCheck = Array.Empty(); } - //get the implicit/inherited permissions for the user for this path, - //if there is no content item for this id, than just use the id as the path (i.e. -1 or -20) + // get the implicit/inherited permissions for the user for this path, + // if there is no content item for this id, than just use the id as the path (i.e. -1 or -20) EntityPermissionSet permission = _userService.GetPermissionsForPath(user, path); var allowed = true; @@ -271,41 +305,7 @@ private bool CheckPermissionsPath(string? path, IUser user, IReadOnlyList? return allowed; } - public static bool HasPathAccess(string? path, int[]? startNodeIds, int recycleBinId) - { - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentException("Value cannot be null or whitespace.", nameof(path)); - } - - // check for no access - if (startNodeIds is null || startNodeIds.Length == 0) - { - return false; - } - - // check for root access - if (startNodeIds.Contains(Constants.System.Root)) - { - return true; - } - - var formattedPath = string.Concat(",", path, ","); - - // only users with root access have access to the recycle bin, - // if the above check didn't pass then access is denied - if (formattedPath.Contains(string.Concat(",", recycleBinId.ToString(CultureInfo.InvariantCulture), ","))) - { - return false; - } - - // check for a start node in the path - return startNodeIds.Any(x => - formattedPath.Contains(string.Concat(",", x.ToString(CultureInfo.InvariantCulture), ","))); - } - - public static bool IsInBranchOfStartNode(string path, int[]? startNodeIds, string[]? startNodePaths, - out bool hasPathAccess) + public static bool IsInBranchOfStartNode(string path, int[]? startNodeIds, string[]? startNodePaths, out bool hasPathAccess) { if (string.IsNullOrWhiteSpace(path)) { @@ -327,7 +327,7 @@ public static bool IsInBranchOfStartNode(string path, int[]? startNodeIds, strin return true; } - //is it self? + // is it self? var self = startNodePaths?.Any(x => x == path) ?? false; if (self) { @@ -335,15 +335,15 @@ public static bool IsInBranchOfStartNode(string path, int[]? startNodeIds, strin return true; } - //is it ancestor? + // is it ancestor? var ancestor = startNodePaths?.Any(x => x.StartsWith(path)) ?? false; if (ancestor) { - //hasPathAccess = false; + // hasPathAccess = false; return true; } - //is it descendant? + // is it descendant? var descendant = startNodePaths?.Any(x => path.StartsWith(x)) ?? false; if (descendant) { diff --git a/src/Umbraco.Core/Security/ITwoFactorProvider.cs b/src/Umbraco.Core/Security/ITwoFactorProvider.cs index 62c664ba4f57..8d2b12b6f889 100644 --- a/src/Umbraco.Core/Security/ITwoFactorProvider.cs +++ b/src/Umbraco.Core/Security/ITwoFactorProvider.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Security; +namespace Umbraco.Cms.Core.Security; public interface ITwoFactorProvider { diff --git a/src/Umbraco.Core/Security/IdentityAuditEventArgs.cs b/src/Umbraco.Core/Security/IdentityAuditEventArgs.cs index dccafafdcdaa..83c11916b163 100644 --- a/src/Umbraco.Core/Security/IdentityAuditEventArgs.cs +++ b/src/Umbraco.Core/Security/IdentityAuditEventArgs.cs @@ -1,5 +1,21 @@ namespace Umbraco.Cms.Core.Security; +public enum AuditEvent +{ + AccountLocked, + AccountUnlocked, + ForgotPasswordRequested, + ForgotPasswordChangedSuccess, + LoginFailed, + LoginRequiresVerification, + LoginSucces, + LogoutSuccess, + PasswordChanged, + PasswordReset, + ResetAccessFailedCount, + SendingUserInvite, +} + /// /// This class is used by events raised from the BackofficeUserManager /// @@ -8,13 +24,7 @@ public class IdentityAuditEventArgs : EventArgs /// /// Default constructor /// - /// - /// - /// - /// - /// - public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string performingUser, string comment, - string affectedUser, string affectedUsername) + public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string performingUser, string comment, string affectedUser, string affectedUsername) { DateTimeUtc = DateTime.UtcNow; Action = action; @@ -25,8 +35,7 @@ public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string perfor AffectedUser = affectedUser; } - public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string performingUser, string comment, - string affectedUsername) + public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string performingUser, string comment, string affectedUsername) : this(action, ipAddress, performingUser, comment, Constants.Security.SuperUserIdAsString, affectedUsername) { } @@ -66,19 +75,3 @@ public IdentityAuditEventArgs(AuditEvent action, string ipAddress, string perfor /// public string AffectedUsername { get; } } - -public enum AuditEvent -{ - AccountLocked, - AccountUnlocked, - ForgotPasswordRequested, - ForgotPasswordChangedSuccess, - LoginFailed, - LoginRequiresVerification, - LoginSucces, - LogoutSuccess, - PasswordChanged, - PasswordReset, - ResetAccessFailedCount, - SendingUserInvite -} diff --git a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs index fc0d8c354b98..597346c34bc4 100644 --- a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs +++ b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs @@ -13,6 +13,13 @@ namespace Umbraco.Cms.Core.Security; /// public class LegacyPasswordSecurity { + public static string GenerateSalt() + { + var numArray = new byte[16]; + new RNGCryptoServiceProvider().GetBytes(numArray); + return Convert.ToBase64String(numArray); + } + // TODO: Remove v11 // Used for tests [EditorBrowsable(EditorBrowsableState.Never)] @@ -24,8 +31,7 @@ public string HashPasswordForStorage(string algorithmType, string password) throw new ArgumentException("password cannot be empty", nameof(password)); } - string salt; - var hashed = HashNewPassword(algorithmType, password, out salt); + var hashed = HashNewPassword(algorithmType, password, out string salt); return FormatPasswordForStorage(algorithmType, hashed, salt); } @@ -70,7 +76,7 @@ public bool VerifyPassword(string algorithm, string password, string dbPassword) } catch (ArgumentOutOfRangeException) { - //This can happen if the length of the password is wrong and a salt cannot be extracted. + // This can happen if the length of the password is wrong and a salt cannot be extracted. return false; } } @@ -82,8 +88,8 @@ public bool VerifyLegacyHashedPassword(string password, string dbPassword) { var hashAlgorithm = new HMACSHA1 { - //the legacy salt was actually the password :( - Key = Encoding.Unicode.GetBytes(password) + // the legacy salt was actually the password :( + Key = Encoding.Unicode.GetBytes(password), }; var hashed = Convert.ToBase64String(hashAlgorithm.ComputeHash(Encoding.Unicode.GetBytes(password))); @@ -127,15 +133,25 @@ public string ParseStoredHashPassword(string algorithm, string storedString, out } var saltLen = GenerateSalt(); - salt = storedString.Substring(0, saltLen.Length); - return storedString.Substring(saltLen.Length); + salt = storedString[..saltLen.Length]; + return storedString[saltLen.Length..]; } - public static string GenerateSalt() + public bool SupportHashAlgorithm(string algorithm) { - var numArray = new byte[16]; - new RNGCryptoServiceProvider().GetBytes(numArray); - return Convert.ToBase64String(numArray); + // This is for the v6-v8 hashing algorithm + if (algorithm.InvariantEquals(Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName)) + { + return true; + } + + // Default validation value for old machine keys (switched to HMACSHA256 aspnet 4 https://docs.microsoft.com/en-us/aspnet/whitepapers/aspnet4/breaking-changes) + if (algorithm.InvariantEquals("SHA1")) + { + return true; + } + + return false; } /// @@ -153,31 +169,29 @@ private string HashPassword(string algorithmType, string pass, string salt) } // This is the correct way to implement this (as per the sql membership provider) - var bytes = Encoding.Unicode.GetBytes(pass); var saltBytes = Convert.FromBase64String(salt); byte[] inArray; using HashAlgorithm hashAlgorithm = GetHashAlgorithm(algorithmType); - var algorithm = hashAlgorithm as KeyedHashAlgorithm; - if (algorithm != null) + if (hashAlgorithm is KeyedHashAlgorithm algorithm) { KeyedHashAlgorithm keyedHashAlgorithm = algorithm; if (keyedHashAlgorithm.Key.Length == saltBytes.Length) { - //if the salt bytes is the required key length for the algorithm, use it as-is + // if the salt bytes is the required key length for the algorithm, use it as-is keyedHashAlgorithm.Key = saltBytes; } else if (keyedHashAlgorithm.Key.Length < saltBytes.Length) { - //if the salt bytes is too long for the required key length for the algorithm, reduce it + // if the salt bytes is too long for the required key length for the algorithm, reduce it var numArray2 = new byte[keyedHashAlgorithm.Key.Length]; Buffer.BlockCopy(saltBytes, 0, numArray2, 0, numArray2.Length); keyedHashAlgorithm.Key = numArray2; } else { - //if the salt bytes is too short for the required key length for the algorithm, extend it + // if the salt bytes is too short for the required key length for the algorithm, extend it var numArray2 = new byte[keyedHashAlgorithm.Key.Length]; var dstOffset = 0; while (dstOffset < numArray2.Length) @@ -224,21 +238,4 @@ private HashAlgorithm GetHashAlgorithm(string algorithm) return alg; } - - public bool SupportHashAlgorithm(string algorithm) - { - // This is for the v6-v8 hashing algorithm - if (algorithm.InvariantEquals(Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName)) - { - return true; - } - - // Default validation value for old machine keys (switched to HMACSHA256 aspnet 4 https://docs.microsoft.com/en-us/aspnet/whitepapers/aspnet4/breaking-changes) - if (algorithm.InvariantEquals("SHA1")) - { - return true; - } - - return false; - } } diff --git a/src/Umbraco.Core/Security/MediaPermissions.cs b/src/Umbraco.Core/Security/MediaPermissions.cs index 7571b8ae8c9e..c46d32f56583 100644 --- a/src/Umbraco.Core/Security/MediaPermissions.cs +++ b/src/Umbraco.Core/Security/MediaPermissions.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Services; @@ -10,14 +10,15 @@ namespace Umbraco.Cms.Core.Security; /// public class MediaPermissions { + private readonly AppCaches _appCaches; + public enum MediaAccess { Granted, Denied, - NotFound + NotFound, } - private readonly AppCaches _appCaches; private readonly IEntityService _entityService; private readonly IMediaService _mediaService; @@ -33,9 +34,8 @@ public MediaPermissions(IMediaService mediaService, IEntityService entityService /// start node and/or permissions for the node /// /// - /// - /// /// The content to lookup, if the contentItem is not specified + /// /// public MediaAccess CheckPermissions(IUser? user, int nodeId, out IMedia? media) { diff --git a/src/Umbraco.Core/Serialization/IConfigurationEditorJsonSerializer.cs b/src/Umbraco.Core/Serialization/IConfigurationEditorJsonSerializer.cs index 4e9b2606ea80..9a0429a75e48 100644 --- a/src/Umbraco.Core/Serialization/IConfigurationEditorJsonSerializer.cs +++ b/src/Umbraco.Core/Serialization/IConfigurationEditorJsonSerializer.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Serialization; +namespace Umbraco.Cms.Core.Serialization; public interface IConfigurationEditorJsonSerializer : IJsonSerializer { diff --git a/src/Umbraco.Core/Serialization/IJsonSerializer.cs b/src/Umbraco.Core/Serialization/IJsonSerializer.cs index 569907b0c847..5a31a2cf971a 100644 --- a/src/Umbraco.Core/Serialization/IJsonSerializer.cs +++ b/src/Umbraco.Core/Serialization/IJsonSerializer.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Serialization; +namespace Umbraco.Cms.Core.Serialization; public interface IJsonSerializer { diff --git a/src/Umbraco.Core/Services/AuditService.cs b/src/Umbraco.Core/Services/AuditService.cs index b06941ac99a0..ec82c040c876 100644 --- a/src/Umbraco.Core/Services/AuditService.cs +++ b/src/Umbraco.Core/Services/AuditService.cs @@ -49,7 +49,8 @@ public IEnumerable GetUserLogs(int userId, AuditType type, DateTime? { IEnumerable result = sinceDate.HasValue == false ? _auditRepository.Get(type, Query().Where(x => x.UserId == userId)) - : _auditRepository.Get(type, + : _auditRepository.Get( + type, Query().Where(x => x.UserId == userId && x.CreateDate >= sinceDate.Value)); scope.Complete(); return result; @@ -199,12 +200,12 @@ public IAuditEntry Write(int performingUserId, string perfomingDetails, string p throw new ArgumentException("Value cannot be null or whitespace.", nameof(eventDetails)); } - //we need to truncate the data else we'll get SQL errors + // we need to truncate the data else we'll get SQL errors affectedDetails = - affectedDetails?.Substring(0, Math.Min(affectedDetails.Length, Constants.Audit.DetailsLength)); - eventDetails = eventDetails.Substring(0, Math.Min(eventDetails.Length, Constants.Audit.DetailsLength)); + affectedDetails?[..Math.Min(affectedDetails.Length, Constants.Audit.DetailsLength)]; + eventDetails = eventDetails[..Math.Min(eventDetails.Length, Constants.Audit.DetailsLength)]; - //validate the eventType - must contain a forward slash, no spaces, no special chars + // validate the eventType - must contain a forward slash, no spaces, no special chars var eventTypeParts = eventType.ToCharArray(); if (eventTypeParts.Contains('/') == false || eventTypeParts.All(c => char.IsLetterOrDigit(c) || c == '/' || c == '-') == false) @@ -232,7 +233,7 @@ public IAuditEntry Write(int performingUserId, string perfomingDetails, string p AffectedUserId = affectedUserId, AffectedDetails = affectedDetails, EventType = eventType, - EventDetails = eventDetails + EventDetails = eventDetails, }; if (_isAvailable.Value == false) diff --git a/src/Umbraco.Core/Services/Changes/ContentTypeChange.cs b/src/Umbraco.Core/Services/Changes/ContentTypeChange.cs index 5561c776d41f..f3fe533373aa 100644 --- a/src/Umbraco.Core/Services/Changes/ContentTypeChange.cs +++ b/src/Umbraco.Core/Services/Changes/ContentTypeChange.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Services.Changes; diff --git a/src/Umbraco.Core/Services/Changes/ContentTypeChangeExtensions.cs b/src/Umbraco.Core/Services/Changes/ContentTypeChangeExtensions.cs index 560599a17e21..d45a2267bc89 100644 --- a/src/Umbraco.Core/Services/Changes/ContentTypeChangeExtensions.cs +++ b/src/Umbraco.Core/Services/Changes/ContentTypeChangeExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Services.Changes; diff --git a/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs b/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs index 21cce2403296..4346a278cc28 100644 --- a/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs +++ b/src/Umbraco.Core/Services/Changes/ContentTypeChangeTypes.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Services.Changes; +namespace Umbraco.Cms.Core.Services.Changes; [Flags] public enum ContentTypeChangeTypes : byte @@ -23,5 +23,5 @@ public enum ContentTypeChangeTypes : byte /// /// Content type was removed /// - Remove = 8 + Remove = 8, } diff --git a/src/Umbraco.Core/Services/Changes/DomainChangeTypes.cs b/src/Umbraco.Core/Services/Changes/DomainChangeTypes.cs index 28902c7a7eee..303461f48f89 100644 --- a/src/Umbraco.Core/Services/Changes/DomainChangeTypes.cs +++ b/src/Umbraco.Core/Services/Changes/DomainChangeTypes.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Cms.Core.Services.Changes; +namespace Umbraco.Cms.Core.Services.Changes; public enum DomainChangeTypes : byte { None = 0, RefreshAll = 1, Refresh = 2, - Remove = 3 + Remove = 3, } diff --git a/src/Umbraco.Core/Services/ConsentService.cs b/src/Umbraco.Core/Services/ConsentService.cs index d48e0c9a9a4a..d7bb7af13ebc 100644 --- a/src/Umbraco.Core/Services/ConsentService.cs +++ b/src/Umbraco.Core/Services/ConsentService.cs @@ -17,14 +17,12 @@ internal class ConsentService : RepositoryService, IConsentService /// /// Initializes a new instance of the class. /// - public ConsentService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, IConsentRepository consentRepository) + public ConsentService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IConsentRepository consentRepository) : base(provider, loggerFactory, eventMessagesFactory) => _consentRepository = consentRepository; /// - public IConsent RegisterConsent(string source, string context, string action, ConsentState state, - string? comment = null) + public IConsent RegisterConsent(string source, string context, string action, ConsentState state, string? comment = null) { // prevent stupid states var v = 0; @@ -56,7 +54,7 @@ public IConsent RegisterConsent(string source, string context, string action, Co Action = action, CreateDate = DateTime.Now, State = state, - Comment = comment + Comment = comment, }; using (ICoreScope scope = ScopeProvider.CreateCoreScope()) @@ -70,8 +68,13 @@ public IConsent RegisterConsent(string source, string context, string action, Co } /// - public IEnumerable LookupConsent(string? source = null, string? context = null, string? action = null, - bool sourceStartsWith = false, bool contextStartsWith = false, bool actionStartsWith = false, + public IEnumerable LookupConsent( + string? source = null, + string? context = null, + string? action = null, + bool sourceStartsWith = false, + bool contextStartsWith = false, + bool actionStartsWith = false, bool includeHistory = false) { using (ScopeProvider.CreateCoreScope(autoComplete: true)) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index c2823526153f..eb2634709051 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -33,13 +33,18 @@ public class ContentService : RepositoryService, IContentService #region Constructors - public ContentService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, + public ContentService( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - IDocumentRepository documentRepository, IEntityRepository entityRepository, + IDocumentRepository documentRepository, + IEntityRepository entityRepository, IAuditRepository auditRepository, - IContentTypeRepository contentTypeRepository, IDocumentBlueprintRepository documentBlueprintRepository, + IContentTypeRepository contentTypeRepository, + IDocumentBlueprintRepository documentBlueprintRepository, ILanguageRepository languageRepository, - Lazy propertyValidationService, IShortStringHelper shortStringHelper) + Lazy propertyValidationService, + IShortStringHelper shortStringHelper) : base(provider, loggerFactory, eventMessagesFactory) { _documentRepository = documentRepository; @@ -58,7 +63,6 @@ public ContentService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, #region Static queries // lazy-constructed because when the ctor runs, the query factory may not be ready - private IQuery QueryNotTrashed => _queryNotTrashed ??= Query().Where(x => x.Trashed == false); @@ -66,8 +70,7 @@ public ContentService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, #region Rollback - public OperationResult Rollback(int id, int versionId, string culture = "*", - int userId = Constants.Security.SuperUserId) + public OperationResult Rollback(int id, int versionId, string culture = "*", int userId = Constants.Security.SuperUserId) { EventMessages evtMsgs = EventMessagesFactory.Get(); @@ -106,8 +109,7 @@ public OperationResult Rollback(int id, int versionId, string culture = "*", { // Log the error/warning _logger.LogError( - "User '{UserId}' was unable to rollback content '{ContentId}' to version '{VersionId}'", userId, - id, versionId); + "User '{UserId}' was unable to rollback content '{ContentId}' to version '{VersionId}'", userId, id, versionId); } else { @@ -115,10 +117,8 @@ public OperationResult Rollback(int id, int versionId, string culture = "*", new ContentRolledBackNotification(content, evtMsgs).WithStateFrom(rollingBackNotification)); // Logging & Audit message - _logger.LogInformation("User '{UserId}' rolled back content '{ContentId}' to version '{VersionId}'", - userId, id, versionId); - Audit(AuditType.RollBack, userId, id, - $"Content '{content.Name}' was rolled back to version '{versionId}'"); + _logger.LogInformation("User '{UserId}' rolled back content '{ContentId}' to version '{VersionId}'", userId, id, versionId); + Audit(AuditType.RollBack, userId, id, $"Content '{content.Name}' was rolled back to version '{versionId}'"); } scope.Complete(); @@ -236,11 +236,9 @@ public EntityPermissionCollection GetPermissions(IContent content) /// /// /// - public IContent Create(string name, Guid parentId, string contentTypeAlias, - int userId = Constants.Security.SuperUserId) + public IContent Create(string name, Guid parentId, string contentTypeAlias, int userId = Constants.Security.SuperUserId) { // TODO: what about culture? - IContent? parent = GetById(parentId); return Create(name, parent, contentTypeAlias, userId); } @@ -258,11 +256,9 @@ public IContent Create(string name, Guid parentId, string contentTypeAlias, /// The alias of the content type. /// The optional id of the user creating the content. /// The content object. - public IContent Create(string name, int parentId, string contentTypeAlias, - int userId = Constants.Security.SuperUserId) + public IContent Create(string name, int parentId, string contentTypeAlias, int userId = Constants.Security.SuperUserId) { // TODO: what about culture? - IContentType contentType = GetContentType(contentTypeAlias); return Create(name, parentId, contentType, userId); } @@ -280,8 +276,7 @@ public IContent Create(string name, int parentId, string contentTypeAlias, /// The content type of the content /// The optional id of the user creating the content. /// The content object. - public IContent Create(string name, int parentId, IContentType contentType, - int userId = Constants.Security.SuperUserId) + public IContent Create(string name, int parentId, IContentType contentType, int userId = Constants.Security.SuperUserId) { if (contentType is null) { @@ -312,11 +307,9 @@ public IContent Create(string name, int parentId, IContentType contentType, /// The alias of the content type. /// The optional id of the user creating the content. /// The content object. - public IContent Create(string name, IContent? parent, string contentTypeAlias, - int userId = Constants.Security.SuperUserId) + public IContent Create(string name, IContent? parent, string contentTypeAlias, int userId = Constants.Security.SuperUserId) { // TODO: what about culture? - if (parent == null) { throw new ArgumentNullException(nameof(parent)); @@ -325,8 +318,7 @@ public IContent Create(string name, IContent? parent, string contentTypeAlias, IContentType contentType = GetContentType(contentTypeAlias); if (contentType == null) { - throw new ArgumentException("No content type with that alias.", - nameof(contentTypeAlias)); // causes rollback + throw new ArgumentException("No content type with that alias.", nameof(contentTypeAlias)); // causes rollback } var content = new Content(name, parent, contentType, userId); @@ -343,11 +335,9 @@ public IContent Create(string name, IContent? parent, string contentTypeAlias, /// The alias of the content type. /// The optional id of the user creating the content. /// The content object. - public IContent CreateAndSave(string name, int parentId, string contentTypeAlias, - int userId = Constants.Security.SuperUserId) + public IContent CreateAndSave(string name, int parentId, string contentTypeAlias, int userId = Constants.Security.SuperUserId) { // TODO: what about culture? - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { // locking the content tree secures content types too @@ -356,8 +346,7 @@ public IContent CreateAndSave(string name, int parentId, string contentTypeAlias IContentType contentType = GetContentType(contentTypeAlias); // + locks if (contentType == null) { - throw new ArgumentException("No content type with that alias.", - nameof(contentTypeAlias)); // causes rollback + throw new ArgumentException("No content type with that alias.", nameof(contentTypeAlias)); // causes rollback } IContent? parent = parentId > 0 ? GetById(parentId) : null; // + locks @@ -385,11 +374,9 @@ public IContent CreateAndSave(string name, int parentId, string contentTypeAlias /// The alias of the content type. /// The optional id of the user creating the content. /// The content object. - public IContent CreateAndSave(string name, IContent parent, string contentTypeAlias, - int userId = Constants.Security.SuperUserId) + public IContent CreateAndSave(string name, IContent parent, string contentTypeAlias, int userId = Constants.Security.SuperUserId) { // TODO: what about culture? - if (parent == null) { throw new ArgumentNullException(nameof(parent)); @@ -403,8 +390,7 @@ public IContent CreateAndSave(string name, IContent parent, string contentTypeAl IContentType contentType = GetContentType(contentTypeAlias); // + locks if (contentType == null) { - throw new ArgumentException("No content type with that alias.", - nameof(contentTypeAlias)); // causes rollback + throw new ArgumentException("No content type with that alias.", nameof(contentTypeAlias)); // causes rollback } var content = new Content(name, parent, contentType, userId); @@ -535,9 +521,13 @@ public IEnumerable GetByIds(IEnumerable ids) } /// - public IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, - out long totalRecords - , IQuery? filter = null, Ordering? ordering = null) + public IEnumerable GetPagedOfType( + int contentTypeId, + long pageIndex, + int pageSize, + out long totalRecords, + IQuery? filter = null, + Ordering? ordering = null) { if (pageIndex < 0) { @@ -559,13 +549,16 @@ out long totalRecords scope.ReadLock(Constants.Locks.ContentTree); return _documentRepository.GetPage( Query()?.Where(x => x.ContentTypeId == contentTypeId), - pageIndex, pageSize, out totalRecords, filter, ordering); + pageIndex, + pageSize, + out totalRecords, + filter, + ordering); } } /// - public IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize, - out long totalRecords, IQuery? filter, Ordering? ordering = null) + public IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize, out long totalRecords, IQuery? filter, Ordering? ordering = null) { if (pageIndex < 0) { @@ -587,7 +580,11 @@ public IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageInde scope.ReadLock(Constants.Locks.ContentTree); return _documentRepository.GetPage( Query()?.Where(x => contentTypeIds.Contains(x.ContentTypeId)), - pageIndex, pageSize, out totalRecords, filter, ordering); + pageIndex, + pageSize, + out totalRecords, + filter, + ordering); } } @@ -686,7 +683,7 @@ public IEnumerable GetAncestors(int id) /// An Enumerable list of objects public IEnumerable GetAncestors(IContent content) { - //null check otherwise we get exceptions + // null check otherwise we get exceptions if (content.Path.IsNullOrWhiteSpace()) { return Enumerable.Empty(); @@ -721,8 +718,7 @@ public IEnumerable GetPublishedChildren(int id) } /// - public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, - IQuery? filter = null, Ordering? ordering = null) + public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, IQuery? filter = null, Ordering? ordering = null) { if (pageIndex < 0) { @@ -749,8 +745,7 @@ public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSi } /// - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, - IQuery? filter = null, Ordering? ordering = null) + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, IQuery? filter = null, Ordering? ordering = null) { if (ordering == null) { @@ -761,7 +756,7 @@ public IEnumerable GetPagedDescendants(int id, long pageIndex, int pag { scope.ReadLock(Constants.Locks.ContentTree); - //if the id is System Root, then just get all + // if the id is System Root, then just get all if (id != Constants.System.Root) { TreeEntityPath[] contentPath = @@ -772,8 +767,7 @@ public IEnumerable GetPagedDescendants(int id, long pageIndex, int pag return Enumerable.Empty(); } - return GetPagedLocked(GetPagedDescendantQuery(contentPath[0].Path), pageIndex, pageSize, - out totalChildren, filter, ordering); + return GetPagedLocked(GetPagedDescendantQuery(contentPath[0].Path), pageIndex, pageSize, out totalChildren, filter, ordering); } return GetPagedLocked(null, pageIndex, pageSize, out totalChildren, filter, ordering); @@ -791,9 +785,7 @@ public IEnumerable GetPagedDescendants(int id, long pageIndex, int pag return query; } - private IEnumerable GetPagedLocked(IQuery? query, long pageIndex, int pageSize, - out long totalChildren, - IQuery? filter, Ordering? ordering) + private IEnumerable GetPagedLocked(IQuery? query, long pageIndex, int pageSize, out long totalChildren, IQuery? filter, Ordering? ordering) { if (pageIndex < 0) { @@ -892,8 +884,7 @@ public IEnumerable GetContentForRelease(DateTime date) /// Gets a collection of an objects, which resides in the Recycle Bin /// /// An Enumerable list of objects - public IEnumerable GetPagedContentInRecycleBin(long pageIndex, int pageSize, out long totalRecords, - IQuery? filter = null, Ordering? ordering = null) + public IEnumerable GetPagedContentInRecycleBin(long pageIndex, int pageSize, out long totalRecords, IQuery? filter = null, Ordering? ordering = null) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -953,8 +944,7 @@ public bool IsPathPublished(IContent? content) #region Save, Publish, Unpublish /// - public OperationResult Save(IContent content, int? userId = null, - ContentScheduleCollection? contentSchedule = null) + public OperationResult Save(IContent content, int? userId = null, ContentScheduleCollection? contentSchedule = null) { PublishedState publishedState = content.PublishedState; if (publishedState != PublishedState.Published && publishedState != PublishedState.Unpublished) @@ -990,15 +980,15 @@ public OperationResult Save(IContent content, int? userId = null, content.WriterId = userId.Value; - //track the cultures that have changed + // track the cultures that have changed List? culturesChanging = content.ContentType.VariesByCulture() ? content.CultureInfos?.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList() : null; + // TODO: Currently there's no way to change track which variant properties have changed, we only have change // tracking enabled on all values on the Property which doesn't allow us to know which variants have changed. // in this particular case, determining which cultures have changed works with the above with names since it will // have always changed if it's been saved in the back office but that's not really fail safe. - _documentRepository.Save(content); if (contentSchedule != null) @@ -1018,7 +1008,7 @@ public OperationResult Save(IContent content, int? userId = null, if (culturesChanging != null) { - IEnumerable languages = _languageRepository.GetMany()? + IEnumerable? languages = _languageRepository.GetMany()? .Where(x => culturesChanging.InvariantContains(x.IsoCode)) .Select(x => x.CultureName); if (languages is not null) @@ -1068,6 +1058,7 @@ public OperationResult Save(IEnumerable contents, int userId = Constan scope.Notifications.Publish( new ContentSavedNotification(contentsA, eventMessages).WithStateFrom(savingNotification)); + // TODO: See note above about supressing events scope.Notifications.Publish( new ContentTreeChangeNotification(contentsA, TreeChangeTypes.RefreshNode, eventMessages)); @@ -1081,8 +1072,7 @@ public OperationResult Save(IEnumerable contents, int userId = Constan } /// - public PublishResult SaveAndPublish(IContent content, string culture = "*", - int userId = Constants.Security.SuperUserId) + public PublishResult SaveAndPublish(IContent content, string culture = "*", int userId = Constants.Security.SuperUserId) { EventMessages evtMsgs = EventMessagesFactory.Get(); @@ -1131,23 +1121,21 @@ public PublishResult SaveAndPublish(IContent content, string culture = "*", // if culture is specific, first publish the invariant values, then publish the culture itself. // if culture is '*', then publish them all (including variants) - //this will create the correct culture impact even if culture is * or null + // this will create the correct culture impact even if culture is * or null var impact = CultureImpact.Create(culture, IsDefaultCulture(allLangs, culture), content); // publish the culture(s) // we don't care about the response here, this response will be rechecked below but we need to set the culture info values now. content.PublishCulture(impact); - PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, - savingNotification.State, userId); + PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId); scope.Complete(); return result; } } /// - public PublishResult SaveAndPublish(IContent content, string[] cultures, - int userId = Constants.Security.SuperUserId) + public PublishResult SaveAndPublish(IContent content, string[] cultures, int userId = Constants.Security.SuperUserId) { if (content == null) { @@ -1182,7 +1170,7 @@ public PublishResult SaveAndPublish(IContent content, string[] cultures, if (cultures.Length == 0 && !varies) { - //no cultures specified and doesn't vary, so publish it, else nothing to publish + // No cultures specified and doesn't vary, so publish it, else nothing to publish return SaveAndPublish(content, userId: userId); } @@ -1202,16 +1190,14 @@ public PublishResult SaveAndPublish(IContent content, string[] cultures, content.PublishCulture(impact); } - PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, - savingNotification.State, userId); + PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId); scope.Complete(); return result; } } /// - public PublishResult Unpublish(IContent content, string? culture = "*", - int userId = Constants.Security.SuperUserId) + public PublishResult Unpublish(IContent content, string? culture = "*", int userId = Constants.Security.SuperUserId) { if (content == null) { @@ -1272,10 +1258,8 @@ public PublishResult Unpublish(IContent content, string? culture = "*", // we are just unpublishing the whole document but leaving all of the culture's as-is. This is expected // because we don't want to actually unpublish every culture and then the document, we just want everything // to be non-routable so that when it's re-published all variants were as they were. - content.PublishedState = PublishedState.Unpublishing; - PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, - savingNotification.State, userId); + PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId); scope.Complete(); return result; } @@ -1288,9 +1272,8 @@ public PublishResult Unpublish(IContent content, string? culture = "*", // If the result of this is false it means there was no culture to unpublish (i.e. it was already unpublished or it did not exist) var removed = content.UnpublishCulture(culture); - //save and publish any changes - PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, - savingNotification.State, userId); + // Save and publish any changes + PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId); scope.Complete(); @@ -1333,8 +1316,7 @@ public PublishResult Unpublish(IContent content, string? culture = "*", /// /// The document is *always* saved, even when publishing fails. /// - internal PublishResult CommitDocumentChanges(IContent content, - int userId = Constants.Security.SuperUserId) + internal PublishResult CommitDocumentChanges(IContent content, int userId = Constants.Security.SuperUserId) { using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { @@ -1350,8 +1332,7 @@ internal PublishResult CommitDocumentChanges(IContent content, var allLangs = _languageRepository.GetMany().ToList(); - PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, - savingNotification.State, userId); + PublishResult result = CommitDocumentChangesInternal(scope, content, evtMsgs, allLangs, savingNotification.State, userId); scope.Complete(); return result; } @@ -1377,11 +1358,15 @@ internal PublishResult CommitDocumentChanges(IContent content, /// saving/publishing, branch saving/publishing, etc... /// /// - private PublishResult CommitDocumentChangesInternal(ICoreScope scope, IContent content, - EventMessages eventMessages, IReadOnlyCollection allLangs, + private PublishResult CommitDocumentChangesInternal( + ICoreScope scope, + IContent content, + EventMessages eventMessages, + IReadOnlyCollection allLangs, IDictionary? notificationState, int userId = Constants.Security.SuperUserId, - bool branchOne = false, bool branchRoot = false) + bool branchOne = false, + bool branchRoot = false) { if (scope == null) { @@ -1415,7 +1400,7 @@ private PublishResult CommitDocumentChangesInternal(ICoreScope scope, IContent c var variesByCulture = content.ContentType.VariesByCulture(); - //track cultures that are being published, changed, unpublished + // Track cultures that are being published, changed, unpublished IReadOnlyList? culturesPublishing = null; IReadOnlyList? culturesUnpublishing = null; IReadOnlyList? culturesChanging = variesByCulture @@ -1426,7 +1411,7 @@ private PublishResult CommitDocumentChangesInternal(ICoreScope scope, IContent c TreeChangeTypes changeType = isNew ? TreeChangeTypes.RefreshNode : TreeChangeTypes.RefreshBranch; var previouslyPublished = content.HasIdentity && content.Published; - //inline method to persist the document with the documentRepository since this logic could be called a couple times below + // Inline method to persist the document with the documentRepository since this logic could be called a couple times below void SaveDocument(IContent c) { // save, always @@ -1443,21 +1428,28 @@ void SaveDocument(IContent c) if (publishing) { - //determine cultures publishing/unpublishing which will be based on previous calls to content.PublishCulture and ClearPublishInfo + // Determine cultures publishing/unpublishing which will be based on previous calls to content.PublishCulture and ClearPublishInfo culturesUnpublishing = content.GetCulturesUnpublishing(); culturesPublishing = variesByCulture ? content.PublishCultureInfos?.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList() : null; // ensure that the document can be published, and publish handling events, business rules, etc - publishResult = StrategyCanPublish(scope, content, /*checkPath:*/ !branchOne || branchRoot, - culturesPublishing, culturesUnpublishing, eventMessages, allLangs, notificationState); + publishResult = StrategyCanPublish( + scope, + content, /*checkPath:*/ + !branchOne || branchRoot, + culturesPublishing, + culturesUnpublishing, + eventMessages, + allLangs, + notificationState); if (publishResult.Success) { // note: StrategyPublish flips the PublishedState to Publishing! publishResult = StrategyPublish(content, culturesPublishing, culturesUnpublishing, eventMessages); - //check if a culture has been unpublished and if there are no cultures left, and then unpublish document as a whole + // Check if a culture has been unpublished and if there are no cultures left, and then unpublish document as a whole if (publishResult.Result == PublishResultType.SuccessUnpublishCulture && content.PublishCultureInfos?.Count == 0) { @@ -1468,7 +1460,7 @@ void SaveDocument(IContent c) // mark the document for Unpublishing. SaveDocument(content); - //set the flag to unpublish and continue + // Set the flag to unpublish and continue unpublishing = content.Published; // if not published yet, nothing to do } } @@ -1480,7 +1472,7 @@ void SaveDocument(IContent c) return publishResult; } - //check for mandatory culture missing, and then unpublish document as a whole + // Check for mandatory culture missing, and then unpublish document as a whole if (publishResult.Result == PublishResultType.FailedPublishMandatoryCultureMissing) { publishing = false; @@ -1499,13 +1491,13 @@ void SaveDocument(IContent c) } } - if (unpublishing) // won't happen in a branch + // won't happen in a branch + if (unpublishing) { IContent? newest = GetById(content.Id); // ensure we have the newest version - in scope if (content.VersionId != newest?.VersionId) { - return new PublishResult(PublishResultType.FailedPublishConcurrencyViolation, eventMessages, - content); + return new PublishResult(PublishResultType.FailedPublishConcurrencyViolation, eventMessages, content); } if (content.Published) @@ -1538,27 +1530,27 @@ void SaveDocument(IContent c) } } - //Persist the document + // Persist the document SaveDocument(content); // raise the Saved event, always scope.Notifications.Publish( new ContentSavedNotification(content, eventMessages).WithState(notificationState)); - if (unpublishing) // we have tried to unpublish - won't happen in a branch + // we have tried to unpublish - won't happen in a branch + if (unpublishing) { - if (unpublishResult?.Success ?? false) // and succeeded, trigger events + // and succeeded, trigger events + if (unpublishResult?.Success ?? false) { // events and audit scope.Notifications.Publish( new ContentUnpublishedNotification(content, eventMessages).WithState(notificationState)); - scope.Notifications.Publish(new ContentTreeChangeNotification(content, - TreeChangeTypes.RefreshBranch, eventMessages)); + scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages)); if (culturesUnpublishing != null) { // This will mean that that we unpublished a mandatory culture or we unpublished the last culture. - var langs = string.Join(", ", allLangs .Where(x => culturesUnpublishing.InvariantContains(x.IsoCode)) .Select(x => x.CultureName)); @@ -1572,20 +1564,15 @@ void SaveDocument(IContent c) switch (publishResult.Result) { case PublishResultType.FailedPublishMandatoryCultureMissing: - //occurs when a mandatory culture was unpublished (which means we tried publishing the document without a mandatory culture) + // Occurs when a mandatory culture was unpublished (which means we tried publishing the document without a mandatory culture) - //log that the whole content item has been unpublished due to mandatory culture unpublished - Audit(AuditType.Unpublish, userId, content.Id, - "Unpublished (mandatory language unpublished)"); - return new PublishResult(PublishResultType.SuccessUnpublishMandatoryCulture, - eventMessages, content); + // Log that the whole content item has been unpublished due to mandatory culture unpublished + Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (mandatory language unpublished)"); + return new PublishResult(PublishResultType.SuccessUnpublishMandatoryCulture, eventMessages, content); case PublishResultType.SuccessUnpublishCulture: - //occurs when the last culture is unpublished - - Audit(AuditType.Unpublish, userId, content.Id, - "Unpublished (last language unpublished)"); - return new PublishResult(PublishResultType.SuccessUnpublishLastCulture, eventMessages, - content); + // Occurs when the last culture is unpublished + Audit(AuditType.Unpublish, userId, content.Id, "Unpublished (last language unpublished)"); + return new PublishResult(PublishResultType.SuccessUnpublishLastCulture, eventMessages, content); } } @@ -1611,7 +1598,6 @@ void SaveDocument(IContent c) changeType = TreeChangeTypes.RefreshNode; // single node } - // invalidate the node/branch if (!branchOne) // for branches, handled by SaveAndPublishBranch { @@ -1642,8 +1628,7 @@ void SaveDocument(IContent c) var langs = string.Join(", ", allLangs .Where(x => culturesPublishing.InvariantContains(x.IsoCode)) .Select(x => x.CultureName)); - Audit(AuditType.PublishVariant, userId, content.Id, $"Published languages: {langs}", - langs); + Audit(AuditType.PublishVariant, userId, content.Id, $"Published languages: {langs}", langs); } break; @@ -1653,8 +1638,7 @@ void SaveDocument(IContent c) var langs = string.Join(", ", allLangs .Where(x => culturesUnpublishing.InvariantContains(x.IsoCode)) .Select(x => x.CultureName)); - Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", - langs); + Audit(AuditType.UnpublishVariant, userId, content.Id, $"Unpublished languages: {langs}", langs); } break; @@ -1670,7 +1654,7 @@ void SaveDocument(IContent c) throw new PanicException("branchOne && !branchRoot - should not happen"); } - //if publishing didn't happen or if it has failed, we still need to log which cultures were saved + // if publishing didn't happen or if it has failed, we still need to log which cultures were saved if (!branchOne && (publishResult == null || !publishResult.Success)) { if (culturesChanging != null) @@ -1720,7 +1704,7 @@ private void PerformScheduledPublishingExpiration(DateTime date, List x.Culture) .Distinct() @@ -1728,7 +1712,7 @@ private void PerformScheduledPublishingExpiration(DateTime date, List results, - EventMessages evtMsgs, Lazy> allLangs) + private void PerformScheduledPublishingRelease(DateTime date, List results, EventMessages evtMsgs, Lazy> allLangs) { using ICoreScope scope = ScopeProvider.CreateCoreScope(); @@ -1795,7 +1776,7 @@ private void PerformScheduledPublishingRelease(DateTime date, List x.Culture) .Distinct() @@ -1803,7 +1784,7 @@ private void PerformScheduledPublishingRelease(DateTime date, List 0) { _logger.LogWarning( "Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}", - d.Id, culture, string.Join(",", invalidProperties.Select(x => x.Alias))); + d.Id, + culture, + string.Join(",", invalidProperties.Select(x => x.Alias))); } - publishing &= tryPublish; //set the culture to be published + publishing &= tryPublish; // set the culture to be published if (!publishing) { } @@ -1856,21 +1838,19 @@ private void PerformScheduledPublishingRelease(DateTime date, List culturesToPublish, - IReadOnlyCollection allLangs) + private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet culturesToPublish, IReadOnlyCollection allLangs) { - //TODO: This does not support being able to return invalid property details to bubble up to the UI + // TODO: Th is does not support being able to return invalid property details to bubble up to the UI // variant content type - publish specified cultures // invariant content type - publish only the invariant culture @@ -1924,8 +1902,7 @@ private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet? SaveAndPublishBranch_ShouldPublish(ref HashSet? cultures, string c, - bool published, bool edited, bool isRoot, bool force) + private HashSet? SaveAndPublishBranch_ShouldPublish(ref HashSet? cultures, string c, bool published, bool edited, bool isRoot, bool force) { // if published, republish if (published) @@ -1959,8 +1936,7 @@ private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet - public IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = "*", - int userId = Constants.Security.SuperUserId) + public IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = "*", int userId = Constants.Security.SuperUserId) { // note: EditedValue and PublishedValue are objects here, so it is important to .Equals() // and not to == them, else we would be comparing references, and that is a bad thing @@ -1978,14 +1954,13 @@ public IEnumerable SaveAndPublishBranch(IContent content, bool fo if (!c.ContentType.VariesByCulture()) // invariant content type { - return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, - force); + return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force); } - if (culture != "*") // variant content type, specific culture + // variant content type, specific culture + if (culture != "*") { - return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, culture, - c.IsCulturePublished(culture), c.IsCultureEdited(culture), isRoot, force); + return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, culture, c.IsCulturePublished(culture), c.IsCultureEdited(culture), isRoot, force); } // variant content type, all cultures @@ -1995,8 +1970,7 @@ public IEnumerable SaveAndPublishBranch(IContent content, bool fo // others will have to 'republish this culture' foreach (var x in c.AvailableCultures) { - SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), - c.IsCultureEdited(x), isRoot, force); + SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force); } return culturesToPublish; @@ -2004,7 +1978,7 @@ public IEnumerable SaveAndPublishBranch(IContent content, bool fo // if not published, publish if force/root else do nothing return force || isRoot - ? new HashSet {"*"} // "*" means 'publish all' + ? new HashSet { "*" } // "*" means 'publish all' : null; // null means 'nothing to do' } @@ -2012,12 +1986,10 @@ public IEnumerable SaveAndPublishBranch(IContent content, bool fo } /// - public IEnumerable SaveAndPublishBranch(IContent content, bool force, string[] cultures, - int userId = Constants.Security.SuperUserId) + public IEnumerable SaveAndPublishBranch(IContent content, bool force, string[] cultures, int userId = Constants.Security.SuperUserId) { // note: EditedValue and PublishedValue are objects here, so it is important to .Equals() // and not to == them, else we would be comparing references, and that is a bad thing - cultures = cultures ?? Array.Empty(); // determines cultures to be published @@ -2029,8 +2001,7 @@ public IEnumerable SaveAndPublishBranch(IContent content, bool fo if (!c.ContentType.VariesByCulture()) // invariant content type { - return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, - force); + return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force); } // variant content type, specific cultures @@ -2040,8 +2011,7 @@ public IEnumerable SaveAndPublishBranch(IContent content, bool fo // others will have to 'republish this culture' foreach (var x in cultures) { - SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), - c.IsCultureEdited(x), isRoot, force); + SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, x, c.IsCulturePublished(x), c.IsCultureEdited(x), isRoot, force); } return culturesToPublish; @@ -2056,7 +2026,9 @@ public IEnumerable SaveAndPublishBranch(IContent content, bool fo return SaveAndPublishBranch(content, force, ShouldPublish, SaveAndPublishBranch_PublishCultures, userId); } - internal IEnumerable SaveAndPublishBranch(IContent document, bool force, + internal IEnumerable SaveAndPublishBranch( + IContent document, + bool force, Func?> shouldPublish, Func, IReadOnlyCollection, bool> publishCultures, int userId = Constants.Security.SuperUserId) @@ -2093,8 +2065,7 @@ internal IEnumerable SaveAndPublishBranch(IContent document, bool } // deal with the branch root - if it fails, abort - PublishResult? result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, - publishedDocuments, eventMessages, userId, allLangs); + PublishResult? result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, eventMessages, userId, allLangs); if (result != null) { results.Add(result); @@ -2114,10 +2085,10 @@ internal IEnumerable SaveAndPublishBranch(IContent document, bool do { count = 0; + // important to order by Path ASC so make it explicit in case defaults change // ReSharper disable once RedundantArgumentDefaultValue - foreach (IContent d in GetPagedDescendants(document.Id, page, pageSize, out _, - ordering: Ordering.By("Path", Direction.Ascending))) + foreach (IContent d in GetPagedDescendants(document.Id, page, pageSize, out _, ordering: Ordering.By("Path", Direction.Ascending))) { count++; @@ -2129,8 +2100,7 @@ internal IEnumerable SaveAndPublishBranch(IContent document, bool } // no need to check path here, parent has to be published here - result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, - publishedDocuments, eventMessages, userId, allLangs); + result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, eventMessages, userId, allLangs); if (result != null) { results.Add(result); @@ -2145,7 +2115,8 @@ internal IEnumerable SaveAndPublishBranch(IContent document, bool } page++; - } while (count > 0); + } + while (count > 0); Audit(AuditType.Publish, userId, document.Id, "Branch published"); @@ -2164,20 +2135,28 @@ internal IEnumerable SaveAndPublishBranch(IContent document, bool // shouldPublish: a function determining whether the document has changes that need to be published // note - 'force' is handled by 'editing' // publishValues: a function publishing values (using the appropriate PublishCulture calls) - private PublishResult? SaveAndPublishBranchItem(ICoreScope scope, IContent document, + private PublishResult? SaveAndPublishBranchItem( + ICoreScope scope, + IContent document, Func?> shouldPublish, - Func, IReadOnlyCollection, bool> publishCultures, + Func, IReadOnlyCollection, + bool> publishCultures, bool isRoot, ICollection publishedDocuments, - EventMessages evtMsgs, int userId, IReadOnlyCollection allLangs) + EventMessages evtMsgs, + int userId, + IReadOnlyCollection allLangs) { HashSet? culturesToPublish = shouldPublish(document); - if (culturesToPublish == null) // null = do not include + + // null = do not include + if (culturesToPublish == null) { return null; } - if (culturesToPublish.Count == 0) // empty = already published + // empty = already published + if (culturesToPublish.Count == 0) { return new PublishResult(PublishResultType.SuccessPublishAlready, evtMsgs, document); } @@ -2191,12 +2170,11 @@ internal IEnumerable SaveAndPublishBranch(IContent document, bool // publish & check if values are valid if (!publishCultures(document, culturesToPublish, allLangs)) { - //TODO: Based on this callback behavior there is no way to know which properties may have been invalid if this failed, see other results of FailedPublishContentInvalid + // TODO: Based on this callback behavior there is no way to know which properties may have been invalid if this failed, see other results of FailedPublishContentInvalid return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, document); } - PublishResult result = CommitDocumentChangesInternal(scope, document, evtMsgs, allLangs, - savingNotification.State, userId, true, isRoot); + PublishResult result = CommitDocumentChangesInternal(scope, document, evtMsgs, allLangs, savingNotification.State, userId, true, isRoot); if (result.Success) { publishedDocuments.Add(document); @@ -2258,9 +2236,8 @@ void DoDelete(IContent c) var total = long.MaxValue; while (total > 0) { - //get descendants - ordered from deepest to shallowest - IEnumerable descendants = GetPagedDescendants(content.Id, 0, pageSize, out total, - ordering: Ordering.By("Path", Direction.Descending)); + // get descendants - ordered from deepest to shallowest + IEnumerable descendants = GetPagedDescendants(content.Id, 0, pageSize, out total, ordering: Ordering.By("Path", Direction.Descending)); foreach (IContent c in descendants) { DoDelete(c); @@ -2270,7 +2247,7 @@ void DoDelete(IContent c) DoDelete(content); } - //TODO: both DeleteVersions methods below have an issue. Sort of. They do NOT take care of files the way + // TODO: both DeleteVersions methods below have an issue. Sort of. They do NOT take care of files the way // Delete does - for a good reason: the file may be referenced by other, non-deleted, versions. BUT, // if that's not the case, then the file will never be deleted, because when we delete the content, // the version referencing the file will not be there anymore. SO, we can leak files. @@ -2316,8 +2293,7 @@ public void DeleteVersions(int id, DateTime versionDate, int userId = Constants. /// Id of the version to delete /// Boolean indicating whether to delete versions prior to the versionId /// Optional Id of the User deleting versions of a Content object - public void DeleteVersion(int id, int versionId, bool deletePriorVersions, - int userId = Constants.Security.SuperUserId) + public void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = Constants.Security.SuperUserId) { EventMessages evtMsgs = EventMessagesFactory.Get(); @@ -2382,9 +2358,8 @@ public OperationResult MoveToRecycleBin(IContent content, int userId = Constants // if it's published we may want to force-unpublish it - that would be backward-compatible... but... // making a radical decision here: trashing is equivalent to moving under an unpublished node so // it's NOT unpublishing, only the content is now masked - allowing us to restore it if wanted - //if (content.HasPublishedVersion) - //{ } - + // if (content.HasPublishedVersion) + // { } PerformMoveLocked(content, Constants.System.RecycleBinContent, null, userId, moves, true); scope.Notifications.Publish( new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages)); @@ -2467,7 +2442,8 @@ public void Move(IContent content, int parentId, int userId = Constants.Security scope.Notifications.Publish( new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages)); - MoveEventInfo[] moveInfo = moves //changes + // changes + MoveEventInfo[] moveInfo = moves .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) .ToArray(); @@ -2482,9 +2458,7 @@ public void Move(IContent content, int parentId, int userId = Constants.Security // MUST be called from within WriteLock // trash indicates whether we are trashing, un-trashing, or not changing anything - private void PerformMoveLocked(IContent content, int parentId, IContent? parent, int userId, - ICollection<(IContent, string)> moves, - bool? trash) + private void PerformMoveLocked(IContent content, int parentId, IContent? parent, int userId, ICollection<(IContent, string)> moves, bool? trash) { content.WriterId = userId; content.ParentId = parentId; @@ -2497,18 +2471,18 @@ private void PerformMoveLocked(IContent content, int parentId, IContent? parent, moves.Add((content, content.Path)); // capture original path - //need to store the original path to lookup descendants based on it below + // need to store the original path to lookup descendants based on it below var originalPath = content.Path; // these will be updated by the repo because we changed parentId - //content.Path = (parent == null ? "-1" : parent.Path) + "," + content.Id; - //content.SortOrder = ((ContentRepository) repository).NextChildSortOrder(parentId); - //content.Level += levelDelta; + // content.Path = (parent == null ? "-1" : parent.Path) + "," + content.Id; + // content.SortOrder = ((ContentRepository) repository).NextChildSortOrder(parentId); + // content.Level += levelDelta; PerformMoveContentLocked(content, userId, trash); // if uow is not immediate, content.Path will be updated only when the UOW commits, // and because we want it now, we have to calculate it by ourselves - //paths[content.Id] = content.Path; + // paths[content.Id] = content.Path; paths[content.Id] = (parent == null ? parentId == Constants.System.RecycleBinContent ? "-1,-20" : Constants.System.RootString @@ -2532,7 +2506,8 @@ private void PerformMoveLocked(IContent content, int parentId, IContent? parent, descendant.Level += levelDelta; PerformMoveContentLocked(descendant, userId, trash); } - } while (total > pageSize); + } + while (total > pageSize); } private void PerformMoveContentLocked(IContent content, int userId, bool? trash) @@ -2614,8 +2589,7 @@ public bool RecycleBinSmells() /// Boolean indicating whether the copy should be related to the original /// Optional Id of the User copying the Content /// The newly created object - public IContent? Copy(IContent content, int parentId, bool relateToOriginal, - int userId = Constants.Security.SuperUserId) => Copy(content, parentId, relateToOriginal, true, userId); + public IContent? Copy(IContent content, int parentId, bool relateToOriginal, int userId = Constants.Security.SuperUserId) => Copy(content, parentId, relateToOriginal, true, userId); /// /// Copies an object by creating a new Content object of the same type and copies all data from @@ -2628,8 +2602,7 @@ public bool RecycleBinSmells() /// A value indicating whether to recursively copy children. /// Optional Id of the User copying the Content /// The newly created object - public IContent? Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, - int userId = Constants.Security.SuperUserId) + public IContent? Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = Constants.Security.SuperUserId) { EventMessages eventMessages = EventMessagesFactory.Get(); @@ -2648,7 +2621,6 @@ public bool RecycleBinSmells() // note - relateToOriginal is not managed here, // it's just part of the Copied event args so the RelateOnCopyHandler knows what to do // meaning that the event has to trigger for every copied content including descendants - var copies = new List>(); scope.WriteLock(Constants.Locks.ContentTree); @@ -2663,14 +2635,14 @@ public bool RecycleBinSmells() copy.CreatorId = userId; copy.WriterId = userId; - //get the current permissions, if there are any explicit ones they need to be copied + // get the current permissions, if there are any explicit ones they need to be copied EntityPermissionCollection currentPermissions = GetPermissions(content); currentPermissions.RemoveWhere(p => p.IsDefaultPermissions); // save and flush because we need the ID for the recursive Copying events _documentRepository.Save(copy); - //add permissions + // add permissions if (currentPermissions.Count > 0) { var permissionSet = new ContentPermissionSet(copy, currentPermissions); @@ -2679,9 +2651,10 @@ public bool RecycleBinSmells() // keep track of copies copies.Add(Tuple.Create(content, copy)); - var idmap = new Dictionary {[content.Id] = copy.Id}; + var idmap = new Dictionary { [content.Id] = copy.Id }; - if (recursive) // process descendants + // process descendants + if (recursive) { const int pageSize = 500; var page = 0; @@ -2702,8 +2675,7 @@ public bool RecycleBinSmells() descendantCopy.ParentId = parentId; if (scope.Notifications.PublishCancelable( - new ContentCopyingNotification(descendant, descendantCopy, parentId, - eventMessages))) + new ContentCopyingNotification(descendant, descendantCopy, parentId, eventMessages))) { continue; } @@ -2730,13 +2702,11 @@ public bool RecycleBinSmells() // not handling tags here, because // - tags should be handled by the content repository // - a copy is unpublished and therefore has no impact on tags in DB - scope.Notifications.Publish( new ContentTreeChangeNotification(copy, TreeChangeTypes.RefreshBranch, eventMessages)); foreach (Tuple x in copies) { - scope.Notifications.Publish(new ContentCopiedNotification(x.Item1, x.Item2, parentId, - relateToOriginal, eventMessages)); + scope.Notifications.Publish(new ContentCopiedNotification(x.Item1, x.Item2, parentId, relateToOriginal, eventMessages)); } Audit(AuditType.Copy, userId, content.Id); @@ -2772,7 +2742,7 @@ public bool SendToPublication(IContent? content, int userId = Constants.Security return false; } - //track the cultures changing for auditing + // track the cultures changing for auditing var culturesChanging = content.ContentType.VariesByCulture() ? string.Join(",", content.CultureInfos!.Values.Where(x => x.IsDirty()).Select(x => x.Culture)) : null; @@ -2782,7 +2752,7 @@ public bool SendToPublication(IContent? content, int userId = Constants.Security // in this particular case, determining which cultures have changed works with the above with names since it will // have always changed if it's been saved in the back office but that's not really fail safe. - //Save before raising event + // Save before raising event OperationResult saveResult = Save(content, userId); // always complete (but maybe return a failed status) @@ -2798,8 +2768,7 @@ public bool SendToPublication(IContent? content, int userId = Constants.Security if (culturesChanging != null) { - Audit(AuditType.SendToPublishVariant, userId, content.Id, - $"Send To Publish for cultures: {culturesChanging}", culturesChanging); + Audit(AuditType.SendToPublishVariant, userId, content.Id, $"Send To Publish for cultures: {culturesChanging}", culturesChanging); } else { @@ -2920,7 +2889,7 @@ private OperationResult Sort(ICoreScope scope, IContent[] itemsA, int userId, Ev _documentRepository.Save(content); } - //first saved, then sorted + // first saved, then sorted scope.Notifications.Publish( new ContentSavedNotification(itemsA, eventMessages).WithStateFrom(savingNotification)); scope.Notifications.Publish( @@ -2948,10 +2917,9 @@ public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportO if (report.FixedIssues.Count > 0) { - //The event args needs a content item so we'll make a fake one with enough properties to not cause a null ref - var root = new Content("root", -1, new ContentType(_shortStringHelper, -1)) {Id = -1, Key = Guid.Empty}; - scope.Notifications.Publish(new ContentTreeChangeNotification(root, TreeChangeTypes.RefreshAll, - EventMessagesFactory.Get())); + // The event args needs a content item so we'll make a fake one with enough properties to not cause a null ref + var root = new Content("root", -1, new ContentType(_shortStringHelper, -1)) { Id = -1, Key = Guid.Empty }; + scope.Notifications.Publish(new ContentTreeChangeNotification(root, TreeChangeTypes.RefreshAll, EventMessagesFactory.Get())); } return report; @@ -2986,8 +2954,7 @@ internal IEnumerable GetPublishedDescendantsLocked(IContent content) // beware! contents contains all published version below content // including those that are not directly published because below an unpublished content // these must be filtered out here - - var parents = new List {content.Id}; + var parents = new List { content.Id }; if (contents is not null) { foreach (IContent c in contents) @@ -3005,10 +2972,8 @@ internal IEnumerable GetPublishedDescendantsLocked(IContent content) #region Private Methods - private void Audit(AuditType type, int userId, int objectId, string? message = null, - string? parameters = null) => - _auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.Document.GetName(), message, - parameters)); + private void Audit(AuditType type, int userId, int objectId, string? message = null, string? parameters = null) => + _auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.Document.GetName(), message, parameters)); private bool IsDefaultCulture(IReadOnlyCollection? langs, string culture) => langs?.Any(x => x.IsDefault && x.IsoCode.InvariantEquals(culture)) ?? false; @@ -3032,27 +2997,31 @@ private bool IsMandatoryCulture(IReadOnlyCollection langs, string cul /// /// /// - private PublishResult StrategyCanPublish(ICoreScope scope, IContent content, bool checkPath, + private PublishResult StrategyCanPublish( + ICoreScope scope, + IContent content, + bool checkPath, IReadOnlyList? culturesPublishing, - IReadOnlyCollection? culturesUnpublishing, EventMessages evtMsgs, - IReadOnlyCollection allLangs, IDictionary? notificationState) + IReadOnlyCollection? culturesUnpublishing, + EventMessages evtMsgs, + IReadOnlyCollection allLangs, + IDictionary? notificationState) { // raise Publishing notification if (scope.Notifications.PublishCancelable( new ContentPublishingNotification(content, evtMsgs).WithState(notificationState))) { - _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", - content.Name, content.Id, "publishing was cancelled"); + _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "publishing was cancelled"); return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content); } var variesByCulture = content.ContentType.VariesByCulture(); + // If it's null it's invariant CultureImpact[] impactsToPublish = culturesPublishing == null - ? new[] {CultureImpact.Invariant} //if it's null it's invariant + ? new[] { CultureImpact.Invariant } : culturesPublishing.Select(x => - CultureImpact.Explicit(x, - allLangs.Any(lang => lang.IsoCode.InvariantEquals(x) && lang.IsMandatory))).ToArray(); + CultureImpact.Explicit(x, allLangs.Any(lang => lang.IsoCode.InvariantEquals(x) && lang.IsMandatory))).ToArray(); // publish the culture(s) if (!impactsToPublish.All(content.PublishCulture)) @@ -3060,18 +3029,18 @@ private PublishResult StrategyCanPublish(ICoreScope scope, IContent content, boo return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content); } - //validate the property values + // Validate the property values IProperty[]? invalidProperties = null; if (!impactsToPublish.All(x => _propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties, x))) { return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content) { - InvalidProperties = invalidProperties + InvalidProperties = invalidProperties, }; } - //Check if mandatory languages fails, if this fails it will mean anything that the published flag on the document will + // Check if mandatory languages fails, if this fails it will mean anything that the published flag on the document will // be changed to Unpublished and any culture currently published will not be visible. if (variesByCulture) { @@ -3089,7 +3058,6 @@ private PublishResult StrategyCanPublish(ICoreScope scope, IContent content, boo return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content); } - // missing mandatory culture = cannot be published IEnumerable mandatoryCultures = allLangs.Where(x => x.IsMandatory).Select(x => x.IsoCode); var mandatoryMissing = mandatoryCultures.Any(x => @@ -3109,14 +3077,18 @@ private PublishResult StrategyCanPublish(ICoreScope scope, IContent content, boo // either because it is 'publishing' or because it already has a published version if (content.PublishedState != PublishedState.Publishing && content.PublishedVersionId == 0) { - _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", - content.Name, content.Id, "document does not have published values"); + _logger.LogInformation( + "Document {ContentName} (id={ContentId}) cannot be published: {Reason}", + content.Name, + content.Id, + "document does not have published values"); return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content); } ContentScheduleCollection contentSchedule = _documentRepository.GetContentSchedule(content.Id); - //loop over each culture publishing - or string.Empty for invariant - foreach (var culture in culturesPublishing ?? new[] {string.Empty}) + + // loop over each culture publishing - or string.Empty for invariant + foreach (var culture in culturesPublishing ?? new[] { string.Empty }) { // ensure that the document status is correct // note: culture will be string.Empty for invariant @@ -3126,43 +3098,52 @@ private PublishResult StrategyCanPublish(ICoreScope scope, IContent content, boo if (!variesByCulture) { _logger.LogInformation( - "Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, - content.Id, "document has expired"); + "Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, content.Id, "document has expired"); } else { _logger.LogInformation( - "Document {ContentName} (id={ContentId}) culture {Culture} cannot be published: {Reason}", - content.Name, content.Id, culture, "document culture has expired"); + "Document {ContentName} (id={ContentId}) culture {Culture} cannot be published: {Reason}", content.Name, content.Id, culture, "document culture has expired"); } return new PublishResult( !variesByCulture - ? PublishResultType.FailedPublishHasExpired - : PublishResultType.FailedPublishCultureHasExpired, evtMsgs, content); + ? PublishResultType.FailedPublishHasExpired : PublishResultType.FailedPublishCultureHasExpired, + evtMsgs, + content); case ContentStatus.AwaitingRelease: if (!variesByCulture) { _logger.LogInformation( - "Document {ContentName} (id={ContentId}) cannot be published: {Reason}", content.Name, - content.Id, "document is awaiting release"); + "Document {ContentName} (id={ContentId}) cannot be published: {Reason}", + content.Name, + content.Id, + "document is awaiting release"); } else { _logger.LogInformation( "Document {ContentName} (id={ContentId}) culture {Culture} cannot be published: {Reason}", - content.Name, content.Id, culture, "document is culture awaiting release"); + content.Name, + content.Id, + culture, + "document is culture awaiting release"); } return new PublishResult( !variesByCulture ? PublishResultType.FailedPublishAwaitingRelease - : PublishResultType.FailedPublishCultureAwaitingRelease, evtMsgs, content); + : PublishResultType.FailedPublishCultureAwaitingRelease, + evtMsgs, + content); case ContentStatus.Trashed: - _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", - content.Name, content.Id, "document is trashed"); + _logger.LogInformation( + "Document {ContentName} (id={ContentId}) cannot be published: {Reason}", + content.Name, + content.Id, + "document is trashed"); return new PublishResult(PublishResultType.FailedPublishIsTrashed, evtMsgs, content); } } @@ -3175,13 +3156,16 @@ private PublishResult StrategyCanPublish(ICoreScope scope, IContent content, boo var pathIsOk = content.ParentId == Constants.System.Root || IsPathPublished(GetParent(content)); if (!pathIsOk) { - _logger.LogInformation("Document {ContentName} (id={ContentId}) cannot be published: {Reason}", - content.Name, content.Id, "parent is not published"); + _logger.LogInformation( + "Document {ContentName} (id={ContentId}) cannot be published: {Reason}", + content.Name, + content.Id, + "parent is not published"); return new PublishResult(PublishResultType.FailedPublishPathNotPublished, evtMsgs, content); } } - //If we are both publishing and unpublishing cultures, then return a mixed status + // If we are both publishing and unpublishing cultures, then return a mixed status if (variesByCulture && culturesPublishing?.Count > 0 && culturesUnpublishing?.Count > 0) { return new PublishResult(PublishResultType.SuccessMixedCulture, evtMsgs, content); @@ -3202,14 +3186,16 @@ private PublishResult StrategyCanPublish(ICoreScope scope, IContent content, boo /// It is assumed that all publishing checks have passed before calling this method like /// /// - private PublishResult StrategyPublish(IContent content, - IReadOnlyCollection? culturesPublishing, IReadOnlyCollection? culturesUnpublishing, + private PublishResult StrategyPublish( + IContent content, + IReadOnlyCollection? culturesPublishing, + IReadOnlyCollection? culturesUnpublishing, EventMessages evtMsgs) { // change state to publishing content.PublishedState = PublishedState.Publishing; - //if this is a variant then we need to log which cultures have been published/unpublished and return an appropriate result + // if this is a variant then we need to log which cultures have been published/unpublished and return an appropriate result if (content.ContentType.VariesByCulture()) { if (content.Published && culturesUnpublishing?.Count == 0 && culturesPublishing?.Count == 0) @@ -3221,14 +3207,18 @@ private PublishResult StrategyPublish(IContent content, { _logger.LogInformation( "Document {ContentName} (id={ContentId}) cultures: {Cultures} have been unpublished.", - content.Name, content.Id, string.Join(",", culturesUnpublishing)); + content.Name, + content.Id, + string.Join(",", culturesUnpublishing)); } if (culturesPublishing?.Count > 0) { _logger.LogInformation( "Document {ContentName} (id={ContentId}) cultures: {Cultures} have been published.", - content.Name, content.Id, string.Join(",", culturesPublishing)); + content.Name, + content.Id, + string.Join(",", culturesPublishing)); } if (culturesUnpublishing?.Count > 0 && culturesPublishing?.Count > 0) @@ -3244,8 +3234,7 @@ private PublishResult StrategyPublish(IContent content, return new PublishResult(PublishResultType.SuccessPublishCulture, evtMsgs, content); } - _logger.LogInformation("Document {ContentName} (id={ContentId}) has been published.", content.Name, - content.Id); + _logger.LogInformation("Document {ContentName} (id={ContentId}) has been published.", content.Name, content.Id); return new PublishResult(evtMsgs, content); } @@ -3262,8 +3251,7 @@ private PublishResult StrategyCanUnpublish(ICoreScope scope, IContent content, E if (scope.Notifications.PublishCancelable(new ContentUnpublishingNotification(content, evtMsgs))) { _logger.LogInformation( - "Document {ContentName} (id={ContentId}) cannot be unpublished: unpublishing was cancelled.", - content.Name, content.Id); + "Document {ContentName} (id={ContentId}) cannot be unpublished: unpublishing was cancelled.", content.Name, content.Id); return new PublishResult(PublishResultType.FailedUnpublishCancelledByEvent, evtMsgs, content); } @@ -3284,7 +3272,7 @@ private PublishResult StrategyUnpublish(IContent content, EventMessages evtMsgs) { var attempt = new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content); - //TODO: What is this check?? we just created this attempt and of course it is Success?! + // TODO: What is this check?? we just created this attempt and of course it is Success?! if (attempt.Success == false) { return attempt; @@ -3293,7 +3281,6 @@ private PublishResult StrategyUnpublish(IContent content, EventMessages evtMsgs) // if the document has any release dates set to before now, // they should be removed so they don't interrupt an unpublish // otherwise it would remain released == published - ContentScheduleCollection contentSchedule = _documentRepository.GetContentSchedule(content.Id); IReadOnlyList pastReleases = contentSchedule.GetPending(ContentScheduleAction.Expire, DateTime.Now); @@ -3305,16 +3292,15 @@ private PublishResult StrategyUnpublish(IContent content, EventMessages evtMsgs) if (pastReleases.Count > 0) { _logger.LogInformation( - "Document {ContentName} (id={ContentId}) had its release date removed, because it was unpublished.", - content.Name, content.Id); + "Document {ContentName} (id={ContentId}) had its release date removed, because it was unpublished.", content.Name, content.Id); } _documentRepository.PersistContentSchedule(content, contentSchedule); + // change state to unpublishing content.PublishedState = PublishedState.Unpublishing; - _logger.LogInformation("Document {ContentName} (id={ContentId}) has been unpublished.", content.Name, - content.Id); + _logger.LogInformation("Document {ContentName} (id={ContentId}) has been unpublished.", content.Name, content.Id); return attempt; } @@ -3342,7 +3328,6 @@ public void DeleteOfTypes(IEnumerable contentTypeIds, int userId = Constant // of a different type, move them to the recycle bin, then permanently delete the content items. // The main problem with this is that for every content item being deleted, events are raised... // which we need for many things like keeping caches in sync, but we can surely do this MUCH better. - var changes = new List>(); var moves = new List<(IContent, string)>(); var contentTypeIdsA = contentTypeIds.ToArray(); @@ -3351,7 +3336,6 @@ public void DeleteOfTypes(IEnumerable contentTypeIds, int userId = Constant // using an immediate uow here because we keep making changes with // PerformMoveLocked and DeleteLocked that must be applied immediately, // no point queuing operations - // using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { scope.WriteLock(Constants.Locks.ContentTree); @@ -3409,8 +3393,7 @@ public void DeleteOfTypes(IEnumerable contentTypeIds, int userId = Constant scope.Notifications.Publish(new ContentTreeChangeNotification(changes, eventMessages)); - Audit(AuditType.Delete, userId, Constants.System.Root, - $"Delete content of type {string.Join(",", contentTypeIdsA)}"); + Audit(AuditType.Delete, userId, Constants.System.Root, $"Delete content of type {string.Join(",", contentTypeIdsA)}"); scope.Complete(); } @@ -3423,7 +3406,7 @@ public void DeleteOfTypes(IEnumerable contentTypeIds, int userId = Constant /// Id of the /// Optional id of the user deleting the media public void DeleteOfType(int contentTypeId, int userId = Constants.Security.SuperUserId) => - DeleteOfTypes(new[] {contentTypeId}, userId); + DeleteOfTypes(new[] { contentTypeId }, userId); private IContentType GetContentType(ICoreScope scope, string contentTypeAlias) { @@ -3434,8 +3417,7 @@ private IContentType GetContentType(ICoreScope scope, string contentTypeAlias) if (string.IsNullOrWhiteSpace(contentTypeAlias)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", - nameof(contentTypeAlias)); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(contentTypeAlias)); } scope.ReadLock(Constants.Locks.ContentTypes); @@ -3461,8 +3443,7 @@ private IContentType GetContentType(string contentTypeAlias) if (string.IsNullOrWhiteSpace(contentTypeAlias)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", - nameof(contentTypeAlias)); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(contentTypeAlias)); } using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) @@ -3509,7 +3490,7 @@ public void SaveBlueprint(IContent content, int userId = Constants.Security.Supe { EventMessages evtMsgs = EventMessagesFactory.Get(); - //always ensure the blueprint is at the root + // always ensure the blueprint is at the root if (content.ParentId != -1) { content.ParentId = -1; @@ -3530,8 +3511,7 @@ public void SaveBlueprint(IContent content, int userId = Constants.Security.Supe _documentBlueprintRepository.Save(content); - Audit(AuditType.Save, Constants.Security.SuperUserId, content.Id, - $"Saved content template: {content.Name}"); + Audit(AuditType.Save, Constants.Security.SuperUserId, content.Id, $"Saved content template: {content.Name}"); scope.Notifications.Publish(new ContentSavedBlueprintNotification(content, evtMsgs)); @@ -3552,10 +3532,9 @@ public void DeleteBlueprint(IContent content, int userId = Constants.Security.Su } } - private static readonly string?[] ArrayOfOneNullString = {null}; + private static readonly string?[] ArrayOfOneNullString = { null }; - public IContent CreateContentFromBlueprint(IContent blueprint, string name, - int userId = Constants.Security.SuperUserId) + public IContent CreateContentFromBlueprint(IContent blueprint, string name, int userId = Constants.Security.SuperUserId) { if (blueprint == null) { @@ -3575,8 +3554,7 @@ public IContent CreateContentFromBlueprint(IContent blueprint, string name, cultures = blueprint.CultureInfos.Values.Select(x => x.Culture); using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - if (blueprint.CultureInfos.TryGetValue(_languageRepository.GetDefaultIsoCode(), - out ContentCultureInfos defaultCulture)) + if (blueprint.CultureInfos.TryGetValue(_languageRepository.GetDefaultIsoCode(), out ContentCultureInfos defaultCulture)) { defaultCulture.Name = name; } @@ -3621,8 +3599,7 @@ public IEnumerable GetBlueprintsForContentTypes(params int[] contentTy } } - public void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, - int userId = Constants.Security.SuperUserId) + public void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, int userId = Constants.Security.SuperUserId) { EventMessages evtMsgs = EventMessagesFactory.Get(); @@ -3657,7 +3634,7 @@ public void DeleteBlueprintsOfTypes(IEnumerable contentTypeIds, } public void DeleteBlueprintsOfType(int contentTypeId, int userId = Constants.Security.SuperUserId) => - DeleteBlueprintsOfTypes(new[] {contentTypeId}, userId); + DeleteBlueprintsOfTypes(new[] { contentTypeId }, userId); #endregion } diff --git a/src/Umbraco.Core/Services/ContentServiceExtensions.cs b/src/Umbraco.Core/Services/ContentServiceExtensions.cs index 73893ba61e3c..6fd538181c51 100644 --- a/src/Umbraco.Core/Services/ContentServiceExtensions.cs +++ b/src/Umbraco.Core/Services/ContentServiceExtensions.cs @@ -14,13 +14,16 @@ namespace Umbraco.Extensions; /// public static class ContentServiceExtensions { + #region RTE Anchor values + + private static readonly Regex AnchorRegex = new("", RegexOptions.Compiled); + public static IEnumerable? GetByIds(this IContentService contentService, IEnumerable ids) { var guids = new List(); foreach (Udi udi in ids) { - var guidUdi = udi as GuidUdi; - if (guidUdi is null) + if (udi is not GuidUdi guidUdi) { throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by content"); @@ -44,14 +47,13 @@ public static class ContentServiceExtensions public static IContent CreateContent(this IContentService contentService, string name, Udi parentId, string contentTypeAlias, int userId = Constants.Security.SuperUserId) { - var guidUdi = parentId as GuidUdi; - if (guidUdi is null) + if (parentId is not GuidUdi guidUdi) { throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by content"); } - IContent parent = contentService.GetById(guidUdi.Guid); + IContent? parent = contentService.GetById(guidUdi.Guid); return contentService.Create(name, parent, contentTypeAlias, userId); } @@ -63,15 +65,11 @@ public static IContent CreateContent(this IContentService contentService, string public static void RemoveContentPermissions(this IContentService contentService, int contentId) => contentService.SetPermissions(new EntityPermissionSet(contentId, new EntityPermissionCollection())); - #region RTE Anchor values - - private static readonly Regex AnchorRegex = new("", RegexOptions.Compiled); - public static IEnumerable GetAnchorValuesFromRTEs(this IContentService contentService, int id, string? culture = "*") { var result = new List(); - IContent content = contentService.GetById(id); + IContent? content = contentService.GetById(id); if (content is not null) { @@ -92,8 +90,8 @@ public static IEnumerable GetAnchorValuesFromRTEs(this IContentService c return result; } - - public static IEnumerable GetAnchorValuesFromRTEContent(this IContentService contentService, + public static IEnumerable GetAnchorValuesFromRTEContent( + this IContentService contentService, string rteContent) { var result = new List(); diff --git a/src/Umbraco.Core/Services/ContentTypeBaseServiceProvider.cs b/src/Umbraco.Core/Services/ContentTypeBaseServiceProvider.cs index 2cd0d6f2f9c1..36a790b9f689 100644 --- a/src/Umbraco.Core/Services/ContentTypeBaseServiceProvider.cs +++ b/src/Umbraco.Core/Services/ContentTypeBaseServiceProvider.cs @@ -8,8 +8,7 @@ public class ContentTypeBaseServiceProvider : IContentTypeBaseServiceProvider private readonly IMediaTypeService _mediaTypeService; private readonly IMemberTypeService _memberTypeService; - public ContentTypeBaseServiceProvider(IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, - IMemberTypeService memberTypeService) + public ContentTypeBaseServiceProvider(IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService) { _contentTypeService = contentTypeService; _mediaTypeService = mediaTypeService; @@ -32,7 +31,8 @@ public IContentTypeBaseService For(IContentBase contentBase) case IMember _: return _memberTypeService; default: - throw new ArgumentException($"Invalid contentBase type: {contentBase.GetType().FullName}", + throw new ArgumentException( + $"Invalid contentBase type: {contentBase.GetType().FullName}", nameof(contentBase)); } } diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index 2a610903532e..e655d79f0c5b 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -23,13 +23,14 @@ public ContentTypeService(ICoreScopeProvider provider, ILoggerFactory loggerFact ContentService = contentService; // beware! order is important to avoid deadlocks - protected override int[] ReadLockIds { get; } = {Constants.Locks.ContentTypes}; - protected override int[] WriteLockIds { get; } = {Constants.Locks.ContentTree, Constants.Locks.ContentTypes}; + protected override int[] ReadLockIds { get; } = { Constants.Locks.ContentTypes }; - private IContentService ContentService { get; } + protected override int[] WriteLockIds { get; } = { Constants.Locks.ContentTree, Constants.Locks.ContentTypes }; protected override Guid ContainedObjectType => Constants.ObjectTypes.DocumentType; + private IContentService ContentService { get; } + /// /// Gets all property type aliases across content, media and member types. /// @@ -90,28 +91,36 @@ protected override void DeleteItemsOfTypes(IEnumerable typeIds) #region Notifications - protected override SavingNotification GetSavingNotification(IContentType item, + protected override SavingNotification GetSavingNotification( + IContentType item, EventMessages eventMessages) => new ContentTypeSavingNotification(item, eventMessages); - protected override SavingNotification GetSavingNotification(IEnumerable items, + protected override SavingNotification GetSavingNotification( + IEnumerable items, EventMessages eventMessages) => new ContentTypeSavingNotification(items, eventMessages); - protected override SavedNotification GetSavedNotification(IContentType item, + protected override SavedNotification GetSavedNotification( + IContentType item, EventMessages eventMessages) => new ContentTypeSavedNotification(item, eventMessages); - protected override SavedNotification GetSavedNotification(IEnumerable items, + protected override SavedNotification GetSavedNotification( + IEnumerable items, EventMessages eventMessages) => new ContentTypeSavedNotification(items, eventMessages); - protected override DeletingNotification GetDeletingNotification(IContentType item, + protected override DeletingNotification GetDeletingNotification( + IContentType item, EventMessages eventMessages) => new ContentTypeDeletingNotification(item, eventMessages); - protected override DeletingNotification GetDeletingNotification(IEnumerable items, + protected override DeletingNotification GetDeletingNotification( + IEnumerable items, EventMessages eventMessages) => new ContentTypeDeletingNotification(items, eventMessages); - protected override DeletedNotification GetDeletedNotification(IEnumerable items, + protected override DeletedNotification GetDeletedNotification( + IEnumerable items, EventMessages eventMessages) => new ContentTypeDeletedNotification(items, eventMessages); - protected override MovingNotification GetMovingNotification(MoveEventInfo moveInfo, + protected override MovingNotification GetMovingNotification( + MoveEventInfo moveInfo, EventMessages eventMessages) => new ContentTypeMovingNotification(moveInfo, eventMessages); protected override MovedNotification GetMovedNotification( diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index 5970331b2bf6..9026a18ed76e 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -234,7 +234,7 @@ protected void ValidateLocked(TItem compositionContentType) continue; } - IContentTypeComposition contentTypeDependency = allContentTypes.FirstOrDefault(x => + IContentTypeComposition? contentTypeDependency = allContentTypes.FirstOrDefault(x => x.Alias.Equals(dependency.Alias, StringComparison.InvariantCultureIgnoreCase)); if (contentTypeDependency == null) { @@ -353,7 +353,7 @@ internal IEnumerable> ComposeContentTypeChanges(params private static void AddChange(ICollection> changes, TItem contentType, ContentTypeChangeTypes changeTypes) { - ContentTypeChange change = changes.FirstOrDefault(x => x.Item == contentType); + ContentTypeChange? change = changes.FirstOrDefault(x => x.Item == contentType); if (change == null) { changes.Add(new ContentTypeChange(contentType, changeTypes)); @@ -435,7 +435,7 @@ public IEnumerable GetChildren(Guid id) using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { scope.ReadLock(ReadLockIds); - TItem found = Get(id); + TItem? found = Get(id); if (found == null) { return Enumerable.Empty(); @@ -462,7 +462,7 @@ public bool HasChildren(Guid id) using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { scope.ReadLock(ReadLockIds); - TItem found = Get(id); + TItem? found = Get(id); if (found == null) { return false; @@ -507,7 +507,7 @@ public IEnumerable GetDescendants(int id, bool andSelf) var descendants = new List(); if (andSelf) { - TItem self = Repository.Get(id); + TItem? self = Repository.Get(id); if (self is not null) { descendants.Add(self); @@ -905,7 +905,7 @@ public TItem Copy(TItem original, string alias, string name, TItem? parent) { if (containerId > 0) { - EntityContainer container = _containerRepository?.Get(containerId); + EntityContainer? container = _containerRepository?.Get(containerId); if (container == null) { throw new DataOperationException(MoveOperationStatusType @@ -927,7 +927,7 @@ public TItem Copy(TItem original, string alias, string name, TItem? parent) // all other compositions remain in place in the copied content type if (copy.ParentId > 0) { - TItem parent = Repository.Get(copy.ParentId); + TItem? parent = Repository.Get(copy.ParentId); if (parent != null) { copy.RemoveContentType(parent.Alias); diff --git a/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs b/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs index cfa1281513cd..5ae8da3a1236 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceExtensions.cs @@ -60,9 +60,9 @@ public static ContentTypeAvailableCompositionsResults GetAvailableCompositeConte ? Array.Empty() : filterPropertyTypes.Where(x => !x.IsNullOrWhiteSpace()).ToArray(); - //create the full list of property types to use as the filter - //this is the combination of all property type aliases found in the content types passed in for the filter - //as well as the specific property types passed in for the filter + // create the full list of property types to use as the filter + // this is the combination of all property type aliases found in the content types passed in for the filter + // as well as the specific property types passed in for the filter filterPropertyTypes = allContentTypes .Where(c => filterContentTypes.InvariantContains(c.Alias)) .SelectMany(c => c.PropertyTypes) @@ -77,7 +77,7 @@ public static ContentTypeAvailableCompositionsResults GetAvailableCompositeConte allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == sourceId)).ToArray(); if (isUsing.Length > 0) { - //if already in use a composition, do not allow any composited types + // if already in use a composition, do not allow any composited types return new ContentTypeAvailableCompositionsResults(); } @@ -103,19 +103,19 @@ public static ContentTypeAvailableCompositionsResults GetAvailableCompositeConte list.Add(x); } - //At this point we have a list of content types that 'could' be compositions + // At this point we have a list of content types that 'could' be compositions - //now we'll filter this list based on the filters requested + // now we'll filter this list based on the filters requested var filtered = list .Where(x => { - //need to filter any content types that are included in this list + // need to filter any content types that are included in this list return filterContentTypes.Any(c => c.InvariantEquals(x.Alias)) == false; }) .Where(x => { - //need to filter any content types that have matching property aliases that are included in this list - //ensure that we don't return if there's any overlapping property aliases from the filtered ones specified + // need to filter any content types that have matching property aliases that are included in this list + // ensure that we don't return if there's any overlapping property aliases from the filtered ones specified return filterPropertyTypes.Intersect( x.PropertyTypes.Select(p => p.Alias), StringComparer.InvariantCultureIgnoreCase).Any() == false; @@ -123,13 +123,14 @@ public static ContentTypeAvailableCompositionsResults GetAvailableCompositeConte .OrderBy(x => x.Name) .ToList(); - //get ancestor ids - we will filter all ancestors + // get ancestor ids - we will filter all ancestors IContentTypeComposition[] ancestors = GetAncestors(source, allContentTypes); var ancestorIds = ancestors.Select(x => x.Id).ToArray(); - //now we can create our result based on what is still available and the ancestors + // now we can create our result based on what is still available and the ancestors var result = list - //not itself + + // not itself .Where(x => x.Id != sourceId) .OrderBy(x => x.Name) .Select(composition => filtered.Contains(composition) @@ -139,8 +140,8 @@ public static ContentTypeAvailableCompositionsResults GetAvailableCompositeConte return new ContentTypeAvailableCompositionsResults(ancestors, result); } - - private static IContentTypeComposition[] GetAncestors(IContentTypeComposition? ctype, + private static IContentTypeComposition[] GetAncestors( + IContentTypeComposition? ctype, IContentTypeComposition[] allContentTypes) { if (ctype == null) @@ -152,7 +153,7 @@ private static IContentTypeComposition[] GetAncestors(IContentTypeComposition? c var parentId = ctype.ParentId; while (parentId > 0) { - IContentTypeComposition parent = allContentTypes.FirstOrDefault(x => x.Id == parentId); + IContentTypeComposition? parent = allContentTypes.FirstOrDefault(x => x.Id == parentId); if (parent != null) { ancestors.Add(parent); diff --git a/src/Umbraco.Core/Services/ContentVersionService.cs b/src/Umbraco.Core/Services/ContentVersionService.cs index 79df9bb34843..24443a3957db 100644 --- a/src/Umbraco.Core/Services/ContentVersionService.cs +++ b/src/Umbraco.Core/Services/ContentVersionService.cs @@ -39,13 +39,13 @@ public ContentVersionService( /// public IReadOnlyCollection PerformContentVersionCleanup(DateTime asAtDate) => + // Media - ignored // Members - ignored CleanupDocumentVersions(asAtDate); /// - public IEnumerable? GetPagedContentVersions(int contentId, long pageIndex, int pageSize, - out long totalRecords, string? culture = null) + public IEnumerable? GetPagedContentVersions(int contentId, long pageIndex, int pageSize, out long totalRecords, string? culture = null) { if (pageIndex < 0) { @@ -61,8 +61,7 @@ public IReadOnlyCollection PerformContentVersionCleanup(Date { var languageId = _languageRepository.GetIdByIsoCode(culture, true); scope.ReadLock(Constants.Locks.ContentTree); - return _documentVersionRepository.GetPagedItemsByContentId(contentId, pageIndex, pageSize, out totalRecords, - languageId); + return _documentVersionRepository.GetPagedItemsByContentId(contentId, pageIndex, pageSize, out totalRecords, languageId); } } @@ -180,8 +179,7 @@ private IReadOnlyCollection CleanupDocumentVersions(DateTime using (_scopeProvider.CreateCoreScope(autoComplete: true)) { - Audit(AuditType.Delete, Constants.Security.SuperUserId, -1, - $"Removed {versionsToDelete.Count} ContentVersion(s) according to cleanup policy"); + Audit(AuditType.Delete, Constants.Security.SuperUserId, -1, $"Removed {versionsToDelete.Count} ContentVersion(s) according to cleanup policy"); } return versionsToDelete; diff --git a/src/Umbraco.Core/Services/DashboardService.cs b/src/Umbraco.Core/Services/DashboardService.cs index c1e928e7438b..de8fca066b69 100644 --- a/src/Umbraco.Core/Services/DashboardService.cs +++ b/src/Umbraco.Core/Services/DashboardService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Dashboards; +using Umbraco.Cms.Core.Dashboards; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Extensions; @@ -13,8 +13,8 @@ public class DashboardService : IDashboardService private readonly DashboardCollection _dashboardCollection; private readonly ILocalizedTextService _localizedText; - // TODO: Unit test all this!!! :/ + // TODO: Unit test all this!!! :/ private readonly ISectionService _sectionService; public DashboardService(ISectionService sectionService, DashboardCollection dashboardCollection, @@ -25,7 +25,6 @@ public DashboardService(ISectionService sectionService, DashboardCollection dash _localizedText = localizedText ?? throw new ArgumentNullException(nameof(localizedText)); } - /// public IEnumerable> GetDashboards(string section, IUser? currentUser) { @@ -45,13 +44,13 @@ public IEnumerable> GetDashboards(string section, IUser? current throw new NotSupportedException("Legacy UserControl (.ascx) dashboards are no longer supported."); } - var dashboards = new List {dashboard}; + var dashboards = new List { dashboard }; tabs.Add(new Tab { Id = tabId++, Label = _localizedText.Localize("dashboardTabs", dashboard.Alias), Alias = dashboard.Alias, - Properties = dashboards + Properties = dashboards, }); } @@ -62,6 +61,34 @@ public IEnumerable> GetDashboards(string section, IUser? current public IDictionary>> GetDashboards(IUser? currentUser) => _sectionService .GetSections().ToDictionary(x => x.Alias, x => GetDashboards(x.Alias, currentUser)); + private static (IAccessRule[], IAccessRule[], IAccessRule[]) GroupRules(IEnumerable rules) + { + IAccessRule[]? denyRules = null, grantRules = null, grantBySectionRules = null; + + IEnumerable> groupedRules = rules.GroupBy(x => x.Type); + foreach (IGrouping group in groupedRules) + { + IAccessRule[] a = group.ToArray(); + switch (group.Key) + { + case AccessRuleType.Deny: + denyRules = a; + break; + case AccessRuleType.Grant: + grantRules = a; + break; + case AccessRuleType.GrantBySection: + grantBySectionRules = a; + break; + default: + throw new NotSupportedException($"The '{group.Key}'-AccessRuleType is not supported."); + } + } + + return (denyRules ?? Array.Empty(), grantRules ?? Array.Empty(), + grantBySectionRules ?? Array.Empty()); + } + private bool CheckUserAccessByRules(IUser user, ISectionService sectionService, IEnumerable rules) { if (user.Id == Constants.Security.SuperUserId) @@ -119,7 +146,7 @@ private bool CheckUserAccessByRules(IUser user, ISectionService sectionService, // check if this item has any deny arguments, if so check if the user is in one of the denied user groups, if so they will // be denied to see it no matter what - assignedUserGroups = assignedUserGroups ?? user.Groups.Select(x => x.Alias).ToArray(); + assignedUserGroups ??= user.Groups.Select(x => x.Alias).ToArray(); var deniedUserGroups = denyRules.SelectMany(g => g.Value?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty()) @@ -132,32 +159,4 @@ private bool CheckUserAccessByRules(IUser user, ISectionService sectionService, return hasAccess; } - - private static (IAccessRule[], IAccessRule[], IAccessRule[]) GroupRules(IEnumerable rules) - { - IAccessRule[]? denyRules = null, grantRules = null, grantBySectionRules = null; - - IEnumerable> groupedRules = rules.GroupBy(x => x.Type); - foreach (IGrouping group in groupedRules) - { - IAccessRule[] a = group.ToArray(); - switch (group.Key) - { - case AccessRuleType.Deny: - denyRules = a; - break; - case AccessRuleType.Grant: - grantRules = a; - break; - case AccessRuleType.GrantBySection: - grantBySectionRules = a; - break; - default: - throw new NotSupportedException($"The '{group.Key.ToString()}'-AccessRuleType is not supported."); - } - } - - return (denyRules ?? Array.Empty(), grantRules ?? Array.Empty(), - grantBySectionRules ?? Array.Empty()); - } } diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 86c30da7a7b8..4c40aa45408c 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -30,19 +30,45 @@ public class DataTypeService : RepositoryService, IDataTypeService private readonly IEditorConfigurationParser _editorConfigurationParser; private readonly IEntityRepository _entityRepository; private readonly IIOHelper _ioHelper; - private readonly IJsonSerializer _jsonSerializer; - private readonly ILocalizationService _localizationService; - private readonly ILocalizedTextService _localizedTextService; - private readonly IShortStringHelper _shortStringHelper; - [Obsolete("Please use constructor that takes an ")] public DataTypeService( IDataValueEditorFactory dataValueEditorFactory, - ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - IDataTypeRepository dataTypeRepository, IDataTypeContainerRepository dataTypeContainerRepository, - IAuditRepository auditRepository, IEntityRepository entityRepository, + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IDataTypeRepository dataTypeRepository, + IDataTypeContainerRepository dataTypeContainerRepository, + IAuditRepository auditRepository, + IEntityRepository entityRepository, IContentTypeRepository contentTypeRepository, - IIOHelper ioHelper, ILocalizedTextService localizedTextService, ILocalizationService localizationService, + IIOHelper ioHelper, + IEditorConfigurationParser editorConfigurationParser) + : base(provider, loggerFactory, eventMessagesFactory) + { + _dataValueEditorFactory = dataValueEditorFactory; + _dataTypeRepository = dataTypeRepository; + _dataTypeContainerRepository = dataTypeContainerRepository; + _auditRepository = auditRepository; + _entityRepository = entityRepository; + _contentTypeRepository = contentTypeRepository; + _ioHelper = ioHelper; + _editorConfigurationParser = editorConfigurationParser; + } + + [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] + public DataTypeService( + IDataValueEditorFactory dataValueEditorFactory, + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IDataTypeRepository dataTypeRepository, + IDataTypeContainerRepository dataTypeContainerRepository, + IAuditRepository auditRepository, + IEntityRepository entityRepository, + IContentTypeRepository contentTypeRepository, + IIOHelper ioHelper, + ILocalizedTextService localizedTextService, + ILocalizationService localizationService, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer) : this( @@ -62,43 +88,27 @@ public DataTypeService( jsonSerializer, StaticServiceProvider.Instance.GetRequiredService()) { - _dataValueEditorFactory = dataValueEditorFactory; - _dataTypeRepository = dataTypeRepository; - _dataTypeContainerRepository = dataTypeContainerRepository; - _auditRepository = auditRepository; - _entityRepository = entityRepository; - _contentTypeRepository = contentTypeRepository; - _ioHelper = ioHelper; - _localizedTextService = localizedTextService; - _localizationService = localizationService; - _shortStringHelper = shortStringHelper; - _jsonSerializer = jsonSerializer; } + [Obsolete("Please use constructor that takes an does not take ILocalizedTextService, ILocalizationService, IShortStringHelper & IJsonSerializer instead")] public DataTypeService( IDataValueEditorFactory dataValueEditorFactory, - ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - IDataTypeRepository dataTypeRepository, IDataTypeContainerRepository dataTypeContainerRepository, - IAuditRepository auditRepository, IEntityRepository entityRepository, + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IDataTypeRepository dataTypeRepository, + IDataTypeContainerRepository dataTypeContainerRepository, + IAuditRepository auditRepository, + IEntityRepository entityRepository, IContentTypeRepository contentTypeRepository, - IIOHelper ioHelper, ILocalizedTextService localizedTextService, ILocalizationService localizationService, + IIOHelper ioHelper, + ILocalizedTextService localizedTextService, + ILocalizationService localizationService, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IEditorConfigurationParser editorConfigurationParser) - : base(provider, loggerFactory, eventMessagesFactory) + : this(dataValueEditorFactory, provider, loggerFactory, eventMessagesFactory, dataTypeRepository, dataTypeContainerRepository, auditRepository, entityRepository, contentTypeRepository, ioHelper, editorConfigurationParser) { - _dataValueEditorFactory = dataValueEditorFactory; - _dataTypeRepository = dataTypeRepository; - _dataTypeContainerRepository = dataTypeContainerRepository; - _auditRepository = auditRepository; - _entityRepository = entityRepository; - _contentTypeRepository = contentTypeRepository; - _ioHelper = ioHelper; - _localizedTextService = localizedTextService; - _localizationService = localizationService; - _shortStringHelper = shortStringHelper; - _jsonSerializer = jsonSerializer; - _editorConfigurationParser = editorConfigurationParser; } /// @@ -112,8 +122,7 @@ public DataTypeService( { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - IDataType dataType = _dataTypeRepository.Get(Query().Where(x => x.Name == name)) - ?.FirstOrDefault(); + IDataType? dataType = _dataTypeRepository.Get(Query().Where(x => x.Name == name)).FirstOrDefault(); ConvertMissingEditorOfDataTypeToLabel(dataType); return dataType; } @@ -130,7 +139,7 @@ public DataTypeService( { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - IDataType dataType = _dataTypeRepository.Get(id); + IDataType? dataType = _dataTypeRepository.Get(id); ConvertMissingEditorOfDataTypeToLabel(dataType); return dataType; } @@ -148,7 +157,7 @@ public DataTypeService( using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { IQuery query = Query().Where(x => x.Key == id); - IDataType dataType = _dataTypeRepository.Get(query).FirstOrDefault(); + IDataType? dataType = _dataTypeRepository.Get(query).FirstOrDefault(); ConvertMissingEditorOfDataTypeToLabel(dataType); return dataType; } @@ -354,7 +363,6 @@ public void Delete(IDataType dataType, int userId = Constants.Security.SuperUser // // what IS weird is that a content type is losing a property and we do NOT raise any // content type event... so ppl better listen on the data type events too. - _contentTypeRepository.Save(contentType); } @@ -377,35 +385,9 @@ public IReadOnlyDictionary> GetReferences(int id) } } - private void ConvertMissingEditorOfDataTypeToLabel(IDataType? dataType) - { - if (dataType == null) - { - return; - } - - ConvertMissingEditorsOfDataTypesToLabels(new[] {dataType}); - } - - private void ConvertMissingEditorsOfDataTypesToLabels(IEnumerable dataTypes) - { - // Any data types that don't have an associated editor are created of a specific type. - // We convert them to labels to make clear to the user why the data type cannot be used. - IEnumerable dataTypesWithMissingEditors = dataTypes - .Where(x => x.Editor is MissingPropertyEditor); - foreach (IDataType dataType in dataTypesWithMissingEditors) - { - dataType.Editor = new LabelPropertyEditor(_dataValueEditorFactory, _ioHelper, _editorConfigurationParser); - } - } - - private void Audit(AuditType type, int userId, int objectId) => - _auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.DataType.GetName())); - #region Containers - public Attempt?> CreateContainer(int parentId, Guid key, - string name, int userId = Constants.Security.SuperUserId) + public Attempt?> CreateContainer(int parentId, Guid key, string name, int userId = Constants.Security.SuperUserId) { EventMessages evtMsgs = EventMessagesFactory.Get(); using (ICoreScope scope = ScopeProvider.CreateCoreScope()) @@ -414,7 +396,10 @@ private void Audit(AuditType type, int userId, int objectId) => { var container = new EntityContainer(Constants.ObjectTypes.DataType) { - Name = name, ParentId = parentId, CreatorId = userId, Key = key + Name = name, + ParentId = parentId, + CreatorId = userId, + Key = key, }; var savingEntityContainerNotification = new EntityContainerSavingNotification(container, evtMsgs); @@ -432,7 +417,6 @@ private void Audit(AuditType type, int userId, int objectId) => savingEntityContainerNotification)); // TODO: Audit trail ? - return OperationResult.Attempt.Succeed(evtMsgs, container); } catch (Exception ex) @@ -442,6 +426,31 @@ private void Audit(AuditType type, int userId, int objectId) => } } + private void ConvertMissingEditorOfDataTypeToLabel(IDataType? dataType) + { + if (dataType == null) + { + return; + } + + ConvertMissingEditorsOfDataTypesToLabels(new[] { dataType }); + } + + private void ConvertMissingEditorsOfDataTypesToLabels(IEnumerable dataTypes) + { + // Any data types that don't have an associated editor are created of a specific type. + // We convert them to labels to make clear to the user why the data type cannot be used. + IEnumerable dataTypesWithMissingEditors = dataTypes + .Where(x => x.Editor is MissingPropertyEditor); + foreach (IDataType dataType in dataTypesWithMissingEditors) + { + dataType.Editor = new LabelPropertyEditor(_dataValueEditorFactory, _ioHelper, _editorConfigurationParser); + } + } + + private void Audit(AuditType type, int userId, int objectId) => + _auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.DataType.GetName())); + public EntityContainer? GetContainer(int containerId) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) @@ -488,7 +497,8 @@ public IEnumerable GetContainers(int[] containerIds) } } - public Attempt SaveContainer(EntityContainer container, + public Attempt SaveContainer( + EntityContainer container, int userId = Constants.Security.SuperUserId) { EventMessages evtMsgs = EventMessagesFactory.Get(); @@ -532,7 +542,7 @@ public IEnumerable GetContainers(int[] containerIds) EventMessages evtMsgs = EventMessagesFactory.Get(); using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - EntityContainer container = _dataTypeContainerRepository.Get(containerId); + EntityContainer? container = _dataTypeContainerRepository.Get(containerId); if (container == null) { return OperationResult.Attempt.NoOperation(evtMsgs); @@ -540,7 +550,7 @@ public IEnumerable GetContainers(int[] containerIds) // 'container' here does not know about its children, so we need // to get it again from the entity repository, as a light entity - IEntitySlim entity = _entityRepository.Get(container.Id); + IEntitySlim? entity = _entityRepository.Get(container.Id); if (entity?.HasChildren ?? false) { scope.Complete(); @@ -566,17 +576,16 @@ public IEnumerable GetContainers(int[] containerIds) return OperationResult.Attempt.Succeed(evtMsgs); } - public Attempt?> RenameContainer(int id, string name, - int userId = Constants.Security.SuperUserId) + public Attempt?> RenameContainer(int id, string name, int userId = Constants.Security.SuperUserId) { EventMessages evtMsgs = EventMessagesFactory.Get(); using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { try { - EntityContainer container = _dataTypeContainerRepository.Get(id); + EntityContainer? container = _dataTypeContainerRepository.Get(id); - //throw if null, this will be caught by the catch and a failed returned + // throw if null, this will be caught by the catch and a failed returned if (container == null) { throw new InvalidOperationException("No container found with id " + id); diff --git a/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs index 5c737f665f76..476a2ddd4752 100644 --- a/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs +++ b/src/Umbraco.Core/Services/DateTypeServiceExtensions.cs @@ -10,10 +10,10 @@ public static bool IsDataTypeIgnoringUserStartNodes(this IDataTypeService dataTy { if (DataTypeExtensions.IsBuildInDataType(key)) { - return false; //built in ones can never be ignoring start nodes + return false; // built in ones can never be ignoring start nodes } - IDataType dataType = dataTypeService.GetDataType(key); + IDataType? dataType = dataTypeService.GetDataType(key); if (dataType != null && dataType.Configuration is IIgnoreUserStartNodesConfig ignoreStartNodesConfig) { diff --git a/src/Umbraco.Core/Services/DefaultContentVersionCleanupPolicy.cs b/src/Umbraco.Core/Services/DefaultContentVersionCleanupPolicy.cs index dc2cbe3b07e8..f51858fa5b25 100644 --- a/src/Umbraco.Core/Services/DefaultContentVersionCleanupPolicy.cs +++ b/src/Umbraco.Core/Services/DefaultContentVersionCleanupPolicy.cs @@ -13,8 +13,10 @@ public class DefaultContentVersionCleanupPolicy : IContentVersionCleanupPolicy private readonly IDocumentVersionRepository _documentVersionRepository; private readonly ICoreScopeProvider _scopeProvider; - public DefaultContentVersionCleanupPolicy(IOptions contentSettings, - ICoreScopeProvider scopeProvider, IDocumentVersionRepository documentVersionRepository) + public DefaultContentVersionCleanupPolicy( + IOptions contentSettings, + ICoreScopeProvider scopeProvider, + IDocumentVersionRepository documentVersionRepository) { _contentSettings = contentSettings ?? throw new ArgumentNullException(nameof(contentSettings)); _scopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider)); @@ -26,7 +28,6 @@ public IEnumerable Apply(DateTime asAtDate, IEnumerable Apply(DateTime asAtDate, IEnumerable Apply(DateTime asAtDate, IEnumerable new {x.ContentId, x.VersionDate.Date}); + var grouped = theRest.GroupBy(x => new { x.ContentId, x.VersionDate.Date }); foreach (var group in grouped) { @@ -88,7 +89,7 @@ public IEnumerable Apply(DateTime asAtDate, IEnumerable diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index 4368a3f84e62..591fa17909fe 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -17,8 +17,12 @@ public class EntityService : RepositoryService, IEntityService private readonly Dictionary _objectTypes; private IQuery? _queryRootEntity; - public EntityService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, IIdKeyMap idKeyMap, IEntityRepository entityRepository) + public EntityService( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IIdKeyMap idKeyMap, + IEntityRepository entityRepository) : base(provider, loggerFactory, eventMessagesFactory) { _idKeyMap = idKeyMap; @@ -26,22 +30,21 @@ public EntityService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, _objectTypes = new Dictionary { - {typeof(IDataType).FullName!, UmbracoObjectTypes.DataType}, - {typeof(IContent).FullName!, UmbracoObjectTypes.Document}, - {typeof(IContentType).FullName!, UmbracoObjectTypes.DocumentType}, - {typeof(IMedia).FullName!, UmbracoObjectTypes.Media}, - {typeof(IMediaType).FullName!, UmbracoObjectTypes.MediaType}, - {typeof(IMember).FullName!, UmbracoObjectTypes.Member}, - {typeof(IMemberType).FullName!, UmbracoObjectTypes.MemberType} + { typeof(IDataType).FullName!, UmbracoObjectTypes.DataType }, + { typeof(IContent).FullName!, UmbracoObjectTypes.Document }, + { typeof(IContentType).FullName!, UmbracoObjectTypes.DocumentType }, + { typeof(IMedia).FullName!, UmbracoObjectTypes.Media }, + { typeof(IMediaType).FullName!, UmbracoObjectTypes.MediaType }, + { typeof(IMember).FullName!, UmbracoObjectTypes.Member }, + { typeof(IMemberType).FullName!, UmbracoObjectTypes.MemberType }, }; } #region Static Queries // lazy-constructed because when the ctor runs, the query factory may not be ready - private IQuery QueryRootEntity => _queryRootEntity - ?? (_queryRootEntity = Query() - .Where(x => x.ParentId == -1)); + private IQuery QueryRootEntity => _queryRootEntity ??= Query() + .Where(x => x.ParentId == -1); #endregion @@ -119,9 +122,9 @@ public bool Exists(Guid key) } } - /// - public virtual IEnumerable GetAll() where T : IUmbracoEntity + public virtual IEnumerable GetAll() + where T : IUmbracoEntity => GetAll(Array.Empty()); /// @@ -145,7 +148,7 @@ public virtual IEnumerable GetAll(UmbracoObjectTypes objectType) /// public virtual IEnumerable GetAll(UmbracoObjectTypes objectType, params int[] ids) { - Type entityType = objectType.GetClrType(); + Type? entityType = objectType.GetClrType(); if (entityType == null) { throw new NotSupportedException($"Type \"{objectType}\" is not supported here."); @@ -166,7 +169,7 @@ public virtual IEnumerable GetAll(Guid objectType) /// public virtual IEnumerable GetAll(Guid objectType, params int[] ids) { - Type entityType = ObjectTypes.GetClrType(objectType); + Type? entityType = ObjectTypes.GetClrType(objectType); GetObjectType(entityType); using (ScopeProvider.CreateCoreScope(autoComplete: true)) @@ -192,7 +195,7 @@ public virtual IEnumerable GetAll(params Guid[] keys) /// public IEnumerable GetAll(UmbracoObjectTypes objectType, Guid[] keys) { - Type entityType = objectType.GetClrType(); + Type? entityType = objectType.GetClrType(); GetObjectType(entityType); using (ScopeProvider.CreateCoreScope(autoComplete: true)) @@ -204,7 +207,7 @@ public IEnumerable GetAll(UmbracoObjectTypes objectType, Guid[] key /// public virtual IEnumerable GetAll(Guid objectType, params Guid[] keys) { - Type entityType = ObjectTypes.GetClrType(objectType); + Type? entityType = ObjectTypes.GetClrType(objectType); GetObjectType(entityType); using (ScopeProvider.CreateCoreScope(autoComplete: true)) @@ -227,7 +230,7 @@ public virtual IEnumerable GetRootEntities(UmbracoObjectTypes objec { using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - IEntitySlim entity = _entityRepository.Get(id); + IEntitySlim? entity = _entityRepository.Get(id); if (entity is null || entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21) { return null; @@ -242,7 +245,7 @@ public virtual IEnumerable GetRootEntities(UmbracoObjectTypes objec { using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - IEntitySlim entity = _entityRepository.Get(id); + IEntitySlim? entity = _entityRepository.Get(id); if (entity is null || entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21) { return null; @@ -277,7 +280,7 @@ public virtual IEnumerable GetDescendants(int id) { using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - IEntitySlim entity = _entityRepository.Get(id); + IEntitySlim? entity = _entityRepository.Get(id); var pathMatch = entity?.Path + ","; IQuery query = Query() .Where(x => x.Path.StartsWith(pathMatch) && x.Id != id); @@ -290,7 +293,7 @@ public virtual IEnumerable GetDescendants(int id, UmbracoObjectType { using (ScopeProvider.CreateCoreScope(autoComplete: true)) { - IEntitySlim entity = _entityRepository.Get(id); + IEntitySlim? entity = _entityRepository.Get(id); if (entity is null) { return Enumerable.Empty(); @@ -303,23 +306,32 @@ public virtual IEnumerable GetDescendants(int id, UmbracoObjectType } /// - public IEnumerable GetPagedChildren(int id, UmbracoObjectTypes objectType, long pageIndex, - int pageSize, out long totalRecords, - IQuery? filter = null, Ordering? ordering = null) + public IEnumerable GetPagedChildren( + int id, + UmbracoObjectTypes objectType, + long pageIndex, + int pageSize, + out long totalRecords, + IQuery? filter = null, + Ordering? ordering = null) { using (ScopeProvider.CreateCoreScope(autoComplete: true)) { IQuery query = Query().Where(x => x.ParentId == id && x.Trashed == false); - return _entityRepository.GetPagedResultsByQuery(query, objectType.GetGuid(), pageIndex, pageSize, - out totalRecords, filter, ordering); + return _entityRepository.GetPagedResultsByQuery(query, objectType.GetGuid(), pageIndex, pageSize, out totalRecords, filter, ordering); } } /// - public IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes objectType, long pageIndex, - int pageSize, out long totalRecords, - IQuery? filter = null, Ordering? ordering = null) + public IEnumerable GetPagedDescendants( + int id, + UmbracoObjectTypes objectType, + long pageIndex, + int pageSize, + out long totalRecords, + IQuery? filter = null, + Ordering? ordering = null) { using (ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -340,15 +352,19 @@ public IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes o query.Where(x => x.Path.SqlStartsWith(path + ",", TextColumnType.NVarchar)); } - return _entityRepository.GetPagedResultsByQuery(query, objectTypeGuid, pageIndex, pageSize, - out totalRecords, filter, ordering); + return _entityRepository.GetPagedResultsByQuery(query, objectTypeGuid, pageIndex, pageSize, out totalRecords, filter, ordering); } } /// - public IEnumerable GetPagedDescendants(IEnumerable ids, UmbracoObjectTypes objectType, - long pageIndex, int pageSize, out long totalRecords, - IQuery? filter = null, Ordering? ordering = null) + public IEnumerable GetPagedDescendants( + IEnumerable ids, + UmbracoObjectTypes objectType, + long pageIndex, + int pageSize, + out long totalRecords, + IQuery? filter = null, + Ordering? ordering = null) { totalRecords = 0; @@ -381,7 +397,7 @@ public IEnumerable GetPagedDescendants(IEnumerable ids, Umbrac continue; } - TreeEntityPath entityPath = paths.FirstOrDefault(x => x.Id == id); + TreeEntityPath? entityPath = paths.FirstOrDefault(x => x.Id == id); if (entityPath == null) { continue; @@ -397,15 +413,19 @@ public IEnumerable GetPagedDescendants(IEnumerable ids, Umbrac query.WhereAny(clauses); } - return _entityRepository.GetPagedResultsByQuery(query, objectTypeGuid, pageIndex, pageSize, - out totalRecords, filter, ordering); + return _entityRepository.GetPagedResultsByQuery(query, objectTypeGuid, pageIndex, pageSize, out totalRecords, filter, ordering); } } /// - public IEnumerable GetPagedDescendants(UmbracoObjectTypes objectType, long pageIndex, int pageSize, + public IEnumerable GetPagedDescendants( + UmbracoObjectTypes objectType, + long pageIndex, + int pageSize, out long totalRecords, - IQuery? filter = null, Ordering? ordering = null, bool includeTrashed = true) + IQuery? filter = null, + Ordering? ordering = null, + bool includeTrashed = true) { using (ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -415,8 +435,7 @@ public IEnumerable GetPagedDescendants(UmbracoObjectTypes objectTyp query.Where(x => x.Trashed == false); } - return _entityRepository.GetPagedResultsByQuery(query, objectType.GetGuid(), pageIndex, pageSize, - out totalRecords, filter, ordering); + return _entityRepository.GetPagedResultsByQuery(query, objectType.GetGuid(), pageIndex, pageSize, out totalRecords, filter, ordering); } } @@ -464,7 +483,7 @@ public Attempt GetKey(int id, UmbracoObjectTypes umbracoObjectType) => /// public virtual IEnumerable GetAllPaths(UmbracoObjectTypes objectType, params int[]? ids) { - Type entityType = objectType.GetClrType(); + Type? entityType = objectType.GetClrType(); GetObjectType(entityType); using (ScopeProvider.CreateCoreScope(autoComplete: true)) @@ -476,7 +495,7 @@ public virtual IEnumerable GetAllPaths(UmbracoObjectTypes object /// public virtual IEnumerable GetAllPaths(UmbracoObjectTypes objectType, params Guid[] keys) { - Type entityType = objectType.GetClrType(); + Type? entityType = objectType.GetClrType(); GetObjectType(entityType); using (ScopeProvider.CreateCoreScope(autoComplete: true)) diff --git a/src/Umbraco.Core/Services/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/EntityXmlSerializer.cs index 9363e96cd1a3..0a744f3f0f72 100644 --- a/src/Umbraco.Core/Services/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs @@ -53,7 +53,8 @@ public EntityXmlSerializer( /// /// Exports an IContent item as an XElement. /// - public XElement Serialize(IContent content, + public XElement Serialize( + IContent content, bool published, bool withDescendants = false) // TODO: take care of usage! only used for the packager { @@ -64,18 +65,18 @@ public XElement Serialize(IContent content, var nodeName = content.ContentType.Alias.ToSafeAlias(_shortStringHelper); - XElement xml = SerializeContentBase(content, content.GetUrlSegment(_shortStringHelper, _urlSegmentProviders), - nodeName, published); + XElement xml = SerializeContentBase(content, content.GetUrlSegment(_shortStringHelper, _urlSegmentProviders), nodeName, published); xml.Add(new XAttribute("nodeType", content.ContentType.Id)); xml.Add(new XAttribute("nodeTypeAlias", content.ContentType.Alias)); xml.Add(new XAttribute("creatorName", content.GetCreatorProfile(_userService)?.Name ?? "??")); - //xml.Add(new XAttribute("creatorID", content.CreatorId)); + + // xml.Add(new XAttribute("creatorID", content.CreatorId)); xml.Add(new XAttribute("writerName", content.GetWriterProfile(_userService)?.Name ?? "??")); xml.Add(new XAttribute("writerID", content.WriterId)); - xml.Add(new XAttribute("template", content.TemplateId?.ToString(CultureInfo.InvariantCulture) ?? "")); + xml.Add(new XAttribute("template", content.TemplateId?.ToString(CultureInfo.InvariantCulture) ?? string.Empty)); xml.Add(new XAttribute("isPublished", content.Published)); @@ -139,18 +140,16 @@ public XElement Serialize( var urlValue = media.GetUrlSegment(_shortStringHelper, _urlSegmentProviders); XElement xml = SerializeContentBase(media, urlValue, nodeName, published); - xml.Add(new XAttribute("nodeType", media.ContentType.Id)); xml.Add(new XAttribute("nodeTypeAlias", media.ContentType.Alias)); - //xml.Add(new XAttribute("creatorName", media.GetCreatorProfile(userService).Name)); - //xml.Add(new XAttribute("creatorID", media.CreatorId)); + // xml.Add(new XAttribute("creatorName", media.GetCreatorProfile(userService).Name)); + // xml.Add(new XAttribute("creatorID", media.CreatorId)); xml.Add(new XAttribute("writerName", media.GetWriterProfile(_userService)?.Name ?? string.Empty)); xml.Add(new XAttribute("writerID", media.WriterId)); xml.Add(new XAttribute("udi", media.GetUdi())); - //xml.Add(new XAttribute("template", 0)); // no template for media - + // xml.Add(new XAttribute("template", 0)); // no template for media onMediaItemSerialized?.Invoke(media, xml); if (withDescendants) @@ -176,15 +175,14 @@ public XElement Serialize(IMember member) var nodeName = member.ContentType.Alias.ToSafeAlias(_shortStringHelper); const bool published = false; // always false for member - XElement xml = SerializeContentBase(member, "", nodeName, published); + XElement xml = SerializeContentBase(member, string.Empty, nodeName, published); xml.Add(new XAttribute("nodeType", member.ContentType.Id)); xml.Add(new XAttribute("nodeTypeAlias", member.ContentType.Alias)); // what about writer/creator/version? - - xml.Add(new XAttribute("loginName", member.Username!)); - xml.Add(new XAttribute("email", member.Email!)); + xml.Add(new XAttribute("loginName", member.Username)); + xml.Add(new XAttribute("email", member.Email)); xml.Add(new XAttribute("icon", member.ContentType.Icon!)); return xml; @@ -210,7 +208,8 @@ public XElement Serialize(IDataType dataType) { var xml = new XElement("DataType"); xml.Add(new XAttribute("Name", dataType.Name!)); - //The 'ID' when exporting is actually the property editor alias (in pre v7 it was the IDataType GUID id) + + // The 'ID' when exporting is actually the property editor alias (in pre v7 it was the IDataType GUID id) xml.Add(new XAttribute("Id", dataType.EditorAlias)); xml.Add(new XAttribute("Definition", dataType.Key)); xml.Add(new XAttribute("DatabaseType", dataType.DatabaseType.ToString())); @@ -220,7 +219,7 @@ public XElement Serialize(IDataType dataType) var folderKeys = string.Empty; if (dataType.Level != 1) { - //get URL encoded folder names + // get URL encoded folder names IOrderedEnumerable folders = _dataTypeService.GetContainers(dataType) .OrderBy(x => x.Level); @@ -234,7 +233,6 @@ public XElement Serialize(IDataType dataType) xml.Add(new XAttribute("FolderKeys", folderKeys)); } - return xml; } @@ -267,7 +265,7 @@ public XElement Serialize(IDictionaryItem dictionaryItem, bool includeChildren) if (includeChildren) { - IEnumerable children = _localizationService.GetDictionaryItemChildren(dictionaryItem.Key); + IEnumerable? children = _localizationService.GetDictionaryItemChildren(dictionaryItem.Key); if (children is not null) { foreach (IDictionaryItem child in children) @@ -282,7 +280,8 @@ public XElement Serialize(IDictionaryItem dictionaryItem, bool includeChildren) public XElement Serialize(IStylesheet stylesheet, bool includeProperties) { - var xml = new XElement("Stylesheet", + var xml = new XElement( + "Stylesheet", new XElement("Name", stylesheet.Alias), new XElement("FileName", stylesheet.Path), new XElement("Content", new XCData(stylesheet.Content!))); @@ -299,7 +298,8 @@ public XElement Serialize(IStylesheet stylesheet, bool includeProperties) { foreach (IStylesheetProperty prop in stylesheet.Properties) { - props.Add(new XElement("Property", + props.Add(new XElement( + "Property", new XElement("Name", prop.Name), new XElement("Alias", prop.Alias), new XElement("Value", prop.Value))); @@ -327,10 +327,11 @@ public XElement Serialize(IEnumerable languages) public XElement Serialize(ILanguage language) { - var xml = new XElement("Language", + var xml = new XElement( + "Language", new XAttribute("Id", language.Id), new XAttribute("CultureAlias", language.IsoCode), - new XAttribute("FriendlyName", language.CultureName!)); + new XAttribute("FriendlyName", language.CultureName)); return xml; } @@ -372,10 +373,10 @@ public XElement Serialize(IEnumerable templates) return xml; } - public XElement Serialize(IMediaType mediaType) { - var info = new XElement("Info", + var info = new XElement( + "Info", new XElement("Name", mediaType.Name), new XElement("Alias", mediaType.Alias), new XElement("Key", mediaType.Key), @@ -399,13 +400,16 @@ public XElement Serialize(IMediaType mediaType) } } - var genericProperties = new XElement("GenericProperties", + var genericProperties = new XElement( + "GenericProperties", SerializePropertyTypes(mediaType.PropertyTypes, mediaType.PropertyGroups)); // actually, all of them - var tabs = new XElement("Tabs", + var tabs = new XElement( + "Tabs", SerializePropertyGroups(mediaType.PropertyGroups)); // TODO Rename to PropertyGroups - var xml = new XElement("MediaType", + var xml = new XElement( + "MediaType", info, structure, genericProperties, @@ -446,7 +450,8 @@ public XElement Serialize(IMacro macro) var properties = new XElement("properties"); foreach (IMacroProperty property in macro.Properties) { - properties.Add(new XElement("property", + properties.Add(new XElement( + "property", new XAttribute("key", property.Key), new XAttribute("name", property.Name!), new XAttribute("alias", property.Alias), @@ -461,7 +466,8 @@ public XElement Serialize(IMacro macro) public XElement Serialize(IContentType contentType) { - var info = new XElement("Info", + var info = new XElement( + "Info", new XElement("Name", contentType.Name), new XElement("Alias", contentType.Alias), new XElement("Key", contentType.Key), @@ -473,7 +479,7 @@ public XElement Serialize(IContentType contentType) new XElement("IsElement", contentType.IsElement.ToString()), new XElement("Variations", contentType.Variations.ToString())); - IContentTypeComposition masterContentType = + IContentTypeComposition? masterContentType = contentType.ContentTypeComposition.FirstOrDefault(x => x.Id == contentType.ParentId); if (masterContentType != null) { @@ -506,7 +512,7 @@ public XElement Serialize(IContentType contentType) } else { - info.Add(new XElement("DefaultTemplate", "")); + info.Add(new XElement("DefaultTemplate", string.Empty)); } var structure = new XElement("Structure"); @@ -518,13 +524,16 @@ public XElement Serialize(IContentType contentType) } } - var genericProperties = new XElement("GenericProperties", + var genericProperties = new XElement( + "GenericProperties", SerializePropertyTypes(contentType.PropertyTypes, contentType.PropertyGroups)); // actually, all of them - var tabs = new XElement("Tabs", + var tabs = new XElement( + "Tabs", SerializePropertyGroups(contentType.PropertyGroups)); // TODO Rename to PropertyGroups - var xml = new XElement("DocumentType", + var xml = new XElement( + "DocumentType", info, structure, genericProperties, @@ -537,10 +546,11 @@ public XElement Serialize(IContentType contentType) var folderNames = string.Empty; var folderKeys = string.Empty; - //don't add folders if this is a child doc type + + // don't add folders if this is a child doc type if (contentType.Level != 1 && masterContentType == null) { - //get URL encoded folder names + // get URL encoded folder names IOrderedEnumerable folders = _contentTypeService.GetContainers(contentType) .OrderBy(x => x.Level); @@ -554,35 +564,37 @@ public XElement Serialize(IContentType contentType) xml.Add(new XAttribute("FolderKeys", folderKeys)); } - return xml; } private XElement Serialize(IDictionaryItem dictionaryItem) { - var xml = new XElement("DictionaryItem", + var xml = new XElement( + "DictionaryItem", new XAttribute("Key", dictionaryItem.Key), new XAttribute("Name", dictionaryItem.ItemKey)); - foreach (IDictionaryTranslation translation in dictionaryItem.Translations!) + foreach (IDictionaryTranslation translation in dictionaryItem.Translations) { - xml.Add(new XElement("Value", + xml.Add(new XElement( + "Value", new XAttribute("LanguageId", translation.Language!.Id), new XAttribute("LanguageCultureAlias", translation.Language.IsoCode), - new XCData(translation.Value!))); + new XCData(translation.Value))); } return xml; } - private IEnumerable SerializePropertyTypes(IEnumerable propertyTypes, + private IEnumerable SerializePropertyTypes( + IEnumerable propertyTypes, IEnumerable propertyGroups) { foreach (IPropertyType propertyType in propertyTypes) { - IDataType definition = _dataTypeService.GetDataType(propertyType.DataTypeId); + IDataType? definition = _dataTypeService.GetDataType(propertyType.DataTypeId); - PropertyGroup propertyGroup = propertyType.PropertyGroupId == null // true generic property + PropertyGroup? propertyGroup = propertyType.PropertyGroupId == null // true generic property ? null : propertyGroups.FirstOrDefault(x => x.Id == propertyType.PropertyGroupId.Value); @@ -597,7 +609,8 @@ private IEnumerable SerializePropertyGroups(IEnumerable { foreach (PropertyGroup propertyGroup in propertyGroups) { - yield return new XElement("Tab", // TODO Rename to PropertyGroup + yield return new XElement( + "Tab", // TODO Rename to PropertyGroup new XElement("Id", propertyGroup.Id), new XElement("Key", propertyGroup.Key), new XElement("Type", propertyGroup.Type.ToString()), @@ -607,30 +620,22 @@ private IEnumerable SerializePropertyGroups(IEnumerable } } - private XElement SerializePropertyType(IPropertyType propertyType, IDataType? definition, - PropertyGroup? propertyGroup) - => new("GenericProperty", + private XElement SerializePropertyType(IPropertyType propertyType, IDataType? definition, PropertyGroup? propertyGroup) + => new( + "GenericProperty", new XElement("Name", propertyType.Name), new XElement("Alias", propertyType.Alias), new XElement("Key", propertyType.Key), new XElement("Type", propertyType.PropertyEditorAlias), definition is not null ? new XElement("Definition", definition.Key) : null, - propertyGroup is not null - ? new XElement("Tab", propertyGroup.Name, new XAttribute("Alias", propertyGroup.Alias)) - : null, // TODO Replace with PropertyGroupAlias + propertyGroup is not null ? new XElement("Tab", propertyGroup.Name, new XAttribute("Alias", propertyGroup.Alias)) : null, // TODO Replace with PropertyGroupAlias new XElement("SortOrder", propertyType.SortOrder), new XElement("Mandatory", propertyType.Mandatory.ToString()), new XElement("LabelOnTop", propertyType.LabelOnTop.ToString()), - propertyType.MandatoryMessage != null - ? new XElement("MandatoryMessage", propertyType.MandatoryMessage) - : null, + propertyType.MandatoryMessage != null ? new XElement("MandatoryMessage", propertyType.MandatoryMessage) : null, propertyType.ValidationRegExp != null ? new XElement("Validation", propertyType.ValidationRegExp) : null, - propertyType.ValidationRegExpMessage != null - ? new XElement("ValidationRegExpMessage", propertyType.ValidationRegExpMessage) - : null, - propertyType.Description != null - ? new XElement("Description", new XCData(propertyType.Description)) - : null); + propertyType.ValidationRegExpMessage != null ? new XElement("ValidationRegExpMessage", propertyType.ValidationRegExpMessage) : null, + propertyType.Description != null ? new XElement("Description", new XCData(propertyType.Description)) : null); private XElement SerializeCleanupPolicy(HistoryCleanup cleanupPolicy) { @@ -639,7 +644,8 @@ private XElement SerializeCleanupPolicy(HistoryCleanup cleanupPolicy) throw new ArgumentNullException(nameof(cleanupPolicy)); } - var element = new XElement("HistoryCleanupPolicy", + var element = new XElement( + "HistoryCleanupPolicy", new XAttribute("preventCleanup", cleanupPolicy.PreventCleanup)); if (cleanupPolicy.KeepAllVersionsNewerThanDays.HasValue) @@ -658,7 +664,8 @@ private XElement SerializeCleanupPolicy(HistoryCleanup cleanupPolicy) // exports an IContentBase (IContent, IMedia or IMember) as an XElement. private XElement SerializeContentBase(IContentBase contentBase, string? urlValue, string nodeName, bool published) { - var xml = new XElement(nodeName, + var xml = new XElement( + nodeName, new XAttribute("id", contentBase.Id.ToInvariantString()), new XAttribute("key", contentBase.Key), new XAttribute("parentID", (contentBase.Level > 1 ? contentBase.ParentId : -1).ToInvariantString()), @@ -670,8 +677,7 @@ private XElement SerializeContentBase(IContentBase contentBase, string? urlValue new XAttribute("nodeName", contentBase.Name!), new XAttribute("urlName", urlValue!), new XAttribute("path", contentBase.Path), - new XAttribute("isDoc", "")); - + new XAttribute("isDoc", string.Empty)); // Add culture specific node names foreach (var culture in contentBase.AvailableCultures) @@ -693,7 +699,7 @@ private IEnumerable SerializeProperty(IProperty property, bool publish IPropertyType propertyType = property.PropertyType; // get the property editor for this property and let it convert it to the xml structure - IDataEditor propertyEditor = _propertyEditors[propertyType.PropertyEditorAlias]; + IDataEditor? propertyEditor = _propertyEditors[propertyType.PropertyEditorAlias]; return propertyEditor == null ? Array.Empty() : propertyEditor.GetValueEditor().ConvertDbToXml(property, published); @@ -715,6 +721,7 @@ private void SerializeChildren(IEnumerable children, XElement xml, boo { IEnumerable grandChildren = _contentService.GetPagedChildren(child.Id, page++, pageSize, out total); + // recurse SerializeChildren(grandChildren, childXml, published); } @@ -722,8 +729,7 @@ private void SerializeChildren(IEnumerable children, XElement xml, boo } // exports an IMedia item descendants. - private void SerializeChildren(IEnumerable children, XElement xml, - Action? onMediaItemSerialized) + private void SerializeChildren(IEnumerable children, XElement xml, Action? onMediaItemSerialized) { foreach (IMedia child in children) { @@ -738,6 +744,7 @@ private void SerializeChildren(IEnumerable children, XElement xml, { IEnumerable grandChildren = _mediaService.GetPagedChildren(child.Id, page++, pageSize, out total); + // recurse SerializeChildren(grandChildren, childXml, onMediaItemSerialized); } diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index 76f96c6529bb..bd8272a11d1b 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -34,12 +34,18 @@ public class FileService : RepositoryService, IFileService private readonly IStylesheetRepository _stylesheetRepository; private readonly ITemplateRepository _templateRepository; - public FileService(ICoreScopeProvider uowProvider, ILoggerFactory loggerFactory, + public FileService( + ICoreScopeProvider uowProvider, + ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - IStylesheetRepository stylesheetRepository, IScriptRepository scriptRepository, + IStylesheetRepository stylesheetRepository, + IScriptRepository scriptRepository, ITemplateRepository templateRepository, - IPartialViewRepository partialViewRepository, IPartialViewMacroRepository partialViewMacroRepository, - IAuditRepository auditRepository, IShortStringHelper shortStringHelper, IOptions globalSettings, + IPartialViewRepository partialViewRepository, + IPartialViewMacroRepository partialViewMacroRepository, + IAuditRepository auditRepository, + IShortStringHelper shortStringHelper, + IOptions globalSettings, IHostingEnvironment hostingEnvironment) : base(uowProvider, loggerFactory, eventMessagesFactory) { @@ -54,9 +60,6 @@ public FileService(ICoreScopeProvider uowProvider, ILoggerFactory loggerFactory, _hostingEnvironment = hostingEnvironment; } - private void Audit(AuditType type, int userId, int objectId, string? entityType) => - _auditRepository.Save(new AuditItem(objectId, type, userId, entityType)); - #region Stylesheets /// @@ -68,6 +71,9 @@ public IEnumerable GetStylesheets(params string[] paths) } } + private void Audit(AuditType type, int userId, int objectId, string? entityType) => + _auditRepository.Save(new AuditItem(objectId, type, userId, entityType)); + /// public IStylesheet? GetStylesheet(string? path) { @@ -86,7 +92,6 @@ public void SaveStylesheet(IStylesheet? stylesheet, int? userId = null) } using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { EventMessages eventMessages = EventMessagesFactory.Get(); var savingNotification = new StylesheetSavingNotification(stylesheet, eventMessages); @@ -221,7 +226,6 @@ public void SaveScript(IScript? script, int? userId) } using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { EventMessages eventMessages = EventMessagesFactory.Get(); var savingNotification = new ScriptSavingNotification(script, eventMessages); @@ -335,7 +339,8 @@ public long GetScriptFileSize(string filepath) string contentTypeAlias, string? contentTypeName, int userId = Constants.Security.SuperUserId) { var template = new Template(_shortStringHelper, contentTypeName, - //NOTE: We are NOT passing in the content type alias here, we want to use it's name since we don't + + // NOTE: We are NOT passing in the content type alias here, we want to use it's name since we don't // want to save template file names as camelCase, the Template ctor will clean the alias as // `alias.ToCleanString(CleanStringType.UnderscoreAlias)` which has been the default. // This fixes: http://issues.umbraco.org/issue/U4-7953 @@ -374,8 +379,10 @@ public long GetScriptFileSize(string filepath) scope.Complete(); } - return OperationResult.Attempt.Succeed(OperationResultType.Success, - eventMessages, template); + return OperationResult.Attempt.Succeed( + OperationResultType.Success, + eventMessages, + template); } /// @@ -387,8 +394,12 @@ public long GetScriptFileSize(string filepath) /// /// /// - public ITemplate CreateTemplateWithIdentity(string? name, string? alias, string? content, - ITemplate? masterTemplate = null, int userId = Constants.Security.SuperUserId) + public ITemplate CreateTemplateWithIdentity( + string? name, + string? alias, + string? content, + ITemplate? masterTemplate = null, + int userId = Constants.Security.SuperUserId) { if (name == null) { @@ -406,7 +417,7 @@ public ITemplate CreateTemplateWithIdentity(string? name, string? alias, string? } // file might already be on disk, if so grab the content to avoid overwriting - var template = new Template(_shortStringHelper, name, alias) {Content = GetViewContent(alias) ?? content}; + var template = new Template(_shortStringHelper, name, alias) { Content = GetViewContent(alias) ?? content }; if (masterTemplate != null) { @@ -513,7 +524,6 @@ public void SaveTemplate(ITemplate template, int userId = Constants.Security.Sup "Name cannot be null, empty, contain only white-space characters or be more than 255 characters in length."); } - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { EventMessages eventMessages = EventMessagesFactory.Get(); @@ -599,6 +609,15 @@ public void DeleteTemplate(string alias, int userId = Constants.Security.SuperUs } } + /// + public Stream GetTemplateFileContentStream(string filepath) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + { + return _templateRepository.GetFileContentStream(filepath); + } + } + private string? GetViewContent(string? fileName) { if (fileName.IsNullOrWhiteSpace()) @@ -619,15 +638,6 @@ public void DeleteTemplate(string alias, int userId = Constants.Security.SuperUs } } - /// - public Stream GetTemplateFileContentStream(string filepath) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _templateRepository.GetFileContentStream(filepath); - } - } - /// public void SetTemplateFileContent(string filepath, Stream content) { @@ -662,7 +672,7 @@ public IEnumerable GetPartialViewSnippetNames(params string[] filterName .Except(filterNames, StringComparer.InvariantCultureIgnoreCase) .ToArray(); - //Ensure the ones that are called 'Empty' are at the top + // Ensure the ones that are called 'Empty' are at the top var empty = files.Where(x => Path.GetFileName(x)?.InvariantStartsWith("Empty") ?? false) .OrderBy(x => x?.Length) .ToArray(); @@ -712,16 +722,20 @@ public IEnumerable GetPartialViews(params string[] names) } } - public Attempt CreatePartialView(IPartialView partialView, string? snippetName = null, - int? userId = Constants.Security.SuperUserId) => + public Attempt CreatePartialView(IPartialView partialView, string? snippetName = null, int? userId = Constants.Security.SuperUserId) => CreatePartialViewMacro(partialView, PartialViewType.PartialView, snippetName, userId); - public Attempt CreatePartialViewMacro(IPartialView partialView, string? snippetName = null, - int? userId = Constants.Security.SuperUserId) => + public Attempt CreatePartialViewMacro(IPartialView partialView, string? snippetName = null, int? userId = Constants.Security.SuperUserId) => CreatePartialViewMacro(partialView, PartialViewType.PartialViewMacro, snippetName, userId); - private Attempt CreatePartialViewMacro(IPartialView partialView, PartialViewType partialViewType, - string? snippetName = null, int? userId = Constants.Security.SuperUserId) + public bool DeletePartialView(string path, int? userId = null) => + DeletePartialViewMacro(path, PartialViewType.PartialView, userId); + + private Attempt CreatePartialViewMacro( + IPartialView partialView, + PartialViewType partialViewType, + string? snippetName = null, + int? userId = Constants.Security.SuperUserId) { string partialViewHeader; switch (partialViewType) @@ -739,7 +753,7 @@ public IEnumerable GetPartialViews(params string[] names) string? partialViewContent = null; if (snippetName.IsNullOrWhiteSpace() == false) { - //create the file + // create the file Attempt snippetPathAttempt = TryGetSnippetPath(snippetName); if (snippetPathAttempt.Success == false) { @@ -750,10 +764,10 @@ public IEnumerable GetPartialViews(params string[] names) { var snippetContent = snippetFile.ReadToEnd().Trim(); - //strip the @inherits if it's there + // strip the @inherits if it's there snippetContent = StripPartialViewHeader(snippetContent); - //Update Model.Content. to be Model. when used as PartialView + // Update Model.Content. to be Model. when used as PartialView if (partialViewType == PartialViewType.PartialView) { snippetContent = snippetContent.Replace("Model.Content.", "Model."); @@ -792,12 +806,12 @@ public IEnumerable GetPartialViews(params string[] names) return Attempt.Succeed(partialView); } - public bool DeletePartialView(string path, int? userId = null) => - DeletePartialViewMacro(path, PartialViewType.PartialView, userId); - public bool DeletePartialViewMacro(string path, int? userId = null) => DeletePartialViewMacro(path, PartialViewType.PartialViewMacro, userId); + public Attempt SavePartialView(IPartialView partialView, int? userId = null) => + SavePartialView(partialView, PartialViewType.PartialView, userId); + private bool DeletePartialViewMacro(string path, PartialViewType partialViewType, int? userId = null) { using (ICoreScope scope = ScopeProvider.CreateCoreScope()) @@ -830,14 +844,25 @@ private bool DeletePartialViewMacro(string path, PartialViewType partialViewType return true; } - public Attempt SavePartialView(IPartialView partialView, int? userId = null) => - SavePartialView(partialView, PartialViewType.PartialView, userId); - public Attempt SavePartialViewMacro(IPartialView partialView, int? userId = null) => SavePartialView(partialView, PartialViewType.PartialViewMacro, userId); - private Attempt SavePartialView(IPartialView partialView, PartialViewType partialViewType, - int? userId = null) + public void CreatePartialViewFolder(string folderPath) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + _partialViewRepository.AddFolder(folderPath); + scope.Complete(); + } + } + + internal string StripPartialViewHeader(string contents) + { + var headerMatch = new Regex("^@inherits\\s+?.*$", RegexOptions.Multiline); + return headerMatch.Replace(contents, string.Empty); + } + + private Attempt SavePartialView(IPartialView partialView, PartialViewType partialViewType, int? userId = null) { using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { @@ -863,12 +888,6 @@ private bool DeletePartialViewMacro(string path, PartialViewType partialViewType return Attempt.Succeed(partialView); } - internal string StripPartialViewHeader(string contents) - { - var headerMatch = new Regex("^@inherits\\s+?.*$", RegexOptions.Multiline); - return headerMatch.Replace(contents, string.Empty); - } - internal Attempt TryGetSnippetPath(string? fileName) { if (fileName?.EndsWith(".cshtml") == false) @@ -884,21 +903,21 @@ internal Attempt TryGetSnippetPath(string? fileName) : Attempt.Fail(); } - public void CreatePartialViewFolder(string folderPath) + public void CreatePartialViewMacroFolder(string folderPath) { using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - _partialViewRepository.AddFolder(folderPath); + _partialViewMacroRepository.AddFolder(folderPath); scope.Complete(); } } - public void CreatePartialViewMacroFolder(string folderPath) + /// + public Stream GetPartialViewFileContentStream(string filepath) { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - _partialViewMacroRepository.AddFolder(folderPath); - scope.Complete(); + return _partialViewRepository.GetFileContentStream(filepath); } } @@ -915,15 +934,6 @@ private IPartialViewRepository GetPartialViewRepository(PartialViewType partialV } } - /// - public Stream GetPartialViewFileContentStream(string filepath) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _partialViewRepository.GetFileContentStream(filepath); - } - } - /// public void SetPartialViewFileContent(string filepath, Stream content) { @@ -1004,7 +1014,7 @@ private string GetPartialViewMacroSnippetContent(string snippetName, PartialView var snippetProvider = new EmbeddedFileProvider(GetType().Assembly, "Umbraco.Cms.Core.EmbeddedResources.Snippets"); - IFileInfo file = snippetProvider.GetDirectoryContents(string.Empty) + IFileInfo? file = snippetProvider.GetDirectoryContents(string.Empty) .FirstOrDefault(x => x.Exists && x.Name.Equals(snippetName + ".cshtml")); // Try and get the snippet path @@ -1017,10 +1027,10 @@ private string GetPartialViewMacroSnippetContent(string snippetName, PartialView { var snippetContent = snippetFile.ReadToEnd().Trim(); - //strip the @inherits if it's there + // strip the @inherits if it's there snippetContent = StripPartialViewHeader(snippetContent); - //Update Model.Content to be Model when used as PartialView + // Update Model.Content to be Model when used as PartialView if (partialViewType == PartialViewType.PartialView) { snippetContent = snippetContent diff --git a/src/Umbraco.Core/Services/IAuditService.cs b/src/Umbraco.Core/Services/IAuditService.cs index 8023124c74ef..f58da5317448 100644 --- a/src/Umbraco.Core/Services/IAuditService.cs +++ b/src/Umbraco.Core/Services/IAuditService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; namespace Umbraco.Cms.Core.Services; @@ -11,8 +11,11 @@ public interface IAuditService : IService void Add(AuditType type, int userId, int objectId, string? entityType, string comment, string? parameters = null); IEnumerable GetLogs(int objectId); + IEnumerable GetUserLogs(int userId, AuditType type, DateTime? sinceDate = null); + IEnumerable GetLogs(AuditType type, DateTime? sinceDate = null); + void CleanLogs(int maximumAgeOfLogsInMinutes); /// @@ -34,7 +37,11 @@ public interface IAuditService : IService /// Optional filter to be applied /// /// - IEnumerable GetPagedItemsByEntity(int entityId, long pageIndex, int pageSize, out long totalRecords, + IEnumerable GetPagedItemsByEntity( + int entityId, + long pageIndex, + int pageSize, + out long totalRecords, Direction orderDirection = Direction.Descending, AuditType[]? auditTypeFilter = null, IQuery? customFilter = null); @@ -58,7 +65,11 @@ IEnumerable GetPagedItemsByEntity(int entityId, long pageIndex, int /// Optional filter to be applied /// /// - IEnumerable GetPagedItemsByUser(int userId, long pageIndex, int pageSize, out long totalRecords, + IEnumerable GetPagedItemsByUser( + int userId, + long pageIndex, + int pageSize, + out long totalRecords, Direction orderDirection = Direction.Descending, AuditType[]? auditTypeFilter = null, IQuery? customFilter = null); @@ -81,6 +92,13 @@ IEnumerable GetPagedItemsByUser(int userId, long pageIndex, int page /// /// /// Free-form details about the audited event. - IAuditEntry Write(int performingUserId, string perfomingDetails, string performingIp, DateTime eventDateUtc, - int affectedUserId, string affectedDetails, string eventType, string eventDetails); + IAuditEntry Write( + int performingUserId, + string perfomingDetails, + string performingIp, + DateTime eventDateUtc, + int affectedUserId, + string affectedDetails, + string eventType, + string eventDetails); } diff --git a/src/Umbraco.Core/Services/IBasicAuthService.cs b/src/Umbraco.Core/Services/IBasicAuthService.cs index 21417a35863c..3b5f08a412ab 100644 --- a/src/Umbraco.Core/Services/IBasicAuthService.cs +++ b/src/Umbraco.Core/Services/IBasicAuthService.cs @@ -5,5 +5,6 @@ namespace Umbraco.Cms.Core.Services; public interface IBasicAuthService { bool IsBasicAuthEnabled(); + bool IsIpAllowListed(IPAddress clientIpAddress); } diff --git a/src/Umbraco.Core/Services/IConsentService.cs b/src/Umbraco.Core/Services/IConsentService.cs index e63cbbcc6b84..dc0400850374 100644 --- a/src/Umbraco.Core/Services/IConsentService.cs +++ b/src/Umbraco.Core/Services/IConsentService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Services; @@ -41,7 +41,12 @@ public interface IConsentService : IService /// Determines whether is a start pattern. /// Determines whether to include the history of consents. /// Consents matching the parameters. - IEnumerable LookupConsent(string? source = null, string? context = null, string? action = null, - bool sourceStartsWith = false, bool contextStartsWith = false, bool actionStartsWith = false, + IEnumerable LookupConsent( + string? source = null, + string? context = null, + string? action = null, + bool sourceStartsWith = false, + bool contextStartsWith = false, + bool actionStartsWith = false, bool includeHistory = false); } diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index ee9169324b6b..1eb2db83bfdc 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -76,6 +76,8 @@ public interface IContentService : IContentServiceBase /// IContent? GetById(int id); + new + /// /// Gets a document. /// @@ -185,8 +187,7 @@ public interface IContentService : IContentServiceBase /// /// Gets documents in the recycle bin. /// - IEnumerable GetPagedContentInRecycleBin(long pageIndex, int pageSize, out long totalRecords, - IQuery? filter = null, Ordering? ordering = null); + IEnumerable GetPagedContentInRecycleBin(long pageIndex, int pageSize, out long totalRecords, IQuery? filter = null, Ordering? ordering = null); /// /// Gets child documents of a parent. @@ -197,8 +198,7 @@ IEnumerable GetPagedContentInRecycleBin(long pageIndex, int pageSize, /// Total number of documents. /// Query filter. /// Ordering infos. - IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - IQuery? filter = null, Ordering? ordering = null); + IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, IQuery? filter = null, Ordering? ordering = null); /// /// Gets descendant documents of a given parent. @@ -209,8 +209,7 @@ IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out /// Total number of documents. /// Query filter. /// Ordering infos. - IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - IQuery? filter = null, Ordering? ordering = null); + IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, IQuery? filter = null, Ordering? ordering = null); /// /// Gets paged documents of a content @@ -221,8 +220,7 @@ IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, /// Total number of documents. /// Search text filter. /// Ordering infos. - IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords, - IQuery filter, Ordering? ordering = null); + IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering? ordering = null); /// /// Gets paged documents for specified content types @@ -233,8 +231,7 @@ IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int page /// Total number of documents. /// Search text filter. /// Ordering infos. - IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize, out long totalRecords, - IQuery? filter, Ordering? ordering = null); + IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize, out long totalRecords, IQuery? filter, Ordering? ordering = null); /// /// Counts documents of a given document type. @@ -336,8 +333,7 @@ IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int /// /// Optionally recursively copies all children. /// - IContent? Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, - int userId = Constants.Security.SuperUserId); + IContent? Copy(IContent content, int parentId, bool relateToOriginal, bool recursive, int userId = Constants.Security.SuperUserId); /// /// Moves a document to the recycle bin. @@ -423,8 +419,7 @@ IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int /// published. The root of the branch is always published, regardless of . /// /// - IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = "*", - int userId = Constants.Security.SuperUserId); + IEnumerable SaveAndPublishBranch(IContent content, bool force, string culture = "*", int userId = Constants.Security.SuperUserId); /// /// Saves and publishes a document branch. @@ -440,8 +435,7 @@ IEnumerable SaveAndPublishBranch(IContent content, bool force, st /// published. The root of the branch is always published, regardless of . /// /// - IEnumerable SaveAndPublishBranch(IContent content, bool force, string[] cultures, - int userId = Constants.Security.SuperUserId); + IEnumerable SaveAndPublishBranch(IContent content, bool force, string[] cultures, int userId = Constants.Security.SuperUserId); ///// ///// Saves and publishes a document branch. @@ -463,7 +457,7 @@ IEnumerable SaveAndPublishBranch(IContent content, bool force, st ///// each document. It can publish all, one, or a selection of cultures. It returns a boolean indicating ///// whether the cultures could be published. ///// - //IEnumerable SaveAndPublishBranch(IContent content, bool force, + // IEnumerable SaveAndPublishBranch(IContent content, bool force, // Func> shouldPublish, // Func, bool> publishCultures, // int userId = Constants.Security.SuperUserId); @@ -549,20 +543,17 @@ IEnumerable SaveAndPublishBranch(IContent content, bool force, st /// /// Creates a document. /// - IContent Create(string name, IContent? parent, string documentTypeAlias, - int userId = Constants.Security.SuperUserId); + IContent Create(string name, IContent? parent, string documentTypeAlias, int userId = Constants.Security.SuperUserId); /// /// Creates and saves a document. /// - IContent CreateAndSave(string name, int parentId, string contentTypeAlias, - int userId = Constants.Security.SuperUserId); + IContent CreateAndSave(string name, int parentId, string contentTypeAlias, int userId = Constants.Security.SuperUserId); /// /// Creates and saves a document. /// - IContent CreateAndSave(string name, IContent parent, string contentTypeAlias, - int userId = Constants.Security.SuperUserId); + IContent CreateAndSave(string name, IContent parent, string contentTypeAlias, int userId = Constants.Security.SuperUserId); #endregion } diff --git a/src/Umbraco.Core/Services/IContentServiceBase.cs b/src/Umbraco.Core/Services/IContentServiceBase.cs index 9031877e4660..1e07da7d8f91 100644 --- a/src/Umbraco.Core/Services/IContentServiceBase.cs +++ b/src/Umbraco.Core/Services/IContentServiceBase.cs @@ -6,6 +6,7 @@ public interface IContentServiceBase : IContentServiceBase where TItem : class, IContentBase { TItem? GetById(Guid key); + Attempt Save(IEnumerable contents, int userId = Constants.Security.SuperUserId); } diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs index 8e6390a5a776..d38139349b2c 100644 --- a/src/Umbraco.Core/Services/IContentTypeService.cs +++ b/src/Umbraco.Core/Services/IContentTypeService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Services; diff --git a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs index 907cc689d1fd..8e67c78a201f 100644 --- a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs +++ b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Services; @@ -44,22 +44,28 @@ public interface IContentTypeBaseService : IContentTypeBaseService, IServ bool HasContentNodes(int id); IEnumerable GetAll(params int[] ids); + IEnumerable GetAll(IEnumerable? ids); IEnumerable GetDescendants(int id, bool andSelf); // parent-child axis + IEnumerable GetComposedOf(int id); // composition axis IEnumerable GetChildren(int id); + IEnumerable GetChildren(Guid id); bool HasChildren(int id); + bool HasChildren(Guid id); void Save(TItem? item, int userId = Constants.Security.SuperUserId); + void Save(IEnumerable items, int userId = Constants.Security.SuperUserId); + void Delete(TItem item, int userId = Constants.Security.SuperUserId); - void Delete(IEnumerable item, int userId = Constants.Security.SuperUserId); + void Delete(IEnumerable item, int userId = Constants.Security.SuperUserId); Attempt ValidateComposition(TItem? compo); @@ -78,22 +84,29 @@ public interface IContentTypeBaseService : IContentTypeBaseService, IServ /// bool HasContainerInPath(params int[] ids); - Attempt?> CreateContainer(int parentContainerId, Guid key, - string name, int userId = Constants.Security.SuperUserId); + Attempt?> CreateContainer(int parentContainerId, Guid key, string name, int userId = Constants.Security.SuperUserId); Attempt SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId); + EntityContainer? GetContainer(int containerId); + EntityContainer? GetContainer(Guid containerId); + IEnumerable GetContainers(int[] containerIds); + IEnumerable GetContainers(TItem contentType); + IEnumerable GetContainers(string folderName, int level); + Attempt DeleteContainer(int containerId, int userId = Constants.Security.SuperUserId); - Attempt?> RenameContainer(int id, string name, - int userId = Constants.Security.SuperUserId); + Attempt?> RenameContainer(int id, string name, int userId = Constants.Security.SuperUserId); Attempt?> Move(TItem moving, int containerId); + Attempt?> Copy(TItem copying, int containerId); + TItem Copy(TItem original, string alias, string name, int parentId = -1); + TItem Copy(TItem original, string alias, string name, TItem parent); } diff --git a/src/Umbraco.Core/Services/IDashboardService.cs b/src/Umbraco.Core/Services/IDashboardService.cs index 698617be4b4e..2792b142feea 100644 --- a/src/Umbraco.Core/Services/IDashboardService.cs +++ b/src/Umbraco.Core/Services/IDashboardService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Dashboards; +using Umbraco.Cms.Core.Dashboards; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Membership; diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index 4b2ca72d8967..effb4573b45b 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -15,19 +15,23 @@ public interface IDataTypeService : IService /// IReadOnlyDictionary> GetReferences(int id); - Attempt?> CreateContainer(int parentId, Guid key, string name, - int userId = Constants.Security.SuperUserId); + Attempt?> CreateContainer(int parentId, Guid key, string name, int userId = Constants.Security.SuperUserId); Attempt SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId); + EntityContainer? GetContainer(int containerId); + EntityContainer? GetContainer(Guid containerId); + IEnumerable GetContainers(string folderName, int level); + IEnumerable GetContainers(IDataType dataType); + IEnumerable GetContainers(int[] containerIds); + Attempt DeleteContainer(int containerId, int userId = Constants.Security.SuperUserId); - Attempt?> RenameContainer(int id, string name, - int userId = Constants.Security.SuperUserId); + Attempt?> RenameContainer(int id, string name, int userId = Constants.Security.SuperUserId); /// /// Gets a by its Name diff --git a/src/Umbraco.Core/Services/IDomainService.cs b/src/Umbraco.Core/Services/IDomainService.cs index 31e5f6040c6e..54a006ecb1f9 100644 --- a/src/Umbraco.Core/Services/IDomainService.cs +++ b/src/Umbraco.Core/Services/IDomainService.cs @@ -1,14 +1,20 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Services; public interface IDomainService : IService { bool Exists(string domainName); + Attempt Delete(IDomain domain); + IDomain? GetByName(string name); + IDomain? GetById(int id); + IEnumerable GetAll(bool includeWildcards); + IEnumerable GetAssignedDomains(int contentId, bool includeWildcards); + Attempt Save(IDomain domainEntity); } diff --git a/src/Umbraco.Core/Services/IEditorConfigurationParser.cs b/src/Umbraco.Core/Services/IEditorConfigurationParser.cs index 83c70c273cad..1a37045490b6 100644 --- a/src/Umbraco.Core/Services/IEditorConfigurationParser.cs +++ b/src/Umbraco.Core/Services/IEditorConfigurationParser.cs @@ -1,10 +1,11 @@ -using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Cms.Core.Services; public interface IEditorConfigurationParser { - TConfiguration? ParseFromConfigurationEditor(IDictionary? editorValues, + TConfiguration? ParseFromConfigurationEditor( + IDictionary? editorValues, IEnumerable fields); Dictionary ParseToConfigurationEditor(TConfiguration? configuration); diff --git a/src/Umbraco.Core/Services/IEntityService.cs b/src/Umbraco.Core/Services/IEntityService.cs index 7ca182a68b11..74a416a8fe2d 100644 --- a/src/Umbraco.Core/Services/IEntityService.cs +++ b/src/Umbraco.Core/Services/IEntityService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Persistence.Querying; @@ -37,14 +37,16 @@ public interface IEntityService /// /// The type used to determine the object type of the entity. /// The identifier of the entity. - IEntitySlim? Get(int id) where T : IUmbracoEntity; + IEntitySlim? Get(int id) + where T : IUmbracoEntity; /// /// Gets an entity. /// /// The type used to determine the object type of the entity. /// The unique key of the entity. - IEntitySlim? Get(Guid key) where T : IUmbracoEntity; + IEntitySlim? Get(Guid key) + where T : IUmbracoEntity; /// /// Determines whether an entity exists. @@ -62,7 +64,8 @@ public interface IEntityService /// Gets entities of a given object type. /// /// The type used to determine the object type of the entities. - IEnumerable GetAll() where T : IUmbracoEntity; + IEnumerable GetAll() + where T : IUmbracoEntity; /// /// Gets entities of a given object type. @@ -70,7 +73,8 @@ public interface IEntityService /// The type used to determine the object type of the entities. /// The identifiers of the entities. /// If is empty, returns all entities. - IEnumerable GetAll(params int[] ids) where T : IUmbracoEntity; + IEnumerable GetAll(params int[] ids) + where T : IUmbracoEntity; /// /// Gets entities of a given object type. @@ -106,7 +110,8 @@ public interface IEntityService /// The type used to determine the object type of the entities. /// The unique identifiers of the entities. /// If is empty, returns all entities. - IEnumerable GetAll(params Guid[] keys) where T : IUmbracoEntity; + IEnumerable GetAll(params Guid[] keys) + where T : IUmbracoEntity; /// /// Gets entities of a given object type. @@ -172,31 +177,52 @@ public interface IEntityService /// /// Gets children of an entity. /// - IEnumerable GetPagedChildren(int id, UmbracoObjectTypes objectType, long pageIndex, int pageSize, + IEnumerable GetPagedChildren( + int id, + UmbracoObjectTypes objectType, + long pageIndex, + int pageSize, out long totalRecords, - IQuery? filter = null, Ordering? ordering = null); + IQuery? filter = null, + Ordering? ordering = null); /// /// Gets descendants of an entity. /// - IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes objectType, long pageIndex, int pageSize, + IEnumerable GetPagedDescendants( + int id, + UmbracoObjectTypes objectType, + long pageIndex, + int pageSize, out long totalRecords, - IQuery? filter = null, Ordering? ordering = null); + IQuery? filter = null, + Ordering? ordering = null); /// /// Gets descendants of entities. /// - IEnumerable GetPagedDescendants(IEnumerable ids, UmbracoObjectTypes objectType, long pageIndex, - int pageSize, out long totalRecords, - IQuery? filter = null, Ordering? ordering = null); + IEnumerable GetPagedDescendants( + IEnumerable ids, + UmbracoObjectTypes objectType, + long pageIndex, + int pageSize, + out long totalRecords, + IQuery? filter = null, + Ordering? ordering = null); // TODO: Do we really need this? why not just pass in -1 + /// /// Gets descendants of root. /// - IEnumerable GetPagedDescendants(UmbracoObjectTypes objectType, long pageIndex, int pageSize, + IEnumerable GetPagedDescendants( + UmbracoObjectTypes objectType, + long pageIndex, + int pageSize, out long totalRecords, - IQuery? filter = null, Ordering? ordering = null, bool includeTrashed = true); + IQuery? filter = null, + Ordering? ordering = null, + bool includeTrashed = true); /// /// Gets the object type of an entity. diff --git a/src/Umbraco.Core/Services/IEntityXmlSerializer.cs b/src/Umbraco.Core/Services/IEntityXmlSerializer.cs index f102d3522229..5ada7ab5b6bf 100644 --- a/src/Umbraco.Core/Services/IEntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/IEntityXmlSerializer.cs @@ -11,9 +11,10 @@ public interface IEntityXmlSerializer /// /// Exports an IContent item as an XElement. /// - XElement Serialize(IContent content, - bool published, - bool withDescendants = false) // TODO: take care of usage! only used for the packager + XElement Serialize( + IContent content, + bool published, + bool withDescendants = false) // TODO: take care of usage! only used for the packager ; /// @@ -64,6 +65,7 @@ XElement Serialize( XElement Serialize(IEnumerable languages); XElement Serialize(ILanguage language); + XElement Serialize(ITemplate template); /// @@ -83,5 +85,6 @@ XElement Serialize( XElement Serialize(IEnumerable macros); XElement Serialize(IMacro macro); + XElement Serialize(IContentType contentType); } diff --git a/src/Umbraco.Core/Services/IExamineIndexCountService.cs b/src/Umbraco.Core/Services/IExamineIndexCountService.cs index cd7f5848ff51..8d85e17e04c5 100644 --- a/src/Umbraco.Core/Services/IExamineIndexCountService.cs +++ b/src/Umbraco.Core/Services/IExamineIndexCountService.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Services; +namespace Umbraco.Cms.Core.Services; public interface IExamineIndexCountService { diff --git a/src/Umbraco.Core/Services/IFileService.cs b/src/Umbraco.Core/Services/IFileService.cs index 1e8980894dc5..7f329abe1e12 100644 --- a/src/Umbraco.Core/Services/IFileService.cs +++ b/src/Umbraco.Core/Services/IFileService.cs @@ -9,9 +9,13 @@ namespace Umbraco.Cms.Core.Services; public interface IFileService : IService { IEnumerable GetPartialViewSnippetNames(params string[] filterNames); + void CreatePartialViewFolder(string folderPath); + void CreatePartialViewMacroFolder(string folderPath); + void DeletePartialViewFolder(string folderPath); + void DeletePartialViewMacroFolder(string folderPath); /// @@ -21,17 +25,19 @@ public interface IFileService : IService IEnumerable GetPartialViews(params string[] names); IPartialView? GetPartialView(string path); + IPartialView? GetPartialViewMacro(string path); - Attempt CreatePartialView(IPartialView partialView, string? snippetName = null, - int? userId = Constants.Security.SuperUserId); + Attempt CreatePartialView(IPartialView partialView, string? snippetName = null, int? userId = Constants.Security.SuperUserId); - Attempt CreatePartialViewMacro(IPartialView partialView, string? snippetName = null, - int? userId = Constants.Security.SuperUserId); + Attempt CreatePartialViewMacro(IPartialView partialView, string? snippetName = null, int? userId = Constants.Security.SuperUserId); bool DeletePartialView(string path, int? userId = null); + bool DeletePartialViewMacro(string path, int? userId = null); + Attempt SavePartialView(IPartialView partialView, int? userId = null); + Attempt SavePartialViewMacro(IPartialView partialView, int? userId = null); /// @@ -254,12 +260,13 @@ public interface IFileService : IService /// /// The template created /// - Attempt?> CreateTemplateForContentType(string contentTypeAlias, - string? contentTypeName, int userId = Constants.Security.SuperUserId); - - ITemplate CreateTemplateWithIdentity(string? name, string? alias, string? content, ITemplate? masterTemplate = null, + Attempt?> CreateTemplateForContentType( + string contentTypeAlias, + string? contentTypeName, int userId = Constants.Security.SuperUserId); + ITemplate CreateTemplateWithIdentity(string? name, string? alias, string? content, ITemplate? masterTemplate = null, int userId = Constants.Security.SuperUserId); + /// /// Deletes a template by its alias /// diff --git a/src/Umbraco.Core/Services/IIdKeyMap.cs b/src/Umbraco.Core/Services/IIdKeyMap.cs index 8318261fa6e0..e85095d41fc7 100644 --- a/src/Umbraco.Core/Services/IIdKeyMap.cs +++ b/src/Umbraco.Core/Services/IIdKeyMap.cs @@ -5,10 +5,16 @@ namespace Umbraco.Cms.Core.Services; public interface IIdKeyMap { Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType); + Attempt GetIdForUdi(Udi udi); + Attempt GetUdiForId(int id, UmbracoObjectTypes umbracoObjectType); + Attempt GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType); + void ClearCache(); + void ClearCache(int id); + void ClearCache(Guid key); } diff --git a/src/Umbraco.Core/Services/IInstallationService.cs b/src/Umbraco.Core/Services/IInstallationService.cs index 8c238f83c41c..688c6298bd32 100644 --- a/src/Umbraco.Core/Services/IInstallationService.cs +++ b/src/Umbraco.Core/Services/IInstallationService.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Services; +namespace Umbraco.Cms.Core.Services; public interface IInstallationService { diff --git a/src/Umbraco.Core/Services/IIpAddressUtilities.cs b/src/Umbraco.Core/Services/IIpAddressUtilities.cs index 7c68bcfa9fe1..f6c371724416 100644 --- a/src/Umbraco.Core/Services/IIpAddressUtilities.cs +++ b/src/Umbraco.Core/Services/IIpAddressUtilities.cs @@ -1,4 +1,4 @@ -using System.Net; +using System.Net; namespace Umbraco.Cms.Core.Services; diff --git a/src/Umbraco.Core/Services/ILocalizationService.cs b/src/Umbraco.Core/Services/ILocalizationService.cs index 4bd52d16baf7..a628d8440a4e 100644 --- a/src/Umbraco.Core/Services/ILocalizationService.cs +++ b/src/Umbraco.Core/Services/ILocalizationService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Services; @@ -7,17 +7,17 @@ namespace Umbraco.Cms.Core.Services; /// public interface ILocalizationService : IService { - //Possible to-do list: - //Import DictionaryItem (?) - //RemoveByLanguage (translations) - //Add/Set Text (Insert/Update) - //Remove Text (in translation) + // Possible to-do list: + // Import DictionaryItem (?) + // RemoveByLanguage (translations) + // Add/Set Text (Insert/Update) + // Remove Text (in translation) /// /// Adds or updates a translation for a dictionary item and language /// /// - /// /// /// void AddOrUpdateDictionaryValue(IDictionaryItem item, ILanguage? language, string value); @@ -32,7 +32,7 @@ public interface ILocalizationService : IService IDictionaryItem CreateDictionaryItemWithIdentity(string key, Guid? parentId, string? defaultValue = null); /// - /// Gets a by its id + /// Gets a by its id /// /// Id of the /// diff --git a/src/Umbraco.Core/Services/ILocalizedTextService.cs b/src/Umbraco.Core/Services/ILocalizedTextService.cs index 5b5cdb1fb322..23e3888ea0b3 100644 --- a/src/Umbraco.Core/Services/ILocalizedTextService.cs +++ b/src/Umbraco.Core/Services/ILocalizedTextService.cs @@ -1,6 +1,7 @@ -using System.Globalization; +using System.Globalization; namespace Umbraco.Cms.Core.Services; + // TODO: This needs to be merged into one interface in v9, but better yet // the Localize method should just the based on area + alias and we should remove // the one with the 'key' (the concatenated area/alias) to ensure that we never use that again. @@ -24,7 +25,6 @@ public interface ILocalizedTextService /// string Localize(string? area, string? alias, CultureInfo? culture, IDictionary? tokens = null); - /// /// Returns all key/values in storage for the given culture /// diff --git a/src/Umbraco.Core/Services/IMacroService.cs b/src/Umbraco.Core/Services/IMacroService.cs index 0a1cffad3ebc..141b278d93c2 100644 --- a/src/Umbraco.Core/Services/IMacroService.cs +++ b/src/Umbraco.Core/Services/IMacroService.cs @@ -42,12 +42,12 @@ public interface IMacroService : IService ///// Gets a list all available plugins ///// ///// An enumerable list of objects - //IEnumerable GetMacroPropertyTypes(); + // IEnumerable GetMacroPropertyTypes(); ///// ///// Gets an by its alias ///// ///// Alias to retrieve an for ///// An object - //IMacroPropertyType GetMacroPropertyTypeByAlias(string alias); + // IMacroPropertyType GetMacroPropertyTypeByAlias(string alias); } diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index 54bb2007c65d..669a53b63ca6 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -9,11 +9,15 @@ namespace Umbraco.Cms.Core.Services; public interface IMediaService : IContentServiceBase { int CountNotTrashed(string? contentTypeAlias = null); + int Count(string? mediaTypeAlias = null); + int CountChildren(int parentId, string? mediaTypeAlias = null); + int CountDescendants(int parentId, string? mediaTypeAlias = null); IEnumerable GetByIds(IEnumerable ids); + IEnumerable GetByIds(IEnumerable ids); /// @@ -90,9 +94,9 @@ public interface IMediaService : IContentServiceBase /// Direction to order by /// Flag to indicate when ordering by system field /// + /// /// An Enumerable list of objects - IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, - IQuery? filter = null, Ordering? ordering = null); + IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords, IQuery? filter = null, Ordering? ordering = null); /// /// Gets a collection of objects by Parent Id @@ -104,8 +108,7 @@ IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out l /// /// /// An Enumerable list of objects - IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - IQuery? filter = null, Ordering? ordering = null); + IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, IQuery? filter = null, Ordering? ordering = null); /// /// Gets paged documents of a content @@ -116,8 +119,7 @@ IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, ou /// Total number of documents. /// Search text filter. /// Ordering infos. - IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords, - IQuery? filter = null, Ordering? ordering = null); + IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords, IQuery? filter = null, Ordering? ordering = null); /// /// Gets paged documents for specified content types @@ -128,8 +130,13 @@ IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSi /// Total number of documents. /// Search text filter. /// Ordering infos. - IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize, out long totalRecords, - IQuery? filter = null, Ordering? ordering = null); + IEnumerable GetPagedOfTypes( + int[] contentTypeIds, + long pageIndex, + int pageSize, + out long totalRecords, + IQuery? filter = null, + Ordering? ordering = null); /// /// Gets a collection of objects, which reside at the first level / root @@ -141,8 +148,12 @@ IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pa /// Gets a collection of an objects, which resides in the Recycle Bin /// /// An Enumerable list of objects - IEnumerable GetPagedMediaInRecycleBin(long pageIndex, int pageSize, out long totalRecords, - IQuery? filter = null, Ordering? ordering = null); + IEnumerable GetPagedMediaInRecycleBin( + long pageIndex, + int pageSize, + out long totalRecords, + IQuery? filter = null, + Ordering? ordering = null); /// /// Moves an object to a new location @@ -328,8 +339,7 @@ IEnumerable GetPagedMediaInRecycleBin(long pageIndex, int pageSize, out /// /// /// - IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, - int userId = Constants.Security.SuperUserId); + IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = Constants.Security.SuperUserId); /// /// Creates an object using the alias of the @@ -346,8 +356,7 @@ IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias /// /// /// - IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, - int userId = Constants.Security.SuperUserId); + IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId); /// /// Gets the content of a media as a stream. diff --git a/src/Umbraco.Core/Services/IMediaTypeService.cs b/src/Umbraco.Core/Services/IMediaTypeService.cs index d7e730d9b74f..a00b9ae5c657 100644 --- a/src/Umbraco.Core/Services/IMediaTypeService.cs +++ b/src/Umbraco.Core/Services/IMediaTypeService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Services; diff --git a/src/Umbraco.Core/Services/IMemberGroupService.cs b/src/Umbraco.Core/Services/IMemberGroupService.cs index 00606b4ea656..24cc6845adf8 100644 --- a/src/Umbraco.Core/Services/IMemberGroupService.cs +++ b/src/Umbraco.Core/Services/IMemberGroupService.cs @@ -5,10 +5,16 @@ namespace Umbraco.Cms.Core.Services; public interface IMemberGroupService : IService { IEnumerable GetAll(); + IMemberGroup? GetById(int id); + IMemberGroup? GetById(Guid id); + IEnumerable GetByIds(IEnumerable ids); + IMemberGroup? GetByName(string? name); + void Save(IMemberGroup memberGroup); + void Delete(IMemberGroup memberGroup); } diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 7f3ac2dcc305..32c7c951dab9 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Querying; namespace Umbraco.Cms.Core.Services; @@ -22,8 +22,14 @@ public interface IMemberService : IMembershipMemberService /// /// /// - IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string? memberTypeAlias = null, string filter = ""); + IEnumerable GetAll( + long pageIndex, + int pageSize, + out long totalRecords, + string orderBy, + Direction orderDirection, + string? memberTypeAlias = null, + string filter = ""); /// /// Gets a list of paged objects @@ -40,8 +46,15 @@ IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, /// /// /// - IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, string? memberTypeAlias, string filter); + IEnumerable GetAll( + long pageIndex, + int pageSize, + out long totalRecords, + string orderBy, + Direction orderDirection, + bool orderBySystemField, + string? memberTypeAlias, + string filter); /// /// Creates an object without persisting it @@ -203,8 +216,12 @@ IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, /// /// /// - IEnumerable FindMembersByDisplayName(string displayNameToMatch, long pageIndex, int pageSize, - out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + IEnumerable FindMembersByDisplayName( + string displayNameToMatch, + long pageIndex, + int pageSize, + out long totalRecords, + StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); /// /// Gets a list of Members based on a property search @@ -218,7 +235,9 @@ IEnumerable FindMembersByDisplayName(string displayNameToMatch, long pa /// /// /// - IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, string value, + IEnumerable? GetMembersByPropertyValue( + string propertyTypeAlias, + string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact); /// @@ -233,8 +252,7 @@ IEnumerable FindMembersByDisplayName(string displayNameToMatch, long pa /// /// /// - IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, int value, - ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact); + IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact); /// /// Gets a list of Members based on a property search @@ -258,6 +276,5 @@ IEnumerable FindMembersByDisplayName(string displayNameToMatch, long pa /// /// /// - IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, - ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact); + IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact); } diff --git a/src/Umbraco.Core/Services/IMemberTypeService.cs b/src/Umbraco.Core/Services/IMemberTypeService.cs index f044e4a770e1..6a70e620a165 100644 --- a/src/Umbraco.Core/Services/IMemberTypeService.cs +++ b/src/Umbraco.Core/Services/IMemberTypeService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Services; diff --git a/src/Umbraco.Core/Services/IMembershipUserService.cs b/src/Umbraco.Core/Services/IMembershipUserService.cs index dc57adcfa5bf..7a8dc2023f0e 100644 --- a/src/Umbraco.Core/Services/IMembershipUserService.cs +++ b/src/Umbraco.Core/Services/IMembershipUserService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Models.Membership; namespace Umbraco.Cms.Core.Services; diff --git a/src/Umbraco.Core/Services/IMetricsConsentService.cs b/src/Umbraco.Core/Services/IMetricsConsentService.cs index f803bb4cf14b..72f3ebe87361 100644 --- a/src/Umbraco.Core/Services/IMetricsConsentService.cs +++ b/src/Umbraco.Core/Services/IMetricsConsentService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Services; diff --git a/src/Umbraco.Core/Services/INodeCountService.cs b/src/Umbraco.Core/Services/INodeCountService.cs index 86a1c4b14c12..d442a7199f45 100644 --- a/src/Umbraco.Core/Services/INodeCountService.cs +++ b/src/Umbraco.Core/Services/INodeCountService.cs @@ -1,7 +1,8 @@ -namespace Umbraco.Cms.Core.Services; +namespace Umbraco.Cms.Core.Services; public interface INodeCountService { int GetNodeCount(Guid nodeType); + int GetMediaCount(); } diff --git a/src/Umbraco.Core/Services/INotificationService.cs b/src/Umbraco.Core/Services/INotificationService.cs index 81c21de2166f..8472333d1920 100644 --- a/src/Umbraco.Core/Services/INotificationService.cs +++ b/src/Umbraco.Core/Services/INotificationService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; @@ -16,7 +16,11 @@ public interface INotificationService : IService /// /// /// - void SendNotifications(IUser operatingUser, IEnumerable entities, string? action, string? actionName, + void SendNotifications( + IUser operatingUser, + IEnumerable entities, + string? action, + string? actionName, Uri siteUri, Func<(IUser user, NotificationEmailSubjectParams subject), string> createSubject, Func<(IUser user, NotificationEmailBodyParams body, bool isHtml), string> createBody); diff --git a/src/Umbraco.Core/Services/IPropertyValidationService.cs b/src/Umbraco.Core/Services/IPropertyValidationService.cs index fd97db652220..e854d0f7f521 100644 --- a/src/Umbraco.Core/Services/IPropertyValidationService.cs +++ b/src/Umbraco.Core/Services/IPropertyValidationService.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors; diff --git a/src/Umbraco.Core/Services/IPublicAccessService.cs b/src/Umbraco.Core/Services/IPublicAccessService.cs index 5088666a04eb..fb4f080e0348 100644 --- a/src/Umbraco.Core/Services/IPublicAccessService.cs +++ b/src/Umbraco.Core/Services/IPublicAccessService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Services; @@ -45,8 +45,7 @@ public interface IPublicAccessService : IService /// /// /// - Attempt?> AddRule(IContent content, string ruleType, - string ruleValue); + Attempt?> AddRule(IContent content, string ruleType, string ruleValue); /// /// Removes a rule diff --git a/src/Umbraco.Core/Services/IRedirectUrlService.cs b/src/Umbraco.Core/Services/IRedirectUrlService.cs index 5413f82d5867..9da327a60065 100644 --- a/src/Umbraco.Core/Services/IRedirectUrlService.cs +++ b/src/Umbraco.Core/Services/IRedirectUrlService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Services; diff --git a/src/Umbraco.Core/Services/ISectionService.cs b/src/Umbraco.Core/Services/ISectionService.cs index 425637908583..515896cafceb 100644 --- a/src/Umbraco.Core/Services/ISectionService.cs +++ b/src/Umbraco.Core/Services/ISectionService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Sections; +using Umbraco.Cms.Core.Sections; namespace Umbraco.Cms.Core.Services; diff --git a/src/Umbraco.Core/Services/IService.cs b/src/Umbraco.Core/Services/IService.cs index 1bf32b04e164..3147b34a5662 100644 --- a/src/Umbraco.Core/Services/IService.cs +++ b/src/Umbraco.Core/Services/IService.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Services; +namespace Umbraco.Cms.Core.Services; /// /// Marker interface for services, which is used to store difference services in a list or dictionary diff --git a/src/Umbraco.Core/Services/ITagService.cs b/src/Umbraco.Core/Services/ITagService.cs index 7a6766a4ce26..ce436d74a49a 100644 --- a/src/Umbraco.Core/Services/ITagService.cs +++ b/src/Umbraco.Core/Services/ITagService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Services; @@ -77,8 +77,7 @@ public interface ITagService : IService /// /// Gets all tags attached to an entity via a property. /// - IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string? group = null, - string? culture = null); + IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string? group = null, string? culture = null); /// /// Gets all tags attached to an entity. @@ -88,8 +87,7 @@ IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, st /// /// Gets all tags attached to an entity via a property. /// - IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string? group = null, - string? culture = null); + IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string? group = null, string? culture = null); /// /// Gets all tags attached to an entity. diff --git a/src/Umbraco.Core/Services/ITreeService.cs b/src/Umbraco.Core/Services/ITreeService.cs index 8d0a9db7150b..d61fca066a9b 100644 --- a/src/Umbraco.Core/Services/ITreeService.cs +++ b/src/Umbraco.Core/Services/ITreeService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Trees; +using Umbraco.Cms.Core.Trees; namespace Umbraco.Cms.Core.Services; diff --git a/src/Umbraco.Core/Services/IUpgradeService.cs b/src/Umbraco.Core/Services/IUpgradeService.cs index df7e0f285bae..2f1e65f00aa2 100644 --- a/src/Umbraco.Core/Services/IUpgradeService.cs +++ b/src/Umbraco.Core/Services/IUpgradeService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Semver; +using Umbraco.Cms.Core.Semver; namespace Umbraco.Cms.Core.Services; diff --git a/src/Umbraco.Core/Services/IUsageInformationService.cs b/src/Umbraco.Core/Services/IUsageInformationService.cs index a36072c9679e..1d4caaa5268e 100644 --- a/src/Umbraco.Core/Services/IUsageInformationService.cs +++ b/src/Umbraco.Core/Services/IUsageInformationService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Services; diff --git a/src/Umbraco.Core/Services/IUserDataService.cs b/src/Umbraco.Core/Services/IUserDataService.cs index dd557739d323..0bb1d10cc4d9 100644 --- a/src/Umbraco.Core/Services/IUserDataService.cs +++ b/src/Umbraco.Core/Services/IUserDataService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Services; diff --git a/src/Umbraco.Core/Services/IdKeyMap.cs b/src/Umbraco.Core/Services/IdKeyMap.cs index bd5c5e8e5c1f..7aa746ae27a8 100644 --- a/src/Umbraco.Core/Services/IdKeyMap.cs +++ b/src/Umbraco.Core/Services/IdKeyMap.cs @@ -14,12 +14,6 @@ public class IdKeyMap : IIdKeyMap, IDisposable private readonly Dictionary> _id2Key = new(); private readonly Dictionary> _key2Id = new(); - public IdKeyMap(ICoreScopeProvider scopeProvider, IIdKeyMapRepository idKeyMapRepository) - { - _scopeProvider = scopeProvider; - _idKeyMapRepository = idKeyMapRepository; - } - // note - for pure read-only we might want to *not* enforce a transaction? // notes @@ -43,36 +37,21 @@ public IdKeyMap(ICoreScopeProvider scopeProvider, IIdKeyMapRepository idKeyMapRe // if the idkMap already knows about the map, it returns the value // else it tries the published cache via mappers // else it hits the database - private readonly ConcurrentDictionary id2key, Func key2id)> _dictionary = new(); + public IdKeyMap(ICoreScopeProvider scopeProvider, IIdKeyMapRepository idKeyMapRepository) + { + _scopeProvider = scopeProvider; + _idKeyMapRepository = idKeyMapRepository; + } + private bool _disposedValue; public void SetMapper(UmbracoObjectTypes umbracoObjectType, Func id2key, Func key2id) => _dictionary[umbracoObjectType] = (id2key, key2id); - internal void Populate(IEnumerable<(int id, Guid key)> pairs, UmbracoObjectTypes umbracoObjectType) - { - try - { - _locker.EnterWriteLock(); - foreach ((int id, Guid key) pair in pairs) - { - _id2Key[pair.id] = new TypedId(pair.key, umbracoObjectType); - _key2Id[pair.key] = new TypedId(pair.id, umbracoObjectType); - } - } - finally - { - if (_locker.IsWriteLockHeld) - { - _locker.ExitWriteLock(); - } - } - } - #if POPULATE_FROM_DATABASE private void PopulateLocked() { @@ -165,7 +144,6 @@ public Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType) // optimize for read speed: reading database outside a lock means that we could read // multiple times, but we don't lock the cache while accessing the database = better - int? val = null; if (_dictionary.TryGetValue(umbracoObjectType, out (Func id2key, Func key2id) mappers)) @@ -191,9 +169,8 @@ public Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType) } // cache reservations, when something is saved this cache is cleared anyways - //if (umbracoObjectType == UmbracoObjectTypes.IdReservation) + // if (umbracoObjectType == UmbracoObjectTypes.IdReservation) // Attempt.Succeed(val.Value); - try { _locker.EnterWriteLock(); @@ -211,6 +188,26 @@ public Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType) return Attempt.Succeed(val.Value); } + internal void Populate(IEnumerable<(int id, Guid key)> pairs, UmbracoObjectTypes umbracoObjectType) + { + try + { + _locker.EnterWriteLock(); + foreach ((int id, Guid key) in pairs) + { + _id2Key[id] = new TypedId(key, umbracoObjectType); + _key2Id[key] = new TypedId(id, umbracoObjectType); + } + } + finally + { + if (_locker.IsWriteLockHeld) + { + _locker.ExitWriteLock(); + } + } + } + public Attempt GetIdForUdi(Udi udi) { var guidUdi = udi as GuidUdi; @@ -227,7 +224,8 @@ public Attempt GetIdForUdi(Udi udi) { Attempt keyAttempt = GetKeyForId(id, umbracoObjectType); return keyAttempt.Success - ? Attempt.Succeed(new GuidUdi(UdiEntityTypeHelper.FromUmbracoObjectType(umbracoObjectType), + ? Attempt.Succeed(new GuidUdi( + UdiEntityTypeHelper.FromUmbracoObjectType(umbracoObjectType), keyAttempt.Result)) : Attempt.Fail(); } @@ -263,7 +261,6 @@ public Attempt GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType) // optimize for read speed: reading database outside a lock means that we could read // multiple times, but we don't lock the cache while accessing the database = better - Guid? val = null; if (_dictionary.TryGetValue(umbracoObjectType, out (Func id2key, Func key2id) mappers)) @@ -289,9 +286,8 @@ public Attempt GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType) } // cache reservations, when something is saved this cache is cleared anyways - //if (umbracoObjectType == UmbracoObjectTypes.IdReservation) + // if (umbracoObjectType == UmbracoObjectTypes.IdReservation) // Attempt.Succeed(val.Value); - try { _locker.EnterWriteLock(); @@ -372,17 +368,21 @@ public void ClearCache(Guid key) } } - // ReSharper disable ClassNeverInstantiated.Local - // ReSharper disable UnusedAutoPropertyAccessor.Local - private class TypedIdDto + protected virtual void Dispose(bool disposing) { - public int Id { get; set; } - public Guid UniqueId { get; set; } - public Guid NodeObjectType { get; set; } + if (!_disposedValue) + { + if (disposing) + { + _locker.Dispose(); + } + + _disposedValue = true; + } } + // ReSharper restore ClassNeverInstantiated.Local // ReSharper restore UnusedAutoPropertyAccessor.Local - private struct TypedId { public TypedId(T id, UmbracoObjectTypes umbracoObjectType) @@ -396,20 +396,19 @@ public TypedId(T id, UmbracoObjectTypes umbracoObjectType) public T Id { get; } } - protected virtual void Dispose(bool disposing) + // ReSharper disable ClassNeverInstantiated.Local + // ReSharper disable UnusedAutoPropertyAccessor.Local + private class TypedIdDto { - if (!_disposedValue) - { - if (disposing) - { - _locker.Dispose(); - } + public int Id { get; set; } - _disposedValue = true; - } + public Guid UniqueId { get; set; } + + public Guid NodeObjectType { get; set; } } public void Dispose() => + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(true); } diff --git a/src/Umbraco.Core/Services/KeyValueService.cs b/src/Umbraco.Core/Services/KeyValueService.cs index de080a84cece..0a38e3c28469 100644 --- a/src/Umbraco.Core/Services/KeyValueService.cs +++ b/src/Umbraco.Core/Services/KeyValueService.cs @@ -40,10 +40,10 @@ public void SetValue(string key, string value) { scope.WriteLock(Constants.Locks.KeyValues); - IKeyValue keyValue = _repository.Get(key); + IKeyValue? keyValue = _repository.Get(key); if (keyValue == null) { - keyValue = new KeyValue {Identifier = key, Value = value, UpdateDate = DateTime.Now}; + keyValue = new KeyValue { Identifier = key, Value = value, UpdateDate = DateTime.Now }; } else { @@ -73,7 +73,7 @@ public bool TrySetValue(string key, string originalValue, string newValue) { scope.WriteLock(Constants.Locks.KeyValues); - IKeyValue keyValue = _repository.Get(key); + IKeyValue? keyValue = _repository.Get(key); if (keyValue == null || keyValue.Value != originalValue) { return false; diff --git a/src/Umbraco.Core/Services/LocalizationService.cs b/src/Umbraco.Core/Services/LocalizationService.cs index 2d9603a9094c..dac05438e927 100644 --- a/src/Umbraco.Core/Services/LocalizationService.cs +++ b/src/Umbraco.Core/Services/LocalizationService.cs @@ -55,7 +55,7 @@ public void AddOrUpdateDictionaryValue(IDictionaryItem item, ILanguage? language throw new ArgumentNullException(nameof(language)); } - IDictionaryTranslation existing = item.Translations?.FirstOrDefault(x => x.Language?.Id == language.Id); + IDictionaryTranslation? existing = item.Translations?.FirstOrDefault(x => x.Language?.Id == language.Id); if (existing != null) { existing.Value = value; @@ -66,12 +66,12 @@ public void AddOrUpdateDictionaryValue(IDictionaryItem item, ILanguage? language { item.Translations = new List(item.Translations) { - new DictionaryTranslation(language, value) + new DictionaryTranslation(language, value), }; } else { - item.Translations = new List {new DictionaryTranslation(language, value)}; + item.Translations = new List { new DictionaryTranslation(language, value) }; } } } @@ -87,11 +87,10 @@ public IDictionaryItem CreateDictionaryItemWithIdentity(string key, Guid? parent { using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - //validate the parent - + // validate the parent if (parentId.HasValue && parentId.Value != Guid.Empty) { - IDictionaryItem parent = GetDictionaryItemById(parentId.Value); + IDictionaryItem? parent = GetDictionaryItemById(parentId.Value); if (parent == null) { throw new ArgumentException($"No parent dictionary item was found with id {parentId.Value}."); @@ -134,7 +133,7 @@ public IDictionaryItem CreateDictionaryItemWithIdentity(string key, Guid? parent } /// - /// Gets a by its id + /// Gets a by its id /// /// Id of the /// @@ -144,8 +143,9 @@ public IDictionaryItem CreateDictionaryItemWithIdentity(string key, Guid? parent { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - IDictionaryItem item = _dictionaryRepository.Get(id); - //ensure the lazy Language callback is assigned + IDictionaryItem? item = _dictionaryRepository.Get(id); + + // ensure the lazy Language callback is assigned EnsureDictionaryItemLanguageCallback(item); return item; } @@ -162,8 +162,9 @@ public IDictionaryItem CreateDictionaryItemWithIdentity(string key, Guid? parent { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - IDictionaryItem item = _dictionaryRepository.Get(id); - //ensure the lazy Language callback is assigned + IDictionaryItem? item = _dictionaryRepository.Get(id); + + // ensure the lazy Language callback is assigned EnsureDictionaryItemLanguageCallback(item); return item; } @@ -180,8 +181,9 @@ public IDictionaryItem CreateDictionaryItemWithIdentity(string key, Guid? parent { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - IDictionaryItem item = _dictionaryRepository.Get(key); - //ensure the lazy Language callback is assigned + IDictionaryItem? item = _dictionaryRepository.Get(key); + + // ensure the lazy Language callback is assigned EnsureDictionaryItemLanguageCallback(item); return item; } @@ -197,10 +199,10 @@ public IDictionaryItem CreateDictionaryItemWithIdentity(string key, Guid? parent using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { IQuery query = Query().Where(x => x.ParentId == parentId); - IDictionaryItem[] items = _dictionaryRepository.Get(query)?.ToArray(); + IDictionaryItem[]? items = _dictionaryRepository.Get(query)?.ToArray(); if (items is not null) { - //ensure the lazy Language callback is assigned + // ensure the lazy Language callback is assigned foreach (IDictionaryItem item in items) { EnsureDictionaryItemLanguageCallback(item); @@ -221,7 +223,8 @@ public IEnumerable GetDictionaryItemDescendants(Guid? parentId) using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { IDictionaryItem[] items = _dictionaryRepository.GetDictionaryItemDescendants(parentId).ToArray(); - //ensure the lazy Language callback is assigned + + // ensure the lazy Language callback is assigned foreach (IDictionaryItem item in items) { EnsureDictionaryItemLanguageCallback(item); @@ -240,10 +243,10 @@ public IEnumerable GetDictionaryItemDescendants(Guid? parentId) using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { IQuery query = Query().Where(x => x.ParentId == null); - IDictionaryItem[] items = _dictionaryRepository.Get(query)?.ToArray(); + IDictionaryItem[]? items = _dictionaryRepository.Get(query)?.ToArray(); if (items is not null) { - //ensure the lazy Language callback is assigned + // ensure the lazy Language callback is assigned foreach (IDictionaryItem item in items) { EnsureDictionaryItemLanguageCallback(item); @@ -263,7 +266,7 @@ public bool DictionaryItemExists(string key) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - IDictionaryItem item = _dictionaryRepository.Get(key); + IDictionaryItem? item = _dictionaryRepository.Get(key); return item != null; } } @@ -289,7 +292,6 @@ public void Save(IDictionaryItem dictionaryItem, int userId = Constants.Security // ensure the lazy Language callback is assigned // ensure the lazy Language callback is assigned - EnsureDictionaryItemLanguageCallback(dictionaryItem); scope.Notifications.Publish( new DictionaryItemSavedNotification(dictionaryItem, eventMessages).WithStateFrom(savingNotification)); @@ -506,7 +508,9 @@ private bool CreatesCycle(ILanguage language, IDictionary langua } var id = language.FallbackLanguageId; - while (true) // assuming languages does not already contains a cycle, this must end + + // assuming languages does not already contains a cycle, this must end + while (true) { if (!id.HasValue) { @@ -536,14 +540,13 @@ private void Audit(AuditType type, string message, int userId, int objectId, str /// private void EnsureDictionaryItemLanguageCallback(IDictionaryItem? d) { - var item = d as DictionaryItem; - if (item == null) + if (d is not DictionaryItem item) { return; } item.GetLanguage = GetLanguageById; - IEnumerable translations = item.Translations?.OfType(); + IEnumerable? translations = item.Translations?.OfType(); if (translations is not null) { foreach (DictionaryTranslation trans in translations) diff --git a/src/Umbraco.Core/Services/LocalizedTextService.cs b/src/Umbraco.Core/Services/LocalizedTextService.cs index 45909fd808f1..839e52f49e15 100644 --- a/src/Umbraco.Core/Services/LocalizedTextService.cs +++ b/src/Umbraco.Core/Services/LocalizedTextService.cs @@ -21,15 +21,11 @@ private readonly Lazy /// /// - public LocalizedTextService(Lazy fileSources, + public LocalizedTextService( + Lazy fileSources, ILogger logger) { - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } - - _logger = logger; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); if (fileSources == null) { throw new ArgumentNullException(nameof(fileSources)); @@ -49,7 +45,8 @@ public LocalizedTextService(Lazy fileSources, /// /// /// - public LocalizedTextService(IDictionary> source, + public LocalizedTextService( + IDictionary> source, ILogger logger) { if (source == null) @@ -69,8 +66,10 @@ public LocalizedTextService(IDictionary> source, [Obsolete( "Use other ctor with IDictionary>>> as input parameter.")] - public LocalizedTextService(IDictionary>> source, - ILogger logger) : this( + public LocalizedTextService( + IDictionary>> source, + ILogger logger) + : this( source.ToDictionary(x => x.Key, x => new Lazy>>(() => x.Value)), logger) { @@ -98,7 +97,8 @@ public LocalizedTextService( Dictionary> areaAliaValue = GetAreaStoredTranslations(source, cultureDictionary.Key); - cultureNoAreaDictionary.Add(cultureDictionary.Key, + cultureNoAreaDictionary.Add( + cultureDictionary.Key, new Lazy>(() => GetAliasValues(areaAliaValue))); } @@ -106,21 +106,20 @@ public LocalizedTextService( new Lazy>>>(() => cultureNoAreaDictionary); } - private IDictionary>>> _dictionarySource => + private IDictionary>>> DictionarySource => _dictionarySourceLazy.Value; - private IDictionary>> _noAreaDictionarySource => + private IDictionary>> NoAreaDictionarySource => _noAreaDictionarySourceLazy.Value; - public string Localize(string? area, string? alias, CultureInfo? culture, - IDictionary? tokens = null) + public string Localize(string? area, string? alias, CultureInfo? culture, IDictionary? tokens = null) { if (culture == null) { throw new ArgumentNullException(nameof(culture)); } - //This is what the legacy ui service did + // This is what the legacy ui service did if (string.IsNullOrEmpty(alias)) { return string.Empty; @@ -145,7 +144,7 @@ public IDictionary GetAllStoredValues(CultureInfo culture) // TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode culture = ConvertToSupportedCultureWithRegionCode(culture); - if (_dictionarySource.ContainsKey(culture) == false) + if (DictionarySource.ContainsKey(culture) == false) { _logger.LogWarning( "The culture specified {Culture} was not found in any configured sources for this service", @@ -154,13 +153,15 @@ public IDictionary GetAllStoredValues(CultureInfo culture) } IDictionary result = new Dictionary(); - //convert all areas + keys to a single key with a '/' - foreach (KeyValuePair> area in _dictionarySource[culture].Value) + + // convert all areas + keys to a single key with a '/' + foreach (KeyValuePair> area in DictionarySource[culture].Value) { foreach (KeyValuePair key in area.Value) { var dictionaryKey = string.Format("{0}/{1}", area.Key, key.Key); - //i don't think it's possible to have duplicates because we're dealing with a dictionary in the first place, but we'll double check here just in case. + + // i don't think it's possible to have duplicates because we're dealing with a dictionary in the first place, but we'll double check here just in case. if (result.ContainsKey(dictionaryKey) == false) { result.Add(dictionaryKey, key.Value); @@ -175,7 +176,7 @@ public IDictionary GetAllStoredValues(CultureInfo culture) /// Returns a list of all currently supported cultures /// /// - public IEnumerable GetSupportedCultures() => _dictionarySource.Keys; + public IEnumerable GetSupportedCultures() => DictionarySource.Keys; /// /// Tries to resolve a full 4 letter culture from a 2 letter culture name @@ -210,7 +211,7 @@ public CultureInfo ConvertToSupportedCultureWithRegionCode(CultureInfo currentCu return currentCulture; } - Attempt attempt = + Attempt attempt = _fileSources.Value.TryConvert2LetterCultureTo4Letter(currentCulture.TwoLetterISOLanguageName); return attempt.Success ? attempt.Result! : currentCulture; } @@ -229,7 +230,7 @@ public IDictionary> GetAllStoredValuesByArea // TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode culture = ConvertToSupportedCultureWithRegionCode(culture); - if (_dictionarySource.ContainsKey(culture) == false) + if (DictionarySource.ContainsKey(culture) == false) { _logger.LogWarning( "The culture specified {Culture} was not found in any configured sources for this service", @@ -237,7 +238,75 @@ public IDictionary> GetAllStoredValuesByArea return new Dictionary>(0); } - return _dictionarySource[culture].Value; + return DictionarySource[culture].Value; + } + + public string Localize(string key, CultureInfo culture, IDictionary? tokens = null) + { + if (culture == null) + { + throw new ArgumentNullException(nameof(culture)); + } + + // This is what the legacy ui service did + if (string.IsNullOrEmpty(key)) + { + return string.Empty; + } + + var keyParts = key.Split(Constants.CharArrays.ForwardSlash, StringSplitOptions.RemoveEmptyEntries); + var area = keyParts.Length > 1 ? keyParts[0] : null; + var alias = keyParts.Length > 1 ? keyParts[1] : keyParts[0]; + return Localize(area, alias, culture, tokens); + } + + /// + /// Parses the tokens in the value + /// + /// + /// + /// + /// + /// This is based on how the legacy ui localized text worked, each token was just a sequential value delimited with a % + /// symbol. + /// For example: hello %0%, you are %1% ! + /// Since we're going to continue using the same language files for now, the token system needs to remain the same. + /// With our new service + /// we support a dictionary which means in the future we can really have any sort of token system. + /// Currently though, the token key's will need to be an integer and sequential - though we aren't going to throw + /// exceptions if that is not the case. + /// + internal static string ParseTokens(string value, IDictionary? tokens) + { + if (tokens == null || tokens.Any() == false) + { + return value; + } + + foreach (KeyValuePair token in tokens) + { + value = value.Replace(string.Concat("%", token.Key, "%"), token.Value); + } + + return value; + } + + private static Dictionary GetAliasValues( + Dictionary> areaAliaValue) + { + var aliasValue = new Dictionary(); + foreach (KeyValuePair> area in areaAliaValue) + { + foreach (KeyValuePair alias in area.Value) + { + if (!aliasValue.ContainsKey(alias.Key)) + { + aliasValue.Add(alias.Key, alias.Value); + } + } + } + + return aliasValue; } private IDictionary>> FileSourcesToNoAreaDictionarySources( @@ -285,43 +354,6 @@ private IDictionary GetAliasValues( - Dictionary> areaAliaValue) - { - var aliasValue = new Dictionary(); - foreach (KeyValuePair> area in areaAliaValue) - { - foreach (KeyValuePair alias in area.Value) - { - if (!aliasValue.ContainsKey(alias.Key)) - { - aliasValue.Add(alias.Key, alias.Value); - } - } - } - - return aliasValue; - } - - public string Localize(string key, CultureInfo culture, IDictionary? tokens = null) - { - if (culture == null) - { - throw new ArgumentNullException(nameof(culture)); - } - - //This is what the legacy ui service did - if (string.IsNullOrEmpty(key)) - { - return string.Empty; - } - - var keyParts = key.Split(Constants.CharArrays.ForwardSlash, StringSplitOptions.RemoveEmptyEntries); - var area = keyParts.Length > 1 ? keyParts[0] : null; - var alias = keyParts.Length > 1 ? keyParts[1] : keyParts[0]; - return Localize(area, alias, culture, tokens); - } - private IDictionary> GetAreaStoredTranslations( IDictionary> xmlSource, CultureInfo cult) { @@ -335,7 +367,8 @@ private IDictionary> GetAreaStoredTranslatio { var dictionaryKey = (string)key.Attribute("alias")!; - //there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files + + // there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files if (result.ContainsKey(dictionaryKey) == false) { result.Add(dictionaryKey, key.Value); @@ -345,7 +378,7 @@ private IDictionary> GetAreaStoredTranslatio overallResult.Add(area.Attribute("alias")!.Value, result); } - //Merge English Dictionary + // Merge English Dictionary var englishCulture = new CultureInfo("en-US"); if (!cult.Equals(englishCulture)) { @@ -364,7 +397,8 @@ private IDictionary> GetAreaStoredTranslatio { var dictionaryKey = (string)key.Attribute("alias")!; - //there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files + + // there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files if (result.ContainsKey(dictionaryKey) == false) { result.Add(dictionaryKey, key.Value); @@ -391,14 +425,15 @@ private Dictionary GetNoAreaStoredTranslations( { var dictionaryKey = (string)key.Attribute("alias")!; - //there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files + + // there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files if (result.ContainsKey(dictionaryKey) == false) { result.Add(dictionaryKey, key.Value); } } - //Merge English Dictionary + // Merge English Dictionary var englishCulture = new CultureInfo("en-US"); if (!cult.Equals(englishCulture)) { @@ -408,7 +443,8 @@ private Dictionary GetNoAreaStoredTranslations( { var dictionaryKey = (string)key.Attribute("alias")!; - //there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files + + // there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files if (result.ContainsKey(dictionaryKey) == false) { result.Add(dictionaryKey, key.Value); @@ -432,7 +468,7 @@ private Dictionary> GetAreaStoredTranslation ICollection keys = area.Value.Keys; foreach (var key in keys) { - //there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files + // there could be duplicates if the language file isn't formatted nicely - which is probably the case for quite a few lang files if (result.ContainsKey(key) == false) { result.Add(key, area.Value[key]); @@ -445,10 +481,9 @@ private Dictionary> GetAreaStoredTranslation return overallResult; } - private string GetFromDictionarySource(CultureInfo culture, string? area, string key, - IDictionary? tokens) + private string GetFromDictionarySource(CultureInfo culture, string? area, string key, IDictionary? tokens) { - if (_dictionarySource.ContainsKey(culture) == false) + if (DictionarySource.ContainsKey(culture) == false) { _logger.LogWarning( "The culture specified {Culture} was not found in any configured sources for this service", @@ -456,63 +491,30 @@ private string GetFromDictionarySource(CultureInfo culture, string? area, string return "[" + key + "]"; } - string? found = null; if (string.IsNullOrWhiteSpace(area)) { - _noAreaDictionarySource[culture].Value.TryGetValue(key, out found); + NoAreaDictionarySource[culture].Value.TryGetValue(key, out found); } else { - if (_dictionarySource[culture].Value.TryGetValue(area, out IDictionary areaDictionary)) + if (DictionarySource[culture].Value.TryGetValue(area, out IDictionary? areaDictionary)) { areaDictionary.TryGetValue(key, out found); } if (found == null) { - _noAreaDictionarySource[culture].Value.TryGetValue(key, out found); + NoAreaDictionarySource[culture].Value.TryGetValue(key, out found); } } - if (found != null) { return ParseTokens(found, tokens); } - //NOTE: Based on how legacy works, the default text does not contain the area, just the key + // NOTE: Based on how legacy works, the default text does not contain the area, just the key return "[" + key + "]"; } - - /// - /// Parses the tokens in the value - /// - /// - /// - /// - /// - /// This is based on how the legacy ui localized text worked, each token was just a sequential value delimited with a % - /// symbol. - /// For example: hello %0%, you are %1% ! - /// Since we're going to continue using the same language files for now, the token system needs to remain the same. - /// With our new service - /// we support a dictionary which means in the future we can really have any sort of token system. - /// Currently though, the token key's will need to be an integer and sequential - though we aren't going to throw - /// exceptions if that is not the case. - /// - internal static string ParseTokens(string value, IDictionary? tokens) - { - if (tokens == null || tokens.Any() == false) - { - return value; - } - - foreach (KeyValuePair token in tokens) - { - value = value.Replace(string.Concat("%", token.Key, "%"), token.Value); - } - - return value; - } } diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs index e2a372515d41..09a6b5a53c75 100644 --- a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs +++ b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs @@ -39,33 +39,13 @@ public static string Localize(this ILocalizedTextService manager, string? area, /// /// /// - public static string Localize(this ILocalizedTextService manager, string area, string alias, CultureInfo culture, - string?[] tokens) + public static string Localize(this ILocalizedTextService manager, string area, string alias, CultureInfo culture, string?[] tokens) => manager.Localize(area, alias, culture, ConvertToDictionaryVars(tokens)); - /// - /// Convert an array of strings to a dictionary of indices -> values - /// - /// - /// - internal static IDictionary? ConvertToDictionaryVars(string?[]? variables) - { - if (variables == null) - { - return null; - } - - if (variables.Any() == false) - { - return null; - } - - return variables.Select((s, i) => new {index = i.ToString(CultureInfo.InvariantCulture), value = s}) - .ToDictionary(keyvals => keyvals.index, keyvals => keyvals.value); - } - - public static string? UmbracoDictionaryTranslate(this ILocalizedTextService manager, - ICultureDictionary cultureDictionary, string? text) + public static string? UmbracoDictionaryTranslate( + this ILocalizedTextService manager, + ICultureDictionary cultureDictionary, + string? text) { if (text == null) { @@ -77,7 +57,7 @@ public static string Localize(this ILocalizedTextService manager, string area, s return text; } - text = text.Substring(1); + text = text[1..]; var value = cultureDictionary[text]; if (value.IsNullOrWhiteSpace() == false) { @@ -99,4 +79,25 @@ public static string Localize(this ILocalizedTextService manager, string area, s value = manager.Localize(areaAndKey[0], areaAndKey[1]); return value.StartsWith("[") ? text : value; } + + /// + /// Convert an array of strings to a dictionary of indices -> values + /// + /// + /// + internal static IDictionary? ConvertToDictionaryVars(string?[]? variables) + { + if (variables == null) + { + return null; + } + + if (variables.Any() == false) + { + return null; + } + + return variables.Select((s, i) => new { index = i.ToString(CultureInfo.InvariantCulture), value = s }) + .ToDictionary(keyvals => keyvals.index, keyvals => keyvals.value); + } } diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs b/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs index 6de89f3d5a7b..6ac866aef947 100644 --- a/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs +++ b/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs @@ -36,7 +36,8 @@ public LocalizedTextServiceFileSources( logger, appCaches, fileSourceFolder, - supplementFileSources, new NotFoundDirectoryContents()) + supplementFileSources, + new NotFoundDirectoryContents()) { } @@ -46,45 +47,29 @@ public LocalizedTextServiceFileSources( /// localization files. The supplemental files will be loaded in and merged in after the primary files. /// The supplemental files must be named with the 4 letter culture name with a hyphen such as : en-AU.xml /// - /// - /// - /// - /// public LocalizedTextServiceFileSources( ILogger logger, AppCaches appCaches, DirectoryInfo fileSourceFolder, IEnumerable supplementFileSources, - IDirectoryContents directoryContents - ) + IDirectoryContents directoryContents) { - if (logger == null) - { - throw new ArgumentNullException("logger"); - } - if (appCaches == null) { - throw new ArgumentNullException("cache"); - } - - if (fileSourceFolder == null) - { - throw new ArgumentNullException("fileSourceFolder"); + throw new ArgumentNullException("appCaches"); } - _logger = logger; + _logger = logger ?? throw new ArgumentNullException("logger"); _directoryContents = directoryContents; _cache = appCaches.RuntimeCache; - _fileSourceFolder = fileSourceFolder; + _fileSourceFolder = fileSourceFolder ?? throw new ArgumentNullException("fileSourceFolder"); _supplementFileSources = supplementFileSources; - //Create the lazy source for the _xmlSources + // Create the lazy source for the _xmlSources _xmlSources = new Lazy>>(() => { var result = new Dictionary>(); - IEnumerable files = GetLanguageFiles(); if (!files.Any()) @@ -105,8 +90,8 @@ IDirectoryContents directoryContents CultureInfo? culture = null; if (filename.Length == 2) { - //we need to open the file to see if we can read it's 'real' culture, we'll use XmlReader since we don't - //want to load in the entire doc into mem just to read a single value + // we need to open the file to see if we can read it's 'real' culture, we'll use XmlReader since we don't + // want to load in the entire doc into mem just to read a single value using (Stream fs = fileInfo.CreateReadStream()) using (var reader = XmlReader.Create(fs)) { @@ -120,15 +105,18 @@ IDirectoryContents directoryContents try { culture = CultureInfo.GetCultureInfo(cultureVal); - //add to the tracked dictionary + + // add to the tracked dictionary _twoLetterCultureConverter[filename] = culture; } catch (CultureNotFoundException) { _logger.LogWarning( "The culture {CultureValue} found in the file {CultureFile} is not a valid culture", - cultureVal, fileInfo.Name); - //If the culture in the file is invalid, we'll just hope the file name is a valid culture below, otherwise + cultureVal, + fileInfo.Name); + + // If the culture in the file is invalid, we'll just hope the file name is a valid culture below, otherwise // an exception will be thrown. } } @@ -142,23 +130,27 @@ IDirectoryContents directoryContents culture = CultureInfo.GetCultureInfo(filename); } - //get the lazy value from cache - result[culture] = new Lazy(() => _cache.GetCacheItem( - string.Format("{0}-{1}", typeof(LocalizedTextServiceFileSources).Name, culture.Name), () => + // get the lazy value from cache + result[culture] = new Lazy( + () => _cache.GetCacheItem( + string.Format("{0}-{1}", typeof(LocalizedTextServiceFileSources).Name, culture.Name), + () => { XDocument xdoc; - //load in primary + // load in primary using (Stream fs = localCopy.CreateReadStream()) { xdoc = XDocument.Load(fs); } - //load in supplementary + // load in supplementary MergeSupplementaryFiles(culture, xdoc); return xdoc; - }, isSliding: true, timeout: TimeSpan.FromMinutes(10))!); + }, + isSliding: true, + timeout: TimeSpan.FromMinutes(10))!); } return result; @@ -168,12 +160,17 @@ IDirectoryContents directoryContents /// /// Constructor /// - public LocalizedTextServiceFileSources(ILogger logger, AppCaches appCaches, - DirectoryInfo fileSourceFolder) + public LocalizedTextServiceFileSources(ILogger logger, AppCaches appCaches, DirectoryInfo fileSourceFolder) : this(logger, appCaches, fileSourceFolder, Enumerable.Empty()) { } + /// + /// returns all xml sources for all culture files found in the folder + /// + /// + public IDictionary> GetXmlSources() => _xmlSources.Value; + private IEnumerable GetLanguageFiles() { var result = new List(); @@ -182,36 +179,28 @@ private IEnumerable GetLanguageFiles() { result.AddRange( new PhysicalDirectoryContents(_fileSourceFolder.FullName) - .Where(x => !x.IsDirectory && x.Name.EndsWith(".xml")) - ); + .Where(x => !x.IsDirectory && x.Name.EndsWith(".xml"))); } if (_directoryContents.Exists) { result.AddRange( _directoryContents - .Where(x => !x.IsDirectory && x.Name.EndsWith(".xml")) - ); + .Where(x => !x.IsDirectory && x.Name.EndsWith(".xml"))); } return result; } - /// - /// returns all xml sources for all culture files found in the folder - /// - /// - public IDictionary> GetXmlSources() => _xmlSources.Value; - // TODO: See other notes in this class, this is purely a hack because we store 2 letter culture file names that contain 4 letter cultures :( public Attempt TryConvert2LetterCultureTo4Letter(string twoLetterCulture) { if (twoLetterCulture.Length != 2) { - return Attempt.Fail(); + return Attempt.Fail(); } - //This needs to be resolved before continuing so that the _twoLetterCultureConverter cache is initialized + // This needs to be resolved before continuing so that the _twoLetterCultureConverter cache is initialized Dictionary> resolved = _xmlSources.Value; return _twoLetterCultureConverter.ContainsKey(twoLetterCulture) @@ -227,11 +216,11 @@ private IEnumerable GetLanguageFiles() throw new ArgumentNullException("culture"); } - //This needs to be resolved before continuing so that the _twoLetterCultureConverter cache is initialized + // This needs to be resolved before continuing so that the _twoLetterCultureConverter cache is initialized Dictionary> resolved = _xmlSources.Value; return _twoLetterCultureConverter.Values.Contains(culture) - ? Attempt.Succeed(culture.Name.Substring(0, 2)) + ? Attempt.Succeed(culture.Name[..2]) : Attempt.Fail(); } @@ -244,16 +233,15 @@ private void MergeSupplementaryFiles(CultureInfo culture, XDocument xMasterDoc) if (_supplementFileSources != null) { - //now load in supplementary + // now load in supplementary IEnumerable found = _supplementFileSources.Where(x => { var extension = Path.GetExtension(x.File.FullName); var fileCultureName = Path.GetFileNameWithoutExtension(x.File.FullName).Replace("_", "-") - .Replace(".user", ""); + .Replace(".user", string.Empty); return extension.InvariantEquals(".xml") && ( fileCultureName.InvariantEquals(culture.Name) - || fileCultureName.InvariantEquals(culture.TwoLetterISOLanguageName) - ); + || fileCultureName.InvariantEquals(culture.TwoLetterISOLanguageName)); }); foreach (LocalizedTextServiceSupplementaryFileSource supplementaryFile in found) @@ -281,11 +269,10 @@ private void MergeSupplementaryFiles(CultureInfo culture, XDocument xMasterDoc) { var areaAlias = (string)xArea.Attribute("alias")!; - XElement areaFound = xMasterDoc.Root.Elements("area") - .FirstOrDefault(x => (string)x.Attribute("alias")! == areaAlias); + XElement? areaFound = xMasterDoc.Root.Elements("area").FirstOrDefault(x => (string)x.Attribute("alias")! == areaAlias); if (areaFound == null) { - //add the whole thing + // add the whole thing xMasterDoc.Root.Add(xArea); } else @@ -310,21 +297,21 @@ private void MergeChildKeys(XElement source, XElement destination, bool overwrit throw new ArgumentNullException("source"); } - //merge in the child elements + // merge in the child elements foreach (XElement key in source.Elements("key") .Where(x => ((string)x.Attribute("alias")!).IsNullOrWhiteSpace() == false)) { var keyAlias = (string)key.Attribute("alias")!; - XElement keyFound = destination.Elements("key") + XElement? keyFound = destination.Elements("key") .FirstOrDefault(x => (string)x.Attribute("alias")! == keyAlias); if (keyFound == null) { - //append, it doesn't exist + // append, it doesn't exist destination.Add(key); } else if (overwrite) { - //overwrite + // overwrite keyFound.Value = key.Value; } } diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceSupplementaryFileSource.cs b/src/Umbraco.Core/Services/LocalizedTextServiceSupplementaryFileSource.cs index dff1cffbf5eb..cff9a55234b9 100644 --- a/src/Umbraco.Core/Services/LocalizedTextServiceSupplementaryFileSource.cs +++ b/src/Umbraco.Core/Services/LocalizedTextServiceSupplementaryFileSource.cs @@ -1,18 +1,14 @@ -namespace Umbraco.Cms.Core.Services; +namespace Umbraco.Cms.Core.Services; public class LocalizedTextServiceSupplementaryFileSource { public LocalizedTextServiceSupplementaryFileSource(FileInfo file, bool overwriteCoreKeys) { - if (file == null) - { - throw new ArgumentNullException("file"); - } - - File = file; + File = file ?? throw new ArgumentNullException("file"); OverwriteCoreKeys = overwriteCoreKeys; } public FileInfo File { get; } + public bool OverwriteCoreKeys { get; } } diff --git a/src/Umbraco.Core/Services/MacroService.cs b/src/Umbraco.Core/Services/MacroService.cs index 0b1c96d4d599..73889895e257 100644 --- a/src/Umbraco.Core/Services/MacroService.cs +++ b/src/Umbraco.Core/Services/MacroService.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; @@ -15,8 +15,12 @@ internal class MacroService : RepositoryService, IMacroWithAliasService private readonly IAuditRepository _auditRepository; private readonly IMacroRepository _macroRepository; - public MacroService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, IMacroRepository macroRepository, IAuditRepository auditRepository) + public MacroService( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IMacroRepository macroRepository, + IAuditRepository auditRepository) : base(provider, loggerFactory, eventMessagesFactory) { _macroRepository = macroRepository; @@ -69,7 +73,7 @@ public IEnumerable GetAll(params string[] aliases) using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - return macroWithAliasRepository.GetAllByAlias(aliases) ?? Enumerable.Empty(); + return macroWithAliasRepository.GetAllByAlias(aliases); } } @@ -153,21 +157,20 @@ public void Save(IMacro macro, int userId = Constants.Security.SuperUserId) ///// Gets a list all available plugins ///// ///// An enumerable list of objects - //public IEnumerable GetMacroPropertyTypes() - //{ + // public IEnumerable GetMacroPropertyTypes() + // { // return MacroPropertyTypeResolver.Current.MacroPropertyTypes; - //} + // } ///// ///// Gets an by its alias ///// ///// Alias to retrieve an for ///// An object - //public IMacroPropertyType GetMacroPropertyTypeByAlias(string alias) - //{ + // public IMacroPropertyType GetMacroPropertyTypeByAlias(string alias) + // { // return MacroPropertyTypeResolver.Current.MacroPropertyTypes.FirstOrDefault(x => x.Alias == alias); - //} - + // } private void Audit(AuditType type, int userId, int objectId) => _auditRepository.Save(new AuditItem(objectId, type, userId, "Macro")); } diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index b3d34de4727d..7d438fdf87c8 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -30,10 +30,16 @@ public class MediaService : RepositoryService, IMediaService #region Constructors - public MediaService(ICoreScopeProvider provider, MediaFileManager mediaFileManager, ILoggerFactory loggerFactory, + public MediaService( + ICoreScopeProvider provider, + MediaFileManager mediaFileManager, + ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - IMediaRepository mediaRepository, IAuditRepository auditRepository, IMediaTypeRepository mediaTypeRepository, - IEntityRepository entityRepository, IShortStringHelper shortStringHelper) + IMediaRepository mediaRepository, + IAuditRepository auditRepository, + IMediaTypeRepository mediaTypeRepository, + IEntityRepository entityRepository, + IShortStringHelper shortStringHelper) : base(provider, loggerFactory, eventMessagesFactory) { _mediaFileManager = mediaFileManager; @@ -46,13 +52,6 @@ public MediaService(ICoreScopeProvider provider, MediaFileManager mediaFileManag #endregion - #region Private Methods - - private void Audit(AuditType type, int userId, int objectId, string? message = null) => - _auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.Media.GetName(), message)); - - #endregion - #region Count public int Count(string? mediaTypeAlias = null) @@ -64,6 +63,13 @@ public int Count(string? mediaTypeAlias = null) } } + #endregion + + #region Private Methods + + private void Audit(AuditType type, int userId, int objectId, string? message = null) => + _auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.Media.GetName(), message)); + public int CountNotTrashed(string? mediaTypeAlias = null) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) @@ -73,7 +79,7 @@ public int CountNotTrashed(string? mediaTypeAlias = null) var mediaTypeId = 0; if (string.IsNullOrWhiteSpace(mediaTypeAlias) == false) { - IMediaType mediaType = _mediaTypeRepository.Get(mediaTypeAlias); + IMediaType? mediaType = _mediaTypeRepository.Get(mediaTypeAlias); if (mediaType == null) { return 0; @@ -130,10 +136,9 @@ public int CountDescendants(int parentId, string? mediaTypeAlias = null) /// /// /// - public IMedia CreateMedia(string name, Guid parentId, string mediaTypeAlias, - int userId = Constants.Security.SuperUserId) + public IMedia CreateMedia(string name, Guid parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) { - IMedia parent = GetById(parentId); + IMedia? parent = GetById(parentId); return CreateMedia(name, parent, mediaTypeAlias, userId); } @@ -150,8 +155,7 @@ public IMedia CreateMedia(string name, Guid parentId, string mediaTypeAlias, /// The alias of the media type. /// The optional id of the user creating the media. /// The media object. - public IMedia CreateMedia(string? name, int parentId, string mediaTypeAlias, - int userId = Constants.Security.SuperUserId) + public IMedia CreateMedia(string? name, int parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) { IMediaType mediaType = GetMediaType(mediaTypeAlias); if (mediaType == null) @@ -159,7 +163,7 @@ public IMedia CreateMedia(string? name, int parentId, string mediaTypeAlias, throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); } - IMedia parent = parentId > 0 ? GetById(parentId) : null; + IMedia? parent = parentId > 0 ? GetById(parentId) : null; if (parentId > 0 && parent == null) { throw new ArgumentException("No media with that id.", nameof(parentId)); @@ -168,7 +172,6 @@ public IMedia CreateMedia(string? name, int parentId, string mediaTypeAlias, if (name != null && name.Length > 255) { throw new InvalidOperationException("Name cannot be more than 255 characters in length."); - throw new InvalidOperationException("Name cannot be more than 255 characters in length."); } var media = new Models.Media(name, parentId, mediaType); @@ -196,7 +199,6 @@ public IMedia CreateMedia(string? name, int parentId, string mediaTypeAlias, public IMedia CreateMedia(string name, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) { // not locking since not saving anything - IMediaType mediaType = GetMediaType(mediaTypeAlias); if (mediaType == null) { @@ -206,7 +208,6 @@ public IMedia CreateMedia(string name, string mediaTypeAlias, int userId = Const if (name != null && name.Length > 255) { throw new InvalidOperationException("Name cannot be more than 255 characters in length."); - throw new InvalidOperationException("Name cannot be more than 255 characters in length."); } var media = new Models.Media(name, -1, mediaType); @@ -232,8 +233,7 @@ public IMedia CreateMedia(string name, string mediaTypeAlias, int userId = Const /// The alias of the media type. /// The optional id of the user creating the media. /// The media object. - public IMedia CreateMedia(string name, IMedia? parent, string mediaTypeAlias, - int userId = Constants.Security.SuperUserId) + public IMedia CreateMedia(string name, IMedia? parent, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) { if (parent == null) { @@ -243,18 +243,17 @@ public IMedia CreateMedia(string name, IMedia? parent, string mediaTypeAlias, using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { // not locking since not saving anything - IMediaType mediaType = GetMediaType(mediaTypeAlias); if (mediaType == null) { - throw new ArgumentException("No media type with that alias.", + throw new ArgumentException( + "No media type with that alias.", nameof(mediaTypeAlias)); // causes rollback } if (name != null && name.Length > 255) { throw new InvalidOperationException("Name cannot be more than 255 characters in length."); - throw new InvalidOperationException("Name cannot be more than 255 characters in length."); } var media = new Models.Media(name, parent, mediaType); @@ -274,8 +273,7 @@ public IMedia CreateMedia(string name, IMedia? parent, string mediaTypeAlias, /// The alias of the media type. /// The optional id of the user creating the media. /// The media object. - public IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, - int userId = Constants.Security.SuperUserId) + public IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) { using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { @@ -285,11 +283,12 @@ public IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTyp IMediaType mediaType = GetMediaType(mediaTypeAlias); // + locks if (mediaType == null) { - throw new ArgumentException("No media type with that alias.", + throw new ArgumentException( + "No media type with that alias.", nameof(mediaTypeAlias)); // causes rollback } - IMedia parent = parentId > 0 ? GetById(parentId) : null; // + locks + IMedia? parent = parentId > 0 ? GetById(parentId) : null; // + locks if (parentId > 0 && parent == null) { throw new ArgumentException("No media with that id.", nameof(parentId)); // causes rollback @@ -314,8 +313,7 @@ public IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTyp /// The alias of the media type. /// The optional id of the user creating the media. /// The media object. - public IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, - int userId = Constants.Security.SuperUserId) + public IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) { if (parent == null) { @@ -330,7 +328,8 @@ public IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTy IMediaType mediaType = GetMediaType(mediaTypeAlias); // + locks if (mediaType == null) { - throw new ArgumentException("No media type with that alias.", + throw new ArgumentException( + "No media type with that alias.", nameof(mediaTypeAlias)); // causes rollback } @@ -342,6 +341,26 @@ public IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTy } } + #endregion + + #region Get, Has, Is + + /// + /// Gets an object by Id + /// + /// Id of the Media to retrieve + /// + /// + /// + public IMedia? GetById(int id) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.MediaTree); + return _mediaRepository.Get(id); + } + } + private void CreateMedia(ICoreScope scope, Models.Media media, IMedia? parent, int userId, bool withIdentity) { EventMessages eventMessages = EventMessagesFactory.Get(); @@ -360,8 +379,7 @@ private void CreateMedia(ICoreScope scope, Models.Media media, IMedia? parent, i scope.Notifications.Publish( new MediaSavedNotification(media, eventMessages).WithStateFrom(savingNotification)); - scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshNode, - eventMessages)); + scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshNode, eventMessages)); } if (withIdentity == false) @@ -372,26 +390,6 @@ private void CreateMedia(ICoreScope scope, Models.Media media, IMedia? parent, i Audit(AuditType.New, media.CreatorId, media.Id, $"Media '{media.Name}' was created with Id {media.Id}"); } - #endregion - - #region Get, Has, Is - - /// - /// Gets an object by Id - /// - /// Id of the Media to retrieve - /// - /// - /// - public IMedia? GetById(int id) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.MediaTree); - return _mediaRepository.Get(id); - } - } - /// /// Gets an object by Id /// @@ -453,8 +451,7 @@ public IEnumerable GetByIds(IEnumerable ids) } /// - public IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords, - IQuery? filter = null, Ordering? ordering = null) + public IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords, IQuery? filter = null, Ordering? ordering = null) { if (pageIndex < 0) { @@ -476,13 +473,22 @@ public IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int scope.ReadLock(Constants.Locks.ContentTree); return _mediaRepository.GetPage( Query()?.Where(x => x.ContentTypeId == contentTypeId), - pageIndex, pageSize, out totalRecords, filter, ordering); + pageIndex, + pageSize, + out totalRecords, + filter, + ordering); } } /// - public IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize, - out long totalRecords, IQuery? filter = null, Ordering? ordering = null) + public IEnumerable GetPagedOfTypes( + int[] contentTypeIds, + long pageIndex, + int pageSize, + out long totalRecords, + IQuery? filter = null, + Ordering? ordering = null) { if (pageIndex < 0) { @@ -504,7 +510,11 @@ public IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, scope.ReadLock(Constants.Locks.ContentTree); return _mediaRepository.GetPage( Query()?.Where(x => contentTypeIds.Contains(x.ContentTypeId)), - pageIndex, pageSize, out totalRecords, filter, ordering); + pageIndex, + pageSize, + out totalRecords, + filter, + ordering); } } @@ -560,7 +570,7 @@ public IEnumerable GetVersions(int id) public IEnumerable GetAncestors(int id) { // intentionally not locking - IMedia media = GetById(id); + IMedia? media = GetById(id); return GetAncestors(media); } @@ -571,7 +581,7 @@ public IEnumerable GetAncestors(int id) /// An Enumerable list of objects public IEnumerable GetAncestors(IMedia? media) { - //null check otherwise we get exceptions + // null check otherwise we get exceptions if (media is null || media.Path.IsNullOrWhiteSpace()) { return Enumerable.Empty(); @@ -595,8 +605,7 @@ public IEnumerable GetAncestors(IMedia? media) } /// - public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, - IQuery? filter = null, Ordering? ordering = null) + public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, IQuery? filter = null, Ordering? ordering = null) { if (pageIndex < 0) { @@ -617,14 +626,13 @@ public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize { scope.ReadLock(Constants.Locks.MediaTree); - IQuery query = Query()?.Where(x => x.ParentId == id); + IQuery? query = Query()?.Where(x => x.ParentId == id); return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering); } } /// - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, - IQuery? filter = null, Ordering? ordering = null) + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, IQuery? filter = null, Ordering? ordering = null) { if (ordering == null) { @@ -635,7 +643,7 @@ public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageS { scope.ReadLock(Constants.Locks.MediaTree); - //if the id is System Root, then just get all + // if the id is System Root, then just get all if (id != Constants.System.Root) { TreeEntityPath[] mediaPath = _entityRepository.GetAllPaths(Constants.ObjectTypes.Media, id).ToArray(); @@ -645,15 +653,25 @@ public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageS return Enumerable.Empty(); } - return GetPagedLocked(GetPagedDescendantQuery(mediaPath[0].Path), pageIndex, pageSize, - out totalChildren, filter, ordering); + return GetPagedLocked(GetPagedDescendantQuery(mediaPath[0].Path), pageIndex, pageSize, out totalChildren, filter, ordering); } - return GetPagedLocked(GetPagedDescendantQuery(null), pageIndex, pageSize, out totalChildren, filter, - ordering); + return GetPagedLocked(GetPagedDescendantQuery(null), pageIndex, pageSize, out totalChildren, filter, ordering); } } + /// + /// Gets the parent of the current media as an item. + /// + /// Id of the to retrieve the parent from + /// Parent object + public IMedia? GetParent(int id) + { + // intentionally not locking + IMedia? media = GetById(id); + return GetParent(media); + } + private IQuery? GetPagedDescendantQuery(string? mediaPath) { IQuery query = Query(); @@ -665,9 +683,7 @@ public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageS return query; } - private IEnumerable GetPagedLocked(IQuery? query, long pageIndex, int pageSize, - out long totalChildren, - IQuery? filter, Ordering ordering) + private IEnumerable GetPagedLocked(IQuery? query, long pageIndex, int pageSize, out long totalChildren, IQuery? filter, Ordering ordering) { if (pageIndex < 0) { @@ -687,18 +703,6 @@ private IEnumerable GetPagedLocked(IQuery? query, long pageIndex return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering); } - /// - /// Gets the parent of the current media as an item. - /// - /// Id of the to retrieve the parent from - /// Parent object - public IMedia? GetParent(int id) - { - // intentionally not locking - IMedia media = GetById(id); - return GetParent(media); - } - /// /// Gets the parent of the current media as an item. /// @@ -731,8 +735,7 @@ private IEnumerable GetPagedLocked(IQuery? query, long pageIndex } /// - public IEnumerable GetPagedMediaInRecycleBin(long pageIndex, int pageSize, out long totalRecords, - IQuery? filter = null, Ordering? ordering = null) + public IEnumerable GetPagedMediaInRecycleBin(long pageIndex, int pageSize, out long totalRecords, IQuery? filter = null, Ordering? ordering = null) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -742,8 +745,7 @@ public IEnumerable GetPagedMediaInRecycleBin(long pageIndex, int pageSiz } scope.ReadLock(Constants.Locks.MediaTree); - IQuery query = - Query()?.Where(x => x.Path.StartsWith(Constants.System.RecycleBinMediaPathPrefix)); + IQuery? query = Query().Where(x => x.Path.StartsWith(Constants.System.RecycleBinMediaPathPrefix)); return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalRecords, filter, ordering); } } @@ -821,9 +823,9 @@ public bool HasChildren(int id) _mediaRepository.Save(media); scope.Notifications.Publish( new MediaSavedNotification(media, eventMessages).WithStateFrom(savingNotification)); + // TODO: See note about suppressing events in content service - scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshNode, - eventMessages)); + scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshNode, eventMessages)); Audit(AuditType.Save, userId, media.Id); scope.Complete(); @@ -867,6 +869,7 @@ public bool HasChildren(int id) scope.Notifications.Publish( new MediaSavedNotification(mediasA, messages).WithStateFrom(savingNotification)); + // TODO: See note about suppressing events in content service scope.Notifications.Publish(new MediaTreeChangeNotification(treeChanges, messages)); Audit(AuditType.Save, userId == -1 ? 0 : userId, Constants.System.Root, "Bulk save media"); @@ -911,34 +914,7 @@ public bool HasChildren(int id) return OperationResult.Attempt.Succeed(messages); } - private void DeleteLocked(ICoreScope scope, IMedia media, EventMessages evtMsgs) - { - void DoDelete(IMedia c) - { - _mediaRepository.Delete(c); - scope.Notifications.Publish(new MediaDeletedNotification(c, evtMsgs)); - - // media files deleted by QueuingEventDispatcher - } - - const int pageSize = 500; - var page = 0; - var total = long.MaxValue; - while (page * pageSize < total) - { - //get descendants - ordered from deepest to shallowest - IEnumerable descendants = GetPagedDescendants(media.Id, page, pageSize, out total, - ordering: Ordering.By("Path", Direction.Descending)); - foreach (IMedia c in descendants) - { - DoDelete(c); - } - } - - DoDelete(media); - } - - //TODO: both DeleteVersions methods below have an issue. Sort of. They do NOT take care of files the way + // TODO: both DeleteVersions methods below have an issue. Sort of. They do NOT take care of files the way // Delete does - for a good reason: the file may be referenced by other, non-deleted, versions. BUT, // if that's not the case, then the file will never be deleted, because when we delete the media, // the version referencing the file will not be there anymore. SO, we can leak files. @@ -959,31 +935,6 @@ public void DeleteVersions(int id, DateTime versionDate, int userId = Constants. } } - private void DeleteVersions(ICoreScope scope, bool wlock, int id, DateTime versionDate, - int userId = Constants.Security.SuperUserId) - { - EventMessages evtMsgs = EventMessagesFactory.Get(); - - var deletingVersionsNotification = - new MediaDeletingVersionsNotification(id, evtMsgs, dateToRetain: versionDate); - if (scope.Notifications.PublishCancelable(deletingVersionsNotification)) - { - return; - } - - if (wlock) - { - scope.WriteLock(Constants.Locks.MediaTree); - } - - _mediaRepository.DeleteVersions(id, versionDate); - - scope.Notifications.Publish( - new MediaDeletedVersionsNotification(id, evtMsgs, dateToRetain: versionDate).WithStateFrom( - deletingVersionsNotification)); - Audit(AuditType.Delete, userId, Constants.System.Root, "Delete Media by version date"); - } - /// /// Permanently deletes specific version(s) from an object. /// This method will never delete the latest version of a media item. @@ -992,8 +943,7 @@ private void DeleteVersions(ICoreScope scope, bool wlock, int id, DateTime versi /// Id of the version to delete /// Boolean indicating whether to delete versions prior to the versionId /// Optional Id of the User deleting versions of a Media object - public void DeleteVersion(int id, int versionId, bool deletePriorVersions, - int userId = Constants.Security.SuperUserId) + public void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = Constants.Security.SuperUserId) { EventMessages evtMsgs = EventMessagesFactory.Get(); @@ -1008,7 +958,7 @@ public void DeleteVersion(int id, int versionId, bool deletePriorVersions, if (deletePriorVersions) { - IMedia media = GetVersion(versionId); + IMedia? media = GetVersion(versionId); if (media is not null) { DeleteVersions(scope, true, id, media.UpdateDate, userId); @@ -1030,6 +980,56 @@ public void DeleteVersion(int id, int versionId, bool deletePriorVersions, } } + private void DeleteLocked(ICoreScope scope, IMedia media, EventMessages evtMsgs) + { + void DoDelete(IMedia c) + { + _mediaRepository.Delete(c); + scope.Notifications.Publish(new MediaDeletedNotification(c, evtMsgs)); + + // media files deleted by QueuingEventDispatcher + } + + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while (page * pageSize < total) + { + // get descendants - ordered from deepest to shallowest + IEnumerable descendants = GetPagedDescendants(media.Id, page, pageSize, out total, ordering: Ordering.By("Path", Direction.Descending)); + foreach (IMedia c in descendants) + { + DoDelete(c); + } + } + + DoDelete(media); + } + + private void DeleteVersions(ICoreScope scope, bool wlock, int id, DateTime versionDate, int userId = Constants.Security.SuperUserId) + { + EventMessages evtMsgs = EventMessagesFactory.Get(); + + var deletingVersionsNotification = + new MediaDeletingVersionsNotification(id, evtMsgs, dateToRetain: versionDate); + if (scope.Notifications.PublishCancelable(deletingVersionsNotification)) + { + return; + } + + if (wlock) + { + scope.WriteLock(Constants.Locks.MediaTree); + } + + _mediaRepository.DeleteVersions(id, versionDate); + + scope.Notifications.Publish( + new MediaDeletedVersionsNotification(id, evtMsgs, dateToRetain: versionDate).WithStateFrom( + deletingVersionsNotification)); + Audit(AuditType.Delete, userId, Constants.System.Root, "Delete Media by version date"); + } + #endregion #region Move, RecycleBin @@ -1050,7 +1050,6 @@ public void DeleteVersion(int id, int versionId, bool deletePriorVersions, // TODO: missing 7.6 "ensure valid path" thing here? // but then should be in PerformMoveLocked on every moved item? - var originalPath = media.Path; var moveEventInfo = new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia); @@ -1064,8 +1063,7 @@ public void DeleteVersion(int id, int versionId, bool deletePriorVersions, PerformMoveLocked(media, Constants.System.RecycleBinMedia, null, userId, moves, true); - scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshBranch, - messages)); + scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshBranch, messages)); MoveEventInfo[] moveInfo = moves.Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)).ToArray(); scope.Notifications.Publish( @@ -1122,10 +1120,9 @@ public void DeleteVersion(int id, int versionId, bool deletePriorVersions, var trashed = media.Trashed ? false : (bool?)null; PerformMoveLocked(media, parentId, parent, userId, moves, trashed); - scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshBranch, - messages)); + scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshBranch, messages)); - MoveEventInfo[] moveInfo = moves //changes + MoveEventInfo[] moveInfo = moves // changes .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) .ToArray(); scope.Notifications.Publish( @@ -1137,10 +1134,50 @@ public void DeleteVersion(int id, int versionId, bool deletePriorVersions, return OperationResult.Attempt.Succeed(messages); } + /// + /// Empties the Recycle Bin by deleting all that resides in the bin + /// + /// Optional Id of the User emptying the Recycle Bin + public OperationResult EmptyRecycleBin(int userId = Constants.Security.SuperUserId) + { + var deleted = new List(); + EventMessages messages = EventMessagesFactory.Get(); // TODO: and then? + + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + scope.WriteLock(Constants.Locks.MediaTree); + + // emptying the recycle bin means deleting whatever is in there - do it properly! + IQuery? query = Query().Where(x => x.ParentId == Constants.System.RecycleBinMedia); + IMedia[] medias = _mediaRepository.Get(query)?.ToArray() ?? Array.Empty(); + + var emptyingRecycleBinNotification = new MediaEmptyingRecycleBinNotification(medias, messages); + if (scope.Notifications.PublishCancelable(emptyingRecycleBinNotification)) + { + scope.Complete(); + return OperationResult.Cancel(messages); + } + + foreach (IMedia media in medias) + { + DeleteLocked(scope, media, messages); + deleted.Add(media); + } + + scope.Notifications.Publish( + new MediaEmptiedRecycleBinNotification(deleted, new EventMessages()).WithStateFrom( + emptyingRecycleBinNotification)); + scope.Notifications.Publish(new MediaTreeChangeNotification(deleted, TreeChangeTypes.Remove, messages)); + Audit(AuditType.Delete, userId, Constants.System.RecycleBinMedia, "Empty Media recycle bin"); + scope.Complete(); + } + + return OperationResult.Succeed(messages); + } + // MUST be called from within WriteLock // trash indicates whether we are trashing, un-trashing, or not changing anything - private void PerformMoveLocked(IMedia media, int parentId, IMedia? parent, int userId, - ICollection<(IMedia, string)> moves, bool? trash) + private void PerformMoveLocked(IMedia media, int parentId, IMedia? parent, int userId, ICollection<(IMedia, string)> moves, bool? trash) { media.ParentId = parentId; @@ -1152,25 +1189,25 @@ private void PerformMoveLocked(IMedia media, int parentId, IMedia? parent, int u moves.Add((media, media.Path)); // capture original path - //need to store the original path to lookup descendants based on it below + // need to store the original path to lookup descendants based on it below var originalPath = media.Path; // these will be updated by the repo because we changed parentId - //media.Path = (parent == null ? "-1" : parent.Path) + "," + media.Id; - //media.SortOrder = ((MediaRepository) repository).NextChildSortOrder(parentId); - //media.Level += levelDelta; + // media.Path = (parent == null ? "-1" : parent.Path) + "," + media.Id; + // media.SortOrder = ((MediaRepository) repository).NextChildSortOrder(parentId); + // media.Level += levelDelta; PerformMoveMediaLocked(media, userId, trash); // if uow is not immediate, content.Path will be updated only when the UOW commits, // and because we want it now, we have to calculate it by ourselves - //paths[media.Id] = media.Path; + // paths[media.Id] = media.Path; paths[media.Id] = (parent == null ? parentId == Constants.System.RecycleBinMedia ? "-1,-21" : Constants.System.RootString : parent.Path) + "," + media.Id; const int pageSize = 500; - IQuery query = GetPagedDescendantQuery(originalPath); + IQuery? query = GetPagedDescendantQuery(originalPath); long total; do { @@ -1186,7 +1223,8 @@ private void PerformMoveLocked(IMedia media, int parentId, IMedia? parent, int u descendant.Level += levelDelta; PerformMoveMediaLocked(descendant, userId, trash); } - } while (total > pageSize); + } + while (total > pageSize); } private void PerformMoveMediaLocked(IMedia media, int userId, bool? trash) @@ -1199,47 +1237,6 @@ private void PerformMoveMediaLocked(IMedia media, int userId, bool? trash) _mediaRepository.Save(media); } - /// - /// Empties the Recycle Bin by deleting all that resides in the bin - /// - /// Optional Id of the User emptying the Recycle Bin - public OperationResult EmptyRecycleBin(int userId = Constants.Security.SuperUserId) - { - var deleted = new List(); - EventMessages messages = EventMessagesFactory.Get(); // TODO: and then? - - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { - scope.WriteLock(Constants.Locks.MediaTree); - - // emptying the recycle bin means deleting whatever is in there - do it properly! - IQuery? query = Query().Where(x => x.ParentId == Constants.System.RecycleBinMedia); - IMedia[] medias = _mediaRepository.Get(query)?.ToArray() ?? Array.Empty(); - - var emptyingRecycleBinNotification = new MediaEmptyingRecycleBinNotification(medias, messages); - if (scope.Notifications.PublishCancelable(emptyingRecycleBinNotification)) - { - scope.Complete(); - return OperationResult.Cancel(messages); - } - - foreach (IMedia media in medias) - { - DeleteLocked(scope, media, messages); - deleted.Add(media); - } - - scope.Notifications.Publish( - new MediaEmptiedRecycleBinNotification(deleted, new EventMessages()).WithStateFrom( - emptyingRecycleBinNotification)); - scope.Notifications.Publish(new MediaTreeChangeNotification(deleted, TreeChangeTypes.Remove, messages)); - Audit(AuditType.Delete, userId, Constants.System.RecycleBinMedia, "Empty Media recycle bin"); - scope.Complete(); - } - - return OperationResult.Succeed(messages); - } - public bool RecycleBinSmells() { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) @@ -1296,12 +1293,14 @@ public bool Sort(IEnumerable items, int userId = Constants.Security.Supe // else update media.SortOrder = sortOrder++; + // save saved.Add(media); _mediaRepository.Save(media); } scope.Notifications.Publish(new MediaSavedNotification(itemsA, messages).WithStateFrom(savingNotification)); + // TODO: See note about suppressing events in content service scope.Notifications.Publish(new MediaTreeChangeNotification(saved, TreeChangeTypes.RefreshNode, messages)); Audit(AuditType.Sort, userId, 0); @@ -1322,13 +1321,13 @@ public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportO if (report.FixedIssues.Count > 0) { - //The event args needs a content item so we'll make a fake one with enough properties to not cause a null ref + // The event args needs a content item so we'll make a fake one with enough properties to not cause a null ref var root = new Models.Media("root", -1, new MediaType(_shortStringHelper, -1)) { - Id = -1, Key = Guid.Empty + Id = -1, + Key = Guid.Empty, }; - scope.Notifications.Publish(new MediaTreeChangeNotification(root, TreeChangeTypes.RefreshAll, - EventMessagesFactory.Get())); + scope.Notifications.Publish(new MediaTreeChangeNotification(root, TreeChangeTypes.RefreshAll, EventMessagesFactory.Get())); } return report; @@ -1387,7 +1386,6 @@ public void DeleteMediaOfTypes(IEnumerable mediaTypeIds, int userId = Const // of a different type, move them to the recycle bin, then permanently delete the content items. // The main problem with this is that for every content item being deleted, events are raised... // which we need for many things like keeping caches in sync, but we can surely do this MUCH better. - var changes = new List>(); var moves = new List<(IMedia, string)>(); var mediaTypeIdsA = mediaTypeIds.ToArray(); @@ -1440,8 +1438,7 @@ public void DeleteMediaOfTypes(IEnumerable mediaTypeIds, int userId = Const scope.Notifications.Publish(new MediaTreeChangeNotification(changes, messages)); - Audit(AuditType.Delete, userId, Constants.System.Root, - $"Delete Media of types {string.Join(",", mediaTypeIdsA)}"); + Audit(AuditType.Delete, userId, Constants.System.Root, $"Delete Media of types {string.Join(",", mediaTypeIdsA)}"); scope.Complete(); } @@ -1454,7 +1451,7 @@ public void DeleteMediaOfTypes(IEnumerable mediaTypeIds, int userId = Const /// Id of the /// Optional id of the user deleting the media public void DeleteMediaOfType(int mediaTypeId, int userId = Constants.Security.SuperUserId) => - DeleteMediaOfTypes(new[] {mediaTypeId}, userId); + DeleteMediaOfTypes(new[] { mediaTypeId }, userId); private IMediaType GetMediaType(string mediaTypeAlias) { @@ -1465,7 +1462,8 @@ private IMediaType GetMediaType(string mediaTypeAlias) if (string.IsNullOrWhiteSpace(mediaTypeAlias)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(mediaTypeAlias)); } @@ -1474,7 +1472,7 @@ private IMediaType GetMediaType(string mediaTypeAlias) scope.ReadLock(Constants.Locks.MediaTypes); IQuery query = Query().Where(x => x.Alias == mediaTypeAlias); - IMediaType mediaType = _mediaTypeRepository.Get(query)?.FirstOrDefault(); + IMediaType? mediaType = _mediaTypeRepository.Get(query).FirstOrDefault(); if (mediaType == null) { diff --git a/src/Umbraco.Core/Strings/CleanStringType.cs b/src/Umbraco.Core/Strings/CleanStringType.cs index 83dd2e383de5..75ad000505c5 100644 --- a/src/Umbraco.Core/Strings/CleanStringType.cs +++ b/src/Umbraco.Core/Strings/CleanStringType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Strings; +namespace Umbraco.Cms.Core.Strings; /// /// Specifies the type of a clean string. @@ -19,7 +19,6 @@ public enum CleanStringType /// None = 0x00, - // casing values /// @@ -62,7 +61,6 @@ public enum CleanStringType /// UmbracoCase = 0x20, - // encoding values /// @@ -71,7 +69,7 @@ public enum CleanStringType CodeMask = Utf8 | Ascii | TryAscii, // Unicode encoding is obsolete, use Utf8 - //Unicode = 0x0100, + // Unicode = 0x0100, /// /// Utf8 encoding. @@ -119,5 +117,5 @@ public enum CleanStringType /// UnderscoreAlias role. /// /// This is Alias + leading underscore. - UnderscoreAlias = 0x100000 + UnderscoreAlias = 0x100000, } diff --git a/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs b/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs index d78b0497b6dd..36c0d6e85ef3 100644 --- a/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs +++ b/src/Umbraco.Core/Strings/DefaultUrlSegmentProvider.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Strings; diff --git a/src/Umbraco.Core/Strings/Diff.cs b/src/Umbraco.Core/Strings/Diff.cs index eed1437f5c33..e8a3fdf84c2f 100644 --- a/src/Umbraco.Core/Strings/Diff.cs +++ b/src/Umbraco.Core/Strings/Diff.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using System.Text.RegularExpressions; namespace Umbraco.Cms.Core.Strings; @@ -35,7 +35,6 @@ public static Item[] DiffText(string textA, string textB) => public static Item[] DiffText1(string textA, string textB) => DiffInt(DiffCharCodes(textA, false), DiffCharCodes(textB, false)); - /// /// Find the difference in 2 text documents, comparing by text lines. /// The algorithm itself is comparing 2 arrays of numbers so when comparing 2 text documents @@ -72,8 +71,10 @@ public static Item[] DiffText(string textA, string textB, bool trimSpace, bool i h = null; // free up Hashtable memory (maybe) var max = dataA.Length + dataB.Length + 1; + // vector for the (0,0) to (x,y) search var downVector = new int[(2 * max) + 2]; + // vector for the (u,v) to (N,M) search var upVector = new int[(2 * max) + 2]; @@ -84,6 +85,31 @@ public static Item[] DiffText(string textA, string textB, bool trimSpace, bool i return CreateDiffs(dataA, dataB); } // DiffText + /// + /// Find the difference in 2 arrays of integers. + /// + /// A-version of the numbers (usually the old one) + /// B-version of the numbers (usually the new one) + /// Returns a array of Items that describe the differences. + public static Item[] DiffInt(int[] arrayA, int[] arrayB) + { + // The A-Version of the data (original data) to be compared. + var dataA = new DiffData(arrayA); + + // The B-Version of the data (modified data) to be compared. + var dataB = new DiffData(arrayB); + + var max = dataA.Length + dataB.Length + 1; + + // vector for the (0,0) to (x,y) search + var downVector = new int[(2 * max) + 2]; + + // vector for the (u,v) to (N,M) search + var upVector = new int[(2 * max) + 2]; + + Lcs(dataA, 0, dataA.Length, dataB, 0, dataB.Length, downVector, upVector); + return CreateDiffs(dataA, dataB); + } // Diff /// /// Diffs the char codes. @@ -143,32 +169,6 @@ private static void Optimize(DiffData data) } // while } // Optimize - - /// - /// Find the difference in 2 arrays of integers. - /// - /// A-version of the numbers (usually the old one) - /// B-version of the numbers (usually the new one) - /// Returns a array of Items that describe the differences. - public static Item[] DiffInt(int[] arrayA, int[] arrayB) - { - // The A-Version of the data (original data) to be compared. - var dataA = new DiffData(arrayA); - - // The B-Version of the data (modified data) to be compared. - var dataB = new DiffData(arrayB); - - var max = dataA.Length + dataB.Length + 1; - // vector for the (0,0) to (x,y) search - var downVector = new int[(2 * max) + 2]; - // vector for the (u,v) to (N,M) search - var upVector = new int[(2 * max) + 2]; - - Lcs(dataA, 0, dataA.Length, dataB, 0, dataB.Length, downVector, upVector); - return CreateDiffs(dataA, dataB); - } // Diff - - /// /// This function converts all text lines of the text into unique numbers for every unique text line /// so further work can work only with simple numbers. @@ -185,7 +185,7 @@ private static int[] DiffCodes(string aText, IDictionary h, bool trimSpace, bool var lastUsedCode = h.Count; // strip off all cr, only use lf as text line separator. - aText = aText.Replace("\r", ""); + aText = aText.Replace("\r", string.Empty); var lines = aText.Split(Constants.CharArrays.LineFeed); var codes = new int[lines.Length]; @@ -224,7 +224,6 @@ private static int[] DiffCodes(string aText, IDictionary h, bool trimSpace, bool return codes; } // DiffCodes - /// /// This is the algorithm to find the Shortest Middle Snake (SMS). /// @@ -237,8 +236,7 @@ private static int[] DiffCodes(string aText, IDictionary h, bool trimSpace, bool /// a vector for the (0,0) to (x,y) search. Passed as a parameter for speed reasons. /// a vector for the (u,v) to (N,M) search. Passed as a parameter for speed reasons. /// a MiddleSnakeData record containing x,y and u,v - private static Smsrd Sms(DiffData dataA, int lowerA, int upperA, DiffData dataB, int lowerB, int upperB, - int[] downVector, int[] upVector) + private static Smsrd Sms(DiffData dataA, int lowerA, int upperA, DiffData dataB, int lowerB, int upperB, int[] downVector, int[] upVector) { var max = dataA.Length + dataB.Length + 1; @@ -302,6 +300,7 @@ private static Smsrd Sms(DiffData dataA, int lowerA, int upperA, DiffData dataB, { ret.X = downVector[downOffset + k]; ret.Y = downVector[downOffset + k] - k; + // ret.u = UpVector[UpOffset + k]; // 2002.09.20: no need for 2 points // ret.v = UpVector[UpOffset + k] - k; return ret; @@ -346,6 +345,7 @@ private static Smsrd Sms(DiffData dataA, int lowerA, int upperA, DiffData dataB, { ret.X = downVector[downOffset + k]; ret.Y = downVector[downOffset + k] - k; + // ret.u = UpVector[UpOffset + k]; // 2002.09.20: no need for 2 points // ret.v = UpVector[UpOffset + k] - k; return ret; @@ -357,7 +357,6 @@ private static Smsrd Sms(DiffData dataA, int lowerA, int upperA, DiffData dataB, throw new ApplicationException("the algorithm should never come here."); } // SMS - /// /// This is the divide-and-conquer implementation of the longest common-subsequence (LCS) /// algorithm. @@ -372,8 +371,7 @@ private static Smsrd Sms(DiffData dataA, int lowerA, int upperA, DiffData dataB, /// upper bound of the actual range in DataB (exclusive) /// a vector for the (0,0) to (x,y) search. Passed as a parameter for speed reasons. /// a vector for the (u,v) to (N,M) search. Passed as a parameter for speed reasons. - private static void Lcs(DiffData dataA, int lowerA, int upperA, DiffData dataB, int lowerB, int upperB, - int[] downVector, int[] upVector) + private static void Lcs(DiffData dataA, int lowerA, int upperA, DiffData dataB, int lowerB, int upperB, int[] downVector, int[] upVector) { // Debug.Write(2, "LCS", String.Format("Analyze the box: A[{0}-{1}] to B[{2}-{3}]", LowerA, UpperA, LowerB, UpperB)); @@ -411,16 +409,15 @@ private static void Lcs(DiffData dataA, int lowerA, int upperA, DiffData dataB, { // Find the middle snake and length of an optimal path for A and B Smsrd smsrd = Sms(dataA, lowerA, upperA, dataB, lowerB, upperB, downVector, upVector); + // Debug.Write(2, "MiddleSnakeData", String.Format("{0},{1}", smsrd.x, smsrd.y)); // The path is from LowerX to (x,y) and (x,y) to UpperX Lcs(dataA, lowerA, smsrd.X, dataB, lowerB, smsrd.Y, downVector, upVector); - Lcs(dataA, smsrd.X, upperA, dataB, smsrd.Y, upperB, downVector, - upVector); // 2002.09.20: no need for 2 points + Lcs(dataA, smsrd.X, upperA, dataB, smsrd.Y, upperB, downVector, upVector); // 2002.09.20: no need for 2 points } } // LCS() - /// /// Scan the tables of which lines are inserted and deleted, /// producing an edit script in forward order. @@ -450,13 +447,15 @@ private static Item[] CreateDiffs(DiffData dataA, DiffData dataB) var startB = lineB; while (lineA < dataA.Length && (lineB >= dataB.Length || dataA.Modified[lineA])) - // while (LineA < DataA.Length && DataA.modified[LineA]) + + // while (LineA < DataA.Length && DataA.modified[LineA]) { lineA++; } while (lineB < dataB.Length && (lineA >= dataA.Length || dataB.Modified[lineB])) - // while (LineB < DataB.Length && DataB.modified[LineB]) + + // while (LineB < DataB.Length && DataB.modified[LineB]) { lineB++; } @@ -464,11 +463,13 @@ private static Item[] CreateDiffs(DiffData dataA, DiffData dataB) if (startA < lineA || startB < lineB) { // store a new difference-item - aItem = new Item(); - aItem.StartA = startA; - aItem.StartB = startB; - aItem.DeletedA = lineA - startA; - aItem.InsertedB = lineB - startB; + aItem = new Item + { + StartA = startA, + StartB = startB, + DeletedA = lineA - startA, + InsertedB = lineB - startB, + }; a.Add(aItem); } // if } // if @@ -480,6 +481,22 @@ private static Item[] CreateDiffs(DiffData dataA, DiffData dataB) return result; } + /// details of one difference. + public struct Item + { + /// Start Line number in Data A. + public int StartA; + + /// Start Line number in Data B. + public int StartB; + + /// Number of changes in Data A. + public int DeletedA; + + /// Number of changes in Data B. + public int InsertedB; + } // Item + /// /// Data on one input file being compared. /// @@ -510,28 +527,14 @@ internal DiffData(int[] initData) } // DiffData } // class DiffData - /// details of one difference. - public struct Item - { - /// Start Line number in Data A. - public int StartA; - - /// Start Line number in Data B. - public int StartB; - - /// Number of changes in Data A. - public int DeletedA; - - /// Number of changes in Data B. - public int InsertedB; - } // Item - /// /// Shortest Middle Snake Return Data /// private struct Smsrd { - internal int X, Y; + internal int X; + internal int Y; + // internal int u, v; // 2002.09.20: no need for 2 points } } // class Diff diff --git a/src/Umbraco.Core/Strings/IShortStringHelper.cs b/src/Umbraco.Core/Strings/IShortStringHelper.cs index b14cf36ef562..a5c20f1a09fb 100644 --- a/src/Umbraco.Core/Strings/IShortStringHelper.cs +++ b/src/Umbraco.Core/Strings/IShortStringHelper.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Strings; +namespace Umbraco.Cms.Core.Strings; /// /// Provides string functions for short strings such as aliases or URL segments. diff --git a/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs b/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs index 41e5d1f8250c..c7050050e168 100644 --- a/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs +++ b/src/Umbraco.Core/Strings/IUrlSegmentProvider.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Strings; diff --git a/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs b/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs index 842a3ff424f3..1d7d085f90de 100644 --- a/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs +++ b/src/Umbraco.Core/Sync/ISyncBootStateAccessor.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Sync; +namespace Umbraco.Cms.Core.Sync; /// /// Retrieve the for the application during startup diff --git a/src/Umbraco.Core/Templates/HtmlImageSourceParser.cs b/src/Umbraco.Core/Templates/HtmlImageSourceParser.cs index 62c3ceeca913..aa0e9a09bff2 100644 --- a/src/Umbraco.Core/Templates/HtmlImageSourceParser.cs +++ b/src/Umbraco.Core/Templates/HtmlImageSourceParser.cs @@ -38,7 +38,7 @@ public IEnumerable FindUdisFromDataAttributes(string text) foreach (Match match in matches) { - if (match.Groups.Count == 2 && UdiParser.TryParse(match.Groups[1].Value, out Udi udi)) + if (match.Groups.Count == 2 && UdiParser.TryParse(match.Groups[1].Value, out Udi? udi)) { yield return udi; } @@ -67,7 +67,7 @@ public string EnsureImageSources(string text) // - 4 = the data-udi attribute value // - 5 = anything after group 4 until the image tag is closed var udi = match.Groups[4].Value; - if (udi.IsNullOrWhiteSpace() || UdiParser.TryParse(udi, out GuidUdi guidUdi) == false) + if (udi.IsNullOrWhiteSpace() || UdiParser.TryParse(udi, out GuidUdi? guidUdi) == false) { return match.Value; } @@ -90,6 +90,7 @@ public string EnsureImageSources(string text) /// /// public string RemoveImageSources(string text) + // see comment in ResolveMediaFromTextString for group reference => ResolveImgPattern.Replace(text, "$1$3$4$5"); } diff --git a/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs b/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs index c0ccd6686488..103070505127 100644 --- a/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs +++ b/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs @@ -18,7 +18,8 @@ public sealed class HtmlLocalLinkParser private readonly IUmbracoContextAccessor _umbracoContextAccessor; - public HtmlLocalLinkParser(IUmbracoContextAccessor umbracoContextAccessor, + public HtmlLocalLinkParser( + IUmbracoContextAccessor umbracoContextAccessor, IPublishedUrlProvider publishedUrlProvider) { _umbracoContextAccessor = umbracoContextAccessor; @@ -44,7 +45,7 @@ public HtmlLocalLinkParser(IUmbracoContextAccessor umbracoContextAccessor, /// public string EnsureInternalLinks(string text, bool preview) { - if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext umbracoContext)) + if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext)) { throw new InvalidOperationException("Could not parse internal links, there is no current UmbracoContext"); } @@ -54,7 +55,7 @@ public string EnsureInternalLinks(string text, bool preview) return EnsureInternalLinks(text); } - using (umbracoContext!.ForcedPreview(preview)) // force for URL provider + using (umbracoContext.ForcedPreview(preview)) // force for URL provider { return EnsureInternalLinks(text); } @@ -112,10 +113,10 @@ public string EnsureInternalLinks(string text) { if (tag.Groups.Count > 0) { - var id = tag.Groups[1].Value; //.Remove(tag.Groups[1].Value.Length - 1, 1); + var id = tag.Groups[1].Value; // .Remove(tag.Groups[1].Value.Length - 1, 1); - //The id could be an int or a UDI - if (UdiParser.TryParse(id, out Udi udi)) + // The id could be an int or a UDI + if (UdiParser.TryParse(id, out Udi? udi)) { var guidUdi = udi as GuidUdi; if (guidUdi is not null) diff --git a/src/Umbraco.Core/Templates/HtmlUrlParser.cs b/src/Umbraco.Core/Templates/HtmlUrlParser.cs index ddc03d9a6e3b..f4a817485da4 100644 --- a/src/Umbraco.Core/Templates/HtmlUrlParser.cs +++ b/src/Umbraco.Core/Templates/HtmlUrlParser.cs @@ -18,8 +18,7 @@ public sealed class HtmlUrlParser private readonly IProfilingLogger _profilingLogger; private ContentSettings _contentSettings; - public HtmlUrlParser(IOptionsMonitor contentSettings, ILogger logger, - IProfilingLogger profilingLogger, IIOHelper ioHelper) + public HtmlUrlParser(IOptionsMonitor contentSettings, ILogger logger, IProfilingLogger profilingLogger, IIOHelper ioHelper) { _contentSettings = contentSettings.CurrentValue; _logger = logger; @@ -47,16 +46,17 @@ public string EnsureUrls(string text) return text; } - using (DisposableTimer timer = _profilingLogger.DebugDuration(typeof(IOHelper), - "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete")) + using (DisposableTimer? timer = _profilingLogger.DebugDuration( + typeof(IOHelper), + "ResolveUrlsFromTextString starting", + "ResolveUrlsFromTextString complete")) { // find all relative URLs (ie. URLs that contain ~) MatchCollection tags = ResolveUrlPattern.Matches(text); - _logger.LogDebug("After regex: {Duration} matched: {TagsCount}", timer?.Stopwatch.ElapsedMilliseconds, - tags.Count); + _logger.LogDebug("After regex: {Duration} matched: {TagsCount}", timer?.Stopwatch.ElapsedMilliseconds, tags.Count); foreach (Match tag in tags) { - var url = ""; + var url = string.Empty; if (tag.Groups[1].Success) { url = tag.Groups[1].Value; @@ -68,8 +68,8 @@ public string EnsureUrls(string text) // else if (string.IsNullOrEmpty(url) == false) { - var resolvedUrl = url.Substring(0, 1) == "/" - ? _ioHelper.ResolveUrl(url.Substring(1)) + var resolvedUrl = url[..1] == "/" + ? _ioHelper.ResolveUrl(url[1..]) : _ioHelper.ResolveUrl(url); text = text.Replace(url, resolvedUrl); } diff --git a/src/Umbraco.Core/Tour/BackOfficeTourFilter.cs b/src/Umbraco.Core/Tour/BackOfficeTourFilter.cs index 3bb0edb1c3a0..d1d8384502e6 100644 --- a/src/Umbraco.Core/Tour/BackOfficeTourFilter.cs +++ b/src/Umbraco.Core/Tour/BackOfficeTourFilter.cs @@ -1,4 +1,4 @@ -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; namespace Umbraco.Cms.Core.Tour; diff --git a/src/Umbraco.Core/Trees/ActionUrlMethod.cs b/src/Umbraco.Core/Trees/ActionUrlMethod.cs index 7f31c5964735..c2be2cea5439 100644 --- a/src/Umbraco.Core/Trees/ActionUrlMethod.cs +++ b/src/Umbraco.Core/Trees/ActionUrlMethod.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Trees; +namespace Umbraco.Cms.Core.Trees; /// /// Specifies the action to take for a menu item when a URL is specified @@ -6,5 +6,5 @@ public enum ActionUrlMethod { Dialog, - BlankWindow + BlankWindow, } diff --git a/src/Umbraco.Core/Trees/CoreTreeAttribute.cs b/src/Umbraco.Core/Trees/CoreTreeAttribute.cs index 0d787ee80523..b1c29ccb632c 100644 --- a/src/Umbraco.Core/Trees/CoreTreeAttribute.cs +++ b/src/Umbraco.Core/Trees/CoreTreeAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Trees; +namespace Umbraco.Cms.Core.Trees; /// /// Indicates that a tree is a core tree and should not be treated as a plugin tree. diff --git a/src/Umbraco.Core/Trees/ITree.cs b/src/Umbraco.Core/Trees/ITree.cs index ae2511b1541a..efb3cfab972e 100644 --- a/src/Umbraco.Core/Trees/ITree.cs +++ b/src/Umbraco.Core/Trees/ITree.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Trees; +namespace Umbraco.Cms.Core.Trees; // TODO: we don't really use this, it is nice to have the treecontroller, attribute and ApplicationTree streamlined to implement this but it's not used // leave as internal for now, maybe we'll use in the future, means we could pass around ITree diff --git a/src/Umbraco.Core/Web/CookieManagerExtensions.cs b/src/Umbraco.Core/Web/CookieManagerExtensions.cs index 6a39e0cdc653..2e399ac8c1f9 100644 --- a/src/Umbraco.Core/Web/CookieManagerExtensions.cs +++ b/src/Umbraco.Core/Web/CookieManagerExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core; diff --git a/src/Umbraco.Core/Web/ICookieManager.cs b/src/Umbraco.Core/Web/ICookieManager.cs index 93426e708b37..2815675f5d17 100644 --- a/src/Umbraco.Core/Web/ICookieManager.cs +++ b/src/Umbraco.Core/Web/ICookieManager.cs @@ -3,7 +3,10 @@ namespace Umbraco.Cms.Core.Web; public interface ICookieManager { void ExpireCookie(string cookieName); + string? GetCookieValue(string cookieName); + void SetCookieValue(string cookieName, string value); + bool HasCookie(string cookieName); } diff --git a/src/Umbraco.Core/Web/IUmbracoContextAccessor.cs b/src/Umbraco.Core/Web/IUmbracoContextAccessor.cs index 25943d432662..f55cb21ff717 100644 --- a/src/Umbraco.Core/Web/IUmbracoContextAccessor.cs +++ b/src/Umbraco.Core/Web/IUmbracoContextAccessor.cs @@ -5,12 +5,14 @@ namespace Umbraco.Cms.Core.Web; /// /// Provides access to a TryGetUmbracoContext bool method that will return true if the "current" /// is not null. -/// Provides a Clear() method that will clear the current object. -/// Provides a Set() method that til set the current object. +/// Provides a Clear() method that will clear the current object. +/// Provides a Set() method that til set the current object. /// public interface IUmbracoContextAccessor { bool TryGetUmbracoContext([MaybeNullWhen(false)] out IUmbracoContext umbracoContext); + void Clear(); + void Set(IUmbracoContext umbracoContext); } diff --git a/src/Umbraco.Core/Web/IUmbracoContextFactory.cs b/src/Umbraco.Core/Web/IUmbracoContextFactory.cs index 10c473f72930..d8d5475841d1 100644 --- a/src/Umbraco.Core/Web/IUmbracoContextFactory.cs +++ b/src/Umbraco.Core/Web/IUmbracoContextFactory.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Web; +namespace Umbraco.Cms.Core.Web; /// /// Creates and manages instances. @@ -15,11 +15,6 @@ public interface IUmbracoContextFactory /// Otherwise, create a new instance, registers it, and return a root reference /// to it. /// - /// - /// If is null, the factory tries to use - /// if it exists. Otherwise, it uses a dummy - /// . - /// /// /// /// using (var contextReference = contextFactory.EnsureUmbracoContext()) diff --git a/src/Umbraco.Core/WebAssets/AssetFile.cs b/src/Umbraco.Core/WebAssets/AssetFile.cs index a450d550ad5d..a0ad29830279 100644 --- a/src/Umbraco.Core/WebAssets/AssetFile.cs +++ b/src/Umbraco.Core/WebAssets/AssetFile.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; namespace Umbraco.Cms.Core.WebAssets; @@ -13,6 +13,7 @@ public class AssetFile : IAssetFile #region IAssetFile Members public string? FilePath { get; set; } + public AssetType DependencyType { get; } #endregion diff --git a/src/Umbraco.Core/WebAssets/AssetType.cs b/src/Umbraco.Core/WebAssets/AssetType.cs index dc3e55e8b96b..e04caa80a2b1 100644 --- a/src/Umbraco.Core/WebAssets/AssetType.cs +++ b/src/Umbraco.Core/WebAssets/AssetType.cs @@ -1,7 +1,7 @@ -namespace Umbraco.Cms.Core.WebAssets; +namespace Umbraco.Cms.Core.WebAssets; public enum AssetType { Javascript, - Css + Css, } diff --git a/src/Umbraco.Core/WebAssets/BundlingOptions.cs b/src/Umbraco.Core/WebAssets/BundlingOptions.cs index 5ec28d785678..99236494f309 100644 --- a/src/Umbraco.Core/WebAssets/BundlingOptions.cs +++ b/src/Umbraco.Core/WebAssets/BundlingOptions.cs @@ -2,17 +2,20 @@ namespace Umbraco.Cms.Core.WebAssets; public struct BundlingOptions : IEquatable { - public static BundlingOptions OptimizedAndComposite => new(true); - public static BundlingOptions OptimizedNotComposite => new(true, false); - public static BundlingOptions NotOptimizedNotComposite => new(false, false); - public static BundlingOptions NotOptimizedAndComposite => new(false); - public BundlingOptions(bool optimizeOutput = true, bool enabledCompositeFiles = true) { OptimizeOutput = optimizeOutput; EnabledCompositeFiles = enabledCompositeFiles; } + public static BundlingOptions OptimizedAndComposite => new(true); + + public static BundlingOptions OptimizedNotComposite => new(true, false); + + public static BundlingOptions NotOptimizedNotComposite => new(false, false); + + public static BundlingOptions NotOptimizedAndComposite => new(false); + /// /// If true, the files in the bundle will be minified /// @@ -24,6 +27,8 @@ public BundlingOptions(bool optimizeOutput = true, bool enabledCompositeFiles = /// public bool EnabledCompositeFiles { get; } + public static bool operator ==(BundlingOptions left, BundlingOptions right) => left.Equals(right); + public override bool Equals(object? obj) => obj is BundlingOptions options && Equals(options); public bool Equals(BundlingOptions other) => OptimizeOutput == other.OptimizeOutput && @@ -37,7 +42,5 @@ public override int GetHashCode() return hashCode; } - public static bool operator ==(BundlingOptions left, BundlingOptions right) => left.Equals(right); - public static bool operator !=(BundlingOptions left, BundlingOptions right) => !(left == right); } diff --git a/src/Umbraco.Core/WebAssets/CssFile.cs b/src/Umbraco.Core/WebAssets/CssFile.cs index c0c73df77e44..9ba30c83de49 100644 --- a/src/Umbraco.Core/WebAssets/CssFile.cs +++ b/src/Umbraco.Core/WebAssets/CssFile.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.WebAssets; +namespace Umbraco.Cms.Core.WebAssets; /// /// Represents a CSS asset file diff --git a/src/Umbraco.Core/WebAssets/CustomBackOfficeAssetsCollection.cs b/src/Umbraco.Core/WebAssets/CustomBackOfficeAssetsCollection.cs index 204a9ddf3764..523b186c9a26 100644 --- a/src/Umbraco.Core/WebAssets/CustomBackOfficeAssetsCollection.cs +++ b/src/Umbraco.Core/WebAssets/CustomBackOfficeAssetsCollection.cs @@ -4,7 +4,8 @@ namespace Umbraco.Cms.Core.WebAssets; public class CustomBackOfficeAssetsCollection : BuilderCollectionBase { - public CustomBackOfficeAssetsCollection(Func> items) : base(items) + public CustomBackOfficeAssetsCollection(Func> items) + : base(items) { } } diff --git a/src/Umbraco.Core/WebAssets/IAssetFile.cs b/src/Umbraco.Core/WebAssets/IAssetFile.cs index d0e2c339a085..f3e5516f45e7 100644 --- a/src/Umbraco.Core/WebAssets/IAssetFile.cs +++ b/src/Umbraco.Core/WebAssets/IAssetFile.cs @@ -1,7 +1,8 @@ -namespace Umbraco.Cms.Core.WebAssets; +namespace Umbraco.Cms.Core.WebAssets; public interface IAssetFile { string? FilePath { get; set; } + AssetType DependencyType { get; } } diff --git a/src/Umbraco.Core/WebAssets/JavascriptFile.cs b/src/Umbraco.Core/WebAssets/JavascriptFile.cs index 4b38556f728f..e7f4ea239fcc 100644 --- a/src/Umbraco.Core/WebAssets/JavascriptFile.cs +++ b/src/Umbraco.Core/WebAssets/JavascriptFile.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.WebAssets; +namespace Umbraco.Cms.Core.WebAssets; /// /// Represents a JS asset file diff --git a/src/Umbraco.Core/Xml/XPath/INavigableContent.cs b/src/Umbraco.Core/Xml/XPath/INavigableContent.cs index b26b11514688..b9359b4feff0 100644 --- a/src/Umbraco.Core/Xml/XPath/INavigableContent.cs +++ b/src/Umbraco.Core/Xml/XPath/INavigableContent.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Xml.XPath; +namespace Umbraco.Cms.Core.Xml.XPath; /// /// Represents a content that can be navigated via XPath. @@ -58,5 +58,5 @@ public interface INavigableContent ///// The language key. ///// The value of the field for the specified language. ///// ... - //object Value(int index, string languageKey); + // object Value(int index, string languageKey); } diff --git a/src/Umbraco.Core/Xml/XPath/INavigableContentType.cs b/src/Umbraco.Core/Xml/XPath/INavigableContentType.cs index 6a8edfb0d6f7..08a7c1a0f6cb 100644 --- a/src/Umbraco.Core/Xml/XPath/INavigableContentType.cs +++ b/src/Umbraco.Core/Xml/XPath/INavigableContentType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Xml.XPath; +namespace Umbraco.Cms.Core.Xml.XPath; /// /// Represents the type of a content that can be navigated via XPath. diff --git a/src/Umbraco.Core/Xml/XPath/INavigableFieldType.cs b/src/Umbraco.Core/Xml/XPath/INavigableFieldType.cs index 9d5445999949..28fa46e84bb1 100644 --- a/src/Umbraco.Core/Xml/XPath/INavigableFieldType.cs +++ b/src/Umbraco.Core/Xml/XPath/INavigableFieldType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Xml.XPath; +namespace Umbraco.Cms.Core.Xml.XPath; /// /// Represents the type of a field of a content that can be navigated via XPath. diff --git a/src/Umbraco.Core/Xml/XPath/INavigableSource.cs b/src/Umbraco.Core/Xml/XPath/INavigableSource.cs index 67ab52ae8e60..1f8500725bd7 100644 --- a/src/Umbraco.Core/Xml/XPath/INavigableSource.cs +++ b/src/Umbraco.Core/Xml/XPath/INavigableSource.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Xml.XPath; +namespace Umbraco.Cms.Core.Xml.XPath; /// /// Represents a source of content that can be navigated via XPath. diff --git a/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs b/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs index 186d80e0bf93..b158b7ec0f08 100644 --- a/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs +++ b/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs @@ -29,7 +29,7 @@ public override bool IsEmptyElement break; case StatePosition.Parameter: MacroParameter parameter = _macro.Parameters[InternalState.ParameterIndex]; - XPathNavigator nav = parameter.NavigatorValue; + XPathNavigator? nav = parameter.NavigatorValue; if (parameter.WrapNavigatorInNodes || nav != null) { isEmpty = false; @@ -387,7 +387,7 @@ public override bool MoveToFirstChild() break; case StatePosition.Parameter: MacroParameter parameter = _macro.Parameters[InternalState.ParameterIndex]; - XPathNavigator nav = parameter.NavigatorValue; + XPathNavigator? nav = parameter.NavigatorValue; if (parameter.WrapNavigatorInNodes) { InternalState.Position = StatePosition.ParameterNodes; @@ -512,6 +512,7 @@ public override bool MoveToNextNamespace(XPathNamespaceScope namespaceScope) public override bool MoveToId(string id) { DebugEnter("MoveToId"); + // impossible to implement since parameters can contain duplicate fragments of the // main xml and therefore there can be duplicate unique node identifiers. DebugReturn("NotImplementedException"); @@ -798,7 +799,8 @@ private static bool IsDoc(XPathNavigator? nav) { return true; } - } while (clone.MoveToNextAttribute()); + } + while (clone.MoveToNextAttribute()); return false; } @@ -837,13 +839,13 @@ private MacroNavigator(MacroRoot macro, XmlNameTable nameTable, State state) // in Debug configuration, uncomment lines in Debug() to write to console or to log. // // much of this code is duplicated in each navigator due to conditional compilation - #if DEBUG private const string Tabs = " "; private int _tabs; private readonly int _uid = GetUid(); private static int _uidg; private static readonly object Uidl = new(); + private static int GetUid() { lock (Uidl) @@ -857,7 +859,7 @@ private static int GetUid() private void DebugEnter(string name) { #if DEBUG - Debug(""); + Debug(string.Empty); DebugState(":"); Debug(name); _tabs = Math.Min(Tabs.Length, _tabs + 2); @@ -878,6 +880,7 @@ private void DebugReturn() #if DEBUG // ReSharper disable IntroduceOptionalParameters.Local DebugReturn("(void)"); + // ReSharper restore IntroduceOptionalParameters.Local #endif } @@ -914,27 +917,28 @@ private void DebugState(string s = " =>") position = "At macro."; break; case StatePosition.Parameter: - position = string.Format("At parameter '{0}'.", + position = string.Format( + "At parameter '{0}'.", _macro.Parameters[InternalState.ParameterIndex].Name); break; case StatePosition.ParameterAttribute: - position = string.Format("At parameter attribute '{0}/{1}'.", + position = string.Format( + "At parameter attribute '{0}/{1}'.", _macro.Parameters[InternalState.ParameterIndex].Name, - _macro.Parameters[InternalState.ParameterIndex].Attributes?[InternalState.ParameterAttributeIndex] - .Key); + _macro.Parameters[InternalState.ParameterIndex].Attributes?[InternalState.ParameterAttributeIndex].Key); break; case StatePosition.ParameterNavigator: - position = string.Format("In parameter '{0}{1}' navigator.", + position = string.Format( + "In parameter '{0}{1}' navigator.", _macro.Parameters[InternalState.ParameterIndex].Name, - _macro.Parameters[InternalState.ParameterIndex].WrapNavigatorInNodes ? "/nodes" : ""); + _macro.Parameters[InternalState.ParameterIndex].WrapNavigatorInNodes ? "/nodes" : string.Empty); break; case StatePosition.ParameterNodes: - position = string.Format("At parameter '{0}/nodes'.", - _macro.Parameters[InternalState.ParameterIndex].Name); + + position = string.Format("At parameter '{0}/nodes'.", _macro.Parameters[InternalState.ParameterIndex].Name); break; case StatePosition.ParameterText: - position = string.Format("In parameter '{0}' text.", - _macro.Parameters[InternalState.ParameterIndex].Name); + position = string.Format("In parameter '{0}' text.", _macro.Parameters[InternalState.ParameterIndex].Name); break; case StatePosition.Root: position = "At root."; @@ -951,7 +955,6 @@ private void DebugState(string s = " =>") private void Debug(string format, params object[] args) { // remove comments to write - format = "[" + _uid.ToString("00000") + "] " + Tabs.Substring(0, _tabs) + format; #pragma warning disable 168 var msg = string.Format(format, args); // unused if not writing, hence #pragma @@ -975,14 +978,15 @@ public class MacroParameter { // note: assuming we're not thinking about supporting // XPathIterator in parameters - enough nonsense! - public MacroParameter(string name, string value) { Name = name; StringValue = value; } - public MacroParameter(string name, XPathNavigator navigator, + public MacroParameter( + string name, + XPathNavigator navigator, int maxNavigatorDepth = int.MaxValue, bool wrapNavigatorInNodes = false, IEnumerable>? attributes = null) @@ -1003,10 +1007,15 @@ public MacroParameter(string name, XPathNavigator navigator, } public string Name { get; } + public string? StringValue { get; } + public XPathNavigator? NavigatorValue { get; } + public int MaxNavigatorDepth { get; } + public bool WrapNavigatorInNodes { get; } + public KeyValuePair[]? Attributes { get; } } @@ -1023,7 +1032,7 @@ internal enum StatePosition ParameterAttribute, ParameterText, ParameterNodes, - ParameterNavigator + ParameterNavigator, } // gets the state From ffa487e002627548b907153dc7fe1b3f53077ed8 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle Date: Thu, 19 May 2022 10:25:47 +0200 Subject: [PATCH 003/117] Finish up manual pass --- .../Cache/ContentCacheRefresher.cs | 21 +- .../Cache/ContentTypeCacheRefresher.cs | 5 +- .../Cache/MemberCacheRefresher.cs | 19 +- src/Umbraco.Core/Cache/NoAppCache.cs | 18 +- .../Cache/NoCacheRepositoryCachePolicy.cs | 6 +- src/Umbraco.Core/Cache/ObjectCacheAppCache.cs | 78 ++-- .../Cache/PayloadCacheRefresherBase.cs | 10 +- .../Cache/PublicAccessCacheRefresher.cs | 11 +- .../Cache/RelationTypeCacheRefresher.cs | 17 +- .../Cache/RepositoryCachePolicyOptions.cs | 2 +- src/Umbraco.Core/Cache/SafeLazy.cs | 3 +- .../Cache/TemplateCacheRefresher.cs | 19 +- src/Umbraco.Core/Cache/UserCacheRefresher.cs | 14 +- .../Cache/UserGroupCacheRefresher.cs | 19 +- src/Umbraco.Core/Cache/ValueEditorCache.cs | 4 +- .../Cache/ValueEditorCacheRefresher.cs | 7 +- .../UmbracoObjectTypeAttribute.cs | 2 +- .../UmbracoUdiTypeAttribute.cs | 2 +- .../Collections/OrderedHashSet.cs | 7 +- src/Umbraco.Core/Collections/StackQueue.cs | 2 +- src/Umbraco.Core/Collections/TopoGraph.cs | 14 +- .../Composing/OrderedCollectionBuilderBase.cs | 2 +- .../Composing/ReferenceResolver.cs | 71 ++-- src/Umbraco.Core/Composing/RuntimeHash.cs | 1 - .../Composing/RuntimeHashPaths.cs | 1 + .../Composing/SetCollectionBuilderBase.cs | 2 +- .../Composing/TypeCollectionBuilderBase.cs | 12 +- src/Umbraco.Core/Composing/TypeFinder.cs | 111 ++--- .../Composing/TypeFinderExtensions.cs | 20 +- src/Umbraco.Core/Composing/TypeHelper.cs | 49 ++- src/Umbraco.Core/Composing/TypeLoader.cs | 71 ++-- .../Composing/VaryingRuntimeHash.cs | 2 +- src/Umbraco.Core/Composing/WeightAttribute.cs | 2 +- .../WeightedCollectionBuilderBase.cs | 10 +- .../MemberPasswordConfiguration.cs | 2 +- .../Attributes/UmbracoOptionsAttribute.cs | 3 +- .../Models/ModelsBuilderSettings.cs | 1 - .../Models/NuCacheSerializerType.cs | 2 +- .../Models/RequestHandlerSettings.cs | 24 +- .../Models/RichTextEditorSettings.cs | 93 +++-- .../Models/RuntimeMinificationCacheBuster.cs | 4 +- .../Configuration/Models/SmtpSettings.cs | 2 +- .../Models/UmbracoPluginSettings.cs | 2 +- .../RequestHandlerSettingsValidator.cs | 5 +- .../Validation/UnattendedSettingsValidator.cs | 4 +- .../ModelsBuilderConfigExtensions.cs | 6 +- src/Umbraco.Core/Configuration/ModelsMode.cs | 2 +- .../Configuration/PasswordConfiguration.cs | 2 +- .../UserPasswordConfiguration.cs | 2 +- .../MemberEditorContentAppFactory.cs | 4 +- .../Dashboards/MembersDashboard.cs | 4 +- .../Dashboards/ModelsBuilderDashboard.cs | 2 +- .../Dashboards/ProfilerDashboard.cs | 4 +- .../Dashboards/PublishedStatusDashboard.cs | 4 +- .../Dashboards/RedirectUrlDashboard.cs | 4 +- .../Dashboards/SettingsDashboards.cs | 4 +- .../StaticServiceProvider.cs | 2 +- .../UmbracoBuilder.Collections.cs | 29 +- .../UmbracoBuilder.Configuration.cs | 45 +- .../UmbracoBuilder.Events.cs | 12 +- .../DependencyInjection/UmbracoBuilder.cs | 8 +- .../UniqueServiceDescriptor.cs | 9 +- src/Umbraco.Core/Diagnostics/MiniDump.cs | 105 ++--- src/Umbraco.Core/Diagnostics/NoopMarchal.cs | 2 +- .../Dictionary/UmbracoCultureDictionary.cs | 38 +- .../UmbracoCultureDictionaryFactory.cs | 2 +- .../Editors/UserEditorAuthorizationHelper.cs | 50 ++- src/Umbraco.Core/Events/MoveEventArgs.cs | 23 +- src/Umbraco.Core/Events/MoveEventInfo.cs | 8 +- src/Umbraco.Core/Events/NewEventArgs.cs | 13 +- .../Events/PassThroughEventDispatcher.cs | 19 +- src/Umbraco.Core/Events/PublishEventArgs.cs | 16 +- .../Events/QueuingEventDispatcher.cs | 6 +- .../Events/QueuingEventDispatcherBase.cs | 57 ++- .../Events/RecycleBinEventArgs.cs | 6 +- .../Events/RefreshContentEventArgs.cs | 4 +- .../Events/RelateOnCopyNotificationHandler.cs | 9 +- src/Umbraco.Core/Events/RolesEventArgs.cs | 3 +- src/Umbraco.Core/Events/RollbackEventArgs.cs | 8 +- src/Umbraco.Core/Events/SaveEventArgs.cs | 19 +- src/Umbraco.Core/Events/SendEmailEventArgs.cs | 2 +- .../Events/SendToPublishEventArgs.cs | 8 +- .../Events/SupersedeEventAttribute.cs | 2 +- .../Events/TransientEventMessagesFactory.cs | 2 +- src/Umbraco.Core/Events/TypedEventHandler.cs | 2 +- src/Umbraco.Core/Events/UserGroupWithUsers.cs | 4 +- .../Events/UserNotificationsHandler.cs | 45 +- src/Umbraco.Core/Exceptions/PanicException.cs | 2 +- .../Exceptions/UnattendedInstallException.cs | 11 +- .../Extensions/MediaTypeExtensions.cs | 2 +- .../NameValueCollectionExtensions.cs | 2 +- .../Extensions/ObjectExtensions.cs | 390 +++++++++--------- .../PasswordConfigurationExtensions.cs | 8 +- .../Extensions/PublishedContentExtensions.cs | 390 +++++++++--------- .../Extensions/PublishedElementExtensions.cs | 40 +- .../Extensions/PublishedPropertyExtension.cs | 8 +- .../PublishedSnapshotAccessorExtensions.cs | 2 +- .../RequestHandlerSettingsExtension.cs | 8 +- .../Extensions/RuntimeStateExtensions.cs | 3 +- .../Extensions/SemVersionExtensions.cs | 4 +- .../Extensions/StringExtensions.cs | 319 +++++++------- .../Extensions/ThreadExtensions.cs | 4 +- src/Umbraco.Core/Extensions/TypeExtensions.cs | 99 +++-- .../Extensions/TypeLoaderExtensions.cs | 2 +- .../Extensions/UdiGetterExtensions.cs | 72 ++-- .../Extensions/UmbracoBuilderExtensions.cs | 5 +- .../UmbracoContextAccessorExtensions.cs | 4 +- src/Umbraco.Core/Extensions/UriExtensions.cs | 24 +- .../Extensions/VersionExtensions.cs | 17 +- .../Extensions/WaitHandleExtensions.cs | 7 +- src/Umbraco.Core/Extensions/XmlExtensions.cs | 38 +- .../Handlers/PublicAccessHandler.cs | 5 +- .../Configuration/NotificationEmailCheck.cs | 16 +- .../Checks/ProvidedValueValidation.cs | 2 +- .../Security/UmbracoApplicationUrlCheck.cs | 7 +- .../HealthChecks/Checks/Services/SmtpCheck.cs | 59 ++- .../NotificationMethodBase.cs | 9 +- .../HealthChecks/StatusResultType.cs | 4 +- .../HealthChecks/ValueComparisonType.cs | 4 +- .../NoopApplicationShutdownRegistry.cs | 11 +- src/Umbraco.Core/IO/PhysicalFileSystem.cs | 19 +- src/Umbraco.Core/IO/ShadowFileSystem.cs | 106 +++-- src/Umbraco.Core/IO/ShadowFileSystems.cs | 4 +- src/Umbraco.Core/IO/ShadowWrapper.cs | 22 +- src/Umbraco.Core/IO/ViewHelper.cs | 25 +- .../InstallSteps/TelemetryIdentifierStep.cs | 9 +- .../Install/InstallSteps/UpgradeStep.cs | 13 +- src/Umbraco.Core/Install/Models/Package.cs | 11 +- src/Umbraco.Core/Install/Models/UserModel.cs | 11 +- .../Logging/ProfilerExtensions.cs | 2 +- src/Umbraco.Core/Manifest/PackageManifest.cs | 6 +- .../EmbedProviders/OEmbedProviderBase.cs | 5 +- .../Media/EmbedProviders/OEmbedResponse.cs | 39 +- .../Media/EmbedProviders/Slideshare.cs | 9 +- .../Media/EmbedProviders/SoundCloud.cs | 9 +- src/Umbraco.Core/Media/EmbedProviders/Ted.cs | 9 +- .../Media/EmbedProviders/Twitter.cs | 9 +- .../Media/EmbedProviders/Vimeo.cs | 9 +- .../Media/EmbedProviders/Youtube.cs | 13 +- src/Umbraco.Core/Media/Exif/SvgFile.cs | 14 +- src/Umbraco.Core/Media/Exif/TIFFFile.cs | 29 +- src/Umbraco.Core/Media/Exif/TIFFHeader.cs | 7 +- src/Umbraco.Core/Media/Exif/TIFFStrip.cs | 2 +- src/Umbraco.Core/Media/Exif/Utility.cs | 2 +- src/Umbraco.Core/Media/OEmbedResult.cs | 4 +- src/Umbraco.Core/Media/OEmbedStatus.cs | 4 +- .../TypeDetector/RasterizedTypeDetector.cs | 2 +- .../Media/TypeDetector/SvgDetector.cs | 2 +- .../Media/TypeDetector/TIFFDetector.cs | 2 +- .../Media/UploadAutoFillProperties.cs | 37 +- .../Models/ContentEditing/MediaTypeDisplay.cs | 2 +- .../Models/ContentEditing/MediaTypeSave.cs | 2 +- .../Models/ContentEditing/MemberBasic.cs | 8 +- .../Models/ContentEditing/MemberDisplay.cs | 27 +- .../ContentEditing/MemberGroupDisplay.cs | 2 +- .../Models/ContentEditing/MemberGroupSave.cs | 2 +- .../ContentEditing/MemberListDisplay.cs | 5 +- .../ContentEditing/MemberPropertyTypeBasic.cs | 8 +- .../MemberPropertyTypeDisplay.cs | 8 +- .../Models/ContentEditing/MemberSave.cs | 16 +- .../ContentEditing/MemberTypeDisplay.cs | 2 +- .../Models/ContentEditing/MemberTypeSave.cs | 2 +- .../ContentEditing/MessagesExtensions.cs | 23 +- .../ContentEditing/ModelWithNotifications.cs | 2 +- .../Models/ContentEditing/MoveOrCopy.cs | 2 +- .../ContentEditing/NotificationStyle.cs | 2 +- .../Models/ContentEditing/NotifySetting.cs | 8 +- .../Models/ContentEditing/ObjectType.cs | 8 +- .../Models/ContentEditing/Permission.cs | 14 +- .../Models/ContentEditing/PostedFiles.cs | 5 +- .../Models/ContentEditing/PostedFolder.cs | 6 +- .../ContentEditing/PropertyEditorBasic.cs | 11 +- .../ContentEditing/PropertyGroupBasic.cs | 19 +- .../PropertyGroupBasicExtensions.cs | 2 +- .../ContentEditing/PropertyGroupDisplay.cs | 2 +- .../ContentEditing/PropertyTypeBasic.cs | 22 +- .../ContentEditing/PropertyTypeDisplay.cs | 2 +- .../ContentEditing/PropertyTypeValidation.cs | 11 +- .../Models/ContentEditing/PublicAccess.cs | 14 +- .../RedirectUrlSearchResults.cs | 14 +- .../Models/ContentEditing/RelationDisplay.cs | 2 +- .../ContentEditing/RelationTypeDisplay.cs | 2 +- .../Models/ContentEditing/RelationTypeSave.cs | 2 +- .../ContentEditing/RichTextEditorCommand.cs | 25 +- .../RichTextEditorConfiguration.cs | 17 +- .../ContentEditing/RichTextEditorPlugin.cs | 5 +- .../Models/ContentEditing/RollbackVersion.cs | 11 +- .../Models/ContentEditing/SearchResult.cs | 14 +- .../ContentEditing/SearchResultEntity.cs | 2 +- .../Models/ContentEditing/SearchResults.cs | 10 +- .../Models/ContentEditing/Section.cs | 8 +- .../ContentEditing/SimpleNotificationModel.cs | 2 +- .../Models/ContentEditing/SnippetDisplay.cs | 2 +- .../Models/ContentEditing/StyleSheet.cs | 8 +- .../Models/ContentEditing/StylesheetRule.cs | 11 +- src/Umbraco.Core/Models/ContentEditing/Tab.cs | 21 +- .../ContentEditing/TabbedContentItem.cs | 5 +- .../Models/ContentEditing/TemplateDisplay.cs | 21 +- .../Models/ContentEditing/TreeSearchResult.cs | 11 +- .../ContentEditing/UmbracoEntityTypes.cs | 2 +- .../Models/ContentEditing/UnpublishContent.cs | 8 +- .../Models/ContentEditing/UrlAndAnchors.cs | 6 +- .../Models/ContentEditing/UserBasic.cs | 11 +- .../Models/ContentEditing/UserDetail.cs | 2 +- .../Models/ContentEditing/UserDisplay.cs | 8 +- .../Models/ContentEditing/UserGroupBasic.cs | 8 +- .../Models/ContentEditing/UserGroupDisplay.cs | 5 +- .../UserGroupPermissionsSave.cs | 5 +- .../Models/ContentEditing/UserGroupSave.cs | 21 +- .../Models/ContentEditing/UserInvite.cs | 15 +- .../Models/ContentEditing/UserProfile.cs | 3 +- .../Models/ContentEditing/UserSave.cs | 8 +- .../Models/Editors/UmbracoEntityReference.cs | 9 +- .../Models/Entities/MemberEntitySlim.cs | 2 +- .../Models/Entities/TreeEntityBase.cs | 19 +- .../Models/Entities/TreeEntityPath.cs | 2 +- .../Models/Mapping/MemberMapDefinition.cs | 8 +- .../Mapping/MemberTabsAndPropertiesMapper.cs | 261 ++++++------ .../Models/Mapping/PropertyTypeGroupMapper.cs | 139 ++++--- .../Mapping/RedirectUrlMapDefinition.cs | 2 +- .../Models/Mapping/RelationMapDefinition.cs | 38 +- .../Models/Mapping/SectionMapDefinition.cs | 14 +- .../Models/Mapping/TabsAndPropertiesMapper.cs | 23 +- .../Models/Mapping/TagMapDefinition.cs | 2 +- .../Models/Mapping/TemplateMapDefinition.cs | 2 +- .../Models/Mapping/UserMapDefinition.cs | 163 ++++---- src/Umbraco.Core/Models/MediaType.cs | 13 +- src/Umbraco.Core/Models/Member.cs | 67 +-- src/Umbraco.Core/Models/MemberGroup.cs | 10 +- .../Models/MemberPropertyModel.cs | 13 +- src/Umbraco.Core/Models/MemberType.cs | 47 ++- .../Models/MemberTypePropertyProfileAccess.cs | 4 +- .../Models/Membership/MemberCountType.cs | 4 +- .../Models/Membership/MemberExportModel.cs | 11 +- .../Models/Membership/MemberExportProperty.cs | 7 +- .../Models/Membership/ReadOnlyUserGroup.cs | 18 +- src/Umbraco.Core/Models/Membership/User.cs | 71 ++-- .../Models/Membership/UserGroup.cs | 17 +- .../Models/Membership/UserGroupExtensions.cs | 12 +- .../Models/Membership/UserProfile.cs | 17 +- .../Models/Membership/UserState.cs | 4 +- src/Umbraco.Core/Models/MigrationEntry.cs | 2 +- src/Umbraco.Core/Models/Notification.cs | 5 +- .../Models/NotificationEmailBodyParams.cs | 10 +- .../Models/NotificationEmailSubjectParams.cs | 4 +- src/Umbraco.Core/Models/ObjectTypes.cs | 42 +- src/Umbraco.Core/Models/PagedResult.cs | 14 +- src/Umbraco.Core/Models/PagedResultOfT.cs | 5 +- src/Umbraco.Core/Models/PartialView.cs | 2 +- .../Models/PartialViewMacroModel.cs | 8 +- .../Models/PartialViewMacroModelExtensions.cs | 7 +- src/Umbraco.Core/Models/PartialViewType.cs | 4 +- .../Models/PasswordChangedModel.cs | 2 +- src/Umbraco.Core/Models/Property.cs | 133 +++--- src/Umbraco.Core/Models/PropertyCollection.cs | 49 ++- src/Umbraco.Core/Models/PropertyGroup.cs | 19 +- .../Models/PropertyGroupCollection.cs | 93 ++--- .../Models/PropertyGroupExtensions.cs | 22 +- src/Umbraco.Core/Models/PropertyGroupType.cs | 2 +- .../Models/PropertyTagsExtensions.cs | 198 +++++---- src/Umbraco.Core/Models/PropertyType.cs | 54 ++- .../Models/PropertyTypeCollection.cs | 43 +- src/Umbraco.Core/Models/PublicAccessEntry.cs | 48 +-- src/Umbraco.Core/Models/PublicAccessRule.cs | 2 +- .../Models/PublishedContent/ModelType.cs | 242 ++++++----- .../NoopPublishedModelFactory.cs | 2 +- .../NoopPublishedValueFallback.cs | 20 +- .../PublishedContent/PublishedContentBase.cs | 14 +- .../PublishedContentExtensionsForModels.cs | 4 +- .../PublishedContent/PublishedContentModel.cs | 3 +- .../PublishedContent/PublishedContentType.cs | 87 ++-- .../PublishedContentTypeConverter.cs | 7 +- .../PublishedContentTypeFactory.cs | 63 +-- .../PublishedContentWrapped.cs | 26 +- .../PublishedContent/PublishedCultureInfos.cs | 5 +- .../PublishedContent/PublishedDataType.cs | 2 +- .../PublishedContent/PublishedElementModel.cs | 2 +- .../PublishedElementWrapped.cs | 2 +- .../PublishedContent/PublishedItemType.cs | 4 +- .../PublishedModelAttribute.cs | 5 +- .../PublishedContent/PublishedModelFactory.cs | 17 +- .../PublishedContent/PublishedPropertyBase.cs | 2 +- .../PublishedContent/PublishedPropertyType.cs | 154 +++---- .../PublishedContent/PublishedSearchResult.cs | 3 +- .../PublishedValueFallback.cs | 64 ++- .../PublishedContent/RawValueProperty.cs | 8 +- .../ThreadCultureVariationContextAccessor.cs | 2 +- .../Models/PublishedContent/UrlMode.cs | 4 +- .../PublishedContent/VariationContext.cs | 6 +- .../VariationContextAccessorExtensions.cs | 17 +- src/Umbraco.Core/Models/PublishedState.cs | 6 +- src/Umbraco.Core/Models/Range.cs | 20 +- .../Models/ReadOnlyContentBaseAdapter.cs | 4 +- src/Umbraco.Core/Models/ReadOnlyRelation.cs | 8 +- src/Umbraco.Core/Models/RedirectUrl.cs | 2 +- src/Umbraco.Core/Models/Relation.cs | 11 +- src/Umbraco.Core/Models/RelationItem.cs | 26 +- src/Umbraco.Core/Models/RelationType.cs | 14 +- .../Models/RelationTypeExtensions.cs | 2 +- .../Models/RequestPasswordResetModel.cs | 2 +- src/Umbraco.Core/Models/Script.cs | 2 +- src/Umbraco.Core/Models/SendCodeViewModel.cs | 2 +- src/Umbraco.Core/Models/ServerRegistration.cs | 8 +- src/Umbraco.Core/Models/SetPasswordModel.cs | 2 +- src/Umbraco.Core/Models/SimpleContentType.cs | 7 +- .../Models/SimpleValidationModel.cs | 3 +- src/Umbraco.Core/Models/Stylesheet.cs | 56 +-- src/Umbraco.Core/Models/StylesheetProperty.cs | 2 +- src/Umbraco.Core/Models/Tag.cs | 2 +- src/Umbraco.Core/Models/TagModel.cs | 8 +- .../Models/TaggableObjectTypes.cs | 4 +- src/Umbraco.Core/Models/TaggedEntity.cs | 2 +- src/Umbraco.Core/Models/TaggedProperty.cs | 2 +- src/Umbraco.Core/Models/TagsStorageType.cs | 4 +- src/Umbraco.Core/Models/TelemetryLevel.cs | 4 +- src/Umbraco.Core/Models/TelemetryResource.cs | 5 +- src/Umbraco.Core/Models/Template.cs | 5 +- src/Umbraco.Core/Models/TemplateNode.cs | 2 +- src/Umbraco.Core/Models/TemplateOnDisk.cs | 3 +- .../Models/TemplateQuery/Operator.cs | 4 +- .../Models/TemplateQuery/OperatorTerm.cs | 6 +- .../Models/TemplateQuery/PropertyModel.cs | 2 +- .../Models/TemplateQuery/QueryCondition.cs | 4 +- .../TemplateQuery/QueryConditionExtensions.cs | 10 +- .../Models/TemplateQuery/QueryModel.cs | 6 +- .../Models/TemplateQuery/QueryResultModel.cs | 6 +- .../Models/TemplateQuery/SortExpression.cs | 2 +- .../Models/TemplateQuery/SourceModel.cs | 3 +- .../TemplateQuery/TemplateQueryResult.cs | 2 +- src/Umbraco.Core/Models/Trees/RefreshNode.cs | 2 +- src/Umbraco.Core/Models/TwoFactorLogin.cs | 5 +- src/Umbraco.Core/Models/UmbracoDomain.cs | 2 +- src/Umbraco.Core/Models/UmbracoObjectTypes.cs | 26 +- .../Models/UmbracoUserExtensions.cs | 11 +- src/Umbraco.Core/Models/UnLinkLoginModel.cs | 2 +- .../Models/UpgradeCheckResponse.cs | 16 +- src/Umbraco.Core/Models/UsageInformation.cs | 8 +- src/Umbraco.Core/Models/UserData.cs | 8 +- src/Umbraco.Core/Models/UserExtensions.cs | 178 ++++---- src/Umbraco.Core/Models/UserTourStatus.cs | 6 +- .../Models/UserTwoFactorProviderModel.cs | 8 +- .../Models/ValidatePasswordResetCodeModel.cs | 2 +- .../RequiredForPersistenceAttribute.cs | 4 +- src/Umbraco.Core/Models/ValueStorageType.cs | 17 +- src/Umbraco.Core/MonitorLock.cs | 2 +- src/Umbraco.Core/NamedUdiRange.cs | 2 +- src/Umbraco.Core/Net/NullSessionIdResolver.cs | 2 +- .../MediaTreeChangeNotification.cs | 16 +- .../MediaTypeChangedNotification.cs | 8 +- .../MediaTypeDeletedNotification.cs | 6 +- .../MediaTypeDeletingNotification.cs | 7 +- .../MediaTypeMovedNotification.cs | 6 +- .../MediaTypeMovingNotification.cs | 7 +- .../MediaTypeRefreshedNotification.cs | 8 +- .../MediaTypeSavedNotification.cs | 6 +- .../MediaTypeSavingNotification.cs | 6 +- .../MemberCacheRefresherNotification.cs | 6 +- .../MemberDeletedNotification.cs | 3 +- .../MemberDeletingNotification.cs | 6 +- .../MemberGroupCacheRefresherNotification.cs | 6 +- .../MemberGroupDeletedNotification.cs | 3 +- .../MemberGroupDeletingNotification.cs | 7 +- .../MemberGroupSavedNotification.cs | 7 +- .../MemberGroupSavingNotification.cs | 9 +- .../MemberRefreshNotification.cs | 3 +- .../Notifications/MemberSavedNotification.cs | 6 +- .../Notifications/MemberSavingNotification.cs | 6 +- .../MemberTwoFactorRequestedNotification.cs | 2 +- .../MemberTypeChangedNotification.cs | 8 +- .../MemberTypeDeletedNotification.cs | 7 +- .../MemberTypeDeletingNotification.cs | 7 +- .../MemberTypeMovedNotification.cs | 7 +- .../MemberTypeMovingNotification.cs | 7 +- .../MemberTypeRefreshedNotification.cs | 8 +- .../MemberTypeSavedNotification.cs | 6 +- .../MemberTypeSavingNotification.cs | 7 +- .../Notifications/MovedNotification.cs | 6 +- .../MovedToRecycleBinNotification.cs | 8 +- .../Notifications/MovingNotification.cs | 6 +- .../MovingToRecycleBinNotification.cs | 7 +- .../Notifications/NotificationExtensions.cs | 3 +- .../Notifications/ObjectNotification.cs | 3 +- .../PartialViewCreatedNotification.cs | 3 +- .../PartialViewCreatingNotification.cs | 3 +- .../PartialViewDeletedNotification.cs | 3 +- .../PartialViewDeletingNotification.cs | 7 +- .../PartialViewSavedNotification.cs | 7 +- .../PartialViewSavingNotification.cs | 7 +- .../PublicAccessCacheRefresherNotification.cs | 6 +- .../PublicAccessEntryDeletedNotification.cs | 4 +- .../PublicAccessEntryDeletingNotification.cs | 7 +- .../PublicAccessEntrySavedNotification.cs | 6 +- .../PublicAccessEntrySavingNotification.cs | 7 +- .../RelationDeletedNotification.cs | 6 +- .../RelationDeletingNotification.cs | 6 +- .../RelationSavedNotification.cs | 6 +- .../RelationSavingNotification.cs | 6 +- .../RelationTypeCacheRefresherNotification.cs | 6 +- .../RelationTypeDeletedNotification.cs | 3 +- .../RelationTypeDeletingNotification.cs | 7 +- .../RelationTypeSavedNotification.cs | 7 +- .../RelationTypeSavingNotification.cs | 7 +- .../RemovedMemberRolesNotification.cs | 3 +- .../Notifications/RenamedNotification.cs | 6 +- .../Notifications/RenamingNotification.cs | 6 +- .../Notifications/RolledBackNotification.cs | 6 +- .../Notifications/RollingBackNotification.cs | 6 +- .../RuntimeUnattendedUpgradeNotification.cs | 2 +- .../Notifications/SavedNotification.cs | 6 +- .../Notifications/SavingNotification.cs | 6 +- .../ScopedEntityRemoveNotification.cs | 3 +- .../ScriptDeletedNotification.cs | 3 +- .../ScriptDeletingNotification.cs | 6 +- .../Notifications/ScriptSavedNotification.cs | 6 +- .../Notifications/ScriptSavingNotification.cs | 6 +- .../ServerVariablesParsingNotification.cs | 2 +- .../Notifications/SortedNotification.cs | 3 +- .../Notifications/SortingNotification.cs | 3 +- .../StylesheetDeletedNotification.cs | 3 +- .../StylesheetDeletingNotification.cs | 7 +- .../StylesheetSavedNotification.cs | 6 +- .../StylesheetSavingNotification.cs | 7 +- .../TemplateCacheRefresherNotification.cs | 6 +- .../TemplateDeletedNotification.cs | 3 +- .../TemplateDeletingNotification.cs | 6 +- .../TemplateSavedNotification.cs | 19 +- .../TemplateSavingNotification.cs | 28 +- .../Notifications/TreeChangeNotification.cs | 6 +- ...icationComponentsInstallingNotification.cs | 1 + ...oApplicationMainDomAcquiredNotification.cs | 2 +- .../UserCacheRefresherNotification.cs | 6 +- .../Notifications/UserDeletedNotification.cs | 3 +- .../Notifications/UserDeletingNotification.cs | 6 +- .../UserForgotPasswordChangedNotification.cs | 4 +- ...UserForgotPasswordRequestedNotification.cs | 4 +- .../UserGroupCacheRefresherNotification.cs | 6 +- .../UserGroupDeletedNotification.cs | 3 +- .../UserGroupDeletingNotification.cs | 7 +- .../UserGroupSavedNotification.cs | 6 +- .../UserGroupSavingNotification.cs | 6 +- .../UserGroupWithUsersSavedNotification.cs | 7 +- .../UserGroupWithUsersSavingNotification.cs | 7 +- .../Notifications/UserLockedNotification.cs | 7 +- .../UserLoginFailedNotification.cs | 3 +- .../UserLoginSuccessNotification.cs | 3 +- .../UserLogoutSuccessNotification.cs | 3 +- .../UserPasswordChangedNotification.cs | 3 +- .../UserPasswordResetNotification.cs | 3 +- .../UserResetAccessFailedCountNotification.cs | 4 +- .../Notifications/UserSavedNotification.cs | 6 +- .../Notifications/UserSavingNotification.cs | 6 +- .../UserTwoFactorRequestedNotification.cs | 2 +- .../Notifications/UserUnlockedNotification.cs | 4 +- .../Packaging/PackageDefinition.cs | 46 ++- .../Packaging/PackageDefinitionXmlParser.cs | 40 +- .../Packaging/PackageMigrationResource.cs | 61 ++- .../Packaging/PackagesRepository.cs | 154 +++---- .../Querying/StringPropertyMatchType.cs | 6 +- .../Querying/ValuePropertyMatchType.cs | 4 +- .../Repositories/RepositoryCacheKeys.cs | 4 +- .../Repositories/UpgradeCheckRepository.cs | 14 +- .../Persistence/SqlExpressionExtensions.cs | 3 +- .../Persistence/SqlExtensionsStatics.cs | 5 +- .../Persistence/TextColumnType.cs | 2 +- .../MemberGroupPickerPropertyEditor.cs | 2 +- .../MemberPickerConfiguration.cs | 4 +- .../MemberPickerPropertyEditor.cs | 2 +- .../PropertyEditors/MissingPropertyEditor.cs | 2 +- .../MultiNodePickerConfiguration.cs | 11 +- .../MultiNodePickerConfigurationEditor.cs | 4 +- .../MultiNodePickerConfigurationTreeSource.cs | 11 +- .../MultiUrlPickerConfiguration.cs | 12 +- .../MultiUrlPickerConfigurationEditor.cs | 5 +- .../MultipleTextStringConfiguration.cs | 3 +- .../NestedContentConfiguration.cs | 22 +- .../NestedContentConfigurationEditor.cs | 6 +- .../MultipleContentPickerParameterEditor.cs | 15 +- .../MultipleContentTypeParameterEditor.cs | 2 +- .../MultipleMediaPickerParameterEditor.cs | 13 +- ...ultiplePickerParamateterValueEditorBase.cs | 3 +- .../MultiplePropertyGroupParameterEditor.cs | 5 +- .../MultiplePropertyTypeParameterEditor.cs | 5 +- .../PropertyGroupParameterEditor.cs | 5 +- .../PropertyTypeParameterEditor.cs | 5 +- .../PropertyCacheCompression.cs | 8 +- .../PropertyEditors/PropertyCacheLevel.cs | 4 +- .../PropertyEditorTagsExtensions.cs | 2 +- .../PropertyValueConverterBase.cs | 11 +- .../PropertyValueConverterCollection.cs | 6 +- ...PropertyValueConverterCollectionBuilder.cs | 2 +- .../PropertyEditors/PropertyValueLevel.cs | 4 +- .../PropertyEditors/RichTextConfiguration.cs | 14 +- .../RichTextConfigurationEditor.cs | 5 +- .../PropertyEditors/SliderConfiguration.cs | 2 +- .../SliderConfigurationEditor.cs | 5 +- .../PropertyEditors/TagConfiguration.cs | 13 +- .../PropertyEditors/TagConfigurationEditor.cs | 19 +- .../TagsPropertyEditorAttribute.cs | 4 +- .../PropertyEditors/TextAreaConfiguration.cs | 8 +- .../TextAreaConfigurationEditor.cs | 5 +- .../PropertyEditors/TextOnlyValueEditor.cs | 2 +- .../TextStringValueConverter.cs | 15 +- .../PropertyEditors/TextboxConfiguration.cs | 5 +- .../TextboxConfigurationEditor.cs | 5 +- .../PropertyEditors/TrueFalseConfiguration.cs | 9 +- .../TrueFalseConfigurationEditor.cs | 6 +- ...dContentPropertyCacheCompressionOptions.cs | 9 +- .../UserPickerConfiguration.cs | 4 +- .../UserPickerPropertyEditor.cs | 2 +- .../Validators/RegexValidator.cs | 16 +- .../Validators/RequiredValidator.cs | 11 +- .../MemberGroupPickerValueConverter.cs | 5 +- .../MemberPickerValueConverter.cs | 9 +- .../MultiNodeTreePickerValueConverter.cs | 42 +- .../MultipleTextStringValueConverter.cs | 15 +- .../MustBeStringValueConverter.cs | 7 +- .../RadioButtonListValueConverter.cs | 5 +- .../SimpleTinyMceValueConverter.cs | 12 +- .../ValueConverters/SliderValueConverter.cs | 14 +- .../ValueConverters/TagsValueConverter.cs | 13 +- .../UploadPropertyConverter.cs | 5 +- .../ValueConverters/YesNoValueConverter.cs | 9 +- .../PropertyEditors/ValueListConfiguration.cs | 8 +- .../PropertyEditors/ValueTypes.cs | 5 +- .../PublishedCache/PublishedCacheBase.cs | 5 +- .../PublishedCache/PublishedElement.cs | 22 +- .../PublishedElementPropertyBase.cs | 37 +- ...UmbracoContextPublishedSnapshotAccessor.cs | 4 +- src/Umbraco.Core/ReflectionUtilities.cs | 122 ++---- .../Routing/MediaUrlProviderCollection.cs | 3 +- .../MediaUrlProviderCollectionBuilder.cs | 2 +- .../Routing/PublishedRequestOld.cs | 95 +++-- src/Umbraco.Core/Routing/PublishedRouter.cs | 90 ++-- src/Umbraco.Core/Routing/RouteDirection.cs | 2 +- src/Umbraco.Core/Routing/SiteDomainMapper.cs | 168 ++++---- .../Routing/UmbracoRequestPaths.cs | 2 +- .../Routing/UmbracoRouteResult.cs | 4 +- src/Umbraco.Core/Routing/UriUtility.cs | 28 +- src/Umbraco.Core/Routing/UrlInfo.cs | 16 +- src/Umbraco.Core/Routing/UrlProvider.cs | 56 ++- .../Routing/UrlProviderCollection.cs | 3 +- .../Routing/UrlProviderCollectionBuilder.cs | 2 +- .../Routing/UrlProviderExtensions.cs | 20 +- src/Umbraco.Core/Routing/WebPath.cs | 2 +- src/Umbraco.Core/RuntimeLevel.cs | 2 +- src/Umbraco.Core/RuntimeLevelReason.cs | 2 +- .../Scoping/RepositoryCacheMode.cs | 4 +- src/Umbraco.Core/Sections/MembersSection.cs | 2 +- src/Umbraco.Core/Sections/PackagesSection.cs | 2 +- .../Sections/SectionCollection.cs | 3 +- .../Sections/SectionCollectionBuilder.cs | 2 +- src/Umbraco.Core/Sections/SettingsSection.cs | 2 +- .../Sections/TranslationSection.cs | 2 +- src/Umbraco.Core/Sections/UsersSection.cs | 2 +- .../Security/PasswordGenerator.cs | 18 +- .../Security/PublicAccessStatus.cs | 2 +- .../Security/UpdateMemberProfileResult.cs | 6 +- .../Security/UpdateMemberProfileStatus.cs | 4 +- .../Services/Changes/TreeChange.cs | 5 +- .../Services/Changes/TreeChangeExtensions.cs | 2 +- .../Services/Changes/TreeChangeTypes.cs | 4 +- src/Umbraco.Core/Services/MediaService.cs | 21 +- .../Services/MediaServiceExtensions.cs | 11 +- src/Umbraco.Core/Services/MediaTypeService.cs | 50 ++- src/Umbraco.Core/Services/MemberService.cs | 279 +++++++------ .../Services/MemberTypeService.cs | 81 ++-- .../Services/MetricsConsentService.cs | 5 +- .../Services/MoveOperationStatusType.cs | 4 +- src/Umbraco.Core/Services/NodeCountService.cs | 2 +- .../Services/NotificationService.cs | 192 +++++---- src/Umbraco.Core/Services/OperationResult.cs | 48 ++- .../Services/OperationResultType.cs | 4 +- src/Umbraco.Core/Services/Ordering.cs | 8 +- .../Services/ProcessInstructionsResult.cs | 6 +- .../Services/PropertyValidationService.cs | 17 +- .../Services/PublicAccessService.cs | 29 +- .../Services/PublicAccessServiceExtensions.cs | 22 +- src/Umbraco.Core/Services/PublishResult.cs | 2 +- .../Services/PublishResultType.cs | 3 +- .../Services/RedirectUrlService.cs | 8 +- src/Umbraco.Core/Services/RelationService.cs | 116 +++--- src/Umbraco.Core/Services/SectionService.cs | 2 +- .../Services/ServerRegistrationService.cs | 12 +- src/Umbraco.Core/Services/ServiceContext.cs | 46 ++- src/Umbraco.Core/Services/TagService.cs | 12 +- .../Services/TrackedReferencesService.cs | 27 +- src/Umbraco.Core/Services/TreeService.cs | 6 +- .../Services/TwoFactorLoginService.cs | 22 +- src/Umbraco.Core/Services/UserDataService.cs | 9 +- src/Umbraco.Core/Services/UserService.cs | 267 ++++++------ .../Services/UserServiceExtensions.cs | 7 +- src/Umbraco.Core/Settable.cs | 2 +- src/Umbraco.Core/SimpleMainDom.cs | 6 +- src/Umbraco.Core/StaticApplicationLogging.cs | 8 +- src/Umbraco.Core/StringUdi.cs | 15 +- .../Strings/Css/StylesheetHelper.cs | 21 +- .../Strings/Css/StylesheetRule.cs | 3 +- .../Strings/UrlSegmentProviderCollection.cs | 3 +- .../UrlSegmentProviderCollectionBuilder.cs | 2 +- .../Strings/Utf8ToAsciiConverter.cs | 62 ++- src/Umbraco.Core/Sync/MessageType.cs | 4 +- .../Sync/NonRuntimeLevelBootStateAccessor.cs | 2 +- src/Umbraco.Core/Sync/RefreshInstruction.cs | 36 +- src/Umbraco.Core/Sync/RefreshMethodType.cs | 7 +- src/Umbraco.Core/Sync/ServerRole.cs | 2 +- src/Umbraco.Core/Sync/SyncBootState.cs | 4 +- src/Umbraco.Core/SystemLock.cs | 83 ++-- .../Telemetry/Models/TelemetryReportData.cs | 3 +- .../Telemetry/TelemetryService.cs | 5 +- .../Templates/UmbracoComponentRenderer.cs | 14 +- src/Umbraco.Core/Tour/TourFilterCollection.cs | 3 +- .../Tour/TourFilterCollectionBuilder.cs | 10 +- src/Umbraco.Core/Trees/MenuItemCollection.cs | 2 +- src/Umbraco.Core/Trees/MenuItemList.cs | 11 +- .../Trees/SearchableApplicationTree.cs | 8 +- .../Trees/SearchableTreeAttribute.cs | 5 +- .../Trees/SearchableTreeCollection.cs | 7 +- .../Trees/SearchableTreeCollectionBuilder.cs | 2 +- src/Umbraco.Core/Trees/Tree.cs | 5 +- src/Umbraco.Core/Trees/TreeCollection.cs | 3 +- src/Umbraco.Core/Trees/TreeNode.cs | 14 +- src/Umbraco.Core/Trees/TreeNodeCollection.cs | 2 +- src/Umbraco.Core/Trees/TreeNodeExtensions.cs | 18 +- src/Umbraco.Core/Trees/TreeUse.cs | 4 +- src/Umbraco.Core/Udi.cs | 49 ++- src/Umbraco.Core/UdiDefinitionAttribute.cs | 2 +- src/Umbraco.Core/UdiParser.cs | 114 ++--- .../UdiParserServiceConnectors.cs | 6 +- src/Umbraco.Core/UdiRange.cs | 44 +- src/Umbraco.Core/UdiType.cs | 4 +- src/Umbraco.Core/UdiTypeConverter.cs | 5 +- .../UmbracoApiControllerTypeCollection.cs | 3 +- src/Umbraco.Core/UnknownTypeUdi.cs | 2 +- src/Umbraco.Core/UpgradeResult.cs | 4 +- src/Umbraco.Core/UriUtilityCore.cs | 10 +- .../Web/Mvc/PluginControllerMetadata.cs | 5 +- .../Xml/UmbracoXPathPathSyntaxParser.cs | 26 +- .../Xml/XPath/NavigableNavigator.cs | 47 +-- .../Xml/XPath/RenamedRootNavigator.cs | 6 +- .../Xml/XPathNavigatorExtensions.cs | 8 +- src/Umbraco.Core/Xml/XPathVariable.cs | 2 +- src/Umbraco.Core/Xml/XmlHelper.cs | 63 +-- src/Umbraco.Core/Xml/XmlNamespaces.cs | 2 +- src/Umbraco.Core/Xml/XmlNodeListFactory.cs | 19 +- 644 files changed, 5956 insertions(+), 5354 deletions(-) diff --git a/src/Umbraco.Core/Cache/ContentCacheRefresher.cs b/src/Umbraco.Core/Cache/ContentCacheRefresher.cs index 5cef496ca9b9..a515d5c5d197 100644 --- a/src/Umbraco.Core/Cache/ContentCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/ContentCacheRefresher.cs @@ -39,7 +39,6 @@ public static void RefreshContentTypes(AppCaches appCaches) // we could try to have a mechanism to notify the PublishedCachesService // and figure out whether published items were modified or not... keep it // simple for now, just clear the whole thing - appCaches.ClearPartialViewCache(); appCaches.IsolatedCaches.ClearCache(); @@ -70,9 +69,10 @@ public override void Refresh(JsonPayload[] payloads) foreach (JsonPayload payload in payloads.Where(x => x.Id != default)) { - //By INT Id + // By INT Id isolatedCache.Clear(RepositoryCacheKeys.GetKey(payload.Id)); - //By GUID Key + + // By GUID Key isolatedCache.Clear(RepositoryCacheKeys.GetKey(payload.Key)); _idKeyMap.ClearCache(payload.Id); @@ -84,7 +84,7 @@ public override void Refresh(JsonPayload[] payloads) isolatedCache.ClearOfType((k, v) => v.Path?.Contains(pathid) ?? false); } - //if the item is being completely removed, we need to refresh the domains cache if any domain was assigned to the content + // if the item is being completely removed, we need to refresh the domains cache if any domain was assigned to the content if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.Remove)) { idsRemoved.Add(payload.Id); @@ -101,8 +101,8 @@ public override void Refresh(JsonPayload[] payloads) // TODO: this is duplicating the logic in DomainCacheRefresher BUT we cannot inject that into this because it it not registered explicitly in the container, // and we cannot inject the CacheRefresherCollection since that would be a circular reference, so what is the best way to call directly in to the // DomainCacheRefresher? - ClearAllIsolatedCacheByEntityType(); + // note: must do what's above FIRST else the repositories still have the old cached // content and when the PublishedCachesService is notified of changes it does not see // the new content... @@ -118,9 +118,8 @@ public override void Refresh(JsonPayload[] payloads) // TODO: what about this? // should rename it, and then, this is only for Deploy, and then, ??? - //if (Suspendable.PageCacheRefresher.CanUpdateDocumentCache) - // ... - + // if (Suspendable.PageCacheRefresher.CanUpdateDocumentCache) + // ... NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, payloads); base.Refresh(payloads); @@ -128,7 +127,6 @@ public override void Refresh(JsonPayload[] payloads) // these events should never trigger // everything should be PAYLOAD/JSON - public override void RefreshAll() => throw new NotSupportedException(); public override void Refresh(int id) => throw new NotSupportedException(); @@ -148,8 +146,7 @@ public override void Refresh(JsonPayload[] payloads) /// /// /// - internal static void NotifyPublishedSnapshotService(IPublishedSnapshotService service, AppCaches appCaches, - JsonPayload[] payloads) + internal static void NotifyPublishedSnapshotService(IPublishedSnapshotService service, AppCaches appCaches, JsonPayload[] payloads) { service.Notify(payloads, out _, out var publishedChanged); @@ -170,7 +167,9 @@ public JsonPayload(int id, Guid? key, TreeChangeTypes changeTypes) } public int Id { get; } + public Guid? Key { get; } + public TreeChangeTypes ChangeTypes { get; } } diff --git a/src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs index c957f4878072..fa863573ac36 100644 --- a/src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs @@ -73,7 +73,6 @@ public override void Refresh(JsonPayload[] payloads) // TODO: refactor // we should NOT directly clear caches here, but instead ask whatever class // is managing the cache to please clear that cache properly - _contentTypeCommonRepository.ClearCache(); // always if (payloads.Any(x => x.ItemType == typeof(IContentType).Name)) @@ -100,14 +99,14 @@ public override void Refresh(JsonPayload[] payloads) } if (payloads.Any(x => x.ItemType == typeof(IContentType).Name)) - // don't try to be clever - refresh all { + // don't try to be clever - refresh all ContentCacheRefresher.RefreshContentTypes(AppCaches); } if (payloads.Any(x => x.ItemType == typeof(IMediaType).Name)) - // don't try to be clever - refresh all { + // don't try to be clever - refresh all MediaCacheRefresher.RefreshMediaTypes(AppCaches); } diff --git a/src/Umbraco.Core/Cache/MemberCacheRefresher.cs b/src/Umbraco.Core/Cache/MemberCacheRefresher.cs index 1673889b6a7a..712833093787 100644 --- a/src/Umbraco.Core/Cache/MemberCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MemberCacheRefresher.cs @@ -1,4 +1,4 @@ -//using Newtonsoft.Json; +// using Newtonsoft.Json; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; @@ -13,10 +13,13 @@ namespace Umbraco.Cms.Core.Cache; public sealed class MemberCacheRefresher : PayloadCacheRefresherBase { + #region Define + + public static readonly Guid UniqueId = Guid.Parse("E285DF34-ACDC-4226-AE32-C0CB5CF388DA"); + private readonly IIdKeyMap _idKeyMap; - public MemberCacheRefresher(AppCaches appCaches, IJsonSerializer serializer, IIdKeyMap idKeyMap, - IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) + public MemberCacheRefresher(AppCaches appCaches, IJsonSerializer serializer, IIdKeyMap idKeyMap, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) : base(appCaches, serializer, eventAggregator, factory) => _idKeyMap = idKeyMap; @@ -28,7 +31,7 @@ public MemberCacheRefresher(AppCaches appCaches, IJsonSerializer serializer, IId public class JsonPayload { - //[JsonConstructor] + // [JsonConstructor] public JsonPayload(int id, string? username, bool removed) { Id = id; @@ -37,14 +40,12 @@ public JsonPayload(int id, string? username, bool removed) } public int Id { get; } + public string? Username { get; } + public bool Removed { get; } } - #region Define - - public static readonly Guid UniqueId = Guid.Parse("E285DF34-ACDC-4226-AE32-C0CB5CF388DA"); - public override Guid RefresherUniqueId => UniqueId; public override string Name => "Member Cache Refresher"; @@ -74,7 +75,7 @@ public override void Remove(int id) private void ClearCache(params JsonPayload[] payloads) { AppCaches.ClearPartialViewCache(); - Attempt memberCache = AppCaches.IsolatedCaches.Get(); + Attempt memberCache = AppCaches.IsolatedCaches.Get(); foreach (JsonPayload p in payloads) { diff --git a/src/Umbraco.Core/Cache/NoAppCache.cs b/src/Umbraco.Core/Cache/NoAppCache.cs index 1796b50a38f2..70edbcf61d79 100644 --- a/src/Umbraco.Core/Cache/NoAppCache.cs +++ b/src/Umbraco.Core/Cache/NoAppCache.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; namespace Umbraco.Cms.Core.Cache; @@ -7,13 +7,18 @@ namespace Umbraco.Cms.Core.Cache; /// public class NoAppCache : IAppPolicyCache, IRequestCache { - protected NoAppCache() { } + protected NoAppCache() + { + } /// /// Gets the singleton instance. /// public static NoAppCache Instance { get; } = new(); + /// + public bool IsAvailable => false; + /// public virtual object? Get(string cacheKey) => null; @@ -27,12 +32,10 @@ protected NoAppCache() { } public IEnumerable SearchByRegex(string regex) => Enumerable.Empty(); /// - public object? Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, - string[]? dependentFiles = null) => factory(); + public object? Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, string[]? dependentFiles = null) => factory(); /// - public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, - string[]? dependentFiles = null) + public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, string[]? dependentFiles = null) { } @@ -71,9 +74,6 @@ public virtual void ClearByRegex(string regex) { } - /// - public bool IsAvailable => false; - public bool Set(string key, object? value) => false; public bool Remove(string key) => false; diff --git a/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs index 430bee607e10..2b662d4c2cde 100644 --- a/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs @@ -1,11 +1,13 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Cache; public class NoCacheRepositoryCachePolicy : IRepositoryCachePolicy where TEntity : class, IEntity { - private NoCacheRepositoryCachePolicy() { } + private NoCacheRepositoryCachePolicy() + { + } public static NoCacheRepositoryCachePolicy Instance { get; } = new(); diff --git a/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs b/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs index b6cb3798e1bd..dcd83ece94d5 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs @@ -16,6 +16,7 @@ public class ObjectCacheAppCache : IAppPolicyCache, IDisposable /// Initializes a new instance of the . /// public ObjectCacheAppCache() => + // the MemoryCache is created with name "in-memory". That name is // used to retrieve configuration options. It does not identify the memory cache, i.e. // each instance of this class has its own, independent, memory cache. @@ -102,11 +103,9 @@ public IEnumerable SearchByRegex(string regex) } /// - public object? Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, - string[]? dependentFiles = null) + public object? Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, string[]? dependentFiles = null) { // see notes in HttpRuntimeAppCache - Lazy? result; try @@ -114,9 +113,9 @@ public IEnumerable SearchByRegex(string regex) _locker.EnterUpgradeableReadLock(); result = MemoryCache.Get(key) as Lazy; - if (result == null || - SafeLazy.GetSafeLazyValue(result, true) == - null) // get non-created as NonCreatedValue & exceptions as null + + // get non-created as NonCreatedValue & exceptions as null + if (result == null || SafeLazy.GetSafeLazyValue(result, true) == null) { result = SafeLazy.GetSafeLazy(factory); CacheItemPolicy policy = GetPolicy(timeout, isSliding, dependentFiles); @@ -124,7 +123,8 @@ public IEnumerable SearchByRegex(string regex) try { _locker.EnterWriteLock(); - //NOTE: This does an add or update + + // NOTE: This does an add or update MemoryCache.Set(key, result, policy); } finally @@ -144,8 +144,7 @@ public IEnumerable SearchByRegex(string regex) } } - //return result.Value; - + // return result.Value; var value = result.Value; // will not throw (safe lazy) if (value is SafeLazy.ExceptionHolder eh) { @@ -156,13 +155,11 @@ public IEnumerable SearchByRegex(string regex) } /// - public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, - string[]? dependentFiles = null) + public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, string[]? dependentFiles = null) { // NOTE - here also we must insert a Lazy but we can evaluate it right now // and make sure we don't store a null value. - - Lazy result = SafeLazy.GetSafeLazy(factory); + Lazy result = SafeLazy.GetSafeLazy(factory); var value = result.Value; // force evaluation now if (value == null) { @@ -170,7 +167,8 @@ public void Insert(string key, Func factory, TimeSpan? timeout = null, } CacheItemPolicy policy = GetPolicy(timeout, isSliding, dependentFiles); - //NOTE: This does an add or update + + // NOTE: This does an add or update MemoryCache.Set(key, result, policy); } @@ -226,6 +224,8 @@ public virtual void ClearOfType(Type type) try { _locker.EnterWriteLock(); + + // ToArray required to remove foreach (var key in MemoryCache .Where(x => { @@ -240,7 +240,7 @@ public virtual void ClearOfType(Type type) (isInterface ? type.IsInstanceOfType(value) : value.GetType() == type); }) .Select(x => x.Key) - .ToArray()) // ToArray required to remove + .ToArray()) { MemoryCache.Remove(key); } @@ -262,6 +262,8 @@ public virtual void ClearOfType() _locker.EnterWriteLock(); Type typeOfT = typeof(T); var isInterface = typeOfT.IsInterface; + + // ToArray required to remove foreach (var key in MemoryCache .Where(x => { @@ -275,7 +277,7 @@ public virtual void ClearOfType() return value == null || (isInterface ? value is T : value.GetType() == typeOfT); }) .Select(x => x.Key) - .ToArray()) // ToArray required to remove + .ToArray()) { MemoryCache.Remove(key); } @@ -297,6 +299,8 @@ public virtual void ClearOfType(Func predicate) _locker.EnterWriteLock(); Type typeOfT = typeof(T); var isInterface = typeOfT.IsInterface; + + // ToArray required to remove foreach (var key in MemoryCache .Where(x => { @@ -315,7 +319,7 @@ public virtual void ClearOfType(Func predicate) && predicate(x.Key, (T)value); }) .Select(x => x.Key) - .ToArray()) // ToArray required to remove + .ToArray()) { MemoryCache.Remove(key); } @@ -335,10 +339,12 @@ public virtual void ClearByKey(string keyStartsWith) try { _locker.EnterWriteLock(); + + // ToArray required to remove foreach (var key in MemoryCache .Where(x => x.Key.InvariantStartsWith(keyStartsWith)) .Select(x => x.Key) - .ToArray()) // ToArray required to remove + .ToArray()) { MemoryCache.Remove(key); } @@ -360,10 +366,12 @@ public virtual void ClearByRegex(string regex) try { _locker.EnterWriteLock(); + + // ToArray required to remove foreach (var key in MemoryCache .Where(x => compiled.IsMatch(x.Key)) .Select(x => x.Key) - .ToArray()) // ToArray required to remove + .ToArray()) { MemoryCache.Remove(key); } @@ -378,11 +386,24 @@ public virtual void ClearByRegex(string regex) } public void Dispose() => + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(true); - private static CacheItemPolicy GetPolicy(TimeSpan? timeout = null, bool isSliding = false, - string[]? dependentFiles = null) + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _locker.Dispose(); + } + + _disposedValue = true; + } + } + + private static CacheItemPolicy GetPolicy(TimeSpan? timeout = null, bool isSliding = false, string[]? dependentFiles = null) { DateTimeOffset absolute = isSliding ? ObjectCache.InfiniteAbsoluteExpiration : timeout == null ? ObjectCache.InfiniteAbsoluteExpiration : DateTime.Now.Add(timeout.Value); @@ -390,7 +411,7 @@ private static CacheItemPolicy GetPolicy(TimeSpan? timeout = null, bool isSlidin ? ObjectCache.NoSlidingExpiration : timeout ?? ObjectCache.NoSlidingExpiration; - var policy = new CacheItemPolicy {AbsoluteExpiration = absolute, SlidingExpiration = sliding}; + var policy = new CacheItemPolicy { AbsoluteExpiration = absolute, SlidingExpiration = sliding }; if (dependentFiles != null && dependentFiles.Any()) { @@ -399,17 +420,4 @@ private static CacheItemPolicy GetPolicy(TimeSpan? timeout = null, bool isSlidin return policy; } - - protected virtual void Dispose(bool disposing) - { - if (!_disposedValue) - { - if (disposing) - { - _locker.Dispose(); - } - - _disposedValue = true; - } - } } diff --git a/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs b/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs index 6bb735fa459e..f371e809793b 100644 --- a/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs +++ b/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs @@ -8,8 +8,8 @@ namespace Umbraco.Cms.Core.Cache; /// /// A base class for "payload" class refreshers. /// -/// The actual cache refresher type. /// The payload type. +/// The notification type /// The actual cache refresher type is used for strongly typed events. public abstract class PayloadCacheRefresherBase : JsonCacheRefresherBase, @@ -21,18 +21,18 @@ public abstract class /// /// A cache helper. /// - protected PayloadCacheRefresherBase(AppCaches appCaches, IJsonSerializer serializer, - IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) + /// + /// + protected PayloadCacheRefresherBase(AppCaches appCaches, IJsonSerializer serializer, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) : base(appCaches, serializer, eventAggregator, factory) { } - #region Refresher public override void Refresh(string json) { - TPayload[] payload = Deserialize(json); + TPayload[]? payload = Deserialize(json); if (payload is not null) { Refresh(payload); diff --git a/src/Umbraco.Core/Cache/PublicAccessCacheRefresher.cs b/src/Umbraco.Core/Cache/PublicAccessCacheRefresher.cs index ac51c7c84030..9124d7350ec3 100644 --- a/src/Umbraco.Core/Cache/PublicAccessCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/PublicAccessCacheRefresher.cs @@ -6,16 +6,15 @@ namespace Umbraco.Cms.Core.Cache; public sealed class PublicAccessCacheRefresher : CacheRefresherBase { - public PublicAccessCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, - ICacheRefresherNotificationFactory factory) - : base(appCaches, eventAggregator, factory) - { - } - #region Define public static readonly Guid UniqueId = Guid.Parse("1DB08769-B104-4F8B-850E-169CAC1DF2EC"); + public PublicAccessCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) + : base(appCaches, eventAggregator, factory) + { + } + public override Guid RefresherUniqueId => UniqueId; public override string Name => "Public Access Cache Refresher"; diff --git a/src/Umbraco.Core/Cache/RelationTypeCacheRefresher.cs b/src/Umbraco.Core/Cache/RelationTypeCacheRefresher.cs index 89669e76bc8a..8da3cd5be0a4 100644 --- a/src/Umbraco.Core/Cache/RelationTypeCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/RelationTypeCacheRefresher.cs @@ -7,24 +7,17 @@ namespace Umbraco.Cms.Core.Cache; public sealed class RelationTypeCacheRefresher : CacheRefresherBase { - public RelationTypeCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, - ICacheRefresherNotificationFactory factory) + public RelationTypeCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) : base(appCaches, eventAggregator, factory) { } - #region Define - public static readonly Guid UniqueId = Guid.Parse("D8375ABA-4FB3-4F86-B505-92FBA1B6F7C9"); public override Guid RefresherUniqueId => UniqueId; public override string Name => "Relation Type Cache Refresher"; - #endregion - - #region Refresher - public override void RefreshAll() { ClearAllIsolatedCacheByEntityType(); @@ -33,7 +26,7 @@ public override void RefreshAll() public override void Refresh(int id) { - Attempt cache = AppCaches.IsolatedCaches.Get(); + Attempt cache = AppCaches.IsolatedCaches.Get(); if (cache.Success) { cache.Result?.Clear(RepositoryCacheKeys.GetKey(id)); @@ -44,10 +37,10 @@ public override void Refresh(int id) public override void Refresh(Guid id) => throw new NotSupportedException(); - //base.Refresh(id); + // base.Refresh(id); public override void Remove(int id) { - Attempt cache = AppCaches.IsolatedCaches.Get(); + Attempt cache = AppCaches.IsolatedCaches.Get(); if (cache.Success) { cache.Result?.Clear(RepositoryCacheKeys.GetKey(id)); @@ -55,6 +48,4 @@ public override void Remove(int id) base.Remove(id); } - - #endregion } diff --git a/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs b/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs index ee02ab105ea4..ba7b251aa0fc 100644 --- a/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs +++ b/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Cache; +namespace Umbraco.Cms.Core.Cache; /// /// Specifies how a repository cache policy should cache entities. diff --git a/src/Umbraco.Core/Cache/SafeLazy.cs b/src/Umbraco.Core/Cache/SafeLazy.cs index 07c70f0be3c6..40512ece67ac 100644 --- a/src/Umbraco.Core/Cache/SafeLazy.cs +++ b/src/Umbraco.Core/Cache/SafeLazy.cs @@ -1,4 +1,4 @@ -using System.Runtime.ExceptionServices; +using System.Runtime.ExceptionServices; namespace Umbraco.Cms.Core.Cache; @@ -8,6 +8,7 @@ public static class SafeLazy internal static readonly object ValueNotCreated = new(); public static Lazy GetSafeLazy(Func getCacheItem) => + // try to generate the value and if it fails, // wrap in an ExceptionHolder - would be much simpler // to just use lazy.IsValueFaulted alas that field is diff --git a/src/Umbraco.Core/Cache/TemplateCacheRefresher.cs b/src/Umbraco.Core/Cache/TemplateCacheRefresher.cs index 025b3e25fd0f..451ef77096a9 100644 --- a/src/Umbraco.Core/Cache/TemplateCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/TemplateCacheRefresher.cs @@ -8,11 +8,18 @@ namespace Umbraco.Cms.Core.Cache; public sealed class TemplateCacheRefresher : CacheRefresherBase { + #region Define + + public static readonly Guid UniqueId = Guid.Parse("DD12B6A0-14B9-46e8-8800-C154F74047C8"); + private readonly IContentTypeCommonRepository _contentTypeCommonRepository; private readonly IIdKeyMap _idKeyMap; - public TemplateCacheRefresher(AppCaches appCaches, IIdKeyMap idKeyMap, - IContentTypeCommonRepository contentTypeCommonRepository, IEventAggregator eventAggregator, + public TemplateCacheRefresher( + AppCaches appCaches, + IIdKeyMap idKeyMap, + IContentTypeCommonRepository contentTypeCommonRepository, + IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) : base(appCaches, eventAggregator, factory) { @@ -20,10 +27,6 @@ public TemplateCacheRefresher(AppCaches appCaches, IIdKeyMap idKeyMap, _contentTypeCommonRepository = contentTypeCommonRepository; } - #region Define - - public static readonly Guid UniqueId = Guid.Parse("DD12B6A0-14B9-46e8-8800-C154F74047C8"); - public override Guid RefresherUniqueId => UniqueId; public override string Name => "Template Cache Refresher"; @@ -42,7 +45,7 @@ public override void Remove(int id) { RemoveFromCache(id); - //During removal we need to clear the runtime cache for templates, content and content type instances!!! + // During removal we need to clear the runtime cache for templates, content and content type instances!!! // all three of these types are referenced by templates, and the cache needs to be cleared on every server, // otherwise things like looking up content type's after a template is removed is still going to show that // it has an associated template. @@ -58,7 +61,7 @@ private void RemoveFromCache(int id) _idKeyMap.ClearCache(id); AppCaches.RuntimeCache.Clear($"{CacheKeys.TemplateFrontEndCacheKey}{id}"); - //need to clear the runtime cache for templates + // need to clear the runtime cache for templates ClearAllIsolatedCacheByEntityType(); } diff --git a/src/Umbraco.Core/Cache/UserCacheRefresher.cs b/src/Umbraco.Core/Cache/UserCacheRefresher.cs index d07c9633cd8c..d1dc194f9b20 100644 --- a/src/Umbraco.Core/Cache/UserCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/UserCacheRefresher.cs @@ -7,16 +7,15 @@ namespace Umbraco.Cms.Core.Cache; public sealed class UserCacheRefresher : CacheRefresherBase { - public UserCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, - ICacheRefresherNotificationFactory factory) - : base(appCaches, eventAggregator, factory) - { - } - #region Define public static readonly Guid UniqueId = Guid.Parse("E057AF6D-2EE6-41F4-8045-3694010F0AA6"); + public UserCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) + : base(appCaches, eventAggregator, factory) + { + } + public override Guid RefresherUniqueId => UniqueId; public override string Name => "User Cache Refresher"; @@ -39,7 +38,7 @@ public override void Refresh(int id) public override void Remove(int id) { - Attempt userCache = AppCaches.IsolatedCaches.Get(); + Attempt userCache = AppCaches.IsolatedCaches.Get(); if (userCache.Success) { userCache.Result?.Clear(RepositoryCacheKeys.GetKey(id)); @@ -49,7 +48,6 @@ public override void Remove(int id) userCache.Result?.ClearByKey(CacheKeys.UserAllMediaStartNodesPrefix + id); } - base.Remove(id); } diff --git a/src/Umbraco.Core/Cache/UserGroupCacheRefresher.cs b/src/Umbraco.Core/Cache/UserGroupCacheRefresher.cs index 9b01266b984b..ccf004a8d7d0 100644 --- a/src/Umbraco.Core/Cache/UserGroupCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/UserGroupCacheRefresher.cs @@ -13,16 +13,15 @@ namespace Umbraco.Cms.Core.Cache; /// public sealed class UserGroupCacheRefresher : CacheRefresherBase { - public UserGroupCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, - ICacheRefresherNotificationFactory factory) - : base(appCaches, eventAggregator, factory) - { - } - #region Define public static readonly Guid UniqueId = Guid.Parse("45178038-B232-4FE8-AA1A-F2B949C44762"); + public UserGroupCacheRefresher(AppCaches appCaches, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) + : base(appCaches, eventAggregator, factory) + { + } + public override Guid RefresherUniqueId => UniqueId; public override string Name => "User Group Cache Refresher"; @@ -34,13 +33,13 @@ public UserGroupCacheRefresher(AppCaches appCaches, IEventAggregator eventAggreg public override void RefreshAll() { ClearAllIsolatedCacheByEntityType(); - Attempt userGroupCache = AppCaches.IsolatedCaches.Get(); + Attempt userGroupCache = AppCaches.IsolatedCaches.Get(); if (userGroupCache.Success) { userGroupCache.Result?.ClearByKey(CacheKeys.UserGroupGetByAliasCacheKeyPrefix); } - //We'll need to clear all user cache too + // We'll need to clear all user cache too ClearAllIsolatedCacheByEntityType(); base.RefreshAll(); @@ -54,14 +53,14 @@ public override void Refresh(int id) public override void Remove(int id) { - Attempt userGroupCache = AppCaches.IsolatedCaches.Get(); + Attempt userGroupCache = AppCaches.IsolatedCaches.Get(); if (userGroupCache.Success) { userGroupCache.Result?.Clear(RepositoryCacheKeys.GetKey(id)); userGroupCache.Result?.ClearByKey(CacheKeys.UserGroupGetByAliasCacheKeyPrefix); } - //we don't know what user's belong to this group without doing a look up so we'll need to just clear them all + // we don't know what user's belong to this group without doing a look up so we'll need to just clear them all ClearAllIsolatedCacheByEntityType(); base.Remove(id); diff --git a/src/Umbraco.Core/Cache/ValueEditorCache.cs b/src/Umbraco.Core/Cache/ValueEditorCache.cs index a5b6034b4edd..358134ab14f3 100644 --- a/src/Umbraco.Core/Cache/ValueEditorCache.cs +++ b/src/Umbraco.Core/Cache/ValueEditorCache.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Cms.Core.Cache; @@ -36,7 +36,7 @@ public IDataValueEditor GetValueEditor(IDataEditor editor, IDataType dataType) } valueEditor = editor.GetValueEditor(dataType.Configuration); - _valueEditorCache[editor.Alias] = new Dictionary {[dataType.Id] = valueEditor}; + _valueEditorCache[editor.Alias] = new Dictionary { [dataType.Id] = valueEditor }; return valueEditor; } } diff --git a/src/Umbraco.Core/Cache/ValueEditorCacheRefresher.cs b/src/Umbraco.Core/Cache/ValueEditorCacheRefresher.cs index 0bcb3796a51e..68ccdea20d1d 100644 --- a/src/Umbraco.Core/Cache/ValueEditorCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/ValueEditorCacheRefresher.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Serialization; @@ -15,10 +15,12 @@ public ValueEditorCacheRefresher( IJsonSerializer serializer, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory, - IValueEditorCache valueEditorCache) : base(appCaches, serializer, eventAggregator, factory) => + IValueEditorCache valueEditorCache) + : base(appCaches, serializer, eventAggregator, factory) => _valueEditorCache = valueEditorCache; public override Guid RefresherUniqueId => UniqueId; + public override string Name => "ValueEditorCacheRefresher"; public override void Refresh(DataTypeCacheRefresher.JsonPayload[] payloads) @@ -29,7 +31,6 @@ public override void Refresh(DataTypeCacheRefresher.JsonPayload[] payloads) // these events should never trigger // everything should be PAYLOAD/JSON - public override void RefreshAll() => throw new NotSupportedException(); public override void Refresh(int id) => throw new NotSupportedException(); diff --git a/src/Umbraco.Core/CodeAnnotations/UmbracoObjectTypeAttribute.cs b/src/Umbraco.Core/CodeAnnotations/UmbracoObjectTypeAttribute.cs index 750757cea5ca..13ec38f892c7 100644 --- a/src/Umbraco.Core/CodeAnnotations/UmbracoObjectTypeAttribute.cs +++ b/src/Umbraco.Core/CodeAnnotations/UmbracoObjectTypeAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.CodeAnnotations; +namespace Umbraco.Cms.Core.CodeAnnotations; /// /// Attribute to associate a GUID string and Type with an UmbracoObjectType Enum value diff --git a/src/Umbraco.Core/CodeAnnotations/UmbracoUdiTypeAttribute.cs b/src/Umbraco.Core/CodeAnnotations/UmbracoUdiTypeAttribute.cs index 6c40fe8f35d1..90df3185c677 100644 --- a/src/Umbraco.Core/CodeAnnotations/UmbracoUdiTypeAttribute.cs +++ b/src/Umbraco.Core/CodeAnnotations/UmbracoUdiTypeAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.CodeAnnotations; +namespace Umbraco.Cms.Core.CodeAnnotations; [AttributeUsage(AttributeTargets.Field)] public class UmbracoUdiTypeAttribute : Attribute diff --git a/src/Umbraco.Core/Collections/OrderedHashSet.cs b/src/Umbraco.Core/Collections/OrderedHashSet.cs index 367ae7546ae7..d23c81a7b27d 100644 --- a/src/Umbraco.Core/Collections/OrderedHashSet.cs +++ b/src/Umbraco.Core/Collections/OrderedHashSet.cs @@ -1,4 +1,4 @@ -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; namespace Umbraco.Cms.Core.Collections; @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Collections; /// and is customizable to keep the newest or oldest equatable item /// /// -public class OrderedHashSet : KeyedCollection where T : notnull +public class OrderedHashSet : KeyedCollection + where T : notnull { private readonly bool _keepOldest; @@ -24,7 +25,7 @@ protected override void InsertItem(int index, T item) { var exists = Dictionary.ContainsKey(item); - //if we want to keep the newest, then we need to remove the old item and add the new one + // if we want to keep the newest, then we need to remove the old item and add the new one if (exists == false) { base.InsertItem(index, item); diff --git a/src/Umbraco.Core/Collections/StackQueue.cs b/src/Umbraco.Core/Collections/StackQueue.cs index 7d4d8a56dbd5..2324eec892ec 100644 --- a/src/Umbraco.Core/Collections/StackQueue.cs +++ b/src/Umbraco.Core/Collections/StackQueue.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Collections; +namespace Umbraco.Cms.Core.Collections; /// /// Collection that can be both a queue and a stack. diff --git a/src/Umbraco.Core/Collections/TopoGraph.cs b/src/Umbraco.Core/Collections/TopoGraph.cs index 999cfef6ed55..fd2161c6d3a4 100644 --- a/src/Umbraco.Core/Collections/TopoGraph.cs +++ b/src/Umbraco.Core/Collections/TopoGraph.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Collections; +namespace Umbraco.Cms.Core.Collections; public class TopoGraph { @@ -18,7 +18,9 @@ public Node(TKey key, TItem item, IEnumerable dependencies) } public TKey Key { get; } + public TItem Item { get; } + public IEnumerable Dependencies { get; } } } @@ -93,8 +95,7 @@ public IEnumerable GetSortedItems(bool throwOnCycle = true, bool throwOnM private static bool Contains(TItem[] items, TItem item, int start, int count) => Array.IndexOf(items, item, start, count) >= 0; - private void Visit(TItem item, ISet visited, TItem[] sorted, ref int index, int incr, bool throwOnCycle, - bool throwOnMissing) + private void Visit(TItem item, ISet visited, TItem[] sorted, ref int index, int incr, bool throwOnCycle, bool throwOnMissing) { if (visited.Contains(item)) { @@ -111,8 +112,8 @@ private void Visit(TItem item, ISet visited, TItem[] sorted, ref int inde visited.Add(item); - IEnumerable keys = _getDependencies(item); - IEnumerable dependencies = keys == null ? null : FindDependencies(keys, throwOnMissing); + IEnumerable? keys = _getDependencies(item); + IEnumerable? dependencies = keys == null ? null : FindDependencies(keys, throwOnMissing); if (dependencies != null) { @@ -130,8 +131,7 @@ private IEnumerable FindDependencies(IEnumerable keys, bool throwOn { foreach (TKey key in keys) { - TItem? value; - if (_items.TryGetValue(key, out value)) + if (_items.TryGetValue(key, out TItem? value)) { yield return value; } diff --git a/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs index 7842f93da6e8..c724f8267bc8 100644 --- a/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Composing; +namespace Umbraco.Cms.Core.Composing; /// /// Implements an ordered collection builder. diff --git a/src/Umbraco.Core/Composing/ReferenceResolver.cs b/src/Umbraco.Core/Composing/ReferenceResolver.cs index b8676082cc82..1924fb4b75be 100644 --- a/src/Umbraco.Core/Composing/ReferenceResolver.cs +++ b/src/Umbraco.Core/Composing/ReferenceResolver.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Reflection; using System.Security; using Microsoft.Extensions.Logging; @@ -20,8 +20,7 @@ internal class ReferenceResolver private readonly List _lookup = new(); private readonly HashSet _umbracoAssemblies; - public ReferenceResolver(IReadOnlyList targetAssemblies, IReadOnlyList entryPointAssemblies, - ILogger logger) + public ReferenceResolver(IReadOnlyList targetAssemblies, IReadOnlyList entryPointAssemblies, ILogger logger) { _umbracoAssemblies = new HashSet(targetAssemblies, StringComparer.Ordinal); _assemblies = entryPointAssemblies; @@ -34,6 +33,14 @@ public ReferenceResolver(IReadOnlyList targetAssemblies, IReadOnlyList /// Returns a list of assemblies that directly reference or transitively reference the targetAssemblies /// @@ -108,6 +115,29 @@ public IEnumerable ResolveAssemblies() return applicationParts; } + protected virtual IEnumerable GetReferences(Assembly assembly) + { + foreach (AssemblyName referenceName in assembly.GetReferencedAssemblies()) + { + // don't include if this is excluded + if (TypeFinder.KnownAssemblyExclusionFilter.Any(f => + referenceName.FullName.StartsWith(f, StringComparison.InvariantCultureIgnoreCase))) + { + continue; + } + + var reference = Assembly.Load(referenceName); + + if (!_lookup.Contains(reference)) + { + // A dependency references an item that isn't referenced by this project. + // We'll add this reference so that we can calculate the classification. + _lookup.Add(reference); + } + + yield return reference; + } + } private IEnumerable GetAssemblyFolders(IEnumerable assemblies) => assemblies.Select(x => Path.GetDirectoryName(GetAssemblyLocation(x))).Distinct(); @@ -115,7 +145,7 @@ public IEnumerable ResolveAssemblies() // borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Core/src/ApplicationParts/RelatedAssemblyAttribute.cs private string GetAssemblyLocation(Assembly assembly) { - if (Uri.TryCreate(assembly.CodeBase, UriKind.Absolute, out Uri result) && + if (Uri.TryCreate(assembly.Location, UriKind.Absolute, out Uri? result) && result.IsFile && string.IsNullOrWhiteSpace(result.Fragment)) { return result.LocalPath; @@ -166,37 +196,4 @@ private Classification Resolve(Assembly assembly) _classifications[assembly] = classification; return classification; } - - protected virtual IEnumerable GetReferences(Assembly assembly) - { - foreach (AssemblyName referenceName in assembly.GetReferencedAssemblies()) - { - // don't include if this is excluded - if (TypeFinder.KnownAssemblyExclusionFilter.Any(f => - referenceName.FullName.StartsWith(f, StringComparison.InvariantCultureIgnoreCase))) - { - continue; - } - - var reference = Assembly.Load(referenceName); - - if (!_lookup.Contains(reference)) - { - // A dependency references an item that isn't referenced by this project. - // We'll add this reference so that we can calculate the classification. - - _lookup.Add(reference); - } - - yield return reference; - } - } - - protected enum Classification - { - Unknown, - DoesNotReferenceUmbraco, - ReferencesUmbraco, - IsUmbraco - } } diff --git a/src/Umbraco.Core/Composing/RuntimeHash.cs b/src/Umbraco.Core/Composing/RuntimeHash.cs index 38f0f10f6bec..e66bedf79fff 100644 --- a/src/Umbraco.Core/Composing/RuntimeHash.cs +++ b/src/Umbraco.Core/Composing/RuntimeHash.cs @@ -18,7 +18,6 @@ public RuntimeHash(IProfilingLogger logger, RuntimeHashPaths paths) _paths = paths; } - public string GetHashValue() { if (_calculated != null) diff --git a/src/Umbraco.Core/Composing/RuntimeHashPaths.cs b/src/Umbraco.Core/Composing/RuntimeHashPaths.cs index e94494bf36f0..5720fdebe2e6 100644 --- a/src/Umbraco.Core/Composing/RuntimeHashPaths.cs +++ b/src/Umbraco.Core/Composing/RuntimeHashPaths.cs @@ -38,5 +38,6 @@ public RuntimeHashPaths AddAssemblies(IAssemblyProvider assemblyProvider) public void AddFile(FileInfo fileInfo, bool scanFileContent = false) => _files.Add(fileInfo, scanFileContent); public IEnumerable GetFolders() => _paths; + public IReadOnlyDictionary GetFiles() => _files; } diff --git a/src/Umbraco.Core/Composing/SetCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/SetCollectionBuilderBase.cs index 4d64529ce007..5ca76024d147 100644 --- a/src/Umbraco.Core/Composing/SetCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/SetCollectionBuilderBase.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Composing; +namespace Umbraco.Cms.Core.Composing; /// /// Implements an un-ordered collection builder. diff --git a/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs index a5931e860159..072a9d99e300 100644 --- a/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/TypeCollectionBuilderBase.cs @@ -21,6 +21,12 @@ public TCollection CreateCollection(IServiceProvider factory) public void RegisterWith(IServiceCollection services) => services.Add(new ServiceDescriptor(typeof(TCollection), CreateCollection, ServiceLifetime.Singleton)); + public TBuilder Add(Type type) + { + _types.Add(Validate(type, "add")); + return This; + } + private static Type Validate(Type type, string action) { if (!typeof(TConstraint).IsAssignableFrom(type)) @@ -32,12 +38,6 @@ private static Type Validate(Type type, string action) return type; } - public TBuilder Add(Type type) - { - _types.Add(Validate(type, "add")); - return This; - } - public TBuilder Add() { Add(typeof(T)); diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index 238f609822b7..3ac826880ced 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -11,8 +11,6 @@ namespace Umbraco.Cms.Core.Composing; /// public class TypeFinder : ITypeFinder { - private static readonly ConcurrentDictionary s_typeNamesCache = new(); - // TODO: Kill this /// @@ -36,9 +34,11 @@ public class TypeFinder : ITypeFinder "ServiceStack.", "SqlCE4Umbraco,", "Superpower,", // used by Serilog "System.", "TidyNet,", "TidyNet.", "WebDriver,", "itextsharp,", "mscorlib,", "NUnit,", "NUnit.", "NUnit3.", "Selenium.", "ImageProcessor", "MiniProfiler.", "Owin,", "SQLite", - "ReSharperTestRunner32" // used by resharper testrunner + "ReSharperTestRunner32", // used by resharper testrunner }; + private static readonly ConcurrentDictionary TypeNamesCache = new(); + private readonly IAssemblyProvider _assemblyProvider; private readonly object _localFilteredAssemblyCacheLocker = new(); private readonly ILogger _logger; @@ -49,14 +49,32 @@ public class TypeFinder : ITypeFinder private string[]? _assembliesAcceptingLoadExceptions; private volatile HashSet? _localFilteredAssemblyCache; - public TypeFinder(ILogger logger, IAssemblyProvider assemblyProvider, - ITypeFinderConfig? typeFinderConfig = null) + public TypeFinder(ILogger logger, IAssemblyProvider assemblyProvider, ITypeFinderConfig? typeFinderConfig = null) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _assemblyProvider = assemblyProvider; _typeFinderConfig = typeFinderConfig; } + /// + public IEnumerable AssembliesToScan + { + get + { + lock (_localFilteredAssemblyCacheLocker) + { + if (_localFilteredAssemblyCache != null) + { + return _localFilteredAssemblyCache; + } + + IEnumerable assemblies = GetFilteredAssemblies(null, KnownAssemblyExclusionFilter); + _localFilteredAssemblyCache = new HashSet(assemblies); + return _localFilteredAssemblyCache; + } + } + } + // used for benchmark tests internal bool QueryWithReferencingAssemblies { get; set; } = true; @@ -77,25 +95,6 @@ private string[] AssembliesAcceptingLoadExceptions } } - /// - public IEnumerable AssembliesToScan - { - get - { - lock (_localFilteredAssemblyCacheLocker) - { - if (_localFilteredAssemblyCache != null) - { - return _localFilteredAssemblyCache; - } - - IEnumerable assemblies = GetFilteredAssemblies(null, KnownAssemblyExclusionFilter); - _localFilteredAssemblyCache = new HashSet(assemblies); - return _localFilteredAssemblyCache; - } - } - } - /// /// Finds any classes derived from the assignTypeFrom Type that contain the attribute TAttribute /// @@ -113,7 +112,8 @@ public IEnumerable FindClassesOfTypeWithAttribute( IEnumerable assemblyList = assemblies ?? AssembliesToScan; return GetClassesWithBaseType(assignTypeFrom, assemblyList, onlyConcreteClasses, - //the additional filter will ensure that any found types also have the attribute applied. + + // the additional filter will ensure that any found types also have the attribute applied. t => t.GetCustomAttributes(attributeType, false).Any()); } @@ -124,8 +124,7 @@ public IEnumerable FindClassesOfTypeWithAttribute( /// /// /// - public IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable? assemblies = null, - bool onlyConcreteClasses = true) + public IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable? assemblies = null, bool onlyConcreteClasses = true) { IEnumerable assemblyList = assemblies ?? AssembliesToScan; @@ -156,9 +155,8 @@ public IEnumerable FindClassesWithAttribute( /// public virtual Type? GetTypeByName(string name) { - //NOTE: This will not find types in dynamic assemblies unless those assemblies are already loaded - //into the appdomain. - + // NOTE: This will not find types in dynamic assemblies unless those assemblies are already loaded + // into the appdomain. // This is exactly what the BuildManager does, if the type is an assembly qualified type // name it will find it. @@ -168,12 +166,16 @@ public IEnumerable FindClassesWithAttribute( } // It didn't parse, so try loading from each already loaded assembly and cache it - return s_typeNamesCache.GetOrAdd(name, s => + return TypeNamesCache.GetOrAdd(name, s => AppDomain.CurrentDomain.GetAssemblies() .Select(x => x.GetType(s)) .FirstOrDefault(x => x != null)); } + #region Private methods + + // borrowed from aspnet System.Web.UI.Util + private static bool TypeNameContainsAssembly(string typeName) => CommaIndexInTypeName(typeName) > 0; private bool AcceptsLoadExceptions(Assembly a) { @@ -209,7 +211,6 @@ private bool AcceptsLoadExceptions(Assembly a) }); } - private IEnumerable GetAllAssemblies() => _assemblyProvider.Assemblies; /// @@ -235,15 +236,9 @@ private IEnumerable GetFilteredAssemblies( return GetAllAssemblies() .Where(x => excludeFromResults.Contains(x) == false - && x.GlobalAssemblyCache == false && exclusionFilter.Any(f => x.FullName?.StartsWith(f) ?? false) == false); } - #region Private methods - - // borrowed from aspnet System.Web.UI.Util - private static bool TypeNameContainsAssembly(string typeName) => CommaIndexInTypeName(typeName) > 0; - // borrowed from aspnet System.Web.UI.Util private static int CommaIndexInTypeName(string typeName) { @@ -262,6 +257,15 @@ private static int CommaIndexInTypeName(string typeName) return typeName.IndexOf(',', num2 + 1); } + private static void AppendCouldNotLoad(StringBuilder sb, Assembly a, bool getAll) + { + sb.Append("Could not load "); + sb.Append(getAll ? "all" : "exported"); + sb.Append(" types from \""); + sb.Append(a.FullName); + sb.AppendLine("\" due to LoaderExceptions, skipping:"); + } + private IEnumerable GetClassesWithAttribute( Type attributeType, IEnumerable assemblies, @@ -303,7 +307,8 @@ private IEnumerable GetClassesWithAttribute( } catch (TypeLoadException ex) { - _logger.LogError(ex, + _logger.LogError( + ex, "Could not query types on {Assembly} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", assembly); continue; @@ -388,7 +393,8 @@ private IEnumerable GetClassesWithBaseType( } catch (TypeLoadException ex) { - _logger.LogError(ex, + _logger.LogError( + ex, "Could not query types on {Assembly} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", assembly); continue; @@ -423,7 +429,7 @@ private IEnumerable GetClassesWithBaseType( private IEnumerable GetTypesWithFormattedException(Assembly a) { - //if the assembly is dynamic, do not try to scan it + // if the assembly is dynamic, do not try to scan it if (a.IsDynamic) { return Enumerable.Empty(); @@ -433,20 +439,24 @@ private IEnumerable GetTypesWithFormattedException(Assembly a) try { - //we need to detect if an assembly is partially trusted, if so we cannot go interrogating all of it's types - //only its exported types, otherwise we'll get exceptions. + // we need to detect if an assembly is partially trusted, if so we cannot go interrogating all of it's types + // only its exported types, otherwise we'll get exceptions. return getAll ? a.GetTypes() : a.GetExportedTypes(); } - catch (TypeLoadException ex) // GetExportedTypes *can* throw TypeLoadException! + + // GetExportedTypes *can* throw TypeLoadException! + catch (TypeLoadException ex) { var sb = new StringBuilder(); AppendCouldNotLoad(sb, a, getAll); AppendLoaderException(sb, ex); // rethrow as ReflectionTypeLoadException (for consistency) with new message - throw new ReflectionTypeLoadException(new Type[0], new Exception[] {ex}, sb.ToString()); + throw new ReflectionTypeLoadException(new Type[0], new Exception[] { ex }, sb.ToString()); } - catch (ReflectionTypeLoadException rex) // GetTypes throws ReflectionTypeLoadException + + // GetTypes throws ReflectionTypeLoadException + catch (ReflectionTypeLoadException rex) { var sb = new StringBuilder(); AppendCouldNotLoad(sb, a, getAll); @@ -477,15 +487,6 @@ private IEnumerable GetTypesWithFormattedException(Assembly a) } } - private static void AppendCouldNotLoad(StringBuilder sb, Assembly a, bool getAll) - { - sb.Append("Could not load "); - sb.Append(getAll ? "all" : "exported"); - sb.Append(" types from \""); - sb.Append(a.FullName); - sb.AppendLine("\" due to LoaderExceptions, skipping:"); - } - private static void AppendLoaderException(StringBuilder sb, Exception loaderException) { sb.Append(". "); diff --git a/src/Umbraco.Core/Composing/TypeFinderExtensions.cs b/src/Umbraco.Core/Composing/TypeFinderExtensions.cs index 8524be300f3a..c67d9357164f 100644 --- a/src/Umbraco.Core/Composing/TypeFinderExtensions.cs +++ b/src/Umbraco.Core/Composing/TypeFinderExtensions.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using Umbraco.Cms.Core.Composing; namespace Umbraco.Extensions; @@ -14,8 +14,10 @@ public static class TypeFinderExtensions /// /// /// - public static IEnumerable FindClassesOfTypeWithAttribute(this ITypeFinder typeFinder, - IEnumerable? assemblies = null, bool onlyConcreteClasses = true) + public static IEnumerable FindClassesOfTypeWithAttribute( + this ITypeFinder typeFinder, + IEnumerable? assemblies = null, + bool onlyConcreteClasses = true) where TAttribute : Attribute => typeFinder.FindClassesOfTypeWithAttribute(typeof(T), typeof(TAttribute), assemblies, onlyConcreteClasses); @@ -27,8 +29,10 @@ public static IEnumerable FindClassesOfTypeWithAttribute(th /// /// /// - public static IEnumerable FindClassesOfType(this ITypeFinder typeFinder, - IEnumerable? assemblies = null, bool onlyConcreteClasses = true) + public static IEnumerable FindClassesOfType( + this ITypeFinder typeFinder, + IEnumerable? assemblies = null, + bool onlyConcreteClasses = true) => typeFinder.FindClassesOfType(typeof(T), assemblies, onlyConcreteClasses); /// @@ -39,8 +43,10 @@ public static IEnumerable FindClassesOfType(this ITypeFinder typeFinder /// The assemblies. /// if set to true only concrete classes. /// - public static IEnumerable FindClassesWithAttribute(this ITypeFinder typeFinder, - IEnumerable? assemblies = null, bool onlyConcreteClasses = true) + public static IEnumerable FindClassesWithAttribute( + this ITypeFinder typeFinder, + IEnumerable? assemblies = null, + bool onlyConcreteClasses = true) where T : Attribute => typeFinder.FindClassesWithAttribute(typeof(T), assemblies, onlyConcreteClasses); } diff --git a/src/Umbraco.Core/Composing/TypeHelper.cs b/src/Umbraco.Core/Composing/TypeHelper.cs index 5f604296b234..6cb5426f77fd 100644 --- a/src/Umbraco.Core/Composing/TypeHelper.cs +++ b/src/Umbraco.Core/Composing/TypeHelper.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using System.Collections.Concurrent; using System.Collections.ObjectModel; using System.Reflection; @@ -19,7 +19,6 @@ private static readonly ConcurrentDictionary, Prop private static readonly Assembly[] EmptyAssemblies = new Assembly[0]; - /// /// Based on a type we'll check if it is IEnumerable{T} (or similar) and if so we'll return a List{T}, this will also /// deal with array types and return List{T} for those too. @@ -43,24 +42,27 @@ private static readonly ConcurrentDictionary, Prop || genericTypeDef == typeof(Collection<>) || genericTypeDef == typeof(IList<>) || genericTypeDef == typeof(List<>) - //this will occur when Linq is used and we get the odd WhereIterator or DistinctIterators since those are special iterator types + + // this will occur when Linq is used and we get the odd WhereIterator or DistinctIterators since those are special iterator types || obj is IEnumerable) { - //if it is a IEnumerable<>, IList or ICollection<> we'll use a List<> + // if it is a IEnumerable<>, IList or ICollection<> we'll use a List<> Type genericType = typeof(List<>).MakeGenericType(type.GetGenericArguments()); - //pass in obj to fill the list + + // pass in obj to fill the list return (IList?)Activator.CreateInstance(genericType, obj); } } if (type.IsArray) { - //if its an array, we'll use a List<> - Type typeArguments = type.GetElementType(); + // if its an array, we'll use a List<> + Type? typeArguments = type.GetElementType(); if (typeArguments is not null) { Type genericType = typeof(List<>).MakeGenericType(typeArguments); - //pass in obj to fill the list + + // pass in obj to fill the list return (IList?)Activator.CreateInstance(genericType, obj); } } @@ -92,7 +94,6 @@ public static IReadOnlyList GetReferencingAssemblies(Assembly assembly return EmptyAssemblies; } - // find all assembly references that are referencing the current type's assembly since we // should only be scanning those assemblies because any other assembly will definitely not // contain sub type's of the one we're currently looking for @@ -151,7 +152,7 @@ public static bool HasReference(Assembly assembly, string name) { if (types.Length == 0) { - return Attempt.Fail(); + return Attempt.Fail(); } if (types.Length == 1) @@ -161,12 +162,12 @@ public static bool HasReference(Assembly assembly, string name) foreach (Type curr in types) { - IEnumerable others = types.Except(new[] {curr}); + IEnumerable others = types.Except(new[] { curr }); - //is the current type a common denominator for all others ? + // is the current type a common denominator for all others ? var isBase = others.All(curr.IsAssignableFrom); - //if this type is the base for all others + // if this type is the base for all others if (isBase) { return Attempt.Succeed(curr); @@ -239,7 +240,9 @@ public static bool IsImplicitValueType(Type implementation) => /// /// /// - public static PropertyInfo? GetProperty(Type type, string name, + public static PropertyInfo? GetProperty( + Type type, + string name, bool mustRead = true, bool mustWrite = true, bool includeIndexed = false, @@ -270,8 +273,7 @@ public static FieldInfo[] CachedDiscoverableFields(Type type) => /// true if the properties discovered are writable /// true if the properties discovered are indexable /// - public static PropertyInfo[] CachedDiscoverableProperties(Type type, bool mustRead = true, bool mustWrite = true, - bool includeIndexed = false) => + public static PropertyInfo[] CachedDiscoverableProperties(Type type, bool mustRead = true, bool mustWrite = true, bool includeIndexed = false) => GetPropertiesCache.GetOrAdd( new Tuple(type, mustRead, mustWrite, includeIndexed), x => type @@ -281,6 +283,9 @@ public static PropertyInfo[] CachedDiscoverableProperties(Type type, bool mustRe && (includeIndexed || y.GetIndexParameters().Any() == false)) .ToArray()); + public static bool MatchType(Type implementation, Type contract) => + MatchType(implementation, contract, new Dictionary()); + #region Match Type // TODO: Need to determine if these methods should replace/combine/merge etc with IsTypeAssignableFrom, IsAssignableFromGeneric @@ -291,13 +296,11 @@ public static PropertyInfo[] CachedDiscoverableProperties(Type type, bool mustRe // http://stackoverflow.com/questions/8401738/c-sharp-casting-generics-covariance-and-contravariance // http://stackoverflow.com/questions/1827425/how-to-check-programatically-if-a-type-is-a-struct-or-a-class // http://stackoverflow.com/questions/74616/how-to-detect-if-type-is-another-generic-type/1075059#1075059 - private static bool MatchGeneric(Type implementation, Type contract, IDictionary bindings) { // trying to match eg List with List // or List>> with List>> // classes are NOT invariant so List does not match List - if (implementation.IsGenericType == false) { return false; @@ -334,11 +337,7 @@ private static bool MatchGeneric(Type implementation, Type contract, IDictionary return true; } - public static bool MatchType(Type implementation, Type contract) => - MatchType(implementation, contract, new Dictionary()); - - public static bool MatchType(Type implementation, Type contract, IDictionary bindings, - bool variance = true) + public static bool MatchType(Type implementation, Type contract, IDictionary bindings, bool variance = true) { if (contract.IsGenericType) { @@ -359,7 +358,7 @@ public static bool MatchType(Type implementation, Type contract, IDictionary - if (bindings.ContainsKey(contract.Name)) { // already bound: ensure it's compatible @@ -394,7 +392,6 @@ public static bool MatchType(Type implementation, Type contract, IDictionary GetTypes(bool cache = true, IEnumerable? s { // warn _logger.LogDebug( - "Running a full, " + (cache ? "" : "non-") + - "cached, scan for non-discoverable type {TypeName} (slow).", typeof(T).FullName); + "Running a full, " + (cache ? string.Empty : "non-") + + "cached, scan for non-discoverable type {TypeName} (slow).", + typeof(T).FullName); return GetTypesInternal( - typeof(T), null, + typeof(T), + null, () => TypeFinder.FindClassesOfType(specificAssemblies ?? AssembliesToScan), "scanning assemblies", cache); @@ -203,7 +205,8 @@ public IEnumerable GetTypes(bool cache = true, IEnumerable? s // get IDiscoverable and always cache IEnumerable discovered = GetTypesInternal( - typeof(IDiscoverable), null, + typeof(IDiscoverable), + null, () => TypeFinder.FindClassesOfType(AssembliesToScan), "scanning assemblies", true); @@ -211,15 +214,16 @@ public IEnumerable GetTypes(bool cache = true, IEnumerable? s // warn if (!cache) { - _logger.LogDebug("Running a non-cached, filter for discoverable type {TypeName} (slowish).", + _logger.LogDebug( + "Running a non-cached, filter for discoverable type {TypeName} (slowish).", typeof(T).FullName); } // filter the cached discovered types (and maybe cache the result) return GetTypesInternal( - typeof(T), null, - () => discovered - .Where(x => typeof(T).IsAssignableFrom(x)), + typeof(T), + null, + () => discovered.Where(x => typeof(T).IsAssignableFrom(x)), "filtering IDiscoverable", cache); } @@ -233,7 +237,8 @@ public IEnumerable GetTypes(bool cache = true, IEnumerable? s /// A set of assemblies for type resolution. /// All class types inheriting from or implementing the specified type and marked with the specified attribute. /// Caching is disabled when using specific assemblies. - public IEnumerable GetTypesWithAttribute(bool cache = true, + public IEnumerable GetTypesWithAttribute( + bool cache = true, IEnumerable? specificAssemblies = null) where TAttribute : Attribute { @@ -249,12 +254,14 @@ public IEnumerable GetTypesWithAttribute(bool cache = true, if (!typeof(IDiscoverable).IsAssignableFrom(typeof(T))) { _logger.LogDebug( - "Running a full, " + (cache ? "" : "non-") + + "Running a full, " + (cache ? string.Empty : "non-") + "cached, scan for non-discoverable type {TypeName} / attribute {AttributeName} (slow).", - typeof(T).FullName, typeof(TAttribute).FullName); + typeof(T).FullName, + typeof(TAttribute).FullName); return GetTypesInternal( - typeof(T), typeof(TAttribute), + typeof(T), + typeof(TAttribute), () => TypeFinder.FindClassesOfTypeWithAttribute(specificAssemblies ?? AssembliesToScan), "scanning assemblies", cache); @@ -262,7 +269,8 @@ public IEnumerable GetTypesWithAttribute(bool cache = true, // get IDiscoverable and always cache IEnumerable discovered = GetTypesInternal( - typeof(IDiscoverable), null, + typeof(IDiscoverable), + null, () => TypeFinder.FindClassesOfType(AssembliesToScan), "scanning assemblies", true); @@ -272,12 +280,14 @@ public IEnumerable GetTypesWithAttribute(bool cache = true, { _logger.LogDebug( "Running a non-cached, filter for discoverable type {TypeName} / attribute {AttributeName} (slowish).", - typeof(T).FullName, typeof(TAttribute).FullName); + typeof(T).FullName, + typeof(TAttribute).FullName); } // filter the cached discovered types (and maybe cache the result) return GetTypesInternal( - typeof(T), typeof(TAttribute), + typeof(T), + typeof(TAttribute), () => discovered .Where(x => typeof(T).IsAssignableFrom(x)) .Where(x => x.GetCustomAttributes(false).Any()), @@ -293,7 +303,8 @@ public IEnumerable GetTypesWithAttribute(bool cache = true, /// A set of assemblies for type resolution. /// All class types marked with the specified attribute. /// Caching is disabled when using specific assemblies. - public IEnumerable GetAttributedTypes(bool cache = true, + public IEnumerable GetAttributedTypes( + bool cache = true, IEnumerable? specificAssemblies = null) where TAttribute : Attribute { @@ -307,17 +318,26 @@ public IEnumerable GetAttributedTypes(bool cache = true, if (!cache) { - _logger.LogDebug("Running a full, non-cached, scan for types / attribute {AttributeName} (slow).", + _logger.LogDebug( + "Running a full, non-cached, scan for types / attribute {AttributeName} (slow).", typeof(TAttribute).FullName); } return GetTypesInternal( - typeof(object), typeof(TAttribute), + typeof(object), + typeof(TAttribute), () => TypeFinder.FindClassesWithAttribute(specificAssemblies ?? AssembliesToScan), "scanning assemblies", cache); } + private static string GetName(Type? baseType, Type? attributeType) + { + var s = attributeType == null ? string.Empty : "[" + attributeType + "]"; + s += baseType; + return s; + } + private IEnumerable GetTypesInternal( Type baseType, Type? attributeType, @@ -329,20 +349,12 @@ private IEnumerable GetTypesInternal( // lock at a time, and we don't have non-upgradeable readers, and quite probably the type // loader is mostly not going to be used in any kind of massively multi-threaded scenario - so, // a plain lock is enough - lock (_locko) { return GetTypesInternalLocked(baseType, attributeType, finder, action, cache); } } - private static string GetName(Type? baseType, Type? attributeType) - { - var s = attributeType == null ? string.Empty : "[" + attributeType + "]"; - s += baseType; - return s; - } - private IEnumerable GetTypesInternalLocked( Type? baseType, Type? attributeType, @@ -388,8 +400,7 @@ private IEnumerable GetTypesInternalLocked( _types[listKey] = typeList; } - _logger.LogDebug("Got {TypeName}, caching ({CacheType}).", GetName(baseType, attributeType), - added.ToString().ToLowerInvariant()); + _logger.LogDebug("Got {TypeName}, caching ({CacheType}).", GetName(baseType, attributeType), added.ToString().ToLowerInvariant()); } else { @@ -418,6 +429,7 @@ public TypeList(Type? baseType, Type? attributeType) } public Type? BaseType { get; } + public Type? AttributeType { get; } /// @@ -432,7 +444,8 @@ public void Add(Type type) { if (BaseType?.IsAssignableFrom(type) == false) { - throw new ArgumentException("Base type " + BaseType + " is not assignable from type " + type + ".", + throw new ArgumentException( + "Base type " + BaseType + " is not assignable from type " + type + ".", nameof(type)); } diff --git a/src/Umbraco.Core/Composing/VaryingRuntimeHash.cs b/src/Umbraco.Core/Composing/VaryingRuntimeHash.cs index 61145d76313a..740921974db2 100644 --- a/src/Umbraco.Core/Composing/VaryingRuntimeHash.cs +++ b/src/Umbraco.Core/Composing/VaryingRuntimeHash.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Composing; +namespace Umbraco.Cms.Core.Composing; /// /// A runtime hash this is always different on each app startup diff --git a/src/Umbraco.Core/Composing/WeightAttribute.cs b/src/Umbraco.Core/Composing/WeightAttribute.cs index cb5c9c122c2c..a69ca4636e90 100644 --- a/src/Umbraco.Core/Composing/WeightAttribute.cs +++ b/src/Umbraco.Core/Composing/WeightAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Composing; +namespace Umbraco.Cms.Core.Composing; /// /// Specifies the weight of pretty much anything. diff --git a/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs index 3af7902a3656..9d1062fd53a9 100644 --- a/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Composing; +namespace Umbraco.Cms.Core.Composing; /// /// Implements a weighted collection builder. @@ -12,10 +12,11 @@ public abstract class where TCollection : class, IBuilderCollection { private readonly Dictionary _customWeights = new(); - protected abstract TBuilder This { get; } public virtual int DefaultWeight { get; set; } = 100; + protected abstract TBuilder This { get; } + /// /// Clears all types in the collection. /// @@ -128,7 +129,8 @@ public TBuilder Remove(Type type) /// The type of item /// The new weight /// - public TBuilder SetWeight(int weight) where T : TItem + public TBuilder SetWeight(int weight) + where T : TItem { _customWeights[typeof(T)] = weight; return This; @@ -148,7 +150,7 @@ protected virtual int GetWeight(Type type) return _customWeights[type]; } - WeightAttribute attr = type.GetCustomAttributes(typeof(WeightAttribute), false).OfType() + WeightAttribute? attr = type.GetCustomAttributes(typeof(WeightAttribute), false).OfType() .SingleOrDefault(); return attr?.Weight ?? DefaultWeight; } diff --git a/src/Umbraco.Core/Configuration/MemberPasswordConfiguration.cs b/src/Umbraco.Core/Configuration/MemberPasswordConfiguration.cs index b800dfc66df4..33471ced160f 100644 --- a/src/Umbraco.Core/Configuration/MemberPasswordConfiguration.cs +++ b/src/Umbraco.Core/Configuration/MemberPasswordConfiguration.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Configuration; +namespace Umbraco.Cms.Core.Configuration; /// /// The password configuration for members diff --git a/src/Umbraco.Core/Configuration/Models/Attributes/UmbracoOptionsAttribute.cs b/src/Umbraco.Core/Configuration/Models/Attributes/UmbracoOptionsAttribute.cs index f470cfed2a22..5f42aac54523 100644 --- a/src/Umbraco.Core/Configuration/Models/Attributes/UmbracoOptionsAttribute.cs +++ b/src/Umbraco.Core/Configuration/Models/Attributes/UmbracoOptionsAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Configuration.Models; +namespace Umbraco.Cms.Core.Configuration.Models; [AttributeUsage(AttributeTargets.Class)] public class UmbracoOptionsAttribute : Attribute @@ -6,5 +6,6 @@ public class UmbracoOptionsAttribute : Attribute public UmbracoOptionsAttribute(string configurationKey) => ConfigurationKey = configurationKey; public string ConfigurationKey { get; } + public bool BindNonPublicProperties { get; set; } } diff --git a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs index 6a78d7af3724..fdb7bac0ef82 100644 --- a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs @@ -62,7 +62,6 @@ public bool FlagOutOfDateModels [DefaultValue(StaticModelsDirectory)] public string ModelsDirectory { get; set; } = StaticModelsDirectory; - /// /// Gets or sets a value indicating whether to accept an unsafe value for ModelsDirectory. /// diff --git a/src/Umbraco.Core/Configuration/Models/NuCacheSerializerType.cs b/src/Umbraco.Core/Configuration/Models/NuCacheSerializerType.cs index 5fff8047d39e..0506ddb98b8d 100644 --- a/src/Umbraco.Core/Configuration/Models/NuCacheSerializerType.cs +++ b/src/Umbraco.Core/Configuration/Models/NuCacheSerializerType.cs @@ -9,5 +9,5 @@ namespace Umbraco.Cms.Core.Configuration.Models; public enum NuCacheSerializerType { MessagePack = 1, // Default - JSON = 2 + JSON = 2, } diff --git a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs index f8438005f701..a80dad68744a 100644 --- a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs @@ -19,18 +19,18 @@ public class RequestHandlerSettings internal static readonly CharItem[] DefaultCharCollection = { - new() {Char = " ", Replacement = "-"}, new() {Char = "\"", Replacement = string.Empty}, - new() {Char = "'", Replacement = string.Empty}, new() {Char = "%", Replacement = string.Empty}, - new() {Char = ".", Replacement = string.Empty}, new() {Char = ";", Replacement = string.Empty}, - new() {Char = "/", Replacement = string.Empty}, new() {Char = "\\", Replacement = string.Empty}, - new() {Char = ":", Replacement = string.Empty}, new() {Char = "#", Replacement = string.Empty}, - new() {Char = "+", Replacement = "plus"}, new() {Char = "*", Replacement = "star"}, - new() {Char = "&", Replacement = string.Empty}, new() {Char = "?", Replacement = string.Empty}, - new() {Char = "æ", Replacement = "ae"}, new() {Char = "ä", Replacement = "ae"}, - new() {Char = "ø", Replacement = "oe"}, new() {Char = "ö", Replacement = "oe"}, - new() {Char = "å", Replacement = "aa"}, new() {Char = "ü", Replacement = "ue"}, - new() {Char = "ß", Replacement = "ss"}, new() {Char = "|", Replacement = "-"}, - new() {Char = "<", Replacement = string.Empty}, new() {Char = ">", Replacement = string.Empty} + new() { Char = " ", Replacement = "-" }, new() { Char = "\"", Replacement = string.Empty }, + new() { Char = "'", Replacement = string.Empty }, new() { Char = "%", Replacement = string.Empty }, + new() { Char = ".", Replacement = string.Empty }, new() { Char = ";", Replacement = string.Empty }, + new() { Char = "/", Replacement = string.Empty }, new() { Char = "\\", Replacement = string.Empty }, + new() { Char = ":", Replacement = string.Empty }, new() { Char = "#", Replacement = string.Empty }, + new() { Char = "+", Replacement = "plus" }, new() { Char = "*", Replacement = "star" }, + new() { Char = "&", Replacement = string.Empty }, new() { Char = "?", Replacement = string.Empty }, + new() { Char = "æ", Replacement = "ae" }, new() { Char = "ä", Replacement = "ae" }, + new() { Char = "ø", Replacement = "oe" }, new() { Char = "ö", Replacement = "oe" }, + new() { Char = "å", Replacement = "aa" }, new() { Char = "ü", Replacement = "ue" }, + new() { Char = "ß", Replacement = "ss" }, new() { Char = "|", Replacement = "-" }, + new() { Char = "<", Replacement = string.Empty }, new() { Char = ">", Replacement = string.Empty }, }; /// diff --git a/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs b/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs index 3fe9821918ba..55fa7b2c5f3c 100644 --- a/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RichTextEditorSettings.cs @@ -12,125 +12,125 @@ public class RichTextEditorSettings internal const string StaticInvalidElements = "font"; - private static readonly string[] s_default_plugins = + private static readonly string[] Default_plugins = { "paste", "anchor", "charmap", "table", "lists", "advlist", "hr", "autolink", "directionality", "tabfocus", - "searchreplace" + "searchreplace", }; - private static readonly RichTextEditorCommand[] s_default_commands = + private static readonly RichTextEditorCommand[] Default_commands = { new RichTextEditorCommand { - Alias = "ace", Name = "Source code editor", Mode = RichTextEditorCommandMode.Insert + Alias = "ace", Name = "Source code editor", Mode = RichTextEditorCommandMode.Insert, }, new RichTextEditorCommand { - Alias = "removeformat", Name = "Remove format", Mode = RichTextEditorCommandMode.Selection + Alias = "removeformat", Name = "Remove format", Mode = RichTextEditorCommandMode.Selection, }, - new RichTextEditorCommand {Alias = "undo", Name = "Undo", Mode = RichTextEditorCommandMode.Insert}, - new RichTextEditorCommand {Alias = "redo", Name = "Redo", Mode = RichTextEditorCommandMode.Insert}, - new RichTextEditorCommand {Alias = "cut", Name = "Cut", Mode = RichTextEditorCommandMode.Selection}, - new RichTextEditorCommand {Alias = "copy", Name = "Copy", Mode = RichTextEditorCommandMode.Selection}, - new RichTextEditorCommand {Alias = "paste", Name = "Paste", Mode = RichTextEditorCommandMode.All}, + new RichTextEditorCommand { Alias = "undo", Name = "Undo", Mode = RichTextEditorCommandMode.Insert }, + new RichTextEditorCommand { Alias = "redo", Name = "Redo", Mode = RichTextEditorCommandMode.Insert }, + new RichTextEditorCommand { Alias = "cut", Name = "Cut", Mode = RichTextEditorCommandMode.Selection }, + new RichTextEditorCommand { Alias = "copy", Name = "Copy", Mode = RichTextEditorCommandMode.Selection }, + new RichTextEditorCommand { Alias = "paste", Name = "Paste", Mode = RichTextEditorCommandMode.All }, new RichTextEditorCommand { - Alias = "styleselect", Name = "Style select", Mode = RichTextEditorCommandMode.All + Alias = "styleselect", Name = "Style select", Mode = RichTextEditorCommandMode.All, }, - new RichTextEditorCommand {Alias = "bold", Name = "Bold", Mode = RichTextEditorCommandMode.Selection}, - new RichTextEditorCommand {Alias = "italic", Name = "Italic", Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand { Alias = "bold", Name = "Bold", Mode = RichTextEditorCommandMode.Selection }, + new RichTextEditorCommand { Alias = "italic", Name = "Italic", Mode = RichTextEditorCommandMode.Selection }, new RichTextEditorCommand { - Alias = "underline", Name = "Underline", Mode = RichTextEditorCommandMode.Selection + Alias = "underline", Name = "Underline", Mode = RichTextEditorCommandMode.Selection, }, new RichTextEditorCommand { - Alias = "strikethrough", Name = "Strikethrough", Mode = RichTextEditorCommandMode.Selection + Alias = "strikethrough", Name = "Strikethrough", Mode = RichTextEditorCommandMode.Selection, }, new RichTextEditorCommand { - Alias = "alignleft", Name = "Justify left", Mode = RichTextEditorCommandMode.Selection + Alias = "alignleft", Name = "Justify left", Mode = RichTextEditorCommandMode.Selection, }, new RichTextEditorCommand { - Alias = "aligncenter", Name = "Justify center", Mode = RichTextEditorCommandMode.Selection + Alias = "aligncenter", Name = "Justify center", Mode = RichTextEditorCommandMode.Selection, }, new RichTextEditorCommand { - Alias = "alignright", Name = "Justify right", Mode = RichTextEditorCommandMode.Selection + Alias = "alignright", Name = "Justify right", Mode = RichTextEditorCommandMode.Selection, }, new RichTextEditorCommand { - Alias = "alignjustify", Name = "Justify full", Mode = RichTextEditorCommandMode.Selection + Alias = "alignjustify", Name = "Justify full", Mode = RichTextEditorCommandMode.Selection, }, - new RichTextEditorCommand {Alias = "bullist", Name = "Bullet list", Mode = RichTextEditorCommandMode.All}, - new RichTextEditorCommand {Alias = "numlist", Name = "Numbered list", Mode = RichTextEditorCommandMode.All}, + new RichTextEditorCommand { Alias = "bullist", Name = "Bullet list", Mode = RichTextEditorCommandMode.All }, + new RichTextEditorCommand { Alias = "numlist", Name = "Numbered list", Mode = RichTextEditorCommandMode.All }, new RichTextEditorCommand { - Alias = "outdent", Name = "Decrease indent", Mode = RichTextEditorCommandMode.All + Alias = "outdent", Name = "Decrease indent", Mode = RichTextEditorCommandMode.All, }, new RichTextEditorCommand { - Alias = "indent", Name = "Increase indent", Mode = RichTextEditorCommandMode.All + Alias = "indent", Name = "Increase indent", Mode = RichTextEditorCommandMode.All, }, - new RichTextEditorCommand {Alias = "link", Name = "Insert/edit link", Mode = RichTextEditorCommandMode.All}, + new RichTextEditorCommand { Alias = "link", Name = "Insert/edit link", Mode = RichTextEditorCommandMode.All }, new RichTextEditorCommand { - Alias = "unlink", Name = "Remove link", Mode = RichTextEditorCommandMode.Selection + Alias = "unlink", Name = "Remove link", Mode = RichTextEditorCommandMode.Selection, }, - new RichTextEditorCommand {Alias = "anchor", Name = "Anchor", Mode = RichTextEditorCommandMode.Selection}, + new RichTextEditorCommand { Alias = "anchor", Name = "Anchor", Mode = RichTextEditorCommandMode.Selection }, new RichTextEditorCommand { - Alias = "umbmediapicker", Name = "Image", Mode = RichTextEditorCommandMode.Insert + Alias = "umbmediapicker", Name = "Image", Mode = RichTextEditorCommandMode.Insert, }, - new RichTextEditorCommand {Alias = "umbmacro", Name = "Macro", Mode = RichTextEditorCommandMode.All}, - new RichTextEditorCommand {Alias = "table", Name = "Table", Mode = RichTextEditorCommandMode.Insert}, + new RichTextEditorCommand { Alias = "umbmacro", Name = "Macro", Mode = RichTextEditorCommandMode.All }, + new RichTextEditorCommand { Alias = "table", Name = "Table", Mode = RichTextEditorCommandMode.Insert }, new RichTextEditorCommand { - Alias = "umbembeddialog", Name = "Embed", Mode = RichTextEditorCommandMode.Insert + Alias = "umbembeddialog", Name = "Embed", Mode = RichTextEditorCommandMode.Insert, }, - new RichTextEditorCommand {Alias = "hr", Name = "Horizontal rule", Mode = RichTextEditorCommandMode.Insert}, + new RichTextEditorCommand { Alias = "hr", Name = "Horizontal rule", Mode = RichTextEditorCommandMode.Insert }, new RichTextEditorCommand { - Alias = "subscript", Name = "Subscript", Mode = RichTextEditorCommandMode.Selection + Alias = "subscript", Name = "Subscript", Mode = RichTextEditorCommandMode.Selection, }, new RichTextEditorCommand { - Alias = "superscript", Name = "Superscript", Mode = RichTextEditorCommandMode.Selection + Alias = "superscript", Name = "Superscript", Mode = RichTextEditorCommandMode.Selection, }, new RichTextEditorCommand { - Alias = "charmap", Name = "Character map", Mode = RichTextEditorCommandMode.Insert + Alias = "charmap", Name = "Character map", Mode = RichTextEditorCommandMode.Insert, }, new RichTextEditorCommand { - Alias = "rtl", Name = "Right to left", Mode = RichTextEditorCommandMode.Selection + Alias = "rtl", Name = "Right to left", Mode = RichTextEditorCommandMode.Selection, }, new RichTextEditorCommand { - Alias = "ltr", Name = "Left to right", Mode = RichTextEditorCommandMode.Selection - } + Alias = "ltr", Name = "Left to right", Mode = RichTextEditorCommandMode.Selection, + }, }; - private static readonly IDictionary s_default_custom_config = - new Dictionary {["entity_encoding"] = "raw"}; + private static readonly IDictionary Default_custom_config = + new Dictionary { ["entity_encoding"] = "raw" }; /// /// HTML RichText Editor TinyMCE Commands /// /// WB-TODO Custom Array of objects - public RichTextEditorCommand[] Commands { get; set; } = s_default_commands; + public RichTextEditorCommand[] Commands { get; set; } = Default_commands; /// /// HTML RichText Editor TinyMCE Plugins /// - public string[] Plugins { get; set; } = s_default_plugins; + public string[] Plugins { get; set; } = Default_plugins; /// /// HTML RichText Editor TinyMCE Custom Config /// /// WB-TODO Custom Dictionary - public IDictionary CustomConfig { get; set; } = s_default_custom_config; + public IDictionary CustomConfig { get; set; } = Default_custom_config; /// /// @@ -145,10 +145,13 @@ public class RichTextEditorSettings public class RichTextEditorCommand { - [Required] public string Alias { get; set; } = null!; + [Required] + public string Alias { get; set; } = null!; - [Required] public string Name { get; set; } = null!; + [Required] + public string Name { get; set; } = null!; - [Required] public RichTextEditorCommandMode Mode { get; set; } + [Required] + public RichTextEditorCommandMode Mode { get; set; } } } diff --git a/src/Umbraco.Core/Configuration/Models/RuntimeMinificationCacheBuster.cs b/src/Umbraco.Core/Configuration/Models/RuntimeMinificationCacheBuster.cs index f3491543b2a6..37426fa84f6c 100644 --- a/src/Umbraco.Core/Configuration/Models/RuntimeMinificationCacheBuster.cs +++ b/src/Umbraco.Core/Configuration/Models/RuntimeMinificationCacheBuster.cs @@ -1,8 +1,8 @@ -namespace Umbraco.Cms.Core.Configuration.Models; +namespace Umbraco.Cms.Core.Configuration.Models; public enum RuntimeMinificationCacheBuster { Version, AppDomain, - Timestamp + Timestamp, } diff --git a/src/Umbraco.Core/Configuration/Models/SmtpSettings.cs b/src/Umbraco.Core/Configuration/Models/SmtpSettings.cs index 055c318cc5e9..5a9ec1b94fbb 100644 --- a/src/Umbraco.Core/Configuration/Models/SmtpSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/SmtpSettings.cs @@ -42,7 +42,7 @@ public enum SecureSocketOptions /// Elevates the connection to use TLS encryption immediately after reading the greeting and capabilities of the /// server, but only if the server supports the STARTTLS extension. /// - StartTlsWhenAvailable = 4 + StartTlsWhenAvailable = 4, } /// diff --git a/src/Umbraco.Core/Configuration/Models/UmbracoPluginSettings.cs b/src/Umbraco.Core/Configuration/Models/UmbracoPluginSettings.cs index 4beef98d9b49..bec6d77bfb1b 100644 --- a/src/Umbraco.Core/Configuration/Models/UmbracoPluginSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/UmbracoPluginSettings.cs @@ -22,6 +22,6 @@ public class UmbracoPluginSettings ".eot", ".ttf", ".woff", // fonts ".xml", ".json", ".config", // configurations ".lic", // license - ".map" // js map files + ".map", // js map files }); } diff --git a/src/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidator.cs b/src/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidator.cs index fedb32ad3ec9..4a1872cf3063 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidator.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/RequestHandlerSettingsValidator.cs @@ -23,9 +23,8 @@ public ValidateOptionsResult Validate(string name, RequestHandlerSettings option private bool ValidateConvertUrlsToAscii(string value, out string message) { - var validValues = new[] {"try", "true", "false"}; + var validValues = new[] { "try", "true", "false" }; return ValidateStringIsOneOfValidValues( - $"{Constants.Configuration.ConfigRequestHandler}:{nameof(RequestHandlerSettings.ConvertUrlsToAscii)}", - value, validValues, out message); + $"{Constants.Configuration.ConfigRequestHandler}:{nameof(RequestHandlerSettings.ConvertUrlsToAscii)}", value, validValues, out message); } } diff --git a/src/Umbraco.Core/Configuration/Models/Validation/UnattendedSettingsValidator.cs b/src/Umbraco.Core/Configuration/Models/Validation/UnattendedSettingsValidator.cs index f96a88b13fe9..e262de76e7b3 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/UnattendedSettingsValidator.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/UnattendedSettingsValidator.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.Options; @@ -32,7 +32,7 @@ public ValidateOptionsResult Validate(string name, UnattendedSettings options) setValues++; } - if (0 < setValues && setValues < 3) + if (setValues > 0 && setValues < 3) { return ValidateOptionsResult.Fail( $"Configuration entry {Constants.Configuration.ConfigUnattended} contains invalid values.\nIf any of the {nameof(options.UnattendedUserName)}, {nameof(options.UnattendedUserEmail)}, {nameof(options.UnattendedUserPassword)} are set, all of them are required."); diff --git a/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs b/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs index fe10b46746ba..00686b689670 100644 --- a/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs +++ b/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Exceptions; using Umbraco.Cms.Core.Hosting; @@ -8,7 +8,8 @@ public static class ModelsBuilderConfigExtensions { private static string? _modelsDirectoryAbsolute; - public static string ModelsDirectoryAbsolute(this ModelsBuilderSettings modelsBuilderConfig, + public static string ModelsDirectoryAbsolute( + this ModelsBuilderSettings modelsBuilderConfig, IHostingEnvironment hostingEnvironment) { if (_modelsDirectoryAbsolute is null) @@ -28,7 +29,6 @@ internal static string GetModelsDirectory(string root, string config, bool accep { // making sure it is safe, ie under the website root, // unless AcceptUnsafeModelsDirectory and then everything is OK. - if (!Path.IsPathRooted(root)) { throw new ConfigurationException($"Root is not rooted \"{root}\"."); diff --git a/src/Umbraco.Core/Configuration/ModelsMode.cs b/src/Umbraco.Core/Configuration/ModelsMode.cs index 778b60db1582..9e76710e2b58 100644 --- a/src/Umbraco.Core/Configuration/ModelsMode.cs +++ b/src/Umbraco.Core/Configuration/ModelsMode.cs @@ -38,5 +38,5 @@ public enum ModelsMode /// Generation can be triggered from the dashboard. The app does not restart. /// Models are not compiled and thus are not available to the project. /// - SourceCodeAuto + SourceCodeAuto, } diff --git a/src/Umbraco.Core/Configuration/PasswordConfiguration.cs b/src/Umbraco.Core/Configuration/PasswordConfiguration.cs index 2b7684223aab..4c7472086081 100644 --- a/src/Umbraco.Core/Configuration/PasswordConfiguration.cs +++ b/src/Umbraco.Core/Configuration/PasswordConfiguration.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Configuration; +namespace Umbraco.Cms.Core.Configuration; public abstract class PasswordConfiguration : IPasswordConfiguration { diff --git a/src/Umbraco.Core/Configuration/UserPasswordConfiguration.cs b/src/Umbraco.Core/Configuration/UserPasswordConfiguration.cs index fb132ebf0d7b..47b950de9cfe 100644 --- a/src/Umbraco.Core/Configuration/UserPasswordConfiguration.cs +++ b/src/Umbraco.Core/Configuration/UserPasswordConfiguration.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Configuration; +namespace Umbraco.Cms.Core.Configuration; /// /// The password configuration for back office users diff --git a/src/Umbraco.Core/ContentApps/MemberEditorContentAppFactory.cs b/src/Umbraco.Core/ContentApps/MemberEditorContentAppFactory.cs index e3b4148fa158..5ba19cabb042 100644 --- a/src/Umbraco.Core/ContentApps/MemberEditorContentAppFactory.cs +++ b/src/Umbraco.Core/ContentApps/MemberEditorContentAppFactory.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Membership; @@ -22,7 +22,7 @@ internal class MemberEditorContentAppFactory : IContentAppFactory Name = "Member", Icon = "icon-user", View = "views/member/apps/membership/membership.html", - Weight = Weight + Weight = Weight, }; default: diff --git a/src/Umbraco.Core/Dashboards/MembersDashboard.cs b/src/Umbraco.Core/Dashboards/MembersDashboard.cs index 4317be78a0af..f69d0a1ed05b 100644 --- a/src/Umbraco.Core/Dashboards/MembersDashboard.cs +++ b/src/Umbraco.Core/Dashboards/MembersDashboard.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Dashboards; @@ -7,7 +7,7 @@ public class MembersDashboard : IDashboard { public string Alias => "memberIntro"; - public string[] Sections => new[] {"member"}; + public string[] Sections => new[] { "member" }; public string View => "views/dashboard/members/membersdashboardvideos.html"; diff --git a/src/Umbraco.Core/Dashboards/ModelsBuilderDashboard.cs b/src/Umbraco.Core/Dashboards/ModelsBuilderDashboard.cs index e37833c6b93f..640f6daf6e85 100644 --- a/src/Umbraco.Core/Dashboards/ModelsBuilderDashboard.cs +++ b/src/Umbraco.Core/Dashboards/ModelsBuilderDashboard.cs @@ -7,7 +7,7 @@ public class ModelsBuilderDashboard : IDashboard { public string Alias => "settingsModelsBuilder"; - public string[] Sections => new[] {"settings"}; + public string[] Sections => new[] { "settings" }; public string View => "views/dashboard/settings/modelsbuildermanagement.html"; diff --git a/src/Umbraco.Core/Dashboards/ProfilerDashboard.cs b/src/Umbraco.Core/Dashboards/ProfilerDashboard.cs index a2a6c97e7de7..b84b1529c32c 100644 --- a/src/Umbraco.Core/Dashboards/ProfilerDashboard.cs +++ b/src/Umbraco.Core/Dashboards/ProfilerDashboard.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Dashboards; @@ -7,7 +7,7 @@ public class ProfilerDashboard : IDashboard { public string Alias => "settingsProfiler"; - public string[] Sections => new[] {"settings"}; + public string[] Sections => new[] { "settings" }; public string View => "views/dashboard/settings/profiler.html"; diff --git a/src/Umbraco.Core/Dashboards/PublishedStatusDashboard.cs b/src/Umbraco.Core/Dashboards/PublishedStatusDashboard.cs index 3fcf4c3e5b79..49709436ab23 100644 --- a/src/Umbraco.Core/Dashboards/PublishedStatusDashboard.cs +++ b/src/Umbraco.Core/Dashboards/PublishedStatusDashboard.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Dashboards; @@ -7,7 +7,7 @@ public class PublishedStatusDashboard : IDashboard { public string Alias => "settingsPublishedStatus"; - public string[] Sections => new[] {"settings"}; + public string[] Sections => new[] { "settings" }; public string View => "views/dashboard/settings/publishedstatus.html"; diff --git a/src/Umbraco.Core/Dashboards/RedirectUrlDashboard.cs b/src/Umbraco.Core/Dashboards/RedirectUrlDashboard.cs index ad88b060d009..25b064154b0c 100644 --- a/src/Umbraco.Core/Dashboards/RedirectUrlDashboard.cs +++ b/src/Umbraco.Core/Dashboards/RedirectUrlDashboard.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Dashboards; @@ -7,7 +7,7 @@ public class RedirectUrlDashboard : IDashboard { public string Alias => "contentRedirectManager"; - public string[] Sections => new[] {"content"}; + public string[] Sections => new[] { "content" }; public string View => "views/dashboard/content/redirecturls.html"; diff --git a/src/Umbraco.Core/Dashboards/SettingsDashboards.cs b/src/Umbraco.Core/Dashboards/SettingsDashboards.cs index 04929b041826..b9cb57224099 100644 --- a/src/Umbraco.Core/Dashboards/SettingsDashboards.cs +++ b/src/Umbraco.Core/Dashboards/SettingsDashboards.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Dashboards; @@ -7,7 +7,7 @@ public class SettingsDashboard : IDashboard { public string Alias => "settingsWelcome"; - public string[] Sections => new[] {"settings"}; + public string[] Sections => new[] { "settings" }; public string View => "views/dashboard/settings/settingsdashboardintro.html"; diff --git a/src/Umbraco.Core/DependencyInjection/StaticServiceProvider.cs b/src/Umbraco.Core/DependencyInjection/StaticServiceProvider.cs index 7489f94fd29c..6f8e4a2173f8 100644 --- a/src/Umbraco.Core/DependencyInjection/StaticServiceProvider.cs +++ b/src/Umbraco.Core/DependencyInjection/StaticServiceProvider.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; namespace Umbraco.Cms.Web.Common.DependencyInjection; diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs index 4a051e98bc29..fef14f9fc644 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs @@ -26,6 +26,13 @@ namespace Umbraco.Cms.Core.DependencyInjection; /// public static partial class UmbracoBuilderExtensions { + /// + /// Gets the actions collection builder. + /// + /// The builder. + public static ActionCollectionBuilder? Actions(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + /// /// Adds all core collection builders /// @@ -66,6 +73,7 @@ internal static void AddAllCoreCollectionBuilders(this IUmbracoBuilder builder) .Append(); builder.MediaUrlProviders()? .Append(); + // register back office sections in the order we want them rendered builder.Sections()? .Append() @@ -77,6 +85,7 @@ internal static void AddAllCoreCollectionBuilders(this IUmbracoBuilder builder) .Append() .Append(); builder.Components(); + // register core CMS dashboards and 3rd party types - will be ordered by weight attribute & merged with package.manifest dashboards builder.Dashboards()? .Add() @@ -103,6 +112,7 @@ internal static void AddAllCoreCollectionBuilders(this IUmbracoBuilder builder) .Add(); builder.ManifestFilters(); builder.MediaUrlGenerators(); + // register OEmbed providers - no type scanning - all explicit opt-in of adding types, IEmbedProvider is not IDiscoverable builder.EmbedProviders()? .Append() @@ -123,13 +133,6 @@ internal static void AddAllCoreCollectionBuilders(this IUmbracoBuilder builder) builder.BackOfficeAssets(); } - /// - /// Gets the actions collection builder. - /// - /// The builder. - public static ActionCollectionBuilder? Actions(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - /// /// Gets the content apps collection builder. /// @@ -245,18 +248,18 @@ internal static void AddAllCoreCollectionBuilders(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// - /// Gets the validators collection builder. + /// Gets the manifest filter collection builder. /// /// The builder. - internal static ManifestValueValidatorCollectionBuilder? ManifestValueValidators(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + public static ManifestFilterCollectionBuilder? ManifestFilters(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); /// - /// Gets the manifest filter collection builder. + /// Gets the validators collection builder. /// /// The builder. - public static ManifestFilterCollectionBuilder? ManifestFilters(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + internal static ManifestValueValidatorCollectionBuilder? ManifestValueValidators(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); /// /// Gets the content finders collection builder. diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs index 26023c2443db..4af2c40411fd 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -13,29 +13,6 @@ namespace Umbraco.Cms.Core.DependencyInjection; /// public static partial class UmbracoBuilderExtensions { - private static IUmbracoBuilder AddUmbracoOptions(this IUmbracoBuilder builder, - Action>? configure = null) - where TOptions : class - { - UmbracoOptionsAttribute umbracoOptionsAttribute = - typeof(TOptions).GetCustomAttribute(); - if (umbracoOptionsAttribute is null) - { - throw new ArgumentException($"{typeof(TOptions)} do not have the UmbracoOptionsAttribute."); - } - - OptionsBuilder optionsBuilder = builder.Services.AddOptions() - .Bind( - builder.Config.GetSection(umbracoOptionsAttribute.ConfigurationKey), - o => o.BindNonPublicProperties = umbracoOptionsAttribute.BindNonPublicProperties - ) - .ValidateDataAnnotations(); - - configure?.Invoke(optionsBuilder); - - return builder; - } - /// /// Add Umbraco configuration services and options /// @@ -111,4 +88,26 @@ public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder) return builder; } + + private static IUmbracoBuilder AddUmbracoOptions( + this IUmbracoBuilder builder, + Action>? configure = null) + where TOptions : class + { + UmbracoOptionsAttribute? umbracoOptionsAttribute = typeof(TOptions).GetCustomAttribute(); + if (umbracoOptionsAttribute is null) + { + throw new ArgumentException($"{typeof(TOptions)} do not have the UmbracoOptionsAttribute."); + } + + OptionsBuilder optionsBuilder = builder.Services.AddOptions() + .Bind( + builder.Config.GetSection(umbracoOptionsAttribute.ConfigurationKey), + o => o.BindNonPublicProperties = umbracoOptionsAttribute.BindNonPublicProperties) + .ValidateDataAnnotations(); + + configure?.Invoke(optionsBuilder); + + return builder; + } } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs index 3ec78c40251f..844c52a5ab0b 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Events.cs @@ -50,8 +50,10 @@ internal static IServiceCollection AddNotificationHandler), - typeof(TNotificationHandler), ServiceLifetime.Transient); + var descriptor = new UniqueServiceDescriptor( + typeof(INotificationHandler), + typeof(TNotificationHandler), + ServiceLifetime.Transient); if (!services.Contains(descriptor)) { @@ -67,8 +69,10 @@ internal static IServiceCollection AddNotificationAsyncHandler), - typeof(TNotificationAsyncHandler), ServiceLifetime.Transient); + var descriptor = new ServiceDescriptor( + typeof(INotificationAsyncHandler), + typeof(TNotificationAsyncHandler), + ServiceLifetime.Transient); if (!services.Contains(descriptor)) { diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 523c85555ba2..57d5fd60d031 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -115,7 +115,7 @@ public UmbracoBuilder( { builder = Activator.CreateInstance(); } - else if (typeof(TBuilder).GetConstructor(new[] {typeof(IUmbracoBuilder)}) != null) + else if (typeof(TBuilder).GetConstructor(new[] { typeof(IUmbracoBuilder) }) != null) { // Handle those collection builders which need a reference to umbraco builder i.e. DistributedLockingCollectionBuilder. builder = (TBuilder?)Activator.CreateInstance(typeof(TBuilder), this); @@ -233,7 +233,8 @@ private void AddCoreServices() Services.AddUnique(); // register distributed cache - Services.AddUnique(f => new DistributedCache(f.GetRequiredService(), + Services.AddUnique(f => new DistributedCache( + f.GetRequiredService(), f.GetRequiredService())); Services.AddUnique(); @@ -311,8 +312,7 @@ private void AddCoreServices() factory.GetRequiredService(), factory.GetRequiredService(), factory.GetRequiredService(), - factory.GetRequiredService() - )); + factory.GetRequiredService())); Services.AddUnique(factory => factory.GetRequiredService()); Services.AddUnique(factory => factory.GetRequiredService()); Services.AddUnique(factory => new LocalizedTextService( diff --git a/src/Umbraco.Core/DependencyInjection/UniqueServiceDescriptor.cs b/src/Umbraco.Core/DependencyInjection/UniqueServiceDescriptor.cs index 0d9f442f4f01..57a8bcfe99d8 100644 --- a/src/Umbraco.Core/DependencyInjection/UniqueServiceDescriptor.cs +++ b/src/Umbraco.Core/DependencyInjection/UniqueServiceDescriptor.cs @@ -24,14 +24,17 @@ public UniqueServiceDescriptor(Type serviceType, Type implementationType, Servic /// public bool Equals(UniqueServiceDescriptor? other) => other != null && Lifetime == other.Lifetime && - EqualityComparer.Default.Equals(ServiceType, + EqualityComparer.Default.Equals( + ServiceType, other.ServiceType) && - EqualityComparer.Default.Equals(ImplementationType, + EqualityComparer.Default.Equals( + ImplementationType, other.ImplementationType) && EqualityComparer.Default.Equals( ImplementationInstance, other.ImplementationInstance) && EqualityComparer?>.Default - .Equals(ImplementationFactory, + .Equals( + ImplementationFactory, other.ImplementationFactory); /// diff --git a/src/Umbraco.Core/Diagnostics/MiniDump.cs b/src/Umbraco.Core/Diagnostics/MiniDump.cs index e07db99b853e..ac37c69f122d 100644 --- a/src/Umbraco.Core/Diagnostics/MiniDump.cs +++ b/src/Umbraco.Core/Diagnostics/MiniDump.cs @@ -3,12 +3,14 @@ using Umbraco.Cms.Core.Hosting; namespace Umbraco.Cms.Core.Diagnostics; + // taken from https://blogs.msdn.microsoft.com/dondu/2010/10/24/writing-minidumps-in-c/ // and https://blogs.msdn.microsoft.com/dondu/2010/10/31/writing-minidumps-from-exceptions-in-c/ // which itself got it from http://blog.kalmbach-software.de/2008/12/13/writing-minidumps-in-c/ - public static class MiniDump { + private static readonly object LockO = new(); + [Flags] public enum Option : uint { @@ -32,14 +34,39 @@ public enum Option : uint WithFullAuxiliaryState = 0x00008000, WithPrivateWriteCopyMemory = 0x00010000, IgnoreInaccessibleMemory = 0x00020000, - ValidTypeFlags = 0x0003ffff + ValidTypeFlags = 0x0003ffff, } - private static readonly object LockO = new(); + public static bool Dump(IMarchal marchal, IHostingEnvironment hostingEnvironment, Option options = Option.WithFullMemory, bool withException = false) + { + lock (LockO) + { + // work around "stack trace is not available while minidump debugging", + // by making sure a local var (that we can inspect) contains the stack trace. + // getting the call stack before it is unwound would require a special exception + // filter everywhere in our code = not! + var stacktrace = withException ? Environment.StackTrace : string.Empty; + + var directory = hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data + "/MiniDump"); + + if (Directory.Exists(directory) == false) + { + Directory.CreateDirectory(directory); + } + + var filename = Path.Combine( + directory, + $"{DateTime.UtcNow:yyyyMMddTHHmmss}.{Guid.NewGuid().ToString("N")[..4]}.dmp"); + using (var stream = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite, FileShare.Write)) + { + return Write(marchal, stream.SafeFileHandle, options, withException); + } + } + } - //BOOL - //WINAPI - //MiniDumpWriteDump( + // BOOL + // WINAPI + // MiniDumpWriteDump( // __in HANDLE hProcess, // __in DWORD ProcessId, // __in HANDLE hFile, @@ -50,16 +77,12 @@ public enum Option : uint // ); // Overload requiring MiniDumpExceptionInformation - [DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, - CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] - private static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, - ref MiniDumpExceptionInformation expParam, IntPtr userStreamParam, IntPtr callbackParam); + [DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + private static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, ref MiniDumpExceptionInformation expParam, IntPtr userStreamParam, IntPtr callbackParam); // Overload supporting MiniDumpExceptionInformation == NULL - [DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, - CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] - private static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, - IntPtr expParam, IntPtr userStreamParam, IntPtr callbackParam); + [DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + private static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, IntPtr expParam, IntPtr userStreamParam, IntPtr callbackParam); [DllImport("kernel32.dll", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)] private static extern uint GetCurrentThreadId(); @@ -83,42 +106,27 @@ private static bool Write(IMarchal marchal, SafeHandle fileHandle, Option option } var bRet = exp.ExceptionPointers == IntPtr.Zero - ? MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint)options, IntPtr.Zero, - IntPtr.Zero, IntPtr.Zero) - : MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint)options, ref exp, - IntPtr.Zero, IntPtr.Zero); + ? MiniDumpWriteDump( + currentProcessHandle, + currentProcessId, + fileHandle, + (uint)options, + IntPtr.Zero, + IntPtr.Zero, + IntPtr.Zero) + : MiniDumpWriteDump( + currentProcessHandle, + currentProcessId, + fileHandle, + (uint)options, + ref exp, + IntPtr.Zero, + IntPtr.Zero); return bRet; } } - public static bool Dump(IMarchal marchal, IHostingEnvironment hostingEnvironment, - Option options = Option.WithFullMemory, bool withException = false) - { - lock (LockO) - { - // work around "stack trace is not available while minidump debugging", - // by making sure a local var (that we can inspect) contains the stack trace. - // getting the call stack before it is unwound would require a special exception - // filter everywhere in our code = not! - var stacktrace = withException ? Environment.StackTrace : string.Empty; - - var directory = hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data + "/MiniDump"); - - if (Directory.Exists(directory) == false) - { - Directory.CreateDirectory(directory); - } - - var filename = Path.Combine(directory, - $"{DateTime.UtcNow:yyyyMMddTHHmmss}.{Guid.NewGuid().ToString("N").Substring(0, 4)}.dmp"); - using (var stream = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite, FileShare.Write)) - { - return Write(marchal, stream.SafeFileHandle, options, withException); - } - } - } - public static bool OkToDump(IHostingEnvironment hostingEnvironment) { lock (LockO) @@ -134,16 +142,17 @@ public static bool OkToDump(IHostingEnvironment hostingEnvironment) } } - //typedef struct _MINIDUMP_EXCEPTION_INFORMATION { + // typedef struct _MINIDUMP_EXCEPTION_INFORMATION { // DWORD ThreadId; // PEXCEPTION_POINTERS ExceptionPointers; // BOOL ClientPointers; - //} MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION; + // } MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION; [StructLayout(LayoutKind.Sequential, Pack = 4)] // Pack=4 is important! So it works also for x64! public struct MiniDumpExceptionInformation { public uint ThreadId; public IntPtr ExceptionPointers; - [MarshalAs(UnmanagedType.Bool)] public bool ClientPointers; + [MarshalAs(UnmanagedType.Bool)] + public bool ClientPointers; } } diff --git a/src/Umbraco.Core/Diagnostics/NoopMarchal.cs b/src/Umbraco.Core/Diagnostics/NoopMarchal.cs index d1e775dbaac1..770aefd50f42 100644 --- a/src/Umbraco.Core/Diagnostics/NoopMarchal.cs +++ b/src/Umbraco.Core/Diagnostics/NoopMarchal.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Diagnostics; +namespace Umbraco.Cms.Core.Diagnostics; internal class NoopMarchal : IMarchal { diff --git a/src/Umbraco.Core/Dictionary/UmbracoCultureDictionary.cs b/src/Umbraco.Core/Dictionary/UmbracoCultureDictionary.cs index 37738a5d4ebc..de968f167640 100644 --- a/src/Umbraco.Core/Dictionary/UmbracoCultureDictionary.cs +++ b/src/Umbraco.Core/Dictionary/UmbracoCultureDictionary.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; @@ -39,25 +39,31 @@ public DefaultCultureDictionary(ILocalizationService localizationService, IAppCa /// /// /// - public DefaultCultureDictionary(CultureInfo specificCulture, ILocalizationService localizationService, - IAppCache requestCache) + public DefaultCultureDictionary(CultureInfo specificCulture, ILocalizationService localizationService, IAppCache requestCache) { _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); _requestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache)); _specificCulture = specificCulture ?? throw new ArgumentNullException(nameof(specificCulture)); } + /// + /// Returns the current culture + /// + public CultureInfo Culture => _specificCulture ?? Thread.CurrentThread.CurrentUICulture; + private ILanguage? Language => - //ensure it's stored/retrieved from request cache - //NOTE: This is no longer necessary since these are cached at the runtime level, but we can leave it here for now. - _requestCache.GetCacheItem(typeof(DefaultCultureDictionary).Name + "Culture" + Culture.Name, + + // ensure it's stored/retrieved from request cache + // NOTE: This is no longer necessary since these are cached at the runtime level, but we can leave it here for now. + _requestCache.GetCacheItem( + typeof(DefaultCultureDictionary).Name + "Culture" + Culture.Name, () => { // find a language that matches the current culture or any of its parent cultures CultureInfo culture = Culture; while (culture != CultureInfo.InvariantCulture) { - ILanguage language = _localizationService.GetLanguageByIsoCode(culture.Name); + ILanguage? language = _localizationService.GetLanguageByIsoCode(culture.Name); if (language != null) { return language; @@ -78,14 +84,14 @@ public string? this[string key] { get { - IDictionaryItem found = _localizationService.GetDictionaryItemByKey(key); + IDictionaryItem? found = _localizationService.GetDictionaryItemByKey(key); if (found == null) { return string.Empty; } - IDictionaryTranslation byLang = - found.Translations?.FirstOrDefault(x => x.Language?.Equals(Language) ?? false); + IDictionaryTranslation? byLang = + found.Translations.FirstOrDefault(x => x.Language?.Equals(Language) ?? false); if (byLang == null) { return string.Empty; @@ -95,11 +101,6 @@ public string? this[string key] } } - /// - /// Returns the current culture - /// - public CultureInfo Culture => _specificCulture ?? Thread.CurrentThread.CurrentUICulture; - /// /// Returns the child dictionary entries for a given key /// @@ -114,13 +115,13 @@ public IDictionary GetChildren(string key) { var result = new Dictionary(); - IDictionaryItem found = _localizationService.GetDictionaryItemByKey(key); + IDictionaryItem? found = _localizationService.GetDictionaryItemByKey(key); if (found == null) { return result; } - IEnumerable children = _localizationService.GetDictionaryItemChildren(found.Key); + IEnumerable? children = _localizationService.GetDictionaryItemChildren(found.Key); if (children == null) { return result; @@ -128,8 +129,7 @@ public IDictionary GetChildren(string key) foreach (IDictionaryItem dictionaryItem in children) { - IDictionaryTranslation byLang = - dictionaryItem.Translations?.FirstOrDefault(x => x.Language?.Equals(Language) ?? false); + IDictionaryTranslation? byLang = dictionaryItem.Translations.FirstOrDefault(x => x.Language?.Equals(Language) ?? false); if (byLang != null && dictionaryItem.ItemKey is not null && byLang.Value is not null) { result.Add(dictionaryItem.ItemKey, byLang.Value); diff --git a/src/Umbraco.Core/Dictionary/UmbracoCultureDictionaryFactory.cs b/src/Umbraco.Core/Dictionary/UmbracoCultureDictionaryFactory.cs index afa66f4238cc..4c4eb030cc7e 100644 --- a/src/Umbraco.Core/Dictionary/UmbracoCultureDictionaryFactory.cs +++ b/src/Umbraco.Core/Dictionary/UmbracoCultureDictionaryFactory.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Core.Dictionary; diff --git a/src/Umbraco.Core/Editors/UserEditorAuthorizationHelper.cs b/src/Umbraco.Core/Editors/UserEditorAuthorizationHelper.cs index 07cf89872ba8..be9b05230f2e 100644 --- a/src/Umbraco.Core/Editors/UserEditorAuthorizationHelper.cs +++ b/src/Umbraco.Core/Editors/UserEditorAuthorizationHelper.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Cache; @@ -17,8 +17,7 @@ public class UserEditorAuthorizationHelper private readonly IEntityService _entityService; private readonly IMediaService _mediaService; - public UserEditorAuthorizationHelper(IContentService contentService, IMediaService mediaService, - IEntityService entityService, AppCaches appCaches) + public UserEditorAuthorizationHelper(IContentService contentService, IMediaService mediaService, IEntityService entityService, AppCaches appCaches) { _contentService = contentService; _mediaService = mediaService; @@ -35,15 +34,16 @@ public UserEditorAuthorizationHelper(IContentService contentService, IMediaServi /// The start media ids of the user being saved (can be null or empty) /// The user aliases of the user being saved (can be null or empty) /// - public Attempt IsAuthorized(IUser? currentUser, + public Attempt IsAuthorized( + IUser? currentUser, IUser? savingUser, - IEnumerable? startContentIds, IEnumerable? startMediaIds, + IEnumerable? startContentIds, + IEnumerable? startMediaIds, IEnumerable? userGroupAliases) { var currentIsAdmin = currentUser?.IsAdmin() ?? false; // a) A non-admin cannot save an admin - if (savingUser != null) { if (savingUser.IsAdmin() && currentIsAdmin == false) @@ -54,21 +54,20 @@ public UserEditorAuthorizationHelper(IContentService contentService, IMediaServi // b) If a start node is changing, a user cannot set a start node on another user that they don't have access to, this even goes for admins - //only validate any start nodes that have changed. - //a user can remove any start nodes and add start nodes that they have access to - //but they cannot add a start node that they do not have access to - - IEnumerable changedStartContentIds = savingUser == null + // only validate any start nodes that have changed. + // a user can remove any start nodes and add start nodes that they have access to + // but they cannot add a start node that they do not have access to + IEnumerable? changedStartContentIds = savingUser == null ? startContentIds : startContentIds == null || savingUser.StartContentIds is null ? null : startContentIds.Except(savingUser.StartContentIds).ToArray(); - IEnumerable changedStartMediaIds = savingUser == null + IEnumerable? changedStartMediaIds = savingUser == null ? startMediaIds : startMediaIds == null || savingUser.StartMediaIds is null ? null : startMediaIds.Except(savingUser.StartMediaIds).ToArray(); - Attempt pathResult = currentUser is null + Attempt pathResult = currentUser is null ? Attempt.Fail() : AuthorizePath(currentUser, changedStartContentIds, changedStartMediaIds); if (pathResult == false) @@ -77,10 +76,9 @@ public UserEditorAuthorizationHelper(IContentService contentService, IMediaServi } // c) an admin can manage any group or section access - if (currentIsAdmin) { - return Attempt.Succeed(); + return Attempt.Succeed(); } if (userGroupAliases != null) @@ -101,11 +99,10 @@ public UserEditorAuthorizationHelper(IContentService contentService, IMediaServi "', the current user is not part of them or admin"); } - //only validate any groups that have changed. - //a non-admin user can remove groups and add groups that they have access to - //but they cannot add a group that they do not have access to or that grants them - //path or section access that they don't have access to. - + // only validate any groups that have changed. + // a non-admin user can remove groups and add groups that they have access to + // but they cannot add a group that they do not have access to or that grants them + // path or section access that they don't have access to. var newGroups = savingUser == null ? savingGroupAliases : savingGroupAliases.Except(savingUser.Groups.Select(x => x.Alias)).ToArray(); @@ -129,8 +126,7 @@ public UserEditorAuthorizationHelper(IContentService contentService, IMediaServi return Attempt.Succeed(); } - private Attempt AuthorizePath(IUser currentUser, IEnumerable? startContentIds, - IEnumerable? startMediaIds) + private Attempt AuthorizePath(IUser currentUser, IEnumerable? startContentIds, IEnumerable? startMediaIds) { if (startContentIds != null) { @@ -138,7 +134,8 @@ public UserEditorAuthorizationHelper(IContentService contentService, IMediaServi { if (contentId == Constants.System.Root) { - var hasAccess = ContentPermissions.HasPathAccess("-1", + var hasAccess = ContentPermissions.HasPathAccess( + "-1", currentUser.CalculateContentStartNodeIds(_entityService, _appCaches), Constants.System.RecycleBinContent); if (hasAccess == false) @@ -148,7 +145,7 @@ public UserEditorAuthorizationHelper(IContentService contentService, IMediaServi } else { - IContent content = _contentService.GetById(contentId); + IContent? content = _contentService.GetById(contentId); if (content == null) { continue; @@ -170,7 +167,8 @@ public UserEditorAuthorizationHelper(IContentService contentService, IMediaServi { if (mediaId == Constants.System.Root) { - var hasAccess = ContentPermissions.HasPathAccess("-1", + var hasAccess = ContentPermissions.HasPathAccess( + "-1", currentUser.CalculateMediaStartNodeIds(_entityService, _appCaches), Constants.System.RecycleBinMedia); if (hasAccess == false) @@ -180,7 +178,7 @@ public UserEditorAuthorizationHelper(IContentService contentService, IMediaServi } else { - IMedia media = _mediaService.GetById(mediaId); + IMedia? media = _mediaService.GetById(mediaId); if (media == null) { continue; diff --git a/src/Umbraco.Core/Events/MoveEventArgs.cs b/src/Umbraco.Core/Events/MoveEventArgs.cs index 194a76900b30..312f1b8146c6 100644 --- a/src/Umbraco.Core/Events/MoveEventArgs.cs +++ b/src/Umbraco.Core/Events/MoveEventArgs.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; public class MoveEventArgs : CancellableObjectEventArgs, IEquatable> { @@ -21,7 +21,8 @@ public MoveEventArgs(bool canCancel, EventMessages eventMessages, params MoveEve } MoveInfoCollection = moveInfo; - //assign the legacy props + + // assign the legacy props EventObject = moveInfo.First().Entity; } @@ -41,7 +42,8 @@ public MoveEventArgs(EventMessages eventMessages, params MoveEventInfo[ } MoveInfoCollection = moveInfo; - //assign the legacy props + + // assign the legacy props EventObject = moveInfo.First().Entity; } @@ -61,7 +63,8 @@ public MoveEventArgs(bool canCancel, params MoveEventInfo[] moveInfo) } MoveInfoCollection = moveInfo; - //assign the legacy props + + // assign the legacy props EventObject = moveInfo.First().Entity; } @@ -80,11 +83,11 @@ public MoveEventArgs(params MoveEventInfo[] moveInfo) } MoveInfoCollection = moveInfo; - //assign the legacy props + + // assign the legacy props EventObject = moveInfo.First().Entity; } - /// /// Gets all MoveEventInfo objects used to create the object /// @@ -93,7 +96,7 @@ public IEnumerable>? MoveInfoCollection get => _moveInfoCollection; set { - MoveEventInfo first = value?.FirstOrDefault(); + MoveEventInfo? first = value?.FirstOrDefault(); if (first is null) { throw new InvalidOperationException("MoveInfoCollection must have at least one item"); @@ -101,11 +104,13 @@ public IEnumerable>? MoveInfoCollection _moveInfoCollection = value; - //assign the legacy props + // assign the legacy props EventObject = first.Entity; } } + public static bool operator ==(MoveEventArgs left, MoveEventArgs right) => Equals(left, right); + public bool Equals(MoveEventArgs? other) { if (other is null) @@ -154,7 +159,5 @@ public override int GetHashCode() } } - public static bool operator ==(MoveEventArgs left, MoveEventArgs right) => Equals(left, right); - public static bool operator !=(MoveEventArgs left, MoveEventArgs right) => !Equals(left, right); } diff --git a/src/Umbraco.Core/Events/MoveEventInfo.cs b/src/Umbraco.Core/Events/MoveEventInfo.cs index 0c0d557ff993..92c09c92a8bf 100644 --- a/src/Umbraco.Core/Events/MoveEventInfo.cs +++ b/src/Umbraco.Core/Events/MoveEventInfo.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; public class MoveEventInfo : IEquatable> { @@ -10,9 +10,13 @@ public MoveEventInfo(TEntity entity, string originalPath, int newParentId) } public TEntity Entity { get; set; } + public string OriginalPath { get; set; } + public int NewParentId { get; set; } + public static bool operator ==(MoveEventInfo left, MoveEventInfo right) => Equals(left, right); + public bool Equals(MoveEventInfo? other) { if (ReferenceEquals(null, other)) @@ -62,7 +66,5 @@ public override int GetHashCode() } } - public static bool operator ==(MoveEventInfo left, MoveEventInfo right) => Equals(left, right); - public static bool operator !=(MoveEventInfo left, MoveEventInfo right) => !Equals(left, right); } diff --git a/src/Umbraco.Core/Events/NewEventArgs.cs b/src/Umbraco.Core/Events/NewEventArgs.cs index 64a129c53af1..0db72488aa45 100644 --- a/src/Umbraco.Core/Events/NewEventArgs.cs +++ b/src/Umbraco.Core/Events/NewEventArgs.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; public class NewEventArgs : CancellableObjectEventArgs, IEquatable> { @@ -30,8 +30,8 @@ public NewEventArgs(TEntity eventObject, string alias, TEntity? parent, EventMes Parent = parent; } - - public NewEventArgs(TEntity eventObject, bool canCancel, string alias, int parentId) : base(eventObject, canCancel) + public NewEventArgs(TEntity eventObject, bool canCancel, string alias, int parentId) + : base(eventObject, canCancel) { Alias = alias; ParentId = parentId; @@ -44,7 +44,8 @@ public NewEventArgs(TEntity eventObject, bool canCancel, string alias, TEntity? Parent = parent; } - public NewEventArgs(TEntity eventObject, string alias, int parentId) : base(eventObject) + public NewEventArgs(TEntity eventObject, string alias, int parentId) + : base(eventObject) { Alias = alias; ParentId = parentId; @@ -77,6 +78,8 @@ public NewEventArgs(TEntity eventObject, string alias, TEntity? parent) /// public TEntity? Parent { get; } + public static bool operator ==(NewEventArgs left, NewEventArgs right) => Equals(left, right); + public bool Equals(NewEventArgs? other) { if (ReferenceEquals(null, other)) @@ -129,7 +132,5 @@ public override int GetHashCode() } } - public static bool operator ==(NewEventArgs left, NewEventArgs right) => Equals(left, right); - public static bool operator !=(NewEventArgs left, NewEventArgs right) => !Equals(left, right); } diff --git a/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs b/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs index 2c7a52f5b3a2..20398502a18b 100644 --- a/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs +++ b/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; /// /// An IEventDispatcher that immediately raise all events. @@ -9,8 +9,7 @@ /// internal class PassThroughEventDispatcher : IEventDispatcher { - public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, - string? eventName = null) + public bool DispatchCancelable(EventHandler? eventHandler, object sender, CancellableEventArgs args, string? eventName = null) { if (eventHandler == null) { @@ -21,8 +20,7 @@ public bool DispatchCancelable(EventHandler eventHandler, object sender, Cancell return args.Cancel; } - public bool DispatchCancelable(EventHandler eventHandler, object sender, TArgs args, - string? eventName = null) + public bool DispatchCancelable(EventHandler? eventHandler, object sender, TArgs args, string? eventName = null) where TArgs : CancellableEventArgs { if (eventHandler == null) @@ -34,8 +32,7 @@ public bool DispatchCancelable(EventHandler eventHandler, object s return args.Cancel; } - public bool DispatchCancelable(TypedEventHandler eventHandler, TSender sender, - TArgs args, string? eventName = null) + public bool DispatchCancelable(TypedEventHandler? eventHandler, TSender sender, TArgs args, string? eventName = null) where TArgs : CancellableEventArgs { if (eventHandler == null) @@ -47,14 +44,12 @@ public bool DispatchCancelable(TypedEventHandler return args.Cancel; } - public void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string? eventName = null) => + public void Dispatch(EventHandler? eventHandler, object sender, EventArgs args, string? eventName = null) => eventHandler?.Invoke(sender, args); - public void Dispatch(EventHandler eventHandler, object sender, TArgs args, - string? eventName = null) => eventHandler?.Invoke(sender, args); + public void Dispatch(EventHandler? eventHandler, object sender, TArgs args, string? eventName = null) => eventHandler?.Invoke(sender, args); - public void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, - string? eventName = null) => eventHandler?.Invoke(sender, args); + public void Dispatch(TypedEventHandler? eventHandler, TSender sender, TArgs args, string? eventName = null) => eventHandler?.Invoke(sender, args); public IEnumerable GetEvents(EventDefinitionFilter filter) => Enumerable.Empty(); diff --git a/src/Umbraco.Core/Events/PublishEventArgs.cs b/src/Umbraco.Core/Events/PublishEventArgs.cs index 2e8996013c84..8a48a0cfa90b 100644 --- a/src/Umbraco.Core/Events/PublishEventArgs.cs +++ b/src/Umbraco.Core/Events/PublishEventArgs.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; public class PublishEventArgs : CancellableEnumerableObjectEventArgs, IEquatable> @@ -30,7 +30,7 @@ public PublishEventArgs(IEnumerable eventObject, EventMessages eventMes /// /// public PublishEventArgs(TEntity eventObject, EventMessages eventMessages) - : base(new List {eventObject}, eventMessages) + : base(new List { eventObject }, eventMessages) { } @@ -41,7 +41,7 @@ public PublishEventArgs(TEntity eventObject, EventMessages eventMessages) /// /// public PublishEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) - : base(new List {eventObject}, canCancel, eventMessages) + : base(new List { eventObject }, canCancel, eventMessages) { } @@ -70,7 +70,7 @@ public PublishEventArgs(IEnumerable eventObject) /// /// public PublishEventArgs(TEntity eventObject) - : base(new List {eventObject}) + : base(new List { eventObject }) { } @@ -81,7 +81,7 @@ public PublishEventArgs(TEntity eventObject) /// /// public PublishEventArgs(TEntity eventObject, bool canCancel, bool isAllPublished) - : base(new List {eventObject}, canCancel) + : base(new List { eventObject }, canCancel) { } @@ -90,6 +90,9 @@ public PublishEventArgs(TEntity eventObject, bool canCancel, bool isAllPublished /// public IEnumerable? PublishedEntities => EventObject; + public static bool operator ==(PublishEventArgs left, PublishEventArgs right) => + Equals(left, right); + public bool Equals(PublishEventArgs? other) { if (ReferenceEquals(null, other)) @@ -133,9 +136,6 @@ public override int GetHashCode() } } - public static bool operator ==(PublishEventArgs left, PublishEventArgs right) => - Equals(left, right); - public static bool operator !=(PublishEventArgs left, PublishEventArgs right) => !Equals(left, right); } diff --git a/src/Umbraco.Core/Events/QueuingEventDispatcher.cs b/src/Umbraco.Core/Events/QueuingEventDispatcher.cs index ea24f409076a..bc8eac29a15e 100644 --- a/src/Umbraco.Core/Events/QueuingEventDispatcher.cs +++ b/src/Umbraco.Core/Events/QueuingEventDispatcher.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.IO; @@ -23,15 +23,13 @@ protected override void ScopeExitCompleted() // this is probably far from perfect, because if eg a content is saved in a list // and then as a single content, the two events will probably not be de-duplicated, // but it's better than nothing - foreach (IEventDefinition e in GetEvents(EventDefinitionFilter.LastIn)) { e.RaiseEvent(); // separating concerns means that this should probably not be here, // but then where should it be (without making things too complicated)? - var delete = e.Args as IDeletingMediaFilesEventArgs; - if (delete != null && delete.MediaFilesToDelete.Count > 0) + if (e.Args is IDeletingMediaFilesEventArgs delete && delete.MediaFilesToDelete.Count > 0) { _mediaFileManager.DeleteMediaFiles(delete.MediaFilesToDelete); } diff --git a/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs b/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs index 18e9e760f90e..c259e271e54b 100644 --- a/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs +++ b/src/Umbraco.Core/Events/QueuingEventDispatcherBase.cs @@ -21,15 +21,14 @@ public abstract class QueuingEventDispatcherBase : IEventDispatcher { private readonly bool _raiseCancelable; - //events will be enlisted in the order they are raised + // events will be enlisted in the order they are raised private List? _events; protected QueuingEventDispatcherBase(bool raiseCancelable) => _raiseCancelable = raiseCancelable; - private List Events => _events ?? (_events = new List()); + private List Events => _events ??= new List(); - public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, - string? eventName = null) + public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string? eventName = null) { if (eventHandler == null) { @@ -45,8 +44,7 @@ public bool DispatchCancelable(EventHandler eventHandler, object sender, Cancell return args.Cancel; } - public bool DispatchCancelable(EventHandler eventHandler, object sender, TArgs args, - string? eventName = null) + public bool DispatchCancelable(EventHandler eventHandler, object sender, TArgs args, string? eventName = null) where TArgs : CancellableEventArgs { if (eventHandler == null) @@ -63,8 +61,7 @@ public bool DispatchCancelable(EventHandler eventHandler, object s return args.Cancel; } - public bool DispatchCancelable(TypedEventHandler eventHandler, TSender sender, - TArgs args, string? eventName = null) + public bool DispatchCancelable(TypedEventHandler eventHandler, TSender sender, TArgs args, string? eventName = null) where TArgs : CancellableEventArgs { if (eventHandler == null) @@ -101,8 +98,7 @@ public void Dispatch(EventHandler eventHandler, object sender, TAr Events.Add(new EventDefinition(eventHandler, sender, args, eventName)); } - public void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, - string? eventName = null) + public void Dispatch(TypedEventHandler eventHandler, TSender sender, TArgs args, string? eventName = null) { if (eventHandler == null) { @@ -196,7 +192,8 @@ internal static IEnumerable FilterSupersededAndUpdateToLatestE // eagerly fetch superseded arg types for each arg type var argTypeSuperceeding = events.Select(x => x.Args.GetType()) .Distinct() - .ToDictionary(x => x, + .ToDictionary( + x => x, x => x.GetCustomAttributes(false).Select(y => y.SupersededEventArgsType) .ToArray()); @@ -211,7 +208,8 @@ internal static IEnumerable FilterSupersededAndUpdateToLatestE var infos = new EventDefinitionInfos { - EventDefinition = def, SupersedeTypes = argTypeSuperceeding[def.Args.GetType()] + EventDefinition = def, + SupersedeTypes = argTypeSuperceeding[def.Args.GetType()], }; var args = def.Args as CancellableObjectEventArgs; @@ -224,13 +222,12 @@ internal static IEnumerable FilterSupersededAndUpdateToLatestE { // event object can either be a single object or an enumerable of objects // try to get as an enumerable, get null if it's not - IList eventObjects = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject); + IList? eventObjects = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject); if (eventObjects == null) { // single object, cast as an IEntity // if cannot cast, cannot filter, nothing - just include event definition in result - var eventEntity = args.EventObject as IEntity; - if (eventEntity == null) + if (args.EventObject is not IEntity eventEntity) { result.Add(def); continue; @@ -257,8 +254,7 @@ internal static IEnumerable FilterSupersededAndUpdateToLatestE { // extract the event object, cast as an IEntity // if cannot cast, cannot filter, nothing to do - just leave it in the list & continue - var eventEntity = eventObject as IEntity; - if (eventEntity == null) + if (eventObject is not IEntity eventEntity) { continue; } @@ -308,8 +304,11 @@ internal static IEnumerable FilterSupersededAndUpdateToLatestE return result; } + protected abstract void ScopeExitCompleted(); + // edits event args to use the latest instance of each entity - private static void UpdateToLatestEntities(IEnumerable> entities, + private static void UpdateToLatestEntities( + IEnumerable> entities, IEnumerable args) { // get the latest entities @@ -325,13 +324,13 @@ private static void UpdateToLatestEntities(IEnumerable Equals(x, arg.EventObject)); + IEntity? foundEntity = latestEntities.FirstOrDefault(x => Equals(x, arg.EventObject)); if (foundEntity != null) { arg.EventObject = foundEntity; @@ -344,7 +343,7 @@ private static void UpdateToLatestEntities(IEnumerable Equals(x, eventObjects[i])); + IEntity? foundEntity = latestEntities.FirstOrDefault(x => Equals(x, eventObjects[i])); if (foundEntity == null) { continue; @@ -365,11 +364,10 @@ private static void UpdateToLatestEntities(IEnumerable> entities) + private static bool IsSuperceeded(IEntity entity, EventDefinitionInfos infos, List> entities) { - //var argType = meta.EventArgsType; - Type argType = infos.EventDefinition?.Args.GetType(); + // var argType = meta.EventArgsType; + Type? argType = infos.EventDefinition?.Args.GetType(); // look for other instances of the same entity, coming from an event args that supersedes other event args, // ie is marked with the attribute, and is not this event args (cannot supersede itself) @@ -396,11 +394,13 @@ private static bool IsSuperceeded(IEntity entity, EventDefinitionInfos infos, if (argType?.IsGenericType ?? false) { // generic, must compare type arguments - Tuple supercededBy = superceeding.FirstOrDefault(x => + Tuple? supercededBy = superceeding.FirstOrDefault(x => x.Item2.SupersedeTypes?.Any(y => + // superseding a generic type which has the same generic type definition // (but ... no matter the generic type parameters? could be different?) (y.IsGenericTypeDefinition && y == argType.GetGenericTypeDefinition()) + // or superceeding a non-generic type which is ... (but... how is this ever possible? argType *is* generic? || (y.IsGenericTypeDefinition == false && y == argType)) ?? false); return supercededBy != null; @@ -408,17 +408,16 @@ private static bool IsSuperceeded(IEntity entity, EventDefinitionInfos infos, else { // non-generic, can compare types 1:1 - Tuple supercededBy = superceeding.FirstOrDefault(x => + Tuple? supercededBy = superceeding.FirstOrDefault(x => x.Item2.SupersedeTypes?.Any(y => y == argType) ?? false); return supercededBy != null; } } - protected abstract void ScopeExitCompleted(); - private class EventDefinitionInfos { public IEventDefinition? EventDefinition { get; set; } + public Type[]? SupersedeTypes { get; set; } } } diff --git a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs index 39543e82bd9c..44fb13016b55 100644 --- a/src/Umbraco.Core/Events/RecycleBinEventArgs.cs +++ b/src/Umbraco.Core/Events/RecycleBinEventArgs.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; public class RecycleBinEventArgs : CancellableEventArgs, IEquatable { @@ -31,6 +31,8 @@ public RecycleBinEventArgs(Guid nodeObjectType) /// public bool IsMediaRecycleBin => NodeObjectType == Constants.ObjectTypes.Media; + public static bool operator ==(RecycleBinEventArgs left, RecycleBinEventArgs right) => Equals(left, right); + public bool Equals(RecycleBinEventArgs? other) { if (ReferenceEquals(null, other)) @@ -78,7 +80,5 @@ public override int GetHashCode() } } - public static bool operator ==(RecycleBinEventArgs left, RecycleBinEventArgs right) => Equals(left, right); - public static bool operator !=(RecycleBinEventArgs left, RecycleBinEventArgs right) => !Equals(left, right); } diff --git a/src/Umbraco.Core/Events/RefreshContentEventArgs.cs b/src/Umbraco.Core/Events/RefreshContentEventArgs.cs index 366a4c29bc2d..00302e9f3527 100644 --- a/src/Umbraco.Core/Events/RefreshContentEventArgs.cs +++ b/src/Umbraco.Core/Events/RefreshContentEventArgs.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; -//public class RefreshContentEventArgs : System.ComponentModel.CancelEventArgs { } +// public class RefreshContentEventArgs : System.ComponentModel.CancelEventArgs { } diff --git a/src/Umbraco.Core/Events/RelateOnCopyNotificationHandler.cs b/src/Umbraco.Core/Events/RelateOnCopyNotificationHandler.cs index b1319687b9ee..3817f93f6f4c 100644 --- a/src/Umbraco.Core/Events/RelateOnCopyNotificationHandler.cs +++ b/src/Umbraco.Core/Events/RelateOnCopyNotificationHandler.cs @@ -25,12 +25,12 @@ public void Handle(ContentCopiedNotification notification) return; } - IRelationType relationType = - _relationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias); + IRelationType? relationType = _relationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias); if (relationType == null) { - relationType = new RelationType(Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, + relationType = new RelationType( + Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, Constants.Conventions.RelationTypes.RelateDocumentOnCopyName, true, Constants.ObjectTypes.Document, @@ -46,7 +46,8 @@ public void Handle(ContentCopiedNotification notification) _auditService.Add( AuditType.Copy, notification.Copy.WriterId, - notification.Copy.Id, UmbracoObjectTypes.Document.GetName() ?? string.Empty, + notification.Copy.Id, + UmbracoObjectTypes.Document.GetName() ?? string.Empty, $"Copied content with Id: '{notification.Copy.Id}' related to original content with Id: '{notification.Original.Id}'"); } } diff --git a/src/Umbraco.Core/Events/RolesEventArgs.cs b/src/Umbraco.Core/Events/RolesEventArgs.cs index d71dcef9fbd7..a96de0671397 100644 --- a/src/Umbraco.Core/Events/RolesEventArgs.cs +++ b/src/Umbraco.Core/Events/RolesEventArgs.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; public class RolesEventArgs : EventArgs { @@ -9,5 +9,6 @@ public RolesEventArgs(int[] memberIds, string[] roles) } public int[] MemberIds { get; set; } + public string[] Roles { get; set; } } diff --git a/src/Umbraco.Core/Events/RollbackEventArgs.cs b/src/Umbraco.Core/Events/RollbackEventArgs.cs index 4debddd60964..d23ac75f9aca 100644 --- a/src/Umbraco.Core/Events/RollbackEventArgs.cs +++ b/src/Umbraco.Core/Events/RollbackEventArgs.cs @@ -1,12 +1,14 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; public class RollbackEventArgs : CancellableObjectEventArgs { - public RollbackEventArgs(TEntity eventObject, bool canCancel) : base(eventObject, canCancel) + public RollbackEventArgs(TEntity eventObject, bool canCancel) + : base(eventObject, canCancel) { } - public RollbackEventArgs(TEntity eventObject) : base(eventObject) + public RollbackEventArgs(TEntity eventObject) + : base(eventObject) { } diff --git a/src/Umbraco.Core/Events/SaveEventArgs.cs b/src/Umbraco.Core/Events/SaveEventArgs.cs index 2678df1f9dd4..319a0726f299 100644 --- a/src/Umbraco.Core/Events/SaveEventArgs.cs +++ b/src/Umbraco.Core/Events/SaveEventArgs.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; public class SaveEventArgs : CancellableEnumerableObjectEventArgs { @@ -9,8 +9,7 @@ public class SaveEventArgs : CancellableEnumerableObjectEventArgs /// /// - public SaveEventArgs(IEnumerable eventObject, bool canCancel, EventMessages messages, - IDictionary additionalData) + public SaveEventArgs(IEnumerable eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) : base(eventObject, canCancel, messages, additionalData) { } @@ -43,9 +42,8 @@ public SaveEventArgs(IEnumerable eventObject, EventMessages eventMessag /// /// /// - public SaveEventArgs(TEntity eventObject, bool canCancel, EventMessages messages, - IDictionary additionalData) - : base(new List {eventObject}, canCancel, messages, additionalData) + public SaveEventArgs(TEntity eventObject, bool canCancel, EventMessages messages, IDictionary additionalData) + : base(new List { eventObject }, canCancel, messages, additionalData) { } @@ -55,7 +53,7 @@ public SaveEventArgs(TEntity eventObject, bool canCancel, EventMessages messages /// /// public SaveEventArgs(TEntity eventObject, EventMessages eventMessages) - : base(new List {eventObject}, eventMessages) + : base(new List { eventObject }, eventMessages) { } @@ -66,11 +64,10 @@ public SaveEventArgs(TEntity eventObject, EventMessages eventMessages) /// /// public SaveEventArgs(TEntity eventObject, bool canCancel, EventMessages eventMessages) - : base(new List {eventObject}, canCancel, eventMessages) + : base(new List { eventObject }, canCancel, eventMessages) { } - /// /// Constructor accepting multiple entities that are used in the saving operation /// @@ -95,7 +92,7 @@ public SaveEventArgs(IEnumerable eventObject) /// /// public SaveEventArgs(TEntity eventObject) - : base(new List {eventObject}) + : base(new List { eventObject }) { } @@ -105,7 +102,7 @@ public SaveEventArgs(TEntity eventObject) /// /// public SaveEventArgs(TEntity eventObject, bool canCancel) - : base(new List {eventObject}, canCancel) + : base(new List { eventObject }, canCancel) { } diff --git a/src/Umbraco.Core/Events/SendEmailEventArgs.cs b/src/Umbraco.Core/Events/SendEmailEventArgs.cs index 094ffc847aa9..2e75d1b58391 100644 --- a/src/Umbraco.Core/Events/SendEmailEventArgs.cs +++ b/src/Umbraco.Core/Events/SendEmailEventArgs.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Email; +using Umbraco.Cms.Core.Models.Email; namespace Umbraco.Cms.Core.Events; diff --git a/src/Umbraco.Core/Events/SendToPublishEventArgs.cs b/src/Umbraco.Core/Events/SendToPublishEventArgs.cs index 815972b8e5c5..a72cd8201293 100644 --- a/src/Umbraco.Core/Events/SendToPublishEventArgs.cs +++ b/src/Umbraco.Core/Events/SendToPublishEventArgs.cs @@ -1,12 +1,14 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; public class SendToPublishEventArgs : CancellableObjectEventArgs { - public SendToPublishEventArgs(TEntity eventObject, bool canCancel) : base(eventObject, canCancel) + public SendToPublishEventArgs(TEntity eventObject, bool canCancel) + : base(eventObject, canCancel) { } - public SendToPublishEventArgs(TEntity eventObject) : base(eventObject) + public SendToPublishEventArgs(TEntity eventObject) + : base(eventObject) { } diff --git a/src/Umbraco.Core/Events/SupersedeEventAttribute.cs b/src/Umbraco.Core/Events/SupersedeEventAttribute.cs index 89cf4f20dd10..21137968f0fb 100644 --- a/src/Umbraco.Core/Events/SupersedeEventAttribute.cs +++ b/src/Umbraco.Core/Events/SupersedeEventAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; /// /// This is used to know if the event arg attributed should supersede another event arg type when diff --git a/src/Umbraco.Core/Events/TransientEventMessagesFactory.cs b/src/Umbraco.Core/Events/TransientEventMessagesFactory.cs index 383be4a6eb23..8495da25b001 100644 --- a/src/Umbraco.Core/Events/TransientEventMessagesFactory.cs +++ b/src/Umbraco.Core/Events/TransientEventMessagesFactory.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; /// /// A simple/default transient messages factory diff --git a/src/Umbraco.Core/Events/TypedEventHandler.cs b/src/Umbraco.Core/Events/TypedEventHandler.cs index d65dc9e9216e..e359bd47f9e1 100644 --- a/src/Umbraco.Core/Events/TypedEventHandler.cs +++ b/src/Umbraco.Core/Events/TypedEventHandler.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Events; +namespace Umbraco.Cms.Core.Events; [Serializable] public delegate void TypedEventHandler(TSender sender, TEventArgs e); diff --git a/src/Umbraco.Core/Events/UserGroupWithUsers.cs b/src/Umbraco.Core/Events/UserGroupWithUsers.cs index b5f15124876e..f3a77e22e6db 100644 --- a/src/Umbraco.Core/Events/UserGroupWithUsers.cs +++ b/src/Umbraco.Core/Events/UserGroupWithUsers.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Models.Membership; namespace Umbraco.Cms.Core.Events; @@ -12,6 +12,8 @@ public UserGroupWithUsers(IUserGroup userGroup, IUser[] addedUsers, IUser[] remo } public IUserGroup UserGroup { get; } + public IUser[] AddedUsers { get; } + public IUser[] RemovedUsers { get; } } diff --git a/src/Umbraco.Core/Events/UserNotificationsHandler.cs b/src/Umbraco.Core/Events/UserNotificationsHandler.cs index 151bdacc8228..042355630f70 100644 --- a/src/Umbraco.Core/Events/UserNotificationsHandler.cs +++ b/src/Umbraco.Core/Events/UserNotificationsHandler.cs @@ -43,8 +43,7 @@ public UserNotificationsHandler(Notifier notifier, ActionCollection actions, ICo public void Handle(AssignedUserGroupPermissionsNotification notification) { - IContent[] entities = _contentService.GetByIds(notification.EntityPermissions.Select(e => e.EntityId)) - ?.ToArray(); + IContent[]? entities = _contentService.GetByIds(notification.EntityPermissions.Select(e => e.EntityId)).ToArray(); if (entities?.Any() == false) { return; @@ -59,7 +58,8 @@ public void Handle(ContentCopiedNotification notification) => public void Handle(ContentMovedNotification notification) { // notify about the move for all moved items - _notifier.Notify(_actions.GetAction(), + _notifier.Notify( + _actions.GetAction(), notification.MoveInfoCollection.Select(m => m.Entity).ToArray()); // for any items being moved from the recycle bin (restored), explicitly notify about that too @@ -87,18 +87,18 @@ public void Handle(ContentSavedNotification notification) var newEntities = new List(); var updatedEntities = new List(); - //need to determine if this is updating or if it is new + // need to determine if this is updating or if it is new foreach (IContent entity in notification.SavedEntities) { var dirty = (IRememberBeingDirty)entity; if (dirty.WasPropertyDirty("Id")) { - //it's new + // it's new newEntities.Add(entity); } else { - //it's updating + // it's updating updatedEntities.Add(entity); } } @@ -125,7 +125,7 @@ public void Handle(ContentSortedNotification notification) return; } - IContent parent = _contentService.GetById(parentId[0]); + IContent? parent = _contentService.GetById(parentId[0]); if (parent == null) { return; // this shouldn't happen @@ -139,14 +139,13 @@ public void Handle(ContentUnpublishedNotification notification) => public void Handle(PublicAccessEntrySavedNotification notification) { - IContent[] entities = _contentService.GetByIds(notification.SavedEntities.Select(e => e.ProtectedNodeId)) - ?.ToArray(); - if (entities?.Any() == false) + IContent[] entities = _contentService.GetByIds(notification.SavedEntities.Select(e => e.ProtectedNodeId)).ToArray(); + if (entities.Any() == false) { return; } - _notifier.Notify(_actions.GetAction(), entities!); + _notifier.Notify(_actions.GetAction(), entities); } /// @@ -187,9 +186,9 @@ public Notifier( public void Notify(IAction? action, params IContent[] entities) { - IUser user = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser; + IUser? user = _backOfficeSecurityAccessor?.BackOfficeSecurity?.CurrentUser; - //if there is no current user, then use the admin + // if there is no current user, then use the admin if (user == null) { _logger.LogDebug( @@ -220,7 +219,7 @@ private void SendNotification(IUser sender, IEnumerable entities, IAct return; } - //group by the content type variation since the emails will be different + // group by the content type variation since the emails will be different foreach (IGrouping contentVariantGroup in entities.GroupBy(x => x.ContentType.Variations)) { @@ -232,26 +231,24 @@ private void SendNotification(IUser sender, IEnumerable entities, IAct siteUri, x => _textService.Localize( - "notifications", "mailSubject", - x.user.GetUserCulture(_textService, _globalSettings), - new[] {x.subject.SiteUrl, x.subject.Action, x.subject.ItemName}), + "notifications", "mailSubject", x.user.GetUserCulture(_textService, _globalSettings), new[] { x.subject.SiteUrl, x.subject.Action, x.subject.ItemName }), x => _textService.Localize( - "notifications", x.isHtml ? "mailBodyHtml" : "mailBody", + "notifications", + x.isHtml ? "mailBodyHtml" : "mailBody", x.user.GetUserCulture(_textService, _globalSettings), new[] { x.body.RecipientName, x.body.Action, x.body.ItemName, x.body.EditedUser, x.body.SiteUrl, x.body.ItemId, - //format the summary depending on if it's variant or not + + // format the summary depending on if it's variant or not contentVariantGroup.Key == ContentVariation.Culture ? x.isHtml - ? _textService.Localize("notifications", "mailBodyVariantHtmlSummary", - new[] {x.body.Summary}) - : _textService.Localize("notifications", "mailBodyVariantSummary", - new[] {x.body.Summary}) + ? _textService.Localize("notifications", "mailBodyVariantHtmlSummary", new[] { x.body.Summary }) + : _textService.Localize("notifications", "mailBodyVariantSummary", new[] { x.body.Summary }) : x.body.Summary, - x.body.ItemUrl + x.body.ItemUrl, })); } } diff --git a/src/Umbraco.Core/Exceptions/PanicException.cs b/src/Umbraco.Core/Exceptions/PanicException.cs index cec361d4f166..99ce96c27301 100644 --- a/src/Umbraco.Core/Exceptions/PanicException.cs +++ b/src/Umbraco.Core/Exceptions/PanicException.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Exceptions; diff --git a/src/Umbraco.Core/Exceptions/UnattendedInstallException.cs b/src/Umbraco.Core/Exceptions/UnattendedInstallException.cs index b4c998f151f0..f65da5074522 100644 --- a/src/Umbraco.Core/Exceptions/UnattendedInstallException.cs +++ b/src/Umbraco.Core/Exceptions/UnattendedInstallException.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Exceptions; @@ -19,7 +19,8 @@ public UnattendedInstallException() /// Initializes a new instance of the class with a specified error message. /// /// The message that describes the error. - public UnattendedInstallException(string message) : base(message) + public UnattendedInstallException(string message) + : base(message) { } @@ -29,7 +30,8 @@ public UnattendedInstallException(string message) : base(message) /// /// The message that describes the error. /// The inner exception, or null. - public UnattendedInstallException(string message, Exception innerException) : base(message, innerException) + public UnattendedInstallException(string message, Exception innerException) + : base(message, innerException) { } @@ -44,7 +46,8 @@ public UnattendedInstallException(string message, Exception innerException) : ba /// The that contains contextual /// information about the source or destination. /// - protected UnattendedInstallException(SerializationInfo info, StreamingContext context) : base(info, context) + protected UnattendedInstallException(SerializationInfo info, StreamingContext context) + : base(info, context) { } } diff --git a/src/Umbraco.Core/Extensions/MediaTypeExtensions.cs b/src/Umbraco.Core/Extensions/MediaTypeExtensions.cs index d29df4f4993b..f9aec08a6194 100644 --- a/src/Umbraco.Core/Extensions/MediaTypeExtensions.cs +++ b/src/Umbraco.Core/Extensions/MediaTypeExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core; diff --git a/src/Umbraco.Core/Extensions/NameValueCollectionExtensions.cs b/src/Umbraco.Core/Extensions/NameValueCollectionExtensions.cs index 155c68cc4863..f8fdcdc83f7f 100644 --- a/src/Umbraco.Core/Extensions/NameValueCollectionExtensions.cs +++ b/src/Umbraco.Core/Extensions/NameValueCollectionExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.Collections.Specialized; diff --git a/src/Umbraco.Core/Extensions/ObjectExtensions.cs b/src/Umbraco.Core/Extensions/ObjectExtensions.cs index aa3c38fc9ab0..6dc220446baa 100644 --- a/src/Umbraco.Core/Extensions/ObjectExtensions.cs +++ b/src/Umbraco.Core/Extensions/ObjectExtensions.cs @@ -27,10 +27,10 @@ public static class ObjectExtensions private static readonly ConcurrentDictionary AssignableTypeCache = new(); private static readonly ConcurrentDictionary BoolConvertCache = new(); - private static readonly char[] NumberDecimalSeparatorsToNormalize = {'.', ','}; + private static readonly char[] NumberDecimalSeparatorsToNormalize = { '.', ',' }; private static readonly CustomBooleanTypeConverter CustomBooleanTypeConverter = new(); - //private static readonly ConcurrentDictionary> ObjectFactoryCache = new ConcurrentDictionary>(); + // private static readonly ConcurrentDictionary> ObjectFactoryCache = new ConcurrentDictionary>(); /// /// @@ -158,7 +158,7 @@ public static Attempt TryConvertTo(this object? input) // Any other generic types need to fall through if (target.IsGenericType) { - Type underlying = GetCachedGenericNullableType(target); + Type? underlying = GetCachedGenericNullableType(target); if (underlying != null) { // Special case for empty strings for bools/dates which should return null if an empty string. @@ -173,7 +173,7 @@ public static Attempt TryConvertTo(this object? input) } // Recursively call into this method with the inner (not-nullable) type and handle the outcome - Attempt inner = input.TryConvertTo(underlying); + Attempt inner = input.TryConvertTo(underlying); // And if successful, fall on through to rewrap in a nullable; if failed, pass on the exception if (inner.Success) @@ -189,13 +189,12 @@ public static Attempt TryConvertTo(this object? input) else { // target is not a generic type - if (input is string inputString) { // Try convert from string, returns an Attempt if the string could be // processed (either succeeded or failed), else null if we need to try // other methods - Attempt? result = TryConvertToFromString(inputString, target); + Attempt? result = TryConvertToFromString(inputString, target); if (result.HasValue) { return result.Value; @@ -218,13 +217,13 @@ public static Attempt TryConvertTo(this object? input) } } - TypeConverter inputConverter = GetCachedSourceTypeConverter(inputType, target); + TypeConverter? inputConverter = GetCachedSourceTypeConverter(inputType, target); if (inputConverter != null) { return Attempt.Succeed(inputConverter.ConvertTo(input, target)); } - TypeConverter outputConverter = GetCachedTargetTypeConverter(inputType, target); + TypeConverter? outputConverter = GetCachedTargetTypeConverter(inputType, target); if (outputConverter != null) { return Attempt.Succeed(outputConverter.ConvertFrom(input!)); @@ -252,6 +251,94 @@ public static Attempt TryConvertTo(this object? input) return Attempt.Fail(); } + // public enum PropertyNamesCaseType + // { + // CamelCase, + // CaseInsensitive + // } + + ///// + ///// Convert an object to a JSON string with camelCase formatting + ///// + ///// + ///// + // public static string ToJsonString(this object obj) + // { + // return obj.ToJsonString(PropertyNamesCaseType.CamelCase); + // } + + ///// + ///// Convert an object to a JSON string with the specified formatting + ///// + ///// The obj. + ///// Type of the property names case. + ///// + // public static string ToJsonString(this object obj, PropertyNamesCaseType propertyNamesCaseType) + // { + // var type = obj.GetType(); + // var dateTimeStyle = "yyyy-MM-dd HH:mm:ss"; + + // if (type.IsPrimitive || typeof(string).IsAssignableFrom(type)) + // { + // return obj.ToString(); + // } + + // if (typeof(DateTime).IsAssignableFrom(type) || typeof(DateTimeOffset).IsAssignableFrom(type)) + // { + // return Convert.ToDateTime(obj).ToString(dateTimeStyle); + // } + + // var serializer = new JsonSerializer(); + + // switch (propertyNamesCaseType) + // { + // case PropertyNamesCaseType.CamelCase: + // serializer.ContractResolver = new CamelCasePropertyNamesContractResolver(); + // break; + // } + + // var dateTimeConverter = new IsoDateTimeConverter + // { + // DateTimeStyles = System.Globalization.DateTimeStyles.None, + // DateTimeFormat = dateTimeStyle + // }; + + // if (typeof(IDictionary).IsAssignableFrom(type)) + // { + // return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter); + // } + + // if (type.IsArray || (typeof(IEnumerable).IsAssignableFrom(type))) + // { + // return JArray.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter); + // } + + // return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter); + // } + + /// + /// Converts an object into a dictionary + /// + /// + /// + /// + /// + /// + /// + public static IDictionary? ToDictionary( + this T o, + params Expression>[] ignoreProperties) => o?.ToDictionary(ignoreProperties + .Select(e => o.GetPropertyInfo(e)).Select(propInfo => propInfo.Name).ToArray()); + + internal static void CheckThrowObjectDisposed(this IDisposable disposable, bool isDisposed, string objectname) + { + // TODO: Localize this exception + if (isDisposed) + { + throw new ObjectDisposedException(objectname); + } + } + /// /// Attempts to convert the input string to the output type. /// @@ -321,7 +408,6 @@ public static Attempt TryConvertTo(this object? input) } // TODO: Should we do the decimal trick for short, byte, unsigned? - if (target == typeof(bool)) { if (bool.TryParse(input, out var value)) @@ -405,100 +491,13 @@ public static Attempt TryConvertTo(this object? input) } else if (input != null && target == typeof(Version)) { - return Attempt.If(Version.TryParse(input, out Version value), value); + return Attempt.If(Version.TryParse(input, out Version? value), value); } // E_NOTIMPL IPAddress, BigInteger return null; // we can't decide... } - internal static void CheckThrowObjectDisposed(this IDisposable disposable, bool isDisposed, string objectname) - { - // TODO: Localize this exception - if (isDisposed) - { - throw new ObjectDisposedException(objectname); - } - } - - //public enum PropertyNamesCaseType - //{ - // CamelCase, - // CaseInsensitive - //} - - ///// - ///// Convert an object to a JSON string with camelCase formatting - ///// - ///// - ///// - //public static string ToJsonString(this object obj) - //{ - // return obj.ToJsonString(PropertyNamesCaseType.CamelCase); - //} - - ///// - ///// Convert an object to a JSON string with the specified formatting - ///// - ///// The obj. - ///// Type of the property names case. - ///// - //public static string ToJsonString(this object obj, PropertyNamesCaseType propertyNamesCaseType) - //{ - // var type = obj.GetType(); - // var dateTimeStyle = "yyyy-MM-dd HH:mm:ss"; - - // if (type.IsPrimitive || typeof(string).IsAssignableFrom(type)) - // { - // return obj.ToString(); - // } - - // if (typeof(DateTime).IsAssignableFrom(type) || typeof(DateTimeOffset).IsAssignableFrom(type)) - // { - // return Convert.ToDateTime(obj).ToString(dateTimeStyle); - // } - - // var serializer = new JsonSerializer(); - - // switch (propertyNamesCaseType) - // { - // case PropertyNamesCaseType.CamelCase: - // serializer.ContractResolver = new CamelCasePropertyNamesContractResolver(); - // break; - // } - - // var dateTimeConverter = new IsoDateTimeConverter - // { - // DateTimeStyles = System.Globalization.DateTimeStyles.None, - // DateTimeFormat = dateTimeStyle - // }; - - // if (typeof(IDictionary).IsAssignableFrom(type)) - // { - // return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter); - // } - - // if (type.IsArray || (typeof(IEnumerable).IsAssignableFrom(type))) - // { - // return JArray.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter); - // } - - // return JObject.FromObject(obj, serializer).ToString(Formatting.None, dateTimeConverter); - //} - - /// - /// Converts an object into a dictionary - /// - /// - /// - /// - /// - /// - /// - public static IDictionary? ToDictionary(this T o, - params Expression>[] ignoreProperties) => o?.ToDictionary(ignoreProperties - .Select(e => o.GetPropertyInfo(e)).Select(propInfo => propInfo.Name).ToArray()); - /// /// Turns object into dictionary /// @@ -527,99 +526,6 @@ public static IDictionary ToDictionary(this object o, params return new Dictionary(); } - - internal static string? ToDebugString(this object? obj, int levels = 0) - { - if (obj == null) - { - return "{null}"; - } - - try - { - if (obj is string) - { - return "\"{0}\"".InvariantFormat(obj); - } - - if (obj is int || obj is short || obj is long || obj is float || obj is double || obj is bool || - obj is int? || obj is short? || obj is long? || obj is float? || obj is double? || obj is bool?) - { - return "{0}".InvariantFormat(obj); - } - - if (obj is Enum) - { - return "[{0}]".InvariantFormat(obj); - } - - if (obj is IEnumerable enumerable) - { - var items = (from object enumItem in enumerable - let value = GetEnumPropertyDebugString(enumItem, levels) - where value != null - select value).Take(10).ToList(); - - return items.Any() - ? "{{ {0} }}".InvariantFormat(string.Join(", ", items)) - : null; - } - - PropertyInfo[] props = obj.GetType().GetProperties(); - if (props.Length == 2 && props[0].Name == "Key" && props[1].Name == "Value" && levels > -2) - { - try - { - var key = props[0].GetValue(obj, null) as string; - var value = props[1].GetValue(obj, null).ToDebugString(levels - 1); - return "{0}={1}".InvariantFormat(key, value); - } - catch (Exception) - { - return "[KeyValuePropertyException]"; - } - } - - if (levels > -1) - { - var items = - (from propertyInfo in props - let value = GetPropertyDebugString(propertyInfo, obj, levels) - where value != null - select "{0}={1}".InvariantFormat(propertyInfo.Name, value)).ToArray(); - - return items.Any() - ? "[{0}]:{{ {1} }}".InvariantFormat(obj.GetType().Name, string.Join(", ", items)) - : null; - } - } - catch (Exception ex) - { - return "[Exception:{0}]".InvariantFormat(ex.Message); - } - - return null; - } - - /// - /// Attempts to serialize the value to an XmlString using ToXmlString - /// - /// - /// - /// - internal static Attempt TryConvertToXmlString(this object value, Type type) - { - try - { - var output = value.ToXmlString(type); - return Attempt.Succeed(output); - } - catch (NotSupportedException ex) - { - return Attempt.Fail(ex); - } - } - /// /// Returns an XmlSerialized safe string representation for the value /// @@ -635,7 +541,7 @@ public static string ToXmlString(this object value, Type type) if (type == typeof(string)) { - return value.ToString().IsNullOrWhiteSpace() ? "" : value.ToString()!; + return value.ToString().IsNullOrWhiteSpace() ? string.Empty : value.ToString()!; } if (type == typeof(bool)) @@ -727,6 +633,98 @@ public static string ToXmlString(this object value, Type type) " to a string using ToXmlString as it is not supported by XmlConvert"); } + internal static string? ToDebugString(this object? obj, int levels = 0) + { + if (obj == null) + { + return "{null}"; + } + + try + { + if (obj is string) + { + return "\"{0}\"".InvariantFormat(obj); + } + + if (obj is int || obj is short || obj is long || obj is float || obj is double || obj is bool || + obj is int? || obj is short? || obj is long? || obj is float? || obj is double? || obj is bool?) + { + return "{0}".InvariantFormat(obj); + } + + if (obj is Enum) + { + return "[{0}]".InvariantFormat(obj); + } + + if (obj is IEnumerable enumerable) + { + var items = (from object enumItem in enumerable + let value = GetEnumPropertyDebugString(enumItem, levels) + where value != null + select value).Take(10).ToList(); + + return items.Any() + ? "{{ {0} }}".InvariantFormat(string.Join(", ", items)) + : null; + } + + PropertyInfo[] props = obj.GetType().GetProperties(); + if (props.Length == 2 && props[0].Name == "Key" && props[1].Name == "Value" && levels > -2) + { + try + { + var key = props[0].GetValue(obj, null) as string; + var value = props[1].GetValue(obj, null).ToDebugString(levels - 1); + return "{0}={1}".InvariantFormat(key, value); + } + catch (Exception) + { + return "[KeyValuePropertyException]"; + } + } + + if (levels > -1) + { + var items = + (from propertyInfo in props + let value = GetPropertyDebugString(propertyInfo, obj, levels) + where value != null + select "{0}={1}".InvariantFormat(propertyInfo.Name, value)).ToArray(); + + return items.Any() + ? "[{0}]:{{ {1} }}".InvariantFormat(obj.GetType().Name, string.Join(", ", items)) + : null; + } + } + catch (Exception ex) + { + return "[Exception:{0}]".InvariantFormat(ex.Message); + } + + return null; + } + + /// + /// Attempts to serialize the value to an XmlString using ToXmlString + /// + /// + /// + /// + internal static Attempt TryConvertToXmlString(this object value, Type type) + { + try + { + var output = value.ToXmlString(type); + return Attempt.Succeed(output); + } + catch (NotSupportedException ex) + { + return Attempt.Fail(ex); + } + } + /// /// Returns an XmlSerialized safe string representation for the value and type /// @@ -735,6 +733,8 @@ public static string ToXmlString(this object value, Type type) /// public static string ToXmlString(this object value) => value.ToXmlString(typeof(T)); + public static Guid AsGuid(this object value) => value is Guid guid ? guid : Guid.Empty; + private static string? GetEnumPropertyDebugString(object enumItem, int levels) { try @@ -759,8 +759,6 @@ public static string ToXmlString(this object value, Type type) } } - public static Guid AsGuid(this object value) => value is Guid guid ? guid : Guid.Empty; - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string NormalizeNumberDecimalSeparator(string s) { @@ -774,7 +772,7 @@ private static string NormalizeNumberDecimalSeparator(string s) { var key = new CompositeTypeTypeKey(source, target); - if (InputTypeConverterCache.TryGetValue(key, out TypeConverter typeConverter)) + if (InputTypeConverterCache.TryGetValue(key, out TypeConverter? typeConverter)) { return typeConverter; } @@ -795,7 +793,7 @@ private static string NormalizeNumberDecimalSeparator(string s) { var key = new CompositeTypeTypeKey(source, target); - if (DestinationTypeConverterCache.TryGetValue(key, out TypeConverter typeConverter)) + if (DestinationTypeConverterCache.TryGetValue(key, out TypeConverter? typeConverter)) { return typeConverter; } @@ -814,7 +812,7 @@ private static string NormalizeNumberDecimalSeparator(string s) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Type? GetCachedGenericNullableType(Type type) { - if (NullableGenericCache.TryGetValue(type, out Type underlyingType)) + if (NullableGenericCache.TryGetValue(type, out Type? underlyingType)) { return underlyingType; } diff --git a/src/Umbraco.Core/Extensions/PasswordConfigurationExtensions.cs b/src/Umbraco.Core/Extensions/PasswordConfigurationExtensions.cs index 6a74970c93b8..61b284383ab9 100644 --- a/src/Umbraco.Core/Extensions/PasswordConfigurationExtensions.cs +++ b/src/Umbraco.Core/Extensions/PasswordConfigurationExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Configuration; @@ -18,15 +18,15 @@ public static IDictionary GetConfiguration( bool allowManuallyChangingPassword = false) => new Dictionary { - {"minPasswordLength", passwordConfiguration.RequiredLength}, + { "minPasswordLength", passwordConfiguration.RequiredLength }, // TODO: This doesn't make a ton of sense with asp.net identity and also there's a bunch of other settings // that we can consider with IPasswordConfiguration, but these are currently still based on how membership providers worked. - {"minNonAlphaNumericChars", passwordConfiguration.GetMinNonAlphaNumericChars()}, + { "minNonAlphaNumericChars", passwordConfiguration.GetMinNonAlphaNumericChars() }, // A flag to indicate if the current password box should be shown or not, only a user that has access to change other user/member passwords // doesn't have to specify the current password for the user/member. A user changing their own password must specify their current password. - {"allowManuallyChangingPassword", allowManuallyChangingPassword} + { "allowManuallyChangingPassword", allowManuallyChangingPassword }, }; public static int GetMinNonAlphaNumericChars(this IPasswordConfiguration passwordConfiguration) => diff --git a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs index b2bc5f723d28..f7ad53d7d646 100644 --- a/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs +++ b/src/Umbraco.Core/Extensions/PublishedContentExtensions.cs @@ -25,8 +25,7 @@ public static class PublishedContentExtensions /// The specific culture to get the name for. If null is used the current culture is used (Default is /// null). /// - public static string? Name(this IPublishedContent content, IVariationContextAccessor? variationContextAccessor, - string? culture = null) + public static string? Name(this IPublishedContent content, IVariationContextAccessor? variationContextAccessor, string? culture = null) { if (content == null) { @@ -36,7 +35,7 @@ public static class PublishedContentExtensions // invariant has invariant value (whatever the requested culture) if (!content.ContentType.VariesByCulture()) { - return content.Cultures.TryGetValue(string.Empty, out PublishedCultureInfo invariantInfos) + return content.Cultures.TryGetValue(string.Empty, out PublishedCultureInfo? invariantInfos) ? invariantInfos.Name : null; } @@ -48,7 +47,7 @@ public static class PublishedContentExtensions } // get - return culture != string.Empty && content.Cultures.TryGetValue(culture, out PublishedCultureInfo infos) + return culture != string.Empty && content.Cultures.TryGetValue(culture, out PublishedCultureInfo? infos) ? infos.Name : null; } @@ -66,8 +65,7 @@ public static class PublishedContentExtensions /// The specific culture to get the URL segment for. If null is used the current culture is used /// (Default is null). /// - public static string? UrlSegment(this IPublishedContent content, - IVariationContextAccessor? variationContextAccessor, string? culture = null) + public static string? UrlSegment(this IPublishedContent content, IVariationContextAccessor? variationContextAccessor, string? culture = null) { if (content == null) { @@ -77,7 +75,7 @@ public static class PublishedContentExtensions // invariant has invariant value (whatever the requested culture) if (!content.ContentType.VariesByCulture()) { - return content.Cultures.TryGetValue("", out PublishedCultureInfo invariantInfos) + return content.Cultures.TryGetValue(string.Empty, out PublishedCultureInfo? invariantInfos) ? invariantInfos.UrlSegment : null; } @@ -85,11 +83,11 @@ public static class PublishedContentExtensions // handle context culture for variant if (culture == null) { - culture = variationContextAccessor?.VariationContext?.Culture ?? ""; + culture = variationContextAccessor?.VariationContext?.Culture ?? string.Empty; } // get - return culture != "" && content.Cultures.TryGetValue(culture, out PublishedCultureInfo infos) + return culture != string.Empty && content.Cultures.TryGetValue(culture, out PublishedCultureInfo? infos) ? infos.UrlSegment : null; } @@ -153,8 +151,7 @@ public static bool IsComposedOf(this IPublishedContent content, string alias) => /// specified culture. Otherwise, it is the invariant url. /// /// - public static string Url(this IPublishedContent content, IPublishedUrlProvider publishedUrlProvider, - string? culture = null, UrlMode mode = UrlMode.Default) + public static string Url(this IPublishedContent content, IPublishedUrlProvider publishedUrlProvider, string? culture = null, UrlMode mode = UrlMode.Default) { if (publishedUrlProvider == null) { @@ -198,7 +195,7 @@ public static bool HasCulture(this IPublishedContent content, string? culture) /// /// Culture is case-insensitive. public static bool IsInvariantOrHasCulture(this IPublishedContent content, string culture) - => !content.ContentType.VariesByCulture() || content.Cultures.ContainsKey(culture ?? ""); + => !content.ContentType.VariesByCulture() || content.Cultures.ContainsKey(culture ?? string.Empty); /// /// Filters a sequence of to return invariant items, and items that are published for @@ -210,8 +207,7 @@ public static bool IsInvariantOrHasCulture(this IPublishedContent content, strin /// The specific culture to filter for. If null is used the current culture is used. (Default is /// null). /// - internal static IEnumerable WhereIsInvariantOrHasCulture(this IEnumerable contents, - IVariationContextAccessor variationContextAccessor, string? culture = null) + internal static IEnumerable WhereIsInvariantOrHasCulture(this IEnumerable contents, IVariationContextAccessor variationContextAccessor, string? culture = null) where T : class, IPublishedContent { if (contents == null) @@ -219,7 +215,7 @@ internal static IEnumerable WhereIsInvariantOrHasCulture(this IEnumerable< throw new ArgumentNullException(nameof(contents)); } - culture = culture ?? variationContextAccessor.VariationContext?.Culture ?? ""; + culture = culture ?? variationContextAccessor.VariationContext?.Culture ?? string.Empty; // either does not vary by culture, or has the specified culture return contents.Where(x => !x.ContentType.VariesByCulture() || HasCulture(x, culture)); @@ -234,8 +230,7 @@ internal static IEnumerable WhereIsInvariantOrHasCulture(this IEnumerable< /// The specific culture to get the name for. If null is used the current culture is used (Default is /// null). /// - public static DateTime CultureDate(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, string? culture = null) + public static DateTime CultureDate(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) { // invariant has invariant value (whatever the requested culture) if (!content.ContentType.VariesByCulture()) @@ -246,11 +241,11 @@ public static DateTime CultureDate(this IPublishedContent content, // handle context culture for variant if (culture == null) { - culture = variationContextAccessor?.VariationContext?.Culture ?? ""; + culture = variationContextAccessor?.VariationContext?.Culture ?? string.Empty; } // get - return culture != "" && content.Cultures.TryGetValue(culture, out PublishedCultureInfo infos) + return culture != string.Empty && content.Cultures.TryGetValue(culture, out PublishedCultureInfo? infos) ? infos.Date : DateTime.MinValue; } @@ -270,18 +265,14 @@ public static string GetTemplateAlias(this IPublishedContent content, IFileServi return string.Empty; } - ITemplate template = fileService.GetTemplate(content.TemplateId.Value); + ITemplate? template = fileService.GetTemplate(content.TemplateId.Value); return template?.Alias ?? string.Empty; } - public static bool IsAllowedTemplate(this IPublishedContent content, IContentTypeService contentTypeService, - WebRoutingSettings webRoutingSettings, int templateId) => - content.IsAllowedTemplate(contentTypeService, - webRoutingSettings.DisableAlternativeTemplates, - webRoutingSettings.ValidateAlternativeTemplates, templateId); + public static bool IsAllowedTemplate(this IPublishedContent content, IContentTypeService contentTypeService, WebRoutingSettings webRoutingSettings, int templateId) => + content.IsAllowedTemplate(contentTypeService, webRoutingSettings.DisableAlternativeTemplates, webRoutingSettings.ValidateAlternativeTemplates, templateId); - public static bool IsAllowedTemplate(this IPublishedContent content, IContentTypeService contentTypeService, - bool disableAlternativeTemplates, bool validateAlternativeTemplates, int templateId) + public static bool IsAllowedTemplate(this IPublishedContent content, IContentTypeService contentTypeService, bool disableAlternativeTemplates, bool validateAlternativeTemplates, int templateId) { if (disableAlternativeTemplates) { @@ -293,7 +284,7 @@ public static bool IsAllowedTemplate(this IPublishedContent content, IContentTyp return true; } - IContentType publishedContentContentType = contentTypeService.Get(content.ContentType.Id); + IContentType? publishedContentContentType = contentTypeService.Get(content.ContentType.Id); if (publishedContentContentType == null) { throw new NullReferenceException("No content type returned for published content (contentType='" + @@ -303,13 +294,10 @@ public static bool IsAllowedTemplate(this IPublishedContent content, IContentTyp return publishedContentContentType.IsAllowedTemplate(templateId); } - public static bool IsAllowedTemplate(this IPublishedContent content, IFileService fileService, - IContentTypeService contentTypeService, bool disableAlternativeTemplates, bool validateAlternativeTemplates, - string templateAlias) + public static bool IsAllowedTemplate(this IPublishedContent content, IFileService fileService, IContentTypeService contentTypeService, bool disableAlternativeTemplates, bool validateAlternativeTemplates, string templateAlias) { - ITemplate template = fileService.GetTemplate(templateAlias); - return template != null && content.IsAllowedTemplate(contentTypeService, disableAlternativeTemplates, - validateAlternativeTemplates, template.Id); + ITemplate? template = fileService.GetTemplate(templateAlias); + return template != null && content.IsAllowedTemplate(contentTypeService, disableAlternativeTemplates, validateAlternativeTemplates, template.Id); } #endregion @@ -327,10 +315,9 @@ public static bool IsAllowedTemplate(this IPublishedContent content, IFileServic /// Optional fallback strategy. /// A value indicating whether the content has a value for the property identified by the alias. /// Returns true if HasValue is true, or a fallback strategy can provide a value. - public static bool HasValue(this IPublishedContent content, IPublishedValueFallback publishedValueFallback, - string alias, string? culture = null, string? segment = null, Fallback fallback = default) + public static bool HasValue(this IPublishedContent content, IPublishedValueFallback publishedValueFallback, string alias, string? culture = null, string? segment = null, Fallback fallback = default) { - IPublishedProperty property = content.GetProperty(alias); + IPublishedProperty? property = content.GetProperty(alias); // if we have a property, and it has a value, return that value if (property != null && property.HasValue(culture, segment)) @@ -353,11 +340,16 @@ public static bool HasValue(this IPublishedContent content, IPublishedValueFallb /// Optional fallback strategy. /// The default value. /// The value of the content's property identified by the alias, if it exists, otherwise a default value. - public static object? Value(this IPublishedContent content, IPublishedValueFallback publishedValueFallback, - string alias, string? culture = null, string? segment = null, Fallback fallback = default, + public static object? Value( + this IPublishedContent content, + IPublishedValueFallback publishedValueFallback, + string alias, + string? culture = null, + string? segment = null, + Fallback fallback = default, object? defaultValue = default) { - IPublishedProperty property = content.GetProperty(alias); + IPublishedProperty? property = content.GetProperty(alias); // if we have a property, and it has a value, return that value if (property != null && property.HasValue(culture, segment)) @@ -366,8 +358,7 @@ public static bool HasValue(this IPublishedContent content, IPublishedValueFallb } // else let fallback try to get a value - if (publishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value, - out property)) + if (publishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value, out property)) { return value; } @@ -389,11 +380,16 @@ public static bool HasValue(this IPublishedContent content, IPublishedValueFallb /// Optional fallback strategy. /// The default value. /// The value of the content's property identified by the alias, converted to the specified type. - public static T? Value(this IPublishedContent content, IPublishedValueFallback publishedValueFallback, - string alias, string? culture = null, string? segment = null, Fallback fallback = default, + public static T? Value( + this IPublishedContent content, + IPublishedValueFallback publishedValueFallback, + string alias, + string? culture = null, + string? segment = null, + Fallback fallback = default, T? defaultValue = default) { - IPublishedProperty property = content.GetProperty(alias); + IPublishedProperty? property = content.GetProperty(alias); // if we have a property, and it has a value, return that value if (property != null && property.HasValue(culture, segment)) @@ -402,8 +398,7 @@ public static bool HasValue(this IPublishedContent content, IPublishedValueFallb } // else let fallback try to get a value - if (publishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out T value, - out property)) + if (publishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out T? value, out property)) { return value; } @@ -724,8 +719,7 @@ public static IEnumerable AncestorsOrSelf(this IPublishedContent content, where T : class, IPublishedContent => content.AncestorsOrSelf(maxLevel).FirstOrDefault(); - public static IEnumerable AncestorsOrSelf(this IPublishedContent content, bool orSelf, - Func? func) + public static IEnumerable AncestorsOrSelf(this IPublishedContent content, bool orSelf, Func? func) { IEnumerable ancestorsOrSelf = content.EnumerateAncestors(orSelf); return func == null ? ancestorsOrSelf : ancestorsOrSelf.Where(func); @@ -781,8 +775,11 @@ public static IEnumerable Breadcrumbs(this IPublishedContent /// The breadcrumbs (ancestors and self, top to bottom) for the specified at a level higher /// or equal to . /// - public static IEnumerable Breadcrumbs(this IPublishedContent content, int minLevel, - bool andSelf = true) => content.AncestorsOrSelf(andSelf, n => n.Level >= minLevel).Reverse(); + public static IEnumerable Breadcrumbs( + this IPublishedContent content, + int minLevel, + bool andSelf = true) => + content.AncestorsOrSelf(andSelf, n => n.Level >= minLevel).Reverse(); /// /// Gets the breadcrumbs (ancestors and self, top to bottom) for the specified at a level @@ -798,8 +795,7 @@ public static IEnumerable Breadcrumbs(this IPublishedContent public static IEnumerable Breadcrumbs(this IPublishedContent content, bool andSelf = true) where T : class, IPublishedContent { - static IEnumerable TakeUntil(IEnumerable source, - Func predicate) + static IEnumerable TakeUntil(IEnumerable source, Func predicate) { foreach (IPublishedContent item in source) { @@ -833,8 +829,7 @@ static IEnumerable TakeUntil(IEnumerable s /// This can be useful in order to return all nodes in an entire site by a type when combined with TypedContentAtRoot /// public static IEnumerable DescendantsOrSelfOfType( - this IEnumerable parentNodes, IVariationContextAccessor variationContextAccessor, - string docTypeAlias, string? culture = null) => parentNodes.SelectMany(x => + this IEnumerable parentNodes, IVariationContextAccessor variationContextAccessor, string docTypeAlias, string? culture = null) => parentNodes.SelectMany(x => x.DescendantsOrSelfOfType(variationContextAccessor, docTypeAlias, culture)); /// @@ -850,12 +845,10 @@ public static IEnumerable DescendantsOrSelfOfType( /// /// This can be useful in order to return all nodes in an entire site by a type when combined with TypedContentAtRoot /// - public static IEnumerable DescendantsOrSelf(this IEnumerable parentNodes, - IVariationContextAccessor variationContextAccessor, string? culture = null) + public static IEnumerable DescendantsOrSelf(this IEnumerable parentNodes, IVariationContextAccessor variationContextAccessor, string? culture = null) where T : class, IPublishedContent => parentNodes.SelectMany(x => x.DescendantsOrSelf(variationContextAccessor, culture)); - // as per XPath 1.0 specs �2.2, // - the descendant axis contains the descendants of the context node; a descendant is a child or a child of a child and so on; thus // the descendant axis never contains attribute or namespace nodes. @@ -874,105 +867,85 @@ public static IEnumerable DescendantsOrSelf(this IEnumerable Descendants(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, string? culture = null) => + public static IEnumerable Descendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) => content.DescendantsOrSelf(variationContextAccessor, false, null, culture); - public static IEnumerable Descendants(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, int level, string? culture = null) => + public static IEnumerable Descendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) => content.DescendantsOrSelf(variationContextAccessor, false, p => p.Level >= level, culture); - public static IEnumerable DescendantsOfType(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) => - content.DescendantsOrSelf(variationContextAccessor, false, - p => p.ContentType.Alias.InvariantEquals(contentTypeAlias), culture); + public static IEnumerable DescendantsOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) => + content.DescendantsOrSelf(variationContextAccessor, false, p => p.ContentType.Alias.InvariantEquals(contentTypeAlias), culture); - public static IEnumerable Descendants(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, string? culture = null) + public static IEnumerable Descendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) where T : class, IPublishedContent => content.Descendants(variationContextAccessor, culture).OfType(); - public static IEnumerable Descendants(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, int level, string? culture = null) + public static IEnumerable Descendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) where T : class, IPublishedContent => content.Descendants(variationContextAccessor, level, culture).OfType(); - public static IEnumerable DescendantsOrSelf(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, string? culture = null) => + public static IEnumerable DescendantsOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) => content.DescendantsOrSelf(variationContextAccessor, true, null, culture); - public static IEnumerable DescendantsOrSelf(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, int level, string? culture = null) => + public static IEnumerable DescendantsOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) => content.DescendantsOrSelf(variationContextAccessor, true, p => p.Level >= level, culture); - public static IEnumerable DescendantsOrSelfOfType(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) => - content.DescendantsOrSelf(variationContextAccessor, true, - p => p.ContentType.Alias.InvariantEquals(contentTypeAlias), culture); + public static IEnumerable DescendantsOrSelfOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) => + content.DescendantsOrSelf(variationContextAccessor, true, p => p.ContentType.Alias.InvariantEquals(contentTypeAlias), culture); - public static IEnumerable DescendantsOrSelf(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, string? culture = null) + public static IEnumerable DescendantsOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) where T : class, IPublishedContent => content.DescendantsOrSelf(variationContextAccessor, culture).OfType(); - public static IEnumerable DescendantsOrSelf(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, int level, string? culture = null) + public static IEnumerable DescendantsOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) where T : class, IPublishedContent => content.DescendantsOrSelf(variationContextAccessor, level, culture).OfType(); - public static IPublishedContent? Descendant(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, string? culture = null) => + public static IPublishedContent? Descendant(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) => content.Children(variationContextAccessor, culture)?.FirstOrDefault(); - public static IPublishedContent? Descendant(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, int level, string? culture = null) => content + public static IPublishedContent? Descendant(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) => content .EnumerateDescendants(variationContextAccessor, false, culture).FirstOrDefault(x => x.Level == level); - public static IPublishedContent? DescendantOfType(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) => content + public static IPublishedContent? DescendantOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) => content .EnumerateDescendants(variationContextAccessor, false, culture) .FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias)); - public static T? Descendant(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, - string? culture = null) + public static T? Descendant(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) where T : class, IPublishedContent => content.EnumerateDescendants(variationContextAccessor, false, culture).FirstOrDefault(x => x is T) as T; - public static T? Descendant(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, - int level, string? culture = null) + public static T? Descendant(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) where T : class, IPublishedContent => content.Descendant(variationContextAccessor, level, culture) as T; - public static IPublishedContent DescendantOrSelf(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, string? culture = null) => content; + public static IPublishedContent DescendantOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) => content; - public static IPublishedContent? DescendantOrSelf(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, int level, string? culture = null) => content + public static IPublishedContent? DescendantOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) => content .EnumerateDescendants(variationContextAccessor, true, culture).FirstOrDefault(x => x.Level == level); - public static IPublishedContent? DescendantOrSelfOfType(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) => content + public static IPublishedContent? DescendantOrSelfOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) => content .EnumerateDescendants(variationContextAccessor, true, culture) .FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAlias)); - public static T? DescendantOrSelf(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, string? culture = null) + public static T? DescendantOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) where T : class, IPublishedContent => content.EnumerateDescendants(variationContextAccessor, true, culture).FirstOrDefault(x => x is T) as T; - public static T? DescendantOrSelf(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, int level, string? culture = null) + public static T? DescendantOrSelf(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, int level, string? culture = null) where T : class, IPublishedContent => content.DescendantOrSelf(variationContextAccessor, level, culture) as T; - internal static IEnumerable DescendantsOrSelf(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, bool orSelf, Func? func, - string? culture = null) => content.EnumerateDescendants(variationContextAccessor, orSelf, culture) + internal static IEnumerable DescendantsOrSelf( + this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, + bool orSelf, + Func? func, + string? culture = null) => + content.EnumerateDescendants(variationContextAccessor, orSelf, culture) .Where(x => func == null || func(x)); - internal static IEnumerable EnumerateDescendants(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, bool orSelf, string? culture = null) + internal static IEnumerable EnumerateDescendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, bool orSelf, string? culture = null) { if (content == null) { @@ -984,7 +957,7 @@ internal static IEnumerable EnumerateDescendants(this IPublis yield return content; } - IEnumerable children = content.Children(variationContextAccessor, culture); + IEnumerable? children = content.Children(variationContextAccessor, culture); if (children is not null) { foreach (IPublishedContent desc in children.SelectMany(x => @@ -995,11 +968,10 @@ internal static IEnumerable EnumerateDescendants(this IPublis } } - internal static IEnumerable EnumerateDescendants(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, string? culture = null) + internal static IEnumerable EnumerateDescendants(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) { yield return content; - IEnumerable children = content.Children(variationContextAccessor, culture); + IEnumerable? children = content.Children(variationContextAccessor, culture); if (children is not null) { foreach (IPublishedContent desc in children.SelectMany(x => @@ -1040,16 +1012,15 @@ internal static IEnumerable EnumerateDescendants(this IPublis /// However, if an empty string is specified only invariant children are returned. /// /// - public static IEnumerable? Children(this IPublishedContent content, - IVariationContextAccessor? variationContextAccessor, string? culture = null) + public static IEnumerable? Children(this IPublishedContent content, IVariationContextAccessor? variationContextAccessor, string? culture = null) { // handle context culture for variant if (culture == null) { - culture = variationContextAccessor?.VariationContext?.Culture ?? ""; + culture = variationContextAccessor?.VariationContext?.Culture ?? string.Empty; } - IEnumerable children = content.ChildrenForAllCultures; + IEnumerable? children = content.ChildrenForAllCultures; return culture == "*" ? children : children?.Where(x => x.IsInvariantOrHasCulture(culture)); @@ -1059,7 +1030,7 @@ internal static IEnumerable EnumerateDescendants(this IPublis /// Gets the children of the content, filtered by a predicate. /// /// The content. - /// Published snapshot instance + /// The accessor for VariationContext /// The predicate. /// /// The specific culture to filter for. If null is used the current culture is used. (Default is @@ -1069,23 +1040,25 @@ internal static IEnumerable EnumerateDescendants(this IPublis /// /// Children are sorted by their sortOrder. /// - public static IEnumerable? Children(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, Func predicate, - string? culture = null) => content.Children(variationContextAccessor, culture)?.Where(predicate); + public static IEnumerable? Children( + this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, + Func predicate, + string? culture = null) => + content.Children(variationContextAccessor, culture)?.Where(predicate); /// /// Gets the children of the content, of any of the specified types. /// /// The content. - /// Published snapshot instance + /// The accessor for the VariationContext /// /// The specific culture to filter for. If null is used the current culture is used. (Default is /// null) /// /// The content type alias. /// The children of the content, of any of the specified types. - public static IEnumerable? ChildrenOfType(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, string? contentTypeAlias, string? culture = null) => + public static IEnumerable? ChildrenOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? contentTypeAlias, string? culture = null) => content.Children(variationContextAccessor, x => x.ContentType.Alias.InvariantEquals(contentTypeAlias), culture); /// @@ -1093,7 +1066,7 @@ internal static IEnumerable EnumerateDescendants(this IPublis /// /// The content type. /// The content. - /// Published snapshot instance + /// The accessor for the VariationContext /// /// The specific culture to filter for. If null is used the current culture is used. (Default is /// null) @@ -1102,37 +1075,29 @@ internal static IEnumerable EnumerateDescendants(this IPublis /// /// Children are sorted by their sortOrder. /// - public static IEnumerable? Children(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, string? culture = null) + public static IEnumerable? Children(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) where T : class, IPublishedContent => content.Children(variationContextAccessor, culture)?.OfType(); - public static IPublishedContent? FirstChild(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, string? culture = null) => + public static IPublishedContent? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) => content.Children(variationContextAccessor, culture)?.FirstOrDefault(); /// /// Gets the first child of the content, of a given content type. /// - public static IPublishedContent? FirstChildOfType(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) => + public static IPublishedContent? FirstChildOfType(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string contentTypeAlias, string? culture = null) => content.ChildrenOfType(variationContextAccessor, contentTypeAlias, culture)?.FirstOrDefault(); - public static IPublishedContent? FirstChild(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, Func predicate, - string? culture = null) => content.Children(variationContextAccessor, predicate, culture)?.FirstOrDefault(); + public static IPublishedContent? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, Func predicate, string? culture = null) => content.Children(variationContextAccessor, predicate, culture)?.FirstOrDefault(); - public static IPublishedContent? FirstChild(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, Guid uniqueId, string? culture = null) => content + public static IPublishedContent? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, Guid uniqueId, string? culture = null) => content .Children(variationContextAccessor, x => x.Key == uniqueId, culture)?.FirstOrDefault(); - public static T? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, - string? culture = null) + public static T? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, string? culture = null) where T : class, IPublishedContent => content.Children(variationContextAccessor, culture)?.FirstOrDefault(); - public static T? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, - Func predicate, string? culture = null) + public static T? FirstChild(this IPublishedContent content, IVariationContextAccessor variationContextAccessor, Func predicate, string? culture = null) where T : class, IPublishedContent => content.Children(variationContextAccessor, culture)?.FirstOrDefault(predicate); @@ -1154,10 +1119,12 @@ internal static IEnumerable EnumerateDescendants(this IPublis /// /// Note that in V7 this method also return the content node self. /// - public static IEnumerable? Siblings(this IPublishedContent content, - IPublishedSnapshot? publishedSnapshot, IVariationContextAccessor variationContextAccessor, - string? culture = null) => SiblingsAndSelf(content, publishedSnapshot, variationContextAccessor, culture) - ?.Where(x => x.Id != content.Id); + public static IEnumerable? Siblings( + this IPublishedContent content, + IPublishedSnapshot? publishedSnapshot, + IVariationContextAccessor variationContextAccessor, + string? culture = null) => + SiblingsAndSelf(content, publishedSnapshot, variationContextAccessor, culture)?.Where(x => x.Id != content.Id); /// /// Gets the siblings of the content, of a given content type. @@ -1174,9 +1141,12 @@ internal static IEnumerable EnumerateDescendants(this IPublis /// /// Note that in V7 this method also return the content node self. /// - public static IEnumerable? SiblingsOfType(this IPublishedContent content, - IPublishedSnapshot? publishedSnapshot, IVariationContextAccessor variationContextAccessor, - string contentTypeAlias, string? culture = null) => + public static IEnumerable? SiblingsOfType( + this IPublishedContent content, + IPublishedSnapshot? publishedSnapshot, + IVariationContextAccessor variationContextAccessor, + string contentTypeAlias, + string? culture = null) => SiblingsAndSelfOfType(content, publishedSnapshot, variationContextAccessor, contentTypeAlias, culture) ?.Where(x => x.Id != content.Id); @@ -1195,8 +1165,7 @@ internal static IEnumerable EnumerateDescendants(this IPublis /// /// Note that in V7 this method also return the content node self. /// - public static IEnumerable? Siblings(this IPublishedContent content, IPublishedSnapshot? publishedSnapshot, - IVariationContextAccessor variationContextAccessor, string? culture = null) + public static IEnumerable? Siblings(this IPublishedContent content, IPublishedSnapshot? publishedSnapshot, IVariationContextAccessor variationContextAccessor, string? culture = null) where T : class, IPublishedContent => SiblingsAndSelf(content, publishedSnapshot, variationContextAccessor, culture) ?.Where(x => x.Id != content.Id); @@ -1212,8 +1181,10 @@ internal static IEnumerable EnumerateDescendants(this IPublis /// null) /// /// The siblings of the content including the node itself. - public static IEnumerable? SiblingsAndSelf(this IPublishedContent content, - IPublishedSnapshot? publishedSnapshot, IVariationContextAccessor variationContextAccessor, + public static IEnumerable? SiblingsAndSelf( + this IPublishedContent content, + IPublishedSnapshot? publishedSnapshot, + IVariationContextAccessor variationContextAccessor, string? culture = null) => content.Parent != null ? content.Parent.Children(variationContextAccessor, culture) @@ -1232,9 +1203,12 @@ internal static IEnumerable EnumerateDescendants(this IPublis /// /// The content type alias. /// The siblings of the content including the node itself, of the given content type. - public static IEnumerable? SiblingsAndSelfOfType(this IPublishedContent content, - IPublishedSnapshot? publishedSnapshot, IVariationContextAccessor variationContextAccessor, - string contentTypeAlias, string? culture = null) => + public static IEnumerable? SiblingsAndSelfOfType( + this IPublishedContent content, + IPublishedSnapshot? publishedSnapshot, + IVariationContextAccessor variationContextAccessor, + string contentTypeAlias, + string? culture = null) => content.Parent != null ? content.Parent.ChildrenOfType(variationContextAccessor, contentTypeAlias, culture) : publishedSnapshot?.Content?.GetAtRoot(culture).OfTypes(contentTypeAlias) @@ -1252,8 +1226,10 @@ internal static IEnumerable EnumerateDescendants(this IPublis /// null) /// /// The siblings of the content including the node itself, of the given content type. - public static IEnumerable? SiblingsAndSelf(this IPublishedContent content, - IPublishedSnapshot? publishedSnapshot, IVariationContextAccessor variationContextAccessor, + public static IEnumerable? SiblingsAndSelf( + this IPublishedContent content, + IPublishedSnapshot? publishedSnapshot, + IVariationContextAccessor variationContextAccessor, string? culture = null) where T : class, IPublishedContent => content.Parent != null @@ -1304,13 +1280,13 @@ internal static IEnumerable EnumerateDescendants(this IPublis public static string? GetCreatorName(this IPublishedContent content, IUserService userService) { - IProfile user = userService.GetProfileById(content.CreatorId); + IProfile? user = userService.GetProfileById(content.CreatorId); return user?.Name; } public static string? GetWriterName(this IPublishedContent content, IUserService userService) { - IProfile user = userService.GetProfileById(content.WriterId); + IProfile? user = userService.GetProfileById(content.WriterId); return user?.Name; } @@ -1333,12 +1309,16 @@ internal static IEnumerable EnumerateDescendants(this IPublis /// null) /// /// The children of the content. - public static DataTable ChildrenAsTable(this IPublishedContent content, - IVariationContextAccessor variationContextAccessor, IContentTypeService contentTypeService, - IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, - IPublishedUrlProvider publishedUrlProvider, string contentTypeAliasFilter = "", string? culture = null) - => GenerateDataTable(content, variationContextAccessor, contentTypeService, mediaTypeService, memberTypeService, - publishedUrlProvider, contentTypeAliasFilter, culture); + public static DataTable ChildrenAsTable( + this IPublishedContent content, + IVariationContextAccessor variationContextAccessor, + IContentTypeService contentTypeService, + IMediaTypeService mediaTypeService, + IMemberTypeService memberTypeService, + IPublishedUrlProvider publishedUrlProvider, + string contentTypeAliasFilter = "", + string? culture = null) + => GenerateDataTable(content, variationContextAccessor, contentTypeService, mediaTypeService, memberTypeService, publishedUrlProvider, contentTypeAliasFilter, culture); /// /// Gets the children of the content in a DataTable. @@ -1355,12 +1335,17 @@ public static DataTable ChildrenAsTable(this IPublishedContent content, /// null) /// /// The children of the content. - private static DataTable GenerateDataTable(IPublishedContent content, - IVariationContextAccessor variationContextAccessor, IContentTypeService contentTypeService, - IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, - IPublishedUrlProvider publishedUrlProvider, string contentTypeAliasFilter = "", string? culture = null) + private static DataTable GenerateDataTable( + IPublishedContent content, + IVariationContextAccessor variationContextAccessor, + IContentTypeService contentTypeService, + IMediaTypeService mediaTypeService, + IMemberTypeService memberTypeService, + IPublishedUrlProvider publishedUrlProvider, + string contentTypeAliasFilter = "", + string? culture = null) { - IPublishedContent firstNode = contentTypeAliasFilter.IsNullOrWhiteSpace() + IPublishedContent? firstNode = contentTypeAliasFilter.IsNullOrWhiteSpace() ? content.Children(variationContextAccessor, culture)?.Any() ?? false ? content.Children(variationContextAccessor, culture)?.ElementAt(0) : null @@ -1368,46 +1353,49 @@ private static DataTable GenerateDataTable(IPublishedContent content, ?.FirstOrDefault(x => x.ContentType.Alias.InvariantEquals(contentTypeAliasFilter)); if (firstNode == null) { - return new DataTable(); //no children found + // No children found + return new DataTable(); } - //use new utility class to create table so that we don't have to maintain code in many places, just one + // use new utility class to create table so that we don't have to maintain code in many places, just one DataTable dt = DataTableExtensions.GenerateDataTable( - //pass in the alias of the first child node since this is the node type we're rendering headers for + + // pass in the alias of the first child node since this is the node type we're rendering headers for firstNode.ContentType.Alias, - //pass in the callback to extract the Dictionary of all defined aliases to their names + + // pass in the callback to extract the Dictionary of all defined aliases to their names alias => GetPropertyAliasesAndNames(contentTypeService, mediaTypeService, memberTypeService, alias), - //pass in a callback to populate the datatable, yup its a bit ugly but it's already legacy and we just want to maintain code in one place. () => { - //create all row data - List>, IEnumerable>>> + // here we pass in a callback to populate the datatable, yup its a bit ugly but it's already legacy and we just want to maintain code in one place. + // create all row data + List>, IEnumerable>>> tableData = DataTableExtensions.CreateTableData(); - IOrderedEnumerable children = + IOrderedEnumerable? children = content.Children(variationContextAccessor)?.OrderBy(x => x.SortOrder); if (children is not null) { - //loop through each child and create row data for it + // loop through each child and create row data for it foreach (IPublishedContent n in children) { if (contentTypeAliasFilter.IsNullOrWhiteSpace() == false) { if (n.ContentType.Alias.InvariantEquals(contentTypeAliasFilter) == false) { - continue; //skip this one, it doesn't match the filter + continue; // skip this one, it doesn't match the filter } } var standardVals = new Dictionary { - {"Id", n.Id}, - {"NodeName", n.Name(variationContextAccessor)}, - {"NodeTypeAlias", n.ContentType.Alias}, - {"CreateDate", n.CreateDate}, - {"UpdateDate", n.UpdateDate}, - {"CreatorId", n.CreatorId}, - {"WriterId", n.WriterId}, - {"Url", n.Url(publishedUrlProvider)} + { "Id", n.Id }, + { "NodeName", n.Name(variationContextAccessor) }, + { "NodeTypeAlias", n.ContentType.Alias }, + { "CreateDate", n.CreateDate }, + { "UpdateDate", n.UpdateDate }, + { "CreatorId", n.CreatorId }, + { "WriterId", n.WriterId }, + { "Url", n.Url(publishedUrlProvider) }, }; var userVals = new Dictionary(); @@ -1420,7 +1408,7 @@ private static DataTable GenerateDataTable(IPublishedContent content, userVals[p.Alias] = p.GetValue(); } - //add the row data + // Add the row data DataTableExtensions.AddRowData(tableData, standardVals, userVals); } } @@ -1434,8 +1422,7 @@ private static DataTable GenerateDataTable(IPublishedContent content, #region PropertyAliasesAndNames - private static Func>? - _getPropertyAliasesAndNames; + private static Func>? _getPropertyAliasesAndNames; /// /// This is used only for unit tests to set the delegate to look up aliases/names dictionary of a content type @@ -1447,25 +1434,24 @@ internal static Func _getPropertyAliasesAndNames = value; } - private static Dictionary GetAliasesAndNames(IContentTypeService contentTypeService, - IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, string alias) + private static Dictionary GetAliasesAndNames(IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, string alias) { - IContentTypeBase type = contentTypeService.Get(alias) - ?? mediaTypeService.Get(alias) - ?? (IContentTypeBase?)memberTypeService.Get(alias); + IContentTypeBase? type = contentTypeService.Get(alias) + ?? mediaTypeService.Get(alias) + ?? (IContentTypeBase?)memberTypeService.Get(alias); Dictionary fields = GetAliasesAndNames(type); // ensure the standard fields are there var stdFields = new Dictionary { - {"Id", "Id"}, - {"NodeName", "NodeName"}, - {"NodeTypeAlias", "NodeTypeAlias"}, - {"CreateDate", "CreateDate"}, - {"UpdateDate", "UpdateDate"}, - {"CreatorId", "CreatorId"}, - {"WriterId", "WriterId"}, - {"Url", "Url"} + { "Id", "Id" }, + { "NodeName", "NodeName" }, + { "NodeTypeAlias", "NodeTypeAlias" }, + { "CreateDate", "CreateDate" }, + { "UpdateDate", "UpdateDate" }, + { "CreatorId", "CreatorId" }, + { "WriterId", "WriterId" }, + { "Url", "Url" }, }; foreach (KeyValuePair field in stdFields.Where(x => fields.ContainsKey(x.Key) == false)) diff --git a/src/Umbraco.Core/Extensions/PublishedElementExtensions.cs b/src/Umbraco.Core/Extensions/PublishedElementExtensions.cs index bea09a492dd6..c85178c85c49 100644 --- a/src/Umbraco.Core/Extensions/PublishedElementExtensions.cs +++ b/src/Umbraco.Core/Extensions/PublishedElementExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core; @@ -68,10 +68,9 @@ public static bool HasProperty(this IPublishedElement content, string alias) => /// Returns true if GetProperty(alias) is not null and GetProperty(alias).HasValue is /// true. /// - public static bool HasValue(this IPublishedElement content, string alias, string? culture = null, - string? segment = null) + public static bool HasValue(this IPublishedElement content, string alias, string? culture = null, string? segment = null) { - IPublishedProperty prop = content.GetProperty(alias); + IPublishedProperty? prop = content.GetProperty(alias); return prop != null && prop.HasValue(culture, segment); } @@ -105,11 +104,16 @@ public static bool HasValue(this IPublishedElement content, string alias, string /// /// The alias is case-insensitive. /// - public static object? Value(this IPublishedElement content, IPublishedValueFallback publishedValueFallback, - string alias, string? culture = null, string? segment = null, Fallback fallback = default, + public static object? Value( + this IPublishedElement content, + IPublishedValueFallback publishedValueFallback, + string alias, + string? culture = null, + string? segment = null, + Fallback fallback = default, object? defaultValue = default) { - IPublishedProperty property = content.GetProperty(alias); + IPublishedProperty? property = content.GetProperty(alias); // if we have a property, and it has a value, return that value if (property != null && property.HasValue(culture, segment)) @@ -159,11 +163,16 @@ public static bool HasValue(this IPublishedElement content, string alias, string /// /// The alias is case-insensitive. /// - public static T? Value(this IPublishedElement content, IPublishedValueFallback publishedValueFallback, - string alias, string? culture = null, string? segment = null, Fallback fallback = default, + public static T? Value( + this IPublishedElement content, + IPublishedValueFallback publishedValueFallback, + string alias, + string? culture = null, + string? segment = null, + Fallback fallback = default, T? defaultValue = default) { - IPublishedProperty property = content.GetProperty(alias); + IPublishedProperty? property = content.GetProperty(alias); // if we have a property, and it has a value, return that value if (property != null && property.HasValue(culture, segment)) @@ -172,7 +181,7 @@ public static bool HasValue(this IPublishedElement content, string alias, string } // else let fallback try to get a value - if (publishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out T value)) + if (publishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out T? value)) { return value; } @@ -214,6 +223,7 @@ public static IndexedArrayItem[] ToIndexedArray(this IEnumer /// the content is visible. /// public static bool IsVisible(this IPublishedElement content, IPublishedValueFallback publishedValueFallback) => + // rely on the property converter - will return default bool value, ie false, if property // is not defined, or has no value, else will return its value. content.Value(publishedValueFallback, Constants.Conventions.Content.NaviHide) == false; @@ -238,8 +248,12 @@ public static bool IsVisible(this IPublishedElement content, IPublishedValueFall /// specified culture. Otherwise, it is the invariant url. /// /// - public static string MediaUrl(this IPublishedContent content, IPublishedUrlProvider publishedUrlProvider, - string? culture = null, UrlMode mode = UrlMode.Default, string propertyAlias = Constants.Conventions.Media.File) + public static string MediaUrl( + this IPublishedContent content, + IPublishedUrlProvider publishedUrlProvider, + string? culture = null, + UrlMode mode = UrlMode.Default, + string propertyAlias = Constants.Conventions.Media.File) { if (publishedUrlProvider == null) { diff --git a/src/Umbraco.Core/Extensions/PublishedPropertyExtension.cs b/src/Umbraco.Core/Extensions/PublishedPropertyExtension.cs index f935437de2d5..267157cf7acf 100644 --- a/src/Umbraco.Core/Extensions/PublishedPropertyExtension.cs +++ b/src/Umbraco.Core/Extensions/PublishedPropertyExtension.cs @@ -13,8 +13,7 @@ public static class PublishedPropertyExtension { #region Value - public static object? Value(this IPublishedProperty property, IPublishedValueFallback publishedValueFallback, - string? culture = null, string? segment = null, Fallback fallback = default, object? defaultValue = default) + public static object? Value(this IPublishedProperty property, IPublishedValueFallback publishedValueFallback, string? culture = null, string? segment = null, Fallback fallback = default, object? defaultValue = default) { if (property.HasValue(culture, segment)) { @@ -30,8 +29,7 @@ public static class PublishedPropertyExtension #region Value - public static T? Value(this IPublishedProperty property, IPublishedValueFallback publishedValueFallback, - string? culture = null, string? segment = null, Fallback fallback = default, T? defaultValue = default) + public static T? Value(this IPublishedProperty property, IPublishedValueFallback publishedValueFallback, string? culture = null, string? segment = null, Fallback fallback = default, T? defaultValue = default) { if (property.HasValue(culture, segment)) { @@ -55,7 +53,7 @@ public static class PublishedPropertyExtension } // we don't have a value, try fallback - if (publishedValueFallback.TryGetValue(property, culture, segment, fallback, defaultValue, out T fallbackValue)) + if (publishedValueFallback.TryGetValue(property, culture, segment, fallback, defaultValue, out T? fallbackValue)) { return fallbackValue; } diff --git a/src/Umbraco.Core/Extensions/PublishedSnapshotAccessorExtensions.cs b/src/Umbraco.Core/Extensions/PublishedSnapshotAccessorExtensions.cs index 9e080cc58ea7..5e6d356674a6 100644 --- a/src/Umbraco.Core/Extensions/PublishedSnapshotAccessorExtensions.cs +++ b/src/Umbraco.Core/Extensions/PublishedSnapshotAccessorExtensions.cs @@ -7,7 +7,7 @@ public static class PublishedSnapshotAccessorExtensions public static IPublishedSnapshot GetRequiredPublishedSnapshot( this IPublishedSnapshotAccessor publishedSnapshotAccessor) { - if (publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot publishedSnapshot)) + if (publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot)) { return publishedSnapshot!; } diff --git a/src/Umbraco.Core/Extensions/RequestHandlerSettingsExtension.cs b/src/Umbraco.Core/Extensions/RequestHandlerSettingsExtension.cs index 82d1320aaa80..475f093785b2 100644 --- a/src/Umbraco.Core/Extensions/RequestHandlerSettingsExtension.cs +++ b/src/Umbraco.Core/Extensions/RequestHandlerSettingsExtension.cs @@ -28,14 +28,16 @@ public static IEnumerable GetCharReplacements(this RequestHandlerSetti return RequestHandlerSettings.DefaultCharCollection; } - return MergeUnique(requestHandlerSettings.UserDefinedCharCollection, + return MergeUnique( + requestHandlerSettings.UserDefinedCharCollection, RequestHandlerSettings.DefaultCharCollection); } /// /// Merges CharCollection and UserDefinedCharCollection, prioritizing UserDefinedCharCollection /// - internal static void MergeReplacements(this RequestHandlerSettings requestHandlerSettings, + internal static void MergeReplacements( + this RequestHandlerSettings requestHandlerSettings, IConfiguration configuration) { var sectionKey = $"{Constants.Configuration.ConfigRequestHandler}:"; @@ -62,7 +64,7 @@ private static IEnumerable GetReplacements(IConfiguration configuratio { var @char = section.GetValue(nameof(CharItem.Char)); var replacement = section.GetValue(nameof(CharItem.Replacement)); - replacements.Add(new CharItem {Char = @char, Replacement = replacement}); + replacements.Add(new CharItem { Char = @char, Replacement = replacement }); } return replacements; diff --git a/src/Umbraco.Core/Extensions/RuntimeStateExtensions.cs b/src/Umbraco.Core/Extensions/RuntimeStateExtensions.cs index 9c64ae944f82..219b73c39f25 100644 --- a/src/Umbraco.Core/Extensions/RuntimeStateExtensions.cs +++ b/src/Umbraco.Core/Extensions/RuntimeStateExtensions.cs @@ -12,9 +12,10 @@ public static class RuntimeStateExtensions /// public static bool EnableInstaller(this IRuntimeState state) => state.Level == RuntimeLevel.Install || state.Level == RuntimeLevel.Upgrade; + // TODO: If we want to enable the installer for package migrations, but IMO i think we should do migrations in the back office // if they are not unattended. - //=> state.Level == RuntimeLevel.Install || state.Level == RuntimeLevel.Upgrade || state.Level == RuntimeLevel.PackageMigrations; + // => state.Level == RuntimeLevel.Install || state.Level == RuntimeLevel.Upgrade || state.Level == RuntimeLevel.PackageMigrations; /// /// Returns true if Umbraco is greater than diff --git a/src/Umbraco.Core/Extensions/SemVersionExtensions.cs b/src/Umbraco.Core/Extensions/SemVersionExtensions.cs index 11ab97b374d2..afdd49612ee0 100644 --- a/src/Umbraco.Core/Extensions/SemVersionExtensions.cs +++ b/src/Umbraco.Core/Extensions/SemVersionExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Semver; @@ -14,6 +14,6 @@ public static string ToSemanticStringWithoutBuild(this SemVersion semVersion) { var version = semVersion.ToSemanticString(); var indexOfBuild = version.IndexOf('+'); - return indexOfBuild >= 0 ? version.Substring(0, indexOfBuild) : version; + return indexOfBuild >= 0 ? version[..indexOfBuild] : version; } } diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index 646bd1cf6a5a..91d6c061efee 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -17,12 +17,19 @@ namespace Umbraco.Extensions; /// public static class StringExtensions { + internal static readonly Lazy Whitespace = new(() => new Regex(@"\s+", RegexOptions.Compiled)); + private const char DefaultEscapedStringEscapeChar = '\\'; private static readonly char[] ToCSharpHexDigitLower = "0123456789abcdef".ToCharArray(); private static readonly char[] ToCSharpEscapeChars; + internal static readonly string[] JsonEmpties = { "[]", "{}" }; + + /// + /// The namespace for URLs (from RFC 4122, Appendix C). + /// See RFC 4122 + /// + internal static readonly Guid UrlNamespace = new("6ba7b811-9dad-11d1-80b4-00c04fd430c8"); - internal static readonly Lazy Whitespace = new(() => new Regex(@"\s+", RegexOptions.Compiled)); - internal static readonly string[] JsonEmpties = {"[]", "{}"}; private static readonly char[] CleanForXssChars = "*?(){}[];:%<>/\\|&'\"".ToCharArray(); // From: http://stackoverflow.com/a/961504/5018 @@ -32,15 +39,9 @@ public static class StringExtensions @"(? - /// The namespace for URLs (from RFC 4122, Appendix C). - /// See RFC 4122 - /// - internal static readonly Guid UrlNamespace = new("6ba7b811-9dad-11d1-80b4-00c04fd430c8"); - static StringExtensions() { - var escapes = new[] {"\aa", "\bb", "\ff", "\nn", "\rr", "\tt", "\vv", "\"\"", "\\\\", "??", "\00"}; + var escapes = new[] { "\aa", "\bb", "\ff", "\nn", "\rr", "\tt", "\vv", "\"\"", "\\\\", "??", "\00" }; ToCSharpEscapeChars = new char[escapes.Max(e => e[0]) + 1]; foreach (var escape in escapes) { @@ -76,7 +77,7 @@ public static int[] GetIdsFromPathReversed(this string path) public static string StripFileExtension(this string fileName) { - //filenames cannot contain line breaks + // filenames cannot contain line breaks if (fileName.Contains(Environment.NewLine) || fileName.Contains("\r") || fileName.Contains("\n")) { return fileName; @@ -85,14 +86,15 @@ public static string StripFileExtension(this string fileName) var lastIndex = fileName.LastIndexOf('.'); if (lastIndex > 0) { - var ext = fileName.Substring(lastIndex); - //file extensions cannot contain whitespace + var ext = fileName[lastIndex..]; + + // file extensions cannot contain whitespace if (ext.Contains(" ")) { return fileName; } - return string.Format("{0}", fileName.Substring(0, fileName.IndexOf(ext, StringComparison.Ordinal))); + return string.Format("{0}", fileName[..fileName.IndexOf(ext, StringComparison.Ordinal)]); } return fileName; @@ -105,7 +107,7 @@ public static string StripFileExtension(this string fileName) /// Extension of the file public static string GetFileExtension(this string file) { - //Find any characters between the last . and the start of a query string or the end of the string + // Find any characters between the last . and the start of a query string or the end of the string const string pattern = @"(?\.[^\.\?]+)(\?.*|$)"; Match match = Regex.Match(file, pattern); return match.Success @@ -136,7 +138,7 @@ public static bool DetectIsEmptyJson(this string input) => public static string ReplaceNonAlphanumericChars(this string input, string replacement) { - //any character that is not alphanumeric, convert to a hyphen + // any character that is not alphanumeric, convert to a hyphen var mName = input; foreach (var c in mName.ToCharArray().Where(c => !char.IsLetterOrDigit(c))) { @@ -166,9 +168,10 @@ public static string ReplaceNonAlphanumericChars(this string input, char replace /// public static string CleanForXss(this string input, params char[] ignoreFromClean) { - //remove any HTML + // remove any HTML input = input.StripHtml(); - //strip out any potential chars involved with XSS + + // strip out any potential chars involved with XSS return input.ExceptChars(new HashSet(CleanForXssChars.Except(ignoreFromClean))); } @@ -183,21 +186,6 @@ public static string ExceptChars(this string str, HashSet toExclude) return sb.ToString(); } - /// - /// Returns a stream from a string - /// - /// - /// - internal static Stream GenerateStreamFromString(this string s) - { - var stream = new MemoryStream(); - var writer = new StreamWriter(stream); - writer.Write(s); - writer.Flush(); - stream.Position = 0; - return stream; - } - /// /// This will append the query string to the URL /// @@ -210,7 +198,7 @@ internal static Stream GenerateStreamFromString(this string s) /// public static string AppendQueryStringToUrl(this string url, params string[] queryStrings) { - //remove any prefixed '&' or '?' + // remove any prefixed '&' or '?' for (var i = 0; i < queryStrings.Length; i++) { queryStrings[i] = queryStrings[i].TrimStart(Constants.CharArrays.QuestionMarkAmpersand) @@ -227,8 +215,22 @@ public static string AppendQueryStringToUrl(this string url, params string[] que return url + string.Join("&", nonEmpty).EnsureStartsWith('?'); } + /// + /// Returns a stream from a string + /// + /// + /// + internal static Stream GenerateStreamFromString(this string s) + { + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + writer.Write(s); + writer.Flush(); + stream.Position = 0; + return stream; + } - //this is from SqlMetal and just makes it a bit of fun to allow pluralization + // this is from SqlMetal and just makes it a bit of fun to allow pluralization public static string MakePluralName(this string name) { if (name.EndsWith("x", StringComparison.OrdinalIgnoreCase) || @@ -236,21 +238,21 @@ public static string MakePluralName(this string name) name.EndsWith("s", StringComparison.OrdinalIgnoreCase) || name.EndsWith("sh", StringComparison.OrdinalIgnoreCase)) { - name = name + "es"; + name += "es"; return name; } if (name.EndsWith("y", StringComparison.OrdinalIgnoreCase) && name.Length > 1 && - !IsVowel(name[name.Length - 2])) + !IsVowel(name[^2])) { name = name.Remove(name.Length - 1, 1); - name = name + "ies"; + name += "ies"; return name; } if (!name.EndsWith("s", StringComparison.OrdinalIgnoreCase)) { - name = name + "s"; + name += "s"; } return name; @@ -375,7 +377,7 @@ public static string TrimStart(this string value, string forRemoving) while (value.StartsWith(forRemoving, StringComparison.InvariantCultureIgnoreCase)) { - value = value.Substring(forRemoving.Length); + value = value[forRemoving.Length..]; } return value; @@ -428,7 +430,7 @@ public static bool IsUpperCase(this char ch) => ch.ToString(CultureInfo.Invarian [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification = "By design")] public static IList ToDelimitedList(this string list, string delimiter = ",") { - var delimiters = new[] {delimiter}; + var delimiters = new[] { delimiter }; return !list.IsNullOrWhiteSpace() ? list.Split(delimiters, StringSplitOptions.RemoveEmptyEntries) .Select(i => i.Trim()) @@ -494,7 +496,7 @@ public static Guid EncodeAsGuid(this string input) var convertToHex = input.ConvertToHex(); var hexLength = convertToHex.Length < 32 ? convertToHex.Length : 32; - var hex = convertToHex.Substring(0, hexLength).PadLeft(32, '0'); + var hex = convertToHex[..hexLength].PadLeft(32, '0'); Guid output = Guid.Empty; return Guid.TryParse(hex, out output) ? output : Guid.Empty; } @@ -522,11 +524,11 @@ public static string ConvertToHex(this string input) public static string DecodeFromHex(this string hexValue) { - var strValue = ""; + var strValue = string.Empty; while (hexValue.Length > 0) { - strValue += Convert.ToChar(Convert.ToUInt32(hexValue.Substring(0, 2), 16)).ToString(); - hexValue = hexValue.Substring(2, hexValue.Length - 2); + strValue += Convert.ToChar(Convert.ToUInt32(hexValue[..2], 16)).ToString(); + hexValue = hexValue[2..]; } return strValue; @@ -549,7 +551,7 @@ public static string ToUrlBase64(this string input) return string.Empty; } - //return Convert.ToBase64String(bytes).Replace(".", "-").Replace("/", "_").Replace("=", ","); + // return Convert.ToBase64String(bytes).Replace(".", "-").Replace("/", "_").Replace("=", ","); var bytes = Encoding.UTF8.GetBytes(input); return UrlTokenEncode(bytes); } @@ -566,11 +568,10 @@ public static string ToUrlBase64(this string input) throw new ArgumentNullException(nameof(input)); } - //if (input.IsInvalidBase64()) return null; - + // if (input.IsInvalidBase64()) return null; try { - //var decodedBytes = Convert.FromBase64String(input.Replace("-", ".").Replace("_", "/").Replace(",", "=")); + // var decodedBytes = Convert.FromBase64String(input.Replace("-", ".").Replace("_", "/").Replace(",", "=")); var decodedBytes = UrlTokenDecode(input); return decodedBytes != null ? Encoding.UTF8.GetString(decodedBytes) : null; } @@ -625,7 +626,6 @@ public static int InvariantIndexOf(this string s, string value) => public static int InvariantLastIndexOf(this string s, string value) => s.LastIndexOf(value, StringComparison.OrdinalIgnoreCase); - /// /// Tries to parse a string into the supplied type by finding and using the Type's "Parse" method /// @@ -676,52 +676,6 @@ public static string GenerateHash(this string str) /// The SHA1 hashed string public static string ToSHA1(this string stringToConvert) => stringToConvert.GenerateHash("SHA1"); - /// - /// Generate a hash of a string based on the hashType passed in - /// - /// Refers to itself - /// - /// String with the hash type. See remarks section of the CryptoConfig Class in MSDN docs for a - /// list of possible values. - /// - /// The hashed string - private static string GenerateHash(this string str, string? hashType) - { - HashAlgorithm? hasher = null; - //create an instance of the correct hashing provider based on the type passed in - if (hashType is not null) - { - hasher = HashAlgorithm.Create(hashType); - } - - if (hasher == null) - { - throw new InvalidOperationException("No hashing type found by name " + hashType); - } - - using (hasher) - { - //convert our string into byte array - var byteArray = Encoding.UTF8.GetBytes(str); - - //get the hashed values created by our selected provider - var hashedByteArray = hasher.ComputeHash(byteArray); - - //create a StringBuilder object - var stringBuilder = new StringBuilder(); - - //loop to each byte - foreach (var b in hashedByteArray) - { - //append it to our StringBuilder - stringBuilder.Append(b.ToString("x2")); - } - - //return the hashed value - return stringBuilder.ToString(); - } - } - /// /// Decodes a string that was encoded with UrlTokenEncode /// @@ -776,6 +730,53 @@ public static byte[] UrlTokenDecode(this string input) return Convert.FromBase64CharArray(inArray, 0, inArray.Length); } + /// + /// Generate a hash of a string based on the hashType passed in + /// + /// Refers to itself + /// + /// String with the hash type. See remarks section of the CryptoConfig Class in MSDN docs for a + /// list of possible values. + /// + /// The hashed string + private static string GenerateHash(this string str, string? hashType) + { + HashAlgorithm? hasher = null; + + // create an instance of the correct hashing provider based on the type passed in + if (hashType is not null) + { + hasher = HashAlgorithm.Create(hashType); + } + + if (hasher == null) + { + throw new InvalidOperationException("No hashing type found by name " + hashType); + } + + using (hasher) + { + // convert our string into byte array + var byteArray = Encoding.UTF8.GetBytes(str); + + // get the hashed values created by our selected provider + var hashedByteArray = hasher.ComputeHash(byteArray); + + // create a StringBuilder object + var stringBuilder = new StringBuilder(); + + // loop to each byte + foreach (var b in hashedByteArray) + { + // append it to our StringBuilder + stringBuilder.Append(b.ToString("x2")); + } + + // return the hashed value + return stringBuilder.ToString(); + } + } + /// /// Encodes a string so that it is 'safe' for URLs, files, etc.. /// @@ -795,7 +796,6 @@ public static string UrlTokenEncode(this byte[] input) // base-64 digits are A-Z, a-z, 0-9, + and / // the = char is used for trailing padding - var str = Convert.ToBase64String(input); var pos = str.IndexOf('='); @@ -870,7 +870,7 @@ public static string Truncate(this string text, int maxLength, string suffix = " return truncatedString; } - truncatedString = text.Substring(0, strLength); + truncatedString = text[..strLength]; truncatedString = truncatedString.TrimEnd(); truncatedString += suffix; @@ -882,7 +882,7 @@ public static string Truncate(this string text, int maxLength, string suffix = " /// /// The input. /// - public static string StripNewLines(this string input) => input.Replace("\r", "").Replace("\n", ""); + public static string StripNewLines(this string input) => input.Replace("\r", string.Empty).Replace("\n", string.Empty); /// /// Converts to single line by replacing line breaks with spaces. @@ -913,7 +913,7 @@ public static string OrIfNullOrWhiteSpace(this string input, string alternative) public static string ToFirstUpper(this string input) => string.IsNullOrWhiteSpace(input) ? input - : input.Substring(0, 1).ToUpper() + input.Substring(1); + : input[..1].ToUpper() + input[1..]; /// /// Returns a copy of the string with the first character converted to lowercase. @@ -923,7 +923,7 @@ public static string ToFirstUpper(this string input) => public static string ToFirstLower(this string input) => string.IsNullOrWhiteSpace(input) ? input - : input.Substring(0, 1).ToLower() + input.Substring(1); + : input[..1].ToLower() + input[1..]; /// /// Returns a copy of the string with the first character converted to uppercase using the casing rules of the @@ -935,7 +935,7 @@ public static string ToFirstLower(this string input) => public static string ToFirstUpper(this string input, CultureInfo culture) => string.IsNullOrWhiteSpace(input) ? input - : input.Substring(0, 1).ToUpper(culture) + input.Substring(1); + : input[..1].ToUpper(culture) + input[1..]; /// /// Returns a copy of the string with the first character converted to lowercase using the casing rules of the @@ -947,7 +947,7 @@ public static string ToFirstUpper(this string input, CultureInfo culture) => public static string ToFirstLower(this string input, CultureInfo culture) => string.IsNullOrWhiteSpace(input) ? input - : input.Substring(0, 1).ToLower(culture) + input.Substring(1); + : input[..1].ToLower(culture) + input[1..]; /// /// Returns a copy of the string with the first character converted to uppercase using the casing rules of the @@ -958,7 +958,7 @@ public static string ToFirstLower(this string input, CultureInfo culture) => public static string ToFirstUpperInvariant(this string input) => string.IsNullOrWhiteSpace(input) ? input - : input.Substring(0, 1).ToUpperInvariant() + input.Substring(1); + : input[..1].ToUpperInvariant() + input[1..]; /// /// Returns a copy of the string with the first character converted to lowercase using the casing rules of the @@ -969,7 +969,7 @@ public static string ToFirstUpperInvariant(this string input) => public static string ToFirstLowerInvariant(this string input) => string.IsNullOrWhiteSpace(input) ? input - : input.Substring(0, 1).ToLowerInvariant() + input.Substring(1); + : input[..1].ToLowerInvariant() + input[1..]; /// /// Returns a new string in which all occurrences of specified strings are replaced by other specified strings. @@ -989,7 +989,6 @@ public static string ReplaceMany(this string text, IDictionary r throw new ArgumentNullException(nameof(replacements)); } - foreach (KeyValuePair item in replacements) { text = text.Replace(item.Key, item.Value); @@ -1017,7 +1016,6 @@ public static string ReplaceMany(this string text, char[] chars, char replacemen throw new ArgumentNullException(nameof(chars)); } - for (var i = 0; i < chars.Length; i++) { text = text.Replace(chars[i], replacement); @@ -1048,10 +1046,9 @@ public static string ReplaceFirst(this string text, string search, string replac return text; } - return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); + return text[..pos] + replace + text[(pos + search.Length)..]; } - /// /// An extension method that returns a new string in which all occurrences of a /// specified string in the current instance are replaced with another specified string. @@ -1062,8 +1059,7 @@ public static string ReplaceFirst(this string text, string search, string replac /// Specified string to inject /// String Comparison object to specify search type /// Updated string - public static string Replace(this string source, string oldString, string newString, - StringComparison stringComparison) + public static string Replace(this string source, string oldString, string newString, StringComparison stringComparison) { // This initialization ensures the first check starts at index zero of the source. On successive checks for // a match, the source is skipped to immediately after the last replaced occurrence for efficiency @@ -1096,16 +1092,15 @@ public static string ToCSharpString(this string s) } // http://stackoverflow.com/questions/323640/can-i-convert-a-c-sharp-string-value-to-an-escaped-string-literal - var sb = new StringBuilder(s.Length + 2); for (var rp = 0; rp < s.Length; rp++) { var c = s[rp]; - if (c < ToCSharpEscapeChars.Length && '\0' != ToCSharpEscapeChars[c]) + if (c < ToCSharpEscapeChars.Length && ToCSharpEscapeChars[c] != '\0') { sb.Append('\\').Append(ToCSharpEscapeChars[c]); } - else if ('~' >= c && c >= ' ') + else if (c <= '~' && c >= ' ') { sb.Append(c); } @@ -1136,22 +1131,22 @@ public static string EscapeRegexSpecialCharacters(this string text) { var regexSpecialCharacters = new Dictionary { - {".", @"\."}, - {"(", @"\("}, - {")", @"\)"}, - {"]", @"\]"}, - {"[", @"\["}, - {"{", @"\{"}, - {"}", @"\}"}, - {"?", @"\?"}, - {"!", @"\!"}, - {"$", @"\$"}, - {"^", @"\^"}, - {"+", @"\+"}, - {"*", @"\*"}, - {"|", @"\|"}, - {"<", @"\<"}, - {">", @"\>"} + { ".", @"\." }, + { "(", @"\(" }, + { ")", @"\)" }, + { "]", @"\]" }, + { "[", @"\[" }, + { "{", @"\{" }, + { "}", @"\}" }, + { "?", @"\?" }, + { "!", @"\!" }, + { "$", @"\$" }, + { "^", @"\^" }, + { "+", @"\+" }, + { "*", @"\*" }, + { "|", @"\|" }, + { "<", @"\<" }, + { ">", @"\>" }, }; return ReplaceMany(text, regexSpecialCharacters); } @@ -1167,8 +1162,7 @@ public static string EscapeRegexSpecialCharacters(this string text) /// /// True if any of the needles are contained with haystack; otherwise returns false /// Added fix to ensure the comparison is used - see http://issues.umbraco.org/issue/U4-11313 - public static bool ContainsAny(this string haystack, IEnumerable needles, - StringComparison comparison = StringComparison.CurrentCulture) + public static bool ContainsAny(this string haystack, IEnumerable needles, StringComparison comparison = StringComparison.CurrentCulture) { if (haystack == null) { @@ -1218,7 +1212,6 @@ public static string ToFriendlyName(this string fileName) return fileName; } - /// /// An extension method that returns a new string in which all occurrences of an /// unicode characters that are invalid in XML files are replaced with an empty string. @@ -1229,7 +1222,7 @@ public static string ToFriendlyName(this string fileName) /// removes any unusual unicode characters that can't be encoded into XML /// public static string ToValidXmlString(this string text) => - string.IsNullOrEmpty(text) ? text : InvalidXmlChars.Value.Replace(text, ""); + string.IsNullOrEmpty(text) ? text : InvalidXmlChars.Value.Replace(text, string.Empty); /// /// Converts a string to a Guid - WARNING, depending on the string, this may not be unique @@ -1237,12 +1230,18 @@ public static string ToValidXmlString(this string text) => /// /// public static Guid ToGuid(this string text) => - CreateGuidFromHash(UrlNamespace, + CreateGuidFromHash( + UrlNamespace, text, - CryptoConfig.AllowOnlyFipsAlgorithms - ? 5 // SHA1 + CryptoConfig.AllowOnlyFipsAlgorithms ? 5 // SHA1 : 3); // MD5 + /// + /// Turns an null-or-whitespace string into a null string. + /// + public static string? NullOrWhiteSpaceAsNull(this string text) + => string.IsNullOrWhiteSpace(text) ? null : text; + /// /// Creates a name-based UUID using the algorithm from RFC 4122 §4.3. /// See @@ -1321,13 +1320,6 @@ private static void SwapBytes(byte[] guid, int left, int right) guid[right] = temp; } - /// - /// Turns an null-or-whitespace string into a null string. - /// - public static string? NullOrWhiteSpaceAsNull(this string text) - => string.IsNullOrWhiteSpace(text) ? null : text; - - /// /// Checks if a given path is a full path including drive letter /// @@ -1366,7 +1358,7 @@ public static string ToSafeAlias(this string alias, IShortStringHelper shortStri return a; } - return char.ToLowerInvariant(a[0]) + a.Substring(1); + return char.ToLowerInvariant(a[0]) + a[1..]; } /// @@ -1379,7 +1371,6 @@ public static string ToSafeAlias(this string alias, IShortStringHelper shortStri public static string ToSafeAlias(this string alias, IShortStringHelper shortStringHelper, string culture) => shortStringHelper.CleanStringForSafeAlias(alias, culture); - // the new methods to get a url segment /// @@ -1397,7 +1388,8 @@ public static string ToUrlSegment(this string text, IShortStringHelper shortStri if (string.IsNullOrWhiteSpace(text)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(text)); } @@ -1421,14 +1413,14 @@ public static string ToUrlSegment(this string text, IShortStringHelper shortStri if (string.IsNullOrWhiteSpace(text)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(text)); } return shortStringHelper.CleanStringForUrlSegment(text, culture); } - /// /// Cleans a string. /// @@ -1440,8 +1432,7 @@ public static string ToUrlSegment(this string text, IShortStringHelper shortStri /// /// The clean string. /// The string is cleaned in the context of the ICurrent.ShortStringHelper default culture. - public static string ToCleanString(this string text, IShortStringHelper shortStringHelper, - CleanStringType stringType) => shortStringHelper.CleanString(text, stringType); + public static string ToCleanString(this string text, IShortStringHelper shortStringHelper, CleanStringType stringType) => shortStringHelper.CleanString(text, stringType); /// /// Cleans a string, using a specified separator. @@ -1455,8 +1446,7 @@ public static string ToCleanString(this string text, IShortStringHelper shortStr /// The separator. /// The clean string. /// The string is cleaned in the context of the ICurrent.ShortStringHelper default culture. - public static string ToCleanString(this string text, IShortStringHelper shortStringHelper, - CleanStringType stringType, char separator) => shortStringHelper.CleanString(text, stringType, separator); + public static string ToCleanString(this string text, IShortStringHelper shortStringHelper, CleanStringType stringType, char separator) => shortStringHelper.CleanString(text, stringType, separator); /// /// Cleans a string in the context of a specified culture. @@ -1469,8 +1459,7 @@ public static string ToCleanString(this string text, IShortStringHelper shortStr /// /// The culture. /// The clean string. - public static string ToCleanString(this string text, IShortStringHelper shortStringHelper, - CleanStringType stringType, string culture) => shortStringHelper.CleanString(text, stringType, culture); + public static string ToCleanString(this string text, IShortStringHelper shortStringHelper, CleanStringType stringType, string culture) => shortStringHelper.CleanString(text, stringType, culture); /// /// Cleans a string in the context of a specified culture, using a specified separator. @@ -1484,8 +1473,7 @@ public static string ToCleanString(this string text, IShortStringHelper shortStr /// The separator. /// The culture. /// The clean string. - public static string ToCleanString(this string text, IShortStringHelper shortStringHelper, - CleanStringType stringType, char separator, string culture) => + public static string ToCleanString(this string text, IShortStringHelper shortStringHelper, CleanStringType stringType, char separator, string culture) => shortStringHelper.CleanString(text, stringType, separator, culture); // note: LegacyCurrent.ShortStringHelper will produce 100% backward-compatible output for SplitPascalCasing. @@ -1500,14 +1488,6 @@ public static string ToCleanString(this string text, IShortStringHelper shortStr public static string SplitPascalCasing(this string phrase, IShortStringHelper shortStringHelper) => shortStringHelper.SplitPascalCasing(phrase, ' '); - //NOTE: Not sure what this actually does but is used a few places, need to figure it out and then move to StringExtensions and obsolete. - // it basically is yet another version of SplitPascalCasing - // plugging string extensions here to be 99% compatible - // the only diff. is with numbers, Number6Is was "Number6 Is", and the new string helper does it too, - // but the legacy one does "Number6Is"... assuming it is not a big deal. - internal static string SpaceCamelCasing(this string phrase, IShortStringHelper shortStringHelper) => - phrase.Length < 2 ? phrase : phrase.SplitPascalCasing(shortStringHelper).ToFirstUpperInvariant(); - /// /// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a /// filename, @@ -1519,6 +1499,14 @@ internal static string SpaceCamelCasing(this string phrase, IShortStringHelper s public static string ToSafeFileName(this string text, IShortStringHelper shortStringHelper) => shortStringHelper.CleanStringForSafeFileName(text); + // NOTE: Not sure what this actually does but is used a few places, need to figure it out and then move to StringExtensions and obsolete. + // it basically is yet another version of SplitPascalCasing + // plugging string extensions here to be 99% compatible + // the only diff. is with numbers, Number6Is was "Number6 Is", and the new string helper does it too, + // but the legacy one does "Number6Is"... assuming it is not a big deal. + internal static string SpaceCamelCasing(this string phrase, IShortStringHelper shortStringHelper) => + phrase.Length < 2 ? phrase : phrase.SplitPascalCasing(shortStringHelper).ToFirstUpperInvariant(); + /// /// Cleans a string, in the context of the invariant culture, to produce a string that can safely be used as a /// filename, @@ -1538,8 +1526,7 @@ public static string ToSafeFileName(this string text, IShortStringHelper shortSt /// The character to split on /// The character which can be used to escape the character to split on /// The string split into substrings delimited by the split character - public static IEnumerable EscapedSplit(this string value, char splitChar, - char escapeChar = DefaultEscapedStringEscapeChar) + public static IEnumerable EscapedSplit(this string value, char splitChar, char escapeChar = DefaultEscapedStringEscapeChar) { if (value == null) { diff --git a/src/Umbraco.Core/Extensions/ThreadExtensions.cs b/src/Umbraco.Core/Extensions/ThreadExtensions.cs index 536587b9ba11..b1e5515b8896 100644 --- a/src/Umbraco.Core/Extensions/ThreadExtensions.cs +++ b/src/Umbraco.Core/Extensions/ThreadExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.Globalization; @@ -41,7 +41,6 @@ public static void SanitizeThreadCulture(this Thread thread) // granted, that comparison should probably be a .Equals comparison, but who knows // how many times the framework assumes that it can do a reference comparison? So, // better fix the cultures. - if (ReferenceEquals(invariantCulture, CultureInfo.InvariantCulture)) { return; @@ -49,7 +48,6 @@ public static void SanitizeThreadCulture(this Thread thread) // if we do not have equality, fix cultures by replacing them with a culture with // the same name, but obtained here and now, with a proper invariant top culture - thread.CurrentCulture = CultureInfo.GetCultureInfo(thread.CurrentCulture.Name); thread.CurrentUICulture = CultureInfo.GetCultureInfo(thread.CurrentUICulture.Name); } diff --git a/src/Umbraco.Core/Extensions/TypeExtensions.cs b/src/Umbraco.Core/Extensions/TypeExtensions.cs index 391e69bdb7de..e3da8d9ee11c 100644 --- a/src/Umbraco.Core/Extensions/TypeExtensions.cs +++ b/src/Umbraco.Core/Extensions/TypeExtensions.cs @@ -17,21 +17,6 @@ public static class TypeExtensions ? Activator.CreateInstance(t) : null; - internal static MethodInfo? GetGenericMethod(this Type type, string name, params Type[] parameterTypes) - { - IEnumerable methods = type.GetMethods().Where(method => method.Name == name); - - foreach (MethodInfo method in methods) - { - if (method.HasParameters(parameterTypes)) - { - return method; - } - } - - return null; - } - /// /// Checks if the type is an anonymous type /// @@ -47,13 +32,39 @@ public static bool IsAnonymousType(this Type type) throw new ArgumentNullException("type"); } - return Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false) && type.IsGenericType && type.Name.Contains("AnonymousType") && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$")) && (type.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic; } + public static IEnumerable GetBaseTypes(this Type? type, bool andSelf) + { + if (andSelf) + { + yield return type; + } + + while ((type = type?.BaseType) != null) + { + yield return type; + } + } + + internal static MethodInfo? GetGenericMethod(this Type type, string name, params Type[] parameterTypes) + { + IEnumerable methods = type.GetMethods().Where(method => method.Name == name); + + foreach (MethodInfo method in methods) + { + if (method.HasParameters(parameterTypes)) + { + return method; + } + } + + return null; + } /// /// Determines whether the specified type is enumerable. @@ -80,22 +91,9 @@ internal static bool HasParameters(this MethodInfo method, params Type[] paramet return true; } - public static IEnumerable GetBaseTypes(this Type? type, bool andSelf) - { - if (andSelf) - { - yield return type; - } - - while ((type = type?.BaseType) != null) - { - yield return type; - } - } - public static IEnumerable AllMethods(this Type target) { - //var allTypes = target.AllInterfaces().ToList(); + // var allTypes = target.AllInterfaces().ToList(); var allTypes = target.GetInterfaces().ToList(); // GetInterfaces is ok here allTypes.Add(target); @@ -135,8 +133,7 @@ public static bool IsEnumerable(this Type type) /// public static bool IsOfGenericType(this Type type, Type genericType) { - Type[]? args; - return type.TryGetGenericArguments(genericType, out args); + return type.TryGetGenericArguments(genericType, out Type[]? args); } /// @@ -177,14 +174,14 @@ public static bool TryGetGenericArguments(this Type type, Type genericType, out return null; }; - //first, check if the type passed in is already the generic type + // first, check if the type passed in is already the generic type genericArgType = checkGenericType(type, genericType); if (genericArgType != null) { return true; } - //if we're looking for interfaces, enumerate them: + // if we're looking for interfaces, enumerate them: if (genericType.IsInterface) { foreach (Type @interface in type.GetInterfaces()) @@ -198,7 +195,7 @@ public static bool TryGetGenericArguments(this Type type, Type genericType, out } else { - //loop back into the base types as long as they are generic + // loop back into the base types as long as they are generic while (type.BaseType != null && type.BaseType != typeof(object)) { genericArgType = checkGenericType(type.BaseType, genericType); @@ -374,8 +371,7 @@ public static string GetFullNameWithAssembly(this Type type) { AssemblyName assemblyName = type.Assembly.GetName(); - return string.Concat(type.FullName, ", ", - assemblyName.FullName.StartsWith("App_Code.") ? "App_Code" : assemblyName.Name); + return string.Concat(type.FullName, ", ", assemblyName.FullName.StartsWith("App_Code.") ? "App_Code" : assemblyName.Name); } /// @@ -392,7 +388,6 @@ public static bool IsAssignableFromGtd(this Type type, Type c) { // type *can* be a generic type definition // c is a real type, cannot be a generic type definition - if (type.IsGenericTypeDefinition == false) { return type.IsAssignableFrom(c); @@ -400,7 +395,7 @@ public static bool IsAssignableFromGtd(this Type type, Type c) if (c.IsInterface == false) { - Type t = c; + Type? t = c; while (t != typeof(object)) { if (t is not null && t.IsGenericType && t.GetGenericTypeDefinition() == type) @@ -429,8 +424,8 @@ public static bool IsAssignableFromGtd(this Type type, Type c) } // provided by Array - Type elType = type.GetElementType(); - if (null != elType) + Type? elType = type.GetElementType(); + if (elType != null) { return elType; } @@ -450,7 +445,7 @@ public static bool IsAssignableFromGtd(this Type type, Type c) where T : Attribute => type.GetCustomAttributes(inherit).SingleOrDefault(); - public static IEnumerable GetCustomAttributes(this Type type, bool inherited) + public static IEnumerable GetCustomAttributes(this Type? type, bool inherited) where T : Attribute { if (type == null) @@ -477,8 +472,7 @@ public static bool HasCustomAttribute(this Type type, bool inherit) /// Currently this will only work for ProperCase and camelCase properties, see the TODO below to enable complete case /// insensitivity /// - internal static Attempt GetMemberIgnoreCase(this Type type, IShortStringHelper shortStringHelper, - object target, string memberName) + internal static Attempt GetMemberIgnoreCase(this Type type, IShortStringHelper shortStringHelper, object target, string memberName) { Func> getMember = memberAlias => @@ -486,10 +480,9 @@ internal static Attempt GetMemberIgnoreCase(this Type type, IShortString try { return Attempt.Succeed( - type.InvokeMember(memberAlias, - BindingFlags.GetProperty | - BindingFlags.Instance | - BindingFlags.Public, + type.InvokeMember( + memberAlias, + BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public, null, target, null)); @@ -500,15 +493,17 @@ internal static Attempt GetMemberIgnoreCase(this Type type, IShortString } }; - //try with the current casing + // try with the current casing Attempt attempt = getMember(memberName); if (attempt.Success == false) { - //if we cannot get with the current alias, try changing it's case + // if we cannot get with the current alias, try changing it's case attempt = memberName[0].IsUpperCase() - ? getMember(memberName.ToCleanString(shortStringHelper, + ? getMember(memberName.ToCleanString( + shortStringHelper, CleanStringType.Ascii | CleanStringType.ConvertCase | CleanStringType.CamelCase)) - : getMember(memberName.ToCleanString(shortStringHelper, + : getMember(memberName.ToCleanString( + shortStringHelper, CleanStringType.Ascii | CleanStringType.ConvertCase | CleanStringType.PascalCase)); // TODO: If this still fails then we should get a list of properties from the object and then compare - doing the above without listing diff --git a/src/Umbraco.Core/Extensions/TypeLoaderExtensions.cs b/src/Umbraco.Core/Extensions/TypeLoaderExtensions.cs index ca87d6bb7702..1ea73af00910 100644 --- a/src/Umbraco.Core/Extensions/TypeLoaderExtensions.cs +++ b/src/Umbraco.Core/Extensions/TypeLoaderExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Actions; diff --git a/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs b/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs index d663e48d22cf..1ad94cbdc3e3 100644 --- a/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs +++ b/src/Umbraco.Core/Extensions/UdiGetterExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core; @@ -114,7 +114,8 @@ public static GuidUdi GetUdi(this IContentTypeComposition entity) } else { - throw new NotSupportedException(string.Format("Composition type {0} is not supported.", + throw new NotSupportedException(string.Format( + "Composition type {0} is not supported.", entity.GetType().FullName)); } @@ -163,7 +164,8 @@ public static GuidUdi GetUdi(this EntityContainer entity) } else { - throw new NotSupportedException(string.Format("Contained object type {0} is not supported.", + throw new NotSupportedException(string.Format( + "Contained object type {0} is not supported.", entity.ContainedObjectType)); } @@ -230,7 +232,8 @@ public static StringUdi GetUdi(this Stylesheet entity) throw new ArgumentNullException("entity"); } - return new StringUdi(Constants.UdiEntityType.Stylesheet, + return new StringUdi( + Constants.UdiEntityType.Stylesheet, entity.Path.TrimStart(Constants.CharArrays.ForwardSlash)).EnsureClosed(); } @@ -327,7 +330,8 @@ public static GuidUdi GetUdi(this IContentBase entity) } else { - throw new NotSupportedException(string.Format("ContentBase type {0} is not supported.", + throw new NotSupportedException(string.Format( + "ContentBase type {0} is not supported.", entity.GetType().FullName)); } @@ -378,117 +382,97 @@ public static Udi GetUdi(this IEntity entity) // entity could eg be anything implementing IThing // so we have to go through casts here - - var template = entity as ITemplate; - if (template != null) + if (entity is ITemplate template) { return template.GetUdi(); } - var contentType = entity as IContentType; - if (contentType != null) + if (entity is IContentType contentType) { return contentType.GetUdi(); } - var mediaType = entity as IMediaType; - if (mediaType != null) + if (entity is IMediaType mediaType) { return mediaType.GetUdi(); } - var memberType = entity as IMemberType; - if (memberType != null) + if (entity is IMemberType memberType) { return memberType.GetUdi(); } - var memberGroup = entity as IMemberGroup; - if (memberGroup != null) + if (entity is IMemberGroup memberGroup) { return memberGroup.GetUdi(); } - var contentTypeComposition = entity as IContentTypeComposition; - if (contentTypeComposition != null) + if (entity is IContentTypeComposition contentTypeComposition) { return contentTypeComposition.GetUdi(); } - var dataTypeComposition = entity as IDataType; - if (dataTypeComposition != null) + if (entity is IDataType dataTypeComposition) { return dataTypeComposition.GetUdi(); } - var container = entity as EntityContainer; - if (container != null) + if (entity is EntityContainer container) { return container.GetUdi(); } - var media = entity as IMedia; - if (media != null) + if (entity is IMedia media) { return media.GetUdi(); } - var content = entity as IContent; - if (content != null) + if (entity is IContent content) { return content.GetUdi(); } - var member = entity as IMember; - if (member != null) + if (entity is IMember member) { return member.GetUdi(); } - var stylesheet = entity as Stylesheet; - if (stylesheet != null) + if (entity is Stylesheet stylesheet) { return stylesheet.GetUdi(); } - var script = entity as Script; - if (script != null) + if (entity is Script script) { return script.GetUdi(); } - var dictionaryItem = entity as IDictionaryItem; - if (dictionaryItem != null) + if (entity is IDictionaryItem dictionaryItem) { return dictionaryItem.GetUdi(); } - var macro = entity as IMacro; - if (macro != null) + if (entity is IMacro macro) { return macro.GetUdi(); } - var partialView = entity as IPartialView; - if (partialView != null) + if (entity is IPartialView partialView) { return partialView.GetUdi(); } - var contentBase = entity as IContentBase; - if (contentBase != null) + if (entity is IContentBase contentBase) { return contentBase.GetUdi(); } - var relationType = entity as IRelationType; - if (relationType != null) + if (entity is IRelationType relationType) { return relationType.GetUdi(); } - var language = entity as ILanguage; - if (language != null) + if (entity is ILanguage language) { return language.GetUdi(); } diff --git a/src/Umbraco.Core/Extensions/UmbracoBuilderExtensions.cs b/src/Umbraco.Core/Extensions/UmbracoBuilderExtensions.cs index 21249883fa9e..53e86109c3f6 100644 --- a/src/Umbraco.Core/Extensions/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Core/Extensions/UmbracoBuilderExtensions.cs @@ -70,8 +70,7 @@ private static List GetAsyncNotificationHandlerImplementations(Type han .Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(INotificationAsyncHandler<>)) .ToList(); - private static void RegisterNotificationHandler(IUmbracoBuilder self, Type notificationHandlerType, - Type implementingHandlerType) + private static void RegisterNotificationHandler(IUmbracoBuilder self, Type notificationHandlerType, Type implementingHandlerType) { var descriptor = new UniqueServiceDescriptor(notificationHandlerType, implementingHandlerType, ServiceLifetime.Transient); @@ -98,7 +97,7 @@ private static bool IsAssignableToGenericType(this Type givenType, Type genericT return true; } - Type baseType = givenType.BaseType; + Type? baseType = givenType.BaseType; return baseType != null && IsAssignableToGenericType(baseType, genericType); } } diff --git a/src/Umbraco.Core/Extensions/UmbracoContextAccessorExtensions.cs b/src/Umbraco.Core/Extensions/UmbracoContextAccessorExtensions.cs index 735e4611276c..b0256ad9e625 100644 --- a/src/Umbraco.Core/Extensions/UmbracoContextAccessorExtensions.cs +++ b/src/Umbraco.Core/Extensions/UmbracoContextAccessorExtensions.cs @@ -14,11 +14,11 @@ public static IUmbracoContext GetRequiredUmbracoContext(this IUmbracoContextAcce throw new ArgumentNullException(nameof(umbracoContextAccessor)); } - if (!umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext umbracoContext)) + if (!umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext)) { throw new InvalidOperationException("Wasn't able to get an UmbracoContext"); } - return umbracoContext!; + return umbracoContext; } } diff --git a/src/Umbraco.Core/Extensions/UriExtensions.cs b/src/Umbraco.Core/Extensions/UriExtensions.cs index 4839c7411dd4..74a8ba482c40 100644 --- a/src/Umbraco.Core/Extensions/UriExtensions.cs +++ b/src/Umbraco.Core/Extensions/UriExtensions.cs @@ -53,7 +53,7 @@ public static Uri Rewrite(this Uri uri, string path, string query) if (query == "?") { - query = ""; + query = string.Empty; } return uri.IsAbsoluteUri @@ -81,7 +81,7 @@ public static string GetSafeAbsolutePath(this Uri uri) var posq = s.IndexOf("?", StringComparison.Ordinal); var posf = s.IndexOf("#", StringComparison.Ordinal); var pos = posq > 0 ? posq : posf > 0 ? posf : 0; - var path = pos > 0 ? s.Substring(0, pos) : s; + var path = pos > 0 ? s[..pos] : s; return path; } @@ -172,6 +172,14 @@ public static Uri MakeAbsolute(this Uri uri, Uri baseUri) return new Uri(baseUri.GetLeftPart(UriPartial.Authority) + uri.GetSafeAbsolutePath() + uri.GetSafeQuery()); } + /// + /// Removes the port from the uri. + /// + /// The uri. + /// The same uri, without its port. + public static Uri WithoutPort(this Uri uri) => + new Uri(uri.GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped)); + private static string? GetSafeQuery(this Uri uri) { if (uri.IsAbsoluteUri) @@ -183,24 +191,16 @@ public static Uri MakeAbsolute(this Uri uri, Uri baseUri) var s = uri.OriginalString; var posq = s.IndexOf("?", StringComparison.Ordinal); var posf = s.IndexOf("#", StringComparison.Ordinal); - var query = posq < 0 ? null : posf < 0 ? s.Substring(posq) : s.Substring(posq, posf - posq); + var query = posq < 0 ? null : posf < 0 ? s[posq..] : s[posq..posf]; return query; } - /// - /// Removes the port from the uri. - /// - /// The uri. - /// The same uri, without its port. - public static Uri WithoutPort(this Uri uri) => - new Uri(uri.GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped)); - /// /// Replaces the host of a uri. /// /// The uri. /// A replacement host. /// The same uri, with its host replaced. - public static Uri ReplaceHost(this Uri uri, string host) => new UriBuilder(uri) {Host = host}.Uri; + public static Uri ReplaceHost(this Uri uri, string host) => new UriBuilder(uri) { Host = host }.Uri; } diff --git a/src/Umbraco.Core/Extensions/VersionExtensions.cs b/src/Umbraco.Core/Extensions/VersionExtensions.cs index 97cabca5ea5f..4e9309da4586 100644 --- a/src/Umbraco.Core/Extensions/VersionExtensions.cs +++ b/src/Umbraco.Core/Extensions/VersionExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.Globalization; @@ -10,8 +10,7 @@ public static class VersionExtensions { public static Version GetVersion(this SemVersion semVersion, int maxParts = 4) { - var build = 0; - int.TryParse(semVersion.Build, NumberStyles.Integer, CultureInfo.InvariantCulture, out build); + int.TryParse(semVersion.Build, NumberStyles.Integer, CultureInfo.InvariantCulture, out int build); if (maxParts >= 4) { @@ -28,9 +27,9 @@ public static Version GetVersion(this SemVersion semVersion, int maxParts = 4) public static Version SubtractRevision(this Version version) { - var parts = new List(new[] {version.Major, version.Minor, version.Build, version.Revision}); + var parts = new List(new[] { version.Major, version.Minor, version.Build, version.Revision }); - //remove all prefixed zero parts + // remove all prefixed zero parts while (parts[0] <= 0) { parts.RemoveAt(0); @@ -50,7 +49,7 @@ public static Version SubtractRevision(this Version version) } else { - //break when there isn't a zero part + // break when there isn't a zero part break; } } @@ -62,16 +61,16 @@ public static Version SubtractRevision(this Version version) var lastNonZero = parts.FindLastIndex(i => i > 0); - //subtract 1 from the last non-zero + // subtract 1 from the last non-zero parts[lastNonZero] = parts[lastNonZero] - 1; - //the last non zero is actually the revision so we can just return + // the last non zero is actually the revision so we can just return if (lastNonZero == parts.Count - 1) { return FromList(parts); } - //the last non zero isn't the revision so the remaining zero's need to be replaced with int.max + // the last non zero isn't the revision so the remaining zero's need to be replaced with int.max for (var i = lastNonZero + 1; i < parts.Count; i++) { parts[i] = int.MaxValue; diff --git a/src/Umbraco.Core/Extensions/WaitHandleExtensions.cs b/src/Umbraco.Core/Extensions/WaitHandleExtensions.cs index 994997f37579..b0058dd79812 100644 --- a/src/Umbraco.Core/Extensions/WaitHandleExtensions.cs +++ b/src/Umbraco.Core/Extensions/WaitHandleExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. namespace Umbraco.Extensions; @@ -9,7 +9,6 @@ public static class WaitHandleExtensions // http://blog.nerdbank.net/2011/07/c-await-for-waithandle.html // F# has a AwaitWaitHandle method that accepts a time out... and seems pretty complex... // version below should be OK - public static Task WaitOneAsync(this WaitHandle handle, int millisecondsTimeout = Timeout.Infinite) { var tcs = new TaskCompletionSource(); @@ -17,13 +16,13 @@ public static Task WaitOneAsync(this WaitHandle handle, int millisecondsTimeout lock (callbackHandleInitLock) { RegisteredWaitHandle? callbackHandle = null; + // ReSharper disable once RedundantAssignment callbackHandle = ThreadPool.RegisterWaitForSingleObject( handle, (state, timedOut) => { - //TODO: We aren't checking if this is timed out - + // TODO: We aren't checking if this is timed out tcs.SetResult(null); // we take a lock here to make sure the outer method has completed setting the local variable callbackHandle. diff --git a/src/Umbraco.Core/Extensions/XmlExtensions.cs b/src/Umbraco.Core/Extensions/XmlExtensions.cs index 91d032b678d8..34e2b7b2aa4d 100644 --- a/src/Umbraco.Core/Extensions/XmlExtensions.cs +++ b/src/Umbraco.Core/Extensions/XmlExtensions.cs @@ -34,10 +34,9 @@ public static bool HasAttribute(this XmlAttributeCollection attributes, string a /// /// The XPath expression should reference variables as $var. /// - public static XmlNodeList? SelectNodes(this XmlNode source, string expression, - IEnumerable? variables) + public static XmlNodeList? SelectNodes(this XmlNode source, string expression, IEnumerable? variables) { - XPathVariable[] av = variables == null ? null : variables.ToArray(); + XPathVariable[]? av = variables?.ToArray(); return SelectNodes(source, expression, av); } @@ -57,10 +56,9 @@ public static bool HasAttribute(this XmlAttributeCollection attributes, string a /// /// The XPath expression should reference variables as $var. /// - public static XmlNodeList? SelectNodes(this XmlNode source, XPathExpression expression, - IEnumerable? variables) + public static XmlNodeList? SelectNodes(this XmlNode source, XPathExpression expression, IEnumerable? variables) { - XPathVariable[] av = variables == null ? null : variables.ToArray(); + XPathVariable[]? av = variables?.ToArray(); return SelectNodes(source, expression, av); } @@ -84,10 +82,10 @@ public static bool HasAttribute(this XmlAttributeCollection attributes, string a { if (variables == null || variables.Length == 0 || variables[0] == null) { - return source.SelectNodes(expression ?? ""); + return source.SelectNodes(expression ?? string.Empty); } - XPathNodeIterator iterator = source.CreateNavigator()?.Select(expression ?? "", variables); + XPathNodeIterator? iterator = source.CreateNavigator()?.Select(expression ?? string.Empty, variables); return XmlNodeListFactory.CreateNodeList(iterator); } @@ -107,15 +105,14 @@ public static bool HasAttribute(this XmlAttributeCollection attributes, string a /// /// The XPath expression should reference variables as $var. /// - public static XmlNodeList SelectNodes(this XmlNode source, XPathExpression expression, - params XPathVariable[]? variables) + public static XmlNodeList SelectNodes(this XmlNode source, XPathExpression expression, params XPathVariable[]? variables) { if (variables == null || variables.Length == 0 || variables[0] == null) { return source.SelectNodes(expression); } - XPathNodeIterator iterator = source.CreateNavigator()?.Select(expression, variables); + XPathNodeIterator? iterator = source.CreateNavigator()?.Select(expression, variables); return XmlNodeListFactory.CreateNodeList(iterator); } @@ -135,10 +132,9 @@ public static XmlNodeList SelectNodes(this XmlNode source, XPathExpression expre /// /// The XPath expression should reference variables as $var. /// - public static XmlNode? SelectSingleNode(this XmlNode source, string expression, - IEnumerable? variables) + public static XmlNode? SelectSingleNode(this XmlNode source, string expression, IEnumerable? variables) { - XPathVariable[] av = variables == null ? null : variables.ToArray(); + XPathVariable[]? av = variables?.ToArray(); return SelectSingleNode(source, expression, av); } @@ -158,10 +154,9 @@ public static XmlNodeList SelectNodes(this XmlNode source, XPathExpression expre /// /// The XPath expression should reference variables as $var. /// - public static XmlNode? SelectSingleNode(this XmlNode source, XPathExpression expression, - IEnumerable? variables) + public static XmlNode? SelectSingleNode(this XmlNode source, XPathExpression expression, IEnumerable? variables) { - XPathVariable[] av = variables == null ? null : variables.ToArray(); + XPathVariable[]? av = variables?.ToArray(); return SelectSingleNode(source, expression, av); } @@ -207,8 +202,7 @@ public static XmlNodeList SelectNodes(this XmlNode source, XPathExpression expre /// /// The XPath expression should reference variables as $var. /// - public static XmlNode? SelectSingleNode(this XmlNode source, XPathExpression expression, - params XPathVariable[]? variables) + public static XmlNode? SelectSingleNode(this XmlNode source, XPathExpression expression, params XPathVariable[]? variables) { if (variables == null || variables.Length == 0 || variables[0] == null) { @@ -382,7 +376,7 @@ public static XElement ToXElement(this XmlNode xmlElement) public static XmlNode? GetXmlNode(this XContainer element, XmlDocument xmlDoc) { - XmlNode node = element.GetXmlNode(); + XmlNode? node = element.GetXmlNode(); if (node is not null) { return xmlDoc.ImportNode(node, true); @@ -400,7 +394,9 @@ public static string ToDataString(this XElement xml) { var settings = new XmlWriterSettings { - OmitXmlDeclaration = true, NewLineHandling = NewLineHandling.None, Indent = false + OmitXmlDeclaration = true, + NewLineHandling = NewLineHandling.None, + Indent = false, }; var output = new StringBuilder(); using (var writer = XmlWriter.Create(output, settings)) diff --git a/src/Umbraco.Core/Handlers/PublicAccessHandler.cs b/src/Umbraco.Core/Handlers/PublicAccessHandler.cs index 412fd6f737a2..d441509a8571 100644 --- a/src/Umbraco.Core/Handlers/PublicAccessHandler.cs +++ b/src/Umbraco.Core/Handlers/PublicAccessHandler.cs @@ -23,13 +23,14 @@ private void Handle(IEnumerable affectedEntities) { foreach (IMemberGroup grp in affectedEntities) { - //check if the name has changed + // check if the name has changed if ((grp.AdditionalData?.ContainsKey("previousName") ?? false) && grp.AdditionalData["previousName"] != null && grp.AdditionalData["previousName"]?.ToString().IsNullOrWhiteSpace() == false && grp.AdditionalData["previousName"]?.ToString() != grp.Name) { - _publicAccessService.RenameMemberGroupRoleRules(grp.AdditionalData["previousName"]?.ToString(), + _publicAccessService.RenameMemberGroupRoleRules( + grp.AdditionalData["previousName"]?.ToString(), grp.Name); } } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Configuration/NotificationEmailCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Configuration/NotificationEmailCheck.cs index c52a55b18e8b..9629aa8917b1 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Configuration/NotificationEmailCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Configuration/NotificationEmailCheck.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.Options; @@ -14,8 +14,7 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Configuration; [HealthCheck( "3E2F7B14-4B41-452B-9A30-E67FBC8E1206", "Notification Email Settings", - Description = - "If notifications are used, the 'from' email address should be specified and changed from the default value.", + Description = "If notifications are used, the 'from' email address should be specified and changed from the default value.", Group = "Configuration")] public class NotificationEmailCheck : AbstractSettingsCheck { @@ -40,7 +39,7 @@ public NotificationEmailCheck( /// public override IEnumerable Values => new List { - new() {IsRecommended = false, Value = DefaultFromEmail}, new() {IsRecommended = false, Value = string.Empty} + new() { IsRecommended = false, Value = DefaultFromEmail }, new() { IsRecommended = false, Value = string.Empty }, }; /// @@ -48,12 +47,13 @@ public NotificationEmailCheck( /// public override string CheckSuccessMessage => - LocalizedTextService.Localize("healthcheck", "notificationEmailsCheckSuccessMessage", - new[] {CurrentValue ?? "<null>"}); + LocalizedTextService.Localize("healthcheck", "notificationEmailsCheckSuccessMessage", new[] { CurrentValue ?? "<null>" }); /// - public override string CheckErrorMessage => LocalizedTextService.Localize("healthcheck", - "notificationEmailsCheckErrorMessage", new[] {DefaultFromEmail}); + public override string CheckErrorMessage => LocalizedTextService.Localize( + "healthcheck", + "notificationEmailsCheckErrorMessage", + new[] { DefaultFromEmail }); /// public override string ReadMoreLink => diff --git a/src/Umbraco.Core/HealthChecks/Checks/ProvidedValueValidation.cs b/src/Umbraco.Core/HealthChecks/Checks/ProvidedValueValidation.cs index 2b12d5ebb847..041ace503f42 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/ProvidedValueValidation.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/ProvidedValueValidation.cs @@ -7,5 +7,5 @@ public enum ProvidedValueValidation { None = 1, Email = 2, - Regex = 3 + Regex = 3, } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/UmbracoApplicationUrlCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/UmbracoApplicationUrlCheck.cs index bd90e57647a5..55406b9c0afe 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/UmbracoApplicationUrlCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/UmbracoApplicationUrlCheck.cs @@ -18,7 +18,8 @@ public class UmbracoApplicationUrlCheck : HealthCheck private readonly ILocalizedTextService _textService; private readonly IOptionsMonitor _webRoutingSettings; - public UmbracoApplicationUrlCheck(ILocalizedTextService textService, + public UmbracoApplicationUrlCheck( + ILocalizedTextService textService, IOptionsMonitor webRoutingSettings) { _textService = textService; @@ -52,7 +53,7 @@ private HealthCheckStatus CheckUmbracoApplicationUrl() } else { - resultMessage = _textService.Localize("healthcheck", "umbracoApplicationUrlCheckResultTrue", new[] {url}); + resultMessage = _textService.Localize("healthcheck", "umbracoApplicationUrlCheckResultTrue", new[] { url }); resultType = StatusResultType.Success; success = true; } @@ -62,7 +63,7 @@ private HealthCheckStatus CheckUmbracoApplicationUrl() ResultType = resultType, ReadMoreLink = success ? null - : Constants.HealthChecks.DocumentationLinks.Security.UmbracoApplicationUrlCheck + : Constants.HealthChecks.DocumentationLinks.Security.UmbracoApplicationUrlCheck, }; } } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Services/SmtpCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Services/SmtpCheck.cs index 17d1f7bc833e..6119f4c71570 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Services/SmtpCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Services/SmtpCheck.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.Net.Sockets; @@ -43,6 +43,32 @@ public override Task> GetStatus() => public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => throw new InvalidOperationException("SmtpCheck has no executable actions"); + private static bool CanMakeSmtpConnection(string host, int port) + { + try + { + using (var client = new TcpClient()) + { + client.Connect(host, port); + using (NetworkStream stream = client.GetStream()) + { + using (var writer = new StreamWriter(stream)) + using (var reader = new StreamReader(stream)) + { + writer.WriteLine("EHLO " + host); + writer.Flush(); + reader.ReadLine(); + return true; + } + } + } + } + catch + { + return false; + } + } + private HealthCheckStatus CheckSmtpSettings() { var success = false; @@ -66,8 +92,7 @@ private HealthCheckStatus CheckSmtpSettings() message = success ? _textService.Localize("healthcheck", "smtpMailSettingsConnectionSuccess") : _textService.Localize( - "healthcheck", "smtpMailSettingsConnectionFail", - new[] {smtpSettings.Host, smtpSettings.Port.ToString()}); + "healthcheck", "smtpMailSettingsConnectionFail", new[] { smtpSettings.Host, smtpSettings.Port.ToString() }); } } @@ -75,33 +100,7 @@ private HealthCheckStatus CheckSmtpSettings() new HealthCheckStatus(message) { ResultType = success ? StatusResultType.Success : StatusResultType.Error, - ReadMoreLink = success ? null : Constants.HealthChecks.DocumentationLinks.SmtpCheck + ReadMoreLink = success ? null : Constants.HealthChecks.DocumentationLinks.SmtpCheck, }; } - - private static bool CanMakeSmtpConnection(string host, int port) - { - try - { - using (var client = new TcpClient()) - { - client.Connect(host, port); - using (NetworkStream stream = client.GetStream()) - { - using (var writer = new StreamWriter(stream)) - using (var reader = new StreamReader(stream)) - { - writer.WriteLine("EHLO " + host); - writer.Flush(); - reader.ReadLine(); - return true; - } - } - } - } - catch - { - return false; - } - } } diff --git a/src/Umbraco.Core/HealthChecks/NotificationMethods/NotificationMethodBase.cs b/src/Umbraco.Core/HealthChecks/NotificationMethods/NotificationMethodBase.cs index 060224c84ee8..acb9af7cac08 100644 --- a/src/Umbraco.Core/HealthChecks/NotificationMethods/NotificationMethodBase.cs +++ b/src/Umbraco.Core/HealthChecks/NotificationMethods/NotificationMethodBase.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; @@ -9,8 +9,7 @@ public abstract class NotificationMethodBase : IHealthCheckNotificationMethod protected NotificationMethodBase(IOptionsMonitor healthCheckSettings) { Type type = GetType(); - HealthCheckNotificationMethodAttribute attribute = - type.GetCustomAttribute(); + HealthCheckNotificationMethodAttribute? attribute = type.GetCustomAttribute(); if (attribute == null) { Enabled = false; @@ -19,8 +18,8 @@ protected NotificationMethodBase(IOptionsMonitor healthChe IDictionary notificationMethods = healthCheckSettings.CurrentValue.Notification.NotificationMethods; - if (!notificationMethods.TryGetValue(attribute.Alias, - out HealthChecksNotificationMethodSettings notificationMethod)) + if (!notificationMethods.TryGetValue( + attribute.Alias, out HealthChecksNotificationMethodSettings? notificationMethod)) { Enabled = false; return; diff --git a/src/Umbraco.Core/HealthChecks/StatusResultType.cs b/src/Umbraco.Core/HealthChecks/StatusResultType.cs index 7fa329429f9c..0516fc35448a 100644 --- a/src/Umbraco.Core/HealthChecks/StatusResultType.cs +++ b/src/Umbraco.Core/HealthChecks/StatusResultType.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Cms.Core.HealthChecks; +namespace Umbraco.Cms.Core.HealthChecks; public enum StatusResultType { Success, Warning, Error, - Info + Info, } diff --git a/src/Umbraco.Core/HealthChecks/ValueComparisonType.cs b/src/Umbraco.Core/HealthChecks/ValueComparisonType.cs index 20419087d781..9269f905f4d7 100644 --- a/src/Umbraco.Core/HealthChecks/ValueComparisonType.cs +++ b/src/Umbraco.Core/HealthChecks/ValueComparisonType.cs @@ -1,7 +1,7 @@ -namespace Umbraco.Cms.Core.HealthChecks; +namespace Umbraco.Cms.Core.HealthChecks; public enum ValueComparisonType { ShouldEqual, - ShouldNotEqual + ShouldNotEqual, } diff --git a/src/Umbraco.Core/Hosting/NoopApplicationShutdownRegistry.cs b/src/Umbraco.Core/Hosting/NoopApplicationShutdownRegistry.cs index 49729aae80f1..e821102f0997 100644 --- a/src/Umbraco.Core/Hosting/NoopApplicationShutdownRegistry.cs +++ b/src/Umbraco.Core/Hosting/NoopApplicationShutdownRegistry.cs @@ -1,7 +1,12 @@ -namespace Umbraco.Cms.Core.Hosting; +namespace Umbraco.Cms.Core.Hosting; internal class NoopApplicationShutdownRegistry : IApplicationShutdownRegistry { - public void RegisterObject(IRegisteredObject registeredObject) { } - public void UnregisterObject(IRegisteredObject registeredObject) { } + public void RegisterObject(IRegisteredObject registeredObject) + { + } + + public void UnregisterObject(IRegisteredObject registeredObject) + { + } } diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index 457aa0eb5c25..d9ff2dd44039 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -27,8 +27,7 @@ public class PhysicalFileSystem : IPhysicalFileSystem, IFileProviderFactory // eg "" or "/Views" or "/Media" or "//Media" in case of a virtual path private readonly string _rootUrl; - public PhysicalFileSystem(IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, - ILogger logger, string rootPath, string rootUrl) + public PhysicalFileSystem(IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, ILogger logger, string rootPath, string rootUrl) { _ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -74,6 +73,8 @@ public PhysicalFileSystem(IIOHelper ioHelper, IHostingEnvironment hostingEnviron _rootUrl = EnsureUrlSeparatorChar(rootUrl).TrimEnd(Constants.CharArrays.ForwardSlash); } + public bool CanAddPhysical => true; + /// /// Gets directories in a directory. /// @@ -288,14 +289,14 @@ public string GetRelativePath(string fullPathOrUrl) // or on unix systems "/var/wwwroot/test/Meia/1234/img.jpg" if (_ioHelper.PathStartsWith(path, _rootPathFwd, '/')) { - return path.Substring(_rootPathFwd.Length).TrimStart(Constants.CharArrays.ForwardSlash); + return path[_rootPathFwd.Length..].TrimStart(Constants.CharArrays.ForwardSlash); } // if it starts with the root URL, strip it and trim the starting slash to make it relative // eg "/Media/1234/img.jpg" => "1234/img.jpg" if (_ioHelper.PathStartsWith(path, _rootUrl, '/')) { - return path.Substring(_rootUrl.Length).TrimStart(Constants.CharArrays.ForwardSlash); + return path[_rootUrl.Length..].TrimStart(Constants.CharArrays.ForwardSlash); } // unchanged - what else? @@ -411,8 +412,6 @@ public long GetSize(string path) return file.Exists ? file.Length : -1; } - public bool CanAddPhysical => true; - public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) { var fullPath = GetFullPath(path); @@ -445,6 +444,9 @@ public void AddFile(string path, string physicalPath, bool overrideIfExists = tr } } + /// + public IFileProvider Create() => new PhysicalFileProvider(_rootPath); + #region Helper Methods protected virtual void EnsureDirectory(string path) @@ -474,7 +476,7 @@ protected void WithRetry(Action action) const int count = 10; const int pausems = 100; - for (var i = 0;; i++) + for (var i = 0; ; i++) { try { @@ -503,8 +505,5 @@ protected void WithRetry(Action action) } } - /// - public IFileProvider Create() => new PhysicalFileProvider(_rootPath); - #endregion } diff --git a/src/Umbraco.Core/IO/ShadowFileSystem.cs b/src/Umbraco.Core/IO/ShadowFileSystem.cs index 8094a137f100..95517f805445 100644 --- a/src/Umbraco.Core/IO/ShadowFileSystem.cs +++ b/src/Umbraco.Core/IO/ShadowFileSystem.cs @@ -1,4 +1,4 @@ -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; namespace Umbraco.Cms.Core.IO; @@ -16,7 +16,9 @@ public ShadowFileSystem(IFileSystem fs, IFileSystem sfs) public IFileSystem Inner { get; } - private Dictionary Nodes => _nodes ?? (_nodes = new Dictionary()); + public bool CanAddPhysical => true; + + private Dictionary Nodes => _nodes ??= new Dictionary(); public IEnumerable GetDirectories(string path) { @@ -54,8 +56,9 @@ public void DeleteDirectory(string path, bool recursive) } else { + // actual content if (Nodes.Any(x => IsChild(normPath, x.Key) && x.Value.IsExist) // shadow content - || Inner.GetDirectories(path).Any() || Inner.GetFiles(path).Any()) // actual content + || Inner.GetDirectories(path).Any() || Inner.GetFiles(path).Any()) { throw new InvalidOperationException("Directory is not empty."); } @@ -73,8 +76,7 @@ public void DeleteDirectory(string path, bool recursive) public bool DirectoryExists(string path) { - ShadowNode? sf; - if (Nodes.TryGetValue(NormPath(path), out sf)) + if (Nodes.TryGetValue(NormPath(path), out ShadowNode? sf)) { return sf.IsDir && sf.IsExist; } @@ -86,9 +88,8 @@ public bool DirectoryExists(string path) public void AddFile(string path, Stream stream, bool overrideIfExists) { - ShadowNode? sf; var normPath = NormPath(path); - if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false)) + if (Nodes.TryGetValue(normPath, out ShadowNode? sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false)) { throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); } @@ -97,8 +98,7 @@ public void AddFile(string path, Stream stream, bool overrideIfExists) for (var i = 0; i < parts.Length - 1; i++) { var dirPath = string.Join("/", parts.Take(i + 1)); - ShadowNode? sd; - if (Nodes.TryGetValue(dirPath, out sd)) + if (Nodes.TryGetValue(dirPath, out ShadowNode? sd)) { if (sd.IsFile) { @@ -137,7 +137,7 @@ public IEnumerable GetFiles(string path, string? filter) var normPath = NormPath(path); KeyValuePair[] shadows = Nodes.Where(kvp => IsChild(normPath, kvp.Key)).ToArray(); IEnumerable files = filter != null ? Inner.GetFiles(path, filter) : Inner.GetFiles(path); - WildcardExpression wildcard = filter == null ? null : new WildcardExpression(filter); + WildcardExpression? wildcard = filter == null ? null : new WildcardExpression(filter); return files .Except(shadows.Where(kvp => (kvp.Value.IsFile && kvp.Value.IsDelete) || kvp.Value.IsDir) .Select(kvp => kvp.Key)) @@ -169,8 +169,7 @@ public void DeleteFile(string path) public bool FileExists(string path) { - ShadowNode? sf; - if (Nodes.TryGetValue(NormPath(path), out sf)) + if (Nodes.TryGetValue(NormPath(path), out ShadowNode? sf)) { return sf.IsFile && sf.IsExist; } @@ -182,8 +181,7 @@ public bool FileExists(string path) public string GetFullPath(string path) { - ShadowNode? sf; - if (Nodes.TryGetValue(NormPath(path), out sf)) + if (Nodes.TryGetValue(NormPath(path), out ShadowNode? sf)) { return sf.IsDir || sf.IsDelete ? string.Empty : _sfs.GetFullPath(path); } @@ -195,8 +193,7 @@ public string GetFullPath(string path) public DateTimeOffset GetLastModified(string path) { - ShadowNode? sf; - if (Nodes.TryGetValue(NormPath(path), out sf) == false) + if (Nodes.TryGetValue(NormPath(path), out ShadowNode? sf) == false) { return Inner.GetLastModified(path); } @@ -211,8 +208,7 @@ public DateTimeOffset GetLastModified(string path) public DateTimeOffset GetCreated(string path) { - ShadowNode? sf; - if (Nodes.TryGetValue(NormPath(path), out sf) == false) + if (Nodes.TryGetValue(NormPath(path), out ShadowNode? sf) == false) { return Inner.GetCreated(path); } @@ -227,8 +223,7 @@ public DateTimeOffset GetCreated(string path) public long GetSize(string path) { - ShadowNode? sf; - if (Nodes.TryGetValue(NormPath(path), out sf) == false) + if (Nodes.TryGetValue(NormPath(path), out ShadowNode? sf) == false) { return Inner.GetSize(path); } @@ -241,13 +236,10 @@ public long GetSize(string path) return _sfs.GetSize(path); } - public bool CanAddPhysical => true; - public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) { - ShadowNode? sf; var normPath = NormPath(path); - if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false)) + if (Nodes.TryGetValue(normPath, out ShadowNode? sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false)) { throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); } @@ -256,8 +248,7 @@ public void AddFile(string path, string physicalPath, bool overrideIfExists = tr for (var i = 0; i < parts.Length - 1; i++) { var dirPath = string.Join("/", parts.Take(i + 1)); - ShadowNode? sd; - if (Nodes.TryGetValue(dirPath, out sd)) + if (Nodes.TryGetValue(dirPath, out ShadowNode? sd)) { if (sd.IsFile) { @@ -400,30 +391,15 @@ private void Delete(string path, bool recurse) } } - private class ShadowNode - { - public ShadowNode(bool isDelete, bool isdir) - { - IsDelete = isDelete; - IsDir = isdir; - } - - public bool IsDelete { get; } - public bool IsDir { get; } - - public bool IsExist => IsDelete == false; - public bool IsFile => IsDir == false; - } - // copied from System.Web.Util.Wildcard internal internal class WildcardExpression { - private static readonly Regex metaRegex = new("[\\+\\{\\\\\\[\\|\\(\\)\\.\\^\\$]"); - private static readonly Regex questRegex = new("\\?"); - private static readonly Regex starRegex = new("\\*"); - private static readonly Regex commaRegex = new(","); - private static Regex slashRegex = new("(?=/)"); - private static Regex backslashRegex = new("(?=[\\\\:])"); + private static readonly Regex MetaRegex = new("[\\+\\{\\\\\\[\\|\\(\\)\\.\\^\\$]"); + private static readonly Regex QuestRegex = new("\\?"); + private static readonly Regex StarRegex = new("\\*"); + private static readonly Regex CommaRegex = new(","); + private static readonly Regex SlashRegex = new("(?=/)"); + private static readonly Regex BackslashRegex = new("(?=[\\\\:])"); private readonly bool _caseInsensitive; private readonly string _pattern; private Regex? _regex; @@ -434,6 +410,12 @@ public WildcardExpression(string pattern, bool caseInsensitive = true) _caseInsensitive = caseInsensitive; } + public bool IsMatch(string input) + { + EnsureRegex(_pattern); + return _regex?.IsMatch(input) ?? false; + } + private void EnsureRegex(string pattern) { if (_regex != null) @@ -444,7 +426,6 @@ private void EnsureRegex(string pattern) RegexOptions options = RegexOptions.None; // match right-to-left (for speed) if the pattern starts with a * - if (pattern.Length > 0 && pattern[0] == '*') { options = RegexOptions.RightToLeft | RegexOptions.Singleline; @@ -455,31 +436,38 @@ private void EnsureRegex(string pattern) } // case insensitivity - if (_caseInsensitive) { options |= RegexOptions.IgnoreCase | RegexOptions.CultureInvariant; } // Remove regex metacharacters - - pattern = metaRegex.Replace(pattern, "\\$0"); + pattern = MetaRegex.Replace(pattern, "\\$0"); // Replace wildcard metacharacters with regex codes - - pattern = questRegex.Replace(pattern, "."); - pattern = starRegex.Replace(pattern, ".*"); - pattern = commaRegex.Replace(pattern, "\\z|\\A"); + pattern = QuestRegex.Replace(pattern, "."); + pattern = StarRegex.Replace(pattern, ".*"); + pattern = CommaRegex.Replace(pattern, "\\z|\\A"); // anchor the pattern at beginning and end, and return the regex - _regex = new Regex("\\A" + pattern + "\\z", options); } + } - public bool IsMatch(string input) + private class ShadowNode + { + public ShadowNode(bool isDelete, bool isdir) { - EnsureRegex(_pattern); - return _regex?.IsMatch(input) ?? false; + IsDelete = isDelete; + IsDir = isdir; } + + public bool IsDelete { get; } + + public bool IsDir { get; } + + public bool IsExist => IsDelete == false; + + public bool IsFile => IsDir == false; } } diff --git a/src/Umbraco.Core/IO/ShadowFileSystems.cs b/src/Umbraco.Core/IO/ShadowFileSystems.cs index c2fd54f489f3..3d69875dc4d6 100644 --- a/src/Umbraco.Core/IO/ShadowFileSystems.cs +++ b/src/Umbraco.Core/IO/ShadowFileSystems.cs @@ -1,6 +1,6 @@ -namespace Umbraco.Cms.Core.IO; -// shadow filesystems is definitively ... too convoluted +namespace Umbraco.Cms.Core.IO; +// shadow filesystems is definitively ... too convoluted internal class ShadowFileSystems : ICompletable { private readonly FileSystems _fileSystems; diff --git a/src/Umbraco.Core/IO/ShadowWrapper.cs b/src/Umbraco.Core/IO/ShadowWrapper.cs index 0b412812b885..5833c0ab1209 100644 --- a/src/Umbraco.Core/IO/ShadowWrapper.cs +++ b/src/Umbraco.Core/IO/ShadowWrapper.cs @@ -17,8 +17,7 @@ internal class ShadowWrapper : IFileSystem, IFileProviderFactory private string? _shadowDir; private ShadowFileSystem? _shadowFileSystem; - public ShadowWrapper(IFileSystem innerFileSystem, IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, - ILoggerFactory loggerFactory, string shadowPath, Func? isScoped = null) + public ShadowWrapper(IFileSystem innerFileSystem, IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory, string shadowPath, Func? isScoped = null) { InnerFileSystem = innerFileSystem; _ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper)); @@ -30,6 +29,8 @@ public ShadowWrapper(IFileSystem innerFileSystem, IIOHelper ioHelper, IHostingEn public IFileSystem InnerFileSystem { get; } + public bool CanAddPhysical => FileSystem.CanAddPhysical; + private IFileSystem FileSystem { get @@ -93,11 +94,6 @@ public void AddFile(string path, Stream stream, bool overrideExisting) => public long GetSize(string path) => FileSystem.GetSize(path); - public bool CanAddPhysical => FileSystem.CanAddPhysical; - - public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) => - FileSystem.AddFile(path, physicalPath, overrideIfExists, copy); - public static string CreateShadowId(IHostingEnvironment hostingEnvironment) { const int retries = 50; // avoid infinite loop @@ -107,7 +103,6 @@ public static string CreateShadowId(IHostingEnvironment hostingEnvironment) // with an existing directory or not - if it does, try again, and // we should end up with a unique identifier eventually - but just // detect infinite loops (just in case) - for (var i = 0; i < retries; i++) { var id = GuidUtils.ToBase32String(Guid.NewGuid(), idLength); @@ -126,23 +121,24 @@ public static string CreateShadowId(IHostingEnvironment hostingEnvironment) throw new Exception($"Could not get a shadow identifier (tried {retries} times)"); } + public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) => + FileSystem.AddFile(path, physicalPath, overrideIfExists, copy); + internal void Shadow(string id) { // note: no thread-safety here, because ShadowFs is thread-safe due to the check // on ShadowFileSystemsScope.None - and if None is false then we should be running // in a single thread anyways - var virt = Path.Combine(ShadowFsPath, id, _shadowPath); _shadowDir = _hostingEnvironment.MapPathContentRoot(virt); Directory.CreateDirectory(_shadowDir); - var tempfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, - _loggerFactory.CreateLogger(), _shadowDir, _hostingEnvironment.ToAbsolute(virt)); + var tempfs = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, _loggerFactory.CreateLogger(), _shadowDir, _hostingEnvironment.ToAbsolute(virt)); _shadowFileSystem = new ShadowFileSystem(InnerFileSystem, tempfs); } internal void UnShadow(bool complete) { - ShadowFileSystem shadowFileSystem = _shadowFileSystem; + ShadowFileSystem? shadowFileSystem = _shadowFileSystem; var dir = _shadowDir; _shadowFileSystem = null; _shadowDir = null; @@ -168,7 +164,7 @@ internal void UnShadow(bool complete) var pos = dir.LastIndexOf(Path.DirectorySeparatorChar); while (pos > min) { - dir = dir.Substring(0, pos); + dir = dir[..pos]; if (Directory.EnumerateFileSystemEntries(dir).Any() == false) { Directory.Delete(dir, true); diff --git a/src/Umbraco.Core/IO/ViewHelper.cs b/src/Umbraco.Core/IO/ViewHelper.cs index 3199b51ecbe8..e2502e466961 100644 --- a/src/Umbraco.Core/IO/ViewHelper.cs +++ b/src/Umbraco.Core/IO/ViewHelper.cs @@ -25,12 +25,19 @@ public ViewHelper(FileSystems fileSystems, IDefaultViewContentProvider defaultVi throw new ArgumentNullException(nameof(defaultViewContentProvider)); } - public bool ViewExists(ITemplate t) => t.Alias is not null && _viewFileSystem.FileExists(ViewPath(t.Alias)); + [Obsolete("Inject IDefaultViewContentProvider instead")] + public static string GetDefaultFileContent(string? layoutPageAlias = null, string? modelClassName = null, string? modelNamespace = null, string? modelNamespaceAlias = null) + { + IDefaultViewContentProvider viewContentProvider = + StaticServiceProvider.Instance.GetRequiredService(); + return viewContentProvider.GetDefaultFileContent(layoutPageAlias, modelClassName, modelNamespace, modelNamespaceAlias); + } + public bool ViewExists(ITemplate t) => t.Alias is not null && _viewFileSystem.FileExists(ViewPath(t.Alias)); public string GetFileContents(ITemplate t) { - var viewContent = ""; + var viewContent = string.Empty; var path = ViewPath(t.Alias ?? string.Empty); if (_viewFileSystem.FileExists(path)) @@ -72,7 +79,7 @@ public string CreateView(ITemplate t, bool overWrite = false) if (string.IsNullOrEmpty(currentAlias) == false && currentAlias != t.Alias) { - //then kill the old file.. + // then kill the old file.. var oldFile = ViewPath(currentAlias); if (_viewFileSystem.FileExists(oldFile)) { @@ -91,17 +98,7 @@ public string CreateView(ITemplate t, bool overWrite = false) return t.Content; } - public string ViewPath(string alias) => _viewFileSystem.GetRelativePath(alias.Replace(" ", "") + ".cshtml"); - - [Obsolete("Inject IDefaultViewContentProvider instead")] - public static string GetDefaultFileContent(string? layoutPageAlias = null, string? modelClassName = null, - string? modelNamespace = null, string? modelNamespaceAlias = null) - { - IDefaultViewContentProvider viewContentProvider = - StaticServiceProvider.Instance.GetRequiredService(); - return viewContentProvider.GetDefaultFileContent(layoutPageAlias, modelClassName, modelNamespace, - modelNamespaceAlias); - } + public string ViewPath(string alias) => _viewFileSystem.GetRelativePath(alias.Replace(" ", string.Empty) + ".cshtml"); private string SaveTemplateToFile(ITemplate template) { diff --git a/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs b/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs index 8e21f6e28040..cb008bf77c0b 100644 --- a/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs +++ b/src/Umbraco.Core/Install/InstallSteps/TelemetryIdentifierStep.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration; @@ -9,8 +9,11 @@ namespace Umbraco.Cms.Core.Install.InstallSteps; -[InstallSetupStep(InstallationType.NewInstall | InstallationType.Upgrade, - "TelemetryIdConfiguration", 0, "", +[InstallSetupStep( + InstallationType.NewInstall | InstallationType.Upgrade, + "TelemetryIdConfiguration", + 0, + "", PerformsAppRestart = false)] public class TelemetryIdentifierStep : InstallSetupStep { diff --git a/src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs b/src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs index 53973f4b0843..1a4ef649344d 100644 --- a/src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs +++ b/src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Install.Models; using Umbraco.Cms.Core.Semver; using Umbraco.Cms.Core.Services; @@ -9,8 +9,7 @@ namespace Umbraco.Cms.Core.Install.InstallSteps; /// /// This step is purely here to show the button to commence the upgrade /// -[InstallSetupStep(InstallationType.Upgrade, "Upgrade", "upgrade", 1, - "Upgrading Umbraco to the latest and greatest version.")] +[InstallSetupStep(InstallationType.Upgrade, "Upgrade", "upgrade", 1, "Upgrading Umbraco to the latest and greatest version.")] public class UpgradeStep : InstallSetupStep { private readonly IRuntimeState _runtimeState; @@ -26,7 +25,7 @@ public override object ViewModel { get { - string FormatGuidState(string? value) + static string FormatGuidState(string? value) { if (string.IsNullOrWhiteSpace(value)) { @@ -34,7 +33,7 @@ string FormatGuidState(string? value) } else if (Guid.TryParse(value, out Guid currentStateGuid)) { - value = currentStateGuid.ToString("N").Substring(0, 8); + value = currentStateGuid.ToString("N")[..8]; } return value; @@ -45,7 +44,7 @@ string FormatGuidState(string? value) var newVersion = _umbracoVersion.SemanticVersion?.ToSemanticStringWithoutBuild(); var oldVersion = new SemVersion(_umbracoVersion.SemanticVersion?.Major ?? 0) - .ToString(); //TODO can we find the old version somehow? e.g. from current state + .ToString(); // TODO can we find the old version somehow? e.g. from current state var reportUrl = $"https://our.umbraco.com/contribute/releases/compare?from={oldVersion}&to={newVersion}¬es=1"; @@ -56,7 +55,7 @@ string FormatGuidState(string? value) newVersion, currentState, newState, - reportUrl + reportUrl, }; } } diff --git a/src/Umbraco.Core/Install/Models/Package.cs b/src/Umbraco.Core/Install/Models/Package.cs index 6ea6110da5e9..9ac30ab9a7e3 100644 --- a/src/Umbraco.Core/Install/Models/Package.cs +++ b/src/Umbraco.Core/Install/Models/Package.cs @@ -1,13 +1,16 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Install.Models; [DataContract(Name = "package")] public class Package { - [DataMember(Name = "name")] public string? Name { get; set; } + [DataMember(Name = "name")] + public string? Name { get; set; } - [DataMember(Name = "thumbnail")] public string? Thumbnail { get; set; } + [DataMember(Name = "thumbnail")] + public string? Thumbnail { get; set; } - [DataMember(Name = "id")] public Guid Id { get; set; } + [DataMember(Name = "id")] + public Guid Id { get; set; } } diff --git a/src/Umbraco.Core/Install/Models/UserModel.cs b/src/Umbraco.Core/Install/Models/UserModel.cs index 034e34ed7c73..4b5117ba65c8 100644 --- a/src/Umbraco.Core/Install/Models/UserModel.cs +++ b/src/Umbraco.Core/Install/Models/UserModel.cs @@ -1,15 +1,18 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Install.Models; [DataContract(Name = "user", Namespace = "")] public class UserModel { - [DataMember(Name = "name")] public string Name { get; set; } = null!; + [DataMember(Name = "name")] + public string Name { get; set; } = null!; - [DataMember(Name = "email")] public string Email { get; set; } = null!; + [DataMember(Name = "email")] + public string Email { get; set; } = null!; - [DataMember(Name = "password")] public string Password { get; set; } = null!; + [DataMember(Name = "password")] + public string Password { get; set; } = null!; [DataMember(Name = "subscribeToNewsLetter")] public bool SubscribeToNewsLetter { get; set; } diff --git a/src/Umbraco.Core/Logging/ProfilerExtensions.cs b/src/Umbraco.Core/Logging/ProfilerExtensions.cs index 4f7a1fee253d..e69506702af9 100644 --- a/src/Umbraco.Core/Logging/ProfilerExtensions.cs +++ b/src/Umbraco.Core/Logging/ProfilerExtensions.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Logging; +namespace Umbraco.Cms.Core.Logging; internal static class ProfilerExtensions { diff --git a/src/Umbraco.Core/Manifest/PackageManifest.cs b/src/Umbraco.Core/Manifest/PackageManifest.cs index ffdd33cd6f89..7bf07cfde9c3 100644 --- a/src/Umbraco.Core/Manifest/PackageManifest.cs +++ b/src/Umbraco.Core/Manifest/PackageManifest.cs @@ -35,7 +35,8 @@ public string? PackageName set => _packageName = value; } - [DataMember(Name = "packageView")] public string? PackageView { get; set; } + [DataMember(Name = "packageView")] + public string? PackageView { get; set; } /// /// Gets the source path of the manifest. @@ -61,7 +62,8 @@ public string? PackageName [DataMember(Name = "allowPackageTelemetry")] public bool AllowPackageTelemetry { get; set; } = true; - [DataMember(Name = "bundleOptions")] public BundleOptions BundleOptions { get; set; } + [DataMember(Name = "bundleOptions")] + public BundleOptions BundleOptions { get; set; } /// /// Gets or sets the scripts listed in the manifest. diff --git a/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs b/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs index 99adc504b6ed..b09baba0dbd2 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs @@ -64,7 +64,8 @@ public virtual string DownloadResponse(string url) } } - public virtual T? GetJsonResponse(string url) where T : class + public virtual T? GetJsonResponse(string url) + where T : class { var response = DownloadResponse(url); return _jsonSerializer.Deserialize(response); @@ -81,7 +82,7 @@ public virtual XmlDocument GetXmlResponse(string url) public virtual string GetXmlProperty(XmlDocument doc, string property) { - XmlNode selectSingleNode = doc.SelectSingleNode(property); + XmlNode? selectSingleNode = doc.SelectSingleNode(property); return selectSingleNode != null ? selectSingleNode.InnerText : string.Empty; } } diff --git a/src/Umbraco.Core/Media/EmbedProviders/OEmbedResponse.cs b/src/Umbraco.Core/Media/EmbedProviders/OEmbedResponse.cs index 32ff72c09f95..370d2609c79f 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/OEmbedResponse.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/OEmbedResponse.cs @@ -9,34 +9,47 @@ namespace Umbraco.Cms.Core.Media.EmbedProviders; [DataContract] public class OEmbedResponse { - [DataMember(Name = "type")] public string? Type { get; set; } + [DataMember(Name = "type")] + public string? Type { get; set; } - [DataMember(Name = "version")] public string? Version { get; set; } + [DataMember(Name = "version")] + public string? Version { get; set; } - [DataMember(Name = "title")] public string? Title { get; set; } + [DataMember(Name = "title")] + public string? Title { get; set; } - [DataMember(Name = "author_name")] public string? AuthorName { get; set; } + [DataMember(Name = "author_name")] + public string? AuthorName { get; set; } - [DataMember(Name = "author_url")] public string? AuthorUrl { get; set; } + [DataMember(Name = "author_url")] + public string? AuthorUrl { get; set; } - [DataMember(Name = "provider_name")] public string? ProviderName { get; set; } + [DataMember(Name = "provider_name")] + public string? ProviderName { get; set; } - [DataMember(Name = "provider_url")] public string? ProviderUrl { get; set; } + [DataMember(Name = "provider_url")] + public string? ProviderUrl { get; set; } - [DataMember(Name = "thumbnail_url")] public string? ThumbnailUrl { get; set; } + [DataMember(Name = "thumbnail_url")] + public string? ThumbnailUrl { get; set; } [DataMember(Name = "thumbnail_height")] public double? ThumbnailHeight { get; set; } - [DataMember(Name = "thumbnail_width")] public double? ThumbnailWidth { get; set; } + [DataMember(Name = "thumbnail_width")] + public double? ThumbnailWidth { get; set; } - [DataMember(Name = "html")] public string? Html { get; set; } + [DataMember(Name = "html")] + public string? Html { get; set; } - [DataMember(Name = "url")] public string? Url { get; set; } + [DataMember(Name = "url")] + public string? Url { get; set; } - [DataMember(Name = "height")] public double? Height { get; set; } + [DataMember(Name = "height")] + public double? Height { get; set; } - [DataMember(Name = "width")] public double? Width { get; set; } + [DataMember(Name = "width")] + public double? Width { get; set; } /// /// Gets the HTML. diff --git a/src/Umbraco.Core/Media/EmbedProviders/Slideshare.cs b/src/Umbraco.Core/Media/EmbedProviders/Slideshare.cs index 19c9f997c55a..cd017c60387c 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Slideshare.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Slideshare.cs @@ -6,20 +6,21 @@ namespace Umbraco.Cms.Core.Media.EmbedProviders; // TODO(V10) : change base class to OEmbedProviderBase public class Slideshare : EmbedProviderBase { - public Slideshare(IJsonSerializer jsonSerializer) : base(jsonSerializer) + public Slideshare(IJsonSerializer jsonSerializer) + : base(jsonSerializer) { } public override string ApiEndpoint => "http://www.slideshare.net/api/oembed/2"; - public override string[] UrlSchemeRegex => new[] {@"slideshare\.net/"}; + public override string[] UrlSchemeRegex => new[] { @"slideshare\.net/" }; public override Dictionary RequestParams => new(); public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); + var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = this.GetXmlResponse(requestUrl); return GetXmlProperty(xmlDocument, "/oembed/html"); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/SoundCloud.cs b/src/Umbraco.Core/Media/EmbedProviders/SoundCloud.cs index f48da2cc9df7..02ebaaefdffe 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/SoundCloud.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/SoundCloud.cs @@ -6,20 +6,21 @@ namespace Umbraco.Cms.Core.Media.EmbedProviders; // TODO(V10) : change base class to OEmbedProviderBase public class Soundcloud : EmbedProviderBase { - public Soundcloud(IJsonSerializer jsonSerializer) : base(jsonSerializer) + public Soundcloud(IJsonSerializer jsonSerializer) + : base(jsonSerializer) { } public override string ApiEndpoint => "https://soundcloud.com/oembed"; - public override string[] UrlSchemeRegex => new[] {@"soundcloud.com\/*"}; + public override string[] UrlSchemeRegex => new[] { @"soundcloud.com\/*" }; public override Dictionary RequestParams => new(); public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); + var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = this.GetXmlResponse(requestUrl); return GetXmlProperty(xmlDocument, "/oembed/html"); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Ted.cs b/src/Umbraco.Core/Media/EmbedProviders/Ted.cs index e6637cf598e8..3eed04fd0136 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Ted.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Ted.cs @@ -6,20 +6,21 @@ namespace Umbraco.Cms.Core.Media.EmbedProviders; // TODO(V10) : change base class to OEmbedProviderBase public class Ted : EmbedProviderBase { - public Ted(IJsonSerializer jsonSerializer) : base(jsonSerializer) + public Ted(IJsonSerializer jsonSerializer) + : base(jsonSerializer) { } public override string ApiEndpoint => "http://www.ted.com/talks/oembed.xml"; - public override string[] UrlSchemeRegex => new[] {@"ted.com\/talks\/*"}; + public override string[] UrlSchemeRegex => new[] { @"ted.com\/talks\/*" }; public override Dictionary RequestParams => new(); public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); + var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = this.GetXmlResponse(requestUrl); return GetXmlProperty(xmlDocument, "/oembed/html"); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs b/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs index 66a08fa4d23b..7ef914218ab4 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs @@ -5,20 +5,21 @@ namespace Umbraco.Cms.Core.Media.EmbedProviders; // TODO(V10) : change base class to OEmbedProviderBase public class Twitter : EmbedProviderBase { - public Twitter(IJsonSerializer jsonSerializer) : base(jsonSerializer) + public Twitter(IJsonSerializer jsonSerializer) + : base(jsonSerializer) { } public override string ApiEndpoint => "http://publish.twitter.com/oembed"; - public override string[] UrlSchemeRegex => new[] {@"twitter.com/.*/status/.*"}; + public override string[] UrlSchemeRegex => new[] { @"twitter.com/.*/status/.*" }; public override Dictionary RequestParams => new(); public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse oembed = base.GetJsonResponse(requestUrl); + var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse? oembed = this.GetJsonResponse(requestUrl); return oembed?.GetHtml(); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Vimeo.cs b/src/Umbraco.Core/Media/EmbedProviders/Vimeo.cs index a8655f096c6c..f9aa5afab177 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Vimeo.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Vimeo.cs @@ -6,20 +6,21 @@ namespace Umbraco.Cms.Core.Media.EmbedProviders; // TODO(V10) : change base class to OEmbedProviderBase public class Vimeo : EmbedProviderBase { - public Vimeo(IJsonSerializer jsonSerializer) : base(jsonSerializer) + public Vimeo(IJsonSerializer jsonSerializer) + : base(jsonSerializer) { } public override string ApiEndpoint => "https://vimeo.com/api/oembed.xml"; - public override string[] UrlSchemeRegex => new[] {@"vimeo\.com/"}; + public override string[] UrlSchemeRegex => new[] { @"vimeo\.com/" }; public override Dictionary RequestParams => new(); public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); + var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = this.GetXmlResponse(requestUrl); return GetXmlProperty(xmlDocument, "/oembed/html"); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Youtube.cs b/src/Umbraco.Core/Media/EmbedProviders/Youtube.cs index 02c34436e954..40c40a88ad0c 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Youtube.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Youtube.cs @@ -5,24 +5,25 @@ namespace Umbraco.Cms.Core.Media.EmbedProviders; // TODO(V10) : change base class to OEmbedProviderBase public class YouTube : EmbedProviderBase { - public YouTube(IJsonSerializer jsonSerializer) : base(jsonSerializer) + public YouTube(IJsonSerializer jsonSerializer) + : base(jsonSerializer) { } public override string ApiEndpoint => "https://www.youtube.com/oembed"; - public override string[] UrlSchemeRegex => new[] {@"youtu.be/.*", @"youtube.com/watch.*"}; + public override string[] UrlSchemeRegex => new[] { @"youtu.be/.*", @"youtube.com/watch.*" }; public override Dictionary RequestParams => new() { - //ApiUrl/?format=json - {"format", "json"} + // ApiUrl/?format=json + { "format", "json" }, }; public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse oembed = base.GetJsonResponse(requestUrl); + var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse? oembed = this.GetJsonResponse(requestUrl); return oembed?.GetHtml(); } diff --git a/src/Umbraco.Core/Media/Exif/SvgFile.cs b/src/Umbraco.Core/Media/Exif/SvgFile.cs index 9223d8f76fd0..08326e634c59 100644 --- a/src/Umbraco.Core/Media/Exif/SvgFile.cs +++ b/src/Umbraco.Core/Media/Exif/SvgFile.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; using System.Xml.Linq; namespace Umbraco.Cms.Core.Media.Exif; @@ -10,16 +10,16 @@ public SvgFile(Stream fileStream) fileStream.Position = 0; var document = - XDocument.Load(fileStream); //if it throws an exception the ugly try catch in MediaFileSystem will catch it + XDocument.Load(fileStream); // if it throws an exception the ugly try catch in MediaFileSystem will catch it var width = document.Root?.Attributes().Where(x => x.Name == "width").Select(x => x.Value).FirstOrDefault(); var height = document.Root?.Attributes().Where(x => x.Name == "height").Select(x => x.Value).FirstOrDefault(); - Properties.Add(new ExifSInt(ExifTag.PixelYDimension, - height == null - ? Constants.Conventions.Media.DefaultSize - : int.Parse(height, CultureInfo.InvariantCulture))); - Properties.Add(new ExifSInt(ExifTag.PixelXDimension, + Properties.Add(new ExifSInt( + ExifTag.PixelYDimension, + height == null ? Constants.Conventions.Media.DefaultSize : int.Parse(height, CultureInfo.InvariantCulture))); + Properties.Add(new ExifSInt( + ExifTag.PixelXDimension, width == null ? Constants.Conventions.Media.DefaultSize : int.Parse(width, CultureInfo.InvariantCulture))); Format = ImageFileFormat.SVG; diff --git a/src/Umbraco.Core/Media/Exif/TIFFFile.cs b/src/Umbraco.Core/Media/Exif/TIFFFile.cs index a23d9b91cd2f..e562e769f27f 100644 --- a/src/Umbraco.Core/Media/Exif/TIFFFile.cs +++ b/src/Umbraco.Core/Media/Exif/TIFFFile.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; namespace Umbraco.Cms.Core.Media.Exif; @@ -51,6 +51,15 @@ protected internal TIFFFile(Stream stream, Encoding encoding) #endregion + #region Properties + + /// + /// Gets the TIFF header. + /// + public TIFFHeader TIFFHeader { get; } + + #endregion + #region Instance Methods /// @@ -63,13 +72,16 @@ public override void Save(Stream stream) // Write TIFF header uint ifdoffset = 8; + // Byte order stream.Write( BitConverterEx.SystemByteOrder == BitConverterEx.ByteOrder.LittleEndian - ? new byte[] {0x49, 0x49} - : new byte[] {0x4D, 0x4D}, 0, 2); + ? new byte[] { 0x49, 0x49 } + : new byte[] { 0x4D, 0x4D }, 0, 2); + // TIFF ID stream.Write(conv.GetBytes((ushort)42), 0, 2); + // Offset to 0th IFD, will be corrected below stream.Write(conv.GetBytes(ifdoffset), 0, 4); @@ -130,8 +142,10 @@ public override void Save(Stream stream) { // Tag stream.Write(conv.GetBytes(field.Tag), 0, 2); + // Type stream.Write(conv.GetBytes(field.Type), 0, 2); + // Count stream.Write(conv.GetBytes(field.Count), 0, 4); @@ -162,15 +176,6 @@ public override void Save(Stream stream) } } - #endregion - - #region Properties - - /// - /// Gets the TIFF header. - /// - public TIFFHeader TIFFHeader { get; } - /// /// Gets the image file directories. /// diff --git a/src/Umbraco.Core/Media/Exif/TIFFHeader.cs b/src/Umbraco.Core/Media/Exif/TIFFHeader.cs index a68af4ceb0ac..54a79d90b419 100644 --- a/src/Umbraco.Core/Media/Exif/TIFFHeader.cs +++ b/src/Umbraco.Core/Media/Exif/TIFFHeader.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Media.Exif; +namespace Umbraco.Cms.Core.Media.Exif; /// /// Represents a TIFF Header. @@ -36,8 +36,7 @@ internal struct TIFFHeader /// start of the TIFF header. /// /// The byte order of the TIFF header itself. - public TIFFHeader(BitConverterEx.ByteOrder byteOrder, byte id, uint ifdOffset, - BitConverterEx.ByteOrder headerByteOrder) + public TIFFHeader(BitConverterEx.ByteOrder byteOrder, byte id, uint ifdOffset, BitConverterEx.ByteOrder headerByteOrder) { if (id != 42) { @@ -58,7 +57,7 @@ public TIFFHeader(BitConverterEx.ByteOrder byteOrder, byte id, uint ifdOffset, /// A initialized from the given byte data. public static TIFFHeader FromBytes(byte[] data, int offset) { - var header = new TIFFHeader(); + var header = default(TIFFHeader); // TIFF header if (data[offset] == 0x49 && data[offset + 1] == 0x49) diff --git a/src/Umbraco.Core/Media/Exif/TIFFStrip.cs b/src/Umbraco.Core/Media/Exif/TIFFStrip.cs index b8a954f77fd0..8bf91abde6ec 100644 --- a/src/Umbraco.Core/Media/Exif/TIFFStrip.cs +++ b/src/Umbraco.Core/Media/Exif/TIFFStrip.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Media.Exif; +namespace Umbraco.Cms.Core.Media.Exif; /// /// Represents a strip of compressed image data in a TIFF file. diff --git a/src/Umbraco.Core/Media/Exif/Utility.cs b/src/Umbraco.Core/Media/Exif/Utility.cs index e3a3cb049dc6..1ce1b1cdc751 100644 --- a/src/Umbraco.Core/Media/Exif/Utility.cs +++ b/src/Umbraco.Core/Media/Exif/Utility.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Media.Exif; +namespace Umbraco.Cms.Core.Media.Exif; /// /// Contains utility functions. diff --git a/src/Umbraco.Core/Media/OEmbedResult.cs b/src/Umbraco.Core/Media/OEmbedResult.cs index bf7e36e61e83..3e4834521da8 100644 --- a/src/Umbraco.Core/Media/OEmbedResult.cs +++ b/src/Umbraco.Core/Media/OEmbedResult.cs @@ -1,8 +1,10 @@ -namespace Umbraco.Cms.Core.Media; +namespace Umbraco.Cms.Core.Media; public class OEmbedResult { public OEmbedStatus OEmbedStatus { get; set; } + public bool SupportsDimensions { get; set; } + public string? Markup { get; set; } } diff --git a/src/Umbraco.Core/Media/OEmbedStatus.cs b/src/Umbraco.Core/Media/OEmbedStatus.cs index 3e1f2024aa14..1903643d5e2b 100644 --- a/src/Umbraco.Core/Media/OEmbedStatus.cs +++ b/src/Umbraco.Core/Media/OEmbedStatus.cs @@ -1,8 +1,8 @@ -namespace Umbraco.Cms.Core.Media; +namespace Umbraco.Cms.Core.Media; public enum OEmbedStatus { NotSupported, Error, - Success + Success, } diff --git a/src/Umbraco.Core/Media/TypeDetector/RasterizedTypeDetector.cs b/src/Umbraco.Core/Media/TypeDetector/RasterizedTypeDetector.cs index 562dc7a1dc0e..6f4e7a8a863c 100644 --- a/src/Umbraco.Core/Media/TypeDetector/RasterizedTypeDetector.cs +++ b/src/Umbraco.Core/Media/TypeDetector/RasterizedTypeDetector.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Media.TypeDetector; +namespace Umbraco.Cms.Core.Media.TypeDetector; public abstract class RasterizedTypeDetector { diff --git a/src/Umbraco.Core/Media/TypeDetector/SvgDetector.cs b/src/Umbraco.Core/Media/TypeDetector/SvgDetector.cs index b9b1318b06d6..c790806b9b3d 100644 --- a/src/Umbraco.Core/Media/TypeDetector/SvgDetector.cs +++ b/src/Umbraco.Core/Media/TypeDetector/SvgDetector.cs @@ -1,4 +1,4 @@ -using System.Xml.Linq; +using System.Xml.Linq; namespace Umbraco.Cms.Core.Media.TypeDetector; diff --git a/src/Umbraco.Core/Media/TypeDetector/TIFFDetector.cs b/src/Umbraco.Core/Media/TypeDetector/TIFFDetector.cs index 16acf7032630..5581c81a6256 100644 --- a/src/Umbraco.Core/Media/TypeDetector/TIFFDetector.cs +++ b/src/Umbraco.Core/Media/TypeDetector/TIFFDetector.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; namespace Umbraco.Cms.Core.Media.TypeDetector; diff --git a/src/Umbraco.Core/Media/UploadAutoFillProperties.cs b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs index bb66995515be..6a5ffd23d738 100644 --- a/src/Umbraco.Core/Media/UploadAutoFillProperties.cs +++ b/src/Umbraco.Core/Media/UploadAutoFillProperties.cs @@ -61,8 +61,7 @@ public void Reset(IContentBase content, ImagingAutoFillUploadField autoFillConfi /// The parameter is the path relative to the filesystem. /// Variation language. /// Variation segment. - public void Populate(IContentBase content, ImagingAutoFillUploadField autoFillConfig, string filepath, - string? culture, string? segment) + public void Populate(IContentBase content, ImagingAutoFillUploadField autoFillConfig, string filepath, string? culture, string? segment) { if (content == null) { @@ -110,8 +109,7 @@ public void Populate(IContentBase content, ImagingAutoFillUploadField autoFillCo /// The stream containing the file data. /// Variation language. /// Variation segment. - public void Populate(IContentBase content, ImagingAutoFillUploadField autoFillConfig, string filepath, - Stream filestream, string culture, string segment) + public void Populate(IContentBase content, ImagingAutoFillUploadField autoFillConfig, string filepath, Stream filestream, string culture, string segment) { if (content == null) { @@ -134,21 +132,7 @@ public void Populate(IContentBase content, ImagingAutoFillUploadField autoFillCo } } - private void SetProperties(IContentBase content, ImagingAutoFillUploadField autoFillConfig, string filepath, - Stream? filestream, string? culture, string? segment) - { - var extension = (Path.GetExtension(filepath) ?? string.Empty).TrimStart(Constants.CharArrays.Period); - - Size? size = _imageUrlGenerator.IsSupportedImageFormat(extension) - ? _imageDimensionExtractor.GetDimensions(filestream) ?? - (Size?)new Size(Constants.Conventions.Media.DefaultSize, Constants.Conventions.Media.DefaultSize) - : null; - - SetProperties(content, autoFillConfig, size, filestream?.Length, extension, culture, segment); - } - - private static void SetProperties(IContentBase content, ImagingAutoFillUploadField autoFillConfig, Size? size, - long? length, string extension, string? culture, string? segment) + private static void SetProperties(IContentBase content, ImagingAutoFillUploadField autoFillConfig, Size? size, long? length, string extension, string? culture, string? segment) { if (content == null) { @@ -187,8 +171,19 @@ private static void SetProperties(IContentBase content, ImagingAutoFillUploadFie } } - private static void ResetProperties(IContentBase content, ImagingAutoFillUploadField autoFillConfig, - string? culture, string? segment) + private void SetProperties(IContentBase content, ImagingAutoFillUploadField autoFillConfig, string filepath, Stream? filestream, string? culture, string? segment) + { + var extension = (Path.GetExtension(filepath) ?? string.Empty).TrimStart(Constants.CharArrays.Period); + + Size? size = _imageUrlGenerator.IsSupportedImageFormat(extension) + ? _imageDimensionExtractor.GetDimensions(filestream) ?? + (Size?)new Size(Constants.Conventions.Media.DefaultSize, Constants.Conventions.Media.DefaultSize) + : null; + + SetProperties(content, autoFillConfig, size, filestream?.Length, extension, culture, segment); + } + + private static void ResetProperties(IContentBase content, ImagingAutoFillUploadField autoFillConfig, string? culture, string? segment) { if (content == null) { diff --git a/src/Umbraco.Core/Models/ContentEditing/MediaTypeDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/MediaTypeDisplay.cs index b7a1290601d4..899be9504057 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MediaTypeDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MediaTypeDisplay.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/MediaTypeSave.cs b/src/Umbraco.Core/Models/ContentEditing/MediaTypeSave.cs index 5d5ccd03b358..b3fdeea1e251 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MediaTypeSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MediaTypeSave.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberBasic.cs b/src/Umbraco.Core/Models/ContentEditing/MemberBasic.cs index a54e585635fe..7ef1ce5f729b 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberBasic.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -7,9 +7,11 @@ namespace Umbraco.Cms.Core.Models.ContentEditing; /// public class MemberBasic : ContentItemBasic { - [DataMember(Name = "username")] public string? Username { get; set; } + [DataMember(Name = "username")] + public string? Username { get; set; } - [DataMember(Name = "email")] public string? Email { get; set; } + [DataMember(Name = "email")] + public string? Email { get; set; } [DataMember(Name = "properties")] public override IEnumerable Properties diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/MemberDisplay.cs index 0e8849b969b4..161c085d355e 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberDisplay.cs @@ -9,21 +9,27 @@ namespace Umbraco.Cms.Core.Models.ContentEditing; public class MemberDisplay : ListViewAwareContentItemDisplayBase { public MemberDisplay() => - // MemberProviderFieldMapping = new Dictionary(); + + // MemberProviderFieldMapping = new Dictionary(); ContentApps = new List(); - [DataMember(Name = "contentType")] public ContentTypeBasic? ContentType { get; set; } + [DataMember(Name = "contentType")] + public ContentTypeBasic? ContentType { get; set; } - [DataMember(Name = "username")] public string? Username { get; set; } + [DataMember(Name = "username")] + public string? Username { get; set; } - [DataMember(Name = "email")] public string? Email { get; set; } + [DataMember(Name = "email")] + public string? Email { get; set; } - [DataMember(Name = "isLockedOut")] public bool IsLockedOut { get; set; } + [DataMember(Name = "isLockedOut")] + public bool IsLockedOut { get; set; } - [DataMember(Name = "isApproved")] public bool IsApproved { get; set; } + [DataMember(Name = "isApproved")] + public bool IsApproved { get; set; } - //[DataMember(Name = "membershipScenario")] - //public MembershipScenario MembershipScenario { get; set; } + // [DataMember(Name = "membershipScenario")] + // public MembershipScenario MembershipScenario { get; set; } // /// // /// This is used to indicate how to map the membership provider properties to the save model, this mapping @@ -32,9 +38,8 @@ public MemberDisplay() => // /// // [DataMember(Name = "fieldConfig")] // public IDictionary MemberProviderFieldMapping { get; set; } - - [DataMember(Name = "apps")] public IEnumerable ContentApps { get; set; } - + [DataMember(Name = "apps")] + public IEnumerable ContentApps { get; set; } [DataMember(Name = "membershipProperties")] public IEnumerable? MembershipProperties { get; set; } diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberGroupDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/MemberGroupDisplay.cs index 2c0bf10a25a6..0804fd53d74e 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberGroupDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberGroupDisplay.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberGroupSave.cs b/src/Umbraco.Core/Models/ContentEditing/MemberGroupSave.cs index 25033258eacb..292d410625d7 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberGroupSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberGroupSave.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberListDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/MemberListDisplay.cs index 7a533c01fa3c..cd89f46fc663 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberListDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberListDisplay.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -8,5 +8,6 @@ namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "content", Namespace = "")] public class MemberListDisplay : ContentItemDisplayBase { - [DataMember(Name = "apps")] public IEnumerable? ContentApps { get; set; } + [DataMember(Name = "apps")] + public IEnumerable? ContentApps { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberPropertyTypeBasic.cs b/src/Umbraco.Core/Models/ContentEditing/MemberPropertyTypeBasic.cs index fb25776ee9a8..9ef0ebf3b987 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberPropertyTypeBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberPropertyTypeBasic.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -11,7 +11,9 @@ public class MemberPropertyTypeBasic : PropertyTypeBasic [DataMember(Name = "showOnMemberProfile")] public bool MemberCanViewProperty { get; set; } - [DataMember(Name = "memberCanEdit")] public bool MemberCanEditProperty { get; set; } + [DataMember(Name = "memberCanEdit")] + public bool MemberCanEditProperty { get; set; } - [DataMember(Name = "isSensitiveData")] public bool IsSensitiveData { get; set; } + [DataMember(Name = "isSensitiveData")] + public bool IsSensitiveData { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberPropertyTypeDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/MemberPropertyTypeDisplay.cs index 59beb56f6b09..1038440974e8 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberPropertyTypeDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberPropertyTypeDisplay.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -8,7 +8,9 @@ public class MemberPropertyTypeDisplay : PropertyTypeDisplay [DataMember(Name = "showOnMemberProfile")] public bool MemberCanViewProperty { get; set; } - [DataMember(Name = "memberCanEdit")] public bool MemberCanEditProperty { get; set; } + [DataMember(Name = "memberCanEdit")] + public bool MemberCanEditProperty { get; set; } - [DataMember(Name = "isSensitiveData")] public bool IsSensitiveData { get; set; } + [DataMember(Name = "isSensitiveData")] + public bool IsSensitiveData { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberSave.cs b/src/Umbraco.Core/Models/ContentEditing/MemberSave.cs index 3d44de4bdcc7..2963618e1b01 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberSave.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Validation; using Umbraco.Extensions; @@ -17,22 +17,26 @@ public class MemberSave : ContentBaseSave [EmailAddress] public string Email { get; set; } = null!; - [DataMember(Name = "password")] public ChangingPasswordModel? Password { get; set; } + [DataMember(Name = "password")] + public ChangingPasswordModel? Password { get; set; } - [DataMember(Name = "memberGroups")] public IEnumerable? Groups { get; set; } + [DataMember(Name = "memberGroups")] + public IEnumerable? Groups { get; set; } /// /// Returns the value from the Comments property /// public string? Comments => GetPropertyValue(Constants.Conventions.Member.Comments); - [DataMember(Name = "isLockedOut")] public bool IsLockedOut { get; set; } + [DataMember(Name = "isLockedOut")] + public bool IsLockedOut { get; set; } - [DataMember(Name = "isApproved")] public bool IsApproved { get; set; } + [DataMember(Name = "isApproved")] + public bool IsApproved { get; set; } private T? GetPropertyValue(string alias) { - ContentPropertyBasic prop = Properties.FirstOrDefault(x => x.Alias == alias); + ContentPropertyBasic? prop = Properties.FirstOrDefault(x => x.Alias == alias); if (prop == null) { return default; diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberTypeDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/MemberTypeDisplay.cs index 98819c1016b2..ea8aa5c1e365 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberTypeDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberTypeDisplay.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/MemberTypeSave.cs b/src/Umbraco.Core/Models/ContentEditing/MemberTypeSave.cs index f9c49190241d..59a604749461 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MemberTypeSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MemberTypeSave.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.ContentEditing; +namespace Umbraco.Cms.Core.Models.ContentEditing; /// /// Model used to save a member type diff --git a/src/Umbraco.Core/Models/ContentEditing/MessagesExtensions.cs b/src/Umbraco.Core/Models/ContentEditing/MessagesExtensions.cs index 760f6b1ba031..5a6511134570 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MessagesExtensions.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MessagesExtensions.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Models.ContentEditing; namespace Umbraco.Extensions; @@ -11,7 +11,7 @@ public static void AddNotification(this INotificationModel model, string header, return; } - model.Notifications?.Add(new BackOfficeNotification {Header = header, Message = msg, NotificationType = type}); + model.Notifications?.Add(new BackOfficeNotification { Header = header, Message = msg, NotificationType = type }); } public static void AddSuccessNotification(this INotificationModel model, string header, string msg) @@ -23,7 +23,9 @@ public static void AddSuccessNotification(this INotificationModel model, string model.Notifications?.Add(new BackOfficeNotification { - Header = header, Message = msg, NotificationType = NotificationStyle.Success + Header = header, + Message = msg, + NotificationType = NotificationStyle.Success, }); } @@ -36,7 +38,9 @@ public static void AddErrorNotification(this INotificationModel model, string? h model.Notifications?.Add(new BackOfficeNotification { - Header = header, Message = msg, NotificationType = NotificationStyle.Error + Header = header, + Message = msg, + NotificationType = NotificationStyle.Error, }); } @@ -49,7 +53,9 @@ public static void AddWarningNotification(this INotificationModel model, string model.Notifications?.Add(new BackOfficeNotification { - Header = header, Message = msg, NotificationType = NotificationStyle.Warning + Header = header, + Message = msg, + NotificationType = NotificationStyle.Warning, }); } @@ -62,12 +68,13 @@ public static void AddInfoNotification(this INotificationModel model, string hea model.Notifications?.Add(new BackOfficeNotification { - Header = header, Message = msg, NotificationType = NotificationStyle.Info + Header = header, + Message = msg, + NotificationType = NotificationStyle.Info, }); } - private static bool Exists(this INotificationModel model, string? header, string message, - NotificationStyle notificationType) => model.Notifications?.Any(x => + private static bool Exists(this INotificationModel model, string? header, string message, NotificationStyle notificationType) => model.Notifications?.Any(x => (x.Header?.InvariantEquals(header) ?? false) && (x.Message?.InvariantEquals(message) ?? false) && x.NotificationType == notificationType) ?? false; } diff --git a/src/Umbraco.Core/Models/ContentEditing/ModelWithNotifications.cs b/src/Umbraco.Core/Models/ContentEditing/ModelWithNotifications.cs index a93acf28aa07..56275bfb6cbe 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ModelWithNotifications.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ModelWithNotifications.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/MoveOrCopy.cs b/src/Umbraco.Core/Models/ContentEditing/MoveOrCopy.cs index 3fa3d25b73d8..ecbcc027f47b 100644 --- a/src/Umbraco.Core/Models/ContentEditing/MoveOrCopy.cs +++ b/src/Umbraco.Core/Models/ContentEditing/MoveOrCopy.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/NotificationStyle.cs b/src/Umbraco.Core/Models/ContentEditing/NotificationStyle.cs index d645b135fdbd..1fe9e9b525eb 100644 --- a/src/Umbraco.Core/Models/ContentEditing/NotificationStyle.cs +++ b/src/Umbraco.Core/Models/ContentEditing/NotificationStyle.cs @@ -28,5 +28,5 @@ public enum NotificationStyle /// /// Warning icon /// - Warning = 4 + Warning = 4, } diff --git a/src/Umbraco.Core/Models/ContentEditing/NotifySetting.cs b/src/Umbraco.Core/Models/ContentEditing/NotifySetting.cs index 8fefe34065a0..603ec953b078 100644 --- a/src/Umbraco.Core/Models/ContentEditing/NotifySetting.cs +++ b/src/Umbraco.Core/Models/ContentEditing/NotifySetting.cs @@ -1,13 +1,15 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "notifySetting", Namespace = "")] public class NotifySetting { - [DataMember(Name = "name")] public string? Name { get; set; } + [DataMember(Name = "name")] + public string? Name { get; set; } - [DataMember(Name = "checked")] public bool Checked { get; set; } + [DataMember(Name = "checked")] + public bool Checked { get; set; } /// /// The letter from the IAction diff --git a/src/Umbraco.Core/Models/ContentEditing/ObjectType.cs b/src/Umbraco.Core/Models/ContentEditing/ObjectType.cs index e95fb7484a3d..c2f69218b3b0 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ObjectType.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ObjectType.cs @@ -1,11 +1,13 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "objectType", Namespace = "")] public class ObjectType { - [DataMember(Name = "name")] public string? Name { get; set; } + [DataMember(Name = "name")] + public string? Name { get; set; } - [DataMember(Name = "id")] public Guid Id { get; set; } + [DataMember(Name = "id")] + public Guid Id { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/Permission.cs b/src/Umbraco.Core/Models/ContentEditing/Permission.cs index 388c842de0b4..9bdb664579f7 100644 --- a/src/Umbraco.Core/Models/ContentEditing/Permission.cs +++ b/src/Umbraco.Core/Models/ContentEditing/Permission.cs @@ -1,17 +1,21 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "permission", Namespace = "")] public class Permission : ICloneable { - [DataMember(Name = "name")] public string? Name { get; set; } + [DataMember(Name = "name")] + public string? Name { get; set; } - [DataMember(Name = "description")] public string? Description { get; set; } + [DataMember(Name = "description")] + public string? Description { get; set; } - [DataMember(Name = "checked")] public bool Checked { get; set; } + [DataMember(Name = "checked")] + public bool Checked { get; set; } - [DataMember(Name = "icon")] public string? Icon { get; set; } + [DataMember(Name = "icon")] + public string? Icon { get; set; } /// /// We'll use this to map the categories but it wont' be returned in the json diff --git a/src/Umbraco.Core/Models/ContentEditing/PostedFiles.cs b/src/Umbraco.Core/Models/ContentEditing/PostedFiles.cs index e2e4dae6c5cc..5d713691413c 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PostedFiles.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PostedFiles.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Editors; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -18,5 +18,6 @@ public PostedFiles() public List UploadedFiles { get; } - [DataMember(Name = "notifications")] public List Notifications { get; private set; } + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/PostedFolder.cs b/src/Umbraco.Core/Models/ContentEditing/PostedFolder.cs index 148d15f93da6..79769559db24 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PostedFolder.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PostedFolder.cs @@ -8,7 +8,9 @@ namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract] public class PostedFolder { - [DataMember(Name = "parentId")] public string? ParentId { get; set; } + [DataMember(Name = "parentId")] + public string? ParentId { get; set; } - [DataMember(Name = "name")] public string? Name { get; set; } + [DataMember(Name = "name")] + public string? Name { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/PropertyEditorBasic.cs b/src/Umbraco.Core/Models/ContentEditing/PropertyEditorBasic.cs index 5f82d4361005..498537cf1e5d 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PropertyEditorBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PropertyEditorBasic.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -8,9 +8,12 @@ namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "propertyEditor", Namespace = "")] public class PropertyEditorBasic { - [DataMember(Name = "alias")] public string? Alias { get; set; } + [DataMember(Name = "alias")] + public string? Alias { get; set; } - [DataMember(Name = "name")] public string? Name { get; set; } + [DataMember(Name = "name")] + public string? Name { get; set; } - [DataMember(Name = "icon")] public string? Icon { get; set; } + [DataMember(Name = "icon")] + public string? Icon { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/PropertyGroupBasic.cs b/src/Umbraco.Core/Models/ContentEditing/PropertyGroupBasic.cs index 8d5dbb5b6adb..5b45776a8e4a 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PropertyGroupBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PropertyGroupBasic.cs @@ -30,19 +30,25 @@ public abstract class PropertyGroupBasic public bool Inherited { get; set; } // needed - so we can handle alias renames - [DataMember(Name = "id")] public int Id { get; set; } + [DataMember(Name = "id")] + public int Id { get; set; } - [DataMember(Name = "key")] public Guid Key { get; set; } + [DataMember(Name = "key")] + public Guid Key { get; set; } - [DataMember(Name = "type")] public PropertyGroupType Type { get; set; } + [DataMember(Name = "type")] + public PropertyGroupType Type { get; set; } - [Required] [DataMember(Name = "name")] public string? Name { get; set; } + [Required] + [DataMember(Name = "name")] + public string? Name { get; set; } [Required] [DataMember(Name = "alias")] public string Alias { get; set; } = null!; - [DataMember(Name = "sortOrder")] public int SortOrder { get; set; } + [DataMember(Name = "sortOrder")] + public int SortOrder { get; set; } } [DataContract(Name = "propertyGroup", Namespace = "")] @@ -51,5 +57,6 @@ public class PropertyGroupBasic : PropertyGroupBasic { public PropertyGroupBasic() => Properties = new List(); - [DataMember(Name = "properties")] public IEnumerable Properties { get; set; } + [DataMember(Name = "properties")] + public IEnumerable Properties { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/PropertyGroupBasicExtensions.cs b/src/Umbraco.Core/Models/ContentEditing/PropertyGroupBasicExtensions.cs index 1e38ceed3569..4e3b530f99d4 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PropertyGroupBasicExtensions.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PropertyGroupBasicExtensions.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.ContentEditing; +namespace Umbraco.Cms.Core.Models.ContentEditing; internal static class PropertyGroupBasicExtensions { diff --git a/src/Umbraco.Core/Models/ContentEditing/PropertyGroupDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/PropertyGroupDisplay.cs index 9c8cd30b8750..67a200cf65ee 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PropertyGroupDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PropertyGroupDisplay.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/PropertyTypeBasic.cs b/src/Umbraco.Core/Models/ContentEditing/PropertyTypeBasic.cs index 4e4b5e33d2b3..4574e62cde4a 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PropertyTypeBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PropertyTypeBasic.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; @@ -20,22 +20,26 @@ public class PropertyTypeBasic public bool Inherited { get; set; } // needed - so we can handle alias renames - [DataMember(Name = "id")] public int Id { get; set; } + [DataMember(Name = "id")] + public int Id { get; set; } [Required] [RegularExpression(@"^([a-zA-Z]\w.*)$", ErrorMessage = "Invalid alias")] [DataMember(Name = "alias")] public string Alias { get; set; } = null!; - [DataMember(Name = "description")] public string? Description { get; set; } + [DataMember(Name = "description")] + public string? Description { get; set; } - [DataMember(Name = "validation")] public PropertyTypeValidation? Validation { get; set; } + [DataMember(Name = "validation")] + public PropertyTypeValidation? Validation { get; set; } [DataMember(Name = "label")] [Required] public string Label { get; set; } = null!; - [DataMember(Name = "sortOrder")] public int SortOrder { get; set; } + [DataMember(Name = "sortOrder")] + public int SortOrder { get; set; } [DataMember(Name = "dataTypeId")] [Required] @@ -53,8 +57,9 @@ public class PropertyTypeBasic [ReadOnly(true)] public string? DataTypeIcon { get; set; } - //SD: Is this really needed ? - [DataMember(Name = "groupId")] public int GroupId { get; set; } + // SD: Is this really needed ? + [DataMember(Name = "groupId")] + public int GroupId { get; set; } [DataMember(Name = "allowCultureVariant")] public bool AllowCultureVariant { get; set; } @@ -62,5 +67,6 @@ public class PropertyTypeBasic [DataMember(Name = "allowSegmentVariant")] public bool AllowSegmentVariant { get; set; } - [DataMember(Name = "labelOnTop")] public bool LabelOnTop { get; set; } + [DataMember(Name = "labelOnTop")] + public bool LabelOnTop { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/PropertyTypeDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/PropertyTypeDisplay.cs index 17edd12c0b9c..926ea50106fa 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PropertyTypeDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PropertyTypeDisplay.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/PropertyTypeValidation.cs b/src/Umbraco.Core/Models/ContentEditing/PropertyTypeValidation.cs index 3fac28a49b8e..76e9547c0769 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PropertyTypeValidation.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PropertyTypeValidation.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -8,12 +8,15 @@ namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "propertyValidation", Namespace = "")] public class PropertyTypeValidation { - [DataMember(Name = "mandatory")] public bool Mandatory { get; set; } + [DataMember(Name = "mandatory")] + public bool Mandatory { get; set; } [DataMember(Name = "mandatoryMessage")] public string? MandatoryMessage { get; set; } - [DataMember(Name = "pattern")] public string? Pattern { get; set; } + [DataMember(Name = "pattern")] + public string? Pattern { get; set; } - [DataMember(Name = "patternMessage")] public string? PatternMessage { get; set; } + [DataMember(Name = "patternMessage")] + public string? PatternMessage { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/PublicAccess.cs b/src/Umbraco.Core/Models/ContentEditing/PublicAccess.cs index 1772158cce30..1c21aec0339e 100644 --- a/src/Umbraco.Core/Models/ContentEditing/PublicAccess.cs +++ b/src/Umbraco.Core/Models/ContentEditing/PublicAccess.cs @@ -1,15 +1,19 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "publicAccess", Namespace = "")] public class PublicAccess { - [DataMember(Name = "groups")] public MemberGroupDisplay[]? Groups { get; set; } + [DataMember(Name = "groups")] + public MemberGroupDisplay[]? Groups { get; set; } - [DataMember(Name = "loginPage")] public EntityBasic? LoginPage { get; set; } + [DataMember(Name = "loginPage")] + public EntityBasic? LoginPage { get; set; } - [DataMember(Name = "errorPage")] public EntityBasic? ErrorPage { get; set; } + [DataMember(Name = "errorPage")] + public EntityBasic? ErrorPage { get; set; } - [DataMember(Name = "members")] public MemberDisplay[]? Members { get; set; } + [DataMember(Name = "members")] + public MemberDisplay[]? Members { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/RedirectUrlSearchResults.cs b/src/Umbraco.Core/Models/ContentEditing/RedirectUrlSearchResults.cs index e2116b53b11c..8a1a8d91c924 100644 --- a/src/Umbraco.Core/Models/ContentEditing/RedirectUrlSearchResults.cs +++ b/src/Umbraco.Core/Models/ContentEditing/RedirectUrlSearchResults.cs @@ -1,15 +1,19 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "redirectUrlSearchResult", Namespace = "")] public class RedirectUrlSearchResult { - [DataMember(Name = "searchResults")] public IEnumerable? SearchResults { get; set; } + [DataMember(Name = "searchResults")] + public IEnumerable? SearchResults { get; set; } - [DataMember(Name = "totalCount")] public long TotalCount { get; set; } + [DataMember(Name = "totalCount")] + public long TotalCount { get; set; } - [DataMember(Name = "pageCount")] public int PageCount { get; set; } + [DataMember(Name = "pageCount")] + public int PageCount { get; set; } - [DataMember(Name = "currentPage")] public int CurrentPage { get; set; } + [DataMember(Name = "currentPage")] + public int CurrentPage { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/RelationDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/RelationDisplay.cs index de1c3b3db0bb..d4cb9602517f 100644 --- a/src/Umbraco.Core/Models/ContentEditing/RelationDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/RelationDisplay.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/RelationTypeDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/RelationTypeDisplay.cs index e3fee5931c8f..b6168e13d501 100644 --- a/src/Umbraco.Core/Models/ContentEditing/RelationTypeDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/RelationTypeDisplay.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/RelationTypeSave.cs b/src/Umbraco.Core/Models/ContentEditing/RelationTypeSave.cs index 12f9c64b0c88..910d2827f7d4 100644 --- a/src/Umbraco.Core/Models/ContentEditing/RelationTypeSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/RelationTypeSave.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/RichTextEditorCommand.cs b/src/Umbraco.Core/Models/ContentEditing/RichTextEditorCommand.cs index 2bee6173fd96..782f34c88ced 100644 --- a/src/Umbraco.Core/Models/ContentEditing/RichTextEditorCommand.cs +++ b/src/Umbraco.Core/Models/ContentEditing/RichTextEditorCommand.cs @@ -1,20 +1,23 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; +public enum RichTextEditorCommandMode +{ + Insert, + Selection, + All, +} + [DataContract(Name = "richtexteditorcommand", Namespace = "")] public class RichTextEditorCommand { - [DataMember(Name = "name")] public string? Name { get; set; } + [DataMember(Name = "name")] + public string? Name { get; set; } - [DataMember(Name = "alias")] public string? Alias { get; set; } + [DataMember(Name = "alias")] + public string? Alias { get; set; } - [DataMember(Name = "mode")] public RichTextEditorCommandMode Mode { get; set; } -} - -public enum RichTextEditorCommandMode -{ - Insert, - Selection, - All + [DataMember(Name = "mode")] + public RichTextEditorCommandMode Mode { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/RichTextEditorConfiguration.cs b/src/Umbraco.Core/Models/ContentEditing/RichTextEditorConfiguration.cs index e791a9759286..c621aa8c59ea 100644 --- a/src/Umbraco.Core/Models/ContentEditing/RichTextEditorConfiguration.cs +++ b/src/Umbraco.Core/Models/ContentEditing/RichTextEditorConfiguration.cs @@ -1,17 +1,22 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "richtexteditorconfiguration", Namespace = "")] public class RichTextEditorConfiguration { - [DataMember(Name = "plugins")] public IEnumerable? Plugins { get; set; } + [DataMember(Name = "plugins")] + public IEnumerable? Plugins { get; set; } - [DataMember(Name = "commands")] public IEnumerable? Commands { get; set; } + [DataMember(Name = "commands")] + public IEnumerable? Commands { get; set; } - [DataMember(Name = "validElements")] public string? ValidElements { get; set; } + [DataMember(Name = "validElements")] + public string? ValidElements { get; set; } - [DataMember(Name = "inValidElements")] public string? InvalidElements { get; set; } + [DataMember(Name = "inValidElements")] + public string? InvalidElements { get; set; } - [DataMember(Name = "customConfig")] public IDictionary? CustomConfig { get; set; } + [DataMember(Name = "customConfig")] + public IDictionary? CustomConfig { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/RichTextEditorPlugin.cs b/src/Umbraco.Core/Models/ContentEditing/RichTextEditorPlugin.cs index 49cbdc63ab4e..c35eb1e18c95 100644 --- a/src/Umbraco.Core/Models/ContentEditing/RichTextEditorPlugin.cs +++ b/src/Umbraco.Core/Models/ContentEditing/RichTextEditorPlugin.cs @@ -1,9 +1,10 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "richtexteditorplugin", Namespace = "")] public class RichTextEditorPlugin { - [DataMember(Name = "name")] public string? Name { get; set; } + [DataMember(Name = "name")] + public string? Name { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/RollbackVersion.cs b/src/Umbraco.Core/Models/ContentEditing/RollbackVersion.cs index 4ea93e7da74b..dfd4511aa168 100644 --- a/src/Umbraco.Core/Models/ContentEditing/RollbackVersion.cs +++ b/src/Umbraco.Core/Models/ContentEditing/RollbackVersion.cs @@ -1,15 +1,18 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "rollbackVersion", Namespace = "")] public class RollbackVersion { - [DataMember(Name = "versionId")] public int VersionId { get; set; } + [DataMember(Name = "versionId")] + public int VersionId { get; set; } - [DataMember(Name = "versionDate")] public DateTime? VersionDate { get; set; } + [DataMember(Name = "versionDate")] + public DateTime? VersionDate { get; set; } - [DataMember(Name = "versionAuthorId")] public int VersionAuthorId { get; set; } + [DataMember(Name = "versionAuthorId")] + public int VersionAuthorId { get; set; } [DataMember(Name = "versionAuthorName")] public string? VersionAuthorName { get; set; } diff --git a/src/Umbraco.Core/Models/ContentEditing/SearchResult.cs b/src/Umbraco.Core/Models/ContentEditing/SearchResult.cs index d4f556cd6736..8a7fc5360597 100644 --- a/src/Umbraco.Core/Models/ContentEditing/SearchResult.cs +++ b/src/Umbraco.Core/Models/ContentEditing/SearchResult.cs @@ -1,15 +1,19 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "result", Namespace = "")] public class SearchResult { - [DataMember(Name = "id")] public string? Id { get; set; } + [DataMember(Name = "id")] + public string? Id { get; set; } - [DataMember(Name = "score")] public float Score { get; set; } + [DataMember(Name = "score")] + public float Score { get; set; } - [DataMember(Name = "fieldCount")] public int FieldCount => Values?.Count ?? 0; + [DataMember(Name = "fieldCount")] + public int FieldCount => Values?.Count ?? 0; - [DataMember(Name = "values")] public IReadOnlyDictionary>? Values { get; set; } + [DataMember(Name = "values")] + public IReadOnlyDictionary>? Values { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/SearchResultEntity.cs b/src/Umbraco.Core/Models/ContentEditing/SearchResultEntity.cs index 0544c37c62b2..f86ffc232a95 100644 --- a/src/Umbraco.Core/Models/ContentEditing/SearchResultEntity.cs +++ b/src/Umbraco.Core/Models/ContentEditing/SearchResultEntity.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/SearchResults.cs b/src/Umbraco.Core/Models/ContentEditing/SearchResults.cs index 485347479d42..fb7b0fc1012c 100644 --- a/src/Umbraco.Core/Models/ContentEditing/SearchResults.cs +++ b/src/Umbraco.Core/Models/ContentEditing/SearchResults.cs @@ -1,13 +1,15 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "results", Namespace = "")] public class SearchResults { - [DataMember(Name = "totalRecords")] public long TotalRecords { get; set; } + [DataMember(Name = "totalRecords")] + public long TotalRecords { get; set; } - [DataMember(Name = "results")] public IEnumerable? Results { get; set; } + [DataMember(Name = "results")] + public IEnumerable? Results { get; set; } - public static SearchResults Empty() => new() {Results = Enumerable.Empty(), TotalRecords = 0}; + public static SearchResults Empty() => new() { Results = Enumerable.Empty(), TotalRecords = 0 }; } diff --git a/src/Umbraco.Core/Models/ContentEditing/Section.cs b/src/Umbraco.Core/Models/ContentEditing/Section.cs index 9ff238536cec..68d34822c389 100644 --- a/src/Umbraco.Core/Models/ContentEditing/Section.cs +++ b/src/Umbraco.Core/Models/ContentEditing/Section.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -8,9 +8,11 @@ namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "section", Namespace = "")] public class Section { - [DataMember(Name = "name")] public string Name { get; set; } = null!; + [DataMember(Name = "name")] + public string Name { get; set; } = null!; - [DataMember(Name = "alias")] public string Alias { get; set; } = null!; + [DataMember(Name = "alias")] + public string Alias { get; set; } = null!; /// /// In some cases a custom route path can be specified so that when clicking on a section it goes to this diff --git a/src/Umbraco.Core/Models/ContentEditing/SimpleNotificationModel.cs b/src/Umbraco.Core/Models/ContentEditing/SimpleNotificationModel.cs index e8bb0533022a..9fe429cf3fba 100644 --- a/src/Umbraco.Core/Models/ContentEditing/SimpleNotificationModel.cs +++ b/src/Umbraco.Core/Models/ContentEditing/SimpleNotificationModel.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/SnippetDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/SnippetDisplay.cs index 922ff045d0b4..48b3d71cacc1 100644 --- a/src/Umbraco.Core/Models/ContentEditing/SnippetDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/SnippetDisplay.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; diff --git a/src/Umbraco.Core/Models/ContentEditing/StyleSheet.cs b/src/Umbraco.Core/Models/ContentEditing/StyleSheet.cs index af2193d64236..6a8d7c14fe70 100644 --- a/src/Umbraco.Core/Models/ContentEditing/StyleSheet.cs +++ b/src/Umbraco.Core/Models/ContentEditing/StyleSheet.cs @@ -1,11 +1,13 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "stylesheet", Namespace = "")] public class Stylesheet { - [DataMember(Name = "name")] public string? Name { get; set; } + [DataMember(Name = "name")] + public string? Name { get; set; } - [DataMember(Name = "path")] public string? Path { get; set; } + [DataMember(Name = "path")] + public string? Path { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/StylesheetRule.cs b/src/Umbraco.Core/Models/ContentEditing/StylesheetRule.cs index 0c37b86e9ded..f7af3d984fef 100644 --- a/src/Umbraco.Core/Models/ContentEditing/StylesheetRule.cs +++ b/src/Umbraco.Core/Models/ContentEditing/StylesheetRule.cs @@ -1,13 +1,16 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "stylesheetRule", Namespace = "")] public class StylesheetRule { - [DataMember(Name = "name")] public string Name { get; set; } = null!; + [DataMember(Name = "name")] + public string Name { get; set; } = null!; - [DataMember(Name = "selector")] public string Selector { get; set; } = null!; + [DataMember(Name = "selector")] + public string Selector { get; set; } = null!; - [DataMember(Name = "styles")] public string Styles { get; set; } = null!; + [DataMember(Name = "styles")] + public string Styles { get; set; } = null!; } diff --git a/src/Umbraco.Core/Models/ContentEditing/Tab.cs b/src/Umbraco.Core/Models/ContentEditing/Tab.cs index 54345733b8cb..ab1e92d340e8 100644 --- a/src/Umbraco.Core/Models/ContentEditing/Tab.cs +++ b/src/Umbraco.Core/Models/ContentEditing/Tab.cs @@ -8,17 +8,23 @@ namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "tab", Namespace = "")] public class Tab { - [DataMember(Name = "id")] public int Id { get; set; } + [DataMember(Name = "id")] + public int Id { get; set; } - [DataMember(Name = "key")] public Guid Key { get; set; } + [DataMember(Name = "key")] + public Guid Key { get; set; } - [DataMember(Name = "type")] public string? Type { get; set; } + [DataMember(Name = "type")] + public string? Type { get; set; } - [DataMember(Name = "active")] public bool IsActive { get; set; } + [DataMember(Name = "active")] + public bool IsActive { get; set; } - [DataMember(Name = "label")] public string? Label { get; set; } + [DataMember(Name = "label")] + public string? Label { get; set; } - [DataMember(Name = "alias")] public string? Alias { get; set; } + [DataMember(Name = "alias")] + public string? Alias { get; set; } /// /// The expanded state of the tab @@ -26,5 +32,6 @@ public class Tab [DataMember(Name = "open")] public bool Expanded { get; set; } = true; - [DataMember(Name = "properties")] public IEnumerable? Properties { get; set; } + [DataMember(Name = "properties")] + public IEnumerable? Properties { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/TabbedContentItem.cs b/src/Umbraco.Core/Models/ContentEditing/TabbedContentItem.cs index 2c9ee4e2e9c8..c47424cdf0f9 100644 --- a/src/Umbraco.Core/Models/ContentEditing/TabbedContentItem.cs +++ b/src/Umbraco.Core/Models/ContentEditing/TabbedContentItem.cs @@ -1,8 +1,9 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; -public abstract class TabbedContentItem : ContentItemBasic, ITabbedContent where T : ContentPropertyBasic +public abstract class TabbedContentItem : ContentItemBasic, ITabbedContent + where T : ContentPropertyBasic { protected TabbedContentItem() => Tabs = new List>(); diff --git a/src/Umbraco.Core/Models/ContentEditing/TemplateDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/TemplateDisplay.cs index ee40bd57b008..b6dadcdc2a34 100644 --- a/src/Umbraco.Core/Models/ContentEditing/TemplateDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/TemplateDisplay.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -6,21 +6,28 @@ namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "template", Namespace = "")] public class TemplateDisplay : INotificationModel { - [DataMember(Name = "id")] public int Id { get; set; } + [DataMember(Name = "id")] + public int Id { get; set; } - [Required] [DataMember(Name = "name")] public string? Name { get; set; } + [Required] + [DataMember(Name = "name")] + public string? Name { get; set; } [Required] [DataMember(Name = "alias")] public string Alias { get; set; } = string.Empty; - [DataMember(Name = "key")] public Guid Key { get; set; } + [DataMember(Name = "key")] + public Guid Key { get; set; } - [DataMember(Name = "content")] public string? Content { get; set; } + [DataMember(Name = "content")] + public string? Content { get; set; } - [DataMember(Name = "path")] public string? Path { get; set; } + [DataMember(Name = "path")] + public string? Path { get; set; } - [DataMember(Name = "virtualPath")] public string? VirtualPath { get; set; } + [DataMember(Name = "virtualPath")] + public string? VirtualPath { get; set; } [DataMember(Name = "masterTemplateAlias")] public string? MasterTemplateAlias { get; set; } diff --git a/src/Umbraco.Core/Models/ContentEditing/TreeSearchResult.cs b/src/Umbraco.Core/Models/ContentEditing/TreeSearchResult.cs index 949b9b0616ca..f1b3dea9b261 100644 --- a/src/Umbraco.Core/Models/ContentEditing/TreeSearchResult.cs +++ b/src/Umbraco.Core/Models/ContentEditing/TreeSearchResult.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -8,9 +8,11 @@ namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "searchResult", Namespace = "")] public class TreeSearchResult { - [DataMember(Name = "appAlias")] public string? AppAlias { get; set; } + [DataMember(Name = "appAlias")] + public string? AppAlias { get; set; } - [DataMember(Name = "treeAlias")] public string? TreeAlias { get; set; } + [DataMember(Name = "treeAlias")] + public string? TreeAlias { get; set; } /// /// This is optional but if specified should be the name of an angular service to format the search result. @@ -25,5 +27,6 @@ public class TreeSearchResult [DataMember(Name = "jsMethod")] public string? JsFormatterMethod { get; set; } - [DataMember(Name = "results")] public IEnumerable? Results { get; set; } + [DataMember(Name = "results")] + public IEnumerable? Results { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/UmbracoEntityTypes.cs b/src/Umbraco.Core/Models/ContentEditing/UmbracoEntityTypes.cs index e194101d7d80..e089093174c0 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UmbracoEntityTypes.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UmbracoEntityTypes.cs @@ -93,5 +93,5 @@ public enum UmbracoEntityTypes /// /// Dictionary Item /// - DictionaryItem + DictionaryItem, } diff --git a/src/Umbraco.Core/Models/ContentEditing/UnpublishContent.cs b/src/Umbraco.Core/Models/ContentEditing/UnpublishContent.cs index cf8dfeea7e68..cc77bf5dbfa1 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UnpublishContent.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UnpublishContent.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -8,7 +8,9 @@ namespace Umbraco.Cms.Core.Models.ContentEditing; [DataContract(Name = "unpublish", Namespace = "")] public class UnpublishContent { - [DataMember(Name = "id")] public int Id { get; set; } + [DataMember(Name = "id")] + public int Id { get; set; } - [DataMember(Name = "cultures")] public string[]? Cultures { get; set; } + [DataMember(Name = "cultures")] + public string[]? Cultures { get; set; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/UrlAndAnchors.cs b/src/Umbraco.Core/Models/ContentEditing/UrlAndAnchors.cs index 55814dc2bc3e..1a732ed01762 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UrlAndAnchors.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UrlAndAnchors.cs @@ -11,7 +11,9 @@ public UrlAndAnchors(string url, IEnumerable anchorValues) AnchorValues = anchorValues; } - [DataMember(Name = "url")] public string Url { get; } + [DataMember(Name = "url")] + public string Url { get; } - [DataMember(Name = "anchorValues")] public IEnumerable AnchorValues { get; } + [DataMember(Name = "anchorValues")] + public IEnumerable AnchorValues { get; } } diff --git a/src/Umbraco.Core/Models/ContentEditing/UserBasic.cs b/src/Umbraco.Core/Models/ContentEditing/UserBasic.cs index 9a6d085288d9..6d20e54bfa0e 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UserBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UserBasic.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Membership; @@ -17,7 +17,8 @@ public UserBasic() UserGroups = new List(); } - [DataMember(Name = "username")] public string? Username { get; set; } + [DataMember(Name = "username")] + public string? Username { get; set; } /// /// The MD5 lowercase hash of the email which can be used by gravatar @@ -25,7 +26,8 @@ public UserBasic() [DataMember(Name = "emailHash")] public string? EmailHash { get; set; } - [DataMember(Name = "lastLoginDate")] public DateTime? LastLoginDate { get; set; } + [DataMember(Name = "lastLoginDate")] + public DateTime? LastLoginDate { get; set; } /// /// Returns a list of different size avatars @@ -33,7 +35,8 @@ public UserBasic() [DataMember(Name = "avatars")] public string[]? Avatars { get; set; } - [DataMember(Name = "userState")] public UserState UserState { get; set; } + [DataMember(Name = "userState")] + public UserState UserState { get; set; } [DataMember(Name = "culture", IsRequired = true)] public string? Culture { get; set; } diff --git a/src/Umbraco.Core/Models/ContentEditing/UserDetail.cs b/src/Umbraco.Core/Models/ContentEditing/UserDetail.cs index bcbf426225f7..88b31ee4a23c 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UserDetail.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UserDetail.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; diff --git a/src/Umbraco.Core/Models/ContentEditing/UserDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/UserDisplay.cs index 52a1c6234190..4b300c17a928 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UserDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UserDisplay.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -29,9 +29,11 @@ public UserDisplay() [DataMember(Name = "availableCultures")] public IDictionary AvailableCultures { get; set; } - [DataMember(Name = "startContentIds")] public IEnumerable StartContentIds { get; set; } + [DataMember(Name = "startContentIds")] + public IEnumerable StartContentIds { get; set; } - [DataMember(Name = "startMediaIds")] public IEnumerable StartMediaIds { get; set; } + [DataMember(Name = "startMediaIds")] + public IEnumerable StartMediaIds { get; set; } /// /// If the password is reset on save, this value will be populated diff --git a/src/Umbraco.Core/Models/ContentEditing/UserGroupBasic.cs b/src/Umbraco.Core/Models/ContentEditing/UserGroupBasic.cs index 8821f4012f0b..036694f2b624 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UserGroupBasic.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UserGroupBasic.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -11,12 +11,14 @@ public UserGroupBasic() Sections = Enumerable.Empty
(); } - [DataMember(Name = "sections")] public IEnumerable
Sections { get; set; } + [DataMember(Name = "sections")] + public IEnumerable
Sections { get; set; } [DataMember(Name = "contentStartNode")] public EntityBasic? ContentStartNode { get; set; } - [DataMember(Name = "mediaStartNode")] public EntityBasic? MediaStartNode { get; set; } + [DataMember(Name = "mediaStartNode")] + public EntityBasic? MediaStartNode { get; set; } /// /// The number of users assigned to this group diff --git a/src/Umbraco.Core/Models/ContentEditing/UserGroupDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/UserGroupDisplay.cs index 2b7f59bd996b..30cca62c4a44 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UserGroupDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UserGroupDisplay.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -11,7 +11,8 @@ public UserGroupDisplay() AssignedPermissions = Enumerable.Empty(); } - [DataMember(Name = "users")] public IEnumerable Users { get; set; } + [DataMember(Name = "users")] + public IEnumerable Users { get; set; } /// /// The default permissions for the user group organized by permission group name diff --git a/src/Umbraco.Core/Models/ContentEditing/UserGroupPermissionsSave.cs b/src/Umbraco.Core/Models/ContentEditing/UserGroupPermissionsSave.cs index 96e90ca37be5..1c04496e04b8 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UserGroupPermissionsSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UserGroupPermissionsSave.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Extensions; @@ -13,7 +13,6 @@ public class UserGroupPermissionsSave public UserGroupPermissionsSave() => AssignedPermissions = new Dictionary>(); // TODO: we should have an option to clear the permissions assigned to this node and instead just have them inherit - yes once we actually have inheritance! - [DataMember(Name = "contentId", IsRequired = true)] [Required] public int ContentId { get; set; } @@ -29,7 +28,7 @@ public IEnumerable Validate(ValidationContext validationContex { if (AssignedPermissions.SelectMany(x => x.Value).Any(x => x.IsNullOrWhiteSpace())) { - yield return new ValidationResult("A permission value cannot be null or empty", new[] {"Permissions"}); + yield return new ValidationResult("A permission value cannot be null or empty", new[] { "Permissions" }); } } } diff --git a/src/Umbraco.Core/Models/ContentEditing/UserGroupSave.cs b/src/Umbraco.Core/Models/ContentEditing/UserGroupSave.cs index f26473464274..ef49a6ab2808 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UserGroupSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UserGroupSave.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Extensions; @@ -22,13 +22,17 @@ public class UserGroupSave : EntityBasic, IValidatableObject [Required] public override string Alias { get; set; } = string.Empty; - [DataMember(Name = "sections")] public IEnumerable? Sections { get; set; } + [DataMember(Name = "sections")] + public IEnumerable? Sections { get; set; } - [DataMember(Name = "users")] public IEnumerable? Users { get; set; } + [DataMember(Name = "users")] + public IEnumerable? Users { get; set; } - [DataMember(Name = "startContentId")] public int? StartContentId { get; set; } + [DataMember(Name = "startContentId")] + public int? StartContentId { get; set; } - [DataMember(Name = "startMediaId")] public int? StartMediaId { get; set; } + [DataMember(Name = "startMediaId")] + public int? StartMediaId { get; set; } /// /// The list of letters (permission codes) to assign as the default for the user group @@ -55,7 +59,7 @@ public IEnumerable Validate(ValidationContext validationContex { if (DefaultPermissions?.Any(x => x.IsNullOrWhiteSpace()) ?? false) { - yield return new ValidationResult("A permission value cannot be null or empty", new[] {"Permissions"}); + yield return new ValidationResult("A permission value cannot be null or empty", new[] { "Permissions" }); } if (AssignedPermissions is not null) @@ -66,8 +70,9 @@ public IEnumerable Validate(ValidationContext validationContex { if (permission.IsNullOrWhiteSpace()) { - yield return new ValidationResult("A permission value cannot be null or empty", - new[] {"AssignedPermissions"}); + yield return new ValidationResult( + "A permission value cannot be null or empty", + new[] { "AssignedPermissions" }); } } } diff --git a/src/Umbraco.Core/Models/ContentEditing/UserInvite.cs b/src/Umbraco.Core/Models/ContentEditing/UserInvite.cs index f27dc1f2318b..02a10b45afff 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UserInvite.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UserInvite.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -22,16 +22,19 @@ public class UserInvite : EntityBasic, IValidatableObject [EmailAddress] public string Email { get; set; } = null!; - [DataMember(Name = "username")] public string? Username { get; set; } + [DataMember(Name = "username")] + public string? Username { get; set; } - [DataMember(Name = "message")] public string? Message { get; set; } + [DataMember(Name = "message")] + public string? Message { get; set; } public IEnumerable Validate(ValidationContext validationContext) { if (UserGroups.Any() == false) { - yield return new ValidationResult("A user must be assigned to at least one group", - new[] {nameof(UserGroups)}); + yield return new ValidationResult( + "A user must be assigned to at least one group", + new[] { nameof(UserGroups) }); } IOptionsSnapshot securitySettings = @@ -39,7 +42,7 @@ public IEnumerable Validate(ValidationContext validationContex if (securitySettings.Value.UsernameIsEmail == false && Username.IsNullOrWhiteSpace()) { - yield return new ValidationResult("A username cannot be empty", new[] {nameof(Username)}); + yield return new ValidationResult("A username cannot be empty", new[] { nameof(Username) }); } } } diff --git a/src/Umbraco.Core/Models/ContentEditing/UserProfile.cs b/src/Umbraco.Core/Models/ContentEditing/UserProfile.cs index b5f3a2f8d0f3..441972e8bc15 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UserProfile.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UserProfile.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.ContentEditing; @@ -17,6 +17,5 @@ public class UserProfile : IComparable [Required] public string? Name { get; set; } - int IComparable.CompareTo(object? obj) => string.Compare(Name, ((UserProfile?)obj)?.Name, StringComparison.Ordinal); } diff --git a/src/Umbraco.Core/Models/ContentEditing/UserSave.cs b/src/Umbraco.Core/Models/ContentEditing/UserSave.cs index ee0ef916cd18..e0a3d41d4fd6 100644 --- a/src/Umbraco.Core/Models/ContentEditing/UserSave.cs +++ b/src/Umbraco.Core/Models/ContentEditing/UserSave.cs @@ -40,15 +40,17 @@ public class UserSave : EntityBasic, IValidatableObject [Required] public IEnumerable UserGroups { get; set; } = null!; - [DataMember(Name = "startContentIds")] public int[]? StartContentIds { get; set; } + [DataMember(Name = "startContentIds")] + public int[]? StartContentIds { get; set; } - [DataMember(Name = "startMediaIds")] public int[]? StartMediaIds { get; set; } + [DataMember(Name = "startMediaIds")] + public int[]? StartMediaIds { get; set; } public IEnumerable Validate(ValidationContext validationContext) { if (UserGroups.Any() == false) { - yield return new ValidationResult("A user must be assigned to at least one group", new[] {"UserGroups"}); + yield return new ValidationResult("A user must be assigned to at least one group", new[] { "UserGroups" }); } } } diff --git a/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs b/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs index 81395c99d975..c093962408c2 100644 --- a/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs +++ b/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Editors; +namespace Umbraco.Cms.Core.Models.Editors; /// /// Used to track reference to other entities in a property value @@ -28,13 +28,16 @@ public UmbracoEntityReference(Udi udi) } } + public Udi Udi { get; } + public static UmbracoEntityReference Empty() => _empty; public static bool IsEmpty(UmbracoEntityReference reference) => reference == Empty(); - public Udi Udi { get; } public string RelationTypeAlias { get; } + public static bool operator ==(UmbracoEntityReference left, UmbracoEntityReference right) => left.Equals(right); + public override bool Equals(object? obj) => obj is UmbracoEntityReference reference && Equals(reference); public bool Equals(UmbracoEntityReference other) => @@ -49,7 +52,5 @@ public override int GetHashCode() return hashCode; } - public static bool operator ==(UmbracoEntityReference left, UmbracoEntityReference right) => left.Equals(right); - public static bool operator !=(UmbracoEntityReference left, UmbracoEntityReference right) => !(left == right); } diff --git a/src/Umbraco.Core/Models/Entities/MemberEntitySlim.cs b/src/Umbraco.Core/Models/Entities/MemberEntitySlim.cs index 00639405c068..923fef2477e7 100644 --- a/src/Umbraco.Core/Models/Entities/MemberEntitySlim.cs +++ b/src/Umbraco.Core/Models/Entities/MemberEntitySlim.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Entities; +namespace Umbraco.Cms.Core.Models.Entities; public class MemberEntitySlim : ContentEntitySlim, IMemberEntitySlim { diff --git a/src/Umbraco.Core/Models/Entities/TreeEntityBase.cs b/src/Umbraco.Core/Models/Entities/TreeEntityBase.cs index 06f1683eb250..f10e49b9574e 100644 --- a/src/Umbraco.Core/Models/Entities/TreeEntityBase.cs +++ b/src/Umbraco.Core/Models/Entities/TreeEntityBase.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models.Entities; @@ -64,6 +64,7 @@ public int ParentId _parent = null; return _parentId; } + set { if (value == 0) @@ -77,14 +78,6 @@ public int ParentId } } - /// - public void SetParent(ITreeEntity? parent) - { - _hasParentId = false; - _parent = parent; - OnPropertyChanged(nameof(ParentId)); - } - /// [DataMember] public int Level @@ -93,6 +86,14 @@ public int Level set => SetPropertyValueAndDetectChanges(value, ref _level, nameof(Level)); } + /// + public void SetParent(ITreeEntity? parent) + { + _hasParentId = false; + _parent = parent; + OnPropertyChanged(nameof(ParentId)); + } + /// [DataMember] public string Path diff --git a/src/Umbraco.Core/Models/Entities/TreeEntityPath.cs b/src/Umbraco.Core/Models/Entities/TreeEntityPath.cs index 018a865a7674..fe284a1e1119 100644 --- a/src/Umbraco.Core/Models/Entities/TreeEntityPath.cs +++ b/src/Umbraco.Core/Models/Entities/TreeEntityPath.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Entities; +namespace Umbraco.Cms.Core.Models.Entities; /// /// Represents the path of a tree entity. diff --git a/src/Umbraco.Core/Models/Mapping/MemberMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/MemberMapDefinition.cs index 69fefe6bb7a1..8444d5bd0a63 100644 --- a/src/Umbraco.Core/Models/Mapping/MemberMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/MemberMapDefinition.cs @@ -22,10 +22,10 @@ private static void Map(MemberSave source, IMember target, MapperContext context target.Email = source.Email; // TODO: ensure all properties are mapped as required - //target.Id = source.Id; - //target.ParentId = -1; - //target.Path = "-1," + source.Id; + // target.Id = source.Id; + // target.ParentId = -1; + // target.Path = "-1," + source.Id; - //TODO: add groups as required + // TODO: add groups as required } } diff --git a/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs b/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs index 2ab6242b2bd0..2a9696d855fe 100644 --- a/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs @@ -29,7 +29,8 @@ public class MemberTabsAndPropertiesMapper : TabsAndPropertiesMapper private readonly IMemberTypeService _memberTypeService; private readonly PropertyEditorCollection _propertyEditorCollection; - public MemberTabsAndPropertiesMapper(ICultureDictionary cultureDictionary, + public MemberTabsAndPropertiesMapper( + ICultureDictionary cultureDictionary, IBackOfficeSecurityAccessor backofficeSecurityAccessor, ILocalizedTextService localizedTextService, IMemberTypeService memberTypeService, @@ -54,7 +55,7 @@ public MemberTabsAndPropertiesMapper(ICultureDictionary cultureDictionary, /// Overridden to deal with custom member properties and permissions. public override IEnumerable> Map(IMember source, MapperContext context) { - IMemberType memberType = _memberTypeService.Get(source.ContentTypeId); + IMemberType? memberType = _memberTypeService.Get(source.ContentTypeId); if (memberType is not null) { @@ -69,121 +70,6 @@ public override IEnumerable> Map(IMember source, Map return resolved; } - [Obsolete("Use MapMembershipProperties. Will be removed in Umbraco 10.")] - protected override IEnumerable GetCustomGenericProperties(IContentBase content) - { - var member = (IMember)content; - return MapMembershipProperties(member, null); - } - - private Dictionary GetPasswordConfig(IMember member) - { - var result = new Dictionary(_memberPasswordConfiguration.GetConfiguration(true)) - { - // the password change toggle will only be displayed if there is already a password assigned. - {"hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false} - }; - - // This will always be true for members since we always want to allow admins to change a password - so long as that - // user has access to edit members (but that security is taken care of separately) - result["allowManuallyChangingPassword"] = true; - - return result; - } - - /// - /// Overridden to assign the IsSensitive property values - /// - /// - /// - /// - /// - protected override List MapProperties(IContentBase content, List properties, - MapperContext context) - { - List result = base.MapProperties(content, properties, context); - var member = (IMember)content; - IMemberType memberType = _memberTypeService.Get(member.ContentTypeId); - - // now update the IsSensitive value - foreach (ContentPropertyDisplay prop in result) - { - // check if this property is flagged as sensitive - var isSensitiveProperty = memberType?.IsSensitiveProperty(prop.Alias) ?? false; - // check permissions for viewing sensitive data - if (isSensitiveProperty && - _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasAccessToSensitiveData() == false) - { - // mark this property as sensitive - prop.IsSensitive = true; - // mark this property as readonly so that it does not post any data - prop.Readonly = true; - // replace this editor with a sensitive value - prop.View = "sensitivevalue"; - // clear the value - prop.Value = null; - } - } - - return result; - } - - /// - /// Returns the login property display field - /// - /// - /// - /// - /// - /// - /// If the membership provider installed is the umbraco membership provider, then we will allow changing the username, - /// however if - /// the membership provider is a custom one, we cannot allow changing the username because MembershipProvider's do not - /// actually natively - /// allow that. - /// - internal static ContentPropertyDisplay GetLoginProperty(IMember member, ILocalizedTextService localizedText) - { - var prop = new ContentPropertyDisplay - { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}login", - Label = localizedText.Localize(null, "login"), - Value = member.Username - }; - - prop.View = "textbox"; - prop.Validation.Mandatory = true; - return prop; - } - - internal IDictionary GetMemberGroupValue(string? username) - { - IEnumerable? userRoles = username.IsNullOrWhiteSpace() ? null : _memberService.GetAllRoles(username); - - // create a dictionary of all roles (except internal roles) + "false" - var result = _memberGroupService.GetAll() - .Select(x => x.Name!) - // if a role starts with __umbracoRole we won't show it as it's an internal role used for public access - .Where(x => x?.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false) - .OrderBy(x => x, StringComparer.OrdinalIgnoreCase) - .ToDictionary(x => x, x => false); - - // if user has no roles, just return the dictionary - if (userRoles == null) - { - return result; - } - - // else update the dictionary to "true" for the user roles (except internal roles) - foreach (var userRole in userRoles.Where(x => - x?.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false)) - { - result[userRole] = true; - } - - return result; - } - public IEnumerable MapMembershipProperties(IMember member, MapperContext? context) { var properties = new List @@ -195,7 +81,7 @@ public IEnumerable MapMembershipProperties(IMember membe Label = _localizedTextService.Localize("general", "email"), Value = member.Email, View = "email", - Validation = {Mandatory = true} + Validation = { Mandatory = true }, }, new() { @@ -204,11 +90,11 @@ public IEnumerable MapMembershipProperties(IMember membe Value = new Dictionary { // TODO: why ignoreCase, what are we doing here?! - {"newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null)} + { "newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null) }, }, View = "changepassword", Config = GetPasswordConfig( - member) // Initialize the dictionary with the configuration from the default membership provider + member), // Initialize the dictionary with the configuration from the default membership provider }, new() { @@ -216,7 +102,7 @@ public IEnumerable MapMembershipProperties(IMember membe Label = _localizedTextService.Localize("content", "membergroup"), Value = GetMemberGroupValue(member.Username), View = "membergroups", - Config = new Dictionary {{"IsRequired", true}} + Config = new Dictionary { { "IsRequired", true } }, }, // These properties used to live on the member as property data, defaulting to sensitive, so we set them to sensitive here too @@ -226,7 +112,7 @@ public IEnumerable MapMembershipProperties(IMember membe Label = _localizedTextService.Localize("user", "failedPasswordAttempts"), Value = member.FailedPasswordAttempts, View = "readonlyvalue", - IsSensitive = true + IsSensitive = true, }, new() { @@ -235,7 +121,7 @@ public IEnumerable MapMembershipProperties(IMember membe Value = member.IsApproved, View = "boolean", IsSensitive = true, - Readonly = false + Readonly = false, }, new() { @@ -245,7 +131,7 @@ public IEnumerable MapMembershipProperties(IMember membe View = "boolean", IsSensitive = true, Readonly = !member - .IsLockedOut // IMember.IsLockedOut can't be set to true, so make it readonly when that's the case (you can only unlock) + .IsLockedOut, // IMember.IsLockedOut can't be set to true, so make it readonly when that's the case (you can only unlock) }, new() { @@ -253,7 +139,7 @@ public IEnumerable MapMembershipProperties(IMember membe Label = _localizedTextService.Localize("user", "lastLockoutDate"), Value = member.LastLockoutDate?.ToString(), View = "readonlyvalue", - IsSensitive = true + IsSensitive = true, }, new() { @@ -261,7 +147,7 @@ public IEnumerable MapMembershipProperties(IMember membe Label = _localizedTextService.Localize("user", "lastLogin"), Value = member.LastLoginDate?.ToString(), View = "readonlyvalue", - IsSensitive = true + IsSensitive = true, }, new() { @@ -269,8 +155,8 @@ public IEnumerable MapMembershipProperties(IMember membe Label = _localizedTextService.Localize("user", "lastPasswordChangeDate"), Value = member.LastPasswordChangeDate?.ToString(), View = "readonlyvalue", - IsSensitive = true - } + IsSensitive = true, + }, }; if (_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasAccessToSensitiveData() is false) @@ -289,4 +175,123 @@ public IEnumerable MapMembershipProperties(IMember membe return properties; } + + /// + /// Returns the login property display field + /// + /// + /// + /// + /// + /// + /// If the membership provider installed is the umbraco membership provider, then we will allow changing the username, + /// however if + /// the membership provider is a custom one, we cannot allow changing the username because MembershipProvider's do not + /// actually natively + /// allow that. + /// + internal static ContentPropertyDisplay GetLoginProperty(IMember member, ILocalizedTextService localizedText) + { + var prop = new ContentPropertyDisplay + { + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}login", + Label = localizedText.Localize(null, "login"), + Value = member.Username, + }; + + prop.View = "textbox"; + prop.Validation.Mandatory = true; + return prop; + } + + [Obsolete("Use MapMembershipProperties. Will be removed in Umbraco 10.")] + protected override IEnumerable GetCustomGenericProperties(IContentBase content) + { + var member = (IMember)content; + return MapMembershipProperties(member, null); + } + + /// + /// Overridden to assign the IsSensitive property values + /// + /// + /// + /// + /// + protected override List MapProperties(IContentBase content, List properties, MapperContext context) + { + List result = base.MapProperties(content, properties, context); + var member = (IMember)content; + IMemberType? memberType = _memberTypeService.Get(member.ContentTypeId); + + // now update the IsSensitive value + foreach (ContentPropertyDisplay prop in result) + { + // check if this property is flagged as sensitive + var isSensitiveProperty = memberType?.IsSensitiveProperty(prop.Alias) ?? false; + + // check permissions for viewing sensitive data + if (isSensitiveProperty && + _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasAccessToSensitiveData() == false) + { + // mark this property as sensitive + prop.IsSensitive = true; + + // mark this property as readonly so that it does not post any data + prop.Readonly = true; + + // replace this editor with a sensitive value + prop.View = "sensitivevalue"; + + // clear the value + prop.Value = null; + } + } + + return result; + } + + private Dictionary GetPasswordConfig(IMember member) + { + var result = new Dictionary(_memberPasswordConfiguration.GetConfiguration(true)) + { + // the password change toggle will only be displayed if there is already a password assigned. + { "hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false }, + }; + + // This will always be true for members since we always want to allow admins to change a password - so long as that + // user has access to edit members (but that security is taken care of separately) + result["allowManuallyChangingPassword"] = true; + + return result; + } + + internal IDictionary GetMemberGroupValue(string? username) + { + IEnumerable? userRoles = username.IsNullOrWhiteSpace() ? null : _memberService.GetAllRoles(username); + + // create a dictionary of all roles (except internal roles) + "false" + var result = _memberGroupService.GetAll() + .Select(x => x.Name!) + + // if a role starts with __umbracoRole we won't show it as it's an internal role used for public access + .Where(x => x?.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false) + .OrderBy(x => x, StringComparer.OrdinalIgnoreCase) + .ToDictionary(x => x, x => false); + + // if user has no roles, just return the dictionary + if (userRoles == null) + { + return result; + } + + // else update the dictionary to "true" for the user roles (except internal roles) + foreach (var userRole in userRoles.Where(x => + x?.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false)) + { + result[userRole] = true; + } + + return result; + } } diff --git a/src/Umbraco.Core/Models/Mapping/PropertyTypeGroupMapper.cs b/src/Umbraco.Core/Models/Mapping/PropertyTypeGroupMapper.cs index d398298b0115..cb77d790cdda 100644 --- a/src/Umbraco.Core/Models/Mapping/PropertyTypeGroupMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/PropertyTypeGroupMapper.cs @@ -15,8 +15,7 @@ public class PropertyTypeGroupMapper private readonly PropertyEditorCollection _propertyEditors; private readonly IShortStringHelper _shortStringHelper; - public PropertyTypeGroupMapper(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, - IShortStringHelper shortStringHelper, ILogger> logger) + public PropertyTypeGroupMapper(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IShortStringHelper shortStringHelper, ILogger> logger) { _propertyEditors = propertyEditors; _dataTypeService = dataTypeService; @@ -24,52 +23,6 @@ public PropertyTypeGroupMapper(PropertyEditorCollection propertyEditors, IDataTy _logger = logger; } - /// - /// Gets the content type that defines a property group, within a composition. - /// - /// The composition. - /// The identifier of the property group. - /// The composition content type that defines the specified property group. - private static IContentTypeComposition? GetContentTypeForPropertyGroup(IContentTypeComposition contentType, - int propertyGroupId) - { - // test local groups - if (contentType.PropertyGroups.Any(x => x.Id == propertyGroupId)) - { - return contentType; - } - - // test composition types groups - // .ContentTypeComposition is just the local ones, not recursive, - // so we have to recurse here - return contentType.ContentTypeComposition - .Select(x => GetContentTypeForPropertyGroup(x, propertyGroupId)) - .FirstOrDefault(x => x != null); - } - - /// - /// Gets the content type that defines a property group, within a composition. - /// - /// The composition. - /// The identifier of the property type. - /// The composition content type that defines the specified property group. - private static IContentTypeComposition? GetContentTypeForPropertyType(IContentTypeComposition contentType, - int propertyTypeId) - { - // test local property types - if (contentType.PropertyTypes.Any(x => x.Id == propertyTypeId)) - { - return contentType; - } - - // test composition property types - // .ContentTypeComposition is just the local ones, not recursive, - // so we have to recurse here - return contentType.ContentTypeComposition - .Select(x => GetContentTypeForPropertyType(x, propertyTypeId)) - .FirstOrDefault(x => x != null); - } - public IEnumerable> Map(IContentTypeComposition source) { // deal with groups @@ -87,7 +40,7 @@ public IEnumerable> Map(IContentTypeComposit Alias = propertyGroup.Alias, SortOrder = propertyGroup.SortOrder, Properties = MapProperties(propertyGroup.PropertyTypes, source, propertyGroup.Id, false), - ContentTypeId = source.Id + ContentTypeId = source.Id, }; groups.Add(group); @@ -104,7 +57,7 @@ public IEnumerable> Map(IContentTypeComposit } // get the content type that defines this group - IContentTypeComposition definingContentType = GetContentTypeForPropertyGroup(source, propertyGroup.Id); + IContentTypeComposition? definingContentType = GetContentTypeForPropertyGroup(source, propertyGroup.Id); if (definingContentType == null) { throw new Exception("PropertyGroup with id=" + propertyGroup.Id + @@ -123,8 +76,8 @@ public IEnumerable> Map(IContentTypeComposit Properties = MapProperties(propertyGroup.PropertyTypes, definingContentType, propertyGroup.Id, true), ContentTypeId = definingContentType.Id, - ParentTabContentTypes = new[] {definingContentType.Id}, - ParentTabContentTypeNames = new[] {definingContentType.Name} + ParentTabContentTypes = new[] { definingContentType.Id }, + ParentTabContentTypeNames = new[] { definingContentType.Name }, }; groups.Add(group); @@ -135,8 +88,7 @@ public IEnumerable> Map(IContentTypeComposit // add generic properties local to this content type IEnumerable entityGenericProperties = source.PropertyTypes.Where(x => x.PropertyGroupId == null); - genericProperties.AddRange(MapProperties(entityGenericProperties, source, - PropertyGroupBasic.GenericPropertiesGroupId, false)); + genericProperties.AddRange(MapProperties(entityGenericProperties, source, PropertyGroupBasic.GenericPropertiesGroupId, false)); // add generic properties inherited through compositions var localGenericPropertyIds = genericProperties.Select(x => x.Id).ToArray(); @@ -145,7 +97,7 @@ public IEnumerable> Map(IContentTypeComposit && localGenericPropertyIds.Contains(x.Id) == false); // skip those that are local foreach (IPropertyType compositionGenericProperty in compositionGenericProperties) { - IContentTypeComposition definingContentType = + IContentTypeComposition? definingContentType = GetContentTypeForPropertyType(source, compositionGenericProperty.Id); if (definingContentType == null) { @@ -153,8 +105,7 @@ public IEnumerable> Map(IContentTypeComposit " was not found on any of the content type's compositions."); } - genericProperties.AddRange(MapProperties(new[] {compositionGenericProperty}, definingContentType, - PropertyGroupBasic.GenericPropertiesGroupId, true)); + genericProperties.AddRange(MapProperties(new[] { compositionGenericProperty }, definingContentType, PropertyGroupBasic.GenericPropertiesGroupId, true)); } // if there are any generic properties, add the corresponding tab @@ -167,7 +118,7 @@ public IEnumerable> Map(IContentTypeComposit Alias = "genericProperties", SortOrder = 999, Properties = genericProperties, - ContentTypeId = source.Id + ContentTypeId = source.Id, }; groups.Add(genericGroup); @@ -175,6 +126,7 @@ public IEnumerable> Map(IContentTypeComposit // handle locked properties var lockedPropertyAliases = new List(); + // add built-in member property aliases to list of aliases to be locked foreach (var propertyAlias in ConventionsHelper.GetStandardPropertyTypeStubs(_shortStringHelper).Keys) { @@ -229,8 +181,59 @@ public IEnumerable> Map(IContentTypeComposit return groups.OrderBy(x => x.SortOrder); } - private IEnumerable MapProperties(IEnumerable? properties, - IContentTypeBase contentType, int groupId, bool inherited) + /// + /// Gets the content type that defines a property group, within a composition. + /// + /// The composition. + /// The identifier of the property group. + /// The composition content type that defines the specified property group. + private static IContentTypeComposition? GetContentTypeForPropertyGroup( + IContentTypeComposition contentType, + int propertyGroupId) + { + // test local groups + if (contentType.PropertyGroups.Any(x => x.Id == propertyGroupId)) + { + return contentType; + } + + // test composition types groups + // .ContentTypeComposition is just the local ones, not recursive, + // so we have to recurse here + return contentType.ContentTypeComposition + .Select(x => GetContentTypeForPropertyGroup(x, propertyGroupId)) + .FirstOrDefault(x => x != null); + } + + /// + /// Gets the content type that defines a property group, within a composition. + /// + /// The composition. + /// The identifier of the property type. + /// The composition content type that defines the specified property group. + private static IContentTypeComposition? GetContentTypeForPropertyType( + IContentTypeComposition contentType, + int propertyTypeId) + { + // test local property types + if (contentType.PropertyTypes.Any(x => x.Id == propertyTypeId)) + { + return contentType; + } + + // test composition property types + // .ContentTypeComposition is just the local ones, not recursive, + // so we have to recurse here + return contentType.ContentTypeComposition + .Select(x => GetContentTypeForPropertyType(x, propertyTypeId)) + .FirstOrDefault(x => x != null); + } + + private IEnumerable MapProperties( + IEnumerable? properties, + IContentTypeBase contentType, + int groupId, + bool inherited) { var mappedProperties = new List(); @@ -238,10 +241,10 @@ private IEnumerable MapProperties(IEnumerable? pro Enumerable.Empty()) { var propertyEditorAlias = p.PropertyEditorAlias; - IDataEditor propertyEditor = _propertyEditors[propertyEditorAlias]; - IDataType dataType = _dataTypeService.GetDataType(p.DataTypeId); + IDataEditor? propertyEditor = _propertyEditors[propertyEditorAlias]; + IDataType? dataType = _dataTypeService.GetDataType(p.DataTypeId); - //fixme: Don't explode if we can't find this, log an error and change this to a label + // fixme: Don't explode if we can't find this, log an error and change this to a label if (propertyEditor == null) { _logger.LogError( @@ -251,8 +254,7 @@ private IEnumerable MapProperties(IEnumerable? pro propertyEditor = _propertyEditors[propertyEditorAlias]; } - IDictionary config = propertyEditor is null || dataType is null - ? new Dictionary() + IDictionary? config = propertyEditor is null || dataType is null ? new Dictionary() : dataType.Editor?.GetConfigurationEditor().ToConfigurationEditor(dataType.Configuration); mappedProperties.Add(new TPropertyType @@ -267,12 +269,13 @@ private IEnumerable MapProperties(IEnumerable? pro Mandatory = p.Mandatory, MandatoryMessage = p.MandatoryMessage, Pattern = p.ValidationRegExp, - PatternMessage = p.ValidationRegExpMessage + PatternMessage = p.ValidationRegExpMessage, }, Label = p.Name, View = propertyEditor?.GetValueEditor().View, Config = config, - //Value = "", + + // Value = "", GroupId = groupId, Inherited = inherited, DataTypeId = p.DataTypeId, @@ -283,7 +286,7 @@ private IEnumerable MapProperties(IEnumerable? pro ContentTypeId = contentType.Id, ContentTypeName = contentType.Name, AllowCultureVariant = p.VariesByCulture(), - AllowSegmentVariant = p.VariesBySegment() + AllowSegmentVariant = p.VariesBySegment(), }); } diff --git a/src/Umbraco.Core/Models/Mapping/RedirectUrlMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/RedirectUrlMapDefinition.cs index dd463cb8103e..148470c706fa 100644 --- a/src/Umbraco.Core/Models/Mapping/RedirectUrlMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/RedirectUrlMapDefinition.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Routing; diff --git a/src/Umbraco.Core/Models/Mapping/RelationMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/RelationMapDefinition.cs index 12d16e4f4856..d5658368474a 100644 --- a/src/Umbraco.Core/Models/Mapping/RelationMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/RelationMapDefinition.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; @@ -24,6 +24,23 @@ public void DefineMaps(IUmbracoMapper mapper) mapper.Define(Map); } + // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate + private static void Map(RelationTypeSave source, IRelationType target, MapperContext context) + { + target.Alias = source.Alias; + target.ChildObjectType = source.ChildObjectType; + target.Id = source.Id.TryConvertTo().Result; + target.IsBidirectional = source.IsBidirectional; + if (target is IRelationTypeWithIsDependency targetWithIsDependency) + { + targetWithIsDependency.IsDependency = source.IsDependency; + } + + target.Key = source.Key; + target.Name = source.Name; + target.ParentObjectType = source.ParentObjectType; + } + // Umbraco.Code.MapAll -Icon -Trashed -AdditionalData // Umbraco.Code.MapAll -ParentId -Notifications private void Map(IRelationType source, RelationTypeDisplay target, MapperContext context) @@ -68,7 +85,7 @@ private void Map(IRelation source, RelationDisplay target, MapperContext context target.CreateDate = source.CreateDate; target.ParentId = source.ParentId; - Tuple entities = _relationService.GetEntitiesFromRelation(source); + Tuple? entities = _relationService.GetEntitiesFromRelation(source); if (entities is not null) { @@ -76,21 +93,4 @@ private void Map(IRelation source, RelationDisplay target, MapperContext context target.ChildName = entities.Item2.Name; } } - - // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate - private static void Map(RelationTypeSave source, IRelationType target, MapperContext context) - { - target.Alias = source.Alias; - target.ChildObjectType = source.ChildObjectType; - target.Id = source.Id.TryConvertTo().Result; - target.IsBidirectional = source.IsBidirectional; - if (target is IRelationTypeWithIsDependency targetWithIsDependency) - { - targetWithIsDependency.IsDependency = source.IsDependency; - } - - target.Key = source.Key; - target.Name = source.Name; - target.ParentObjectType = source.ParentObjectType; - } } diff --git a/src/Umbraco.Core/Models/Mapping/SectionMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/SectionMapDefinition.cs index e66a08addf39..c64af5ac0a2e 100644 --- a/src/Umbraco.Core/Models/Mapping/SectionMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/SectionMapDefinition.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Manifest; +using Umbraco.Cms.Core.Manifest; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Sections; @@ -29,17 +29,17 @@ public void DefineMaps(IUmbracoMapper mapper) mapper.Define(); } - // Umbraco.Code.MapAll -RoutePath - private void Map(ISection source, Section target, MapperContext context) + // Umbraco.Code.MapAll + private static void Map(Section source, ManifestSection target, MapperContext context) { target.Alias = source.Alias; - target.Name = _textService.Localize("sections", source.Alias); + target.Name = source.Name; } - // Umbraco.Code.MapAll - private static void Map(Section source, ManifestSection target, MapperContext context) + // Umbraco.Code.MapAll -RoutePath + private void Map(ISection source, Section target, MapperContext context) { target.Alias = source.Alias; - target.Name = source.Name; + target.Name = _textService.Localize("sections", source.Alias); } } diff --git a/src/Umbraco.Core/Models/Mapping/TabsAndPropertiesMapper.cs b/src/Umbraco.Core/Models/Mapping/TabsAndPropertiesMapper.cs index 7a68f803ac9b..42ea05e8f9d5 100644 --- a/src/Umbraco.Core/Models/Mapping/TabsAndPropertiesMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/TabsAndPropertiesMapper.cs @@ -13,8 +13,7 @@ protected TabsAndPropertiesMapper(ICultureDictionary cultureDictionary, ILocaliz { } - protected TabsAndPropertiesMapper(ICultureDictionary cultureDictionary, ILocalizedTextService localizedTextService, - IEnumerable ignoreProperties) + protected TabsAndPropertiesMapper(ICultureDictionary cultureDictionary, ILocalizedTextService localizedTextService, IEnumerable ignoreProperties) { CultureDictionary = cultureDictionary ?? throw new ArgumentNullException(nameof(cultureDictionary)); LocalizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); @@ -22,7 +21,9 @@ protected TabsAndPropertiesMapper(ICultureDictionary cultureDictionary, ILocaliz } protected ICultureDictionary CultureDictionary { get; } + protected ILocalizedTextService LocalizedTextService { get; } + protected IEnumerable IgnoreProperties { get; set; } /// @@ -42,8 +43,7 @@ protected virtual IEnumerable GetCustomGenericProperties /// The generic properties tab is responsible for /// setting up the properties such as Created date, updated date, template selected, etc... /// - protected virtual void MapGenericProperties(IContentBase content, List> tabs, - MapperContext context) + protected virtual void MapGenericProperties(IContentBase content, List> tabs, MapperContext context) { // add the generic properties tab, for properties that don't belong to a tab // get the properties, map and translate them, then add the tab @@ -52,7 +52,6 @@ protected virtual void MapGenericProperties(IContentBase content, List genericProperties = MapProperties(content, noGroupProperties, context); - IEnumerable customProperties = GetCustomGenericProperties(content); if (customProperties != null) { @@ -66,7 +65,7 @@ protected virtual void MapGenericProperties(IContentBase content, List /// /// - protected virtual List MapProperties(IContentBase content, List properties, - MapperContext context) => + protected virtual List MapProperties(IContentBase content, List properties, MapperContext context) => context.MapEnumerable(properties.OrderBy(x => x.PropertyType?.SortOrder)) .WhereNotNull().ToList(); } @@ -92,8 +90,7 @@ public class TabsAndPropertiesMapper : TabsAndPropertiesMapper { private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; - public TabsAndPropertiesMapper(ICultureDictionary cultureDictionary, ILocalizedTextService localizedTextService, - IContentTypeBaseServiceProvider contentTypeBaseServiceProvider) + public TabsAndPropertiesMapper(ICultureDictionary cultureDictionary, ILocalizedTextService localizedTextService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider) : base(cultureDictionary, localizedTextService) => _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider ?? throw new ArgumentNullException(nameof(contentTypeBaseServiceProvider)); @@ -103,10 +100,10 @@ public virtual IEnumerable> Map(TSource source, Mapp var tabs = new List>(); // Property groups only exist on the content type (as it's only used for display purposes) - IContentTypeComposition contentType = _contentTypeBaseServiceProvider.GetContentTypeOf(source); + IContentTypeComposition? contentType = _contentTypeBaseServiceProvider.GetContentTypeOf(source); // Merge the groups, as compositions can introduce duplicate aliases - PropertyGroup[] groups = contentType?.CompositionPropertyGroups.OrderBy(x => x.SortOrder).ToArray(); + PropertyGroup[]? groups = contentType?.CompositionPropertyGroups.OrderBy(x => x.SortOrder).ToArray(); var parentAliases = groups?.Select(x => x.GetParentAlias()).Distinct().ToArray(); if (groups is not null) { @@ -141,7 +138,7 @@ public virtual IEnumerable> Map(TSource source, Mapp Type = g.Type.ToString(), Alias = g.Alias, Label = LocalizedTextService.UmbracoDictionaryTranslate(CultureDictionary, g.Name), - Properties = mappedProperties + Properties = mappedProperties, }); } } diff --git a/src/Umbraco.Core/Models/Mapping/TagMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/TagMapDefinition.cs index 85d8e5cad671..f9c1690c6a46 100644 --- a/src/Umbraco.Core/Models/Mapping/TagMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/TagMapDefinition.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Mapping; namespace Umbraco.Cms.Core.Models.Mapping; diff --git a/src/Umbraco.Core/Models/Mapping/TemplateMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/TemplateMapDefinition.cs index 176e07753894..5afc8bc8d777 100644 --- a/src/Umbraco.Core/Models/Mapping/TemplateMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/TemplateMapDefinition.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Strings; diff --git a/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs index a030eb9d6061..ad1152ae16c3 100644 --- a/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Actions; using Umbraco.Cms.Core.Cache; @@ -30,8 +30,7 @@ public class UserMapDefinition : IMapDefinition private readonly ILocalizedTextService _textService; private readonly IUserService _userService; - public UserMapDefinition(ILocalizedTextService textService, IUserService userService, IEntityService entityService, - ISectionService sectionService, + public UserMapDefinition(ILocalizedTextService textService, IUserService userService, IEntityService entityService, ISectionService sectionService, AppCaches appCaches, ActionCollection actions, IOptions globalSettings, MediaFileManager mediaFileManager, IShortStringHelper shortStringHelper, IImageUrlGenerator imageUrlGenerator) @@ -51,14 +50,16 @@ public UserMapDefinition(ILocalizedTextService textService, IUserService userSer public void DefineMaps(IUmbracoMapper mapper) { mapper.Define( - (source, context) => new UserGroup(_shortStringHelper) {CreateDate = DateTime.UtcNow}, Map); + (source, context) => new UserGroup(_shortStringHelper) { CreateDate = DateTime.UtcNow }, Map); mapper.Define(Map); mapper.Define((source, context) => new UserProfile(), Map); mapper.Define((source, context) => new UserGroupBasic(), Map); mapper.Define((source, context) => new UserGroupBasic(), Map); - mapper.Define((source, context) => new AssignedUserGroupPermissions(), + mapper.Define( + (source, context) => new AssignedUserGroupPermissions(), Map); - mapper.Define((source, context) => new AssignedContentPermissions(), + mapper.Define( + (source, context) => new AssignedContentPermissions(), Map); mapper.Define((source, context) => new UserGroupDisplay(), Map); mapper.Define((source, context) => new UserBasic(), Map); @@ -73,7 +74,6 @@ public void DefineMaps(IUmbracoMapper mapper) } // mappers - private static void Map(UserGroupSave source, IUserGroup target, MapperContext context) { if (!(target is UserGroup ttarget)) @@ -111,6 +111,52 @@ private static void Map(UserGroupSave source, UserGroup target) } } + // Umbraco.Code.MapAll + private static void Map(IProfile source, UserProfile target, MapperContext context) + { + target.Name = source.Name; + target.UserId = source.Id; + } + + // Umbraco.Code.MapAll -Trashed -Alias -AssignedPermissions + private static void Map(EntitySlim source, AssignedContentPermissions target, MapperContext context) + { + target.Icon = MapContentTypeIcon(source); + target.Id = source.Id; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = source.ParentId; + target.Path = source.Path; + target.Udi = Udi.Create(ObjectTypes.GetUdiType(source.NodeObjectType), source.Key); + + if (source.NodeObjectType == Constants.ObjectTypes.Member && target.Icon.IsNullOrWhiteSpace()) + { + target.Icon = Constants.Icons.Member; + } + } + + private static string? MapContentTypeIcon(IEntitySlim entity) + => entity is IContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null; + + private static int GetIntId(object? id) + { + if (id is string strId && + int.TryParse(strId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var asInt)) + { + return asInt; + } + + Attempt result = id.TryConvertTo(); + if (result.Success == false) + { + throw new InvalidOperationException( + "Cannot convert the profile to a " + typeof(UserDetail).Name + + " object since the id is not an integer"); + } + + return result.Result; + } + // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate // Umbraco.Code.MapAll -Id -TourData -StartContentIds -StartMediaIds -Language -Username // Umbraco.Code.MapAll -PasswordQuestion -SessionTimeout -EmailConfirmedDate -InvitedDate @@ -158,13 +204,6 @@ private void Map(UserSave source, IUser target, MapperContext context) } } - // Umbraco.Code.MapAll - private static void Map(IProfile source, UserProfile target, MapperContext context) - { - target.Name = source.Name; - target.UserId = source.Id; - } - // Umbraco.Code.MapAll -ContentStartNode -UserCount -MediaStartNode -Key -Sections // Umbraco.Code.MapAll -Notifications -Udi -Trashed -AdditionalData -IsSystemUserGroup private void Map(IReadOnlyUserGroup source, UserGroupBasic target, MapperContext context) @@ -216,23 +255,6 @@ private void Map(IUserGroup source, AssignedUserGroupPermissions target, MapperC } } - // Umbraco.Code.MapAll -Trashed -Alias -AssignedPermissions - private static void Map(EntitySlim source, AssignedContentPermissions target, MapperContext context) - { - target.Icon = MapContentTypeIcon(source); - target.Id = source.Id; - target.Key = source.Key; - target.Name = source.Name; - target.ParentId = source.ParentId; - target.Path = source.Path; - target.Udi = Udi.Create(ObjectTypes.GetUdiType(source.NodeObjectType), source.Key); - - if (source.NodeObjectType == Constants.ObjectTypes.Member && target.Icon.IsNullOrWhiteSpace()) - { - target.Icon = Constants.Icons.Member; - } - } - // Umbraco.Code.MapAll -ContentStartNode -MediaStartNode -Sections -Notifications -Udi // Umbraco.Code.MapAll -Trashed -AdditionalData -Users -AssignedPermissions private void Map(IUserGroup source, UserGroupDisplay target, MapperContext context) @@ -250,13 +272,12 @@ private void Map(IUserGroup source, UserGroupDisplay target, MapperContext conte MapUserGroupBasic(target, source.AllowedSections, source.StartContentId, source.StartMediaId, context); - //Important! Currently we are never mapping to multiple UserGroupDisplay objects but if we start doing that + // Important! Currently we are never mapping to multiple UserGroupDisplay objects but if we start doing that // this will cause an N+1 and we'll need to change how this works. IEnumerable users = _userService.GetAllInGroup(source.Id); target.Users = context.MapEnumerable(users).WhereNotNull(); - //Deal with assigned permissions: - + // Deal with assigned permissions: var allContentPermissions = _userService.GetPermissions(source, true) .ToDictionary(x => x.EntityId, x => x); @@ -270,7 +291,6 @@ private void Map(IUserGroup source, UserGroupDisplay target, MapperContext conte // a group can end up with way more than 2000 assigned permissions, // so we need to break them into groups in order to avoid breaking // the entity service due to too many Sql parameters. - var list = new List(); foreach (IEnumerable idGroup in allContentPermissions.Keys.InGroupsOf(Constants.Sql.MaxParameterCount)) { @@ -285,7 +305,7 @@ private void Map(IUserGroup source, UserGroupDisplay target, MapperContext conte { EntityPermission contentPermissions = allContentPermissions[entity.Id]; - AssignedContentPermissions assignedContentPermissions = context.Map(entity); + AssignedContentPermissions? assignedContentPermissions = context.Map(entity); if (assignedContentPermissions is null) { continue; @@ -294,13 +314,14 @@ private void Map(IUserGroup source, UserGroupDisplay target, MapperContext conte assignedContentPermissions.AssignedPermissions = AssignedUserGroupPermissions.ClonePermissions(target.DefaultPermissions); - //since there is custom permissions assigned to this node for this group, we need to clear all of the default permissions - //and we'll re-check it if it's one of the explicitly assigned ones + // since there is custom permissions assigned to this node for this group, we need to clear all of the default permissions + // and we'll re-check it if it's one of the explicitly assigned ones foreach (Permission permission in assignedContentPermissions.AssignedPermissions.SelectMany(x => x.Value)) { permission.Checked = false; permission.Checked = - contentPermissions.AssignedPermissions.Contains(permission.PermissionCode, + contentPermissions.AssignedPermissions.Contains( + permission.PermissionCode, StringComparer.InvariantCulture); } @@ -317,10 +338,8 @@ private void Map(IUser source, UserDisplay target, MapperContext context) target.AvailableCultures = _textService.GetSupportedCultures().ToDictionary(x => x.Name, x => x.DisplayName); target.Avatars = source.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileManager, _imageUrlGenerator); target.CalculatedStartContentIds = - GetStartNodes(source.CalculateContentStartNodeIds(_entityService, _appCaches), UmbracoObjectTypes.Document, - "content", "contentRoot", context); - target.CalculatedStartMediaIds = GetStartNodes(source.CalculateMediaStartNodeIds(_entityService, _appCaches), - UmbracoObjectTypes.Media, "media", "mediaRoot", context); + GetStartNodes(source.CalculateContentStartNodeIds(_entityService, _appCaches), UmbracoObjectTypes.Document, "content", "contentRoot", context); + target.CalculatedStartMediaIds = GetStartNodes(source.CalculateMediaStartNodeIds(_entityService, _appCaches), UmbracoObjectTypes.Media, "media", "mediaRoot", context); target.CreateDate = source.CreateDate; target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString(); target.Email = source.Email; @@ -335,10 +354,8 @@ private void Map(IUser source, UserDisplay target, MapperContext context) target.Navigation = CreateUserEditorNavigation(); target.ParentId = -1; target.Path = "-1," + source.Id; - target.StartContentIds = GetStartNodes(source.StartContentIds?.ToArray(), UmbracoObjectTypes.Document, - "content", "contentRoot", context); - target.StartMediaIds = GetStartNodes(source.StartMediaIds?.ToArray(), UmbracoObjectTypes.Media, "media", - "mediaRoot", context); + target.StartContentIds = GetStartNodes(source.StartContentIds?.ToArray(), UmbracoObjectTypes.Document, "content", "contentRoot", context); + target.StartMediaIds = GetStartNodes(source.StartMediaIds?.ToArray(), UmbracoObjectTypes.Media, "media", "mediaRoot", context); target.UpdateDate = source.UpdateDate; target.UserGroups = context.MapEnumerable(source.Groups).WhereNotNull(); target.Username = source.Username; @@ -348,9 +365,9 @@ private void Map(IUser source, UserDisplay target, MapperContext context) // Umbraco.Code.MapAll -Notifications -IsCurrentUser -Udi -Icon -Trashed -Alias -AdditionalData private void Map(IUser source, UserBasic target, MapperContext context) { - //Loading in the user avatar's requires an external request if they don't have a local file avatar, this means that initial load of paging may incur a cost - //Alternatively, if this is annoying the back office UI would need to be updated to request the avatars for the list of users separately so it doesn't look - //like the load time is waiting. + // Loading in the user avatar's requires an external request if they don't have a local file avatar, this means that initial load of paging may incur a cost + // Alternatively, if this is annoying the back office UI would need to be updated to request the avatars for the list of users separately so it doesn't look + // like the load time is waiting. target.Avatars = source.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileManager, _imageUrlGenerator); target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString(); target.Email = source.Email; @@ -379,16 +396,14 @@ private void Map(IUser source, UserDetail target, MapperContext context) target.StartMediaIds = source.CalculateMediaStartNodeIds(_entityService, _appCaches); target.UserId = source.Id; - //we need to map the legacy UserType - //the best we can do here is to return the user's first user group as a IUserType object - //but we should attempt to return any group that is the built in ones first + // we need to map the legacy UserType + // the best we can do here is to return the user's first user group as a IUserType object + // but we should attempt to return any group that is the built in ones first target.UserGroups = source.Groups.Select(x => x.Alias).ToArray(); } // helpers - - private void MapUserGroupBasic(UserGroupBasic target, IEnumerable sourceAllowedSections, - int? sourceStartContentId, int? sourceStartMediaId, MapperContext context) + private void MapUserGroupBasic(UserGroupBasic target, IEnumerable sourceAllowedSections, int? sourceStartContentId, int? sourceStartMediaId, MapperContext context) { IEnumerable allSections = _sectionService.GetSections(); target.Sections = context @@ -428,7 +443,8 @@ Permission GetPermission(IAction action) return new() { Category = action.Category.IsNullOrWhiteSpace() - ? _textService.Localize("actionCategories", + ? _textService.Localize( + "actionCategories", Constants.Conventions.PermissionCategories.OtherCategory) : _textService.Localize("actionCategories", action.Category), Name = _textService.Localize("actions", action.Alias), @@ -436,7 +452,7 @@ Permission GetPermission(IAction action) Icon = action.Icon, Checked = source.Permissions != null && source.Permissions.Contains(action.Letter.ToString(CultureInfo.InvariantCulture)), - PermissionCode = action.Letter.ToString(CultureInfo.InvariantCulture) + PermissionCode = action.Letter.ToString(CultureInfo.InvariantCulture), }; } @@ -447,11 +463,7 @@ Permission GetPermission(IAction action) .ToDictionary(x => x.Key, x => (IEnumerable)x.ToArray()); } - private static string? MapContentTypeIcon(IEntitySlim entity) - => entity is IContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null; - - private IEnumerable GetStartNodes(int[]? startNodeIds, UmbracoObjectTypes objectType, - string localizedArea, string localizedAlias, MapperContext context) + private IEnumerable GetStartNodes(int[]? startNodeIds, UmbracoObjectTypes objectType, string localizedArea, string localizedAlias, MapperContext context) { if (startNodeIds is null || startNodeIds.Length <= 0) { @@ -478,29 +490,10 @@ private IEnumerable CreateUserEditorNavigation() => Alias = "details", Icon = "icon-umb-users", Name = _textService.Localize("general", "user"), - View = "views/users/views/user/details.html" - } + View = "views/users/views/user/details.html", + }, }; - private static int GetIntId(object? id) - { - if (id is string strId && - int.TryParse(strId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var asInt)) - { - return asInt; - } - - Attempt result = id.TryConvertTo(); - if (result.Success == false) - { - throw new InvalidOperationException( - "Cannot convert the profile to a " + typeof(UserDetail).Name + - " object since the id is not an integer"); - } - - return result.Result; - } - private EntityBasic CreateRootNode(string name) => new EntityBasic { @@ -509,6 +502,6 @@ private EntityBasic CreateRootNode(string name) => Icon = "icon-folder", Id = -1, Trashed = false, - ParentId = -1 + ParentId = -1, }; } diff --git a/src/Umbraco.Core/Models/MediaType.cs b/src/Umbraco.Core/Models/MediaType.cs index 6e4d7c9eaabc..64683ae462a9 100644 --- a/src/Umbraco.Core/Models/MediaType.cs +++ b/src/Umbraco.Core/Models/MediaType.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Strings; namespace Umbraco.Cms.Core.Models; @@ -16,8 +16,8 @@ public class MediaType : ContentTypeCompositionBase, IMediaType /// Constuctor for creating a MediaType with the parent's id. /// /// Only use this for creating MediaTypes at the root (with ParentId -1). - /// - public MediaType(IShortStringHelper shortStringHelper, int parentId) : base(shortStringHelper, parentId) + public MediaType(IShortStringHelper shortStringHelper, int parentId) + : base(shortStringHelper, parentId) { } @@ -25,9 +25,8 @@ public MediaType(IShortStringHelper shortStringHelper, int parentId) : base(shor /// Constuctor for creating a MediaType with the parent as an inherited type. /// /// Use this to ensure inheritance from parent. - /// - public MediaType(IShortStringHelper shortStringHelper, IMediaType parent) : this(shortStringHelper, parent, - string.Empty) + public MediaType(IShortStringHelper shortStringHelper, IMediaType parent) + : this(shortStringHelper, parent, string.Empty) { } @@ -35,8 +34,6 @@ public MediaType(IShortStringHelper shortStringHelper, IMediaType parent) : this /// Constuctor for creating a MediaType with the parent as an inherited type. /// /// Use this to ensure inheritance from parent. - /// - /// public MediaType(IShortStringHelper shortStringHelper, IMediaType parent, string alias) : base(shortStringHelper, parent, alias) { diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 65553f497862..cddf04b4fe8c 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -31,14 +31,14 @@ public class Member : ContentBase, IMember /// /// ContentType for the current Content object public Member(IMemberType contentType) - : base("", -1, contentType, new PropertyCollection()) + : base(string.Empty, -1, contentType, new PropertyCollection()) { IsApproved = true; // this cannot be null but can be empty - _rawPasswordValue = ""; - _email = ""; - _username = ""; + _rawPasswordValue = string.Empty; + _email = string.Empty; + _username = string.Empty; } /// @@ -57,16 +57,17 @@ public Member(string name, IMemberType contentType) if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(name)); } IsApproved = true; // this cannot be null but can be empty - _rawPasswordValue = ""; - _email = ""; - _username = ""; + _rawPasswordValue = string.Empty; + _email = string.Empty; + _username = string.Empty; } /// @@ -77,6 +78,7 @@ public Member(string name, IMemberType contentType) /// /// /// + /// public Member(string name, string email, string username, IMemberType contentType, bool isApproved = true) : base(name, -1, contentType, new PropertyCollection()) { @@ -87,7 +89,8 @@ public Member(string name, string email, string username, IMemberType contentTyp if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(name)); } @@ -98,7 +101,8 @@ public Member(string name, string email, string username, IMemberType contentTyp if (string.IsNullOrWhiteSpace(email)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(email)); } @@ -109,7 +113,8 @@ public Member(string name, string email, string username, IMemberType contentTyp if (string.IsNullOrWhiteSpace(username)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(username)); } @@ -118,7 +123,7 @@ public Member(string name, string email, string username, IMemberType contentTyp IsApproved = isApproved; // this cannot be null but can be empty - _rawPasswordValue = ""; + _rawPasswordValue = string.Empty; } /// @@ -131,8 +136,7 @@ public Member(string name, string email, string username, IMemberType contentTyp /// /// /// - public Member(string name, string email, string username, IMemberType contentType, int userId, - bool isApproved = true) + public Member(string name, string email, string username, IMemberType contentType, int userId, bool isApproved = true) : base(name, -1, contentType, new PropertyCollection()) { if (name == null) @@ -142,7 +146,8 @@ public Member(string name, string email, string username, IMemberType contentTyp if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(name)); } @@ -153,7 +158,8 @@ public Member(string name, string email, string username, IMemberType contentTyp if (string.IsNullOrWhiteSpace(email)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(email)); } @@ -164,7 +170,8 @@ public Member(string name, string email, string username, IMemberType contentTyp if (string.IsNullOrWhiteSpace(username)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(username)); } @@ -173,8 +180,8 @@ public Member(string name, string email, string username, IMemberType contentTyp CreatorId = userId; IsApproved = isApproved; - //this cannot be null but can be empty - _rawPasswordValue = ""; + // this cannot be null but can be empty + _rawPasswordValue = string.Empty; } /// @@ -210,8 +217,7 @@ public Member(string? name, string email, string username, string? rawPasswordVa /// /// /// - public Member(string name, string email, string username, string rawPasswordValue, IMemberType contentType, - bool isApproved) + public Member(string name, string email, string username, string rawPasswordValue, IMemberType contentType, bool isApproved) : base(name, -1, contentType, new PropertyCollection()) { _email = email; @@ -233,8 +239,7 @@ public Member(string name, string email, string username, string rawPasswordValu /// /// /// - public Member(string name, string email, string username, string rawPasswordValue, IMemberType contentType, - bool isApproved, int userId) + public Member(string name, string email, string username, string rawPasswordValue, IMemberType contentType, bool isApproved, int userId) : base(name, -1, contentType, new PropertyCollection()) { _email = email; @@ -288,8 +293,8 @@ public string? RawPasswordValue { if (value == null) { - //special case, this is used to ensure that the password is not updated when persisting, in this case - //we don't want to track changes either + // special case, this is used to ensure that the password is not updated when persisting, in this case + // we don't want to track changes either _rawPasswordValue = null; } else @@ -322,8 +327,7 @@ public string? Comments { get { - Attempt a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.Comments, nameof(Comments), - default(string)); + Attempt a = WarnIfPropertyTypeNotFoundOnGet(Constants.Conventions.Member.Comments, nameof(Comments), default(string)); if (a.Success == false) { return a.Result; @@ -333,6 +337,7 @@ public string? Comments ? string.Empty : Properties[Constants.Conventions.Member.Comments]?.GetValue()?.ToString(); } + set { if (WarnIfPropertyTypeNotFoundOnSet( @@ -443,7 +448,6 @@ public string? SecurityStamp set => SetPropertyValueAndDetectChanges(value, ref _securityStamp, nameof(SecurityStamp)); } - /// /// Internal/Experimental - only used for mapping queries. /// @@ -507,8 +511,7 @@ public string? SecurityStamp /// [DataMember] [DoNotClone] - public IDictionary? AdditionalData => - _additionalData ?? (_additionalData = new Dictionary()); + public IDictionary AdditionalData => _additionalData ??= new Dictionary(); /// [IgnoreDataMember] @@ -516,7 +519,7 @@ public string? SecurityStamp private Attempt WarnIfPropertyTypeNotFoundOnGet(string propertyAlias, string propertyName, T defaultVal) { - void DoLog(string logPropertyAlias, string logPropertyName) + static void DoLog(string logPropertyAlias, string logPropertyName) { StaticApplicationLogging.Logger.LogWarning( "Trying to access the '{PropertyName}' property on '{MemberType}' " + @@ -545,7 +548,7 @@ void DoLog(string logPropertyAlias, string logPropertyName) private bool WarnIfPropertyTypeNotFoundOnSet(string propertyAlias, string propertyName) { - void DoLog(string logPropertyAlias, string logPropertyName) + static void DoLog(string logPropertyAlias, string logPropertyName) { StaticApplicationLogging.Logger.LogWarning( "An attempt was made to set a value on the property '{PropertyName}' on type '{MemberType}' but the " + diff --git a/src/Umbraco.Core/Models/MemberGroup.cs b/src/Umbraco.Core/Models/MemberGroup.cs index b544dfe7c999..5ae7a7edd224 100644 --- a/src/Umbraco.Core/Models/MemberGroup.cs +++ b/src/Umbraco.Core/Models/MemberGroup.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; @@ -18,7 +18,7 @@ public class MemberGroup : EntityBase, IMemberGroup [DataMember] [DoNotClone] public IDictionary AdditionalData => - _additionalData ?? (_additionalData = new Dictionary()); +_additionalData ??= new Dictionary(); /// [IgnoreDataMember] @@ -32,9 +32,9 @@ public string? Name { if (_name != value) { - //if the name has changed, add the value to the additional data, - //this is required purely for event handlers to know the previous name of the group - //so we can keep the public access up to date. + // if the name has changed, add the value to the additional data, + // this is required purely for event handlers to know the previous name of the group + // so we can keep the public access up to date. AdditionalData["previousName"] = _name; } diff --git a/src/Umbraco.Core/Models/MemberPropertyModel.cs b/src/Umbraco.Core/Models/MemberPropertyModel.cs index 80807a353073..96466af39747 100644 --- a/src/Umbraco.Core/Models/MemberPropertyModel.cs +++ b/src/Umbraco.Core/Models/MemberPropertyModel.cs @@ -8,19 +8,20 @@ namespace Umbraco.Cms.Core.Models; /// public class MemberPropertyModel { - [Required] public string Alias { get; set; } = null!; + [Required] + public string Alias { get; set; } = null!; - //NOTE: This has to be a string currently, if it is an object it will bind as an array which we don't want. + // NOTE: This has to be a string currently, if it is an object it will bind as an array which we don't want. // If we want to have this as an 'object' with a true type on it, we have to create a custom model binder // for an UmbracoProperty and then bind with the correct type based on the property type for this alias. This // would be a bit long winded and perhaps unnecessary. The reason is because it is always posted as a string anyways // and when we set this value on the property object that gets sent to the database we do a TryConvertTo to the // real type anyways. - [DataType(System.ComponentModel.DataAnnotations.DataType.Text)] public string? Value { get; set; } - [ReadOnly(true)] public string? Name { get; set; } + [ReadOnly(true)] + public string? Name { get; set; } // TODO: Perhaps one day we'll ship with our own EditorTempates but for now developers can just render their own inside the view @@ -28,6 +29,6 @@ public class MemberPropertyModel ///// This can dynamically be set to a custom template name to change ///// the editor type for this property ///// - //[ReadOnly(true)] - //public string EditorTemplate { get; set; } + // [ReadOnly(true)] + // public string EditorTemplate { get; set; } } diff --git a/src/Umbraco.Core/Models/MemberType.cs b/src/Umbraco.Core/Models/MemberType.cs index eacd2967230e..502a61df9f4a 100644 --- a/src/Umbraco.Core/Models/MemberType.cs +++ b/src/Umbraco.Core/Models/MemberType.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; @@ -14,22 +14,26 @@ public class MemberType : ContentTypeCompositionBase, IMemberType public const bool SupportsPublishingConst = false; private readonly IShortStringHelper _shortStringHelper; - //Dictionary is divided into string: PropertyTypeAlias, Tuple: MemberCanEdit, VisibleOnProfile, PropertyTypeId - private string _alias = string.Empty; - /// /// Gets or Sets a Dictionary of Tuples (MemberCanEdit, VisibleOnProfile, IsSensitive) by the PropertyTypes' alias. /// - private IDictionary _memberTypePropertyTypes; + private readonly IDictionary _memberTypePropertyTypes; + + // Dictionary is divided into string: PropertyTypeAlias, Tuple: MemberCanEdit, VisibleOnProfile, PropertyTypeId + private string _alias = string.Empty; - public MemberType(IShortStringHelper shortStringHelper, int parentId) : base(shortStringHelper, parentId) + public MemberType(IShortStringHelper shortStringHelper, int parentId) + : base(shortStringHelper, parentId) { _shortStringHelper = shortStringHelper; _memberTypePropertyTypes = new Dictionary(); } - public MemberType(IShortStringHelper shortStringHelper, IContentTypeComposition parent) : this(shortStringHelper, - parent, string.Empty) + public MemberType(IShortStringHelper shortStringHelper, IContentTypeComposition parent) + : this( + shortStringHelper, + parent, + string.Empty) { } @@ -43,19 +47,18 @@ public MemberType(IShortStringHelper shortStringHelper, IContentTypeComposition /// public override bool SupportsPublishing => SupportsPublishingConst; - /// - public override ISimpleContentType ToSimple() => new SimpleContentType(this); - public override ContentVariation Variations { // note: although technically possible, variations on members don't make much sense // and therefore are disabled - they are fully supported at service level, though, // but not at published snapshot level. - get => base.Variations; set => throw new NotSupportedException("Variations are not supported on members."); } + /// + public override ISimpleContentType ToSimple() => new SimpleContentType(this); + /// /// The Alias of the ContentType /// @@ -65,7 +68,7 @@ public override string Alias get => _alias; set { - //NOTE: WE are overriding this because we don't want to do a ToSafeAlias when the alias is the special case of + // NOTE: WE are overriding this because we don't want to do a ToSafeAlias when the alias is the special case of // "_umbracoSystemDefaultProtectType" which is used internally, currently there is an issue with the safe alias as it strips // leading underscores which we don't want in this case. // see : http://issues.umbraco.org/issue/U4-3968 @@ -73,7 +76,6 @@ public override string Alias // TODO: BUT, I'm pretty sure we could do this with regards to underscores now: // .ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase) // Need to ask Stephen - var newVal = value == "_umbracoSystemDefaultProtectType" ? value : value == null @@ -92,8 +94,7 @@ public override string Alias public bool MemberCanEditProperty(string? propertyTypeAlias) => propertyTypeAlias is not null && _memberTypePropertyTypes.TryGetValue( propertyTypeAlias, - out MemberTypePropertyProfileAccess - propertyProfile) && + out MemberTypePropertyProfileAccess? propertyProfile) && propertyProfile.IsEditable; /// @@ -102,7 +103,7 @@ out MemberTypePropertyProfileAccess /// PropertyType Alias of the Property to check /// public bool MemberCanViewProperty(string propertyTypeAlias) => - _memberTypePropertyTypes.TryGetValue(propertyTypeAlias, out MemberTypePropertyProfileAccess propertyProfile) && + _memberTypePropertyTypes.TryGetValue(propertyTypeAlias, out MemberTypePropertyProfileAccess? propertyProfile) && propertyProfile.IsVisible; /// @@ -111,7 +112,7 @@ public bool MemberCanViewProperty(string propertyTypeAlias) => /// PropertyType Alias of the Property to check /// public bool IsSensitiveProperty(string propertyTypeAlias) => - _memberTypePropertyTypes.TryGetValue(propertyTypeAlias, out MemberTypePropertyProfileAccess propertyProfile) && + _memberTypePropertyTypes.TryGetValue(propertyTypeAlias, out MemberTypePropertyProfileAccess? propertyProfile) && propertyProfile.IsSensitive; /// @@ -121,8 +122,7 @@ public bool IsSensitiveProperty(string propertyTypeAlias) => /// Boolean value, true or false public void SetMemberCanEditProperty(string propertyTypeAlias, bool value) { - if (_memberTypePropertyTypes.TryGetValue(propertyTypeAlias, - out MemberTypePropertyProfileAccess propertyProfile)) + if (_memberTypePropertyTypes.TryGetValue(propertyTypeAlias, out MemberTypePropertyProfileAccess? propertyProfile)) { propertyProfile.IsEditable = value; } @@ -140,8 +140,7 @@ public void SetMemberCanEditProperty(string propertyTypeAlias, bool value) /// Boolean value, true or false public void SetMemberCanViewProperty(string propertyTypeAlias, bool value) { - if (_memberTypePropertyTypes.TryGetValue(propertyTypeAlias, - out MemberTypePropertyProfileAccess propertyProfile)) + if (_memberTypePropertyTypes.TryGetValue(propertyTypeAlias, out MemberTypePropertyProfileAccess? propertyProfile)) { propertyProfile.IsVisible = value; } @@ -159,8 +158,8 @@ public void SetMemberCanViewProperty(string propertyTypeAlias, bool value) /// Boolean value, true or false public void SetIsSensitiveProperty(string propertyTypeAlias, bool value) { - if (_memberTypePropertyTypes.TryGetValue(propertyTypeAlias, - out MemberTypePropertyProfileAccess propertyProfile)) + if (_memberTypePropertyTypes.TryGetValue( + propertyTypeAlias, out MemberTypePropertyProfileAccess? propertyProfile)) { propertyProfile.IsSensitive = value; } diff --git a/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs b/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs index 35962402d268..e6e619354b97 100644 --- a/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs +++ b/src/Umbraco.Core/Models/MemberTypePropertyProfileAccess.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; /// /// Used to track the property types that are visible/editable on member profiles @@ -13,6 +13,8 @@ public MemberTypePropertyProfileAccess(bool isVisible, bool isEditable, bool isS } public bool IsVisible { get; set; } + public bool IsEditable { get; set; } + public bool IsSensitive { get; set; } } diff --git a/src/Umbraco.Core/Models/Membership/MemberCountType.cs b/src/Umbraco.Core/Models/Membership/MemberCountType.cs index 504212fa5fdc..6ff29bdee2ee 100644 --- a/src/Umbraco.Core/Models/Membership/MemberCountType.cs +++ b/src/Umbraco.Core/Models/Membership/MemberCountType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Membership; +namespace Umbraco.Cms.Core.Models.Membership; /// /// The types of members to count @@ -7,5 +7,5 @@ public enum MemberCountType { All, LockedOut, - Approved + Approved, } diff --git a/src/Umbraco.Core/Models/Membership/MemberExportModel.cs b/src/Umbraco.Core/Models/Membership/MemberExportModel.cs index 9ec3b1c0eeb2..17e4182b8706 100644 --- a/src/Umbraco.Core/Models/Membership/MemberExportModel.cs +++ b/src/Umbraco.Core/Models/Membership/MemberExportModel.cs @@ -1,15 +1,24 @@ -namespace Umbraco.Cms.Core.Models.Membership; +namespace Umbraco.Cms.Core.Models.Membership; public class MemberExportModel { public int Id { get; set; } + public Guid Key { get; set; } + public string? Name { get; set; } + public string? Username { get; set; } + public string? Email { get; set; } + public List? Groups { get; set; } + public string? ContentTypeAlias { get; set; } + public DateTime CreateDate { get; set; } + public DateTime UpdateDate { get; set; } + public List? Properties { get; set; } } diff --git a/src/Umbraco.Core/Models/Membership/MemberExportProperty.cs b/src/Umbraco.Core/Models/Membership/MemberExportProperty.cs index 4b82e4689c65..a34f1a8d1dc6 100644 --- a/src/Umbraco.Core/Models/Membership/MemberExportProperty.cs +++ b/src/Umbraco.Core/Models/Membership/MemberExportProperty.cs @@ -1,11 +1,16 @@ -namespace Umbraco.Cms.Core.Models.Membership; +namespace Umbraco.Cms.Core.Models.Membership; public class MemberExportProperty { public int Id { get; set; } + public string? Alias { get; set; } + public string? Name { get; set; } + public object? Value { get; set; } + public DateTime? CreateDate { get; set; } + public DateTime? UpdateDate { get; set; } } diff --git a/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs b/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs index 66b1306e82d2..2e32f4172bcc 100644 --- a/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/ReadOnlyUserGroup.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.Models.Membership; +namespace Umbraco.Cms.Core.Models.Membership; public class ReadOnlyUserGroup : IReadOnlyUserGroup, IEquatable { - public ReadOnlyUserGroup(int id, string? name, string? icon, int? startContentId, int? startMediaId, string? alias, - IEnumerable allowedSections, IEnumerable? permissions) + public ReadOnlyUserGroup(int id, string? name, string? icon, int? startContentId, int? startMediaId, string? alias, IEnumerable allowedSections, IEnumerable? permissions) { Name = name ?? string.Empty; Icon = icon; @@ -12,11 +11,13 @@ public ReadOnlyUserGroup(int id, string? name, string? icon, int? startContentId AllowedSections = allowedSections.ToArray(); Permissions = permissions?.ToArray(); - //Zero is invalid and will be treated as Null + // Zero is invalid and will be treated as Null StartContentId = startContentId == 0 ? null : startContentId; StartMediaId = startMediaId == 0 ? null : startMediaId; } + public int Id { get; } + public bool Equals(ReadOnlyUserGroup? other) { if (ReferenceEquals(null, other)) @@ -32,11 +33,14 @@ public bool Equals(ReadOnlyUserGroup? other) return string.Equals(Alias, other.Alias); } - public int Id { get; } public string Name { get; } + public string? Icon { get; } + public int? StartContentId { get; } + public int? StartMediaId { get; } + public string Alias { get; } /// @@ -50,6 +54,8 @@ public bool Equals(ReadOnlyUserGroup? other) public IEnumerable AllowedSections { get; } + public static bool operator ==(ReadOnlyUserGroup left, ReadOnlyUserGroup right) => Equals(left, right); + public override bool Equals(object? obj) { if (ReferenceEquals(null, obj)) @@ -72,7 +78,5 @@ public override bool Equals(object? obj) public override int GetHashCode() => Alias?.GetHashCode() ?? base.GetHashCode(); - public static bool operator ==(ReadOnlyUserGroup left, ReadOnlyUserGroup right) => Equals(left, right); - public static bool operator !=(ReadOnlyUserGroup left, ReadOnlyUserGroup right) => !Equals(left, right); } diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 2a53342f3bcf..4607b7c81184 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -12,7 +12,7 @@ namespace Umbraco.Cms.Core.Models.Membership; [DataContract(IsReference = true)] public class User : EntityBase, IUser, IProfile { - //Custom comparer for enumerable + // Custom comparer for enumerable private static readonly DelegateEqualityComparer> IntegerEnumerableComparer = new( (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), @@ -55,8 +55,9 @@ public User(GlobalSettings globalSettings) _isLockedOut = false; _startContentIds = new int[] { }; _startMediaIds = new int[] { }; - //cannot be null - _rawPasswordValue = ""; + + // cannot be null + _rawPasswordValue = string.Empty; _username = string.Empty; _email = string.Empty; _name = string.Empty; @@ -69,6 +70,7 @@ public User(GlobalSettings globalSettings) /// /// /// + /// public User(GlobalSettings globalSettings, string? name, string email, string username, string rawPasswordValue) : this(globalSettings) { @@ -115,12 +117,21 @@ public User(GlobalSettings globalSettings, string? name, string email, string us /// /// /// - public User(GlobalSettings globalSettings, int id, string? name, string email, string? username, - string? rawPasswordValue, string? passwordConfig, - IEnumerable userGroups, int[] startContentIds, int[] startMediaIds) + /// + public User( + GlobalSettings globalSettings, + int id, + string? name, + string email, + string? username, + string? rawPasswordValue, + string? passwordConfig, + IEnumerable userGroups, + int[] startContentIds, + int[] startMediaIds) : this(globalSettings) { - //we allow whitespace for this value so just check null + // we allow whitespace for this value so just check null if (rawPasswordValue == null) { throw new ArgumentNullException(nameof(rawPasswordValue)); @@ -131,16 +142,6 @@ public User(GlobalSettings globalSettings, int id, string? name, string email, s throw new ArgumentNullException(nameof(userGroups)); } - if (startContentIds == null) - { - throw new ArgumentNullException(nameof(startContentIds)); - } - - if (startMediaIds == null) - { - throw new ArgumentNullException(nameof(startMediaIds)); - } - if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); @@ -160,11 +161,10 @@ public User(GlobalSettings globalSettings, int id, string? name, string email, s _userGroups = new HashSet(userGroups); _isApproved = true; _isLockedOut = false; - _startContentIds = startContentIds; - _startMediaIds = startMediaIds; + _startContentIds = startContentIds ?? throw new ArgumentNullException(nameof(startContentIds)); + _startMediaIds = startMediaIds ?? throw new ArgumentNullException(nameof(startMediaIds)); } - [DataMember] public DateTime? EmailConfirmedDate { @@ -249,7 +249,8 @@ public int FailedPasswordAttempts set => SetPropertyValueAndDetectChanges(value, ref _failedLoginAttempts, nameof(FailedPasswordAttempts)); } - [IgnoreDataMember] public string? Comments { get; set; } + [IgnoreDataMember] + public string? Comments { get; set; } public UserState UserState { @@ -287,9 +288,8 @@ public string? Name set => SetPropertyValueAndDetectChanges(value, ref _name!, nameof(Name)); } - public IEnumerable AllowedSections => _allowedSections ?? - (_allowedSections = new List(_userGroups - .SelectMany(x => x.AllowedSections).Distinct())); + public IEnumerable AllowedSections => _allowedSections ??= new List(_userGroups + .SelectMany(x => x.AllowedSections).Distinct()); public IProfile ProfileData => new WrappedUserProfile(this); @@ -344,8 +344,7 @@ public int SessionTimeout public int[]? StartContentIds { get => _startContentIds; - set => SetPropertyValueAndDetectChanges(value, ref _startContentIds, nameof(StartContentIds), - IntegerEnumerableComparer); + set => SetPropertyValueAndDetectChanges(value, ref _startContentIds, nameof(StartContentIds), IntegerEnumerableComparer); } /// @@ -359,8 +358,7 @@ public int[]? StartContentIds public int[]? StartMediaIds { get => _startMediaIds; - set => SetPropertyValueAndDetectChanges(value, ref _startMediaIds, nameof(StartMediaIds), - IntegerEnumerableComparer); + set => SetPropertyValueAndDetectChanges(value, ref _startMediaIds, nameof(StartMediaIds), IntegerEnumerableComparer); } [DataMember] @@ -383,7 +381,8 @@ public void RemoveGroup(string group) if (userGroup.Alias == group) { _userGroups.Remove(userGroup); - //reset this flag so it's rebuilt with the assigned groups + + // reset this flag so it's rebuilt with the assigned groups _allowedSections = null; OnPropertyChanged(nameof(Groups)); } @@ -395,7 +394,8 @@ public void ClearGroups() if (_userGroups.Count > 0) { _userGroups.Clear(); - //reset this flag so it's rebuilt with the assigned groups + + // reset this flag so it's rebuilt with the assigned groups _allowedSections = null; OnPropertyChanged(nameof(Groups)); } @@ -405,7 +405,7 @@ public void AddGroup(IReadOnlyUserGroup group) { if (_userGroups.Add(group)) { - //reset this flag so it's rebuilt with the assigned groups + // reset this flag so it's rebuilt with the assigned groups _allowedSections = null; OnPropertyChanged(nameof(Groups)); } @@ -417,10 +417,11 @@ protected override void PerformDeepClone(object clone) var clonedEntity = (User)clone; - //manually clone the start node props + // manually clone the start node props clonedEntity._startContentIds = _startContentIds?.ToArray(); clonedEntity._startMediaIds = _startMediaIds?.ToArray(); - //need to create new collections otherwise they'll get copied by ref + + // need to create new collections otherwise they'll get copied by ref clonedEntity._userGroups = new HashSet(_userGroups); clonedEntity._allowedSections = _allowedSections != null ? new List(_allowedSections) : null; } @@ -438,8 +439,6 @@ private class WrappedUserProfile : IProfile public string? Name => _user.Name; - private bool Equals(WrappedUserProfile other) => _user.Equals(other._user); - public override bool Equals(object? obj) { if (ReferenceEquals(null, obj)) @@ -460,6 +459,8 @@ public override bool Equals(object? obj) return Equals((WrappedUserProfile)obj); } + private bool Equals(WrappedUserProfile other) => _user.Equals(other._user); + public override int GetHashCode() => _user.GetHashCode(); } } diff --git a/src/Umbraco.Core/Models/Membership/UserGroup.cs b/src/Umbraco.Core/Models/Membership/UserGroup.cs index 6ac1156e41b8..fcc12912cc0b 100644 --- a/src/Umbraco.Core/Models/Membership/UserGroup.cs +++ b/src/Umbraco.Core/Models/Membership/UserGroup.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; @@ -12,7 +12,7 @@ namespace Umbraco.Cms.Core.Models.Membership; [DataContract(IsReference = true)] public class UserGroup : EntityBase, IUserGroup, IReadOnlyUserGroup { - //Custom comparer for enumerable + // Custom comparer for enumerable private static readonly DelegateEqualityComparer> StringEnumerableComparer = new( (enum1, enum2) => enum1.UnsortedSequenceEqual(enum2), @@ -46,8 +46,8 @@ public UserGroup(IShortStringHelper shortStringHelper) /// /// /// - public UserGroup(IShortStringHelper shortStringHelper, int userCount, string? alias, string? name, - IEnumerable permissions, string? icon) + /// + public UserGroup(IShortStringHelper shortStringHelper, int userCount, string? alias, string? name, IEnumerable permissions, string? icon) : this(shortStringHelper) { UserCount = userCount; @@ -83,8 +83,7 @@ public string Alias { get => _alias; set => SetPropertyValueAndDetectChanges( - value.ToCleanString(_shortStringHelper, CleanStringType.Alias | CleanStringType.UmbracoCase), ref _alias!, - nameof(Alias)); + value.ToCleanString(_shortStringHelper, CleanStringType.Alias | CleanStringType.UmbracoCase), ref _alias!, nameof(Alias)); } [DataMember] @@ -110,6 +109,8 @@ public IEnumerable? Permissions public IEnumerable AllowedSections => _sectionCollection; + public int UserCount { get; } + public void RemoveAllowedSection(string sectionAlias) { if (_sectionCollection.Contains(sectionAlias)) @@ -128,15 +129,13 @@ public void AddAllowedSection(string sectionAlias) public void ClearAllowedSections() => _sectionCollection.Clear(); - public int UserCount { get; } - protected override void PerformDeepClone(object clone) { base.PerformDeepClone(clone); var clonedEntity = (UserGroup)clone; - //manually clone the start node props + // manually clone the start node props clonedEntity._sectionCollection = new List(_sectionCollection); } } diff --git a/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs b/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs index 62d2886fd102..d71c7aa4ce5c 100644 --- a/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs +++ b/src/Umbraco.Core/Models/Membership/UserGroupExtensions.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models.Membership; namespace Umbraco.Extensions; @@ -7,16 +7,14 @@ public static class UserGroupExtensions { public static IReadOnlyUserGroup ToReadOnlyGroup(this IUserGroup group) { - //this will generally always be the case - var readonlyGroup = group as IReadOnlyUserGroup; - if (readonlyGroup != null) + // this will generally always be the case + if (group is IReadOnlyUserGroup readonlyGroup) { return readonlyGroup; } - //otherwise create one - return new ReadOnlyUserGroup(group.Id, group.Name, group.Icon, group.StartContentId, group.StartMediaId, - group.Alias, group.AllowedSections, group.Permissions); + // otherwise create one + return new ReadOnlyUserGroup(group.Id, group.Name, group.Icon, group.StartContentId, group.StartMediaId, group.Alias, group.AllowedSections, group.Permissions); } public static bool IsSystemUserGroup(this IUserGroup group) => diff --git a/src/Umbraco.Core/Models/Membership/UserProfile.cs b/src/Umbraco.Core/Models/Membership/UserProfile.cs index 58eb68ba4244..51eb882a6b16 100644 --- a/src/Umbraco.Core/Models/Membership/UserProfile.cs +++ b/src/Umbraco.Core/Models/Membership/UserProfile.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Membership; +namespace Umbraco.Cms.Core.Models.Membership; public class UserProfile : IProfile, IEquatable { @@ -8,6 +8,14 @@ public UserProfile(int id, string? name) Name = name; } + public int Id { get; } + + public string? Name { get; } + + public static bool operator ==(UserProfile left, UserProfile right) => Equals(left, right); + + public static bool operator !=(UserProfile left, UserProfile right) => Equals(left, right) == false; + public bool Equals(UserProfile? other) { if (ReferenceEquals(null, other)) @@ -23,9 +31,6 @@ public bool Equals(UserProfile? other) return Id == other.Id; } - public int Id { get; } - public string? Name { get; } - public override bool Equals(object? obj) { if (ReferenceEquals(null, obj)) @@ -47,8 +52,4 @@ public override bool Equals(object? obj) } public override int GetHashCode() => Id; - - public static bool operator ==(UserProfile left, UserProfile right) => Equals(left, right); - - public static bool operator !=(UserProfile left, UserProfile right) => Equals(left, right) == false; } diff --git a/src/Umbraco.Core/Models/Membership/UserState.cs b/src/Umbraco.Core/Models/Membership/UserState.cs index a941c6fd6bf2..e59e4d25c854 100644 --- a/src/Umbraco.Core/Models/Membership/UserState.cs +++ b/src/Umbraco.Core/Models/Membership/UserState.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.Membership; +namespace Umbraco.Cms.Core.Models.Membership; /// /// The state of a user @@ -10,5 +10,5 @@ public enum UserState Disabled = 1, LockedOut = 2, Invited = 3, - Inactive = 4 + Inactive = 4, } diff --git a/src/Umbraco.Core/Models/MigrationEntry.cs b/src/Umbraco.Core/Models/MigrationEntry.cs index aab68d8ed230..ab1294b13efe 100644 --- a/src/Umbraco.Core/Models/MigrationEntry.cs +++ b/src/Umbraco.Core/Models/MigrationEntry.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Semver; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/Notification.cs b/src/Umbraco.Core/Models/Notification.cs index 2cc1ded652f3..31d17513a6c6 100644 --- a/src/Umbraco.Core/Models/Notification.cs +++ b/src/Umbraco.Core/Models/Notification.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; public class Notification { @@ -11,7 +11,10 @@ public Notification(int entityId, int userId, string action, Guid? entityType) } public int EntityId { get; } + public int UserId { get; } + public string Action { get; } + public Guid? EntityType { get; } } diff --git a/src/Umbraco.Core/Models/NotificationEmailBodyParams.cs b/src/Umbraco.Core/Models/NotificationEmailBodyParams.cs index 01cf6c5ebdfa..85e2cfdcd68c 100644 --- a/src/Umbraco.Core/Models/NotificationEmailBodyParams.cs +++ b/src/Umbraco.Core/Models/NotificationEmailBodyParams.cs @@ -1,9 +1,8 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; public class NotificationEmailBodyParams { - public NotificationEmailBodyParams(string? recipientName, string? action, string? itemName, string itemId, - string itemUrl, string? editedUser, string siteUrl, string summary) + public NotificationEmailBodyParams(string? recipientName, string? action, string? itemName, string itemId, string itemUrl, string? editedUser, string siteUrl, string summary) { RecipientName = recipientName ?? throw new ArgumentNullException(nameof(recipientName)); Action = action ?? throw new ArgumentNullException(nameof(action)); @@ -16,9 +15,13 @@ public NotificationEmailBodyParams(string? recipientName, string? action, string } public string RecipientName { get; } + public string Action { get; } + public string ItemName { get; } + public string ItemId { get; } + public string ItemUrl { get; } /// @@ -27,5 +30,6 @@ public NotificationEmailBodyParams(string? recipientName, string? action, string public string Summary { get; } public string EditedUser { get; } + public string SiteUrl { get; } } diff --git a/src/Umbraco.Core/Models/NotificationEmailSubjectParams.cs b/src/Umbraco.Core/Models/NotificationEmailSubjectParams.cs index 91fe2275e329..51b1e4031ee8 100644 --- a/src/Umbraco.Core/Models/NotificationEmailSubjectParams.cs +++ b/src/Umbraco.Core/Models/NotificationEmailSubjectParams.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; public class NotificationEmailSubjectParams { @@ -10,6 +10,8 @@ public NotificationEmailSubjectParams(string siteUrl, string? action, string? it } public string SiteUrl { get; } + public string Action { get; } + public string ItemName { get; } } diff --git a/src/Umbraco.Core/Models/ObjectTypes.cs b/src/Umbraco.Core/Models/ObjectTypes.cs index b2f21c342431..0f44a269cca4 100644 --- a/src/Umbraco.Core/Models/ObjectTypes.cs +++ b/src/Umbraco.Core/Models/ObjectTypes.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Reflection; using Umbraco.Cms.Core.CodeAnnotations; @@ -18,6 +18,12 @@ public static class ObjectTypes private static readonly ConcurrentDictionary GuidObjectTypes = new(); private static readonly ConcurrentDictionary GuidTypes = new(); + /// + /// Gets the Umbraco object type corresponding to a name. + /// + public static UmbracoObjectTypes GetUmbracoObjectType(string name) => + (UmbracoObjectTypes)Enum.Parse(typeof(UmbracoObjectTypes), name, true); + private static FieldInfo? GetEnumField(string name) => typeof(UmbracoObjectTypes).GetField(name, BindingFlags.Public | BindingFlags.Static); @@ -26,7 +32,7 @@ public static class ObjectTypes FieldInfo[] fields = typeof(UmbracoObjectTypes).GetFields(BindingFlags.Public | BindingFlags.Static); foreach (FieldInfo field in fields) { - UmbracoObjectTypeAttribute attribute = field.GetCustomAttribute(false); + UmbracoObjectTypeAttribute? attribute = field.GetCustomAttribute(false); if (attribute != null && attribute.ObjectId == guid) { return field; @@ -36,12 +42,6 @@ public static class ObjectTypes return null; } - /// - /// Gets the Umbraco object type corresponding to a name. - /// - public static UmbracoObjectTypes GetUmbracoObjectType(string name) => - (UmbracoObjectTypes)Enum.Parse(typeof(UmbracoObjectTypes), name, true); - #region Guid object type utilities /// @@ -50,7 +50,7 @@ public static UmbracoObjectTypes GetUmbracoObjectType(string name) => public static UmbracoObjectTypes GetUmbracoObjectType(Guid objectType) => GuidObjectTypes.GetOrAdd(objectType, t => { - FieldInfo field = GetEnumField(objectType); + FieldInfo? field = GetEnumField(objectType); if (field == null) { return UmbracoObjectTypes.Unknown; @@ -65,13 +65,13 @@ public static UmbracoObjectTypes GetUmbracoObjectType(Guid objectType) => public static string GetUdiType(Guid objectType) => GuidUdiTypes.GetOrAdd(objectType, t => { - FieldInfo field = GetEnumField(objectType); + FieldInfo? field = GetEnumField(objectType); if (field == null) { return Constants.UdiEntityType.Unknown; } - UmbracoUdiTypeAttribute attribute = field.GetCustomAttribute(false); + UmbracoUdiTypeAttribute? attribute = field.GetCustomAttribute(false); return attribute?.UdiType ?? Constants.UdiEntityType.Unknown; }); @@ -81,13 +81,13 @@ public static string GetUdiType(Guid objectType) => public static Type? GetClrType(Guid objectType) => GuidTypes.GetOrAdd(objectType, t => { - FieldInfo field = GetEnumField(objectType); + FieldInfo? field = GetEnumField(objectType); if (field == null) { return null; } - UmbracoObjectTypeAttribute attribute = field.GetCustomAttribute(false); + UmbracoObjectTypeAttribute? attribute = field.GetCustomAttribute(false); return attribute?.ModelType; }); @@ -101,8 +101,8 @@ public static string GetUdiType(Guid objectType) => public static Guid GetGuid(this UmbracoObjectTypes objectType) => UmbracoGuids.GetOrAdd(objectType, t => { - FieldInfo field = GetEnumField(t.ToString()); - UmbracoObjectTypeAttribute attribute = field?.GetCustomAttribute(false); + FieldInfo? field = GetEnumField(t.ToString()); + UmbracoObjectTypeAttribute? attribute = field?.GetCustomAttribute(false); return attribute?.ObjectId ?? Guid.Empty; }); @@ -113,8 +113,8 @@ public static Guid GetGuid(this UmbracoObjectTypes objectType) => public static string GetUdiType(this UmbracoObjectTypes objectType) => UmbracoUdiTypes.GetOrAdd(objectType, t => { - FieldInfo field = GetEnumField(t.ToString()); - UmbracoUdiTypeAttribute attribute = field?.GetCustomAttribute(false); + FieldInfo? field = GetEnumField(t.ToString()); + UmbracoUdiTypeAttribute? attribute = field?.GetCustomAttribute(false); return attribute?.UdiType ?? Constants.UdiEntityType.Unknown; }); @@ -131,8 +131,8 @@ public static string GetUdiType(this UmbracoObjectTypes objectType) => public static string GetFriendlyName(this UmbracoObjectTypes objectType) => UmbracoFriendlyNames.GetOrAdd(objectType, t => { - FieldInfo field = GetEnumField(t.ToString()); - FriendlyNameAttribute attribute = field?.GetCustomAttribute(false); + FieldInfo? field = GetEnumField(t.ToString()); + FriendlyNameAttribute? attribute = field?.GetCustomAttribute(false); return attribute?.ToString() ?? string.Empty; }); @@ -143,8 +143,8 @@ public static string GetFriendlyName(this UmbracoObjectTypes objectType) => public static Type? GetClrType(this UmbracoObjectTypes objectType) => UmbracoTypes.GetOrAdd(objectType, t => { - FieldInfo field = GetEnumField(t.ToString()); - UmbracoObjectTypeAttribute attribute = field?.GetCustomAttribute(false); + FieldInfo? field = GetEnumField(t.ToString()); + UmbracoObjectTypeAttribute? attribute = field?.GetCustomAttribute(false); return attribute?.ModelType; }); diff --git a/src/Umbraco.Core/Models/PagedResult.cs b/src/Umbraco.Core/Models/PagedResult.cs index 24133efdf2a2..6dbe6dd703ca 100644 --- a/src/Umbraco.Core/Models/PagedResult.cs +++ b/src/Umbraco.Core/Models/PagedResult.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; @@ -25,13 +25,17 @@ public PagedResult(long totalItems, long pageNumber, long pageSize) } } - [DataMember(Name = "pageNumber")] public long PageNumber { get; private set; } + [DataMember(Name = "pageNumber")] + public long PageNumber { get; private set; } - [DataMember(Name = "pageSize")] public long PageSize { get; private set; } + [DataMember(Name = "pageSize")] + public long PageSize { get; private set; } - [DataMember(Name = "totalPages")] public long TotalPages { get; private set; } + [DataMember(Name = "totalPages")] + public long TotalPages { get; private set; } - [DataMember(Name = "totalItems")] public long TotalItems { get; private set; } + [DataMember(Name = "totalItems")] + public long TotalItems { get; private set; } /// /// Calculates the skip size based on the paged parameters specified diff --git a/src/Umbraco.Core/Models/PagedResultOfT.cs b/src/Umbraco.Core/Models/PagedResultOfT.cs index eb0a28a0925f..c2d11a4f826c 100644 --- a/src/Umbraco.Core/Models/PagedResultOfT.cs +++ b/src/Umbraco.Core/Models/PagedResultOfT.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; @@ -14,5 +14,6 @@ public PagedResult(long totalItems, long pageNumber, long pageSize) { } - [DataMember(Name = "items")] public IEnumerable? Items { get; set; } + [DataMember(Name = "items")] + public IEnumerable? Items { get; set; } } diff --git a/src/Umbraco.Core/Models/PartialView.cs b/src/Umbraco.Core/Models/PartialView.cs index 132404a9ecec..2900674570f6 100644 --- a/src/Umbraco.Core/Models/PartialView.cs +++ b/src/Umbraco.Core/Models/PartialView.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/PartialViewMacroModel.cs b/src/Umbraco.Core/Models/PartialViewMacroModel.cs index 2a8ce781f016..0d999d5dd6f3 100644 --- a/src/Umbraco.Core/Models/PartialViewMacroModel.cs +++ b/src/Umbraco.Core/Models/PartialViewMacroModel.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Cms.Core.Models; @@ -7,7 +7,8 @@ namespace Umbraco.Cms.Core.Models; /// public class PartialViewMacroModel : IContentModel { - public PartialViewMacroModel(IPublishedContent page, + public PartialViewMacroModel( + IPublishedContent page, int macroId, string? macroAlias, string? macroName, @@ -21,8 +22,11 @@ public PartialViewMacroModel(IPublishedContent page, } public string? MacroName { get; } + public string? MacroAlias { get; } + public int MacroId { get; } + public IDictionary MacroParameters { get; } public IPublishedContent Content { get; } diff --git a/src/Umbraco.Core/Models/PartialViewMacroModelExtensions.cs b/src/Umbraco.Core/Models/PartialViewMacroModelExtensions.cs index f36c75723ced..ecbf22323be3 100644 --- a/src/Umbraco.Core/Models/PartialViewMacroModelExtensions.cs +++ b/src/Umbraco.Core/Models/PartialViewMacroModelExtensions.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; namespace Umbraco.Extensions; @@ -15,8 +15,7 @@ public static class PartialViewMacroModelExtensions /// /// /// Parameter value if available, the default value that was passed otherwise. - public static T? GetParameterValue(this PartialViewMacroModel partialViewMacroModel, string parameterAlias, - T defaultValue) + public static T? GetParameterValue(this PartialViewMacroModel partialViewMacroModel, string parameterAlias, T defaultValue) { if (partialViewMacroModel.MacroParameters.ContainsKey(parameterAlias) == false || string.IsNullOrEmpty(partialViewMacroModel.MacroParameters[parameterAlias]?.ToString())) @@ -24,7 +23,7 @@ public static class PartialViewMacroModelExtensions return defaultValue; } - Attempt attempt = partialViewMacroModel.MacroParameters[parameterAlias].TryConvertTo(typeof(T)); + Attempt attempt = partialViewMacroModel.MacroParameters[parameterAlias].TryConvertTo(typeof(T)); return attempt.Success ? (T?)attempt.Result : defaultValue; } diff --git a/src/Umbraco.Core/Models/PartialViewType.cs b/src/Umbraco.Core/Models/PartialViewType.cs index 761a963cbfa3..65499be9a2c4 100644 --- a/src/Umbraco.Core/Models/PartialViewType.cs +++ b/src/Umbraco.Core/Models/PartialViewType.cs @@ -1,8 +1,8 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; public enum PartialViewType : byte { Unknown = 0, // default PartialView = 1, - PartialViewMacro = 2 + PartialViewMacro = 2, } diff --git a/src/Umbraco.Core/Models/PasswordChangedModel.cs b/src/Umbraco.Core/Models/PasswordChangedModel.cs index 5f70171831c5..0cd405e60432 100644 --- a/src/Umbraco.Core/Models/PasswordChangedModel.cs +++ b/src/Umbraco.Core/Models/PasswordChangedModel.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index 469d091fa484..195772be3a6d 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using System.Runtime.Serialization; using Umbraco.Cms.Core.Collections; using Umbraco.Cms.Core.Models.Entities; @@ -42,7 +42,8 @@ public class Property : EntityBase, IProperty } return o.Equals(o1); - }, o => o!.GetHashCode()); + }, + o => o!.GetHashCode()); // _pvalue contains the invariant-neutral property value private IPropertyValue? _pvalue; @@ -114,6 +115,36 @@ public IReadOnlyCollection Values [IgnoreDataMember] public ValueStorageType ValueStorageType => PropertyType.ValueStorageType; + /// + /// Creates a new instance for existing + /// + /// + /// + /// + /// Generally will contain a published and an unpublished property values + /// + /// + public static Property CreateWithValues(int id, IPropertyType propertyType, params InitialPropertyValue[] values) + { + var property = new Property(propertyType); + try + { + property.DisableChangeTracking(); + property.Id = id; + foreach (InitialPropertyValue value in values) + { + property.FactorySetValue(value.Culture, value.Segment, value.Published, value.Value); + } + + property.ResetDirtyProperties(false); + return property; + } + finally + { + property.EnableChangeTracking(); + } + } + /// /// Gets the value. /// @@ -138,7 +169,7 @@ public IReadOnlyCollection Values return null; } - return _vvalues.TryGetValue(new CompositeNStringNStringKey(culture, segment), out IPropertyValue pvalue) + return _vvalues.TryGetValue(new CompositeNStringNStringKey(culture, segment), out IPropertyValue? pvalue) ? GetPropertyValue(pvalue, published) : null; } @@ -224,7 +255,7 @@ public void SetValue(object? value, string? culture = null, string? segment = nu $"Variation \"{culture ?? ""},{segment ?? ""}\" is not supported by the property type."); } - (IPropertyValue pvalue, var change) = GetPValue(culture, segment, true); + (IPropertyValue? pvalue, var change) = GetPValue(culture, segment, true); if (pvalue is not null) { @@ -237,34 +268,17 @@ public void SetValue(object? value, string? culture = null, string? segment = nu } } - /// - /// Creates a new instance for existing - /// - /// - /// - /// - /// Generally will contain a published and an unpublished property values - /// - /// - public static Property CreateWithValues(int id, IPropertyType propertyType, params InitialPropertyValue[] values) + public object? ConvertAssignedValue(object? value) => + TryConvertAssignedValue(value, true, out var converted) ? converted : null; + + protected override void PerformDeepClone(object clone) { - var property = new Property(propertyType); - try - { - property.DisableChangeTracking(); - property.Id = id; - foreach (InitialPropertyValue value in values) - { - property.FactorySetValue(value.Culture, value.Segment, value.Published, value.Value); - } + base.PerformDeepClone(clone); - property.ResetDirtyProperties(false); - return property; - } - finally - { - property.EnableChangeTracking(); - } + var clonedEntity = (Property)clone; + + // need to manually assign since this is a readonly property + clonedEntity.PropertyType = (PropertyType)PropertyType.DeepClone(); } private object? GetPropertyValue(IPropertyValue? pvalue, bool published) @@ -316,7 +330,7 @@ private void UnpublishValue(IPropertyValue? pvalue) // bypasses all changes detection and is the *only* way to set the published value private void FactorySetValue(string? culture, string? segment, bool published, object? value) { - (IPropertyValue pvalue, _) = GetPValue(culture, segment, true); + (IPropertyValue? pvalue, _) = GetPValue(culture, segment, true); if (pvalue is not null) { @@ -369,7 +383,7 @@ private void FactorySetValue(string? culture, string? segment, bool published, o } var k = new CompositeNStringNStringKey(culture, segment); - if (!_vvalues.TryGetValue(k, out IPropertyValue pvalue)) + if (!_vvalues.TryGetValue(k, out IPropertyValue? pvalue)) { if (!create) { @@ -386,9 +400,9 @@ private void FactorySetValue(string? culture, string? segment, bool published, o return (pvalue, change); } - /// - public object? ConvertAssignedValue(object? value) => - TryConvertAssignedValue(value, true, out var converted) ? converted : null; + private static void ThrowTypeException(object? value, Type expected, string alias) => + throw new InvalidOperationException( + $"Cannot assign value \"{value}\" of type \"{value?.GetType()}\" to property \"{alias}\" expecting type \"{expected}\"."); /// /// Tries to convert a value assigned to a property. @@ -408,7 +422,6 @@ private bool TryConvertAssignedValue(object? value, bool throwOnError, out objec // isOfExpectedType is true if value is null - so if false, value is *not* null // "garbage-in", accept what we can & convert // throw only if conversion is not possible - var s = value?.ToString(); converted = null; @@ -436,7 +449,7 @@ private bool TryConvertAssignedValue(object? value, bool throwOnError, out objec if (throwOnError) { - ThrowTypeException(value, typeof(int), Alias ?? string.Empty); + ThrowTypeException(value, typeof(int), Alias); } return false; @@ -458,7 +471,7 @@ private bool TryConvertAssignedValue(object? value, bool throwOnError, out objec if (throwOnError) { - ThrowTypeException(value, typeof(decimal), Alias ?? string.Empty); + ThrowTypeException(value, typeof(decimal), Alias); } return false; @@ -478,7 +491,7 @@ private bool TryConvertAssignedValue(object? value, bool throwOnError, out objec if (throwOnError) { - ThrowTypeException(value, typeof(DateTime), Alias ?? string.Empty); + ThrowTypeException(value, typeof(DateTime), Alias); } return false; @@ -488,10 +501,6 @@ private bool TryConvertAssignedValue(object? value, bool throwOnError, out objec } } - private static void ThrowTypeException(object? value, Type expected, string alias) => - throw new InvalidOperationException( - $"Cannot assign value \"{value}\" of type \"{value?.GetType()}\" to property \"{alias}\" expecting type \"{expected}\"."); - /// /// Determines whether a value is of the expected type for this property type. /// @@ -529,17 +538,6 @@ private bool IsOfExpectedPropertyType(object? value) } } - - protected override void PerformDeepClone(object clone) - { - base.PerformDeepClone(clone); - - var clonedEntity = (Property)clone; - - //need to manually assign since this is a readonly property - clonedEntity.PropertyType = (PropertyType)PropertyType.DeepClone(); - } - /// /// Used for constructing a new instance /// @@ -554,8 +552,11 @@ public InitialPropertyValue(string? culture, string? segment, bool published, ob } public string? Culture { get; } + public string? Segment { get; } + public bool Published { get; } + public object? Value { get; } } @@ -566,19 +567,9 @@ public class PropertyValue : IPropertyValue, IDeepCloneable, IEquatable Clone(); - - public bool Equals(PropertyValue? other) => - other != null && - _culture == other._culture && - _segment == other._segment && - EqualityComparer.Default.Equals(EditedValue, other.EditedValue) && - EqualityComparer.Default.Equals(PublishedValue, other.PublishedValue); - /// /// Gets or sets the culture of the property. /// @@ -592,6 +583,15 @@ public string? Culture set => _culture = value.IsNullOrWhiteSpace() ? null : value!.ToLowerInvariant(); } + public object DeepClone() => Clone(); + + public bool Equals(PropertyValue? other) => + other != null && + _culture == other._culture && + _segment == other._segment && + EqualityComparer.Default.Equals(EditedValue, other.EditedValue) && + EqualityComparer.Default.Equals(PublishedValue, other.PublishedValue); + /// /// Gets or sets the segment of the property. /// @@ -621,7 +621,10 @@ public string? Segment public IPropertyValue Clone() => new PropertyValue { - _culture = _culture, _segment = _segment, PublishedValue = PublishedValue, EditedValue = EditedValue + _culture = _culture, + _segment = _segment, + PublishedValue = PublishedValue, + EditedValue = EditedValue, }; public override bool Equals(object? obj) => Equals(obj as PropertyValue); diff --git a/src/Umbraco.Core/Models/PropertyCollection.cs b/src/Umbraco.Core/Models/PropertyCollection.cs index f1a41311bb3f..ca0febd5bc80 100644 --- a/src/Umbraco.Core/Models/PropertyCollection.cs +++ b/src/Umbraco.Core/Models/PropertyCollection.cs @@ -1,4 +1,4 @@ -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; @@ -30,6 +30,11 @@ public PropertyCollection(IEnumerable properties) : this() => Reset(properties); + /// + /// Occurs when the collection changes. + /// + public event NotifyCollectionChangedEventHandler? CollectionChanged; + /// /// Gets the property with the specified PropertyType. /// @@ -66,25 +71,19 @@ public PropertyCollection(IEnumerable properties) } } - //collection events will be raised in InsertItem with Add + // collection events will be raised in InsertItem with Add base.Add(property); } } - public bool TryGetValue(string propertyTypeAlias, [MaybeNullWhen(false)] out IProperty property) + public new bool TryGetValue(string propertyTypeAlias, [MaybeNullWhen(false)] out IProperty property) { property = this.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias)); return property != null; } - /// - /// Occurs when the collection changes. - /// - public event NotifyCollectionChangedEventHandler? CollectionChanged; - public void ClearCollectionChangedEvents() => CollectionChanged = null; - /// public void EnsurePropertyTypes(IEnumerable propertyTypes) { @@ -99,7 +98,6 @@ public void EnsurePropertyTypes(IEnumerable propertyTypes) } } - /// public void EnsureCleanPropertyTypes(IEnumerable propertyTypes) { @@ -121,7 +119,6 @@ public void EnsureCleanPropertyTypes(IEnumerable propertyTypes) } } - foreach (IPropertyType propertyType in propertyTypesA) { Add(new Property(propertyType)); @@ -142,32 +139,32 @@ public object DeepClone() return clone; } + /// + /// Replaces the property at the specified index with the specified property. + /// + protected override void SetItem(int index, IProperty property) + { + IProperty oldItem = index >= 0 ? this[index] : property; + base.SetItem(index, property); + OnCollectionChanged( + new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, property, oldItem)); + } + /// /// Replaces all properties, whilst maintaining validation delegates. /// private void Reset(IEnumerable properties) { - //collection events will be raised in each of these calls + // collection events will be raised in each of these calls Clear(); - //collection events will be raised in each of these calls + // collection events will be raised in each of these calls foreach (IProperty property in properties) { Add(property); } } - /// - /// Replaces the property at the specified index with the specified property. - /// - protected override void SetItem(int index, IProperty property) - { - IProperty oldItem = index >= 0 ? this[index] : property; - base.SetItem(index, property); - OnCollectionChanged( - new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, property, oldItem)); - } - /// /// Removes the property at the specified index. /// @@ -196,6 +193,8 @@ protected override void ClearItems() OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } + protected override string GetKeyForItem(IProperty item) => item.Alias; + /// /// Gets the index for a specified property alias. /// @@ -212,8 +211,6 @@ private int IndexOfKey(string key) return -1; } - protected override string GetKeyForItem(IProperty item) => item.Alias!; - protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) => CollectionChanged?.Invoke(this, args); } diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs index 68a10fe7177b..034770cdfc8d 100644 --- a/src/Umbraco.Core/Models/PropertyGroup.cs +++ b/src/Umbraco.Core/Models/PropertyGroup.cs @@ -14,6 +14,9 @@ namespace Umbraco.Cms.Core.Models; [DebuggerDisplay("Id: {Id}, Name: {Name}, Alias: {Alias}")] public class PropertyGroup : EntityBase, IEquatable { + [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This field is for internal use only (to allow changing item keys).")] + internal PropertyGroupCollection? Collection; + private string _alias; private string? _name; private PropertyTypeCollection? _propertyTypes; @@ -21,10 +24,6 @@ public class PropertyGroup : EntityBase, IEquatable private PropertyGroupType _type; - [SuppressMessage("Style", "IDE1006:Naming Styles", - Justification = "This field is for internal use only (to allow changing item keys).")] - internal PropertyGroupCollection? Collection; - public PropertyGroup(bool isPublishing) : this(new PropertyTypeCollection(isPublishing)) { @@ -135,9 +134,6 @@ public PropertyTypeCollection? PropertyTypes public bool Equals(PropertyGroup? other) => base.Equals(other) || (other != null && Type == other.Type && Alias == other.Alias); - private void PropertyTypesChanged(object? sender, NotifyCollectionChangedEventArgs e) => - OnPropertyChanged(nameof(PropertyTypes)); - public override int GetHashCode() => (base.GetHashCode(), Type, Alias).GetHashCode(); protected override void PerformDeepClone(object clone) @@ -149,10 +145,13 @@ protected override void PerformDeepClone(object clone) if (clonedEntity._propertyTypes != null) { - clonedEntity._propertyTypes.ClearCollectionChangedEvents(); //clear this event handler if any - clonedEntity._propertyTypes = (PropertyTypeCollection)_propertyTypes!.DeepClone(); //manually deep clone + clonedEntity._propertyTypes.ClearCollectionChangedEvents(); // clear this event handler if any + clonedEntity._propertyTypes = (PropertyTypeCollection)_propertyTypes!.DeepClone(); // manually deep clone clonedEntity._propertyTypes.CollectionChanged += - clonedEntity.PropertyTypesChanged; //re-assign correct event handler + clonedEntity.PropertyTypesChanged; // re-assign correct event handler } } + + private void PropertyTypesChanged(object? sender, NotifyCollectionChangedEventArgs e) => + OnPropertyChanged(nameof(PropertyTypes)); } diff --git a/src/Umbraco.Core/Models/PropertyGroupCollection.cs b/src/Umbraco.Core/Models/PropertyGroupCollection.cs index a018d4963cc5..5e4479ec378a 100644 --- a/src/Umbraco.Core/Models/PropertyGroupCollection.cs +++ b/src/Umbraco.Core/Models/PropertyGroupCollection.cs @@ -10,6 +10,7 @@ namespace Umbraco.Cms.Core.Models; /// [Serializable] [DataContract] + // TODO: Change this to ObservableDictionary so we can reduce the INotifyCollectionChanged implementation details public class PropertyGroupCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable { @@ -26,6 +27,8 @@ public PropertyGroupCollection() /// The groups. public PropertyGroupCollection(IEnumerable groups) => Reset(groups); + public event NotifyCollectionChangedEventHandler? CollectionChanged; + public object DeepClone() { var clone = new PropertyGroupCollection(); @@ -37,7 +40,48 @@ public object DeepClone() return clone; } - public event NotifyCollectionChangedEventHandler? CollectionChanged; + public new void Add(PropertyGroup item) + { + // Ensure alias is set + if (string.IsNullOrEmpty(item.Alias)) + { + throw new InvalidOperationException("Set an alias before adding the property group."); + } + + // Note this is done to ensure existing groups can be renamed + if (item.HasIdentity && item.Id > 0) + { + var index = IndexOfKey(item.Id); + if (index != -1) + { + var keyExists = Contains(item.Alias); + if (keyExists) + { + throw new ArgumentException( + $"Naming conflict: changing the alias of property group '{item.Name}' would result in duplicates."); + } + + // Collection events will be raised in SetItem + SetItem(index, item); + return; + } + } + else + { + var index = IndexOfKey(item.Alias); + if (index != -1) + { + // Collection events will be raised in SetItem + SetItem(index, item); + return; + } + } + + // Collection events will be raised in InsertItem + base.Add(item); + } + + public bool Contains(int id) => IndexOfKey(id) != -1; /// /// Resets the collection to only contain the instances referenced in the @@ -100,62 +144,19 @@ protected override void ClearItems() OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } - public new void Add(PropertyGroup item) - { - // Ensure alias is set - if (string.IsNullOrEmpty(item.Alias)) - { - throw new InvalidOperationException("Set an alias before adding the property group."); - } - - // Note this is done to ensure existing groups can be renamed - if (item.HasIdentity && item.Id > 0) - { - var index = IndexOfKey(item.Id); - if (index != -1) - { - var keyExists = Contains(item.Alias); - if (keyExists) - { - throw new ArgumentException( - $"Naming conflict: changing the alias of property group '{item.Name}' would result in duplicates."); - } - - // Collection events will be raised in SetItem - SetItem(index, item); - return; - } - } - else - { - var index = IndexOfKey(item.Alias); - if (index != -1) - { - // Collection events will be raised in SetItem - SetItem(index, item); - return; - } - } - - // Collection events will be raised in InsertItem - base.Add(item); - } - internal void ChangeKey(PropertyGroup item, string newKey) => ChangeItemKey(item, newKey); - public bool Contains(int id) => IndexOfKey(id) != -1; - public int IndexOfKey(string key) => this.FindIndex(x => x.Alias == key); public int IndexOfKey(int id) => this.FindIndex(x => x.Id == id); - protected override string GetKeyForItem(PropertyGroup item) => item.Alias; - /// /// Clears all event handlers /// public void ClearCollectionChangedEvents() => CollectionChanged = null; + protected override string GetKeyForItem(PropertyGroup item) => item.Alias; + protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) => CollectionChanged?.Invoke(this, args); } diff --git a/src/Umbraco.Core/Models/PropertyGroupExtensions.cs b/src/Umbraco.Core/Models/PropertyGroupExtensions.cs index 4aad70b194d6..289a411f14d8 100644 --- a/src/Umbraco.Core/Models/PropertyGroupExtensions.cs +++ b/src/Umbraco.Core/Models/PropertyGroupExtensions.cs @@ -4,12 +4,21 @@ public static class PropertyGroupExtensions { private const char AliasSeparator = '/'; + /// + /// Gets the local alias. + /// + /// The property group. + /// + /// The local alias. + /// + public static string? GetLocalAlias(this PropertyGroup propertyGroup) => GetLocalAlias(propertyGroup.Alias); + internal static string? GetLocalAlias(string alias) { var lastIndex = alias?.LastIndexOf(AliasSeparator) ?? -1; if (lastIndex != -1) { - return alias?.Substring(lastIndex + 1); + return alias?[(lastIndex + 1)..]; } return alias; @@ -23,18 +32,9 @@ public static class PropertyGroupExtensions return null; } - return alias?.Substring(0, lastIndex); + return alias?[..lastIndex]; } - /// - /// Gets the local alias. - /// - /// The property group. - /// - /// The local alias. - /// - public static string? GetLocalAlias(this PropertyGroup propertyGroup) => GetLocalAlias(propertyGroup.Alias); - /// /// Updates the local alias. /// diff --git a/src/Umbraco.Core/Models/PropertyGroupType.cs b/src/Umbraco.Core/Models/PropertyGroupType.cs index 50b53ac3e1b0..9111bf9bb4c7 100644 --- a/src/Umbraco.Core/Models/PropertyGroupType.cs +++ b/src/Umbraco.Core/Models/PropertyGroupType.cs @@ -13,5 +13,5 @@ public enum PropertyGroupType : short /// /// Display property types in a tab. /// - Tab = 1 + Tab = 1, } diff --git a/src/Umbraco.Core/Models/PropertyTagsExtensions.cs b/src/Umbraco.Core/Models/PropertyTagsExtensions.cs index d85b4e56e217..9ad98d66c0ac 100644 --- a/src/Umbraco.Core/Models/PropertyTagsExtensions.cs +++ b/src/Umbraco.Core/Models/PropertyTagsExtensions.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.PropertyEditors; @@ -14,16 +14,15 @@ public static class PropertyTagsExtensions { // gets the tag configuration for a property // from the datatype configuration, and the editor tag configuration attribute - public static TagConfiguration? GetTagConfiguration(this IProperty property, - PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService) + public static TagConfiguration? GetTagConfiguration(this IProperty property, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService) { if (property == null) { throw new ArgumentNullException(nameof(property)); } - IDataEditor editor = propertyEditors[property.PropertyType?.PropertyEditorAlias]; - TagsPropertyEditorAttribute tagAttribute = editor?.GetTagAttribute(); + IDataEditor? editor = propertyEditors[property.PropertyType?.PropertyEditorAlias]; + TagsPropertyEditorAttribute? tagAttribute = editor?.GetTagAttribute(); if (tagAttribute == null) { return null; @@ -32,7 +31,7 @@ public static class PropertyTagsExtensions var configurationObject = property.PropertyType is null ? null : dataTypeService.GetDataType(property.PropertyType.DataTypeId)?.Configuration; - TagConfiguration configuration = ConfigurationEditor.ConfigurationAs(configurationObject); + TagConfiguration? configuration = ConfigurationEditor.ConfigurationAs(configurationObject); if (configuration?.Delimiter == default && configuration?.Delimiter is not null) { @@ -52,16 +51,14 @@ public static class PropertyTagsExtensions /// A culture, for multi-lingual properties. /// /// - public static void AssignTags(this IProperty property, PropertyEditorCollection propertyEditors, - IDataTypeService dataTypeService, IJsonSerializer serializer, IEnumerable tags, bool merge = false, - string? culture = null) + public static void AssignTags(this IProperty property, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IJsonSerializer serializer, IEnumerable tags, bool merge = false, string? culture = null) { if (property == null) { throw new ArgumentNullException(nameof(property)); } - TagConfiguration configuration = property.GetTagConfiguration(propertyEditors, dataTypeService); + TagConfiguration? configuration = property.GetTagConfiguration(propertyEditors, dataTypeService); if (configuration == null) { throw new NotSupportedException($"Property with alias \"{property.Alias}\" does not support tags."); @@ -70,9 +67,83 @@ public static void AssignTags(this IProperty property, PropertyEditorCollection property.AssignTags(tags, merge, configuration.StorageType, serializer, configuration.Delimiter, culture); } + /// + /// Removes tags. + /// + /// The property. + /// + /// The tags. + /// A culture, for multi-lingual properties. + /// + /// + public static void RemoveTags(this IProperty property, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IJsonSerializer serializer, IEnumerable tags, string? culture = null) + { + if (property == null) + { + throw new ArgumentNullException(nameof(property)); + } + + TagConfiguration? configuration = property.GetTagConfiguration(propertyEditors, dataTypeService); + if (configuration == null) + { + throw new NotSupportedException($"Property with alias \"{property.Alias}\" does not support tags."); + } + + property.RemoveTags(tags, configuration.StorageType, serializer, configuration.Delimiter, culture); + } + + // used by ContentRepositoryBase + public static IEnumerable GetTagsValue(this IProperty property, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IJsonSerializer serializer, string? culture = null) + { + if (property == null) + { + throw new ArgumentNullException(nameof(property)); + } + + TagConfiguration? configuration = property.GetTagConfiguration(propertyEditors, dataTypeService); + if (configuration == null) + { + throw new NotSupportedException($"Property with alias \"{property.Alias}\" does not support tags."); + } + + return property.GetTagsValue(configuration.StorageType, serializer, configuration.Delimiter, culture); + } + + /// + /// Sets tags on a content property, based on the property editor tags configuration. + /// + /// The property. + /// + /// The property value. + /// The datatype configuration. + /// A culture, for multi-lingual properties. + /// + /// The value is either a string (delimited string) or an enumeration of strings (tag list). + /// + /// This is used both by the content repositories to initialize a property with some tag values, and by the + /// content controllers to update a property with values received from the property editor. + /// + /// + public static void SetTagsValue(this IProperty property, IJsonSerializer serializer, object? value, TagConfiguration? tagConfiguration, string? culture) + { + if (property == null) + { + throw new ArgumentNullException(nameof(property)); + } + + if (tagConfiguration == null) + { + throw new ArgumentNullException(nameof(tagConfiguration)); + } + + TagsStorageType storageType = tagConfiguration.StorageType; + var delimiter = tagConfiguration.Delimiter; + + SetTagsValue(property, value, storageType, serializer, delimiter, culture); + } + // assumes that parameters are consistent with the datatype configuration - private static void AssignTags(this IProperty property, IEnumerable tags, bool merge, - TagsStorageType storageType, IJsonSerializer serializer, char delimiter, string? culture) + private static void AssignTags(this IProperty property, IEnumerable tags, bool merge, TagsStorageType storageType, IJsonSerializer serializer, char delimiter, string? culture) { // set the property value var trimmedTags = tags.Select(x => x.Trim()).ToArray(); @@ -101,7 +172,8 @@ private static void AssignTags(this IProperty property, IEnumerable tags switch (storageType) { case TagsStorageType.Csv: - property.SetValue(string.Join(delimiter.ToString(), trimmedTags).NullOrWhiteSpaceAsNull(), + property.SetValue( + string.Join(delimiter.ToString(), trimmedTags).NullOrWhiteSpaceAsNull(), culture); // csv string break; @@ -113,35 +185,8 @@ private static void AssignTags(this IProperty property, IEnumerable tags } } - /// - /// Removes tags. - /// - /// The property. - /// - /// The tags. - /// A culture, for multi-lingual properties. - /// - /// - public static void RemoveTags(this IProperty property, PropertyEditorCollection propertyEditors, - IDataTypeService dataTypeService, IJsonSerializer serializer, IEnumerable tags, string? culture = null) - { - if (property == null) - { - throw new ArgumentNullException(nameof(property)); - } - - TagConfiguration configuration = property.GetTagConfiguration(propertyEditors, dataTypeService); - if (configuration == null) - { - throw new NotSupportedException($"Property with alias \"{property.Alias}\" does not support tags."); - } - - property.RemoveTags(tags, configuration.StorageType, serializer, configuration.Delimiter, culture); - } - // assumes that parameters are consistent with the datatype configuration - private static void RemoveTags(this IProperty property, IEnumerable tags, TagsStorageType storageType, - IJsonSerializer serializer, char delimiter, string? culture) + private static void RemoveTags(this IProperty property, IEnumerable tags, TagsStorageType storageType, IJsonSerializer serializer, char delimiter, string? culture) { // already empty = nothing to do var value = property.GetValue(culture)?.ToString(); @@ -169,26 +214,7 @@ private static void RemoveTags(this IProperty property, IEnumerable tags } } - // used by ContentRepositoryBase - public static IEnumerable GetTagsValue(this IProperty property, PropertyEditorCollection propertyEditors, - IDataTypeService dataTypeService, IJsonSerializer serializer, string? culture = null) - { - if (property == null) - { - throw new ArgumentNullException(nameof(property)); - } - - TagConfiguration configuration = property.GetTagConfiguration(propertyEditors, dataTypeService); - if (configuration == null) - { - throw new NotSupportedException($"Property with alias \"{property.Alias}\" does not support tags."); - } - - return property.GetTagsValue(configuration.StorageType, serializer, configuration.Delimiter, culture); - } - - private static IEnumerable GetTagsValue(this IProperty property, TagsStorageType storageType, - IJsonSerializer serializer, char delimiter, string? culture = null) + private static IEnumerable GetTagsValue(this IProperty property, TagsStorageType storageType, IJsonSerializer serializer, char delimiter, string? culture = null) { if (property == null) { @@ -204,7 +230,7 @@ private static IEnumerable GetTagsValue(this IProperty property, TagsSto switch (storageType) { case TagsStorageType.Csv: - return value.Split(new[] {delimiter}, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()); + return value.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()); case TagsStorageType.Json: try @@ -213,7 +239,7 @@ private static IEnumerable GetTagsValue(this IProperty property, TagsSto } catch (Exception) { - //cannot parse, malformed + // cannot parse, malformed return Enumerable.Empty(); } @@ -222,43 +248,9 @@ private static IEnumerable GetTagsValue(this IProperty property, TagsSto } } - /// - /// Sets tags on a content property, based on the property editor tags configuration. - /// - /// The property. - /// The property value. - /// The datatype configuration. - /// A culture, for multi-lingual properties. - /// - /// The value is either a string (delimited string) or an enumeration of strings (tag list). - /// - /// This is used both by the content repositories to initialize a property with some tag values, and by the - /// content controllers to update a property with values received from the property editor. - /// - /// - public static void SetTagsValue(this IProperty property, IJsonSerializer serializer, object? value, - TagConfiguration? tagConfiguration, string? culture) - { - if (property == null) - { - throw new ArgumentNullException(nameof(property)); - } - - if (tagConfiguration == null) - { - throw new ArgumentNullException(nameof(tagConfiguration)); - } - - TagsStorageType storageType = tagConfiguration.StorageType; - var delimiter = tagConfiguration.Delimiter; - - SetTagsValue(property, value, storageType, serializer, delimiter, culture); - } - // assumes that parameters are consistent with the datatype configuration // value can be an enumeration of string, or a serialized value using storageType format - private static void SetTagsValue(IProperty property, object? value, TagsStorageType storageType, - IJsonSerializer serializer, char delimiter, string? culture) + private static void SetTagsValue(IProperty property, object? value, TagsStorageType storageType, IJsonSerializer serializer, char delimiter, string? culture) { if (value == null) { @@ -276,20 +268,20 @@ private static void SetTagsValue(IProperty property, object? value, TagsStorageT switch (storageType) { case TagsStorageType.Csv: - var tags2 = value.ToString()!.Split(new[] {delimiter}, StringSplitOptions.RemoveEmptyEntries); + var tags2 = value.ToString()!.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries); property.AssignTags(tags2, false, storageType, serializer, delimiter, culture); break; case TagsStorageType.Json: try { - IEnumerable tags3 = serializer.Deserialize>(value.ToString()!); - property.AssignTags(tags3 ?? Enumerable.Empty(), false, storageType, serializer, delimiter, - culture); + IEnumerable? tags3 = serializer.Deserialize>(value.ToString()!); + property.AssignTags(tags3 ?? Enumerable.Empty(), false, storageType, serializer, delimiter, culture); } catch (Exception ex) { - StaticApplicationLogging.Logger.LogWarning(ex, + StaticApplicationLogging.Logger.LogWarning( + ex, "Could not automatically convert stored json value to an enumerable string '{Json}'", value.ToString()); } diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 696b9dc9239c..0699ecbc0db2 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Strings; @@ -66,8 +66,7 @@ public PropertyType(IShortStringHelper shortStringHelper, IDataType dataType, st /// /// Initializes a new instance of the class. /// - public PropertyType(IShortStringHelper shortStringHelper, string propertyEditorAlias, - ValueStorageType valueStorageType) + public PropertyType(IShortStringHelper shortStringHelper, string propertyEditorAlias, ValueStorageType valueStorageType) : this(shortStringHelper, propertyEditorAlias, valueStorageType, false) { } @@ -75,8 +74,7 @@ public PropertyType(IShortStringHelper shortStringHelper, string propertyEditorA /// /// Initializes a new instance of the class. /// - public PropertyType(IShortStringHelper shortStringHelper, string propertyEditorAlias, - ValueStorageType valueStorageType, string propertyTypeAlias) + public PropertyType(IShortStringHelper shortStringHelper, string propertyEditorAlias, ValueStorageType valueStorageType, string propertyTypeAlias) : this(shortStringHelper, propertyEditorAlias, valueStorageType, false, propertyTypeAlias) { } @@ -88,8 +86,7 @@ public PropertyType(IShortStringHelper shortStringHelper, string propertyEditorA /// Set to true to force the value storage type. Values assigned to /// the property, eg from the underlying datatype, will be ignored. /// - public PropertyType(IShortStringHelper shortStringHelper, string propertyEditorAlias, - ValueStorageType valueStorageType, bool forceValueStorageType, string? propertyTypeAlias = null) + public PropertyType(IShortStringHelper shortStringHelper, string propertyEditorAlias, ValueStorageType valueStorageType, bool forceValueStorageType, string? propertyTypeAlias = null) { _shortStringHelper = shortStringHelper; _propertyEditorAlias = propertyEditorAlias; @@ -100,10 +97,6 @@ public PropertyType(IShortStringHelper shortStringHelper, string propertyEditorA _name = string.Empty; } - /// - public bool Equals(PropertyType? other) => - other != null && (base.Equals(other) || (Alias?.InvariantEquals(other.Alias) ?? false)); - /// /// Gets a value indicating whether the content type owning this property type is publishing. /// @@ -130,6 +123,10 @@ public bool Equals(PropertyType? other) => /// public bool SupportsPublishing { get; set; } + /// + public bool Equals(PropertyType? other) => + other != null && (base.Equals(other) || (Alias?.InvariantEquals(other.Alias) ?? false)); + /// [DataMember] public string Name @@ -210,7 +207,6 @@ public bool Mandatory set => SetPropertyValueAndDetectChanges(value, ref _mandatory, nameof(Mandatory)); } - /// [DataMember] public string? MandatoryMessage @@ -243,7 +239,6 @@ public string? ValidationRegExp set => SetPropertyValueAndDetectChanges(value, ref _validationRegExp, nameof(ValidationRegExp)); } - /// /// Gets or sets the custom validation message used when a pattern for this PropertyType must be matched /// @@ -263,32 +258,22 @@ public ContentVariation Variations /// public bool SupportsVariation(string? culture, string? segment, bool wildcards = false) => + // exact validation: cannot accept a 'null' culture if the property type varies // by culture, and likewise for segment // wildcard validation: can accept a '*' culture or segment Variations.ValidateVariation(culture, segment, true, wildcards, false); - /// - /// Sanitizes a property type alias. - /// - private string SanitizeAlias(string value) => - //NOTE: WE are doing this because we don't want to do a ToSafeAlias when the alias is the special case of - // being prefixed with Constants.PropertyEditors.InternalGenericPropertiesPrefix - // which is used internally - value.StartsWith(Constants.PropertyEditors.InternalGenericPropertiesPrefix) - ? value - : value.ToCleanString(_shortStringHelper, CleanStringType.Alias | CleanStringType.UmbracoCase); - /// public override int GetHashCode() { - //Get hash code for the Name field if it is not null. + // Get hash code for the Name field if it is not null. var baseHash = base.GetHashCode(); - //Get hash code for the Alias field. + // Get hash code for the Alias field. var hashAlias = Alias?.ToLowerInvariant().GetHashCode(); - //Calculate the hash code for the product. + // Calculate the hash code for the product. return baseHash ^ hashAlias ?? baseHash; } @@ -298,10 +283,23 @@ protected override void PerformDeepClone(object clone) base.PerformDeepClone(clone); var clonedEntity = (PropertyType)clone; - //need to manually assign the Lazy value as it will not be automatically mapped + + // need to manually assign the Lazy value as it will not be automatically mapped if (PropertyGroupId != null) { clonedEntity._propertyGroupId = new Lazy(() => PropertyGroupId.Value); } } + + /// + /// Sanitizes a property type alias. + /// + private string SanitizeAlias(string value) => + + // NOTE: WE are doing this because we don't want to do a ToSafeAlias when the alias is the special case of + // being prefixed with Constants.PropertyEditors.InternalGenericPropertiesPrefix + // which is used internally + value.StartsWith(Constants.PropertyEditors.InternalGenericPropertiesPrefix) + ? value + : value.ToCleanString(_shortStringHelper, CleanStringType.Alias | CleanStringType.UmbracoCase); } diff --git a/src/Umbraco.Core/Models/PropertyTypeCollection.cs b/src/Umbraco.Core/Models/PropertyTypeCollection.cs index 2dccca5279e0..49c83b4c9da1 100644 --- a/src/Umbraco.Core/Models/PropertyTypeCollection.cs +++ b/src/Umbraco.Core/Models/PropertyTypeCollection.cs @@ -1,16 +1,18 @@ -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; -//public interface IPropertyTypeCollection: IEnumerable +// public interface IPropertyTypeCollection: IEnumerable + /// /// Represents a collection of objects. /// [Serializable] [DataContract] + // TODO: Change this to ObservableDictionary so we can reduce the INotifyCollectionChanged implementation details public class PropertyTypeCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable, ICollection @@ -21,6 +23,8 @@ public PropertyTypeCollection(bool supportsPublishing, IEnumerable Reset(properties); + public event NotifyCollectionChangedEventHandler? CollectionChanged; + public bool SupportsPublishing { get; } // This baseclass calling is needed, else compiler will complain about nullability @@ -35,27 +39,26 @@ public PropertyTypeCollection(bool supportsPublishing, IEnumerable x.SortOrder == item.SortOrder)) { - //make it the next iteration + // make it the next iteration item.SortOrder = this.Max(x => x.SortOrder) + 1; } - //collection events will be raised in InsertItem + // collection events will be raised in InsertItem base.Add(item); } @@ -70,7 +73,14 @@ public object DeepClone() return clone; } - public event NotifyCollectionChangedEventHandler? CollectionChanged; + /// + /// Determines whether this collection contains a whose alias matches the specified + /// PropertyType. + /// + /// Alias of the PropertyType. + /// true if the collection contains the specified alias; otherwise, false. + /// + public new bool Contains(string propertyAlias) => this.Any(x => x.Alias == propertyAlias); /// /// Resets the collection to only contain the instances referenced in the @@ -80,10 +90,10 @@ public object DeepClone() /// internal void Reset(IEnumerable properties) { - //collection events will be raised in each of these calls + // collection events will be raised in each of these calls Clear(); - //collection events will be raised in each of these calls + // collection events will be raised in each of these calls foreach (IPropertyType property in properties) { Add(property); @@ -138,15 +148,6 @@ private void Item_PropertyChanged(object? sender, PropertyChangedEventArgs e) new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, propType, propType)); } - /// - /// Determines whether this collection contains a whose alias matches the specified - /// PropertyType. - /// - /// Alias of the PropertyType. - /// true if the collection contains the specified alias; otherwise, false. - /// - public new bool Contains(string propertyAlias) => this.Any(x => x.Alias == propertyAlias); - public bool RemoveItem(string propertyTypeAlias) { var key = IndexOfKey(propertyTypeAlias); @@ -171,13 +172,13 @@ public int IndexOfKey(string key) return -1; } - protected override string GetKeyForItem(IPropertyType item) => item.Alias!; - /// /// Clears all event handlers /// public void ClearCollectionChangedEvents() => CollectionChanged = null; + protected override string GetKeyForItem(IPropertyType item) => item.Alias; + protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) => CollectionChanged?.Invoke(this, args); } diff --git a/src/Umbraco.Core/Models/PublicAccessEntry.cs b/src/Umbraco.Core/Models/PublicAccessEntry.cs index e391d56b675b..8789ef505279 100644 --- a/src/Umbraco.Core/Models/PublicAccessEntry.cs +++ b/src/Umbraco.Core/Models/PublicAccessEntry.cs @@ -1,4 +1,4 @@ -using System.Collections.Specialized; +using System.Collections.Specialized; using System.Runtime.Serialization; using Umbraco.Cms.Core.Collections; using Umbraco.Cms.Core.Models.Entities; @@ -15,8 +15,7 @@ public class PublicAccessEntry : EntityBase private int _noAccessNodeId; private int _protectedNodeId; - public PublicAccessEntry(IContent protectedNode, IContent loginNode, IContent noAccessNode, - IEnumerable ruleCollection) + public PublicAccessEntry(IContent protectedNode, IContent loginNode, IContent noAccessNode, IEnumerable ruleCollection) { if (protectedNode == null) { @@ -38,7 +37,7 @@ public PublicAccessEntry(IContent protectedNode, IContent loginNode, IContent no _protectedNodeId = protectedNode.Id; _ruleCollection = new EventClearingObservableCollection(ruleCollection); - _ruleCollection.CollectionChanged += _ruleCollection_CollectionChanged; + _ruleCollection.CollectionChanged += RuleCollection_CollectionChanged; foreach (PublicAccessRule rule in _ruleCollection) { @@ -46,8 +45,7 @@ public PublicAccessEntry(IContent protectedNode, IContent loginNode, IContent no } } - public PublicAccessEntry(Guid id, int protectedNodeId, int loginNodeId, int noAccessNodeId, - IEnumerable ruleCollection) + public PublicAccessEntry(Guid id, int protectedNodeId, int loginNodeId, int noAccessNodeId, IEnumerable ruleCollection) { Key = id; Id = Key.GetHashCode(); @@ -57,7 +55,7 @@ public PublicAccessEntry(Guid id, int protectedNodeId, int loginNodeId, int noAc _protectedNodeId = protectedNodeId; _ruleCollection = new EventClearingObservableCollection(ruleCollection); - _ruleCollection.CollectionChanged += _ruleCollection_CollectionChanged; + _ruleCollection.CollectionChanged += RuleCollection_CollectionChanged; foreach (PublicAccessRule rule in _ruleCollection) { @@ -90,23 +88,29 @@ public int ProtectedNodeId set => SetPropertyValueAndDetectChanges(value, ref _protectedNodeId, nameof(ProtectedNodeId)); } - private void _ruleCollection_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + public PublicAccessRule AddRule(string ruleValue, string ruleType) + { + var rule = new PublicAccessRule { AccessEntryId = Key, RuleValue = ruleValue, RuleType = ruleType }; + _ruleCollection.Add(rule); + return rule; + } + + private void RuleCollection_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { OnPropertyChanged(nameof(Rules)); - //if (e.Action == NotifyCollectionChangedAction.Add) - //{ + // if (e.Action == NotifyCollectionChangedAction.Add) + // { // var item = e.NewItems.Cast().First(); - // if (_addedSections.Contains(item) == false) + // if (_addedSections.Contains(item) == false) // { // _addedSections.Add(item); // } - //} - + // } if (e.Action == NotifyCollectionChangedAction.Remove) { - PublicAccessRule item = e.OldItems?.Cast().First(); + PublicAccessRule? item = e.OldItems?.Cast().First(); if (item is not null) { @@ -118,20 +122,10 @@ private void _ruleCollection_CollectionChanged(object? sender, NotifyCollectionC } } - public PublicAccessRule AddRule(string ruleValue, string ruleType) - { - var rule = new PublicAccessRule {AccessEntryId = Key, RuleValue = ruleValue, RuleType = ruleType}; - _ruleCollection.Add(rule); - return rule; - } - public void RemoveRule(PublicAccessRule rule) => _ruleCollection.Remove(rule); public void ClearRules() => _ruleCollection.Clear(); - - internal void ClearRemovedRules() => _removedRules.Clear(); - public override void ResetDirtyProperties(bool rememberDirty) { _removedRules.Clear(); @@ -142,6 +136,8 @@ public override void ResetDirtyProperties(bool rememberDirty) } } + internal void ClearRemovedRules() => _removedRules.Clear(); + protected override void PerformDeepClone(object clone) { base.PerformDeepClone(clone); @@ -150,9 +146,9 @@ protected override void PerformDeepClone(object clone) if (cloneEntity._ruleCollection != null) { - cloneEntity._ruleCollection.ClearCollectionChangedEvents(); //clear this event handler if any + cloneEntity._ruleCollection.ClearCollectionChangedEvents(); // clear this event handler if any cloneEntity._ruleCollection.CollectionChanged += - cloneEntity._ruleCollection_CollectionChanged; //re-assign correct event handler + cloneEntity.RuleCollection_CollectionChanged; // re-assign correct event handler } } } diff --git a/src/Umbraco.Core/Models/PublicAccessRule.cs b/src/Umbraco.Core/Models/PublicAccessRule.cs index 613643bb7c99..f8af1a6d980e 100644 --- a/src/Umbraco.Core/Models/PublicAccessRule.cs +++ b/src/Umbraco.Core/Models/PublicAccessRule.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/PublishedContent/ModelType.cs b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs index 096dcb245dfe..a0d10ff89034 100644 --- a/src/Umbraco.Core/Models/PublishedContent/ModelType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; using System.Reflection; using Umbraco.Cms.Core.Exceptions; @@ -24,7 +24,8 @@ private ModelType(string? contentTypeAlias) if (string.IsNullOrWhiteSpace(contentTypeAlias)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(contentTypeAlias)); } @@ -64,10 +65,6 @@ private ModelType(string? contentTypeAlias) /// public override string AssemblyQualifiedName => Name; - /// - public override string ToString() - => Name; - /// /// Gets the model type for a published element type. /// @@ -76,6 +73,10 @@ public override string ToString() public static ModelType For(string? alias) => new(alias); + /// + public override string ToString() + => Name; + /// /// Gets the actual CLR type by replacing model types, if any. /// @@ -95,7 +96,7 @@ public static Type Map(Type type, Dictionary? modelTypes, bool dic if (type is ModelType modelType) { - if (modelTypes?.TryGetValue(modelType.ContentTypeAlias, out Type actualType) ?? false) + if (modelTypes?.TryGetValue(modelType.ContentTypeAlias, out Type? actualType) ?? false) { return actualType; } @@ -106,7 +107,7 @@ public static Type Map(Type type, Dictionary? modelTypes, bool dic if (type is ModelTypeArrayType arrayType) { - if (modelTypes?.TryGetValue(arrayType.ContentTypeAlias, out Type actualType) ?? false) + if (modelTypes?.TryGetValue(arrayType.ContentTypeAlias, out Type? actualType) ?? false) { return actualType.MakeArrayType(); } @@ -139,52 +140,6 @@ public static Type Map(Type type, Dictionary? modelTypes, bool dic public static string MapToName(Type type, Dictionary map) => MapToName(type, map, false); - private static string MapToName(Type type, Dictionary map, bool dictionaryIsInvariant) - { - // it may be that senders forgot to send an invariant dictionary (garbage-in) - if (!dictionaryIsInvariant) - { - map = new Dictionary(map, StringComparer.InvariantCultureIgnoreCase); - } - - if (type is ModelType modelType) - { - if (map.TryGetValue(modelType.ContentTypeAlias, out var actualTypeName)) - { - return actualTypeName; - } - - throw new InvalidOperationException( - $"Don't know how to map ModelType with content type alias \"{modelType.ContentTypeAlias}\"."); - } - - if (type is ModelTypeArrayType arrayType) - { - if (map.TryGetValue(arrayType.ContentTypeAlias, out var actualTypeName)) - { - return actualTypeName + "[]"; - } - - throw new InvalidOperationException( - $"Don't know how to map ModelType with content type alias \"{arrayType.ContentTypeAlias}\"."); - } - - if (type.IsGenericType == false) - { - return type.FullName!; - } - - Type def = type.GetGenericTypeDefinition(); - if (def == null) - { - throw new PanicException($"The type {type} has not generic type definition"); - } - - var args = type.GetGenericArguments().Select(x => MapToName(x, map, true)).ToArray(); - var defFullName = def.FullName?.Substring(0, def.FullName.IndexOf('`')); - return defFullName + "<" + string.Join(", ", args) + ">"; - } - /// /// Gets a value indicating whether two instances are equal. /// @@ -233,23 +188,73 @@ public static bool Equals(Type t1, Type t2) return true; } - /// - protected override TypeAttributes GetAttributeFlagsImpl() - => TypeAttributes.Class; - /// public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) => Array.Empty(); - /// - protected override ConstructorInfo? GetConstructorImpl(BindingFlags bindingAttr, Binder? binder, - CallingConventions callConvention, Type[] types, ParameterModifier[]? modifiers) - => null; - /// public override Type[] GetInterfaces() => Array.Empty(); + private static string MapToName(Type type, Dictionary map, bool dictionaryIsInvariant) + { + // it may be that senders forgot to send an invariant dictionary (garbage-in) + if (!dictionaryIsInvariant) + { + map = new Dictionary(map, StringComparer.InvariantCultureIgnoreCase); + } + + if (type is ModelType modelType) + { + if (map.TryGetValue(modelType.ContentTypeAlias, out var actualTypeName)) + { + return actualTypeName; + } + + throw new InvalidOperationException( + $"Don't know how to map ModelType with content type alias \"{modelType.ContentTypeAlias}\"."); + } + + if (type is ModelTypeArrayType arrayType) + { + if (map.TryGetValue(arrayType.ContentTypeAlias, out var actualTypeName)) + { + return actualTypeName + "[]"; + } + + throw new InvalidOperationException( + $"Don't know how to map ModelType with content type alias \"{arrayType.ContentTypeAlias}\"."); + } + + if (type.IsGenericType == false) + { + return type.FullName!; + } + + Type def = type.GetGenericTypeDefinition(); + if (def == null) + { + throw new PanicException($"The type {type} has not generic type definition"); + } + + var args = type.GetGenericArguments().Select(x => MapToName(x, map, true)).ToArray(); + var defFullName = def.FullName?[..def.FullName.IndexOf('`')]; + return defFullName + "<" + string.Join(", ", args) + ">"; + } + + /// + protected override TypeAttributes GetAttributeFlagsImpl() + => TypeAttributes.Class; + + /// + protected override ConstructorInfo? GetConstructorImpl( + BindingFlags bindingAttr, + Binder? binder, + CallingConventions callConvention, + Type[] types, + ParameterModifier[]? modifiers) + => null; + /// public override Type? GetInterface(string name, bool ignoreCase) => null; @@ -274,23 +279,33 @@ public override Type[] GetNestedTypes(BindingFlags bindingAttr) public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) => Array.Empty(); - /// - protected override PropertyInfo? GetPropertyImpl(string name, BindingFlags bindingAttr, Binder? binder, - Type? returnType, Type[]? types, ParameterModifier[]? modifiers) - => null; - /// public override MethodInfo[] GetMethods(BindingFlags bindingAttr) => Array.Empty(); /// - protected override MethodInfo? GetMethodImpl(string name, BindingFlags bindingAttr, Binder? binder, - CallingConventions callConvention, Type[]? types, ParameterModifier[]? modifiers) + public override FieldInfo[] GetFields(BindingFlags bindingAttr) + => Array.Empty(); + + /// + protected override PropertyInfo? GetPropertyImpl( + string name, + BindingFlags bindingAttr, + Binder? binder, + Type? returnType, + Type[]? types, + ParameterModifier[]? modifiers) => null; /// - public override FieldInfo[] GetFields(BindingFlags bindingAttr) - => Array.Empty(); + protected override MethodInfo? GetMethodImpl( + string name, + BindingFlags bindingAttr, + Binder? binder, + CallingConventions callConvention, + Type[]? types, + ParameterModifier[]? modifiers) + => null; /// public override FieldInfo? GetField(string name, BindingFlags bindingAttr) @@ -316,6 +331,18 @@ public override bool IsDefined(Type attributeType, bool inherit) public override Type? GetElementType() => null; + /// + public override object InvokeMember( + string name, + BindingFlags invokeAttr, + Binder? binder, + object? target, + object?[]? args, + ParameterModifier[]? modifiers, + CultureInfo? culture, + string[]? namedParameters) + => throw new NotSupportedException(); + /// protected override bool HasElementTypeImpl() => false; @@ -340,11 +367,6 @@ protected override bool IsPrimitiveImpl() protected override bool IsCOMObjectImpl() => false; - /// - public override object InvokeMember(string name, BindingFlags invokeAttr, Binder? binder, object? target, - object?[]? args, ParameterModifier[]? modifiers, CultureInfo? culture, string[]? namedParameters) - => throw new NotSupportedException(); - /// public override Type MakeArrayType() => new ModelTypeArrayType(this); @@ -364,32 +386,43 @@ public ModelTypeArrayType(ModelType type) public string ContentTypeAlias { get; } public override Type UnderlyingSystemType => this; + public override Type? BaseType => null; public override string Name { get; } + public override Guid GUID { get; } = Guid.NewGuid(); + public override Module Module => GetType().Module; // hackish but FullName requires something + public override Assembly Assembly => GetType().Assembly; // hackish but FullName requires something + public override string FullName => Name; + public override string Namespace => string.Empty; + public override string AssemblyQualifiedName => Name; public override string ToString() => Name; - protected override TypeAttributes GetAttributeFlagsImpl() - => TypeAttributes.Class; - public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr) => Array.Empty(); - protected override ConstructorInfo? GetConstructorImpl(BindingFlags bindingAttr, Binder? binder, - CallingConventions callConvention, Type[] types, ParameterModifier[]? modifiers) - => null; - public override Type[] GetInterfaces() => Array.Empty(); + protected override TypeAttributes GetAttributeFlagsImpl() + => TypeAttributes.Class; + + protected override ConstructorInfo? GetConstructorImpl( + BindingFlags bindingAttr, + Binder? binder, + CallingConventions callConvention, + Type[] types, + ParameterModifier[]? modifiers) + => null; + public override Type? GetInterface(string name, bool ignoreCase) => null; @@ -408,20 +441,30 @@ public override Type[] GetNestedTypes(BindingFlags bindingAttr) public override PropertyInfo[] GetProperties(BindingFlags bindingAttr) => Array.Empty(); - protected override PropertyInfo? GetPropertyImpl(string name, BindingFlags bindingAttr, Binder? binder, - Type? returnType, Type[]? types, ParameterModifier[]? modifiers) - => null; - public override MethodInfo[] GetMethods(BindingFlags bindingAttr) => Array.Empty(); - protected override MethodInfo? GetMethodImpl(string name, BindingFlags bindingAttr, Binder? binder, - CallingConventions callConvention, Type[]? types, ParameterModifier[]? modifiers) - => null; - public override FieldInfo[] GetFields(BindingFlags bindingAttr) => Array.Empty(); + protected override PropertyInfo? GetPropertyImpl( + string name, + BindingFlags bindingAttr, + Binder? binder, + Type? returnType, + Type[]? types, + ParameterModifier[]? modifiers) + => null; + + protected override MethodInfo? GetMethodImpl( + string name, + BindingFlags bindingAttr, + Binder? binder, + CallingConventions callConvention, + Type[]? types, + ParameterModifier[]? modifiers) + => null; + public override FieldInfo? GetField(string name, BindingFlags bindingAttr) => null; @@ -440,6 +483,17 @@ public override bool IsDefined(Type attributeType, bool inherit) public override Type GetElementType() => _elementType; + public override object InvokeMember( + string name, + BindingFlags invokeAttr, + Binder? binder, + object? target, + object?[]? args, + ParameterModifier[]? modifiers, + CultureInfo? culture, + string[]? namedParameters) => + throw new NotSupportedException(); + protected override bool HasElementTypeImpl() => true; @@ -458,10 +512,6 @@ protected override bool IsPrimitiveImpl() protected override bool IsCOMObjectImpl() => false; - public override object InvokeMember(string name, BindingFlags invokeAttr, Binder? binder, object? target, - object?[]? args, ParameterModifier[]? modifiers, CultureInfo? culture, string[]? namedParameters) => - throw new NotSupportedException(); - public override int GetArrayRank() => 1; } diff --git a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedModelFactory.cs index 05a7b305b0f9..5eefd1e12b60 100644 --- a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedModelFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedModelFactory.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; namespace Umbraco.Cms.Core.Models.PublishedContent; diff --git a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs index 20e78a2bfcee..1dd2fef1241b 100644 --- a/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/NoopPublishedValueFallback.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent; +namespace Umbraco.Cms.Core.Models.PublishedContent; /// /// Provides a noop implementation for . @@ -9,40 +9,35 @@ public class NoopPublishedValueFallback : IPublishedValueFallback { /// - public bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, - object? defaultValue, out object? value) + public bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, object? defaultValue, out object? value) { value = default; return false; } /// - public bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, - T? defaultValue, out T? value) + public bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, T? defaultValue, out T? value) { value = default; return false; } /// - public bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, - Fallback fallback, object? defaultValue, out object? value) + public bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, Fallback fallback, object? defaultValue, out object? value) { value = default; return false; } /// - public bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, - Fallback fallback, T? defaultValue, out T? value) + public bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, Fallback fallback, T? defaultValue, out T? value) { value = default; return false; } /// - public bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, - Fallback fallback, object? defaultValue, out object? value, out IPublishedProperty? noValueProperty) + public bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, Fallback fallback, object? defaultValue, out object? value, out IPublishedProperty? noValueProperty) { value = default; noValueProperty = default; @@ -50,8 +45,7 @@ public bool TryGetValue(IPublishedContent content, string alias, string? culture } /// - public bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, - Fallback fallback, T defaultValue, out T? value, out IPublishedProperty? noValueProperty) + public bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, Fallback fallback, T defaultValue, out T? value, out IPublishedProperty? noValueProperty) { value = default; noValueProperty = default; diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentBase.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentBase.cs index 01dabef96214..5fd16e26aa2c 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentBase.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentBase.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Models.PublishedContent; @@ -72,12 +72,6 @@ protected PublishedContentBase(IVariationContextAccessor? variationContextAccess /// public abstract PublishedItemType ItemType { get; } - /// - public abstract bool IsDraft(string? culture = null); - - /// - public abstract bool IsPublished(string? culture = null); - #endregion #region Tree @@ -85,6 +79,12 @@ protected PublishedContentBase(IVariationContextAccessor? variationContextAccess /// public abstract IPublishedContent? Parent { get; } + /// + public abstract bool IsDraft(string? culture = null); + + /// + public abstract bool IsPublished(string? culture = null); + /// public virtual IEnumerable? Children => this.Children(_variationContextAccessor); diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs index 66b1d70370c4..2b123a33a948 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentExtensionsForModels.cs @@ -11,8 +11,10 @@ public static class PublishedContentExtensionsForModels /// Creates a strongly typed published content model for an internal published content. /// /// The internal published content. + /// The published model factory /// The strongly typed published content model. - public static IPublishedContent? CreateModel(this IPublishedContent content, + public static IPublishedContent? CreateModel( + this IPublishedContent? content, IPublishedModelFactory? publishedModelFactory) { if (publishedModelFactory == null) diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModel.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentModel.cs index feb9383092f1..cfa648594e70 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModel.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentModel.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent; +namespace Umbraco.Cms.Core.Models.PublishedContent; /// /// Represents a strongly-typed published content. @@ -14,6 +14,7 @@ public abstract class PublishedContentModel : PublishedContentWrapped /// an original instance. /// /// The original content. + /// the PublishedValueFallback protected PublishedContentModel(IPublishedContent content, IPublishedValueFallback publishedValueFallback) : base(content, publishedValueFallback) { diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index d2fcb9e9f4ee..ddcf2cd64c81 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -16,15 +16,15 @@ public class PublishedContentType : IPublishedContentType // TODO: this list somehow also exists in constants, see memberTypeRepository => remove duplicate! private static readonly Dictionary BuiltinMemberProperties = new() { - {nameof(IMember.Email), Constants.DataTypes.Textbox}, - {nameof(IMember.Username), Constants.DataTypes.Textbox}, - {nameof(IMember.Comments), Constants.DataTypes.Textbox}, - {nameof(IMember.IsApproved), Constants.DataTypes.Boolean}, - {nameof(IMember.IsLockedOut), Constants.DataTypes.Boolean}, - {nameof(IMember.LastLockoutDate), Constants.DataTypes.LabelDateTime}, - {nameof(IMember.CreateDate), Constants.DataTypes.LabelDateTime}, - {nameof(IMember.LastLoginDate), Constants.DataTypes.LabelDateTime}, - {nameof(IMember.LastPasswordChangeDate), Constants.DataTypes.LabelDateTime} + { nameof(IMember.Email), Constants.DataTypes.Textbox }, + { nameof(IMember.Username), Constants.DataTypes.Textbox }, + { nameof(IMember.Comments), Constants.DataTypes.Textbox }, + { nameof(IMember.IsApproved), Constants.DataTypes.Boolean }, + { nameof(IMember.IsLockedOut), Constants.DataTypes.Boolean }, + { nameof(IMember.LastLockoutDate), Constants.DataTypes.LabelDateTime }, + { nameof(IMember.CreateDate), Constants.DataTypes.LabelDateTime }, + { nameof(IMember.LastLoginDate), Constants.DataTypes.LabelDateTime }, + { nameof(IMember.LastPasswordChangeDate), Constants.DataTypes.LabelDateTime }, }; // fast alias-to-index xref containing both the raw alias and its lowercase version @@ -35,8 +35,7 @@ public class PublishedContentType : IPublishedContentType /// Initializes a new instance of the class with a content type. /// public PublishedContentType(IContentTypeComposition contentType, IPublishedContentTypeFactory factory) - : this(contentType.Key, contentType.Id, contentType.Alias, contentType.GetItemType(), - contentType.CompositionAliases(), contentType.Variations, contentType.IsElement) + : this(contentType.Key, contentType.Id, contentType.Alias, contentType.GetItemType(), contentType.CompositionAliases(), contentType.Variations, contentType.IsElement) { var propertyTypes = contentType.CompositionPropertyTypes .Select(x => factory.CreatePropertyType(this, x)) @@ -58,9 +57,15 @@ public PublishedContentType(IContentTypeComposition contentType, IPublishedConte /// /// Values are assumed to be consistent and are not checked. /// - public PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, - IEnumerable compositionAliases, IEnumerable propertyTypes, - ContentVariation variations, bool isElement = false) + public PublishedContentType( + Guid key, + int id, + string alias, + PublishedItemType itemType, + IEnumerable compositionAliases, + IEnumerable propertyTypes, + ContentVariation variations, + bool isElement = false) : this(key, id, alias, itemType, compositionAliases, variations, isElement) { PublishedPropertyType[] propertyTypesA = propertyTypes.ToArray(); @@ -75,9 +80,14 @@ public PublishedContentType(Guid key, int id, string alias, PublishedItemType it } [Obsolete("Use the overload specifying a key instead")] - public PublishedContentType(int id, string alias, PublishedItemType itemType, - IEnumerable compositionAliases, IEnumerable propertyTypes, - ContentVariation variations, bool isElement = false) + public PublishedContentType( + int id, + string alias, + PublishedItemType itemType, + IEnumerable compositionAliases, + IEnumerable propertyTypes, + ContentVariation variations, + bool isElement = false) : this(Guid.Empty, id, alias, itemType, compositionAliases, variations, isElement) { PublishedPropertyType[] propertyTypesA = propertyTypes.ToArray(); @@ -97,9 +107,14 @@ public PublishedContentType(int id, string alias, PublishedItemType itemType, /// /// Values are assumed to be consistent and are not checked. /// - public PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, + public PublishedContentType( + Guid key, + int id, + string alias, + PublishedItemType itemType, IEnumerable compositionAliases, - Func> propertyTypes, ContentVariation variations, + Func> propertyTypes, + ContentVariation variations, bool isElement = false) : this(key, id, alias, itemType, compositionAliases, variations, isElement) { @@ -109,9 +124,13 @@ public PublishedContentType(Guid key, int id, string alias, PublishedItemType it } [Obsolete("Use the overload specifying a key instead")] - public PublishedContentType(int id, string alias, PublishedItemType itemType, + public PublishedContentType( + int id, + string alias, + PublishedItemType itemType, IEnumerable compositionAliases, - Func> propertyTypes, ContentVariation variations, + Func> propertyTypes, + ContentVariation variations, bool isElement = false) : this(Guid.Empty, id, alias, itemType, compositionAliases, variations, isElement) { @@ -120,8 +139,7 @@ public PublishedContentType(int id, string alias, PublishedItemType itemType, InitializeIndexes(); } - private PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, - IEnumerable compositionAliases, ContentVariation variations, bool isElement) + private PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, ContentVariation variations, bool isElement) { Key = key; Id = id; @@ -132,6 +150,11 @@ private PublishedContentType(Guid key, int id, string alias, PublishedItemType i IsElement = isElement; } + #region Content type + + /// + public Guid Key { get; } + private void InitializeIndexes() { if (_propertyTypes is not null) @@ -151,12 +174,13 @@ private void InitializeIndexes() // Members have properties such as IMember LastLoginDate which are plain C# properties and not content // properties; they are exposed as pseudo content properties, as long as a content property with the // same alias does not exist already. - private void EnsureMemberProperties(List propertyTypes, + private void EnsureMemberProperties( + List propertyTypes, IPublishedContentTypeFactory factory) { var aliases = new HashSet(propertyTypes.Select(x => x.Alias), StringComparer.OrdinalIgnoreCase); - foreach (var (alias, dataTypeId) in BuiltinMemberProperties) + foreach ((string alias, int dataTypeId) in BuiltinMemberProperties) { if (aliases.Contains(alias)) { @@ -167,11 +191,6 @@ private void EnsureMemberProperties(List propertyTypes, } } - #region Content type - - /// - public Guid Key { get; } - /// public int Id { get; } @@ -194,6 +213,9 @@ private void EnsureMemberProperties(List propertyTypes, /// public IEnumerable PropertyTypes => _propertyTypes; + /// + public bool IsElement { get; } + /// public int GetPropertyIndex(string alias) { @@ -212,6 +234,7 @@ public int GetPropertyIndex(string alias) // virtual for unit tests // TODO: explain why + /// public virtual IPublishedPropertyType? GetPropertyType(string alias) { @@ -221,12 +244,10 @@ public int GetPropertyIndex(string alias) // virtual for unit tests // TODO: explain why + /// public virtual IPublishedPropertyType? GetPropertyType(int index) => index >= 0 && _propertyTypes is not null && index < _propertyTypes.Length ? _propertyTypes[index] : null; - /// - public bool IsElement { get; } - #endregion } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeConverter.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeConverter.cs index 6d1601ee29b3..957246ccfe35 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeConverter.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeConverter.cs @@ -1,18 +1,17 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Globalization; namespace Umbraco.Cms.Core.Models.PublishedContent; internal class PublishedContentTypeConverter : TypeConverter { - private static readonly Type[] ConvertingTypes = {typeof(int)}; + private static readonly Type[] ConvertingTypes = { typeof(int) }; public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType) => ConvertingTypes.Any(x => x.IsAssignableFrom(destinationType)) || (destinationType is not null && CanConvertFrom(context, destinationType)); - public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, - Type destinationType) + public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) { if (!(value is IPublishedContent publishedContent)) { diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs index 581097de3e57..f2b1b9bbcadd 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeFactory.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Core.Models.PublishedContent; @@ -14,8 +14,10 @@ public class PublishedContentTypeFactory : IPublishedContentTypeFactory private readonly IPublishedModelFactory _publishedModelFactory; private Dictionary? _publishedDataTypes; - public PublishedContentTypeFactory(IPublishedModelFactory publishedModelFactory, - PropertyValueConverterCollection propertyValueConverters, IDataTypeService dataTypeService) + public PublishedContentTypeFactory( + IPublishedModelFactory publishedModelFactory, + PropertyValueConverterCollection propertyValueConverters, + IDataTypeService dataTypeService) { _publishedModelFactory = publishedModelFactory; _propertyValueConverters = propertyValueConverters; @@ -31,16 +33,21 @@ public IPublishedPropertyType CreatePropertyType(IPublishedContentType contentTy new PublishedPropertyType(contentType, propertyType, _propertyValueConverters, _publishedModelFactory, this); /// - public IPublishedPropertyType CreatePropertyType(IPublishedContentType contentType, string propertyTypeAlias, - int dataTypeId, ContentVariation variations = ContentVariation.Nothing) => new PublishedPropertyType( - contentType, propertyTypeAlias, dataTypeId, true, variations, _propertyValueConverters, _publishedModelFactory, - this); + public IPublishedPropertyType CreatePropertyType( + IPublishedContentType contentType, + string propertyTypeAlias, + int dataTypeId, + ContentVariation variations = ContentVariation.Nothing) => + new PublishedPropertyType( + contentType, propertyTypeAlias, dataTypeId, true, variations, _propertyValueConverters, _publishedModelFactory, this); /// - public IPublishedPropertyType CreateCorePropertyType(IPublishedContentType contentType, string propertyTypeAlias, - int dataTypeId, ContentVariation variations = ContentVariation.Nothing) => new PublishedPropertyType( - contentType, propertyTypeAlias, dataTypeId, false, variations, _propertyValueConverters, _publishedModelFactory, - this); + public IPublishedPropertyType CreateCorePropertyType( + IPublishedContentType contentType, + string propertyTypeAlias, + int dataTypeId, + ContentVariation variations = ContentVariation.Nothing) => + new PublishedPropertyType(contentType, propertyTypeAlias, dataTypeId, false, variations, _propertyValueConverters, _publishedModelFactory, this); /// public PublishedDataType GetDataType(int id) @@ -57,7 +64,7 @@ public PublishedDataType GetDataType(int id) publishedDataTypes = _publishedDataTypes; } - if (publishedDataTypes is null || !publishedDataTypes.TryGetValue(id, out PublishedDataType dataType)) + if (publishedDataTypes is null || !publishedDataTypes.TryGetValue(id, out PublishedDataType? dataType)) { throw new ArgumentException($"Could not find a datatype with identifier {id}.", nameof(id)); } @@ -95,30 +102,40 @@ public void NotifyDataTypeChanges(int[] ids) /// This method is for tests and is not intended to be used directly from application code. /// /// Values are assumed to be consisted and are not checked. - internal IPublishedContentType CreateContentType(Guid key, int id, string alias, + internal IPublishedContentType CreateContentType( + Guid key, + int id, + string alias, Func> propertyTypes, - ContentVariation variations = ContentVariation.Nothing, bool isElement = false) => new PublishedContentType(key, - id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, variations, isElement); + ContentVariation variations = ContentVariation.Nothing, + bool isElement = false) => + new PublishedContentType(key, id, alias, PublishedItemType.Content, Enumerable.Empty(), propertyTypes, variations, isElement); /// /// This method is for tests and is not intended to be used directly from application code. /// /// Values are assumed to be consisted and are not checked. - internal IPublishedContentType CreateContentType(Guid key, int id, string alias, + internal IPublishedContentType CreateContentType( + Guid key, + int id, + string alias, IEnumerable compositionAliases, Func> propertyTypes, - ContentVariation variations = ContentVariation.Nothing, bool isElement = false) => new PublishedContentType(key, - id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, variations, isElement); + ContentVariation variations = ContentVariation.Nothing, + bool isElement = false) => + new PublishedContentType(key, id, alias, PublishedItemType.Content, compositionAliases, propertyTypes, variations, isElement); /// /// This method is for tests and is not intended to be used directly from application code. /// /// Values are assumed to be consisted and are not checked. - internal IPublishedPropertyType CreatePropertyType(string propertyTypeAlias, int dataTypeId, bool umbraco = false, - ContentVariation variations = ContentVariation.Nothing) => new PublishedPropertyType(propertyTypeAlias, - dataTypeId, umbraco, variations, _propertyValueConverters, _publishedModelFactory, this); + internal IPublishedPropertyType CreatePropertyType( + string propertyTypeAlias, + int dataTypeId, + bool umbraco = false, + ContentVariation variations = ContentVariation.Nothing) => + new PublishedPropertyType(propertyTypeAlias, dataTypeId, umbraco, variations, _propertyValueConverters, _publishedModelFactory, this); private PublishedDataType CreatePublishedDataType(IDataType dataType) - => new(dataType.Id, dataType.EditorAlias, - dataType is DataType d ? d.GetLazyConfiguration() : new Lazy(() => dataType.Configuration)); + => new(dataType.Id, dataType.EditorAlias, dataType is DataType d ? d.GetLazyConfiguration() : new Lazy(() => dataType.Configuration)); } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index 62ec95238127..c9cf54c1399b 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -1,7 +1,7 @@ -using System.Diagnostics; +using System.Diagnostics; namespace Umbraco.Cms.Core.Models.PublishedContent; -// + // we cannot implement strongly-typed content by inheriting from some sort // of "master content" because that master content depends on the actual content cache // that is being used. It can be an XmlPublishedContent with the XmlPublishedCache, @@ -46,6 +46,11 @@ protected PublishedContentWrapped(IPublishedContent content, IPublishedValueFall /// public Guid Key => _content.Key; + #region PublishedContent + + /// + public virtual int Id => _content.Id; + #endregion /// @@ -54,11 +59,6 @@ protected PublishedContentWrapped(IPublishedContent content, IPublishedValueFall /// The wrapped content, that was passed as an argument to the constructor. public IPublishedContent Unwrap() => _content; - #region PublishedContent - - /// - public virtual int Id => _content.Id; - /// public virtual string? Name => _content.Name; @@ -95,12 +95,6 @@ protected PublishedContentWrapped(IPublishedContent content, IPublishedValueFall /// public virtual PublishedItemType ItemType => _content.ItemType; - /// - public virtual bool IsDraft(string? culture = null) => _content.IsDraft(culture); - - /// - public virtual bool IsPublished(string? culture = null) => _content.IsPublished(culture); - #endregion #region Tree @@ -108,6 +102,12 @@ protected PublishedContentWrapped(IPublishedContent content, IPublishedValueFall /// public virtual IPublishedContent? Parent => _content.Parent; + /// + public virtual bool IsDraft(string? culture = null) => _content.IsDraft(culture); + + /// + public virtual bool IsPublished(string? culture = null) => _content.IsPublished(culture); + /// public virtual IEnumerable? Children => _content.Children; diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs index 81735da01bf7..1101301f3616 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedCultureInfos.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; namespace Umbraco.Cms.Core.Models.PublishedContent; @@ -20,7 +20,8 @@ public PublishedCultureInfo(string culture, string? name, string? urlSegment, Da if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(name)); } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedDataType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedDataType.cs index 05fd97e08d2b..8f77f404ae4d 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedDataType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedDataType.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; namespace Umbraco.Cms.Core.Models.PublishedContent; diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedElementModel.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedElementModel.cs index bb220e5e7d9c..b91171012cc0 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedElementModel.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedElementModel.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent; +namespace Umbraco.Cms.Core.Models.PublishedContent; /// /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedElementWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedElementWrapped.cs index 3293627f3760..d56230cbfad0 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedElementWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedElementWrapped.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent; +namespace Umbraco.Cms.Core.Models.PublishedContent; /// /// Provides an abstract base class for IPublishedElement implementations that diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs index d36fee270b4b..2204cc5107d1 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedItemType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent; +namespace Umbraco.Cms.Core.Models.PublishedContent; /// /// The type of published element. @@ -29,5 +29,5 @@ public enum PublishedItemType /// /// A member. /// - Member + Member, } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedModelAttribute.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedModelAttribute.cs index 3444783e8f71..5048f6190860 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedModelAttribute.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedModelAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent; +namespace Umbraco.Cms.Core.Models.PublishedContent; /// /// @@ -25,7 +25,8 @@ public PublishedModelAttribute(string contentTypeAlias) if (string.IsNullOrWhiteSpace(contentTypeAlias)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(contentTypeAlias)); } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs index 96fc909f3a3e..b2d5da7876b2 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs @@ -16,6 +16,7 @@ public class PublishedModelFactory : IPublishedModelFactory /// Initializes a new instance of the class with types. /// /// The model types. + /// /// /// /// Types must implement IPublishedContent and have a unique constructor that @@ -38,7 +39,6 @@ public PublishedModelFactory(IEnumerable types, IPublishedValueFallback pu // so... the model type has to implement a ctor with one parameter being, or inheriting from, // IPublishedElement - but it can be IPublishedContent - so we cannot get one precise ctor, // we have to iterate over all ctors and try to find the right one - ConstructorInfo? constructor = null; Type? parameterType = null; @@ -65,10 +65,10 @@ public PublishedModelFactory(IEnumerable types, IPublishedValueFallback pu $"Type {type.FullName} is missing a public constructor with one argument of type, or implementing, IPublishedElement."); } - PublishedModelAttribute attribute = type.GetCustomAttribute(false); + PublishedModelAttribute? attribute = type.GetCustomAttribute(false); var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias; - if (modelInfos.TryGetValue(typeName, out ModelInfo modelInfo)) + if (modelInfos.TryGetValue(typeName, out ModelInfo? modelInfo)) { throw new InvalidOperationException( $"Both types '{type.AssemblyQualifiedName}' and '{modelInfo.ModelType?.AssemblyQualifiedName}' want to be a model type for content type with alias \"{typeName}\"."); @@ -77,7 +77,7 @@ public PublishedModelFactory(IEnumerable types, IPublishedValueFallback pu // have to use an unsafe ctor because we don't know the types, really Func modelCtor = ReflectionUtilities.EmitConstructorUnsafe>(constructor); - modelInfos[typeName] = new ModelInfo {ParameterType = parameterType, ModelType = type, Ctor = modelCtor}; + modelInfos[typeName] = new ModelInfo { ParameterType = parameterType, ModelType = type, Ctor = modelCtor }; modelTypeMap[typeName] = type; } @@ -91,7 +91,7 @@ public IPublishedElement CreateModel(IPublishedElement element) { // fail fast if (_modelInfos is null || element.ContentType.Alias is null || - !_modelInfos.TryGetValue(element.ContentType.Alias, out ModelInfo modelInfo)) + !_modelInfos.TryGetValue(element.ContentType.Alias, out ModelInfo? modelInfo)) { return element; } @@ -111,13 +111,13 @@ public IPublishedElement CreateModel(IPublishedElement element) public IList? CreateModelList(string? alias) { // fail fast - if (_modelInfos is null || alias is null || !_modelInfos.TryGetValue(alias, out ModelInfo modelInfo) || + if (_modelInfos is null || alias is null || !_modelInfos.TryGetValue(alias, out ModelInfo? modelInfo) || modelInfo.ModelType is null) { return new List(); } - Func ctor = modelInfo.ListCtor; + Func? ctor = modelInfo.ListCtor; if (ctor != null) { return ctor(); @@ -139,8 +139,7 @@ public Type GetModelType(string? alias) // fail fast if (_modelInfos is null || alias is null || - !_modelInfos.TryGetValue(alias, out ModelInfo modelInfo) || - modelInfo.ModelType is null) + !_modelInfos.TryGetValue(alias, out ModelInfo? modelInfo) || modelInfo.ModelType is null) { return typeof(IPublishedElement); } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs index a953653b8b50..25cf64899b8e 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Cms.Core.Models.PublishedContent; diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs index 5ae3bd564124..7e69d0970d79 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Xml.Linq; using System.Xml.XPath; using Umbraco.Cms.Core.PropertyEditors; @@ -33,11 +33,13 @@ public class PublishedPropertyType : IPublishedPropertyType /// /// The new published property type belongs to the published content type. /// - public PublishedPropertyType(IPublishedContentType contentType, IPropertyType propertyType, - PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, + public PublishedPropertyType( + IPublishedContentType contentType, + IPropertyType propertyType, + PropertyValueConverterCollection propertyValueConverters, + IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) - : this(propertyType.Alias, propertyType.DataTypeId, true, propertyType.Variations, propertyValueConverters, - publishedModelFactory, factory) => + : this(propertyType.Alias, propertyType.DataTypeId, true, propertyType.Variations, propertyValueConverters, publishedModelFactory, factory) => ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); /// @@ -47,11 +49,16 @@ public PublishedPropertyType(IPublishedContentType contentType, IPropertyType pr /// Values are assumed to be consisted and are not checked. /// The new published property type belongs to the published content type. /// - public PublishedPropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, - bool isUserProperty, ContentVariation variations, PropertyValueConverterCollection propertyValueConverters, - IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) - : this(propertyTypeAlias, dataTypeId, isUserProperty, variations, propertyValueConverters, - publishedModelFactory, factory) => + public PublishedPropertyType( + IPublishedContentType contentType, + string propertyTypeAlias, + int dataTypeId, + bool isUserProperty, + ContentVariation variations, + PropertyValueConverterCollection propertyValueConverters, + IPublishedModelFactory publishedModelFactory, + IPublishedContentTypeFactory factory) + : this(propertyTypeAlias, dataTypeId, isUserProperty, variations, propertyValueConverters, publishedModelFactory, factory) => ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); /// @@ -61,9 +68,14 @@ public PublishedPropertyType(IPublishedContentType contentType, string propertyT /// Values are assumed to be consistent and are not checked. /// The new published property type does not belong to a published content type. /// - public PublishedPropertyType(string propertyTypeAlias, int dataTypeId, bool isUserProperty, - ContentVariation variations, PropertyValueConverterCollection propertyValueConverters, - IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) + public PublishedPropertyType( + string propertyTypeAlias, + int dataTypeId, + bool isUserProperty, + ContentVariation variations, + PropertyValueConverterCollection propertyValueConverters, + IPublishedModelFactory publishedModelFactory, + IPublishedContentTypeFactory factory) { _publishedModelFactory = publishedModelFactory ?? throw new ArgumentNullException(nameof(publishedModelFactory)); @@ -84,7 +96,8 @@ public PublishedPropertyType(string propertyTypeAlias, int dataTypeId, bool isUs /// public IPublishedContentType? - ContentType { get; internal set; } // internally set by PublishedContentType constructor + ContentType + { get; internal set; } // internally set by PublishedContentType constructor /// public PublishedDataType DataType { get; } @@ -101,6 +114,52 @@ public IPublishedContentType? /// public ContentVariation Variations { get; } + /// + public PropertyCacheLevel CacheLevel + { + get + { + if (!_initialized) + { + Initialize(); + } + + return _cacheLevel; + } + } + + /// + public Type ModelClrType + { + get + { + if (!_initialized) + { + Initialize(); + } + + return _modelClrType!; + } + } + + /// + public bool? IsValue(object? value, PropertyValueLevel level) + { + if (!_initialized) + { + Initialize(); + } + + // if we have a converter, use the converter + if (_converter != null) + { + return _converter.IsValue(value, level); + } + + // otherwise use the old magic null & string comparisons + return value != null && (!(value is string) || string.IsNullOrWhiteSpace((string)value) == false); + } + #endregion #region Converters @@ -164,8 +223,10 @@ private void InitializeLocked() "Type '{2}' cannot be an IPropertyValueConverter" + " for property '{1}' of content type '{0}' because type '{3}' has already been detected as a converter" + " for that property, and only one converter can exist for a property.", - ContentType?.Alias, Alias, - converter.GetType().FullName, _converter.GetType().FullName)); + ContentType?.Alias, + Alias, + converter.GetType().FullName, + _converter.GetType().FullName)); } } else @@ -184,11 +245,14 @@ private void InitializeLocked() else { // previous was non-default, and got another non-default - bad - throw new InvalidOperationException(string.Format("Type '{2}' cannot be an IPropertyValueConverter" + throw new InvalidOperationException(string.Format( + "Type '{2}' cannot be an IPropertyValueConverter" + " for property '{1}' of content type '{0}' because type '{3}' has already been detected as a converter" + " for that property, and only one converter can exist for a property.", - ContentType?.Alias, Alias, - converter.GetType().FullName, _converter.GetType().FullName)); + ContentType?.Alias, + Alias, + converter.GetType().FullName, + _converter.GetType().FullName)); } } } @@ -197,38 +261,6 @@ private void InitializeLocked() _modelClrType = _converter == null ? typeof(object) : _converter.GetPropertyValueType(this); } - /// - public bool? IsValue(object? value, PropertyValueLevel level) - { - if (!_initialized) - { - Initialize(); - } - - // if we have a converter, use the converter - if (_converter != null) - { - return _converter.IsValue(value, level); - } - - // otherwise use the old magic null & string comparisons - return value != null && (!(value is string) || string.IsNullOrWhiteSpace((string)value) == false); - } - - /// - public PropertyCacheLevel CacheLevel - { - get - { - if (!_initialized) - { - Initialize(); - } - - return _cacheLevel; - } - } - /// public object? ConvertSourceToInter(IPublishedElement owner, object? source, bool preview) { @@ -244,8 +276,7 @@ public PropertyCacheLevel CacheLevel } /// - public object? ConvertInterToObject(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, - bool preview) + public object? ConvertInterToObject(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) { if (!_initialized) { @@ -259,8 +290,7 @@ public PropertyCacheLevel CacheLevel } /// - public object? ConvertInterToXPath(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, - bool preview) + public object? ConvertInterToXPath(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) { if (!_initialized) { @@ -287,20 +317,6 @@ public PropertyCacheLevel CacheLevel return inter.ToString()?.Trim(); } - /// - public Type ModelClrType - { - get - { - if (!_initialized) - { - Initialize(); - } - - return _modelClrType!; - } - } - /// public Type? ClrType { diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedSearchResult.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedSearchResult.cs index d489b6678f8e..f0c2626f906c 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedSearchResult.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedSearchResult.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; namespace Umbraco.Cms.Core.Models.PublishedContent; @@ -12,5 +12,6 @@ public PublishedSearchResult(IPublishedContent content, float score) } public IPublishedContent Content { get; } + public float Score { get; } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs index 2f319e0e5d49..64f0160383e1 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedValueFallback.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Models.PublishedContent; @@ -21,13 +21,11 @@ public PublishedValueFallback(ServiceContext serviceContext, IVariationContextAc } /// - public bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, - object? defaultValue, out object? value) => + public bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, object? defaultValue, out object? value) => TryGetValue(property, culture, segment, fallback, defaultValue, out value); /// - public bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, - T? defaultValue, out T? value) + public bool TryGetValue(IPublishedProperty property, string? culture, string? segment, Fallback fallback, T? defaultValue, out T? value) { _variationContextAccessor.ContextualizeVariation(property.PropertyType.Variations, ref culture, ref segment); @@ -57,15 +55,13 @@ public bool TryGetValue(IPublishedProperty property, string? culture, string? } /// - public bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, - Fallback fallback, object? defaultValue, out object? value) => + public bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, Fallback fallback, object? defaultValue, out object? value) => TryGetValue(content, alias, culture, segment, fallback, defaultValue, out value); /// - public bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, - Fallback fallback, T? defaultValue, out T? value) + public bool TryGetValue(IPublishedElement content, string alias, string? culture, string? segment, Fallback fallback, T? defaultValue, out T? value) { - IPublishedPropertyType propertyType = content.ContentType.GetPropertyType(alias); + IPublishedPropertyType? propertyType = content.ContentType.GetPropertyType(alias); if (propertyType == null) { value = default; @@ -100,28 +96,24 @@ public bool TryGetValue(IPublishedElement content, string alias, string? cult } /// - public bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, - Fallback fallback, object? defaultValue, out object? value, out IPublishedProperty? noValueProperty) => + public bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, Fallback fallback, object? defaultValue, out object? value, out IPublishedProperty? noValueProperty) => TryGetValue(content, alias, culture, segment, fallback, defaultValue, out value, out noValueProperty); /// - public virtual bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, - Fallback fallback, T? defaultValue, out T? value, out IPublishedProperty? noValueProperty) + public virtual bool TryGetValue(IPublishedContent content, string alias, string? culture, string? segment, Fallback fallback, T? defaultValue, out T? value, out IPublishedProperty? noValueProperty) { noValueProperty = default; - IPublishedPropertyType propertyType = content.ContentType.GetPropertyType(alias); + IPublishedPropertyType? propertyType = content.ContentType.GetPropertyType(alias); if (propertyType != null) { - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, - ref segment); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment); noValueProperty = content.GetProperty(alias); } // note: we don't support "recurse & language" which would walk up the tree, // looking at languages at each level - should someone need it... they'll have // to implement it. - foreach (var f in fallback) { switch (f) @@ -144,8 +136,7 @@ public virtual bool TryGetValue(IPublishedContent content, string alias, stri break; case Fallback.Ancestors: - if (TryGetValueWithAncestorsFallback(content, alias, culture, segment, out value, - ref noValueProperty)) + if (TryGetValueWithAncestorsFallback(content, alias, culture, segment, out value, ref noValueProperty)) { return true; } @@ -167,22 +158,20 @@ private NotSupportedException NotSupportedFallbackMethod(int fallback, string le // tries to get a value, recursing the tree // because we recurse, content may not even have the a property with the specified alias (but only some ancestor) // in case no value was found, noValueProperty contains the first property that was found (which does not have a value) - private bool TryGetValueWithAncestorsFallback(IPublishedContent? content, string alias, string? culture, - string? segment, out T? value, ref IPublishedProperty? noValueProperty) + private bool TryGetValueWithAncestorsFallback(IPublishedContent? content, string alias, string? culture, string? segment, out T? value, ref IPublishedProperty? noValueProperty) { IPublishedProperty? property; // if we are here, content's property has no value do { content = content?.Parent; - IPublishedPropertyType propertyType = content?.ContentType.GetPropertyType(alias); + IPublishedPropertyType? propertyType = content?.ContentType.GetPropertyType(alias); if (propertyType != null && content is not null) { culture = null; segment = null; - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, - ref segment); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment); } property = content?.GetProperty(alias); @@ -190,7 +179,8 @@ private bool TryGetValueWithAncestorsFallback(IPublishedContent? content, str { noValueProperty = property; } - } while (content != null && (property == null || property.HasValue(culture, segment) == false)); + } + while (content != null && (property == null || property.HasValue(culture, segment) == false)); // if we found a content with the property having a value, return that property value if (property != null && property.HasValue(culture, segment)) @@ -204,8 +194,7 @@ private bool TryGetValueWithAncestorsFallback(IPublishedContent? content, str } // tries to get a value, falling back onto other languages - private bool TryGetValueWithLanguageFallback(IPublishedProperty property, string? culture, string? segment, - out T? value) + private bool TryGetValueWithLanguageFallback(IPublishedProperty property, string? culture, string? segment, out T? value) { value = default; @@ -216,7 +205,7 @@ private bool TryGetValueWithLanguageFallback(IPublishedProperty property, str var visited = new HashSet(); - ILanguage language = culture is not null ? _localizationService?.GetLanguageByIsoCode(culture) : null; + ILanguage? language = culture is not null ? _localizationService?.GetLanguageByIsoCode(culture) : null; if (language == null) { return false; @@ -237,7 +226,7 @@ private bool TryGetValueWithLanguageFallback(IPublishedProperty property, str visited.Add(language2Id); - ILanguage language2 = _localizationService?.GetLanguageById(language2Id); + ILanguage? language2 = _localizationService?.GetLanguageById(language2Id); if (language2 == null) { return false; @@ -256,8 +245,7 @@ private bool TryGetValueWithLanguageFallback(IPublishedProperty property, str } // tries to get a value, falling back onto other languages - private bool TryGetValueWithLanguageFallback(IPublishedElement content, string alias, string? culture, - string? segment, out T? value) + private bool TryGetValueWithLanguageFallback(IPublishedElement content, string alias, string? culture, string? segment, out T? value) { value = default; @@ -268,7 +256,7 @@ private bool TryGetValueWithLanguageFallback(IPublishedElement content, strin var visited = new HashSet(); - ILanguage language = culture is not null ? _localizationService?.GetLanguageByIsoCode(culture) : null; + ILanguage? language = culture is not null ? _localizationService?.GetLanguageByIsoCode(culture) : null; if (language == null) { return false; @@ -289,7 +277,7 @@ private bool TryGetValueWithLanguageFallback(IPublishedElement content, strin visited.Add(language2Id); - ILanguage language2 = _localizationService?.GetLanguageById(language2Id); + ILanguage? language2 = _localizationService?.GetLanguageById(language2Id); if (language2 == null) { return false; @@ -308,8 +296,7 @@ private bool TryGetValueWithLanguageFallback(IPublishedElement content, strin } // tries to get a value, falling back onto other languages - private bool TryGetValueWithLanguageFallback(IPublishedContent content, string alias, string? culture, - string? segment, out T? value) + private bool TryGetValueWithLanguageFallback(IPublishedContent content, string alias, string? culture, string? segment, out T? value) { value = default; @@ -322,8 +309,7 @@ private bool TryGetValueWithLanguageFallback(IPublishedContent content, strin // TODO: _localizationService.GetXxx() is expensive, it deep clones objects // we want _localizationService.GetReadOnlyXxx() returning IReadOnlyLanguage which cannot be saved back = no need to clone - - ILanguage language = culture is not null ? _localizationService?.GetLanguageByIsoCode(culture) : null; + ILanguage? language = culture is not null ? _localizationService?.GetLanguageByIsoCode(culture) : null; if (language == null) { return false; @@ -344,7 +330,7 @@ private bool TryGetValueWithLanguageFallback(IPublishedContent content, strin visited.Add(language2Id); - ILanguage language2 = _localizationService?.GetLanguageById(language2Id); + ILanguage? language2 = _localizationService?.GetLanguageById(language2Id); if (language2 == null) { return false; diff --git a/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs index 4de6d3a4d0a3..763006f8f1a7 100644 --- a/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs +++ b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Cms.Core.Models.PublishedContent; @@ -20,11 +20,10 @@ namespace Umbraco.Cms.Core.Models.PublishedContent; public class RawValueProperty : PublishedPropertyBase { private readonly Lazy _objectValue; - private readonly object _sourceValue; //the value in the db + private readonly object _sourceValue; // the value in the db private readonly Lazy _xpathValue; - public RawValueProperty(IPublishedPropertyType propertyType, IPublishedElement content, object sourceValue, - bool isPreviewing = false) + public RawValueProperty(IPublishedPropertyType propertyType, IPublishedElement content, object sourceValue, bool isPreviewing = false) : base(propertyType, PropertyCacheLevel.Unknown) // cache level is ignored { if (propertyType.Variations != ContentVariation.Nothing) @@ -44,7 +43,6 @@ public RawValueProperty(IPublishedPropertyType propertyType, IPublishedElement c // RawValueProperty does not (yet?) support variants, // only manages the current "default" value - public override object? GetSourceValue(string? culture = null, string? segment = null) => string.IsNullOrEmpty(culture) & string.IsNullOrEmpty(segment) ? _sourceValue : null; diff --git a/src/Umbraco.Core/Models/PublishedContent/ThreadCultureVariationContextAccessor.cs b/src/Umbraco.Core/Models/PublishedContent/ThreadCultureVariationContextAccessor.cs index 130f7c62b39a..591937079262 100644 --- a/src/Umbraco.Core/Models/PublishedContent/ThreadCultureVariationContextAccessor.cs +++ b/src/Umbraco.Core/Models/PublishedContent/ThreadCultureVariationContextAccessor.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; namespace Umbraco.Cms.Core.Models.PublishedContent; diff --git a/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs b/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs index f25ce4bfe1ba..ff13964fb3e5 100644 --- a/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs +++ b/src/Umbraco.Core/Models/PublishedContent/UrlMode.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent; +namespace Umbraco.Cms.Core.Models.PublishedContent; /// /// Specifies the type of URLs that the URL provider should produce, Auto is the default. @@ -23,5 +23,5 @@ public enum UrlMode /// /// Indicates that the URL provider should determine automatically whether to return relative or absolute URLs. /// - Auto + Auto, } diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs index 3ca99e1e3ae0..92326ae35955 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.PublishedContent; +namespace Umbraco.Cms.Core.Models.PublishedContent; /// /// Represents the variation context. @@ -10,8 +10,8 @@ public class VariationContext /// public VariationContext(string? culture = null, string? segment = null) { - Culture = culture ?? ""; // cannot be null, default to invariant - Segment = segment ?? ""; // cannot be null, default to neutral + Culture = culture ?? string.Empty; // cannot be null, default to invariant + Segment = segment ?? string.Empty; // cannot be null, default to neutral } /// diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs index dec81b10a750..2d501b569036 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Models; @@ -8,15 +8,18 @@ namespace Umbraco.Extensions; public static class VariationContextAccessorExtensions { - public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, + public static void ContextualizeVariation( + this IVariationContextAccessor variationContextAccessor, ContentVariation variations, ref string? culture, ref string? segment) => variationContextAccessor.ContextualizeVariation(variations, null, ref culture, ref segment); - public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, + public static void ContextualizeVariation( + this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int contentId, ref string? culture, ref string? segment) => variationContextAccessor.ContextualizeVariation(variations, (int?)contentId, ref culture, ref segment); - private static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, + private static void ContextualizeVariation( + this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int? contentId, ref string? culture, ref string? segment) { if (culture != null && segment != null) @@ -25,10 +28,10 @@ private static void ContextualizeVariation(this IVariationContextAccessor variat } // use context values - VariationContext publishedVariationContext = variationContextAccessor?.VariationContext; + VariationContext? publishedVariationContext = variationContextAccessor?.VariationContext; if (culture == null) { - culture = variations.VariesByCulture() ? publishedVariationContext?.Culture : ""; + culture = variations.VariesByCulture() ? publishedVariationContext?.Culture : string.Empty; } if (segment == null) @@ -41,7 +44,7 @@ private static void ContextualizeVariation(this IVariationContextAccessor variat } else { - segment = ""; + segment = string.Empty; } } } diff --git a/src/Umbraco.Core/Models/PublishedState.cs b/src/Umbraco.Core/Models/PublishedState.cs index 33668b57035e..39d68ea27300 100644 --- a/src/Umbraco.Core/Models/PublishedState.cs +++ b/src/Umbraco.Core/Models/PublishedState.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; /// /// The states of a content item. @@ -31,12 +31,14 @@ public enum PublishedState /// The content item is published. /// Published, + // also: handled over to repo to save draft values for a published content /// /// The content item is not published. /// Unpublished, + // also: handled over to repo to save draft values for an unpublished content // when it is handled over to the repository, its state can also be one of those: @@ -65,5 +67,5 @@ public enum PublishedState /// Unpublished /// . /// - Unpublishing + Unpublishing, } diff --git a/src/Umbraco.Core/Models/Range.cs b/src/Umbraco.Core/Models/Range.cs index 7cf035c7c3fd..78d49ad8514a 100644 --- a/src/Umbraco.Core/Models/Range.cs +++ b/src/Umbraco.Core/Models/Range.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; namespace Umbraco.Cms.Core.Models; @@ -37,15 +37,15 @@ public class Range : IEquatable> public bool Equals(Range? other) => other != null && Equals(other.Minimum, other.Maximum); /// - /// Returns a that represents this instance. + /// Returns a that represents this instance. /// /// - /// A that represents this instance. + /// A that represents this instance. /// public override string ToString() => ToString("{0},{1}", CultureInfo.InvariantCulture); /// - /// Returns a that represents this instance. + /// Returns a that represents this instance. /// /// /// A composite format string for a single value (minimum and maximum are equal). Use {0} for the @@ -57,13 +57,13 @@ public class Range : IEquatable> /// /// An object that supplies culture-specific formatting information. /// - /// A that represents this instance. + /// A that represents this instance. /// public string ToString(string format, string formatRange, IFormatProvider? provider = null) => ToString(Minimum?.CompareTo(Maximum) == 0 ? format : formatRange, provider); /// - /// Returns a that represents this instance. + /// Returns a that represents this instance. /// /// /// A composite format string for the range values. Use {0} for the minimum and {1} for the maximum @@ -71,7 +71,7 @@ public string ToString(string format, string formatRange, IFormatProvider? provi /// /// An object that supplies culture-specific formatting information. /// - /// A that represents this instance. + /// A that represents this instance. /// public string ToString(string format, IFormatProvider? provider = null) => string.Format(provider, format, Minimum, Maximum); @@ -114,11 +114,11 @@ public bool ContainsRange(Range range) => IsValid() && range.IsValid() && ContainsValue(range.Minimum) && ContainsValue(range.Maximum); /// - /// Determines whether the specified , is equal to this instance. + /// Determines whether the specified , is equal to this instance. /// - /// The to compare with this instance. + /// The to compare with this instance. /// - /// true if the specified is equal to this instance; otherwise, false. + /// true if the specified is equal to this instance; otherwise, false. /// public override bool Equals(object? obj) => obj is Range other && Equals(other); diff --git a/src/Umbraco.Core/Models/ReadOnlyContentBaseAdapter.cs b/src/Umbraco.Core/Models/ReadOnlyContentBaseAdapter.cs index 795bf517d839..77b6253178d7 100644 --- a/src/Umbraco.Core/Models/ReadOnlyContentBaseAdapter.cs +++ b/src/Umbraco.Core/Models/ReadOnlyContentBaseAdapter.cs @@ -7,10 +7,10 @@ public struct ReadOnlyContentBaseAdapter : IReadOnlyContentBase private ReadOnlyContentBaseAdapter(IContentBase content) => _content = content ?? throw new ArgumentNullException(nameof(content)); - public static ReadOnlyContentBaseAdapter Create(IContentBase content) => new(content); - public int Id => _content.Id; + public static ReadOnlyContentBaseAdapter Create(IContentBase content) => new(content); + public Guid Key => _content.Key; public DateTime CreateDate => _content.CreateDate; diff --git a/src/Umbraco.Core/Models/ReadOnlyRelation.cs b/src/Umbraco.Core/Models/ReadOnlyRelation.cs index 765a84d265c2..7b0413453fb1 100644 --- a/src/Umbraco.Core/Models/ReadOnlyRelation.cs +++ b/src/Umbraco.Core/Models/ReadOnlyRelation.cs @@ -16,16 +16,22 @@ public ReadOnlyRelation(int id, int parentId, int childId, int relationTypeId, D Comment = comment; } - public ReadOnlyRelation(int parentId, int childId, int relationTypeId) : this(0, parentId, childId, relationTypeId, + public ReadOnlyRelation(int parentId, int childId, int relationTypeId) + : this(0, parentId, childId, relationTypeId, DateTime.Now, string.Empty) { } public int Id { get; } + public int ParentId { get; } + public int ChildId { get; } + public int RelationTypeId { get; } + public DateTime CreateDate { get; } + public string Comment { get; } public bool HasIdentity => Id != 0; diff --git a/src/Umbraco.Core/Models/RedirectUrl.cs b/src/Umbraco.Core/Models/RedirectUrl.cs index 496f9e85b070..ed0cde70bd55 100644 --- a/src/Umbraco.Core/Models/RedirectUrl.cs +++ b/src/Umbraco.Core/Models/RedirectUrl.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/Relation.cs b/src/Umbraco.Core/Models/Relation.cs index a7eee839e8e3..c495ed39fb4c 100644 --- a/src/Umbraco.Core/Models/Relation.cs +++ b/src/Umbraco.Core/Models/Relation.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; @@ -14,7 +14,7 @@ public class Relation : EntityBase, IRelation private string? _comment; - //NOTE: The datetime column from umbracoRelation is set on CreateDate on the Entity + // NOTE: The datetime column from umbracoRelation is set on CreateDate on the Entity private int _parentId; private IRelationType _relationType; @@ -48,7 +48,6 @@ public Relation(int parentId, int childId, Guid parentObjectType, Guid childObje ChildObjectType = childObjectType; } - /// /// Gets or sets the Parent Id of the Relation (Source) /// @@ -59,7 +58,8 @@ public int ParentId set => SetPropertyValueAndDetectChanges(value, ref _parentId, nameof(ParentId)); } - [DataMember] public Guid ParentObjectType { get; set; } + [DataMember] + public Guid ParentObjectType { get; set; } /// /// Gets or sets the Child Id of the Relation (Destination) @@ -71,7 +71,8 @@ public int ChildId set => SetPropertyValueAndDetectChanges(value, ref _childId, nameof(ChildId)); } - [DataMember] public Guid ChildObjectType { get; set; } + [DataMember] + public Guid ChildObjectType { get; set; } /// /// Gets or sets the for the Relation diff --git a/src/Umbraco.Core/Models/RelationItem.cs b/src/Umbraco.Core/Models/RelationItem.cs index d2782afd7d8b..409776b7e39f 100644 --- a/src/Umbraco.Core/Models/RelationItem.cs +++ b/src/Umbraco.Core/Models/RelationItem.cs @@ -1,25 +1,33 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; [DataContract(Name = "relationItem", Namespace = "")] public class RelationItem { - [DataMember(Name = "id")] public int NodeId { get; set; } + [DataMember(Name = "id")] + public int NodeId { get; set; } - [DataMember(Name = "key")] public Guid NodeKey { get; set; } + [DataMember(Name = "key")] + public Guid NodeKey { get; set; } - [DataMember(Name = "name")] public string? NodeName { get; set; } + [DataMember(Name = "name")] + public string? NodeName { get; set; } - [DataMember(Name = "type")] public string? NodeType { get; set; } + [DataMember(Name = "type")] + public string? NodeType { get; set; } - [DataMember(Name = "udi")] public Udi NodeUdi => Udi.Create(NodeType, NodeKey); + [DataMember(Name = "udi")] + public Udi NodeUdi => Udi.Create(NodeType, NodeKey); - [DataMember(Name = "icon")] public string? ContentTypeIcon { get; set; } + [DataMember(Name = "icon")] + public string? ContentTypeIcon { get; set; } - [DataMember(Name = "alias")] public string? ContentTypeAlias { get; set; } + [DataMember(Name = "alias")] + public string? ContentTypeAlias { get; set; } - [DataMember(Name = "contentTypeName")] public string? ContentTypeName { get; set; } + [DataMember(Name = "contentTypeName")] + public string? ContentTypeName { get; set; } [DataMember(Name = "relationTypeName")] public string? RelationTypeName { get; set; } diff --git a/src/Umbraco.Core/Models/RelationType.cs b/src/Umbraco.Core/Models/RelationType.cs index b34d507dcb9a..d48e802c6eaa 100644 --- a/src/Umbraco.Core/Models/RelationType.cs +++ b/src/Umbraco.Core/Models/RelationType.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; @@ -8,7 +8,7 @@ namespace Umbraco.Cms.Core.Models; /// [Serializable] [DataContract(IsReference = true)] -public class RelationType : EntityBase, IRelationType, IRelationTypeWithIsDependency +public class RelationType : EntityBase, IRelationTypeWithIsDependency { private string _alias; private Guid? _childObjectType; @@ -28,8 +28,7 @@ public RelationType(string name, string alias, bool isBidrectional, Guid? parent { } - public RelationType(string? name, string? alias, bool isBidrectional, Guid? parentObjectType, Guid? childObjectType, - bool isDependency) + public RelationType(string? name, string? alias, bool isBidrectional, Guid? parentObjectType, Guid? childObjectType, bool isDependency) { if (name == null) { @@ -38,7 +37,8 @@ public RelationType(string? name, string? alias, bool isBidrectional, Guid? pare if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(name)); } @@ -49,7 +49,8 @@ public RelationType(string? name, string? alias, bool isBidrectional, Guid? pare if (string.IsNullOrWhiteSpace(alias)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(alias)); } @@ -113,7 +114,6 @@ public Guid? ChildObjectType set => SetPropertyValueAndDetectChanges(value, ref _childObjectType, nameof(ChildObjectType)); } - public bool IsDependency { get => _isDependency; diff --git a/src/Umbraco.Core/Models/RelationTypeExtensions.cs b/src/Umbraco.Core/Models/RelationTypeExtensions.cs index 94ca81f3eebe..b5803d3fb356 100644 --- a/src/Umbraco.Core/Models/RelationTypeExtensions.cs +++ b/src/Umbraco.Core/Models/RelationTypeExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core; diff --git a/src/Umbraco.Core/Models/RequestPasswordResetModel.cs b/src/Umbraco.Core/Models/RequestPasswordResetModel.cs index 973f183323b3..9b4932f88a36 100644 --- a/src/Umbraco.Core/Models/RequestPasswordResetModel.cs +++ b/src/Umbraco.Core/Models/RequestPasswordResetModel.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/Script.cs b/src/Umbraco.Core/Models/Script.cs index bb0fc10a33ba..03888bd27a42 100644 --- a/src/Umbraco.Core/Models/Script.cs +++ b/src/Umbraco.Core/Models/Script.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/SendCodeViewModel.cs b/src/Umbraco.Core/Models/SendCodeViewModel.cs index ef90eace8aeb..c73fd73eb3e7 100644 --- a/src/Umbraco.Core/Models/SendCodeViewModel.cs +++ b/src/Umbraco.Core/Models/SendCodeViewModel.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/ServerRegistration.cs b/src/Umbraco.Core/Models/ServerRegistration.cs index a6970ffee097..6507d5d64c34 100644 --- a/src/Umbraco.Core/Models/ServerRegistration.cs +++ b/src/Umbraco.Core/Models/ServerRegistration.cs @@ -30,9 +30,8 @@ public ServerRegistration() /// The date and time the registration was created. /// The date and time the registration was last accessed. /// A value indicating whether the registration is active. - /// A value indicating whether the registration is master. - public ServerRegistration(int id, string? serverAddress, string? serverIdentity, DateTime registered, - DateTime accessed, bool isActive, bool isSchedulingPublisher) + /// A value indicating whether the registration is scheduling publisher. + public ServerRegistration(int id, string? serverAddress, string? serverIdentity, DateTime registered, DateTime accessed, bool isActive, bool isSchedulingPublisher) { UpdateDate = accessed; CreateDate = registered; @@ -117,6 +116,5 @@ public DateTime Accessed /// Converts the value of this instance to its equivalent string representation. /// /// - public override string ToString() => string.Format("{{\"{0}\", \"{1}\", {2}active, {3}master}}", ServerAddress, - ServerIdentity, IsActive ? "" : "!", IsSchedulingPublisher ? "" : "!"); + public override string ToString() => string.Format("{{\"{0}\", \"{1}\", {2}active, {3}master}}", ServerAddress, ServerIdentity, IsActive ? string.Empty : "!", IsSchedulingPublisher ? string.Empty : "!"); } diff --git a/src/Umbraco.Core/Models/SetPasswordModel.cs b/src/Umbraco.Core/Models/SetPasswordModel.cs index 790c90b41ae3..57d1abc38f94 100644 --- a/src/Umbraco.Core/Models/SetPasswordModel.cs +++ b/src/Umbraco.Core/Models/SetPasswordModel.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/SimpleContentType.cs b/src/Umbraco.Core/Models/SimpleContentType.cs index 9476ce7f8dc5..7fe88a8a8ae9 100644 --- a/src/Umbraco.Core/Models/SimpleContentType.cs +++ b/src/Umbraco.Core/Models/SimpleContentType.cs @@ -1,4 +1,4 @@ -using Umbraco.Extensions; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.Models; @@ -70,13 +70,12 @@ private SimpleContentType(IContentTypeBase contentType) public bool IsElement { get; } public bool SupportsPropertyVariation(string? culture, string segment, bool wildcards = false) => + // non-exact validation: can accept a 'null' culture if the property type varies // by culture, and likewise for segment // wildcard validation: can accept a '*' culture or segment Variations.ValidateVariation(culture, segment, false, wildcards, false); - protected bool Equals(SimpleContentType other) => string.Equals(Alias, other.Alias) && Id == other.Id; - public override bool Equals(object? obj) { if (ReferenceEquals(null, obj)) @@ -97,6 +96,8 @@ public override bool Equals(object? obj) return Equals((SimpleContentType)obj); } + protected bool Equals(SimpleContentType other) => string.Equals(Alias, other.Alias) && Id == other.Id; + public override int GetHashCode() { unchecked diff --git a/src/Umbraco.Core/Models/SimpleValidationModel.cs b/src/Umbraco.Core/Models/SimpleValidationModel.cs index 5e7ecbb176ac..390fe5a31cfa 100644 --- a/src/Umbraco.Core/Models/SimpleValidationModel.cs +++ b/src/Umbraco.Core/Models/SimpleValidationModel.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; public class SimpleValidationModel { @@ -9,5 +9,6 @@ public SimpleValidationModel(IDictionary modelState, string mess } public string Message { get; } + public IDictionary ModelState { get; } } diff --git a/src/Umbraco.Core/Models/Stylesheet.cs b/src/Umbraco.Core/Models/Stylesheet.cs index e9dbc27f5c62..07f35c88e317 100644 --- a/src/Umbraco.Core/Models/Stylesheet.cs +++ b/src/Umbraco.Core/Models/Stylesheet.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Data; using System.Runtime.Serialization; using Umbraco.Cms.Core.Strings.Css; @@ -33,7 +33,8 @@ public override string? Content set { base.Content = value; - //re-set the properties so they are re-read from the content + + // re-set the properties so they are re-read from the content InitializeProperties(); } } @@ -48,6 +49,14 @@ public override string? Content [IgnoreDataMember] public IEnumerable? Properties => _properties?.Value; + /// + /// Indicates whether the current entity has an identity, which in this case is a path/name. + /// + /// + /// Overrides the default Entity identity check. + /// + public override bool HasIdentity => string.IsNullOrEmpty(Path) == false; + /// /// Adds an Umbraco stylesheet property for use in the back office /// @@ -60,11 +69,12 @@ public void AddProperty(IStylesheetProperty property) " already exists in the collection"); } - //now we need to serialize out the new property collection over-top of the string Content. - Content = StylesheetHelper.AppendRule(Content, - new StylesheetRule {Name = property.Name, Selector = property.Alias, Styles = property.Value}); + // now we need to serialize out the new property collection over-top of the string Content. + Content = StylesheetHelper.AppendRule( + Content, + new StylesheetRule { Name = property.Name, Selector = property.Alias, Styles = property.Value }); - //re-set lazy collection + // re-set lazy collection InitializeProperties(); } @@ -80,37 +90,32 @@ public void RemoveProperty(string name) } } - /// - /// Indicates whether the current entity has an identity, which in this case is a path/name. - /// - /// - /// Overrides the default Entity identity check. - /// - public override bool HasIdentity => string.IsNullOrEmpty(Path) == false; - private void InitializeProperties() { - //if the value is already created, we need to be created and update the collection according to - //what is now in the content + // if the value is already created, we need to be created and update the collection according to + // what is now in the content if (_properties != null && _properties.IsValueCreated) { - //re-parse it so we can check what properties are different and adjust the event handlers + // re-parse it so we can check what properties are different and adjust the event handlers StylesheetRule[] parsed = StylesheetHelper.ParseRules(Content).ToArray(); var names = parsed.Select(x => x.Name).ToArray(); StylesheetProperty[] existing = _properties.Value.Where(x => names.InvariantContains(x.Name)).ToArray(); - //update existing + + // update existing foreach (StylesheetProperty stylesheetProperty in existing) { StylesheetRule updateFrom = parsed.Single(x => x.Name.InvariantEquals(stylesheetProperty.Name)); - //remove current event handler while we update, we'll reset it after + + // remove current event handler while we update, we'll reset it after stylesheetProperty.PropertyChanged -= Property_PropertyChanged; stylesheetProperty.Alias = updateFrom.Selector; stylesheetProperty.Value = updateFrom.Styles; - //re-add + + // re-add stylesheetProperty.PropertyChanged += Property_PropertyChanged; } - //remove no longer existing + // remove no longer existing StylesheetProperty[] nonExisting = _properties.Value.Where(x => names.InvariantContains(x.Name) == false).ToArray(); foreach (StylesheetProperty stylesheetProperty in nonExisting) @@ -119,7 +124,7 @@ private void InitializeProperties() _properties.Value.Remove(stylesheetProperty); } - //add new ones + // add new ones IEnumerable newItems = parsed.Where(x => _properties.Value.Select(p => p.Name).InvariantContains(x.Name) == false); foreach (StylesheetRule stylesheetRule in newItems) @@ -130,7 +135,7 @@ private void InitializeProperties() } } - //we haven't read the properties yet so create the lazy delegate + // we haven't read the properties yet so create the lazy delegate _properties = new Lazy>(() => { IEnumerable parsed = StylesheetHelper.ParseRules(Content); @@ -154,9 +159,8 @@ private void Property_PropertyChanged(object? sender, PropertyChangedEventArgs e if (prop is not null) { - //Ensure we are setting base.Content here so that the properties don't get reset and thus any event handlers would get reset too - base.Content = StylesheetHelper.ReplaceRule(Content, prop.Name, - new StylesheetRule {Name = prop.Name, Selector = prop.Alias, Styles = prop.Value}); + // Ensure we are setting base.Content here so that the properties don't get reset and thus any event handlers would get reset too + base.Content = StylesheetHelper.ReplaceRule(Content, prop.Name, new StylesheetRule { Name = prop.Name, Selector = prop.Alias, Styles = prop.Value }); } } } diff --git a/src/Umbraco.Core/Models/StylesheetProperty.cs b/src/Umbraco.Core/Models/StylesheetProperty.cs index 1f90572d4adf..730ff8ff3e20 100644 --- a/src/Umbraco.Core/Models/StylesheetProperty.cs +++ b/src/Umbraco.Core/Models/StylesheetProperty.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/Tag.cs b/src/Umbraco.Core/Models/Tag.cs index d59492262a19..1c4bf4b88cd4 100644 --- a/src/Umbraco.Core/Models/Tag.cs +++ b/src/Umbraco.Core/Models/Tag.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/TagModel.cs b/src/Umbraco.Core/Models/TagModel.cs index 57ea5d04fa63..2646b216e388 100644 --- a/src/Umbraco.Core/Models/TagModel.cs +++ b/src/Umbraco.Core/Models/TagModel.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; @@ -11,7 +11,9 @@ public class TagModel [DataMember(Name = "text", IsRequired = true)] public string? Text { get; set; } - [DataMember(Name = "group")] public string? Group { get; set; } + [DataMember(Name = "group")] + public string? Group { get; set; } - [DataMember(Name = "nodeCount")] public int NodeCount { get; set; } + [DataMember(Name = "nodeCount")] + public int NodeCount { get; set; } } diff --git a/src/Umbraco.Core/Models/TaggableObjectTypes.cs b/src/Umbraco.Core/Models/TaggableObjectTypes.cs index d06f2d9829d2..03be2273a2f4 100644 --- a/src/Umbraco.Core/Models/TaggableObjectTypes.cs +++ b/src/Umbraco.Core/Models/TaggableObjectTypes.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; /// /// Enum representing the taggable object types @@ -8,5 +8,5 @@ public enum TaggableObjectTypes All, Content, Media, - Member + Member, } diff --git a/src/Umbraco.Core/Models/TaggedEntity.cs b/src/Umbraco.Core/Models/TaggedEntity.cs index 39658769e363..821f592343c2 100644 --- a/src/Umbraco.Core/Models/TaggedEntity.cs +++ b/src/Umbraco.Core/Models/TaggedEntity.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; /// /// Represents a tagged entity. diff --git a/src/Umbraco.Core/Models/TaggedProperty.cs b/src/Umbraco.Core/Models/TaggedProperty.cs index 70bf33abbc6a..90257a1a3e13 100644 --- a/src/Umbraco.Core/Models/TaggedProperty.cs +++ b/src/Umbraco.Core/Models/TaggedProperty.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; /// /// Represents a tagged property on an entity. diff --git a/src/Umbraco.Core/Models/TagsStorageType.cs b/src/Umbraco.Core/Models/TagsStorageType.cs index 58a12640a753..ccff41bb72ec 100644 --- a/src/Umbraco.Core/Models/TagsStorageType.cs +++ b/src/Umbraco.Core/Models/TagsStorageType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; /// /// Defines how tags are stored. @@ -17,5 +17,5 @@ public enum TagsStorageType /// /// Store tags as serialized Json. /// - Json + Json, } diff --git a/src/Umbraco.Core/Models/TelemetryLevel.cs b/src/Umbraco.Core/Models/TelemetryLevel.cs index e5ab213067c9..cdf1d24e90d1 100644 --- a/src/Umbraco.Core/Models/TelemetryLevel.cs +++ b/src/Umbraco.Core/Models/TelemetryLevel.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; @@ -7,5 +7,5 @@ public enum TelemetryLevel { Minimal, Basic, - Detailed + Detailed, } diff --git a/src/Umbraco.Core/Models/TelemetryResource.cs b/src/Umbraco.Core/Models/TelemetryResource.cs index d783fe500f74..1c6284238190 100644 --- a/src/Umbraco.Core/Models/TelemetryResource.cs +++ b/src/Umbraco.Core/Models/TelemetryResource.cs @@ -1,9 +1,10 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; [DataContract] public class TelemetryResource { - [DataMember] public TelemetryLevel TelemetryLevel { get; set; } + [DataMember] + public TelemetryLevel TelemetryLevel { get; set; } } diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index 4331711461e7..1900233aa989 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; @@ -22,8 +22,7 @@ public Template(IShortStringHelper shortStringHelper, string? name, string? alia { } - public Template(IShortStringHelper shortStringHelper, string? name, string? alias, - Func? getFileContent) + public Template(IShortStringHelper shortStringHelper, string? name, string? alias, Func? getFileContent) : base(string.Empty, getFileContent) { _shortStringHelper = shortStringHelper; diff --git a/src/Umbraco.Core/Models/TemplateNode.cs b/src/Umbraco.Core/Models/TemplateNode.cs index c973f17b00ba..f02988e6d2b8 100644 --- a/src/Umbraco.Core/Models/TemplateNode.cs +++ b/src/Umbraco.Core/Models/TemplateNode.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models; +namespace Umbraco.Cms.Core.Models; /// /// Represents a template in a template tree diff --git a/src/Umbraco.Core/Models/TemplateOnDisk.cs b/src/Umbraco.Core/Models/TemplateOnDisk.cs index 9115ca963219..04fffb7c10ea 100644 --- a/src/Umbraco.Core/Models/TemplateOnDisk.cs +++ b/src/Umbraco.Core/Models/TemplateOnDisk.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Strings; namespace Umbraco.Cms.Core.Models; @@ -15,6 +15,7 @@ public class TemplateOnDisk : Template /// /// The name of the template. /// The alias of the template. + /// The short string helper public TemplateOnDisk(IShortStringHelper shortStringHelper, string name, string alias) : base(shortStringHelper, name, alias) => IsOnDisk = true; diff --git a/src/Umbraco.Core/Models/TemplateQuery/Operator.cs b/src/Umbraco.Core/Models/TemplateQuery/Operator.cs index 86100e972106..c76202fb68a7 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/Operator.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/Operator.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.TemplateQuery; +namespace Umbraco.Cms.Core.Models.TemplateQuery; public enum Operator { @@ -9,5 +9,5 @@ public enum Operator LessThan = 5, LessThanEqualTo = 6, GreaterThan = 7, - GreaterThanEqualTo = 8 + GreaterThanEqualTo = 8, } diff --git a/src/Umbraco.Core/Models/TemplateQuery/OperatorTerm.cs b/src/Umbraco.Core/Models/TemplateQuery/OperatorTerm.cs index d1f1a104062a..d2a8c8e0dbbd 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/OperatorTerm.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/OperatorTerm.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.TemplateQuery; +namespace Umbraco.Cms.Core.Models.TemplateQuery; public class OperatorTerm { @@ -6,7 +6,7 @@ public OperatorTerm() { Name = "is"; Operator = Operator.Equals; - AppliesTo = new[] {"string"}; + AppliesTo = new[] { "string" }; } public OperatorTerm(string name, Operator @operator, IEnumerable appliesTo) @@ -17,6 +17,8 @@ public OperatorTerm(string name, Operator @operator, IEnumerable applies } public string Name { get; set; } + public Operator Operator { get; set; } + public IEnumerable AppliesTo { get; set; } } diff --git a/src/Umbraco.Core/Models/TemplateQuery/PropertyModel.cs b/src/Umbraco.Core/Models/TemplateQuery/PropertyModel.cs index bd117534f964..39ea100e7d45 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/PropertyModel.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/PropertyModel.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.TemplateQuery; +namespace Umbraco.Cms.Core.Models.TemplateQuery; public class PropertyModel { diff --git a/src/Umbraco.Core/Models/TemplateQuery/QueryCondition.cs b/src/Umbraco.Core/Models/TemplateQuery/QueryCondition.cs index 9ab5919d32ef..2c64f13876ce 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/QueryCondition.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/QueryCondition.cs @@ -1,8 +1,10 @@ -namespace Umbraco.Cms.Core.Models.TemplateQuery; +namespace Umbraco.Cms.Core.Models.TemplateQuery; public class QueryCondition { public PropertyModel Property { get; set; } = new(); + public OperatorTerm Term { get; set; } = new(); + public string ConstraintValue { get; set; } = string.Empty; } diff --git a/src/Umbraco.Core/Models/TemplateQuery/QueryConditionExtensions.cs b/src/Umbraco.Core/Models/TemplateQuery/QueryConditionExtensions.cs index ac12061760c0..0722422aaef8 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/QueryConditionExtensions.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/QueryConditionExtensions.cs @@ -1,4 +1,4 @@ -using System.Linq.Expressions; +using System.Linq.Expressions; using System.Reflection; using Umbraco.Cms.Core.Models.TemplateQuery; @@ -7,7 +7,7 @@ namespace Umbraco.Extensions; public static class QueryConditionExtensions { private static Lazy StringContainsMethodInfo => - new(() => typeof(string).GetMethod("Contains", new[] {typeof(string)})!); + new(() => typeof(string).GetMethod("Contains", new[] { typeof(string) })!); public static Expression> BuildCondition(this QueryCondition condition, string parameterAlias) { @@ -51,11 +51,11 @@ public static Expression> BuildCondition(this QueryCondition co bodyExpression = Expression.LessThanOrEqual(propertyExpression, valueExpression); break; case Operator.Contains: - bodyExpression = Expression.Call(propertyExpression, StringContainsMethodInfo.Value, - valueExpression); + bodyExpression = Expression.Call(propertyExpression, StringContainsMethodInfo.Value, valueExpression); break; case Operator.NotContains: - MethodCallExpression tempExpression = Expression.Call(propertyExpression, + MethodCallExpression tempExpression = Expression.Call( + propertyExpression, StringContainsMethodInfo.Value, valueExpression); bodyExpression = Expression.Equal(tempExpression, Expression.Constant(false)); diff --git a/src/Umbraco.Core/Models/TemplateQuery/QueryModel.cs b/src/Umbraco.Core/Models/TemplateQuery/QueryModel.cs index caf26d093c6d..06f5c82d1911 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/QueryModel.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/QueryModel.cs @@ -1,10 +1,14 @@ -namespace Umbraco.Cms.Core.Models.TemplateQuery; +namespace Umbraco.Cms.Core.Models.TemplateQuery; public class QueryModel { public ContentTypeModel? ContentType { get; set; } + public SourceModel? Source { get; set; } + public IEnumerable? Filters { get; set; } + public SortExpression? Sort { get; set; } + public int Take { get; set; } } diff --git a/src/Umbraco.Core/Models/TemplateQuery/QueryResultModel.cs b/src/Umbraco.Core/Models/TemplateQuery/QueryResultModel.cs index 80c33286306a..61845214a5eb 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/QueryResultModel.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/QueryResultModel.cs @@ -1,10 +1,14 @@ -namespace Umbraco.Cms.Core.Models.TemplateQuery; +namespace Umbraco.Cms.Core.Models.TemplateQuery; public class QueryResultModel { public string? QueryExpression { get; set; } + public IEnumerable? SampleResults { get; set; } + public int ResultCount { get; set; } + public long ExecutionTime { get; set; } + public int Take { get; set; } } diff --git a/src/Umbraco.Core/Models/TemplateQuery/SortExpression.cs b/src/Umbraco.Core/Models/TemplateQuery/SortExpression.cs index 1a41eef4ab72..b5accd7ccda9 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/SortExpression.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/SortExpression.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.TemplateQuery; +namespace Umbraco.Cms.Core.Models.TemplateQuery; public class SortExpression { diff --git a/src/Umbraco.Core/Models/TemplateQuery/SourceModel.cs b/src/Umbraco.Core/Models/TemplateQuery/SourceModel.cs index bdaaf54841d9..a36ae38a9e70 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/SourceModel.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/SourceModel.cs @@ -1,7 +1,8 @@ -namespace Umbraco.Cms.Core.Models.TemplateQuery; +namespace Umbraco.Cms.Core.Models.TemplateQuery; public class SourceModel { public int Id { get; set; } + public string? Name { get; set; } } diff --git a/src/Umbraco.Core/Models/TemplateQuery/TemplateQueryResult.cs b/src/Umbraco.Core/Models/TemplateQuery/TemplateQueryResult.cs index 40cede0c92f6..4e56beb63543 100644 --- a/src/Umbraco.Core/Models/TemplateQuery/TemplateQueryResult.cs +++ b/src/Umbraco.Core/Models/TemplateQuery/TemplateQueryResult.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Models.TemplateQuery; +namespace Umbraco.Cms.Core.Models.TemplateQuery; public class TemplateQueryResult { diff --git a/src/Umbraco.Core/Models/Trees/RefreshNode.cs b/src/Umbraco.Core/Models/Trees/RefreshNode.cs index 9dfdc3ec29dc..befbec019e79 100644 --- a/src/Umbraco.Core/Models/Trees/RefreshNode.cs +++ b/src/Umbraco.Core/Models/Trees/RefreshNode.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Core.Models.Trees; diff --git a/src/Umbraco.Core/Models/TwoFactorLogin.cs b/src/Umbraco.Core/Models/TwoFactorLogin.cs index fd1483c689d6..551482e3a25a 100644 --- a/src/Umbraco.Core/Models/TwoFactorLogin.cs +++ b/src/Umbraco.Core/Models/TwoFactorLogin.cs @@ -1,11 +1,14 @@ -using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; public class TwoFactorLogin : EntityBase, ITwoFactorLogin { public bool Confirmed { get; set; } + public string ProviderName { get; set; } = null!; + public string Secret { get; set; } = null!; + public Guid UserOrMemberKey { get; set; } } diff --git a/src/Umbraco.Core/Models/UmbracoDomain.cs b/src/Umbraco.Core/Models/UmbracoDomain.cs index 6ae269981df7..c883e147709b 100644 --- a/src/Umbraco.Core/Models/UmbracoDomain.cs +++ b/src/Umbraco.Core/Models/UmbracoDomain.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs index 598f8127d4b6..600927db84f0 100644 --- a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs +++ b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.CodeAnnotations; +using Umbraco.Cms.Core.CodeAnnotations; namespace Umbraco.Cms.Core.Models; @@ -12,11 +12,11 @@ public enum UmbracoObjectTypes /// Unknown, - /// /// Root /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.SystemRoot)] [FriendlyName("Root")] + [UmbracoObjectType(Constants.ObjectTypes.Strings.SystemRoot)] + [FriendlyName("Root")] ROOT, /// @@ -78,7 +78,8 @@ public enum UmbracoObjectTypes /// /// Recycle Bin /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.ContentRecycleBin)] [FriendlyName("Recycle Bin")] + [UmbracoObjectType(Constants.ObjectTypes.Strings.ContentRecycleBin)] + [FriendlyName("Recycle Bin")] RecycleBin, /// @@ -132,25 +133,29 @@ public enum UmbracoObjectTypes /// /// Forms Form /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsForm)] [FriendlyName("Form")] + [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsForm)] + [FriendlyName("Form")] FormsForm, /// /// Forms PreValue /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsPreValue)] [FriendlyName("PreValue")] + [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsPreValue)] + [FriendlyName("PreValue")] FormsPreValue, /// /// Forms DataSource /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsDataSource)] [FriendlyName("DataSource")] + [UmbracoObjectType(Constants.ObjectTypes.Strings.FormsDataSource)] + [FriendlyName("DataSource")] FormsDataSource, /// /// Language /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.Language)] [FriendlyName("Language")] + [UmbracoObjectType(Constants.ObjectTypes.Strings.Language)] + [FriendlyName("Language")] Language, /// @@ -164,6 +169,7 @@ public enum UmbracoObjectTypes /// /// Reserved Identifier /// - [UmbracoObjectType(Constants.ObjectTypes.Strings.IdReservation)] [FriendlyName("Identifier Reservation")] - IdReservation + [UmbracoObjectType(Constants.ObjectTypes.Strings.IdReservation)] + [FriendlyName("Identifier Reservation")] + IdReservation, } diff --git a/src/Umbraco.Core/Models/UmbracoUserExtensions.cs b/src/Umbraco.Core/Models/UmbracoUserExtensions.cs index 4581ef19a390..d708704fac45 100644 --- a/src/Umbraco.Core/Models/UmbracoUserExtensions.cs +++ b/src/Umbraco.Core/Models/UmbracoUserExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.Globalization; @@ -54,8 +54,7 @@ public static bool IsAdmin(this IUser user) /// /// /// - public static CultureInfo GetUserCulture(this IUser user, ILocalizedTextService textService, - GlobalSettings globalSettings) + public static CultureInfo GetUserCulture(this IUser user, ILocalizedTextService textService, GlobalSettings globalSettings) { if (user == null) { @@ -70,12 +69,12 @@ public static CultureInfo GetUserCulture(this IUser user, ILocalizedTextService return GetUserCulture(user.Language, textService, globalSettings); } - public static CultureInfo GetUserCulture(string? userLanguage, ILocalizedTextService textService, - GlobalSettings globalSettings) + public static CultureInfo GetUserCulture(string? userLanguage, ILocalizedTextService textService, GlobalSettings globalSettings) { try { var culture = CultureInfo.GetCultureInfo(userLanguage!.Replace("_", "-")); + // TODO: This is a hack because we store the user language as 2 chars instead of the full culture // which is actually stored in the language files (which are also named with 2 chars!) so we need to attempt // to convert to a supported full culture @@ -84,7 +83,7 @@ public static CultureInfo GetUserCulture(string? userLanguage, ILocalizedTextSer } catch (CultureNotFoundException) { - //return the default one + // return the default one return CultureInfo.GetCultureInfo(globalSettings.DefaultUILanguage); } } diff --git a/src/Umbraco.Core/Models/UnLinkLoginModel.cs b/src/Umbraco.Core/Models/UnLinkLoginModel.cs index 2dbc487c2b26..c12123081048 100644 --- a/src/Umbraco.Core/Models/UnLinkLoginModel.cs +++ b/src/Umbraco.Core/Models/UnLinkLoginModel.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/UpgradeCheckResponse.cs b/src/Umbraco.Core/Models/UpgradeCheckResponse.cs index 79797620dbc3..b639616524e5 100644 --- a/src/Umbraco.Core/Models/UpgradeCheckResponse.cs +++ b/src/Umbraco.Core/Models/UpgradeCheckResponse.cs @@ -7,19 +7,23 @@ namespace Umbraco.Cms.Core.Models; [DataContract(Name = "upgrade", Namespace = "")] public class UpgradeCheckResponse { - public UpgradeCheckResponse() { } + public UpgradeCheckResponse() + { + } - public UpgradeCheckResponse(string upgradeType, string upgradeComment, string upgradeUrl, - IUmbracoVersion umbracoVersion) + public UpgradeCheckResponse(string upgradeType, string upgradeComment, string upgradeUrl, IUmbracoVersion umbracoVersion) { Type = upgradeType; Comment = upgradeComment; Url = upgradeUrl + "?version=" + WebUtility.UrlEncode(umbracoVersion.Version?.ToString(3)); } - [DataMember(Name = "type")] public string? Type { get; set; } + [DataMember(Name = "type")] + public string? Type { get; set; } - [DataMember(Name = "comment")] public string? Comment { get; set; } + [DataMember(Name = "comment")] + public string? Comment { get; set; } - [DataMember(Name = "url")] public string? Url { get; set; } + [DataMember(Name = "url")] + public string? Url { get; set; } } diff --git a/src/Umbraco.Core/Models/UsageInformation.cs b/src/Umbraco.Core/Models/UsageInformation.cs index 6656a18c4e1b..3de3a1201aa7 100644 --- a/src/Umbraco.Core/Models/UsageInformation.cs +++ b/src/Umbraco.Core/Models/UsageInformation.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; @@ -11,7 +11,9 @@ public UsageInformation(string name, object data) Data = data; } - [DataMember(Name = "name")] public string Name { get; } + [DataMember(Name = "name")] + public string Name { get; } - [DataMember(Name = "data")] public object Data { get; } + [DataMember(Name = "data")] + public object Data { get; } } diff --git a/src/Umbraco.Core/Models/UserData.cs b/src/Umbraco.Core/Models/UserData.cs index e35cd5a2d737..144871c3f7d2 100644 --- a/src/Umbraco.Core/Models/UserData.cs +++ b/src/Umbraco.Core/Models/UserData.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; @@ -11,7 +11,9 @@ public UserData(string name, string data) Data = data; } - [DataMember(Name = "name")] public string Name { get; } + [DataMember(Name = "name")] + public string Name { get; } - [DataMember(Name = "data")] public string Data { get; } + [DataMember(Name = "data")] + public string Data { get; } } diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 4eca05c0e3e2..87f91978e0b1 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -1,4 +1,4 @@ -using System.Net; +using System.Net; using System.Security.Cryptography; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.IO; @@ -19,11 +19,11 @@ public static class UserExtensions /// /// /// + /// /// /// A list of 5 different sized avatar URLs /// - public static string[] GetUserAvatarUrls(this IUser user, IAppCache cache, MediaFileManager mediaFileManager, - IImageUrlGenerator imageUrlGenerator) + public static string[] GetUserAvatarUrls(this IUser user, IAppCache cache, MediaFileManager mediaFileManager, IImageUrlGenerator imageUrlGenerator) { // If FIPS is required, never check the Gravatar service as it only supports MD5 hashing. // Unfortunately, if the FIPS setting is enabled on Windows, using MD5 will throw an exception @@ -39,16 +39,19 @@ public static string[] GetUserAvatarUrls(this IUser user, IAppCache cache, Media var gravatarHash = user.Email?.GenerateHash(); var gravatarUrl = "https://www.gravatar.com/avatar/" + gravatarHash + "?d=404"; - //try Gravatar + // try Gravatar var gravatarAccess = cache.GetCacheItem("UserAvatar" + user.Id, () => { // Test if we can reach this URL, will fail when there's network or firewall errors var request = (HttpWebRequest)WebRequest.Create(gravatarUrl); + // Require response within 10 seconds request.Timeout = 10000; try { - using ((HttpWebResponse)request.GetResponse()) { } + using ((HttpWebResponse)request.GetResponse()) + { + } } catch (Exception) { @@ -64,68 +67,76 @@ public static string[] GetUserAvatarUrls(this IUser user, IAppCache cache, Media return new[] { gravatarUrl + "&s=30", gravatarUrl + "&s=60", gravatarUrl + "&s=90", gravatarUrl + "&s=150", - gravatarUrl + "&s=300" + gravatarUrl + "&s=300", }; } return new string[0]; } - //use the custom avatar + // use the custom avatar var avatarUrl = mediaFileManager.FileSystem.GetUrl(user.Avatar); return new[] { imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { - ImageCropMode = ImageCropMode.Crop, Width = 30, Height = 30 + ImageCropMode = ImageCropMode.Crop, Width = 30, Height = 30, }), imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { - ImageCropMode = ImageCropMode.Crop, Width = 60, Height = 60 + ImageCropMode = ImageCropMode.Crop, Width = 60, Height = 60, }), imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { - ImageCropMode = ImageCropMode.Crop, Width = 90, Height = 90 + ImageCropMode = ImageCropMode.Crop, Width = 90, Height = 90, }), imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { - ImageCropMode = ImageCropMode.Crop, Width = 150, Height = 150 + ImageCropMode = ImageCropMode.Crop, Width = 150, Height = 150, }), imageUrlGenerator.GetImageUrl(new ImageUrlGenerationOptions(avatarUrl) { - ImageCropMode = ImageCropMode.Crop, Width = 300, Height = 300 - }) + ImageCropMode = ImageCropMode.Crop, Width = 300, Height = 300, + }), }.WhereNotNull().ToArray(); } + public static bool HasPathAccess(this IUser user, IContent content, IEntityService entityService, AppCaches appCaches) + { + if (content == null) + { + throw new ArgumentNullException(nameof(content)); + } + + return ContentPermissions.HasPathAccess( + content.Path, + user.CalculateContentStartNodeIds(entityService, appCaches), + Constants.System.RecycleBinContent); + } internal static bool HasContentRootAccess(this IUser user, IEntityService entityService, AppCaches appCaches) => - ContentPermissions.HasPathAccess(Constants.System.RootString, - user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent); + ContentPermissions.HasPathAccess( + Constants.System.RootString, + user.CalculateContentStartNodeIds(entityService, appCaches), + Constants.System.RecycleBinContent); internal static bool HasContentBinAccess(this IUser user, IEntityService entityService, AppCaches appCaches) => - ContentPermissions.HasPathAccess(Constants.System.RecycleBinContentString, - user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent); + ContentPermissions.HasPathAccess( + Constants.System.RecycleBinContentString, + user.CalculateContentStartNodeIds(entityService, appCaches), + Constants.System.RecycleBinContent); internal static bool HasMediaRootAccess(this IUser user, IEntityService entityService, AppCaches appCaches) => - ContentPermissions.HasPathAccess(Constants.System.RootString, - user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia); + ContentPermissions.HasPathAccess( + Constants.System.RootString, + user.CalculateMediaStartNodeIds(entityService, appCaches), + Constants.System.RecycleBinMedia); internal static bool HasMediaBinAccess(this IUser user, IEntityService entityService, AppCaches appCaches) => - ContentPermissions.HasPathAccess(Constants.System.RecycleBinMediaString, - user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia); - - public static bool HasPathAccess(this IUser user, IContent content, IEntityService entityService, - AppCaches appCaches) - { - if (content == null) - { - throw new ArgumentNullException(nameof(content)); - } - - return ContentPermissions.HasPathAccess(content.Path, - user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent); - } + ContentPermissions.HasPathAccess( + Constants.System.RecycleBinMediaString, + user.CalculateMediaStartNodeIds(entityService, appCaches), + Constants.System.RecycleBinMedia); public static bool HasPathAccess(this IUser user, IMedia? media, IEntityService entityService, AppCaches appCaches) { @@ -134,32 +145,30 @@ public static bool HasPathAccess(this IUser user, IMedia? media, IEntityService throw new ArgumentNullException(nameof(media)); } - return ContentPermissions.HasPathAccess(media.Path, user.CalculateMediaStartNodeIds(entityService, appCaches), - Constants.System.RecycleBinMedia); + return ContentPermissions.HasPathAccess(media.Path, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia); } - public static bool HasContentPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService, - AppCaches appCaches) + public static bool HasContentPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService, AppCaches appCaches) { if (entity == null) { throw new ArgumentNullException(nameof(entity)); } - return ContentPermissions.HasPathAccess(entity.Path, - user.CalculateContentStartNodeIds(entityService, appCaches), Constants.System.RecycleBinContent); + return ContentPermissions.HasPathAccess( + entity.Path, + user.CalculateContentStartNodeIds(entityService, appCaches), + Constants.System.RecycleBinContent); } - public static bool HasMediaPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService, - AppCaches appCaches) + public static bool HasMediaPathAccess(this IUser user, IUmbracoEntity entity, IEntityService entityService, AppCaches appCaches) { if (entity == null) { throw new ArgumentNullException(nameof(entity)); } - return ContentPermissions.HasPathAccess(entity.Path, user.CalculateMediaStartNodeIds(entityService, appCaches), - Constants.System.RecycleBinMedia); + return ContentPermissions.HasPathAccess(entity.Path, user.CalculateMediaStartNodeIds(entityService, appCaches), Constants.System.RecycleBinMedia); } /// @@ -179,12 +188,13 @@ public static bool HasAccessToSensitiveData(this IUser user) /// /// Calculate start nodes, combining groups' and user's, and excluding what's in the bin /// - public static int[]? CalculateContentStartNodeIds(this IUser user, IEntityService entityService, - AppCaches appCaches) + public static int[]? CalculateContentStartNodeIds(this IUser user, IEntityService entityService, AppCaches appCaches) { var cacheKey = CacheKeys.UserAllContentStartNodesPrefix + user.Id; IAppPolicyCache runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); - var result = runtimeCache.GetCacheItem(cacheKey, () => + var result = runtimeCache.GetCacheItem( + cacheKey, + () => { // This returns a nullable array even though we're checking if items have value and there cannot be null // We use Cast to recast into non-nullable array @@ -198,7 +208,9 @@ public static bool HasAccessToSensitiveData(this IUser user) } return null; - }, TimeSpan.FromMinutes(2), true); + }, + TimeSpan.FromMinutes(2), + true); return result; } @@ -208,13 +220,15 @@ public static bool HasAccessToSensitiveData(this IUser user) /// /// /// - /// + /// /// public static int[]? CalculateMediaStartNodeIds(this IUser user, IEntityService entityService, AppCaches appCaches) { var cacheKey = CacheKeys.UserAllMediaStartNodesPrefix + user.Id; IAppPolicyCache runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); - var result = runtimeCache.GetCacheItem(cacheKey, () => + var result = runtimeCache.GetCacheItem( + cacheKey, + () => { var gsn = user.Groups.Where(x => x.StartMediaId.HasValue).Select(x => x.StartMediaId!.Value).Distinct() .ToArray(); @@ -226,7 +240,9 @@ public static bool HasAccessToSensitiveData(this IUser user) } return null; - }, TimeSpan.FromMinutes(2), true); + }, + TimeSpan.FromMinutes(2), + true); return result; } @@ -235,12 +251,16 @@ public static bool HasAccessToSensitiveData(this IUser user) { var cacheKey = CacheKeys.UserMediaStartNodePathsPrefix + user.Id; IAppPolicyCache runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); - var result = runtimeCache.GetCacheItem(cacheKey, () => + var result = runtimeCache.GetCacheItem( + cacheKey, + () => { var startNodeIds = user.CalculateMediaStartNodeIds(entityService, appCaches); var vals = entityService.GetAllPaths(UmbracoObjectTypes.Media, startNodeIds).Select(x => x.Path).ToArray(); return vals; - }, TimeSpan.FromMinutes(2), true); + }, + TimeSpan.FromMinutes(2), + true); return result; } @@ -249,43 +269,24 @@ public static bool HasAccessToSensitiveData(this IUser user) { var cacheKey = CacheKeys.UserContentStartNodePathsPrefix + user.Id; IAppPolicyCache runtimeCache = appCaches.IsolatedCaches.GetOrCreate(); - var result = runtimeCache.GetCacheItem(cacheKey, () => + var result = runtimeCache.GetCacheItem( + cacheKey, + () => { var startNodeIds = user.CalculateContentStartNodeIds(entityService, appCaches); var vals = entityService.GetAllPaths(UmbracoObjectTypes.Document, startNodeIds).Select(x => x.Path) .ToArray(); return vals; - }, TimeSpan.FromMinutes(2), true); + }, + TimeSpan.FromMinutes(2), + true); return result; } - private static bool StartsWithPath(string test, string path) => - test.StartsWith(path) && test.Length > path.Length && test[path.Length] == ','; - - private static string GetBinPath(UmbracoObjectTypes objectType) - { - var binPath = Constants.System.RootString + ","; - switch (objectType) - { - case UmbracoObjectTypes.Document: - binPath += Constants.System.RecycleBinContentString; - break; - case UmbracoObjectTypes.Media: - binPath += Constants.System.RecycleBinMediaString; - break; - default: - throw new ArgumentOutOfRangeException(nameof(objectType)); - } - - return binPath; - } - - internal static int[] CombineStartNodes(UmbracoObjectTypes objectType, int[] groupSn, int[] userSn, - IEntityService entityService) + internal static int[] CombineStartNodes(UmbracoObjectTypes objectType, int[] groupSn, int[] userSn, IEntityService entityService) { // assume groupSn and userSn each don't contain duplicates - var asn = groupSn.Concat(userSn).Distinct().ToArray(); Dictionary paths = asn.Length > 0 ? entityService.GetAllPaths(objectType, asn).ToDictionary(x => x.Id, x => x.Path) @@ -350,4 +351,25 @@ internal static int[] CombineStartNodes(UmbracoObjectTypes objectType, int[] gro return lsn.ToArray(); } + + private static bool StartsWithPath(string test, string path) => + test.StartsWith(path) && test.Length > path.Length && test[path.Length] == ','; + + private static string GetBinPath(UmbracoObjectTypes objectType) + { + var binPath = Constants.System.RootString + ","; + switch (objectType) + { + case UmbracoObjectTypes.Document: + binPath += Constants.System.RecycleBinContentString; + break; + case UmbracoObjectTypes.Media: + binPath += Constants.System.RecycleBinMediaString; + break; + default: + throw new ArgumentOutOfRangeException(nameof(objectType)); + } + + return binPath; + } } diff --git a/src/Umbraco.Core/Models/UserTourStatus.cs b/src/Umbraco.Core/Models/UserTourStatus.cs index ef076ca23423..a954a0b86443 100644 --- a/src/Umbraco.Core/Models/UserTourStatus.cs +++ b/src/Umbraco.Core/Models/UserTourStatus.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; @@ -26,6 +26,8 @@ public class UserTourStatus : IEquatable [DataMember(Name = "disabled")] public bool Disabled { get; set; } + public static bool operator ==(UserTourStatus? left, UserTourStatus? right) => Equals(left, right); + public bool Equals(UserTourStatus? other) { if (ReferenceEquals(null, other)) @@ -63,7 +65,5 @@ public override bool Equals(object? obj) public override int GetHashCode() => Alias.GetHashCode(); - public static bool operator ==(UserTourStatus? left, UserTourStatus? right) => Equals(left, right); - public static bool operator !=(UserTourStatus? left, UserTourStatus? right) => !Equals(left, right); } diff --git a/src/Umbraco.Core/Models/UserTwoFactorProviderModel.cs b/src/Umbraco.Core/Models/UserTwoFactorProviderModel.cs index d79aefd14e04..acdaed7dd9ae 100644 --- a/src/Umbraco.Core/Models/UserTwoFactorProviderModel.cs +++ b/src/Umbraco.Core/Models/UserTwoFactorProviderModel.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; @@ -11,7 +11,9 @@ public UserTwoFactorProviderModel(string providerName, bool isEnabledOnUser) IsEnabledOnUser = isEnabledOnUser; } - [DataMember(Name = "providerName")] public string ProviderName { get; } + [DataMember(Name = "providerName")] + public string ProviderName { get; } - [DataMember(Name = "isEnabledOnUser")] public bool IsEnabledOnUser { get; } + [DataMember(Name = "isEnabledOnUser")] + public bool IsEnabledOnUser { get; } } diff --git a/src/Umbraco.Core/Models/ValidatePasswordResetCodeModel.cs b/src/Umbraco.Core/Models/ValidatePasswordResetCodeModel.cs index f892b51f44c2..b4ebb89e47ec 100644 --- a/src/Umbraco.Core/Models/ValidatePasswordResetCodeModel.cs +++ b/src/Umbraco.Core/Models/ValidatePasswordResetCodeModel.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; diff --git a/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs b/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs index af969154b01a..bffd5518153a 100644 --- a/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs +++ b/src/Umbraco.Core/Models/Validation/RequiredForPersistenceAttribute.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Reflection; using Umbraco.Cms.Core.PropertyEditors; @@ -26,7 +26,7 @@ public class RequiredForPersistenceAttribute : RequiredAttribute public static bool HasRequiredValuesForPersistence(object model) => model.GetType().GetProperties().All(x => { - RequiredForPersistenceAttribute a = x.GetCustomAttribute(); + RequiredForPersistenceAttribute? a = x.GetCustomAttribute(); return a == null || a.IsValid(x.GetValue(model)); }); } diff --git a/src/Umbraco.Core/Models/ValueStorageType.cs b/src/Umbraco.Core/Models/ValueStorageType.cs index ae83356f95ee..975369f9939c 100644 --- a/src/Umbraco.Core/Models/ValueStorageType.cs +++ b/src/Umbraco.Core/Models/ValueStorageType.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Models; @@ -16,25 +16,30 @@ public enum ValueStorageType /// /// Store property value as NText. /// - [EnumMember] Ntext, + [EnumMember] + Ntext, /// /// Store property value as NVarChar. /// - [EnumMember] Nvarchar, + [EnumMember] + Nvarchar, /// /// Store property value as Integer. /// - [EnumMember] Integer, + [EnumMember] + Integer, /// /// Store property value as Date. /// - [EnumMember] Date, + [EnumMember] + Date, /// /// Store property value as Decimal. /// - [EnumMember] Decimal + [EnumMember] + Decimal, } diff --git a/src/Umbraco.Core/MonitorLock.cs b/src/Umbraco.Core/MonitorLock.cs index 3ab559f72723..45dbdbbd109a 100644 --- a/src/Umbraco.Core/MonitorLock.cs +++ b/src/Umbraco.Core/MonitorLock.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; /// /// Provides an equivalent to the c# lock statement, to be used in a using block. diff --git a/src/Umbraco.Core/NamedUdiRange.cs b/src/Umbraco.Core/NamedUdiRange.cs index becc6f58b57d..e0d52df9f4fd 100644 --- a/src/Umbraco.Core/NamedUdiRange.cs +++ b/src/Umbraco.Core/NamedUdiRange.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; /// /// Represents a complemented with a name. diff --git a/src/Umbraco.Core/Net/NullSessionIdResolver.cs b/src/Umbraco.Core/Net/NullSessionIdResolver.cs index 79ea1457c863..c76c6c86322e 100644 --- a/src/Umbraco.Core/Net/NullSessionIdResolver.cs +++ b/src/Umbraco.Core/Net/NullSessionIdResolver.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Net; +namespace Umbraco.Cms.Core.Net; public class NullSessionIdResolver : ISessionIdResolver { diff --git a/src/Umbraco.Core/Notifications/MediaTreeChangeNotification.cs b/src/Umbraco.Core/Notifications/MediaTreeChangeNotification.cs index c1686210c85e..c8a2e9d19b13 100644 --- a/src/Umbraco.Core/Notifications/MediaTreeChangeNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTreeChangeNotification.cs @@ -6,22 +6,28 @@ namespace Umbraco.Cms.Core.Notifications; public class MediaTreeChangeNotification : TreeChangeNotification { - public MediaTreeChangeNotification(TreeChange target, EventMessages messages) : base(target, messages) + public MediaTreeChangeNotification(TreeChange target, EventMessages messages) + : base(target, messages) { } - public MediaTreeChangeNotification(IEnumerable> target, EventMessages messages) : base(target, + public MediaTreeChangeNotification(IEnumerable> target, EventMessages messages) + : base( + target, messages) { } - public MediaTreeChangeNotification(IEnumerable target, + public MediaTreeChangeNotification( + IEnumerable target, TreeChangeTypes changeTypes, - EventMessages messages) : base(target.Select(x => new TreeChange(x, changeTypes)), messages) + EventMessages messages) + : base(target.Select(x => new TreeChange(x, changeTypes)), messages) { } - public MediaTreeChangeNotification(IMedia target, TreeChangeTypes changeTypes, EventMessages messages) : base( + public MediaTreeChangeNotification(IMedia target, TreeChangeTypes changeTypes, EventMessages messages) + : base( new TreeChange(target, changeTypes), messages) { } diff --git a/src/Umbraco.Core/Notifications/MediaTypeChangedNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeChangedNotification.cs index 56c9d94472cc..6300361bade4 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeChangedNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeChangedNotification.cs @@ -6,13 +6,15 @@ namespace Umbraco.Cms.Core.Notifications; public class MediaTypeChangedNotification : ContentTypeChangeNotification { - public MediaTypeChangedNotification(ContentTypeChange target, EventMessages messages) : base(target, + public MediaTypeChangedNotification(ContentTypeChange target, EventMessages messages) + : base( + target, messages) { } - public MediaTypeChangedNotification(IEnumerable> target, EventMessages messages) : - base(target, messages) + public MediaTypeChangedNotification(IEnumerable> target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MediaTypeDeletedNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeDeletedNotification.cs index 2db2449b23e3..8ad8e1bce577 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeDeletedNotification.cs @@ -5,11 +5,13 @@ namespace Umbraco.Cms.Core.Notifications; public class MediaTypeDeletedNotification : DeletedNotification { - public MediaTypeDeletedNotification(IMediaType target, EventMessages messages) : base(target, messages) + public MediaTypeDeletedNotification(IMediaType target, EventMessages messages) + : base(target, messages) { } - public MediaTypeDeletedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public MediaTypeDeletedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MediaTypeDeletingNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeDeletingNotification.cs index 9aaedd924904..d0c7507cf7cc 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeDeletingNotification.cs @@ -5,11 +5,14 @@ namespace Umbraco.Cms.Core.Notifications; public class MediaTypeDeletingNotification : DeletingNotification { - public MediaTypeDeletingNotification(IMediaType target, EventMessages messages) : base(target, messages) + public MediaTypeDeletingNotification(IMediaType target, EventMessages messages) + : base(target, messages) { } - public MediaTypeDeletingNotification(IEnumerable target, EventMessages messages) : base(target, + public MediaTypeDeletingNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/MediaTypeMovedNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeMovedNotification.cs index aa9de887e72c..13b47074dd74 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeMovedNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeMovedNotification.cs @@ -5,11 +5,13 @@ namespace Umbraco.Cms.Core.Notifications; public class MediaTypeMovedNotification : MovedNotification { - public MediaTypeMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, messages) + public MediaTypeMovedNotification(MoveEventInfo target, EventMessages messages) + : base(target, messages) { } - public MediaTypeMovedNotification(IEnumerable> target, EventMessages messages) : base( + public MediaTypeMovedNotification(IEnumerable> target, EventMessages messages) + : base( target, messages) { } diff --git a/src/Umbraco.Core/Notifications/MediaTypeMovingNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeMovingNotification.cs index 7ac77445590e..bfad254a07aa 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeMovingNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeMovingNotification.cs @@ -5,12 +5,15 @@ namespace Umbraco.Cms.Core.Notifications; public class MediaTypeMovingNotification : MovingNotification { - public MediaTypeMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, + public MediaTypeMovingNotification(MoveEventInfo target, EventMessages messages) + : base( + target, messages) { } - public MediaTypeMovingNotification(IEnumerable> target, EventMessages messages) : base( + public MediaTypeMovingNotification(IEnumerable> target, EventMessages messages) + : base( target, messages) { } diff --git a/src/Umbraco.Core/Notifications/MediaTypeRefreshedNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeRefreshedNotification.cs index cc35bb31f9a7..30719c7587f3 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeRefreshedNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeRefreshedNotification.cs @@ -9,13 +9,15 @@ namespace Umbraco.Cms.Core.Notifications; [EditorBrowsable(EditorBrowsableState.Never)] public class MediaTypeRefreshedNotification : ContentTypeRefreshNotification { - public MediaTypeRefreshedNotification(ContentTypeChange target, EventMessages messages) : base(target, + public MediaTypeRefreshedNotification(ContentTypeChange target, EventMessages messages) + : base( + target, messages) { } - public MediaTypeRefreshedNotification(IEnumerable> target, EventMessages messages) : - base(target, messages) + public MediaTypeRefreshedNotification(IEnumerable> target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MediaTypeSavedNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeSavedNotification.cs index 2ffc42ce47be..17063f5252c5 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeSavedNotification.cs @@ -5,11 +5,13 @@ namespace Umbraco.Cms.Core.Notifications; public class MediaTypeSavedNotification : SavedNotification { - public MediaTypeSavedNotification(IMediaType target, EventMessages messages) : base(target, messages) + public MediaTypeSavedNotification(IMediaType target, EventMessages messages) + : base(target, messages) { } - public MediaTypeSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public MediaTypeSavedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MediaTypeSavingNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeSavingNotification.cs index f6a9bac2ae02..46bc588b39ed 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeSavingNotification.cs @@ -5,11 +5,13 @@ namespace Umbraco.Cms.Core.Notifications; public class MediaTypeSavingNotification : SavingNotification { - public MediaTypeSavingNotification(IMediaType target, EventMessages messages) : base(target, messages) + public MediaTypeSavingNotification(IMediaType target, EventMessages messages) + : base(target, messages) { } - public MediaTypeSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public MediaTypeSavingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MemberCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/MemberCacheRefresherNotification.cs index d305ab8b139d..860d0eaa55ad 100644 --- a/src/Umbraco.Core/Notifications/MemberCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberCacheRefresherNotification.cs @@ -1,10 +1,12 @@ -using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Notifications; public class MemberCacheRefresherNotification : CacheRefresherNotification { - public MemberCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + public MemberCacheRefresherNotification(object messageObject, MessageType messageType) + : base( + messageObject, messageType) { } diff --git a/src/Umbraco.Core/Notifications/MemberDeletedNotification.cs b/src/Umbraco.Core/Notifications/MemberDeletedNotification.cs index d06694a2af74..b1578fd99812 100644 --- a/src/Umbraco.Core/Notifications/MemberDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberDeletedNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class MemberDeletedNotification : DeletedNotification { - public MemberDeletedNotification(IMember target, EventMessages messages) : base(target, messages) + public MemberDeletedNotification(IMember target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MemberDeletingNotification.cs b/src/Umbraco.Core/Notifications/MemberDeletingNotification.cs index 22c732b61cd4..df599d7b0845 100644 --- a/src/Umbraco.Core/Notifications/MemberDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberDeletingNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class MemberDeletingNotification : DeletingNotification { - public MemberDeletingNotification(IMember target, EventMessages messages) : base(target, messages) + public MemberDeletingNotification(IMember target, EventMessages messages) + : base(target, messages) { } - public MemberDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public MemberDeletingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MemberGroupCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/MemberGroupCacheRefresherNotification.cs index a858f03820aa..63e234d5f2a9 100644 --- a/src/Umbraco.Core/Notifications/MemberGroupCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberGroupCacheRefresherNotification.cs @@ -1,10 +1,12 @@ -using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Notifications; public class MemberGroupCacheRefresherNotification : CacheRefresherNotification { - public MemberGroupCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + public MemberGroupCacheRefresherNotification(object messageObject, MessageType messageType) + : base( + messageObject, messageType) { } diff --git a/src/Umbraco.Core/Notifications/MemberGroupDeletedNotification.cs b/src/Umbraco.Core/Notifications/MemberGroupDeletedNotification.cs index dc0c8194529c..528dc37254ee 100644 --- a/src/Umbraco.Core/Notifications/MemberGroupDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberGroupDeletedNotification.cs @@ -5,7 +5,8 @@ namespace Umbraco.Cms.Core.Notifications; public class MemberGroupDeletedNotification : DeletedNotification { - public MemberGroupDeletedNotification(IMemberGroup target, EventMessages messages) : base(target, messages) + public MemberGroupDeletedNotification(IMemberGroup target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MemberGroupDeletingNotification.cs b/src/Umbraco.Core/Notifications/MemberGroupDeletingNotification.cs index 93bbc2b2e00f..ee9a1cf98b78 100644 --- a/src/Umbraco.Core/Notifications/MemberGroupDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberGroupDeletingNotification.cs @@ -5,11 +5,14 @@ namespace Umbraco.Cms.Core.Notifications; public class MemberGroupDeletingNotification : DeletingNotification { - public MemberGroupDeletingNotification(IMemberGroup target, EventMessages messages) : base(target, messages) + public MemberGroupDeletingNotification(IMemberGroup target, EventMessages messages) + : base(target, messages) { } - public MemberGroupDeletingNotification(IEnumerable target, EventMessages messages) : base(target, + public MemberGroupDeletingNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/MemberGroupSavedNotification.cs b/src/Umbraco.Core/Notifications/MemberGroupSavedNotification.cs index 8e9040785e39..ac4ef6a9405d 100644 --- a/src/Umbraco.Core/Notifications/MemberGroupSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberGroupSavedNotification.cs @@ -5,11 +5,14 @@ namespace Umbraco.Cms.Core.Notifications; public class MemberGroupSavedNotification : SavedNotification { - public MemberGroupSavedNotification(IMemberGroup target, EventMessages messages) : base(target, messages) + public MemberGroupSavedNotification(IMemberGroup target, EventMessages messages) + : base(target, messages) { } - public MemberGroupSavedNotification(IEnumerable target, EventMessages messages) : base(target, + public MemberGroupSavedNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/MemberGroupSavingNotification.cs b/src/Umbraco.Core/Notifications/MemberGroupSavingNotification.cs index 95585480c578..294600d731e9 100644 --- a/src/Umbraco.Core/Notifications/MemberGroupSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberGroupSavingNotification.cs @@ -1,15 +1,18 @@ -using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Notifications; public class MemberGroupSavingNotification : SavingNotification { - public MemberGroupSavingNotification(IMemberGroup target, EventMessages messages) : base(target, messages) + public MemberGroupSavingNotification(IMemberGroup target, EventMessages messages) + : base(target, messages) { } - public MemberGroupSavingNotification(IEnumerable target, EventMessages messages) : base(target, + public MemberGroupSavingNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/MemberRefreshNotification.cs b/src/Umbraco.Core/Notifications/MemberRefreshNotification.cs index db089a96223c..ddab089c0b18 100644 --- a/src/Umbraco.Core/Notifications/MemberRefreshNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberRefreshNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; [EditorBrowsable(EditorBrowsableState.Never)] public class MemberRefreshNotification : EntityRefreshNotification { - public MemberRefreshNotification(IMember target, EventMessages messages) : base(target, messages) + public MemberRefreshNotification(IMember target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MemberSavedNotification.cs b/src/Umbraco.Core/Notifications/MemberSavedNotification.cs index cb29e0866930..f59f41f0ecd8 100644 --- a/src/Umbraco.Core/Notifications/MemberSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberSavedNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class MemberSavedNotification : SavedNotification { - public MemberSavedNotification(IMember target, EventMessages messages) : base(target, messages) + public MemberSavedNotification(IMember target, EventMessages messages) + : base(target, messages) { } - public MemberSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public MemberSavedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MemberSavingNotification.cs b/src/Umbraco.Core/Notifications/MemberSavingNotification.cs index 095be39b74ca..813e6f726921 100644 --- a/src/Umbraco.Core/Notifications/MemberSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberSavingNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class MemberSavingNotification : SavingNotification { - public MemberSavingNotification(IMember target, EventMessages messages) : base(target, messages) + public MemberSavingNotification(IMember target, EventMessages messages) + : base(target, messages) { } - public MemberSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public MemberSavingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MemberTwoFactorRequestedNotification.cs b/src/Umbraco.Core/Notifications/MemberTwoFactorRequestedNotification.cs index 5182f4a46f90..fc9e392598f2 100644 --- a/src/Umbraco.Core/Notifications/MemberTwoFactorRequestedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTwoFactorRequestedNotification.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Notifications; +namespace Umbraco.Cms.Core.Notifications; public class MemberTwoFactorRequestedNotification : INotification { diff --git a/src/Umbraco.Core/Notifications/MemberTypeChangedNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeChangedNotification.cs index ae43a3e34898..0bac5d44b0da 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeChangedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeChangedNotification.cs @@ -6,13 +6,15 @@ namespace Umbraco.Cms.Core.Notifications; public class MemberTypeChangedNotification : ContentTypeChangeNotification { - public MemberTypeChangedNotification(ContentTypeChange target, EventMessages messages) : base(target, + public MemberTypeChangedNotification(ContentTypeChange target, EventMessages messages) + : base( + target, messages) { } - public MemberTypeChangedNotification(IEnumerable> target, EventMessages messages) : - base(target, messages) + public MemberTypeChangedNotification(IEnumerable> target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MemberTypeDeletedNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeDeletedNotification.cs index 561db465cbc4..c1560273cff4 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeDeletedNotification.cs @@ -5,11 +5,14 @@ namespace Umbraco.Cms.Core.Notifications; public class MemberTypeDeletedNotification : DeletedNotification { - public MemberTypeDeletedNotification(IMemberType target, EventMessages messages) : base(target, messages) + public MemberTypeDeletedNotification(IMemberType target, EventMessages messages) + : base(target, messages) { } - public MemberTypeDeletedNotification(IEnumerable target, EventMessages messages) : base(target, + public MemberTypeDeletedNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/MemberTypeDeletingNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeDeletingNotification.cs index 1c8a0a54fda8..a692daab10a6 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeDeletingNotification.cs @@ -5,11 +5,14 @@ namespace Umbraco.Cms.Core.Notifications; public class MemberTypeDeletingNotification : DeletingNotification { - public MemberTypeDeletingNotification(IMemberType target, EventMessages messages) : base(target, messages) + public MemberTypeDeletingNotification(IMemberType target, EventMessages messages) + : base(target, messages) { } - public MemberTypeDeletingNotification(IEnumerable target, EventMessages messages) : base(target, + public MemberTypeDeletingNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/MemberTypeMovedNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeMovedNotification.cs index 64a4d3ea50bc..991f95f88670 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeMovedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeMovedNotification.cs @@ -5,12 +5,15 @@ namespace Umbraco.Cms.Core.Notifications; public class MemberTypeMovedNotification : MovedNotification { - public MemberTypeMovedNotification(MoveEventInfo target, EventMessages messages) : base(target, + public MemberTypeMovedNotification(MoveEventInfo target, EventMessages messages) + : base( + target, messages) { } - public MemberTypeMovedNotification(IEnumerable> target, EventMessages messages) : base( + public MemberTypeMovedNotification(IEnumerable> target, EventMessages messages) + : base( target, messages) { } diff --git a/src/Umbraco.Core/Notifications/MemberTypeMovingNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeMovingNotification.cs index d6cf2c62c86e..42dd54b439d4 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeMovingNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeMovingNotification.cs @@ -5,12 +5,15 @@ namespace Umbraco.Cms.Core.Notifications; public class MemberTypeMovingNotification : MovingNotification { - public MemberTypeMovingNotification(MoveEventInfo target, EventMessages messages) : base(target, + public MemberTypeMovingNotification(MoveEventInfo target, EventMessages messages) + : base( + target, messages) { } - public MemberTypeMovingNotification(IEnumerable> target, EventMessages messages) : base( + public MemberTypeMovingNotification(IEnumerable> target, EventMessages messages) + : base( target, messages) { } diff --git a/src/Umbraco.Core/Notifications/MemberTypeRefreshedNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeRefreshedNotification.cs index d9cdc51f8427..100fa05185ad 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeRefreshedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeRefreshedNotification.cs @@ -9,13 +9,15 @@ namespace Umbraco.Cms.Core.Notifications; [EditorBrowsable(EditorBrowsableState.Never)] public class MemberTypeRefreshedNotification : ContentTypeRefreshNotification { - public MemberTypeRefreshedNotification(ContentTypeChange target, EventMessages messages) : base(target, + public MemberTypeRefreshedNotification(ContentTypeChange target, EventMessages messages) + : base( + target, messages) { } - public MemberTypeRefreshedNotification(IEnumerable> target, EventMessages messages) : - base(target, messages) + public MemberTypeRefreshedNotification(IEnumerable> target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MemberTypeSavedNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeSavedNotification.cs index 5cc464f680c2..3101c794e225 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeSavedNotification.cs @@ -5,11 +5,13 @@ namespace Umbraco.Cms.Core.Notifications; public class MemberTypeSavedNotification : SavedNotification { - public MemberTypeSavedNotification(IMemberType target, EventMessages messages) : base(target, messages) + public MemberTypeSavedNotification(IMemberType target, EventMessages messages) + : base(target, messages) { } - public MemberTypeSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public MemberTypeSavedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/MemberTypeSavingNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeSavingNotification.cs index c5f457f856e8..e0c8b9f4c390 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeSavingNotification.cs @@ -5,11 +5,14 @@ namespace Umbraco.Cms.Core.Notifications; public class MemberTypeSavingNotification : SavingNotification { - public MemberTypeSavingNotification(IMemberType target, EventMessages messages) : base(target, messages) + public MemberTypeSavingNotification(IMemberType target, EventMessages messages) + : base(target, messages) { } - public MemberTypeSavingNotification(IEnumerable target, EventMessages messages) : base(target, + public MemberTypeSavingNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/MovedNotification.cs b/src/Umbraco.Core/Notifications/MovedNotification.cs index 123f94b2b58f..f67273a6d412 100644 --- a/src/Umbraco.Core/Notifications/MovedNotification.cs +++ b/src/Umbraco.Core/Notifications/MovedNotification.cs @@ -7,11 +7,13 @@ namespace Umbraco.Cms.Core.Notifications; public abstract class MovedNotification : ObjectNotification>> { - protected MovedNotification(MoveEventInfo target, EventMessages messages) : base(new[] {target}, messages) + protected MovedNotification(MoveEventInfo target, EventMessages messages) + : base(new[] { target }, messages) { } - protected MovedNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + protected MovedNotification(IEnumerable> target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/MovedToRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/MovedToRecycleBinNotification.cs index 7ce074bfdd4e..217aea7ebb5f 100644 --- a/src/Umbraco.Core/Notifications/MovedToRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/MovedToRecycleBinNotification.cs @@ -7,12 +7,16 @@ namespace Umbraco.Cms.Core.Notifications; public abstract class MovedToRecycleBinNotification : ObjectNotification>> { - protected MovedToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(new[] {target}, + protected MovedToRecycleBinNotification(MoveEventInfo target, EventMessages messages) + : base( + new[] { target }, messages) { } - protected MovedToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base(target, + protected MovedToRecycleBinNotification(IEnumerable> target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/MovingNotification.cs b/src/Umbraco.Core/Notifications/MovingNotification.cs index 729bd57ad424..47a2ecf7bf07 100644 --- a/src/Umbraco.Core/Notifications/MovingNotification.cs +++ b/src/Umbraco.Core/Notifications/MovingNotification.cs @@ -7,11 +7,13 @@ namespace Umbraco.Cms.Core.Notifications; public abstract class MovingNotification : CancelableObjectNotification>> { - protected MovingNotification(MoveEventInfo target, EventMessages messages) : base(new[] {target}, messages) + protected MovingNotification(MoveEventInfo target, EventMessages messages) + : base(new[] { target }, messages) { } - protected MovingNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + protected MovingNotification(IEnumerable> target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/MovingToRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/MovingToRecycleBinNotification.cs index d5e5842b3aad..1440dd9acc2f 100644 --- a/src/Umbraco.Core/Notifications/MovingToRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/MovingToRecycleBinNotification.cs @@ -7,12 +7,15 @@ namespace Umbraco.Cms.Core.Notifications; public abstract class MovingToRecycleBinNotification : CancelableObjectNotification>> { - protected MovingToRecycleBinNotification(MoveEventInfo target, EventMessages messages) : base(new[] {target}, + protected MovingToRecycleBinNotification(MoveEventInfo target, EventMessages messages) + : base( + new[] { target }, messages) { } - protected MovingToRecycleBinNotification(IEnumerable> target, EventMessages messages) : base( + protected MovingToRecycleBinNotification(IEnumerable> target, EventMessages messages) + : base( target, messages) { } diff --git a/src/Umbraco.Core/Notifications/NotificationExtensions.cs b/src/Umbraco.Core/Notifications/NotificationExtensions.cs index 30b259acaa94..540cf0840a25 100644 --- a/src/Umbraco.Core/Notifications/NotificationExtensions.cs +++ b/src/Umbraco.Core/Notifications/NotificationExtensions.cs @@ -10,6 +10,7 @@ public static T WithState(this T notification, IDictionary? } public static T WithStateFrom(this T notification, TSource source) - where T : IStatefulNotification where TSource : IStatefulNotification + where T : IStatefulNotification + where TSource : IStatefulNotification => notification.WithState(source.State); } diff --git a/src/Umbraco.Core/Notifications/ObjectNotification.cs b/src/Umbraco.Core/Notifications/ObjectNotification.cs index 2cf605544e9e..e7c60c5bbc13 100644 --- a/src/Umbraco.Core/Notifications/ObjectNotification.cs +++ b/src/Umbraco.Core/Notifications/ObjectNotification.cs @@ -5,7 +5,8 @@ namespace Umbraco.Cms.Core.Notifications; -public abstract class ObjectNotification : StatefulNotification where T : class +public abstract class ObjectNotification : StatefulNotification + where T : class { protected ObjectNotification(T target, EventMessages messages) { diff --git a/src/Umbraco.Core/Notifications/PartialViewCreatedNotification.cs b/src/Umbraco.Core/Notifications/PartialViewCreatedNotification.cs index 81d235c91c4e..3fe571843d53 100644 --- a/src/Umbraco.Core/Notifications/PartialViewCreatedNotification.cs +++ b/src/Umbraco.Core/Notifications/PartialViewCreatedNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public class PartialViewCreatedNotification : CreatedNotification { - public PartialViewCreatedNotification(IPartialView target, EventMessages messages) : base(target, messages) + public PartialViewCreatedNotification(IPartialView target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/PartialViewCreatingNotification.cs b/src/Umbraco.Core/Notifications/PartialViewCreatingNotification.cs index de8c71bac9d7..d53b4eb1c88d 100644 --- a/src/Umbraco.Core/Notifications/PartialViewCreatingNotification.cs +++ b/src/Umbraco.Core/Notifications/PartialViewCreatingNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public class PartialViewCreatingNotification : CreatingNotification { - public PartialViewCreatingNotification(IPartialView target, EventMessages messages) : base(target, messages) + public PartialViewCreatingNotification(IPartialView target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/PartialViewDeletedNotification.cs b/src/Umbraco.Core/Notifications/PartialViewDeletedNotification.cs index 6140caf011a9..29e1548bf326 100644 --- a/src/Umbraco.Core/Notifications/PartialViewDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/PartialViewDeletedNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public class PartialViewDeletedNotification : DeletedNotification { - public PartialViewDeletedNotification(IPartialView target, EventMessages messages) : base(target, messages) + public PartialViewDeletedNotification(IPartialView target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/PartialViewDeletingNotification.cs b/src/Umbraco.Core/Notifications/PartialViewDeletingNotification.cs index a6d4a543783f..ffabca9e2313 100644 --- a/src/Umbraco.Core/Notifications/PartialViewDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/PartialViewDeletingNotification.cs @@ -8,11 +8,14 @@ namespace Umbraco.Cms.Core.Notifications; public class PartialViewDeletingNotification : DeletingNotification { - public PartialViewDeletingNotification(IPartialView target, EventMessages messages) : base(target, messages) + public PartialViewDeletingNotification(IPartialView target, EventMessages messages) + : base(target, messages) { } - public PartialViewDeletingNotification(IEnumerable target, EventMessages messages) : base(target, + public PartialViewDeletingNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/PartialViewSavedNotification.cs b/src/Umbraco.Core/Notifications/PartialViewSavedNotification.cs index ba8f974c98ee..8700d59bd4e4 100644 --- a/src/Umbraco.Core/Notifications/PartialViewSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/PartialViewSavedNotification.cs @@ -8,11 +8,14 @@ namespace Umbraco.Cms.Core.Notifications; public class PartialViewSavedNotification : SavedNotification { - public PartialViewSavedNotification(IPartialView target, EventMessages messages) : base(target, messages) + public PartialViewSavedNotification(IPartialView target, EventMessages messages) + : base(target, messages) { } - public PartialViewSavedNotification(IEnumerable target, EventMessages messages) : base(target, + public PartialViewSavedNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/PartialViewSavingNotification.cs b/src/Umbraco.Core/Notifications/PartialViewSavingNotification.cs index adf73510b7c6..9239f1ed85be 100644 --- a/src/Umbraco.Core/Notifications/PartialViewSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/PartialViewSavingNotification.cs @@ -8,11 +8,14 @@ namespace Umbraco.Cms.Core.Notifications; public class PartialViewSavingNotification : SavingNotification { - public PartialViewSavingNotification(IPartialView target, EventMessages messages) : base(target, messages) + public PartialViewSavingNotification(IPartialView target, EventMessages messages) + : base(target, messages) { } - public PartialViewSavingNotification(IEnumerable target, EventMessages messages) : base(target, + public PartialViewSavingNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/PublicAccessCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/PublicAccessCacheRefresherNotification.cs index b50ef47ac328..8dec858f8319 100644 --- a/src/Umbraco.Core/Notifications/PublicAccessCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/PublicAccessCacheRefresherNotification.cs @@ -1,10 +1,12 @@ -using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Notifications; public class PublicAccessCacheRefresherNotification : CacheRefresherNotification { - public PublicAccessCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + public PublicAccessCacheRefresherNotification(object messageObject, MessageType messageType) + : base( + messageObject, messageType) { } diff --git a/src/Umbraco.Core/Notifications/PublicAccessEntryDeletedNotification.cs b/src/Umbraco.Core/Notifications/PublicAccessEntryDeletedNotification.cs index e15b6381c889..b0e80352c6ed 100644 --- a/src/Umbraco.Core/Notifications/PublicAccessEntryDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/PublicAccessEntryDeletedNotification.cs @@ -8,7 +8,9 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class PublicAccessEntryDeletedNotification : DeletedNotification { - public PublicAccessEntryDeletedNotification(PublicAccessEntry target, EventMessages messages) : base(target, + public PublicAccessEntryDeletedNotification(PublicAccessEntry target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/PublicAccessEntryDeletingNotification.cs b/src/Umbraco.Core/Notifications/PublicAccessEntryDeletingNotification.cs index 3b88d4cc100e..0d122badeb34 100644 --- a/src/Umbraco.Core/Notifications/PublicAccessEntryDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/PublicAccessEntryDeletingNotification.cs @@ -8,12 +8,15 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class PublicAccessEntryDeletingNotification : DeletingNotification { - public PublicAccessEntryDeletingNotification(PublicAccessEntry target, EventMessages messages) : base(target, + public PublicAccessEntryDeletingNotification(PublicAccessEntry target, EventMessages messages) + : base( + target, messages) { } - public PublicAccessEntryDeletingNotification(IEnumerable target, EventMessages messages) : base( + public PublicAccessEntryDeletingNotification(IEnumerable target, EventMessages messages) + : base( target, messages) { } diff --git a/src/Umbraco.Core/Notifications/PublicAccessEntrySavedNotification.cs b/src/Umbraco.Core/Notifications/PublicAccessEntrySavedNotification.cs index 2e6abf1f47fc..67c2df807c37 100644 --- a/src/Umbraco.Core/Notifications/PublicAccessEntrySavedNotification.cs +++ b/src/Umbraco.Core/Notifications/PublicAccessEntrySavedNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class PublicAccessEntrySavedNotification : SavedNotification { - public PublicAccessEntrySavedNotification(PublicAccessEntry target, EventMessages messages) : base(target, messages) + public PublicAccessEntrySavedNotification(PublicAccessEntry target, EventMessages messages) + : base(target, messages) { } - public PublicAccessEntrySavedNotification(IEnumerable target, EventMessages messages) : base( + public PublicAccessEntrySavedNotification(IEnumerable target, EventMessages messages) + : base( target, messages) { } diff --git a/src/Umbraco.Core/Notifications/PublicAccessEntrySavingNotification.cs b/src/Umbraco.Core/Notifications/PublicAccessEntrySavingNotification.cs index 4a284a61a204..c25d29e6f100 100644 --- a/src/Umbraco.Core/Notifications/PublicAccessEntrySavingNotification.cs +++ b/src/Umbraco.Core/Notifications/PublicAccessEntrySavingNotification.cs @@ -8,12 +8,15 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class PublicAccessEntrySavingNotification : SavingNotification { - public PublicAccessEntrySavingNotification(PublicAccessEntry target, EventMessages messages) : base(target, + public PublicAccessEntrySavingNotification(PublicAccessEntry target, EventMessages messages) + : base( + target, messages) { } - public PublicAccessEntrySavingNotification(IEnumerable target, EventMessages messages) : base( + public PublicAccessEntrySavingNotification(IEnumerable target, EventMessages messages) + : base( target, messages) { } diff --git a/src/Umbraco.Core/Notifications/RelationDeletedNotification.cs b/src/Umbraco.Core/Notifications/RelationDeletedNotification.cs index bd282c8a8e76..2d93e077c595 100644 --- a/src/Umbraco.Core/Notifications/RelationDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationDeletedNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public class RelationDeletedNotification : DeletedNotification { - public RelationDeletedNotification(IRelation target, EventMessages messages) : base(target, messages) + public RelationDeletedNotification(IRelation target, EventMessages messages) + : base(target, messages) { } - public RelationDeletedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public RelationDeletedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/RelationDeletingNotification.cs b/src/Umbraco.Core/Notifications/RelationDeletingNotification.cs index 07878334aefd..54b49afb54d2 100644 --- a/src/Umbraco.Core/Notifications/RelationDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationDeletingNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public class RelationDeletingNotification : DeletingNotification { - public RelationDeletingNotification(IRelation target, EventMessages messages) : base(target, messages) + public RelationDeletingNotification(IRelation target, EventMessages messages) + : base(target, messages) { } - public RelationDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public RelationDeletingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/RelationSavedNotification.cs b/src/Umbraco.Core/Notifications/RelationSavedNotification.cs index 8bbe427e6234..3a0b4d9ec823 100644 --- a/src/Umbraco.Core/Notifications/RelationSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationSavedNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public class RelationSavedNotification : SavedNotification { - public RelationSavedNotification(IRelation target, EventMessages messages) : base(target, messages) + public RelationSavedNotification(IRelation target, EventMessages messages) + : base(target, messages) { } - public RelationSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public RelationSavedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/RelationSavingNotification.cs b/src/Umbraco.Core/Notifications/RelationSavingNotification.cs index 65646bd3b696..069e0d5fdc3b 100644 --- a/src/Umbraco.Core/Notifications/RelationSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationSavingNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public class RelationSavingNotification : SavingNotification { - public RelationSavingNotification(IRelation target, EventMessages messages) : base(target, messages) + public RelationSavingNotification(IRelation target, EventMessages messages) + : base(target, messages) { } - public RelationSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public RelationSavingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/RelationTypeCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/RelationTypeCacheRefresherNotification.cs index 82c4478dbd7d..887840d9fce7 100644 --- a/src/Umbraco.Core/Notifications/RelationTypeCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationTypeCacheRefresherNotification.cs @@ -1,10 +1,12 @@ -using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Notifications; public class RelationTypeCacheRefresherNotification : CacheRefresherNotification { - public RelationTypeCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + public RelationTypeCacheRefresherNotification(object messageObject, MessageType messageType) + : base( + messageObject, messageType) { } diff --git a/src/Umbraco.Core/Notifications/RelationTypeDeletedNotification.cs b/src/Umbraco.Core/Notifications/RelationTypeDeletedNotification.cs index 602b1c5fd2c9..498a4c43706a 100644 --- a/src/Umbraco.Core/Notifications/RelationTypeDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationTypeDeletedNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public class RelationTypeDeletedNotification : DeletedNotification { - public RelationTypeDeletedNotification(IRelationType target, EventMessages messages) : base(target, messages) + public RelationTypeDeletedNotification(IRelationType target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/RelationTypeDeletingNotification.cs b/src/Umbraco.Core/Notifications/RelationTypeDeletingNotification.cs index aa44256b4219..8ca3698682f5 100644 --- a/src/Umbraco.Core/Notifications/RelationTypeDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationTypeDeletingNotification.cs @@ -8,11 +8,14 @@ namespace Umbraco.Cms.Core.Notifications; public class RelationTypeDeletingNotification : DeletingNotification { - public RelationTypeDeletingNotification(IRelationType target, EventMessages messages) : base(target, messages) + public RelationTypeDeletingNotification(IRelationType target, EventMessages messages) + : base(target, messages) { } - public RelationTypeDeletingNotification(IEnumerable target, EventMessages messages) : base(target, + public RelationTypeDeletingNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/RelationTypeSavedNotification.cs b/src/Umbraco.Core/Notifications/RelationTypeSavedNotification.cs index e0c815536689..519ca33ce9ac 100644 --- a/src/Umbraco.Core/Notifications/RelationTypeSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationTypeSavedNotification.cs @@ -8,11 +8,14 @@ namespace Umbraco.Cms.Core.Notifications; public class RelationTypeSavedNotification : SavedNotification { - public RelationTypeSavedNotification(IRelationType target, EventMessages messages) : base(target, messages) + public RelationTypeSavedNotification(IRelationType target, EventMessages messages) + : base(target, messages) { } - public RelationTypeSavedNotification(IEnumerable target, EventMessages messages) : base(target, + public RelationTypeSavedNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/RelationTypeSavingNotification.cs b/src/Umbraco.Core/Notifications/RelationTypeSavingNotification.cs index a978d55fc9c7..cb12d15b11e5 100644 --- a/src/Umbraco.Core/Notifications/RelationTypeSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationTypeSavingNotification.cs @@ -8,11 +8,14 @@ namespace Umbraco.Cms.Core.Notifications; public class RelationTypeSavingNotification : SavingNotification { - public RelationTypeSavingNotification(IRelationType target, EventMessages messages) : base(target, messages) + public RelationTypeSavingNotification(IRelationType target, EventMessages messages) + : base(target, messages) { } - public RelationTypeSavingNotification(IEnumerable target, EventMessages messages) : base(target, + public RelationTypeSavingNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/RemovedMemberRolesNotification.cs b/src/Umbraco.Core/Notifications/RemovedMemberRolesNotification.cs index 601c29eccb8d..4ae0a720f72f 100644 --- a/src/Umbraco.Core/Notifications/RemovedMemberRolesNotification.cs +++ b/src/Umbraco.Core/Notifications/RemovedMemberRolesNotification.cs @@ -2,7 +2,8 @@ namespace Umbraco.Cms.Core.Notifications; public class RemovedMemberRolesNotification : MemberRolesNotification { - public RemovedMemberRolesNotification(int[] memberIds, string[] roles) : base(memberIds, roles) + public RemovedMemberRolesNotification(int[] memberIds, string[] roles) + : base(memberIds, roles) { } } diff --git a/src/Umbraco.Core/Notifications/RenamedNotification.cs b/src/Umbraco.Core/Notifications/RenamedNotification.cs index 07856fc66552..ab25fbdeb96e 100644 --- a/src/Umbraco.Core/Notifications/RenamedNotification.cs +++ b/src/Umbraco.Core/Notifications/RenamedNotification.cs @@ -7,11 +7,13 @@ namespace Umbraco.Cms.Core.Notifications; public abstract class RenamedNotification : EnumerableObjectNotification { - protected RenamedNotification(T target, EventMessages messages) : base(target, messages) + protected RenamedNotification(T target, EventMessages messages) + : base(target, messages) { } - protected RenamedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + protected RenamedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/RenamingNotification.cs b/src/Umbraco.Core/Notifications/RenamingNotification.cs index 3940a1cfa0dc..4f15827ae4be 100644 --- a/src/Umbraco.Core/Notifications/RenamingNotification.cs +++ b/src/Umbraco.Core/Notifications/RenamingNotification.cs @@ -7,11 +7,13 @@ namespace Umbraco.Cms.Core.Notifications; public abstract class RenamingNotification : CancelableEnumerableObjectNotification { - protected RenamingNotification(T target, EventMessages messages) : base(target, messages) + protected RenamingNotification(T target, EventMessages messages) + : base(target, messages) { } - protected RenamingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + protected RenamingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/RolledBackNotification.cs b/src/Umbraco.Core/Notifications/RolledBackNotification.cs index 862c8d5c31e1..280f55538e98 100644 --- a/src/Umbraco.Core/Notifications/RolledBackNotification.cs +++ b/src/Umbraco.Core/Notifications/RolledBackNotification.cs @@ -5,9 +5,11 @@ namespace Umbraco.Cms.Core.Notifications; -public abstract class RolledBackNotification : ObjectNotification where T : class +public abstract class RolledBackNotification : ObjectNotification + where T : class { - protected RolledBackNotification(T target, EventMessages messages) : base(target, messages) + protected RolledBackNotification(T target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/RollingBackNotification.cs b/src/Umbraco.Core/Notifications/RollingBackNotification.cs index 64d5789cae90..3d06d443ea9d 100644 --- a/src/Umbraco.Core/Notifications/RollingBackNotification.cs +++ b/src/Umbraco.Core/Notifications/RollingBackNotification.cs @@ -5,9 +5,11 @@ namespace Umbraco.Cms.Core.Notifications; -public abstract class RollingBackNotification : CancelableObjectNotification where T : class +public abstract class RollingBackNotification : CancelableObjectNotification + where T : class { - protected RollingBackNotification(T target, EventMessages messages) : base(target, messages) + protected RollingBackNotification(T target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/RuntimeUnattendedUpgradeNotification.cs b/src/Umbraco.Core/Notifications/RuntimeUnattendedUpgradeNotification.cs index 30fc2025f015..fd1fa0211301 100644 --- a/src/Umbraco.Core/Notifications/RuntimeUnattendedUpgradeNotification.cs +++ b/src/Umbraco.Core/Notifications/RuntimeUnattendedUpgradeNotification.cs @@ -14,7 +14,7 @@ public enum UpgradeResult NotRequired = 0, HasErrors = 1, CoreUpgradeComplete = 100, - PackageMigrationComplete = 101 + PackageMigrationComplete = 101, } /// diff --git a/src/Umbraco.Core/Notifications/SavedNotification.cs b/src/Umbraco.Core/Notifications/SavedNotification.cs index 5fcae2a39eb4..655b9b66d10b 100644 --- a/src/Umbraco.Core/Notifications/SavedNotification.cs +++ b/src/Umbraco.Core/Notifications/SavedNotification.cs @@ -7,11 +7,13 @@ namespace Umbraco.Cms.Core.Notifications; public abstract class SavedNotification : EnumerableObjectNotification { - protected SavedNotification(T target, EventMessages messages) : base(target, messages) + protected SavedNotification(T target, EventMessages messages) + : base(target, messages) { } - protected SavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + protected SavedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/SavingNotification.cs b/src/Umbraco.Core/Notifications/SavingNotification.cs index 0af55857c208..9724d4580a7d 100644 --- a/src/Umbraco.Core/Notifications/SavingNotification.cs +++ b/src/Umbraco.Core/Notifications/SavingNotification.cs @@ -7,11 +7,13 @@ namespace Umbraco.Cms.Core.Notifications; public abstract class SavingNotification : CancelableEnumerableObjectNotification { - protected SavingNotification(T target, EventMessages messages) : base(target, messages) + protected SavingNotification(T target, EventMessages messages) + : base(target, messages) { } - protected SavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + protected SavingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/ScopedEntityRemoveNotification.cs b/src/Umbraco.Core/Notifications/ScopedEntityRemoveNotification.cs index 865f95e083a3..f72af376c311 100644 --- a/src/Umbraco.Core/Notifications/ScopedEntityRemoveNotification.cs +++ b/src/Umbraco.Core/Notifications/ScopedEntityRemoveNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; [EditorBrowsable(EditorBrowsableState.Never)] public class ScopedEntityRemoveNotification : ObjectNotification { - public ScopedEntityRemoveNotification(IContentBase target, EventMessages messages) : base(target, messages) + public ScopedEntityRemoveNotification(IContentBase target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/ScriptDeletedNotification.cs b/src/Umbraco.Core/Notifications/ScriptDeletedNotification.cs index d4150b2e94c1..3ca5f1dc4276 100644 --- a/src/Umbraco.Core/Notifications/ScriptDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/ScriptDeletedNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public class ScriptDeletedNotification : DeletedNotification { - public ScriptDeletedNotification(IScript target, EventMessages messages) : base(target, messages) + public ScriptDeletedNotification(IScript target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/ScriptDeletingNotification.cs b/src/Umbraco.Core/Notifications/ScriptDeletingNotification.cs index aa4675416c9c..946dc7f75009 100644 --- a/src/Umbraco.Core/Notifications/ScriptDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/ScriptDeletingNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public class ScriptDeletingNotification : DeletingNotification { - public ScriptDeletingNotification(IScript target, EventMessages messages) : base(target, messages) + public ScriptDeletingNotification(IScript target, EventMessages messages) + : base(target, messages) { } - public ScriptDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public ScriptDeletingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/ScriptSavedNotification.cs b/src/Umbraco.Core/Notifications/ScriptSavedNotification.cs index 8e70f3d9d68d..2a292383e9f0 100644 --- a/src/Umbraco.Core/Notifications/ScriptSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/ScriptSavedNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public class ScriptSavedNotification : SavedNotification { - public ScriptSavedNotification(IScript target, EventMessages messages) : base(target, messages) + public ScriptSavedNotification(IScript target, EventMessages messages) + : base(target, messages) { } - public ScriptSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public ScriptSavedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/ScriptSavingNotification.cs b/src/Umbraco.Core/Notifications/ScriptSavingNotification.cs index 87c8fd290b39..3ab2b13ce45b 100644 --- a/src/Umbraco.Core/Notifications/ScriptSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/ScriptSavingNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public class ScriptSavingNotification : SavingNotification { - public ScriptSavingNotification(IScript target, EventMessages messages) : base(target, messages) + public ScriptSavingNotification(IScript target, EventMessages messages) + : base(target, messages) { } - public ScriptSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public ScriptSavingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/ServerVariablesParsingNotification.cs b/src/Umbraco.Core/Notifications/ServerVariablesParsingNotification.cs index cfeb075de4ac..0171009bf2fb 100644 --- a/src/Umbraco.Core/Notifications/ServerVariablesParsingNotification.cs +++ b/src/Umbraco.Core/Notifications/ServerVariablesParsingNotification.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Notifications; +namespace Umbraco.Cms.Core.Notifications; /// /// A notification for when server variables are parsing diff --git a/src/Umbraco.Core/Notifications/SortedNotification.cs b/src/Umbraco.Core/Notifications/SortedNotification.cs index fd3af5b028d3..49910f82238a 100644 --- a/src/Umbraco.Core/Notifications/SortedNotification.cs +++ b/src/Umbraco.Core/Notifications/SortedNotification.cs @@ -7,7 +7,8 @@ namespace Umbraco.Cms.Core.Notifications; public abstract class SortedNotification : EnumerableObjectNotification { - protected SortedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + protected SortedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/SortingNotification.cs b/src/Umbraco.Core/Notifications/SortingNotification.cs index d46768f42b20..26e735f91b65 100644 --- a/src/Umbraco.Core/Notifications/SortingNotification.cs +++ b/src/Umbraco.Core/Notifications/SortingNotification.cs @@ -7,7 +7,8 @@ namespace Umbraco.Cms.Core.Notifications; public abstract class SortingNotification : CancelableEnumerableObjectNotification { - protected SortingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + protected SortingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/StylesheetDeletedNotification.cs b/src/Umbraco.Core/Notifications/StylesheetDeletedNotification.cs index 0afdfdbbdc5f..4b359d60ec8f 100644 --- a/src/Umbraco.Core/Notifications/StylesheetDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/StylesheetDeletedNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public class StylesheetDeletedNotification : DeletedNotification { - public StylesheetDeletedNotification(IStylesheet target, EventMessages messages) : base(target, messages) + public StylesheetDeletedNotification(IStylesheet target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/StylesheetDeletingNotification.cs b/src/Umbraco.Core/Notifications/StylesheetDeletingNotification.cs index 0bc6fe2a4e8c..e4ea920aa662 100644 --- a/src/Umbraco.Core/Notifications/StylesheetDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/StylesheetDeletingNotification.cs @@ -8,11 +8,14 @@ namespace Umbraco.Cms.Core.Notifications; public class StylesheetDeletingNotification : DeletingNotification { - public StylesheetDeletingNotification(IStylesheet target, EventMessages messages) : base(target, messages) + public StylesheetDeletingNotification(IStylesheet target, EventMessages messages) + : base(target, messages) { } - public StylesheetDeletingNotification(IEnumerable target, EventMessages messages) : base(target, + public StylesheetDeletingNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/StylesheetSavedNotification.cs b/src/Umbraco.Core/Notifications/StylesheetSavedNotification.cs index 97bb16830865..2f12bebe15bb 100644 --- a/src/Umbraco.Core/Notifications/StylesheetSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/StylesheetSavedNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public class StylesheetSavedNotification : SavedNotification { - public StylesheetSavedNotification(IStylesheet target, EventMessages messages) : base(target, messages) + public StylesheetSavedNotification(IStylesheet target, EventMessages messages) + : base(target, messages) { } - public StylesheetSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public StylesheetSavedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/StylesheetSavingNotification.cs b/src/Umbraco.Core/Notifications/StylesheetSavingNotification.cs index fca2846b811b..dbbf263ec419 100644 --- a/src/Umbraco.Core/Notifications/StylesheetSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/StylesheetSavingNotification.cs @@ -8,11 +8,14 @@ namespace Umbraco.Cms.Core.Notifications; public class StylesheetSavingNotification : SavingNotification { - public StylesheetSavingNotification(IStylesheet target, EventMessages messages) : base(target, messages) + public StylesheetSavingNotification(IStylesheet target, EventMessages messages) + : base(target, messages) { } - public StylesheetSavingNotification(IEnumerable target, EventMessages messages) : base(target, + public StylesheetSavingNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/TemplateCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/TemplateCacheRefresherNotification.cs index c1f76cfb8a18..296d8538b2a9 100644 --- a/src/Umbraco.Core/Notifications/TemplateCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/TemplateCacheRefresherNotification.cs @@ -1,10 +1,12 @@ -using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Notifications; public class TemplateCacheRefresherNotification : CacheRefresherNotification { - public TemplateCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + public TemplateCacheRefresherNotification(object messageObject, MessageType messageType) + : base( + messageObject, messageType) { } diff --git a/src/Umbraco.Core/Notifications/TemplateDeletedNotification.cs b/src/Umbraco.Core/Notifications/TemplateDeletedNotification.cs index f619b26aeb21..1bab7d2dc510 100644 --- a/src/Umbraco.Core/Notifications/TemplateDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/TemplateDeletedNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public class TemplateDeletedNotification : DeletedNotification { - public TemplateDeletedNotification(ITemplate target, EventMessages messages) : base(target, messages) + public TemplateDeletedNotification(ITemplate target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/TemplateDeletingNotification.cs b/src/Umbraco.Core/Notifications/TemplateDeletingNotification.cs index 61463d331f3f..791f43d116c3 100644 --- a/src/Umbraco.Core/Notifications/TemplateDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/TemplateDeletingNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public class TemplateDeletingNotification : DeletingNotification { - public TemplateDeletingNotification(ITemplate target, EventMessages messages) : base(target, messages) + public TemplateDeletingNotification(ITemplate target, EventMessages messages) + : base(target, messages) { } - public TemplateDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public TemplateDeletingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/TemplateSavedNotification.cs b/src/Umbraco.Core/Notifications/TemplateSavedNotification.cs index 4505f1b80c68..8b51e795d4bb 100644 --- a/src/Umbraco.Core/Notifications/TemplateSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/TemplateSavedNotification.cs @@ -8,14 +8,16 @@ namespace Umbraco.Cms.Core.Notifications; public class TemplateSavedNotification : SavedNotification { - private const string s_templateForContentTypeKey = "CreateTemplateForContentType"; - private const string s_contentTypeAliasKey = "ContentTypeAlias"; + private const string TemplateForContentTypeKey = "CreateTemplateForContentType"; + private const string ContentTypeAliasKey = "ContentTypeAlias"; - public TemplateSavedNotification(ITemplate target, EventMessages messages) : base(target, messages) + public TemplateSavedNotification(ITemplate target, EventMessages messages) + : base(target, messages) { } - public TemplateSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public TemplateSavedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } @@ -23,7 +25,7 @@ public bool CreateTemplateForContentType { get { - if (State?.TryGetValue(s_templateForContentTypeKey, out var result) ?? false) + if (State?.TryGetValue(TemplateForContentTypeKey, out var result) ?? false) { if (result is not bool createTemplate) { @@ -35,11 +37,12 @@ public bool CreateTemplateForContentType return false; } + set { if (!value is bool && State is not null) { - State[s_templateForContentTypeKey] = value; + State[TemplateForContentTypeKey] = value; } } } @@ -48,7 +51,7 @@ public string? ContentTypeAlias { get { - if (State?.TryGetValue(s_contentTypeAliasKey, out var result) ?? false) + if (State?.TryGetValue(ContentTypeAliasKey, out var result) ?? false) { return result as string; } @@ -60,7 +63,7 @@ public string? ContentTypeAlias { if (value is not null && State is not null) { - State[s_contentTypeAliasKey] = value; + State[ContentTypeAliasKey] = value; } } } diff --git a/src/Umbraco.Core/Notifications/TemplateSavingNotification.cs b/src/Umbraco.Core/Notifications/TemplateSavingNotification.cs index ec03747e4e93..45a325feed22 100644 --- a/src/Umbraco.Core/Notifications/TemplateSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/TemplateSavingNotification.cs @@ -8,27 +8,28 @@ namespace Umbraco.Cms.Core.Notifications; public class TemplateSavingNotification : SavingNotification { - private const string s_templateForContentTypeKey = "CreateTemplateForContentType"; - private const string s_contentTypeAliasKey = "ContentTypeAlias"; + private const string TemplateForContentTypeKey = "CreateTemplateForContentType"; + private const string ContentTypeAliasKey = "ContentTypeAlias"; - public TemplateSavingNotification(ITemplate target, EventMessages messages) : base(target, messages) + public TemplateSavingNotification(ITemplate target, EventMessages messages) + : base(target, messages) { } - public TemplateSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public TemplateSavingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } - public TemplateSavingNotification(ITemplate target, EventMessages messages, bool createTemplateForContentType, - string contentTypeAlias) : base(target, messages) + public TemplateSavingNotification(ITemplate target, EventMessages messages, bool createTemplateForContentType, string contentTypeAlias) + : base(target, messages) { CreateTemplateForContentType = createTemplateForContentType; ContentTypeAlias = contentTypeAlias; } - public TemplateSavingNotification(IEnumerable target, EventMessages messages, - bool createTemplateForContentType, - string contentTypeAlias) : base(target, messages) + public TemplateSavingNotification(IEnumerable target, EventMessages messages, bool createTemplateForContentType, string contentTypeAlias) + : base(target, messages) { CreateTemplateForContentType = createTemplateForContentType; ContentTypeAlias = contentTypeAlias; @@ -38,7 +39,7 @@ public bool CreateTemplateForContentType { get { - if (State?.TryGetValue(s_templateForContentTypeKey, out var result) ?? false) + if (State?.TryGetValue(TemplateForContentTypeKey, out var result) ?? false) { if (result is not bool createTemplate) { @@ -50,11 +51,12 @@ public bool CreateTemplateForContentType return false; } + set { if (!value is bool && State is not null) { - State[s_templateForContentTypeKey] = value; + State[TemplateForContentTypeKey] = value; } } } @@ -63,7 +65,7 @@ public string? ContentTypeAlias { get { - if (State?.TryGetValue(s_contentTypeAliasKey, out var result) ?? false) + if (State?.TryGetValue(ContentTypeAliasKey, out var result) ?? false) { return result as string; } @@ -75,7 +77,7 @@ public string? ContentTypeAlias { if (value is not null && State is not null) { - State[s_contentTypeAliasKey] = value; + State[ContentTypeAliasKey] = value; } } } diff --git a/src/Umbraco.Core/Notifications/TreeChangeNotification.cs b/src/Umbraco.Core/Notifications/TreeChangeNotification.cs index 6cd65682838a..2187f726597f 100644 --- a/src/Umbraco.Core/Notifications/TreeChangeNotification.cs +++ b/src/Umbraco.Core/Notifications/TreeChangeNotification.cs @@ -5,11 +5,13 @@ namespace Umbraco.Cms.Core.Notifications; public abstract class TreeChangeNotification : EnumerableObjectNotification> { - protected TreeChangeNotification(TreeChange target, EventMessages messages) : base(target, messages) + protected TreeChangeNotification(TreeChange target, EventMessages messages) + : base(target, messages) { } - protected TreeChangeNotification(IEnumerable> target, EventMessages messages) : base(target, messages) + protected TreeChangeNotification(IEnumerable> target, EventMessages messages) + : base(target, messages) { } diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationComponentsInstallingNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationComponentsInstallingNotification.cs index 715ef5a81fce..036d5cf8a42a 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationComponentsInstallingNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationComponentsInstallingNotification.cs @@ -2,6 +2,7 @@ // See LICENSE for more details. namespace Umbraco.Cms.Core.Notifications; + // TODO (V10): Remove this class. /// diff --git a/src/Umbraco.Core/Notifications/UmbracoApplicationMainDomAcquiredNotification.cs b/src/Umbraco.Core/Notifications/UmbracoApplicationMainDomAcquiredNotification.cs index 7aa1da0e5067..2bbab6e7eced 100644 --- a/src/Umbraco.Core/Notifications/UmbracoApplicationMainDomAcquiredNotification.cs +++ b/src/Umbraco.Core/Notifications/UmbracoApplicationMainDomAcquiredNotification.cs @@ -2,6 +2,7 @@ // See LICENSE for more details. namespace Umbraco.Cms.Core.Notifications; + // TODO (V10): Remove this class. /// @@ -16,7 +17,6 @@ public class UmbracoApplicationMainDomAcquiredNotification : INotification /// /// Initializes a new instance of the class. /// - /// The runtime level public UmbracoApplicationMainDomAcquiredNotification() { } diff --git a/src/Umbraco.Core/Notifications/UserCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/UserCacheRefresherNotification.cs index 60ea00bc3f9f..35daf62b2cb2 100644 --- a/src/Umbraco.Core/Notifications/UserCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/UserCacheRefresherNotification.cs @@ -1,10 +1,12 @@ -using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Notifications; public class UserCacheRefresherNotification : CacheRefresherNotification { - public UserCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + public UserCacheRefresherNotification(object messageObject, MessageType messageType) + : base( + messageObject, messageType) { } diff --git a/src/Umbraco.Core/Notifications/UserDeletedNotification.cs b/src/Umbraco.Core/Notifications/UserDeletedNotification.cs index 7518340c679e..a5d89bf1674b 100644 --- a/src/Umbraco.Core/Notifications/UserDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserDeletedNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class UserDeletedNotification : DeletedNotification { - public UserDeletedNotification(IUser target, EventMessages messages) : base(target, messages) + public UserDeletedNotification(IUser target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/UserDeletingNotification.cs b/src/Umbraco.Core/Notifications/UserDeletingNotification.cs index 3fca9e799ba7..611f8aa0ea22 100644 --- a/src/Umbraco.Core/Notifications/UserDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/UserDeletingNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class UserDeletingNotification : DeletingNotification { - public UserDeletingNotification(IUser target, EventMessages messages) : base(target, messages) + public UserDeletingNotification(IUser target, EventMessages messages) + : base(target, messages) { } - public UserDeletingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public UserDeletingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/UserForgotPasswordChangedNotification.cs b/src/Umbraco.Core/Notifications/UserForgotPasswordChangedNotification.cs index e23b6b4f9925..b40e902e104b 100644 --- a/src/Umbraco.Core/Notifications/UserForgotPasswordChangedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserForgotPasswordChangedNotification.cs @@ -2,8 +2,8 @@ namespace Umbraco.Cms.Core.Notifications; public class UserForgotPasswordChangedNotification : UserNotification { - public UserForgotPasswordChangedNotification(string ipAddress, string affectedUserId, string performingUserId) : - base(ipAddress, affectedUserId, performingUserId) + public UserForgotPasswordChangedNotification(string ipAddress, string affectedUserId, string performingUserId) + : base(ipAddress, affectedUserId, performingUserId) { } } diff --git a/src/Umbraco.Core/Notifications/UserForgotPasswordRequestedNotification.cs b/src/Umbraco.Core/Notifications/UserForgotPasswordRequestedNotification.cs index bd6f35da0cc6..6181a33809bf 100644 --- a/src/Umbraco.Core/Notifications/UserForgotPasswordRequestedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserForgotPasswordRequestedNotification.cs @@ -2,8 +2,8 @@ namespace Umbraco.Cms.Core.Notifications; public class UserForgotPasswordRequestedNotification : UserNotification { - public UserForgotPasswordRequestedNotification(string ipAddress, string affectedUserId, string performingUserId) : - base(ipAddress, affectedUserId, performingUserId) + public UserForgotPasswordRequestedNotification(string ipAddress, string affectedUserId, string performingUserId) + : base(ipAddress, affectedUserId, performingUserId) { } } diff --git a/src/Umbraco.Core/Notifications/UserGroupCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/UserGroupCacheRefresherNotification.cs index 3e2075a0ad23..946a8a4b0470 100644 --- a/src/Umbraco.Core/Notifications/UserGroupCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/UserGroupCacheRefresherNotification.cs @@ -1,10 +1,12 @@ -using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Notifications; public class UserGroupCacheRefresherNotification : CacheRefresherNotification { - public UserGroupCacheRefresherNotification(object messageObject, MessageType messageType) : base(messageObject, + public UserGroupCacheRefresherNotification(object messageObject, MessageType messageType) + : base( + messageObject, messageType) { } diff --git a/src/Umbraco.Core/Notifications/UserGroupDeletedNotification.cs b/src/Umbraco.Core/Notifications/UserGroupDeletedNotification.cs index f83e176cae11..0555611f3a34 100644 --- a/src/Umbraco.Core/Notifications/UserGroupDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserGroupDeletedNotification.cs @@ -8,7 +8,8 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class UserGroupDeletedNotification : DeletedNotification { - public UserGroupDeletedNotification(IUserGroup target, EventMessages messages) : base(target, messages) + public UserGroupDeletedNotification(IUserGroup target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/UserGroupDeletingNotification.cs b/src/Umbraco.Core/Notifications/UserGroupDeletingNotification.cs index bceea00f01ec..729d35152d67 100644 --- a/src/Umbraco.Core/Notifications/UserGroupDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/UserGroupDeletingNotification.cs @@ -8,11 +8,14 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class UserGroupDeletingNotification : DeletingNotification { - public UserGroupDeletingNotification(IUserGroup target, EventMessages messages) : base(target, messages) + public UserGroupDeletingNotification(IUserGroup target, EventMessages messages) + : base(target, messages) { } - public UserGroupDeletingNotification(IEnumerable target, EventMessages messages) : base(target, + public UserGroupDeletingNotification(IEnumerable target, EventMessages messages) + : base( + target, messages) { } diff --git a/src/Umbraco.Core/Notifications/UserGroupSavedNotification.cs b/src/Umbraco.Core/Notifications/UserGroupSavedNotification.cs index 75e95be87834..aa4484c3d341 100644 --- a/src/Umbraco.Core/Notifications/UserGroupSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserGroupSavedNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class UserGroupSavedNotification : SavedNotification { - public UserGroupSavedNotification(IUserGroup target, EventMessages messages) : base(target, messages) + public UserGroupSavedNotification(IUserGroup target, EventMessages messages) + : base(target, messages) { } - public UserGroupSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public UserGroupSavedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/UserGroupSavingNotification.cs b/src/Umbraco.Core/Notifications/UserGroupSavingNotification.cs index f1e2840493af..06c82c0298d3 100644 --- a/src/Umbraco.Core/Notifications/UserGroupSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/UserGroupSavingNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class UserGroupSavingNotification : SavingNotification { - public UserGroupSavingNotification(IUserGroup target, EventMessages messages) : base(target, messages) + public UserGroupSavingNotification(IUserGroup target, EventMessages messages) + : base(target, messages) { } - public UserGroupSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public UserGroupSavingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/UserGroupWithUsersSavedNotification.cs b/src/Umbraco.Core/Notifications/UserGroupWithUsersSavedNotification.cs index 22d6ebff7880..31565db85e0c 100644 --- a/src/Umbraco.Core/Notifications/UserGroupWithUsersSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserGroupWithUsersSavedNotification.cs @@ -7,12 +7,15 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class UserGroupWithUsersSavedNotification : SavedNotification { - public UserGroupWithUsersSavedNotification(UserGroupWithUsers target, EventMessages messages) : base(target, + public UserGroupWithUsersSavedNotification(UserGroupWithUsers target, EventMessages messages) + : base( + target, messages) { } - public UserGroupWithUsersSavedNotification(IEnumerable target, EventMessages messages) : base( + public UserGroupWithUsersSavedNotification(IEnumerable target, EventMessages messages) + : base( target, messages) { } diff --git a/src/Umbraco.Core/Notifications/UserGroupWithUsersSavingNotification.cs b/src/Umbraco.Core/Notifications/UserGroupWithUsersSavingNotification.cs index 2c5513e64485..c34d66841c1a 100644 --- a/src/Umbraco.Core/Notifications/UserGroupWithUsersSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/UserGroupWithUsersSavingNotification.cs @@ -7,12 +7,15 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class UserGroupWithUsersSavingNotification : SavingNotification { - public UserGroupWithUsersSavingNotification(UserGroupWithUsers target, EventMessages messages) : base(target, + public UserGroupWithUsersSavingNotification(UserGroupWithUsers target, EventMessages messages) + : base( + target, messages) { } - public UserGroupWithUsersSavingNotification(IEnumerable target, EventMessages messages) : base( + public UserGroupWithUsersSavingNotification(IEnumerable target, EventMessages messages) + : base( target, messages) { } diff --git a/src/Umbraco.Core/Notifications/UserLockedNotification.cs b/src/Umbraco.Core/Notifications/UserLockedNotification.cs index 2a366987bad1..9016ccb938f4 100644 --- a/src/Umbraco.Core/Notifications/UserLockedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserLockedNotification.cs @@ -2,8 +2,11 @@ namespace Umbraco.Cms.Core.Notifications; public class UserLockedNotification : UserNotification { - public UserLockedNotification(string ipAddress, string? affectedUserId, string performingUserId) : base(ipAddress, - affectedUserId, performingUserId) + public UserLockedNotification(string ipAddress, string? affectedUserId, string performingUserId) + : base( + ipAddress, + affectedUserId, + performingUserId) { } } diff --git a/src/Umbraco.Core/Notifications/UserLoginFailedNotification.cs b/src/Umbraco.Core/Notifications/UserLoginFailedNotification.cs index d5d1b1dbc79b..a8cb3e9cc448 100644 --- a/src/Umbraco.Core/Notifications/UserLoginFailedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserLoginFailedNotification.cs @@ -2,7 +2,8 @@ namespace Umbraco.Cms.Core.Notifications; public class UserLoginFailedNotification : UserNotification { - public UserLoginFailedNotification(string ipAddress, string affectedUserId, string performingUserId) : base( + public UserLoginFailedNotification(string ipAddress, string affectedUserId, string performingUserId) + : base( ipAddress, affectedUserId, performingUserId) { } diff --git a/src/Umbraco.Core/Notifications/UserLoginSuccessNotification.cs b/src/Umbraco.Core/Notifications/UserLoginSuccessNotification.cs index f2b8d09e5de7..e10d221de6a2 100644 --- a/src/Umbraco.Core/Notifications/UserLoginSuccessNotification.cs +++ b/src/Umbraco.Core/Notifications/UserLoginSuccessNotification.cs @@ -2,7 +2,8 @@ namespace Umbraco.Cms.Core.Notifications; public class UserLoginSuccessNotification : UserNotification { - public UserLoginSuccessNotification(string ipAddress, string affectedUserId, string performingUserId) : base( + public UserLoginSuccessNotification(string ipAddress, string affectedUserId, string performingUserId) + : base( ipAddress, affectedUserId, performingUserId) { } diff --git a/src/Umbraco.Core/Notifications/UserLogoutSuccessNotification.cs b/src/Umbraco.Core/Notifications/UserLogoutSuccessNotification.cs index 6af01f9d13bd..ecc764b117e2 100644 --- a/src/Umbraco.Core/Notifications/UserLogoutSuccessNotification.cs +++ b/src/Umbraco.Core/Notifications/UserLogoutSuccessNotification.cs @@ -2,7 +2,8 @@ namespace Umbraco.Cms.Core.Notifications; public class UserLogoutSuccessNotification : UserNotification { - public UserLogoutSuccessNotification(string ipAddress, string? affectedUserId, string performingUserId) : base( + public UserLogoutSuccessNotification(string ipAddress, string? affectedUserId, string performingUserId) + : base( ipAddress, affectedUserId, performingUserId) { } diff --git a/src/Umbraco.Core/Notifications/UserPasswordChangedNotification.cs b/src/Umbraco.Core/Notifications/UserPasswordChangedNotification.cs index 1aec4f485ae5..a7cd1e51aebc 100644 --- a/src/Umbraco.Core/Notifications/UserPasswordChangedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserPasswordChangedNotification.cs @@ -2,7 +2,8 @@ namespace Umbraco.Cms.Core.Notifications; public class UserPasswordChangedNotification : UserNotification { - public UserPasswordChangedNotification(string ipAddress, string affectedUserId, string performingUserId) : base( + public UserPasswordChangedNotification(string ipAddress, string affectedUserId, string performingUserId) + : base( ipAddress, affectedUserId, performingUserId) { } diff --git a/src/Umbraco.Core/Notifications/UserPasswordResetNotification.cs b/src/Umbraco.Core/Notifications/UserPasswordResetNotification.cs index b79f2333aa03..47edf85bc353 100644 --- a/src/Umbraco.Core/Notifications/UserPasswordResetNotification.cs +++ b/src/Umbraco.Core/Notifications/UserPasswordResetNotification.cs @@ -2,7 +2,8 @@ namespace Umbraco.Cms.Core.Notifications; public class UserPasswordResetNotification : UserNotification { - public UserPasswordResetNotification(string ipAddress, string affectedUserId, string performingUserId) : base( + public UserPasswordResetNotification(string ipAddress, string affectedUserId, string performingUserId) + : base( ipAddress, affectedUserId, performingUserId) { } diff --git a/src/Umbraco.Core/Notifications/UserResetAccessFailedCountNotification.cs b/src/Umbraco.Core/Notifications/UserResetAccessFailedCountNotification.cs index 03fcdebbd8f1..f1cce2df63a0 100644 --- a/src/Umbraco.Core/Notifications/UserResetAccessFailedCountNotification.cs +++ b/src/Umbraco.Core/Notifications/UserResetAccessFailedCountNotification.cs @@ -2,8 +2,8 @@ namespace Umbraco.Cms.Core.Notifications; public class UserResetAccessFailedCountNotification : UserNotification { - public UserResetAccessFailedCountNotification(string ipAddress, string affectedUserId, string performingUserId) : - base(ipAddress, affectedUserId, performingUserId) + public UserResetAccessFailedCountNotification(string ipAddress, string affectedUserId, string performingUserId) + : base(ipAddress, affectedUserId, performingUserId) { } } diff --git a/src/Umbraco.Core/Notifications/UserSavedNotification.cs b/src/Umbraco.Core/Notifications/UserSavedNotification.cs index fa80753c8470..8292cb9f6d19 100644 --- a/src/Umbraco.Core/Notifications/UserSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserSavedNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class UserSavedNotification : SavedNotification { - public UserSavedNotification(IUser target, EventMessages messages) : base(target, messages) + public UserSavedNotification(IUser target, EventMessages messages) + : base(target, messages) { } - public UserSavedNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public UserSavedNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/UserSavingNotification.cs b/src/Umbraco.Core/Notifications/UserSavingNotification.cs index 2922b9a1b152..3760f02881ac 100644 --- a/src/Umbraco.Core/Notifications/UserSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/UserSavingNotification.cs @@ -8,11 +8,13 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class UserSavingNotification : SavingNotification { - public UserSavingNotification(IUser target, EventMessages messages) : base(target, messages) + public UserSavingNotification(IUser target, EventMessages messages) + : base(target, messages) { } - public UserSavingNotification(IEnumerable target, EventMessages messages) : base(target, messages) + public UserSavingNotification(IEnumerable target, EventMessages messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/UserTwoFactorRequestedNotification.cs b/src/Umbraco.Core/Notifications/UserTwoFactorRequestedNotification.cs index 93c9432b5e8a..1eb6d774d0ce 100644 --- a/src/Umbraco.Core/Notifications/UserTwoFactorRequestedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserTwoFactorRequestedNotification.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Notifications; +namespace Umbraco.Cms.Core.Notifications; public class UserTwoFactorRequestedNotification : INotification { diff --git a/src/Umbraco.Core/Notifications/UserUnlockedNotification.cs b/src/Umbraco.Core/Notifications/UserUnlockedNotification.cs index 1fbdbe1589e9..4c6a686069c9 100644 --- a/src/Umbraco.Core/Notifications/UserUnlockedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserUnlockedNotification.cs @@ -2,7 +2,9 @@ namespace Umbraco.Cms.Core.Notifications; public class UserUnlockedNotification : UserNotification { - public UserUnlockedNotification(string ipAddress, string affectedUserId, string performingUserId) : base(ipAddress, + public UserUnlockedNotification(string ipAddress, string affectedUserId, string performingUserId) + : base( + ipAddress, affectedUserId, performingUserId) { } diff --git a/src/Umbraco.Core/Packaging/PackageDefinition.cs b/src/Umbraco.Core/Packaging/PackageDefinition.cs index cbf682ad106b..7b0b5f5df491 100644 --- a/src/Umbraco.Core/Packaging/PackageDefinition.cs +++ b/src/Umbraco.Core/Packaging/PackageDefinition.cs @@ -13,11 +13,15 @@ namespace Umbraco.Cms.Core.Packaging; [DataContract(Name = "packageInstance")] public class PackageDefinition { - [DataMember(Name = "id")] public int Id { get; set; } + [DataMember(Name = "id")] + public int Id { get; set; } - [DataMember(Name = "packageGuid")] public Guid PackageId { get; set; } + [DataMember(Name = "packageGuid")] + public Guid PackageId { get; set; } - [DataMember(Name = "name")] [Required] public string Name { get; set; } = string.Empty; + [DataMember(Name = "name")] + [Required] + public string Name { get; set; } = string.Empty; /// /// The full path to the package's XML file. @@ -29,29 +33,41 @@ public class PackageDefinition [DataMember(Name = "contentLoadChildNodes")] public bool ContentLoadChildNodes { get; set; } - [DataMember(Name = "contentNodeId")] public string? ContentNodeId { get; set; } + [DataMember(Name = "contentNodeId")] + public string? ContentNodeId { get; set; } - [DataMember(Name = "macros")] public IList Macros { get; set; } = new List(); + [DataMember(Name = "macros")] + public IList Macros { get; set; } = new List(); - [DataMember(Name = "languages")] public IList Languages { get; set; } = new List(); + [DataMember(Name = "languages")] + public IList Languages { get; set; } = new List(); - [DataMember(Name = "dictionaryItems")] public IList DictionaryItems { get; set; } = new List(); + [DataMember(Name = "dictionaryItems")] + public IList DictionaryItems { get; set; } = new List(); - [DataMember(Name = "templates")] public IList Templates { get; set; } = new List(); + [DataMember(Name = "templates")] + public IList Templates { get; set; } = new List(); - [DataMember(Name = "partialViews")] public IList PartialViews { get; set; } = new List(); + [DataMember(Name = "partialViews")] + public IList PartialViews { get; set; } = new List(); - [DataMember(Name = "documentTypes")] public IList DocumentTypes { get; set; } = new List(); + [DataMember(Name = "documentTypes")] + public IList DocumentTypes { get; set; } = new List(); - [DataMember(Name = "mediaTypes")] public IList MediaTypes { get; set; } = new List(); + [DataMember(Name = "mediaTypes")] + public IList MediaTypes { get; set; } = new List(); - [DataMember(Name = "stylesheets")] public IList Stylesheets { get; set; } = new List(); + [DataMember(Name = "stylesheets")] + public IList Stylesheets { get; set; } = new List(); - [DataMember(Name = "scripts")] public IList Scripts { get; set; } = new List(); + [DataMember(Name = "scripts")] + public IList Scripts { get; set; } = new List(); - [DataMember(Name = "dataTypes")] public IList DataTypes { get; set; } = new List(); + [DataMember(Name = "dataTypes")] + public IList DataTypes { get; set; } = new List(); - [DataMember(Name = "mediaUdis")] public IList MediaUdis { get; set; } = new List(); + [DataMember(Name = "mediaUdis")] + public IList MediaUdis { get; set; } = new List(); [DataMember(Name = "mediaLoadChildNodes")] public bool MediaLoadChildNodes { get; set; } diff --git a/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs b/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs index 9bb2d0d69c4d..01221480c4e5 100644 --- a/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs +++ b/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs @@ -8,11 +8,10 @@ namespace Umbraco.Cms.Core.Packaging; /// public class PackageDefinitionXmlParser { - private static readonly IList s_emptyStringList = new List(); - private static readonly IList s_emptyGuidUdiList = new List(); + private static readonly IList EmptyStringList = new List(); + private static readonly IList EmptyGuidUdiList = new List(); - - public PackageDefinition? ToPackageDefinition(XElement xml) + public PackageDefinition? ToPackageDefinition(XElement? xml) { if (xml == null) { @@ -29,46 +28,46 @@ public class PackageDefinitionXmlParser ContentLoadChildNodes = xml.Element("content")?.AttributeValue("loadChildNodes") ?? false, MediaUdis = xml.Element("media")?.Elements("nodeUdi").Select(x => (GuidUdi)UdiParser.Parse(x.Value)).ToList() ?? - s_emptyGuidUdiList, + EmptyGuidUdiList, MediaLoadChildNodes = xml.Element("media")?.AttributeValue("loadChildNodes") ?? false, Macros = xml.Element("macros")?.Value .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? - s_emptyStringList, + EmptyStringList, Templates = xml.Element("templates")?.Value .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? - s_emptyStringList, + EmptyStringList, Stylesheets = xml.Element("stylesheets")?.Value .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? - s_emptyStringList, + EmptyStringList, Scripts = xml.Element("scripts")?.Value .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? - s_emptyStringList, + EmptyStringList, PartialViews = xml.Element("partialViews")?.Value .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? - s_emptyStringList, + EmptyStringList, DocumentTypes = xml.Element("documentTypes")?.Value .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? - s_emptyStringList, + EmptyStringList, MediaTypes = - xml.Element("mediaTypes")?.Value.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries) - .ToList() ?? s_emptyStringList, + xml.Element("mediaTypes")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .ToList() ?? EmptyStringList, Languages = xml.Element("languages")?.Value .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? - s_emptyStringList, + EmptyStringList, DictionaryItems = xml.Element("dictionaryitems")?.Value .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? - s_emptyStringList, + EmptyStringList, DataTypes = xml.Element("datatypes")?.Value .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? - s_emptyStringList + EmptyStringList, }; return retVal; @@ -76,13 +75,15 @@ public class PackageDefinitionXmlParser public XElement ToXml(PackageDefinition def) { - var packageXml = new XElement("package", + var packageXml = new XElement( + "package", new XAttribute("id", def.Id), new XAttribute("name", def.Name ?? string.Empty), new XAttribute("packagePath", def.PackagePath ?? string.Empty), new XAttribute("packageGuid", def.PackageId), new XElement("datatypes", string.Join(",", def.DataTypes ?? Array.Empty())), - new XElement("content", + new XElement( + "content", new XAttribute("nodeId", def.ContentNodeId ?? string.Empty), new XAttribute("loadChildNodes", def.ContentLoadChildNodes)), new XElement("templates", string.Join(",", def.Templates ?? Array.Empty())), @@ -97,8 +98,7 @@ public XElement ToXml(PackageDefinition def) new XElement( "media", def.MediaUdis.Select(x => (object)new XElement("nodeUdi", x)) - .Union(new[] {new XAttribute("loadChildNodes", def.MediaLoadChildNodes)})) - ); + .Union(new[] { new XAttribute("loadChildNodes", def.MediaLoadChildNodes) }))); return packageXml; } } diff --git a/src/Umbraco.Core/Packaging/PackageMigrationResource.cs b/src/Umbraco.Core/Packaging/PackageMigrationResource.cs index 6f22a6d32e30..ef25ab0b3dd8 100644 --- a/src/Umbraco.Core/Packaging/PackageMigrationResource.cs +++ b/src/Umbraco.Core/Packaging/PackageMigrationResource.cs @@ -8,20 +8,10 @@ namespace Umbraco.Cms.Core.Packaging; public static class PackageMigrationResource { - private static Stream? GetEmbeddedPackageZipStream(Type planType) - { - // lookup the embedded resource by convention - Assembly currentAssembly = planType.Assembly; - var fileName = $"{planType.Namespace}.package.zip"; - Stream? stream = currentAssembly.GetManifestResourceStream(fileName); - - return stream; - } - public static XDocument? GetEmbeddedPackageDataManifest(Type planType, out ZipArchive? zipArchive) { XDocument? packageXml; - Stream zipStream = GetEmbeddedPackageZipStream(planType); + Stream? zipStream = GetEmbeddedPackageZipStream(planType); if (zipStream is not null) { zipArchive = GetPackageDataManifest(zipStream, out packageXml); @@ -33,36 +23,25 @@ public static class PackageMigrationResource return packageXml; } - public static XDocument? GetEmbeddedPackageDataManifest(Type planType) => - GetEmbeddedPackageDataManifest(planType, out _); - - private static XDocument? GetEmbeddedPackageXmlDoc(Type planType) + private static Stream? GetEmbeddedPackageZipStream(Type planType) { // lookup the embedded resource by convention Assembly currentAssembly = planType.Assembly; - var fileName = $"{planType.Namespace}.package.xml"; + var fileName = $"{planType.Namespace}.package.zip"; Stream? stream = currentAssembly.GetManifestResourceStream(fileName); - if (stream == null) - { - return null; - } - - XDocument xml; - using (stream) - { - xml = XDocument.Load(stream); - } - return xml; + return stream; } + public static XDocument? GetEmbeddedPackageDataManifest(Type planType) => + GetEmbeddedPackageDataManifest(planType, out _); + public static string GetEmbeddedPackageDataManifestHash(Type planType) { // SEE: HashFromStreams in the benchmarks project for how fast this is. It will run // on every startup for every embedded package.zip. The bigger the zip, the more time it takes. // But it is still very fast ~303ms for a 100MB file. This will only be an issue if there are // several very large package.zips. - using Stream? stream = GetEmbeddedPackageZipStream(planType); if (stream is not null) @@ -70,7 +49,7 @@ public static string GetEmbeddedPackageDataManifestHash(Type planType) return stream.GetStreamHash(); } - XDocument xml = GetEmbeddedPackageXmlDoc(planType); + XDocument? xml = GetEmbeddedPackageXmlDoc(planType); if (xml is not null) { @@ -80,10 +59,30 @@ public static string GetEmbeddedPackageDataManifestHash(Type planType) throw new IOException("Missing embedded files for planType: " + planType); } + private static XDocument? GetEmbeddedPackageXmlDoc(Type planType) + { + // lookup the embedded resource by convention + Assembly currentAssembly = planType.Assembly; + var fileName = $"{planType.Namespace}.package.xml"; + Stream? stream = currentAssembly.GetManifestResourceStream(fileName); + if (stream == null) + { + return null; + } + + XDocument xml; + using (stream) + { + xml = XDocument.Load(stream); + } + + return xml; + } + public static bool TryGetEmbeddedPackageDataManifest(Type planType, out XDocument? packageXml, out ZipArchive? zipArchive) { - Stream zipStream = GetEmbeddedPackageZipStream(planType); + Stream? zipStream = GetEmbeddedPackageZipStream(planType); if (zipStream is not null) { zipArchive = GetPackageDataManifest(zipStream, out packageXml); @@ -110,7 +109,7 @@ public static ZipArchive GetPackageDataManifest(Stream packageZipStream, out XDo } using (Stream packageXmlStream = packageXmlEntry.Open()) - using (var xmlReader = XmlReader.Create(packageXmlStream, new XmlReaderSettings {IgnoreWhitespace = true})) + using (var xmlReader = XmlReader.Create(packageXmlStream, new XmlReaderSettings { IgnoreWhitespace = true })) { packageXml = XDocument.Load(xmlReader); } diff --git a/src/Umbraco.Core/Packaging/PackagesRepository.cs b/src/Umbraco.Core/Packaging/PackagesRepository.cs index 0ced74cc3d86..061219311e1b 100644 --- a/src/Umbraco.Core/Packaging/PackagesRepository.cs +++ b/src/Umbraco.Core/Packaging/PackagesRepository.cs @@ -120,7 +120,7 @@ public PackagesRepository( public PackageDefinition? GetById(int id) { XDocument packagesXml = EnsureStorage(out var packageFile); - XElement packageXml = packagesXml?.Root?.Elements("package") + XElement? packageXml = packagesXml?.Root?.Elements("package") .FirstOrDefault(x => x.AttributeValue("id") == id); return packageXml == null ? null : _parser.ToPackageDefinition(packageXml); } @@ -128,7 +128,7 @@ public PackagesRepository( public void Delete(int id) { XDocument packagesXml = EnsureStorage(out var packagesFile); - XElement packageXml = packagesXml?.Root?.Elements("package") + XElement? packageXml = packagesXml?.Root?.Elements("package") .FirstOrDefault(x => x.AttributeValue("id") == id); if (packageXml == null) { @@ -154,12 +154,12 @@ public bool SavePackage(PackageDefinition definition) return false; } - //ensure it's valid + // ensure it's valid ValidatePackage(definition); if (definition.Id == default) { - //need to gen an id and persist + // need to gen an id and persist // Find max id var maxId = packagesXml.Root.Elements("package").Max(x => x.AttributeValue("id")) ?? 0; var newId = maxId + 1; @@ -170,8 +170,8 @@ public bool SavePackage(PackageDefinition definition) } else { - //existing - XElement packageXml = packagesXml.Root.Elements("package") + // existing + XElement? packageXml = packagesXml.Root.Elements("package") .FirstOrDefault(x => x.AttributeValue("id") == definition.Id); if (packageXml == null) { @@ -201,10 +201,10 @@ public string ExportPackage(PackageDefinition definition) "the package definition does not have a GUID, it must be saved before being exported"); } - //ensure it's valid + // ensure it's valid ValidatePackage(definition); - //Create a folder for building this package + // Create a folder for building this package var temporaryPath = _hostingEnvironment.MapPathContentRoot(_tempFolderPath.EnsureEndsWith('/') + Guid.NewGuid()); if (Directory.Exists(temporaryPath) == false) @@ -214,10 +214,10 @@ public string ExportPackage(PackageDefinition definition) try { - //Init package file + // Init package file XDocument compiledPackageXml = CreateCompiledPackageXml(out XElement root); - //Info section + // Info section root.Add(GetPackageInfoXml(definition)); PackageDocumentsAndTags(definition, root); @@ -274,7 +274,8 @@ public string ExportPackage(PackageDefinition definition) } var directoryName = - _hostingEnvironment.MapPathContentRoot(Path.Combine(_createdPackagesFolderPath, + _hostingEnvironment.MapPathContentRoot(Path.Combine( + _createdPackagesFolderPath, definition.Name.Replace(' ', '_'))); Directory.CreateDirectory(directoryName); @@ -299,6 +300,32 @@ public string ExportPackage(PackageDefinition definition) } } + public void DeleteLocalRepositoryFiles() + { + var packagesFile = _hostingEnvironment.MapPathContentRoot(CreatedPackagesFile); + if (File.Exists(packagesFile)) + { + File.Delete(packagesFile); + } + + var packagesFolder = _hostingEnvironment.MapPathContentRoot(_packagesFolderPath); + if (Directory.Exists(packagesFolder)) + { + Directory.Delete(packagesFolder); + } + } + + private static XElement GetPackageInfoXml(PackageDefinition definition) + { + var info = new XElement("info"); + + // Package info + var package = new XElement("package"); + package.Add(new XElement("name", definition.Name)); + info.Add(package); + return info; + } + private void ValidatePackage(PackageDefinition definition) { // ensure it's valid @@ -322,7 +349,7 @@ private void PackageDataTypes(PackageDefinition definition, XContainer root) continue; } - IDataType dataType = _dataTypeService.GetDataType(outInt); + IDataType? dataType = _dataTypeService.GetDataType(outInt); if (dataType == null) { continue; @@ -344,7 +371,7 @@ private void PackageLanguages(PackageDefinition definition, XContainer root) continue; } - ILanguage lang = _languageService.GetLanguageById(outInt); + ILanguage? lang = _languageService.GetLanguageById(outInt); if (lang == null) { continue; @@ -417,14 +444,17 @@ private void PackageDictionaryItems(PackageDefinition definition, XContainer roo root.Add(rootDictionaryItems); - static void AppendDictionaryElement(XElement rootDictionaryItems, + static void AppendDictionaryElement( + XElement rootDictionaryItems, Dictionary items, Dictionary processed, Guid key, XElement serializedDictionaryValue) { // track it processed.Add(key, serializedDictionaryValue); + // append it rootDictionaryItems.Add(serializedDictionaryValue); + // remove it so its not re-processed items.Remove(key); } @@ -459,7 +489,7 @@ private void PackageMacros(PackageDefinition definition, XContainer root) // Get the partial views for macros and package those (exclude views outside of the default directory, e.g. App_Plugins\*\Views) IEnumerable views = packagedMacros.Where(x => x.MacroSource is not null) .Where(x => x.MacroSource!.StartsWith(Constants.SystemDirectories.MacroPartials)) - .Select(x => x.MacroSource!.Substring(Constants.SystemDirectories.MacroPartials.Length).Replace('/', '\\')); + .Select(x => x.MacroSource![Constants.SystemDirectories.MacroPartials.Length..].Replace('/', '\\')); PackageStaticFiles(views, root, "MacroPartialViews", "View", _fileSystems.MacroPartialsFileSystem); } @@ -530,7 +560,7 @@ private void PackageTemplates(PackageDefinition definition, XContainer root) continue; } - ITemplate template = _fileService.GetTemplate(outInt); + ITemplate? template = _fileService.GetTemplate(outInt); if (template == null) { continue; @@ -553,7 +583,7 @@ private void PackageDocumentTypes(PackageDefinition definition, XContainer root) continue; } - IContentType contentType = _contentTypeService.Get(outInt); + IContentType? contentType = _contentTypeService.Get(outInt); if (contentType == null) { continue; @@ -581,7 +611,7 @@ private void PackageMediaTypes(PackageDefinition definition, XContainer root) continue; } - IMediaType mediaType = _mediaTypeService.Get(outInt); + IMediaType? mediaType = _mediaTypeService.Get(outInt); if (mediaType == null) { continue; @@ -600,63 +630,65 @@ private void PackageMediaTypes(PackageDefinition definition, XContainer root) private void PackageDocumentsAndTags(PackageDefinition definition, XContainer root) { - //Documents and tags - if (string.IsNullOrEmpty(definition.ContentNodeId) == false && int.TryParse(definition.ContentNodeId, + // Documents and tags + if (string.IsNullOrEmpty(definition.ContentNodeId) == false && int.TryParse( + definition.ContentNodeId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var contentNodeId)) { if (contentNodeId > 0) { - //load content from umbraco. - IContent content = _contentService.GetById(contentNodeId); + // load content from umbraco. + IContent? content = _contentService.GetById(contentNodeId); if (content != null) { XElement contentXml = definition.ContentLoadChildNodes ? content.ToDeepXml(_serializer) : content.ToXml(_serializer); - //Create the Documents/DocumentSet node - + // Create the Documents/DocumentSet node root.Add( - new XElement("Documents", - new XElement("DocumentSet", + new XElement( + "Documents", + new XElement( + "DocumentSet", new XAttribute("importMode", "root"), contentXml))); // TODO: I guess tags has been broken for a very long time for packaging, we should get this working again sometime ////Create the TagProperties node - this is used to store a definition for all //// document properties that are tags, this ensures that we can re-import tags properly - //XmlNode tagProps = new XElement("TagProperties"); + // XmlNode tagProps = new XElement("TagProperties"); ////before we try to populate this, we'll do a quick lookup to see if any of the documents //// being exported contain published tags. - //var allExportedIds = documents.SelectNodes("//@id").Cast() + // var allExportedIds = documents.SelectNodes("//@id").Cast() // .Select(x => x.Value.TryConvertTo()) // .Where(x => x.Success) // .Select(x => x.Result) // .ToArray(); - //var allContentTags = new List(); - //foreach (var exportedId in allExportedIds) - //{ + // var allContentTags = new List(); + // foreach (var exportedId in allExportedIds) + // { // allContentTags.AddRange( // Current.Services.TagService.GetTagsForEntity(exportedId)); - //} + // } ////This is pretty round-about but it works. Essentially we need to get the properties that are tagged //// but to do that we need to lookup by a tag (string) - //var allTaggedEntities = new List(); - //foreach (var group in allContentTags.Select(x => x.Group).Distinct()) - //{ + // var allTaggedEntities = new List(); + // foreach (var group in allContentTags.Select(x => x.Group).Distinct()) + // { // allTaggedEntities.AddRange( // Current.Services.TagService.GetTaggedContentByTagGroup(group)); - //} + // } ////Now, we have all property Ids/Aliases and their referenced document Ids and tags - //var allExportedTaggedEntities = allTaggedEntities.Where(x => allExportedIds.Contains(x.EntityId)) + // var allExportedTaggedEntities = allTaggedEntities.Where(x => allExportedIds.Contains(x.EntityId)) // .DistinctBy(x => x.EntityId) // .OrderBy(x => x.EntityId); - //foreach (var taggedEntity in allExportedTaggedEntities) - //{ + // foreach (var taggedEntity in allExportedTaggedEntities) + // { // foreach (var taggedProperty in taggedEntity.TaggedProperties.Where(x => x.Tags.Any())) // { // XmlNode tagProp = new XElement("TagProperty"); @@ -664,28 +696,27 @@ private void PackageDocumentsAndTags(PackageDefinition definition, XContainer ro // docId.Value = taggedEntity.EntityId.ToString(CultureInfo.InvariantCulture); // tagProp.Attributes.Append(docId); - // var propertyAlias = packageManifest.CreateAttribute("propertyAlias", ""); + // var propertyAlias = packageManifest.CreateAttribute("propertyAlias", ""); // propertyAlias.Value = taggedProperty.PropertyTypeAlias; // tagProp.Attributes.Append(propertyAlias); - // var group = packageManifest.CreateAttribute("group", ""); + // var group = packageManifest.CreateAttribute("group", ""); // group.Value = taggedProperty.Tags.First().Group; // tagProp.Attributes.Append(group); - // tagProp.AppendChild(packageManifest.CreateCDataSection( + // tagProp.AppendChild(packageManifest.CreateCDataSection( // JsonConvert.SerializeObject(taggedProperty.Tags.Select(x => x.Text).ToArray()))); - // tagProps.AppendChild(tagProp); + // tagProps.AppendChild(tagProp); // } - //} + // } - //manifestRoot.Add(tagProps); + // manifestRoot.Add(tagProps); } } } } - private Dictionary PackageMedia(PackageDefinition definition, XElement root) { var mediaStreams = new Dictionary(); @@ -726,6 +757,7 @@ void OnSerializedMedia(IMedia media, XElement xmlMedia) } // TODO: Delete this + /// private XElement? GetMacroXml(int macroId, out IMacro? macro) { @@ -765,7 +797,7 @@ private void AddDocumentType(IContentType dt, HashSet dtl) { if (dt.ParentId > 0) { - IContentType parent = _contentTypeService.Get(dt.ParentId); + IContentType? parent = _contentTypeService.Get(dt.ParentId); if (parent != null) // could be a container { AddDocumentType(parent, dtl); @@ -782,7 +814,7 @@ private void AddMediaType(IMediaType mediaType, HashSet mediaTypes) { if (mediaType.ParentId > 0) { - IMediaType parent = _mediaTypeService.Get(mediaType.ParentId); + IMediaType? parent = _mediaTypeService.Get(mediaType.ParentId); if (parent != null) // could be a container { AddMediaType(parent, mediaTypes); @@ -795,17 +827,6 @@ private void AddMediaType(IMediaType mediaType, HashSet mediaTypes) } } - private static XElement GetPackageInfoXml(PackageDefinition definition) - { - var info = new XElement("info"); - - //Package info - var package = new XElement("package"); - package.Add(new XElement("name", definition.Name)); - info.Add(package); - return info; - } - private static XDocument CreateCompiledPackageXml(out XElement root) { root = new XElement("umbPackage"); @@ -830,19 +851,4 @@ private XDocument EnsureStorage(out string packagesFile) var packagesXml = XDocument.Load(packagesFile); return packagesXml; } - - public void DeleteLocalRepositoryFiles() - { - var packagesFile = _hostingEnvironment.MapPathContentRoot(CreatedPackagesFile); - if (File.Exists(packagesFile)) - { - File.Delete(packagesFile); - } - - var packagesFolder = _hostingEnvironment.MapPathContentRoot(_packagesFolderPath); - if (Directory.Exists(packagesFolder)) - { - Directory.Delete(packagesFolder); - } - } } diff --git a/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs b/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs index abcfd7a4d7b2..fa8e674b971b 100644 --- a/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs +++ b/src/Umbraco.Core/Persistence/Querying/StringPropertyMatchType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Persistence.Querying; +namespace Umbraco.Cms.Core.Persistence.Querying; /// /// Determines how to match a string property value @@ -10,6 +10,6 @@ public enum StringPropertyMatchType StartsWith, EndsWith, - //Deals with % as wildcard chars in a string - Wildcard + // Deals with % as wildcard chars in a string + Wildcard, } diff --git a/src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs b/src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs index 4002b4b07962..ab6fd4f938e5 100644 --- a/src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs +++ b/src/Umbraco.Core/Persistence/Querying/ValuePropertyMatchType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Persistence.Querying; +namespace Umbraco.Cms.Core.Persistence.Querying; /// /// Determine how to match a number or data value @@ -9,5 +9,5 @@ public enum ValuePropertyMatchType GreaterThan, LessThan, GreaterThanOrEqualTo, - LessThanOrEqualTo + LessThanOrEqualTo, } diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeys.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeys.cs index 98047356fc50..a6b6c16aa55a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeys.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheKeys.cs @@ -6,12 +6,12 @@ namespace Umbraco.Cms.Core.Persistence.Repositories; public static class RepositoryCacheKeys { // used to cache keys so we don't keep allocating strings - private static readonly Dictionary s_keys = new(); + private static readonly Dictionary Keys = new(); public static string GetKey() { Type type = typeof(T); - return s_keys.TryGetValue(type, out var key) ? key : s_keys[type] = "uRepo_" + type.Name + "_"; + return Keys.TryGetValue(type, out var key) ? key : Keys[type] = "uRepo_" + type.Name + "_"; } public static string GetKey(TId? id) diff --git a/src/Umbraco.Core/Persistence/Repositories/UpgradeCheckRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UpgradeCheckRepository.cs index a95da5becf6f..4d4e642d9d3c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UpgradeCheckRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UpgradeCheckRepository.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; using Umbraco.Cms.Core.Semver; using Umbraco.Cms.Core.Serialization; @@ -21,20 +21,19 @@ public async Task CheckUpgradeAsync(SemVersion version) _httpClient = new HttpClient(); } - var content = new StringContent(_jsonSerializer.Serialize(new CheckUpgradeDto(version)), Encoding.UTF8, - "application/json"); + var content = new StringContent(_jsonSerializer.Serialize(new CheckUpgradeDto(version)), Encoding.UTF8, "application/json"); _httpClient.Timeout = TimeSpan.FromSeconds(1); HttpResponseMessage task = await _httpClient.PostAsync(RestApiUpgradeChecklUrl, content); var json = await task.Content.ReadAsStringAsync(); - UpgradeResult result = _jsonSerializer.Deserialize(json); + UpgradeResult? result = _jsonSerializer.Deserialize(json); - return result ?? new UpgradeResult("None", "", ""); + return result ?? new UpgradeResult("None", string.Empty, string.Empty); } catch (HttpRequestException) { // this occurs if the server for Our is down or cannot be reached - return new UpgradeResult("None", "", ""); + return new UpgradeResult("None", string.Empty, string.Empty); } } @@ -49,8 +48,11 @@ public CheckUpgradeDto(SemVersion version) } public int VersionMajor { get; } + public int VersionMinor { get; } + public int VersionPatch { get; } + public string VersionComment { get; } } } diff --git a/src/Umbraco.Core/Persistence/SqlExpressionExtensions.cs b/src/Umbraco.Core/Persistence/SqlExpressionExtensions.cs index 5851f2862bd2..20db5106d7fe 100644 --- a/src/Umbraco.Core/Persistence/SqlExpressionExtensions.cs +++ b/src/Umbraco.Core/Persistence/SqlExpressionExtensions.cs @@ -26,7 +26,8 @@ public static bool SqlNullableEquals(this T? value, T? other, T fallbackValue public static bool SqlWildcard(this string str, string txt, TextColumnType columnType) { var wildcardmatch = new Regex("^" + Regex.Escape(txt). - //deal with any wildcard chars % + + // deal with any wildcard chars % Replace(@"\%", ".*") + "$"); return wildcardmatch.IsMatch(str); diff --git a/src/Umbraco.Core/Persistence/SqlExtensionsStatics.cs b/src/Umbraco.Core/Persistence/SqlExtensionsStatics.cs index 906bb4246d7e..506e516447fc 100644 --- a/src/Umbraco.Core/Persistence/SqlExtensionsStatics.cs +++ b/src/Umbraco.Core/Persistence/SqlExtensionsStatics.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Persistence; +namespace Umbraco.Cms.Core.Persistence; /// /// Provides a mean to express aliases in SELECT Sql statements. @@ -40,6 +40,5 @@ public static class SqlExtensionsStatics /// The name of the second field. /// The name of the third field. /// A function producing Sql text. - public static T? SqlText(string field1, string field2, string field3, - Func expr) => default; + public static T? SqlText(string field1, string field2, string field3, Func expr) => default; } diff --git a/src/Umbraco.Core/Persistence/TextColumnType.cs b/src/Umbraco.Core/Persistence/TextColumnType.cs index 335f9c32d202..9e3a4dd71b70 100644 --- a/src/Umbraco.Core/Persistence/TextColumnType.cs +++ b/src/Umbraco.Core/Persistence/TextColumnType.cs @@ -3,5 +3,5 @@ namespace Umbraco.Cms.Core.Persistence; public enum TextColumnType { NVarchar, - NText + NText, } diff --git a/src/Umbraco.Core/PropertyEditors/MemberGroupPickerPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/MemberGroupPickerPropertyEditor.cs index 090b667bc789..221481328b64 100644 --- a/src/Umbraco.Core/PropertyEditors/MemberGroupPickerPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/MemberGroupPickerPropertyEditor.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; [DataEditor( Constants.PropertyEditors.Aliases.MemberGroupPicker, diff --git a/src/Umbraco.Core/PropertyEditors/MemberPickerConfiguration.cs b/src/Umbraco.Core/PropertyEditors/MemberPickerConfiguration.cs index aa3cc122a845..dc0ab648dff6 100644 --- a/src/Umbraco.Core/PropertyEditors/MemberPickerConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/MemberPickerConfiguration.cs @@ -1,7 +1,7 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; public class MemberPickerConfiguration : ConfigurationEditor { public override IDictionary DefaultConfiguration => - new Dictionary {{"idType", "udi"}}; + new Dictionary { { "idType", "udi" } }; } diff --git a/src/Umbraco.Core/PropertyEditors/MemberPickerPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/MemberPickerPropertyEditor.cs index 40d9482b2ce4..055bd354fd51 100644 --- a/src/Umbraco.Core/PropertyEditors/MemberPickerPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/MemberPickerPropertyEditor.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; [DataEditor( Constants.PropertyEditors.Aliases.MemberPicker, diff --git a/src/Umbraco.Core/PropertyEditors/MissingPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/MissingPropertyEditor.cs index 83694b7647ee..c256c7b483f7 100644 --- a/src/Umbraco.Core/PropertyEditors/MissingPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/MissingPropertyEditor.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.PropertyEditors; diff --git a/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfiguration.cs b/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfiguration.cs index ff8e7f741ab5..c1ca368c4709 100644 --- a/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfiguration.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Represents the configuration for the multinode picker value editor. @@ -8,8 +8,7 @@ public class MultiNodePickerConfiguration : IIgnoreUserStartNodesConfig [ConfigurationField("startNode", "Node type", "treesource")] public MultiNodePickerConfigurationTreeSource? TreeSource { get; set; } - [ConfigurationField("filter", "Allow items of type", "treesourcetypepicker", - Description = "Select the applicable types")] + [ConfigurationField("filter", "Allow items of type", "treesourcetypepicker", Description = "Select the applicable types")] public string? Filter { get; set; } [ConfigurationField("minNumber", "Minimum number of items", "number")] @@ -21,8 +20,10 @@ public class MultiNodePickerConfiguration : IIgnoreUserStartNodesConfig [ConfigurationField("showOpenButton", "Show open button", "boolean", Description = "Opens the node in a dialog")] public bool ShowOpen { get; set; } - [ConfigurationField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, - "Ignore User Start Nodes", "boolean", + [ConfigurationField( + Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, + "Ignore User Start Nodes", + "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] public bool IgnoreUserStartNodes { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfigurationEditor.cs index e01c69e3b15e..a377dae5dba1 100644 --- a/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfigurationEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; @@ -23,7 +23,7 @@ public MultiNodePickerConfigurationEditor(IIOHelper ioHelper) public MultiNodePickerConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base(ioHelper, editorConfigurationParser) => Field(nameof(MultiNodePickerConfiguration.TreeSource)) - .Config = new Dictionary {{"idType", "udi"}}; + .Config = new Dictionary { { "idType", "udi" } }; /// public override Dictionary ToConfigurationEditor(MultiNodePickerConfiguration? configuration) diff --git a/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfigurationTreeSource.cs b/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfigurationTreeSource.cs index f4a14e6f4fc7..2dcd0f6e9340 100644 --- a/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfigurationTreeSource.cs +++ b/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfigurationTreeSource.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.PropertyEditors; @@ -8,9 +8,12 @@ namespace Umbraco.Cms.Core.PropertyEditors; [DataContract] public class MultiNodePickerConfigurationTreeSource { - [DataMember(Name = "type")] public string? ObjectType { get; set; } + [DataMember(Name = "type")] + public string? ObjectType { get; set; } - [DataMember(Name = "query")] public string? StartNodeQuery { get; set; } + [DataMember(Name = "query")] + public string? StartNodeQuery { get; set; } - [DataMember(Name = "id")] public Udi? StartNodeId { get; set; } + [DataMember(Name = "id")] + public Udi? StartNodeId { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/MultiUrlPickerConfiguration.cs b/src/Umbraco.Core/PropertyEditors/MultiUrlPickerConfiguration.cs index 3212c1decea7..35d51cb94484 100644 --- a/src/Umbraco.Core/PropertyEditors/MultiUrlPickerConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/MultiUrlPickerConfiguration.cs @@ -11,13 +11,17 @@ public class MultiUrlPickerConfiguration : IIgnoreUserStartNodesConfig [ConfigurationField("overlaySize", "Overlay Size", "overlaysize", Description = "Select the width of the overlay.")] public string? OverlaySize { get; set; } - [ConfigurationField("hideAnchor", - "Hide anchor/query string input", "boolean", + [ConfigurationField( + "hideAnchor", + "Hide anchor/query string input", + "boolean", Description = "Selecting this hides the anchor/query string input field in the linkpicker overlay.")] public bool HideAnchor { get; set; } - [ConfigurationField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, - "Ignore user start nodes", "boolean", + [ConfigurationField( + Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, + "Ignore user start nodes", + "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] public bool IgnoreUserStartNodes { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/MultiUrlPickerConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/MultiUrlPickerConfigurationEditor.cs index 99112080f630..f85cafa817bc 100644 --- a/src/Umbraco.Core/PropertyEditors/MultiUrlPickerConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/MultiUrlPickerConfigurationEditor.cs @@ -14,12 +14,11 @@ public class MultiUrlPickerConfigurationEditor : ConfigurationEditor()) - { } - public MultiUrlPickerConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : - base(ioHelper, editorConfigurationParser) + public MultiUrlPickerConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) + : base(ioHelper, editorConfigurationParser) { } } diff --git a/src/Umbraco.Core/PropertyEditors/MultipleTextStringConfiguration.cs b/src/Umbraco.Core/PropertyEditors/MultipleTextStringConfiguration.cs index d897960f564a..6c7f93374de2 100644 --- a/src/Umbraco.Core/PropertyEditors/MultipleTextStringConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/MultipleTextStringConfiguration.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Represents the configuration for a multiple textstring value editor. @@ -6,7 +6,6 @@ public class MultipleTextStringConfiguration { // fields are configured in the editor - public int Minimum { get; set; } public int Maximum { get; set; } diff --git a/src/Umbraco.Core/PropertyEditors/NestedContentConfiguration.cs b/src/Umbraco.Core/PropertyEditors/NestedContentConfiguration.cs index 283e06521e1c..a22eb352c09b 100644 --- a/src/Umbraco.Core/PropertyEditors/NestedContentConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/NestedContentConfiguration.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.PropertyEditors; @@ -7,9 +7,7 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// public class NestedContentConfiguration { - [ConfigurationField("contentTypes", "Element Types", - "views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html", - Description = "Select the Element Types to use as models for the items.")] + [ConfigurationField("contentTypes", "Element Types", "views/propertyeditors/nestedcontent/nestedcontent.doctypepicker.html", Description = "Select the Element Types to use as models for the items.")] public ContentType[]? ContentTypes { get; set; } [ConfigurationField("minItems", "Min Items", "number", Description = "Minimum number of items allowed.")] @@ -18,25 +16,25 @@ public class NestedContentConfiguration [ConfigurationField("maxItems", "Max Items", "number", Description = "Maximum number of items allowed.")] public int? MaxItems { get; set; } - [ConfigurationField("confirmDeletes", "Confirm Deletes", "boolean", - Description = "Requires editor confirmation for delete actions.")] + [ConfigurationField("confirmDeletes", "Confirm Deletes", "boolean", Description = "Requires editor confirmation for delete actions.")] public bool ConfirmDeletes { get; set; } = true; [ConfigurationField("showIcons", "Show Icons", "boolean", Description = "Show the Element Type icons.")] public bool ShowIcons { get; set; } = true; - [ConfigurationField("hideLabel", "Hide Label", "boolean", - Description = "Hide the property label and let the item list span the full width of the editor window.")] + [ConfigurationField("hideLabel", "Hide Label", "boolean", Description = "Hide the property label and let the item list span the full width of the editor window.")] public bool HideLabel { get; set; } - [DataContract] public class ContentType { - [DataMember(Name = "ncAlias")] public string? Alias { get; set; } + [DataMember(Name = "ncAlias")] + public string? Alias { get; set; } - [DataMember(Name = "ncTabAlias")] public string? TabAlias { get; set; } + [DataMember(Name = "ncTabAlias")] + public string? TabAlias { get; set; } - [DataMember(Name = "nameTemplate")] public string? Template { get; set; } + [DataMember(Name = "nameTemplate")] + public string? Template { get; set; } } } diff --git a/src/Umbraco.Core/PropertyEditors/NestedContentConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/NestedContentConfigurationEditor.cs index 58e3065567f1..5adb06b42f7f 100644 --- a/src/Umbraco.Core/PropertyEditors/NestedContentConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/NestedContentConfigurationEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; @@ -20,8 +20,8 @@ public NestedContentConfigurationEditor(IIOHelper ioHelper) { } - public NestedContentConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : - base(ioHelper, editorConfigurationParser) + public NestedContentConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) + : base(ioHelper, editorConfigurationParser) { } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs index 6da7680540da..2897a8c4ed2c 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentPickerParameterEditor.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; @@ -34,14 +34,19 @@ protected override IDataValueEditor CreateValueEditor() => internal class MultipleContentPickerParamateterValueEditor : MultiplePickerParamateterValueEditorBase { - public MultipleContentPickerParamateterValueEditor(ILocalizedTextService localizedTextService, - IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper, - DataEditorAttribute attribute, IEntityService entityService) : base(localizedTextService, shortStringHelper, - jsonSerializer, ioHelper, attribute, entityService) + public MultipleContentPickerParamateterValueEditor( + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper, + DataEditorAttribute attribute, + IEntityService entityService) + : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute, entityService) { } public override string UdiEntityType { get; } = Constants.UdiEntityType.Document; + public override UmbracoObjectTypes UmbracoObjectType { get; } = UmbracoObjectTypes.Document; } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentTypeParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentTypeParameterEditor.cs index 7036a4ea73e9..44ff5d94c6e1 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentTypeParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleContentTypeParameterEditor.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors; +namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors; [DataEditor( "contentTypeMultiple", diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs index b0cae56d58ac..71f626107b4d 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultipleMediaPickerParameterEditor.cs @@ -30,14 +30,19 @@ protected override IDataValueEditor CreateValueEditor() => internal class MultipleMediaPickerPropertyValueEditor : MultiplePickerParamateterValueEditorBase { - public MultipleMediaPickerPropertyValueEditor(ILocalizedTextService localizedTextService, - IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper, - DataEditorAttribute attribute, IEntityService entityService) : base(localizedTextService, shortStringHelper, - jsonSerializer, ioHelper, attribute, entityService) + public MultipleMediaPickerPropertyValueEditor( + ILocalizedTextService localizedTextService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IIOHelper ioHelper, + DataEditorAttribute attribute, + IEntityService entityService) + : base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute, entityService) { } public override string UdiEntityType { get; } = Constants.UdiEntityType.Media; + public override UmbracoObjectTypes UmbracoObjectType { get; } = UmbracoObjectTypes.Media; } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePickerParamateterValueEditorBase.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePickerParamateterValueEditorBase.cs index 24e8cf9e6ea3..8aaea32ab458 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePickerParamateterValueEditorBase.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePickerParamateterValueEditorBase.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.Serialization; @@ -22,6 +22,7 @@ public MultiplePickerParamateterValueEditorBase( _entityService = entityService; public abstract string UdiEntityType { get; } + public abstract UmbracoObjectTypes UmbracoObjectType { get; } public IEnumerable GetReferences(object? value) diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePropertyGroupParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePropertyGroupParameterEditor.cs index ec912a2a15bd..f9485441b934 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePropertyGroupParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePropertyGroupParameterEditor.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors; +namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors; [DataEditor( "tabPickerMultiple", @@ -14,7 +14,8 @@ public MultiplePropertyGroupParameterEditor( // configure DefaultConfiguration.Add("multiple", true); DefaultConfiguration.Add("entityType", "PropertyGroup"); - //don't publish the id for a property group, publish its alias, which is actually just its lower cased name + + // don't publish the id for a property group, publish its alias, which is actually just its lower cased name DefaultConfiguration.Add("publishBy", "alias"); } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePropertyTypeParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePropertyTypeParameterEditor.cs index 2df36ca8a33f..913c452fb9ad 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePropertyTypeParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/MultiplePropertyTypeParameterEditor.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors; +namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors; [DataEditor( "propertyTypePickerMultiple", @@ -14,7 +14,8 @@ public MultiplePropertyTypeParameterEditor( // configure DefaultConfiguration.Add("multiple", "1"); DefaultConfiguration.Add("entityType", "PropertyType"); - //don't publish the id for a property type, publish its alias + + // don't publish the id for a property type, publish its alias DefaultConfiguration.Add("publishBy", "alias"); } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/PropertyGroupParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/PropertyGroupParameterEditor.cs index c134564e5166..345a3e49717f 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/PropertyGroupParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/PropertyGroupParameterEditor.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors; +namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors; [DataEditor( "tabPicker", @@ -14,7 +14,8 @@ public PropertyGroupParameterEditor( // configure DefaultConfiguration.Add("multiple", "0"); DefaultConfiguration.Add("entityType", "PropertyGroup"); - //don't publish the id for a property group, publish it's alias (which is actually just it's lower cased name) + + // don't publish the id for a property group, publish it's alias (which is actually just it's lower cased name) DefaultConfiguration.Add("publishBy", "alias"); } } diff --git a/src/Umbraco.Core/PropertyEditors/ParameterEditors/PropertyTypeParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/ParameterEditors/PropertyTypeParameterEditor.cs index 858101e2e34b..781d072e1042 100644 --- a/src/Umbraco.Core/PropertyEditors/ParameterEditors/PropertyTypeParameterEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ParameterEditors/PropertyTypeParameterEditor.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors; +namespace Umbraco.Cms.Core.PropertyEditors.ParameterEditors; [DataEditor( "propertyTypePicker", @@ -14,7 +14,8 @@ public PropertyTypeParameterEditor( // configure DefaultConfiguration.Add("multiple", "0"); DefaultConfiguration.Add("entityType", "PropertyType"); - //don't publish the id for a property type, publish its alias + + // don't publish the id for a property type, publish its alias DefaultConfiguration.Add("publishBy", "alias"); } } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyCacheCompression.cs b/src/Umbraco.Core/PropertyEditors/PropertyCacheCompression.cs index 826eb8c1d1aa..75342371a4dd 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyCacheCompression.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyCacheCompression.cs @@ -32,23 +32,23 @@ public bool IsCompressed(IReadOnlyContentBase content, string alias, bool publis { var compressedStorage = _isCompressedCache.GetOrAdd((content.ContentTypeId, alias, published), x => { - if (!_contentTypes.TryGetValue(x.contentTypeId, out IContentTypeComposition ct)) + if (!_contentTypes.TryGetValue(x.contentTypeId, out IContentTypeComposition? ct)) { return false; } - IPropertyType propertyType = ct.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == alias); + IPropertyType? propertyType = ct.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == alias); if (propertyType == null) { return false; } - if (!_propertyEditors.TryGet(propertyType.PropertyEditorAlias, out IDataEditor propertyEditor)) + if (!_propertyEditors.TryGet(propertyType.PropertyEditorAlias, out IDataEditor? propertyEditor)) { return false; } - return _compressionOptions.IsCompressed(content, propertyType, propertyEditor!, published); + return _compressionOptions.IsCompressed(content, propertyType, propertyEditor, published); }); return compressedStorage; diff --git a/src/Umbraco.Core/PropertyEditors/PropertyCacheLevel.cs b/src/Umbraco.Core/PropertyEditors/PropertyCacheLevel.cs index 5a23b6b825b4..c835c0ae958d 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyCacheLevel.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyCacheLevel.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Specifies the level of cache for a property value. @@ -36,5 +36,5 @@ public enum PropertyCacheLevel /// Indicates that the property value cannot be cached and has to be converted each time /// it is requested. /// - None = 4 + None = 4, } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyEditorTagsExtensions.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditorTagsExtensions.cs index 38906446e144..ff92c2012f2b 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyEditorTagsExtensions.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyEditorTagsExtensions.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Extensions; diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs index 10a47aea19d4..d73eb5a2eb94 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Cms.Core.PropertyEditors; @@ -39,18 +39,15 @@ public virtual PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType p => PropertyCacheLevel.Snapshot; /// - public virtual object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, - object? source, bool preview) + public virtual object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) => source; /// - public virtual object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + public virtual object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => inter; /// - public virtual object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + public virtual object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => inter?.ToString() ?? string.Empty; [Obsolete( diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollection.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollection.cs index 5dfb8a275e6a..20eb9ae4c40f 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollection.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollection.cs @@ -8,7 +8,8 @@ public class PropertyValueConverterCollection : BuilderCollectionBase? _defaultConverters; - public PropertyValueConverterCollection(Func> items) : base(items) + public PropertyValueConverterCollection(Func> items) + : base(items) { } @@ -27,8 +28,7 @@ private Dictionary DefaultConverters foreach (IPropertyValueConverter converter in this) { - DefaultPropertyValueConverterAttribute attr = converter.GetType() - .GetCustomAttribute(false); + DefaultPropertyValueConverterAttribute? attr = converter.GetType().GetCustomAttribute(false); if (attr != null) { _defaultConverters[converter] = attr.DefaultConvertersToShadow; diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollectionBuilder.cs index 2d1f8ae73648..7746e05647ca 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollectionBuilder.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.PropertyEditors; diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueLevel.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueLevel.cs index afdba8434102..52389fb92ad4 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueLevel.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueLevel.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Indicates the level of a value. @@ -18,5 +18,5 @@ public enum PropertyValueLevel /// /// The converted value. /// - Object + Object, } diff --git a/src/Umbraco.Core/PropertyEditors/RichTextConfiguration.cs b/src/Umbraco.Core/PropertyEditors/RichTextConfiguration.cs index 1130e7ebf989..6a80144d0d9a 100644 --- a/src/Umbraco.Core/PropertyEditors/RichTextConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/RichTextConfiguration.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Represents the configuration for the rich text value editor. @@ -9,19 +9,19 @@ public class RichTextConfiguration : IIgnoreUserStartNodesConfig [ConfigurationField("editor", "Editor", "views/propertyeditors/rte/rte.prevalues.html", HideLabel = true)] public object? Editor { get; set; } - [ConfigurationField("overlaySize", "Overlay Size", "overlaysize", - Description = "Select the width of the overlay (link picker).")] + [ConfigurationField("overlaySize", "Overlay Size", "overlaysize", Description = "Select the width of the overlay (link picker).")] public string? OverlaySize { get; set; } [ConfigurationField("hideLabel", "Hide Label", "boolean")] public bool HideLabel { get; set; } - [ConfigurationField("mediaParentId", "Image Upload Folder", "mediafolderpicker", - Description = "Choose the upload location of pasted images")] + [ConfigurationField("mediaParentId", "Image Upload Folder", "mediafolderpicker", Description = "Choose the upload location of pasted images")] public GuidUdi? MediaParentId { get; set; } - [ConfigurationField(Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, - "Ignore User Start Nodes", "boolean", + [ConfigurationField( + Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, + "Ignore User Start Nodes", + "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] public bool IgnoreUserStartNodes { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/RichTextConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/RichTextConfigurationEditor.cs index 8412aa6b2bd8..3bf11c1bf451 100644 --- a/src/Umbraco.Core/PropertyEditors/RichTextConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/RichTextConfigurationEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; @@ -20,7 +20,8 @@ public RichTextConfigurationEditor(IIOHelper ioHelper) { } - public RichTextConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base( + public RichTextConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) + : base( ioHelper, editorConfigurationParser) { } diff --git a/src/Umbraco.Core/PropertyEditors/SliderConfiguration.cs b/src/Umbraco.Core/PropertyEditors/SliderConfiguration.cs index 501121d439c7..709fb3ce9f86 100644 --- a/src/Umbraco.Core/PropertyEditors/SliderConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/SliderConfiguration.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Represents the configuration for the slider value editor. diff --git a/src/Umbraco.Core/PropertyEditors/SliderConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/SliderConfigurationEditor.cs index a740ed1f35eb..586e4cd3afe2 100644 --- a/src/Umbraco.Core/PropertyEditors/SliderConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/SliderConfigurationEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; @@ -20,7 +20,8 @@ public SliderConfigurationEditor(IIOHelper ioHelper) { } - public SliderConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base( + public SliderConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) + : base( ioHelper, editorConfigurationParser) { } diff --git a/src/Umbraco.Core/PropertyEditors/TagConfiguration.cs b/src/Umbraco.Core/PropertyEditors/TagConfiguration.cs index e06a9d4aab3c..5a9808f22706 100644 --- a/src/Umbraco.Core/PropertyEditors/TagConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/TagConfiguration.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.PropertyEditors; @@ -7,13 +7,14 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// public class TagConfiguration { - [ConfigurationField("group", "Tag group", "requiredfield", - Description = "Define a tag group")] + [ConfigurationField("group", "Tag group", "requiredfield", Description = "Define a tag group")] public string Group { get; set; } = "default"; - [ConfigurationField("storageType", "Storage Type", "views/propertyeditors/tags/tags.prevalues.html", - Description = - "Select whether to store the tags in cache as JSON (default) or as CSV. The only benefits of storage as JSON is that you are able to have commas in a tag value")] + [ConfigurationField( + "storageType", + "Storage Type", + "views/propertyeditors/tags/tags.prevalues.html", + Description = "Select whether to store the tags in cache as JSON (default) or as CSV. The only benefits of storage as JSON is that you are able to have commas in a tag value")] public TagsStorageType StorageType { get; set; } = TagsStorageType.Json; // not a field diff --git a/src/Umbraco.Core/PropertyEditors/TagConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/TagConfigurationEditor.cs index c69631f5044d..f22f9b74c416 100644 --- a/src/Umbraco.Core/PropertyEditors/TagConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/TagConfigurationEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; @@ -17,16 +17,13 @@ public class TagConfigurationEditor : ConfigurationEditor { // Scheduled for removal in v12 [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public TagConfigurationEditor(ManifestValueValidatorCollection validators, IIOHelper ioHelper, - ILocalizedTextService localizedTextService) - : this(validators, ioHelper, localizedTextService, - StaticServiceProvider.Instance.GetRequiredService()) + public TagConfigurationEditor(ManifestValueValidatorCollection validators, IIOHelper ioHelper, ILocalizedTextService localizedTextService) + : this(validators, ioHelper, localizedTextService, StaticServiceProvider.Instance.GetRequiredService()) { } - public TagConfigurationEditor(ManifestValueValidatorCollection validators, IIOHelper ioHelper, - ILocalizedTextService localizedTextService, IEditorConfigurationParser editorConfigurationParser) : base( - ioHelper, editorConfigurationParser) + public TagConfigurationEditor(ManifestValueValidatorCollection validators, IIOHelper ioHelper, ILocalizedTextService localizedTextService, IEditorConfigurationParser editorConfigurationParser) + : base(ioHelper, editorConfigurationParser) { Field(nameof(TagConfiguration.Group)).Validators.Add(new RequiredValidator(localizedTextService)); Field(nameof(TagConfiguration.StorageType)).Validators.Add(new RequiredValidator(localizedTextService)); @@ -39,7 +36,7 @@ public override Dictionary ToConfigurationEditor(TagConfiguratio // the front-end editor expects the string value of the storage type if (!dictionary.TryGetValue("storageType", out var storageType)) { - storageType = TagsStorageType.Json; //default to Json + storageType = TagsStorageType.Json; // default to Json } dictionary["storageType"] = storageType.ToString()!; @@ -47,14 +44,14 @@ public override Dictionary ToConfigurationEditor(TagConfiguratio return dictionary; } - public override TagConfiguration? FromConfigurationEditor(IDictionary? editorValues, + public override TagConfiguration? FromConfigurationEditor( + IDictionary? editorValues, TagConfiguration? configuration) { // the front-end editor returns the string value of the storage type // pure Json could do with // [JsonConverter(typeof(StringEnumConverter))] // but here we're only deserializing to object and it's too late - if (editorValues is not null) { editorValues["storageType"] = Enum.Parse(typeof(TagsStorageType), (string)editorValues["storageType"]!); diff --git a/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs b/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs index 71775fe64a72..849d6446a93b 100644 --- a/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs +++ b/src/Umbraco.Core/PropertyEditors/TagsPropertyEditorAttribute.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.PropertyEditors; @@ -53,6 +53,6 @@ public TagsPropertyEditorAttribute() /// /// Gets the type of the dynamic configuration provider. /// - //TODO: This is not used and should be implemented in a nicer way, see https://github.com/umbraco/Umbraco-CMS/issues/6017#issuecomment-516253562 + // TODO: This is not used and should be implemented in a nicer way, see https://github.com/umbraco/Umbraco-CMS/issues/6017#issuecomment-516253562 public Type? TagsConfigurationProviderType { get; } } diff --git a/src/Umbraco.Core/PropertyEditors/TextAreaConfiguration.cs b/src/Umbraco.Core/PropertyEditors/TextAreaConfiguration.cs index 0c7982a0122a..8e6355258b36 100644 --- a/src/Umbraco.Core/PropertyEditors/TextAreaConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/TextAreaConfiguration.cs @@ -1,15 +1,13 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Represents the configuration for the textarea value editor. /// public class TextAreaConfiguration { - [ConfigurationField("maxChars", "Maximum allowed characters", "number", - Description = "If empty - no character limit")] + [ConfigurationField("maxChars", "Maximum allowed characters", "number", Description = "If empty - no character limit")] public int? MaxChars { get; set; } - [ConfigurationField("rows", "Number of rows", "number", - Description = "If empty - 10 rows would be set as the default value")] + [ConfigurationField("rows", "Number of rows", "number", Description = "If empty - 10 rows would be set as the default value")] public int? Rows { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/TextAreaConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/TextAreaConfigurationEditor.cs index 0cea517df73c..d1a1fb1a42e8 100644 --- a/src/Umbraco.Core/PropertyEditors/TextAreaConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/TextAreaConfigurationEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; @@ -20,7 +20,8 @@ public TextAreaConfigurationEditor(IIOHelper ioHelper) { } - public TextAreaConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base( + public TextAreaConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) + : base( ioHelper, editorConfigurationParser) { } diff --git a/src/Umbraco.Core/PropertyEditors/TextOnlyValueEditor.cs b/src/Umbraco.Core/PropertyEditors/TextOnlyValueEditor.cs index 6c97d485c0e8..6a0995dccd3a 100644 --- a/src/Umbraco.Core/PropertyEditors/TextOnlyValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/TextOnlyValueEditor.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; diff --git a/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs index c69fca53957e..74de3fea8e88 100644 --- a/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Templates; namespace Umbraco.Cms.Core.PropertyEditors; @@ -8,7 +8,7 @@ public class TextStringValueConverter : PropertyValueConverterBase { private static readonly string[] PropertyTypeAliases = { - Constants.PropertyEditors.Aliases.TextBox, Constants.PropertyEditors.Aliases.TextArea + Constants.PropertyEditors.Aliases.TextBox, Constants.PropertyEditors.Aliases.TextArea, }; private readonly HtmlLocalLinkParser _linkParser; @@ -29,8 +29,7 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Snapshot; - public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, - object? source, bool preview) + public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) { if (source == null) { @@ -46,13 +45,13 @@ public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType return sourceString; } - public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => + // source should come from ConvertSource and be a string (or null) already inter ?? string.Empty; - public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => + public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => + // source should come from ConvertSource and be a string (or null) already inter; } diff --git a/src/Umbraco.Core/PropertyEditors/TextboxConfiguration.cs b/src/Umbraco.Core/PropertyEditors/TextboxConfiguration.cs index 363eadd81dc2..26262f35897a 100644 --- a/src/Umbraco.Core/PropertyEditors/TextboxConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/TextboxConfiguration.cs @@ -1,11 +1,10 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Represents the configuration for the textbox value editor. /// public class TextboxConfiguration { - [ConfigurationField("maxChars", "Maximum allowed characters", "textstringlimited", - Description = "If empty, 512 character limit")] + [ConfigurationField("maxChars", "Maximum allowed characters", "textstringlimited", Description = "If empty, 512 character limit")] public int? MaxChars { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/TextboxConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/TextboxConfigurationEditor.cs index 8949a2f2471a..f0a776334b8f 100644 --- a/src/Umbraco.Core/PropertyEditors/TextboxConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/TextboxConfigurationEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; @@ -20,7 +20,8 @@ public TextboxConfigurationEditor(IIOHelper ioHelper) { } - public TextboxConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : base( + public TextboxConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) + : base( ioHelper, editorConfigurationParser) { } diff --git a/src/Umbraco.Core/PropertyEditors/TrueFalseConfiguration.cs b/src/Umbraco.Core/PropertyEditors/TrueFalseConfiguration.cs index 56db4ce52a58..604f4d3c303a 100644 --- a/src/Umbraco.Core/PropertyEditors/TrueFalseConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/TrueFalseConfiguration.cs @@ -1,17 +1,14 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; /// /// Represents the configuration for the boolean value editor. /// public class TrueFalseConfiguration { - [ConfigurationField("default", "Initial State", "boolean", - Description = - "The initial state for the toggle, when it is displayed for the first time in the backoffice, eg. for a new content item.")] + [ConfigurationField("default", "Initial State", "boolean", Description = "The initial state for the toggle, when it is displayed for the first time in the backoffice, eg. for a new content item.")] public bool Default { get; set; } - [ConfigurationField("showLabels", "Show toggle labels", "boolean", - Description = "Show labels next to toggle button.")] + [ConfigurationField("showLabels", "Show toggle labels", "boolean", Description = "Show labels next to toggle button.")] public bool ShowLabels { get; set; } [ConfigurationField("labelOn", "Label On", "textstring", Description = "Label text when enabled.")] diff --git a/src/Umbraco.Core/PropertyEditors/TrueFalseConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/TrueFalseConfigurationEditor.cs index cfb37697e9b5..72578f7c5ef8 100644 --- a/src/Umbraco.Core/PropertyEditors/TrueFalseConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/TrueFalseConfigurationEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; @@ -20,8 +20,8 @@ public TrueFalseConfigurationEditor(IIOHelper ioHelper) { } - public TrueFalseConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) : - base(ioHelper, editorConfigurationParser) + public TrueFalseConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) + : base(ioHelper, editorConfigurationParser) { } } diff --git a/src/Umbraco.Core/PropertyEditors/UnPublishedContentPropertyCacheCompressionOptions.cs b/src/Umbraco.Core/PropertyEditors/UnPublishedContentPropertyCacheCompressionOptions.cs index bc7cc532d954..4e5fc41d494d 100644 --- a/src/Umbraco.Core/PropertyEditors/UnPublishedContentPropertyCacheCompressionOptions.cs +++ b/src/Umbraco.Core/PropertyEditors/UnPublishedContentPropertyCacheCompressionOptions.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.PropertyEditors; @@ -7,19 +7,18 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// public class UnPublishedContentPropertyCacheCompressionOptions : IPropertyCacheCompressionOptions { - public bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor, - bool published) + public bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor, bool published) { if (!published && propertyType.SupportsPublishing && propertyType.ValueStorageType == ValueStorageType.Ntext) { - //Only compress non published content that supports publishing and the property is text + // Only compress non published content that supports publishing and the property is text return true; } if (propertyType.ValueStorageType == ValueStorageType.Integer && Constants.PropertyEditors.Aliases.Boolean.Equals(dataEditor.Alias)) { - //Compress boolean values from int to bool + // Compress boolean values from int to bool return true; } diff --git a/src/Umbraco.Core/PropertyEditors/UserPickerConfiguration.cs b/src/Umbraco.Core/PropertyEditors/UserPickerConfiguration.cs index d90e75fcfab1..9dce63bf124a 100644 --- a/src/Umbraco.Core/PropertyEditors/UserPickerConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/UserPickerConfiguration.cs @@ -1,9 +1,9 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; public class UserPickerConfiguration : ConfigurationEditor { public override IDictionary DefaultConfiguration => new Dictionary { - {"entityType", "User"}, {"multiPicker", "0"} + { "entityType", "User" }, { "multiPicker", "0" }, }; } diff --git a/src/Umbraco.Core/PropertyEditors/UserPickerPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/UserPickerPropertyEditor.cs index a0e317b69050..269178c0b03d 100644 --- a/src/Umbraco.Core/PropertyEditors/UserPickerPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/UserPickerPropertyEditor.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.PropertyEditors; +namespace Umbraco.Cms.Core.PropertyEditors; [DataEditor( Constants.PropertyEditors.Aliases.UserPicker, diff --git a/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs index 7c99ebaf0f26..5a9032303cf3 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.ComponentModel.DataAnnotations; @@ -26,7 +26,8 @@ public sealed class RegexValidator : IValueFormatValidator, IManifestValueValida /// the validator is used as an and the regular expression /// is supplied via the method. /// - public RegexValidator(ILocalizedTextService textService) : this(textService, string.Empty) + public RegexValidator(ILocalizedTextService textService) + : this(textService, string.Empty) { } @@ -58,7 +59,8 @@ public string Configuration if (string.IsNullOrWhiteSpace(value)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(value)); } @@ -90,14 +92,16 @@ public IEnumerable ValidateFormat(object? value, string? value if (string.IsNullOrWhiteSpace(format)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(format)); } if (value == null || !new Regex(format).IsMatch(value.ToString()!)) { - yield return new ValidationResult(_textService?.Localize("validation", "invalidPattern") ?? ValueIsInvalid, - new[] {"value"}); + yield return new ValidationResult( + _textService?.Localize("validation", "invalidPattern") ?? ValueIsInvalid, + new[] { "value" }); } } } diff --git a/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs index 323f1042a067..296e8eed361b 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs @@ -1,4 +1,4 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; @@ -27,8 +27,9 @@ public IEnumerable ValidateRequired(object? value, string? val { if (value == null) { - yield return new ValidationResult(_textService?.Localize("validation", "invalidNull") ?? ValueCannotBeNull, - new[] {"value"}); + yield return new ValidationResult( + _textService?.Localize("validation", "invalidNull") ?? ValueCannotBeNull, + new[] { "value" }); yield break; } @@ -37,7 +38,7 @@ public IEnumerable ValidateRequired(object? value, string? val if (value.ToString()?.DetectIsEmptyJson() ?? false) { yield return new ValidationResult( - _textService?.Localize("validation", "invalidEmpty") ?? ValueCannotBeEmpty, new[] {"value"}); + _textService?.Localize("validation", "invalidEmpty") ?? ValueCannotBeEmpty, new[] { "value" }); } yield break; @@ -46,7 +47,7 @@ public IEnumerable ValidateRequired(object? value, string? val if (value.ToString().IsNullOrWhiteSpace()) { yield return new ValidationResult( - _textService?.Localize("validation", "invalidEmpty") ?? ValueCannotBeEmpty, new[] {"value"}); + _textService?.Localize("validation", "invalidEmpty") ?? ValueCannotBeEmpty, new[] { "value" }); } } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberGroupPickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberGroupPickerValueConverter.cs index e04dbcc54619..a94da59c3696 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberGroupPickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberGroupPickerValueConverter.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; @@ -15,6 +15,5 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, - object? source, bool preview) => source?.ToString() ?? string.Empty; + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) => source?.ToString() ?? string.Empty; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs index d41c4ff71e39..8c1226419853 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MemberPickerValueConverter.cs @@ -33,8 +33,7 @@ public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof(IPublishedContent); - public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, - object? source, bool preview) + public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) { if (source == null) { @@ -56,8 +55,7 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) return null; } - public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel cacheLevel, object? source, bool preview) + public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview) { if (source == null) { @@ -82,8 +80,7 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) } else { - var sourceUdi = source as GuidUdi; - if (sourceUdi is null) + if (source is not GuidUdi sourceUdi) { return null; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs index c53c5fe2ad49..de8965ef3b4b 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs @@ -17,7 +17,7 @@ public class MultiNodeTreePickerValueConverter : PropertyValueConverterBase private static readonly List PropertiesToExclude = new() { Constants.Conventions.Content.InternalRedirectId.ToLower(CultureInfo.InvariantCulture), - Constants.Conventions.Content.Redirect.ToLower(CultureInfo.InvariantCulture) + Constants.Conventions.Content.Redirect.ToLower(CultureInfo.InvariantCulture), }; private readonly IMemberService _memberService; @@ -46,8 +46,7 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) ? typeof(IPublishedContent) : typeof(IEnumerable); - public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, - object? source, bool preview) + public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) { if (source == null) { @@ -56,7 +55,7 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) if (propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.MultiNodeTreePicker)) { - Udi[] nodeIds = source.ToString()? + Udi[]? nodeIds = source.ToString()? .Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) .Select(UdiParser.Parse) .ToArray(); @@ -66,8 +65,7 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) return null; } - public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel cacheLevel, object? source, bool preview) + public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview) { if (source == null) { @@ -90,8 +88,7 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot(); foreach (Udi udi in udis) { - var guidUdi = udi as GuidUdi; - if (guidUdi is null) + if (udi is not GuidUdi guidUdi) { continue; } @@ -100,17 +97,25 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) switch (udi.EntityType) { case Constants.UdiEntityType.Document: - multiNodeTreePickerItem = GetPublishedContent(udi, ref objectType, + multiNodeTreePickerItem = GetPublishedContent( + udi, + ref objectType, UmbracoObjectTypes.Document, id => publishedSnapshot.Content?.GetById(guidUdi.Guid)); break; case Constants.UdiEntityType.Media: - multiNodeTreePickerItem = GetPublishedContent(udi, ref objectType, - UmbracoObjectTypes.Media, id => publishedSnapshot.Media?.GetById(guidUdi.Guid)); + multiNodeTreePickerItem = GetPublishedContent( + udi, + ref objectType, + UmbracoObjectTypes.Media, + id => publishedSnapshot.Media?.GetById(guidUdi.Guid)); break; case Constants.UdiEntityType.Member: - multiNodeTreePickerItem = GetPublishedContent(udi, ref objectType, - UmbracoObjectTypes.Member, id => + multiNodeTreePickerItem = GetPublishedContent( + udi, + ref objectType, + UmbracoObjectTypes.Member, + id => { IMember? m = _memberService.GetByKey(guidUdi.Guid); if (m == null) @@ -151,6 +156,9 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) return source; } + private static bool IsSingleNodePicker(IPublishedPropertyType propertyType) => + propertyType.DataType.ConfigurationAs()?.MaxNumber == 1; + /// /// Attempt to get an IPublishedContent instance based on ID and content type /// @@ -162,8 +170,7 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) /// The requested content, or null if either it does not exist or does not match /// /// - private IPublishedContent? GetPublishedContent(T nodeId, ref UmbracoObjectTypes actualType, - UmbracoObjectTypes expectedType, Func contentFetcher) + private IPublishedContent? GetPublishedContent(T nodeId, ref UmbracoObjectTypes actualType, UmbracoObjectTypes expectedType, Func contentFetcher) { // is the actual type supported by the content fetcher? if (actualType != UmbracoObjectTypes.Unknown && actualType != expectedType) @@ -173,7 +180,7 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) } // attempt to get the content - IPublishedContent content = contentFetcher(nodeId); + IPublishedContent? content = contentFetcher(nodeId); if (content != null) { // if we found the content, assign the expected type to the actual type so we don't have to keep looking for other types of content @@ -182,7 +189,4 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) return content; } - - private static bool IsSingleNodePicker(IPublishedPropertyType propertyType) => - propertyType.DataType.ConfigurationAs()?.MaxNumber == 1; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs index 1affb1f21d0a..d3815d7f6f72 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs @@ -1,4 +1,4 @@ -using System.Xml; +using System.Xml; using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; @@ -6,7 +6,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; [DefaultPropertyValueConverter] public class MultipleTextStringValueConverter : PropertyValueConverterBase { - private static readonly string[] NewLineDelimiters = {"\r\n", "\r", "\n"}; + private static readonly string[] NewLineDelimiters = { "\r\n", "\r", "\n" }; public override bool IsConverter(IPublishedPropertyType propertyType) => Constants.PropertyEditors.Aliases.MultipleTextstring.Equals(propertyType.EditorAlias); @@ -17,8 +17,7 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, - object? source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) { // data is (both in database and xml): // @@ -28,14 +27,13 @@ public override object ConvertSourceToIntermediate(IPublishedElement owner, IPub // Efficient // // - var sourceString = source?.ToString(); if (string.IsNullOrWhiteSpace(sourceString)) { return Enumerable.Empty(); } - //SD: I have no idea why this logic is here, I'm pretty sure we've never saved the multiple txt string + // SD: I have no idea why this logic is here, I'm pretty sure we've never saved the multiple txt string // as xml in the database, it's always been new line delimited. Will ask Stephen about this. // In the meantime, we'll do this xml check, see if it parses and if not just continue with // splitting by newline @@ -48,7 +46,7 @@ public override object ConvertSourceToIntermediate(IPublishedElement owner, IPub { pos += "".Length; var npos = sourceString.IndexOf("<", pos, StringComparison.Ordinal); - var value = sourceString.Substring(pos, npos - pos); + var value = sourceString[pos..npos]; values.Add(value); pos = sourceString.IndexOf("", pos, StringComparison.Ordinal); } @@ -59,8 +57,7 @@ public override object ConvertSourceToIntermediate(IPublishedElement owner, IPub : values.ToArray(); } - public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) { var d = new XmlDocument(); XmlElement e = d.CreateElement("values"); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MustBeStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MustBeStringValueConverter.cs index 500d5fa2bfa2..141cfe53ec3d 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MustBeStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MustBeStringValueConverter.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; @@ -19,7 +19,7 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; [DefaultPropertyValueConverter] public class MustBeStringValueConverter : PropertyValueConverterBase { - private static readonly string[] Aliases = {Constants.PropertyEditors.Aliases.MultiNodeTreePicker}; + private static readonly string[] Aliases = { Constants.PropertyEditors.Aliases.MultiNodeTreePicker }; public override bool IsConverter(IPublishedPropertyType propertyType) => Aliases.Contains(propertyType.EditorAlias); @@ -30,6 +30,5 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, - object? source, bool preview) => source?.ToString(); + public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) => source?.ToString(); } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs index 836a9fe574c1..c18363a2db05 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/RadioButtonListValueConverter.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; @@ -15,8 +15,7 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, - object? source, bool preview) + public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) { Attempt attempt = source.TryConvertTo(); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs index c126f29b90b8..7503e6711fd4 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs @@ -19,20 +19,20 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, - object? source, bool preview) => + public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) => + // in xml a string is: string // in the database a string is: string // default value is: null source; - public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => + // source should come from ConvertSource and be a string (or null) already new HtmlEncodedString(inter == null ? string.Empty : (string)inter); - public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => + public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => + // source should come from ConvertSource and be a string (or null) already inter; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs index 5364ec4d992a..76f5b6226592 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/SliderValueConverter.cs @@ -15,6 +15,8 @@ public class SliderValueConverter : PropertyValueConverterBase public SliderValueConverter(IDataTypeService dataTypeService) => _dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService)); + public static void ClearCaches() => Storages.Clear(); + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.Slider); @@ -24,8 +26,7 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel cacheLevel, object? source, bool preview) + public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview) { if (source == null) { @@ -40,7 +41,7 @@ public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType if (minimumAttempt.Success && maximumAttempt.Success) { - return new Range {Maximum = maximumAttempt.Result, Minimum = minimumAttempt.Result}; + return new Range { Maximum = maximumAttempt.Result, Minimum = minimumAttempt.Result }; } } @@ -64,16 +65,15 @@ public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType /// The . /// private bool IsRangeDataType(int dataTypeId) => + // GetPreValuesCollectionByDataTypeId is cached at repository level; // still, the collection is deep-cloned so this is kinda expensive, // better to cache here + trigger refresh in DataTypeCacheRefresher // TODO: this is cheap now, remove the caching Storages.GetOrAdd(dataTypeId, id => { - IDataType dataType = _dataTypeService.GetDataType(id); - SliderConfiguration configuration = dataType?.ConfigurationAs(); + IDataType? dataType = _dataTypeService.GetDataType(id); + SliderConfiguration? configuration = dataType?.ConfigurationAs(); return configuration?.EnableRange ?? false; }); - - public static void ClearCaches() => Storages.Clear(); } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs index c9c6c416528f..3afc5a659670 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs @@ -20,6 +20,8 @@ public TagsValueConverter(IDataTypeService dataTypeService, IJsonSerializer json _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer)); } + public static void ClearCaches() => Storages.Clear(); + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(Constants.PropertyEditors.Aliases.Tags); @@ -29,8 +31,7 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, - object? source, bool preview) + public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) { if (source == null) { @@ -50,8 +51,7 @@ public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType return source.ToString()?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); } - public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel cacheLevel, object? source, bool preview) => (string[]?)source; + public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview) => (string[]?)source; /// /// Discovers if the tags data type is storing its data in a Json format @@ -63,14 +63,13 @@ public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType /// The . /// private bool JsonStorageType(int dataTypeId) => + // GetDataType(id) is cached at repository level; still, there is some // deep-cloning involved (expensive) - better cache here + trigger // refresh in DataTypeCacheRefresher Storages.GetOrAdd(dataTypeId, id => { - TagConfiguration configuration = _dataTypeService.GetDataType(id)?.ConfigurationAs(); + TagConfiguration? configuration = _dataTypeService.GetDataType(id)?.ConfigurationAs(); return configuration?.StorageType == TagsStorageType.Json; }); - - public static void ClearCaches() => Storages.Clear(); } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/UploadPropertyConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/UploadPropertyConverter.cs index 67b538330b64..7a9ab907d876 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/UploadPropertyConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/UploadPropertyConverter.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; @@ -17,6 +17,5 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel cacheLevel, object? source, bool preview) => source?.ToString() ?? ""; + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview) => source?.ToString() ?? string.Empty; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs index e16ece995b27..ab7f99e7f8d3 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; @@ -14,14 +14,12 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, - object? source, bool preview) + public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) { // in xml a boolean is: string // in the database a boolean is: string "1" or "0" or empty // typically the converter does not need to handle anything else ("true"...) // however there are cases where the value passed to the converter could be a non-string object, e.g. int, bool - if (source is string s) { if (s.Length == 0 || s == "0") @@ -58,9 +56,8 @@ public override object ConvertSourceToIntermediate(IPublishedElement owner, IPub } // default ConvertSourceToObject just returns source ie a boolean value + public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => - public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => // source should come from ConvertSource and be a boolean already (bool?)inter ?? false ? "1" : "0"; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueListConfiguration.cs b/src/Umbraco.Core/PropertyEditors/ValueListConfiguration.cs index 121c111e2392..ca727f700816 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueListConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueListConfiguration.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.PropertyEditors; @@ -13,8 +13,10 @@ public class ValueListConfiguration [DataContract] public class ValueListItem { - [DataMember(Name = "id")] public int Id { get; set; } + [DataMember(Name = "id")] + public int Id { get; set; } - [DataMember(Name = "value")] public string? Value { get; set; } + [DataMember(Name = "value")] + public string? Value { get; set; } } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueTypes.cs b/src/Umbraco.Core/PropertyEditors/ValueTypes.cs index 07375e875c21..ac6e6a9bb865 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueTypes.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueTypes.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.PropertyEditors; @@ -105,7 +105,8 @@ public static ValueStorageType ToStorageType(string valueType) return ValueStorageType.Date; default: - throw new ArgumentOutOfRangeException(nameof(valueType), + throw new ArgumentOutOfRangeException( + nameof(valueType), $"Value \"{valueType}\" is not a valid ValueTypes."); } } diff --git a/src/Umbraco.Core/PublishedCache/PublishedCacheBase.cs b/src/Umbraco.Core/PublishedCache/PublishedCacheBase.cs index b610febdc7d7..3e961ce434dd 100644 --- a/src/Umbraco.Core/PublishedCache/PublishedCacheBase.cs +++ b/src/Umbraco.Core/PublishedCache/PublishedCacheBase.cs @@ -1,4 +1,4 @@ -using System.Xml.XPath; +using System.Xml.XPath; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Xml; using Umbraco.Extensions; @@ -72,10 +72,13 @@ public IEnumerable GetByXPath(XPathExpression xpath, XPathVar public bool HasContent() => HasContent(PreviewDefault); public abstract IPublishedContentType? GetContentType(int id); + public abstract IPublishedContentType? GetContentType(string alias); + public abstract IPublishedContentType? GetContentType(Guid key); public virtual IEnumerable GetByContentType(IPublishedContentType contentType) => + // this is probably not super-efficient, but works // some cache implementation may want to override it, though GetAtRoot() diff --git a/src/Umbraco.Core/PublishedCache/PublishedElement.cs b/src/Umbraco.Core/PublishedCache/PublishedElement.cs index baa3b54b57f2..324a4aa8213a 100644 --- a/src/Umbraco.Core/PublishedCache/PublishedElement.cs +++ b/src/Umbraco.Core/PublishedCache/PublishedElement.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PropertyEditors; namespace Umbraco.Cms.Core.PublishedCache; @@ -13,11 +13,13 @@ namespace Umbraco.Cms.Core.PublishedCache; // public class PublishedElement : IPublishedElement { + #region Properties + + private readonly IPublishedProperty[] _propertiesArray; + // initializes a new instance of the PublishedElement class // within the context of a published snapshot service (eg a published content property value) - public PublishedElement(IPublishedContentType contentType, Guid key, Dictionary? values, - bool previewing, - PropertyCacheLevel referenceCacheLevel, IPublishedSnapshotAccessor? publishedSnapshotAccessor) + public PublishedElement(IPublishedContentType contentType, Guid key, Dictionary? values, bool previewing, PropertyCacheLevel referenceCacheLevel, IPublishedSnapshotAccessor? publishedSnapshotAccessor) { if (key == Guid.Empty) { @@ -46,8 +48,7 @@ public PublishedElement(IPublishedContentType contentType, Guid key, Dictionary< .Select(propertyType => { values.TryGetValue(propertyType.Alias, out var value); - return (IPublishedProperty)new PublishedElementPropertyBase(propertyType, this, - previewing, referenceCacheLevel, value, publishedSnapshotAccessor); + return (IPublishedProperty)new PublishedElementPropertyBase(propertyType, this, previewing, referenceCacheLevel, value, publishedSnapshotAccessor); }) .ToArray() ?? new IPublishedProperty[0]; @@ -58,8 +59,7 @@ public PublishedElement(IPublishedContentType contentType, Guid key, Dictionary< // + using an initial reference cache level of .None ensures that everything will be // cached at .Content level - and that reference cache level will propagate to all // properties - public PublishedElement(IPublishedContentType contentType, Guid key, Dictionary values, - bool previewing) + public PublishedElement(IPublishedContentType contentType, Guid key, Dictionary values, bool previewing) : this(contentType, key, values, previewing, PropertyCacheLevel.None, null) { } @@ -86,16 +86,12 @@ public PublishedElement(IPublishedContentType contentType, Guid key, Dictionary< return ignoreCase ? values : new Dictionary(values, StringComparer.OrdinalIgnoreCase); } - #region Properties - - private readonly IPublishedProperty[] _propertiesArray; - public IEnumerable Properties => _propertiesArray; public IPublishedProperty? GetProperty(string alias) { var index = ContentType.GetPropertyIndex(alias); - IPublishedProperty property = index < 0 ? null : _propertiesArray?[index]; + IPublishedProperty? property = index < 0 ? null : _propertiesArray?[index]; return property; } diff --git a/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs b/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs index ce77dfbd36f0..6beb094bef65 100644 --- a/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs +++ b/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs @@ -7,6 +7,8 @@ namespace Umbraco.Cms.Core.PublishedCache; internal class PublishedElementPropertyBase : PublishedPropertyBase { + protected readonly IPublishedElement Element; + // define constant - determines whether to use cache when previewing // to store eg routes, property converted values, anything - caching // means faster execution, but uses memory - not sure if we want it @@ -15,8 +17,6 @@ internal class PublishedElementPropertyBase : PublishedPropertyBase private readonly object _locko = new(); private readonly IPublishedSnapshotAccessor? _publishedSnapshotAccessor; private readonly object? _sourceValue; - - protected readonly IPublishedElement Element; protected readonly bool IsMember; protected readonly bool IsPreviewing; private CacheValues? _cacheValues; @@ -25,8 +25,12 @@ internal class PublishedElementPropertyBase : PublishedPropertyBase private object? _interValue; private string? _valuesCacheKey; - public PublishedElementPropertyBase(IPublishedPropertyType propertyType, IPublishedElement element, bool previewing, - PropertyCacheLevel referenceCacheLevel, object? sourceValue = null, + public PublishedElementPropertyBase( + IPublishedPropertyType propertyType, + IPublishedElement element, + bool previewing, + PropertyCacheLevel referenceCacheLevel, + object? sourceValue = null, IPublishedSnapshotAccessor? publishedSnapshotAccessor = null) : base(propertyType, referenceCacheLevel) { @@ -39,10 +43,12 @@ public PublishedElementPropertyBase(IPublishedPropertyType propertyType, IPublis // used to cache the CacheValues of this property // ReSharper disable InconsistentlySynchronizedField - internal string ValuesCacheKey => _valuesCacheKey - ?? (_valuesCacheKey = PropertyCacheValues(Element.Key, Alias, IsPreviewing)); - // ReSharper restore InconsistentlySynchronizedField + internal string ValuesCacheKey => _valuesCacheKey ??= PropertyCacheValues(Element.Key, Alias, IsPreviewing); + + public static string PropertyCacheValues(Guid contentUid, string typeAlias, bool previewing) => + "PublishedSnapshot.Property.CacheValues[" + (previewing ? "D:" : "P:") + contentUid + ":" + typeAlias + "]"; + // ReSharper restore InconsistentlySynchronizedField public override bool HasValue(string? culture = null, string? segment = null) { var hasValue = PropertyType.IsValue(_sourceValue, PropertyValueLevel.Source); @@ -75,8 +81,7 @@ public override bool HasValue(string? culture = null, string? segment = null) } } - public static string PropertyCacheValues(Guid contentUid, string typeAlias, bool previewing) => - "PublishedSnapshot.Property.CacheValues[" + (previewing ? "D:" : "P:") + contentUid + ":" + typeAlias + "]"; + public override object? GetSourceValue(string? culture = null, string? segment = null) => _sourceValue; private void GetCacheLevels(out PropertyCacheLevel cacheLevel, out PropertyCacheLevel referenceCacheLevel) { @@ -92,7 +97,6 @@ private void GetCacheLevels(out PropertyCacheLevel cacheLevel, out PropertyCache // currently (reference) caching at published snapshot, property specifies // elements, ok to use element. OTOH, currently caching at elements, // property specifies snapshot, need to use snapshot. - // if (PropertyType.CacheLevel > ReferenceCacheLevel || PropertyType.CacheLevel == PropertyCacheLevel.None) { cacheLevel = PropertyType.CacheLevel; @@ -116,7 +120,7 @@ private void GetCacheLevels(out PropertyCacheLevel cacheLevel, out PropertyCache return null; } - if (!_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot publishedSnapshot)) + if (!_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot)) { return null; } @@ -137,18 +141,19 @@ private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel) break; case PropertyCacheLevel.Element: // cache within the property object itself, ie within the content object - cacheValues = _cacheValues ?? (_cacheValues = new CacheValues()); + cacheValues = _cacheValues ??= new CacheValues(); break; case PropertyCacheLevel.Elements: // cache within the elements cache, depending... - IAppCache snapshotCache = GetSnapshotCache(); + IAppCache? snapshotCache = GetSnapshotCache(); cacheValues = (CacheValues?)snapshotCache?.Get(ValuesCacheKey, () => new CacheValues()) ?? new CacheValues(); break; case PropertyCacheLevel.Snapshot: - IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor?.GetRequiredPublishedSnapshot(); + IPublishedSnapshot? publishedSnapshot = _publishedSnapshotAccessor?.GetRequiredPublishedSnapshot(); + // cache within the snapshot cache - IAppCache facadeCache = publishedSnapshot?.SnapshotCache; + IAppCache? facadeCache = publishedSnapshot?.SnapshotCache; cacheValues = (CacheValues?)facadeCache?.Get(ValuesCacheKey, () => new CacheValues()) ?? new CacheValues(); break; @@ -171,8 +176,6 @@ private CacheValues GetCacheValues(PropertyCacheLevel cacheLevel) return _interValue; } - public override object? GetSourceValue(string? culture = null, string? segment = null) => _sourceValue; - public override object? GetValue(string? culture = null, string? segment = null) { GetCacheLevels(out PropertyCacheLevel cacheLevel, out PropertyCacheLevel referenceCacheLevel); diff --git a/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs b/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs index 0c793890d3f1..8f3e4fe8271b 100644 --- a/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs +++ b/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs @@ -18,7 +18,7 @@ public IPublishedSnapshot? PublishedSnapshot { get { - if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext umbracoContext)) + if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext)) { return null; } @@ -31,7 +31,7 @@ public IPublishedSnapshot? PublishedSnapshot public bool TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) { - if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext umbracoContext)) + if (!_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? umbracoContext)) { publishedSnapshot = null; return false; diff --git a/src/Umbraco.Core/ReflectionUtilities.cs b/src/Umbraco.Core/ReflectionUtilities.cs index e7b5bccc63af..3b81d360fd87 100644 --- a/src/Umbraco.Core/ReflectionUtilities.cs +++ b/src/Umbraco.Core/ReflectionUtilities.cs @@ -128,20 +128,18 @@ private static FieldInfo GetField(string fieldName) if (string.IsNullOrWhiteSpace(fieldName)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", - nameof(fieldName)); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(fieldName)); } // get the field - FieldInfo field = typeof(TDeclaring).GetField(fieldName, - BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + FieldInfo? field = typeof(TDeclaring).GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) { throw new InvalidOperationException($"Could not find field {typeof(TDeclaring)}.{fieldName}."); } // validate field type - if (field.FieldType != typeof(TValue)) // strict + if (field.FieldType != typeof(TValue)) { throw new ArgumentException( $"Value type {typeof(TValue)} does not match field {typeof(TDeclaring)}.{fieldName} type {field.FieldType}."); @@ -154,7 +152,7 @@ private static Func EmitFieldGetter(Fiel { // emit (DynamicMethod dm, ILGenerator ilgen) = - CreateIlGenerator(field.DeclaringType?.Module, new[] {typeof(TDeclaring)}, typeof(TValue)); + CreateIlGenerator(field.DeclaringType?.Module, new[] { typeof(TDeclaring) }, typeof(TValue)); ilgen.Emit(OpCodes.Ldarg_0); ilgen.Emit(OpCodes.Ldfld, field); ilgen.Return(); @@ -165,8 +163,7 @@ private static Func EmitFieldGetter(Fiel private static Action EmitFieldSetter(FieldInfo field) { // emit - (DynamicMethod dm, ILGenerator ilgen) = CreateIlGenerator(field.DeclaringType?.Module, - new[] {typeof(TDeclaring), typeof(TValue)}, typeof(void)); + (DynamicMethod dm, ILGenerator ilgen) = CreateIlGenerator(field.DeclaringType?.Module, new[] { typeof(TDeclaring), typeof(TValue) }, typeof(void)); ilgen.Emit(OpCodes.Ldarg_0); ilgen.Emit(OpCodes.Ldarg_1); ilgen.Emit(OpCodes.Stfld, field); @@ -201,8 +198,7 @@ private static Action EmitFieldSetter(Fi /// Could not find property getter for . /// . /// - public static Func? EmitPropertyGetter(string propertyName, - bool mustExist = true) + public static Func? EmitPropertyGetter(string propertyName, bool mustExist = true) { if (propertyName == null) { @@ -211,12 +207,10 @@ private static Action EmitFieldSetter(Fi if (string.IsNullOrWhiteSpace(propertyName)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", - nameof(propertyName)); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyName)); } - PropertyInfo property = typeof(TDeclaring).GetProperty(propertyName, - BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + PropertyInfo? property = typeof(TDeclaring).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property?.GetMethod != null) { @@ -253,8 +247,7 @@ private static Action EmitFieldSetter(Fi /// Could not find property setter for . /// . /// - public static Action? EmitPropertySetter(string propertyName, - bool mustExist = true) + public static Action? EmitPropertySetter(string propertyName, bool mustExist = true) { if (propertyName == null) { @@ -263,12 +256,10 @@ private static Action EmitFieldSetter(Fi if (string.IsNullOrWhiteSpace(propertyName)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", - nameof(propertyName)); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyName)); } - PropertyInfo property = typeof(TDeclaring).GetProperty(propertyName, - BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + PropertyInfo? property = typeof(TDeclaring).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property?.SetMethod != null) { @@ -315,12 +306,10 @@ public static (Func, Action) if (string.IsNullOrWhiteSpace(propertyName)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", - nameof(propertyName)); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyName)); } - PropertyInfo property = typeof(TDeclaring).GetProperty(propertyName, - BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + PropertyInfo? property = typeof(TDeclaring).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property?.GetMethod != null && property.SetMethod != null) { @@ -486,12 +475,11 @@ public static Action EmitPropertySetterUnsafe(ConstructorInfo ctor) private static TLambda EmitConstructorSafe(Type[] lambdaParameters, Type returned, ConstructorInfo ctor) { // get type and args - Type ctorDeclaring = ctor.DeclaringType; + Type? ctorDeclaring = ctor.DeclaringType; Type[] ctorParameters = ctor.GetParameters().Select(x => x.ParameterType).ToArray(); // validate arguments @@ -668,16 +656,14 @@ private static TLambda EmitConstructor(Type? declaring, Type[] lambdaPa if (string.IsNullOrWhiteSpace(methodName)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", - nameof(methodName)); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(methodName)); } - (Type lambdaDeclaring, Type[] lambdaParameters, Type lambdaReturned) = + (Type? lambdaDeclaring, Type[] lambdaParameters, Type lambdaReturned) = AnalyzeLambda(true, out var isFunction); // get the method infos - MethodInfo method = declaring.GetMethod(methodName, - BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, null, lambdaParameters, null); + MethodInfo? method = declaring.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, null, lambdaParameters, null); if (method == null || (isFunction && !lambdaReturned.IsAssignableFrom(method.ReturnType))) { if (!mustExist) @@ -712,12 +698,12 @@ public static TLambda EmitMethod(MethodInfo method) } // get type and args - Type methodDeclaring = method.DeclaringType; + Type? methodDeclaring = method.DeclaringType; Type methodReturned = method.ReturnType; Type[] methodParameters = method.GetParameters().Select(x => x.ParameterType).ToArray(); var isStatic = method.IsStatic; - (Type lambdaDeclaring, Type[] lambdaParameters, Type lambdaReturned) = + (Type? lambdaDeclaring, Type[] lambdaParameters, Type lambdaReturned) = AnalyzeLambda(isStatic, out var isFunction); // if not static, then the first lambda arg must be the method declaring type @@ -768,7 +754,7 @@ public static TLambda EmitMethodUnsafe(MethodInfo method) } var isStatic = method.IsStatic; - (Type lambdaDeclaring, Type[] lambdaParameters, Type lambdaReturned) = AnalyzeLambda(isStatic, out _); + (Type? lambdaDeclaring, Type[] lambdaParameters, Type lambdaReturned) = AnalyzeLambda(isStatic, out _); // emit - unsafe - use lambda's args and assume they are correct return EmitMethod(lambdaDeclaring, lambdaReturned, lambdaParameters, method); @@ -805,17 +791,15 @@ public static TLambda EmitMethodUnsafe(MethodInfo method) if (string.IsNullOrWhiteSpace(methodName)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", - nameof(methodName)); + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(methodName)); } // validate lambda type - (Type lambdaDeclaring, Type[] lambdaParameters, Type lambdaReturned) = + (Type? lambdaDeclaring, Type[] lambdaParameters, Type lambdaReturned) = AnalyzeLambda(false, out var isFunction); // get the method infos - MethodInfo method = lambdaDeclaring?.GetMethod(methodName, - BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, lambdaParameters, null); + MethodInfo? method = lambdaDeclaring?.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, lambdaParameters, null); if (method == null || (isFunction && method.ReturnType != lambdaReturned)) { if (!mustExist) @@ -833,8 +817,7 @@ public static TLambda EmitMethodUnsafe(MethodInfo method) // lambdaReturned = the lambda returned type (can be void) // lambdaArgTypes = the lambda argument types - private static TLambda EmitMethod(Type? lambdaDeclaring, Type lambdaReturned, Type[] lambdaParameters, - MethodInfo method) + private static TLambda EmitMethod(Type? lambdaDeclaring, Type lambdaReturned, Type[] lambdaParameters, MethodInfo method) { // non-static methods need the declaring type as first arg Type[] parameters = lambdaParameters; @@ -868,12 +851,11 @@ private static TLambda EmitMethod(Type? lambdaDeclaring, Type lambdaRet // hence, when !isStatic, the lambda cannot be a simple Action, as it requires at least one generic argument // when isFunction, the last generic argument of the lambda is the returned type // everything in between is parameters - private static (Type? Declaring, Type[] Parameters, Type Returned) AnalyzeLambda(bool isStatic, - bool isFunction) + private static (Type? Declaring, Type[] Parameters, Type Returned) AnalyzeLambda(bool isStatic, bool isFunction) { Type typeLambda = typeof(TLambda); - (Type declaring, Type[] parameters, Type returned) = AnalyzeLambda(isStatic, out var maybeFunction); + (Type? declaring, Type[] parameters, Type returned) = AnalyzeLambda(isStatic, out var maybeFunction); if (isFunction) { @@ -897,8 +879,7 @@ private static (Type? Declaring, Type[] Parameters, Type Returned) AnalyzeLambda // hence, when !isStatic, the lambda cannot be a simple Action, as it requires at least one generic argument // when isFunction, the last generic argument of the lambda is the returned type // everything in between is parameters - private static (Type? Declaring, Type[] Parameters, Type Returned) AnalyzeLambda(bool isStatic, - out bool isFunction) + private static (Type? Declaring, Type[] Parameters, Type Returned) AnalyzeLambda(bool isStatic, out bool isFunction) { isFunction = false; @@ -917,7 +898,7 @@ private static (Type? Declaring, Type[] Parameters, Type Returned) AnalyzeLambda return (null, Array.Empty(), typeof(void)); } - Type genericDefinition = typeLambda.IsGenericType ? typeLambda.GetGenericTypeDefinition() : null; + Type? genericDefinition = typeLambda.IsGenericType ? typeLambda.GetGenericTypeDefinition() : null; var name = genericDefinition?.FullName; if (name == null) @@ -1006,7 +987,7 @@ private static Type[] GetParameters(MethodInfo method, bool withDeclaring) // emits args private static void EmitLdargs(ILGenerator ilgen, Type[] lambdaArgTypes, Type[] methodArgTypes) { - OpCode[] ldargOpCodes = new[] {OpCodes.Ldarg_0, OpCodes.Ldarg_1, OpCodes.Ldarg_2, OpCodes.Ldarg_3}; + OpCode[] ldargOpCodes = new[] { OpCodes.Ldarg_0, OpCodes.Ldarg_1, OpCodes.Ldarg_2, OpCodes.Ldarg_3 }; if (lambdaArgTypes.Length != methodArgTypes.Length) { @@ -1024,7 +1005,7 @@ private static void EmitLdargs(ILGenerator ilgen, Type[] lambdaArgTypes, Type[] ilgen.Emit(OpCodes.Ldarg, i); } - //var local = false; + // var local = false; EmitInputAdapter(ilgen, lambdaArgTypes[i], methodArgTypes[i] /*, ref local*/); } } @@ -1057,49 +1038,40 @@ private static void EmitInputAdapter(ILGenerator ilgen, Type inputType, Type met // parameter is value type, but input is reference type // unbox the input to the parameter value type // this is more or less equivalent to the ToT method below - Label unbox = ilgen.DefineLabel(); - //if (!local) - //{ + // if (!local) + // { // ilgen.DeclareLocal(typeof(object)); // declare local var for st/ld loc_0 // local = true; - //} + // } // stack: value // following code can be replaced with .Dump (and then we don't need the local variable anymore) - //ilgen.Emit(OpCodes.Stloc_0); // pop value into loc.0 + // ilgen.Emit(OpCodes.Stloc_0); // pop value into loc.0 //// stack: - //ilgen.Emit(OpCodes.Ldloc_0); // push loc.0 - //ilgen.Emit(OpCodes.Ldloc_0); // push loc.0 - + // ilgen.Emit(OpCodes.Ldloc_0); // push loc.0 + // ilgen.Emit(OpCodes.Ldloc_0); // push loc.0 ilgen.Emit(OpCodes.Dup); // duplicate top of stack // stack: value ; value - - ilgen.Emit(OpCodes.Isinst, - methodParamType); // test, pops value, and pushes either a null ref, or an instance of the type + ilgen.Emit(OpCodes.Isinst, methodParamType); // test, pops value, and pushes either a null ref, or an instance of the type // stack: inst|null ; value - ilgen.Emit(OpCodes.Ldnull); // push null // stack: null ; inst|null ; value - ilgen.Emit(OpCodes .Cgt_Un); // compare what isInst returned to null - pops 2 values, and pushes 1 if greater else 0 // stack: 0|1 ; value - ilgen.Emit(OpCodes.Brtrue_S, unbox); // pops value, branches to unbox if true, ie nonzero // stack: value - ilgen.Convert(methodParamType); // convert // stack: value|converted - ilgen.MarkLabel(unbox); ilgen.Emit(OpCodes.Unbox_Any, methodParamType); } @@ -1119,11 +1091,10 @@ private static void EmitInputAdapter(ILGenerator ilgen, Type inputType, Type met } } - //private static T ToT(object o) - //{ - // return o is T t ? t : (T) System.Convert.ChangeType(o, typeof(T)); - //} - + // private static T ToT(object o) + // { + // return o is T t ? t : (T) System.Convert.ChangeType(o, typeof(T)); + // } private static MethodInfo? _convertMethod; private static MethodInfo? _getTypeFromHandle; @@ -1131,14 +1102,12 @@ private static void Convert(this ILGenerator ilgen, Type type) { if (_getTypeFromHandle == null) { - _getTypeFromHandle = typeof(Type).GetMethod("GetTypeFromHandle", BindingFlags.Public | BindingFlags.Static, - null, new[] {typeof(RuntimeTypeHandle)}, null); + _getTypeFromHandle = typeof(Type).GetMethod("GetTypeFromHandle", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(RuntimeTypeHandle) }, null); } if (_convertMethod == null) { - _convertMethod = typeof(Convert).GetMethod("ChangeType", BindingFlags.Public | BindingFlags.Static, null, - new[] {typeof(object), typeof(Type)}, null); + _convertMethod = typeof(Convert).GetMethod("ChangeType", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(object), typeof(Type) }, null); } ilgen.Emit(OpCodes.Ldtoken, type); @@ -1165,7 +1134,6 @@ private static void EmitOutputAdapter(ILGenerator ilgen, Type outputType, Type m // as an object, when emitting the method as a Func<..., object> - anything else // is pointless really - so we box value types, and ensure that non value types // can be assigned - if (methodReturnedType.IsValueType) { if (outputType.IsValueType) diff --git a/src/Umbraco.Core/Routing/MediaUrlProviderCollection.cs b/src/Umbraco.Core/Routing/MediaUrlProviderCollection.cs index 51be5c934350..85b864d717be 100644 --- a/src/Umbraco.Core/Routing/MediaUrlProviderCollection.cs +++ b/src/Umbraco.Core/Routing/MediaUrlProviderCollection.cs @@ -4,7 +4,8 @@ namespace Umbraco.Cms.Core.Routing; public class MediaUrlProviderCollection : BuilderCollectionBase { - public MediaUrlProviderCollection(Func> items) : base(items) + public MediaUrlProviderCollection(Func> items) + : base(items) { } } diff --git a/src/Umbraco.Core/Routing/MediaUrlProviderCollectionBuilder.cs b/src/Umbraco.Core/Routing/MediaUrlProviderCollectionBuilder.cs index 366b74a5eb6f..fbe97f40e3e8 100644 --- a/src/Umbraco.Core/Routing/MediaUrlProviderCollectionBuilder.cs +++ b/src/Umbraco.Core/Routing/MediaUrlProviderCollectionBuilder.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Routing; diff --git a/src/Umbraco.Core/Routing/PublishedRequestOld.cs b/src/Umbraco.Core/Routing/PublishedRequestOld.cs index ccd05db43424..c7167971dfb3 100644 --- a/src/Umbraco.Core/Routing/PublishedRequestOld.cs +++ b/src/Umbraco.Core/Routing/PublishedRequestOld.cs @@ -22,8 +22,7 @@ public class PublishedRequestOld // : IPublishedRequest /// /// Initializes a new instance of the class. /// - public PublishedRequestOld(IPublishedRouter publishedRouter, IUmbracoContext umbracoContext, - IOptions webRoutingSettings, Uri? uri = null) + public PublishedRequestOld(IPublishedRouter publishedRouter, IUmbracoContext umbracoContext, IOptions webRoutingSettings, Uri? uri = null) { UmbracoContext = umbracoContext ?? throw new ArgumentNullException(nameof(umbracoContext)); _publishedRouter = publishedRouter ?? throw new ArgumentNullException(nameof(publishedRouter)); @@ -47,10 +46,10 @@ public PublishedRequestOld(IPublishedRouter publishedRouter, IUmbracoContext umb ///// ///// Prepares the request. ///// - //public void Prepare() - //{ - // _publishedRouter.PrepareRequest(this); - //} + // public void Prepare() + // { + // _publishedRouter.PrepareRequest(this); + // } /// /// Gets or sets a value indicating whether the Umbraco Backoffice should ignore a collision for this request. @@ -67,7 +66,6 @@ public PublishedRequestOld(IPublishedRouter publishedRouter, IUmbracoContext umb /// public string? TemplateAlias => Template?.Alias; - /// /// Gets or sets the content request's domain. /// @@ -113,7 +111,7 @@ public void EnsureWriteable() } } - //#region Events + // #region Events ///// ///// Triggers before the published content request is prepared. @@ -121,7 +119,7 @@ public void EnsureWriteable() ///// When the event triggers, no preparation has been done. It is still possible to ///// modify the request's Uri property, for example to restore its original, public-facing value ///// that might have been modified by an in-between equipment such as a load-balancer. - //public static event EventHandler Preparing; + // public static event EventHandler Preparing; ///// ///// Triggers once the published content request has been prepared, but before it is processed. @@ -129,39 +127,38 @@ public void EnsureWriteable() ///// When the event triggers, preparation is done ie domain, culture, document, template, ///// rendering engine, etc. have been setup. It is then possible to change anything, before ///// the request is actually processed and rendered by Umbraco. - //public static event EventHandler Prepared; + // public static event EventHandler Prepared; ///// ///// Triggers the Preparing event. ///// - //public void OnPreparing() - //{ - // Preparing?.Invoke(this, EventArgs.Empty); - //} + // public void OnPreparing() + // { + // Preparing?.Invoke(this, EventArgs.Empty); + // } ///// ///// Triggers the Prepared event. ///// - //public void OnPrepared() - //{ - // Prepared?.Invoke(this, EventArgs.Empty); - - // if (HasPublishedContent == false) - // Is404 = true; // safety + // public void OnPrepared() + // { + // Prepared?.Invoke(this, EventArgs.Empty); - // _readonly = true; - //} + // if (HasPublishedContent == false) + // Is404 = true; // safety - //#endregion + // _readonly = true; + // } + // #endregion #region PublishedContent ///// ///// Gets or sets the requested content. ///// ///// Setting the requested content clears Template. - //public IPublishedContent PublishedContent - //{ + // public IPublishedContent PublishedContent + // { // get { return _publishedContent; } // set // { @@ -170,7 +167,7 @@ public void EnsureWriteable() // IsInternalRedirectPublishedContent = false; // TemplateModel = null; // } - //} + // } /// /// Sets the requested content, following an internal redirect. @@ -182,39 +179,39 @@ public void EnsureWriteable() /// public void SetInternalRedirectPublishedContent(IPublishedContent content) { - //if (content == null) - // throw new ArgumentNullException(nameof(content)); - //EnsureWriteable(); + // if (content == null) + // throw new ArgumentNullException(nameof(content)); + // EnsureWriteable(); //// unless a template has been set already by the finder, //// template should be null at that point. //// IsInternalRedirect if IsInitial, or already IsInternalRedirect - //var isInternalRedirect = IsInitialPublishedContent || IsInternalRedirectPublishedContent; + // var isInternalRedirect = IsInitialPublishedContent || IsInternalRedirectPublishedContent; //// redirecting to self - //if (content.Id == PublishedContent.Id) // neither can be null - //{ - // // no need to set PublishedContent, we're done - // IsInternalRedirectPublishedContent = isInternalRedirect; - // return; - //} + // if (content.Id == PublishedContent.Id) // neither can be null + // { + // // no need to set PublishedContent, we're done + // IsInternalRedirectPublishedContent = isInternalRedirect; + // return; + // } //// else //// save - //var template = Template; + // var template = Template; //// set published content - this resets the template, and sets IsInternalRedirect to false - //PublishedContent = content; - //IsInternalRedirectPublishedContent = isInternalRedirect; + // PublishedContent = content; + // IsInternalRedirectPublishedContent = isInternalRedirect; //// must restore the template if it's an internal redirect & the config option is set - //if (isInternalRedirect && _webRoutingSettings.InternalRedirectPreservesTemplate) - //{ + // if (isInternalRedirect && _webRoutingSettings.InternalRedirectPreservesTemplate) + // { // // restore // TemplateModel = template; - //} + // } } /// @@ -258,8 +255,6 @@ public void SetIsInitialPublishedContent() // note: do we want to have an ordered list of alternate cultures, // to allow for fallbacks when doing dictionary lookup and such? - - #region Status /// @@ -345,7 +340,9 @@ public void SetRedirect(string url, int status) RedirectUrl = url; IsRedirectPermanent = status == 301 || status == 308; - if (status != 301 && status != 302) // default redirect statuses + + // default redirect statuses + if (status != 301 && status != 302) { ResponseStatusCode = status; } @@ -393,13 +390,13 @@ public void SetResponseStatus(int code, string? description = null) #region Response Cache - /// - /// Gets or sets the System.Web.HttpCacheability - /// + // /// + // /// Gets or sets the System.Web.HttpCacheability + // /// // Note: we used to set a default value here but that would then be the default // for ALL requests, we shouldn't overwrite it though if people are using [OutputCache] for example // see: https://our.umbraco.com/forum/using-umbraco-and-getting-started/79715-output-cache-in-umbraco-752 - //public HttpCacheability Cacheability { get; set; } + // public HttpCacheability Cacheability { get; set; } /// /// Gets or sets a list of Extensions to append to the Response.Cache object. diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs index 14cd36a4c17a..256e9b4d499b 100644 --- a/src/Umbraco.Core/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -95,7 +95,8 @@ public async Task CreateRequestAsync(Uri uri) } /// - public async Task RouteRequestAsync(IPublishedRequestBuilder builder, + public async Task RouteRequestAsync( + IPublishedRequestBuilder builder, RouteRequestOptions options) { // outbound routing performs different/simpler logic @@ -117,7 +118,8 @@ public async Task RouteRequestAsync(IPublishedRequestBuilder } /// - public async Task UpdateRequestAsync(IPublishedRequest request, + public async Task UpdateRequestAsync( + IPublishedRequest request, IPublishedContent? publishedContent) { // store the original (if any) @@ -153,6 +155,32 @@ public async Task UpdateRequestAsync(IPublishedRequest reques return BuildRequest(builder); } + /// + /// This method finalizes/builds the PCR with the values assigned. + /// + /// + /// Returns false if the request was not successfully configured + /// + /// + /// This method logic has been put into it's own method in case developers have created a custom PCR or are assigning + /// their own values + /// but need to finalize it themselves. + /// + internal IPublishedRequest BuildRequest(IPublishedRequestBuilder builder) + { + IPublishedRequest result = builder.Build(); + + if (!builder.HasPublishedContent()) + { + return result; + } + + // set the culture -- again, 'cos it might have changed in the event handler + SetVariationContext(result.Culture); + + return result; + } + private async Task TryRouteRequest(IPublishedRequestBuilder request) { FindDomain(request); @@ -240,32 +268,6 @@ private async Task RouteRequestInternalAsync(IPublishedRequestBuilder builder) // to find out the appropriate template } - /// - /// This method finalizes/builds the PCR with the values assigned. - /// - /// - /// Returns false if the request was not successfully configured - /// - /// - /// This method logic has been put into it's own method in case developers have created a custom PCR or are assigning - /// their own values - /// but need to finalize it themselves. - /// - internal IPublishedRequest BuildRequest(IPublishedRequestBuilder builder) - { - IPublishedRequest result = builder.Build(); - - if (!builder.HasPublishedContent()) - { - return result; - } - - // set the culture -- again, 'cos it might have changed in the event handler - SetVariationContext(result.Culture); - - return result; - } - /// /// Finds the site root (if any) matching the http request, and updates the PublishedRequest accordingly. /// @@ -322,7 +324,8 @@ bool IsPublishedContentDomain(Domain domain) // matching an existing domain if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("{TracePrefix}Matches domain={Domain}, rootId={RootContentId}, culture={Culture}", + _logger.LogDebug( + "{TracePrefix}Matches domain={Domain}, rootId={RootContentId}, culture={Culture}", tracePrefix, domainAndUri.Name, domainAndUri.ContentId, domainAndUri.Culture); } @@ -384,7 +387,8 @@ internal void HandleWildcardDomains(IPublishedRequestBuilder request) request.SetCulture(domain.Culture); if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("{TracePrefix}Got domain on node {DomainContentId}, set culture to {CultureName}", + _logger.LogDebug( + "{TracePrefix}Got domain on node {DomainContentId}, set culture to {CultureName}", tracePrefix, domain.ContentId, request.Culture); } } @@ -408,8 +412,8 @@ internal bool FindTemplateRenderingEngineInDirectory(DirectoryInfo directory, st if (pos > 0) { // recurse - DirectoryInfo? subdir = directory.GetDirectories(alias.Substring(0, pos)).FirstOrDefault(); - alias = alias.Substring(pos + 1); + DirectoryInfo? subdir = directory.GetDirectories(alias[..pos]).FirstOrDefault(); + alias = alias[(pos + 1)..]; return subdir != null && FindTemplateRenderingEngineInDirectory(subdir, alias, extensions); } @@ -535,7 +539,8 @@ private async Task HandlePublishedContent(IPublishedRequestBuilder request) // loop while we don't have page, ie the redirect or access // got us to nowhere and now we need to run the notFoundLookup again // as long as it's not running out of control ie infinite loop of some sort - } while (request.PublishedContent == null && i++ < maxLoop); + } + while (request.PublishedContent == null && i++ < maxLoop); if (i == maxLoop || j == maxLoop) { @@ -578,7 +583,8 @@ private bool FollowInternalRedirects(IPublishedRequestBuilder request) var redirect = false; var valid = false; IPublishedContent? internalRedirectNode = null; - var internalRedirectId = request.PublishedContent.Value(_publishedValueFallback, + var internalRedirectId = request.PublishedContent.Value( + _publishedValueFallback, Constants.Conventions.Content.InternalRedirectId, defaultValue: -1); IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); @@ -590,7 +596,8 @@ private bool FollowInternalRedirects(IPublishedRequestBuilder request) } else { - GuidUdi? udiInternalRedirectId = request.PublishedContent.Value(_publishedValueFallback, + GuidUdi? udiInternalRedirectId = request.PublishedContent.Value( + _publishedValueFallback, Constants.Conventions.Content.InternalRedirectId); if (udiInternalRedirectId is not null) { @@ -709,7 +716,8 @@ private void FindTemplate(IPublishedRequestBuilder request, bool contentFoundByF { if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", + _logger.LogDebug( + "FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); } } @@ -754,7 +762,8 @@ private void FindTemplate(IPublishedRequestBuilder request, bool contentFoundByF request.SetTemplate(template); if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("FindTemplate: Got alternative template id={TemplateId} alias={TemplateAlias}", + _logger.LogDebug( + "FindTemplate: Got alternative template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); } } @@ -773,13 +782,15 @@ private void FindTemplate(IPublishedRequestBuilder request, bool contentFoundByF _logger.LogWarning( "FindTemplate: Alternative template {TemplateAlias} is not allowed on node {NodeId}, ignoring.", altTemplate, request.PublishedContent.Id); + // no allowed, back to default var templateId = request.PublishedContent.TemplateId; ITemplate? template = GetTemplate(templateId); request.SetTemplate(template); if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", + _logger.LogDebug( + "FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", template?.Id, template?.Alias); } } @@ -869,7 +880,8 @@ private void FollowExternalRedirect(IPublishedRequestBuilder request) { // might be a UDI instead of an int Id GuidUdi? redirectUdi = - request.PublishedContent.Value(_publishedValueFallback, + request.PublishedContent.Value( + _publishedValueFallback, Constants.Conventions.Content.Redirect); if (redirectUdi is not null) { diff --git a/src/Umbraco.Core/Routing/RouteDirection.cs b/src/Umbraco.Core/Routing/RouteDirection.cs index 546ea2030a00..7ba637c28867 100644 --- a/src/Umbraco.Core/Routing/RouteDirection.cs +++ b/src/Umbraco.Core/Routing/RouteDirection.cs @@ -13,5 +13,5 @@ public enum RouteDirection /// /// An outbound route used to generate a URL for a content item /// - Outbound = 2 + Outbound = 2, } diff --git a/src/Umbraco.Core/Routing/SiteDomainMapper.cs b/src/Umbraco.Core/Routing/SiteDomainMapper.cs index b317b3b55eba..eaea1cd8bcd8 100644 --- a/src/Umbraco.Core/Routing/SiteDomainMapper.cs +++ b/src/Umbraco.Core/Routing/SiteDomainMapper.cs @@ -8,7 +8,16 @@ namespace Umbraco.Cms.Core.Routing; /// public class SiteDomainMapper : ISiteDomainMapper, IDisposable { + // these are for validation + // private const string DomainValidationSource = @"^(\*|((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/[-\w]*)?))$"; + private const string DomainValidationSource = @"^(((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/)?))$"; + + #region Configure + + private readonly ReaderWriterLockSlim _configLock = new(); + public void Dispose() => + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(true); @@ -27,20 +36,14 @@ protected virtual void Dispose(bool disposing) } } - #region Configure - - private readonly ReaderWriterLockSlim _configLock = new(); private Dictionary>? _qualifiedSites; private bool _disposedValue; internal Dictionary? Sites { get; private set; } - internal Dictionary>? Bindings { get; private set; } - // these are for validation - //private const string DomainValidationSource = @"^(\*|((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/[-\w]*)?))$"; - private const string DomainValidationSource = @"^(((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/)?))$"; + internal Dictionary>? Bindings { get; private set; } - private static readonly Regex s_domainValidation = + private static readonly Regex DomainValidation = new(DomainValidationSource, RegexOptions.IgnoreCase | RegexOptions.Compiled); /// @@ -65,12 +68,38 @@ public void Clear() } } + /// + /// Adds a site. + /// + /// A key uniquely identifying the site. + /// The site domains. + /// At the moment there is no public way to remove a site. Clear and reconfigure. + public void AddSite(string key, IEnumerable domains) + { + try + { + _configLock.EnterWriteLock(); + + Sites ??= new Dictionary(); + Sites[key] = ValidateDomains(domains).ToArray(); + _qualifiedSites = null; + } + finally + { + if (_configLock.IsWriteLockHeld) + { + _configLock.ExitWriteLock(); + } + } + } + private IEnumerable ValidateDomains(IEnumerable domains) => + // must use authority format w/optional scheme and port, but no path // any domain should appear only once domains.Select(domain => { - if (!s_domainValidation.IsMatch(domain)) + if (!DomainValidation.IsMatch(domain)) { throw new ArgumentOutOfRangeException(nameof(domains), $"Invalid domain: \"{domain}\"."); } @@ -84,13 +113,13 @@ private IEnumerable ValidateDomains(IEnumerable domains) => /// A key uniquely identifying the site. /// The site domains. /// At the moment there is no public way to remove a site. Clear and reconfigure. - public void AddSite(string key, IEnumerable domains) + public void AddSite(string key, params string[] domains) { try { _configLock.EnterWriteLock(); - Sites = Sites ?? new Dictionary(); + Sites ??= new Dictionary(); Sites[key] = ValidateDomains(domains).ToArray(); _qualifiedSites = null; } @@ -104,20 +133,43 @@ public void AddSite(string key, IEnumerable domains) } /// - /// Adds a site. + /// Binds some sites. /// - /// A key uniquely identifying the site. - /// The site domains. - /// At the moment there is no public way to remove a site. Clear and reconfigure. - public void AddSite(string key, params string[] domains) + /// The keys uniquely identifying the sites to bind. + /// + /// At the moment there is no public way to unbind sites. Clear and reconfigure. + /// If site1 is bound to site2 and site2 is bound to site3 then site1 is bound to site3. + /// + public void BindSites(params string[] keys) { try { _configLock.EnterWriteLock(); - Sites = Sites ?? new Dictionary(); - Sites[key] = ValidateDomains(domains).ToArray(); - _qualifiedSites = null; + foreach (var key in keys.Where(key => !Sites?.ContainsKey(key) ?? false)) + { + throw new ArgumentException($"Not an existing site key: {key}.", nameof(keys)); + } + + Bindings ??= new Dictionary>(); + + var allkeys = Bindings + .Where(kvp => keys.Contains(kvp.Key)) + .SelectMany(kvp => kvp.Value) + .Union(keys) + .ToArray(); + + foreach (var key in allkeys) + { + if (!Bindings.ContainsKey(key)) + { + Bindings[key] = new List(); + } + + var xkey = key; + IEnumerable addKeys = allkeys.Where(k => k != xkey).Except(Bindings[key]); + Bindings[key].AddRange(addKeys); + } } finally { @@ -178,61 +230,12 @@ internal void RemoveSite(string key) } } - /// - /// Binds some sites. - /// - /// The keys uniquely identifying the sites to bind. - /// - /// At the moment there is no public way to unbind sites. Clear and reconfigure. - /// If site1 is bound to site2 and site2 is bound to site3 then site1 is bound to site3. - /// - public void BindSites(params string[] keys) - { - try - { - _configLock.EnterWriteLock(); - - foreach (var key in keys.Where(key => !Sites?.ContainsKey(key) ?? false)) - { - throw new ArgumentException($"Not an existing site key: {key}.", nameof(keys)); - } - - Bindings = Bindings ?? new Dictionary>(); - - var allkeys = Bindings - .Where(kvp => keys.Contains(kvp.Key)) - .SelectMany(kvp => kvp.Value) - .Union(keys) - .ToArray(); - - foreach (var key in allkeys) - { - if (!Bindings.ContainsKey(key)) - { - Bindings[key] = new List(); - } - - var xkey = key; - IEnumerable addKeys = allkeys.Where(k => k != xkey).Except(Bindings[key]); - Bindings[key].AddRange(addKeys); - } - } - finally - { - if (_configLock.IsWriteLockHeld) - { - _configLock.ExitWriteLock(); - } - } - } - #endregion #region Map domains /// - public virtual DomainAndUri? MapDomain(IReadOnlyCollection domainAndUris, Uri current, - string? culture, string? defaultCulture) + public virtual DomainAndUri? MapDomain(IReadOnlyCollection domainAndUris, Uri current, string? culture, string? defaultCulture) { var currentAuthority = current.GetLeftPart(UriPartial.Authority); Dictionary? qualifiedSites = GetQualifiedSites(current); @@ -241,11 +244,9 @@ public void BindSites(params string[] keys) } /// - public virtual IEnumerable MapDomains(IReadOnlyCollection domainAndUris, - Uri current, bool excludeDefault, string? culture, string? defaultCulture) + public virtual IEnumerable MapDomains(IReadOnlyCollection domainAndUris, Uri current, bool excludeDefault, string? culture, string? defaultCulture) { // TODO: ignoring cultures entirely? - var currentAuthority = current.GetLeftPart(UriPartial.Authority); KeyValuePair[]? candidateSites = null; IEnumerable ret = domainAndUris; @@ -273,8 +274,7 @@ public virtual IEnumerable MapDomains(IReadOnlyCollection d != mainDomain); } } @@ -291,10 +291,9 @@ public virtual IEnumerable MapDomains(IReadOnlyCollection))) { - candidateSites = new[] {currentSite}; + candidateSites = new[] { currentSite }; if (Bindings != null && Bindings.ContainsKey(currentSite.Key)) { IEnumerable> boundSites = @@ -354,7 +353,7 @@ public virtual IEnumerable MapDomains(IReadOnlyCollection>(); + _qualifiedSites ??= new Dictionary>(); // convert sites into authority sites based upon current scheme // because some domains in the sites might not have a scheme -- and cache @@ -364,16 +363,19 @@ public virtual IEnumerable MapDomains(IReadOnlyCollection kvp.Value.Select(d => new Uri(UriUtilityCore.StartWithScheme(d, current.Scheme)) .GetLeftPart(UriPartial.Authority)) - .ToArray() - ); + .ToArray()); // .ToDictionary will evaluate and create the dictionary immediately // the new value is .ToArray so it will also be evaluated immediately // therefore it is safe to return and exit the configuration lock } - private DomainAndUri? MapDomain(IReadOnlyCollection domainAndUris, - Dictionary? qualifiedSites, string currentAuthority, string? culture, string? defaultCulture) + private DomainAndUri? MapDomain( + IReadOnlyCollection domainAndUris, + Dictionary? qualifiedSites, + string currentAuthority, + string? culture, + string? defaultCulture) { if (domainAndUris == null) { @@ -408,14 +410,14 @@ public virtual IEnumerable MapDomains(IReadOnlyCollection site.Key != currentSite.Key) .Select(site => domainAndUris.FirstOrDefault(domainAndUri => site.Value.Contains(domainAndUri.Uri.GetLeftPart(UriPartial.Authority)))) .FirstOrDefault(domainAndUri => domainAndUri != null); // random, really - ret = ret ?? domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)) ?? domainAndUris.First(); + ret ??= domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)) ?? domainAndUris.First(); return ret; } diff --git a/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs index af9107048ad4..fe1e83d2545f 100644 --- a/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs +++ b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs @@ -32,7 +32,7 @@ public UmbracoRequestPaths(IOptions globalSettings, IHostingEnvi .EnsureStartsWith('/').TrimStart(_appPath.EnsureStartsWith('/')).EnsureStartsWith('/'); _mvcArea = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment); - _defaultUmbPaths = new List {"/" + _mvcArea, "/" + _mvcArea + "/"}; + _defaultUmbPaths = new List { "/" + _mvcArea, "/" + _mvcArea + "/" }; _backOfficeMvcPath = "/" + _mvcArea + "/BackOffice/"; _previewMvcPath = "/" + _mvcArea + "/Preview/"; _surfaceMvcPath = "/" + _mvcArea + "/Surface/"; diff --git a/src/Umbraco.Core/Routing/UmbracoRouteResult.cs b/src/Umbraco.Core/Routing/UmbracoRouteResult.cs index 5311596ff4d4..67690e11e8b8 100644 --- a/src/Umbraco.Core/Routing/UmbracoRouteResult.cs +++ b/src/Umbraco.Core/Routing/UmbracoRouteResult.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Routing; +namespace Umbraco.Cms.Core.Routing; public enum UmbracoRouteResult { @@ -15,5 +15,5 @@ public enum UmbracoRouteResult /// /// Nothing matched /// - NotFound + NotFound, } diff --git a/src/Umbraco.Core/Routing/UriUtility.cs b/src/Umbraco.Core/Routing/UriUtility.cs index 0deb57721cad..fb59ada249be 100644 --- a/src/Umbraco.Core/Routing/UriUtility.cs +++ b/src/Umbraco.Core/Routing/UriUtility.cs @@ -26,6 +26,16 @@ public UriUtility(IHostingEnvironment hostingEnvironment) // will be "" or "/foo" public string? AppPathPrefix => _appPathPrefix; + // adds the virtual directory if any + // see also VirtualPathUtility.ToAbsolute + // TODO: Does this do anything differently than IHostingEnvironment.ToAbsolute? Seems it does less, maybe should be removed? + public string ToAbsolute(string url) + { + // return ResolveUrl(url); + url = url.TrimStart(Constants.CharArrays.Tilde); + return _appPathPrefix + url; + } + // internal for unit testing only internal void SetAppDomainAppVirtualPath(string appPath) { @@ -40,16 +50,6 @@ internal void SetAppDomainAppVirtualPath(string appPath) internal void ResetAppDomainAppVirtualPath(IHostingEnvironment hostingEnvironment) => SetAppDomainAppVirtualPath(hostingEnvironment.ApplicationVirtualPath); - // adds the virtual directory if any - // see also VirtualPathUtility.ToAbsolute - // TODO: Does this do anything differently than IHostingEnvironment.ToAbsolute? Seems it does less, maybe should be removed? - public string ToAbsolute(string url) - { - //return ResolveUrl(url); - url = url.TrimStart(Constants.CharArrays.Tilde); - return _appPathPrefix + url; - } - // strips the virtual directory if any // see also VirtualPathUtility.ToAppRelative public string ToAppRelative(string virtualPath) @@ -58,7 +58,7 @@ public string ToAppRelative(string virtualPath) && (virtualPath.Length == _appPathPrefix.Length || virtualPath[_appPathPrefix.Length] == '/')) { - virtualPath = virtualPath.Substring(_appPathPrefix.Length); + virtualPath = virtualPath[_appPathPrefix.Length..]; } if (virtualPath.Length == 0) @@ -123,7 +123,6 @@ public Uri UriToUmbraco(Uri uri) // if browsing http://example.com/sub/page1.aspx then // ResolveUrl("page2.aspx") returns "/page2.aspx" // Page.ResolveUrl("page2.aspx") returns "/sub/page2.aspx" (relative...) - // public string ResolveUrl(string relativeUrl) { if (relativeUrl == null) @@ -148,7 +147,7 @@ public string ResolveUrl(string relativeUrl) var sbUrl = new StringBuilder(); sbUrl.Append(_appPathPrefix); - if (sbUrl.Length == 0 || sbUrl[sbUrl.Length - 1] != '/') + if (sbUrl.Length == 0 || sbUrl[^1] != '/') { sbUrl.Append('/'); } @@ -160,7 +159,7 @@ public string ResolveUrl(string relativeUrl) && relativeUrl[0] == '~' && (relativeUrl[1] == '/' || relativeUrl[1] == '\\')) { - relativeUrl = relativeUrl.Substring(2); + relativeUrl = relativeUrl[2..]; foundSlash = true; } else @@ -205,7 +204,6 @@ public string ResolveUrl(string relativeUrl) #endregion - /// /// Returns an full URL with the host, port, etc... /// diff --git a/src/Umbraco.Core/Routing/UrlInfo.cs b/src/Umbraco.Core/Routing/UrlInfo.cs index 30ae9cc16ecd..f5b208fb73e8 100644 --- a/src/Umbraco.Core/Routing/UrlInfo.cs +++ b/src/Umbraco.Core/Routing/UrlInfo.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Routing; @@ -42,6 +42,13 @@ public UrlInfo(string text, bool isUrl, string? culture) [DataMember(Name = "text")] public string Text { get; } + public static bool operator ==(UrlInfo left, UrlInfo right) => Equals(left, right); + + /// + /// Creates a instance representing a true URL. + /// + public static UrlInfo Url(string text, string? culture = null) => new(text, true, culture); + /// /// Checks equality /// @@ -67,11 +74,6 @@ public bool Equals(UrlInfo? other) IsUrl == other.IsUrl && string.Equals(Text, other.Text, StringComparison.InvariantCultureIgnoreCase); } - /// - /// Creates a instance representing a true URL. - /// - public static UrlInfo Url(string text, string? culture = null) => new(text, true, culture); - /// /// Creates a instance representing a message. /// @@ -109,8 +111,6 @@ public override int GetHashCode() } } - public static bool operator ==(UrlInfo left, UrlInfo right) => Equals(left, right); - public static bool operator !=(UrlInfo left, UrlInfo right) => !Equals(left, right); public override string ToString() => Text; diff --git a/src/Umbraco.Core/Routing/UrlProvider.cs b/src/Umbraco.Core/Routing/UrlProvider.cs index cf78e63f2836..e4fe51686dd2 100644 --- a/src/Umbraco.Core/Routing/UrlProvider.cs +++ b/src/Umbraco.Core/Routing/UrlProvider.cs @@ -11,6 +11,8 @@ namespace Umbraco.Cms.Core.Routing; /// public class UrlProvider : IPublishedUrlProvider { + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + #region Ctor and configuration /// @@ -23,9 +25,7 @@ public class UrlProvider : IPublishedUrlProvider /// The list of media URL providers. /// The current variation accessor. /// - public UrlProvider(IUmbracoContextAccessor umbracoContextAccessor, IOptions routingSettings, - UrlProviderCollection urlProviders, MediaUrlProviderCollection mediaUrlProviders, - IVariationContextAccessor variationContextAccessor) + public UrlProvider(IUmbracoContextAccessor umbracoContextAccessor, IOptions routingSettings, UrlProviderCollection urlProviders, MediaUrlProviderCollection mediaUrlProviders, IVariationContextAccessor variationContextAccessor) { _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); @@ -36,7 +36,6 @@ public UrlProvider(IUmbracoContextAccessor umbracoContextAccessor, IOptions _urlProviders; private readonly IEnumerable _mediaUrlProviders; private readonly IVariationContextAccessor _variationContextAccessor; @@ -46,6 +45,17 @@ public UrlProvider(IUmbracoContextAccessor umbracoContextAccessor, IOptions public UrlMode Mode { get; set; } + /// + /// Gets the URL of a published content. + /// + /// The published content identifier. + /// The URL mode. + /// A culture. + /// The current absolute URL. + /// The URL for the published content. + public string GetUrl(Guid id, UrlMode mode = UrlMode.Default, string? culture = null, Uri? current = null) + => GetUrl(GetDocument(id), mode, culture, current); + #endregion #region GetUrl @@ -68,17 +78,6 @@ public UrlProvider(IUmbracoContextAccessor umbracoContextAccessor, IOptions - /// Gets the URL of a published content. - /// - /// The published content identifier. - /// The URL mode. - /// A culture. - /// The current absolute URL. - /// The URL for the published content. - public string GetUrl(Guid id, UrlMode mode = UrlMode.Default, string? culture = null, Uri? current = null) - => GetUrl(GetDocument(id), mode, culture, current); - /// /// Gets the URL of a published content. /// @@ -106,8 +105,7 @@ public string GetUrl(int id, UrlMode mode = UrlMode.Default, string? culture = n /// /// If the provider is unable to provide a URL, it returns "#". /// - public string GetUrl(IPublishedContent? content, UrlMode mode = UrlMode.Default, string? culture = null, - Uri? current = null) + public string GetUrl(IPublishedContent? content, UrlMode mode = UrlMode.Default, string? culture = null, Uri? current = null) { if (content == null || content.ContentType.ItemType == PublishedItemType.Element) { @@ -126,7 +124,7 @@ public string GetUrl(IPublishedContent? content, UrlMode mode = UrlMode.Default, { if (culture == null) { - culture = _variationContextAccessor?.VariationContext?.Culture ?? ""; + culture = _variationContextAccessor?.VariationContext?.Culture ?? string.Empty; } } @@ -136,8 +134,7 @@ public string GetUrl(IPublishedContent? content, UrlMode mode = UrlMode.Default, current = umbracoContext.CleanedUmbracoUrl; } - - UrlInfo url = _urlProviders.Select(provider => provider.GetUrl(content, mode, culture, current)) + UrlInfo? url = _urlProviders.Select(provider => provider.GetUrl(content, mode, culture, current)) .FirstOrDefault(u => u is not null); return url?.Text ?? "#"; // legacy wants this } @@ -145,7 +142,7 @@ public string GetUrl(IPublishedContent? content, UrlMode mode = UrlMode.Default, public string GetUrlFromRoute(int id, string? route, string? culture) { IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - DefaultUrlProvider provider = _urlProviders.OfType().FirstOrDefault(); + DefaultUrlProvider? provider = _urlProviders.OfType().FirstOrDefault(); var url = provider == null ? route // what else? : provider.GetUrlFromRoute(route, umbracoContext, id, umbracoContext.CleanedUmbracoUrl, Mode, culture) @@ -203,8 +200,7 @@ public IEnumerable GetOtherUrls(int id, Uri current) => _urlProviders.S /// /// /// - public string GetMediaUrl(Guid id, UrlMode mode = UrlMode.Default, string? culture = null, - string propertyAlias = Constants.Conventions.Media.File, Uri? current = null) + public string GetMediaUrl(Guid id, UrlMode mode = UrlMode.Default, string? culture = null, string propertyAlias = Constants.Conventions.Media.File, Uri? current = null) => GetMediaUrl(GetMedia(id), mode, culture, propertyAlias, current); /// @@ -222,10 +218,9 @@ public string GetMediaUrl(Guid id, UrlMode mode = UrlMode.Default, string? cultu /// If the media is multi-lingual, gets the URL for the specified culture or, /// when no culture is specified, the current culture. /// - /// If the provider is unable to provide a URL, it returns . + /// If the provider is unable to provide a URL, it returns . /// - public string GetMediaUrl(IPublishedContent? content, UrlMode mode = UrlMode.Default, string? culture = null, - string propertyAlias = Constants.Conventions.Media.File, Uri? current = null) + public string GetMediaUrl(IPublishedContent? content, UrlMode mode = UrlMode.Default, string? culture = null, string propertyAlias = Constants.Conventions.Media.File, Uri? current = null) { if (propertyAlias == null) { @@ -234,7 +229,7 @@ public string GetMediaUrl(IPublishedContent? content, UrlMode mode = UrlMode.Def if (content == null) { - return ""; + return string.Empty; } if (mode == UrlMode.Default) @@ -249,7 +244,7 @@ public string GetMediaUrl(IPublishedContent? content, UrlMode mode = UrlMode.Def { if (culture == null) { - culture = _variationContextAccessor?.VariationContext?.Culture ?? ""; + culture = _variationContextAccessor?.VariationContext?.Culture ?? string.Empty; } } @@ -259,12 +254,11 @@ public string GetMediaUrl(IPublishedContent? content, UrlMode mode = UrlMode.Def current = umbracoContext.CleanedUmbracoUrl; } - - UrlInfo url = _mediaUrlProviders.Select(provider => + UrlInfo? url = _mediaUrlProviders.Select(provider => provider.GetMediaUrl(content, propertyAlias, mode, culture, current)) .FirstOrDefault(u => u is not null); - return url?.Text ?? ""; + return url?.Text ?? string.Empty; } #endregion diff --git a/src/Umbraco.Core/Routing/UrlProviderCollection.cs b/src/Umbraco.Core/Routing/UrlProviderCollection.cs index 59744c0737f2..0acb75264d10 100644 --- a/src/Umbraco.Core/Routing/UrlProviderCollection.cs +++ b/src/Umbraco.Core/Routing/UrlProviderCollection.cs @@ -4,7 +4,8 @@ namespace Umbraco.Cms.Core.Routing; public class UrlProviderCollection : BuilderCollectionBase { - public UrlProviderCollection(Func> items) : base(items) + public UrlProviderCollection(Func> items) + : base(items) { } } diff --git a/src/Umbraco.Core/Routing/UrlProviderCollectionBuilder.cs b/src/Umbraco.Core/Routing/UrlProviderCollectionBuilder.cs index 8ee1caf343c9..441f687c59d2 100644 --- a/src/Umbraco.Core/Routing/UrlProviderCollectionBuilder.cs +++ b/src/Umbraco.Core/Routing/UrlProviderCollectionBuilder.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Routing; diff --git a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs index 9c4e81c536dc..8e2a577f3a47 100644 --- a/src/Umbraco.Core/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Core/Routing/UrlProviderExtensions.cs @@ -64,9 +64,7 @@ public static async Task> GetContentUrlsAsync( // get all URLs for all cultures // in a HashSet, so de-duplicates too - foreach (UrlInfo cultureUrl in await GetContentUrlsByCultureAsync(content, cultures, publishedRouter, - umbracoContext, contentService, textService, variationContextAccessor, logger, uriUtility, - publishedUrlProvider)) + foreach (UrlInfo cultureUrl in await GetContentUrlsByCultureAsync(content, cultures, publishedRouter, umbracoContext, contentService, textService, variationContextAccessor, logger, uriUtility, publishedUrlProvider)) { urls.Add(cultureUrl); } @@ -155,8 +153,7 @@ private static async Task> GetContentUrlsByCultureAsync( // got a URL, deal with collisions, add URL default: // detect collisions, etc - Attempt hasCollision = await DetectCollisionAsync(logger, content, url, culture, - umbracoContext, publishedRouter, textService, variationContextAccessor, uriUtility); + Attempt hasCollision = await DetectCollisionAsync(logger, content, url, culture, umbracoContext, publishedRouter, textService, variationContextAccessor, uriUtility); if (hasCollision.Success && hasCollision.Result is not null) { result.Add(hasCollision.Result); @@ -173,8 +170,7 @@ private static async Task> GetContentUrlsByCultureAsync( return result; } - private static UrlInfo HandleCouldNotGetUrl(IContent content, string culture, IContentService contentService, - ILocalizedTextService textService) + private static UrlInfo HandleCouldNotGetUrl(IContent content, string culture, IContentService contentService, ILocalizedTextService textService) { // document has a published version yet its URL is "#" => a parent must be // unpublished, walk up the tree until we find it, and report. @@ -182,7 +178,8 @@ private static UrlInfo HandleCouldNotGetUrl(IContent content, string culture, IC do { parent = parent.ParentId > 0 ? contentService.GetParent(parent) : null; - } while (parent != null && parent.Published && + } + while (parent != null && parent.Published && (!parent.ContentType.VariesByCulture() || parent.IsCulturePublished(culture))); if (parent == null) @@ -194,11 +191,12 @@ private static UrlInfo HandleCouldNotGetUrl(IContent content, string culture, IC if (!parent.Published) { // totally not published - return UrlInfo.Message(textService.Localize("content", "parentNotPublished", new[] {parent.Name}), culture); + return UrlInfo.Message(textService.Localize("content", "parentNotPublished", new[] { parent.Name }), culture); } // culture not published - return UrlInfo.Message(textService.Localize("content", "parentCultureNotPublished", new[] {parent.Name}), + return UrlInfo.Message( + textService.Localize("content", "parentCultureNotPublished", new[] { parent.Name }), culture); } @@ -253,7 +251,7 @@ private static UrlInfo HandleCouldNotGetUrl(IContent content, string culture, IC l.Reverse(); var s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent?.Id + ")"; - var urlInfo = UrlInfo.Message(textService.Localize("content", "routeError", new[] {s}), culture); + var urlInfo = UrlInfo.Message(textService.Localize("content", "routeError", new[] { s }), culture); return Attempt.Succeed(urlInfo); } diff --git a/src/Umbraco.Core/Routing/WebPath.cs b/src/Umbraco.Core/Routing/WebPath.cs index e95fd8e54de0..7ecafff8a324 100644 --- a/src/Umbraco.Core/Routing/WebPath.cs +++ b/src/Umbraco.Core/Routing/WebPath.cs @@ -35,7 +35,7 @@ public static string Combine(params string[]? paths) } // always trim end - if (path[path.Length - 1] == PathSeparator) + if (path[^1] == PathSeparator) { count = path.Length - 1; } diff --git a/src/Umbraco.Core/RuntimeLevel.cs b/src/Umbraco.Core/RuntimeLevel.cs index d36c7df1d831..5b726045a93f 100644 --- a/src/Umbraco.Core/RuntimeLevel.cs +++ b/src/Umbraco.Core/RuntimeLevel.cs @@ -35,5 +35,5 @@ public enum RuntimeLevel /// /// The runtime has detected an up-to-date Umbraco install and is running. /// - Run = 100 + Run = 100, } diff --git a/src/Umbraco.Core/RuntimeLevelReason.cs b/src/Umbraco.Core/RuntimeLevelReason.cs index 3a89bc115d40..76f47e8a1728 100644 --- a/src/Umbraco.Core/RuntimeLevelReason.cs +++ b/src/Umbraco.Core/RuntimeLevelReason.cs @@ -73,5 +73,5 @@ public enum RuntimeLevelReason /// /// Umbraco is running. /// - Run + Run, } diff --git a/src/Umbraco.Core/Scoping/RepositoryCacheMode.cs b/src/Umbraco.Core/Scoping/RepositoryCacheMode.cs index de5a6a82aab3..75361726f374 100644 --- a/src/Umbraco.Core/Scoping/RepositoryCacheMode.cs +++ b/src/Umbraco.Core/Scoping/RepositoryCacheMode.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Scoping; +namespace Umbraco.Cms.Core.Scoping; /// /// Specifies the cache mode of repositories. @@ -31,5 +31,5 @@ public enum RepositoryCacheMode /// Bypasses caches entirely. /// Upon scope completion, clears the global L2 cache. /// - None = 3 + None = 3, } diff --git a/src/Umbraco.Core/Sections/MembersSection.cs b/src/Umbraco.Core/Sections/MembersSection.cs index 52952ed088cb..a2e98ac871e1 100644 --- a/src/Umbraco.Core/Sections/MembersSection.cs +++ b/src/Umbraco.Core/Sections/MembersSection.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Sections; +namespace Umbraco.Cms.Core.Sections; /// /// Defines the back office members section diff --git a/src/Umbraco.Core/Sections/PackagesSection.cs b/src/Umbraco.Core/Sections/PackagesSection.cs index 637aac8b21e5..d65acfccec8f 100644 --- a/src/Umbraco.Core/Sections/PackagesSection.cs +++ b/src/Umbraco.Core/Sections/PackagesSection.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Sections; +namespace Umbraco.Cms.Core.Sections; /// /// Defines the back office packages section diff --git a/src/Umbraco.Core/Sections/SectionCollection.cs b/src/Umbraco.Core/Sections/SectionCollection.cs index 22a7544c8401..83169a390dca 100644 --- a/src/Umbraco.Core/Sections/SectionCollection.cs +++ b/src/Umbraco.Core/Sections/SectionCollection.cs @@ -4,7 +4,8 @@ namespace Umbraco.Cms.Core.Sections; public class SectionCollection : BuilderCollectionBase { - public SectionCollection(Func> items) : base(items) + public SectionCollection(Func> items) + : base(items) { } } diff --git a/src/Umbraco.Core/Sections/SectionCollectionBuilder.cs b/src/Umbraco.Core/Sections/SectionCollectionBuilder.cs index 73c72a307c21..7644b1cc8ced 100644 --- a/src/Umbraco.Core/Sections/SectionCollectionBuilder.cs +++ b/src/Umbraco.Core/Sections/SectionCollectionBuilder.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Manifest; diff --git a/src/Umbraco.Core/Sections/SettingsSection.cs b/src/Umbraco.Core/Sections/SettingsSection.cs index 8b49e796118a..3fe825c70df3 100644 --- a/src/Umbraco.Core/Sections/SettingsSection.cs +++ b/src/Umbraco.Core/Sections/SettingsSection.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Sections; +namespace Umbraco.Cms.Core.Sections; /// /// Defines the back office settings section diff --git a/src/Umbraco.Core/Sections/TranslationSection.cs b/src/Umbraco.Core/Sections/TranslationSection.cs index a403eb02392a..d11391c8111f 100644 --- a/src/Umbraco.Core/Sections/TranslationSection.cs +++ b/src/Umbraco.Core/Sections/TranslationSection.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Sections; +namespace Umbraco.Cms.Core.Sections; /// /// Defines the back office translation section diff --git a/src/Umbraco.Core/Sections/UsersSection.cs b/src/Umbraco.Core/Sections/UsersSection.cs index 257fd91c5110..cea5047c8104 100644 --- a/src/Umbraco.Core/Sections/UsersSection.cs +++ b/src/Umbraco.Core/Sections/UsersSection.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Sections; +namespace Umbraco.Cms.Core.Sections; /// /// Defines the back office users section diff --git a/src/Umbraco.Core/Security/PasswordGenerator.cs b/src/Umbraco.Core/Security/PasswordGenerator.cs index 3a72b66b1bb3..0a3e8925adaa 100644 --- a/src/Umbraco.Core/Security/PasswordGenerator.cs +++ b/src/Umbraco.Core/Security/PasswordGenerator.cs @@ -1,4 +1,4 @@ -using System.Security.Cryptography; +using System.Security.Cryptography; using Umbraco.Cms.Core.Configuration; using Umbraco.Extensions; @@ -64,7 +64,7 @@ public string GeneratePassword() private static class PasswordStore { private static readonly char[] Punctuations = "!@#$%^&*()_-+=[{]};:>|./?".ToCharArray(); - private static readonly char[] StartingChars = {'<', '&'}; + private static readonly char[] StartingChars = { '<', '&' }; /// Generates a random password of the specified length. /// A random password of the specified length. @@ -89,12 +89,12 @@ public static string GeneratePassword(int length, int numberOfNonAlphanumericCha if (numberOfNonAlphanumericCharacters > length || numberOfNonAlphanumericCharacters < 0) { - throw new ArgumentException("min required non alphanumeric characters incorrect", + throw new ArgumentException( + "min required non alphanumeric characters incorrect", nameof(numberOfNonAlphanumericCharacters)); } string s; - int matchIndex; do { var data = new byte[length]; @@ -132,24 +132,26 @@ public static string GeneratePassword(int length, int numberOfNonAlphanumericCha do { index2 = random.Next(0, length); - } while (!char.IsLetterOrDigit(chArray[index2])); + } + while (!char.IsLetterOrDigit(chArray[index2])); chArray[index2] = Punctuations[random.Next(0, Punctuations.Length)]; } } s = new string(chArray); - } while (IsDangerousString(s, out matchIndex)); + } + while (IsDangerousString(s, out int matchIndex)); return s; } private static bool IsDangerousString(string s, out int matchIndex) { - //bool inComment = false; + // bool inComment = false; matchIndex = 0; - for (var i = 0;;) + for (var i = 0; ;) { // Look for the start of one of our patterns var n = s.IndexOfAny(StartingChars, i); diff --git a/src/Umbraco.Core/Security/PublicAccessStatus.cs b/src/Umbraco.Core/Security/PublicAccessStatus.cs index fad5ddb196e2..9026b11fd5fc 100644 --- a/src/Umbraco.Core/Security/PublicAccessStatus.cs +++ b/src/Umbraco.Core/Security/PublicAccessStatus.cs @@ -6,5 +6,5 @@ public enum PublicAccessStatus AccessDenied, NotApproved, LockedOut, - AccessAccepted + AccessAccepted, } diff --git a/src/Umbraco.Core/Security/UpdateMemberProfileResult.cs b/src/Umbraco.Core/Security/UpdateMemberProfileResult.cs index 2c4407fcc106..0809f6c5018c 100644 --- a/src/Umbraco.Core/Security/UpdateMemberProfileResult.cs +++ b/src/Umbraco.Core/Security/UpdateMemberProfileResult.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Security; +namespace Umbraco.Cms.Core.Security; public class UpdateMemberProfileResult { @@ -11,8 +11,8 @@ private UpdateMemberProfileResult() public string? ErrorMessage { get; private set; } public static UpdateMemberProfileResult Success() => - new UpdateMemberProfileResult {Status = UpdateMemberProfileStatus.Success}; + new UpdateMemberProfileResult { Status = UpdateMemberProfileStatus.Success }; public static UpdateMemberProfileResult Error(string message) => - new UpdateMemberProfileResult {Status = UpdateMemberProfileStatus.Error, ErrorMessage = message}; + new UpdateMemberProfileResult { Status = UpdateMemberProfileStatus.Error, ErrorMessage = message }; } diff --git a/src/Umbraco.Core/Security/UpdateMemberProfileStatus.cs b/src/Umbraco.Core/Security/UpdateMemberProfileStatus.cs index 81103386a86a..74fb52e6971c 100644 --- a/src/Umbraco.Core/Security/UpdateMemberProfileStatus.cs +++ b/src/Umbraco.Core/Security/UpdateMemberProfileStatus.cs @@ -1,7 +1,7 @@ -namespace Umbraco.Cms.Core.Security; +namespace Umbraco.Cms.Core.Security; public enum UpdateMemberProfileStatus { Success, - Error + Error, } diff --git a/src/Umbraco.Core/Services/Changes/TreeChange.cs b/src/Umbraco.Core/Services/Changes/TreeChange.cs index 97aaf4c4ea26..bb722dce24e0 100644 --- a/src/Umbraco.Core/Services/Changes/TreeChange.cs +++ b/src/Umbraco.Core/Services/Changes/TreeChange.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Services.Changes; +namespace Umbraco.Cms.Core.Services.Changes; public class TreeChange { @@ -9,6 +9,7 @@ public TreeChange(TItem changedItem, TreeChangeTypes changeTypes) } public TItem Item { get; } + public TreeChangeTypes ChangeTypes { get; } public EventArgs ToEventArgs() => new EventArgs(this); @@ -18,7 +19,7 @@ public class EventArgs : System.EventArgs public EventArgs(IEnumerable> changes) => Changes = changes.ToArray(); public EventArgs(TreeChange change) - : this(new[] {change}) + : this(new[] { change }) { } diff --git a/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs b/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs index 1e15da177716..1dc972eb7a35 100644 --- a/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs +++ b/src/Umbraco.Core/Services/Changes/TreeChangeExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Services.Changes; diff --git a/src/Umbraco.Core/Services/Changes/TreeChangeTypes.cs b/src/Umbraco.Core/Services/Changes/TreeChangeTypes.cs index 88dcb85f54b6..85db740a56ee 100644 --- a/src/Umbraco.Core/Services/Changes/TreeChangeTypes.cs +++ b/src/Umbraco.Core/Services/Changes/TreeChangeTypes.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Services.Changes; +namespace Umbraco.Cms.Core.Services.Changes; [Flags] public enum TreeChangeTypes : byte @@ -18,5 +18,5 @@ public enum TreeChangeTypes : byte // an item node has been removed // never to return - Remove = 8 + Remove = 8, } diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 7d438fdf87c8..a5fe4ced9b0a 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -524,7 +524,7 @@ public IEnumerable GetPagedOfTypes( /// The level to retrieve Media from /// An Enumerable list of objects /// Contrary to most methods, this method filters out trashed media items. - public IEnumerable? GetByLevel(int level) + public IEnumerable GetByLevel(int level) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -724,7 +724,7 @@ private IEnumerable GetPagedLocked(IQuery? query, long pageIndex /// Gets a collection of objects, which reside at the first level / root /// /// An Enumerable list of objects - public IEnumerable? GetRootMedia() + public IEnumerable GetRootMedia() { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -811,7 +811,6 @@ public bool HasChildren(int id) if (media.Name != null && media.Name.Length > 255) { throw new InvalidOperationException("Name cannot be more than 255 characters in length."); - throw new InvalidOperationException("Name cannot be more than 255 characters in length."); } scope.WriteLock(Constants.Locks.MediaTree); @@ -1410,16 +1409,14 @@ public void DeleteMediaOfTypes(IEnumerable mediaTypeIds, int userId = Const { // if current media has children, move them to trash IMedia m = media; - IQuery? childQuery = Query().Where(x => x.Path.StartsWith(m.Path)); - IEnumerable? children = _mediaRepository.Get(childQuery); - if (children is not null) + IQuery childQuery = Query().Where(x => x.Path.StartsWith(m.Path)); + IEnumerable children = _mediaRepository.Get(childQuery); + + foreach (IMedia child in children.Where(x => mediaTypeIdsA.Contains(x.ContentTypeId) == false)) { - foreach (IMedia child in children.Where(x => mediaTypeIdsA.Contains(x.ContentTypeId) == false)) - { - // see MoveToRecycleBin - PerformMoveLocked(child, Constants.System.RecycleBinMedia, null, userId, moves, true); - changes.Add(new TreeChange(media, TreeChangeTypes.RefreshBranch)); - } + // see MoveToRecycleBin + PerformMoveLocked(child, Constants.System.RecycleBinMedia, null, userId, moves, true); + changes.Add(new TreeChange(media, TreeChangeTypes.RefreshBranch)); } // delete media diff --git a/src/Umbraco.Core/Services/MediaServiceExtensions.cs b/src/Umbraco.Core/Services/MediaServiceExtensions.cs index 3687ed6a2a09..8d45367e6181 100644 --- a/src/Umbraco.Core/Services/MediaServiceExtensions.cs +++ b/src/Umbraco.Core/Services/MediaServiceExtensions.cs @@ -22,8 +22,7 @@ public static IEnumerable GetByIds(this IMediaService mediaService, IEnu var guids = new List(); foreach (Udi udi in ids) { - var guidUdi = udi as GuidUdi; - if (guidUdi is null) + if (udi is not GuidUdi guidUdi) { throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by media"); @@ -35,17 +34,15 @@ public static IEnumerable GetByIds(this IMediaService mediaService, IEnu return mediaService.GetByIds(guids.Select(x => x.Guid)); } - public static IMedia CreateMedia(this IMediaService mediaService, string name, Udi parentId, string mediaTypeAlias, - int userId = 0) + public static IMedia CreateMedia(this IMediaService mediaService, string name, Udi parentId, string mediaTypeAlias, int userId = 0) { - var guidUdi = parentId as GuidUdi; - if (guidUdi is null) + if (parentId is not GuidUdi guidUdi) { throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by media"); } - IMedia parent = mediaService.GetById(guidUdi.Guid); + IMedia? parent = mediaService.GetById(guidUdi.Guid); return mediaService.CreateMedia(name, parent, mediaTypeAlias, userId); } } diff --git a/src/Umbraco.Core/Services/MediaTypeService.cs b/src/Umbraco.Core/Services/MediaTypeService.cs index 4375246b165f..eff6ba0fbacc 100644 --- a/src/Umbraco.Core/Services/MediaTypeService.cs +++ b/src/Umbraco.Core/Services/MediaTypeService.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; @@ -10,23 +10,27 @@ namespace Umbraco.Cms.Core.Services; public class MediaTypeService : ContentTypeServiceBase, IMediaTypeService { - public MediaTypeService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, IMediaService mediaService, - IMediaTypeRepository mediaTypeRepository, IAuditRepository auditRepository, + public MediaTypeService( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IMediaService mediaService, + IMediaTypeRepository mediaTypeRepository, + IAuditRepository auditRepository, IMediaTypeContainerRepository entityContainerRepository, - IEntityRepository entityRepository, IEventAggregator eventAggregator) - : base(provider, loggerFactory, eventMessagesFactory, mediaTypeRepository, auditRepository, - entityContainerRepository, entityRepository, eventAggregator) => - MediaService = mediaService; + IEntityRepository entityRepository, + IEventAggregator eventAggregator) + : base(provider, loggerFactory, eventMessagesFactory, mediaTypeRepository, auditRepository, entityContainerRepository, entityRepository, eventAggregator) => MediaService = mediaService; // beware! order is important to avoid deadlocks - protected override int[] ReadLockIds { get; } = {Constants.Locks.MediaTypes}; - protected override int[] WriteLockIds { get; } = {Constants.Locks.MediaTree, Constants.Locks.MediaTypes}; + protected override int[] ReadLockIds { get; } = { Constants.Locks.MediaTypes }; - private IMediaService MediaService { get; } + protected override int[] WriteLockIds { get; } = { Constants.Locks.MediaTree, Constants.Locks.MediaTypes }; protected override Guid ContainedObjectType => Constants.ObjectTypes.MediaType; + private IMediaService MediaService { get; } + protected override void DeleteItemsOfTypes(IEnumerable typeIds) { foreach (var typeId in typeIds) @@ -37,28 +41,36 @@ protected override void DeleteItemsOfTypes(IEnumerable typeIds) #region Notifications - protected override SavingNotification GetSavingNotification(IMediaType item, + protected override SavingNotification GetSavingNotification( + IMediaType item, EventMessages eventMessages) => new MediaTypeSavingNotification(item, eventMessages); - protected override SavingNotification GetSavingNotification(IEnumerable items, + protected override SavingNotification GetSavingNotification( + IEnumerable items, EventMessages eventMessages) => new MediaTypeSavingNotification(items, eventMessages); - protected override SavedNotification GetSavedNotification(IMediaType item, + protected override SavedNotification GetSavedNotification( + IMediaType item, EventMessages eventMessages) => new MediaTypeSavedNotification(item, eventMessages); - protected override SavedNotification GetSavedNotification(IEnumerable items, + protected override SavedNotification GetSavedNotification( + IEnumerable items, EventMessages eventMessages) => new MediaTypeSavedNotification(items, eventMessages); - protected override DeletingNotification GetDeletingNotification(IMediaType item, + protected override DeletingNotification GetDeletingNotification( + IMediaType item, EventMessages eventMessages) => new MediaTypeDeletingNotification(item, eventMessages); - protected override DeletingNotification GetDeletingNotification(IEnumerable items, + protected override DeletingNotification GetDeletingNotification( + IEnumerable items, EventMessages eventMessages) => new MediaTypeDeletingNotification(items, eventMessages); - protected override DeletedNotification GetDeletedNotification(IEnumerable items, + protected override DeletedNotification GetDeletedNotification( + IEnumerable items, EventMessages eventMessages) => new MediaTypeDeletedNotification(items, eventMessages); - protected override MovingNotification GetMovingNotification(MoveEventInfo moveInfo, + protected override MovingNotification GetMovingNotification( + MoveEventInfo moveInfo, EventMessages eventMessages) => new MediaTypeMovingNotification(moveInfo, eventMessages); protected override MovedNotification GetMovedNotification( diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index a181fa99d1fe..9cc16f617a74 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -25,10 +25,15 @@ public class MemberService : RepositoryService, IMemberService #region Constructor - public MemberService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService, - IMemberRepository memberRepository, IMemberTypeRepository memberTypeRepository, - IMemberGroupRepository memberGroupRepository, IAuditRepository auditRepository) + public MemberService( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IMemberGroupService memberGroupService, + IMemberRepository memberRepository, + IMemberTypeRepository memberTypeRepository, + IMemberGroupRepository memberGroupRepository, + IAuditRepository auditRepository) : base(provider, loggerFactory, eventMessagesFactory) { _memberRepository = memberRepository; @@ -40,13 +45,6 @@ public MemberService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, #endregion - #region Private Methods - - private void Audit(AuditType type, int userId, int objectId, string? message = null) => - _auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.Member.GetName(), message)); - - #endregion - #region Count /// @@ -88,6 +86,13 @@ public int GetCount(MemberCountType countType) } } + #endregion + + #region Private Methods + + private void Audit(AuditType type, int userId, int objectId, string? message = null) => + _auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.Member.GetName(), message)); + /// /// Gets the count of Members by an optional MemberType alias /// @@ -178,8 +183,7 @@ public IMember CreateMember(string username, string email, string name, IMemberT /// /// /// - IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, - string memberTypeAlias) + IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias) => CreateMemberWithIdentity(username, email, username, passwordValue, memberTypeAlias); /// @@ -197,22 +201,20 @@ IMember IMembershipMemberService.CreateWithIdentity(string username, st /// /// /// - IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, - string memberTypeAlias, bool isApproved) + IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias, bool isApproved) => CreateMemberWithIdentity(username, email, username, passwordValue, memberTypeAlias, isApproved); public IMember CreateMemberWithIdentity(string username, string email, string memberTypeAlias) - => CreateMemberWithIdentity(username, email, username, "", memberTypeAlias); + => CreateMemberWithIdentity(username, email, username, string.Empty, memberTypeAlias); public IMember CreateMemberWithIdentity(string username, string email, string memberTypeAlias, bool isApproved) - => CreateMemberWithIdentity(username, email, username, "", memberTypeAlias, isApproved); + => CreateMemberWithIdentity(username, email, username, string.Empty, memberTypeAlias, isApproved); public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias) - => CreateMemberWithIdentity(username, email, name, "", memberTypeAlias); + => CreateMemberWithIdentity(username, email, name, string.Empty, memberTypeAlias); - public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias, - bool isApproved) - => CreateMemberWithIdentity(username, email, name, "", memberTypeAlias, isApproved); + public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias, bool isApproved) + => CreateMemberWithIdentity(username, email, name, string.Empty, memberTypeAlias, isApproved); /// /// Creates and persists a Member @@ -226,11 +228,11 @@ public IMember CreateMemberWithIdentity(string username, string email, string na /// Name of the Member to create /// Alias of the MemberType the Member should be based on /// Optional IsApproved of the Member to create + /// Password of the Member to create /// /// /// - public IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, - string memberTypeAlias, bool isApproved = true) + public IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, string memberTypeAlias, bool isApproved = true) { using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { @@ -240,7 +242,8 @@ public IMember CreateMemberWithIdentity(string username, string email, string na IMemberType memberType = GetMemberType(scope, memberTypeAlias); // + locks // + locks if (memberType == null) { - throw new ArgumentException("No member type with that alias.", + throw new ArgumentException( + "No member type with that alias.", nameof(memberTypeAlias)); // causes rollback // causes rollback } @@ -255,7 +258,7 @@ public IMember CreateMemberWithIdentity(string username, string email, string na } public IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType) - => CreateMemberWithIdentity(username, email, username, "", memberType); + => CreateMemberWithIdentity(username, email, username, string.Empty, memberType); /// /// Creates and persists a Member @@ -267,14 +270,15 @@ public IMember CreateMemberWithIdentity(string username, string email, IMemberTy /// Username of the Member to create /// Email of the Member to create /// MemberType the Member should be based on + /// /// /// /// public IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType, bool isApproved) - => CreateMemberWithIdentity(username, email, username, "", memberType, isApproved); + => CreateMemberWithIdentity(username, email, username, string.Empty, memberType, isApproved); public IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType) - => CreateMemberWithIdentity(username, email, name, "", memberType); + => CreateMemberWithIdentity(username, email, name, string.Empty, memberType); /// /// Creates and persists a Member @@ -287,12 +291,32 @@ public IMember CreateMemberWithIdentity(string username, string email, string na /// Email of the Member to create /// Name of the Member to create /// MemberType the Member should be based on + /// + /// + /// + /// + public IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType, bool isApproved) + => CreateMemberWithIdentity(username, email, name, string.Empty, memberType, isApproved); + + #endregion + + #region Get, Has, Is, Exists... + + /// + /// Gets a Member by its integer id + /// + /// Id /// /// /// - public IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType, - bool isApproved) - => CreateMemberWithIdentity(username, email, name, "", memberType, isApproved); + public IMember? GetById(int id) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.MemberTree); + return _memberRepository.Get(id); + } + } /// /// Creates and persists a Member @@ -309,11 +333,11 @@ public IMember CreateMemberWithIdentity(string username, string email, string na /// stored in the database /// /// MemberType the Member should be based on + /// /// /// /// - private IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, - IMemberType memberType, bool isApproved = true) + private IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, IMemberType memberType, bool isApproved = true) { if (memberType == null) { @@ -344,26 +368,6 @@ private IMember CreateMemberWithIdentity(string username, string email, string n } } - #endregion - - #region Get, Has, Is, Exists... - - /// - /// Gets a Member by its integer id - /// - /// Id - /// - /// - /// - public IMember? GetById(int id) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.MemberTree); - return _memberRepository.Get(id); - } - } - /// /// Gets a Member by the unique key /// @@ -399,30 +403,47 @@ public IEnumerable GetAll(long pageIndex, int pageSize, out long totalR using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { scope.ReadLock(Constants.Locks.MemberTree); - return _memberRepository.GetPage(null, pageIndex, pageSize, out totalRecords, null, - Ordering.By("LoginName")); + return _memberRepository.GetPage(null, pageIndex, pageSize, out totalRecords, null, Ordering.By("LoginName")); } } - public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, string? memberTypeAlias = null, string filter = "") => + public IEnumerable GetAll( + long pageIndex, + int pageSize, + out long totalRecords, + string orderBy, + Direction orderDirection, + string? memberTypeAlias = null, + string filter = "") => GetAll(pageIndex, pageSize, out totalRecords, orderBy, orderDirection, true, memberTypeAlias, filter); - public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, bool orderBySystemField, string? memberTypeAlias, string filter) + public IEnumerable GetAll( + long pageIndex, + int pageSize, + out long totalRecords, + string orderBy, + Direction orderDirection, + bool orderBySystemField, + string? memberTypeAlias, + string filter) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { scope.ReadLock(Constants.Locks.MemberTree); - IQuery query1 = memberTypeAlias == null + IQuery? query1 = memberTypeAlias == null ? null : Query()?.Where(x => x.ContentTypeAlias == memberTypeAlias); - IQuery query2 = filter == null + IQuery? query2 = filter == null ? null : Query()?.Where(x => (x.Name != null && x.Name.Contains(filter)) || x.Username.Contains(filter) || x.Email.Contains(filter)); - return _memberRepository.GetPage(query1, pageIndex, pageSize, out totalRecords, query2, + return _memberRepository.GetPage( + query1, + pageIndex, + pageSize, + out totalRecords, + query2, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); } } @@ -491,7 +512,7 @@ public IEnumerable GetAll(long pageIndex, int pageSize, out long totalR /// /// /// - public IEnumerable? GetMembersByMemberType(string memberTypeAlias) + public IEnumerable GetMembersByMemberType(string memberTypeAlias) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -508,7 +529,7 @@ public IEnumerable GetAll(long pageIndex, int pageSize, out long totalR /// /// /// - public IEnumerable? GetMembersByMemberType(int memberTypeId) + public IEnumerable GetMembersByMemberType(int memberTypeId) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -565,8 +586,12 @@ public IEnumerable GetAllMembers(params int[] ids) /// /// /// - public IEnumerable FindMembersByDisplayName(string displayNameToMatch, long pageIndex, int pageSize, - out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + public IEnumerable FindMembersByDisplayName( + string displayNameToMatch, + long pageIndex, + int pageSize, + out long totalRecords, + StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -613,8 +638,12 @@ public IEnumerable FindMembersByDisplayName(string displayNameToMatch, /// /// /// - public IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, - out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + public IEnumerable FindByEmail( + string emailStringToMatch, + long pageIndex, + int pageSize, + out long totalRecords, + StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -660,7 +689,11 @@ public IEnumerable FindByEmail(string emailStringToMatch, long pageInde /// /// /// - public IEnumerable FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, + public IEnumerable FindByUsername( + string login, + long pageIndex, + int pageSize, + out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) @@ -689,8 +722,7 @@ public IEnumerable FindByUsername(string login, long pageIndex, int pag throw new ArgumentOutOfRangeException(nameof(matchType)); } - return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, null, - Ordering.By("LoginName")); + return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, null, Ordering.By("LoginName")); } } @@ -706,8 +738,7 @@ public IEnumerable FindByUsername(string login, long pageIndex, int pag /// /// /// - public IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, string value, - StringPropertyMatchType matchType = StringPropertyMatchType.Exact) + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -760,8 +791,7 @@ public IEnumerable FindByUsername(string login, long pageIndex, int pag /// /// /// - public IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, int value, - ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -809,7 +839,7 @@ public IEnumerable FindByUsername(string login, long pageIndex, int pag /// /// /// - public IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, bool value) + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, bool value) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -833,8 +863,7 @@ public IEnumerable FindByUsername(string login, long pageIndex, int pag /// /// /// - public IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, - ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) + public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -971,7 +1000,7 @@ public void Save(IEnumerable members) foreach (IMember member in membersA) { - //trimming username and email to make sure we have no trailing space + // trimming username and email to make sure we have no trailing space member.Username = member.Username.Trim(); member.Email = member.Email.Trim(); @@ -1016,16 +1045,6 @@ public void Delete(IMember member) } } - private void DeleteLocked(ICoreScope scope, IMember member, EventMessages evtMsgs, - IDictionary? notificationState = null) - { - // a member has no descendants - _memberRepository.Delete(member); - scope.Notifications.Publish(new MemberDeletedNotification(member, evtMsgs).WithState(notificationState)); - - // media files deleted by QueuingEventDispatcher - } - #endregion #region Roles @@ -1040,6 +1059,15 @@ public void AddRole(string roleName) } } + private void DeleteLocked(ICoreScope scope, IMember member, EventMessages evtMsgs, IDictionary? notificationState = null) + { + // a member has no descendants + _memberRepository.Delete(member); + scope.Notifications.Publish(new MemberDeletedNotification(member, evtMsgs).WithState(notificationState)); + + // media files deleted by QueuingEventDispatcher + } + /// /// Returns a list of all member roles /// @@ -1058,7 +1086,7 @@ public IEnumerable GetAllRoles() /// /// /// A list of member roles - public IEnumerable? GetAllRoles(int memberId) + public IEnumerable GetAllRoles(int memberId) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -1116,8 +1144,7 @@ public IEnumerable GetMembersInRole(string roleName) } } - public IEnumerable FindMembersInRole(string roleName, string usernameToMatch, - StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + public IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -1158,7 +1185,7 @@ public bool DeleteRole(string roleName, bool throwIfBeingUsed) } } - public void AssignRole(string username, string roleName) => AssignRoles(new[] {username}, new[] {roleName}); + public void AssignRole(string username, string roleName) => AssignRoles(new[] { username }, new[] { roleName }); public void AssignRoles(string[] usernames, string[] roleNames) { @@ -1172,7 +1199,7 @@ public void AssignRoles(string[] usernames, string[] roleNames) } } - public void DissociateRole(string username, string roleName) => DissociateRoles(new[] {username}, new[] {roleName}); + public void DissociateRole(string username, string roleName) => DissociateRoles(new[] { username }, new[] { roleName }); public void DissociateRoles(string[] usernames, string[] roleNames) { @@ -1186,7 +1213,7 @@ public void DissociateRoles(string[] usernames, string[] roleNames) } } - public void AssignRole(int memberId, string roleName) => AssignRoles(new[] {memberId}, new[] {roleName}); + public void AssignRole(int memberId, string roleName) => AssignRoles(new[] { memberId }, new[] { roleName }); public void AssignRoles(int[] memberIds, string[] roleNames) { @@ -1199,7 +1226,7 @@ public void AssignRoles(int[] memberIds, string[] roleNames) } } - public void DissociateRole(int memberId, string roleName) => DissociateRoles(new[] {memberId}, new[] {roleName}); + public void DissociateRole(int memberId, string roleName) => DissociateRoles(new[] { memberId }, new[] { roleName }); public void DissociateRoles(int[] memberIds, string[] roleNames) { @@ -1269,7 +1296,7 @@ public void ReplaceRoles(int[] memberIds, string[] roleNames) ContentTypeAlias = member.ContentTypeAlias, CreateDate = member.CreateDate, UpdateDate = member.UpdateDate, - Properties = new List(GetPropertyExportItems(member)) + Properties = new List(GetPropertyExportItems(member)), }; scope.Notifications.Publish(new ExportedMemberNotification(member, model)); @@ -1278,32 +1305,6 @@ public void ReplaceRoles(int[] memberIds, string[] roleNames) } } - private static IEnumerable GetPropertyExportItems(IMember member) - { - if (member == null) - { - throw new ArgumentNullException(nameof(member)); - } - - var exportProperties = new List(); - - foreach (IProperty property in member.Properties) - { - var propertyExportModel = new MemberExportProperty - { - Id = property.Id, - Alias = property.Alias, - Name = property.PropertyType.Name, - Value = property.GetValue(), // TODO: ignoring variants - CreateDate = property.CreateDate, - UpdateDate = property.UpdateDate - }; - exportProperties.Add(propertyExportModel); - } - - return exportProperties; - } - #endregion #region Content Types @@ -1322,7 +1323,7 @@ public void DeleteMembersOfType(int memberTypeId) scope.WriteLock(Constants.Locks.MemberTree); // TODO: What about content that has the contenttype as part of its composition? - IQuery? query = Query().Where(x => x.ContentTypeId == memberTypeId); + IQuery query = Query().Where(x => x.ContentTypeId == memberTypeId); IMember[]? members = _memberRepository.Get(query)?.ToArray(); @@ -1348,6 +1349,32 @@ public void DeleteMembersOfType(int memberTypeId) } } + private static IEnumerable GetPropertyExportItems(IMember member) + { + if (member == null) + { + throw new ArgumentNullException(nameof(member)); + } + + var exportProperties = new List(); + + foreach (IProperty property in member.Properties) + { + var propertyExportModel = new MemberExportProperty + { + Id = property.Id, + Alias = property.Alias, + Name = property.PropertyType.Name, + Value = property.GetValue(), // TODO: ignoring variants + CreateDate = property.CreateDate, + UpdateDate = property.UpdateDate, + }; + exportProperties.Add(propertyExportModel); + } + + return exportProperties; + } + private IMemberType GetMemberType(ICoreScope scope, string memberTypeAlias) { if (memberTypeAlias == null) @@ -1357,7 +1384,8 @@ private IMemberType GetMemberType(ICoreScope scope, string memberTypeAlias) if (string.IsNullOrWhiteSpace(memberTypeAlias)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(memberTypeAlias)); } @@ -1383,7 +1411,8 @@ private IMemberType GetMemberType(string memberTypeAlias) if (string.IsNullOrWhiteSpace(memberTypeAlias)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(memberTypeAlias)); } diff --git a/src/Umbraco.Core/Services/MemberTypeService.cs b/src/Umbraco.Core/Services/MemberTypeService.cs index 44e3ca6df881..67b7f0811181 100644 --- a/src/Umbraco.Core/Services/MemberTypeService.cs +++ b/src/Umbraco.Core/Services/MemberTypeService.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; @@ -16,36 +16,61 @@ public class MemberTypeService : ContentTypeServiceBase(), entityRepository, + public MemberTypeService( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IMemberService memberService, + IMemberTypeRepository memberTypeRepository, + IAuditRepository auditRepository, + IEntityRepository entityRepository, + IEventAggregator eventAggregator) + : this( + provider, + loggerFactory, + eventMessagesFactory, + memberService, + memberTypeRepository, + auditRepository, + StaticServiceProvider.Instance.GetRequiredService(), + entityRepository, eventAggregator) { } - public MemberTypeService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, IMemberService memberService, - IMemberTypeRepository memberTypeRepository, IAuditRepository auditRepository, - IMemberTypeContainerRepository entityContainerRepository, IEntityRepository entityRepository, + public MemberTypeService( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IMemberService memberService, + IMemberTypeRepository memberTypeRepository, + IAuditRepository auditRepository, + IMemberTypeContainerRepository entityContainerRepository, + IEntityRepository entityRepository, IEventAggregator eventAggregator) - : base(provider, loggerFactory, eventMessagesFactory, memberTypeRepository, auditRepository, - entityContainerRepository, entityRepository, eventAggregator) + : base( + provider, + loggerFactory, + eventMessagesFactory, + memberTypeRepository, + auditRepository, + entityContainerRepository, + entityRepository, + eventAggregator) { MemberService = memberService; _memberTypeRepository = memberTypeRepository; } // beware! order is important to avoid deadlocks - protected override int[] ReadLockIds { get; } = {Constants.Locks.MemberTypes}; - protected override int[] WriteLockIds { get; } = {Constants.Locks.MemberTree, Constants.Locks.MemberTypes}; + protected override int[] ReadLockIds { get; } = { Constants.Locks.MemberTypes }; - private IMemberService MemberService { get; } + protected override int[] WriteLockIds { get; } = { Constants.Locks.MemberTree, Constants.Locks.MemberTypes }; protected override Guid ContainedObjectType => Constants.ObjectTypes.MemberType; + private IMemberService MemberService { get; } + public string GetDefault() { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) @@ -80,28 +105,36 @@ protected override void DeleteItemsOfTypes(IEnumerable typeIds) #region Notifications - protected override SavingNotification GetSavingNotification(IMemberType item, + protected override SavingNotification GetSavingNotification( + IMemberType item, EventMessages eventMessages) => new MemberTypeSavingNotification(item, eventMessages); - protected override SavingNotification GetSavingNotification(IEnumerable items, + protected override SavingNotification GetSavingNotification( + IEnumerable items, EventMessages eventMessages) => new MemberTypeSavingNotification(items, eventMessages); - protected override SavedNotification GetSavedNotification(IMemberType item, + protected override SavedNotification GetSavedNotification( + IMemberType item, EventMessages eventMessages) => new MemberTypeSavedNotification(item, eventMessages); - protected override SavedNotification GetSavedNotification(IEnumerable items, + protected override SavedNotification GetSavedNotification( + IEnumerable items, EventMessages eventMessages) => new MemberTypeSavedNotification(items, eventMessages); - protected override DeletingNotification GetDeletingNotification(IMemberType item, + protected override DeletingNotification GetDeletingNotification( + IMemberType item, EventMessages eventMessages) => new MemberTypeDeletingNotification(item, eventMessages); - protected override DeletingNotification GetDeletingNotification(IEnumerable items, + protected override DeletingNotification GetDeletingNotification( + IEnumerable items, EventMessages eventMessages) => new MemberTypeDeletingNotification(items, eventMessages); - protected override DeletedNotification GetDeletedNotification(IEnumerable items, + protected override DeletedNotification GetDeletedNotification( + IEnumerable items, EventMessages eventMessages) => new MemberTypeDeletedNotification(items, eventMessages); - protected override MovingNotification GetMovingNotification(MoveEventInfo moveInfo, + protected override MovingNotification GetMovingNotification( + MoveEventInfo moveInfo, EventMessages eventMessages) => new MemberTypeMovingNotification(moveInfo, eventMessages); protected override MovedNotification GetMovedNotification( diff --git a/src/Umbraco.Core/Services/MetricsConsentService.cs b/src/Umbraco.Core/Services/MetricsConsentService.cs index 5a0de6508677..6f350ce9375e 100644 --- a/src/Umbraco.Core/Services/MetricsConsentService.cs +++ b/src/Umbraco.Core/Services/MetricsConsentService.cs @@ -50,9 +50,8 @@ public TelemetryLevel GetConsentLevel() public void SetConsentLevel(TelemetryLevel telemetryLevel) { - IUser currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; - _logger.LogInformation("Telemetry level set to {telemetryLevel} by {username}", telemetryLevel, - currentUser?.Username); + IUser? currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; + _logger.LogInformation("Telemetry level set to {telemetryLevel} by {username}", telemetryLevel, currentUser?.Username); _keyValueService.SetValue(Key, telemetryLevel.ToString()); } } diff --git a/src/Umbraco.Core/Services/MoveOperationStatusType.cs b/src/Umbraco.Core/Services/MoveOperationStatusType.cs index 1cfa301ee750..26e70eb9e0f9 100644 --- a/src/Umbraco.Core/Services/MoveOperationStatusType.cs +++ b/src/Umbraco.Core/Services/MoveOperationStatusType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Services; +namespace Umbraco.Cms.Core.Services; /// /// A status type of the result of moving an item @@ -26,5 +26,5 @@ public enum MoveOperationStatusType : byte /// /// Trying to move an item to an invalid path (i.e. a child of itself) /// - FailedNotAllowedByPath = 15 + FailedNotAllowedByPath = 15, } diff --git a/src/Umbraco.Core/Services/NodeCountService.cs b/src/Umbraco.Core/Services/NodeCountService.cs index 9832bf47aeb4..cf7417058e0e 100644 --- a/src/Umbraco.Core/Services/NodeCountService.cs +++ b/src/Umbraco.Core/Services/NodeCountService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; diff --git a/src/Umbraco.Core/Services/NotificationService.cs b/src/Umbraco.Core/Services/NotificationService.cs index 6c050832a55f..01127cb8d508 100644 --- a/src/Umbraco.Core/Services/NotificationService.cs +++ b/src/Umbraco.Core/Services/NotificationService.cs @@ -18,6 +18,10 @@ namespace Umbraco.Cms.Core.Services; public class NotificationService : INotificationService { + // manage notifications + // ideally, would need to use IBackgroundTasks - but they are not part of Core! + private static readonly object Locker = new(); + private readonly IContentService _contentService; private readonly ContentSettings _contentSettings; private readonly IEmailSender _emailSender; @@ -29,10 +33,17 @@ public class NotificationService : INotificationService private readonly ICoreScopeProvider _uowProvider; private readonly IUserService _userService; - public NotificationService(ICoreScopeProvider provider, IUserService userService, IContentService contentService, + public NotificationService( + ICoreScopeProvider provider, + IUserService userService, + IContentService contentService, ILocalizationService localizationService, - ILogger logger, IIOHelper ioHelper, INotificationsRepository notificationsRepository, - IOptions globalSettings, IOptions contentSettings, IEmailSender emailSender) + ILogger logger, + IIOHelper ioHelper, + INotificationsRepository notificationsRepository, + IOptions globalSettings, + IOptions contentSettings, + IEmailSender emailSender) { _notificationsRepository = notificationsRepository; _globalSettings = globalSettings.Value; @@ -56,20 +67,24 @@ public NotificationService(ICoreScopeProvider provider, IUserService userService /// /// /// - public void SendNotifications(IUser operatingUser, IEnumerable entities, string? action, - string? actionName, Uri siteUri, + public void SendNotifications( + IUser operatingUser, + IEnumerable entities, + string? action, + string? actionName, + Uri siteUri, Func<(IUser user, NotificationEmailSubjectParams subject), string> createSubject, Func<(IUser user, NotificationEmailBodyParams body, bool isHtml), string> createBody) { var entitiesL = entities.ToList(); - //exit if there are no entities + // exit if there are no entities if (entitiesL.Count == 0) { return; } - //put all entity's paths into a list with the same indices + // put all entity's paths into a list with the same indices var paths = entitiesL.Select(x => x.Path.Split(Constants.CharArrays.Comma).Select(s => int.Parse(s, CultureInfo.InvariantCulture)) .ToArray()) @@ -85,8 +100,7 @@ public void SendNotifications(IUser operatingUser, IEnumerable entitie { // users are returned ordered by id, notifications are returned ordered by user id var users = _userService.GetNextUsers(id, pagesz).Where(x => x.IsApproved).ToList(); - var notifications = GetUsersNotifications(users.Select(x => x.Id), action, Enumerable.Empty(), - Constants.ObjectTypes.Document)?.ToList(); + var notifications = GetUsersNotifications(users.Select(x => x.Id), action, Enumerable.Empty(), Constants.ObjectTypes.Document)?.ToList(); if (notifications is null || notifications.Count == 0) { break; @@ -118,8 +132,7 @@ public void SendNotifications(IUser operatingUser, IEnumerable entitie } // queue notification - NotificationRequest req = CreateNotificationRequest(operatingUser, user, content, - prevVersionDictionary[content.Id], actionName, siteUri, createSubject, createBody); + NotificationRequest req = CreateNotificationRequest(operatingUser, user, content, prevVersionDictionary[content.Id], actionName, siteUri, createSubject, createBody); Enqueue(req); } @@ -128,7 +141,8 @@ public void SendNotifications(IUser operatingUser, IEnumerable entitie do { i++; - } while (i < notifications.Count && notifications[i].UserId == user.Id); + } + while (i < notifications.Count && notifications[i].UserId == user.Id); if (i >= notifications.Count) { @@ -138,7 +152,8 @@ public void SendNotifications(IUser operatingUser, IEnumerable entitie // load more users if any id = users.Count == pagesz ? users.Last().Id + 1 : -1; - } while (id > 0); + } + while (id > 0); } /// @@ -171,7 +186,7 @@ public void SendNotifications(IUser operatingUser, IEnumerable entitie return null; } - IEnumerable userNotifications = GetUserNotifications(user); + IEnumerable? userNotifications = GetUserNotifications(user); return FilterUserNotificationsByPath(userNotifications, path); } @@ -268,6 +283,21 @@ public Notification CreateNotification(IUser user, IEntity entity, string action } } + /// + /// Filters a userNotifications collection by a path + /// + /// + /// + /// + public IEnumerable? FilterUserNotificationsByPath( + IEnumerable? userNotifications, + string path) + { + var pathParts = path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); + return userNotifications + ?.Where(r => pathParts.InvariantContains(r.EntityId.ToString(CultureInfo.InvariantCulture))).ToList(); + } + /// /// Gets the previous version to the latest version of the content item if there is one /// @@ -283,8 +313,7 @@ public Notification CreateNotification(IUser user, IEntity entity, string action return _contentService.GetVersion(allVersions[prevVersionIndex]); } - private IEnumerable? GetUsersNotifications(IEnumerable userIds, string? action, - IEnumerable nodeIds, Guid objectType) + private IEnumerable? GetUsersNotifications(IEnumerable userIds, string? action, IEnumerable nodeIds, Guid objectType) { using (ICoreScope scope = _uowProvider.CreateCoreScope(autoComplete: true)) { @@ -293,17 +322,22 @@ public Notification CreateNotification(IUser user, IEntity entity, string action } /// - /// Filters a userNotifications collection by a path + /// Replaces the HTML symbols with the character equivalent. /// - /// - /// - /// - public IEnumerable? FilterUserNotificationsByPath(IEnumerable? userNotifications, - string path) + /// The old string. + private static void ReplaceHtmlSymbols(ref string? oldString) { - var pathParts = path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries); - return userNotifications - ?.Where(r => pathParts.InvariantContains(r.EntityId.ToString(CultureInfo.InvariantCulture))).ToList(); + if (oldString.IsNullOrWhiteSpace()) + { + return; + } + + oldString = oldString!.Replace(" ", " "); + oldString = oldString.Replace("’", "'"); + oldString = oldString.Replace("&", "&"); + oldString = oldString.Replace("“", "“"); + oldString = oldString.Replace("”", "”"); + oldString = oldString.Replace(""", "\""); } #region private methods @@ -322,7 +356,10 @@ public Notification CreateNotification(IUser user, IEntity entity, string action /// /// Callback to create the mail subject /// Callback to create the mail body - private NotificationRequest CreateNotificationRequest(IUser performingUser, IUser mailingUser, IContent content, + private NotificationRequest CreateNotificationRequest( + IUser performingUser, + IUser mailingUser, + IContent content, IContentBase? oldDoc, string? actionName, Uri siteUri, @@ -366,29 +403,28 @@ private NotificationRequest CreateNotificationRequest(IUser performingUser, IUse { if (!_contentSettings.Notifications.DisableHtmlEmail) { - //create the HTML summary for invariant content + // create the HTML summary for invariant content - //list all of the property values like we used to + // list all of the property values like we used to summary.Append(""); foreach (IProperty p in content.Properties) { // TODO: doesn't take into account variants - - var newText = p.GetValue() != null ? p.GetValue()?.ToString() : ""; + var newText = p.GetValue() != null ? p.GetValue()?.ToString() : string.Empty; var oldText = newText; // check if something was changed and display the changes otherwise display the fields if (oldDoc?.Properties.Contains(p.PropertyType.Alias) ?? false) { - IProperty oldProperty = oldDoc.Properties[p.PropertyType.Alias]; - oldText = oldProperty?.GetValue() != null ? oldProperty.GetValue()?.ToString() : ""; + IProperty? oldProperty = oldDoc.Properties[p.PropertyType.Alias]; + oldText = oldProperty?.GetValue() != null ? oldProperty.GetValue()?.ToString() : string.Empty; // replace HTML with char equivalent ReplaceHtmlSymbols(ref oldText); ReplaceHtmlSymbols(ref newText); } - //show the values + // show the values summary.Append(""); summary.Append( "
"); @@ -405,13 +441,11 @@ private NotificationRequest CreateNotificationRequest(IUser performingUser, IUse } else if (content.ContentType.VariesByCulture()) { - //it's variant, so detect what cultures have changed - + // it's variant, so detect what cultures have changed if (!_contentSettings.Notifications.DisableHtmlEmail) { - //Create the HTML based summary (ul of culture names) - - IEnumerable culturesChanged = content.CultureInfos?.Values.Where(x => x.WasDirty()) + // Create the HTML based summary (ul of culture names) + IEnumerable? culturesChanged = content.CultureInfos?.Values.Where(x => x.WasDirty()) .Select(x => x.Culture) .Select(_localizationService.GetLanguageByIsoCode) .WhereNotNull() @@ -431,8 +465,7 @@ private NotificationRequest CreateNotificationRequest(IUser performingUser, IUse } else { - //Create the text based summary (csv of culture names) - + // Create the text based summary (csv of culture names) var culturesChanged = string.Join(", ", content.CultureInfos!.Values.Where(x => x.WasDirty()) .Select(x => x.Culture) .Select(_localizationService.GetLanguageByIsoCode) @@ -446,7 +479,7 @@ private NotificationRequest CreateNotificationRequest(IUser performingUser, IUse } else { - //not supported yet... + // not supported yet... throw new NotSupportedException(); } @@ -462,8 +495,10 @@ private NotificationRequest CreateNotificationRequest(IUser performingUser, IUse actionName, content.Name, content.Id.ToString(CultureInfo.InvariantCulture), - string.Format("{2}://{0}/{1}", + string.Format( + "{2}://{0}/{1}", string.Concat(siteUri.Authority), + // TODO: RE-enable this so we can have a nice URL /*umbraco.library.NiceUrl(documentObject.Id))*/ string.Concat(content.Id, ".aspx"), @@ -475,7 +510,7 @@ private NotificationRequest CreateNotificationRequest(IUser performingUser, IUse var fromMail = _contentSettings.Notifications.Email ?? _globalSettings.Smtp?.From; var subject = createSubject((mailingUser, subjectVars)); - var body = ""; + var body = string.Empty; var isBodyHtml = false; if (_contentSettings.Notifications.DisableHtmlEmail) @@ -486,7 +521,8 @@ private NotificationRequest CreateNotificationRequest(IUser performingUser, IUse { isBodyHtml = true; body = - string.Concat(@" + string.Concat( + @" ", createBody((user: mailingUser, body: bodyVars, true))); @@ -519,29 +555,6 @@ private string ReplaceLinks(string text, Uri siteUri) return text; } - /// - /// Replaces the HTML symbols with the character equivalent. - /// - /// The old string. - private static void ReplaceHtmlSymbols(ref string? oldString) - { - if (oldString.IsNullOrWhiteSpace()) - { - return; - } - - oldString = oldString!.Replace(" ", " "); - oldString = oldString.Replace("’", "'"); - oldString = oldString.Replace("&", "&"); - oldString = oldString.Replace("“", "“"); - oldString = oldString.Replace("”", "”"); - oldString = oldString.Replace(""", "\""); - } - - // manage notifications - // ideally, would need to use IBackgroundTasks - but they are not part of Core! - - private static readonly object Locker = new(); private static readonly BlockingCollection Queue = new(); private static volatile bool _running; @@ -565,40 +578,20 @@ private void Enqueue(NotificationRequest notification) } } - private class NotificationRequest - { - public NotificationRequest(EmailMessage mail, string? action, string? userName, string? email) - { - Mail = mail; - Action = action; - UserName = userName; - Email = email; - } - - public EmailMessage Mail { get; } - - public string? Action { get; } - - public string? UserName { get; } - - public string? Email { get; } - } - private void Process(BlockingCollection notificationRequests) => ThreadPool.QueueUserWorkItem(state => { _logger.LogDebug("Begin processing notifications."); while (true) { - NotificationRequest? request; - while (notificationRequests.TryTake(out request, 8 * 1000)) // stay on for 8s + // stay on for 8s + while (notificationRequests.TryTake(out NotificationRequest? request, 8 * 1000)) { try { _emailSender.SendAsync(request.Mail, Constants.Web.EmailTypes.Notification).GetAwaiter() .GetResult(); - _logger.LogDebug("Notification '{Action}' sent to {Username} ({Email})", request.Action, - request.UserName, request.Email); + _logger.LogDebug("Notification '{Action}' sent to {Username} ({Email})", request.Action, request.UserName, request.Email); } catch (Exception ex) { @@ -621,5 +614,24 @@ private void Process(BlockingCollection notificationRequest _logger.LogDebug("Done processing notifications."); }); + private class NotificationRequest + { + public NotificationRequest(EmailMessage mail, string? action, string? userName, string? email) + { + Mail = mail; + Action = action; + UserName = userName; + Email = email; + } + + public EmailMessage Mail { get; } + + public string? Action { get; } + + public string? UserName { get; } + + public string? Email { get; } + } + #endregion } diff --git a/src/Umbraco.Core/Services/OperationResult.cs b/src/Umbraco.Core/Services/OperationResult.cs index d170a3b0efb1..919077919c4b 100644 --- a/src/Umbraco.Core/Services/OperationResult.cs +++ b/src/Umbraco.Core/Services/OperationResult.cs @@ -1,6 +1,7 @@ -using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Events; namespace Umbraco.Cms.Core.Services; + // TODO: no need for Attempt - the operation result SHOULD KNOW if it's a success or a failure! // but then each WhateverResultType must @@ -141,7 +142,8 @@ public static Attempt?> Succeed(EventMessages eventMessages, TValue value) => Core.Attempt.Succeed( new OperationResult(OperationResultType.Success, eventMessages, value)); - public static Attempt?> Succeed(TStatusType statusType, + public static Attempt?> Succeed( + TStatusType statusType, EventMessages eventMessages) where TStatusType : struct => Core.Attempt.Succeed(new OperationResult(statusType, eventMessages)); @@ -169,13 +171,13 @@ public static Attempt?> public static Attempt?> Cancel(EventMessages eventMessages) => Core.Attempt.Fail( - new OperationResult(OperationResultType.FailedCancelledByEvent, + new OperationResult( + OperationResultType.FailedCancelledByEvent, eventMessages)); public static Attempt?> Cancel(EventMessages eventMessages, TValue value) => Core.Attempt.Fail( - new OperationResult(OperationResultType.FailedCancelledByEvent, eventMessages, - value)); + new OperationResult(OperationResultType.FailedCancelledByEvent, eventMessages, value)); /// /// Creates a failed operation attempt indicating that an exception was thrown during the operation. @@ -185,8 +187,9 @@ public static Attempt?> /// A new attempt instance. public static Attempt Fail(EventMessages eventMessages, Exception exception) { - eventMessages.Add(new EventMessage("", exception.Message, EventMessageType.Error)); - return Core.Attempt.Fail(new OperationResult(OperationResultType.FailedExceptionThrown, eventMessages), + eventMessages.Add(new EventMessage(string.Empty, exception.Message, EventMessageType.Error)); + return Core.Attempt.Fail( + new OperationResult(OperationResultType.FailedExceptionThrown, eventMessages), exception); } @@ -195,33 +198,44 @@ public static Attempt?> new OperationResult(OperationResultType.FailedExceptionThrown, eventMessages), exception); - public static Attempt?> Fail(TStatusType statusType, + public static Attempt?> Fail( + TStatusType statusType, EventMessages eventMessages) where TStatusType : struct => Core.Attempt.Fail(new OperationResult(statusType, eventMessages)); - public static Attempt?> Fail(TStatusType statusType, - EventMessages eventMessages, Exception exception) + public static Attempt?> Fail( + TStatusType statusType, + EventMessages eventMessages, + Exception exception) where TStatusType : struct => Core.Attempt.Fail(new OperationResult(statusType, eventMessages), exception); - public static Attempt?> Fail(TStatusType statusType, + public static Attempt?> Fail( + TStatusType statusType, EventMessages eventMessages) where TStatusType : struct => Core.Attempt.Fail(new OperationResult(statusType, eventMessages)); - public static Attempt?> Fail(TStatusType statusType, - EventMessages eventMessages, TValue value) + public static Attempt?> Fail( + TStatusType statusType, + EventMessages eventMessages, + TValue value) where TStatusType : struct => Core.Attempt.Fail(new OperationResult(statusType, eventMessages, value)); - public static Attempt?> Fail(TStatusType statusType, - EventMessages eventMessages, Exception exception) + public static Attempt?> Fail( + TStatusType statusType, + EventMessages eventMessages, + Exception exception) where TStatusType : struct => Core.Attempt.Fail(new OperationResult(statusType, eventMessages), exception); - public static Attempt?> Fail(TStatusType statusType, - EventMessages eventMessages, TValue value, Exception exception) + public static Attempt?> Fail( + TStatusType statusType, + EventMessages eventMessages, + TValue value, + Exception exception) where TStatusType : struct => Core.Attempt.Fail(new OperationResult(statusType, eventMessages, value), exception); diff --git a/src/Umbraco.Core/Services/OperationResultType.cs b/src/Umbraco.Core/Services/OperationResultType.cs index 1c3cfa38845f..c87b70c2a208 100644 --- a/src/Umbraco.Core/Services/OperationResultType.cs +++ b/src/Umbraco.Core/Services/OperationResultType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Services; +namespace Umbraco.Cms.Core.Services; /// /// A value indicating the result of an operation. @@ -38,7 +38,7 @@ public enum OperationResultType : byte /// /// No operation has been executed because it was not needed (eg deleting an item that doesn't exist). /// - NoOperation = Failed | 6 // TODO: shouldn't it be a success? + NoOperation = Failed | 6, // TODO: shouldn't it be a success? // TODO: In the future, we might need to add more operations statuses, potentially like 'FailedByPermissions', etc... } diff --git a/src/Umbraco.Core/Services/Ordering.cs b/src/Umbraco.Core/Services/Ordering.cs index 0a3f5fa911ca..39c89e5c4a43 100644 --- a/src/Umbraco.Core/Services/Ordering.cs +++ b/src/Umbraco.Core/Services/Ordering.cs @@ -1,4 +1,4 @@ -using Umbraco.Extensions; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services; @@ -26,8 +26,7 @@ public class Ordering /// empty string. /// /// - public Ordering(string? orderBy, Direction direction = Direction.Ascending, string? culture = null, - bool isCustomField = false) + public Ordering(string? orderBy, Direction direction = Direction.Ascending, string? culture = null, bool isCustomField = false) { OrderBy = orderBy.IfNullOrWhiteSpace(null); // empty is null and means, not sorting Direction = direction; @@ -82,8 +81,7 @@ public Ordering(string? orderBy, Direction direction = Direction.Ascending, stri /// empty string. /// /// - public static Ordering By(string orderBy, Direction direction = Direction.Ascending, string? culture = null, - bool isCustomField = false) + public static Ordering By(string orderBy, Direction direction = Direction.Ascending, string? culture = null, bool isCustomField = false) => new(orderBy, direction, culture, isCustomField); /// diff --git a/src/Umbraco.Core/Services/ProcessInstructionsResult.cs b/src/Umbraco.Core/Services/ProcessInstructionsResult.cs index 460860d7456f..39751dad61c0 100644 --- a/src/Umbraco.Core/Services/ProcessInstructionsResult.cs +++ b/src/Umbraco.Core/Services/ProcessInstructionsResult.cs @@ -1,7 +1,7 @@ namespace Umbraco.Cms.Core.Services; /// -/// Defines a result object for the +/// Defines a result object for the /// operation. /// public class ProcessInstructionsResult @@ -17,13 +17,13 @@ private ProcessInstructionsResult() public bool InstructionsWerePruned { get; private set; } public static ProcessInstructionsResult AsCompleted(int numberOfInstructionsProcessed, int lastId) => - new() {NumberOfInstructionsProcessed = numberOfInstructionsProcessed, LastId = lastId}; + new() { NumberOfInstructionsProcessed = numberOfInstructionsProcessed, LastId = lastId }; public static ProcessInstructionsResult AsCompletedAndPruned(int numberOfInstructionsProcessed, int lastId) => new() { NumberOfInstructionsProcessed = numberOfInstructionsProcessed, LastId = lastId, - InstructionsWerePruned = true + InstructionsWerePruned = true, }; } diff --git a/src/Umbraco.Core/Services/PropertyValidationService.cs b/src/Umbraco.Core/Services/PropertyValidationService.cs index 50c6c3b5bad7..d93cbd4a7ccf 100644 --- a/src/Umbraco.Core/Services/PropertyValidationService.cs +++ b/src/Umbraco.Core/Services/PropertyValidationService.cs @@ -35,21 +35,20 @@ public IEnumerable ValidatePropertyValue( throw new ArgumentNullException(nameof(propertyType)); } - IDataType dataType = _dataTypeService.GetDataType(propertyType.DataTypeId); + IDataType? dataType = _dataTypeService.GetDataType(propertyType.DataTypeId); if (dataType == null) { throw new InvalidOperationException("No data type found by id " + propertyType.DataTypeId); } - IDataEditor editor = _propertyEditors[propertyType.PropertyEditorAlias]; + IDataEditor? editor = _propertyEditors[propertyType.PropertyEditorAlias]; if (editor == null) { throw new InvalidOperationException("No property editor found by alias " + propertyType.PropertyEditorAlias); } - return ValidatePropertyValue(editor, dataType, postedValue, propertyType.Mandatory, - propertyType.ValidationRegExp, propertyType.MandatoryMessage, propertyType.ValidationRegExpMessage); + return ValidatePropertyValue(editor, dataType, postedValue, propertyType.Mandatory, propertyType.ValidationRegExp, propertyType.MandatoryMessage, propertyType.ValidationRegExpMessage); } /// @@ -66,9 +65,9 @@ public IEnumerable ValidatePropertyValue( // if set with custom ones if they've been provided for a given property. var requiredDefaultMessages = new[] { - _textService.Localize("validation", "invalidNull"), _textService.Localize("validation", "invalidEmpty") + _textService.Localize("validation", "invalidNull"), _textService.Localize("validation", "invalidEmpty"), }; - var formatDefaultMessages = new[] {_textService.Localize("validation", "invalidPattern")}; + var formatDefaultMessages = new[] { _textService.Localize("validation", "invalidPattern") }; IDataValueEditor valueEditor = _valueEditorCache.GetValueEditor(editor, dataType); foreach (ValidationResult validationResult in valueEditor.Validate(postedValue, isRequired, validationRegExp)) @@ -125,7 +124,6 @@ public bool IsPropertyDataValid(IContent content, out IProperty[] invalidPropert // if either // - it is impacted (default culture), or // - there is no published version of the content - maybe non-default culture, but no published version - var alsoInvariant = impact.ImpactsAlsoInvariantProperties || !content.Published; return alsoInvariant && !IsPropertyValid(x, null); }).ToArray(); @@ -136,9 +134,8 @@ public bool IsPropertyDataValid(IContent content, out IProperty[] invalidPropert /// public bool IsPropertyValid(IProperty property, string? culture = "*", string? segment = "*") { - //NOTE - the pvalue and vvalues logic in here is borrowed directly from the Property.Values setter so if you are wondering what that's all about, look there. + // NOTE - the pvalue and vvalues logic in here is borrowed directly from the Property.Values setter so if you are wondering what that's all about, look there. // The underlying Property._pvalue and Property._vvalues are not exposed but we can re-create these values ourselves which is what it's doing. - culture = culture?.NullOrWhiteSpaceAsNull(); segment = segment?.NullOrWhiteSpaceAsNull(); @@ -206,7 +203,7 @@ private bool IsValidPropertyValue(IProperty property, object? value) => /// private bool IsPropertyValueValid(IPropertyType propertyType, object? value) { - IDataEditor editor = _propertyEditors[propertyType.PropertyEditorAlias]; + IDataEditor? editor = _propertyEditors[propertyType.PropertyEditorAlias]; if (editor == null) { // nothing much we can do validation wise if the property editor has been removed. diff --git a/src/Umbraco.Core/Services/PublicAccessService.cs b/src/Umbraco.Core/Services/PublicAccessService.cs index 93eda7694220..6f3de02c55d8 100644 --- a/src/Umbraco.Core/Services/PublicAccessService.cs +++ b/src/Umbraco.Core/Services/PublicAccessService.cs @@ -13,7 +13,9 @@ internal class PublicAccessService : RepositoryService, IPublicAccessService { private readonly IPublicAccessRepository _publicAccessRepository; - public PublicAccessService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, + public PublicAccessService( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IPublicAccessRepository publicAccessRepository) : base(provider, loggerFactory, eventMessagesFactory) => @@ -49,23 +51,23 @@ public IEnumerable GetAll() /// public PublicAccessEntry? GetEntryForContent(string contentPath) { - //Get all ids in the path for the content item and ensure they all + // Get all ids in the path for the content item and ensure they all // parse to ints that are not -1. var ids = contentPath.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) .Select(x => int.TryParse(x, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val) ? val : -1) .Where(x => x != -1) .ToList(); - //start with the deepest id + // start with the deepest id ids.Reverse(); using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - //This will retrieve from cache! + // This will retrieve from cache! var entries = _publicAccessRepository.GetMany().ToList(); foreach (var id in ids) { - PublicAccessEntry found = entries.FirstOrDefault(x => x.ProtectedNodeId == id); + PublicAccessEntry? found = entries.FirstOrDefault(x => x.ProtectedNodeId == id); if (found != null) { return found; @@ -83,7 +85,7 @@ public IEnumerable GetAll() /// public Attempt IsProtected(IContent content) { - PublicAccessEntry result = GetEntryForContent(content); + PublicAccessEntry? result = GetEntryForContent(content); return Attempt.If(result != null, result); } @@ -94,7 +96,7 @@ public IEnumerable GetAll() /// public Attempt IsProtected(string contentPath) { - PublicAccessEntry result = GetEntryForContent(contentPath); + PublicAccessEntry? result = GetEntryForContent(contentPath); return Attempt.If(result != null, result); } @@ -105,8 +107,7 @@ public IEnumerable GetAll() /// /// /// - public Attempt?> AddRule(IContent content, string ruleType, - string ruleValue) + public Attempt?> AddRule(IContent content, string ruleType, string ruleValue) { EventMessages evtMsgs = EventMessagesFactory.Get(); PublicAccessEntry? entry; @@ -118,7 +119,7 @@ public IEnumerable GetAll() return OperationResult.Attempt.Cannot(evtMsgs); // causes rollback } - PublicAccessRule existingRule = + PublicAccessRule? existingRule = entry.Rules.FirstOrDefault(x => x.RuleType == ruleType && x.RuleValue == ruleValue); if (existingRule == null) { @@ -126,7 +127,7 @@ public IEnumerable GetAll() } else { - //If they are both the same already then there's nothing to update, exit + // If they are both the same already then there's nothing to update, exit return OperationResult.Attempt.Succeed(evtMsgs, entry); } @@ -163,14 +164,14 @@ public IEnumerable GetAll() entry = _publicAccessRepository.GetMany().FirstOrDefault(x => x.ProtectedNodeId == content.Id); if (entry == null) { - return Attempt.Fail(); // causes rollback // causes rollback + return Attempt.Fail(); // causes rollback // causes rollback } - PublicAccessRule existingRule = + PublicAccessRule? existingRule = entry.Rules.FirstOrDefault(x => x.RuleType == ruleType && x.RuleValue == ruleValue); if (existingRule == null) { - return Attempt.Fail(); // causes rollback // causes rollback + return Attempt.Fail(); // causes rollback // causes rollback } entry.RemoveRule(existingRule); diff --git a/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs b/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs index e3b8efb0c28d..eb42dcda73fa 100644 --- a/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs +++ b/src/Umbraco.Core/Services/PublicAccessServiceExtensions.cs @@ -12,8 +12,7 @@ namespace Umbraco.Extensions; /// public static class PublicAccessServiceExtensions { - public static bool RenameMemberGroupRoleRules(this IPublicAccessService publicAccessService, string? oldRolename, - string? newRolename) + public static bool RenameMemberGroupRoleRules(this IPublicAccessService publicAccessService, string? oldRolename, string? newRolename) { var hasChange = false; if (oldRolename == newRolename) @@ -25,14 +24,14 @@ public static bool RenameMemberGroupRoleRules(this IPublicAccessService publicAc foreach (PublicAccessEntry entry in allEntries) { - //get rules that match + // get rules that match IEnumerable roleRules = entry.Rules .Where(x => x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType) .Where(x => x.RuleValue == oldRolename); var save = false; foreach (PublicAccessRule roleRule in roleRules) { - //a rule is being updated so flag this entry to be saved + // a rule is being updated so flag this entry to be saved roleRule.RuleValue = newRolename ?? string.Empty; save = true; } @@ -47,16 +46,15 @@ public static bool RenameMemberGroupRoleRules(this IPublicAccessService publicAc return hasChange; } - public static bool HasAccess(this IPublicAccessService publicAccessService, int documentId, - IContentService contentService, string username, IEnumerable currentMemberRoles) + public static bool HasAccess(this IPublicAccessService publicAccessService, int documentId, IContentService contentService, string username, IEnumerable currentMemberRoles) { - IContent content = contentService.GetById(documentId); + IContent? content = contentService.GetById(documentId); if (content == null) { return true; } - PublicAccessEntry entry = publicAccessService.GetEntryForContent(content); + PublicAccessEntry? entry = publicAccessService.GetEntryForContent(content); if (entry == null) { return true; @@ -74,8 +72,7 @@ public static bool HasAccess(this IPublicAccessService publicAccessService, int /// /// A callback to retrieve the roles for this member /// - public static async Task HasAccessAsync(this IPublicAccessService publicAccessService, string path, - string username, Func>> rolesCallback) + public static async Task HasAccessAsync(this IPublicAccessService publicAccessService, string path, string username, Func>> rolesCallback) { if (rolesCallback == null) { @@ -92,7 +89,7 @@ public static async Task HasAccessAsync(this IPublicAccessService publicAc throw new ArgumentException("Value cannot be null or whitespace.", "path"); } - PublicAccessEntry entry = publicAccessService.GetEntryForContent(path.EnsureEndsWith(path)); + PublicAccessEntry? entry = publicAccessService.GetEntryForContent(path.EnsureEndsWith(path)); if (entry == null) { return true; @@ -123,7 +120,6 @@ private static bool HasAccess(PublicAccessEntry entry, string username, IEnumera return entry.Rules.Any(x => (x.RuleType == Constants.Conventions.PublicAccess.MemberUsernameRuleType && username.Equals(x.RuleValue, StringComparison.OrdinalIgnoreCase)) - || (x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType && roles.Contains(x.RuleValue)) - ); + || (x.RuleType == Constants.Conventions.PublicAccess.MemberRoleRuleType && roles.Contains(x.RuleValue))); } } diff --git a/src/Umbraco.Core/Services/PublishResult.cs b/src/Umbraco.Core/Services/PublishResult.cs index 4511680b03d0..f689249afca7 100644 --- a/src/Umbraco.Core/Services/PublishResult.cs +++ b/src/Umbraco.Core/Services/PublishResult.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Services; diff --git a/src/Umbraco.Core/Services/PublishResultType.cs b/src/Umbraco.Core/Services/PublishResultType.cs index b4a063ec7aa0..b8ebd5edd49c 100644 --- a/src/Umbraco.Core/Services/PublishResultType.cs +++ b/src/Umbraco.Core/Services/PublishResultType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Services; +namespace Umbraco.Cms.Core.Services; /// /// A value indicating the result of publishing or unpublishing a document. @@ -7,7 +7,6 @@ public enum PublishResultType : byte { // all "ResultType" enum's must be byte-based, and declare Failed = 128, and declare // every failure codes as >128 - see OperationResult and OperationResultType for details. - #region Success - Publish /// diff --git a/src/Umbraco.Core/Services/RedirectUrlService.cs b/src/Umbraco.Core/Services/RedirectUrlService.cs index ff7347974a48..e68eed31e779 100644 --- a/src/Umbraco.Core/Services/RedirectUrlService.cs +++ b/src/Umbraco.Core/Services/RedirectUrlService.cs @@ -10,7 +10,9 @@ internal class RedirectUrlService : RepositoryService, IRedirectUrlService { private readonly IRedirectUrlRepository _redirectUrlRepository; - public RedirectUrlService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, + public RedirectUrlService( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IRedirectUrlRepository redirectUrlRepository) : base(provider, loggerFactory, eventMessagesFactory) => @@ -20,14 +22,14 @@ public void Register(string url, Guid contentKey, string? culture = null) { using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - IRedirectUrl redir = _redirectUrlRepository.Get(url, contentKey, culture); + IRedirectUrl? redir = _redirectUrlRepository.Get(url, contentKey, culture); if (redir != null) { redir.CreateDateUtc = DateTime.UtcNow; } else { - redir = new RedirectUrl {Key = Guid.NewGuid(), Url = url, ContentKey = contentKey, Culture = culture}; + redir = new RedirectUrl { Key = Guid.NewGuid(), Url = url, ContentKey = contentKey, Culture = culture }; } _redirectUrlRepository.Save(redir); diff --git a/src/Umbraco.Core/Services/RelationService.cs b/src/Umbraco.Core/Services/RelationService.cs index a9bb0ea03fc9..20cd72e7ccaf 100644 --- a/src/Umbraco.Core/Services/RelationService.cs +++ b/src/Umbraco.Core/Services/RelationService.cs @@ -17,10 +17,7 @@ public class RelationService : RepositoryService, IRelationService private readonly IRelationRepository _relationRepository; private readonly IRelationTypeRepository _relationTypeRepository; - public RelationService(ICoreScopeProvider uowProvider, ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, IEntityService entityService, - IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, - IAuditRepository auditRepository) + public RelationService(ICoreScopeProvider uowProvider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IEntityService entityService, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, IAuditRepository auditRepository) : base(uowProvider, loggerFactory, eventMessagesFactory) { _relationRepository = relationRepository; @@ -69,11 +66,11 @@ public IEnumerable GetAllRelations(params int[] ids) } /// - public IEnumerable? GetAllRelationsByRelationType(IRelationType relationType) => + public IEnumerable GetAllRelationsByRelationType(IRelationType relationType) => GetAllRelationsByRelationType(relationType.Id); /// - public IEnumerable? GetAllRelationsByRelationType(int relationTypeId) + public IEnumerable GetAllRelationsByRelationType(int relationTypeId) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -92,7 +89,7 @@ public IEnumerable GetAllRelationTypes(params int[] ids) } /// - public IEnumerable? GetByParentId(int id) => GetByParentId(id, null); + public IEnumerable GetByParentId(int id) => GetByParentId(id, null); /// public IEnumerable GetByParentId(int id, string? relationTypeAlias) @@ -102,10 +99,10 @@ public IEnumerable GetByParentId(int id, string? relationTypeAlias) if (relationTypeAlias.IsNullOrWhiteSpace()) { IQuery qry1 = Query().Where(x => x.ParentId == id); - return _relationRepository.Get(qry1) ?? Enumerable.Empty(); + return _relationRepository.Get(qry1); } - IRelationType relationType = GetRelationType(relationTypeAlias!); + IRelationType? relationType = GetRelationType(relationTypeAlias!); if (relationType == null) { return Enumerable.Empty(); @@ -113,12 +110,12 @@ public IEnumerable GetByParentId(int id, string? relationTypeAlias) IQuery qry2 = Query().Where(x => x.ParentId == id && x.RelationTypeId == relationType.Id); - return _relationRepository.Get(qry2) ?? Enumerable.Empty(); + return _relationRepository.Get(qry2); } } /// - public IEnumerable? GetByParent(IUmbracoEntity parent) => GetByParentId(parent.Id); + public IEnumerable GetByParent(IUmbracoEntity parent) => GetByParentId(parent.Id); /// public IEnumerable GetByParent(IUmbracoEntity parent, string relationTypeAlias) => @@ -135,10 +132,10 @@ public IEnumerable GetByChildId(int id, string? relationTypeAlias) if (relationTypeAlias.IsNullOrWhiteSpace()) { IQuery qry1 = Query().Where(x => x.ChildId == id); - return _relationRepository.Get(qry1) ?? Enumerable.Empty(); + return _relationRepository.Get(qry1); } - IRelationType relationType = GetRelationType(relationTypeAlias!); + IRelationType? relationType = GetRelationType(relationTypeAlias!); if (relationType == null) { return Enumerable.Empty(); @@ -146,7 +143,7 @@ public IEnumerable GetByChildId(int id, string? relationTypeAlias) IQuery qry2 = Query().Where(x => x.ChildId == id && x.RelationTypeId == relationType.Id); - return _relationRepository.Get(qry2) ?? Enumerable.Empty(); + return _relationRepository.Get(qry2); } } @@ -171,7 +168,7 @@ public IEnumerable GetByParentOrChildId(int id, string relationTypeAl { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - IRelationType relationType = GetRelationType(relationTypeAlias); + IRelationType? relationType = GetRelationType(relationTypeAlias); if (relationType == null) { return Enumerable.Empty(); @@ -191,7 +188,7 @@ public IEnumerable GetByParentOrChildId(int id, string relationTypeAl IQuery query = Query().Where(x => x.ParentId == parentId && x.ChildId == childId && x.RelationTypeId == relationType.Id); - return _relationRepository.Get(query)?.FirstOrDefault(); + return _relationRepository.Get(query).FirstOrDefault(); } } @@ -201,13 +198,13 @@ public IEnumerable GetByRelationTypeName(string relationTypeName) List? relationTypeIds; using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - //This is a silly query - but i guess it's needed in case someone has more than one relation with the same Name (not alias), odd. + // This is a silly query - but i guess it's needed in case someone has more than one relation with the same Name (not alias), odd. IQuery query = Query().Where(x => x.Name == relationTypeName); IEnumerable relationTypes = _relationTypeRepository.Get(query); - relationTypeIds = relationTypes?.Select(x => x.Id).ToList(); + relationTypeIds = relationTypes.Select(x => x.Id).ToList(); } - return relationTypeIds is null || relationTypeIds.Count == 0 + return relationTypeIds.Count == 0 ? Enumerable.Empty() : GetRelationsByListOfTypeIds(relationTypeIds); } @@ -215,15 +212,15 @@ public IEnumerable GetByRelationTypeName(string relationTypeName) /// public IEnumerable GetByRelationTypeAlias(string relationTypeAlias) { - IRelationType relationType = GetRelationType(relationTypeAlias); + IRelationType? relationType = GetRelationType(relationTypeAlias); return relationType == null ? Enumerable.Empty() - : GetRelationsByListOfTypeIds(new[] {relationType.Id}); + : GetRelationsByListOfTypeIds(new[] { relationType.Id }); } /// - public IEnumerable? GetByRelationTypeId(int relationTypeId) + public IEnumerable GetByRelationTypeId(int relationTypeId) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -233,12 +230,11 @@ public IEnumerable GetByRelationTypeAlias(string relationTypeAlias) } /// - public IEnumerable GetPagedByRelationTypeId(int relationTypeId, long pageIndex, int pageSize, - out long totalRecords, Ordering? ordering = null) + public IEnumerable GetPagedByRelationTypeId(int relationTypeId, long pageIndex, int pageSize, out long totalRecords, Ordering? ordering = null) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - IQuery query = Query()?.Where(x => x.RelationTypeId == relationTypeId); + IQuery? query = Query().Where(x => x.RelationTypeId == relationTypeId); return _relationRepository.GetPagedRelationsByQuery(query, pageIndex, pageSize, out totalRecords, ordering); } } @@ -263,8 +259,8 @@ public IEnumerable GetPagedByRelationTypeId(int relationTypeId, long UmbracoObjectTypes childObjectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType); UmbracoObjectTypes parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType); - IEntitySlim child = _entityService.Get(relation.ChildId, childObjectType); - IEntitySlim parent = _entityService.Get(relation.ParentId, parentObjectType); + IEntitySlim? child = _entityService.Get(relation.ChildId, childObjectType); + IEntitySlim? parent = _entityService.Get(relation.ParentId, parentObjectType); if (parent is null || child is null) { @@ -279,7 +275,6 @@ public IEnumerable GetChildEntitiesFromRelations(IEnumerable groupedRelations in relations.GroupBy(x => ObjectTypes.GetUmbracoObjectType(x.ChildObjectType))) { @@ -297,7 +292,6 @@ public IEnumerable GetParentEntitiesFromRelations(IEnumerable groupedRelations in relations.GroupBy(x => ObjectTypes.GetUmbracoObjectType(x.ParentObjectType))) { @@ -311,39 +305,34 @@ public IEnumerable GetParentEntitiesFromRelations(IEnumerable - public IEnumerable GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, - out long totalChildren, params UmbracoObjectTypes[] entityTypes) + public IEnumerable GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - return _relationRepository.GetPagedParentEntitiesByChildId(id, pageIndex, pageSize, out totalChildren, - entityTypes.Select(x => x.GetGuid()).ToArray()); + return _relationRepository.GetPagedParentEntitiesByChildId(id, pageIndex, pageSize, out totalChildren, entityTypes.Select(x => x.GetGuid()).ToArray()); } } /// - public IEnumerable GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize, - out long totalChildren, params UmbracoObjectTypes[] entityTypes) + public IEnumerable GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - return _relationRepository.GetPagedChildEntitiesByParentId(id, pageIndex, pageSize, out totalChildren, - entityTypes.Select(x => x.GetGuid()).ToArray()); + return _relationRepository.GetPagedChildEntitiesByParentId(id, pageIndex, pageSize, out totalChildren, entityTypes.Select(x => x.GetGuid()).ToArray()); } } /// public IEnumerable> GetEntitiesFromRelations(IEnumerable relations) { - //TODO: Argh! N+1 - + // TODO: Argh! N+1 foreach (IRelation relation in relations) { UmbracoObjectTypes childObjectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType); UmbracoObjectTypes parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType); - IEntitySlim child = _entityService.Get(relation.ChildId, childObjectType); - IEntitySlim parent = _entityService.Get(relation.ParentId, parentObjectType); + IEntitySlim? child = _entityService.Get(relation.ChildId, childObjectType); + IEntitySlim? parent = _entityService.Get(relation.ParentId, parentObjectType); if (parent is not null && child is not null) { @@ -361,8 +350,7 @@ public IRelation Relate(int parentId, int childId, IRelationType relationType) Save(relationType); } - //TODO: We don't check if this exists first, it will throw some sort of data integrity exception if it already exists, is that ok? - + // TODO: We don't check if this exists first, it will throw some sort of data integrity exception if it already exists, is that ok? var relation = new Relation(parentId, childId, relationType); using (ICoreScope scope = ScopeProvider.CreateCoreScope()) @@ -390,7 +378,7 @@ public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationTy /// public IRelation Relate(int parentId, int childId, string relationTypeAlias) { - IRelationType relationType = GetRelationTypeByAlias(relationTypeAlias); + IRelationType? relationType = GetRelationTypeByAlias(relationTypeAlias); if (relationType == null || string.IsNullOrEmpty(relationType.Alias)) { throw new ArgumentNullException( @@ -403,7 +391,7 @@ public IRelation Relate(int parentId, int childId, string relationTypeAlias) /// public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias) { - IRelationType relationType = GetRelationTypeByAlias(relationTypeAlias); + IRelationType? relationType = GetRelationTypeByAlias(relationTypeAlias); if (relationType == null || string.IsNullOrEmpty(relationType.Alias)) { throw new ArgumentNullException( @@ -419,7 +407,7 @@ public bool HasRelations(IRelationType relationType) using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { IQuery query = Query().Where(x => x.RelationTypeId == relationType.Id); - return _relationRepository.Get(query)?.Any() ?? false; + return _relationRepository.Get(query).Any(); } } @@ -429,7 +417,7 @@ public bool IsRelated(int id) using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { IQuery query = Query().Where(x => x.ParentId == id || x.ChildId == id); - return _relationRepository.Get(query)?.Any() ?? false; + return _relationRepository.Get(query).Any(); } } @@ -439,14 +427,14 @@ public bool AreRelated(int parentId, int childId) using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { IQuery query = Query().Where(x => x.ParentId == parentId && x.ChildId == childId); - return _relationRepository.Get(query)?.Any() ?? false; + return _relationRepository.Get(query).Any(); } } /// public bool AreRelated(int parentId, int childId, string relationTypeAlias) { - IRelationType relType = GetRelationTypeByAlias(relationTypeAlias); + IRelationType? relType = GetRelationTypeByAlias(relationTypeAlias); if (relType == null) { return false; @@ -462,7 +450,6 @@ public bool AreRelated(int parentId, int childId, string relationTypeAlias) public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias) => AreRelated(parent.Id, child.Id, relationTypeAlias); - /// public void Save(IRelation relation) { @@ -518,8 +505,7 @@ public void Save(IRelationType relationType) } _relationTypeRepository.Save(relationType); - Audit(AuditType.Save, Constants.Security.SuperUserId, relationType.Id, - $"Saved relation type: {relationType.Name}"); + Audit(AuditType.Save, Constants.Security.SuperUserId, relationType.Id, $"Saved relation type: {relationType.Name}"); scope.Complete(); scope.Notifications.Publish( new RelationTypeSavedNotification(relationType, eventMessages).WithStateFrom(savingNotification)); @@ -572,15 +558,11 @@ public void DeleteRelationsOfType(IRelationType relationType) var relations = new List(); using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - IQuery? query = Query().Where(x => x.RelationTypeId == relationType.Id); - var allRelations = _relationRepository.Get(query)?.ToList(); - if (allRelations is not null) - { - relations.AddRange(allRelations); - } - - //TODO: N+1, we should be able to do this in a single call + IQuery query = Query().Where(x => x.RelationTypeId == relationType.Id); + var allRelations = _relationRepository.Get(query).ToList(); + relations.AddRange(allRelations); + // TODO: N+1, we should be able to do this in a single call foreach (IRelation relation in relations) { _relationRepository.Delete(relation); @@ -592,15 +574,13 @@ public void DeleteRelationsOfType(IRelationType relationType) } } - - /// public bool AreRelated(int parentId, int childId, IRelationType relationType) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { IQuery query = Query().Where(x => x.ParentId == parentId && x.ChildId == childId && x.RelationTypeId == relationType.Id); - return _relationRepository.Get(query)?.Any() ?? false; + return _relationRepository.Get(query).Any(); } } @@ -611,7 +591,7 @@ public bool AreRelated(int parentId, int childId, IRelationType relationType) using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { IQuery query = Query().Where(x => x.Alias == relationTypeAlias); - return _relationTypeRepository.Get(query)?.FirstOrDefault(); + return _relationTypeRepository.Get(query).FirstOrDefault(); } } @@ -625,10 +605,7 @@ private IEnumerable GetRelationsByListOfTypeIds(IEnumerable rela var id = relationTypeId; IQuery query = Query().Where(x => x.RelationTypeId == id); IEnumerable relation = _relationRepository.Get(query); - if (relation is not null) - { - relations.AddRange(relation); - } + relations.AddRange(relation); } } @@ -636,8 +613,7 @@ private IEnumerable GetRelationsByListOfTypeIds(IEnumerable rela } private void Audit(AuditType type, int userId, int objectId, string? message = null) => - _auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.RelationType.GetName(), - message)); + _auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.RelationType.GetName(), message)); #endregion } diff --git a/src/Umbraco.Core/Services/SectionService.cs b/src/Umbraco.Core/Services/SectionService.cs index ffb7914640d7..61ff97889483 100644 --- a/src/Umbraco.Core/Services/SectionService.cs +++ b/src/Umbraco.Core/Services/SectionService.cs @@ -25,7 +25,7 @@ public IEnumerable GetSections() /// public IEnumerable GetAllowedSections(int userId) { - IUser user = _userService.GetUserById(userId); + IUser? user = _userService.GetUserById(userId); if (user == null) { throw new InvalidOperationException("No user found with id " + userId); diff --git a/src/Umbraco.Core/Services/ServerRegistrationService.cs b/src/Umbraco.Core/Services/ServerRegistrationService.cs index 825cc646a6f2..070e9e8e1f52 100644 --- a/src/Umbraco.Core/Services/ServerRegistrationService.cs +++ b/src/Umbraco.Core/Services/ServerRegistrationService.cs @@ -48,9 +48,9 @@ public void TouchServer(string serverAddress, TimeSpan staleTimeout) _serverRegistrationRepository.ClearCache(); // ensure we have up-to-date cache - IServerRegistration[] regs = _serverRegistrationRepository.GetMany()?.ToArray(); + IServerRegistration[]? regs = _serverRegistrationRepository.GetMany()?.ToArray(); var hasSchedulingPublisher = regs?.Any(x => ((ServerRegistration)x).IsSchedulingPublisher); - IServerRegistration server = + IServerRegistration? server = regs?.FirstOrDefault(x => x.ServerIdentity?.InvariantEquals(serverIdentity) ?? false); if (server == null) @@ -73,12 +73,11 @@ public void TouchServer(string serverAddress, TimeSpan staleTimeout) _serverRegistrationRepository.DeactiveStaleServers(staleTimeout); // triggers a cache reload // reload - cheap, cached - - regs = _serverRegistrationRepository.GetMany()?.ToArray(); + regs = _serverRegistrationRepository.GetMany().ToArray(); // default role is single server, but if registrations contain more // than one active server, then role is scheduling publisher or subscriber - _currentServerRole = regs?.Count(x => x.IsActive) > 1 + _currentServerRole = regs.Count(x => x.IsActive) > 1 ? server.IsSchedulingPublisher ? ServerRole.SchedulingPublisher : ServerRole.Subscriber : ServerRole.Single; @@ -93,7 +92,6 @@ public void TouchServer(string serverAddress, TimeSpan staleTimeout) public void DeactiveServer(string serverIdentity) { // because the repository caches "all" and has queries disabled... - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { scope.WriteLock(Constants.Locks.Servers); @@ -101,7 +99,7 @@ public void DeactiveServer(string serverIdentity) _serverRegistrationRepository .ClearCache(); // ensure we have up-to-date cache // ensure we have up-to-date cache - IServerRegistration server = _serverRegistrationRepository.GetMany() + IServerRegistration? server = _serverRegistrationRepository.GetMany() ?.FirstOrDefault(x => x.ServerIdentity?.InvariantEquals(serverIdentity) ?? false); if (server == null) { diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index 1e79cb5e9a5b..0e24f27be50c 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Services; +namespace Umbraco.Cms.Core.Services; /// /// Represents the Umbraco Service context, which provides access to all services. @@ -36,19 +36,34 @@ public class ServiceContext /// /// Initializes a new instance of the class with lazy services. /// - public ServiceContext(Lazy? publicAccessService, Lazy? domainService, - Lazy? auditService, Lazy? localizedTextService, - Lazy? tagService, Lazy? contentService, Lazy? userService, - Lazy? memberService, Lazy? mediaService, - Lazy? contentTypeService, Lazy? mediaTypeService, - Lazy? dataTypeService, Lazy? fileService, - Lazy? localizationService, Lazy? packagingService, - Lazy? serverRegistrationService, Lazy? entityService, - Lazy? relationService, Lazy? macroService, - Lazy? memberTypeService, Lazy? memberGroupService, - Lazy? notificationService, Lazy? externalLoginService, - Lazy? redirectUrlService, Lazy? consentService, - Lazy? keyValueService, Lazy? contentTypeBaseServiceProvider) + public ServiceContext( + Lazy? publicAccessService, + Lazy? domainService, + Lazy? auditService, + Lazy? localizedTextService, + Lazy? tagService, + Lazy? contentService, + Lazy? userService, + Lazy? memberService, + Lazy? mediaService, + Lazy? contentTypeService, + Lazy? mediaTypeService, + Lazy? dataTypeService, + Lazy? fileService, + Lazy? localizationService, + Lazy? packagingService, + Lazy? serverRegistrationService, + Lazy? entityService, + Lazy? relationService, + Lazy? macroService, + Lazy? memberTypeService, + Lazy? memberGroupService, + Lazy? notificationService, + Lazy? externalLoginService, + Lazy? redirectUrlService, + Lazy? consentService, + Lazy? keyValueService, + Lazy? contentTypeBaseServiceProvider) { _publicAccessService = publicAccessService; _domainService = domainService; @@ -281,7 +296,6 @@ public static ServiceContext CreatePartial( Lazy(redirectUrlService), Lazy(consentService), Lazy(keyValueService), - Lazy(contentTypeBaseServiceProvider) - ); + Lazy(contentTypeBaseServiceProvider)); } } diff --git a/src/Umbraco.Core/Services/TagService.cs b/src/Umbraco.Core/Services/TagService.cs index f25614419e2b..c75863f6de67 100644 --- a/src/Umbraco.Core/Services/TagService.cs +++ b/src/Umbraco.Core/Services/TagService.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; @@ -17,9 +17,7 @@ public class TagService : RepositoryService, ITagService { private readonly ITagRepository _tagRepository; - public TagService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, - ITagRepository tagRepository) + public TagService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, ITagRepository tagRepository) : base(provider, loggerFactory, eventMessagesFactory) => _tagRepository = tagRepository; @@ -132,8 +130,7 @@ public IEnumerable GetAllMemberTags(string? group = null, string? culture } /// - public IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string? group = null, - string? culture = null) + public IEnumerable GetTagsForProperty(int contentId, string propertyTypeAlias, string? group = null, string? culture = null) { using (ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -151,8 +148,7 @@ public IEnumerable GetTagsForEntity(int contentId, string? group = null, s } /// - public IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string? group = null, - string? culture = null) + public IEnumerable GetTagsForProperty(Guid contentId, string propertyTypeAlias, string? group = null, string? culture = null) { using (ScopeProvider.CreateCoreScope(autoComplete: true)) { diff --git a/src/Umbraco.Core/Services/TrackedReferencesService.cs b/src/Umbraco.Core/Services/TrackedReferencesService.cs index 3a6f585ed329..32dc9c18cc56 100644 --- a/src/Umbraco.Core/Services/TrackedReferencesService.cs +++ b/src/Umbraco.Core/Services/TrackedReferencesService.cs @@ -10,8 +10,10 @@ public class TrackedReferencesService : ITrackedReferencesService private readonly ICoreScopeProvider _scopeProvider; private readonly ITrackedReferencesRepository _trackedReferencesRepository; - public TrackedReferencesService(ITrackedReferencesRepository trackedReferencesRepository, - ICoreScopeProvider scopeProvider, IEntityService entityService) + public TrackedReferencesService( + ITrackedReferencesRepository trackedReferencesRepository, + ICoreScopeProvider scopeProvider, + IEntityService entityService) { _trackedReferencesRepository = trackedReferencesRepository; _scopeProvider = scopeProvider; @@ -22,34 +24,29 @@ public TrackedReferencesService(ITrackedReferencesRepository trackedReferencesRe /// Gets a paged result of items which are in relation with the current item. /// Basically, shows the items which depend on the current item. /// - public PagedResult GetPagedRelationsForItem(int id, long pageIndex, int pageSize, - bool filterMustBeIsDependency) + public PagedResult GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency) { using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); - IEnumerable items = _trackedReferencesRepository.GetPagedRelationsForItem(id, pageIndex, pageSize, - filterMustBeIsDependency, out var totalItems); + IEnumerable items = _trackedReferencesRepository.GetPagedRelationsForItem(id, pageIndex, pageSize, filterMustBeIsDependency, out var totalItems); - return new PagedResult(totalItems, pageIndex + 1, pageSize) {Items = items}; + return new PagedResult(totalItems, pageIndex + 1, pageSize) { Items = items }; } /// /// Gets a paged result of items used in any kind of relation from selected integer ids. /// - public PagedResult GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, - bool filterMustBeIsDependency) + public PagedResult GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency) { using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); - IEnumerable items = _trackedReferencesRepository.GetPagedItemsWithRelations(ids, pageIndex, - pageSize, filterMustBeIsDependency, out var totalItems); + IEnumerable items = _trackedReferencesRepository.GetPagedItemsWithRelations(ids, pageIndex, pageSize, filterMustBeIsDependency, out var totalItems); - return new PagedResult(totalItems, pageIndex + 1, pageSize) {Items = items}; + return new PagedResult(totalItems, pageIndex + 1, pageSize) { Items = items }; } /// /// Gets a paged result of the descending items that have any references, given a parent id. /// - public PagedResult GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, - bool filterMustBeIsDependency) + public PagedResult GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency) { using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); @@ -59,6 +56,6 @@ public PagedResult GetPagedDescendantsInReferences(int parentId, l pageSize, filterMustBeIsDependency, out var totalItems); - return new PagedResult(totalItems, pageIndex + 1, pageSize) {Items = items}; + return new PagedResult(totalItems, pageIndex + 1, pageSize) { Items = items }; } } diff --git a/src/Umbraco.Core/Services/TreeService.cs b/src/Umbraco.Core/Services/TreeService.cs index 39d1960fb692..3b2b5f361814 100644 --- a/src/Umbraco.Core/Services/TreeService.cs +++ b/src/Umbraco.Core/Services/TreeService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Trees; +using Umbraco.Cms.Core.Trees; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Services; @@ -21,11 +21,13 @@ public class TreeService : ITreeService /// public IEnumerable GetAll(TreeUse use = TreeUse.Main) + // use HasFlagAny: if use is Main|Dialog, we want to return Main *and* Dialog trees => _treeCollection.Where(x => x.TreeUse.HasFlagAny(use)); /// public IEnumerable GetBySection(string sectionAlias, TreeUse use = TreeUse.Main) + // use HasFlagAny: if use is Main|Dialog, we want to return Main *and* Dialog trees => _treeCollection.Where(x => x.SectionAlias.InvariantEquals(sectionAlias) && x.TreeUse.HasFlagAny(use)) .OrderBy(x => x.SortOrder).ToList(); @@ -34,6 +36,6 @@ public IEnumerable GetBySection(string sectionAlias, TreeUse use = TreeUse public IDictionary> GetBySectionGrouped(string sectionAlias, TreeUse use = TreeUse.Main) => GetBySection(sectionAlias, use).GroupBy(x => x.TreeGroup).ToDictionary( - x => x.Key ?? "", + x => x.Key ?? string.Empty, x => (IEnumerable)x.ToArray()); } diff --git a/src/Umbraco.Core/Services/TwoFactorLoginService.cs b/src/Umbraco.Core/Services/TwoFactorLoginService.cs index b3c90234d7b2..de79284ac9fc 100644 --- a/src/Umbraco.Core/Services/TwoFactorLoginService.cs +++ b/src/Umbraco.Core/Services/TwoFactorLoginService.cs @@ -46,7 +46,8 @@ public TwoFactorLoginService( IEnumerable twoFactorSetupGenerators, IOptions identityOptions, IOptions backOfficeIdentityOptions) - : this(twoFactorLoginRepository, + : this( + twoFactorLoginRepository, scopeProvider, twoFactorSetupGenerators, identityOptions, @@ -97,7 +98,10 @@ public async Task ValidateAndSaveAsync(string providerName, Guid userOrMem var twoFactorLogin = new TwoFactorLogin { - Confirmed = true, Secret = secret, UserOrMemberKey = userOrMemberKey, ProviderName = providerName + Confirmed = true, + Secret = secret, + UserOrMemberKey = userOrMemberKey, + ProviderName = providerName, }; await SaveAsync(twoFactorLogin); @@ -175,13 +179,19 @@ public Task SaveAsync(TwoFactorLogin twoFactorLogin) return Task.CompletedTask; } + /// + /// Generates a new random unique secret. + /// + /// The random secret + protected virtual string GenerateSecret() => Guid.NewGuid().ToString(); + private async Task> GetEnabledProviderNamesAsync(Guid userOrMemberKey) { using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); var providersOnUser = (await _twoFactorLoginRepository.GetByUserOrMemberKeyAsync(userOrMemberKey)) .Select(x => x.ProviderName).ToArray(); - return providersOnUser.Where(IsKnownProviderName)!; + return providersOnUser.Where(IsKnownProviderName); } /// @@ -206,10 +216,4 @@ private bool IsKnownProviderName(string? providerName) return false; } - - /// - /// Generates a new random unique secret. - /// - /// The random secret - protected virtual string GenerateSecret() => Guid.NewGuid().ToString(); } diff --git a/src/Umbraco.Core/Services/UserDataService.cs b/src/Umbraco.Core/Services/UserDataService.cs index 3a60a6b9081f..14b2e581f9b1 100644 --- a/src/Umbraco.Core/Services/UserDataService.cs +++ b/src/Umbraco.Core/Services/UserDataService.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Runtime.InteropServices; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Models; @@ -12,7 +12,6 @@ public class UserDataService : IUserDataService private readonly ILocalizationService _localizationService; private readonly IUmbracoVersion _version; - public UserDataService(IUmbracoVersion version, ILocalizationService localizationService) { _version = version; @@ -28,11 +27,9 @@ public IEnumerable GetUserData() => new("Umbraco Version", _version.SemanticVersion.ToSemanticStringWithoutBuild()), new("Current Culture", Thread.CurrentThread.CurrentCulture.ToString()), new("Current UI Culture", Thread.CurrentThread.CurrentUICulture.ToString()), - new("Current Webserver", GetCurrentWebServer()) + new("Current Webserver", GetCurrentWebServer()), }; - private string GetCurrentWebServer() => IsRunningInProcessIIS() ? "IIS" : "Kestrel"; - public bool IsRunningInProcessIIS() { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -43,4 +40,6 @@ public bool IsRunningInProcessIIS() var processName = Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().ProcessName); return processName.Contains("w3wp") || processName.Contains("iisexpress"); } + + private string GetCurrentWebServer() => IsRunningInProcessIIS() ? "IIS" : "Kestrel"; } diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index cf16621273ff..88e2708b2ce1 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -27,9 +27,13 @@ internal class UserService : RepositoryService, IUserService private readonly IUserGroupRepository _userGroupRepository; private readonly IUserRepository _userRepository; - public UserService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, IRuntimeState runtimeState, - IUserRepository userRepository, IUserGroupRepository userGroupRepository, + public UserService( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IRuntimeState runtimeState, + IUserRepository userRepository, + IUserGroupRepository userGroupRepository, IOptions globalSettings) : base(provider, loggerFactory, eventMessagesFactory) { @@ -43,6 +47,38 @@ public UserService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, private bool IsUpgrading => _runtimeState.Level == RuntimeLevel.Install || _runtimeState.Level == RuntimeLevel.Upgrade; + /// + /// Checks in a set of permissions associated with a user for those related to a given nodeId + /// + /// The set of permissions + /// The node Id + /// The permissions to return + /// True if permissions for the given path are found + public static bool TryGetAssignedPermissionsForNode( + IList permissions, + int nodeId, + out string assignedPermissions) + { + if (permissions.Any(x => x.EntityId == nodeId)) + { + EntityPermission found = permissions.First(x => x.EntityId == nodeId); + var assignedPermissionsArray = found.AssignedPermissions.ToList(); + + // Working with permissions assigned directly to a user AND to their groups, so maybe several per node + // and we need to get the most permissive set + foreach (EntityPermission permission in permissions.Where(x => x.EntityId == nodeId).Skip(1)) + { + AddAdditionalPermissions(assignedPermissionsArray, permission.AssignedPermissions); + } + + assignedPermissions = string.Join(string.Empty, assignedPermissionsArray); + return true; + } + + assignedPermissions = string.Empty; + return false; + } + #region Implementation of IMembershipUserService /// @@ -83,8 +119,7 @@ public IUser CreateUserWithIdentity(string username, string email) => /// /// /// - IUser IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, - string memberTypeAlias) => CreateUserWithIdentity(username, email, passwordValue); + IUser IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias) => CreateUserWithIdentity(username, email, passwordValue); /// /// Creates and persists a new @@ -100,8 +135,22 @@ IUser IMembershipMemberService.CreateWithIdentity(string username, string /// /// /// - IUser IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, - string memberTypeAlias, bool isApproved) => CreateUserWithIdentity(username, email, passwordValue, isApproved); + IUser IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias, bool isApproved) => CreateUserWithIdentity(username, email, passwordValue, isApproved); + + /// + /// Gets a User by its integer id + /// + /// Id + /// + /// + /// + public IUser? GetById(int id) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + { + return _userRepository.Get(id); + } + } /// /// Creates and persists a Member @@ -129,14 +178,14 @@ private IUser CreateUserWithIdentity(string username, string email, string passw if (string.IsNullOrWhiteSpace(username)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(username)); } EventMessages evtMsgs = EventMessagesFactory.Get(); // TODO: PUT lock here!! - User user; using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { @@ -154,7 +203,7 @@ private IUser CreateUserWithIdentity(string username, string email, string passw RawPasswordValue = passwordValue, Username = username, IsLockedOut = false, - IsApproved = isApproved + IsApproved = isApproved, }; var savingNotification = new UserSavingNotification(user, evtMsgs); @@ -173,21 +222,6 @@ private IUser CreateUserWithIdentity(string username, string email, string passw return user; } - /// - /// Gets a User by its integer id - /// - /// Id - /// - /// - /// - public IUser? GetById(int id) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _userRepository.Get(id); - } - } - /// /// Gets an by its provider key /// @@ -245,7 +279,7 @@ private IUser CreateUserWithIdentity(string username, string email, string passw // be better BUT requires that the app restarts after the upgrade! if (IsUpgrading) { - //NOTE: this will not be cached + // NOTE: this will not be cached return _userRepository.GetByUsername(username, false); } @@ -260,7 +294,7 @@ private IUser CreateUserWithIdentity(string username, string email, string passw /// to disable public void Delete(IUser membershipUser) { - //disable + // disable membershipUser.IsApproved = false; Save(membershipUser); @@ -347,7 +381,8 @@ public void Save(IUser entity) throw; } - _logger.LogWarning(ex, + _logger.LogWarning( + ex, "An error occurred attempting to save a user instance during upgrade, normally this warning can be ignored"); // we don't want the uow to rollback its scope! @@ -393,7 +428,7 @@ public void Save(IEnumerable entities) scope.Notifications.Publish( new UserSavedNotification(entitiesA, evtMsgs).WithStateFrom(savingNotification)); - //commit the whole lot in one go + // commit the whole lot in one go scope.Complete(); } } @@ -418,8 +453,7 @@ public void Save(IEnumerable entities) /// /// /// - public IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, - out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + public IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -446,8 +480,7 @@ public IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, throw new ArgumentOutOfRangeException(nameof(matchType)); } - return _userRepository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, - dto => dto.Email); + return _userRepository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, dto => dto.Email); } } @@ -465,8 +498,7 @@ public IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, /// /// /// - public IEnumerable FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, - StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) + public IEnumerable FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -493,8 +525,7 @@ public IEnumerable FindByUsername(string login, long pageIndex, int pageS throw new ArgumentOutOfRangeException(nameof(matchType)); } - return _userRepository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, - dto => dto.Username); + return _userRepository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, dto => dto.Username); } } @@ -582,8 +613,7 @@ public IDictionary GetUserStates() } } - public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, string orderBy, - Direction orderDirection, UserState[]? userState = null, string[]? userGroups = null, string? filter = null) + public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, UserState[]? userState = null, string[]? userGroups = null, string? filter = null) { IQuery? filterQuery = null; if (filter.IsNullOrWhiteSpace() == false) @@ -592,13 +622,19 @@ public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRec (x.Name != null && x.Name.Contains(filter!)) || x.Username.Contains(filter!)); } - return GetAll(pageIndex, pageSize, out totalRecords, orderBy, orderDirection, userState, userGroups, null, - filterQuery); + return GetAll(pageIndex, pageSize, out totalRecords, orderBy, orderDirection, userState, userGroups, null, filterQuery); } - public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, string orderBy, - Direction orderDirection, UserState[]? userState = null, string[]? includeUserGroups = null, - string[]? excludeUserGroups = null, IQuery? filter = null) + public IEnumerable GetAll( + long pageIndex, + int pageSize, + out long totalRecords, + string orderBy, + Direction orderDirection, + UserState[]? userState = null, + string[]? includeUserGroups = null, + string[]? excludeUserGroups = null, + IQuery? filter = null) { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { @@ -639,8 +675,7 @@ public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRec throw new IndexOutOfRangeException("The orderBy parameter " + orderBy + " is not valid"); } - return _userRepository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, sort, - orderDirection, includeUserGroups, excludeUserGroups, userState, filter); + return _userRepository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, sort, orderDirection, includeUserGroups, excludeUserGroups, userState, filter); } } @@ -657,8 +692,7 @@ public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRec { using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - return _userRepository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, - member => member.Name); + return _userRepository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, member => member.Name); } } @@ -718,8 +752,8 @@ public IEnumerable GetAllNotInGroup(int groupId) /// public IProfile? GetProfileById(int id) { - //This is called a TON. Go get the full user from cache which should already be IProfile - IUser fullUser = GetUserById(id); + // This is called a TON. Go get the full user from cache which should already be IProfile + IUser? fullUser = GetUserById(id); if (fullUser == null) { return null; @@ -767,7 +801,7 @@ public IEnumerable GetAllNotInGroup(int groupId) // be better BUT requires that the app restarts after the upgrade! if (IsUpgrading) { - //NOTE: this will not be cached + // NOTE: this will not be cached return _userRepository.Get(id, false); } @@ -843,7 +877,7 @@ public void AssignUserGroupPermission(int groupId, char permission, params int[] _userGroupRepository.AssignGroupPermission(groupId, permission, entityIds); scope.Complete(); - var assigned = new[] {permission.ToString(CultureInfo.InvariantCulture)}; + var assigned = new[] { permission.ToString(CultureInfo.InvariantCulture) }; EntityPermission[] entityPermissions = entityIds.Select(x => new EntityPermission(groupId, x, assigned)).ToArray(); scope.Notifications.Publish(new AssignedUserGroupPermissionsNotification(entityPermissions, evtMsgs)); @@ -1023,8 +1057,8 @@ public void DeleteSectionFromAllUserGroups(string sectionAlias) IEnumerable assignedGroups = _userGroupRepository.GetGroupsAssignedToSection(sectionAlias); foreach (IUserGroup group in assignedGroups) { - //now remove the section for each user and commit - //now remove the section for each user and commit + // now remove the section for each user and commit + // now remove the section for each user and commit group.RemoveAllowedSection(sectionAlias); _userGroupRepository.Save(group); } @@ -1050,15 +1084,14 @@ public EntityPermissionCollection GetPermissions(IUser? user, params int[] nodeI /// /// Get explicitly assigned permissions for a group and optional node Ids /// - /// Groups to retrieve permissions for + /// /// /// Flag indicating if we want to include the default group permissions for each result if there are not explicit /// permissions set /// /// Specifying nothing will return all permissions for all nodes /// An enumerable list of - private IEnumerable GetPermissions(IReadOnlyUserGroup[] groups, bool fallbackToDefaultPermissions, - params int[] nodeIds) + public EntityPermissionCollection GetPermissions(IUserGroup?[] groups, bool fallbackToDefaultPermissions, params int[] nodeIds) { if (groups == null) { @@ -1067,22 +1100,24 @@ private IEnumerable GetPermissions(IReadOnlyUserGroup[] groups using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - return _userGroupRepository.GetPermissions(groups, fallbackToDefaultPermissions, nodeIds); + return _userGroupRepository.GetPermissions( + groups.WhereNotNull().Select(x => x.ToReadOnlyGroup()).ToArray(), + fallbackToDefaultPermissions, + nodeIds); } } /// /// Get explicitly assigned permissions for a group and optional node Ids /// - /// + /// Groups to retrieve permissions for /// /// Flag indicating if we want to include the default group permissions for each result if there are not explicit /// permissions set /// /// Specifying nothing will return all permissions for all nodes /// An enumerable list of - public EntityPermissionCollection GetPermissions(IUserGroup?[] groups, bool fallbackToDefaultPermissions, - params int[] nodeIds) + private IEnumerable GetPermissions(IReadOnlyUserGroup[] groups, bool fallbackToDefaultPermissions, params int[] nodeIds) { if (groups == null) { @@ -1091,8 +1126,7 @@ public EntityPermissionCollection GetPermissions(IUserGroup?[] groups, bool fall using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - return _userGroupRepository.GetPermissions(groups.WhereNotNull().Select(x => x.ToReadOnlyGroup()).ToArray(), - fallbackToDefaultPermissions, nodeIds); + return _userGroupRepository.GetPermissions(groups, fallbackToDefaultPermissions, nodeIds); } } @@ -1110,7 +1144,7 @@ public EntityPermissionSet GetPermissionsForPath(IUser? user, string? path) return EntityPermissionSet.Empty(); } - //collect all permissions structures for all nodes for all groups belonging to the user + // collect all permissions structures for all nodes for all groups belonging to the user EntityPermission[] groupPermissions = GetPermissionsForPath(user.Groups.ToArray(), nodeIds, true).ToArray(); return CalculatePermissionsForPathForUser(groupPermissions, nodeIds); @@ -1126,8 +1160,7 @@ public EntityPermissionSet GetPermissionsForPath(IUser? user, string? path) /// permissions set /// /// String indicating permissions for provided user and path - public EntityPermissionSet GetPermissionsForPath(IUserGroup[] groups, string path, - bool fallbackToDefaultPermissions = false) + public EntityPermissionSet GetPermissionsForPath(IUserGroup[] groups, string path, bool fallbackToDefaultPermissions = false) { var nodeIds = path.GetIdsFromPathReversed(); @@ -1136,31 +1169,13 @@ public EntityPermissionSet GetPermissionsForPath(IUserGroup[] groups, string pat return EntityPermissionSet.Empty(); } - //collect all permissions structures for all nodes for all groups + // collect all permissions structures for all nodes for all groups EntityPermission[] groupPermissions = GetPermissionsForPath(groups.Select(x => x.ToReadOnlyGroup()).ToArray(), nodeIds, true).ToArray(); return CalculatePermissionsForPathForUser(groupPermissions, nodeIds); } - private EntityPermissionCollection GetPermissionsForPath(IReadOnlyUserGroup[] groups, int[] pathIds, - bool fallbackToDefaultPermissions = false) - { - if (pathIds.Length == 0) - { - return new EntityPermissionCollection(Enumerable.Empty()); - } - - //get permissions for all nodes in the path by group - IEnumerable> permissions = - GetPermissions(groups, fallbackToDefaultPermissions, pathIds) - .GroupBy(x => x.UserGroupId); - - return new EntityPermissionCollection( - permissions.Select(x => GetPermissionsForPathForGroup(x, pathIds, fallbackToDefaultPermissions)) - .Where(x => x is not null)!); - } - /// /// This performs the calculations for inherited nodes based on this /// http://issues.umbraco.org/issue/U4-10075#comment=67-40085 @@ -1178,48 +1193,47 @@ internal static EntityPermissionSet CalculatePermissionsForPathForUser( return EntityPermissionSet.Empty(); } - //The actual entity id being looked at (deepest part of the path) + // The actual entity id being looked at (deepest part of the path) var entityId = pathIds[0]; var resultPermissions = new EntityPermissionCollection(); - //create a grouped by dictionary of another grouped by dictionary + // create a grouped by dictionary of another grouped by dictionary var permissionsByGroup = groupPermissions .GroupBy(x => x.UserGroupId) .ToDictionary( x => x.Key, x => x.GroupBy(a => a.EntityId).ToDictionary(a => a.Key, a => a.ToArray())); - //iterate through each group + // iterate through each group foreach (KeyValuePair> byGroup in permissionsByGroup) { var added = false; - //iterate deepest to shallowest + // iterate deepest to shallowest foreach (var pathId in pathIds) { - EntityPermission[]? permissionsForNodeAndGroup; - if (byGroup.Value.TryGetValue(pathId, out permissionsForNodeAndGroup) == false) + if (byGroup.Value.TryGetValue(pathId, out EntityPermission[]? permissionsForNodeAndGroup) == false) { continue; } - //In theory there will only be one EntityPermission in this group + // In theory there will only be one EntityPermission in this group // but there's nothing stopping the logic of this method // from having more so we deal with it here foreach (EntityPermission entityPermission in permissionsForNodeAndGroup) { if (entityPermission.IsDefaultPermissions == false) { - //explicit permission found so we'll append it and move on, the collection is a hashset anyways - //so only supports adding one element per groupid/contentid + // explicit permission found so we'll append it and move on, the collection is a hashset anyways + // so only supports adding one element per groupid/contentid resultPermissions.Add(entityPermission); added = true; break; } } - //if the permission has been added for this group and this branch then we can exit this loop + // if the permission has been added for this group and this branch then we can exit this loop if (added) { break; @@ -1228,8 +1242,8 @@ internal static EntityPermissionSet CalculatePermissionsForPathForUser( if (added == false && byGroup.Value.Count > 0) { - //if there was no explicit permissions assigned in this branch for this group, then we will - //add the group's default permissions + // if there was no explicit permissions assigned in this branch for this group, then we will + // add the group's default permissions resultPermissions.Add(byGroup.Value[entityId][0]); } } @@ -1238,6 +1252,23 @@ internal static EntityPermissionSet CalculatePermissionsForPathForUser( return permissionSet; } + private EntityPermissionCollection GetPermissionsForPath(IReadOnlyUserGroup[] groups, int[] pathIds, bool fallbackToDefaultPermissions = false) + { + if (pathIds.Length == 0) + { + return new EntityPermissionCollection(Enumerable.Empty()); + } + + // get permissions for all nodes in the path by group + IEnumerable> permissions = + GetPermissions(groups, fallbackToDefaultPermissions, pathIds) + .GroupBy(x => x.UserGroupId); + + return new EntityPermissionCollection( + permissions.Select(x => GetPermissionsForPathForGroup(x, pathIds, fallbackToDefaultPermissions)) + .Where(x => x is not null)!); + } + /// /// Returns the resulting permission set for a group for the path based on all permissions provided for the branch /// @@ -1256,16 +1287,15 @@ internal static EntityPermissionSet CalculatePermissionsForPathForUser( int[] pathIds, bool fallbackToDefaultPermissions = false) { - //get permissions for all nodes in the path + // get permissions for all nodes in the path var permissionsByEntityId = pathPermissions.ToDictionary(x => x.EntityId, x => x); - //then the permissions assigned to the path will be the 'deepest' node found that has permissions + // then the permissions assigned to the path will be the 'deepest' node found that has permissions foreach (var id in pathIds) { - EntityPermission? permission; - if (permissionsByEntityId.TryGetValue(id, out permission)) + if (permissionsByEntityId.TryGetValue(id, out EntityPermission? permission)) { - //don't return the default permissions if that is the one assigned here (we'll do that below if nothing was found) + // don't return the default permissions if that is the one assigned here (we'll do that below if nothing was found) if (permission.IsDefaultPermissions == false) { return permission; @@ -1273,7 +1303,7 @@ internal static EntityPermissionSet CalculatePermissionsForPathForUser( } } - //if we've made it here it means that no implicit/inherited permissions were found so we return the defaults if that is specified + // if we've made it here it means that no implicit/inherited permissions were found so we return the defaults if that is specified if (fallbackToDefaultPermissions == false) { return null; @@ -1282,37 +1312,6 @@ internal static EntityPermissionSet CalculatePermissionsForPathForUser( return permissionsByEntityId[pathIds[0]]; } - /// - /// Checks in a set of permissions associated with a user for those related to a given nodeId - /// - /// The set of permissions - /// The node Id - /// The permissions to return - /// True if permissions for the given path are found - public static bool TryGetAssignedPermissionsForNode(IList permissions, - int nodeId, - out string assignedPermissions) - { - if (permissions.Any(x => x.EntityId == nodeId)) - { - EntityPermission found = permissions.First(x => x.EntityId == nodeId); - var assignedPermissionsArray = found.AssignedPermissions.ToList(); - - // Working with permissions assigned directly to a user AND to their groups, so maybe several per node - // and we need to get the most permissive set - foreach (EntityPermission permission in permissions.Where(x => x.EntityId == nodeId).Skip(1)) - { - AddAdditionalPermissions(assignedPermissionsArray, permission.AssignedPermissions); - } - - assignedPermissions = string.Join("", assignedPermissionsArray); - return true; - } - - assignedPermissions = string.Empty; - return false; - } - private static void AddAdditionalPermissions(List assignedPermissions, string[] additionalPermissions) { IEnumerable permissionsToAdd = additionalPermissions diff --git a/src/Umbraco.Core/Services/UserServiceExtensions.cs b/src/Umbraco.Core/Services/UserServiceExtensions.cs index 544b22077743..3d06c0d6f20d 100644 --- a/src/Umbraco.Core/Services/UserServiceExtensions.cs +++ b/src/Umbraco.Core/Services/UserServiceExtensions.cs @@ -23,7 +23,7 @@ public static class UserServiceExtensions " could not be parsed into an array of integers or the path was empty"); } - return userService.GetPermissions(user, ids[ids.Length - 1]).FirstOrDefault(); + return userService.GetPermissions(user, ids[^1]).FirstOrDefault(); } /// @@ -39,7 +39,7 @@ public static class UserServiceExtensions /// An enumerable list of public static EntityPermissionCollection GetPermissions(this IUserService service, IUserGroup? group, bool fallbackToDefaultPermissions, params int[] nodeIds) => - service.GetPermissions(new[] {group}, fallbackToDefaultPermissions, nodeIds); + service.GetPermissions(new[] { group }, fallbackToDefaultPermissions, nodeIds); /// /// Gets the permissions for the provided group and path @@ -53,7 +53,7 @@ public static EntityPermissionCollection GetPermissions(this IUserService servic /// public static EntityPermissionSet GetPermissionsForPath(this IUserService service, IUserGroup group, string path, bool fallbackToDefaultPermissions = false) => - service.GetPermissionsForPath(new[] {group}, path, fallbackToDefaultPermissions); + service.GetPermissionsForPath(new[] { group }, path, fallbackToDefaultPermissions); /// /// Remove all permissions for this user group for all nodes specified @@ -72,7 +72,6 @@ public static void RemoveUserGroupPermissions(this IUserService userService, int public static void RemoveUserGroupPermissions(this IUserService userService, int groupId) => userService.ReplaceUserGroupPermissions(groupId, null); - public static IEnumerable GetProfilesById(this IUserService userService, params int[] ids) { IEnumerable fullUsers = userService.GetUsersById(ids); diff --git a/src/Umbraco.Core/Settable.cs b/src/Umbraco.Core/Settable.cs index f3a92d140451..9f91ee15ff34 100644 --- a/src/Umbraco.Core/Settable.cs +++ b/src/Umbraco.Core/Settable.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; /// /// Represents a value that can be assigned a value. diff --git a/src/Umbraco.Core/SimpleMainDom.cs b/src/Umbraco.Core/SimpleMainDom.cs index 4d76e3a9eb30..3b3bc1b0c017 100644 --- a/src/Umbraco.Core/SimpleMainDom.cs +++ b/src/Umbraco.Core/SimpleMainDom.cs @@ -13,6 +13,9 @@ public class SimpleMainDom : IMainDom, IDisposable private bool _disposedValue; private bool _isStopping; + /// + public bool IsMainDom { get; private set; } = true; + public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method @@ -20,9 +23,6 @@ public void Dispose() GC.SuppressFinalize(this); } - /// - public bool IsMainDom { get; private set; } = true; - // always acquire public bool Acquire(IApplicationShutdownRegistry hostingEnvironment) => true; diff --git a/src/Umbraco.Core/StaticApplicationLogging.cs b/src/Umbraco.Core/StaticApplicationLogging.cs index cf30cf3bd468..eac0a3f51b4f 100644 --- a/src/Umbraco.Core/StaticApplicationLogging.cs +++ b/src/Umbraco.Core/StaticApplicationLogging.cs @@ -5,14 +5,14 @@ namespace Umbraco.Cms.Core; public static class StaticApplicationLogging { - private static ILoggerFactory? s_loggerFactory; + private static ILoggerFactory? loggerFactory; public static ILogger Logger => CreateLogger(); - public static void Initialize(ILoggerFactory loggerFactory) => s_loggerFactory = loggerFactory; + public static void Initialize(ILoggerFactory loggerFactory) => StaticApplicationLogging.loggerFactory = loggerFactory; public static ILogger CreateLogger() => - s_loggerFactory?.CreateLogger() ?? NullLoggerFactory.Instance.CreateLogger(); + loggerFactory?.CreateLogger() ?? NullLoggerFactory.Instance.CreateLogger(); - public static ILogger CreateLogger(Type type) => s_loggerFactory?.CreateLogger(type) ?? NullLogger.Instance; + public static ILogger CreateLogger(Type type) => loggerFactory?.CreateLogger(type) ?? NullLogger.Instance; } diff --git a/src/Umbraco.Core/StringUdi.cs b/src/Umbraco.Core/StringUdi.cs index e27acc61d73f..2b1229be77c5 100644 --- a/src/Umbraco.Core/StringUdi.cs +++ b/src/Umbraco.Core/StringUdi.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; namespace Umbraco.Cms.Core; @@ -33,7 +33,14 @@ public StringUdi(Uri uriValue) /// public override bool IsRoot => Id == string.Empty; + public StringUdi EnsureClosed() + { + EnsureNotRoot(); + return this; + } + private static string EscapeUriString(string s) => + // Uri.EscapeUriString preserves / but also [ and ] which is bad // Uri.EscapeDataString does not preserve / which is bad // reserved = : / ? # [ ] @ ! $ & ' ( ) * + , ; = @@ -41,10 +48,4 @@ private static string EscapeUriString(string s) => // we want to preserve the / and the unreserved // so... string.Join("/", s.Split(Constants.CharArrays.ForwardSlash).Select(Uri.EscapeDataString)); - - public StringUdi EnsureClosed() - { - EnsureNotRoot(); - return this; - } } diff --git a/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs b/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs index 1ee31db1f3d4..e2eb3df7a446 100644 --- a/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs +++ b/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs @@ -1,4 +1,4 @@ -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Strings.Css; @@ -11,7 +11,8 @@ public class StylesheetHelper public static IEnumerable ParseRules(string? input) { var rules = new List(); - var ruleRegex = new Regex(string.Format(RuleRegexFormat, @"[^\*\r\n]*"), + var ruleRegex = new Regex( + string.Format(RuleRegexFormat, @"[^\*\r\n]*"), RegexOptions.IgnoreCase | RegexOptions.Singleline); if (input is not null) @@ -23,7 +24,7 @@ public static IEnumerable ParseRules(string? input) { var name = match.Groups["Name"].Value; - //If this name already exists, only use the first one + // If this name already exists, only use the first one if (rules.Any(x => x.Name == name)) { continue; @@ -33,15 +34,16 @@ public static IEnumerable ParseRules(string? input) { Name = match.Groups["Name"].Value, Selector = match.Groups["Selector"].Value, + // Only match first selector when chained together - Styles = string.Join(Environment.NewLine, - match.Groups["Styles"].Value.Split(new[] {"\r\n", "\n"}, StringSplitOptions.None) - .Select(x => x.Trim()).ToArray()) + Styles = string.Join( + Environment.NewLine, + match.Groups["Styles"].Value.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None) + .Select(x => x.Trim()).ToArray()), }); } } - return rules; } @@ -50,9 +52,10 @@ public static IEnumerable ParseRules(string? input) var contents = input; if (contents is not null) { - var ruleRegex = new Regex(string.Format(RuleRegexFormat, oldRuleName.EscapeRegexSpecialCharacters()), + var ruleRegex = new Regex( + string.Format(RuleRegexFormat, oldRuleName.EscapeRegexSpecialCharacters()), RegexOptions.IgnoreCase | RegexOptions.Singleline); - contents = ruleRegex.Replace(contents, rule != null ? rule.ToString() : ""); + contents = ruleRegex.Replace(contents, rule != null ? rule.ToString() : string.Empty); } return contents; diff --git a/src/Umbraco.Core/Strings/Css/StylesheetRule.cs b/src/Umbraco.Core/Strings/Css/StylesheetRule.cs index d22ac19f2ae6..4b726f34ef05 100644 --- a/src/Umbraco.Core/Strings/Css/StylesheetRule.cs +++ b/src/Umbraco.Core/Strings/Css/StylesheetRule.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Strings.Css; @@ -21,6 +21,7 @@ public override string ToString() sb.Append(Selector); sb.Append(" {"); sb.Append(Environment.NewLine); + // append nicely formatted style rules // - using tabs because the back office code editor uses tabs if (Styles.IsNullOrWhiteSpace() == false) diff --git a/src/Umbraco.Core/Strings/UrlSegmentProviderCollection.cs b/src/Umbraco.Core/Strings/UrlSegmentProviderCollection.cs index 1e842fefd278..39b826dae98d 100644 --- a/src/Umbraco.Core/Strings/UrlSegmentProviderCollection.cs +++ b/src/Umbraco.Core/Strings/UrlSegmentProviderCollection.cs @@ -4,7 +4,8 @@ namespace Umbraco.Cms.Core.Strings; public class UrlSegmentProviderCollection : BuilderCollectionBase { - public UrlSegmentProviderCollection(Func> items) : base(items) + public UrlSegmentProviderCollection(Func> items) + : base(items) { } } diff --git a/src/Umbraco.Core/Strings/UrlSegmentProviderCollectionBuilder.cs b/src/Umbraco.Core/Strings/UrlSegmentProviderCollectionBuilder.cs index 3cb8b9569238..dcab5b39fdda 100644 --- a/src/Umbraco.Core/Strings/UrlSegmentProviderCollectionBuilder.cs +++ b/src/Umbraco.Core/Strings/UrlSegmentProviderCollectionBuilder.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Strings; diff --git a/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs b/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs index 2a80e638658d..1accfde86b7d 100644 --- a/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs +++ b/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Strings; +namespace Umbraco.Cms.Core.Strings; /// /// Provides methods to convert Utf8 text to Ascii. @@ -23,14 +23,13 @@ public static string ToAsciiString(string text, char fail = '?') // this is faster although it uses more memory // but... we should be filtering short strings only... - var output = new char[input.Length * 3]; // *3 because of things such as OE var len = ToAscii(input, output, fail); return new string(output, 0, len); - //var output = new StringBuilder(input.Length + 16); // default is 16, start with at least input length + little extra - //ToAscii(input, output); - //return output.ToString(); + // var output = new StringBuilder(input.Length + 16); // default is 16, start with at least input length + little extra + // ToAscii(input, output); + // return output.ToString(); } /// @@ -45,18 +44,17 @@ public static char[] ToAsciiCharArray(string text, char fail = '?') // this is faster although it uses more memory // but... we should be filtering short strings only... - var output = new char[input.Length * 3]; // *3 because of things such as OE var len = ToAscii(input, output, fail); var array = new char[len]; Array.Copy(output, array, len); return array; - //var temp = new StringBuilder(input.Length + 16); // default is 16, start with at least input length + little extra - //ToAscii(input, temp); - //var output = new char[temp.Length]; - //temp.CopyTo(0, output, 0, temp.Length); - //return output; + // var temp = new StringBuilder(input.Length + 16); // default is 16, start with at least input length + little extra + // ToAscii(input, temp); + // var output = new char[temp.Length]; + // temp.CopyTo(0, output, 0, temp.Length); + // return output; } /// @@ -88,11 +86,11 @@ private static int ToAscii(char[] input, char[] output, char fail = '?') return opos; } - //private static void ToAscii(char[] input, StringBuilder output) - //{ + // private static void ToAscii(char[] input, StringBuilder output) + // { // var chars = new char[5]; - // for (var ipos = 0; ipos < input.Length; ipos++) + // for (var ipos = 0; ipos < input.Length; ipos++) // { // var opos = 0; // if (char.IsSurrogate(input[ipos])) @@ -103,7 +101,7 @@ private static int ToAscii(char[] input, char[] output, char fail = '?') // output.Append(chars, 0, opos); // } // } - //} + // } /// /// Converts the character at position in input array of Utf8 characters @@ -136,8 +134,9 @@ private static void ToAscii(char[] input, int ipos, char[] output, ref int opos, // we don't want them } - //else if (char.IsSeparator(c)) - //{ + + // else if (char.IsSeparator(c)) + // { // // The Unicode standard recognizes three subcategories of separators: // // - Space separators (the UnicodeCategory.SpaceSeparator category), which includes characters such as \u0020. // // - Line separators (the UnicodeCategory.LineSeparator category), which includes \u2028. @@ -146,8 +145,8 @@ private static void ToAscii(char[] input, int ipos, char[] output, ref int opos, // // Note: The Unicode standard classifies the characters \u000A (LF), \u000C (FF), and \u000A (CR) as control // // characters (members of the UnicodeCategory.Control category), not as separator characters. - // // better do it via WhiteSpace - //} + // // better do it via WhiteSpace + // } else if (char.IsWhiteSpace(c)) { // White space characters are the following Unicode characters: @@ -3325,7 +3324,6 @@ private static void ToAscii(char[] input, int ipos, char[] output, ref int opos, // BEGIN CUSTOM TRANSLITERATION OF CYRILIC CHARS - #region Cyrillic chars // russian uppercase "А Б В Г Д Е Ё Ж З И Й К Л М Н О П Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я" // russian lowercase "а б в г д е ё ж з и й к л м н о п р с т у ф х ц ч ш щ ъ ы ь э ю я" @@ -3343,7 +3341,6 @@ private static void ToAscii(char[] input, int ipos, char[] output, ref int opos, // TODO: transliterates Анастасия as Anastasiya, and not Anastasia // Ольга --> Ol'ga, Татьяна --> Tat'yana -- that's bad (?) // Note: should ä (German umlaut) become a or ae ? - case '\u0410': // А output[opos++] = 'A'; break; @@ -3557,7 +3554,6 @@ private static void ToAscii(char[] input, int ipos, char[] output, ref int opos, output[opos++] = 'a'; break; - #endregion // BEGIN EXTRA /* @@ -3580,12 +3576,12 @@ private static void ToAscii(char[] input, int ipos, char[] output, ref int opos, break; */ default: - //if (ToMoreAscii(input, ipos, output, ref opos)) + // if (ToMoreAscii(input, ipos, output, ref opos)) // break; - //if (!char.IsLetterOrDigit(c)) // that would not catch eg 汉 unfortunately + // if (!char.IsLetterOrDigit(c)) // that would not catch eg 汉 unfortunately // output[opos++] = '?'; - //else + // else // output[opos++] = c; // strict ASCII @@ -3596,11 +3592,11 @@ private static void ToAscii(char[] input, int ipos, char[] output, ref int opos, } } - //private static bool ToMoreAscii(char[] input, int ipos, char[] output, ref int opos) - //{ + // private static bool ToMoreAscii(char[] input, int ipos, char[] output, ref int opos) + // { // var c = input[ipos]; - // switch (c) + // switch (c) // { // case '£': // output[opos++] = 'G'; @@ -3608,22 +3604,22 @@ private static void ToAscii(char[] input, int ipos, char[] output, ref int opos, // output[opos++] = 'P'; // break; - // case '€': + // case '€': // output[opos++] = 'E'; // output[opos++] = 'U'; // output[opos++] = 'R'; // break; - // case '©': + // case '©': // output[opos++] = '('; // output[opos++] = 'C'; // output[opos++] = ')'; // break; - // default: + // default: // return false; // } - // return true; - //} + // return true; + // } } diff --git a/src/Umbraco.Core/Sync/MessageType.cs b/src/Umbraco.Core/Sync/MessageType.cs index ab1a54c873cc..282aebeb54b3 100644 --- a/src/Umbraco.Core/Sync/MessageType.cs +++ b/src/Umbraco.Core/Sync/MessageType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Sync; +namespace Umbraco.Cms.Core.Sync; /// /// The message type to be used for syncing across servers. @@ -11,5 +11,5 @@ public enum MessageType RemoveById, RefreshByInstance, RemoveByInstance, - RefreshByPayload + RefreshByPayload, } diff --git a/src/Umbraco.Core/Sync/NonRuntimeLevelBootStateAccessor.cs b/src/Umbraco.Core/Sync/NonRuntimeLevelBootStateAccessor.cs index 0df9a645c825..4040edd8f723 100644 --- a/src/Umbraco.Core/Sync/NonRuntimeLevelBootStateAccessor.cs +++ b/src/Umbraco.Core/Sync/NonRuntimeLevelBootStateAccessor.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Sync; +namespace Umbraco.Cms.Core.Sync; /// /// Boot state implementation for when umbraco is not in the run state diff --git a/src/Umbraco.Core/Sync/RefreshInstruction.cs b/src/Umbraco.Core/Sync/RefreshInstruction.cs index 1ac0e7479edd..2a80dbf95f85 100644 --- a/src/Umbraco.Core/Sync/RefreshInstruction.cs +++ b/src/Umbraco.Core/Sync/RefreshInstruction.cs @@ -33,8 +33,7 @@ public RefreshInstruction() => /// Need this public one so it can be de-serialized - used by the Json thing /// otherwise, should use GetInstructions(...) /// - public RefreshInstruction(Guid refresherId, RefreshMethodType refreshType, Guid guidId, int intId, string jsonIds, - string jsonPayload) + public RefreshInstruction(Guid refresherId, RefreshMethodType refreshType, Guid guidId, int intId, string jsonIds, string jsonPayload) : this() { RefresherId = refresherId; @@ -119,6 +118,8 @@ private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshT /// public string? JsonPayload { get; set; } + public static bool operator ==(RefreshInstruction left, RefreshInstruction right) => Equals(left, right); + public static IEnumerable GetInstructions( ICacheRefresher refresher, IJsonSerializer jsonSerializer, @@ -130,10 +131,10 @@ public static IEnumerable GetInstructions( switch (messageType) { case MessageType.RefreshAll: - return new[] {new RefreshInstruction(refresher, RefreshMethodType.RefreshAll)}; + return new[] { new RefreshInstruction(refresher, RefreshMethodType.RefreshAll) }; case MessageType.RefreshByJson: - return new[] {new RefreshInstruction(refresher, RefreshMethodType.RefreshByJson, json)}; + return new[] { new RefreshInstruction(refresher, RefreshMethodType.RefreshByJson, json) }; case MessageType.RefreshById: if (idType == null) @@ -147,8 +148,7 @@ public static IEnumerable GetInstructions( var intIds = ids?.Cast().ToArray(); return new[] { - new RefreshInstruction(refresher, RefreshMethodType.RefreshByIds, - jsonSerializer.Serialize(intIds), intIds?.Length ?? 0) + new RefreshInstruction(refresher, RefreshMethodType.RefreshByIds, jsonSerializer.Serialize(intIds), intIds?.Length ?? 0), }; } @@ -165,23 +165,15 @@ public static IEnumerable GetInstructions( // Must be ints, bulk-remove is not supported, so iterate. return ids?.Select(x => new RefreshInstruction(refresher, RefreshMethodType.RemoveById, (int)x)) ?? Enumerable.Empty(); - //return new[] { new RefreshInstruction(refresher, RefreshMethodType.RemoveByIds, JsonConvert.SerializeObject(ids.Cast().ToArray())) }; + // return new[] { new RefreshInstruction(refresher, RefreshMethodType.RemoveByIds, JsonConvert.SerializeObject(ids.Cast().ToArray())) }; default: - //case MessageType.RefreshByInstance: - //case MessageType.RemoveByInstance: + // case MessageType.RefreshByInstance: + // case MessageType.RemoveByInstance: throw new ArgumentOutOfRangeException("messageType"); } } - protected bool Equals(RefreshInstruction other) => - RefreshType == other.RefreshType - && RefresherId.Equals(other.RefresherId) - && GuidId.Equals(other.GuidId) - && IntId == other.IntId - && string.Equals(JsonIds, other.JsonIds) - && string.Equals(JsonPayload, other.JsonPayload); - public override bool Equals(object? other) { if (other is null) @@ -202,6 +194,14 @@ public override bool Equals(object? other) return Equals((RefreshInstruction)other); } + protected bool Equals(RefreshInstruction other) => + RefreshType == other.RefreshType + && RefresherId.Equals(other.RefresherId) + && GuidId.Equals(other.GuidId) + && IntId == other.IntId + && string.Equals(JsonIds, other.JsonIds) + && string.Equals(JsonPayload, other.JsonPayload); + public override int GetHashCode() { unchecked @@ -216,7 +216,5 @@ public override int GetHashCode() } } - public static bool operator ==(RefreshInstruction left, RefreshInstruction right) => Equals(left, right); - public static bool operator !=(RefreshInstruction left, RefreshInstruction right) => Equals(left, right) == false; } diff --git a/src/Umbraco.Core/Sync/RefreshMethodType.cs b/src/Umbraco.Core/Sync/RefreshMethodType.cs index 78d1880951e3..f249a4701e15 100644 --- a/src/Umbraco.Core/Sync/RefreshMethodType.cs +++ b/src/Umbraco.Core/Sync/RefreshMethodType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Sync; +namespace Umbraco.Cms.Core.Sync; /// /// Describes refresh action type. @@ -10,16 +10,15 @@ public enum RefreshMethodType // that enum should get merged somehow with MessageType and renamed somehow // but at the moment it is exposed in CacheRefresher webservice through RefreshInstruction // so for the time being we keep it as-is for backward compatibility reasons - RefreshAll, RefreshByGuid, RefreshById, RefreshByIds, RefreshByJson, - RemoveById + RemoveById, // would adding values break backward compatibility? - //RemoveByIds + // RemoveByIds // these are MessageType values // note that AnythingByInstance are local messages and cannot be distributed diff --git a/src/Umbraco.Core/Sync/ServerRole.cs b/src/Umbraco.Core/Sync/ServerRole.cs index b1c2b7c444a8..15f546fc3594 100644 --- a/src/Umbraco.Core/Sync/ServerRole.cs +++ b/src/Umbraco.Core/Sync/ServerRole.cs @@ -23,5 +23,5 @@ public enum ServerRole : byte /// /// In a multi-servers environment, the server is the Scheduling Publisher. /// - SchedulingPublisher = 3 + SchedulingPublisher = 3, } diff --git a/src/Umbraco.Core/Sync/SyncBootState.cs b/src/Umbraco.Core/Sync/SyncBootState.cs index 307a358e23dc..6233ace01ae2 100644 --- a/src/Umbraco.Core/Sync/SyncBootState.cs +++ b/src/Umbraco.Core/Sync/SyncBootState.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Sync; +namespace Umbraco.Cms.Core.Sync; public enum SyncBootState { @@ -15,5 +15,5 @@ public enum SyncBootState /// /// Warm boot. Sync state present /// - WarmBoot = 2 + WarmBoot = 2, } diff --git a/src/Umbraco.Core/SystemLock.cs b/src/Umbraco.Core/SystemLock.cs index 587c6565cb82..ff4783802757 100644 --- a/src/Umbraco.Core/SystemLock.cs +++ b/src/Umbraco.Core/SystemLock.cs @@ -1,4 +1,4 @@ -using System.Runtime.ConstrainedExecution; +using System.Runtime.ConstrainedExecution; namespace Umbraco.Cms.Core; @@ -35,13 +35,11 @@ public SystemLock(string? name) // Release() increments count // initial count: the initial count value // maximum count: the max value of count, and then Release() throws - if (string.IsNullOrWhiteSpace(name)) { // anonymous semaphore // use one unique releaser, that will not release the semaphore when finalized // because the semaphore is destroyed anyway if the app goes down - _semaphore = new SemaphoreSlim(1, 1); // create a local (to the app domain) semaphore _releaser = new SemaphoreSlimReleaser(_semaphore); _releaserTask = Task.FromResult(_releaser); @@ -51,17 +49,10 @@ public SystemLock(string? name) // named semaphore // use dedicated releasers, that will release the semaphore when finalized // because the semaphore is system-wide and we cannot leak counts - _semaphore2 = new Semaphore(1, 1, name); // create a system-wide named semaphore } } - private IDisposable? CreateReleaser() => - // for anonymous semaphore, use the unique releaser, else create a new one - _semaphore != null - ? _releaser // (IDisposable)new SemaphoreSlimReleaser(_semaphore) - : new NamedSemaphoreReleaser(_semaphore2); - public IDisposable? Lock() { if (_semaphore != null) @@ -76,6 +67,13 @@ public SystemLock(string? name) return _releaser ?? CreateReleaser(); // anonymous vs named } + private IDisposable? CreateReleaser() => + + // for anonymous semaphore, use the unique releaser, else create a new one + _semaphore != null + ? _releaser // (IDisposable)new SemaphoreSlimReleaser(_semaphore) + : new NamedSemaphoreReleaser(_semaphore2); + public IDisposable? Lock(int millisecondsTimeout) { var entered = _semaphore != null @@ -91,45 +89,16 @@ public SystemLock(string? name) // note - before making those classes some structs, read // about "impure methods" and mutating readonly structs... - private class NamedSemaphoreReleaser : CriticalFinalizerObject, IDisposable { private readonly Semaphore? _semaphore; - internal NamedSemaphoreReleaser(Semaphore? semaphore) => _semaphore = semaphore; - #region IDisposable Support // This code added to correctly implement the disposable pattern. - private bool disposedValue; // To detect redundant calls - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); // finalize will not run - } - - private void Dispose(bool disposing) - { - if (!disposedValue) - { - try - { - _semaphore?.Release(); - } - finally - { - try - { - _semaphore?.Dispose(); - } - catch { } - } - - disposedValue = true; - } - } + internal NamedSemaphoreReleaser(Semaphore? semaphore) => _semaphore = semaphore; // we WANT to release the semaphore because it's a system object, ie a critical // non-managed resource - and if it is not released then noone else can acquire @@ -147,7 +116,6 @@ private void Dispose(bool disposing) // - use a GCHandler to ensure the semaphore is still there when the finalizer // runs, so we can actually release it // - wrap the finalizer code in a try...catch to make sure it never throws - ~NamedSemaphoreReleaser() { try @@ -160,6 +128,35 @@ private void Dispose(bool disposing) } } + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); // finalize will not run + } + + private void Dispose(bool disposing) + { + if (!disposedValue) + { + try + { + _semaphore?.Release(); + } + finally + { + try + { + _semaphore?.Dispose(); + } + catch + { + } + } + + disposedValue = true; + } + } + #endregion } @@ -169,6 +166,8 @@ private class SemaphoreSlimReleaser : IDisposable internal SemaphoreSlimReleaser(SemaphoreSlim semaphore) => _semaphore = semaphore; + ~SemaphoreSlimReleaser() => Dispose(false); + public void Dispose() { Dispose(true); @@ -183,7 +182,5 @@ private void Dispose(bool disposing) _semaphore.Release(); } } - - ~SemaphoreSlimReleaser() => Dispose(false); } } diff --git a/src/Umbraco.Core/Telemetry/Models/TelemetryReportData.cs b/src/Umbraco.Core/Telemetry/Models/TelemetryReportData.cs index fb6a4a990213..31bab02f1c95 100644 --- a/src/Umbraco.Core/Telemetry/Models/TelemetryReportData.cs +++ b/src/Umbraco.Core/Telemetry/Models/TelemetryReportData.cs @@ -30,5 +30,6 @@ public class TelemetryReportData [DataMember(Name = "packages")] public IEnumerable? Packages { get; set; } - [DataMember(Name = "detailed")] public IEnumerable? Detailed { get; set; } + [DataMember(Name = "detailed")] + public IEnumerable? Detailed { get; set; } } diff --git a/src/Umbraco.Core/Telemetry/TelemetryService.cs b/src/Umbraco.Core/Telemetry/TelemetryService.cs index f3255c63bdd1..4ebf1ba0b920 100644 --- a/src/Umbraco.Core/Telemetry/TelemetryService.cs +++ b/src/Umbraco.Core/Telemetry/TelemetryService.cs @@ -50,7 +50,7 @@ public bool TryGetTelemetryReportData(out TelemetryReportData? telemetryReportDa Id = telemetryId, Version = GetVersion(), Packages = GetPackageTelemetry(), - Detailed = _usageInformationService.GetDetailed() + Detailed = _usageInformationService.GetDetailed(), }; return true; } @@ -84,7 +84,8 @@ public bool TryGetTelemetryReportData(out TelemetryReportData? telemetryReportDa packages.Add(new PackageTelemetry { - Name = manifest.PackageName, Version = manifest.Version ?? string.Empty + Name = manifest.PackageName, + Version = manifest.Version ?? string.Empty, }); } diff --git a/src/Umbraco.Core/Templates/UmbracoComponentRenderer.cs b/src/Umbraco.Core/Templates/UmbracoComponentRenderer.cs index 3d7926878e00..e419bd5be328 100644 --- a/src/Umbraco.Core/Templates/UmbracoComponentRenderer.cs +++ b/src/Umbraco.Core/Templates/UmbracoComponentRenderer.cs @@ -22,8 +22,7 @@ public class UmbracoComponentRenderer : IUmbracoComponentRenderer /// /// Initializes a new instance of the class. /// - public UmbracoComponentRenderer(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, - ITemplateRenderer templateRenderer) + public UmbracoComponentRenderer(IUmbracoContextAccessor umbracoContextAccessor, IMacroRenderer macroRenderer, ITemplateRenderer templateRenderer) { _umbracoContextAccessor = umbracoContextAccessor; _macroRenderer = macroRenderer; @@ -57,8 +56,7 @@ public async Task RenderMacroAsync(int contentId, string ali await RenderMacroAsync(contentId, alias, parameters.ToDictionary()); /// - public async Task RenderMacroAsync(int contentId, string alias, - IDictionary? parameters) + public async Task RenderMacroAsync(int contentId, string alias, IDictionary? parameters) { if (contentId == default) { @@ -66,7 +64,7 @@ public async Task RenderMacroAsync(int contentId, string ali } IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - IPublishedContent content = umbracoContext.Content?.GetById(contentId); + IPublishedContent? content = umbracoContext.Content?.GetById(contentId); if (content == null) { @@ -77,8 +75,7 @@ public async Task RenderMacroAsync(int contentId, string ali } /// - public async Task RenderMacroForContent(IPublishedContent content, string alias, - IDictionary? parameters) + public async Task RenderMacroForContent(IPublishedContent content, string alias, IDictionary? parameters) { if (content == null) { @@ -91,8 +88,7 @@ public async Task RenderMacroForContent(IPublishedContent co /// /// Renders the macro with the specified alias, passing in the specified parameters. /// - private async Task RenderMacroAsync(IPublishedContent content, string alias, - IDictionary? parameters) + private async Task RenderMacroAsync(IPublishedContent content, string alias, IDictionary? parameters) { if (content == null) { diff --git a/src/Umbraco.Core/Tour/TourFilterCollection.cs b/src/Umbraco.Core/Tour/TourFilterCollection.cs index 0803222dfac4..44905f912747 100644 --- a/src/Umbraco.Core/Tour/TourFilterCollection.cs +++ b/src/Umbraco.Core/Tour/TourFilterCollection.cs @@ -7,7 +7,8 @@ namespace Umbraco.Cms.Core.Tour; /// public class TourFilterCollection : BuilderCollectionBase { - public TourFilterCollection(Func> items) : base(items) + public TourFilterCollection(Func> items) + : base(items) { } } diff --git a/src/Umbraco.Core/Tour/TourFilterCollectionBuilder.cs b/src/Umbraco.Core/Tour/TourFilterCollectionBuilder.cs index 437c20a2dfec..b39bcede465e 100644 --- a/src/Umbraco.Core/Tour/TourFilterCollectionBuilder.cs +++ b/src/Umbraco.Core/Tour/TourFilterCollectionBuilder.cs @@ -1,4 +1,4 @@ -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using Umbraco.Cms.Core.Composing; using Umbraco.Extensions; @@ -12,15 +12,15 @@ public class TourFilterCollectionBuilder : CollectionBuilderBase _instances = new(); - /// - protected override IEnumerable CreateItems(IServiceProvider factory) => - base.CreateItems(factory).Concat(_instances); - /// /// Adds a filter instance. /// public void AddFilter(BackOfficeTourFilter filter) => _instances.Add(filter); + /// + protected override IEnumerable CreateItems(IServiceProvider factory) => + base.CreateItems(factory).Concat(_instances); + /// /// Removes a filter instance. /// diff --git a/src/Umbraco.Core/Trees/MenuItemCollection.cs b/src/Umbraco.Core/Trees/MenuItemCollection.cs index ea1c6f196b61..aaace2cbd32b 100644 --- a/src/Umbraco.Core/Trees/MenuItemCollection.cs +++ b/src/Umbraco.Core/Trees/MenuItemCollection.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Actions; using Umbraco.Cms.Core.Models.Trees; diff --git a/src/Umbraco.Core/Trees/MenuItemList.cs b/src/Umbraco.Core/Trees/MenuItemList.cs index ade35f632bb1..a4cb1899e367 100644 --- a/src/Umbraco.Core/Trees/MenuItemList.cs +++ b/src/Umbraco.Core/Trees/MenuItemList.cs @@ -34,7 +34,7 @@ public MenuItemList(ActionCollection actionCollection, IEnumerable ite public MenuItem? Add(ILocalizedTextService textService, bool hasSeparator = false, bool opensDialog = false) where T : IAction { - MenuItem item = CreateMenuItem(textService, hasSeparator, opensDialog); + MenuItem? item = CreateMenuItem(textService, hasSeparator, opensDialog); if (item != null) { Add(item); @@ -44,11 +44,10 @@ public MenuItemList(ActionCollection actionCollection, IEnumerable ite return null; } - private MenuItem? CreateMenuItem(ILocalizedTextService textService, bool hasSeparator = false, - bool opensDialog = false) + private MenuItem? CreateMenuItem(ILocalizedTextService textService, bool hasSeparator = false, bool opensDialog = false) where T : IAction { - T item = _actionCollection.GetAction(); + T? item = _actionCollection.GetAction(); if (item == null) { return null; @@ -59,7 +58,9 @@ public MenuItemList(ActionCollection actionCollection, IEnumerable ite var menuItem = new MenuItem(item, textService.Localize("actions", item.Alias)) { - SeparatorBefore = hasSeparator, OpensDialog = opensDialog, TextDescription = textDescription + SeparatorBefore = hasSeparator, + OpensDialog = opensDialog, + TextDescription = textDescription, }; return menuItem; diff --git a/src/Umbraco.Core/Trees/SearchableApplicationTree.cs b/src/Umbraco.Core/Trees/SearchableApplicationTree.cs index aa8f31112a4a..44b0a896acf1 100644 --- a/src/Umbraco.Core/Trees/SearchableApplicationTree.cs +++ b/src/Umbraco.Core/Trees/SearchableApplicationTree.cs @@ -2,8 +2,7 @@ namespace Umbraco.Cms.Core.Trees; public class SearchableApplicationTree { - public SearchableApplicationTree(string appAlias, string treeAlias, int sortOrder, string formatterService, - string formatterMethod, ISearchableTree searchableTree) + public SearchableApplicationTree(string appAlias, string treeAlias, int sortOrder, string formatterService, string formatterMethod, ISearchableTree searchableTree) { AppAlias = appAlias; TreeAlias = treeAlias; @@ -14,9 +13,14 @@ public SearchableApplicationTree(string appAlias, string treeAlias, int sortOrde } public string AppAlias { get; } + public string TreeAlias { get; } + public int SortOrder { get; } + public string FormatterService { get; } + public string FormatterMethod { get; } + public ISearchableTree SearchableTree { get; } } diff --git a/src/Umbraco.Core/Trees/SearchableTreeAttribute.cs b/src/Umbraco.Core/Trees/SearchableTreeAttribute.cs index a21096b1f496..f3a92fe82f0b 100644 --- a/src/Umbraco.Core/Trees/SearchableTreeAttribute.cs +++ b/src/Umbraco.Core/Trees/SearchableTreeAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Trees; +namespace Umbraco.Cms.Core.Trees; [AttributeUsage(AttributeTargets.Class)] public sealed class SearchableTreeAttribute : Attribute @@ -46,7 +46,8 @@ public SearchableTreeAttribute(string serviceName, string methodName, int sortOr if (string.IsNullOrWhiteSpace(serviceName)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(serviceName)); } diff --git a/src/Umbraco.Core/Trees/SearchableTreeCollection.cs b/src/Umbraco.Core/Trees/SearchableTreeCollection.cs index e72548bc9c02..fdf2c8124b07 100644 --- a/src/Umbraco.Core/Trees/SearchableTreeCollection.cs +++ b/src/Umbraco.Core/Trees/SearchableTreeCollection.cs @@ -25,10 +25,10 @@ private Dictionary CreateDictionary(ITreeServ ISearchableTree[] searchableTrees = this.ToArray(); foreach (Tree appTree in appTrees) { - ISearchableTree found = searchableTrees.FirstOrDefault(x => x.TreeAlias.InvariantEquals(appTree.TreeAlias)); + ISearchableTree? found = searchableTrees.FirstOrDefault(x => x.TreeAlias.InvariantEquals(appTree.TreeAlias)); if (found != null) { - SearchableTreeAttribute searchableTreeAttribute = + SearchableTreeAttribute? searchableTreeAttribute = found.GetType().GetCustomAttribute(false); dictionary[found.TreeAlias] = new SearchableApplicationTree( appTree.SectionAlias, @@ -36,8 +36,7 @@ private Dictionary CreateDictionary(ITreeServ searchableTreeAttribute?.SortOrder ?? SearchableTreeAttribute.DefaultSortOrder, searchableTreeAttribute?.ServiceName ?? string.Empty, searchableTreeAttribute?.MethodName ?? string.Empty, - found - ); + found); } } diff --git a/src/Umbraco.Core/Trees/SearchableTreeCollectionBuilder.cs b/src/Umbraco.Core/Trees/SearchableTreeCollectionBuilder.cs index 54133fbeacf5..372866ba68a0 100644 --- a/src/Umbraco.Core/Trees/SearchableTreeCollectionBuilder.cs +++ b/src/Umbraco.Core/Trees/SearchableTreeCollectionBuilder.cs @@ -8,6 +8,6 @@ public class SearchableTreeCollectionBuilder : LazyCollectionBuilderBase this; - //per request because generally an instance of ISearchableTree is a controller + // per request because generally an instance of ISearchableTree is a controller protected override ServiceLifetime CollectionLifetime => ServiceLifetime.Scoped; } diff --git a/src/Umbraco.Core/Trees/Tree.cs b/src/Umbraco.Core/Trees/Tree.cs index 711e54ef45b5..47ee0b234b6a 100644 --- a/src/Umbraco.Core/Trees/Tree.cs +++ b/src/Umbraco.Core/Trees/Tree.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.Diagnostics; @@ -10,8 +10,7 @@ namespace Umbraco.Cms.Core.Trees; [DebuggerDisplay("Tree - {SectionAlias}/{TreeAlias}")] public class Tree : ITree { - public Tree(int sortOrder, string applicationAlias, string? group, string alias, string? title, TreeUse use, - Type treeControllerType, bool isSingleNodeTree) + public Tree(int sortOrder, string applicationAlias, string? group, string alias, string? title, TreeUse use, Type treeControllerType, bool isSingleNodeTree) { SortOrder = sortOrder; SectionAlias = applicationAlias ?? throw new ArgumentNullException(nameof(applicationAlias)); diff --git a/src/Umbraco.Core/Trees/TreeCollection.cs b/src/Umbraco.Core/Trees/TreeCollection.cs index 0328f9001bf6..fa6283753ad6 100644 --- a/src/Umbraco.Core/Trees/TreeCollection.cs +++ b/src/Umbraco.Core/Trees/TreeCollection.cs @@ -7,7 +7,8 @@ namespace Umbraco.Cms.Core.Trees; /// public class TreeCollection : BuilderCollectionBase { - public TreeCollection(Func> items) : base(items) + public TreeCollection(Func> items) + : base(items) { } } diff --git a/src/Umbraco.Core/Trees/TreeNode.cs b/src/Umbraco.Core/Trees/TreeNode.cs index bd9913616bff..dde66bd3a3c5 100644 --- a/src/Umbraco.Core/Trees/TreeNode.cs +++ b/src/Umbraco.Core/Trees/TreeNode.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Extensions; @@ -29,7 +29,8 @@ public TreeNode(string nodeId, string? parentId, string? getChildNodesUrl, strin if (string.IsNullOrWhiteSpace(nodeId)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(nodeId)); } @@ -38,7 +39,8 @@ public TreeNode(string nodeId, string? parentId, string? getChildNodesUrl, strin ChildNodesUrl = getChildNodesUrl; MenuUrl = menuUrl; CssClasses = new List(); - //default + + // default Icon = "icon-folder-close"; Path = "-1"; } @@ -97,8 +99,7 @@ public bool IconIsClass return false; } - - //if it starts with a '.' or doesn't contain a '.' at all then it is a class + // if it starts with a '.' or doesn't contain a '.' at all then it is a class return Icon.StartsWith(".") || Icon.Contains(".") == false; } } @@ -109,7 +110,7 @@ public bool IconIsClass [DataMember(Name = "iconFilePath")] public string IconFilePath => string.Empty; - //TODO Figure out how to do this, without the model has to know a bout services and config. + // TODO Figure out how to do this, without the model has to know a bout services and config. // // if (IconIsClass) // return string.Empty; @@ -120,6 +121,7 @@ public bool IconIsClass // // //legacy icon path // return string.Format("{0}images/umbraco/{1}", Current.Configs.Global().Path.EnsureEndsWith("/"), Icon); + /// /// A list of additional/custom css classes to assign to the node /// diff --git a/src/Umbraco.Core/Trees/TreeNodeCollection.cs b/src/Umbraco.Core/Trees/TreeNodeCollection.cs index dcd7c67908eb..b76fcc41ce8e 100644 --- a/src/Umbraco.Core/Trees/TreeNodeCollection.cs +++ b/src/Umbraco.Core/Trees/TreeNodeCollection.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Umbraco.Cms.Core.Trees; diff --git a/src/Umbraco.Core/Trees/TreeNodeExtensions.cs b/src/Umbraco.Core/Trees/TreeNodeExtensions.cs index 1de63ebdf47a..7fdc8ef480ed 100644 --- a/src/Umbraco.Core/Trees/TreeNodeExtensions.cs +++ b/src/Umbraco.Core/Trees/TreeNodeExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.Trees; @@ -9,14 +9,6 @@ public static class TreeNodeExtensions { internal const string LegacyJsCallbackKey = "jsClickCallback"; - /// - /// Legacy tree node's assign a JS method callback for when an item is clicked, this method facilitates that. - /// - /// - /// - internal static void AssignLegacyJsCallback(this TreeNode treeNode, string jsCallback) => - treeNode.AdditionalData[LegacyJsCallbackKey] = jsCallback; - /// /// Sets the node style to show that it is a container type /// @@ -29,6 +21,14 @@ public static void SetContainerStyle(this TreeNode treeNode) } } + /// + /// Legacy tree node's assign a JS method callback for when an item is clicked, this method facilitates that. + /// + /// + /// + internal static void AssignLegacyJsCallback(this TreeNode treeNode, string jsCallback) => + treeNode.AdditionalData[LegacyJsCallbackKey] = jsCallback; + /// /// Sets the node style to show that it is currently protected publicly /// diff --git a/src/Umbraco.Core/Trees/TreeUse.cs b/src/Umbraco.Core/Trees/TreeUse.cs index 65f8729c3840..ff06bc1dead6 100644 --- a/src/Umbraco.Core/Trees/TreeUse.cs +++ b/src/Umbraco.Core/Trees/TreeUse.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Trees; +namespace Umbraco.Cms.Core.Trees; /// /// Defines tree uses. @@ -19,5 +19,5 @@ public enum TreeUse /// /// The tree is used as a dialog. /// - Dialog = 2 + Dialog = 2, } diff --git a/src/Umbraco.Core/Udi.cs b/src/Umbraco.Core/Udi.cs index 857c95b99f4f..bc6c1ab6ac34 100644 --- a/src/Umbraco.Core/Udi.cs +++ b/src/Umbraco.Core/Udi.cs @@ -32,7 +32,6 @@ protected Udi(Uri uriValue) public Uri UriValue { get; } - /// /// Gets the entity type part of the identifier. /// @@ -44,14 +43,20 @@ protected Udi(Uri uriValue) /// A root Udi points to the "root of all things" for a given entity type, e.g. the content tree root. public abstract bool IsRoot { get; } - public int CompareTo(Udi? other) => string.Compare(UriValue.ToString(), other?.UriValue.ToString(), - StringComparison.OrdinalIgnoreCase); + public static bool operator ==(Udi? udi1, Udi? udi2) + { + if (ReferenceEquals(udi1, udi2)) + { + return true; + } - public override string ToString() => - // UriValue is created in the ctor and is never null - // use AbsoluteUri here and not ToString else it's not encoded! - UriValue.AbsoluteUri; + if (udi1 is null || udi2 is null) + { + return false; + } + return udi1.Equals(udi2); + } /// /// Creates a root Udi for an entity type. @@ -60,6 +65,14 @@ public override string ToString() => /// The root Udi for the entity type. public static Udi Create(string entityType) => UdiParser.GetRootUdi(entityType); + public int CompareTo(Udi? other) => string.Compare(UriValue.ToString(), other?.UriValue.ToString(), StringComparison.OrdinalIgnoreCase); + + public override string ToString() => + + // UriValue is created in the ctor and is never null + // use AbsoluteUri here and not ToString else it's not encoded! + UriValue.AbsoluteUri; + /// /// Creates a string Udi. /// @@ -80,7 +93,8 @@ public static Udi Create(string entityType, string id) if (udiType != UdiType.StringUdi) { - throw new InvalidOperationException(string.Format("Entity type \"{0}\" does not have string udis.", + throw new InvalidOperationException(string.Format( + "Entity type \"{0}\" does not have string udis.", entityType)); } @@ -102,7 +116,8 @@ public static Udi Create(string? entityType, Guid id) if (udiType != UdiType.GuidUdi) { - throw new InvalidOperationException(string.Format("Entity type \"{0}\" does not have guid udis.", + throw new InvalidOperationException(string.Format( + "Entity type \"{0}\" does not have guid udis.", entityType)); } @@ -118,7 +133,6 @@ public static Udi Create(Uri uri) { // if it's a know type go fast and use ctors // else fallback to parsing the string (and guess the type) - if (UdiParser.UdiTypes.TryGetValue(uri.Host, out UdiType udiType) == false) { throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", uri.Host), "uri"); @@ -168,20 +182,5 @@ public override bool Equals(object? obj) public override int GetHashCode() => UriValue.GetHashCode(); - public static bool operator ==(Udi? udi1, Udi? udi2) - { - if (ReferenceEquals(udi1, udi2)) - { - return true; - } - - if ((object?)udi1 == null || (object?)udi2 == null) - { - return false; - } - - return udi1.Equals(udi2); - } - public static bool operator !=(Udi? udi1, Udi? udi2) => udi1 == udi2 == false; } diff --git a/src/Umbraco.Core/UdiDefinitionAttribute.cs b/src/Umbraco.Core/UdiDefinitionAttribute.cs index b9e7c865fdbc..fe96909f78b5 100644 --- a/src/Umbraco.Core/UdiDefinitionAttribute.cs +++ b/src/Umbraco.Core/UdiDefinitionAttribute.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] public sealed class UdiDefinitionAttribute : Attribute diff --git a/src/Umbraco.Core/UdiParser.cs b/src/Umbraco.Core/UdiParser.cs index 966fdbe44ce0..30448e1b4543 100644 --- a/src/Umbraco.Core/UdiParser.cs +++ b/src/Umbraco.Core/UdiParser.cs @@ -9,6 +9,7 @@ public sealed class UdiParser private static readonly ConcurrentDictionary RootUdis = new(); static UdiParser() => + // initialize with known (built-in) Udi types // we will add scanned types later on UdiTypes = new ConcurrentDictionary(GetKnownUdiTypes()); @@ -28,7 +29,7 @@ static UdiParser() => /// An Udi instance that contains the value that was parsed. public static Udi Parse(string s) { - ParseInternal(s, false, false, out Udi udi); + ParseInternal(s, false, false, out Udi? udi); return udi!; } @@ -51,7 +52,7 @@ public static Udi Parse(string s) /// public static Udi Parse(string s, bool knownTypes) { - ParseInternal(s, false, knownTypes, out Udi udi); + ParseInternal(s, false, knownTypes, out Udi? udi); return udi!; } @@ -72,7 +73,7 @@ public static Udi Parse(string s, bool knownTypes) public static bool TryParse(string? s, [MaybeNullWhen(false)] out T udi) where T : Udi? { - var result = ParseInternal(s, true, false, out Udi parsed); + var result = ParseInternal(s, true, false, out Udi? parsed); if (result && parsed is T) { udi = (T)parsed; @@ -104,11 +105,31 @@ public static bool TryParse(string? s, [MaybeNullWhen(false)] out T udi) public static bool TryParse(string? s, bool knownTypes, [MaybeNullWhen(false)] out Udi udi) => ParseInternal(s, true, knownTypes, out udi); + /// + /// Registers a custom entity type. + /// + /// + /// + public static void RegisterUdiType(string entityType, UdiType udiType) => UdiTypes.TryAdd(entityType, udiType); + + internal static Udi GetRootUdi(string entityType) => + RootUdis.GetOrAdd(entityType, x => + { + if (UdiTypes.TryGetValue(x, out UdiType udiType) == false) + { + throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType)); + } + + return udiType == UdiType.StringUdi + ? new StringUdi(entityType, string.Empty) + : new GuidUdi(entityType, Guid.Empty); + }); + private static bool ParseInternal(string? s, bool tryParse, bool knownTypes, [MaybeNullWhen(false)] out Udi udi) { udi = null; if (Uri.IsWellFormedUriString(s, UriKind.Absolute) == false - || Uri.TryCreate(s, UriKind.Absolute, out Uri uri) == false) + || Uri.TryCreate(s, UriKind.Absolute, out Uri? uri) == false) { if (tryParse) { @@ -175,61 +196,40 @@ private static bool ParseInternal(string? s, bool tryParse, bool knownTypes, [Ma throw new InvalidOperationException(string.Format("Invalid udi type \"{0}\".", udiType)); } - internal static Udi GetRootUdi(string entityType) => - RootUdis.GetOrAdd(entityType, x => - { - if (UdiTypes.TryGetValue(x, out UdiType udiType) == false) - { - throw new ArgumentException(string.Format("Unknown entity type \"{0}\".", entityType)); - } - - return udiType == UdiType.StringUdi - ? new StringUdi(entityType, string.Empty) - : new GuidUdi(entityType, Guid.Empty); - }); - - - /// - /// Registers a custom entity type. - /// - /// - /// - public static void RegisterUdiType(string entityType, UdiType udiType) => UdiTypes.TryAdd(entityType, udiType); - public static Dictionary GetKnownUdiTypes() => new() { - {Constants.UdiEntityType.Unknown, UdiType.Unknown}, - {Constants.UdiEntityType.AnyGuid, UdiType.GuidUdi}, - {Constants.UdiEntityType.Element, UdiType.GuidUdi}, - {Constants.UdiEntityType.Document, UdiType.GuidUdi}, - {Constants.UdiEntityType.DocumentBlueprint, UdiType.GuidUdi}, - {Constants.UdiEntityType.Media, UdiType.GuidUdi}, - {Constants.UdiEntityType.Member, UdiType.GuidUdi}, - {Constants.UdiEntityType.DictionaryItem, UdiType.GuidUdi}, - {Constants.UdiEntityType.Macro, UdiType.GuidUdi}, - {Constants.UdiEntityType.Template, UdiType.GuidUdi}, - {Constants.UdiEntityType.DocumentType, UdiType.GuidUdi}, - {Constants.UdiEntityType.DocumentTypeContainer, UdiType.GuidUdi}, - {Constants.UdiEntityType.DocumentTypeBluePrints, UdiType.GuidUdi}, - {Constants.UdiEntityType.MediaType, UdiType.GuidUdi}, - {Constants.UdiEntityType.MediaTypeContainer, UdiType.GuidUdi}, - {Constants.UdiEntityType.DataType, UdiType.GuidUdi}, - {Constants.UdiEntityType.DataTypeContainer, UdiType.GuidUdi}, - {Constants.UdiEntityType.MemberType, UdiType.GuidUdi}, - {Constants.UdiEntityType.MemberGroup, UdiType.GuidUdi}, - {Constants.UdiEntityType.RelationType, UdiType.GuidUdi}, - {Constants.UdiEntityType.FormsForm, UdiType.GuidUdi}, - {Constants.UdiEntityType.FormsPreValue, UdiType.GuidUdi}, - {Constants.UdiEntityType.FormsDataSource, UdiType.GuidUdi}, - {Constants.UdiEntityType.AnyString, UdiType.StringUdi}, - {Constants.UdiEntityType.Language, UdiType.StringUdi}, - {Constants.UdiEntityType.MacroScript, UdiType.StringUdi}, - {Constants.UdiEntityType.MediaFile, UdiType.StringUdi}, - {Constants.UdiEntityType.TemplateFile, UdiType.StringUdi}, - {Constants.UdiEntityType.Script, UdiType.StringUdi}, - {Constants.UdiEntityType.PartialView, UdiType.StringUdi}, - {Constants.UdiEntityType.PartialViewMacro, UdiType.StringUdi}, - {Constants.UdiEntityType.Stylesheet, UdiType.StringUdi} + { Constants.UdiEntityType.Unknown, UdiType.Unknown }, + { Constants.UdiEntityType.AnyGuid, UdiType.GuidUdi }, + { Constants.UdiEntityType.Element, UdiType.GuidUdi }, + { Constants.UdiEntityType.Document, UdiType.GuidUdi }, + { Constants.UdiEntityType.DocumentBlueprint, UdiType.GuidUdi }, + { Constants.UdiEntityType.Media, UdiType.GuidUdi }, + { Constants.UdiEntityType.Member, UdiType.GuidUdi }, + { Constants.UdiEntityType.DictionaryItem, UdiType.GuidUdi }, + { Constants.UdiEntityType.Macro, UdiType.GuidUdi }, + { Constants.UdiEntityType.Template, UdiType.GuidUdi }, + { Constants.UdiEntityType.DocumentType, UdiType.GuidUdi }, + { Constants.UdiEntityType.DocumentTypeContainer, UdiType.GuidUdi }, + { Constants.UdiEntityType.DocumentTypeBluePrints, UdiType.GuidUdi }, + { Constants.UdiEntityType.MediaType, UdiType.GuidUdi }, + { Constants.UdiEntityType.MediaTypeContainer, UdiType.GuidUdi }, + { Constants.UdiEntityType.DataType, UdiType.GuidUdi }, + { Constants.UdiEntityType.DataTypeContainer, UdiType.GuidUdi }, + { Constants.UdiEntityType.MemberType, UdiType.GuidUdi }, + { Constants.UdiEntityType.MemberGroup, UdiType.GuidUdi }, + { Constants.UdiEntityType.RelationType, UdiType.GuidUdi }, + { Constants.UdiEntityType.FormsForm, UdiType.GuidUdi }, + { Constants.UdiEntityType.FormsPreValue, UdiType.GuidUdi }, + { Constants.UdiEntityType.FormsDataSource, UdiType.GuidUdi }, + { Constants.UdiEntityType.AnyString, UdiType.StringUdi }, + { Constants.UdiEntityType.Language, UdiType.StringUdi }, + { Constants.UdiEntityType.MacroScript, UdiType.StringUdi }, + { Constants.UdiEntityType.MediaFile, UdiType.StringUdi }, + { Constants.UdiEntityType.TemplateFile, UdiType.StringUdi }, + { Constants.UdiEntityType.Script, UdiType.StringUdi }, + { Constants.UdiEntityType.PartialView, UdiType.StringUdi }, + { Constants.UdiEntityType.PartialViewMacro, UdiType.StringUdi }, + { Constants.UdiEntityType.Stylesheet, UdiType.StringUdi }, }; } diff --git a/src/Umbraco.Core/UdiParserServiceConnectors.cs b/src/Umbraco.Core/UdiParserServiceConnectors.cs index f6dfd2df7570..4c307435dec9 100644 --- a/src/Umbraco.Core/UdiParserServiceConnectors.cs +++ b/src/Umbraco.Core/UdiParserServiceConnectors.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Deploy; using Umbraco.Extensions; @@ -6,14 +6,14 @@ namespace Umbraco.Cms.Core; public static class UdiParserServiceConnectors { + private static readonly object ScanLocker = new(); + // notes - see U4-10409 // if this class is used during application pre-start it cannot scans the assemblies, // this is addressed by lazily-scanning, with the following caveats: // - parsing a root udi still requires a scan and therefore still breaks // - parsing an invalid udi ("umb://should-be-guid/") corrupts KnowUdiTypes - private static volatile bool _scanned; - private static readonly object ScanLocker = new(); /// /// Scan for deploy in assemblies for known UDI types. diff --git a/src/Umbraco.Core/UdiRange.cs b/src/Umbraco.Core/UdiRange.cs index 263b39acc615..5d98664a3e6f 100644 --- a/src/Umbraco.Core/UdiRange.cs +++ b/src/Umbraco.Core/UdiRange.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; /// /// Represents a range. @@ -57,18 +57,33 @@ public UdiRange(Udi udi, string selector = Constants.DeploySelector.This) /// public string EntityType => Udi.EntityType; - public static UdiRange Parse(string s) + public static bool operator ==(UdiRange? range1, UdiRange? range2) { - Uri? uri; + if (ReferenceEquals(range1, range2)) + { + return true; + } + + if (range1 is null || range2 is null) + { + return false; + } + + return range1.Equals(range2); + } + public static bool operator !=(UdiRange range1, UdiRange range2) => !(range1 == range2); + + public static UdiRange Parse(string s) + { if (Uri.IsWellFormedUriString(s, UriKind.Absolute) == false - || Uri.TryCreate(s, UriKind.Absolute, out uri) == false) + || Uri.TryCreate(s, UriKind.Absolute, out Uri? uri) == false) { - //if (tryParse) return false; + // if (tryParse) return false; throw new FormatException(string.Format("String \"{0}\" is not a valid udi range.", s)); } - Uri udiUri = uri.Query == string.Empty ? uri : new UriBuilder(uri) {Query = string.Empty}.Uri; + Uri udiUri = uri.Query == string.Empty ? uri : new UriBuilder(uri) { Query = string.Empty }.Uri; return new UdiRange(Udi.Create(udiUri), uri.Query.TrimStart(Constants.CharArrays.QuestionMark)); } @@ -78,21 +93,4 @@ public override bool Equals(object? obj) => obj is UdiRange other && GetType() == other.GetType() && _uriValue == other._uriValue; public override int GetHashCode() => _uriValue.GetHashCode(); - - public static bool operator ==(UdiRange range1, UdiRange range2) - { - if (ReferenceEquals(range1, range2)) - { - return true; - } - - if ((object)range1 == null || (object)range2 == null) - { - return false; - } - - return range1.Equals(range2); - } - - public static bool operator !=(UdiRange range1, UdiRange range2) => !(range1 == range2); } diff --git a/src/Umbraco.Core/UdiType.cs b/src/Umbraco.Core/UdiType.cs index d71463a55402..e5ebd2f7ce04 100644 --- a/src/Umbraco.Core/UdiType.cs +++ b/src/Umbraco.Core/UdiType.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; /// /// Defines Udi types. @@ -7,5 +7,5 @@ public enum UdiType { Unknown, GuidUdi, - StringUdi + StringUdi, } diff --git a/src/Umbraco.Core/UdiTypeConverter.cs b/src/Umbraco.Core/UdiTypeConverter.cs index a1d7e3c7345f..2a52a1e09361 100644 --- a/src/Umbraco.Core/UdiTypeConverter.cs +++ b/src/Umbraco.Core/UdiTypeConverter.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Globalization; namespace Umbraco.Cms.Core; @@ -25,8 +25,7 @@ public override bool CanConvertFrom(ITypeDescriptorContext? context, Type source { if (value is string) { - Udi? udi; - if (UdiParser.TryParse((string)value, out udi)) + if (UdiParser.TryParse((string)value, out Udi? udi)) { return udi; } diff --git a/src/Umbraco.Core/UmbracoApiControllerTypeCollection.cs b/src/Umbraco.Core/UmbracoApiControllerTypeCollection.cs index 4643558e1365..afd6183b548a 100644 --- a/src/Umbraco.Core/UmbracoApiControllerTypeCollection.cs +++ b/src/Umbraco.Core/UmbracoApiControllerTypeCollection.cs @@ -4,7 +4,8 @@ namespace Umbraco.Cms.Core; public class UmbracoApiControllerTypeCollection : BuilderCollectionBase { - public UmbracoApiControllerTypeCollection(Func> items) : base(items) + public UmbracoApiControllerTypeCollection(Func> items) + : base(items) { } } diff --git a/src/Umbraco.Core/UnknownTypeUdi.cs b/src/Umbraco.Core/UnknownTypeUdi.cs index 5cee5e3f47ba..3c38418f0e84 100644 --- a/src/Umbraco.Core/UnknownTypeUdi.cs +++ b/src/Umbraco.Core/UnknownTypeUdi.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; public class UnknownTypeUdi : Udi { diff --git a/src/Umbraco.Core/UpgradeResult.cs b/src/Umbraco.Core/UpgradeResult.cs index aadd83ffd07c..7f27e503fe42 100644 --- a/src/Umbraco.Core/UpgradeResult.cs +++ b/src/Umbraco.Core/UpgradeResult.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core; +namespace Umbraco.Cms.Core; public class UpgradeResult { @@ -10,6 +10,8 @@ public UpgradeResult(string upgradeType, string comment, string upgradeUrl) } public string UpgradeType { get; } + public string Comment { get; } + public string UpgradeUrl { get; } } diff --git a/src/Umbraco.Core/UriUtilityCore.cs b/src/Umbraco.Core/UriUtilityCore.cs index ebb837b1fb1a..8d541fca7852 100644 --- a/src/Umbraco.Core/UriUtilityCore.cs +++ b/src/Umbraco.Core/UriUtilityCore.cs @@ -1,4 +1,4 @@ -using Umbraco.Extensions; +using Umbraco.Extensions; namespace Umbraco.Cms.Core; @@ -19,12 +19,12 @@ public static string EndPathWithSlash(string uri) var pos2 = Math.Max(0, uri.IndexOf('#')); var pos = Math.Min(pos1, pos2); - var path = pos > 0 ? uri.Substring(0, pos) : uri; + var path = pos > 0 ? uri[..pos] : uri; path = path.EnsureEndsWith('/'); if (pos > 0) { - path += uri.Substring(pos); + path += uri[pos..]; } return path; @@ -36,12 +36,12 @@ public static string TrimPathEndSlash(string uri) var pos2 = Math.Max(0, uri.IndexOf('#')); var pos = Math.Min(pos1, pos2); - var path = pos > 0 ? uri.Substring(0, pos) : uri; + var path = pos > 0 ? uri[..pos] : uri; path = path.TrimEnd(Constants.CharArrays.ForwardSlash); if (pos > 0) { - path += uri.Substring(pos); + path += uri[pos..]; } return path; diff --git a/src/Umbraco.Core/Web/Mvc/PluginControllerMetadata.cs b/src/Umbraco.Core/Web/Mvc/PluginControllerMetadata.cs index ca533e1832ac..5f484c8fe027 100644 --- a/src/Umbraco.Core/Web/Mvc/PluginControllerMetadata.cs +++ b/src/Umbraco.Core/Web/Mvc/PluginControllerMetadata.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Cms.Core.Web.Mvc; +namespace Umbraco.Cms.Core.Web.Mvc; /// /// Represents some metadata about the controller @@ -6,8 +6,11 @@ public class PluginControllerMetadata { public Type ControllerType { get; set; } = null!; + public string? ControllerName { get; set; } + public string? ControllerNamespace { get; set; } + public string? AreaName { get; set; } /// diff --git a/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs b/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs index 777e3689d757..bb5c186ca68a 100644 --- a/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs +++ b/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; namespace Umbraco.Cms.Core.Xml; @@ -30,7 +30,6 @@ public static string ParseXPathQuery( // previous tokens were: "$currentPage", "$ancestorOrSelf", "$parentPage" and I believe they were // allowed 'inline', not just at the beginning... whether or not we want to support that is up // for discussion. - if (xpathExpression == null) { throw new ArgumentNullException(nameof(xpathExpression)); @@ -38,7 +37,8 @@ public static string ParseXPathQuery( if (string.IsNullOrWhiteSpace(xpathExpression)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(xpathExpression)); } @@ -52,21 +52,20 @@ public static string ParseXPathQuery( throw new ArgumentNullException(nameof(publishedContentExists)); } - //no need to parse it + // no need to parse it if (xpathExpression.StartsWith("$") == false) { return xpathExpression; } - //get nearest published item + // get nearest published item Func?, int> getClosestPublishedAncestor = path => { if (path is not null) { foreach (var i in path) { - int idAsInt; - if (int.TryParse(i, NumberStyles.Integer, CultureInfo.InvariantCulture, out idAsInt)) + if (int.TryParse(i, NumberStyles.Integer, CultureInfo.InvariantCulture, out int idAsInt)) { var exists = publishedContentExists(int.Parse(i, CultureInfo.InvariantCulture)); if (exists) @@ -82,10 +81,10 @@ public static string ParseXPathQuery( const string rootXpath = "id({0})"; - //parseable items: + // parseable items: var vars = new Dictionary>(); - //These parameters must have a node id context + // These parameters must have a node id context if (nodeContextId.HasValue) { vars.Add("$current", q => @@ -96,8 +95,8 @@ public static string ParseXPathQuery( vars.Add("$parent", q => { - //remove the first item in the array if its the current node - //this happens when current is published, but we are looking for its parent specifically + // remove the first item in the array if its the current node + // this happens when current is published, but we are looking for its parent specifically var path = getPath(nodeContextId.Value)?.ToArray(); if (path?[0] == nodeContextId.ToString()) { @@ -108,11 +107,11 @@ public static string ParseXPathQuery( return q.Replace("$parent", string.Format(rootXpath, closestPublishedAncestorId)); }); - vars.Add("$site", q => { var closestPublishedAncestorId = getClosestPublishedAncestor(getPath(nodeContextId.Value)); - return q.Replace("$site", + return q.Replace( + "$site", string.Format(rootXpath, closestPublishedAncestorId) + "/ancestor-or-self::*[@level = 1]"); }); } @@ -121,7 +120,6 @@ public static string ParseXPathQuery( // the root is always "/root . Need to confirm with Per why this was string.Empty before! vars.Add("$root", q => q.Replace("$root", "/root")); - foreach (KeyValuePair> varible in vars) { if (xpathExpression.StartsWith(varible.Key)) diff --git a/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs b/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs index 0d067bf16e3b..af9ec53fedf6 100644 --- a/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs +++ b/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs @@ -5,7 +5,6 @@ // but by default nothing is written, unless some lines are un-commented in Debug(...) below. // // Beware! Diagnostics are extremely verbose and can overflow logging pretty easily. - #if DEBUG // define to enable diagnostics code #undef DEBUGNAVIGATOR @@ -52,7 +51,6 @@ public class NavigableNavigator : XPathNavigator // local names come from cached instances of INavigableContentType or // INavigableFieldType and are already unique. So... create a one nametable // because we need one, and share it amongst all clones. - private readonly XmlNameTable _nameTable; private readonly INavigableSource _source; private readonly int _lastAttributeIndex; // last index of attributes in the fields collection @@ -65,12 +63,12 @@ public class NavigableNavigator : XPathNavigator ///// ///// The content source. ///// The maximum depth. - //private NavigableNavigator(INavigableSource source, int maxDepth) - //{ + // private NavigableNavigator(INavigableSource source, int maxDepth) + // { // _source = source; // _lastAttributeIndex = source.LastAttributeIndex; // _maxDepth = maxDepth; - //} + // } /// /// Initializes a new instance of the class with a content source, @@ -81,7 +79,7 @@ public class NavigableNavigator : XPathNavigator /// The maximum depth. /// When no root content is supplied then the root of the source is used. public NavigableNavigator(INavigableSource source, int rootId = 0, int maxDepth = int.MaxValue) - //: this(source, maxDepth) + // : this(source, maxDepth) { _source = source; _lastAttributeIndex = source.LastAttributeIndex; @@ -89,7 +87,7 @@ public NavigableNavigator(INavigableSource source, int rootId = 0, int maxDepth _nameTable = new NameTable(); _lastAttributeIndex = source.LastAttributeIndex; - INavigableContent content = rootId <= 0 ? source.Root : source.Get(rootId); + INavigableContent? content = rootId <= 0 ? source.Root : source.Get(rootId); if (content == null) { throw new ArgumentException("Not the identifier of a content within the source.", nameof(rootId)); @@ -108,12 +106,12 @@ public NavigableNavigator(INavigableSource source, int rootId = 0, int maxDepth ///// The state. ///// The maximum depth. ///// Privately used for cloning a navigator. - //private NavigableNavigator(INavigableSource source, XmlNameTable nameTable, State state, int maxDepth) + // private NavigableNavigator(INavigableSource source, XmlNameTable nameTable, State state, int maxDepth) // : this(source, rootId: 0, maxDepth: maxDepth) - //{ + // { // _nameTable = nameTable; // _state = state; - //} + // } /// /// Initializes a new instance of the class as a clone. @@ -161,7 +159,6 @@ private static int GetUid() // that no calls to the method will be generated by the compiler. However, the method // does exist. Wrapping the method body with #if/endif ensures that no IL is generated // and so it's only an empty method. - [Conditional("DEBUGNAVIGATOR")] private void DebugEnter(string name) { @@ -266,8 +263,9 @@ void Debug(string format, params object[] args) private readonly ConcurrentDictionary _contents; private INavigableContent? SourceGet(int id) => + // original version, would keep creating INavigableContent objects - //return _source.Get(id); + // return _source.Get(id); // improved version, uses a cache, shared with clones _contents.GetOrAdd(id, x => _source.Get(x)); @@ -324,7 +322,7 @@ public XPathNavigator CloneWithNewRoot(string id, int maxDepth = int.MaxValue) } else { - INavigableContent content = SourceGet(id); + INavigableContent? content = SourceGet(id); if (content != null) { state = new State(content, null, null, 0, StatePosition.Root); @@ -595,7 +593,7 @@ private bool MoveToFirstChildElement() // children may contain IDs that does not correspond to some content in source // because children contains all child IDs including unpublished children - and // then if we're not previewing, the source will return null. - INavigableContent child = children.Select(id => SourceGet(id)).FirstOrDefault(c => c != null); + INavigableContent? child = children.Select(id => SourceGet(id)).FirstOrDefault(c => c != null); if (child != null) { InternalState.Position = StatePosition.Element; @@ -617,7 +615,6 @@ private bool MoveToFirstChildProperty() // - an XPathNavigator over a non-empty XML fragment // - a non-Xml-whitespace string // - null - var nav = valueForXPath as XPathNavigator; if (nav != null) { @@ -711,7 +708,7 @@ public override bool MoveToId(string id) } else { - INavigableContent content = SourceGet(contentId); + INavigableContent? content = SourceGet(contentId); if (content != null) { // walk up to the navigator's root - or the source's root @@ -727,8 +724,7 @@ public override bool MoveToId(string id) InternalState = new State(state.Content, null, null, 0, StatePosition.Element); while (content != null) { - InternalState = new State(content, InternalState, InternalState.Content?.ChildIds, - InternalState.Content?.ChildIds?.IndexOf(content.Id) ?? -1, StatePosition.Element); + InternalState = new State(content, InternalState, InternalState.Content?.ChildIds, InternalState.Content?.ChildIds?.IndexOf(content.Id) ?? -1, StatePosition.Element); content = s.Count == 0 ? null : s.Pop(); } @@ -768,7 +764,7 @@ public override bool MoveToNext() // Siblings may contain IDs that does not correspond to some content in source // because children contains all child IDs including unpublished children - and // then if we're not previewing, the source will return null. - INavigableContent node = SourceGet(InternalState.Siblings[++InternalState.SiblingIndex]); + INavigableContent? node = SourceGet(InternalState.Siblings[++InternalState.SiblingIndex]); if (node == null) { continue; @@ -834,7 +830,7 @@ public override bool MoveToPrevious() // children may contain IDs that does not correspond to some content in source // because children contains all child IDs including unpublished children - and // then if we're not previewing, the source will return null. - INavigableContent content = SourceGet(InternalState.Siblings[--InternalState.SiblingIndex]); + INavigableContent? content = SourceGet(InternalState.Siblings[--InternalState.SiblingIndex]); if (content == null) { continue; @@ -989,7 +985,7 @@ public override bool MoveToParent() private bool MoveToParentElement() { - State p = InternalState.Parent; + State? p = InternalState.Parent; if (p != null) { InternalState = p; @@ -1109,7 +1105,6 @@ public override string Value // - an XPathNavigator over a non-empty XML fragment // - a non-Xml-whitespace string // - null - var nav = valueForXPath as XPathNavigator; var s = valueForXPath as string; if (valueForXPath == null) @@ -1155,7 +1150,7 @@ public enum StatePosition Attribute, PropertyElement, PropertyText, - PropertyXml + PropertyXml, } // gets the state @@ -1180,8 +1175,7 @@ private State(StatePosition position) // initialize a new state // used for creating the very first state // and also when moving to a child element - public State(INavigableContent? content, State? parent, IList? siblings, int siblingIndex, - StatePosition position) + public State(INavigableContent? content, State? parent, IList? siblings, int siblingIndex, StatePosition position) : this(position) { Content = content; @@ -1209,11 +1203,10 @@ private State(State other, bool recurse = false) } // NielsK did - //Parent = other.Parent; + // Parent = other.Parent; // but that creates corrupted stacks of states when cloning // because clones share the parents : have to clone the whole // stack of states. Avoid recursion. - if (recurse) { return; diff --git a/src/Umbraco.Core/Xml/XPath/RenamedRootNavigator.cs b/src/Umbraco.Core/Xml/XPath/RenamedRootNavigator.cs index a2fc9e6b5285..1b710c8db54d 100644 --- a/src/Umbraco.Core/Xml/XPath/RenamedRootNavigator.cs +++ b/src/Umbraco.Core/Xml/XPath/RenamedRootNavigator.cs @@ -1,4 +1,4 @@ -using System.Xml; +using System.Xml; using System.Xml.XPath; namespace Umbraco.Cms.Core.Xml.XPath; @@ -23,7 +23,6 @@ public override string LocalName get { // local name without prefix - XPathNavigator nav = _navigator.Clone(); if (nav.MoveToParent() && nav.MoveToParent()) { @@ -39,7 +38,6 @@ public override string Name get { // qualified name with prefix - XPathNavigator nav = _navigator.Clone(); if (nav.MoveToParent() && nav.MoveToParent()) { @@ -48,7 +46,7 @@ public override string Name var name = _navigator.Name; var pos = name.IndexOf(':'); - return pos < 0 ? _rootName : name.Substring(0, pos + 1) + _rootName; + return pos < 0 ? _rootName : name[..(pos + 1)] + _rootName; } } diff --git a/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs b/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs index 5ca8d98a4482..44cda2c69183 100644 --- a/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs +++ b/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.Xml.XPath; @@ -18,8 +18,7 @@ public static class XPathNavigatorExtensions /// An XPath expression. /// A set of XPathVariables. /// An iterator over the nodes matching the specified expression. - public static XPathNodeIterator Select(this XPathNavigator navigator, string expression, - params XPathVariable[] variables) + public static XPathNodeIterator Select(this XPathNavigator navigator, string expression, params XPathVariable[] variables) { if (variables == null || variables.Length == 0 || variables[0] == null) { @@ -51,8 +50,7 @@ public static XPathNodeIterator Select(this XPathNavigator navigator, string exp /// An XPath expression. /// A set of XPathVariables. /// An iterator over the nodes matching the specified expression. - public static XPathNodeIterator Select(this XPathNavigator navigator, XPathExpression expression, - params XPathVariable[] variables) + public static XPathNodeIterator Select(this XPathNavigator navigator, XPathExpression expression, params XPathVariable[] variables) { if (variables == null || variables.Length == 0 || variables[0] == null) { diff --git a/src/Umbraco.Core/Xml/XPathVariable.cs b/src/Umbraco.Core/Xml/XPathVariable.cs index e370237d2f20..4c2d2d0f4e9a 100644 --- a/src/Umbraco.Core/Xml/XPathVariable.cs +++ b/src/Umbraco.Core/Xml/XPathVariable.cs @@ -1,4 +1,4 @@ -// source: mvpxml.codeplex.com +// source: mvpxml.codeplex.com namespace Umbraco.Cms.Core.Xml; diff --git a/src/Umbraco.Core/Xml/XmlHelper.cs b/src/Umbraco.Core/Xml/XmlHelper.cs index 856950e91a6e..ad97120c93dc 100644 --- a/src/Umbraco.Core/Xml/XmlHelper.cs +++ b/src/Umbraco.Core/Xml/XmlHelper.cs @@ -36,7 +36,8 @@ public static void SetAttribute(XmlDocument xml, XmlNode n, string name, string if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(name)); } @@ -112,10 +113,8 @@ public static bool TryCreateXPathDocumentFromPropertyValue(object value, out XPa // to be returned as a DynamicXml and element names such as "value-item" are // invalid and must be converted to "valueitem". But we don't have that sort of // problem here - and we don't need to bother with dashes nor dots, etc. - doc = null; - var xml = value as string; - if (xml == null) + if (value is not string xml) { return false; // no a string } @@ -138,12 +137,11 @@ public static bool TryCreateXPathDocumentFromPropertyValue(object value, out XPa XPathNavigator nav = doc!.CreateNavigator(); if (nav.MoveToFirstChild()) { - //SD: This used to do this but the razor macros and the entire razor macros section is gone, it was all legacy, it seems this method isn't even + // SD: This used to do this but the razor macros and the entire razor macros section is gone, it was all legacy, it seems this method isn't even // used apart from for tests so don't think this matters. In any case, we no longer check for this! - //var name = nav.LocalName; // must not match an excluded tag - //if (UmbracoConfig.For.UmbracoSettings().Scripting.NotDynamicXmlDocumentElements.All(x => x.Element.InvariantEquals(name) == false)) return true; - + // var name = nav.LocalName; // must not match an excluded tag + // if (UmbracoConfig.For.UmbracoSettings().Scripting.NotDynamicXmlDocumentElements.All(x => x.Element.InvariantEquals(name) == false)) return true; return true; } @@ -151,7 +149,6 @@ public static bool TryCreateXPathDocumentFromPropertyValue(object value, out XPa return false; } - /// /// Sorts the children of a parentNode. /// @@ -163,7 +160,7 @@ public static void SortNodes( string childNodesXPath, Func orderBy) { - XmlNode[] sortedChildNodes = parentNode.SelectNodes(childNodesXPath)?.Cast() + XmlNode[]? sortedChildNodes = parentNode.SelectNodes(childNodesXPath)?.Cast() .OrderBy(orderBy) .ToArray(); @@ -178,7 +175,6 @@ public static void SortNodes( } } - /// /// Sorts a single child node of a parentNode. /// @@ -198,7 +194,7 @@ public static bool SortNode( Func orderBy) { var nodeSortOrder = orderBy(node); - Tuple[] childNodesAndOrder = parentNode.SelectNodes(childNodesXPath)?.Cast() + Tuple[]? childNodesAndOrder = parentNode.SelectNodes(childNodesXPath)?.Cast() .Select(x => Tuple.Create(x, orderBy(x))).ToArray(); // only one node = node is in the right place already, obviously @@ -240,23 +236,23 @@ public static bool SortNode( return false; } - /// /// Opens a file as a XmlDocument. /// /// The relative file path. ie. /config/umbraco.config - /// + /// /// Returns a XmlDocument class public static XmlDocument OpenAsXmlDocument(string filePath, IHostingEnvironment hostingEnvironment) { using (var reader = new XmlTextReader(hostingEnvironment.MapPathContentRoot(filePath)) { - WhitespaceHandling = WhitespaceHandling.All + WhitespaceHandling = WhitespaceHandling.All, }) { var xmlDoc = new XmlDocument(); - //Load the file into the XmlDocument + + // Load the file into the XmlDocument xmlDoc.Load(reader); return xmlDoc; @@ -284,7 +280,8 @@ public static XmlAttribute AddAttribute(XmlDocument xd, string name, string valu if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(name)); } @@ -314,11 +311,12 @@ public static XmlNode AddTextNode(XmlDocument xd, string name, string value) if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(name)); } - XmlNode temp = xd.CreateNode(XmlNodeType.Element, name, ""); + XmlNode temp = xd.CreateNode(XmlNodeType.Element, name, string.Empty); temp.AppendChild(xd.CreateTextNode(value)); return temp; } @@ -350,11 +348,12 @@ public static XmlNode SetTextNode(XmlDocument xd, XmlNode parent, string name, s if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(name)); } - XmlNode child = parent.SelectSingleNode(name); + XmlNode? child = parent.SelectSingleNode(name); if (child != null) { child.InnerText = value; @@ -391,11 +390,12 @@ public static XmlNode SetInnerXmlNode(XmlDocument xd, XmlNode parent, string nam if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(name)); } - XmlNode child = parent.SelectSingleNode(name) ?? xd.CreateNode(XmlNodeType.Element, name, ""); + XmlNode child = parent.SelectSingleNode(name) ?? xd.CreateNode(XmlNodeType.Element, name, string.Empty); child.InnerXml = value; return child; } @@ -421,11 +421,12 @@ public static XmlNode AddCDataNode(XmlDocument xd, string name, string value) if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(name)); } - XmlNode temp = xd.CreateNode(XmlNodeType.Element, name, ""); + XmlNode temp = xd.CreateNode(XmlNodeType.Element, name, string.Empty); temp.AppendChild(xd.CreateCDataSection(value)); return temp; } @@ -457,11 +458,12 @@ public static XmlNode SetCDataNode(XmlDocument xd, XmlNode parent, string name, if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentException("Value can't be empty or consist only of white-space characters.", + throw new ArgumentException( + "Value can't be empty or consist only of white-space characters.", nameof(name)); } - XmlNode child = parent.SelectSingleNode(name); + XmlNode? child = parent.SelectSingleNode(name); if (child != null) { child.InnerXml = ""; @@ -514,10 +516,11 @@ public static bool CouldItBeXml(string? xml) public static Dictionary GetAttributesFromElement(string tag) { MatchCollection m = - Regex.Matches(tag, "(?\\S*)=\"(?[^\"]*)\"", - RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + Regex.Matches(tag, "(?\\S*)=\"(?[^\"]*)\"", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); + // fix for issue 14862: return lowercase attributes for case insensitive matching - var d = m.ToDictionary(attributeSet => attributeSet.Groups["attributeName"].Value.ToString().ToLower(), + var d = m.ToDictionary( + attributeSet => attributeSet.Groups["attributeName"].Value.ToString().ToLower(), attributeSet => attributeSet.Groups["attributeValue"].Value.ToString()); return d; } diff --git a/src/Umbraco.Core/Xml/XmlNamespaces.cs b/src/Umbraco.Core/Xml/XmlNamespaces.cs index a1fd06952184..55a23736ff43 100644 --- a/src/Umbraco.Core/Xml/XmlNamespaces.cs +++ b/src/Umbraco.Core/Xml/XmlNamespaces.cs @@ -1,4 +1,4 @@ -// source: mvpxml.codeplex.com +// source: mvpxml.codeplex.com namespace Umbraco.Cms.Core.Xml; diff --git a/src/Umbraco.Core/Xml/XmlNodeListFactory.cs b/src/Umbraco.Core/Xml/XmlNodeListFactory.cs index 9d3fca8c24a9..17c2f418438c 100644 --- a/src/Umbraco.Core/Xml/XmlNodeListFactory.cs +++ b/src/Umbraco.Core/Xml/XmlNodeListFactory.cs @@ -1,14 +1,15 @@ -using System.Collections; +using System.Collections; using System.Xml; using System.Xml.XPath; // source: mvpxml.codeplex.com - namespace Umbraco.Cms.Core.Xml; public class XmlNodeListFactory { - private XmlNodeListFactory() { } + private XmlNodeListFactory() + { + } #region Public members @@ -80,7 +81,6 @@ public override int Count return _nodes[index]; } - /// /// Reads the entire iterator. /// @@ -88,9 +88,8 @@ private void ReadToEnd() { while (_iterator is not null && _iterator.MoveNext()) { - var node = _iterator.Current as IHasXmlNode; // Check IHasXmlNode interface. - if (node == null) + if (_iterator.Current is not IHasXmlNode node) { throw new ArgumentException("IHasXmlNode is missing."); } @@ -111,9 +110,8 @@ private void ReadTo(int to) { if (_iterator is not null && _iterator.MoveNext()) { - var node = _iterator.Current as IHasXmlNode; // Check IHasXmlNode interface. - if (node == null) + if (_iterator.Current is not IHasXmlNode node) { throw new ArgumentException("IHasXmlNode is missing."); } @@ -137,11 +135,12 @@ private class XmlNodeListEnumerator : IEnumerator public XmlNodeListEnumerator(XmlNodeListIterator iterator) => _iterator = iterator; + object? IEnumerator.Current => _iterator[_position]; + #region IEnumerator Members void IEnumerator.Reset() => _position = -1; - bool IEnumerator.MoveNext() { _position++; @@ -157,8 +156,6 @@ bool IEnumerator.MoveNext() return true; } - object? IEnumerator.Current => _iterator[_position]; - #endregion } From bb8763eb7cba437eb4163ded8e3b8fab06952335 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle Date: Fri, 20 May 2022 08:55:19 +0200 Subject: [PATCH 004/117] Fix up missed warnings --- src/Umbraco.Core/Attempt.cs | 15 +- .../Cache/ContentTypeCacheRefresher.cs | 3 +- .../Cache/DomainCacheRefresher.cs | 2 +- .../Cache/LanguageCacheRefresher.cs | 22 +-- .../Cache/MemberGroupCacheRefresher.cs | 5 +- .../Collections/ObservableDictionary.cs | 13 +- src/Umbraco.Core/Composing/ComposerGraph.cs | 8 +- .../FindAssembliesWithReferencesTo.cs | 6 +- src/Umbraco.Core/Composing/ITypeFinder.cs | 3 +- .../Validation/ConfigurationValidatorBase.cs | 9 +- .../Validation/GlobalSettingsValidator.cs | 7 +- .../HealthChecksSettingsValidator.cs | 3 +- .../ModelsBuilderConfigExtensions.cs | 3 +- .../ContentAppFactoryCollectionBuilder.cs | 4 +- .../UmbracoBuilder.Composers.cs | 3 +- .../Deploy/ArtifactDeployState.cs | 3 +- .../Deploy/IDataTypeConfigurationConnector.cs | 7 +- .../Extensions/EnumerableExtensions.cs | 9 +- .../Handlers/AuditNotificationsHandler.cs | 91 ++++++--- .../Checks/Data/DatabaseIntegrityCheck.cs | 3 +- .../Checks/Security/BaseHttpHeaderCheck.cs | 3 +- .../Checks/Security/ClickJackingCheck.cs | 3 +- .../Checks/Security/XssProtectionCheck.cs | 3 +- .../IO/DefaultViewContentProvider.cs | 3 +- .../IO/IDefaultViewContentProvider.cs | 3 +- src/Umbraco.Core/Logging/IProfilingLogger.cs | 39 +++- src/Umbraco.Core/Logging/ProfilingLogger.cs | 75 +++++-- src/Umbraco.Core/Media/Exif/BitConverterEx.cs | 2 +- .../Media/Exif/ExifInterOperability.cs | 3 +- .../Media/Exif/ExifPropertyCollection.cs | 9 +- src/Umbraco.Core/Media/Exif/JFIFThumbnail.cs | 2 +- src/Umbraco.Core/Media/Exif/JPEGFile.cs | 184 ++++++++++-------- src/Umbraco.Core/Media/Exif/JPEGSection.cs | 3 +- src/Umbraco.Core/Media/Exif/MathEx.cs | 18 +- src/Umbraco.Core/Media/Exif/TIFFFile.cs | 7 +- src/Umbraco.Core/Models/AuditItem.cs | 3 +- src/Umbraco.Core/Models/ContentBase.cs | 22 ++- .../Models/ContentBaseExtensions.cs | 3 +- .../Models/ContentEditing/HistoryCleanup.cs | 6 +- .../Models/ContentRepositoryExtensions.cs | 3 +- src/Umbraco.Core/Models/CultureImpact.cs | 6 +- src/Umbraco.Core/Models/DataType.cs | 3 +- .../Models/Entities/BeingDirty.cs | 3 +- .../Models/Entities/BeingDirtyBase.cs | 9 +- .../Models/Mapping/UserMapDefinition.cs | 13 +- src/Umbraco.Core/Models/MediaExtensions.cs | 6 +- src/Umbraco.Core/Models/PropertyCollection.cs | 3 +- .../VariationContextAccessorExtensions.cs | 14 +- src/Umbraco.Core/Models/ReadOnlyRelation.cs | 3 +- src/Umbraco.Core/Models/Trees/MenuItem.cs | 12 +- .../ContentCopiedNotification.cs | 3 +- .../DeletedVersionsNotification.cs | 3 +- .../DeletingVersionsNotification.cs | 3 +- .../Notifications/UserUnlockedNotification.cs | 3 +- .../Packaging/IPackageInstallation.cs | 3 +- .../Packaging/PackageMigrationResource.cs | 3 +- .../Packaging/PackagesRepository.cs | 30 +-- .../IDocumentVersionRepository.cs | 3 +- .../Repositories/IMemberRepository.cs | 3 +- .../Repositories/IRelationRepository.cs | 9 +- .../ITrackedReferencesRepository.cs | 9 +- .../Repositories/IUserRepository.cs | 13 +- .../PropertyEditors/BlockListConfiguration.cs | 14 +- .../MediaPickerConfiguration.cs | 9 +- .../NoopPropertyCacheCompressionOptions.cs | 3 +- .../EyeDropperValueConverter.cs | 3 +- .../InternalPublishedSnapshotService.cs | 3 +- src/Umbraco.Core/ReflectionUtilities.cs | 3 +- src/Umbraco.Core/Routing/AliasUrlProvider.cs | 4 +- .../Routing/DefaultUrlProvider.cs | 7 +- src/Umbraco.Core/Routing/DomainUtilities.cs | 19 +- src/Umbraco.Core/Routing/ISiteDomainMapper.cs | 6 +- src/Umbraco.Core/Routing/PublishedRequest.cs | 18 +- src/Umbraco.Core/Routing/PublishedRouter.cs | 41 ++-- .../Runtime/EssentialDirectoryCreator.cs | 3 +- .../Security/IdentityUserToken.cs | 3 +- src/Umbraco.Core/Semver/Semver.cs | 12 +- src/Umbraco.Core/Services/AuditService.cs | 33 ++-- src/Umbraco.Core/Services/ContentService.cs | 26 ++- .../Services/ContentServiceExtensions.cs | 6 +- .../Services/ContentTypeService.cs | 16 +- .../Services/ContentTypeServiceBase.cs | 3 +- ...peServiceBaseOfTRepositoryTItemTService.cs | 96 ++++----- src/Umbraco.Core/Services/DashboardService.cs | 3 +- .../Services/ExternalLoginService.cs | 12 +- .../Services/IContentVersionService.cs | 3 +- .../Services/IMembershipMemberService.cs | 6 +- .../Services/IMembershipRoleService.cs | 3 +- src/Umbraco.Core/Services/IRelationService.cs | 9 +- .../Services/ITrackedReferencesService.cs | 9 +- src/Umbraco.Core/Services/IUserService.cs | 22 ++- .../Services/MemberGroupService.cs | 4 +- .../Services/NotificationService.cs | 3 +- .../Services/RepositoryService.cs | 3 +- .../Services/UserServiceExtensions.cs | 6 +- .../Strings/DefaultShortStringHelper.cs | 50 ++--- .../Strings/DefaultShortStringHelperConfig.cs | 43 ++-- .../Strings/Utf8ToAsciiConverter.cs | 5 +- .../Templates/IUmbracoComponentRenderer.cs | 3 +- src/Umbraco.Core/UmbracoContextReference.cs | 3 +- src/Umbraco.Core/Xml/DynamicContext.cs | 5 +- .../Xml/XPath/NavigableNavigator.cs | 8 +- 102 files changed, 728 insertions(+), 570 deletions(-) diff --git a/src/Umbraco.Core/Attempt.cs b/src/Umbraco.Core/Attempt.cs index cf6df0d01094..7a438dece660 100644 --- a/src/Umbraco.Core/Attempt.cs +++ b/src/Umbraco.Core/Attempt.cs @@ -73,8 +73,7 @@ public static Attempt FailWithStatus(TStatus /// The result of the attempt. /// The exception causing the failure of the attempt. /// The failed attempt. - public static Attempt FailWithStatus(TStatus status, TResult result, - Exception exception) => Attempt.Fail(status, result, exception); + public static Attempt FailWithStatus(TStatus status, TResult result, Exception exception) => Attempt.Fail(status, result, exception); /// /// Creates a successful or a failed attempt, with a result. @@ -96,6 +95,14 @@ public static Attempt If(bool condition, TResult result) => /// The status of the failed attempt. /// The result of the attempt. /// The attempt. - public static Attempt IfWithStatus(bool condition, TStatus succStatus, - TStatus failStatus, TResult result) => Attempt.If(condition, succStatus, failStatus, result); + public static Attempt IfWithStatus( + bool condition, + TStatus succStatus, + TStatus failStatus, + TResult result) => + Attempt.If( + condition, + succStatus, + failStatus, + result); } diff --git a/src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs index fa863573ac36..82bfc0a0984e 100644 --- a/src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs @@ -111,8 +111,8 @@ public override void Refresh(JsonPayload[] payloads) } if (payloads.Any(x => x.ItemType == typeof(IMemberType).Name)) - // don't try to be clever - refresh all { + // don't try to be clever - refresh all MemberCacheRefresher.RefreshMemberTypes(AppCaches); } @@ -124,7 +124,6 @@ public override void Refresh(JsonPayload[] payloads) base.Refresh(payloads); } - public override void RefreshAll() => throw new NotSupportedException(); public override void Refresh(int id) => throw new NotSupportedException(); diff --git a/src/Umbraco.Core/Cache/DomainCacheRefresher.cs b/src/Umbraco.Core/Cache/DomainCacheRefresher.cs index e18cdeb16335..610a203d6084 100644 --- a/src/Umbraco.Core/Cache/DomainCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/DomainCacheRefresher.cs @@ -60,13 +60,13 @@ public override void Refresh(JsonPayload[] payloads) // notify _publishedSnapshotService.Notify(payloads); + // then trigger event base.Refresh(payloads); } // these events should never trigger // everything should be PAYLOAD/JSON - public override void RefreshAll() => throw new NotSupportedException(); public override void Refresh(int id) => throw new NotSupportedException(); diff --git a/src/Umbraco.Core/Cache/LanguageCacheRefresher.cs b/src/Umbraco.Core/Cache/LanguageCacheRefresher.cs index 0fbe66c14316..2ff447246b2d 100644 --- a/src/Umbraco.Core/Cache/LanguageCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/LanguageCacheRefresher.cs @@ -30,10 +30,9 @@ private void RefreshDomains() // note: must do what's above FIRST else the repositories still have the old cached // content and when the PublishedCachesService is notified of changes it does not see // the new content... - DomainCacheRefresher.JsonPayload[] payloads = new[] { - new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll) + new DomainCacheRefresher.JsonPayload(0, DomainChangeTypes.RefreshAll), }; _publishedSnapshotService.Notify(payloads); } @@ -62,7 +61,7 @@ public enum LanguageChangeType /// /// A language has been updated - it's culture has changed /// - ChangeCulture = 3 + ChangeCulture = 3, } public JsonPayload(int id, string isoCode, LanguageChangeType changeType) @@ -73,7 +72,9 @@ public JsonPayload(int id, string isoCode, LanguageChangeType changeType) } public int Id { get; } + public string IsoCode { get; } + public LanguageChangeType ChangeType { get; } } @@ -102,7 +103,7 @@ public override void Refresh(JsonPayload[] payloads) var clearDictionary = false; var clearContent = false; - //clear all no matter what type of payload + // clear all no matter what type of payload ClearAllIsolatedCacheByEntityType(); foreach (JsonPayload payload in payloads) @@ -125,17 +126,17 @@ public override void Refresh(JsonPayload[] payloads) ClearAllIsolatedCacheByEntityType(); } - //if this flag is set, we will tell the published snapshot service to refresh ALL content and evict ALL IContent items + // if this flag is set, we will tell the published snapshot service to refresh ALL content and evict ALL IContent items if (clearContent) { - //clear all domain caches + // clear all domain caches RefreshDomains(); ContentCacheRefresher.RefreshContentTypes(AppCaches); // we need to evict all IContent items - //now refresh all nucache + + // now refresh all nucache ContentCacheRefresher.JsonPayload[] clearContentPayload = - new[] {new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll)}; - ContentCacheRefresher.NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, - clearContentPayload); + new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }; + ContentCacheRefresher.NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, clearContentPayload); } // then trigger event @@ -144,7 +145,6 @@ public override void Refresh(JsonPayload[] payloads) // these events should never trigger // everything should be PAYLOAD/JSON - public override void RefreshAll() => throw new NotSupportedException(); public override void Refresh(int id) => throw new NotSupportedException(); diff --git a/src/Umbraco.Core/Cache/MemberGroupCacheRefresher.cs b/src/Umbraco.Core/Cache/MemberGroupCacheRefresher.cs index af551aa070af..71f1b35f1766 100644 --- a/src/Umbraco.Core/Cache/MemberGroupCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MemberGroupCacheRefresher.cs @@ -8,8 +8,7 @@ namespace Umbraco.Cms.Core.Cache; public sealed class MemberGroupCacheRefresher : PayloadCacheRefresherBase { - public MemberGroupCacheRefresher(AppCaches appCaches, IJsonSerializer jsonSerializer, - IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) + public MemberGroupCacheRefresher(AppCaches appCaches, IJsonSerializer jsonSerializer, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) : base(appCaches, jsonSerializer, eventAggregator, factory) { } @@ -25,6 +24,7 @@ public JsonPayload(int id, string name) } public string Name { get; } + public int Id { get; } } @@ -61,6 +61,7 @@ public override void Remove(int id) } private void ClearCache() => + // Since we cache by group name, it could be problematic when renaming to // previously existing names - see http://issues.umbraco.org/issue/U4-10846. // To work around this, just clear all the cache items diff --git a/src/Umbraco.Core/Collections/ObservableDictionary.cs b/src/Umbraco.Core/Collections/ObservableDictionary.cs index 186e16bff736..9e52b4dae707 100644 --- a/src/Umbraco.Core/Collections/ObservableDictionary.cs +++ b/src/Umbraco.Core/Collections/ObservableDictionary.cs @@ -34,6 +34,7 @@ public ObservableDictionary(Func keySelector, IEqualityComparer Indecies { get; } + protected Func KeySelector { get; } public bool Remove(TKey key) @@ -65,7 +66,7 @@ public TValue this[TKey key] get => this[Indecies[key]]; set { - //confirm key matches + // confirm key matches if (!KeySelector(value)!.Equals(key)) { throw new InvalidOperationException("Key of new value does not match."); @@ -101,7 +102,7 @@ public bool Replace(TKey key, TValue value) return false; } - //confirm key matches + // confirm key matches if (!KeySelector(value)!.Equals(key)) { throw new InvalidOperationException("Key of new value does not match."); @@ -140,8 +141,7 @@ public void ChangeKey(TKey currentKey, TKey newKey) if (ContainsKey(newKey)) { - throw new ArgumentException($"An element with the same key '{newKey}' already exists in the dictionary.", - nameof(newKey)); + throw new ArgumentException($"An element with the same key '{newKey}' already exists in the dictionary.", nameof(newKey)); } var currentIndex = Indecies[currentKey]; @@ -157,8 +157,7 @@ protected override void InsertItem(int index, TValue item) TKey key = KeySelector(item); if (Indecies.ContainsKey(key)) { - throw new ArgumentException($"An element with the same key '{key}' already exists in the dictionary.", - nameof(item)); + throw new ArgumentException($"An element with the same key '{key}' already exists in the dictionary.", nameof(item)); } if (index != Count) @@ -222,7 +221,7 @@ public bool TryGetValue(TKey key, out TValue val) ICollection IDictionary.Keys => Indecies.Keys; - //this will never be used + // this will never be used ICollection IDictionary.Values => Values.ToList(); bool ICollection>.IsReadOnly => false; diff --git a/src/Umbraco.Core/Composing/ComposerGraph.cs b/src/Umbraco.Core/Composing/ComposerGraph.cs index b675180e3950..3c602b0ad93c 100644 --- a/src/Umbraco.Core/Composing/ComposerGraph.cs +++ b/src/Umbraco.Core/Composing/ComposerGraph.cs @@ -315,7 +315,9 @@ private static void GatherRequirementsFromAfterAttribute(Type type, ICollection< requirements[type]!.AddRange(implems); } - else if (attr.Weak == false && throwOnMissing) // if explicitly set to !weak, is strong, else is weak + + // if explicitly set to !weak, is strong, else is weak + else if (attr.Weak == false && throwOnMissing) { throw new Exception( $"Broken composer dependency: {type.FullName} -> {attr.RequiredType.FullName}."); @@ -335,7 +337,9 @@ private static void GatherRequirementsFromAfterAttribute(Type type, ICollection< requirements[type]!.Add(attr.RequiredType); } - else if (attr.Weak != true && throwOnMissing) // if not explicitly set to weak, is strong + + // if not explicitly set to weak, is strong + else if (attr.Weak != true && throwOnMissing) { throw new Exception( $"Broken composer dependency: {type.FullName} -> {attr.RequiredType.FullName}."); diff --git a/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs b/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs index 6c502c11fc22..f9e4ed6dbeef 100644 --- a/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs +++ b/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs @@ -29,8 +29,7 @@ internal class FindAssembliesWithReferencesTo /// /// If true will also use the target assembly names as entry point assemblies /// Logger factory for when scanning goes wrong - public FindAssembliesWithReferencesTo(Assembly[] referenceAssemblies, string[] targetAssemblyNames, - bool includeTargets, ILoggerFactory loggerFactory) + public FindAssembliesWithReferencesTo(Assembly[] referenceAssemblies, string[] targetAssemblyNames, bool includeTargets, ILoggerFactory loggerFactory) { _referenceAssemblies = referenceAssemblies; _targetAssemblies = targetAssemblyNames; @@ -63,8 +62,7 @@ public IEnumerable Find() } } - var provider = new ReferenceResolver(_targetAssemblies, referenceItems, - _loggerFactory.CreateLogger()); + var provider = new ReferenceResolver(_targetAssemblies, referenceItems, _loggerFactory.CreateLogger()); IEnumerable assemblyNames = provider.ResolveAssemblies(); return assemblyNames.ToList(); } diff --git a/src/Umbraco.Core/Composing/ITypeFinder.cs b/src/Umbraco.Core/Composing/ITypeFinder.cs index 79c189d5aa68..4bebfae33467 100644 --- a/src/Umbraco.Core/Composing/ITypeFinder.cs +++ b/src/Umbraco.Core/Composing/ITypeFinder.cs @@ -36,8 +36,7 @@ IEnumerable FindClassesOfTypeWithAttribute( /// /// /// - IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable? assemblies = null, - bool onlyConcreteClasses = true); + IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable? assemblies = null, bool onlyConcreteClasses = true); /// /// Finds any classes with the attribute. diff --git a/src/Umbraco.Core/Configuration/Models/Validation/ConfigurationValidatorBase.cs b/src/Umbraco.Core/Configuration/Models/Validation/ConfigurationValidatorBase.cs index d293ee4b09ad..447a27f026d2 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/ConfigurationValidatorBase.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/ConfigurationValidatorBase.cs @@ -18,8 +18,7 @@ public abstract class ConfigurationValidatorBase /// The set of valid values. /// A message to output if the value does not match. /// True if valid, false if not. - public bool ValidateStringIsOneOfValidValues(string configPath, string value, IEnumerable validValues, - out string message) + public bool ValidateStringIsOneOfValidValues(string configPath, string value, IEnumerable validValues, out string message) { if (!validValues.InvariantContains(value)) { @@ -40,8 +39,7 @@ public bool ValidateStringIsOneOfValidValues(string configPath, string value, IE /// Description of validation appended to message if validation fails. /// A message to output if the value does not match. /// True if valid, false if not. - public bool ValidateCollection(string configPath, IEnumerable values, - string validationDescription, out string message) + public bool ValidateCollection(string configPath, IEnumerable values, string validationDescription, out string message) { if (values.Any(x => !x.IsValid())) { @@ -61,8 +59,7 @@ public bool ValidateCollection(string configPath, IEnumerableDescription of validation appended to message if validation fails. /// A message to output if the value does not match. /// True if valid, false if not. - public bool ValidateOptionalEntry(string configPath, ValidatableEntryBase? value, string validationDescription, - out string message) + public bool ValidateOptionalEntry(string configPath, ValidatableEntryBase? value, string validationDescription, out string message) { if (value != null && !value.IsValid()) { diff --git a/src/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidator.cs b/src/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidator.cs index ce31b56ec4ec..32ad130c3376 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidator.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/GlobalSettingsValidator.cs @@ -28,16 +28,17 @@ public ValidateOptionsResult Validate(string name, GlobalSettings options) } private bool ValidateSmtpSetting(SmtpSettings? value, out string message) => - ValidateOptionalEntry($"{Constants.Configuration.ConfigGlobal}:{nameof(GlobalSettings.Smtp)}", value, - "A valid From email address is required", out message); + ValidateOptionalEntry($"{Constants.Configuration.ConfigGlobal}:{nameof(GlobalSettings.Smtp)}", value, "A valid From email address is required", out message); private bool ValidateSqlWriteLockTimeOutSetting(TimeSpan configuredTimeOut, out string message) { // Only apply this setting if it's not excessively high or low const int minimumTimeOut = 100; const int maximumTimeOut = 20000; + + // between 0.1 and 20 seconds if (configuredTimeOut.TotalMilliseconds < minimumTimeOut || - configuredTimeOut.TotalMilliseconds > maximumTimeOut) // between 0.1 and 20 seconds + configuredTimeOut.TotalMilliseconds > maximumTimeOut) { message = $"The `{Constants.Configuration.ConfigGlobal}:{nameof(GlobalSettings.DistributedLockingWriteLockDefaultTimeout)}` setting is not between the minimum of {minimumTimeOut} ms and maximum of {maximumTimeOut} ms"; diff --git a/src/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidator.cs b/src/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidator.cs index d7e7250551ae..ac0e1651eab9 100644 --- a/src/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidator.cs +++ b/src/Umbraco.Core/Configuration/Models/Validation/HealthChecksSettingsValidator.cs @@ -32,7 +32,8 @@ public ValidateOptionsResult Validate(string name, HealthChecksSettings options) private bool ValidateNotificationFirstRunTime(string value, out string message) => ValidateOptionalCronTab( $"{Constants.Configuration.ConfigHealthChecks}:{nameof(HealthChecksSettings.Notification)}:{nameof(HealthChecksSettings.Notification.FirstRunTime)}", - value, out message); + value, + out message); private bool ValidateOptionalCronTab(string configPath, string value, out string message) { diff --git a/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs b/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs index 00686b689670..bcd659c734a3 100644 --- a/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs +++ b/src/Umbraco.Core/Configuration/ModelsBuilderConfigExtensions.cs @@ -17,8 +17,7 @@ public static string ModelsDirectoryAbsolute( var modelsDirectory = modelsBuilderConfig.ModelsDirectory; var root = hostingEnvironment.MapPathContentRoot("~/"); - _modelsDirectoryAbsolute = GetModelsDirectory(root, modelsDirectory, - modelsBuilderConfig.AcceptUnsafeModelsDirectory); + _modelsDirectoryAbsolute = GetModelsDirectory(root, modelsDirectory, modelsBuilderConfig.AcceptUnsafeModelsDirectory); } return _modelsDirectoryAbsolute; diff --git a/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs b/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs index 4bfa45dbc74e..bef95c77cf86 100644 --- a/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs +++ b/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs @@ -20,9 +20,7 @@ public override ContentAppFactoryCollection CreateCollection(IServiceProvider fa ILoggerFactory loggerFactory = factory.GetRequiredService(); IBackOfficeSecurityAccessor backOfficeSecurityAccessor = factory.GetRequiredService(); - return new ContentAppFactoryCollection( - () => CreateItems(factory), - loggerFactory.CreateLogger(), backOfficeSecurityAccessor); + return new ContentAppFactoryCollection(() => CreateItems(factory), loggerFactory.CreateLogger(), backOfficeSecurityAccessor); } protected override IEnumerable CreateItems(IServiceProvider factory) diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Composers.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Composers.cs index 558c18027608..e3a659056bdb 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Composers.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Composers.cs @@ -17,8 +17,7 @@ public static IUmbracoBuilder AddComposers(this IUmbracoBuilder builder) IEnumerable enableDisable = builder.TypeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute)); - new ComposerGraph(builder, composerTypes, enableDisable, - builder.BuilderLoggerFactory.CreateLogger()).Compose(); + new ComposerGraph(builder, composerTypes, enableDisable, builder.BuilderLoggerFactory.CreateLogger()).Compose(); return builder; } diff --git a/src/Umbraco.Core/Deploy/ArtifactDeployState.cs b/src/Umbraco.Core/Deploy/ArtifactDeployState.cs index cee34706a397..2a61cca8b5fc 100644 --- a/src/Umbraco.Core/Deploy/ArtifactDeployState.cs +++ b/src/Umbraco.Core/Deploy/ArtifactDeployState.cs @@ -30,8 +30,7 @@ public abstract class ArtifactDeployState /// The service connector deploying the artifact. /// The next pass number. /// A deploying artifact. - public static ArtifactDeployState Create(TArtifact art, TEntity entity, - IServiceConnector connector, int nextPass) + public static ArtifactDeployState Create(TArtifact art, TEntity entity, IServiceConnector connector, int nextPass) where TArtifact : IArtifact => new ArtifactDeployState(art, entity, connector, nextPass); diff --git a/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs b/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs index fdc1471358a0..fdf076262a12 100644 --- a/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs +++ b/src/Umbraco.Core/Deploy/IDataTypeConfigurationConnector.cs @@ -10,9 +10,10 @@ namespace Umbraco.Cms.Core.Deploy; /// Configuration may contain values such as content identifiers, that would be local /// to one environment, and need to be converted in order to be deployed. /// -[SuppressMessage("ReSharper", "UnusedMember.Global", - Justification = - "This is actual only used by Deploy, but we don't want third parties to have references on deploy, that's why this interface is part of core.")] +[SuppressMessage( + "ReSharper", + "UnusedMember.Global", + Justification = "This is actual only used by Deploy, but we don't want third parties to have references on deploy, that's why this interface is part of core.")] public interface IDataTypeConfigurationConnector { /// diff --git a/src/Umbraco.Core/Extensions/EnumerableExtensions.cs b/src/Umbraco.Core/Extensions/EnumerableExtensions.cs index f4a7f92e0fa1..6628dc4f3ddb 100644 --- a/src/Umbraco.Core/Extensions/EnumerableExtensions.cs +++ b/src/Umbraco.Core/Extensions/EnumerableExtensions.cs @@ -80,7 +80,8 @@ public static IEnumerable> InGroupsOf(this IEnumerable? sou public static IEnumerable SelectByGroups( this IEnumerable source, - Func, IEnumerable> selector, int groupSize) + Func, IEnumerable> selector, + int groupSize) { // don't want to use a SelectMany(x => x) here - isn't this better? // ReSharper disable once LoopCanBeConvertedToQuery @@ -193,7 +194,8 @@ public static void RemoveAll(this ICollection list, Func predicat public static IEnumerable SelectRecursive( this IEnumerable source, - Func> recursiveSelector, int maxRecusionDepth = 100) + Func> recursiveSelector, + int maxRecusionDepth = 100) { var stack = new Stack>(); stack.Push(source.GetEnumerator()); @@ -390,7 +392,8 @@ public static IEnumerable SkipLast(this IEnumerable source) public static IOrderedEnumerable OrderBy( this IEnumerable source, - Func keySelector, Direction sortOrder) => sortOrder == Direction.Ascending + Func keySelector, + Direction sortOrder) => sortOrder == Direction.Ascending ? source.OrderBy(keySelector) : source.OrderByDescending(keySelector); } diff --git a/src/Umbraco.Core/Handlers/AuditNotificationsHandler.cs b/src/Umbraco.Core/Handlers/AuditNotificationsHandler.cs index ebd888506bbb..28fbea027bff 100644 --- a/src/Umbraco.Core/Handlers/AuditNotificationsHandler.cs +++ b/src/Umbraco.Core/Handlers/AuditNotificationsHandler.cs @@ -99,10 +99,13 @@ public void Handle(AssignedUserGroupPermissionsNotification notification) var assigned = string.Join(", ", perm.AssignedPermissions ?? Array.Empty()); IEntitySlim? entity = _entityService.Get(perm.EntityId); - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + _auditService.Write( + performingUser.Id, + $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.UtcNow, - -1, $"User Group {group?.Id} \"{group?.Name}\" ({group?.Alias})", + -1, + $"User Group {group?.Id} \"{group?.Name}\" ({group?.Alias})", "umbraco/user-group/permissions-change", $"assigning {(string.IsNullOrWhiteSpace(assigned) ? "(nothing)" : assigned)} on id:{perm.EntityId} \"{entity?.Name}\""); } @@ -113,11 +116,15 @@ public void Handle(ExportedMemberNotification notification) IUser performingUser = CurrentPerformingUser; IMember member = notification.Member; - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + _auditService.Write( + performingUser.Id, + $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.UtcNow, - -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", - "umbraco/member/exported", "exported member data"); + -1, + $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", + "umbraco/member/exported", + "exported member data"); } public void Handle(MemberDeletedNotification notification) @@ -126,11 +133,15 @@ public void Handle(MemberDeletedNotification notification) IEnumerable members = notification.DeletedEntities; foreach (IMember member in members) { - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + _auditService.Write( + performingUser.Id, + $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.UtcNow, - -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", - "umbraco/member/delete", $"delete member id:{member.Id} \"{member.Name}\" {FormatEmail(member)}"); + -1, + $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", + "umbraco/member/delete", + $"delete member id:{member.Id} \"{member.Name}\" {FormatEmail(member)}"); } } @@ -142,11 +153,15 @@ public void Handle(MemberSavedNotification notification) { var dp = string.Join(", ", ((Member)member).GetWereDirtyProperties()); - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + _auditService.Write( + performingUser.Id, + $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.UtcNow, - -1, $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", - "umbraco/member/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}"); + -1, + $"Member {member.Id} \"{member.Name}\" {FormatEmail(member)}", + "umbraco/member/save", + $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}"); } } @@ -158,11 +173,15 @@ public void Handle(RemovedMemberRolesNotification notification) foreach (var id in notification.MemberIds) { members.TryGetValue(id, out IMember? member); - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + _auditService.Write( + performingUser.Id, + $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.UtcNow, - -1, $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}", - "umbraco/member/roles/removed", $"roles modified, removed {roles}"); + -1, + $"Member {id} \"{member?.Name ?? "(unknown)"}\" {FormatEmail(member)}", + "umbraco/member/roles/removed", + $"roles modified, removed {roles}"); } } @@ -172,11 +191,15 @@ public void Handle(UserDeletedNotification notification) IEnumerable affectedUsers = notification.DeletedEntities; foreach (IUser affectedUser in affectedUsers) { - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + _auditService.Write( + performingUser.Id, + $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.UtcNow, - affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", - "umbraco/user/delete", "delete user"); + affectedUser.Id, + $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", + "umbraco/user/delete", + "delete user"); } } @@ -212,29 +235,39 @@ public void Handle(UserGroupWithUsersSavedNotification notification) sb.Append($"default perms: {perms}"); } - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + _auditService.Write( + performingUser.Id, + $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.UtcNow, - -1, $"User Group {group.Id} \"{group.Name}\" ({group.Alias})", - "umbraco/user-group/save", $"{sb}"); + -1, + $"User Group {group.Id} \"{group.Name}\" ({group.Alias})", + "umbraco/user-group/save", + $"{sb}"); // now audit the users that have changed foreach (IUser user in groupWithUser.RemovedUsers) { - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + _auditService.Write( + performingUser.Id, + $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.UtcNow, - user.Id, $"User \"{user.Name}\" {FormatEmail(user)}", + user.Id, + $"User \"{user.Name}\" {FormatEmail(user)}", "umbraco/user-group/save", $"Removed user \"{user.Name}\" {FormatEmail(user)} from group {group.Id} \"{group.Name}\" ({group.Alias})"); } foreach (IUser user in groupWithUser.AddedUsers) { - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + _auditService.Write( + performingUser.Id, + $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.UtcNow, - user.Id, $"User \"{user.Name}\" {FormatEmail(user)}", + user.Id, + $"User \"{user.Name}\" {FormatEmail(user)}", "umbraco/user-group/save", $"Added user \"{user.Name}\" {FormatEmail(user)} to group {group.Id} \"{group.Name}\" ({group.Alias})"); } @@ -253,10 +286,13 @@ public void Handle(UserSavedNotification notification) var dp = string.Join(", ", ((User)affectedUser).GetWereDirtyProperties()); - _auditService.Write(performingUser.Id, $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", + _auditService.Write( + performingUser.Id, + $"User \"{performingUser.Name}\" {FormatEmail(performingUser)}", PerformingIp, DateTime.UtcNow, - affectedUser.Id, $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", + affectedUser.Id, + $"User \"{affectedUser.Name}\" {FormatEmail(affectedUser)}", "umbraco/user/save", $"updating {(string.IsNullOrWhiteSpace(dp) ? "(nothing)" : dp)}{(groups == null ? string.Empty : "; groups assigned: " + groups)}"); } @@ -265,6 +301,5 @@ public void Handle(UserSavedNotification notification) private string FormatEmail(IMember? member) => member == null ? string.Empty : member.Email.IsNullOrWhiteSpace() ? string.Empty : $"<{member.Email}>"; - private string FormatEmail(IUser user) => - user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? string.Empty : $"<{user.Email}>"; + private string FormatEmail(IUser user) => user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? string.Empty : $"<{user.Email}>"; } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Data/DatabaseIntegrityCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Data/DatabaseIntegrityCheck.cs index 91fe2efb2555..4c3936f6cbde 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Data/DatabaseIntegrityCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Data/DatabaseIntegrityCheck.cs @@ -108,8 +108,7 @@ private HealthCheckStatus CheckDocuments(bool fix) => fix, () => _contentService.CheckDataIntegrity(new ContentDataIntegrityReportOptions { FixIssues = fix })); - private HealthCheckStatus CheckPaths(string actionAlias, string actionName, string entityType, bool detailedReport, - Func doCheck) + private HealthCheckStatus CheckPaths(string actionAlias, string actionName, string entityType, bool detailedReport, Func doCheck) { ContentDataIntegrityReport report = doCheck(); diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs index f27c786b84ff..5e830e1f61ae 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/BaseHttpHeaderCheck.cs @@ -27,8 +27,7 @@ protected BaseHttpHeaderCheck( string value, string localizedTextPrefix, bool metaTagOptionAvailable) - : this(hostingEnvironment, textService, header, localizedTextPrefix, - metaTagOptionAvailable) + : this(hostingEnvironment, textService, header, localizedTextPrefix, metaTagOptionAvailable) { } diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs index 04abe70fd79d..2d15e49e6a70 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/ClickJackingCheck.cs @@ -12,8 +12,7 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security; [HealthCheck( "ED0D7E40-971E-4BE8-AB6D-8CC5D0A6A5B0", "Click-Jacking Protection", - Description = - "Checks if your site is allowed to be IFRAMEd by another site and thus would be susceptible to click-jacking.", + Description = "Checks if your site is allowed to be IFRAMEd by another site and thus would be susceptible to click-jacking.", Group = "Security")] public class ClickJackingCheck : BaseHttpHeaderCheck { diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/XssProtectionCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/XssProtectionCheck.cs index 6dd1841d415d..ca988fe45af2 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/XssProtectionCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/XssProtectionCheck.cs @@ -12,8 +12,7 @@ namespace Umbraco.Cms.Core.HealthChecks.Checks.Security; [HealthCheck( "F4D2B02E-28C5-4999-8463-05759FA15C3A", "Cross-site scripting Protection (X-XSS-Protection header)", - Description = - "This header enables the Cross-site scripting (XSS) filter in your browser. It checks for the presence of the X-XSS-Protection-header.", + Description = "This header enables the Cross-site scripting (XSS) filter in your browser. It checks for the presence of the X-XSS-Protection-header.", Group = "Security")] public class XssProtectionCheck : BaseHttpHeaderCheck { diff --git a/src/Umbraco.Core/IO/DefaultViewContentProvider.cs b/src/Umbraco.Core/IO/DefaultViewContentProvider.cs index 3e284ca7dfaf..5e0c10d80d9c 100644 --- a/src/Umbraco.Core/IO/DefaultViewContentProvider.cs +++ b/src/Umbraco.Core/IO/DefaultViewContentProvider.cs @@ -5,8 +5,7 @@ namespace Umbraco.Cms.Core.IO; public class DefaultViewContentProvider : IDefaultViewContentProvider { - public string GetDefaultFileContent(string? layoutPageAlias = null, string? modelClassName = null, - string? modelNamespace = null, string? modelNamespaceAlias = null) + public string GetDefaultFileContent(string? layoutPageAlias = null, string? modelClassName = null, string? modelNamespace = null, string? modelNamespaceAlias = null) { var content = new StringBuilder(); diff --git a/src/Umbraco.Core/IO/IDefaultViewContentProvider.cs b/src/Umbraco.Core/IO/IDefaultViewContentProvider.cs index 5739024ebe90..3ca1fadbff10 100644 --- a/src/Umbraco.Core/IO/IDefaultViewContentProvider.cs +++ b/src/Umbraco.Core/IO/IDefaultViewContentProvider.cs @@ -2,6 +2,5 @@ namespace Umbraco.Cms.Core.IO; public interface IDefaultViewContentProvider { - string GetDefaultFileContent(string? layoutPageAlias = null, string? modelClassName = null, - string? modelNamespace = null, string? modelNamespaceAlias = null); + string GetDefaultFileContent(string? layoutPageAlias = null, string? modelClassName = null, string? modelNamespace = null, string? modelNamespaceAlias = null); } diff --git a/src/Umbraco.Core/Logging/IProfilingLogger.cs b/src/Umbraco.Core/Logging/IProfilingLogger.cs index 75d9422d2072..92c4d55f0c86 100644 --- a/src/Umbraco.Core/Logging/IProfilingLogger.cs +++ b/src/Umbraco.Core/Logging/IProfilingLogger.cs @@ -13,14 +13,24 @@ public interface IProfilingLogger /// /// Profiles an action and log as information messages. /// - DisposableTimer TraceDuration(string startMessage, string completeMessage, string? failMessage = null, - object[]? startMessageArgs = null, object[]? endMessageArgs = null, object[]? failMessageArgs = null); + DisposableTimer TraceDuration( + string startMessage, + string completeMessage, + string? failMessage = null, + object[]? startMessageArgs = null, + object[]? endMessageArgs = null, + object[]? failMessageArgs = null); /// /// Profiles an action and log as information messages. /// - DisposableTimer TraceDuration(Type loggerType, string startMessage, string completeMessage, - string? failMessage = null, object[]? startMessageArgs = null, object[]? endMessageArgs = null, + DisposableTimer TraceDuration( + Type loggerType, + string startMessage, + string completeMessage, + string? failMessage = null, + object[]? startMessageArgs = null, + object[]? endMessageArgs = null, object[]? failMessageArgs = null); /// @@ -31,14 +41,25 @@ DisposableTimer TraceDuration(Type loggerType, string startMessage, string compl /// /// Profiles an action and log as debug messages. /// - DisposableTimer? DebugDuration(string startMessage, string completeMessage, string? failMessage = null, - int thresholdMilliseconds = 0, object[]? startMessageArgs = null, object[]? endMessageArgs = null, + DisposableTimer? DebugDuration( + string startMessage, + string completeMessage, + string? failMessage = null, + int thresholdMilliseconds = 0, + object[]? startMessageArgs = null, + object[]? endMessageArgs = null, object[]? failMessageArgs = null); /// /// Profiles an action and log as debug messages. /// - DisposableTimer? DebugDuration(Type loggerType, string startMessage, string completeMessage, - string? failMessage = null, int thresholdMilliseconds = 0, object[]? startMessageArgs = null, - object[]? endMessageArgs = null, object[]? failMessageArgs = null); + DisposableTimer? DebugDuration( + Type loggerType, + string startMessage, + string completeMessage, + string? failMessage = null, + int thresholdMilliseconds = 0, + object[]? startMessageArgs = null, + object[]? endMessageArgs = null, + object[]? failMessageArgs = null); } diff --git a/src/Umbraco.Core/Logging/ProfilingLogger.cs b/src/Umbraco.Core/Logging/ProfilingLogger.cs index 3f03b1c75f54..997f139539ae 100644 --- a/src/Umbraco.Core/Logging/ProfilingLogger.cs +++ b/src/Umbraco.Core/Logging/ProfilingLogger.cs @@ -38,36 +38,75 @@ public ProfilingLogger(ILogger logger, IProfiler profiler) public DisposableTimer TraceDuration(string startMessage, object[]? startMessageArgs = null) => TraceDuration(startMessage, "Completed.", startMessageArgs: startMessageArgs); - public DisposableTimer TraceDuration(string startMessage, string completeMessage, string? failMessage = null, - object[]? startMessageArgs = null, object[]? endMessageArgs = null, object[]? failMessageArgs = null) - => new(Logger, LogLevel.Information, Profiler, typeof(T), startMessage, completeMessage, failMessage, - startMessageArgs, endMessageArgs, failMessageArgs); - - public DisposableTimer TraceDuration(Type loggerType, string startMessage, string completeMessage, - string? failMessage = null, object[]? startMessageArgs = null, object[]? endMessageArgs = null, + public DisposableTimer TraceDuration( + string startMessage, + string completeMessage, + string? failMessage = null, + object[]? startMessageArgs = null, + object[]? endMessageArgs = null, + object[]? failMessageArgs = null) + => new(Logger, LogLevel.Information, Profiler, typeof(T), startMessage, completeMessage, failMessage, startMessageArgs, endMessageArgs, failMessageArgs); + + public DisposableTimer TraceDuration( + Type loggerType, + string startMessage, + string completeMessage, + string? failMessage = null, + object[]? startMessageArgs = null, + object[]? endMessageArgs = null, object[]? failMessageArgs = null) - => new(Logger, LogLevel.Information, Profiler, loggerType, startMessage, completeMessage, failMessage, - startMessageArgs, endMessageArgs, failMessageArgs); + => new(Logger, LogLevel.Information, Profiler, loggerType, startMessage, completeMessage, failMessage, startMessageArgs, endMessageArgs, failMessageArgs); public DisposableTimer? DebugDuration(string startMessage, object[]? startMessageArgs = null) => Logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug) ? DebugDuration(startMessage, "Completed.", startMessageArgs: startMessageArgs) : null; - public DisposableTimer? DebugDuration(string startMessage, string completeMessage, string? failMessage = null, - int thresholdMilliseconds = 0, object[]? startMessageArgs = null, object[]? endMessageArgs = null, + public DisposableTimer? DebugDuration( + string startMessage, + string completeMessage, + string? failMessage = null, + int thresholdMilliseconds = 0, + object[]? startMessageArgs = null, + object[]? endMessageArgs = null, object[]? failMessageArgs = null) => Logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug) - ? new DisposableTimer(Logger, LogLevel.Debug, Profiler, typeof(T), startMessage, completeMessage, - failMessage, startMessageArgs, endMessageArgs, failMessageArgs, thresholdMilliseconds) + ? new DisposableTimer( + Logger, + LogLevel.Debug, + Profiler, + typeof(T), + startMessage, + completeMessage, + failMessage, + startMessageArgs, + endMessageArgs, + failMessageArgs, + thresholdMilliseconds) : null; - public DisposableTimer? DebugDuration(Type loggerType, string startMessage, string completeMessage, - string? failMessage = null, int thresholdMilliseconds = 0, object[]? startMessageArgs = null, - object[]? endMessageArgs = null, object[]? failMessageArgs = null) + public DisposableTimer? DebugDuration( + Type loggerType, + string startMessage, + string completeMessage, + string? failMessage = null, + int thresholdMilliseconds = 0, + object[]? startMessageArgs = null, + object[]? endMessageArgs = null, + object[]? failMessageArgs = null) => Logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug) - ? new DisposableTimer(Logger, LogLevel.Debug, Profiler, loggerType, startMessage, completeMessage, - failMessage, startMessageArgs, endMessageArgs, failMessageArgs, thresholdMilliseconds) + ? new DisposableTimer( + Logger, + LogLevel.Debug, + Profiler, + loggerType, + startMessage, + completeMessage, + failMessage, + startMessageArgs, + endMessageArgs, + failMessageArgs, + thresholdMilliseconds) : null; #region ILogger diff --git a/src/Umbraco.Core/Media/Exif/BitConverterEx.cs b/src/Umbraco.Core/Media/Exif/BitConverterEx.cs index e8252d662df2..f6cc50f80103 100644 --- a/src/Umbraco.Core/Media/Exif/BitConverterEx.cs +++ b/src/Umbraco.Core/Media/Exif/BitConverterEx.cs @@ -14,7 +14,7 @@ internal class BitConverterEx public enum ByteOrder { LittleEndian = 1, - BigEndian = 2 + BigEndian = 2, } #endregion diff --git a/src/Umbraco.Core/Media/Exif/ExifInterOperability.cs b/src/Umbraco.Core/Media/Exif/ExifInterOperability.cs index e4e3669eb77a..d2d9f8be6afa 100644 --- a/src/Umbraco.Core/Media/Exif/ExifInterOperability.cs +++ b/src/Umbraco.Core/Media/Exif/ExifInterOperability.cs @@ -51,6 +51,5 @@ public ExifInterOperability(ushort tagid, ushort typeid, uint count, byte[] data /// Returns the string representation of this instance. /// /// - public override string ToString() => string.Format("Tag: {0}, Type: {1}, Count: {2}, Data Length: {3}", TagID, - TypeID, Count, Data.Length); + public override string ToString() => string.Format("Tag: {0}, Type: {1}, Count: {2}, Data Length: {3}", TagID, TypeID, Count, Data.Length); } diff --git a/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs b/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs index fa7cc5b73363..f77f0c89cd10 100644 --- a/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs +++ b/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs @@ -250,8 +250,7 @@ public void Set(ExifTag key, float d, float m, float s) items.Remove(key); } - items.Add(key, - new ExifURationalArray(key, new[] {new(d), new MathEx.UFraction32(m), new MathEx.UFraction32(s)})); + items.Add(key, new ExifURationalArray(key, new[] { new(d), new MathEx.UFraction32(m), new MathEx.UFraction32(s) })); } #endregion @@ -411,8 +410,7 @@ bool ICollection>.Contains(KeyValuePair to the end of the destination .-or-Type /// cannot be cast automatically to the type of the destination . /// - void ICollection>.CopyTo(KeyValuePair[] array, - int arrayIndex) + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { if (array == null) { @@ -431,8 +429,7 @@ void ICollection>.CopyTo(KeyValuePair= array.Length) { - throw new ArgumentException("arrayIndex is equal to or greater than the length of destination array", - "array"); + throw new ArgumentException("arrayIndex is equal to or greater than the length of destination array", "array"); } if (arrayIndex + items.Count > array.Length) diff --git a/src/Umbraco.Core/Media/Exif/JFIFThumbnail.cs b/src/Umbraco.Core/Media/Exif/JFIFThumbnail.cs index ac85377190dd..cafa804c3a4b 100644 --- a/src/Umbraco.Core/Media/Exif/JFIFThumbnail.cs +++ b/src/Umbraco.Core/Media/Exif/JFIFThumbnail.cs @@ -11,7 +11,7 @@ public enum ImageFormat { JPEG, BMPPalette, - BMP24Bit + BMP24Bit, } #endregion diff --git a/src/Umbraco.Core/Media/Exif/JPEGFile.cs b/src/Umbraco.Core/Media/Exif/JPEGFile.cs index 963ae3be56b1..bdf7208ea06a 100644 --- a/src/Umbraco.Core/Media/Exif/JPEGFile.cs +++ b/src/Umbraco.Core/Media/Exif/JPEGFile.cs @@ -47,6 +47,7 @@ protected internal JPEGFile(Stream stream, Encoding encoding) var marker = (JPEGMarker)markerbytes[1]; var header = new byte[0]; + // SOI, EOI and RST markers do not contain any header if (marker != JPEGMarker.SOI && marker != JPEGMarker.EOI && !(marker >= JPEGMarker.RST0 && marker <= JPEGMarker.RST7)) @@ -98,7 +99,8 @@ protected internal JPEGFile(Stream stream, Encoding encoding) { throw new NotValidJPEGFileException(); } - } while ((byte)nextbyte != 0xFF); + } + while ((byte)nextbyte != 0xFF); // Skip filler bytes (0xFF) do @@ -108,7 +110,8 @@ protected internal JPEGFile(Stream stream, Encoding encoding) { throw new NotValidJPEGFileException(); } - } while ((byte)nextbyte == 0xFF); + } + while ((byte)nextbyte == 0xFF); // Looks like a section marker. The next byte must not be 0x00. if ((byte)nextbyte != 0x00) @@ -168,21 +171,26 @@ protected internal JPEGFile(Stream stream, Encoding encoding) ReadExifAPP1(); // Process the maker note - makerNoteProcessed = false; + _makerNoteProcessed = false; } #endregion #region Member Variables - private JPEGSection? jfifApp0; - private JPEGSection? jfxxApp0; - private JPEGSection? exifApp1; - private uint makerNoteOffset; - private long exifIFDFieldOffset, gpsIFDFieldOffset, interopIFDFieldOffset, firstIFDFieldOffset; - private long thumbOffsetLocation, thumbSizeLocation; - private uint thumbOffsetValue, thumbSizeValue; - private readonly bool makerNoteProcessed; + private JPEGSection? _jfifApp0; + private JPEGSection? _jfxxApp0; + private JPEGSection? _exifApp1; + private uint _makerNoteOffset; + private long _exifIfdFieldOffset; + private long _gpsIfdFieldOffset; + private long _interopIfdFieldOffset; + private long _firstIfdFieldOffset; + private long _thumbOffsetLocation; + private long _thumbSizeLocation; + private uint _thumbOffsetValue; + private uint _thumbSizeValue; + private readonly bool _makerNoteProcessed; #endregion @@ -210,7 +218,7 @@ protected internal JPEGFile(Stream stream, Encoding encoding) /// /// Saves the JPEG/Exif image to the given stream. /// - /// The path to the JPEG/Exif file. + /// The stream of the JPEG/Exif file. /// /// Determines whether the maker note offset of /// the original file will be preserved. @@ -239,7 +247,7 @@ public void Save(Stream stream, bool preserveMakerNote) } // Write section marker - stream.Write(new byte[] {0xFF, (byte)section.Marker}, 0, 2); + stream.Write(new byte[] { 0xFF, (byte)section.Marker }, 0, 2); // SOI, EOI and RST markers do not contain any header if (section.Marker != JPEGMarker.SOI && section.Marker != JPEGMarker.EOI && @@ -294,7 +302,7 @@ public void Save(string filename, bool preserveMakerNote) /// /// Saves the JPEG/Exif image to the given stream. /// - /// The path to the JPEG/Exif file. + /// The stream of the JPEG/Exif file. public override void Save(Stream stream) => Save(stream, true); #endregion @@ -307,17 +315,17 @@ public void Save(string filename, bool preserveMakerNote) private void ReadJFIFAPP0() { // Find the APP0 section containing JFIF metadata - jfifApp0 = Sections.Find(a => a.Marker == JPEGMarker.APP0 && + _jfifApp0 = Sections.Find(a => a.Marker == JPEGMarker.APP0 && a.Header.Length >= 5 && Encoding.ASCII.GetString(a.Header, 0, 5) == "JFIF\0"); // If there is no APP0 section, return. - if (jfifApp0 == null) + if (_jfifApp0 == null) { return; } - var header = jfifApp0.Header; + var header = _jfifApp0.Header; BitConverterEx jfifConv = BitConverterEx.BigEndian; // Version @@ -344,8 +352,7 @@ private void ReadJFIFAPP0() var n = xthumbnail * ythumbnail; var jfifThumbnail = new byte[n]; Array.Copy(header, 14, jfifThumbnail, 0, n); - Properties.Add(new JFIFThumbnailProperty(ExifTag.JFIFThumbnail, - new JFIFThumbnail(JFIFThumbnail.ImageFormat.JPEG, jfifThumbnail))); + Properties.Add(new JFIFThumbnailProperty(ExifTag.JFIFThumbnail, new JFIFThumbnail(JFIFThumbnail.ImageFormat.JPEG, jfifThumbnail))); } /// @@ -369,7 +376,6 @@ private bool WriteJFIFApp0() return false; } - // Create a memory stream to write the APP0 section to var ms = new MemoryStream(); @@ -392,9 +398,9 @@ private bool WriteJFIFApp0() ms.Close(); // Return APP0 header - if (jfifApp0 is not null) + if (_jfifApp0 is not null) { - jfifApp0.Header = ms.ToArray(); + _jfifApp0.Header = ms.ToArray(); return true; } @@ -407,17 +413,17 @@ private bool WriteJFIFApp0() private void ReadJFXXAPP0() { // Find the APP0 section containing JFIF metadata - jfxxApp0 = Sections.Find(a => a.Marker == JPEGMarker.APP0 && + _jfxxApp0 = Sections.Find(a => a.Marker == JPEGMarker.APP0 && a.Header.Length >= 5 && Encoding.ASCII.GetString(a.Header, 0, 5) == "JFXX\0"); // If there is no APP0 section, return. - if (jfxxApp0 == null) + if (_jfxxApp0 == null) { return; } - var header = jfxxApp0.Header; + var header = _jfxxApp0.Header; // Version var version = (JFIFExtension)header[5]; @@ -428,8 +434,7 @@ private void ReadJFXXAPP0() { var data = new byte[header.Length - 6]; Array.Copy(header, 6, data, 0, data.Length); - Properties.Add(new JFIFThumbnailProperty(ExifTag.JFXXThumbnail, - new JFIFThumbnail(JFIFThumbnail.ImageFormat.JPEG, data))); + Properties.Add(new JFIFThumbnailProperty(ExifTag.JFXXThumbnail, new JFIFThumbnail(JFIFThumbnail.ImageFormat.JPEG, data))); } else if (version == JFIFExtension.Thumbnail24BitRGB) { @@ -440,8 +445,7 @@ private void ReadJFXXAPP0() Properties.Add(new ExifByte(ExifTag.JFXXYThumbnail, ythumbnail)); var data = new byte[3 * xthumbnail * ythumbnail]; Array.Copy(header, 8, data, 0, data.Length); - Properties.Add(new JFIFThumbnailProperty(ExifTag.JFXXThumbnail, - new JFIFThumbnail(JFIFThumbnail.ImageFormat.BMP24Bit, data))); + Properties.Add(new JFIFThumbnailProperty(ExifTag.JFXXThumbnail, new JFIFThumbnail(JFIFThumbnail.ImageFormat.BMP24Bit, data))); } else if (version == JFIFExtension.ThumbnailPaletteRGB) { @@ -500,10 +504,10 @@ private bool WriteJFXXApp0() ms.Close(); - if (jfxxApp0 is not null) + if (_jfxxApp0 is not null) { // Return APP0 header - jfxxApp0.Header = ms.ToArray(); + _jfxxApp0.Header = ms.ToArray(); return true; } @@ -516,12 +520,12 @@ private bool WriteJFXXApp0() private void ReadExifAPP1() { // Find the APP1 section containing Exif metadata - exifApp1 = Sections.Find(a => a.Marker == JPEGMarker.APP1 && + _exifApp1 = Sections.Find(a => a.Marker == JPEGMarker.APP1 && a.Header.Length >= 6 && Encoding.ASCII.GetString(a.Header, 0, 6) == "Exif\0\0"); // If there is no APP1 section, add a new one after the last APP0 section (if any). - if (exifApp1 == null) + if (_exifApp1 == null) { var insertionIndex = Sections.FindLastIndex(a => a.Marker == JPEGMarker.APP0); if (insertionIndex == -1) @@ -530,8 +534,8 @@ private void ReadExifAPP1() } insertionIndex++; - exifApp1 = new JPEGSection(JPEGMarker.APP1); - Sections.Insert(insertionIndex, exifApp1); + _exifApp1 = new JPEGSection(JPEGMarker.APP1); + Sections.Insert(insertionIndex, _exifApp1); if (BitConverterEx.SystemByteOrder == BitConverterEx.ByteOrder.LittleEndian) { ByteOrder = BitConverterEx.ByteOrder.LittleEndian; @@ -544,9 +548,9 @@ private void ReadExifAPP1() return; } - var header = exifApp1.Header; + var header = _exifApp1.Header; var ifdqueue = new SortedList(); - makerNoteOffset = 0; + _makerNoteOffset = 0; // TIFF header var tiffoffset = 6; @@ -579,14 +583,14 @@ private void ReadExifAPP1() } // Offset to 0th IFD - var ifd0offset = - (int)BitConverterEx.ToUInt32(header, tiffoffset + 4, tiffByteOrder, BitConverterEx.SystemByteOrder); + var ifd0offset = (int)BitConverterEx.ToUInt32(header, tiffoffset + 4, tiffByteOrder, BitConverterEx.SystemByteOrder); ifdqueue.Add(ifd0offset, IFD.Zeroth); var conv = new BitConverterEx(ByteOrder, BitConverterEx.SystemByteOrder); var thumboffset = -1; var thumblength = 0; var thumbtype = -1; + // Read IFDs while (ifdqueue.Count != 0) { @@ -626,7 +630,7 @@ private void ReadExifAPP1() // Save the offset to maker note data if (currentifd == IFD.EXIF && tag == 37500) { - makerNoteOffset = conv.ToUInt32(value, 0); + _makerNoteOffset = conv.ToUInt32(value, 0); } // Calculate the bytes we need to read @@ -676,6 +680,7 @@ private void ReadExifAPP1() if (currentifd == IFD.First && tag == 0x111) { thumbtype = 1; + // Offset to first strip if (type == 3) { @@ -734,15 +739,17 @@ private void ReadExifAPP1() private bool WriteExifApp1(bool preserveMakerNote) { // Zero out IFD field offsets. We will fill those as we write the IFD sections - exifIFDFieldOffset = 0; - gpsIFDFieldOffset = 0; - interopIFDFieldOffset = 0; - firstIFDFieldOffset = 0; + _exifIfdFieldOffset = 0; + _gpsIfdFieldOffset = 0; + _interopIfdFieldOffset = 0; + _firstIfdFieldOffset = 0; + // We also do not know the location of the embedded thumbnail yet - thumbOffsetLocation = 0; - thumbOffsetValue = 0; - thumbSizeLocation = 0; - thumbSizeValue = 0; + _thumbOffsetLocation = 0; + _thumbOffsetValue = 0; + _thumbSizeLocation = 0; + _thumbSizeValue = 0; + // Write thumbnail tags if they are missing, remove otherwise if (Thumbnail == null) { @@ -843,10 +850,11 @@ private bool WriteExifApp1(bool preserveMakerNote) // TIFF header // Byte order var tiffoffset = ms.Position; - ms.Write(ByteOrder == BitConverterEx.ByteOrder.LittleEndian ? new byte[] {0x49, 0x49} : new byte[] {0x4D, 0x4D}, - 0, 2); + ms.Write(ByteOrder == BitConverterEx.ByteOrder.LittleEndian ? new byte[] { 0x49, 0x49 } : new byte[] { 0x4D, 0x4D }, 0, 2); + // TIFF ID ms.Write(bceExif.GetBytes((ushort)42), 0, 2); + // Offset to 0th IFD ms.Write(bceExif.GetBytes((uint)8), 0, 4); @@ -862,57 +870,56 @@ private bool WriteExifApp1(bool preserveMakerNote) WriteIFD(ms, ifdfirst, IFD.First, tiffoffset, preserveMakerNote); // Now that we now the location of IFDs we can go back and write IFD offsets - if (exifIFDFieldOffset != 0) + if (_exifIfdFieldOffset != 0) { - ms.Seek(exifIFDFieldOffset, SeekOrigin.Begin); + ms.Seek(_exifIfdFieldOffset, SeekOrigin.Begin); ms.Write(bceExif.GetBytes(exififdrelativeoffset), 0, 4); } - if (gpsIFDFieldOffset != 0) + if (_gpsIfdFieldOffset != 0) { - ms.Seek(gpsIFDFieldOffset, SeekOrigin.Begin); + ms.Seek(_gpsIfdFieldOffset, SeekOrigin.Begin); ms.Write(bceExif.GetBytes(gpsifdrelativeoffset), 0, 4); } - if (interopIFDFieldOffset != 0) + if (_interopIfdFieldOffset != 0) { - ms.Seek(interopIFDFieldOffset, SeekOrigin.Begin); + ms.Seek(_interopIfdFieldOffset, SeekOrigin.Begin); ms.Write(bceExif.GetBytes(interopifdrelativeoffset), 0, 4); } - if (firstIFDFieldOffset != 0) + if (_firstIfdFieldOffset != 0) { - ms.Seek(firstIFDFieldOffset, SeekOrigin.Begin); + ms.Seek(_firstIfdFieldOffset, SeekOrigin.Begin); ms.Write(bceExif.GetBytes(firstifdrelativeoffset), 0, 4); } // We can write thumbnail location now - if (thumbOffsetLocation != 0) + if (_thumbOffsetLocation != 0) { - ms.Seek(thumbOffsetLocation, SeekOrigin.Begin); - ms.Write(bceExif.GetBytes(thumbOffsetValue), 0, 4); + ms.Seek(_thumbOffsetLocation, SeekOrigin.Begin); + ms.Write(bceExif.GetBytes(_thumbOffsetValue), 0, 4); } - if (thumbSizeLocation != 0) + if (_thumbSizeLocation != 0) { - ms.Seek(thumbSizeLocation, SeekOrigin.Begin); - ms.Write(bceExif.GetBytes(thumbSizeValue), 0, 4); + ms.Seek(_thumbSizeLocation, SeekOrigin.Begin); + ms.Write(bceExif.GetBytes(_thumbSizeValue), 0, 4); } ms.Close(); - if (exifApp1 is not null) + if (_exifApp1 is not null) { // Return APP1 header - exifApp1.Header = ms.ToArray(); + _exifApp1.Header = ms.ToArray(); return true; } return false; } - private void WriteIFD(MemoryStream stream, Dictionary ifd, IFD ifdtype, long tiffoffset, - bool preserveMakerNote) + private void WriteIFD(MemoryStream stream, Dictionary ifd, IFD ifdtype, long tiffoffset, bool preserveMakerNote) { var conv = new BitConverterEx(BitConverterEx.SystemByteOrder, ByteOrder); @@ -938,8 +945,10 @@ private void WriteIFD(MemoryStream stream, Dictionary ifd var absolutedataoffset = stream.Position + (2 + (ifd.Count * 12) + 4); var makernotewritten = false; + // Field count stream.Write(conv.GetBytes((ushort)ifd.Count), 0, 2); + // Fields while (fieldqueue.Count != 0) { @@ -950,12 +959,12 @@ private void WriteIFD(MemoryStream stream, Dictionary ifd // Try to preserve the makernote data offset if (!makernotewritten && - !makerNoteProcessed && - makerNoteOffset != 0 && + !_makerNoteProcessed && + _makerNoteOffset != 0 && ifdtype == IFD.EXIF && field.Tag != ExifTag.MakerNote && interop.Data.Length > 4 && - currentdataoffset + interop.Data.Length > makerNoteOffset && + currentdataoffset + interop.Data.Length > _makerNoteOffset && ifd.ContainsKey(ExifTag.MakerNote)) { // Delay writing this field until we write the creator's note data @@ -966,10 +975,11 @@ private void WriteIFD(MemoryStream stream, Dictionary ifd if (field.Tag == ExifTag.MakerNote) { makernotewritten = true; + // We may need to write filler bytes to preserve maker note offset - if (preserveMakerNote && !makerNoteProcessed && makerNoteOffset > currentdataoffset) + if (preserveMakerNote && !_makerNoteProcessed && _makerNoteOffset > currentdataoffset) { - fillerbytecount = makerNoteOffset - currentdataoffset; + fillerbytecount = _makerNoteOffset - currentdataoffset; } else { @@ -979,10 +989,13 @@ private void WriteIFD(MemoryStream stream, Dictionary ifd // Tag stream.Write(conv.GetBytes(interop.TagID), 0, 2); + // Type stream.Write(conv.GetBytes(interop.TypeID), 0, 2); + // Count stream.Write(conv.GetBytes(interop.Count), 0, 4); + // Field data var data = interop.Data; if (ByteOrder != BitConverterEx.SystemByteOrder && @@ -1007,23 +1020,23 @@ private void WriteIFD(MemoryStream stream, Dictionary ifd // Just store their offsets, we will write the values later on when we know the lengths of IFDs if (ifdtype == IFD.Zeroth && interop.TagID == 0x8769) { - exifIFDFieldOffset = stream.Position; + _exifIfdFieldOffset = stream.Position; } else if (ifdtype == IFD.Zeroth && interop.TagID == 0x8825) { - gpsIFDFieldOffset = stream.Position; + _gpsIfdFieldOffset = stream.Position; } else if (ifdtype == IFD.EXIF && interop.TagID == 0xa005) { - interopIFDFieldOffset = stream.Position; + _interopIfdFieldOffset = stream.Position; } else if (ifdtype == IFD.First && interop.TagID == 0x201) { - thumbOffsetLocation = stream.Position; + _thumbOffsetLocation = stream.Position; } else if (ifdtype == IFD.First && interop.TagID == 0x202) { - thumbSizeLocation = stream.Position; + _thumbSizeLocation = stream.Position; } // Write 4 byte field value or field data @@ -1039,9 +1052,11 @@ private void WriteIFD(MemoryStream stream, Dictionary ifd { // Pointer to data area relative to TIFF header stream.Write(conv.GetBytes(currentdataoffset + fillerbytecount), 0, 4); + // Actual data var currentoffset = stream.Position; stream.Seek(absolutedataoffset, SeekOrigin.Begin); + // Write filler bytes for (var i = 0; i < fillerbytecount; i++) { @@ -1050,6 +1065,7 @@ private void WriteIFD(MemoryStream stream, Dictionary ifd stream.Write(data, 0, data.Length); stream.Seek(currentoffset, SeekOrigin.Begin); + // Increment pointers currentdataoffset += fillerbytecount + (uint)data.Length; absolutedataoffset += fillerbytecount + data.Length; @@ -1060,10 +1076,10 @@ private void WriteIFD(MemoryStream stream, Dictionary ifd // We will write zeros for now. This will be filled after we write all IFDs if (ifdtype == IFD.Zeroth) { - firstIFDFieldOffset = stream.Position; + _firstIfdFieldOffset = stream.Position; } - stream.Write(new byte[] {0, 0, 0, 0}, 0, 4); + stream.Write(new byte[] { 0, 0, 0, 0 }, 0, 4); // Seek to end of IFD stream.Seek(absolutedataoffset, SeekOrigin.Begin); @@ -1077,15 +1093,15 @@ private void WriteIFD(MemoryStream stream, Dictionary ifd Thumbnail.Save(ts); ts.Close(); var thumb = ts.ToArray(); - thumbOffsetValue = (uint)(stream.Position - tiffoffset); - thumbSizeValue = (uint)thumb.Length; + _thumbOffsetValue = (uint)(stream.Position - tiffoffset); + _thumbSizeValue = (uint)thumb.Length; stream.Write(thumb, 0, thumb.Length); ts.Dispose(); } else { - thumbOffsetValue = 0; - thumbSizeValue = 0; + _thumbOffsetValue = 0; + _thumbSizeValue = 0; } } } diff --git a/src/Umbraco.Core/Media/Exif/JPEGSection.cs b/src/Umbraco.Core/Media/Exif/JPEGSection.cs index d87d876755c4..787b04b0563a 100644 --- a/src/Umbraco.Core/Media/Exif/JPEGSection.cs +++ b/src/Umbraco.Core/Media/Exif/JPEGSection.cs @@ -12,8 +12,7 @@ internal class JPEGSection /// Returns a string representation of the current section. /// /// A System.String that represents the current section. - public override string ToString() => string.Format("{0} => Header: {1} bytes, Entropy Data: {2} bytes", Marker, - Header.Length, EntropyData.Length); + public override string ToString() => string.Format("{0} => Header: {1} bytes, Entropy Data: {2} bytes", Marker, Header.Length, EntropyData.Length); #endregion diff --git a/src/Umbraco.Core/Media/Exif/MathEx.cs b/src/Umbraco.Core/Media/Exif/MathEx.cs index f29f58179543..fbf5f2dbde5f 100644 --- a/src/Umbraco.Core/Media/Exif/MathEx.cs +++ b/src/Umbraco.Core/Media/Exif/MathEx.cs @@ -200,9 +200,11 @@ public static bool TryParse(string s, out Fraction32 f) // Multiplication public static Fraction32 operator *(Fraction32 f, int n) => new(f.Numerator * n, f.Denominator * Math.Abs(n)); + public static Fraction32 operator *(int n, Fraction32 f) => f * n; public static Fraction32 operator *(Fraction32 f, float n) => new((float)f * n); + public static Fraction32 operator *(float n, Fraction32 f) => f * n; public static Fraction32 operator *(Fraction32 f, double n) => new((double)f * n); @@ -216,6 +218,7 @@ public static bool TryParse(string s, out Fraction32 f) public static Fraction32 operator /(Fraction32 f, int n) => new(f.Numerator / n, f.Denominator / Math.Abs(n)); public static Fraction32 operator /(Fraction32 f, float n) => new((float)f / n); + public static Fraction32 operator /(Fraction32 f, double n) => new((double)f / n); public static Fraction32 operator /(Fraction32 f1, Fraction32 f2) => f1 * Inverse(f2); @@ -226,9 +229,11 @@ public static bool TryParse(string s, out Fraction32 f) public static Fraction32 operator +(int n, Fraction32 f) => f + n; public static Fraction32 operator +(Fraction32 f, float n) => new((float)f + n); + public static Fraction32 operator +(float n, Fraction32 f) => f + n; public static Fraction32 operator +(Fraction32 f, double n) => new((double)f + n); + public static Fraction32 operator +(double n, Fraction32 f) => f + n; public static Fraction32 operator +(Fraction32 f1, Fraction32 f2) @@ -245,8 +250,11 @@ public static bool TryParse(string s, out Fraction32 f) public static Fraction32 operator -(int n, Fraction32 f) => new Fraction32(n, 1) - f; public static Fraction32 operator -(Fraction32 f, float n) => new((float)f - n); + public static Fraction32 operator -(float n, Fraction32 f) => new Fraction32(n) - f; + public static Fraction32 operator -(Fraction32 f, double n) => new((double)f - n); + public static Fraction32 operator -(double n, Fraction32 f) => new Fraction32(n) - f; public static Fraction32 operator -(Fraction32 f1, Fraction32 f2) @@ -270,6 +278,7 @@ public static bool TryParse(string s, out Fraction32 f) public static explicit operator int(Fraction32 f) => f.Numerator / f.Denominator; public static explicit operator float(Fraction32 f) => f.Numerator / (float)f.Denominator; + public static explicit operator double(Fraction32 f) => f.Numerator / (double)f.Denominator; #endregion @@ -617,6 +626,7 @@ private static Fraction32 FromDouble(double value) } var err = ((cnum / (double)cden) - (num / (double)den)) / (num / (double)den); + // Are we converging? if (err >= lasterr) { @@ -755,7 +765,6 @@ public uint Denominator } } - /// /// Gets the error term. /// @@ -828,6 +837,7 @@ public static bool TryParse(string s, out UFraction32 f) // Multiplication public static UFraction32 operator *(UFraction32 f, uint n) => new(f.Numerator * n, f.Denominator * n); + public static UFraction32 operator *(uint n, UFraction32 f) => f * n; public static UFraction32 operator *(UFraction32 f, float n) => new((float)f * n); @@ -835,6 +845,7 @@ public static bool TryParse(string s, out UFraction32 f) public static UFraction32 operator *(float n, UFraction32 f) => f * n; public static UFraction32 operator *(UFraction32 f, double n) => new((double)f * n); + public static UFraction32 operator *(double n, UFraction32 f) => f * n; public static UFraction32 operator *(UFraction32 f1, UFraction32 f2) => @@ -844,6 +855,7 @@ public static bool TryParse(string s, out UFraction32 f) public static UFraction32 operator /(UFraction32 f, uint n) => new(f.Numerator / n, f.Denominator / n); public static UFraction32 operator /(UFraction32 f, float n) => new((float)f / n); + public static UFraction32 operator /(UFraction32 f, double n) => new((double)f / n); public static UFraction32 operator /(UFraction32 f1, UFraction32 f2) => f1 * Inverse(f2); @@ -854,9 +866,11 @@ public static bool TryParse(string s, out UFraction32 f) public static UFraction32 operator +(uint n, UFraction32 f) => f + n; public static UFraction32 operator +(UFraction32 f, float n) => new((float)f + n); + public static UFraction32 operator +(float n, UFraction32 f) => f + n; public static UFraction32 operator +(UFraction32 f, double n) => new((double)f + n); + public static UFraction32 operator +(double n, UFraction32 f) => f + n; public static UFraction32 operator +(UFraction32 f1, UFraction32 f2) @@ -873,6 +887,7 @@ public static bool TryParse(string s, out UFraction32 f) public static UFraction32 operator -(uint n, UFraction32 f) => new UFraction32(n, 1) - f; public static UFraction32 operator -(UFraction32 f, float n) => new((float)f - n); + public static UFraction32 operator -(float n, UFraction32 f) => new UFraction32(n) - f; public static UFraction32 operator -(UFraction32 f, double n) => new((double)f - n); @@ -1221,6 +1236,7 @@ private static UFraction32 FromDouble(double value) } var err = ((cnum / (double)cden) - (num / (double)den)) / (num / (double)den); + // Are we converging? if (err >= lasterr) { diff --git a/src/Umbraco.Core/Media/Exif/TIFFFile.cs b/src/Umbraco.Core/Media/Exif/TIFFFile.cs index e562e769f27f..2ae27c46dc44 100644 --- a/src/Umbraco.Core/Media/Exif/TIFFFile.cs +++ b/src/Umbraco.Core/Media/Exif/TIFFFile.cs @@ -44,8 +44,7 @@ protected internal TIFFFile(Stream stream, Encoding encoding) // TODO: Add support for multiple frames foreach (ImageFileDirectoryEntry field in IFDs[0].Fields) { - Properties.Add(ExifPropertyFactory.Get(field.Tag, field.Type, field.Count, field.Data, - BitConverterEx.SystemByteOrder, IFD.Zeroth, Encoding)); + Properties.Add(ExifPropertyFactory.Get(field.Tag, field.Type, field.Count, field.Data, BitConverterEx.SystemByteOrder, IFD.Zeroth, Encoding)); } } @@ -77,7 +76,9 @@ public override void Save(Stream stream) stream.Write( BitConverterEx.SystemByteOrder == BitConverterEx.ByteOrder.LittleEndian ? new byte[] { 0x49, 0x49 } - : new byte[] { 0x4D, 0x4D }, 0, 2); + : new byte[] { 0x4D, 0x4D }, + 0, + 2); // TIFF ID stream.Write(conv.GetBytes((ushort)42), 0, 2); diff --git a/src/Umbraco.Core/Models/AuditItem.cs b/src/Umbraco.Core/Models/AuditItem.cs index 995875f8c61f..bbfca724aa30 100644 --- a/src/Umbraco.Core/Models/AuditItem.cs +++ b/src/Umbraco.Core/Models/AuditItem.cs @@ -7,8 +7,7 @@ public sealed class AuditItem : EntityBase, IAuditItem /// /// Initializes a new instance of the class. /// - public AuditItem(int objectId, AuditType type, int userId, string? entityType, string? comment = null, - string? parameters = null) + public AuditItem(int objectId, AuditType type, int userId, string? entityType, string? comment = null, string? parameters = null) { DisableChangeTracking(); diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index 65ec36042b9c..e9fcc61e7c91 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -293,24 +293,33 @@ public ContentCultureInfosCollection? CultureInfos /// public void SetCultureName(string? name, string? culture) { - if (ContentType.VariesByCulture()) // set on variant content type + // set on variant content type + if (ContentType.VariesByCulture()) { - if (culture.IsNullOrWhiteSpace()) // invariant is ok + // invariant is ok + if (culture.IsNullOrWhiteSpace()) { Name = name; // may be null } - else if (name.IsNullOrWhiteSpace()) // clear + + // clear + else if (name.IsNullOrWhiteSpace()) { ClearCultureInfo(culture!); } - else // set + + // set + else { this.SetCultureInfo(culture!, name, DateTime.Now); } } - else // set on invariant content type + + // set on invariant content type + else { - if (!culture.IsNullOrWhiteSpace()) // invariant is NOT ok + // invariant is NOT ok + if (!culture.IsNullOrWhiteSpace()) { throw new NotSupportedException("Content type does not vary by culture."); } @@ -396,6 +405,7 @@ private void CultureInfosCollectionChanged(object? sender, NotifyCollectionChang break; } + case NotifyCollectionChangedAction.Replace: { // Replace occurs when an Update occurs diff --git a/src/Umbraco.Core/Models/ContentBaseExtensions.cs b/src/Umbraco.Core/Models/ContentBaseExtensions.cs index 7ea4884359aa..1337375e6fee 100644 --- a/src/Umbraco.Core/Models/ContentBaseExtensions.cs +++ b/src/Umbraco.Core/Models/ContentBaseExtensions.cs @@ -18,8 +18,7 @@ public static class ContentBaseExtensions /// /// The culture. /// The URL segment. - public static string? GetUrlSegment(this IContentBase content, IShortStringHelper shortStringHelper, - IEnumerable urlSegmentProviders, string? culture = null) + public static string? GetUrlSegment(this IContentBase content, IShortStringHelper shortStringHelper, IEnumerable urlSegmentProviders, string? culture = null) { if (content == null) { diff --git a/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs b/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs index 2488517a3512..386ca5f12f9d 100644 --- a/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs +++ b/src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs @@ -21,15 +21,13 @@ public bool PreventCleanup public int? KeepAllVersionsNewerThanDays { get => _keepAllVersionsNewerThanDays; - set => SetPropertyValueAndDetectChanges(value, ref _keepAllVersionsNewerThanDays, - nameof(KeepAllVersionsNewerThanDays)); + set => SetPropertyValueAndDetectChanges(value, ref _keepAllVersionsNewerThanDays, nameof(KeepAllVersionsNewerThanDays)); } [DataMember(Name = "keepLatestVersionPerDayForDays")] public int? KeepLatestVersionPerDayForDays { get => _keepLatestVersionPerDayForDays; - set => SetPropertyValueAndDetectChanges(value, ref _keepLatestVersionPerDayForDays, - nameof(KeepLatestVersionPerDayForDays)); + set => SetPropertyValueAndDetectChanges(value, ref _keepLatestVersionPerDayForDays, nameof(KeepLatestVersionPerDayForDays)); } } diff --git a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs index 2f449f8ad920..bf0879f8dd74 100644 --- a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs +++ b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs @@ -310,7 +310,8 @@ public static bool PublishCulture(this IContent content, CultureImpact? impact) // set names if (impact.ImpactsAllCultures) { - foreach (var c in content.AvailableCultures) // does NOT contain the invariant culture + // does NOT contain the invariant culture + foreach (var c in content.AvailableCultures) { var name = content.GetCultureName(c); if (string.IsNullOrWhiteSpace(name)) diff --git a/src/Umbraco.Core/Models/CultureImpact.cs b/src/Umbraco.Core/Models/CultureImpact.cs index 348c25125030..a6e83a7b7c18 100644 --- a/src/Umbraco.Core/Models/CultureImpact.cs +++ b/src/Umbraco.Core/Models/CultureImpact.cs @@ -142,8 +142,7 @@ public Behavior CultureBehavior /// /// /// - public static string? GetCultureForInvariantErrors(IContent? content, string?[] savingCultures, - string? defaultCulture) + public static string? GetCultureForInvariantErrors(IContent? content, string?[] savingCultures, string? defaultCulture) { if (content == null) { @@ -229,8 +228,7 @@ public static CultureImpact Explicit(string? culture, bool isDefault) /// /// Validates that the culture is compatible with the variation. /// - internal static bool TryCreate(string culture, bool isDefault, ContentVariation variation, bool throwOnFail, - out CultureImpact? impact) + internal static bool TryCreate(string culture, bool isDefault, ContentVariation variation, bool throwOnFail, out CultureImpact? impact) { impact = null; diff --git a/src/Umbraco.Core/Models/DataType.cs b/src/Umbraco.Core/Models/DataType.cs index c4b65be4e56d..630ef338bd63 100644 --- a/src/Umbraco.Core/Models/DataType.cs +++ b/src/Umbraco.Core/Models/DataType.cs @@ -120,7 +120,8 @@ public object? Configuration // we don't support re-assigning the same object // configurations are kinda non-mutable, mainly because detecting changes would be a pain - if (_configuration == value) // reference comparison + // reference comparison + if (_configuration == value) { throw new ArgumentException( "Configurations are kinda non-mutable. Do not reassign the same object.", diff --git a/src/Umbraco.Core/Models/Entities/BeingDirty.cs b/src/Umbraco.Core/Models/Entities/BeingDirty.cs index faf302b1a668..0ae2a142ed52 100644 --- a/src/Umbraco.Core/Models/Entities/BeingDirty.cs +++ b/src/Umbraco.Core/Models/Entities/BeingDirty.cs @@ -20,8 +20,7 @@ public sealed class BeingDirty : BeingDirtyBase /// A reference to the value to set. /// The property name. /// A comparer to compare property values. - public new void SetPropertyValueAndDetectChanges(T value, ref T? valueRef, string propertyName, - IEqualityComparer? comparer = null) => + public new void SetPropertyValueAndDetectChanges(T value, ref T? valueRef, string propertyName, IEqualityComparer? comparer = null) => base.SetPropertyValueAndDetectChanges(value, ref valueRef, propertyName, comparer); /// diff --git a/src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs b/src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs index d453da5a245d..887477c743ea 100644 --- a/src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs +++ b/src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs @@ -26,6 +26,7 @@ public virtual bool IsPropertyDirty(string propertyName) => /// public virtual IEnumerable GetDirtyProperties() => + // ReSharper disable once MergeConditionalExpression _currentChanges == null ? Enumerable.Empty() @@ -48,6 +49,7 @@ public virtual bool WasPropertyDirty(string propertyName) => /// public virtual void ResetWereDirtyProperties() => + // note: cannot .Clear() because when memberwise-cloning this will be the SAME // instance as the one on the clone, so we need to create a new instance. _savedChanges = null; @@ -68,6 +70,7 @@ public virtual void ResetDirtyProperties(bool rememberDirty) /// public virtual IEnumerable GetWereDirtyProperties() => + // ReSharper disable once MergeConditionalExpression _savedChanges == null ? Enumerable.Empty() @@ -120,8 +123,7 @@ protected virtual void OnPropertyChanged(string propertyName) /// A reference to the value to set. /// The property name. /// A comparer to compare property values. - protected void SetPropertyValueAndDetectChanges(T? value, ref T? valueRef, string propertyName, - IEqualityComparer? comparer = null) + protected void SetPropertyValueAndDetectChanges(T? value, ref T? valueRef, string propertyName, IEqualityComparer? comparer = null) { if (comparer == null) { @@ -130,8 +132,7 @@ protected void SetPropertyValueAndDetectChanges(T? value, ref T? valueRef, st Type typeofT = typeof(T); if (!(typeofT == typeof(string)) && typeof(IEnumerable).IsAssignableFrom(typeofT)) { - throw new ArgumentNullException(nameof(comparer), - "A custom comparer must be supplied for IEnumerable values."); + throw new ArgumentNullException(nameof(comparer), "A custom comparer must be supplied for IEnumerable values."); } comparer = EqualityComparer.Default; diff --git a/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs b/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs index ad1152ae16c3..47ed9ec4abff 100644 --- a/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs +++ b/src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs @@ -30,9 +30,16 @@ public class UserMapDefinition : IMapDefinition private readonly ILocalizedTextService _textService; private readonly IUserService _userService; - public UserMapDefinition(ILocalizedTextService textService, IUserService userService, IEntityService entityService, ISectionService sectionService, - AppCaches appCaches, ActionCollection actions, IOptions globalSettings, - MediaFileManager mediaFileManager, IShortStringHelper shortStringHelper, + public UserMapDefinition( + ILocalizedTextService textService, + IUserService userService, + IEntityService entityService, + ISectionService sectionService, + AppCaches appCaches, + ActionCollection actions, + IOptions globalSettings, + MediaFileManager mediaFileManager, + IShortStringHelper shortStringHelper, IImageUrlGenerator imageUrlGenerator) { _sectionService = sectionService; diff --git a/src/Umbraco.Core/Models/MediaExtensions.cs b/src/Umbraco.Core/Models/MediaExtensions.cs index f665bda9c2cb..ee69c25de48b 100644 --- a/src/Umbraco.Core/Models/MediaExtensions.cs +++ b/src/Umbraco.Core/Models/MediaExtensions.cs @@ -9,8 +9,7 @@ public static class MediaExtensions /// /// Gets the URL of a media item. /// - public static string? GetUrl(this IMedia media, string propertyAlias, - MediaUrlGeneratorCollection mediaUrlGenerators) + public static string? GetUrl(this IMedia media, string propertyAlias, MediaUrlGeneratorCollection mediaUrlGenerators) { if (media.TryGetMediaPath(propertyAlias, mediaUrlGenerators, out var mediaPath)) { @@ -23,8 +22,7 @@ public static class MediaExtensions /// /// Gets the URLs of a media item. /// - public static string?[] GetUrls(this IMedia media, ContentSettings contentSettings, - MediaUrlGeneratorCollection mediaUrlGenerators) + public static string?[] GetUrls(this IMedia media, ContentSettings contentSettings, MediaUrlGeneratorCollection mediaUrlGenerators) => contentSettings.Imaging.AutoFillImageProperties .Select(field => media.GetUrl(field.Alias, mediaUrlGenerators)) .Where(link => string.IsNullOrWhiteSpace(link) == false) diff --git a/src/Umbraco.Core/Models/PropertyCollection.cs b/src/Umbraco.Core/Models/PropertyCollection.cs index ca0febd5bc80..dbb648df2947 100644 --- a/src/Umbraco.Core/Models/PropertyCollection.cs +++ b/src/Umbraco.Core/Models/PropertyCollection.cs @@ -44,7 +44,8 @@ public PropertyCollection(IEnumerable properties) /// public new void Add(IProperty property) { - lock (_addLocker) // TODO: why are we locking here and not everywhere else?! + // TODO: why are we locking here and not everywhere else?! + lock (_addLocker) { var key = GetKeyForItem(property); if (key != null) diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs index 2d501b569036..e8f6e3bdc1a7 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs @@ -10,17 +10,25 @@ public static class VariationContextAccessorExtensions { public static void ContextualizeVariation( this IVariationContextAccessor variationContextAccessor, - ContentVariation variations, ref string? culture, ref string? segment) + ContentVariation variations, + ref string? culture, + ref string? segment) => variationContextAccessor.ContextualizeVariation(variations, null, ref culture, ref segment); public static void ContextualizeVariation( this IVariationContextAccessor variationContextAccessor, - ContentVariation variations, int contentId, ref string? culture, ref string? segment) + ContentVariation variations, + int contentId, + ref string? culture, + ref string? segment) => variationContextAccessor.ContextualizeVariation(variations, (int?)contentId, ref culture, ref segment); private static void ContextualizeVariation( this IVariationContextAccessor variationContextAccessor, - ContentVariation variations, int? contentId, ref string? culture, ref string? segment) + ContentVariation variations, + int? contentId, + ref string? culture, + ref string? segment) { if (culture != null && segment != null) { diff --git a/src/Umbraco.Core/Models/ReadOnlyRelation.cs b/src/Umbraco.Core/Models/ReadOnlyRelation.cs index 7b0413453fb1..4388499e98de 100644 --- a/src/Umbraco.Core/Models/ReadOnlyRelation.cs +++ b/src/Umbraco.Core/Models/ReadOnlyRelation.cs @@ -17,8 +17,7 @@ public ReadOnlyRelation(int id, int parentId, int childId, int relationTypeId, D } public ReadOnlyRelation(int parentId, int childId, int relationTypeId) - : this(0, parentId, childId, relationTypeId, - DateTime.Now, string.Empty) + : this(0, parentId, childId, relationTypeId, DateTime.Now, string.Empty) { } diff --git a/src/Umbraco.Core/Models/Trees/MenuItem.cs b/src/Umbraco.Core/Models/Trees/MenuItem.cs index 1680c3d35585..3f77ccf2b608 100644 --- a/src/Umbraco.Core/Models/Trees/MenuItem.cs +++ b/src/Umbraco.Core/Models/Trees/MenuItem.cs @@ -33,8 +33,7 @@ public MenuItem(string alias, ILocalizedTextService textService) { Alias = alias; Name = textService.Localize("actions", Alias); - TextDescription = textService.Localize("visuallyHiddenTexts", alias + "_description", - Thread.CurrentThread.CurrentUICulture); + TextDescription = textService.Localize("visuallyHiddenTexts", alias + "_description", Thread.CurrentThread.CurrentUICulture); } /// @@ -56,7 +55,8 @@ public MenuItem(IAction action, string name = "") #region Properties - [IgnoreDataMember] public IAction? Action { get; set; } + [IgnoreDataMember] + public IAction? Action { get; set; } /// /// A dictionary to support any additional meta data that should be rendered for the node which is @@ -77,7 +77,8 @@ public MenuItem(IAction action, string name = "") [Required] public string? Alias { get; set; } - [DataMember(Name = "textDescription")] public string? TextDescription { get; set; } + [DataMember(Name = "textDescription")] + public string? TextDescription { get; set; } /// /// Ensures a menu separator will exist before this menu item @@ -85,7 +86,8 @@ public MenuItem(IAction action, string name = "") [DataMember(Name = "separator")] public bool SeparatorBefore { get; set; } - [DataMember(Name = "cssclass")] public string Icon { get; set; } + [DataMember(Name = "cssclass")] + public string Icon { get; set; } /// /// Used in the UI to inform the user that the menu item will open a dialog/confirmation diff --git a/src/Umbraco.Core/Notifications/ContentCopiedNotification.cs b/src/Umbraco.Core/Notifications/ContentCopiedNotification.cs index b28d6e444237..a5c6ede43293 100644 --- a/src/Umbraco.Core/Notifications/ContentCopiedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentCopiedNotification.cs @@ -8,8 +8,7 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class ContentCopiedNotification : CopiedNotification { - public ContentCopiedNotification(IContent original, IContent copy, int parentId, bool relateToOriginal, - EventMessages messages) + public ContentCopiedNotification(IContent original, IContent copy, int parentId, bool relateToOriginal, EventMessages messages) : base(original, copy, parentId, relateToOriginal, messages) { } diff --git a/src/Umbraco.Core/Notifications/DeletedVersionsNotification.cs b/src/Umbraco.Core/Notifications/DeletedVersionsNotification.cs index b709799afdb1..03b8e150b7dd 100644 --- a/src/Umbraco.Core/Notifications/DeletedVersionsNotification.cs +++ b/src/Umbraco.Core/Notifications/DeletedVersionsNotification.cs @@ -8,8 +8,7 @@ namespace Umbraco.Cms.Core.Notifications; public abstract class DeletedVersionsNotification : DeletedVersionsNotificationBase where T : class { - protected DeletedVersionsNotification(int id, EventMessages messages, int specificVersion = default, - bool deletePriorVersions = false, DateTime dateToRetain = default) + protected DeletedVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) { } diff --git a/src/Umbraco.Core/Notifications/DeletingVersionsNotification.cs b/src/Umbraco.Core/Notifications/DeletingVersionsNotification.cs index 2709fed0eac8..6b708da28b35 100644 --- a/src/Umbraco.Core/Notifications/DeletingVersionsNotification.cs +++ b/src/Umbraco.Core/Notifications/DeletingVersionsNotification.cs @@ -8,8 +8,7 @@ namespace Umbraco.Cms.Core.Notifications; public abstract class DeletingVersionsNotification : DeletedVersionsNotificationBase, ICancelableNotification where T : class { - protected DeletingVersionsNotification(int id, EventMessages messages, int specificVersion = default, - bool deletePriorVersions = false, DateTime dateToRetain = default) + protected DeletingVersionsNotification(int id, EventMessages messages, int specificVersion = default, bool deletePriorVersions = false, DateTime dateToRetain = default) : base(id, messages, specificVersion, deletePriorVersions, dateToRetain) { } diff --git a/src/Umbraco.Core/Notifications/UserUnlockedNotification.cs b/src/Umbraco.Core/Notifications/UserUnlockedNotification.cs index 4c6a686069c9..24ffe9f058b7 100644 --- a/src/Umbraco.Core/Notifications/UserUnlockedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserUnlockedNotification.cs @@ -5,7 +5,8 @@ public class UserUnlockedNotification : UserNotification public UserUnlockedNotification(string ipAddress, string affectedUserId, string performingUserId) : base( ipAddress, - affectedUserId, performingUserId) + affectedUserId, + performingUserId) { } } diff --git a/src/Umbraco.Core/Packaging/IPackageInstallation.cs b/src/Umbraco.Core/Packaging/IPackageInstallation.cs index 6417eb979b62..7fc714bfdbb8 100644 --- a/src/Umbraco.Core/Packaging/IPackageInstallation.cs +++ b/src/Umbraco.Core/Packaging/IPackageInstallation.cs @@ -17,8 +17,7 @@ public interface IPackageInstallation // Possibly, we could continue to persist that file so that you could uninstall package data for an installed package in the // back office (but it won't actually uninstall the package until you do that via nuget). If we want that functionality we'll have // to restore a bunch of deleted code. - InstallationSummary InstallPackageData(CompiledPackage compiledPackage, int userId, - out PackageDefinition packageDefinition); + InstallationSummary InstallPackageData(CompiledPackage compiledPackage, int userId, out PackageDefinition packageDefinition); /// /// Reads the package xml and returns the model diff --git a/src/Umbraco.Core/Packaging/PackageMigrationResource.cs b/src/Umbraco.Core/Packaging/PackageMigrationResource.cs index ef25ab0b3dd8..0d72cad38ac8 100644 --- a/src/Umbraco.Core/Packaging/PackageMigrationResource.cs +++ b/src/Umbraco.Core/Packaging/PackageMigrationResource.cs @@ -79,8 +79,7 @@ public static string GetEmbeddedPackageDataManifestHash(Type planType) return xml; } - public static bool TryGetEmbeddedPackageDataManifest(Type planType, out XDocument? packageXml, - out ZipArchive? zipArchive) + public static bool TryGetEmbeddedPackageDataManifest(Type planType, out XDocument? packageXml, out ZipArchive? zipArchive) { Stream? zipStream = GetEmbeddedPackageZipStream(planType); if (zipStream is not null) diff --git a/src/Umbraco.Core/Packaging/PackagesRepository.cs b/src/Umbraco.Core/Packaging/PackagesRepository.cs index 061219311e1b..a5982aef7e48 100644 --- a/src/Umbraco.Core/Packaging/PackagesRepository.cs +++ b/src/Umbraco.Core/Packaging/PackagesRepository.cs @@ -49,13 +49,17 @@ public class PackagesRepository : ICreatedPackagesRepository /// /// /// - /// + /// /// /// The file name for storing the package definitions (i.e. "createdPackages.config") /// /// /// /// + /// + /// + /// + /// public PackagesRepository( IContentService contentService, IContentTypeService contentTypeService, @@ -226,8 +230,7 @@ public string ExportPackage(PackageDefinition definition) PackageTemplates(definition, root); PackageStylesheets(definition, root); PackageStaticFiles(definition.Scripts, root, "Scripts", "Script", _fileSystems.ScriptsFileSystem); - PackageStaticFiles(definition.PartialViews, root, "PartialViews", "View", - _fileSystems.PartialViewsFileSystem); + PackageStaticFiles(definition.PartialViews, root, "PartialViews", "View", _fileSystems.PartialViewsFileSystem); PackageMacros(definition, root); PackageDictionaryItems(definition, root); PackageLanguages(definition, root); @@ -424,8 +427,7 @@ private void PackageDictionaryItems(PackageDefinition definition, XContainer roo if (processed.ContainsKey(dictionaryItem.ParentId.Value)) { // we've processed this parent element already so we can just append this xml child to it - AppendDictionaryElement(processed[dictionaryItem.ParentId.Value], items, processed, key, - serializedDictionaryValue); + AppendDictionaryElement(processed[dictionaryItem.ParentId.Value], items, processed, key, serializedDictionaryValue); } else if (items.ContainsKey(dictionaryItem.ParentId.Value)) { @@ -447,7 +449,9 @@ private void PackageDictionaryItems(PackageDefinition definition, XContainer roo static void AppendDictionaryElement( XElement rootDictionaryItems, Dictionary items, - Dictionary processed, Guid key, XElement serializedDictionaryValue) + Dictionary processed, + Guid key, + XElement serializedDictionaryValue) { // track it processed.Add(key, serializedDictionaryValue); @@ -631,9 +635,7 @@ private void PackageMediaTypes(PackageDefinition definition, XContainer root) private void PackageDocumentsAndTags(PackageDefinition definition, XContainer root) { // Documents and tags - if (string.IsNullOrEmpty(definition.ContentNodeId) == false && int.TryParse( - definition.ContentNodeId, - NumberStyles.Integer, CultureInfo.InvariantCulture, out var contentNodeId)) + if (string.IsNullOrEmpty(definition.ContentNodeId) == false && int.TryParse(definition.ContentNodeId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var contentNodeId)) { if (contentNodeId > 0) { @@ -757,8 +759,6 @@ void OnSerializedMedia(IMedia media, XElement xmlMedia) } // TODO: Delete this - - /// private XElement? GetMacroXml(int macroId, out IMacro? macro) { macro = _macroService.GetById(macroId); @@ -798,7 +798,9 @@ private void AddDocumentType(IContentType dt, HashSet dtl) if (dt.ParentId > 0) { IContentType? parent = _contentTypeService.Get(dt.ParentId); - if (parent != null) // could be a container + + // could be a container + if (parent != null) { AddDocumentType(parent, dtl); } @@ -815,7 +817,9 @@ private void AddMediaType(IMediaType mediaType, HashSet mediaTypes) if (mediaType.ParentId > 0) { IMediaType? parent = _mediaTypeService.Get(mediaType.ParentId); - if (parent != null) // could be a container + + // could be a container + if (parent != null) { AddMediaType(parent, mediaTypes); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IDocumentVersionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDocumentVersionRepository.cs index 652dbd12b871..7526d83cd0ad 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDocumentVersionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDocumentVersionRepository.cs @@ -17,8 +17,7 @@ public interface IDocumentVersionRepository : IRepository /// /// Gets paginated content versions for given content id paginated. /// - public IEnumerable? GetPagedItemsByContentId(int contentId, long pageIndex, int pageSize, - out long totalRecords, int? languageId = null); + public IEnumerable? GetPagedItemsByContentId(int contentId, long pageIndex, int pageSize, out long totalRecords, int? languageId = null); /// /// Deletes multiple content versions by ID. diff --git a/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs index 09f6513b2951..58475f802d12 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs @@ -16,8 +16,7 @@ public interface IMemberRepository : IContentRepository /// /// /// - IEnumerable FindMembersInRole(string roleName, string usernameToMatch, - StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); /// /// Get all members in a specific group diff --git a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs index bea9de47d2c5..a058a1b605d3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs @@ -7,8 +7,7 @@ namespace Umbraco.Cms.Core.Persistence.Repositories; public interface IRelationRepository : IReadWriteQueryRepository { - IEnumerable GetPagedRelationsByQuery(IQuery? query, long pageIndex, int pageSize, - out long totalRecords, Ordering? ordering); + IEnumerable GetPagedRelationsByQuery(IQuery? query, long pageIndex, int pageSize, out long totalRecords, Ordering? ordering); /// /// Persist multiple at once @@ -32,9 +31,7 @@ IEnumerable GetPagedRelationsByQuery(IQuery? query, long p /// void DeleteByParent(int parentId, params string[] relationTypeAliases); - IEnumerable GetPagedParentEntitiesByChildId(int childId, long pageIndex, int pageSize, - out long totalRecords, params Guid[] entityTypes); + IEnumerable GetPagedParentEntitiesByChildId(int childId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes); - IEnumerable GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, - out long totalRecords, params Guid[] entityTypes); + IEnumerable GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes); } diff --git a/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs index 7121a2b956e9..a69722c04a76 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs @@ -17,8 +17,7 @@ public interface ITrackedReferencesRepository /// /// The total count of the items with reference to the current item. /// An enumerable list of objects. - IEnumerable GetPagedRelationsForItem(int id, long pageIndex, int pageSize, - bool filterMustBeIsDependency, out long totalRecords); + IEnumerable GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords); /// /// Gets a page of items used in any kind of relation from selected integer ids. @@ -32,8 +31,7 @@ IEnumerable GetPagedRelationsForItem(int id, long pageIndex, int p /// /// The total count of the items in any kind of relation. /// An enumerable list of objects. - IEnumerable GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, - bool filterMustBeIsDependency, out long totalRecords); + IEnumerable GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords); /// /// Gets a page of the descending items that have any references, given a parent id. @@ -47,6 +45,5 @@ IEnumerable GetPagedItemsWithRelations(int[] ids, long pageIndex, /// /// The total count of descending items. /// An enumerable list of objects. - IEnumerable GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, - bool filterMustBeIsDependency, out long totalRecords); + IEnumerable GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs index 3eb6a684ef03..893a3c248e34 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IUserRepository.cs @@ -65,9 +65,16 @@ public interface IUserRepository : IReadWriteQueryRepository /// Optional parameter to filter by specified user state /// /// - IEnumerable GetPagedResultsByQuery(IQuery? query, long pageIndex, int pageSize, out long totalRecords, - Expression> orderBy, Direction orderDirection = Direction.Ascending, - string[]? includeUserGroups = null, string[]? excludeUserGroups = null, UserState[]? userState = null, + IEnumerable GetPagedResultsByQuery( + IQuery? query, + long pageIndex, + int pageSize, + out long totalRecords, + Expression> orderBy, + Direction orderDirection = Direction.Ascending, + string[]? includeUserGroups = null, + string[]? excludeUserGroups = null, + UserState[]? userState = null, IQuery? filter = null); /// diff --git a/src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs b/src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs index f3427b23d0df..5e038f0e7660 100644 --- a/src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/BlockListConfiguration.cs @@ -7,25 +7,19 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// public class BlockListConfiguration { - [ConfigurationField("blocks", "Available Blocks", - "views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.html", - Description = "Define the available blocks.")] + [ConfigurationField("blocks", "Available Blocks", "views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.html", Description = "Define the available blocks.")] public BlockConfiguration[] Blocks { get; set; } = null!; [ConfigurationField("validationLimit", "Amount", "numberrange", Description = "Set a required range of blocks")] public NumberRange ValidationLimit { get; set; } = new(); - [ConfigurationField("useLiveEditing", "Live editing mode", "boolean", - Description = - "Live editing in editor overlays for live updated custom views or labels using custom expression.")] + [ConfigurationField("useLiveEditing", "Live editing mode", "boolean", Description = "Live editing in editor overlays for live updated custom views or labels using custom expression.")] public bool UseLiveEditing { get; set; } - [ConfigurationField("useInlineEditingAsDefault", "Inline editing mode", "boolean", - Description = "Use the inline editor as the default block view.")] + [ConfigurationField("useInlineEditingAsDefault", "Inline editing mode", "boolean", Description = "Use the inline editor as the default block view.")] public bool UseInlineEditingAsDefault { get; set; } - [ConfigurationField("maxPropertyWidth", "Property editor width", "textstring", - Description = "optional css overwrite, example: 800px or 100%")] + [ConfigurationField("maxPropertyWidth", "Property editor width", "textstring", Description = "optional css overwrite, example: 800px or 100%")] public string? MaxPropertyWidth { get; set; } [DataContract] diff --git a/src/Umbraco.Core/PropertyEditors/MediaPickerConfiguration.cs b/src/Umbraco.Core/PropertyEditors/MediaPickerConfiguration.cs index 8d6d99671641..055f4fea4d7b 100644 --- a/src/Umbraco.Core/PropertyEditors/MediaPickerConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/MediaPickerConfiguration.cs @@ -8,12 +8,10 @@ public class MediaPickerConfiguration : IIgnoreUserStartNodesConfig [ConfigurationField("multiPicker", "Pick multiple items", "boolean")] public bool Multiple { get; set; } - [ConfigurationField("onlyImages", "Pick only images", "boolean", - Description = "Only let the editor choose images from media.")] + [ConfigurationField("onlyImages", "Pick only images", "boolean", Description = "Only let the editor choose images from media.")] public bool OnlyImages { get; set; } - [ConfigurationField("disableFolderSelect", "Disable folder select", "boolean", - Description = "Do not allow folders to be picked.")] + [ConfigurationField("disableFolderSelect", "Disable folder select", "boolean", Description = "Do not allow folders to be picked.")] public bool DisableFolderSelect { get; set; } [ConfigurationField("startNodeId", "Start node", "mediapicker")] @@ -21,7 +19,8 @@ public class MediaPickerConfiguration : IIgnoreUserStartNodesConfig [ConfigurationField( Constants.DataTypes.ReservedPreValueKeys.IgnoreUserStartNodes, - "Ignore User Start Nodes", "boolean", + "Ignore User Start Nodes", + "boolean", Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")] public bool IgnoreUserStartNodes { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/NoopPropertyCacheCompressionOptions.cs b/src/Umbraco.Core/PropertyEditors/NoopPropertyCacheCompressionOptions.cs index 54a6e6d2a780..f1d295bc3db6 100644 --- a/src/Umbraco.Core/PropertyEditors/NoopPropertyCacheCompressionOptions.cs +++ b/src/Umbraco.Core/PropertyEditors/NoopPropertyCacheCompressionOptions.cs @@ -8,6 +8,5 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// public sealed class NoopPropertyCacheCompressionOptions : IPropertyCacheCompressionOptions { - public bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor, - bool published) => false; + public bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor, bool published) => false; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/EyeDropperValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/EyeDropperValueConverter.cs index c0127fbf471c..b6bbff3b4196 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/EyeDropperValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/EyeDropperValueConverter.cs @@ -15,7 +15,6 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType) public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.Element; - public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, - PropertyCacheLevel cacheLevel, object? source, bool preview) + public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview) => source?.ToString() ?? string.Empty; } diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshotService.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshotService.cs index f2194b2aa85e..09de76ace5e6 100644 --- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshotService.cs +++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedSnapshotService.cs @@ -49,8 +49,7 @@ public void Notify(DomainCacheRefresher.JsonPayload[] payloads) { } - public void Rebuild(IReadOnlyCollection? contentTypeIds = null, IReadOnlyCollection? mediaTypeIds = null, - IReadOnlyCollection? memberTypeIds = null) + public void Rebuild(IReadOnlyCollection? contentTypeIds = null, IReadOnlyCollection? mediaTypeIds = null, IReadOnlyCollection? memberTypeIds = null) { } } diff --git a/src/Umbraco.Core/ReflectionUtilities.cs b/src/Umbraco.Core/ReflectionUtilities.cs index 3b81d360fd87..2a219fb53ab3 100644 --- a/src/Umbraco.Core/ReflectionUtilities.cs +++ b/src/Umbraco.Core/ReflectionUtilities.cs @@ -533,7 +533,8 @@ private static TLambda EmitConstructorSafe(Type[] lambdaParameters, Typ for (var i = 0; i < lambdaParameters.Length; i++) { - if (lambdaParameters[i] != ctorParameters[i]) // note: relax the constraint with IsAssignableFrom? + // note: relax the constraint with IsAssignableFrom? + if (lambdaParameters[i] != ctorParameters[i]) { ThrowInvalidLambda("ctor", ctorDeclaring, ctorParameters); } diff --git a/src/Umbraco.Core/Routing/AliasUrlProvider.cs b/src/Umbraco.Core/Routing/AliasUrlProvider.cs index e053a23703f0..d47680905a2e 100644 --- a/src/Umbraco.Core/Routing/AliasUrlProvider.cs +++ b/src/Umbraco.Core/Routing/AliasUrlProvider.cs @@ -75,7 +75,9 @@ public IEnumerable GetOtherUrls(int id, Uri current) // look for domains, walking up the tree IPublishedContent? n = node; IEnumerable? domainUris = DomainUtilities.DomainsForNode(umbracoContext.PublishedSnapshot.Domains, _siteDomainMapper, n.Id, current, false); - while (domainUris == null && n != null) // n is null at root + + // n is null at root + while (domainUris == null && n != null) { // move to parent node n = n.Parent; diff --git a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs index 18dcbeda2953..09db8464adf6 100644 --- a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs @@ -193,7 +193,8 @@ private Uri AssembleUrl(DomainAndUri? domainUri, string path, Uri current, UrlMo Uri uri; // ignore vdir at that point, UriFromUmbraco will do it - if (domainUri == null) // no domain was found + // no domain was found + if (domainUri == null) { if (current == null) { @@ -213,7 +214,9 @@ private Uri AssembleUrl(DomainAndUri? domainUri, string path, Uri current, UrlMo throw new ArgumentOutOfRangeException(nameof(mode)); } } - else // a domain was found + + // a domain was found + else { if (mode == UrlMode.Auto) { diff --git a/src/Umbraco.Core/Routing/DomainUtilities.cs b/src/Umbraco.Core/Routing/DomainUtilities.cs index 42e945f47968..e7fc788aef46 100644 --- a/src/Umbraco.Core/Routing/DomainUtilities.cs +++ b/src/Umbraco.Core/Routing/DomainUtilities.cs @@ -144,7 +144,8 @@ public static class DomainUtilities IReadOnlyCollection considerForBaseDomains = domainsAndUris; if (cultureDomains != null) { - if (cultureDomains.Count == 1) // only 1, return + // only 1, return + if (cultureDomains.Count == 1) { return cultureDomains.First(); } @@ -155,7 +156,9 @@ public static class DomainUtilities // look for domains that would be the base of the uri IReadOnlyCollection baseDomains = SelectByBase(considerForBaseDomains, uri, culture); - if (baseDomains.Count > 0) // found, return + + // found, return + if (baseDomains.Count > 0) { return baseDomains.First(); } @@ -351,7 +354,8 @@ private static IReadOnlyCollection SelectByBase( string? defaultCulture) { // we try our best to match cultures, but may end with a bogus domain - if (culture != null) // try the supplied culture + // try the supplied culture + if (culture != null) { var cultureDomains = domainsAndUris.Where(x => x.Culture.InvariantEquals(culture)).ToList(); if (cultureDomains.Count > 0) @@ -360,7 +364,8 @@ private static IReadOnlyCollection SelectByBase( } } - if (defaultCulture != null) // try the defaultCulture culture + // try the defaultCulture culture + if (defaultCulture != null) { var cultureDomains = domainsAndUris.Where(x => x.Culture.InvariantEquals(defaultCulture)).ToList(); if (cultureDomains.Count > 0) @@ -377,7 +382,8 @@ private static DomainAndUri GetByCulture(IReadOnlyCollection domai DomainAndUri? domainAndUri; // we try our best to match cultures, but may end with a bogus domain - if (culture != null) // try the supplied culture + // try the supplied culture + if (culture != null) { domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)); if (domainAndUri != null) @@ -386,7 +392,8 @@ private static DomainAndUri GetByCulture(IReadOnlyCollection domai } } - if (defaultCulture != null) // try the defaultCulture culture + // try the defaultCulture culture + if (defaultCulture != null) { domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(defaultCulture)); if (domainAndUri != null) diff --git a/src/Umbraco.Core/Routing/ISiteDomainMapper.cs b/src/Umbraco.Core/Routing/ISiteDomainMapper.cs index d5e81c6eb317..93afe32d9359 100644 --- a/src/Umbraco.Core/Routing/ISiteDomainMapper.cs +++ b/src/Umbraco.Core/Routing/ISiteDomainMapper.cs @@ -25,8 +25,7 @@ public interface ISiteDomainMapper /// /// The filter _must_ return something else an exception will be thrown. /// - DomainAndUri? MapDomain(IReadOnlyCollection domainAndUris, Uri current, string? culture, - string? defaultCulture); + DomainAndUri? MapDomain(IReadOnlyCollection domainAndUris, Uri current, string? culture, string? defaultCulture); /// /// Filters a list of DomainAndUri to pick those that best matches the current request. @@ -44,6 +43,5 @@ public interface ISiteDomainMapper /// to help pick the best matches. /// /// - IEnumerable MapDomains(IReadOnlyCollection domainAndUris, Uri current, - bool excludeDefault, string? culture, string? defaultCulture); + IEnumerable MapDomains(IReadOnlyCollection domainAndUris, Uri current, bool excludeDefault, string? culture, string? defaultCulture); } diff --git a/src/Umbraco.Core/Routing/PublishedRequest.cs b/src/Umbraco.Core/Routing/PublishedRequest.cs index 6e0a07da7895..e3fc3818ef9a 100644 --- a/src/Umbraco.Core/Routing/PublishedRequest.cs +++ b/src/Umbraco.Core/Routing/PublishedRequest.cs @@ -8,10 +8,20 @@ public class PublishedRequest : IPublishedRequest /// /// Initializes a new instance of the class. /// - public PublishedRequest(Uri uri, string absolutePathDecoded, IPublishedContent? publishedContent, - bool isInternalRedirect, ITemplate? template, DomainAndUri? domain, string? culture, string? redirectUrl, - int? responseStatusCode, IReadOnlyList? cacheExtensions, IReadOnlyDictionary? headers, - bool setNoCacheHeader, bool ignorePublishedContentCollisions) + public PublishedRequest( + Uri uri, + string absolutePathDecoded, + IPublishedContent? publishedContent, + bool isInternalRedirect, + ITemplate? template, + DomainAndUri? domain, + string? culture, + string? redirectUrl, + int? responseStatusCode, + IReadOnlyList? cacheExtensions, + IReadOnlyDictionary? headers, + bool setNoCacheHeader, + bool ignorePublishedContentCollisions) { Uri = uri ?? throw new ArgumentNullException(nameof(uri)); AbsolutePathDecoded = absolutePathDecoded ?? throw new ArgumentNullException(nameof(absolutePathDecoded)); diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs index 256e9b4d499b..f155c7515caf 100644 --- a/src/Umbraco.Core/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -326,7 +326,10 @@ bool IsPublishedContentDomain(Domain domain) { _logger.LogDebug( "{TracePrefix}Matches domain={Domain}, rootId={RootContentId}, culture={Culture}", - tracePrefix, domainAndUri.Name, domainAndUri.ContentId, domainAndUri.Culture); + tracePrefix, + domainAndUri.Name, + domainAndUri.ContentId, + domainAndUri.Culture); } request.SetDomain(domainAndUri); @@ -378,8 +381,7 @@ internal void HandleWildcardDomains(IPublishedRequestBuilder request) var rootNodeId = request.Domain != null ? request.Domain.ContentId : (int?)null; IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); Domain? domain = - DomainUtilities.FindWildcardDomainInPath(umbracoContext.PublishedSnapshot.Domains?.GetAll(true), nodePath, - rootNodeId); + DomainUtilities.FindWildcardDomainInPath(umbracoContext.PublishedSnapshot.Domains?.GetAll(true), nodePath, rootNodeId); // always has a contentId and a culture if (domain != null) @@ -389,7 +391,9 @@ internal void HandleWildcardDomains(IPublishedRequestBuilder request) { _logger.LogDebug( "{TracePrefix}Got domain on node {DomainContentId}, set culture to {CultureName}", - tracePrefix, domain.ContentId, request.Culture); + tracePrefix, + domain.ContentId, + request.Culture); } } else @@ -401,7 +405,7 @@ internal void HandleWildcardDomains(IPublishedRequestBuilder request) } } - internal bool FindTemplateRenderingEngineInDirectory(DirectoryInfo directory, string alias, string[] extensions) + internal bool FindTemplateRenderingEngineInDirectory(DirectoryInfo? directory, string alias, string[] extensions) { if (directory == null || directory.Exists == false) { @@ -585,7 +589,8 @@ private bool FollowInternalRedirects(IPublishedRequestBuilder request) IPublishedContent? internalRedirectNode = null; var internalRedirectId = request.PublishedContent.Value( _publishedValueFallback, - Constants.Conventions.Content.InternalRedirectId, defaultValue: -1); + Constants.Conventions.Content.InternalRedirectId, + defaultValue: -1); IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); if (internalRedirectId > 0) @@ -614,8 +619,7 @@ private bool FollowInternalRedirects(IPublishedRequestBuilder request) { _logger.LogDebug( "FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: value is not an int nor a GuidUdi.", - request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId) - ?.GetSourceValue()); + request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId)?.GetSourceValue()); } } @@ -625,8 +629,7 @@ private bool FollowInternalRedirects(IPublishedRequestBuilder request) { _logger.LogDebug( "FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: no such published document.", - request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId) - ?.GetSourceValue()); + request.PublishedContent.GetProperty(Constants.Conventions.Content.InternalRedirectId)?.GetSourceValue()); } } else if (internalRedirectId == request.PublishedContent.Id) @@ -718,7 +721,8 @@ private void FindTemplate(IPublishedRequestBuilder request, bool contentFoundByF { _logger.LogDebug( "FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", - template.Id, template.Alias); + template.Id, + template.Alias); } } else @@ -764,7 +768,8 @@ private void FindTemplate(IPublishedRequestBuilder request, bool contentFoundByF { _logger.LogDebug( "FindTemplate: Got alternative template id={TemplateId} alias={TemplateAlias}", - template.Id, template.Alias); + template.Id, + template.Alias); } } else @@ -781,7 +786,8 @@ private void FindTemplate(IPublishedRequestBuilder request, bool contentFoundByF { _logger.LogWarning( "FindTemplate: Alternative template {TemplateAlias} is not allowed on node {NodeId}, ignoring.", - altTemplate, request.PublishedContent.Id); + altTemplate, + request.PublishedContent.Id); // no allowed, back to default var templateId = request.PublishedContent.TemplateId; @@ -791,7 +797,8 @@ private void FindTemplate(IPublishedRequestBuilder request, bool contentFoundByF { _logger.LogDebug( "FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", - template?.Id, template?.Alias); + template?.Id, + template?.Alias); } } } @@ -845,8 +852,7 @@ private void FindTemplate(IPublishedRequestBuilder request, bool contentFoundByF if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("GetTemplateModel: Got template id={TemplateId} alias={TemplateAlias}", template.Id, - template.Alias); + _logger.LogDebug("GetTemplateModel: Got template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias); } return template; @@ -869,8 +875,7 @@ private void FollowExternalRedirect(IPublishedRequestBuilder request) return; } - var redirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.Redirect, - defaultValue: -1); + var redirectId = request.PublishedContent.Value(_publishedValueFallback, Constants.Conventions.Content.Redirect, defaultValue: -1); var redirectUrl = "#"; if (redirectId > 0) { diff --git a/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs b/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs index c35c1a828aa2..8d7ec082be1d 100644 --- a/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs +++ b/src/Umbraco.Core/Runtime/EssentialDirectoryCreator.cs @@ -13,8 +13,7 @@ public class EssentialDirectoryCreator : INotificationHandler globalSettings) + public EssentialDirectoryCreator(IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, IOptions globalSettings) { _ioHelper = ioHelper; _hostingEnvironment = hostingEnvironment; diff --git a/src/Umbraco.Core/Security/IdentityUserToken.cs b/src/Umbraco.Core/Security/IdentityUserToken.cs index b8c41c936bb1..f4fcd46ace25 100644 --- a/src/Umbraco.Core/Security/IdentityUserToken.cs +++ b/src/Umbraco.Core/Security/IdentityUserToken.cs @@ -18,8 +18,7 @@ public IdentityUserToken(string loginProvider, string? name, string? value, stri /// /// Initializes a new instance of the class. /// - public IdentityUserToken(int id, string? loginProvider, string? name, string? value, string userId, - DateTime createDate) + public IdentityUserToken(int id, string? loginProvider, string? name, string? value, string userId, DateTime createDate) { Id = id; LoginProvider = loginProvider ?? throw new ArgumentNullException(nameof(loginProvider)); diff --git a/src/Umbraco.Core/Semver/Semver.cs b/src/Umbraco.Core/Semver/Semver.cs index 9af4a1aa40ee..3c33f4308749 100644 --- a/src/Umbraco.Core/Semver/Semver.cs +++ b/src/Umbraco.Core/Semver/Semver.cs @@ -40,7 +40,8 @@ public sealed class SemVersion : IComparable, IComparable, ISerializ #endif { private static Regex parseEx = - new(@"^(?\d+)" + + new( + @"^(?\d+)" + @"(\.(?\d+))?" + @"(\.(?\d+))?" + @"(\-(?
[0-9A-Za-z\-\.]+))?" +
@@ -88,8 +89,8 @@ public SemVersion(int major, int minor = 0, int patch = 0, string prerelease = "
             Minor = minor;
             Patch = patch;
 
-            Prerelease = prerelease ?? "";
-            Build = build ?? "";
+            Prerelease = prerelease ?? string.Empty;
+            Build = build ?? string.Empty;
         }
 
         /// 
@@ -252,8 +253,7 @@ public static int Compare(SemVersion versionA, SemVersion versionB)
         /// The prerelease text.
         /// The build text.
         /// The new version object.
-        public SemVersion Change(int? major = null, int? minor = null, int? patch = null,
-            string? prerelease = null, string? build = null) =>
+        public SemVersion Change(int? major = null, int? minor = null, int? patch = null, string? prerelease = null, string? build = null) =>
             new(
                 major ?? Major,
                 minor ?? Minor,
@@ -309,7 +309,7 @@ public SemVersion Change(int? major = null, int? minor = null, int? patch = null
         /// 
         public override string ToString()
         {
-            var version = "" + Major + "." + Minor + "." + Patch;
+            var version = string.Empty + Major + "." + Minor + "." + Patch;
             if (!string.IsNullOrEmpty(Prerelease))
             {
                 version += "-" + Prerelease;
diff --git a/src/Umbraco.Core/Services/AuditService.cs b/src/Umbraco.Core/Services/AuditService.cs
index ec82c040c876..046c5fff3dba 100644
--- a/src/Umbraco.Core/Services/AuditService.cs
+++ b/src/Umbraco.Core/Services/AuditService.cs
@@ -13,9 +13,12 @@ public sealed class AuditService : RepositoryService, IAuditService
     private readonly IAuditRepository _auditRepository;
     private readonly Lazy _isAvailable;
 
-    public AuditService(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
+    public AuditService(
+        ICoreScopeProvider provider,
+        ILoggerFactory loggerFactory,
         IEventMessagesFactory eventMessagesFactory,
-        IAuditRepository auditRepository, IAuditEntryRepository auditEntryRepository)
+        IAuditRepository auditRepository,
+        IAuditEntryRepository auditEntryRepository)
         : base(provider, loggerFactory, eventMessagesFactory)
     {
         _auditRepository = auditRepository;
@@ -23,8 +26,7 @@ public AuditService(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
         _isAvailable = new Lazy(DetermineIsAvailable);
     }
 
-    public void Add(AuditType type, int userId, int objectId, string? entityType, string comment,
-        string? parameters = null)
+    public void Add(AuditType type, int userId, int objectId, string? entityType, string comment, string? parameters = null)
     {
         using (ICoreScope scope = ScopeProvider.CreateCoreScope())
         {
@@ -97,7 +99,10 @@ public void CleanLogs(int maximumAgeOfLogsInMinutes)
     ///     Optional filter to be applied
     /// 
     /// 
-    public IEnumerable GetPagedItemsByEntity(int entityId, long pageIndex, int pageSize,
+    public IEnumerable GetPagedItemsByEntity(
+        int entityId,
+        long pageIndex,
+        int pageSize,
         out long totalRecords,
         Direction orderDirection = Direction.Descending,
         AuditType[]? auditTypeFilter = null,
@@ -123,8 +128,7 @@ public IEnumerable GetPagedItemsByEntity(int entityId, long pageInde
         {
             IQuery query = Query().Where(x => x.Id == entityId);
 
-            return _auditRepository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderDirection,
-                auditTypeFilter, customFilter);
+            return _auditRepository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderDirection, auditTypeFilter, customFilter);
         }
     }
 
@@ -147,8 +151,13 @@ public IEnumerable GetPagedItemsByEntity(int entityId, long pageInde
     ///     Optional filter to be applied
     /// 
     /// 
-    public IEnumerable GetPagedItemsByUser(int userId, long pageIndex, int pageSize, out long totalRecords,
-        Direction orderDirection = Direction.Descending, AuditType[]? auditTypeFilter = null,
+    public IEnumerable GetPagedItemsByUser(
+        int userId,
+        long pageIndex,
+        int pageSize,
+        out long totalRecords,
+        Direction orderDirection = Direction.Descending,
+        AuditType[]? auditTypeFilter = null,
         IQuery? customFilter = null)
     {
         if (pageIndex < 0)
@@ -171,14 +180,12 @@ public IEnumerable GetPagedItemsByUser(int userId, long pageIndex, i
         {
             IQuery query = Query().Where(x => x.UserId == userId);
 
-            return _auditRepository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderDirection,
-                auditTypeFilter, customFilter);
+            return _auditRepository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderDirection, auditTypeFilter, customFilter);
         }
     }
 
     /// 
-    public IAuditEntry Write(int performingUserId, string perfomingDetails, string performingIp, DateTime eventDateUtc,
-        int affectedUserId, string? affectedDetails, string eventType, string eventDetails)
+    public IAuditEntry Write(int performingUserId, string perfomingDetails, string performingIp, DateTime eventDateUtc, int affectedUserId, string? affectedDetails, string eventType, string eventDetails)
     {
         if (performingUserId < 0 && performingUserId != Constants.Security.SuperUserId)
         {
diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs
index eb2634709051..1cc0081cbb46 100644
--- a/src/Umbraco.Core/Services/ContentService.cs
+++ b/src/Umbraco.Core/Services/ContentService.cs
@@ -1585,9 +1585,11 @@ void SaveDocument(IContent c)
             return new PublishResult(PublishResultType.FailedUnpublish, eventMessages, content); // bah
         }
 
-        if (publishing) // we have tried to publish
+        // we have tried to publish
+        if (publishing)
         {
-            if (publishResult?.Success ?? false) // and succeeded, trigger events
+            // and succeeded, trigger events
+            if (publishResult?.Success ?? false)
             {
                 if (isNew == false && previouslyPublished == false)
                 {
@@ -1599,7 +1601,8 @@ void SaveDocument(IContent c)
                 }
 
                 // invalidate the node/branch
-                if (!branchOne) // for branches, handled by SaveAndPublishBranch
+                // for branches, handled by SaveAndPublishBranch
+                if (!branchOne)
                 {
                     scope.Notifications.Publish(
                         new ContentTreeChangeNotification(content, changeType, eventMessages));
@@ -1688,8 +1691,7 @@ public IEnumerable PerformScheduledPublish(DateTime date)
         return results;
     }
 
-    private void PerformScheduledPublishingExpiration(DateTime date, List results,
-        EventMessages evtMsgs, Lazy> allLangs)
+    private void PerformScheduledPublishingExpiration(DateTime date, List results, EventMessages evtMsgs, Lazy> allLangs)
     {
         using ICoreScope scope = ScopeProvider.CreateCoreScope();
 
@@ -1726,6 +1728,7 @@ private void PerformScheduledPublishingExpiration(DateTime date, List SaveAndPublishBranch(IContent content, bool fo
             var isRoot = c.Id == content.Id;
             HashSet? culturesToPublish = null;
 
-            if (!c.ContentType.VariesByCulture()) // invariant content type
+            // invariant content type
+            if (!c.ContentType.VariesByCulture())
             {
                 return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force);
             }
@@ -1999,7 +2002,8 @@ public IEnumerable SaveAndPublishBranch(IContent content, bool fo
             var isRoot = c.Id == content.Id;
             HashSet? culturesToPublish = null;
 
-            if (!c.ContentType.VariesByCulture()) // invariant content type
+            // invariant content type
+            if (!c.ContentType.VariesByCulture())
             {
                 return SaveAndPublishBranch_ShouldPublish(ref culturesToPublish, "*", c.Published, c.Edited, isRoot, force);
             }
@@ -2314,8 +2318,10 @@ public void DeleteVersion(int id, int versionId, bool deletePriorVersions, int u
 
             scope.WriteLock(Constants.Locks.ContentTree);
             IContent? c = _documentRepository.Get(id);
+
+            // don't delete the current or published version
             if (c?.VersionId != versionId &&
-                c?.PublishedVersionId != versionId) // don't delete the current or published version
+                c?.PublishedVersionId != versionId)
             {
                 _documentRepository.DeleteVersion(versionId);
             }
diff --git a/src/Umbraco.Core/Services/ContentServiceExtensions.cs b/src/Umbraco.Core/Services/ContentServiceExtensions.cs
index 6fd538181c51..b042612b1afc 100644
--- a/src/Umbraco.Core/Services/ContentServiceExtensions.cs
+++ b/src/Umbraco.Core/Services/ContentServiceExtensions.cs
@@ -44,8 +44,7 @@ public static class ContentServiceExtensions
     /// 
     /// 
     /// 
-    public static IContent CreateContent(this IContentService contentService, string name, Udi parentId,
-        string contentTypeAlias, int userId = Constants.Security.SuperUserId)
+    public static IContent CreateContent(this IContentService contentService, string name, Udi parentId, string contentTypeAlias, int userId = Constants.Security.SuperUserId)
     {
         if (parentId is not GuidUdi guidUdi)
         {
@@ -65,8 +64,7 @@ public static IContent CreateContent(this IContentService contentService, string
     public static void RemoveContentPermissions(this IContentService contentService, int contentId) =>
         contentService.SetPermissions(new EntityPermissionSet(contentId, new EntityPermissionCollection()));
 
-    public static IEnumerable GetAnchorValuesFromRTEs(this IContentService contentService, int id,
-        string? culture = "*")
+    public static IEnumerable GetAnchorValuesFromRTEs(this IContentService contentService, int id, string? culture = "*")
     {
         var result = new List();
         IContent? content = contentService.GetById(id);
diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs
index e655d79f0c5b..39adcf0daf72 100644
--- a/src/Umbraco.Core/Services/ContentTypeService.cs
+++ b/src/Umbraco.Core/Services/ContentTypeService.cs
@@ -13,13 +13,17 @@ namespace Umbraco.Cms.Core.Services;
 /// 
 public class ContentTypeService : ContentTypeServiceBase, IContentTypeService
 {
-    public ContentTypeService(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
-        IEventMessagesFactory eventMessagesFactory, IContentService contentService,
-        IContentTypeRepository repository, IAuditRepository auditRepository,
-        IDocumentTypeContainerRepository entityContainerRepository, IEntityRepository entityRepository,
+    public ContentTypeService(
+        ICoreScopeProvider provider,
+        ILoggerFactory loggerFactory,
+        IEventMessagesFactory eventMessagesFactory,
+        IContentService contentService,
+        IContentTypeRepository repository,
+        IAuditRepository auditRepository,
+        IDocumentTypeContainerRepository entityContainerRepository,
+        IEntityRepository entityRepository,
         IEventAggregator eventAggregator)
-        : base(provider, loggerFactory, eventMessagesFactory, repository, auditRepository, entityContainerRepository,
-            entityRepository, eventAggregator) =>
+        : base(provider, loggerFactory, eventMessagesFactory, repository, auditRepository, entityContainerRepository, entityRepository, eventAggregator) =>
         ContentService = contentService;
 
     // beware! order is important to avoid deadlocks
diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBase.cs b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs
index 5bacf5102e6c..7549cd849c65 100644
--- a/src/Umbraco.Core/Services/ContentTypeServiceBase.cs
+++ b/src/Umbraco.Core/Services/ContentTypeServiceBase.cs
@@ -6,8 +6,7 @@ namespace Umbraco.Cms.Core.Services;
 
 public abstract class ContentTypeServiceBase : RepositoryService
 {
-    protected ContentTypeServiceBase(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
-        IEventMessagesFactory eventMessagesFactory)
+    protected ContentTypeServiceBase(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory)
         : base(provider, loggerFactory, eventMessagesFactory)
     {
     }
diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
index 9026a18ed76e..339128e41d18 100644
--- a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
+++ b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
@@ -23,9 +23,13 @@ public abstract class ContentTypeServiceBase : ContentTypeSe
     private readonly IEntityRepository _entityRepository;
     private readonly IEventAggregator _eventAggregator;
 
-    protected ContentTypeServiceBase(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
+    protected ContentTypeServiceBase(
+        ICoreScopeProvider provider,
+        ILoggerFactory loggerFactory,
         IEventMessagesFactory eventMessagesFactory,
-        TRepository repository, IAuditRepository auditRepository, IEntityContainerRepository containerRepository,
+        TRepository repository,
+        IAuditRepository auditRepository,
+        IEntityContainerRepository containerRepository,
         IEntityRepository entityRepository,
         IEventAggregator eventAggregator)
         : base(provider, loggerFactory, eventMessagesFactory)
@@ -38,7 +42,9 @@ protected ContentTypeServiceBase(ICoreScopeProvider provider, ILoggerFactory log
     }
 
     protected TRepository Repository { get; }
+
     protected abstract int[] WriteLockIds { get; }
+
     protected abstract int[] ReadLockIds { get; }
 
     #region Move
@@ -98,8 +104,7 @@ protected ContentTypeServiceBase(ICoreScopeProvider provider, ILoggerFactory log
     #region Audit
 
     private void Audit(AuditType type, int userId, int objectId) =>
-        _auditRepository.Save(new AuditItem(objectId, type, userId,
-            ObjectTypes.GetUmbracoObjectType(ContainedObjectType).GetName()));
+        _auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetUmbracoObjectType(ContainedObjectType).GetName()));
 
     #endregion
 
@@ -107,32 +112,27 @@ private void Audit(AuditType type, int userId, int objectId) =>
 
     protected abstract SavingNotification GetSavingNotification(TItem item, EventMessages eventMessages);
 
-    protected abstract SavingNotification GetSavingNotification(IEnumerable items,
-        EventMessages eventMessages);
+    protected abstract SavingNotification GetSavingNotification(IEnumerable items, EventMessages eventMessages);
 
     protected abstract SavedNotification GetSavedNotification(TItem item, EventMessages eventMessages);
 
-    protected abstract SavedNotification GetSavedNotification(IEnumerable items,
-        EventMessages eventMessages);
+    protected abstract SavedNotification GetSavedNotification(IEnumerable items, EventMessages eventMessages);
 
     protected abstract DeletingNotification GetDeletingNotification(TItem item, EventMessages eventMessages);
 
-    protected abstract DeletingNotification GetDeletingNotification(IEnumerable items,
-        EventMessages eventMessages);
+    protected abstract DeletingNotification GetDeletingNotification(IEnumerable items, EventMessages eventMessages);
 
-    protected abstract DeletedNotification GetDeletedNotification(IEnumerable items,
-        EventMessages eventMessages);
+    protected abstract DeletedNotification GetDeletedNotification(IEnumerable items, EventMessages eventMessages);
 
-    protected abstract MovingNotification GetMovingNotification(MoveEventInfo moveInfo,
-        EventMessages eventMessages);
+    protected abstract MovingNotification GetMovingNotification(MoveEventInfo moveInfo, EventMessages eventMessages);
 
-    protected abstract MovedNotification GetMovedNotification(IEnumerable> moveInfo,
-        EventMessages eventMessages);
+    protected abstract MovedNotification GetMovedNotification(IEnumerable> moveInfo, EventMessages eventMessages);
 
     protected abstract ContentTypeChangeNotification GetContentTypeChangedNotification(
         IEnumerable> changes, EventMessages eventMessages);
 
     // This notification is identical to GetTypeChangeNotification, however it needs to be a different notification type because it's published within the transaction
+
     /// The purpose of this notification being published within the transaction is so that listeners can perform database
     /// operations from within the same transaction and guarantee data consistency so that if anything goes wrong
     /// the entire transaction can be rolled back. This is used by Nucache.
@@ -168,7 +168,6 @@ protected void ValidateLocked(TItem compositionContentType)
 
         // eg maybe a property has been added, with an alias that's OK (no conflict with ancestors)
         // but that cannot be used (conflict with descendants)
-
         IContentTypeComposition[] allContentTypes =
             Repository.GetMany(new int[0]).Cast().ToArray();
 
@@ -176,8 +175,7 @@ protected void ValidateLocked(TItem compositionContentType)
         IEnumerable compositions =
             allContentTypes.Where(x => compositionAliases.Any(y => x.Alias.Equals(y)));
         var propertyTypeAliases = compositionContentType.PropertyTypes.Select(x => x.Alias).ToArray();
-        var propertyGroupAliases = compositionContentType.PropertyGroups.ToDictionary(x => x.Alias, x => x.Type,
-            StringComparer.InvariantCultureIgnoreCase);
+        var propertyGroupAliases = compositionContentType.PropertyGroups.ToDictionary(x => x.Alias, x => x.Type, StringComparer.InvariantCultureIgnoreCase);
         IEnumerable indirectReferences =
             allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == compositionContentType.Id));
         var comparer = new DelegateEqualityComparer((x, y) => x?.Id == y?.Id, x => x.Id);
@@ -249,10 +247,8 @@ protected void ValidateLocked(TItem compositionContentType)
         }
 
         if (duplicatePropertyTypeAliases.Count > 0 || invalidPropertyGroupAliases.Count > 0)
-
         {
-            throw new InvalidCompositionException(compositionContentType.Alias, null,
-                duplicatePropertyTypeAliases.Distinct().ToArray(), invalidPropertyGroupAliases.Distinct().ToArray());
+            throw new InvalidCompositionException(compositionContentType.Alias, null, duplicatePropertyTypeAliases.Distinct().ToArray(), invalidPropertyGroupAliases.Distinct().ToArray());
         }
     }
 
@@ -274,7 +270,6 @@ internal IEnumerable> ComposeContentTypeChanges(params
         // note
         // this is meant to run *after* uow.Commit() so must use WasPropertyDirty() everywhere
         // instead of IsPropertyDirty() since dirty properties have been reset already
-
         var changes = new List>();
 
         foreach (TItem contentType in contentTypes)
@@ -350,8 +345,7 @@ internal IEnumerable> ComposeContentTypeChanges(params
     }
 
     // ensures changes contains no duplicates
-    private static void AddChange(ICollection> changes, TItem contentType,
-        ContentTypeChangeTypes changeTypes)
+    private static void AddChange(ICollection> changes, TItem contentType, ContentTypeChangeTypes changeTypes)
     {
         ContentTypeChange? change = changes.FirstOrDefault(x => x.Item == contentType);
         if (change == null)
@@ -413,7 +407,6 @@ public IEnumerable GetAll(IEnumerable? ids)
         }
 
         using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
-
         {
             scope.ReadLock(ReadLockIds);
             return Repository.GetMany(ids.ToArray());
@@ -578,7 +571,6 @@ public void Save(TItem? item, int userId = Constants.Security.SuperUserId)
         }
 
         using (ICoreScope scope = ScopeProvider.CreateCoreScope())
-
         {
             EventMessages eventMessages = EventMessagesFactory.Get();
             SavingNotification savingNotification = GetSavingNotification(item, eventMessages);
@@ -666,7 +658,6 @@ public void Save(IEnumerable items, int userId = Constants.Security.Super
 
             // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info.
             _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages));
-            ;
 
             scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages));
 
@@ -720,7 +711,8 @@ public void Delete(TItem item, int userId = Constants.Security.SuperUserId)
             {
                 reference.AllowedContentTypes = reference.AllowedContentTypes?.Where(p => p.Id.Value != item.Id);
                 var changedRef =
-                    new List> {new(reference, ContentTypeChangeTypes.RefreshMain)};
+                    new List> { new(reference, ContentTypeChangeTypes.RefreshMain) };
+
                 // Fire change event
                 scope.Notifications.Publish(GetContentTypeChangedNotification(changedRef, eventMessages));
             }
@@ -736,8 +728,7 @@ public void Delete(TItem item, int userId = Constants.Security.SuperUserId)
             ContentTypeChange[] changes = descendantsAndSelf
                 .Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.Remove))
                 .Concat(changed.Select(x =>
-                    new ContentTypeChange(x,
-                        ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther)))
+                    new ContentTypeChange(x, ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther)))
                 .ToArray();
 
             // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info.
@@ -797,8 +788,7 @@ public void Delete(IEnumerable items, int userId = Constants.Security.Sup
             ContentTypeChange[] changes = allDescendantsAndSelf
                 .Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.Remove))
                 .Concat(changed.Select(x =>
-                    new ContentTypeChange(x,
-                        ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther)))
+                    new ContentTypeChange(x, ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther)))
                 .ToArray();
 
             // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info.
@@ -851,8 +841,7 @@ public TItem Copy(TItem original, string alias, string name, TItem? parent)
 
         if (string.IsNullOrWhiteSpace(alias))
         {
-            throw new ArgumentException("Value can't be empty or consist only of white-space characters.",
-                nameof(alias));
+            throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(alias));
         }
 
         if (parent != null && parent.HasIdentity == false)
@@ -861,30 +850,30 @@ public TItem Copy(TItem original, string alias, string name, TItem? parent)
         }
 
         // this is illegal
-        //var originalb = (ContentTypeCompositionBase)original;
+        // var originalb = (ContentTypeCompositionBase)original;
         // but we *know* it has to be a ContentTypeCompositionBase anyways
         var originalb = (ContentTypeCompositionBase)(object)original;
         var clone = (TItem)(object)originalb.DeepCloneWithResetIdentities(alias);
 
         clone.Name = name;
 
-        //remove all composition that is not it's current alias
-        var compositionAliases = clone.CompositionAliases().Except(new[] {alias}).ToList();
+        // remove all composition that is not it's current alias
+        var compositionAliases = clone.CompositionAliases().Except(new[] { alias }).ToList();
         foreach (var a in compositionAliases)
         {
             clone.RemoveContentType(a);
         }
 
-        //if a parent is specified set it's composition and parent
+        // if a parent is specified set it's composition and parent
         if (parent != null)
         {
-            //add a new parent composition
+            // add a new parent composition
             clone.AddContentType(parent);
             clone.ParentId = parent.Id;
         }
         else
         {
-            //set to root
+            // set to root
             clone.ParentId = -1;
         }
 
@@ -916,7 +905,7 @@ public TItem Copy(TItem original, string alias, string name, TItem? parent)
                 var alias = Repository.GetUniqueAlias(copying.Alias);
 
                 // this is illegal
-                //var copyingb = (ContentTypeCompositionBase) copying;
+                // var copyingb = (ContentTypeCompositionBase) copying;
                 // but we *know* it has to be a ContentTypeCompositionBase anyways
                 var copyingb = (ContentTypeCompositionBase)(object)copying;
                 copy = (TItem)(object)copyingb.DeepCloneWithResetIdentities(alias);
@@ -940,8 +929,7 @@ public TItem Copy(TItem original, string alias, string name, TItem? parent)
             }
             catch (DataOperationException ex)
             {
-                return OperationResult.Attempt.Fail(ex.Operation,
-                    evtMsgs); // causes rollback
+                return OperationResult.Attempt.Fail(ex.Operation, evtMsgs); // causes rollback
             }
         }
 
@@ -956,8 +944,7 @@ public TItem Copy(TItem original, string alias, string name, TItem? parent)
 
     protected Guid ContainerObjectType => EntityContainer.GetContainerObjectType(ContainedObjectType);
 
-    public Attempt?> CreateContainer(int parentId, Guid key,
-        string name, int userId = Constants.Security.SuperUserId)
+    public Attempt?> CreateContainer(int parentId, Guid key, string name, int userId = Constants.Security.SuperUserId)
     {
         EventMessages eventMessages = EventMessagesFactory.Get();
         using (ICoreScope scope = ScopeProvider.CreateCoreScope())
@@ -968,7 +955,10 @@ public TItem Copy(TItem original, string alias, string name, TItem? parent)
             {
                 var container = new EntityContainer(ContainedObjectType)
                 {
-                    Name = name, ParentId = parentId, CreatorId = userId, Key = key
+                    Name = name,
+                    ParentId = parentId,
+                    CreatorId = userId,
+                    Key = key,
                 };
 
                 var savingNotification = new EntityContainerSavingNotification(container, eventMessages);
@@ -984,8 +974,8 @@ public TItem Copy(TItem original, string alias, string name, TItem? parent)
                 var savedNotification = new EntityContainerSavedNotification(container, eventMessages);
                 savedNotification.WithStateFrom(savingNotification);
                 scope.Notifications.Publish(savedNotification);
-                // TODO: Audit trail ?
 
+                // TODO: Audit trail ?
                 return OperationResult.Attempt.Succeed(eventMessages, container);
             }
             catch (Exception ex)
@@ -997,8 +987,7 @@ public TItem Copy(TItem original, string alias, string name, TItem? parent)
         }
     }
 
-    public Attempt SaveContainer(EntityContainer container,
-        int userId = Constants.Security.SuperUserId)
+    public Attempt SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId)
     {
         EventMessages eventMessages = EventMessagesFactory.Get();
 
@@ -1036,7 +1025,6 @@ public TItem Copy(TItem original, string alias, string name, TItem? parent)
         }
 
         // TODO: Audit trail ?
-
         return OperationResult.Attempt.Succeed(eventMessages);
     }
 
@@ -1130,12 +1118,12 @@ public IEnumerable GetContainers(string name, int level)
             scope.Notifications.Publish(deletedNotification);
 
             return OperationResult.Attempt.Succeed(eventMessages);
+
             // TODO: Audit trail ?
         }
     }
 
-    public Attempt?> RenameContainer(int id, string name,
-        int userId = Constants.Security.SuperUserId)
+    public Attempt?> RenameContainer(int id, string name, int userId = Constants.Security.SuperUserId)
     {
         EventMessages eventMessages = EventMessagesFactory.Get();
         using (ICoreScope scope = ScopeProvider.CreateCoreScope())
@@ -1146,7 +1134,7 @@ public IEnumerable GetContainers(string name, int level)
             {
                 EntityContainer? container = _containerRepository?.Get(id);
 
-                //throw if null, this will be caught by the catch and a failed returned
+                // throw if null, this will be caught by the catch and a failed returned
                 if (container == null)
                 {
                     throw new InvalidOperationException("No container found with id " + id);
diff --git a/src/Umbraco.Core/Services/DashboardService.cs b/src/Umbraco.Core/Services/DashboardService.cs
index de8fca066b69..f5ddb30557bf 100644
--- a/src/Umbraco.Core/Services/DashboardService.cs
+++ b/src/Umbraco.Core/Services/DashboardService.cs
@@ -17,8 +17,7 @@ public class DashboardService : IDashboardService
     // TODO: Unit test all this!!! :/
     private readonly ISectionService _sectionService;
 
-    public DashboardService(ISectionService sectionService, DashboardCollection dashboardCollection,
-        ILocalizedTextService localizedText)
+    public DashboardService(ISectionService sectionService, DashboardCollection dashboardCollection, ILocalizedTextService localizedText)
     {
         _sectionService = sectionService ?? throw new ArgumentNullException(nameof(sectionService));
         _dashboardCollection = dashboardCollection ?? throw new ArgumentNullException(nameof(dashboardCollection));
diff --git a/src/Umbraco.Core/Services/ExternalLoginService.cs b/src/Umbraco.Core/Services/ExternalLoginService.cs
index 9baf5dcd374d..677108dbcd15 100644
--- a/src/Umbraco.Core/Services/ExternalLoginService.cs
+++ b/src/Umbraco.Core/Services/ExternalLoginService.cs
@@ -13,18 +13,17 @@ public class ExternalLoginService : RepositoryService, IExternalLoginService, IE
 {
     private readonly IExternalLoginWithKeyRepository _externalLoginRepository;
 
-    public ExternalLoginService(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
+    public ExternalLoginService(
+        ICoreScopeProvider provider,
+        ILoggerFactory loggerFactory,
         IEventMessagesFactory eventMessagesFactory,
         IExternalLoginWithKeyRepository externalLoginRepository)
         : base(provider, loggerFactory, eventMessagesFactory) =>
         _externalLoginRepository = externalLoginRepository;
 
     [Obsolete("Use ctor injecting IExternalLoginWithKeyRepository")]
-    public ExternalLoginService(ICoreScopeProvider provider, ILoggerFactory loggerFactory,
-        IEventMessagesFactory eventMessagesFactory,
-        IExternalLoginRepository externalLoginRepository)
-        : this(provider, loggerFactory, eventMessagesFactory,
-            StaticServiceProvider.Instance.GetRequiredService())
+    public ExternalLoginService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IExternalLoginRepository externalLoginRepository)
+        : this(provider, loggerFactory, eventMessagesFactory, StaticServiceProvider.Instance.GetRequiredService())
     {
     }
 
@@ -53,7 +52,6 @@ public void Save(int userId, IEnumerable tokens)
     public void DeleteUserLogins(int userId)
         => DeleteUserLogins(userId.ToGuid());
 
-    /// 
     public IEnumerable Find(string loginProvider, string providerKey)
     {
         using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
diff --git a/src/Umbraco.Core/Services/IContentVersionService.cs b/src/Umbraco.Core/Services/IContentVersionService.cs
index 8d85b9039b1c..e0d518f52a7b 100644
--- a/src/Umbraco.Core/Services/IContentVersionService.cs
+++ b/src/Umbraco.Core/Services/IContentVersionService.cs
@@ -13,8 +13,7 @@ public interface IContentVersionService
     ///     Gets paginated content versions for given content id paginated.
     /// 
/// Thrown when is invalid. - IEnumerable? GetPagedContentVersions(int contentId, long pageIndex, int pageSize, - out long totalRecords, string? culture = null); + IEnumerable? GetPagedContentVersions(int contentId, long pageIndex, int pageSize, out long totalRecords, string? culture = null); /// /// Updates preventCleanup value for given content version. diff --git a/src/Umbraco.Core/Services/IMembershipMemberService.cs b/src/Umbraco.Core/Services/IMembershipMemberService.cs index 74b98dbfea15..dc96535f8b88 100644 --- a/src/Umbraco.Core/Services/IMembershipMemberService.cs +++ b/src/Umbraco.Core/Services/IMembershipMemberService.cs @@ -171,8 +171,7 @@ public interface IMembershipMemberService : IService /// /// /// - IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, out long totalRecords, - StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); /// /// Finds a list of objects by a partial username @@ -189,8 +188,7 @@ IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSi /// /// /// - IEnumerable FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, - StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + IEnumerable FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); /// /// Gets a list of paged objects diff --git a/src/Umbraco.Core/Services/IMembershipRoleService.cs b/src/Umbraco.Core/Services/IMembershipRoleService.cs index c335159605b1..6e05f736d638 100644 --- a/src/Umbraco.Core/Services/IMembershipRoleService.cs +++ b/src/Umbraco.Core/Services/IMembershipRoleService.cs @@ -23,8 +23,7 @@ public interface IMembershipRoleService IEnumerable GetMembersInRole(string roleName); - IEnumerable FindMembersInRole(string roleName, string usernameToMatch, - StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); + IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith); bool DeleteRole(string roleName, bool throwIfBeingUsed); diff --git a/src/Umbraco.Core/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs index 22782b9bba92..6f8fa9b75a54 100644 --- a/src/Umbraco.Core/Services/IRelationService.cs +++ b/src/Umbraco.Core/Services/IRelationService.cs @@ -169,8 +169,7 @@ public interface IRelationService : IService /// /// /// - IEnumerable GetPagedByRelationTypeId(int relationTypeId, long pageIndex, int pageSize, - out long totalRecords, Ordering? ordering = null); + IEnumerable GetPagedByRelationTypeId(int relationTypeId, long pageIndex, int pageSize, out long totalRecords, Ordering? ordering = null); /// /// Gets the Child object from a Relation as an @@ -215,8 +214,7 @@ IEnumerable GetPagedByRelationTypeId(int relationTypeId, long pageInd /// /// /// An enumerable list of - IEnumerable GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, - out long totalChildren, params UmbracoObjectTypes[] entityTypes); + IEnumerable GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes); /// /// Returns paged child entities for a related parent id @@ -226,8 +224,7 @@ IEnumerable GetPagedParentEntitiesByChildId(int id, long pageInd /// /// /// An enumerable list of - IEnumerable GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize, - out long totalChildren, params UmbracoObjectTypes[] entityTypes); + IEnumerable GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes); /// /// Gets the Parent and Child objects from a list of Relations as a list of objects. diff --git a/src/Umbraco.Core/Services/ITrackedReferencesService.cs b/src/Umbraco.Core/Services/ITrackedReferencesService.cs index 0d40d9f29ef3..16b953c35ac0 100644 --- a/src/Umbraco.Core/Services/ITrackedReferencesService.cs +++ b/src/Umbraco.Core/Services/ITrackedReferencesService.cs @@ -16,8 +16,7 @@ public interface ITrackedReferencesService /// dependencies (isDependency field is set to true). /// /// A paged result of objects. - PagedResult GetPagedRelationsForItem(int id, long pageIndex, int pageSize, - bool filterMustBeIsDependency); + PagedResult GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency); /// /// Gets a paged result of the descending items that have any references, given a parent id. @@ -30,8 +29,7 @@ PagedResult GetPagedRelationsForItem(int id, long pageIndex, int p /// dependencies (isDependency field is set to true). /// /// A paged result of objects. - PagedResult GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, - bool filterMustBeIsDependency); + PagedResult GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency); /// /// Gets a paged result of items used in any kind of relation from selected integer ids. @@ -44,6 +42,5 @@ PagedResult GetPagedDescendantsInReferences(int parentId, long pag /// dependencies (isDependency field is set to true). /// /// A paged result of objects. - PagedResult GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, - bool filterMustBeIsDependency); + PagedResult GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency); } diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index 9711dfc9e2e9..40a3fbd89978 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -59,8 +59,12 @@ public interface IUserService : IMembershipUserService /// /// /// - IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, + IEnumerable GetAll( + long pageIndex, + int pageSize, + out long totalRecords, + string orderBy, + Direction orderDirection, UserState[]? userState = null, string[]? includeUserGroups = null, string[]? excludeUserGroups = null, @@ -80,8 +84,12 @@ IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, /// /// /// - IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, + IEnumerable GetAll( + long pageIndex, + int pageSize, + out long totalRecords, + string orderBy, + Direction orderDirection, UserState[]? userState = null, string[]? userGroups = null, string? filter = null); @@ -162,8 +170,7 @@ IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, /// /// Specifying nothing will return all permissions for all nodes /// An enumerable list of - EntityPermissionCollection GetPermissions(IUserGroup?[] groups, bool fallbackToDefaultPermissions, - params int[] nodeIds); + EntityPermissionCollection GetPermissions(IUserGroup?[] groups, bool fallbackToDefaultPermissions, params int[] nodeIds); /// /// Gets the implicit/inherited permissions for the user for the given path @@ -181,8 +188,7 @@ EntityPermissionCollection GetPermissions(IUserGroup?[] groups, bool fallbackToD /// Flag indicating if we want to include the default group permissions for each result if there are not explicit /// permissions set /// - EntityPermissionSet GetPermissionsForPath(IUserGroup[] groups, string path, - bool fallbackToDefaultPermissions = false); + EntityPermissionSet GetPermissionsForPath(IUserGroup[] groups, string path, bool fallbackToDefaultPermissions = false); /// /// Replaces the same permission set for a single group to any number of entities diff --git a/src/Umbraco.Core/Services/MemberGroupService.cs b/src/Umbraco.Core/Services/MemberGroupService.cs index 0cd869feca8f..5a68236455a9 100644 --- a/src/Umbraco.Core/Services/MemberGroupService.cs +++ b/src/Umbraco.Core/Services/MemberGroupService.cs @@ -11,9 +11,7 @@ internal class MemberGroupService : RepositoryService, IMemberGroupService { private readonly IMemberGroupRepository _memberGroupRepository; - public MemberGroupService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, - IMemberGroupRepository memberGroupRepository) + public MemberGroupService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, IMemberGroupRepository memberGroupRepository) : base(provider, loggerFactory, eventMessagesFactory) => _memberGroupRepository = memberGroupRepository; diff --git a/src/Umbraco.Core/Services/NotificationService.cs b/src/Umbraco.Core/Services/NotificationService.cs index 01127cb8d508..822ba890794b 100644 --- a/src/Umbraco.Core/Services/NotificationService.cs +++ b/src/Umbraco.Core/Services/NotificationService.cs @@ -525,7 +525,8 @@ private NotificationRequest CreateNotificationRequest( @" -", createBody((user: mailingUser, body: bodyVars, true))); +", + createBody((user: mailingUser, body: bodyVars, true))); } // nh, issue 30724. Due to hardcoded http strings in resource files, we need to check for https replacements here diff --git a/src/Umbraco.Core/Services/RepositoryService.cs b/src/Umbraco.Core/Services/RepositoryService.cs index 9afdd2ed6783..2c7bb39085fa 100644 --- a/src/Umbraco.Core/Services/RepositoryService.cs +++ b/src/Umbraco.Core/Services/RepositoryService.cs @@ -10,8 +10,7 @@ namespace Umbraco.Cms.Core.Services; /// public abstract class RepositoryService : IService { - protected RepositoryService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory) + protected RepositoryService(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory) { EventMessagesFactory = eventMessagesFactory ?? throw new ArgumentNullException(nameof(eventMessagesFactory)); ScopeProvider = provider ?? throw new ArgumentNullException(nameof(provider)); diff --git a/src/Umbraco.Core/Services/UserServiceExtensions.cs b/src/Umbraco.Core/Services/UserServiceExtensions.cs index 3d06c0d6f20d..f17a26661681 100644 --- a/src/Umbraco.Core/Services/UserServiceExtensions.cs +++ b/src/Umbraco.Core/Services/UserServiceExtensions.cs @@ -37,8 +37,7 @@ public static class UserServiceExtensions /// /// Specifying nothing will return all permissions for all nodes /// An enumerable list of - public static EntityPermissionCollection GetPermissions(this IUserService service, IUserGroup? group, - bool fallbackToDefaultPermissions, params int[] nodeIds) => + public static EntityPermissionCollection GetPermissions(this IUserService service, IUserGroup? group, bool fallbackToDefaultPermissions, params int[] nodeIds) => service.GetPermissions(new[] { group }, fallbackToDefaultPermissions, nodeIds); /// @@ -51,8 +50,7 @@ public static EntityPermissionCollection GetPermissions(this IUserService servic /// Flag indicating if we want to include the default group permissions for each result if there are not explicit /// permissions set /// - public static EntityPermissionSet GetPermissionsForPath(this IUserService service, IUserGroup group, string path, - bool fallbackToDefaultPermissions = false) => + public static EntityPermissionSet GetPermissionsForPath(this IUserService service, IUserGroup group, string path, bool fallbackToDefaultPermissions = false) => service.GetPermissionsForPath(new[] { group }, path, fallbackToDefaultPermissions); /// diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs index da729f0c0a70..39aa3502d865 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs @@ -94,17 +94,15 @@ public DefaultShortStringHelper(IOptions settings) => // see notes for CleanAsciiString //// beware! the order is quite important here! - //const string ValidStringCharactersSource = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - //readonly static char[] ValidStringCharacters; - + // const string ValidStringCharactersSource = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + // readonly static char[] ValidStringCharacters; private readonly DefaultShortStringHelperConfig _config; // see notes for CleanAsciiString - //static DefaultShortStringHelper() - //{ + // static DefaultShortStringHelper() + // { // ValidStringCharacters = ValidStringCharactersSource.ToCharArray(); - //} - + // } #endregion #region Filters @@ -197,7 +195,7 @@ public virtual string CleanStringForSafeFileName(string text, string culture) return string.Empty; } - culture = culture ?? ""; + culture = culture ?? string.Empty; text = text.ReplaceMany(Path.GetInvalidFileNameChars(), '-'); var name = Path.GetFileNameWithoutExtension(text); @@ -233,7 +231,6 @@ public virtual string CleanStringForSafeFileName(string text, string culture) // Our additional stuff: // - Leading digits are removed. // - Many consecutive separators are folded into one unique separator. - private const byte StateBreak = 1; private const byte StateUp = 2; private const byte StateWord = 3; @@ -301,7 +298,7 @@ protected virtual string CleanString(string text, CleanStringType stringType, st throw new ArgumentNullException(nameof(text)); } - culture = culture ?? ""; + culture = culture ?? string.Empty; // get config DefaultShortStringHelperConfig.Config config = _config.For(stringType, culture); @@ -328,7 +325,7 @@ protected virtual string CleanString(string text, CleanStringType stringType, st } // apply replacements - //if (config.Replacements != null) + // if (config.Replacements != null) // text = ReplaceMany(text, config.Replacements); // recode @@ -373,7 +370,9 @@ private static string RemoveSurrogatePairs(string text) for (var ipos = 0; ipos < input.Length; ipos++) { var c = input[ipos]; - if (char.IsSurrogate(c)) // ignore high surrogate + + // ignore high surrogate + if (char.IsSurrogate(c)) { ipos++; // and skip low surrogate output[opos++] = '?'; @@ -392,13 +391,12 @@ private static string RemoveSurrogatePairs(string text) // that the utf8 version. Micro-optimizing sometimes isn't such a good idea. // note: does NOT support surrogate pairs in text - internal string CleanCodeString(string text, CleanStringType caseType, char separator, string culture, - DefaultShortStringHelperConfig.Config config) + internal string CleanCodeString(string text, CleanStringType caseType, char separator, string culture, DefaultShortStringHelperConfig.Config config) { int opos = 0, ipos = 0; var state = StateBreak; - culture = culture ?? ""; + culture = culture ?? string.Empty; caseType &= CleanStringType.CaseMask; // if we apply global ToUpper or ToLower to text here @@ -412,13 +410,15 @@ internal string CleanCodeString(string text, CleanStringType caseType, char sepa for (var i = 0; i < ilen; i++) { var c = input[i]; + // leading as long as StateBreak and ipos still zero var leading = state == StateBreak && ipos == 0; var isTerm = config.IsTerm(c, leading); - //var isDigit = char.IsDigit(c); + // var isDigit = char.IsDigit(c); var isUpper = char.IsUpper(c); // false for digits, symbols... - //var isLower = char.IsLower(c); // false for digits, symbols... + + // var isLower = char.IsLower(c); // false for digits, symbols... // what should I do with surrogates? - E.g emojis like 🎈 // no idea, really, so they are not supported at the moment and we just continue @@ -428,7 +428,6 @@ internal string CleanCodeString(string text, CleanStringType caseType, char sepa continue; } - switch (state) { // within a break @@ -477,7 +476,8 @@ internal string CleanCodeString(string text, CleanStringType caseType, char sepa i -= 1; // handle that char again, in another state - not part of the acronym } - if (i - ipos > 1) // single-char can't be an acronym + // single-char can't be an acronym + if (i - ipos > 1) { CopyTerm(input, ipos, output, ref opos, i - ipos, caseType, culture, true); ipos = i; @@ -492,7 +492,9 @@ internal string CleanCodeString(string text, CleanStringType caseType, char sepa state = StateWord; } } - else if (isUpper == false) // isTerm == true + + // isTerm == true + else if (isUpper == false) { // it's a term char and we don't cut... // keep moving forward as a word @@ -544,8 +546,7 @@ internal string CleanCodeString(string text, CleanStringType caseType, char sepa } // note: supports surrogate pairs in input string - internal void CopyTerm(string input, int ipos, char[] output, ref int opos, int len, - CleanStringType caseType, string culture, bool isAcronym) + internal void CopyTerm(string input, int ipos, char[] output, ref int opos, int len, CleanStringType caseType, string culture, bool isAcronym) { var term = input.Substring(ipos, len); CultureInfo cultureInfo = string.IsNullOrEmpty(culture) @@ -564,14 +565,13 @@ internal void CopyTerm(string input, int ipos, char[] output, ref int opos, int // note: MSDN seems to imply that ToUpper or ToLower preserve the length // of the string, but that this behavior is not guaranteed and could change. - char c; int i; string s; switch (caseType) { - //case CleanStringType.LowerCase: - //case CleanStringType.UpperCase: + // case CleanStringType.LowerCase: + // case CleanStringType.UpperCase: case CleanStringType.Unchanged: term.CopyTo(0, output, opos, len); opos += len; diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs index 7ac3c3b726d2..ec7ed9d0023c 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs @@ -8,7 +8,7 @@ public class DefaultShortStringHelperConfig { private readonly Dictionary> _configs = new(); - public string DefaultCulture { get; set; } = ""; // invariant + public string DefaultCulture { get; set; } = string.Empty; // invariant public Dictionary? UrlReplaceCharacters { get; set; } @@ -16,7 +16,8 @@ public DefaultShortStringHelperConfig Clone() { var config = new DefaultShortStringHelperConfig { - DefaultCulture = DefaultCulture, UrlReplaceCharacters = UrlReplaceCharacters + DefaultCulture = DefaultCulture, + UrlReplaceCharacters = UrlReplaceCharacters, }; foreach (KeyValuePair> kvp1 in _configs) @@ -38,14 +39,14 @@ public DefaultShortStringHelperConfig WithConfig(Config config) => public DefaultShortStringHelperConfig WithConfig(CleanStringType stringRole, Config config) => WithConfig(DefaultCulture, stringRole, config); - public DefaultShortStringHelperConfig WithConfig(string culture, CleanStringType stringRole, Config config) + public DefaultShortStringHelperConfig WithConfig(string? culture, CleanStringType stringRole, Config config) { if (config == null) { throw new ArgumentNullException(nameof(config)); } - culture = culture ?? ""; + culture = culture ?? string.Empty; if (_configs.ContainsKey(culture) == false) { @@ -86,14 +87,14 @@ public DefaultShortStringHelperConfig WithDefault(RequestHandlerSettings request IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore StringType = urlSegmentConvertTo | CleanStringType.LowerCase, BreakTermsOnUpper = false, - Separator = '-' + Separator = '-', }).WithConfig(CleanStringType.FileName, new Config { PreFilter = ApplyUrlReplaceCharacters, IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore StringType = CleanStringType.Utf8 | CleanStringType.LowerCase, BreakTermsOnUpper = false, - Separator = '-' + Separator = '-', }).WithConfig(CleanStringType.Alias, new Config { PreFilter = ApplyUrlReplaceCharacters, @@ -101,39 +102,42 @@ public DefaultShortStringHelperConfig WithDefault(RequestHandlerSettings request ? char.IsLetter(c) // only letters : char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore StringType = CleanStringType.Ascii | CleanStringType.UmbracoCase, - BreakTermsOnUpper = false + BreakTermsOnUpper = false, }).WithConfig(CleanStringType.UnderscoreAlias, new Config { PreFilter = ApplyUrlReplaceCharacters, IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore StringType = CleanStringType.Ascii | CleanStringType.UmbracoCase, - BreakTermsOnUpper = false + BreakTermsOnUpper = false, }).WithConfig(CleanStringType.ConvertCase, new Config { PreFilter = null, IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore StringType = CleanStringType.Ascii, - BreakTermsOnUpper = true + BreakTermsOnUpper = true, }); } // internal: we don't want ppl to retrieve a config and modify it // (the helper uses a private clone to prevent modifications) - internal Config For(CleanStringType stringType, string culture) + internal Config For(CleanStringType stringType, string? culture) { - culture = culture ?? ""; + culture = culture ?? string.Empty; stringType = stringType & CleanStringType.RoleMask; Dictionary config; if (_configs.ContainsKey(culture)) { config = _configs[culture]; - if (config.ContainsKey(stringType)) // have we got a config for _that_ role? + + // have we got a config for _that_ role? + if (config.ContainsKey(stringType)) { return config[stringType]; } - if (config.ContainsKey(CleanStringType.RoleMask)) // have we got a generic config for _all_ roles? + // have we got a generic config for _all_ roles? + if (config.ContainsKey(CleanStringType.RoleMask)) { return config[CleanStringType.RoleMask]; } @@ -141,12 +145,15 @@ internal Config For(CleanStringType stringType, string culture) else if (_configs.ContainsKey(DefaultCulture)) { config = _configs[DefaultCulture]; - if (config.ContainsKey(stringType)) // have we got a config for _that_ role? + + // have we got a config for _that_ role? + if (config.ContainsKey(stringType)) { return config[stringType]; } - if (config.ContainsKey(CleanStringType.RoleMask)) // have we got a generic config for _all_ roles? + // have we got a generic config for _all_ roles? + if (config.ContainsKey(CleanStringType.RoleMask)) { return config[CleanStringType.RoleMask]; } @@ -183,7 +190,9 @@ public Config() } public Func? PreFilter { get; set; } + public Func? PostFilter { get; set; } + public Func IsTerm { get; set; } public CleanStringType StringType { get; set; } @@ -215,14 +224,14 @@ public Config Clone() => BreakTermsOnUpper = BreakTermsOnUpper, CutAcronymOnNonUpper = CutAcronymOnNonUpper, GreedyAcronyms = GreedyAcronyms, - Separator = Separator + Separator = Separator, }; // extends the config public CleanStringType StringTypeExtend(CleanStringType stringType) { CleanStringType st = StringType; - foreach (CleanStringType mask in new[] {CleanStringType.CaseMask, CleanStringType.CodeMask}) + foreach (CleanStringType mask in new[] { CleanStringType.CaseMask, CleanStringType.CodeMask }) { CleanStringType a = stringType & mask; if (a == 0) diff --git a/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs b/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs index 1accfde86b7d..4221273150ac 100644 --- a/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs +++ b/src/Umbraco.Core/Strings/Utf8ToAsciiConverter.cs @@ -72,7 +72,8 @@ private static int ToAscii(char[] input, char[] output, char fail = '?') for (var ipos = 0; ipos < input.Length; ipos++) { - if (char.IsSurrogate(input[ipos])) // ignore high surrogate + // ignore high surrogate + if (char.IsSurrogate(input[ipos])) { ipos++; // and skip low surrogate output[opos++] = fail; @@ -3324,7 +3325,6 @@ private static void ToAscii(char[] input, int ipos, char[] output, ref int opos, // BEGIN CUSTOM TRANSLITERATION OF CYRILIC CHARS - // russian uppercase "А Б В Г Д Е Ё Ж З И Й К Л М Н О П Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я" // russian lowercase "а б в г д е ё ж з и й к л м н о п р с т у ф х ц ч ш щ ъ ы ь э ю я" @@ -3554,7 +3554,6 @@ private static void ToAscii(char[] input, int ipos, char[] output, ref int opos, output[opos++] = 'a'; break; - // BEGIN EXTRA /* case '£': diff --git a/src/Umbraco.Core/Templates/IUmbracoComponentRenderer.cs b/src/Umbraco.Core/Templates/IUmbracoComponentRenderer.cs index 4d930cb72d19..b94d575b9faa 100644 --- a/src/Umbraco.Core/Templates/IUmbracoComponentRenderer.cs +++ b/src/Umbraco.Core/Templates/IUmbracoComponentRenderer.cs @@ -49,6 +49,5 @@ public interface IUmbracoComponentRenderer /// Currently only used when the node is unpublished and unable to get the contentId item from the /// content cache as its unpublished. This deals with taking in a preview/draft version of the content node /// - Task RenderMacroForContent(IPublishedContent content, string alias, - IDictionary? parameters); + Task RenderMacroForContent(IPublishedContent content, string alias, IDictionary? parameters); } diff --git a/src/Umbraco.Core/UmbracoContextReference.cs b/src/Umbraco.Core/UmbracoContextReference.cs index 0a7c764d4d1b..d17012e0f98c 100644 --- a/src/Umbraco.Core/UmbracoContextReference.cs +++ b/src/Umbraco.Core/UmbracoContextReference.cs @@ -22,8 +22,7 @@ public class UmbracoContextReference : IDisposable /// /// Initializes a new instance of the class. /// - public UmbracoContextReference(IUmbracoContext umbracoContext, bool isRoot, - IUmbracoContextAccessor umbracoContextAccessor) + public UmbracoContextReference(IUmbracoContext umbracoContext, bool isRoot, IUmbracoContextAccessor umbracoContextAccessor) { IsRoot = isRoot; diff --git a/src/Umbraco.Core/Xml/DynamicContext.cs b/src/Umbraco.Core/Xml/DynamicContext.cs index 48bdb198260b..4418862d0e78 100644 --- a/src/Umbraco.Core/Xml/DynamicContext.cs +++ b/src/Umbraco.Core/Xml/DynamicContext.cs @@ -4,7 +4,6 @@ using System.Xml.Xsl; // source: mvpxml.codeplex.com - namespace Umbraco.Cms.Core.Xml; /// @@ -89,6 +88,7 @@ public DynamicVariable(string name, object value) try { _value = Convert.ToDouble(value); + // We succeeded, so it's a number. _type = XPathResultType.Number; } @@ -176,6 +176,7 @@ public DynamicContext(XmlNamespaceManager context, NameTable table) foreach (string prefix in context) { var uri = context.LookupNamespace(prefix); + // Use fast object reference comparison to omit forbidden namespace declarations. if (Equals(uri, xml) || Equals(uri, xmlns)) { @@ -299,7 +300,7 @@ public override IXsltContextVariable ResolveVariable(string prefix, string name) { IXsltContextVariable var; _variables.TryGetValue(name, out var!); - return var!; + return var; } #endregion Variable Handling Code diff --git a/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs b/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs index af9ec53fedf6..3529f559229b 100644 --- a/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs +++ b/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs @@ -79,6 +79,7 @@ public class NavigableNavigator : XPathNavigator /// The maximum depth. /// When no root content is supplied then the root of the source is used. public NavigableNavigator(INavigableSource source, int rootId = 0, int maxDepth = int.MaxValue) + // : this(source, maxDepth) { _source = source; @@ -691,7 +692,9 @@ public override bool MoveToId(string id) // navigator may be rooted below source root // find the navigator root id State state = InternalState; - while (state.Parent != null) // root state has no parent + + // root state has no parent + while (state.Parent != null) { state = state.Parent; } @@ -846,7 +849,8 @@ public override bool MoveToPrevious() InternalState.FieldsCount - 1 > _lastAttributeIndex) { // before children elements may come some property elements - if (MoveToParentElement()) // pops the state + // pops the state + if (MoveToParentElement()) { InternalState.FieldIndex = InternalState.FieldsCount - 1; DebugState(); From a6cea6175f4b85907df1dd0fe55b099709fa3e5b Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle Date: Fri, 20 May 2022 10:01:26 +0200 Subject: [PATCH 005/117] Fix after merge --- .../Configuration/Models/ConnectionStrings.cs | 2 +- .../UmbracoBuilder.Collections.cs | 138 +++---- .../UmbracoBuilder.Configuration.cs | 5 +- src/Umbraco.Core/Deploy/ArtifactBase.cs | 4 - .../Mapping/MemberTabsAndPropertiesMapper.cs | 386 ++++++------------ .../Services/MetricsConsentService.cs | 45 +- 6 files changed, 222 insertions(+), 358 deletions(-) diff --git a/src/Umbraco.Core/Configuration/Models/ConnectionStrings.cs b/src/Umbraco.Core/Configuration/Models/ConnectionStrings.cs index 1165ccca370a..b293202bad17 100644 --- a/src/Umbraco.Core/Configuration/Models/ConnectionStrings.cs +++ b/src/Umbraco.Core/Configuration/Models/ConnectionStrings.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Configuration; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.Configuration.Models; diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs index dbff006deeab..5ad759de681b 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs @@ -23,25 +23,18 @@ namespace Umbraco.Cms.Core.DependencyInjection; /// -/// Extension methods for +/// Extension methods for /// public static partial class UmbracoBuilderExtensions { /// - /// Gets the actions collection builder. - /// - /// The builder. - public static ActionCollectionBuilder? Actions(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - /// - /// Adds all core collection builders + /// Adds all core collection builders /// internal static void AddAllCoreCollectionBuilders(this IUmbracoBuilder builder) { builder.CacheRefreshers().Add(() => builder.TypeLoader.GetCacheRefreshers()); builder.DataEditors().Add(() => builder.TypeLoader.GetDataEditors()); - builder.Actions().Add(() => builder.TypeLoader.GetActions()); + builder.Actions().Add(() => builder .TypeLoader.GetActions()); // register known content apps builder.ContentApps() @@ -66,15 +59,13 @@ internal static void AddAllCoreCollectionBuilders(this IUmbracoBuilder builder) .Append(); builder.EditorValidators().Add(() => builder.TypeLoader.GetTypes()); builder.HealthChecks().Add(() => builder.TypeLoader.GetTypes()); - builder.HealthCheckNotificationMethods() - .Add(() => builder.TypeLoader.GetTypes()); + builder.HealthCheckNotificationMethods().Add(() => builder.TypeLoader.GetTypes()); builder.TourFilters(); builder.UrlProviders() .Append() .Append(); builder.MediaUrlProviders() .Append(); - // register back office sections in the order we want them rendered builder.Sections() .Append() @@ -86,7 +77,6 @@ internal static void AddAllCoreCollectionBuilders(this IUmbracoBuilder builder) .Append() .Append(); builder.Components(); - // register core CMS dashboards and 3rd party types - will be ordered by weight attribute & merged with package.manifest dashboards builder.Dashboards() .Add() @@ -102,7 +92,8 @@ internal static void AddAllCoreCollectionBuilders(this IUmbracoBuilder builder) .Add() .Add(builder.TypeLoader.GetTypes()); builder.PartialViewSnippets(); - builder.PartialViewMacroSnippets();builder.DataValueReferenceFactories(); + builder.PartialViewMacroSnippets(); + builder.DataValueReferenceFactories(); builder.PropertyValueConverters().Append(builder.TypeLoader.GetTypes()); builder.UrlSegmentProviders().Append(); builder.ManifestValueValidators() @@ -114,7 +105,6 @@ internal static void AddAllCoreCollectionBuilders(this IUmbracoBuilder builder) .Add(); builder.ManifestFilters(); builder.MediaUrlGenerators(); - // register OEmbed providers - no type scanning - all explicit opt-in of adding types, IEmbedProvider is not IDiscoverable builder.EmbedProviders() .Append() @@ -135,171 +125,163 @@ internal static void AddAllCoreCollectionBuilders(this IUmbracoBuilder builder) builder.BackOfficeAssets(); } - /// - /// Gets the actions collection builder. - /// - /// The builder. - public static ActionCollectionBuilder Actions(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the actions collection builder. + /// + /// The builder. + public static ActionCollectionBuilder Actions(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the content apps collection builder. - /// - /// The builder. - public static ContentAppFactoryCollectionBuilder ContentApps(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the content apps collection builder. + /// + /// The builder. + public static ContentAppFactoryCollectionBuilder ContentApps(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); /// - /// Gets the content finders collection builder. + /// Gets the content finders collection builder. /// /// The builder. public static ContentFinderCollectionBuilder ContentFinders(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// - /// Gets the editor validators collection builder. + /// Gets the editor validators collection builder. /// /// The builder. public static EditorValidatorCollectionBuilder EditorValidators(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// - /// Gets the health checks collection builder. + /// Gets the health checks collection builder. /// /// The builder. public static HealthCheckCollectionBuilder HealthChecks(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); - public static HealthCheckNotificationMethodCollectionBuilder HealthCheckNotificationMethods( - this IUmbracoBuilder builder) + public static HealthCheckNotificationMethodCollectionBuilder HealthCheckNotificationMethods(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// - /// Gets the TourFilters collection builder. + /// Gets the TourFilters collection builder. /// public static TourFilterCollectionBuilder TourFilters(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// - /// Gets the URL providers collection builder. + /// Gets the URL providers collection builder. /// /// The builder. public static UrlProviderCollectionBuilder UrlProviders(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// - /// Gets the media url providers collection builder. + /// Gets the media url providers collection builder. /// /// The builder. public static MediaUrlProviderCollectionBuilder MediaUrlProviders(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// - /// Gets the backoffice sections/applications collection builder. + /// Gets the backoffice sections/applications collection builder. /// /// The builder. public static SectionCollectionBuilder Sections(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// - /// Gets the components collection builder. + /// Gets the components collection builder. /// public static ComponentCollectionBuilder Components(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// - /// Gets the backoffice dashboards collection builder. + /// Gets the backoffice dashboards collection builder. /// /// The builder. public static DashboardCollectionBuilder Dashboards(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// - /// Gets the partial view snippets collection builder. - /// - /// The builder. - public static PartialViewSnippetCollectionBuilder? PartialViewSnippets(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// Gets the partial view snippets collection builder. + /// + /// The builder. + public static PartialViewSnippetCollectionBuilder? PartialViewSnippets(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the partial view macro snippets collection builder. - /// - /// The builder. - public static PartialViewMacroSnippetCollectionBuilder? PartialViewMacroSnippets(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); + /// + /// Gets the partial view macro snippets collection builder. + /// + /// The builder. + public static PartialViewMacroSnippetCollectionBuilder? PartialViewMacroSnippets(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); - /// - /// Gets the cache refreshers collection builder. + /// + /// Gets the cache refreshers collection builder. /// /// The builder. public static CacheRefresherCollectionBuilder CacheRefreshers(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// - /// Gets the map definitions collection builder. + /// Gets the map definitions collection builder. /// /// The builder. public static MapDefinitionCollectionBuilder MapDefinitions(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// - /// Gets the data editor collection builder. + /// Gets the data editor collection builder. /// /// The builder. public static DataEditorCollectionBuilder DataEditors(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// - /// Gets the data value reference factory collection builder. + /// Gets the data value reference factory collection builder. /// /// The builder. public static DataValueReferenceFactoryCollectionBuilder DataValueReferenceFactories(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// - /// Gets the property value converters collection builder. + /// Gets the property value converters collection builder. /// /// The builder. public static PropertyValueConverterCollectionBuilder PropertyValueConverters(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// - /// Gets the url segment providers collection builder. + /// Gets the url segment providers collection builder. /// /// The builder. public static UrlSegmentProviderCollectionBuilder UrlSegmentProviders(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); - /// - /// Gets the validators collection builder. - /// - /// The builder. - internal static ManifestValueValidatorCollectionBuilder ManifestValueValidators(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - - /// - /// Gets the manifest filter collection builder. - /// - /// The builder. - public static ManifestFilterCollectionBuilder ManifestFilters(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - /// - /// Gets the validators collection builder. + /// Gets the validators collection builder. /// /// The builder. - internal static ManifestValueValidatorCollectionBuilder? ManifestValueValidators(this IUmbracoBuilder builder) + internal static ManifestValueValidatorCollectionBuilder ManifestValueValidators(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// - /// Gets the content finders collection builder. + /// Gets the manifest filter collection builder. + /// + /// The builder. + public static ManifestFilterCollectionBuilder ManifestFilters(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); + + /// + /// Gets the content finders collection builder. /// /// The builder. public static MediaUrlGeneratorCollectionBuilder MediaUrlGenerators(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// - /// Gets the backoffice OEmbed Providers collection builder. + /// Gets the backoffice OEmbed Providers collection builder. /// /// The builder. [Obsolete("Use EmbedProviders() instead")] @@ -307,20 +289,20 @@ public static EmbedProvidersCollectionBuilder OEmbedProviders(this IUmbracoBuild => EmbedProviders(builder); /// - /// Gets the backoffice Embed Providers collection builder. + /// Gets the backoffice Embed Providers collection builder. /// /// The builder. public static EmbedProvidersCollectionBuilder EmbedProviders(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// - /// Gets the back office searchable tree collection builder + /// Gets the back office searchable tree collection builder /// public static SearchableTreeCollectionBuilder SearchableTrees(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); /// - /// Gets the back office custom assets collection builder + /// Gets the back office custom assets collection builder /// public static CustomBackOfficeAssetsCollectionBuilder BackOfficeAssets(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs index 57607d347ae1..c8e1ca52643c 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -1,5 +1,4 @@ using System.Reflection; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration; @@ -17,13 +16,13 @@ public static partial class UmbracoBuilderExtensions private static IUmbracoBuilder AddUmbracoOptions(this IUmbracoBuilder builder, Action>? configure = null) where TOptions : class { - var umbracoOptionsAttribute = typeof(TOptions).GetCustomAttribute(); + UmbracoOptionsAttribute? umbracoOptionsAttribute = typeof(TOptions).GetCustomAttribute(); if (umbracoOptionsAttribute is null) { throw new ArgumentException($"{typeof(TOptions)} do not have the UmbracoOptionsAttribute."); } - var optionsBuilder = builder.Services.AddOptions() + OptionsBuilder? optionsBuilder = builder.Services.AddOptions() .Bind( builder.Config.GetSection(umbracoOptionsAttribute.ConfigurationKey), o => o.BindNonPublicProperties = umbracoOptionsAttribute.BindNonPublicProperties) diff --git a/src/Umbraco.Core/Deploy/ArtifactBase.cs b/src/Umbraco.Core/Deploy/ArtifactBase.cs index 200b47096df8..cc2415f4cde1 100644 --- a/src/Umbraco.Core/Deploy/ArtifactBase.cs +++ b/src/Umbraco.Core/Deploy/ArtifactBase.cs @@ -21,8 +21,6 @@ protected ArtifactBase(TUdi udi, IEnumerable? dependencies = protected abstract string GetChecksum(); - #region Abstract implementation of IArtifactSignature - Udi IArtifactSignature.Udi => Udi; public TUdi Udi { get; set; } @@ -45,8 +43,6 @@ public IEnumerable Dependencies set => _dependencies = value.OrderBy(x => x.Udi); } - #endregion - public string Name { get; set; } public string Alias { get; set; } = string.Empty; diff --git a/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs b/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs index d52423fa5a3e..ae9876628fea 100644 --- a/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs +++ b/src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs @@ -11,22 +11,21 @@ namespace Umbraco.Cms.Core.Models.Mapping; /// -/// A custom tab/property resolver for members which will ensure that the built-in membership properties are or aren't -/// displayed -/// depending on if the member type has these properties +/// A custom tab/property resolver for members which will ensure that the built-in membership properties are or aren't displayed +/// depending on if the member type has these properties /// /// -/// This also ensures that the IsLocked out property is readonly when the member is not locked out - this is because -/// an admin cannot actually set isLockedOut = true, they can only unlock. +/// This also ensures that the IsLocked out property is readonly when the member is not locked out - this is because +/// an admin cannot actually set isLockedOut = true, they can only unlock. /// public class MemberTabsAndPropertiesMapper : TabsAndPropertiesMapper { private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; private readonly ILocalizedTextService _localizedTextService; + private readonly IMemberTypeService _memberTypeService; + private readonly IMemberService _memberService; private readonly IMemberGroupService _memberGroupService; private readonly MemberPasswordConfigurationSettings _memberPasswordConfiguration; - private readonly IMemberService _memberService; - private readonly IMemberTypeService _memberTypeService; private readonly PropertyEditorCollection _propertyEditorCollection; public MemberTabsAndPropertiesMapper( @@ -41,8 +40,7 @@ public MemberTabsAndPropertiesMapper( PropertyEditorCollection propertyEditorCollection) : base(cultureDictionary, localizedTextService, contentTypeBaseServiceProvider) { - _backofficeSecurityAccessor = backofficeSecurityAccessor ?? - throw new ArgumentNullException(nameof(backofficeSecurityAccessor)); + _backofficeSecurityAccessor = backofficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backofficeSecurityAccessor)); _localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); _memberTypeService = memberTypeService ?? throw new ArgumentNullException(nameof(memberTypeService)); _memberService = memberService ?? throw new ArgumentNullException(nameof(memberService)); @@ -55,10 +53,12 @@ public MemberTabsAndPropertiesMapper( /// Overridden to deal with custom member properties and permissions. public override IEnumerable> Map(IMember source, MapperContext context) { + IMemberType? memberType = _memberTypeService.Get(source.ContentTypeId); if (memberType is not null) { + IgnoreProperties = memberType.CompositionPropertyTypes .Where(x => x.HasIdentity == false) .Select(x => x.Alias) @@ -70,151 +70,150 @@ public override IEnumerable> Map(IMember source, Map return resolved; } - [Obsolete("Use MapMembershipProperties. Will be removed in Umbraco 10.")] - protected override IEnumerable GetCustomGenericProperties(IContentBase content) - { - var member = (IMember)content; - return MapMembershipProperties(member, null); - } + [Obsolete("Use MapMembershipProperties. Will be removed in Umbraco 10.")] + protected override IEnumerable GetCustomGenericProperties(IContentBase content) + { + var member = (IMember)content; + return MapMembershipProperties(member, null); + } - private Dictionary GetPasswordConfig(IMember member) + private Dictionary GetPasswordConfig(IMember member) + { + var result = new Dictionary(_memberPasswordConfiguration.GetConfiguration(true)) { - var result = new Dictionary(_memberPasswordConfiguration.GetConfiguration(true)) - { - // the password change toggle will only be displayed if there is already a password assigned. - {"hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false} - }; - - // This will always be true for members since we always want to allow admins to change a password - so long as that - // user has access to edit members (but that security is taken care of separately) - result["allowManuallyChangingPassword"] = true; + // the password change toggle will only be displayed if there is already a password assigned. + {"hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false} + }; - return result; - } + // This will always be true for members since we always want to allow admins to change a password - so long as that + // user has access to edit members (but that security is taken care of separately) + result["allowManuallyChangingPassword"] = true; - /// - /// Overridden to assign the IsSensitive property values - /// - /// - /// - /// - /// - protected override List MapProperties(IContentBase content, List properties, MapperContext context) - { - var result = base.MapProperties(content, properties, context); - var member = (IMember)content; - var memberType = _memberTypeService.Get(member.ContentTypeId); + return result; + } - // now update the IsSensitive value - foreach (var prop in result) - { - // check if this property is flagged as sensitive - var isSensitiveProperty = memberType?.IsSensitiveProperty(prop.Alias) ?? false; - // check permissions for viewing sensitive data - if (isSensitiveProperty && (_backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasAccessToSensitiveData() == false)) - { - // mark this property as sensitive - prop.IsSensitive = true; - // mark this property as readonly so that it does not post any data - prop.Readonly = true; - // replace this editor with a sensitive value - prop.View = "sensitivevalue"; - // clear the value - prop.Value = null; - } - } - return result; - } + /// + /// Overridden to assign the IsSensitive property values + /// + /// + /// + /// + /// + protected override List MapProperties(IContentBase content, List properties, MapperContext context) + { + List result = base.MapProperties(content, properties, context); + var member = (IMember)content; + IMemberType? memberType = _memberTypeService.Get(member.ContentTypeId); - /// - /// Returns the login property display field - /// - /// - /// - /// - /// - /// - /// If the membership provider installed is the umbraco membership provider, then we will allow changing the username, however if - /// the membership provider is a custom one, we cannot allow changing the username because MembershipProvider's do not actually natively - /// allow that. - /// - internal static ContentPropertyDisplay GetLoginProperty(IMember member, ILocalizedTextService localizedText) + // now update the IsSensitive value + foreach (ContentPropertyDisplay prop in result) { - var prop = new ContentPropertyDisplay + // check if this property is flagged as sensitive + var isSensitiveProperty = memberType?.IsSensitiveProperty(prop.Alias) ?? false; + // check permissions for viewing sensitive data + if (isSensitiveProperty && _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasAccessToSensitiveData() == false) { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}login", - Label = localizedText.Localize(null,"login"), - Value = member.Username - }; - - prop.View = "textbox"; - prop.Validation.Mandatory = true; - return prop; + // mark this property as sensitive + prop.IsSensitive = true; + // mark this property as readonly so that it does not post any data + prop.Readonly = true; + // replace this editor with a sensitive value + prop.View = "sensitivevalue"; + // clear the value + prop.Value = null; + } } + return result; + } - internal IDictionary GetMemberGroupValue(string username) + /// + /// Returns the login property display field + /// + /// + /// + /// + /// + /// If the membership provider installed is the umbraco membership provider, then we will allow changing the username, however if + /// the membership provider is a custom one, we cannot allow changing the username because MembershipProvider's do not actually natively + /// allow that. + /// + internal static ContentPropertyDisplay GetLoginProperty(IMember member, ILocalizedTextService localizedText) + { + var prop = new ContentPropertyDisplay { - IEnumerable userRoles = _memberService.GetAllRoles(username); + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}login", + Label = localizedText.Localize(null,"login"), + Value = member.Username + }; - // create a dictionary of all roles (except internal roles) + "false" - var result = _memberGroupService.GetAll() - .Select(x => x.Name!) - // if a role starts with __umbracoRole we won't show it as it's an internal role used for public access - .Where(x => x?.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false) - .OrderBy(x => x, StringComparer.OrdinalIgnoreCase) - .ToDictionary(x => x, x => false); + prop.View = "textbox"; + prop.Validation.Mandatory = true; + return prop; + } - // if user has no roles, just return the dictionary - if (userRoles == null) - { - return result; - } + internal IDictionary GetMemberGroupValue(string username) + { + IEnumerable userRoles = _memberService.GetAllRoles(username); - // else update the dictionary to "true" for the user roles (except internal roles) - foreach (var userRole in userRoles.Where(x => x?.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false)) - { - result[userRole] = true; - } + // create a dictionary of all roles (except internal roles) + "false" + var result = _memberGroupService.GetAll() + .Select(x => x.Name!) + // if a role starts with __umbracoRole we won't show it as it's an internal role used for public access + .Where(x => x?.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false) + .OrderBy(x => x, StringComparer.OrdinalIgnoreCase) + .ToDictionary(x => x, x => false); + // if user has no roles, just return the dictionary + if (userRoles == null) + { return result; } - public IEnumerable MapMembershipProperties(IMember member, MapperContext? context) + // else update the dictionary to "true" for the user roles (except internal roles) + foreach (var userRole in userRoles.Where(x => x?.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false)) { - var properties = new List + result[userRole] = true; + } + + return result; + } + + public IEnumerable MapMembershipProperties(IMember member, MapperContext? context) + { + var properties = new List + { + GetLoginProperty(member, _localizedTextService), + new() { - GetLoginProperty(member, _localizedTextService), - new() - { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}email", - Label = _localizedTextService.Localize("general","email"), - Value = member.Email, - View = "email", - Validation = { Mandatory = true } - }, - new() + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}email", + Label = _localizedTextService.Localize("general","email"), + Value = member.Email, + View = "email", + Validation = { Mandatory = true } + }, + new() + { + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}password", + Label = _localizedTextService.Localize(null,"password"), + Value = new Dictionary { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}password", - Label = _localizedTextService.Localize(null,"password"), - Value = new Dictionary - { - // TODO: why ignoreCase, what are we doing here?! - { "newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null) } - }, - View = "changepassword", - Config = GetPasswordConfig(member) // Initialize the dictionary with the configuration from the default membership provider + // TODO: why ignoreCase, what are we doing here?! + { "newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null) } }, - new() + View = "changepassword", + Config = GetPasswordConfig(member) // Initialize the dictionary with the configuration from the default membership provider + }, + new() + { + Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}membergroup", + Label = _localizedTextService.Localize("content","membergroup"), + Value = GetMemberGroupValue(member.Username), + View = "membergroups", + Config = new Dictionary { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}membergroup", - Label = _localizedTextService.Localize("content","membergroup"), - Value = GetMemberGroupValue(member.Username), - View = "membergroups", - Config = new Dictionary - { - { "IsRequired", true } - }, + { "IsRequired", true } }, + }, // These properties used to live on the member as property data, defaulting to sensitive, so we set them to sensitive here too new() @@ -225,6 +224,7 @@ public IEnumerable MapMembershipProperties(IMember membe View = "readonlyvalue", IsSensitive = true, }, + new() { Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}approved", @@ -234,6 +234,7 @@ public IEnumerable MapMembershipProperties(IMember membe IsSensitive = true, Readonly = false, }, + new() { Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lockedOut", @@ -241,9 +242,9 @@ public IEnumerable MapMembershipProperties(IMember membe Value = member.IsLockedOut, View = "boolean", IsSensitive = true, - Readonly = !member - .IsLockedOut, // IMember.IsLockedOut can't be set to true, so make it readonly when that's the case (you can only unlock) + Readonly = !member.IsLockedOut, // IMember.IsLockedOut can't be set to true, so make it readonly when that's the case (you can only unlock) }, + new() { Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lastLockoutDate", @@ -252,6 +253,7 @@ public IEnumerable MapMembershipProperties(IMember membe View = "readonlyvalue", IsSensitive = true, }, + new() { Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lastLoginDate", @@ -260,6 +262,7 @@ public IEnumerable MapMembershipProperties(IMember membe View = "readonlyvalue", IsSensitive = true, }, + new() { Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}lastPasswordChangeDate", @@ -286,123 +289,4 @@ public IEnumerable MapMembershipProperties(IMember membe return properties; } - - /// - /// Returns the login property display field - /// - /// - /// - /// - /// - /// - /// If the membership provider installed is the umbraco membership provider, then we will allow changing the username, - /// however if - /// the membership provider is a custom one, we cannot allow changing the username because MembershipProvider's do not - /// actually natively - /// allow that. - /// - internal static ContentPropertyDisplay GetLoginProperty(IMember member, ILocalizedTextService localizedText) - { - var prop = new ContentPropertyDisplay - { - Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}login", - Label = localizedText.Localize(null, "login"), - Value = member.Username, - }; - - prop.View = "textbox"; - prop.Validation.Mandatory = true; - return prop; - } - - [Obsolete("Use MapMembershipProperties. Will be removed in Umbraco 10.")] - protected override IEnumerable GetCustomGenericProperties(IContentBase content) - { - var member = (IMember)content; - return MapMembershipProperties(member, null); - } - - /// - /// Overridden to assign the IsSensitive property values - /// - /// - /// - /// - /// - protected override List MapProperties(IContentBase content, List properties, MapperContext context) - { - List result = base.MapProperties(content, properties, context); - var member = (IMember)content; - IMemberType? memberType = _memberTypeService.Get(member.ContentTypeId); - - // now update the IsSensitive value - foreach (ContentPropertyDisplay prop in result) - { - // check if this property is flagged as sensitive - var isSensitiveProperty = memberType?.IsSensitiveProperty(prop.Alias) ?? false; - - // check permissions for viewing sensitive data - if (isSensitiveProperty && - _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasAccessToSensitiveData() == false) - { - // mark this property as sensitive - prop.IsSensitive = true; - - // mark this property as readonly so that it does not post any data - prop.Readonly = true; - - // replace this editor with a sensitive value - prop.View = "sensitivevalue"; - - // clear the value - prop.Value = null; - } - } - - return result; - } - - private Dictionary GetPasswordConfig(IMember member) - { - var result = new Dictionary(_memberPasswordConfiguration.GetConfiguration(true)) - { - // the password change toggle will only be displayed if there is already a password assigned. - { "hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false }, - }; - - // This will always be true for members since we always want to allow admins to change a password - so long as that - // user has access to edit members (but that security is taken care of separately) - result["allowManuallyChangingPassword"] = true; - - return result; - } - - internal IDictionary GetMemberGroupValue(string? username) - { - IEnumerable? userRoles = username.IsNullOrWhiteSpace() ? null : _memberService.GetAllRoles(username); - - // create a dictionary of all roles (except internal roles) + "false" - var result = _memberGroupService.GetAll() - .Select(x => x.Name!) - - // if a role starts with __umbracoRole we won't show it as it's an internal role used for public access - .Where(x => x?.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false) - .OrderBy(x => x, StringComparer.OrdinalIgnoreCase) - .ToDictionary(x => x, x => false); - - // if user has no roles, just return the dictionary - if (userRoles == null) - { - return result; - } - - // else update the dictionary to "true" for the user roles (except internal roles) - foreach (var userRole in userRoles.Where(x => - x?.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false)) - { - result[userRole] = true; - } - - return result; - } } diff --git a/src/Umbraco.Core/Services/MetricsConsentService.cs b/src/Umbraco.Core/Services/MetricsConsentService.cs index 08a4d859b04c..a5309d35f1f5 100644 --- a/src/Umbraco.Core/Services/MetricsConsentService.cs +++ b/src/Umbraco.Core/Services/MetricsConsentService.cs @@ -10,12 +10,11 @@ namespace Umbraco.Cms.Core.Services; public class MetricsConsentService : IMetricsConsentService { internal const string Key = "UmbracoAnalyticsLevel"; - private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - private readonly IKeyValueService _keyValueService; - private readonly ILogger _logger; - private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; - private readonly IUserService _userService; + private readonly IKeyValueService _keyValueService; + private readonly ILogger _logger; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + private readonly IUserService _userService; // Scheduled for removal in V12 [Obsolete("Please use the constructor that takes an ILogger and IBackOfficeSecurity instead")] @@ -24,33 +23,35 @@ public MetricsConsentService(IKeyValueService keyValueService) keyValueService, StaticServiceProvider.Instance.GetRequiredService>(), StaticServiceProvider.Instance.GetRequiredService(), - StaticServiceProvider.Instance.GetRequiredService()){ + StaticServiceProvider.Instance.GetRequiredService()) + { } // Scheduled for removal in V12 - [Obsolete("Please use the constructor that takes an IUserService instead")]public MetricsConsentService( + [Obsolete("Please use the constructor that takes an IUserService instead")] + public MetricsConsentService( IKeyValueService keyValueService, ILogger logger, IBackOfficeSecurityAccessor backOfficeSecurityAccessor) - : this( + : this( keyValueService, logger, backOfficeSecurityAccessor, StaticServiceProvider.Instance.GetRequiredService()) - { - } + { + } - public MetricsConsentService( - IKeyValueService keyValueService, - ILogger logger, - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IUserService userService - ){ + public MetricsConsentService( + IKeyValueService keyValueService, + ILogger logger, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IUserService userService) + { _keyValueService = keyValueService; _logger = logger; _backOfficeSecurityAccessor = backOfficeSecurityAccessor; - _userService = userService; - } + _userService = userService; + } public TelemetryLevel GetConsentLevel() { @@ -69,9 +70,11 @@ public void SetConsentLevel(TelemetryLevel telemetryLevel) { IUser? currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; if (currentUser is null) - { - currentUser = _userService.GetUserById(Constants.Security.SuperUserId); - }_logger.LogInformation("Telemetry level set to {telemetryLevel} by {username}", telemetryLevel, currentUser?.Username); + { + currentUser = _userService.GetUserById(Constants.Security.SuperUserId); + } + + _logger.LogInformation("Telemetry level set to {telemetryLevel} by {username}", telemetryLevel, currentUser?.Username); _keyValueService.SetValue(Key, telemetryLevel.ToString()); } } From f86682c39d4a450c09fc2964a239edb0531d0517 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:44:23 +0200 Subject: [PATCH 006/117] Update src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs Co-authored-by: Mole --- src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs index 82bfc0a0984e..e1a82d6108fd 100644 --- a/src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/ContentTypeCacheRefresher.cs @@ -11,8 +11,7 @@ namespace Umbraco.Cms.Core.Cache; -public sealed class ContentTypeCacheRefresher : PayloadCacheRefresherBase +public sealed class ContentTypeCacheRefresher : PayloadCacheRefresherBase { private readonly IContentTypeCommonRepository _contentTypeCommonRepository; private readonly IIdKeyMap _idKeyMap; From fdb6ed5374fe459e61e2197c55d51466609d1c66 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:44:34 +0200 Subject: [PATCH 007/117] Update src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs Co-authored-by: Mole --- src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs b/src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs index 057c98dcf5c7..ea661c549854 100644 --- a/src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/DataTypeCacheRefresher.cs @@ -11,8 +11,7 @@ namespace Umbraco.Cms.Core.Cache; -public sealed class DataTypeCacheRefresher : PayloadCacheRefresherBase +public sealed class DataTypeCacheRefresher : PayloadCacheRefresherBase { private readonly IIdKeyMap _idKeyMap; private readonly IPublishedModelFactory _publishedModelFactory; From 8ac48ed7dece8d42ab7ceb8ceb48718f2a6f2d23 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:44:46 +0200 Subject: [PATCH 008/117] Update src/Umbraco.Core/Cache/DeepCloneAppCache.cs Co-authored-by: Mole --- src/Umbraco.Core/Cache/DeepCloneAppCache.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Cache/DeepCloneAppCache.cs b/src/Umbraco.Core/Cache/DeepCloneAppCache.cs index 1bc38f529a18..da86be4b703a 100644 --- a/src/Umbraco.Core/Cache/DeepCloneAppCache.cs +++ b/src/Umbraco.Core/Cache/DeepCloneAppCache.cs @@ -46,8 +46,7 @@ public DeepCloneAppCache(IAppPolicyCache innerCache) var cached = InnerCache.Get(key, () => { Lazy result = SafeLazy.GetSafeLazy(factory); - var value = result - .Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache + var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache // do not store null values (backward compat), clone / reset to go into the cache return value == null ? null : CheckCloneableAndTracksChanges(value); From e1e76675e7e7f3197e1c78ac395eb8cceccbbf50 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:44:56 +0200 Subject: [PATCH 009/117] Update src/Umbraco.Core/Cache/DomainCacheRefresher.cs Co-authored-by: Mole --- src/Umbraco.Core/Cache/DomainCacheRefresher.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Cache/DomainCacheRefresher.cs b/src/Umbraco.Core/Cache/DomainCacheRefresher.cs index 610a203d6084..a6e46ee2e48b 100644 --- a/src/Umbraco.Core/Cache/DomainCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/DomainCacheRefresher.cs @@ -7,8 +7,7 @@ namespace Umbraco.Cms.Core.Cache; -public sealed class - DomainCacheRefresher : PayloadCacheRefresherBase +public sealed class DomainCacheRefresher : PayloadCacheRefresherBase { private readonly IPublishedSnapshotService _publishedSnapshotService; From 441c4719e7e5a47495354cdf6356d04239f1cc13 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:45:05 +0200 Subject: [PATCH 010/117] Update src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs Co-authored-by: Mole --- src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs b/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs index fe7acb614f22..a417c3b62afe 100644 --- a/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs +++ b/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs @@ -43,7 +43,7 @@ public virtual IEnumerable SearchByKey(string keyStartsWith) { EnterReadLock(); entries = GetDictionaryEntries() - .Where(x => ((string)x.Key)[plen..].InvariantStartsWith(keyStartsWith)) + .Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith)) .ToArray(); // evaluate while locked } finally From bef747bf71ad41292a6aeb751684fbaf3a654e0d Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:45:24 +0200 Subject: [PATCH 011/117] Update src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs Co-authored-by: Mole --- src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs b/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs index a417c3b62afe..692968999944 100644 --- a/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs +++ b/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs @@ -67,7 +67,7 @@ public virtual IEnumerable SearchByKey(string keyStartsWith) { EnterReadLock(); entries = GetDictionaryEntries() - .Where(x => compiled.IsMatch(((string)x.Key)[plen..])) + .Where(x => compiled.IsMatch(((string)x.Key).Substring(plen))) .ToArray(); // evaluate while locked } finally From c059af6faa52ed4aab626170dce2e3b8036ed0a2 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:45:37 +0200 Subject: [PATCH 012/117] Update src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs Co-authored-by: Mole --- src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs b/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs index 692968999944..a6b7ad4fe819 100644 --- a/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs +++ b/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs @@ -207,7 +207,7 @@ public virtual void ClearOfType(Func predicate) return (isInterface ? value is T : value.GetType() == typeOfT) // run predicate on the 'public key' part only, ie without prefix - && predicate(((string)x.Key)[plen..], (T)value); + && predicate(((string) x.Key).Substring(plen), (T) value); })) { RemoveEntry((string)entry.Key); From 1c93056fc55f5e483a7bfdd54fdbcb85659f172d Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:45:49 +0200 Subject: [PATCH 013/117] Update src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs Co-authored-by: Mole --- src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs b/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs index a6b7ad4fe819..112f17477735 100644 --- a/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs +++ b/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs @@ -227,7 +227,7 @@ public virtual void ClearByKey(string keyStartsWith) { EnterWriteLock(); foreach (KeyValuePair entry in GetDictionaryEntries() - .Where(x => ((string)x.Key)[plen..].InvariantStartsWith(keyStartsWith)) + .Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith)) .ToArray()) { RemoveEntry((string)entry.Key); From 6834c0292eef09c0eb2c57e12b289af93849ef0a Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:46:00 +0200 Subject: [PATCH 014/117] Update src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs Co-authored-by: Mole --- src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs b/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs index 112f17477735..967d5aa5a70b 100644 --- a/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs +++ b/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs @@ -248,7 +248,7 @@ public virtual void ClearByRegex(string regex) { EnterWriteLock(); foreach (KeyValuePair entry in GetDictionaryEntries() - .Where(x => compiled.IsMatch(((string)x.Key)[plen..])) + .Where(x => compiled.IsMatch(((string)x.Key).Substring(plen))) .ToArray()) { RemoveEntry((string)entry.Key); From dfc0bf88608a6665989541b8378495227268dbbd Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:46:15 +0200 Subject: [PATCH 015/117] Update src/Umbraco.Core/Cache/MacroCacheRefresher.cs Co-authored-by: Mole --- src/Umbraco.Core/Cache/MacroCacheRefresher.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Cache/MacroCacheRefresher.cs b/src/Umbraco.Core/Cache/MacroCacheRefresher.cs index 6d984237fd7e..9975abae8c72 100644 --- a/src/Umbraco.Core/Cache/MacroCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MacroCacheRefresher.cs @@ -6,8 +6,7 @@ namespace Umbraco.Cms.Core.Cache; -public sealed class - MacroCacheRefresher : PayloadCacheRefresherBase +public sealed class MacroCacheRefresher : PayloadCacheRefresherBase { public MacroCacheRefresher( AppCaches appCaches, From 6e80faa53a760cc08c51b95b4c94a3a4d3e05b22 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:46:28 +0200 Subject: [PATCH 016/117] Update src/Umbraco.Core/Cache/MediaCacheRefresher.cs Co-authored-by: Mole --- src/Umbraco.Core/Cache/MediaCacheRefresher.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Cache/MediaCacheRefresher.cs b/src/Umbraco.Core/Cache/MediaCacheRefresher.cs index 5ba07f1ae570..43e6a7ce47a1 100644 --- a/src/Umbraco.Core/Cache/MediaCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MediaCacheRefresher.cs @@ -10,8 +10,7 @@ namespace Umbraco.Cms.Core.Cache; -public sealed class - MediaCacheRefresher : PayloadCacheRefresherBase +public sealed class MediaCacheRefresher : PayloadCacheRefresherBase { private readonly IIdKeyMap _idKeyMap; private readonly IPublishedSnapshotService _publishedSnapshotService; From f10dd03fcdaeb4ae9397f771454f2f2047ee32d6 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:46:39 +0200 Subject: [PATCH 017/117] Update src/Umbraco.Core/Cache/MemberCacheRefresher.cs Co-authored-by: Mole --- src/Umbraco.Core/Cache/MemberCacheRefresher.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Cache/MemberCacheRefresher.cs b/src/Umbraco.Core/Cache/MemberCacheRefresher.cs index 712833093787..d16e41209438 100644 --- a/src/Umbraco.Core/Cache/MemberCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MemberCacheRefresher.cs @@ -10,8 +10,7 @@ namespace Umbraco.Cms.Core.Cache; -public sealed class - MemberCacheRefresher : PayloadCacheRefresherBase +public sealed class MemberCacheRefresher : PayloadCacheRefresherBase { #region Define From 1a4921c316d98194609cae4412a8ba13a69ff2ed Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:46:55 +0200 Subject: [PATCH 018/117] Update src/Umbraco.Core/Cache/MemberGroupCacheRefresher.cs Co-authored-by: Mole --- src/Umbraco.Core/Cache/MemberGroupCacheRefresher.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Cache/MemberGroupCacheRefresher.cs b/src/Umbraco.Core/Cache/MemberGroupCacheRefresher.cs index 71f1b35f1766..05bd6049c8e9 100644 --- a/src/Umbraco.Core/Cache/MemberGroupCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MemberGroupCacheRefresher.cs @@ -5,8 +5,7 @@ namespace Umbraco.Cms.Core.Cache; -public sealed class MemberGroupCacheRefresher : PayloadCacheRefresherBase +public sealed class MemberGroupCacheRefresher : PayloadCacheRefresherBase { public MemberGroupCacheRefresher(AppCaches appCaches, IJsonSerializer jsonSerializer, IEventAggregator eventAggregator, ICacheRefresherNotificationFactory factory) : base(appCaches, jsonSerializer, eventAggregator, factory) From 13f05bb3aaf6db893c738ab6dcb026b5a4c36637 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:47:10 +0200 Subject: [PATCH 019/117] Update src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs Co-authored-by: Mole --- src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs index c724f8267bc8..d9c733da7d18 100644 --- a/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/OrderedCollectionBuilderBase.cs @@ -6,8 +6,7 @@ namespace Umbraco.Cms.Core.Composing; /// The type of the builder. /// The type of the collection. /// The type of the items. -public abstract class - OrderedCollectionBuilderBase : CollectionBuilderBase +public abstract class OrderedCollectionBuilderBase : CollectionBuilderBase where TBuilder : OrderedCollectionBuilderBase where TCollection : class, IBuilderCollection { From e42fa7a2881932d050525df28213cccb9370fd80 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:47:25 +0200 Subject: [PATCH 020/117] Update src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs Co-authored-by: Mole --- .../Models/RequestHandlerSettings.cs | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs index a80dad68744a..0c5d39f47a76 100644 --- a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs @@ -19,18 +19,30 @@ public class RequestHandlerSettings internal static readonly CharItem[] DefaultCharCollection = { - new() { Char = " ", Replacement = "-" }, new() { Char = "\"", Replacement = string.Empty }, - new() { Char = "'", Replacement = string.Empty }, new() { Char = "%", Replacement = string.Empty }, - new() { Char = ".", Replacement = string.Empty }, new() { Char = ";", Replacement = string.Empty }, - new() { Char = "/", Replacement = string.Empty }, new() { Char = "\\", Replacement = string.Empty }, - new() { Char = ":", Replacement = string.Empty }, new() { Char = "#", Replacement = string.Empty }, - new() { Char = "+", Replacement = "plus" }, new() { Char = "*", Replacement = "star" }, - new() { Char = "&", Replacement = string.Empty }, new() { Char = "?", Replacement = string.Empty }, - new() { Char = "æ", Replacement = "ae" }, new() { Char = "ä", Replacement = "ae" }, - new() { Char = "ø", Replacement = "oe" }, new() { Char = "ö", Replacement = "oe" }, - new() { Char = "å", Replacement = "aa" }, new() { Char = "ü", Replacement = "ue" }, - new() { Char = "ß", Replacement = "ss" }, new() { Char = "|", Replacement = "-" }, - new() { Char = "<", Replacement = string.Empty }, new() { Char = ">", Replacement = string.Empty }, + new () { Char = " ", Replacement = "-" }, + new () { Char = "\"", Replacement = string.Empty }, + new () { Char = "'", Replacement = string.Empty }, + new () { Char = "%", Replacement = string.Empty }, + new () { Char = ".", Replacement = string.Empty }, + new () { Char = ";", Replacement = string.Empty }, + new () { Char = "/", Replacement = string.Empty }, + new () { Char = "\\", Replacement = string.Empty }, + new () { Char = ":", Replacement = string.Empty }, + new () { Char = "#", Replacement = string.Empty }, + new () { Char = "+", Replacement = "plus" }, + new () { Char = "*", Replacement = "star" }, + new () { Char = "&", Replacement = string.Empty }, + new () { Char = "?", Replacement = string.Empty }, + new () { Char = "æ", Replacement = "ae" }, + new () { Char = "ä", Replacement = "ae" }, + new () { Char = "ø", Replacement = "oe" }, + new () { Char = "ö", Replacement = "oe" }, + new () { Char = "å", Replacement = "aa" }, + new () { Char = "ü", Replacement = "ue" }, + new () { Char = "ß", Replacement = "ss" }, + new () { Char = "|", Replacement = "-" }, + new () { Char = "<", Replacement = string.Empty }, + new () { Char = ">", Replacement = string.Empty }, }; /// From 36db3665d88385558fe3dae7ab2ef1a970e1ae7c Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:47:43 +0200 Subject: [PATCH 021/117] Update src/Umbraco.Core/Composing/SetCollectionBuilderBase.cs Co-authored-by: Mole --- src/Umbraco.Core/Composing/SetCollectionBuilderBase.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Composing/SetCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/SetCollectionBuilderBase.cs index 5ca76024d147..b686067d30b8 100644 --- a/src/Umbraco.Core/Composing/SetCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/SetCollectionBuilderBase.cs @@ -12,8 +12,7 @@ namespace Umbraco.Cms.Core.Composing; /// where items are not ordered. /// /// -public abstract class - SetCollectionBuilderBase : CollectionBuilderBase +public abstract class SetCollectionBuilderBase : CollectionBuilderBase where TBuilder : SetCollectionBuilderBase where TCollection : class, IBuilderCollection { From 003d0c2b1dda3de4f7d097155b9225fe9dc1a93a Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:47:59 +0200 Subject: [PATCH 022/117] Update src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs Co-authored-by: Mole --- src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs index 9d1062fd53a9..56b714d35a0e 100644 --- a/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/WeightedCollectionBuilderBase.cs @@ -6,8 +6,7 @@ namespace Umbraco.Cms.Core.Composing; /// The type of the builder. /// The type of the collection. /// The type of the items. -public abstract class - WeightedCollectionBuilderBase : CollectionBuilderBase +public abstract class WeightedCollectionBuilderBase : CollectionBuilderBase where TBuilder : WeightedCollectionBuilderBase where TCollection : class, IBuilderCollection { From 6c5da5acbf8b4308830c7995d51fe188ea9b8d16 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:48:17 +0200 Subject: [PATCH 023/117] Update src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs Co-authored-by: Mole --- .../ContentApps/ContentAppFactoryCollectionBuilder.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs b/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs index bef95c77cf86..fe6fdd423a2b 100644 --- a/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs +++ b/src/Umbraco.Core/ContentApps/ContentAppFactoryCollectionBuilder.cs @@ -8,8 +8,7 @@ namespace Umbraco.Cms.Core.ContentApps; -public class ContentAppFactoryCollectionBuilder : OrderedCollectionBuilderBase +public class ContentAppFactoryCollectionBuilder : OrderedCollectionBuilderBase { protected override ContentAppFactoryCollectionBuilder This => this; From 9239fc3000be0e3ae7a647f490cea4d9d85a9551 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:48:32 +0200 Subject: [PATCH 024/117] Update src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs Co-authored-by: Mole --- src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs b/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs index 56efe47f6b58..50867c90f4a0 100644 --- a/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs +++ b/src/Umbraco.Core/Dashboards/DashboardCollectionBuilder.cs @@ -4,9 +4,7 @@ namespace Umbraco.Cms.Core.Dashboards; -public class - DashboardCollectionBuilder : WeightedCollectionBuilderBase +public class DashboardCollectionBuilder : WeightedCollectionBuilderBase { protected override DashboardCollectionBuilder This => this; From 649431f63481bfa3a10b4b02b51877a00f729d37 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:49:13 +0200 Subject: [PATCH 025/117] Update src/Umbraco.Core/Extensions/StringExtensions.cs Co-authored-by: Mole --- src/Umbraco.Core/Extensions/StringExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index 91d6c061efee..672a7bb51112 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -86,7 +86,7 @@ public static string StripFileExtension(this string fileName) var lastIndex = fileName.LastIndexOf('.'); if (lastIndex > 0) { - var ext = fileName[lastIndex..]; + var ext = fileName.Substring(lastIndex); // file extensions cannot contain whitespace if (ext.Contains(" ")) From cddaa5c6d0427e79a596f8ea2804b8ce825a4950 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:49:34 +0200 Subject: [PATCH 026/117] Update src/Umbraco.Core/Extensions/StringExtensions.cs Co-authored-by: Mole --- src/Umbraco.Core/Extensions/StringExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index 672a7bb51112..e8ca17f1210d 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -94,7 +94,7 @@ public static string StripFileExtension(this string fileName) return fileName; } - return string.Format("{0}", fileName[..fileName.IndexOf(ext, StringComparison.Ordinal)]); + return string.Format("{0}", fileName.Substring(0, fileName.IndexOf(ext, StringComparison.Ordinal))); } return fileName; From cf4e8a760eca591bc5693d00ee5a0922b77bf58c Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:50:00 +0200 Subject: [PATCH 027/117] Update src/Umbraco.Core/Extensions/StringExtensions.cs Co-authored-by: Mole --- src/Umbraco.Core/Extensions/StringExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index e8ca17f1210d..e8ba6452f627 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -377,7 +377,7 @@ public static string TrimStart(this string value, string forRemoving) while (value.StartsWith(forRemoving, StringComparison.InvariantCultureIgnoreCase)) { - value = value[forRemoving.Length..]; + value = value.Substring(forRemoving.Length); } return value; From 0f39bd3930876c5521d313fe5872b9867faa4e37 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:50:17 +0200 Subject: [PATCH 028/117] Update src/Umbraco.Core/Extensions/StringExtensions.cs Co-authored-by: Mole --- src/Umbraco.Core/Extensions/StringExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index e8ba6452f627..75fd49a08c02 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -496,7 +496,7 @@ public static Guid EncodeAsGuid(this string input) var convertToHex = input.ConvertToHex(); var hexLength = convertToHex.Length < 32 ? convertToHex.Length : 32; - var hex = convertToHex[..hexLength].PadLeft(32, '0'); + var hex = convertToHex.Substring(0, hexLength).PadLeft(32, '0'); Guid output = Guid.Empty; return Guid.TryParse(hex, out output) ? output : Guid.Empty; } From 5f1b3e6229541938c9d48d319e74ecb361980ca9 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:50:34 +0200 Subject: [PATCH 029/117] Update src/Umbraco.Core/Extensions/StringExtensions.cs Co-authored-by: Mole --- src/Umbraco.Core/Extensions/StringExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index 75fd49a08c02..5a047a9c6bea 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -527,8 +527,8 @@ public static string DecodeFromHex(this string hexValue) var strValue = string.Empty; while (hexValue.Length > 0) { - strValue += Convert.ToChar(Convert.ToUInt32(hexValue[..2], 16)).ToString(); - hexValue = hexValue[2..]; + strValue += Convert.ToChar(Convert.ToUInt32(hexValue.Substring(0, 2), 16)).ToString(); + hexValue = hexValue.Substring(2, hexValue.Length - 2); } return strValue; From acb21aac2a7de1db3668ca8cac1d40068039d572 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:50:46 +0200 Subject: [PATCH 030/117] Update src/Umbraco.Core/Extensions/StringExtensions.cs Co-authored-by: Mole --- src/Umbraco.Core/Extensions/StringExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index 5a047a9c6bea..95008f383a60 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -870,7 +870,7 @@ public static string Truncate(this string text, int maxLength, string suffix = " return truncatedString; } - truncatedString = text[..strLength]; + truncatedString = text.Substring(0, strLength); truncatedString = truncatedString.TrimEnd(); truncatedString += suffix; From 600b07d7e03d7ab1ed2b3847c507f5ca49317ec5 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:51:02 +0200 Subject: [PATCH 031/117] Update src/Umbraco.Core/Extensions/StringExtensions.cs Co-authored-by: Mole --- src/Umbraco.Core/Extensions/StringExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index 95008f383a60..927983f67d13 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -913,7 +913,7 @@ public static string OrIfNullOrWhiteSpace(this string input, string alternative) public static string ToFirstUpper(this string input) => string.IsNullOrWhiteSpace(input) ? input - : input[..1].ToUpper() + input[1..]; + : input.Substring(0, 1).ToUpper() + input.Substring(1); /// /// Returns a copy of the string with the first character converted to lowercase. From 720d746e239ab054462c9bf2d8ef982ba8bbfd67 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:51:15 +0200 Subject: [PATCH 032/117] Update src/Umbraco.Core/Notifications/ContentTypeRefreshedNotification.cs Co-authored-by: Mole --- .../Notifications/ContentTypeRefreshedNotification.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Notifications/ContentTypeRefreshedNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeRefreshedNotification.cs index d30cab14ca91..b49eef287656 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeRefreshedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeRefreshedNotification.cs @@ -10,8 +10,7 @@ namespace Umbraco.Cms.Core.Notifications; public class ContentTypeRefreshedNotification : ContentTypeRefreshNotification { public ContentTypeRefreshedNotification(ContentTypeChange target, EventMessages messages) - : base( - target, messages) + : base(target, messages) { } From 13c5236699d93c4b40ac432189dca7f131e8f872 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:51:23 +0200 Subject: [PATCH 033/117] Update src/Umbraco.Core/Notifications/ContentTypeSavedNotification.cs Co-authored-by: Mole --- .../Notifications/ContentTypeSavedNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/ContentTypeSavedNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeSavedNotification.cs index 0cbe82dadedd..f5c45c632384 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeSavedNotification.cs @@ -11,9 +11,7 @@ public ContentTypeSavedNotification(IContentType target, EventMessages messages) } public ContentTypeSavedNotification(IEnumerable target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } From e0b16b48a0e3eb129fb4b2cb6d4a2a6b8047cffd Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:51:32 +0200 Subject: [PATCH 034/117] Update src/Umbraco.Core/Notifications/ContentTypeSavingNotification.cs Co-authored-by: Mole --- .../Notifications/ContentTypeSavingNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/ContentTypeSavingNotification.cs b/src/Umbraco.Core/Notifications/ContentTypeSavingNotification.cs index b94a2770cfbb..5c1bc5d611e1 100644 --- a/src/Umbraco.Core/Notifications/ContentTypeSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentTypeSavingNotification.cs @@ -11,9 +11,7 @@ public ContentTypeSavingNotification(IContentType target, EventMessages messages } public ContentTypeSavingNotification(IEnumerable target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } From 883cb0daa59edee08fe70288157435df81c637e3 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:51:49 +0200 Subject: [PATCH 035/117] Update src/Umbraco.Core/Notifications/DictionaryCacheRefresherNotification.cs Co-authored-by: Mole --- .../Notifications/DictionaryCacheRefresherNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/DictionaryCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/DictionaryCacheRefresherNotification.cs index 11b23cc5fdbe..170e8e21be86 100644 --- a/src/Umbraco.Core/Notifications/DictionaryCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryCacheRefresherNotification.cs @@ -5,9 +5,7 @@ namespace Umbraco.Cms.Core.Notifications; public class DictionaryCacheRefresherNotification : CacheRefresherNotification { public DictionaryCacheRefresherNotification(object messageObject, MessageType messageType) - : base( - messageObject, - messageType) + : base(messageObject, messageType) { } } From d780e7a7daa95683bea854e7019943b19a4c71b7 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:52:01 +0200 Subject: [PATCH 036/117] Update src/Umbraco.Core/Notifications/DataTypeCacheRefresherNotification.cs Co-authored-by: Mole --- .../Notifications/DataTypeCacheRefresherNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/DataTypeCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/DataTypeCacheRefresherNotification.cs index ed3bd26d1477..5f8b34fb221b 100644 --- a/src/Umbraco.Core/Notifications/DataTypeCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/DataTypeCacheRefresherNotification.cs @@ -5,9 +5,7 @@ namespace Umbraco.Cms.Core.Notifications; public class DataTypeCacheRefresherNotification : CacheRefresherNotification { public DataTypeCacheRefresherNotification(object messageObject, MessageType messageType) - : base( - messageObject, - messageType) + : base(messageObject, messageType) { } } From 8e4e9d0259929e906b6399821277825148f1409d Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:52:10 +0200 Subject: [PATCH 037/117] Update src/Umbraco.Core/Notifications/ContentUnpublishingNotification.cs Co-authored-by: Mole --- .../Notifications/ContentUnpublishingNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/ContentUnpublishingNotification.cs b/src/Umbraco.Core/Notifications/ContentUnpublishingNotification.cs index aa26e61e41d9..7fc0717c0421 100644 --- a/src/Umbraco.Core/Notifications/ContentUnpublishingNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentUnpublishingNotification.cs @@ -14,9 +14,7 @@ public ContentUnpublishingNotification(IContent target, EventMessages messages) } public ContentUnpublishingNotification(IEnumerable target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } From cbfdc91c46aba5fd88fcd64530f0ed10fa3cd077 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:52:25 +0200 Subject: [PATCH 038/117] Update src/Umbraco.Core/Notifications/DictionaryItemDeletingNotification.cs Co-authored-by: Mole --- .../Notifications/DictionaryItemDeletingNotification.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Notifications/DictionaryItemDeletingNotification.cs b/src/Umbraco.Core/Notifications/DictionaryItemDeletingNotification.cs index 4f66c33b455f..d882bb594f6d 100644 --- a/src/Umbraco.Core/Notifications/DictionaryItemDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryItemDeletingNotification.cs @@ -14,8 +14,7 @@ public DictionaryItemDeletingNotification(IDictionaryItem target, EventMessages } public DictionaryItemDeletingNotification(IEnumerable target, EventMessages messages) - : base( - target, messages) + : base(target, messages) { } } From 2258bebd08a201338bd371771e8ff1c08b4bca17 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:52:41 +0200 Subject: [PATCH 039/117] Update src/Umbraco.Core/Notifications/DictionaryItemSavedNotification.cs Co-authored-by: Mole --- .../Notifications/DictionaryItemSavedNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/DictionaryItemSavedNotification.cs b/src/Umbraco.Core/Notifications/DictionaryItemSavedNotification.cs index e8423507aa82..386871a28b7e 100644 --- a/src/Umbraco.Core/Notifications/DictionaryItemSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryItemSavedNotification.cs @@ -14,9 +14,7 @@ public DictionaryItemSavedNotification(IDictionaryItem target, EventMessages mes } public DictionaryItemSavedNotification(IEnumerable target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } From cf9b7a610fb4818833f6fb5a355e80c4badc1e78 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:53:00 +0200 Subject: [PATCH 040/117] Update src/Umbraco.Core/Notifications/DictionaryItemSavingNotification.cs Co-authored-by: Mole --- .../Notifications/DictionaryItemSavingNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/DictionaryItemSavingNotification.cs b/src/Umbraco.Core/Notifications/DictionaryItemSavingNotification.cs index b2e6646de545..517fc772a02f 100644 --- a/src/Umbraco.Core/Notifications/DictionaryItemSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/DictionaryItemSavingNotification.cs @@ -14,9 +14,7 @@ public DictionaryItemSavingNotification(IDictionaryItem target, EventMessages me } public DictionaryItemSavingNotification(IEnumerable target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } From 4236d1b5728604edc8c045f57cfdd3db3d19eaab Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:53:15 +0200 Subject: [PATCH 041/117] Update src/Umbraco.Core/Notifications/DomainCacheRefresherNotification.cs Co-authored-by: Mole --- .../Notifications/DomainCacheRefresherNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/DomainCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/DomainCacheRefresherNotification.cs index 5a9ecb6a184f..86114b500375 100644 --- a/src/Umbraco.Core/Notifications/DomainCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/DomainCacheRefresherNotification.cs @@ -5,9 +5,7 @@ namespace Umbraco.Cms.Core.Notifications; public class DomainCacheRefresherNotification : CacheRefresherNotification { public DomainCacheRefresherNotification(object messageObject, MessageType messageType) - : base( - messageObject, - messageType) + : base(messageObject, messageType) { } } From 10dadb8942879bedc397aada5b51a320a6b4baa1 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:53:30 +0200 Subject: [PATCH 042/117] Update src/Umbraco.Core/Notifications/LanguageCacheRefresherNotification.cs Co-authored-by: Mole --- .../Notifications/LanguageCacheRefresherNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/LanguageCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/LanguageCacheRefresherNotification.cs index b07277a3fa83..8e62c68b1d39 100644 --- a/src/Umbraco.Core/Notifications/LanguageCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/LanguageCacheRefresherNotification.cs @@ -5,9 +5,7 @@ namespace Umbraco.Cms.Core.Notifications; public class LanguageCacheRefresherNotification : CacheRefresherNotification { public LanguageCacheRefresherNotification(object messageObject, MessageType messageType) - : base( - messageObject, - messageType) + : base(messageObject, messageType) { } } From 02727d655cc6d30d8f57c5df87e18d626daf1182 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:53:47 +0200 Subject: [PATCH 043/117] Update src/Umbraco.Core/Notifications/MacroCacheRefresherNotification.cs Co-authored-by: Mole --- .../Notifications/MacroCacheRefresherNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MacroCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/MacroCacheRefresherNotification.cs index c4e580113914..4d8815507443 100644 --- a/src/Umbraco.Core/Notifications/MacroCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/MacroCacheRefresherNotification.cs @@ -5,9 +5,7 @@ namespace Umbraco.Cms.Core.Notifications; public class MacroCacheRefresherNotification : CacheRefresherNotification { public MacroCacheRefresherNotification(object messageObject, MessageType messageType) - : base( - messageObject, - messageType) + : base(messageObject, messageType) { } } From c012522e69fa294db118600c8a915dd134f18c7e Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:54:00 +0200 Subject: [PATCH 044/117] Update src/Umbraco.Core/Notifications/MediaCacheRefresherNotification.cs Co-authored-by: Mole --- .../Notifications/MediaCacheRefresherNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MediaCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/MediaCacheRefresherNotification.cs index 877ad75617e8..9277e20423b4 100644 --- a/src/Umbraco.Core/Notifications/MediaCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaCacheRefresherNotification.cs @@ -5,9 +5,7 @@ namespace Umbraco.Cms.Core.Notifications; public class MediaCacheRefresherNotification : CacheRefresherNotification { public MediaCacheRefresherNotification(object messageObject, MessageType messageType) - : base( - messageObject, - messageType) + : base(messageObject, messageType) { } } From 9bd4d8ab35cb5d904f986d835f625c071444429f Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:54:14 +0200 Subject: [PATCH 045/117] Update src/Umbraco.Core/Notifications/MediaEmptiedRecycleBinNotification.cs Co-authored-by: Mole --- .../Notifications/MediaEmptiedRecycleBinNotification.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MediaEmptiedRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/MediaEmptiedRecycleBinNotification.cs index e0c58c8653bb..3aea97d608b2 100644 --- a/src/Umbraco.Core/Notifications/MediaEmptiedRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaEmptiedRecycleBinNotification.cs @@ -9,8 +9,7 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class MediaEmptiedRecycleBinNotification : EmptiedRecycleBinNotification { public MediaEmptiedRecycleBinNotification(IEnumerable deletedEntities, EventMessages messages) - : base( - deletedEntities, messages) + : base(deletedEntities, messages) { } } From 0bd3f025f8eec9107eca1c21bc7cfef64f9ce524 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:54:39 +0200 Subject: [PATCH 046/117] Update src/Umbraco.Core/Notifications/MediaEmptyingRecycleBinNotification.cs Co-authored-by: Mole --- .../Notifications/MediaEmptyingRecycleBinNotification.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MediaEmptyingRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/MediaEmptyingRecycleBinNotification.cs index 43a3971bdb42..432d48084796 100644 --- a/src/Umbraco.Core/Notifications/MediaEmptyingRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaEmptyingRecycleBinNotification.cs @@ -9,8 +9,7 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class MediaEmptyingRecycleBinNotification : EmptyingRecycleBinNotification { public MediaEmptyingRecycleBinNotification(IEnumerable deletedEntities, EventMessages messages) - : base( - deletedEntities, messages) + : base(deletedEntities, messages) { } } From 2d72effd46b70ade1d3b34e218d3849b7d4c703a Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:54:51 +0200 Subject: [PATCH 047/117] Update src/Umbraco.Core/Notifications/MediaMovedNotification.cs Co-authored-by: Mole --- src/Umbraco.Core/Notifications/MediaMovedNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MediaMovedNotification.cs b/src/Umbraco.Core/Notifications/MediaMovedNotification.cs index 07ded8c61459..d7cf614ed9ec 100644 --- a/src/Umbraco.Core/Notifications/MediaMovedNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaMovedNotification.cs @@ -14,9 +14,7 @@ public MediaMovedNotification(MoveEventInfo target, EventMessages messag } public MediaMovedNotification(IEnumerable> target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } From 2af88981e3a9d9e0cc080b385f37d132c5b16f39 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:55:12 +0200 Subject: [PATCH 048/117] Update src/Umbraco.Core/Notifications/MediaMovedToRecycleBinNotification.cs Co-authored-by: Mole --- .../Notifications/MediaMovedToRecycleBinNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MediaMovedToRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/MediaMovedToRecycleBinNotification.cs index b072c1282b23..038d314db93e 100644 --- a/src/Umbraco.Core/Notifications/MediaMovedToRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaMovedToRecycleBinNotification.cs @@ -9,9 +9,7 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class MediaMovedToRecycleBinNotification : MovedToRecycleBinNotification { public MediaMovedToRecycleBinNotification(MoveEventInfo target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } From ce850c09b1905e67e8815d934c4928e1b33b780b Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:57:25 +0200 Subject: [PATCH 049/117] Update src/Umbraco.Core/Extensions/StringExtensions.cs Co-authored-by: Mole --- src/Umbraco.Core/Extensions/StringExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index 927983f67d13..a6ae70eb56f0 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -923,7 +923,7 @@ public static string ToFirstUpper(this string input) => public static string ToFirstLower(this string input) => string.IsNullOrWhiteSpace(input) ? input - : input[..1].ToLower() + input[1..]; + : input.Substring(0, 1).ToLower() + input.Substring(1); /// /// Returns a copy of the string with the first character converted to uppercase using the casing rules of the From 7bb7299540cdc350a9d240f8f4bf6f48efacb0a2 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:57:41 +0200 Subject: [PATCH 050/117] Update src/Umbraco.Core/Extensions/StringExtensions.cs Co-authored-by: Mole --- src/Umbraco.Core/Extensions/StringExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index a6ae70eb56f0..ce689537d20c 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -935,7 +935,7 @@ public static string ToFirstLower(this string input) => public static string ToFirstUpper(this string input, CultureInfo culture) => string.IsNullOrWhiteSpace(input) ? input - : input[..1].ToUpper(culture) + input[1..]; + : input.Substring(0, 1).ToUpper(culture) + input.Substring(1); /// /// Returns a copy of the string with the first character converted to lowercase using the casing rules of the From 26b88e9da42e8651d13c614011300d303a993e7a Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:57:53 +0200 Subject: [PATCH 051/117] Update src/Umbraco.Core/Extensions/StringExtensions.cs Co-authored-by: Mole --- src/Umbraco.Core/Extensions/StringExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index ce689537d20c..15e7be309e47 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -947,7 +947,7 @@ public static string ToFirstUpper(this string input, CultureInfo culture) => public static string ToFirstLower(this string input, CultureInfo culture) => string.IsNullOrWhiteSpace(input) ? input - : input[..1].ToLower(culture) + input[1..]; + : input.Substring(0, 1).ToLower(culture) + input.Substring(1); /// /// Returns a copy of the string with the first character converted to uppercase using the casing rules of the From 21b5bb19e697a052493e021fb1b93865a474fce6 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:58:16 +0200 Subject: [PATCH 052/117] Update src/Umbraco.Core/Extensions/StringExtensions.cs Co-authored-by: Mole --- src/Umbraco.Core/Extensions/StringExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index 15e7be309e47..e9de90cd49dc 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -958,7 +958,7 @@ public static string ToFirstLower(this string input, CultureInfo culture) => public static string ToFirstUpperInvariant(this string input) => string.IsNullOrWhiteSpace(input) ? input - : input[..1].ToUpperInvariant() + input[1..]; + : input.Substring(0, 1).ToUpperInvariant() + input.Substring(1); /// /// Returns a copy of the string with the first character converted to lowercase using the casing rules of the From 67f6885e52a1e90f19ea3368ad92108f4c60b7a8 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:58:28 +0200 Subject: [PATCH 053/117] Update src/Umbraco.Core/Extensions/StringExtensions.cs Co-authored-by: Mole --- src/Umbraco.Core/Extensions/StringExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index e9de90cd49dc..5ac402f0a85c 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -969,7 +969,7 @@ public static string ToFirstUpperInvariant(this string input) => public static string ToFirstLowerInvariant(this string input) => string.IsNullOrWhiteSpace(input) ? input - : input[..1].ToLowerInvariant() + input[1..]; + : input.Substring(0, 1).ToLowerInvariant() + input.Substring(1); /// /// Returns a new string in which all occurrences of specified strings are replaced by other specified strings. From 7a08b7266fbea935a0cb5dc3bf5623884ed39734 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:59:22 +0200 Subject: [PATCH 054/117] Update src/Umbraco.Core/Extensions/StringExtensions.cs Co-authored-by: Mole --- src/Umbraco.Core/Extensions/StringExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index 5ac402f0a85c..3d4bb591e64d 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -1046,7 +1046,7 @@ public static string ReplaceFirst(this string text, string search, string replac return text; } - return text[..pos] + replace + text[(pos + search.Length)..]; + text.Substring(0, pos) + replace + text.Substring(pos + search.Length); } /// From af2f9f7929c4f7efb6bd816badb0af05ff52b904 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:59:39 +0200 Subject: [PATCH 055/117] Update src/Umbraco.Core/Notifications/MediaMovedToRecycleBinNotification.cs Co-authored-by: Mole --- .../Notifications/MediaMovedToRecycleBinNotification.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MediaMovedToRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/MediaMovedToRecycleBinNotification.cs index 038d314db93e..78d771847b1c 100644 --- a/src/Umbraco.Core/Notifications/MediaMovedToRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaMovedToRecycleBinNotification.cs @@ -14,8 +14,7 @@ public MediaMovedToRecycleBinNotification(MoveEventInfo target, EventMes } public MediaMovedToRecycleBinNotification(IEnumerable> target, EventMessages messages) - : base( - target, messages) + : base(target, messages) { } } From 61c9bedbb21da297a9b4e2504031e52f5ea01fe4 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:59:54 +0200 Subject: [PATCH 056/117] Update src/Umbraco.Core/Notifications/MediaMovingNotification.cs Co-authored-by: Mole --- src/Umbraco.Core/Notifications/MediaMovingNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MediaMovingNotification.cs b/src/Umbraco.Core/Notifications/MediaMovingNotification.cs index 6ea117d636af..c1f5a7ab942c 100644 --- a/src/Umbraco.Core/Notifications/MediaMovingNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaMovingNotification.cs @@ -14,9 +14,7 @@ public MediaMovingNotification(MoveEventInfo target, EventMessages messa } public MediaMovingNotification(IEnumerable> target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } From 47eb1c4488069b6d8aba7023f27aa7fa164cd92a Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:00:28 +0200 Subject: [PATCH 057/117] Update src/Umbraco.Core/Notifications/MediaMovingToRecycleBinNotification.cs Co-authored-by: Mole --- .../Notifications/MediaMovingToRecycleBinNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MediaMovingToRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/MediaMovingToRecycleBinNotification.cs index c236262530df..ee5618f9fb29 100644 --- a/src/Umbraco.Core/Notifications/MediaMovingToRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaMovingToRecycleBinNotification.cs @@ -9,9 +9,7 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class MediaMovingToRecycleBinNotification : MovingToRecycleBinNotification { public MediaMovingToRecycleBinNotification(MoveEventInfo target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } From f547324cc6a1d05eac7281c83a146c3e54ab6427 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:00:45 +0200 Subject: [PATCH 058/117] Update src/Umbraco.Core/Notifications/MediaTreeChangeNotification.cs Co-authored-by: Mole --- src/Umbraco.Core/Notifications/MediaTreeChangeNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MediaTreeChangeNotification.cs b/src/Umbraco.Core/Notifications/MediaTreeChangeNotification.cs index c8a2e9d19b13..60dba8eba359 100644 --- a/src/Umbraco.Core/Notifications/MediaTreeChangeNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTreeChangeNotification.cs @@ -12,9 +12,7 @@ public MediaTreeChangeNotification(TreeChange target, EventMessages mess } public MediaTreeChangeNotification(IEnumerable> target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } From 5d6c842dfbfd42b75f52e09650b351aae94d54c1 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:00:59 +0200 Subject: [PATCH 059/117] Update src/Umbraco.Core/Notifications/MediaTreeChangeNotification.cs Co-authored-by: Mole --- src/Umbraco.Core/Notifications/MediaTreeChangeNotification.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MediaTreeChangeNotification.cs b/src/Umbraco.Core/Notifications/MediaTreeChangeNotification.cs index 60dba8eba359..cd896cd1fc55 100644 --- a/src/Umbraco.Core/Notifications/MediaTreeChangeNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTreeChangeNotification.cs @@ -25,8 +25,7 @@ public MediaTreeChangeNotification( } public MediaTreeChangeNotification(IMedia target, TreeChangeTypes changeTypes, EventMessages messages) - : base( - new TreeChange(target, changeTypes), messages) + : base(new TreeChange(target, changeTypes), messages) { } } From a9f17b1e7f84b74f7785139c3841023dd1661257 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:01:18 +0200 Subject: [PATCH 060/117] Update src/Umbraco.Core/Notifications/MediaTypeChangedNotification.cs Co-authored-by: Mole --- .../Notifications/MediaTypeChangedNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MediaTypeChangedNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeChangedNotification.cs index 6300361bade4..1882c7cc742c 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeChangedNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeChangedNotification.cs @@ -7,9 +7,7 @@ namespace Umbraco.Cms.Core.Notifications; public class MediaTypeChangedNotification : ContentTypeChangeNotification { public MediaTypeChangedNotification(ContentTypeChange target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } From 7fd4e26bbe23add800ebcb950bf5ec27d6709220 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:01:33 +0200 Subject: [PATCH 061/117] Update src/Umbraco.Core/Notifications/MediaTypeDeletingNotification.cs Co-authored-by: Mole --- .../Notifications/MediaTypeDeletingNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MediaTypeDeletingNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeDeletingNotification.cs index d0c7507cf7cc..a819ef0d8c02 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeDeletingNotification.cs @@ -11,9 +11,7 @@ public MediaTypeDeletingNotification(IMediaType target, EventMessages messages) } public MediaTypeDeletingNotification(IEnumerable target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } From 61396d3b24892fbf8560cbb1f2561cd28faf0a5e Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:01:53 +0200 Subject: [PATCH 062/117] Update src/Umbraco.Core/Notifications/MediaTypeMovedNotification.cs Co-authored-by: Mole --- src/Umbraco.Core/Notifications/MediaTypeMovedNotification.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MediaTypeMovedNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeMovedNotification.cs index 13b47074dd74..f05d5fd37be5 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeMovedNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeMovedNotification.cs @@ -11,8 +11,7 @@ public MediaTypeMovedNotification(MoveEventInfo target, EventMessage } public MediaTypeMovedNotification(IEnumerable> target, EventMessages messages) - : base( - target, messages) + : base(target, messages) { } } From c3eb485fdc8769ad1300af5857a764cffd498a21 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:02:12 +0200 Subject: [PATCH 063/117] Update src/Umbraco.Core/Notifications/MediaTypeMovingNotification.cs Co-authored-by: Mole --- src/Umbraco.Core/Notifications/MediaTypeMovingNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MediaTypeMovingNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeMovingNotification.cs index bfad254a07aa..d683df9f78c0 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeMovingNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeMovingNotification.cs @@ -6,9 +6,7 @@ namespace Umbraco.Cms.Core.Notifications; public class MediaTypeMovingNotification : MovingNotification { public MediaTypeMovingNotification(MoveEventInfo target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } From 9812bb3d63e3a255ad1f7ca2a8921c51de5805f2 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:02:31 +0200 Subject: [PATCH 064/117] Update src/Umbraco.Core/Notifications/MediaTypeMovingNotification.cs Co-authored-by: Mole --- src/Umbraco.Core/Notifications/MediaTypeMovingNotification.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MediaTypeMovingNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeMovingNotification.cs index d683df9f78c0..9b7ac27c13ee 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeMovingNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeMovingNotification.cs @@ -11,8 +11,7 @@ public MediaTypeMovingNotification(MoveEventInfo target, EventMessag } public MediaTypeMovingNotification(IEnumerable> target, EventMessages messages) - : base( - target, messages) + : base(target, messages) { } } From 6729dcc862bc2ef9981dde697a64938bd727e73b Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:02:50 +0200 Subject: [PATCH 065/117] Update src/Umbraco.Core/Notifications/MediaTypeRefreshedNotification.cs Co-authored-by: Mole --- .../Notifications/MediaTypeRefreshedNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MediaTypeRefreshedNotification.cs b/src/Umbraco.Core/Notifications/MediaTypeRefreshedNotification.cs index 30719c7587f3..5b6814fdb152 100644 --- a/src/Umbraco.Core/Notifications/MediaTypeRefreshedNotification.cs +++ b/src/Umbraco.Core/Notifications/MediaTypeRefreshedNotification.cs @@ -10,9 +10,7 @@ namespace Umbraco.Cms.Core.Notifications; public class MediaTypeRefreshedNotification : ContentTypeRefreshNotification { public MediaTypeRefreshedNotification(ContentTypeChange target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } From b40ba1cbfc378351d60bffecf20262cc89f6907f Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:03:08 +0200 Subject: [PATCH 066/117] Update src/Umbraco.Core/Notifications/MemberCacheRefresherNotification.cs Co-authored-by: Mole --- .../Notifications/MemberCacheRefresherNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MemberCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/MemberCacheRefresherNotification.cs index 860d0eaa55ad..46101878aab6 100644 --- a/src/Umbraco.Core/Notifications/MemberCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberCacheRefresherNotification.cs @@ -5,9 +5,7 @@ namespace Umbraco.Cms.Core.Notifications; public class MemberCacheRefresherNotification : CacheRefresherNotification { public MemberCacheRefresherNotification(object messageObject, MessageType messageType) - : base( - messageObject, - messageType) + : base(messageObject, messageType) { } } From cef658a2a0d9eaed8c7b037668e2e735afbdcf7f Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:03:30 +0200 Subject: [PATCH 067/117] Update src/Umbraco.Core/Notifications/MemberGroupCacheRefresherNotification.cs Co-authored-by: Mole --- .../Notifications/MemberGroupCacheRefresherNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MemberGroupCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/MemberGroupCacheRefresherNotification.cs index 63e234d5f2a9..333a8fbb5542 100644 --- a/src/Umbraco.Core/Notifications/MemberGroupCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberGroupCacheRefresherNotification.cs @@ -5,9 +5,7 @@ namespace Umbraco.Cms.Core.Notifications; public class MemberGroupCacheRefresherNotification : CacheRefresherNotification { public MemberGroupCacheRefresherNotification(object messageObject, MessageType messageType) - : base( - messageObject, - messageType) + : base(messageObject, messageType) { } } From ff2b8e9f119a8e91afedbb571bce681f89e88737 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:03:47 +0200 Subject: [PATCH 068/117] Update src/Umbraco.Core/Notifications/MemberGroupDeletingNotification.cs Co-authored-by: Mole --- .../Notifications/MemberGroupDeletingNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MemberGroupDeletingNotification.cs b/src/Umbraco.Core/Notifications/MemberGroupDeletingNotification.cs index ee9a1cf98b78..f0ed3dc49ca2 100644 --- a/src/Umbraco.Core/Notifications/MemberGroupDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberGroupDeletingNotification.cs @@ -11,9 +11,7 @@ public MemberGroupDeletingNotification(IMemberGroup target, EventMessages messag } public MemberGroupDeletingNotification(IEnumerable target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } From 159a3a7f0e4f6177e52eefa6bab074f9426a33cc Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:04:51 +0200 Subject: [PATCH 069/117] Update src/Umbraco.Core/Notifications/MemberGroupSavedNotification.cs Co-authored-by: Mole --- .../Notifications/MemberGroupSavedNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MemberGroupSavedNotification.cs b/src/Umbraco.Core/Notifications/MemberGroupSavedNotification.cs index ac4ef6a9405d..9f8671d923de 100644 --- a/src/Umbraco.Core/Notifications/MemberGroupSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberGroupSavedNotification.cs @@ -11,9 +11,7 @@ public MemberGroupSavedNotification(IMemberGroup target, EventMessages messages) } public MemberGroupSavedNotification(IEnumerable target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } From 0817fd793e2baffe8b3eef69f6aacf2f121ba335 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:05:10 +0200 Subject: [PATCH 070/117] Update src/Umbraco.Core/Notifications/MemberGroupSavingNotification.cs Co-authored-by: Mole --- .../Notifications/MemberGroupSavingNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MemberGroupSavingNotification.cs b/src/Umbraco.Core/Notifications/MemberGroupSavingNotification.cs index 294600d731e9..233714c54270 100644 --- a/src/Umbraco.Core/Notifications/MemberGroupSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberGroupSavingNotification.cs @@ -11,9 +11,7 @@ public MemberGroupSavingNotification(IMemberGroup target, EventMessages messages } public MemberGroupSavingNotification(IEnumerable target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } From 990cb3d3ad15e8afb76c7a2d6bc82f443329e26e Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:05:25 +0200 Subject: [PATCH 071/117] Update src/Umbraco.Core/Notifications/MemberTypeChangedNotification.cs Co-authored-by: Mole --- .../Notifications/MemberTypeChangedNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MemberTypeChangedNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeChangedNotification.cs index 0bac5d44b0da..cbce239394d9 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeChangedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeChangedNotification.cs @@ -7,9 +7,7 @@ namespace Umbraco.Cms.Core.Notifications; public class MemberTypeChangedNotification : ContentTypeChangeNotification { public MemberTypeChangedNotification(ContentTypeChange target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } From 330390a21689832a12720962465be8d6fb3169f7 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:05:48 +0200 Subject: [PATCH 072/117] Update src/Umbraco.Core/Notifications/MemberTypeDeletedNotification.cs Co-authored-by: Mole --- .../Notifications/MemberTypeDeletedNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MemberTypeDeletedNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeDeletedNotification.cs index c1560273cff4..b3061cc0744c 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeDeletedNotification.cs @@ -11,9 +11,7 @@ public MemberTypeDeletedNotification(IMemberType target, EventMessages messages) } public MemberTypeDeletedNotification(IEnumerable target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } From 78c5fd0890a5aaa9389505ecb9cecf4ce98d88c5 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:07:08 +0200 Subject: [PATCH 073/117] Update src/Umbraco.Core/Notifications/MemberTypeDeletingNotification.cs Co-authored-by: Mole --- .../Notifications/MemberTypeDeletingNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MemberTypeDeletingNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeDeletingNotification.cs index a692daab10a6..d80fcd1c1661 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeDeletingNotification.cs @@ -11,9 +11,7 @@ public MemberTypeDeletingNotification(IMemberType target, EventMessages messages } public MemberTypeDeletingNotification(IEnumerable target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } From c8ce324c4cc6a2ecfea26f96d42b1f0241adc3cc Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:07:24 +0200 Subject: [PATCH 074/117] Update src/Umbraco.Core/Notifications/MemberTypeMovedNotification.cs Co-authored-by: Mole --- src/Umbraco.Core/Notifications/MemberTypeMovedNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MemberTypeMovedNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeMovedNotification.cs index 991f95f88670..c9d73bd0ca3c 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeMovedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeMovedNotification.cs @@ -6,9 +6,7 @@ namespace Umbraco.Cms.Core.Notifications; public class MemberTypeMovedNotification : MovedNotification { public MemberTypeMovedNotification(MoveEventInfo target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } From db1e3b996310f4f2d80fceaece43d3616dafc5f4 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:07:43 +0200 Subject: [PATCH 075/117] Update src/Umbraco.Core/Notifications/MemberTypeMovedNotification.cs Co-authored-by: Mole --- src/Umbraco.Core/Notifications/MemberTypeMovedNotification.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MemberTypeMovedNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeMovedNotification.cs index c9d73bd0ca3c..5ab605612459 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeMovedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeMovedNotification.cs @@ -11,8 +11,7 @@ public MemberTypeMovedNotification(MoveEventInfo target, EventMessa } public MemberTypeMovedNotification(IEnumerable> target, EventMessages messages) - : base( - target, messages) + : base(target, messages) { } } From 3a22e8fc0136a978f826a5e209a41124fdf744d5 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:08:01 +0200 Subject: [PATCH 076/117] Update src/Umbraco.Core/Notifications/MemberTypeMovingNotification.cs Co-authored-by: Mole --- .../Notifications/MemberTypeMovingNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MemberTypeMovingNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeMovingNotification.cs index 42dd54b439d4..512a43215cc4 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeMovingNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeMovingNotification.cs @@ -6,9 +6,7 @@ namespace Umbraco.Cms.Core.Notifications; public class MemberTypeMovingNotification : MovingNotification { public MemberTypeMovingNotification(MoveEventInfo target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } From 4905375d9b718792add57b77c683b7ad42adf44a Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:08:18 +0200 Subject: [PATCH 077/117] Update src/Umbraco.Core/Notifications/MemberTypeMovingNotification.cs Co-authored-by: Mole --- src/Umbraco.Core/Notifications/MemberTypeMovingNotification.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MemberTypeMovingNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeMovingNotification.cs index 512a43215cc4..9b4445c17157 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeMovingNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeMovingNotification.cs @@ -11,8 +11,7 @@ public MemberTypeMovingNotification(MoveEventInfo target, EventMess } public MemberTypeMovingNotification(IEnumerable> target, EventMessages messages) - : base( - target, messages) + : base(target, messages) { } } From 2c090a05d0c352d941f7f9820b7379065ad9c050 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:08:40 +0200 Subject: [PATCH 078/117] Update src/Umbraco.Core/Notifications/MemberTypeRefreshedNotification.cs Co-authored-by: Mole --- .../Notifications/MemberTypeRefreshedNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MemberTypeRefreshedNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeRefreshedNotification.cs index 100fa05185ad..050c24a9e78d 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeRefreshedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeRefreshedNotification.cs @@ -10,9 +10,7 @@ namespace Umbraco.Cms.Core.Notifications; public class MemberTypeRefreshedNotification : ContentTypeRefreshNotification { public MemberTypeRefreshedNotification(ContentTypeChange target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } From ff4aca8399511744def1d5d102c5f35b79be271c Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:08:55 +0200 Subject: [PATCH 079/117] Update src/Umbraco.Core/Notifications/MemberTypeSavingNotification.cs Co-authored-by: Mole --- .../Notifications/MemberTypeSavingNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MemberTypeSavingNotification.cs b/src/Umbraco.Core/Notifications/MemberTypeSavingNotification.cs index e0c8b9f4c390..7cfcb12b91c6 100644 --- a/src/Umbraco.Core/Notifications/MemberTypeSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberTypeSavingNotification.cs @@ -11,9 +11,7 @@ public MemberTypeSavingNotification(IMemberType target, EventMessages messages) } public MemberTypeSavingNotification(IEnumerable target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } From 26836729f4817d72289d3ba8bfeb86752c9f4b81 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:09:12 +0200 Subject: [PATCH 080/117] Update src/Umbraco.Core/Extensions/StringExtensions.cs Co-authored-by: Mole --- src/Umbraco.Core/Extensions/StringExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index 3d4bb591e64d..71119593c3d0 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -1358,7 +1358,7 @@ public static string ToSafeAlias(this string alias, IShortStringHelper shortStri return a; } - return char.ToLowerInvariant(a[0]) + a[1..]; + return char.ToLowerInvariant(a[0]) + a.Substring(1); } /// From b2dea4c0fa6fb58f906d5bf09379b388796c9f3e Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:09:22 +0200 Subject: [PATCH 081/117] Update src/Umbraco.Core/Extensions/UriExtensions.cs Co-authored-by: Mole --- src/Umbraco.Core/Extensions/UriExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Extensions/UriExtensions.cs b/src/Umbraco.Core/Extensions/UriExtensions.cs index 74a8ba482c40..ac17f86fd7d7 100644 --- a/src/Umbraco.Core/Extensions/UriExtensions.cs +++ b/src/Umbraco.Core/Extensions/UriExtensions.cs @@ -81,7 +81,7 @@ public static string GetSafeAbsolutePath(this Uri uri) var posq = s.IndexOf("?", StringComparison.Ordinal); var posf = s.IndexOf("#", StringComparison.Ordinal); var pos = posq > 0 ? posq : posf > 0 ? posf : 0; - var path = pos > 0 ? s[..pos] : s; + var path = pos > 0 ? s.Substring(0, pos) : s; return path; } From 1ce4aef1a13f30b7f32ea5ad6281d9ab3aefbb3e Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:10:02 +0200 Subject: [PATCH 082/117] Update src/Umbraco.Core/Notifications/MovedToRecycleBinNotification.cs Co-authored-by: Mole --- .../Notifications/MovedToRecycleBinNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MovedToRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/MovedToRecycleBinNotification.cs index 217aea7ebb5f..18186725de05 100644 --- a/src/Umbraco.Core/Notifications/MovedToRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/MovedToRecycleBinNotification.cs @@ -8,9 +8,7 @@ namespace Umbraco.Cms.Core.Notifications; public abstract class MovedToRecycleBinNotification : ObjectNotification>> { protected MovedToRecycleBinNotification(MoveEventInfo target, EventMessages messages) - : base( - new[] { target }, - messages) + : base(new[] { target }, messages) { } From f72a01d9d9a80ffc352f758d6c5065c40381a8a6 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:10:19 +0200 Subject: [PATCH 083/117] Update src/Umbraco.Core/Notifications/MovedToRecycleBinNotification.cs Co-authored-by: Mole --- .../Notifications/MovedToRecycleBinNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MovedToRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/MovedToRecycleBinNotification.cs index 18186725de05..fddb0ab106e8 100644 --- a/src/Umbraco.Core/Notifications/MovedToRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/MovedToRecycleBinNotification.cs @@ -13,9 +13,7 @@ protected MovedToRecycleBinNotification(MoveEventInfo target, EventMessages m } protected MovedToRecycleBinNotification(IEnumerable> target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } From acdabe2e533edae4e9ac491ba3dcb40bdd6b7e50 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:11:56 +0200 Subject: [PATCH 084/117] Update src/Umbraco.Core/Notifications/MovingToRecycleBinNotification.cs Co-authored-by: Mole --- .../Notifications/MovingToRecycleBinNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MovingToRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/MovingToRecycleBinNotification.cs index 1440dd9acc2f..2da0a9d8302e 100644 --- a/src/Umbraco.Core/Notifications/MovingToRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/MovingToRecycleBinNotification.cs @@ -8,9 +8,7 @@ namespace Umbraco.Cms.Core.Notifications; public abstract class MovingToRecycleBinNotification : CancelableObjectNotification>> { protected MovingToRecycleBinNotification(MoveEventInfo target, EventMessages messages) - : base( - new[] { target }, - messages) + : base(new[] { target }, messages) { } From c0694f65976ec653102847aa232d13daf176726f Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:12:39 +0200 Subject: [PATCH 085/117] Update src/Umbraco.Core/Notifications/MovingToRecycleBinNotification.cs Co-authored-by: Mole --- .../Notifications/MovingToRecycleBinNotification.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Notifications/MovingToRecycleBinNotification.cs b/src/Umbraco.Core/Notifications/MovingToRecycleBinNotification.cs index 2da0a9d8302e..37e486e3ff65 100644 --- a/src/Umbraco.Core/Notifications/MovingToRecycleBinNotification.cs +++ b/src/Umbraco.Core/Notifications/MovingToRecycleBinNotification.cs @@ -13,8 +13,7 @@ protected MovingToRecycleBinNotification(MoveEventInfo target, EventMessages } protected MovingToRecycleBinNotification(IEnumerable> target, EventMessages messages) - : base( - target, messages) + : base(target, messages) { } From 2514d7a8670475da99e142d19a3db447473d8eff Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:13:37 +0200 Subject: [PATCH 086/117] Update src/Umbraco.Core/Notifications/PartialViewDeletingNotification.cs Co-authored-by: Mole --- .../Notifications/PartialViewDeletingNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/PartialViewDeletingNotification.cs b/src/Umbraco.Core/Notifications/PartialViewDeletingNotification.cs index ffabca9e2313..26a6fa86e07b 100644 --- a/src/Umbraco.Core/Notifications/PartialViewDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/PartialViewDeletingNotification.cs @@ -14,9 +14,7 @@ public PartialViewDeletingNotification(IPartialView target, EventMessages messag } public PartialViewDeletingNotification(IEnumerable target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } From b56e49380a8f42bba5ecc5acf2fcde8d4aa12416 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:14:02 +0200 Subject: [PATCH 087/117] Update src/Umbraco.Core/Extensions/UriExtensions.cs Co-authored-by: Mole --- src/Umbraco.Core/Extensions/UriExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Extensions/UriExtensions.cs b/src/Umbraco.Core/Extensions/UriExtensions.cs index ac17f86fd7d7..60ef7b6a7eb5 100644 --- a/src/Umbraco.Core/Extensions/UriExtensions.cs +++ b/src/Umbraco.Core/Extensions/UriExtensions.cs @@ -191,7 +191,7 @@ public static Uri WithoutPort(this Uri uri) => var s = uri.OriginalString; var posq = s.IndexOf("?", StringComparison.Ordinal); var posf = s.IndexOf("#", StringComparison.Ordinal); - var query = posq < 0 ? null : posf < 0 ? s[posq..] : s[posq..posf]; + var query = posq < 0 ? null : (posf < 0 ? s.Substring(posq) : s.Substring(posq, posf - posq)); return query; } From 4dd4a58a6637a988a24beb9e0e140cd43083c745 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:14:17 +0200 Subject: [PATCH 088/117] Update src/Umbraco.Core/IO/PhysicalFileSystem.cs Co-authored-by: Mole --- src/Umbraco.Core/IO/PhysicalFileSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index d9ff2dd44039..7d31fdb9fa23 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -289,7 +289,7 @@ public string GetRelativePath(string fullPathOrUrl) // or on unix systems "/var/wwwroot/test/Meia/1234/img.jpg" if (_ioHelper.PathStartsWith(path, _rootPathFwd, '/')) { - return path[_rootPathFwd.Length..].TrimStart(Constants.CharArrays.ForwardSlash); + return path.Substring(_rootPathFwd.Length).TrimStart(Constants.CharArrays.ForwardSlash); } // if it starts with the root URL, strip it and trim the starting slash to make it relative From 38ec95d9b1057f96c65b74fce7bef3c65be579a6 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:14:32 +0200 Subject: [PATCH 089/117] Update src/Umbraco.Core/IO/PhysicalFileSystem.cs Co-authored-by: Mole --- src/Umbraco.Core/IO/PhysicalFileSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index 7d31fdb9fa23..23c56f5f2761 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -296,7 +296,7 @@ public string GetRelativePath(string fullPathOrUrl) // eg "/Media/1234/img.jpg" => "1234/img.jpg" if (_ioHelper.PathStartsWith(path, _rootUrl, '/')) { - return path[_rootUrl.Length..].TrimStart(Constants.CharArrays.ForwardSlash); + return path.Substring(_rootUrl.Length).TrimStart(Constants.CharArrays.ForwardSlash); } // unchanged - what else? From b463e475f109773c656dfbea1114a0da67e32996 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:14:47 +0200 Subject: [PATCH 090/117] Update src/Umbraco.Core/IO/ShadowWrapper.cs Co-authored-by: Mole --- src/Umbraco.Core/IO/ShadowWrapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/IO/ShadowWrapper.cs b/src/Umbraco.Core/IO/ShadowWrapper.cs index 5833c0ab1209..2f2e0a991cb0 100644 --- a/src/Umbraco.Core/IO/ShadowWrapper.cs +++ b/src/Umbraco.Core/IO/ShadowWrapper.cs @@ -164,7 +164,7 @@ internal void UnShadow(bool complete) var pos = dir.LastIndexOf(Path.DirectorySeparatorChar); while (pos > min) { - dir = dir[..pos]; + dir = dir.Substring(0, pos); if (Directory.EnumerateFileSystemEntries(dir).Any() == false) { Directory.Delete(dir, true); From 159272d1fd76909326ce3eac07be6491c77a1166 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:15:20 +0200 Subject: [PATCH 091/117] Update src/Umbraco.Core/Logging/DisposableTimer.cs Co-authored-by: Mole --- src/Umbraco.Core/Logging/DisposableTimer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Logging/DisposableTimer.cs b/src/Umbraco.Core/Logging/DisposableTimer.cs index e97fbd34fbb8..b153e096c4b8 100644 --- a/src/Umbraco.Core/Logging/DisposableTimer.cs +++ b/src/Umbraco.Core/Logging/DisposableTimer.cs @@ -44,7 +44,7 @@ internal DisposableTimer( _endMessageArgs = endMessageArgs; _failMessageArgs = failMessageArgs; _thresholdMilliseconds = thresholdMilliseconds < 0 ? 0 : thresholdMilliseconds; - _timingId = Guid.NewGuid().ToString("N")[..7]; // keep it short-ish + _timingId = Guid.NewGuid().ToString("N").Substring(0, 7); // keep it short-ish if (thresholdMilliseconds == 0) { From 78886ba9eb48c5316643c35f288ec40dc62d0a7d Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:15:46 +0200 Subject: [PATCH 092/117] Update src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs Co-authored-by: Mole --- src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs b/src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs index 1a4ef649344d..d25a8a31dac2 100644 --- a/src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs +++ b/src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs @@ -33,7 +33,7 @@ static string FormatGuidState(string? value) } else if (Guid.TryParse(value, out Guid currentStateGuid)) { - value = currentStateGuid.ToString("N")[..8]; + value = currentStateGuid.ToString("N").Substring(0, 8); } return value; From b4c6cf52064a1d829fa12a840d1bc9afbbe738d8 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:16:08 +0200 Subject: [PATCH 093/117] Update src/Umbraco.Core/Mapping/MapDefinitionCollectionBuilder.cs Co-authored-by: Mole --- src/Umbraco.Core/Mapping/MapDefinitionCollectionBuilder.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Mapping/MapDefinitionCollectionBuilder.cs b/src/Umbraco.Core/Mapping/MapDefinitionCollectionBuilder.cs index 84df4cd45bef..1ac6de5b33e2 100644 --- a/src/Umbraco.Core/Mapping/MapDefinitionCollectionBuilder.cs +++ b/src/Umbraco.Core/Mapping/MapDefinitionCollectionBuilder.cs @@ -3,8 +3,7 @@ namespace Umbraco.Cms.Core.Mapping; -public class MapDefinitionCollectionBuilder : SetCollectionBuilderBase +public class MapDefinitionCollectionBuilder : SetCollectionBuilderBase { protected override MapDefinitionCollectionBuilder This => this; From e540c44e46e59a3f21f9f2bc7520826e14cc1e6a Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:16:29 +0200 Subject: [PATCH 094/117] Update src/Umbraco.Core/Media/EmbedProviders/EmbedProvidersCollectionBuilder.cs Co-authored-by: Mole --- .../Media/EmbedProviders/EmbedProvidersCollectionBuilder.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Media/EmbedProviders/EmbedProvidersCollectionBuilder.cs b/src/Umbraco.Core/Media/EmbedProviders/EmbedProvidersCollectionBuilder.cs index 411bf7af6f84..121785d7ebe3 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/EmbedProvidersCollectionBuilder.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/EmbedProvidersCollectionBuilder.cs @@ -2,8 +2,7 @@ namespace Umbraco.Cms.Core.Media.EmbedProviders; -public class EmbedProvidersCollectionBuilder : OrderedCollectionBuilderBase +public class EmbedProvidersCollectionBuilder : OrderedCollectionBuilderBase { protected override EmbedProvidersCollectionBuilder This => this; } From cc3506aecf8e1e25dc573721cdca869b2a3d0c03 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:16:48 +0200 Subject: [PATCH 095/117] Update src/Umbraco.Core/Notifications/PartialViewSavedNotification.cs Co-authored-by: Mole --- .../Notifications/PartialViewSavedNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/PartialViewSavedNotification.cs b/src/Umbraco.Core/Notifications/PartialViewSavedNotification.cs index 8700d59bd4e4..e7d0702e021e 100644 --- a/src/Umbraco.Core/Notifications/PartialViewSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/PartialViewSavedNotification.cs @@ -14,9 +14,7 @@ public PartialViewSavedNotification(IPartialView target, EventMessages messages) } public PartialViewSavedNotification(IEnumerable target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } From 7480738381d15750dcb319045ce40628af140fd0 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:17:26 +0200 Subject: [PATCH 096/117] Update src/Umbraco.Core/Models/ContentEditing/ContentVariationDisplay.cs Co-authored-by: Mole --- .../Models/ContentEditing/ContentVariationDisplay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Models/ContentEditing/ContentVariationDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/ContentVariationDisplay.cs index 3004e29fadc8..15b97ab24f2e 100644 --- a/src/Umbraco.Core/Models/ContentEditing/ContentVariationDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/ContentVariationDisplay.cs @@ -7,8 +7,7 @@ namespace Umbraco.Cms.Core.Models.ContentEditing; /// Represents the variant info for a content item /// [DataContract(Name = "contentVariant", Namespace = "")] -public class ContentVariantDisplay : ITabbedContent, IContentProperties, - INotificationModel +public class ContentVariantDisplay : ITabbedContent, IContentProperties, INotificationModel { public ContentVariantDisplay() { From d860a68bb080763ea864ef7634334857cce489bf Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:17:47 +0200 Subject: [PATCH 097/117] Update src/Umbraco.Core/Notifications/PartialViewSavingNotification.cs Co-authored-by: Mole --- .../Notifications/PartialViewSavingNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/PartialViewSavingNotification.cs b/src/Umbraco.Core/Notifications/PartialViewSavingNotification.cs index 9239f1ed85be..ee7401c772a5 100644 --- a/src/Umbraco.Core/Notifications/PartialViewSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/PartialViewSavingNotification.cs @@ -14,9 +14,7 @@ public PartialViewSavingNotification(IPartialView target, EventMessages messages } public PartialViewSavingNotification(IEnumerable target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } From 8a02257516160bd63206fc5c9b67b705e74320e0 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:18:13 +0200 Subject: [PATCH 098/117] Update src/Umbraco.Core/Notifications/PublicAccessCacheRefresherNotification.cs Co-authored-by: Mole --- .../Notifications/PublicAccessCacheRefresherNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/PublicAccessCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/PublicAccessCacheRefresherNotification.cs index 8dec858f8319..223cf16cc340 100644 --- a/src/Umbraco.Core/Notifications/PublicAccessCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/PublicAccessCacheRefresherNotification.cs @@ -5,9 +5,7 @@ namespace Umbraco.Cms.Core.Notifications; public class PublicAccessCacheRefresherNotification : CacheRefresherNotification { public PublicAccessCacheRefresherNotification(object messageObject, MessageType messageType) - : base( - messageObject, - messageType) + : base(messageObject, messageType) { } } From 8d23456a9580fb2ba6a52266e7ecc5a211fccdd4 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:18:33 +0200 Subject: [PATCH 099/117] Update src/Umbraco.Core/Notifications/PublicAccessEntryDeletedNotification.cs Co-authored-by: Mole --- .../Notifications/PublicAccessEntryDeletedNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/PublicAccessEntryDeletedNotification.cs b/src/Umbraco.Core/Notifications/PublicAccessEntryDeletedNotification.cs index b0e80352c6ed..2bf6062b9028 100644 --- a/src/Umbraco.Core/Notifications/PublicAccessEntryDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/PublicAccessEntryDeletedNotification.cs @@ -9,9 +9,7 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class PublicAccessEntryDeletedNotification : DeletedNotification { public PublicAccessEntryDeletedNotification(PublicAccessEntry target, EventMessages messages) - : base( - target, - messages) + : base(messageObject, messageType) { } } From 8dbc4a3bfbe6286058b303561af517b53c1296c3 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:18:56 +0200 Subject: [PATCH 100/117] Update src/Umbraco.Core/Notifications/PublicAccessEntryDeletingNotification.cs Co-authored-by: Mole --- .../Notifications/PublicAccessEntryDeletingNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/PublicAccessEntryDeletingNotification.cs b/src/Umbraco.Core/Notifications/PublicAccessEntryDeletingNotification.cs index 0d122badeb34..195aa5fb81c1 100644 --- a/src/Umbraco.Core/Notifications/PublicAccessEntryDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/PublicAccessEntryDeletingNotification.cs @@ -9,9 +9,7 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class PublicAccessEntryDeletingNotification : DeletingNotification { public PublicAccessEntryDeletingNotification(PublicAccessEntry target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } From d4f26df18bb37f51099ab0032fe80bdb96e88726 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:19:18 +0200 Subject: [PATCH 101/117] Update src/Umbraco.Core/Notifications/PublicAccessEntryDeletingNotification.cs Co-authored-by: Mole --- .../Notifications/PublicAccessEntryDeletingNotification.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Notifications/PublicAccessEntryDeletingNotification.cs b/src/Umbraco.Core/Notifications/PublicAccessEntryDeletingNotification.cs index 195aa5fb81c1..d135af805b8d 100644 --- a/src/Umbraco.Core/Notifications/PublicAccessEntryDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/PublicAccessEntryDeletingNotification.cs @@ -14,8 +14,7 @@ public PublicAccessEntryDeletingNotification(PublicAccessEntry target, EventMess } public PublicAccessEntryDeletingNotification(IEnumerable target, EventMessages messages) - : base( - target, messages) + : base(target, messages) { } } From a594b6400e31ed4444786c910e31975c76de3168 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:19:55 +0200 Subject: [PATCH 102/117] Update src/Umbraco.Core/Notifications/PublicAccessEntrySavedNotification.cs Co-authored-by: Mole --- .../Notifications/PublicAccessEntrySavedNotification.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Notifications/PublicAccessEntrySavedNotification.cs b/src/Umbraco.Core/Notifications/PublicAccessEntrySavedNotification.cs index 67c2df807c37..1f92d935d7e7 100644 --- a/src/Umbraco.Core/Notifications/PublicAccessEntrySavedNotification.cs +++ b/src/Umbraco.Core/Notifications/PublicAccessEntrySavedNotification.cs @@ -14,8 +14,7 @@ public PublicAccessEntrySavedNotification(PublicAccessEntry target, EventMessage } public PublicAccessEntrySavedNotification(IEnumerable target, EventMessages messages) - : base( - target, messages) + : base(target, messages) { } } From 9587d811cc675cece3cfabb0f0781a44f915ee90 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:20:16 +0200 Subject: [PATCH 103/117] Update src/Umbraco.Core/Notifications/PublicAccessEntrySavingNotification.cs Co-authored-by: Mole --- .../Notifications/PublicAccessEntrySavingNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/PublicAccessEntrySavingNotification.cs b/src/Umbraco.Core/Notifications/PublicAccessEntrySavingNotification.cs index c25d29e6f100..30aa856e1c40 100644 --- a/src/Umbraco.Core/Notifications/PublicAccessEntrySavingNotification.cs +++ b/src/Umbraco.Core/Notifications/PublicAccessEntrySavingNotification.cs @@ -9,9 +9,7 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class PublicAccessEntrySavingNotification : SavingNotification { public PublicAccessEntrySavingNotification(PublicAccessEntry target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } From 0173eabf6d3904a588226b893e130dffcc07099a Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:20:41 +0200 Subject: [PATCH 104/117] Update src/Umbraco.Core/Notifications/PublicAccessEntrySavingNotification.cs Co-authored-by: Mole --- .../Notifications/PublicAccessEntrySavingNotification.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Notifications/PublicAccessEntrySavingNotification.cs b/src/Umbraco.Core/Notifications/PublicAccessEntrySavingNotification.cs index 30aa856e1c40..9f9e6f8a4a70 100644 --- a/src/Umbraco.Core/Notifications/PublicAccessEntrySavingNotification.cs +++ b/src/Umbraco.Core/Notifications/PublicAccessEntrySavingNotification.cs @@ -14,8 +14,7 @@ public PublicAccessEntrySavingNotification(PublicAccessEntry target, EventMessag } public PublicAccessEntrySavingNotification(IEnumerable target, EventMessages messages) - : base( - target, messages) + : base(target, messages) { } } From 8ed94602112a2931f4c2cefa067fa241f4d070d3 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:21:03 +0200 Subject: [PATCH 105/117] Update src/Umbraco.Core/Notifications/RelationTypeCacheRefresherNotification.cs Co-authored-by: Mole --- .../Notifications/RelationTypeCacheRefresherNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/RelationTypeCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/RelationTypeCacheRefresherNotification.cs index 887840d9fce7..1d816a40679e 100644 --- a/src/Umbraco.Core/Notifications/RelationTypeCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationTypeCacheRefresherNotification.cs @@ -5,9 +5,7 @@ namespace Umbraco.Cms.Core.Notifications; public class RelationTypeCacheRefresherNotification : CacheRefresherNotification { public RelationTypeCacheRefresherNotification(object messageObject, MessageType messageType) - : base( - messageObject, - messageType) + : base(messageObject, messageType) { } } From 1414932db30a064e176f2b9098eb6006791abade Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:21:29 +0200 Subject: [PATCH 106/117] Update src/Umbraco.Core/Notifications/RelationTypeDeletingNotification.cs Co-authored-by: Mole --- .../Notifications/RelationTypeDeletingNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/RelationTypeDeletingNotification.cs b/src/Umbraco.Core/Notifications/RelationTypeDeletingNotification.cs index 8ca3698682f5..d9ba61b2b562 100644 --- a/src/Umbraco.Core/Notifications/RelationTypeDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationTypeDeletingNotification.cs @@ -14,9 +14,7 @@ public RelationTypeDeletingNotification(IRelationType target, EventMessages mess } public RelationTypeDeletingNotification(IEnumerable target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } From cbe4c45f86a3067b8b6b53e0948f11275d75566d Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:29:18 +0200 Subject: [PATCH 107/117] Update src/Umbraco.Core/Notifications/RelationTypeSavedNotification.cs Co-authored-by: Mole --- .../Notifications/RelationTypeSavedNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/RelationTypeSavedNotification.cs b/src/Umbraco.Core/Notifications/RelationTypeSavedNotification.cs index 519ca33ce9ac..d0a1aaf16e7e 100644 --- a/src/Umbraco.Core/Notifications/RelationTypeSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationTypeSavedNotification.cs @@ -14,9 +14,7 @@ public RelationTypeSavedNotification(IRelationType target, EventMessages message } public RelationTypeSavedNotification(IEnumerable target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } From 3c02be51ac0fa0cc1a452166951f13b6be709f3d Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:29:38 +0200 Subject: [PATCH 108/117] Update src/Umbraco.Core/Notifications/RelationTypeSavingNotification.cs Co-authored-by: Mole --- .../Notifications/RelationTypeSavingNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/RelationTypeSavingNotification.cs b/src/Umbraco.Core/Notifications/RelationTypeSavingNotification.cs index cb12d15b11e5..e2f7979e869a 100644 --- a/src/Umbraco.Core/Notifications/RelationTypeSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/RelationTypeSavingNotification.cs @@ -14,9 +14,7 @@ public RelationTypeSavingNotification(IRelationType target, EventMessages messag } public RelationTypeSavingNotification(IEnumerable target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } From 650aeb04cbf89728f853ea74ae90586503221f3c Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:30:01 +0200 Subject: [PATCH 109/117] Update src/Umbraco.Core/Notifications/StatefulNotification.cs Co-authored-by: Mole --- src/Umbraco.Core/Notifications/StatefulNotification.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Notifications/StatefulNotification.cs b/src/Umbraco.Core/Notifications/StatefulNotification.cs index 8306d976fb97..5f84000d48af 100644 --- a/src/Umbraco.Core/Notifications/StatefulNotification.cs +++ b/src/Umbraco.Core/Notifications/StatefulNotification.cs @@ -8,8 +8,7 @@ public abstract class StatefulNotification : IStatefulNotification /// /// This can be used by event subscribers to store state in the notification so they easily deal with custom state data - /// between - /// a starting ("ing") and an ending ("ed") notification + /// between a starting ("ing") and an ending ("ed") notification /// public IDictionary State { From 92e196c018e3433a54ace095ac02cfaf4761d795 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:30:24 +0200 Subject: [PATCH 110/117] Update src/Umbraco.Core/Notifications/StylesheetDeletingNotification.cs Co-authored-by: Mole --- .../Notifications/StylesheetDeletingNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/StylesheetDeletingNotification.cs b/src/Umbraco.Core/Notifications/StylesheetDeletingNotification.cs index e4ea920aa662..868936357782 100644 --- a/src/Umbraco.Core/Notifications/StylesheetDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/StylesheetDeletingNotification.cs @@ -14,9 +14,7 @@ public StylesheetDeletingNotification(IStylesheet target, EventMessages messages } public StylesheetDeletingNotification(IEnumerable target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } From 78f831b7fe28ae5ed808d07e9e0083dbf3f28a73 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 10:30:56 +0200 Subject: [PATCH 111/117] Update src/Umbraco.Core/Notifications/StylesheetSavingNotification.cs Co-authored-by: Mole --- .../Notifications/StylesheetSavingNotification.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Notifications/StylesheetSavingNotification.cs b/src/Umbraco.Core/Notifications/StylesheetSavingNotification.cs index dbbf263ec419..0d6804a76c0c 100644 --- a/src/Umbraco.Core/Notifications/StylesheetSavingNotification.cs +++ b/src/Umbraco.Core/Notifications/StylesheetSavingNotification.cs @@ -14,9 +14,7 @@ public StylesheetSavingNotification(IStylesheet target, EventMessages messages) } public StylesheetSavingNotification(IEnumerable target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } From c64688a22e3abd2d9e0836fbcd51d3d1164e8e82 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 11:14:00 +0200 Subject: [PATCH 112/117] Update src/Umbraco.Core/Models/DeepCloneHelper.cs Co-authored-by: Mole --- src/Umbraco.Core/Models/DeepCloneHelper.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Models/DeepCloneHelper.cs b/src/Umbraco.Core/Models/DeepCloneHelper.cs index 840351fed486..ce34dab6f12b 100644 --- a/src/Umbraco.Core/Models/DeepCloneHelper.cs +++ b/src/Umbraco.Core/Models/DeepCloneHelper.cs @@ -63,8 +63,7 @@ public static void DeepCloneRefProperties(IDeepCloneable input, IDeepCloneable o && (propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>) || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>) || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>) - || propertyInfo.PropertyType.GetGenericTypeDefinition() == - typeof(IReadOnlyCollection<>))) + || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IReadOnlyCollection<>))) { // if it is a IEnumerable<>, IReadOnlyCollection, IList or ICollection<> we'll use a List<> since it implements them all Type genericType = From 85da9b9e07169df9841c4b5f8ab87e6d7c5d793f Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 11:14:27 +0200 Subject: [PATCH 113/117] Update src/Umbraco.Core/Models/File.cs Co-authored-by: Mole --- src/Umbraco.Core/Models/File.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index e77dc4e07aff..8abfdd1ef559 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -53,7 +53,7 @@ public virtual string Alias } var lastIndexOf = name.LastIndexOf(".", StringComparison.InvariantCultureIgnoreCase); - _alias = name[..lastIndexOf]; + _alias = name.Substring(0, lastIndexOf); } return _alias; From dfc23be830089e6dafb54a64f3251733b2f03707 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 11:14:49 +0200 Subject: [PATCH 114/117] Update src/Umbraco.Core/Models/PropertyGroupExtensions.cs Co-authored-by: Mole --- src/Umbraco.Core/Models/PropertyGroupExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Models/PropertyGroupExtensions.cs b/src/Umbraco.Core/Models/PropertyGroupExtensions.cs index 289a411f14d8..24da4c7c7a14 100644 --- a/src/Umbraco.Core/Models/PropertyGroupExtensions.cs +++ b/src/Umbraco.Core/Models/PropertyGroupExtensions.cs @@ -18,7 +18,7 @@ public static class PropertyGroupExtensions var lastIndex = alias?.LastIndexOf(AliasSeparator) ?? -1; if (lastIndex != -1) { - return alias?[(lastIndex + 1)..]; + return alias?.Substring(lastIndex + 1); } return alias; From 7531c3c67a775e2436571a84c5508f0c3142f09e Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 7 Jun 2022 11:15:06 +0200 Subject: [PATCH 115/117] Update src/Umbraco.Core/Models/PropertyGroupExtensions.cs Co-authored-by: Mole --- src/Umbraco.Core/Models/PropertyGroupExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Models/PropertyGroupExtensions.cs b/src/Umbraco.Core/Models/PropertyGroupExtensions.cs index 24da4c7c7a14..95f3bce75bc2 100644 --- a/src/Umbraco.Core/Models/PropertyGroupExtensions.cs +++ b/src/Umbraco.Core/Models/PropertyGroupExtensions.cs @@ -32,7 +32,7 @@ public static class PropertyGroupExtensions return null; } - return alias?[..lastIndex]; + return alias?.Substring(0, lastIndex); } /// From c4214ac495d0dac4efd67cafe8ccfcfa7738bab9 Mon Sep 17 00:00:00 2001 From: Zeegaan Date: Tue, 7 Jun 2022 14:48:18 +0200 Subject: [PATCH 116/117] Fix based on review --- .../Cache/MemberCacheRefresher.cs | 8 - .../Cache/TemplateCacheRefresher.cs | 8 - .../Snippets/MultinodeTree-picker.cshtml | 2 + .../Extensions/HostEnvironmentExtensions.cs | 8 +- .../Extensions/StringExtensions.cs | 2 +- .../Checks/Security/HttpsCheck.cs | 6 +- src/Umbraco.Core/IO/FileSystems.cs | 619 +++--- src/Umbraco.Core/IO/PhysicalFileSystem.cs | 810 +++---- .../Install/InstallSteps/UpgradeStep.cs | 85 +- .../Media/EmbedProviders/DailyMotion.cs | 4 +- .../Media/EmbedProviders/Flickr.cs | 4 +- .../Media/EmbedProviders/GettyImages.cs | 4 +- .../Media/EmbedProviders/Giphy.cs | 4 +- src/Umbraco.Core/Media/EmbedProviders/Hulu.cs | 4 +- .../Media/EmbedProviders/Issuu.cs | 4 +- .../Media/EmbedProviders/Kickstarter.cs | 4 +- .../Media/EmbedProviders/Slideshare.cs | 4 +- .../Media/EmbedProviders/SoundCloud.cs | 4 +- src/Umbraco.Core/Media/EmbedProviders/Ted.cs | 4 +- .../Media/EmbedProviders/Twitter.cs | 4 +- .../Media/EmbedProviders/Vimeo.cs | 4 +- .../Media/EmbedProviders/Youtube.cs | 4 +- .../Models/ContentBaseExtensions.cs | 8 +- .../PublishedContent/IPublishedContent.cs | 8 - .../Models/PublishedContent/ModelType.cs | 3 +- .../PublishedContent/PublishedContentBase.cs | 134 +- .../PublishedContent/PublishedContentType.cs | 360 ++- .../PublishedContentWrapped.cs | 16 - .../PublishedContent/PublishedPropertyType.cs | 491 ++--- .../PublicAccessEntryDeletedNotification.cs | 2 +- .../TemplateCacheRefresherNotification.cs | 6 +- .../UserCacheRefresherNotification.cs | 6 +- .../UserGroupCacheRefresherNotification.cs | 6 +- .../UserGroupDeletingNotification.cs | 4 +- .../UserGroupWithUsersSavedNotification.cs | 7 +- .../Notifications/UserLockedNotification.cs | 5 +- .../UserLoginSuccessNotification.cs | 3 +- .../UserLogoutSuccessNotification.cs | 3 +- .../UserPasswordResetNotification.cs | 3 +- .../Notifications/UserUnlockedNotification.cs | 5 +- .../Packaging/PackageDefinitionXmlParser.cs | 2 +- .../Repositories/IRelationRepository.cs | 3 +- .../DataEditorCollectionBuilder.cs | 5 +- ...aValueReferenceFactoryCollectionBuilder.cs | 5 +- ...yeDropperColorPickerConfigurationEditor.cs | 5 +- .../LabelConfigurationEditor.cs | 5 +- .../ListViewConfigurationEditor.cs | 5 +- ...ManifestValueValidatorCollectionBuilder.cs | 3 +- .../MarkdownConfigurationEditor.cs | 5 +- .../MediaUrlGeneratorCollectionBuilder.cs | 3 +- ...PropertyValueConverterCollectionBuilder.cs | 5 +- .../RichTextConfigurationEditor.cs | 5 +- .../TextAreaConfigurationEditor.cs | 5 +- .../TextboxConfigurationEditor.cs | 5 +- .../ValueConverters/IntegerValueConverter.cs | 5 +- .../MultipleTextStringValueConverter.cs | 4 +- .../PublishedCache/PublishedElement.cs | 11 - src/Umbraco.Core/ReflectionUtilities.cs | 3 +- .../Routing/ContentFinderByIdPath.cs | 2 +- .../Routing/ContentFinderByUrlAndTemplate.cs | 4 +- .../Routing/ContentFinderCollectionBuilder.cs | 5 +- .../Routing/DefaultUrlProvider.cs | 2 +- src/Umbraco.Core/Routing/DomainUtilities.cs | 717 +++--- src/Umbraco.Core/Routing/IPublishedRouter.cs | 3 +- .../MediaUrlProviderCollectionBuilder.cs | 5 +- src/Umbraco.Core/Routing/PublishedRouter.cs | 4 +- src/Umbraco.Core/Routing/SiteDomainMapper.cs | 641 +++--- src/Umbraco.Core/Routing/UrlProvider.cs | 425 ++-- .../Routing/UrlProviderCollectionBuilder.cs | 5 +- src/Umbraco.Core/Runtime/MainDom.cs | 439 ++-- .../Security/LegacyPasswordSecurity.cs | 4 +- src/Umbraco.Core/Services/DataTypeService.cs | 865 ++++---- .../Services/IPackagingService.cs | 3 +- src/Umbraco.Core/Services/ITagService.cs | 5 +- .../LocalizedTextServiceExtensions.cs | 2 +- .../LocalizedTextServiceFileSources.cs | 2 +- src/Umbraco.Core/Services/MediaService.cs | 1944 ++++++++--------- src/Umbraco.Core/Services/MemberService.cs | 1453 +++++------- .../Strings/DefaultShortStringHelper.cs | 1077 +++++---- .../UrlSegmentProviderCollectionBuilder.cs | 5 +- src/Umbraco.Core/SystemLock.cs | 12 +- src/Umbraco.Core/UriUtilityCore.cs | 8 +- src/Umbraco.Core/Web/IUmbracoContext.cs | 3 +- .../Web/IUmbracoContextAccessor.cs | 3 +- ...CustomBackOfficeAssetsCollectionBuilder.cs | 3 +- src/Umbraco.Core/Xml/DynamicContext.cs | 509 ++--- src/Umbraco.Core/Xml/XPath/MacroNavigator.cs | 1788 ++++++++------- 87 files changed, 5998 insertions(+), 6694 deletions(-) diff --git a/src/Umbraco.Core/Cache/MemberCacheRefresher.cs b/src/Umbraco.Core/Cache/MemberCacheRefresher.cs index d16e41209438..ac9dac5a09d1 100644 --- a/src/Umbraco.Core/Cache/MemberCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/MemberCacheRefresher.cs @@ -12,8 +12,6 @@ namespace Umbraco.Cms.Core.Cache; public sealed class MemberCacheRefresher : PayloadCacheRefresherBase { - #region Define - public static readonly Guid UniqueId = Guid.Parse("E285DF34-ACDC-4226-AE32-C0CB5CF388DA"); private readonly IIdKeyMap _idKeyMap; @@ -49,10 +47,6 @@ public JsonPayload(int id, string? username, bool removed) public override string Name => "Member Cache Refresher"; - #endregion - - #region Refresher - public override void Refresh(JsonPayload[] payloads) { ClearCache(payloads); @@ -86,6 +80,4 @@ private void ClearCache(params JsonPayload[] payloads) } } } - - #endregion } diff --git a/src/Umbraco.Core/Cache/TemplateCacheRefresher.cs b/src/Umbraco.Core/Cache/TemplateCacheRefresher.cs index 451ef77096a9..221ad7c8363a 100644 --- a/src/Umbraco.Core/Cache/TemplateCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/TemplateCacheRefresher.cs @@ -8,8 +8,6 @@ namespace Umbraco.Cms.Core.Cache; public sealed class TemplateCacheRefresher : CacheRefresherBase { - #region Define - public static readonly Guid UniqueId = Guid.Parse("DD12B6A0-14B9-46e8-8800-C154F74047C8"); private readonly IContentTypeCommonRepository _contentTypeCommonRepository; @@ -31,10 +29,6 @@ public TemplateCacheRefresher( public override string Name => "Template Cache Refresher"; - #endregion - - #region Refresher - public override void Refresh(int id) { RemoveFromCache(id); @@ -64,6 +58,4 @@ private void RemoveFromCache(int id) // need to clear the runtime cache for templates ClearAllIsolatedCacheByEntityType(); } - - #endregion } diff --git a/src/Umbraco.Core/EmbeddedResources/Snippets/MultinodeTree-picker.cshtml b/src/Umbraco.Core/EmbeddedResources/Snippets/MultinodeTree-picker.cshtml index 6edf84dbc72c..e89f9e7076d8 100644 --- a/src/Umbraco.Core/EmbeddedResources/Snippets/MultinodeTree-picker.cshtml +++ b/src/Umbraco.Core/EmbeddedResources/Snippets/MultinodeTree-picker.cshtml @@ -1,4 +1,6 @@ @using Umbraco.Cms.Core.Models.PublishedContent +@using Umbraco.Cms.Core.Routing +@using Umbraco.Extensions @inherits Umbraco.Cms.Web.Common.Macros.PartialViewMacroPage @inject IPublishedValueFallback PublishedValueFallback @inject IPublishedUrlProvider PublishedUrlProvider diff --git a/src/Umbraco.Core/Extensions/HostEnvironmentExtensions.cs b/src/Umbraco.Core/Extensions/HostEnvironmentExtensions.cs index a1135164de0e..f1b19569ff93 100644 --- a/src/Umbraco.Core/Extensions/HostEnvironmentExtensions.cs +++ b/src/Umbraco.Core/Extensions/HostEnvironmentExtensions.cs @@ -8,7 +8,7 @@ namespace Umbraco.Cms.Core.Extensions; /// public static class HostEnvironmentExtensions { - private static string? temporaryApplicationId; + private static string? _temporaryApplicationId; /// /// Maps a virtual path to a physical path to the application's content root. @@ -41,11 +41,11 @@ public static string MapPathContentRoot(this IHostEnvironment hostEnvironment, s /// public static string GetTemporaryApplicationId(this IHostEnvironment hostEnvironment) { - if (temporaryApplicationId != null) + if (_temporaryApplicationId != null) { - return temporaryApplicationId; + return _temporaryApplicationId; } - return temporaryApplicationId = hostEnvironment.ContentRootPath.GenerateHash(); + return _temporaryApplicationId = hostEnvironment.ContentRootPath.GenerateHash(); } } diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index 71119593c3d0..694b4d05e669 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -1046,7 +1046,7 @@ public static string ReplaceFirst(this string text, string search, string replac return text; } - text.Substring(0, pos) + replace + text.Substring(pos + search.Length); + return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); } /// diff --git a/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs b/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs index c9d1a69aec27..dbff50c480b6 100644 --- a/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs +++ b/src/Umbraco.Core/HealthChecks/Checks/Security/HttpsCheck.cs @@ -25,7 +25,7 @@ public class HttpsCheck : HealthCheck private const int NumberOfDaysForExpiryWarning = 14; private const string HttpPropertyKeyCertificateDaysToExpiry = "CertificateDaysToExpiry"; - private static HttpClient? httpClient; + private static HttpClient? _httpClient; private readonly IOptionsMonitor _globalSettings; private readonly IHostingEnvironment _hostingEnvironment; @@ -47,7 +47,7 @@ public HttpsCheck( _hostingEnvironment = hostingEnvironment; } - private static HttpClient HttpClient => httpClient ??= new HttpClient(new HttpClientHandler + private static HttpClient _httpClientEnsureInitialized => _httpClient ??= new HttpClient(new HttpClientHandler { ServerCertificateCustomValidationCallback = ServerCertificateCustomValidation, }); @@ -92,7 +92,7 @@ private async Task CheckForValidCertificate() try { - using HttpResponseMessage response = await HttpClient.SendAsync(request); + using HttpResponseMessage response = await _httpClientEnsureInitialized.SendAsync(request); if (response.StatusCode == HttpStatusCode.OK) { diff --git a/src/Umbraco.Core/IO/FileSystems.cs b/src/Umbraco.Core/IO/FileSystems.cs index c1bf9f183fd9..2a5fa685df02 100644 --- a/src/Umbraco.Core/IO/FileSystems.cs +++ b/src/Umbraco.Core/IO/FileSystems.cs @@ -4,416 +4,359 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Hosting; -namespace Umbraco.Cms.Core.IO; - -/// -/// Provides the system filesystems. -/// -public sealed class FileSystems +namespace Umbraco.Cms.Core.IO { - private static string? _shadowCurrentId; // static - unique!! - private readonly IHostingEnvironment _hostingEnvironment; - private readonly IIOHelper _ioHelper; - private readonly ILogger _logger; - private readonly ILoggerFactory _loggerFactory; - private readonly object _shadowLocker = new(); - - // shadow support - private readonly List _shadowWrappers = new(); - private readonly GlobalSettings _globalSettings; - - // wrappers for shadow support - private ShadowWrapper? _macroPartialFileSystem; - private ShadowWrapper? _mvcViewsFileSystem; - private ShadowWrapper? _partialViewsFileSystem; - private ShadowWrapper? _scriptsFileSystem; - private ShadowWrapper? _stylesheetsFileSystem; - private bool _wkfsInitialized; - - // well-known file systems lazy initialization - private object _wkfsLock = new(); - private object? _wkfsObject; // unused - - #region Constructor - - // DI wants a public ctor - public FileSystems( - ILoggerFactory loggerFactory, - IIOHelper ioHelper, - IOptions globalSettings, - IHostingEnvironment hostingEnvironment) + /// + /// Provides the system filesystems. + /// + public sealed class FileSystems { - _logger = loggerFactory.CreateLogger(); - _loggerFactory = loggerFactory; - _ioHelper = ioHelper; - _globalSettings = globalSettings.Value; - _hostingEnvironment = hostingEnvironment; - } + private readonly ILogger _logger; + private readonly ILoggerFactory _loggerFactory; + private readonly IIOHelper _ioHelper; + private GlobalSettings _globalSettings; + private readonly IHostingEnvironment _hostingEnvironment; + + + // wrappers for shadow support + private ShadowWrapper? _macroPartialFileSystem; + private ShadowWrapper? _partialViewsFileSystem; + private ShadowWrapper? _stylesheetsFileSystem; + private ShadowWrapper? _scriptsFileSystem; + private ShadowWrapper? _mvcViewsFileSystem; + + // well-known file systems lazy initialization + private object _wkfsLock = new(); + private bool _wkfsInitialized; + private object? _wkfsObject; // unused + + // shadow support + private readonly List _shadowWrappers = new(); + private readonly object _shadowLocker = new(); + private static string? _shadowCurrentId; // static - unique!! + #region Constructor + + // DI wants a public ctor + public FileSystems( + ILoggerFactory loggerFactory, + IIOHelper ioHelper, + IOptions globalSettings, + IHostingEnvironment hostingEnvironment) + { + _logger = loggerFactory.CreateLogger(); + _loggerFactory = loggerFactory; + _ioHelper = ioHelper; + _globalSettings = globalSettings.Value; + _hostingEnvironment = hostingEnvironment; + } - // Ctor for tests, allows you to set the various filesystems - internal FileSystems( - ILoggerFactory loggerFactory, - IIOHelper ioHelper, - IOptions globalSettings, - IHostingEnvironment hostingEnvironment, - IFileSystem macroPartialFileSystem, - IFileSystem partialViewsFileSystem, - IFileSystem stylesheetFileSystem, - IFileSystem scriptsFileSystem, - IFileSystem mvcViewFileSystem) - : this(loggerFactory, ioHelper, globalSettings, hostingEnvironment) - { - _macroPartialFileSystem = CreateShadowWrapperInternal(macroPartialFileSystem, "macro-partials"); - _partialViewsFileSystem = CreateShadowWrapperInternal(partialViewsFileSystem, "partials"); - _stylesheetsFileSystem = CreateShadowWrapperInternal(stylesheetFileSystem, "css"); - _scriptsFileSystem = CreateShadowWrapperInternal(scriptsFileSystem, "scripts"); - _mvcViewsFileSystem = CreateShadowWrapperInternal(mvcViewFileSystem, "view"); - - // Set initialized to true so the filesystems doesn't get overwritten. - _wkfsInitialized = true; - } + // Ctor for tests, allows you to set the various filesystems + internal FileSystems( + ILoggerFactory loggerFactory, + IIOHelper ioHelper, + IOptions globalSettings, + IHostingEnvironment hostingEnvironment, + IFileSystem macroPartialFileSystem, + IFileSystem partialViewsFileSystem, + IFileSystem stylesheetFileSystem, + IFileSystem scriptsFileSystem, + IFileSystem mvcViewFileSystem) : this(loggerFactory, ioHelper, globalSettings, hostingEnvironment) + { + _macroPartialFileSystem = CreateShadowWrapperInternal(macroPartialFileSystem, "macro-partials"); + _partialViewsFileSystem = CreateShadowWrapperInternal(partialViewsFileSystem, "partials"); + _stylesheetsFileSystem = CreateShadowWrapperInternal(stylesheetFileSystem, "css"); + _scriptsFileSystem = CreateShadowWrapperInternal(scriptsFileSystem, "scripts"); + _mvcViewsFileSystem = CreateShadowWrapperInternal(mvcViewFileSystem, "view"); + // Set initialized to true so the filesystems doesn't get overwritten. + _wkfsInitialized = true; - /// - /// Used be Scope provider to take control over the filesystems, should never be used for anything else. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public Func? IsScoped { get; set; } = () => false; + } - #endregion + /// + /// Used be Scope provider to take control over the filesystems, should never be used for anything else. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public Func? IsScoped { get; set; } = () => false; - #region Well-Known FileSystems + #endregion - /// - /// Gets the macro partials filesystem. - /// - public IFileSystem? MacroPartialsFileSystem - { - get + #region Well-Known FileSystems + + /// + /// Gets the macro partials filesystem. + /// + public IFileSystem? MacroPartialsFileSystem { - if (Volatile.Read(ref _wkfsInitialized) == false) + get { - EnsureWellKnownFileSystems(); - } + if (Volatile.Read(ref _wkfsInitialized) == false) + { + EnsureWellKnownFileSystems(); + } - return _macroPartialFileSystem; + return _macroPartialFileSystem; + } } - } - /// - /// Gets the partial views filesystem. - /// - public IFileSystem? PartialViewsFileSystem - { - get + /// + /// Gets the partial views filesystem. + /// + public IFileSystem? PartialViewsFileSystem { - if (Volatile.Read(ref _wkfsInitialized) == false) + get { - EnsureWellKnownFileSystems(); - } + if (Volatile.Read(ref _wkfsInitialized) == false) + { + EnsureWellKnownFileSystems(); + } - return _partialViewsFileSystem; + return _partialViewsFileSystem; + } } - } - /// - /// Gets the stylesheets filesystem. - /// - public IFileSystem? StylesheetsFileSystem - { - get + /// + /// Gets the stylesheets filesystem. + /// + public IFileSystem? StylesheetsFileSystem { - if (Volatile.Read(ref _wkfsInitialized) == false) + get { - EnsureWellKnownFileSystems(); - } + if (Volatile.Read(ref _wkfsInitialized) == false) + { + EnsureWellKnownFileSystems(); + } - return _stylesheetsFileSystem; + return _stylesheetsFileSystem; + } } - } - /// - /// Gets the scripts filesystem. - /// - public IFileSystem? ScriptsFileSystem - { - get + /// + /// Gets the scripts filesystem. + /// + public IFileSystem? ScriptsFileSystem { - if (Volatile.Read(ref _wkfsInitialized) == false) + get { - EnsureWellKnownFileSystems(); - } + if (Volatile.Read(ref _wkfsInitialized) == false) + { + EnsureWellKnownFileSystems(); + } - return _scriptsFileSystem; + return _scriptsFileSystem; + } } - } - /// - /// Gets the MVC views filesystem. - /// - public IFileSystem? MvcViewsFileSystem - { - get + /// + /// Gets the MVC views filesystem. + /// + public IFileSystem? MvcViewsFileSystem { - if (Volatile.Read(ref _wkfsInitialized) == false) + get { - EnsureWellKnownFileSystems(); - } + if (Volatile.Read(ref _wkfsInitialized) == false) + { + EnsureWellKnownFileSystems(); + } - return _mvcViewsFileSystem; + return _mvcViewsFileSystem; + } } - } - /// - /// Sets the stylesheet filesystem. - /// - /// - /// Be careful when using this, the root path and root url must be correct for this to work. - /// - /// The . - /// If the is null - /// Throws exception if the StylesheetFileSystem has already been initialized. - /// Throws exception if full path can't be resolved successfully. - public void SetStylesheetFilesystem(IFileSystem fileSystem) - { - if (fileSystem == null) + /// + /// Sets the stylesheet filesystem. + /// + /// + /// Be careful when using this, the root path and root url must be correct for this to work. + /// + /// The . + /// If the is null + /// Throws exception if the StylesheetFileSystem has already been initialized. + /// Throws exception if full path can't be resolved successfully. + public void SetStylesheetFilesystem(IFileSystem fileSystem) { - throw new ArgumentNullException(nameof(fileSystem)); - } + if (fileSystem == null) + { + throw new ArgumentNullException(nameof(fileSystem)); + } - if (_stylesheetsFileSystem != null) - { - throw new InvalidOperationException( - "The StylesheetFileSystem cannot be changed when it's already been initialized."); - } + if (_stylesheetsFileSystem != null) + { + throw new InvalidOperationException( + "The StylesheetFileSystem cannot be changed when it's already been initialized."); + } - // Verify that _rootUrl/_rootPath is correct - // We have to do this because there's a tight coupling - // to the VirtualPath we get with CodeFileDisplay from the frontend. - try - { - var rootPath = fileSystem.GetFullPath("/css/"); + // Verify that _rootUrl/_rootPath is correct + // We have to do this because there's a tight coupling + // to the VirtualPath we get with CodeFileDisplay from the frontend. + try + { + fileSystem.GetFullPath("/css/"); + } + catch (UnauthorizedAccessException exception) + { + throw new UnauthorizedAccessException( + "Can't register the stylesheet filesystem, " + + "this is most likely caused by using a PhysicalFileSystem with an incorrect " + + "rootPath/rootUrl. RootPath must be \\wwwroot\\css" + + " and rootUrl must be /css", + exception); + } + + _stylesheetsFileSystem = CreateShadowWrapperInternal(fileSystem, "css"); } - catch (UnauthorizedAccessException exception) + + private void EnsureWellKnownFileSystems() => LazyInitializer.EnsureInitialized(ref _wkfsObject, ref _wkfsInitialized, ref _wkfsLock, CreateWellKnownFileSystems); + + // need to return something to LazyInitializer.EnsureInitialized + // but it does not really matter what we return - here, null + private object? CreateWellKnownFileSystems() { - throw new UnauthorizedAccessException( - "Can't register the stylesheet filesystem, " - + "this is most likely caused by using a PhysicalFileSystem with an incorrect " - + "rootPath/rootUrl. RootPath must be \\wwwroot\\css" - + " and rootUrl must be /css", - exception); - } + ILogger logger = _loggerFactory.CreateLogger(); - _stylesheetsFileSystem = CreateShadowWrapperInternal(fileSystem, "css"); - } + //TODO this is fucked, why do PhysicalFileSystem has a root url? Mvc views cannot be accessed by url! + var macroPartialFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MacroPartials), _hostingEnvironment.ToAbsolute(Constants.SystemDirectories.MacroPartials)); + var partialViewsFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.PartialViews), _hostingEnvironment.ToAbsolute(Constants.SystemDirectories.PartialViews)); + var scriptsFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, _hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoScriptsPath), _hostingEnvironment.ToAbsolute(_globalSettings.UmbracoScriptsPath)); + var mvcViewsFileSystem = new PhysicalFileSystem(_ioHelper, _hostingEnvironment, logger, _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MvcViews), _hostingEnvironment.ToAbsolute(Constants.SystemDirectories.MvcViews)); - #endregion + _macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, _ioHelper, _hostingEnvironment, _loggerFactory, "macro-partials", IsScoped); + _partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, _ioHelper, _hostingEnvironment, _loggerFactory, "partials", IsScoped); + _scriptsFileSystem = new ShadowWrapper(scriptsFileSystem, _ioHelper, _hostingEnvironment, _loggerFactory, "scripts", IsScoped); + _mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, _ioHelper, _hostingEnvironment, _loggerFactory, "views", IsScoped); - #region Shadow + if (_stylesheetsFileSystem == null) + { + var stylesheetsFileSystem = new PhysicalFileSystem( + _ioHelper, + _hostingEnvironment, + logger, + _hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoCssPath), + _hostingEnvironment.ToAbsolute(_globalSettings.UmbracoCssPath)); - // note - // shadowing is thread-safe, but entering and exiting shadow mode is not, and there is only one - // global shadow for the entire application, so great care should be taken to ensure that the - // application is *not* doing anything else when using a shadow. + _stylesheetsFileSystem = new ShadowWrapper(stylesheetsFileSystem, _ioHelper, _hostingEnvironment, _loggerFactory, "css", IsScoped); - /// - /// Shadows the filesystem, should never be used outside the Scope class. - /// - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public ICompletable Shadow() - { - if (Volatile.Read(ref _wkfsInitialized) == false) - { - EnsureWellKnownFileSystems(); - } + _shadowWrappers.Add(_stylesheetsFileSystem); + } - var id = ShadowWrapper.CreateShadowId(_hostingEnvironment); - return new ShadowFileSystems(this, id); // will invoke BeginShadow and EndShadow - } + // TODO: do we need a lock here? + _shadowWrappers.Add(_macroPartialFileSystem); + _shadowWrappers.Add(_partialViewsFileSystem); + _shadowWrappers.Add(_scriptsFileSystem); + _shadowWrappers.Add(_mvcViewsFileSystem); - /// - /// Creates a shadow wrapper for a filesystem, should never be used outside UmbracoBuilder or testing - /// - /// - /// - /// - [EditorBrowsable(EditorBrowsableState.Never)] - public IFileSystem CreateShadowWrapper(IFileSystem filesystem, string shadowPath) => - CreateShadowWrapperInternal(filesystem, shadowPath); - - private void EnsureWellKnownFileSystems() => LazyInitializer.EnsureInitialized( - ref _wkfsObject, - ref _wkfsInitialized, - ref _wkfsLock, - CreateWellKnownFileSystems); - - // need to return something to LazyInitializer.EnsureInitialized - // but it does not really matter what we return - here, null - private object? CreateWellKnownFileSystems() - { - ILogger logger = _loggerFactory.CreateLogger(); - - // TODO this is fucked, why do PhysicalFileSystem has a root url? Mvc views cannot be accessed by url! - var macroPartialFileSystem = new PhysicalFileSystem( - _ioHelper, - _hostingEnvironment, - logger, - _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MacroPartials), - _hostingEnvironment.ToAbsolute(Constants.SystemDirectories.MacroPartials)); - var partialViewsFileSystem = new PhysicalFileSystem( - _ioHelper, - _hostingEnvironment, - logger, - _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.PartialViews), - _hostingEnvironment.ToAbsolute(Constants.SystemDirectories.PartialViews)); - var scriptsFileSystem = new PhysicalFileSystem( - _ioHelper, - _hostingEnvironment, - logger, - _hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoScriptsPath), - _hostingEnvironment.ToAbsolute(_globalSettings.UmbracoScriptsPath)); - var mvcViewsFileSystem = new PhysicalFileSystem( - _ioHelper, - _hostingEnvironment, - logger, - _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MvcViews), - _hostingEnvironment.ToAbsolute(Constants.SystemDirectories.MvcViews)); - - _macroPartialFileSystem = new ShadowWrapper( - macroPartialFileSystem, - _ioHelper, - _hostingEnvironment, - _loggerFactory, - "macro-partials", - IsScoped); - _partialViewsFileSystem = new ShadowWrapper( - partialViewsFileSystem, - _ioHelper, - _hostingEnvironment, - _loggerFactory, - "partials", - IsScoped); - _scriptsFileSystem = new ShadowWrapper( - scriptsFileSystem, - _ioHelper, - _hostingEnvironment, - _loggerFactory, - "scripts", - IsScoped); - _mvcViewsFileSystem = new ShadowWrapper( - mvcViewsFileSystem, - _ioHelper, - _hostingEnvironment, - _loggerFactory, - "views", - IsScoped); - - if (_stylesheetsFileSystem == null) - { - var stylesheetsFileSystem = new PhysicalFileSystem( - _ioHelper, - _hostingEnvironment, - logger, - _hostingEnvironment.MapPathWebRoot(_globalSettings.UmbracoCssPath), - _hostingEnvironment.ToAbsolute(_globalSettings.UmbracoCssPath)); - - _stylesheetsFileSystem = new ShadowWrapper( - stylesheetsFileSystem, - _ioHelper, - _hostingEnvironment, - _loggerFactory, - "css", - IsScoped); - - _shadowWrappers.Add(_stylesheetsFileSystem); + return null; } - // TODO: do we need a lock here? - _shadowWrappers.Add(_macroPartialFileSystem); - _shadowWrappers.Add(_partialViewsFileSystem); - _shadowWrappers.Add(_scriptsFileSystem); - _shadowWrappers.Add(_mvcViewsFileSystem); + #endregion - return null; - } + #region Shadow - internal void BeginShadow(string id) - { - lock (_shadowLocker) + // note + // shadowing is thread-safe, but entering and exiting shadow mode is not, and there is only one + // global shadow for the entire application, so great care should be taken to ensure that the + // application is *not* doing anything else when using a shadow. + + /// + /// Shadows the filesystem, should never be used outside the Scope class. + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public ICompletable Shadow() { - // if we throw here, it means that something very wrong happened. - if (_shadowCurrentId != null) + if (Volatile.Read(ref _wkfsInitialized) == false) { - throw new InvalidOperationException("Already shadowing."); + EnsureWellKnownFileSystems(); } - _shadowCurrentId = id; - - _logger.LogDebug("Shadow '{ShadowId}'", _shadowCurrentId); - - foreach (ShadowWrapper wrapper in _shadowWrappers) - { - wrapper.Shadow(_shadowCurrentId); - } + var id = ShadowWrapper.CreateShadowId(_hostingEnvironment); + return new ShadowFileSystems(this, id); // will invoke BeginShadow and EndShadow } - } - internal void EndShadow(string id, bool completed) - { - lock (_shadowLocker) + internal void BeginShadow(string id) { - // if we throw here, it means that something very wrong happened. - if (_shadowCurrentId == null) + lock (_shadowLocker) { - throw new InvalidOperationException("Not shadowing."); - } + // if we throw here, it means that something very wrong happened. + if (_shadowCurrentId != null) + { + throw new InvalidOperationException("Already shadowing."); + } - if (id != _shadowCurrentId) - { - throw new InvalidOperationException("Not the current shadow."); - } + _shadowCurrentId = id; - _logger.LogDebug("UnShadow '{ShadowId}' {Status}", id, completed ? "complete" : "abort"); + _logger.LogDebug("Shadow '{ShadowId}'", _shadowCurrentId); - var exceptions = new List(); - foreach (ShadowWrapper wrapper in _shadowWrappers) + foreach (ShadowWrapper wrapper in _shadowWrappers) + { + wrapper.Shadow(_shadowCurrentId); + } + } + } + + internal void EndShadow(string id, bool completed) + { + lock (_shadowLocker) { - try + // if we throw here, it means that something very wrong happened. + if (_shadowCurrentId == null) { - // this may throw an AggregateException if some of the changes could not be applied - wrapper.UnShadow(completed); + throw new InvalidOperationException("Not shadowing."); } - catch (AggregateException ae) + + if (id != _shadowCurrentId) { - exceptions.Add(ae); + throw new InvalidOperationException("Not the current shadow."); } - } - _shadowCurrentId = null; + _logger.LogDebug("UnShadow '{ShadowId}' {Status}", id, completed ? "complete" : "abort"); - if (exceptions.Count > 0) - { - throw new AggregateException( - completed ? "Failed to apply all changes (see exceptions)." : "Failed to abort (see exceptions).", - exceptions); + var exceptions = new List(); + foreach (ShadowWrapper wrapper in _shadowWrappers) + { + try + { + // this may throw an AggregateException if some of the changes could not be applied + wrapper.UnShadow(completed); + } + catch (AggregateException ae) + { + exceptions.Add(ae); + } + } + + _shadowCurrentId = null; + + if (exceptions.Count > 0) + { + throw new AggregateException(completed ? "Failed to apply all changes (see exceptions)." : "Failed to abort (see exceptions).", exceptions); + } } } - } - private ShadowWrapper CreateShadowWrapperInternal(IFileSystem filesystem, string shadowPath) - { - lock (_shadowLocker) + /// + /// Creates a shadow wrapper for a filesystem, should never be used outside UmbracoBuilder or testing + /// + /// + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public IFileSystem CreateShadowWrapper(IFileSystem filesystem, string shadowPath) => CreateShadowWrapperInternal(filesystem, shadowPath); + + private ShadowWrapper CreateShadowWrapperInternal(IFileSystem filesystem, string shadowPath) { - var wrapper = new ShadowWrapper(filesystem, _ioHelper, _hostingEnvironment, _loggerFactory, shadowPath, () => IsScoped?.Invoke()); - if (_shadowCurrentId != null) + lock (_shadowLocker) { - wrapper.Shadow(_shadowCurrentId); - } + var wrapper = new ShadowWrapper(filesystem, _ioHelper, _hostingEnvironment, _loggerFactory, shadowPath,() => IsScoped?.Invoke()); + if (_shadowCurrentId != null) + { + wrapper.Shadow(_shadowCurrentId); + } - _shadowWrappers.Add(wrapper); - return wrapper; + _shadowWrappers.Add(wrapper); + return wrapper; + } } - } - #endregion + #endregion + } } diff --git a/src/Umbraco.Core/IO/PhysicalFileSystem.cs b/src/Umbraco.Core/IO/PhysicalFileSystem.cs index 23c56f5f2761..ede481b83344 100644 --- a/src/Umbraco.Core/IO/PhysicalFileSystem.cs +++ b/src/Umbraco.Core/IO/PhysicalFileSystem.cs @@ -3,507 +3,513 @@ using Umbraco.Cms.Core.Hosting; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.IO; - -public interface IPhysicalFileSystem : IFileSystem -{ -} - -public class PhysicalFileSystem : IPhysicalFileSystem, IFileProviderFactory +namespace Umbraco.Cms.Core.IO { - private readonly IIOHelper _ioHelper; - private readonly ILogger _logger; + public interface IPhysicalFileSystem : IFileSystem + { } - // the rooted, filesystem path, using directory separator chars, NOT ending with a separator - // eg "c:" or "c:\path\to\site" or "\\server\path" - private readonly string _rootPath; + public class PhysicalFileSystem : IPhysicalFileSystem, IFileProviderFactory + { + private readonly IIOHelper _ioHelper; + private readonly ILogger _logger; - // _rootPath, but with separators replaced by forward-slashes - // eg "c:" or "c:/path/to/site" or "//server/path" - // (is used in GetRelativePath) - private readonly string _rootPathFwd; + // the rooted, filesystem path, using directory separator chars, NOT ending with a separator + // eg "c:" or "c:\path\to\site" or "\\server\path" + private readonly string _rootPath; - // the relative URL, using URL separator chars, NOT ending with a separator - // eg "" or "/Views" or "/Media" or "//Media" in case of a virtual path - private readonly string _rootUrl; + // _rootPath, but with separators replaced by forward-slashes + // eg "c:" or "c:/path/to/site" or "//server/path" + // (is used in GetRelativePath) + private readonly string _rootPathFwd; - public PhysicalFileSystem(IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, ILogger logger, string rootPath, string rootUrl) - { - _ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + // the relative URL, using URL separator chars, NOT ending with a separator + // eg "" or "/Views" or "/Media" or "//Media" in case of a virtual path + private readonly string _rootUrl; - if (rootPath == null) + public PhysicalFileSystem(IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, ILogger logger, string rootPath, string rootUrl) { - throw new ArgumentNullException(nameof(rootPath)); + _ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + if (rootPath == null) + { + throw new ArgumentNullException(nameof(rootPath)); + } + + if (string.IsNullOrEmpty(rootPath)) + { + throw new ArgumentException("Value can't be empty.", nameof(rootPath)); + } + + if (rootUrl == null) + { + throw new ArgumentNullException(nameof(rootUrl)); + } + + if (string.IsNullOrEmpty(rootUrl)) + { + throw new ArgumentException("Value can't be empty.", nameof(rootUrl)); + } + + if (rootPath.StartsWith("~/")) + { + throw new ArgumentException("Value can't be a virtual path and start with '~/'.", nameof(rootPath)); + } + + // rootPath should be... rooted, as in, it's a root path! + if (Path.IsPathRooted(rootPath) == false) + { + // but the test suite App.config cannot really "root" anything so we have to do it here + var localRoot = hostingEnvironment.MapPathContentRoot("~"); + rootPath = Path.Combine(localRoot, rootPath); + } + + // clean up root path + rootPath = Path.GetFullPath(rootPath); + + _rootPath = EnsureDirectorySeparatorChar(rootPath).TrimEnd(Path.DirectorySeparatorChar); + _rootPathFwd = EnsureUrlSeparatorChar(_rootPath); + _rootUrl = EnsureUrlSeparatorChar(rootUrl).TrimEnd(Constants.CharArrays.ForwardSlash); } - if (string.IsNullOrEmpty(rootPath)) + /// + /// Gets directories in a directory. + /// + /// The filesystem-relative path to the directory. + /// The filesystem-relative path to the directories in the directory. + /// Filesystem-relative paths use forward-slashes as directory separators. + public IEnumerable GetDirectories(string path) { - throw new ArgumentException("Value can't be empty.", nameof(rootPath)); + var fullPath = GetFullPath(path); + + try + { + if (Directory.Exists(fullPath)) + { + return Directory.EnumerateDirectories(fullPath).Select(GetRelativePath); + } + } + catch (UnauthorizedAccessException ex) + { + _logger.LogError(ex, "Not authorized to get directories for '{Path}'", fullPath); + } + catch (DirectoryNotFoundException ex) + { + _logger.LogError(ex, "Directory not found for '{Path}'", fullPath); + } + + return Enumerable.Empty(); } - if (rootUrl == null) + /// + /// Deletes a directory. + /// + /// The filesystem-relative path of the directory. + public void DeleteDirectory(string path) { - throw new ArgumentNullException(nameof(rootUrl)); + DeleteDirectory(path, false); } - if (string.IsNullOrEmpty(rootUrl)) + /// + /// Deletes a directory. + /// + /// The filesystem-relative path of the directory. + /// A value indicating whether to recursively delete sub-directories. + public void DeleteDirectory(string path, bool recursive) { - throw new ArgumentException("Value can't be empty.", nameof(rootUrl)); + var fullPath = GetFullPath(path); + if (Directory.Exists(fullPath) == false) + { + return; + } + + try + { + WithRetry(() => Directory.Delete(fullPath, recursive)); + } + catch (DirectoryNotFoundException ex) + { + _logger.LogError(ex, "Directory not found for '{Path}'", fullPath); + } } - if (rootPath.StartsWith("~/")) + /// + /// Gets a value indicating whether a directory exists. + /// + /// The filesystem-relative path of the directory. + /// A value indicating whether a directory exists. + public bool DirectoryExists(string path) { - throw new ArgumentException("Value can't be a virtual path and start with '~/'.", nameof(rootPath)); + var fullPath = GetFullPath(path); + return Directory.Exists(fullPath); } - // rootPath should be... rooted, as in, it's a root path! - if (Path.IsPathRooted(rootPath) == false) + /// + /// Saves a file. + /// + /// The filesystem-relative path of the file. + /// A stream containing the file data. + /// Overrides the existing file, if any. + public void AddFile(string path, Stream stream) { - // but the test suite App.config cannot really "root" anything so we have to do it here - var localRoot = hostingEnvironment.MapPathContentRoot("~"); - rootPath = Path.Combine(localRoot, rootPath); + AddFile(path, stream, true); } - // clean up root path - rootPath = Path.GetFullPath(rootPath); - - _rootPath = EnsureDirectorySeparatorChar(rootPath).TrimEnd(Path.DirectorySeparatorChar); - _rootPathFwd = EnsureUrlSeparatorChar(_rootPath); - _rootUrl = EnsureUrlSeparatorChar(rootUrl).TrimEnd(Constants.CharArrays.ForwardSlash); - } + /// + /// Saves a file. + /// + /// The filesystem-relative path of the file. + /// A stream containing the file data. + /// A value indicating whether to override the existing file, if any. + /// If a file exists and is false, an exception is thrown. + public void AddFile(string path, Stream stream, bool overrideExisting) + { + var fullPath = GetFullPath(path); + var exists = File.Exists(fullPath); + if (exists && overrideExisting == false) + { + throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); + } - public bool CanAddPhysical => true; + var directory = Path.GetDirectoryName(fullPath); + if (directory == null) + { + throw new InvalidOperationException("Could not get directory."); + } - /// - /// Gets directories in a directory. - /// - /// The filesystem-relative path to the directory. - /// The filesystem-relative path to the directories in the directory. - /// Filesystem-relative paths use forward-slashes as directory separators. - public IEnumerable GetDirectories(string path) - { - var fullPath = GetFullPath(path); + Directory.CreateDirectory(directory); // ensure it exists - try - { - if (Directory.Exists(fullPath)) + if (stream.CanSeek) { - return Directory.EnumerateDirectories(fullPath).Select(GetRelativePath); + stream.Seek(0, 0); } - } - catch (UnauthorizedAccessException ex) - { - _logger.LogError(ex, "Not authorized to get directories for '{Path}'", fullPath); - } - catch (DirectoryNotFoundException ex) - { - _logger.LogError(ex, "Directory not found for '{Path}'", fullPath); - } - - return Enumerable.Empty(); - } - /// - /// Deletes a directory. - /// - /// The filesystem-relative path of the directory. - public void DeleteDirectory(string path) => DeleteDirectory(path, false); - - /// - /// Deletes a directory. - /// - /// The filesystem-relative path of the directory. - /// A value indicating whether to recursively delete sub-directories. - public void DeleteDirectory(string path, bool recursive) - { - var fullPath = GetFullPath(path); - if (Directory.Exists(fullPath) == false) - { - return; + using var destination = (Stream)File.Create(fullPath); + stream.CopyTo(destination); } - try + /// + /// Gets files in a directory. + /// + /// The filesystem-relative path of the directory. + /// The filesystem-relative path to the files in the directory. + /// Filesystem-relative paths use forward-slashes as directory separators. + public IEnumerable GetFiles(string path) { - WithRetry(() => Directory.Delete(fullPath, recursive)); + return GetFiles(path, "*.*"); } - catch (DirectoryNotFoundException ex) + + /// + /// Gets files in a directory. + /// + /// The filesystem-relative path of the directory. + /// A filter. + /// The filesystem-relative path to the matching files in the directory. + /// Filesystem-relative paths use forward-slashes as directory separators. //TODO check is this is true on linux and windows.. + public IEnumerable GetFiles(string path, string filter) { - _logger.LogError(ex, "Directory not found for '{Path}'", fullPath); - } - } + var fullPath = GetFullPath(path); - /// - /// Gets a value indicating whether a directory exists. - /// - /// The filesystem-relative path of the directory. - /// A value indicating whether a directory exists. - public bool DirectoryExists(string path) - { - var fullPath = GetFullPath(path); - return Directory.Exists(fullPath); - } + try + { + if (Directory.Exists(fullPath)) + { + return Directory.EnumerateFiles(fullPath, filter).Select(GetRelativePath); + } + } + catch (UnauthorizedAccessException ex) + { + _logger.LogError(ex, "Not authorized to get directories for '{Path}'", fullPath); + } + catch (DirectoryNotFoundException ex) + { + _logger.LogError(ex, "Directory not found for '{FullPath}'", fullPath); + } - /// - /// Saves a file. - /// - /// The filesystem-relative path of the file. - /// A stream containing the file data. - /// Overrides the existing file, if any. - public void AddFile(string path, Stream stream) => AddFile(path, stream, true); - - /// - /// Saves a file. - /// - /// The filesystem-relative path of the file. - /// A stream containing the file data. - /// A value indicating whether to override the existing file, if any. - /// If a file exists and is false, an exception is thrown. - public void AddFile(string path, Stream stream, bool overrideExisting) - { - var fullPath = GetFullPath(path); - var exists = File.Exists(fullPath); - if (exists && overrideExisting == false) - { - throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path)); + return Enumerable.Empty(); } - var directory = Path.GetDirectoryName(fullPath); - if (directory == null) + /// + /// Opens a file. + /// + /// The filesystem-relative path to the file. + /// + public Stream OpenFile(string path) { - throw new InvalidOperationException("Could not get directory."); + var fullPath = GetFullPath(path); + return File.OpenRead(fullPath); } - Directory.CreateDirectory(directory); // ensure it exists - - if (stream.CanSeek) + /// + /// Deletes a file. + /// + /// The filesystem-relative path to the file. + public void DeleteFile(string path) { - stream.Seek(0, 0); - } - - using var destination = (Stream)File.Create(fullPath); - stream.CopyTo(destination); - } - - /// - /// Gets files in a directory. - /// - /// The filesystem-relative path of the directory. - /// The filesystem-relative path to the files in the directory. - /// Filesystem-relative paths use forward-slashes as directory separators. - public IEnumerable GetFiles(string path) => GetFiles(path, "*.*"); - - /// - /// Gets files in a directory. - /// - /// The filesystem-relative path of the directory. - /// A filter. - /// The filesystem-relative path to the matching files in the directory. - /// Filesystem-relative paths use forward-slashes as directory separators. - /// //TODO check is this is true on linux and windows.. - public IEnumerable GetFiles(string path, string filter) - { - var fullPath = GetFullPath(path); + var fullPath = GetFullPath(path); + if (File.Exists(fullPath) == false) + { + return; + } - try - { - if (Directory.Exists(fullPath)) + try + { + WithRetry(() => File.Delete(fullPath)); + } + catch (FileNotFoundException ex) { - return Directory.EnumerateFiles(fullPath, filter).Select(GetRelativePath); + _logger.LogError(ex.InnerException, "DeleteFile failed with FileNotFoundException for '{Path}'", fullPath); } } - catch (UnauthorizedAccessException ex) + + /// + /// Gets a value indicating whether a file exists. + /// + /// The filesystem-relative path to the file. + /// A value indicating whether the file exists. + public bool FileExists(string path) { - _logger.LogError(ex, "Not authorized to get directories for '{Path}'", fullPath); + var fullpath = GetFullPath(path); + return File.Exists(fullpath); } - catch (DirectoryNotFoundException ex) + + /// + /// Gets the filesystem-relative path of a full path or of an URL. + /// + /// The full path or URL. + /// The path, relative to this filesystem's root. + /// + /// The relative path is relative to this filesystem's root, not starting with any + /// directory separator. All separators are forward-slashes. + /// + public string GetRelativePath(string fullPathOrUrl) { - _logger.LogError(ex, "Directory not found for '{FullPath}'", fullPath); - } + // test URL + var path = fullPathOrUrl.Replace('\\', '/'); // ensure URL separator char - return Enumerable.Empty(); - } + // if it starts with the root path, strip it and trim the starting slash to make it relative + // eg "c:/websites/test/root/Media/1234/img.jpg" => "1234/img.jpg" + // or on unix systems "/var/wwwroot/test/Meia/1234/img.jpg" + if (_ioHelper.PathStartsWith(path, _rootPathFwd, '/')) + { + return path.Substring(_rootPathFwd.Length).TrimStart(Constants.CharArrays.ForwardSlash); + } - /// - /// Opens a file. - /// - /// The filesystem-relative path to the file. - /// - public Stream OpenFile(string path) - { - var fullPath = GetFullPath(path); - return File.OpenRead(fullPath); - } + // if it starts with the root URL, strip it and trim the starting slash to make it relative + // eg "/Media/1234/img.jpg" => "1234/img.jpg" + if (_ioHelper.PathStartsWith(path, _rootUrl, '/')) + { + return path.Substring(_rootUrl.Length).TrimStart(Constants.CharArrays.ForwardSlash); + } - /// - /// Deletes a file. - /// - /// The filesystem-relative path to the file. - public void DeleteFile(string path) - { - var fullPath = GetFullPath(path); - if (File.Exists(fullPath) == false) - { - return; + // unchanged - what else? + return path.TrimStart(Constants.CharArrays.ForwardSlash); } - try + /// + /// Gets the full path. + /// + /// The full or filesystem-relative path. + /// The full path. + /// + /// On the physical filesystem, the full path is the rooted (ie non-relative), safe (ie within this + /// filesystem's root) path. All separators are Path.DirectorySeparatorChar. + /// + public string GetFullPath(string path) { - WithRetry(() => File.Delete(fullPath)); - } - catch (FileNotFoundException ex) - { - _logger.LogError(ex.InnerException, "DeleteFile failed with FileNotFoundException for '{Path}'", fullPath); - } - } + // normalize + var originalPath = path; + path = EnsureDirectorySeparatorChar(path); + + // FIXME: this part should go! + // not sure what we are doing here - so if input starts with a (back) slash, + // we assume it's not a FS relative path and we try to convert it... but it + // really makes little sense? + if (path.StartsWith(Path.DirectorySeparatorChar.ToString())) + { + path = GetRelativePath(path); + } - /// - /// Gets a value indicating whether a file exists. - /// - /// The filesystem-relative path to the file. - /// A value indicating whether the file exists. - public bool FileExists(string path) - { - var fullpath = GetFullPath(path); - return File.Exists(fullpath); - } + // if not already rooted, combine with the root path + if (_ioHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar) == false) + { + path = Path.Combine(_rootPath, path); + } - /// - /// Gets the filesystem-relative path of a full path or of an URL. - /// - /// The full path or URL. - /// The path, relative to this filesystem's root. - /// - /// - /// The relative path is relative to this filesystem's root, not starting with any - /// directory separator. All separators are forward-slashes. - /// - /// - public string GetRelativePath(string fullPathOrUrl) - { - // test URL - var path = fullPathOrUrl.Replace('\\', '/'); // ensure URL separator char + // sanitize - GetFullPath will take care of any relative + // segments in path, eg '../../foo.tmp' - it may throw a SecurityException + // if the combined path reaches illegal parts of the filesystem + path = Path.GetFullPath(path); - // if it starts with the root path, strip it and trim the starting slash to make it relative - // eg "c:/websites/test/root/Media/1234/img.jpg" => "1234/img.jpg" - // or on unix systems "/var/wwwroot/test/Meia/1234/img.jpg" - if (_ioHelper.PathStartsWith(path, _rootPathFwd, '/')) - { - return path.Substring(_rootPathFwd.Length).TrimStart(Constants.CharArrays.ForwardSlash); + // at that point, path is within legal parts of the filesystem, ie we have + // permissions to reach that path, but it may nevertheless be outside of + // our root path, due to relative segments, so better check + if (_ioHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar)) + { + // this says that 4.7.2 supports long paths - but Windows does not + // https://docs.microsoft.com/en-us/dotnet/api/system.io.pathtoolongexception?view=netframework-4.7.2 + if (path.Length > 260) + { + throw new PathTooLongException($"Path {path} is too long."); + } + + return path; + } + + // nothing prevents us to reach the file, security-wise, yet it is outside + // this filesystem's root - throw + throw new UnauthorizedAccessException($"File original: [{originalPath}] full: [{path}] is outside this filesystem's root."); } - // if it starts with the root URL, strip it and trim the starting slash to make it relative - // eg "/Media/1234/img.jpg" => "1234/img.jpg" - if (_ioHelper.PathStartsWith(path, _rootUrl, '/')) + /// + /// Gets the URL. + /// + /// The filesystem-relative path. + /// The URL. + /// All separators are forward-slashes. + public string GetUrl(string? path) { - return path.Substring(_rootUrl.Length).TrimStart(Constants.CharArrays.ForwardSlash); + path = EnsureUrlSeparatorChar(path ?? string.Empty).Trim(Constants.CharArrays.ForwardSlash); + return _rootUrl + "/" + path; } - // unchanged - what else? - return path.TrimStart(Constants.CharArrays.ForwardSlash); - } - - /// - /// Gets the full path. - /// - /// The full or filesystem-relative path. - /// The full path. - /// - /// - /// On the physical filesystem, the full path is the rooted (ie non-relative), safe (ie within this - /// filesystem's root) path. All separators are Path.DirectorySeparatorChar. - /// - /// - public string GetFullPath(string path) - { - // normalize - var originalPath = path; - path = EnsureDirectorySeparatorChar(path); - - // FIXME: this part should go! - // not sure what we are doing here - so if input starts with a (back) slash, - // we assume it's not a FS relative path and we try to convert it... but it - // really makes little sense? - if (path.StartsWith(Path.DirectorySeparatorChar.ToString())) + /// + /// Gets the last-modified date of a directory or file. + /// + /// The filesystem-relative path to the directory or the file. + /// The last modified date of the directory or the file. + public DateTimeOffset GetLastModified(string path) { - path = GetRelativePath(path); + var fullpath = GetFullPath(path); + return DirectoryExists(fullpath) + ? new DirectoryInfo(fullpath).LastWriteTimeUtc + : new FileInfo(fullpath).LastWriteTimeUtc; } - // if not already rooted, combine with the root path - if (_ioHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar) == false) + /// + /// Gets the created date of a directory or file. + /// + /// The filesystem-relative path to the directory or the file. + /// The created date of the directory or the file. + public DateTimeOffset GetCreated(string path) { - path = Path.Combine(_rootPath, path); + var fullpath = GetFullPath(path); + return DirectoryExists(fullpath) + ? Directory.GetCreationTimeUtc(fullpath) + : File.GetCreationTimeUtc(fullpath); } - // sanitize - GetFullPath will take care of any relative - // segments in path, eg '../../foo.tmp' - it may throw a SecurityException - // if the combined path reaches illegal parts of the filesystem - path = Path.GetFullPath(path); - - // at that point, path is within legal parts of the filesystem, ie we have - // permissions to reach that path, but it may nevertheless be outside of - // our root path, due to relative segments, so better check - if (_ioHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar)) + /// + /// Gets the size of a file. + /// + /// The filesystem-relative path to the file. + /// The file of the size, in bytes. + /// If the file does not exist, returns -1. + public long GetSize(string path) { - // this says that 4.7.2 supports long paths - but Windows does not - // https://docs.microsoft.com/en-us/dotnet/api/system.io.pathtoolongexception?view=netframework-4.7.2 - if (path.Length > 260) - { - throw new PathTooLongException($"Path {path} is too long."); - } - - return path; + var fullPath = GetFullPath(path); + var file = new FileInfo(fullPath); + return file.Exists ? file.Length : -1; } - // nothing prevents us to reach the file, security-wise, yet it is outside - // this filesystem's root - throw - throw new UnauthorizedAccessException( - $"File original: [{originalPath}] full: [{path}] is outside this filesystem's root."); - } + public bool CanAddPhysical => true; - /// - /// Gets the URL. - /// - /// The filesystem-relative path. - /// The URL. - /// All separators are forward-slashes. - public string GetUrl(string? path) - { - path = EnsureUrlSeparatorChar(path ?? string.Empty).Trim(Constants.CharArrays.ForwardSlash); - return _rootUrl + "/" + path; - } + public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) + { + var fullPath = GetFullPath(path); - /// - /// Gets the last-modified date of a directory or file. - /// - /// The filesystem-relative path to the directory or the file. - /// The last modified date of the directory or the file. - public DateTimeOffset GetLastModified(string path) - { - var fullpath = GetFullPath(path); - return DirectoryExists(fullpath) - ? new DirectoryInfo(fullpath).LastWriteTimeUtc - : new FileInfo(fullpath).LastWriteTimeUtc; - } + if (File.Exists(fullPath)) + { + if (overrideIfExists == false) + { + throw new InvalidOperationException($"A file at path '{path}' already exists"); + } - /// - /// Gets the created date of a directory or file. - /// - /// The filesystem-relative path to the directory or the file. - /// The created date of the directory or the file. - public DateTimeOffset GetCreated(string path) - { - var fullpath = GetFullPath(path); - return DirectoryExists(fullpath) - ? Directory.GetCreationTimeUtc(fullpath) - : File.GetCreationTimeUtc(fullpath); - } + WithRetry(() => File.Delete(fullPath)); + } - /// - /// Gets the size of a file. - /// - /// The filesystem-relative path to the file. - /// The file of the size, in bytes. - /// If the file does not exist, returns -1. - public long GetSize(string path) - { - var fullPath = GetFullPath(path); - var file = new FileInfo(fullPath); - return file.Exists ? file.Length : -1; - } + var directory = Path.GetDirectoryName(fullPath); + if (directory == null) + { + throw new InvalidOperationException("Could not get directory."); + } - public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false) - { - var fullPath = GetFullPath(path); + Directory.CreateDirectory(directory); // ensure it exists - if (File.Exists(fullPath)) - { - if (overrideIfExists == false) + if (copy) { - throw new InvalidOperationException($"A file at path '{path}' already exists"); + WithRetry(() => File.Copy(physicalPath, fullPath)); + } + else + { + WithRetry(() => File.Move(physicalPath, fullPath)); } - - WithRetry(() => File.Delete(fullPath)); } - var directory = Path.GetDirectoryName(fullPath); - if (directory == null) + #region Helper Methods + + protected virtual void EnsureDirectory(string path) { - throw new InvalidOperationException("Could not get directory."); + path = GetFullPath(path); + Directory.CreateDirectory(path); } - Directory.CreateDirectory(directory); // ensure it exists - - if (copy) + protected string EnsureTrailingSeparator(string path) { - WithRetry(() => File.Copy(physicalPath, fullPath)); + return path.EnsureEndsWith(Path.DirectorySeparatorChar); } - else + + protected string EnsureDirectorySeparatorChar(string path) { - WithRetry(() => File.Move(physicalPath, fullPath)); + path = path.Replace('/', Path.DirectorySeparatorChar); + path = path.Replace('\\', Path.DirectorySeparatorChar); + return path; } - } - - /// - public IFileProvider Create() => new PhysicalFileProvider(_rootPath); - - #region Helper Methods - protected virtual void EnsureDirectory(string path) - { - path = GetFullPath(path); - Directory.CreateDirectory(path); - } - - protected string EnsureTrailingSeparator(string path) => path.EnsureEndsWith(Path.DirectorySeparatorChar); - - protected string EnsureDirectorySeparatorChar(string path) - { - path = path.Replace('/', Path.DirectorySeparatorChar); - path = path.Replace('\\', Path.DirectorySeparatorChar); - return path; - } - - protected string EnsureUrlSeparatorChar(string path) - { - path = path.Replace('\\', '/'); - return path; - } - - protected void WithRetry(Action action) - { - // 10 times 100ms is 1s - const int count = 10; - const int pausems = 100; + protected string EnsureUrlSeparatorChar(string path) + { + path = path.Replace('\\', '/'); + return path; + } - for (var i = 0; ; i++) + protected void WithRetry(Action action) { - try - { - action(); - break; // done - } - catch (IOException e) + // 10 times 100ms is 1s + const int count = 10; + const int pausems = 100; + + for (var i = 0;; i++) { - // if it's not *exactly* IOException then it could be - // some inherited exception such as FileNotFoundException, - // and then we don't want to retry - if (e.GetType() != typeof(IOException)) + try { - throw; + action(); + break; // done } - - // if we have tried enough, throw, else swallow - // the exception and retry after a pause - if (i == count) + catch (IOException e) { - throw; + // if it's not *exactly* IOException then it could be + // some inherited exception such as FileNotFoundException, + // and then we don't want to retry + if (e.GetType() != typeof(IOException)) + { + throw; + } + + // if we have tried enough, throw, else swallow + // the exception and retry after a pause + if (i == count) + { + throw; + } } - } - Thread.Sleep(pausems); + Thread.Sleep(pausems); + } } - } - #endregion + /// + public IFileProvider Create() => new PhysicalFileProvider(_rootPath); + + #endregion + } } diff --git a/src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs b/src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs index d25a8a31dac2..763b69226e99 100644 --- a/src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs +++ b/src/Umbraco.Core/Install/InstallSteps/UpgradeStep.cs @@ -3,64 +3,53 @@ using Umbraco.Cms.Core.Semver; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; - -namespace Umbraco.Cms.Core.Install.InstallSteps; - -/// -/// This step is purely here to show the button to commence the upgrade -/// -[InstallSetupStep(InstallationType.Upgrade, "Upgrade", "upgrade", 1, "Upgrading Umbraco to the latest and greatest version.")] -public class UpgradeStep : InstallSetupStep +namespace Umbraco.Cms.Core.Install.InstallSteps { - private readonly IRuntimeState _runtimeState; - private readonly IUmbracoVersion _umbracoVersion; - - public UpgradeStep(IUmbracoVersion umbracoVersion, IRuntimeState runtimeState) + /// + /// This step is purely here to show the button to commence the upgrade + /// + [InstallSetupStep(InstallationType.Upgrade, "Upgrade", "upgrade", 1, "Upgrading Umbraco to the latest and greatest version.")] + public class UpgradeStep : InstallSetupStep { - _umbracoVersion = umbracoVersion; - _runtimeState = runtimeState; - } + public override bool RequiresExecution(object model) => true; + private readonly IUmbracoVersion _umbracoVersion; + private readonly IRuntimeState _runtimeState; - public override object ViewModel - { - get + public UpgradeStep(IUmbracoVersion umbracoVersion, IRuntimeState runtimeState) + { + _umbracoVersion = umbracoVersion; + _runtimeState = runtimeState; + } + + public override Task ExecuteAsync(object model) => Task.FromResult(null); + + public override object ViewModel { - static string FormatGuidState(string? value) + get { - if (string.IsNullOrWhiteSpace(value)) + string FormatGuidState(string? value) { - value = "unknown"; + if (string.IsNullOrWhiteSpace(value)) + { + value = "unknown"; + } + else if (Guid.TryParse(value, out Guid currentStateGuid)) + { + value = currentStateGuid.ToString("N").Substring(0, 8); + } + + return value; } - else if (Guid.TryParse(value, out Guid currentStateGuid)) - { - value = currentStateGuid.ToString("N").Substring(0, 8); - } - - return value; - } - var currentState = FormatGuidState(_runtimeState.CurrentMigrationState); - var newState = FormatGuidState(_runtimeState.FinalMigrationState); - var newVersion = _umbracoVersion.SemanticVersion?.ToSemanticStringWithoutBuild(); - var oldVersion = - new SemVersion(_umbracoVersion.SemanticVersion?.Major ?? 0) - .ToString(); // TODO can we find the old version somehow? e.g. from current state + var currentState = FormatGuidState(_runtimeState.CurrentMigrationState); + var newState = FormatGuidState(_runtimeState.FinalMigrationState); + var newVersion = _umbracoVersion.SemanticVersion?.ToSemanticStringWithoutBuild(); + var oldVersion = new SemVersion(_umbracoVersion.SemanticVersion?.Major ?? 0).ToString(); //TODO can we find the old version somehow? e.g. from current state - var reportUrl = - $"https://our.umbraco.com/contribute/releases/compare?from={oldVersion}&to={newVersion}¬es=1"; + var reportUrl = $"https://our.umbraco.com/contribute/releases/compare?from={oldVersion}&to={newVersion}¬es=1"; - return new - { - oldVersion, - newVersion, - currentState, - newState, - reportUrl, - }; + return new { oldVersion, newVersion, currentState, newState, reportUrl }; + } } } - - public override bool RequiresExecution(object model) => true; - - public override Task ExecuteAsync(object model) => Task.FromResult(null); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/DailyMotion.cs b/src/Umbraco.Core/Media/EmbedProviders/DailyMotion.cs index e3cba152c5ee..3ea329abec6f 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/DailyMotion.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/DailyMotion.cs @@ -23,8 +23,8 @@ public DailyMotion(IJsonSerializer jsonSerializer) public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); - XmlDocument xmlDocument = this.GetXmlResponse(requestUrl); + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); return GetXmlProperty(xmlDocument, "/oembed/html"); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Flickr.cs b/src/Umbraco.Core/Media/EmbedProviders/Flickr.cs index 2de4f7536567..5e11780645db 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Flickr.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Flickr.cs @@ -20,8 +20,8 @@ public Flickr(IJsonSerializer jsonSerializer) public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); - XmlDocument xmlDocument = this.GetXmlResponse(requestUrl); + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); var imageUrl = GetXmlProperty(xmlDocument, "/oembed/url"); var imageWidth = GetXmlProperty(xmlDocument, "/oembed/width"); diff --git a/src/Umbraco.Core/Media/EmbedProviders/GettyImages.cs b/src/Umbraco.Core/Media/EmbedProviders/GettyImages.cs index b8a69d2ea817..53d13cc063d7 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/GettyImages.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/GettyImages.cs @@ -20,8 +20,8 @@ public GettyImages(IJsonSerializer jsonSerializer) public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse? oembed = this.GetJsonResponse(requestUrl); + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse? oembed = base.GetJsonResponse(requestUrl); return oembed?.GetHtml(); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Giphy.cs b/src/Umbraco.Core/Media/EmbedProviders/Giphy.cs index 445687a5a347..4adb02f8fbe0 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Giphy.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Giphy.cs @@ -21,8 +21,8 @@ public Giphy(IJsonSerializer jsonSerializer) public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse? oembed = this.GetJsonResponse(requestUrl); + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse? oembed = base.GetJsonResponse(requestUrl); return oembed?.GetHtml(); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Hulu.cs b/src/Umbraco.Core/Media/EmbedProviders/Hulu.cs index 2aa2c27b1cbe..2fdadee6ea42 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Hulu.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Hulu.cs @@ -18,8 +18,8 @@ public Hulu(IJsonSerializer jsonSerializer) public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse? oembed = this.GetJsonResponse(requestUrl); + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse? oembed = base.GetJsonResponse(requestUrl); return oembed?.GetHtml(); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs b/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs index 0ca8cf68f32c..ded01ef0d95e 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Issuu.cs @@ -23,8 +23,8 @@ public Issuu(IJsonSerializer jsonSerializer) public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); - XmlDocument xmlDocument = this.GetXmlResponse(requestUrl); + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); return GetXmlProperty(xmlDocument, "/oembed/html"); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Kickstarter.cs b/src/Umbraco.Core/Media/EmbedProviders/Kickstarter.cs index 8cbeae5e6405..4e3f5b6731ef 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Kickstarter.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Kickstarter.cs @@ -18,8 +18,8 @@ public Kickstarter(IJsonSerializer jsonSerializer) public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse? oembed = this.GetJsonResponse(requestUrl); + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse? oembed = base.GetJsonResponse(requestUrl); return oembed?.GetHtml(); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Slideshare.cs b/src/Umbraco.Core/Media/EmbedProviders/Slideshare.cs index cd017c60387c..f00e631d2552 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Slideshare.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Slideshare.cs @@ -19,8 +19,8 @@ public Slideshare(IJsonSerializer jsonSerializer) public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); - XmlDocument xmlDocument = this.GetXmlResponse(requestUrl); + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); return GetXmlProperty(xmlDocument, "/oembed/html"); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/SoundCloud.cs b/src/Umbraco.Core/Media/EmbedProviders/SoundCloud.cs index 02ebaaefdffe..f3d4e2caaef2 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/SoundCloud.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/SoundCloud.cs @@ -19,8 +19,8 @@ public Soundcloud(IJsonSerializer jsonSerializer) public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); - XmlDocument xmlDocument = this.GetXmlResponse(requestUrl); + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); return GetXmlProperty(xmlDocument, "/oembed/html"); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Ted.cs b/src/Umbraco.Core/Media/EmbedProviders/Ted.cs index 3eed04fd0136..9c8a607e13a2 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Ted.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Ted.cs @@ -19,8 +19,8 @@ public Ted(IJsonSerializer jsonSerializer) public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); - XmlDocument xmlDocument = this.GetXmlResponse(requestUrl); + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); return GetXmlProperty(xmlDocument, "/oembed/html"); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs b/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs index 7ef914218ab4..555224032a1c 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Twitter.cs @@ -18,8 +18,8 @@ public Twitter(IJsonSerializer jsonSerializer) public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse? oembed = this.GetJsonResponse(requestUrl); + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse? oembed = base.GetJsonResponse(requestUrl); return oembed?.GetHtml(); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Vimeo.cs b/src/Umbraco.Core/Media/EmbedProviders/Vimeo.cs index f9aa5afab177..ed3990ba4de2 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Vimeo.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Vimeo.cs @@ -19,8 +19,8 @@ public Vimeo(IJsonSerializer jsonSerializer) public override string GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); - XmlDocument xmlDocument = this.GetXmlResponse(requestUrl); + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + XmlDocument xmlDocument = base.GetXmlResponse(requestUrl); return GetXmlProperty(xmlDocument, "/oembed/html"); } diff --git a/src/Umbraco.Core/Media/EmbedProviders/Youtube.cs b/src/Umbraco.Core/Media/EmbedProviders/Youtube.cs index 40c40a88ad0c..594c7ead8325 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/Youtube.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/Youtube.cs @@ -22,8 +22,8 @@ public YouTube(IJsonSerializer jsonSerializer) public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) { - var requestUrl = this.GetEmbedProviderUrl(url, maxWidth, maxHeight); - OEmbedResponse? oembed = this.GetJsonResponse(requestUrl); + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse? oembed = base.GetJsonResponse(requestUrl); return oembed?.GetHtml(); } diff --git a/src/Umbraco.Core/Models/ContentBaseExtensions.cs b/src/Umbraco.Core/Models/ContentBaseExtensions.cs index 1337375e6fee..656db0f82f39 100644 --- a/src/Umbraco.Core/Models/ContentBaseExtensions.cs +++ b/src/Umbraco.Core/Models/ContentBaseExtensions.cs @@ -8,7 +8,7 @@ namespace Umbraco.Extensions; /// public static class ContentBaseExtensions { - private static DefaultUrlSegmentProvider? defaultUrlSegmentProvider; + private static DefaultUrlSegmentProvider? _defaultUrlSegmentProvider; /// /// Gets the URL segment for a specified content and culture. @@ -33,12 +33,12 @@ public static class ContentBaseExtensions var url = urlSegmentProviders.Select(p => p.GetUrlSegment(content, culture)).FirstOrDefault(u => u != null); if (url == null) { - if (defaultUrlSegmentProvider == null) + if (_defaultUrlSegmentProvider == null) { - defaultUrlSegmentProvider = new DefaultUrlSegmentProvider(shortStringHelper); + _defaultUrlSegmentProvider = new DefaultUrlSegmentProvider(shortStringHelper); } - url = defaultUrlSegmentProvider.GetUrlSegment(content, culture); // be safe + url = _defaultUrlSegmentProvider.GetUrlSegment(content, culture); // be safe } return url; diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index 94db74078c6f..01b57f38f8cf 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -9,8 +9,6 @@ namespace Umbraco.Cms.Core.Models.PublishedContent; /// public interface IPublishedContent : IPublishedElement { - #region Content - // TODO: IPublishedContent properties colliding with models // we need to find a way to remove as much clutter as possible from IPublishedContent, // since this is preventing someone from creating a property named 'Path' and have it @@ -98,10 +96,6 @@ public interface IPublishedContent : IPublishedElement /// PublishedItemType ItemType { get; } - #endregion - - #region Tree - /// /// Gets the parent of the content item. /// @@ -153,6 +147,4 @@ public interface IPublishedContent : IPublishedElement /// Gets all the children of the content item, regardless of whether they are available for the current culture. /// IEnumerable? ChildrenForAllCultures { get; } - - #endregion } diff --git a/src/Umbraco.Core/Models/PublishedContent/ModelType.cs b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs index a0d10ff89034..4588d47967fa 100644 --- a/src/Umbraco.Core/Models/PublishedContent/ModelType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/ModelType.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; using System.Reflection; using Umbraco.Cms.Core.Exceptions; @@ -372,6 +372,7 @@ public override Type MakeArrayType() => new ModelTypeArrayType(this); } +/// internal class ModelTypeArrayType : Type { private readonly Type _elementType; diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentBase.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentBase.cs index 5fd16e26aa2c..077b420735b2 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentBase.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentBase.cs @@ -1,105 +1,83 @@ -using System.Diagnostics; +using System.Diagnostics; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.PublishedContent; - -/// -/// Provide an abstract base class for IPublishedContent implementations. -/// -/// -/// This base class does which (a) consistently resolves and caches the URL, (b) provides an implementation -/// for this[alias], and (c) provides basic content set management. -/// -[DebuggerDisplay("Content Id: {Id}")] -public abstract class PublishedContentBase : IPublishedContent +namespace Umbraco.Cms.Core.Models.PublishedContent { - private readonly IVariationContextAccessor? _variationContextAccessor; + /// + /// Provide an abstract base class for IPublishedContent implementations. + /// + /// This base class does which (a) consistently resolves and caches the URL, (b) provides an implementation + /// for this[alias], and (c) provides basic content set management. + [DebuggerDisplay("Content Id: {Id}")] + public abstract class PublishedContentBase : IPublishedContent + { + private readonly IVariationContextAccessor? _variationContextAccessor; - protected PublishedContentBase(IVariationContextAccessor? variationContextAccessor) => - _variationContextAccessor = variationContextAccessor; + protected PublishedContentBase(IVariationContextAccessor? variationContextAccessor) => _variationContextAccessor = variationContextAccessor; - #region ContentType + public abstract IPublishedContentType ContentType { get; } - public abstract IPublishedContentType ContentType { get; } + /// + public abstract Guid Key { get; } - #endregion + /// + public abstract int Id { get; } - #region PublishedElement + /// + public virtual string? Name => this.Name(_variationContextAccessor); - /// - public abstract Guid Key { get; } + /// + public virtual string? UrlSegment => this.UrlSegment(_variationContextAccessor); - #endregion + /// + public abstract int SortOrder { get; } - #region PublishedContent + /// + public abstract int Level { get; } - /// - public abstract int Id { get; } + /// + public abstract string Path { get; } - /// - public virtual string? Name => this.Name(_variationContextAccessor); + /// + public abstract int? TemplateId { get; } - /// - public virtual string? UrlSegment => this.UrlSegment(_variationContextAccessor); + /// + public abstract int CreatorId { get; } - /// - public abstract int SortOrder { get; } + /// + public abstract DateTime CreateDate { get; } - /// - public abstract int Level { get; } + /// + public abstract int WriterId { get; } - /// - public abstract string Path { get; } + /// + public abstract DateTime UpdateDate { get; } - /// - public abstract int? TemplateId { get; } + /// + public abstract IReadOnlyDictionary Cultures { get; } - /// - public abstract int CreatorId { get; } + /// + public abstract PublishedItemType ItemType { get; } - /// - public abstract DateTime CreateDate { get; } + /// + public abstract bool IsDraft(string? culture = null); - /// - public abstract int WriterId { get; } + /// + public abstract bool IsPublished(string? culture = null); - /// - public abstract DateTime UpdateDate { get; } + /// + public abstract IPublishedContent? Parent { get; } - /// - public abstract IReadOnlyDictionary Cultures { get; } + /// + public virtual IEnumerable? Children => this.Children(_variationContextAccessor); - /// - public abstract PublishedItemType ItemType { get; } + /// + public abstract IEnumerable ChildrenForAllCultures { get; } - #endregion + /// + public abstract IEnumerable Properties { get; } - #region Tree - - /// - public abstract IPublishedContent? Parent { get; } - - /// - public abstract bool IsDraft(string? culture = null); - - /// - public abstract bool IsPublished(string? culture = null); - - /// - public virtual IEnumerable? Children => this.Children(_variationContextAccessor); - - /// - public abstract IEnumerable ChildrenForAllCultures { get; } - - #endregion - - #region Properties - - /// - public abstract IEnumerable Properties { get; } - - /// - public abstract IPublishedProperty? GetProperty(string alias); - - #endregion + /// + public abstract IPublishedProperty? GetProperty(string alias); + } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index ddcf2cd64c81..bd5e7af0a490 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -1,253 +1,221 @@ using System.Diagnostics; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Models.PublishedContent; - -/// -/// Represents an type. -/// -/// -/// Instances of the class are immutable, ie -/// if the content type changes, then a new class needs to be created. -/// -[DebuggerDisplay("{Alias}")] -public class PublishedContentType : IPublishedContentType +namespace Umbraco.Cms.Core.Models.PublishedContent { - // TODO: this list somehow also exists in constants, see memberTypeRepository => remove duplicate! - private static readonly Dictionary BuiltinMemberProperties = new() - { - { nameof(IMember.Email), Constants.DataTypes.Textbox }, - { nameof(IMember.Username), Constants.DataTypes.Textbox }, - { nameof(IMember.Comments), Constants.DataTypes.Textbox }, - { nameof(IMember.IsApproved), Constants.DataTypes.Boolean }, - { nameof(IMember.IsLockedOut), Constants.DataTypes.Boolean }, - { nameof(IMember.LastLockoutDate), Constants.DataTypes.LabelDateTime }, - { nameof(IMember.CreateDate), Constants.DataTypes.LabelDateTime }, - { nameof(IMember.LastLoginDate), Constants.DataTypes.LabelDateTime }, - { nameof(IMember.LastPasswordChangeDate), Constants.DataTypes.LabelDateTime }, - }; - - // fast alias-to-index xref containing both the raw alias and its lowercase version - private readonly Dictionary _indexes = new(); - private readonly IPublishedPropertyType[] _propertyTypes = null!; - /// - /// Initializes a new instance of the class with a content type. + /// Represents an type. /// - public PublishedContentType(IContentTypeComposition contentType, IPublishedContentTypeFactory factory) - : this(contentType.Key, contentType.Id, contentType.Alias, contentType.GetItemType(), contentType.CompositionAliases(), contentType.Variations, contentType.IsElement) + /// Instances of the class are immutable, ie + /// if the content type changes, then a new class needs to be created. + [DebuggerDisplay("{Alias}")] + public class PublishedContentType : IPublishedContentType { - var propertyTypes = contentType.CompositionPropertyTypes - .Select(x => factory.CreatePropertyType(this, x)) - .ToList(); + private readonly IPublishedPropertyType[] _propertyTypes = null!; - if (ItemType == PublishedItemType.Member) + // fast alias-to-index xref containing both the raw alias and its lowercase version + private readonly Dictionary _indexes = new Dictionary(); + + /// + /// Initializes a new instance of the class with a content type. + /// + public PublishedContentType(IContentTypeComposition contentType, IPublishedContentTypeFactory factory) + : this(contentType.Key, contentType.Id, contentType.Alias, contentType.GetItemType(), contentType.CompositionAliases(), contentType.Variations, contentType.IsElement) { - EnsureMemberProperties(propertyTypes, factory); - } + var propertyTypes = contentType.CompositionPropertyTypes + .Select(x => factory.CreatePropertyType(this, x)) + .ToList(); - _propertyTypes = propertyTypes.ToArray(); + if (ItemType == PublishedItemType.Member) + { + EnsureMemberProperties(propertyTypes, factory); + } - InitializeIndexes(); - } + _propertyTypes = propertyTypes.ToArray(); - /// - /// This constructor is for tests and is not intended to be used directly from application code. - /// - /// - /// Values are assumed to be consistent and are not checked. - /// - public PublishedContentType( - Guid key, - int id, - string alias, - PublishedItemType itemType, - IEnumerable compositionAliases, - IEnumerable propertyTypes, - ContentVariation variations, - bool isElement = false) - : this(key, id, alias, itemType, compositionAliases, variations, isElement) - { - PublishedPropertyType[] propertyTypesA = propertyTypes.ToArray(); - foreach (PublishedPropertyType propertyType in propertyTypesA) - { - propertyType.ContentType = this; + InitializeIndexes(); } - _propertyTypes = propertyTypesA; + /// + /// This constructor is for tests and is not intended to be used directly from application code. + /// + /// + /// Values are assumed to be consistent and are not checked. + /// + public PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations, bool isElement = false) + : this(key, id, alias, itemType, compositionAliases, variations, isElement) + { + PublishedPropertyType[] propertyTypesA = propertyTypes.ToArray(); + foreach (PublishedPropertyType propertyType in propertyTypesA) + { + propertyType.ContentType = this; + } - InitializeIndexes(); - } + _propertyTypes = propertyTypesA; - [Obsolete("Use the overload specifying a key instead")] - public PublishedContentType( - int id, - string alias, - PublishedItemType itemType, - IEnumerable compositionAliases, - IEnumerable propertyTypes, - ContentVariation variations, - bool isElement = false) - : this(Guid.Empty, id, alias, itemType, compositionAliases, variations, isElement) - { - PublishedPropertyType[] propertyTypesA = propertyTypes.ToArray(); - foreach (PublishedPropertyType propertyType in propertyTypesA) - { - propertyType.ContentType = this; + InitializeIndexes(); } - _propertyTypes = propertyTypesA; - - InitializeIndexes(); - } + [Obsolete("Use the overload specifying a key instead")] + public PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, IEnumerable propertyTypes, ContentVariation variations, bool isElement = false) + : this (Guid.Empty, id, alias, itemType, compositionAliases, variations, isElement) + { + PublishedPropertyType[] propertyTypesA = propertyTypes.ToArray(); + foreach (PublishedPropertyType propertyType in propertyTypesA) + { + propertyType.ContentType = this; + } - /// - /// This constructor is for tests and is not intended to be used directly from application code. - /// - /// - /// Values are assumed to be consistent and are not checked. - /// - public PublishedContentType( - Guid key, - int id, - string alias, - PublishedItemType itemType, - IEnumerable compositionAliases, - Func> propertyTypes, - ContentVariation variations, - bool isElement = false) - : this(key, id, alias, itemType, compositionAliases, variations, isElement) - { - _propertyTypes = propertyTypes(this).ToArray(); + _propertyTypes = propertyTypesA; - InitializeIndexes(); - } + InitializeIndexes(); + } - [Obsolete("Use the overload specifying a key instead")] - public PublishedContentType( - int id, - string alias, - PublishedItemType itemType, - IEnumerable compositionAliases, - Func> propertyTypes, - ContentVariation variations, - bool isElement = false) - : this(Guid.Empty, id, alias, itemType, compositionAliases, variations, isElement) - { - _propertyTypes = propertyTypes(this).ToArray(); + /// + /// This constructor is for tests and is not intended to be used directly from application code. + /// + /// + /// Values are assumed to be consistent and are not checked. + /// + public PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, Func> propertyTypes, ContentVariation variations, bool isElement = false) + : this(key, id, alias, itemType, compositionAliases, variations, isElement) + { + _propertyTypes = propertyTypes(this).ToArray(); - InitializeIndexes(); - } + InitializeIndexes(); + } - private PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, ContentVariation variations, bool isElement) - { - Key = key; - Id = id; - Alias = alias; - ItemType = itemType; - CompositionAliases = new HashSet(compositionAliases, StringComparer.InvariantCultureIgnoreCase); - Variations = variations; - IsElement = isElement; - } + [Obsolete("Use the overload specifying a key instead")] + public PublishedContentType(int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, Func> propertyTypes, ContentVariation variations, bool isElement = false) + : this(Guid.Empty, id, alias, itemType, compositionAliases, variations, isElement) + { + _propertyTypes = propertyTypes(this).ToArray(); - #region Content type + InitializeIndexes(); + } - /// - public Guid Key { get; } + private PublishedContentType(Guid key, int id, string alias, PublishedItemType itemType, IEnumerable compositionAliases, ContentVariation variations, bool isElement) + { + Key = key; + Id = id; + Alias = alias; + ItemType = itemType; + CompositionAliases = new HashSet(compositionAliases, StringComparer.InvariantCultureIgnoreCase); + Variations = variations; + IsElement = isElement; + } - private void InitializeIndexes() - { - if (_propertyTypes is not null) + private void InitializeIndexes() { - for (var i = 0; i < _propertyTypes.Length; i++) + if (_propertyTypes is not null) { - IPublishedPropertyType propertyType = _propertyTypes[i]; - if (propertyType.Alias is not null) + for (var i = 0; i < _propertyTypes.Length; i++) { - _indexes[propertyType.Alias] = i; - _indexes[propertyType.Alias.ToLowerInvariant()] = i; + IPublishedPropertyType propertyType = _propertyTypes[i]; + if (propertyType.Alias is not null) + { + _indexes[propertyType.Alias] = i; + _indexes[propertyType.Alias.ToLowerInvariant()] = i; + } } } } - } - - // Members have properties such as IMember LastLoginDate which are plain C# properties and not content - // properties; they are exposed as pseudo content properties, as long as a content property with the - // same alias does not exist already. - private void EnsureMemberProperties( - List propertyTypes, - IPublishedContentTypeFactory factory) - { - var aliases = new HashSet(propertyTypes.Select(x => x.Alias), StringComparer.OrdinalIgnoreCase); - foreach ((string alias, int dataTypeId) in BuiltinMemberProperties) + // Members have properties such as IMember LastLoginDate which are plain C# properties and not content + // properties; they are exposed as pseudo content properties, as long as a content property with the + // same alias does not exist already. + private void EnsureMemberProperties(List propertyTypes, IPublishedContentTypeFactory factory) { - if (aliases.Contains(alias)) + var aliases = new HashSet(propertyTypes.Select(x => x.Alias), StringComparer.OrdinalIgnoreCase); + + foreach (var (alias, dataTypeId) in _builtinMemberProperties) { - continue; - } + if (aliases.Contains(alias)) + { + continue; + } - propertyTypes.Add(factory.CreateCorePropertyType(this, alias, dataTypeId, ContentVariation.Nothing)); + propertyTypes.Add(factory.CreateCorePropertyType(this, alias, dataTypeId, ContentVariation.Nothing)); + } } - } - /// - public int Id { get; } + // TODO: this list somehow also exists in constants, see memberTypeRepository => remove duplicate! + private static readonly Dictionary _builtinMemberProperties = new Dictionary + { + { nameof(IMember.Email), Constants.DataTypes.Textbox }, + { nameof(IMember.Username), Constants.DataTypes.Textbox }, + { nameof(IMember.Comments), Constants.DataTypes.Textbox }, + { nameof(IMember.IsApproved), Constants.DataTypes.Boolean }, + { nameof(IMember.IsLockedOut), Constants.DataTypes.Boolean }, + { nameof(IMember.LastLockoutDate), Constants.DataTypes.LabelDateTime }, + { nameof(IMember.CreateDate), Constants.DataTypes.LabelDateTime }, + { nameof(IMember.LastLoginDate), Constants.DataTypes.LabelDateTime }, + { nameof(IMember.LastPasswordChangeDate), Constants.DataTypes.LabelDateTime }, + }; + + #region Content type - /// - public string Alias { get; } + /// + public Guid Key { get; } - /// - public PublishedItemType ItemType { get; } + /// + public int Id { get; } - /// - public HashSet CompositionAliases { get; } + /// + public string Alias { get; } - /// - public ContentVariation Variations { get; } + /// + public PublishedItemType ItemType { get; } - #endregion + /// + public HashSet CompositionAliases { get; } - #region Properties + /// + public ContentVariation Variations { get; } - /// - public IEnumerable PropertyTypes => _propertyTypes; + #endregion - /// - public bool IsElement { get; } + #region Properties - /// - public int GetPropertyIndex(string alias) - { - if (_indexes.TryGetValue(alias, out var index)) + /// + public IEnumerable PropertyTypes => _propertyTypes; + + /// + public int GetPropertyIndex(string alias) { - return index; // fastest + if (_indexes.TryGetValue(alias, out var index)) + { + return index; // fastest + } + + if (_indexes.TryGetValue(alias.ToLowerInvariant(), out index)) + { + return index; // slower + } + + return -1; } - if (_indexes.TryGetValue(alias.ToLowerInvariant(), out index)) + // virtual for unit tests + // TODO: explain why + /// + public virtual IPublishedPropertyType? GetPropertyType(string alias) { - return index; // slower + var index = GetPropertyIndex(alias); + return GetPropertyType(index); } - return -1; - } + // virtual for unit tests + // TODO: explain why + /// + public virtual IPublishedPropertyType? GetPropertyType(int index) + { + return index >= 0 && _propertyTypes is not null && index < _propertyTypes.Length ? _propertyTypes[index] : null; + } - // virtual for unit tests - // TODO: explain why + /// + public bool IsElement { get; } - /// - public virtual IPublishedPropertyType? GetPropertyType(string alias) - { - var index = GetPropertyIndex(alias); - return GetPropertyType(index); + #endregion } - - // virtual for unit tests - // TODO: explain why - - /// - public virtual IPublishedPropertyType? GetPropertyType(int index) => - index >= 0 && _propertyTypes is not null && index < _propertyTypes.Length ? _propertyTypes[index] : null; - - #endregion } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index c9cf54c1399b..b5e9a94e1349 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -34,15 +34,9 @@ protected PublishedContentWrapped(IPublishedContent content, IPublishedValueFall _publishedValueFallback = publishedValueFallback; } - #region ContentType - /// public virtual IPublishedContentType ContentType => _content.ContentType; - #endregion - - #region PublishedElement - /// public Guid Key => _content.Key; @@ -95,10 +89,6 @@ protected PublishedContentWrapped(IPublishedContent content, IPublishedValueFall /// public virtual PublishedItemType ItemType => _content.ItemType; - #endregion - - #region Tree - /// public virtual IPublishedContent? Parent => _content.Parent; @@ -114,15 +104,9 @@ protected PublishedContentWrapped(IPublishedContent content, IPublishedValueFall /// public virtual IEnumerable? ChildrenForAllCultures => _content.ChildrenForAllCultures; - #endregion - - #region Properties - /// public virtual IEnumerable Properties => _content.Properties; /// public virtual IPublishedProperty? GetProperty(string alias) => _content.GetProperty(alias); - - #endregion } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs index 7e69d0970d79..4bc4b02f6823 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs @@ -1,337 +1,314 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Xml.Linq; using System.Xml.XPath; using Umbraco.Cms.Core.PropertyEditors; -namespace Umbraco.Cms.Core.Models.PublishedContent; - -/// -/// Represents a published property type. -/// -/// -/// Instances of the class are immutable, ie -/// if the property type changes, then a new class needs to be created. -/// -[DebuggerDisplay("{Alias} ({EditorAlias})")] -public class PublishedPropertyType : IPublishedPropertyType +namespace Umbraco.Cms.Core.Models.PublishedContent { - private readonly object _locker = new(); - private readonly PropertyValueConverterCollection _propertyValueConverters; - private readonly IPublishedModelFactory _publishedModelFactory; - private PropertyCacheLevel _cacheLevel; - private Type? _clrType; - private IPropertyValueConverter? _converter; - private volatile bool _initialized; - - private Type? _modelClrType; - - #region Constructors - - /// - /// Initialize a new instance of the class with a property type. - /// - /// - /// The new published property type belongs to the published content type. - /// - public PublishedPropertyType( - IPublishedContentType contentType, - IPropertyType propertyType, - PropertyValueConverterCollection propertyValueConverters, - IPublishedModelFactory publishedModelFactory, - IPublishedContentTypeFactory factory) - : this(propertyType.Alias, propertyType.DataTypeId, true, propertyType.Variations, propertyValueConverters, publishedModelFactory, factory) => - ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - - /// - /// This constructor is for tests and is not intended to be used directly from application code. - /// - /// - /// Values are assumed to be consisted and are not checked. - /// The new published property type belongs to the published content type. - /// - public PublishedPropertyType( - IPublishedContentType contentType, - string propertyTypeAlias, - int dataTypeId, - bool isUserProperty, - ContentVariation variations, - PropertyValueConverterCollection propertyValueConverters, - IPublishedModelFactory publishedModelFactory, - IPublishedContentTypeFactory factory) - : this(propertyTypeAlias, dataTypeId, isUserProperty, variations, propertyValueConverters, publishedModelFactory, factory) => - ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); - /// - /// This constructor is for tests and is not intended to be used directly from application code. + /// Represents a published property type. /// - /// - /// Values are assumed to be consistent and are not checked. - /// The new published property type does not belong to a published content type. - /// - public PublishedPropertyType( - string propertyTypeAlias, - int dataTypeId, - bool isUserProperty, - ContentVariation variations, - PropertyValueConverterCollection propertyValueConverters, - IPublishedModelFactory publishedModelFactory, - IPublishedContentTypeFactory factory) + /// Instances of the class are immutable, ie + /// if the property type changes, then a new class needs to be created. + [DebuggerDisplay("{Alias} ({EditorAlias})")] + public class PublishedPropertyType : IPublishedPropertyType { - _publishedModelFactory = - publishedModelFactory ?? throw new ArgumentNullException(nameof(publishedModelFactory)); - _propertyValueConverters = - propertyValueConverters ?? throw new ArgumentNullException(nameof(propertyValueConverters)); - - Alias = propertyTypeAlias; - - IsUserProperty = isUserProperty; - Variations = variations; - - DataType = factory.GetDataType(dataTypeId); - } - - #endregion - - #region Property type - - /// - public IPublishedContentType? - ContentType - { get; internal set; } // internally set by PublishedContentType constructor + private readonly IPublishedModelFactory _publishedModelFactory; + private readonly PropertyValueConverterCollection _propertyValueConverters; + private readonly object _locker = new object(); + private volatile bool _initialized; + private IPropertyValueConverter? _converter; + private PropertyCacheLevel _cacheLevel; + + private Type? _modelClrType; + private Type? _clrType; + + #region Constructors + + /// + /// Initialize a new instance of the class with a property type. + /// + /// + /// The new published property type belongs to the published content type. + /// + public PublishedPropertyType(IPublishedContentType contentType, IPropertyType propertyType, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) + : this(propertyType.Alias, propertyType.DataTypeId, true, propertyType.Variations, propertyValueConverters, publishedModelFactory, factory) + { + ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + } - /// - public PublishedDataType DataType { get; } + /// + /// This constructor is for tests and is not intended to be used directly from application code. + /// + /// + /// Values are assumed to be consisted and are not checked. + /// The new published property type belongs to the published content type. + /// + public PublishedPropertyType(IPublishedContentType contentType, string propertyTypeAlias, int dataTypeId, bool isUserProperty, ContentVariation variations, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) + : this(propertyTypeAlias, dataTypeId, isUserProperty, variations, propertyValueConverters, publishedModelFactory, factory) + { + ContentType = contentType ?? throw new ArgumentNullException(nameof(contentType)); + } - /// - public string Alias { get; } + /// + /// This constructor is for tests and is not intended to be used directly from application code. + /// + /// + /// Values are assumed to be consistent and are not checked. + /// The new published property type does not belong to a published content type. + /// + public PublishedPropertyType(string propertyTypeAlias, int dataTypeId, bool isUserProperty, ContentVariation variations, PropertyValueConverterCollection propertyValueConverters, IPublishedModelFactory publishedModelFactory, IPublishedContentTypeFactory factory) + { + _publishedModelFactory = publishedModelFactory ?? throw new ArgumentNullException(nameof(publishedModelFactory)); + _propertyValueConverters = propertyValueConverters ?? throw new ArgumentNullException(nameof(propertyValueConverters)); - /// - public string EditorAlias => DataType.EditorAlias; + Alias = propertyTypeAlias; - /// - public bool IsUserProperty { get; } + IsUserProperty = isUserProperty; + Variations = variations; - /// - public ContentVariation Variations { get; } + DataType = factory.GetDataType(dataTypeId); + } - /// - public PropertyCacheLevel CacheLevel - { - get - { - if (!_initialized) - { - Initialize(); - } + #endregion - return _cacheLevel; - } - } + #region Property type - /// - public Type ModelClrType - { - get - { - if (!_initialized) - { - Initialize(); - } + /// + public IPublishedContentType? ContentType { get; internal set; } // internally set by PublishedContentType constructor - return _modelClrType!; - } - } + /// + public PublishedDataType DataType { get; } - /// - public bool? IsValue(object? value, PropertyValueLevel level) - { - if (!_initialized) - { - Initialize(); - } + /// + public string Alias { get; } - // if we have a converter, use the converter - if (_converter != null) - { - return _converter.IsValue(value, level); - } + /// + public string EditorAlias => DataType.EditorAlias; - // otherwise use the old magic null & string comparisons - return value != null && (!(value is string) || string.IsNullOrWhiteSpace((string)value) == false); - } + /// + public bool IsUserProperty { get; } - #endregion + /// + public ContentVariation Variations { get; } - #region Converters + #endregion - private void Initialize() - { - if (_initialized) - { - return; - } + #region Converters - lock (_locker) + private void Initialize() { if (_initialized) { return; } - InitializeLocked(); - _initialized = true; - } - } + lock (_locker) + { + if (_initialized) + { + return; + } - private void InitializeLocked() - { - _converter = null; - var isdefault = false; + InitializeLocked(); + _initialized = true; + } + } - foreach (IPropertyValueConverter converter in _propertyValueConverters) + private void InitializeLocked() { - if (!converter.IsConverter(this)) - { - continue; - } + _converter = null; + var isdefault = false; - if (_converter == null) + foreach (IPropertyValueConverter converter in _propertyValueConverters) { - _converter = converter; - isdefault = _propertyValueConverters.IsDefault(converter); - continue; - } + if (!converter.IsConverter(this)) + { + continue; + } - if (isdefault) - { - if (_propertyValueConverters.IsDefault(converter)) + if (_converter == null) + { + _converter = converter; + isdefault = _propertyValueConverters.IsDefault(converter); + continue; + } + + if (isdefault) { - // previous was default, and got another default - if (_propertyValueConverters.Shadows(_converter, converter)) + if (_propertyValueConverters.IsDefault(converter)) { - // previous shadows, ignore + // previous was default, and got another default + if (_propertyValueConverters.Shadows(_converter, converter)) + { + // previous shadows, ignore + } + else if (_propertyValueConverters.Shadows(converter, _converter)) + { + // shadows previous, replace + _converter = converter; + } + else + { + // no shadow - bad + throw new InvalidOperationException(string.Format( + "Type '{2}' cannot be an IPropertyValueConverter" + + " for property '{1}' of content type '{0}' because type '{3}' has already been detected as a converter" + + " for that property, and only one converter can exist for a property.", + ContentType?.Alias, + Alias, + converter.GetType().FullName, + _converter.GetType().FullName)); + } } - else if (_propertyValueConverters.Shadows(converter, _converter)) + else { - // shadows previous, replace + // previous was default, replaced by non-default _converter = converter; + isdefault = false; + } + } + else + { + if (_propertyValueConverters.IsDefault(converter)) + { + // previous was non-default, ignore default } else { - // no shadow - bad + // previous was non-default, and got another non-default - bad throw new InvalidOperationException(string.Format( "Type '{2}' cannot be an IPropertyValueConverter" - + " for property '{1}' of content type '{0}' because type '{3}' has already been detected as a converter" - + " for that property, and only one converter can exist for a property.", + + " for property '{1}' of content type '{0}' because type '{3}' has already been detected as a converter" + + " for that property, and only one converter can exist for a property.", ContentType?.Alias, Alias, converter.GetType().FullName, _converter.GetType().FullName)); } } - else - { - // previous was default, replaced by non-default - _converter = converter; - isdefault = false; - } } - else + + _cacheLevel = _converter?.GetPropertyCacheLevel(this) ?? PropertyCacheLevel.Snapshot; + _modelClrType = _converter == null ? typeof (object) : _converter.GetPropertyValueType(this); + } + + /// + public bool? IsValue(object? value, PropertyValueLevel level) + { + if (!_initialized) { - if (_propertyValueConverters.IsDefault(converter)) - { - // previous was non-default, ignore default - } - else - { - // previous was non-default, and got another non-default - bad - throw new InvalidOperationException(string.Format( - "Type '{2}' cannot be an IPropertyValueConverter" - + " for property '{1}' of content type '{0}' because type '{3}' has already been detected as a converter" - + " for that property, and only one converter can exist for a property.", - ContentType?.Alias, - Alias, - converter.GetType().FullName, - _converter.GetType().FullName)); - } + Initialize(); } - } - _cacheLevel = _converter?.GetPropertyCacheLevel(this) ?? PropertyCacheLevel.Snapshot; - _modelClrType = _converter == null ? typeof(object) : _converter.GetPropertyValueType(this); - } + // if we have a converter, use the converter + if (_converter != null) + { + return _converter.IsValue(value, level); + } - /// - public object? ConvertSourceToInter(IPublishedElement owner, object? source, bool preview) - { - if (!_initialized) - { - Initialize(); + // otherwise use the old magic null & string comparisons + return value != null && (!(value is string) || string.IsNullOrWhiteSpace((string) value) == false); } - // use the converter if any, else just return the source value - return _converter != null - ? _converter.ConvertSourceToIntermediate(owner, this, source, preview) - : source; - } - - /// - public object? ConvertInterToObject(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) - { - if (!_initialized) + /// + public PropertyCacheLevel CacheLevel { - Initialize(); - } + get + { + if (!_initialized) + { + Initialize(); + } - // use the converter if any, else just return the inter value - return _converter != null - ? _converter.ConvertIntermediateToObject(owner, this, referenceCacheLevel, inter, preview) - : inter; - } + return _cacheLevel; + } + } - /// - public object? ConvertInterToXPath(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) - { - if (!_initialized) + /// + public object? ConvertSourceToInter(IPublishedElement owner, object? source, bool preview) { - Initialize(); + if (!_initialized) + { + Initialize(); + } + + // use the converter if any, else just return the source value + return _converter != null + ? _converter.ConvertSourceToIntermediate(owner, this, source, preview) + : source; } - // use the converter if any - if (_converter != null) + /// + public object? ConvertInterToObject(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) { - return _converter.ConvertIntermediateToXPath(owner, this, referenceCacheLevel, inter, preview); + if (!_initialized) + { + Initialize(); + } + + // use the converter if any, else just return the inter value + return _converter != null + ? _converter.ConvertIntermediateToObject(owner, this, referenceCacheLevel, inter, preview) + : inter; } - // else just return the inter value as a string or an XPathNavigator - if (inter == null) + /// + public object? ConvertInterToXPath(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) { - return null; + if (!_initialized) + { + Initialize(); + } + + // use the converter if any + if (_converter != null) + { + return _converter.ConvertIntermediateToXPath(owner, this, referenceCacheLevel, inter, preview); + } + + // else just return the inter value as a string or an XPathNavigator + if (inter == null) + { + return null; + } + + if (inter is XElement xElement) + { + return xElement.CreateNavigator(); + } + + return inter.ToString()?.Trim(); } - if (inter is XElement xElement) + /// + public Type ModelClrType { - return xElement.CreateNavigator(); - } + get + { + if (!_initialized) + { + Initialize(); + } - return inter.ToString()?.Trim(); - } + return _modelClrType!; + } + } - /// - public Type? ClrType - { - get + /// + public Type? ClrType { - if (!_initialized) + get { - Initialize(); - } + if (!_initialized) + { + Initialize(); + } - return _clrType ?? (_modelClrType is not null - ? _clrType = _publishedModelFactory.MapModelType(_modelClrType) - : null); + return _clrType ?? (_modelClrType is not null ? _clrType = _publishedModelFactory.MapModelType(_modelClrType) : null); + } } - } - #endregion + #endregion + } } diff --git a/src/Umbraco.Core/Notifications/PublicAccessEntryDeletedNotification.cs b/src/Umbraco.Core/Notifications/PublicAccessEntryDeletedNotification.cs index 2bf6062b9028..a90601cf50e7 100644 --- a/src/Umbraco.Core/Notifications/PublicAccessEntryDeletedNotification.cs +++ b/src/Umbraco.Core/Notifications/PublicAccessEntryDeletedNotification.cs @@ -9,7 +9,7 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class PublicAccessEntryDeletedNotification : DeletedNotification { public PublicAccessEntryDeletedNotification(PublicAccessEntry target, EventMessages messages) - : base(messageObject, messageType) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/TemplateCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/TemplateCacheRefresherNotification.cs index 296d8538b2a9..a8b119390f0b 100644 --- a/src/Umbraco.Core/Notifications/TemplateCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/TemplateCacheRefresherNotification.cs @@ -1,13 +1,11 @@ -using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Notifications; public class TemplateCacheRefresherNotification : CacheRefresherNotification { public TemplateCacheRefresherNotification(object messageObject, MessageType messageType) - : base( - messageObject, - messageType) + : base(messageObject, messageType) { } } diff --git a/src/Umbraco.Core/Notifications/UserCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/UserCacheRefresherNotification.cs index 35daf62b2cb2..589a2df68250 100644 --- a/src/Umbraco.Core/Notifications/UserCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/UserCacheRefresherNotification.cs @@ -1,13 +1,11 @@ -using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Notifications; public class UserCacheRefresherNotification : CacheRefresherNotification { public UserCacheRefresherNotification(object messageObject, MessageType messageType) - : base( - messageObject, - messageType) + : base(messageObject, messageType) { } } diff --git a/src/Umbraco.Core/Notifications/UserGroupCacheRefresherNotification.cs b/src/Umbraco.Core/Notifications/UserGroupCacheRefresherNotification.cs index 946a8a4b0470..d8e519ee9daa 100644 --- a/src/Umbraco.Core/Notifications/UserGroupCacheRefresherNotification.cs +++ b/src/Umbraco.Core/Notifications/UserGroupCacheRefresherNotification.cs @@ -1,13 +1,11 @@ -using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Core.Sync; namespace Umbraco.Cms.Core.Notifications; public class UserGroupCacheRefresherNotification : CacheRefresherNotification { public UserGroupCacheRefresherNotification(object messageObject, MessageType messageType) - : base( - messageObject, - messageType) + : base(messageObject, messageType) { } } diff --git a/src/Umbraco.Core/Notifications/UserGroupDeletingNotification.cs b/src/Umbraco.Core/Notifications/UserGroupDeletingNotification.cs index 729d35152d67..aea73393abce 100644 --- a/src/Umbraco.Core/Notifications/UserGroupDeletingNotification.cs +++ b/src/Umbraco.Core/Notifications/UserGroupDeletingNotification.cs @@ -14,9 +14,7 @@ public UserGroupDeletingNotification(IUserGroup target, EventMessages messages) } public UserGroupDeletingNotification(IEnumerable target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/UserGroupWithUsersSavedNotification.cs b/src/Umbraco.Core/Notifications/UserGroupWithUsersSavedNotification.cs index 31565db85e0c..399d1946908f 100644 --- a/src/Umbraco.Core/Notifications/UserGroupWithUsersSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserGroupWithUsersSavedNotification.cs @@ -8,15 +8,12 @@ namespace Umbraco.Cms.Core.Notifications; public sealed class UserGroupWithUsersSavedNotification : SavedNotification { public UserGroupWithUsersSavedNotification(UserGroupWithUsers target, EventMessages messages) - : base( - target, - messages) + : base(target, messages) { } public UserGroupWithUsersSavedNotification(IEnumerable target, EventMessages messages) - : base( - target, messages) + : base(target, messages) { } } diff --git a/src/Umbraco.Core/Notifications/UserLockedNotification.cs b/src/Umbraco.Core/Notifications/UserLockedNotification.cs index 9016ccb938f4..81fc798f639f 100644 --- a/src/Umbraco.Core/Notifications/UserLockedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserLockedNotification.cs @@ -3,10 +3,7 @@ namespace Umbraco.Cms.Core.Notifications; public class UserLockedNotification : UserNotification { public UserLockedNotification(string ipAddress, string? affectedUserId, string performingUserId) - : base( - ipAddress, - affectedUserId, - performingUserId) + : base(ipAddress, affectedUserId, performingUserId) { } } diff --git a/src/Umbraco.Core/Notifications/UserLoginSuccessNotification.cs b/src/Umbraco.Core/Notifications/UserLoginSuccessNotification.cs index e10d221de6a2..5b20ca48ef86 100644 --- a/src/Umbraco.Core/Notifications/UserLoginSuccessNotification.cs +++ b/src/Umbraco.Core/Notifications/UserLoginSuccessNotification.cs @@ -3,8 +3,7 @@ namespace Umbraco.Cms.Core.Notifications; public class UserLoginSuccessNotification : UserNotification { public UserLoginSuccessNotification(string ipAddress, string affectedUserId, string performingUserId) - : base( - ipAddress, affectedUserId, performingUserId) + : base(ipAddress, affectedUserId, performingUserId) { } } diff --git a/src/Umbraco.Core/Notifications/UserLogoutSuccessNotification.cs b/src/Umbraco.Core/Notifications/UserLogoutSuccessNotification.cs index ecc764b117e2..c93d42accf97 100644 --- a/src/Umbraco.Core/Notifications/UserLogoutSuccessNotification.cs +++ b/src/Umbraco.Core/Notifications/UserLogoutSuccessNotification.cs @@ -3,8 +3,7 @@ namespace Umbraco.Cms.Core.Notifications; public class UserLogoutSuccessNotification : UserNotification { public UserLogoutSuccessNotification(string ipAddress, string? affectedUserId, string performingUserId) - : base( - ipAddress, affectedUserId, performingUserId) + : base(ipAddress, affectedUserId, performingUserId) { } diff --git a/src/Umbraco.Core/Notifications/UserPasswordResetNotification.cs b/src/Umbraco.Core/Notifications/UserPasswordResetNotification.cs index 47edf85bc353..8b23b5aa4f05 100644 --- a/src/Umbraco.Core/Notifications/UserPasswordResetNotification.cs +++ b/src/Umbraco.Core/Notifications/UserPasswordResetNotification.cs @@ -3,8 +3,7 @@ namespace Umbraco.Cms.Core.Notifications; public class UserPasswordResetNotification : UserNotification { public UserPasswordResetNotification(string ipAddress, string affectedUserId, string performingUserId) - : base( - ipAddress, affectedUserId, performingUserId) + : base(ipAddress, affectedUserId, performingUserId) { } } diff --git a/src/Umbraco.Core/Notifications/UserUnlockedNotification.cs b/src/Umbraco.Core/Notifications/UserUnlockedNotification.cs index 24ffe9f058b7..7883595733c6 100644 --- a/src/Umbraco.Core/Notifications/UserUnlockedNotification.cs +++ b/src/Umbraco.Core/Notifications/UserUnlockedNotification.cs @@ -3,10 +3,7 @@ namespace Umbraco.Cms.Core.Notifications; public class UserUnlockedNotification : UserNotification { public UserUnlockedNotification(string ipAddress, string affectedUserId, string performingUserId) - : base( - ipAddress, - affectedUserId, - performingUserId) + : base(ipAddress, affectedUserId, performingUserId) { } } diff --git a/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs b/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs index 01221480c4e5..99a18dbcf9fb 100644 --- a/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs +++ b/src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs @@ -11,7 +11,7 @@ public class PackageDefinitionXmlParser private static readonly IList EmptyStringList = new List(); private static readonly IList EmptyGuidUdiList = new List(); - public PackageDefinition? ToPackageDefinition(XElement? xml) + public PackageDefinition? ToPackageDefinition(XElement xml) { if (xml == null) { diff --git a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs index a058a1b605d3..8077a80dc124 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs @@ -26,8 +26,7 @@ public interface IRelationRepository : IReadWriteQueryRepository /// /// /// - /// A list of relation types to match for deletion, if none are specified then all relations for this parent id are - /// deleted + /// A list of relation types to match for deletion, if none are specified then all relations for this parent id are deleted. /// void DeleteByParent(int parentId, params string[] relationTypeAliases); diff --git a/src/Umbraco.Core/PropertyEditors/DataEditorCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/DataEditorCollectionBuilder.cs index 162c2cc5866c..36e70f2738f2 100644 --- a/src/Umbraco.Core/PropertyEditors/DataEditorCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/DataEditorCollectionBuilder.cs @@ -1,10 +1,9 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.PropertyEditors; public class - DataEditorCollectionBuilder : LazyCollectionBuilderBase + DataEditorCollectionBuilder : LazyCollectionBuilderBase { protected override DataEditorCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs index d1b5027f8a5b..f2868276537e 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs @@ -1,9 +1,8 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.PropertyEditors; -public class DataValueReferenceFactoryCollectionBuilder : OrderedCollectionBuilderBase< - DataValueReferenceFactoryCollectionBuilder, DataValueReferenceFactoryCollection, IDataValueReferenceFactory> +public class DataValueReferenceFactoryCollectionBuilder : OrderedCollectionBuilderBase { protected override DataValueReferenceFactoryCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerConfigurationEditor.cs index 2df1a5c11c73..487034a6b19b 100644 --- a/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/EyeDropperColorPickerConfigurationEditor.cs @@ -14,9 +14,8 @@ public EyeDropperColorPickerConfigurationEditor( } /// - public override Dictionary - ToConfigurationEditor(EyeDropperColorPickerConfiguration? configuration) => - new Dictionary + public override Dictionary ToConfigurationEditor(EyeDropperColorPickerConfiguration? configuration) => + new() { { "showAlpha", configuration?.ShowAlpha ?? false }, { "showPalette", configuration?.ShowPalette ?? false }, }; diff --git a/src/Umbraco.Core/PropertyEditors/LabelConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/LabelConfigurationEditor.cs index 47cda5dcb8df..cb5a531f65a3 100644 --- a/src/Umbraco.Core/PropertyEditors/LabelConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/LabelConfigurationEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; @@ -21,8 +21,7 @@ public LabelConfigurationEditor(IIOHelper ioHelper) } public LabelConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) - : base( - ioHelper, editorConfigurationParser) + : base(ioHelper, editorConfigurationParser) { } diff --git a/src/Umbraco.Core/PropertyEditors/ListViewConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/ListViewConfigurationEditor.cs index 5c6bc0bc355c..8ecab6d751fc 100644 --- a/src/Umbraco.Core/PropertyEditors/ListViewConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ListViewConfigurationEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; @@ -21,8 +21,7 @@ public ListViewConfigurationEditor(IIOHelper ioHelper) } public ListViewConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) - : base( - ioHelper, editorConfigurationParser) + : base(ioHelper, editorConfigurationParser) { } } diff --git a/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs index 80dcfb8c85c6..044c7f2c0c9a 100644 --- a/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/ManifestValueValidatorCollectionBuilder.cs @@ -2,8 +2,7 @@ namespace Umbraco.Cms.Core.PropertyEditors; -public class ManifestValueValidatorCollectionBuilder : SetCollectionBuilderBase +public class ManifestValueValidatorCollectionBuilder : SetCollectionBuilderBase { protected override ManifestValueValidatorCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/PropertyEditors/MarkdownConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/MarkdownConfigurationEditor.cs index 6b626ec01f5e..032bafd12b5c 100644 --- a/src/Umbraco.Core/PropertyEditors/MarkdownConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/MarkdownConfigurationEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Umbraco.Cms.Core.IO; @@ -12,8 +12,7 @@ namespace Umbraco.Cms.Core.PropertyEditors; internal class MarkdownConfigurationEditor : ConfigurationEditor { public MarkdownConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) - : base( - ioHelper, editorConfigurationParser) + : base(ioHelper, editorConfigurationParser) { } } diff --git a/src/Umbraco.Core/PropertyEditors/MediaUrlGeneratorCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/MediaUrlGeneratorCollectionBuilder.cs index 2f4cf4b2cda9..0c9bf6070f4c 100644 --- a/src/Umbraco.Core/PropertyEditors/MediaUrlGeneratorCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/MediaUrlGeneratorCollectionBuilder.cs @@ -3,8 +3,7 @@ namespace Umbraco.Cms.Core.PropertyEditors; -public class MediaUrlGeneratorCollectionBuilder : SetCollectionBuilderBase +public class MediaUrlGeneratorCollectionBuilder : SetCollectionBuilderBase { protected override MediaUrlGeneratorCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollectionBuilder.cs index 7746e05647ca..6d1e329c7ef9 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollectionBuilder.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterCollectionBuilder.cs @@ -1,9 +1,8 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.PropertyEditors; -public class PropertyValueConverterCollectionBuilder : OrderedCollectionBuilderBase< - PropertyValueConverterCollectionBuilder, PropertyValueConverterCollection, IPropertyValueConverter> +public class PropertyValueConverterCollectionBuilder : OrderedCollectionBuilderBase { protected override PropertyValueConverterCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/PropertyEditors/RichTextConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/RichTextConfigurationEditor.cs index 3bf11c1bf451..4e0b5b557d19 100644 --- a/src/Umbraco.Core/PropertyEditors/RichTextConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/RichTextConfigurationEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; @@ -21,8 +21,7 @@ public RichTextConfigurationEditor(IIOHelper ioHelper) } public RichTextConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) - : base( - ioHelper, editorConfigurationParser) + : base(ioHelper, editorConfigurationParser) { } } diff --git a/src/Umbraco.Core/PropertyEditors/TextAreaConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/TextAreaConfigurationEditor.cs index d1a1fb1a42e8..7ae52825fba7 100644 --- a/src/Umbraco.Core/PropertyEditors/TextAreaConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/TextAreaConfigurationEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; @@ -21,8 +21,7 @@ public TextAreaConfigurationEditor(IIOHelper ioHelper) } public TextAreaConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) - : base( - ioHelper, editorConfigurationParser) + : base(ioHelper, editorConfigurationParser) { } } diff --git a/src/Umbraco.Core/PropertyEditors/TextboxConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/TextboxConfigurationEditor.cs index f0a776334b8f..69d39a44aba8 100644 --- a/src/Umbraco.Core/PropertyEditors/TextboxConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/TextboxConfigurationEditor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using Microsoft.Extensions.DependencyInjection; @@ -21,8 +21,7 @@ public TextboxConfigurationEditor(IIOHelper ioHelper) } public TextboxConfigurationEditor(IIOHelper ioHelper, IEditorConfigurationParser editorConfigurationParser) - : base( - ioHelper, editorConfigurationParser) + : base(ioHelper, editorConfigurationParser) { } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs index a4df25df886b..f0be2754369a 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/IntegerValueConverter.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; @@ -20,6 +20,5 @@ public override object ConvertSourceToIntermediate( IPublishedPropertyType propertyType, object? source, bool preview) => - source.TryConvertTo() - .Result; + source.TryConvertTo().Result; } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs index d3815d7f6f72..3d631afead2e 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs @@ -1,4 +1,4 @@ -using System.Xml; +using System.Xml; using Umbraco.Cms.Core.Models.PublishedContent; namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters; @@ -46,7 +46,7 @@ public override object ConvertSourceToIntermediate(IPublishedElement owner, IPub { pos += "".Length; var npos = sourceString.IndexOf("<", pos, StringComparison.Ordinal); - var value = sourceString[pos..npos]; + var value = sourceString.Substring(pos, npos - pos); values.Add(value); pos = sourceString.IndexOf("", pos, StringComparison.Ordinal); } diff --git a/src/Umbraco.Core/PublishedCache/PublishedElement.cs b/src/Umbraco.Core/PublishedCache/PublishedElement.cs index 324a4aa8213a..297a62b589d2 100644 --- a/src/Umbraco.Core/PublishedCache/PublishedElement.cs +++ b/src/Umbraco.Core/PublishedCache/PublishedElement.cs @@ -13,7 +13,6 @@ namespace Umbraco.Cms.Core.PublishedCache; // public class PublishedElement : IPublishedElement { - #region Properties private readonly IPublishedProperty[] _propertiesArray; @@ -64,18 +63,10 @@ public PublishedElement(IPublishedContentType contentType, Guid key, Dictionary< { } - #region ContentType - public IPublishedContentType ContentType { get; } - #endregion - - #region PublishedElement - public Guid Key { get; } - #endregion - private static Dictionary GetCaseInsensitiveValueDictionary(Dictionary values) { // ensure we ignore case for property aliases @@ -94,6 +85,4 @@ public PublishedElement(IPublishedContentType contentType, Guid key, Dictionary< IPublishedProperty? property = index < 0 ? null : _propertiesArray?[index]; return property; } - - #endregion } diff --git a/src/Umbraco.Core/ReflectionUtilities.cs b/src/Umbraco.Core/ReflectionUtilities.cs index 2a219fb53ab3..a6c58466d27c 100644 --- a/src/Umbraco.Core/ReflectionUtilities.cs +++ b/src/Umbraco.Core/ReflectionUtilities.cs @@ -1063,8 +1063,7 @@ private static void EmitInputAdapter(ILGenerator ilgen, Type inputType, Type met ilgen.Emit(OpCodes.Ldnull); // push null // stack: null ; inst|null ; value - ilgen.Emit(OpCodes - .Cgt_Un); // compare what isInst returned to null - pops 2 values, and pushes 1 if greater else 0 + ilgen.Emit(OpCodes.Cgt_Un); // compare what isInst returned to null - pops 2 values, and pushes 1 if greater else 0 // stack: 0|1 ; value ilgen.Emit(OpCodes.Brtrue_S, unbox); // pops value, branches to unbox if true, ie nonzero diff --git a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs index c54f111ffd4d..c7089f082477 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByIdPath.cs @@ -64,7 +64,7 @@ public Task TryFindContent(IPublishedRequestBuilder frequest) // no id if "/" if (path != "/") { - var noSlashPath = path[1..]; + var noSlashPath = path.Substring(1); if (int.TryParse(noSlashPath, NumberStyles.Integer, CultureInfo.InvariantCulture, out nodeId) == false) { diff --git a/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs b/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs index 200a7d71438e..39fc468ceec9 100644 --- a/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs +++ b/src/Umbraco.Core/Routing/ContentFinderByUrlAndTemplate.cs @@ -77,8 +77,8 @@ public override Task TryFindContent(IPublishedRequestBuilder frequest) // look for template in last position var pos = path.LastIndexOf('/'); - var templateAlias = path[(pos + 1)..]; - path = pos == 0 ? "/" : path[..pos]; + var templateAlias = path.Substring(pos + 1); + path = pos == 0 ? "/" : path.Substring(0, pos);; ITemplate? template = _fileService.GetTemplate(templateAlias); diff --git a/src/Umbraco.Core/Routing/ContentFinderCollectionBuilder.cs b/src/Umbraco.Core/Routing/ContentFinderCollectionBuilder.cs index f82b0720fa85..3c8a0e925d22 100644 --- a/src/Umbraco.Core/Routing/ContentFinderCollectionBuilder.cs +++ b/src/Umbraco.Core/Routing/ContentFinderCollectionBuilder.cs @@ -1,9 +1,8 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Routing; -public class ContentFinderCollectionBuilder : OrderedCollectionBuilderBase +public class ContentFinderCollectionBuilder : OrderedCollectionBuilderBase { protected override ContentFinderCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs index 09db8464adf6..d0a238dbb2d4 100644 --- a/src/Umbraco.Core/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Core/Routing/DefaultUrlProvider.cs @@ -116,7 +116,7 @@ public virtual IEnumerable GetOtherUrls(int id, Uri current) // need to strip off the leading ID for the route if it exists (occurs if the route is for a node with a domain assigned) var pos = route.IndexOf('/'); - var path = pos == 0 ? route : route[pos..]; + var path = pos == 0 ? route : route.Substring(pos); var uri = new Uri(CombinePaths(d.Uri.GetLeftPart(UriPartial.Path), path)); uri = _uriUtility.UriFromUmbraco(uri, _requestSettings); diff --git a/src/Umbraco.Core/Routing/DomainUtilities.cs b/src/Umbraco.Core/Routing/DomainUtilities.cs index e7fc788aef46..f31244d2acf1 100644 --- a/src/Umbraco.Core/Routing/DomainUtilities.cs +++ b/src/Umbraco.Core/Routing/DomainUtilities.cs @@ -3,454 +3,409 @@ using Umbraco.Cms.Core.Web; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Routing; - -/// -/// Provides utilities to handle domains. -/// -public static class DomainUtilities +namespace Umbraco.Cms.Core.Routing { - #region Document Culture - /// - /// Gets the culture assigned to a document by domains, in the context of a current Uri. + /// Provides utilities to handle domains. /// - /// The document identifier. - /// The document path. - /// An optional current Uri. - /// An Umbraco context. - /// The site domain helper. - /// The culture assigned to the document by domains. - /// - /// - /// In 1:1 multilingual setup, a document contains several cultures (there is not - /// one document per culture), and domains, withing the context of a current Uri, assign - /// a culture to that document. - /// - /// - public static string? GetCultureFromDomains( - int contentId, - string contentPath, - Uri? current, - IUmbracoContext umbracoContext, - ISiteDomainMapper siteDomainMapper) + public static class DomainUtilities { - if (umbracoContext == null) + #region Document Culture + + /// + /// Gets the culture assigned to a document by domains, in the context of a current Uri. + /// + /// The document identifier. + /// The document path. + /// An optional current Uri. + /// An Umbraco context. + /// The site domain helper. + /// The culture assigned to the document by domains. + /// + /// In 1:1 multilingual setup, a document contains several cultures (there is not + /// one document per culture), and domains, withing the context of a current Uri, assign + /// a culture to that document. + /// + public static string? GetCultureFromDomains(int contentId, string contentPath, Uri? current, IUmbracoContext umbracoContext, ISiteDomainMapper siteDomainMapper) { - throw new InvalidOperationException("A current UmbracoContext is required."); - } + if (umbracoContext == null) + { + throw new InvalidOperationException("A current UmbracoContext is required."); + } - if (current == null) - { - current = umbracoContext.CleanedUmbracoUrl; - } + if (current == null) + { + current = umbracoContext.CleanedUmbracoUrl; + } - // get the published route, else the preview route - // if both are null then the content does not exist - var route = umbracoContext.Content?.GetRouteById(contentId) ?? - umbracoContext.Content?.GetRouteById(true, contentId); + // get the published route, else the preview route + // if both are null then the content does not exist + var route = umbracoContext.Content?.GetRouteById(contentId) ?? + umbracoContext.Content?.GetRouteById(true, contentId); - if (route == null) - { - return null; - } + if (route == null) + { + return null; + } - var pos = route.IndexOf('/'); - DomainAndUri? domain = pos == 0 - ? null - : DomainForNode(umbracoContext.Domains, siteDomainMapper, int.Parse(route[..pos], CultureInfo.InvariantCulture), current); + var pos = route.IndexOf('/'); + DomainAndUri? domain = pos == 0 + ? null + : DomainForNode(umbracoContext.Domains, siteDomainMapper, int.Parse(route.Substring(0, pos), CultureInfo.InvariantCulture), current); - var rootContentId = domain?.ContentId ?? -1; - Domain? wcDomain = FindWildcardDomainInPath(umbracoContext.Domains?.GetAll(true), contentPath, rootContentId); + var rootContentId = domain?.ContentId ?? -1; + Domain? wcDomain = FindWildcardDomainInPath(umbracoContext.Domains?.GetAll(true), contentPath, rootContentId); - if (wcDomain != null) - { - return wcDomain.Culture; - } + if (wcDomain != null) + { + return wcDomain.Culture; + } - if (domain != null) - { - return domain.Culture; + if (domain != null) + { + return domain.Culture; + } + + return umbracoContext.Domains?.DefaultCulture; } - return umbracoContext.Domains?.DefaultCulture; - } + #endregion + + #region Domain for Document + + /// + /// Finds the domain for the specified node, if any, that best matches a specified uri. + /// + /// A domain cache. + /// The site domain helper. + /// The node identifier. + /// The uri, or null. + /// The culture, or null. + /// The domain and its uri, if any, that best matches the specified uri and culture, else null. + /// + /// If at least a domain is set on the node then the method returns the domain that + /// best matches the specified uri and culture, else it returns null. + /// If culture is null, uses the default culture for the installation instead. Otherwise, + /// will try with the specified culture, else return null. + /// + internal static DomainAndUri? DomainForNode(IDomainCache? domainCache, ISiteDomainMapper siteDomainMapper, int nodeId, Uri current, string? culture = null) + { + // be safe + if (nodeId <= 0) + { + return null; + } - #endregion + // get the domains on that node + Domain[]? domains = domainCache?.GetAssigned(nodeId).ToArray(); - #region Selects Domain(s) + // none? + if (domains is null || domains.Length == 0) + { + return null; + } - /// - /// Selects the domain that best matches a specified uri and cultures, from a set of domains. - /// - /// The group of domains. - /// An optional uri. - /// An optional culture. - /// An optional default culture. - /// An optional function to filter the list of domains, if more than one applies. - /// The domain and its normalized uri, that best matches the specified uri and cultures. - /// - /// TODO: must document and explain this all - /// - /// If is null, pick the first domain that matches , - /// else the first that matches , else the first one (ordered by id), else null. - /// - /// If is not null, look for domains that would be a base uri of the current uri, - /// - /// If more than one domain matches, then the function is used to pick - /// the right one, unless it is null, in which case the method returns null. - /// - /// The filter, if any, will be called only with a non-empty argument, and _must_ return something. - /// - public static DomainAndUri? SelectDomain( - IEnumerable? domains, - Uri uri, - string? culture = null, - string? defaultCulture = null, - Func, Uri, string?, string?, DomainAndUri?>? filter = null) - { - // sanitize the list to have proper uris for comparison (scheme, path end with /) - // we need to end with / because example.com/foo cannot match example.com/foobar - // we need to order so example.com/foo matches before example.com/ - var domainsAndUris = domains? - .Where(d => d.IsWildcard == false) - .Select(d => new DomainAndUri(d, uri)) - .OrderByDescending(d => d.Uri.ToString()) - .ToList(); - - // nothing = no magic, return null - if (domainsAndUris is null || domainsAndUris.Count == 0) - { - return null; + // else filter + // it could be that none apply (due to culture) + return SelectDomain(domains, current, culture, domainCache?.DefaultCulture, siteDomainMapper.MapDomain); } - // sanitize cultures - culture = culture?.NullOrWhiteSpaceAsNull(); - defaultCulture = defaultCulture?.NullOrWhiteSpaceAsNull(); - - if (uri == null) + /// + /// Find the domains for the specified node, if any, that match a specified uri. + /// + /// A domain cache. + /// The site domain helper. + /// The node identifier. + /// The uri, or null. + /// A value indicating whether to exclude the current/default domain. True by default. + /// The domains and their uris, that match the specified uri, else null. + /// If at least a domain is set on the node then the method returns the domains that + /// best match the specified uri, else it returns null. + internal static IEnumerable? DomainsForNode(IDomainCache? domainCache, ISiteDomainMapper siteDomainMapper, int nodeId, Uri current, bool excludeDefault = true) { - // no uri - will only rely on culture - return GetByCulture(domainsAndUris, culture, defaultCulture); - } + // be safe + if (nodeId <= 0) + { + return null; + } - // else we have a uri, - // try to match that uri, else filter + // get the domains on that node + Domain[]? domains = domainCache?.GetAssigned(nodeId).ToArray(); - // if a culture is specified, then try to get domains for that culture - // (else cultureDomains will be null) - // do NOT specify a default culture, else it would pick those domains - IReadOnlyCollection? cultureDomains = SelectByCulture(domainsAndUris, culture, null); - IReadOnlyCollection considerForBaseDomains = domainsAndUris; - if (cultureDomains != null) - { - // only 1, return - if (cultureDomains.Count == 1) + // none? + if (domains is null || domains.Length == 0) { - return cultureDomains.First(); + return null; } - // else restrict to those domains, for base lookup - considerForBaseDomains = cultureDomains; - } - - // look for domains that would be the base of the uri - IReadOnlyCollection baseDomains = SelectByBase(considerForBaseDomains, uri, culture); + // get the domains and their uris + DomainAndUri[] domainAndUris = SelectDomains(domains, current).ToArray(); - // found, return - if (baseDomains.Count > 0) - { - return baseDomains.First(); + // filter + return siteDomainMapper.MapDomains(domainAndUris, current, excludeDefault, null, domainCache?.DefaultCulture).ToArray(); } - // if nothing works, then try to run the filter to select a domain - // either restricting on cultureDomains, or on all domains - if (filter != null) + #endregion + + #region Selects Domain(s) + + /// + /// Selects the domain that best matches a specified uri and cultures, from a set of domains. + /// + /// The group of domains. + /// An optional uri. + /// An optional culture. + /// An optional default culture. + /// An optional function to filter the list of domains, if more than one applies. + /// The domain and its normalized uri, that best matches the specified uri and cultures. + /// + /// TODO: must document and explain this all + /// If is null, pick the first domain that matches , + /// else the first that matches , else the first one (ordered by id), else null. + /// If is not null, look for domains that would be a base uri of the current uri, + /// If more than one domain matches, then the function is used to pick + /// the right one, unless it is null, in which case the method returns null. + /// The filter, if any, will be called only with a non-empty argument, and _must_ return something. + /// + public static DomainAndUri? SelectDomain(IEnumerable? domains, Uri uri, string? culture = null, string? defaultCulture = null, Func, Uri, string?, string?, DomainAndUri?>? filter = null) { - DomainAndUri? domainAndUri = filter(cultureDomains ?? domainsAndUris, uri, culture, defaultCulture); - return domainAndUri; - } + // sanitize the list to have proper uris for comparison (scheme, path end with /) + // we need to end with / because example.com/foo cannot match example.com/foobar + // we need to order so example.com/foo matches before example.com/ + var domainsAndUris = domains? + .Where(d => d.IsWildcard == false) + .Select(d => new DomainAndUri(d, uri)) + .OrderByDescending(d => d.Uri.ToString()) + .ToList(); + + // nothing = no magic, return null + if (domainsAndUris is null || domainsAndUris.Count == 0) + { + return null; + } - return null; - } + // sanitize cultures + culture = culture?.NullOrWhiteSpaceAsNull(); + defaultCulture = defaultCulture?.NullOrWhiteSpaceAsNull(); - /// - /// Parses a domain name into a URI. - /// - /// The domain name to parse - /// - /// The currently requested URI. If the domain name is relative, the authority of URI will be - /// used. - /// - /// The domain name as a URI - public static Uri ParseUriFromDomainName(string domainName, Uri currentUri) - { - // turn "/en" into "http://whatever.com/en" so it becomes a parseable uri - var name = domainName.StartsWith("/") && currentUri != null - ? currentUri.GetLeftPart(UriPartial.Authority) + domainName - : domainName; - var scheme = currentUri?.Scheme ?? Uri.UriSchemeHttp; - return new Uri(UriUtilityCore.TrimPathEndSlash(UriUtilityCore.StartWithScheme(name, scheme))); - } + if (uri == null) + { + // no uri - will only rely on culture + return GetByCulture(domainsAndUris, culture, defaultCulture); + } - /// - /// Gets the deepest wildcard Domain, if any, from a group of Domains, in a node path. - /// - /// The domains. - /// The node path eg '-1,1234,5678'. - /// The current domain root node identifier, or null. - /// The deepest wildcard Domain in the path, or null. - /// Looks _under_ rootNodeId but not _at_ rootNodeId. - public static Domain? FindWildcardDomainInPath(IEnumerable? domains, string path, int? rootNodeId) - { - var stopNodeId = rootNodeId ?? -1; - - return path.Split(Constants.CharArrays.Comma) - .Reverse() - .Select(s => int.Parse(s, CultureInfo.InvariantCulture)) - .TakeWhile(id => id != stopNodeId) - .Select(id => domains?.FirstOrDefault(d => d.ContentId == id && d.IsWildcard)) - .FirstOrDefault(domain => domain != null); - } + // else we have a uri, + // try to match that uri, else filter - #endregion + // if a culture is specified, then try to get domains for that culture + // (else cultureDomains will be null) + // do NOT specify a default culture, else it would pick those domains + IReadOnlyCollection? cultureDomains = SelectByCulture(domainsAndUris, culture, defaultCulture: null); + IReadOnlyCollection considerForBaseDomains = domainsAndUris; + if (cultureDomains != null) + { + if (cultureDomains.Count == 1) // only 1, return + { + return cultureDomains.First(); + } - #region Domain for Document + // else restrict to those domains, for base lookup + considerForBaseDomains = cultureDomains; + } - /// - /// Finds the domain for the specified node, if any, that best matches a specified uri. - /// - /// A domain cache. - /// The site domain helper. - /// The node identifier. - /// The uri, or null. - /// The culture, or null. - /// The domain and its uri, if any, that best matches the specified uri and culture, else null. - /// - /// - /// If at least a domain is set on the node then the method returns the domain that - /// best matches the specified uri and culture, else it returns null. - /// - /// - /// If culture is null, uses the default culture for the installation instead. Otherwise, - /// will try with the specified culture, else return null. - /// - /// - internal static DomainAndUri? DomainForNode( - IDomainCache? domainCache, - ISiteDomainMapper siteDomainMapper, - int nodeId, - Uri current, - string? culture = null) - { - // be safe - if (nodeId <= 0) - { - return null; - } + // look for domains that would be the base of the uri + IReadOnlyCollection baseDomains = SelectByBase(considerForBaseDomains, uri, culture); + if (baseDomains.Count > 0) // found, return + { + return baseDomains.First(); + } - // get the domains on that node - Domain[]? domains = domainCache?.GetAssigned(nodeId).ToArray(); + // if nothing works, then try to run the filter to select a domain + // either restricting on cultureDomains, or on all domains + if (filter != null) + { + DomainAndUri? domainAndUri = filter(cultureDomains ?? domainsAndUris, uri, culture, defaultCulture); + return domainAndUri; + } - // none? - if (domains is null || domains.Length == 0) - { return null; } - // else filter - // it could be that none apply (due to culture) - return SelectDomain(domains, current, culture, domainCache?.DefaultCulture, siteDomainMapper.MapDomain); - } - - /// - /// Find the domains for the specified node, if any, that match a specified uri. - /// - /// A domain cache. - /// The site domain helper. - /// The node identifier. - /// The uri, or null. - /// A value indicating whether to exclude the current/default domain. True by default. - /// The domains and their uris, that match the specified uri, else null. - /// - /// If at least a domain is set on the node then the method returns the domains that - /// best match the specified uri, else it returns null. - /// - internal static IEnumerable? DomainsForNode( - IDomainCache? domainCache, - ISiteDomainMapper siteDomainMapper, - int nodeId, - Uri current, - bool excludeDefault = true) - { - // be safe - if (nodeId <= 0) - { - return null; - } + private static bool IsBaseOf(DomainAndUri domain, Uri uri) + => domain.Uri.EndPathWithSlash().IsBaseOf(uri); - // get the domains on that node - Domain[]? domains = domainCache?.GetAssigned(nodeId).ToArray(); + private static bool MatchesCulture(DomainAndUri domain, string? culture) + => culture == null || domain.Culture.InvariantEquals(culture); - // none? - if (domains is null || domains.Length == 0) + private static IReadOnlyCollection SelectByBase(IReadOnlyCollection domainsAndUris, Uri uri, string? culture) { - return null; - } - - // get the domains and their uris - DomainAndUri[] domainAndUris = SelectDomains(domains, current).ToArray(); + // look for domains that would be the base of the uri + // ie current is www.example.com/foo/bar, look for domain www.example.com + Uri currentWithSlash = uri.EndPathWithSlash(); + var baseDomains = domainsAndUris.Where(d => IsBaseOf(d, currentWithSlash) && MatchesCulture(d, culture)).ToList(); + + // if none matches, try again without the port + // ie current is www.example.com:1234/foo/bar, look for domain www.example.com + Uri currentWithoutPort = currentWithSlash.WithoutPort(); + if (baseDomains.Count == 0) + { + baseDomains = domainsAndUris.Where(d => IsBaseOf(d, currentWithoutPort)).ToList(); + } - // filter - return siteDomainMapper.MapDomains(domainAndUris, current, excludeDefault, null, domainCache?.DefaultCulture) - .ToArray(); - } + return baseDomains; + } - /// - /// Selects the domains that match a specified uri, from a set of domains. - /// - /// The domains. - /// The uri, or null. - /// The domains and their normalized uris, that match the specified uri. - internal static IEnumerable SelectDomains(IEnumerable domains, Uri uri) => - - // TODO: where are we matching ?!!? - domains - .Where(d => d.IsWildcard == false) - .Select(d => new DomainAndUri(d, uri)) - .OrderByDescending(d => d.Uri.ToString()); - - private static bool IsBaseOf(DomainAndUri domain, Uri uri) - => domain.Uri.EndPathWithSlash().IsBaseOf(uri); - - private static bool MatchesCulture(DomainAndUri domain, string? culture) - => culture == null || domain.Culture.InvariantEquals(culture); - - private static IReadOnlyCollection SelectByBase( - IReadOnlyCollection domainsAndUris, - Uri uri, - string? culture) - { - // look for domains that would be the base of the uri - // ie current is www.example.com/foo/bar, look for domain www.example.com - Uri currentWithSlash = uri.EndPathWithSlash(); - var baseDomains = domainsAndUris.Where(d => IsBaseOf(d, currentWithSlash) && MatchesCulture(d, culture)) - .ToList(); - - // if none matches, try again without the port - // ie current is www.example.com:1234/foo/bar, look for domain www.example.com - Uri currentWithoutPort = currentWithSlash.WithoutPort(); - if (baseDomains.Count == 0) + private static IReadOnlyCollection? SelectByCulture(IReadOnlyCollection domainsAndUris, string? culture, string? defaultCulture) { - baseDomains = domainsAndUris.Where(d => IsBaseOf(d, currentWithoutPort)).ToList(); - } + // we try our best to match cultures, but may end with a bogus domain - return baseDomains; - } + if (culture != null) // try the supplied culture + { + var cultureDomains = domainsAndUris.Where(x => x.Culture.InvariantEquals(culture)).ToList(); + if (cultureDomains.Count > 0) + { + return cultureDomains; + } + } - private static IReadOnlyCollection? SelectByCulture( - IReadOnlyCollection domainsAndUris, - string? culture, - string? defaultCulture) - { - // we try our best to match cultures, but may end with a bogus domain - // try the supplied culture - if (culture != null) - { - var cultureDomains = domainsAndUris.Where(x => x.Culture.InvariantEquals(culture)).ToList(); - if (cultureDomains.Count > 0) + if (defaultCulture != null) // try the defaultCulture culture { - return cultureDomains; + var cultureDomains = domainsAndUris.Where(x => x.Culture.InvariantEquals(defaultCulture)).ToList(); + if (cultureDomains.Count > 0) + { + return cultureDomains; + } } + + return null; } - // try the defaultCulture culture - if (defaultCulture != null) + private static DomainAndUri GetByCulture(IReadOnlyCollection domainsAndUris, string? culture, string? defaultCulture) { - var cultureDomains = domainsAndUris.Where(x => x.Culture.InvariantEquals(defaultCulture)).ToList(); - if (cultureDomains.Count > 0) + DomainAndUri? domainAndUri; + + // we try our best to match cultures, but may end with a bogus domain + + if (culture != null) // try the supplied culture { - return cultureDomains; + domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)); + if (domainAndUri != null) + { + return domainAndUri; + } } - } - return null; - } + if (defaultCulture != null) // try the defaultCulture culture + { + domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(defaultCulture)); + if (domainAndUri != null) + { + return domainAndUri; + } + } - private static DomainAndUri GetByCulture(IReadOnlyCollection domainsAndUris, string? culture, string? defaultCulture) - { - DomainAndUri? domainAndUri; + return domainsAndUris.First(); // what else? + } - // we try our best to match cultures, but may end with a bogus domain - // try the supplied culture - if (culture != null) + /// + /// Selects the domains that match a specified uri, from a set of domains. + /// + /// The domains. + /// The uri, or null. + /// The domains and their normalized uris, that match the specified uri. + internal static IEnumerable SelectDomains(IEnumerable domains, Uri uri) { - domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)); - if (domainAndUri != null) - { - return domainAndUri; - } + // TODO: where are we matching ?!!? + return domains + .Where(d => d.IsWildcard == false) + .Select(d => new DomainAndUri(d, uri)) + .OrderByDescending(d => d.Uri.ToString()); } - // try the defaultCulture culture - if (defaultCulture != null) + /// + /// Parses a domain name into a URI. + /// + /// The domain name to parse + /// The currently requested URI. If the domain name is relative, the authority of URI will be used. + /// The domain name as a URI + public static Uri ParseUriFromDomainName(string domainName, Uri currentUri) { - domainAndUri = domainsAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(defaultCulture)); - if (domainAndUri != null) - { - return domainAndUri; - } + // turn "/en" into "http://whatever.com/en" so it becomes a parseable uri + var name = domainName.StartsWith("/") && currentUri != null + ? currentUri.GetLeftPart(UriPartial.Authority) + domainName + : domainName; + var scheme = currentUri?.Scheme ?? Uri.UriSchemeHttp; + return new Uri(UriUtilityCore.TrimPathEndSlash(UriUtilityCore.StartWithScheme(name, scheme))); } - return domainsAndUris.First(); // what else? - } + #endregion - #endregion + #region Utilities - #region Utilities + /// + /// Gets a value indicating whether there is another domain defined down in the path to a node under the current domain's root node. + /// + /// The domains. + /// The path to a node under the current domain's root node eg '-1,1234,5678'. + /// The current domain root node identifier, or null. + /// A value indicating if there is another domain defined down in the path. + /// Looks _under_ rootNodeId but not _at_ rootNodeId. + internal static bool ExistsDomainInPath(IEnumerable domains, string path, int? rootNodeId) + { + return FindDomainInPath(domains, path, rootNodeId) != null; + } - /// - /// Gets a value indicating whether there is another domain defined down in the path to a node under the current - /// domain's root node. - /// - /// The domains. - /// The path to a node under the current domain's root node eg '-1,1234,5678'. - /// The current domain root node identifier, or null. - /// A value indicating if there is another domain defined down in the path. - /// Looks _under_ rootNodeId but not _at_ rootNodeId. - internal static bool ExistsDomainInPath(IEnumerable domains, string path, int? rootNodeId) => - FindDomainInPath(domains, path, rootNodeId) != null; + /// + /// Gets the deepest non-wildcard Domain, if any, from a group of Domains, in a node path. + /// + /// The domains. + /// The node path eg '-1,1234,5678'. + /// The current domain root node identifier, or null. + /// The deepest non-wildcard Domain in the path, or null. + /// Looks _under_ rootNodeId but not _at_ rootNodeId. + internal static Domain? FindDomainInPath(IEnumerable domains, string path, int? rootNodeId) + { + var stopNodeId = rootNodeId ?? -1; + + return path.Split(Constants.CharArrays.Comma) + .Reverse() + .Select(s => int.Parse(s, CultureInfo.InvariantCulture)) + .TakeWhile(id => id != stopNodeId) + .Select(id => domains.FirstOrDefault(d => d.ContentId == id && d.IsWildcard == false)) + .SkipWhile(domain => domain == null) + .FirstOrDefault(); + } - /// - /// Gets the deepest non-wildcard Domain, if any, from a group of Domains, in a node path. - /// - /// The domains. - /// The node path eg '-1,1234,5678'. - /// The current domain root node identifier, or null. - /// The deepest non-wildcard Domain in the path, or null. - /// Looks _under_ rootNodeId but not _at_ rootNodeId. - internal static Domain? FindDomainInPath(IEnumerable domains, string path, int? rootNodeId) - { - var stopNodeId = rootNodeId ?? -1; - - return path.Split(Constants.CharArrays.Comma) - .Reverse() - .Select(s => int.Parse(s, CultureInfo.InvariantCulture)) - .TakeWhile(id => id != stopNodeId) - .Select(id => domains.FirstOrDefault(d => d.ContentId == id && d.IsWildcard == false)) - .SkipWhile(domain => domain == null) - .FirstOrDefault(); - } + /// + /// Gets the deepest wildcard Domain, if any, from a group of Domains, in a node path. + /// + /// The domains. + /// The node path eg '-1,1234,5678'. + /// The current domain root node identifier, or null. + /// The deepest wildcard Domain in the path, or null. + /// Looks _under_ rootNodeId but not _at_ rootNodeId. + public static Domain? FindWildcardDomainInPath(IEnumerable? domains, string path, int? rootNodeId) + { + var stopNodeId = rootNodeId ?? -1; + + return path.Split(Constants.CharArrays.Comma) + .Reverse() + .Select(s => int.Parse(s, CultureInfo.InvariantCulture)) + .TakeWhile(id => id != stopNodeId) + .Select(id => domains?.FirstOrDefault(d => d.ContentId == id && d.IsWildcard)) + .FirstOrDefault(domain => domain != null); + } - /// - /// Returns the part of a path relative to the uri of a domain. - /// - /// The normalized uri of the domain. - /// The full path of the uri. - /// The path part relative to the uri of the domain. - /// Eg the relative part of /foo/bar/nil to domain example.com/foo is /bar/nil. - public static string PathRelativeToDomain(Uri domainUri, string path) - => path[domainUri.GetAbsolutePathDecoded().Length..].EnsureStartsWith('/'); - - #endregion + /// + /// Returns the part of a path relative to the uri of a domain. + /// + /// The normalized uri of the domain. + /// The full path of the uri. + /// The path part relative to the uri of the domain. + /// Eg the relative part of /foo/bar/nil to domain example.com/foo is /bar/nil. + public static string PathRelativeToDomain(Uri domainUri, string path) + => path.Substring(domainUri.GetAbsolutePathDecoded().Length).EnsureStartsWith('/'); + + #endregion + } } diff --git a/src/Umbraco.Core/Routing/IPublishedRouter.cs b/src/Umbraco.Core/Routing/IPublishedRouter.cs index cf6ac6140657..5434c464474d 100644 --- a/src/Umbraco.Core/Routing/IPublishedRouter.cs +++ b/src/Umbraco.Core/Routing/IPublishedRouter.cs @@ -34,8 +34,7 @@ public interface IPublishedRouter /// /// This method is used for 2 cases: /// - When the rendering content needs to change due to Public Access rules. - /// - When there is nothing to render due to circumstances such as no template files. In this case, NULL is used as - /// the parameter. + /// - When there is nothing to render due to circumstances such as no template files. In this case, NULL is used as the parameter. /// /// /// This method is invoked when the pipeline decides it cannot render diff --git a/src/Umbraco.Core/Routing/MediaUrlProviderCollectionBuilder.cs b/src/Umbraco.Core/Routing/MediaUrlProviderCollectionBuilder.cs index fbe97f40e3e8..ba0a9b9fc238 100644 --- a/src/Umbraco.Core/Routing/MediaUrlProviderCollectionBuilder.cs +++ b/src/Umbraco.Core/Routing/MediaUrlProviderCollectionBuilder.cs @@ -1,9 +1,8 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Routing; -public class MediaUrlProviderCollectionBuilder : OrderedCollectionBuilderBase +public class MediaUrlProviderCollectionBuilder : OrderedCollectionBuilderBase { protected override MediaUrlProviderCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs index f155c7515caf..5f195f78b534 100644 --- a/src/Umbraco.Core/Routing/PublishedRouter.cs +++ b/src/Umbraco.Core/Routing/PublishedRouter.cs @@ -416,8 +416,8 @@ internal bool FindTemplateRenderingEngineInDirectory(DirectoryInfo? directory, s if (pos > 0) { // recurse - DirectoryInfo? subdir = directory.GetDirectories(alias[..pos]).FirstOrDefault(); - alias = alias[(pos + 1)..]; + DirectoryInfo? subdir = directory.GetDirectories(alias.Substring(0, pos)).FirstOrDefault(); + alias = alias.Substring(pos + 1); return subdir != null && FindTemplateRenderingEngineInDirectory(subdir, alias, extensions); } diff --git a/src/Umbraco.Core/Routing/SiteDomainMapper.cs b/src/Umbraco.Core/Routing/SiteDomainMapper.cs index eaea1cd8bcd8..b8ae10c3aa84 100644 --- a/src/Umbraco.Core/Routing/SiteDomainMapper.cs +++ b/src/Umbraco.Core/Routing/SiteDomainMapper.cs @@ -1,426 +1,425 @@ using System.Text.RegularExpressions; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Routing; - -/// -/// Provides utilities to handle site domains. -/// -public class SiteDomainMapper : ISiteDomainMapper, IDisposable +namespace Umbraco.Cms.Core.Routing { - // these are for validation - // private const string DomainValidationSource = @"^(\*|((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/[-\w]*)?))$"; - private const string DomainValidationSource = @"^(((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/)?))$"; - - #region Configure - - private readonly ReaderWriterLockSlim _configLock = new(); - - public void Dispose() => - - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(true); - - protected virtual void Dispose(bool disposing) + /// + /// Provides utilities to handle site domains. + /// + public class SiteDomainMapper : ISiteDomainMapper, IDisposable { - if (!_disposedValue) + public void Dispose() => + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(true); + + protected virtual void Dispose(bool disposing) { - if (disposing) + if (!_disposedValue) { - // This is pretty nasty disposing a static on an instance but it's because this whole class - // is pretty fubar. I'm sure we've fixed this all up in netcore now? We need to remove all statics. - _configLock.Dispose(); - } + if (disposing) + { + // This is pretty nasty disposing a static on an instance but it's because this whole class + // is pretty fubar. I'm sure we've fixed this all up in netcore now? We need to remove all statics. + _configLock.Dispose(); + } - _disposedValue = true; + _disposedValue = true; + } } - } - private Dictionary>? _qualifiedSites; - private bool _disposedValue; + #region Configure - internal Dictionary? Sites { get; private set; } + private readonly ReaderWriterLockSlim _configLock = new(); + private Dictionary>? _qualifiedSites; + private bool _disposedValue; - internal Dictionary>? Bindings { get; private set; } + internal Dictionary? Sites { get; private set; } + internal Dictionary>? Bindings { get; private set; } - private static readonly Regex DomainValidation = - new(DomainValidationSource, RegexOptions.IgnoreCase | RegexOptions.Compiled); + // these are for validation + //private const string DomainValidationSource = @"^(\*|((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/[-\w]*)?))$"; + private const string DomainValidationSource = @"^(((?i:http[s]?://)?([-\w]+(\.[-\w]+)*)(:\d+)?(/)?))$"; - /// - /// Clears the entire configuration. - /// - public void Clear() - { - try - { - _configLock.EnterWriteLock(); + private static readonly Regex s_domainValidation = + new(DomainValidationSource, RegexOptions.IgnoreCase | RegexOptions.Compiled); - Sites = null; - Bindings = null; - _qualifiedSites = null; - } - finally + /// + /// Clears the entire configuration. + /// + public void Clear() { - if (_configLock.IsWriteLockHeld) + try { - _configLock.ExitWriteLock(); - } - } - } + _configLock.EnterWriteLock(); - /// - /// Adds a site. - /// - /// A key uniquely identifying the site. - /// The site domains. - /// At the moment there is no public way to remove a site. Clear and reconfigure. - public void AddSite(string key, IEnumerable domains) - { - try - { - _configLock.EnterWriteLock(); - - Sites ??= new Dictionary(); - Sites[key] = ValidateDomains(domains).ToArray(); - _qualifiedSites = null; - } - finally - { - if (_configLock.IsWriteLockHeld) + Sites = null; + Bindings = null; + _qualifiedSites = null; + } + finally { - _configLock.ExitWriteLock(); + if (_configLock.IsWriteLockHeld) + { + _configLock.ExitWriteLock(); + } } } - } - - private IEnumerable ValidateDomains(IEnumerable domains) => - // must use authority format w/optional scheme and port, but no path - // any domain should appear only once - domains.Select(domain => - { - if (!DomainValidation.IsMatch(domain)) + private IEnumerable ValidateDomains(IEnumerable domains) => + // must use authority format w/optional scheme and port, but no path + // any domain should appear only once + domains.Select(domain => { - throw new ArgumentOutOfRangeException(nameof(domains), $"Invalid domain: \"{domain}\"."); - } + if (!s_domainValidation.IsMatch(domain)) + { + throw new ArgumentOutOfRangeException(nameof(domains), $"Invalid domain: \"{domain}\"."); + } - return domain; - }); + return domain; + }); - /// - /// Adds a site. - /// - /// A key uniquely identifying the site. - /// The site domains. - /// At the moment there is no public way to remove a site. Clear and reconfigure. - public void AddSite(string key, params string[] domains) - { - try + /// + /// Adds a site. + /// + /// A key uniquely identifying the site. + /// The site domains. + /// At the moment there is no public way to remove a site. Clear and reconfigure. + public void AddSite(string key, IEnumerable domains) { - _configLock.EnterWriteLock(); + try + { + _configLock.EnterWriteLock(); - Sites ??= new Dictionary(); - Sites[key] = ValidateDomains(domains).ToArray(); - _qualifiedSites = null; - } - finally - { - if (_configLock.IsWriteLockHeld) + Sites = Sites ?? new Dictionary(); + Sites[key] = ValidateDomains(domains).ToArray(); + _qualifiedSites = null; + } + finally { - _configLock.ExitWriteLock(); + if (_configLock.IsWriteLockHeld) + { + _configLock.ExitWriteLock(); + } } } - } - /// - /// Binds some sites. - /// - /// The keys uniquely identifying the sites to bind. - /// - /// At the moment there is no public way to unbind sites. Clear and reconfigure. - /// If site1 is bound to site2 and site2 is bound to site3 then site1 is bound to site3. - /// - public void BindSites(params string[] keys) - { - try + /// + /// Adds a site. + /// + /// A key uniquely identifying the site. + /// The site domains. + /// At the moment there is no public way to remove a site. Clear and reconfigure. + public void AddSite(string key, params string[] domains) { - _configLock.EnterWriteLock(); + try + { + _configLock.EnterWriteLock(); - foreach (var key in keys.Where(key => !Sites?.ContainsKey(key) ?? false)) + Sites = Sites ?? new Dictionary(); + Sites[key] = ValidateDomains(domains).ToArray(); + _qualifiedSites = null; + } + finally { - throw new ArgumentException($"Not an existing site key: {key}.", nameof(keys)); + if (_configLock.IsWriteLockHeld) + { + _configLock.ExitWriteLock(); + } } + } - Bindings ??= new Dictionary>(); + /// + /// Removes a site. + /// + /// A key uniquely identifying the site. + internal void RemoveSite(string key) + { + try + { + _configLock.EnterWriteLock(); - var allkeys = Bindings - .Where(kvp => keys.Contains(kvp.Key)) - .SelectMany(kvp => kvp.Value) - .Union(keys) - .ToArray(); + if (Sites == null || !Sites.ContainsKey(key)) + { + return; + } - foreach (var key in allkeys) - { - if (!Bindings.ContainsKey(key)) + Sites.Remove(key); + if (Sites.Count == 0) + { + Sites = null; + } + + if (Bindings != null && Bindings.ContainsKey(key)) { - Bindings[key] = new List(); + foreach (var b in Bindings[key]) + { + Bindings[b].Remove(key); + if (Bindings[b].Count == 0) + { + Bindings.Remove(b); + } + } + + Bindings.Remove(key); + if (Bindings.Count > 0) + { + Bindings = null; + } } - var xkey = key; - IEnumerable addKeys = allkeys.Where(k => k != xkey).Except(Bindings[key]); - Bindings[key].AddRange(addKeys); + _qualifiedSites = null; } - } - finally - { - if (_configLock.IsWriteLockHeld) + finally { - _configLock.ExitWriteLock(); + if (_configLock.IsWriteLockHeld) + { + _configLock.ExitWriteLock(); + } } } - } - /// - /// Removes a site. - /// - /// A key uniquely identifying the site. - internal void RemoveSite(string key) - { - try + /// + /// Binds some sites. + /// + /// The keys uniquely identifying the sites to bind. + /// + /// At the moment there is no public way to unbind sites. Clear and reconfigure. + /// If site1 is bound to site2 and site2 is bound to site3 then site1 is bound to site3. + /// + public void BindSites(params string[] keys) { - _configLock.EnterWriteLock(); - - if (Sites == null || !Sites.ContainsKey(key)) + try { - return; - } + _configLock.EnterWriteLock(); - Sites.Remove(key); - if (Sites.Count == 0) - { - Sites = null; - } + foreach (var key in keys.Where(key => !Sites?.ContainsKey(key) ?? false)) + { + throw new ArgumentException($"Not an existing site key: {key}.", nameof(keys)); + } - if (Bindings != null && Bindings.ContainsKey(key)) - { - foreach (var b in Bindings[key]) + Bindings = Bindings ?? new Dictionary>(); + + var allkeys = Bindings + .Where(kvp => keys.Contains(kvp.Key)) + .SelectMany(kvp => kvp.Value) + .Union(keys) + .ToArray(); + + foreach (var key in allkeys) { - Bindings[b].Remove(key); - if (Bindings[b].Count == 0) + if (!Bindings.ContainsKey(key)) { - Bindings.Remove(b); + Bindings[key] = new List(); } - } - Bindings.Remove(key); - if (Bindings.Count > 0) - { - Bindings = null; + var xkey = key; + IEnumerable addKeys = allkeys.Where(k => k != xkey).Except(Bindings[key]); + Bindings[key].AddRange(addKeys); } } - - _qualifiedSites = null; - } - finally - { - if (_configLock.IsWriteLockHeld) + finally { - _configLock.ExitWriteLock(); + if (_configLock.IsWriteLockHeld) + { + _configLock.ExitWriteLock(); + } } } - } - - #endregion - #region Map domains + #endregion - /// - public virtual DomainAndUri? MapDomain(IReadOnlyCollection domainAndUris, Uri current, string? culture, string? defaultCulture) - { - var currentAuthority = current.GetLeftPart(UriPartial.Authority); - Dictionary? qualifiedSites = GetQualifiedSites(current); + #region Map domains - return MapDomain(domainAndUris, qualifiedSites, currentAuthority, culture, defaultCulture); - } + /// + public virtual DomainAndUri? MapDomain(IReadOnlyCollection domainAndUris, Uri current, string? culture, string? defaultCulture) + { + var currentAuthority = current.GetLeftPart(UriPartial.Authority); + Dictionary? qualifiedSites = GetQualifiedSites(current); - /// - public virtual IEnumerable MapDomains(IReadOnlyCollection domainAndUris, Uri current, bool excludeDefault, string? culture, string? defaultCulture) - { - // TODO: ignoring cultures entirely? - var currentAuthority = current.GetLeftPart(UriPartial.Authority); - KeyValuePair[]? candidateSites = null; - IEnumerable ret = domainAndUris; + return MapDomain(domainAndUris, qualifiedSites, currentAuthority, culture, defaultCulture); + } - try + /// + public virtual IEnumerable MapDomains(IReadOnlyCollection domainAndUris, Uri current, bool excludeDefault, string? culture, string? defaultCulture) { - _configLock.EnterReadLock(); + // TODO: ignoring cultures entirely? - Dictionary? qualifiedSites = GetQualifiedSitesInsideLock(current); + var currentAuthority = current.GetLeftPart(UriPartial.Authority); + KeyValuePair[]? candidateSites = null; + IEnumerable ret = domainAndUris; - if (excludeDefault) + try { - // exclude the current one (avoid producing the absolute equivalent of what GetUrl returns) - Uri hintWithSlash = current.EndPathWithSlash(); - DomainAndUri? hinted = - domainAndUris.FirstOrDefault(d => d.Uri.EndPathWithSlash().IsBaseOf(hintWithSlash)); - if (hinted != null) + _configLock.EnterReadLock(); + + Dictionary? qualifiedSites = GetQualifiedSitesInsideLock(current); + + if (excludeDefault) { - ret = ret.Where(d => d != hinted); + // exclude the current one (avoid producing the absolute equivalent of what GetUrl returns) + Uri hintWithSlash = current.EndPathWithSlash(); + DomainAndUri? hinted = + domainAndUris.FirstOrDefault(d => d.Uri.EndPathWithSlash().IsBaseOf(hintWithSlash)); + if (hinted != null) + { + ret = ret.Where(d => d != hinted); + } + + // exclude the default one (avoid producing a possible duplicate of what GetUrl returns) + // only if the default one cannot be the current one ie if hinted is not null + if (hinted == null && domainAndUris.Any()) + { + // it is illegal to call MapDomain if domainAndUris is empty + // also, domainAndUris should NOT contain current, hence the test on hinted + DomainAndUri? mainDomain = MapDomain(domainAndUris, qualifiedSites, currentAuthority, culture, defaultCulture); // what GetUrl would get + ret = ret.Where(d => d != mainDomain); + } } - // exclude the default one (avoid producing a possible duplicate of what GetUrl returns) - // only if the default one cannot be the current one ie if hinted is not null - if (hinted == null && domainAndUris.Any()) + // we do our best, but can't do the impossible + if (qualifiedSites == null) { - // it is illegal to call MapDomain if domainAndUris is empty - // also, domainAndUris should NOT contain current, hence the test on hinted - DomainAndUri? mainDomain = MapDomain(domainAndUris, qualifiedSites, currentAuthority, culture, defaultCulture); // what GetUrl would get - ret = ret.Where(d => d != mainDomain); + return ret; } - } - // we do our best, but can't do the impossible - if (qualifiedSites == null) - { - return ret; - } + // find a site that contains the current authority + KeyValuePair currentSite = + qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority)); - // find a site that contains the current authority - KeyValuePair currentSite = - qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority)); + // if current belongs to a site, pick every element from domainAndUris that also belong + // to that site -- or to any site bound to that site - // if current belongs to a site, pick every element from domainAndUris that also belong - // to that site -- or to any site bound to that site - if (!currentSite.Equals(default(KeyValuePair))) - { - candidateSites = new[] { currentSite }; - if (Bindings != null && Bindings.ContainsKey(currentSite.Key)) + if (!currentSite.Equals(default(KeyValuePair))) { - IEnumerable> boundSites = - qualifiedSites.Where(site => Bindings[currentSite.Key].Contains(site.Key)); - candidateSites = candidateSites.Union(boundSites).ToArray(); + candidateSites = new[] { currentSite }; + if (Bindings != null && Bindings.ContainsKey(currentSite.Key)) + { + IEnumerable> boundSites = + qualifiedSites.Where(site => Bindings[currentSite.Key].Contains(site.Key)); + candidateSites = candidateSites.Union(boundSites).ToArray(); - // .ToArray ensures it is evaluated before the configuration lock is exited + // .ToArray ensures it is evaluated before the configuration lock is exited + } } } - } - finally - { - if (_configLock.IsReadLockHeld) + finally { - _configLock.ExitReadLock(); + if (_configLock.IsReadLockHeld) + { + _configLock.ExitReadLock(); + } } - } - // if we are able to filter, then filter, else return the whole lot - return candidateSites == null - ? ret - : ret.Where(d => - { - var authority = d.Uri.GetLeftPart(UriPartial.Authority); - return candidateSites.Any(site => site.Value.Contains(authority)); - }); - } + // if we are able to filter, then filter, else return the whole lot + return candidateSites == null + ? ret + : ret.Where(d => + { + var authority = d.Uri.GetLeftPart(UriPartial.Authority); + return candidateSites.Any(site => site.Value.Contains(authority)); + }); + } - private Dictionary? GetQualifiedSites(Uri current) - { - try + private Dictionary? GetQualifiedSites(Uri current) { - _configLock.EnterReadLock(); + try + { + _configLock.EnterReadLock(); - return GetQualifiedSitesInsideLock(current); - } - finally - { - if (_configLock.IsReadLockHeld) + return GetQualifiedSitesInsideLock(current); + } + finally { - _configLock.ExitReadLock(); + if (_configLock.IsReadLockHeld) + { + _configLock.ExitReadLock(); + } } } - } - - private Dictionary? GetQualifiedSitesInsideLock(Uri current) - { - // we do our best, but can't do the impossible - if (Sites == null) - { - return null; - } - // cached? - if (_qualifiedSites != null && _qualifiedSites.ContainsKey(current.Scheme)) + private Dictionary? GetQualifiedSitesInsideLock(Uri current) { - return _qualifiedSites[current.Scheme]; - } + // we do our best, but can't do the impossible + if (Sites == null) + { + return null; + } - _qualifiedSites ??= new Dictionary>(); - - // convert sites into authority sites based upon current scheme - // because some domains in the sites might not have a scheme -- and cache - return _qualifiedSites[current.Scheme] = Sites - .ToDictionary( - kvp => kvp.Key, - kvp => kvp.Value.Select(d => - new Uri(UriUtilityCore.StartWithScheme(d, current.Scheme)) - .GetLeftPart(UriPartial.Authority)) - .ToArray()); - - // .ToDictionary will evaluate and create the dictionary immediately - // the new value is .ToArray so it will also be evaluated immediately - // therefore it is safe to return and exit the configuration lock - } + // cached? + if (_qualifiedSites != null && _qualifiedSites.ContainsKey(current.Scheme)) + { + return _qualifiedSites[current.Scheme]; + } - private DomainAndUri? MapDomain( - IReadOnlyCollection domainAndUris, - Dictionary? qualifiedSites, - string currentAuthority, - string? culture, - string? defaultCulture) - { - if (domainAndUris == null) - { - throw new ArgumentNullException(nameof(domainAndUris)); + _qualifiedSites = _qualifiedSites ?? new Dictionary>(); + + // convert sites into authority sites based upon current scheme + // because some domains in the sites might not have a scheme -- and cache + return _qualifiedSites[current.Scheme] = Sites + .ToDictionary( + kvp => kvp.Key, + kvp => kvp.Value.Select(d => + new Uri(UriUtilityCore.StartWithScheme(d, current.Scheme)) + .GetLeftPart(UriPartial.Authority)) + .ToArray()); + + // .ToDictionary will evaluate and create the dictionary immediately + // the new value is .ToArray so it will also be evaluated immediately + // therefore it is safe to return and exit the configuration lock } - if (domainAndUris.Count == 0) + private DomainAndUri? MapDomain( + IReadOnlyCollection domainAndUris, + Dictionary? qualifiedSites, + string currentAuthority, + string? culture, + string? defaultCulture) { - throw new ArgumentException("Cannot be empty.", nameof(domainAndUris)); - } + if (domainAndUris == null) + { + throw new ArgumentNullException(nameof(domainAndUris)); + } - if (qualifiedSites == null) - { - return domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)) - ?? domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(defaultCulture)) - ?? (culture is null ? domainAndUris.First() : null); - } + if (domainAndUris.Count == 0) + { + throw new ArgumentException("Cannot be empty.", nameof(domainAndUris)); + } - // find a site that contains the current authority - KeyValuePair currentSite = - qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority)); + if (qualifiedSites == null) + { + return domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)) + ?? domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(defaultCulture)) + ?? (culture is null ? domainAndUris.First() : null); + } + + // find a site that contains the current authority + KeyValuePair currentSite = + qualifiedSites.FirstOrDefault(site => site.Value.Contains(currentAuthority)); - // if current belongs to a site - try to pick the first element - // from domainAndUris that also belongs to that site - DomainAndUri? ret = currentSite.Equals(default(KeyValuePair)) - ? null - : domainAndUris.FirstOrDefault(d => - currentSite.Value.Contains(d.Uri.GetLeftPart(UriPartial.Authority))); + // if current belongs to a site - try to pick the first element + // from domainAndUris that also belongs to that site + DomainAndUri? ret = currentSite.Equals(default(KeyValuePair)) + ? null + : domainAndUris.FirstOrDefault(d => + currentSite.Value.Contains(d.Uri.GetLeftPart(UriPartial.Authority))); - // no match means that either current does not belong to a site, or the site it belongs to - // does not contain any of domainAndUris. Yet we have to return something. here, it becomes - // a bit arbitrary. + // no match means that either current does not belong to a site, or the site it belongs to + // does not contain any of domainAndUris. Yet we have to return something. here, it becomes + // a bit arbitrary. - // look through sites in order and pick the first domainAndUri that belongs to a site - ret ??= qualifiedSites - .Where(site => site.Key != currentSite.Key) - .Select(site => domainAndUris.FirstOrDefault(domainAndUri => - site.Value.Contains(domainAndUri.Uri.GetLeftPart(UriPartial.Authority)))) - .FirstOrDefault(domainAndUri => domainAndUri != null); + // look through sites in order and pick the first domainAndUri that belongs to a site + ret = ret ?? qualifiedSites + .Where(site => site.Key != currentSite.Key) + .Select(site => domainAndUris.FirstOrDefault(domainAndUri => + site.Value.Contains(domainAndUri.Uri.GetLeftPart(UriPartial.Authority)))) + .FirstOrDefault(domainAndUri => domainAndUri != null); - // random, really - ret ??= domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)) ?? domainAndUris.First(); + // random, really + ret = ret ?? domainAndUris.FirstOrDefault(x => x.Culture.InvariantEquals(culture)) ?? domainAndUris.First(); - return ret; - } + return ret; + } - #endregion + #endregion + } } diff --git a/src/Umbraco.Core/Routing/UrlProvider.cs b/src/Umbraco.Core/Routing/UrlProvider.cs index e4fe51686dd2..97385a144b8a 100644 --- a/src/Umbraco.Core/Routing/UrlProvider.cs +++ b/src/Umbraco.Core/Routing/UrlProvider.cs @@ -4,262 +4,251 @@ using Umbraco.Cms.Core.Web; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Routing; - -/// -/// Provides URLs. -/// -public class UrlProvider : IPublishedUrlProvider +namespace Umbraco.Cms.Core.Routing { - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - - #region Ctor and configuration - /// - /// Initializes a new instance of the class with an Umbraco context and a list of URL - /// providers. + /// Provides URLs. /// - /// The Umbraco context accessor. - /// Routing settings. - /// The list of URL providers. - /// The list of media URL providers. - /// The current variation accessor. - /// - public UrlProvider(IUmbracoContextAccessor umbracoContextAccessor, IOptions routingSettings, UrlProviderCollection urlProviders, MediaUrlProviderCollection mediaUrlProviders, IVariationContextAccessor variationContextAccessor) + public class UrlProvider : IPublishedUrlProvider { - _umbracoContextAccessor = - umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); - _urlProviders = urlProviders; - _mediaUrlProviders = mediaUrlProviders; - _variationContextAccessor = variationContextAccessor ?? - throw new ArgumentNullException(nameof(variationContextAccessor)); - Mode = routingSettings.Value.UrlProviderMode; - } - - private readonly IEnumerable _urlProviders; - private readonly IEnumerable _mediaUrlProviders; - private readonly IVariationContextAccessor _variationContextAccessor; - - /// - /// Gets or sets the provider URL mode. - /// - public UrlMode Mode { get; set; } - - /// - /// Gets the URL of a published content. - /// - /// The published content identifier. - /// The URL mode. - /// A culture. - /// The current absolute URL. - /// The URL for the published content. - public string GetUrl(Guid id, UrlMode mode = UrlMode.Default, string? culture = null, Uri? current = null) - => GetUrl(GetDocument(id), mode, culture, current); - - #endregion - - #region GetUrl + #region Ctor and configuration + + /// + /// Initializes a new instance of the class with an Umbraco context and a list of URL providers. + /// + /// The Umbraco context accessor. + /// Routing settings. + /// The list of URL providers. + /// The list of media URL providers. + /// The current variation accessor. + /// + public UrlProvider(IUmbracoContextAccessor umbracoContextAccessor, IOptions routingSettings, UrlProviderCollection urlProviders, MediaUrlProviderCollection mediaUrlProviders, IVariationContextAccessor variationContextAccessor) + { + _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); + _urlProviders = urlProviders; + _mediaUrlProviders = mediaUrlProviders; + _variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); + Mode = routingSettings.Value.UrlProviderMode; - private IPublishedContent? GetDocument(int id) - { - IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - return umbracoContext.Content?.GetById(id); - } + } - private IPublishedContent? GetDocument(Guid id) - { - IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - return umbracoContext.Content?.GetById(id); - } + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IEnumerable _urlProviders; + private readonly IEnumerable _mediaUrlProviders; + private readonly IVariationContextAccessor _variationContextAccessor; - private IPublishedContent? GetMedia(Guid id) - { - IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - return umbracoContext.Media?.GetById(id); - } + /// + /// Gets or sets the provider URL mode. + /// + public UrlMode Mode { get; set; } - /// - /// Gets the URL of a published content. - /// - /// The published content identifier. - /// The URL mode. - /// A culture. - /// The current absolute URL. - /// The URL for the published content. - public string GetUrl(int id, UrlMode mode = UrlMode.Default, string? culture = null, Uri? current = null) - => GetUrl(GetDocument(id), mode, culture, current); + #endregion - /// - /// Gets the URL of a published content. - /// - /// The published content. - /// The URL mode. - /// A culture. - /// The current absolute URL. - /// The URL for the published content. - /// - /// The URL is absolute or relative depending on mode and on current. - /// - /// If the published content is multi-lingual, gets the URL for the specified culture or, - /// when no culture is specified, the current culture. - /// - /// If the provider is unable to provide a URL, it returns "#". - /// - public string GetUrl(IPublishedContent? content, UrlMode mode = UrlMode.Default, string? culture = null, Uri? current = null) - { - if (content == null || content.ContentType.ItemType == PublishedItemType.Element) - { - return "#"; - } + #region GetUrl - if (mode == UrlMode.Default) + private IPublishedContent? GetDocument(int id) { - mode = Mode; + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + return umbracoContext.Content?.GetById(id); } - - // this the ONLY place where we deal with default culture - IUrlProvider always receive a culture - // be nice with tests, assume things can be null, ultimately fall back to invariant - // (but only for variant content of course) - if (content.ContentType.VariesByCulture()) + private IPublishedContent? GetDocument(Guid id) { - if (culture == null) - { - culture = _variationContextAccessor?.VariationContext?.Culture ?? string.Empty; - } + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + return umbracoContext.Content?.GetById(id); } - - if (current == null) + private IPublishedContent? GetMedia(Guid id) { IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - current = umbracoContext.CleanedUmbracoUrl; + return umbracoContext.Media?.GetById(id); } - UrlInfo? url = _urlProviders.Select(provider => provider.GetUrl(content, mode, culture, current)) - .FirstOrDefault(u => u is not null); - return url?.Text ?? "#"; // legacy wants this - } - - public string GetUrlFromRoute(int id, string? route, string? culture) - { - IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - DefaultUrlProvider? provider = _urlProviders.OfType().FirstOrDefault(); - var url = provider == null - ? route // what else? - : provider.GetUrlFromRoute(route, umbracoContext, id, umbracoContext.CleanedUmbracoUrl, Mode, culture) - ?.Text; - return url ?? "#"; - } + /// + /// Gets the URL of a published content. + /// + /// The published content identifier. + /// The URL mode. + /// A culture. + /// The current absolute URL. + /// The URL for the published content. + public string GetUrl(Guid id, UrlMode mode = UrlMode.Default, string? culture = null, Uri? current = null) + => GetUrl(GetDocument(id), mode, culture, current); + + /// + /// Gets the URL of a published content. + /// + /// The published content identifier. + /// The URL mode. + /// A culture. + /// The current absolute URL. + /// The URL for the published content. + public string GetUrl(int id, UrlMode mode = UrlMode.Default, string? culture = null, Uri? current = null) + => GetUrl(GetDocument(id), mode, culture, current); + + /// + /// Gets the URL of a published content. + /// + /// The published content. + /// The URL mode. + /// A culture. + /// The current absolute URL. + /// The URL for the published content. + /// + /// The URL is absolute or relative depending on mode and on current. + /// If the published content is multi-lingual, gets the URL for the specified culture or, + /// when no culture is specified, the current culture. + /// If the provider is unable to provide a URL, it returns "#". + /// + public string GetUrl(IPublishedContent? content, UrlMode mode = UrlMode.Default, string? culture = null, Uri? current = null) + { + if (content == null || content.ContentType.ItemType == PublishedItemType.Element) + { + return "#"; + } - #endregion + if (mode == UrlMode.Default) + { + mode = Mode; + } - #region GetOtherUrls + // this the ONLY place where we deal with default culture - IUrlProvider always receive a culture + // be nice with tests, assume things can be null, ultimately fall back to invariant + // (but only for variant content of course) + // We need to check all ancestors because urls are variant even for invariant content, if an ancestor is variant. + if (culture == null && content.AncestorsOrSelf().Any(x => x.ContentType.VariesByCulture())) + { + culture = _variationContextAccessor?.VariationContext?.Culture ?? string.Empty; + } - /// - /// Gets the other URLs of a published content. - /// - /// The published content id. - /// The other URLs for the published content. - /// - /// - /// Other URLs are those that GetUrl would not return in the current context, but would be valid - /// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...). - /// - /// The results depend on the current URL. - /// - public IEnumerable GetOtherUrls(int id) - { - IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - return GetOtherUrls(id, umbracoContext.CleanedUmbracoUrl); - } + if (current == null) + { + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + current = umbracoContext.CleanedUmbracoUrl; + } - /// - /// Gets the other URLs of a published content. - /// - /// The published content id. - /// The current absolute URL. - /// The other URLs for the published content. - /// - /// - /// Other URLs are those that GetUrl would not return in the current context, but would be valid - /// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...). - /// - /// - public IEnumerable GetOtherUrls(int id, Uri current) => _urlProviders.SelectMany(provider => - provider.GetOtherUrls(id, current) ?? Enumerable.Empty()); - - #endregion - - #region GetMediaUrl - /// - /// Gets the URL of a media item. - /// - /// - /// - /// - /// - /// - /// - public string GetMediaUrl(Guid id, UrlMode mode = UrlMode.Default, string? culture = null, string propertyAlias = Constants.Conventions.Media.File, Uri? current = null) - => GetMediaUrl(GetMedia(id), mode, culture, propertyAlias, current); + UrlInfo? url = _urlProviders.Select(provider => provider.GetUrl(content, mode, culture, current)) + .FirstOrDefault(u => u is not null); + return url?.Text ?? "#"; // legacy wants this + } - /// - /// Gets the URL of a media item. - /// - /// The published content. - /// The property alias to resolve the URL from. - /// The URL mode. - /// The variation language. - /// The current absolute URL. - /// The URL for the media. - /// - /// The URL is absolute or relative depending on mode and on current. - /// - /// If the media is multi-lingual, gets the URL for the specified culture or, - /// when no culture is specified, the current culture. - /// - /// If the provider is unable to provide a URL, it returns . - /// - public string GetMediaUrl(IPublishedContent? content, UrlMode mode = UrlMode.Default, string? culture = null, string propertyAlias = Constants.Conventions.Media.File, Uri? current = null) - { - if (propertyAlias == null) + public string GetUrlFromRoute(int id, string? route, string? culture) { - throw new ArgumentNullException(nameof(propertyAlias)); + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + DefaultUrlProvider? provider = _urlProviders.OfType().FirstOrDefault(); + var url = provider == null + ? route // what else? + : provider.GetUrlFromRoute(route, umbracoContext, id, umbracoContext.CleanedUmbracoUrl, Mode, culture)?.Text; + return url ?? "#"; } - if (content == null) + #endregion + + #region GetOtherUrls + + /// + /// Gets the other URLs of a published content. + /// + /// The published content id. + /// The other URLs for the published content. + /// + /// Other URLs are those that GetUrl would not return in the current context, but would be valid + /// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...). + /// The results depend on the current URL. + /// + public IEnumerable GetOtherUrls(int id) { - return string.Empty; + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + return GetOtherUrls(id, umbracoContext.CleanedUmbracoUrl); } - if (mode == UrlMode.Default) + /// + /// Gets the other URLs of a published content. + /// + /// The published content id. + /// The current absolute URL. + /// The other URLs for the published content. + /// + /// Other URLs are those that GetUrl would not return in the current context, but would be valid + /// URLs for the node in other contexts (different domain for current request, umbracoUrlAlias...). + /// + public IEnumerable GetOtherUrls(int id, Uri current) { - mode = Mode; + return _urlProviders.SelectMany(provider => provider.GetOtherUrls(id, current) ?? Enumerable.Empty()); } - // this the ONLY place where we deal with default culture - IMediaUrlProvider always receive a culture - // be nice with tests, assume things can be null, ultimately fall back to invariant - // (but only for variant content of course) - if (content.ContentType.VariesByCulture()) + #endregion + + #region GetMediaUrl + + /// + /// Gets the URL of a media item. + /// + /// + /// + /// + /// + /// + /// + public string GetMediaUrl(Guid id, UrlMode mode = UrlMode.Default, string? culture = null, string propertyAlias = Constants.Conventions.Media.File, Uri? current = null) + => GetMediaUrl( GetMedia(id), mode, culture, propertyAlias, current); + + /// + /// Gets the URL of a media item. + /// + /// The published content. + /// The property alias to resolve the URL from. + /// The URL mode. + /// The variation language. + /// The current absolute URL. + /// The URL for the media. + /// + /// The URL is absolute or relative depending on mode and on current. + /// If the media is multi-lingual, gets the URL for the specified culture or, + /// when no culture is specified, the current culture. + /// If the provider is unable to provide a URL, it returns . + /// + public string GetMediaUrl(IPublishedContent? content, UrlMode mode = UrlMode.Default, string? culture = null, string propertyAlias = Constants.Conventions.Media.File, Uri? current = null) { - if (culture == null) + if (propertyAlias == null) { - culture = _variationContextAccessor?.VariationContext?.Culture ?? string.Empty; + throw new ArgumentNullException(nameof(propertyAlias)); } - } - if (current == null) - { - IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); - current = umbracoContext.CleanedUmbracoUrl; - } + if (content == null) + { + return string.Empty; + } - UrlInfo? url = _mediaUrlProviders.Select(provider => - provider.GetMediaUrl(content, propertyAlias, mode, culture, current)) - .FirstOrDefault(u => u is not null); + if (mode == UrlMode.Default) + { + mode = Mode; + } - return url?.Text ?? string.Empty; - } + // this the ONLY place where we deal with default culture - IMediaUrlProvider always receive a culture + // be nice with tests, assume things can be null, ultimately fall back to invariant + // (but only for variant content of course) + if (content.ContentType.VariesByCulture()) + { + if (culture == null) + { + culture = _variationContextAccessor?.VariationContext?.Culture ?? string.Empty; + } + } + + if (current == null) + { + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + current = umbracoContext.CleanedUmbracoUrl; + } - #endregion + + UrlInfo? url = _mediaUrlProviders.Select(provider => + provider.GetMediaUrl(content, propertyAlias, mode, culture, current)) + .FirstOrDefault(u => u is not null); + + return url?.Text ?? string.Empty; + } + + #endregion + } } diff --git a/src/Umbraco.Core/Routing/UrlProviderCollectionBuilder.cs b/src/Umbraco.Core/Routing/UrlProviderCollectionBuilder.cs index 441f687c59d2..fe975272dde3 100644 --- a/src/Umbraco.Core/Routing/UrlProviderCollectionBuilder.cs +++ b/src/Umbraco.Core/Routing/UrlProviderCollectionBuilder.cs @@ -1,9 +1,8 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Routing; -public class UrlProviderCollectionBuilder : OrderedCollectionBuilderBase +public class UrlProviderCollectionBuilder : OrderedCollectionBuilderBase { protected override UrlProviderCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/Runtime/MainDom.cs b/src/Umbraco.Core/Runtime/MainDom.cs index d90042b0d410..83736914a2ba 100644 --- a/src/Umbraco.Core/Runtime/MainDom.cs +++ b/src/Umbraco.Core/Runtime/MainDom.cs @@ -3,289 +3,294 @@ using Umbraco.Cms.Core.Hosting; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Runtime; - -/// -/// Provides the full implementation of . -/// -/// -/// When an AppDomain starts, it tries to acquire the main domain status. -/// When an AppDomain stops (eg the application is restarting) it should release the main domain status. -/// -public class MainDom : IMainDom, IRegisteredObject, IDisposable +namespace Umbraco.Cms.Core.Runtime { - #region Ctor - // initializes a new instance of MainDom - public MainDom(ILogger logger, IMainDomLock systemLock) + /// + /// Provides the full implementation of . + /// + /// + /// When an AppDomain starts, it tries to acquire the main domain status. + /// When an AppDomain stops (eg the application is restarting) it should release the main domain status. + /// + public class MainDom : IMainDom, IRegisteredObject, IDisposable { - _logger = logger; - _mainDomLock = systemLock; - } + #region Vars - #endregion + private readonly ILogger _logger; + private IApplicationShutdownRegistry? _hostingEnvironment; + private readonly IMainDomLock _mainDomLock; - /// - public bool Acquire(IApplicationShutdownRegistry hostingEnvironment) - { - _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + // our own lock for local consistency + private object _locko = new(); + + private bool _isInitialized; + // indicates whether... + private bool? _isMainDom; // we are the main domain + private volatile bool _signaled; // we have been signaled + + // actions to run before releasing the main domain + private readonly List> _callbacks = new(); - return LazyInitializer.EnsureInitialized(ref _isMainDom, ref _isInitialized, ref _locko, () => + private const int LockTimeoutMilliseconds = 40000; // 40 seconds + + private Task? _listenTask; + private Task? _listenCompleteTask; + + #endregion + + #region Ctor + + // initializes a new instance of MainDom + public MainDom(ILogger logger, IMainDomLock systemLock) { - hostingEnvironment.RegisterObject(this); - return Acquire(); - })!.Value; - } + _logger = logger; + _mainDomLock = systemLock; + } - /// - /// Registers a resource that requires the current AppDomain to be the main domain to function. - /// - /// An action to execute when registering. - /// An action to execute before the AppDomain releases the main domain status. - /// An optional weight (lower goes first). - /// A value indicating whether it was possible to register. - /// - /// If registering is successful, then the action - /// is guaranteed to execute before the AppDomain releases the main domain status. - /// - public bool Register(Action? install = null, Action? release = null, int weight = 100) - { - lock (_locko) + #endregion + + /// + public bool Acquire(IApplicationShutdownRegistry hostingEnvironment) { - if (_signaled) - { - return false; - } + _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); - if (_isMainDom.HasValue == false) + return LazyInitializer.EnsureInitialized(ref _isMainDom, ref _isInitialized, ref _locko, () => { - throw new InvalidOperationException("Register called before IsMainDom has been established"); - } + hostingEnvironment.RegisterObject(this); + return Acquire(); + })!.Value; + } - if (_isMainDom == false) + /// + /// Registers a resource that requires the current AppDomain to be the main domain to function. + /// + /// An action to execute when registering. + /// An action to execute before the AppDomain releases the main domain status. + /// An optional weight (lower goes first). + /// A value indicating whether it was possible to register. + /// If registering is successful, then the action + /// is guaranteed to execute before the AppDomain releases the main domain status. + public bool Register(Action? install = null, Action? release = null, int weight = 100) + { + lock (_locko) { - _logger.LogWarning("Register called when MainDom has not been acquired"); - return false; - } + if (_signaled) + { + return false; + } - install?.Invoke(); - if (release != null) - { - _callbacks.Add(new KeyValuePair(weight, release)); - } + if (_isMainDom.HasValue == false) + { + throw new InvalidOperationException("Register called before IsMainDom has been established"); + } + else if (_isMainDom == false) + { + _logger.LogWarning("Register called when MainDom has not been acquired"); + return false; + } - return true; + install?.Invoke(); + if (release != null) + { + _callbacks.Add(new KeyValuePair(weight, release)); + } + + return true; + } } - } - /// - /// Gets a value indicating whether the current domain is the main domain. - /// - /// - /// Acquire must be called first else this will always return false - /// - public bool IsMainDom - { - get + // handles the signal requesting that the main domain is released + private void OnSignal(string source) { - if (!_isMainDom.HasValue) + // once signaled, we stop waiting, but then there is the hosting environment + // so we have to make sure that we only enter that method once + + lock (_locko) { - throw new InvalidOperationException("IsMainDom has not been established yet"); - } + _logger.LogDebug("Signaled ({Signaled}) ({SignalSource})", _signaled ? "again" : "first", source); + if (_signaled) + { + return; + } - return _isMainDom.Value; - } - } + if (_isMainDom == false) + { + return; // probably not needed + } - // IRegisteredObject - void IRegisteredObject.Stop(bool immediate) - { - OnSignal("environment"); // will run once + _signaled = true; + + try + { + _logger.LogInformation("Stopping ({SignalSource})", source); + foreach (Action callback in _callbacks.OrderBy(x => x.Key).Select(x => x.Value)) + { + try + { + callback(); // no timeout on callbacks + } + catch (Exception e) + { + _logger.LogError(e, "Error while running callback"); + continue; + } + } - // The web app is stopping, need to wind down - Dispose(true); + _logger.LogDebug("Stopped ({SignalSource})", source); + } + finally + { + // in any case... + _isMainDom = false; + _mainDomLock.Dispose(); + _logger.LogInformation("Released ({SignalSource})", source); + } - _hostingEnvironment?.UnregisterObject(this); - } + } + } - // handles the signal requesting that the main domain is released - private void OnSignal(string source) - { - // once signaled, we stop waiting, but then there is the hosting environment - // so we have to make sure that we only enter that method once - lock (_locko) + // acquires the main domain + private bool Acquire() { - _logger.LogDebug("Signaled ({Signaled}) ({SignalSource})", _signaled ? "again" : "first", source); + // if signaled, too late to acquire, give up + // the handler is not installed so that would be the hosting environment if (_signaled) { - return; + _logger.LogInformation("Cannot acquire MainDom (signaled)."); + return false; } - if (_isMainDom == false) + _logger.LogInformation("Acquiring MainDom."); + + // Get the lock + var acquired = false; + try { - return; // probably not needed + acquired = _mainDomLock.AcquireLockAsync(LockTimeoutMilliseconds).GetAwaiter().GetResult(); } + catch (Exception ex) + { + _logger.LogError(ex, "Error while acquiring MainDom"); + } + + if (!acquired) + { + _logger.LogInformation("Cannot acquire MainDom (timeout)."); + + // In previous versions we'd let a TimeoutException be thrown + // and the appdomain would not start. We have the opportunity to allow it to + // start without having MainDom? This would mean that it couldn't write + // to nucache/examine and would only be ok if this was a super short lived appdomain. + // maybe safer to just keep throwing in this case. - _signaled = true; + throw new TimeoutException("Cannot acquire MainDom"); + // return false; + } try { - _logger.LogInformation("Stopping ({SignalSource})", source); - foreach (Action callback in _callbacks.OrderBy(x => x.Key).Select(x => x.Value)) + // Listen for the signal from another AppDomain coming online to release the lock + _listenTask = _mainDomLock.ListenAsync(); + _listenCompleteTask = _listenTask.ContinueWith( + t => { - try + if (_listenTask.Exception != null) { - callback(); // no timeout on callbacks + _logger.LogWarning("Listening task completed with {TaskStatus}, Exception: {Exception}", _listenTask.Status, _listenTask.Exception); } - catch (Exception e) + else { - _logger.LogError(e, "Error while running callback"); + _logger.LogDebug("Listening task completed with {TaskStatus}", _listenTask.Status); } - } - _logger.LogDebug("Stopped ({SignalSource})", source); + OnSignal("signal"); + }, + TaskScheduler.Default); // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html } - finally + catch (OperationCanceledException ex) { - // in any case... - _isMainDom = false; - _mainDomLock.Dispose(); - _logger.LogInformation("Released ({SignalSource})", source); + // the waiting task could be canceled if this appdomain is naturally shutting down, we'll just swallow this exception + _logger.LogWarning(ex, ex.Message); } - } - } - // acquires the main domain - private bool Acquire() - { - // if signaled, too late to acquire, give up - // the handler is not installed so that would be the hosting environment - if (_signaled) - { - _logger.LogInformation("Cannot acquire MainDom (signaled)."); - return false; + _logger.LogInformation("Acquired MainDom."); + return true; } - _logger.LogInformation("Acquiring MainDom."); - - // Get the lock - var acquired = false; - try - { - acquired = _mainDomLock.AcquireLockAsync(LockTimeoutMilliseconds).GetAwaiter().GetResult(); - } - catch (Exception ex) + /// + /// Gets a value indicating whether the current domain is the main domain. + /// + /// + /// Acquire must be called first else this will always return false + /// + public bool IsMainDom { - _logger.LogError(ex, "Error while acquiring MainDom"); + get + { + if (!_isMainDom.HasValue) + { + throw new InvalidOperationException("IsMainDom has not been established yet"); + } + return _isMainDom.Value; + } } - if (!acquired) + // IRegisteredObject + void IRegisteredObject.Stop(bool immediate) { - _logger.LogInformation("Cannot acquire MainDom (timeout)."); + OnSignal("environment"); // will run once - // In previous versions we'd let a TimeoutException be thrown - // and the appdomain would not start. We have the opportunity to allow it to - // start without having MainDom? This would mean that it couldn't write - // to nucache/examine and would only be ok if this was a super short lived appdomain. - // maybe safer to just keep throwing in this case. - throw new TimeoutException("Cannot acquire MainDom"); + // The web app is stopping, need to wind down + Dispose(true); - // return false; + _hostingEnvironment?.UnregisterObject(this); } - try + #region IDisposable Support + + // This code added to correctly implement the disposable pattern. + + private bool _disposedValue; // To detect redundant calls + + protected virtual void Dispose(bool disposing) { - // Listen for the signal from another AppDomain coming online to release the lock - _listenTask = _mainDomLock.ListenAsync(); - _listenCompleteTask = _listenTask.ContinueWith( - t => + if (!_disposedValue) { - if (_listenTask.Exception != null) + if (disposing) { - _logger.LogWarning("Listening task completed with {TaskStatus}, Exception: {Exception}", _listenTask.Status, _listenTask.Exception); - } - else - { - _logger.LogDebug("Listening task completed with {TaskStatus}", _listenTask.Status); + _mainDomLock.Dispose(); } - OnSignal("signal"); - }, - TaskScheduler.Default); // Must explicitly specify this, see https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html + _disposedValue = true; + } } - catch (OperationCanceledException ex) + + public void Dispose() { - // the waiting task could be canceled if this appdomain is naturally shutting down, we'll just swallow this exception - _logger.LogWarning(ex, ex.Message); + Dispose(true); } - _logger.LogInformation("Acquired MainDom."); - return true; - } - - public static string GetMainDomId(IHostingEnvironment hostingEnvironment) - { - // HostingEnvironment.ApplicationID is null in unit tests, making ReplaceNonAlphanumericChars fail - var appId = hostingEnvironment.ApplicationId?.ReplaceNonAlphanumericChars(string.Empty) ?? string.Empty; - - // combining with the physical path because if running on eg IIS Express, - // two sites could have the same appId even though they are different. - // - // now what could still collide is... two sites, running in two different processes - // and having the same appId, and running on the same app physical path - // - // we *cannot* use the process ID here because when an AppPool restarts it is - // a new process for the same application path - var appPath = hostingEnvironment.ApplicationPhysicalPath?.ToLowerInvariant() ?? string.Empty; - var hash = (appId + ":::" + appPath).GenerateHash(); - - return hash; - } - - #region Vars - - private readonly ILogger _logger; - private IApplicationShutdownRegistry? _hostingEnvironment; - private readonly IMainDomLock _mainDomLock; - - // our own lock for local consistency - private object _locko = new(); + #endregion - private bool _isInitialized; - - // indicates whether... - private bool? _isMainDom; // we are the main domain - private volatile bool _signaled; // we have been signaled - - // actions to run before releasing the main domain - private readonly List> _callbacks = new(); - - private const int LockTimeoutMilliseconds = 40000; // 40 seconds - - private Task? _listenTask; - private Task? _listenCompleteTask; - - #endregion - - #region IDisposable Support - - // This code added to correctly implement the disposable pattern. - private bool disposedValue; // To detect redundant calls - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) + public static string GetMainDomId(IHostingEnvironment hostingEnvironment) { - if (disposing) - { - _mainDomLock.Dispose(); - } - - disposedValue = true; + // HostingEnvironment.ApplicationID is null in unit tests, making ReplaceNonAlphanumericChars fail + var appId = hostingEnvironment.ApplicationId?.ReplaceNonAlphanumericChars(string.Empty) ?? string.Empty; + + // combining with the physical path because if running on eg IIS Express, + // two sites could have the same appId even though they are different. + // + // now what could still collide is... two sites, running in two different processes + // and having the same appId, and running on the same app physical path + // + // we *cannot* use the process ID here because when an AppPool restarts it is + // a new process for the same application path + + var appPath = hostingEnvironment.ApplicationPhysicalPath?.ToLowerInvariant() ?? string.Empty; + var hash = (appId + ":::" + appPath).GenerateHash(); + + return hash; } } - - public void Dispose() => Dispose(true); - - #endregion } diff --git a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs index 597346c34bc4..3b53509240c0 100644 --- a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs +++ b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs @@ -133,8 +133,8 @@ public string ParseStoredHashPassword(string algorithm, string storedString, out } var saltLen = GenerateSalt(); - salt = storedString[..saltLen.Length]; - return storedString[saltLen.Length..]; + salt = storedString.Substring(0, saltLen.Length); + return storedString.Substring(saltLen.Length); } public bool SupportHashAlgorithm(string algorithm) diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 4c40aa45408c..1fdbb4a79b6e 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -15,244 +15,438 @@ using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services.Implement; - -/// -/// Represents the DataType Service, which is an easy access to operations involving -/// -public class DataTypeService : RepositoryService, IDataTypeService +namespace Umbraco.Cms.Core.Services.Implement { - private readonly IAuditRepository _auditRepository; - private readonly IContentTypeRepository _contentTypeRepository; - private readonly IDataTypeContainerRepository _dataTypeContainerRepository; - private readonly IDataTypeRepository _dataTypeRepository; - private readonly IDataValueEditorFactory _dataValueEditorFactory; - private readonly IEditorConfigurationParser _editorConfigurationParser; - private readonly IEntityRepository _entityRepository; - private readonly IIOHelper _ioHelper; - - public DataTypeService( - IDataValueEditorFactory dataValueEditorFactory, - ICoreScopeProvider provider, - ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, - IDataTypeRepository dataTypeRepository, - IDataTypeContainerRepository dataTypeContainerRepository, - IAuditRepository auditRepository, - IEntityRepository entityRepository, - IContentTypeRepository contentTypeRepository, - IIOHelper ioHelper, - IEditorConfigurationParser editorConfigurationParser) - : base(provider, loggerFactory, eventMessagesFactory) + /// + /// Represents the DataType Service, which is an easy access to operations involving + /// + public class DataTypeService : RepositoryService, IDataTypeService { - _dataValueEditorFactory = dataValueEditorFactory; - _dataTypeRepository = dataTypeRepository; - _dataTypeContainerRepository = dataTypeContainerRepository; - _auditRepository = auditRepository; - _entityRepository = entityRepository; - _contentTypeRepository = contentTypeRepository; - _ioHelper = ioHelper; - _editorConfigurationParser = editorConfigurationParser; - } + private readonly IDataValueEditorFactory _dataValueEditorFactory; + private readonly IDataTypeRepository _dataTypeRepository; + private readonly IDataTypeContainerRepository _dataTypeContainerRepository; + private readonly IContentTypeRepository _contentTypeRepository; + private readonly IAuditRepository _auditRepository; + private readonly IEntityRepository _entityRepository; + private readonly IIOHelper _ioHelper; + private readonly ILocalizedTextService _localizedTextService; + private readonly ILocalizationService _localizationService; + private readonly IShortStringHelper _shortStringHelper; + private readonly IJsonSerializer _jsonSerializer; + private readonly IEditorConfigurationParser _editorConfigurationParser; + + [Obsolete("Please use constructor that takes an ")] + public DataTypeService( + IDataValueEditorFactory dataValueEditorFactory, + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IDataTypeRepository dataTypeRepository, + IDataTypeContainerRepository dataTypeContainerRepository, + IAuditRepository auditRepository, + IEntityRepository entityRepository, + IContentTypeRepository contentTypeRepository, + IIOHelper ioHelper, + ILocalizedTextService localizedTextService, + ILocalizationService localizationService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer) + : this( + dataValueEditorFactory, + provider, + loggerFactory, + eventMessagesFactory, + dataTypeRepository, + dataTypeContainerRepository, + auditRepository, + entityRepository, + contentTypeRepository, + ioHelper, + localizedTextService, + localizationService, + shortStringHelper, + jsonSerializer, + StaticServiceProvider.Instance.GetRequiredService()) + { + _dataValueEditorFactory = dataValueEditorFactory; + _dataTypeRepository = dataTypeRepository; + _dataTypeContainerRepository = dataTypeContainerRepository; + _auditRepository = auditRepository; + _entityRepository = entityRepository; + _contentTypeRepository = contentTypeRepository; + _ioHelper = ioHelper; + _localizedTextService = localizedTextService; + _localizationService = localizationService; + _shortStringHelper = shortStringHelper; + _jsonSerializer = jsonSerializer; + } - [Obsolete("Please use constructor that takes an IEditorConfigurationParser instead")] - public DataTypeService( - IDataValueEditorFactory dataValueEditorFactory, - ICoreScopeProvider provider, - ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, - IDataTypeRepository dataTypeRepository, - IDataTypeContainerRepository dataTypeContainerRepository, - IAuditRepository auditRepository, - IEntityRepository entityRepository, - IContentTypeRepository contentTypeRepository, - IIOHelper ioHelper, - ILocalizedTextService localizedTextService, - ILocalizationService localizationService, - IShortStringHelper shortStringHelper, - IJsonSerializer jsonSerializer) - : this( - dataValueEditorFactory, - provider, - loggerFactory, - eventMessagesFactory, - dataTypeRepository, - dataTypeContainerRepository, - auditRepository, - entityRepository, - contentTypeRepository, - ioHelper, - localizedTextService, - localizationService, - shortStringHelper, - jsonSerializer, - StaticServiceProvider.Instance.GetRequiredService()) - { - } + public DataTypeService( + IDataValueEditorFactory dataValueEditorFactory, + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IDataTypeRepository dataTypeRepository, + IDataTypeContainerRepository dataTypeContainerRepository, + IAuditRepository auditRepository, + IEntityRepository entityRepository, + IContentTypeRepository contentTypeRepository, + IIOHelper ioHelper, + ILocalizedTextService localizedTextService, + ILocalizationService localizationService, + IShortStringHelper shortStringHelper, + IJsonSerializer jsonSerializer, + IEditorConfigurationParser editorConfigurationParser) + : base(provider, loggerFactory, eventMessagesFactory) + { + _dataValueEditorFactory = dataValueEditorFactory; + _dataTypeRepository = dataTypeRepository; + _dataTypeContainerRepository = dataTypeContainerRepository; + _auditRepository = auditRepository; + _entityRepository = entityRepository; + _contentTypeRepository = contentTypeRepository; + _ioHelper = ioHelper; + _localizedTextService = localizedTextService; + _localizationService = localizationService; + _shortStringHelper = shortStringHelper; + _jsonSerializer = jsonSerializer; + _editorConfigurationParser = editorConfigurationParser; + } - [Obsolete("Please use constructor that takes an does not take ILocalizedTextService, ILocalizationService, IShortStringHelper & IJsonSerializer instead")] - public DataTypeService( - IDataValueEditorFactory dataValueEditorFactory, - ICoreScopeProvider provider, - ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, - IDataTypeRepository dataTypeRepository, - IDataTypeContainerRepository dataTypeContainerRepository, - IAuditRepository auditRepository, - IEntityRepository entityRepository, - IContentTypeRepository contentTypeRepository, - IIOHelper ioHelper, - ILocalizedTextService localizedTextService, - ILocalizationService localizationService, - IShortStringHelper shortStringHelper, - IJsonSerializer jsonSerializer, - IEditorConfigurationParser editorConfigurationParser) - : this(dataValueEditorFactory, provider, loggerFactory, eventMessagesFactory, dataTypeRepository, dataTypeContainerRepository, auditRepository, entityRepository, contentTypeRepository, ioHelper, editorConfigurationParser) - { - } + #region Containers - /// - /// Gets a by its Name - /// - /// Name of the - /// - /// - /// - public IDataType? GetDataType(string name) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + public Attempt?> CreateContainer(int parentId, Guid key, string name, int userId = Constants.Security.SuperUserId) { - IDataType? dataType = _dataTypeRepository.Get(Query().Where(x => x.Name == name)).FirstOrDefault(); + EventMessages evtMsgs = EventMessagesFactory.Get(); + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + try + { + var container = new EntityContainer(Constants.ObjectTypes.DataType) + { + Name = name, + ParentId = parentId, + CreatorId = userId, + Key = key + }; + + var savingEntityContainerNotification = new EntityContainerSavingNotification(container, evtMsgs); + if (scope.Notifications.PublishCancelable(savingEntityContainerNotification)) + { + scope.Complete(); + return OperationResult.Attempt.Cancel(evtMsgs, container); + } + + _dataTypeContainerRepository.Save(container); + scope.Complete(); + + scope.Notifications.Publish(new EntityContainerSavedNotification(container, evtMsgs).WithStateFrom(savingEntityContainerNotification)); + + // TODO: Audit trail ? + + return OperationResult.Attempt.Succeed(evtMsgs, container); + } + catch (Exception ex) + { + return OperationResult.Attempt.Fail(evtMsgs, ex); + } + } + } + + public EntityContainer? GetContainer(int containerId) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + return _dataTypeContainerRepository.Get(containerId); + } + + public EntityContainer? GetContainer(Guid containerId) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + return _dataTypeContainerRepository.Get(containerId); + } + + public IEnumerable GetContainers(string name, int level) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + return _dataTypeContainerRepository.Get(name, level); + } + + public IEnumerable GetContainers(IDataType dataType) + { + var ancestorIds = dataType.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) + .Select(x => + { + Attempt asInt = x.TryConvertTo(); + return asInt.Success ? asInt.Result : int.MinValue; + }) + .Where(x => x != int.MinValue && x != dataType.Id) + .ToArray(); + + return GetContainers(ancestorIds); + } + + public IEnumerable GetContainers(int[] containerIds) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + return _dataTypeContainerRepository.GetMany(containerIds); + } + + public Attempt SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId) + { + EventMessages evtMsgs = EventMessagesFactory.Get(); + + if (container.ContainedObjectType != Constants.ObjectTypes.DataType) + { + var ex = new InvalidOperationException("Not a " + Constants.ObjectTypes.DataType + " container."); + return OperationResult.Attempt.Fail(evtMsgs, ex); + } + + if (container.HasIdentity && container.IsPropertyDirty("ParentId")) + { + var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); + return OperationResult.Attempt.Fail(evtMsgs, ex); + } + + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + var savingEntityContainerNotification = new EntityContainerSavingNotification(container, evtMsgs); + if (scope.Notifications.PublishCancelable(savingEntityContainerNotification)) + { + scope.Complete(); + return OperationResult.Attempt.Cancel(evtMsgs); + } + + _dataTypeContainerRepository.Save(container); + + scope.Notifications.Publish(new EntityContainerSavedNotification(container, evtMsgs).WithStateFrom(savingEntityContainerNotification)); + scope.Complete(); + } + + // TODO: Audit trail ? + return OperationResult.Attempt.Succeed(evtMsgs); + } + + public Attempt DeleteContainer(int containerId, int userId = Constants.Security.SuperUserId) + { + EventMessages evtMsgs = EventMessagesFactory.Get(); + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + EntityContainer? container = _dataTypeContainerRepository.Get(containerId); + if (container == null) + { + return OperationResult.Attempt.NoOperation(evtMsgs); + } + + // 'container' here does not know about its children, so we need + // to get it again from the entity repository, as a light entity + IEntitySlim? entity = _entityRepository.Get(container.Id); + if (entity?.HasChildren ?? false) + { + scope.Complete(); + return Attempt.Fail(new OperationResult(OperationResultType.FailedCannot, evtMsgs)); + } + + var deletingEntityContainerNotification = new EntityContainerDeletingNotification(container, evtMsgs); + if (scope.Notifications.PublishCancelable(deletingEntityContainerNotification)) + { + scope.Complete(); + return Attempt.Fail(new OperationResult(OperationResultType.FailedCancelledByEvent, evtMsgs)); + } + + _dataTypeContainerRepository.Delete(container); + + scope.Notifications.Publish(new EntityContainerDeletedNotification(container, evtMsgs).WithStateFrom(deletingEntityContainerNotification)); + scope.Complete(); + } + + // TODO: Audit trail ? + return OperationResult.Attempt.Succeed(evtMsgs); + } + + public Attempt?> RenameContainer(int id, string name, int userId = Constants.Security.SuperUserId) + { + EventMessages evtMsgs = EventMessagesFactory.Get(); + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + try + { + EntityContainer? container = _dataTypeContainerRepository.Get(id); + + //throw if null, this will be caught by the catch and a failed returned + if (container == null) + { + throw new InvalidOperationException("No container found with id " + id); + } + + container.Name = name; + + var renamingEntityContainerNotification = new EntityContainerRenamingNotification(container, evtMsgs); + if (scope.Notifications.PublishCancelable(renamingEntityContainerNotification)) + { + scope.Complete(); + return OperationResult.Attempt.Cancel(evtMsgs, container); + } + + _dataTypeContainerRepository.Save(container); + scope.Complete(); + + scope.Notifications.Publish(new EntityContainerRenamedNotification(container, evtMsgs).WithStateFrom(renamingEntityContainerNotification)); + + return OperationResult.Attempt.Succeed(OperationResultType.Success, evtMsgs, container); + } + catch (Exception ex) + { + return OperationResult.Attempt.Fail(evtMsgs, ex); + } + } + } + + #endregion + + /// + /// Gets a by its Name + /// + /// Name of the + /// + public IDataType? GetDataType(string name) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + IDataType? dataType = _dataTypeRepository.Get(Query().Where(x => x.Name == name))?.FirstOrDefault(); ConvertMissingEditorOfDataTypeToLabel(dataType); return dataType; } - } - /// - /// Gets a by its Id - /// - /// Id of the - /// - /// - /// - public IDataType? GetDataType(int id) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Gets a by its Id + /// + /// Id of the + /// + public IDataType? GetDataType(int id) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); IDataType? dataType = _dataTypeRepository.Get(id); ConvertMissingEditorOfDataTypeToLabel(dataType); return dataType; } - } - /// - /// Gets a by its unique guid Id - /// - /// Unique guid Id of the DataType - /// - /// - /// - public IDataType? GetDataType(Guid id) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Gets a by its unique guid Id + /// + /// Unique guid Id of the DataType + /// + public IDataType? GetDataType(Guid id) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); IQuery query = Query().Where(x => x.Key == id); IDataType? dataType = _dataTypeRepository.Get(query).FirstOrDefault(); ConvertMissingEditorOfDataTypeToLabel(dataType); return dataType; } - } - /// - /// Gets a by its control Id - /// - /// Alias of the property editor - /// Collection of objects with a matching control id - public IEnumerable GetByEditorAlias(string propertyEditorAlias) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Gets a by its control Id + /// + /// Alias of the property editor + /// Collection of objects with a matching control id + public IEnumerable GetByEditorAlias(string propertyEditorAlias) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); IQuery query = Query().Where(x => x.EditorAlias == propertyEditorAlias); - IEnumerable dataType = _dataTypeRepository.Get(query); + IEnumerable dataType = _dataTypeRepository.Get(query).ToArray(); ConvertMissingEditorsOfDataTypesToLabels(dataType); return dataType; } - } - /// - /// Gets all objects or those with the ids passed in - /// - /// Optional array of Ids - /// An enumerable list of objects - public IEnumerable GetAll(params int[] ids) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Gets all objects or those with the ids passed in + /// + /// Optional array of Ids + /// An enumerable list of objects + public IEnumerable GetAll(params int[] ids) { - IEnumerable dataTypes = _dataTypeRepository.GetMany(ids); + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + IEnumerable dataTypes = _dataTypeRepository.GetMany(ids).ToArray(); ConvertMissingEditorsOfDataTypesToLabels(dataTypes); return dataTypes; } - } - public Attempt?> Move(IDataType toMove, int parentId) - { - EventMessages evtMsgs = EventMessagesFactory.Get(); - var moveInfo = new List>(); - - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + private void ConvertMissingEditorOfDataTypeToLabel(IDataType? dataType) { - var moveEventInfo = new MoveEventInfo(toMove, toMove.Path, parentId); + if (dataType == null) + { + return; + } - var movingDataTypeNotification = new DataTypeMovingNotification(moveEventInfo, evtMsgs); - if (scope.Notifications.PublishCancelable(movingDataTypeNotification)) + ConvertMissingEditorsOfDataTypesToLabels(new[] { dataType }); + } + + private void ConvertMissingEditorsOfDataTypesToLabels(IEnumerable dataTypes) + { + // Any data types that don't have an associated editor are created of a specific type. + // We convert them to labels to make clear to the user why the data type cannot be used. + IEnumerable dataTypesWithMissingEditors = dataTypes + .Where(x => x.Editor is MissingPropertyEditor); + foreach (IDataType dataType in dataTypesWithMissingEditors) { - scope.Complete(); - return OperationResult.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs); + dataType.Editor = new LabelPropertyEditor(_dataValueEditorFactory, _ioHelper, _editorConfigurationParser); } + } - try + public Attempt?> Move(IDataType toMove, int parentId) + { + EventMessages evtMsgs = EventMessagesFactory.Get(); + var moveInfo = new List>(); + + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - EntityContainer? container = null; - if (parentId > 0) + var moveEventInfo = new MoveEventInfo(toMove, toMove.Path, parentId); + + var movingDataTypeNotification = new DataTypeMovingNotification(moveEventInfo, evtMsgs); + if (scope.Notifications.PublishCancelable(movingDataTypeNotification)) { - container = _dataTypeContainerRepository.Get(parentId); - if (container == null) - { - throw new DataOperationException(MoveOperationStatusType - .FailedParentNotFound); // causes rollback - } + scope.Complete(); + return OperationResult.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, evtMsgs); } - moveInfo.AddRange(_dataTypeRepository.Move(toMove, container)); + try + { + EntityContainer? container = null; + if (parentId > 0) + { + container = _dataTypeContainerRepository.Get(parentId); + if (container == null) + { + throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); // causes rollback + } + } + moveInfo.AddRange(_dataTypeRepository.Move(toMove, container)); - scope.Notifications.Publish( - new DataTypeMovedNotification(moveEventInfo, evtMsgs).WithStateFrom(movingDataTypeNotification)); + scope.Notifications.Publish(new DataTypeMovedNotification(moveEventInfo, evtMsgs).WithStateFrom(movingDataTypeNotification)); - scope.Complete(); - } - catch (DataOperationException ex) - { - scope.Complete(); // TODO: what are we doing here exactly? - return OperationResult.Attempt.Fail(ex.Operation, evtMsgs); + scope.Complete(); + } + catch (DataOperationException ex) + { + scope.Complete(); // TODO: what are we doing here exactly? + return OperationResult.Attempt.Fail(ex.Operation, evtMsgs); + } } - } - - return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs); - } - /// - /// Saves an - /// - /// to save - /// Id of the user issuing the save - public void Save(IDataType dataType, int userId = Constants.Security.SuperUserId) - { - EventMessages evtMsgs = EventMessagesFactory.Get(); - dataType.CreatorId = userId; + return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, evtMsgs); + } - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + /// + /// Saves an + /// + /// to save + /// Id of the user issuing the save + public void Save(IDataType dataType, int userId = Constants.Security.SuperUserId) { + EventMessages evtMsgs = EventMessagesFactory.Get(); + dataType.CreatorId = userId; + + using ICoreScope scope = ScopeProvider.CreateCoreScope(); var saveEventArgs = new SaveEventArgs(dataType); var savingDataTypeNotification = new DataTypeSavingNotification(dataType, evtMsgs); @@ -274,26 +468,23 @@ public void Save(IDataType dataType, int userId = Constants.Security.SuperUserId _dataTypeRepository.Save(dataType); - scope.Notifications.Publish( - new DataTypeSavedNotification(dataType, evtMsgs).WithStateFrom(savingDataTypeNotification)); + scope.Notifications.Publish(new DataTypeSavedNotification(dataType, evtMsgs).WithStateFrom(savingDataTypeNotification)); Audit(AuditType.Save, userId, dataType.Id); scope.Complete(); } - } - /// - /// Saves a collection of - /// - /// to save - /// Id of the user issuing the save - public void Save(IEnumerable dataTypeDefinitions, int userId) - { - EventMessages evtMsgs = EventMessagesFactory.Get(); - IDataType[] dataTypeDefinitionsA = dataTypeDefinitions.ToArray(); - - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + /// + /// Saves a collection of + /// + /// to save + /// Id of the user issuing the save + public void Save(IEnumerable dataTypeDefinitions, int userId) { + EventMessages evtMsgs = EventMessagesFactory.Get(); + IDataType[] dataTypeDefinitionsA = dataTypeDefinitions.ToArray(); + + using ICoreScope scope = ScopeProvider.CreateCoreScope(); var savingDataTypeNotification = new DataTypeSavingNotification(dataTypeDefinitions, evtMsgs); if (scope.Notifications.PublishCancelable(savingDataTypeNotification)) { @@ -307,29 +498,26 @@ public void Save(IEnumerable dataTypeDefinitions, int userId) _dataTypeRepository.Save(dataTypeDefinition); } - scope.Notifications.Publish( - new DataTypeSavedNotification(dataTypeDefinitions, evtMsgs).WithStateFrom(savingDataTypeNotification)); + scope.Notifications.Publish(new DataTypeSavedNotification(dataTypeDefinitions, evtMsgs).WithStateFrom(savingDataTypeNotification)); Audit(AuditType.Save, userId, -1); scope.Complete(); } - } - /// - /// Deletes an - /// - /// - /// Please note that deleting a will remove - /// all the data that references this . - /// - /// to delete - /// Optional Id of the user issuing the deletion - public void Delete(IDataType dataType, int userId = Constants.Security.SuperUserId) - { - EventMessages evtMsgs = EventMessagesFactory.Get(); - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + /// + /// Deletes an + /// + /// + /// Please note that deleting a will remove + /// all the data that references this . + /// + /// to delete + /// Optional Id of the user issuing the deletion + public void Delete(IDataType dataType, int userId = Constants.Security.SuperUserId) { + EventMessages evtMsgs = EventMessagesFactory.Get(); + using ICoreScope scope = ScopeProvider.CreateCoreScope(); var deletingDataTypeNotification = new DataTypeDeletingNotification(dataType, evtMsgs); if (scope.Notifications.PublishCancelable(deletingDataTypeNotification)) { @@ -363,258 +551,29 @@ public void Delete(IDataType dataType, int userId = Constants.Security.SuperUser // // what IS weird is that a content type is losing a property and we do NOT raise any // content type event... so ppl better listen on the data type events too. + _contentTypeRepository.Save(contentType); } _dataTypeRepository.Delete(dataType); - scope.Notifications.Publish( - new DataTypeDeletedNotification(dataType, evtMsgs).WithStateFrom(deletingDataTypeNotification)); + scope.Notifications.Publish(new DataTypeDeletedNotification(dataType, evtMsgs).WithStateFrom(deletingDataTypeNotification)); Audit(AuditType.Delete, userId, dataType.Id); scope.Complete(); } - } - public IReadOnlyDictionary> GetReferences(int id) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + public IReadOnlyDictionary> GetReferences(int id) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete:true); return _dataTypeRepository.FindUsages(id); } - } - - #region Containers - - public Attempt?> CreateContainer(int parentId, Guid key, string name, int userId = Constants.Security.SuperUserId) - { - EventMessages evtMsgs = EventMessagesFactory.Get(); - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { - try - { - var container = new EntityContainer(Constants.ObjectTypes.DataType) - { - Name = name, - ParentId = parentId, - CreatorId = userId, - Key = key, - }; - - var savingEntityContainerNotification = new EntityContainerSavingNotification(container, evtMsgs); - if (scope.Notifications.PublishCancelable(savingEntityContainerNotification)) - { - scope.Complete(); - return OperationResult.Attempt.Cancel(evtMsgs, container); - } - - _dataTypeContainerRepository.Save(container); - scope.Complete(); - - scope.Notifications.Publish( - new EntityContainerSavedNotification(container, evtMsgs).WithStateFrom( - savingEntityContainerNotification)); - - // TODO: Audit trail ? - return OperationResult.Attempt.Succeed(evtMsgs, container); - } - catch (Exception ex) - { - return OperationResult.Attempt.Fail(evtMsgs, ex); - } - } - } - - private void ConvertMissingEditorOfDataTypeToLabel(IDataType? dataType) - { - if (dataType == null) - { - return; - } - - ConvertMissingEditorsOfDataTypesToLabels(new[] { dataType }); - } - - private void ConvertMissingEditorsOfDataTypesToLabels(IEnumerable dataTypes) - { - // Any data types that don't have an associated editor are created of a specific type. - // We convert them to labels to make clear to the user why the data type cannot be used. - IEnumerable dataTypesWithMissingEditors = dataTypes - .Where(x => x.Editor is MissingPropertyEditor); - foreach (IDataType dataType in dataTypesWithMissingEditors) - { - dataType.Editor = new LabelPropertyEditor(_dataValueEditorFactory, _ioHelper, _editorConfigurationParser); - } - } - - private void Audit(AuditType type, int userId, int objectId) => - _auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.DataType.GetName())); - - public EntityContainer? GetContainer(int containerId) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _dataTypeContainerRepository.Get(containerId); - } - } - - public EntityContainer? GetContainer(Guid containerId) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _dataTypeContainerRepository.Get(containerId); - } - } - - public IEnumerable GetContainers(string name, int level) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _dataTypeContainerRepository.Get(name, level); - } - } - - public IEnumerable GetContainers(IDataType dataType) - { - var ancestorIds = dataType.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) - .Select(x => - { - Attempt asInt = x.TryConvertTo(); - return asInt.Success ? asInt.Result : int.MinValue; - }) - .Where(x => x != int.MinValue && x != dataType.Id) - .ToArray(); - - return GetContainers(ancestorIds); - } - - public IEnumerable GetContainers(int[] containerIds) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - return _dataTypeContainerRepository.GetMany(containerIds); - } - } - - public Attempt SaveContainer( - EntityContainer container, - int userId = Constants.Security.SuperUserId) - { - EventMessages evtMsgs = EventMessagesFactory.Get(); - - if (container.ContainedObjectType != Constants.ObjectTypes.DataType) - { - var ex = new InvalidOperationException("Not a " + Constants.ObjectTypes.DataType + " container."); - return OperationResult.Attempt.Fail(evtMsgs, ex); - } - - if (container.HasIdentity && container.IsPropertyDirty("ParentId")) - { - var ex = new InvalidOperationException( - "Cannot save a container with a modified parent, move the container instead."); - return OperationResult.Attempt.Fail(evtMsgs, ex); - } - - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { - var savingEntityContainerNotification = new EntityContainerSavingNotification(container, evtMsgs); - if (scope.Notifications.PublishCancelable(savingEntityContainerNotification)) - { - scope.Complete(); - return OperationResult.Attempt.Cancel(evtMsgs); - } - - _dataTypeContainerRepository.Save(container); - - scope.Notifications.Publish( - new EntityContainerSavedNotification(container, evtMsgs).WithStateFrom( - savingEntityContainerNotification)); - scope.Complete(); - } - - // TODO: Audit trail ? - return OperationResult.Attempt.Succeed(evtMsgs); - } - public Attempt DeleteContainer(int containerId, int userId = Constants.Security.SuperUserId) - { - EventMessages evtMsgs = EventMessagesFactory.Get(); - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + private void Audit(AuditType type, int userId, int objectId) { - EntityContainer? container = _dataTypeContainerRepository.Get(containerId); - if (container == null) - { - return OperationResult.Attempt.NoOperation(evtMsgs); - } - - // 'container' here does not know about its children, so we need - // to get it again from the entity repository, as a light entity - IEntitySlim? entity = _entityRepository.Get(container.Id); - if (entity?.HasChildren ?? false) - { - scope.Complete(); - return Attempt.Fail(new OperationResult(OperationResultType.FailedCannot, evtMsgs)); - } - - var deletingEntityContainerNotification = new EntityContainerDeletingNotification(container, evtMsgs); - if (scope.Notifications.PublishCancelable(deletingEntityContainerNotification)) - { - scope.Complete(); - return Attempt.Fail(new OperationResult(OperationResultType.FailedCancelledByEvent, evtMsgs)); - } - - _dataTypeContainerRepository.Delete(container); - - scope.Notifications.Publish( - new EntityContainerDeletedNotification(container, evtMsgs).WithStateFrom( - deletingEntityContainerNotification)); - scope.Complete(); + _auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.DataType))); } - // TODO: Audit trail ? - return OperationResult.Attempt.Succeed(evtMsgs); - } - - public Attempt?> RenameContainer(int id, string name, int userId = Constants.Security.SuperUserId) - { - EventMessages evtMsgs = EventMessagesFactory.Get(); - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { - try - { - EntityContainer? container = _dataTypeContainerRepository.Get(id); - - // throw if null, this will be caught by the catch and a failed returned - if (container == null) - { - throw new InvalidOperationException("No container found with id " + id); - } - - container.Name = name; - - var renamingEntityContainerNotification = new EntityContainerRenamingNotification(container, evtMsgs); - if (scope.Notifications.PublishCancelable(renamingEntityContainerNotification)) - { - scope.Complete(); - return OperationResult.Attempt.Cancel(evtMsgs, container); - } - - _dataTypeContainerRepository.Save(container); - scope.Complete(); - - scope.Notifications.Publish( - new EntityContainerRenamedNotification(container, evtMsgs).WithStateFrom( - renamingEntityContainerNotification)); - - return OperationResult.Attempt.Succeed(OperationResultType.Success, evtMsgs, container); - } - catch (Exception ex) - { - return OperationResult.Attempt.Fail(evtMsgs, ex); - } - } } - - #endregion } diff --git a/src/Umbraco.Core/Services/IPackagingService.cs b/src/Umbraco.Core/Services/IPackagingService.cs index c6433bbd2a9a..40f39628be26 100644 --- a/src/Umbraco.Core/Services/IPackagingService.cs +++ b/src/Umbraco.Core/Services/IPackagingService.cs @@ -18,8 +18,7 @@ public interface IPackagingService : IService /// /// /// - InstallationSummary - InstallCompiledPackageData(FileInfo packageXmlFile, int userId = Constants.Security.SuperUserId); + InstallationSummary InstallCompiledPackageData(FileInfo packageXmlFile, int userId = Constants.Security.SuperUserId); InstallationSummary InstallCompiledPackageData(XDocument? packageXml, int userId = Constants.Security.SuperUserId); diff --git a/src/Umbraco.Core/Services/ITagService.cs b/src/Umbraco.Core/Services/ITagService.cs index ce436d74a49a..5e2f164a351e 100644 --- a/src/Umbraco.Core/Services/ITagService.cs +++ b/src/Umbraco.Core/Services/ITagService.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; namespace Umbraco.Cms.Core.Services; @@ -8,8 +8,7 @@ namespace Umbraco.Cms.Core.Services; /// /// /// If there is unpublished content with tags, those tags will not be contained. -/// This service does not contain methods to query for content, media or members based on tags, those methods will be -/// added +/// This service does not contain methods to query for content, media or members based on tags, those methods will be added /// to the content, media and member services respectively. /// public interface ITagService : IService diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs index 09a6b5a53c75..f8b44759a023 100644 --- a/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs +++ b/src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs @@ -57,7 +57,7 @@ public static string Localize(this ILocalizedTextService manager, string area, s return text; } - text = text[1..]; + text = text.Substring(1); var value = cultureDictionary[text]; if (value.IsNullOrWhiteSpace() == false) { diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs b/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs index 6ac866aef947..26a2e9fb60ca 100644 --- a/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs +++ b/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs @@ -220,7 +220,7 @@ private IEnumerable GetLanguageFiles() Dictionary> resolved = _xmlSources.Value; return _twoLetterCultureConverter.Values.Contains(culture) - ? Attempt.Succeed(culture.Name[..2]) + ? Attempt.Succeed(culture.Name.Substring(0, 2)) : Attempt.Fail(); } diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index a5fe4ced9b0a..325677407e5c 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -13,67 +13,57 @@ using Umbraco.Cms.Core.Strings; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services; - -/// -/// Represents the Media Service, which is an easy access to operations involving -/// -public class MediaService : RepositoryService, IMediaService +namespace Umbraco.Cms.Core.Services { - private readonly IAuditRepository _auditRepository; - private readonly IEntityRepository _entityRepository; - - private readonly MediaFileManager _mediaFileManager; - private readonly IMediaRepository _mediaRepository; - private readonly IMediaTypeRepository _mediaTypeRepository; - private readonly IShortStringHelper _shortStringHelper; - - #region Constructors - - public MediaService( - ICoreScopeProvider provider, - MediaFileManager mediaFileManager, - ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, - IMediaRepository mediaRepository, - IAuditRepository auditRepository, - IMediaTypeRepository mediaTypeRepository, - IEntityRepository entityRepository, - IShortStringHelper shortStringHelper) - : base(provider, loggerFactory, eventMessagesFactory) + /// + /// Represents the Media Service, which is an easy access to operations involving + /// + public class MediaService : RepositoryService, IMediaService { - _mediaFileManager = mediaFileManager; - _mediaRepository = mediaRepository; - _auditRepository = auditRepository; - _mediaTypeRepository = mediaTypeRepository; - _entityRepository = entityRepository; - _shortStringHelper = shortStringHelper; - } + private readonly IMediaRepository _mediaRepository; + private readonly IMediaTypeRepository _mediaTypeRepository; + private readonly IAuditRepository _auditRepository; + private readonly IEntityRepository _entityRepository; + private readonly IShortStringHelper _shortStringHelper; - #endregion + private readonly MediaFileManager _mediaFileManager; - #region Count + #region Constructors - public int Count(string? mediaTypeAlias = null) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + public MediaService( + ICoreScopeProvider provider, + MediaFileManager mediaFileManager, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IMediaRepository mediaRepository, + IAuditRepository auditRepository, + IMediaTypeRepository mediaTypeRepository, + IEntityRepository entityRepository, + IShortStringHelper shortStringHelper) + : base(provider, loggerFactory, eventMessagesFactory) { - scope.ReadLock(Constants.Locks.MediaTree); - return _mediaRepository.Count(mediaTypeAlias); + _mediaFileManager = mediaFileManager; + _mediaRepository = mediaRepository; + _auditRepository = auditRepository; + _mediaTypeRepository = mediaTypeRepository; + _entityRepository = entityRepository; + _shortStringHelper = shortStringHelper; } - } - #endregion + #endregion - #region Private Methods + #region Count - private void Audit(AuditType type, int userId, int objectId, string? message = null) => - _auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.Media.GetName(), message)); + public int Count(string? mediaTypeAlias = null) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + scope.ReadLock(Constants.Locks.MediaTree); + return _mediaRepository.Count(mediaTypeAlias); + } - public int CountNotTrashed(string? mediaTypeAlias = null) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + public int CountNotTrashed(string? mediaTypeAlias = null) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MediaTree); var mediaTypeId = 0; @@ -96,159 +86,146 @@ public int CountNotTrashed(string? mediaTypeAlias = null) return _mediaRepository.Count(query); } - } - public int CountChildren(int parentId, string? mediaTypeAlias = null) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + public int CountChildren(int parentId, string? mediaTypeAlias = null) { - scope.ReadLock(Constants.Locks.MediaTree); - return _mediaRepository.CountChildren(parentId, mediaTypeAlias); + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.MediaTree); + return _mediaRepository.CountChildren(parentId, mediaTypeAlias); + } } - } - public int CountDescendants(int parentId, string? mediaTypeAlias = null) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + public int CountDescendants(int parentId, string? mediaTypeAlias = null) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MediaTree); return _mediaRepository.CountDescendants(parentId, mediaTypeAlias); } - } - - #endregion - - #region Create - - /// - /// Creates an object using the alias of the - /// that this Media should based on. - /// - /// - /// Note that using this method will simply return a new IMedia without any identity - /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects - /// that does not invoke a save operation against the database. - /// - /// Name of the Media object - /// Id of Parent for the new Media item - /// Alias of the - /// Optional id of the user creating the media item - /// - /// - /// - public IMedia CreateMedia(string name, Guid parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) - { - IMedia? parent = GetById(parentId); - return CreateMedia(name, parent, mediaTypeAlias, userId); - } - /// - /// Creates an object of a specified media type. - /// - /// - /// This method simply returns a new, non-persisted, IMedia without any identity. It - /// is intended as a shortcut to creating new media objects that does not invoke a save - /// operation against the database. - /// - /// The name of the media object. - /// The identifier of the parent, or -1. - /// The alias of the media type. - /// The optional id of the user creating the media. - /// The media object. - public IMedia CreateMedia(string? name, int parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) - { - IMediaType mediaType = GetMediaType(mediaTypeAlias); - if (mediaType == null) - { - throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); - } + #endregion + + #region Create + + /// + /// Creates an object using the alias of the + /// that this Media should based on. + /// + /// + /// Note that using this method will simply return a new IMedia without any identity + /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects + /// that does not invoke a save operation against the database. + /// + /// Name of the Media object + /// Id of Parent for the new Media item + /// Alias of the + /// Optional id of the user creating the media item + /// + public IMedia CreateMedia(string name, Guid parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) + { + IMedia? parent = GetById(parentId); + return CreateMedia(name, parent, mediaTypeAlias, userId); + } + + /// + /// Creates an object of a specified media type. + /// + /// This method simply returns a new, non-persisted, IMedia without any identity. It + /// is intended as a shortcut to creating new media objects that does not invoke a save + /// operation against the database. + /// + /// The name of the media object. + /// The identifier of the parent, or -1. + /// The alias of the media type. + /// The optional id of the user creating the media. + /// The media object. + public IMedia CreateMedia(string? name, int parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) + { + IMediaType? mediaType = GetMediaType(mediaTypeAlias); + if (mediaType == null) + { + throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); + } - IMedia? parent = parentId > 0 ? GetById(parentId) : null; - if (parentId > 0 && parent == null) - { - throw new ArgumentException("No media with that id.", nameof(parentId)); - } + IMedia? parent = parentId > 0 ? GetById(parentId) : null; + if (parentId > 0 && parent == null) + { + throw new ArgumentException("No media with that id.", nameof(parentId)); + } - if (name != null && name.Length > 255) - { - throw new InvalidOperationException("Name cannot be more than 255 characters in length."); - } + if (name != null && name.Length > 255) + { + throw new InvalidOperationException("Name cannot be more than 255 characters in length."); + } - var media = new Models.Media(name, parentId, mediaType); - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { + var media = new Core.Models.Media(name, parentId, mediaType); + using ICoreScope scope = ScopeProvider.CreateCoreScope(); CreateMedia(scope, media, parent!, userId, false); scope.Complete(); - } - - return media; - } - /// - /// Creates an object of a specified media type, at root. - /// - /// - /// This method simply returns a new, non-persisted, IMedia without any identity. It - /// is intended as a shortcut to creating new media objects that does not invoke a save - /// operation against the database. - /// - /// The name of the media object. - /// The alias of the media type. - /// The optional id of the user creating the media. - /// The media object. - public IMedia CreateMedia(string name, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) - { - // not locking since not saving anything - IMediaType mediaType = GetMediaType(mediaTypeAlias); - if (mediaType == null) - { - throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); + return media; } - if (name != null && name.Length > 255) + /// + /// Creates an object of a specified media type, at root. + /// + /// This method simply returns a new, non-persisted, IMedia without any identity. It + /// is intended as a shortcut to creating new media objects that does not invoke a save + /// operation against the database. + /// + /// The name of the media object. + /// The alias of the media type. + /// The optional id of the user creating the media. + /// The media object. + public IMedia CreateMedia(string name, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) { - throw new InvalidOperationException("Name cannot be more than 255 characters in length."); - } + // not locking since not saving anything - var media = new Models.Media(name, -1, mediaType); - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { + IMediaType? mediaType = GetMediaType(mediaTypeAlias); + if (mediaType == null) + { + throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); + } + + if (name != null && name.Length > 255) + { + throw new InvalidOperationException("Name cannot be more than 255 characters in length."); + } + + var media = new Core.Models.Media(name, -1, mediaType); + using ICoreScope scope = ScopeProvider.CreateCoreScope(); CreateMedia(scope, media, null, userId, false); scope.Complete(); - } - - return media; - } - /// - /// Creates an object of a specified media type, under a parent. - /// - /// - /// This method simply returns a new, non-persisted, IMedia without any identity. It - /// is intended as a shortcut to creating new media objects that does not invoke a save - /// operation against the database. - /// - /// The name of the media object. - /// The parent media object. - /// The alias of the media type. - /// The optional id of the user creating the media. - /// The media object. - public IMedia CreateMedia(string name, IMedia? parent, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) - { - if (parent == null) - { - throw new ArgumentNullException(nameof(parent)); + return media; } - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { + /// + /// Creates an object of a specified media type, under a parent. + /// + /// This method simply returns a new, non-persisted, IMedia without any identity. It + /// is intended as a shortcut to creating new media objects that does not invoke a save + /// operation against the database. + /// + /// The name of the media object. + /// The parent media object. + /// The alias of the media type. + /// The optional id of the user creating the media. + /// The media object. + public IMedia CreateMedia(string name, IMedia? parent, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) + { + if (parent == null) + { + throw new ArgumentNullException(nameof(parent)); + } + + using ICoreScope scope = ScopeProvider.CreateCoreScope(); // not locking since not saving anything - IMediaType mediaType = GetMediaType(mediaTypeAlias); + + IMediaType? mediaType = GetMediaType(mediaTypeAlias); if (mediaType == null) { - throw new ArgumentException( - "No media type with that alias.", - nameof(mediaTypeAlias)); // causes rollback + throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); // causes rollback } if (name != null && name.Length > 255) @@ -256,36 +233,32 @@ public IMedia CreateMedia(string name, IMedia? parent, string mediaTypeAlias, in throw new InvalidOperationException("Name cannot be more than 255 characters in length."); } - var media = new Models.Media(name, parent, mediaType); + var media = new Core.Models.Media(name, parent, mediaType); CreateMedia(scope, media, parent, userId, false); scope.Complete(); return media; } - } - /// - /// Creates an object of a specified media type. - /// - /// This method returns a new, persisted, IMedia with an identity. - /// The name of the media object. - /// The identifier of the parent, or -1. - /// The alias of the media type. - /// The optional id of the user creating the media. - /// The media object. - public IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + /// + /// Creates an object of a specified media type. + /// + /// This method returns a new, persisted, IMedia with an identity. + /// The name of the media object. + /// The identifier of the parent, or -1. + /// The alias of the media type. + /// The optional id of the user creating the media. + /// The media object. + public IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(); // locking the media tree secures media types too scope.WriteLock(Constants.Locks.MediaTree); - IMediaType mediaType = GetMediaType(mediaTypeAlias); // + locks + IMediaType? mediaType = GetMediaType(mediaTypeAlias); // + locks if (mediaType == null) { - throw new ArgumentException( - "No media type with that alias.", - nameof(mediaTypeAlias)); // causes rollback + throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); // causes rollback } IMedia? parent = parentId > 0 ? GetById(parentId) : null; // + locks @@ -294,356 +267,300 @@ public IMedia CreateMediaWithIdentity(string name, int parentId, string mediaTyp throw new ArgumentException("No media with that id.", nameof(parentId)); // causes rollback } - Models.Media media = parentId > 0 - ? new Models.Media(name, parent, mediaType) - : new Models.Media(name, parentId, mediaType); + Models.Media media = parentId > 0 ? new Core.Models.Media(name, parent, mediaType) : new Core.Models.Media(name, parentId, mediaType); CreateMedia(scope, media, parent, userId, true); scope.Complete(); return media; } - } - /// - /// Creates an object of a specified media type, under a parent. - /// - /// This method returns a new, persisted, IMedia with an identity. - /// The name of the media object. - /// The parent media object. - /// The alias of the media type. - /// The optional id of the user creating the media. - /// The media object. - public IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) - { - if (parent == null) + /// + /// Creates an object of a specified media type, under a parent. + /// + /// This method returns a new, persisted, IMedia with an identity. + /// The name of the media object. + /// The parent media object. + /// The alias of the media type. + /// The optional id of the user creating the media. + /// The media object. + public IMedia CreateMediaWithIdentity(string name, IMedia parent, string mediaTypeAlias, int userId = Constants.Security.SuperUserId) { - throw new ArgumentNullException(nameof(parent)); - } + if (parent == null) + { + throw new ArgumentNullException(nameof(parent)); + } - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { + using ICoreScope scope = ScopeProvider.CreateCoreScope(); // locking the media tree secures media types too scope.WriteLock(Constants.Locks.MediaTree); - IMediaType mediaType = GetMediaType(mediaTypeAlias); // + locks + IMediaType? mediaType = GetMediaType(mediaTypeAlias); // + locks if (mediaType == null) { - throw new ArgumentException( - "No media type with that alias.", - nameof(mediaTypeAlias)); // causes rollback + throw new ArgumentException("No media type with that alias.", nameof(mediaTypeAlias)); // causes rollback } - var media = new Models.Media(name, parent, mediaType); + var media = new Core.Models.Media(name, parent, mediaType); CreateMedia(scope, media, parent, userId, true); scope.Complete(); return media; } - } - #endregion + private void CreateMedia(ICoreScope scope, Core.Models.Media media, IMedia? parent, int userId, bool withIdentity) + { + EventMessages eventMessages = EventMessagesFactory.Get(); - #region Get, Has, Is + media.CreatorId = userId; - /// - /// Gets an object by Id - /// - /// Id of the Media to retrieve - /// - /// - /// - public IMedia? GetById(int id) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.MediaTree); - return _mediaRepository.Get(id); - } - } + if (withIdentity) + { + var savingNotification = new MediaSavingNotification(media, eventMessages); + if (scope.Notifications.PublishCancelable(savingNotification)) + { + return; + } - private void CreateMedia(ICoreScope scope, Models.Media media, IMedia? parent, int userId, bool withIdentity) - { - EventMessages eventMessages = EventMessagesFactory.Get(); + _mediaRepository.Save(media); - media.CreatorId = userId; + scope.Notifications.Publish(new MediaSavedNotification(media, eventMessages).WithStateFrom(savingNotification)); + scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshNode, eventMessages)); + } - if (withIdentity) - { - var savingNotification = new MediaSavingNotification(media, eventMessages); - if (scope.Notifications.PublishCancelable(savingNotification)) + if (withIdentity == false) { return; } - _mediaRepository.Save(media); - - scope.Notifications.Publish( - new MediaSavedNotification(media, eventMessages).WithStateFrom(savingNotification)); - scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshNode, eventMessages)); + Audit(AuditType.New, media.CreatorId, media.Id, $"Media '{media.Name}' was created with Id {media.Id}"); } - if (withIdentity == false) - { - return; - } + #endregion - Audit(AuditType.New, media.CreatorId, media.Id, $"Media '{media.Name}' was created with Id {media.Id}"); - } + #region Get, Has, Is - /// - /// Gets an object by Id - /// - /// Ids of the Media to retrieve - /// - /// - /// - public IEnumerable GetByIds(IEnumerable ids) - { - var idsA = ids.ToArray(); - if (idsA.Length == 0) + /// + /// Gets an object by Id + /// + /// Id of the Media to retrieve + /// + public IMedia? GetById(int id) { - return Enumerable.Empty(); + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + scope.ReadLock(Constants.Locks.MediaTree); + return _mediaRepository.Get(id); } - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Gets an object by Id + /// + /// Ids of the Media to retrieve + /// + public IEnumerable GetByIds(IEnumerable ids) { + var idsA = ids.ToArray(); + if (idsA.Length == 0) + { + return Enumerable.Empty(); + } + + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MediaTree); return _mediaRepository.GetMany(idsA); } - } - /// - /// Gets an object by its 'UniqueId' - /// - /// Guid key of the Media to retrieve - /// - /// - /// - public IMedia? GetById(Guid key) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Gets an object by its 'UniqueId' + /// + /// Guid key of the Media to retrieve + /// + public IMedia? GetById(Guid key) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MediaTree); return _mediaRepository.Get(key); } - } - /// - /// Gets an object by Id - /// - /// Ids of the Media to retrieve - /// - /// - /// - public IEnumerable GetByIds(IEnumerable ids) - { - Guid[] idsA = ids.ToArray(); - if (idsA.Length == 0) + /// + /// Gets an object by Id + /// + /// Ids of the Media to retrieve + /// + public IEnumerable GetByIds(IEnumerable ids) { - return Enumerable.Empty(); - } + Guid[] idsA = ids.ToArray(); + if (idsA.Length == 0) + { + return Enumerable.Empty(); + } - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MediaTree); return _mediaRepository.GetMany(idsA); } - } - /// - public IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords, IQuery? filter = null, Ordering? ordering = null) - { - if (pageIndex < 0) + /// + public IEnumerable GetPagedOfType(int contentTypeId, long pageIndex, int pageSize, out long totalRecords, IQuery? filter = null, Ordering? ordering = null) { - throw new ArgumentOutOfRangeException(nameof(pageIndex)); - } + if (pageIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(pageIndex)); + } - if (pageSize <= 0) - { - throw new ArgumentOutOfRangeException(nameof(pageSize)); - } + if (pageSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(pageSize)); + } - if (ordering == null) - { - ordering = Ordering.By("sortOrder"); - } + if (ordering == null) + { + ordering = Ordering.By("sortOrder"); + } - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.ContentTree); - return _mediaRepository.GetPage( - Query()?.Where(x => x.ContentTypeId == contentTypeId), - pageIndex, - pageSize, - out totalRecords, - filter, - ordering); + return _mediaRepository.GetPage(Query()?.Where(x => x.ContentTypeId == contentTypeId), pageIndex, pageSize, out totalRecords, filter, ordering); } - } - /// - public IEnumerable GetPagedOfTypes( - int[] contentTypeIds, - long pageIndex, - int pageSize, - out long totalRecords, - IQuery? filter = null, - Ordering? ordering = null) - { - if (pageIndex < 0) + /// + public IEnumerable GetPagedOfTypes(int[] contentTypeIds, long pageIndex, int pageSize, out long totalRecords, IQuery? filter = null, Ordering? ordering = null) { - throw new ArgumentOutOfRangeException(nameof(pageIndex)); - } + if (pageIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(pageIndex)); + } - if (pageSize <= 0) - { - throw new ArgumentOutOfRangeException(nameof(pageSize)); - } + if (pageSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(pageSize)); + } - if (ordering == null) - { - ordering = Ordering.By("sortOrder"); - } + if (ordering == null) + { + ordering = Ordering.By("sortOrder"); + } - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.ContentTree); return _mediaRepository.GetPage( - Query()?.Where(x => contentTypeIds.Contains(x.ContentTypeId)), - pageIndex, - pageSize, - out totalRecords, - filter, - ordering); + Query()?.Where(x => contentTypeIds.Contains(x.ContentTypeId)), pageIndex, pageSize, out totalRecords, filter, ordering); } - } - /// - /// Gets a collection of objects by Level - /// - /// The level to retrieve Media from - /// An Enumerable list of objects - /// Contrary to most methods, this method filters out trashed media items. - public IEnumerable GetByLevel(int level) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Gets a collection of objects by Level + /// + /// The level to retrieve Media from + /// An Enumerable list of objects + /// Contrary to most methods, this method filters out trashed media items. + public IEnumerable? GetByLevel(int level) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MediaTree); IQuery query = Query().Where(x => x.Level == level && x.Trashed == false); return _mediaRepository.Get(query); } - } - /// - /// Gets a specific version of an item. - /// - /// Id of the version to retrieve - /// An item - public IMedia? GetVersion(int versionId) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Gets a specific version of an item. + /// + /// Id of the version to retrieve + /// An item + public IMedia? GetVersion(int versionId) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MediaTree); return _mediaRepository.GetVersion(versionId); } - } - /// - /// Gets a collection of an objects versions by Id - /// - /// - /// An Enumerable list of objects - public IEnumerable GetVersions(int id) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Gets a collection of an objects versions by Id + /// + /// + /// An Enumerable list of objects + public IEnumerable GetVersions(int id) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MediaTree); return _mediaRepository.GetAllVersions(id); } - } - - /// - /// Gets a collection of objects, which are ancestors of the current media. - /// - /// Id of the to retrieve ancestors for - /// An Enumerable list of objects - public IEnumerable GetAncestors(int id) - { - // intentionally not locking - IMedia? media = GetById(id); - return GetAncestors(media); - } - /// - /// Gets a collection of objects, which are ancestors of the current media. - /// - /// to retrieve ancestors for - /// An Enumerable list of objects - public IEnumerable GetAncestors(IMedia? media) - { - // null check otherwise we get exceptions - if (media is null || media.Path.IsNullOrWhiteSpace()) + /// + /// Gets a collection of objects, which are ancestors of the current media. + /// + /// Id of the to retrieve ancestors for + /// An Enumerable list of objects + public IEnumerable GetAncestors(int id) { - return Enumerable.Empty(); + // intentionally not locking + IMedia? media = GetById(id); + return GetAncestors(media); } - var rootId = Constants.System.RootString; - var ids = media.Path.Split(Constants.CharArrays.Comma) - .Where(x => x != rootId && x != media.Id.ToString(CultureInfo.InvariantCulture)) - .Select(s => int.Parse(s, CultureInfo.InvariantCulture)) - .ToArray(); - if (ids.Any() == false) + /// + /// Gets a collection of objects, which are ancestors of the current media. + /// + /// to retrieve ancestors for + /// An Enumerable list of objects + public IEnumerable GetAncestors(IMedia? media) { - return new List(); - } + //null check otherwise we get exceptions + if (media is null || media.Path.IsNullOrWhiteSpace()) + { + return Enumerable.Empty(); + } - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { + var rootId = Constants.System.RootString; + var ids = media.Path.Split(Constants.CharArrays.Comma) + .Where(x => x != rootId && x != media.Id.ToString(CultureInfo.InvariantCulture)) + .Select(s => int.Parse(s, CultureInfo.InvariantCulture)) + .ToArray(); + if (ids.Any() == false) + { + return new List(); + } + + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MediaTree); return _mediaRepository.GetMany(ids); } - } - /// - public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, IQuery? filter = null, Ordering? ordering = null) - { - if (pageIndex < 0) + /// + public IEnumerable GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren, IQuery? filter = null, Ordering? ordering = null) { - throw new ArgumentOutOfRangeException(nameof(pageIndex)); - } + if (pageIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(pageIndex)); + } - if (pageSize <= 0) - { - throw new ArgumentOutOfRangeException(nameof(pageSize)); - } + if (pageSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(pageSize)); + } - if (ordering == null) - { - ordering = Ordering.By("sortOrder"); - } + if (ordering == null) + { + ordering = Ordering.By("sortOrder"); + } - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MediaTree); IQuery? query = Query()?.Where(x => x.ParentId == id); return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering); } - } - /// - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, IQuery? filter = null, Ordering? ordering = null) - { - if (ordering == null) + /// + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, IQuery? filter = null, Ordering? ordering = null) { - ordering = Ordering.By("Path"); - } + if (ordering == null) + { + ordering = Ordering.By("Path"); + } - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MediaTree); - // if the id is System Root, then just get all + //if the id is System Root, then just get all if (id != Constants.System.Root) { TreeEntityPath[] mediaPath = _entityRepository.GetAllPaths(Constants.ObjectTypes.Media, id).ToArray(); @@ -658,297 +575,328 @@ public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageS return GetPagedLocked(GetPagedDescendantQuery(null), pageIndex, pageSize, out totalChildren, filter, ordering); } - } - - /// - /// Gets the parent of the current media as an item. - /// - /// Id of the to retrieve the parent from - /// Parent object - public IMedia? GetParent(int id) - { - // intentionally not locking - IMedia? media = GetById(id); - return GetParent(media); - } - private IQuery? GetPagedDescendantQuery(string? mediaPath) - { - IQuery query = Query(); - if (!mediaPath.IsNullOrWhiteSpace()) + private IQuery? GetPagedDescendantQuery(string? mediaPath) { - query?.Where(x => x.Path.SqlStartsWith(mediaPath + ",", TextColumnType.NVarchar)); - } - - return query; - } + IQuery? query = Query(); + if (!mediaPath.IsNullOrWhiteSpace()) + { + query?.Where(x => x.Path.SqlStartsWith(mediaPath + ",", TextColumnType.NVarchar)); + } - private IEnumerable GetPagedLocked(IQuery? query, long pageIndex, int pageSize, out long totalChildren, IQuery? filter, Ordering ordering) - { - if (pageIndex < 0) - { - throw new ArgumentOutOfRangeException(nameof(pageIndex)); + return query; } - if (pageSize <= 0) + private IEnumerable GetPagedLocked(IQuery? query, long pageIndex, int pageSize, out long totalChildren, IQuery? filter, Ordering ordering) { - throw new ArgumentOutOfRangeException(nameof(pageSize)); + if (pageIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(pageIndex)); + } + + if (pageSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(pageSize)); + } + + if (ordering == null) + { + throw new ArgumentNullException(nameof(ordering)); + } + + return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering); } - if (ordering == null) + /// + /// Gets the parent of the current media as an item. + /// + /// Id of the to retrieve the parent from + /// Parent object + public IMedia? GetParent(int id) { - throw new ArgumentNullException(nameof(ordering)); + // intentionally not locking + IMedia? media = GetById(id); + return GetParent(media); } - return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering); - } - - /// - /// Gets the parent of the current media as an item. - /// - /// to retrieve the parent from - /// Parent object - public IMedia? GetParent(IMedia? media) - { - var parentId = media?.ParentId; - if (parentId is null || media?.ParentId == Constants.System.Root || - media?.ParentId == Constants.System.RecycleBinMedia) + /// + /// Gets the parent of the current media as an item. + /// + /// to retrieve the parent from + /// Parent object + public IMedia? GetParent(IMedia? media) { - return null; - } + var parentId = media?.ParentId; + if (parentId is null || media?.ParentId == Constants.System.Root || media?.ParentId == Constants.System.RecycleBinMedia) + { + return null; + } - return GetById(parentId.Value); - } + return GetById(parentId.Value); + } - /// - /// Gets a collection of objects, which reside at the first level / root - /// - /// An Enumerable list of objects - public IEnumerable GetRootMedia() - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Gets a collection of objects, which reside at the first level / root + /// + /// An Enumerable list of objects + public IEnumerable GetRootMedia() { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MediaTree); IQuery query = Query().Where(x => x.ParentId == Constants.System.Root); return _mediaRepository.Get(query); } - } - /// - public IEnumerable GetPagedMediaInRecycleBin(long pageIndex, int pageSize, out long totalRecords, IQuery? filter = null, Ordering? ordering = null) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + public IEnumerable GetPagedMediaInRecycleBin(long pageIndex, int pageSize, out long totalRecords, IQuery? filter = null, Ordering? ordering = null) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); if (ordering == null) { ordering = Ordering.By("Path"); } scope.ReadLock(Constants.Locks.MediaTree); - IQuery? query = Query().Where(x => x.Path.StartsWith(Constants.System.RecycleBinMediaPathPrefix)); + IQuery? query = Query()?.Where(x => x.Path.StartsWith(Constants.System.RecycleBinMediaPathPrefix)); return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalRecords, filter, ordering); } - } - /// - /// Checks whether an item has any children - /// - /// Id of the - /// True if the media has any children otherwise False - public bool HasChildren(int id) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Checks whether an item has any children + /// + /// Id of the + /// True if the media has any children otherwise False + public bool HasChildren(int id) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); IQuery query = Query().Where(x => x.ParentId == id); var count = _mediaRepository.Count(query); return count > 0; } - } - /// - /// Gets an object from the path stored in the 'umbracoFile' property. - /// - /// Path of the media item to retrieve (for example: /media/1024/koala_403x328.jpg) - /// - /// - /// - public IMedia? GetMediaByPath(string mediaPath) - { - using (ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Gets an object from the path stored in the 'umbracoFile' property. + /// + /// Path of the media item to retrieve (for example: /media/1024/koala_403x328.jpg) + /// + public IMedia? GetMediaByPath(string mediaPath) { - return _mediaRepository.GetMediaByPath(mediaPath); + using (ScopeProvider.CreateCoreScope(autoComplete: true)) + { + return _mediaRepository.GetMediaByPath(mediaPath); + } } - } - #endregion + #endregion - #region Save + #region Save - /// - /// Saves a single object - /// - /// The to save - /// Id of the User saving the Media - public Attempt Save(IMedia media, int userId = Constants.Security.SuperUserId) - { - EventMessages eventMessages = EventMessagesFactory.Get(); - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { - var savingNotification = new MediaSavingNotification(media, eventMessages); - if (scope.Notifications.PublishCancelable(savingNotification)) - { - scope.Complete(); - return OperationResult.Attempt.Cancel(eventMessages); - } - // poor man's validation? - if (string.IsNullOrWhiteSpace(media.Name)) - { - throw new ArgumentException("Media has no name.", nameof(media)); - } - - if (media.Name != null && media.Name.Length > 255) - { - throw new InvalidOperationException("Name cannot be more than 255 characters in length."); - } + /// + /// Saves a single object + /// + /// The to save + /// Id of the User saving the Media + public Attempt Save(IMedia media, int userId = Constants.Security.SuperUserId) + { + EventMessages eventMessages = EventMessagesFactory.Get(); - scope.WriteLock(Constants.Locks.MediaTree); - if (media.HasIdentity == false) + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - media.CreatorId = userId; - } - - _mediaRepository.Save(media); - scope.Notifications.Publish( - new MediaSavedNotification(media, eventMessages).WithStateFrom(savingNotification)); + var savingNotification = new MediaSavingNotification(media, eventMessages); + if (scope.Notifications.PublishCancelable(savingNotification)) + { + scope.Complete(); + return OperationResult.Attempt.Cancel(eventMessages); + } - // TODO: See note about suppressing events in content service - scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshNode, eventMessages)); + // poor man's validation? + if (string.IsNullOrWhiteSpace(media.Name)) + { + throw new ArgumentException("Media has no name.", nameof(media)); + } - Audit(AuditType.Save, userId, media.Id); - scope.Complete(); - } + if (media.Name != null && media.Name.Length > 255) + { + throw new InvalidOperationException("Name cannot be more than 255 characters in length."); + } - return OperationResult.Attempt.Succeed(eventMessages); - } + scope.WriteLock(Constants.Locks.MediaTree); + if (media.HasIdentity == false) + { + media.CreatorId = userId; + } - /// - /// Saves a collection of objects - /// - /// Collection of to save - /// Id of the User saving the Media - public Attempt Save(IEnumerable medias, int userId = Constants.Security.SuperUserId) - { - EventMessages messages = EventMessagesFactory.Get(); - IMedia[] mediasA = medias.ToArray(); + _mediaRepository.Save(media); + scope.Notifications.Publish(new MediaSavedNotification(media, eventMessages).WithStateFrom(savingNotification)); + // TODO: See note about suppressing events in content service + scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshNode, eventMessages)); - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { - var savingNotification = new MediaSavingNotification(mediasA, messages); - if (scope.Notifications.PublishCancelable(savingNotification)) - { + Audit(AuditType.Save, userId, media.Id); scope.Complete(); - return OperationResult.Attempt.Cancel(messages); } - IEnumerable> treeChanges = - mediasA.Select(x => new TreeChange(x, TreeChangeTypes.RefreshNode)); + return OperationResult.Attempt.Succeed(eventMessages); + } - scope.WriteLock(Constants.Locks.MediaTree); - foreach (IMedia media in mediasA) + /// + /// Saves a collection of objects + /// + /// Collection of to save + /// Id of the User saving the Media + public Attempt Save(IEnumerable medias, int userId = Constants.Security.SuperUserId) + { + EventMessages messages = EventMessagesFactory.Get(); + IMedia[] mediasA = medias.ToArray(); + + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - if (media.HasIdentity == false) + var savingNotification = new MediaSavingNotification(mediasA, messages); + if (scope.Notifications.PublishCancelable(savingNotification)) { - media.CreatorId = userId; + scope.Complete(); + return OperationResult.Attempt.Cancel(messages); } - _mediaRepository.Save(media); - } + IEnumerable> treeChanges = mediasA.Select(x => new TreeChange(x, TreeChangeTypes.RefreshNode)); - scope.Notifications.Publish( - new MediaSavedNotification(mediasA, messages).WithStateFrom(savingNotification)); + scope.WriteLock(Constants.Locks.MediaTree); + foreach (IMedia media in mediasA) + { + if (media.HasIdentity == false) + { + media.CreatorId = userId; + } - // TODO: See note about suppressing events in content service - scope.Notifications.Publish(new MediaTreeChangeNotification(treeChanges, messages)); - Audit(AuditType.Save, userId == -1 ? 0 : userId, Constants.System.Root, "Bulk save media"); + _mediaRepository.Save(media); + } - scope.Complete(); - } + scope.Notifications.Publish(new MediaSavedNotification(mediasA, messages).WithStateFrom(savingNotification)); + // TODO: See note about suppressing events in content service + scope.Notifications.Publish(new MediaTreeChangeNotification(treeChanges, messages)); + Audit(AuditType.Save, userId == -1 ? 0 : userId, Constants.System.Root, "Bulk save media"); - return OperationResult.Attempt.Succeed(messages); - } + scope.Complete(); + } - #endregion + return OperationResult.Attempt.Succeed(messages); + } - #region Delete + #endregion - /// - /// Permanently deletes an object - /// - /// The to delete - /// Id of the User deleting the Media - public Attempt Delete(IMedia media, int userId = Constants.Security.SuperUserId) - { - EventMessages messages = EventMessagesFactory.Get(); + #region Delete - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + /// + /// Permanently deletes an object + /// + /// The to delete + /// Id of the User deleting the Media + public Attempt Delete(IMedia media, int userId = Constants.Security.SuperUserId) { - if (scope.Notifications.PublishCancelable(new MediaDeletingNotification(media, messages))) + EventMessages messages = EventMessagesFactory.Get(); + + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { + if (scope.Notifications.PublishCancelable(new MediaDeletingNotification(media, messages))) + { + scope.Complete(); + return OperationResult.Attempt.Cancel(messages); + } + + scope.WriteLock(Constants.Locks.MediaTree); + + DeleteLocked(scope, media, messages); + + scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.Remove, messages)); + Audit(AuditType.Delete, userId, media.Id); + scope.Complete(); - return OperationResult.Attempt.Cancel(messages); } - scope.WriteLock(Constants.Locks.MediaTree); + return OperationResult.Attempt.Succeed(messages); + } - DeleteLocked(scope, media, messages); + private void DeleteLocked(ICoreScope scope, IMedia media, EventMessages evtMsgs) + { + void DoDelete(IMedia c) + { + _mediaRepository.Delete(c); + scope.Notifications.Publish(new MediaDeletedNotification(c, evtMsgs)); - scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.Remove, messages)); - Audit(AuditType.Delete, userId, media.Id); + // media files deleted by QueuingEventDispatcher + } - scope.Complete(); - } + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while (page * pageSize < total) + { + //get descendants - ordered from deepest to shallowest + IEnumerable descendants = GetPagedDescendants(media.Id, page, pageSize, out total, ordering: Ordering.By("Path", Direction.Descending)); + foreach (IMedia c in descendants) + { + DoDelete(c); + } + } - return OperationResult.Attempt.Succeed(messages); - } + DoDelete(media); + } - // TODO: both DeleteVersions methods below have an issue. Sort of. They do NOT take care of files the way - // Delete does - for a good reason: the file may be referenced by other, non-deleted, versions. BUT, - // if that's not the case, then the file will never be deleted, because when we delete the media, - // the version referencing the file will not be there anymore. SO, we can leak files. + //TODO: both DeleteVersions methods below have an issue. Sort of. They do NOT take care of files the way + // Delete does - for a good reason: the file may be referenced by other, non-deleted, versions. BUT, + // if that's not the case, then the file will never be deleted, because when we delete the media, + // the version referencing the file will not be there anymore. SO, we can leak files. - /// - /// Permanently deletes versions from an object prior to a specific date. - /// This method will never delete the latest version of a media item. - /// - /// Id of the object to delete versions from - /// Latest version date - /// Optional Id of the User deleting versions of a Media object - public void DeleteVersions(int id, DateTime versionDate, int userId = Constants.Security.SuperUserId) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + /// + /// Permanently deletes versions from an object prior to a specific date. + /// This method will never delete the latest version of a media item. + /// + /// Id of the object to delete versions from + /// Latest version date + /// Optional Id of the User deleting versions of a Media object + public void DeleteVersions(int id, DateTime versionDate, int userId = Constants.Security.SuperUserId) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(); DeleteVersions(scope, true, id, versionDate, userId); scope.Complete(); } - } - /// - /// Permanently deletes specific version(s) from an object. - /// This method will never delete the latest version of a media item. - /// - /// Id of the object to delete a version from - /// Id of the version to delete - /// Boolean indicating whether to delete versions prior to the versionId - /// Optional Id of the User deleting versions of a Media object - public void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = Constants.Security.SuperUserId) - { - EventMessages evtMsgs = EventMessagesFactory.Get(); + private void DeleteVersions(ICoreScope scope, bool wlock, int id, DateTime versionDate, int userId = Constants.Security.SuperUserId) + { + EventMessages evtMsgs = EventMessagesFactory.Get(); + + var deletingVersionsNotification = new MediaDeletingVersionsNotification(id, evtMsgs, dateToRetain: versionDate); + if (scope.Notifications.PublishCancelable(deletingVersionsNotification)) + { + return; + } + + if (wlock) + { + scope.WriteLock(Constants.Locks.MediaTree); + } + + _mediaRepository.DeleteVersions(id, versionDate); + + scope.Notifications.Publish(new MediaDeletedVersionsNotification(id, evtMsgs, dateToRetain: versionDate).WithStateFrom(deletingVersionsNotification)); + Audit(AuditType.Delete, userId, Constants.System.Root, "Delete Media by version date"); + } - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + /// + /// Permanently deletes specific version(s) from an object. + /// This method will never delete the latest version of a media item. + /// + /// Id of the object to delete a version from + /// Id of the version to delete + /// Boolean indicating whether to delete versions prior to the versionId + /// Optional Id of the User deleting versions of a Media object + public void DeleteVersion(int id, int versionId, bool deletePriorVersions, int userId = Constants.Security.SuperUserId) { - var deletingVersionsNotification = new MediaDeletingVersionsNotification(id, evtMsgs, versionId); + EventMessages evtMsgs = EventMessagesFactory.Get(); + + using ICoreScope scope = ScopeProvider.CreateCoreScope(); + var deletingVersionsNotification = new MediaDeletingVersionsNotification(id, evtMsgs, specificVersion: versionId); if (scope.Notifications.PublishCancelable(deletingVersionsNotification)) { scope.Complete(); @@ -970,506 +918,452 @@ public void DeleteVersion(int id, int versionId, bool deletePriorVersions, int u _mediaRepository.DeleteVersion(versionId); - scope.Notifications.Publish( - new MediaDeletedVersionsNotification(id, evtMsgs, versionId) - .WithStateFrom(deletingVersionsNotification)); + scope.Notifications.Publish(new MediaDeletedVersionsNotification(id, evtMsgs, specificVersion: versionId).WithStateFrom(deletingVersionsNotification)); Audit(AuditType.Delete, userId, Constants.System.Root, "Delete Media by version"); scope.Complete(); } - } - private void DeleteLocked(ICoreScope scope, IMedia media, EventMessages evtMsgs) - { - void DoDelete(IMedia c) - { - _mediaRepository.Delete(c); - scope.Notifications.Publish(new MediaDeletedNotification(c, evtMsgs)); + #endregion - // media files deleted by QueuingEventDispatcher - } + #region Move, RecycleBin - const int pageSize = 500; - var page = 0; - var total = long.MaxValue; - while (page * pageSize < total) + /// + /// Deletes an object by moving it to the Recycle Bin + /// + /// The to delete + /// Id of the User deleting the Media + public Attempt MoveToRecycleBin(IMedia media, int userId = Constants.Security.SuperUserId) { - // get descendants - ordered from deepest to shallowest - IEnumerable descendants = GetPagedDescendants(media.Id, page, pageSize, out total, ordering: Ordering.By("Path", Direction.Descending)); - foreach (IMedia c in descendants) - { - DoDelete(c); - } - } - - DoDelete(media); - } - - private void DeleteVersions(ICoreScope scope, bool wlock, int id, DateTime versionDate, int userId = Constants.Security.SuperUserId) - { - EventMessages evtMsgs = EventMessagesFactory.Get(); - - var deletingVersionsNotification = - new MediaDeletingVersionsNotification(id, evtMsgs, dateToRetain: versionDate); - if (scope.Notifications.PublishCancelable(deletingVersionsNotification)) - { - return; - } - - if (wlock) - { - scope.WriteLock(Constants.Locks.MediaTree); - } + EventMessages messages = EventMessagesFactory.Get(); + var moves = new List<(IMedia, string)>(); - _mediaRepository.DeleteVersions(id, versionDate); - - scope.Notifications.Publish( - new MediaDeletedVersionsNotification(id, evtMsgs, dateToRetain: versionDate).WithStateFrom( - deletingVersionsNotification)); - Audit(AuditType.Delete, userId, Constants.System.Root, "Delete Media by version date"); - } + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + scope.WriteLock(Constants.Locks.MediaTree); - #endregion + // TODO: missing 7.6 "ensure valid path" thing here? + // but then should be in PerformMoveLocked on every moved item? - #region Move, RecycleBin + var originalPath = media.Path; - /// - /// Deletes an object by moving it to the Recycle Bin - /// - /// The to delete - /// Id of the User deleting the Media - public Attempt MoveToRecycleBin(IMedia media, int userId = Constants.Security.SuperUserId) - { - EventMessages messages = EventMessagesFactory.Get(); - var moves = new List<(IMedia, string)>(); + var moveEventInfo = new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia); - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { - scope.WriteLock(Constants.Locks.MediaTree); + var movingToRecycleBinNotification = new MediaMovingToRecycleBinNotification(moveEventInfo, messages); + if (scope.Notifications.PublishCancelable(movingToRecycleBinNotification)) + { + scope.Complete(); + return OperationResult.Attempt.Cancel(messages); + } - // TODO: missing 7.6 "ensure valid path" thing here? - // but then should be in PerformMoveLocked on every moved item? - var originalPath = media.Path; + PerformMoveLocked(media, Constants.System.RecycleBinMedia, null, userId, moves, true); - var moveEventInfo = new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia); + scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshBranch, messages)); + MoveEventInfo[] moveInfo = moves.Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)).ToArray(); + scope.Notifications.Publish(new MediaMovedToRecycleBinNotification(moveInfo, messages).WithStateFrom(movingToRecycleBinNotification)); + Audit(AuditType.Move, userId, media.Id, "Move Media to recycle bin"); - var movingToRecycleBinNotification = new MediaMovingToRecycleBinNotification(moveEventInfo, messages); - if (scope.Notifications.PublishCancelable(movingToRecycleBinNotification)) - { scope.Complete(); - return OperationResult.Attempt.Cancel(messages); } - PerformMoveLocked(media, Constants.System.RecycleBinMedia, null, userId, moves, true); - - scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshBranch, messages)); - MoveEventInfo[] moveInfo = - moves.Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)).ToArray(); - scope.Notifications.Publish( - new MediaMovedToRecycleBinNotification(moveInfo, messages) - .WithStateFrom(movingToRecycleBinNotification)); - Audit(AuditType.Move, userId, media.Id, "Move Media to recycle bin"); - - scope.Complete(); - } - - return OperationResult.Attempt.Succeed(messages); - } - - /// - /// Moves an object to a new location - /// - /// The to move - /// Id of the Media's new Parent - /// Id of the User moving the Media - public Attempt Move(IMedia media, int parentId, int userId = Constants.Security.SuperUserId) - { - EventMessages messages = EventMessagesFactory.Get(); - - // if moving to the recycle bin then use the proper method - if (parentId == Constants.System.RecycleBinMedia) - { - MoveToRecycleBin(media, userId); return OperationResult.Attempt.Succeed(messages); } - var moves = new List<(IMedia, string)>(); - - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + /// + /// Moves an object to a new location + /// + /// The to move + /// Id of the Media's new Parent + /// Id of the User moving the Media + public Attempt Move(IMedia media, int parentId, int userId = Constants.Security.SuperUserId) { - scope.WriteLock(Constants.Locks.MediaTree); + EventMessages messages = EventMessagesFactory.Get(); - IMedia? parent = parentId == Constants.System.Root ? null : GetById(parentId); - if (parentId != Constants.System.Root && (parent == null || parent.Trashed)) + // if moving to the recycle bin then use the proper method + if (parentId == Constants.System.RecycleBinMedia) { - throw new InvalidOperationException("Parent does not exist or is trashed."); // causes rollback + MoveToRecycleBin(media, userId); + return OperationResult.Attempt.Succeed(messages); } - var moveEventInfo = new MoveEventInfo(media, media.Path, parentId); - var movingNotification = new MediaMovingNotification(moveEventInfo, messages); - if (scope.Notifications.PublishCancelable(movingNotification)) + var moves = new List<(IMedia, string)>(); + + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - scope.Complete(); - return OperationResult.Attempt.Cancel(messages); - } + scope.WriteLock(Constants.Locks.MediaTree); - // if media was trashed, and since we're not moving to the recycle bin, - // indicate that the trashed status should be changed to false, else just - // leave it unchanged - var trashed = media.Trashed ? false : (bool?)null; + IMedia? parent = parentId == Constants.System.Root ? null : GetById(parentId); + if (parentId != Constants.System.Root && (parent == null || parent.Trashed)) + { + throw new InvalidOperationException("Parent does not exist or is trashed."); // causes rollback + } - PerformMoveLocked(media, parentId, parent, userId, moves, trashed); - scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshBranch, messages)); + var moveEventInfo = new MoveEventInfo(media, media.Path, parentId); + var movingNotification = new MediaMovingNotification(moveEventInfo, messages); + if (scope.Notifications.PublishCancelable(movingNotification)) + { + scope.Complete(); + return OperationResult.Attempt.Cancel(messages); + } - MoveEventInfo[] moveInfo = moves // changes - .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) - .ToArray(); - scope.Notifications.Publish( - new MediaMovedNotification(moveInfo, messages).WithStateFrom(movingNotification)); - Audit(AuditType.Move, userId, media.Id); - scope.Complete(); - } + // if media was trashed, and since we're not moving to the recycle bin, + // indicate that the trashed status should be changed to false, else just + // leave it unchanged + var trashed = media.Trashed ? false : (bool?)null; - return OperationResult.Attempt.Succeed(messages); - } + PerformMoveLocked(media, parentId, parent, userId, moves, trashed); + scope.Notifications.Publish(new MediaTreeChangeNotification(media, TreeChangeTypes.RefreshBranch, messages)); - /// - /// Empties the Recycle Bin by deleting all that resides in the bin - /// - /// Optional Id of the User emptying the Recycle Bin - public OperationResult EmptyRecycleBin(int userId = Constants.Security.SuperUserId) - { - var deleted = new List(); - EventMessages messages = EventMessagesFactory.Get(); // TODO: and then? + MoveEventInfo[] moveInfo = moves //changes + .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) + .ToArray(); + scope.Notifications.Publish(new MediaMovedNotification(moveInfo, messages).WithStateFrom(movingNotification)); + Audit(AuditType.Move, userId, media.Id); + scope.Complete(); + } + return OperationResult.Attempt.Succeed(messages); + } - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + // MUST be called from within WriteLock + // trash indicates whether we are trashing, un-trashing, or not changing anything + private void PerformMoveLocked(IMedia media, int parentId, IMedia? parent, int userId, ICollection<(IMedia, string)> moves, bool? trash) { - scope.WriteLock(Constants.Locks.MediaTree); + media.ParentId = parentId; - // emptying the recycle bin means deleting whatever is in there - do it properly! - IQuery? query = Query().Where(x => x.ParentId == Constants.System.RecycleBinMedia); - IMedia[] medias = _mediaRepository.Get(query)?.ToArray() ?? Array.Empty(); + // get the level delta (old pos to new pos) + // note that recycle bin (id:-20) level is 0! + var levelDelta = 1 - media.Level + (parent?.Level ?? 0); - var emptyingRecycleBinNotification = new MediaEmptyingRecycleBinNotification(medias, messages); - if (scope.Notifications.PublishCancelable(emptyingRecycleBinNotification)) - { - scope.Complete(); - return OperationResult.Cancel(messages); - } + var paths = new Dictionary(); - foreach (IMedia media in medias) - { - DeleteLocked(scope, media, messages); - deleted.Add(media); - } + moves.Add((media, media.Path)); // capture original path - scope.Notifications.Publish( - new MediaEmptiedRecycleBinNotification(deleted, new EventMessages()).WithStateFrom( - emptyingRecycleBinNotification)); - scope.Notifications.Publish(new MediaTreeChangeNotification(deleted, TreeChangeTypes.Remove, messages)); - Audit(AuditType.Delete, userId, Constants.System.RecycleBinMedia, "Empty Media recycle bin"); - scope.Complete(); - } - - return OperationResult.Succeed(messages); - } + //need to store the original path to lookup descendants based on it below + var originalPath = media.Path; - // MUST be called from within WriteLock - // trash indicates whether we are trashing, un-trashing, or not changing anything - private void PerformMoveLocked(IMedia media, int parentId, IMedia? parent, int userId, ICollection<(IMedia, string)> moves, bool? trash) - { - media.ParentId = parentId; + // these will be updated by the repo because we changed parentId + //media.Path = (parent == null ? "-1" : parent.Path) + "," + media.Id; + //media.SortOrder = ((MediaRepository) repository).NextChildSortOrder(parentId); + //media.Level += levelDelta; + PerformMoveMediaLocked(media, trash); + + // if uow is not immediate, content.Path will be updated only when the UOW commits, + // and because we want it now, we have to calculate it by ourselves + //paths[media.Id] = media.Path; + paths[media.Id] = (parent == null ? parentId == Constants.System.RecycleBinMedia ? "-1,-21" : Constants.System.RootString : parent.Path) + "," + media.Id; + + const int pageSize = 500; + IQuery? query = GetPagedDescendantQuery(originalPath); + long total; + do + { + // We always page a page 0 because for each page, we are moving the result so the resulting total will be reduced + IEnumerable descendants = GetPagedLocked(query, 0, pageSize, out total, null, Ordering.By("Path")); - // get the level delta (old pos to new pos) - // note that recycle bin (id:-20) level is 0! - var levelDelta = 1 - media.Level + (parent?.Level ?? 0); + foreach (IMedia descendant in descendants) + { + moves.Add((descendant, descendant.Path)); // capture original path - var paths = new Dictionary(); + // update path and level since we do not update parentId + descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id; + descendant.Level += levelDelta; + PerformMoveMediaLocked(descendant, trash); + } - moves.Add((media, media.Path)); // capture original path + } + while (total > pageSize); - // need to store the original path to lookup descendants based on it below - var originalPath = media.Path; + } - // these will be updated by the repo because we changed parentId - // media.Path = (parent == null ? "-1" : parent.Path) + "," + media.Id; - // media.SortOrder = ((MediaRepository) repository).NextChildSortOrder(parentId); - // media.Level += levelDelta; - PerformMoveMediaLocked(media, userId, trash); + private void PerformMoveMediaLocked(IMedia media, bool? trash) + { + if (trash.HasValue) + { + ((ContentBase)media).Trashed = trash.Value; + } - // if uow is not immediate, content.Path will be updated only when the UOW commits, - // and because we want it now, we have to calculate it by ourselves - // paths[media.Id] = media.Path; - paths[media.Id] = - (parent == null - ? parentId == Constants.System.RecycleBinMedia ? "-1,-21" : Constants.System.RootString - : parent.Path) + "," + media.Id; + _mediaRepository.Save(media); + } - const int pageSize = 500; - IQuery? query = GetPagedDescendantQuery(originalPath); - long total; - do + /// + /// Empties the Recycle Bin by deleting all that resides in the bin + /// + /// Optional Id of the User emptying the Recycle Bin + public OperationResult EmptyRecycleBin(int userId = Constants.Security.SuperUserId) { - // We always page a page 0 because for each page, we are moving the result so the resulting total will be reduced - IEnumerable descendants = GetPagedLocked(query, 0, pageSize, out total, null, Ordering.By("Path")); + var deleted = new List(); + EventMessages messages = EventMessagesFactory.Get(); // TODO: and then? - foreach (IMedia descendant in descendants) + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - moves.Add((descendant, descendant.Path)); // capture original path + scope.WriteLock(Constants.Locks.MediaTree); - // update path and level since we do not update parentId - descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id; - descendant.Level += levelDelta; - PerformMoveMediaLocked(descendant, userId, trash); + // emptying the recycle bin means deleting whatever is in there - do it properly! + IQuery? query = Query().Where(x => x.ParentId == Constants.System.RecycleBinMedia); + IMedia[] medias = _mediaRepository.Get(query)?.ToArray() ?? Array.Empty(); + + var emptyingRecycleBinNotification = new MediaEmptyingRecycleBinNotification(medias, messages); + if (scope.Notifications.PublishCancelable(emptyingRecycleBinNotification)) + { + scope.Complete(); + return OperationResult.Cancel(messages); + } + + foreach (IMedia media in medias) + { + DeleteLocked(scope, media, messages); + deleted.Add(media); + } + scope.Notifications.Publish(new MediaEmptiedRecycleBinNotification(deleted, new EventMessages()).WithStateFrom(emptyingRecycleBinNotification)); + scope.Notifications.Publish(new MediaTreeChangeNotification(deleted, TreeChangeTypes.Remove, messages)); + Audit(AuditType.Delete, userId, Constants.System.RecycleBinMedia, "Empty Media recycle bin"); + scope.Complete(); } - } - while (total > pageSize); - } - private void PerformMoveMediaLocked(IMedia media, int userId, bool? trash) - { - if (trash.HasValue) - { - ((ContentBase)media).Trashed = trash.Value; + return OperationResult.Succeed(messages); } - _mediaRepository.Save(media); - } - - public bool RecycleBinSmells() - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + public bool RecycleBinSmells() { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MediaTree); return _mediaRepository.RecycleBinSmells(); } - } - #endregion + #endregion - #region Others + #region Others - /// - /// Sorts a collection of objects by updating the SortOrder according - /// to the ordering of items in the passed in . - /// - /// - /// - /// True if sorting succeeded, otherwise False - public bool Sort(IEnumerable items, int userId = Constants.Security.SuperUserId) - { - IMedia[] itemsA = items.ToArray(); - if (itemsA.Length == 0) + /// + /// Sorts a collection of objects by updating the SortOrder according + /// to the ordering of items in the passed in . + /// + /// + /// + /// True if sorting succeeded, otherwise False + public bool Sort(IEnumerable items, int userId = Constants.Security.SuperUserId) { - return true; - } + IMedia[] itemsA = items.ToArray(); + if (itemsA.Length == 0) + { + return true; + } - EventMessages messages = EventMessagesFactory.Get(); + EventMessages messages = EventMessagesFactory.Get(); - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { - var savingNotification = new MediaSavingNotification(itemsA, messages); - if (scope.Notifications.PublishCancelable(savingNotification)) + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - scope.Complete(); - return false; - } + var savingNotification = new MediaSavingNotification(itemsA, messages); + if (scope.Notifications.PublishCancelable(savingNotification)) + { + scope.Complete(); + return false; + } - var saved = new List(); + var saved = new List(); - scope.WriteLock(Constants.Locks.MediaTree); - var sortOrder = 0; + scope.WriteLock(Constants.Locks.MediaTree); + var sortOrder = 0; - foreach (IMedia media in itemsA) - { - // if the current sort order equals that of the media we don't - // need to update it, so just increment the sort order and continue. - if (media.SortOrder == sortOrder) + foreach (IMedia media in itemsA) { - sortOrder++; - continue; + // if the current sort order equals that of the media we don't + // need to update it, so just increment the sort order and continue. + if (media.SortOrder == sortOrder) + { + sortOrder++; + continue; + } + // else update + media.SortOrder = sortOrder++; + // save + saved.Add(media); + _mediaRepository.Save(media); } - // else update - media.SortOrder = sortOrder++; + scope.Notifications.Publish(new MediaSavedNotification(itemsA, messages).WithStateFrom(savingNotification)); + // TODO: See note about suppressing events in content service + scope.Notifications.Publish(new MediaTreeChangeNotification(saved, TreeChangeTypes.RefreshNode, messages)); + Audit(AuditType.Sort, userId, 0); - // save - saved.Add(media); - _mediaRepository.Save(media); + scope.Complete(); } - scope.Notifications.Publish(new MediaSavedNotification(itemsA, messages).WithStateFrom(savingNotification)); - - // TODO: See note about suppressing events in content service - scope.Notifications.Publish(new MediaTreeChangeNotification(saved, TreeChangeTypes.RefreshNode, messages)); - Audit(AuditType.Sort, userId, 0); + return true; - scope.Complete(); } - return true; - } - - public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options) { - scope.WriteLock(Constants.Locks.MediaTree); + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + { + scope.WriteLock(Constants.Locks.MediaTree); - ContentDataIntegrityReport report = _mediaRepository.CheckDataIntegrity(options); + ContentDataIntegrityReport report = _mediaRepository.CheckDataIntegrity(options); - if (report.FixedIssues.Count > 0) - { - // The event args needs a content item so we'll make a fake one with enough properties to not cause a null ref - var root = new Models.Media("root", -1, new MediaType(_shortStringHelper, -1)) + if (report.FixedIssues.Count > 0) { - Id = -1, - Key = Guid.Empty, - }; - scope.Notifications.Publish(new MediaTreeChangeNotification(root, TreeChangeTypes.RefreshAll, EventMessagesFactory.Get())); - } + //The event args needs a content item so we'll make a fake one with enough properties to not cause a null ref + var root = new Core.Models.Media("root", -1, new MediaType(_shortStringHelper, -1)) { Id = -1, Key = Guid.Empty }; + scope.Notifications.Publish(new MediaTreeChangeNotification(root, TreeChangeTypes.RefreshAll, EventMessagesFactory.Get())); + } - return report; + return report; + } } - } - #endregion + #endregion - #region File Management + #region Private Methods - public Stream GetMediaFileContentStream(string filepath) - { - if (_mediaFileManager.FileSystem.FileExists(filepath) == false) + private void Audit(AuditType type, int userId, int objectId, string? message = null) { - return Stream.Null; + _auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.Media), message)); } - try + #endregion + + #region File Management + + public Stream GetMediaFileContentStream(string filepath) { - return _mediaFileManager.FileSystem.OpenFile(filepath); + if (_mediaFileManager.FileSystem.FileExists(filepath) == false) + { + return Stream.Null; + } + + try + { + return _mediaFileManager.FileSystem.OpenFile(filepath); + } + catch + { + return Stream.Null; // deal with race conds + } } - catch + + public void SetMediaFileContent(string filepath, Stream stream) { - return Stream.Null; // deal with race conds + _mediaFileManager.FileSystem.AddFile(filepath, stream, true); } - } - - public void SetMediaFileContent(string filepath, Stream stream) => - _mediaFileManager.FileSystem.AddFile(filepath, stream, true); - public void DeleteMediaFile(string filepath) => _mediaFileManager.FileSystem.DeleteFile(filepath); + public void DeleteMediaFile(string filepath) + { + _mediaFileManager.FileSystem.DeleteFile(filepath); + } - public long GetMediaFileSize(string filepath) => _mediaFileManager.FileSystem.GetSize(filepath); + public long GetMediaFileSize(string filepath) + { + return _mediaFileManager.FileSystem.GetSize(filepath); + } - #endregion + #endregion - #region Content Types + #region Content Types - /// - /// Deletes all media of specified type. All children of deleted media is moved to Recycle Bin. - /// - /// - /// This needs extra care and attention as its potentially a dangerous and extensive operation. - /// - /// Deletes media items of the specified type, and only that type. Does *not* handle content types - /// inheritance and compositions, which need to be managed outside of this method. - /// - /// - /// Id of the - /// Optional id of the user deleting the media - public void DeleteMediaOfTypes(IEnumerable mediaTypeIds, int userId = Constants.Security.SuperUserId) - { - // TODO: This currently this is called from the ContentTypeService but that needs to change, - // if we are deleting a content type, we should just delete the data and do this operation slightly differently. - // This method will recursively go lookup every content item, check if any of it's descendants are - // of a different type, move them to the recycle bin, then permanently delete the content items. - // The main problem with this is that for every content item being deleted, events are raised... - // which we need for many things like keeping caches in sync, but we can surely do this MUCH better. - var changes = new List>(); - var moves = new List<(IMedia, string)>(); - var mediaTypeIdsA = mediaTypeIds.ToArray(); - EventMessages messages = EventMessagesFactory.Get(); - - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + /// + /// Deletes all media of specified type. All children of deleted media is moved to Recycle Bin. + /// + /// + /// This needs extra care and attention as its potentially a dangerous and extensive operation. + /// Deletes media items of the specified type, and only that type. Does *not* handle content types + /// inheritance and compositions, which need to be managed outside of this method. + /// + /// Id of the + /// Optional id of the user deleting the media + public void DeleteMediaOfTypes(IEnumerable mediaTypeIds, int userId = Constants.Security.SuperUserId) { - scope.WriteLock(Constants.Locks.MediaTree); + // TODO: This currently this is called from the ContentTypeService but that needs to change, + // if we are deleting a content type, we should just delete the data and do this operation slightly differently. + // This method will recursively go lookup every content item, check if any of it's descendants are + // of a different type, move them to the recycle bin, then permanently delete the content items. + // The main problem with this is that for every content item being deleted, events are raised... + // which we need for many things like keeping caches in sync, but we can surely do this MUCH better. - IQuery? query = Query().WhereIn(x => x.ContentTypeId, mediaTypeIdsA); - IMedia[] medias = _mediaRepository.Get(query)?.ToArray() ?? Array.Empty(); + var changes = new List>(); + var moves = new List<(IMedia, string)>(); + var mediaTypeIdsA = mediaTypeIds.ToArray(); + EventMessages messages = EventMessagesFactory.Get(); - if (scope.Notifications.PublishCancelable(new MediaDeletingNotification(medias, messages))) + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - scope.Complete(); - return; - } + scope.WriteLock(Constants.Locks.MediaTree); - // order by level, descending, so deepest first - that way, we cannot move - // a media of the deleted type, to the recycle bin (and then delete it...) - foreach (IMedia media in medias.OrderByDescending(x => x.ParentId)) - { - // if current media has children, move them to trash - IMedia m = media; - IQuery childQuery = Query().Where(x => x.Path.StartsWith(m.Path)); - IEnumerable children = _mediaRepository.Get(childQuery); + IQuery? query = Query().WhereIn(x => x.ContentTypeId, mediaTypeIdsA); + IMedia[] medias = _mediaRepository.Get(query)?.ToArray() ?? Array.Empty(); - foreach (IMedia child in children.Where(x => mediaTypeIdsA.Contains(x.ContentTypeId) == false)) + if (scope.Notifications.PublishCancelable(new MediaDeletingNotification(medias, messages))) { - // see MoveToRecycleBin - PerformMoveLocked(child, Constants.System.RecycleBinMedia, null, userId, moves, true); - changes.Add(new TreeChange(media, TreeChangeTypes.RefreshBranch)); + scope.Complete(); + return; } - // delete media - // triggers the deleted event (and handles the files) - DeleteLocked(scope, media, messages); - changes.Add(new TreeChange(media, TreeChangeTypes.Remove)); - } - - MoveEventInfo[] moveInfos = moves - .Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) - .ToArray(); - if (moveInfos.Length > 0) - { - scope.Notifications.Publish(new MediaMovedToRecycleBinNotification(moveInfos, messages)); - } + // order by level, descending, so deepest first - that way, we cannot move + // a media of the deleted type, to the recycle bin (and then delete it...) + foreach (IMedia media in medias.OrderByDescending(x => x.ParentId)) + { + // if current media has children, move them to trash + IMedia m = media; + IQuery? childQuery = Query().Where(x => x.Path.StartsWith(m.Path)); + IEnumerable? children = _mediaRepository.Get(childQuery); + if (children is not null) + { + foreach (IMedia child in children.Where(x => mediaTypeIdsA.Contains(x.ContentTypeId) == false)) + { + // see MoveToRecycleBin + PerformMoveLocked(child, Constants.System.RecycleBinMedia, null, userId, moves, true); + changes.Add(new TreeChange(media, TreeChangeTypes.RefreshBranch)); + } + } + + // delete media + // triggers the deleted event (and handles the files) + DeleteLocked(scope, media, messages); + changes.Add(new TreeChange(media, TreeChangeTypes.Remove)); + } - scope.Notifications.Publish(new MediaTreeChangeNotification(changes, messages)); + MoveEventInfo[] moveInfos = moves.Select(x => new MoveEventInfo(x.Item1, x.Item2, x.Item1.ParentId)) + .ToArray(); + if (moveInfos.Length > 0) + { + scope.Notifications.Publish(new MediaMovedToRecycleBinNotification(moveInfos, messages)); + } + scope.Notifications.Publish(new MediaTreeChangeNotification(changes, messages)); - Audit(AuditType.Delete, userId, Constants.System.Root, $"Delete Media of types {string.Join(",", mediaTypeIdsA)}"); + Audit(AuditType.Delete, userId, Constants.System.Root, $"Delete Media of types {string.Join(",", mediaTypeIdsA)}"); - scope.Complete(); + scope.Complete(); + } } - } - - /// - /// Deletes all media of specified type. All children of deleted media is moved to Recycle Bin. - /// - /// This needs extra care and attention as its potentially a dangerous and extensive operation - /// Id of the - /// Optional id of the user deleting the media - public void DeleteMediaOfType(int mediaTypeId, int userId = Constants.Security.SuperUserId) => - DeleteMediaOfTypes(new[] { mediaTypeId }, userId); - private IMediaType GetMediaType(string mediaTypeAlias) - { - if (mediaTypeAlias == null) + /// + /// Deletes all media of specified type. All children of deleted media is moved to Recycle Bin. + /// + /// This needs extra care and attention as its potentially a dangerous and extensive operation + /// Id of the + /// Optional id of the user deleting the media + public void DeleteMediaOfType(int mediaTypeId, int userId = Constants.Security.SuperUserId) { - throw new ArgumentNullException(nameof(mediaTypeAlias)); + DeleteMediaOfTypes(new[] { mediaTypeId }, userId); } - if (string.IsNullOrWhiteSpace(mediaTypeAlias)) + private IMediaType GetMediaType(string mediaTypeAlias) { - throw new ArgumentException( - "Value can't be empty or consist only of white-space characters.", - nameof(mediaTypeAlias)); - } + if (mediaTypeAlias == null) + { + throw new ArgumentNullException(nameof(mediaTypeAlias)); + } - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { + if (string.IsNullOrWhiteSpace(mediaTypeAlias)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(mediaTypeAlias)); + } + + using ICoreScope scope = ScopeProvider.CreateCoreScope(); scope.ReadLock(Constants.Locks.MediaTypes); IQuery query = Query().Where(x => x.Alias == mediaTypeAlias); - IMediaType? mediaType = _mediaTypeRepository.Get(query).FirstOrDefault(); + IMediaType? mediaType = _mediaTypeRepository.Get(query)?.FirstOrDefault(); if (mediaType == null) { @@ -1479,7 +1373,9 @@ private IMediaType GetMediaType(string mediaTypeAlias) scope.Complete(); return mediaType; } - } - #endregion + #endregion + + + } } diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index 88fa6b1b795d..76d730dc78a4 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -9,60 +9,57 @@ using Umbraco.Cms.Core.Scoping; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services; - -/// -/// Represents the MemberService. -/// -public class MemberService : RepositoryService, IMemberService +namespace Umbraco.Cms.Core.Services { - private readonly IAuditRepository _auditRepository; - private readonly IMemberGroupRepository _memberGroupRepository; - - private readonly IMemberGroupService _memberGroupService; - private readonly IMemberRepository _memberRepository; - private readonly IMemberTypeRepository _memberTypeRepository; - - #region Constructor - - public MemberService( - ICoreScopeProvider provider, - ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, - IMemberGroupService memberGroupService, - IMemberRepository memberRepository, - IMemberTypeRepository memberTypeRepository, - IMemberGroupRepository memberGroupRepository, - IAuditRepository auditRepository) - : base(provider, loggerFactory, eventMessagesFactory) - { - _memberRepository = memberRepository; - _memberTypeRepository = memberTypeRepository; - _memberGroupRepository = memberGroupRepository; - _auditRepository = auditRepository; - _memberGroupService = memberGroupService ?? throw new ArgumentNullException(nameof(memberGroupService)); - } - - #endregion - - #region Count - /// - /// Gets the total number of Members based on the count type + /// Represents the MemberService. /// - /// - /// The way the Online count is done is the same way that it is done in the MS SqlMembershipProvider - We query for any - /// members - /// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact - /// science - /// but that is how MS have made theirs so we'll follow that principal. - /// - /// to count by - /// with number of Members for passed in type - public int GetCount(MemberCountType countType) + public class MemberService : RepositoryService, IMemberService { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + private readonly IMemberRepository _memberRepository; + private readonly IMemberTypeRepository _memberTypeRepository; + private readonly IMemberGroupRepository _memberGroupRepository; + private readonly IAuditRepository _auditRepository; + + private readonly IMemberGroupService _memberGroupService; + + #region Constructor + + public MemberService( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IMemberGroupService memberGroupService, + IMemberRepository memberRepository, + IMemberTypeRepository memberTypeRepository, + IMemberGroupRepository memberGroupRepository, + IAuditRepository auditRepository) + : base(provider, loggerFactory, eventMessagesFactory) { + _memberRepository = memberRepository; + _memberTypeRepository = memberTypeRepository; + _memberGroupRepository = memberGroupRepository; + _auditRepository = auditRepository; + _memberGroupService = memberGroupService ?? throw new ArgumentNullException(nameof(memberGroupService)); + } + + #endregion + + #region Count + + /// + /// Gets the total number of Members based on the count type + /// + /// + /// The way the Online count is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members + /// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact science + /// but that is how MS have made theirs so we'll follow that principal. + /// + /// to count by + /// with number of Members for passed in type + public int GetCount(MemberCountType countType) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); IQuery? query; @@ -84,278 +81,203 @@ public int GetCount(MemberCountType countType) return _memberRepository.GetCountByQuery(query); } - } - #endregion - - #region Private Methods - - private void Audit(AuditType type, int userId, int objectId, string? message = null) => - _auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.Member.GetName(), message)); - - /// - /// Gets the count of Members by an optional MemberType alias - /// - /// If no alias is supplied then the count for all Member will be returned - /// Optional alias for the MemberType when counting number of Members - /// with number of Members - public int Count(string? memberTypeAlias = null) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Gets the count of Members by an optional MemberType alias + /// + /// If no alias is supplied then the count for all Member will be returned + /// Optional alias for the MemberType when counting number of Members + /// with number of Members + public int Count(string? memberTypeAlias = null) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); return _memberRepository.Count(memberTypeAlias); } - } - - #endregion - - #region Create - /// - /// Creates an object without persisting it - /// - /// - /// This method is convenient for when you need to add properties to a new Member - /// before persisting it in order to limit the amount of times its saved. - /// Also note that the returned will not have an Id until its saved. - /// - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// Alias of the MemberType the Member should be based on - /// Thrown when a member type for the given alias isn't found - /// - /// - /// - public IMember CreateMember(string username, string email, string name, string memberTypeAlias) - { - IMemberType memberType = GetMemberType(memberTypeAlias); - if (memberType == null) + #endregion + + #region Create + + /// + /// Creates an object without persisting it + /// + /// This method is convenient for when you need to add properties to a new Member + /// before persisting it in order to limit the amount of times its saved. + /// Also note that the returned will not have an Id until its saved. + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// Alias of the MemberType the Member should be based on + /// Thrown when a member type for the given alias isn't found + /// + public IMember CreateMember(string username, string email, string name, string memberTypeAlias) { - throw new ArgumentException("No member type with that alias.", nameof(memberTypeAlias)); - } - - var member = new Member(name, email.ToLower().Trim(), username, memberType, 0); + IMemberType memberType = GetMemberType(memberTypeAlias); + if (memberType == null) + { + throw new ArgumentException("No member type with that alias.", nameof(memberTypeAlias)); + } - return member; - } + var member = new Member(name, email.ToLower().Trim(), username, memberType, 0); - /// - /// Creates an object without persisting it - /// - /// - /// This method is convenient for when you need to add properties to a new Member - /// before persisting it in order to limit the amount of times its saved. - /// Also note that the returned will not have an Id until its saved. - /// - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// MemberType the Member should be based on - /// - /// - /// - public IMember CreateMember(string username, string email, string name, IMemberType memberType) - { - if (memberType == null) - { - throw new ArgumentNullException(nameof(memberType)); + return member; } - var member = new Member(name, email.ToLower().Trim(), username, memberType, 0); - - return member; - } - - /// - /// Creates and persists a new - /// - /// An can be of type or - /// Username of the to create - /// Email of the to create - /// - /// This value should be the encoded/encrypted/hashed value for the password that will be - /// stored in the database - /// - /// Alias of the Type - /// Is the member approved - /// - /// - /// - IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias) - => CreateMemberWithIdentity(username, email, username, passwordValue, memberTypeAlias); - - /// - /// Creates and persists a new - /// - /// An can be of type or - /// Username of the to create - /// Email of the to create - /// - /// This value should be the encoded/encrypted/hashed value for the password that will be - /// stored in the database - /// - /// Alias of the Type - /// - /// - /// - /// - IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias, bool isApproved) - => CreateMemberWithIdentity(username, email, username, passwordValue, memberTypeAlias, isApproved); - - public IMember CreateMemberWithIdentity(string username, string email, string memberTypeAlias) - => CreateMemberWithIdentity(username, email, username, string.Empty, memberTypeAlias); - - public IMember CreateMemberWithIdentity(string username, string email, string memberTypeAlias, bool isApproved) - => CreateMemberWithIdentity(username, email, username, string.Empty, memberTypeAlias, isApproved); - - public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias) - => CreateMemberWithIdentity(username, email, name, string.Empty, memberTypeAlias); - - public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias, bool isApproved) - => CreateMemberWithIdentity(username, email, name, string.Empty, memberTypeAlias, isApproved); - - /// - /// Creates and persists a Member - /// - /// - /// Using this method will persist the Member object before its returned - /// meaning that it will have an Id available (unlike the CreateMember method) - /// - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// Alias of the MemberType the Member should be based on - /// Optional IsApproved of the Member to create - /// Password of the Member to create - /// - /// - /// - public IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, string memberTypeAlias, bool isApproved = true) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + /// + /// Creates an object without persisting it + /// + /// This method is convenient for when you need to add properties to a new Member + /// before persisting it in order to limit the amount of times its saved. + /// Also note that the returned will not have an Id until its saved. + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// MemberType the Member should be based on + /// + public IMember CreateMember(string username, string email, string name, IMemberType memberType) { - // locking the member tree secures member types too - scope.WriteLock(Constants.Locks.MemberTree); - - IMemberType memberType = GetMemberType(scope, memberTypeAlias); // + locks // + locks if (memberType == null) { - throw new ArgumentException( - "No member type with that alias.", - nameof(memberTypeAlias)); // causes rollback // causes rollback + throw new ArgumentNullException(nameof(memberType)); } - var member = new Member(name, email.ToLower().Trim(), username, passwordValue, memberType, isApproved, -1); - - Save(member); - - scope.Complete(); + var member = new Member(name, email.ToLower().Trim(), username, memberType, 0); return member; } - } - public IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType) - => CreateMemberWithIdentity(username, email, username, string.Empty, memberType); + /// + /// Creates and persists a new + /// + /// An can be of type or + /// Username of the to create + /// Email of the to create + /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database + /// Alias of the Type + /// Is the member approved + /// + IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias) + => CreateMemberWithIdentity(username, email, username, passwordValue, memberTypeAlias); + + /// + /// Creates and persists a new + /// + /// An can be of type or + /// Username of the to create + /// Email of the to create + /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database + /// Alias of the Type + /// + /// + IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias, bool isApproved) + => CreateMemberWithIdentity(username, email, username, passwordValue, memberTypeAlias, isApproved); + + public IMember CreateMemberWithIdentity(string username, string email, string memberTypeAlias) + => CreateMemberWithIdentity(username, email, username, string.Empty, memberTypeAlias); + + public IMember CreateMemberWithIdentity(string username, string email, string memberTypeAlias, bool isApproved) + => CreateMemberWithIdentity(username, email, string.Empty, string.Empty, memberTypeAlias, isApproved); + + public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias) + => CreateMemberWithIdentity(username, email, string.Empty, string.Empty, memberTypeAlias); + + public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias, bool isApproved) + => CreateMemberWithIdentity(username, string.Empty, name, string.Empty, memberTypeAlias, isApproved); + + /// + /// Creates and persists a Member + /// + /// Using this method will persist the Member object before its returned + /// meaning that it will have an Id available (unlike the CreateMember method) + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// Alias of the MemberType the Member should be based on + /// Optional IsApproved of the Member to create + /// + public IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, string memberTypeAlias, bool isApproved = true) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + // locking the member tree secures member types too + scope.WriteLock(Constants.Locks.MemberTree); - /// - /// Creates and persists a Member - /// - /// - /// Using this method will persist the Member object before its returned - /// meaning that it will have an Id available (unlike the CreateMember method) - /// - /// Username of the Member to create - /// Email of the Member to create - /// MemberType the Member should be based on - /// - /// - /// - /// - public IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType, bool isApproved) - => CreateMemberWithIdentity(username, email, username, string.Empty, memberType, isApproved); - - public IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType) - => CreateMemberWithIdentity(username, email, name, string.Empty, memberType); + IMemberType memberType = GetMemberType(scope, memberTypeAlias); // + locks // + locks + if (memberType == null) + { + throw new ArgumentException("No member type with that alias.", nameof(memberTypeAlias)); // causes rollback // causes rollback + } - /// - /// Creates and persists a Member - /// - /// - /// Using this method will persist the Member object before its returned - /// meaning that it will have an Id available (unlike the CreateMember method) - /// - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// MemberType the Member should be based on - /// - /// - /// - /// - public IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType, bool isApproved) - => CreateMemberWithIdentity(username, email, name, string.Empty, memberType, isApproved); - - #endregion - - #region Get, Has, Is, Exists... + var member = new Member(name, email.ToLower().Trim(), username, passwordValue, memberType, isApproved, -1); - /// - /// Gets a Member by its integer id - /// - /// Id - /// - /// - /// - public IMember? GetById(int id) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.MemberTree); - return _memberRepository.Get(id); - } - } + Save(member); - /// - /// Creates and persists a Member - /// - /// - /// Using this method will persist the Member object before its returned - /// meaning that it will have an Id available (unlike the CreateMember method) - /// - /// Username of the Member to create - /// Email of the Member to create - /// Name of the Member to create - /// - /// This value should be the encoded/encrypted/hashed value for the password that will be - /// stored in the database - /// - /// MemberType the Member should be based on - /// - /// - /// - /// - private IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, IMemberType memberType, bool isApproved = true) - { - if (memberType == null) - { - throw new ArgumentNullException(nameof(memberType)); + scope.Complete(); + + return member; + } } - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + public IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType) + => CreateMemberWithIdentity(username, email, username, string.Empty, memberType); + + /// + /// Creates and persists a Member + /// + /// Using this method will persist the Member object before its returned + /// meaning that it will have an Id available (unlike the CreateMember method) + /// Username of the Member to create + /// Email of the Member to create + /// MemberType the Member should be based on + /// + public IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType, bool isApproved) + => CreateMemberWithIdentity(username, email, username, string.Empty, memberType, isApproved); + + public IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType) + => CreateMemberWithIdentity(username, email, name, string.Empty, memberType); + + /// + /// Creates and persists a Member + /// + /// Using this method will persist the Member object before its returned + /// meaning that it will have an Id available (unlike the CreateMember method) + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// MemberType the Member should be based on + /// + public IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType, bool isApproved) + => CreateMemberWithIdentity(username, email, name, string.Empty, memberType, isApproved); + + /// + /// Creates and persists a Member + /// + /// Using this method will persist the Member object before its returned + /// meaning that it will have an Id available (unlike the CreateMember method) + /// Username of the Member to create + /// Email of the Member to create + /// Name of the Member to create + /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database + /// MemberType the Member should be based on + /// + private IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, IMemberType memberType, bool isApproved = true) { + if (memberType == null) + { + throw new ArgumentNullException(nameof(memberType)); + } + + using ICoreScope scope = ScopeProvider.CreateCoreScope(); scope.WriteLock(Constants.Locks.MemberTree); // ensure it all still make sense // ensure it all still make sense - IMemberType vrfy = GetMemberType(scope, memberType.Alias); // + locks + IMemberType? vrfy = GetMemberType(scope, memberType.Alias); // + locks if (vrfy == null || vrfy.Id != memberType.Id) { - throw new ArgumentException( - $"Member type with alias {memberType.Alias} does not exist or is a different member type."); // causes rollback + throw new ArgumentException($"Member type with alias {memberType.Alias} does not exist or is a different member type."); // causes rollback } var member = new Member(name, email.ToLower().Trim(), username, passwordValue, memberType, isApproved, -1); @@ -366,237 +288,191 @@ private IMember CreateMemberWithIdentity(string username, string email, string n return member; } - } - /// - /// Gets a Member by the unique key - /// - /// - /// The guid key corresponds to the unique id in the database - /// and the user id in the membership provider. - /// - /// Id - /// - /// - /// - public IMember? GetByKey(Guid id) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + #endregion + + #region Get, Has, Is, Exists... + + /// + /// Gets a Member by its integer id + /// + /// Id + /// + public IMember? GetById(int id) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + scope.ReadLock(Constants.Locks.MemberTree); + return _memberRepository.Get(id); + } + + /// + /// Gets a Member by the unique key + /// + /// The guid key corresponds to the unique id in the database + /// and the user id in the membership provider. + /// Id + /// + public IMember? GetByKey(Guid id) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); IQuery query = Query().Where(x => x.Key == id); return _memberRepository.Get(query)?.FirstOrDefault(); } - } - /// - /// Gets a list of paged objects - /// - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// - /// - /// - public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Gets a list of paged objects + /// + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// + public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); return _memberRepository.GetPage(null, pageIndex, pageSize, out totalRecords, null, Ordering.By("LoginName")); } - } - public IEnumerable GetAll( - long pageIndex, - int pageSize, - out long totalRecords, - string orderBy, - Direction orderDirection, - string? memberTypeAlias = null, - string filter = "") => - GetAll(pageIndex, pageSize, out totalRecords, orderBy, orderDirection, true, memberTypeAlias, filter); - - public IEnumerable GetAll( - long pageIndex, - int pageSize, - out long totalRecords, - string orderBy, - Direction orderDirection, - bool orderBySystemField, - string? memberTypeAlias, - string filter) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + public IEnumerable GetAll( + long pageIndex, + int pageSize, + out long totalRecords, + string orderBy, + Direction orderDirection, + string? memberTypeAlias = null, + string filter = "") => + GetAll(pageIndex, pageSize, out totalRecords, orderBy, orderDirection, true, memberTypeAlias, filter); + + public IEnumerable GetAll( + long pageIndex, + int pageSize, + out long totalRecords, + string orderBy, + Direction orderDirection, + bool orderBySystemField, + string? memberTypeAlias, + string filter) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); - IQuery? query1 = memberTypeAlias == null - ? null - : Query()?.Where(x => x.ContentTypeAlias == memberTypeAlias); - IQuery? query2 = filter == null - ? null - : Query()?.Where(x => - (x.Name != null && x.Name.Contains(filter)) || x.Username.Contains(filter) || - x.Email.Contains(filter)); - return _memberRepository.GetPage( - query1, - pageIndex, - pageSize, - out totalRecords, - query2, - Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); + IQuery? query1 = memberTypeAlias == null ? null : Query()?.Where(x => x.ContentTypeAlias == memberTypeAlias); + IQuery? query2 = filter == null ? null : Query()?.Where(x => (x.Name != null && x.Name.Contains(filter)) || x.Username.Contains(filter) || x.Email.Contains(filter)); + return _memberRepository.GetPage(query1, pageIndex, pageSize, out totalRecords, query2, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); } - } - /// - /// Gets an by its provider key - /// - /// Id to use for retrieval - /// - /// - /// - public IMember? GetByProviderKey(object id) - { - Attempt asGuid = id.TryConvertTo(); - if (asGuid.Success) + /// + /// Gets an by its provider key + /// + /// Id to use for retrieval + /// + public IMember? GetByProviderKey(object id) { - return GetByKey(asGuid.Result); - } + Attempt asGuid = id.TryConvertTo(); + if (asGuid.Success) + { + return GetByKey(asGuid.Result); + } - Attempt asInt = id.TryConvertTo(); - if (asInt.Success) - { - return GetById(asInt.Result); - } + Attempt asInt = id.TryConvertTo(); + if (asInt.Success) + { + return GetById(asInt.Result); + } - return null; - } + return null; + } - /// - /// Get an by email - /// - /// Email to use for retrieval - /// - /// - /// - public IMember? GetByEmail(string email) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Get an by email + /// + /// Email to use for retrieval + /// + public IMember? GetByEmail(string email) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); IQuery query = Query().Where(x => x.Email.Equals(email)); return _memberRepository.Get(query)?.FirstOrDefault(); } - } - /// - /// Get an by username - /// - /// Username to use for retrieval - /// - /// - /// - public IMember? GetByUsername(string? username) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Get an by username + /// + /// Username to use for retrieval + /// + public IMember? GetByUsername(string? username) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); return _memberRepository.GetByUsername(username); } - } - /// - /// Gets all Members for the specified MemberType alias - /// - /// Alias of the MemberType - /// - /// - /// - public IEnumerable GetMembersByMemberType(string memberTypeAlias) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Gets all Members for the specified MemberType alias + /// + /// Alias of the MemberType + /// + public IEnumerable GetMembersByMemberType(string memberTypeAlias) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); IQuery query = Query().Where(x => x.ContentTypeAlias == memberTypeAlias); return _memberRepository.Get(query); } - } - /// - /// Gets all Members for the MemberType id - /// - /// Id of the MemberType - /// - /// - /// - public IEnumerable GetMembersByMemberType(int memberTypeId) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Gets all Members for the MemberType id + /// + /// Id of the MemberType + /// + public IEnumerable GetMembersByMemberType(int memberTypeId) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); IQuery query = Query().Where(x => x.ContentTypeId == memberTypeId); return _memberRepository.Get(query); } - } - /// - /// Gets all Members within the specified MemberGroup name - /// - /// Name of the MemberGroup - /// - /// - /// - public IEnumerable GetMembersByGroup(string memberGroupName) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Gets all Members within the specified MemberGroup name + /// + /// Name of the MemberGroup + /// + public IEnumerable GetMembersByGroup(string memberGroupName) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); return _memberRepository.GetByMemberGroup(memberGroupName); } - } - /// - /// Gets all Members with the ids specified - /// - /// If no Ids are specified all Members will be retrieved - /// Optional list of Member Ids - /// - /// - /// - public IEnumerable GetAllMembers(params int[] ids) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Gets all Members with the ids specified + /// + /// If no Ids are specified all Members will be retrieved + /// Optional list of Member Ids + /// + public IEnumerable GetAllMembers(params int[] ids) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); return _memberRepository.GetMany(ids); } - } - /// - /// Finds Members based on their display name - /// - /// Display name to match - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// - /// The type of match to make as . Default is - /// - /// - /// - /// - /// - public IEnumerable FindMembersByDisplayName( - string displayNameToMatch, - long pageIndex, - int pageSize, - out long totalRecords, - StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Finds Members based on their display name + /// + /// Display name to match + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// The type of match to make as . Default is + /// + public IEnumerable FindMembersByDisplayName(string displayNameToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); - IQuery query = Query(); + IQuery? query = Query(); switch (matchType) { @@ -613,8 +489,7 @@ public IEnumerable FindMembersByDisplayName( query?.Where(member => member.Name != null && member.Name.EndsWith(displayNameToMatch)); break; case StringPropertyMatchType.Wildcard: - query?.Where(member => - member.Name != null && member.Name.SqlWildcard(displayNameToMatch, TextColumnType.NVarchar)); + query?.Where(member => member.Name != null && member.Name.SqlWildcard(displayNameToMatch, TextColumnType.NVarchar)); break; default: throw new ArgumentOutOfRangeException(nameof(matchType)); // causes rollback // causes rollback @@ -622,33 +497,21 @@ public IEnumerable FindMembersByDisplayName( return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, null, Ordering.By("Name")); } - } - /// - /// Finds a list of objects by a partial email string - /// - /// Partial email string to match - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// - /// The type of match to make as . Default is - /// - /// - /// - /// - /// - public IEnumerable FindByEmail( - string emailStringToMatch, - long pageIndex, - int pageSize, - out long totalRecords, - StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Finds a list of objects by a partial email string + /// + /// Partial email string to match + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// The type of match to make as . Default is + /// + public IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); - IQuery query = Query(); + IQuery? query = Query(); switch (matchType) { @@ -673,33 +536,21 @@ public IEnumerable FindByEmail( return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, null, Ordering.By("Email")); } - } - /// - /// Finds a list of objects by a partial username - /// - /// Partial username to match - /// Current page index - /// Size of the page - /// Total number of records found (out) - /// - /// The type of match to make as . Default is - /// - /// - /// - /// - /// - public IEnumerable FindByUsername( - string login, - long pageIndex, - int pageSize, - out long totalRecords, - StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Finds a list of objects by a partial username + /// + /// Partial username to match + /// Current page index + /// Size of the page + /// Total number of records found (out) + /// The type of match to make as . Default is + /// + public IEnumerable FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); - IQuery query = Query(); + IQuery? query = Query(); switch (matchType) { @@ -724,52 +575,33 @@ public IEnumerable FindByUsername( return _memberRepository.GetPage(query, pageIndex, pageSize, out totalRecords, null, Ordering.By("LoginName")); } - } - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// - /// The type of match to make as . Default is - /// - /// - /// - /// - /// - public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// The type of match to make as . Default is + /// + public IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); IQuery query; switch (matchType) { case StringPropertyMatchType.Exact: - query = Query().Where(x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - (((Member)x).LongStringPropertyValue!.SqlEquals(value, TextColumnType.NText) || - ((Member)x).ShortStringPropertyValue!.SqlEquals(value, TextColumnType.NVarchar))); + query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && (((Member) x).LongStringPropertyValue!.SqlEquals(value, TextColumnType.NText) || ((Member) x).ShortStringPropertyValue!.SqlEquals(value, TextColumnType.NVarchar))); break; case StringPropertyMatchType.Contains: - query = Query().Where(x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - (((Member)x).LongStringPropertyValue!.SqlContains(value, TextColumnType.NText) || - ((Member)x).ShortStringPropertyValue!.SqlContains(value, TextColumnType.NVarchar))); + query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && (((Member) x).LongStringPropertyValue!.SqlContains(value, TextColumnType.NText) || ((Member) x).ShortStringPropertyValue!.SqlContains(value, TextColumnType.NVarchar))); break; case StringPropertyMatchType.StartsWith: - query = Query().Where(x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - (((Member)x).LongStringPropertyValue.SqlStartsWith(value, TextColumnType.NText) || - ((Member)x).ShortStringPropertyValue.SqlStartsWith(value, TextColumnType.NVarchar))); + query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && (((Member) x).LongStringPropertyValue.SqlStartsWith(value, TextColumnType.NText) || ((Member) x).ShortStringPropertyValue.SqlStartsWith(value, TextColumnType.NVarchar))); break; case StringPropertyMatchType.EndsWith: - query = Query().Where(x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - (((Member)x).LongStringPropertyValue!.SqlEndsWith(value, TextColumnType.NText) || - ((Member)x).ShortStringPropertyValue!.SqlEndsWith(value, TextColumnType.NVarchar))); + query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && (((Member) x).LongStringPropertyValue!.SqlEndsWith(value, TextColumnType.NText) || ((Member) x).ShortStringPropertyValue!.SqlEndsWith(value, TextColumnType.NVarchar))); break; default: throw new ArgumentOutOfRangeException(nameof(matchType)); @@ -777,51 +609,36 @@ public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, return _memberRepository.Get(query); } - } - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// - /// The type of match to make as . Default is - /// - /// - /// - /// - /// - public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// The type of match to make as . Default is + /// + public IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); IQuery query; switch (matchType) { case ValuePropertyMatchType.Exact: - query = Query().Where(x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).IntegerPropertyValue == value); + query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).IntegerPropertyValue == value); break; case ValuePropertyMatchType.GreaterThan: - query = Query().Where(x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && ((Member)x).IntegerPropertyValue > value); + query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).IntegerPropertyValue > value); break; case ValuePropertyMatchType.LessThan: - query = Query().Where(x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && ((Member)x).IntegerPropertyValue < value); + query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).IntegerPropertyValue < value); break; case ValuePropertyMatchType.GreaterThanOrEqualTo: - query = Query().Where(x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).IntegerPropertyValue >= value); + query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).IntegerPropertyValue >= value); break; case ValuePropertyMatchType.LessThanOrEqualTo: - query = Query().Where(x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).IntegerPropertyValue <= value); + query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).IntegerPropertyValue <= value); break; default: throw new ArgumentOutOfRangeException(nameof(matchType)); @@ -829,73 +646,51 @@ public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, return _memberRepository.Get(query); } - } - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// - /// - /// - public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, bool value) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// + public IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, bool value) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); - IQuery query = Query().Where(x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && ((Member)x).BoolPropertyValue == value); + IQuery query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).BoolPropertyValue == value); return _memberRepository.Get(query); } - } - /// - /// Gets a list of Members based on a property search - /// - /// Alias of the PropertyType to search for - /// Value to match - /// - /// The type of match to make as . Default is - /// - /// - /// - /// - /// - public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Gets a list of Members based on a property search + /// + /// Alias of the PropertyType to search for + /// Value to match + /// The type of match to make as . Default is + /// + public IEnumerable? GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); IQuery query; switch (matchType) { case ValuePropertyMatchType.Exact: - query = Query().Where(x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue == value); + query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).DateTimePropertyValue == value); break; case ValuePropertyMatchType.GreaterThan: - query = Query().Where(x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue > value); + query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).DateTimePropertyValue > value); break; case ValuePropertyMatchType.LessThan: - query = Query().Where(x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue < value); + query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).DateTimePropertyValue < value); break; case ValuePropertyMatchType.GreaterThanOrEqualTo: - query = Query().Where(x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue >= value); + query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).DateTimePropertyValue >= value); break; case ValuePropertyMatchType.LessThanOrEqualTo: - query = Query().Where(x => - ((Member)x).PropertyTypeAlias == propertyTypeAlias && - ((Member)x).DateTimePropertyValue <= value); + query = Query().Where(x => ((Member) x).PropertyTypeAlias == propertyTypeAlias && ((Member) x).DateTimePropertyValue <= value); break; default: throw new ArgumentOutOfRangeException(nameof(matchType)); // causes rollback // causes rollback @@ -904,58 +699,51 @@ public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, // TODO: Since this is by property value, we need a GetByPropertyQuery on the repo! return _memberRepository.Get(query); } - } - /// - /// Checks if a Member with the id exists - /// - /// Id of the Member - /// True if the Member exists otherwise False - public bool Exists(int id) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Checks if a Member with the id exists + /// + /// Id of the Member + /// True if the Member exists otherwise False + public bool Exists(int id) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); return _memberRepository.Exists(id); } - } - /// - /// Checks if a Member with the username exists - /// - /// Username to check - /// True if the Member exists otherwise False - public bool Exists(string username) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Checks if a Member with the username exists + /// + /// Username to check + /// True if the Member exists otherwise False + public bool Exists(string username) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); return _memberRepository.Exists(username); } - } - #endregion + #endregion - #region Save + #region Save - /// - [Obsolete( - "This is now a NoOp since last login date is no longer an umbraco property, set the date on the IMember directly and Save it instead, scheduled for removal in V11.")] - public void SetLastLogin(string username, DateTime date) - { - } + /// + [Obsolete("This is now a NoOp since last login date is no longer an umbraco property, set the date on the IMember directly and Save it instead, scheduled for removal in V11.")] + public void SetLastLogin(string username, DateTime date) + { + } - /// - public void Save(IMember member) - { - // trimming username and email to make sure we have no trailing space - member.Username = member.Username.Trim(); - member.Email = member.Email.Trim(); + /// + public void Save(IMember member) + { + // trimming username and email to make sure we have no trailing space + member.Username = member.Username.Trim(); + member.Email = member.Email.Trim(); - EventMessages evtMsgs = EventMessagesFactory.Get(); + EventMessages evtMsgs = EventMessagesFactory.Get(); - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { + using ICoreScope scope = ScopeProvider.CreateCoreScope(); var savingNotification = new MemberSavingNotification(member, evtMsgs); if (scope.Notifications.PublishCancelable(savingNotification)) { @@ -978,17 +766,15 @@ public void Save(IMember member) scope.Complete(); } - } - /// - public void Save(IEnumerable members) - { - IMember[] membersA = members.ToArray(); + /// + public void Save(IEnumerable members) + { + IMember[] membersA = members.ToArray(); - EventMessages evtMsgs = EventMessagesFactory.Get(); + EventMessages evtMsgs = EventMessagesFactory.Get(); - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { + using ICoreScope scope = ScopeProvider.CreateCoreScope(); var savingNotification = new MemberSavingNotification(membersA, evtMsgs); if (scope.Notifications.PublishCancelable(savingNotification)) { @@ -1000,36 +786,33 @@ public void Save(IEnumerable members) foreach (IMember member in membersA) { - // trimming username and email to make sure we have no trailing space + //trimming username and email to make sure we have no trailing space member.Username = member.Username.Trim(); member.Email = member.Email.Trim(); _memberRepository.Save(member); } - scope.Notifications.Publish( - new MemberSavedNotification(membersA, evtMsgs).WithStateFrom(savingNotification)); + scope.Notifications.Publish(new MemberSavedNotification(membersA, evtMsgs).WithStateFrom(savingNotification)); Audit(AuditType.Save, 0, -1, "Save multiple Members"); scope.Complete(); } - } - #endregion + #endregion - #region Delete + #region Delete - /// - /// Deletes an - /// - /// to Delete - public void Delete(IMember member) - { - EventMessages evtMsgs = EventMessagesFactory.Get(); - - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + /// + /// Deletes an + /// + /// to Delete + public void Delete(IMember member) { + EventMessages evtMsgs = EventMessagesFactory.Get(); + + using ICoreScope scope = ScopeProvider.CreateCoreScope(); var deletingNotification = new MemberDeletingNotification(member, evtMsgs); if (scope.Notifications.PublishCancelable(deletingNotification)) { @@ -1043,120 +826,101 @@ public void Delete(IMember member) Audit(AuditType.Delete, 0, member.Id); scope.Complete(); } - } - #endregion + private void DeleteLocked(ICoreScope scope, IMember member, EventMessages evtMsgs, IDictionary? notificationState = null) + { + // a member has no descendants + _memberRepository.Delete(member); + scope.Notifications.Publish(new MemberDeletedNotification(member, evtMsgs).WithState(notificationState)); - #region Roles + // media files deleted by QueuingEventDispatcher + } - public void AddRole(string roleName) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + #endregion + + #region Roles + + public void AddRole(string roleName) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(); scope.WriteLock(Constants.Locks.MemberTree); _memberGroupRepository.CreateIfNotExists(roleName); scope.Complete(); } - } - private void DeleteLocked(ICoreScope scope, IMember member, EventMessages evtMsgs, IDictionary? notificationState = null) - { - // a member has no descendants - _memberRepository.Delete(member); - scope.Notifications.Publish(new MemberDeletedNotification(member, evtMsgs).WithState(notificationState)); + /// + /// Returns a list of all member roles + /// + /// A list of member roles - // media files deleted by QueuingEventDispatcher - } - - /// - /// Returns a list of all member roles - /// - /// A list of member roles - public IEnumerable GetAllRoles() - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + public IEnumerable GetAllRoles() { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); return _memberGroupRepository.GetMany().Distinct(); } - } - /// - /// Returns a list of all member roles for a given member ID - /// - /// - /// A list of member roles - public IEnumerable GetAllRoles(int memberId) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + /// + /// Returns a list of all member roles for a given member ID + /// + /// + /// A list of member roles + public IEnumerable GetAllRoles(int memberId) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); IEnumerable result = _memberGroupRepository.GetMemberGroupsForMember(memberId); return result.Select(x => x.Name).WhereNotNull().Distinct(); } - } - public IEnumerable GetAllRoles(string username) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + public IEnumerable GetAllRoles(string username) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); IEnumerable result = _memberGroupRepository.GetMemberGroupsForMember(username); return result.Where(x => x.Name != null).Select(x => x.Name).Distinct()!; } - } - public IEnumerable GetAllRolesIds() - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + public IEnumerable GetAllRolesIds() { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); return _memberGroupRepository.GetMany().Select(x => x.Id).Distinct(); } - } - public IEnumerable GetAllRolesIds(int memberId) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + public IEnumerable GetAllRolesIds(int memberId) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); IEnumerable result = _memberGroupRepository.GetMemberGroupsForMember(memberId); return result.Select(x => x.Id).Distinct(); } - } - public IEnumerable GetAllRolesIds(string username) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + public IEnumerable GetAllRolesIds(string username) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); IEnumerable result = _memberGroupRepository.GetMemberGroupsForMember(username); return result.Select(x => x.Id).Distinct(); } - } - public IEnumerable GetMembersInRole(string roleName) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + public IEnumerable GetMembersInRole(string roleName) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); return _memberRepository.GetByMemberGroup(roleName); } - } - public IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + public IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.MemberTree); return _memberRepository.FindMembersInRole(roleName, usernameToMatch, matchType); } - } - public bool DeleteRole(string roleName, bool throwIfBeingUsed) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + public bool DeleteRole(string roleName, bool throwIfBeingUsed) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(); scope.WriteLock(Constants.Locks.MemberTree); if (throwIfBeingUsed) @@ -1183,100 +947,93 @@ public bool DeleteRole(string roleName, bool throwIfBeingUsed) scope.Complete(); return found?.Length > 0; } - } - public void AssignRole(string username, string roleName) => AssignRoles(new[] { username }, new[] { roleName }); + public void AssignRole(string username, string roleName) => AssignRoles(new[] { username }, new[] { roleName }); - public void AssignRoles(string[] usernames, string[] roleNames) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + public void AssignRoles(string[] usernames, string[] roleNames) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(); scope.WriteLock(Constants.Locks.MemberTree); var ids = _memberRepository.GetMemberIds(usernames); _memberGroupRepository.AssignRoles(ids, roleNames); scope.Notifications.Publish(new AssignedMemberRolesNotification(ids, roleNames)); scope.Complete(); } - } - public void DissociateRole(string username, string roleName) => DissociateRoles(new[] { username }, new[] { roleName }); + public void DissociateRole(string username, string roleName) => DissociateRoles(new[] { username }, new[] { roleName }); - public void DissociateRoles(string[] usernames, string[] roleNames) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + public void DissociateRoles(string[] usernames, string[] roleNames) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(); scope.WriteLock(Constants.Locks.MemberTree); var ids = _memberRepository.GetMemberIds(usernames); _memberGroupRepository.DissociateRoles(ids, roleNames); scope.Notifications.Publish(new RemovedMemberRolesNotification(ids, roleNames)); scope.Complete(); } - } - public void AssignRole(int memberId, string roleName) => AssignRoles(new[] { memberId }, new[] { roleName }); + public void AssignRole(int memberId, string roleName) => AssignRoles(new[] { memberId }, new[] { roleName }); - public void AssignRoles(int[] memberIds, string[] roleNames) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + public void AssignRoles(int[] memberIds, string[] roleNames) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(); scope.WriteLock(Constants.Locks.MemberTree); _memberGroupRepository.AssignRoles(memberIds, roleNames); scope.Notifications.Publish(new AssignedMemberRolesNotification(memberIds, roleNames)); scope.Complete(); } - } - public void DissociateRole(int memberId, string roleName) => DissociateRoles(new[] { memberId }, new[] { roleName }); + public void DissociateRole(int memberId, string roleName) => DissociateRoles(new[] { memberId }, new[] { roleName }); - public void DissociateRoles(int[] memberIds, string[] roleNames) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + public void DissociateRoles(int[] memberIds, string[] roleNames) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(); scope.WriteLock(Constants.Locks.MemberTree); _memberGroupRepository.DissociateRoles(memberIds, roleNames); scope.Notifications.Publish(new RemovedMemberRolesNotification(memberIds, roleNames)); scope.Complete(); } - } - public void ReplaceRoles(string[] usernames, string[] roleNames) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + public void ReplaceRoles(string[] usernames, string[] roleNames) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(); scope.WriteLock(Constants.Locks.MemberTree); - var ids = _memberRepository.GetMemberIds(usernames); + int[] ids = _memberRepository.GetMemberIds(usernames); _memberGroupRepository.ReplaceRoles(ids, roleNames); scope.Notifications.Publish(new AssignedMemberRolesNotification(ids, roleNames)); scope.Complete(); } - } - public void ReplaceRoles(int[] memberIds, string[] roleNames) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + public void ReplaceRoles(int[] memberIds, string[] roleNames) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(); scope.WriteLock(Constants.Locks.MemberTree); _memberGroupRepository.ReplaceRoles(memberIds, roleNames); scope.Notifications.Publish(new AssignedMemberRolesNotification(memberIds, roleNames)); scope.Complete(); } - } - #endregion + #endregion - #region Membership + #region Private Methods - /// - /// Exports a member. - /// - /// - /// This is internal for now and is used to export a member in the member editor, - /// it will raise an event so that auditing logs can be created. - /// - public MemberExportModel? ExportMember(Guid key) - { - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + private void Audit(AuditType type, int userId, int objectId, string? message = null) => _auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.Member), message)); + + #endregion + + #region Membership + + + /// + /// Exports a member. + /// + /// + /// This is internal for now and is used to export a member in the member editor, + /// it will raise an event so that auditing logs can be created. + /// + public MemberExportModel? ExportMember(Guid key) { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); IQuery? query = Query().Where(x => x.Key == key); IMember? member = _memberRepository.Get(query)?.FirstOrDefault(); @@ -1296,34 +1053,58 @@ public void ReplaceRoles(int[] memberIds, string[] roleNames) ContentTypeAlias = member.ContentTypeAlias, CreateDate = member.CreateDate, UpdateDate = member.UpdateDate, - Properties = new List(GetPropertyExportItems(member)), + Properties = new List(GetPropertyExportItems(member)) }; scope.Notifications.Publish(new ExportedMemberNotification(member, model)); return model; } - } - #endregion + private static IEnumerable GetPropertyExportItems(IMember member) + { + if (member == null) + { + throw new ArgumentNullException(nameof(member)); + } - #region Content Types + var exportProperties = new List(); - /// - /// Delete Members of the specified MemberType id - /// - /// Id of the MemberType - public void DeleteMembersOfType(int memberTypeId) - { - EventMessages evtMsgs = EventMessagesFactory.Get(); + foreach (IProperty property in member.Properties) + { + var propertyExportModel = new MemberExportProperty + { + Id = property.Id, + Alias = property.Alias, + Name = property.PropertyType.Name, + Value = property.GetValue(), // TODO: ignoring variants + CreateDate = property.CreateDate, + UpdateDate = property.UpdateDate + }; + exportProperties.Add(propertyExportModel); + } + + return exportProperties; + } + + #endregion + + #region Content Types - // note: no tree to manage here - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + /// + /// Delete Members of the specified MemberType id + /// + /// Id of the MemberType + public void DeleteMembersOfType(int memberTypeId) { + EventMessages evtMsgs = EventMessagesFactory.Get(); + + // note: no tree to manage here + using ICoreScope scope = ScopeProvider.CreateCoreScope(); scope.WriteLock(Constants.Locks.MemberTree); // TODO: What about content that has the contenttype as part of its composition? - IQuery query = Query().Where(x => x.ContentTypeId == memberTypeId); + IQuery? query = Query().Where(x => x.ContentTypeId == memberTypeId); IMember[]? members = _memberRepository.Get(query)?.ToArray(); @@ -1347,80 +1128,46 @@ public void DeleteMembersOfType(int memberTypeId) scope.Complete(); } - } - private static IEnumerable GetPropertyExportItems(IMember member) - { - if (member == null) + private IMemberType GetMemberType(ICoreScope scope, string memberTypeAlias) { - throw new ArgumentNullException(nameof(member)); - } - - var exportProperties = new List(); - - foreach (IProperty property in member.Properties) - { - var propertyExportModel = new MemberExportProperty + if (memberTypeAlias == null) { - Id = property.Id, - Alias = property.Alias, - Name = property.PropertyType.Name, - Value = property.GetValue(), // TODO: ignoring variants - CreateDate = property.CreateDate, - UpdateDate = property.UpdateDate, - }; - exportProperties.Add(propertyExportModel); - } - - return exportProperties; - } + throw new ArgumentNullException(nameof(memberTypeAlias)); + } - private IMemberType GetMemberType(ICoreScope scope, string memberTypeAlias) - { - if (memberTypeAlias == null) - { - throw new ArgumentNullException(nameof(memberTypeAlias)); - } + if (string.IsNullOrWhiteSpace(memberTypeAlias)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(memberTypeAlias)); + } - if (string.IsNullOrWhiteSpace(memberTypeAlias)) - { - throw new ArgumentException( - "Value can't be empty or consist only of white-space characters.", - nameof(memberTypeAlias)); - } + scope.ReadLock(Constants.Locks.MemberTypes); - scope.ReadLock(Constants.Locks.MemberTypes); + IMemberType? memberType = _memberTypeRepository.Get(memberTypeAlias); - IMemberType? memberType = _memberTypeRepository.Get(memberTypeAlias); + if (memberType == null) + { + throw new Exception($"No MemberType matching the passed in Alias: '{memberTypeAlias}' was found"); // causes rollback + } - if (memberType == null) - { - throw new Exception( - $"No MemberType matching the passed in Alias: '{memberTypeAlias}' was found"); // causes rollback + return memberType; } - return memberType; - } - - private IMemberType GetMemberType(string memberTypeAlias) - { - if (memberTypeAlias == null) + private IMemberType GetMemberType(string memberTypeAlias) { - throw new ArgumentNullException(nameof(memberTypeAlias)); - } + if (memberTypeAlias == null) + { + throw new ArgumentNullException(nameof(memberTypeAlias)); + } - if (string.IsNullOrWhiteSpace(memberTypeAlias)) - { - throw new ArgumentException( - "Value can't be empty or consist only of white-space characters.", - nameof(memberTypeAlias)); - } + if (string.IsNullOrWhiteSpace(memberTypeAlias)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(memberTypeAlias)); + } - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); return GetMemberType(scope, memberTypeAlias); } + #endregion } - - #endregion } diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs index 39aa3502d865..ea93a099f840 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelper.cs @@ -4,670 +4,669 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Strings; - -/// -/// New default implementation of string functions for short strings such as aliases or URL segments. -/// -/// -/// Not optimized to work on large bodies of text. -/// Meant to replace LegacyShortStringHelper where/when backward compatibility is not an issue. -/// NOTE: pre-filters run _before_ the string is re-encoded. -/// -public class DefaultShortStringHelper : IShortStringHelper +namespace Umbraco.Cms.Core.Strings { - #region SplitPascalCasing - /// - /// Splits a Pascal-cased string into a phrase separated by a separator. + /// New default implementation of string functions for short strings such as aliases or URL segments. /// - /// The text to split. - /// The separator, which defaults to a whitespace. - /// The split text. - /// Supports Utf8 and Ascii strings, not Unicode strings. - // NOTE does not support surrogates pairs at the moment - public virtual string SplitPascalCasing(string text, char separator) + /// + /// Not optimized to work on large bodies of text. + /// Meant to replace LegacyShortStringHelper where/when backward compatibility is not an issue. + /// NOTE: pre-filters run _before_ the string is re-encoded. + /// + public class DefaultShortStringHelper : IShortStringHelper { - // be safe - if (text == null) - { - throw new ArgumentNullException(nameof(text)); - } + #region Ctor, consts and vars - var input = text.ToCharArray(); - var output = new char[input.Length * 2]; - var opos = 0; - var a = input.Length > 0 ? input[0] : char.MinValue; - var upos = char.IsUpper(a) ? 1 : 0; - - for (var i = 1; i < input.Length; i++) + public DefaultShortStringHelper(IOptions settings) { - var c = input[i]; - if (char.IsUpper(c)) - { - output[opos++] = a; - if (upos == 0) - { - if (opos > 0) - { - output[opos++] = separator; - } - - upos = i + 1; - } - } - else - { - if (upos > 0) - { - if (upos < i && opos > 0) - { - output[opos++] = separator; - } - - upos = 0; - } - - output[opos++] = a; - } - - a = c; + _config = new DefaultShortStringHelperConfig().WithDefault(settings.Value); } - if (a != char.MinValue) + // clones the config so it cannot be changed at runtime + public DefaultShortStringHelper(DefaultShortStringHelperConfig config) { - output[opos++] = a; + _config = config.Clone(); } - return new string(output, 0, opos); - } - - #endregion - - #region Ctor, consts and vars + // see notes for CleanAsciiString + //// beware! the order is quite important here! + //const string ValidStringCharactersSource = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + //readonly static char[] ValidStringCharacters; - public DefaultShortStringHelper(IOptions settings) => - _config = new DefaultShortStringHelperConfig().WithDefault(settings.Value); + private readonly DefaultShortStringHelperConfig _config; - // clones the config so it cannot be changed at runtime - public DefaultShortStringHelper(DefaultShortStringHelperConfig config) => _config = config.Clone(); + // see notes for CleanAsciiString + //static DefaultShortStringHelper() + //{ + // ValidStringCharacters = ValidStringCharactersSource.ToCharArray(); + //} - // see notes for CleanAsciiString - //// beware! the order is quite important here! - // const string ValidStringCharactersSource = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - // readonly static char[] ValidStringCharacters; - private readonly DefaultShortStringHelperConfig _config; + #endregion - // see notes for CleanAsciiString - // static DefaultShortStringHelper() - // { - // ValidStringCharacters = ValidStringCharactersSource.ToCharArray(); - // } - #endregion + #region Filters - #region Filters - - // ok to be static here because it's not configurable in any way - private static readonly char[] InvalidFileNameChars = - Path.GetInvalidFileNameChars() + // ok to be static here because it's not configurable in any way + private static readonly char[] InvalidFileNameChars = + Path.GetInvalidFileNameChars() .Union("!*'();:@&=+$,/?%#[]-~{}\"<>\\^`| ".ToCharArray()) .Distinct() .ToArray(); - public static bool IsValidFileNameChar(char c) => InvalidFileNameChars.Contains(c) == false; - - #endregion - - #region IShortStringHelper CleanFor... - - /// - /// Cleans a string to produce a string that can safely be used in an alias. - /// - /// The text to filter. - /// The safe alias. - /// - /// The string will be cleaned in the context of the default culture. - /// Safe aliases are Ascii only. - /// - public virtual string CleanStringForSafeAlias(string text) => CleanStringForSafeAlias(text, _config.DefaultCulture); - - /// - /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an alias. - /// - /// The text to filter. - /// The culture. - /// The safe alias. - /// - /// Safe aliases are Ascii only. - /// - public virtual string CleanStringForSafeAlias(string text, string culture) => - CleanString(text, CleanStringType.Alias, culture); + public static bool IsValidFileNameChar(char c) + { + return InvalidFileNameChars.Contains(c) == false; + } - /// - /// Cleans a string to produce a string that can safely be used in an URL segment. - /// - /// The text to filter. - /// The safe URL segment. - /// - /// The string will be cleaned in the context of the default culture. - /// Url segments are Ascii only (no accents...). - /// - public virtual string CleanStringForUrlSegment(string text) => - CleanStringForUrlSegment(text, _config.DefaultCulture); + #endregion - /// - /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an URL - /// segment. - /// - /// The text to filter. - /// The culture. - /// The safe URL segment. - /// - /// Url segments are Ascii only (no accents...). - /// - public virtual string CleanStringForUrlSegment(string text, string? culture) => - CleanString(text, CleanStringType.UrlSegment, culture); + #region IShortStringHelper CleanFor... - /// - /// Cleans a string, in the context of the default culture, to produce a string that can safely be used as a filename, - /// both internally (on disk) and externally (as a URL). - /// - /// The text to filter. - /// The safe filename. - /// - /// Legacy says this was used to "overcome an issue when Umbraco is used in IE in an intranet environment" but - /// that issue is not documented. - /// - public virtual string CleanStringForSafeFileName(string text) => - CleanStringForSafeFileName(text, _config.DefaultCulture); - - /// - /// Cleans a string to produce a string that can safely be used as a filename, - /// both internally (on disk) and externally (as a URL). - /// - /// The text to filter. - /// The culture. - /// The safe filename. - public virtual string CleanStringForSafeFileName(string text, string culture) - { - if (string.IsNullOrWhiteSpace(text)) + /// + /// Cleans a string to produce a string that can safely be used in an alias. + /// + /// The text to filter. + /// The safe alias. + /// + /// The string will be cleaned in the context of the default culture. + /// Safe aliases are Ascii only. + /// + public virtual string CleanStringForSafeAlias(string text) { - return string.Empty; + return CleanStringForSafeAlias(text, _config.DefaultCulture); } - culture = culture ?? string.Empty; - text = text.ReplaceMany(Path.GetInvalidFileNameChars(), '-'); + /// + /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an alias. + /// + /// The text to filter. + /// The culture. + /// The safe alias. + /// + /// Safe aliases are Ascii only. + /// + public virtual string CleanStringForSafeAlias(string text, string culture) + { + return CleanString(text, CleanStringType.Alias, culture); + } - var name = Path.GetFileNameWithoutExtension(text); - var ext = Path.GetExtension(text); // includes the dot, empty if no extension + /// + /// Cleans a string to produce a string that can safely be used in an URL segment. + /// + /// The text to filter. + /// The safe URL segment. + /// + /// The string will be cleaned in the context of the default culture. + /// Url segments are Ascii only (no accents...). + /// + public virtual string CleanStringForUrlSegment(string text) + { + return CleanStringForUrlSegment(text, _config.DefaultCulture); + } - Debug.Assert(name != null, "name != null"); - if (name.Length > 0) + /// + /// Cleans a string, in the context of a specified culture, to produce a string that can safely be used in an URL segment. + /// + /// The text to filter. + /// The culture. + /// The safe URL segment. + /// + /// Url segments are Ascii only (no accents...). + /// + public virtual string CleanStringForUrlSegment(string text, string? culture) { - name = CleanString(name, CleanStringType.FileName, culture); + return CleanString(text, CleanStringType.UrlSegment, culture); } - Debug.Assert(ext != null, "ext != null"); - if (ext.Length > 0) + /// + /// Cleans a string, in the context of the default culture, to produce a string that can safely be used as a filename, + /// both internally (on disk) and externally (as a URL). + /// + /// The text to filter. + /// The safe filename. + /// Legacy says this was used to "overcome an issue when Umbraco is used in IE in an intranet environment" but that issue is not documented. + public virtual string CleanStringForSafeFileName(string text) { - ext = CleanString(ext.Substring(1), CleanStringType.FileName, culture); + return CleanStringForSafeFileName(text, _config.DefaultCulture); } - return ext.Length > 0 ? name + "." + ext : name; - } + /// + /// Cleans a string to produce a string that can safely be used as a filename, + /// both internally (on disk) and externally (as a URL). + /// + /// The text to filter. + /// The culture. + /// The safe filename. + public virtual string CleanStringForSafeFileName(string text, string culture) + { + if (string.IsNullOrWhiteSpace(text)) + { + return string.Empty; + } - #endregion - - #region CleanString - - // MS rules & guidelines: - // - Do capitalize both characters of two-character acronyms, except the first word of a camel-cased identifier. - // eg "DBRate" (pascal) or "ioHelper" (camel) - "SpecialDBRate" (pascal) or "specialIOHelper" (camel) - // - Do capitalize only the first character of acronyms with three or more characters, except the first word of a camel-cased identifier. - // eg "XmlWriter (pascal) or "htmlReader" (camel) - "SpecialXmlWriter" (pascal) or "specialHtmlReader" (camel) - // - Do not capitalize any of the characters of any acronyms, whatever their length, at the beginning of a camel-cased identifier. - // eg "xmlWriter" or "dbWriter" (camel) - // - // Our additional stuff: - // - Leading digits are removed. - // - Many consecutive separators are folded into one unique separator. - private const byte StateBreak = 1; - private const byte StateUp = 2; - private const byte StateWord = 3; - private const byte StateAcronym = 4; + culture = culture ?? string.Empty; + text = text.ReplaceMany(Path.GetInvalidFileNameChars(), '-'); - /// - /// Cleans a string. - /// - /// The text to clean. - /// - /// A flag indicating the target casing and encoding of the string. By default, - /// strings are cleaned up to camelCase and Ascii. - /// - /// The clean string. - /// The string is cleaned in the context of the default culture. - public string CleanString(string text, CleanStringType stringType) => - CleanString(text, stringType, _config.DefaultCulture, null); + var name = Path.GetFileNameWithoutExtension(text); + var ext = Path.GetExtension(text); // includes the dot, empty if no extension - /// - /// Cleans a string, using a specified separator. - /// - /// The text to clean. - /// - /// A flag indicating the target casing and encoding of the string. By default, - /// strings are cleaned up to camelCase and Ascii. - /// - /// The separator. - /// The clean string. - /// The string is cleaned in the context of the default culture. - public string CleanString(string text, CleanStringType stringType, char separator) => - CleanString(text, stringType, _config.DefaultCulture, separator); + Debug.Assert(name != null, "name != null"); + if (name.Length > 0) + { + name = CleanString(name, CleanStringType.FileName, culture); + } - /// - /// Cleans a string in the context of a specified culture. - /// - /// The text to clean. - /// - /// A flag indicating the target casing and encoding of the string. By default, - /// strings are cleaned up to camelCase and Ascii. - /// - /// The culture. - /// The clean string. - public string CleanString(string text, CleanStringType stringType, string? culture) => - CleanString(text, stringType, culture, null); + Debug.Assert(ext != null, "ext != null"); + if (ext.Length > 0) + { + ext = CleanString(ext.Substring(1), CleanStringType.FileName, culture); + } - /// - /// Cleans a string in the context of a specified culture, using a specified separator. - /// - /// The text to clean. - /// - /// A flag indicating the target casing and encoding of the string. By default, - /// strings are cleaned up to camelCase and Ascii. - /// - /// The separator. - /// The culture. - /// The clean string. - public string CleanString(string text, CleanStringType stringType, char separator, string culture) => - CleanString(text, stringType, culture, separator); - - protected virtual string CleanString(string text, CleanStringType stringType, string? culture, char? separator) - { - // be safe - if (text == null) - { - throw new ArgumentNullException(nameof(text)); + return ext.Length > 0 ? name + "." + ext : name; } - culture = culture ?? string.Empty; - - // get config - DefaultShortStringHelperConfig.Config config = _config.For(stringType, culture); - stringType = config.StringTypeExtend(stringType); - - // apply defaults - if ((stringType & CleanStringType.CaseMask) == CleanStringType.None) + #endregion + + #region CleanString + + // MS rules & guidelines: + // - Do capitalize both characters of two-character acronyms, except the first word of a camel-cased identifier. + // eg "DBRate" (pascal) or "ioHelper" (camel) - "SpecialDBRate" (pascal) or "specialIOHelper" (camel) + // - Do capitalize only the first character of acronyms with three or more characters, except the first word of a camel-cased identifier. + // eg "XmlWriter (pascal) or "htmlReader" (camel) - "SpecialXmlWriter" (pascal) or "specialHtmlReader" (camel) + // - Do not capitalize any of the characters of any acronyms, whatever their length, at the beginning of a camel-cased identifier. + // eg "xmlWriter" or "dbWriter" (camel) + // + // Our additional stuff: + // - Leading digits are removed. + // - Many consecutive separators are folded into one unique separator. + + private const byte StateBreak = 1; + private const byte StateUp = 2; + private const byte StateWord = 3; + private const byte StateAcronym = 4; + + /// + /// Cleans a string. + /// + /// The text to clean. + /// A flag indicating the target casing and encoding of the string. By default, + /// strings are cleaned up to camelCase and Ascii. + /// The clean string. + /// The string is cleaned in the context of the default culture. + public string CleanString(string text, CleanStringType stringType) => CleanString(text, stringType, _config.DefaultCulture, null); + + /// + /// Cleans a string, using a specified separator. + /// + /// The text to clean. + /// A flag indicating the target casing and encoding of the string. By default, + /// strings are cleaned up to camelCase and Ascii. + /// The separator. + /// The clean string. + /// The string is cleaned in the context of the default culture. + public string CleanString(string text, CleanStringType stringType, char separator) => CleanString(text, stringType, _config.DefaultCulture, separator); + + /// + /// Cleans a string in the context of a specified culture. + /// + /// The text to clean. + /// A flag indicating the target casing and encoding of the string. By default, + /// strings are cleaned up to camelCase and Ascii. + /// The culture. + /// The clean string. + public string CleanString(string text, CleanStringType stringType, string? culture) { - stringType |= CleanStringType.CamelCase; + return CleanString(text, stringType, culture, null); } - if ((stringType & CleanStringType.CodeMask) == CleanStringType.None) + /// + /// Cleans a string in the context of a specified culture, using a specified separator. + /// + /// The text to clean. + /// A flag indicating the target casing and encoding of the string. By default, + /// strings are cleaned up to camelCase and Ascii. + /// The separator. + /// The culture. + /// The clean string. + public string CleanString(string text, CleanStringType stringType, char separator, string culture) { - stringType |= CleanStringType.Ascii; + return CleanString(text, stringType, culture, separator); } - // use configured unless specified - separator = separator ?? config.Separator; - - // apply pre-filter - if (config.PreFilter != null) + protected virtual string CleanString(string text, CleanStringType stringType, string? culture, char? separator) { - text = config.PreFilter(text); - } + // be safe + if (text == null) + { + throw new ArgumentNullException(nameof(text)); + } - // apply replacements - // if (config.Replacements != null) - // text = ReplaceMany(text, config.Replacements); + culture = culture ?? string.Empty; - // recode - CleanStringType codeType = stringType & CleanStringType.CodeMask; - switch (codeType) - { - case CleanStringType.Ascii: - text = Utf8ToAsciiConverter.ToAsciiString(text); - break; - case CleanStringType.TryAscii: - const char ESC = (char)27; - var ctext = Utf8ToAsciiConverter.ToAsciiString(text, ESC); - if (ctext.Contains(ESC) == false) - { - text = ctext; - } + // get config + DefaultShortStringHelperConfig.Config config = _config.For(stringType, culture); + stringType = config.StringTypeExtend(stringType); - break; - default: - text = RemoveSurrogatePairs(text); - break; - } + // apply defaults + if ((stringType & CleanStringType.CaseMask) == CleanStringType.None) + { + stringType |= CleanStringType.CamelCase; + } - // clean - text = CleanCodeString(text, stringType, separator.Value, culture, config); + if ((stringType & CleanStringType.CodeMask) == CleanStringType.None) + { + stringType |= CleanStringType.Ascii; + } - // apply post-filter - if (config.PostFilter != null) - { - text = config.PostFilter(text); - } + // use configured unless specified + separator = separator ?? config.Separator; - return text; - } - - private static string RemoveSurrogatePairs(string text) - { - var input = text.ToCharArray(); - var output = new char[input.Length]; - var opos = 0; + // apply pre-filter + if (config.PreFilter != null) + { + text = config.PreFilter(text); + } - for (var ipos = 0; ipos < input.Length; ipos++) - { - var c = input[ipos]; + // apply replacements + //if (config.Replacements != null) + // text = ReplaceMany(text, config.Replacements); - // ignore high surrogate - if (char.IsSurrogate(c)) + // recode + CleanStringType codeType = stringType & CleanStringType.CodeMask; + switch (codeType) { - ipos++; // and skip low surrogate - output[opos++] = '?'; + case CleanStringType.Ascii: + text = Utf8ToAsciiConverter.ToAsciiString(text); + break; + case CleanStringType.TryAscii: + const char ESC = (char) 27; + var ctext = Utf8ToAsciiConverter.ToAsciiString(text, ESC); + if (ctext.Contains(ESC) == false) + { + text = ctext; + } + + break; + default: + text = RemoveSurrogatePairs(text); + break; } - else + + // clean + text = CleanCodeString(text, stringType, separator.Value, culture, config); + + // apply post-filter + if (config.PostFilter != null) { - output[opos++] = c; + text = config.PostFilter(text); } - } - - return new string(output, 0, opos); - } - // here was a subtle, ascii-optimized version of the cleaning code, and I was - // very proud of it until benchmarking showed it was an order of magnitude slower - // that the utf8 version. Micro-optimizing sometimes isn't such a good idea. + return text; + } - // note: does NOT support surrogate pairs in text - internal string CleanCodeString(string text, CleanStringType caseType, char separator, string culture, DefaultShortStringHelperConfig.Config config) - { - int opos = 0, ipos = 0; - var state = StateBreak; + private static string RemoveSurrogatePairs(string text) + { + var input = text.ToCharArray(); + var output = new char[input.Length]; + var opos = 0; - culture = culture ?? string.Empty; - caseType &= CleanStringType.CaseMask; + for (var ipos = 0; ipos < input.Length; ipos++) + { + var c = input[ipos]; + if (char.IsSurrogate(c)) // ignore high surrogate + { + ipos++; // and skip low surrogate + output[opos++] = '?'; + } + else + { + output[opos++] = c; + } + } - // if we apply global ToUpper or ToLower to text here - // then we cannot break words on uppercase chars - var input = text; + return new string(output, 0, opos); + } - // it's faster to use an array than a StringBuilder - var ilen = input.Length; - var output = new char[ilen * 2]; // twice the length should be OK in all cases + // here was a subtle, ascii-optimized version of the cleaning code, and I was + // very proud of it until benchmarking showed it was an order of magnitude slower + // that the utf8 version. Micro-optimizing sometimes isn't such a good idea. - for (var i = 0; i < ilen; i++) + // note: does NOT support surrogate pairs in text + internal string CleanCodeString(string text, CleanStringType caseType, char separator, string culture, DefaultShortStringHelperConfig.Config config) { - var c = input[i]; + int opos = 0, ipos = 0; + var state = StateBreak; - // leading as long as StateBreak and ipos still zero - var leading = state == StateBreak && ipos == 0; - var isTerm = config.IsTerm(c, leading); + culture = culture ?? string.Empty; + caseType &= CleanStringType.CaseMask; - // var isDigit = char.IsDigit(c); - var isUpper = char.IsUpper(c); // false for digits, symbols... + // if we apply global ToUpper or ToLower to text here + // then we cannot break words on uppercase chars + var input = text; - // var isLower = char.IsLower(c); // false for digits, symbols... + // it's faster to use an array than a StringBuilder + var ilen = input.Length; + var output = new char[ilen * 2]; // twice the length should be OK in all cases - // what should I do with surrogates? - E.g emojis like 🎈 - // no idea, really, so they are not supported at the moment and we just continue - var isPair = char.IsSurrogate(c); - if (isPair) + for (var i = 0; i < ilen; i++) { - continue; - } + var c = input[i]; + // leading as long as StateBreak and ipos still zero + var leading = state == StateBreak && ipos == 0; + var isTerm = config.IsTerm(c, leading); + + //var isDigit = char.IsDigit(c); + var isUpper = char.IsUpper(c); // false for digits, symbols... + //var isLower = char.IsLower(c); // false for digits, symbols... + + // what should I do with surrogates? - E.g emojis like 🎈 + // no idea, really, so they are not supported at the moment and we just continue + var isPair = char.IsSurrogate(c); + if (isPair) + { + continue; + } - switch (state) - { - // within a break - case StateBreak: - // begin a new term if char is a term char, - // and ( pos > 0 or it's also a valid leading char ) - if (isTerm) - { - ipos = i; - if (opos > 0 && separator != char.MinValue) + + switch (state) + { + // within a break + case StateBreak: + // begin a new term if char is a term char, + // and ( pos > 0 or it's also a valid leading char ) + if (isTerm) { - output[opos++] = separator; - } + ipos = i; + if (opos > 0 && separator != char.MinValue) + { + output[opos++] = separator; + } - state = isUpper ? StateUp : StateWord; - } + state = isUpper ? StateUp : StateWord; + } - break; + break; - // within a term / word - case StateWord: - // end a term if char is not a term char, - // or ( it's uppercase and we break terms on uppercase) - if (isTerm == false || (config.BreakTermsOnUpper && isUpper)) - { - CopyTerm(input, ipos, output, ref opos, i - ipos, caseType, culture, false); - ipos = i; - state = isTerm ? StateUp : StateBreak; - if (state != StateBreak && separator != char.MinValue) + // within a term / word + case StateWord: + // end a term if char is not a term char, + // or ( it's uppercase and we break terms on uppercase) + if (isTerm == false || (config.BreakTermsOnUpper && isUpper)) { - output[opos++] = separator; + CopyTerm(input, ipos, output, ref opos, i - ipos, caseType, culture, false); + ipos = i; + state = isTerm ? StateUp : StateBreak; + if (state != StateBreak && separator != char.MinValue) + { + output[opos++] = separator; + } } - } - break; + break; - // within a term / acronym - case StateAcronym: - // end an acronym if char is not a term char, - // or if it's not uppercase / config - if (isTerm == false || (config.CutAcronymOnNonUpper && isUpper == false)) - { - // whether it's part of the acronym depends on whether we're greedy - if (isTerm && config.GreedyAcronyms == false) + // within a term / acronym + case StateAcronym: + // end an acronym if char is not a term char, + // or if it's not uppercase / config + if (isTerm == false || (config.CutAcronymOnNonUpper && isUpper == false)) { - i -= 1; // handle that char again, in another state - not part of the acronym - } + // whether it's part of the acronym depends on whether we're greedy + if (isTerm && config.GreedyAcronyms == false) + { + i -= 1; // handle that char again, in another state - not part of the acronym + } - // single-char can't be an acronym - if (i - ipos > 1) - { - CopyTerm(input, ipos, output, ref opos, i - ipos, caseType, culture, true); - ipos = i; - state = isTerm ? StateWord : StateBreak; - if (state != StateBreak && separator != char.MinValue) + if (i - ipos > 1) // single-char can't be an acronym { - output[opos++] = separator; + CopyTerm(input, ipos, output, ref opos, i - ipos, caseType, culture, true); + ipos = i; + state = isTerm ? StateWord : StateBreak; + if (state != StateBreak && separator != char.MinValue) + { + output[opos++] = separator; + } + } + else if (isTerm) + { + state = StateWord; } } - else if (isTerm) + else if (isUpper == false) // isTerm == true { + // it's a term char and we don't cut... + // keep moving forward as a word state = StateWord; } - } - // isTerm == true - else if (isUpper == false) - { - // it's a term char and we don't cut... - // keep moving forward as a word - state = StateWord; - } + break; + + // within a term / uppercase = could be a word or an acronym + case StateUp: + if (isTerm) + { + // add that char to the term and pick word or acronym + state = isUpper ? StateAcronym : StateWord; + } + else + { + // single char, copy then break + CopyTerm(input, ipos, output, ref opos, 1, caseType, culture, false); + state = StateBreak; + } + break; + + default: + throw new Exception("Invalid state."); + } + } + switch (state) + { + case StateBreak: break; - // within a term / uppercase = could be a word or an acronym - case StateUp: - if (isTerm) - { - // add that char to the term and pick word or acronym - state = isUpper ? StateAcronym : StateWord; - } - else - { - // single char, copy then break - CopyTerm(input, ipos, output, ref opos, 1, caseType, culture, false); - state = StateBreak; - } + case StateWord: + CopyTerm(input, ipos, output, ref opos, input.Length - ipos, caseType, culture, false); + break; + case StateAcronym: + case StateUp: + CopyTerm(input, ipos, output, ref opos, input.Length - ipos, caseType, culture, true); break; default: throw new Exception("Invalid state."); } + + return new string(output, 0, opos); } - switch (state) + // note: supports surrogate pairs in input string + internal void CopyTerm(string input, int ipos, char[] output, ref int opos, int len, CleanStringType caseType, string culture, bool isAcronym) { - case StateBreak: - break; + var term = input.Substring(ipos, len); + CultureInfo cultureInfo = string.IsNullOrEmpty(culture) ? CultureInfo.InvariantCulture : CultureInfo.GetCultureInfo(culture); - case StateWord: - CopyTerm(input, ipos, output, ref opos, input.Length - ipos, caseType, culture, false); - break; + if (isAcronym) + { + if ((caseType == CleanStringType.CamelCase && len <= 2 && opos > 0) || + (caseType == CleanStringType.PascalCase && len <= 2) || + caseType == CleanStringType.UmbracoCase) + { + caseType = CleanStringType.Unchanged; + } + } - case StateAcronym: - case StateUp: - CopyTerm(input, ipos, output, ref opos, input.Length - ipos, caseType, culture, true); - break; + // note: MSDN seems to imply that ToUpper or ToLower preserve the length + // of the string, but that this behavior is not guaranteed and could change. - default: - throw new Exception("Invalid state."); + char c; + int i; + string s; + switch (caseType) + { + //case CleanStringType.LowerCase: + //case CleanStringType.UpperCase: + case CleanStringType.Unchanged: + term.CopyTo(0, output, opos, len); + opos += len; + break; + + case CleanStringType.LowerCase: + term = term.ToLower(cultureInfo); + term.CopyTo(0, output, opos, term.Length); + opos += term.Length; + break; + + case CleanStringType.UpperCase: + term = term.ToUpper(cultureInfo); + term.CopyTo(0, output, opos, term.Length); + opos += term.Length; + break; + + case CleanStringType.CamelCase: + c = term[0]; + i = 1; + if (char.IsSurrogate(c)) + { + s = term.Substring(ipos, 2); + s = opos == 0 ? s.ToLower(cultureInfo) : s.ToUpper(cultureInfo); + s.CopyTo(0, output, opos, s.Length); + opos += s.Length; + i++; // surrogate pair len is 2 + } + else + { + output[opos] = opos++ == 0 ? char.ToLower(c, cultureInfo) : char.ToUpper(c, cultureInfo); + } + if (len > i) + { + term = term.Substring(i).ToLower(cultureInfo); + term.CopyTo(0, output, opos, term.Length); + opos += term.Length; + } + break; + + case CleanStringType.PascalCase: + c = term[0]; + i = 1; + if (char.IsSurrogate(c)) + { + s = term.Substring(ipos, 2); + s = s.ToUpper(cultureInfo); + s.CopyTo(0, output, opos, s.Length); + opos += s.Length; + i++; // surrogate pair len is 2 + } + else + { + output[opos++] = char.ToUpper(c, cultureInfo); + } + if (len > i) + { + term = term.Substring(i).ToLower(cultureInfo); + term.CopyTo(0, output, opos, term.Length); + opos += term.Length; + } + break; + + case CleanStringType.UmbracoCase: + c = term[0]; + i = 1; + if (char.IsSurrogate(c)) + { + s = term.Substring(ipos, 2); + s = opos == 0 ? s : s.ToUpper(cultureInfo); + s.CopyTo(0, output, opos, s.Length); + opos += s.Length; + i++; // surrogate pair len is 2 + } + else + { + output[opos] = opos++ == 0 ? c : char.ToUpper(c, cultureInfo); + } + if (len > i) + { + term = term.Substring(i); + term.CopyTo(0, output, opos, term.Length); + opos += term.Length; + } + break; + + default: + throw new ArgumentOutOfRangeException(nameof(caseType)); + } } - return new string(output, 0, opos); - } + #endregion - // note: supports surrogate pairs in input string - internal void CopyTerm(string input, int ipos, char[] output, ref int opos, int len, CleanStringType caseType, string culture, bool isAcronym) - { - var term = input.Substring(ipos, len); - CultureInfo cultureInfo = string.IsNullOrEmpty(culture) - ? CultureInfo.InvariantCulture - : CultureInfo.GetCultureInfo(culture); + #region SplitPascalCasing - if (isAcronym) + /// + /// Splits a Pascal-cased string into a phrase separated by a separator. + /// + /// The text to split. + /// The separator, which defaults to a whitespace. + /// The split text. + /// Supports Utf8 and Ascii strings, not Unicode strings. + // NOTE does not support surrogates pairs at the moment + public virtual string SplitPascalCasing(string text, char separator) { - if ((caseType == CleanStringType.CamelCase && len <= 2 && opos > 0) || - (caseType == CleanStringType.PascalCase && len <= 2) || - caseType == CleanStringType.UmbracoCase) + // be safe + if (text == null) { - caseType = CleanStringType.Unchanged; + throw new ArgumentNullException(nameof(text)); } - } - // note: MSDN seems to imply that ToUpper or ToLower preserve the length - // of the string, but that this behavior is not guaranteed and could change. - char c; - int i; - string s; - switch (caseType) - { - // case CleanStringType.LowerCase: - // case CleanStringType.UpperCase: - case CleanStringType.Unchanged: - term.CopyTo(0, output, opos, len); - opos += len; - break; - - case CleanStringType.LowerCase: - term = term.ToLower(cultureInfo); - term.CopyTo(0, output, opos, term.Length); - opos += term.Length; - break; - - case CleanStringType.UpperCase: - term = term.ToUpper(cultureInfo); - term.CopyTo(0, output, opos, term.Length); - opos += term.Length; - break; - - case CleanStringType.CamelCase: - c = term[0]; - i = 1; - if (char.IsSurrogate(c)) - { - s = term.Substring(ipos, 2); - s = opos == 0 ? s.ToLower(cultureInfo) : s.ToUpper(cultureInfo); - s.CopyTo(0, output, opos, s.Length); - opos += s.Length; - i++; // surrogate pair len is 2 - } - else - { - output[opos] = opos++ == 0 ? char.ToLower(c, cultureInfo) : char.ToUpper(c, cultureInfo); - } + var input = text.ToCharArray(); + var output = new char[input.Length * 2]; + var opos = 0; + var a = input.Length > 0 ? input[0] : char.MinValue; + var upos = char.IsUpper(a) ? 1 : 0; - if (len > i) + for (var i = 1; i < input.Length; i++) + { + var c = input[i]; + if (char.IsUpper(c)) { - term = term.Substring(i).ToLower(cultureInfo); - term.CopyTo(0, output, opos, term.Length); - opos += term.Length; - } - - break; + output[opos++] = a; + if (upos == 0) + { + if (opos > 0) + { + output[opos++] = separator; + } - case CleanStringType.PascalCase: - c = term[0]; - i = 1; - if (char.IsSurrogate(c)) - { - s = term.Substring(ipos, 2); - s = s.ToUpper(cultureInfo); - s.CopyTo(0, output, opos, s.Length); - opos += s.Length; - i++; // surrogate pair len is 2 + upos = i + 1; + } } else { - output[opos++] = char.ToUpper(c, cultureInfo); - } - - if (len > i) - { - term = term.Substring(i).ToLower(cultureInfo); - term.CopyTo(0, output, opos, term.Length); - opos += term.Length; - } + if (upos > 0) + { + if (upos < i && opos > 0) + { + output[opos++] = separator; + } - break; + upos = 0; + } - case CleanStringType.UmbracoCase: - c = term[0]; - i = 1; - if (char.IsSurrogate(c)) - { - s = term.Substring(ipos, 2); - s = opos == 0 ? s : s.ToUpper(cultureInfo); - s.CopyTo(0, output, opos, s.Length); - opos += s.Length; - i++; // surrogate pair len is 2 - } - else - { - output[opos] = opos++ == 0 ? c : char.ToUpper(c, cultureInfo); + output[opos++] = a; } - if (len > i) - { - term = term.Substring(i); - term.CopyTo(0, output, opos, term.Length); - opos += term.Length; - } + a = c; + } - break; + if (a != char.MinValue) + { + output[opos++] = a; + } - default: - throw new ArgumentOutOfRangeException(nameof(caseType)); + return new string(output, 0, opos); } - } - #endregion + #endregion + } } diff --git a/src/Umbraco.Core/Strings/UrlSegmentProviderCollectionBuilder.cs b/src/Umbraco.Core/Strings/UrlSegmentProviderCollectionBuilder.cs index dcab5b39fdda..f9aa13b335da 100644 --- a/src/Umbraco.Core/Strings/UrlSegmentProviderCollectionBuilder.cs +++ b/src/Umbraco.Core/Strings/UrlSegmentProviderCollectionBuilder.cs @@ -1,9 +1,8 @@ -using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Composing; namespace Umbraco.Cms.Core.Strings; -public class UrlSegmentProviderCollectionBuilder : OrderedCollectionBuilderBase +public class UrlSegmentProviderCollectionBuilder : OrderedCollectionBuilderBase { protected override UrlSegmentProviderCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/SystemLock.cs b/src/Umbraco.Core/SystemLock.cs index ff4783802757..0e47096c2e0c 100644 --- a/src/Umbraco.Core/SystemLock.cs +++ b/src/Umbraco.Core/SystemLock.cs @@ -1,4 +1,4 @@ -using System.Runtime.ConstrainedExecution; +using System.Runtime.ConstrainedExecution; namespace Umbraco.Cms.Core; @@ -93,10 +93,8 @@ private class NamedSemaphoreReleaser : CriticalFinalizerObject, IDisposable { private readonly Semaphore? _semaphore; - #region IDisposable Support - // This code added to correctly implement the disposable pattern. - private bool disposedValue; // To detect redundant calls + private bool _disposedValue; // To detect redundant calls internal NamedSemaphoreReleaser(Semaphore? semaphore) => _semaphore = semaphore; @@ -136,7 +134,7 @@ public void Dispose() private void Dispose(bool disposing) { - if (!disposedValue) + if (!_disposedValue) { try { @@ -153,11 +151,9 @@ private void Dispose(bool disposing) } } - disposedValue = true; + _disposedValue = true; } } - - #endregion } private class SemaphoreSlimReleaser : IDisposable diff --git a/src/Umbraco.Core/UriUtilityCore.cs b/src/Umbraco.Core/UriUtilityCore.cs index 8d541fca7852..3599a7c16a5c 100644 --- a/src/Umbraco.Core/UriUtilityCore.cs +++ b/src/Umbraco.Core/UriUtilityCore.cs @@ -1,4 +1,4 @@ -using Umbraco.Extensions; +using Umbraco.Extensions; namespace Umbraco.Cms.Core; @@ -19,12 +19,12 @@ public static string EndPathWithSlash(string uri) var pos2 = Math.Max(0, uri.IndexOf('#')); var pos = Math.Min(pos1, pos2); - var path = pos > 0 ? uri[..pos] : uri; + var path = pos > 0 ? uri.Substring(0, pos) : uri; path = path.EnsureEndsWith('/'); if (pos > 0) { - path += uri[pos..]; + path += uri.Substring(pos); } return path; @@ -41,7 +41,7 @@ public static string TrimPathEndSlash(string uri) if (pos > 0) { - path += uri[pos..]; + path += uri.Substring(pos); } return path; diff --git a/src/Umbraco.Core/Web/IUmbracoContext.cs b/src/Umbraco.Core/Web/IUmbracoContext.cs index 62473947e37d..17ffc515a22c 100644 --- a/src/Umbraco.Core/Web/IUmbracoContext.cs +++ b/src/Umbraco.Core/Web/IUmbracoContext.cs @@ -60,8 +60,7 @@ public interface IUmbracoContext : IDisposable bool IsDebug { get; } /// - /// Gets a value indicating whether the current user is in a preview mode and browsing the site (ie. not in the admin - /// UI) + /// Gets a value indicating whether the current user is in a preview mode and browsing the site (ie. not in the admin UI) /// bool InPreviewMode { get; } diff --git a/src/Umbraco.Core/Web/IUmbracoContextAccessor.cs b/src/Umbraco.Core/Web/IUmbracoContextAccessor.cs index f55cb21ff717..370412b2814a 100644 --- a/src/Umbraco.Core/Web/IUmbracoContextAccessor.cs +++ b/src/Umbraco.Core/Web/IUmbracoContextAccessor.cs @@ -3,8 +3,7 @@ namespace Umbraco.Cms.Core.Web; /// -/// Provides access to a TryGetUmbracoContext bool method that will return true if the "current" -/// is not null. +/// Provides access to a TryGetUmbracoContext bool method that will return true if the "current" is not null. /// Provides a Clear() method that will clear the current object. /// Provides a Set() method that til set the current object. /// diff --git a/src/Umbraco.Core/WebAssets/CustomBackOfficeAssetsCollectionBuilder.cs b/src/Umbraco.Core/WebAssets/CustomBackOfficeAssetsCollectionBuilder.cs index f45ac4e86bc8..bdfebf128a63 100644 --- a/src/Umbraco.Core/WebAssets/CustomBackOfficeAssetsCollectionBuilder.cs +++ b/src/Umbraco.Core/WebAssets/CustomBackOfficeAssetsCollectionBuilder.cs @@ -2,8 +2,7 @@ namespace Umbraco.Cms.Core.WebAssets; -public class CustomBackOfficeAssetsCollectionBuilder : OrderedCollectionBuilderBase< - CustomBackOfficeAssetsCollectionBuilder, CustomBackOfficeAssetsCollection, IAssetFile> +public class CustomBackOfficeAssetsCollectionBuilder : OrderedCollectionBuilderBase { protected override CustomBackOfficeAssetsCollectionBuilder This => this; } diff --git a/src/Umbraco.Core/Xml/DynamicContext.cs b/src/Umbraco.Core/Xml/DynamicContext.cs index 4418862d0e78..fd8686634893 100644 --- a/src/Umbraco.Core/Xml/DynamicContext.cs +++ b/src/Umbraco.Core/Xml/DynamicContext.cs @@ -1,307 +1,326 @@ -using System.Globalization; -using System.Xml; +using System.Xml; using System.Xml.XPath; using System.Xml.Xsl; // source: mvpxml.codeplex.com -namespace Umbraco.Cms.Core.Xml; - -/// -/// Provides the evaluation context for fast execution and custom -/// variables resolution. -/// -/// -/// This class is responsible for resolving variables during dynamic expression execution. -/// Discussed in http://weblogs.asp.net/cazzu/archive/2003/10/07/30888.aspx -/// Author: Daniel Cazzulino, blog -/// -public class DynamicContext : XsltContext -{ - #region Private vars - - private readonly IDictionary _variables = - new Dictionary(); - - #endregion Private - - #region Public Members +namespace Umbraco.Cms.Core.Xml +{ /// - /// Shortcut method that compiles an expression using an empty navigator. + /// Provides the evaluation context for fast execution and custom + /// variables resolution. /// - /// The expression to compile - /// A compiled . - public static XPathExpression? Compile(string xpath) => new XmlDocument().CreateNavigator()?.Compile(xpath); + /// + /// This class is responsible for resolving variables during dynamic expression execution. + /// Discussed in http://weblogs.asp.net/cazzu/archive/2003/10/07/30888.aspx + /// Author: Daniel Cazzulino, blog + /// + public class DynamicContext : XsltContext + { + #region Private vars - #endregion Public Members + readonly IDictionary _variables = + new Dictionary(); - #region Internal DynamicVariable class + #endregion Private - /// - /// Represents a variable during dynamic expression execution. - /// - internal class DynamicVariable : IXsltContextVariable - { - private readonly object _value; + #region Constructors & Initialization - #region Public Members + /// + /// Initializes a new instance of the class. + /// + public DynamicContext() + : base(new NameTable()) + { + } - public string Name { get; } + /// + /// Initializes a new instance of the + /// class with the specified . + /// + /// The NameTable to use. + public DynamicContext(NameTable table) + : base(table) + { + } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The name of the variable. - /// The value of the variable. - public DynamicVariable(string name, object value) + /// A previously filled context with the namespaces to use. + public DynamicContext(XmlNamespaceManager context) + : this(context, new NameTable()) { - Name = name; - _value = value; + } - if (value is string) - { - _type = XPathResultType.String; - } - else if (value is bool) - { - _type = XPathResultType.Boolean; - } - else if (value is XPathNavigator) - { - _type = XPathResultType.Navigator; - } - else if (value is XPathNodeIterator) + /// + /// Initializes a new instance of the class. + /// + /// A previously filled context with the namespaces to use. + /// The NameTable to use. + public DynamicContext(XmlNamespaceManager context, NameTable table) + : base(table) + { + object xml = table.Add(XmlNamespaces.Xml); + object xmlns = table.Add(XmlNamespaces.XmlNs); + + if (context == null) { - _type = XPathResultType.NodeSet; + return; } - else + + foreach (string prefix in context) { - // Try to convert to double (native XPath numeric type) - if (value is double) + var uri = context.LookupNamespace(prefix); + // Use fast object reference comparison to omit forbidden namespace declarations. + if (Equals(uri, xml) || Equals(uri, xmlns)) { - _type = XPathResultType.Number; + continue; } - else - { - if (value is IConvertible) - { - try - { - _value = Convert.ToDouble(value); - // We succeeded, so it's a number. - _type = XPathResultType.Number; - } - catch (FormatException) - { - _type = XPathResultType.Any; - } - catch (OverflowException) - { - _type = XPathResultType.Any; - } - } - else - { - _type = XPathResultType.Any; - } + if (uri == null) + { + continue; } + + base.AddNamespace(prefix, uri); } } - #endregion Public Members + #endregion Constructors & Initialization - #region IXsltContextVariable Implementation + #region Common Overrides - XPathResultType IXsltContextVariable.VariableType => _type; + /// + /// Implementation equal to . + /// + public override int CompareDocument(string baseUri, string nextbaseUri) => + String.Compare(baseUri, nextbaseUri, false, System.Globalization.CultureInfo.InvariantCulture); - private readonly XPathResultType _type; + /// + /// Same as . + /// + public override string? LookupNamespace(string prefix) + { + var key = NameTable?.Get(prefix); + return key == null ? null : base.LookupNamespace(key); + } - object IXsltContextVariable.Evaluate(XsltContext context) => _value; + /// + /// Same as . + /// + public override string? LookupPrefix(string uri) + { + var key = NameTable?.Get(uri); + return key == null ? null : base.LookupPrefix(key); + } - bool IXsltContextVariable.IsLocal => false; + /// + /// Same as . + /// + public override bool PreserveWhitespace(XPathNavigator node) + { + return true; + } - bool IXsltContextVariable.IsParam => false; + /// + /// Same as . + /// + public override bool Whitespace + { + get { return true; } + } - #endregion IXsltContextVariable Implementation - } + #endregion Common Overrides - #endregion Internal DynamicVariable class + #region Public Members - #region Constructors & Initialization + /// + /// Shortcut method that compiles an expression using an empty navigator. + /// + /// The expression to compile + /// A compiled . + public static XPathExpression? Compile(string xpath) + { + return new XmlDocument().CreateNavigator()?.Compile(xpath); + } - /// - /// Initializes a new instance of the class. - /// - public DynamicContext() - : base(new NameTable()) - { - } + #endregion Public Members - /// - /// Initializes a new instance of the - /// class with the specified . - /// - /// The NameTable to use. - public DynamicContext(NameTable table) - : base(table) - { - } + #region Variable Handling Code - /// - /// Initializes a new instance of the class. - /// - /// A previously filled context with the namespaces to use. - public DynamicContext(XmlNamespaceManager context) - : this(context, new NameTable()) - { - } + /// + /// Adds the variable to the dynamic evaluation context. + /// + /// The name of the variable to add to the context. + /// The value of the variable to add to the context. + /// + /// Value type conversion for XPath evaluation is as follows: + /// + /// + /// CLR Type + /// XPath type + /// + /// + /// System.String + /// XPathResultType.String + /// + /// + /// System.Double (or types that can be converted to) + /// XPathResultType.Number + /// + /// + /// System.Boolean + /// XPathResultType.Boolean + /// + /// + /// System.Xml.XPath.XPathNavigator + /// XPathResultType.Navigator + /// + /// + /// System.Xml.XPath.XPathNodeIterator + /// XPathResultType.NodeSet + /// + /// + /// Others + /// XPathResultType.Any + /// + /// + /// See the topic "Compile, Select, Evaluate, and Matches with + /// XPath and XPathExpressions" in MSDN documentation for additional information. + /// + /// The is null. + public void AddVariable(string name, object value) + { + if (value == null) + { + throw new ArgumentNullException("value"); + } - /// - /// Initializes a new instance of the class. - /// - /// A previously filled context with the namespaces to use. - /// The NameTable to use. - public DynamicContext(XmlNamespaceManager context, NameTable table) - : base(table) - { - object xml = table.Add(XmlNamespaces.Xml); - object xmlns = table.Add(XmlNamespaces.XmlNs); + _variables[name] = new DynamicVariable(name, value); + } + + /// + /// See . Not used in our implementation. + /// + public override IXsltContextFunction ResolveFunction(string prefix, string name, XPathResultType[] argTypes) => throw new NotImplementedException(); - if (context == null) + /// + /// Resolves the dynamic variables added to the context. See . + /// + public override IXsltContextVariable ResolveVariable(string prefix, string name) { - return; + IXsltContextVariable var; + _variables.TryGetValue(name, out var!); + return var; } - foreach (string prefix in context) - { - var uri = context.LookupNamespace(prefix); + #endregion Variable Handling Code - // Use fast object reference comparison to omit forbidden namespace declarations. - if (Equals(uri, xml) || Equals(uri, xmlns)) - { - continue; - } + #region Internal DynamicVariable class - if (uri == null) - { - continue; - } + /// + /// Represents a variable during dynamic expression execution. + /// + internal class DynamicVariable : IXsltContextVariable + { + private readonly string _name; + private readonly object _value; - base.AddNamespace(prefix, uri); - } - } + #region Public Members - #endregion Constructors & Initialization + public string Name { get { return _name; } } - #region Common Overrides + /// + /// Initializes a new instance of the class. + /// + /// The name of the variable. + /// The value of the variable. + public DynamicVariable(string name, object value) + { - /// - /// Implementation equal to . - /// - public override int CompareDocument(string baseUri, string nextbaseUri) => - string.Compare(baseUri, nextbaseUri, false, CultureInfo.InvariantCulture); + _name = name; + _value = value; - /// - /// Same as . - /// - public override string? LookupNamespace(string prefix) - { - var key = NameTable?.Get(prefix); - return key == null ? null : base.LookupNamespace(key); - } + if (value is string) + { + _type = XPathResultType.String; + } + else if (value is bool) + { + _type = XPathResultType.Boolean; + } + else if (value is XPathNavigator) + { + _type = XPathResultType.Navigator; + } + else if (value is XPathNodeIterator) + { + _type = XPathResultType.NodeSet; + } + else + { + // Try to convert to double (native XPath numeric type) + if (value is double) + { + _type = XPathResultType.Number; + } + else + { + if (value is IConvertible) + { + try + { + _value = Convert.ToDouble(value); + // We succeeded, so it's a number. + _type = XPathResultType.Number; + } + catch (FormatException) + { + _type = XPathResultType.Any; + } + catch (OverflowException) + { + _type = XPathResultType.Any; + } + } + else + { + _type = XPathResultType.Any; + } + } + } + } - /// - /// Same as . - /// - public override string? LookupPrefix(string uri) - { - var key = NameTable?.Get(uri); - return key == null ? null : base.LookupPrefix(key); - } + #endregion Public Members - /// - /// Same as . - /// - public override bool PreserveWhitespace(XPathNavigator node) => true; + #region IXsltContextVariable Implementation - /// - /// Same as . - /// - public override bool Whitespace => true; + XPathResultType IXsltContextVariable.VariableType + { + get { return _type; } + } - #endregion Common Overrides + private readonly XPathResultType _type; - #region Variable Handling Code + object IXsltContextVariable.Evaluate(XsltContext context) + { + return _value; + } - /// - /// Adds the variable to the dynamic evaluation context. - /// - /// The name of the variable to add to the context. - /// The value of the variable to add to the context. - /// - /// Value type conversion for XPath evaluation is as follows: - /// - /// - /// CLR Type - /// XPath type - /// - /// - /// System.String - /// XPathResultType.String - /// - /// - /// System.Double (or types that can be converted to) - /// XPathResultType.Number - /// - /// - /// System.Boolean - /// XPathResultType.Boolean - /// - /// - /// System.Xml.XPath.XPathNavigator - /// XPathResultType.Navigator - /// - /// - /// System.Xml.XPath.XPathNodeIterator - /// XPathResultType.NodeSet - /// - /// - /// Others - /// XPathResultType.Any - /// - /// - /// - /// See the topic "Compile, Select, Evaluate, and Matches with - /// XPath and XPathExpressions" in MSDN documentation for additional information. - /// - /// - /// The is null. - public void AddVariable(string name, object value) - { - if (value == null) - { - throw new ArgumentNullException("value"); - } + bool IXsltContextVariable.IsLocal + { + get { return false; } + } - _variables[name] = new DynamicVariable(name, value); - } + bool IXsltContextVariable.IsParam + { + get { return false; } + } - /// - /// See . Not used in our implementation. - /// - public override IXsltContextFunction ResolveFunction(string prefix, string name, XPathResultType[] argTypes) => - throw new NotImplementedException(); + #endregion IXsltContextVariable Implementation + } - /// - /// Resolves the dynamic variables added to the context. See . - /// - public override IXsltContextVariable ResolveVariable(string prefix, string name) - { - IXsltContextVariable var; - _variables.TryGetValue(name, out var!); - return var; + #endregion Internal DynamicVariable class } - - #endregion Variable Handling Code } diff --git a/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs b/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs index b158b7ec0f08..dd27e6124c51 100644 --- a/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs +++ b/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs @@ -2,1108 +2,1080 @@ using System.Xml; using System.Xml.XPath; -namespace Umbraco.Cms.Core.Xml.XPath; - -/// -/// Provides a cursor model for navigating {macro /} as if it were XML. -/// -public class MacroNavigator : XPathNavigator +namespace Umbraco.Cms.Core.Xml.XPath { - private readonly MacroRoot _macro; - private readonly XmlNameTable _nameTable; - /// - /// Gets a value indicating whether the current node is an empty element without an end element tag. + /// Provides a cursor model for navigating {macro /} as if it were XML. /// - public override bool IsEmptyElement + public class MacroNavigator : XPathNavigator { - get + private readonly XmlNameTable _nameTable; + private readonly MacroRoot _macro; + private State _state; + + #region Constructor + + /// + /// Initializes a new instance of the class with macro parameters. + /// + /// The macro parameters. + public MacroNavigator(IEnumerable parameters) + : this(new MacroRoot(parameters), new NameTable(), new State()) + { } + + /// + /// Initializes a new instance of the class with a macro node, + /// a name table and a state. + /// + /// The macro node. + /// The name table. + /// The state. + /// Privately used for cloning a navigator. + private MacroNavigator(MacroRoot macro, XmlNameTable nameTable, State state) + { + _macro = macro; + _nameTable = nameTable; + _state = state; + } + + #endregion + + #region Diagnostics + + // diagnostics code will not be compiled nor called into Release configuration. + // in Debug configuration, uncomment lines in Debug() to write to console or to log. + // + // much of this code is duplicated in each navigator due to conditional compilation + +#if DEBUG + private const string Tabs = " "; + private int _tabs; + private readonly int _uid = GetUid(); + private static int _uidg; + private static readonly object Uidl = new object(); + private static int GetUid() + { + lock (Uidl) + { + return _uidg++; + } + } +#endif + + [Conditional("DEBUG")] + private void DebugEnter(string name) + { +#if DEBUG + Debug(string.Empty); + DebugState(":"); + Debug(name); + _tabs = Math.Min(Tabs.Length, _tabs + 2); +#endif + } + + [Conditional("DEBUG")] + private void DebugCreate(MacroNavigator nav) + { +#if DEBUG + Debug("Create: [MacroNavigator::{0}]", nav._uid); +#endif + } + + [Conditional("DEBUG")] + private void DebugReturn() + { +#if DEBUG +// ReSharper disable IntroduceOptionalParameters.Local + DebugReturn("(void)"); +// ReSharper restore IntroduceOptionalParameters.Local +#endif + } + + [Conditional("DEBUG")] + private void DebugReturn(bool value) { - DebugEnter("IsEmptyElement"); - bool isEmpty; +#if DEBUG + DebugReturn(value ? "true" : "false"); +#endif + } + + [Conditional("DEBUG")] + private void DebugReturn(string format, params object[] args) + { +#if DEBUG + Debug("=> " + format, args); + if (_tabs > 0) + { + _tabs -= 2; + } +#endif + } + + [Conditional("DEBUG")] + private void DebugState(string s = " =>") + { +#if DEBUG + string position; - switch (InternalState.Position) + switch (_state.Position) { case StatePosition.Macro: - isEmpty = _macro.Parameters.Length == 0; + position = "At macro."; break; case StatePosition.Parameter: - MacroParameter parameter = _macro.Parameters[InternalState.ParameterIndex]; - XPathNavigator? nav = parameter.NavigatorValue; - if (parameter.WrapNavigatorInNodes || nav != null) - { - isEmpty = false; - } - else - { - var s = _macro.Parameters[InternalState.ParameterIndex].StringValue; - isEmpty = s == null; - } - + position = string.Format("At parameter '{0}'.", _macro.Parameters[_state.ParameterIndex].Name); + break; + case StatePosition.ParameterAttribute: + position = string.Format( + "At parameter attribute '{0}/{1}'.", + _macro.Parameters[_state.ParameterIndex].Name, + _macro.Parameters[_state.ParameterIndex].Attributes?[_state.ParameterAttributeIndex].Key); break; case StatePosition.ParameterNavigator: - isEmpty = InternalState.ParameterNavigator?.IsEmptyElement ?? true; + position = string.Format( + "In parameter '{0}{1}' navigator.", + _macro.Parameters[_state.ParameterIndex].Name, + _macro.Parameters[_state.ParameterIndex].WrapNavigatorInNodes ? "/nodes" : string.Empty); break; case StatePosition.ParameterNodes: - isEmpty = _macro.Parameters[InternalState.ParameterIndex].NavigatorValue == null; + position = string.Format( + "At parameter '{0}/nodes'.", + _macro.Parameters[_state.ParameterIndex].Name); break; - case StatePosition.ParameterAttribute: case StatePosition.ParameterText: + position = string.Format( + "In parameter '{0}' text.", + _macro.Parameters[_state.ParameterIndex].Name); + break; case StatePosition.Root: - throw new InvalidOperationException("Not an element."); + position = "At root."; + break; default: throw new InvalidOperationException("Invalid position."); } - DebugReturn(isEmpty); - return isEmpty; + Debug("State{0} {1}", s, position); +#endif } - } - /// - /// Gets the qualified name of the current node. - /// - public override string Name - { - get +#if DEBUG + private void Debug(string format, params object[] args) + { + // remove comments to write + + format = "[" + _uid.ToString("00000") + "] " + Tabs.Substring(0, _tabs) + format; +#pragma warning disable 168 + var msg = string.Format(format, args); // unused if not writing, hence #pragma +#pragma warning restore 168 + } +#endif + + #endregion + + #region Macro + + private class MacroRoot + { + public MacroRoot(IEnumerable parameters) + { + Parameters = parameters == null ? new MacroParameter[] {} : parameters.ToArray(); + } + + public MacroParameter[] Parameters { get; private set; } + } + + public class MacroParameter + { + // note: assuming we're not thinking about supporting + // XPathIterator in parameters - enough nonsense! + + public MacroParameter(string name, string value) + { + Name = name; + StringValue = value; + } + + public MacroParameter( + string name, + XPathNavigator navigator, + int maxNavigatorDepth = int.MaxValue, + bool wrapNavigatorInNodes = false, + IEnumerable>? attributes = null) + { + Name = name; + MaxNavigatorDepth = maxNavigatorDepth; + WrapNavigatorInNodes = wrapNavigatorInNodes; + if (attributes != null) + { + KeyValuePair[] a = attributes.ToArray(); + if (a.Length > 0) + { + Attributes = a; + } + } + + NavigatorValue = navigator; // should not be empty + } + + public string Name { get; private set; } + public string? StringValue { get; private set; } + public XPathNavigator? NavigatorValue { get; private set; } + public int MaxNavigatorDepth { get; private set; } + public bool WrapNavigatorInNodes { get; private set; } + public KeyValuePair[]? Attributes { get; private set; } + } + + #endregion + + /// + /// Creates a new XPathNavigator positioned at the same node as this XPathNavigator. + /// + /// A new XPathNavigator positioned at the same node as this XPathNavigator. + public override XPathNavigator Clone() + { + DebugEnter("Clone"); + var nav = new MacroNavigator(_macro, _nameTable, _state.Clone()); + DebugCreate(nav); + DebugReturn("[XPathNavigator]"); + return nav; + } + + /// + /// Gets a value indicating whether the current node is an empty element without an end element tag. + /// + public override bool IsEmptyElement + { + get + { + DebugEnter("IsEmptyElement"); + bool isEmpty; + + switch (_state.Position) + { + case StatePosition.Macro: + isEmpty = _macro.Parameters.Length == 0; + break; + case StatePosition.Parameter: + MacroParameter parameter = _macro.Parameters[_state.ParameterIndex]; + XPathNavigator? nav = parameter.NavigatorValue; + if (parameter.WrapNavigatorInNodes || nav != null) + { + isEmpty = false; + } + else + { + var s = _macro.Parameters[_state.ParameterIndex].StringValue; + isEmpty = s == null; + } + + break; + case StatePosition.ParameterNavigator: + isEmpty = _state.ParameterNavigator?.IsEmptyElement ?? true; + break; + case StatePosition.ParameterNodes: + isEmpty = _macro.Parameters[_state.ParameterIndex].NavigatorValue == null; + break; + case StatePosition.ParameterAttribute: + case StatePosition.ParameterText: + case StatePosition.Root: + throw new InvalidOperationException("Not an element."); + default: + throw new InvalidOperationException("Invalid position."); + } + + DebugReturn(isEmpty); + return isEmpty; + } + } + + /// + /// Determines whether the current XPathNavigator is at the same position as the specified XPathNavigator. + /// + /// The XPathNavigator to compare to this XPathNavigator. + /// true if the two XPathNavigator objects have the same position; otherwise, false. + public override bool IsSamePosition(XPathNavigator nav) { - DebugEnter("Name"); - string name; + DebugEnter("IsSamePosition"); + bool isSame; - switch (InternalState.Position) + switch (_state.Position) { + case StatePosition.ParameterNavigator: case StatePosition.Macro: - name = "macro"; - break; case StatePosition.Parameter: - name = _macro.Parameters[InternalState.ParameterIndex].Name; - break; case StatePosition.ParameterAttribute: - name = _macro.Parameters[InternalState.ParameterIndex] - .Attributes?[InternalState.ParameterAttributeIndex].Key ?? string.Empty; - break; - case StatePosition.ParameterNavigator: - name = InternalState.ParameterNavigator?.Name ?? string.Empty; - break; case StatePosition.ParameterNodes: - name = "nodes"; - break; case StatePosition.ParameterText: case StatePosition.Root: - name = string.Empty; + var other = nav as MacroNavigator; + isSame = other != null && other._macro == _macro && _state.IsSamePosition(other._state); break; default: throw new InvalidOperationException("Invalid position."); } - DebugReturn("\"{0}\"", name); - return name; + DebugReturn(isSame); + return isSame; } - } - /// - /// Gets the Name of the current node without any namespace prefix. - /// - public override string LocalName - { - get + /// + /// Gets the qualified name of the current node. + /// + public override string Name { - DebugEnter("LocalName"); - var name = Name; - DebugReturn("\"{0}\"", name); - return name; + get + { + DebugEnter("Name"); + string name; + + switch (_state.Position) + { + case StatePosition.Macro: + name = "macro"; + break; + case StatePosition.Parameter: + name = _macro.Parameters[_state.ParameterIndex].Name; + break; + case StatePosition.ParameterAttribute: + name = _macro.Parameters[_state.ParameterIndex].Attributes?[_state.ParameterAttributeIndex].Key ?? string.Empty; + break; + case StatePosition.ParameterNavigator: + name = _state.ParameterNavigator?.Name ?? string.Empty; + break; + case StatePosition.ParameterNodes: + name = "nodes"; + break; + case StatePosition.ParameterText: + case StatePosition.Root: + name = string.Empty; + break; + default: + throw new InvalidOperationException("Invalid position."); + } + + DebugReturn("\"{0}\"", name); + return name; + } } - } - /// - /// Gets the base URI for the current node. - /// - public override string BaseURI => string.Empty; + /// + /// Gets the Name of the current node without any namespace prefix. + /// + public override string LocalName + { + get + { + DebugEnter("LocalName"); + var name = Name; + DebugReturn("\"{0}\"", name); + return name; + } + } - /// - /// Gets the XmlNameTable of the XPathNavigator. - /// - public override XmlNameTable NameTable => _nameTable; + /// + /// Moves the XPathNavigator to the same position as the specified XPathNavigator. + /// + /// The XPathNavigator positioned on the node that you want to move to. + /// Returns true if the XPathNavigator is successful moving to the same position as the specified XPathNavigator; + /// otherwise, false. If false, the position of the XPathNavigator is unchanged. + public override bool MoveTo(XPathNavigator nav) + { + DebugEnter("MoveTo"); - /// - /// Gets the namespace URI of the current node. - /// - public override string NamespaceURI => string.Empty; + var other = nav as MacroNavigator; + var succ = false; - /// - /// Gets the XPathNodeType of the current node. - /// - public override XPathNodeType NodeType - { - get + if (other != null && other._macro == _macro) + { + _state = other._state.Clone(); + DebugState(); + succ = true; + } + + DebugReturn(succ); + return succ; + } + + /// + /// Moves the XPathNavigator to the first attribute of the current node. + /// + /// Returns true if the XPathNavigator is successful moving to the first attribute of the current node; + /// otherwise, false. If false, the position of the XPathNavigator is unchanged. + public override bool MoveToFirstAttribute() { - DebugEnter("NodeType"); - XPathNodeType type; + DebugEnter("MoveToFirstAttribute"); + bool succ; - switch (InternalState.Position) + switch (_state.Position) { - case StatePosition.Macro: - case StatePosition.Parameter: - case StatePosition.ParameterNodes: - type = XPathNodeType.Element; - break; case StatePosition.ParameterNavigator: - type = InternalState.ParameterNavigator?.NodeType ?? XPathNodeType.Root; + succ = _state.ParameterNavigator?.MoveToFirstAttribute() ?? false; break; - case StatePosition.ParameterAttribute: - type = XPathNodeType.Attribute; + case StatePosition.Parameter: + if (_macro.Parameters[_state.ParameterIndex].Attributes != null) + { + _state.Position = StatePosition.ParameterAttribute; + _state.ParameterAttributeIndex = 0; + succ = true; + DebugState(); + } + else + { + succ = false; + } + break; + case StatePosition.ParameterAttribute: + case StatePosition.ParameterNodes: + case StatePosition.Macro: case StatePosition.ParameterText: - type = XPathNodeType.Text; - break; case StatePosition.Root: - type = XPathNodeType.Root; + succ = false; break; default: throw new InvalidOperationException("Invalid position."); } - DebugReturn("\'{0}\'", type); - return type; + DebugReturn(succ); + return succ; } - } - - /// - /// Gets the namespace prefix associated with the current node. - /// - public override string Prefix => string.Empty; - /// - /// Gets the string value of the item. - /// - /// - /// Does not fully behave as per the specs, as we report empty value on root and macro elements, and we start - /// reporting values only on parameter elements. This is because, otherwise, we would might dump the whole database - /// and it probably does not make sense at Umbraco level. - /// - public override string Value - { - get + /// + /// Moves the XPathNavigator to the first child node of the current node. + /// + /// Returns true if the XPathNavigator is successful moving to the first child node of the current node; + /// otherwise, false. If false, the position of the XPathNavigator is unchanged. + public override bool MoveToFirstChild() { - DebugEnter("Value"); - string value; + DebugEnter("MoveToFirstChild"); + bool succ; - XPathNavigator? nav; - switch (InternalState.Position) + switch (_state.Position) { + case StatePosition.Macro: + if (_macro.Parameters.Length == 0) + { + succ = false; + } + else + { + _state.ParameterIndex = 0; + _state.Position = StatePosition.Parameter; + succ = true; + } + break; case StatePosition.Parameter: - nav = _macro.Parameters[InternalState.ParameterIndex].NavigatorValue; - if (nav != null) + MacroParameter parameter = _macro.Parameters[_state.ParameterIndex]; + XPathNavigator? nav = parameter.NavigatorValue; + if (parameter.WrapNavigatorInNodes) + { + _state.Position = StatePosition.ParameterNodes; + DebugState(); + succ = true; + } + else if (nav != null) { nav = nav.Clone(); // never use the raw parameter's navigator nav.MoveToFirstChild(); - value = nav.Value; + _state.ParameterNavigator = nav; + _state.ParameterNavigatorDepth = 0; + _state.Position = StatePosition.ParameterNavigator; + DebugState(); + succ = true; } else { - var s = _macro.Parameters[InternalState.ParameterIndex].StringValue; - value = s ?? string.Empty; + var s = _macro.Parameters[_state.ParameterIndex].StringValue; + if (s != null) + { + _state.Position = StatePosition.ParameterText; + DebugState(); + succ = true; + } + else + { + succ = false; + } } break; - case StatePosition.ParameterAttribute: - value = _macro.Parameters[InternalState.ParameterIndex] - .Attributes?[InternalState.ParameterAttributeIndex].Value ?? string.Empty; - break; case StatePosition.ParameterNavigator: - value = InternalState.ParameterNavigator?.Value ?? string.Empty; + if (_state.ParameterNavigatorDepth == _macro.Parameters[_state.ParameterIndex].MaxNavigatorDepth) + { + succ = false; + } + else + { + // move to first doc child => increment depth, else (property child) do nothing + succ = _state.ParameterNavigator?.MoveToFirstChild() ?? false; + if (succ && IsDoc(_state.ParameterNavigator)) + { + ++_state.ParameterNavigatorDepth; + DebugState(); + } + } break; case StatePosition.ParameterNodes: - nav = _macro.Parameters[InternalState.ParameterIndex].NavigatorValue; - if (nav == null) + if (_macro.Parameters[_state.ParameterIndex].NavigatorValue != null) { - value = string.Empty; + // never use the raw parameter's navigator + _state.ParameterNavigator = _macro.Parameters[_state.ParameterIndex].NavigatorValue?.Clone(); + _state.Position = StatePosition.ParameterNavigator; + succ = true; + DebugState(); } else { - nav = nav.Clone(); // never use the raw parameter's navigator - nav.MoveToFirstChild(); - value = nav.Value; + succ = false; } break; + case StatePosition.ParameterAttribute: case StatePosition.ParameterText: - value = _macro.Parameters[InternalState.ParameterIndex].StringValue ?? string.Empty; + succ = false; break; - case StatePosition.Macro: case StatePosition.Root: - value = string.Empty; + _state.Position = StatePosition.Macro; + DebugState(); + succ = true; break; default: throw new InvalidOperationException("Invalid position."); } - DebugReturn("\"{0}\"", value); - return value; + DebugReturn(succ); + return succ; } - } - /// - /// Creates a new XPathNavigator positioned at the same node as this XPathNavigator. - /// - /// A new XPathNavigator positioned at the same node as this XPathNavigator. - public override XPathNavigator Clone() - { - DebugEnter("Clone"); - var nav = new MacroNavigator(_macro, _nameTable, InternalState.Clone()); - DebugCreate(nav); - DebugReturn("[XPathNavigator]"); - return nav; - } - - /// - /// Determines whether the current XPathNavigator is at the same position as the specified XPathNavigator. - /// - /// The XPathNavigator to compare to this XPathNavigator. - /// true if the two XPathNavigator objects have the same position; otherwise, false. - public override bool IsSamePosition(XPathNavigator nav) - { - DebugEnter("IsSamePosition"); - bool isSame; - - switch (InternalState.Position) + /// + /// Moves the XPathNavigator to the first namespace node that matches the XPathNamespaceScope specified. + /// + /// An XPathNamespaceScope value describing the namespace scope. + /// Returns true if the XPathNavigator is successful moving to the first namespace node; + /// otherwise, false. If false, the position of the XPathNavigator is unchanged. + public override bool MoveToFirstNamespace(XPathNamespaceScope namespaceScope) { - case StatePosition.ParameterNavigator: - case StatePosition.Macro: - case StatePosition.Parameter: - case StatePosition.ParameterAttribute: - case StatePosition.ParameterNodes: - case StatePosition.ParameterText: - case StatePosition.Root: - var other = nav as MacroNavigator; - isSame = other != null && other._macro == _macro && InternalState.IsSamePosition(other.InternalState); - break; - default: - throw new InvalidOperationException("Invalid position."); + DebugEnter("MoveToFirstNamespace"); + DebugReturn(false); + return false; } - DebugReturn(isSame); - return isSame; - } - - /// - /// Moves the XPathNavigator to the same position as the specified XPathNavigator. - /// - /// The XPathNavigator positioned on the node that you want to move to. - /// - /// Returns true if the XPathNavigator is successful moving to the same position as the specified XPathNavigator; - /// otherwise, false. If false, the position of the XPathNavigator is unchanged. - /// - public override bool MoveTo(XPathNavigator nav) - { - DebugEnter("MoveTo"); - - var other = nav as MacroNavigator; - var succ = false; - - if (other != null && other._macro == _macro) + /// + /// Moves the XPathNavigator to the next namespace node matching the XPathNamespaceScope specified. + /// + /// An XPathNamespaceScope value describing the namespace scope. + /// Returns true if the XPathNavigator is successful moving to the next namespace node; + /// otherwise, false. If false, the position of the XPathNavigator is unchanged. + public override bool MoveToNextNamespace(XPathNamespaceScope namespaceScope) { - InternalState = other.InternalState.Clone(); - DebugState(); - succ = true; + DebugEnter("MoveToNextNamespace"); + DebugReturn(false); + return false; } - DebugReturn(succ); - return succ; - } - - /// - /// Moves the XPathNavigator to the first attribute of the current node. - /// - /// - /// Returns true if the XPathNavigator is successful moving to the first attribute of the current node; - /// otherwise, false. If false, the position of the XPathNavigator is unchanged. - /// - public override bool MoveToFirstAttribute() - { - DebugEnter("MoveToFirstAttribute"); - bool succ; - - switch (InternalState.Position) + /// + /// Moves to the node that has an attribute of type ID whose value matches the specified String. + /// + /// A String representing the ID value of the node to which you want to move. + /// true if the XPathNavigator is successful moving; otherwise, false. + /// If false, the position of the navigator is unchanged. + public override bool MoveToId(string id) { - case StatePosition.ParameterNavigator: - succ = InternalState.ParameterNavigator?.MoveToFirstAttribute() ?? false; - break; - case StatePosition.Parameter: - if (_macro.Parameters[InternalState.ParameterIndex].Attributes != null) - { - InternalState.Position = StatePosition.ParameterAttribute; - InternalState.ParameterAttributeIndex = 0; - succ = true; - DebugState(); - } - else - { - succ = false; - } - - break; - case StatePosition.ParameterAttribute: - case StatePosition.ParameterNodes: - case StatePosition.Macro: - case StatePosition.ParameterText: - case StatePosition.Root: - succ = false; - break; - default: - throw new InvalidOperationException("Invalid position."); + DebugEnter("MoveToId"); + // impossible to implement since parameters can contain duplicate fragments of the + // main xml and therefore there can be duplicate unique node identifiers. + DebugReturn("NotImplementedException"); + throw new NotImplementedException(); } - DebugReturn(succ); - return succ; - } - - /// - /// Moves the XPathNavigator to the first child node of the current node. - /// - /// - /// Returns true if the XPathNavigator is successful moving to the first child node of the current node; - /// otherwise, false. If false, the position of the XPathNavigator is unchanged. - /// - public override bool MoveToFirstChild() - { - DebugEnter("MoveToFirstChild"); - bool succ; - - switch (InternalState.Position) + /// + /// Moves the XPathNavigator to the next sibling node of the current node. + /// + /// true if the XPathNavigator is successful moving to the next sibling node; + /// otherwise, false if there are no more siblings or if the XPathNavigator is currently + /// positioned on an attribute node. If false, the position of the XPathNavigator is unchanged. + public override bool MoveToNext() { - case StatePosition.Macro: - if (_macro.Parameters.Length == 0) - { - succ = false; - } - else - { - InternalState.ParameterIndex = 0; - InternalState.Position = StatePosition.Parameter; - succ = true; - } + DebugEnter("MoveToNext"); + bool succ; - break; - case StatePosition.Parameter: - MacroParameter parameter = _macro.Parameters[InternalState.ParameterIndex]; - XPathNavigator? nav = parameter.NavigatorValue; - if (parameter.WrapNavigatorInNodes) - { - InternalState.Position = StatePosition.ParameterNodes; - DebugState(); - succ = true; - } - else if (nav != null) - { - nav = nav.Clone(); // never use the raw parameter's navigator - nav.MoveToFirstChild(); - InternalState.ParameterNavigator = nav; - InternalState.ParameterNavigatorDepth = 0; - InternalState.Position = StatePosition.ParameterNavigator; - DebugState(); - succ = true; - } - else - { - var s = _macro.Parameters[InternalState.ParameterIndex].StringValue; - if (s != null) + switch (_state.Position) + { + case StatePosition.Parameter: + if (_state.ParameterIndex == _macro.Parameters.Length - 1) { - InternalState.Position = StatePosition.ParameterText; - DebugState(); - succ = true; + succ = false; } else { - succ = false; + ++_state.ParameterIndex; + DebugState(); + succ = true; } - } - - break; - case StatePosition.ParameterNavigator: - if (InternalState.ParameterNavigatorDepth == - _macro.Parameters[InternalState.ParameterIndex].MaxNavigatorDepth) - { - succ = false; - } - else - { - // move to first doc child => increment depth, else (property child) do nothing - succ = InternalState.ParameterNavigator?.MoveToFirstChild() ?? false; - if (succ && IsDoc(InternalState.ParameterNavigator)) + break; + case StatePosition.ParameterNavigator: + var wasDoc = IsDoc(_state.ParameterNavigator); + succ = _state.ParameterNavigator?.MoveToNext() ?? false; + if (succ && !wasDoc && IsDoc(_state.ParameterNavigator)) { - ++InternalState.ParameterNavigatorDepth; - DebugState(); + // move to first doc child => increment depth, else (another property child) do nothing + if (_state.ParameterNavigatorDepth == _macro.Parameters[_state.ParameterIndex].MaxNavigatorDepth) + { + _state.ParameterNavigator?.MoveToPrevious(); + succ = false; + } + else + { + ++_state.ParameterNavigatorDepth; + DebugState(); + } } - } - - break; - case StatePosition.ParameterNodes: - if (_macro.Parameters[InternalState.ParameterIndex].NavigatorValue != null) - { - // never use the raw parameter's navigator - InternalState.ParameterNavigator = - _macro.Parameters[InternalState.ParameterIndex].NavigatorValue?.Clone(); - InternalState.Position = StatePosition.ParameterNavigator; - succ = true; - DebugState(); - } - else - { + break; + case StatePosition.ParameterNodes: + case StatePosition.ParameterAttribute: + case StatePosition.ParameterText: + case StatePosition.Macro: + case StatePosition.Root: succ = false; - } + break; + default: + throw new InvalidOperationException("Invalid position."); + } - break; - case StatePosition.ParameterAttribute: - case StatePosition.ParameterText: - succ = false; - break; - case StatePosition.Root: - InternalState.Position = StatePosition.Macro; - DebugState(); - succ = true; - break; - default: - throw new InvalidOperationException("Invalid position."); + DebugReturn(succ); + return succ; } - DebugReturn(succ); - return succ; - } - - /// - /// Moves the XPathNavigator to the first namespace node that matches the XPathNamespaceScope specified. - /// - /// An XPathNamespaceScope value describing the namespace scope. - /// - /// Returns true if the XPathNavigator is successful moving to the first namespace node; - /// otherwise, false. If false, the position of the XPathNavigator is unchanged. - /// - public override bool MoveToFirstNamespace(XPathNamespaceScope namespaceScope) - { - DebugEnter("MoveToFirstNamespace"); - DebugReturn(false); - return false; - } - - /// - /// Moves the XPathNavigator to the next namespace node matching the XPathNamespaceScope specified. - /// - /// An XPathNamespaceScope value describing the namespace scope. - /// - /// Returns true if the XPathNavigator is successful moving to the next namespace node; - /// otherwise, false. If false, the position of the XPathNavigator is unchanged. - /// - public override bool MoveToNextNamespace(XPathNamespaceScope namespaceScope) - { - DebugEnter("MoveToNextNamespace"); - DebugReturn(false); - return false; - } - - /// - /// Moves to the node that has an attribute of type ID whose value matches the specified String. - /// - /// A String representing the ID value of the node to which you want to move. - /// - /// true if the XPathNavigator is successful moving; otherwise, false. - /// If false, the position of the navigator is unchanged. - /// - public override bool MoveToId(string id) - { - DebugEnter("MoveToId"); + /// + /// Moves the XPathNavigator to the previous sibling node of the current node. + /// + /// Returns true if the XPathNavigator is successful moving to the previous sibling node; + /// otherwise, false if there is no previous sibling node or if the XPathNavigator is currently + /// positioned on an attribute node. If false, the position of the XPathNavigator is unchanged. + public override bool MoveToPrevious() + { + DebugEnter("MoveToPrevious"); + bool succ; - // impossible to implement since parameters can contain duplicate fragments of the - // main xml and therefore there can be duplicate unique node identifiers. - DebugReturn("NotImplementedException"); - throw new NotImplementedException(); - } + switch (_state.Position) + { + case StatePosition.Parameter: + if (_state.ParameterIndex == -1) + { + succ = false; + } + else + { + --_state.ParameterIndex; + DebugState(); + succ = true; + } + break; + case StatePosition.ParameterNavigator: + var wasDoc = IsDoc(_state.ParameterNavigator); + succ = _state.ParameterNavigator?.MoveToPrevious() ?? false; + if (succ && wasDoc && !IsDoc(_state.ParameterNavigator)) + { + // move from doc child back to property child => decrement depth + --_state.ParameterNavigatorDepth; + DebugState(); + } + break; + case StatePosition.ParameterAttribute: + case StatePosition.ParameterNodes: + case StatePosition.ParameterText: + case StatePosition.Macro: + case StatePosition.Root: + succ = false; + break; + default: + throw new InvalidOperationException("Invalid position."); + } - /// - /// Moves the XPathNavigator to the next sibling node of the current node. - /// - /// - /// true if the XPathNavigator is successful moving to the next sibling node; - /// otherwise, false if there are no more siblings or if the XPathNavigator is currently - /// positioned on an attribute node. If false, the position of the XPathNavigator is unchanged. - /// - public override bool MoveToNext() - { - DebugEnter("MoveToNext"); - bool succ; + DebugReturn(succ); + return succ; + } - switch (InternalState.Position) + /// + /// Moves the XPathNavigator to the next attribute. + /// + /// Returns true if the XPathNavigator is successful moving to the next attribute; + /// false if there are no more attributes. If false, the position of the XPathNavigator is unchanged. + public override bool MoveToNextAttribute() { - case StatePosition.Parameter: - if (InternalState.ParameterIndex == _macro.Parameters.Length - 1) - { - succ = false; - } - else - { - ++InternalState.ParameterIndex; - DebugState(); - succ = true; - } + DebugEnter("MoveToNextAttribute"); + bool succ; - break; - case StatePosition.ParameterNavigator: - var wasDoc = IsDoc(InternalState.ParameterNavigator); - succ = InternalState.ParameterNavigator?.MoveToNext() ?? false; - if (succ && !wasDoc && IsDoc(InternalState.ParameterNavigator)) - { - // move to first doc child => increment depth, else (another property child) do nothing - if (InternalState.ParameterNavigatorDepth == - _macro.Parameters[InternalState.ParameterIndex].MaxNavigatorDepth) + switch (_state.Position) + { + case StatePosition.ParameterNavigator: + succ = _state.ParameterNavigator?.MoveToNextAttribute() ?? false; + break; + case StatePosition.ParameterAttribute: + if (_state.ParameterAttributeIndex == _macro.Parameters[_state.ParameterIndex].Attributes?.Length - 1) { - InternalState.ParameterNavigator?.MoveToPrevious(); succ = false; } else { - ++InternalState.ParameterNavigatorDepth; + ++_state.ParameterAttributeIndex; DebugState(); + succ = true; } - } + break; + case StatePosition.Parameter: + case StatePosition.ParameterNodes: + case StatePosition.ParameterText: + case StatePosition.Macro: + case StatePosition.Root: + succ = false; + break; + default: + throw new InvalidOperationException("Invalid position."); + } - break; - case StatePosition.ParameterNodes: - case StatePosition.ParameterAttribute: - case StatePosition.ParameterText: - case StatePosition.Macro: - case StatePosition.Root: - succ = false; - break; - default: - throw new InvalidOperationException("Invalid position."); + DebugReturn(succ); + return succ; } - DebugReturn(succ); - return succ; - } - - /// - /// Moves the XPathNavigator to the previous sibling node of the current node. - /// - /// - /// Returns true if the XPathNavigator is successful moving to the previous sibling node; - /// otherwise, false if there is no previous sibling node or if the XPathNavigator is currently - /// positioned on an attribute node. If false, the position of the XPathNavigator is unchanged. - /// - public override bool MoveToPrevious() - { - DebugEnter("MoveToPrevious"); - bool succ; - - switch (InternalState.Position) + /// + /// Moves the XPathNavigator to the parent node of the current node. + /// + /// Returns true if the XPathNavigator is successful moving to the parent node of the current node; + /// otherwise, false. If false, the position of the XPathNavigator is unchanged. + public override bool MoveToParent() { - case StatePosition.Parameter: - if (InternalState.ParameterIndex == -1) - { - succ = false; - } - else - { - --InternalState.ParameterIndex; + DebugEnter("MoveToParent"); + bool succ; + + switch (_state.Position) + { + case StatePosition.Macro: + _state.Position = StatePosition.Root; DebugState(); succ = true; - } - - break; - case StatePosition.ParameterNavigator: - var wasDoc = IsDoc(InternalState.ParameterNavigator); - succ = InternalState.ParameterNavigator?.MoveToPrevious() ?? false; - if (succ && wasDoc && !IsDoc(InternalState.ParameterNavigator)) - { - // move from doc child back to property child => decrement depth - --InternalState.ParameterNavigatorDepth; + break; + case StatePosition.Parameter: + _state.Position = StatePosition.Macro; DebugState(); - } - - break; - case StatePosition.ParameterAttribute: - case StatePosition.ParameterNodes: - case StatePosition.ParameterText: - case StatePosition.Macro: - case StatePosition.Root: - succ = false; - break; - default: - throw new InvalidOperationException("Invalid position."); - } - - DebugReturn(succ); - return succ; - } - - /// - /// Moves the XPathNavigator to the next attribute. - /// - /// - /// Returns true if the XPathNavigator is successful moving to the next attribute; - /// false if there are no more attributes. If false, the position of the XPathNavigator is unchanged. - /// - public override bool MoveToNextAttribute() - { - DebugEnter("MoveToNextAttribute"); - bool succ; - - switch (InternalState.Position) - { - case StatePosition.ParameterNavigator: - succ = InternalState.ParameterNavigator?.MoveToNextAttribute() ?? false; - break; - case StatePosition.ParameterAttribute: - if (InternalState.ParameterAttributeIndex == - _macro.Parameters[InternalState.ParameterIndex].Attributes?.Length - 1) - { - succ = false; - } - else - { - ++InternalState.ParameterAttributeIndex; + succ = true; + break; + case StatePosition.ParameterAttribute: + case StatePosition.ParameterNodes: + _state.Position = StatePosition.Parameter; DebugState(); succ = true; - } - - break; - case StatePosition.Parameter: - case StatePosition.ParameterNodes: - case StatePosition.ParameterText: - case StatePosition.Macro: - case StatePosition.Root: - succ = false; - break; - default: - throw new InvalidOperationException("Invalid position."); - } - - DebugReturn(succ); - return succ; - } - - /// - /// Moves the XPathNavigator to the parent node of the current node. - /// - /// - /// Returns true if the XPathNavigator is successful moving to the parent node of the current node; - /// otherwise, false. If false, the position of the XPathNavigator is unchanged. - /// - public override bool MoveToParent() - { - DebugEnter("MoveToParent"); - bool succ; - - switch (InternalState.Position) - { - case StatePosition.Macro: - InternalState.Position = StatePosition.Root; - DebugState(); - succ = true; - break; - case StatePosition.Parameter: - InternalState.Position = StatePosition.Macro; - DebugState(); - succ = true; - break; - case StatePosition.ParameterAttribute: - case StatePosition.ParameterNodes: - InternalState.Position = StatePosition.Parameter; - DebugState(); - succ = true; - break; - case StatePosition.ParameterNavigator: - var wasDoc = IsDoc(InternalState.ParameterNavigator); - succ = InternalState.ParameterNavigator?.MoveToParent() ?? false; - if (succ) - { - // move from doc child => decrement depth - if (wasDoc && --InternalState.ParameterNavigatorDepth == 0) + break; + case StatePosition.ParameterNavigator: + var wasDoc = IsDoc(_state.ParameterNavigator); + succ = _state.ParameterNavigator?.MoveToParent() ?? false; + if (succ) { - InternalState.Position = StatePosition.Parameter; - InternalState.ParameterNavigator = null; - DebugState(); + // move from doc child => decrement depth + if (wasDoc && --_state.ParameterNavigatorDepth == 0) + { + _state.Position = StatePosition.Parameter; + _state.ParameterNavigator = null; + DebugState(); + } } - } + break; + case StatePosition.ParameterText: + _state.Position = StatePosition.Parameter; + DebugState(); + succ = true; + break; + case StatePosition.Root: + succ = false; + break; + default: + throw new InvalidOperationException("Invalid position."); + } - break; - case StatePosition.ParameterText: - InternalState.Position = StatePosition.Parameter; - DebugState(); - succ = true; - break; - case StatePosition.Root: - succ = false; - break; - default: - throw new InvalidOperationException("Invalid position."); + DebugReturn(succ); + return succ; } - DebugReturn(succ); - return succ; - } - - /// - /// Moves the XPathNavigator to the root node that the current node belongs to. - /// - public override void MoveToRoot() - { - DebugEnter("MoveToRoot"); - - switch (InternalState.Position) + /// + /// Moves the XPathNavigator to the root node that the current node belongs to. + /// + public override void MoveToRoot() { - case StatePosition.ParameterNavigator: - InternalState.ParameterNavigator = null; - InternalState.ParameterNavigatorDepth = -1; - break; - case StatePosition.Parameter: - case StatePosition.ParameterText: - InternalState.ParameterIndex = -1; - break; - case StatePosition.ParameterAttribute: - case StatePosition.ParameterNodes: - case StatePosition.Macro: - case StatePosition.Root: - break; - default: - throw new InvalidOperationException("Invalid position."); - } + DebugEnter("MoveToRoot"); - InternalState.Position = StatePosition.Root; - DebugState(); + switch (_state.Position) + { + case StatePosition.ParameterNavigator: + _state.ParameterNavigator = null; + _state.ParameterNavigatorDepth = -1; + break; + case StatePosition.Parameter: + case StatePosition.ParameterText: + _state.ParameterIndex = -1; + break; + case StatePosition.ParameterAttribute: + case StatePosition.ParameterNodes: + case StatePosition.Macro: + case StatePosition.Root: + break; + default: + throw new InvalidOperationException("Invalid position."); + } - DebugReturn(); - } + _state.Position = StatePosition.Root; + DebugState(); - private static bool IsDoc(XPathNavigator? nav) - { - if (nav is null) - { - return false; + DebugReturn(); } - if (nav.NodeType != XPathNodeType.Element) + /// + /// Gets the base URI for the current node. + /// + public override string BaseURI { - return false; + get { return string.Empty; } } - XPathNavigator clone = nav.Clone(); - if (!clone.MoveToFirstAttribute()) + /// + /// Gets the XmlNameTable of the XPathNavigator. + /// + public override XmlNameTable NameTable { - return false; + get { return _nameTable; } } - do + /// + /// Gets the namespace URI of the current node. + /// + public override string NamespaceURI { - if (clone.Name == "isDoc") - { - return true; - } + get { return string.Empty; } } - while (clone.MoveToNextAttribute()); - - return false; - } - - #region Constructor - - /// - /// Initializes a new instance of the class with macro parameters. - /// - /// The macro parameters. - public MacroNavigator(IEnumerable parameters) - : this(new MacroRoot(parameters), new NameTable(), new State()) - { - } - - /// - /// Initializes a new instance of the class with a macro node, - /// a name table and a state. - /// - /// The macro node. - /// The name table. - /// The state. - /// Privately used for cloning a navigator. - private MacroNavigator(MacroRoot macro, XmlNameTable nameTable, State state) - { - _macro = macro; - _nameTable = nameTable; - InternalState = state; - } - - #endregion - - #region Diagnostics - - // diagnostics code will not be compiled nor called into Release configuration. - // in Debug configuration, uncomment lines in Debug() to write to console or to log. - // - // much of this code is duplicated in each navigator due to conditional compilation -#if DEBUG - private const string Tabs = " "; - private int _tabs; - private readonly int _uid = GetUid(); - private static int _uidg; - private static readonly object Uidl = new(); - private static int GetUid() - { - lock (Uidl) + /// + /// Gets the XPathNodeType of the current node. + /// + public override XPathNodeType NodeType { - return _uidg++; - } - } -#endif - - [Conditional("DEBUG")] - private void DebugEnter(string name) - { -#if DEBUG - Debug(string.Empty); - DebugState(":"); - Debug(name); - _tabs = Math.Min(Tabs.Length, _tabs + 2); -#endif - } - - [Conditional("DEBUG")] - private void DebugCreate(MacroNavigator nav) - { -#if DEBUG - Debug("Create: [MacroNavigator::{0}]", nav._uid); -#endif - } - - [Conditional("DEBUG")] - private void DebugReturn() - { -#if DEBUG -// ReSharper disable IntroduceOptionalParameters.Local - DebugReturn("(void)"); - -// ReSharper restore IntroduceOptionalParameters.Local -#endif - } + get + { + DebugEnter("NodeType"); + XPathNodeType type; - [Conditional("DEBUG")] - private void DebugReturn(bool value) - { -#if DEBUG - DebugReturn(value ? "true" : "false"); -#endif - } + switch (_state.Position) + { + case StatePosition.Macro: + case StatePosition.Parameter: + case StatePosition.ParameterNodes: + type = XPathNodeType.Element; + break; + case StatePosition.ParameterNavigator: + type = _state.ParameterNavigator?.NodeType ?? XPathNodeType.Root; + break; + case StatePosition.ParameterAttribute: + type = XPathNodeType.Attribute; + break; + case StatePosition.ParameterText: + type = XPathNodeType.Text; + break; + case StatePosition.Root: + type = XPathNodeType.Root; + break; + default: + throw new InvalidOperationException("Invalid position."); + } - [Conditional("DEBUG")] - private void DebugReturn(string format, params object[] args) - { -#if DEBUG - Debug("=> " + format, args); - if (_tabs > 0) - { - _tabs -= 2; + DebugReturn("\'{0}\'", type); + return type; + } } -#endif - } - - [Conditional("DEBUG")] - private void DebugState(string s = " =>") - { -#if DEBUG - string position; - switch (InternalState.Position) + /// + /// Gets the namespace prefix associated with the current node. + /// + public override string Prefix { - case StatePosition.Macro: - position = "At macro."; - break; - case StatePosition.Parameter: - position = string.Format( - "At parameter '{0}'.", - _macro.Parameters[InternalState.ParameterIndex].Name); - break; - case StatePosition.ParameterAttribute: - position = string.Format( - "At parameter attribute '{0}/{1}'.", - _macro.Parameters[InternalState.ParameterIndex].Name, - _macro.Parameters[InternalState.ParameterIndex].Attributes?[InternalState.ParameterAttributeIndex].Key); - break; - case StatePosition.ParameterNavigator: - position = string.Format( - "In parameter '{0}{1}' navigator.", - _macro.Parameters[InternalState.ParameterIndex].Name, - _macro.Parameters[InternalState.ParameterIndex].WrapNavigatorInNodes ? "/nodes" : string.Empty); - break; - case StatePosition.ParameterNodes: - - position = string.Format("At parameter '{0}/nodes'.", _macro.Parameters[InternalState.ParameterIndex].Name); - break; - case StatePosition.ParameterText: - position = string.Format("In parameter '{0}' text.", _macro.Parameters[InternalState.ParameterIndex].Name); - break; - case StatePosition.Root: - position = "At root."; - break; - default: - throw new InvalidOperationException("Invalid position."); + get { return string.Empty; } } - Debug("State{0} {1}", s, position); -#endif - } - -#if DEBUG - private void Debug(string format, params object[] args) - { - // remove comments to write - format = "[" + _uid.ToString("00000") + "] " + Tabs.Substring(0, _tabs) + format; -#pragma warning disable 168 - var msg = string.Format(format, args); // unused if not writing, hence #pragma -#pragma warning restore 168 - } -#endif - - #endregion - - #region Macro - - private class MacroRoot - { - public MacroRoot(IEnumerable parameters) => - Parameters = parameters == null ? new MacroParameter[] { } : parameters.ToArray(); + /// + /// Gets the string value of the item. + /// + /// Does not fully behave as per the specs, as we report empty value on root and macro elements, and we start + /// reporting values only on parameter elements. This is because, otherwise, we would might dump the whole database + /// and it probably does not make sense at Umbraco level. + public override string Value + { + get + { + DebugEnter("Value"); + string value; - public MacroParameter[] Parameters { get; } - } + XPathNavigator? nav; + switch (_state.Position) + { + case StatePosition.Parameter: + nav = _macro.Parameters[_state.ParameterIndex].NavigatorValue; + if (nav != null) + { + nav = nav.Clone(); // never use the raw parameter's navigator + nav.MoveToFirstChild(); + value = nav.Value; + } + else + { + var s = _macro.Parameters[_state.ParameterIndex].StringValue; + value = s ?? string.Empty; + } + break; + case StatePosition.ParameterAttribute: + value = _macro.Parameters[_state.ParameterIndex].Attributes?[_state.ParameterAttributeIndex].Value ?? string.Empty; + break; + case StatePosition.ParameterNavigator: + value = _state.ParameterNavigator?.Value ?? string.Empty; + break; + case StatePosition.ParameterNodes: + nav = _macro.Parameters[_state.ParameterIndex].NavigatorValue; + if (nav == null) + { + value = string.Empty; + } + else + { + nav = nav.Clone(); // never use the raw parameter's navigator + nav.MoveToFirstChild(); + value = nav.Value; + } + break; + case StatePosition.ParameterText: + value = _macro.Parameters[_state.ParameterIndex].StringValue ?? string.Empty; + break; + case StatePosition.Macro: + case StatePosition.Root: + value = string.Empty; + break; + default: + throw new InvalidOperationException("Invalid position."); + } - public class MacroParameter - { - // note: assuming we're not thinking about supporting - // XPathIterator in parameters - enough nonsense! - public MacroParameter(string name, string value) - { - Name = name; - StringValue = value; + DebugReturn("\"{0}\"", value); + return value; + } } - public MacroParameter( - string name, - XPathNavigator navigator, - int maxNavigatorDepth = int.MaxValue, - bool wrapNavigatorInNodes = false, - IEnumerable>? attributes = null) + private static bool IsDoc(XPathNavigator? nav) { - Name = name; - MaxNavigatorDepth = maxNavigatorDepth; - WrapNavigatorInNodes = wrapNavigatorInNodes; - if (attributes != null) + if (nav is null) + { + return false; + } + if (nav.NodeType != XPathNodeType.Element) { - KeyValuePair[] a = attributes.ToArray(); - if (a.Length > 0) + return false; + } + + XPathNavigator clone = nav.Clone(); + if (!clone.MoveToFirstAttribute()) + { + return false; + } + + do + { + if (clone.Name == "isDoc") { - Attributes = a; + return true; } } + while (clone.MoveToNextAttribute()); - NavigatorValue = navigator; // should not be empty + return false; } - public string Name { get; } - - public string? StringValue { get; } - - public XPathNavigator? NavigatorValue { get; } - - public int MaxNavigatorDepth { get; } - - public bool WrapNavigatorInNodes { get; } - - public KeyValuePair[]? Attributes { get; } - } - - #endregion - - #region State management - - // the possible state positions - internal enum StatePosition - { - Root, - Macro, - Parameter, - ParameterAttribute, - ParameterText, - ParameterNodes, - ParameterNavigator, - } - - // gets the state - // for unit tests only - internal State InternalState { get; private set; } + #region State management - // represents the XPathNavigator state - internal class State - { - // initialize a new state - private State(StatePosition position) + // the possible state positions + internal enum StatePosition { - Position = position; - ParameterIndex = 0; - ParameterNavigatorDepth = 0; - ParameterAttributeIndex = 0; + Root, + Macro, + Parameter, + ParameterAttribute, + ParameterText, + ParameterNodes, + ParameterNavigator } - // initialize a new state - // used for creating the very first state - public State() - : this(StatePosition.Root) - { - } + // gets the state + // for unit tests only + internal State InternalState { get { return _state; } } - // initialize a clone state - private State(State other) + // represents the XPathNavigator state + internal class State { - Position = other.Position; + public StatePosition Position { get; set; } - ParameterIndex = other.ParameterIndex; - - if (Position == StatePosition.ParameterNavigator) + // initialize a new state + private State(StatePosition position) { - ParameterNavigator = other.ParameterNavigator?.Clone(); - ParameterNavigatorDepth = other.ParameterNavigatorDepth; - ParameterAttributeIndex = other.ParameterAttributeIndex; + Position = position; + ParameterIndex = 0; + ParameterNavigatorDepth = 0; + ParameterAttributeIndex = 0; } - } - public StatePosition Position { get; set; } + // initialize a new state + // used for creating the very first state + public State() + : this(StatePosition.Root) + { } - // the index of the current element - public int ParameterIndex { get; set; } + // initialize a clone state + private State(State other) + { + Position = other.Position; - // the current depth within the element navigator - public int ParameterNavigatorDepth { get; set; } + ParameterIndex = other.ParameterIndex; - // the index of the current element's attribute - public int ParameterAttributeIndex { get; set; } + if (Position == StatePosition.ParameterNavigator) + { + ParameterNavigator = other.ParameterNavigator?.Clone(); + ParameterNavigatorDepth = other.ParameterNavigatorDepth; + ParameterAttributeIndex = other.ParameterAttributeIndex; + } + } - // gets or sets the element navigator - public XPathNavigator? ParameterNavigator { get; set; } + public State Clone() + { + return new State(this); + } - public State Clone() => new State(this); + // the index of the current element + public int ParameterIndex { get; set; } - // gets a value indicating whether this state is at the same position as another one. - public bool IsSamePosition(State other) - { - if (other.ParameterNavigator is null || ParameterNavigator is null) + // the current depth within the element navigator + public int ParameterNavigatorDepth { get; set; } + + // the index of the current element's attribute + public int ParameterAttributeIndex { get; set; } + + // gets or sets the element navigator + public XPathNavigator? ParameterNavigator { get; set; } + + // gets a value indicating whether this state is at the same position as another one. + public bool IsSamePosition(State other) { - return false; + if (other.ParameterNavigator is null || ParameterNavigator is null) + { + return false; + } + return other.Position == Position + && (Position != StatePosition.ParameterNavigator || other.ParameterNavigator.IsSamePosition(ParameterNavigator)) + && other.ParameterIndex == ParameterIndex + && other.ParameterAttributeIndex == ParameterAttributeIndex; } - - return other.Position == Position - && (Position != StatePosition.ParameterNavigator || - other.ParameterNavigator.IsSamePosition(ParameterNavigator)) - && other.ParameterIndex == ParameterIndex - && other.ParameterAttributeIndex == ParameterAttributeIndex; } - } - #endregion + #endregion + } } From f0e7547fc34852fc67ee342905ead5a4e00d6cb0 Mon Sep 17 00:00:00 2001 From: Zeegaan Date: Tue, 7 Jun 2022 15:02:02 +0200 Subject: [PATCH 117/117] Fix after merge Signed-off-by: Zeegaan --- src/Umbraco.Core/Actions/ActionDelete.cs | 49 +- .../Configuration/Models/BasicAuthSettings.cs | 47 +- src/Umbraco.Core/Services/BasicAuthService.cs | 74 +- ...peServiceBaseOfTRepositoryTItemTService.cs | 1627 +++++++++-------- .../Services/IBasicAuthService.cs | 15 +- 5 files changed, 910 insertions(+), 902 deletions(-) diff --git a/src/Umbraco.Core/Actions/ActionDelete.cs b/src/Umbraco.Core/Actions/ActionDelete.cs index 85c9b39dff29..7d9c4e6a03f8 100644 --- a/src/Umbraco.Core/Actions/ActionDelete.cs +++ b/src/Umbraco.Core/Actions/ActionDelete.cs @@ -1,39 +1,38 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -namespace Umbraco.Cms.Core.Actions +namespace Umbraco.Cms.Core.Actions; + +/// +/// This action is invoked when a document, media, member is deleted +/// +public class ActionDelete : IAction { /// - /// This action is invoked when a document, media, member is deleted + /// The unique action alias /// - public class ActionDelete : IAction - { - /// - /// The unique action alias - /// - public const string ActionAlias = "delete"; + public const string ActionAlias = "delete"; - /// - /// The unique action letter - /// - public const char ActionLetter = 'D'; + /// + /// The unique action letter + /// + public const char ActionLetter = 'D'; - /// - public char Letter => ActionLetter; + /// + public char Letter => ActionLetter; - /// - public string Alias => ActionAlias; + /// + public string Alias => ActionAlias; - /// - public string Category => Constants.Conventions.PermissionCategories.ContentCategory; + /// + public string Category => Constants.Conventions.PermissionCategories.ContentCategory; - /// - public string Icon => "delete"; + /// + public string Icon => "delete"; - /// - public bool ShowInNotifier => true; + /// + public bool ShowInNotifier => true; - /// - public bool CanBePermissionAssigned => true; - } + /// + public bool CanBePermissionAssigned => true; } diff --git a/src/Umbraco.Core/Configuration/Models/BasicAuthSettings.cs b/src/Umbraco.Core/Configuration/Models/BasicAuthSettings.cs index aa82f69d2e2d..b743fdcdd281 100644 --- a/src/Umbraco.Core/Configuration/Models/BasicAuthSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/BasicAuthSettings.cs @@ -1,40 +1,37 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System; using System.ComponentModel; -using System.Net; -namespace Umbraco.Cms.Core.Configuration.Models +namespace Umbraco.Cms.Core.Configuration.Models; + +/// +/// Typed configuration options for basic authentication settings. +/// +[UmbracoOptions(Constants.Configuration.ConfigBasicAuth)] +public class BasicAuthSettings { + private const bool StaticEnabled = false; + /// - /// Typed configuration options for basic authentication settings. + /// Gets or sets a value indicating whether to keep the user logged in. /// - [UmbracoOptions(Constants.Configuration.ConfigBasicAuth)] - public class BasicAuthSettings - { - private const bool StaticEnabled = false; - - /// - /// Gets or sets a value indicating whether to keep the user logged in. - /// - [DefaultValue(StaticEnabled)] - public bool Enabled { get; set; } = StaticEnabled; + [DefaultValue(StaticEnabled)] + public bool Enabled { get; set; } = StaticEnabled; - public string[] AllowedIPs { get; set; } = Array.Empty(); - public SharedSecret SharedSecret { get; set; } = new SharedSecret(); + public string[] AllowedIPs { get; set; } = Array.Empty(); + public SharedSecret SharedSecret { get; set; } = new SharedSecret(); - public bool RedirectToLoginPage { get; set; } = false; + public bool RedirectToLoginPage { get; set; } = false; - } +} - public class SharedSecret - { - private const string StaticHeaderName = "X-Authentication-Shared-Secret"; +public class SharedSecret +{ + private const string StaticHeaderName = "X-Authentication-Shared-Secret"; - [DefaultValue(StaticHeaderName)] - public string? HeaderName { get; set; } = StaticHeaderName; - public string? Value { get; set; } - } + [DefaultValue(StaticHeaderName)] + public string? HeaderName { get; set; } = StaticHeaderName; + public string? Value { get; set; } } diff --git a/src/Umbraco.Core/Services/BasicAuthService.cs b/src/Umbraco.Core/Services/BasicAuthService.cs index d81469fac0f2..02f955bad6d3 100644 --- a/src/Umbraco.Core/Services/BasicAuthService.cs +++ b/src/Umbraco.Core/Services/BasicAuthService.cs @@ -1,4 +1,3 @@ -using System; using System.Net; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -6,58 +5,57 @@ using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Web.Common.DependencyInjection; -namespace Umbraco.Cms.Core.Services.Implement +namespace Umbraco.Cms.Core.Services.Implement; + +public class BasicAuthService : IBasicAuthService { - public class BasicAuthService : IBasicAuthService - { - private readonly IIpAddressUtilities _ipAddressUtilities; - private BasicAuthSettings _basicAuthSettings; + private readonly IIpAddressUtilities _ipAddressUtilities; + private BasicAuthSettings _basicAuthSettings; - // Scheduled for removal in v12 - [Obsolete("Please use the contructor that takes an IIpadressUtilities instead")] - public BasicAuthService(IOptionsMonitor optionsMonitor) + // Scheduled for removal in v12 + [Obsolete("Please use the contructor that takes an IIpadressUtilities instead")] + public BasicAuthService(IOptionsMonitor optionsMonitor) : this(optionsMonitor, StaticServiceProvider.Instance.GetRequiredService()) - { - _basicAuthSettings = optionsMonitor.CurrentValue; + { + _basicAuthSettings = optionsMonitor.CurrentValue; - optionsMonitor.OnChange(basicAuthSettings => _basicAuthSettings = basicAuthSettings); - } + optionsMonitor.OnChange(basicAuthSettings => _basicAuthSettings = basicAuthSettings); + } - public BasicAuthService(IOptionsMonitor optionsMonitor, IIpAddressUtilities ipAddressUtilities) - { - _ipAddressUtilities = ipAddressUtilities; - _basicAuthSettings = optionsMonitor.CurrentValue; + public BasicAuthService(IOptionsMonitor optionsMonitor, IIpAddressUtilities ipAddressUtilities) + { + _ipAddressUtilities = ipAddressUtilities; + _basicAuthSettings = optionsMonitor.CurrentValue; - optionsMonitor.OnChange(basicAuthSettings => _basicAuthSettings = basicAuthSettings); - } + optionsMonitor.OnChange(basicAuthSettings => _basicAuthSettings = basicAuthSettings); + } - public bool IsBasicAuthEnabled() => _basicAuthSettings.Enabled; - public bool IsRedirectToLoginPageEnabled() => _basicAuthSettings.RedirectToLoginPage; + public bool IsBasicAuthEnabled() => _basicAuthSettings.Enabled; + public bool IsRedirectToLoginPageEnabled() => _basicAuthSettings.RedirectToLoginPage; - public bool IsIpAllowListed(IPAddress clientIpAddress) + public bool IsIpAllowListed(IPAddress clientIpAddress) + { + foreach (var allowedIpString in _basicAuthSettings.AllowedIPs) { - foreach (var allowedIpString in _basicAuthSettings.AllowedIPs) + if (_ipAddressUtilities.IsAllowListed(clientIpAddress, allowedIpString)) { - if (_ipAddressUtilities.IsAllowListed(clientIpAddress, allowedIpString)) - { - return true; - } + return true; } - - return false; } - public bool HasCorrectSharedSecret(IDictionary headers) - { - var headerName = _basicAuthSettings.SharedSecret.HeaderName; - var sharedSecret = _basicAuthSettings.SharedSecret.Value; + return false; + } - if (string.IsNullOrWhiteSpace(headerName) || string.IsNullOrWhiteSpace(sharedSecret)) - { - return false; - } + public bool HasCorrectSharedSecret(IDictionary headers) + { + var headerName = _basicAuthSettings.SharedSecret.HeaderName; + var sharedSecret = _basicAuthSettings.SharedSecret.Value; - return headers.TryGetValue(headerName, out var value) && value.Equals(sharedSecret); + if (string.IsNullOrWhiteSpace(headerName) || string.IsNullOrWhiteSpace(sharedSecret)) + { + return false; } + + return headers.TryGetValue(headerName, out StringValues value) && value.Equals(sharedSecret); } } diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index ce49a4f9d31c..98a7195fbfb4 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -5,1098 +5,1113 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services.Changes; using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Services +namespace Umbraco.Cms.Core.Services; + +public abstract class ContentTypeServiceBase : ContentTypeServiceBase, IContentTypeBaseService + where TRepository : IContentTypeRepositoryBase + where TItem : class, IContentTypeComposition { - public abstract class ContentTypeServiceBase : ContentTypeServiceBase, IContentTypeBaseService - where TRepository : IContentTypeRepositoryBase - where TItem : class, IContentTypeComposition + private readonly IAuditRepository _auditRepository; + private readonly IEntityContainerRepository _containerRepository; + private readonly IEntityRepository _entityRepository; + private readonly IEventAggregator _eventAggregator; + + protected ContentTypeServiceBase( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + TRepository repository, + IAuditRepository auditRepository, + IEntityContainerRepository containerRepository, + IEntityRepository entityRepository, + IEventAggregator eventAggregator) + : base(provider, loggerFactory, eventMessagesFactory) { - private readonly IAuditRepository _auditRepository; - private readonly IEntityContainerRepository _containerRepository; - private readonly IEntityRepository _entityRepository; - private readonly IEventAggregator _eventAggregator; - - protected ContentTypeServiceBase(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - TRepository repository, IAuditRepository auditRepository, IEntityContainerRepository containerRepository, IEntityRepository entityRepository, - IEventAggregator eventAggregator) - : base(provider, loggerFactory, eventMessagesFactory) - { - Repository = repository; - _auditRepository = auditRepository; - _containerRepository = containerRepository; - _entityRepository = entityRepository; - _eventAggregator = eventAggregator; - } + Repository = repository; + _auditRepository = auditRepository; + _containerRepository = containerRepository; + _entityRepository = entityRepository; + _eventAggregator = eventAggregator; + } - protected TRepository Repository { get; } - protected abstract int[] WriteLockIds { get; } - protected abstract int[] ReadLockIds { get; } + protected TRepository Repository { get; } + protected abstract int[] WriteLockIds { get; } + protected abstract int[] ReadLockIds { get; } - #region Notifications + #region Notifications - protected abstract SavingNotification GetSavingNotification(TItem item, EventMessages eventMessages); - protected abstract SavingNotification GetSavingNotification(IEnumerable items, EventMessages eventMessages); + protected abstract SavingNotification GetSavingNotification(TItem item, EventMessages eventMessages); + protected abstract SavingNotification GetSavingNotification(IEnumerable items, EventMessages eventMessages); - protected abstract SavedNotification GetSavedNotification(TItem item, EventMessages eventMessages); - protected abstract SavedNotification GetSavedNotification(IEnumerable items, EventMessages eventMessages); + protected abstract SavedNotification GetSavedNotification(TItem item, EventMessages eventMessages); + protected abstract SavedNotification GetSavedNotification(IEnumerable items, EventMessages eventMessages); - protected abstract DeletingNotification GetDeletingNotification(TItem item, EventMessages eventMessages); - protected abstract DeletingNotification GetDeletingNotification(IEnumerable items, EventMessages eventMessages); + protected abstract DeletingNotification GetDeletingNotification(TItem item, EventMessages eventMessages); + protected abstract DeletingNotification GetDeletingNotification(IEnumerable items, EventMessages eventMessages); - protected abstract DeletedNotification GetDeletedNotification(IEnumerable items, EventMessages eventMessages); + protected abstract DeletedNotification GetDeletedNotification(IEnumerable items, EventMessages eventMessages); - protected abstract MovingNotification GetMovingNotification(MoveEventInfo moveInfo, EventMessages eventMessages); + protected abstract MovingNotification GetMovingNotification(MoveEventInfo moveInfo, EventMessages eventMessages); - protected abstract MovedNotification GetMovedNotification(IEnumerable> moveInfo, EventMessages eventMessages); + protected abstract MovedNotification GetMovedNotification(IEnumerable> moveInfo, EventMessages eventMessages); - protected abstract ContentTypeChangeNotification GetContentTypeChangedNotification(IEnumerable> changes, EventMessages eventMessages); + protected abstract ContentTypeChangeNotification GetContentTypeChangedNotification(IEnumerable> changes, EventMessages eventMessages); - // This notification is identical to GetTypeChangeNotification, however it needs to be a different notification type because it's published within the transaction - /// The purpose of this notification being published within the transaction is so that listeners can perform database - /// operations from within the same transaction and guarantee data consistency so that if anything goes wrong - /// the entire transaction can be rolled back. This is used by Nucache. - protected abstract ContentTypeRefreshNotification GetContentTypeRefreshedNotification(IEnumerable> changes, EventMessages eventMessages); + // This notification is identical to GetTypeChangeNotification, however it needs to be a different notification type because it's published within the transaction + /// The purpose of this notification being published within the transaction is so that listeners can perform database + /// operations from within the same transaction and guarantee data consistency so that if anything goes wrong + /// the entire transaction can be rolled back. This is used by Nucache. + protected abstract ContentTypeRefreshNotification GetContentTypeRefreshedNotification(IEnumerable> changes, EventMessages eventMessages); - #endregion + #endregion - #region Validation + #region Validation - public Attempt ValidateComposition(TItem? compo) + public Attempt ValidateComposition(TItem? compo) + { + try { - try - { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - scope.ReadLock(ReadLockIds); - ValidateLocked(compo!); - } - return Attempt.Succeed(); - } - catch (InvalidCompositionException ex) + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - return Attempt.Fail(ex.PropertyTypeAliases, ex); + scope.ReadLock(ReadLockIds); + ValidateLocked(compo!); } - } - protected void ValidateLocked(TItem compositionContentType) + return Attempt.Succeed(); + } + catch (InvalidCompositionException ex) { - // performs business-level validation of the composition - // should ensure that it is absolutely safe to save the composition + return Attempt.Fail(ex.PropertyTypeAliases, ex); + } + } - // eg maybe a property has been added, with an alias that's OK (no conflict with ancestors) - // but that cannot be used (conflict with descendants) + protected void ValidateLocked(TItem compositionContentType) + { + // performs business-level validation of the composition + // should ensure that it is absolutely safe to save the composition - var allContentTypes = Repository.GetMany(new int[0]).Cast().ToArray(); + // eg maybe a property has been added, with an alias that's OK (no conflict with ancestors) + // but that cannot be used (conflict with descendants) - var compositionAliases = compositionContentType.CompositionAliases(); - var compositions = allContentTypes.Where(x => compositionAliases.Any(y => x.Alias.Equals(y))); - var propertyTypeAliases = compositionContentType.PropertyTypes.Select(x => x.Alias).ToArray(); - var propertyGroupAliases = compositionContentType.PropertyGroups.ToDictionary(x => x.Alias, x => x.Type, StringComparer.InvariantCultureIgnoreCase); - var indirectReferences = allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == compositionContentType.Id)); - var comparer = new DelegateEqualityComparer((x, y) => x?.Id == y?.Id, x => x.Id); - var dependencies = new HashSet(compositions, comparer); + IContentTypeComposition[] allContentTypes = Repository.GetMany(new int[0]).Cast().ToArray(); - var stack = new Stack(); - foreach (var indirectReference in indirectReferences) - stack.Push(indirectReference); // push indirect references to a stack, so we can add recursively + IEnumerable compositionAliases = compositionContentType.CompositionAliases(); + IEnumerable compositions = allContentTypes.Where(x => compositionAliases.Any(y => x.Alias.Equals(y))); + var propertyTypeAliases = compositionContentType.PropertyTypes.Select(x => x.Alias).ToArray(); + var propertyGroupAliases = compositionContentType.PropertyGroups.ToDictionary(x => x.Alias, x => x.Type, StringComparer.InvariantCultureIgnoreCase); + IEnumerable indirectReferences = allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == compositionContentType.Id)); + var comparer = new DelegateEqualityComparer((x, y) => x?.Id == y?.Id, x => x.Id); + var dependencies = new HashSet(compositions, comparer); - while (stack.Count > 0) - { - var indirectReference = stack.Pop(); - dependencies.Add(indirectReference); + var stack = new Stack(); + foreach (IContentTypeComposition indirectReference in indirectReferences) + { + stack.Push(indirectReference); // push indirect references to a stack, so we can add recursively + } + + while (stack.Count > 0) + { + IContentTypeComposition indirectReference = stack.Pop(); + dependencies.Add(indirectReference); - // get all compositions for the current indirect reference - var directReferences = indirectReference.ContentTypeComposition; - foreach (var directReference in directReferences) + // get all compositions for the current indirect reference + IEnumerable directReferences = indirectReference.ContentTypeComposition; + foreach (IContentTypeComposition directReference in directReferences) + { + if (directReference.Id == compositionContentType.Id || directReference.Alias.Equals(compositionContentType.Alias)) { - if (directReference.Id == compositionContentType.Id || directReference.Alias.Equals(compositionContentType.Alias)) - continue; + continue; + } - dependencies.Add(directReference); + dependencies.Add(directReference); - // a direct reference has compositions of its own - these also need to be taken into account - var directReferenceGraph = directReference.CompositionAliases(); - foreach (var c in allContentTypes.Where(x => directReferenceGraph.Any(y => x.Alias.Equals(y, StringComparison.InvariantCultureIgnoreCase)))) - dependencies.Add(c); + // a direct reference has compositions of its own - these also need to be taken into account + IEnumerable directReferenceGraph = directReference.CompositionAliases(); + foreach (IContentTypeComposition c in allContentTypes.Where(x => directReferenceGraph.Any(y => x.Alias.Equals(y, StringComparison.InvariantCultureIgnoreCase)))) + { + dependencies.Add(c); } - - // recursive lookup of indirect references - foreach (var c in allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == indirectReference.Id))) - stack.Push(c); } - var duplicatePropertyTypeAliases = new List(); - var invalidPropertyGroupAliases = new List(); - - foreach (var dependency in dependencies) + // recursive lookup of indirect references + foreach (IContentTypeComposition c in allContentTypes.Where(x => x.ContentTypeComposition.Any(y => y.Id == indirectReference.Id))) { - if (dependency.Id == compositionContentType.Id) - continue; + stack.Push(c); + } + } - var contentTypeDependency = allContentTypes.FirstOrDefault(x => x.Alias.Equals(dependency.Alias, StringComparison.InvariantCultureIgnoreCase)); - if (contentTypeDependency == null) - continue; + var duplicatePropertyTypeAliases = new List(); + var invalidPropertyGroupAliases = new List(); - duplicatePropertyTypeAliases.AddRange(contentTypeDependency.PropertyTypes.Select(x => x.Alias).Intersect(propertyTypeAliases, StringComparer.InvariantCultureIgnoreCase)); - invalidPropertyGroupAliases.AddRange(contentTypeDependency.PropertyGroups.Where(x => propertyGroupAliases.TryGetValue(x.Alias, out var type) && type != x.Type).Select(x => x.Alias)); + foreach (IContentTypeComposition dependency in dependencies) + { + if (dependency.Id == compositionContentType.Id) + { + continue; } - if (duplicatePropertyTypeAliases.Count > 0 || invalidPropertyGroupAliases.Count > 0) - + IContentTypeComposition? contentTypeDependency = allContentTypes.FirstOrDefault(x => x.Alias.Equals(dependency.Alias, StringComparison.InvariantCultureIgnoreCase)); + if (contentTypeDependency == null) { - throw new InvalidCompositionException(compositionContentType.Alias, null, duplicatePropertyTypeAliases.Distinct().ToArray(), invalidPropertyGroupAliases.Distinct().ToArray()); + continue; } + + duplicatePropertyTypeAliases.AddRange(contentTypeDependency.PropertyTypes.Select(x => x.Alias).Intersect(propertyTypeAliases, StringComparer.InvariantCultureIgnoreCase)); + invalidPropertyGroupAliases.AddRange(contentTypeDependency.PropertyGroups.Where(x => propertyGroupAliases.TryGetValue(x.Alias, out PropertyGroupType type) && type != x.Type).Select(x => x.Alias)); } - #endregion + if (duplicatePropertyTypeAliases.Count > 0 || invalidPropertyGroupAliases.Count > 0) - #region Composition + { + throw new InvalidCompositionException(compositionContentType.Alias, null, duplicatePropertyTypeAliases.Distinct().ToArray(), invalidPropertyGroupAliases.Distinct().ToArray()); + } + } + + #endregion + + #region Composition - internal IEnumerable> ComposeContentTypeChanges(params TItem[] contentTypes) + internal IEnumerable> ComposeContentTypeChanges(params TItem[] contentTypes) + { + // find all content types impacted by the changes, + // - content type alias changed + // - content type property removed, or alias changed + // - content type composition removed (not testing if composition had properties...) + // - content type variation changed + // - property type variation changed + // + // because these are the changes that would impact the raw content data + + // note + // this is meant to run *after* uow.Commit() so must use WasPropertyDirty() everywhere + // instead of IsPropertyDirty() since dirty properties have been reset already + + var changes = new List>(); + + foreach (TItem contentType in contentTypes) { - // find all content types impacted by the changes, - // - content type alias changed - // - content type property removed, or alias changed - // - content type composition removed (not testing if composition had properties...) - // - content type variation changed - // - property type variation changed - // - // because these are the changes that would impact the raw content data - - // note - // this is meant to run *after* uow.Commit() so must use WasPropertyDirty() everywhere - // instead of IsPropertyDirty() since dirty properties have been reset already - - var changes = new List>(); - - foreach (var contentType in contentTypes) + var dirty = (IRememberBeingDirty)contentType; + + // skip new content types + // TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly + var isNewContentType = dirty.WasPropertyDirty("Id"); + if (isNewContentType) { - var dirty = (IRememberBeingDirty)contentType; + AddChange(changes, contentType, ContentTypeChangeTypes.Create); + continue; + } - // skip new content types + // alias change? + var hasAliasChanged = dirty.WasPropertyDirty("Alias"); + + // existing property alias change? + var hasAnyPropertyChangedAlias = contentType.PropertyTypes.Any(propertyType => + { + // skip new properties // TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly - var isNewContentType = dirty.WasPropertyDirty("Id"); - if (isNewContentType) + var isNewProperty = propertyType.WasPropertyDirty("Id"); + if (isNewProperty) { - AddChange(changes, contentType, ContentTypeChangeTypes.Create); - continue; + return false; } // alias change? - var hasAliasChanged = dirty.WasPropertyDirty("Alias"); + return propertyType.WasPropertyDirty("Alias"); + }); - // existing property alias change? - var hasAnyPropertyChangedAlias = contentType.PropertyTypes.Any(propertyType => - { - // skip new properties - // TODO: This used to be WasPropertyDirty("HasIdentity") but i don't think that actually worked for detecting new entities this does seem to work properly - var isNewProperty = propertyType.WasPropertyDirty("Id"); - if (isNewProperty) return false; + // removed properties? + var hasAnyPropertyBeenRemoved = dirty.WasPropertyDirty("HasPropertyTypeBeenRemoved"); - // alias change? - return propertyType.WasPropertyDirty("Alias"); - }); + // removed compositions? + var hasAnyCompositionBeenRemoved = dirty.WasPropertyDirty("HasCompositionTypeBeenRemoved"); - // removed properties? - var hasAnyPropertyBeenRemoved = dirty.WasPropertyDirty("HasPropertyTypeBeenRemoved"); + // variation changed? + var hasContentTypeVariationChanged = dirty.WasPropertyDirty("Variations"); - // removed compositions? - var hasAnyCompositionBeenRemoved = dirty.WasPropertyDirty("HasCompositionTypeBeenRemoved"); + // property variation change? + var hasAnyPropertyVariationChanged = contentType.WasPropertyTypeVariationChanged(); - // variation changed? - var hasContentTypeVariationChanged = dirty.WasPropertyDirty("Variations"); + // main impact on properties? + var hasPropertyMainImpact = hasContentTypeVariationChanged || hasAnyPropertyVariationChanged + || hasAnyCompositionBeenRemoved || hasAnyPropertyBeenRemoved || hasAnyPropertyChangedAlias; - // property variation change? - var hasAnyPropertyVariationChanged = contentType.WasPropertyTypeVariationChanged(); - - // main impact on properties? - var hasPropertyMainImpact = hasContentTypeVariationChanged || hasAnyPropertyVariationChanged - || hasAnyCompositionBeenRemoved || hasAnyPropertyBeenRemoved || hasAnyPropertyChangedAlias; - - if (hasAliasChanged || hasPropertyMainImpact) - { - // add that one, as a main change - AddChange(changes, contentType, ContentTypeChangeTypes.RefreshMain); + if (hasAliasChanged || hasPropertyMainImpact) + { + // add that one, as a main change + AddChange(changes, contentType, ContentTypeChangeTypes.RefreshMain); - if (hasPropertyMainImpact) - foreach (var c in GetComposedOf(contentType.Id)) - AddChange(changes, c, ContentTypeChangeTypes.RefreshMain); - } - else + if (hasPropertyMainImpact) { - // add that one, as an other change - AddChange(changes, contentType, ContentTypeChangeTypes.RefreshOther); + foreach (TItem c in GetComposedOf(contentType.Id)) + { + AddChange(changes, c, ContentTypeChangeTypes.RefreshMain); + } } } - - return changes; - } - - // ensures changes contains no duplicates - private static void AddChange(ICollection> changes, TItem contentType, ContentTypeChangeTypes changeTypes) - { - var change = changes.FirstOrDefault(x => x.Item == contentType); - if (change == null) + else { - changes.Add(new ContentTypeChange(contentType, changeTypes)); - return; + // add that one, as an other change + AddChange(changes, contentType, ContentTypeChangeTypes.RefreshOther); } - change.ChangeTypes |= changeTypes; } - #endregion - - #region Get, Has, Is, Count + return changes; + } - IContentTypeComposition? IContentTypeBaseService.Get(int id) + // ensures changes contains no duplicates + private static void AddChange(ICollection> changes, TItem contentType, ContentTypeChangeTypes changeTypes) + { + ContentTypeChange? change = changes.FirstOrDefault(x => x.Item == contentType); + if (change == null) { - return Get(id); + changes.Add(new ContentTypeChange(contentType, changeTypes)); + return; } - public TItem? Get(int id) - { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - scope.ReadLock(ReadLockIds); - return Repository.Get(id); - } - } + change.ChangeTypes |= changeTypes; + } - public TItem? Get(string alias) - { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - scope.ReadLock(ReadLockIds); - return Repository.Get(alias); - } - } + #endregion - public TItem? Get(Guid id) - { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - scope.ReadLock(ReadLockIds); - return Repository.Get(id); - } - } + #region Get, Has, Is, Count - public IEnumerable GetAll(params int[] ids) - { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - scope.ReadLock(ReadLockIds); - return Repository.GetMany(ids); - } - } + IContentTypeComposition? IContentTypeBaseService.Get(int id) + { + return Get(id); + } - public IEnumerable GetAll(IEnumerable? ids) - { - if (ids is null) - { - return Enumerable.Empty(); - } + public TItem? Get(int id) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + scope.ReadLock(ReadLockIds); + return Repository.Get(id); + } - using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + public TItem? Get(string alias) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + scope.ReadLock(ReadLockIds); + return Repository.Get(alias); + } - { - scope.ReadLock(ReadLockIds); - return Repository.GetMany(ids.ToArray()); - } - } + public TItem? Get(Guid id) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + scope.ReadLock(ReadLockIds); + return Repository.Get(id); + } - public IEnumerable GetChildren(int id) + public IEnumerable GetAll(params int[] ids) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + scope.ReadLock(ReadLockIds); + return Repository.GetMany(ids); + } + + public IEnumerable GetAll(IEnumerable? ids) + { + if (ids is null) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - scope.ReadLock(ReadLockIds); - var query = Query().Where(x => x.ParentId == id); - return Repository.Get(query); - } + return Enumerable.Empty(); } - public IEnumerable GetChildren(Guid id) + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - scope.ReadLock(ReadLockIds); - var found = Get(id); - if (found == null) return Enumerable.Empty(); - var query = Query().Where(x => x.ParentId == found.Id); - return Repository.Get(query); - } + scope.ReadLock(ReadLockIds); + return Repository.GetMany(ids.ToArray()); } + } + + public IEnumerable GetChildren(int id) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + scope.ReadLock(ReadLockIds); + IQuery query = Query().Where(x => x.ParentId == id); + return Repository.Get(query); + } - public bool HasChildren(int id) + public IEnumerable GetChildren(Guid id) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + scope.ReadLock(ReadLockIds); + TItem? found = Get(id); + if (found == null) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - scope.ReadLock(ReadLockIds); - var query = Query().Where(x => x.ParentId == id); - var count = Repository.Count(query); - return count > 0; - } + return Enumerable.Empty(); } - public bool HasChildren(Guid id) + IQuery query = Query().Where(x => x.ParentId == found.Id); + return Repository.Get(query); + } + + public bool HasChildren(int id) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + scope.ReadLock(ReadLockIds); + IQuery query = Query().Where(x => x.ParentId == id); + var count = Repository.Count(query); + return count > 0; + } + + public bool HasChildren(Guid id) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + scope.ReadLock(ReadLockIds); + TItem? found = Get(id); + if (found == null) { - scope.ReadLock(ReadLockIds); - var found = Get(id); - if (found == null) return false; - var query = Query().Where(x => x.ParentId == found.Id); - var count = Repository.Count(query); - return count > 0; + return false; } + + IQuery query = Query().Where(x => x.ParentId == found.Id); + var count = Repository.Count(query); + return count > 0; } + } - /// - /// Given the path of a content item, this will return true if the content item exists underneath a list view content item - /// - /// - /// - public bool HasContainerInPath(string contentPath) + /// + /// Given the path of a content item, this will return true if the content item exists underneath a list view content item + /// + /// + /// + public bool HasContainerInPath(string contentPath) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + // can use same repo for both content and media + return Repository.HasContainerInPath(contentPath); + } + + public bool HasContainerInPath(params int[] ids) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + // can use same repo for both content and media + return Repository.HasContainerInPath(ids); + } + + public IEnumerable GetDescendants(int id, bool andSelf) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + scope.ReadLock(ReadLockIds); + + var descendants = new List(); + if (andSelf) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + TItem? self = Repository.Get(id); + if (self is not null) { - // can use same repo for both content and media - return Repository.HasContainerInPath(contentPath); + descendants.Add(self); } } - public bool HasContainerInPath(params int[] ids) + var ids = new Stack(); + ids.Push(id); + + while (ids.Count > 0) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + var i = ids.Pop(); + IQuery query = Query().Where(x => x.ParentId == i); + TItem[]? result = Repository.Get(query).ToArray(); + + if (result is not null) { - // can use same repo for both content and media - return Repository.HasContainerInPath(ids); + foreach (TItem c in result) + { + descendants.Add(c); + ids.Push(c.Id); + } } } - public IEnumerable GetDescendants(int id, bool andSelf) - { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - scope.ReadLock(ReadLockIds); + return descendants.ToArray(); + } - var descendants = new List(); - if (andSelf) - { - var self = Repository.Get(id); - if (self is not null) - { - descendants.Add(self); - } - } - var ids = new Stack(); - ids.Push(id); + public IEnumerable GetComposedOf(int id, IEnumerable all) => + all.Where(x => x.ContentTypeComposition.Any(y => y.Id == id)); - while (ids.Count > 0) - { - var i = ids.Pop(); - var query = Query().Where(x => x.ParentId == i); - var result = Repository.Get(query).ToArray(); + public IEnumerable GetComposedOf(int id) + { + // GetAll is cheap, repository has a full dataset cache policy + // TODO: still, because it uses the cache, race conditions! + IEnumerable allContentTypes = GetAll(Array.Empty()); + return GetComposedOf(id, allContentTypes); + } - if (result is not null) - { - foreach (var c in result) - { - descendants.Add(c); - ids.Push(c.Id); - } - } - } + public int Count() + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + scope.ReadLock(ReadLockIds); + return Repository.Count(Query()); + } - return descendants.ToArray(); - } - } + public bool HasContentNodes(int id) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + scope.ReadLock(ReadLockIds); + return Repository.HasContentNodes(id); + } - public IEnumerable GetComposedOf(int id, IEnumerable all) - { - return all.Where(x => x.ContentTypeComposition.Any(y => y.Id == id)); + #endregion - } + #region Save - public IEnumerable GetComposedOf(int id) + public void Save(TItem? item, int userId = Constants.Security.SuperUserId) + { + if (item is null) { - // GetAll is cheap, repository has a full dataset cache policy - // TODO: still, because it uses the cache, race conditions! - var allContentTypes = GetAll(Array.Empty()); - return GetComposedOf(id, allContentTypes); + return; } - public int Count() + using ICoreScope scope = ScopeProvider.CreateCoreScope(); + EventMessages eventMessages = EventMessagesFactory.Get(); + SavingNotification savingNotification = GetSavingNotification(item, eventMessages); + if (scope.Notifications.PublishCancelable(savingNotification)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - scope.ReadLock(ReadLockIds); - return Repository.Count(Query()); - } + scope.Complete(); + return; } - public bool HasContentNodes(int id) + if (string.IsNullOrWhiteSpace(item.Name)) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - scope.ReadLock(ReadLockIds); - return Repository.HasContentNodes(id); - } + throw new ArgumentException("Cannot save item with empty name."); } - #endregion - - #region Save - - public void Save(TItem? item, int userId = Cms.Core.Constants.Security.SuperUserId) + if (item.Name != null && item.Name.Length > 255) { - if (item is null) - { - return; - } + throw new InvalidOperationException("Name cannot be more than 255 characters in length."); + } - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + scope.WriteLock(WriteLockIds); - { - EventMessages eventMessages = EventMessagesFactory.Get(); - SavingNotification savingNotification = GetSavingNotification(item, eventMessages); - if (scope.Notifications.PublishCancelable(savingNotification)) - { - scope.Complete(); - return; - } + // validate the DAG transform, within the lock + ValidateLocked(item); // throws if invalid - if (string.IsNullOrWhiteSpace(item.Name)) - { - throw new ArgumentException("Cannot save item with empty name."); - } + item.CreatorId = userId; + if (item.Description == string.Empty) + { + item.Description = null; + } - if (item.Name != null && item.Name.Length > 255) - { - throw new InvalidOperationException("Name cannot be more than 255 characters in length."); - } + Repository.Save(item); // also updates content/media/member items - scope.WriteLock(WriteLockIds); + // figure out impacted content types + ContentTypeChange[] changes = ComposeContentTypeChanges(item).ToArray(); - // validate the DAG transform, within the lock - ValidateLocked(item); // throws if invalid + // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info. + _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages)); - item.CreatorId = userId; - if (item.Description == string.Empty) - { - item.Description = null; - } + scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages)); - Repository.Save(item); // also updates content/media/member items + SavedNotification savedNotification = GetSavedNotification(item, eventMessages); + savedNotification.WithStateFrom(savingNotification); + scope.Notifications.Publish(savedNotification); - // figure out impacted content types - ContentTypeChange[] changes = ComposeContentTypeChanges(item).ToArray(); - - // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info. - _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages)); - - scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages)); + Audit(AuditType.Save, userId, item.Id); + scope.Complete(); + } - SavedNotification savedNotification = GetSavedNotification(item, eventMessages); - savedNotification.WithStateFrom(savingNotification); - scope.Notifications.Publish(savedNotification); + public void Save(IEnumerable items, int userId = Constants.Security.SuperUserId) + { + TItem[] itemsA = items.ToArray(); - Audit(AuditType.Save, userId, item.Id); + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + EventMessages eventMessages = EventMessagesFactory.Get(); + SavingNotification savingNotification = GetSavingNotification(itemsA, eventMessages); + if (scope.Notifications.PublishCancelable(savingNotification)) + { scope.Complete(); + return; } - } - public void Save(IEnumerable items, int userId = Cms.Core.Constants.Security.SuperUserId) - { - TItem[] itemsA = items.ToArray(); + scope.WriteLock(WriteLockIds); - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + // all-or-nothing, validate them all first + foreach (TItem contentType in itemsA) { - EventMessages eventMessages = EventMessagesFactory.Get(); - SavingNotification savingNotification = GetSavingNotification(itemsA, eventMessages); - if (scope.Notifications.PublishCancelable(savingNotification)) + ValidateLocked(contentType); // throws if invalid + } + foreach (TItem contentType in itemsA) + { + contentType.CreatorId = userId; + if (contentType.Description == string.Empty) { - scope.Complete(); - return; + contentType.Description = null; } - scope.WriteLock(WriteLockIds); - - // all-or-nothing, validate them all first - foreach (TItem contentType in itemsA) - { - ValidateLocked(contentType); // throws if invalid - } - foreach (TItem contentType in itemsA) - { - contentType.CreatorId = userId; - if (contentType.Description == string.Empty) - { - contentType.Description = null; - } + Repository.Save(contentType); + } - Repository.Save(contentType); - } + // figure out impacted content types + ContentTypeChange[] changes = ComposeContentTypeChanges(itemsA).ToArray(); - // figure out impacted content types - ContentTypeChange[] changes = ComposeContentTypeChanges(itemsA).ToArray(); + // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info. + _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages)); - // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info. - _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages)); ; + scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages)); - scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages)); + SavedNotification savedNotification = GetSavedNotification(itemsA, eventMessages); + savedNotification.WithStateFrom(savingNotification); + scope.Notifications.Publish(savedNotification); - SavedNotification savedNotification = GetSavedNotification(itemsA, eventMessages); - savedNotification.WithStateFrom(savingNotification); - scope.Notifications.Publish(savedNotification); - - Audit(AuditType.Save, userId, -1); - scope.Complete(); - } + Audit(AuditType.Save, userId, -1); + scope.Complete(); } + } - #endregion + #endregion - #region Delete + #region Delete - public void Delete(TItem item, int userId = Cms.Core.Constants.Security.SuperUserId) + public void Delete(TItem item, int userId = Constants.Security.SuperUserId) + { + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + EventMessages eventMessages = EventMessagesFactory.Get(); + DeletingNotification deletingNotification = GetDeletingNotification(item, eventMessages); + if (scope.Notifications.PublishCancelable(deletingNotification)) { - EventMessages eventMessages = EventMessagesFactory.Get(); - DeletingNotification deletingNotification = GetDeletingNotification(item, eventMessages); - if (scope.Notifications.PublishCancelable(deletingNotification)) - { - scope.Complete(); - return; - } - - scope.WriteLock(WriteLockIds); + scope.Complete(); + return; + } - // all descendants are going to be deleted - TItem[] descendantsAndSelf = GetDescendants(item.Id, true) - .ToArray(); - TItem[] deleted = descendantsAndSelf; + scope.WriteLock(WriteLockIds); - // all impacted (through composition) probably lose some properties - // don't try to be too clever here, just report them all - // do this before anything is deleted - TItem[] changed = descendantsAndSelf.SelectMany(xx => GetComposedOf(xx.Id)) - .Distinct() - .Except(descendantsAndSelf) - .ToArray(); + // all descendants are going to be deleted + TItem[] descendantsAndSelf = GetDescendants(item.Id, true) + .ToArray(); + TItem[] deleted = descendantsAndSelf; + + // all impacted (through composition) probably lose some properties + // don't try to be too clever here, just report them all + // do this before anything is deleted + TItem[] changed = descendantsAndSelf.SelectMany(xx => GetComposedOf(xx.Id)) + .Distinct() + .Except(descendantsAndSelf) + .ToArray(); - // delete content - DeleteItemsOfTypes(descendantsAndSelf.Select(x => x.Id)); + // delete content + DeleteItemsOfTypes(descendantsAndSelf.Select(x => x.Id)); - // Next find all other document types that have a reference to this content type - IEnumerable referenceToAllowedContentTypes = GetAll().Where(q => q.AllowedContentTypes?.Any(p=>p.Id.Value==item.Id) ?? false); - foreach (TItem reference in referenceToAllowedContentTypes) - { - reference.AllowedContentTypes = reference.AllowedContentTypes?.Where(p => p.Id.Value != item.Id); - var changedRef = new List>() { new ContentTypeChange(reference, ContentTypeChangeTypes.RefreshMain) }; - // Fire change event - scope.Notifications.Publish(GetContentTypeChangedNotification(changedRef, eventMessages)); - } + // Next find all other document types that have a reference to this content type + IEnumerable referenceToAllowedContentTypes = GetAll().Where(q => q.AllowedContentTypes?.Any(p=>p.Id.Value==item.Id) ?? false); + foreach (TItem reference in referenceToAllowedContentTypes) + { + reference.AllowedContentTypes = reference.AllowedContentTypes?.Where(p => p.Id.Value != item.Id); + var changedRef = new List>() { new ContentTypeChange(reference, ContentTypeChangeTypes.RefreshMain) }; + // Fire change event + scope.Notifications.Publish(GetContentTypeChangedNotification(changedRef, eventMessages)); + } - // finally delete the content type - // - recursively deletes all descendants - // - deletes all associated property data - // (contents of any descendant type have been deleted but - // contents of any composed (impacted) type remain but - // need to have their property data cleared) - Repository.Delete(item); + // finally delete the content type + // - recursively deletes all descendants + // - deletes all associated property data + // (contents of any descendant type have been deleted but + // contents of any composed (impacted) type remain but + // need to have their property data cleared) + Repository.Delete(item); - ContentTypeChange[] changes = descendantsAndSelf.Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.Remove)) - .Concat(changed.Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther))) - .ToArray(); + ContentTypeChange[] changes = descendantsAndSelf.Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.Remove)) + .Concat(changed.Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther))) + .ToArray(); - // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info. - _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages)); + // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info. + _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages)); - scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages)); + scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages)); - DeletedNotification deletedNotification = GetDeletedNotification(deleted.DistinctBy(x => x.Id), eventMessages); - deletedNotification.WithStateFrom(deletingNotification); - scope.Notifications.Publish(deletedNotification); + DeletedNotification deletedNotification = GetDeletedNotification(deleted.DistinctBy(x => x.Id), eventMessages); + deletedNotification.WithStateFrom(deletingNotification); + scope.Notifications.Publish(deletedNotification); - Audit(AuditType.Delete, userId, item.Id); - scope.Complete(); - } + Audit(AuditType.Delete, userId, item.Id); + scope.Complete(); } + } - public void Delete(IEnumerable items, int userId = Cms.Core.Constants.Security.SuperUserId) - { - TItem[] itemsA = items.ToArray(); + public void Delete(IEnumerable items, int userId = Constants.Security.SuperUserId) + { + TItem[] itemsA = items.ToArray(); - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + EventMessages eventMessages = EventMessagesFactory.Get(); + DeletingNotification deletingNotification = GetDeletingNotification(itemsA, eventMessages); + if (scope.Notifications.PublishCancelable(deletingNotification)) { - EventMessages eventMessages = EventMessagesFactory.Get(); - DeletingNotification deletingNotification = GetDeletingNotification(itemsA, eventMessages); - if (scope.Notifications.PublishCancelable(deletingNotification)) - { - scope.Complete(); - return; - } + scope.Complete(); + return; + } - scope.WriteLock(WriteLockIds); + scope.WriteLock(WriteLockIds); - // all descendants are going to be deleted - TItem[] allDescendantsAndSelf = itemsA.SelectMany(xx => GetDescendants(xx.Id, true)).DistinctBy(x => x.Id).ToArray(); - TItem[] deleted = allDescendantsAndSelf; + // all descendants are going to be deleted + TItem[] allDescendantsAndSelf = itemsA.SelectMany(xx => GetDescendants(xx.Id, true)).DistinctBy(x => x.Id).ToArray(); + TItem[] deleted = allDescendantsAndSelf; - // all impacted (through composition) probably lose some properties - // don't try to be too clever here, just report them all - // do this before anything is deleted - TItem[] changed = allDescendantsAndSelf.SelectMany(x => GetComposedOf(x.Id)) - .Distinct() - .Except(allDescendantsAndSelf) - .ToArray(); + // all impacted (through composition) probably lose some properties + // don't try to be too clever here, just report them all + // do this before anything is deleted + TItem[] changed = allDescendantsAndSelf.SelectMany(x => GetComposedOf(x.Id)) + .Distinct() + .Except(allDescendantsAndSelf) + .ToArray(); - // delete content - DeleteItemsOfTypes(allDescendantsAndSelf.Select(x => x.Id)); + // delete content + DeleteItemsOfTypes(allDescendantsAndSelf.Select(x => x.Id)); - // finally delete the content types - // (see notes in overload) - foreach (TItem item in itemsA) - { - Repository.Delete(item); - } + // finally delete the content types + // (see notes in overload) + foreach (TItem item in itemsA) + { + Repository.Delete(item); + } - ContentTypeChange[] changes = allDescendantsAndSelf.Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.Remove)) - .Concat(changed.Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther))) - .ToArray(); + ContentTypeChange[] changes = allDescendantsAndSelf.Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.Remove)) + .Concat(changed.Select(x => new ContentTypeChange(x, ContentTypeChangeTypes.RefreshMain | ContentTypeChangeTypes.RefreshOther))) + .ToArray(); - // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info. - _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages)); + // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info. + _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages)); - scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages)); + scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages)); - DeletedNotification deletedNotification = GetDeletedNotification(deleted.DistinctBy(x => x.Id), eventMessages); - deletedNotification.WithStateFrom(deletingNotification); - scope.Notifications.Publish(deletedNotification); + DeletedNotification deletedNotification = GetDeletedNotification(deleted.DistinctBy(x => x.Id), eventMessages); + deletedNotification.WithStateFrom(deletingNotification); + scope.Notifications.Publish(deletedNotification); - Audit(AuditType.Delete, userId, -1); - scope.Complete(); - } + Audit(AuditType.Delete, userId, -1); + scope.Complete(); } + } - protected abstract void DeleteItemsOfTypes(IEnumerable typeIds); + protected abstract void DeleteItemsOfTypes(IEnumerable typeIds); - #endregion + #endregion - #region Copy + #region Copy - public TItem Copy(TItem original, string alias, string name, int parentId = -1) + public TItem Copy(TItem original, string alias, string name, int parentId = -1) + { + TItem? parent = null; + if (parentId > 0) { - TItem? parent = null; - if (parentId > 0) + parent = Get(parentId); + if (parent == null) { - parent = Get(parentId); - if (parent == null) - { - throw new InvalidOperationException("Could not find parent with id " + parentId); - } + throw new InvalidOperationException("Could not find parent with id " + parentId); } - return Copy(original, alias, name, parent); } + return Copy(original, alias, name, parent); + } - public TItem Copy(TItem original, string alias, string name, TItem? parent) + public TItem Copy(TItem original, string alias, string name, TItem? parent) + { + if (original == null) { - if (original == null) throw new ArgumentNullException(nameof(original)); - if (alias == null) throw new ArgumentNullException(nameof(alias)); - if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(alias)); - if (parent != null && parent.HasIdentity == false) throw new InvalidOperationException("Parent must have an identity."); - - // this is illegal - //var originalb = (ContentTypeCompositionBase)original; - // but we *know* it has to be a ContentTypeCompositionBase anyways - var originalb = (ContentTypeCompositionBase) (object) original; - var clone = (TItem) (object) originalb.DeepCloneWithResetIdentities(alias); - - clone.Name = name; - - //remove all composition that is not it's current alias - var compositionAliases = clone.CompositionAliases().Except(new[] { alias }).ToList(); - foreach (var a in compositionAliases) - { - clone.RemoveContentType(a); - } + throw new ArgumentNullException(nameof(original)); + } - //if a parent is specified set it's composition and parent - if (parent != null) - { - //add a new parent composition - clone.AddContentType(parent); - clone.ParentId = parent.Id; - } - else - { - //set to root - clone.ParentId = -1; - } + if (alias == null) + { + throw new ArgumentNullException(nameof(alias)); + } + + if (string.IsNullOrWhiteSpace(alias)) + { + throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(alias)); + } - Save(clone); - return clone; + if (parent != null && parent.HasIdentity == false) + { + throw new InvalidOperationException("Parent must have an identity."); } - public Attempt?> Copy(TItem copying, int containerId) + // this is illegal + //var originalb = (ContentTypeCompositionBase)original; + // but we *know* it has to be a ContentTypeCompositionBase anyways + var originalb = (ContentTypeCompositionBase) (object) original; + var clone = (TItem) (object) originalb.DeepCloneWithResetIdentities(alias); + + clone.Name = name; + + //remove all composition that is not it's current alias + var compositionAliases = clone.CompositionAliases().Except(new[] { alias }).ToList(); + foreach (var a in compositionAliases) { - var eventMessages = EventMessagesFactory.Get(); + clone.RemoveContentType(a); + } - TItem copy; - using (var scope = ScopeProvider.CreateCoreScope()) - { - scope.WriteLock(WriteLockIds); + //if a parent is specified set it's composition and parent + if (parent != null) + { + //add a new parent composition + clone.AddContentType(parent); + clone.ParentId = parent.Id; + } + else + { + //set to root + clone.ParentId = -1; + } + + Save(clone); + return clone; + } + + public Attempt?> Copy(TItem copying, int containerId) + { + EventMessages eventMessages = EventMessagesFactory.Get(); - try + TItem copy; + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + scope.WriteLock(WriteLockIds); + + try + { + if (containerId > 0) { - if (containerId > 0) + EntityContainer? container = _containerRepository?.Get(containerId); + if (container == null) { - var container = _containerRepository?.Get(containerId); - if (container == null) - throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); // causes rollback + throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); // causes rollback } - var alias = Repository.GetUniqueAlias(copying.Alias); + } + + var alias = Repository.GetUniqueAlias(copying.Alias); - // this is illegal - //var copyingb = (ContentTypeCompositionBase) copying; - // but we *know* it has to be a ContentTypeCompositionBase anyways - var copyingb = (ContentTypeCompositionBase) (object)copying; - copy = (TItem) (object) copyingb.DeepCloneWithResetIdentities(alias); + // this is illegal + //var copyingb = (ContentTypeCompositionBase) copying; + // but we *know* it has to be a ContentTypeCompositionBase anyways + var copyingb = (ContentTypeCompositionBase) (object)copying; + copy = (TItem) (object) copyingb.DeepCloneWithResetIdentities(alias); - copy.Name = copy.Name + " (copy)"; // might not be unique + copy.Name = copy.Name + " (copy)"; // might not be unique - // if it has a parent, and the parent is a content type, unplug composition - // all other compositions remain in place in the copied content type - if (copy.ParentId > 0) + // if it has a parent, and the parent is a content type, unplug composition + // all other compositions remain in place in the copied content type + if (copy.ParentId > 0) + { + TItem? parent = Repository.Get(copy.ParentId); + if (parent != null) { - var parent = Repository.Get(copy.ParentId); - if (parent != null) - copy.RemoveContentType(parent.Alias); + copy.RemoveContentType(parent.Alias); } + } - copy.ParentId = containerId; + copy.ParentId = containerId; - SavingNotification savingNotification = GetSavingNotification(copy, eventMessages); - if (scope.Notifications.PublishCancelable(savingNotification)) - { - scope.Complete(); - return OperationResult.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, eventMessages, copy); - } + SavingNotification savingNotification = GetSavingNotification(copy, eventMessages); + if (scope.Notifications.PublishCancelable(savingNotification)) + { + scope.Complete(); + return OperationResult.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, eventMessages, copy); + } - Repository.Save(copy); + Repository.Save(copy); - ContentTypeChange[] changes = ComposeContentTypeChanges(copy).ToArray(); + ContentTypeChange[] changes = ComposeContentTypeChanges(copy).ToArray(); - _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages)); - scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages)); + _eventAggregator.Publish(GetContentTypeRefreshedNotification(changes, eventMessages)); + scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages)); - SavedNotification savedNotification = GetSavedNotification(copy, eventMessages); - savedNotification.WithStateFrom(savingNotification); - scope.Notifications.Publish(savedNotification); + SavedNotification savedNotification = GetSavedNotification(copy, eventMessages); + savedNotification.WithStateFrom(savingNotification); + scope.Notifications.Publish(savedNotification); - scope.Complete(); - } - catch (DataOperationException ex) - { - return OperationResult.Attempt.Fail(ex.Operation, eventMessages); // causes rollback - } + scope.Complete(); + } + catch (DataOperationException ex) + { + return OperationResult.Attempt.Fail(ex.Operation, eventMessages); // causes rollback } - - return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, eventMessages, copy); } - #endregion + return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, eventMessages, copy); + } - #region Move + #endregion - public Attempt?> Move(TItem moving, int containerId) - { - EventMessages eventMessages = EventMessagesFactory.Get(); + #region Move - var moveInfo = new List>(); - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + public Attempt?> Move(TItem moving, int containerId) + { + EventMessages eventMessages = EventMessagesFactory.Get(); + + var moveInfo = new List>(); + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + var moveEventInfo = new MoveEventInfo(moving, moving.Path, containerId); + MovingNotification movingNotification = GetMovingNotification(moveEventInfo, eventMessages); + if (scope.Notifications.PublishCancelable(movingNotification)) { - var moveEventInfo = new MoveEventInfo(moving, moving.Path, containerId); - MovingNotification movingNotification = GetMovingNotification(moveEventInfo, eventMessages); - if (scope.Notifications.PublishCancelable(movingNotification)) - { - scope.Complete(); - return OperationResult.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, eventMessages); - } + scope.Complete(); + return OperationResult.Attempt.Fail(MoveOperationStatusType.FailedCancelledByEvent, eventMessages); + } - scope.WriteLock(WriteLockIds); // also for containers + scope.WriteLock(WriteLockIds); // also for containers - try + try + { + EntityContainer? container = null; + if (containerId > 0) { - EntityContainer? container = null; - if (containerId > 0) + container = _containerRepository?.Get(containerId); + if (container == null) { - container = _containerRepository?.Get(containerId); - if (container == null) - throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); // causes rollback + throw new DataOperationException(MoveOperationStatusType.FailedParentNotFound); // causes rollback } - moveInfo.AddRange(Repository.Move(moving, container!)); - scope.Complete(); - } - catch (DataOperationException ex) - { - scope.Complete(); - return OperationResult.Attempt.Fail(ex.Operation, eventMessages); } - - // note: not raising any Changed event here because moving a content type under another container - // has no impact on the published content types - would be entirely different if we were to support - // moving a content type under another content type. - MovedNotification movedNotification = GetMovedNotification(moveInfo, eventMessages); - movedNotification.WithStateFrom(movingNotification); - scope.Notifications.Publish(movedNotification); + moveInfo.AddRange(Repository.Move(moving, container!)); + scope.Complete(); + } + catch (DataOperationException ex) + { + scope.Complete(); + return OperationResult.Attempt.Fail(ex.Operation, eventMessages); } - return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, eventMessages); + // note: not raising any Changed event here because moving a content type under another container + // has no impact on the published content types - would be entirely different if we were to support + // moving a content type under another content type. + MovedNotification movedNotification = GetMovedNotification(moveInfo, eventMessages); + movedNotification.WithStateFrom(movingNotification); + scope.Notifications.Publish(movedNotification); } - #endregion + return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, eventMessages); + } + + #endregion + + #region Containers - #region Containers + protected abstract Guid ContainedObjectType { get; } - protected abstract Guid ContainedObjectType { get; } + protected Guid ContainerObjectType => EntityContainer.GetContainerObjectType(ContainedObjectType); - protected Guid ContainerObjectType => EntityContainer.GetContainerObjectType(ContainedObjectType); + public Attempt?> CreateContainer(int parentId, Guid key, string name, int userId = Constants.Security.SuperUserId) + { + EventMessages eventMessages = EventMessagesFactory.Get(); + using ICoreScope scope = ScopeProvider.CreateCoreScope(); + scope.WriteLock(WriteLockIds); // also for containers - public Attempt?> CreateContainer(int parentId, Guid key, string name, int userId = Cms.Core.Constants.Security.SuperUserId) + try { - EventMessages eventMessages = EventMessagesFactory.Get(); - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + var container = new EntityContainer(ContainedObjectType) { - scope.WriteLock(WriteLockIds); // also for containers + Name = name, + ParentId = parentId, + CreatorId = userId, + Key = key + }; + + var savingNotification = new EntityContainerSavingNotification(container, eventMessages); + if (scope.Notifications.PublishCancelable(savingNotification)) + { + scope.Complete(); + return OperationResult.Attempt.Cancel(eventMessages, container); + } - try - { - var container = new EntityContainer(ContainedObjectType) - { - Name = name, - ParentId = parentId, - CreatorId = userId, - Key = key - }; - - var savingNotification = new EntityContainerSavingNotification(container, eventMessages); - if (scope.Notifications.PublishCancelable(savingNotification)) - { - scope.Complete(); - return OperationResult.Attempt.Cancel(eventMessages, container); - } + _containerRepository?.Save(container); + scope.Complete(); - _containerRepository?.Save(container); - scope.Complete(); + var savedNotification = new EntityContainerSavedNotification(container, eventMessages); + savedNotification.WithStateFrom(savingNotification); + scope.Notifications.Publish(savedNotification); + // TODO: Audit trail ? - var savedNotification = new EntityContainerSavedNotification(container, eventMessages); - savedNotification.WithStateFrom(savingNotification); - scope.Notifications.Publish(savedNotification); - // TODO: Audit trail ? + return OperationResult.Attempt.Succeed(eventMessages, container); + } + catch (Exception ex) + { + scope.Complete(); + return OperationResult.Attempt.Fail(OperationResultType.FailedCancelledByEvent, eventMessages, ex); + } + } - return OperationResult.Attempt.Succeed(eventMessages, container); - } - catch (Exception ex) - { - scope.Complete(); - return OperationResult.Attempt.Fail(OperationResultType.FailedCancelledByEvent, eventMessages, ex); - } - } + public Attempt SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId) + { + EventMessages eventMessages = EventMessagesFactory.Get(); + + Guid containerObjectType = ContainerObjectType; + if (container.ContainerObjectType != containerObjectType) + { + var ex = new InvalidOperationException("Not a container of the proper type."); + return OperationResult.Attempt.Fail(eventMessages, ex); } - public Attempt SaveContainer(EntityContainer container, int userId = Cms.Core.Constants.Security.SuperUserId) + if (container.HasIdentity && container.IsPropertyDirty("ParentId")) { - EventMessages eventMessages = EventMessagesFactory.Get(); + var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); + return OperationResult.Attempt.Fail(eventMessages, ex); + } - Guid containerObjectType = ContainerObjectType; - if (container.ContainerObjectType != containerObjectType) + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + { + var savingNotification = new EntityContainerSavingNotification(container, eventMessages); + if (scope.Notifications.PublishCancelable(savingNotification)) { - var ex = new InvalidOperationException("Not a container of the proper type."); - return OperationResult.Attempt.Fail(eventMessages, ex); + scope.Complete(); + return OperationResult.Attempt.Cancel(eventMessages); } - if (container.HasIdentity && container.IsPropertyDirty("ParentId")) - { - var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); - return OperationResult.Attempt.Fail(eventMessages, ex); - } + scope.WriteLock(WriteLockIds); // also for containers - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) - { - var savingNotification = new EntityContainerSavingNotification(container, eventMessages); - if (scope.Notifications.PublishCancelable(savingNotification)) - { - scope.Complete(); - return OperationResult.Attempt.Cancel(eventMessages); - } + _containerRepository?.Save(container); + scope.Complete(); - scope.WriteLock(WriteLockIds); // also for containers + var savedNotification = new EntityContainerSavedNotification(container, eventMessages); + savedNotification.WithStateFrom(savingNotification); + scope.Notifications.Publish(savedNotification); + } - _containerRepository?.Save(container); - scope.Complete(); + // TODO: Audit trail ? - var savedNotification = new EntityContainerSavedNotification(container, eventMessages); - savedNotification.WithStateFrom(savingNotification); - scope.Notifications.Publish(savedNotification); - } + return OperationResult.Attempt.Succeed(eventMessages); + } - // TODO: Audit trail ? + public EntityContainer? GetContainer(int containerId) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + scope.ReadLock(ReadLockIds); // also for containers - return OperationResult.Attempt.Succeed(eventMessages); - } + return _containerRepository.Get(containerId); + } - public EntityContainer? GetContainer(int containerId) - { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - scope.ReadLock(ReadLockIds); // also for containers + public EntityContainer? GetContainer(Guid containerId) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + scope.ReadLock(ReadLockIds); // also for containers - return _containerRepository.Get(containerId); - } - } + return _containerRepository.Get(containerId); + } - public EntityContainer? GetContainer(Guid containerId) - { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - scope.ReadLock(ReadLockIds); // also for containers + public IEnumerable GetContainers(int[] containerIds) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + scope.ReadLock(ReadLockIds); // also for containers - return _containerRepository.Get(containerId); - } - } + return _containerRepository.GetMany(containerIds); + } - public IEnumerable GetContainers(int[] containerIds) - { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - scope.ReadLock(ReadLockIds); // also for containers + public IEnumerable GetContainers(TItem item) + { + var ancestorIds = item.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) + .Select(x => int.TryParse(x, NumberStyles.Integer, CultureInfo.InvariantCulture, out var asInt) ? asInt : int.MinValue) + .Where(x => x != int.MinValue && x != item.Id) + .ToArray(); - return _containerRepository.GetMany(containerIds); - } - } + return GetContainers(ancestorIds); + } - public IEnumerable GetContainers(TItem item) - { - var ancestorIds = item.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) - .Select(x => int.TryParse(x, NumberStyles.Integer, CultureInfo.InvariantCulture, out var asInt) ? asInt : int.MinValue) - .Where(x => x != int.MinValue && x != item.Id) - .ToArray(); + public IEnumerable GetContainers(string name, int level) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + scope.ReadLock(ReadLockIds); // also for containers + + return _containerRepository.Get(name, level); + } - return GetContainers(ancestorIds); + public Attempt DeleteContainer(int containerId, int userId = Constants.Security.SuperUserId) + { + EventMessages eventMessages = EventMessagesFactory.Get(); + using ICoreScope scope = ScopeProvider.CreateCoreScope(); + scope.WriteLock(WriteLockIds); // also for containers + + EntityContainer? container = _containerRepository?.Get(containerId); + if (container == null) + { + return OperationResult.Attempt.NoOperation(eventMessages); } - public IEnumerable GetContainers(string name, int level) + // 'container' here does not know about its children, so we need + // to get it again from the entity repository, as a light entity + IEntitySlim? entity = _entityRepository.Get(container.Id); + if (entity?.HasChildren ?? false) { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - scope.ReadLock(ReadLockIds); // also for containers + scope.Complete(); + return Attempt.Fail(new OperationResult(OperationResultType.FailedCannot, eventMessages)); + } - return _containerRepository.Get(name, level); - } + var deletingNotification = new EntityContainerDeletingNotification(container, eventMessages); + if (scope.Notifications.PublishCancelable(deletingNotification)) + { + scope.Complete(); + return Attempt.Fail(new OperationResult(OperationResultType.FailedCancelledByEvent, eventMessages)); } - public Attempt DeleteContainer(int containerId, int userId = Cms.Core.Constants.Security.SuperUserId) + _containerRepository?.Delete(container); + scope.Complete(); + + var deletedNotification = new EntityContainerDeletedNotification(container, eventMessages); + deletedNotification.WithStateFrom(deletingNotification); + scope.Notifications.Publish(deletedNotification); + + return OperationResult.Attempt.Succeed(eventMessages); + // TODO: Audit trail ? + } + + public Attempt?> RenameContainer(int id, string name, int userId = Constants.Security.SuperUserId) + { + EventMessages eventMessages = EventMessagesFactory.Get(); + using (ICoreScope scope = ScopeProvider.CreateCoreScope()) { - EventMessages eventMessages = EventMessagesFactory.Get(); - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + scope.WriteLock(WriteLockIds); // also for containers + + try { - scope.WriteLock(WriteLockIds); // also for containers + EntityContainer? container = _containerRepository?.Get(id); - EntityContainer? container = _containerRepository?.Get(containerId); + //throw if null, this will be caught by the catch and a failed returned if (container == null) { - return OperationResult.Attempt.NoOperation(eventMessages); + throw new InvalidOperationException("No container found with id " + id); } - // 'container' here does not know about its children, so we need - // to get it again from the entity repository, as a light entity - IEntitySlim? entity = _entityRepository.Get(container.Id); - if (entity?.HasChildren ?? false) - { - scope.Complete(); - return Attempt.Fail(new OperationResult(OperationResultType.FailedCannot, eventMessages)); - } + container.Name = name; - var deletingNotification = new EntityContainerDeletingNotification(container, eventMessages); - if (scope.Notifications.PublishCancelable(deletingNotification)) + var renamingNotification = new EntityContainerRenamingNotification(container, eventMessages); + if (scope.Notifications.PublishCancelable(renamingNotification)) { scope.Complete(); - return Attempt.Fail(new OperationResult(OperationResultType.FailedCancelledByEvent, eventMessages)); + return OperationResult.Attempt.Cancel(eventMessages); } - _containerRepository?.Delete(container); + _containerRepository?.Save(container); scope.Complete(); - var deletedNotification = new EntityContainerDeletedNotification(container, eventMessages); - deletedNotification.WithStateFrom(deletingNotification); - scope.Notifications.Publish(deletedNotification); + var renamedNotification = new EntityContainerRenamedNotification(container, eventMessages); + renamedNotification.WithStateFrom(renamingNotification); + scope.Notifications.Publish(renamedNotification); - return OperationResult.Attempt.Succeed(eventMessages); - // TODO: Audit trail ? + return OperationResult.Attempt.Succeed(OperationResultType.Success, eventMessages, container); } - } - - public Attempt?> RenameContainer(int id, string name, int userId = Cms.Core.Constants.Security.SuperUserId) - { - EventMessages eventMessages = EventMessagesFactory.Get(); - using (ICoreScope scope = ScopeProvider.CreateCoreScope()) + catch (Exception ex) { - scope.WriteLock(WriteLockIds); // also for containers - - try - { - EntityContainer? container = _containerRepository?.Get(id); - - //throw if null, this will be caught by the catch and a failed returned - if (container == null) - { - throw new InvalidOperationException("No container found with id " + id); - } - - container.Name = name; - - var renamingNotification = new EntityContainerRenamingNotification(container, eventMessages); - if (scope.Notifications.PublishCancelable(renamingNotification)) - { - scope.Complete(); - return OperationResult.Attempt.Cancel(eventMessages); - } - - _containerRepository?.Save(container); - scope.Complete(); - - var renamedNotification = new EntityContainerRenamedNotification(container, eventMessages); - renamedNotification.WithStateFrom(renamingNotification); - scope.Notifications.Publish(renamedNotification); - - return OperationResult.Attempt.Succeed(OperationResultType.Success, eventMessages, container); - } - catch (Exception ex) - { - return OperationResult.Attempt.Fail(eventMessages, ex); - } + return OperationResult.Attempt.Fail(eventMessages, ex); } } + } - #endregion + #endregion - #region Audit + #region Audit - private void Audit(AuditType type, int userId, int objectId) - { - _auditRepository.Save(new AuditItem(objectId, type, userId, - ObjectTypes.GetUmbracoObjectType(ContainedObjectType).GetName())); - } + private void Audit(AuditType type, int userId, int objectId) + { + _auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetUmbracoObjectType(ContainedObjectType).GetName())); + } - #endregion + #endregion - } } diff --git a/src/Umbraco.Core/Services/IBasicAuthService.cs b/src/Umbraco.Core/Services/IBasicAuthService.cs index 82e48e11802f..c371376f8552 100644 --- a/src/Umbraco.Core/Services/IBasicAuthService.cs +++ b/src/Umbraco.Core/Services/IBasicAuthService.cs @@ -1,14 +1,13 @@ using System.Net; using Microsoft.Extensions.Primitives; -namespace Umbraco.Cms.Core.Services +namespace Umbraco.Cms.Core.Services; + +public interface IBasicAuthService { - public interface IBasicAuthService - { - bool IsBasicAuthEnabled(); - bool IsIpAllowListed(IPAddress clientIpAddress); - bool HasCorrectSharedSecret(IDictionary headers) => false; + bool IsBasicAuthEnabled(); + bool IsIpAllowListed(IPAddress clientIpAddress); + bool HasCorrectSharedSecret(IDictionary headers) => false; - bool IsRedirectToLoginPageEnabled() => false; - } + bool IsRedirectToLoginPageEnabled() => false; }